Module: Dottie
- Defined in:
- lib/dottie.rb,
lib/dottie/freckle.rb,
lib/dottie/methods.rb,
lib/dottie/version.rb
Defined Under Namespace
Modules: Methods Classes: Freckle
Constant Summary collapse
- VERSION =
'0.0.2'
Class Method Summary collapse
-
.[](obj) ⇒ Object
Creates a new Dottie::Freckle from a standard Ruby Hash or Array.
-
.build_key(parts) ⇒ Object
Builds a Dottie key from an Array of strings and integers.
-
.delete(obj, key) ⇒ Object
Deletes the value at the specified key and returns it.
-
.dottie_key?(key) ⇒ Boolean
Checks whether a key looks like a key Dottie understands.
-
.fetch(obj, key, default = :_fetch_default_) ⇒ Object
Mimics the behavior of Hash#fetch, raising an error if a key does not exist and no default value or block is provided.
-
.flatten(obj, options = {}, path = nil, flat = nil) ⇒ Object
Flattens a Hash or Array to a single-depth Hash with Dottie-style keys.
-
.get(obj, key) ⇒ Object
Gets a value from an object.
-
.has_key?(obj, key) ⇒ Boolean
Checks whether a Hash or Array contains the last part of a Dottie-style key.
-
.key_parts(key) ⇒ Object
Parses a Dottie key into an Array of strings and integers.
-
.keys(obj, options = {}) ⇒ Object
Gets an array of the Dottie-style keys that exist in a Hash or Array.
-
.set(obj, key, value) ⇒ Object
Sets a value in an object, creating missing nodes (Hashes and Arrays) as needed.
Class Method Details
.[](obj) ⇒ Object
Creates a new Dottie::Freckle from a standard Ruby Hash or Array.
12 13 14 15 16 17 18 |
# File 'lib/dottie.rb', line 12 def self.[](obj) if obj.is_a?(Dottie::Freckle) obj else Dottie::Freckle.new(obj) end end |
.build_key(parts) ⇒ Object
Builds a Dottie key from an Array of strings and integers.
237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/dottie.rb', line 237 def self.build_key(parts) key = '' parts.each_with_index do |part, i| case part when String key << '.' unless i == 0 key << part when Fixnum key << "[#{part}]" end end key end |
.delete(obj, key) ⇒ Object
Deletes the value at the specified key and returns it.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/dottie.rb', line 135 def self.delete(obj, key) if Dottie.has_key?(obj, key) key_parts = Dottie.key_parts(key) if key_parts.size > 1 key = Dottie.build_key(key_parts[0..-2]) obj = Dottie.get(obj, key) end if obj.is_a?(Array) && key_parts.last.is_a?(Fixnum) obj.delete_at(key_parts.last) else obj.delete(key_parts.last) end else nil end end |
.dottie_key?(key) ⇒ Boolean
Checks whether a key looks like a key Dottie understands.
200 201 202 |
# File 'lib/dottie.rb', line 200 def self.dottie_key?(key) !!(key.is_a?(String) && key =~ /[.\[]/) || key.is_a?(Array) end |
.fetch(obj, key, default = :_fetch_default_) ⇒ Object
Mimics the behavior of Hash#fetch, raising an error if a key does not exist and no default value or block is provided.
120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/dottie.rb', line 120 def self.fetch(obj, key, default = :_fetch_default_) if Dottie.has_key?(obj, key) Dottie.get(obj, key) elsif block_given? yield(key) elsif default != :_fetch_default_ default else raise KeyError.new(%{key not found: "#{key}"}) end end |
.flatten(obj, options = {}, path = nil, flat = nil) ⇒ Object
Flattens a Hash or Array to a single-depth Hash with Dottie-style keys.
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
# File 'lib/dottie.rb', line 155 def self.flatten(obj, = {}, path = nil, flat = nil) path ||= [] flat ||= {} case obj when Hash obj.each do |k, v| this_path = path + [k] case v when Hash, Array flat[this_path.join('.')] = [:keys_only] ? nil : v if [:intermediate] Dottie.flatten(v, , this_path, flat) else flat[this_path.join('.')] = [:keys_only] ? nil : v end end when Array obj.each_with_index do |v, i| this_path = path.dup if this_path.any? this_path[-1] = this_path[-1].to_s + "[#{i}]" else this_path = ["[#{i}]"] end case v when Hash, Array flat[this_path.join('.')] = [:keys_only] ? nil : v if [:intermediate] Dottie.flatten(v, , this_path, flat) else flat[this_path.join('.')] = [:keys_only] ? nil : v end end end flat end |
.get(obj, key) ⇒ Object
Gets a value from an object. Does not assume the object has been extended with Dottie methods.
24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/dottie.rb', line 24 def self.get(obj, key) Dottie.key_parts(key).each do |k| obj = case obj when Hash, Array # use an array index if it appears that's what was intended k = k.to_i if obj.is_a?(Array) && k.to_i.to_s == k obj[k] else nil end end obj end |
.has_key?(obj, key) ⇒ Boolean
Checks whether a Hash or Array contains the last part of a Dottie-style key.
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/dottie.rb', line 93 def self.has_key?(obj, key) key_parts = Dottie.key_parts(key) key_parts.each_with_index do |k, i| # look for the key if this is the last key part if i == key_parts.size - 1 if obj.is_a?(Array) && k.is_a?(Integer) return obj.size > k elsif obj.is_a?(Hash) return obj.has_key?(k) else return false end else obj = case obj when Hash, Array obj[k] else return false end end end end |
.key_parts(key) ⇒ Object
Parses a Dottie key into an Array of strings and integers.
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/dottie.rb', line 207 def self.key_parts(key) if key.is_a?(String) parts = [] s = StringScanner.new(key) loop do if s.scan(/\./) next elsif (p = s.scan(/[^\[\].]+/)) parts << p elsif (p = s.scan(/\[-?\d+\]/)) parts << p.scan(/-?\d+/).first.to_i elsif (p = s.scan(/\[(first|last)\]/)) parts << (p[1..-2] == 'first' ? 0 : -1) elsif (p = s.scan(/\[.+?\]/)) parts << p[1..-2] # remove '[' and ']' else break end end parts elsif key.is_a?(Array) key else raise TypeError.new("expected String or Array but got #{key.class.name}") end end |
.keys(obj, options = {}) ⇒ Object
Gets an array of the Dottie-style keys that exist in a Hash or Array.
193 194 195 |
# File 'lib/dottie.rb', line 193 def self.keys(obj, = {}) Dottie.flatten(obj, { keys_only: true }.merge()).keys end |
.set(obj, key, value) ⇒ Object
Sets a value in an object, creating missing nodes (Hashes and Arrays) as needed. Does not assume the object has been extended with Dottie methods.
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/dottie.rb', line 42 def self.set(obj, key, value) key_parts = Dottie.key_parts(key) key_parts.each_with_index do |k, i| # set the value if this is the last key part if i == key_parts.size - 1 case obj when Hash obj[k] = value when Array case k when '-', 'prepend', '>>' obj.unshift(value) when '+', 'append', '<<' obj << value else obj[k] = value end else raise TypeError.new("expected Hash or Array but got #{obj.class.name}") end # otherwise, walk down the tree, creating missing nodes along the way else obj = case obj when Hash, Array # look ahead at the next key to see if an array should be created if key_parts[i + 1].is_a?(Integer) || key_parts[i + 1] =~ /\A(-|\+|prepend|append|>>|<<)\z/ obj[k] ||= [] else obj[k] ||= {} end when nil # look at the key to see if an array should be created case k when Integer, '-', '+', 'prepend', 'append', '>>', '<<' obj[k] = [] else obj[k] = {} end else raise TypeError.new("expected Hash, Array, or nil but got #{obj.class.name}") end end end # return the value that was set value end |