hocon 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -1,4 +1,7 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon'
4
+ require 'hocon/config_error'
2
5
 
3
6
  #
4
7
  # The type of a configuration value (following the <a
@@ -20,7 +23,7 @@ module Hocon::ConfigValueType
20
23
  when BOOLEAN then "BOOLEAN"
21
24
  when NULL then "NULL"
22
25
  when STRING then "STRING"
23
- else raise ConfigBugError, "Unrecognized value type '#{config_value_type}'"
26
+ else raise Hocon::ConfigError::ConfigBugOrBrokenError, "Unrecognized value type '#{config_value_type}'"
24
27
  end
25
28
  end
26
- end
29
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon'
2
4
 
3
5
  module Hocon::Impl
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/parser/config_node'
5
+ require 'hocon/config_error'
6
+
7
+ module Hocon::Impl::AbstractConfigNode
8
+ include Hocon::Parser::ConfigNode
9
+ def tokens
10
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of AbstractConfigNode should override `tokens` (#{self.class})"
11
+ end
12
+
13
+ def render
14
+ orig_text = StringIO.new
15
+ tokens.each do |t|
16
+ orig_text << t.token_text
17
+ end
18
+ orig_text.string
19
+ end
20
+
21
+ def ==(other)
22
+ other.is_a?(Hocon::Impl::AbstractConfigNode) &&
23
+ (render == other.render)
24
+ end
25
+
26
+ def hash
27
+ render.hash
28
+ end
29
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/impl/abstract_config_node'
5
+
6
+ # This essentially exists in the upstream so we can ensure only certain types of
7
+ # config nodes can be passed into some methods. That's not a problem in Ruby, so this is
8
+ # unnecessary, but it seems best to keep it around for consistency
9
+ module Hocon::Impl::AbstractConfigNodeValue
10
+ include Hocon::Impl::AbstractConfigNode
11
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
  require 'hocon/impl/abstract_config_value'
3
5
  require 'hocon/impl/simple_config'
@@ -7,9 +9,13 @@ require 'hocon/impl/resolve_status'
7
9
  require 'hocon/impl/simple_config_origin'
8
10
  require 'hocon/config_error'
9
11
  require 'hocon/impl/config_impl'
12
+ require 'hocon/impl/unsupported_operation_error'
13
+ require 'hocon/impl/container'
10
14
 
11
- class Hocon::Impl::AbstractConfigObject < Hocon::Impl::AbstractConfigValue
15
+ module Hocon::Impl::AbstractConfigObject
12
16
  include Hocon::ConfigObject
17
+ include Hocon::Impl::Container
18
+ include Hocon::Impl::AbstractConfigValue
13
19
 
14
20
  ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
15
21
  ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError
@@ -27,17 +33,117 @@ class Hocon::Impl::AbstractConfigObject < Hocon::Impl::AbstractConfigValue
27
33
  self
28
34
  end
29
35
 
36
+ def with_only_key(key)
37
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_only_key`"
38
+ end
39
+
40
+ def without_key(key)
41
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `without_key`"
42
+ end
43
+
44
+ def with_value(key, value)
45
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_value`"
46
+ end
47
+
48
+ def with_only_path_or_nil(path)
49
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_only_path_or_nil`"
50
+ end
51
+
52
+ def with_only_path(path)
53
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_only_path`"
54
+ end
55
+
56
+ def without_path(path)
57
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `without_path`"
58
+ end
59
+
60
+ def with_path_value(path, value)
61
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_path_value`"
62
+ end
63
+
64
+ # This looks up the key with no transformation or type conversion of any
65
+ # kind, and returns null if the key is not present. The object must be
66
+ # resolved along the nodes needed to get the key or
67
+ # ConfigNotResolvedError will be thrown.
68
+ #
69
+ # @param key
70
+ # @return the unmodified raw value or null
71
+ def peek_assuming_resolved(key, original_path)
72
+ begin
73
+ attempt_peek_with_partial_resolve(key)
74
+ rescue ConfigNotResolvedError => e
75
+ raise Hocon::Impl::ConfigImpl.improve_not_resolved(original_path, e)
76
+ end
77
+ end
78
+
79
+ # Look up the key on an only-partially-resolved object, with no
80
+ # transformation or type conversion of any kind; if 'this' is not resolved
81
+ # then try to look up the key anyway if possible.
82
+ #
83
+ # @param key
84
+ # key to look up
85
+ # @return the value of the key, or null if known not to exist
86
+ # @throws ConfigNotResolvedError
87
+ # if can't figure out key's value (or existence) without more
88
+ # resolving
89
+ def attempt_peek_with_partial_resolve(key)
90
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `attempt_peek_with_partial_resolve`"
91
+ end
92
+
93
+ # Looks up the path with no transformation or type conversion. Returns null
94
+ # if the path is not found; throws ConfigException.NotResolved if we need
95
+ # to go through an unresolved node to look up the path.
96
+ def peek_path(path)
97
+ peek_path_from_obj(self, path)
98
+ end
99
+
100
+ def peek_path_from_obj(obj, path)
101
+ begin
102
+ # we'll fail if anything along the path can't be looked at without resolving
103
+ path_next = path.remainder
104
+ v = obj.attempt_peek_with_partial_resolve(path.first)
105
+
106
+ if path_next.nil?
107
+ v
108
+ else
109
+ if v.is_a?(Hocon::Impl::AbstractConfigObject)
110
+ peek_path_from_obj(v, path_next)
111
+ else
112
+ nil
113
+ end
114
+ end
115
+ rescue ConfigNotResolvedError => e
116
+ raise Hocon::Impl::ConfigImpl.improve_not_resolved(path, e)
117
+ end
118
+ end
119
+
30
120
  def value_type
31
121
  Hocon::ConfigValueType::OBJECT
32
122
  end
33
123
 
124
+ def new_copy_with_status(status, origin)
125
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `new_copy_with_status`"
126
+ end
127
+
34
128
  def new_copy(origin)
35
129
  new_copy_with_status(resolve_status, origin)
36
130
  end
37
131
 
38
- def merge_origins(stack)
132
+ def construct_delayed_merge(origin, stack)
133
+ Hocon::Impl::ConfigDelayedMergeObject.new(origin, stack)
134
+ end
135
+
136
+ def merged_with_object(fallback)
137
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `merged_with_object`"
138
+ end
139
+
140
+ def with_fallback(mergeable)
141
+ super(mergeable)
142
+ end
143
+
144
+ def self.merge_origins(stack)
39
145
  if stack.empty?
40
- raise ConfigBugError, "can't merge origins on empty list"
146
+ raise ConfigBugOrBrokenError, "can't merge origins on empty list"
41
147
  end
42
148
  origins = []
43
149
  first_origin = nil
@@ -67,47 +173,47 @@ class Hocon::Impl::AbstractConfigObject < Hocon::Impl::AbstractConfigValue
67
173
  Hocon::Impl::SimpleConfigOrigin.merge_origins(origins)
68
174
  end
69
175
 
70
- def peek_assuming_resolved(key, original_path)
71
- begin
72
- attempt_peek_with_partial_resolve(key)
73
- rescue ConfigNotResolvedError => e
74
- raise Hocon::Impl::ConfigImpl.improve_not_resolved(original_path, e)
75
- end
176
+ def resolve_substitutions(context, source)
177
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `resolve_substituions`"
76
178
  end
77
179
 
78
- def peek_path(path)
79
- begin
80
- return peek_path_impl(self, path, nil)
81
- rescue ConfigNotResolvedError => e
82
- raise ConfigBugOrBrokenError.new("NotPossibleToResolve happened though we had no ResolveContext in peek_path", nil)
83
- end
180
+ def relativized(path)
181
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `relativized`"
84
182
  end
85
183
 
86
- def peek_path_impl(me, path, context)
87
- begin
88
- if not context.nil?
89
- partially_resolved = context.restrict(path).resolve(me)
90
- if partially_resolved.is_a?(self.class)
91
- return peek_path_impl(partially_resolved, path, nil)
92
- else
93
- raise ConfigBugOrBrokenError.new(
94
- "resolved object to non-object #{me} to #{partially_resolved}", nil)
95
- end
96
- else
97
- remainder = path.remainder
98
- v = me.attempt_peek_with_partial_resolve(path.first)
99
- if remainder.nil?
100
- return v
101
- else
102
- if v.is_a?(self.class)
103
- return peek_path_impl(v, remainder, nil)
104
- else
105
- return nil
106
- end
107
- end
108
- end
109
- rescue ConfigNotResolvedError => e
110
- raise Hocon::Impl::ConfigImpl.improve_not_resolved(path, e)
111
- end
184
+ def [](key)
185
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `[]`"
186
+ end
187
+
188
+ def render_value_to_sb(sb, indent, at_root, options)
189
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `render_value_to_sb`"
190
+ end
191
+
192
+ def we_are_immutable(method)
193
+ Hocon::Impl::UnsupportedOperationError.new("ConfigObject is immutable, you can't call Map.#{method}")
194
+ end
195
+
196
+ def clear
197
+ raise we_are_immutable("clear")
198
+ end
199
+
200
+ def []=(key, value)
201
+ raise we_are_immutable("[]=")
202
+ end
203
+
204
+ def putAll(map)
205
+ raise we_are_immutable("putAll")
206
+ end
207
+
208
+ def remove(key)
209
+ raise we_are_immutable("remove")
210
+ end
211
+
212
+ def delete(key)
213
+ raise we_are_immutable("delete")
214
+ end
215
+
216
+ def with_origin(origin)
217
+ super(origin)
112
218
  end
113
- end
219
+ end
@@ -1,19 +1,27 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
  require 'stringio'
3
5
  require 'hocon/config_render_options'
4
6
  require 'hocon/config_object'
5
7
  require 'hocon/impl/resolve_status'
8
+ require 'hocon/impl/resolve_result'
6
9
  require 'hocon/impl/unmergeable'
7
- require 'hocon/impl/abstract_config_object'
8
10
  require 'hocon/impl/config_impl_util'
11
+ require 'hocon/config_error'
12
+ require 'hocon/config_value'
9
13
 
10
14
  ##
11
15
  ## Trying very hard to avoid a parent reference in config values; when you have
12
16
  ## a tree like this, the availability of parent() tends to result in a lot of
13
17
  ## improperly-factored and non-modular code. Please don't add parent().
14
18
  ##
15
- class Hocon::Impl::AbstractConfigValue
19
+ module Hocon::Impl::AbstractConfigValue
20
+ include Hocon::ConfigValue
21
+
16
22
  ConfigImplUtil = Hocon::Impl::ConfigImplUtil
23
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
24
+ ResolveStatus = Hocon::Impl::ResolveStatus
17
25
 
18
26
  def initialize(origin)
19
27
  @origin = origin
@@ -21,10 +29,110 @@ class Hocon::Impl::AbstractConfigValue
21
29
 
22
30
  attr_reader :origin
23
31
 
32
+ # This exception means that a value is inherently not resolveable, at the
33
+ # moment the only known cause is a cycle of substitutions. This is a
34
+ # checked exception since it's internal to the library and we want to be
35
+ # sure we handle it before passing it out to public API. This is only
36
+ # supposed to be thrown by the target of a cyclic reference and it's
37
+ # supposed to be caught by the ConfigReference looking up that reference,
38
+ # so it should be impossible for an outermost resolve() to throw this.
39
+ #
40
+ # Contrast with ConfigException.NotResolved which just means nobody called
41
+ # resolve().
42
+ class NotPossibleToResolve < Exception
43
+ def initialize(context)
44
+ super("was not possible to resolve")
45
+ @trace_string = context.trace_string
46
+ end
47
+
48
+ attr_reader :trace_string
49
+ end
50
+
51
+ # Called only by ResolveContext.resolve
52
+ #
53
+ # @param context
54
+ # state of the current resolve
55
+ # @param source
56
+ # where to look up values
57
+ # @return a new value if there were changes, or this if no changes
58
+ def resolve_substitutions(context, source)
59
+ Hocon::Impl::ResolveResult.make(context, self)
60
+ end
61
+
24
62
  def resolve_status
25
63
  Hocon::Impl::ResolveStatus::RESOLVED
26
64
  end
27
65
 
66
+ def self.replace_child_in_list(list, child, replacement)
67
+ i = 0
68
+ while (i < list.size) && (! list[i].equal?(child))
69
+ i += 1
70
+ end
71
+ if (i == list.size)
72
+ raise ConfigBugOrBrokenError, "tried to replace #{child} which is not in #{list}"
73
+ end
74
+
75
+ new_stack = list.clone
76
+ if ! replacement.nil?
77
+ new_stack[i] = replacement
78
+ else
79
+ new_stack.delete(i)
80
+ end
81
+
82
+ if new_stack.empty?
83
+ nil
84
+ else
85
+ new_stack
86
+ end
87
+ end
88
+
89
+ def self.has_descendant_in_list?(list, descendant)
90
+ list.each do |v|
91
+ if v.equal?(descendant)
92
+ return true
93
+ end
94
+ end
95
+ # now the expensive traversal
96
+ list.each do |v|
97
+ if v.is_a?(Hocon::Impl::Container) && v.has_descendant?(descendant)
98
+ return true
99
+ end
100
+ end
101
+ false
102
+ end
103
+
104
+ # This is used when including one file in another; the included file is
105
+ # relativized to the path it's included into in the parent file. The point
106
+ # is that if you include a file at foo.bar in the parent, and the included
107
+ # file as a substitution ${a.b.c}, the included substitution now needs to
108
+ # be ${foo.bar.a.b.c} because we resolve substitutions globally only after
109
+ # parsing everything.
110
+ #
111
+ # @param prefix
112
+ # @return value relativized to the given path or the same value if nothing
113
+ # to do
114
+ def relativized(prefix)
115
+ self
116
+ end
117
+
118
+ module NoExceptionsModifier
119
+ def modify_child_may_throw(key_or_nil, v)
120
+ begin
121
+ modify_child(key_or_nil, v)
122
+ rescue Hocon::ConfigError => e
123
+ raise e
124
+ end
125
+ end
126
+ end
127
+
128
+ def to_fallback_value
129
+ self
130
+ end
131
+
132
+ def new_copy(origin)
133
+ raise ConfigBugOrBrokenError, "subclasses of AbstractConfigValue should provide their own implementation of `new_copy` (#{self.class})"
134
+ end
135
+
28
136
  # this is virtualized rather than a field because only some subclasses
29
137
  # really need to store the boolean, and they may be able to pack it
30
138
  # with another boolean to save space.
@@ -34,16 +142,97 @@ class Hocon::Impl::AbstractConfigValue
34
142
  resolve_status == Hocon::Impl::ResolveStatus::RESOLVED
35
143
  end
36
144
 
145
+ def with_fallbacks_ignored
146
+ if ignores_fallbacks?
147
+ self
148
+ else
149
+ raise ConfigBugOrBrokenError, "value class doesn't implement forced fallback-ignoring #{self}"
150
+ end
151
+ end
152
+
37
153
  # the withFallback() implementation is supposed to avoid calling
38
154
  # mergedWith* if we're ignoring fallbacks.
39
155
  def require_not_ignoring_fallbacks
40
156
  if ignores_fallbacks?
41
- raise ConfigBugError, "method should not have been called with ignoresFallbacks=true #{self.class.name}"
157
+ raise ConfigBugOrBrokenError, "method should not have been called with ignoresFallbacks=true #{self.class.name}"
42
158
  end
43
159
  end
44
160
 
161
+ def construct_delayed_merge(origin, stack)
162
+ # TODO: this might not work because ConfigDelayedMerge inherits
163
+ # from this class, so we can't `require` it from this file
164
+ require 'hocon/impl/config_delayed_merge'
165
+ Hocon::Impl::ConfigDelayedMerge.new(origin, stack)
166
+ end
167
+
168
+ def merged_stack_with_the_unmergeable(stack, fallback)
169
+ require_not_ignoring_fallbacks
170
+
171
+ # if we turn out to be an object, and the fallback also does,
172
+ # then a merge may be required; delay until we resolve.
173
+ new_stack = stack.clone
174
+ new_stack.concat(fallback.unmerged_values)
175
+ # TODO: this might not work because AbstractConfigObject inherits
176
+ # from this class, so we can't `require` it from this file
177
+ construct_delayed_merge(Hocon::Impl::AbstractConfigObject.merge_origins(new_stack), new_stack)
178
+ end
179
+
180
+ def delay_merge(stack, fallback)
181
+ # if we turn out to be an object, and the fallback also does,
182
+ # then a merge may be required.
183
+ # if we contain a substitution, resolving it may need to look
184
+ # back to the fallback
185
+ new_stack = stack.clone
186
+ new_stack << fallback
187
+ # TODO: this might not work because AbstractConfigObject inherits
188
+ # from this class, so we can't `require` it from this file
189
+ construct_delayed_merge(Hocon::Impl::AbstractConfigObject.merge_origins(new_stack), new_stack)
190
+ end
191
+
192
+ def merged_stack_with_object(stack, fallback)
193
+ require_not_ignoring_fallbacks
194
+
195
+ # TODO: this might not work because AbstractConfigObject inherits
196
+ # from this class, so we can't `require` it from this file
197
+ if self.is_a?(Hocon::Impl::AbstractConfigObject)
198
+ raise ConfigBugOrBrokenError, "Objects must reimplement merged_with_object"
199
+ end
200
+
201
+ merged_stack_with_non_object(stack, fallback)
202
+ end
203
+
204
+ def merged_stack_with_non_object(stack, fallback)
205
+ require_not_ignoring_fallbacks
206
+
207
+ if resolve_status == ResolveStatus::RESOLVED
208
+ # falling back to a non-object doesn't merge anything, and also
209
+ # prohibits merging any objects that we fall back to later.
210
+ # so we have to switch to ignoresFallbacks mode.
211
+ with_fallbacks_ignored
212
+ else
213
+ # if unresolved we may have to look back to fallbacks as part of
214
+ # the resolution process, so always delay
215
+ delay_merge(stack, fallback)
216
+ end
217
+ end
218
+
219
+ def merged_with_the_unmergeable(fallback)
220
+ require_not_ignoring_fallbacks
221
+ merged_stack_with_the_unmergeable([self], fallback)
222
+ end
223
+
224
+ def merged_with_object(fallback)
225
+ require_not_ignoring_fallbacks
226
+ merged_stack_with_object([self], fallback)
227
+ end
228
+
229
+ def merged_with_non_object(fallback)
230
+ require_not_ignoring_fallbacks
231
+ merged_stack_with_non_object([self], fallback)
232
+ end
233
+
45
234
  def with_origin(origin)
46
- if @origin == origin
235
+ if @origin.equal?(origin)
47
236
  self
48
237
  else
49
238
  new_copy(origin)
@@ -57,6 +246,8 @@ class Hocon::Impl::AbstractConfigValue
57
246
  other = mergeable.to_fallback_value
58
247
  if other.is_a?(Hocon::Impl::Unmergeable)
59
248
  merged_with_the_unmergeable(other)
249
+ # TODO: this probably isn't going to work because AbstractConfigObject inherits
250
+ # from this class, so we can't `require` it from this file
60
251
  elsif other.is_a?(Hocon::Impl::AbstractConfigObject)
61
252
  merged_with_object(other)
62
253
  else
@@ -65,13 +256,42 @@ class Hocon::Impl::AbstractConfigValue
65
256
  end
66
257
  end
67
258
 
259
+ def can_equal(other)
260
+ other.is_a?(Hocon::Impl::AbstractConfigValue)
261
+ end
262
+
263
+ def ==(other)
264
+ # note that "origin" is deliberately NOT part of equality
265
+ if other.is_a?(Hocon::Impl::AbstractConfigValue)
266
+ can_equal(other) &&
267
+ value_type == other.value_type &&
268
+ ConfigImplUtil.equals_handling_nil?(unwrapped, other.unwrapped)
269
+ else
270
+ false
271
+ end
272
+ end
273
+
274
+ def hash
275
+ # note that "origin" is deliberately NOT part of equality
276
+ unwrapped_value = unwrapped
277
+ if unwrapped_value.nil?
278
+ 0
279
+ else
280
+ unwrapped_value.hash
281
+ end
282
+ end
283
+
68
284
  def to_s
69
285
  sb = StringIO.new
70
286
  render_to_sb(sb, 0, true, nil, Hocon::ConfigRenderOptions.concise)
71
- "#{self.class.name}(#{sb.string})"
287
+ "#{self.class.name.split('::').last}(#{sb.string})"
72
288
  end
73
289
 
74
- def indent(sb, indent_size, options)
290
+ def inspect
291
+ to_s
292
+ end
293
+
294
+ def self.indent(sb, indent_size, options)
75
295
  if options.formatted?
76
296
  remaining = indent_size
77
297
  while remaining > 0
@@ -127,20 +347,40 @@ class Hocon::Impl::AbstractConfigValue
127
347
  sb.string[0, sb.pos]
128
348
  end
129
349
 
130
- def at_key(origin, key)
350
+ # toString() is a debugging-oriented string but this is defined
351
+ # to create a string that would parse back to the value in JSON.
352
+ # It only works for primitive values (that would be a single
353
+ # which are auto-converted to strings when concatenating with
354
+ # other strings or by the DefaultTransformer.
355
+ def transform_to_string
356
+ nil
357
+ end
358
+
359
+ def at_key(key)
360
+ at_key_with_origin(Hocon::Impl::SimpleConfigOrigin.new_simple("at_key(#{key})"), key)
361
+ end
362
+
363
+ # Renamed this to be consistent with the other at_key* overloaded methods
364
+ def at_key_with_origin(origin, key)
131
365
  m = {key=>self}
132
366
  Hocon::Impl::SimpleConfigObject.new(origin, m).to_config
133
367
  end
134
368
 
135
- def at_path(origin, path)
369
+ # In java this is an overloaded version of atPath
370
+ def at_path_with_origin(origin, path)
136
371
  parent = path.parent
137
- result = at_key(origin, path.last)
372
+ result = at_key_with_origin(origin, path.last)
138
373
  while not parent.nil? do
139
374
  key = parent.last
140
- result = result.at_key(origin, key)
375
+ result = result.at_key_with_origin(origin, key)
141
376
  parent = parent.parent
142
377
  end
143
378
  result
144
379
  end
145
380
 
146
- end
381
+ def at_path(path_expression)
382
+ origin = Hocon::Impl::SimpleConfigOrigin.new_simple("at_path(#{path_expression})")
383
+ at_path_with_origin(origin, Hocon::Impl::Path.new_path(path_expression))
384
+ end
385
+
386
+ end