hocon 0.0.7 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +4 -2
  3. data/lib/hocon.rb +2 -0
  4. data/lib/hocon/config.rb +1010 -0
  5. data/lib/hocon/config_error.rb +32 -2
  6. data/lib/hocon/config_factory.rb +46 -0
  7. data/lib/hocon/config_include_context.rb +49 -0
  8. data/lib/hocon/config_includer_file.rb +27 -0
  9. data/lib/hocon/config_list.rb +49 -0
  10. data/lib/hocon/config_mergeable.rb +74 -0
  11. data/lib/hocon/config_object.rb +144 -1
  12. data/lib/hocon/config_parse_options.rb +33 -9
  13. data/lib/hocon/config_parseable.rb +51 -0
  14. data/lib/hocon/config_render_options.rb +4 -2
  15. data/lib/hocon/config_resolve_options.rb +31 -0
  16. data/lib/hocon/config_syntax.rb +5 -2
  17. data/lib/hocon/config_util.rb +73 -0
  18. data/lib/hocon/config_value.rb +122 -0
  19. data/lib/hocon/config_value_factory.rb +66 -2
  20. data/lib/hocon/config_value_type.rb +5 -2
  21. data/lib/hocon/impl.rb +2 -0
  22. data/lib/hocon/impl/abstract_config_node.rb +29 -0
  23. data/lib/hocon/impl/abstract_config_node_value.rb +11 -0
  24. data/lib/hocon/impl/abstract_config_object.rb +148 -42
  25. data/lib/hocon/impl/abstract_config_value.rb +251 -11
  26. data/lib/hocon/impl/array_iterator.rb +19 -0
  27. data/lib/hocon/impl/config_boolean.rb +7 -1
  28. data/lib/hocon/impl/config_concatenation.rb +177 -28
  29. data/lib/hocon/impl/config_delayed_merge.rb +329 -0
  30. data/lib/hocon/impl/config_delayed_merge_object.rb +274 -0
  31. data/lib/hocon/impl/config_document_parser.rb +647 -0
  32. data/lib/hocon/impl/config_double.rb +44 -0
  33. data/lib/hocon/impl/config_impl.rb +143 -19
  34. data/lib/hocon/impl/config_impl_util.rb +18 -0
  35. data/lib/hocon/impl/config_include_kind.rb +10 -0
  36. data/lib/hocon/impl/config_int.rb +13 -1
  37. data/lib/hocon/impl/config_node_array.rb +11 -0
  38. data/lib/hocon/impl/config_node_comment.rb +19 -0
  39. data/lib/hocon/impl/config_node_complex_value.rb +54 -0
  40. data/lib/hocon/impl/config_node_concatenation.rb +11 -0
  41. data/lib/hocon/impl/config_node_field.rb +81 -0
  42. data/lib/hocon/impl/config_node_include.rb +33 -0
  43. data/lib/hocon/impl/config_node_object.rb +276 -0
  44. data/lib/hocon/impl/config_node_path.rb +48 -0
  45. data/lib/hocon/impl/config_node_root.rb +60 -0
  46. data/lib/hocon/impl/config_node_simple_value.rb +42 -0
  47. data/lib/hocon/impl/config_node_single_token.rb +17 -0
  48. data/lib/hocon/impl/config_null.rb +15 -7
  49. data/lib/hocon/impl/config_number.rb +43 -4
  50. data/lib/hocon/impl/config_parser.rb +403 -0
  51. data/lib/hocon/impl/config_reference.rb +142 -0
  52. data/lib/hocon/impl/config_string.rb +55 -7
  53. data/lib/hocon/impl/container.rb +29 -0
  54. data/lib/hocon/impl/default_transformer.rb +24 -15
  55. data/lib/hocon/impl/from_map_mode.rb +3 -1
  56. data/lib/hocon/impl/full_includer.rb +2 -0
  57. data/lib/hocon/impl/memo_key.rb +42 -0
  58. data/lib/hocon/impl/mergeable_value.rb +8 -0
  59. data/lib/hocon/impl/origin_type.rb +8 -2
  60. data/lib/hocon/impl/parseable.rb +455 -91
  61. data/lib/hocon/impl/path.rb +181 -59
  62. data/lib/hocon/impl/path_builder.rb +24 -3
  63. data/lib/hocon/impl/path_parser.rb +280 -0
  64. data/lib/hocon/impl/replaceable_merge_stack.rb +22 -0
  65. data/lib/hocon/impl/resolve_context.rb +254 -0
  66. data/lib/hocon/impl/resolve_memos.rb +21 -0
  67. data/lib/hocon/impl/resolve_result.rb +39 -0
  68. data/lib/hocon/impl/resolve_source.rb +354 -0
  69. data/lib/hocon/impl/resolve_status.rb +3 -1
  70. data/lib/hocon/impl/simple_config.rb +264 -10
  71. data/lib/hocon/impl/simple_config_document.rb +48 -0
  72. data/lib/hocon/impl/simple_config_list.rb +282 -8
  73. data/lib/hocon/impl/simple_config_object.rb +424 -88
  74. data/lib/hocon/impl/simple_config_origin.rb +263 -71
  75. data/lib/hocon/impl/simple_include_context.rb +31 -1
  76. data/lib/hocon/impl/simple_includer.rb +196 -1
  77. data/lib/hocon/impl/substitution_expression.rb +38 -0
  78. data/lib/hocon/impl/token.rb +17 -4
  79. data/lib/hocon/impl/token_type.rb +6 -2
  80. data/lib/hocon/impl/tokenizer.rb +339 -109
  81. data/lib/hocon/impl/tokens.rb +330 -79
  82. data/lib/hocon/impl/unmergeable.rb +14 -1
  83. data/lib/hocon/impl/unsupported_operation_error.rb +6 -0
  84. data/lib/hocon/impl/url.rb +37 -0
  85. data/lib/hocon/parser.rb +7 -0
  86. data/lib/hocon/parser/config_document.rb +92 -0
  87. data/lib/hocon/parser/config_document_factory.rb +36 -0
  88. data/lib/hocon/parser/config_node.rb +30 -0
  89. metadata +67 -43
  90. data/lib/hocon/impl/config_float.rb +0 -13
  91. data/lib/hocon/impl/parser.rb +0 -977
  92. data/lib/hocon/impl/properties_parser.rb +0 -83
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/impl/container'
5
+ require 'hocon/config_error'
6
+
7
+ #
8
+ # Implemented by a merge stack (ConfigDelayedMerge, ConfigDelayedMergeObject)
9
+ # that replaces itself during substitution resolution in order to implement
10
+ # "look backwards only" semantics.
11
+ #
12
+ module Hocon::Impl::ReplaceableMergeStack
13
+ include Hocon::Impl::Container
14
+
15
+ #
16
+ # Make a replacement for this object skipping the given number of elements
17
+ # which are lower in merge priority.
18
+ #
19
+ def make_replacement(context, skipping)
20
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ReplaceableMergeStack` must implement `make_replacement` (#{self.class})"
21
+ end
22
+ end
@@ -0,0 +1,254 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'hocon/config_error'
5
+ require 'hocon/impl/resolve_source'
6
+ require 'hocon/impl/resolve_memos'
7
+ require 'hocon/impl/memo_key'
8
+ require 'hocon/impl/abstract_config_value'
9
+ require 'hocon/impl/config_impl'
10
+
11
+ class Hocon::Impl::ResolveContext
12
+
13
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
14
+ NotPossibleToResolve = Hocon::Impl::AbstractConfigValue::NotPossibleToResolve
15
+
16
+ attr_reader :restrict_to_child
17
+
18
+ def initialize(memos, options, restrict_to_child, resolve_stack, cycle_markers)
19
+ @memos = memos
20
+ @options = options
21
+ @restrict_to_child = restrict_to_child
22
+ @resolve_stack = resolve_stack
23
+ @cycle_markers = cycle_markers
24
+ end
25
+
26
+ def self.new_cycle_markers
27
+ # This looks crazy, but wtf else should we do with
28
+ # return Collections.newSetFromMap(new IdentityHashMap<AbstractConfigValue, Boolean>());
29
+ Set.new
30
+ end
31
+
32
+ def add_cycle_marker(value)
33
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
34
+ Hocon::Impl::ConfigImpl.trace("++ Cycle marker #{value}@#{value.hash}",
35
+ depth)
36
+ end
37
+ if @cycle_markers.include?(value)
38
+ raise ConfigBugOrBrokenError.new("Added cycle marker twice " + value)
39
+ end
40
+ copy = self.class.new_cycle_markers
41
+ copy.merge(@cycle_markers)
42
+ copy.add(value)
43
+ self.class.new(@memos, @options, @restrict_to_child, @resolve_stack, copy)
44
+ end
45
+
46
+ def remove_cycle_marker(value)
47
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
48
+ Hocon::Impl::ConfigImpl.trace("-- Cycle marker #{value}@#{value.hash}",
49
+ depth)
50
+ end
51
+
52
+ copy = self.class.new_cycle_markers
53
+ copy.merge(@cycle_markers)
54
+ copy.delete(value)
55
+ self.class.new(@memos, @options, @restrict_to_child, @resolve_stack, copy)
56
+ end
57
+
58
+ def memoize(key, value)
59
+ changed = @memos.put(key, value)
60
+ self.class.new(changed, @options, @restrict_to_child, @resolve_stack, @cycle_markers)
61
+ end
62
+
63
+ def options
64
+ @options
65
+ end
66
+
67
+ def is_restricted_to_child
68
+ @restrict_to_child != nil
69
+ end
70
+
71
+ def restrict(restrict_to)
72
+ if restrict_to.equal?(@restrict_to_child)
73
+ self
74
+ else
75
+ Hocon::Impl::ResolveContext.new(@memos, @options, restrict_to, @resolve_stack, @cycle_markers)
76
+ end
77
+ end
78
+
79
+ def unrestricted
80
+ restrict(nil)
81
+ end
82
+
83
+ def resolve(original, source)
84
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
85
+ Hocon::Impl::ConfigImpl.trace(
86
+ "resolving #{original} restrict_to_child=#{@restrict_to_child} in #{source}",
87
+ depth)
88
+ end
89
+ push_trace(original).real_resolve(original, source).pop_trace
90
+ end
91
+
92
+ def real_resolve(original, source)
93
+ # a fully-resolved (no restrict_to_child) object can satisfy a
94
+ # request for a restricted object, so always check that first.
95
+ full_key = Hocon::Impl::MemoKey.new(original, nil)
96
+ restricted_key = nil
97
+
98
+ cached = @memos.get(full_key)
99
+
100
+ # but if there was no fully-resolved object cached, we'll only
101
+ # compute the restrictToChild object so use a more limited
102
+ # memo key
103
+ if cached == nil && is_restricted_to_child
104
+ restricted_key = Hocon::Impl::MemoKey.new(original, @restrict_to_child)
105
+ cached = @memos.get(restricted_key)
106
+ end
107
+
108
+ if cached != nil
109
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
110
+ Hocon::Impl::ConfigImpl.trace(
111
+ "using cached resolution #{cached} for #{original} restrict_to_child #{@restrict_to_child}",
112
+ depth)
113
+ end
114
+ Hocon::Impl::ResolveResult.make(self, cached)
115
+ else
116
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
117
+ Hocon::Impl::ConfigImpl.trace(
118
+ "not found in cache, resolving #{original}@#{original.hash}",
119
+ depth)
120
+ end
121
+
122
+ if @cycle_markers.include?(original)
123
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
124
+ Hocon::Impl::ConfigImpl.trace(
125
+ "Cycle detected, can't resolve; #{original}@#{original.hash}",
126
+ depth)
127
+ end
128
+ raise NotPossibleToResolve.new(self)
129
+ end
130
+
131
+ result = original.resolve_substitutions(self, source)
132
+ resolved = result.value
133
+
134
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
135
+ Hocon::Impl::ConfigImpl.trace(
136
+ "resolved to #{resolved}@#{resolved.hash} from #{original}@#{resolved.hash}",
137
+ depth)
138
+ end
139
+
140
+ with_memo = result.context
141
+
142
+ if resolved == nil || resolved.resolve_status == Hocon::Impl::ResolveStatus::RESOLVED
143
+ # if the resolved object is fully resolved by resolving
144
+ # only the restrictToChildOrNull, then it can be cached
145
+ # under fullKey since the child we were restricted to
146
+ # turned out to be the only unresolved thing.
147
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
148
+ Hocon::Impl::ConfigImpl.trace(
149
+ "caching #{full_key} result #{resolved}",
150
+ depth)
151
+ end
152
+
153
+ with_memo = with_memo.memoize(full_key, resolved)
154
+ else
155
+ # if we have an unresolved object then either we did a
156
+ # partial resolve restricted to a certain child, or we are
157
+ # allowing incomplete resolution, or it's a bug.
158
+ if is_restricted_to_child
159
+ if restricted_key == nil
160
+ raise ConfigBugOrBrokenError.new("restricted_key should not be null here")
161
+ end
162
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
163
+ Hocon::Impl::ConfigImpl.trace(
164
+ "caching #{restricted_key} result #{resolved}",
165
+ depth)
166
+ end
167
+
168
+ with_memo = with_memo.memoize(restricted_key, resolved)
169
+ elsif @options.allow_unresolved
170
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
171
+ Hocon::Impl::ConfigImpl.trace(
172
+ "caching #{full_key} result #{resolved}",
173
+ depth)
174
+ end
175
+
176
+ with_memo = with_memo.memoize(full_key, resolved)
177
+ else
178
+ raise ConfigBugOrBrokenError.new(
179
+ "resolve_substitutions did not give us a resolved object")
180
+ end
181
+ end
182
+ Hocon::Impl::ResolveResult.make(with_memo, resolved)
183
+ end
184
+ end
185
+
186
+ # This method is a translation of the constructor in the Java version with signature
187
+ # ResolveContext(ConfigResolveOptions options, Path restrictToChild)
188
+ def self.construct(options, restrict_to_child)
189
+ context = self.new(Hocon::Impl::ResolveMemos.new,
190
+ options,
191
+ restrict_to_child,
192
+ [],
193
+ new_cycle_markers)
194
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
195
+ Hocon::Impl::ConfigImpl.trace(
196
+ "ResolveContext restrict to child #{restrict_to_child}", context.depth)
197
+ end
198
+ context
199
+ end
200
+
201
+ def trace_string
202
+ separator = ", "
203
+ sb = ""
204
+ @resolve_stack.each { |value|
205
+ if value.instance_of?(Hocon::Impl::ConfigReference)
206
+ sb << value.expression.to_s
207
+ sb << separator
208
+ end
209
+ }
210
+ if sb.length > 0
211
+ sb.chomp!(separator)
212
+ end
213
+ sb
214
+ end
215
+
216
+ def depth
217
+ if @resolve_stack.size > 30
218
+ raise Hocon::ConfigError::ConfigBugOrBrokenError.new("resolve getting too deep")
219
+ end
220
+ @resolve_stack.size
221
+ end
222
+
223
+ def self.resolve(value, root, options)
224
+ source = Hocon::Impl::ResolveSource.new(root)
225
+ context = construct(options, nil)
226
+ begin
227
+ context.resolve(value, source).value
228
+ rescue NotPossibleToResolve => e
229
+ # ConfigReference was supposed to catch NotPossibleToResolve
230
+ raise ConfigBugOrBrokenError(
231
+ "NotPossibleToResolve was thrown from an outermost resolve", e)
232
+ end
233
+ end
234
+
235
+ def pop_trace
236
+ copy = @resolve_stack.clone
237
+ old = copy.delete_at(@resolve_stack.size - 1)
238
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
239
+ Hocon::Impl::ConfigImpl.trace("popped trace #{old}", depth - 1)
240
+ end
241
+ Hocon::Impl::ResolveContext.new(@memos, @options, @restrict_to_child, copy, @cycle_markers)
242
+ end
243
+
244
+ private
245
+
246
+ def push_trace(value)
247
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
248
+ Hocon::Impl::ConfigImpl.trace("pushing trace #{value}", depth)
249
+ end
250
+ copy = @resolve_stack.clone
251
+ copy << value
252
+ Hocon::Impl::ResolveContext.new(@memos, @options, @restrict_to_child, copy, @cycle_markers)
253
+ end
254
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'hocon/impl'
5
+
6
+ class Hocon::Impl::ResolveMemos
7
+
8
+ def initialize(memos = {})
9
+ @memos = memos
10
+ end
11
+
12
+ def get(key)
13
+ @memos[key]
14
+ end
15
+
16
+ def put(key, value)
17
+ copy = @memos.clone
18
+ copy[key] = value
19
+ Hocon::Impl::ResolveMemos.new(copy)
20
+ end
21
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'hocon/impl'
5
+
6
+ # value is allowed to be null
7
+ class Hocon::Impl::ResolveResult
8
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
9
+
10
+ attr_accessor :context, :value
11
+
12
+ def initialize(context, value)
13
+ @context = context
14
+ @value = value
15
+ end
16
+
17
+ def self.make(context, value)
18
+ self.new(context, value)
19
+ end
20
+
21
+ def as_object_result
22
+ unless @value.is_a?(Hocon::Impl::AbstractConfigObject)
23
+ raise ConfigBugOrBrokenError.new("Expecting a resolve result to be an object, but it was #{@value}")
24
+ end
25
+ self
26
+ end
27
+
28
+ def as_value_result
29
+ self
30
+ end
31
+
32
+ def pop_trace
33
+ self.class.make(@context.pop_trace, value)
34
+ end
35
+
36
+ def to_s
37
+ "ResolveResult(#{@value})"
38
+ end
39
+ end
@@ -0,0 +1,354 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'hocon/config_error'
5
+ require 'hocon/impl'
6
+ require 'hocon/impl/config_impl'
7
+ require 'hocon/impl/container'
8
+
9
+ class Hocon::Impl::ResolveSource
10
+
11
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
12
+ ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError
13
+
14
+ # 'path_from_root' is used for knowing the chain of parents we used to get here.
15
+ # null if we should assume we are not a descendant of the root.
16
+ # the root itself should be a node in this if non-null.
17
+
18
+ attr_accessor :root, :path_from_root
19
+
20
+ def initialize(root, path_from_root = nil)
21
+ @root = root
22
+ @path_from_root = path_from_root
23
+ end
24
+
25
+ # as a side effect, findInObject() will have to resolve all parents of the
26
+ # child being peeked, but NOT the child itself.Caller has to resolve
27
+ # the child itself if needed.ValueWithPath.value can be null but
28
+ # the ValueWithPath instance itself should not be.
29
+ def find_in_object(obj, context, path)
30
+ # resolve ONLY portions of the object which are along our path
31
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
32
+ Hocon::Impl::ConfigImpl.trace("*** finding '#{path}' in #{obj}")
33
+ end
34
+ restriction = context.restrict_to_child
35
+ partially_resolved = context.restrict(path).resolve(obj, self.class.new(obj))
36
+ new_context = partially_resolved.context.restrict(restriction)
37
+ if partially_resolved.value.is_a?(Hocon::Impl::AbstractConfigObject)
38
+ pair = self.class.find_in_object_impl(partially_resolved.value, path)
39
+ ResultWithPath.new(Hocon::Impl::ResolveResult.make(new_context, pair.value), pair.path_from_root)
40
+ else
41
+ raise ConfigBugOrBrokenError.new("resolved object to non-object " + obj + " to " + partially_resolved)
42
+ end
43
+ end
44
+
45
+ def lookup_subst(context, subst, prefix_length)
46
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
47
+ Hocon::Impl::ConfigImpl.trace("searching for #{subst}", context.depth)
48
+ end
49
+
50
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
51
+ Hocon::Impl::ConfigImpl.trace("#{subst} - looking up relative to file it occurred in",
52
+ context.depth)
53
+ end
54
+ # First we look up the full path, which means relative to the
55
+ # included file if we were not a root file
56
+ result = find_in_object(@root, context, subst.path)
57
+
58
+ if result.result.value == nil
59
+ # Then we want to check relative to the root file.We don 't
60
+ # want the prefix we were included at to be used when looking
61
+ # up env variables either.
62
+ unprefixed = subst.path.sub_path_to_end(prefix_length)
63
+
64
+ if prefix_length > 0
65
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
66
+ Hocon::Impl::ConfigImpl.trace(
67
+ unprefixed + " - looking up relative to parent file",
68
+ result.result.context.depth)
69
+ end
70
+ result = find_in_object(@root, result.result.context, unprefixed)
71
+ end
72
+
73
+ if result.result.value == nil && result.result.context.options.use_system_environment
74
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
75
+ Hocon::Impl::ConfigImpl.trace(
76
+ "#{unprefixed} - looking up in system environment",
77
+ result.result.context.depth)
78
+ end
79
+ result = find_in_object(Hocon::Impl::ConfigImpl.env_variables_as_config_object, context, unprefixed)
80
+ end
81
+ end
82
+
83
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
84
+ Hocon::Impl::ConfigImpl.trace(
85
+ "resolved to #{result}",
86
+ result.result.context.depth)
87
+ end
88
+
89
+ result
90
+ end
91
+
92
+ def push_parent(parent)
93
+ unless parent
94
+ raise ConfigBugOrBrokenError.new("can't push null parent")
95
+ end
96
+
97
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
98
+ Hocon::Impl::ConfigImpl.trace("pushing parent #{parent} ==root #{(parent == root)} onto #{self}")
99
+ end
100
+
101
+ if @path_from_root == nil
102
+ if parent.equal?(@root)
103
+ return self.class.new(@root, Node.new(parent))
104
+ else
105
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
106
+ # this hasDescendant check is super-expensive so it's a
107
+ # trace message rather than an assertion
108
+ if @root.has_descendant?(parent)
109
+ Hocon::Impl::ConfigImpl.trace(
110
+ "***** BUG ***** tried to push parent #{parent} without having a path to it in #{self}")
111
+ end
112
+ end
113
+ # ignore parents if we aren't proceeding from the
114
+ # root
115
+ return self
116
+ end
117
+ else
118
+ parent_parent = @path_from_root.head
119
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
120
+ # this hasDescendant check is super-expensive so it's a
121
+ # trace message rather than an assertion
122
+ if parent_parent != nil && !parent_parent.has_descendant?(parent)
123
+ Hocon::Impl::ConfigImpl.trace(
124
+ "***** BUG ***** trying to push non-child of #{parent_parent}, non-child was #{parent}")
125
+ end
126
+ end
127
+
128
+ self.class.new(@root, @path_from_root.prepend(parent))
129
+ end
130
+ end
131
+
132
+ def reset_parents
133
+ if @path_from_root == nil
134
+ this
135
+ else
136
+ self.class.new(@root)
137
+ end
138
+ end
139
+
140
+ def replace_current_parent(old, replacement)
141
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
142
+ Hocon::Impl::ConfigImpl.trace("replaceCurrentParent old #{old}@#{old.hash} replacement " +
143
+ "#{replacement}@#{old.hash} in #{self}")
144
+ end
145
+ if old.equal?(replacement)
146
+ self
147
+ elsif @path_from_root != nil
148
+ new_path = self.class.replace(@path_from_root, old, replacement)
149
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
150
+ Hocon::Impl::ConfigImpl.trace("replaced #{old} with #{replacement} in #{self}")
151
+ Hocon::Impl::ConfigImpl.trace("path was: #{@path_from_root} is now #{new_path}")
152
+ end
153
+ # if we end up nuking the root object itself, we replace it with an
154
+ # empty root
155
+ if new_path != nil
156
+ return self.class.new(new_path.last, new_path)
157
+ else
158
+ return self.class.new(Hocon::Impl::SimpleConfigObject.empty)
159
+ end
160
+ else
161
+ if old.equal?(@root)
162
+ return self.class.new(root_must_be_obj(replacement))
163
+ else
164
+ raise ConfigBugOrBrokenError.new("attempt to replace root #{root} with #{replacement}")
165
+ end
166
+ end
167
+ end
168
+
169
+ # replacement may be null to delete
170
+ def replace_within_current_parent(old, replacement)
171
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
172
+ Hocon::Impl::ConfigImpl.trace("replaceWithinCurrentParent old #{old}@#{old.hash}" +
173
+ " replacement #{replacement}@#{old.hash} in #{self}")
174
+ end
175
+ if old.equal?(replacement)
176
+ self
177
+ elsif @path_from_root != nil
178
+ parent = @path_from_root.head
179
+ new_parent = parent.replace_child(old, replacement)
180
+ return replace_current_parent(parent, new_parent.is_a?(Hocon::Impl::Container) ? new_parent : nil)
181
+ else
182
+ if old.equal?(@root) && replacement.is_a?(Hocon::Impl::Container)
183
+ return self.class.new(root_must_be_obj(replacement))
184
+ else
185
+ raise ConfigBugOrBrokenError.new("replace in parent not possible #{old} with #{replacement}" +
186
+ " in #{self}")
187
+ end
188
+ end
189
+ end
190
+
191
+ def to_s
192
+ "ResolveSource(root=#{@root}, pathFromRoot=#{@path_from_root})"
193
+ end
194
+
195
+ # a persistent list
196
+ class Node
197
+ attr_reader :next_node, :value
198
+
199
+ def initialize(value, next_node = nil)
200
+ @value = value
201
+ @next_node = next_node
202
+ end
203
+
204
+ def prepend(value)
205
+ Node.new(value, self)
206
+ end
207
+
208
+ def head
209
+ @value
210
+ end
211
+
212
+ def tail
213
+ @next_node
214
+ end
215
+
216
+ def last
217
+ i = self
218
+ while i.next_node != nil
219
+ i = i.next_node
220
+ end
221
+ i.value
222
+ end
223
+
224
+ def reverse
225
+ if @next_node == nil
226
+ self
227
+ else
228
+ reversed = Node.new(@value)
229
+ i = @next_node
230
+ while i != nil
231
+ reversed = reversed.prepend(i.value)
232
+ i = i.next_node
233
+ end
234
+ reversed
235
+ end
236
+ end
237
+
238
+ def to_s
239
+ sb = ""
240
+ sb << "["
241
+ to_append_value = self.reverse
242
+ while to_append_value != nil
243
+ sb << to_append_value.value.to_s
244
+ if to_append_value.next_node != nil
245
+ sb << " <= "
246
+ end
247
+ to_append_value = to_append_value.next_node
248
+ end
249
+ sb << "]"
250
+ sb
251
+ end
252
+ end
253
+
254
+ # value is allowed to be null
255
+ class ValueWithPath
256
+ attr_reader :value, :path_from_root
257
+
258
+ def initialize(value, path_from_root)
259
+ @value = value
260
+ @path_from_root = path_from_root
261
+ end
262
+
263
+ def to_s
264
+ "ValueWithPath(value=" + @value + ", pathFromRoot=" + @path_from_root + ")"
265
+ end
266
+ end
267
+
268
+ class ResultWithPath
269
+ attr_reader :result, :path_from_root
270
+
271
+ def initialize(result, path_from_root)
272
+ @result = result
273
+ @path_from_root = path_from_root
274
+ end
275
+
276
+ def to_s
277
+ "ResultWithPath(result=#{@result}, pathFromRoot=#{@path_from_root})"
278
+ end
279
+ end
280
+
281
+ private
282
+
283
+ def root_must_be_obj(value)
284
+ if value.is_a?(Hocon::Impl::AbstractConfigObject)
285
+ value
286
+ else
287
+ Hocon::Impl::SimpleConfigObject.empty
288
+ end
289
+ end
290
+
291
+ def self.find_in_object_impl(obj, path, parents = nil)
292
+ begin
293
+ # we 'll fail if anything along the path can' t
294
+ # be looked at without resolving.
295
+ find_in_object_impl_impl(obj, path, nil)
296
+ rescue ConfigNotResolvedError => e
297
+ raise Hocon::Impl::ConfigImpl.improve_not_resolved(path, e)
298
+ end
299
+ end
300
+
301
+ def self.find_in_object_impl_impl(obj, path, parents)
302
+ key = path.first
303
+ remainder = path.remainder
304
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
305
+ Hocon::Impl::ConfigImpl.trace("*** looking up '#{key}' in #{obj}")
306
+ end
307
+ v = obj.attempt_peek_with_partial_resolve(key)
308
+ new_parents = parents == nil ? Node.new(obj) : parents.prepend(obj)
309
+
310
+ if remainder == nil
311
+ ValueWithPath.new(v, new_parents)
312
+ else
313
+ if v.is_a?(Hocon::Impl::AbstractConfigObject)
314
+ find_in_object_impl_impl(v, remainder, new_parents)
315
+ else
316
+ ValueWithPath.new(nil, new_parents)
317
+ end
318
+ end
319
+ end
320
+
321
+ # returns null if the replacement results in deleting all the nodes.
322
+ def self.replace(list, old, replacement)
323
+ child = list.head
324
+ unless child.equal?(old)
325
+ raise ConfigBugOrBrokenError.new("Can only replace() the top node we're resolving; had " + child +
326
+ " on top and tried to replace " + old + " overall list was " + list)
327
+ end
328
+ parent = list.tail == nil ? nil : list.tail.head
329
+ if replacement == nil || !replacement.is_a?(Hocon::Impl::Container)
330
+ if parent == nil
331
+ return nil
332
+ else
333
+ # we are deleting the child from the stack of containers
334
+ # because it's either going away or not a container
335
+ new_parent = parent.replace_child(old, nil)
336
+
337
+ return replace(list.tail, parent, new_parent)
338
+ end
339
+ else
340
+ # we replaced the container with another container
341
+ if parent == nil
342
+ return Node.new(replacement)
343
+ else
344
+ new_parent = parent.replace_child(old, replacement)
345
+ new_tail = replace(list.tail, parent, new_parent)
346
+ if new_tail != nil
347
+ return new_tail.prepend(replacement)
348
+ else
349
+ return Node.new(replacement)
350
+ end
351
+ end
352
+ end
353
+ end
354
+ end