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,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