Macros
Introduction
Macros highly facilitate the writing of complex CFoods. Consider the following common example:
ExperimentalData:
type: Directory
match: ExperimentalData
subtree:
README:
type: SimpleFile
match: ^README.md$
records:
ReadmeFile:
parents:
- MarkdownFile
role: File
path: $README
file: $README
This example just inserts a file called README.md
contained in Folder ExpreimentalData/
into CaosDB, assigns the parent (RecordType) MarkdownFile
and allows for later referencing this entity within the cfood. As file objects are created in the cfood specification using the records
section with the special role File
, defining and using many files can become very cumbersome and make the cfood file difficult to read.
The same version using cfood macros could be defined as follows:
---
metadata:
macros:
- !defmacro
name: MarkdownFile
params:
name: null
filename: null
definition:
${name}_filename:
type: SimpleFile
match: $filename
records:
$name:
parents:
- MarkdownFile
role: File
path: ${name}_filename
file: ${name}_filename
---
ExperimentalData:
type: Directory
match: ExperimentalData
subtree: !macro
MarkdownFile:
- name: README
filename: ^README.md$
The “MarkdownFile” key and its value will be replaced by everything that is given below “definition” in the Macro.
The expanded version of ExperimentalData will look like:
ExperimentalData:
match: ExperimentalData
subtree:
README_filename:
match: ^README.md$
records:
README:
file: README_filename
parents:
- MarkdownFile
path: README_filename
role: File
type: SimpleFile
type: Directory
This example can also be found in the macro unit tests (see unittests.test_macros.test_documentation_example_2()
).
Mixing macros and plain definitions
You can also mix macros and plain definitions. Whenever a name cannot be resolved to a macro, a plain yaml node definition is used as a fallback:
---
metadata:
macros:
- !defmacro
name: MarkdownFile
# ... Definition here ...
---
ExperimentalData:
type: Directory
match: ExperimentalData
subtree: !macro
MarkdownFile:
- name: README
filename: ^README.md$
OtherContent: # There is no macro named "OtherContent", so this is parsed as normal content.
type: SimpleFile
match: .*txt
records:
# ... Normal content ...
Complex example
Let’s try something more complex: what happens to multiple $
? This example demonstrates the use
of macro variable substitutions to generate crawler variable substitutions:
$$
will be converted into$
.$$$nodename
will retain a single$
and substitute$nodename
during macro expansion.So in the cfood, if
nodename: value
, the string$$$nodename
will be converted to$value
.
macros:
- !defmacro
name: SimulationDatasetFile
params:
match: null
recordtype: null
nodename: null
definition:
$nodename:
match: $match
type: SimpleFile
records:
File:
parents:
- $recordtype
role: File
path: $$$nodename
file: $$$nodename
Simulation:
$recordtype: +$File
The expanded version of the example above (with nodename: Dataset
) can be seen
here:
SimulationData:
match: SimulationData
subtree:
Dataset:
match: .*
records:
File:
file: $Dataset
parents:
- DatasetFile
path: $Dataset
role: File
Simulation:
DatasetFile: +$File
type: SimpleFile
type: Directory
This example can also be found in the macro unit tests (see
unittests.test_macros.test_documentation_example_1()
).
Using macros multiple times
To use the same macro multiple times in the same yaml node, lists can be used:
---
metadata:
macros:
- !defmacro
name: test_twice
params:
macro_name: default_name
a: 4
definition:
$macro_name:
something:
a: $a
---
extroot: !macro
test_twice:
- macro_name: once # <- This is the first replacement of the macro
- macro_name: twice # <- This is the second one, with different arguments
a: 5
- {} # <- This is the third one, just using default arguments
This example is taken from the macro unit tests (see unittests.test_macros.test_use_macro_twice()
).
The example will be expanded to:
extroot:
default_name:
something:
a: '4'
once:
something:
a: '4'
twice:
something:
a: '5'
Note, that you need to make sure that subsequent macro calls do not
use the same top level key. Because later versions would overwrite previous
ones. Here we used $macro_name
to prevent that.
Limitations
Currently it is not possible to use the same macro twice in the same yaml node, if it occurs in different positions. Consider:
---
metadata:
macros:
- !defmacro
name: test_twice
params:
macro_name: default_name
a: 4
definition:
$macro_name:
something:
a: $a
---
extroot: !macro
test_twice:
- macro_name: once # <- This is the first replacement of the macro
Other_node:
type: test
test_twice: # This is NOT possible as each key
# can only appear once in a yaml node.
- macro_name: twice # <- This is the second one, with different arguments
a: 5
- {} # <- This is the third one, just using default arguments
This should not be a real limitation however, as the order of nodes does not matter for the crawler.
Using macros within macro definitions
It is possible to use other macros in macro definitions. Again, examples can be found in
the macro unit tests (see e.g. unittests.test_macros.test_macros_in_macros()
):
---
metadata:
crawler-version: 0.3.1
macros:
- !defmacro
name: one_macro
params:
a: 25
definition:
macro_sub_$a:
b: $a
another_param: 3
- !defmacro
name: test_macrodef
params: {}
definition:
macro_top: !macro
one_macro:
- a: 17
- {}
- a: 98
not_macro:
a: 26
---
extroot: !macro
test_macrodef:
TODO: to be continued