typesafe_config 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/LICENSE +202 -0
  2. data/README.md +2 -0
  3. data/lib/typesafe/config/config_error.rb +12 -0
  4. data/lib/typesafe/config/config_factory.rb +9 -0
  5. data/lib/typesafe/config/config_object.rb +4 -0
  6. data/lib/typesafe/config/config_parse_options.rb +53 -0
  7. data/lib/typesafe/config/config_render_options.rb +46 -0
  8. data/lib/typesafe/config/config_syntax.rb +7 -0
  9. data/lib/typesafe/config/config_value_type.rb +26 -0
  10. data/lib/typesafe/config/impl/abstract_config_object.rb +64 -0
  11. data/lib/typesafe/config/impl/abstract_config_value.rb +130 -0
  12. data/lib/typesafe/config/impl/config_concatenation.rb +136 -0
  13. data/lib/typesafe/config/impl/config_float.rb +9 -0
  14. data/lib/typesafe/config/impl/config_impl.rb +10 -0
  15. data/lib/typesafe/config/impl/config_impl_util.rb +78 -0
  16. data/lib/typesafe/config/impl/config_int.rb +31 -0
  17. data/lib/typesafe/config/impl/config_number.rb +27 -0
  18. data/lib/typesafe/config/impl/config_string.rb +37 -0
  19. data/lib/typesafe/config/impl/full_includer.rb +4 -0
  20. data/lib/typesafe/config/impl/origin_type.rb +9 -0
  21. data/lib/typesafe/config/impl/parseable.rb +151 -0
  22. data/lib/typesafe/config/impl/parser.rb +882 -0
  23. data/lib/typesafe/config/impl/path.rb +59 -0
  24. data/lib/typesafe/config/impl/path_builder.rb +36 -0
  25. data/lib/typesafe/config/impl/resolve_status.rb +18 -0
  26. data/lib/typesafe/config/impl/simple_config.rb +11 -0
  27. data/lib/typesafe/config/impl/simple_config_list.rb +70 -0
  28. data/lib/typesafe/config/impl/simple_config_object.rb +178 -0
  29. data/lib/typesafe/config/impl/simple_config_origin.rb +174 -0
  30. data/lib/typesafe/config/impl/simple_include_context.rb +7 -0
  31. data/lib/typesafe/config/impl/simple_includer.rb +19 -0
  32. data/lib/typesafe/config/impl/token.rb +32 -0
  33. data/lib/typesafe/config/impl/token_type.rb +42 -0
  34. data/lib/typesafe/config/impl/tokenizer.rb +370 -0
  35. data/lib/typesafe/config/impl/tokens.rb +157 -0
  36. data/lib/typesafe/config/impl/unmergeable.rb +4 -0
  37. data/lib/typesafe/config/impl.rb +5 -0
  38. data/lib/typesafe/config.rb +4 -0
  39. data/lib/typesafe.rb +2 -0
  40. metadata +85 -0
