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
@@ -0,0 +1,142 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'hocon/impl'
5
+ require 'hocon/impl/abstract_config_value'
6
+ require 'hocon/impl/resolve_source'
7
+ require 'hocon/impl/resolve_result'
8
+
9
+ class Hocon::Impl::ConfigReference
10
+ include Hocon::Impl::Unmergeable
11
+ include Hocon::Impl::AbstractConfigValue
12
+
13
+ NotPossibleToResolve = Hocon::Impl::AbstractConfigValue::NotPossibleToResolve
14
+ UnresolvedSubstitutionError = Hocon::ConfigError::UnresolvedSubstitutionError
15
+
16
+ attr_reader :expr, :prefix_length
17
+
18
+ def initialize(origin, expr, prefix_length = 0)
19
+ super(origin)
20
+ @expr = expr
21
+ @prefix_length = prefix_length
22
+ end
23
+
24
+ def unmerged_values
25
+ [self]
26
+ end
27
+
28
+ # ConfigReference should be a firewall against NotPossibleToResolve going
29
+ # further up the stack; it should convert everything to ConfigException.
30
+ # This way it 's impossible for NotPossibleToResolve to "escape" since
31
+ # any failure to resolve has to start with a ConfigReference.
32
+ def resolve_substitutions(context, source)
33
+ new_context = context.add_cycle_marker(self)
34
+ begin
35
+ result_with_path = source.lookup_subst(new_context, @expr, @prefix_length)
36
+ new_context = result_with_path.result.context
37
+
38
+ if result_with_path.result.value != nil
39
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
40
+ Hocon::Impl::ConfigImpl.trace(
41
+ "recursively resolving #{result_with_path} which was the resolution of #{expr} against #{source}",
42
+ context.depth)
43
+ end
44
+
45
+ recursive_resolve_source = Hocon::Impl::ResolveSource.new(
46
+ result_with_path.path_from_root.last, result_with_path.path_from_root)
47
+
48
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
49
+ Hocon::Impl::ConfigImpl.trace("will recursively resolve against #{recursive_resolve_source}", context.depth)
50
+ end
51
+
52
+ result = new_context.resolve(result_with_path.result.value,
53
+ recursive_resolve_source)
54
+ v = result.value
55
+ new_context = result.context
56
+ else
57
+ v = nil
58
+ end
59
+ rescue NotPossibleToResolve => e
60
+ if Hocon::Impl::ConfigImpl.trace_substitution_enabled
61
+ Hocon::Impl::ConfigImpl.trace(
62
+ "not possible to resolve #{expr}, cycle involved: #{e.trace_string}", new_context.depth)
63
+ end
64
+ if @expr.optional
65
+ v = nil
66
+ else
67
+ raise UnresolvedSubstitutionError.new(
68
+ origin,
69
+ "#{@expr} was part of a cycle of substitutions involving #{e.trace_string}", e)
70
+ end
71
+ end
72
+
73
+ if v == nil && !@expr.optional
74
+ if new_context.options.allow_unresolved
75
+ ResolveResult.make(new_context.remove_cycle_marker(self), self)
76
+ else
77
+ raise UnresolvedSubstitutionError.new(origin, @expr.to_s)
78
+ end
79
+ else
80
+ Hocon::Impl::ResolveResult.make(new_context.remove_cycle_marker(self), v)
81
+ end
82
+
83
+ end
84
+
85
+ def value_type
86
+ raise not_resolved
87
+ end
88
+
89
+ def unwrapped
90
+ raise not_resolved
91
+ end
92
+
93
+ def new_copy(new_origin)
94
+ Hocon::Impl::ConfigReference.new(new_origin, @expr, @prefix_length)
95
+ end
96
+
97
+ def ignores_fallbacks?
98
+ false
99
+ end
100
+
101
+ def resolve_status
102
+ Hocon::Impl::ResolveStatus::UNRESOLVED
103
+ end
104
+
105
+ def relativized(prefix)
106
+ new_expr = @expr.change_path(@expr.path.prepend(prefix))
107
+
108
+ Hocon::Impl::ConfigReference.new(origin, new_expr, @prefix_length + prefix.length)
109
+ end
110
+
111
+ def can_equal(other)
112
+ other.is_a? Hocon::Impl::ConfigReference
113
+ end
114
+
115
+ def ==(other)
116
+ # note that "origin" is deliberately NOT part of equality
117
+ if other.is_a? Hocon::Impl::ConfigReference
118
+ can_equal(other) && @expr == other.expr
119
+ end
120
+ end
121
+
122
+ def hash
123
+ # note that "origin" is deliberately NOT part of equality
124
+ @expr.hash
125
+ end
126
+
127
+ def render_value_to_sb(sb, indent, at_root, options)
128
+ sb << @expr.to_s
129
+ end
130
+
131
+ def expression
132
+ @expr
133
+ end
134
+
135
+ private
136
+
137
+ def not_resolved
138
+ error_message = "need to Config#resolve, see the API docs for Config#resolve; substitution not resolved: #{self}"
139
+ Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil)
140
+ end
141
+
142
+ end
@@ -1,14 +1,59 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
  require 'hocon/impl/abstract_config_value'
