# YAML.jl Custom Tag Constructor Example: !include

**URL:** <https://discourse.julialang.org/t/yaml-jl-custom-tag-constructor-example-include/3900>\
**Category:** General Usage\
**Created:** [May 24, 2017, 7:42am UTC](https://discourse.julialang.org/t/yaml-jl-custom-tag-constructor-example-include/3900 "2017-05-24T07:42:21Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![joshbode](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/joshbode/32/260_2.png) [@joshbode](https://discourse.julialang.org/u/joshbode)\
**Post date:** [May 24, 2017, 7:42am UTC](https://discourse.julialang.org/t/yaml-jl-custom-tag-constructor-example-include/3900/1 "2017-05-24T07:42:21Z")

</div>

YAML.jl has recently gained support for custom tag constructors ([YAML.jl/pull/33](https://github.com/dcjones/YAML.jl/pull/33)) and to understand how they work, I thought I’d try it out to implement an `!include` tag that can be used to include/merge the contents of arbitrary files into YAML files at parse time.

e.g. if you have files `foo.yaml` and `bar.yaml`

```yaml
# foo.yaml
x: !include bar.yaml
y: [1, 2, 3]

```

```yaml
# bar.yaml
a: 1
b: 2

```

through _some_ process, these should effectively become:

```yaml
x:
  a: 1
  b: 2
y: [1, 2, 3]

```

Ideally, this would work for relative and absolute paths 🙂

Here is a solution using the new tag constructor features in YAML.jl:

```julia
module yaml

using YAML

const _constructors = Dict{AbstractString, Function}()

"""Loads YAML files relative to the current directory"""
function load_file_relative(filename::AbstractString;
    constructors::Dict{AbstractString, Function}=_constructors
)
    filename = abspath(filename)
    cd(dirname(filename)) do
        YAML.load_file(filename, constructors)
    end
end

"""Processes an !include constructor"""
function construct_include(constructor::YAML.Constructor, node::YAML.ScalarNode)
    filename = node.value
    if !isfile(filename)
        throw(YAML.ConstructorError(nothing, nothing, "file $(abspath(filename)) does not exist", node.start_mark))
    end
    if !(last(splitext(filename)) in (".yaml", ".yml", ".json"))
        return readstring(filename)
    end
    # pass forward custom constructors
    constructors = Dict{AbstractString, Function}(
        tag => f for (tag, f) in constructor.yaml_constructors
        if tag !== nothing && ismatch(r"^!\w+$", tag)
    )
    load_file_relative(filename; constructors=constructors)
end
construct_include(::YAML.Constructor, node::YAML.Node) = throw(
    YAML.ConstructorError(nothing, nothing, "expected a scalar node, but found $(typeof(node))", node.start_mark)
)
_constructors["!include"] = construct_include

end

```

Then, `foo.yaml` can be loaded as follows:

```julia
julia> yaml.load_file_relative("/tmp/foo.yaml")
Dict{Any,Any} with 2 entries:
  "x" => Dict{Any,Any}(Pair{Any,Any}("b",2),Pair{Any,Any}("a",1))
  "y" => [1,2,3]

```

Hopefully this is useful to someone!
