spyret 0.1.1 → 0.3.0
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.
- package/README.md +86 -23
- package/THIRD_PARTY_NOTICES.txt +28 -1
- package/dist/spyret-browser.amd.js +61 -0
- package/dist/spyret-browser.d.mts +272 -0
- package/dist/spyret-browser.d.ts +272 -0
- package/dist/spyret-browser.global.js +57 -0
- package/dist/spyret-browser.js +57 -0
- package/dist/spyret-browser.mjs +57 -0
- package/dist/spyret.d.mts +29 -1
- package/dist/spyret.d.ts +29 -1
- package/dist/spyret.global.js +12 -6
- package/dist/spyret.js +12 -6
- package/dist/spyret.mjs +12 -6
- package/dist/spyret.pyret.js +66 -0
- package/docs/BROWSER_LIBRARY.md +102 -0
- package/docs/IDE_MIGRATION.md +38 -0
- package/docs/PYRET_CAPTURE.md +4 -2
- package/docs/RELEASING.md +60 -63
- package/docs/SPYTIAL_HOOKS.md +71 -0
- package/docs/SPYTIAL_LANGUAGE.md +194 -0
- package/docs/SPYTIAL_RULES_REFERENCE.md +53 -0
- package/package.json +29 -8
- package/pyret/spytial.arr +217 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Spyret library and IDE ownership
|
|
2
|
+
|
|
3
|
+
Spyret now supplies the reusable browser integration. A Drive-imported native
|
|
4
|
+
module can install its own CPO display adapter; the IDE does **not** need a
|
|
5
|
+
Spytial-specific output branch. See [library setup](BROWSER_LIBRARY.md).
|
|
6
|
+
|
|
7
|
+
| Functionality | Owner |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| Value capture, relationalization, reconstruction, `_spytial` collection | Spyret |
|
|
10
|
+
| Generated Pyret rule types, YAML section composition | Spyret |
|
|
11
|
+
| Pyret-facing diagram functions and runtime suspension/Stop handling | Spyret |
|
|
12
|
+
| Evaluator/layout setup, opaque handles, source preview and views | Spyret browser entry |
|
|
13
|
+
| CPO renderer compatibility code | Spyret, isolated in `registerCpoOutput` |
|
|
14
|
+
| Layout semantics, parsing, solving, graph controls and diagnostics | Core |
|
|
15
|
+
| Source selection, YAML source writeback, undo and keyboard shortcuts | IDE |
|
|
16
|
+
| Builtin aliases and package/asset delivery for existing programs | IDE build configuration |
|
|
17
|
+
|
|
18
|
+
In the separate companion IDE change, the `spytial` builtin becomes a small
|
|
19
|
+
forwarding wrapper around
|
|
20
|
+
`spyret/browser`. `spytial-rules` is a build-time copy of the generated Pyret
|
|
21
|
+
module. Its old `spytial-view` module and Spytial branch in `output-ui` are removed.
|
|
22
|
+
Existing `SP.diagram(value, yaml)` and `_output` methods continue to work; new
|
|
23
|
+
programs can use `SP.diagram(value)` and typed rules.
|
|
24
|
+
|
|
25
|
+
Stock CPO can instead load `spyret/pyret-module` through its existing `gdrive-js`
|
|
26
|
+
locator, optionally hidden behind one URL-imported `.arr` wrapper. No new native
|
|
27
|
+
URL locator or IDE plugin is required for that route. Drive hosting/access is a
|
|
28
|
+
separate deployment step.
|
|
29
|
+
|
|
30
|
+
The companion IDE change consumes a lockfile-verified vendored Spyret 0.2.0 package
|
|
31
|
+
archive, so this coordinated change builds without an unpublished registry
|
|
32
|
+
version or a sibling source checkout. Once 0.2.0 is published, replace that
|
|
33
|
+
archive dependency with the exact npm version and regenerate the copied assets;
|
|
34
|
+
no runtime code needs to change.
|
|
35
|
+
|
|
36
|
+
Future work should focus on an upstream public display-registration and
|
|
37
|
+
cancellation API, deployment/Drive-access testing, and optional source-editor
|
|
38
|
+
integration. The existing private CPO adapter remains a compatibility dependency.
|
package/docs/PYRET_CAPTURE.md
CHANGED
|
@@ -62,8 +62,10 @@ inspect its nominal token/display spelling.
|
|
|
62
62
|
|
|
63
63
|
No printer or arbitrary annotation runs during capture. Declared datatype methods
|
|
64
64
|
are behavior supplied by declarations and are outside the declared-slot state
|
|
65
|
-
contract; this includes `_output`.
|
|
66
|
-
|
|
65
|
+
contract; this includes `_output`. Callable `_spytial` metadata is also omitted
|
|
66
|
+
on ordinary objects and outside a datatype's declared slots; collecting layout
|
|
67
|
+
rules is a separate, explicit operation. Other non-method extension fields on
|
|
68
|
+
data values are rejected instead of dropped. Functions/methods in state-bearing slots,
|
|
67
69
|
opaque values, unrecognized branded library objects, standalone table Row
|
|
68
70
|
values, sparse arrays, unset/frozen references, and arbitrary reference
|
|
69
71
|
annotations produce explicit diagnostics. This API does not serialize closures
|
package/docs/RELEASING.md
CHANGED
|
@@ -1,68 +1,65 @@
|
|
|
1
|
-
# Releasing Spyret
|
|
1
|
+
# Releasing Spyret to Google Drive
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
declarations, with no Core runtime dependency. The host passes the returned
|
|
6
|
-
`IDataInstance` to Core for layout/rendering; compatibility tests use Core 6.3.1.
|
|
7
|
-
|
|
8
|
-
## One-time npm setup
|
|
9
|
-
|
|
10
|
-
The first package publication requires an npm maintainer login. From a checked,
|
|
11
|
-
merged release commit, run:
|
|
3
|
+
With Node 22+, [GitHub CLI](https://cli.github.com/), and `tar` installed, run
|
|
4
|
+
from this checkout (no `npm install` or build needed):
|
|
12
5
|
|
|
13
6
|
```sh
|
|
14
|
-
npm
|
|
15
|
-
npm ci
|
|
16
|
-
npm run typecheck
|
|
17
|
-
npm test
|
|
18
|
-
npx --yes --package npm@11.20.0 -c 'npm run test:package -- --out release'
|
|
19
|
-
npx --yes --package npm@11.20.0 npm publish ./release/spyret-0.1.0.tgz --access public
|
|
7
|
+
npm run release:drive
|
|
20
8
|
```
|
|
21
9
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
10
|
+
The script downloads the latest GitHub release and walks you through:
|
|
11
|
+
|
|
12
|
+
1. Open a new [CPO editor](https://code.pyret.org/editor) with normal Google login.
|
|
13
|
+
Replace **all** editor text (including the initial `use context` line) with the
|
|
14
|
+
contents of the prepared `.js` file. Name it `spyret-vVERSION.js`, **Save**, and
|
|
15
|
+
wait for saving to finish. **Do not Run** this document: it contains JavaScript.
|
|
16
|
+
In Google Drive, set the saved file to **Anyone with the link / Viewer** and
|
|
17
|
+
keep it under **My Drive**. Paste its CPO editor URL (`#program=...`), Drive
|
|
18
|
+
link, or file ID into the script. Use the saved file, not a CPO Share copy.
|
|
19
|
+
2. Upload the generated `.arr` wrapper to My Drive with the same sharing setting
|
|
20
|
+
and paste its Drive link into the script.
|
|
21
|
+
3. Copy the printed import into CPO and run the provided diagram example.
|
|
22
|
+
4. Test that same import with a **second account using normal CPO login** before
|
|
23
|
+
distributing it. The script prepares artifacts; it does not verify Google
|
|
24
|
+
permissions or perform either browser test.
|
|
25
|
+
|
|
26
|
+
### Why save the JavaScript through CPO?
|
|
27
|
+
|
|
28
|
+
CPO's normal login requests `drive.file`. Its `gdrive-js` loader uses an
|
|
29
|
+
authenticated request, so uploading a public JavaScript file directly to Drive
|
|
30
|
+
does not by itself authorize CPO to read it. Saving through CPO creates the file
|
|
31
|
+
with CPO's authorization. CPO saves the editor text without compiling it.
|
|
32
|
+
The outer `.arr` wrapper uses `shared-gdrive`, which has a public-file retrieval
|
|
33
|
+
path, so that file can still be uploaded directly.
|
|
34
|
+
|
|
35
|
+
For an existing JavaScript upload, **Open with → Code Pyret** in Google Drive,
|
|
36
|
+
if offered, is another way to grant per-file access without changing its ID.
|
|
37
|
+
Google documents this mechanism in its
|
|
38
|
+
[Drive authorization guidance](https://developers.google.com/workspace/drive/api/guides/handle-errors#appNotAuthorizedToFile).
|
|
39
|
+
Opening an arbitrary `editor#program=ID` link alone is not the same authorization
|
|
40
|
+
action. Pasting a link into this script only selects an ID; it grants no access.
|
|
41
|
+
|
|
42
|
+
This workflow still needs end-to-end verification with real CPO accounts.
|
|
43
|
+
Success as the publisher does not establish access for other users. If another
|
|
44
|
+
user gets a 403, they may need to authorize the JavaScript through Drive's
|
|
45
|
+
**Open with → Code Pyret** too. Do not advertise a setup-free import until the
|
|
46
|
+
second-account test passes. The old full-access login flow is not a prerequisite
|
|
47
|
+
or a recommended workaround: Google may block it.
|
|
48
|
+
|
|
49
|
+
Keep both filenames unchanged and preserve the file contents without conversion.
|
|
50
|
+
Use **My Drive**, not a Google **Shared drive**: CPO’s Drive loader omits the
|
|
51
|
+
shared-drive API flag, so files in Shared drives return 404 even when public.
|
|
52
|
+
A shortcut under My Drive does not change where a file lives.
|
|
53
|
+
The two upload files are in `release/vVERSION/upload/`; the CPO example is
|
|
54
|
+
saved separately as `release/vVERSION/import.arr`. Keep published Drive files
|
|
55
|
+
unchanged so existing imports continue to work.
|
|
56
|
+
|
|
57
|
+
If you previously uploaded directly to Drive (or used a Shared drive), rerun with
|
|
58
|
+
`npm run release:drive -- --tag v0.2.0 --out release/cpo-authorized` and follow
|
|
59
|
+
the CPO-save workflow above. Paste the new links when prompted so the wrapper
|
|
60
|
+
uses the new JavaScript file ID. The fresh output directory preserves the old
|
|
61
|
+
release artifacts; existing wrappers are never overwritten with a different ID.
|
|
62
|
+
|
|
63
|
+
To select a specific release, use `npm run release:drive -- --tag v0.2.0`.
|
|
64
|
+
The release must contain the browser module and Pyret rules; `v0.1.1` is headless
|
|
65
|
+
and cannot be used. No Google Cloud project, service account, or API setup is needed.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Layout hooks and typed rules
|
|
2
|
+
|
|
3
|
+
The package includes `pyret/spytial.arr`, a typed rule library generated from
|
|
4
|
+
Core's language manifest. Copy or serve that file where your Pyret program can
|
|
5
|
+
import it (Node hosts can locate it with `require.resolve('spyret/spytial.arr')`).
|
|
6
|
+
|
|
7
|
+
```pyret
|
|
8
|
+
import file("spytial.arr") as S
|
|
9
|
+
|
|
10
|
+
data Tree:
|
|
11
|
+
| leaf(value)
|
|
12
|
+
| branch(children)
|
|
13
|
+
sharing:
|
|
14
|
+
method _spytial(self) -> List<S.SpytialRule>:
|
|
15
|
+
[list:
|
|
16
|
+
S.orientation("children", [list: S.below]),
|
|
17
|
+
S.align("siblings", S.horizontal)
|
|
18
|
+
]
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Public constructors wrap their results as `constraint(Constraint)` or
|
|
24
|
+
`directive(Directive)` automatically. Compose rules with ordinary Pyret lists;
|
|
25
|
+
the serializer puts each rule in its proper YAML section. Use named fields in
|
|
26
|
+
records for optional properties and typed constructors for nested style blocks:
|
|
27
|
+
|
|
28
|
+
```pyret
|
|
29
|
+
S.atom-style("leaf", {fill-style: S.fill-style({color: "red"})})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
From the JavaScript host, after evaluating the program:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import { toDataInstance, getSpytialSpec } from 'spyret';
|
|
36
|
+
|
|
37
|
+
const instance = toDataInstance(pyretValue, runtime);
|
|
38
|
+
const specs = await getSpytialSpec(pyretValue, runtime); // string[]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The collector walks reachable values in breadth-first order, including lists,
|
|
42
|
+
objects, tuples, arrays, references, dictionaries and table cells. It handles
|
|
43
|
+
cycles and invokes each distinct `_spytial` function or method once per call,
|
|
44
|
+
with methods bound to their first encountered owner. Repeated hooks never stop
|
|
45
|
+
the traversal of an instance's children. Hooks should describe types, independent
|
|
46
|
+
of the particular instance; unobserved datatype variants cannot be discovered.
|
|
47
|
+
|
|
48
|
+
Each hook returns `List<SpytialRule>`, serialized as one YAML document. A raw YAML
|
|
49
|
+
string is also accepted as an escape hatch, unchanged and unvalidated. No hooks
|
|
50
|
+
yields `[]`; a hook returning an empty list yields one empty spec. Identical
|
|
51
|
+
documents from different hooks remain separate. Hook failures, malformed rules
|
|
52
|
+
and unsupported values reject the promise with a `SpytialSpecError` and path.
|
|
53
|
+
|
|
54
|
+
Hooks execute user code. Call the async collector while its owning runtime is
|
|
55
|
+
idle or paused; a native Pyret module can use `runtime.pauseStack` around the
|
|
56
|
+
promise. `spytialRulesToYaml(rules, runtime)` also serializes an already evaluated
|
|
57
|
+
rule list directly. Capture and `toDataInstance` never invoke hooks, and omit
|
|
58
|
+
callable `_spytial` metadata on ordinary objects or outside a datatype's declared
|
|
59
|
+
slots. Other capture restrictions still apply.
|
|
60
|
+
|
|
61
|
+
See the [layout rule guide](SPYTIAL_LANGUAGE.md) for examples and the
|
|
62
|
+
[generated rule reference](SPYTIAL_RULES_REFERENCE.md) for every constructor,
|
|
63
|
+
enum and option. Pyret annotations check field types; serialization
|
|
64
|
+
checks numeric bounds, string patterns and incompatible directions. Selector
|
|
65
|
+
meaning and result arity remain Core's responsibility.
|
|
66
|
+
|
|
67
|
+
To update the API, pin the desired Core release in `package.json` and the lockfile,
|
|
68
|
+
then run `npm run generate:spytial`. Commit the generated Pyret source, serializer
|
|
69
|
+
schema and reference together. `npm run check:spytial` and the tests detect drift;
|
|
70
|
+
unknown field types or enum vocabularies fail generation for explicit handling.
|
|
71
|
+
No Core runtime dependency is added to the published package.
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Spyret's layout rule language
|
|
2
|
+
|
|
3
|
+
Spyret lets a Pyret program describe how its data should be drawn. The
|
|
4
|
+
`pyret/spytial.arr` library, imported as `S` below, provides typed constructors
|
|
5
|
+
for Spytial constraints and directives. Each constructor returns an
|
|
6
|
+
`S.SpytialRule`; put rules in an ordinary Pyret list. `S.diagram(value)` finds
|
|
7
|
+
`_spytial` hooks on reachable values, collects their rules, and displays the
|
|
8
|
+
diagram. See [layout hooks](SPYTIAL_HOOKS.md) for the hook contract and
|
|
9
|
+
[the README](../README.md#pyret-import-describe-display) for the CPO import.
|
|
10
|
+
This page describes the 0.3.0 source API. The published v0.2.0 wrapper uses
|
|
11
|
+
prefixed enum names and `atom-style-with`; update those calls when moving to
|
|
12
|
+
0.3.0. Style properties now use named records and typed blocks instead of
|
|
13
|
+
`.with-*` chains.
|
|
14
|
+
|
|
15
|
+
## A first diagram
|
|
16
|
+
|
|
17
|
+
```pyret
|
|
18
|
+
# Use the versioned wrapper import supplied by the library maintainer.
|
|
19
|
+
import shared-gdrive("spyret-vVERSION.arr", "WRAPPER_DRIVE_FILE_ID") as S
|
|
20
|
+
|
|
21
|
+
data Tree:
|
|
22
|
+
| leaf(value)
|
|
23
|
+
| branch(left, right)
|
|
24
|
+
sharing:
|
|
25
|
+
method _spytial(self) -> List<S.SpytialRule>:
|
|
26
|
+
[list:
|
|
27
|
+
S.orientation("left + right", [list: S.below]),
|
|
28
|
+
S.orientation("left", [list: S.left]),
|
|
29
|
+
S.orientation("right", [list: S.right]),
|
|
30
|
+
S.atom-style("leaf", {fill-style: S.fill-style({color: "#e0f2ff"})})
|
|
31
|
+
]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
S.diagram(branch(leaf(1), leaf(2)))
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The `left + right` selector finds parent-to-child pairs. For each pair,
|
|
39
|
+
`below` places the child below the parent. The next two rules put
|
|
40
|
+
left children to the left and right children to the right. The style rule fills
|
|
41
|
+
leaf nodes. Rule order does not turn later rules into overrides; Spytial applies
|
|
42
|
+
them together and reports conflicting constraints.
|
|
43
|
+
|
|
44
|
+
The same constructors work in a local `import file("spytial.arr") as S` if the
|
|
45
|
+
library is available as a file. That import supplies rule constructors; the
|
|
46
|
+
versioned wrapper also supplies `diagram` and `diagram-with-rules`.
|
|
47
|
+
|
|
48
|
+
## Selectors and direction
|
|
49
|
+
|
|
50
|
+
A selector is a **string containing a Spytial relational expression**, not a
|
|
51
|
+
Pyret function. A unary selector such as `"leaf"` selects nodes. A binary
|
|
52
|
+
selector such as `"left"` or `"left + right"` selects `(source, target)` pairs.
|
|
53
|
+
The expected arity depends on the rule. For orientation, the direction names
|
|
54
|
+
describe where the **target** goes relative to the source. Thus, if `left`
|
|
55
|
+
contains `(parent, child)`, `S.below` puts the child below its parent.
|
|
56
|
+
|
|
57
|
+
Common expressions are a type name (`"leaf"`), a field name (`"left"`), a
|
|
58
|
+
union (`"left + right"`), or a transitive closure (`"^(left + right)"`). Keep
|
|
59
|
+
operators inside the quoted Pyret string. See [Spytial Core's selector
|
|
60
|
+
documentation](https://github.com/sidprasad/spytial-core/blob/main/site/selectors.md)
|
|
61
|
+
for the full expression language and arity rules.
|
|
62
|
+
|
|
63
|
+
| Pyret direction | Meaning for the target |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `S.above`, `S.below` | Strictly above or below; sideways offset is allowed. |
|
|
66
|
+
| `S.left`, `S.right` | Strictly left or right; vertical offset is allowed. |
|
|
67
|
+
| `S.directly-above`, `S.directly-below` | Above or below and horizontally centered. |
|
|
68
|
+
| `S.directly-left`, `S.directly-right` | Left or right and vertically centered. |
|
|
69
|
+
|
|
70
|
+
You can combine compatible directions, for example
|
|
71
|
+
`[list: S.below, S.left]`. Opposites such as `above` and
|
|
72
|
+
`below` cannot be combined. A `directly-*` direction can only be combined with
|
|
73
|
+
its matching plain direction. Spyret checks these combinations when it
|
|
74
|
+
serializes the rule list.
|
|
75
|
+
|
|
76
|
+
## Constraints: layout and visibility
|
|
77
|
+
|
|
78
|
+
| Constructor | What it does |
|
|
79
|
+
| --- | --- |
|
|
80
|
+
| `S.orientation(selector, directions)` | Places targets of a binary selector relative to sources. |
|
|
81
|
+
| `S.cyclic(selector)` | Arranges nodes in the order of a binary selector around a circle. |
|
|
82
|
+
| `S.align(selector, direction)` | Gives selected nodes or node pairs a common row (`S.horizontal`) or column (`S.vertical`). |
|
|
83
|
+
| `S.group(selector, name)` | Draws a box around selected nodes; a binary selector makes a group per first-column key. |
|
|
84
|
+
| `S.size(width, height)` | Sets node dimensions in pixels; select the affected nodes with `S.size-with(width, height, {selector: "..."})`. Both numbers must be positive. |
|
|
85
|
+
| `S.hide-atom(selector)` | Removes selected nodes and their edges from the diagram. |
|
|
86
|
+
|
|
87
|
+
For example:
|
|
88
|
+
|
|
89
|
+
```pyret
|
|
90
|
+
[list:
|
|
91
|
+
S.cyclic-with("next", {direction: S.counterclockwise}),
|
|
92
|
+
S.align("siblings", S.horizontal),
|
|
93
|
+
S.group("children", "Family"),
|
|
94
|
+
S.size-with(150, 80, {selector: "branch"}),
|
|
95
|
+
S.hide-atom("InternalNode")
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Constraints can also use `{hold: S.never}` to require that a relation
|
|
100
|
+
**does not** hold. For instance, negating `S.above` means the target is
|
|
101
|
+
not strictly above the source; it does not require the target to be below.
|
|
102
|
+
`S.always` expresses the normal positive constraint.
|
|
103
|
+
|
|
104
|
+
## Directives: appearance and labels
|
|
105
|
+
|
|
106
|
+
| Constructor | What it does |
|
|
107
|
+
| --- | --- |
|
|
108
|
+
| `S.flag(name)` | Applies a global display flag such as `S.hide-disconnected` or `S.hide-disconnected-built-ins`. |
|
|
109
|
+
| `S.atom-style(selector, style)` | Styles the selected nodes' fill, border, icon, label, and label visibility. |
|
|
110
|
+
| `S.edge-style(field, style)` | Styles a relation's edge line and label, or hides the edge. |
|
|
111
|
+
| `S.attribute(field)` | Shows a field as text inside its source node instead of as an edge. |
|
|
112
|
+
| `S.tag(to-tag, name, value)` | Adds computed text to matching nodes while retaining the original edges. |
|
|
113
|
+
| `S.hide-field(field)` | Hides a field's drawn edges. |
|
|
114
|
+
| `S.inferred-edge(name, selector)` | Draws additional edges computed from a selector. |
|
|
115
|
+
|
|
116
|
+
`field` names a relation; optional `selector` and `filter` fields narrow where
|
|
117
|
+
field-based directives apply. `tag` takes a unary `to-tag` selector and a
|
|
118
|
+
`value` selector whose first column identifies the node receiving the tag.
|
|
119
|
+
|
|
120
|
+
```pyret
|
|
121
|
+
[list:
|
|
122
|
+
S.flag(S.hide-disconnected-built-ins),
|
|
123
|
+
S.atom-style("leaf", {border-style: S.border-style({
|
|
124
|
+
color: "#2563eb", width: 2
|
|
125
|
+
})}),
|
|
126
|
+
S.edge-style("left", {line-style: S.line-style({
|
|
127
|
+
color: "#64748b", pattern: S.dashed
|
|
128
|
+
})}),
|
|
129
|
+
S.attribute("value"),
|
|
130
|
+
S.hide-field("internal"),
|
|
131
|
+
S.inferred-edge("descendant", "^(left + right)")
|
|
132
|
+
]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Use `atom-style`, `edge-style`, and `inferred-edge` for new styles. The exported
|
|
136
|
+
`icon`, `atom-color`, and `edge-color` constructors are older forms retained for
|
|
137
|
+
compatibility.
|
|
138
|
+
|
|
139
|
+
## Optional fields and reusable style blocks
|
|
140
|
+
|
|
141
|
+
Rules with optional fields have a short constructor for their required
|
|
142
|
+
arguments and a `-with` constructor that takes a Pyret record of named fields.
|
|
143
|
+
`atom-style` takes a selector and a style record; `edge-style` takes a field
|
|
144
|
+
and a style record. Within those records, nested styles use the named types
|
|
145
|
+
`S.TextStyle`, `S.LineStyle`, `S.FillStyle`, `S.BorderStyle`, and `S.IconStyle`.
|
|
146
|
+
For example, `S.text-style({color: "navy"})` makes a `TextStyle`. Leave any
|
|
147
|
+
field out to let Spytial Core supply its display default.
|
|
148
|
+
|
|
149
|
+
```pyret
|
|
150
|
+
S.orientation-with("children", [list: S.below], {hold: S.always})
|
|
151
|
+
|
|
152
|
+
S.group-with("children", "Family", {
|
|
153
|
+
add-edge: S.group-add-edge({
|
|
154
|
+
points: S.togroup,
|
|
155
|
+
line-style: S.line-style({weight: 2})
|
|
156
|
+
}),
|
|
157
|
+
text-style: S.text-style({color: "#7c3aed"})
|
|
158
|
+
})
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The group example draws a connector from each group key to its box. Its
|
|
162
|
+
`line-style` styles that connector; the group's own `text-style` styles its
|
|
163
|
+
caption. Most rule records also accept a `source: S.rule-source({...})` field;
|
|
164
|
+
this records the originating rule text and optional location
|
|
165
|
+
for error reports without changing layout.
|
|
166
|
+
|
|
167
|
+
The [generated rule reference](SPYTIAL_RULES_REFERENCE.md) lists every
|
|
168
|
+
constructor, option, enum, and style block for the currently pinned Core
|
|
169
|
+
language version. Spyret checks Pyret types through annotations, then checks
|
|
170
|
+
numeric bounds, string patterns, and direction compatibility while serializing.
|
|
171
|
+
|
|
172
|
+
## How rules become a diagram
|
|
173
|
+
|
|
174
|
+
`_spytial` returns `List<S.SpytialRule>`. Spyret turns each list into one YAML
|
|
175
|
+
document with `constraints` and `directives` sections; constructors choose the
|
|
176
|
+
correct section automatically. `S.diagram(value)` collects the hooks reachable
|
|
177
|
+
from `value`. To bypass hooks, call `S.diagram-with-rules(value, rules)`; an
|
|
178
|
+
existing YAML document can be passed as `S.diagram(value, yaml)`.
|
|
179
|
+
|
|
180
|
+
For example, `S.orientation("left", [list: S.below])` represents the
|
|
181
|
+
following constraint (the serializer quotes YAML keys and strings):
|
|
182
|
+
|
|
183
|
+
```yaml
|
|
184
|
+
constraints:
|
|
185
|
+
- orientation:
|
|
186
|
+
selector: left
|
|
187
|
+
directions: [below]
|
|
188
|
+
directives: []
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
For host code, `getSpytialSpec(value, runtime)` returns the collected YAML
|
|
192
|
+
documents as `string[]`, and `spytialRulesToYaml(rules, runtime)` serializes an
|
|
193
|
+
already evaluated list. See [layout hooks](SPYTIAL_HOOKS.md) for collection
|
|
194
|
+
order, error behavior, and runtime requirements.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Generated Pyret rule reference
|
|
2
|
+
|
|
3
|
+
Core 6.3.2; language 2026-09-18.
|
|
4
|
+
|
|
5
|
+
Import `pyret/spytial.arr` as `S`. Constructors return `S.SpytialRule` automatically.
|
|
6
|
+
Enum values are qualified through `S`, for example `S.below` and `S.horizontal`.
|
|
7
|
+
Optional fields use Pyret records with named keys. Pass the record to `<rule>-with`;
|
|
8
|
+
`atom-style(selector, style)` and `edge-style(field, style)` take one directly.
|
|
9
|
+
Nested style blocks are typed constructors such as `S.fill-style({color: "red"})`.
|
|
10
|
+
Unset fields are omitted from YAML; Spytial Core supplies their defaults.
|
|
11
|
+
|
|
12
|
+
Numeric bounds, patterns and incompatible direction combinations are checked during serialization.
|
|
13
|
+
|
|
14
|
+
| Constructor | Optional fields |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `orientation(selector :: String, directions :: List<Direction>)` | `hold: Hold`, `source: RuleSource` |
|
|
17
|
+
| `cyclic(selector :: String)` | `direction: Rotation`, `hold: Hold`, `source: RuleSource` |
|
|
18
|
+
| `align(selector :: String, direction :: Alignment)` | `hold: Hold`, `source: RuleSource` |
|
|
19
|
+
| `group(selector :: String, name :: String)` | `add-edge: GroupAddEdge`, `show-label: Boolean`, `text-style: TextStyle`, `hold: Hold`, `source: RuleSource` |
|
|
20
|
+
| `size(width :: Number, height :: Number)` | `selector: String`, `source: RuleSource` |
|
|
21
|
+
| `hide-atom(selector :: String)` | `source: RuleSource` |
|
|
22
|
+
| `flag(name :: LayoutFlag)` | |
|
|
23
|
+
| `atom-style(selector :: String, style :: Any)` | A record with optional `fill-style`, `border-style`, `icon-style`, `text-style`, `show-label`, and `source` keys. |
|
|
24
|
+
| `edge-style(field :: String, style :: Any)` | A record with optional `selector`, `filter`, `line-style`, `text-style`, `show-label`, `hidden`, and `source` keys. |
|
|
25
|
+
| `attribute(field :: String)` | `selector: String`, `filter: String`, `text-style: TextStyle`, `source: RuleSource` |
|
|
26
|
+
| `tag(to-tag :: String, name :: String, value :: String)` | `text-style: TextStyle`, `source: RuleSource` |
|
|
27
|
+
| `hide-field(field :: String)` | `selector: String`, `filter: String`, `source: RuleSource` |
|
|
28
|
+
| `inferred-edge(name :: String, selector :: String)` | `draw: String`, `line-style: LineStyle`, `text-style: TextStyle`, `color: String`, `style: LinePattern`, `weight: Number`, `highlight: String`, `source: RuleSource` |
|
|
29
|
+
| `icon(selector :: String, path :: String)` (deprecated; use atom-style) | `show-labels: Boolean`, `source: RuleSource` |
|
|
30
|
+
| `atom-color(value :: String, selector :: String)` (deprecated; use atom-style) | `source: RuleSource` |
|
|
31
|
+
| `edge-color(field :: String, value :: String)` (deprecated; use edge-style) | `selector: String`, `filter: String`, `style: LinePattern`, `weight: Number`, `highlight: String`, `show-label: Boolean`, `hidden: Boolean`, `source: RuleSource` |
|
|
32
|
+
|
|
33
|
+
## Enum values
|
|
34
|
+
|
|
35
|
+
- `TextSize`: `small`, `normal`, `large`
|
|
36
|
+
- `LinePattern`: `solid`, `dashed`, `dotted`
|
|
37
|
+
- `IconPlacement`: `full`, `badge`
|
|
38
|
+
- `Direction`: `above`, `below`, `left`, `right`, `directly-above`, `directly-below`, `directly-left`, `directly-right`
|
|
39
|
+
- `Hold`: `always`, `never`
|
|
40
|
+
- `Rotation`: `clockwise`, `counterclockwise`
|
|
41
|
+
- `Alignment`: `horizontal`, `vertical`
|
|
42
|
+
- `GroupEdgeDirection`: `no-group-edge`, `togroup`, `fromgroup`
|
|
43
|
+
- `LayoutFlag`: `hide-disconnected`, `hide-disconnected-built-ins`
|
|
44
|
+
|
|
45
|
+
## Blocks
|
|
46
|
+
|
|
47
|
+
- `text-style({size: TextSize?, color: String?})`
|
|
48
|
+
- `line-style({color: String?, pattern: LinePattern?, weight: Number?, highlight: String?})`
|
|
49
|
+
- `fill-style({color: String?})`
|
|
50
|
+
- `border-style({color: String?, width: Number?})`
|
|
51
|
+
- `icon-style({path: String?, placement: IconPlacement?, opacity: Number?})`
|
|
52
|
+
- `rule-source({text: String, location: String?})`
|
|
53
|
+
- `group-add-edge({points: GroupEdgeDirection?, line-style: LineStyle?, text-style: TextStyle?})`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spyret",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Headless interfaces between standard Pyret and Spytial",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Siddhartha Prasad",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"files": [
|
|
15
15
|
"dist",
|
|
16
16
|
"docs",
|
|
17
|
-
"THIRD_PARTY_NOTICES.txt"
|
|
17
|
+
"THIRD_PARTY_NOTICES.txt",
|
|
18
|
+
"pyret"
|
|
18
19
|
],
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
@@ -27,31 +28,51 @@
|
|
|
27
28
|
"default": "./dist/spyret.js"
|
|
28
29
|
}
|
|
29
30
|
},
|
|
30
|
-
"./global": "./dist/spyret.global.js"
|
|
31
|
+
"./global": "./dist/spyret.global.js",
|
|
32
|
+
"./spytial.arr": "./pyret/spytial.arr",
|
|
33
|
+
"./browser": {
|
|
34
|
+
"import": {
|
|
35
|
+
"types": "./dist/spyret-browser.d.mts",
|
|
36
|
+
"default": "./dist/spyret-browser.mjs"
|
|
37
|
+
},
|
|
38
|
+
"require": {
|
|
39
|
+
"types": "./dist/spyret-browser.d.ts",
|
|
40
|
+
"default": "./dist/spyret-browser.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"./browser/amd": "./dist/spyret-browser.amd.js",
|
|
44
|
+
"./pyret-module": "./dist/spyret.pyret.js"
|
|
31
45
|
},
|
|
32
46
|
"scripts": {
|
|
33
|
-
"build": "tsup && node scripts/write-notices.mjs",
|
|
47
|
+
"build": "npm run check:spytial && tsup && node scripts/write-pyret-module.mjs && node scripts/write-notices.mjs",
|
|
34
48
|
"typecheck": "tsc --noEmit",
|
|
35
49
|
"test": "vitest run",
|
|
36
50
|
"test:upstream": "node scripts/check-pyret-capture.mjs",
|
|
37
51
|
"test:program": "node scripts/check-pyret-capture-program.mjs",
|
|
38
52
|
"test:pbt": "node scripts/check-pyret-pbt.mjs",
|
|
39
53
|
"prepack": "npm run typecheck && npm run build",
|
|
40
|
-
"test:package": "npm run build && node scripts/check-package.mjs"
|
|
54
|
+
"test:package": "npm run build && node scripts/check-package.mjs",
|
|
55
|
+
"generate:spytial": "node scripts/generate-spytial.mjs",
|
|
56
|
+
"check:spytial": "node scripts/generate-spytial.mjs --check",
|
|
57
|
+
"test:spytial": "node scripts/check-spytial-program.mjs",
|
|
58
|
+
"make:drive-wrapper": "node scripts/make-drive-wrapper.mjs",
|
|
59
|
+
"release:drive": "node scripts/prepare-drive-release.mjs"
|
|
41
60
|
},
|
|
42
61
|
"engines": {
|
|
43
62
|
"node": ">=22"
|
|
44
63
|
},
|
|
45
64
|
"devDependencies": {
|
|
65
|
+
"@types/js-yaml": "4.0.9",
|
|
46
66
|
"@types/node": "^24.0.3",
|
|
47
67
|
"fast-check": "^4.6.0",
|
|
68
|
+
"graphlib": "^2.1.8",
|
|
69
|
+
"js-yaml": "4.1.0",
|
|
48
70
|
"jsdom": "^26.1.0",
|
|
71
|
+
"spytial-core": "6.3.2",
|
|
49
72
|
"tsup": "^8.5.0",
|
|
50
73
|
"tsx": "^4.21.0",
|
|
51
74
|
"typescript": "^5.8.3",
|
|
52
|
-
"vitest": "^3.2.4"
|
|
53
|
-
"spytial-core": "6.3.1",
|
|
54
|
-
"graphlib": "^2.1.8"
|
|
75
|
+
"vitest": "^3.2.4"
|
|
55
76
|
},
|
|
56
77
|
"publishConfig": {
|
|
57
78
|
"access": "public",
|