ofplang-export 0.1.0__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.
@@ -0,0 +1,4 @@
1
+ # Normalise line endings in the repository; CI runs on Linux.
2
+ * text=auto eol=lf
3
+ *.png binary
4
+ *.svg text eol=lf
@@ -0,0 +1,32 @@
1
+ # Working notes — not published (same convention as the sibling ofplang repos).
2
+ /dev-notes/
3
+
4
+ # Node / Vite
5
+ node_modules/
6
+ dist/
7
+ dist-single/
8
+ *.tsbuildinfo
9
+
10
+ # Datasets are generated from external/ at build time (design.md D9 ①).
11
+ /web/public/datasets/
12
+
13
+ # Python
14
+ __pycache__/
15
+ *.egg-info/
16
+ /build/
17
+ .pytest_cache/
18
+ .ruff_cache/
19
+ .mypy_cache/
20
+ # Built from web/ by `npm run build:single`; the wheel carries it (design.md D50).
21
+ /ofplang/export/_template/
22
+
23
+ # Editor / OS
24
+ .DS_Store
25
+ Thumbs.db
26
+ *.local
27
+
28
+ # Playwright
29
+ /web/shots/
30
+ /web/test-results/
31
+ /web/playwright-report/
32
+ /web/blob-report/
@@ -0,0 +1,3 @@
1
+ [submodule "external/ofplang-schedule"]
2
+ path = external/ofplang-schedule
3
+ url = https://github.com/ofplang/schedule.git
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kazunari Kaizu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ # setuptools-scm puts every tracked file in the sdist. The viewer template is
2
+ # built, not tracked, so it is named here; the web sources it is built from are
3
+ # not Python and stay out.
4
+ include ofplang/export/_template/viewer.html
5
+ prune web
6
+ prune prototype
7
+ prune external
8
+ prune .github
9
+ prune skills
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: ofplang-export
3
+ Version: 0.1.0
4
+ Summary: Write Object-flow Programming Language documents out in a form people read.
5
+ Author-email: Kazunari Kaizu <kwaizu@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ofplang/export
8
+ Project-URL: Repository, https://github.com/ofplang/export
9
+ Project-URL: Viewer, https://ofplang.github.io/export/
10
+ Keywords: ofplang,dataflow,workflow,schedule,viewer,html
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Visualization
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: PyYAML>=6.0
26
+ Provides-Extra: test
27
+ Requires-Dist: pytest>=7.0; extra == "test"
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: ruff>=0.16; extra == "dev"
31
+ Requires-Dist: mypy>=1.11; extra == "dev"
32
+ Requires-Dist: types-PyYAML; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # ofplang export
36
+
37
+ [![CI](https://github.com/ofplang/export/actions/workflows/ci.yml/badge.svg)](https://github.com/ofplang/export/actions/workflows/ci.yml)
38
+
39
+ Readable views of [ofplang](https://github.com/ofplang/spec) documents: a
40
+ workflow as its dataflow graph, and the execution plans
41
+ [`ofp-schedule`](https://github.com/ofplang/schedule) produces from it, shown
42
+ side by side and linked — as a web site, and as one self-contained HTML file.
43
+
44
+ It reads the documents the ofplang specifications define and nothing more, so
45
+ it does not depend on any dialect built on top of them.
46
+
47
+ ```sh
48
+ pip install ofplang-export
49
+ ofp-export view plan.yaml -o plan.html # a plan; its workflow and environment come from its meta
50
+ ofp-export view workflow.yaml -o wf.html # a workflow on its own
51
+ ```
52
+
53
+ `ofp-export view` writes the viewer as **one HTML file with the documents in
54
+ it**, to open from disk or send to someone. It is also meant to be reachable as
55
+ `ofp export view` and, under labcode, `lc export view`.
56
+
57
+ **→ [ofplang.github.io/export](https://ofplang.github.io/export/)**
58
+ — ten plans are bundled; `?doc=plate_batch` opens one directly. Drop your own
59
+ YAML on the window to read that instead: a plan, a workflow, an environment, or
60
+ all three at once. A workflow on its own is fine — the graph does not need a
61
+ plan to be read. Either pane can take the whole window: *Both / Workflow / Plan*
62
+ in the top bar, or `&layout=workflow` / `&layout=plan` in the address.
63
+
64
+ The point is to be able to hand someone a URL. They open it and see the
65
+ dataflow graph and the Gantt chart of a plan side by side, linked: pick a bar
66
+ and the workflow node it came from lights up, pick a node and every bar under
67
+ it lights up. No install, no server, no Python.
68
+
69
+ > **Status: early but usable.** Both panes work and are linked: pick a bar and
70
+ > the workflow box it came from lights up, pick a box and everything under it
71
+ > lights up in the plan. Plans can be exported as SVG or put in a link.
72
+ > `prototype/` holds the single-file look-and-feel study the visual decisions
73
+ > were made against.
74
+
75
+ ## The command
76
+
77
+ ```sh
78
+ ofp-export view <file>... [-o OUT] [--layout split|workflow|plan] [--gantt device|flow|activity]
79
+ [--name NAME] [--no-follow] [--json]
80
+ ```
81
+
82
+ - Files are a plan, a workflow and/or an environment, in any order; which is
83
+ which is read from the file. A plan's `meta` supplies the workflow and the
84
+ environment unless they are given (`--no-follow` turns that off).
85
+ - Without `-o` the HTML goes to standard output, as `ofp-schedule` does with a
86
+ plan. With `-o` the path written is printed; `--json` prints a summary instead.
87
+ - Exit codes: **0** written (warnings, if any, on stderr — parts of a workflow
88
+ the viewer shows only as source structure), **2** bad input, **3** refused
89
+ and nothing written — a joint plan (several workflows scheduled together as
90
+ jobs), which this viewer does not draw.
91
+
92
+ [`skills/ofp-export/SKILL.md`](skills/ofp-export/SKILL.md) says the same for an
93
+ agent calling the command, as a skill to install where the agent looks for them.
94
+
95
+ ## A single file
96
+
97
+ The same viewer also builds as **one self-contained HTML file** that carries
98
+ its documents inside it: open it from disk, no server, nothing beside it.
99
+ `npm run build:single` writes the empty template, `web/dist-single/viewer.html`
100
+ (also kept as the `viewer-template` artifact of every CI run), and
101
+ `web/scripts/embed.mjs` puts documents into it:
102
+
103
+ ```sh
104
+ node scripts/embed.mjs dist-single/viewer.html out.html plan.yaml --workflow w.yaml --env e.yaml
105
+ ```
106
+
107
+ The documents go in as the YAML text as written, in one element —
108
+ `<script type="application/json" id="ofp-documents" data-contract="1">` — and
109
+ the page reads them with the same reader it uses for a dropped file. An empty
110
+ template (`null` in that element) is an offline viewer to drop files on. The
111
+ web fonts stay a link, so offline the page falls back to system fonts; *Copy
112
+ link* is hidden, since a link made from a file on disk would point at the disk.
113
+
114
+ ## Layout
115
+
116
+ | Path | What it is |
117
+ |---|---|
118
+ | `web/` | the application — Vite + TypeScript, no runtime dependency beyond `yaml` |
119
+ | `web/src/model/` | types for the workflow, the environment and the execution document |
120
+ | `web/src/read/` | YAML → those types; the only part that tracks the specifications |
121
+ | `web/src/model/scene.ts` | the indices every view is built on — by node, by arc, by machine |
122
+ | `web/src/layout/` | lanes, bars and the time scale; pure functions, no DOM |
123
+ | `web/src/view/` | SVG rendering, the inspector, and the SVG export |
124
+ | `web/scripts/collect-datasets.mjs` | turns the submodule's examples into the bundled datasets |
125
+ | `web/scripts/build-single.mjs` | folds the build into the one-file viewer template |
126
+ | `web/scripts/embed.mjs` | puts documents into that template — the contract, in one place |
127
+ | `ofplang/export/` | the `ofp-export` Python package; `template.py` is the same contract in Python |
128
+ | `tests/` | its tests (pytest), against the pinned submodule's examples |
129
+ | `web/tests/golden/` | every example the pinned submodule ships must read |
130
+ | `external/ofplang-schedule` | submodule, pinned by tag — specifications and examples |
131
+ | `prototype/` | a single-file look-and-feel study; not the codebase |
132
+
133
+ ## Why a TypeScript reader instead of reusing the Python one
134
+
135
+ The sibling repositories own the specifications and the semantics, and this one
136
+ does not modify them. It also has to run with nothing installed on the viewer's
137
+ machine, which rules out a Python pre-processing step. So the document readers
138
+ here are a deliberate, bounded re-implementation of two stable schemas —
139
+ `SPECIFICATIONS.md` §5 (environment) and §6 (execution document) — kept honest
140
+ by the golden test against the pinned submodule's own examples.
141
+
142
+ Anything outside that subset — `$import`, generics, structured nodes, and
143
+ joint plans that schedule several workflows together as jobs — is refused
144
+ rather than guessed at.
145
+
146
+ ## Working on it
147
+
148
+ ```sh
149
+ git clone --recurse-submodules git@github.com:ofplang/export.git
150
+ cd web
151
+ npm install
152
+
153
+ npm run dev # development server (collects the datasets first)
154
+ npm run datasets # rebuild public/datasets/ from external/
155
+ npm run typecheck # tsc --noEmit
156
+ npm test # golden tests against external/ofplang-schedule
157
+ npm run build # typecheck + production build into web/dist
158
+ npm run build:single # …and the one-file template into web/dist-single
159
+ npm run test:e2e # the browser tests (builds the template first)
160
+ ```
161
+
162
+ The Python package, from the repository root (after `npm run build:single`,
163
+ which also puts the template into `ofplang/export/_template/`):
164
+
165
+ ```sh
166
+ pip install -e ".[dev]"
167
+ pytest
168
+ ```
169
+
170
+ A release is a `v*` tag: `publish.yml` builds the template and the wheel from
171
+ the same commit and publishes through PyPI trusted publishing (an `rc` tag goes
172
+ to TestPyPI). Installing straight from git gives no template — it is built, not
173
+ committed — and the command says so.
174
+
175
+ Already cloned without `--recurse-submodules`? `git submodule update --init`.
176
+
177
+ ## License
178
+
179
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,145 @@
1
+ # ofplang export
2
+
3
+ [![CI](https://github.com/ofplang/export/actions/workflows/ci.yml/badge.svg)](https://github.com/ofplang/export/actions/workflows/ci.yml)
4
+
5
+ Readable views of [ofplang](https://github.com/ofplang/spec) documents: a
6
+ workflow as its dataflow graph, and the execution plans
7
+ [`ofp-schedule`](https://github.com/ofplang/schedule) produces from it, shown
8
+ side by side and linked — as a web site, and as one self-contained HTML file.
9
+
10
+ It reads the documents the ofplang specifications define and nothing more, so
11
+ it does not depend on any dialect built on top of them.
12
+
13
+ ```sh
14
+ pip install ofplang-export
15
+ ofp-export view plan.yaml -o plan.html # a plan; its workflow and environment come from its meta
16
+ ofp-export view workflow.yaml -o wf.html # a workflow on its own
17
+ ```
18
+
19
+ `ofp-export view` writes the viewer as **one HTML file with the documents in
20
+ it**, to open from disk or send to someone. It is also meant to be reachable as
21
+ `ofp export view` and, under labcode, `lc export view`.
22
+
23
+ **→ [ofplang.github.io/export](https://ofplang.github.io/export/)**
24
+ — ten plans are bundled; `?doc=plate_batch` opens one directly. Drop your own
25
+ YAML on the window to read that instead: a plan, a workflow, an environment, or
26
+ all three at once. A workflow on its own is fine — the graph does not need a
27
+ plan to be read. Either pane can take the whole window: *Both / Workflow / Plan*
28
+ in the top bar, or `&layout=workflow` / `&layout=plan` in the address.
29
+
30
+ The point is to be able to hand someone a URL. They open it and see the
31
+ dataflow graph and the Gantt chart of a plan side by side, linked: pick a bar
32
+ and the workflow node it came from lights up, pick a node and every bar under
33
+ it lights up. No install, no server, no Python.
34
+
35
+ > **Status: early but usable.** Both panes work and are linked: pick a bar and
36
+ > the workflow box it came from lights up, pick a box and everything under it
37
+ > lights up in the plan. Plans can be exported as SVG or put in a link.
38
+ > `prototype/` holds the single-file look-and-feel study the visual decisions
39
+ > were made against.
40
+
41
+ ## The command
42
+
43
+ ```sh
44
+ ofp-export view <file>... [-o OUT] [--layout split|workflow|plan] [--gantt device|flow|activity]
45
+ [--name NAME] [--no-follow] [--json]
46
+ ```
47
+
48
+ - Files are a plan, a workflow and/or an environment, in any order; which is
49
+ which is read from the file. A plan's `meta` supplies the workflow and the
50
+ environment unless they are given (`--no-follow` turns that off).
51
+ - Without `-o` the HTML goes to standard output, as `ofp-schedule` does with a
52
+ plan. With `-o` the path written is printed; `--json` prints a summary instead.
53
+ - Exit codes: **0** written (warnings, if any, on stderr — parts of a workflow
54
+ the viewer shows only as source structure), **2** bad input, **3** refused
55
+ and nothing written — a joint plan (several workflows scheduled together as
56
+ jobs), which this viewer does not draw.
57
+
58
+ [`skills/ofp-export/SKILL.md`](skills/ofp-export/SKILL.md) says the same for an
59
+ agent calling the command, as a skill to install where the agent looks for them.
60
+
61
+ ## A single file
62
+
63
+ The same viewer also builds as **one self-contained HTML file** that carries
64
+ its documents inside it: open it from disk, no server, nothing beside it.
65
+ `npm run build:single` writes the empty template, `web/dist-single/viewer.html`
66
+ (also kept as the `viewer-template` artifact of every CI run), and
67
+ `web/scripts/embed.mjs` puts documents into it:
68
+
69
+ ```sh
70
+ node scripts/embed.mjs dist-single/viewer.html out.html plan.yaml --workflow w.yaml --env e.yaml
71
+ ```
72
+
73
+ The documents go in as the YAML text as written, in one element —
74
+ `<script type="application/json" id="ofp-documents" data-contract="1">` — and
75
+ the page reads them with the same reader it uses for a dropped file. An empty
76
+ template (`null` in that element) is an offline viewer to drop files on. The
77
+ web fonts stay a link, so offline the page falls back to system fonts; *Copy
78
+ link* is hidden, since a link made from a file on disk would point at the disk.
79
+
80
+ ## Layout
81
+
82
+ | Path | What it is |
83
+ |---|---|
84
+ | `web/` | the application — Vite + TypeScript, no runtime dependency beyond `yaml` |
85
+ | `web/src/model/` | types for the workflow, the environment and the execution document |
86
+ | `web/src/read/` | YAML → those types; the only part that tracks the specifications |
87
+ | `web/src/model/scene.ts` | the indices every view is built on — by node, by arc, by machine |
88
+ | `web/src/layout/` | lanes, bars and the time scale; pure functions, no DOM |
89
+ | `web/src/view/` | SVG rendering, the inspector, and the SVG export |
90
+ | `web/scripts/collect-datasets.mjs` | turns the submodule's examples into the bundled datasets |
91
+ | `web/scripts/build-single.mjs` | folds the build into the one-file viewer template |
92
+ | `web/scripts/embed.mjs` | puts documents into that template — the contract, in one place |
93
+ | `ofplang/export/` | the `ofp-export` Python package; `template.py` is the same contract in Python |
94
+ | `tests/` | its tests (pytest), against the pinned submodule's examples |
95
+ | `web/tests/golden/` | every example the pinned submodule ships must read |
96
+ | `external/ofplang-schedule` | submodule, pinned by tag — specifications and examples |
97
+ | `prototype/` | a single-file look-and-feel study; not the codebase |
98
+
99
+ ## Why a TypeScript reader instead of reusing the Python one
100
+
101
+ The sibling repositories own the specifications and the semantics, and this one
102
+ does not modify them. It also has to run with nothing installed on the viewer's
103
+ machine, which rules out a Python pre-processing step. So the document readers
104
+ here are a deliberate, bounded re-implementation of two stable schemas —
105
+ `SPECIFICATIONS.md` §5 (environment) and §6 (execution document) — kept honest
106
+ by the golden test against the pinned submodule's own examples.
107
+
108
+ Anything outside that subset — `$import`, generics, structured nodes, and
109
+ joint plans that schedule several workflows together as jobs — is refused
110
+ rather than guessed at.
111
+
112
+ ## Working on it
113
+
114
+ ```sh
115
+ git clone --recurse-submodules git@github.com:ofplang/export.git
116
+ cd web
117
+ npm install
118
+
119
+ npm run dev # development server (collects the datasets first)
120
+ npm run datasets # rebuild public/datasets/ from external/
121
+ npm run typecheck # tsc --noEmit
122
+ npm test # golden tests against external/ofplang-schedule
123
+ npm run build # typecheck + production build into web/dist
124
+ npm run build:single # …and the one-file template into web/dist-single
125
+ npm run test:e2e # the browser tests (builds the template first)
126
+ ```
127
+
128
+ The Python package, from the repository root (after `npm run build:single`,
129
+ which also puts the template into `ofplang/export/_template/`):
130
+
131
+ ```sh
132
+ pip install -e ".[dev]"
133
+ pytest
134
+ ```
135
+
136
+ A release is a `v*` tag: `publish.yml` builds the template and the wheel from
137
+ the same commit and publishes through PyPI trusted publishing (an `rc` tag goes
138
+ to TestPyPI). Installing straight from git gives no template — it is built, not
139
+ committed — and the command says so.
140
+
141
+ Already cloned without `--recurse-submodules`? `git submodule update --init`.
142
+
143
+ ## License
144
+
145
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,13 @@
1
+ """ofplang export: write ofplang documents out in a form people read.
2
+
3
+ The one target so far is `view` — the interactive viewer as a single HTML
4
+ file. See `ofplang.export.cli` for the command, `ofplang.export.template` for
5
+ the contract between this package and the page it fills.
6
+ """
7
+
8
+ from importlib.metadata import PackageNotFoundError, version
9
+
10
+ try:
11
+ __version__ = version("ofplang-export")
12
+ except PackageNotFoundError: # a source tree without installed metadata
13
+ __version__ = "0+unknown"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from .cli import main
4
+
5
+ sys.exit(main())