hocon 0.0.4 → 0.0.5

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.
@@ -127,4 +127,20 @@ class Hocon::Impl::AbstractConfigValue
127
127
  sb.string[0, sb.pos]
128
128
  end
129
129
 
130
+ def at_key(origin, key)
131
+ m = {key=>self}
132
+ Hocon::Impl::SimpleConfigObject.new(origin, m).to_config
133
+ end
134
+
135
+ def at_path(origin, path)
136
+ parent = path.parent
137
+ result = at_key(origin, path.last)
138
+ while not parent.nil? do
139
+ key = parent.last
140
+ result = result.at_key(origin, key)
141
+ parent = parent.parent
142
+ end
143
+ result
144
+ end
145
+
130
146
  end
@@ -0,0 +1,25 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/impl/abstract_config_value'
3
+
4
+ class Hocon::Impl::ConfigBoolean < Hocon::Impl::AbstractConfigValue
5
+ def initialize(origin, value)
6
+ super(origin)
7
+ @value = value
8
+ end
9
+
10
+ def value_type
11
+ Hocon::ConfigValueType::BOOLEAN
12
+ end
13
+
14
+ def unwrapped
15
+ @value
16
+ end
17
+
18
+ def transform_to_string
19
+ @value.to_s
20
+ end
21
+
22
+ def new_copy(origin)
23
+ Hocon::Impl::ConfigBoolean.new(origin, @value)
24
+ end
25
+ end
@@ -6,4 +6,8 @@ class Hocon::Impl::ConfigFloat < Hocon::Impl::ConfigNumber
6
6
  super(origin, original_text)
7
7
  @value = value
8
8
  end
9
+
10
+ def unwrapped
11
+ @value
12
+ end
9
13
  end
@@ -1,10 +1,135 @@
1
1
  require 'hocon/impl'
2
2
  require 'hocon/impl/simple_includer'
3
+ require 'hocon/config_error'
4
+ require 'hocon/impl/from_map_mode'
5
+ require 'hocon/impl/simple_config_origin'
6
+ require 'hocon/impl/simple_config_list'
7
+ require 'hocon/impl/config_boolean'
8
+ require 'hocon/impl/config_null'
3
9
 
4
10
  class Hocon::Impl::ConfigImpl
5
11
  @default_includer = Hocon::Impl::SimpleIncluder.new
12
+ @default_value_origin = Hocon::Impl::SimpleConfigOrigin.new_simple("hardcoded value")
13
+ @default_true_value = Hocon::Impl::ConfigBoolean.new(@default_value_origin, true)
14
+ @default_false_value = Hocon::Impl::ConfigBoolean.new(@default_value_origin, false)
15
+ @default_null_value = Hocon::Impl::ConfigNull.new(@default_value_origin)
16
+ @default_empty_list = Hocon::Impl::SimpleConfigList.new(@default_value_origin, Array.new)
17
+
18
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
19
+ ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError
20
+ FromMapMode = Hocon::Impl::FromMapMode
6
21
 
7
22
  def self.default_includer
8
23
  @default_includer
9
24
  end
