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.1'

Class Method Summary collapse

Class Method Details

.[](obj) ⇒ Object

Creates a new Dottie::Freckle from a standard Ruby Hash or Array.



12
13
14
# File 'lib/dottie.rb', line 12

def self.[](obj)
  Dottie::Freckle.new(obj)
end

.dottie_key?(key) ⇒ Boolean

Checks whether a key looks like a key Dottie understands.

Returns:

  • (Boolean)


119
120
121
# File 'lib/dottie.rb', line 119

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.



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/dottie.rb', line 104

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

.get(obj, key) ⇒ Object

Gets a value from an object. Does not assume the object has been extended with Dottie methods.



20
21
22
23
24
25
26
27
28
29
30
# File 'lib/dottie.rb', line 20

def self.get(obj, key)
  Dottie.key_parts(key).each do |k|
    obj = case obj
    when Hash, Array
      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.

Returns:

  • (Boolean)


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/dottie.rb', line 77

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.



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/dottie.rb', line 126

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

.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.



36
37
38
39
40
41
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
# File 'lib/dottie.rb', line 36

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, Array
        obj[k] = value
      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)
          obj[k] ||= []
        else
          obj[k] ||= {}
        end
      when nil
        # look at the key to see if an array should be created
        case k
        when Integer
          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