diff --git a/README.md b/README.md index fe48d945..82fe12b4 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![Coverage](https://codecov.io/gh/bkryza/clang-uml/branch/master/graph/badge.svg)](https://codecov.io/gh/bkryza/clang-uml) [![Version](https://img.shields.io/badge/version-0.3.0-blue)](https://github.com/bkryza/clang-uml/releases) -`clang-uml` is an automatic C++ to UML class, sequence -and package diagram generator, driven by YAML configuration files. The main idea behind the +`clang-uml` is an automatic C++ to UML class, sequence, package and include diagram generator, driven by +YAML configuration files. The main idea behind the project is to easily maintain up-to-date diagrams within a code-base or document legacy code. The configuration file or files for `clang-uml` define the type and contents of each generated diagram. @@ -42,6 +42,8 @@ Main features supported so far include: To see what `clang-uml` can do so far, checkout the diagrams generated for unit test cases [here](./docs/test_cases.md) and examples in [clang-uml-examples](https://github.com/bkryza/clang-uml-examples) repository. +More comprehensive documentation can be found [here](./docs/README.md). + ## Installation ### Distribution packages @@ -451,34 +453,6 @@ exclude: - clanguml::common::ClassF ``` -### Comment decorators - -`clang-uml` provides a set of in-comment directives, called decorators, which allow custom control over -generation of UML diagrams from C++ and overriding default inference rules for relationships. - -The following decorators are currently supported: - -- [note](docs/test_cases/t00028.md) - add a PlantUML note to a C++ entity -- [skip](docs/test_cases/t00029.md) - skip the underlying C++ entity -- [skiprelationship](docs/test_cases/t00029.md) - skip only relationship generation for a class property -- [composition](docs/test_cases/t00030.md) - document the property as composition -- [association](docs/test_cases/t00030.md) - document the property as association -- [aggregation](docs/test_cases/t00030.md) - document the property as aggregation -- [style](docs/test_cases/t00031.md) - add PlantUML style to a C++ entity - -### Doxygen integration - -`clang-uml` decorstors can be omitted completely in [Doxygen](https://www.doxygen.nl/index.html), by adding the -following -lines to the Doxygen config file: - -``` -ALIASES += clanguml="" -ALIASES += clanguml{1}="" -ALIASES += clanguml{2}="" -ALIASES += clanguml{3}="" -``` - ### Test cases The build-in test cases used for unit testing of the `clang-uml`, can be browsed [here](./docs/test_cases.md). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..3c6a48f2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,18 @@ +# Documentation + + +* [Quick start](./quick_start.md) +* Generating diagrams + * [Common options](./common_options.md) + * [Class diagrams](./class_diagrams.md) + * [Sequence diagrams](./sequence_diagrams.md) + * [Package diagrams](./package_diagrams.md) + * [Include diagrams](./include_diagrams.md) +* [Comment decorators](./comment_decorators.md) +* [Diagram filters](./diagram_filters.md) +* [Using Jinja templates](./jinja_templates.md) +* [Interactive SVG diagrams using links](./interactive_svg_diagrams.md) +* [Configuration file reference](./configuration_file.md) +* [Doxygen integration](./doxygen_integration.md) +* [Test cases documentation](./test_cases.md) +* [Troubleshooting](./troubleshooting.md) diff --git a/docs/class_diagrams.md b/docs/class_diagrams.md new file mode 100644 index 00000000..1a49fcd2 --- /dev/null +++ b/docs/class_diagrams.md @@ -0,0 +1,159 @@ +# Generating class diagrams + +The minimal config required to generate a class diagram is presented below: +```yaml +# Path to the directory where `compile_commands.json` can be found +compilation_database_dir: _build +# Output directory for the diagrams +output_directory: puml +# Diagrams definitions +diagrams: + # Diagram name + t00002_class: + # Type of diagram (has to be `class`) + type: class + # Include only translation units matching the following patterns + glob: + - src/*.cc + # Render all names in the diagram relative to specific namespace + using_namespace: + - ns1 + # Include only classes from specific namespace + include: + namespaces: + - ns1::ns2 +``` + +## Classes and their properties +The basic class diagram generated by `clang-uml` and rendered using PlantUML looks like this: + +![extension](test_cases/t00003_class.svg) + +Parameter types and method return types are rendered at the end after `:` sign. + +Static methods and members are underlined. + +In case method argument lists are too long and not required for diagram readability, they can be suppressed completely +or abbreviated by setting `generate_method_arguments` option to either `none`, `abbreviated` or `full` (default). + + +### Excluding private or protected members from the diagram +In order to only include public members in the class diagrams, we can add the following inclusion filters: +```yaml + include: + access: + - public +``` + +To render only classes without any properties an exclusion filter can be added: +```yaml + exclude: + access: + - public + - protected + - private +``` + +## Relationships + +The following table presents the PlantUML arrows representing each relationship in the class diagrams. + +| UML | PlantUML | +| ---- | --- | +| Inheritance | ![extension](img/puml_inheritance.png) | +| Association | ![association](img/puml_association.png) | +| Dependency | ![dependency](img/puml_dependency.png) | +| Aggregation | ![aggregation](img/puml_aggregation.png) | +| Composition | ![composition](img/puml_composition.png) | +| Template specialization/instantiation | ![specialization](img/puml_instantiation.png) | +| Nesting (inner class/enum) | ![nesting](img/puml_nested.png) | +| Include (local) | ![association](img/puml_association.png) | +| Include (system) | ![dependency](img/puml_dependency.png) | + + +By default, a member from which a relationship has been added to the diagram between 2 classes will also be rendered +inside the class. This behaviour can be however disabled by adding the following option to the +diagram definition: +```yaml +include_relations_also_as_members: false +``` + +### Relationships to classes in containers or smart pointers +`clang-uml` will automatically detect class members as well as method arguments, which reference or own +values of types relevant for a given diagram but wrapped in smart pointers or containers ang still generate +relationship between these classes, for instance the following code: + +```cpp +class A { }; + +class B { }; + +class C { }; + +class R { +public: + std::unique_ptr a; + std::shared_ptr b; + std::weak_ptr c; +}; +``` + +generates the following diagram: + +![extension](test_cases/t00007_class.svg) + +## Inheritance diagrams + +A common type of class diagram is an inheritance diagram, where only subclasses of a specific base class are +included and only the inheritance relationships are rendered. This can be easily achieved in `clang-uml` through +inclusion filters: +```yaml + include: + subclasses: + - clanguml::t00039::A + relationships: + - inheritance +``` + +## Namespaces as packages +By default, `clang-uml` will render all element names including a namespace (relative to `using_namespace` property), +e.g. `ns1::ns2::MyClass`. +In order to generate packages in the diagram for each namespace instead, the following option must be set to `true`: + +```yaml +generate_packages: true +``` + +which results in the following diagram: + +![extension](test_cases/t00036_class.svg) + +## Class context diagram +Sometimes it's helpful to generate a class diagram depicting only direct relationships of a given class, e.g. +within the classes documentation page, this can be easily achieved using `context` inclusion filter: + +```yaml + include: + context: + - ns1::MyClass +``` + +## Disabling dependency relationships +In many cases, dependency relationships between classes can clutter the diagram too much, for instance consider this +diagram: + +![extension](test_cases/t00019_class.svg) + +where the dependency relationships do not bring much information into the diagram. Thus in such cases it might +be useful to disable dependency relationships for this diagram completely using the following exclusion filter: +```yaml + exclude: + relationships: + - dependency +``` + +Dependency relationships are inferred whenever a class uses another class, thus often dependency relationship +will be rendered in addition to other relationships such as association or inheritance. In the future there might +be an option to remove the redundant dependency relationships from the diagram automatically. + + diff --git a/docs/comment_decorators.md b/docs/comment_decorators.md new file mode 100644 index 00000000..3492c2bf --- /dev/null +++ b/docs/comment_decorators.md @@ -0,0 +1,222 @@ +# Comment decorators + +`clang-uml` provides a set of in-comment directives, called decorators, which allow custom control over +generation of UML diagrams from C++ and overriding default inference rules for relationships. + +`clang-uml` decorators are specified in the following format: + +``` +\uml{[:][] } +``` + +or +``` +@uml{[:][] } +``` + +The optional `:` suffix will apply this decorator only to a specific diagram. + +Currently the following decorators are supported. + +## `note` + +This decorator allows to specify directly in the code comments that should be included in the generated diagrams. + +The following code: +```cpp +/// \uml{note[top] A class note.} +class A { +}; + +/// \uml{note[] B class note.} +class B { +}; + +/// +/// @uml{note:t00028_class[bottom] C class note.} +/// This is class C. +class C { +}; + +/// \uml{note +/// D +/// class +/// note.} +class D { +}; + +/// \uml{note E template class note.} +template class E { + T param; +}; + +/// \uml{note:other_diagram[left] G class note.} +class G { +}; + +/// @uml{note[ bottom ] F enum note.} +enum class F { one, two, three }; + +/// \uml{note[right] R class note.} +class R { + explicit R(C &c) + : ccc(c) + { + } + + A aaa; + + B *bbb; + + C &ccc; + + std::vector> ddd; + + E eee; + + G **ggg; +}; +``` + +generates the following class diagram: + +![note](./test_cases/t00028_class.svg) + +# `skip` and `skiprelationship` +This decorator allows to skip the specific classes or methods from the diagrams, for instance the following code: +```cpp + +class A { +}; + +/// \uml{skip} +class B { +}; + +template class C { + T param; +}; + +/// @uml{skip:t00029_class} +template class D { + T param; +}; + +enum class E { one, two, three }; + +/// \uml{skip} +enum class F { red, green, blue }; + +class G1 { +}; + +class G2 { +}; + +class G3 { +}; + +class G4 { +}; + +struct R { + G1 g1; + + /// \uml{skip} + G2 g2; + + /// \uml{skiprelationship} + G3 &g3; + + std::shared_ptr g4; +}; +``` + +generates the following diagram: + +![skip](./test_cases/t00029_class.svg) + +## `composition`, `association` and `aggregation` + +These decorators allow to specify explicitly the type of relationship within a class diagram that should be +generated for a given class member. For instance the following code: + +```cpp + +class A { +}; + +class B { +}; + +class C { +}; + +class D { +}; + +class E { +}; + +struct R { + /// @uml{association[]} + A aaa; + + /// @uml{composition[0..1:1..*]} + std::vector bbb; + + /// @uml{aggregation[0..1:1..5]} + std::vector ccc; + + /// @uml{association[:1]} + D ddd; + + /// @uml{aggregation[:1]} + E *eee; +}; +``` + +generates the following diagram: + +![skip](./test_cases/t00030_class.svg) + + +## `style` +This decorator allows to specify in the code specific styles for diagram elements, for instance: + +```cpp + +/// @uml{style[#back:lightgreen|yellow;header:blue/red]} +class A { +}; + +/// @uml{style[#line.dotted:blue]} +enum B { one, two, three }; + +/// @uml{style[#pink;line:red;line.bold;text:red]} +template class C { + T ttt; +}; + +class D { +}; + +struct R { + /// @uml{style[#red,dashed,thickness=2]} + A *aaa; + + /// @uml{composition} + /// @uml{style[#green,dashed,thickness=4]} + std::vector bbb; + + /// @uml{style[#blue,dotted,thickness=8]} + C ccc; + + /// @uml{style[#blue,plain,thickness=16]} + D *ddd; +}; +``` + +generates the following diagram: + +![skip](./test_cases/t00031_class.svg) diff --git a/docs/common_options.md b/docs/common_options.md new file mode 100644 index 00000000..a13870ee --- /dev/null +++ b/docs/common_options.md @@ -0,0 +1,73 @@ +# Common diagram generation options + +## Overall configuration file structure +By default, `clang-uml` will look for file `.clang-uml` in the projects directory and read all diagrams definitions +from it. The file must be specified in YAML and it's overall structure is as follows: + +```yaml + +diagrams: + : + type: [class|sequence|package|include] + + : + type: [class|sequence|package|include] + + ... +``` + +The top level common options are inherited by specific diagrams, if the option is applicable to them and they themselves +do not override this option. + +For detailed reference of all configuration options see [here](./configuration_file.md). + +## Translation unit glob patterns +One of the key options of the diagram configuration is the list of translation units, which should be parsed to +get all necessary information for a diagram. + +The syntax is simple and based on glob patterns, which can be added to the configuration file as follows: + +```yaml + glob: + - src/dir1/*.cc + - src/dir3/*.cc +``` + +The glob patterns only need to match the translation units, which are also in the `compile_commands.json` file, i.e. +any files that match the glob patterns but are not in `compile_commands.json` will be ignored. In case the `glob` +pattern set does not much any translation units an error will be printed on the standard output. + +For small projects, the `glob` property can be omitted, which will result in `clang-uml` parsing all translation units +from `compile_commands.json` for the diagram. However for large projects, constraining the number of translation units +for each diagram to absolute minimum will significantly decrease the diagram generation times. + +## PlantUML custom directives +In case it's necessary to add some custom PlantUML declarations before or after the generated diagram content, +it can be achieved simply using the `plantuml` configuration properties, for instance: + +```yaml + plantuml: + before: + - left to right direction + after: + - note left of {{ alias("ns1::ns2::MyClass") }} This is my class. +``` + +These directive are useful for instance for adding notes to elements in the diagrams or customizing diagram layout +or style. + +Please note that when referring to diagram elements in the PlantUML directives, they must be added using Jinja +templates `alias` command as in the example above. + +More options can be found in the official PlantUML [documentation](https://plantuml.com/). + +## Adding debug information in the generated diagrams +Sometimes it is useful for debugging issues with the diagrams to have information on the exact source location, +from which given declaration or call expression was derived. By adding option: + +```yaml +debug_mode: true +``` + +the generated PlantUML diagram will contain comments before each line containing the source location of the +specific diagram element. \ No newline at end of file diff --git a/docs/configuration_file.md b/docs/configuration_file.md index f16714b0..76a777c2 100644 --- a/docs/configuration_file.md +++ b/docs/configuration_file.md @@ -5,6 +5,7 @@ * `output_directory` - path to the directory where PlantUML diagrams will be generated * `diagrams` - the map of diagrams to be generated, each diagram name is provided as the key of the diagram YAML node +* `debug_mode` - add inline debug information in the generated diagrams ### Diagram options * `type` - type of diagram, one of [`class`, `sequence`, `package`, `include`] @@ -22,7 +23,7 @@ * `dependants` - include all classes, which depend on the specified class * `dependencies` - include all classes, which are dependencies of the specified class * `context` - include only entities in direct relationship with specified classes -* `exclude` - definition of excqlusion patterns: +* `exclude` - definition of exclusion patterns: * `namespaces` - list of namespaces to exclude * `relationships` - list of relationships to exclude * `elements` - list of elements, i.e. specific classes, enums, templates to exclude @@ -37,112 +38,6 @@ * `before` - list of directives which will be added before the generated diagram * `after` - list of directives which will be added after the generated diagram -### Template engine -`clang-uml` integrates [inja](https://github.com/pantor/inja) template engine, with several -additional functions which can be used in textual directives within the configuration files, -notes and to generate links and tooltips to diagrams. - -The following, are the `clang-uml` additional template functions: -* `ltrim(string)` - left trims a string -* `rtrim(string)` - right trims a string -* `trim(string)` - trims a string -* `substr(string, offset, length)` - returns a substring of a string from offset of length -* `split(string)` - splits a string and returns a list of strings -* `replace(string, regex, replacement)` - returns a string with replace matches to regex with replacement string -* `abbrv(string, length)` - returns a string truncated to length including trailing ellipsis -* `element(string)` - returns the entire JSON context a given diagram element, including the following properties: - * `name` - name of the element - * `type` - type of diagram element (e.g. `class`, `enum`, `package`) - * `namespace` - fully qualified element namespace - * `full_name` - fully qualified element name - * `comment` [optional] - elements comment, if any - * `alias` - internal diagram element alias (e.g. PlantUML alias) -* `alias(string)` - returns a PlantUML alias of an C++ entity represented by string name -* `comment(string)` - returns a comment of an C++ entity represented by string name - -Templates allow complex postprocessing of the diagrams, for instance creation of customized PlantUML -notes in the diagrams from comments in the code. Below is an example of using the above commands to -generate notes in the PlantUML diagram from code comments (see also test case [t00050](./test_cases/t00050.md)): - -```yaml - plantuml: - after: - # Add a note left of the `A` class with the entire clas comment as content - - > - note left of {{ alias("A") }} - {{ comment("clanguml::t00050::A").formatted }} - end note - # Same as above - - > - note right of {{ element("clanguml::t00050::A").alias }} - {% set e=element("clanguml::t00050::A") %} {{ e.comment.formatted }} - end note - # Add a note left of class 'C' using trimmed text content from the class comment - - > - note left of {{ alias("C") }} #AABBCC - {{ trim(comment("clanguml::t00050::C").text) }} - end note - # For each element in the diagram (class, template, enum): - # - Add a note with \brief comment if exists - # - Add a note with \todo for each element which has it - # - Add a note with template parameter descriptions based on \tparam comment - - > - {# Render brief comments and todos, if any were written for an element #} - {% for e in diagram.elements %} - {% if existsIn(e, "comment") and existsIn(e.comment, "brief") %} - - note top of {{ e.alias }} {% if e.type == "class" %} #22AA22 {% else %} #2222AA {% endif %} - {% set c=e.comment %} {{ c.brief.0 }} - end note - - {% endif %} - {% if existsIn(e, "comment") and existsIn(e.comment, "todo") %} - {% set c=e.comment %} - {% for t in c.todo %} - note top of {{ e.alias }} #882222 - **TODO** - {{ t }} - end note - - {% endfor %} - - {% endif %} - {# Render template paramete if any #} - {% if existsIn(e, "comment") and existsIn(e.comment, "tparam") %} - {% set c=e.comment %} - - note top of {{ e.alias }} #AAAAFF - **Template parameters** - {% for tp in c.tparam %} - //{{ tp.name }}// {{ trim(tp.description) }} - {% endfor %} - end note - - {% endif %} - {% endfor %} -``` -Currently there are 2 available comment parsers: -* `plain` - default -* `clang` -They can be selected using `comment_parser` config option. - -#### `plain` comment parser -This parser provides only 2 options to the Jinja context: -* `comment.raw` - raw comment text, including comment markers such as `///` or `/**` -* `comment.formatted` - formatted entire comment - -#### `clang` comment parser -This parser uses Clang comment parsing API to extract commands from the command: -* `comment.raw` - raw comment text, including comment markers such as `///` or `/**` -* `comment.formatted` - formatted entire comment -* `comment..` - where command is the command used in the command e.g. `brief`, `todo`, etc. - and `N` is the index of the command in the array (each comment can have multiple instances of the - same command such as `\todo`) -* `comment.text` - entire text of the comment that is not attached to any command -* `comment.paragraph.` - array of plain text paragraphs, for instance if you don't use `\brief` - commands but often provide brief description as first sentence of the comment separated with a new line - from the rest of the comment - ## Example complete config ```yaml diff --git a/docs/diagram_filters.md b/docs/diagram_filters.md new file mode 100644 index 00000000..946217cf --- /dev/null +++ b/docs/diagram_filters.md @@ -0,0 +1,161 @@ +# Diagram filters + +Diagram filters are at the core of generating diagrams with `clang-uml`, as they allow to fine tune the scope +of each diagram, and thus provide you with a several small, but readable diagrams instead of a single huge diagram +that cannot be effectively browsed, printed or included in an online documentation of your project. + +Filters can be specified separate for each diagram, and they can be added as either `include` or `exclude` filters, +depending on which is more appropriate for a given diagram. + +For instance to include only C++ entities from a namespace `ns1::ns2` but not `ns1::ns2::detail` add the following +to your diagram configuration: + +```yaml + include: + namespaces: + - ns1::ns2 + exclude: + namespaces: + - ns1::ns2::detail +``` + +The following filters are available. + +## `namespaces` + +Allows to include or exclude entities from specific namespaces. + +## `elements` + +Allows to directly include or exclude specific entities from the diagrams, for instance to exclude a specific class +from an included namespace: + +```yaml + include: + namespaces: + - ns1::ns2 + exclude: + elements: + - ns1::ns2::MyClass +``` + +## `context` + +This filter allows to limit the diagram elements only to classes which are in direct relationship (of any kind) with +the specified class: + +```yaml + include: + context: + - ns1::ns2::MyClass +``` + + +## `relationships` + +This filter allows to include or exclude specific types of relationships from the diagram, for instance to only +include inheritance and template specialization/instantiation relationships add the following to the diagram: + +```yaml + include: + relationships: + - inheritance + - instantiation +``` + +The following relationships can be used in this filter: + * inheritance + * composition + * aggregation + * ownership + * association + * instantiation + * friendship + * dependency + +## `subclasses` + +This filter allows to include or exclude all subclasses of a given class in the diagram. + +## `specializations` + +This filter allows to include or exclude specializations and instantiations of a specific template from the diagram. + +## `dependants` and `dependencies` + +These filters allow to specify that only dependants or dependencies of a given class should be included in the diagram. +This can be useful for analyzing what classes in your project depend on some other class, which could have impact for +instance on refactoring. + +For instance the following code: +```cpp + +namespace dependants { +struct A { +}; + +struct B { + void b(A *a) { } +}; + +struct BB { + void bb(A *a) { } +}; + +struct C { + void c(B *b) { } +}; + +struct D { + void d(C *c) { } + void dd(BB *bb) { } +}; + +struct E { + void e(D *d) { } +}; + +struct F { +}; +} // namespace dependants + +namespace dependencies { + +struct G { +}; + +struct GG { +}; + +struct H { + void h(G *g) { } + void hh(GG *gg) { } +}; + +struct HH { + void hh(G *g) { } +}; + +struct I { + void i(H *h) { } +}; + +struct J { + void i(I *i) { } +}; +``` + +and the following filter: +```yaml + include: + dependants: + - clanguml::t00043::dependants::A + dependencies: + - clanguml::t00043::dependencies::J + relationships: + - dependency +``` + +generates the following diagram: + +![t00043_class](./test_cases/t00043_class.svg) diff --git a/docs/doxygen_integration.md b/docs/doxygen_integration.md new file mode 100644 index 00000000..119096c3 --- /dev/null +++ b/docs/doxygen_integration.md @@ -0,0 +1,14 @@ +# Doxygen integration + +`clang-uml` diagrams can be easily added to the Doxygen documentation using the image tag, however +[Doxygen](https://www.doxygen.nl/index.html) does not support the `clang-uml` specific commands. + +`clang-uml` decorators can be omitted completely in Doxygen, by adding the +following lines to the Doxygen config file: + +``` +ALIASES += uml="" +ALIASES += uml{1}="" +ALIASES += uml{2}="" +ALIASES += uml{3}="" +``` \ No newline at end of file diff --git a/docs/include_diagrams.md b/docs/include_diagrams.md new file mode 100644 index 00000000..28daf7cc --- /dev/null +++ b/docs/include_diagrams.md @@ -0,0 +1,51 @@ +# Generating include diagrams + +Include diagrams allow to document the include dependencies among different parts of the project. This can be very useful +for instance to detect that a file was included from a module directory, on which specific part of the project +should not ever depend. + +The minimal config required to generate an include diagram is presented below: +```yaml +# Path to the directory where `compile_commands.json` can be found +compilation_database_dir: _build +# Output directory for the diagrams +output_directory: puml +# Diagrams definitions +diagrams: + # Diagram name + my_class_diagram: + # Type of diagram (has to be `include`) + type: include + # Include only translation units matching the following patterns + glob: + - src/*.cc + # Render the paths relative to this directory + relative_to: src + # Include also external system headers + generate_system_headers: true + # Include only classes and functions from files in `src` directory + include: + # Include only files belonging to these paths + paths: + - src +``` + +One distinctive option in `include` diagrams is `relative_to`, which tells `clang-uml` to render all filename +paths relative to this directory. + +## Tracking system headers directly included by projects files + +In case you would like to include the information about what system headers your projects file include simply add +the following option to the diagram: + +```yaml +generate_system_headers: true +``` + +This will include only system headers directly included from the projects source files (matched by `glob`) and not +their dependencies, for example: + +![t40001_include](./test_cases/t40001_include.svg) + +Please note that generating include diagram, which contains third party and system library headers will result +in a huge diagram that will be unlikely to be useful. \ No newline at end of file diff --git a/docs/interactive_svg_diagrams.md b/docs/interactive_svg_diagrams.md new file mode 100644 index 00000000..92a09d13 --- /dev/null +++ b/docs/interactive_svg_diagrams.md @@ -0,0 +1,15 @@ +# Interactive SVG diagrams + +`clang-uml` in combination with PlantUML's link generation in diagrams allows to generate interactive diagrams, +where clicking on any class, method or call expression can direct the user directly to the source code or some other +diagram or document available online. + +For instance to generate links to GitHub repository directly for most of diagram elements simple add this to your +`.clang-uml` file: +```yaml +generate_links: + link: 'https://github.com/myorg/myrepo/blob/{{ git.commit }}/{{ element.source.path }}#L{{ element.source.line }}' + tooltip: '{% if "comment" in element %}{{ abbrv(trim(replace(element.comment, "\n+", " ")), 256) }}{% else %}{{ element.name }}{% endif %}' +``` + +You can open example diagram [here](https://raw.githubusercontent.com/bkryza/clang-uml/master/docs/test_cases/t00014_class.svg) to see how it works in action. \ No newline at end of file diff --git a/docs/jinja_templates.md b/docs/jinja_templates.md new file mode 100644 index 00000000..168a0c9d --- /dev/null +++ b/docs/jinja_templates.md @@ -0,0 +1,111 @@ +# Template engine +`clang-uml` integrates [inja](https://github.com/pantor/inja) template engine, with several +additional functions which can be used in textual directives within the configuration files, +notes and to generate links and tooltips in diagrams. + +The following, are the `clang-uml` additional template functions: +* `ltrim(string)` - left trims a string +* `rtrim(string)` - right trims a string +* `trim(string)` - trims a string +* `substr(string, offset, length)` - returns a substring of a string from offset of length +* `split(string)` - splits a string and returns a list of strings +* `replace(string, regex, replacement)` - returns a string with replace matches to regex with replacement string +* `abbrv(string, length)` - returns a string truncated to length including trailing ellipsis +* `element(string)` - returns the entire JSON context a given diagram element, including the following properties: + * `name` - name of the element + * `type` - type of diagram element (e.g. `class`, `enum`, `package`) + * `namespace` - fully qualified element namespace + * `full_name` - fully qualified element name + * `comment` [optional] - elements comment, if any + * `alias` - internal diagram element alias (e.g. PlantUML alias) +* `alias(string)` - returns a PlantUML alias of an C++ entity represented by string name +* `comment(string)` - returns a comment of an C++ entity represented by string name + +Templates allow complex postprocessing of the diagrams, for instance creation of customized PlantUML +notes in the diagrams from comments in the code. Below is an example of using the above commands to +generate notes in the PlantUML diagram from code comments (see also test case [t00050](./test_cases/t00050.md)): + +```yaml + plantuml: + after: + # Add a note left of the `A` class with the entire clas comment as content + - > + note left of {{ alias("A") }} + {{ comment("clanguml::t00050::A").formatted }} + end note + # Same as above + - > + note right of {{ element("clanguml::t00050::A").alias }} + {% set e=element("clanguml::t00050::A") %} {{ e.comment.formatted }} + end note + # Add a note left of class 'C' using trimmed text content from the class comment + - > + note left of {{ alias("C") }} #AABBCC + {{ trim(comment("clanguml::t00050::C").text) }} + end note + # For each element in the diagram (class, template, enum): + # - Add a note with \brief comment if exists + # - Add a note with \todo for each element which has it + # - Add a note with template parameter descriptions based on \tparam comment + - > + {# Render brief comments and todos, if any were written for an element #} + {% for e in diagram.elements %} + {% if existsIn(e, "comment") and existsIn(e.comment, "brief") %} + + note top of {{ e.alias }} {% if e.type == "class" %} #22AA22 {% else %} #2222AA {% endif %} + {% set c=e.comment %} {{ c.brief.0 }} + end note + + {% endif %} + {% if existsIn(e, "comment") and existsIn(e.comment, "todo") %} + {% set c=e.comment %} + {% for t in c.todo %} + note top of {{ e.alias }} #882222 + **TODO** + {{ t }} + end note + + {% endfor %} + + {% endif %} + {# Render template paramete if any #} + {% if existsIn(e, "comment") and existsIn(e.comment, "tparam") %} + {% set c=e.comment %} + + note top of {{ e.alias }} #AAAAFF + **Template parameters** + {% for tp in c.tparam %} + //{{ tp.name }}// {{ trim(tp.description) }} + {% endfor %} + end note + + {% endif %} + {% endfor %} +``` + +### Accessing comment content +Text available in the code comment blocks can be accessed in the templates depending on the selected comment +parser. + +Currently there are 2 available comment parsers: +* `plain` - default +* `clang` - Clang's comment parser + +They can be selected using `comment_parser` config option. + +#### `plain` comment parser +This parser provides only 2 options to the Jinja context: +* `comment.raw` - raw comment text, including comment markers such as `///` or `/**` +* `comment.formatted` - formatted entire comment + +#### `clang` comment parser +This parser uses Clang comment parsing API to extract commands from the command: +* `comment.raw` - raw comment text, including comment markers such as `///` or `/**` +* `comment.formatted` - formatted entire comment +* `comment..` - where command is the command used in the command e.g. `brief`, `todo`, etc. + and `N` is the index of the command in the array (each comment can have multiple instances of the + same command such as `\todo`) +* `comment.text` - entire text of the comment that is not attached to any command +* `comment.paragraph.` - array of plain text paragraphs, for instance if you don't use `\brief` + commands but often provide brief description as first sentence of the comment separated with a new line + from the rest of the comment \ No newline at end of file diff --git a/docs/package_diagrams.md b/docs/package_diagrams.md new file mode 100644 index 00000000..03182010 --- /dev/null +++ b/docs/package_diagrams.md @@ -0,0 +1,151 @@ +# Generating package diagrams + +Package diagrams are simple diagrams which can be useful to determine the high level structure of a C++ project, +by rendering all projects namespaces as UML packages and their interdependencies. + +The minimal config required to generate a package diagram is presented below: +```yaml +# Path to the directory where `compile_commands.json` can be found +compilation_database_dir: _build +# Output directory for the diagrams +output_directory: puml +# Diagrams definitions +diagrams: + # Diagram name + my_class_diagram: + # Type of diagram (has to be `package`) + type: package + # Include only translation units matching the following patterns + glob: + - src/*.cc + # Include only classes and functions from files in `src` directory + include: + namespaces: + - ns1::ns2 +``` + +For instance the following C++ code: +``` +namespace A::AA { +namespace A1 { +struct CA { +}; +} +namespace A2 { +struct CB { +}; +} +namespace A3 { +struct CC { +}; +} +namespace A4 { +struct CD { +}; +} +namespace A5 { +struct CE { +}; +} +namespace A6 { +struct CF { +}; +} +namespace A7 { +struct CG { +}; +} +namespace A8 { +struct CH { +}; +} +namespace A9 { +struct CI { +}; +} +namespace A10 { +struct CJ { +}; +} +namespace A11 { +struct CK { +}; +} +namespace A12 { +struct CL { +}; +} +namespace A13 { +struct CM { +}; +} +namespace A14 { +struct CN { +}; +} +namespace A15 { +struct CO { +}; +} +namespace A16 { +struct CP { +}; +} +namespace A17 { +struct CR { +}; +} +} +namespace B::BB::BBB { +class CBA : public A::AA::A6::CF { +public: + A::AA::A1::CA *ca_; + A::AA::A2::CB cb_; + std::shared_ptr cc_; + std::map> *cd_; + std::array co_; + static A::AA::A16::CP *cp_; + + CBA() = default; + + CBA(A::AA::A14::CN *cn) { } + + friend A::AA::A17::CR; + + template CBA(std::tuple &items) { } + + void ce(const std::vector /*ce_*/) { } + + std::shared_ptr cg() { return {}; } + + template + void ch(std::map> &ch_) + { + } + + template + std::map> ci(T * /*t*/) + { + return {}; + } +}; + +void cj(std::unique_ptr /*cj_*/) { } + +std::unique_ptr ck() { return {}; } + +template +void cl(std::map> & /*ch_*/) +{ +} + +template std::map> cm() +{ + return {}; +} +} +``` + +generates the following diagram: + +![package_deps](./test_cases/t30002_package.svg) diff --git a/docs/quick_start.md b/docs/quick_start.md new file mode 100644 index 00000000..23054f05 --- /dev/null +++ b/docs/quick_start.md @@ -0,0 +1,56 @@ +# Quick start + +To add an initial class diagram to your project, follow these steps: + +1. Enter your projects top level directory and run: + ```bash + $ clang-uml --init + ``` +2. Edit the generated `.clang-uml` file and set the following: + ```yaml + # Path to `compile_commands.json` directory + compilation_database_dir: . + # Path to diagram output directory + output_directory: puml + diagrams: + # This is the name of the diagram + some_class_diagram: + type: class + # Parse only translation units in `src` subdirectory + glob: + - src/*.cc + # Render all names relative to `myproject` namespace + using_namespace: + - myproject + include: + # Include only elements in `myproject` namespace + namespaces: + - myproject + exclude: + # Exclude elements in `myproject::detail` namespace + namespaces: + - myproject::detail + ``` +3. Run `clang-uml` in the projects top directory: + ```bash + $ clang-uml + ``` +4. Generate SVG images from the PlantUML diagrams: + ```bash + $ plantuml -tsvg puml/*.puml + ``` +5. Add another diagram: + ```bash + $ clang-uml --add-sequence-diagram another_diagram + ``` +6. Now list the diagrams defined in the config: + ```bash + $ clang-uml -l + The following diagrams are defined in the config file: + - another_diagram [sequence] + - some_class_diagram [class] + ``` +7. Generate only the new diagram: + ```bash + clang-uml -n another_diagram + ``` diff --git a/docs/sequence_diagrams.md b/docs/sequence_diagrams.md new file mode 100644 index 00000000..bc8ffd1d --- /dev/null +++ b/docs/sequence_diagrams.md @@ -0,0 +1,235 @@ +# Generating sequence diagrams + +The minimal config required to generate a sequence diagram is presented below: +```yaml +# Path to the directory where `compile_commands.json` can be found +compilation_database_dir: _build +# Output directory for the diagrams +output_directory: puml +# Diagrams definitions +diagrams: + # Diagram name + my_class_diagram: + # Type of diagram (has to be `sequence`) + type: sequence + # Include only translation units matching the following patterns + glob: + - src/*.cc + # Include only classes and functions from files in `src` directory + include: + paths: + - src + # Exclude calls to/from `std` namespace + exclude: + namespaces: + - std + start_from: + - function: "main(int,const char**)" +``` + +## Sequence diagram overview + +Consider the following diagram: + +![extension](test_cases/t20029_sequence.svg) + +`clang-uml` generated sequence diagrams are not stricly speaking conforming to the UML specification, in order to +make them more useful for documenting modern C++ code, the following assumptions were made: + * Free functions are included in the sequence diagrams as standalone participants (in fact `clang-uml` can be used + to generate sequence diagrams from plain old C code). Functions can also be aggregated into file participants, + based on their place of declaration + * Call expressions in conditional expressions in block statements (e.g. `if` or `while`) are rendered inside the + UML `alt` or `loop` blocks but wrapped in `[`, `]` brackets + * Lambda expressions are generated as standalone participants, whose name comprises of the parent context where they + are defined and the exact source code location + +## Specifying diagram entry point +Sequence diagrams require an entry point for the diagram in order to determine, at which point in the code the sequence +diagram should start. Currently the entry point can only be a method or a free function, both specified using `start_from` +configuration property, for instance: +```yaml + start_from: + - function: "main(int,char**)" +``` +or +```yaml +start_from: + - function: "clanguml::sequence_diagram::visitor::translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *)" +``` + +The entrypoints must be fully qualified and they must match exactly the string representation of given function or +method in the `clang-uml` model, which can be tricky. If not sure, the best way is to put anything in the `function` +property value at first, run the `clang-uml` on the diagram with verbose set to `-vvv` and look in the logs +for the relevant function signature. At the end of the diagram generation at this verbosity level, `clang-uml` will +generate a textual representation of all discovered activities relevant for this diagram, for instance if you're looking +_for exact signature of method `translation_unit_visitor::VisitCXXRecordDecl`, look for similar_ +output in the logs: + +```bash +[trace] [tid 3842954] [diagram.cc:194] Sequence id=1875210076312968845: +[trace] [tid 3842954] [diagram.cc:198] Activity id=1875210076312968845, from=clanguml::sequence_diagram::visitor::translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *): +[trace] [tid 3842954] [diagram.cc:208] Message from=clanguml::sequence_diagram::visitor::translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *), from_id=1875210076312968845, to=__UNRESOLVABLE_ID__, to_id=0, name=, type=if +[trace] [tid 3842954] [diagram.cc:217] Message from=clanguml::sequence_diagram::visitor::translation_unit_visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *), from_id=1875210076312968845, to=clanguml::sequence_diagram::visitor::translation_unit_visitor::should_include(const clang::TagDecl *), to_id=664596622746486441, name=should_include, type=call +``` + +Then you just need to copy and paste the signature exactly and rerun `clang-uml`. + +## Grouping free functions by file +By default, `clang-uml` will generate a new participant for each call to a free function (not method), which can lead +to a very large number of participants in the diagram. If it's an issue, an option can be provided in the diagram +definition: +```yaml +combine_free_functions_into_file_participants: true +``` +which will aggregate free functions per source file where they were declared thus minimizing the +diagram size. An example of such diagram is presented below: + +![extension](test_cases/t20017_sequence.svg) + +## Lambda expressions in sequence diagrams +Lambda expressions in sequence diagrams are... tricky. There is currently tentative support, which follows the +following rules: + * If lambda expression is called within the scope of the diagram, the calls from the lambda will be placed + at the lambda invocation and not declaration + * If lambda expression is passed to some function or method, which is outside the scope of the diagram + (e.g. used in `std::transform` call) the call will be generated at the point where lambda is passed as parameter + * If the lambda is passed as template parameter in instantiation it will not be generated at the moment at all + +Another issue is the naming of lambda participants. Currently, each lambda is rendered in the diagram as a separate +class whose name is composed of the lambda location in the code (the only unique way of identifying lambdas I was able +to find). For example the follwing code: +```cpp +#include +#include +#include +#include +#include + +namespace clanguml { +namespace t20012 { +struct A { + void a() { aa(); } + + void aa() { aaa(); } + + void aaa() { } +}; + +struct B { + void b() { bb(); } + + void bb() { bbb(); } + + void bbb() { } + + void eb() { } +}; + +struct C { + void c() { cc(); } + + void cc() { ccc(); } + + void ccc() { } +}; + +struct D { + int add5(int arg) const { return arg + 5; } +}; + +class E { + std::optional> maybe_b; + std::shared_ptr a; + +public: + template void setup(F &&f) { f(maybe_b); } +}; + +template struct R { + R(F &&f) + : f_{std::move(f)} + { + } + + void r() { f_(); } + + F f_; +}; + +void tmain() +{ + A a; + B b; + C c; + + // The activity shouldn't be marked at the lambda definition, but + // wherever it is actually called... + auto alambda = [&a, &b]() { + a.a(); + b.b(); + }; + + // ...like here + alambda(); + + // There should be no call to B in the sequence diagram as the blambda + // is never called + [[maybe_unused]] auto blambda = [&b]() { b.b(); }; + + // Nested lambdas should also work + auto clambda = [alambda, &c]() { + c.c(); + alambda(); + }; + clambda(); + + R r{[&c]() { c.c(); }}; + + r.r(); + + D d; + + std::vector ints{0, 1, 2, 3, 4}; + std::transform(ints.begin(), ints.end(), ints.begin(), + [&d](auto i) { return d.add5(i); }); +} +} +} +``` + +generates the following diagram: + +![extension](test_cases/t20012_sequence.svg) + +## Customizing participants order +The default participant order in the sequence diagram can be suboptimal in the sense that consecutive calls +can go right, then left, then right again depending on the specific call chain in the code. It is however +possible to override this order in the diagram definition using `participants_order` property, +for instance like this test case: + +```yaml +compilation_database_dir: .. +output_directory: puml +diagrams: + t20029_sequence: + type: sequence + glob: + - ../../tests/t20029/t20029.cc + include: + namespaces: + - clanguml::t20029 + exclude: + access: + - private + using_namespace: + - clanguml::t20029 + start_from: + - function: clanguml::t20029::tmain() + participants_order: + - clanguml::t20029::tmain() + - clanguml::t20029::Encoder> + - clanguml::t20029::Retrier + - clanguml::t20029::ConnectionPool + - clanguml::t20029::encode_b64(std::string &&) +``` + diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md new file mode 100644 index 00000000..cb624294 --- /dev/null +++ b/docs/troubleshooting.md @@ -0,0 +1,56 @@ +# Troubleshooting + +## General issues +### Diagram generated with PlantUML is cropped +When generating diagrams with PlantUML without specifying an output file format, the default is PNG. +Unfortunately PlantUML will not check if the diagram will fit in the default PNG size, and often the diagram +will be be incomplete in the picture. A better option is to specify SVG as output format and then convert +to PNG, e.g.: +```bash +$ plantuml -tsvg mydiagram.puml +$ convert +antialias mydiagram.svg mydiagram.png +``` + +## Class diagrams +### "fatal error: 'stddef.h' file not found" +This error means that Clang cannot find some standard headers in the include paths +specified in the `compile_commands.json`. This typically happens on macos and sometimes on Linux, when +the code was compiled with different Clang version than `clang-uml` itself. + +One solution to this issue is to add the following line to your `CMakeLists.txt` file: + +```cmake +set(CMAKE_CXX_STANDARD_INCLUDE_DIRECTORIES ${CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES}) +``` + +Another option is to make sure that the Clang is installed on the system (even if not used for building your +project), e.g.: +```bash +apt install clang +``` + +## Sequence diagrams +### Generated diagram is empty +In order to generate sequence diagram the `start_from` configuration option must have a valid starting point +for the diagram (e.g. `function`), which must match exactly the function signature in the `clang-uml` model. +Look for error in the console output such as: +```bash +Failed to find participant mynamespace::foo(int) for start_from condition +``` +which means that either you have a typo in the function signature in the configuration file, or that the function +was not defined in the translation units you specified in the `glob` patterns for this diagram. Run again the +`clang-uml` tool with `-vvv` option and look in the console output for any mentions of the function from +which the diagram should start and copy the exact signature into the configuration file. + +### Generated diagram contains several empty control blocks or calls which should not be there +Currently the filtering of call expressions and purging empty control blocks (e.g. loops or conditional statements), +within which no interesting calls were included in the diagram is not perfect. In case the regular `namespaces` filter +is not enough, it is useful to add also a `paths` filter, which will only include participants and call expressions +from files in a subdirectory of your project, e.g.: +```yaml + include: + namespaces: + - myproject + paths: + - src +``` \ No newline at end of file