From da8fd4f2fd3b1fc622872119315e8ea33f228438 Mon Sep 17 00:00:00 2001 From: David Sillman Date: Sun, 15 Mar 2026 21:29:20 -0400 Subject: [PATCH 1/4] feat: Add support for !ignore tag and related functionality in YAML processing --- README.md | 71 ++++++++++++-- tests/unit/test_ignore.py | 184 +++++++++++++++++++++++++++++++++++ tests/unit/test_reference.py | 51 ++++++++++ yaml_reference/__init__.py | 74 +++++++++++++- 4 files changed, 369 insertions(+), 11 deletions(-) create mode 100644 tests/unit/test_ignore.py diff --git a/README.md b/README.md index 28a7637..daf2958 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # yaml-reference -Using `ruamel.yaml`, support cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, and `!merge`. +Using `ruamel.yaml`, support cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`. Install the package from PyPI with: @@ -14,9 +14,9 @@ uv add yaml-reference ``` ## Spec -![Spec Status](https://img.shields.io/badge/spec%20v0.2.6--4-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.6-4) +![Spec Status](https://img.shields.io/badge/spec%20v0.2.8--0-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.8-0) -This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, and `!merge` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs). +This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs). ## Example @@ -24,15 +24,13 @@ This Python library implements the YAML specification for cross-file references # root.yaml version: "3.1" services: - - !reference - path: "services/website.yaml" + - !reference "services/website.yaml" - !reference path: "services/database.yaml" networkConfigs: - !reference-all - glob: "networks/*.yaml" + !reference-all "networks/*.yaml" tags: !flatten - !reference { path: "common/tags.yaml" } @@ -43,6 +41,14 @@ config: !merge - !reference { path: "config/defaults.yaml" } - !reference { path: "config/overrides.yaml" } +.anchors: !ignore + commonTags: &commonTags + - common:http + - common:security + dbDefaults: &dbDefaults + host: localhost + port: 5432 + ``` Supposing there are `services/website.yaml` and `services/database.yaml` files in the same directory as `root.yaml`, and a `networks` directory with YAML files, the above will be expanded to account for the referenced files with the following Python code: @@ -73,6 +79,46 @@ print(data["networkConfigs"]) data = parse_yaml_with_references("root.yaml", allow_paths=["/allowed/path"]) ``` +For `!reference` and `!reference-all`, both mapping and scalar shorthand forms are supported. These are equivalent: + +```yaml +# Scalar shorthand +service: !reference "services/api.yaml" + +# Mapping form +service: !reference { path: "services/api.yaml" } + +# Scalar shorthand +networks: !reference-all "networks/*.yaml" + +# Mapping form +networks: !reference-all { glob: "networks/*.yaml" } +``` + +Use the mapping form when you need optional arguments such as `anchor`; use the scalar shorthand when you only need `path` or `glob`. + +### The `!ignore` Tag + +The `!ignore` tag marks YAML content that should be parsed but omitted from the final resolved output. The most common use case is a hidden section of reusable anchors that should remain available for aliases elsewhere in the document without being emitted in the resolved result. + +```yaml +.anchors: !ignore + commonLabels: &commonLabels + app: payments + team: platform + defaultResources: &defaultResources + requests: + cpu: "100m" + memory: "128Mi" + +service: + metadata: + labels: *commonLabels + resources: *defaultResources +``` + +When loaded with `load_yaml_with_references`, the `.anchors` key is removed entirely, but the anchors it defined remain usable by aliases elsewhere in the document. + ### The `!merge` Tag The `!merge` tag combines multiple YAML mappings (dictionaries) into a single mapping. This is useful for composing configuration from multiple sources or applying overrides. When you use `!merge`, you provide a sequence of mappings that will be merged together, with later mappings overriding keys from earlier ones. @@ -151,20 +197,25 @@ Note that the `app_name` and `cache_settings` fields from `config.yaml` are not ### VSCode squigglies -To get rid of red squigglies in VSCode when using the `!reference`, `!reference-all`, `!flatten`, and `!merge` tags, you can add the following to your `settings.json` file: +To get rid of red squigglies in VSCode when using the `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` tags, you can add the following to your `settings.json` file: ```json "yaml.customTags": [ + "!reference scalar", "!reference mapping", + "!reference-all scalar", "!reference-all mapping", "!flatten sequence", - "!merge sequence" + "!merge sequence", + "!ignore scalar", + "!ignore sequence", + "!ignore mapping" ] ``` ## CLI interface -There is a CLI interface for this package which can be used to read a YAML file which contains `!reference` tags and dump its contents as pretty-printed JSON with references expanded. This is useful for generating a single file for deployment or other purposes. Note that the keys of mappings will be sorted alphabetically. This CLI interface is used to test the contract of this package against the `yaml-reference-specs` project. +There is a CLI interface for this package which can be used to read a YAML file which contains composition tags such as `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`, and dump its contents as pretty-printed JSON with references expanded and ignored content removed. This is useful for generating a single file for deployment or other purposes. Note that the keys of mappings will be sorted alphabetically. This CLI interface is used to test the contract of this package against the `yaml-reference-specs` project. ```bash $ yaml-reference-cli -h diff --git a/tests/unit/test_ignore.py b/tests/unit/test_ignore.py new file mode 100644 index 0000000..6654bfe --- /dev/null +++ b/tests/unit/test_ignore.py @@ -0,0 +1,184 @@ +from yaml_reference import ( + Ignore, + load_yaml_with_references, + parse_yaml_with_references, + prune_ignores, +) + + +def test_ignore_parse_produces_ignore_object(stage_files): + """Test that !ignore tags are parsed into Ignore objects by parse_yaml_with_references.""" + files = { + "test.yml": "key: !ignore some_value", + } + stg = stage_files(files) + data = parse_yaml_with_references(stg / "test.yml") + assert isinstance(data["key"], Ignore) + + +def test_ignore_dict_value_removed(stage_files): + """Test that a dict value tagged with !ignore is removed from the output.""" + files = { + "test.yml": """\ +keep: hello +drop: !ignore world +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert "keep" in data + assert data["keep"] == "hello" + assert "drop" not in data + + +def test_ignore_list_item_removed(stage_files): + """Test that a list item tagged with !ignore is removed from the output.""" + files = { + "test.yml": """\ +items: + - one + - !ignore two + - three +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["items"] == ["one", "three"] + + +def test_ignore_standalone_value_becomes_none(stage_files): + """Test that a standalone (non-list, non-dict) !ignore value is replaced with None.""" + files = { + "test.yml": "!ignore standalone", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data is None + + +def test_ignore_multiple_items_in_list(stage_files): + """Test that multiple !ignore items in a list are all removed.""" + files = { + "test.yml": """\ +items: + - !ignore a + - b + - !ignore c + - d +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["items"] == ["b", "d"] + + +def test_ignore_multiple_keys_in_dict(stage_files): + """Test that multiple !ignore values in a dict are all removed.""" + files = { + "test.yml": """\ +a: !ignore 1 +b: 2 +c: !ignore 3 +d: 4 +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data == {"b": 2, "d": 4} + + +def test_ignore_mapping_value(stage_files): + """Test that a mapping tagged with !ignore is removed.""" + files = { + "test.yml": """\ +keep: + x: 1 +drop: !ignore + y: 2 + z: 3 +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert "keep" in data + assert "drop" not in data + + +def test_ignore_sequence_value(stage_files): + """Test that a sequence tagged with !ignore is removed when used as a dict value.""" + files = { + "test.yml": """\ +keep: [1, 2] +drop: !ignore [3, 4] +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data == {"keep": [1, 2]} + + +def test_ignore_does_not_affect_non_tagged_content(stage_files): + """Test that !ignore only affects tagged content and leaves everything else intact.""" + files = { + "test.yml": """\ +name: yaml-reference +version: 1.0 +description: !ignore internal note +tags: + - alpha + - !ignore beta + - gamma +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["name"] == "yaml-reference" + assert data["version"] == 1.0 + assert "description" not in data + assert data["tags"] == ["alpha", "gamma"] + + +def test_ignore_in_referenced_file(stage_files): + """Test that !ignore tags in a referenced file are pruned during resolution.""" + files = { + "root.yml": "contents: !reference { path: ./inner.yml }", + "inner.yml": """\ +public: visible +private: !ignore hidden +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "root.yml") + assert data["contents"] == {"public": "visible"} + assert "private" not in data["contents"] + + +def test_prune_ignores_standalone(stage_files): + """Test prune_ignores() directly on a structure containing Ignore objects.""" + data = { + "a": Ignore("should be removed"), + "b": 42, + "c": [1, Ignore("also removed"), 3], + } + result = prune_ignores(data) + assert "a" not in result + assert result["b"] == 42 + assert result["c"] == [1, 3] + + +def test_ignore_preserves_none_values(stage_files): + """Test that existing null/None values in YAML are preserved (not confused with ignored values).""" + files = { + "test.yml": """\ +present: ~ +also_present: null +dropped: !ignore something +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert "present" in data + assert data["present"] is None + assert "also_present" in data + assert data["also_present"] is None + assert "dropped" not in data diff --git a/tests/unit/test_reference.py b/tests/unit/test_reference.py index 2cc476a..6e5c3bd 100644 --- a/tests/unit/test_reference.py +++ b/tests/unit/test_reference.py @@ -46,6 +46,17 @@ def test_reference_load(stage_files): assert data["contents"]["another_inner"]["another_inner2"]["leaf"] == "leaf_value" +def test_reference_load_shorthand(stage_files): + files = { + "test.yml": "hello: world\ncontents: !reference ./inner.yml", + "inner.yml": "inner: inner_value\ninner_list:\n - inner_list_value_1\n - inner_list_value_2", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["hello"] == "world" + assert data["contents"]["inner"] == "inner_value" + + def test_reference_all_load(stage_files): files = { "test.yml": "hello: world\ncontents: !reference-all { glob: ./chapters/*.yml }", @@ -76,6 +87,22 @@ def test_reference_all_load(stage_files): assert {"chapter2_summary": 2} in data["inner"]["open"] +def test_reference_all_load_shorthand(stage_files): + files = { + "test.yml": "hello: world\ncontents: !reference-all ./chapters/*.yml", + "chapters/chapter1.yml": "chapter_value: 1\n", + "chapters/chapter2.yml": "chapter_value: 2\n", + "chapters/chapter3.yml": "chapter_value: 3\n", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["hello"] == "world" + assert len(data["contents"]) == 3 + assert {"chapter_value": 1} in data["contents"] + assert {"chapter_value": 2} in data["contents"] + assert {"chapter_value": 3} in data["contents"] + + def test_parse_references(stage_files): files = { "test.yml": "inner: !reference { path: next/open.yml }\n", @@ -102,6 +129,30 @@ def test_parse_references(stage_files): assert data["open"].location == str((stg / "next/open.yml").absolute()) +def test_parse_reference_shorthand(stage_files): + files = { + "test.yml": "inner: !reference next/open.yml\n", + } + stg = stage_files(files) + data = parse_yaml_with_references(stg / "test.yml") + assert isinstance(data["inner"], Reference) + assert data["inner"].path == "next/open.yml" + assert data["inner"].anchor is None + assert data["inner"].location == str((stg / "test.yml").absolute()) + + +def test_parse_reference_all_shorthand(stage_files): + files = { + "test.yml": "inner: !reference-all next/*.yml\n", + } + stg = stage_files(files) + data = parse_yaml_with_references(stg / "test.yml") + assert isinstance(data["inner"], ReferenceAll) + assert data["inner"].glob == "next/*.yml" + assert data["inner"].anchor is None + assert data["inner"].location == str((stg / "test.yml").absolute()) + + def test_disallow_absolute_path_references(stage_files): """Test that absolute path references are disallowed.""" actual_file = Path("/tmp/file.yml") diff --git a/yaml_reference/__init__.py b/yaml_reference/__init__.py index 3180d84..534bbc0 100644 --- a/yaml_reference/__init__.py +++ b/yaml_reference/__init__.py @@ -44,6 +44,8 @@ def __repr__(self): @classmethod def from_yaml(cls, constructor, node): + if node.id == "scalar": + return cls(constructor.construct_scalar(node)) mapping = constructor.construct_mapping(node) path = mapping["path"] anchor = mapping.get("anchor") @@ -86,12 +88,40 @@ def __repr__(self): @classmethod def from_yaml(cls, constructor, node): + if node.id == "scalar": + return cls(constructor.construct_scalar(node)) mapping = constructor.construct_mapping(node) glob = mapping["glob"] anchor = mapping.get("anchor") return cls(glob, anchor) +class Ignore: + """Represents an ignore marker for YAML content. + + This class is used as a marker for YAML content that should be ignored and is registered + with ruamel.yaml to handle the `!ignore` tag. Any content marked with `!ignore` will be + parsed but effectively discarded in the final output. + + Args: + yaml_tag (str): The YAML tag associated with this class, set to `!ignore`. + """ + + content: Any + yaml_tag = "!ignore" + + def __init__(self, content: Any): + self.content = content + + def __repr__(self): + return f"Ignore(content={repr(self.content)})" + + @classmethod + def from_yaml(cls, constructor, node): + content = constructor.construct_object(node) + return cls(content) + + class Flatten: """Represents a flattening operation for nested sequences. @@ -354,6 +384,7 @@ def parse_yaml_with_references( yaml.register_class(ReferenceAll) yaml.register_class(Flatten) yaml.register_class(Merge) + yaml.register_class(Ignore) if not anchor: with path.open("r") as f: @@ -374,6 +405,12 @@ def _recursively_attribute_location_to_references(data: Any, base_path: Path): for item in data.sequence ] ) + if isinstance(data, Ignore): + return Ignore( + content=_recursively_attribute_location_to_references( + data.content, base_path + ) + ) if isinstance(data, Merge): return Merge( sequence=[ @@ -476,6 +513,13 @@ def _recursively_resolve_references( ] ) + if isinstance(data, Ignore): + return Ignore( + content=_recursively_resolve_references( + data.content, allow_paths=allow_paths, visited_paths=visited_paths + ) + ) + if isinstance(data, Merge): return Merge( sequence=[ @@ -596,6 +640,27 @@ def merge_mappings(data: Any) -> Any: return data +def prune_ignores(data: Any) -> Any: + """ + Given an object which may contain Ignore(...) objects which was parsed from a YAML document containing !ignore + tags, return the object with all Ignore(...) objects removed. If an Ignore(...) object is found in a list, it is + removed from the list. If an Ignore(...) object is found as a value in a dict, the key-value pair is removed from + the dict. If an Ignore(...) object is found as a value which is not in a list or dict, it is replaced with None. + """ + if isinstance(data, Ignore): + return None + if isinstance(data, list): + return [prune_ignores(item) for item in data if not isinstance(item, Ignore)] + elif isinstance(data, dict): + return { + key: prune_ignores(value) + for key, value in data.items() + if not isinstance(value, Ignore) + } + else: + return data + + def load_yaml_with_references( file_path: PathLike, allow_paths: Sequence[PathLike] = [] ) -> Any: @@ -633,7 +698,12 @@ def load_yaml_with_references( allow_paths=allow_paths, # type: ignore visited_paths=visited_paths, ) - flattened = flatten_sequences(resolved) + # Prune ignores after full resolution so that Ignore wrappers introduced by + # referenced files propagate up to their parent containers, allowing keys and + # list items whose resolved value is !ignore to be dropped entirely rather + # than replaced with null. + pruned = prune_ignores(resolved) + flattened = flatten_sequences(pruned) merged = merge_mappings(flattened) return merged @@ -645,4 +715,6 @@ def load_yaml_with_references( "Flatten", "merge_mappings", "Merge", + "prune_ignores", + "Ignore", ] From e51fe079503e23eaf25e8cbd89efee63fc97b8c5 Mon Sep 17 00:00:00 2001 From: David Sillman Date: Sun, 15 Mar 2026 21:51:47 -0400 Subject: [PATCH 2/4] fix: Add tests for ignoring items in !flatten and !merge sequences --- README.md | 2 ++ tests/unit/test_flatten.py | 15 +++++++++++++++ tests/unit/test_merge.py | 15 +++++++++++++++ yaml_reference/__init__.py | 16 ++++++++++++++++ 4 files changed, 48 insertions(+) diff --git a/README.md b/README.md index daf2958..ae0315f 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,8 @@ service: When loaded with `load_yaml_with_references`, the `.anchors` key is removed entirely, but the anchors it defined remain usable by aliases elsewhere in the document. +Ignored items are also pruned before `!flatten` and `!merge` are evaluated, so an ignored sequence entry inside either tag is simply omitted from the flattened or merged result. + ### The `!merge` Tag The `!merge` tag combines multiple YAML mappings (dictionaries) into a single mapping. This is useful for composing configuration from multiple sources or applying overrides. When you use `!merge`, you provide a sequence of mappings that will be merged together, with later mappings overriding keys from earlier ones. diff --git a/tests/unit/test_flatten.py b/tests/unit/test_flatten.py index 2cc7499..5bbebcb 100644 --- a/tests/unit/test_flatten.py +++ b/tests/unit/test_flatten.py @@ -197,6 +197,21 @@ def test_flatten_with_scalars(stage_files): assert data["data"] == [1, 2, 3, 4, 5, 6, 7] +def test_flatten_ignores_ignored_sequence_items(stage_files): + """Test that !ignore items inside a !flatten sequence are omitted from the flattened result.""" + files = { + "test.yml": """ +data: !flatten + - [1, 2] + - !ignore [99, 100] + - [3, 4] +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["data"] == [1, 2, 3, 4] + + def test_flatten_mixed_objects_references(stage_files): """Test flattening a sequence of objects, references, and reference-all tags.""" files = { diff --git a/tests/unit/test_merge.py b/tests/unit/test_merge.py index 4337af2..fe6c082 100644 --- a/tests/unit/test_merge.py +++ b/tests/unit/test_merge.py @@ -124,6 +124,21 @@ def test_merge_nested(stage_files): assert data["result"] == {"a": 1, "b": 2, "inner": {"x": 2, "y": 1}} +def test_merge_ignores_ignored_sequence_items(stage_files): + """Test that !ignore items inside a !merge sequence are omitted before merging.""" + files = { + "test.yml": """ +result: !merge + - {a: 1} + - !ignore {ignored: true} + - {b: 2} +""", + } + stg = stage_files(files) + data = load_yaml_with_references(stg / "test.yml") + assert data["result"] == {"a": 1, "b": 2} + + def test_flatten_and_merge(stage_files): """Test flattening and merging together.""" diff --git a/yaml_reference/__init__.py b/yaml_reference/__init__.py index 534bbc0..ff22808 100644 --- a/yaml_reference/__init__.py +++ b/yaml_reference/__init__.py @@ -649,6 +649,22 @@ def prune_ignores(data: Any) -> Any: """ if isinstance(data, Ignore): return None + if isinstance(data, Flatten): + return Flatten( + sequence=[ + prune_ignores(item) + for item in data.sequence + if not isinstance(item, Ignore) + ] + ) + if isinstance(data, Merge): + return Merge( + sequence=[ + prune_ignores(item) + for item in data.sequence + if not isinstance(item, Ignore) + ] + ) if isinstance(data, list): return [prune_ignores(item) for item in data if not isinstance(item, Ignore)] elif isinstance(data, dict): From ea4a77e57f8aa612f38814ea38c1d4d8464004aa Mon Sep 17 00:00:00 2001 From: David Sillman Date: Sun, 15 Mar 2026 21:54:48 -0400 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- yaml_reference/__init__.py | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ae0315f..43cec17 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # yaml-reference -Using `ruamel.yaml`, support cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`. +Using `ruamel.yaml`, yaml-reference supports cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`. Install the package from PyPI with: diff --git a/yaml_reference/__init__.py b/yaml_reference/__init__.py index ff22808..16945f9 100644 --- a/yaml_reference/__init__.py +++ b/yaml_reference/__init__.py @@ -104,7 +104,7 @@ class Ignore: parsed but effectively discarded in the final output. Args: - yaml_tag (str): The YAML tag associated with this class, set to `!ignore`. + content (Any): The YAML content associated with this tag that should be ignored. """ content: Any @@ -118,7 +118,18 @@ def __repr__(self): @classmethod def from_yaml(cls, constructor, node): - content = constructor.construct_object(node) + # Construct the underlying content based on the node type instead of + # calling construct_object(node), which can recurse back into this + # method or yield partially constructed placeholders with ruamel.yaml. + if node.id == "scalar": + content = constructor.construct_scalar(node) + elif node.id == "sequence": + content = constructor.construct_sequence(node) + elif node.id == "mapping": + content = constructor.construct_mapping(node) + else: + # Fallback for any unexpected node types. + content = constructor.construct_object(node) return cls(content) From 9c0a627e052fcb408cfca13991ee704681ed204d Mon Sep 17 00:00:00 2001 From: David Sillman Date: Sun, 15 Mar 2026 22:26:42 -0400 Subject: [PATCH 4/4] fix: Update spec status badge to reflect version 0.2.8-1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 43cec17..e1b6866 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ uv add yaml-reference ``` ## Spec -![Spec Status](https://img.shields.io/badge/spec%20v0.2.8--0-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.8-0) +![Spec Status](https://img.shields.io/badge/spec%20v0.2.8--1-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.8-1) This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs).