Metanorma-plugin-datastruct
Functionality
Metanorma plugin that allows you to access static data structures like JSON, YAML, XML from a Metanorma document
Installation
$ gem install metanorma-plugin-datastruct
Usage
In order to use the macros in Metanorma, add the gem gem 'metanorma-plugin-datastruct' in your Gemfile. Or have the gem installed, and Metanorma can use them automatically via the extension :plugin: datastruct.
Expressions
Currently, there are 2 plugins available: yaml2text
and json2text
. As states from the name, yaml2text
allows to load yaml file and use it in expressions, json2text
works with json files.
Macroses supports all Liquid syntax expressions, including:
-
variables, variable assignment
-
flow control (if/case)
-
filters
-
loops
See here for the full description of Liquid tags and expressions.
Defining the block
A yaml2text
(json2text
) block is created with the following syntax.
Block opening and closing is demarcated by an open block syntax (--
)
or the [source]
block syntax (----
or more -
).
[yaml2text,{YAML(JSON) file path},{self-defined context name}]
----
this is content within the block!
----
Where:
-
content within the block is called the “template”;
-
{YAML(JSON) file path}
is the location of the YAML(JSON) file that contains data to be loaded. Location of the file is computed relative to the source directory that[yaml2text]
([json2text]
) is used (e.g., if[yaml2text,data.yaml,data]
is invoked in an.adoc
file located at/foo/bar/doc.adoc
, the data file is expected to be found at/foo/bar/data.yaml
); -
{self-defined context name}
is the name where the data read from the data file can be accessed with.
Interpolation
yaml2text
(json2text
) accepts string interpolation of the following forms:
-
{variable}
: as in AsciiDoc syntax; -
{{ variable }}
,{% if/else/for/case %}
: basic Liquid tags and expressions are supported.
The value within the curly braces will be interpolated by yaml2text
(json2text
).
Where:
-
In
{variable}
({{variable}}
),variable
is the name of the variable or AsciiDoc attribute. -
The location of
{variable}
({{variable}}
) in text will be replaced with the value ofvariable
. -
Evaluation order will be first from the defined context, then of the Metanorma AsciiDoc document.
Accessing object values
Object values are accessed via the .
(dot) separator.
EXAMPLE:
Given:
strings.yaml
---
foo: bar
dead: beef
And the block:
[yaml2text,strings.yaml,data]
----
I'm heading to the {{data.foo}} for {{data.dead}}.
----
The file path is strings.yaml
, and context name is data
.
{{data.foo}}
evaluates to the value of the key foo
in data
.
Will render as:
I'm heading to the bar for beef.
Accessing arrays
Length
The length of an array can be obtained by {{arrayname.size}}
.
EXAMPLE:
Given:
strings.yaml
---
- lorem
- ipsum
- dolor
And the block:
[yaml2text,strings.yaml,data]
----
The length of the YAML array is {{data.size}}.
----
The file path is strings.yaml
, and context name is data
.
{{data.size}}
evaluates to the length of the array using liquid size
filter.
Will render as:
The length of the YAML array is 3.
Enumeration and context
The following syntax is used to enumerate items within an array:
{% for item in array_name %}
...content...
{% endfor %}
Where:
-
array_name
is the name of the existing context that contains array data -
item
is the current item within the array
Within an array enumerator, the following expressions can be used:
-
{{forloop.index0}}
gives the zero-based position of the itemitem_name
within the parent array -
{{forloop.length}}
returns the number of iterations of the loop. -
{{forloop.first}}
returnstrue
if it’s the first iteration of the for loop. Returnsfalse
if it is not the first iteration. -
{{forloop.last}}
returnstrue
if it’s the last iteration of the for loop. Returnsfalse
if it is not the last iteration. -
{{array_name.size}}
gives the length of the arrayarray_name
-
{{array_name[i]}}
provides the value at indexi
(zero-based: starts with0
) in the arrayarray_name
;-1
can be used to refer to the last item,-2
the second last item, and so on.
EXAMPLE:
Given:
strings.yaml
---
- lorem
- ipsum
- dolor
And the block:
[yaml2text,strings.yaml,arr]
----
{% for item in arr %}
=== {{forloop.index0}} {item}
This section is about {item}.
{endfor}
----
Where:
-
file path is
strings.yaml
-
current context within the enumerator is called
item
-
{{forloop.index0}}
gives the zero-based position of itemitem
in the parent arrayarr
.
Will render as:
=== 0 lorem
This section is about lorem.
=== 1 ipsum
This section is about ipsum.
=== 2 dolor
This section is about dolor.
Accessing objects
Size
Similar to arrays, the number of key-value pairs within an object can be
obtained by {{objectname.size}}
.
EXAMPLE:
Given:
object.yaml
---
name: Lorem ipsum
desc: dolor sit amet
And the block:
[yaml2text,object.yaml,data]
----
=== {{data.name}}
{{data.desc}}
----
The file path is object.yaml
, and context name is data
.
{{data.size}}
evaluates to the size of the object.
Will render as:
=== Lorem ipsum
dolor sit amet
Enumeration and context
The following syntax is used to enumerate key-value pairs within an object:
{% for item in object_name %}
{{item[0]}}, {{item[1]}}
{% endfor %}
Where:
-
object_name
is the name of the existing context that contains the object -
{{item[0]}}
contains the key of the current enumrated object -
{{item[1]}}
contains the value -
{% endfor %}
indicates where the object enumeration block ends
EXAMPLE:
Given:
object.yaml
---
name: Lorem ipsum
desc: dolor sit amet
And the block:
[yaml2text,object.yaml,my_item]
----
{% for item in my_item %}
=== {{item[0]}}
{{item[1]}}
{% endfor %}
----
Where:
-
file path is
object.yaml
-
current key within the enumerator is called
item[0]
-
{{item[0]}}
gives the key name in the current iteration -
{{item[1]}}
gives the value in the current iteration
Will render as:
=== name
Lorem ipsum
=== desc
dolor sit amet
Moreover, the keys
and values
attributes can also be used in object enumerators.
EXAMPLE:
Given:
object.yaml
---
name: Lorem ipsum
desc: dolor sit amet
And the block:
[yaml2text,object.yaml,item]
----
.{{item.values[1]}}
[%noheader,cols="h,1"]
|===
{% for elem in item %}
| {{elem[0]}} | {{elem[1]}}
{% endfor %}
|===
----
Where:
-
file path is
object.yaml
-
current key within the enumerator is called
key
-
{{item[1]}}
gives the value of key in the current iteration the parent arraymy_item
. -
{{item.values[1]}}
gives the value located at the second key withinitem
Will render as:
.dolor sit amet
[%noheader,cols="h,1"]
|===
| name | Lorem ipsum
| desc | dolor sit amet
|===
There are several optional arguments to the for tag that can influence which items you receive in your loop and what order they appear in:
-
limit:<INTEGER> lets you restrict how many items you get.
-
offset:<INTEGER> lets you start the collection with the nth item.
-
reversed iterates over the collection from last to first.
EXAMPLE:
Given:
strings.yaml
---
- lorem
- ipsum
- dolor
- sit
- amet
And the block:
[yaml2text,strings.yaml,items]
----
{% for elem in items limit:2 offset:2 %}
{{item}}
{% endfor %}
----
Where:
-
file path is
strings.yaml
-
limit
- how many items we shoudl take from the array -
offset
- zero-based offset of item from which start the loop -
{{item}}
gives the value of item in the array
Will render as:
dolor sit
Advanced examples
With the syntax of enumerating arrays and objects we can now try more powerful examples.
Array of objects
EXAMPLE:
Given:
array_of_objects.yaml
---
- name: Lorem
desc: ipsum
nums: [2]
- name: dolor
desc: sit
nums: []
- name: amet
desc: lorem
nums: [2, 4, 6]
And the block:
[yaml2text,array_of_objects.yaml,ar]
----
{% for item in ar %}
{{item.name}}:: {{item.desc}}
{% for num in item.nums %}
- {{item.name}}: {{num}}
{% endfor %}
{% endfor %}
----
Notice we are now defining multiple contexts:
-
using different context names:
ar
,item
, andnum
Will render as:
Lorem:: ipsum
- Lorem: 2
dolor:: sit
amet:: lorem
- amet: 2
- amet: 4
- amet: 6
An array with interpolated file names (for AsciiDoc consumption)
yaml2text
(json2text
) blocks can be used for pre-processing document elements for AsciiDoc consumption.
EXAMPLE:
Given:
strings.yaml
---
prefix: doc-
items:
- lorem
- ipsum
- dolor
And the block:
[yaml2text,strings.yaml,yaml]
------
First item is {{yaml.items.first}}.
Last item is {{yaml.items.last}}.
{% for s in yaml.items %}
=== {{forloop.index0}} -> {{forloop.index0 | plus: 1}} {{s}} == {{yaml.items[forloop.index0]}}
[source,ruby]
----
include::{{yaml.prefix}}{{forloop.index0}}.rb[]
----
{% endfor %}
------
Will render as:
First item is lorem.
Last item is dolor.
=== 0 -> 1 lorem == lorem
[source,ruby]
----
include::doc-0.rb[]
----
=== 1 -> 2 ipsum == ipsum
[source,ruby]
----
include::doc-1.rb[]
----
=== 2 -> 3 dolor == dolor
[source,ruby]
----
include::doc-2.rb[]
----