Added initial documentation

This commit is contained in:
Bartek Kryza
2023-01-01 20:36:58 +01:00
parent 9d7275e388
commit e4a29c7117
15 changed files with 1328 additions and 137 deletions

18
docs/README.md Normal file
View File

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

159
docs/class_diagrams.md Normal file
View File

@@ -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> a;
std::shared_ptr<B> b;
std::weak_ptr<C> 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.

222
docs/comment_decorators.md Normal file
View File

@@ -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{<decorator>[:<diagram_name>][<options>] <text>}
```
or
```
@uml{<decorator>[:<diagram_name>][<options>] <text>}
```
The optional `:<diagram_name>` 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 <typename T> 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<std::shared_ptr<D>> ddd;
E<int> 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 <typename T> class C {
T param;
};
/// @uml{skip:t00029_class}
template <typename T> 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> 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<B> bbb;
/// @uml{aggregation[0..1:1..5]}
std::vector<C> 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 <typename T> 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<B> bbb;
/// @uml{style[#blue,dotted,thickness=8]}
C<int> ccc;
/// @uml{style[#blue,plain,thickness=16]}
D *ddd;
};
```
generates the following diagram:
![skip](./test_cases/t00031_class.svg)

73
docs/common_options.md Normal file
View File

@@ -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
<common options for all diagrams>
diagrams:
<first diagram name>:
type: [class|sequence|package|include]
<diagram specific options>
<second diagram name>:
type: [class|sequence|package|include]
<diagram specific options>
...
```
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.

View File

@@ -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.<command>.<N>` - 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.<N>` - 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

161
docs/diagram_filters.md Normal file
View File

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

View File

@@ -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}=""
```

51
docs/include_diagrams.md Normal file
View File

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

View File

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

111
docs/jinja_templates.md Normal file
View File

@@ -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.<command>.<N>` - 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.<N>` - 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

151
docs/package_diagrams.md Normal file
View File

@@ -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<A::AA::A3::CC> cc_;
std::map<std::string, std::unique_ptr<A::AA::A4::CD>> *cd_;
std::array<A::AA::A15::CO, 5> co_;
static A::AA::A16::CP *cp_;
CBA() = default;
CBA(A::AA::A14::CN *cn) { }
friend A::AA::A17::CR;
template <typename... Item> CBA(std::tuple<Item...> &items) { }
void ce(const std::vector<A::AA::A5::CE> /*ce_*/) { }
std::shared_ptr<A::AA::A7::CG> cg() { return {}; }
template <typename T>
void ch(std::map<T, std::shared_ptr<A::AA::A8::CH>> &ch_)
{
}
template <typename T>
std::map<T, std::shared_ptr<A::AA::A9::CI>> ci(T * /*t*/)
{
return {};
}
};
void cj(std::unique_ptr<A::AA::A10::CJ> /*cj_*/) { }
std::unique_ptr<A::AA::A11::CK> ck() { return {}; }
template <typename T>
void cl(std::map<T, std::shared_ptr<A::AA::A12::CL>> & /*ch_*/)
{
}
template <typename T> std::map<T, std::shared_ptr<A::AA::A13::CM>> cm()
{
return {};
}
}
```
generates the following diagram:
![package_deps](./test_cases/t30002_package.svg)

56
docs/quick_start.md Normal file
View File

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

235
docs/sequence_diagrams.md Normal file
View File

@@ -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 <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
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<std::shared_ptr<B>> maybe_b;
std::shared_ptr<A> a;
public:
template <typename F> void setup(F &&f) { f(maybe_b); }
};
template <typename F> 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<int> 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::Retrier<clanguml::t20029::ConnectionPool>
- clanguml::t20029::ConnectionPool
- clanguml::t20029::encode_b64(std::string &&)
```

56
docs/troubleshooting.md Normal file
View File

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