YAML Parsing
============

AccelForge inputs can be evaluated from YAML files. YAML parsing occurs once when YAML
files are loaded into Python.

We use an extended version of the standard YAML syntax, including the ``<<`` and ``<<<``
operators. ``<<``, when used as a dictionary key, will merge the contents of its value
with the current dictionary. ``<<<`` will merge the contents of its value and will merge
nested dictionaries. The ``!nomerge`` tag will block merging from occuring.

The following is a YAML parsing cheat sheet:

.. code-block:: yaml

  # YAML Nodes
  listNode:
  - element1
  - element2

  dict_node:
    key1: value1
    key2: value2

  # Styles
  list_block_style:
  - element1
  - element2
  list_flow_style: {element1, element2}

  dict_block_style:
    key1: value1
    key2: value2
  dict_flow_style: {key1: value1, key2: value2}

  # Anchors, Aliases, and Merge Keys

  # Anchors
  anchored_list_flow_style: &my_anchored_list
  - element1
  - element2
  anchored_list_block_style: &my_anchored_list [1, 2, 3, 4, 5]

  anchored_dict_flow_style: &my_anchored_dict
    key1: value1
    key2: value2
  anchored_dict_block_style: &my_anchored_dict {key1: value1, key2: value2}

  # Aliases
  my_list_alias: *my_anchored_list
  result_of_my_list_alias: [1, 2, 3, 4, 5]

  my_dict_alias: *my_anchored_dict
  result_of_my_dict_alias: {key1: value1, key2: value2}

  # Merge Keys
  anchored_dict_1: &my_anchored_dict
    key1: value1_dict1
    key2: value2_dict1

  anchored_dict_2: &my_anchored_dict2
    key2: value2_dict2
    key3: value3_dict2

  merged_dict:
    <<: [*my_anchored_dict, *my_anchored_dict2] # My_anchored_dict takes precedence

  result_of_merged_dict:
    key1: value1_dict1
    key2: value2_dict1 # Earlier anchors take precedence
    key3: value3_dict2

  merged_dict2:
    <<: *my_anchored_dict
    value2: override_value2 # Override value2

  result_of_merged_dict2:
    key1: value1_dict1
    key2: override_value2

  # Hierarchical Merge Keys
  anchored_dict_hierarchical_1: &my_anchored_dict
    key1: value1_dict1
    key2: {subkey1: subvalue1, subkey2: subvalue2}
    mylist: [d, e, f]
    mylist_nomerge: [4, 5, 6]

  merged_dict_hierarchical:
    <<<: *my_anchored_dict
    key2: {subkey1: override1} # subkey2: subvalue2 will come from the merge
    mylist: [a, b, c]
    mylist_nomerge: !nomerge [1, 2, 3]

  result_of_merged_dict_hierarchical:
    key1: value1_dict1
    key2: {subkey1: override1, subkey2: subvalue2}
    mylist: [a, b, c, d, e, f]
    mylist_nomerge: [1, 2, 3]

  merged_dict_non_hierarchical:
    <<: *my_anchored_dict
    key2: {subkey1: override1} # This will override all of key2
    mylist: [a, b, c]
    mylist_nomerge: !nomerge [1, 2, 3]

  result_of_merged_dict_non_hierarchical:
    key1: value1_dict1
    key2: {subkey1: override1}
    mylist: [a, b, c]
    mylist_nomerge: [1, 2, 3]



Jinja2 Templating
-----------------

We also support Jinja2 templating. To substitute Jinja2 variables, the
``jinja_parse_data`` argument can be passed to the
:py:meth:`~accelforge.util._basetypes._FromYAMLAble.from_yaml` function. Additional Jinja2
functions are also supported, including:

- ``add_to_path(path)``: Add a path to the search path for the ``include`` function.

- ``cwd()``: Return the current working directory.

- ``find_path(path)``: Find a file in the search path and return the path to the file.

- ``include(path, key)``: Include a file and return the value of the key. For example,
  ``include(path/x.yaml, a)`` will open the file ``path/x.yaml``, look for a top-level
  dictionary, and return the ``a`` key from that dictionary. Multiple levels of indexing
  can be used, such as ``include(path/x.yaml, a.b.c)``.

- ``include_all(path, key)``: Include all files in a directory and return the value of the
  key. For example, ``include_all(path/dir, a)`` will open all files in the directory
  ``path/dir``, look for a top-level dictionary, and return the ``a`` key from that dictionary.

- ``include_text(path)``: Include a file and return the text of the file.

- ``path_exists(path)``: Check if a file exists in the search path.

The following is a Jinja2 template cheat sheet:

.. code-block:: jinja

  # Add files to be included in the environment
  {{add_to_path('path/to/some/dir')}}
  {{add_to_path('path/to/some/other/dir')}}

  variables:
    var1: 5
    var3: "{{cwd()}}/some_file.yaml" # {{cwd()}} is the directory of this file
    var4: "{{find_path('some_file.yaml')}}" # find_path searches all paths added by add_to_path
    var5: {{set_by_jinja}} # Sets the value to a "set_by_jinja" variable that must be defined

    {% if path_exists('some_file.yaml') %} # Check if a file exists
    var6: "some_file.yaml exists" # Include this line if the file exists
    {% else %}

  arch:
    # Include a subset of the file. Index into the structure with
    # dot-separated keys.
    nodes: {{include('other.arch.yaml', 'arch.nodes')}}

  # Include the entire file
  {{include_text('grab_text_from_file.yaml')}}

  compound_components:
    # Include the subsets of multiple files. They will be merged into one list.
    classes: {{include_all('compound_components/*.yaml', 'compound_components.classes')}}


  {% if enable_text_flag|default(False) %}
  text_included_if_enable_text_flag_is_true: |
    This text will be included if enable_text_flag is true. The |default(False) sets
    the default value of enable_text_flag to False if it is not set.
  {% endif %}
