diff --git a/src/keypath.jl b/src/keypath.jl index 518a087..ac468fa 100644 --- a/src/keypath.jl +++ b/src/keypath.jl @@ -9,14 +9,46 @@ A type for representing a path of keys to a value in a nested structure. Can be constructed with a sequence of keys, or by concatenating other `KeyPath`s. Keys can be of type `Symbol`, `String`, or `Int`. +For custom types, access through symbol keys is assumed to be done with `getproperty`. +For consistency, the method `Base.propertynames` is used to get the viable property names. + +For string and integer keys instead, the access is done with `getindex`. + +See also [`getkeypath`](@ref), [`haskeypath`](@ref). + # Examples ```jldoctest julia> kp = KeyPath(:b, 3) KeyPath(:b, 3) -julia> KeyPath(:a, kp, :c, 4) +julia> KeyPath(:a, kp, :c, 4) # construct mixing keys and keypaths KeyPath(:a, :b, 3, :c, 4) + +julia> struct T + a + b + end + +julia> function Base.getproperty(x::T, k::Symbol) + if k in fieldnames(T) + return getfield(x, k) + elseif k === :ab + return "ab" + else + error() + end + end; + +julia> Base.propertynames(::T) = (:a, :b, :ab); + +julia> x = T(3, Dict(:c => 4, :d => 5)); + +julia> getkeypath(x, KeyPath(:ab)) # equivalent to x.ab +"ab" + +julia> getkeypath(x, KeyPath(:b, :c)) # equivalent to (x.b)[:c] +4 ``` """ struct KeyPath{T<:Tuple} @@ -52,14 +84,14 @@ end keypathstr(kp::KeyPath) = join(kp.keys, ".") _getkey(x, k::Integer) = x[k] -_getkey(x, k::Symbol) = getfield(x, k) +_getkey(x, k::Symbol) = getproperty(x, k) _getkey(x::AbstractDict, k::Symbol) = x[k] _getkey(x, k::AbstractString) = x[k] _haskey(x, k::Integer) = haskey(x, k) _haskey(x::Tuple, k::Integer) = 1 <= k <= length(x) _haskey(x::AbstractArray, k::Integer) = 1 <= k <= length(x) # TODO: extend to generic indexing -_haskey(x, k::Symbol) = k in fieldnames(typeof(x)) +_haskey(x, k::Symbol) = k in propertynames(x) _haskey(x::AbstractDict, k::Symbol) = haskey(x, k) _haskey(x, k::AbstractString) = haskey(x, k) @@ -68,7 +100,7 @@ _haskey(x, k::AbstractString) = haskey(x, k) Return the value in `x` at the path `kp`. -See also [`haskeypath`](@ref). +See also [`KeyPath`](@ref) and [`haskeypath`](@ref). # Examples ```jldoctest @@ -94,7 +126,7 @@ end Return `true` if `x` has a value at the path `kp`. -See also [`getkeypath`](@ref). +See also [`KeyPath`](@ref) and [`getkeypath`](@ref). # Examples ```jldoctest diff --git a/test/basics.jl b/test/basics.jl index c3ae540..feee703 100644 --- a/test/basics.jl +++ b/test/basics.jl @@ -400,10 +400,13 @@ end @functor A a = A(1) @test Functors.children(a) === (x = 1,) - Functors.@leaf A - children, re = Functors.functor(a) + + struct B; x; end + Functors.@leaf B + b = B(1) + children, re = Functors.functor(b) @test children == Functors.NoChildren() - @test re(children) === a + @test re(children) === b end @testset "IterateWalk" begin diff --git a/test/keypath.jl b/test/keypath.jl index 9069fe0..a206d53 100644 --- a/test/keypath.jl +++ b/test/keypath.jl @@ -16,20 +16,40 @@ kp0 = KeyPath() @test (kp0...,) === () + struct Tkp + a + b + c + end + + function Base.getproperty(x::Tkp, k::Symbol) + if k in fieldnames(Tkp) + return getfield(x, k) + elseif k === :ab + return "ab" + else + error() + end + end + + Base.propertynames(::Tkp) = (:a, :b, :c, :ab) + @testset "getkeypath" begin x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7])) @test getkeypath(x, KeyPath(:a)) == 3 @test getkeypath(x, KeyPath(:b, :c)) == 4 @test getkeypath(x, KeyPath(:b, "d", 2)) == 6 - - struct Tkp - a - b - c - end + x = Tkp(3, Tkp(4, 5, (6, 7)), 8) kp = KeyPath(:b, :c, 2) @test getkeypath(x, kp) == 7 + + @testset "access through getproperty" begin + x = Tkp(3, Dict(:c => 4, :d => 5), 6); + + @test getkeypath(x, KeyPath(:ab)) == "ab" + @test getkeypath(x, KeyPath(:b, :c)) == 4 + end end @testset "haskeypath" begin @@ -39,5 +59,13 @@ @test haskeypath(x, KeyPath(:b, "d", 2)) @test !haskeypath(x, KeyPath(:b, "d", 4)) @test !haskeypath(x, KeyPath(:b, "e")) + + @testset "access through getproperty" begin + x = Tkp(3, Dict(:c => 4, :d => 5), 6); + + @test haskeypath(x, KeyPath(:ab)) + @test haskeypath(x, KeyPath(:b, :c)) + @test !haskeypath(x, KeyPath(:b, :e)) + end end end