3
5
  require 'hocon/config_value_type'
4
6
  require 'hocon/impl/config_impl_util'
5
7
 
6
- class Hocon::Impl::ConfigString < Hocon::Impl::AbstractConfigValue
8
+ class Hocon::Impl::ConfigString
9
+ include Hocon::Impl::AbstractConfigValue
10
+
7
11
  ConfigImplUtil = Hocon::Impl::ConfigImplUtil
8
12
 
9
- def initialize(origin, value)
10
- super(origin)
11
- @value = value
13
+ attr_reader :value
14
+
15
+ class Quoted < Hocon::Impl::ConfigString
16
+ def initialize(origin, value)
17
+ super(origin, value)
18
+ end
19
+
20
+ def new_copy(origin)
21
+ self.class.new(origin, @value)
22
+ end
23
+
24
+ private
25
+
26
+ # serialization all goes through SerializedConfigValue
27
+ def write_replace
28
+ Hocon::Impl::SerializedConfigValue.new(self)
29
+ end
30
+ end
31
+
32
+ # this is sort of a hack; we want to preserve whether whitespace
33
+ # was quoted until we process substitutions, so we can ignore
34
+ # unquoted whitespace when concatenating lists or objects.
35
+ # We dump this distinction when serializing and deserializing,
36
+ # but that 's OK because it isn' t in equals/hashCode, and we
37
+ # don 't allow serializing unresolved objects which is where
38
+ # quoted-ness matters. If we later make ConfigOrigin point
39
+ # to the original token range, we could use that to implement
40
+ # wasQuoted()
41
+ class Unquoted < Hocon::Impl::ConfigString
42
+ def initialize(origin, value)
43
+ super(origin, value)
44
+ end
45
+
46
+ def new_copy(origin)
47
+ self.class.new(origin, @value)
48
+ end
49
+
50
+ def write_replace
51
+ Hocon::Impl::SerializedConfigValue.new(self)
52
+ end
53
+ end
54
+
55
+ def was_quoted?
56
+ self.is_a?(Quoted)
12
57
  end
13
58
 
14
59
  def value_type
@@ -31,7 +76,10 @@ class Hocon::Impl::ConfigString < Hocon::Impl::AbstractConfigValue
31
76
  end
32
77
  end
33
78
 
34
- def new_copy(origin)
35
- Hocon::Impl::ConfigString.new(origin, @value)
79
+ private
80
+
81
+ def initialize(origin, value)
82
+ super(origin)
83
+ @value = value
36
84
  end
