1 ## oils_failures_allowed: 4
2
3 # Hay: Hay Ain't YAML
4
5 #### hay builtin usage
6
7 hay define
8 echo status=$?
9
10 hay define -- package user
11 echo status=$?
12
13 hay pp | wc -l | read n
14 echo read $?
15 test $n -gt 0
16 echo greater $?
17
18 ## STDOUT:
19 status=2
20 status=0
21 read 0
22 greater 0
23 ## END
24
25 #### hay reset
26 shopt --set parse_brace
27
28 hay define package
29
30 hay eval :a {
31 package foo
32 echo "package $?"
33 }
34
35 hay reset # no more names
36
37 echo "reset $?"
38
39 hay eval :b {
40 package foo
41 echo "package $?"
42 }
43
44 ## status: 127
45 ## STDOUT:
46 package 0
47 reset 0
48 ## END
49
50
51 #### hay eval can't be nested
52 shopt --set parse_brace
53
54 hay eval :foo {
55 echo foo
56 hay eval :bar {
57 echo bar
58 }
59 }
60 ## status: 127
61 ## STDOUT:
62 foo
63 ## END
64
65 #### hay names at top level
66 shopt --set parse_brace parse_at
67 shopt --unset errexit
68
69 hay define Package
70
71 Package one
72 echo status=$?
73
74 setvar args = _hay()['children'][0]['args']
75 write --sep ' ' $[len(_hay()['children'])] @args
76
77 hay eval :result {
78 Package two
79 echo status=$?
80 }
81
82 setvar args = result['children'][0]['args']
83 write --sep ' ' $[len(result['children'])] @args
84
85 Package three
86 echo status=$?
87
88 setvar args = _hay()['children'][0]['args']
89 write --sep ' ' $[len(_hay()['children'])] $[_hay()['children'][0]['args'][0]]
90
91 ## STDOUT:
92 status=0
93 1 one
94 status=0
95 1 two
96 status=0
97 1 three
98 ## END
99
100 #### Parsing Nested Attributes nodes (bug fix)
101
102 shopt --set parse_brace parse_equals
103
104 hay define Package/License
105
106 Package glibc {
107 version = '1.0'
108
109 License {
110 path = 'LICENSE.txt'
111 }
112
113 other = 'foo'
114 }
115
116 json write (_hay()) | jq '.children[0].children[0].attrs' > actual.txt
117
118 diff -u - actual.txt <<EOF
119 {
120 "path": "LICENSE.txt"
121 }
122 EOF
123
124 invalid = 'syntax' # parse error
125
126 ## status: 2
127 ## STDOUT:
128 ## END
129
130 #### hay eval Attr node, and JSON
131 shopt --set parse_brace parse_equals
132
133 hay define Package User
134
135 hay eval :result {
136 Package foo {
137 # not doing floats now
138 int = 42
139 bool = true
140 mynull = null
141 mystr = $'spam\n'
142
143 mylist = [5, 'foo', {}]
144 # TODO: Dict literals need to be in insertion order!
145 #mydict = {alice: 10, bob: 20}
146 }
147
148 User alice
149 }
150
151 # Note: using jq to normalize
152 json write (result) | jq . > out.txt
153
154 diff -u - out.txt <<EOF
155 {
156 "source": null,
157 "children": [
158 {
159 "type": "Package",
160 "args": [
161 "foo"
162 ],
163 "children": [],
164 "attrs": {
165 "int": 42,
166 "bool": true,
167 "mynull": null,
168 "mystr": "spam\n",
169 "mylist": [
170 5,
171 "foo",
172 {}
173 ]
174 }
175 },
176 {
177 "type": "User",
178 "args": [
179 "alice"
180 ]
181 }
182 ]
183 }
184 EOF
185
186 echo "diff $?"
187
188 ## STDOUT:
189 diff 0
190 ## END
191
192 #### hay eval shell node, and JSON
193 shopt --set parse_brace parse_equals
194
195 hay define TASK
196
197 hay eval :result {
198 TASK { echo hi }
199
200 TASK {
201 echo one
202 echo two
203 }
204 }
205
206 #= result
207 json write (result) | jq . > out.txt
208
209 diff -u - out.txt <<'EOF'
210 {
211 "source": null,
212 "children": [
213 {
214 "type": "TASK",
215 "args": [],
216 "location_str": "[ stdin ]",
217 "location_start_line": 6,
218 "code_str": " echo hi "
219 },
220 {
221 "type": "TASK",
222 "args": [],
223 "location_str": "[ stdin ]",
224 "location_start_line": 8,
225 "code_str": " \n echo one\n echo two\n "
226 }
227 ]
228 }
229 EOF
230
231 ## STDOUT:
232 ## END
233
234
235 #### _hay() register
236 shopt --set parse_paren parse_brace parse_equals parse_proc
237
238 hay define user
239
240 var result = {}
241
242 hay eval :result {
243
244 user alice
245 # = _hay()
246 write -- $[len(_hay()['children'])]
247
248 user bob
249 setvar result = _hay()
250 write -- $[len(_hay()['children'])]
251
252 }
253
254 # TODO: Should be cleared here
255 setvar result = _hay()
256 write -- $[len(_hay()['children'])]
257
258 ## STDOUT:
259 1
260 2
261 0
262 ## END
263
264
265 #### haynode builtin can define nodes
266 shopt --set parse_paren parse_brace parse_equals parse_proc
267
268 # It prints JSON by default? What about the code blocks?
269 # Or should there be a --json flag?
270
271 hay eval :result {
272
273 # note that 'const' is required because haynode isn't capitalized
274 haynode parent alice {
275 const age = '50'
276
277 haynode child bob {
278 # TODO: Is 'const' being created in the old ENCLOSING frame? Not the new
279 # ENCLOSED one?
280 const age = '10'
281 }
282
283 haynode child carol {
284 const age = '20'
285 }
286
287 const other = 'str'
288 }
289 }
290
291 #= result
292 write -- 'level 0 children' $[len(result['children'])]
293 write -- 'level 1 children' $[len(result['children'][0]['children'])]
294
295 hay eval :result {
296 haynode parent foo
297 haynode parent bar
298 }
299 write -- 'level 0 children' $[len(result['children'])]
300
301
302 ## STDOUT:
303 level 0 children
304 1
305 level 1 children
306 2
307 level 0 children
308 2
309 ## END
310
311
312 #### haynode: usage errors (name or block required)
313 shopt --set parse_brace parse_equals parse_proc
314
315 # should we make it name or block required?
316 # license { ... } might be useful?
317
318 try {
319 hay eval :result {
320 haynode package
321 }
322 }
323 echo "haynode attr $_status"
324 var result = _hay()
325 echo "LEN $[len(result['children'])]"
326
327 # requires block arg
328 try {
329 hay eval :result {
330 haynode TASK build
331 }
332 }
333 echo "haynode code $_status"
334 echo "LEN $[len(result['children'])]"
335
336 echo ---
337 hay define package TASK
338
339 try {
340 hay eval :result {
341 package
342 }
343 }
344 echo "define attr $_status"
345 echo "LEN $[len(result['children'])]"
346
347 try {
348 hay eval :result {
349 TASK build
350 }
351 }
352 echo "define code $_status"
353 echo "LEN $[len(result['children'])]"
354
355 ## STDOUT:
356 haynode attr 2
357 LEN 0
358 haynode code 2
359 LEN 0
360 ---
361 define attr 2
362 LEN 0
363 define code 2
364 LEN 0
365 ## END
366
367 #### haynode: shell nodes require block args; attribute nodes don't
368
369 shopt --set parse_brace parse_equals parse_proc
370
371 hay define package TASK
372
373 try {
374 hay eval :result {
375 package glibc > /dev/null
376 }
377 }
378 echo "status $_status"
379
380
381 try {
382 hay eval :result {
383 TASK build
384 }
385 }
386 echo "status $_status"
387
388 ## STDOUT:
389 status 0
390 status 2
391 ## END
392
393
394 #### hay eval with shopt -s ysh:all
395 shopt --set parse_brace parse_equals parse_proc
396
397 hay define Package
398
399 const x = 'foo bar'
400
401 hay eval :result {
402 Package foo {
403 # set -e should be active!
404 #false
405
406 version = '1.0'
407
408 # simple_word_eval should be active!
409 write -- $x
410 }
411 }
412
413 ## STDOUT:
414 foo bar
415 ## END
416
417 #### Attr block with duplicate names
418
419 shopt --set ysh:upgrade
420
421 hay define Package
422
423 Package cpython {
424 version = '3.11'
425 version = '3.12'
426 }
427
428 = _hay()
429
430 ## status: 1
431 ## STDOUT:
432 ## END
433
434 #### Scope of Variables Inside Hay Blocks
435
436 shopt --set ysh:all
437
438 hay define package
439 hay define deps/package
440
441 hay eval :result {
442
443 const URL_PATH = 'downloads/foo.tar.gz'
444
445 package foo {
446 echo "location = https://example.com/$URL_PATH"
447 echo "backup = https://archive.example.com/$URL_PATH"
448 }
449
450 # Note: PushTemp() happens here
451 deps spam {
452 # OVERRIDE
453 const URL_PATH = 'downloads/spam.tar.gz'
454
455 const URL2 = 'downloads/spam.tar.xz'
456
457 package foo {
458 # this is a global
459 echo "deps location https://example.com/$URL_PATH"
460 echo "deps backup https://archive.example.com/$URL2"
461 }
462 }
463
464 echo "AFTER $URL_PATH"
465
466 }
467
468 ## STDOUT:
469 location = https://example.com/downloads/foo.tar.gz
470 backup = https://archive.example.com/downloads/foo.tar.gz
471 deps location https://example.com/downloads/spam.tar.gz
472 deps backup https://archive.example.com/downloads/spam.tar.xz
473 AFTER downloads/foo.tar.gz
474 ## END
475
476 #### Nested bare assignment
477 shopt --set ysh:all
478
479 hay define Package/Deps
480
481 Package {
482 x = 10
483 Deps {
484 # this is a const
485 x = 20
486 }
487 }
488
489 json write (_hay())
490
491 ## STDOUT:
492 {
493 "source": null,
494 "children": [
495 {
496 "type": "Package",
497 "args": [],
498 "children": [
499 {
500 "type": "Deps",
501 "args": [],
502 "children": [],
503 "attrs": {
504 "x": 20
505 }
506 }
507 ],
508 "attrs": {
509 "x": 10
510 }
511 }
512 ]
513 }
514 ## END
515
516 #### Param with same name as Hay attribute
517 shopt --set ysh:all
518
519 # Danilo reported this on Zulip
520
521 hay define Service
522
523 proc gen-service(; ; variant = null) {
524 Service {
525 variant = variant
526 port = 80
527 }
528 }
529
530 gen-service
531 gen-service (variant = 'z')
532
533 var attrs = _hay().children[0].attrs
534 json write (attrs)
535
536 ## STDOUT:
537 {
538 "variant": null,
539 "port": 80
540 }
541 ## END
542
543
544 #### hay define and then an error
545 shopt --set parse_brace parse_equals parse_proc
546
547 hay define Package/License User TASK
548
549 hay pp defs > /dev/null
550
551 hay eval :result {
552 User bob
553 echo "user $?"
554
555 Package cppunit
556 echo "package $?"
557
558 TASK build {
559 configure
560 }
561 echo "TASK $?"
562
563 Package unzip {
564 version = '1.0'
565
566 License FOO {
567 echo 'inside'
568 }
569 echo "license $?"
570
571 License BAR
572 echo "license $?"
573
574 zz foo
575 echo 'should not get here'
576 }
577 }
578
579 echo 'ditto'
580
581 ## status: 127
582 ## STDOUT:
583 user 0
584 package 0
585 TASK 0
586 inside
587 license 0
588 license 0
589 ## END
590
591 #### parseHay()
592 shopt --set parse_proc
593
594 const config_path = "$REPO_ROOT/spec/testdata/config/ci.oil"
595 const block = parseHay(config_path)
596
597 # Are blocks opaque?
598 {
599 = block
600 } | wc -l | read n
601
602 # Just make sure we got more than one line?
603 if test "$n" -eq 1; then
604 echo "OK"
605 fi
606
607 ## STDOUT:
608 OK
609 ## END
610
611
612 #### Code Blocks: parseHay() then shvar _DIALECT= { evalHay() }
613 shopt --set parse_brace parse_proc
614
615 hay define TASK
616
617 const config_path = "$REPO_ROOT/spec/testdata/config/ci.oil"
618 const block = parseHay(config_path)
619
620 shvar _DIALECT=sourcehut {
621 const d = evalHay(block)
622 }
623
624 const children = d['children']
625 write 'level 0 children' $[len(children)] ---
626
627 # TODO: Do we need @[] for array expression sub?
628 write 'child 0' $[children[0].type] $[join(children[0].args)] ---
629 write 'child 1' $[children[1].type] $[join(children[1].args)] ---
630
631 ## STDOUT:
632 level 0 children
633 2
634 ---
635 child 0
636 TASK
637 cpp
638 ---
639 child 1
640 TASK
641 publish-html
642 ---
643 ## END
644
645 #### evalHay() usage
646 shopt -s parse_brace
647
648 try {
649 var d = evalHay()
650 }
651 echo status $_status
652
653 try {
654 var d = evalHay(3)
655 }
656 echo status $_status
657
658 try {
659 var d = evalHay(^(echo hi), 5)
660 }
661 echo status $_status
662
663 ## STDOUT:
664 status 3
665 status 3
666 status 3
667 ## END
668
669 #### Attribute / Data Blocks (package-manager)
670 shopt --set parse_proc
671
672 const path = "$REPO_ROOT/spec/testdata/config/package-manager.oil"
673
674 const block = parseHay(path)
675
676 hay define Package
677 const d = evalHay(block)
678 write 'level 0 children' $[len(d['children'])]
679 write 'level 1 children' $[len(d['children'][1]['children'])]
680
681 ## STDOUT:
682 level 0 children
683 3
684 level 1 children
685 0
686 ## END
687
688
689 #### Typed Args to Hay Node
690 shopt --set ysh:all
691
692 hay define when
693
694 # Hm I get 'too many typed args'
695 # Ah this is because of 'haynode'
696 # 'haynode' could silently pass through blocks and typed args?
697
698 when NAME [x > 0] {
699 const version = '1.0'
700 const other = 'str'
701 }
702
703 = _hay()
704
705 ## STDOUT:
706 ## END
707
708
709 #### OSH and hay (dynamic parsing)
710
711 source $REPO_ROOT/spec/testdata/config/osh-hay.osh
712
713 # TODO: code not serialized correctly - Samuel brought this up
714
715 ## STDOUT:
716 backticks
717 eval
718 TYPE TASK
719 CODE
720 echo `echo task backticks`
721 eval 'echo task eval'
722 ___
723 ## END
724
725 #### CODE node provides code_str, serialized code - issue #2050
726 shopt --set ysh:all
727
728 hay define Package
729 hay define Package/INSTALL
730
731 Package {
732 name = "osh"
733 INSTALL {
734 #echo hi
735
736 # The block causes a bug? Nesting?
737 cd dist {
738 ./install
739 }
740 }
741 }
742
743 = _hay()
744
745 ## STDOUT:
746 ## END
747
748 #### Proc within Hay node
749 shopt --set ysh:all
750
751 hay define Package
752
753 Package cpython {
754 version = '3.11'
755
756 proc build {
757 # procs have to capture
758 echo "version=$version"
759 make
760 }
761 }
762
763 # OK we have the proc
764 = _hay()
765
766 var build_proc = _hay().children[0].attrs.build
767
768 = build_proc
769
770 build_proc
771
772 #json write (_hay())
773
774 ## STDOUT:
775 ## END
776
777
778 #### Using Hay node from another module
779 shopt --set ysh:all
780
781 hay define Package/INSTALL
782
783 use $[ENV.REPO_ROOT]/spec/testdata/config/use-hay.ysh
784
785 #pp test_ (_hay())
786 json write (_hay().children[0].attrs)
787
788 ## STDOUT:
789 {
790 "version": "3.3"
791 }
792 ## END
793
794 #### Defining Hay node in another module
795 shopt --set ysh:all
796
797 use $[ENV.REPO_ROOT]/spec/testdata/config/define-hay.ysh
798
799 Package foo {
800 version = '3.3'
801 INSTALL {
802 echo version=$version
803 }
804 }
805
806 json write (_hay().children[0].attrs)
807
808 ## STDOUT:
809 {
810 "version": "3.3"
811 }
812 ## END
813
814
815 #### Using Hay with --eval flags
816 shopt --set ysh:all
817
818 echo 'hay define Package' >pre.ysh
819
820 echo '
821 Package cpython {
822 version = "3.12"
823 url = "https://python.org/release/$version/"
824 proc build {
825 echo "version = $version, url = $url"
826 }
827 }
828 ' >def.hay
829
830 # TODO:
831 # null_replacer=true
832 # JavaScript has a second "replacer" arg, which can be a function, or an array
833 # I guess you can specify replacer=null
834 #
835 # Invert it: Or maybe type_errors=true
836 #
837 # When type_errors=false (default), any unserializable value becomes null
838
839 echo 'json write (_hay().children[0], type_errors=false)' > stage-1.ysh
840
841 # Stage 1
842
843 ... $[ENV.SH] -o ysh:all
844 # TODO: restore purity
845 #--eval-pure pre.ysh
846 #--eval-pure def.hay
847 #--eval-pure stage-1.ysh
848 --eval pre.ysh
849 --eval def.hay
850 --eval stage-1.ysh
851 -c ''
852 || true
853 ;
854
855 # Stage 2
856
857 echo '
858 var pkg = _hay().children[0]
859 var build_proc = pkg.attrs.build
860 build_proc
861 ' > stage-2.ysh
862
863 # Stage 1
864
865 ... $[ENV.SH] -o ysh:all
866 # TODO: restore purity
867 #--eval-pure pre.ysh
868 #--eval-pure def.hay
869 --eval pre.ysh
870 --eval def.hay
871 --eval stage-2.ysh # This one isn't pure
872 -c ''
873 ;
874
875
876 ## STDOUT:
877 {
878 "type": "Package",
879 "args": [
880 "cpython"
881 ],
882 "children": [],
883 "attrs": {
884 "version": "3.12",
885 "url": "https://python.org/release/3.12/",
886 "build": null
887 }
888 }
889 version = 3.12, url = https://python.org/release/3.12/
890 ## END