Merge pull request #5 from bkryza/add-first-version-of-inline-directive-support
Add first version of inline directive support
This commit is contained in:
@@ -26,6 +26,10 @@
|
||||
* [t00025](./test_cases/t00025.md) - Template proxy pattern
|
||||
* [t00026](./test_cases/t00026.md) - Template memento pattern
|
||||
* [t00027](./test_cases/t00027.md) - Template decorator pattern
|
||||
* [t00028](./test_cases/t00028.md) - PlantUML note decorator test case
|
||||
* [t00029](./test_cases/t00029.md) - PlantUML skip decorator test case
|
||||
* [t00030](./test_cases/t00030.md) - PlantUML relationship decorators test case
|
||||
* [t00031](./test_cases/t00031.md) - PlantUML style decorator test case
|
||||
## Sequence diagrams
|
||||
* [t20001](./test_cases/t20001.md) - Basic sequence diagram
|
||||
## Configuration diagrams
|
||||
|
||||
80
docs/test_cases/t00028.md
Normal file
80
docs/test_cases/t00028.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# t00028 - PlantUML note decorator test case
|
||||
## Config
|
||||
```yaml
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00028_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00028/t00028.cc
|
||||
using_namespace:
|
||||
- clanguml::t00028
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00028
|
||||
|
||||
```
|
||||
## Source code
|
||||
File t00028.cc
|
||||
```cpp
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00028 {
|
||||
|
||||
/// \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 {
|
||||
A aaa;
|
||||
|
||||
B *bbb;
|
||||
|
||||
C &ccc;
|
||||
|
||||
std::vector<std::shared_ptr<D>> ddd;
|
||||
|
||||
E<int> eee;
|
||||
|
||||
G **ggg;
|
||||
};
|
||||
|
||||
} // namespace t00028
|
||||
} // namespace clanguml
|
||||
|
||||
```
|
||||
## Generated UML diagrams
|
||||

|
||||
BIN
docs/test_cases/t00028_class.png
Normal file
BIN
docs/test_cases/t00028_class.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
77
docs/test_cases/t00029.md
Normal file
77
docs/test_cases/t00029.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# t00029 - PlantUML skip decorator test case
|
||||
## Config
|
||||
```yaml
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00029_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00029/t00029.cc
|
||||
using_namespace:
|
||||
- clanguml::t00029
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00029
|
||||
|
||||
```
|
||||
## Source code
|
||||
File t00029.cc
|
||||
```cpp
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00029 {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace t00029
|
||||
} // namespace clanguml
|
||||
|
||||
```
|
||||
## Generated UML diagrams
|
||||

|
||||
BIN
docs/test_cases/t00029_class.png
Normal file
BIN
docs/test_cases/t00029_class.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
58
docs/test_cases/t00030.md
Normal file
58
docs/test_cases/t00030.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# t00030 - PlantUML relationship decorators test case
|
||||
## Config
|
||||
```yaml
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00030_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00030/t00030.cc
|
||||
using_namespace:
|
||||
- clanguml::t00030
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00030
|
||||
|
||||
```
|
||||
## Source code
|
||||
File t00030.cc
|
||||
```cpp
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00030 {
|
||||
|
||||
class A {
|
||||
};
|
||||
|
||||
class B {
|
||||
};
|
||||
|
||||
class C {
|
||||
};
|
||||
|
||||
class D {
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace t00030
|
||||
} // namespace clanguml
|
||||
|
||||
```
|
||||
## Generated UML diagrams
|
||||

|
||||
BIN
docs/test_cases/t00030_class.png
Normal file
BIN
docs/test_cases/t00030_class.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
62
docs/test_cases/t00031.md
Normal file
62
docs/test_cases/t00031.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# t00031 - PlantUML style decorator test case
|
||||
## Config
|
||||
```yaml
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00031_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00031/t00031.cc
|
||||
using_namespace:
|
||||
- clanguml::t00031
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00031
|
||||
|
||||
```
|
||||
## Source code
|
||||
File t00031.cc
|
||||
```cpp
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00031 {
|
||||
|
||||
/// @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;
|
||||
};
|
||||
|
||||
} // namespace t00031
|
||||
} // namespace clanguml
|
||||
|
||||
```
|
||||
## Generated UML diagrams
|
||||

