Skip to content

Commit af5ba31

Browse files
committed
Add docstrings, return types and some clarifications
1 parent 1a60e1f commit af5ba31

File tree

1 file changed

+106
-15
lines changed

1 file changed

+106
-15
lines changed

nancy/__init__.py

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ def sorting_name(n: str) -> str:
4040
def expand(
4141
inputs: list[Path], output_path: Path, build_path: Optional[Path] = Path()
4242
) -> None:
43+
'''
44+
Args:
45+
inputs (list[Path]): a list of filesystem `Path`s to overlay to make
46+
an abstract input tree
47+
output_path (Path): the filesystem `Path` of the output directory
48+
build_path (Optional[Path]): the subtree of `inputs` to process.
49+
Defaults to the whole tree.
50+
'''
4351
if len(inputs) == 0:
4452
raise ValueError("at least one input must be given")
4553
if build_path is None:
@@ -52,15 +60,17 @@ def expand(
5260
if not root.is_dir():
5361
raise ValueError(f"input '{root}' is not a directory")
5462

55-
# Find the first file or directory with relative path `object` in the
56-
# input tree, scanning the roots from left to right.
57-
# If the result is a file, return its path.
58-
# If the result is a directory, return its contents as a list of
59-
# os.DirEntry, obtained by similarly scanning the tree from left to
60-
# right.
61-
# If something neither a file nor directory is found, raise an error.
62-
# If no result is found, return `None`.
6363
def find_object(obj: Path) -> Optional[Union[Path, list[os.DirEntry[str]]]]:
64+
'''
65+
Find the first file or directory with relative path `obj` in the
66+
input tree, scanning the roots from left to right.
67+
If the result is a file, return its path.
68+
If the result is a directory, return its contents as a list of
69+
os.DirEntry, obtained by similarly scanning the tree from left to
70+
right.
71+
If something neither a file nor directory is found, raise an error.
72+
If no result is found, return `None`.
73+
'''
6474
debug(f"find_object {obj} {inputs}")
6575
objects = [root / obj for root in inputs]
6676
dirs = []
@@ -85,8 +95,22 @@ def find_object(obj: Path) -> Optional[Union[Path, list[os.DirEntry[str]]]]:
8595
def parse_arguments(
8696
text: bytes, arg_start: int, initial_closing: int
8797
) -> tuple[list[bytes], int]:
98+
'''
99+
Parse macro arguments from `text[arg_start + 1:]` until the first
100+
unpaired occurrence of `initial_closing`.
101+
102+
Args:
103+
text (bytes): the string to parse
104+
arg_start (int): the start position
105+
initial_closing (int): the ASCII code of the closing bracket
106+
107+
Returns:
108+
tuple[list[bytes], int]:
109+
- list of arguments
110+
- position within `text` of the character after closing delimiter
111+
'''
88112
args = []
89-
closing = [initial_closing]
113+
closing = [initial_closing] # Stack of expected close brackets
90114
next_index = arg_start + 1
91115
while next_index < len(text):
92116
if text[next_index] == closing[-1]:
@@ -116,7 +140,32 @@ def expand_bytes(
116140
file_path: Path,
117141
output_path: Optional[Path] = None,
118142
) -> bytes:
143+
'''
144+
Expand `text`.
145+
146+
Args:
147+
text (bytes): the text to expand
148+
base_file (Path): the filesystem input `Path`
149+
file_path (Path): the `inputs`-relative `Path`
150+
output_file (Path): the filesystem output `Path`
151+
152+
Returns:
153+
bytes
154+
'''
155+
119156
def inner_expand(text: bytes, expand_stack: list[Path]) -> bytes:
157+
'''
158+
Expand `text`.
159+
160+
Args:
161+
text (bytes): the text to expand
162+
expand_stack (list[Path]): a list of `inputs`-relative `Path`s
163+
which are currently being expanded. This is used to avoid
164+
infinite loops.
165+
166+
Returns:
167+
bytes
168+
'''
120169
debug(f"inner_expand {text} {expand_stack}")
121170

122171
def find_on_path(start_path: Path, file: Path) -> Optional[Path]:
@@ -173,6 +222,8 @@ def read_file(file: Path) -> tuple[Optional[Path], bytes]:
173222
return (found_file, output)
174223

175224
def do_expand(text: bytes) -> bytes:
225+
debug("do_expand")
226+
176227
# Set up macros
177228
macros: dict[bytes, Callable[..., bytes]] = {}
178229
macros[b"path"] = lambda _args, _external_args: bytes(base_file)
@@ -185,16 +236,26 @@ def do_expand(text: bytes) -> bytes:
185236

186237
def filter_bytes(
187238
input: Optional[bytes],
188-
external_args: list[bytes],
189-
):
190-
exe_name = Path(os.fsdecode(external_args[0]))
239+
external_command: list[bytes],
240+
) -> bytes:
241+
'''
242+
Run `external_command` passing `input` on stdin.
243+
244+
Args:
245+
input (Optional[bytes]): passed to `stdin`
246+
external_command (list[bytes]): command and arguments
247+
248+
Returns:
249+
bytes: stdout of the command
250+
'''
251+
exe_name = Path(os.fsdecode(external_command[0]))
191252
exe_path = find_on_path(base_file.parent, exe_name)
192253
if exe_path is None:
193254
exe_path_str = shutil.which(exe_name)
194255
if exe_path_str is None:
195256
raise ValueError(f"cannot find program '{exe_name}'")
196257
exe_path = Path(exe_path_str)
197-
exe_args = external_args[1:]
258+
exe_args = external_command[1:]
198259
debug(f"Running {exe_path} {b' '.join(exe_args)}")
199260
try:
200261
res = subprocess.run(
@@ -213,7 +274,10 @@ def command_to_str(
213274
name: bytes,
214275
args: Optional[list[bytes]],
215276
input: Optional[bytes],
216-
):
277+
) -> bytes:
278+
'''
279+
Reconstitute a macro call from its parsed form.
280+
'''
217281
args_string = (
218282
b"(" + b",".join(args) + b")" if args is not None else b""
219283
)
@@ -305,7 +369,6 @@ def do_macro(
305369
raise ValueError(f"no such macro '${decoded_macro}'")
306370
return macros[macro](expanded_args, expanded_input)
307371

308-
debug("do_match")
309372
startpos = 0
310373
expanded = text
311374
while True:
@@ -344,10 +407,28 @@ def do_macro(
344407
return inner_expand(text, [file_path])
345408

346409
def expand_file(base_file: Path, file_path: Path, output_file: Path) -> bytes:
410+
'''
411+
Expand a file given its filesystem `Path`.
412+
413+
Args:
414+
base_file (Path): the filesystem input `Path`
415+
file_path (Path): the `inputs`-relative `Path`
416+
output_file (Path): the filesystem output `Path`
417+
'''
347418
debug(f"expand_file {base_file} on path {file_path} to {output_file}")
348419
return expand_bytes(file_path.read_bytes(), base_file, file_path, output_file)
349420

350421
def get_output_path(base_file: Path, file_path: Path) -> Path:
422+
'''
423+
Compute the output path of an input file.
424+
425+
Args:
426+
base_file (Path): the filesystem input `Path`
427+
file_path (Path): the `inputs`-relative `Path`
428+
429+
Returns:
430+
Path
431+
'''
351432
relpath = base_file.relative_to(build_path)
352433
output_file = relpath
353434
if output_file.name != "":
@@ -360,6 +441,13 @@ def get_output_path(base_file: Path, file_path: Path) -> Path:
360441
return output_path / output_file
361442

362443
def process_file(base_file: Path, file_path: Path) -> None:
444+
'''
445+
Expand, copy or ignore a single file.
446+
447+
Args:
448+
base_file (Path): the filesystem `Path`
449+
file_path (Path): the `inputs`-relative `Path`
450+
'''
363451
output_file = get_output_path(base_file, file_path)
364452
debug(f"Processing file '{file_path}'")
365453
if re.search(TEMPLATE_REGEX, file_path.name):
@@ -379,6 +467,9 @@ def process_file(base_file: Path, file_path: Path) -> None:
379467
shutil.copyfile(file_path, output_file)
380468

381469
def process_path(obj: Path) -> None:
470+
'''
471+
Recursively scan `obj` and pass every file to `process_file`.
472+
'''
382473
dirent = find_object(obj)
383474
if dirent is None:
384475
raise ValueError(f"'{obj}' matches no path in the inputs")

0 commit comments

Comments
 (0)