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.zomlline25: attaches the contents of then1node fromsub.modunder the noden2inmain. The behavior of the!includecommand insub/mod.zomllines10-13can be seen in the result. Notice thatfloating-refis resolved according to its new location (module resolved tomaininstead ofsub.mod). This node also shows an example of passing an argument from the caller to the calle using a dedicated fieldzoti-argswhich is destroyed after resolving the document. Also notice that its metadata contains its entire history and previous attributes.in
main.zomlline23: attaches thedataentry of noden_whofrommod1to thedataentry ofn1_n2frommain. This is seen intoy.yamlat line30. Since the attached node is of type string, it does not contain any metadata.in
main.zomlline19: attaches theextraentry of the sibling noden1_n2frommain(implicit) to thedataentry of noden1_n1_n1in the same module. This is seen intoy.yamlat 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.