File: config_node_object.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 (283 lines) | stat: -rw-r--r-- 11,022 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
# encoding: utf-8

require_relative '../../hocon/config_syntax'
require_relative '../../hocon/impl'
require_relative '../../hocon/impl/config_node_complex_value'
require_relative '../../hocon/impl/config_node_field'
require_relative '../../hocon/impl/config_node_single_token'
require_relative '../../hocon/impl/tokens'

class Hocon::Impl::ConfigNodeObject
  include Hocon::Impl::ConfigNodeComplexValue

  ConfigSyntax = Hocon::ConfigSyntax
  Tokens = Hocon::Impl::Tokens

  def new_node(nodes)
    self.class.new(nodes)
  end

  def has_value(desired_path)
    @children.each do |node|
      if node.is_a?(Hocon::Impl::ConfigNodeField)
        field = node
        key = field.path.value
        if key == desired_path || key.starts_with(desired_path)
          return true
        elsif desired_path.starts_with(key)
          if field.value.is_a?(self.class)
            obj = field.value
            remaining_path = desired_path.sub_path_to_end(key.length)
            if obj.has_value(remaining_path)
              return true
            end
          end
        end
      end
    end
    false
  end

  def change_value_on_path(desired_path, value, flavor)
    children_copy = @children.clone
    seen_non_matching = false
    # Copy the value so we can change it to null but not modify the original parameter
    value_copy = value
    i = children_copy.size
    while i >= 0 do
      if children_copy[i].is_a?(Hocon::Impl::ConfigNodeSingleToken)
        t = children_copy[i].token
        # Ensure that, when we are removing settings in JSON, we don't end up with a trailing comma
        if flavor.equal?(ConfigSyntax::JSON) && !seen_non_matching && t.equal?(Tokens::COMMA)
          children_copy.delete_at(i)
        end
        i -= 1
        next
      elsif !children_copy[i].is_a?(Hocon::Impl::ConfigNodeField)
        i -= 1
        next
      end
      node = children_copy[i]
      key = node.path.value

      # Delete all multi-element paths that start with the desired path, since technically they are duplicates
      if (value_copy.nil? && key == desired_path) || (key.starts_with(desired_path) && !(key == desired_path))
        children_copy.delete_at(i)
        # Remove any whitespace or commas after the deleted setting
        j = i
        while j < children_copy.size
          if children_copy[j].is_a?(Hocon::Impl::ConfigNodeSingleToken)
            t = children_copy[j].token
            if Tokens.ignored_whitespace?(t) || t.equal?(Tokens::COMMA)
              children_copy.delete_at(j)
              j -= 1
            else
              break
            end
          else
            break
          end
          j += 1
        end
      elsif key == desired_path
        seen_non_matching = true
        before = i - 1 > 0 ? children_copy[i - 1] : nil
        if value.is_a?(Hocon::Impl::ConfigNodeComplexValue) && before.is_a?(Hocon::Impl::ConfigNodeSingleToken) &&
            Tokens.ignored_whitespace?(before.token)
          indented_value = value.indent_text(before)
        else
          indented_value = value
        end
        children_copy[i] = node.replace_value(indented_value)
        value_copy = nil
      elsif desired_path.starts_with(key)
        seen_non_matching = true
        if node.value.is_a?(self.class)
          remaining_path = desired_path.sub_path_to_end(key.length)
          children_copy[i] = node.replace_value(node.value.change_value_on_path(remaining_path, value_copy, flavor))
          if !value_copy.nil? && !(node == @children[i])
            value_copy = nil
          end
        end
      else
        seen_non_matching = true
      end
      i -= 1
    end
    self.class.new(children_copy)
  end

  def set_value_on_path(desired_path, value, flavor = ConfigSyntax::CONF)
    path = Hocon::Impl::PathParser.parse_path_node(desired_path, flavor)
    set_value_on_path_node(path, value, flavor)
  end

  def set_value_on_path_node(desired_path, value, flavor)
    node = change_value_on_path(desired_path.value, value, flavor)

    # If the desired Path did not exist, add it
    unless node.has_value(desired_path.value)
      return node.add_value_on_path(desired_path, value, flavor)
    end
    node
  end

  def indentation
    seen_new_line = false
    indentation = []

    if @children.empty?
      return indentation
    end

    @children.each_index do |i|
      unless seen_new_line
        if @children[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(@children[i].token)
          seen_new_line = true
          indentation.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_line(nil)))
        end
      else
        if @children[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) &&
            Tokens.ignored_whitespace?(@children[i].token) &&
            i + 1 < @children.size &&
            (@children[i + 1].is_a?(Hocon::Impl::ConfigNodeField) || @children[i + 1].is_a?(Hocon::Impl::ConfigNodeInclude))
          # Return the indentation of the first setting on its own line
          indentation.push(@children[i])
          return indentation
        end
      end
    end
    if indentation.empty?
      indentation.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, " ")))
      return indentation
    else
      # Calculate the indentation of the ending curly-brace to get the indentation of the root object
      last = @children[-1]
      if last.is_a?(Hocon::Impl::ConfigNodeSingleToken) && last.token.equal?(Tokens::CLOSE_CURLY)
        beforeLast = @children[-2]
        indent = ""
        if beforeLast.is_a?(Hocon::Impl::ConfigNodeSingleToken) &&
            Tokens.ignored_whitespace?(beforeLast.token)
          indent = beforeLast.token.token_text
        end
        indent += "  "
        indentation.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, indent)))
        return indentation
      end
    end

    # The object has no curly braces and is at the root level, so don't indent
    indentation
  end

  def add_value_on_path(desired_path, value, flavor)
    path = desired_path.value
    children_copy = @children.clone
    indentation = indentation().clone

    # If the value we're inserting is a complex value, we'll need to indent it for insertion
    if value.is_a?(Hocon::Impl::ConfigNodeComplexValue) && indentation.length > 0
      indented_value = value.indent_text(indentation[-1])
    else
      indented_value = value
    end
    same_line = !(indentation.length > 0 && indentation[0].is_a?(Hocon::Impl::ConfigNodeSingleToken) &&
                    Tokens.newline?(indentation[0].token))

    # If the path is of length greater than one, see if the value needs to be added further down
    if path.length > 1
       (0..@children.size - 1).reverse_each do |i|
        unless @children[i].is_a?(Hocon::Impl::ConfigNodeField)
          next
        end
        node = @children[i]
        key = node.path.value
        if path.starts_with(key) && node.value.is_a?(self.class)
          remaining_path = desired_path.sub_path(key.length)
          new_value = node.value
          children_copy[i] = node.replace_value(new_value.add_value_on_path(remaining_path, value, flavor))
          return self.class.new(children_copy)
        end
      end
    end

    # Otherwise, construct the new setting
    starts_with_brace = @children[0].is_a?(Hocon::Impl::ConfigNodeSingleToken) && @children[0].token.equal?(Tokens::OPEN_CURLY)
    new_nodes = []
    new_nodes += indentation
    new_nodes.push(desired_path.first)
    new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COLON))
    new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, ' ')))

    if path.length == 1
      new_nodes.push(indented_value)
    else
      # If the path is of length greater than one add the required new objects along the path
      new_object_nodes = []
      new_object_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::OPEN_CURLY))
      if indentation.empty?
        new_object_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_line(nil)))
      end
      new_object_nodes += indentation
      new_object_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::CLOSE_CURLY))
      new_object = self.class.new(new_object_nodes)
      new_nodes.push(new_object.add_value_on_path(desired_path.sub_path(1), indented_value, flavor))
    end

    # Combine these two cases so that we only have to iterate once
    if flavor.equal?(ConfigSyntax::JSON) || starts_with_brace || same_line
      i = children_copy.size - 1
      while i >= 0

        # If we are in JSON or are adding a setting on the same line, we need to add a comma to the
        # last setting
        if (flavor.equal?(ConfigSyntax::JSON) || same_line) && children_copy[i].is_a?(Hocon::Impl::ConfigNodeField)
          if i + 1 >= children_copy.size ||
              !(children_copy[i + 1].is_a?(Hocon::Impl::ConfigNodeSingleToken) && children_copy[i + 1].token.equal?(Tokens::COMMA))
            children_copy.insert(i + 1, Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COMMA))
            break
          end
        end

        # Add the value into the copy of the children map, keeping any whitespace/newlines
        # before the close curly brace
        if starts_with_brace && children_copy[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) &&
            children_copy[i].token == Tokens::CLOSE_CURLY
          previous = children_copy[i - 1]
          if previous.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(previous.token)
            children_copy.insert(i - 1, Hocon::Impl::ConfigNodeField.new(new_nodes))
            i -= 1
          elsif previous.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.ignored_whitespace?(previous.token)
            before_previous = children_copy[i - 2]
            if same_line
              children_copy.insert(i - 1, Hocon::Impl::ConfigNodeField.new(new_nodes))
              i -= 1
            elsif before_previous.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(before_previous.token)
              children_copy.insert(i - 2, Hocon::Impl::ConfigNodeField.new(new_nodes))
              i -= 2
            else
              children_copy.insert(i, Hocon::Impl::ConfigNodeField.new(new_nodes))
            end
          else
            children_copy.insert(i, Hocon::Impl::ConfigNodeField.new(new_nodes))
          end
        end

        i -= 1
      end
    end
    unless starts_with_brace
      if children_copy[-1].is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(children_copy[-1].token)
        children_copy.insert(-2, Hocon::Impl::ConfigNodeField.new(new_nodes))
      else
        children_copy.push(Hocon::Impl::ConfigNodeField.new(new_nodes))
      end
    end
    self.class.new(children_copy)
  end

  def remove_value_on_path(desired_path, flavor)
    path = Hocon::Impl::PathParser.parse_path_node(desired_path, flavor).value
    change_value_on_path(path, nil, flavor)
  end
end