Tutorial¶
This tutorial goes through the main features of ZOTI-YAML, and is supposed to complement the Syntax Reference. ZOTI-YAML extends the YAML 1.1 language with support for modules and library-like component imports.
First, follow the README instructions in the ZOTI-YAML project folder
on how to build lhe package. Optionally (if not in a Pipenv shell) you
might want to make the package visible by adding it to PYTHONPATH
,
e.g.
export PYTHONPATH=$PYTHONPATH:/path/to/zoti-yaml/src
To showcase a workflow based on ZOTI-YAML modules, we set up a toy example in an arbitrary folder where the ZOTI-YAML CLI tool is accessible. In this example we run the CLI tool as executable Python module:
export ZOML=python3 -m zoml
Let a “top” module (i.e., a module which imports and uses nodes from
other modules) called main.zoml
be defined in this folder as
follows:
1module: main
2import:
3 - {module: mod1}
4 - {module: sub.mod, as: mod2}
5
6---
7
8!default
9- root:
10 - mark: DEFAULT_MARKING
11- root:
12 - name: n1
13 nodes:
14 - name: n1_n1
15 nodes:
16 - name: n1_n1_n1
17 data:
18 !attach
19 ref: !ref {path: "../../../nodes[n1_n2]/extra"}
20 - name: n1_n2
21 extra: "I am referenced by n1_n1_n1!"
22 data:
23 !attach
24 ref: !ref {module: mod1, path: "/root/node[n_who]/data"}
25 - !attach
26 ref: !ref {module: mod2, path: "/root/nodes[n1]"}
27 name: n2
28 zoti-args:
29 i-need-this: "This field is used only to pass caller argument and will be destroyed"
30 content-extra: "I will be ignored!"
31
This module imports two other modules: mod1
, used with its qualified
name throughout the document (see line 24
), and sub.mod
called
with an alias mod2
throughout this document (see line 26
).
To keep this example simple we do not extend pathvar
(see how
configuration works in the Syntax Reference),
hence mod1
needs to be defined in the same root as the top module:
1module: mod1
2
3---
4
5root:
6 node:
7 - name: n_who
8 data: Hello World!
whereas sub.mod
is defined at the coresponding path:
1module: sub.mod
2
3---
4
5root:
6 nodes:
7 - name: n1
8 zoti-args:
9 i-need-this: "This will be replaced by the caller"
10 content1: !include {file: test.txt}
11 content2: !include {file: test.txt, begin: 3, end: 6}
12 content3: !include {file: test.txt, name: block1}
13 content4: !include {file: test.txt, begin: ":(!)", end: "(!):"}
14 # Recommended way to pass caller arguments
15 passed-arg: !attach {ref: !ref {path: "../zoti-args/i-need-this"}}
16 # OBS: direct access to caller members is possible but NOT RECOMMENDED
17 floating-ref: !ref {path: "../../root[n1]"}
18
As seen above, module sub.mod
references a text file in the same
relative path called test.txt
and includes raw text from it under
its contentX
entries, using different directives. This file contains
the following text:
BEGIN block1
I am the 'block1'.
Fear me!
END block1
:(!)
I am a monkey! Ook!
(!):
Finally, let us define a configuration file in the root of the project so that we do not need to manually pass the respective CLI arguments:
[zoti.yaml.toy]
tool = "toy-example-0.1.0" # for bookkeeping in node metadata
keys = ["root", "nodes"] # adds metadata to children of these nodes
ext = [".zoml"] # checks only files with extension .zoml
pathvar = ["."] # (not required) does not look for
# libraries outside the current path
To process the source files above, we run the command the command:
$ZOML --verbose --spec=toy --out=toy.yaml main
The argument --verbose
can be used to print out extensive debug
information to stderr
and can be used to trace every step in the
processing of the source file. It can be ignored for silent
output.
Since we declared main
to be the top module then the output file
toy.yaml
will contain the following info:
1---
2import:
3- {module: mod1}
4- {as: mod2, module: sub.mod}
5module: main
6path: tests/scenario1/main.zoml
7tool-log:
8- ['2023-04-22 01:35:38.854440', '']
9---
10root:
11- _info:
12 _pos:
13 - [11, 4, 135, 486, tests/scenario1/main.zoml, '']
14 mark: DEFAULT_MARKING
15 name: n1
16 nodes:
17 - _info:
18 _pos:
19 - [13, 8, 163, 325, tests/scenario1/main.zoml, '']
20 name: n1_n1
21 nodes:
22 - _info:
23 _pos:
24 - [15, 12, 202, 325, tests/scenario1/main.zoml, '']
25 data: I am referenced by n1_n1_n1!
26 name: n1_n1_n1
27 - _info:
28 _pos:
29 - [19, 8, 327, 486, tests/scenario1/main.zoml, '']
30 data: Hello World!
31 extra: I am referenced by n1_n1_n1!
32 name: n1_n2
33- _info:
34 _pos:
35 - [6, 6, 43, 624, tests/scenario1/sub/mod.zoml, '']
36 - [24, 4, 488, 721, tests/scenario1/main.zoml, ':!attach']
37 _prev_attrs: {name: n1}
38 content1: |
39 BEGIN block1
40 I am the 'block1'.
41 Fear me!
42 END block1
43
44 :(!)
45 I am a monkey! Ook!
46 (!):
47 content2: |
48 Fear me!
49 END block1
50
51 :(!)
52 content3: |
53 I am the 'block1'.
54 Fear me!
55 content4: |
56 I am a monkey! Ook!
57 floating-ref: {module: main, path: '/root[n1]'}
58 mark: DEFAULT_MARKING
59 name: n2
60 passed-arg: This field is used only to pass caller argument and will be destroyed
The new preamble has additional information such as the original file
path and a log. Also, notice that every child of root
and nodes
contains an _info
entry with positional metadata.
The effects of !default
(in main.zoml
line 8
) can be seen in the
fact that each 1st level child node (corresponding to the first
argument specification) contains a mark: DEFAULT_MARKING
entry, as a
result of merging the default values under !policy:union
(see
Syntax Reference).
The !ref
command has been resolved each time pointing to a valid
node in the module trees, enabling the !attach
command to act
accordingly. !attach
is used in three places, from last to first:
in
main.zoml
line25
: attaches the contents of then1
node fromsub.mod
under the noden2
inmain
. The behavior of the!include
command insub/mod.zoml
lines10-13
can be seen in the result. Notice thatfloating-ref
is resolved according to its new location (module resolved tomain
instead ofsub.mod
). This node also shows an example of passing an argument from the caller to the calle using a dedicated fieldzoti-args
which is destroyed after resolving the document. Also notice that its metadata contains its entire history and previous attributes.in
main.zoml
line23
: attaches thedata
entry of noden_who
frommod1
to thedata
entry ofn1_n2
frommain
. This is seen intoy.yaml
at line30
. Since the attached node is of type string, it does not contain any metadata.in
main.zoml
line19
: attaches theextra
entry of the sibling noden1_n2
frommain
(implicit) to thedata
entry of noden1_n1_n1
in the same module. This is seen intoy.yaml
at line25
.
This tutorial showcases a typical scenario for ZOTI-YAML where document trees are built from information spread across multiple files. In turn, this should enable any tool downstream to focus on parsing the right information and construct its core objects irrespective to where information originated, while also bookkeeping metadata for debugging purposes. As further shown in the Genny Producer-Consumer Example, these capabilities can be used to (minimally) emulate a component framework where parameters and interfaces can be specified after loading core components from designated libraries.