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 @@

56 for input_ in inputs: 57 if input_.data.type.primitive == InputTypePrimitive.SubCommand: 58 assert input_.data.sub_command is not None - 59 module.header.extend( - 60 indent([ - 61 f"{input_.symbol}: {sub_command_output_class_aliases[input_.data.sub_command.internal_id]}", - 62 '"""Subcommand outputs"""', - 63 ]) - 64 ) - 65 - 66 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: - 67 assert input_.data.sub_command_union is not None - 68 - 69 sub_commands = [ - 70 sub_command_output_class_aliases[sub_command.internal_id] - 71 for sub_command in input_.data.sub_command_union - 72 ] - 73 sub_commands_type = ", ".join(sub_commands) - 74 sub_commands_type = f"typing.Union[{sub_commands_type}]" - 75 - 76 if input_.data.type.is_list: - 77 sub_commands_type = f"typing.List[{sub_commands_type}]" - 78 - 79 module.header.extend( - 80 indent([ - 81 f"{input_.symbol}: {sub_commands_type}", - 82 '"""Subcommand outputs"""', - 83 ]) - 84 ) - 85 - 86 - 87def generate_output_building( - 88 func: PyFunc, - 89 func_scope: Scope, - 90 symbol_execution: str, - 91 symbol_output_class: str, - 92 symbol_return_var: str, - 93 outputs: list[WithSymbol[OutputArgument]], - 94 inputs: list[WithSymbol[InputArgument]], - 95) -> None: - 96 """Generate the output building code.""" - 97 py_rstrip_fun = func_scope.add_or_dodge("_rstrip") - 98 if any([out.data.stripped_file_extensions is not None for out in outputs]): - 99 func.body.extend([ -100 f"def {py_rstrip_fun}(s, r):", -101 *indent([ -102 "for postfix in r:", -103 *indent([ -104 "if s.endswith(postfix):", -105 *indent(["return s[: -len(postfix)]"]), -106 ]), -107 "return s", -108 ]), -109 ]) -110 -111 func.body.append(f"{symbol_return_var} = {symbol_output_class}(") -112 -113 # Set root output path -114 func.body.extend(indent([f'root={symbol_execution}.output_file("."),'])) + 59 + 60 sub_commands_type = sub_command_output_class_aliases[input_.data.sub_command.internal_id] + 61 if input_.data.type.is_list: + 62 sub_commands_type = f"typing.List[{sub_commands_type}]" + 63 + 64 module.header.extend( + 65 indent([ + 66 f"{input_.symbol}: {sub_commands_type}", + 67 '"""Subcommand outputs"""', + 68 ]) + 69 ) + 70 + 71 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: + 72 assert input_.data.sub_command_union is not None + 73 + 74 sub_commands = [ + 75 sub_command_output_class_aliases[sub_command.internal_id] + 76 for sub_command in input_.data.sub_command_union + 77 ] + 78 sub_commands_type = ", ".join(sub_commands) + 79 sub_commands_type = f"typing.Union[{sub_commands_type}]" + 80 + 81 if input_.data.type.is_list: + 82 sub_commands_type = f"typing.List[{sub_commands_type}]" + 83 + 84 module.header.extend( + 85 indent([ + 86 f"{input_.symbol}: {sub_commands_type}", + 87 '"""Subcommand outputs"""', + 88 ]) + 89 ) + 90 + 91 + 92def generate_output_building( + 93 func: PyFunc, + 94 func_scope: Scope, + 95 symbol_execution: str, + 96 symbol_output_class: str, + 97 symbol_return_var: str, + 98 outputs: list[WithSymbol[OutputArgument]], + 99 inputs: list[WithSymbol[InputArgument]], +100) -> None: +101 """Generate the output building code.""" +102 py_rstrip_fun = func_scope.add_or_dodge("_rstrip") +103 if any([out.data.stripped_file_extensions is not None for out in outputs]): +104 func.body.extend([ +105 f"def {py_rstrip_fun}(s, r):", +106 *indent([ +107 "for postfix in r:", +108 *indent([ +109 "if s.endswith(postfix):", +110 *indent(["return s[: -len(postfix)]"]), +111 ]), +112 "return s", +113 ]), +114 ]) 115 -116 for out in outputs: -117 strip_extensions = out.data.stripped_file_extensions is not None -118 s_optional = ", optional=True" if out.data.optional else "" -119 if out.data.path_template is not None: -120 path_template = out.data.path_template -121 -122 input_dependencies = _find_output_dependencies(out, inputs) -123 -124 if len(input_dependencies) == 0: -125 # No substitutions needed -126 func.body.extend( -127 indent([f"{out.symbol}={symbol_execution}.output_file(f{enquote(path_template)}{s_optional}),"]) -128 ) -129 else: -130 for input_ in input_dependencies: -131 substitute = input_.symbol -132 -133 if input_.data.type.is_list: -134 raise Exception(f"Output path template replacements cannot be lists. ({input_.data.name})") -135 -136 if input_.data.type.primitive == InputTypePrimitive.File: -137 # Just use the name of the file -138 # This is commonly used when output files 'inherit' the name of an input file -139 substitute = f"pathlib.Path({substitute}).name" -140 elif (input_.data.type.primitive == InputTypePrimitive.Number) or ( -141 input_.data.type.primitive == InputTypePrimitive.Integer -142 ): -143 # Convert to string -144 substitute = f"str({substitute})" -145 elif input_.data.type.primitive != InputTypePrimitive.String: -146 raise Exception( -147 f"Unsupported input type {input_.data.type.primitive} " -148 f"for output path template of '{out.data.name}'." -149 ) -150 -151 if strip_extensions: -152 exts = as_py_literal(out.data.stripped_file_extensions, "'") -153 substitute = f"{py_rstrip_fun}({substitute}, {exts})" -154 -155 path_template = path_template.replace(input_.data.template_key, enbrace(substitute)) -156 -157 resolved_output = f"{symbol_execution}.output_file(f{enquote(path_template)}{s_optional})" -158 -159 if any([input_.data.type.is_optional for input_ in input_dependencies]): -160 # Codegen: Condition: Is any variable in the segment set by the user? -161 condition = [codegen_var_is_set_by_user(i) for i in input_dependencies] -162 resolved_output = f"{resolved_output} if {' and '.join(condition)} else None" +116 func.body.append(f"{symbol_return_var} = {symbol_output_class}(") +117 +118 # Set root output path +119 func.body.extend(indent([f'root={symbol_execution}.output_file("."),'])) +120 +121 for out in outputs: +122 strip_extensions = out.data.stripped_file_extensions is not None +123 s_optional = ", optional=True" if out.data.optional else "" +124 if out.data.path_template is not None: +125 path_template = out.data.path_template +126 +127 input_dependencies = _find_output_dependencies(out, inputs) +128 +129 if len(input_dependencies) == 0: +130 # No substitutions needed +131 func.body.extend( +132 indent([f"{out.symbol}={symbol_execution}.output_file(f{enquote(path_template)}{s_optional}),"]) +133 ) +134 else: +135 for input_ in input_dependencies: +136 substitute = input_.symbol +137 +138 if input_.data.type.is_list: +139 raise Exception(f"Output path template replacements cannot be lists. ({input_.data.name})") +140 +141 if input_.data.type.primitive == InputTypePrimitive.File: +142 # Just use the name of the file +143 # This is commonly used when output files 'inherit' the name of an input file +144 substitute = f"pathlib.Path({substitute}).name" +145 elif (input_.data.type.primitive == InputTypePrimitive.Number) or ( +146 input_.data.type.primitive == InputTypePrimitive.Integer +147 ): +148 # Convert to string +149 substitute = f"str({substitute})" +150 elif input_.data.type.primitive != InputTypePrimitive.String: +151 raise Exception( +152 f"Unsupported input type {input_.data.type.primitive} " +153 f"for output path template of '{out.data.name}'." +154 ) +155 +156 if strip_extensions: +157 exts = as_py_literal(out.data.stripped_file_extensions, "'") +158 substitute = f"{py_rstrip_fun}({substitute}, {exts})" +159 +160 path_template = path_template.replace(input_.data.template_key, enbrace(substitute)) +161 +162 resolved_output = f"{symbol_execution}.output_file(f{enquote(path_template)}{s_optional})" 163 -164 func.body.extend(indent([f"{out.symbol}={resolved_output},"])) -165 else: -166 raise NotImplementedError -167 -168 for input_ in inputs: -169 if (input_.data.type.primitive == InputTypePrimitive.SubCommand) or ( -170 input_.data.type.primitive == InputTypePrimitive.SubCommandUnion -171 ): -172 if input_.data.type.is_list: -173 func.body.extend( -174 indent([ -175 f"{input_.symbol}=" -176 f"[{input_.symbol}.outputs({symbol_execution}) for {input_.symbol} in {input_.symbol}]," -177 ]) -178 ) -179 else: -180 func.body.extend(indent([f"{input_.symbol}={input_.symbol}.outputs({symbol_execution}),"])) -181 -182 func.body.extend([")"]) +164 if any([input_.data.type.is_optional for input_ in input_dependencies]): +165 # Codegen: Condition: Is any variable in the segment set by the user? +166 condition = [codegen_var_is_set_by_user(i) for i in input_dependencies] +167 resolved_output = f"{resolved_output} if {' and '.join(condition)} else None" +168 +169 func.body.extend(indent([f"{out.symbol}={resolved_output},"])) +170 else: +171 raise NotImplementedError +172 +173 for input_ in inputs: +174 if (input_.data.type.primitive == InputTypePrimitive.SubCommand) or ( +175 input_.data.type.primitive == InputTypePrimitive.SubCommandUnion +176 ): +177 if input_.data.type.is_list: +178 func.body.extend( +179 indent([ +180 f"{input_.symbol}=" +181 f"[{input_.symbol}.outputs({symbol_execution}) for {input_.symbol} in {input_.symbol}]," +182 ]) +183 ) +184 else: +185 func.body.extend(indent([f"{input_.symbol}={input_.symbol}.outputs({symbol_execution}),"])) +186 +187 func.body.extend([")"]) @@ -293,32 +298,37 @@

