Skip to content

Latest commit

 

History

History
2032 lines (1669 loc) · 44.6 KB

README.adoc

File metadata and controls

2032 lines (1669 loc) · 44.6 KB

Jamal Yaml integration module

Using this integration module, you can mix Jamal macro text with YAML data. To use this module, you have to add the dependency to your Maven project, as:

<dependency>
    <groupId>com.javax0.jamal</groupId>
    <artifactId>jamal-yaml</artifactId>
    <version>2.8.3-SNAPSHOT</version>
</dependency>

Following that, you can use the

macros.

Macros implemented in the package

i. yaml:define

since 1.7.5 ? and ! macro redefinition control since 2.8.1 OGNL subgraph can be used as macro argument

You can use this macro to define a Yaml structure. The format of the macro is

{@yaml:define yamlMacro=
tabulated Yaml content
}

After that, the name yamlMacro will be defined as a user-defined macro and can be used as {yamlMacro}. The value will replace the place of the use with the actual Yaml content. The macro can have one argument, which is an OGNL expression. If it is present then the macro will return part of the yaml structure pointed by the OGNL expression.

Note
Internally, Jamal converts the Yaml read in an object structure consisting of strings, primitives, maps, and lists. The structure is stored in an object of the type YamlObject. This class technically is a user-defined macro. The yaml:define macro will register the structure among the user-defined macros. When the name is used the same way as any other user-defined macro (without any argument), the content of the Yaml structure is converted to text.

The yamlMacro is stored along with the "usual" user-defined macros. Any usual or other user-defined macro can be redefined any number of times. If you want to define a Yaml macro only if it was not defined prior, use the ? after the keyword yaml:define. If you want to get an error message if the macro was already defined, use the ! after the keyword yaml:define. This functionality is implemented the same way as it is for the core built-in macro define.

The example:

Jamal source
{@yaml:format prettyFlow flowStyle=BLOCK}
{@yaml:define xyz=
a: this is the string value of a
b:
  - first value of b
  - second value in b
c:
  a: this is c.a
  b: this is c.b
}\
{xyz}\
or
--
{xyz| c}\
--

will result

output
a: this is the string value of a
b:
- first value of b
- second value in b
c:
  a: this is c.a
  b: this is c.b
or
--
a: this is c.a
b: this is c.b
--

The advantage of using this macro over just writing the Yaml directly to the output is that:

  • you can split up the Yaml file into smaller pieces using the ref, and resolve macros,

  • you can use user-defined macro parameters mixing the yaml content with Jamal macros.

Many times Yaml structures become profoundly nested, and it is not trivial to move things around. The Yaml structure is a data description structure, and many times these data structures are redundant. When editing Yaml structures, it is common to copy some part of the structure and edit its art. Using Jamal, you can solve the indentation hell as well as you can reduce the poisonous redundancy. Utilizing user-defined macros, you can use macros inside Yaml code, and at the same time, you can use Yaml code inside the macros. That way, you can pull out the part, repeat, and use only the macro as a reference.

It is a solution for strings and parts of some strings used at multiple locations in the Yaml file. User-defined macros alone do not solve the problem of overcomplexity and deep tabulation.

The Jamal Yaml module allows you to have a partially defined Yaml file to overcome this problem. When a Yaml structure is read from the text representation, it is stored in the memory as a Java object structure. It utilizes primitive values, strings, lists, and maps.

There is a particular class in the javax0.jamal.api package, called Ref. When you specify a value

!ref xyz

it will create a new Ref(xyz) object. Jamal will interpret this object as a reference to another Yaml data structure stored as a user-defined macro with the name xyz. When you specify the Yaml structure, the user-defined macro xyz does not need to be defined yet. It may be defined, but it may also be defined later. What is more, the macro can also be the same as the one that contains it. These references can be recursive.

The example: .Jamal source

{@yaml:define b=- x
- y
- !ref a}
{@yaml:define a=[a,b,c]}
{b}

resulting in the output

output
- x
- y
- !!javax0.jamal.api.Ref
  id: a
Note
You can notice that the macro a referenced from b is defined later than b.

Having these references with Jamal specific class types is of no general use. Their value is that Jamal can resolve them by converting the references to the content of the named macro. When you invoke the built-in macro yaml:resolve, these references will be replaced with their actual value.

The same example as above, but resolving the Yaml macro before using it

Jamal source
{#yaml:define b=- x
- y
- !ref a}
{@yaml:define a=[a,b,c]}
{@yaml:resolve b}
{b}

will result

output
- x
- y
- - a
  - b
  - c

When you invoke yaml:resolve, all the referenced Yaml macros have to be defined.

