File: resolve_source.rb

package info (click to toggle)
ruby-hocon 1.4.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 768 kB
  • sloc: ruby: 7,903; makefile: 4
file content (354 lines) | stat: -rw-r--r-- 11,449 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
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
152
153
154
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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
# encoding: utf-8

require_relative '../../hocon'
require_relative '../../hocon/config_error'
require_relative '../../hocon/impl'
require_relative '../../hocon/impl/config_impl'
require_relative '../../hocon/impl/container'

class Hocon::Impl::ResolveSource

  ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
  ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError

  # 'path_from_root' is used for knowing the chain of parents we used to get here.
  # null if we should assume we are not a descendant of the root.
  # the root itself should be a node in this if non-null.

  attr_accessor :root, :path_from_root

  def initialize(root, path_from_root = nil)
    @root = root
    @path_from_root = path_from_root
  end

  # as a side effect, findInObject() will have to resolve all parents of the
  # child being peeked, but NOT the child itself.Caller has to resolve
  # the child itself if needed.ValueWithPath.value can be null but
  # the ValueWithPath instance itself should not be.
  def find_in_object(obj, context, path)
    # resolve ONLY portions of the object which are along our path
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("*** finding '#{path}' in #{obj}")
    end
    restriction = context.restrict_to_child
    partially_resolved = context.restrict(path).resolve(obj, self.class.new(obj))
    new_context = partially_resolved.context.restrict(restriction)
    if partially_resolved.value.is_a?(Hocon::Impl::AbstractConfigObject)
      pair = self.class.find_in_object_impl(partially_resolved.value, path)
      ResultWithPath.new(Hocon::Impl::ResolveResult.make(new_context, pair.value), pair.path_from_root)
    else
      raise ConfigBugOrBrokenError.new("resolved object to non-object " + obj + " to " + partially_resolved)
    end
  end

  def lookup_subst(context, subst, prefix_length)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("searching for #{subst}", context.depth)
    end

    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("#{subst} - looking up relative to file it occurred in",
                                    context.depth)
    end
    # First we look up the full path, which means relative to the
    # included file if we were not a root file
    result = find_in_object(@root, context, subst.path)

    if result.result.value == nil
      # Then we want to check relative to the root file.We don 't
      # want the prefix we were included at to be used when looking
      # up env variables either.
      unprefixed = subst.path.sub_path_to_end(prefix_length)

      if prefix_length > 0
        if Hocon::Impl::ConfigImpl.trace_substitution_enabled
          Hocon::Impl::ConfigImpl.trace(
              unprefixed + " - looking up relative to parent file",
              result.result.context.depth)
        end
        result = find_in_object(@root, result.result.context, unprefixed)
      end

      if result.result.value == nil && result.result.context.options.use_system_environment
        if Hocon::Impl::ConfigImpl.trace_substitution_enabled
          Hocon::Impl::ConfigImpl.trace(
              "#{unprefixed} - looking up in system environment",
              result.result.context.depth)
        end
        result = find_in_object(Hocon::Impl::ConfigImpl.env_variables_as_config_object, context, unprefixed)
      end
    end

    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace(
          "resolved to #{result}",
          result.result.context.depth)
    end

    result
  end

  def push_parent(parent)
    unless parent
      raise ConfigBugOrBrokenError.new("can't push null parent")
    end

    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("pushing parent #{parent} ==root #{(parent == root)} onto #{self}")
    end

    if @path_from_root == nil
      if parent.equal?(@root)
        return self.class.new(@root, Node.new(parent))
      else
        if Hocon::Impl::ConfigImpl.trace_substitution_enabled
          # this hasDescendant check is super-expensive so it's a
          # trace message rather than an assertion
          if @root.has_descendant?(parent)
            Hocon::Impl::ConfigImpl.trace(
                "***** BUG ***** tried to push parent #{parent} without having a path to it in #{self}")
          end
        end
        # ignore parents if we aren't proceeding from the
        # root
        return self
      end
    else
      parent_parent = @path_from_root.head
      if Hocon::Impl::ConfigImpl.trace_substitution_enabled
        # this hasDescendant check is super-expensive so it's a
        # trace message rather than an assertion
        if parent_parent != nil && !parent_parent.has_descendant?(parent)
          Hocon::Impl::ConfigImpl.trace(
              "***** BUG ***** trying to push non-child of #{parent_parent}, non-child was #{parent}")
        end
      end

      self.class.new(@root, @path_from_root.prepend(parent))
    end
  end

  def reset_parents
    if @path_from_root == nil
      this
    else
      self.class.new(@root)
    end
  end

  def replace_current_parent(old, replacement)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("replaceCurrentParent old #{old}@#{old.hash} replacement " +
                                        "#{replacement}@#{old.hash} in #{self}")
    end
    if old.equal?(replacement)
      self
    elsif @path_from_root != nil
      new_path = self.class.replace(@path_from_root, old, replacement)
      if Hocon::Impl::ConfigImpl.trace_substitution_enabled
        Hocon::Impl::ConfigImpl.trace("replaced #{old} with #{replacement} in #{self}")
        Hocon::Impl::ConfigImpl.trace("path was: #{@path_from_root} is now #{new_path}")
      end
      # if we end up nuking the root object itself, we replace it with an
      # empty root
      if new_path != nil
        return self.class.new(new_path.last, new_path)
      else
        return self.class.new(Hocon::Impl::SimpleConfigObject.empty)
      end
    else
      if old.equal?(@root)
        return self.class.new(root_must_be_obj(replacement))
      else
        raise ConfigBugOrBrokenError.new("attempt to replace root #{root} with #{replacement}")
      end
    end
  end

  # replacement may be null to delete
  def replace_within_current_parent(old, replacement)
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("replaceWithinCurrentParent old #{old}@#{old.hash}" +
                                        " replacement #{replacement}@#{old.hash} in #{self}")
    end
    if old.equal?(replacement)
      self
    elsif @path_from_root != nil
      parent = @path_from_root.head
      new_parent = parent.replace_child(old, replacement)
      return replace_current_parent(parent, new_parent.is_a?(Hocon::Impl::Container) ? new_parent : nil)
    else
      if old.equal?(@root) && replacement.is_a?(Hocon::Impl::Container)
        return self.class.new(root_must_be_obj(replacement))
      else
        raise ConfigBugOrBrokenError.new("replace in parent not possible #{old} with #{replacement}" +
                                             " in #{self}")
      end
    end
  end

  def to_s
    "ResolveSource(root=#{@root}, pathFromRoot=#{@path_from_root})"
  end

  # a persistent list
  class Node
    attr_reader :next_node, :value

    def initialize(value, next_node = nil)
      @value = value
      @next_node = next_node
    end

    def prepend(value)
      Node.new(value, self)
    end

    def head
      @value
    end

    def tail
      @next_node
    end

    def last
      i = self
      while i.next_node != nil
        i = i.next_node
      end
      i.value
    end

    def reverse
      if @next_node == nil
        self
      else
        reversed = Node.new(@value)
        i = @next_node
        while i != nil
          reversed = reversed.prepend(i.value)
          i = i.next_node
        end
        reversed
      end
    end

    def to_s
      sb = ""
      sb << "["
      to_append_value = self.reverse
      while to_append_value != nil
        sb << to_append_value.value.to_s
        if to_append_value.next_node != nil
          sb << " <= "
        end
        to_append_value = to_append_value.next_node
      end
      sb << "]"
      sb
    end
  end

  # value is allowed to be null
  class ValueWithPath
    attr_reader :value, :path_from_root

    def initialize(value, path_from_root)
      @value = value
      @path_from_root = path_from_root
    end

    def to_s
      "ValueWithPath(value=" + @value + ", pathFromRoot=" + @path_from_root + ")"
    end
  end

  class ResultWithPath
    attr_reader :result, :path_from_root

    def initialize(result, path_from_root)
      @result = result
      @path_from_root = path_from_root
    end

    def to_s
      "ResultWithPath(result=#{@result}, pathFromRoot=#{@path_from_root})"
    end
  end

  private

  def root_must_be_obj(value)
    if value.is_a?(Hocon::Impl::AbstractConfigObject)
      value
    else
      Hocon::Impl::SimpleConfigObject.empty
    end
  end
  
  def self.find_in_object_impl(obj, path, parents = nil)
    begin
      # we 'll fail if anything along the path can' t
      # be looked at without resolving.
      find_in_object_impl_impl(obj, path, nil)
    rescue ConfigNotResolvedError => e
      raise Hocon::Impl::ConfigImpl.improve_not_resolved(path, e)
    end
  end

  def self.find_in_object_impl_impl(obj, path, parents)
    key = path.first
    remainder = path.remainder
    if Hocon::Impl::ConfigImpl.trace_substitution_enabled
      Hocon::Impl::ConfigImpl.trace("*** looking up '#{key}' in #{obj}")
    end
    v = obj.attempt_peek_with_partial_resolve(key)
    new_parents = parents == nil ? Node.new(obj) : parents.prepend(obj)

    if remainder == nil
      ValueWithPath.new(v, new_parents)
    else
      if v.is_a?(Hocon::Impl::AbstractConfigObject)
        find_in_object_impl_impl(v, remainder, new_parents)
      else
        ValueWithPath.new(nil, new_parents)
      end
    end
  end

  # returns null if the replacement results in deleting all the nodes.
  def self.replace(list, old, replacement)
    child = list.head
    unless child.equal?(old)
      raise ConfigBugOrBrokenError.new("Can only replace() the top node we're resolving; had " + child +
                                           " on top and tried to replace " + old + " overall list was " + list)
    end
    parent = list.tail == nil ? nil : list.tail.head
    if replacement == nil || !replacement.is_a?(Hocon::Impl::Container)
      if parent == nil
        return nil
      else
        # we are deleting the child from the stack of containers
        # because it's either going away or not a container
        new_parent = parent.replace_child(old, nil)

        return replace(list.tail, parent, new_parent)
      end
    else
      # we replaced the container with another container
      if parent == nil
        return Node.new(replacement)
      else
        new_parent = parent.replace_child(old, replacement)
        new_tail = replace(list.tail, parent, new_parent)
        if new_tail != nil
          return new_tail.prepend(replacement)
        else
          return Node.new(replacement)
        end
      end
    end
  end
end