@@ -0,0 +1,370 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/config_impl_util'
3
+ require 'typesafe/config/impl/tokens'
4
+ require 'stringio'
5
+
6
+ class Typesafe::Config::Impl::Tokenizer
7
+ Tokens = Typesafe::Config::Impl::Tokens
8
+
9
+ class TokenizerProblemError < StandardError
10
+ end
11
+
12
+ class TokenIterator
13
+ class WhitespaceSaver
14
+ def initialize
15
+ @whitespace = StringIO.new
16
+ @last_token_was_simple_value = false
17
+ end
18
+
19
+ def add(c)
20
+ if @last_token_was_simple_value
21
+ @whitespace << c
22
+ end
23
+ end
24
+
25
+ def check(t, base_origin, line_number)
26
+ if Typesafe::Config::Impl::Tokenizer::TokenIterator.simple_value?(t)
27
+ next_is_a_simple_value(base_origin, line_number)
28
+ else
29
+ next_is_not_a_simple_value
30
+ nil
31
+ end
32
+ end
33
+
34
+ private
35
+ # called if the next token is not a simple value;
36
+ # discards any whitespace we were saving between
37
+ # simple values.
38
+ def next_is_not_a_simple_value
39
+ @last_token_was_simple_value = false
40
+ @whitespace.reopen("")
41
+ end
42
+
43
+ # called if the next token IS a simple value,
44
+ # so creates a whitespace token if the previous
45
+ # token also was.
46
+ def next_is_a_simple_value(base_origin, line_number)
47
+ if @last_token_was_simple_value
48
+ # need to save whitespace between the two so
49
+ # the parser has the option to concatenate it.
50
+ if @whitespace.length > 0
51
+ Tokens.new_unquoted_text(
52
+ line_origin(base_origin, line_number),
53
+ @whitespace.string = ""
54
+ )
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ # chars JSON allows a number to start with
62
+ FIRST_NUMBER_CHARS = "0123456789-"
63
+ # chars JSON allows to be part of a number
64
+ NUMBER_CHARS = "0123456789eE+-."
65
+ # chars that stop an unquoted string
66
+ NOT_IN_UNQUOTED_TEXT = "$\"{}[]:=,+#`^?!@*&\\"
67
+
68
+ def self.simple_value?(t)
69
+ Tokens.substitution?(t) ||
70
+ Tokens.unquoted_text?(t) ||
71
+ Tokens.value?(t)
72
+ end
73
+
74
+ def self.whitespace?(c)
75
+ Typesafe::Config::Impl::ConfigImplUtil.whitespace?(c)
76
+ end
77
+
78
+ def self.whitespace_not_newline?(c)
79
+ (c != "\n") and (Typesafe::Config::Impl::ConfigImplUtil.whitespace?(c))
80
+ end
81
+
82
+ def initialize(origin, input, allow_comments)
83
+ @origin = origin
84
+ @input = input
85
+ @allow_comments = allow_comments
86
+ @buffer = []
87
+ @line_number = 1
88
+ @line_origin = @origin.set_line_number(@line_number)
89
+ @tokens = []
90
+ @tokens << Tokens::START
91
+ @whitespace_saver = WhitespaceSaver.new
92
+ end
93
+
94
+ def start_of_comment?(c)
95
+ if c == -1
96
+ false
97
+ else
98
+ if @allow_comments
99
+ if c == '#'
100
+ true
101
+ elsif c == '/'
102
+ maybe_second_slash = next_char_raw
103
+ # we want to predictably NOT consume any chars
104
+ put_back(maybe_second_slash)
105
+ if maybe_second_slash == '/'
106
+ true
107
+ else
108
+ false
109
+ end
110
+ end
111
+ else
112
+ false
113
+ end
114
+ end
115
+ end
116
+
117
+ def put_back(c)
118
+ if @buffer.length > 2
119
+ raise ConfigBugError, "bug: putBack() three times, undesirable look-ahead"
120
+ end
121
+ @buffer.push(c)
122
+ end
123
+
124
+ def next_char_raw
125
+ if @buffer.empty?
126
+ begin
127
+ @input.readchar
128
+ rescue EOFError
129
+ -1
130
+ end
131
+ else
132
+ @buffer.pop
133
+ end
134
+ end
135
+
136
+ def next_char_after_whitespace(saver)
137
+ while true
138
+ c = next_char_raw
139
+ if c == -1
140
+ return -1
141
+ else
142
+ if self.class.whitespace_not_newline?(c)
143
+ saver.add(c)
144
+ else
145
+ return c
146
+ end
147
+ end
148
+ end
149
+ end
150
+
151
+ # The rules here are intended to maximize convenience while
152
+ # avoiding confusion with real valid JSON. Basically anything
153
+ # that parses as JSON is treated the JSON way and otherwise
154
+ # we assume it's a string and let the parser sort it out.
155
+ def pull_unquoted_text
156
+ origin = @line_origin
157
+ io = StringIO.new
158
+ c = next_char_raw
159
+ while true
160
+ if (c == -1) or
161
+ (NOT_IN_UNQUOTED_TEXT.index(c)) or
162
+ (self.class.whitespace?(c)) or
163
+ (start_of_comment?(c))
164
+ break
165
+ else
166
+ io << c
167
+ end
168
+
169
+ # we parse true/false/null tokens as such no matter
170
+ # what is after them, as long as they are at the
171
+ # start of the unquoted token.
172
+ if io.length == 4
173
+ if io.string == "true"
174
+ return Tokens.new_boolean(origin, true)
175
+ elsif io.string == "null"
176
+ return Tokens.new_null(origin)
177
+ end
178
+ elsif io.length == 5
179
+ if io.string == "false"
180
+ return Tokens.new_boolean(origin, false)
181
+ end
182
+ end
183
+
184
+ c = next_char_raw
185
+ end
186
+
187
+ # put back the char that ended the unquoted text
188
+ put_back(c)
189
+
190
+ Tokens.new_unquoted_text(origin, io.string)
191
+ end
192
+
193
+
194
+ def pull_comment(first_char)
195
+ if first_char == '/'
196
+ discard = next_char_raw
197
+ if discard != '/'
198
+ raise ConfigBugError, "called pullComment but // not seen"
199
+ end
200
+ end
201
+
202
+ io = StringIO.new
203
+ while true
204
+ c = next_char_raw
205
+ if (c == -1) || (c == "\n")
206
+ put_back(c)
207
+ return Tokens.new_comment(@line_origin, io.string)
208
+ else
209
+ io << c
210
+ end
211
+ end
212
+ end
213
+
214
+ def pull_number(first_char)
215
+ sb = StringIO.new
216
+ sb << first_char
217
+ contained_decimal_or_e = false
218
+ c = next_char_raw
219
+ while (c != -1) && (NUMBER_CHARS.index(c))
220
+ if (c == '.') ||
221
+ (c == 'e') ||
222
+ (c == 'E')
223
+ contained_decimal_or_e = true
224
+ end
225
+ sb << c
226
+ c = next_char_raw
227
+ end
228
+ # the last character we looked at wasn't part of the number, put it
229
+ # back
230
+ put_back(c)
231
+ s = sb.string
232
+ begin
233
+ if contained_decimal_or_e
234
+ # force floating point representation
235
+ Tokens.new_double(@line_origin, s.to_f, s)
236
+ else
237
+ Tokens.new_long(@line_origin, s.to_i, s)
238
+ end
239
+ rescue ArgumentError => e
240
+ if e.message =~ /^invalid value for (Float|Integer)\(\)/
241
+ # not a number after all, see if it's an unquoted string.
242
+ s.each do |u|
243
+ if NOT_IN_UNQUOTED_TEXT.index
244
+ raise problem(u, "Reserved character '#{u}'" +
245
+ "is not allowed outside quotes", true)
246
+ end
247
+ end
248
+ # no evil chars so we just decide this was a string and
249
+ # not a number.
250
+ Tokens.new_unquoted_text(@line_origin, s)
251
+ else
252
+ raise e
253
+ end
254
+ end
255
+ end
256
+
257
+ def pull_quoted_string
258
+ # the open quote has already been consumed
259
+ sb = StringIO.new
260
+ c = ""
261
+ while c != '"'
262
+ c = next_char_raw
263
+ if c == -1
264
+ raise problem("End of input but string quote was still open")
265
+ end
266
+
267
+ if c == "\\"
268
+ pull_escape_sequence(sb)
269
+ elsif c == '"'
270
+ # done!
271
+ elsif c =~ /[[:cntrl:]]/
272
+ raise problem(c, "JSON does not allow unescaped #{c}" +
273
+ " in quoted strings, use a backslash escape")
274
+ else
275
+ sb << c
276
+ end
277
+ end
278
+
279
+ # maybe switch to triple-quoted string, sort of hacky...
280
+ if sb.length == 0
281
+ third = next_char_raw
282
+ if third == '"'
283
+ append_triple_quoted_string(sb)
284
+ else
285
+ put_back(third)
286
+ end
287
+ end
288
+
289
+ Tokens.new_string(@line_origin, sb.string)
290
+ end
291
+
292
+ def pull_next_token(saver)
293
+ c = next_char_after_whitespace(saver)
294
+ if c == -1
295
+ Tokens::EOF
296
+ elsif c == "\n"
297
+ # newline tokens have the just-ended line number
298
+ line = Tokens.new_line(@line_origin)
299
+ @line_number += 1
300
+ @line_origin = @origin.set_line_number(@line_number)
301
+ line
302
+ else
303
+ t = nil
304
+ if start_of_comment?(c)
305
+ t = pull_comment(c)
306
+ else
307
+ t = case c
308
+ when '"' then pull_quoted_string
309
+ when '$' then pull_substitution
310
+ when ':' then Tokens::COLON
311
+ when ',' then Tokens::COMMA
312
+ when '=' then Tokens::EQUALS
313
+ when '{' then Tokens::OPEN_CURLY
314
+ when '}' then Tokens::CLOSE_CURLY
315
+ when '[' then Tokens::OPEN_SQUARE
316
+ when ']' then Tokens::CLOSE_SQUARE
317
+ when '+' then pull_plus_equals
318
+ else nil
319
+ end
320
+
321
+ if t.nil?
322
+ if FIRST_NUMBER_CHARS.index(c)
323
+ t = pull_number(c)
324
+ elsif NOT_IN_UNQUOTED_TEXT.index(c)
325
+ raise problem(c, "Reserved character '#{c}' is not allowed outside quotes", true)
326
+ else
327
+ put_back(c)
328
+ t = pull_unquoted_text
329
+ end
330
+ end
331
+ end
332
+
333
+ if t.nil?
334
+ raise ConfigBugError, "bug: failed to generate next token"
335
+ end
336
+
337
+ t
338
+ end
339
+ end
340
+
341
+ def queue_next_token
342
+ t = pull_next_token(@whitespace_saver)
343
+ whitespace = @whitespace_saver.check(t, @origin, @line_number)
344
+ if whitespace
345
+ @tokens.push(whitespace)
346
+ end
347
+ @tokens.push(t)
348
+ end
349
+
350
+ def next
351
+ t = @tokens.shift
352
+ if (@tokens.empty?) and (t != Tokens::EOF)
353
+ begin
354
+ queue_next_token
355
+ rescue TokenizerProblemError => e
356
+ @tokens.push(e.problem)
357
+ end
358
+ if @tokens.empty?
359
+ raise ConfigBugError, "bug: tokens queue should not be empty here"
360
+ end
361
+ end
362
+ t
363
+ end
364
+ end
365
+
366
+
367
+ def self.tokenize(origin, input, syntax)
368
+ TokenIterator.new(origin, input, syntax != Typesafe::Config::ConfigSyntax::JSON)
369
+ end
370
+ end
@@ -0,0 +1,157 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/token'
3
+ require 'typesafe/config/impl/token_type'
4
+ require 'typesafe/config/impl/config_number'
5
+ require 'typesafe/config/impl/config_string'
6
+
7
+ # FIXME the way the subclasses of Token are private with static isFoo and accessors is kind of ridiculous.
8
+ class Typesafe::Config::Impl::Tokens
9
+ Token = Typesafe::Config::Impl::Token
10
+ TokenType = Typesafe::Config::Impl::TokenType
11
+ ConfigNumber = Typesafe::Config::Impl::ConfigNumber
12
+ ConfigString = Typesafe::Config::Impl::ConfigString
13
+
14
+ START = Token.new_without_origin(TokenType::START, "start of file")
15
+ EOF = Token.new_without_origin(TokenType::EOF, "end of file")
16
+ COMMA = Token.new_without_origin(TokenType::COMMA, "','")
17
+ EQUALS = Token.new_without_origin(TokenType::EQUALS, "'='")
18
+ COLON = Token.new_without_origin(TokenType::COLON, "':'")
19
+ OPEN_CURLY = Token.new_without_origin(TokenType::OPEN_CURLY, "'{'")
20
+ CLOSE_CURLY = Token.new_without_origin(TokenType::CLOSE_CURLY, "'}'")
21
+ OPEN_SQUARE = Token.new_without_origin(TokenType::OPEN_SQUARE, "'['")
22
+ CLOSE_SQUARE = Token.new_without_origin(TokenType::CLOSE_SQUARE, "']'")
23
+ PLUS_EQUALS = Token.new_without_origin(TokenType::PLUS_EQUALS, "'+='")
24
+
25
+ class Comment < Token
26
+ def initialize(origin, text)
27
+ super(TokenType::COMMENT, origin)
28
+ @text = text
29
+ end
30
+ attr_reader :text
31
+ end
32
+
33
+ # This is not a Value, because it requires special processing
34
+ class Substitution < Token
35
+ def initialize(origin, optional, expression)
36
+ super(TokenType::SUBSTITUTION, origin)
37
+ @optional = optional
38
+ @value = expression
39
+ end
40
+ end
41
+
42
+ class UnquotedText < Token
43
+ def initialize(origin, s)
44
+ super(TokenType::UNQUOTED_TEXT, origin)
45
+ @value = s
46
+ end
47
+ attr_reader :value
48
+
49
+ def to_s
50
+ "'#{value}'"
51
+ end
52
+ end
53
+
54
+ class Value < Token
55
+ def initialize(value)
56
+ super(TokenType::VALUE, value.origin)
57
+ @value = value
58
+ end
59
+ attr_reader :value
60
+
61
+ def to_s
62
+ "'#{value.unwrapped}' (#{Typesafe::Config::ConfigValueType.name(value.value_type)})"
63
+ end
64
+ end
65
+
66
+ class Line < Token
67
+ def initialize(origin)
68
+ super(TokenType::NEWLINE, origin)
69
+ end
70
+ end
71
+
72
+ class Problem < Token
73
+ def initialize(origin, what, message, suggest_quotes, cause)
74
+ super(TokenType::PROBLEM, origin)
75
+ @what = what
76
+ @message = message
77
+ @suggest_quotes = suggest_quotes
78
+ @cause = cause
79
+ end
80
+ end
81
+
82
+ def self.new_line(origin)
83
+ Line.new(origin)
84
+ end
85
+
86
+ def self.new_comment(origin, text)
87
+ Comment.new(origin, text)
88
+ end
89
+
90
+ def self.new_unquoted_text(origin, s)
91
+ UnquotedText.new(origin, s)
92
+ end
93
+
94
+ def self.new_value(value)
95
+ Value.new(value)
96
+ end
97
+
98
+ def self.new_string(origin, value)
99
+ new_value(ConfigString.new(origin, value))
100
+ end
101
+
102
+ def self.new_long(origin, value, original_text)
103
+ new_value(ConfigNumber.new_number(origin, value, original_text))
104
+ end
105
+
106
+ def self.comment?(t)
107
+ t.is_a?(Comment)
108
+ end
109
+
110
+ def self.comment_text(token)
111
+ if comment?(token)
112
+ token.text
113
+ else
114
+ raise ConfigBugError, "tried to get comment text from #{token}"
115
+ end
116
+ end
117
+
118
+ def self.substitution?(t)
119
+ t.is_a?(Substitution)
120
+ end
121
+
122
+ def self.unquoted_text?(token)
123
+ token.is_a?(UnquotedText)
124
+ end
125
+
126
+ def self.unquoted_text(token)
127
+ if unquoted_text?(token)
128
+ token.value
129
+ else
130
+ raise ConfigBugError, "tried to get unquoted text from #{token}"
131
+ end
132
+ end
133
+
134
+ def self.value?(token)
135
+ token.is_a?(Value)
136
+ end
137
+
138
+ def self.value(token)
139
+ if token.is_a?(Value)
140
+ token.value
141
+ else
142
+ raise ConfigBugError, "tried to get value of non-value token #{token}"
143
+ end
144
+ end
145
+
146
+ def self.value_with_type?(t, value_type)
147
+ value?(t) && (value(t).value_type == value_type)
148
+ end
149
+
150
+ def self.newline?(t)
151
+ t.is_a?(Line)
152
+ end
153
+
154
+ def self.problem?(t)
155
+ t.is_a?(Problem)
156
+ end
157
+ end
@@ -0,0 +1,4 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ module Typesafe::Config::Impl::Unmergeable
4
+ end
@@ -0,0 +1,5 @@
1
+ require 'typesafe/config'
2
+
3
+ module Typesafe::Config::Impl
4
+
5
+ end
@@ -0,0 +1,4 @@
1
+ require 'typesafe'
2
+
3
+ module Typesafe::Config
4
+ end
data/lib/typesafe.rb ADDED
@@ -0,0 +1,2 @@
1
+ module Typesafe
2
+ end
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: typesafe_config
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Price
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-03-14 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: ==A port of the Java [Typesafe Config](https://github.com/typesafehub/config)
15
+ library to Ruby
16
+ email: chris@puppetlabs.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/typesafe/config.rb
22
+ - lib/typesafe/config/config_object.rb
23
+ - lib/typesafe/config/config_error.rb
24
+ - lib/typesafe/config/impl/simple_includer.rb
25
+ - lib/typesafe/config/impl/config_number.rb
26
+ - lib/typesafe/config/impl/simple_config.rb
27
+ - lib/typesafe/config/impl/config_int.rb
28
+ - lib/typesafe/config/impl/resolve_status.rb
29
+ - lib/typesafe/config/impl/simple_config_origin.rb
30
+ - lib/typesafe/config/impl/token.rb
31
+ - lib/typesafe/config/impl/config_string.rb
32
+ - lib/typesafe/config/impl/config_float.rb
33
+ - lib/typesafe/config/impl/full_includer.rb
34
+ - lib/typesafe/config/impl/parser.rb
35
+ - lib/typesafe/config/impl/tokens.rb
36
+ - lib/typesafe/config/impl/tokenizer.rb
37
+ - lib/typesafe/config/impl/abstract_config_object.rb
38
+ - lib/typesafe/config/impl/simple_include_context.rb
39
+ - lib/typesafe/config/impl/token_type.rb
40
+ - lib/typesafe/config/impl/path_builder.rb
41
+ - lib/typesafe/config/impl/parseable.rb
42
+ - lib/typesafe/config/impl/path.rb
43
+ - lib/typesafe/config/impl/unmergeable.rb
44
+ - lib/typesafe/config/impl/config_impl_util.rb
45
+ - lib/typesafe/config/impl/simple_config_object.rb
46
+ - lib/typesafe/config/impl/simple_config_list.rb
47
+ - lib/typesafe/config/impl/config_impl.rb
48
+ - lib/typesafe/config/impl/config_concatenation.rb
49
+ - lib/typesafe/config/impl/abstract_config_value.rb
50
+ - lib/typesafe/config/impl/origin_type.rb
51
+ - lib/typesafe/config/config_factory.rb
52
+ - lib/typesafe/config/impl.rb
53
+ - lib/typesafe/config/config_parse_options.rb
54
+ - lib/typesafe/config/config_render_options.rb
55
+ - lib/typesafe/config/config_value_type.rb
56
+ - lib/typesafe/config/config_syntax.rb
57
+ - lib/typesafe.rb
58
+ - LICENSE
59
+ - README.md
60
+ homepage: https://github.com/cprice404/ruby-typesafe-config
61
+ licenses:
62
+ - Apache License, v2
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.25
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Typesafe Config
85
+ test_files: []