@@ -40,6 +40,14 @@ def sorting_name(n: str) -> str:
4040def 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