quickdbd-cli 0.1.1__tar.gz → 0.1.3__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.1 → quickdbd_cli-0.1.3}/PKG-INFO +24 -2
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/README.md +23 -1
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/pyproject.toml +1 -1
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/scripts/release.sh +7 -2
- quickdbd_cli-0.1.3/src/quickdbd_cli/__init__.py +1 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/render.js +16 -5
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/style.css +9 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/viewer.js +45 -2
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/cli.py +59 -0
- quickdbd_cli-0.1.3/src/quickdbd_cli/dbt.py +412 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/html.py +1 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/measure.py +11 -4
- quickdbd_cli-0.1.3/tests/fixtures/jaffle/LICENSE +201 -0
- quickdbd_cli-0.1.3/tests/fixtures/jaffle/README.md +2 -0
- quickdbd_cli-0.1.3/tests/fixtures/jaffle/catalog.json +227 -0
- quickdbd_cli-0.1.3/tests/fixtures/jaffle/manifest.json +880 -0
- quickdbd_cli-0.1.3/tests/test_dbt.py +231 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/tests/test_pipeline.py +31 -0
- quickdbd_cli-0.1.1/src/quickdbd_cli/__init__.py +0 -1
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/.gitignore +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/LICENSE +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/NOTICE +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/quickdbd-sample.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/synthetic/finance.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/synthetic/hr.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/synthetic/inventory.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/synthetic/marketing.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/synthetic/qdbd.layout.json +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/examples/synthetic/sales.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/scripts/gen_font_metrics.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/scripts/gen_schema.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/__main__.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/Inter-Bold.ttf +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/Inter-Bold.woff2 +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/Inter-LICENSE.txt +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/Inter-Regular.ttf +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/Inter-Regular.woff2 +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/editor.js +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/elk-LICENSE.md +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/elk.bundled.js +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/assets/font-metrics.json +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/export.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/jsrt.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/layout.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/model.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/parser.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/project.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/src/quickdbd_cli/serve.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/tests/test_parser.py +0 -0
- {quickdbd_cli-0.1.1 → quickdbd_cli-0.1.3}/tests/test_serve.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: quickdbd-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -55,12 +55,33 @@ Not affiliated with QuickDBD. It reads the same text syntax, so schemas move fre
|
|
|
55
55
|
- click a table to focus on it and its neighbours
|
|
56
56
|
- collapse tables: full / keys only / names only
|
|
57
57
|
- hover a column to highlight its relationship
|
|
58
|
+
- hover a dotted-underlined table or column to read its `#` doc comments
|
|
58
59
|
- PNG/SVG export buttons
|
|
59
60
|
- **Live editing:** `qdbd serve` reloads when you save, has a browser editor pane with autosave, and remembers where you drag tables.
|
|
60
61
|
- **Many files, one diagram:** point it at a folder and foreign keys can cross files.
|
|
61
62
|
- **Pip-only install:** no Graphviz, Node or browser needed. Layout uses [ELK](https://eclipse.dev/elk/), running in an embedded V8.
|
|
62
63
|
- **CI-friendly:** builds are deterministic, and errors are `file:line: error: …` with exit code 1.
|
|
63
64
|
|
|
65
|
+
## From a dbt project
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
dbt docs generate # optional: adds real column types (catalog.json)
|
|
69
|
+
qdbd import dbt target/manifest.json -o erd/ # one .txt per folder (staging, marts, sources, ...)
|
|
70
|
+
qdbd build erd/ -o erd.html
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
- **Tables:** models, seeds and snapshots of your project, plus sources.
|
|
74
|
+
- **Relationships:** `relationships` tests (including dbt_utils `relationships_where`) and `foreign_key` constraints.
|
|
75
|
+
- **Primary keys:** `primary_key` constraints, or else a column with both `unique` and `not_null` tests.
|
|
76
|
+
- **Docs:** model and column descriptions become doc comments, shown as tooltips.
|
|
77
|
+
- **Options:**
|
|
78
|
+
- `--select 'marts.*'` narrows by name, fqn or path (repeatable)
|
|
79
|
+
- `--package all` includes installed packages
|
|
80
|
+
- `--no-sources` drops sources
|
|
81
|
+
- `-o schema.txt` writes a single file
|
|
82
|
+
|
|
83
|
+
Re-running the import overwrites the generated files. Table positions survive because they live in the layout file, keyed by table name.
|
|
84
|
+
|
|
64
85
|
## Commands
|
|
65
86
|
|
|
66
87
|
```bash
|
|
@@ -69,6 +90,7 @@ qdbd build PATH... -o diagram.html # shareable interactive HTML (pos
|
|
|
69
90
|
qdbd serve PATH... [--port 8765] # live editor + drag-to-arrange
|
|
70
91
|
qdbd export PATH... -o out.png [--focus Customer --depth 1] # PNG or SVG image
|
|
71
92
|
qdbd layout PATH... [--reset] # (re)write the layout file
|
|
93
|
+
qdbd import dbt target/manifest.json -o erd/ # generate schema files from a dbt project
|
|
72
94
|
```
|
|
73
95
|
|
|
74
96
|
`PATH` is a `.txt` file or a folder. Every `*.txt` under a folder is read recursively.
|
|
@@ -157,7 +179,7 @@ TWINE_USERNAME=__token__
|
|
|
157
179
|
TWINE_PASSWORD=pypi-...
|
|
158
180
|
```
|
|
159
181
|
|
|
160
|
-
Bump `version` in `pyproject.toml` and `src/quickdbd_cli/__init__.py`, commit, then run `scripts/release.sh`. It builds, uploads to PyPI, tags `vX.Y.Z` and creates the GitHub release.
|
|
182
|
+
Bump `version` in `pyproject.toml` and `src/quickdbd_cli/__init__.py`, commit, then run `PATH="$PWD/.venv/bin:$PATH" scripts/release.sh`. It builds, uploads to PyPI, tags `vX.Y.Z` and creates the GitHub release.
|
|
161
183
|
|
|
162
184
|
## License
|
|
163
185
|
|
|
@@ -30,12 +30,33 @@ Not affiliated with QuickDBD. It reads the same text syntax, so schemas move fre
|
|
|
30
30
|
- click a table to focus on it and its neighbours
|
|
31
31
|
- collapse tables: full / keys only / names only
|
|
32
32
|
- hover a column to highlight its relationship
|
|
33
|
+
- hover a dotted-underlined table or column to read its `#` doc comments
|
|
33
34
|
- PNG/SVG export buttons
|
|
34
35
|
- **Live editing:** `qdbd serve` reloads when you save, has a browser editor pane with autosave, and remembers where you drag tables.
|
|
35
36
|
- **Many files, one diagram:** point it at a folder and foreign keys can cross files.
|
|
36
37
|
- **Pip-only install:** no Graphviz, Node or browser needed. Layout uses [ELK](https://eclipse.dev/elk/), running in an embedded V8.
|
|
37
38
|
- **CI-friendly:** builds are deterministic, and errors are `file:line: error: …` with exit code 1.
|
|
38
39
|
|
|
40
|
+
## From a dbt project
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
dbt docs generate # optional: adds real column types (catalog.json)
|
|
44
|
+
qdbd import dbt target/manifest.json -o erd/ # one .txt per folder (staging, marts, sources, ...)
|
|
45
|
+
qdbd build erd/ -o erd.html
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
- **Tables:** models, seeds and snapshots of your project, plus sources.
|
|
49
|
+
- **Relationships:** `relationships` tests (including dbt_utils `relationships_where`) and `foreign_key` constraints.
|
|
50
|
+
- **Primary keys:** `primary_key` constraints, or else a column with both `unique` and `not_null` tests.
|
|
51
|
+
- **Docs:** model and column descriptions become doc comments, shown as tooltips.
|
|
52
|
+
- **Options:**
|
|
53
|
+
- `--select 'marts.*'` narrows by name, fqn or path (repeatable)
|
|
54
|
+
- `--package all` includes installed packages
|
|
55
|
+
- `--no-sources` drops sources
|
|
56
|
+
- `-o schema.txt` writes a single file
|
|
57
|
+
|
|
58
|
+
Re-running the import overwrites the generated files. Table positions survive because they live in the layout file, keyed by table name.
|
|
59
|
+
|
|
39
60
|
## Commands
|
|
40
61
|
|
|
41
62
|
```bash
|
|
@@ -44,6 +65,7 @@ qdbd build PATH... -o diagram.html # shareable interactive HTML (pos
|
|
|
44
65
|
qdbd serve PATH... [--port 8765] # live editor + drag-to-arrange
|
|
45
66
|
qdbd export PATH... -o out.png [--focus Customer --depth 1] # PNG or SVG image
|
|
46
67
|
qdbd layout PATH... [--reset] # (re)write the layout file
|
|
68
|
+
qdbd import dbt target/manifest.json -o erd/ # generate schema files from a dbt project
|
|
47
69
|
```
|
|
48
70
|
|
|
49
71
|
`PATH` is a `.txt` file or a folder. Every `*.txt` under a folder is read recursively.
|
|
@@ -132,7 +154,7 @@ TWINE_USERNAME=__token__
|
|
|
132
154
|
TWINE_PASSWORD=pypi-...
|
|
133
155
|
```
|
|
134
156
|
|
|
135
|
-
Bump `version` in `pyproject.toml` and `src/quickdbd_cli/__init__.py`, commit, then run `scripts/release.sh`. It builds, uploads to PyPI, tags `vX.Y.Z` and creates the GitHub release.
|
|
157
|
+
Bump `version` in `pyproject.toml` and `src/quickdbd_cli/__init__.py`, commit, then run `PATH="$PWD/.venv/bin:$PATH" scripts/release.sh`. It builds, uploads to PyPI, tags `vX.Y.Z` and creates the GitHub release.
|
|
136
158
|
|
|
137
159
|
## License
|
|
138
160
|
|
|
@@ -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.3"
|
|
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"
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Build and upload the current version to PyPI using credentials from .env,
|
|
3
3
|
# then tag and create the GitHub release.
|
|
4
|
-
# Usage: bump `version` in pyproject.toml and src/quickdbd_cli/__init__.py, commit, then run
|
|
4
|
+
# Usage: bump `version` in pyproject.toml and src/quickdbd_cli/__init__.py, commit, then run
|
|
5
|
+
# PATH="$PWD/.venv/bin:$PATH" scripts/release.sh
|
|
5
6
|
set -euo pipefail
|
|
6
7
|
cd "$(dirname "$0")/.."
|
|
7
8
|
|
|
@@ -12,7 +13,11 @@ version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','
|
|
|
12
13
|
[ -z "$(git status --porcelain --untracked-files=no)" ] || { echo "error: uncommitted changes" >&2; exit 1; }
|
|
13
14
|
|
|
14
15
|
rm -rf dist
|
|
15
|
-
|
|
16
|
+
# uv-created venvs have no pip; only install the tools when they're missing.
|
|
17
|
+
python3 -c "import build, twine" 2>/dev/null || {
|
|
18
|
+
if command -v uv >/dev/null; then uv pip install -q --python "$(command -v python3)" build twine
|
|
19
|
+
else python3 -m pip install -q --upgrade build twine; fi
|
|
20
|
+
}
|
|
16
21
|
python3 -m build
|
|
17
22
|
python3 -m twine check dist/*
|
|
18
23
|
python3 -m twine upload --non-interactive dist/*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.3"
|
|
@@ -66,13 +66,22 @@
|
|
|
66
66
|
'<path d="M' + n(x + 6.5) + " " + n(cy) + "h7m-2 0v3m-2.5-3v2" + '"/></g>';
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
/* Dotted underline marking text that has a doc comment (interactive viewer only). */
|
|
70
|
+
function docMark(x, y, w, color) {
|
|
71
|
+
return '<line class="dm" x1="' + n(x) + '" y1="' + n(y) + '" x2="' + n(x + w) + '" y2="' + n(y) +
|
|
72
|
+
'" stroke="' + color + '" stroke-width="1" stroke-dasharray="1.5 2"/>';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderTable(model, t, g, p, docMarks) {
|
|
70
76
|
var st = model.style, w = g.w, h = g.h, out = [];
|
|
71
77
|
out.push('<g class="t" data-id="' + esc(t.id) + '" transform="translate(' + n(p[0]) + "," + n(p[1]) + ')">');
|
|
72
78
|
out.push('<rect class="tb" width="' + w + '" height="' + h + '" rx="2" fill="' + C.body + '" stroke="' + C.border + '"/>');
|
|
73
79
|
out.push('<path class="th" d="M0 2a2 2 0 0 1 2-2h' + (w - 4) + "a2 2 0 0 1 2 2v" + (st.headerH - 2) + "h-" + w + 'z" fill="' + C.header + '"/>');
|
|
74
80
|
out.push('<text x="' + st.padX + '" y="' + n(st.headerH / 2 + st.headerSize * 0.36) + '" fill="' + C.headerText +
|
|
75
81
|
'" font-family="' + FONT + '" font-size="' + st.headerSize + '">' + esc(t.name) + "</text>");
|
|
82
|
+
if (docMarks && t.doc) {
|
|
83
|
+
out.push(docMark(st.padX, st.headerH / 2 + st.headerSize * 0.36 + 3, t.nameW, "rgba(255,255,255,.7)"));
|
|
84
|
+
}
|
|
76
85
|
g.cols.forEach(function (c, i) {
|
|
77
86
|
var y = st.headerH + i * st.rowH, cy = y + st.rowH / 2, base = n(cy + st.rowSize * 0.36);
|
|
78
87
|
var weight = c.pk || c.fk ? ' font-weight="bold"' : "";
|
|
@@ -81,6 +90,7 @@
|
|
|
81
90
|
out.push('<rect class="rb" y="' + y + '" width="' + w + '" height="' + st.rowH + '" fill="' + C.body + '" fill-opacity="0"/>');
|
|
82
91
|
out.push('<text x="' + st.padX + '" y="' + base + '" fill="' + color + '" font-family="' + FONT + '" font-size="' +
|
|
83
92
|
st.rowSize + '"' + weight + ">" + esc(c.name) + "</text>");
|
|
93
|
+
if (docMarks && c.doc) out.push(docMark(st.padX, cy + st.rowSize * 0.36 + 2.5, c.nameW, color));
|
|
84
94
|
var bx = st.padX + c.nameW + 6;
|
|
85
95
|
c.badges.forEach(function (b) {
|
|
86
96
|
var bh = st.badgeSize + 5;
|
|
@@ -89,7 +99,7 @@
|
|
|
89
99
|
'" font-family="' + FONT + '" font-size="' + st.badgeSize + '">' + esc(b.t) + "</text>");
|
|
90
100
|
bx += b.w + st.badgeGap;
|
|
91
101
|
});
|
|
92
|
-
if (c.pk) out.push(keyIcon(w - st.padX - c.typeW - st.keyW
|
|
102
|
+
if (c.pk) out.push(keyIcon(w - st.padX - c.typeW - st.keyW, cy));
|
|
93
103
|
out.push('<text x="' + (w - st.padX) + '" y="' + base + '" text-anchor="end" fill="' + C.type + '" font-family="' + FONT +
|
|
94
104
|
'" font-size="' + st.rowSize + '">' + esc(c.type) + "</text>");
|
|
95
105
|
out.push("</g>");
|
|
@@ -164,9 +174,10 @@
|
|
|
164
174
|
return model.rels.map(function (r) { return renderEdge(model, geo, pos, r); }).join("");
|
|
165
175
|
}
|
|
166
176
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
var
|
|
177
|
+
/* opts.docMarks: underline tables/columns that have doc comments (viewer only, not exports). */
|
|
178
|
+
function renderInner(model, pos, mode, opts) {
|
|
179
|
+
var geo = geometry(model, mode), marks = !!(opts && opts.docMarks);
|
|
180
|
+
var tables = model.tables.map(function (t) { return pos[t.id] ? renderTable(model, t, geo[t.id], pos[t.id], marks) : ""; }).join("");
|
|
170
181
|
return '<g class="edges">' + renderEdges(model, pos, mode, geo) + '</g><g class="tables">' + tables + "</g>";
|
|
171
182
|
}
|
|
172
183
|
|
|
@@ -53,6 +53,15 @@ html, body { margin: 0; height: 100%; background: var(--bg); color: var(--ink);
|
|
|
53
53
|
.minimap svg { width: 100%; height: 100%; display: block; }
|
|
54
54
|
.minimap .vpr { fill: rgba(43,87,151,.12); stroke: var(--blue); stroke-width: 1.5; vector-effect: non-scaling-stroke; }
|
|
55
55
|
|
|
56
|
+
/* doc-comment tooltip */
|
|
57
|
+
.tip { position: absolute; display: none; max-width: 340px; background: #23272b; color: #eee; border-radius: 6px;
|
|
58
|
+
padding: 8px 10px; font-size: 12px; line-height: 1.45; pointer-events: none; box-shadow: 0 4px 14px rgba(0,0,0,.25);
|
|
59
|
+
z-index: 5; }
|
|
60
|
+
.tip.open { display: block; }
|
|
61
|
+
.tip .tt { font-weight: bold; margin-bottom: 3px; }
|
|
62
|
+
.tip .tt span { font-weight: normal; color: #9aa4ad; margin-left: 8px; }
|
|
63
|
+
.tip .td { white-space: pre-wrap; color: #d8dde2; }
|
|
64
|
+
|
|
56
65
|
.hint { position: absolute; left: 16px; bottom: 14px; color: var(--muted); font-size: 12px; }
|
|
57
66
|
|
|
58
67
|
/* serve: editor pane */
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
/* ------------------------------------------------------------- render */
|
|
26
26
|
function render() {
|
|
27
|
-
vp.innerHTML = Q.renderInner(S.model, S.pos, S.mode);
|
|
27
|
+
vp.innerHTML = Q.renderInner(S.model, S.pos, S.mode, { docMarks: true });
|
|
28
28
|
applyFocus();
|
|
29
29
|
drawMinimap();
|
|
30
30
|
}
|
|
@@ -124,7 +124,50 @@
|
|
|
124
124
|
if (t) t.classList.add("hl");
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
|
-
vp.addEventListener("mouseleave", clearHover);
|
|
127
|
+
vp.addEventListener("mouseleave", function () { clearHover(); hideTip(); });
|
|
128
|
+
|
|
129
|
+
/* ------------------------------------------------------------- doc-comment tooltips */
|
|
130
|
+
var tip = document.getElementById("tip");
|
|
131
|
+
function hideTip() { tip.classList.remove("open"); }
|
|
132
|
+
function showTip(ev, title, sub, doc) {
|
|
133
|
+
tip.innerHTML = "";
|
|
134
|
+
var h = document.createElement("div");
|
|
135
|
+
h.className = "tt";
|
|
136
|
+
h.textContent = title;
|
|
137
|
+
if (sub) {
|
|
138
|
+
var s = document.createElement("span");
|
|
139
|
+
s.textContent = sub;
|
|
140
|
+
h.appendChild(s);
|
|
141
|
+
}
|
|
142
|
+
var d = document.createElement("div");
|
|
143
|
+
d.className = "td";
|
|
144
|
+
d.textContent = doc;
|
|
145
|
+
tip.appendChild(h);
|
|
146
|
+
tip.appendChild(d);
|
|
147
|
+
tip.classList.add("open");
|
|
148
|
+
moveTip(ev);
|
|
149
|
+
}
|
|
150
|
+
function moveTip(ev) {
|
|
151
|
+
var r = svg.getBoundingClientRect(), x = ev.clientX - r.left + 14, y = ev.clientY - r.top + 14;
|
|
152
|
+
var w = tip.offsetWidth, h = tip.offsetHeight;
|
|
153
|
+
if (x + w > r.width - 8) x = ev.clientX - r.left - w - 14;
|
|
154
|
+
if (y + h > r.height - 8) y = ev.clientY - r.top - h - 14;
|
|
155
|
+
tip.style.left = Math.max(8, x) + "px";
|
|
156
|
+
tip.style.top = Math.max(8, y) + "px";
|
|
157
|
+
}
|
|
158
|
+
vp.addEventListener("mousemove", function (ev) {
|
|
159
|
+
if (drag && drag.moved) { hideTip(); return; }
|
|
160
|
+
var tEl = ev.target.closest && ev.target.closest(".t");
|
|
161
|
+
if (!tEl) { hideTip(); return; }
|
|
162
|
+
var t = byId[tEl.getAttribute("data-id")], row = ev.target.closest(".r");
|
|
163
|
+
if (row) {
|
|
164
|
+
var cid = row.getAttribute("data-c"), c = t.cols.filter(function (x) { return x.id === cid; })[0];
|
|
165
|
+
if (c && c.doc) showTip(ev, t.name + "." + c.name, c.type, c.doc);
|
|
166
|
+
else hideTip();
|
|
167
|
+
} else if (t.doc && ev.target.closest(".th, text")) {
|
|
168
|
+
showTip(ev, t.name, t.aliases.length ? "as " + t.aliases.join(", ") : "", t.doc);
|
|
169
|
+
} else hideTip();
|
|
170
|
+
});
|
|
128
171
|
|
|
129
172
|
/* ------------------------------------------------------------- pointer: pan, click, drag */
|
|
130
173
|
var drag = null;
|
|
@@ -103,6 +103,43 @@ def cmd_layout(args) -> int:
|
|
|
103
103
|
return 0
|
|
104
104
|
|
|
105
105
|
|
|
106
|
+
def cmd_import_dbt(args) -> int:
|
|
107
|
+
from .dbt import DbtImportError, import_dbt, write_result
|
|
108
|
+
from .parser import compile_sources
|
|
109
|
+
|
|
110
|
+
out = Path(args.output)
|
|
111
|
+
split = out.suffix != ".txt"
|
|
112
|
+
try:
|
|
113
|
+
result = import_dbt(
|
|
114
|
+
Path(args.manifest),
|
|
115
|
+
Path(args.catalog) if args.catalog else None,
|
|
116
|
+
packages=args.package,
|
|
117
|
+
select=args.select,
|
|
118
|
+
sources=not args.no_sources,
|
|
119
|
+
split=split,
|
|
120
|
+
)
|
|
121
|
+
except DbtImportError as e:
|
|
122
|
+
print(f"error: {e}", file=sys.stderr)
|
|
123
|
+
return 1
|
|
124
|
+
# Self-check: the generated text must compile cleanly.
|
|
125
|
+
schema = compile_sources([(name, text) for name, text in result.files.items()])
|
|
126
|
+
if schema.errors:
|
|
127
|
+
for d in schema.errors:
|
|
128
|
+
print(d.format(), file=sys.stderr)
|
|
129
|
+
print("error: generated schema did not compile (please report this as a bug)", file=sys.stderr)
|
|
130
|
+
return 1
|
|
131
|
+
written = write_result(result, out)
|
|
132
|
+
for s in result.skipped:
|
|
133
|
+
print(f"skipped: {s}", file=sys.stderr)
|
|
134
|
+
where = str(out) if not split else f"{out}/ ({len(written)} files)"
|
|
135
|
+
print(f"Wrote {where}: {result.tables} tables, {result.relationships} relationships")
|
|
136
|
+
if result.tables and not result.relationships:
|
|
137
|
+
print(
|
|
138
|
+
"hint: no relationships found; add `relationships` tests or foreign_key constraints in dbt", file=sys.stderr
|
|
139
|
+
)
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
|
|
106
143
|
def cmd_serve(args) -> int:
|
|
107
144
|
from .serve import serve
|
|
108
145
|
|
|
@@ -147,6 +184,28 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
147
184
|
sp.add_argument("--layout")
|
|
148
185
|
sp.set_defaults(func=cmd_layout)
|
|
149
186
|
|
|
187
|
+
sp = sub.add_parser("import", help="generate QuickDBD schema files from another source")
|
|
188
|
+
isub = sp.add_subparsers(dest="source", required=True)
|
|
189
|
+
dp = isub.add_parser(
|
|
190
|
+
"dbt",
|
|
191
|
+
help="from a dbt project's target/manifest.json",
|
|
192
|
+
description="Tables from models/seeds/snapshots/sources, relationships from `relationships` tests and "
|
|
193
|
+
"foreign_key constraints, types from catalog.json (run `dbt docs generate` first for types).",
|
|
194
|
+
)
|
|
195
|
+
dp.add_argument("manifest", help="path to manifest.json (or the target/ directory)")
|
|
196
|
+
dp.add_argument("-o", "--output", default="schema.txt", help="a .txt file, or a directory for one file per folder")
|
|
197
|
+
dp.add_argument("--catalog", help="catalog.json for column types (default: next to the manifest, if present)")
|
|
198
|
+
dp.add_argument(
|
|
199
|
+
"--package",
|
|
200
|
+
action="append",
|
|
201
|
+
help="dbt package(s) to include (default: the root project; 'all' for every package)",
|
|
202
|
+
)
|
|
203
|
+
dp.add_argument(
|
|
204
|
+
"--select", action="append", help="glob on model name, fqn (a.b.c) or file path, e.g. 'marts.*' (repeatable)"
|
|
205
|
+
)
|
|
206
|
+
dp.add_argument("--no-sources", action="store_true", help="leave out dbt sources")
|
|
207
|
+
dp.set_defaults(func=cmd_import_dbt)
|
|
208
|
+
|
|
150
209
|
sp = sub.add_parser("serve", help="live editor: reload on save, drag tables to arrange")
|
|
151
210
|
paths(sp)
|
|
152
211
|
sp.add_argument("--port", type=int, default=8765)
|