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