1
+ using Base: tail
2
+
1
3
KeyT = Union{Symbol, AbstractString, Integer}
2
4
3
5
"""
@@ -7,14 +9,46 @@ A type for representing a path of keys to a value in a nested structure.
7
9
Can be constructed with a sequence of keys, or by concatenating other `KeyPath`s.
8
10
Keys can be of type `Symbol`, `String`, or `Int`.
9
11
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
+
10
19
# Examples
11
20
12
21
```jldoctest
13
22
julia> kp = KeyPath(:b, 3)
14
23
KeyPath(:b, 3)
15
24
16
- julia> KeyPath(:a, kp, :c, 4)
25
+ julia> KeyPath(:a, kp, :c, 4) # construct mixing keys and keypaths
17
26
KeyPath(: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
18
52
```
19
53
"""
20
54
struct KeyPath{T<: Tuple }
@@ -29,10 +63,14 @@ function KeyPath(keys::Union{KeyT, KeyPath}...)
29
63
return KeyPath (((ks... ). .. ,))
30
64
end
31
65
66
+ Base. isempty (kp:: KeyPath ) = false
67
+ Base. isempty (kp:: KeyPath{Tuple{}} ) = true
32
68
Base. getindex (kp:: KeyPath , i:: Int ) = kp. keys[i]
33
69
Base. length (kp:: KeyPath ) = length (kp. keys)
34
70
Base. iterate (kp:: KeyPath , state= 1 ) = iterate (kp. keys, state)
35
71
Base.:(== )(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)
36
74
37
75
function Base. show (io:: IO , kp:: KeyPath )
38
76
compat = get (io, :compact , false )
45
83
46
84
keypathstr (kp:: KeyPath ) = join (kp. keys, " ." )
47
85
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