57 for input_ in inputs: 58 if input_.data.type.primitive == InputTypePrimitive.SubCommand: 59 assert input_.data.sub_command is not None -60 module.header.extend( -61 indent([ -62 f"{input_.symbol}: {sub_command_output_class_aliases[input_.data.sub_command.internal_id]}", -63 '"""Subcommand outputs"""', -64 ]) -65 ) -66 -67 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: -68 assert input_.data.sub_command_union is not None -69 -70 sub_commands = [ -71 sub_command_output_class_aliases[sub_command.internal_id] -72 for sub_command in input_.data.sub_command_union -73 ] -74 sub_commands_type = ", ".join(sub_commands) -75 sub_commands_type = f"typing.Union[{sub_commands_type}]" -76 -77 if input_.data.type.is_list: -78 sub_commands_type = f"typing.List[{sub_commands_type}]" -79 -80 module.header.extend( -81 indent([ -82 f"{input_.symbol}: {sub_commands_type}", -83 '"""Subcommand outputs"""', -84 ]) -85 ) +60 +61 sub_commands_type = sub_command_output_class_aliases[input_.data.sub_command.internal_id] +62 if input_.data.type.is_list: +63 sub_commands_type = f"typing.List[{sub_commands_type}]" +64 +65 module.header.extend( +66 indent([ +67 f"{input_.symbol}: {sub_commands_type}", +68 '"""Subcommand outputs"""', +69 ]) +70 ) +71 +72 if input_.data.type.primitive == InputTypePrimitive.SubCommandUnion: +73 assert input_.data.sub_command_union is not None +74 +75 sub_commands = [ +76 sub_command_output_class_aliases[sub_command.internal_id] +77 for sub_command in input_.data.sub_command_union +78 ] +79 sub_commands_type = ", ".join(sub_commands) +80 sub_commands_type = f"typing.Union[{sub_commands_type}]" +81 +82 if input_.data.type.is_list: +83 sub_commands_type = f"typing.List[{sub_commands_type}]" +84 +85 module.header.extend( +86 indent([ +87 f"{input_.symbol}: {sub_commands_type}", +88 '"""Subcommand outputs"""', +89 ]) +90 ) @@ -338,102 +348,102 @@

-
 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("."),']))
+            
 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([")"])
 
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 @@

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

@@ -244,83 +246,83 @@

-
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