1+ using Base: tail
2+
13KeyT = Union{Symbol, AbstractString, Integer}
24
35"""
@@ -7,14 +9,46 @@ A type for representing a path of keys to a value in a nested structure.
79Can be constructed with a sequence of keys, or by concatenating other `KeyPath`s.
810Keys can be of type `Symbol`, `String`, or `Int`.
911
12+ For custom types, access through symbol keys is assumed to be done with `getproperty`.
13+ For consistency, the method `Base.propertynames` is used to get the viable property names.
14+
15+ For string and integer keys instead, the access is done with `getindex`.
16+
17+ See also [`getkeypath`](@ref), [`haskeypath`](@ref).
18+
1019# Examples
1120
1221```jldoctest
1322julia> kp = KeyPath(:b, 3)
1423KeyPath(:b, 3)
1524
16- julia> KeyPath(:a, kp, :c, 4)
25+ julia> KeyPath(:a, kp, :c, 4) # construct mixing keys and keypaths
1726KeyPath(:a, :b, 3, :c, 4)
27+
28+ julia> struct T
29+ a
30+ b
31+ end
32+
33+ julia> function Base.getproperty(x::T, k::Symbol)
34+ if k in fieldnames(T)
35+ return getfield(x, k)
36+ elseif k === :ab
37+ return "ab"
38+ else
39+ error()
40+ end
41+ end;
42+
43+ julia> Base.propertynames(::T) = (:a, :b, :ab);
44+
45+ julia> x = T(3, Dict(:c => 4, :d => 5));
46+
47+ julia> getkeypath(x, KeyPath(:ab)) # equivalent to x.ab
48+ "ab"
49+
50+ julia> getkeypath(x, KeyPath(:b, :c)) # equivalent to (x.b)[:c]
51+ 4
1852```
1953"""
2054struct KeyPath{T<: Tuple }
@@ -29,10 +63,14 @@ function KeyPath(keys::Union{KeyT, KeyPath}...)
2963 return KeyPath (((ks... ). .. ,))
3064end
3165
66+ Base. isempty (kp:: KeyPath ) = false
67+ Base. isempty (kp:: KeyPath{Tuple{}} ) = true
3268Base. getindex (kp:: KeyPath , i:: Int ) = kp. keys[i]
3369Base. length (kp:: KeyPath ) = length (kp. keys)
3470Base. iterate (kp:: KeyPath , state= 1 ) = iterate (kp. keys, state)
3571Base.:(== )(kp1:: KeyPath , kp2:: KeyPath ) = kp1. keys == kp2. keys
72+ Base. tail (kp:: KeyPath ) = KeyPath (Base. tail (kp. keys))
73+ Base. last (kp:: KeyPath ) = last (kp. keys)
3674
3775function Base. show (io:: IO , kp:: KeyPath )
3876 compat = get (io, :compact , false )
4583
4684keypathstr (kp:: KeyPath ) = join (kp. keys, " ." )
4785
86+ _getkey (x, k:: Integer ) = x[k]
87+ _getkey (x, k:: Symbol ) = getproperty (x, k)
88+ _getkey (x:: AbstractDict , k:: Symbol ) = x[k]
89+ _getkey (x, k:: AbstractString ) = x[k]
90+
91+ _haskey (x, k:: Integer ) = haskey (x, k)
92+ _haskey (x:: Tuple , k:: Integer ) = 1 <= k <= length (x)
93+ _haskey (x:: AbstractArray , k:: Integer ) = 1 <= k <= length (x) # TODO : extend to generic indexing
94+ _haskey (x, k:: Symbol ) = k in propertynames (x)
95+ _haskey (x:: AbstractDict , k:: Symbol ) = haskey (x, k)
96+ _haskey (x, k:: AbstractString ) = haskey (x, k)
97+
98+ """
99+ getkeypath(x, kp::KeyPath)
100+
101+ Return the value in `x` at the path `kp`.
102+
103+ See also [`KeyPath`](@ref) and [`haskeypath`](@ref).
104+
105+ # Examples
106+ ```jldoctest
107+ julia> x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7]))
108+ Dict{Symbol, Any} with 2 entries:
109+ :a => 3
110+ :b => Dict{Any, Any}(:c=>4, "d"=>[5, 6, 7])
111+
112+ julia> getkeypath(x, KeyPath(:b, "d", 2))
113+ 6
114+ ```
115+ """
116+ function getkeypath (x, kp:: KeyPath )
117+ if isempty (kp)
118+ return x
119+ else
120+ return getkeypath (_getkey (x, first (kp)), tail (kp))
121+ end
122+ end
123+
124+ """
125+ haskeypath(x, kp::KeyPath)
126+
127+ Return `true` if `x` has a value at the path `kp`.
128+
129+ See also [`KeyPath`](@ref) and [`getkeypath`](@ref).
130+
131+ # Examples
132+ ```jldoctest
133+ julia> x = Dict(:a => 3, :b => Dict(:c => 4, "d" => [5, 6, 7]))
134+ Dict{Any,Any} with 2 entries:
135+ :a => 3
136+ :b => Dict{Any,Any}(:c=>4,"d"=>[5, 6, 7])
137+
138+ julia> haskeypath(x, KeyPath(:a))
139+ true
140+
141+ julia> haskeypath(x, KeyPath(:b, "d", 1))
142+ true
143+
144+ julia> haskeypath(x, KeyPath(:b, "d", 4))
145+ false
146+ """
147+ function haskeypath (x, kp:: KeyPath )
148+ if isempty (kp)
149+ return true
150+ else
151+ k = first (kp)
152+ return _haskey (x, k) && haskeypath (_getkey (x, k), tail (kp))
153+ end
154+ end
0 commit comments