hocon 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,