Using this reference possibility, you can have several small Yaml fragments possibly referencing each others using the !ref. When the small segments are done, you can apply yaml:resolve on the root one and create the output.

Note

When processing Yaml input, you can use the { and } characters as macro opening and macro closing strings. When you edit a Yaml file, you do not usually use the JSON-compatible { …​ } format for mapped values. However, when you use a user-defined, named Yaml content, like {yamlMacro}, it is likely to happen that the underlying rendering will generate a textual representation of the Yaml data, which contains { and } characters. The values of the user-defined macros are evaluated after they were de-referenced. The Yaml macros are exempt from this. These user-defined macros are defined verbatim, like a normal user-defined macro was defined using the ~ character after the define keyword. The evaluation is unnecessary because Yaml data hardly ever contain Jamal macros to be processed. The { and } characters may also cause a problem for Jamal. Precisely, it would interpret the first identifier following the { character as a user-defined macro. It will not find it. Even if it found it, it would not be likely to properly evaluate.

One solution to this problem is to use a different opening and closing string that does not appear inside the Yaml output. If you can find one for your application, you can go for it. Usually, you cannot guarantee that none of the string fields will contain the macro opening string. The safe solution is that these macros are defined by the Yaml built-in macros as verbatim. If you need to evaluate the content of the Yaml structure with embedded macros you have to use the {!yamlMacro} format.

For more information on macro evaluation order, see the core documentation of Jamal.

ii. yaml:ref

The use of this macro has been deprecated since Jamal version 2.0.0.

This macro will reference another Yaml definition. With the release 2.0.0, the local tag !ref is defined, which is a much cleaner and shorter way to reference another Yaml definition.

Jamal source
{@yaml:define x=
a: this is a string
b: !ref xyz}

iii. yaml:resolve

Use this macro to resolve one or more user-defined Yaml macro.

The format of the macro is

Jamal source
{@yaml:resolve macroName1, macroName2, ..., macroNameX}

User-defined Yaml macros created using the define or import macros may reference other user-defined Yaml macros. When you invoke the macro yaml:resolve, it will replace the references in the Yaml macro content with the content of the Yaml macro it references. The resolving process is recursive. If there are any references in the referenced Yaml macro, it will also be resolved. After resolving a macro xyz that references the macro aqt, the macro aqt will also be resolved. In some rare cases, this should not happen. If the referenced Yaml macros should not de resolved, then the option yamlReferenceClone should be set using the macro {@option yamlReferenceClone}. This option also has a local parameter alias, clone, that can be used between ( and ) as a macro option.

The example

Jamal source
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a}
{#yaml:define xyz=
a: 1
b: 3
c: !ref aqt}
{@yaml:resolve (clone) xyz}
Resolved:
{xyz}
Not resolved:
{aqt}

will result

output
Resolved:
a: 1
b: 3
c:
  z:
  - a
  - b
  - c

Not resolved:
z: !!javax0.jamal.api.Ref
  id: a

This process will not change the value of the macro aqt. In this case, the resolving process will create a copy of the referenced macro, and it will resolve the copy recursively. That way, xyz is still fully resolved and ready to be used.

The same example doing the resolve without the clone option, however

Jamal source
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a}
{#yaml:define xyz=
a: 1
b: 3
c: !ref aqt}
{@yaml:resolve xyz}
Resolved:
{xyz}
Also resolved:
{aqt}

will result

output
Resolved:
a: 1
b: 3
c:
  z:
  - a
  - b
  - c

Also resolved:
z:
- a
- b
- c
Note
When a macro is resolved, it will remember that it was already resolved and will not execute the resolve process anymore. It also means that calling yaml:resolve with the clone option on xyz and then calling it again without it will not resolve the referenced aqt. Unless you have a specific need, use the yaml:resolve macro without cloning.

There is another option that alters the behaviour of the resolving process. This is yamlResolveCopy with the alias copy. This option creates a copy of the referenced structures, cloned or not. To understand this, we can have a look at the following example:

Jamal source
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a
y: !ref a
}
{@yaml:resolve aqt}
{aqt}

will result

output
z: &id001
- a
- b
- c
y: *id001

When the underlying Snake Yaml library generates the text format of the Yaml data it realizes that both z and y fields refer to the same object. Thus, SnakeYaml generates a label, something like &id001 at the first occurrence and instead of repeating the same structure it on second occasion it only references that as *id001. The resolving process can circumvent this creating a copy for every reference.

Jamal source
{@yaml:define a=[ a, b ,c ]}
{#yaml:define aqt=z: !ref a
y: !ref a
}
{@yaml:resolve (copy) aqt}
{aqt}

will result

output
z:
- a
- b
- c
y:
- a
- b
- c