|
||||
BIN
docs/test_cases/t00031_class.png
Normal file
BIN
docs/test_cases/t00031_class.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -75,24 +75,24 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
std::string to_string(relationship_t r) const
|
||||
std::string to_string(relationship_t r, std::string style = "") const
|
||||
{
|
||||
switch (r) {
|
||||
case relationship_t::kOwnership:
|
||||
case relationship_t::kComposition:
|
||||
return "*--";
|
||||
return style.empty() ? "*--" : fmt::format("*-[{}]-", style);
|
||||
case relationship_t::kAggregation:
|
||||
return "o--";
|
||||
return style.empty() ? "o--" : fmt::format("o-[{}]-", style);
|
||||
case relationship_t::kContainment:
|
||||
return "--+";
|
||||
return style.empty() ? "--+" : fmt::format("-[{}]-+", style);
|
||||
case relationship_t::kAssociation:
|
||||
return "-->";
|
||||
return style.empty() ? "-->" : fmt::format("-[{}]->", style);
|
||||
case relationship_t::kInstantiation:
|
||||
return "..|>";
|
||||
return style.empty() ? "..|>" : fmt::format(".[{}].|>", style);
|
||||
case relationship_t::kFriendship:
|
||||
return "<..";
|
||||
return style.empty() ? "<.." : fmt::format("<.[{}].", style);
|
||||
case relationship_t::kDependency:
|
||||
return "..>";
|
||||
return style.empty() ? "..>" : fmt::format(".[{}].>", style);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
|
||||
ostr << class_type << " \"" << c.full_name(m_config.using_namespace);
|
||||
|
||||
ostr << "\" as " << c.alias() << std::endl;
|
||||
ostr << "\" as " << c.alias() << '\n';
|
||||
}
|
||||
|
||||
void generate_alias(const enum_ &e, std::ostream &ostr) const
|
||||
@@ -137,7 +137,7 @@ public:
|
||||
ostr << "enum"
|
||||
<< " \"" << e.full_name(m_config.using_namespace);
|
||||
|
||||
ostr << "\" as " << e.alias() << std::endl;
|
||||
ostr << "\" as " << e.alias() << '\n';
|
||||
}
|
||||
|
||||
void generate(const class_ &c, std::ostream &ostr) const
|
||||
@@ -149,7 +149,12 @@ public:
|
||||
if (c.is_abstract())
|
||||
class_type = "abstract";
|
||||
|
||||
ostr << class_type << " " << c.alias() << " {" << std::endl;
|
||||
ostr << class_type << " " << c.alias();
|
||||
|
||||
if (!c.style.empty())
|
||||
ostr << " " << c.style;
|
||||
|
||||
ostr << " {" << '\n';
|
||||
|
||||
//
|
||||
// Process methods
|
||||
@@ -192,7 +197,7 @@ public:
|
||||
|
||||
ostr << " : " << ns_relative(uns, type);
|
||||
|
||||
ostr << std::endl;
|
||||
ostr << '\n';
|
||||
}
|
||||
|
||||
//
|
||||
@@ -224,9 +229,18 @@ public:
|
||||
destination = r.destination;
|
||||
}
|
||||
|
||||
std::string puml_relation;
|
||||
if (!r.multiplicity_source.empty())
|
||||
puml_relation += "\"" + r.multiplicity_source + "\" ";
|
||||
|
||||
puml_relation += to_string(r.type, r.style);
|
||||
|
||||
if (!r.multiplicity_destination.empty())
|
||||
puml_relation += " \"" + r.multiplicity_destination + "\"";
|
||||
|
||||
relstr << m_model.to_alias(
|
||||
uns, ns_relative(uns, c.full_name(uns)))
|
||||
<< " " << to_string(r.type) << " "
|
||||
<< " " << puml_relation << " "
|
||||
<< m_model.to_alias(uns, ns_relative(uns, destination));
|
||||
|
||||
if (!r.label.empty()) {
|
||||
@@ -234,7 +248,7 @@ public:
|
||||
rendered_relations.emplace(r.label);
|
||||
}
|
||||
|
||||
relstr << std::endl;
|
||||
relstr << '\n';
|
||||
|
||||
all_relations_str << relstr.str();
|
||||
}
|
||||
@@ -260,10 +274,10 @@ public:
|
||||
ostr << "{static} ";
|
||||
|
||||
ostr << to_string(m.scope) << m.name << " : "
|
||||
<< ns_relative(uns, m.type) << std::endl;
|
||||
<< ns_relative(uns, m.type) << '\n';
|
||||
}
|
||||
|
||||
ostr << "}" << std::endl;
|
||||
ostr << "}" << '\n';
|
||||
|
||||
if (m_config.should_include_relationship("inheritance"))
|
||||
for (const auto &b : c.bases) {
|
||||
@@ -273,7 +287,7 @@ public:
|
||||
<< " <|-- "
|
||||
<< m_model.to_alias(
|
||||
uns, ns_relative(uns, c.full_name(uns)))
|
||||
<< std::endl;
|
||||
<< '\n';
|
||||
ostr << relstr.str();
|
||||
}
|
||||
catch (error::uml_alias_missing &e) {
|
||||
@@ -283,19 +297,36 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Process notes
|
||||
//
|
||||
for (auto decorator : c.decorators) {
|
||||
auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
|
||||
if (note && note->applies_to_diagram(m_config.name)) {
|
||||
ostr << "note " << note->position << " of " << c.alias() << '\n'
|
||||
<< note->text << '\n'
|
||||
<< "end note\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Print relationships
|
||||
ostr << all_relations_str.str();
|
||||
}
|
||||
|
||||
void generate(const enum_ &e, std::ostream &ostr) const
|
||||
{
|
||||
ostr << "enum " << e.alias() << " {" << std::endl;
|
||||
ostr << "enum " << e.alias();
|
||||
|
||||
if (!e.style.empty())
|
||||
ostr << " " << e.style;
|
||||
|
||||
ostr << " {" << '\n';
|
||||
|
||||
for (const auto &enum_constant : e.constants) {
|
||||
ostr << enum_constant << std::endl;
|
||||
ostr << enum_constant << '\n';
|
||||
}
|
||||
|
||||
ostr << "}" << std::endl;
|
||||
ostr << "}" << '\n';
|
||||
|
||||
for (const auto &r : e.relationships) {
|
||||
if (!m_config.should_include_relationship(name(r.type)))
|
||||
@@ -327,7 +358,8 @@ public:
|
||||
if (!r.label.empty())
|
||||
relstr << " : " << r.label;
|
||||
|
||||
relstr << std::endl;
|
||||
relstr << '\n';
|
||||
|
||||
ostr << relstr.str();
|
||||
}
|
||||
catch (error::uml_alias_missing &ex) {
|
||||
@@ -336,11 +368,23 @@ public:
|
||||
to_string(r.type), e.name, destination, ex.what());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Process notes
|
||||
//
|
||||
for (auto decorator : e.decorators) {
|
||||
auto note = std::dynamic_pointer_cast<decorators::note>(decorator);
|
||||
if (note && note->applies_to_diagram(m_config.name)) {
|
||||
ostr << "note " << note->position << " of " << e.alias() << '\n'
|
||||
<< note->text << '\n'
|
||||
<< "end note\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void generate(std::ostream &ostr) const
|
||||
{
|
||||
ostr << "@startuml" << std::endl;
|
||||
ostr << "@startuml" << '\n';
|
||||
|
||||
for (const auto &b : m_config.puml.before) {
|
||||
std::string note{b};
|
||||
@@ -352,7 +396,7 @@ public:
|
||||
note.replace(
|
||||
std::get<1>(alias_match), std::get<2>(alias_match), alias);
|
||||
}
|
||||
ostr << note << std::endl;
|
||||
ostr << note << '\n';
|
||||
}
|
||||
|
||||
if (m_config.should_include_entities("classes")) {
|
||||
@@ -361,14 +405,14 @@ public:
|
||||
!m_config.should_include(c.name))
|
||||
continue;
|
||||
generate_alias(c, ostr);
|
||||
ostr << std::endl;
|
||||
ostr << '\n';
|
||||
}
|
||||
|
||||
for (const auto &e : m_model.enums) {
|
||||
if (!m_config.should_include(e.name))
|
||||
continue;
|
||||
generate_alias(e, ostr);
|
||||
ostr << std::endl;
|
||||
ostr << '\n';
|
||||
}
|
||||
|
||||
for (const auto &c : m_model.classes) {
|
||||
@@ -376,14 +420,14 @@ public:
|
||||
!m_config.should_include(c.name))
|
||||
continue;
|
||||
generate(c, ostr);
|
||||
ostr << std::endl;
|
||||
ostr << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (m_config.should_include_entities("enums"))
|
||||
for (const auto &e : m_model.enums) {
|
||||
generate(e, ostr);
|
||||
ostr << std::endl;
|
||||
ostr << '\n';
|
||||
}
|
||||
|
||||
// Process aliases in any of the puml directives
|
||||
@@ -397,10 +441,10 @@ public:
|
||||
note.replace(
|
||||
std::get<1>(alias_match), std::get<2>(alias_match), alias);
|
||||
}
|
||||
ostr << note << std::endl;
|
||||
ostr << note << '\n';
|
||||
}
|
||||
|
||||
ostr << "@enduml" << std::endl;
|
||||
ostr << "@enduml" << '\n';
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const generator &g);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "decorators.h"
|
||||
#include "util/error.h"
|
||||
#include "util/util.h"
|
||||
|
||||
@@ -53,7 +54,61 @@ enum class relationship_t {
|
||||
|
||||
std::string to_string(relationship_t r);
|
||||
|
||||
class element {
|
||||
struct stylable_element {
|
||||
std::string style;
|
||||
};
|
||||
|
||||
struct decorated_element {
|
||||
std::vector<std::shared_ptr<decorators::decorator>> decorators;
|
||||
|
||||
bool skip() const
|
||||
{
|
||||
for (auto d : decorators)
|
||||
if (std::dynamic_pointer_cast<decorators::skip>(d))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool skip_relationship() const
|
||||
{
|
||||
for (auto d : decorators)
|
||||
if (std::dynamic_pointer_cast<decorators::skip_relationship>(d))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<relationship_t, std::string> relationship() const
|
||||
{
|
||||
for (auto &d : decorators)
|
||||
if (std::dynamic_pointer_cast<decorators::association>(d))
|
||||
return {relationship_t::kAssociation,
|
||||
std::dynamic_pointer_cast<decorators::relationship>(d)
|
||||
->multiplicity};
|
||||
else if (std::dynamic_pointer_cast<decorators::aggregation>(d))
|
||||
return {relationship_t::kAggregation,
|
||||
std::dynamic_pointer_cast<decorators::relationship>(d)
|
||||
->multiplicity};
|
||||
else if (std::dynamic_pointer_cast<decorators::composition>(d))
|
||||
return {relationship_t::kComposition,
|
||||
std::dynamic_pointer_cast<decorators::relationship>(d)
|
||||
->multiplicity};
|
||||
|
||||
return {relationship_t::kNone, ""};
|
||||
}
|
||||
|
||||
std::string style_spec()
|
||||
{
|
||||
for (auto d : decorators)
|
||||
if (std::dynamic_pointer_cast<decorators::style>(d))
|
||||
return std::dynamic_pointer_cast<decorators::style>(d)->spec;
|
||||
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
class element : public decorated_element {
|
||||
public:
|
||||
element()
|
||||
: m_id{m_nextId++}
|
||||
@@ -71,7 +126,7 @@ private:
|
||||
static std::atomic_uint64_t m_nextId;
|
||||
};
|
||||
|
||||
struct class_element {
|
||||
struct class_element : public decorated_element {
|
||||
scope_t scope;
|
||||
std::string name;
|
||||
std::string type;
|
||||
@@ -82,7 +137,7 @@ struct class_member : public class_element {
|
||||
bool is_static{false};
|
||||
};
|
||||
|
||||
struct method_parameter {
|
||||
struct method_parameter : public decorated_element {
|
||||
std::string type;
|
||||
std::string name;
|
||||
std::string default_value;
|
||||
@@ -115,11 +170,11 @@ struct class_parent {
|
||||
access_t access;
|
||||
};
|
||||
|
||||
struct class_relationship {
|
||||
struct class_relationship : public decorated_element, public stylable_element {
|
||||
relationship_t type{relationship_t::kAssociation};
|
||||
std::string destination;
|
||||
std::string cardinality_source;
|
||||
std::string cardinality_destination;
|
||||
std::string multiplicity_source;
|
||||
std::string multiplicity_destination;
|
||||
std::string label;
|
||||
scope_t scope{scope_t::kNone};
|
||||
|
||||
@@ -148,7 +203,7 @@ struct type_alias {
|
||||
std::string underlying_type;
|
||||
};
|
||||
|
||||
class class_ : public element {
|
||||
class class_ : public element, public stylable_element {
|
||||
public:
|
||||
std::string usr;
|
||||
bool is_struct{false};
|
||||
@@ -233,7 +288,7 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct enum_ : public element {
|
||||
struct enum_ : public element, public stylable_element {
|
||||
std::vector<std::string> constants;
|
||||
std::vector<class_relationship> relationships;
|
||||
|
||||
|
||||
@@ -193,6 +193,18 @@ void tu_visitor::process_enum_declaration(const cppast::cpp_enum &enm)
|
||||
enum_ e;
|
||||
e.name = cx::util::full_name(ctx.namespace_, enm);
|
||||
|
||||
if (enm.comment().has_value())
|
||||
e.decorators = decorators::parse(enm.comment().value());
|
||||
|
||||
if (e.skip())
|
||||
return;
|
||||
|
||||
e.style = e.style_spec();
|
||||
|
||||
// Process enum documentation comment
|
||||
if (enm.comment().has_value())
|
||||
e.decorators = decorators::parse(enm.comment().value());
|
||||
|
||||
for (const auto &ev : enm) {
|
||||
if (ev.kind() == cppast::cpp_entity_kind::enum_value_t) {
|
||||
e.constants.push_back(ev.name());
|
||||
@@ -226,9 +238,28 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls,
|
||||
c.is_struct = cls.class_kind() == cppast::cpp_class_kind::struct_t;
|
||||
c.name = cx::util::full_name(ctx.namespace_, cls);
|
||||
|
||||
if (cls.comment().has_value())
|
||||
c.decorators = decorators::parse(cls.comment().value());
|
||||
|
||||
cppast::cpp_access_specifier_kind last_access_specifier =
|
||||
cppast::cpp_access_specifier_kind::cpp_private;
|
||||
|
||||
// Process class documentation comment
|
||||
if (cppast::is_templated(cls)) {
|
||||
if (cls.parent().value().comment().has_value())
|
||||
c.decorators =
|
||||
decorators::parse(cls.parent().value().comment().value());
|
||||
}
|
||||
else {
|
||||
if (cls.comment().has_value())
|
||||
c.decorators = decorators::parse(cls.comment().value());
|
||||
}
|
||||
|
||||
if (c.skip())
|
||||
return;
|
||||
|
||||
c.style = c.style_spec();
|
||||
|
||||
// Process class child entities
|
||||
if (c.is_struct)
|
||||
last_access_specifier = cppast::cpp_access_specifier_kind::cpp_public;
|
||||
@@ -240,7 +271,6 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls,
|
||||
}
|
||||
else if (child.kind() == cppast::cpp_entity_kind::member_variable_t) {
|
||||
auto &mv = static_cast<const cppast::cpp_member_variable &>(child);
|
||||
LOG_DBG("Found member variable {}", mv.name());
|
||||
process_field(mv, c, last_access_specifier);
|
||||
}
|
||||
else if (child.kind() == cppast::cpp_entity_kind::variable_t) {
|
||||
@@ -489,7 +519,7 @@ void tu_visitor::process_class_declaration(const cppast::cpp_class &cls,
|
||||
|
||||
bool tu_visitor::process_field_with_template_instantiation(
|
||||
const cppast::cpp_member_variable &mv, const cppast::cpp_type &tr,
|
||||
class_ &c, cppast::cpp_access_specifier_kind as)
|
||||
class_ &c, class_member &m, cppast::cpp_access_specifier_kind as)
|
||||
{
|
||||
LOG_DBG("Processing field with template instatiation type {}",
|
||||
cppast::to_string(tr));
|
||||
@@ -546,6 +576,17 @@ bool tu_visitor::process_field_with_template_instantiation(
|
||||
rr.type = relationship_t::kAggregation;
|
||||
rr.label = mv.name();
|
||||
rr.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
rr.style = m.style_spec();
|
||||
|
||||
auto [decorator_rtype, decorator_rmult] = m.relationship();
|
||||
if (decorator_rtype != relationship_t::kNone) {
|
||||
rr.type = decorator_rtype;
|
||||
auto mult = util::split(decorator_rmult, ":");
|
||||
if (mult.size() == 2) {
|
||||
rr.multiplicity_source = mult[0];
|
||||
rr.multiplicity_destination = mult[1];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("Adding field instantiation relationship {} {} {} : {}",
|
||||
rr.destination, model::class_diagram::to_string(rr.type), c.usr,
|
||||
@@ -576,6 +617,12 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c,
|
||||
m.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
m.is_static = false;
|
||||
|
||||
if (mv.comment().has_value())
|
||||
m.decorators = decorators::parse(mv.comment().value());
|
||||
|
||||
if (m.skip())
|
||||
return;
|
||||
|
||||
auto &tr = cx::util::unreferenced(cppast::remove_cv(mv.type()));
|
||||
|
||||
LOG_DBG("Processing field {} with unreferenced type of kind {}", mv.name(),
|
||||
@@ -591,7 +638,7 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c,
|
||||
else if (tr.kind() == cppast::cpp_type_kind::template_instantiation_t) {
|
||||
template_instantiation_added_as_aggregation =
|
||||
process_field_with_template_instantiation(
|
||||
mv, resolve_alias(tr), c, as);
|
||||
mv, resolve_alias(tr), c, m, as);
|
||||
}
|
||||
else if (tr.kind() == cppast::cpp_type_kind::unexposed_t) {
|
||||
LOG_DBG(
|
||||
@@ -599,7 +646,8 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c,
|
||||
// TODO
|
||||
}
|
||||
|
||||
if (!template_instantiation_added_as_aggregation &&
|
||||
if (!m.skip_relationship() &&
|
||||
!template_instantiation_added_as_aggregation &&
|
||||
(tr.kind() != cppast::cpp_type_kind::builtin_t) &&
|
||||
(tr.kind() != cppast::cpp_type_kind::template_parameter_t)) {
|
||||
const auto &ttt = resolve_alias(mv.type());
|
||||
@@ -613,6 +661,17 @@ void tu_visitor::process_field(const cppast::cpp_member_variable &mv, class_ &c,
|
||||
r.type = relationship_type;
|
||||
r.label = m.name;
|
||||
r.scope = m.scope;
|
||||
r.style = m.style_spec();
|
||||
|
||||
auto [decorator_rtype, decorator_rmult] = m.relationship();
|
||||
if (decorator_rtype != relationship_t::kNone) {
|
||||
r.type = decorator_rtype;
|
||||
auto mult = util::split(decorator_rmult, ":");
|
||||
if (mult.size() == 2) {
|
||||
r.multiplicity_source = mult[0];
|
||||
r.multiplicity_destination = mult[1];
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DBG("Adding field relationship {} {} {} : {}",
|
||||
r.destination, model::class_diagram::to_string(r.type),
|
||||
@@ -650,6 +709,12 @@ void tu_visitor::process_static_field(const cppast::cpp_variable &mv, class_ &c,
|
||||
m.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
m.is_static = true;
|
||||
|
||||
if (mv.comment().has_value())
|
||||
m.decorators = decorators::parse(mv.comment().value());
|
||||
|
||||
if (m.skip())
|
||||
return;
|
||||
|
||||
c.members.emplace_back(std::move(m));
|
||||
}
|
||||
|
||||
@@ -666,6 +731,12 @@ void tu_visitor::process_method(const cppast::cpp_member_function &mf,
|
||||
m.is_static = false;
|
||||
m.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
|
||||
if (mf.comment().has_value())
|
||||
m.decorators = decorators::parse(mf.comment().value());
|
||||
|
||||
if (m.skip())
|
||||
return;
|
||||
|
||||
for (auto ¶m : mf.parameters())
|
||||
process_function_parameter(param, m, c);
|
||||
|
||||
@@ -695,6 +766,12 @@ void tu_visitor::process_template_method(
|
||||
m.is_static = false;
|
||||
m.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
|
||||
if (mf.comment().has_value())
|
||||
m.decorators = decorators::parse(mf.comment().value());
|
||||
|
||||
if (m.skip())
|
||||
return;
|
||||
|
||||
for (auto ¶m : mf.function().parameters())
|
||||
process_function_parameter(param, m, c);
|
||||
|
||||
@@ -716,6 +793,12 @@ void tu_visitor::process_static_method(const cppast::cpp_function &mf,
|
||||
m.is_static = true;
|
||||
m.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
|
||||
if (mf.comment().has_value())
|
||||
m.decorators = decorators::parse(mf.comment().value());
|
||||
|
||||
if (m.skip())
|
||||
return;
|
||||
|
||||
for (auto ¶m : mf.parameters())
|
||||
process_function_parameter(param, m, c);
|
||||
|
||||
@@ -737,6 +820,12 @@ void tu_visitor::process_constructor(const cppast::cpp_constructor &mf,
|
||||
m.is_static = false;
|
||||
m.scope = detail::cpp_access_specifier_to_scope(as);
|
||||
|
||||
if (mf.comment().has_value())
|
||||
m.decorators = decorators::parse(mf.comment().value());
|
||||
|
||||
if (m.skip())
|
||||
return;
|
||||
|
||||
for (auto ¶m : mf.parameters())
|
||||
process_function_parameter(param, m, c);
|
||||
|
||||
@@ -764,6 +853,13 @@ void tu_visitor::process_function_parameter(
|
||||
{
|
||||
method_parameter mp;
|
||||
mp.name = param.name();
|
||||
|
||||
if (param.comment().has_value())
|
||||
m.decorators = decorators::parse(param.comment().value());
|
||||
|
||||
if (mp.skip())
|
||||
return;
|
||||
|
||||
const auto ¶m_type =
|
||||
cppast::remove_cv(cx::util::unreferenced(param.type()));
|
||||
if (param_type.kind() == cppast::cpp_type_kind::template_instantiation_t) {
|
||||
@@ -777,7 +873,7 @@ void tu_visitor::process_function_parameter(
|
||||
}
|
||||
|
||||
auto dv = param.default_value();
|
||||
if (dv)
|
||||
if (dv) {
|
||||
switch (dv.value().kind()) {
|
||||
case cppast::cpp_expression_kind::literal_t:
|
||||
mp.default_value =
|
||||
@@ -794,20 +890,24 @@ void tu_visitor::process_function_parameter(
|
||||
default:
|
||||
mp.default_value = "{}";
|
||||
}
|
||||
}
|
||||
|
||||
if (!mp.skip_relationship()) {
|
||||
// find relationship for the type
|
||||
std::vector<std::pair<std::string, relationship_t>> relationships;
|
||||
find_relationships(cppast::remove_cv(param.type()), relationships,
|
||||
relationship_t::kDependency);
|
||||
for (const auto &[type, relationship_type] : relationships) {
|
||||
if ((relationship_type != relationship_t::kNone) && (type != c.name)) {
|
||||
if ((relationship_type != relationship_t::kNone) &&
|
||||
(type != c.name)) {
|
||||
class_relationship r;
|
||||
r.destination = type;
|
||||
r.type = relationship_t::kDependency;
|
||||
r.label = "";
|
||||
|
||||
LOG_DBG("Adding field relationship {} {} {} : {}", r.destination,
|
||||
model::class_diagram::to_string(r.type), c.usr, r.label);
|
||||
LOG_DBG("Adding field relationship {} {} {} : {}",
|
||||
r.destination, model::class_diagram::to_string(r.type),
|
||||
c.usr, r.label);
|
||||
|
||||
c.add_relationship(std::move(r));
|
||||
}
|
||||
@@ -828,15 +928,15 @@ void tu_visitor::process_function_parameter(
|
||||
.get(ctx.entity_index)[0]
|
||||
.get());
|
||||
|
||||
LOG_DBG(
|
||||
"Maybe building instantiation for: {}", primary_template_name);
|
||||
LOG_DBG("Maybe building instantiation for: {}",
|
||||
primary_template_name);
|
||||
|
||||
if (ctx.config.should_include(primary_template_name)) {
|
||||
class_ tinst =
|
||||
build_template_instantiation(template_instantiation_type);
|
||||
class_ tinst = build_template_instantiation(
|
||||
template_instantiation_type);
|
||||
|
||||
LOG_DBG("Created template instantiation: {}, {}", tinst.name,
|
||||
tinst.usr);
|
||||
LOG_DBG("Created template instantiation: {}, {}",
|
||||
tinst.name, tinst.usr);
|
||||
|
||||
class_relationship r;
|
||||
r.destination = tinst.base_template_usr;
|
||||
@@ -848,15 +948,18 @@ void tu_visitor::process_function_parameter(
|
||||
rr.destination = tinst.usr;
|
||||
rr.type = relationship_t::kDependency;
|
||||
rr.label = "";
|
||||
LOG_DBG("Adding field dependency relationship {} {} {} : {}",
|
||||
rr.destination, model::class_diagram::to_string(rr.type),
|
||||
c.usr, rr.label);
|
||||
LOG_DBG(
|
||||
"Adding field dependency relationship {} {} {} : {}",
|
||||
rr.destination,
|
||||
model::class_diagram::to_string(rr.type), c.usr,
|
||||
rr.label);
|
||||
c.add_relationship(std::move(rr));
|
||||
|
||||
ctx.d.add_class(std::move(tinst));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m.parameters.emplace_back(std::move(mp));
|
||||
}
|
||||
@@ -910,6 +1013,12 @@ void tu_visitor::process_friend(const cppast::cpp_friend &f, class_ &parent)
|
||||
r.type = relationship_t::kFriendship;
|
||||
r.label = "<<friend>>";
|
||||
|
||||
if (f.comment().has_value())
|
||||
r.decorators = decorators::parse(f.comment().value());
|
||||
|
||||
if (r.skip() || r.skip_relationship())
|
||||
return;
|
||||
|
||||
if (f.type()) {
|
||||
auto name = cppast::to_string(f.type().value());
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ public:
|
||||
bool process_field_with_template_instantiation(
|
||||
const cppast::cpp_member_variable &mv, const cppast::cpp_type &tr,
|
||||
clanguml::model::class_diagram::class_ &c,
|
||||
clanguml::model::class_diagram::class_member &m,
|
||||
cppast::cpp_access_specifier_kind as);
|
||||
|
||||
void process_static_field(const cppast::cpp_variable &mv,
|
||||
|
||||
219
src/uml/decorators.cc
Normal file
219
src/uml/decorators.cc
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* src/uml/decorators.cc
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#include "decorators.h"
|
||||
|
||||
#include "util/util.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace clanguml {
|
||||
namespace decorators {
|
||||
|
||||
const std::string note::label = "note";
|
||||
const std::string skip::label = "skip";
|
||||
const std::string skip_relationship::label = "skiprelationship";
|
||||
const std::string style::label = "style";
|
||||
const std::string aggregation::label = "aggregation";
|
||||
const std::string composition::label = "composition";
|
||||
const std::string association::label = "association";
|
||||
|
||||
std::shared_ptr<decorator> decorator::from_string(std::string_view c)
|
||||
{
|
||||
if (c.find(note::label) == 0) {
|
||||
return note::from_string(c);
|
||||
}
|
||||
else if (c.find(skip_relationship::label) == 0) {
|
||||
return skip_relationship::from_string(c);
|
||||
}
|
||||
else if (c.find(skip::label) == 0) {
|
||||
return skip::from_string(c);
|
||||
}
|
||||
else if (c.find(style::label) == 0) {
|
||||
return style::from_string(c);
|
||||
}
|
||||
else if (c.find(aggregation::label) == 0) {
|
||||
return aggregation::from_string(c);
|
||||
}
|
||||
else if (c.find(composition::label) == 0) {
|
||||
return composition::from_string(c);
|
||||
}
|
||||
else if (c.find(association::label) == 0) {
|
||||
return association::from_string(c);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool decorator::applies_to_diagram(std::string name)
|
||||
{
|
||||
return diagrams.empty() ||
|
||||
(std::find(diagrams.begin(), diagrams.end(), name) != diagrams.end());
|
||||
}
|
||||
|
||||
decorator_toks decorator::tokenize(const std::string &label, std::string_view c)
|
||||
{
|
||||
decorator_toks res;
|
||||
res.label = label;
|
||||
size_t pos{};
|
||||
auto it = c.begin();
|
||||
std::advance(it, label.size());
|
||||
|
||||
if (*it == ':') {
|
||||
std::advance(it, 1);
|
||||
|
||||
pos = std::distance(c.begin(), it);
|
||||
// If the diagram list is provided after ':', [] is mandatory
|
||||
// even if empty
|
||||
auto d = c.substr(pos, c.find("[", pos) - pos);
|
||||
if (!d.empty()) {
|
||||
std::string d_str{d};
|
||||
d_str.erase(std::remove_if(d_str.begin(), d_str.end(),
|
||||
(int (*)(int))std::isspace),
|
||||
d_str.end());
|
||||
res.diagrams = util::split(d_str, ",");
|
||||
}
|
||||
|
||||
std::advance(it, d.size());
|
||||
}
|
||||
|
||||
if (*it == '[') {
|
||||
std::advance(it, 1);
|
||||
|
||||
pos = std::distance(c.begin(), it);
|
||||
res.param = c.substr(pos, c.find("]", pos) - pos);
|
||||
|
||||
std::advance(it, res.param.size() + 1);
|
||||
}
|
||||
else if (std::isspace(*it)) {
|
||||
std::advance(it, 1);
|
||||
}
|
||||
|
||||
pos = std::distance(c.begin(), it);
|
||||
res.text = c.substr(pos, c.find("}", pos) - pos);
|
||||
res.text = util::trim(res.text);
|
||||
res.param = util::trim(res.param);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> note::from_string(std::string_view c)
|
||||
{
|
||||
auto res = std::make_shared<note>();
|
||||
auto toks = res->tokenize(note::label, c);
|
||||
|
||||
res->diagrams = toks.diagrams;
|
||||
|
||||
if (!toks.param.empty())
|
||||
res->position = toks.param;
|
||||
|
||||
res->text = toks.text;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> skip::from_string(std::string_view c)
|
||||
{
|
||||
return std::make_shared<skip>();
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> skip_relationship::from_string(std::string_view c)
|
||||
{
|
||||
return std::make_shared<skip_relationship>();
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> style::from_string(std::string_view c)
|
||||
{
|
||||
auto res = std::make_shared<style>();
|
||||
auto toks = res->tokenize(style::label, c);
|
||||
|
||||
res->diagrams = toks.diagrams;
|
||||
res->spec = toks.param;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> aggregation::from_string(std::string_view c)
|
||||
{
|
||||
auto res = std::make_shared<aggregation>();
|
||||
auto toks = res->tokenize(aggregation::label, c);
|
||||
|
||||
res->diagrams = toks.diagrams;
|
||||
res->multiplicity = toks.param;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> composition::from_string(std::string_view c)
|
||||
{
|
||||
auto res = std::make_shared<composition>();
|
||||
auto toks = res->tokenize(composition::label, c);
|
||||
|
||||
res->diagrams = toks.diagrams;
|
||||
res->multiplicity = toks.param;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::shared_ptr<decorator> association::from_string(std::string_view c)
|
||||
{
|
||||
auto res = std::make_shared<association>();
|
||||
auto toks = res->tokenize(association::label, c);
|
||||
|
||||
res->diagrams = toks.diagrams;
|
||||
res->multiplicity = toks.param;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<decorator>> parse(
|
||||
std::string documentation_block, std::string clanguml_tag)
|
||||
{
|
||||
std::vector<std::shared_ptr<decorator>> res;
|
||||
const std::string begin_tag{"@" + clanguml_tag};
|
||||
const auto begin_tag_size = begin_tag.size();
|
||||
|
||||
// First replace all \uml occurences with @uml
|
||||
util::replace_all(
|
||||
documentation_block, "\\" + clanguml_tag, "@" + clanguml_tag);
|
||||
documentation_block = util::trim(documentation_block);
|
||||
|
||||
std::string_view block_view{documentation_block};
|
||||
|
||||
auto pos = block_view.find("@" + clanguml_tag + "{");
|
||||
while (pos < documentation_block.size()) {
|
||||
auto c_begin = pos + begin_tag_size;
|
||||
auto c_end = documentation_block.find("}", c_begin);
|
||||
|
||||
if (c_end == std::string::npos)
|
||||
return res;
|
||||
|
||||
auto com =
|
||||
decorator::from_string(block_view.substr(c_begin + 1, c_end - 2));
|
||||
|
||||
if (com)
|
||||
res.emplace_back(std::move(com));
|
||||
|
||||
pos = block_view.find("@" + clanguml_tag + "{", c_end);
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
} // namespace decorators
|
||||
} // namespace clanguml
|
||||
101
src/uml/decorators.h
Normal file
101
src/uml/decorators.h
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* src/uml/decorators.h
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace clanguml {
|
||||
namespace decorators {
|
||||
struct decorator_toks {
|
||||
std::string label;
|
||||
std::vector<std::string> diagrams;
|
||||
std::string param;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct decorator {
|
||||
std::vector<std::string> diagrams;
|
||||
|
||||
virtual ~decorator() = default;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
|
||||
bool applies_to_diagram(std::string name);
|
||||
|
||||
protected:
|
||||
decorator_toks tokenize(const std::string &label, std::string_view c);
|
||||
};
|
||||
|
||||
struct note : public decorator {
|
||||
static const std::string label;
|
||||
|
||||
std::string position{"left"};
|
||||
std::string text;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
struct skip : public decorator {
|
||||
static const std::string label;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
struct skip_relationship : public decorator {
|
||||
static const std::string label;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
struct style : public decorator {
|
||||
static const std::string label;
|
||||
|
||||
std::string spec;
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
struct relationship : public decorator {
|
||||
std::string multiplicity;
|
||||
};
|
||||
|
||||
struct aggregation : public relationship {
|
||||
static const std::string label;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
struct composition : public relationship {
|
||||
static const std::string label;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
struct association : public relationship {
|
||||
static const std::string label;
|
||||
|
||||
static std::shared_ptr<decorator> from_string(std::string_view c);
|
||||
};
|
||||
|
||||
std::vector<std::shared_ptr<decorator>> parse(
|
||||
std::string documentation_block, std::string clanguml_tag = "uml");
|
||||
|
||||
} // namespace decorators
|
||||
} // namespace clanguml
|
||||
@@ -126,5 +126,20 @@ bool find_element_alias(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool replace_all(
|
||||
std::string &input, std::string pattern, std::string replace_with)
|
||||
{
|
||||
bool replaced{false};
|
||||
|
||||
auto pos = input.find(pattern);
|
||||
while (pos < input.size()) {
|
||||
input.replace(pos, pattern.size(), replace_with);
|
||||
pos = input.find(pattern, pos + replace_with.size());
|
||||
replaced = true;
|
||||
}
|
||||
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,5 +104,15 @@ std::string unqualify(const std::string &s);
|
||||
*/
|
||||
bool find_element_alias(
|
||||
const std::string &input, std::tuple<std::string, size_t, size_t> &result);
|
||||
|
||||
/**
|
||||
* @brief Find and replace in string
|
||||
*
|
||||
* Replaces all occurences of pattern with replace_with in input string.
|
||||
*
|
||||
* @return True if at least on replacement was made
|
||||
*/
|
||||
bool replace_all(
|
||||
std::string &input, std::string pattern, std::string replace_with);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,14 @@ set(CLANG_UML_TEST_CASES_HEADER
|
||||
catch.h
|
||||
)
|
||||
|
||||
set(CLANG_UML_TEST_DECORATOR_PARSER_SRC
|
||||
test_decorator_parser.cc
|
||||
${TEST_UTIL_SOURCES}
|
||||
)
|
||||
set(CLANG_UML_TEST_DECORATOR_PARSER_HEADER
|
||||
catch.h
|
||||
)
|
||||
|
||||
add_executable(test_util
|
||||
${CLANG_UML_TEST_UTIL_SRC}
|
||||
${CLANG_UML_TEST_UTIL_HEADER})
|
||||
@@ -33,6 +41,15 @@ target_link_libraries(test_util
|
||||
${YAML_CPP_LIBRARIES}
|
||||
spdlog::spdlog clang-umllib cppast)
|
||||
|
||||
add_executable(test_decorator_parser
|
||||
${CLANG_UML_TEST_DECORATOR_PARSER_SRC}
|
||||
${CLANG_UML_TEST_DECORATOR_PARSER_HEADER})
|
||||
|
||||
target_link_libraries(test_decorator_parser
|
||||
PRIVATE
|
||||
${LIBCLANG_LIBRARIES}
|
||||
${YAML_CPP_LIBRARIES}
|
||||
spdlog::spdlog clang-umllib cppast)
|
||||
|
||||
add_executable(test_cases
|
||||
${CLANG_UML_TEST_CASES_SRC}
|
||||
@@ -57,4 +74,5 @@ foreach(TEST_CASE_CONFIG ${TEST_CASE_CONFIGS})
|
||||
endforeach()
|
||||
|
||||
add_test(NAME test_util COMMAND test_util)
|
||||
add_test(NAME test_decorator_parser COMMAND test_decorator_parser)
|
||||
add_test(NAME test_cases COMMAND test_cases)
|
||||
|
||||
12
tests/t00028/.clang-uml
Normal file
12
tests/t00028/.clang-uml
Normal file
@@ -0,0 +1,12 @@
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00028_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00028/t00028.cc
|
||||
using_namespace:
|
||||
- clanguml::t00028
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00028
|
||||
56
tests/t00028/t00028.cc
Normal file
56
tests/t00028/t00028.cc
Normal file
@@ -0,0 +1,56 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00028 {
|
||||
|
||||
/// \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 {
|
||||
A aaa;
|
||||
|
||||
B *bbb;
|
||||
|
||||
C &ccc;
|
||||
|
||||
std::vector<std::shared_ptr<D>> ddd;
|
||||
|
||||
E<int> eee;
|
||||
|
||||
G **ggg;
|
||||
};
|
||||
|
||||
} // namespace t00028
|
||||
} // namespace clanguml
|
||||
67
tests/t00028/test_case.h
Normal file
67
tests/t00028/test_case.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* tests/t00028/test_case.cc
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
TEST_CASE("t00028", "[test-case][class]")
|
||||
{
|
||||
auto [config, db] = load_config("t00028");
|
||||
|
||||
auto diagram = config.diagrams["t00028_class"];
|
||||
|
||||
REQUIRE(diagram->name == "t00028_class");
|
||||
|
||||
REQUIRE(diagram->include.namespaces.size() == 1);
|
||||
REQUIRE_THAT(diagram->include.namespaces,
|
||||
VectorContains(std::string{"clanguml::t00028"}));
|
||||
|
||||
REQUIRE(diagram->exclude.namespaces.size() == 0);
|
||||
|
||||
REQUIRE(diagram->should_include("clanguml::t00028::A"));
|
||||
|
||||
auto model = generate_class_diagram(db, diagram);
|
||||
|
||||
REQUIRE(model.name == "t00028_class");
|
||||
|
||||
auto puml = generate_class_puml(diagram, model);
|
||||
AliasMatcher _A(puml);
|
||||
|
||||
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||
|
||||
REQUIRE_THAT(puml, IsClass(_A("A")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("B")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("C")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("D")));
|
||||
REQUIRE_THAT(puml, IsClassTemplate("E", "T"));
|
||||
REQUIRE_THAT(puml, IsEnum(_A("F")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("R")));
|
||||
REQUIRE_THAT(puml, HasNote(_A("A"), "top", "A class note."));
|
||||
REQUIRE_THAT(puml, HasNote(_A("B"), "left", "B class note."));
|
||||
REQUIRE_THAT(puml, HasNote(_A("C"), "bottom", "C class note."));
|
||||
const auto d_note = R"(
|
||||
D
|
||||
class
|
||||
note.)";
|
||||
REQUIRE_THAT(puml, HasNote(_A("D"), "left", d_note));
|
||||
REQUIRE_THAT(puml, HasNote(_A("E<T>"), "left", "E template class note."));
|
||||
REQUIRE_THAT(puml, HasNote(_A("F"), "bottom", "F enum note."));
|
||||
REQUIRE_THAT(puml, !HasNote(_A("G"), "left", "G class note."));
|
||||
REQUIRE_THAT(puml, HasNote(_A("R"), "right", "R class note."));
|
||||
|
||||
save_puml(
|
||||
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
|
||||
}
|
||||
12
tests/t00029/.clang-uml
Normal file
12
tests/t00029/.clang-uml
Normal file
@@ -0,0 +1,12 @@
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00029_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00029/t00029.cc
|
||||
using_namespace:
|
||||
- clanguml::t00029
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00029
|
||||
53
tests/t00029/t00029.cc
Normal file
53
tests/t00029/t00029.cc
Normal file
@@ -0,0 +1,53 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00029 {
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace t00029
|
||||
} // namespace clanguml
|
||||
65
tests/t00029/test_case.h
Normal file
65
tests/t00029/test_case.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* tests/t00029/test_case.cc
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
TEST_CASE("t00029", "[test-case][class]")
|
||||
{
|
||||
auto [config, db] = load_config("t00029");
|
||||
|
||||
auto diagram = config.diagrams["t00029_class"];
|
||||
|
||||
REQUIRE(diagram->name == "t00029_class");
|
||||
|
||||
REQUIRE(diagram->include.namespaces.size() == 1);
|
||||
REQUIRE_THAT(diagram->include.namespaces,
|
||||
VectorContains(std::string{"clanguml::t00029"}));
|
||||
|
||||
REQUIRE(diagram->exclude.namespaces.size() == 0);
|
||||
|
||||
REQUIRE(diagram->should_include("clanguml::t00029::A"));
|
||||
|
||||
auto model = generate_class_diagram(db, diagram);
|
||||
|
||||
REQUIRE(model.name == "t00029_class");
|
||||
|
||||
auto puml = generate_class_puml(diagram, model);
|
||||
AliasMatcher _A(puml);
|
||||
|
||||
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||
|
||||
REQUIRE_THAT(puml, IsClass(_A("A")));
|
||||
REQUIRE_THAT(puml, !IsClass(_A("B")));
|
||||
REQUIRE_THAT(puml, IsClassTemplate("C", "T"));
|
||||
REQUIRE_THAT(puml, !IsClassTemplate("D", "T"));
|
||||
REQUIRE_THAT(puml, IsEnum(_A("E")));
|
||||
REQUIRE_THAT(puml, !IsEnum(_A("F")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("G1")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("G2")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("G3")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("G4")));
|
||||
|
||||
REQUIRE_THAT(puml, IsClass(_A("R")));
|
||||
|
||||
REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("G1"), "+g1"));
|
||||
REQUIRE_THAT(puml, !IsAggregation(_A("R"), _A("G2"), "+g2"));
|
||||
REQUIRE_THAT(puml, !IsAggregation(_A("R"), _A("G3"), "+g3"));
|
||||
REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("G4"), "+g4"));
|
||||
|
||||
save_puml(
|
||||
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
|
||||
}
|
||||
12
tests/t00030/.clang-uml
Normal file
12
tests/t00030/.clang-uml
Normal file
@@ -0,0 +1,12 @@
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00030_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00030/t00030.cc
|
||||
using_namespace:
|
||||
- clanguml::t00030
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00030
|
||||
34
tests/t00030/t00030.cc
Normal file
34
tests/t00030/t00030.cc
Normal file
@@ -0,0 +1,34 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00030 {
|
||||
|
||||
class A {
|
||||
};
|
||||
|
||||
class B {
|
||||
};
|
||||
|
||||
class C {
|
||||
};
|
||||
|
||||
class D {
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
} // namespace t00030
|
||||
} // namespace clanguml
|
||||
57
tests/t00030/test_case.h
Normal file
57
tests/t00030/test_case.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* tests/t00030/test_case.cc
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
TEST_CASE("t00030", "[test-case][class]")
|
||||
{
|
||||
auto [config, db] = load_config("t00030");
|
||||
|
||||
auto diagram = config.diagrams["t00030_class"];
|
||||
|
||||
REQUIRE(diagram->name == "t00030_class");
|
||||
|
||||
REQUIRE(diagram->include.namespaces.size() == 1);
|
||||
REQUIRE_THAT(diagram->include.namespaces,
|
||||
VectorContains(std::string{"clanguml::t00030"}));
|
||||
|
||||
REQUIRE(diagram->exclude.namespaces.size() == 0);
|
||||
|
||||
REQUIRE(diagram->should_include("clanguml::t00030::A"));
|
||||
|
||||
auto model = generate_class_diagram(db, diagram);
|
||||
|
||||
REQUIRE(model.name == "t00030_class");
|
||||
|
||||
auto puml = generate_class_puml(diagram, model);
|
||||
AliasMatcher _A(puml);
|
||||
|
||||
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||
|
||||
REQUIRE_THAT(puml, IsClass(_A("A")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("B")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("C")));
|
||||
REQUIRE_THAT(puml, IsClass(_A("D")));
|
||||
|
||||
REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("A"), "+aaa"));
|
||||
REQUIRE_THAT(puml, IsComposition(_A("R"), _A("B"), "+bbb", "0..1", "1..*"));
|
||||
REQUIRE_THAT(puml, IsAggregation(_A("R"), _A("C"), "+ccc", "0..1", "1..5"));
|
||||
REQUIRE_THAT(puml, IsAssociation(_A("R"), _A("D"), "+ddd", "", "1"));
|
||||
|
||||
save_puml(
|
||||
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
|
||||
}
|
||||
12
tests/t00031/.clang-uml
Normal file
12
tests/t00031/.clang-uml
Normal file
@@ -0,0 +1,12 @@
|
||||
compilation_database_dir: ..
|
||||
output_directory: puml
|
||||
diagrams:
|
||||
t00031_class:
|
||||
type: class
|
||||
glob:
|
||||
- ../../tests/t00031/t00031.cc
|
||||
using_namespace:
|
||||
- clanguml::t00031
|
||||
include:
|
||||
namespaces:
|
||||
- clanguml::t00031
|
||||
38
tests/t00031/t00031.cc
Normal file
38
tests/t00031/t00031.cc
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace clanguml {
|
||||
namespace t00031 {
|
||||
|
||||
/// @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;
|
||||
};
|
||||
|
||||
} // namespace t00031
|
||||
} // namespace clanguml
|
||||
65
tests/t00031/test_case.h
Normal file
65
tests/t00031/test_case.h
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* tests/t00031/test_case.cc
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
TEST_CASE("t00031", "[test-case][class]")
|
||||
{
|
||||
auto [config, db] = load_config("t00031");
|
||||
|
||||
auto diagram = config.diagrams["t00031_class"];
|
||||
|
||||
REQUIRE(diagram->name == "t00031_class");
|
||||
|
||||
REQUIRE(diagram->include.namespaces.size() == 1);
|
||||
REQUIRE_THAT(diagram->include.namespaces,
|
||||
VectorContains(std::string{"clanguml::t00031"}));
|
||||
|
||||
REQUIRE(diagram->exclude.namespaces.size() == 0);
|
||||
|
||||
REQUIRE(diagram->should_include("clanguml::t00031::A"));
|
||||
|
||||
auto model = generate_class_diagram(db, diagram);
|
||||
|
||||
REQUIRE(model.name == "t00031_class");
|
||||
|
||||
auto puml = generate_class_puml(diagram, model);
|
||||
AliasMatcher _A(puml);
|
||||
|
||||
REQUIRE_THAT(puml, StartsWith("@startuml"));
|
||||
REQUIRE_THAT(puml, EndsWith("@enduml\n"));
|
||||
|
||||
REQUIRE_THAT(puml, IsClass(_A("A")));
|
||||
REQUIRE_THAT(puml, IsEnum(_A("B")));
|
||||
REQUIRE_THAT(puml, IsClassTemplate("C", "T"));
|
||||
REQUIRE_THAT(puml, IsClass(_A("D")));
|
||||
|
||||
REQUIRE_THAT(puml,
|
||||
IsAssociationWithStyle(
|
||||
_A("R"), _A("A"), "+aaa", "#red,dashed,thickness=2"));
|
||||
REQUIRE_THAT(puml,
|
||||
IsCompositionWithStyle(
|
||||
_A("R"), _A("B"), "+bbb", "#green,dashed,thickness=4"));
|
||||
REQUIRE_THAT(puml,
|
||||
IsAggregationWithStyle(
|
||||
_A("R"), _A("C<int>"), "+ccc", "#blue,dotted,thickness=8"));
|
||||
REQUIRE_THAT(puml,
|
||||
IsAssociationWithStyle(
|
||||
_A("R"), _A("D"), "+ddd", "#blue,plain,thickness=16"));
|
||||
|
||||
save_puml(
|
||||
"./" + config.output_directory + "/" + diagram->name + ".puml", puml);
|
||||
}
|
||||
@@ -131,6 +131,10 @@ using namespace clanguml::test::matchers;
|
||||
#include "t00025/test_case.h"
|
||||
#include "t00026/test_case.h"
|
||||
#include "t00027/test_case.h"
|
||||
#include "t00028/test_case.h"
|
||||
#include "t00029/test_case.h"
|
||||
#include "t00030/test_case.h"
|
||||
#include "t00031/test_case.h"
|
||||
|
||||
//
|
||||
// Sequence diagram tests
|
||||
|
||||
@@ -170,8 +170,7 @@ struct AliasMatcher {
|
||||
}
|
||||
}
|
||||
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Cannot find alias {} in {}", name, fmt::join(puml, "\n")));
|
||||
return "__INVALID__ALIAS__";
|
||||
}
|
||||
|
||||
const std::vector<std::string> puml;
|
||||
@@ -218,11 +217,23 @@ ContainsMatcher IsInnerClass(std::string const &parent,
|
||||
}
|
||||
|
||||
ContainsMatcher IsAssociation(std::string const &from, std::string const &to,
|
||||
std::string const &label,
|
||||
std::string const &label, std::string multiplicity_source = "",
|
||||
std::string multiplicity_dest = "",
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
std::string format_string = "{}";
|
||||
if (!multiplicity_source.empty())
|
||||
format_string += " \"" + multiplicity_source + "\"";
|
||||
|
||||
format_string += " -->";
|
||||
|
||||
if (!multiplicity_dest.empty())
|
||||
format_string += " \"" + multiplicity_dest + "\"";
|
||||
|
||||
format_string += " {} : {}";
|
||||
|
||||
return ContainsMatcher(CasedString(
|
||||
fmt::format("{} --> {} : {}", from, to, label), caseSensitivity));
|
||||
fmt::format(format_string, from, to, label), caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsFriend(std::string const &from, std::string const &to,
|
||||
@@ -233,19 +244,70 @@ ContainsMatcher IsFriend(std::string const &from, std::string const &to,
|
||||
}
|
||||
|
||||
ContainsMatcher IsComposition(std::string const &from, std::string const &to,
|
||||
std::string const &label,
|
||||
std::string const &label, std::string multiplicity_source = "",
|
||||
std::string multiplicity_dest = "",
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
std::string format_string = "{}";
|
||||
if (!multiplicity_source.empty())
|
||||
format_string += " \"" + multiplicity_source + "\"";
|
||||
|
||||
format_string += " *--";
|
||||
|
||||
if (!multiplicity_dest.empty())
|
||||
format_string += " \"" + multiplicity_dest + "\"";
|
||||
|
||||
format_string += " {} : {}";
|
||||
|
||||
return ContainsMatcher(CasedString(
|
||||
fmt::format("{} *-- {} : {}", from, to, label), caseSensitivity));
|
||||
fmt::format(format_string, from, to, label), caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsAggregation(std::string const &from, std::string const &to,
|
||||
std::string const &label,
|
||||
std::string const &label, std::string multiplicity_source = "",
|
||||
std::string multiplicity_dest = "",
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
std::string format_string = "{}";
|
||||
if (!multiplicity_source.empty())
|
||||
format_string += " \"" + multiplicity_source + "\"";
|
||||
|
||||
format_string += " o--";
|
||||
|
||||
if (!multiplicity_dest.empty())
|
||||
format_string += " \"" + multiplicity_dest + "\"";
|
||||
|
||||
format_string += " {} : {}";
|
||||
|
||||
return ContainsMatcher(CasedString(
|
||||
fmt::format("{} o-- {} : {}", from, to, label), caseSensitivity));
|
||||
fmt::format(format_string, from, to, label), caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsAggregationWithStyle(std::string const &from,
|
||||
std::string const &to, std::string const &label, std::string style,
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
return ContainsMatcher(
|
||||
CasedString(fmt::format("{} o-[{}]- {} : {}", from, style, to, label),
|
||||
caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsAssociationWithStyle(std::string const &from,
|
||||
std::string const &to, std::string const &label, std::string style,
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
return ContainsMatcher(
|
||||
CasedString(fmt::format("{} -[{}]-> {} : {}", from, style, to, label),
|
||||
caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsCompositionWithStyle(std::string const &from,
|
||||
std::string const &to, std::string const &label, std::string style,
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
return ContainsMatcher(
|
||||
CasedString(fmt::format("{} *-[{}]- {} : {}", from, style, to, label),
|
||||
caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher IsInstantiation(std::string const &from, std::string const &to,
|
||||
@@ -262,6 +324,14 @@ ContainsMatcher IsDependency(std::string const &from, std::string const &to,
|
||||
CasedString(fmt::format("{} ..> {}", from, to), caseSensitivity));
|
||||
}
|
||||
|
||||
ContainsMatcher HasNote(std::string const &cls, std::string const &position,
|
||||
std::string const ¬e,
|
||||
CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes)
|
||||
{
|
||||
return ContainsMatcher(CasedString(
|
||||
fmt::format("note {} of {}", position, cls), caseSensitivity));
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
ContainsMatcher IsMethod(std::string const &name,
|
||||
std::string const &type = "void",
|
||||
|
||||
@@ -78,6 +78,18 @@ test_cases:
|
||||
- name: t00027
|
||||
title: Template decorator pattern
|
||||
description:
|
||||
- name: t00028
|
||||
title: PlantUML note decorator test case
|
||||
description:
|
||||
- name: t00029
|
||||
title: PlantUML skip decorator test case
|
||||
description:
|
||||
- name: t00030
|
||||
title: PlantUML relationship decorators test case
|
||||
description:
|
||||
- name: t00031
|
||||
title: PlantUML style decorator test case
|
||||
description:
|
||||
Sequence diagrams:
|
||||
- name: t20001
|
||||
title: Basic sequence diagram
|
||||
|
||||
235
tests/test_decorator_parser.cc
Normal file
235
tests/test_decorator_parser.cc
Normal file
@@ -0,0 +1,235 @@
|
||||
/**
|
||||
* tests/test_decorator_parser.cc
|
||||
*
|
||||
* Copyright (c) 2021 Bartek Kryza <bkryza@gmail.com>
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include "uml/decorators.h"
|
||||
|
||||
#include "catch.h"
|
||||
|
||||
TEST_CASE("Test decorator parser on regular comment", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\brief This is a comment.
|
||||
|
||||
This is a longer comment.
|
||||
|
||||
\param a int an int
|
||||
\param b float a float
|
||||
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on note", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\brief This is a comment.
|
||||
|
||||
This is a longer comment.
|
||||
|
||||
\
|
||||
|
||||
\param a int an int
|
||||
\param b float a float
|
||||
|
||||
\uml{note[left] This is a comment}
|
||||
@uml{note[ top ]
|
||||
|
||||
This is a comment }
|
||||
\uml{note This is a comment}
|
||||
\uml{note[] This is a comment}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.size() == 4);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<note>(decorators.at(0));
|
||||
auto n2 = std::dynamic_pointer_cast<note>(decorators.at(1));
|
||||
auto n3 = std::dynamic_pointer_cast<note>(decorators.at(2));
|
||||
auto n4 = std::dynamic_pointer_cast<note>(decorators.at(3));
|
||||
|
||||
CHECK(n1);
|
||||
CHECK(n1->position == "left");
|
||||
CHECK(n1->text == "This is a comment");
|
||||
|
||||
CHECK(n2);
|
||||
CHECK(n2->position == "top");
|
||||
CHECK(n2->text == "This is a comment");
|
||||
|
||||
CHECK(n3);
|
||||
CHECK(n3->position == "left");
|
||||
CHECK(n3->text == "This is a comment");
|
||||
|
||||
CHECK(n4);
|
||||
CHECK(n4->position == "left");
|
||||
CHECK(n4->text == "This is a comment");
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on note with custom tag", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\brief This is a comment.
|
||||
|
||||
This is a longer comment.
|
||||
|
||||
\
|
||||
|
||||
\param a int an int
|
||||
\param b float a float
|
||||
|
||||
\clanguml{note[left] This is a comment}
|
||||
@clanguml{note[ top ]
|
||||
|
||||
This is a comment }
|
||||
\clanguml{note This is a comment}
|
||||
\clanguml{note[] This is a comment}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment, "clanguml");
|
||||
|
||||
CHECK(decorators.size() == 4);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<note>(decorators.at(0));
|
||||
auto n2 = std::dynamic_pointer_cast<note>(decorators.at(1));
|
||||
auto n3 = std::dynamic_pointer_cast<note>(decorators.at(2));
|
||||
auto n4 = std::dynamic_pointer_cast<note>(decorators.at(3));
|
||||
|
||||
CHECK(n1);
|
||||
CHECK(n1->position == "left");
|
||||
CHECK(n1->text == "This is a comment");
|
||||
|
||||
CHECK(n2);
|
||||
CHECK(n2->position == "top");
|
||||
CHECK(n2->text == "This is a comment");
|
||||
|
||||
CHECK(n3);
|
||||
CHECK(n3->position == "left");
|
||||
CHECK(n3->text == "This is a comment");
|
||||
|
||||
CHECK(n4);
|
||||
CHECK(n4->position == "left");
|
||||
CHECK(n4->text == "This is a comment");
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on style", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\uml{style[#green,dashed,thickness=4]}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.size() == 1);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<style>(decorators.at(0));
|
||||
|
||||
CHECK(n1);
|
||||
CHECK(n1->spec == "#green,dashed,thickness=4");
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on aggregation", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\uml{aggregation[0..1:0..*]}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.size() == 1);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<aggregation>(decorators.at(0));
|
||||
|
||||
CHECK(n1);
|
||||
CHECK(n1->multiplicity == "0..1:0..*");
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on skip", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\uml{skip}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.size() == 1);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<skip>(decorators.at(0));
|
||||
|
||||
CHECK(n1);
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on skiprelationship", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\uml{skiprelationship}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.size() == 1);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<skip_relationship>(decorators.at(0));
|
||||
|
||||
CHECK(n1);
|
||||
}
|
||||
|
||||
TEST_CASE("Test decorator parser on diagram scope", "[unit-test]")
|
||||
{
|
||||
std::string comment = R"(
|
||||
\uml{note:diagram1, diagram2,
|
||||
diagram3[left] Note only for diagrams 1, 2 and 3.}
|
||||
)";
|
||||
|
||||
using namespace clanguml::decorators;
|
||||
|
||||
auto decorators = parse(comment);
|
||||
|
||||
CHECK(decorators.size() == 1);
|
||||
|
||||
auto n1 = std::dynamic_pointer_cast<note>(decorators.at(0));
|
||||
|
||||
CHECK(n1);
|
||||
CHECK(n1->diagrams.size() == 3);
|
||||
CHECK(n1->diagrams[0] == "diagram1");
|
||||
CHECK(n1->diagrams[1] == "diagram2");
|
||||
CHECK(n1->diagrams[2] == "diagram3");
|
||||
|
||||
CHECK(n1->position == "left");
|
||||
CHECK(n1->text == "Note only for diagrams 1, 2 and 3.");
|
||||
|
||||
CHECK(n1->applies_to_diagram("diagram2"));
|
||||
CHECK(!n1->applies_to_diagram("diagram4"));
|
||||
}
|
||||
@@ -46,3 +46,25 @@ TEST_CASE("Test ns_relative", "[unit-test]")
|
||||
"static const vector<a>&");
|
||||
CHECK(ns_relative({"clanguml::t0"}, "clanguml::t0") == "t0");
|
||||
}
|
||||
|
||||
TEST_CASE("Test replace_all", "[unit-test]")
|
||||
{
|
||||
using namespace clanguml::util;
|
||||
|
||||
const std::string orig = R"(
|
||||
Lorem ipsum \clanguml{note} something something...
|
||||
|
||||
\clanguml{style}
|
||||
|
||||
)";
|
||||
std::string text{orig};
|
||||
|
||||
CHECK(replace_all(text, "NOTHERE", "HERE") == false);
|
||||
CHECK(replace_all(text, "\\clanguml", "@clanguml") == true);
|
||||
CHECK(replace_all(text, "something", "nothing") == true);
|
||||
CHECK(replace_all(text, "something", "nothing") == false);
|
||||
CHECK(replace_all(text, "nothing", "something") == true);
|
||||
CHECK(replace_all(text, "@clanguml", "\\clanguml") == true);
|
||||
|
||||
CHECK(text == orig);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ with open(r'tests/test_cases.yaml') as f:
|
||||
tc.write("{}\n".format(test_case['description']))
|
||||
|
||||
# Write test config file
|
||||
config = open('tests/{0}/.clanguml'.format(name), 'r').read()
|
||||
config = open('tests/{0}/.clang-uml'.format(name), 'r').read()
|
||||
tc.write("## Config\n")
|
||||
tc.write("```yaml\n")
|
||||
tc.write(config)
|
||||
|
||||
Reference in New Issue
Block a user