25
+
26
+ def self.improve_not_resolved(what, original)
27
+ new_message = "#{what.render} has not been resolved, you need to call Config#resolve, see API docs for Config#resolve"
28
+ if new_message == original.get_message
29
+ return original
30
+ else
31
+ return ConfigNotResolvedError.new(new_message, original)
32
+ end
33
+ end
34
+
35
+ def self.value_origin(origin_description)
36
+ if origin_description.nil?
37
+ return @default_value_origin
38
+ else
39
+ return Hocon::Impl::SimpleConfigOrigin.new_simple(origin_description)
40
+ end
41
+ end
42
+
43
+ def self.empty_object(origin)
44
+ # we want null origin to go to SimpleConfigObject.empty() to get the
45
+ # origin "empty config" rather than "hardcoded value"
46
+ if origin == @default_value_origin
47
+ return default_empty_object
48
+ else
49
+ return Hocon::Impl::SimpleConfigObject.empty(origin)
50
+ end
51
+ end
52
+
53
+ def self.empty_list(origin)
54
+ if origin.nil? || origin == @default_value_origin
55
+ return @default_empty_list
56
+ else
57
+ return Hocon::Impl::SimpleConfigList.new(origin, Array.new)
58
+ end
59
+ end
60
+
61
+ def self.from_any_ref(object, origin_description)
62
+ origin = self.value_origin(origin_description)
63
+ from_any_ref_mode(object, origin, FromMapMode::KEYS_ARE_KEYS)
64
+ end
65
+
66
+ def self.from_any_ref_mode(object, origin, map_mode)
67
+ if origin.nil?
68
+ raise ConfigBugOrBrokenError.new("origin not supposed to be nil", nil)
69
+ end
70
+ if object.nil?
71
+ if origin != @default_value_origin
72
+ return Hocon::Impl::ConfigNull.new(origin)
73
+ else
74
+ return @default_null_value
75
+ end
76
+ elsif object.is_a?(TrueClass) || object.is_a?(FalseClass)
77
+ if origin != @default_value_origin
78
+ return Hocon::Impl::ConfigBoolean.new(origin, object)
79
+ elsif object
80
+ return @default_true_value
81
+ else
82
+ return @default_false_value
83
+ end
84
+ elsif object.is_a?(String)
85
+ return Hocon::Impl::ConfigString.new(origin, object)
86
+ elsif object.is_a?(Numeric)
87
+ # here we always keep the same type that was passed to us,
88
+ # rather than figuring out if a Long would fit in an Int
89
+ # or a Double has no fractional part. i.e. deliberately
90
+ # not using ConfigNumber.newNumber() when we have a
91
+ # Double, Integer, or Long.
92
+ if object.is_a?(Float)
93
+ return Hocon::Impl::ConfigFloat.new(origin, object, nil)
94
+ elsif object.is_a?(Integer)
95
+ return Hocon::Impl::ConfigInt.new(origin, object, nil)
96
+ else
97
+ return Hocon::Impl::ConfigNumber.new_number(origin, Float(object), nil)
98
+ end
99
+ elsif object.is_a?(Hash)
100
+ if object.empty?
101
+ return self.empty_object(origin)
102
+ end
103
+
104
+ if map_mode == FromMapMode::KEYS_ARE_KEYS
105
+ values = Hash.new
106
+ object.each do |key, entry|
107
+ if not key.is_a?(String)
108
+ raise ConfigBugOrBrokenError.new(
109
+ "bug in method caller: not valid to create ConfigObject from map with non-String key: #{key}",
110
+ nil)
111
+ end
112
+ value = self.from_any_ref_mode(entry, origin, map_mode)
113
+ values[key] = value
114
+ end
115
+ return Hocon::Impl::SimpleConfigObject.new(origin, values)
116
+ else
117
+ return Hocon::Impl::PropertiesParser.from_path_map(origin, object)
118
+ end
119
+ elsif object.is_a?(Enumerable)
120
+ if object.count == 0
121
+ return self.empty_list(origin)
122
+ end
123
+
124
+ values = Array.new
125
+ object.each do |item|
126
+ v = from_any_ref_mode(item, origin, map_mode)
127
+ values.push(v)
128
+ end
129
+
130
+ return Hocon::Impl::SimpleConfigList.new(origin, values)
131
+ else
132
+ raise ConfigBugOrBrokenError.new("bug in method caller: not valid to create ConfigValue from: #{object}", nil)
133
+ end
134
+ end
10
135
  end
@@ -75,4 +75,12 @@ class Hocon::Impl::ConfigImplUtil
75
75
  # file).
76
76
  c =~ /[[:space:]]/
77
77
  end
78
+
79
+ def self.unicode_trim(s)
80
+ # this implementation is *not* a port of the java code. Ruby can strip
81
+ # unicode whitespace much easier than Java can, and relies on a lot of
82
+ # Java functions that don't really have straight equivalents in Ruby.
83
+ s.gsub(/[[:space]]/, ' ')
84
+ s.strip
85
+ end
78
86
  end
@@ -0,0 +1,25 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/config_value_type'
3
+
4
+ class Hocon::Impl::ConfigNull < Hocon::Impl::AbstractConfigValue
5
+ def value_type
6
+ Hocon::Impl::ConfigValueType::NULL
7
+ end
8
+
9
+ def unwrapped
10
+ nil
11
+ end
12
+
13
+ def transform_to_string
14
+ "null"
15
+ end
16
+
17
+ def render(sb, indent, atRoot, options)
18
+ sb.append("null")
19
+ end
20
+
21
+ def newCopy(origin)
22
+ ConfigNull.new(origin)
23
+ end
24
+
25
+ end
@@ -0,0 +1,97 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/config_value_type'
3
+
4
+ class Hocon::Impl::DefaultTransformer
5
+
6
+ ConfigValueType = Hocon::ConfigValueType
7
+
8
+ def self.transform(value, requested)
9
+ if value.value == ConfigValueType::STRING
10
+ s = value.unwrapped
11
+ case requested
12
+ when NUMBER
13
+ begin
14
+ v = Integer(s)
15
+ return ConfigInt.new(value.origin, v, s)
16
+ rescue ArgumentError
17
+ # try Float
18
+ end
19
+ begin
20
+ v = Float(s)
21
+ return ConfigFloat.new(value.origin, v, s)
22
+ rescue ArgumentError
23
+ # oh well.
24
+ end
25
+ when NULL
26
+ if s == "null"
27
+ return ConfigNull.new(value.origin)
28
+ end
29
+ when BOOLEAN
30
+ if s == "true" || s == "yes" || s == "on"
31
+ return ConfigBoolean.new(value.origin, true)
32
+ elsif s == "false" || s == "no" || s == "off"
33
+ return ConfigBoolean.new(value.origin, false)
34
+ end
35
+ when LIST
36
+ # can't go STRING to LIST automatically
37
+ when OBJECT
38
+ # can't go STRING to OBJECT automatically
39
+ when STRING
40
+ # no-op STRING to STRING
41
+ end
42
+ elsif requested == ConfigValueType::STRING
43
+ # if we converted null to string here, then you wouldn't properly
44
+ # get a missing-value error if you tried to get a null value
45
+ # as a string.
46
+ 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
51
+ # want to be sure this throws instead of returning "null" as a
52
+ # string
53
+ when OBJECT
54
+ # no OBJECT to STRING automatically
55
+ when LIST
56
+ # no LIST to STRING automatically
57
+ when STRING
58
+ # no-op STRING to STRING
59
+ end
60
+ elsif requested == ConfigValueType::LIST && value.value_type == ConfigValueType::OBJECT
61
+ # attempt to convert an array-like (numeric indices) object to a
62
+ # list. This would be used with .properties syntax for example:
63
+ # -Dfoo.0=bar -Dfoo.1=baz
64
+ # To ensure we still throw type errors for objects treated
65
+ # as lists in most cases, we'll refuse to convert if the object
66
+ # does not contain any numeric keys. This means we don't allow
67
+ # empty objects here though :-/
68
+ o = value
69
+ values = Hash.new
70
+ values
71
+ o.keys.each do |key|
72
+ begin
73
+ i = Integer(key, 10)
74
+ if i < 0
75
+ next
76
+ end
77
+ values[key] = i
78
+ rescue ArgumentError
79
+ next
80
+ end
81
+ end
82
+ if not values.empty?
83
+ entry_list = values.to_a
84
+ # sort by numeric index
85
+ entry_list.sort! {|a,b| b[0] <=> a[0]}
86
+ # drop the indices (we allow gaps in the indices, for better or
87
+ # worse)
88
+ list = Array.new
89
+ entry_list.each do |entry|
90
+ list.push(entry[1])
91
+ end
92
+ return SimpleConfigList.new(value.origin, list)
93
+ end
94
+ end
95
+ value
96
+ end
97
+ end
@@ -0,0 +1,16 @@
1
+ require 'hocon/impl'
2
+ require 'hocon/config_error'
3
+
4
+ module Hocon::Impl::FromMapMode
5
+ KEYS_ARE_PATHS = 0
6
+ KEYS_ARE_KEYS = 1
7
+ ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError
8
+
9
+ def self.name(from_map_mode)
10
+ case from_map_mode
11
+ when KEYS_ARE_PATHS then "KEYS_ARE_PATHS"
12
+ when KEYS_ARE_KEYS then "KEYS_ARE_KEYS"
13
+ else raise ConfigBugOrBrokenError.new("Unrecognized FromMapMode #{from_map_mode}", nil)
14
+ end
15
+ end
16
+ end
@@ -9,6 +9,9 @@ require 'hocon/impl/config_concatenation'
9
9
  require 'hocon/config_error'
10
10
  require 'hocon/impl/simple_config_list'
11
11
  require 'hocon/impl/simple_config_object'
12
+ require 'hocon/impl/config_impl_util'
13
+ require 'hocon/impl/tokenizer'
14
+ require 'hocon/impl/simple_config_origin'
12
15
 
13
16
  class Hocon::Impl::Parser
14
17
 
@@ -19,6 +22,10 @@ class Hocon::Impl::Parser
19
22
  ConfigParseError = Hocon::ConfigError::ConfigParseError
20
23
  SimpleConfigObject = Hocon::Impl::SimpleConfigObject
21
24
  SimpleConfigList = Hocon::Impl::SimpleConfigList
25
+ SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin
26
+ ConfigImplUtil = Hocon::Impl::ConfigImplUtil
27
+ PathBuilder = Hocon::Impl::PathBuilder
28
+ Tokenizer = Hocon::Impl::Tokenizer
22
29
 
23
30
  class TokenWithComments
24
31
  def initialize(token, comments = [])
@@ -49,6 +56,17 @@ class Hocon::Impl::Parser
49
56
  end
50
57
  end
51
58
 
59
+ def add(after)
60
+ if @comments.empty?
61
+ TokenWithComments.new(@token, [after])
62
+ else
63
+ merged = Array.new
64
+ merged += @comments
65
+ merged.push(after)
66
+ TokenWithComments.new(@token, merged)
67
+ end
68
+ end
69
+
52
70
  def prepend_comments(origin)
53
71
  if @comments.empty?
54
72
  origin
@@ -115,97 +133,6 @@ class Hocon::Impl::Parser
115
133
  (Tokens.unquoted_text(token) == "include")
116
134
  end
117
135
 
