Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
.DS_Store
Manifest.toml
.vscode
83 changes: 64 additions & 19 deletions src/PortableStructs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -276,10 +276,72 @@ function from_dict(::Type{T}, dict::AbstractDict; type_key, base_module) where {
elseif isconcretetype(T)
# println("This type is concrete, so we can construct it directly.")
return from_named_tuple(T, get_children(T, dict; type_key, base_module))
elseif dict isa T
return dict
end
error("Could not construct a $T from the given dictionary:\n\n$(dict)\n\n Adding a \"$type_key\" key would help resolve which type to construct.")
end

function make_exception!(d, path, value)

# Try to match from the beginning of a string to the first dot, capturing everything
# before and everything after the dot.
m = match(r"^([^.]+)\.(.+)$", path)

# If there were no matches, assume this is a new valud to add.
if isnothing(m)

@assert haskey(d, path) "\"$path\" is not a valid key. Available keys: $(keys(d))."
d[path] = value

else

@assert haskey(d, m.captures[1]) "While overwriting the value in \"$path\", the \"$(m.captures[1])\" key was not found. Available keys: $(keys(d))."
make_exception!(d[m.captures[1]], m.captures[2], value)

end

end

function fetch_included_file(d, dir, include::AbstractDict; include_key)

@assert haskey(include, "source") "No source was provided for an include entry."

# See if we should use the given file name (absolute path) or join it with our
# current path. Also, remove that key.
filename = if isabspath(include["source"])
include["source"]
else
joinpath(dir, include["source"])
end
delete!(d, include_key)

# Now do exactly the same thing that was done to get here in the first place.
subdict = YAML.load_file(filename; dicttype = OrderedDict{String, Any})
subdict = expand_include_files(subdict, dirname(filename); include_key)

# Let any other keys in the dictionary overwrite what we loaded (the parent is
# allowed to overwrite the child).
d = merge(subdict, d)

# Now process the "except"s.
if haskey(include, "except")
for exception in include["except"]
make_exception!(d, exception["path"], exception["value"])
end
end

return d

end

function fetch_included_file(d, dir, include::AbstractString; include_key)
include = Dict(
"source" => include,
)
return fetch_included_file(d, dir, include; include_key)
end

# Replace "include" with a dictionary loaded from the given file name.
function expand_include_files(d, dir; include_key = "include")

Expand All @@ -292,25 +354,8 @@ function expand_include_files(d, dir; include_key = "include")

# Now if there's an "include" in there, load that file, and expand it the same way we
# were expanded.
if haskey(d, include_key) && d[include_key] isa AbstractString

# See if we should use the given file name (absolute path) or join it with our
# current path. Also, remove that key.
filename = if isabspath(d[include_key])
d[include_key]
else
joinpath(dir, d[include_key])
end
delete!(d, include_key)

# Now do exactly the same thing that was done to get here in the first place.
subdict = YAML.load_file(filename; dicttype = OrderedDict{String, Any})
subdict = expand_include_files(subdict, dirname(filename); include_key)

# Let any other keys in the dictionary overwrite what we loaded (the parent is
# allowed to overwrite the child).
d = merge(subdict, d)

if haskey(d, include_key)
d = fetch_included_file(d, dir, d[include_key]; include_key)
end

# Return the possibly updated, possibly completely replaced dictionary.
Expand Down
7 changes: 7 additions & 0 deletions test/exceptions/bad_exceptions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include:
source: roses.yaml
except:
- path: rosac7eae.malus
value:
- domestica
- fusca
7 changes: 7 additions & 0 deletions test/exceptions/exceptions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
include:
source: roses.yaml
except:
- path: rosaceae.malus
value:
- domestica
- fusca
10 changes: 10 additions & 0 deletions test/exceptions/more_bad_exceptions.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
include:
source: roses.yaml
except:
- path: rosaceae.malus
value:
- domestica
- fusca
- path: rosaceae.arctostaphylos # arctostaphylos isn't in rosaceae.
value:
- uva-ursi
8 changes: 8 additions & 0 deletions test/exceptions/roses.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
rosaceae:
type: Family
pyrus:
- communis
malus:
- domestica
sorbus:
- aucuparia
12 changes: 12 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,18 @@ end

end

@testset "exceptions" begin

x = load_from_yaml("exceptions/exceptions.yaml")
@test x["rosaceae"]["pyrus"] == ["communis"]
@test x["rosaceae"]["malus"] == ["domestica", "fusca"]
@test x["rosaceae"]["sorbus"] == ["aucuparia"]

@test_throws "While overwriting the value in \"rosac7eae.malus\"" load_from_yaml("exceptions/bad_exceptions.yaml")
@test_throws "\"arctostaphylos\" is not a valid key." load_from_yaml("exceptions/more_bad_exceptions.yaml")

end

@testset "custom to_dict and from_dict" begin

file = "out/my_custom_type.yaml"
Expand Down
Loading