37
- end
85
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/config_value'
5
+ require 'hocon/config_error'
6
+
7
+ # An AbstractConfigValue which contains other values. Java has no way to
8
+ # express "this has to be an AbstractConfigValue also" other than making
9
+ # AbstractConfigValue an interface which would be aggravating. But we can say
10
+ # we are a ConfigValue.
11
+ module Hocon::Impl::Container
12
+ include Hocon::ConfigValue
13
+ #
14
+ # Replace a child of this value. CAUTION if replacement is null, delete the
15
+ # child, which may also delete the parent, or make the parent into a
16
+ # non-container.
17
+ #
18
+ def replace_child(child, replacement)
19
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Container` must implement `replace_child` (#{self.class})"
20
+ end
21
+
22
+ #
23
+ # Super-expensive full traversal to see if descendant is anywhere
24
+ # underneath this container.
25
+ #
26
+ def has_descendant?(descendant)
27
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Container` must implement `has_descendant?` (#{self.class})"
28
+ end
29
+ end
@@ -1,15 +1,21 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
4
+ require 'hocon/impl/config_string'
2
5
  require 'hocon/config_value_type'
6
+ require 'hocon/impl/config_boolean'
3
7
 
4
8
  class Hocon::Impl::DefaultTransformer
5
9
 
6
10
  ConfigValueType = Hocon::ConfigValueType
11
+ ConfigString = Hocon::Impl::ConfigString
12
+ ConfigBoolean = Hocon::Impl::ConfigBoolean
7
13
 
8
14
  def self.transform(value, requested)
9
- if value.value == ConfigValueType::STRING
15
+ if value.value_type == ConfigValueType::STRING
10
16
  s = value.unwrapped
11
17
  case requested
12
- when NUMBER
18
+ when ConfigValueType::NUMBER
13
19
  begin
14
20
  v = Integer(s)
15
21
  return ConfigInt.new(value.origin, v, s)
@@ -22,21 +28,21 @@ class Hocon::Impl::DefaultTransformer
22
28
  rescue ArgumentError
23
29
  # oh well.
24
30
  end
25
- when NULL
31
+ when ConfigValueType::NULL
26
32
  if s == "null"
27
33
  return ConfigNull.new(value.origin)
28
34
  end
29
- when BOOLEAN
35
+ when ConfigValueType::BOOLEAN
30
36
  if s == "true" || s == "yes" || s == "on"
31
37
  return ConfigBoolean.new(value.origin, true)
32
38
  elsif s == "false" || s == "no" || s == "off"
33
39
  return ConfigBoolean.new(value.origin, false)
34
40
  end
35
- when LIST
41
+ when ConfigValueType::LIST
36
42
  # can't go STRING to LIST automatically
37
- when OBJECT
43
+ when ConfigValueType::OBJECT
38
44
  # can't go STRING to OBJECT automatically
39
- when STRING
45
+ when ConfigValueType::STRING
40
46
  # no-op STRING to STRING
41
47
  end
42
48
  elsif requested == ConfigValueType::STRING
@@ -44,17 +50,20 @@ class Hocon::Impl::DefaultTransformer
44
50
  # get a missing-value error if you tried to get a null value
45
51
  # as a string.
46
52
  case value.value_type
47
- # NUMBER case removed since you can't fallthrough in a ruby case statement
48
- when BOOLEAN
49
- return ConfigString.new(value.origin, value.transform_to_string)
50
- when NULL
53
+ # Ruby note: can't fall through in ruby. In the java code, NUMBER
54
+ # just rolls over to the BOOLEAN case
55
+ when ConfigValueType::NUMBER
56
+ return ConfigString::Quoted.new(value.origin, value.transform_to_string)
57
+ when ConfigValueType::BOOLEAN
58
+ return ConfigString::Quoted.new(value.origin, value.transform_to_string)
59
+ when ConfigValueType::NULL
51
60
  # want to be sure this throws instead of returning "null" as a
52
61
  # string
53
- when OBJECT
62
+ when ConfigValueType::OBJECT
54
63
  # no OBJECT to STRING automatically
55
- when LIST
64
+ when ConfigValueType::LIST
56
65
  # no LIST to STRING automatically
57
- when STRING
66
+ when ConfigValueType::STRING
58
67
  # no-op STRING to STRING
59
68
  end
60
69
  elsif requested == ConfigValueType::LIST && value.value_type == ConfigValueType::OBJECT
@@ -94,4 +103,4 @@ class Hocon::Impl::DefaultTransformer
94
103
  end
95
104
  value
96
105
  end
97
- end
106
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
  require 'hocon/config_error'
3
5
 
@@ -10,7 +12,7 @@ module Hocon::Impl::FromMapMode
10
12
  case from_map_mode
11
13
  when KEYS_ARE_PATHS then "KEYS_ARE_PATHS"
12
14
  when KEYS_ARE_KEYS then "KEYS_ARE_KEYS"
13
- else raise ConfigBugOrBrokenError.new("Unrecognized FromMapMode #{from_map_mode}", nil)
15
+ else raise ConfigBugOrBrokenError.new("Unrecognized FromMapMode #{from_map_mode}")
14
16
  end
15
17
  end
16
18
  end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
 
3
5
  class Hocon::Impl::FullIncluder
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon'
4
+ require 'hocon/impl'
5
+
6
+ class Hocon::Impl::MemoKey
7
+
8
+ def initialize(value, restrict_to_child_or_nil)
9
+ @value = value
10
+ @restrict_to_child_or_nil = restrict_to_child_or_nil
11
+ end
12
+
13
+ def hash
14
+ h = @value.hash
15
+ if @restrict_to_child_or_nil != nil
16
+ h + 41 * (41 + @restrict_to_child_or_nil.hash)
17
+ else
18
+ h
19
+ end
20
+ end
21
+
22
+ def ==(other)
23
+ if other.is_a?(self.class)
24
+ o = other
25
+ if !o.value.equal?(@value)
26
+ return false
27
+ elsif o.restrict_to_child_or_nil.equals(@restrict_to_child_or_nil)
28
+ return true
29
+ elsif o.restrict_to_child_or_nil == nil || @restrict_to_child_or_nil == nil
30
+ return false
31
+ else
32
+ return o.restrict_to_child_or_nil == @restrict_to_child_or_nil
33
+ end
34
+ else
35
+ false
36
+ end
37
+ end
38
+
39
+ def to_s
40
+ "MemoKey(#{@value}@#{@value.hash},#{@restrict_to_child_or_nil})"
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ require 'hocon/impl'
4
+ require 'hocon/config_mergeable'
5
+
6
+ class Hocon::Impl::MergeableValue < Hocon::ConfigMergeable
7
+ # TODO
8
+ end
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'hocon/impl'
2
4
 
3
5
  module Hocon::Impl::OriginType
@@ -5,5 +7,9 @@ module Hocon::Impl::OriginType
5
7
  GENERIC = 0
6
8
  FILE = 1
7
9
  #URL = 2
8
- #RESOURCE = 3
9
- end
10
+ # We don't actually support loading from the classpath / loadpath, which is
11
+ # what 'RESOURCE' is about in the upstream library. However, some code paths
12
+ # still flow through our simplistic implementation of `ParseableResource`, so
13
+ # we need this constant.
14
+ RESOURCE = 3
15
+ end
@@ -1,4 +1,7 @@
1
+ # encoding: utf-8
2
+
1
3
  require 'stringio'
4
+ require 'pathname'
2
5
  require 'hocon/impl'
3
6
  require 'hocon/config_error'
4
7
  require 'hocon/config_syntax'
@@ -7,78 +10,108 @@ require 'hocon/impl/simple_include_context'
7
10
  require 'hocon/impl/simple_config_object'
8
11
  require 'hocon/impl/simple_config_origin'
9
12
  require 'hocon/impl/tokenizer'
10
- require 'hocon/impl/parser'
13
+ require 'hocon/impl/config_parser'
14
+ require 'hocon/config_parseable'
15
+ require 'hocon/impl/config_document_parser'
16
+ require 'hocon/impl/simple_config_document'
11
17
 
18
+ #
19
+ # Internal implementation detail, not ABI stable, do not touch.
20
+ # For use only by the {@link com.typesafe.config} package.
21
+ # The point of this class is to avoid "propagating" each
22
+ # overload on "thing which can be parsed" through multiple
23
+ # interfaces. Most interfaces can have just one overload that
24
+ # takes a Parseable. Also it's used as an abstract "resource
25
+ # handle" in the ConfigIncluder interface.
26
+ #
12
27
  class Hocon::Impl::Parseable
13
- class ParseableFile < Hocon::Impl::Parseable
14
- def initialize(file_path, options)
15
- @input = file_path
16
- post_construct(options)
17
- end
28
+ include Hocon::ConfigParseable
18
29
 
19
- def guess_syntax
20
- Hocon::Impl::Parseable.syntax_from_extension(File.basename(@input))
30
+ # Internal implementation detail, not ABI stable, do not touch
31
+ module Relativizer
32
+ def relative_to(filename)
33
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Relativizer` must implement `relative_to` (#{self.class})"
21
34
  end
35
+ end
22
36
 
23
- def create_origin
24
- Hocon::Impl::SimpleConfigOrigin.new_file(@input)
25
- end
37
+ # Changed this to a class variable because the upstream library seems to use it
38
+ # as a global way of keeping track of how many files have been included, to
39
+ # avoid cycles
40
+ @@parse_stack= []
26
41
 
27
- def reader
28
- self
29
- end
42
+ MAX_INCLUDE_DEPTH = 50
43
+
44
+ def initialize
30
45
 
31
- def open
32
- if block_given?
33
- File.open(@input) do |f|
34
- yield f
35
- end
36
- else
37
- File.open(@input)
38
- end
39
- end
40
46
  end
41
47
 
42
- class ParseableString < Hocon::Impl::Parseable
43
- def initialize(string, options)
44
- @input = string
45
- post_construct(options)
48
+ def fixup_options(base_options)
49
+ syntax = base_options.syntax
50
+ if !syntax
51
+ syntax = guess_syntax
46
52
  end
47
-
48
- def create_origin
49
- Hocon::Impl::SimpleConfigOrigin.new_simple("String")
53
+ if !syntax
54
+ syntax = Hocon::ConfigSyntax::CONF
50
55
  end
56
+ modified = base_options.set_syntax(syntax)
51
57
 
52
- def reader
53
- self
54
- end
58
+ # make sure the app-provided includer falls back to default
59
+ modified = modified.append_includer(Hocon::Impl::ConfigImpl.default_includer)
60
+ # make sure the app-provided includer is complete
61
+ modified = modified.set_includer(Hocon::Impl::SimpleIncluder.make_full(modified.includer))
55
62
 
56
- def open
57
- if block_given?
58
- StringIO.open(@input) do |f|
59
- yield f
60
- end
61
- else
62
- StringIO.open(@input)
63
- end
63
+ modified
64
+ end
65
+
66
+ def post_construct(base_options)
67
+ @initial_options = fixup_options(base_options)
68
+ @include_context = Hocon::Impl::SimpleIncludeContext.new(self)
69
+ if @initial_options.origin_description
70
+ @initial_origin = Hocon::Impl::SimpleConfigOrigin.new_simple(@initial_options.origin_description)
71
+ else
72
+ @initial_origin = create_origin
64
73
  end
74
+ end
65
75
 
76
+ # the general idea is that any work should be in here, not in the
77
+ # constructor, so that exceptions are thrown from the public parse()
78
+ # function and not from the creation of the Parseable.
79
+ # Essentially this is a lazy field. The parser should close the
80
+ # reader when it's done with it.
81
+ #{//}# ALSO, IMPORTANT: if the file or URL is not found, this must throw.
82
+ #{//}# to support the "allow missing" feature.
83
+ def custom_reader
84
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Parseable` must implement `custom_reader` (#{self.class})"
66
85
  end
67
86
 
68
- def self.new_file(file_path, options)
69
- ParseableFile.new(file_path, options)
87
+ def reader(options)
88
+ custom_reader
70
89
  end
71
90
 
72
- def self.new_string(string, options)
73
- ParseableString.new(string, options)
91
+ def self.trace(message)
92
+ if Hocon::Impl::ConfigImpl.trace_loads_enabled
93
+ Hocon::Impl::ConfigImpl.trace(message)
94
+ end
74
95
  end
75
96
 
76
97
  def guess_syntax
77
98
  nil
78
99
  end
79
100
 
80
- def options
81
- @initial_options
101
+ def content_type
102
+ nil
103
+ end
104
+
105
+ def relative_to(filename)
106
+ # fall back to classpath; we treat the "filename" as absolute
107
+ # (don't add a package name in front),
108
+ # if it starts with "/" then remove the "/", for consistency
109
+ # with ParseableResources.relativeTo
110
+ resource = filename
111
+ if filename.start_with?("/")
112
+ resource = filename.slice(1)
113
+ end
114
+ self.class.new_resources(resource, options.set_origin_description(nil))
82
115
  end
83
116
 
84
117
  def include_context
@@ -89,17 +122,40 @@ class Hocon::Impl::Parseable
89
122
  if value.is_a? Hocon::Impl::AbstractConfigObject
90
123
  value
91
124
  else
92
- raise Hocon::ConfigError::ConfigWrongTypeError.new(value.origin, "",
125
+ raise Hocon::ConfigError::ConfigWrongTypeError.with_expected_actual(value.origin,
126
+ "",
93
127
  "object at file root",
94
128
  value.value_type.name)
95
129
  end
96
130
  end
97
131
 
98
- def parse
99
- self.class.force_parsed_to_object(parse_value(options))
132
+ def parse(base_options = nil)
133
+ if (base_options.nil?)
134
+ base_options = options
135
+ end
136
+ stack = @@parse_stack
137
+ if stack.length >= MAX_INCLUDE_DEPTH
138
+ raise Hocon::ConfigError::ConfigParseError.new(@initial_origin,
139
+ "include statements nested more than #{MAX_INCLUDE_DEPTH} times, " +
140
+ "you probably have a cycle in your includes. Trace: #{stack}",
141
+ nil)
142
+ end
143
+
144
+ # Push into beginning of stack
145
+ stack.unshift(self)
146
+ begin
147
+ self.class.force_parsed_to_object(parse_value(base_options))
148
+ ensure
149
+ # Pop from beginning of stack
150
+ stack.shift
151
+ end
100
152
  end
101
153
 
102
- def parse_value(base_options)
154
+ def parse_value(base_options = nil)
155
+ if base_options.nil?
156
+ base_options = options
157
+ end
158
+
103
159
  # note that we are NOT using our "initialOptions",
104
160
  # but using the ones from the passed-in options. The idea is that
105
161
  # callers can get our original options and then parse with different
@@ -116,55 +172,49 @@ class Hocon::Impl::Parseable
116
172
  parse_value_from_origin(origin, options)
117
173
  end
118
174
 
119
-
120
- private
121
-
122
- def self.syntax_from_extension(filename)
123
- case File.extname(filename)
124
- when ".json"
125
- Hocon::ConfigSyntax::JSON
126
- when ".conf"
127
- Hocon::ConfigSyntax::CONF
128
- when ".properties"
129
- Hocon::ConfigSyntax::PROPERTIES
175
+ def parse_value_from_origin(origin, final_options)
176
+ begin
177
+ raw_parse_value(origin, final_options)
178
+ rescue IOError => e
179
+ if final_options.allow_missing?
180
+ Hocon::Impl::SimpleConfigObject.empty_missing(origin)
130
181
  else
131
- nil
132
- end
133
- end
134
-
135
- def post_construct(base_options)
136
- @initial_options = fixup_options(base_options)
137
- @include_context = Hocon::Impl::SimpleIncludeContext.new(self)
138
- if @initial_options.origin_description
139
- @initial_origin = SimpleConfigOrigin.new_simple(@initial_options.origin_description)
140
- else
141
- @initial_origin = create_origin
182
+ self.class.trace("exception loading #{origin.description}: #{e.class}: #{e.message}")
183
+ raise Hocon::ConfigError::ConfigIOError.new(origin, "#{e.class.name}: #{e.message}", e)
184
+ end
142
185
  end
143
186
  end
144
187
 
145
- def fixup_options(base_options)
146
- syntax = base_options.syntax
147
- if !syntax
148
- syntax = guess_syntax
149
- end
150
- if !syntax
151
- syntax = Hocon::ConfigSyntax::CONF
188
+ def parse_document(base_options = nil)
189
+ if base_options.nil?
190
+ base_options = options
152
191
  end
153
192
 
154
- modified = base_options.with_syntax(syntax)
155
- modified = modified.append_includer(Hocon::Impl::ConfigImpl.default_includer)
156
- modified = modified.with_includer(Hocon::Impl::SimpleIncluder.make_full(modified.includer))
193
+ # note that we are NOT using our "initialOptions",
194
+ # but using the ones from the passed-in options. The idea is that
195
+ # callers can get our original options and then parse with different
196
+ # ones if they want.
197
+ options = fixup_options(base_options)
157
198
 
158
- modified
199
+ # passed-in option can override origin
200
+ origin = nil
201
+ if ! options.origin_description.nil?
202
+ origin = Hocon::Impl::SimpleConfigOrigin.new_simple(options.origin_description)
203
+ else
204
+ origin = @initial_origin
205
+ end
206
+ parse_document_from_origin(origin, options)
159
207
  end
160
208
 
161
- def parse_value_from_origin(origin, final_options)
209
+ def parse_document_from_origin(origin, final_options)
162
210
  begin
163
- raw_parse_value(origin, final_options)
211
+ raw_parse_document(origin, final_options)
164
212
  rescue IOError => e
165
213
  if final_options.allow_missing?
166
- Hocon::Impl::SimpleConfigObject.empty_missing(origin)
214
+ Hocon::Impl::SimpleConfigDocument.new(
215
+ Hocon::Impl::ConfigNodeObject.new([]), final_options)
167
216
  else
217
+ self.class.trace("exception loading #{origin.description}: #{e.class}: #{e.message}")
168
218
  raise ConfigIOError.new(origin, "#{e.class.name}: #{e.message}", e)
169
219
  end
170
220
  end
@@ -173,16 +223,330 @@ class Hocon::Impl::Parseable
173
223
  # this is parseValue without post-processing the IOException or handling
174
224
  # options.getAllowMissing()
175
225
  def raw_parse_value(origin, final_options)
176
- ## TODO: if we were going to support loading from URLs, this
177
- ## method would need to deal with the content-type.
226
+ reader = reader(final_options)
227
+
228
+ # after reader() we will have loaded the Content-Type
229
+ content_type = content_type()
230
+
231
+ options_with_content_type = nil
232
+ if !(content_type.nil?)
233
+ if Hocon::Impl::ConfigImpl.trace_loads_enabled && (! final_options.get_syntax.nil?)
234
+ self.class.trace("Overriding syntax #{final_options.get_syntax} with Content-Type which specified #{content-type}")
235
+ end
236
+ options_with_content_type = final_options.set_syntax(content_type)
237
+ else
238
+ options_with_content_type = final_options
239
+ end
178
240
 
179
241
  reader.open { |io|
180
- raw_parse_value_from_io(io, origin, final_options)
242
+ raw_parse_value_from_io(io, origin, options_with_content_type)
181
243
  }
182
244
  end
183
245
 
184
246
  def raw_parse_value_from_io(io, origin, final_options)
185
247
  tokens = Hocon::Impl::Tokenizer.tokenize(origin, io, final_options.syntax)
186
- Hocon::Impl::Parser.parse(tokens, origin, final_options, include_context)
248
+ document = Hocon::Impl::ConfigDocumentParser.parse(tokens, origin, final_options)
249
+ Hocon::Impl::ConfigParser.parse(document, origin, final_options, include_context)
250
+ end
251
+
252
+ def raw_parse_document(origin, final_options)
253
+ reader = reader(final_options)
254
+ content_type = content_type()
255
+
256
+ options_with_content_type = nil
257
+ if !(content_type.nil?)
258
+ if Hocon::Impl::ConfigImpl.trace_loads_enabled && (! final_options.get_syntax.nil?)
259
+ self.class.trace("Overriding syntax #{final_options.get_syntax} with Content-Type which specified #{content-type}")
260
+ end
261
+ options_with_content_type = final_options.set_syntax(content_type)
262
+ else
263
+ options_with_content_type = final_options
264
+ end
265
+
266
+ reader.open { |io|
267
+ raw_parse_document_from_io(io, origin, options_with_content_type)
268
+ }
269
+ end
270
+
271
+ def raw_parse_document_from_io(reader, origin, final_options)
272
+ tokens = Hocon::Impl::Tokenizer.tokenize(origin, reader, final_options.syntax)
273
+ Hocon::Impl::SimpleConfigDocument.new(
274
+ Hocon::Impl::ConfigDocumentParser.parse(tokens, origin, final_options),
275
+ final_options)
276
+ end
277
+
278
+ def parse_config_document
279
+ parse_document(options)
280
+ end
281
+
282
+ def origin
283
+ @initial_origin
284
+ end
285
+
286
+ def create_origin
287
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Parseable` must implement `create_origin` (#{self.class})"
288
+ end
289
+
290
+ def options
291
+ @initial_options
292
+ end
293
+
294
+ def to_s
295
+ self.class.name.split('::').last
296
+ end
297
+
298
+ def self.syntax_from_extension(name)
299
+ if name.end_with?(".json")
300
+ Hocon::ConfigSyntax::JSON
301
+ elsif name.end_with?(".conf")
302
+ Hocon::ConfigSyntax::CONF
303
+ else
304
+ # Skipping PROPERTIES because we can't really support that in ruby
305
+ nil
306
+ end
307
+ end
308
+
309
+ # NOTE: skipping `readerFromStream` and `doNotClose` because they don't seem relevant in Ruby
310
+
311
+ # NOTE: skipping `relativeTo(URL, String)` because we're not supporting URLs for now
312
+ def self.relative_to(file, filename)
313
+ child = Pathname.new(filename)
314
+ file = Pathname.new(file)
315
+
316
+ if child.absolute?
317
+ nil
318
+ end
319
+
320
+ parent = file.parent
321
+
322
+ if parent.nil?
323
+ nil
324
+ else
325
+ File.join(parent, filename)
326
+ end
187
327
  end
328
+
329
+ # this is a parseable that doesn't exist and just throws when you try to parse it
330
+ class ParseableNotFound < Hocon::Impl::Parseable
331
+ def initialize(what, message, options)
332
+ super()
333
+ @what = what
334
+ @message = message
335
+ post_construct(options)
336
+ end
337
+
338
+ def custom_reader
339
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, @message
340
+ end
341
+
342
+ def create_origin
343
+ Hocon::Impl::SimpleConfigOrigin.new_simple(@what)
344
+ end
345
+ end
346
+
347
+ def self.new_not_found(what_not_found, message, options)
348
+ ParseableNotFound.new(what_not_found, message, options)
349
+ end
350
+
351
+ # NOTE: skipping `ParseableReader` until we know we need it (probably should
352
+ # have done that with `ParseableNotFound`)
353
+
354
+ class ParseableString < Hocon::Impl::Parseable
355
+ def initialize(string, options)
356
+ super()
357
+ @input = string
358
+ post_construct(options)
359
+ end
360
+
361
+ def custom_reader
362
+ if Hocon::Impl::ConfigImpl.trace_loads_enabled
363
+ self.class.trace("Loading config from a String: #{@input}")
364
+ end
365
+ # we return self here, which will cause `open` to be called on us, so
366
+ # we can provide an implementation of that.
367
+ self
368
+ end
369
+
370
+ def open
371
+ if block_given?
372
+ StringIO.open(@input) do |f|
373
+ yield f
374
+ end
375
+ else
376
+ StringIO.open(@input)
377
+ end
378
+ end
379
+
380
+ def create_origin
381
+ Hocon::Impl::SimpleConfigOrigin.new_simple("String")
382
+ end
383
+
384
+ def to_s
385
+ "#{self.class.name.split('::').last} (#{@input})"
386
+ end
387
+ end
388
+
389
+ def self.new_string(string, options)
390
+ ParseableString.new(string, options)
391
+ end
392
+
393
+ # NOTE: Skipping `ParseableURL` for now as we probably won't support this right away
394
+
395
+ class ParseableFile < Hocon::Impl::Parseable
396
+ def initialize(input, options)
397
+ super()
398
+ @input = input
399
+ post_construct(options)
400
+ end
401
+
402
+ def custom_reader
403
+ if Hocon::Impl::ConfigImpl.trace_loads_enabled
404
+ self.class.trace("Loading config from a String: #{@input}")
405
+ end
406
+ # we return self here, which will cause `open` to be called on us, so
407
+ # we can provide an implementation of that.
408
+ self
409
+ end
410
+
411
+ def open
412
+ begin
413
+ if block_given?
414
+ File.open(@input) do |f|
415
+ yield f
416
+ end
417
+ else
418
+ File.open(@input)
419
+ end
420
+ rescue Errno::ENOENT
421
+ if @initial_options.allow_missing?
422
+ return Hocon::Impl::SimpleConfigObject.empty
423
+ end
424
+
425
+ raise Hocon::ConfigError::ConfigIOError.new(nil, "File not found. No file called #{@input}")
426
+ end
427
+ end
428
+
429
+ def guess_syntax
430
+ Hocon::Impl::Parseable.syntax_from_extension(File.basename(@input))
431
+ end
432
+
433
+ def relative_to(filename)
434
+ sibling = nil
435
+ if Pathname.new(filename).absolute?
436
+ sibling = File.new(filename)
437
+ else
438
+ # this may return nil
439
+ sibling = Hocon::Impl::Parseable.relative_to(@input, filename)
440
+ end
441
+ if sibling.nil?
442
+ nil
443
+ elsif File.exists?(sibling)
444
+ self.class.trace("#{sibling} exists, so loading it as a file")
445
+ Hocon::Impl::Parseable.new_file(sibling, options.set_origin_description(nil))
446
+ else
447
+ self.class.trace("#{sibling} does not exist, so trying it as a resource")
448
+ super(filename)
449
+ end
450
+ end
451
+
452
+ def create_origin
453
+ Hocon::Impl::SimpleConfigOrigin.new_file(@input)
454
+ end
455
+
456
+ def to_s
457
+ "#{self.class.name.split('::').last} (#{@input})"
458
+ end
459
+
460
+ end
461
+
462
+
463
+ def self.new_file(file_path, options)
464
+ ParseableFile.new(file_path, options)
465
+ end
466
+
467
+ # NOTE: skipping `ParseableResourceURL`, we probably won't support that
468
+
469
+ # NOTE: this is not a faithful port of the `ParseableResources` class from the
470
+ # upstream, because at least for now we're not going to try to do anything
471
+ # crazy like look for files on the ruby load path. However, there is a decent
472
+ # chunk of logic elsewhere in the codebase that is written with the assumption
473
+ # that this class will provide the 'last resort' attempt to find a config file
474
+ # before giving up, so we're basically port just enough to have it provide
475
+ # that last resort behavior
476
+ class ParseableResources < Hocon::Impl::Parseable
477
+ include Relativizer
478
+
479
+ def initialize(resource, options)
480
+ super()
481
+ @resource = resource
482
+ post_construct(options)
483
+ end
484
+
485
+ def reader
486
+ raise Hocon::ConfigError::ConfigBugOrBrokenError, "reader() should not be called on resources"
487
+ end
488
+
489
+ def raw_parse_value(origin, final_options)
490
+ # this is where the upstream code would go out and look for a file on the
491
+ # classpath. We're not going to do that, and instead we're just going to
492
+ # raise the same exception that the upstream code would raise if it failed
493
+ # to find the file.
494
+ raise IOError, "resource not found: #{@resource}"
495
+ end
496
+
497
+ def guess_syntax
498
+ Hocon::Impl::Parseable.syntax_from_extension(@resource)
499
+ end
500
+
501
+ def self.parent(resource)
502
+ # the "resource" is not supposed to begin with a "/"
503
+ # because it's supposed to be the raw resource
504
+ # (ClassLoader#getResource), not the
505
+ # resource "syntax" (Class#getResource)
506
+ i = resource.rindex("/")
507
+ if i < 0
508
+ nil
509
+ else
510
+ resource.slice(0..i)
511
+ end
512
+ end
513
+
514
+ def relative_to(sibling)
515
+ if sibling.start_with?("/")
516
+ # if it starts with "/" then don't make it relative to the
517
+ # including resource
518
+ Hocon::Impl::Parseable.new_resources(sibling.slice(1), options.set_origin_description(nil))
519
+ else
520
+ # here we want to build a new resource name and let
521
+ # the class loader have it, rather than getting the
522
+ # url with getResource() and relativizing to that url.
523
+ # This is needed in case the class loader is going to
524
+ # search a classpath.
525
+ parent = self.class.parent(@resource)
526
+ if parent.nil?
527
+ Hocon::Impl::Parseable.new_resources(sibling, options.set_origin_description(nil))
528
+ else
529
+ Hocon::Impl::Parseable.new_resources("#{parent}/sibling", options.set_origin_description(nil))
530
+ end
531
+ end
532
+ end
533
+
534
+ def create_origin
535
+ Hocon::Impl::SimpleConfigOrigin.new_resource(@resource)
536
+ end
537
+
538
+ def to_s
539
+ "#{self.class.name.split('::').last}(#{@resource})"
540
+ end
541
+ end
542
+
543
+ def self.new_resources(resource, options)
544
+ ParseableResources.new(resource, options)
545
+ end
546
+
547
+
548
+
549
+
550
+ # NOTE: skipping `ParseableProperties`, we probably won't support that
551
+
188
552
  end