quickdbd-cli 0.1.12__tar.gz → 0.1.14__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.
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/NOTICE +2 -1
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/PKG-INFO +6 -2
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/README.md +5 -1
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/pyproject.toml +1 -1
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/build_gallery.py +2 -2
- quickdbd_cli-0.1.14/src/quickdbd_cli/__init__.py +1 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/style.css +78 -59
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/viewer.js +1 -1
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/cli.py +31 -1
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/html.py +27 -6
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_gallery.py +5 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_pipeline.py +41 -0
- quickdbd_cli-0.1.12/src/quickdbd_cli/__init__.py +0 -1
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/.gitignore +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/LICENSE +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/quickdbd-sample.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/finance.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/hr.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/inventory.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/marketing.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/qdbd.layout.json +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/examples/synthetic/sales.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/gen_font_metrics.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/gen_schema.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/gen_thumbnails.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/scripts/release.sh +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/__main__.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/editor.js +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/font-metrics.json +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/assets/render.js +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/dbt.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/diff.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/export.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/jsrt.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/layout.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/measure.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/model.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/parser.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/project.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/serve.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/share.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/src/quickdbd_cli/tags.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/LICENSE +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/README.md +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/catalog.json +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/fixtures/jaffle/manifest.json +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_dbt.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_diff.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_parser.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_path.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_select.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_serve.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_share.py +0 -0
- {quickdbd_cli-0.1.12 → quickdbd_cli-0.1.14}/tests/test_tags.py +0 -0
|
@@ -16,7 +16,8 @@ This product bundles third-party components, distributed unmodified:
|
|
|
16
16
|
|
|
17
17
|
The demo gallery (gallery/, not part of the Python package) includes schemas derived from:
|
|
18
18
|
|
|
19
|
-
* Northwind and AdventureWorks (gallery/northwind, gallery/
|
|
19
|
+
* Northwind, pubs, AdventureWorks and AdventureWorks DW (gallery/northwind, gallery/pubs,
|
|
20
|
+
gallery/adventureworks, gallery/adventureworks-dw), converted from
|
|
20
21
|
microsoft/sql-server-samples. Copyright (c) Microsoft Corporation. MIT License.
|
|
21
22
|
* Sakila (gallery/sakila), converted from sakila-schema.sql. Copyright (c) 2006, 2026, Oracle and/or
|
|
22
23
|
its affiliates. BSD-3-Clause.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quickdbd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14
|
|
4
4
|
Summary: Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit.
|
|
5
5
|
Project-URL: Homepage, https://github.com/MarkPhamm/quickdbd-cli
|
|
6
6
|
Author: Minh (Mark) Pham
|
|
@@ -77,7 +77,7 @@ Every demo is a single HTML page written by `qdbd build`, from a QuickDBD schema
|
|
|
77
77
|
| [](https://qdbd.dev/demo/synthetic/)<br>**[200 tables](https://qdbd.dev/demo/synthetic/)**<br>Five business domains, 200 tables and every relationship type. Search, filter, click a table to focus it. | [](https://qdbd.dev/demo/diff/)<br>**[Schema diff](https://qdbd.dev/demo/diff/)**<br>What a branch changed, coloured in: new tables in green, changed columns in amber, removals listed. |
|
|
78
78
|
| [](https://qdbd.dev/demo/adventureworks/)<br>**[AdventureWorks](https://qdbd.dev/demo/adventureworks/)**<br>Microsoft's bicycle-company OLTP database: 68 tables in five schemas, one region each, with column descriptions. | [](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. |
|
|
79
79
|
|
|
80
|
-
[View all
|
|
80
|
+
[View all 18 demos →](https://qdbd.dev/demo/)
|
|
81
81
|
<!-- gallery:end -->
|
|
82
82
|
|
|
83
83
|
## Quickstart
|
|
@@ -244,6 +244,10 @@ You edit the text; the diagram is generated from it. Table positions are the one
|
|
|
244
244
|
- [Schema Diffs](https://qdbd.dev/diffs/), the [GitHub Action](https://qdbd.dev/github-action/) and the [CLI reference](https://qdbd.dev/cli/)
|
|
245
245
|
- [Development](https://qdbd.dev/development/): working on qdbd itself, releasing, and adding a demo
|
|
246
246
|
|
|
247
|
+
## About the author
|
|
248
|
+
|
|
249
|
+
qdbd is built and maintained by **Minh (Mark) Pham** ([@MarkPhamm](https://github.com/MarkPhamm)). Issues, ideas and pull requests are welcome on [GitHub](https://github.com/MarkPhamm/quickdbd-cli/issues).
|
|
250
|
+
|
|
247
251
|
## License
|
|
248
252
|
|
|
249
253
|
Apache-2.0. Bundled third-party files (ELK, Inter) and the sources of the demo schemas are listed in [NOTICE](https://github.com/MarkPhamm/quickdbd-cli/blob/main/NOTICE).
|
|
@@ -49,7 +49,7 @@ Every demo is a single HTML page written by `qdbd build`, from a QuickDBD schema
|
|
|
49
49
|
| [](https://qdbd.dev/demo/synthetic/)<br>**[200 tables](https://qdbd.dev/demo/synthetic/)**<br>Five business domains, 200 tables and every relationship type. Search, filter, click a table to focus it. | [](https://qdbd.dev/demo/diff/)<br>**[Schema diff](https://qdbd.dev/demo/diff/)**<br>What a branch changed, coloured in: new tables in green, changed columns in amber, removals listed. |
|
|
50
50
|
| [](https://qdbd.dev/demo/adventureworks/)<br>**[AdventureWorks](https://qdbd.dev/demo/adventureworks/)**<br>Microsoft's bicycle-company OLTP database: 68 tables in five schemas, one region each, with column descriptions. | [](https://qdbd.dev/demo/synthea/#t=concept)<br>**[OMOP CDM on Synthea](https://qdbd.dev/demo/synthea/#t=concept)**<br>OHDSI's dbt-synthea: synthetic patient records mapped to the OMOP Common Data Model. 145 tables, 235 relationships from tests. |
|
|
51
51
|
|
|
52
|
-
[View all
|
|
52
|
+
[View all 18 demos →](https://qdbd.dev/demo/)
|
|
53
53
|
<!-- gallery:end -->
|
|
54
54
|
|
|
55
55
|
## Quickstart
|
|
@@ -216,6 +216,10 @@ You edit the text; the diagram is generated from it. Table positions are the one
|
|
|
216
216
|
- [Schema Diffs](https://qdbd.dev/diffs/), the [GitHub Action](https://qdbd.dev/github-action/) and the [CLI reference](https://qdbd.dev/cli/)
|
|
217
217
|
- [Development](https://qdbd.dev/development/): working on qdbd itself, releasing, and adding a demo
|
|
218
218
|
|
|
219
|
+
## About the author
|
|
220
|
+
|
|
221
|
+
qdbd is built and maintained by **Minh (Mark) Pham** ([@MarkPhamm](https://github.com/MarkPhamm)). Issues, ideas and pull requests are welcome on [GitHub](https://github.com/MarkPhamm/quickdbd-cli/issues).
|
|
222
|
+
|
|
219
223
|
## License
|
|
220
224
|
|
|
221
225
|
Apache-2.0. Bundled third-party files (ELK, Inter) and the sources of the demo schemas are listed in [NOTICE](https://github.com/MarkPhamm/quickdbd-cli/blob/main/NOTICE).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "quickdbd-cli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.14"
|
|
8
8
|
description = "Compile QuickDBD-syntax schema files into interactive HTML ER diagrams, with no table limit."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -37,8 +37,8 @@ KINDS = ("source", "dbt", "of")
|
|
|
37
37
|
|
|
38
38
|
# Added to every demo page: a way back to the gallery, as the first item of the viewer's toolbar.
|
|
39
39
|
BACK_LINK = (
|
|
40
|
-
'<a href="../" style="color:var(--blue);
|
|
41
|
-
'font-weight:500
|
|
40
|
+
'<a href="../" style="color:var(--blue);padding:5px 9px;border-radius:6px;text-decoration:none;'
|
|
41
|
+
'font-weight:500">← All demos</a><div class="sep"></div>'
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.14"
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
--bg: #
|
|
2
|
+
--bg: #f5f6f7;
|
|
3
3
|
--blue: #2b5797;
|
|
4
|
-
--ink: #
|
|
5
|
-
--
|
|
4
|
+
--ink: #15181c;
|
|
5
|
+
--ink-2: #4a515b;
|
|
6
|
+
--muted: #6b7280;
|
|
6
7
|
--panel: #ffffff;
|
|
7
|
-
--line: #
|
|
8
|
+
--line: #e4e7eb;
|
|
9
|
+
--hover: #f1f3f5;
|
|
10
|
+
--sel: #eaf0f8;
|
|
11
|
+
--amber: #f0a020;
|
|
12
|
+
--shadow: 0 1px 2px rgba(16, 24, 40, .05), 0 8px 24px rgba(16, 24, 40, .07);
|
|
8
13
|
}
|
|
14
|
+
::selection { background: #fde3b0; color: var(--ink); }
|
|
15
|
+
:focus-visible { outline: 2px solid var(--amber); outline-offset: 1px; }
|
|
16
|
+
* { scrollbar-width: thin; scrollbar-color: #cfd4da transparent; }
|
|
9
17
|
* { box-sizing: border-box; }
|
|
10
18
|
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
11
19
|
font-family: Inter, "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 13px; overflow: hidden; }
|
|
@@ -26,32 +34,36 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
26
34
|
#canvas.panning .t { cursor: grabbing; }
|
|
27
35
|
|
|
28
36
|
/* toolbar (top right, like QuickDBD's zoom controls) */
|
|
29
|
-
.bar { position: absolute; top: 14px; right: 16px; display: flex; gap:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.bar button.
|
|
34
|
-
.
|
|
35
|
-
.
|
|
36
|
-
.seg
|
|
37
|
-
.
|
|
37
|
+
.bar { position: absolute; top: 14px; right: 16px; display: flex; gap: 2px; align-items: center; padding: 4px;
|
|
38
|
+
background: var(--panel); border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); z-index: 4; }
|
|
39
|
+
.bar button, .seg button { border: 0; background: transparent; color: var(--ink-2); font: inherit; font-weight: 500;
|
|
40
|
+
cursor: pointer; padding: 5px 9px; border-radius: 6px; transition: background .12s, color .12s; }
|
|
41
|
+
.bar button:hover, .seg button:hover { background: var(--hover); color: var(--ink); }
|
|
42
|
+
.bar button.on { background: var(--ink); color: #fff; }
|
|
43
|
+
.zoomval { min-width: 46px; text-align: center; color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
44
|
+
.seg { display: flex; background: var(--hover); border-radius: 7px; padding: 2px; }
|
|
45
|
+
.seg button.on { background: var(--panel); color: var(--ink); box-shadow: 0 1px 2px rgba(16, 24, 40, .12); }
|
|
46
|
+
.sep { width: 1px; height: 18px; background: var(--line); margin: 0 4px; }
|
|
47
|
+
.bar button svg, .exhead button svg, .details .close svg { display: block; }
|
|
48
|
+
#zin, #zout, #zfit { padding: 5px 6px; }
|
|
38
49
|
|
|
39
50
|
/* explorer (left): search + table tree grouped by file/folder */
|
|
40
51
|
.explorer { position: absolute; top: 14px; left: 16px; width: 270px; max-height: calc(100% - 60px); display: flex;
|
|
41
|
-
flex-direction: column; background:
|
|
42
|
-
box-shadow:
|
|
52
|
+
flex-direction: column; background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
|
|
53
|
+
box-shadow: var(--shadow); z-index: 3; }
|
|
43
54
|
.exhead { display: flex; align-items: center; gap: 4px; padding: 6px; border-bottom: 1px solid var(--line); }
|
|
44
55
|
.explorer.collapsed .exhead { border-bottom: 0; }
|
|
45
|
-
.exhead button { border: 0; background: transparent; cursor: pointer; font-size: 15px; color:
|
|
46
|
-
.exhead button:hover { background:
|
|
47
|
-
.exhead input { flex: 1; min-width: 0; padding: 6px
|
|
48
|
-
background:
|
|
49
|
-
.exhead input
|
|
56
|
+
.exhead button { border: 0; background: transparent; cursor: pointer; font-size: 15px; color: var(--muted); padding: 4px 7px; border-radius: 6px; }
|
|
57
|
+
.exhead button:hover { background: var(--hover); color: var(--ink); }
|
|
58
|
+
.exhead input { flex: 1; min-width: 0; padding: 6px 9px; border: 1px solid transparent; border-radius: 7px; font: inherit;
|
|
59
|
+
background: var(--hover); color: var(--ink); outline: none; }
|
|
60
|
+
.exhead input::placeholder, .exfilter input::placeholder, .details .pathbox input::placeholder { color: var(--muted); }
|
|
61
|
+
.exhead input:focus { border-color: var(--ink-2); background: var(--panel); }
|
|
50
62
|
.exfilter { padding: 6px 6px 4px; border-bottom: 1px solid var(--line); }
|
|
51
63
|
.explorer.collapsed .exfilter { display: none; }
|
|
52
|
-
.exfilter input { width: 100%; padding: 5px 8px; border: 1px solid var(--line); border-radius:
|
|
53
|
-
outline: none; }
|
|
54
|
-
.exfilter input:focus { border-color: var(--
|
|
64
|
+
.exfilter input { width: 100%; padding: 5px 8px; border: 1px solid var(--line); border-radius: 7px; font: 12px ui-monospace, Menlo, Consolas, monospace;
|
|
65
|
+
color: var(--ink); outline: none; }
|
|
66
|
+
.exfilter input:focus { border-color: var(--ink-2); }
|
|
55
67
|
.exfilter input.bad { border-color: #e57373; }
|
|
56
68
|
.fstat { font-size: 11px; color: var(--muted); padding: 3px 2px 0; min-height: 14px; }
|
|
57
69
|
.fstat.bad { color: #c0392b; }
|
|
@@ -62,50 +74,55 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
62
74
|
.tchip i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
|
|
63
75
|
.tchip small { color: var(--muted); }
|
|
64
76
|
.tchip:hover { border-color: #b8c2d0; }
|
|
65
|
-
.tchip.on { border-color: var(--
|
|
77
|
+
.tchip.on { border-color: var(--ink-2); background: var(--sel); }
|
|
66
78
|
.tdot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin: 0 6px 1px -13px; vertical-align: middle; }
|
|
67
79
|
.tree.flat .tdot { margin-left: 0; }
|
|
68
80
|
.details .ttags { margin: -4px 0 10px; display: flex; gap: 4px; flex-wrap: wrap; }
|
|
69
81
|
.details .ttag { color: #fff; font-size: 11px; padding: 1px 8px; border-radius: 10px; cursor: pointer; text-decoration: none; }
|
|
70
82
|
.tree { overflow: auto; padding: 4px 0 8px; }
|
|
71
83
|
.explorer.collapsed .tree { display: none; }
|
|
72
|
-
.gh { padding:
|
|
73
|
-
letter-spacing: .
|
|
74
|
-
.gh::before { content: "
|
|
75
|
-
|
|
84
|
+
.gh { padding: 8px 12px 4px; font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase;
|
|
85
|
+
letter-spacing: .05em; cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: space-between; }
|
|
86
|
+
.gh::before { content: ""; width: 5px; height: 5px; margin: -2px 9px 0 2px; border-right: 1.5px solid currentColor;
|
|
87
|
+
border-bottom: 1.5px solid currentColor; transform: rotate(45deg); transition: transform .15s; }
|
|
88
|
+
.grp.shut .gh::before { transform: rotate(-45deg); margin-top: 0; }
|
|
89
|
+
.gh:hover { color: var(--ink); }
|
|
76
90
|
.gh span { font-weight: normal; }
|
|
77
91
|
.gh .gname { flex: 1; }
|
|
78
92
|
.grp.shut .gl { display: none; }
|
|
79
93
|
.ti { padding: 4px 12px 4px 26px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
80
94
|
.tree.flat .ti { padding-left: 14px; }
|
|
81
95
|
.ti small { color: var(--muted); margin-left: 6px; }
|
|
82
|
-
.ti:
|
|
96
|
+
.ti { border-radius: 6px; margin: 0 6px; padding-left: 20px; }
|
|
97
|
+
.ti:hover { background: var(--hover); }
|
|
98
|
+
.ti.sel { background: var(--sel); }
|
|
83
99
|
.ti.cur { color: var(--blue); font-weight: bold; }
|
|
84
100
|
.tree .empty { padding: 8px 14px; color: var(--muted); }
|
|
85
101
|
|
|
86
102
|
/* details drawer (right) */
|
|
87
|
-
.details { position: absolute; top:
|
|
88
|
-
border: 1px solid var(--line); border-radius:
|
|
103
|
+
.details { position: absolute; top: 64px; right: 16px; bottom: 16px; width: 380px; background: var(--panel);
|
|
104
|
+
border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); overflow: auto;
|
|
89
105
|
display: none; z-index: 3; padding: 16px 18px; }
|
|
90
106
|
.details.open { display: block; }
|
|
91
107
|
.has-details .minimap { right: 412px; }
|
|
92
|
-
.details .close { position: absolute; top:
|
|
93
|
-
color:
|
|
94
|
-
.details
|
|
108
|
+
.details .close { position: absolute; top: 10px; right: 10px; width: 28px; height: 28px; border: 0; border-radius: 6px;
|
|
109
|
+
background: transparent; display: flex; align-items: center; justify-content: center; color: var(--muted); cursor: pointer; }
|
|
110
|
+
.details .close:hover { background: var(--hover); color: var(--ink); }
|
|
111
|
+
.details h2 { margin: 0 28px 2px 0; font-size: 18px; font-weight: 650; letter-spacing: -.01em; color: var(--ink); word-break: break-word; }
|
|
95
112
|
.details .meta { color: var(--muted); font-size: 12px; margin-bottom: 10px; }
|
|
96
113
|
.details .doc { white-space: pre-wrap; margin: 8px 0 12px; line-height: 1.45; }
|
|
97
|
-
.details h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin:
|
|
114
|
+
.details h3 { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 18px 0 6px; }
|
|
98
115
|
.details table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
99
|
-
.details td { padding:
|
|
116
|
+
.details td { padding: 6px 4px; border-top: 1px solid var(--line); vertical-align: top; }
|
|
100
117
|
.details td.cn { font-weight: 600; word-break: break-word; }
|
|
101
|
-
.details td.ct { color:
|
|
102
|
-
.details .cd { color:
|
|
118
|
+
.details td.ct { color: var(--muted); text-align: right; white-space: nowrap; }
|
|
119
|
+
.details .cd { color: var(--ink-2); font-weight: normal; margin-top: 2px; white-space: pre-wrap; }
|
|
103
120
|
.details .tag { display: inline-block; font-size: 10px; padding: 0 5px; border-radius: 3px; background: #eef1f5;
|
|
104
121
|
color: #6b7a8c; margin: 0 3px 0 0; font-weight: normal; }
|
|
105
122
|
.details .tag.pk { background: #dfe8f6; color: var(--blue); }
|
|
106
123
|
.details .tag.fk { background: #ece6f7; color: #6a4fa3; }
|
|
107
124
|
.details ul { list-style: none; margin: 0; padding: 0; }
|
|
108
|
-
.details li { padding:
|
|
125
|
+
.details li { padding: 6px 0; border-top: 1px solid var(--line); }
|
|
109
126
|
.details a { color: var(--blue); text-decoration: none; cursor: pointer; }
|
|
110
127
|
.details a:hover { text-decoration: underline; }
|
|
111
128
|
.details .card { color: var(--muted); font-size: 11px; margin-left: 4px; }
|
|
@@ -113,22 +130,23 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
113
130
|
.details .hidden { color: var(--muted); }
|
|
114
131
|
|
|
115
132
|
/* minimap (bottom right) */
|
|
116
|
-
.minimap { position: absolute; right: 16px; bottom: 16px; width: 200px; height: 140px; background: rgba(255,255,255
|
|
117
|
-
border: 1px solid var(--line); border-radius:
|
|
133
|
+
.minimap { position: absolute; right: 16px; bottom: 16px; width: 200px; height: 140px; background: rgba(255, 255, 255, .94);
|
|
134
|
+
border: 1px solid var(--line); border-radius: 10px; box-shadow: var(--shadow); overflow: hidden; cursor: pointer; }
|
|
118
135
|
.minimap svg { width: 100%; height: 100%; display: block; }
|
|
119
136
|
.minimap .vpr { fill: rgba(43,87,151,.12); stroke: var(--blue); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
|
|
120
137
|
|
|
121
|
-
.toast { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); background:
|
|
122
|
-
padding: 8px 14px; border-radius:
|
|
138
|
+
.toast { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); background: var(--ink); color: #fff;
|
|
139
|
+
padding: 8px 14px; border-radius: 8px; font-size: 12px; opacity: 0; transition: opacity .2s; pointer-events: none; z-index: 6; }
|
|
123
140
|
.toast.open { opacity: 1; }
|
|
124
141
|
|
|
125
142
|
/* path finder (in the details panel) */
|
|
126
143
|
.details .pathbox { display: flex; gap: 6px; margin: 10px 0 4px; }
|
|
127
|
-
.details .pathbox input { flex: 1; min-width: 0; padding:
|
|
128
|
-
|
|
144
|
+
.details .pathbox input { flex: 1; min-width: 0; padding: 6px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit;
|
|
145
|
+
color: var(--ink); outline: none; }
|
|
146
|
+
.details .pathbox input:focus { border-color: var(--ink-2); }
|
|
129
147
|
.details .pathres ol { margin: 6px 0 8px 18px; padding: 0; }
|
|
130
148
|
.details .pathres li { border: 0; padding: 2px 0; }
|
|
131
|
-
.details .pathres pre { background:
|
|
149
|
+
.details .pathres pre { background: var(--bg); border: 1px solid var(--line); border-radius: 7px; padding: 8px; font-size: 11px;
|
|
132
150
|
overflow-x: auto; margin: 6px 0; }
|
|
133
151
|
.details .pathres .acts a { margin-right: 12px; font-size: 12px; }
|
|
134
152
|
.details .pathres .none { margin-top: 6px; }
|
|
@@ -137,8 +155,8 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
137
155
|
.t.ph .tb { stroke: #f0a020; stroke-width: 2.5; }
|
|
138
156
|
|
|
139
157
|
/* diff banner (build --diff-base) */
|
|
140
|
-
.diffbar { position: absolute; top:
|
|
141
|
-
border-radius:
|
|
158
|
+
.diffbar { position: absolute; top: 64px; left: 50%; transform: translateX(-50%); background: var(--panel); border: 1px solid var(--line);
|
|
159
|
+
border-radius: 10px; padding: 7px 14px; font-size: 12px; box-shadow: var(--shadow); display: none; z-index: 2;
|
|
142
160
|
white-space: nowrap; }
|
|
143
161
|
.diffbar.open { display: block; }
|
|
144
162
|
.diffbar b.add { color: #2e7d32; }
|
|
@@ -155,7 +173,7 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
155
173
|
.details .state.changed { background: #d48806; }
|
|
156
174
|
|
|
157
175
|
/* doc-comment tooltip */
|
|
158
|
-
.tip { position: absolute; display: none; max-width: 340px; background:
|
|
176
|
+
.tip { position: absolute; display: none; max-width: 340px; background: var(--ink); color: #eef0f2; border-radius: 8px;
|
|
159
177
|
padding: 8px 10px; font-size: 12px; line-height: 1.45; pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.25);
|
|
160
178
|
z-index: 5; }
|
|
161
179
|
.tip.open { display: block; }
|
|
@@ -166,17 +184,18 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
166
184
|
.hint { position: absolute; left: 16px; bottom: 14px; color: var(--muted); font-size: 12px; }
|
|
167
185
|
|
|
168
186
|
/* serve: editor pane */
|
|
169
|
-
#side { width: 420px; display: flex; flex-direction: column; background: #
|
|
187
|
+
#side { width: 420px; display: flex; flex-direction: column; background: #0f1114; color: #d9dce1; border-right: 1px solid #000; }
|
|
170
188
|
#side.hidden { display: none; }
|
|
171
|
-
.tabs { display: flex; overflow-x: auto; background: #
|
|
172
|
-
.tabs button { background: transparent; color: #
|
|
173
|
-
.tabs button
|
|
189
|
+
.tabs { display: flex; overflow-x: auto; background: #08090b; border-bottom: 1px solid rgba(255, 255, 255, .08); flex: none; }
|
|
190
|
+
.tabs button { background: transparent; color: #a1a7b0; border: 0; padding: 9px 13px; font: inherit; cursor: pointer; white-space: nowrap; }
|
|
191
|
+
.tabs button:hover { color: #edeef0; }
|
|
192
|
+
.tabs button.on { color: #edeef0; background: #0f1114; box-shadow: inset 0 -2px 0 var(--amber); }
|
|
174
193
|
.tabs button .dirty { color: #f0a020; }
|
|
175
194
|
#editor { flex: 1; min-height: 0; position: relative; }
|
|
176
195
|
#editor .CodeMirror, #editor textarea { position: absolute; inset: 0; height: 100%; width: 100%; font-size: 13px;
|
|
177
|
-
font-family: Menlo, Consolas, monospace; background: #
|
|
178
|
-
#problems { height: 140px; flex: none; overflow: auto; border-top: 2px solid #
|
|
179
|
-
font-family: Menlo, Consolas, monospace; font-size: 12px; color: #
|
|
196
|
+
font-family: ui-monospace, Menlo, Consolas, monospace; background: #0f1114; color: #d9dce1; border: 0; padding: 6px; resize: none; }
|
|
197
|
+
#problems { height: 140px; flex: none; overflow: auto; border-top: 2px solid #4ade80; padding: 8px 10px; background: #08090b;
|
|
198
|
+
font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; color: #a1a7b0; }
|
|
180
199
|
#problems.bad { border-top-color: #e57373; }
|
|
181
200
|
#problems .err { color: #ff9b9b; cursor: pointer; }
|
|
182
201
|
#problems .warn { color: #f0c674; cursor: pointer; }
|
|
@@ -185,17 +204,17 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
185
204
|
.banner button { margin-left: 8px; }
|
|
186
205
|
.cm-qdbd-op { color: #f0a020; }
|
|
187
206
|
.cm-qdbd-kw { color: #8fbcff; }
|
|
188
|
-
.CodeMirror-gutters { background: #
|
|
207
|
+
.CodeMirror-gutters { background: #0b0c0f !important; border-right: 0 !important; }
|
|
189
208
|
.cm-s-default .cm-comment { color: #7f8c8d; }
|
|
190
209
|
.cm-s-default .cm-header { color: #6fc3df; }
|
|
191
210
|
.CodeMirror-cursor { border-left-color: #fff !important; }
|
|
192
|
-
.CodeMirror-selected { background: #
|
|
211
|
+
.CodeMirror-selected { background: #2a2e35 !important; }
|
|
193
212
|
.lineerr { background: rgba(229,115,115,.18); }
|
|
194
213
|
|
|
195
214
|
/* narrow screens and embedded frames: toolbar to the bottom, smaller minimap */
|
|
196
215
|
@media (max-width: 900px) {
|
|
197
216
|
.bar { top: auto; bottom: 12px; right: auto; left: 12px; flex-wrap: wrap; max-width: calc(100% - 24px);
|
|
198
|
-
|
|
217
|
+
border-radius: 10px; padding: 3px; }
|
|
199
218
|
.hint { display: none; }
|
|
200
219
|
.minimap { width: 140px; height: 100px; bottom: auto; top: 14px; right: 12px; }
|
|
201
220
|
.explorer { max-width: calc(100% - 180px); }
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
// Keep clear of the floating search box / toolbar at the top and, on narrow screens, the toolbar at the bottom.
|
|
61
61
|
var top = 70, bottom = sz[0] < 900 ? 70 : pad, availH = sz[1] - top - bottom;
|
|
62
62
|
var k = Math.min((sz[0] - 2 * pad) / (b[2] - b[0]), availH / (b[3] - b[1]), 1.5);
|
|
63
|
-
k = Math.max(k, 0.
|
|
63
|
+
k = Math.max(k, 0.03); // same floor as manual zoom, so wide diagrams still fit a phone screen
|
|
64
64
|
S.view = { k: k, x: (sz[0] - (b[2] - b[0]) * k) / 2 - b[0] * k, y: top + (availH - (b[3] - b[1]) * k) / 2 - b[1] * k };
|
|
65
65
|
applyView();
|
|
66
66
|
}
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
|
+
import dataclasses
|
|
7
|
+
import json
|
|
6
8
|
import sys
|
|
7
9
|
from pathlib import Path
|
|
8
10
|
|
|
9
11
|
from . import __version__
|
|
10
|
-
from .model import Schema
|
|
12
|
+
from .model import Diagnostic, Schema
|
|
11
13
|
from .project import Project, SelectionError, selector_expr
|
|
12
14
|
|
|
13
15
|
OK = "No errors found. Good job!"
|
|
@@ -36,9 +38,31 @@ def _title(project: Project) -> str:
|
|
|
36
38
|
return "diagram"
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
def _github_annotation(d: Diagnostic, level: str) -> str:
|
|
42
|
+
"""A GitHub Actions workflow command, which shows `d` inline on the PR's changed lines."""
|
|
43
|
+
|
|
44
|
+
def esc(s: str) -> str:
|
|
45
|
+
return s.replace("%", "%25").replace("\r", "%0D").replace("\n", "%0A")
|
|
46
|
+
|
|
47
|
+
def esc_prop(s: str) -> str:
|
|
48
|
+
return esc(s).replace(":", "%3A").replace(",", "%2C")
|
|
49
|
+
|
|
50
|
+
return f"::{level} file={esc_prop(d.path)},line={d.line},title=qdbd::{esc(d.message)}"
|
|
51
|
+
|
|
52
|
+
|
|
39
53
|
def cmd_check(args) -> int:
|
|
40
54
|
project = _open(args)
|
|
41
55
|
schema = project.compile()
|
|
56
|
+
if args.format == "json":
|
|
57
|
+
ok = not schema.errors and not (args.strict and schema.warnings)
|
|
58
|
+
diags = [dataclasses.asdict(d) for d in schema.diagnostics]
|
|
59
|
+
report = {"ok": ok, "errors": len(schema.errors), "warnings": len(schema.warnings), "diagnostics": diags}
|
|
60
|
+
print(json.dumps(report, indent=2))
|
|
61
|
+
return 0 if ok else 1
|
|
62
|
+
if args.format == "github":
|
|
63
|
+
# Under --strict a warning fails the job, so it's annotated as an error.
|
|
64
|
+
for d in schema.diagnostics:
|
|
65
|
+
print(_github_annotation(d, "error" if args.strict else d.severity))
|
|
42
66
|
ok = _print_diagnostics(schema, args.strict)
|
|
43
67
|
if ok and not schema.diagnostics:
|
|
44
68
|
print(OK)
|
|
@@ -345,6 +369,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
345
369
|
sp = sub.add_parser("check", help="validate schema files")
|
|
346
370
|
paths(sp)
|
|
347
371
|
sp.add_argument("--strict", action="store_true", help="treat warnings (QuickDBD incompatibilities) as errors")
|
|
372
|
+
sp.add_argument(
|
|
373
|
+
"--format",
|
|
374
|
+
choices=["text", "github", "json"],
|
|
375
|
+
default="text",
|
|
376
|
+
help="github: also emit GitHub Actions annotations (inline on PR lines); json: a machine-readable report",
|
|
377
|
+
)
|
|
348
378
|
sp.set_defaults(func=cmd_check)
|
|
349
379
|
|
|
350
380
|
sp = sub.add_parser("build", help="write an interactive HTML diagram")
|
|
@@ -23,6 +23,25 @@ def _escape(text: str) -> str:
|
|
|
23
23
|
return text.replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
def _icon(d: str) -> str:
|
|
27
|
+
"""A 16px stroke icon, drawn in the toolbar's text colour."""
|
|
28
|
+
return (
|
|
29
|
+
'<svg viewBox="0 0 24 24" width="16" height="16" aria-hidden="true">'
|
|
30
|
+
f'<path d="{d}" fill="none" stroke="currentColor" stroke-width="1.8"'
|
|
31
|
+
' stroke-linecap="round" stroke-linejoin="round"/>'
|
|
32
|
+
"</svg>"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
ICONS = {
|
|
37
|
+
"icon_menu": _icon("M4 6h16M4 12h16M4 18h16"),
|
|
38
|
+
"icon_close": _icon("M6 6l12 12M18 6L6 18"),
|
|
39
|
+
"icon_minus": _icon("M5 12h14"),
|
|
40
|
+
"icon_plus": _icon("M12 5v14M5 12h14"),
|
|
41
|
+
"icon_fit": _icon("M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5"),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
26
45
|
BODY = """
|
|
27
46
|
<div id="app">
|
|
28
47
|
{side}
|
|
@@ -30,7 +49,7 @@ BODY = """
|
|
|
30
49
|
<svg id="canvas" xmlns="http://www.w3.org/2000/svg"><g id="vp"></g></svg>
|
|
31
50
|
<div class="explorer" id="explorer">
|
|
32
51
|
<div class="exhead">
|
|
33
|
-
<button id="extoggle" title="Show/hide table list"
|
|
52
|
+
<button id="extoggle" title="Show/hide table list" aria-label="Show or hide the table list">{icon_menu}</button>
|
|
34
53
|
<input id="q" type="search" placeholder="Search {count} tables ( / )" autocomplete="off" spellcheck="false">
|
|
35
54
|
</div>
|
|
36
55
|
<div class="exfilter">
|
|
@@ -42,7 +61,7 @@ BODY = """
|
|
|
42
61
|
<div id="tree" class="tree"></div>
|
|
43
62
|
</div>
|
|
44
63
|
<aside class="details" id="details" aria-label="Table details">
|
|
45
|
-
<button class="close" id="dclose" title="Close (Esc)"
|
|
64
|
+
<button class="close" id="dclose" title="Close (Esc)" aria-label="Close">{icon_close}</button>
|
|
46
65
|
<div id="dbody"></div>
|
|
47
66
|
</aside>
|
|
48
67
|
<div class="bar">
|
|
@@ -59,10 +78,10 @@ BODY = """
|
|
|
59
78
|
<button id="xpng" title="Export PNG">PNG</button>
|
|
60
79
|
<button id="xdrawio" title="Export an editable draw.io / diagrams.net file">draw.io</button>
|
|
61
80
|
<div class="sep"></div>
|
|
62
|
-
<button id="zout" title="Zoom out"
|
|
81
|
+
<button id="zout" title="Zoom out" aria-label="Zoom out">{icon_minus}</button>
|
|
63
82
|
<span class="zoomval" id="zoomval">100%</span>
|
|
64
|
-
<button id="zin" title="Zoom in"
|
|
65
|
-
<button id="zfit" title="Fit to screen"
|
|
83
|
+
<button id="zin" title="Zoom in" aria-label="Zoom in">{icon_plus}</button>
|
|
84
|
+
<button id="zfit" title="Fit to screen" aria-label="Fit to screen">{icon_fit}</button>
|
|
66
85
|
</div>
|
|
67
86
|
<div class="minimap">
|
|
68
87
|
<svg id="mini" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet"></svg>
|
|
@@ -113,7 +132,9 @@ def render_page(model: dict, positions: dict, title: str, serve: bool = False, l
|
|
|
113
132
|
hint = (
|
|
114
133
|
"Drag tables to rearrange (not saved). Click a table for details. Scroll to zoom, drag empty space to pan."
|
|
115
134
|
)
|
|
116
|
-
body = BODY.format(
|
|
135
|
+
body = BODY.format(
|
|
136
|
+
side=SIDE if serve else "", count=len(model["tables"]), hint=hint, filter_help=FILTER_HELP, **ICONS
|
|
137
|
+
)
|
|
117
138
|
head_extra = ""
|
|
118
139
|
scripts = ""
|
|
119
140
|
if serve:
|
|
@@ -91,6 +91,11 @@ def test_sample_databases_and_dbt_projects(built):
|
|
|
91
91
|
assert sizes["adventureworks"][0] == 68 and sizes["adventureworks"][1] >= 90
|
|
92
92
|
assert sizes["synthea"] == (145, 235)
|
|
93
93
|
assert sizes["shopify"][0] == 155 and sizes["shopify"][1] > 150 # every link inferred
|
|
94
|
+
for d, n in (("pubs", 11), ("adventureworks-dw", 28), ("saas-billing", 13), ("courses", 14)):
|
|
95
|
+
m = _model(built / d / "index.html")
|
|
96
|
+
assert len(m["tables"]) == n and m["rels"], d
|
|
97
|
+
dw = _model(built / "adventureworks-dw" / "index.html")
|
|
98
|
+
assert {"dim", "fact"} <= {t["name"] for t in dw["tags"]} # coloured by the Dim/Fact name prefixes
|
|
94
99
|
aw = _model(built / "adventureworks" / "index.html")
|
|
95
100
|
assert aw["regions"] and any(t.get("doc") for t in aw["tables"]) # MS_Description -> tooltips
|
|
96
101
|
|
|
@@ -136,6 +136,47 @@ def test_check_strict_warnings(tmp_path):
|
|
|
136
136
|
assert main(["check", "--strict", str(f)]) == 1
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def test_check_format_github(tmp_path, capsys):
|
|
140
|
+
bad = tmp_path / "bad.txt"
|
|
141
|
+
bad.write_text("T\n-\nA int foo\nB decimal(10, 2)\n")
|
|
142
|
+
assert main(["check", "--format", "github", str(bad)]) == 1
|
|
143
|
+
out = capsys.readouterr().out.splitlines()
|
|
144
|
+
assert f"::error file={bad},line=3,title=qdbd::Unrecognized keyword 'foo'" in out
|
|
145
|
+
assert any(line.startswith(f"::warning file={bad},line=4,title=qdbd::QuickDBD would reject this") for line in out)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_check_format_github_strict_promotes_warnings(tmp_path, capsys):
|
|
149
|
+
f = tmp_path / "w.txt"
|
|
150
|
+
f.write_text("T\n-\nA decimal(10, 2)\n")
|
|
151
|
+
assert main(["check", "--format", "github", "--strict", str(f)]) == 1
|
|
152
|
+
assert capsys.readouterr().out.startswith(f"::error file={f},line=3,")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_github_annotation_escaping():
|
|
156
|
+
from quickdbd_cli.cli import _github_annotation
|
|
157
|
+
from quickdbd_cli.model import Diagnostic
|
|
158
|
+
|
|
159
|
+
d = Diagnostic("a,b:c.txt", 2, "50% bad\nnext", "warning")
|
|
160
|
+
assert _github_annotation(d, "warning") == "::warning file=a%2Cb%3Ac.txt,line=2,title=qdbd::50%25 bad%0Anext"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_check_format_json(tmp_path, capsys):
|
|
164
|
+
bad = tmp_path / "bad.txt"
|
|
165
|
+
bad.write_text("T\n-\nA int foo\n")
|
|
166
|
+
assert main(["check", "--format", "json", str(bad)]) == 1
|
|
167
|
+
data = json.loads(capsys.readouterr().out)
|
|
168
|
+
assert data["ok"] is False and data["errors"] == 1 and data["warnings"] == 0
|
|
169
|
+
assert data["diagnostics"] == [
|
|
170
|
+
{"path": str(bad), "line": 3, "severity": "error", "message": "Unrecognized keyword 'foo'"}
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def test_check_format_json_ok(capsys):
|
|
175
|
+
assert main(["check", "--format", "json", str(SAMPLE)]) == 0
|
|
176
|
+
data = json.loads(capsys.readouterr().out)
|
|
177
|
+
assert data == {"ok": True, "errors": 0, "warnings": 0, "diagnostics": []}
|
|
178
|
+
|
|
179
|
+
|
|
139
180
|
def test_build_is_deterministic_and_does_not_write_sidecar(tmp_path):
|
|
140
181
|
work = tmp_path / "s"
|
|
141
182
|
shutil.copytree(SYNTH, work)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.12"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|