118
- def self.add_path_text(buf, was_quoted, new_text)
119
- i = if was_quoted
120
- -1
121
- else
122
- new_text.index('.') || -1
123
- end
124
- current = buf.last
125
- if i < 0
126
- # add to current path element
127
- current.sb << new_text
128
- # any empty quoted string means this element can
129
- # now be empty.
130
- if was_quoted && (current.sb.length == 0)
131
- current.can_be_empty = true
132
- end
133
- else
134
- # "buf" plus up to the period is an element
135
- current.sb << new_text[0, i]
136
- # then start a new element
137
- buf.push(Element.new("", false))
138
- # recurse to consume remainder of new_text
139
- add_path_text(buf, false, new_text[i + 1, new_text.length - 1])
140
- end
141
- end
142
-
143
- def self.parse_path_expression(expression, origin, original_text = nil)
144
- buf = []
145
- buf.push(Element.new("", false))
146
-
147
- if expression.empty?
148
- raise ConfigBadPathError.new(
149
- origin,
150
- original_text,
151
- "Expecting a field name or path here, but got nothing")
152
- end
153
-
154
- expression.each do |t|
155
- if Tokens.value_with_type?(t, ConfigValueType::STRING)
156
- v = Tokens.value(t)
157
- # this is a quoted string; so any periods
158
- # in here don't count as path separators
159
- s = v.transform_to_string
160
- add_path_text(buf, true, s)
161
- elsif t == Tokens::EOF
162
- # ignore this; when parsing a file, it should not happen
163
- # since we're parsing a token list rather than the main
164
- # token iterator, and when parsing a path expression from the
165
- # API, it's expected to have an EOF.
166
- else
167
- # any periods outside of a quoted string count as
168
- # separators
169
- text = nil
170
- if Tokens.value?(t)
171
- # appending a number here may add
172
- # a period, but we _do_ count those as path
173
- # separators, because we basically want
174
- # "foo 3.0bar" to parse as a string even
175
- # though there's a number in it. The fact that
176
- # we tokenize non-string values is largely an
177
- # implementation detail.
178
- v = Tokens.value(t)
179
- text = v.transform_to_string
180
- elsif Tokens.unquoted_text?(t)
181
- text = Tokens.unquoted_text(t)
182
- else
183
- raise ConfigBadPathError.new(
184
- origin,
185
- original_text,
186
- "Token not allowed in path expression: #{t}" +
187
- " (you can double-quote this token if you really want it here)")
188
- end
189
-
190
- add_path_text(buf, false, text)
191
- end
192
- end
193
-
194
- pb = Hocon::Impl::PathBuilder.new
195
- buf.each do |e|
196
- if (e.sb.length == 0) && !e.can_be_empty?
197
- raise ConfigBadPathError.new(
198
- origin,
199
- original_text,
200
- "path has a leading, trailing, or two adjacent period '.' (use quoted \"\" empty string if you want an empty element)")
201
- else
202
- pb.append_key(e.sb.string)
203
- end
204
- end
205
-
206
- pb.result
207
- end
208
-
209
136
  def initialize(flavor, origin, tokens, includer, include_context)
210
137
  @line_number = 1
211
138
  @flavor = flavor
@@ -410,7 +337,7 @@ class Hocon::Impl::Parser
410
337
  end
411
338
 
412
339
  put_back(t)
413
- self.class.parse_path_expression(expression, line_origin)
340
+ Hocon::Impl::Parser.parse_path_expression(expression, line_origin)
414
341
  end
415
342
  end
416
343
 
@@ -872,6 +799,172 @@ class Hocon::Impl::Parser
872
799
 
873
800
  end
874
801
 
