forma-dsl-cli 0.3.9 → 0.4.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 +74 -9
- package/package.json +2 -2
- package/src/cli.js +43 -1
package/README.md
CHANGED
|
@@ -36,12 +36,74 @@ tree - is refused with exit code 2.
|
|
|
36
36
|
| `-m, --model <name>` | which model to build. Defaults to the first declared |
|
|
37
37
|
| `--part <name>` | one part by name. Defaults to the only part, or every part unioned |
|
|
38
38
|
| `-p, --param <name=val>` | a value for one declared param. Repeatable |
|
|
39
|
+
| `--params <file>` | a JSON object of values by name. `-p` wins over it |
|
|
39
40
|
| `--segments <n>` | a fixed segment count for every curve |
|
|
40
41
|
| `--min-angle <deg>` | smallest angle between adjacent segments of a curve |
|
|
41
42
|
| `--min-edge <n>` | smallest length a curve segment may be shortened to |
|
|
42
43
|
| `--max-nodes <n>` | refuse a document that would build more blocks than this |
|
|
43
44
|
| `-q, --quiet` | do not print the summary |
|
|
44
45
|
|
|
46
|
+
## Passing parameters
|
|
47
|
+
|
|
48
|
+
Three ways, and they compose.
|
|
49
|
+
|
|
50
|
+
**One at a time, on the command line.** A value is JSON where it parses as JSON, and the
|
|
51
|
+
string as written otherwise - so there is no second layer of quoting to get wrong:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
forma render part.forma -p count=6 -p center=true -p size=[10,20] -p label=M6
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
| Written | Arrives as |
|
|
58
|
+
| --- | --- |
|
|
59
|
+
| `-p count=6` | the number `6` |
|
|
60
|
+
| `-p center=true` | the boolean `true` |
|
|
61
|
+
| `-p size=[10,20]` | the list `[10, 20]` |
|
|
62
|
+
| `-p label=M6` | the string `"M6"` |
|
|
63
|
+
|
|
64
|
+
**From a file.** `--params` takes a JSON object of values by name - the thing you commit and
|
|
65
|
+
diff, rather than assemble in a shell loop:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"height": 36,
|
|
70
|
+
"label": "M6",
|
|
71
|
+
"center": true
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
forma render riser.forma --params params.json -o riser.stl
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
It has to be a JSON **object**. An array or a bare number parses but names nothing, and is
|
|
80
|
+
refused with exit code 2 rather than quietly contributing nothing. Like every other path, it
|
|
81
|
+
must resolve inside the directory the command runs in.
|
|
82
|
+
|
|
83
|
+
**Both.** `-p` wins over `--params`, which is what a build matrix wants: the values every
|
|
84
|
+
variant shares live in the file, and the one that varies goes on the line.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
for h in 8 20 40; do
|
|
88
|
+
forma render riser.forma --params shared.json -p "height=$h" -o "build/riser-$h.stl"
|
|
89
|
+
done
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
And the document will tell you what it takes, so the first `params.json` need not be written
|
|
93
|
+
by hand:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
forma params riser.forma --json | jq 'map({(.name): .default}) | add' > params.json
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"height": 20,
|
|
102
|
+
"label": "A",
|
|
103
|
+
"center": false
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
45
107
|
## In a pipeline
|
|
46
108
|
|
|
47
109
|
Only what was asked for goes to stdout — the STL under `-o -`, and the listings. The summary
|
|
@@ -57,13 +119,13 @@ that does not build, and `2` for a mistake in the invocation itself.
|
|
|
57
119
|
A failure is reported where an editor and a CI annotation can both find it:
|
|
58
120
|
|
|
59
121
|
```
|
|
60
|
-
riser.forma:7:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
122
|
+
riser.forma:7:7: rounded_rect: unknown attribute "corner_radius" - rounded_rect reads "center", "radius", "segments", "size"
|
|
123
|
+
5 | extrude {
|
|
124
|
+
6 | height = var.height
|
|
125
|
+
7 | rounded_rect { size = [40, 20] corner_radius = 4 }
|
|
126
|
+
| ^
|
|
127
|
+
8 | }
|
|
128
|
+
9 | }
|
|
67
129
|
```
|
|
68
130
|
|
|
69
131
|
Build a matrix of variants from what the document itself declares:
|
|
@@ -91,8 +153,11 @@ millimetres.
|
|
|
91
153
|
| [`forma-dsl`](https://www.npmjs.com/package/forma-dsl) | the language, its evaluator and the geometry kernel binding |
|
|
92
154
|
| [`forma-dsl-mcp`](https://www.npmjs.com/package/forma-dsl-mcp) | an MCP server: it knows the language, solves what it writes, and produces `.forma` files |
|
|
93
155
|
|
|
94
|
-
The
|
|
95
|
-
the
|
|
156
|
+
**[The site](https://kalpak44.github.io/forma-dsl/)** ·
|
|
157
|
+
**[Try the editor](https://kalpak44.github.io/forma-dsl/editor/)** ·
|
|
158
|
+
**[Read the reference](https://kalpak44.github.io/forma-dsl/docs/)**
|
|
159
|
+
|
|
160
|
+
The reference manual documents the language, and the editor runs the same kernel in a browser.
|
|
96
161
|
|
|
97
162
|
## License
|
|
98
163
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forma-dsl-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The forma command line: render .forma documents to STL, and list what one declares.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -44,6 +44,6 @@
|
|
|
44
44
|
"check:package": "node scripts/check-package.mjs"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"forma-dsl": "^0.
|
|
47
|
+
"forma-dsl": "^0.4.0"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/cli.js
CHANGED
|
@@ -40,6 +40,7 @@ Render options
|
|
|
40
40
|
-m, --model <name> which model to build. Defaults to the first declared
|
|
41
41
|
--part <name> one part by name. Defaults to the only part, or every part union
|
|
42
42
|
-p, --param <name=val> a value for one declared param. Repeatable
|
|
43
|
+
--params <file> a JSON object of values, by name. -p wins over it
|
|
43
44
|
--segments <n> a fixed segment count for every curve
|
|
44
45
|
--min-angle <deg> smallest angle between adjacent segments of a curve
|
|
45
46
|
--min-edge <n> smallest length a curve segment may be shortened to
|
|
@@ -61,6 +62,7 @@ const OPTIONS = {
|
|
|
61
62
|
model: { type: 'string', short: 'm' },
|
|
62
63
|
part: { type: 'string' },
|
|
63
64
|
param: { type: 'string', short: 'p', multiple: true },
|
|
65
|
+
params: { type: 'string' },
|
|
64
66
|
segments: { type: 'string' },
|
|
65
67
|
'min-angle': { type: 'string' },
|
|
66
68
|
'min-edge': { type: 'string' },
|
|
@@ -173,6 +175,41 @@ async function readSource(file) {
|
|
|
173
175
|
}
|
|
174
176
|
}
|
|
175
177
|
|
|
178
|
+
/**
|
|
179
|
+
* Reads a JSON object of parameter values.
|
|
180
|
+
*
|
|
181
|
+
* A file rather than a flag per value is what a build matrix wants: the values a variant
|
|
182
|
+
* needs are a thing you generate, commit and diff, and a shell loop assembling forty `-p`
|
|
183
|
+
* flags is a worse version of that file.
|
|
184
|
+
*
|
|
185
|
+
* Held to an object on purpose. A JSON array or a bare number parses but names nothing, and
|
|
186
|
+
* `params` the language understands are named.
|
|
187
|
+
*
|
|
188
|
+
* @param {string} path The file, which must sit inside the working directory.
|
|
189
|
+
* @returns {Promise<Record<string, ParameterValue>>} The values, by name.
|
|
190
|
+
* @throws {UsageError} If it cannot be read, is not JSON, or is not an object.
|
|
191
|
+
*/
|
|
192
|
+
async function parameterFile(path) {
|
|
193
|
+
let text;
|
|
194
|
+
try {
|
|
195
|
+
text = await readFile(await containedPath(path, true), 'utf8');
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new UsageError(`cannot read ${path}: ${error.message}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let parsed;
|
|
201
|
+
try {
|
|
202
|
+
parsed = JSON.parse(text);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
throw new UsageError(`${path} is not valid JSON: ${error.message}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
208
|
+
throw new UsageError(`${path} must hold a JSON object of name/value pairs`);
|
|
209
|
+
}
|
|
210
|
+
return parsed;
|
|
211
|
+
}
|
|
212
|
+
|
|
176
213
|
/**
|
|
177
214
|
* Where an STL goes when `--out` was not given: beside the document, named after it.
|
|
178
215
|
*
|
|
@@ -261,7 +298,12 @@ async function renderCommand(file, source, flags, io) {
|
|
|
261
298
|
try {
|
|
262
299
|
const result = await render(source, {
|
|
263
300
|
model: flags.model ?? null,
|
|
264
|
-
|
|
301
|
+
// The file first, so a `-p` on the command line overrides it. A matrix keeps the
|
|
302
|
+
// values it shares in the file and varies the one that differs on the line.
|
|
303
|
+
params: {
|
|
304
|
+
...(flags.params ? await parameterFile(flags.params) : {}),
|
|
305
|
+
...Object.fromEntries((flags.param ?? []).map(parameter)),
|
|
306
|
+
},
|
|
265
307
|
maxNodes: numeric(flags['max-nodes'], '--max-nodes') ?? null,
|
|
266
308
|
context,
|
|
267
309
|
});
|