jli  Linuxx86_641.10.3v1.10.30b4590a5507d3f3046e5bafc007cacbbfc9b310bq UConfigurationsJRm.a79>/opt/julia/packages/Configurations/Yxczn/src/Configurations.jl1Av3TOI`&TOML$H5UExproniconLite#]i^r^XźOrderedCollections6/opt/julia/packages/Configurations/Yxczn/src/compat.jl1A6/opt/julia/packages/Configurations/Yxczn/src/errors.jl1A5/opt/julia/packages/Configurations/Yxczn/src/types.jl1A7/opt/julia/packages/Configurations/Yxczn/src/convert.jl1A:/opt/julia/packages/Configurations/Yxczn/src/reflection.jl1A5/opt/julia/packages/Configurations/Yxczn/src/utils.jl1A7/opt/julia/packages/Configurations/Yxczn/src/codegen.jl1A9/opt/julia/packages/Configurations/Yxczn/src/from_dict.jl1A;/opt/julia/packages/Configurations/Yxczn/src/from_kwargs.jl1A9/opt/julia/packages/Configurations/Yxczn/src/from_toml.jl1A9/opt/julia/packages/Configurations/Yxczn/src/serialize.jl1A CoremуJ5Basemу]J5MainmуJ5ArgToolsBń x(mуF K5 Artifactsmr-V3|mу K5Base64UlD*_mу> K5CRC32c\y.jmуj K5 FileWatchingXzsy`{,zmуh& K5LibdluVW59˗,mу-" K5LoggingT{VhUXM=mуrU" K5MmapP~:xg,Omу|' K5NetworkOptionsC0YW,mуʠ, K5SHAQ<$!<%mу1 K5 Serialization [)*k1mу-G K5Sockets1V$ bdސݗmуYBY K5UnicodeP>I>Nrmуeszo K5 LinearAlgebraSm7̏mуuux K5 OpenBLAS_jll[(Śb6EcQ FmуDux K5libblastrampoline_jllLSۆ }lxӠmу^} K5MarkdownZPn7z`smу/Ed~ K5Printfg^cX׸QDmу;h K5Random_ɢ?\Ymу? K5TarOi>աmу!t, K5DatesEY8pj2 mуX K5FuturebS;3{I xVMmуsD K5InteractiveUtilsWL ~@'ZmуVg K5LibGit2Z[&RPTv3EКRmу8J K5 LibGit2_jll YXg}]$mуD K5 MbedTLS_jllAX 3ȡ_mу- K5 LibSSH2_jlloTZk)߆/opt/julia/packages/Configurations/Yxczn/src/Configurations.jlmodule Configurations using TOML using ExproniconLite using OrderedCollections export @option, @type_alias, # main interface from_dict, from_kwargs, from_toml, to_dict, to_toml, # builtin types no_default, Maybe, Reflect, OptionField, # reflection field_default, # field_alias, type_alias, # traits is_option, # parse from_dict, from_kwargs, from_toml, from_toml_if_exists, # serialize to_dict, DuplicatedFieldError, DuplicatedAliasError, InvalidKeyError, FieldTypeConversionError, TOMLStyle, YAMLStyle, JSONStyle include("compat.jl") include("errors.jl") include("types.jl") include("convert.jl") include("reflection.jl") include("utils.jl") include("codegen.jl") include("from_dict.jl") include("from_kwargs.jl") include("from_toml.jl") # include("parse.jl") include("serialize.jl") end 6/opt/julia/packages/Configurations/Yxczn/src/compat.jlQ# backward compatibilty @static if VERSION < v"1.1" function fieldtypes(T::Type) ntuple(fieldcount(T)) do idx fieldtype(T, idx) end end end # NOTE: for 1.0 compat @static if !@isdefined(hasfield) function hasfield(T::Type, name::Symbol) return fieldindex(T, name, false) > 0 end end 6/opt/julia/packages/Configurations/Yxczn/src/errors.jl' struct DuplicatedAliasError <: Exception name::String end function Base.showerror(io::IO, err::DuplicatedAliasError) print(io, "DuplicatedAliasError: duplicated alias name: ") return printstyled(io, err.name; color=:cyan) end struct InvalidKeyError <: Exception got::Union{Symbol, String} keys::Union{Vector{Symbol}, Vector{String}} end function Base.showerror(io::IO, err::InvalidKeyError) print(io, "InvalidKeyError: invalid key ") printstyled(io, err.got; color=:light_blue) print(io, ", possible keys are: ") if length(err.keys) > 8 for idx in 1:8 printstyled(io, err.keys[idx]; color=:light_blue) if idx != 8 print(io, ", ") end end print(io, "... please check documentation for other valid keys") else for idx in eachindex(err.keys) printstyled(io, err.keys[idx]; color=:light_blue) if idx != lastindex(err.keys) print(io, ", ") end end end return nothing end """ DuplicatedFieldError(name, type) A field with `name` of given option `type` is duplicated in the subfields option type. Thus one cannot use the field keyword convention when seeing this error. """ struct DuplicatedFieldError <: Exception name::Symbol type end function Base.showerror(io::IO, err::DuplicatedFieldError) print(io, "DuplicatedFieldError: duplicated field ") printstyled(io, err.name; color=:light_blue) print(io, " in type ") printstyled(io, err.type; color=:green) return print(io, " and its sub-fields") end """ FieldTypeConversionError(type, fieldname, fieldtype, optiontype) A conversion from `type` to `fieldtype` belonging to `fieldname` in an `optiontype` failed. """ struct FieldTypeConversionError <: Exception type fieldname::Symbol fieldtype optiontype end function Base.showerror(io::IO, err::FieldTypeConversionError) print(io, "FieldTypeConversionError: conversion from ") printstyled(io, err.type; color=:red) print(io, " to type ") printstyled(io, err.fieldtype; color=:green) print(io, " for field ") printstyled(io, err.fieldname; color=:light_blue) print(io, " in type ") printstyled(io, err.optiontype; color=:green) return print(io, " failed") end 5/opt/julia/packages/Configurations/Yxczn/src/types.jli"maybe of type `T` or nothing" const Maybe{T} = Union{Nothing,T} """ PartialDefault{F} Type for non-constant default value, it depends on the value of another field that has default value. """ struct PartialDefault{F} lambda::F vars::Vector{Symbol} expr::Expr end (f::PartialDefault)(x) = f.lambda(x) function Base.show(io::IO, x::PartialDefault) return print(io, JLFunction(; head=:->, args=x.vars, body=x.expr)) end """ Reflect Placeholder type for reflected type string. # Type Alias if the corresponding type has a [`type_alias`](@ref) defined, serialization and parsing will use the [`type_alias`](@ref) instead of the type name, this only works on concrete types since the alias cannot contain any type var information. # Example the following option struct ```julia @option struct MyOption type::Reflect name::String = "Sam" end ``` would be equivalent to ```toml type = "MyOption" name = "Sam" ``` this is useful for defining list of different types etc. """ struct Reflect end """ create(::Type{T}; kwargs...) where T Create an instance of option type `T` from `kwargs`. Similar to the default keyword argument constructor, but one can use this to create custom keyword argument constructor with extra custom keywords. """ function create(::Type{T}; kwargs...) where {T} return error("$T is not an option type") end 7/opt/julia/packages/Configurations/Yxczn/src/convert.jl""" ConvertNotFound Conversion is not defined via [`convert_to_option`](@ref). One should let the conversion fallback to `Base.convert` when see this. """ struct ConvertNotFound end """ convert_to_option(::Type{OptionType}, ::Type{ValueType}, x) where {OptionType, ValueType} Convert `x` to type `ValueType` for option type `OptionType`. This is similar to `Base.convert`, but will not error if the conversion is not overloaded, and will return a [`ConvertNotFound`](@ref) object, but one can use this to avoid type piracy and define contextual conversion based on option types. !!! compat "Configurations 0.17" This interface is deprecated in favor of [`from_dict`](@ref) from 0.17. # Example One may have different string syntax for a `Symbol`, e.g ```julia @option struct StringIsSymbol name::Symbol end @option struct SymbolNeedsColon name::Symbol end Configurations.convert_to_option(::Type{StringIsSymbol}, ::Type{Symbol}, x::String) = Symbol(x) function Configurations.convert_to_option(::Type{SymbolNeedsColon}, ::Type{Symbol}, x::String) if startswith(x, ':') Symbol(x[2:end]) else error("expect a Symbol, got String") end end ``` then if we run it, we will have different behaviour by context. ```julia julia> from_dict(StringIsSymbol, d) StringIsSymbol(:ccc) julia> from_dict(SymbolNeedsColon, d) ERROR: expect a Symbol, got String ``` """ convert_to_option(::Type, ::Type{T}, x) where {T} = convert_to_option(T, x) convert_to_option(::Type{T}, x) where {T} = ConvertNotFound() :/opt/julia/packages/Configurations/Yxczn/src/reflection.jl""" field_default(::Type{T}, name::Symbol) Return the default value of field `name` of an option type `T`. """ function field_default(::Type{T}, name::Symbol) where {T} if !isconcretetype(T) && is_option(T) error("field_default requires a concrete type, got $T{...}") else error("field_default is not defined for $T, it may not be an option type") end end """ field_defaults(::Type) Return default values of given option types. """ function field_defaults(::Type{T}) where {T} is_option(T) || error("$T is not an option type") return Any[field_default(T, each) for each in fieldnames(T)] end """ type_alias(::Type{OptionType}) -> String Return the alias name of given `OptionType`. """ function type_alias(::Type{T}) where {T} is_option(T) || error("$T is not an option type") return error("`type_alias` is not defined for $T, it may not be an option type") end function set_type_alias(::Type{T}, name::String) where {T} isconcretetype(T) || error("cannot set alias for non concrete type") type_alias_map = get_type_alias_map(T)::Dict{String, Any} type_alias_map[name] = T return end function get_type_alias_map(::Type{T}) where {T} error("`get_type_alias_map` is not defined for `$T`, it may not be an option type") end 5/opt/julia/packages/Configurations/Yxczn/src/utils.jlb""" is_option(x) Check if `x` is an option type or not. """ is_option(x) = false """ compare_options(a, b, xs...)::Bool Compare option types check if they are the same. """ function compare_options(a, b, xs...)::Bool compare_options(a, b) || return false return compare_options(b, xs...) end compare_options(a, b) = false function compare_options(a::A, b::A) where {A} is_option(A) || return a == b # fall through to normal compare for idx in 1:nfields(a) compare_options(getfield(a, idx), getfield(b, idx)) || return false end return true end function tryparse_jltype(s, alias_map=nothing) s isa String || throw(ArgumentError("expect type String, got: $(typeof(s))")) if alias_map !== nothing haskey(alias_map, s) && return alias_map[s] end type_ex = Meta.parse(s) is_datatype_expr(type_ex) || throw(ArgumentError("expect type expression got: $type_ex")) try return eval(type_ex) catch return nothing end end function parse_jltype(s, alias_map=nothing) type = tryparse_jltype(s, alias_map) type === nothing && throw(ArgumentError("cannot parse $s to a Julia type")) return type end # NOTE: copied from JLD # https://github.com/JuliaIO/JLD.jl/blob/83ea0c5ef7293c78d7d9c8ffdf9ede599b54dc4c/src/JLD00.jl#L991 # we only have DataType to serialize function full_typename(jltype::DataType) tname = string(jltype.name.module, ".", jltype.name.name) if isempty(jltype.parameters) return tname else params_str = join([full_typename(x) for x in jltype.parameters], ",") return string(tname, "{", params_str, "}") end end function contains_reflect_type(::Type{T}) where {T} for idx in 1:fieldcount(T) Reflect === fieldtype(T, idx) && return true end return false end function is_union_of_multiple_options(::Type{T}) where {T} T isa Union || return false T.a === Nothing && return is_union_of_multiple_options(T.b) T.b === Nothing && return is_union_of_multiple_options(T.a) # not option type return is_option_maybe(T.a) && is_option_maybe(T.b) end 7/opt/julia/packages/Configurations/Yxczn/src/codegen.jl)""" @option [alias::String] Define an option struct type. This will auto-generate methods that parse a given `Dict{String}` object (the keys must be of type `String`) into an instance of the struct type you defined. One can use `alias` string to distinguish multiple possible option type for the same field. # Special Types - `Maybe{T}`: this type is equivalent to `Union{Nothing, T}` and is treated specially in `@option`, it will always have a default value of `nothing` if not specified. - [`Reflect`](@ref): this type is treated specially to allow one to use a field to store the corresponding type information. !!! compat "Configurations 0.16" from v0.16.0 Configurations stops overloading the `Base.show` method for you, if you need pretty printing of your option types, consider overloading the `Base.show(io::IO, mime::MIME, x)` method to `pprint_struct(io, mime, x)` provided by [GarishPrint](https://github.com/Roger-luo/GarishPrint.jl) !!! compat "Configurations 0.12" from v0.12.0 the field alias feature is removed due to the syntax conflict with field docstring. Please refer to [#17](https://github.com/Roger-luo/Configurations.jl/issues/17). # Example One can define option type via `@option` macro with or without an alias. ```julia-repl julia> "Option A" @option "option_a" struct OptionA name::String int::Int = 1 end julia> "Option B" @option "option_b" struct OptionB opt::OptionA = OptionA(;name = "Sam") float::Float64 = 0.3 end julia> option = from_dict(OptionB, d) OptionB(; opt = OptionA(; name = "Roger", int = 2, ), float = 0.33, ) ``` when there are multiple possible option type for one field, one can use the alias to distinguish them ```julia-repl julia> @option struct OptionD opt::Union{OptionA, OptionB} end julia> d1 = Dict{String, Any}( "opt" => Dict{String, Any}( "option_b" => d ) ); julia> from_dict(OptionD, d1) OptionD(; opt = OptionB(; opt = OptionA(; name = "Roger", int = 2, ), float = 0.33, ), ) ``` """ macro option(ex) return esc(option_m(__module__, ex)) end macro option(alias::String, ex) return esc(option_m(__module__, ex, alias)) end function option_m(mod::Module, ex, type_alias=nothing) ex = macroexpand(mod, ex) def = JLKwStruct(ex, type_alias) return codegen_option_type(mod, def) end """ validate_option_def(def::JLKwStruct) Validate the option definition. """ function validate_option_def(mod::Module, def::JLKwStruct) if def.typealias !== nothing isempty(def.typevars) || throw(ArgumentError( "only concrete type definition can have type alias" )) end has_duplicated_reflect_type(mod, def) && throw(ArgumentError("struct fields contain duplicated `Reflect` type")) return end """ codegen_option_type(mod::Module, def::JLKwStruct) Generate the `Configurations` option type definition from a given `JLKwStruct` created by [`Expronicon`](https://github.com/Roger-luo/Expronicon.jl). """ function codegen_option_type(mod::Module, def::JLKwStruct) # preprocess validate_option_def(mod, def) add_field_defaults!(mod, def) quote $(codegen_ast(def)) Core.@__doc__ $(def.name) $(codegen_create(def)) $(codegen_is_option(def)) $(codegen_convert(def)) $(codegen_field_default(def)) $(codegen_type_alias(def)) $(codegen_isequal(def)) $(codegen_from_dict_specialize(def)) nothing end end """ add_field_defaults!(m::Module, def::JLKwStruct) Add default value for `Maybe` and `Reflect` type. """ function add_field_defaults!(m::Module, def::JLKwStruct) for field in def.fields if is_reflect_type_expr(m, field.type) field.default = Reflect() elseif is_maybe_type_expr(m, field.type) && field.default === no_default field.default = nothing end end return def end """ has_duplicated_reflect_type(m::Module, def::JLKwStruct) Check if the definition has duplicated reflect type. """ function has_duplicated_reflect_type(m::Module, def::JLKwStruct) has_reflect_type = false for field in def.fields if is_reflect_type_expr(m, field.type) has_reflect_type && return true has_reflect_type = true end end return false end """ is_reflect_type_expr(m::Module, @nospecialize(ex)) Check if the expression `ex` evaluates to a [`Reflect`](@ref). """ function is_reflect_type_expr(m::Module, @nospecialize(ex)) if isdefined(m, :Reflect) && (getfield(m, :Reflect) === Reflect) ex === :Reflect && return true end # no need to check definition ex == Reflect && return true ex == GlobalRef(Configurations, :Reflect) && return true ex == :($Configurations.Reflect) && return true ex == :($Configurations.$Reflect) && return true ex == :(Configurations.$Reflect) && return true if isdefined(m, :Configurations) ex == :(Configurations.Reflect) && return true end return false end """ is_maybe_type_expr(m::Module, @nospecialize(ex)) Check if the expression `ex` evaluates to a `Maybe{T}`. """ function is_maybe_type_expr(m::Module, @nospecialize(ex)) if isdefined(m, :Maybe) && (getfield(m, :Maybe) === Maybe) _is_maybe_type_expr(ex) && return true end if ex isa GlobalRef && ex.mod === Configurations ex.name === :Maybe && return true end if ex isa Type && ex isa Union && Nothing <: ex return true end ex isa Expr || return false if ex.head === :. if ex.args[1] === Configurations || ex.args[1] === :Configurations return _is_maybe_type_expr(ex.args[2]) end elseif ex.head === :curly return is_maybe_type_expr(m, ex.args[1]) end return false end function _is_maybe_type_expr(@nospecialize(ex)) ex === Maybe && return true ex === :Maybe && return true if ex isa QuoteNode ex.value === :Maybe && return true ex.value isa Type && ex.value <: Maybe && return true end ex isa Expr || return false if ex.head === :curly ex.args[1] === :Maybe && return true ex.args[1] isa Type && ex.args[1] <: Maybe && return true end return false end """ codegen_is_option(x::JLKwStruct) Generate the [`is_option`](@ref) method. """ function codegen_is_option(def::JLKwStruct) quote $Configurations.is_option(::$(def.name)) = true $Configurations.is_option(::Type{<:$(def.name)}) = true end end """ codegen_convert(x::JLKwStruct) Generate `Base.convert` from `AbstractDict{String}` to the given option type. """ function codegen_convert(def::JLKwStruct) quote function $Base.convert(::Type{<:$(def.name)}, d::AbstractDict{String}) return $Configurations.from_dict($(def.name), d) end end end """ codegen_field_default(def::JLKwStruct) Generate [`field_default`](@ref) overload to support the default value reflection. """ function codegen_field_default(def::JLKwStruct) obj = gensym(:x) msg = Expr(:string, "type $(def.name) does not have field ", obj) err = :(error($msg)) ret = JLIfElse() ret.otherwise = err prev_field_names = Symbol[] for (k, field) in enumerate(def.fields) vars = Symbol[] for name in prev_field_names if has_symbol(field.default, name) push!(vars, name) end end cond = :($obj == $(QuoteNode(field.name))) if isempty(vars) # const default ret[cond] = field.default else jlfn = JLFunction(; args=vars, body=field.default) fn = gensym(:fn) ret[cond] = quote $fn = $(codegen_ast(jlfn)) $PartialDefault($fn, $vars, $(Expr(:quote, field.default))) end end push!(prev_field_names, field.name) end type = gensym(:type) typevars = name_only.(def.typevars) ub = isempty(def.typevars) ? def.name : Expr(:curly, def.name, typevars...) return codegen_ast( JLFunction(; name=:($Configurations.field_default), args=[:(::Type{$type}), :($obj::Symbol)], body=codegen_ast(ret), whereparams=[typevars..., :($type <: $ub)], ), ) end # function codegen_field_alias(def::JLKwStruct) # end """ codegen_isequal(x::JLKwStruct) Generate `Base.:(==)` to overload comparison operator to [`compare_options`](@ref) for given option type. """ function codegen_isequal(x::JLKwStruct) return :(Base.:(==)(a::$(x.name), b::$(x.name)) = $compare_options(a, b)) end """ codegen_type_alias(def::JLKwStruct) Generate type alias method [`type_alias`](@ref). """ function codegen_type_alias(def::JLKwStruct) @gensym TYPE_ALIAS_MAP ret = quote const $TYPE_ALIAS_MAP = Dict{String, Any}() $Configurations.get_type_alias_map(::Type{<:$(def.name)}) = $TYPE_ALIAS_MAP # the type can only be a concrete type if it has alias # if the type is not concrete we will return nothing $Configurations.type_alias(::Type{<:$(def.name)}) = $(def.typealias) end if !(def.typealias === nothing) push!(ret.args, :($TYPE_ALIAS_MAP[$(def.typealias)] = $(def.name))) end return ret end """ codegen_create(def::JLKwStruct) Generate [`Configurations.create`](@ref) overload. """ function codegen_create(def::JLKwStruct) return codegen_ast_kwfn(def, :($Configurations.create)) end """ codegen_from_dict_specialize(def::JLKwStruct) Generate the specialized `from_dict` for the given definition. """ function codegen_from_dict_specialize(def::JLKwStruct) quote @generated function $Configurations.from_dict_specialize( ::Type{T}, d::AbstractDict{String} ) where {T<:$(def.name)} return $Configurations.from_dict_generated(T, :d) end end end """ @type_alias Define a type alias for option type `type`. The corresponding `type` must be a concrete type in order to map this Julia type to a human readable markup language (e.g TOML, YAML, etc.). """ macro type_alias(type, name::String) esc(type_alias_m(type, name)) end function type_alias_m(type, name::String) quote $Configurations.type_alias(::Type{$type}) = $name $Configurations.set_type_alias($type, $name) end end 9/opt/julia/packages/Configurations/Yxczn/src/from_dict.jl:""" from_dict(::Type{T}, d::AbstractDict{String}; kw...) where T Convert dictionary `d` to an option type `T`, the value of valid fields of `T` in this dictionary `d` can be override by keyword arguments. !!! compat "Configurations 0.17" `convert_to_option` interface is deprecated for conversion, please overload the 3-arg or 4-arg `from_dict` instead. See also [Type Conversion](@ref type-conversion) section. # Example ```julia-repl julia> @option struct OptionA name::String = "Sam" age::Int = 25 end julia> d = Dict{String, Any}( "name" => "Roger", "age" => 10, ); julia> from_dict(OptionA, d; age=25) OptionA(; name = "Roger", age = 25, ) ``` """ function from_dict(::Type{OptionType}, d::AbstractDict{String}; kw...) where {OptionType} if !isconcretetype(OptionType) throw(ArgumentError("expect a concrete type, got $OptionType")) end # deepcopy is quite expensive # error earlier before that assert_field_match_exactly(OptionType, d) if !isempty(kw) d = from_underscore_kwargs!(deepcopy(d), OptionType; kw...) end return from_dict_specialize(OptionType, d) end # An already-parsed option should be kept in nested parsing. from_dict(::Type{T}, t::T) where {T} = t """ ignore_extra(option_type) -> Bool Return `true` if the option type ignores extra fields when read from a dict-like object. !!! note Normally, we require the dict-like object to have exactly the same number of fields with the option type. However, it could be useful to have ignore extra fields when wrapping network work services to ignore some irrelavent optional fields. !!! note Unlike [pydantic](https://pydantic-docs.helpmanual.io/usage/model_config/), we do not allow dynamically adding fields to a given type. One should manually define fields you would like to include in a struct type and let it to have type `Dict{String, Any}`. # Example ```julia julia> Configurations.ignore_extra(::Type{MyOption}) = true ``` """ function ignore_extra(::Type{OptionType}) where {OptionType} is_option(OptionType) || error("expect an option type") return false end function assert_field_match_exactly(::Type{OptionType}, d::AbstractDict{String}) where {OptionType} ignore_extra(OptionType) && return nf = fieldcount(OptionType) option_keys = [string(fieldname(OptionType, idx)) for idx in 1:nf] for key in keys(d) key == "#metadata#" && continue key in option_keys || throw(InvalidKeyError(key, option_keys)) end return end """ OptionField{name} OptionField(name::Symbol) Trait type that denotes a field of an option type. Mainly used for dispatch purpose. `name` should be a `Symbol`. """ struct OptionField{name} end OptionField(name::Symbol) = OptionField{name}() """ from_dict(::Type{OptionType}, ::OptionField{f_name}, ::Type{T}, x) where {OptionType, f_name, T} For option type `OptionType`, convert the object `x` to the field type `T` and assign it to the field `f_name`. Raise `FieldTypeConversionError`s errors if `Base.convert` raises exception ``` ERROR: MethodError: Cannot `convert` an object of type ... ``` """ function from_dict(::Type{OptionType}, optionfield::OptionField{f_name}, ::Type{T}, x ) where {OptionType,f_name,T} try return from_dict(OptionType, T, x) catch err if err isa MethodError && err.f === convert throw(FieldTypeConversionError(typeof(x), f_name, T, OptionType)) else throw(err) end end end function from_dict( ::Type{OptionType}, of::OptionField, ::Type{T}, x ) where {OptionType,T<:AbstractVector} if eltype(T) isa Union return map(x) do each from_dict_union_type(OptionType, of, eltype(T), each) end else return map(x) do each from_dict(OptionType, of, eltype(T), each) end end end # default conversions """ from_dict(::Type{OptionType}, ::Type{T}, x) where {OptionType, T} For option type `OptionType`, convert the object `x` to type `T`. This is similar to `Base.convert(::Type{T}, x)` and will fallback to `Base.convert` if not defined. """ function from_dict(::Type{OptionType}, ::Type{T}, x) where {OptionType,T} is_option(T) && return from_dict(T, x) # TODO: deprecate convert_to_option # then just use the following # return convert(T, x) return deprecated_conversion(OptionType, T, x) # return convert(T, x) end from_dict(::Type, ::Type{VersionNumber}, x) = VersionNumber(x) function deprecated_conversion(::Type{OptionType}, ::Type{T}, x) where {OptionType,T} ret = convert_to_option(OptionType, T, x) if ret === ConvertNotFound() return convert(T, x) else return ret end end @generated function from_dict_union_type( ::Type{OptionType}, ::OptionField{f_name}, ::Type{FieldType}, value ) where {OptionType,f_name,FieldType} types = Base.uniontypes(FieldType) return from_dict_union_type_generated(OptionType, OptionField(f_name), types, :value) end function from_dict_union_type_dynamic( ::Type{OptionType}, of::OptionField{f_name}, ::Type{FieldType}, value ) where {OptionType,f_name,FieldType} FieldType isa Union || return from_dict(OptionType, of, FieldType, value) assert_duplicated_alias_union(FieldType) types = Base.uniontypes(FieldType) if Nothing in types value === nothing && return nothing # happy path types = filter(x -> x !== Nothing, types) if length(types) == 1 return from_dict(OptionType, of, types[1], value) end end for T in types if is_option(T) alias = type_alias(T) reflect_idx = find_reflect_field(T) # type can be determined by alias if haskey(value, alias) return from_dict(T, value[alias]) elseif reflect_idx !== nothing # type can be determined by reflect field reflect_f_name = fieldname(T, reflect_idx) reflect_key = string(reflect_f_name) if haskey(value, reflect_key) if alias == value[reflect_key] # find alias in reflect return from_dict(T, value) else type = tryparse_jltype(value[reflect_key], get_type_alias_map(OptionType)) type === nothing && continue # NOTE: type is always more specialized type <: T && return from_dict(type, value) end end continue end if alias === nothing && reflect_idx === nothing # not much information, try parse try return from_dict(T, value) catch continue end end else try return from_dict(OptionType, of, T, value) catch continue end end end return error("cannot parse field $f_name, expect $FieldType, got $(typeof(value))") end function find_reflect_field(::Type{OptionType}) where {OptionType} nf = fieldcount(OptionType) for f_idx in 1:nf f_type = fieldtype(OptionType, f_idx) if f_type <: Reflect return f_idx end end return nothing end function assert_duplicated_alias_union(::Type{UnionType}) where {UnionType} set = String[] for T in Base.uniontypes(UnionType) if is_option(T) alias = type_alias(T) if alias !== nothing alias in set && throw(DuplicatedAliasError(alias)) push!(set, alias) end end end return nothing end """ from_dict_specialize(::Type{OptionType}, x) where {OptionType} A specialized [`from_dict`](@ref) method for option type `OptionType`. This is usually generated by [`@option`](@ref), but one may also specialized this manually to acheive maximal performance. """ function from_dict_specialize(::Type{OptionType}, x) where {OptionType} error( "specialized from_dict is not generated for `$OptionType` " * "consider define your own `from_dict` " * "otherwise use the `@option` macro to create the option type." ) end """ from_dict_generated(::Type{OptionType}, value::Symbol) where {OptionType} Generate a specialized Julia expression to convert an `AbstractDict{String}` to our `OptionType`. """ function from_dict_generated(::Type{OptionType}, value::Symbol) where {OptionType} nf = fieldcount(OptionType) ret = Expr(:block) construct = Expr(:call, OptionType) for f_idx in 1:nf f_name = fieldname(OptionType, f_idx) f_type = fieldtype(OptionType, f_idx) f_default = field_default(OptionType, f_name) var = f_name key = string(f_name) field_value = gensym(:field_value) push!(construct.args, var) jl = JLIfElse() if f_default === no_default err_msg = "expect key: $key" jl[:(!haskey($value, $key))] = :(error($err_msg)) elseif f_default isa PartialDefault jl[:(!haskey($value, $key))] = :($var = $(f_default.lambda)($(f_default.vars...))) else jl[:(!haskey($value, $key))] = :($var = $(QuoteNode(f_default))) end body = from_dict_generated(OptionType, OptionField(f_name), f_type, field_value) # Maybe{option} type wants to treat empty dict # as all default value types = Base.uniontypes(f_type) types = filter(x -> x !== Nothing, types) if f_default === nothing && length(types) == 1 && is_option(types[1]) jl.otherwise = quote $field_value = $value[$key] $var = $body end elseif f_default === nothing jl.otherwise = quote $field_value = $value[$key] if $field_value isa AbstractDict && isempty($field_value) $var = nothing else $var = $body end end else jl.otherwise = quote $field_value = $value[$key] $var = $body end end push!(ret.args, codegen_ast(jl)) end push!(ret.args, construct) return ret end function from_dict_generated( option_type, of::OptionField, f_type::Type, field_value::Symbol ) if is_option(f_type) quote $field_value isa AbstractDict || $field_value isa $f_type || error("expect an AbstractDict or $($f_type), got $(typeof($field_value))") # NOTE: we want to allow user overloaded from_dict here # thus we don't use $(from_dict_generated(f_type, value)) $Configurations.from_dict($f_type, $field_value) end elseif f_type isa Union types = Base.uniontypes(f_type) from_dict_union_type_generated(option_type, of, types, field_value) elseif f_type <: Reflect # check if the reflect value match current type msg = "type mismatch, expect $option_type got" alias = type_alias(option_type) alias_map = get_type_alias_map(option_type) if alias === nothing quote $Configurations.parse_jltype($field_value, $alias_map) <: $option_type || throw(ArgumentError($msg * " $($field_value)")) $Reflect() end else quote $field_value == $alias || $Configurations.parse_jltype($field_value, $alias_map) <: $option_type || throw(ArgumentError($msg * " $($field_value)")) $Reflect() end end else quote $Configurations.from_dict($option_type, $of, $f_type, $field_value) end end end function from_dict_union_type_generated( option_type, of::OptionField, types::Vector{Any}, value::Symbol ) if Nothing in types from_dict_maybe_type_generated(option_type, of, types, value) elseif has_same_reflect_field(types) _from_dict_union_type_similar_reflect_field(types, value) else # fallback to dynamic FieldType = Union{types...} return quote $Configurations.from_dict_union_type_dynamic( $option_type, $of, $FieldType, $value ) end end end function from_dict_maybe_type_generated( option_type, of::OptionField, types::Vector{Any}, value::Symbol ) types = filter(x -> x !== Nothing, types) if length(types) == 1 # Maybe{T} return quote if $value === nothing nothing else $(from_dict_generated(option_type, of, types[1], value)) end end else # Maybe{Union{A, B, C...}} return quote if $value === nothing nothing else $(from_dict_union_type_generated(option_type, of, types, value)) end end end end function _from_dict_union_type_similar_reflect_field(types::Vector{Any}, value::Symbol) T = first(types) f_alias_map = alias_map(types) f_alias_map = isempty(f_alias_map) ? nothing : f_alias_map idx = find_reflect_field(T) reflect_key = string(fieldname(T, idx)) msg = "expect key: $reflect_key" type_err_msg = "expect one of the following option type: " * join(map(x->string("`", x, "`"), types), ", ") @gensym type return quote haskey($value, $reflect_key) || error($msg) $type = $Configurations.parse_jltype($value[$reflect_key], $f_alias_map) $type <: $(Union{types...}) || error($type_err_msg * " got `$($type)`") $Configurations.from_dict_specialize($type, $value) end end """ has_same_reflect_field(types::Vector{Any}) Check if all types has the same reflect field name. """ function has_same_reflect_field(types::Vector{Any}) idx = find_reflect_field(first(types)) idx === nothing && return false f_reflect = fieldname(first(types), idx) for t_idx in 2:length(types) T = types[t_idx] t_reflect_idx = find_reflect_field(T) t_reflect_idx === nothing && return false t_f_reflect = fieldname(T, t_reflect_idx) if f_reflect !== t_f_reflect return false end end return true end """ alias_map(types::Vector{Any}) Create a `Dict` mapping type alias to a type. """ function alias_map(types::Vector{Any}) d = Dict{String,Any}() for t in types if is_option(t) alias_map = get_type_alias_map(t)::Dict{String, Any} merge!(d, alias_map) end end return d end ;/opt/julia/packages/Configurations/Yxczn/src/from_kwargs.jl~""" from_kwargs(convention!, ::Type{T}; kw...) where T Convert keyword arguments to given option type `T` using `convention!`. See also [`from_dict`](@ref). # Convention - `from_underscore_kwargs!`: use `_` to disambiguate subfields of the same name, this is the default behaviour. - `from_field_kwargs!`: do not disambiguate subfields, errors if there are disambiguity """ function from_kwargs(convention!, ::Type{T}; kw...) where {T} d = OrderedDict{String,Any}() convention!(d, T; kw...) return from_dict(T, d) end """ from_kwargs(::Type{T}; kw...) where T Convert keyword arguments to given option type `T` using the underscore convention. """ from_kwargs(::Type{T}; kw...) where {T} = from_underscore_kwargs(T; kw...) """ from_underscore_kwargs(::Type{T}; kw...) where T Convert keyword arguments to given option type `T` using the underscore convention. """ function from_underscore_kwargs(::Type{T}; kw...) where {T} return from_kwargs(from_underscore_kwargs!, T; kw...) end """ from_field_kwargs(::Type{T}; kw...) where T Convert keyword arguments to given option type `T` using the field keyword convention. """ from_field_kwargs(::Type{T}; kw...) where {T} = from_kwargs(from_field_kwargs!, T; kw...) # NOTE: this is for compatibilty """ from_kwargs!(d::AbstractDict{String}, ::Type{T}, prefix::Maybe{Symbol} = nothing; kw...) where T Internal method for inserting keyword arguments to given dictionary object `d`. It will overwrite existing keys in `d` if it is specified by keyword argument. """ function from_kwargs!( d::AbstractDict{String}, ::Type{T}, prefix::Maybe{Symbol}=nothing; kw... ) where {T} return from_underscore_kwargs!(d, T, prefix; kw...) end function from_underscore_kwargs!( d::AbstractDict{String}, ::Type{T}, prefix::Maybe{Symbol}=nothing; kw... ) where {T} validate_keywords(T, underscore_keywords(T); kw...) return unsafe_from_underscore_kwargs!(d, T, prefix; kw...) end function unsafe_from_underscore_kwargs!( d::AbstractDict{String}, ::Type{T}, prefix::Maybe{Symbol}=nothing; kw... ) where {T} foreach_keywords(T) do name, type key = underscore(prefix, name) from_kwargs_option_key!(d, type, name, key, kw) do field_d, field_type unsafe_from_underscore_kwargs!(field_d, field_type, key; kw...) end end return d end function from_field_kwargs!(d::AbstractDict{String}, ::Type{T}; kw...) where {T} validate_keywords(T, field_keywords(T); kw...) return unsafe_from_field_kwargs!(d, T; kw...) end function unsafe_from_field_kwargs!(d::AbstractDict{String}, ::Type{T}; kw...) where {T} foreach_keywords(T) do name, type from_kwargs_option_key!(d, type, name, name, kw) do field_d, field_type unsafe_from_field_kwargs!(field_d, field_type; kw...) end end return d end function from_kwargs_option_key!( f, d::AbstractDict, ::Type{T}, name::Symbol, key::Symbol, kw ) where {T} key_str = string(key) name_str = string(name) # shortcut if haskey(kw, key) d[name_str] = kw[key] return d end if is_option(T) field_d = OrderedDict{String,Any}() if haskey(d, name_str) && (d_value = d[name_str]) isa AbstractDict field_d = merge!(field_d, d_value) end f(field_d, T) # recurse into subfields if !isempty(field_d) d[name_str] = field_d end elseif T isa Union from_kwargs_option_key!(f, d, T.a, name, key, kw) from_kwargs_option_key!(f, d, T.b, name, key, kw) end return d end function validate_keywords(::Type{T}, keys=underscore_keywords(T); kw...) where {T} for (k, v) in kw k in keys || throw(InvalidKeyError(k, keys)) end return nothing end """ field_keywords(::Type{T}) where T Return all the option type field names given `T`, error if there are duplicated sub-fields. """ function field_keywords(::Type{T}) where {T} return collect_field_keywords!(Symbol[], T, T) end """ underscore_keywords(::Type{T}) where T Return keywords given `T` using the underscore convention. """ function underscore_keywords(::Type{T}) where {T} return collect_underscore_keywords!(Symbol[], T) end function foreach_keywords(f, ::Type{T}) where {T} is_option(T) || return for name in fieldnames(T) type = fieldtype(T, name) f(name, type) end return end function underscore(prefix::Maybe{Symbol}, name) if prefix === nothing return name else return Symbol(prefix, :_, name) end end function collect_field_keywords!(list::Vector{Symbol}, ::Type{Top}, ::Type{T}) where {Top,T} foreach_keywords(T) do name, type if is_option(type) collect_field_keywords!(list, Top, type) elseif type isa Union name in list && error(msg) push!(list, name) # recurse into Union collect_field_keywords!(list, Top, type.a) collect_field_keywords!(list, Top, type.b) else name in list && throw(DuplicatedFieldError(name, Top)) push!(list, name) end end return list end function collect_underscore_keywords!( list::Vector{Symbol}, ::Type{T}, prefix::Maybe{Symbol}=nothing ) where {T} foreach_keywords(T) do name, type key = underscore(prefix, name) if is_option(type) collect_underscore_keywords!(list, type, key) elseif type isa Union push!(list, key) collect_underscore_keywords!(list, type.a, key) collect_underscore_keywords!(list, type.b, key) else push!(list, key) end end return list end 9/opt/julia/packages/Configurations/Yxczn/src/from_toml.jl """ from_toml(::Type{T}, filename::String; kw...) where T Convert a given TOML file `filename` to an option type `T`. Valid fields can be override by keyword arguments. See also [`from_dict`](@ref). """ function from_toml(::Type{T}, filename::String; kw...) where {T} is_option(T) || error("not an option type") d = TOML.parsefile(filename) filepath = normpath(filename) d["#metadata#"] = Dict{String, Any}( "file" => filepath, "dir" => dirname(filepath), "format" => "TOML", ) return from_dict(T, d; kw...) end """ from_toml_if_exists(::Type{T}, filename::String; kw...) where T Similar to [`from_toml`](@ref) but will create the option instance via `from_kwargs(T;kw...)` instead of error if the file does not exist. """ function from_toml_if_exists(::Type{T}, filename::String; kw...) where {T} if isfile(filename) return from_toml(T, filename; kw...) else return from_kwargs(T; kw...) end end 9/opt/julia/packages/Configurations/Yxczn/src/serialize.jlM function assert_option(x) return is_option(x) || error("$(typeof(x)) is not an option type") end @option struct ToDictOption include_defaults::Bool = true exclude_nothing::Bool = false end """ TOMLStyle::ToDictOption Predefined option for TOML compatible [`to_dict`](@ref) option. """ const TOMLStyle = ToDictOption(; include_defaults=true, exclude_nothing=true) """ YAMLStyle::ToDictOption Predefined option for YAML compatible [`to_dict`](@ref) option. """ const YAMLStyle = ToDictOption(; include_defaults=true, exclude_nothing=false) """ JSONStyle::ToDictOption Predefined option for JSON compatible [`to_dict`](@ref) option. """ const JSONStyle = ToDictOption(; include_defaults=true, exclude_nothing=false) """ to_dict(x; include_defaults=true, exclude_nothing=false) -> OrderedDict Convert an object `x` to an `OrderedDict`. # Kwargs - `include_defaults`: include the default value, default is `true`. - `exclude_nothing`: exclude fields that have value `nothing`, this supersedes `include_defaults` when they are both `true`. # Format Compatibilty When mapping an option struct from Julia to TOML/YAML/JSON/etc. format, there are some subtle semantic compatibilty one need to deal with, we provide some convenient predefined conversion option constants as [`TOMLStyle`](@ref), [`YAMLStyle`](@ref), [`JSONStyle`](@ref). !!! tips `to_dict` does not export fields that are of the same values as the defaults. In most cases, this should be the default behaviour, and users should not use `include_defaults`, however, this can be overridden by changing `include_defaults` to `true`. """ function to_dict(x; kw...) return to_dict(x, ToDictOption(; kw...)) end """ to_dict(x, option::ToDictOption) -> OrderedDict Convert an object `x` to an `OrderedDict` with `ToDictOption` specified. # Example ```julia to_dict(x, TOMLStyle) # TOML compatible to_dict(x, YAMLStyle) # YAML compatible to_dict(x, JSONStyle) # JSON compatible ``` """ function to_dict(x, option::ToDictOption) assert_option(x) return to_dict(typeof(x), x, option) end # disambiguity to_dict(x::Type, ::ToDictOption) = error("$x is not an option type") """ to_dict(::Type{T}, x, option::ToDictOption) where T Convert `x` when `x` is inside an option type `T`. `option` is a set of options to determine the conversion behaviour. this can be overloaded to change the behaviour of `to_dict(x; kw...)`. to_dict(::Type{T}, x) where T One can also use the 2-arg version when `x` is not or does not contain an option type for convenience. # Example The following is a builtin overload to handle list of options. ```julia function Configurations.to_dict(::Type{T}, x::Vector, option::ToDictOption) where T if is_option(eltype(x)) return map(p->to_dict(T, p, include_defaults), x) else return x end end ``` The following overloads the 2-arg `to_dict` to convert all `VersionNumber` to a `String` for all kinds of option types. ```julia Configurations.to_dict(::Type, x::VersionNumber) = string(x) ``` """ function to_dict(::Type{T}, x, option::ToDictOption) where {T} if is_option(x) return _option_to_dict(x, option) else return to_dict(T, x) # fall through 2-arg version end end to_dict(::Type, x) = x # handle list of options as builtin function to_dict(::Type{T}, x::Vector, option::ToDictOption) where {T} return map(x) do each d = to_dict(T, each, option) if eltype(x) isa Union && is_option(each) FieldType = typeof(each) alias = type_alias(FieldType) idx = find_reflect_field(FieldType) if alias !== nothing && idx === nothing return OrderedDict{String,Any}(alias => d) end end return d end end function _option_to_dict(x, option::ToDictOption) assert_option(x) d = OrderedDict{String,Any}() T = typeof(x) for name in fieldnames(T) type = fieldtype(T, name) value = getfield(x, name) name_str = string(name) if option.exclude_nothing && value === nothing continue end if option.include_defaults || value != field_default(T, name) field_dict = to_dict(T, value, option) # 1. option type contains field of type Reflect # 2. option type contains field of Union{options...} # 3. other types # # NOTE: # we always add an alias if it's a Union # of multiple option types if type === Reflect # we don't implement this via `to_dict` # because we want it error when `Reflect` # is used as a normal type # `Reflect` should be only used to denote # a field contains the type info as a `String`. if type_alias(T) === nothing d[name_str] = full_typename(T) else d[name_str] = type_alias(T) end elseif is_option(value) && is_union_of_multiple_options(type) if contains_reflect_type(typeof(value)) d[name_str] = field_dict continue end alias = type_alias(typeof(value)) if alias === nothing error("please define an alias for option type $(typeof(value))") end d[name_str] = OrderedDict{String,Any}(alias => field_dict) else d[name_str] = field_dict end end end return d end """ is_option_maybe(::Type{T}) where T `T` is an option struct or if `T` is an union, one of the types is an option struct. """ function is_option_maybe(::Type{T}) where {T} is_option(T) && return true T isa Union || return false return is_option_maybe(T.a) || is_option_maybe(T.b) end """ to_toml([f::Function], io::IO, option; sorted=false, by=identity, kw...) Convert an instance `option` of option type to TOML and write it to `IO`. See [`to_dict`](@ref) for other valid keyword options. See also `TOML.print` in the stdlib for the explaination of `sorted`, `by` and `f`. # Exclude `nothing` In TOML specification, there is [no null type](https://github.com/toml-lang/toml/issues/802). One should exclude the field if it is not specified (of value `nothing` in Julia). In `to_toml` the option `exclude_nothing` is always `true`. In most cases, `nothing` is used with another type to denote optional or not specified field, thus one should always put a default value `nothing` to the option struct, e.g One should define ```julia @option struct OptionX a::Union{Nothing, Int} = nothing b::Maybe{Int} = nothing end ``` Here `Maybe{T}` is a convenient alias of `Union{Nothing, T}`. """ function to_toml(f, io::IO, x; sorted::Bool=false, by=identity, kw...) is_option(x) || error("argument is not an option type") d = to_dict(x; exclude_nothing=true, kw...) return TOML.print(f, io, d; sorted=sorted, by=by) end """ to_toml([f::Function], filename::String, option; sorted=false, by=identity, kw...) Convert an instance `option` of option type to TOML and write it to `filename`. See also `TOML.print`. """ function to_toml(f, filename::String, x; sorted::Bool=false, by=identity, kw...) open(filename, "w+") do io to_toml(f, io, x; sorted=sorted, by=by, kw...) end end function to_toml(io::IO, x; sorted::Bool=false, by=identity, kw...) return to_toml(identity, io, x; sorted=sorted, by=by, kw...) end function to_toml(filename::String, x; sorted::Bool=false, by=identity, kw...) return to_toml(identity, filename, x; sorted=sorted, by=by, kw...) end """ to_toml(x; sorted=false, by=identity, kw...) Convert an instance `x` of option type to TOML and write it to `String`. See also `TOML.print`. `to_toml` does not export fields that are of the same values as the defaults. This can be overridden by changing `include_defaults` to `true`. """ function to_toml(x; sorted::Bool=false, by=identity, kw...) return sprint(x) do io, x to_toml(io, x; sorted=sorted, by=by, kw...) end end OSj$