diff --git a/stdlib/TOML/src/print.jl b/stdlib/TOML/src/print.jl index 71896b7a1d717..0cbee990d8ae1 100644 --- a/stdlib/TOML/src/print.jl +++ b/stdlib/TOML/src/print.jl @@ -2,6 +2,7 @@ import Dates +import Base: @invokelatest import ..isvalid_barekey_char function printkey(io::IO, keys::Vector{String}) @@ -20,46 +21,36 @@ function printkey(io::IO, keys::Vector{String}) end const MbyFunc = Union{Function, Nothing} -const TOMLValue = Union{AbstractVector, AbstractDict, Dates.DateTime, Dates.Time, Dates.Date, Bool, Integer, AbstractFloat, String} -function printvalue(f::MbyFunc, io::IO, value::AbstractVector; sorted=false) +const TOMLValue = Union{AbstractVector, AbstractDict, Dates.DateTime, Dates.Time, Dates.Date, Bool, Integer, AbstractFloat, AbstractString} +function printvalue(f::MbyFunc, io::IO, value::AbstractVector; sorted=false, by=identity) Base.print(io, "[") for (i, x) in enumerate(value) i != 1 && Base.print(io, ", ") if isa(x, AbstractDict) - _print(f, io, x; sorted) + _print(f, io, x; sorted, by) else - printvalue(f, io, x; sorted) + printvalue(f, io, x; sorted, by) end end Base.print(io, "]") end -function printvalue(f::MbyFunc, io::IO, value; sorted) - if f === nothing - error("type `$(typeof(value))` is not a valid TOML type, pass a conversion function to `TOML.print`") - end - toml_value = f(value) - if !(toml_value isa TOMLValue) - error("TOML syntax function for type `$(typeof(value))` did not return a valid TOML type but a `$(typeof(toml_value))`") - end - Base.invokelatest(printvalue, f, io, toml_value; sorted) -end -printvalue(f::MbyFunc, io::IO, value::AbstractDict; sorted) = - _print(f, io, value; sorted) -printvalue(f::MbyFunc, io::IO, value::Dates.DateTime; sorted) = +printvalue(f::MbyFunc, io::IO, value::AbstractDict; sorted=false, by=identity) = + _print(f, io, value; sorted, by) +printvalue(f::MbyFunc, io::IO, value::Dates.DateTime; _...) = Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd\THH:MM:SS.sss\Z")) -printvalue(f::MbyFunc, io::IO, value::Dates.Time; sorted) = +printvalue(f::MbyFunc, io::IO, value::Dates.Time; _...) = Base.print(io, Dates.format(value, Dates.dateformat"HH:MM:SS.sss")) -printvalue(f::MbyFunc, io::IO, value::Dates.Date; sorted) = +printvalue(f::MbyFunc, io::IO, value::Dates.Date; _...) = Base.print(io, Dates.format(value, Dates.dateformat"YYYY-mm-dd")) -printvalue(f::MbyFunc, io::IO, value::Bool; sorted) = +printvalue(f::MbyFunc, io::IO, value::Bool; _...) = Base.print(io, value ? "true" : "false") -printvalue(f::MbyFunc, io::IO, value::Integer; sorted) = +printvalue(f::MbyFunc, io::IO, value::Integer; _...) = Base.print(io, Int64(value)) # TOML specifies 64-bit signed long range for integer -printvalue(f::MbyFunc, io::IO, value::AbstractFloat; sorted) = +printvalue(f::MbyFunc, io::IO, value::AbstractFloat; _...) = Base.print(io, isnan(value) ? "nan" : isinf(value) ? string(value > 0 ? "+" : "-", "inf") : Float64(value)) # TOML specifies IEEE 754 binary64 for float -printvalue(f::MbyFunc, io::IO, value::AbstractString; sorted) = Base.print(io, "\"", escape_string(value), "\"") +printvalue(f::MbyFunc, io::IO, value::AbstractString; _...) = Base.print(io, "\"", escape_string(value), "\"") is_table(value) = isa(value, AbstractDict) is_array_of_tables(value) = isa(value, AbstractArray) && @@ -70,8 +61,8 @@ function _print(f::MbyFunc, io::IO, a::AbstractDict, ks::Vector{String} = String[]; indent::Int = 0, first_block::Bool = true, - sorted::Bool, - by::Function, + sorted::Bool = false, + by::Function = identity, ) akeys = keys(a) if sorted @@ -82,11 +73,25 @@ function _print(f::MbyFunc, io::IO, a::AbstractDict, for key in akeys value = a[key] is_tabular(value) && continue - Base.print(io, ' '^4max(0,indent-1)) - printkey(io, [String(key)]) - Base.print(io, " = ") # print separator - printvalue(f, io, value; sorted) - Base.print(io, "\n") # new line? + if !isa(value, TOMLValue) + if f === nothing + error("type `$(typeof(value))` is not a valid TOML type, pass a conversion function to `TOML.print`") + end + toml_value = f(value) + if !(toml_value isa TOMLValue) + error("TOML syntax function for type `$(typeof(value))` did not return a valid TOML type but a `$(typeof(toml_value))`") + end + value = toml_value + end + if is_tabular(value) + _print(f, io, Dict(key => value); indent, first_block, sorted, by) + else + Base.print(io, ' '^4max(0,indent-1)) + printkey(io, [String(key)]) + Base.print(io, " = ") # print separator + printvalue(f, io, value; sorted, by) + Base.print(io, "\n") # new line? + end first_block = false end @@ -105,7 +110,7 @@ function _print(f::MbyFunc, io::IO, a::AbstractDict, Base.print(io,"]\n") end # Use runtime dispatch here since the type of value seems not to be enforced other than as AbstractDict - Base.invokelatest(_print, f, io, value, ks; indent = indent + header, first_block = header, sorted, by) + @invokelatest _print(f, io, value, ks; indent = indent + header, first_block = header, sorted, by) pop!(ks) elseif is_array_of_tables(value) # print array of tables @@ -119,7 +124,7 @@ function _print(f::MbyFunc, io::IO, a::AbstractDict, Base.print(io,"]]\n") # TODO, nicer error here !isa(v, AbstractDict) && error("array should contain only tables") - Base.invokelatest(_print, f, io, v, ks; indent = indent + 1, sorted, by) + @invokelatest _print(f, io, v, ks; indent = indent + 1, sorted, by) end pop!(ks) end diff --git a/stdlib/TOML/test/print.jl b/stdlib/TOML/test/print.jl index d6bcdf10d4f17..10bb48ffae411 100644 --- a/stdlib/TOML/test/print.jl +++ b/stdlib/TOML/test/print.jl @@ -21,12 +21,34 @@ struct MyStruct a::Int end @test_throws ErrorException toml_str(Dict("foo" => MyStruct(1))) +# simple value @test toml_str(Dict("foo" => MyStruct(1))) do x x isa MyStruct && return x.a end == """ foo = 1 """ +# tabular values +@test toml_str(Dict("foo" => MyStruct(1)); sorted=true) do x + x isa MyStruct && return [x.a] + end == """ + foo = [1] + """ +@test toml_str(Dict("foo" => MyStruct(1)); sorted=true) do x + x isa MyStruct && return Dict(:bar => x.a) + end == """ + [foo] + bar = 1 + """ + +# validation against the usual case +@test toml_str(Dict("foo" => MyStruct(1)); sorted=true) do x + x isa MyStruct && return [x.a] + end == toml_str(Dict("foo" => [1]); sorted=true) +@test toml_str(Dict("foo" => MyStruct(1)); sorted=true) do x + x isa MyStruct && return Dict(:bar => x.a) + end == toml_str(Dict("foo" => Dict(:bar => 1)); sorted=true) + @test toml_str(Dict("b" => SubString("foo"))) == "b = \"foo\"\n" @testset "empty dict print" begin