@typfolio/core 0.1.0 → 0.2.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 +16 -5
- package/dist/capture/dom.d.ts +8 -3
- package/dist/capture/dom.js +5 -9
- package/dist/capture/svg.d.ts +19 -6
- package/dist/capture/svg.js +67 -48
- package/dist/definition.d.ts +156 -0
- package/dist/definition.js +161 -0
- package/dist/engine.js +32 -10
- package/dist/explain.d.ts +2 -0
- package/dist/explain.js +42 -0
- package/dist/index.d.ts +18 -2
- package/dist/index.js +67 -4
- package/dist/internal.d.ts +2 -0
- package/dist/internal.js +2 -0
- package/dist/request.d.ts +3 -1
- package/dist/request.js +20 -12
- package/dist/types.d.ts +15 -2
- package/dist/types.js +2 -0
- package/dist/typst-package.d.ts +2 -0
- package/dist/typst-package.generated.d.ts +2 -2
- package/dist/typst-package.generated.js +2 -2
- package/dist/typst-package.js +2 -0
- package/package.json +11 -2
- package/typst-packages/local/typfolio/0.1.0/lib.typ +14 -5
- package/typst-packages/local/typfolio/0.1.0/sample.typ +58 -20
|
@@ -7,10 +7,20 @@
|
|
|
7
7
|
// `sample-from-schema(schema, mode: "full")` builds a value from a JSON Schema (a dictionary):
|
|
8
8
|
// "full" shape-complete, for editor completion: every property, one array item, non-null shapes.
|
|
9
9
|
// "minimal" the sparsest value the schema allows, to expose templates that assume optional data.
|
|
10
|
+
// In both, an image field (`contentMediaType: "image/..."`) is a placeholder image's bytes.
|
|
10
11
|
// The sample is not validated against the schema.
|
|
11
12
|
|
|
12
13
|
#let _is-empty(v) = (type(v) == array or type(v) == dictionary) and v.len() == 0
|
|
13
14
|
|
|
15
|
+
// A field that holds the path of an image file says so with `contentMediaType: "image/..."`. In the
|
|
16
|
+
// editor and in template checks there is no such file to read, so the sample is the bytes of a grey
|
|
17
|
+
// placeholder image instead, which `image()` draws: write `image(data.logo)`, not `image("/" + ...)`.
|
|
18
|
+
#let _placeholder = bytes("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"400\" height=\"200\" viewBox=\"0 0 400 200\"><rect width=\"400\" height=\"200\" fill=\"#e6e8eb\"/><path d=\"M0 0L400 200M400 0L0 200\" stroke=\"#c4c8cd\" stroke-width=\"2\"/></svg>")
|
|
19
|
+
#let _is-image(node) = {
|
|
20
|
+
let media = node.at("contentMediaType", default: none)
|
|
21
|
+
type(media) == str and media.starts-with("image/")
|
|
22
|
+
}
|
|
23
|
+
|
|
14
24
|
#let _formats = (
|
|
15
25
|
"date": "2024-01-15",
|
|
16
26
|
"date-time": "2024-01-15T09:30:00Z",
|
|
@@ -19,8 +29,9 @@
|
|
|
19
29
|
"uuid": "123e4567-e89b-12d3-a456-426614174000",
|
|
20
30
|
)
|
|
21
31
|
|
|
22
|
-
// Resolves a local "#/..." reference against the root schema.
|
|
32
|
+
// Resolves a local "#" or "#/..." reference against the root schema.
|
|
23
33
|
#let _resolve(root, ref) = {
|
|
34
|
+
if ref == "#" { return root }
|
|
24
35
|
let node = root
|
|
25
36
|
for part in ref.slice(2).split("/") {
|
|
26
37
|
node = node.at(part.replace("~1", "/").replace("~0", "~"))
|
|
@@ -28,10 +39,31 @@
|
|
|
28
39
|
node
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
// `seen` holds the $ref values being expanded on the current path
|
|
32
|
-
//
|
|
42
|
+
// `seen` holds the $ref values being expanded on the current path. A recursive schema is expanded
|
|
43
|
+
// one level (a tree gets one child) and then cut: the cut value is `_cut`, which an array turns into
|
|
44
|
+
// no items and an object into a missing optional property, so a recursive list ends empty instead
|
|
45
|
+
// of holding `none`. `depth` bounds anything else.
|
|
46
|
+
#let _cut = "\u{E000}typfolio-recursion-cut"
|
|
47
|
+
#let _recursing(seen, ref) = seen.filter(r => r == ref).len() >= 2
|
|
48
|
+
|
|
49
|
+
// An object's properties from their samples: a cut optional property is left out, a cut required
|
|
50
|
+
// one is `none` (the schema demands an endless value there).
|
|
51
|
+
#let _object(node, sampled) = {
|
|
52
|
+
let required = node.at("required", default: ())
|
|
53
|
+
let out = (:)
|
|
54
|
+
for (key, v) in sampled {
|
|
55
|
+
if v == _cut {
|
|
56
|
+
if key in required { out.insert(key, none) }
|
|
57
|
+
} else {
|
|
58
|
+
out.insert(key, v)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
out
|
|
62
|
+
}
|
|
63
|
+
|
|
33
64
|
#let _sample(root, node, depth, seen) = {
|
|
34
65
|
if depth > 16 or type(node) != dictionary { return none }
|
|
66
|
+
if _is-image(node) { return _placeholder }
|
|
35
67
|
let candidates = ()
|
|
36
68
|
if "examples" in node { candidates += node.at("examples") }
|
|
37
69
|
if "default" in node { candidates.push(node.at("default")) }
|
|
@@ -45,14 +77,14 @@
|
|
|
45
77
|
}
|
|
46
78
|
if "$ref" in node {
|
|
47
79
|
let ref = node.at("$ref")
|
|
48
|
-
if ref
|
|
80
|
+
if _recursing(seen, ref) { return _cut }
|
|
49
81
|
return _sample(root, _resolve(root, ref), depth + 1, seen + (ref,))
|
|
50
82
|
}
|
|
51
83
|
for key in ("anyOf", "oneOf") {
|
|
52
84
|
if key in node {
|
|
53
85
|
for branch in node.at(key) {
|
|
54
86
|
let v = _sample(root, branch, depth + 1, seen)
|
|
55
|
-
if v != none { return v }
|
|
87
|
+
if v != none and v != _cut { return v }
|
|
56
88
|
}
|
|
57
89
|
}
|
|
58
90
|
}
|
|
@@ -70,10 +102,11 @@
|
|
|
70
102
|
if "properties" in node { t = "object" } else if "items" in node { t = "array" }
|
|
71
103
|
}
|
|
72
104
|
if t == "object" {
|
|
73
|
-
let
|
|
105
|
+
let sampled = (:)
|
|
74
106
|
for (key, sub) in node.at("properties", default: (:)) {
|
|
75
|
-
|
|
107
|
+
sampled.insert(key, _sample(root, sub, depth + 1, seen))
|
|
76
108
|
}
|
|
109
|
+
let out = _object(node, sampled)
|
|
77
110
|
// A partial object default fills in the generated shape rather than replacing it.
|
|
78
111
|
if filled.len() > 0 { out += filled.first() }
|
|
79
112
|
return out
|
|
@@ -81,7 +114,9 @@
|
|
|
81
114
|
if t == "array" {
|
|
82
115
|
let items = node.at("items", default: none)
|
|
83
116
|
if items == none { return () }
|
|
84
|
-
|
|
117
|
+
let item = _sample(root, items, depth + 1, seen)
|
|
118
|
+
if item == _cut { return () }
|
|
119
|
+
return range(calc.max(1, node.at("minItems", default: 1))).map(_ => item)
|
|
85
120
|
}
|
|
86
121
|
if t == "string" { return _formats.at(node.at("format", default: ""), default: "Sample text") }
|
|
87
122
|
if t == "integer" or t == "number" { return node.at("minimum", default: 1) }
|
|
@@ -89,9 +124,6 @@
|
|
|
89
124
|
none
|
|
90
125
|
}
|
|
91
126
|
|
|
92
|
-
/// Builds a shape-complete sample value from a JSON Schema (a dictionary).
|
|
93
|
-
#let sample-from-schema(schema) = _sample(schema, schema, 0, ())
|
|
94
|
-
|
|
95
127
|
// Minimal mode. Only `required` properties, nullable values are `none`, arrays are empty unless
|
|
96
128
|
// `minItems` says otherwise, `examples` and `default` are ignored. `$ref` and `allOf` follow full mode.
|
|
97
129
|
#let _is-nullable(node) = {
|
|
@@ -108,16 +140,18 @@
|
|
|
108
140
|
#let _minimal(root, node, depth, seen) = {
|
|
109
141
|
if depth > 16 or type(node) != dictionary { return none }
|
|
110
142
|
if _is-nullable(node) { return none }
|
|
143
|
+
if _is-image(node) { return _placeholder }
|
|
111
144
|
if "const" in node { return node.at("const") }
|
|
112
145
|
if "enum" in node and node.at("enum").len() > 0 { return node.at("enum").first() }
|
|
113
146
|
if "$ref" in node {
|
|
114
147
|
let ref = node.at("$ref")
|
|
115
|
-
if ref
|
|
148
|
+
if _recursing(seen, ref) { return _cut }
|
|
116
149
|
return _minimal(root, _resolve(root, ref), depth + 1, seen + (ref,))
|
|
117
150
|
}
|
|
118
151
|
for key in ("anyOf", "oneOf") {
|
|
119
152
|
if key in node and node.at(key).len() > 0 {
|
|
120
|
-
|
|
153
|
+
let v = _minimal(root, node.at(key).first(), depth + 1, seen)
|
|
154
|
+
return if v == _cut { none } else { v }
|
|
121
155
|
}
|
|
122
156
|
}
|
|
123
157
|
if "allOf" in node {
|
|
@@ -134,18 +168,20 @@
|
|
|
134
168
|
if "properties" in node { t = "object" } else if "items" in node { t = "array" }
|
|
135
169
|
}
|
|
136
170
|
if t == "object" {
|
|
137
|
-
let
|
|
171
|
+
let sampled = (:)
|
|
138
172
|
let properties = node.at("properties", default: (:))
|
|
139
173
|
for key in node.at("required", default: ()) {
|
|
140
|
-
|
|
174
|
+
sampled.insert(key, _minimal(root, properties.at(key, default: none), depth + 1, seen))
|
|
141
175
|
}
|
|
142
|
-
return
|
|
176
|
+
return _object(node, sampled)
|
|
143
177
|
}
|
|
144
178
|
if t == "array" {
|
|
145
179
|
let items = node.at("items", default: none)
|
|
146
180
|
let count = node.at("minItems", default: 0)
|
|
147
181
|
if items == none or count <= 0 { return () }
|
|
148
|
-
|
|
182
|
+
let item = _minimal(root, items, depth + 1, seen)
|
|
183
|
+
if item == _cut { return () }
|
|
184
|
+
return range(count).map(_ => item)
|
|
149
185
|
}
|
|
150
186
|
if t == "string" { return "a" * node.at("minLength", default: 0) }
|
|
151
187
|
if t == "integer" or t == "number" {
|
|
@@ -160,7 +196,9 @@
|
|
|
160
196
|
|
|
161
197
|
/// Builds a sample value from a JSON Schema (a dictionary). `mode` is "full" or "minimal".
|
|
162
198
|
#let sample-from-schema(schema, mode: "full") = {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
199
|
+
// The root is itself an expansion of "#", so a schema that refers to itself recurses one level too.
|
|
200
|
+
let value = if mode == "full" { _sample(schema, schema, 0, ("#",)) }
|
|
201
|
+
else if mode == "minimal" { _minimal(schema, schema, 0, ("#",)) }
|
|
202
|
+
else { panic("@local/typfolio: sample mode must be \"full\" or \"minimal\", got " + repr(mode)) }
|
|
203
|
+
if value == _cut { none } else { value }
|
|
166
204
|
}
|