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
|