├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── asciidoctor-example
├── asciidoctor-html.sh
├── asciidoctor-pdf.sh
├── example.adoc
├── example.html
├── example.pdf
├── wavedrom_test0.svg
├── wavedrom_test1.svg
├── wavedrom_test10.svg
├── wavedrom_test11.svg
├── wavedrom_test12.svg
├── wavedrom_test1n.svg
├── wavedrom_test2.svg
├── wavedrom_test3.svg
├── wavedrom_test4.svg
├── wavedrom_test5.svg
├── wavedrom_test6.svg
├── wavedrom_test7.svg
├── wavedrom_test8.svg
└── wavedrom_test9.svg
├── css
└── default.css
├── doc
├── demo1.svg
├── demo2.svg
└── demo3.svg
├── setup.cfg
├── setup.py
├── test
├── brick_regressions.py
├── conftest.py
├── diff.py
├── files
│ ├── assign_74ls688.json
│ ├── assign_binary2gray.json
│ ├── assign_gray2binary.json
│ ├── assign_iec60617.json
│ ├── assign_xor.json
│ ├── bitfield_0.json
│ ├── issue_10.json
│ ├── issue_11.json
│ ├── issue_13.json
│ ├── issue_14.json
│ ├── issue_16.json
│ ├── issue_37.json
│ ├── issue_7.json
│ ├── signal_0.json
│ ├── subcycle_0.json
│ ├── subcycle_1.json
│ ├── tutorial_0.json
│ ├── tutorial_0n.json
│ ├── tutorial_1.json
│ ├── tutorial_10.json
│ ├── tutorial_11.json
│ ├── tutorial_12.json
│ ├── tutorial_1n.json
│ ├── tutorial_2.json
│ ├── tutorial_3.json
│ ├── tutorial_4.json
│ ├── tutorial_5.json
│ ├── tutorial_6.json
│ ├── tutorial_7.json
│ ├── tutorial_8.json
│ └── tutorial_9.json
├── single_test.py
├── test_brick_regression.py
└── test_render.py
├── tox.ini
└── wavedrom
├── __init__.py
├── assign.py
├── attrdict.py
├── base.py
├── bitfield.py
├── css.py
├── tspan.py
├── waveform.py
└── waveskin.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | env/
12 | build/
13 | develop-eggs/
14 | dist/
15 | downloads/
16 | eggs/
17 | .eggs/
18 | lib/
19 | lib64/
20 | parts/
21 | sdist/
22 | var/
23 | wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | wavedrom/version.py
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .coverage
43 | .coverage.*
44 | .cache
45 | nosetests.xml
46 | coverage.xml
47 | *.cover
48 | .hypothesis/
49 |
50 | # Translations
51 | *.mo
52 | *.pot
53 |
54 | # Django stuff:
55 | *.log
56 | local_settings.py
57 |
58 | # Flask stuff:
59 | instance/
60 | .webassets-cache
61 |
62 | # Scrapy stuff:
63 | .scrapy
64 |
65 | # Sphinx documentation
66 | docs/_build/
67 |
68 | # PyBuilder
69 | target/
70 |
71 | # Jupyter Notebook
72 | .ipynb_checkpoints
73 |
74 | # pyenv
75 | .python-version
76 |
77 | # celery beat schedule file
78 | celerybeat-schedule
79 |
80 | # SageMath parsed files
81 | *.sage.py
82 |
83 | # dotenv
84 | .env
85 |
86 | # virtualenv
87 | .venv
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 | .spyproject
94 |
95 | # Rope project settings
96 | .ropeproject
97 |
98 | # mkdocs documentation
99 | /site
100 |
101 | # mypy
102 | .mypy_cache/
103 |
104 | test/*.svg
105 | test/*.png
106 | test/*.pdf
107 |
108 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "3.7"
4 | - "3.8"
5 | - "3.9"
6 | - "3.10"
7 |
8 | before_install:
9 | - sudo apt-get install -y npm
10 |
11 | install:
12 | - pip install tox-travis
13 |
14 | script:
15 | - tox
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2011-2019 Aliaksei Chapyzhenka, BreizhGeek, Kazuki Yamamoto,
4 | MutantPlatypus, Stefan Wallentowitz
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WaveDromPy
2 |
3 | This is a python module and command line fully compatible with [WaveDrom](https://wavedrom.com/), which is originally implemented in JavaScript. It is useful if you want to generate wavedrom diagrams from a python environment or simply don't want to install the _Node.js_ environment just to use WaveDrom as simple command line.
4 |
5 | WaveDromPy is for example used in [sphinxcontrib-wavedrom](https://pypi.org/project/sphinxcontrib-wavedrom/) to render wavedrom for Sphinx documentation. While the original project renders the diagrams in JavaScript in the browser, WaveDromPy renders them to SVG files.
6 |
7 | This tool is a direct translation of original Javascript file _WaveDrom.js_ to Python. No extra feature added. We seek to have it fully compatible.
8 |
9 | The tool _WaveDromPy_ directly converts _WaveDrom_ compatible JSON files into SVG format.
10 |
11 | [](https://travis-ci.org/wallento/wavedrompy)
12 | [](https://badge.fury.io/py/wavedrom)
13 |
14 | ## Installation
15 |
16 | It is most easy to just install wavedrom via pip/pypi:
17 |
18 | pip install wavedrom
19 |
20 | Alternatively you can install the latest version from this repository:
21 |
22 | pip install git+https://github.com/wallento/wavedrompy
23 |
24 | or from your local copy:
25 |
26 | pip install .
27 |
28 | ## Usage
29 |
30 | You can either use the tool from Python:
31 |
32 | import wavedrom
33 | svg = wavedrom.render("""
34 | { "signal": [
35 | { "name": "CK", "wave": "P.......", "period": 2 },
36 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 },
37 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 },
38 | { "name": "DQS", "wave": "z.......0.1010z." },
39 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" }
40 | ]}""")
41 | svg.saveas("demo1.svg")
42 |
43 | This will render a waveform as:
44 |
45 | 
46 |
47 | You can find more examples [in the WaveDrom tutorial](https://wavedrom.com/tutorial.html).
48 |
49 | A second feature is that WaveDrom can render logic circuit diagrams:
50 |
51 | import wavedrom
52 | svg = wavedrom.render("""
53 | { "assign":[
54 | ["out",
55 | ["|",
56 | ["&", ["~", "a"], "b"],
57 | ["&", ["~", "b"], "a"]
58 | ]
59 | ]
60 | ]}""")
61 | svg.saveas("demo2.svg")
62 |
63 | This will render a as:
64 |
65 | 
66 |
67 | You can find more examples [in the WaveDrom tutorial2](https://wavedrom.com/tutorial2.html).
68 |
69 | Finally, wavedrom can draw registers as bitfields:
70 |
71 | import wavedrom
72 | svg = wavedrom.render("""
73 | {"reg": [
74 | { "name": "IPO", "bits": 8, "attr": "RO" },
75 | { "bits": 7 },
76 | { "name": "BRK", "bits": 5, "attr": "RW", "type": 4 },
77 | { "name": "CPK", "bits": 1 },
78 | { "name": "Clear", "bits": 3 },
79 | { "bits": 8 }
80 | ]
81 | ]}""")
82 | svg.saveas("demo3.svg")
83 |
84 |
85 | This will render as:
86 |
87 | 
88 |
89 | This mode is documented as part of the [bit-field](https://www.npmjs.com/package/bit-field) JavaScript package.
90 |
91 | Alternatively, WaveDromPy can be called from the command line:
92 |
93 | wavedrompy --input input.json --svg output.svg
94 |
95 | ## Important notice
96 |
97 | The command line uses Python's JSON interpreter that is more restrictive (coherent with the JSOC spec), while the JavaScript json is more relaxed:
98 |
99 | * All strings have to be written between quotes (""),
100 | * Extra comma (,) not supported at end of lists or dictionaries
101 |
102 | ## AsciiDoctor example
103 |
104 | An _AsciiDoctor_ example is provided to directly generate timing diagrams from _AsciiDoctor_ formatted documents.
105 |
106 |
--------------------------------------------------------------------------------
/asciidoctor-example/asciidoctor-html.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | export PATH=$PATH:$(pwd)/..
3 | asciidoctor -r asciidoctor-diagram example.adoc
4 |
--------------------------------------------------------------------------------
/asciidoctor-example/asciidoctor-pdf.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | export PATH=$PATH:$(pwd)/..
3 | asciidoctor-pdf -r asciidoctor-diagram example.adoc
4 |
--------------------------------------------------------------------------------
/asciidoctor-example/example.adoc:
--------------------------------------------------------------------------------
1 | = AsciiDoctor demo for HDL designers =
2 | v1.0
3 | :toc: left
4 | :icons: font
5 | :numbered:
6 | :source-autofit:
7 | :doctype: book
8 | :description: An AsciiDoctor demo with wavedrom python command line
9 | :source-highlighter: pygments
10 |
11 | == WaveDrom diagrams examples ==
12 |
13 | === Test #0 ===
14 | [wavedrom,wavedrom_test0,svg]
15 | ----
16 | { "signal": [{ "name": "Alfa", "wave": "01.zx=ud.23.45" }] }
17 | ----
18 |
19 | === Test #1 (default) ===
20 | [wavedrom,wavedrom_test1,svg]
21 | ----
22 | { "signal": [
23 | { "name": "pclk", "wave": "p......." },
24 | { "name": "Pclk", "wave": "P......." },
25 | { "name": "nclk", "wave": "n......." },
26 | { "name": "Nclk", "wave": "N......." },
27 | {},
28 | { "name": "clk0", "wave": "phnlPHNL" },
29 | { "name": "clk1", "wave": "xhlhLHl." },
30 | { "name": "clk2", "wave": "hpHplnLn" },
31 | { "name": "clk3", "wave": "nhNhplPl" },
32 | { "name": "clk4", "wave": "xlh.L.Hx" }
33 | ]}
34 | ----
35 |
36 | === Test #1 (narrow) ===
37 | [wavedrom,wavedrom_test1n,svg]
38 | ----
39 | { "signal": [
40 | { "name": "pclk", "wave": "p......." },
41 | { "name": "Pclk", "wave": "P......." },
42 | { "name": "nclk", "wave": "n......." },
43 | { "name": "Nclk", "wave": "N......." },
44 | {},
45 | { "name": "clk0", "wave": "phnlPHNL" },
46 | { "name": "clk1", "wave": "xhlhLHl." },
47 | { "name": "clk2", "wave": "hpHplnLn" },
48 | { "name": "clk3", "wave": "nhNhplPl" },
49 | { "name": "clk4", "wave": "xlh.L.Hx" }
50 | ],
51 | "config": { "skin": "narrow" }
52 | }
53 | ----
54 |
55 | === Test #2 ===
56 | [wavedrom,wavedrom_test2,svg]
57 | ----
58 | { "signal": [
59 | { "name": "clk", "wave": "P......" },
60 | { "name": "bus", "wave": "x.==.=x", "data": ["head", "body", "tail", "data"] },
61 | { "name": "wire", "wave": "0.1..0." }
62 | ]}
63 | ----
64 |
65 | === Test #3 ===
66 | [wavedrom,wavedrom_test3,svg]
67 | ----
68 | { "signal": [
69 | { "name": "clk", "wave": "p.....|..." },
70 | { "name": "data", "wave": "x.345x|=.x", "data": ["head", "body", "tail", "data"] },
71 | { "name": "Request", "wave": "0.1..0|1.0" },
72 | {},
73 | { "name": "Acknowledge", "wave": "1.....|01." }
74 | ]}
75 | ----
76 |
77 | === Test #4 ===
78 | [wavedrom,wavedrom_test4,svg]
79 | ----
80 | { "signal": [
81 | { "name": "clk", "wave": "p..Pp..P"},
82 | ["Master",
83 | ["ctrl",
84 | {"name": "write", "wave": "01.0...."},
85 | {"name": "read", "wave": "0...1..0"}
86 | ],
87 | { "name": "addr", "wave": "x3.x4..x", "data": "A1 A2"},
88 | { "name": "wdata", "wave": "x3.x....", "data": "D1" }
89 | ],
90 | {},
91 | ["Slave",
92 | ["ctrl",
93 | {"name": "ack", "wave": "x01x0.1x"}
94 | ],
95 | { "name": "rdata", "wave": "x.....4x", "data": "Q2"}
96 | ]
97 | ]}
98 | ----
99 |
100 | === Test #5 ===
101 | [wavedrom,wavedrom_test5,svg]
102 | ----
103 | { "signal": [
104 | { "name": "CK", "wave": "P.......", "period": 2 },
105 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 },
106 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 },
107 | { "name": "DQS", "wave": "z.......0.1010z." },
108 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" }
109 | ]}
110 | ----
111 |
112 | === Test #6 ===
113 | [wavedrom,wavedrom_test6,svg]
114 | ----
115 | { "signal": [
116 | { "name": "clk", "wave": "p...." },
117 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] },
118 | { "name": "Request", "wave": "01..0" }
119 | ],
120 | "config": { "hscale": 1 }
121 | }
122 | ----
123 |
124 | === Test #7 ===
125 | [wavedrom,wavedrom_test7,svg]
126 | ----
127 | { "signal" : [
128 | { "name": "clk", "wave": "p...." },
129 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] },
130 | { "name": "Request", "wave": "01..0" }
131 | ],
132 | "config" : { "hscale" : 2 }
133 | }
134 | ----
135 |
136 | === Test #8 ===
137 | [wavedrom,wavedrom_test8,svg]
138 | ----
139 | { "signal" : [
140 | { "name": "clk", "wave": "p...." },
141 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] },
142 | { "name": "Request", "wave": "01..0" }
143 | ],
144 | "config" : { "hscale" : 3 }
145 | }
146 | ----
147 |
148 | === Test #9 ===
149 | [wavedrom,wavedrom_test9,svg]
150 | ----
151 | {"signal": [
152 | {"name":"clk", "wave": "p...." },
153 | {"name":"data", "wave": "x345x", "data": "a b c" },
154 | {"name":"Request", "wave": "01..0" }
155 | ],
156 | "head":{
157 | "text":"WaveDrom example",
158 | "tick":0
159 | },
160 | "foot":{
161 | "text":"Figure 100",
162 | "tock":9
163 | }
164 | }
165 | ----
166 |
167 | === Test #10 ===
168 | [wavedrom,wavedrom_test10,svg]
169 | ----
170 | {"signal": [
171 | {"name":"clk", "wave": "p.....PPPPp...." },
172 | {"name":"dat", "wave": "x....2345x.....", "data": "a b c d" },
173 | {"name":"req", "wave": "0....1...0....." }
174 | ],
175 | "head": {"text":
176 | ["tspan",
177 | ["tspan", {"class":"error h1"}, "error "],
178 | ["tspan", {"class":"warning h2"}, "warning "],
179 | ["tspan", {"class":"info h3"}, "info "],
180 | ["tspan", {"class":"success h4"}, "success "],
181 | ["tspan", {"class":"muted h5"}, "muted "],
182 | ["tspan", {"class":"h6"}, "h6 "],
183 | "default ",
184 | ["tspan", {"fill":"pink", "font-weight":"bold", "font-style":"italic"}, "pink-bold-italic"]
185 | ]
186 | },
187 | "foot": {"text":
188 | ["tspan", "E=mc",
189 | ["tspan", {"dy":"-5"}, "2"],
190 | ["tspan", {"dy": "5"}, ". "],
191 | ["tspan", {"font-size":"25"}, "B "],
192 | ["tspan", {"text-decoration":"overline"},"over "],
193 | ["tspan", {"text-decoration":"underline"},"under "],
194 | ["tspan", {"baseline-shift":"sub"}, "sub "],
195 | ["tspan", {"baseline-shift":"super"}, "super "]
196 | ],"tock":-5
197 | }
198 | }
199 | ----
200 |
201 | === Test #11 ===
202 | [wavedrom,wavedrom_test11,svg]
203 | ----
204 | { "signal": [
205 | { "name": "A", "wave": "01........0....", "node": ".a........j" },
206 | { "name": "B", "wave": "0.1.......0.1..", "node": "..b.......i" },
207 | { "name": "C", "wave": "0..1....0...1..", "node": "...c....h.." },
208 | { "name": "D", "wave": "0...1..0.....1.", "node": "....d..g..." },
209 | { "name": "E", "wave": "0....10.......1", "node": ".....ef...." }
210 | ],
211 | "edge": [
212 | "a~b t1", "c-~a t2", "c-~>d time 3", "d~-e",
213 | "e~>f", "f->g", "g-~>h", "h~>i some text", "h~->j"
214 | ]
215 | }
216 | ----
217 |
218 | === Test #12 ===
219 | [wavedrom,wavedrom_test12,svg]
220 | ----
221 | { "signal": [
222 | { "name": "A", "wave": "01..0..", "node": ".a..e.." },
223 | { "name": "B", "wave": "0.1..0.", "node": "..b..d.", "phase":0.5 },
224 | { "name": "C", "wave": "0..1..0", "node": "...c..f" },
225 | { "node": "...g..h" }
226 | ],
227 | "edge": [
228 | "b-|a t1", "a-|c t2", "b-|-c t3", "c-|->e t4", "e-|>f more text",
229 | "e|->d t6", "c-g", "f-h", "g<->h 3 ms"
230 | ]
231 | }
232 | ----
233 |
234 | == Port description example ==
235 |
236 | [width="100%",cols="<2,^1,^7",options="header"]
237 | |=================================
238 | |Signal name |Type | Description
239 | |clk | in <| Clk input
240 | |reset | in <| Reset
241 | |address | in <| Address bus
242 | |read | in <| Read signal
243 | |readdata | out <| Read data bus
244 | |readvalid | out <| Read valid signal
245 | |write | in <| Write signal
246 | |writedata | in <| Write data bus
247 | |=================================
248 |
249 | == Register description example ==
250 |
251 | [width="100%",cols="^2,^1,^2,^1,^4",options="header"]
252 | |=========================================================
253 | |Address |Bits | Field Name |Access |Description
254 | .4+|0x00000000
255 | |31:24 | NU |RO <| Not used.
256 | |23:16 | VMAJ |RO <| Version major.
257 | |15:8 | VMIN |RO <| Version minor.
258 | |7:0 | VPATCH |RO <| Version patch.
259 | .7+|0x00000004
260 | |31:16 | STATUS | RO <| Status bits.
261 | |15 | PLL_LOCKED | RO <| PLL locked.
262 | |14 | DDR_INIT_DONE | RO <| DDR Init_done.
263 | |13:12 | NU | RO <| Not used.
264 | |11:8 | GROUP_0_INTR | R/W <| Group #0 interrupt requests.
265 | |7:4 | GROUP_1_INTR | R/W <| Group #1 interrupt requests.
266 | |3:0 | GROUP_2_INTR | R/W <| Group #2 interrupt requests.
267 | |=========================================================
268 |
269 | == VHDL syntax coloring example ==
270 | [source,vhdl]
271 | ----
272 | proc_column_counter : process ( reset, clk )
273 | begin
274 | if reset = '1' then
275 | col <= 0;
276 | elsif rising_edge( clk ) then
277 | if enable then
278 | if sink_endofpacket = '1' then
279 | col <= 0;
280 | elsif col = g_width - g_data_size / c_pixel_size then
281 | col <= 0;
282 | else
283 | col <= col + g_data_size / c_pixel_size;
284 | end if;
285 | end if;
286 | end if;
287 | end process proc_column_counter;
288 | ----
--------------------------------------------------------------------------------
/asciidoctor-example/example.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wallento/wavedrompy/82a0e75cf201ed4e9f9c2ec3e56e54b6ec93c11a/asciidoctor-example/example.pdf
--------------------------------------------------------------------------------
/asciidoctor-example/wavedrom_test0.svg:
--------------------------------------------------------------------------------
1 |
708 |
--------------------------------------------------------------------------------
/asciidoctor-example/wavedrom_test6.svg:
--------------------------------------------------------------------------------
1 |
731 |
--------------------------------------------------------------------------------
/asciidoctor-example/wavedrom_test9.svg:
--------------------------------------------------------------------------------
1 |
757 |
--------------------------------------------------------------------------------
/css/default.css:
--------------------------------------------------------------------------------
1 | text{font-size:11pt;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;fill-opacity:1;font-family:Helvetica}.muted{fill:#aaa}.warning{fill:#f6b900}.error{fill:#f60000}.info{fill:#0041c4}.success{fill:#00ab00}.h1{font-size:33pt;font-weight:bold}.h2{font-size:27pt;font-weight:bold}.h3{font-size:20pt;font-weight:bold}.h4{font-size:14pt;font-weight:bold}.h5{font-size:11pt;font-weight:bold}.h6{font-size:8pt;font-weight:bold}.s1{fill:none;stroke:#000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none}.s2{fill:none;stroke:#000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none}.s3{color:#000;fill:none;stroke:#000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:1, 3;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s4{color:#000;fill:none;stroke:#000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible}.s5{fill:#fff;stroke:none}.s6{color:#000;fill:#ffffb4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s7{color:#000;fill:#ffe0b9;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s8{color:#000;fill:#b9e0ff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s9{fill:#000;fill-opacity:1;stroke:none}.s10{color:#000;fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate}.s11{fill:#0041c4;fill-opacity:1;stroke:none}.s12{fill:none;stroke:#0041c4;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none}
2 |
--------------------------------------------------------------------------------
/doc/demo2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/doc/demo3.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 |
2 | [bdist_wheel]
3 | # This flag says to generate wheels that support both Python 2 and Python
4 | # 3. If your code will not run unchanged on both Python 2 and 3, you will
5 | # need to generate separate wheels for each Python version that you
6 | # support.
7 | universal=1
8 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | """A setuptools based setup module.
2 | See:
3 | https://packaging.python.org/en/latest/distributing.html
4 | https://github.com/pypa/sampleproject
5 | """
6 |
7 | # Always prefer setuptools over distutils
8 | import sys
9 |
10 | from setuptools import setup, find_packages
11 | # To use a consistent encoding
12 | from codecs import open
13 | from os import path
14 |
15 | here = path.abspath(path.dirname(__file__))
16 |
17 | # Get the long description from the README file
18 | with open(path.join(here, "README.md"), encoding="utf-8") as f:
19 | long_description = f.read()
20 |
21 | # Arguments marked as "Required" below must be included for upload to PyPI.
22 | # Fields marked as "Optional" may be commented out.
23 |
24 | requires = ["svgwrite",
25 | "six", # 2 and 3 compatibility
26 | "pyyaml"
27 | ]
28 | setup(
29 | # This is the name of your project. The first time you publish this
30 | # package, this name will be registered for you. It will determine how
31 | # users can install this project, e.g.:
32 | #
33 | # $ pip install sampleproject
34 | #
35 | # And where it will live on PyPI: https://pypi.org/project/sampleproject/
36 | #
37 | # There are some restrictions on what makes a valid project name
38 | # specification here:
39 | # https://packaging.python.org/specifications/core-metadata/#name
40 | name="wavedrom", # Required
41 |
42 | # Versions should comply with PEP 440:
43 | # https://www.python.org/dev/peps/pep-0440/
44 | #
45 | # For a discussion on single-sourcing the version across setup.py and the
46 | # project code, see
47 | # https://packaging.python.org/en/latest/single_source_version.html
48 | use_scm_version={
49 | "relative_to": __file__,
50 | "write_to": "wavedrom/version.py",
51 | },
52 |
53 | # This is a one-line description or tagline of what your project does. This
54 | # corresponds to the "Summary" metadata field:
55 | # https://packaging.python.org/specifications/core-metadata/#summary
56 | description="WaveDrom compatible python command line", # Required
57 |
58 | # This is an optional longer description of your project that represents
59 | # the body of text which users will see when they visit PyPI.
60 | #
61 | # Often, this is the same as your README, so you can just read it in from
62 | # that file directly (as we have already done above)
63 | #
64 | # This field corresponds to the "Description" metadata field:
65 | # https://packaging.python.org/specifications/core-metadata/#description-optional
66 | long_description=long_description, # Optional
67 | long_description_content_type='text/markdown',
68 |
69 | # This should be a valid link to your project"s main homepage.
70 | #
71 | # This field corresponds to the "Home-Page" metadata field:
72 | # https://packaging.python.org/specifications/core-metadata/#home-page-optional
73 | url="https://github.com/wallento/wavedrompy", # Optional
74 |
75 | # This should be your name or the name of the organization which owns the
76 | # project.
77 | author="Aliaksei Chapyzhenka, BreizhGeek, Kazuki Yamamoto, Stefan Wallentowitz", # Optional
78 |
79 | # This should be a valid email address corresponding to the author listed
80 | # above.
81 | author_email="k4zuki@github.com, stefan@wallentowitz.de", # Optional
82 |
83 | # Classifiers help users find your project by categorizing it.
84 | #
85 | # For a list of valid classifiers, see
86 | # https://pypi.python.org/pypi?%3Aaction=list_classifiers
87 | classifiers=[ # Optional
88 | # How mature is this project? Common values are
89 | # 3 - Alpha
90 | # 4 - Beta
91 | # 5 - Production/Stable
92 | "Development Status :: 4 - Beta",
93 |
94 | # Indicate who your project is intended for
95 | "Intended Audience :: Developers",
96 | "Topic :: Software Development :: Build Tools",
97 |
98 | # Pick your license as you wish
99 | "License :: OSI Approved :: MIT License",
100 |
101 | # Specify the Python versions you support here. In particular, ensure
102 | # that you indicate whether you support Python 2, Python 3 or both.
103 | "Programming Language :: Python :: 3"
104 | "Programming Language :: Python :: 3.7",
105 | "Programming Language :: Python :: 3.8",
106 | "Programming Language :: Python :: 3.9",
107 | "Programming Language :: Python :: 3.10"
108 | ],
109 |
110 | # This field adds keywords for your project which will appear on the
111 | # project page. What does your project relate to?
112 | #
113 | # Note that this is a string of words separated by whitespace, not a list.
114 | keywords="wavedrom svg", # Optional
115 |
116 | # You can just specify package directories manually here if your project is
117 | # simple. Or you can use find_packages().
118 | #
119 | # Alternatively, if you just want to distribute a single Python file, use
120 | # the `py_modules` argument instead as follows, which will expect a file
121 | # called `my_module.py` to exist:
122 | #
123 | # py_modules=["my_module"],
124 | #
125 | packages=find_packages(exclude=["contrib", "docs", "tests"]), # Required
126 |
127 | # This field lists other packages that your project depends on to run.
128 | # Any package you put here will be installed by pip when your project is
129 | # installed, so they must be valid existing projects.
130 | #
131 | # For an analysis of "install_requires" vs pip"s requirements files see:
132 | # https://packaging.python.org/en/latest/requirements.html
133 | install_requires=requires, # Optional
134 |
135 | # The minimum required Python version for installation
136 | python_requires='>=3.7',
137 |
138 | # List additional groups of dependencies here (e.g. development
139 | # dependencies). Users will be able to install these using the "extras"
140 | # syntax, for example:
141 | #
142 | # $ pip install sampleproject[dev]
143 | #
144 | # Similar to `install_requires` above, these must be valid existing
145 | # projects.
146 | extras_require={ # Optional
147 | # "dev": ["check-manifest"],
148 | "test": [
149 | "xmldiff",
150 | #Per release notes, Python 2 support dropped at version 2.0.0
151 | "cairosvg<2" if sys.version_info.major < 3 else "cairosvg",
152 | "pillow"
153 | ],
154 | },
155 |
156 | setup_requires=[
157 | 'setuptools_scm',
158 | ],
159 |
160 | # If there are data files included in your packages that need to be
161 | # installed, specify them here.
162 | #
163 | # If using Python 2.6 or earlier, then these have to be included in
164 | # MANIFEST.in as well.
165 | package_data={ # Optional
166 | # "sample": ["package_data.dat"],
167 | },
168 |
169 | # Although "package_data" is the preferred approach, in some case you may
170 | # need to place data files outside of your packages. See:
171 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files
172 | #
173 | # In this case, "data_file" will be installed into "/my_data"
174 | data_files=[
175 | # ("my_data", ["data/data_file"]),
176 | ], # Optional
177 |
178 | # To provide executable scripts, use entry points in preference to the
179 | # "scripts" keyword. Entry points provide cross-platform support and allow
180 | # `pip` to create the appropriate form of executable for the target
181 | # platform.
182 | #
183 | # For example, the following would provide a command called `sample` which
184 | # executes the function `main` from this package when invoked:
185 | entry_points={ # Optional
186 | "console_scripts": [
187 | "wavedrompy=wavedrom:main",
188 | ],
189 | },
190 | )
191 |
--------------------------------------------------------------------------------
/test/brick_regressions.py:
--------------------------------------------------------------------------------
1 | from collections import namedtuple
2 |
3 | Regression = namedtuple("Regression", ["wave", "hscale", "period", "phase", "expected"])
4 | Regression.__new__.__defaults__ = ("", 1, 1, 0, [])
5 |
6 |
7 | basic = [
8 | Regression(wave="P", expected=['Pclk', 'nclk']),
9 | Regression(wave="P", hscale=2, expected=['Pclk', '111', 'nclk', '000']),
10 | Regression(wave="P", period=2, expected=['Pclk', '111', 'nclk', '000']),
11 | Regression(wave="P", hscale=2, period=0.5, expected=['Pclk', 'nclk']),
12 | Regression(wave="P", phase=1, expected=['nclk']),
13 | Regression(wave="P", hscale=2, phase=2, expected=['nclk', '000']),
14 | Regression(wave="P.", expected=['Pclk', 'nclk', 'Pclk', 'nclk']),
15 | Regression(wave="P..", expected=['Pclk', 'nclk', 'Pclk', 'nclk', 'Pclk', 'nclk']),
16 | Regression(wave="P.", hscale=2, expected=['Pclk', '111', 'nclk', '000', 'Pclk', '111', 'nclk', '000']),
17 | Regression(wave="P..", hscale=2, expected=['Pclk', '111', 'nclk', '000', 'Pclk', '111', 'nclk', '000',
18 | 'Pclk', '111', 'nclk', '000']),
19 | Regression(wave="px", expected=['pclk', 'nclk', '0mx', 'xxx']),
20 | Regression(wave="hx", expected=['111', '111', '1mx', 'xxx']),
21 | Regression(wave="nx", expected=['nclk', 'pclk', '1mx', 'xxx']),
22 | Regression(wave="lx", expected=['000', '000', '0mx', 'xxx']),
23 | Regression(wave="Px", expected=['Pclk', 'nclk', '0mx', 'xxx']),
24 | Regression(wave="Hx", expected=['111', '111', '1mx', 'xxx']),
25 | Regression(wave="Nx", expected=['Nclk', 'pclk', '1mx', 'xxx']),
26 | Regression(wave="Lx", expected=['000', '000', '0mx', 'xxx']),
27 | Regression(wave="xpx", expected=['xxx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx']),
28 | Regression(wave="xhx", expected=['xxx', 'xxx', 'pclk', '111', '1mx', 'xxx']),
29 | Regression(wave="xnx", expected=['xxx', 'xxx', 'nclk', 'pclk', '1mx', 'xxx']),
30 | Regression(wave="xlx", expected=['xxx', 'xxx', 'nclk', '000', '0mx', 'xxx']),
31 | Regression(wave="xPx", expected=['xxx', 'xxx', 'Pclk', 'nclk', '0mx', 'xxx']),
32 | Regression(wave="xHx", expected=['xxx', 'xxx', 'Pclk', '111', '1mx', 'xxx']),
33 | Regression(wave="xNx", expected=['xxx', 'xxx', 'Nclk', 'pclk', '1mx', 'xxx']),
34 | Regression(wave="xLx", expected=['xxx', 'xxx', 'Nclk', '000', '0mx', 'xxx']),
35 | Regression(wave="0", expected=['000', '000']),
36 | Regression(wave="1", expected=['111', '111']),
37 | Regression(wave="01", expected=['000', '000', '0m1', '111']),
38 | Regression(wave="01", hscale=2, expected=['000', '000', '000', '000', '0m1', '111', '111', '111']),
39 | Regression(wave="01.0", expected=['000', '000', '0m1', '111', '111', '111', '1m0', '000']),
40 | Regression(wave="01.zx=ud.23.45",
41 | expected=['000', '000', '0m1', '111', '111', '111', '1mz', 'zzz', 'zmx', 'xxx', 'xmv-2', 'vvv-2',
42 | 'vmu-2', 'uuu', 'umd', 'ddd', 'ddd', 'ddd', 'dmv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vvv-3',
43 | 'vvv-3', 'vmv-3-4', 'vvv-4', 'vmv-4-5', 'vvv-5']),
44 | Regression(wave="01.zx=ud.23.45", hscale=2,
45 | expected=['000', '000', '000', '000', '0m1', '111', '111', '111', '111', '111', '111', '111', '1mz',
46 | 'zzz', 'zzz', 'zzz', 'zmx', 'xxx', 'xxx', 'xxx', 'xmv-2', 'vvv-2', 'vvv-2', 'vvv-2', 'vmu-2',
47 | 'uuu', 'uuu', 'uuu', 'umd', 'ddd', 'ddd', 'ddd', 'ddd', 'ddd', 'ddd', 'ddd', 'dmv-2', 'vvv-2',
48 | 'vvv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vvv-3', 'vvv-3', 'vvv-3', 'vvv-3', 'vvv-3',
49 | 'vvv-3', 'vmv-3-4', 'vvv-4', 'vvv-4', 'vvv-4', 'vmv-4-5', 'vvv-5', 'vvv-5', 'vvv-5']),
50 | Regression(wave="phnlPHNL", expected=['pclk', 'nclk', 'pclk', '111', 'nclk', 'pclk', 'nclk', '000', 'Pclk', 'nclk',
51 | 'Pclk', '111', 'Nclk', 'pclk', 'Nclk', '000']),
52 | Regression(wave="xhlhLHl.", expected=['xxx', 'xxx', 'pclk', '111', 'nclk', '000', 'pclk', '111', 'Nclk', '000',
53 | 'Pclk', '111', 'nclk', '000', '000', '000']),
54 | Regression(wave="hpHplnLn", expected=['111', '111', '111', 'nclk', 'Pclk', '111', '111', 'nclk', '000', '000',
55 | '000', 'pclk', 'Nclk', '000', '000', 'pclk']),
56 | Regression(wave="nhNhplPl", expected=['nclk', 'pclk', '111', '111', 'Nclk', 'pclk', '111', '111', '111', 'nclk',
57 | '000', '000', 'Pclk', 'nclk', '000', '000']),
58 | Regression(wave="xlh.L.Hx", expected=['xxx', 'xxx', 'nclk', '000', 'pclk', '111', '111', '111', 'Nclk', '000',
59 | '000', '000', 'Pclk', '111', '1mx', 'xxx'])
60 | ]
61 |
62 | period = [
63 | Regression(wave="h.nh.nh.l..hlx.hlx.hlx.hlx.hlx.hlx.hlx.hlx.hnh.l..hlx.hlx.h", period=0.5,
64 | expected=['111', '111', 'nclk', 'pclk', '111', '111', 'nclk', 'pclk', '111', '111', 'nclk', '000', '000',
65 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
66 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
67 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', 'pclk', '111',
68 | '111', 'nclk', '000', '000', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
69 | 'pclk']),
70 | Regression(wave="h.lh..lh..l..hlx.hlx.hlx.hlx.hlx.hlx.hlx.hlx.hnh.l..hlx.hlx.h", period=0.5,
71 | expected=['111', '111', 'nclk', 'pclk', '111', '111', 'nclk', 'pclk', '111', '111', 'nclk', '000', '000',
72 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
73 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
74 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', 'pclk', '111',
75 | '111', 'nclk', '000', '000', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
76 | 'pclk']),
77 | Regression(wave="0..............................................50.............", period=0.5,
78 | expected=['000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000',
79 | '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000',
80 | '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000', '000',
81 | '000', '000', '000', '000', '000', '000', '000', '000', '0mv-5', 'vm0-5', '000', '000', '000',
82 | '000', '000', '000', '000', '000', '000', '000', '000', '000', '000']),
83 | ]
84 |
85 | subcycle = [
86 | Regression(wave="0<10>1", expected=['000', '000', '0m1', '1m0', '0m1', '111']),
87 | Regression(wave="<10>10", expected=['111', '1m0', '0m1', '111', '1m0', '000']),
88 | Regression(wave="1x", expected=['xxx', 'xm0', '0m1', '111', '1mx', 'xxx']),
89 | Regression(wave="<01>", expected=['000', '0m1']),
90 | Regression(wave="x.<01...0>x", expected=['xxx', 'xxx', 'xxx', 'xxx', 'xm0', '0m1', '111', '111', '111', '1m0',
91 | '0mx', 'xxx']),
92 | Regression(wave="==2<30>2<0xx1>2333444555",
93 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vmv-2-3', 'vm0-3', '0mv-2', 'vvv-2',
94 | 'vm0-2', '0mx', 'xmx', 'xm1', '1mv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vmv-3-3', 'vvv-3',
95 | 'vmv-3-3', 'vvv-3', 'vmv-3-4', 'vvv-4', 'vmv-4-4', 'vvv-4', 'vmv-4-4', 'vvv-4', 'vmv-4-5',
96 | 'vvv-5', 'vmv-5-5', 'vvv-5', 'vmv-5-5', 'vvv-5']),
97 | Regression(wave="=2<15.1>2<01>2355",
98 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vm1-2', '1mv-5', 'vvv-5', 'vm1-5', '1mv-2', 'vvv-2',
99 | 'vm0-2', '0m1', '1mv-2', 'vvv-2', 'vmv-2-3', 'vvv-3', 'vmv-3-5', 'vvv-5', 'vmv-5-5', 'vvv-5']),
100 | Regression(wave="=2<1001.1>2<01.5>3",
101 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vm1-2', '1m0', '0m0', '0m1', '111', '1m1', '1mv-2',
102 | 'vvv-2', 'vm0-2', '0m1', '111', '1mv-5', 'vmv-5-3', 'vvv-3']),
103 | Regression(wave="=2<10zuzd1x.1>2",
104 | expected=['vvv-2', 'vvv-2', 'vmv-2-2', 'vvv-2', 'vm1-2', '1m0', '0mz', 'zmu', 'umz', 'zmd', 'dm1', '1mx',
105 | 'xxx', 'xm1', '1mv-2', 'vvv-2']),
106 | Regression(wave="<=|>.x", expected=['vvv-2', 'vvv-2', 'vvv-2', 'vvv-2', 'vmx-2', 'xxx']),
107 | Regression(wave="x<=|>.x", expected=['xxx', 'xxx', 'xmv-2', 'vvv-2', 'vvv-2', 'vvv-2', 'vmx-2', 'xxx']),
108 | Regression(wave="x<.|>0x", expected=['xxx', 'xxx', 'xxx', 'xxx', 'xm0', '000', '0mx', 'xxx']),
109 | Regression(wave="hnhnhlnnhln",
110 | expected=['111', '111', 'nclk', 'pclk', '111', '111', 'nclk', 'pclk', '111', '111', 'nclk', '000', '000',
111 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
112 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
113 | 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', 'pclk', '111',
114 | '111', 'nclk', '000', '000', 'pclk', 'nclk', '0mx', 'xxx', 'pclk', 'nclk', '0mx', 'xxx',
115 | 'pclk']),
116 | Regression(wave="0.h<.>l.", expected=['000', '000', '000', '000', 'pclk', '111', '111', 'nclk', '000', '000', '000']),
117 | Regression(wave="0..l.", expected=['000', '000', '000', '000', 'pclk', '111', '111', 'nclk', '000', '000', '000']),
118 | Regression(wave="0.l.", expected=['000', '000', '000', '000', 'pclk', '111', '111', 'nclk', '000', '000', '000']),
119 | ]
120 |
121 | all = basic + period + subcycle
--------------------------------------------------------------------------------
/test/conftest.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | collect_ignore = ["single_test.py"]
4 | def pytest_report_header(config):
5 | if "WAVEDROMDIR" in os.environ:
6 | print("Using wavedrom in WAVEDROMDIR ({})".format(os.environ["WAVEDROMDIR"]))
--------------------------------------------------------------------------------
/test/diff.py:
--------------------------------------------------------------------------------
1 | import re
2 | from collections import namedtuple
3 |
4 | import xmldiff
5 | import xmldiff.main
6 | from lxml import etree
7 |
8 | from PIL import Image, ImageChops
9 | import cairosvg
10 | import io
11 |
12 | UpdateAttribx = namedtuple("UpdateAttribx", 'node name value old')
13 | MoveNodex = namedtuple("MoveNodex", 'node target position nodex targetx')
14 |
15 |
16 | def main(f_out, f_out_py):
17 | parser = etree.XMLParser(remove_blank_text=True)
18 | orig_tree = etree.parse(f_out, parser)
19 | py_tree = etree.parse(f_out_py, parser)
20 |
21 | diff = xmldiff.main.diff_trees(orig_tree, py_tree)
22 | unknown = []
23 |
24 | for action in diff:
25 | if isinstance(action, xmldiff.actions.UpdateAttrib):
26 | node = orig_tree.xpath(action.node)[0]
27 | if node.tag[-3:] == "svg" and action.name == "viewBox":
28 | # The viewBox format differs, both are legal notations (space vs. comma-separated)
29 | if re.sub(r"\s+", ",", node.attrib[action.name]) == action.value:
30 | continue
31 | elif re.sub(r"\s+", "", node.attrib[action.name]) == re.sub(r"\s+", "", action.value):
32 | # Whitespace differences are okay
33 | continue
34 | else:
35 | py = action.value
36 | js = node.attrib[action.name]
37 | if action.name in ["transform", "d", "x"]:
38 | # Floating point and int differences
39 | pattern_float = re.compile(r'(?3.5, remove once we are over 2.7..
48 | # action = UpdateAttribx(**{ **action._asdict(), "old": node.attrib[action.name]})
49 | action_dict = action._asdict()
50 | action_dict["old"] = node.attrib[action.name]
51 | action = UpdateAttribx(**action_dict)
52 | elif isinstance(action, xmldiff.actions.InsertAttrib):
53 | node = orig_tree.xpath(action.node)[0]
54 | if node.tag[-3:] == "svg" and action.name in ["baseProfile", "version"]:
55 | # svgwrite adds more info to the svg element
56 | continue
57 | elif isinstance(action, xmldiff.actions.MoveNode):
58 | node = orig_tree.xpath(action.node)[0]
59 | node.getparent().remove(node)
60 | target = orig_tree.xpath(action.target)[0]
61 | target.insert(action.position, node)
62 | if node.tag.endswith("}style"):
63 | # This is okay
64 | continue
65 | action_dict = action._asdict()
66 | action_dict["nodex"] = etree.tostring(node)
67 | action_dict["targetx"] = etree.tostring(target)
68 | action = MoveNodex(**action_dict)
69 | elif isinstance(action, xmldiff.actions.UpdateTextIn):
70 | node = orig_tree.xpath(action.node)[0]
71 | if action.text is None:
72 | if node.tag[-2:] == "}g" and node.attrib["id"] == "groups_0":
73 | # Upstream bug, reported: https://github.com/wavedrom/wavedrom/issues/251
74 | continue
75 | elif node.text is None:
76 | pass
77 | elif re.sub(r"\s+", "", node.text) == re.sub(r"\s+", "", action.text):
78 | # Whitespace differences are okay
79 | continue
80 | action = action._replace(node=etree.tostring(node))
81 | elif isinstance(action, xmldiff.actions.InsertNode):
82 | # Not okay, but we must do the same to preserve the valid tree for further checks
83 | target = orig_tree.xpath(action.target)[0]
84 | node = target.makeelement(action.tag)
85 | target.insert(action.position, node)
86 | action = action._replace(target=etree.tostring(orig_tree.xpath(action.target)[0]))
87 | elif isinstance(action, xmldiff.actions.DeleteNode):
88 | node = orig_tree.xpath(action.node)[0]
89 | node.getparent().remove(node)
90 | action = action._replace(node=etree.tostring(node))
91 |
92 | unknown.append(action)
93 | return unknown
94 |
95 | def diff_raster(f_out_js, f_out_py):
96 | with open(f_out_js, encoding="utf-8") as fileObj_svg_js:
97 | svg_js = fileObj_svg_js.read()
98 |
99 | with open(f_out_py, encoding="utf-8") as fileObj_svg_py:
100 | svg_py = fileObj_svg_py.read()
101 |
102 | png_js = cairosvg.svg2png(svg_js)
103 | png_py = cairosvg.svg2png(svg_py)
104 |
105 | image_js = Image.open(io.BytesIO(png_js))
106 | image_py = Image.open(io.BytesIO(png_py))
107 |
108 | return ImageChops.difference(image_js, image_py)
109 |
--------------------------------------------------------------------------------
/test/files/assign_74ls688.json:
--------------------------------------------------------------------------------
1 | { "assign":[
2 | ["z", ["~&",
3 | ["~^", ["~", "p0"], ["~", "q0"]],
4 | ["~^", ["~", "p1"], ["~", "q1"]],
5 | ["~^", ["~", "p2"], ["~", "q2"]],
6 | "...",
7 | ["~^", ["~", "p7"], ["~", "q7"]],
8 | ["~","~en"]
9 | ]]
10 | ]}
11 |
--------------------------------------------------------------------------------
/test/files/assign_binary2gray.json:
--------------------------------------------------------------------------------
1 | { "assign":[
2 | ["g0", ["^", "b0", "b1"]],
3 | ["g1", ["^", "b1", "b2"]],
4 | ["g2", ["^", "b2", "b3"]],
5 | ["g3", ["=", "b3"]]
6 | ]}
--------------------------------------------------------------------------------
/test/files/assign_gray2binary.json:
--------------------------------------------------------------------------------
1 | { "assign":[
2 | ["b3", "g3"],
3 | ["b2", ["^", "b3", "g2"]],
4 | ["b1", ["^", "b2", "g1"]],
5 | ["b0", ["^", "b1", "g0"]]
6 | ]}
7 |
--------------------------------------------------------------------------------
/test/files/assign_iec60617.json:
--------------------------------------------------------------------------------
1 | { "assign":[
2 | ["out",
3 | ["XNOR",
4 | ["NAND",
5 | ["INV", "a"],
6 | ["NOR", "b", ["BUF","c"]]
7 | ],
8 | ["AND",
9 | ["XOR", "d", "e", ["OR","f","g"]],
10 | "h"
11 | ]
12 | ]
13 | ]
14 | ]}
15 |
--------------------------------------------------------------------------------
/test/files/assign_xor.json:
--------------------------------------------------------------------------------
1 | { "assign":[
2 | ["out",
3 | ["|",
4 | ["&", ["~", "a"], "b"],
5 | ["&", ["~", "b"], "a"]
6 | ]
7 | ]
8 | ]}
--------------------------------------------------------------------------------
/test/files/bitfield_0.json:
--------------------------------------------------------------------------------
1 | {"reg": [
2 | { "name": "IPO", "bits": 8, "attr": "RO" },
3 | { "bits": 7 },
4 | { "name": "BRK", "bits": 5, "attr": "RW", "type": 4 },
5 | { "name": "CPK", "bits": 1, "attr": 0 },
6 | { "name": "Clear", "bits": 3, "attr": ["001", "110"]},
7 | { "bits": 8 }
8 | ]
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/test/files/issue_10.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "clk", "wave": "p..Pp..P"},
3 | ["Master",
4 | ["ctrl",
5 | {"name": "write", "wave": "01.0...."},
6 | {"name": "read", "wave": "0...1..0"}
7 | ],
8 | { "name": "addr", "wave": "x3.x4..x", "data": "A1 A2"},
9 | { "name": "wdata", "wave": "x3.x....", "data": "D1" }
10 | ],
11 | {},
12 | ["Slave",
13 | ["ctrl",
14 | {"name": "ack", "wave": "x01x0.1x"}
15 | ],
16 | { "name": "rdata", "wave": "x.....4x", "data": "Q2"}
17 | ]
18 | ]}
--------------------------------------------------------------------------------
/test/files/issue_11.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "0", "wave": "lh", "node": ".a", "phase":0},
3 | { "name": "0.1", "wave": "lh", "node": ".b", "phase":-0.1},
4 | { "name": "0.2", "wave": "lh", "node": ".c", "phase":-0.2},
5 | { "name": "0.3", "wave": "lh", "node": ".d", "phase":-0.3},
6 | { "name": "0.4", "wave": "lh", "node": ".e", "phase":-0.4},
7 | { "name": "0.5", "wave": "lh", "node": ".f", "phase":-0.5},
8 | { "name": "0.6", "wave": "lh", "node": ".g", "phase":-0.6},
9 | { "name": "0.7", "wave": "lh", "node": ".h", "phase":-0.7},
10 | { "name": "0.8", "wave": "lh", "node": ".i", "phase":-0.8},
11 | { "name": "0.9", "wave": "lh", "node": ".j", "phase":-0.9},
12 | { "name": "1", "wave": "lh", "node": ".k", "phase":-1}
13 | ],
14 | "edge": [
15 | "a-b",
16 | "b-c",
17 | "c-d",
18 | "d-e",
19 | "e-f",
20 | "f-g",
21 | "g-h",
22 | "h-i",
23 | "i-j",
24 | "j-k"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------
/test/files/issue_13.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "10", "wave": "lh===...........", "data": ["abcd", "1234"], "phase":-10}
3 | ]
4 | }
--------------------------------------------------------------------------------
/test/files/issue_14.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "trig", "wave": "lh......|.........", "node": ".t................"},
3 | { "name": "0", "wave": "l..====4|=0.......", "node": "...a..............", "data": [ "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]"], "phase":0},
4 | { "name": "1", "wave": "l..====4|=0.......", "node": "...b..............", "data": [ "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]"], "phase":-1},
5 | { "name": "2", "wave": "x.3====4|==30.....", "node": "...c..............", "data": ["SoT", "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]", "CRC", "EoT"], "phase":-1.5},
6 | { "name": "3", "wave": "x35====4|=5=30....", "node": "...d..............", "data": ["SoT", "PH", "b[0]", "b[1]", "b[2]", "b[3]", "...", "b[n]", "PF", "CRC", "EoT"], "phase":-5}
7 | ],
8 | "edge": [
9 | "t~>a",
10 | "a~>b",
11 | "b~>c",
12 | "c~>d"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/test/files/issue_16.json:
--------------------------------------------------------------------------------
1 | {
2 | "signal": [
3 | {
4 | "wave": "|nh....nh.nhn.xnhnh..|."
5 | },
6 | {
7 | "wave": "x=.....=..=..x|=.=...=.",
8 | "data": [
9 | "Calib.",
10 | "Stat./Comm.",
11 | "N data nibbles",
12 | "CRC",
13 | "Pause",
14 | "Calib."
15 | ]
16 | },
17 | {
18 | "node": "...................1.2."
19 | }
20 | ],
21 | "edge": [
22 | "1<->2 Interrupt"
23 | ],
24 | "head": {
25 | "text": [
26 | "tspan",
27 | [
28 | "tspan",
29 | {
30 | "font-weight": "bold"
31 | },
32 | "SENT message signal timing diagram"
33 | ]
34 | ]
35 | }
36 | }
--------------------------------------------------------------------------------
/test/files/issue_37.json:
--------------------------------------------------------------------------------
1 | {signal: [
2 | {name: 'e', wave: '0.30.30..', data: ['τ', 'τ+1']},
3 | {name: 'x', wave: 'x.=x.=x..', data: ['x₀', 'x₁']},
4 | {name: 'δₑx', wave: 'x..=..=..', data: ['x₀', 'x₁']},
5 | {name: 'δe', wave: '0..30.30.', data: ['τ', 'τ+1']},
6 | ]}
--------------------------------------------------------------------------------
/test/files/issue_7.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "CK", "wave": "P.......", "period": 2 },
3 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 },
4 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 },
5 | { "name": "DQS", "wave": "z.......0.1010z." },
6 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" }
7 | ],
8 | "head":{
9 | "text": "WaveDrom example"
10 | },
11 | "foot":{
12 | "text": "Figure 100"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/test/files/signal_0.json:
--------------------------------------------------------------------------------
1 | { "signal": [{ "name": "sig", "wave": "1" }] }
2 |
--------------------------------------------------------------------------------
/test/files/subcycle_0.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | {
3 | "name": "P1",
4 | "wave": "==2<30>2<0xx1>2333444555",
5 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8"
6 | },
7 | {
8 | "name": "P2",
9 | "wave": "=2<15.1>2<01>2355",
10 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8",
11 | "period": 2
12 | },
13 | {
14 | "name": "P3",
15 | "wave": "=2<1001.1>2<01.5>3",
16 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8",
17 | "period": 3
18 | },
19 | {
20 | "name": "P4",
21 | "wave": "=2<10zuzd1x.1>2",
22 | "data": "0 1 2 a b c 3 4 5 d e f 6 7 8",
23 | "period": 4
24 | }
25 | ]}
26 |
--------------------------------------------------------------------------------
/test/files/subcycle_1.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "clk", "wave": "p....."},
3 | { "name": "A1", "wave": "x=x|=x|=.", "data": "a1 b1" },
4 | { "name": "A2", "wave": "x=x|=x|=.", "data": "a2 b2", "period": 2 },
5 | { "name": "A3", "wave": "x=x|=x|=.", "data": "a3 b3", "period": 3 },
6 | { "name": "A4", "wave": "x=x|=x|=.", "data": "a4 b4", "period": 4 },
7 | { "name": "B1", "wave": "x===.", "data": "a1 b1" },
8 | { "name": "B2", "wave": "x===.", "data": "a2 b2", "period": 2 },
9 | { "name": "B3", "wave": "x===.", "data": "a3 b3", "period": 3 },
10 | { "name": "B4", "wave": "x===.", "data": "a4 b4", "period": 4 }
11 | ]}
12 |
--------------------------------------------------------------------------------
/test/files/tutorial_0.json:
--------------------------------------------------------------------------------
1 | { "signal": [{ "name": "Alfa", "wave": "01.zx=ud.23.45" }] }
--------------------------------------------------------------------------------
/test/files/tutorial_0n.json:
--------------------------------------------------------------------------------
1 | {
2 | "signal": [{ "name": "Alfa", "wave": "01.zx=ud.23.45" }],
3 | "config": { "skin": "narrow" }
4 | }
5 |
--------------------------------------------------------------------------------
/test/files/tutorial_1.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "pclk", "wave": "p......." },
3 | { "name": "Pclk", "wave": "P......." },
4 | { "name": "nclk", "wave": "n......." },
5 | { "name": "Nclk", "wave": "N......." },
6 | {},
7 | { "name": "clk0", "wave": "phnlPHNL" },
8 | { "name": "clk1", "wave": "xhlhLHl." },
9 | { "name": "clk2", "wave": "hpHplnLn" },
10 | { "name": "clk3", "wave": "nhNhplPl" },
11 | { "name": "clk4", "wave": "xlh.L.Hx" }
12 | ]}
--------------------------------------------------------------------------------
/test/files/tutorial_10.json:
--------------------------------------------------------------------------------
1 | {"signal": [
2 | {"name":"clk", "wave": "p.....PPPPp...." },
3 | {"name":"dat", "wave": "x....2345x.....", "data": "a b c d" },
4 | {"name":"req", "wave": "0....1...0....." }
5 | ],
6 | "head": {"text":
7 | ["tspan",
8 | ["tspan", {"class":"error h1"}, "error "],
9 | ["tspan", {"class":"warning h2"}, "warning "],
10 | ["tspan", {"class":"info h3"}, "info "],
11 | ["tspan", {"class":"success h4"}, "success "],
12 | ["tspan", {"class":"muted h5"}, "muted "],
13 | ["tspan", {"class":"h6"}, "h6 "],
14 | "default ",
15 | ["tspan", {"fill":"pink", "font-weight":"bold", "font-style":"italic"}, "pink-bold-italic"]
16 | ]
17 | },
18 | "foot": {"text":
19 | ["tspan", "E=mc",
20 | ["tspan", {"dy":"-5"}, "2"],
21 | ["tspan", {"dy": "5"}, ". "],
22 | ["tspan", {"font-size":"25"}, "B "],
23 | ["tspan", {"text-decoration":"overline"},"over "],
24 | ["tspan", {"text-decoration":"underline"},"under "],
25 | ["tspan", {"baseline-shift":"sub"}, "sub "],
26 | ["tspan", {"baseline-shift":"super"}, "super "]
27 | ],"tock":-5
28 | }
29 | }
--------------------------------------------------------------------------------
/test/files/tutorial_11.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "A", "wave": "01........0....", "node": ".a........j" },
3 | { "name": "B", "wave": "0.1.......0.1..", "node": "..b.......i" },
4 | { "name": "C", "wave": "0..1....0...1..", "node": "...c....h.." },
5 | { "name": "D", "wave": "0...1..0.....1.", "node": "....d..g..." },
6 | { "name": "E", "wave": "0....10.......1", "node": ".....ef...." }
7 | ],
8 | "edge": [
9 | "a~b t1", "c-~a t2", "c-~>d time 3", "d~-e",
10 | "e~>f", "f->g", "g-~>h", "h~>i some text", "h~->j"
11 | ]
12 | }
--------------------------------------------------------------------------------
/test/files/tutorial_12.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "A", "wave": "01..0..", "node": ".a..e.." },
3 | { "name": "B", "wave": "0.1..0.", "node": "..b..d.", "phase":0.5 },
4 | { "name": "C", "wave": "0..1..0", "node": "...c..f" },
5 | { "node": "...g..h" }
6 | ],
7 | "edge": [
8 | "b-|a t1", "a-|c t2", "b-|-c t3", "c-|->e t4", "e-|>f more text",
9 | "e|->d t6", "c-g", "f-h", "g<->h 3 ms"
10 | ]
11 | }
--------------------------------------------------------------------------------
/test/files/tutorial_1n.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "pclk", "wave": "p......." },
3 | { "name": "Pclk", "wave": "P......." },
4 | { "name": "nclk", "wave": "n......." },
5 | { "name": "Nclk", "wave": "N......." },
6 | {},
7 | { "name": "clk0", "wave": "phnlPHNL" },
8 | { "name": "clk1", "wave": "xhlhLHl." },
9 | { "name": "clk2", "wave": "hpHplnLn" },
10 | { "name": "clk3", "wave": "nhNhplPl" },
11 | { "name": "clk4", "wave": "xlh.L.Hx" }
12 | ],
13 | "config": { "skin": "narrow" }
14 | }
--------------------------------------------------------------------------------
/test/files/tutorial_2.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "clk", "wave": "P......" },
3 | { "name": "bus", "wave": "x.==.=x", "data": ["head", "body", "tail", "data"] },
4 | { "name": "wire", "wave": "0.1..0." }
5 | ]}
--------------------------------------------------------------------------------
/test/files/tutorial_3.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "clk", "wave": "p.....|..." },
3 | { "name": "data", "wave": "x.345x|=.x", "data": ["head", "body", "tail", "data"] },
4 | { "name": "Request", "wave": "0.1..0|1.0" },
5 | {},
6 | { "name": "Acknowledge", "wave": "1.....|01." }
7 | ]}
--------------------------------------------------------------------------------
/test/files/tutorial_4.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "clk", "wave": "p..Pp..P"},
3 | ["Master",
4 | ["ctrl",
5 | {"name": "write", "wave": "01.0...."},
6 | {"name": "read", "wave": "0...1..0"}
7 | ],
8 | { "name": "addr", "wave": "x3.x4..x", "data": "A1 A2"},
9 | { "name": "wdata", "wave": "x3.x....", "data": "D1" }
10 | ],
11 | {},
12 | ["Slave",
13 | ["ctrl",
14 | {"name": "ack", "wave": "x01x0.1x"}
15 | ],
16 | { "name": "rdata", "wave": "x.....4x", "data": "Q2"}
17 | ]
18 | ]}
--------------------------------------------------------------------------------
/test/files/tutorial_5.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "CK", "wave": "P.......", "period": 2 },
3 | { "name": "CMD", "wave": "x.3x=x4x=x=x=x=x", "data": "RAS NOP CAS NOP NOP NOP NOP", "phase": 0.5 },
4 | { "name": "ADDR", "wave": "x.=x..=x........", "data": "ROW COL", "phase": 0.5 },
5 | { "name": "DQS", "wave": "z.......0.1010z." },
6 | { "name": "DQ", "wave": "z.........5555z.", "data": "D0 D1 D2 D3" }
7 | ]}
--------------------------------------------------------------------------------
/test/files/tutorial_6.json:
--------------------------------------------------------------------------------
1 | { "signal": [
2 | { "name": "clk", "wave": "p...." },
3 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] },
4 | { "name": "Request", "wave": "01..0" }
5 | ],
6 | "config": { "hscale": 1 }
7 | }
--------------------------------------------------------------------------------
/test/files/tutorial_7.json:
--------------------------------------------------------------------------------
1 | { "signal" : [
2 | { "name": "clk", "wave": "p...." },
3 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] },
4 | { "name": "Request", "wave": "01..0" }
5 | ],
6 | "config" : { "hscale" : 2 }
7 | }
--------------------------------------------------------------------------------
/test/files/tutorial_8.json:
--------------------------------------------------------------------------------
1 | { "signal" : [
2 | { "name": "clk", "wave": "p...." },
3 | { "name": "data", "wave": "x345x", "data": ["head", "body", "tail"] },
4 | { "name": "Request", "wave": "01..0" }
5 | ],
6 | "config" : { "hscale" : 3 }
7 | }
--------------------------------------------------------------------------------
/test/files/tutorial_9.json:
--------------------------------------------------------------------------------
1 | {"signal": [
2 | {"name":"clk", "wave": "p...." },
3 | {"name":"data", "wave": "x345x", "data": "a b c" },
4 | {"name":"Request", "wave": "01..0" }
5 | ],
6 | "head":{
7 | "text":"WaveDrom example",
8 | "tick":0
9 | },
10 | "foot":{
11 | "text":"Figure 100",
12 | "tock":9
13 | }
14 | }
--------------------------------------------------------------------------------
/test/single_test.py:
--------------------------------------------------------------------------------
1 | #%% [markdown]
2 | # The current working directory must be in
3 | # the `test` folder. Even though this file resides in the `test` folder, VSCode starts the kernel in the "opened" folder by default, which is the root of the GitHub project.
4 | #%%
5 | import os
6 |
7 | if not os.getcwd()[-4:] == 'test':
8 | os.chdir('./test')
9 |
10 | #%% [markdown]
11 | # # Test Case Selection and Configuration
12 | # Change `file` to the .json that should be rendered and tested
13 | #%%
14 | #The json file to render with `wavedrompy` and `wavedrom`, then compare the results
15 | file = './files/issue_14.json'
16 |
17 | #The directory where the SVG and PNG files will be saved
18 | tmpdir = './tmp'
19 | wavedromdir = './tmp/wavedrom'
20 |
21 | #%% from `test_render.py`
22 | import subprocess
23 | from os.path import splitext, basename
24 |
25 | import wavedrom
26 | from diff import diff_raster
27 | from diff import main as diff
28 |
29 | #%% [markdown]
30 | # # Upstream Setup
31 | # Uncomment these two lines if the environment needs to have wavedrom downloaded and installed
32 | #%% from `test_render.py`, `wavedromdir(tmpdir_factory)`
33 |
34 | #subprocess.check_call("git clone https://github.com/wavedrom/wavedrom.git {}".format(wavedromdir), shell=True)
35 | #subprocess.check_call("npm install", cwd=str(wavedromdir), shell=True)
36 |
37 | #%% [markdown]
38 | # # Generate SVGs, Determine XML Differences, and Calculate Raster Difference
39 | #%% from `test_render.py`, `test_upstream(tmpdir,wavedromdir,file)`
40 | from IPython.display import SVG, display
41 |
42 | test_name = splitext(basename(file))[0]
43 | f_out_js = "{}/{}_js.svg".format(tmpdir, test_name)
44 | f_out_py = "{}/{}_py.svg".format(tmpdir, test_name)
45 |
46 | subprocess.check_call("node {}/bin/cli.js -i {} > {}".format(wavedromdir, file, f_out_js), shell=True)
47 | wavedrom.render_file(file, f_out_py, strict_js_features=True)
48 |
49 | display('wavedrom:')
50 | display(SVG(f_out_js))
51 | display('wavedrompy:')
52 | display(SVG(f_out_py))
53 |
54 | unknown = diff(f_out_js, f_out_py)
55 |
56 | if len(unknown) > 0:
57 | msg = "{} mismatch(es)\n".format(len(unknown))
58 | msg += "js file: {}\npy file: {}\n".format(f_out_js, f_out_py)
59 | msg += "\n".join([str(action) for action in unknown])
60 | #pytest.fail(msg)
61 | display(msg)
62 |
63 | raster_difference = diff_raster(f_out_js, f_out_py)
64 |
65 | #%% [markdown]
66 | # # Analyze Raster Difference
67 | # This cell does some analysis on the difference between the two images generarted by rasterizing the SVG output by *wavedrom* and *wavedrompy*. The cell saves several channel-specific differences to images so the differences can be visualized.
68 | #
69 | # The process should work with differences in all four channels of an RGBA image. It will not work with L (grayscale) images. This has only been tested with RGBA images that have differences in either:
70 | # - The RGB channels only
71 | # - The alpha channel only
72 | #%%
73 | from IPython.display import display
74 | from PIL import Image, ImageChops, ImageOps
75 |
76 | differentBands = '' #To be appened with bands that have at least one different pixel
77 |
78 | if raster_difference.getbbox() is None: #The images are identical
79 | print('Wavedrom and Wavedrompy rendered to indentical PNG images')
80 | else:
81 | #Check which individual bands are different, and add bands with differences to `differentBands`
82 | for bandName, band in zip(raster_difference.getbands(), raster_difference.split()):
83 | if band.getbbox() is not None:
84 | differentBands += bandName
85 | print('Difference in ' + bandName + ' channel')
86 |
87 | #Display differences in color bands, ignoring alpha
88 | if 'R' in differentBands or 'G' in differentBands or 'B' in differentBands:
89 | print('Difference of RGB:')
90 | noAlpha = Image.merge('RGB', raster_difference.split()[0:3])
91 | noAlphaEnhanced = ImageOps.autocontrast(noAlpha)
92 | display(noAlphaEnhanced)
93 | noAlphaEnhanced.save('./tmp/' + test_name + '_noAlphaDiff.png')
94 |
95 | #Display differences in alpha band as grayscale image
96 | if 'A' in differentBands:
97 | print('Difference of alpha:')
98 | alphaOnly = raster_difference.split()[-1]
99 | alphaOnlyEnhanced = ImageOps.autocontrast(alphaOnly)
100 | display(alphaOnlyEnhanced)
101 | alphaOnlyEnhanced.save('./tmp/' + test_name + '_alphaOnlyDiff.png')
102 |
103 | #%% Compose the original and difference images
104 | import cairosvg, io
105 |
106 | noAlphaCopy = noAlphaEnhanced.copy()
107 |
108 | with open(f_out_js, encoding="utf-8") as fileObj_svg_js:
109 | svg_js = fileObj_svg_js.read()
110 |
111 | png_js = cairosvg.svg2png(svg_js)
112 | orig = Image.open(io.BytesIO(png_js))
113 | origCopy = orig.copy()
114 |
115 | #Compose the two images,
116 | noAlphaCopy.putalpha(255)
117 | origCopy.putalpha(64)
118 | noAlphaCopy.alpha_composite(origCopy)
119 |
120 | #Use original transparency from wavedrom image
121 | noAlphaCopy.putalpha(orig.split()[-1])
122 |
123 | display(noAlphaCopy)
124 | noAlphaCopy.save('./tmp/' + test_name + '_differenceAndOriginalComposed.png')
125 |
--------------------------------------------------------------------------------
/test/test_brick_regression.py:
--------------------------------------------------------------------------------
1 | import pytest
2 |
3 | from wavedrom import WaveDrom
4 | from brick_regressions import all
5 |
6 |
7 | def pytest_generate_tests(metafunc):
8 | metafunc.parametrize("test", all)
9 |
10 |
11 | def test_regression(test):
12 | w = WaveDrom()
13 | w.lane.period = test.period
14 | w.lane.hscale = test.hscale
15 | w.lane.phase = test.phase
16 | output = w.parse_wave_lane(test.wave, test.period * test.hscale - 1)
17 | assert(output == test.expected)
--------------------------------------------------------------------------------
/test/test_render.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 | import sys
4 | from glob import glob
5 | from os.path import splitext, basename
6 |
7 | import wavedrom
8 | import pytest
9 | from diff import diff_raster
10 | from diff import main as diff
11 |
12 | files_basic = glob("test/files/signal_*.json")
13 | files_subcycle = glob("test/files/subcycle_*.json")
14 | files_assign = glob("test/files/assign_*.json")
15 | files_bitfield = glob("test/files/bitfield_*.json")
16 | files_tutorial = glob("test/files/tutorial_*.json")
17 | files_issues = glob("test/files/issue_*.json")
18 |
19 | files = files_basic + files_tutorial + files_issues
20 |
21 |
22 | def pytest_generate_tests(metafunc):
23 | metafunc.parametrize("file", files)
24 |
25 |
26 | def test_render(file):
27 | jinput = open(file).read()
28 | wavedrom.render(jinput)
29 |
30 |
31 | @pytest.fixture(scope="session")
32 | def wavedromdir(tmpdir_factory):
33 | if "WAVEDROMDIR" in os.environ:
34 | return os.environ["WAVEDROMDIR"]
35 | else:
36 | wavedromdir = tmpdir_factory.mktemp("wavedrom")
37 | subprocess.check_call("git clone https://github.com/wavedrom/wavedrom.git {}".format(wavedromdir), shell=True)
38 | subprocess.check_call("git reset --hard 1d4d25181d6660b5d069defcf04583158b51aa5c~1", cwd=str(wavedromdir), shell=True)
39 | subprocess.check_call("npm install", cwd=str(wavedromdir), shell=True)
40 | return wavedromdir
41 |
42 |
43 | @pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
44 | def test_upstream(tmpdir,wavedromdir,file):
45 | base = splitext(basename(file))[0]
46 | f_out = "{}/{}.svg".format(tmpdir, base)
47 | f_out_py = "{}/{}_py.svg".format(tmpdir, base)
48 |
49 | subprocess.check_call("node {}/bin/cli.js -i {} > {}".format(wavedromdir, file, f_out), shell=True)
50 | wavedrom.render_file(file, f_out_py, strict_js_features=True)
51 |
52 | unknown = diff(f_out, f_out_py)
53 |
54 | if len(unknown) > 0:
55 | msg = "{} mismatch(es)\n".format(len(unknown))
56 | msg += "js file: {}\npy file: {}\n".format(f_out, f_out_py)
57 | msg += "\n".join([str(action) for action in unknown])
58 | pytest.fail(msg)
59 |
60 | img = diff_raster(f_out, f_out_py)
61 |
62 | if img.getbbox() is not None:
63 | pytest.fail("Raster image comparison failed for " + file)
64 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py37, py38, py39, p310
3 |
4 | [testenv]
5 | deps = pytest
6 | extras = test
7 |
8 | commands =
9 | pytest -v {posargs}
10 |
--------------------------------------------------------------------------------
/wavedrom/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright wavedrompy contributors.
2 | # SPDX-License-Identifier: MIT
3 |
4 |
5 | import argparse
6 | import json
7 | import yaml
8 | import sys
9 |
10 | from .waveform import WaveDrom
11 | from .assign import Assign
12 | from .version import version
13 | from .bitfield import BitField
14 |
15 | import json
16 |
17 |
18 | def fixQuotes(inputString):
19 | # fix double quotes in the input file. opening with yaml and dumping with json fix the issues.
20 | yamlCode = yaml.load(inputString, Loader=yaml.FullLoader)
21 | fixedString = json.dumps(yamlCode, indent=4)
22 | return fixedString
23 |
24 |
25 | def render(source="", output=[], strict_js_features=False):
26 | source = json.loads(fixQuotes(source))
27 | if source.get("signal"):
28 | return WaveDrom().render_waveform(0, source, output, strict_js_features)
29 | elif source.get("assign"):
30 | return Assign().render(0, source, output)
31 | elif source.get("reg"):
32 | return BitField().renderJson(source)
33 |
34 |
35 | def render_write(source, output, strict_js_features=False):
36 | jinput = source.read()
37 | out = render(jinput, strict_js_features=strict_js_features)
38 | out.write(output)
39 |
40 |
41 | def render_file(source, output, strict_js_features=False):
42 | out = open(output, "w")
43 | render_write(open(source, "r"), out, strict_js_features=strict_js_features)
44 | out.close()
45 |
46 |
47 | def main():
48 | parser = argparse.ArgumentParser(description="")
49 | parser.add_argument(
50 | "--input",
51 | "-i",
52 | help="",
53 | required=True,
54 | type=argparse.FileType("r"),
55 | )
56 | parser.add_argument(
57 | "--svg",
58 | "-s",
59 | help="