@canonical/anatomy-dsl 0.2.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 +187 -5
- package/definitions/ontology.ttl +98 -4
- package/definitions/shapes.ttl +136 -5
- package/package.json +2 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/parse.js +0 -72
- package/dist/esm/transform.js +0 -116
- package/dist/esm/types.js +0 -1
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -2
- package/dist/parse.d.ts +0 -2
- package/dist/parse.js +0 -72
- package/dist/transform.d.ts +0 -2
- package/dist/transform.js +0 -114
- package/dist/types/index.d.ts +0 -3
- package/dist/types/parse.d.ts +0 -2
- package/dist/types/transform.d.ts +0 -2
- package/dist/types/types.d.ts +0 -36
- package/dist/types.d.ts +0 -36
- package/dist/types.js +0 -1
package/README.md
CHANGED
|
@@ -30,6 +30,175 @@ node:
|
|
|
30
30
|
|
|
31
31
|
Style values are **design token paths** (`spacing/medium`, `color/surface/button`) — forward-slash delimited references resolved at runtime against the active theme. Primitives like `flow` and `center` are used for layout semantics that don't vary across themes.
|
|
32
32
|
|
|
33
|
+
## Projections
|
|
34
|
+
|
|
35
|
+
Projections bind the anatomy tree directly to graph data, after the Relay
|
|
36
|
+
fragment-colocation pattern: the tree carries its data requirements the way a
|
|
37
|
+
lens component carries its fragment.
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
---
|
|
41
|
+
node:
|
|
42
|
+
uri: global.component.entity-card
|
|
43
|
+
projection:
|
|
44
|
+
on: Component # type condition — the tree is a view over one Component
|
|
45
|
+
edges:
|
|
46
|
+
- node:
|
|
47
|
+
uri: global.subcomponent.entity-card-header
|
|
48
|
+
projection:
|
|
49
|
+
field: _meta.title # this node renders the entity's title
|
|
50
|
+
relation:
|
|
51
|
+
cardinality: "1"
|
|
52
|
+
slotName: header
|
|
53
|
+
- node:
|
|
54
|
+
uri: global.component.chip
|
|
55
|
+
projection:
|
|
56
|
+
field: _meta.title # relative to the traversed Tag
|
|
57
|
+
relation:
|
|
58
|
+
cardinality: "0..*"
|
|
59
|
+
slotName: tags
|
|
60
|
+
projection:
|
|
61
|
+
field: documentationStages # traversal populating the slot
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Semantics:
|
|
65
|
+
|
|
66
|
+
- The **root node's** `projection.on` establishes the data context — the anatomy
|
|
67
|
+
is a parameterized view over one entity of that GraphQL type, like
|
|
68
|
+
`fragment EntityCard on Component`. Children inherit the context.
|
|
69
|
+
- `projection.field` on a **relation** is a traversal: the slot is populated
|
|
70
|
+
from that field of the current context. The DSL names the field only —
|
|
71
|
+
never `edges.node`. Anatomy cardinality ↔ graph multiplicity is the Relay
|
|
72
|
+
pattern taken one level further.
|
|
73
|
+
- **Cardinality decomposes against the schema.** The upper bound claims
|
|
74
|
+
multiplicity: `..1` maps to an object or scalar field, `..*` to a
|
|
75
|
+
connection or list. The lower bound claims nullability: `0..` tolerates
|
|
76
|
+
`null`, `1..` requires the provider to always have the value. So
|
|
77
|
+
`field: _meta.title` may sit under `cardinality: "1"` (title is total in
|
|
78
|
+
the contract), but a nullable field like `summary` must sit under `0..1`.
|
|
79
|
+
`1..*` asserts a non-empty list — deliberately stronger than GraphQL can
|
|
80
|
+
express, and checkable only at runtime.
|
|
81
|
+
- **Mechanism-blindness.** Whether a plural field is a Relay connection
|
|
82
|
+
(`subcomponents`) or a plain list (`properties`) is a provider mechanism,
|
|
83
|
+
not an anatomy fact. Consumers discover the shape from the SDL and unwrap
|
|
84
|
+
`edges { node }` when needed; an anatomy survives a provider promoting a
|
|
85
|
+
list to a connection unchanged.
|
|
86
|
+
- `projection.on` on a **child node** narrows the traversed entity's type — the
|
|
87
|
+
analog of an inline fragment. Switch cases carrying different `on` values
|
|
88
|
+
mirror an interface resolved through inline fragments.
|
|
89
|
+
- `projection.field` on a **node** means the node renders that field's value,
|
|
90
|
+
as a dot-delimited path relative to the enclosing context (`_meta.title`).
|
|
91
|
+
- A node projection needs at least one of `on` / `field`; a relation projection
|
|
92
|
+
requires `field` and admits no type condition (narrowing belongs on the
|
|
93
|
+
child node).
|
|
94
|
+
|
|
95
|
+
Field names are anchored to the provider schema the docsite runs against
|
|
96
|
+
(compiled with `prefixing: "none"`); the committed SDL is the naming authority.
|
|
97
|
+
|
|
98
|
+
A fully projected anatomy **derives a GraphQL fragment** mechanically — the
|
|
99
|
+
example above reads as:
|
|
100
|
+
|
|
101
|
+
```graphql
|
|
102
|
+
fragment EntityCardAnatomy on Component {
|
|
103
|
+
_meta { title } # header node
|
|
104
|
+
documentationStages { # tags relation (connection per SDL)
|
|
105
|
+
edges { node { _meta { title } } } # each chip
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The derivation rules (type conditions, dot-path expansion, connection
|
|
111
|
+
unwrapping, inline fragments) are specified in the API reference §3.10
|
|
112
|
+
*Derived fragment*; a worked gallery of every projection form is in §13, and
|
|
113
|
+
`examples/yaml/entity-card.anatomy.yaml` is the golden example.
|
|
114
|
+
|
|
115
|
+
## Props (pinned values)
|
|
116
|
+
|
|
117
|
+
A named node can **pin** props of the component it references — fixing a prop
|
|
118
|
+
value at one tree position:
|
|
119
|
+
|
|
120
|
+
```yaml
|
|
121
|
+
node:
|
|
122
|
+
uri: global.component.icon
|
|
123
|
+
props:
|
|
124
|
+
icon: chevron-down
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
This is how icons become idiomatic with zero icon-specific machinery. The
|
|
128
|
+
design system models the icon as a component whose glyph is a required prop
|
|
129
|
+
(`ds:global.component.icon` › `ds:hasProperty [ ds:name "icon" ]`), so icon
|
|
130
|
+
usage in anatomies splits into exactly two cases:
|
|
131
|
+
|
|
132
|
+
- **Consumer-filled icon slot** — an icon-component edge with a slot and no
|
|
133
|
+
pin. The consumer chooses the glyph; the anatomy correctly says nothing.
|
|
134
|
+
- **Component-intrinsic icon** — the accordion chevron, the modal close ×, a
|
|
135
|
+
status glyph: the component's own spec fixes the glyph, and the anatomy
|
|
136
|
+
pins it.
|
|
137
|
+
|
|
138
|
+
Semantics:
|
|
139
|
+
|
|
140
|
+
- Pins live on **named nodes only** — anonymous nodes have no prop surface.
|
|
141
|
+
This is enforced in the types, the parser, the JSON Schema, and SHACL.
|
|
142
|
+
- The DSL **never defines a prop surface** (names, types, optionality live in
|
|
143
|
+
the design system ontology); it only asserts values. Whether a pinned prop
|
|
144
|
+
exists on the component, and whether a value is admissible (e.g. a glyph
|
|
145
|
+
name in the icon set), are consumer-side checks against the DS graph — the
|
|
146
|
+
same posture as projection checking against the provider SDL.
|
|
147
|
+
- Values are scalars, coerced to strings — no token paths or fallback arrays.
|
|
148
|
+
Pins are *values with meaning*, not styles: a theme may reskin what
|
|
149
|
+
`chevron-down` looks like (asset layer), but never remap which glyph an
|
|
150
|
+
anatomy means.
|
|
151
|
+
- A **data-driven** value is a projection (`projection: { field: … }`), and a
|
|
152
|
+
**state-driven** one is a `switch` — pinned props are static by design.
|
|
153
|
+
|
|
154
|
+
In TTL, pins reify like styles do: `hasProp [ a :Prop ; propName "…" ;
|
|
155
|
+
propValue "…" ]`. See `examples/yaml/status-header.anatomy.yaml` for
|
|
156
|
+
intrinsic icons, a status-glyph switch, and an unpinned consumer slot in one
|
|
157
|
+
anatomy.
|
|
158
|
+
|
|
159
|
+
## Interaction states
|
|
160
|
+
|
|
161
|
+
Interaction states re-value style channels — they never add structure. A
|
|
162
|
+
style key takes an `@state` suffix scoping its value to a state; the unmarked
|
|
163
|
+
key is the default state:
|
|
164
|
+
|
|
165
|
+
```yaml
|
|
166
|
+
styles:
|
|
167
|
+
interaction.cursor: pointer
|
|
168
|
+
interaction.cursor@disabled: not-allowed
|
|
169
|
+
appearance.background: color/fill/default
|
|
170
|
+
appearance.background@hover: color/fill/default/hover
|
|
171
|
+
appearance.background@disabled: color/fill/default/disabled
|
|
172
|
+
appearance.outline@focus: color/focus-ring # exists only in a state
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The state vocabulary is **closed and registry-governed**: `hover`, `active`,
|
|
176
|
+
`focus`, `disabled`, `selected`. Naming follows the industry consensus where
|
|
177
|
+
systems diverge — `active` subsumes Material's *pressed* and Spectrum's
|
|
178
|
+
*down* (and matches Canonical's own token tree); `focus` maps to CSS
|
|
179
|
+
`:focus-visible` (Spectrum's *key-focus*). `@default` is invalid — absence is
|
|
180
|
+
the default. Candidate additions (`checked`, `visited`, `dragged`, `pending`,
|
|
181
|
+
`error`, `read-only`) go through the registry, never by loosening the schema.
|
|
182
|
+
|
|
183
|
+
The boundaries that keep "state machines out of scope" true:
|
|
184
|
+
|
|
185
|
+
- **States hold style values only.** A state that changes the tree is not a
|
|
186
|
+
state — it is a `switch on: internal` case (async-button's
|
|
187
|
+
idle/loading/success/error). The DSL declares appearance *per* state, never
|
|
188
|
+
transitions, triggers, or logic.
|
|
189
|
+
- **Gate vs appearance**: `props: { disabled: true }` (or the consumer) puts
|
|
190
|
+
a node in the disabled state; `…@disabled` styles say how it looks there.
|
|
191
|
+
- The canonical state token is the base token path plus a state leaf segment
|
|
192
|
+
(`color/fill/default/hover`), matching the token tree. A consumer-side lint
|
|
193
|
+
checks that a state-leafed token value agrees with its key's `@state`.
|
|
194
|
+
- The grammar reserves repeatable markers for compound states
|
|
195
|
+
(`@selected@hover`, canonical order: value/control state before user-action
|
|
196
|
+
state); v1 permits a single `@`.
|
|
197
|
+
|
|
198
|
+
In TTL, the Style tuple gains one optional dimension:
|
|
199
|
+
`[ styleKey "appearance.background" ; styleState "hover" ; styleValue "…" ]`.
|
|
200
|
+
See `examples/yaml/stateful-button.anatomy.yaml`.
|
|
201
|
+
|
|
33
202
|
## Install
|
|
34
203
|
|
|
35
204
|
```sh
|
|
@@ -111,35 +280,48 @@ All types mirror the [OWL ontology](definitions/ontology.ttl) exactly:
|
|
|
111
280
|
| `Node` | `NamedNode \| AnonymousNode` (discriminated on `type`) |
|
|
112
281
|
| `Edge` | Reified parent→child relationship |
|
|
113
282
|
| `Relation` | Cardinality and optional slot name |
|
|
114
|
-
| `Style` | Reified key-value tuple |
|
|
283
|
+
| `Style` | Reified key-value tuple, with an optional interaction `state` dimension |
|
|
115
284
|
| `Switch` | Polymorphic position (discriminator: `props \| internal \| override`) |
|
|
116
285
|
| `SwitchCase` | One alternative within a switch |
|
|
286
|
+
| `Projection` | Fragment-style graph binding on a node (`on` type condition and/or `field` path) |
|
|
287
|
+
| `RelationProjection` | Traversal populating a slot (`field` required) |
|
|
288
|
+
| `Prop` | Pinned prop value on a named node (reified name-value tuple) |
|
|
117
289
|
|
|
118
290
|
## Repository Structure
|
|
119
291
|
|
|
120
292
|
```
|
|
121
293
|
definitions/ Turtle ontology (OWL) + SHACL shapes
|
|
122
294
|
schemas/ JSON Schema for validating .anatomy.yaml files
|
|
123
|
-
docs/ API reference (
|
|
295
|
+
docs/ API reference (WD404 + WD404.1 + WD404.2 + WD404.3)
|
|
124
296
|
examples/ Example anatomy files (YAML + Turtle pairs)
|
|
125
297
|
src/ TypeScript types, parser, and transform
|
|
126
298
|
```
|
|
127
299
|
|
|
128
300
|
## Scope
|
|
129
301
|
|
|
130
|
-
The Anatomy DSL describes **structure
|
|
302
|
+
The Anatomy DSL describes **structure**, **graph-data bindings** (projections
|
|
303
|
+
— what data each position renders), **pinned prop values** (props — fixed
|
|
304
|
+
component configuration at a position), and **state-scoped styles**
|
|
305
|
+
(interaction states — how channels re-value per state). It does not handle:
|
|
131
306
|
|
|
132
|
-
- **Prop
|
|
133
|
-
|
|
307
|
+
- **Prop surface definition** — which props a component accepts, their types
|
|
308
|
+
and optionality live in the design system ontology; the DSL only pins values
|
|
309
|
+
- **State machines** — transitions, triggers, and interaction logic; the DSL
|
|
310
|
+
declares appearance per state only, and structural state variation is the
|
|
311
|
+
switch construct's job
|
|
134
312
|
- **Modifier descriptions** — only design token references are supported, not semantic modifier definitions
|
|
135
313
|
|
|
136
314
|
## Design Notes
|
|
137
315
|
|
|
138
316
|
Styles are modelled as reified key-value tuples (`hasStyle [ styleKey "…" ; styleValue "…" ]`). This keeps the ontology open-ended while remaining lossless. Frequently used style keys may be promoted to first-class datatype properties in a future version.
|
|
139
317
|
|
|
318
|
+
Projections and pinned props reuse the same reification idiom (`hasProjection [ projectionType "…" ; projectionField "…" ]`, `hasProp [ propName "…" ; propValue "…" ]`). Projections attach to both nodes and relations — the reified `Relation` is precisely what makes slot-level traversal annotations possible without changing the `Edge` class; pins attach to named nodes only.
|
|
319
|
+
|
|
140
320
|
## Specification Status
|
|
141
321
|
|
|
142
322
|
| Index | Title | Status |
|
|
143
323
|
|---------|--------------------------|----------------|
|
|
144
324
|
| [WD404](https://docs.google.com/document/d/1eFr-SNsAZyidnZzpWp1Jeegiat_SSM7mOW_G8p3nXo8/edit?tab=t.pndvuecem8cf) | Anatomy DSL | Approved |
|
|
145
325
|
| [WD404.1](https://docs.google.com/document/d/1eFr-SNsAZyidnZzpWp1Jeegiat_SSM7mOW_G8p3nXo8/edit?tab=t.pndvuecem8cf) | Anatomy DSL — Addendum 1 | Pending Review |
|
|
326
|
+
| WD404.2 | Anatomy DSL — Projections | Draft (this repository) |
|
|
327
|
+
| WD404.3 | Anatomy DSL — Prop pinning | Draft (this repository) |
|
package/definitions/ontology.ttl
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
|
|
5
5
|
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
6
6
|
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
7
|
+
@prefix graphql: <https://pragma.canonical.com/graphql#> .
|
|
7
8
|
|
|
8
9
|
# ═══════════════════════════════════════════════════════════════
|
|
9
10
|
# ONTOLOGY DECLARATION
|
|
10
11
|
# ═══════════════════════════════════════════════════════════════
|
|
11
12
|
|
|
12
13
|
<http://anatomy-dsl.example.org/ontology> a owl:Ontology ;
|
|
13
|
-
owl:versionInfo "0.2
|
|
14
|
+
owl:versionInfo "0.3.2" ;
|
|
14
15
|
rdfs:label "Anatomy DSL Ontology" ;
|
|
15
16
|
rdfs:comment "Compliant with OWL DL profile" ;
|
|
16
17
|
skos:definition """Meta-model for the Anatomy DSL — a YAML-based language
|
|
@@ -19,11 +20,20 @@
|
|
|
19
20
|
Models anatomy files as trees of nodes connected by reified edges.
|
|
20
21
|
Inspired by the Relay GraphQL connection pattern.
|
|
21
22
|
|
|
22
|
-
Consolidates WD404 (core)
|
|
23
|
+
Consolidates WD404 (core), WD404.1 (addendum 1), projections
|
|
24
|
+
(addendum 2): fragment-style bindings of nodes and relations to
|
|
25
|
+
graph data, prop pinning (addendum 3): fixed prop values on
|
|
26
|
+
referenced components, and interaction states (addendum 4):
|
|
27
|
+
state-scoped style values.
|
|
23
28
|
|
|
24
29
|
OUT OF SCOPE (matching the DSL itself):
|
|
25
|
-
- Prop
|
|
26
|
-
|
|
30
|
+
- Prop surface definition — names, types, and optionality of a
|
|
31
|
+
component's props live in the design system ontology. Pinned prop
|
|
32
|
+
VALUES are in scope as of addendum 3.
|
|
33
|
+
- State MACHINES — transitions, triggers, and interaction logic.
|
|
34
|
+
Appearance PER interaction state is in scope as of addendum 4
|
|
35
|
+
(styleState); structural state variation remains the Switch
|
|
36
|
+
construct's job.
|
|
27
37
|
- Modifier descriptions (only token references)
|
|
28
38
|
|
|
29
39
|
Validation shapes defined in shapes.ttl.""" .
|
|
@@ -62,6 +72,23 @@ anatomy:Style a owl:Class ;
|
|
|
62
72
|
rdfs:label "Style" ;
|
|
63
73
|
skos:definition "A single style declaration binding a property key to a value." .
|
|
64
74
|
|
|
75
|
+
anatomy:Projection a owl:Class ;
|
|
76
|
+
rdfs:label "Projection" ;
|
|
77
|
+
skos:definition """Fragment-style binding to graph data, after the Relay
|
|
78
|
+
colocation pattern. On a node it declares a type condition (like a
|
|
79
|
+
fragment's 'on') and/or the field the node renders; on a relation it
|
|
80
|
+
declares the traversal that populates the slot.""" .
|
|
81
|
+
|
|
82
|
+
anatomy:Prop a owl:Class ;
|
|
83
|
+
rdfs:label "Prop" ;
|
|
84
|
+
skos:definition """A pinned property value: the anatomy fixes one prop of
|
|
85
|
+
the referenced component at this tree position (e.g. the icon component's
|
|
86
|
+
'icon' prop pinned to 'chevron-down' for an intrinsic chevron). References
|
|
87
|
+
a property defined in the design system ontology — the DSL never defines
|
|
88
|
+
the prop surface itself. Whether the pinned prop exists on the component,
|
|
89
|
+
and whether the value is admissible, is validated by consumers holding
|
|
90
|
+
the design system graph.""" .
|
|
91
|
+
|
|
65
92
|
# ═══════════════════════════════════════════════════════════════
|
|
66
93
|
# SWITCH CONSTRUCT
|
|
67
94
|
# ═══════════════════════════════════════════════════════════════
|
|
@@ -126,12 +153,31 @@ anatomy:hasStyle a owl:ObjectProperty ;
|
|
|
126
153
|
rdfs:range anatomy:Style ;
|
|
127
154
|
skos:definition "Attaches a style declaration to a node." .
|
|
128
155
|
|
|
156
|
+
anatomy:hasProjection a owl:ObjectProperty ;
|
|
157
|
+
rdfs:label "has projection" ;
|
|
158
|
+
rdfs:domain [ a owl:Class ; owl:unionOf ( anatomy:Node anatomy:Relation ) ] ;
|
|
159
|
+
rdfs:range anatomy:Projection ;
|
|
160
|
+
skos:definition "Attaches a graph-data projection to a node or relation." .
|
|
161
|
+
|
|
162
|
+
anatomy:hasProp a owl:ObjectProperty ;
|
|
163
|
+
rdfs:label "has prop" ;
|
|
164
|
+
rdfs:domain anatomy:NamedNode ;
|
|
165
|
+
rdfs:range anatomy:Prop ;
|
|
166
|
+
skos:definition "Attaches a pinned prop value to a named node. Named nodes only: anonymous nodes have no prop surface to pin." .
|
|
167
|
+
|
|
129
168
|
# ═══════════════════════════════════════════════════════════════
|
|
130
169
|
# DATATYPE PROPERTIES
|
|
131
170
|
# ═══════════════════════════════════════════════════════════════
|
|
132
171
|
|
|
133
172
|
anatomy:uri a owl:DatatypeProperty ;
|
|
134
173
|
rdfs:label "uri" ;
|
|
174
|
+
# `uri` is the structural primary key of every node in a GraphQL projection
|
|
175
|
+
# of this ontology (one absolute IRI per entity), so an ontology property of
|
|
176
|
+
# the same name collides with it. The projection names this one
|
|
177
|
+
# `anatomyUri`; the RDF term is unchanged and every SPARQL query keeps
|
|
178
|
+
# working. See canonical/pragma-adrs session/B (ruling R-4: a collision is
|
|
179
|
+
# an error, never a silent rename).
|
|
180
|
+
graphql:name "anatomyUri" ;
|
|
135
181
|
rdfs:domain anatomy:NamedNode ;
|
|
136
182
|
rdfs:range xsd:string ;
|
|
137
183
|
skos:definition "Unique identifier for a named node within the design system." ;
|
|
@@ -188,3 +234,51 @@ anatomy:styleValue a owl:DatatypeProperty ;
|
|
|
188
234
|
"spacing/medium" ,
|
|
189
235
|
"color/surface/primary" ,
|
|
190
236
|
"color/surface/button?" .
|
|
237
|
+
|
|
238
|
+
anatomy:styleState a owl:DatatypeProperty ;
|
|
239
|
+
rdfs:label "style state" ;
|
|
240
|
+
rdfs:domain anatomy:Style ;
|
|
241
|
+
rdfs:range xsd:string ;
|
|
242
|
+
skos:definition """Interaction state this style value applies in. Absent
|
|
243
|
+
means the default state. Closed vocabulary (hover, active, focus,
|
|
244
|
+
disabled, selected), governed by registry — 'active' subsumes Material's
|
|
245
|
+
'pressed' and Spectrum's 'down'; 'focus' maps to CSS :focus-visible
|
|
246
|
+
(Spectrum's 'key-focus'). States re-value style channels only: they never
|
|
247
|
+
add structure (that is the Switch construct) and never define transitions
|
|
248
|
+
or interaction logic (state machines stay out of scope). Authored as an
|
|
249
|
+
@state suffix on the style key.""" ;
|
|
250
|
+
skos:example "hover" , "active" , "focus" , "disabled" , "selected" .
|
|
251
|
+
|
|
252
|
+
anatomy:projectionType a owl:DatatypeProperty ;
|
|
253
|
+
rdfs:label "projection type" ;
|
|
254
|
+
rdfs:domain anatomy:Projection ;
|
|
255
|
+
rdfs:range xsd:string ;
|
|
256
|
+
skos:definition "GraphQL type condition, like a Relay fragment's 'on'. On a root node it establishes the data context for the tree; on a child it narrows the traversed entity's type, like an inline fragment." ;
|
|
257
|
+
skos:example "Component" ,
|
|
258
|
+
"OntologyClass" ,
|
|
259
|
+
"Pattern" .
|
|
260
|
+
|
|
261
|
+
anatomy:propName a owl:DatatypeProperty ;
|
|
262
|
+
rdfs:label "prop name" ;
|
|
263
|
+
rdfs:domain anatomy:Prop ;
|
|
264
|
+
rdfs:range xsd:string ;
|
|
265
|
+
skos:definition "Name of the pinned prop, as defined on the referenced component in the design system ontology." ;
|
|
266
|
+
skos:example "icon" .
|
|
267
|
+
|
|
268
|
+
anatomy:propValue a owl:DatatypeProperty ;
|
|
269
|
+
rdfs:label "prop value" ;
|
|
270
|
+
rdfs:domain anatomy:Prop ;
|
|
271
|
+
rdfs:range xsd:string ;
|
|
272
|
+
skos:definition "The fixed value the anatomy pins the prop to." ;
|
|
273
|
+
skos:example "chevron-down" ,
|
|
274
|
+
"checkmark" ,
|
|
275
|
+
"spinner" .
|
|
276
|
+
|
|
277
|
+
anatomy:projectionField a owl:DatatypeProperty ;
|
|
278
|
+
rdfs:label "projection field" ;
|
|
279
|
+
rdfs:domain anatomy:Projection ;
|
|
280
|
+
rdfs:range xsd:string ;
|
|
281
|
+
skos:definition "Dot-delimited GraphQL field path relative to the enclosing data context. On a node: the field the node renders. On a relation: the traversal populating the slot. Names the field only, never an unwrapping path like edges/node — whether a plural field is a connection or a plain list is a provider mechanism discovered from the schema. The relation's cardinality maps onto the field's shape: upper bound to multiplicity, lower bound to nullability." ;
|
|
282
|
+
skos:example "_meta.title" ,
|
|
283
|
+
"summary" ,
|
|
284
|
+
"documentationStages" .
|
package/definitions/shapes.ttl
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
|
|
6
6
|
|
|
7
7
|
# ═══════════════════════════════════════════════════════════════
|
|
8
|
-
# SHACL SHAPES FOR ANATOMY DSL 0.2
|
|
8
|
+
# SHACL SHAPES FOR ANATOMY DSL 0.3.2
|
|
9
9
|
# ═══════════════════════════════════════════════════════════════
|
|
10
10
|
#
|
|
11
11
|
# Constraints, patterns, enumerations, and encoding conventions.
|
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
#
|
|
14
14
|
# Shapes:
|
|
15
15
|
# SpecificationShape — root document
|
|
16
|
-
# NamedNodeShape — uri required, encoding pattern
|
|
17
|
-
# AnonymousNodeShape — role required
|
|
16
|
+
# NamedNodeShape — uri required, encoding pattern, props allowed
|
|
17
|
+
# AnonymousNodeShape — role required, props forbidden
|
|
18
18
|
# EdgeShape — edgeTarget XOR edgeSwitch
|
|
19
|
-
# RelationShape — cardinality pattern, optional slotName
|
|
19
|
+
# RelationShape — cardinality pattern, optional slotName,
|
|
20
|
+
# optional traversal projection (field required)
|
|
20
21
|
# SwitchShape — discriminator enum, minimum cases
|
|
21
22
|
# SwitchCaseShape — case node required
|
|
22
|
-
# StyleShape — reified key-value tuple (one key, one value
|
|
23
|
+
# StyleShape — reified key-value tuple (one key, one value,
|
|
24
|
+
# optional closed-vocabulary interaction state)
|
|
25
|
+
# ProjectionShape — at least one of projectionType/projectionField
|
|
26
|
+
# PropShape — reified name-value tuple (one name, one value)
|
|
23
27
|
# ═══════════════════════════════════════════════════════════════
|
|
24
28
|
|
|
25
29
|
# ───────────────────────────────────────────────────────────────
|
|
@@ -70,6 +74,19 @@ anatomy:NamedNodeShape a sh:NodeShape ;
|
|
|
70
74
|
sh:path anatomy:role ;
|
|
71
75
|
sh:maxCount 0 ;
|
|
72
76
|
sh:message "NamedNode must not have a role (use AnonymousNode instead)." ;
|
|
77
|
+
] ;
|
|
78
|
+
|
|
79
|
+
sh:property [
|
|
80
|
+
sh:path anatomy:hasProjection ;
|
|
81
|
+
sh:maxCount 1 ;
|
|
82
|
+
sh:class anatomy:Projection ;
|
|
83
|
+
sh:message "NamedNode admits at most one projection." ;
|
|
84
|
+
] ;
|
|
85
|
+
|
|
86
|
+
sh:property [
|
|
87
|
+
sh:path anatomy:hasProp ;
|
|
88
|
+
sh:class anatomy:Prop ;
|
|
89
|
+
sh:message "hasProp must point at a Prop." ;
|
|
73
90
|
] .
|
|
74
91
|
|
|
75
92
|
# ───────────────────────────────────────────────────────────────
|
|
@@ -94,6 +111,19 @@ anatomy:AnonymousNodeShape a sh:NodeShape ;
|
|
|
94
111
|
sh:path anatomy:uri ;
|
|
95
112
|
sh:maxCount 0 ;
|
|
96
113
|
sh:message "AnonymousNode must not have a uri (use NamedNode instead)." ;
|
|
114
|
+
] ;
|
|
115
|
+
|
|
116
|
+
sh:property [
|
|
117
|
+
sh:path anatomy:hasProjection ;
|
|
118
|
+
sh:maxCount 1 ;
|
|
119
|
+
sh:class anatomy:Projection ;
|
|
120
|
+
sh:message "AnonymousNode admits at most one projection." ;
|
|
121
|
+
] ;
|
|
122
|
+
|
|
123
|
+
sh:property [
|
|
124
|
+
sh:path anatomy:hasProp ;
|
|
125
|
+
sh:maxCount 0 ;
|
|
126
|
+
sh:message "Props belong to components: anonymous nodes have no prop surface to pin — use a NamedNode." ;
|
|
97
127
|
] .
|
|
98
128
|
|
|
99
129
|
# ───────────────────────────────────────────────────────────────
|
|
@@ -161,6 +191,28 @@ anatomy:RelationShape a sh:NodeShape ;
|
|
|
161
191
|
sh:path anatomy:slotName ;
|
|
162
192
|
sh:maxCount 1 ;
|
|
163
193
|
sh:datatype xsd:string ;
|
|
194
|
+
] ;
|
|
195
|
+
|
|
196
|
+
# A relation projection is a traversal: the field is mandatory.
|
|
197
|
+
# Type narrowing belongs on the child node, never on the relation.
|
|
198
|
+
sh:property [
|
|
199
|
+
sh:path anatomy:hasProjection ;
|
|
200
|
+
sh:maxCount 1 ;
|
|
201
|
+
sh:class anatomy:Projection ;
|
|
202
|
+
sh:node [
|
|
203
|
+
a sh:NodeShape ;
|
|
204
|
+
sh:property [
|
|
205
|
+
sh:path anatomy:projectionField ;
|
|
206
|
+
sh:minCount 1 ;
|
|
207
|
+
sh:message "Relation projections must name the traversal field." ;
|
|
208
|
+
] ;
|
|
209
|
+
sh:property [
|
|
210
|
+
sh:path anatomy:projectionType ;
|
|
211
|
+
sh:maxCount 0 ;
|
|
212
|
+
sh:message "Type narrowing lives on the child node, not the relation." ;
|
|
213
|
+
] ;
|
|
214
|
+
] ;
|
|
215
|
+
sh:message "Relation admits at most one projection, and it must be a traversal (projectionField, no projectionType)." ;
|
|
164
216
|
] .
|
|
165
217
|
|
|
166
218
|
# ───────────────────────────────────────────────────────────────
|
|
@@ -229,4 +281,83 @@ anatomy:StyleShape a sh:NodeShape ;
|
|
|
229
281
|
sh:maxCount 1 ;
|
|
230
282
|
sh:datatype xsd:string ;
|
|
231
283
|
sh:message "Style requires exactly one value." ;
|
|
284
|
+
] ;
|
|
285
|
+
|
|
286
|
+
# Closed v1 vocabulary, registry-governed. Additions go through the
|
|
287
|
+
# registry (checked, visited, dragged, pending, error, read-only are
|
|
288
|
+
# candidates), never through loosening this list ad hoc. There is no
|
|
289
|
+
# "default" state: an absent styleState IS the default.
|
|
290
|
+
sh:property [
|
|
291
|
+
sh:path anatomy:styleState ;
|
|
292
|
+
sh:maxCount 1 ;
|
|
293
|
+
sh:datatype xsd:string ;
|
|
294
|
+
sh:in ( "hover" "active" "focus" "disabled" "selected" ) ;
|
|
295
|
+
sh:message "styleState must be one of: hover, active, focus, disabled, selected (absent = default state)." ;
|
|
296
|
+
] .
|
|
297
|
+
|
|
298
|
+
# ───────────────────────────────────────────────────────────────
|
|
299
|
+
# PROJECTION
|
|
300
|
+
# ───────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
anatomy:ProjectionShape a sh:NodeShape ;
|
|
303
|
+
sh:targetClass anatomy:Projection ;
|
|
304
|
+
rdfs:label "Projection Shape" ;
|
|
305
|
+
skos:definition "Fragment-style graph binding: a type condition, a field path, or both — never neither." ;
|
|
306
|
+
|
|
307
|
+
sh:property [
|
|
308
|
+
sh:path anatomy:projectionType ;
|
|
309
|
+
sh:maxCount 1 ;
|
|
310
|
+
sh:datatype xsd:string ;
|
|
311
|
+
sh:pattern "^[A-Z][A-Za-z0-9_]*$" ;
|
|
312
|
+
sh:message "Projection type must be a GraphQL type name (PascalCase)." ;
|
|
313
|
+
] ;
|
|
314
|
+
|
|
315
|
+
sh:property [
|
|
316
|
+
sh:path anatomy:projectionField ;
|
|
317
|
+
sh:maxCount 1 ;
|
|
318
|
+
sh:datatype xsd:string ;
|
|
319
|
+
sh:pattern "^[_A-Za-z][_A-Za-z0-9]*(\\.[_A-Za-z][_A-Za-z0-9]*)*$" ;
|
|
320
|
+
sh:message "Projection field must be a dot-delimited path of GraphQL field names." ;
|
|
321
|
+
] ;
|
|
322
|
+
|
|
323
|
+
sh:or (
|
|
324
|
+
[
|
|
325
|
+
sh:property [
|
|
326
|
+
sh:path anatomy:projectionType ;
|
|
327
|
+
sh:minCount 1 ;
|
|
328
|
+
] ;
|
|
329
|
+
]
|
|
330
|
+
[
|
|
331
|
+
sh:property [
|
|
332
|
+
sh:path anatomy:projectionField ;
|
|
333
|
+
sh:minCount 1 ;
|
|
334
|
+
] ;
|
|
335
|
+
]
|
|
336
|
+
) ;
|
|
337
|
+
sh:message "Projection requires at least one of projectionType or projectionField." .
|
|
338
|
+
|
|
339
|
+
# ───────────────────────────────────────────────────────────────
|
|
340
|
+
# PROP
|
|
341
|
+
# ───────────────────────────────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
anatomy:PropShape a sh:NodeShape ;
|
|
344
|
+
sh:targetClass anatomy:Prop ;
|
|
345
|
+
rdfs:label "Prop Shape" ;
|
|
346
|
+
skos:definition "Reified pinned-prop tuple: exactly one name and one value. Whether the prop exists on the referenced component is validated by consumers holding the design system graph, not here." ;
|
|
347
|
+
|
|
348
|
+
sh:property [
|
|
349
|
+
sh:path anatomy:propName ;
|
|
350
|
+
sh:minCount 1 ;
|
|
351
|
+
sh:maxCount 1 ;
|
|
352
|
+
sh:datatype xsd:string ;
|
|
353
|
+
sh:pattern "^[a-z][a-zA-Z0-9]*$" ;
|
|
354
|
+
sh:message "Prop name must be a camelCase identifier as defined on the component." ;
|
|
355
|
+
] ;
|
|
356
|
+
|
|
357
|
+
sh:property [
|
|
358
|
+
sh:path anatomy:propValue ;
|
|
359
|
+
sh:minCount 1 ;
|
|
360
|
+
sh:maxCount 1 ;
|
|
361
|
+
sh:datatype xsd:string ;
|
|
362
|
+
sh:message "Prop requires exactly one value." ;
|
|
232
363
|
] .
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonical/anatomy-dsl",
|
|
3
3
|
"description": "Anatomy DSL meta-model: TypeScript types mirroring the OWL ontology, and a YAML-to-Turtle transform",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/types/index.d.ts",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"@biomejs/biome": "^2.4.6",
|
|
30
30
|
"@canonical/biome-config": "^0.17.1",
|
|
31
31
|
"@canonical/typescript-config": "^0.17.1",
|
|
32
|
+
"@types/node": "^26.4.0",
|
|
32
33
|
"expect-type": "^1.3.0",
|
|
33
34
|
"typescript": "^5.9.3",
|
|
34
35
|
"vitest": "^4.0.18",
|
package/dist/esm/index.js
DELETED
package/dist/esm/parse.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export function parseAnatomyYAML(raw) {
|
|
2
|
-
const doc = raw;
|
|
3
|
-
return {
|
|
4
|
-
root: toNamedNode(doc.node),
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
function toNamedNode(raw) {
|
|
8
|
-
return {
|
|
9
|
-
type: "named",
|
|
10
|
-
uri: raw.uri,
|
|
11
|
-
...(raw.styles ? { styles: toStyles(raw.styles) } : {}),
|
|
12
|
-
...(raw.edges ? { edges: raw.edges.map(toEdge) } : {}),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
function toNode(raw) {
|
|
16
|
-
if ("uri" in raw && raw.uri) {
|
|
17
|
-
return toNamedNode(raw);
|
|
18
|
-
}
|
|
19
|
-
const anon = raw;
|
|
20
|
-
return {
|
|
21
|
-
type: "anonymous",
|
|
22
|
-
role: anon.role,
|
|
23
|
-
...(anon.styles ? { styles: toStyles(anon.styles) } : {}),
|
|
24
|
-
...(anon.edges ? { edges: anon.edges.map(toEdge) } : {}),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function toStyles(raw) {
|
|
28
|
-
return Object.entries(raw).map(([key, value]) => ({
|
|
29
|
-
key,
|
|
30
|
-
value: String(value),
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
function toEdge(raw) {
|
|
34
|
-
let target;
|
|
35
|
-
if (raw.switch) {
|
|
36
|
-
target = toSwitch(raw.switch);
|
|
37
|
-
}
|
|
38
|
-
else if (raw.node) {
|
|
39
|
-
target = toNode(raw.node);
|
|
40
|
-
}
|
|
41
|
-
else if (raw.uri) {
|
|
42
|
-
target = { type: "named", uri: raw.uri };
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
throw new Error("Edge must have node, uri, or switch");
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
target,
|
|
49
|
-
relation: toRelation(raw.relation),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function toSwitch(raw) {
|
|
53
|
-
return {
|
|
54
|
-
discriminator: raw.on,
|
|
55
|
-
cases: raw.cases.map(toSwitchCase),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function toSwitchCase(raw) {
|
|
59
|
-
if (raw.uri !== undefined) {
|
|
60
|
-
const node = { type: "named", uri: raw.uri };
|
|
61
|
-
return { value: raw.uri, node };
|
|
62
|
-
}
|
|
63
|
-
const node = toNode(raw.node);
|
|
64
|
-
const value = node.type === "named" ? node.uri : node.role;
|
|
65
|
-
return { value, node };
|
|
66
|
-
}
|
|
67
|
-
function toRelation(raw) {
|
|
68
|
-
return {
|
|
69
|
-
cardinality: raw.cardinality,
|
|
70
|
-
...(raw.slotName ? { slotName: raw.slotName } : {}),
|
|
71
|
-
};
|
|
72
|
-
}
|
package/dist/esm/transform.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
const PREFIX = "@prefix : <http://anatomy-dsl.example.org/ontology#> .";
|
|
2
|
-
const INDENT = " ";
|
|
3
|
-
export function anatomyToTTL(spec) {
|
|
4
|
-
const lines = [PREFIX, ""];
|
|
5
|
-
lines.push("[] a :Specification ;");
|
|
6
|
-
lines.push(`${INDENT}:rootNode [`);
|
|
7
|
-
writeNode(lines, spec.root, 2);
|
|
8
|
-
lines.push(`${INDENT}] .`);
|
|
9
|
-
return `${lines.join("\n")}\n`;
|
|
10
|
-
}
|
|
11
|
-
function writeNode(lines, node, depth) {
|
|
12
|
-
if (node.type === "named") {
|
|
13
|
-
writeNamedNode(lines, node, depth);
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
writeAnonymousNode(lines, node, depth);
|
|
17
|
-
}
|
|
18
|
-
const styles = node.styles ?? [];
|
|
19
|
-
const edges = node.edges ?? [];
|
|
20
|
-
if (styles.length > 0) {
|
|
21
|
-
writeStyles(lines, styles, depth, edges.length === 0);
|
|
22
|
-
}
|
|
23
|
-
if (edges.length > 0) {
|
|
24
|
-
writeEdges(lines, edges, depth);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function writeNamedNode(lines, node, depth) {
|
|
28
|
-
const indent = INDENT.repeat(depth);
|
|
29
|
-
lines.push(`${indent}a :NamedNode ;`);
|
|
30
|
-
const hasMore = (node.styles && node.styles.length > 0) ||
|
|
31
|
-
(node.edges && node.edges.length > 0);
|
|
32
|
-
lines.push(`${indent}:uri "${node.uri}"${hasMore ? " ;" : ""}`);
|
|
33
|
-
}
|
|
34
|
-
function writeAnonymousNode(lines, node, depth) {
|
|
35
|
-
const indent = INDENT.repeat(depth);
|
|
36
|
-
lines.push(`${indent}a :AnonymousNode ;`);
|
|
37
|
-
const hasMore = (node.styles && node.styles.length > 0) ||
|
|
38
|
-
(node.edges && node.edges.length > 0);
|
|
39
|
-
lines.push(`${indent}:role "${node.role}"${hasMore ? " ;" : ""}`);
|
|
40
|
-
}
|
|
41
|
-
function writeStyles(lines, styles, depth, isLast) {
|
|
42
|
-
const indent = INDENT.repeat(depth);
|
|
43
|
-
const innerIndent = INDENT.repeat(depth + 1);
|
|
44
|
-
lines.push(`${indent}:hasStyle`);
|
|
45
|
-
for (const [i, style] of styles.entries()) {
|
|
46
|
-
const sep = i < styles.length - 1 ? " ," : isLast ? "" : " ;";
|
|
47
|
-
lines.push(`${innerIndent}[ :styleKey "${style.key}" ; :styleValue "${style.value}" ]${sep}`);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function writeEdges(lines, edges, depth) {
|
|
51
|
-
const indent = INDENT.repeat(depth);
|
|
52
|
-
for (const [i, edge] of edges.entries()) {
|
|
53
|
-
if (i === 0) {
|
|
54
|
-
lines.push(`${indent}:hasEdge [`);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
lines.push(`${indent}] , [`);
|
|
58
|
-
}
|
|
59
|
-
writeEdge(lines, edge, depth + 1);
|
|
60
|
-
}
|
|
61
|
-
lines.push(`${indent}]`);
|
|
62
|
-
}
|
|
63
|
-
function writeEdge(lines, edge, depth) {
|
|
64
|
-
const indent = INDENT.repeat(depth);
|
|
65
|
-
lines.push(`${indent}a :Edge ;`);
|
|
66
|
-
if (isSwitch(edge.target)) {
|
|
67
|
-
lines.push(`${indent}:edgeSwitch [`);
|
|
68
|
-
writeSwitch(lines, edge.target, depth + 1);
|
|
69
|
-
lines.push(`${indent}] ;`);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
lines.push(`${indent}:edgeTarget [`);
|
|
73
|
-
writeNode(lines, edge.target, depth + 1);
|
|
74
|
-
lines.push(`${indent}] ;`);
|
|
75
|
-
}
|
|
76
|
-
writeRelation(lines, edge, depth);
|
|
77
|
-
}
|
|
78
|
-
function writeSwitch(lines, sw, depth) {
|
|
79
|
-
const indent = INDENT.repeat(depth);
|
|
80
|
-
lines.push(`${indent}a :Switch ;`);
|
|
81
|
-
lines.push(`${indent}:discriminator "${sw.discriminator}" ;`);
|
|
82
|
-
for (const [i, sc] of sw.cases.entries()) {
|
|
83
|
-
if (i === 0) {
|
|
84
|
-
lines.push(`${indent}:hasCase [`);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
lines.push(`${indent}] , [`);
|
|
88
|
-
}
|
|
89
|
-
writeSwitchCase(lines, sc, depth + 1);
|
|
90
|
-
}
|
|
91
|
-
lines.push(`${indent}]`);
|
|
92
|
-
}
|
|
93
|
-
function writeSwitchCase(lines, sc, depth) {
|
|
94
|
-
const indent = INDENT.repeat(depth);
|
|
95
|
-
lines.push(`${indent}a :SwitchCase ;`);
|
|
96
|
-
lines.push(`${indent}:caseNode [`);
|
|
97
|
-
writeNode(lines, sc.node, depth + 1);
|
|
98
|
-
lines.push(`${indent}]`);
|
|
99
|
-
}
|
|
100
|
-
function writeRelation(lines, edge, depth) {
|
|
101
|
-
const indent = INDENT.repeat(depth);
|
|
102
|
-
lines.push(`${indent}:hasRelation [`);
|
|
103
|
-
const inner = INDENT.repeat(depth + 1);
|
|
104
|
-
lines.push(`${inner}a :Relation ;`);
|
|
105
|
-
if (edge.relation.slotName) {
|
|
106
|
-
lines.push(`${inner}:cardinality "${edge.relation.cardinality}" ;`);
|
|
107
|
-
lines.push(`${inner}:slotName "${edge.relation.slotName}"`);
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
lines.push(`${inner}:cardinality "${edge.relation.cardinality}"`);
|
|
111
|
-
}
|
|
112
|
-
lines.push(`${indent}]`);
|
|
113
|
-
}
|
|
114
|
-
function isSwitch(target) {
|
|
115
|
-
return "discriminator" in target;
|
|
116
|
-
}
|
package/dist/esm/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/index.d.ts
DELETED
package/dist/index.js
DELETED
package/dist/parse.d.ts
DELETED
package/dist/parse.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export function parseAnatomyYAML(raw) {
|
|
2
|
-
const doc = raw;
|
|
3
|
-
return {
|
|
4
|
-
root: toNamedNode(doc.node),
|
|
5
|
-
};
|
|
6
|
-
}
|
|
7
|
-
function toNamedNode(raw) {
|
|
8
|
-
return {
|
|
9
|
-
type: "named",
|
|
10
|
-
uri: raw.uri,
|
|
11
|
-
...(raw.styles ? { styles: toStyles(raw.styles) } : {}),
|
|
12
|
-
...(raw.edges ? { edges: raw.edges.map(toEdge) } : {}),
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
function toNode(raw) {
|
|
16
|
-
if ("uri" in raw && raw.uri) {
|
|
17
|
-
return toNamedNode(raw);
|
|
18
|
-
}
|
|
19
|
-
const anon = raw;
|
|
20
|
-
return {
|
|
21
|
-
type: "anonymous",
|
|
22
|
-
role: anon.role,
|
|
23
|
-
...(anon.styles ? { styles: toStyles(anon.styles) } : {}),
|
|
24
|
-
...(anon.edges ? { edges: anon.edges.map(toEdge) } : {}),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function toStyles(raw) {
|
|
28
|
-
return Object.entries(raw).map(([key, value]) => ({
|
|
29
|
-
key,
|
|
30
|
-
value: String(value),
|
|
31
|
-
}));
|
|
32
|
-
}
|
|
33
|
-
function toEdge(raw) {
|
|
34
|
-
let target;
|
|
35
|
-
if (raw.switch) {
|
|
36
|
-
target = toSwitch(raw.switch);
|
|
37
|
-
}
|
|
38
|
-
else if (raw.node) {
|
|
39
|
-
target = toNode(raw.node);
|
|
40
|
-
}
|
|
41
|
-
else if (raw.uri) {
|
|
42
|
-
target = { type: "named", uri: raw.uri };
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
throw new Error("Edge must have node, uri, or switch");
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
target,
|
|
49
|
-
relation: toRelation(raw.relation),
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
function toSwitch(raw) {
|
|
53
|
-
return {
|
|
54
|
-
discriminator: raw.on,
|
|
55
|
-
cases: raw.cases.map(toSwitchCase),
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function toSwitchCase(raw) {
|
|
59
|
-
if (raw.uri !== undefined) {
|
|
60
|
-
const node = { type: "named", uri: raw.uri };
|
|
61
|
-
return { value: raw.uri, node };
|
|
62
|
-
}
|
|
63
|
-
const node = toNode(raw.node);
|
|
64
|
-
const value = node.type === "named" ? node.uri : node.role;
|
|
65
|
-
return { value, node };
|
|
66
|
-
}
|
|
67
|
-
function toRelation(raw) {
|
|
68
|
-
return {
|
|
69
|
-
cardinality: raw.cardinality,
|
|
70
|
-
...(raw.slotName ? { slotName: raw.slotName } : {}),
|
|
71
|
-
};
|
|
72
|
-
}
|
package/dist/transform.d.ts
DELETED
package/dist/transform.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
const PREFIX = "@prefix : <http://anatomy-dsl.example.org/ontology#> .";
|
|
2
|
-
const INDENT = " ";
|
|
3
|
-
export function anatomyToTTL(spec) {
|
|
4
|
-
const lines = [PREFIX, ""];
|
|
5
|
-
lines.push("[] a :Specification ;");
|
|
6
|
-
lines.push(`${INDENT}:rootNode [`);
|
|
7
|
-
writeNode(lines, spec.root, 2);
|
|
8
|
-
lines.push(`${INDENT}] .`);
|
|
9
|
-
return `${lines.join("\n")}\n`;
|
|
10
|
-
}
|
|
11
|
-
function writeNode(lines, node, depth) {
|
|
12
|
-
if (node.type === "named") {
|
|
13
|
-
writeNamedNode(lines, node, depth);
|
|
14
|
-
}
|
|
15
|
-
else {
|
|
16
|
-
writeAnonymousNode(lines, node, depth);
|
|
17
|
-
}
|
|
18
|
-
const styles = node.styles ?? [];
|
|
19
|
-
const edges = node.edges ?? [];
|
|
20
|
-
if (styles.length > 0) {
|
|
21
|
-
writeStyles(lines, styles, depth, edges.length === 0);
|
|
22
|
-
}
|
|
23
|
-
if (edges.length > 0) {
|
|
24
|
-
writeEdges(lines, edges, depth);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
function writeNamedNode(lines, node, depth) {
|
|
28
|
-
const indent = INDENT.repeat(depth);
|
|
29
|
-
lines.push(`${indent}a :NamedNode ;`);
|
|
30
|
-
const hasMore = (node.styles && node.styles.length > 0) || (node.edges && node.edges.length > 0);
|
|
31
|
-
lines.push(`${indent}:uri "${node.uri}"${hasMore ? " ;" : ""}`);
|
|
32
|
-
}
|
|
33
|
-
function writeAnonymousNode(lines, node, depth) {
|
|
34
|
-
const indent = INDENT.repeat(depth);
|
|
35
|
-
lines.push(`${indent}a :AnonymousNode ;`);
|
|
36
|
-
const hasMore = (node.styles && node.styles.length > 0) || (node.edges && node.edges.length > 0);
|
|
37
|
-
lines.push(`${indent}:role "${node.role}"${hasMore ? " ;" : ""}`);
|
|
38
|
-
}
|
|
39
|
-
function writeStyles(lines, styles, depth, isLast) {
|
|
40
|
-
const indent = INDENT.repeat(depth);
|
|
41
|
-
const innerIndent = INDENT.repeat(depth + 1);
|
|
42
|
-
lines.push(`${indent}:hasStyle`);
|
|
43
|
-
for (const [i, style] of styles.entries()) {
|
|
44
|
-
const sep = i < styles.length - 1 ? " ," : isLast ? "" : " ;";
|
|
45
|
-
lines.push(`${innerIndent}[ :styleKey "${style.key}" ; :styleValue "${style.value}" ]${sep}`);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function writeEdges(lines, edges, depth) {
|
|
49
|
-
const indent = INDENT.repeat(depth);
|
|
50
|
-
for (const [i, edge] of edges.entries()) {
|
|
51
|
-
if (i === 0) {
|
|
52
|
-
lines.push(`${indent}:hasEdge [`);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
lines.push(`${indent}] , [`);
|
|
56
|
-
}
|
|
57
|
-
writeEdge(lines, edge, depth + 1);
|
|
58
|
-
}
|
|
59
|
-
lines.push(`${indent}]`);
|
|
60
|
-
}
|
|
61
|
-
function writeEdge(lines, edge, depth) {
|
|
62
|
-
const indent = INDENT.repeat(depth);
|
|
63
|
-
lines.push(`${indent}a :Edge ;`);
|
|
64
|
-
if (isSwitch(edge.target)) {
|
|
65
|
-
lines.push(`${indent}:edgeSwitch [`);
|
|
66
|
-
writeSwitch(lines, edge.target, depth + 1);
|
|
67
|
-
lines.push(`${indent}] ;`);
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
lines.push(`${indent}:edgeTarget [`);
|
|
71
|
-
writeNode(lines, edge.target, depth + 1);
|
|
72
|
-
lines.push(`${indent}] ;`);
|
|
73
|
-
}
|
|
74
|
-
writeRelation(lines, edge, depth);
|
|
75
|
-
}
|
|
76
|
-
function writeSwitch(lines, sw, depth) {
|
|
77
|
-
const indent = INDENT.repeat(depth);
|
|
78
|
-
lines.push(`${indent}a :Switch ;`);
|
|
79
|
-
lines.push(`${indent}:discriminator "${sw.discriminator}" ;`);
|
|
80
|
-
for (const [i, sc] of sw.cases.entries()) {
|
|
81
|
-
if (i === 0) {
|
|
82
|
-
lines.push(`${indent}:hasCase [`);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
lines.push(`${indent}] , [`);
|
|
86
|
-
}
|
|
87
|
-
writeSwitchCase(lines, sc, depth + 1);
|
|
88
|
-
}
|
|
89
|
-
lines.push(`${indent}]`);
|
|
90
|
-
}
|
|
91
|
-
function writeSwitchCase(lines, sc, depth) {
|
|
92
|
-
const indent = INDENT.repeat(depth);
|
|
93
|
-
lines.push(`${indent}a :SwitchCase ;`);
|
|
94
|
-
lines.push(`${indent}:caseNode [`);
|
|
95
|
-
writeNode(lines, sc.node, depth + 1);
|
|
96
|
-
lines.push(`${indent}]`);
|
|
97
|
-
}
|
|
98
|
-
function writeRelation(lines, edge, depth) {
|
|
99
|
-
const indent = INDENT.repeat(depth);
|
|
100
|
-
lines.push(`${indent}:hasRelation [`);
|
|
101
|
-
const inner = INDENT.repeat(depth + 1);
|
|
102
|
-
lines.push(`${inner}a :Relation ;`);
|
|
103
|
-
if (edge.relation.slotName) {
|
|
104
|
-
lines.push(`${inner}:cardinality "${edge.relation.cardinality}" ;`);
|
|
105
|
-
lines.push(`${inner}:slotName "${edge.relation.slotName}"`);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
lines.push(`${inner}:cardinality "${edge.relation.cardinality}"`);
|
|
109
|
-
}
|
|
110
|
-
lines.push(`${indent}]`);
|
|
111
|
-
}
|
|
112
|
-
function isSwitch(target) {
|
|
113
|
-
return "discriminator" in target;
|
|
114
|
-
}
|
package/dist/types/index.d.ts
DELETED
package/dist/types/parse.d.ts
DELETED
package/dist/types/types.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface Specification {
|
|
2
|
-
root: NamedNode;
|
|
3
|
-
}
|
|
4
|
-
export interface NamedNode {
|
|
5
|
-
type: "named";
|
|
6
|
-
uri: string;
|
|
7
|
-
styles?: Style[];
|
|
8
|
-
edges?: Edge[];
|
|
9
|
-
}
|
|
10
|
-
export interface AnonymousNode {
|
|
11
|
-
type: "anonymous";
|
|
12
|
-
role: string;
|
|
13
|
-
styles?: Style[];
|
|
14
|
-
edges?: Edge[];
|
|
15
|
-
}
|
|
16
|
-
export type Node = NamedNode | AnonymousNode;
|
|
17
|
-
export interface Edge {
|
|
18
|
-
target: Node | Switch;
|
|
19
|
-
relation: Relation;
|
|
20
|
-
}
|
|
21
|
-
export interface Relation {
|
|
22
|
-
cardinality: string;
|
|
23
|
-
slotName?: string;
|
|
24
|
-
}
|
|
25
|
-
export interface Style {
|
|
26
|
-
key: string;
|
|
27
|
-
value: string;
|
|
28
|
-
}
|
|
29
|
-
export interface Switch {
|
|
30
|
-
discriminator: "props" | "internal" | "override";
|
|
31
|
-
cases: SwitchCase[];
|
|
32
|
-
}
|
|
33
|
-
export interface SwitchCase {
|
|
34
|
-
value: string;
|
|
35
|
-
node: Node;
|
|
36
|
-
}
|
package/dist/types.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
export interface Specification {
|
|
2
|
-
root: NamedNode;
|
|
3
|
-
}
|
|
4
|
-
export interface NamedNode {
|
|
5
|
-
type: "named";
|
|
6
|
-
uri: string;
|
|
7
|
-
styles?: Style[];
|
|
8
|
-
edges?: Edge[];
|
|
9
|
-
}
|
|
10
|
-
export interface AnonymousNode {
|
|
11
|
-
type: "anonymous";
|
|
12
|
-
role: string;
|
|
13
|
-
styles?: Style[];
|
|
14
|
-
edges?: Edge[];
|
|
15
|
-
}
|
|
16
|
-
export type Node = NamedNode | AnonymousNode;
|
|
17
|
-
export interface Edge {
|
|
18
|
-
target: Node | Switch;
|
|
19
|
-
relation: Relation;
|
|
20
|
-
}
|
|
21
|
-
export interface Relation {
|
|
22
|
-
cardinality: string;
|
|
23
|
-
slotName?: string;
|
|
24
|
-
}
|
|
25
|
-
export interface Style {
|
|
26
|
-
key: string;
|
|
27
|
-
value: string;
|
|
28
|
-
}
|
|
29
|
-
export interface Switch {
|
|
30
|
-
discriminator: "props" | "internal" | "override";
|
|
31
|
-
cases: SwitchCase[];
|
|
32
|
-
}
|
|
33
|
-
export interface SwitchCase {
|
|
34
|
-
value: string;
|
|
35
|
-
node: Node;
|
|
36
|
-
}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|