1 | ---
|
2 | default_highlighter: oils-sh
|
3 | ---
|
4 |
|
5 | Hay - Custom Languages for Unix Systems
|
6 | =======================================
|
7 |
|
8 | *Hay* lets you use the syntax of the YSH to declare **data** and
|
9 | interleaved **code**. It allows the shell to better serve its role as
|
10 | essential **glue**. For example, these systems all combine Unix processes in
|
11 | various ways:
|
12 |
|
13 | - local build systems (Ninja, CMake, Debian package builds, Docker/OCI builds)
|
14 | - remote build services (VM-based continuous integration like sourcehut, Github
|
15 | Actions)
|
16 | - local process supervisors (SysV init, systemd)
|
17 | - remote process supervisors / cluster managers (Slurm, Kubernetes)
|
18 |
|
19 | Slogans:
|
20 |
|
21 | - *Hay Ain't YAML*.
|
22 | - It evaluates to [JSON][] + Shell Scripts.
|
23 | - *We need a better **control plane** language for the cloud*.
|
24 | - *YSH adds the missing declarative part to shell*.
|
25 |
|
26 | This doc describes how to use Hay, with motivating examples.
|
27 |
|
28 | As of 2022, this is a new feature of YSH, and **it needs user feedback**.
|
29 | Nothing is set in stone, so you can influence the language and its features!
|
30 |
|
31 |
|
32 | [JSON]: $xref:JSON
|
33 |
|
34 | <!--
|
35 | - although also Tcl, Lua, Python, Ruby
|
36 | - DSLs, Config Files, and More
|
37 | - For Dialects of YSH
|
38 |
|
39 | Use case examples
|
40 | -->
|
41 |
|
42 | <!-- cmark.py expands this -->
|
43 | <div id="toc">
|
44 | </div>
|
45 |
|
46 | ## Example
|
47 |
|
48 | Hay could be used to configure a hypothetical Linux package manager:
|
49 |
|
50 | # cpython.hay -- A package definition
|
51 |
|
52 | hay define Package/TASK # define a tree of Hay node types
|
53 |
|
54 | Package cpython { # a node with attributes, and children
|
55 |
|
56 | version = '3.9'
|
57 | url = 'https://python.org'
|
58 |
|
59 | TASK build { # a child node, with YSH code
|
60 | ./configure
|
61 | make
|
62 | }
|
63 | }
|
64 |
|
65 | This program evaluates to a JSON tree, which you can consume from programs in
|
66 | any language, including YSH:
|
67 |
|
68 | { "type": "Package",
|
69 | "args": [ "cpython" ],
|
70 | "attrs": { "version": "3.9", "url": "https://python.org" },
|
71 | "children": [
|
72 | { "type": "TASK",
|
73 | "args": [ "build" ],
|
74 | "code_str": " ./configure\n make\n"
|
75 | }
|
76 | ]
|
77 | }
|
78 |
|
79 | That is, a package manager can use the attributes to create a build
|
80 | environment, then execute shell code within it. This is a *staged evaluation
|
81 | model*.
|
82 |
|
83 | ## Understanding Hay
|
84 |
|
85 | A goal of Hay is to restore the **simplicity** of Unix to distributed systems.
|
86 | It's all just **code and data**!
|
87 |
|
88 | This means that it's a bit abstract, so here are a few ways of understanding
|
89 | it.
|
90 |
|
91 | ### Analogies
|
92 |
|
93 | The relation between Hay and YSH is like the relationship between these pairs
|
94 | of languages:
|
95 |
|
96 | - [YAML][] / [Go templates][], which are used in Helm config for Kubernetes.
|
97 | - YAML data specifies a **service**, and templates specify **variants**.
|
98 | - Two common ways of building C and C++ code:
|
99 | - [Make]($xref:make) / [Autotools]($xref:autotools)
|
100 | - [Ninja]($xref:ninja) / [CMake][]
|
101 | - Make and Ninja specify a **build graph**, while autotools and CMake detect
|
102 | a **configured variant** with respect to your system.
|
103 |
|
104 | Each of these is *70's-style macro programming* — a stringly-typed
|
105 | language generating another stringly-typed language, with all the associated
|
106 | problems.
|
107 |
|
108 | In contrast, Hay and YSH are really the same language, with the same syntax,
|
109 | and the same Python- and JavaScript-like dynamic **types**. Hay is just YSH
|
110 | that **builds up data** instead of executing commands.
|
111 |
|
112 | (Counterpoint: Ninja is intended for code generation, and it makes sense for
|
113 | YSH to generate simple languages.)
|
114 |
|
115 |
|
116 | [Go templates]: https://pkg.go.dev/text/template
|
117 | [CMake]: https://cmake.org
|
118 |
|
119 | ### Prior Art
|
120 |
|
121 | See the [Survey of Config Languages]($wiki) on the wiki, which puts them in
|
122 | these categories:
|
123 |
|
124 | 1. Languages for String Data
|
125 | - INI, XML, [YAML][], ...
|
126 | 1. Languages for Typed Data
|
127 | - [JSON][], TOML, ...
|
128 | 1. Programmable String-ish Languages
|
129 | - Go templates, CMake, autotools/m4, ...
|
130 | 1. Programmable Typed Data
|
131 | - Nix expressions, Starlark, Cue, ...
|
132 | 1. Internal DSLs in General Purpose Languages
|
133 | - Hay, Guile Scheme for Guix, Ruby blocks, ...
|
134 |
|
135 | Excerpts:
|
136 |
|
137 | [YAML][] is a data format that is (surprisingly) the de-facto control plane
|
138 | language for the cloud. It's an approximate superset of [JSON][].
|
139 |
|
140 | [UCL][] (universal config language) and [HCL][] (HashiCorp config language) are
|
141 | influenced by the [Nginx][] config file syntax. If you can read any of these
|
142 | languages, you can read Hay.
|
143 |
|
144 | [Nix][] has a [functional language][nix-lang] to configure Linux distros. In
|
145 | contrast, Hay is multi-paradigm and imperative.
|
146 |
|
147 | [nix-lang]: https://wiki.nixos.org/wiki/Nix_Expression_Language
|
148 |
|
149 | The [Starlark][] language is a dialect of Python used by the [Bazel][] build
|
150 | system. It uses imperative code to specify build graph variants, and you can
|
151 | use this same pattern in Hay. That is, if statements, for loops, and functions
|
152 | are useful in Starlark and Hay.
|
153 |
|
154 | [Ruby][]'s use of [first-class
|
155 | blocks](http://radar.oreilly.com/2014/04/make-magic-with-ruby-dsls.html)
|
156 | inspired YSH. They're used in systems like Vagrant (VM dev environments) and
|
157 | Rake (a build system).
|
158 |
|
159 | In [Lisp][], code and data are expressed with the same syntax, and can be
|
160 | interleaved.
|
161 | [G-Expressions](https://guix.gnu.org/manual/en/html_node/G_002dExpressions.html)
|
162 | in Guix use a *staged evaluation model*, like Hay.
|
163 |
|
164 | [YAML]: $xref:YAML
|
165 | [UCL]: https://github.com/vstakhov/libucl
|
166 | [Nginx]: https://en.wikipedia.org/wiki/Nginx
|
167 | [HCL]: https://github.com/hashicorp/hcl
|
168 | [Nix]: $xref:nix
|
169 |
|
170 | [Starlark]: https://github.com/bazelbuild/starlark
|
171 | [Bazel]: https://bazel.build/
|
172 |
|
173 | [Ruby]: https://www.ruby-lang.org/en/
|
174 | [Lisp]: https://en.wikipedia.org/wiki/Lisp_(programming_language)
|
175 |
|
176 |
|
177 | ### Comparison
|
178 |
|
179 | The biggest difference between Hay and [UCL][] / [HCL][] is that it's
|
180 | **embedded in a shell**. In other words, Hay languages are *internal DSLs*,
|
181 | while those languages are *external*.
|
182 |
|
183 | This means:
|
184 |
|
185 | 1. You can **interleave** shell code with Hay data. We'll discuss the many
|
186 | uses of this below.
|
187 | - On the other hand, it's OK to configure simple systems with plain data
|
188 | like [JSON][]. Hay is for when that stops working!
|
189 | 1. Hay isn't a library you embed in another program. Instead, you use
|
190 | Unix-style **process-based** composition.
|
191 | - For example, [HCL][] is written in Go, which may be hard to embed in a C
|
192 | or Rust program.
|
193 | - Note that a process is a good **security** boundary. It can be
|
194 | additionally run in an OS container or VM.
|
195 |
|
196 | <!--
|
197 | - Code on the **outside** of Hay blocks may use the ["staged programming" / "graph metaprogramming" pattern][build-ci-comments] mentioned above.
|
198 | - Code on the **inside** is *unevaluated*. You can execute it in another
|
199 | context, like a remote machine, Linux container, or virtual machine.
|
200 | -->
|
201 |
|
202 | The sections below elaborate on these points.
|
203 |
|
204 | [shell-pipelines]: https://www.oilshell.org/blog/2017/01/15.html
|
205 |
|
206 | <!--
|
207 | - YSH has an imperative programming model. It's a little like Starlark.
|
208 | - Guile / GNU Make.
|
209 | - Tensorflow.
|
210 | -->
|
211 |
|
212 |
|
213 | ## Overview
|
214 |
|
215 | Hay nodes have a regular structure:
|
216 |
|
217 | - They start with a "command", which is called the **type**.
|
218 | - They accept **string** arguments and **block** arguments. There must be at
|
219 | least one argument.
|
220 |
|
221 | ### Two Kinds of Nodes, and Three Kinds of Evaluation
|
222 |
|
223 | There are two kinds of node with this structure.
|
224 |
|
225 | (1) `SHELL` nodes contain **unevaluated** code, and their type is ALL CAPS.
|
226 | The code is turned into a string that can be executed elsewhere.
|
227 |
|
228 | TASK build {
|
229 | ./configure
|
230 | make
|
231 | }
|
232 | # =>
|
233 | # ... {"code_str": " ./configure\n make\n"}
|
234 |
|
235 | (2) `Attr` nodes contain **data**, and their type starts with a capital letter.
|
236 | They eagerly evaluate a block in a new **stack frame** and turn it into an
|
237 | **attributes dict**.
|
238 |
|
239 | Package cpython {
|
240 | version = '3.9'
|
241 | }
|
242 | # =>
|
243 | # ... {"attrs": {"version": "3.9"}} ...
|
244 |
|
245 | These blocks have a special rule to allow *bare assignments* like `version =
|
246 | '3.9'`. That is, you don't need keywords like `const` or `var`.
|
247 |
|
248 | (3) In contrast to these two types of Hay nodes, YSH builtins that take a block
|
249 | usually evaluate it eagerly:
|
250 |
|
251 | cd /tmp { # run in a new directory
|
252 | echo $PWD
|
253 | }
|
254 |
|
255 | Builtins are spelled with `lower` case letters, so `SHELL` and `Attr` nodes
|
256 | won't be confused with them.
|
257 |
|
258 | ### Two Stages of Evaluation
|
259 |
|
260 | So Hay is designed to be used with a *staged evaluation model*:
|
261 |
|
262 | 1. The first stage follows the rules above:
|
263 | - Tree of Hay nodes → [JSON]($xref) + Unevaluated shell.
|
264 | - You can use variables, conditionals, loops, and more.
|
265 | 2. Your app or system controls the second stage. You can invoke YSH again to
|
266 | execute shell inside a VM, inside a Linux container, or on a remote machine.
|
267 |
|
268 | These two stages conceptually different, but use the **same** syntax and
|
269 | evaluator! Again, the evaluator runs in a mode where it **builds up data**
|
270 | rather than executing commands.
|
271 |
|
272 | ### Result Schema
|
273 |
|
274 | Here's a description of the result of Hay evaluation (the first stage).
|
275 |
|
276 | # The source may be "cpython.hay"
|
277 | FileResult = (source Str, children List[NodeResult])
|
278 |
|
279 | NodeResult =
|
280 | # package cpython { version = '3.9' }
|
281 | Attr (type Str,
|
282 | args List[Str],
|
283 | attrs Map[Str, Any],
|
284 | children List[NodeResult])
|
285 |
|
286 | # TASK build { ./configure; make }
|
287 | | Shell(type Str,
|
288 | args List[Str],
|
289 | location_str Str,
|
290 | location_start_line Int,
|
291 | code_str Str)
|
292 |
|
293 |
|
294 | Notes:
|
295 |
|
296 | - Except for user-defined attributes, the result is statically typed.
|
297 | - Shell nodes are always leaf nodes.
|
298 | - Attr nodes may or may not be leaf nodes.
|
299 |
|
300 | ## Three Ways to Invoke Hay
|
301 |
|
302 | ### Inline Hay Has No Restrictions
|
303 |
|
304 | You can put Hay blocks and normal shell code in the same file. Retrieve the
|
305 | result of Hay evaluation with the `_hay()` function.
|
306 |
|
307 | # myscript.ysh
|
308 |
|
309 | hay define Rule
|
310 |
|
311 | Rule mylib.o {
|
312 | inputs = ['mylib.c']
|
313 |
|
314 | # not recommended, but allowed
|
315 | echo 'hi'
|
316 | ls /tmp/$(whoami)
|
317 | }
|
318 |
|
319 | echo 'bye' # other shell code
|
320 |
|
321 | const result = _hay()
|
322 | json write (result)
|
323 |
|
324 | In this case, there are no restrictions on the commands you can run.
|
325 |
|
326 | ### In Separate Files
|
327 |
|
328 | You can put hay definitions in their own file:
|
329 |
|
330 | # my-config.hay
|
331 |
|
332 | Rule mylib.o {
|
333 | inputs = ['mylib.c']
|
334 | }
|
335 |
|
336 | echo 'hi' # allowed for debugging
|
337 | # ls /tmp/$(whoami) would fail due to restrictions on hay evaluation
|
338 |
|
339 | In this case, you can use `echo` and `write`, but the interpreted is
|
340 | **restricted** (see below).
|
341 |
|
342 | Parse it with `parse_hay()`, and evaluate it with `eval_hay()`:
|
343 |
|
344 | # my-evaluator.ysh
|
345 |
|
346 | hay define Rule # node types for the file
|
347 | const h = parse_hay('build.hay')
|
348 | const result = eval_hay(h)
|
349 |
|
350 | json write (result)
|
351 | # =>
|
352 | # {
|
353 | # "children": [
|
354 | # { "type": "Rule",
|
355 | # "args": ["mylib.o"],
|
356 | # "attrs": {"inputs": ["mylib.c"]}
|
357 | # }
|
358 | # ]
|
359 | # }
|
360 |
|
361 | ### In A Block
|
362 |
|
363 | Instead of creating separate files, you can also use the `hay eval` builtin:
|
364 |
|
365 | hay define Rule
|
366 |
|
367 | hay eval :result { # assign to the variable 'result'
|
368 | Rule mylib.o {
|
369 | inputs = ['mylib.c']
|
370 | }
|
371 | }
|
372 |
|
373 | json write (result) # same as above
|
374 |
|
375 | This is mainly for testing and demos.
|
376 |
|
377 | ## Security Model: Restricted != Sandboxed
|
378 |
|
379 | The "restrictions" are **not** a security boundary! (They could be, but we're
|
380 | not making promises now.)
|
381 |
|
382 | Even with `eval_hay()` and `hay eval`, the config file is evaluated in the
|
383 | **same interpreter**. But the following restrictions apply:
|
384 |
|
385 | - External commands aren't allowed
|
386 | - Builtins other than `echo` and `write` aren't allowed
|
387 | - For example, the `.hay` file can't invoke `shopt` to change global shell
|
388 | options
|
389 | - A new stack frame is created, so the `.hay` file can't mutate your locals
|
390 | - However it can still mutate globals with `setglobal`!
|
391 |
|
392 | In summary, Hay evaluation is restricted to prevent basic mistakes, but your
|
393 | code isn't completely separate from the evaluated Hay file.
|
394 |
|
395 | If you want to evaluate untrusted code, use a **separate process**, and run it
|
396 | in a container or VM.
|
397 |
|
398 | ## Reference
|
399 |
|
400 | Here is a list of all the mechanisms mentioned.
|
401 |
|
402 | ### Shell Builtins
|
403 |
|
404 | - `hay`
|
405 | - `hay define` to define node types.
|
406 | - `hay pp` to pretty print the node types.
|
407 | - `hay reset` to delete both the node types **and** the current evaluation
|
408 | result.
|
409 | - `hay eval :result { ... }` to evaluate in restricted mode, and put the
|
410 | result in a variable.
|
411 | - Implementation detail: the `haynode` builtin is run when types like
|
412 | `Package` and `TASK` are invoked. That is, all node types are aliases for
|
413 | this same builtin.
|
414 |
|
415 | ### Functions
|
416 |
|
417 | - `parse_hay()` parses a file, just as `bin/ysh` does.
|
418 | - `eval_hay()` evaluates the parsed file in restricted mode, like `hay eval`.
|
419 | - `_hay()` retrieves the current result
|
420 | - It's useful interactive debugging.
|
421 | - The name starts with `_` because it's a "register" mutated by the
|
422 | interpreter.
|
423 |
|
424 | ### Options
|
425 |
|
426 | Hay is parsed and evaluated with option group `ysh:all`, which includes
|
427 | `parse_proc` and `parse_equals`.
|
428 |
|
429 | <!--
|
430 |
|
431 | - The `parse_brace` and `parse_equals` options are what let us inside attribute nodes
|
432 | - `_running_hay`
|
433 |
|
434 | -->
|
435 |
|
436 |
|
437 | ## Usage: Interleaving Hay and YSH
|
438 |
|
439 | Why would you want to interleave data and code? One reason is to naturally
|
440 | express variants of a configuration. Here are some examples.
|
441 |
|
442 | **Build variants**. There are many variants of the YSH binary:
|
443 |
|
444 | - `dbg` and `opt`. the compiler optimization level, and whether debug symbols
|
445 | are included.
|
446 | - `asan` and `ubsan`. Dynamic analysis with Clang sanitizers.
|
447 | - `-D GC_EVERY_ALLOC`. Make a build that helps debug the garbage collector.
|
448 |
|
449 | So the Ninja build graph to produce these binaries is **shaped** similarly, but
|
450 | it **varies** with compiler and linker flags.
|
451 |
|
452 | **Service variants**. A common problem in distributed systems is how to
|
453 | develop and debug services locally.
|
454 |
|
455 | Do your service dependencies live in the cloud, or are they run locally? What
|
456 | about state? Common variants:
|
457 |
|
458 | - `local`. Part or all of the service runs locally, so you may pass flags like
|
459 | `--auth-service localhost:8001` to binaries.
|
460 | - `staging`. A complete copy of the service, in a different cloud, with a
|
461 | different database.
|
462 | - `prod`. The live instance running with user data.
|
463 |
|
464 | Again, these collections of services are all **shaped** similarly, but the
|
465 | flags **vary** based on where binaries are physically running.
|
466 |
|
467 | ---
|
468 |
|
469 | This model can be referred to as ["graph metaprogramming" or "staged
|
470 | programming"][build-ci-comments]. In YSH, it's done with dynamically typed
|
471 | data like integers and dictionaries. In contrast, systems like CMake and
|
472 | autotools are more stringly typed.
|
473 |
|
474 | [build-ci-comments]: https://www.oilshell.org/blog/2021/04/build-ci-comments.html
|
475 |
|
476 | The following **examples** are meant to be "evocative"; they're not based on
|
477 | real code. Again, user feedback can improve them!
|
478 |
|
479 | ### Conditionals
|
480 |
|
481 | Conditionals can go on the inside of a block:
|
482 |
|
483 | Service auth.example.com { # node taking a block
|
484 | if (variant === 'local') { # condition
|
485 | port = 8001
|
486 | } else {
|
487 | port = 80
|
488 | }
|
489 | }
|
490 |
|
491 | Or on the outside:
|
492 |
|
493 | Service web { # node
|
494 | root = '/home/www'
|
495 | }
|
496 |
|
497 | if (variant === 'local') { # condition
|
498 | Service auth-local { # node
|
499 | port = 8001
|
500 | }
|
501 | }
|
502 |
|
503 |
|
504 | ### Iteration
|
505 |
|
506 | Iteration can also go on the inside of a block:
|
507 |
|
508 | Rule foo.o { # node
|
509 | inputs = [] # populate with all .cc files except one
|
510 |
|
511 | # variables ending with _ are "hidden" from block evaluation
|
512 | for name_ in *.cc {
|
513 | if name_ !== 'skipped.cc' {
|
514 | call inputs->append(name_)
|
515 | }
|
516 | }
|
517 | }
|
518 |
|
519 | Or on the outside:
|
520 |
|
521 | for name_ in *.cc { # loop
|
522 | Rule $(basename $name_ .cc).o { # node
|
523 | inputs = [name_]
|
524 | }
|
525 | }
|
526 |
|
527 |
|
528 | ### Remove Duplication with `proc`
|
529 |
|
530 | Procs can wrap blocks:
|
531 |
|
532 | proc myrule(name) {
|
533 |
|
534 | # needed for blocks to use variables higher on the stack
|
535 | shopt --set dynamic_scope {
|
536 |
|
537 | Rule dbg/$name.o { # node
|
538 | inputs = ["$name.c"]
|
539 | flags = ['-O0']
|
540 | }
|
541 |
|
542 | Rule opt/$name.o { # node
|
543 | inputs = ["$name.c"]
|
544 | flags = ['-O2']
|
545 | }
|
546 |
|
547 | }
|
548 | }
|
549 |
|
550 | myrule foo # call proc
|
551 | myrule bar # call proc
|
552 |
|
553 | Or they can be invoked from within blocks:
|
554 |
|
555 | proc set-port (port_num; out) {
|
556 | call out->setValue("localhost:$port_num")
|
557 | }
|
558 |
|
559 | Service foo { # node
|
560 | set-port 80 :p1 # call proc
|
561 | set-port 81 :p2 # call proc
|
562 | }
|
563 |
|
564 | ## More Usage Patterns
|
565 |
|
566 | ### Using YSH for the Second Stage
|
567 |
|
568 | The general pattern is:
|
569 |
|
570 | ./my-evaluator.ysh my-config.hay | json read :result
|
571 |
|
572 | The evaluator does the following:
|
573 |
|
574 | 1. Sets up the execution context with `hay define`
|
575 | 1. Parses `my-config.hay` with `parse_hay()`
|
576 | 1. Evaluates it with `eval_hay()`
|
577 | 1. Prints the result as JSON.
|
578 |
|
579 | Then a separate YSH processes reads this JSON and executes application code.
|
580 |
|
581 | TODO: Show code example.
|
582 |
|
583 | ### Using Python for the Second Stage
|
584 |
|
585 | In Python, you would:
|
586 |
|
587 | 1. Use the `subprocess` module to invoke `./my-evaluator.ysh my-config.hay`.
|
588 | 2. Use the `json` module to parse the result.
|
589 | 3. Then execute application code using the data.
|
590 |
|
591 | TODO: Show code example.
|
592 |
|
593 | ### Locating Errors in the Original `.hay` File
|
594 |
|
595 | The YSH interpreter has 2 flags starting with `--location` that give you
|
596 | control over error messages.
|
597 |
|
598 | ysh --location-str 'foo.hay' --location-start-line 42 -- stage2.ysh
|
599 |
|
600 | Set them to the values of fields `location_str` and `location_start_line` in
|
601 | the result of `SHELL` node evaluation.
|
602 |
|
603 | ### Debian `.d` Dirs
|
604 |
|
605 | Debian has a pattern of splitting configuration into a **directory** of
|
606 | concatenated files. It's easier for shell scripts to add to a directory than
|
607 | add to a file.
|
608 |
|
609 | This can be done with an evaluator that simply enumerates all files:
|
610 |
|
611 | var results = []
|
612 | for path in myconfig.d/*.hay {
|
613 | const code = parse_hay(path)
|
614 | const result = eval(hay)
|
615 | call results->append(result)
|
616 | }
|
617 |
|
618 | # Now iterate through results
|
619 |
|
620 | ### Parallel Loading
|
621 |
|
622 | TODO: Example of using `xargs -P` to spawn processes with `parse_hay()` and
|
623 | `eval_hay()`. Then merge the JSON results.
|
624 |
|
625 | ## Style
|
626 |
|
627 | ### Attributes vs. Procs
|
628 |
|
629 | Assigning attributes and invoking procs can look similar:
|
630 |
|
631 | Package grep {
|
632 | version = '1.0' # An attribute?
|
633 |
|
634 | version 1.0 # or call proc 'version'?
|
635 | }
|
636 |
|
637 | The first style is better for typed data like integers and dictionaries. The
|
638 | latter style isn't useful here, but it could be if `version 1.0` created
|
639 | complex Hay nodes.
|
640 |
|
641 | ### Attributes vs. Flags
|
642 |
|
643 | Hay nodes shouldn't take flags or `--`. Flags are for key-value pairs, and
|
644 | blocks are better for expressing such data.
|
645 |
|
646 | No:
|
647 |
|
648 | Package --version 1.0 grep {
|
649 | license = 'GPL'
|
650 | }
|
651 |
|
652 | Yes:
|
653 |
|
654 | Package grep {
|
655 | version = '1.0'
|
656 | license = 'GPL'
|
657 | }
|
658 |
|
659 | ### Dicts vs. Blocks
|
660 |
|
661 | Superficially, dicts and blocks are similar:
|
662 |
|
663 | Package grep {
|
664 | mydict = {name: 'value'} # a dict
|
665 |
|
666 | mynode foo { # a node taking a block
|
667 | name = 'value'
|
668 | }
|
669 | }
|
670 |
|
671 | Use dicts in cases where you don't know the names or types up front, like
|
672 |
|
673 | files = {'README.md': true, '__init__.py': false}
|
674 |
|
675 | Use blocks when there's a **schema**. Blocks are also different because:
|
676 |
|
677 | - You can use `if` statements and `for` loops in them.
|
678 | - You can call `TASK build; TASK test` within a block, creating multiple
|
679 | objects of the same type.
|
680 | - Later: custom validation
|
681 |
|
682 | ### YSH vs. Shell
|
683 |
|
684 | Hay files are parsed as YSH, not OSH. That includes `SHELL` nodes:
|
685 |
|
686 | TASK build {
|
687 | cp @deps /tmp # YSH splicing syntax
|
688 | }
|
689 |
|
690 | If you want to use POSIX shell or bash, use two arguments, the second of which
|
691 | is a multi-line string:
|
692 |
|
693 | TASK build '''
|
694 | cp "${deps[@]}" /tmp
|
695 | '''
|
696 |
|
697 | The YSH style gives you *static parsing*, which catches some errors earlier.
|
698 |
|
699 | ## Future Work
|
700 |
|
701 | - `hay proc` for arbitrary schema validation, including JSON schema
|
702 | - Examples of running hay in a secure process / container, in various languages
|
703 | - Sandboxing:
|
704 | - More find-grained rules?
|
705 | - "restricted" could come with a security guarantee. I've avoided making
|
706 | such guarantees, but I think it's possible as YSH matures. The
|
707 | interpreter uses dependency inversion to isolate I/O.
|
708 | - More location info, including the source file.
|
709 |
|
710 | [Please send
|
711 | feedback](https://github.com/oilshell/oil/wiki/Where-To-Send-Feedback) about
|
712 | Hay. It will inform and prioritize this work!
|
713 |
|
714 | ## Links
|
715 |
|
716 | - Blog posts tagged #[hay]($blog-tag). Hay is a general mechanism, so it's
|
717 | useful to explain it with concrete examples.
|
718 | - [Data Definition and Code Generation in Tcl](https://trs.jpl.nasa.gov/bitstream/handle/2014/7660/03-1728.pdf) (2003, PDF)
|
719 | - Like Hay, it has the (Type, Name, Attributes) data model.
|
720 | - <https://github.com/oilshell/oil/wiki/Config-Dialect>. Design notes and related links on the wiki.
|