@@ -86,6 +86,32 @@ def find_object(obj: Path) -> Optional[Union[Path, list[os.DirEntry[str]]]]:
8686 return None
8787 return sorted (list (dirents .values ()), key = lambda x : sorting_name (x .name ))
8888
89+ def parse_arguments (
90+ text : bytes , arg_start : int , opening : int , closing : int
91+ ) -> tuple [list [bytes ], int ]:
92+ args = []
93+ depth = 1
94+ next_index = arg_start + 1
95+ while next_index < len (text ):
96+ if text [next_index ] == closing :
97+ depth -= 1
98+ if depth == 0 :
99+ args .append (text [arg_start + 1 : next_index ])
100+ break
101+ elif text [next_index ] == opening :
102+ depth += 1
103+ elif (
104+ depth == 1
105+ and text [next_index ] == ord (b"," )
106+ and text [next_index - 1 ] != ord (b"\\ " )
107+ ):
108+ args .append (text [arg_start + 1 : next_index ])
109+ arg_start = next_index
110+ next_index += 1
111+ if next_index == len (text ):
112+ raise ValueError (f"missing '{ closing } '" )
113+ return args , next_index + 1
114+
89115 def expand_bytes (
90116 text : bytes ,
91117 base_file : Path ,
@@ -149,14 +175,16 @@ def read_file(
149175
150176 # Set up macros
151177 macros : dict [bytes , Callable [..., bytes ]] = {}
152- macros [b"path" ] = lambda _args : bytes (base_file )
153- macros [b"realpath" ] = lambda _args : bytes (file_path )
178+ macros [b"path" ] = lambda _args , _external_args : bytes (base_file )
179+ macros [b"realpath" ] = lambda _args , _external_args : bytes (file_path )
154180 macros [b"outputpath" ] = (
155- lambda _args : bytes (output_path ) if output_path is not None else b""
181+ lambda _args , _external_args : bytes (output_path )
182+ if output_path is not None
183+ else b""
156184 )
157185
158186 def get_included_file (
159- command_name : str , args : list [bytes ]
187+ command_name : str , args : list [bytes ], external_args : list [ bytes ]
160188 ) -> tuple [Optional [Path ], bytes ]:
161189 debug (f"${ command_name } {{{ b',' .join (args )} }}" )
162190 if len (args ) < 1 :
@@ -166,8 +194,8 @@ def get_included_file(
166194 file = get_file (Path (os .fsdecode (args [0 ])))
167195 return read_file (file , args [1 :])
168196
169- def include (args : list [bytes ]) -> bytes :
170- file , contents = get_included_file ("include" , args )
197+ def include (args : list [bytes ], external_args : list [ bytes ] ) -> bytes :
198+ file , contents = get_included_file ("include" , args , external_args )
171199 return strip_final_newline (
172200 inner_expand (
173201 contents , expand_stack + [file ] if file is not None else []
@@ -182,19 +210,26 @@ def paste(args: list[bytes]) -> bytes:
182210
183211 macros [b"paste" ] = paste
184212
185- def do_macro (macro : bytes , args : list [bytes ]) -> bytes :
186- debug (f"do_macro { macro } { args } " )
213+ def expand_args (args : list [bytes ]) -> list [bytes ]:
187214 expanded_args : list [bytes ] = []
188215 for a in args :
189216 # Unescape escaped commas
190217 debug (f"escaped arg { a } " )
191218 unescaped_arg = re .sub (rb"\\," , b"," , a )
192219 debug (f"unescaped arg { unescaped_arg } " )
193220 expanded_args .append (do_expand (unescaped_arg ))
221+ return expanded_args
222+
223+ def do_macro (
224+ macro : bytes , args : list [bytes ], external_args : list [bytes ]
225+ ) -> bytes :
226+ debug (f"do_macro { macro } { args } { external_args } " )
227+ expanded_args = expand_args (args )
228+ expanded_external_args = expand_args (external_args )
194229 if macro not in macros :
195230 decoded_macro = macro .decode ("iso-8859-1" )
196231 raise ValueError (f"no such macro '${ decoded_macro } '" )
197- return macros [macro ](expanded_args )
232+ return macros [macro ](expanded_args , expanded_external_args )
198233
199234 debug ("do_match" )
200235 startpos = 0
@@ -206,38 +241,23 @@ def do_macro(macro: bytes, args: list[bytes]) -> bytes:
206241 debug (f"match: { res } { res .end ()} " )
207242 escaped = res [1 ]
208243 name = res [2 ]
209- arg_start = res .end ()
210- startpos = arg_start
244+ startpos = res .end ()
211245 args = []
212- # Parse arguments, respecting nested commands
213- if arg_start < len (expanded ) and expanded [arg_start ] == ord (b"{" ):
214- depth = 1
215- next_index = arg_start + 1
216- while next_index < len (expanded ):
217- if expanded [next_index ] == ord (b"}" ):
218- depth -= 1
219- if depth == 0 :
220- args .append (expanded [arg_start + 1 : next_index ])
221- break
222- elif expanded [next_index ] == ord (b"{" ):
223- depth += 1
224- elif (
225- depth == 1
226- and expanded [next_index ] == ord (b"," )
227- and expanded [next_index - 1 ] != ord (b"\\ " )
228- ):
229- args .append (expanded [arg_start + 1 : next_index ])
230- arg_start = next_index
231- next_index += 1
232- if next_index == len (expanded ):
233- raise ValueError ("missing close brace" )
234- startpos = next_index + 1
246+ # Parse external program arguments
247+ if startpos < len (expanded ) and expanded [startpos ] == ord (b"(" ):
248+ external_args , startpos = parse_arguments (
249+ expanded , startpos , ord ("(" ), ord (")" )
250+ )
251+ if startpos < len (expanded ) and expanded [startpos ] == ord (b"{" ):
252+ args , startpos = parse_arguments (
253+ expanded , startpos , ord ("{" ), ord ("}" )
254+ )
235255 if escaped != b"" :
236256 # Just remove the leading '\'
237257 args_string = b"{" + b"," .join (args ) + b"}"
238258 output = b"$" + name + (args_string if len (args ) > 0 else b"" )
239259 else :
240- output = do_macro (name , args )
260+ output = do_macro (name , args , external_args )
241261 expanded = expanded [: res .start ()] + output + expanded [startpos :]
242262 # Update search position to restart matching after output of macro
243263 startpos = res .start () + len (output )
0 commit comments