Rewrite of Exporter, Minor Changes in Importer#15
Conversation
# Conflicts: # capella_ros_tools/importer.py
Co-authored-by: Martin Lehmann <martin.lehmann@deutschebahn.com>
abb5919 to
f98e3e6
Compare
Wuestengecko
left a comment
There was a problem hiding this comment.
Yeah IDK if it works then I'm okay with it I guess. Just some minor notes.
| help="Decide whether experimental cmake files should be generated.", | ||
| envvar="CAPELLA_ROS_TOOLS_GENERATE_CMAKE", | ||
| ) | ||
| def export( |
There was a problem hiding this comment.
Now that the function is called export (just like the click command), we no longer need to pass the command name explicitly in the outermost decorator.
-@cli.command("export")
+@cli.command()| @click.option( | ||
| "--generate-cmake", | ||
| type=bool, | ||
| default=False, | ||
| is_flag=True, | ||
| help="Decide whether experimental cmake files should be generated.", | ||
| envvar="CAPELLA_ROS_TOOLS_GENERATE_CMAKE", | ||
| ) |
There was a problem hiding this comment.
I'm a fan of short flag names. And short help texts. :)
| @click.option( | |
| "--generate-cmake", | |
| type=bool, | |
| default=False, | |
| is_flag=True, | |
| help="Decide whether experimental cmake files should be generated.", | |
| envvar="CAPELLA_ROS_TOOLS_GENERATE_CMAKE", | |
| ) | |
| @click.option( | |
| "--cmake", | |
| type=bool, | |
| default=False, | |
| is_flag=True, | |
| help="Generate cmake files. Experimental.", | |
| envvar="CAPELLA_ROS_TOOLS_GENERATE_CMAKE", | |
| ) |
| raise RuntimeError( | ||
| "Neither config nor root package nor layer specified." | ||
| ) |
There was a problem hiding this comment.
This should be a click.UsageError, which has a much nicer UX. Compare these error messages:
RuntimeError
INFO:capellambse.cli_helpers:Loading model from [$WORKSPACE]/models/ife-demo
Traceback (most recent call last):
File "", line 198, in _run_module_as_main
File "", line 88, in _run_code
File "[$WORKSPACE]/rostools/capella_ros_tools/__main__.py", line 234, in
cli()
~~~^^
File "[$WORKSPACE]/rostools/.venv/lib/python3.13/site-packages/click/core.py", line 1462, in __call__
return self.main(*args, **kwargs)
~~~~~~~~~^^^^^^^^^^^^^^^^^
File "[$WORKSPACE]/rostools/.venv/lib/python3.13/site-packages/click/core.py", line 1383, in main
rv = self.invoke(ctx)
File "[$WORKSPACE]/rostools/.venv/lib/python3.13/site-packages/click/core.py", line 1850, in invoke
return _process_result(sub_ctx.command.invoke(sub_ctx))
~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
File "[$WORKSPACE]/rostools/.venv/lib/python3.13/site-packages/click/core.py", line 1246, in invoke
return ctx.invoke(self.callback, **ctx.params)
~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "[$WORKSPACE]/rostools/.venv/lib/python3.13/site-packages/click/core.py", line 814, in invoke
return callback(*args, **kwargs)
File "[$WORKSPACE]/rostools/capella_ros_tools/__main__.py", line 196, in export
raise RuntimeError(
"Neither config nor root package nor layer specified."
)
RuntimeError: Neither config nor root package nor layer specified.
UsageError
INFO:capellambse.cli_helpers:Loading model from [$WORKSPACE]/models/ife-demo Usage: python -m capella_ros_tools export [OPTIONS] Try 'python -m capella_ros_tools export --help' for help.
Error: Neither config nor root package nor layer specified.
IMO we should also use the parameter names in the error message:
| raise RuntimeError( | |
| "Neither config nor root package nor layer specified." | |
| ) | |
| raise RuntimeError( | |
| "One of --config=, --root= or --layer= is required." | |
| ) |
| name = re.sub("[^a-z0-9_]", "_", name) | ||
| name = re.sub("^[^a-z]+", "", name) | ||
| name = re.sub("_+", "_", name) | ||
| return re.sub("_$", "", name) |
There was a problem hiding this comment.
| name = re.sub("[^a-z0-9_]", "_", name) | |
| name = re.sub("^[^a-z]+", "", name) | |
| name = re.sub("_+", "_", name) | |
| return re.sub("_$", "", name) | |
| name = re.sub("[^a-z0-9]+", "_", name) | |
| name = re.sub("^[^a-z]+", "", name) | |
| return re.sub("_+$", "", name) |
| if not camel_case_name or not camel_case_name[0].isalpha(): | ||
| camel_case_name = "A" + camel_case_name |
There was a problem hiding this comment.
In make_snake_case we're stripping invalid characters from the beginning, while here in make_camel_case we're prepending a capital "A" in such cases. Does this have a specific reason? If not, IMO we should have the same behavior in both cases (either both strip, or both prepend).
|
|
||
| @staticmethod | ||
| def make_snake_case(name: str) -> str: | ||
| """Convert all cases to snake_case.""" |
There was a problem hiding this comment.
I'd like to see some basic unit tests for make_snake_case and make_camel_case. IMO they're complex enough to warrant that.
| name = re.sub("([a-z0-9])([A-Z])", r"\1_\2", name) | ||
| name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) | ||
| name = name.lower() | ||
| # Replace invalid characters with underscores | ||
| name = re.sub("[^a-z0-9_]", "_", name) | ||
| name = re.sub("^[^a-z]+", "", name) | ||
| name = re.sub("_+", "_", name) | ||
| return re.sub("_$", "", name) |
There was a problem hiding this comment.
This can lead to empty names:
from capella_ros_tools.exporter import RosExportHelper as e
print(repr(e.make_snake_case("1+1=2"))) # ''
print(repr(e.make_snake_case("A 1+1=2"))) # 'a_1_1_2'| ````yaml | ||
| packages: | ||
| asdf: <capella_pkg_uuid> | ||
| built_ins: | ||
| ros_pkg: <capella_build_in_pkg_uuid> | ||
| custom_pkg: | ||
| "abc": | ||
| - <capella_cls1_uuid> | ||
| "xyz": | ||
| - <capella_cls2_uuid> | ||
| custom_types: | ||
| Bitset16: int16 | ||
| Bitset32: int32 | ||
| ```` |
There was a problem hiding this comment.
The world needs more TOML.
Advantages over YAML:
- it's not YAML
tomllibis in stdlib since Python 3.11 (previouslytomlion PyPI)- very simple and very explicit format
- no significant whitespace, which is especially beneficial with the config templating feature
Disadvantages:
- only 4 different types of strings instead of 9
- can't easily introduce an ACE by accidentally using
load()instead ofsafe_load()
````toml
[packages]
asdf = "<capella_pkg_uuid>"
[built_ins]
ros_pkg = "<capella_built_in_pkg_uuid>"
[custom_pkg]
abc = ["<capella_cls1_uuid>"]
xyz = [
"<capella_cls2_uuid>",
"<capella_cls3_uuid>",
]
[custom_types]
Bitset16 = "int16"
Bitset32 = "int32"
````
No description provided.