diff --git a/styx/compiler/compile/outputs.html b/styx/compiler/compile/outputs.html index b22e794..800d22d 100644 --- a/styx/compiler/compile/outputs.html +++ b/styx/compiler/compile/outputs.html @@ -115,130 +115,135 @@
88def generate_output_building( - 89 func: PyFunc, - 90 func_scope: Scope, - 91 symbol_execution: str, - 92 symbol_output_class: str, - 93 symbol_return_var: str, - 94 outputs: list[WithSymbol[OutputArgument]], - 95 inputs: list[WithSymbol[InputArgument]], - 96) -> None: - 97 """Generate the output building code.""" - 98 py_rstrip_fun = func_scope.add_or_dodge("_rstrip") - 99 if any([out.data.stripped_file_extensions is not None for out in outputs]): -100 func.body.extend([ -101 f"def {py_rstrip_fun}(s, r):", -102 *indent([ -103 "for postfix in r:", -104 *indent([ -105 "if s.endswith(postfix):", -106 *indent(["return s[: -len(postfix)]"]), -107 ]), -108 "return s", -109 ]), -110 ]) -111 -112 func.body.append(f"{symbol_return_var} = {symbol_output_class}(") -113 -114 # Set root output path -115 func.body.extend(indent([f'root={symbol_execution}.output_file("."),'])) +diff --git a/styx/compiler/compile/subcommand.html b/styx/compiler/compile/subcommand.html index 49e7370..6b98f7d 100644 --- a/styx/compiler/compile/subcommand.html +++ b/styx/compiler/compile/subcommand.html @@ -64,171 +64,173 @@93def generate_output_building( + 94 func: PyFunc, + 95 func_scope: Scope, + 96 symbol_execution: str, + 97 symbol_output_class: str, + 98 symbol_return_var: str, + 99 outputs: list[WithSymbol[OutputArgument]], +100 inputs: list[WithSymbol[InputArgument]], +101) -> None: +102 """Generate the output building code.""" +103 py_rstrip_fun = func_scope.add_or_dodge("_rstrip") +104 if any([out.data.stripped_file_extensions is not None for out in outputs]): +105 func.body.extend([ +106 f"def {py_rstrip_fun}(s, r):", +107 *indent([ +108 "for postfix in r:", +109 *indent([ +110 "if s.endswith(postfix):", +111 *indent(["return s[: -len(postfix)]"]), +112 ]), +113 "return s", +114 ]), +115 ]) 116 -117 for out in outputs: -118 strip_extensions = out.data.stripped_file_extensions is not None -119 s_optional = ", optional=True" if out.data.optional else "" -120 if out.data.path_template is not None: -121 path_template = out.data.path_template -122 -123 input_dependencies = _find_output_dependencies(out, inputs) -124 -125 if len(input_dependencies) == 0: -126 # No substitutions needed -127 func.body.extend( -128 indent([f"{out.symbol}={symbol_execution}.output_file(f{enquote(path_template)}{s_optional}),"]) -129 ) -130 else: -131 for input_ in input_dependencies: -132 substitute = input_.symbol -133 -134 if input_.data.type.is_list: -135 raise Exception(f"Output path template replacements cannot be lists. ({input_.data.name})") -136 -137 if input_.data.type.primitive == InputTypePrimitive.File: -138 # Just use the name of the file -139 # This is commonly used when output files 'inherit' the name of an input file -140 substitute = f"pathlib.Path({substitute}).name" -141 elif (input_.data.type.primitive == InputTypePrimitive.Number) or ( -142 input_.data.type.primitive == InputTypePrimitive.Integer -143 ): -144 # Convert to string -145 substitute = f"str({substitute})" -146 elif input_.data.type.primitive != InputTypePrimitive.String: -147 raise Exception( -148 f"Unsupported input type {input_.data.type.primitive} " -149 f"for output path template of '{out.data.name}'." -150 ) -151 -152 if strip_extensions: -153 exts = as_py_literal(out.data.stripped_file_extensions, "'") -154 substitute = f"{py_rstrip_fun}({substitute}, {exts})" -155 -156 path_template = path_template.replace(input_.data.template_key, enbrace(substitute)) -157 -158 resolved_output = f"{symbol_execution}.output_file(f{enquote(path_template)}{s_optional})" -159 -160 if any([input_.data.type.is_optional for input_ in input_dependencies]): -161 # Codegen: Condition: Is any variable in the segment set by the user? -162 condition = [codegen_var_is_set_by_user(i) for i in input_dependencies] -163 resolved_output = f"{resolved_output} if {' and '.join(condition)} else None" +117 func.body.append(f"{symbol_return_var} = {symbol_output_class}(") +118 +119 # Set root output path +120 func.body.extend(indent([f'root={symbol_execution}.output_file("."),'])) +121 +122 for out in outputs: +123 strip_extensions = out.data.stripped_file_extensions is not None +124 s_optional = ", optional=True" if out.data.optional else "" +125 if out.data.path_template is not None: +126 path_template = out.data.path_template +127 +128 input_dependencies = _find_output_dependencies(out, inputs) +129 +130 if len(input_dependencies) == 0: +131 # No substitutions needed +132 func.body.extend( +133 indent([f"{out.symbol}={symbol_execution}.output_file(f{enquote(path_template)}{s_optional}),"]) +134 ) +135 else: +136 for input_ in input_dependencies: +137 substitute = input_.symbol +138 +139 if input_.data.type.is_list: +140 raise Exception(f"Output path template replacements cannot be lists. ({input_.data.name})") +141 +142 if input_.data.type.primitive == InputTypePrimitive.File: +143 # Just use the name of the file +144 # This is commonly used when output files 'inherit' the name of an input file +145 substitute = f"pathlib.Path({substitute}).name" +146 elif (input_.data.type.primitive == InputTypePrimitive.Number) or ( +147 input_.data.type.primitive == InputTypePrimitive.Integer +148 ): +149 # Convert to string +150 substitute = f"str({substitute})" +151 elif input_.data.type.primitive != InputTypePrimitive.String: +152 raise Exception( +153 f"Unsupported input type {input_.data.type.primitive} " +154 f"for output path template of '{out.data.name}'." +155 ) +156 +157 if strip_extensions: +158 exts = as_py_literal(out.data.stripped_file_extensions, "'") +159 substitute = f"{py_rstrip_fun}({substitute}, {exts})" +160 +161 path_template = path_template.replace(input_.data.template_key, enbrace(substitute)) +162 +163 resolved_output = f"{symbol_execution}.output_file(f{enquote(path_template)}{s_optional})" 164 -165 func.body.extend(indent([f"{out.symbol}={resolved_output},"])) -166 else: -167 raise NotImplementedError -168 -169 for input_ in inputs: -170 if (input_.data.type.primitive == InputTypePrimitive.SubCommand) or ( -171 input_.data.type.primitive == InputTypePrimitive.SubCommandUnion -172 ): -173 if input_.data.type.is_list: -174 func.body.extend( -175 indent([ -176 f"{input_.symbol}=" -177 f"[{input_.symbol}.outputs({symbol_execution}) for {input_.symbol} in {input_.symbol}]," -178 ]) -179 ) -180 else: -181 func.body.extend(indent([f"{input_.symbol}={input_.symbol}.outputs({symbol_execution}),"])) -182 -183 func.body.extend([")"]) +165 if any([input_.data.type.is_optional for input_ in input_dependencies]): +166 # Codegen: Condition: Is any variable in the segment set by the user? +167 condition = [codegen_var_is_set_by_user(i) for i in input_dependencies] +168 resolved_output = f"{resolved_output} if {' and '.join(condition)} else None" +169 +170 func.body.extend(indent([f"{out.symbol}={resolved_output},"])) +171 else: +172 raise NotImplementedError +173 +174 for input_ in inputs: +175 if (input_.data.type.primitive == InputTypePrimitive.SubCommand) or ( +176 input_.data.type.primitive == InputTypePrimitive.SubCommandUnion +177 ): +178 if input_.data.type.is_list: +179 func.body.extend( +180 indent([ +181 f"{input_.symbol}=" +182 f"[{input_.symbol}.outputs({symbol_execution}) for {input_.symbol} in {input_.symbol}]," +183 ]) +184 ) +185 else: +186 func.body.extend(indent([f"{input_.symbol}={input_.symbol}.outputs({symbol_execution}),"])) +187 +188 func.body.extend([")"])8from styx.pycodegen.utils import python_pascalize, python_snakify 9 10 - 11def _sub_command_class_name(sub_command: SubCommand) -> str: + 11def _sub_command_class_name(symbol_module: str, sub_command: SubCommand) -> str: 12 """Return the name of the sub-command class.""" - 13 return python_pascalize(f"{sub_command.name}") - 14 + 13 # Prefix the sub-command name with the module name so its likely unique across modules. + 14 return python_pascalize(f"{symbol_module}_{sub_command.name}") 15 - 16def _sub_command_output_class_name(sub_command: SubCommand) -> str: - 17 """Return the name of the sub-command output class.""" - 18 return python_pascalize(f"{sub_command.name}_Outputs") - 19 - 20 - 21def _generate_sub_command( - 22 module: PyModule, - 23 scope_module: Scope, - 24 symbols: SharedSymbols, - 25 sub_command: SubCommand, - 26 outputs: list[WithSymbol[OutputArgument]], - 27 inputs: list[WithSymbol[InputArgument]], - 28 aliases: dict[str, str], - 29 sub_command_output_class_aliases: dict[str, str], - 30) -> tuple[str, str]: - 31 """Generate the static output class definition.""" - 32 class_name = scope_module.add_or_dodge(_sub_command_class_name(sub_command)) - 33 output_class_name = scope_module.add_or_dodge(_sub_command_output_class_name(sub_command)) - 34 - 35 module.exports.append(class_name) - 36 sub_command_class = PyDataClass( - 37 name=class_name, - 38 docstring=sub_command.doc, - 39 ) - 40 # generate arguments - 41 sub_command_class.fields.extend(build_input_arguments(inputs, aliases)) - 42 - 43 # generate run method - 44 run_method = PyFunc( - 45 name="run", - 46 docstring_body="Build command line arguments. This method is called by the main command.", - 47 args=[ - 48 PyArg(name="self", type=None, default=None, docstring="The sub-command object."), - 49 PyArg(name="execution", type="Execution", default=None, docstring="The execution object."), - 50 ], - 51 return_type="list[str]", - 52 body=[ - 53 "cargs = []", - 54 ], - 55 ) - 56 inputs_self = [WithSymbol(i.data, f"self.{i.symbol}") for i in inputs] - 57 - 58 generate_constraint_checks(run_method, sub_command.group_constraints, inputs_self) + 16 + 17def _sub_command_output_class_name(symbol_module: str, sub_command: SubCommand) -> str: + 18 """Return the name of the sub-command output class.""" + 19 # Prefix the sub-command name with the module name so its likely unique across modules. + 20 return python_pascalize(f"{symbol_module}_{sub_command.name}_Outputs") + 21 + 22 + 23def _generate_sub_command( + 24 module: PyModule, + 25 scope_module: Scope, + 26 symbols: SharedSymbols, + 27 sub_command: SubCommand, + 28 outputs: list[WithSymbol[OutputArgument]], + 29 inputs: list[WithSymbol[InputArgument]], + 30 aliases: dict[str, str], + 31 sub_command_output_class_aliases: dict[str, str], + 32) -> tuple[str, str]: + 33 """Generate the static output class definition.""" + 34 class_name = scope_module.add_or_dodge(_sub_command_class_name(symbols.function, sub_command)) + 35 output_class_name = scope_module.add_or_dodge(_sub_command_output_class_name(symbols.function, sub_command)) + 36 + 37 module.exports.append(class_name) + 38 sub_command_class = PyDataClass( + 39 name=class_name, + 40 docstring=sub_command.doc, + 41 ) + 42 # generate arguments + 43 sub_command_class.fields.extend(build_input_arguments(inputs, aliases)) + 44 + 45 # generate run method + 46 run_method = PyFunc( + 47 name="run", + 48 docstring_body="Build command line arguments. This method is called by the main command.", + 49 args=[ + 50 PyArg(name="self", type=None, default=None, docstring="The sub-command object."), + 51 PyArg(name="execution", type="Execution", default=None, docstring="The execution object."), + 52 ], + 53 return_type="list[str]", + 54 body=[ + 55 "cargs = []", + 56 ], + 57 ) + 58 inputs_self = [WithSymbol(i.data, f"self.{i.symbol}") for i in inputs] 59 - 60 generate_command_line_args_building(sub_command.input_command_line_template, symbols, run_method, inputs_self) - 61 run_method.body.extend([ - 62 "return cargs", - 63 ]) - 64 sub_command_class.methods.append(run_method) - 65 - 66 # Outputs method + 60 generate_constraint_checks(run_method, sub_command.group_constraints, inputs_self) + 61 + 62 generate_command_line_args_building(sub_command.input_command_line_template, symbols, run_method, inputs_self) + 63 run_method.body.extend([ + 64 "return cargs", + 65 ]) + 66 sub_command_class.methods.append(run_method) 67 - 68 outputs_method = PyFunc( - 69 name="outputs", - 70 docstring_body="Collect output file paths.", - 71 return_type=output_class_name, - 72 return_descr=f"NamedTuple of outputs (described in `{output_class_name}`).", - 73 args=[ - 74 PyArg(name="self", type=None, default=None, docstring="The sub-command object."), - 75 PyArg(name="execution", type="Execution", default=None, docstring="The execution object."), - 76 ], - 77 body=[], - 78 ) - 79 generate_outputs_class( - 80 module, - 81 output_class_name, - 82 class_name + ".run", - 83 outputs, - 84 inputs_self, - 85 sub_command_output_class_aliases, - 86 ) - 87 module.exports.append(output_class_name) - 88 generate_output_building(outputs_method, Scope(), symbols.execution, output_class_name, "ret", outputs, inputs_self) - 89 outputs_method.body.extend(["return ret"]) - 90 sub_command_class.methods.append(outputs_method) - 91 - 92 module.header.extend(blank_before(sub_command_class.generate(), 2)) - 93 if "import dataclasses" not in module.imports: - 94 module.imports.append("import dataclasses") - 95 - 96 return class_name, output_class_name + 68 # Outputs method + 69 + 70 outputs_method = PyFunc( + 71 name="outputs", + 72 docstring_body="Collect output file paths.", + 73 return_type=output_class_name, + 74 return_descr=f"NamedTuple of outputs (described in `{output_class_name}`).", + 75 args=[ + 76 PyArg(name="self", type=None, default=None, docstring="The sub-command object."), + 77 PyArg(name="execution", type="Execution", default=None, docstring="The execution object."), + 78 ], + 79 body=[], + 80 ) + 81 generate_outputs_class( + 82 module, + 83 output_class_name, + 84 class_name + ".run", + 85 outputs, + 86 inputs_self, + 87 sub_command_output_class_aliases, + 88 ) + 89 module.exports.append(output_class_name) + 90 generate_output_building(outputs_method, Scope(), symbols.execution, output_class_name, "ret", outputs, inputs_self) + 91 outputs_method.body.extend(["return ret"]) + 92 sub_command_class.methods.append(outputs_method) + 93 + 94 module.header.extend(blank_before(sub_command_class.generate(), 2)) + 95 if "import dataclasses" not in module.imports: + 96 module.imports.append("import dataclasses") 97 - 98 - 99def generate_sub_command_classes( -100 module: PyModule, -101 symbols: SharedSymbols, -102 command: SubCommand, -103 scope_module: Scope, -104) -> tuple[dict[str, str], dict[str, str], list[WithSymbol[InputArgument]]]: -105 """Build Python function arguments from input arguments.""" -106 # internal_id -> class_name -107 aliases: dict[str, str] = {} -108 # subcommand.internal_id -> subcommand.outputs() class name -109 sub_command_output_class_aliases: dict[str, str] = {} -110 -111 inputs_scope = Scope(parent=scope_module) -112 outputs_scope = Scope(parent=scope_module) -113 -114 # Input symbols -115 inputs: list[WithSymbol[InputArgument]] = [] -116 for i in command.inputs: -117 py_symbol = inputs_scope.add_or_dodge(python_snakify(i.name)) -118 inputs.append(WithSymbol(i, py_symbol)) -119 -120 for input_ in inputs: -121 if input_.data.type.primitive == InputTypePrimitive.SubCommand: -122 assert input_.data.sub_command is not None -123 sub_command = input_.data.sub_command -124 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( -125 module, symbols, sub_command, inputs_scope -126 ) -127 aliases.update(sub_aliases) -128 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) -129 -130 sub_outputs = [] -131 for output in sub_command.outputs: -132 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) -133 sub_outputs.append(WithSymbol(output, py_symbol)) -134 -135 sub_command_type, sub_command_output_type = _generate_sub_command( -136 module, -137 scope_module, -138 symbols, -139 sub_command, -140 sub_outputs, -141 sub_inputs, -142 aliases, -143 sub_command_output_class_aliases, -144 ) -145 aliases[sub_command.internal_id] = sub_command_type -146 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type -147 -148 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: -149 assert input_.data.sub_command_union is not None -150 for sub_command in input_.data.sub_command_union: -151 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( -152 module, symbols, sub_command, inputs_scope -153 ) -154 aliases.update(sub_aliases) -155 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) -156 -157 sub_outputs = [] -158 for output in sub_command.outputs: -159 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) -160 sub_outputs.append(WithSymbol(output, py_symbol)) -161 -162 sub_command_type, sub_command_output_type = _generate_sub_command( -163 module, -164 scope_module, -165 symbols, -166 sub_command, -167 sub_outputs, -168 sub_inputs, -169 aliases, -170 sub_command_output_class_aliases, -171 ) -172 aliases[sub_command.internal_id] = sub_command_type -173 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type -174 -175 return aliases, sub_command_output_class_aliases, inputs + 98 return class_name, output_class_name + 99 +100 +101def generate_sub_command_classes( +102 module: PyModule, +103 symbols: SharedSymbols, +104 command: SubCommand, +105 scope_module: Scope, +106) -> tuple[dict[str, str], dict[str, str], list[WithSymbol[InputArgument]]]: +107 """Build Python function arguments from input arguments.""" +108 # internal_id -> class_name +109 aliases: dict[str, str] = {} +110 # subcommand.internal_id -> subcommand.outputs() class name +111 sub_command_output_class_aliases: dict[str, str] = {} +112 +113 inputs_scope = Scope(parent=scope_module) +114 outputs_scope = Scope(parent=scope_module) +115 +116 # Input symbols +117 inputs: list[WithSymbol[InputArgument]] = [] +118 for i in command.inputs: +119 py_symbol = inputs_scope.add_or_dodge(python_snakify(i.name)) +120 inputs.append(WithSymbol(i, py_symbol)) +121 +122 for input_ in inputs: +123 if input_.data.type.primitive == InputTypePrimitive.SubCommand: +124 assert input_.data.sub_command is not None +125 sub_command = input_.data.sub_command +126 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( +127 module, symbols, sub_command, inputs_scope +128 ) +129 aliases.update(sub_aliases) +130 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) +131 +132 sub_outputs = [] +133 for output in sub_command.outputs: +134 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) +135 sub_outputs.append(WithSymbol(output, py_symbol)) +136 +137 sub_command_type, sub_command_output_type = _generate_sub_command( +138 module, +139 scope_module, +140 symbols, +141 sub_command, +142 sub_outputs, +143 sub_inputs, +144 aliases, +145 sub_command_output_class_aliases, +146 ) +147 aliases[sub_command.internal_id] = sub_command_type +148 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type +149 +150 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: +151 assert input_.data.sub_command_union is not None +152 for sub_command in input_.data.sub_command_union: +153 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( +154 module, symbols, sub_command, inputs_scope +155 ) +156 aliases.update(sub_aliases) +157 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) +158 +159 sub_outputs = [] +160 for output in sub_command.outputs: +161 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) +162 sub_outputs.append(WithSymbol(output, py_symbol)) +163 +164 sub_command_type, sub_command_output_type = _generate_sub_command( +165 module, +166 scope_module, +167 symbols, +168 sub_command, +169 sub_outputs, +170 sub_inputs, +171 aliases, +172 sub_command_output_class_aliases, +173 ) +174 aliases[sub_command.internal_id] = sub_command_type +175 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type +176 +177 return aliases, sub_command_output_class_aliases, inputs
100def generate_sub_command_classes( -101 module: PyModule, -102 symbols: SharedSymbols, -103 command: SubCommand, -104 scope_module: Scope, -105) -> tuple[dict[str, str], dict[str, str], list[WithSymbol[InputArgument]]]: -106 """Build Python function arguments from input arguments.""" -107 # internal_id -> class_name -108 aliases: dict[str, str] = {} -109 # subcommand.internal_id -> subcommand.outputs() class name -110 sub_command_output_class_aliases: dict[str, str] = {} -111 -112 inputs_scope = Scope(parent=scope_module) -113 outputs_scope = Scope(parent=scope_module) -114 -115 # Input symbols -116 inputs: list[WithSymbol[InputArgument]] = [] -117 for i in command.inputs: -118 py_symbol = inputs_scope.add_or_dodge(python_snakify(i.name)) -119 inputs.append(WithSymbol(i, py_symbol)) -120 -121 for input_ in inputs: -122 if input_.data.type.primitive == InputTypePrimitive.SubCommand: -123 assert input_.data.sub_command is not None -124 sub_command = input_.data.sub_command -125 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( -126 module, symbols, sub_command, inputs_scope -127 ) -128 aliases.update(sub_aliases) -129 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) -130 -131 sub_outputs = [] -132 for output in sub_command.outputs: -133 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) -134 sub_outputs.append(WithSymbol(output, py_symbol)) -135 -136 sub_command_type, sub_command_output_type = _generate_sub_command( -137 module, -138 scope_module, -139 symbols, -140 sub_command, -141 sub_outputs, -142 sub_inputs, -143 aliases, -144 sub_command_output_class_aliases, -145 ) -146 aliases[sub_command.internal_id] = sub_command_type -147 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type -148 -149 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: -150 assert input_.data.sub_command_union is not None -151 for sub_command in input_.data.sub_command_union: -152 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( -153 module, symbols, sub_command, inputs_scope -154 ) -155 aliases.update(sub_aliases) -156 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) -157 -158 sub_outputs = [] -159 for output in sub_command.outputs: -160 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) -161 sub_outputs.append(WithSymbol(output, py_symbol)) -162 -163 sub_command_type, sub_command_output_type = _generate_sub_command( -164 module, -165 scope_module, -166 symbols, -167 sub_command, -168 sub_outputs, -169 sub_inputs, -170 aliases, -171 sub_command_output_class_aliases, -172 ) -173 aliases[sub_command.internal_id] = sub_command_type -174 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type -175 -176 return aliases, sub_command_output_class_aliases, inputs +102def generate_sub_command_classes( +103 module: PyModule, +104 symbols: SharedSymbols, +105 command: SubCommand, +106 scope_module: Scope, +107) -> tuple[dict[str, str], dict[str, str], list[WithSymbol[InputArgument]]]: +108 """Build Python function arguments from input arguments.""" +109 # internal_id -> class_name +110 aliases: dict[str, str] = {} +111 # subcommand.internal_id -> subcommand.outputs() class name +112 sub_command_output_class_aliases: dict[str, str] = {} +113 +114 inputs_scope = Scope(parent=scope_module) +115 outputs_scope = Scope(parent=scope_module) +116 +117 # Input symbols +118 inputs: list[WithSymbol[InputArgument]] = [] +119 for i in command.inputs: +120 py_symbol = inputs_scope.add_or_dodge(python_snakify(i.name)) +121 inputs.append(WithSymbol(i, py_symbol)) +122 +123 for input_ in inputs: +124 if input_.data.type.primitive == InputTypePrimitive.SubCommand: +125 assert input_.data.sub_command is not None +126 sub_command = input_.data.sub_command +127 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( +128 module, symbols, sub_command, inputs_scope +129 ) +130 aliases.update(sub_aliases) +131 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) +132 +133 sub_outputs = [] +134 for output in sub_command.outputs: +135 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) +136 sub_outputs.append(WithSymbol(output, py_symbol)) +137 +138 sub_command_type, sub_command_output_type = _generate_sub_command( +139 module, +140 scope_module, +141 symbols, +142 sub_command, +143 sub_outputs, +144 sub_inputs, +145 aliases, +146 sub_command_output_class_aliases, +147 ) +148 aliases[sub_command.internal_id] = sub_command_type +149 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type +150 +151 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: +152 assert input_.data.sub_command_union is not None +153 for sub_command in input_.data.sub_command_union: +154 sub_aliases, sub_sub_command_output_class_aliases, sub_inputs = generate_sub_command_classes( +155 module, symbols, sub_command, inputs_scope +156 ) +157 aliases.update(sub_aliases) +158 sub_command_output_class_aliases.update(sub_sub_command_output_class_aliases) +159 +160 sub_outputs = [] +161 for output in sub_command.outputs: +162 py_symbol = outputs_scope.add_or_dodge(python_snakify(output.name)) +163 sub_outputs.append(WithSymbol(output, py_symbol)) +164 +165 sub_command_type, sub_command_output_type = _generate_sub_command( +166 module, +167 scope_module, +168 symbols, +169 sub_command, +170 sub_outputs, +171 sub_inputs, +172 aliases, +173 sub_command_output_class_aliases, +174 ) +175 aliases[sub_command.internal_id] = sub_command_type +176 sub_command_output_class_aliases[sub_command.internal_id] = sub_command_output_type +177 +178 return aliases, sub_command_output_class_aliases, inputs