Note that copy and clone are not the same. You can use the clone option together or without copy and also the other way around. The implementation of copy resolution can handle recursive data structures and it will generate references into the output.

For example

Jamal source
{#yaml:define a=[ a, b ,c, !ref a]}
{@yaml:resolve (copy clone) a}
{a}

works as expected

output
&id002
- a
- b
- c
- *id002

However, when trying to resolve the following:

Jamal source
{#yaml:define a=[ a, b ,c, !ref a, !ref a]}
{@try! {@yaml:resolve (copy clone) a}}
{a}

then the result is

output
Jamal source seems to have infinite recursion
- a
- b
- c
- !!javax0.jamal.api.Ref
  id: a
- !!javax0.jamal.api.Ref
  id: a

Here we got an error message from the macro try, and the Yaml structure stored in a remained unresolved.

iv. Importing yaml from a file

There is no import macro to read Yaml formatted data from a file. If you want to read data from a file, you should combine the yaml:define and the core include macros. For example, there is a resource file src/test/resources/sample.yaml in the project where this documentation is compiled. It can be referenced from the test execution, which also converts this document as res:sample.yaml. The content of this file is

Jamal source
# this is a sample Yaml file that the test TestImport imports
# note that this file is a pure YAML file and no Jamal macros are in it
# contains user defined macro reference `a`
&id
a: this is {a}
b: this is b
c:
  - 1
  - 2
  - 3
  - 5
q: *id

You can use the following structure to read it from the file and assign the Yaml data to the macro aqt:

Jamal source
{#yaml:define aqt={@include [verbatim] res:sample.yaml}}
{aqt}

which will result

output
&id003
a: this is {a}
b: this is b
c:
- 1
- 2
- 3
- 5
q: *id003

The file has to be a Yaml formatted file, and it should not contain any Jamal macro. (If it does, it will be treated as raw data and will not be macro processed by Jamal.) If you want to read a Jamal formatted Yaml file, you must include it using the core include macro without the [verbatim] option.

Note
The built-in core macros use the [ and ] characters to enclose the options. Other packages usually use ( and ).

The file src/test/resources/sample.yaml.jam contains Jamal macros:

Jamal source
# this is a sample Yaml file that the test TestImport imports
# note that this file is a pure YAML file and no Jamal macros are in it
# contains user defined macro reference `a`
&id
a: this is {a}
b: this is b
c:
  - 1
  - 2
  - 3
  - 5
q: *id

You can include it with evaluation using the following macro sequence.

Jamal source
{@define a=wuff wuff}
{#yaml:define h={@include res:sample.yaml.jam}}
{h}

which will result:

output
&id004
a: this is wuff wuff
b: this is b
c:
- 1
- 2
- 3
- 5
q: *id004

v. yaml:isResolved

The macro yaml:isResolved results true or false if the Yaml macro given as argument is either resolved or not. The syntax of the macro is

Jamal source
{@yaml:isResolved macro_name}

Example:

Jamal source
{@yaml:define a=
a: this is a
b: this is b
}
{@yaml:isResolved a}
{@yaml:resolve a}\
{@yaml:isResolved a}

results

output
false
true

Note that the example Yaml structure does not need resolution. This macro does not test the structure. It simply tells that the structure went through the resolve process or not.

Usually there is narrow use of this macro. There is no penalty invoking resolve on a structure that was already resolved. The macro resolve does not re-run the resolution process for a structure that was already resolved. Other macros that need resolved structures automatically invoke resolving.

vi. yaml:get

This macro will fetch one value from a Yaml structure. This can be useful when you want to document some configuration or other data structure that is present as a Yaml file in your project. In that case you can import the Yaml structure into your Jamal document and refer individual values in it. The format of the macro is:

Jamal source
{@yaml:get (from=yamlMacro) OGNL-PATH}

The option from names a Yaml user defined macro, where the Yaml structure was loaded. It can also be defined outside as a user defined macro of the name yamlDataSource. This is useful when you want to retrieve multiple values from the same data structure.

The OGNL-PATH is a Object Graph Navigation Library Path. The functionality to fetch a value is implemented using the Apache Commons OGNL library. For more information about the OGNL language visit the web site https://commons.apache.org/proper/commons-ognl/index.html.

When getting a value out of a Yaml user defined macro the macro will automatically be resolved. The resolution can be cloning or in-place. To control the resolution process the same options can be used as for the resolve macro.

Examples
Jamal source
{@yaml:define a=
a: alma
b:
  c: 3
  d:
    - 1
    - 2
    - q:
        h: deep h}
{@yaml:get (from=a) b.d[2].q.h}

will result

output
deep h

vii. yaml:set

The macro yaml:set can define a user-defined YAML macro from an already existing YAML macro. It is similar to yaml:define but this macro does not parse a text and does not interpret it as YAML formatted text. Instead, it uses an already defined YAML user-defined macro and uses some part of it. It assigns the specific part to a new user-defined macro.

The syntax of the macro is

Jamal source
{@yaml:set (options) macroName=OGNL}
  • The options are the same as in the macro yaml:get:

    • yamlResolveClone (alias clone) to clone

    • yamlResolveCopy (alias copy) to copy resolve

    • yamlDataSource (alias from) the name of the user-defined macro which is the source of the data

If the from value is missing then the macro interprets the OGNL expression start as the name of the macro. In this case the first character of the expression has to be /. It essentially will start with /xxxx. where xxxx is the name of the macro.

The macroName is the name of the macro to assign the new object value to.

Example
Jamal source
{@yaml:define a=
a: alma
b:
  c: 3
  d:
    - 1
    - 2
    - q:
        h: deep h}
{@yaml:set s=/a.b.d[2].q.h}
{@yaml:set (from=a) r=b.d[2].q.h}
{s}
{r}

will result

output
deep h

deep h

When calling set the new macro will refer to the same object as the original one. It does not create a copy of the object pointed by the OGNL expression. This is demonstrated with the followinf example:

Jamal source
{@yaml:define a=
a: alma
b:
  c: 3
  d:
    - 1
    - 2
    - q:
        h: shallow dust}
{@yaml:set s=/a.b.d[2].q}
{@yaml:add to=a.b.d[2].q key=z
deep water
}
{s}

We set the reference to the object q. Then we add a new key-value pair to the object q using the macro a. In the output the result is modified in the macro s as well.

output
h: shallow dust
z: deep water

It is the behavior even of the clone or copy options are used. These options are controlling the resolution process and not the object creation.

viii. yaml:add

The macro yaml:add can modify an already parsed Yaml data structure. You can add elements to lists or maps inside the Yaml structure. The syntax of the macro is

Jamal source
{@yaml:add options
yaml data structure
}

The yaml data structure is the textual representation of the Yaml data to be hooked on the already existing data structure. The options present on the same line as the macro keyword yaml:add and the yaml data structure starts on the second line. The possible options are:

  • yamlDataTarget (alias to) must be specified and should define the point where the new data structure is added. The keyword yamlDataTarget can also be a user defined macro. The alias to can only be used in the macro use. Using yamlDataTarget defined as a user-defined macro makes sense when there are several additions to the same point. The format of the option is macroName.ognl expression. The name of the macro that holds the current data structure to be modified is at the start of the to string. It is separated by a . dot character from the Ognl expression that identifies the part of the structure to be modified. If there is no . in this parameter then the root of the structure is used.

  • key should only be specified when adding new data to a Map. The value of this option will be the key used in the Map. If data with the key already exists it will be overwritten. It is an error to specify a key when adding value to a data point, which is a list.

  • flat or flatten will decompose the Yaml structure before adding to the data point in the original yaml. Adding values to a Map then the top level of the Yaml structure to be added also has to be Map. Adding values to a List then the top level of the Yaml structure to be added also has to be List. Adding a Map this way the key value pairs of the map will be added to the original Yaml map. Adding a List this way the values of the list will be appended to the original Yaml list. When this option is specified it is an error to specify any key since in this case the keys of the map will be used.

Examples

Adding a value to the top-level Map

This example adds a new value to the root of the Yaml structure.

Jamal source
{@yaml:define a=
a: this is a simple Yaml with a top level Map
}
{@yaml:add to=a key=b
this is the value to be added to yaml structure a
}
{a}

will result:

output
a: this is a simple Yaml with a top level Map
b: this is the value to be added to yaml structure a
Adding an element to a Map in the Yaml structure

In this example the value is added to the value of the map from the top level named b.

Jamal source
{@yaml:define a=
a: this is a simple Yaml with a top level Map
b: {}
}
{@yaml:add to=a.b key=c
this is the value to be added to yaml structure a
}
{a}

will result:

output
a: this is a simple Yaml with a top level Map
b:
  c: this is the value to be added to yaml structure a
Using flat to add multiple elements to a Map

This example will add multiple elements to a map inside the yaml structure.

Jamal source
{@yaml:define docker=
version: "3.6"
services:
  jamal-mongodb:
    build:
      args:
        dump_dir: dump
      context: ./config-dev/mongodb
    container_name: zrch-mongodb
    environment:
      - TZ=Europe/Zurich
    image: zrch/mongodb:1.0.1-dev
    labels:
      com.javax0.jamal.description: "Persistence service."
      com.javax0.jamal.is-production: "false"
    ports:
      - "27017:27017"
}
{@yaml:add to=docker.services["jamal-mongodb"].labels flat
com.javax0.jamal.title: "Non-relational DB Instance"
com.javax0.jamal.sizing: 1000
com.javax0.jamal.nodeType: primary
}
{docker}

will result:

output
version: '3.6'
services:
  jamal-mongodb:
    build:
      args:
        dump_dir: dump
      context: ./config-dev/mongodb
    container_name: zrch-mongodb
    environment:
    - TZ=Europe/Zurich
    image: zrch/mongodb:1.0.1-dev
    labels:
      com.javax0.jamal.description: Persistence service.
      com.javax0.jamal.is-production: 'false'
      com.javax0.jamal.title: Non-relational DB Instance
      com.javax0.jamal.sizing: 1000
      com.javax0.jamal.nodeType: primary
    ports:
    - 27017:27017
Adding elements to an array

This example adds one element to an array. The added element itself is an array. It is not flattened

Jamal source
{@yaml:define a=
- this is a simple Yaml with a top level Map
- kukuruc
}
{@yaml:add to=a
- this is one element
- this is the second element}
{a}

will result:

output
- this is a simple Yaml with a top level Map
- kukuruc
- - this is one element
  - this is the second element

If we use flattening, we get a different result

Jamal source
{@yaml:define a=
- this is a simple Yaml with a top level Map
- kukuruc
}
{@yaml:format flowStyle=BLOCK}
{@yaml:add to=a flatten
- this is one element
- this is the second element
}
{a}

will result:

output
- this is a simple Yaml with a top level Map
- kukuruc
- this is one element
- this is the second element

ix. yaml:format

This macro can be used to set the options for Snake Yaml. The format of the macro is

Jamal source
{@yaml:format options}

The options of the macro are

  • allowUnicode specify whether to emit non-ASCII printable Unicode characters.

  • canonical force the emitter to produce a canonical YAML document.

  • explicitEnd force to add …​ at the end of the Yaml data

  • explicitStart force to ass --- at the start of the yaml data

  • prettyFlow instruct the output to follow pretty flow

  • splitLines instruct the output to split too long lines

  • defaultFlowStyle,flowStyle the flow style can be FLOW, BLOCK or AUTO

  • defaultScalarStyle,scalarStyle the scalar style can be DOUBLE_QUOTED, SINGLE_QUOTED, LITERAL, FOLDED, or PLAIN,

  • lineBreak the output line break can be WIN, MAC, or UNIX

  • indent sets the indentation size, should be max 10

  • indicatorIndent set the number of white-spaces to use for the sequence indicator '-'

  • width sets the desired width

Each of these options has a setXXX counterpart in SnakeYaml DumperOption class. setXXX methods with boolean argument need boolean option in this macro. Similarly, methods with in argument need integer options. Those options that have an enum setXXX counterpart should use the name of the individual enum values.

Examples

In these examples, we will use the yaml structure:

Jamal source
{@yaml:define yaml=
a: this is a euuu
b: this is b
bb:
  h:
    z:
      t: t34 panzer
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1,2,3]
}
{@yaml:format}{@comment this is resetting all previous formatting}
{yaml}

It prints as the following without specifying any format:

output
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
Allow Unicode

allowUnicode instructs the snake yaml output to include the unicode characters into the output instead of using escape sequences in string.

Jamal source
{@yaml:format allowUnicode}
{yaml}
output
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
Canonical

The option canonical instructs snake yaml to output the structure in a canonical format.

Jamal source
{@yaml:format canonical}
{yaml}
output
---
!!map {
  ? !!str "a"
  : !!str "this is a euuu",
  ? !!str "b"
  : !!str "this is b",
  ? !!str "bb"
  : !!map {
    ? !!str "h"
    : !!map {
      ? !!str "z"
      : !!map {
        ? !!str "t"
        : !!str "t34 panzer",
      },
    },
  },
  ? !!str "c"
  : !!seq [
    !!int "1",
    !!int "2",
    !!str "this is\n  a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n",
  ],
  ? !!str "k"
  : !!int "3",
  ? !!str "h"
  : !!seq [
    !!int "1",
    !!int "2",
    !!int "3",
  ],
}
Explicit Start and End

The option explicitStart and explicitEnd instructs snake yaml to output the starting --- and ending …​ characters.

Jamal source
{@yaml:format explicitEnd explicitStart}
{yaml}
output
---
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
...
prettyFlow

The option prettyFlow instructs snake yaml to output the yaml structure in pretty flow.

Jamal source
{@yaml:format prettyFlow}
{yaml}
output
a: this is a euuu
b: this is b
bb:
  h:
    z: {
      t: t34 panzer
    }
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [
  1,
  2,
  3
]
splitLines

The option splitLines instructs snake yaml to output the yaml structure splitting the lines.

Jamal source
{@yaml:format splitLines}
{yaml}

For some reason, it does not split the lines, may be later versions of snakeYaml will do.

output
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
Flow Style
Jamal source
{@yaml:format flowStyle=FLOW}
FLOW
{yaml}
{@yaml:format flowStyle=BLOCK}
BLOCK
{yaml}
{@yaml:format flowStyle=AUTO}
AUTO
{yaml}
output
FLOW
{a: this is a euuu, b: this is b, bb: {h: {z: {t: t34 panzer}}}, c: [1, 2, "this is\n  a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n"], k: 3, h: [1, 2, 3]}


BLOCK
a: this is a euuu
b: this is b
bb:
  h:
    z:
      t: t34 panzer
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h:
- 1
- 2
- 3


AUTO
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
Scalar Style
Jamal source
{@yaml:format scalarStyle=DOUBLE_QUOTED}
DOUBLE_QUOTED
{yaml}
{@yaml:format scalarStyle=SINGLE_QUOTED}
SINGLE_QUOTED
{yaml}
{@yaml:format scalarStyle=LITERAL}
LITERAL
{yaml}
{@yaml:format scalarStyle=FOLDED}
FOLDED
{yaml}
{@yaml:format scalarStyle=PLAIN}
PLAIN
{yaml}
output
DOUBLE_QUOTED
"a": "this is a euuu"
"b": "this is b"
"bb":
  "h":
    "z":
      "t": "t34 panzer"
"c":
- !!int "1"
- !!int "2"
- "this is\n  a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n"
"k": !!int "3"
"h":
- !!int "1"
- !!int "2"
- !!int "3"


SINGLE_QUOTED
'a': 'this is a euuu'
'b': 'this is b'
'bb':
  'h':
    'z':
      't': 't34 panzer'
'c':
- !!int '1'
- !!int '2'
- "this is\n  a multi\nline\nstring with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666\n"
'k': !!int '3'
'h':
- !!int '1'
- !!int '2'
- !!int '3'


LITERAL
"a": |-
  this is a euuu
"b": |-
  this is b
"bb":
  "h":
    "z":
      "t": |-
        t34 panzer
"c":
- !!int |-
  1
- !!int |-
  2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
"k": !!int |-
  3
"h":
- !!int |-
  1
- !!int |-
  2
- !!int |-
  3


FOLDED
"a": >-
  this is a euuu
"b": >-
  this is b
"bb":
  "h":
    "z":
      "t": >-
        t34 panzer
"c":
- !!int >-
  1
- !!int >-
  2
- >
  this is
    a multi
  line

  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
"k": !!int >-
  3
"h":
- !!int >-
  1
- !!int >-
  2
- !!int >-
  3


PLAIN
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
Indenting
Jamal source
{@yaml:format indent=5}
{yaml}
output
a: this is a euuu
b: this is b
bb:
     h:
          z: {t: t34 panzer}
c:
- 1
- 2
- |
     this is
       a multi
     line
     string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]
Width
Jamal source
{@yaml:format width=20}
{yaml}
output
a: this is a euuu
b: this is b
bb:
  h:
    z: {t: t34 panzer}
c:
- 1
- 2
- |
  this is
    a multi
  line
  string with one fairly long line that will be split 0000000000 1111111111 2222222222 3333333333 4444444444 5555555555 66666666666
k: 3
h: [1, 2, 3]

x. yaml:dump

The macro yaml:dump can dump the Yaml data structure to a file. The format of the macro is

Jamal source
{@yaml:dump yamlMacro to file_name}

where yamlMacro is the name of the macro that holds the Yaml data structure. file_name is the name of the file where the Yaml formatted content is to be written. The to separating them is a keyword to ease readability. The following structure presents an example:

Jamal source
{@yaml:define x=[a,b,c]}
{@yaml:dump x to ./target/dump.yaml}
{@include [verbatim] ./target/dump.yaml}

will result

output
[a, b, c]

There is no reason to dump an unresolved structure into a file. If the macro to be dumped to the file was not yet resolved, it will be resolved. The resolution process will be in-place unless the option yamlResolveClone (alias clone) is used before the name of the macro between ( and ) characters.

Jamal source
{@yaml:dump (clone) x to ./target/dump.yaml}

xi. yaml:xml

The macro yaml:xml converts a Yaml structure to XML format. The format of the macro is:

Jamal source
{@yaml:xml (options) yamlMacroName}

Here yamlMacroName is the name of a Yaml macro that was defined using yaml:define. Before converting, the Yaml structure will be resolved in case it was not resolved yet. For this the options clone and copy can be specified. For more information in these options see the macro documentation of yaml:resolve.

In addition to these options you can use the options

  • yamlXmlTopTag (alias tag) can specify the name of the top level tag of the XML. The default value is xml

  • yamlXmlAttributes (alias attributes) can specify extra attributes for the top level XML tag. The default value is not to specify any attribute for the top level tag.

The maps from the Yaml structure the (key,value) elements will be converted to the XML

<key>value</key>

structure.

Here value can be another map, a list or something else represented as a string. For example:

Jamal source
{@yaml:define z=
tagValues:
  a: 1
  b: 2
  c: 3}
{#xmlFormat {@yaml:xml (tag="tag" attributes="a=\"53\"")z}}

will be converted to

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<tag a="53">
    <tagValues>
        <a>1</a>
        <b>2</b>
        <c>3</c>
    </tagValues>
</tag>

You can also specify attributes, use of CDATA and tag names for list elements using special classes. Jamal contains five classes, which can be referred to in Yaml files, and they are treated special during the XML conversion. When reading and writing Yaml files Jamal defines special tags for these classes. These tags start with a single ! character and the name of the tag. The tags are listed here.

  • !attr will be used as an attribute for the tag containing this object. When the containing object is a map, the !attr object can be a simple string, or it can be a map. When the !attr is a simple string, the key of it will be used as attribute name. If the !attr object is a member of an array then it can only be a map. When the !attr object is a map then the keys and values will be used as attribute keys and values. You have to use this form in a map if the name of the attribute is the same as one of the content keys.

Jamal source
{@yaml:define z=
tagValues:
  myTag: !attr my name
  yourTag: !attr your name
  cAttributeMustBeAMap: !attr {c: colliding tag name}
  c:
    - !attr {name: my name}
    - !tag cc
    - 1
    - 2
}
{#xmlFormat {@yaml:xml (tag="tag" attributes="a=\"53\"")z}}

will be converted to

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<tag a="53">
    <tagValues c="colliding tag name" myTag="my name" yourTag="your name">
        <c name="my name">
            <cc>1</cc>
            <cc>2</cc>
        </c>
    </tagValues>
</tag>
  • !text will be used as the text value for the tag containing this object. In an ordinary situation this just happens when you specify a string. In those cases there is no need for this class. It is only needed when you specified an attribute for some object using an !attr for an object, which is supposed to be a text. To use the !attr you had to specify the object as a Map already containing a key with the attribute. When the conversion sees the !text object it knows that it must treat this Map as a special one and should convert it to a simple tag with a text content. If you use this object type it has to be the last in the map. Any further keys will result error.

Jamal source
{@yaml:define z=
text_tag:
  name: !attr Peter Muster
  textContent: !text This is the text content
}
{#xmlFormat {@yaml:xml (tag="myXml")z}}

will result in:

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<myXml>
    <text_tag name="Peter Muster">This is the text content</text_tag>
</myXml>
  • !cdatatext is the same as the TEXT, but it will also enclose the text as CDATA.

Jamal source
{@yaml:define z=
text_tag:
  name: !attr Peter Muster
  textContent: !cdatatext This is the text content
}
{#xmlFormat {@yaml:xml (tag="myXml")z}}

will result in:

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<myXml>
    <text_tag name="Peter Muster"><![CDATA[This is the text content]]></text_tag>
</myXml>
  • !cdata will instruct the converter to convert the actual node to CDATA.

Jamal source
{@yaml:define z=
text_tag:
  name: !attr Peter Muster
  textContent: !cdata This is the text content
}
{#xmlFormat {@yaml:xml (tag="myXml")z}}

will result in:

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<myXml>
    <text_tag name="Peter Muster">
        <textContent><![CDATA[This is the text content]]></textContent>
    </text_tag>
</myXml>
  • !tag can specify a TAG name for the list members. The default behaviour is that a list will be converted to a <As><A></A><A></A>…​</As> structure. Here As is the plural form of a word, like dependencies. The conversion will calculate the singular in the very simple way chopping off the last character. In the example case it will be dependencie, which is eventually wrong. To save the day a object TAG can be used. The value of this object will be used as the tag for the list elements. The TAG object can be interleaved with !attr objects, but it should never be specified twice for the same list and it always should preceed the first "real" list member. You MUST use a TAG if the enclosing object tag is a single character.

    You can find examples of the use of these classes in the file TestXml.jyt

Jamal source
{@yaml:define z=
tagValues:
- a
- b
- c}
{#xmlFormat {@yaml:xml z}}

will be converted to

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<xml>
    <tagValues>
        <tagValue>a</tagValue>
        <tagValue>b</tagValue>
        <tagValue>c</tagValue>
    </tagValues>
</xml>

The tag names in the list is the same as the one containing the list with the last character chopped off. The convention is that map members that contain lists should be some plural nouns having an extra 's' at the end.

If a list element is a list itself then the iterated tag value will be the same as the enclosing one chopping off another character again. For example:

Jamal source
{@yaml:define z=
tagValues:
- a
- [x, y, z, k]
- c}
{#xmlFormat {@yaml:xml (tag=tagV)z}}

will be converted to

output
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<tagV>
    <tagValues>
        <tagValue>a</tagValue>
        <tagValue>
            <tagValu>x</tagValu>
            <tagValu>y</tagValu>
            <tagValu>z</tagValu>
            <tagValu>k</tagValu>
        </tagValue>
        <tagValue>c</tagValue>
    </tagValues>
</tagV>

There is a limitation in the Yaml structure. Yaml structures can be recursive but XML cannot be. In case the Yaml structure is recursive or too deep (by default 300) then Jamal will stop the evaluation.

Note
This macro was introduced in Jamal version 1.7.5. The version had a major bug that rendered this macro unusable. Version 1.7.6 extended the conversion from Yaml to XML making it possible to use CDATA sections, spefify tag names for lists and to add attributes to tags.

xii. yaml:output

The macro yaml:output redefines the output of the Jamal processing. The format of the macro is:

Jamal source
{@yaml:output yamlMacro}

Here the yamlMacro is the name of a YAML macro to be rendered as the final output of the Jamal processing. It has to be defined at the end of the processing. It also means that this macro has to be on the top level in the macro hierarchy. In other words, it has to be a global macro.

When this macro is used, the output of the Jamal processing will be the Yaml formatted structure of the data held in the macro yamlMacro. If this macro contained references and was not yet resolved, then it will be resolved. Since this is the last step to processing the whole Jamal structure following the entire process, usually there is no need for cloning. If for any reason there is need for cloning then the clone option may be used on the command. The command also supports the copy option.

xiii. Looping over values

There is no special macro to support looping over values in a YAML structure. It can be done using the core for macro.

The core for macro has two forms. The conventional is when the macro loops over strings provided in the macro invocation. In this case the loop the in keyword separates the variables from the value list.

The other case is when the looping is over some Java object. In this case the from keyword separates the variable from the macro holding the object. These macros must be so-called "object holder" macros, and the user-defined YAML macros are such.

In the following example we have a YAML structure defined in the macro games. This structure has two lists, PCgames and ConsoleGames. We want to loop over the PCgames list.

Jamal source
{@yaml:define games=
PCgames:
  - "Doom"
  - "Quake"
  - "Unreal"
ConsoleGames:
    - "Mario"
    - "Zelda"
    - "Metroid"
    }
{@yaml:set cGames=/games.PCgames}
{@for game from cGames=
- game}

The output is the list of the games under the PCgames key.

output
- Doom
- Quake
- Unreal

Note that we had to use the macro yaml:set to set the cGames macro to the PCgames list. That is because the for macro can only loop over an object provided by a macro. Writing

Jamal source
{@yaml:define games=
PCgames:
- "Doom"
- "Quake"
- "Unreal"
ConsoleGames:
- "Mario"
- "Zelda"
- "Metroid"
}
{@yaml:set cGames=/games.PCgames}
{@try! {#for game from {@yaml:get /games.PCgames}
- game}}

will result in the error message:

output
The user defined macro '[Doom,' does not exist or cannot be used as data source for a 'for' loop.

because the {@yaml:get /games.PCgames} macro evaluated is a string and not a macro. As you can see from the error message, the result of the macro is the list of the game names.

You can iterate through lists and maps in the same way. When you iterate through maps you will get the keys:

Jamal source
{@yaml:define cGames=
  first: "Doom"
  second: "Quake"
  third: "Unreal"
}
{@for game from cGames=
- game}

The output is the list of the games under the PCgames key.

output
- first
- second
- third

If you want to go deeper, you can use the loop variable as a key in the map. For example, you can loop through the list of games by key:

Jamal source
cGames is still defined from the prior example
{!@for game from cGames=
{@yaml:get (from=cGames) game}}

The output is the list of the games under cGames

output
cGames is still defined from the prior example

Doom
Quake
Unreal

For a full-fledged example how to document an OpenAPI stack using Jamal and the YAML macros, visit the documentation at https://github.com/serverless-u/AxsessGard/blob/main/README.adoc.jam.