-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ddc376e
Showing
43 changed files
with
1,255 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
on: [push, pull_request] | ||
|
||
env: | ||
APT_INSTALL: sudo apt install -y --no-install-recommends | ||
|
||
|
||
jobs: | ||
jammy_test: | ||
runs-on: ubuntu-22.04 | ||
|
||
steps: | ||
- uses: actions/checkout@v3 | ||
- run: make install_deps_apt | ||
- run: make install | ||
- run: make test | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
PROJECT_NAME=hiearch | ||
|
||
APT_INSTALL=sudo apt install -y --no-install-recommends | ||
# DEB_PYTHON_INSTALL_LAYOUT fixes UNKNOWN -> https://github.com/pypa/setuptools/issues/3269 | ||
export DEB_PYTHON_INSTALL_LAYOUT=deb_system | ||
export PYTHONDONTWRITEBYTECODE=1 | ||
|
||
CURRENT_DIR=$(shell pwd) | ||
BUILD_DIR?=${CURRENT_DIR}/build | ||
TEST_NOT= | ||
|
||
.DEFAULT: | ||
@echo "Testing $@..." | ||
${TEST_NOT} hiearch -o ${BUILD_DIR}/$@ ./test/$@/*.yaml | ||
mkdir -p ./build/$@ | ||
# TODO awkward and fragile | ||
find build/$@/ -iname '*.gv' | sort | xargs --no-run-if-empty -I {} sh -c "sort {} | md5sum > ./build/$@/checksum.build" | ||
find test/$@/ -iname '*.gv' | sort | xargs --no-run-if-empty -I {} sh -c "sort {} | md5sum > ./build/$@/checksum.test" | ||
${TEST_NOT} cmp ./build/$@/checksum.build ./build/$@/checksum.test | ||
|
||
venv: builddir | ||
python3 -m venv ${BUILD_DIR}/venv | ||
|
||
venv_test: venv | ||
/bin/sh -c ". ${BUILD_DIR}/venv/bin/activate && ${MAKE} reinstall && ${MAKE} test" | ||
|
||
test: | ||
@${MAKE} \ | ||
01_basic 02_default_view 03_default_view_split 06_multiscope \ | ||
07_trivial 08_node_realations 09_tags 10_minimal || (echo "Failure!" && false) | ||
@${MAKE} TEST_NOT=! 04_node_cycle 05_style_cycle || (echo "Failure!" && false) | ||
@echo "Success!" | ||
|
||
clean: | ||
rm -Rf .pytest_cache | ||
rm -Rf ${BUILD_DIR} | ||
find ./ -name "__pycache__" | xargs rm -Rf | ||
|
||
builddir: | ||
mkdir -p ${BUILD_DIR} | ||
|
||
install: | ||
pip install --no-cache-dir ./ | ||
#${MAKE} clean | ||
|
||
install_edit: | ||
pip install --editable ./ | ||
${MAKE} clean | ||
|
||
install_deps: builddir | ||
pip-compile --verbose --output-file ./${BUILD_DIR}/requirements.txt pyproject.toml | ||
pip3 install -r ./${BUILD_DIR}/requirements.txt | ||
|
||
uninstall: clean | ||
pip uninstall -y ${PROJECT_NAME} | ||
|
||
reinstall: | ||
${MAKE} uninstall | ||
${MAKE} install | ||
|
||
spell: | ||
hunspell README.md | ||
|
||
install_deps_apt: | ||
${APT_INSTALL} graphviz | ||
|
||
|
||
.PHONY: test | ||
|
||
# https://packaging.python.org/en/latest/tutorials/packaging-projects/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
Introduction | ||
============ | ||
|
||
`hiearch` is a CLI utility that generates diagrams from textual descriptions, | ||
a.k.a., "diagrams as code". Unlike many other generators like `graphviz` it is | ||
designed to support hierarchical decomposition and multiple views, in which | ||
sense it is conceptually similar to <https://structurizr.com>. In other words, | ||
each node in a the description is a hierarchy of nodes, that is automatically | ||
expanded, collapsed, or hidden depending on preferences of each particular view | ||
requested by the same description. Currently, `hiearch` uses `graphviz` to | ||
generate individual diagrams corresponding to views, but other backends may be | ||
added in the future. | ||
|
||
The main purpose of `hiearch` is graphical representation of complex systems, | ||
but it is meant to be generic and may find other applications. Why would anyone | ||
need another system diagram generator when there is a multitude of tools that | ||
support UML, C4, etc? I believe that the most important aspects of the system | ||
are its decomposition into components and connections between them, `hiearch` | ||
provides just that, nothing more, so that you can focus on documenting your | ||
system rather than fitting it into a specific design framework. | ||
|
||
|
||
Features | ||
======== | ||
|
||
- `hiearch` does not use a DSL, but rather accepts a set of input `yaml` files | ||
in arbitrary order. The file contents get composed into a single description, | ||
which in turn gets decomposed into views. | ||
|
||
- Description files have flat structure without nesting or inclusion and | ||
contain lists of the following objects: nodes, edges, and views. Hierarchical | ||
relations between nodes are specified using node parameters. | ||
|
||
- Unlike `graphviz`, `hiearch` does not have a concept of a subgraphs: each | ||
node may automatically become a subgraph depending on a view. | ||
|
||
- `hiearch` is also somewhat stricter than `graphviz`: for example, all nodes | ||
must be defined explicitly and cannot be deduced from edge definitions. | ||
|
||
- View is not the same thing as `graphviz` layer | ||
<https://graphviz.org/docs/attrs/layer/>: `graphviz` places all nodes on each | ||
layer and simply makes some of them invisible, which results in awkward | ||
spacing. | ||
|
||
- `hiearch` allows nodes to have multiple parent nodes, which is referenced | ||
here as 'multiscoping'. The idea is, of course, to show parents in different | ||
views, for example, to outline system from logical or hardware point of view. | ||
However, it is possible to visualize all parents in the same diagram, which | ||
may be a bit kinky. | ||
|
||
- `hiearch` supports label templates, which facilitates automatic generation of | ||
URLs, tables, icon inclusions, etc. | ||
|
||
|
||
Examples | ||
======== | ||
|
||
Using hiearch | ||
------------- | ||
|
||
``` | ||
usage: hiearch [-h] [-o OUTPUT] [-f FORMAT] <filename> [<filename> ...] | ||
Generates diagrams | ||
positional arguments: | ||
<filename> Input files | ||
optional arguments: | ||
-h, --help show this help message and exit | ||
-o OUTPUT, --output OUTPUT | ||
Output directory | ||
-f FORMAT, --format FORMAT | ||
Output format | ||
``` | ||
|
||
|
||
|
||
Trivial | ||
------- | ||
|
||
<table> | ||
<tr> | ||
<td> | ||
<pre> | ||
nodes: | ||
- id: ["Test 1", test1] # [label, unique id] | ||
edges: | ||
- link: [test1, test1] # [from node id, to node id] | ||
views: | ||
- id: view1 # unique id / output filename | ||
nodes: [test1] # nodes to include | ||
</pre> | ||
</td> | ||
<td align="center"> | ||
<img src="test/07_trivial/view1.svg" alt="view1" /> | ||
view1 | ||
</td> | ||
</tr> | ||
</table> | ||
|
||
|
||
|
||
Node relations | ||
-------------- | ||
|
||
<table> | ||
<tr> | ||
<td rowspan="3"> | ||
<pre> | ||
nodes: | ||
- id: ["Test 1", test1] | ||
- id: ["Test 2", test2] | ||
graphviz: # set graphviz attributes directly | ||
fillcolor: aqua | ||
style: filled | ||
- id: ["Test 3", test3] | ||
scope: test1 # test3 is contained in test1 | ||
style: test2 # test3 inherits all test2 attributes | ||
edges: | ||
- link: [test3, test3] | ||
views: | ||
- id: view1 | ||
nodes: [test2, test3] | ||
- id: view2 # test1 is shown as subgraph | ||
nodes: [test1, test3] | ||
- id: view3 | ||
nodes: [test1, test2] | ||
</pre> | ||
</td> | ||
<td align="center"> | ||
<img src="test/08_node_realations/view1.svg" alt="view1" /> | ||
view1 | ||
</td> | ||
</tr> | ||
<tr> | ||
<td align="center"> | ||
<img src="test/08_node_realations/view2.svg" alt="view2" /> | ||
view2 | ||
</td> | ||
</tr> | ||
<tr> | ||
<td align="center"> | ||
<img src="test/08_node_realations/view3.svg" alt="view3" /> | ||
view3 | ||
</td> | ||
</tr> | ||
</table> | ||
|
||
|
||
Node selection using tags | ||
------------------------- | ||
|
||
<table> | ||
<tr> | ||
<td rowspan="2"> | ||
<pre> | ||
nodes: | ||
- id: ["Test 1", test1] | ||
# tags: ["default"] if not specified | ||
- id: ["Test 2", test2] | ||
tags: ["test2_tag"] | ||
edges: | ||
- link: [test1, test1] | ||
- link: [test2, test2] | ||
views: | ||
- id: view1 | ||
tags: ["test2_tag"] | ||
- id: view2 | ||
tags: ["default"] | ||
</pre> | ||
</td> | ||
<td align="center"> | ||
<img src="test/09_tags/view1.svg" alt="view1" /> | ||
view1 | ||
</td> | ||
</tr> | ||
<tr> | ||
<td align="center"> | ||
<img src="test/09_tags/view2.svg" alt="view2" /> | ||
view2 | ||
</td> | ||
</tr> | ||
</table> | ||
|
||
<table> | ||
<tr> | ||
<td> | ||
<pre> | ||
nodes: | ||
- id: ["Test 1", test1] | ||
# if no views are specified, | ||
# a default view is automatically | ||
# added with 'tags: ["default"]' | ||
</pre> | ||
</td> | ||
<td align="center"> | ||
<img src="test/10_minimal/default.svg" alt="default" /> | ||
view1 | ||
</td> | ||
</tr> | ||
</table> | ||
|
||
|
||
|
||
Node selection using relations | ||
------------------------------ | ||
|
||
|
||
Multiscoping | ||
------------ | ||
|
||
|
||
https://docs.structurizr.com/dsl/example | ||
https://c4model.com/#DeploymentDiagram | ||
https://docs.structurizr.com/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import hiearch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
def get_key(edge): | ||
return f'{edge["link"][0]}.{edge["link"][1]}' | ||
|
||
|
||
def parse(yaml_edges, edges, must_exist_nodes): | ||
default = { | ||
'link': ['', ''], | ||
'style': None, | ||
'graphviz': {}, | ||
# overriden | ||
'id': None, | ||
'in': None, | ||
'out': None, | ||
'scope_in': None, | ||
'scope_out': None | ||
} | ||
|
||
|
||
for edge in yaml_edges: | ||
key = get_key(edge) | ||
|
||
if key in edges.keys(): | ||
raise RuntimeError(f'Duplicate edge id: {key} | file: {filename}') | ||
|
||
edge['out'] = edge['link'][0] | ||
edge['in'] = edge['link'][1] | ||
must_exist_nodes.add(edge['in']) | ||
must_exist_nodes.add(edge['out']) | ||
|
||
edges[key] = default | edge | ||
|
Oops, something went wrong.