hocon 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +8 -0
- data/lib/hocon.rb +10 -2
- data/lib/hocon/config_error.rb +31 -9
- data/lib/hocon/config_factory.rb +7 -9
- data/lib/hocon/config_object.rb +4 -4
- data/lib/hocon/config_parse_options.rb +43 -43
- data/lib/hocon/config_render_options.rb +41 -41
- data/lib/hocon/config_syntax.rb +7 -7
- data/lib/hocon/config_value_factory.rb +10 -0
- data/lib/hocon/config_value_type.rb +23 -23
- data/lib/hocon/impl.rb +5 -4
- data/lib/hocon/impl/abstract_config_object.rb +49 -0
- data/lib/hocon/impl/abstract_config_value.rb +16 -0
- data/lib/hocon/impl/config_boolean.rb +25 -0
- data/lib/hocon/impl/config_float.rb +4 -0
- data/lib/hocon/impl/config_impl.rb +125 -0
- data/lib/hocon/impl/config_impl_util.rb +8 -0
- data/lib/hocon/impl/config_null.rb +25 -0
- data/lib/hocon/impl/default_transformer.rb +97 -0
- data/lib/hocon/impl/from_map_mode.rb +16 -0
- data/lib/hocon/impl/parser.rb +185 -92
- data/lib/hocon/impl/path.rb +77 -0
- data/lib/hocon/impl/path_builder.rb +1 -2
- data/lib/hocon/impl/properties_parser.rb +83 -0
- data/lib/hocon/impl/simple_config.rb +76 -0
- data/lib/hocon/impl/simple_config_list.rb +1 -0
- data/lib/hocon/impl/simple_config_object.rb +67 -0
- data/lib/hocon/impl/tokenizer.rb +8 -0
- data/lib/hocon/impl/tokens.rb +7 -1
- metadata +59 -15
- checksums.yaml +0 -7
@@ -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
|
@@ -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
|
data/lib/hocon/impl/parser.rb
CHANGED
@@ -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
|
-
|
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,
|