802
+ class Element
803
+ def initialize(initial, can_be_empty)
804
+ @can_be_empty = can_be_empty
805
+ @sb = StringIO.new(initial)
806
+ end
807
+
808
+ def to_string
809
+ "Element(#{@sb.string},#{@can_be_empty})"
810
+ end
811
+
812
+ def sb
813
+ @sb
814
+ end
815
+ end
816
+
817
+ def self.has_unsafe_chars(s)
818
+ for i in 0...s.length
819
+ c = s[i]
820
+ if (c =~ /[[:alpha:]]/) || c == '.'
821
+ next
822
+ else
823
+ return true
824
+ end
825
+ end
826
+ false
827
+ end
828
+
829
+ def self.append_path_string(pb, s)
830
+ split_at = s.index('.')
831
+ if split_at.nil?
832
+ pb.append_key(s)
833
+ else
834
+ pb.append_key(s[0...split_at])
835
+ append_path_string(pb, s[(split_at + 1)...s.length])
836
+ end
837
+ end
838
+
839
+ def self.speculative_fast_parse_path(path)
840
+ s = ConfigImplUtil.unicode_trim(path)
841
+ if s.empty?
842
+ return nil
843
+ end
844
+ if has_unsafe_chars(s)
845
+ return nil
846
+ end
847
+ if s.start_with?(".") || s.end_with?(".") || s.include?("..")
848
+ return nil
849
+ end
850
+
851
+ pb = PathBuilder.new
852
+ append_path_string(pb, s)
853
+ pb.result
854
+ end
855
+
856
+ def self.api_origin
857
+ SimpleConfigOrigin.new_simple("path parameter")
858
+ end
859
+
860
+ def self.parse_path(path)
861
+ speculated = speculative_fast_parse_path(path)
862
+ if not speculated.nil?
863
+ return speculated
864
+ end
865
+
866
+ reader = StringIO.new(path)
867
+
868
+ begin
869
+ tokens = Tokenizer.tokenize(api_origin, reader, ConfigSyntax::CONF)
870
+ tokens.next # drop START
871
+ return parse_path_expression(tokens, api_origin, path)
872
+ ensure
873
+ reader.close
874
+ end
875
+ end
876
+
877
+ def self.add_path_text(buf, was_quoted, new_text)
878
+ i = if was_quoted
879
+ -1
880
+ else
881
+ new_text.index('.') || -1
882
+ end
883
+ current = buf.last
884
+ if i < 0
885
+ # add to current path element
886
+ current.sb << new_text
887
+ # any empty quoted string means this element can
888
+ # now be empty.
889
+ if was_quoted && (current.sb.length == 0)
890
+ current.can_be_empty = true
891
+ end
892
+ else
893
+ # "buf" plus up to the period is an element
894
+ current.sb << new_text[0, i]
895
+ # then start a new element
896
+ buf.push(Element.new("", false))
897
+ # recurse to consume remainder of new_text
898
+ add_path_text(buf, false, new_text[i + 1, new_text.length - 1])
899
+ end
900
+ end
901
+
902
+ def self.parse_path_expression(expression, origin, original_text = nil)
903
+ buf = []
904
+ buf.push(Element.new("", false))
905
+
906
+ if expression.empty?
907
+ raise ConfigBadPathError.new(
908
+ origin,
909
+ original_text,
910
+ "Expecting a field name or path here, but got nothing")
911
+ end
912
+
913
+ expression.each do |t|
914
+ if Tokens.value_with_type?(t, ConfigValueType::STRING)
915
+ v = Tokens.value(t)
916
+ # this is a quoted string; so any periods
917
+ # in here don't count as path separators
918
+ s = v.transform_to_string
919
+ add_path_text(buf, true, s)
920
+ elsif t == Tokens::EOF
921
+ # ignore this; when parsing a file, it should not happen
922
+ # since we're parsing a token list rather than the main
923
+ # token iterator, and when parsing a path expression from the
924
+ # API, it's expected to have an EOF.
925
+ else
926
+ # any periods outside of a quoted string count as
927
+ # separators
928
+ text = nil
929
+ if Tokens.value?(t)
930
+ # appending a number here may add
931
+ # a period, but we _do_ count those as path
932
+ # separators, because we basically want
933
+ # "foo 3.0bar" to parse as a string even
934
+ # though there's a number in it. The fact that
935
+ # we tokenize non-string values is largely an
936
+ # implementation detail.
937
+ v = Tokens.value(t)
938
+ text = v.transform_to_string
939
+ elsif Tokens.unquoted_text?(t)
940
+ text = Tokens.unquoted_text(t)
941
+ else
942
+ raise ConfigBadPathError.new(
943
+ origin,
944
+ original_text,
945
+ "Token not allowed in path expression: #{t}" +
946
+ " (you can double-quote this token if you really want it here)")
947
+ end
948
+
949
+ add_path_text(buf, false, text)
950
+ end
951
+ end
952
+
953
+ pb = Hocon::Impl::PathBuilder.new
954
+ buf.each do |e|
955
+ if (e.sb.length == 0) && !e.can_be_empty?
956
+ raise ConfigBadPathError.new(
957
+ origin,
958
+ original_text,
959
+ "path has a leading, trailing, or two adjacent period '.' (use quoted \"\" empty string if you want an empty element)")
960
+ else
961
+ pb.append_key(e.sb.string)
962
+ end
963
+ end
964
+
965
+ pb.result
966
+ end
967
+
875
968
  def self.parse(tokens, origin, options, include_context)
876
969
  context = Hocon::Impl::Parser::ParseContext.new(
877
970
  options.syntax, origin, tokens,