diff --git a/README.md b/README.md index 09e9643..ea72dce 100644 --- a/README.md +++ b/README.md @@ -12,5 +12,5 @@ TBD ## Class Diagram ```bash -$ umlizer class --source ./src/umlizer --target ./docs/uml/class_graph --verbose +$ umlizer class --source ./src/umlizer --target ./docs/uml/ --verbose ``` diff --git a/docs/changelog.md b/docs/changelog.md index 6181ff0..3a2e3ef 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,20 +1,3 @@ # Release Notes --- - -# [0.3.0](https://github.com/osl-incubator/umlizer/compare/0.2.0...0.3.0) (2023-12-22) - -### Features - -- Separate data.lock per profile and fix small issues ([#8](https://github.com/osl-incubator/umlizer/issues/8)) ([91a8b3d](https://github.com/osl-incubator/umlizer/commit/91a8b3d8b0fa2900fc35e3149352a77d0fd40b2b)) - -# [0.2.0](https://github.com/osl-incubator/umlizer/compare/0.1.0...0.2.0) (2023-12-15) - -### Bug Fixes - -- Fix release workflow ([#5](https://github.com/osl-incubator/umlizer/issues/5)) ([8ba1407](https://github.com/osl-incubator/umlizer/commit/8ba1407ea27fb6e1a8712608f7f8af4ee1850475)) -- Fix release workflow ([#6](https://github.com/osl-incubator/umlizer/issues/6)) ([38e9d7c](https://github.com/osl-incubator/umlizer/commit/38e9d7c5ce45a83f6d50bdc9e98fcb76b6a34caf)) - -### Features - -- Implement option for passing password as a stdin ([#4](https://github.com/osl-incubator/umlizer/issues/4)) ([4ab5ba8](https://github.com/osl-incubator/umlizer/commit/4ab5ba8ee54e98b6f580dbd3f7659af77e9a72c3)) diff --git a/docs/uml/class_graph b/docs/uml/class_graph index e91fa6f..f8ab7de 100644 --- a/docs/uml/class_graph +++ b/docs/uml/class_graph @@ -1,10 +1,10 @@ // Graph digraph { node [rankdir=BT shape=record] - "typing.Annotated" [label="{Annotated|\l|+ _class_getitem_inner()\l}"] + "typing.Annotated" [label="{Annotated|\l|- _class_getitem_inner()\l}"] "typer.models.Context" [label="{Context|\l|\l}"] - "pathlib.Path" [label="{Path : pathlib.PurePath|+ cwd: object\l+ home: object\l|+ stat()\l+ lstat()\l+ exists()\l+ is_dir()\l+ is_file()\l+ is_mount()\l+ is_symlink()\l+ is_junction()\l+ is_block_device()\l+ is_char_device()\l+ is_fifo()\l+ is_socket()\l+ samefile()\l+ open()\l+ read_bytes()\l+ read_text()\l+ write_bytes()\l+ write_text()\l+ iterdir()\l+ _scandir()\l+ _make_child_relpath()\l+ glob()\l+ rglob()\l+ walk()\l+ absolute()\l+ resolve()\l+ owner()\l+ group()\l+ readlink()\l+ touch()\l+ mkdir()\l+ chmod()\l+ lchmod()\l+ unlink()\l+ rmdir()\l+ rename()\l+ replace()\l+ symlink_to()\l+ hardlink_to()\l+ expanduser()\l}"] + "pathlib.Path" [label="{Path : pathlib.PurePath|+ cwd: \l+ home: \l|+ stat()\l+ lstat()\l+ exists()\l+ is_dir()\l+ is_file()\l+ is_mount()\l+ is_symlink()\l+ is_junction()\l+ is_block_device()\l+ is_char_device()\l+ is_fifo()\l+ is_socket()\l+ samefile()\l+ open()\l+ read_bytes()\l+ read_text()\l+ write_bytes()\l+ write_text()\l+ iterdir()\l- _scandir()\l- _make_child_relpath()\l+ glob()\l+ rglob()\l+ walk()\l+ absolute()\l+ resolve()\l+ owner()\l+ group()\l+ readlink()\l+ touch()\l+ mkdir()\l+ chmod()\l+ lchmod()\l+ unlink()\l+ rmdir()\l+ rename()\l+ replace()\l+ symlink_to()\l+ hardlink_to()\l+ expanduser()\l}"] "typing.Any" [label="{Any|\l|\l}"] - "pathlib.Path" [label="{Path : pathlib.PurePath|+ cwd: object\l+ home: object\l|+ stat()\l+ lstat()\l+ exists()\l+ is_dir()\l+ is_file()\l+ is_mount()\l+ is_symlink()\l+ is_junction()\l+ is_block_device()\l+ is_char_device()\l+ is_fifo()\l+ is_socket()\l+ samefile()\l+ open()\l+ read_bytes()\l+ read_text()\l+ write_bytes()\l+ write_text()\l+ iterdir()\l+ _scandir()\l+ _make_child_relpath()\l+ glob()\l+ rglob()\l+ walk()\l+ absolute()\l+ resolve()\l+ owner()\l+ group()\l+ readlink()\l+ touch()\l+ mkdir()\l+ chmod()\l+ lchmod()\l+ unlink()\l+ rmdir()\l+ rename()\l+ replace()\l+ symlink_to()\l+ hardlink_to()\l+ expanduser()\l}"] + "pathlib.Path" [label="{Path : pathlib.PurePath|+ cwd: \l+ home: \l|+ stat()\l+ lstat()\l+ exists()\l+ is_dir()\l+ is_file()\l+ is_mount()\l+ is_symlink()\l+ is_junction()\l+ is_block_device()\l+ is_char_device()\l+ is_fifo()\l+ is_socket()\l+ samefile()\l+ open()\l+ read_bytes()\l+ read_text()\l+ write_bytes()\l+ write_text()\l+ iterdir()\l- _scandir()\l- _make_child_relpath()\l+ glob()\l+ rglob()\l+ walk()\l+ absolute()\l+ resolve()\l+ owner()\l+ group()\l+ readlink()\l+ touch()\l+ mkdir()\l+ chmod()\l+ lchmod()\l+ unlink()\l+ rmdir()\l+ rename()\l+ replace()\l+ symlink_to()\l+ hardlink_to()\l+ expanduser()\l}"] "pathlib.PurePath" -> "pathlib.Path" } diff --git a/docs/uml/class_graph.png b/docs/uml/class_graph.png index 36f68da..c5d2453 100644 Binary files a/docs/uml/class_graph.png and b/docs/uml/class_graph.png differ diff --git a/src/umlizer/class_graph.py b/src/umlizer/class_graph.py index 64e5388..4358e5a 100644 --- a/src/umlizer/class_graph.py +++ b/src/umlizer/class_graph.py @@ -87,13 +87,16 @@ def _get_classicclass_structure( klass: Type[Any], ) -> dict[str, Union[dict[str, str], list[str]]]: _methods = _get_methods(klass) + fields = {} + + for k in list(klass.__dict__.keys()): + if k.startswith('__') or k in _methods: + continue + value = _get_annotations(klass).get(k, '') + fields[k] = getattr(value, '__value__', str(value)) return { - 'fields': { - k: _get_annotations(klass).get(k, object).__name__ - for k in list(klass.__dict__.keys()) - if not k.startswith('__') and k not in _methods - }, + 'fields': fields, 'methods': _methods, } @@ -131,15 +134,29 @@ def _get_entity_class_uml(entity: Type[Any]) -> str: class_name = f'{entity.__name__}' if base_classes: - class_name += f' : {base_classes}' + class_name += f' ({base_classes})' # Formatting fields and methods fields_struct = cast(dict[str, str], class_structure['fields']) fields = ( - '\\l'.join([f'+ {k}: {v}' for k, v in fields_struct.items()]) + '\\l' + '\\l'.join( + [ + f'{"-" if k.startswith("_") else "+"} {k}: {v}' + for k, v in fields_struct.items() + ] + ) + + '\\l' ) methods_struct = cast(list[str], class_structure['methods']) - methods = '\\l'.join([f'+ {m}()' for m in methods_struct]) + '\\l' + methods = ( + '\\l'.join( + [ + f'{"-" if m.startswith("_") else "+"} {m}()' + for m in methods_struct + ] + ) + + '\\l' + ) # Combine class name, fields, and methods into the UML node format uml_representation = '{' + f'{class_name}|{fields}|{methods}' + '}' diff --git a/src/umlizer/cli.py b/src/umlizer/cli.py index cf45953..cc06493 100644 --- a/src/umlizer/cli.py +++ b/src/umlizer/cli.py @@ -1,6 +1,8 @@ """Main module template with example functions.""" from __future__ import annotations +import os + from pathlib import Path import typer @@ -13,6 +15,31 @@ app = typer.Typer() +def make_absolute(relative_path: Path) -> Path: + """ + Convert a relative Path to absolute, relative to the current cwd. + + Parameters + ---------- + relative_path : Path + The path to be converted to absolute. + + Returns + ------- + Path + The absolute path. + """ + # Get current working directory + current_directory = Path(os.getcwd()) + + # Return absolute path + return ( + current_directory / relative_path + if not relative_path.is_absolute() + else relative_path + ) + + @app.callback(invoke_without_command=True) def main( ctx: Context, @@ -47,12 +74,15 @@ def class_( typer.Option( ..., help='Target path where the UML graph will be generated.' ), - ] = Path('/tmp'), + ] = Path('/tmp/'), verbose: Annotated[ bool, typer.Option(help='Active the verbose mode.') ] = False, ) -> None: """Run the command for class graph.""" + source = make_absolute(source) + target = make_absolute(target) / 'class_graph' + g = class_graph.create_class_diagram_from_source(source, verbose=verbose) g.format = 'png' g.render(target)