data-flow-diagram 1.12.1.post2__tar.gz → 1.13.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data_flow_diagram-1.13.1/CHANGES.md +102 -0
- data_flow_diagram-1.13.1/MANIFEST.in +1 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/PKG-INFO +1 -1
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/__init__.py +55 -55
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/dependency_checker.py +8 -8
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/dfd.py +61 -54
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/dfd_dot_templates.py +2 -2
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/dot.py +7 -7
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/error.py +1 -1
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/markdown.py +10 -10
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/model.py +27 -26
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/parser.py +65 -85
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/scanner.py +13 -18
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram.egg-info/PKG-INFO +1 -1
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram.egg-info/SOURCES.txt +2 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/README.md +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/pyproject.toml +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/setup.cfg +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/setup.py +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/config.py +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram.egg-info/dependency_links.txt +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram.egg-info/entry_points.txt +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram.egg-info/requires.txt +0 -0
- {data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
## Version 1.13.1:
|
|
2
|
+
|
|
3
|
+
- Supports style rotated (and unrotated).
|
|
4
|
+
|
|
5
|
+
## Version 1.12.1.post3:
|
|
6
|
+
|
|
7
|
+
- CHANGES.md is read by setup.py to deduce the version.
|
|
8
|
+
|
|
9
|
+
## Version 1.12.0:
|
|
10
|
+
|
|
11
|
+
- Support style (and hence attrib) on Stores and Channels.
|
|
12
|
+
|
|
13
|
+
## Version 1.11.1.post2:
|
|
14
|
+
|
|
15
|
+
Bug fixes:
|
|
16
|
+
|
|
17
|
+
- Apply attribs on frames.
|
|
18
|
+
- Attribs are matched by whole names, so e.g. DATA and DATABASE will work.
|
|
19
|
+
|
|
20
|
+
Improvements:
|
|
21
|
+
|
|
22
|
+
- 'make install' to install locally.
|
|
23
|
+
|
|
24
|
+
## Version 1.11.0:
|
|
25
|
+
|
|
26
|
+
- Keyword "attrib" to define styles.
|
|
27
|
+
|
|
28
|
+
## Version 1.10.1:
|
|
29
|
+
|
|
30
|
+
- When item text is numbered, add newline after the number.
|
|
31
|
+
|
|
32
|
+
## Version 1.9.1:
|
|
33
|
+
|
|
34
|
+
- Add troubleshooting in README.md.
|
|
35
|
+
|
|
36
|
+
## Version 1.9.0:
|
|
37
|
+
|
|
38
|
+
- Allow line continuation with a trailing backslash
|
|
39
|
+
|
|
40
|
+
## Version 1.8.0:
|
|
41
|
+
|
|
42
|
+
- Add frames
|
|
43
|
+
|
|
44
|
+
## Version 1.7.1:
|
|
45
|
+
|
|
46
|
+
- Support continuous back- and relaxed- flows.
|
|
47
|
+
|
|
48
|
+
## Version 1.7.0:
|
|
49
|
+
|
|
50
|
+
- Add continuous flow (cflow or -->>).
|
|
51
|
+
- Add control (may only connect to signals).
|
|
52
|
+
|
|
53
|
+
## Version 1.6.0:
|
|
54
|
+
|
|
55
|
+
- Dependencies:
|
|
56
|
+
- items with name #SNIPPET:[NAME] or FILE:[NAME] refer to another graph,
|
|
57
|
+
- referred item is rendered "ghosted",
|
|
58
|
+
- dependencies are checked (unless --no-check-dependencies is passed).
|
|
59
|
+
- Add graph title (unless --no-graph-title is passed).
|
|
60
|
+
- Error in snippets of MD files: display line number relative to MD file (not snippet).
|
|
61
|
+
|
|
62
|
+
## Version 1.5.0:
|
|
63
|
+
|
|
64
|
+
- Wrap labels by `style item-text-width N` (default N=20).
|
|
65
|
+
- and `style connection-text-width N` (default N=14).
|
|
66
|
+
|
|
67
|
+
## Version 1.4.1:
|
|
68
|
+
|
|
69
|
+
- Processes have very light grey backgrounds.
|
|
70
|
+
- Add the 'none' item type.
|
|
71
|
+
- Connections with reversed direction affect the items placements.
|
|
72
|
+
- A '?' postfix to a connection, removes the edge constraint.
|
|
73
|
+
- Fix formatting of '\n' for Store and Channel (which are HTML nodes).
|
|
74
|
+
- Colorize error messages.
|
|
75
|
+
- Add drawable attributes as [ATTRS...] prefix before labels.
|
|
76
|
+
|
|
77
|
+
## Version 1.3.x:
|
|
78
|
+
|
|
79
|
+
- Style vertical: is supported.
|
|
80
|
+
- Style context: for context (top-level) diagrams.
|
|
81
|
+
- Add undirected flow (uflow aka '--').
|
|
82
|
+
|
|
83
|
+
## Version 1.2.3
|
|
84
|
+
|
|
85
|
+
- Snippet reference and ungenerated snippet were marked with '<'; now
|
|
86
|
+
use '#' instead.
|
|
87
|
+
- Detect include (infinite) recursions and print error.
|
|
88
|
+
- Can print its own version.
|
|
89
|
+
|
|
90
|
+
## Version 1.1.1
|
|
91
|
+
|
|
92
|
+
- Fix bug with left/bidir arrows.
|
|
93
|
+
|
|
94
|
+
## Version 1.1.0
|
|
95
|
+
|
|
96
|
+
- Upon error, print error stack trace.
|
|
97
|
+
- Items: label can be ommitted.
|
|
98
|
+
- Connections: syntactic sugars with arrows.
|
|
99
|
+
|
|
100
|
+
## Version 1.0.0
|
|
101
|
+
|
|
102
|
+
- Initial release.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
include CHANGES.md
|
{data_flow_diagram-1.12.1.post2 → data_flow_diagram-1.13.1}/src/data_flow_diagram/__init__.py
RENAMED
|
@@ -27,96 +27,96 @@ from .error import print_error
|
|
|
27
27
|
try:
|
|
28
28
|
VERSION = pkg_resources.require("data-flow-diagram")[0].version
|
|
29
29
|
except pkg_resources.DistributionNotFound:
|
|
30
|
-
VERSION =
|
|
30
|
+
VERSION = "undefined"
|
|
31
31
|
|
|
32
32
|
|
|
33
33
|
def parse_args() -> argparse.Namespace:
|
|
34
|
-
description, epilog = [each.strip() for each in __doc__.split(
|
|
34
|
+
description, epilog = [each.strip() for each in __doc__.split("-----")[:2]]
|
|
35
35
|
|
|
36
36
|
parser = argparse.ArgumentParser(description=description, epilog=epilog)
|
|
37
37
|
|
|
38
38
|
parser.add_argument(
|
|
39
|
-
|
|
40
|
-
action=
|
|
39
|
+
"INPUT_FILE",
|
|
40
|
+
action="store",
|
|
41
41
|
default=None,
|
|
42
|
-
nargs=
|
|
43
|
-
help=
|
|
42
|
+
nargs="?",
|
|
43
|
+
help="UML sequence input file; " "if omitted, stdin is used",
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
parser.add_argument(
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
"--output-file",
|
|
48
|
+
"-o",
|
|
49
49
|
required=False,
|
|
50
|
-
help=
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
help="output file name; pass '-' to use stdout; "
|
|
51
|
+
"if omitted, use INPUT_FILE base name with '.svg' "
|
|
52
|
+
"extension, or stdout",
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
parser.add_argument(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
action=
|
|
59
|
-
help=
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
56
|
+
"--markdown",
|
|
57
|
+
"-m",
|
|
58
|
+
action="store_true",
|
|
59
|
+
help="consider snippets between opening marker: "
|
|
60
|
+
"```data-flow-diagram OUTFILE, and closing marker: ``` "
|
|
61
|
+
"allowing to generate all diagrams contained in an "
|
|
62
|
+
"INPUT_FILE that is a markdown file",
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
parser.add_argument(
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
"--format",
|
|
67
|
+
"-f",
|
|
68
68
|
required=False,
|
|
69
|
-
default=
|
|
70
|
-
help=
|
|
71
|
-
|
|
69
|
+
default="svg",
|
|
70
|
+
help="output format: gif, jpg, tiff, bmp, pnm, eps, "
|
|
71
|
+
"pdf, svg (any supported by Graphviz); default is svg",
|
|
72
72
|
)
|
|
73
73
|
|
|
74
74
|
parser.add_argument(
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
"--percent-zoom",
|
|
76
|
+
"-p",
|
|
77
77
|
required=False,
|
|
78
78
|
default=100,
|
|
79
79
|
type=int,
|
|
80
|
-
help=
|
|
80
|
+
help="magnification percentage; default is 100",
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
parser.add_argument(
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
"--background-color",
|
|
85
|
+
"-b",
|
|
86
86
|
required=False,
|
|
87
|
-
default=
|
|
88
|
-
help=
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
default="white",
|
|
88
|
+
help="background color name (including 'none' for"
|
|
89
|
+
" transparent) in web color notation; see"
|
|
90
|
+
" https://developer.mozilla.org/en-US/docs/Web/CSS/color_value"
|
|
91
|
+
" for a list of valid names; default is white",
|
|
92
92
|
)
|
|
93
93
|
|
|
94
94
|
parser.add_argument(
|
|
95
|
-
|
|
96
|
-
action=
|
|
95
|
+
"--no-graph-title",
|
|
96
|
+
action="store_true",
|
|
97
97
|
default=False,
|
|
98
|
-
help=
|
|
98
|
+
help="suppress graph title",
|
|
99
99
|
)
|
|
100
100
|
|
|
101
101
|
parser.add_argument(
|
|
102
|
-
|
|
103
|
-
action=
|
|
102
|
+
"--no-check-dependencies",
|
|
103
|
+
action="store_true",
|
|
104
104
|
default=False,
|
|
105
|
-
help=
|
|
105
|
+
help="suppress dependencies checking",
|
|
106
106
|
)
|
|
107
107
|
|
|
108
108
|
parser.add_argument(
|
|
109
|
-
|
|
110
|
-
action=
|
|
109
|
+
"--debug",
|
|
110
|
+
action="store_true",
|
|
111
111
|
default=False,
|
|
112
|
-
help=
|
|
112
|
+
help="emit debug messages",
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
parser.add_argument(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
action=
|
|
119
|
-
help=
|
|
116
|
+
"--version",
|
|
117
|
+
"-V",
|
|
118
|
+
action="store_true",
|
|
119
|
+
help="print the version and exit",
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
return parser.parse_args()
|
|
@@ -137,17 +137,17 @@ def handle_markdown_source(
|
|
|
137
137
|
options,
|
|
138
138
|
snippet_by_name=params.snippet_by_name,
|
|
139
139
|
)
|
|
140
|
-
print(f
|
|
140
|
+
print(f"{sys.argv[0]}: generated {params.file_name}", file=sys.stderr)
|
|
141
141
|
|
|
142
142
|
|
|
143
143
|
def handle_dfd_source(
|
|
144
144
|
options: model.Options, provenance: str, input_fp: TextIO, output_path: str
|
|
145
145
|
) -> None:
|
|
146
146
|
root = model.SourceLine("", provenance, None, None)
|
|
147
|
-
if output_path ==
|
|
147
|
+
if output_path == "-":
|
|
148
148
|
# output to stdout
|
|
149
149
|
with tempfile.TemporaryDirectory() as d:
|
|
150
|
-
path = os.path.join(d,
|
|
150
|
+
path = os.path.join(d, "file.svg")
|
|
151
151
|
dfd.build(root, input_fp.read(), path, options)
|
|
152
152
|
with open(path) as f:
|
|
153
153
|
print(f.read())
|
|
@@ -160,10 +160,10 @@ def run(args: argparse.Namespace) -> None:
|
|
|
160
160
|
# adjust input
|
|
161
161
|
if args.INPUT_FILE is None:
|
|
162
162
|
input_fp = sys.stdin
|
|
163
|
-
provenance =
|
|
163
|
+
provenance = "<stdin>"
|
|
164
164
|
else:
|
|
165
165
|
input_fp = open(args.INPUT_FILE)
|
|
166
|
-
provenance = f
|
|
166
|
+
provenance = f"<file:{args.INPUT_FILE}>"
|
|
167
167
|
|
|
168
168
|
options = model.Options(
|
|
169
169
|
args.format,
|
|
@@ -183,9 +183,9 @@ def run(args: argparse.Namespace) -> None:
|
|
|
183
183
|
if args.output_file is None:
|
|
184
184
|
if args.INPUT_FILE is not None:
|
|
185
185
|
basename = os.path.splitext(args.INPUT_FILE)[0]
|
|
186
|
-
output_path = basename +
|
|
186
|
+
output_path = basename + "." + args.format
|
|
187
187
|
else:
|
|
188
|
-
output_path =
|
|
188
|
+
output_path = "-"
|
|
189
189
|
else:
|
|
190
190
|
output_path = args.output_file
|
|
191
191
|
|
|
@@ -200,12 +200,12 @@ def main() -> None:
|
|
|
200
200
|
|
|
201
201
|
args = parse_args()
|
|
202
202
|
if args.version:
|
|
203
|
-
print(
|
|
203
|
+
print("data-flow-diagram", VERSION)
|
|
204
204
|
sys.exit(0)
|
|
205
205
|
|
|
206
206
|
try:
|
|
207
207
|
run(args)
|
|
208
208
|
except model.DfdException as e:
|
|
209
|
-
text = f
|
|
209
|
+
text = f"ERROR: {e}"
|
|
210
210
|
print_error(text)
|
|
211
211
|
sys.exit(1)
|
|
@@ -15,33 +15,33 @@ def check(
|
|
|
15
15
|
prefix = model.mk_err_prefix_from(dep.source)
|
|
16
16
|
|
|
17
17
|
# load source text
|
|
18
|
-
if dep.to_graph.startswith(
|
|
18
|
+
if dep.to_graph.startswith("#"):
|
|
19
19
|
# from snippet
|
|
20
20
|
name = dep.to_graph[1:]
|
|
21
21
|
if name not in snippet_by_name:
|
|
22
22
|
errors.append(f'{prefix}Referring to unknown snippet "{name}"')
|
|
23
23
|
continue
|
|
24
24
|
text = snippet_by_name[name].text
|
|
25
|
-
what =
|
|
25
|
+
what = "snippet"
|
|
26
26
|
else:
|
|
27
27
|
# from file
|
|
28
28
|
name = dep.to_graph
|
|
29
29
|
try:
|
|
30
|
-
with open(name, encoding=
|
|
30
|
+
with open(name, encoding="utf-8") as f:
|
|
31
31
|
text = f.read()
|
|
32
32
|
except FileNotFoundError as e:
|
|
33
33
|
if name in snippet_by_name:
|
|
34
34
|
errors.append(f'{prefix}{e}. Did you mean "#{name}" ?')
|
|
35
35
|
else:
|
|
36
|
-
errors.append(f
|
|
36
|
+
errors.append(f"{prefix}{e}")
|
|
37
37
|
continue
|
|
38
|
-
what =
|
|
38
|
+
what = "file"
|
|
39
39
|
|
|
40
40
|
# if only graph is targetted, we're done
|
|
41
41
|
if dep.to_item is None:
|
|
42
42
|
if dep.to_type != model.NONE:
|
|
43
43
|
errors.append(
|
|
44
|
-
f
|
|
44
|
+
f"{prefix}A whole graph may only be referred to "
|
|
45
45
|
f'by an item of type "{model.NONE}", and not '
|
|
46
46
|
f'"{dep.to_type}"'
|
|
47
47
|
)
|
|
@@ -69,8 +69,8 @@ def check(
|
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
if errors:
|
|
72
|
-
errors.insert(0,
|
|
73
|
-
raise model.DfdException(
|
|
72
|
+
errors.insert(0, "Dependency error(s) found:")
|
|
73
|
+
raise model.DfdException("\n\n".join(errors))
|
|
74
74
|
|
|
75
75
|
|
|
76
76
|
def find_item(name: str, statements: model.Statements) -> model.Item | None:
|
|
@@ -41,13 +41,13 @@ def build(
|
|
|
41
41
|
|
|
42
42
|
def wrap(text: str, cols: int) -> str:
|
|
43
43
|
res: list[str] = []
|
|
44
|
-
for each in text.strip().split(
|
|
45
|
-
res += textwrap.wrap(each, width=cols, break_long_words=False) or [
|
|
46
|
-
return
|
|
44
|
+
for each in text.strip().split("\\n"):
|
|
45
|
+
res += textwrap.wrap(each, width=cols, break_long_words=False) or [""]
|
|
46
|
+
return "\\n".join(res)
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
class Generator:
|
|
50
|
-
RX_NUMBERED_NAME = re.compile(r
|
|
50
|
+
RX_NUMBERED_NAME = re.compile(r"(\d+[.])(.*)")
|
|
51
51
|
|
|
52
52
|
def __init__(
|
|
53
53
|
self, graph_options: model.GraphOptions, attribs: model.Attribs
|
|
@@ -60,32 +60,32 @@ class Generator:
|
|
|
60
60
|
self.attribs_rx = self._compile_attribs_names(attribs)
|
|
61
61
|
|
|
62
62
|
def append(self, line: str, statement: model.Statement) -> None:
|
|
63
|
-
self.lines.append(
|
|
63
|
+
self.lines.append("")
|
|
64
64
|
text = model.pack(statement.source.text)
|
|
65
|
-
self.lines.append(f
|
|
65
|
+
self.lines.append(f"/* {statement.source.line_nr}: {text} */")
|
|
66
66
|
self.lines.append(line)
|
|
67
67
|
|
|
68
68
|
def generate_item(self, item: model.Item) -> None:
|
|
69
69
|
copy = model.Item(**item.__dict__)
|
|
70
70
|
hits = self.RX_NUMBERED_NAME.findall(copy.text)
|
|
71
71
|
if hits:
|
|
72
|
-
copy.text =
|
|
72
|
+
copy.text = "\\n".join(hits[0])
|
|
73
73
|
|
|
74
74
|
copy.text = wrap(copy.text, self.graph_options.item_text_width)
|
|
75
|
-
attrs = copy.attrs or
|
|
75
|
+
attrs = copy.attrs or ""
|
|
76
76
|
attrs = self._expand_attribs(attrs)
|
|
77
77
|
|
|
78
78
|
match copy.type:
|
|
79
79
|
case model.PROCESS:
|
|
80
80
|
if self.graph_options.is_context:
|
|
81
|
-
shape =
|
|
82
|
-
fc =
|
|
81
|
+
shape = "circle"
|
|
82
|
+
fc = "white"
|
|
83
83
|
else:
|
|
84
|
-
shape =
|
|
84
|
+
shape = "ellipse"
|
|
85
85
|
fc = '"#eeeeee"'
|
|
86
86
|
line = (
|
|
87
87
|
f'"{copy.name}" [shape={shape} label="{copy.text}" '
|
|
88
|
-
f
|
|
88
|
+
f"fillcolor={fc} style=filled {attrs}]"
|
|
89
89
|
)
|
|
90
90
|
case model.CONTROL:
|
|
91
91
|
fc = '"#eeeeee"'
|
|
@@ -96,7 +96,7 @@ class Generator:
|
|
|
96
96
|
case model.ENTITY:
|
|
97
97
|
line = (
|
|
98
98
|
f'"{copy.name}" [shape=rectangle label="{copy.text}" '
|
|
99
|
-
f
|
|
99
|
+
f"{attrs}]"
|
|
100
100
|
)
|
|
101
101
|
case model.STORE:
|
|
102
102
|
d = self._attrib_to_dict(copy, attrs)
|
|
@@ -112,22 +112,22 @@ class Generator:
|
|
|
112
112
|
case _:
|
|
113
113
|
prefix = model.mk_err_prefix_from(copy.source)
|
|
114
114
|
raise model.DfdException(
|
|
115
|
-
f
|
|
115
|
+
f"{prefix}Unsupported item type " f'"{copy.type}"'
|
|
116
116
|
)
|
|
117
117
|
self.append(line, item)
|
|
118
118
|
|
|
119
119
|
def _attrib_to_dict(self, item: model.Item, attrs: str) -> dict[str, str]:
|
|
120
120
|
d = self._item_to_html_dict(item)
|
|
121
|
-
d.update({
|
|
121
|
+
d.update({"fontcolor": "black", "color": "black"})
|
|
122
122
|
attrs_d = {
|
|
123
|
-
k: v for k, v in [each.split(
|
|
123
|
+
k: v for k, v in [each.split("=", 1) for each in attrs.split()]
|
|
124
124
|
}
|
|
125
125
|
d.update(attrs_d)
|
|
126
126
|
return d
|
|
127
127
|
|
|
128
128
|
def _item_to_html_dict(self, item: model.Item) -> dict[str, Any]:
|
|
129
129
|
d = item.__dict__
|
|
130
|
-
d[
|
|
130
|
+
d["text"] = d["text"].replace("\\n", "<br/>")
|
|
131
131
|
return d
|
|
132
132
|
|
|
133
133
|
def _compile_attribs_names(
|
|
@@ -135,8 +135,8 @@ class Generator:
|
|
|
135
135
|
) -> re.Pattern[str] | None:
|
|
136
136
|
if not attribs:
|
|
137
137
|
return None
|
|
138
|
-
names = [
|
|
139
|
-
pattern =
|
|
138
|
+
names = ["\\b" + re.escape(k) + "\\b" for k in attribs.keys()]
|
|
139
|
+
pattern = "|".join(names)
|
|
140
140
|
return re.compile(pattern)
|
|
141
141
|
|
|
142
142
|
def _expand_attribs(self, attrs: str) -> str:
|
|
@@ -144,10 +144,10 @@ class Generator:
|
|
|
144
144
|
alias = m[0]
|
|
145
145
|
if alias not in self.attribs:
|
|
146
146
|
raise model.DfdException(
|
|
147
|
-
f
|
|
147
|
+
f"Alias "
|
|
148
148
|
f'"{alias}" '
|
|
149
|
-
f
|
|
150
|
-
f
|
|
149
|
+
f"not found in "
|
|
150
|
+
f"{pprint.pformat(self.attribs)}"
|
|
151
151
|
)
|
|
152
152
|
|
|
153
153
|
return self.attribs[alias].text
|
|
@@ -158,7 +158,7 @@ class Generator:
|
|
|
158
158
|
|
|
159
159
|
def generate_star(self, text: str) -> str:
|
|
160
160
|
text = wrap(text, self.graph_options.item_text_width)
|
|
161
|
-
star_name = f
|
|
161
|
+
star_name = f"__star_{self.star_nr}__"
|
|
162
162
|
line = f'"{star_name}" [shape=none label="{text}" {TMPL.DOT_FONT_EDGE}]'
|
|
163
163
|
self.lines.append(line)
|
|
164
164
|
self.star_nr += 1
|
|
@@ -170,57 +170,57 @@ class Generator:
|
|
|
170
170
|
src_item: model.Item | None,
|
|
171
171
|
dst_item: model.Item | None,
|
|
172
172
|
) -> None:
|
|
173
|
-
text = conn.text or
|
|
173
|
+
text = conn.text or ""
|
|
174
174
|
text = wrap(text, self.graph_options.connection_text_width)
|
|
175
175
|
|
|
176
176
|
src_port = dst_port = ""
|
|
177
177
|
|
|
178
178
|
if not src_item:
|
|
179
179
|
src_name = self.generate_star(text)
|
|
180
|
-
text =
|
|
180
|
+
text = ""
|
|
181
181
|
else:
|
|
182
182
|
src_name = src_item.name
|
|
183
183
|
if src_item.type == model.CHANNEL:
|
|
184
|
-
src_port =
|
|
184
|
+
src_port = ":x:c"
|
|
185
185
|
|
|
186
186
|
if not dst_item:
|
|
187
187
|
dst_name = self.generate_star(text)
|
|
188
|
-
text =
|
|
188
|
+
text = ""
|
|
189
189
|
else:
|
|
190
190
|
dst_name = dst_item.name
|
|
191
191
|
if dst_item.type == model.CHANNEL:
|
|
192
|
-
dst_port =
|
|
192
|
+
dst_port = ":x:c"
|
|
193
193
|
|
|
194
194
|
attrs = f'label="{text}"'
|
|
195
195
|
|
|
196
196
|
if conn.attrs:
|
|
197
|
-
attrs +=
|
|
197
|
+
attrs += " " + self._expand_attribs(conn.attrs)
|
|
198
198
|
|
|
199
199
|
match conn.type:
|
|
200
200
|
case model.FLOW:
|
|
201
201
|
if conn.reversed:
|
|
202
|
-
attrs +=
|
|
202
|
+
attrs += " dir=back"
|
|
203
203
|
case model.BFLOW:
|
|
204
|
-
attrs +=
|
|
204
|
+
attrs += " dir=both"
|
|
205
205
|
case model.CFLOW:
|
|
206
206
|
if conn.reversed:
|
|
207
|
-
attrs +=
|
|
208
|
-
attrs +=
|
|
207
|
+
attrs += " dir=back"
|
|
208
|
+
attrs += " arrowtail=normalnormal"
|
|
209
209
|
else:
|
|
210
|
-
attrs +=
|
|
210
|
+
attrs += " arrowhead=normalnormal"
|
|
211
211
|
case model.UFLOW:
|
|
212
|
-
attrs +=
|
|
212
|
+
attrs += " dir=none"
|
|
213
213
|
case model.SIGNAL:
|
|
214
214
|
if conn.reversed:
|
|
215
|
-
attrs +=
|
|
216
|
-
attrs +=
|
|
215
|
+
attrs += " dir=back"
|
|
216
|
+
attrs += " style=dashed"
|
|
217
217
|
case _:
|
|
218
218
|
prefix = model.mk_err_prefix_from(conn.source)
|
|
219
219
|
raise model.DfdException(
|
|
220
|
-
f
|
|
220
|
+
f"{prefix}Unsupported connection type " f'"{conn.type}"'
|
|
221
221
|
)
|
|
222
222
|
if conn.relaxed:
|
|
223
|
-
attrs +=
|
|
223
|
+
attrs += " constraint=false"
|
|
224
224
|
|
|
225
225
|
line = f'"{src_name}"{src_port} -> "{dst_name}"{dst_port} [{attrs}]'
|
|
226
226
|
self.append(line, conn)
|
|
@@ -229,17 +229,17 @@ class Generator:
|
|
|
229
229
|
pass
|
|
230
230
|
|
|
231
231
|
def generate_frame(self, frame: model.Frame) -> None:
|
|
232
|
-
self.append(f
|
|
232
|
+
self.append(f"subgraph cluster_{self.frame_nr} {{", frame)
|
|
233
233
|
self.frame_nr += 1
|
|
234
234
|
|
|
235
235
|
self.lines.append(f' label="{frame.text}"')
|
|
236
236
|
if frame.attrs:
|
|
237
237
|
attrs = self._expand_attribs(frame.attrs)
|
|
238
|
-
self.lines.append(f
|
|
238
|
+
self.lines.append(f" {attrs}")
|
|
239
239
|
|
|
240
240
|
for item in frame.items:
|
|
241
241
|
self.lines.append(f' "{item}"')
|
|
242
|
-
self.lines.append(
|
|
242
|
+
self.lines.append("}")
|
|
243
243
|
|
|
244
244
|
def generate_dot_text(self, title: str) -> str:
|
|
245
245
|
graph_params = []
|
|
@@ -250,16 +250,19 @@ class Generator:
|
|
|
250
250
|
graph_params.append(TMPL.DOT_GRAPH_TITLE.format(title=title))
|
|
251
251
|
|
|
252
252
|
if self.graph_options.is_vertical:
|
|
253
|
-
graph_params.append(
|
|
253
|
+
graph_params.append("rankdir=TB")
|
|
254
254
|
else:
|
|
255
|
-
graph_params.append(
|
|
255
|
+
graph_params.append("rankdir=LR")
|
|
256
256
|
|
|
257
|
-
|
|
257
|
+
if self.graph_options.is_rotated:
|
|
258
|
+
graph_params.append(f"rotate=90")
|
|
259
|
+
|
|
260
|
+
block = "\n".join(self.lines).replace("\n", "\n ")
|
|
258
261
|
text = TMPL.DOT.format(
|
|
259
262
|
title=title,
|
|
260
263
|
block=block,
|
|
261
|
-
graph_params=
|
|
262
|
-
).replace(
|
|
264
|
+
graph_params="\n ".join(graph_params),
|
|
265
|
+
).replace("\n \n", "\n\n")
|
|
263
266
|
# print(text)
|
|
264
267
|
return text
|
|
265
268
|
|
|
@@ -273,7 +276,7 @@ def generate_dot(
|
|
|
273
276
|
"""Iterate over statements and generate a dot source file"""
|
|
274
277
|
|
|
275
278
|
def get_item(name: str) -> Optional[model.Item]:
|
|
276
|
-
return None if name ==
|
|
279
|
+
return None if name == "*" else items_by_name[name]
|
|
277
280
|
|
|
278
281
|
for statement in statements:
|
|
279
282
|
match statement:
|
|
@@ -328,18 +331,22 @@ def handle_options(
|
|
|
328
331
|
match statement:
|
|
329
332
|
case model.Style() as style:
|
|
330
333
|
match style.style:
|
|
331
|
-
case
|
|
334
|
+
case "vertical":
|
|
332
335
|
options.is_vertical = True
|
|
333
|
-
case
|
|
336
|
+
case "context":
|
|
334
337
|
options.is_context = True
|
|
335
|
-
case
|
|
338
|
+
case "horizontal":
|
|
336
339
|
options.is_vertical = False
|
|
337
|
-
case
|
|
340
|
+
case "rotated":
|
|
341
|
+
options.is_rotated = True
|
|
342
|
+
case "unrotated":
|
|
343
|
+
options.is_rotated = False
|
|
344
|
+
case "item-text-width":
|
|
338
345
|
try:
|
|
339
346
|
options.item_text_width = int(style.value)
|
|
340
347
|
except ValueError as e:
|
|
341
348
|
raise model.DfdException(f'{prefix}{e}"')
|
|
342
|
-
case
|
|
349
|
+
case "connection-text-width":
|
|
343
350
|
try:
|
|
344
351
|
options.connection_text_width = int(style.value)
|
|
345
352
|
except ValueError as e:
|
|
@@ -347,7 +354,7 @@ def handle_options(
|
|
|
347
354
|
|
|
348
355
|
case _:
|
|
349
356
|
raise model.DfdException(
|
|
350
|
-
f
|
|
357
|
+
f"{prefix}Unsupported style " f'"{style.style}"'
|
|
351
358
|
)
|
|
352
359
|
|
|
353
360
|
continue
|