literal_parser 1.0.0
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.markdown +53 -0
- data/Rakefile +10 -0
- data/lib/literal_parser.rb +323 -0
- data/lib/literal_parser/version.rb +13 -0
- data/literal_parser.gemspec +36 -0
- data/test/lib/helper.rb +31 -0
- data/test/runner.rb +20 -0
- data/test/unit/lib/literal_parser.rb +89 -0
- metadata +53 -0
data/README.markdown
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
README
|
|
2
|
+
======
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Summary
|
|
6
|
+
-------
|
|
7
|
+
Parse Strings containing ruby literals and return a proper ruby object.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
Features
|
|
11
|
+
--------
|
|
12
|
+
|
|
13
|
+
* Recognizes about all Ruby literals: nil, true, false, Symbols, Integers, Floats, Hashes,
|
|
14
|
+
Arrays
|
|
15
|
+
* Additionally parses Constants, Dates and Times
|
|
16
|
+
* Very easy to use
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
Installation
|
|
20
|
+
------------
|
|
21
|
+
`gem install literal_parser`
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Usage
|
|
25
|
+
-----
|
|
26
|
+
|
|
27
|
+
A couple of examples:
|
|
28
|
+
|
|
29
|
+
LiteralParser.parse("nil") # => nil
|
|
30
|
+
LiteralParser.parse(":foo") # => :foo
|
|
31
|
+
LiteralParser.parse("123") # => 123
|
|
32
|
+
LiteralParser.parse("1.5") # => 1.5
|
|
33
|
+
LiteralParser.parse("1.5", use_big_decimal: true) # => #<BigDecimal:…,'0.15E1',18(18)>
|
|
34
|
+
LiteralParser.parse("[1, 2, 3]") # => [1, 2, 3]
|
|
35
|
+
LiteralParser.parse("{:a => 1, :b => 2}") # => {:a => 1, :b => 2}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
Links
|
|
41
|
+
-----
|
|
42
|
+
|
|
43
|
+
* [Online API Documentation](http://rdoc.info/github/apeiros/literal_parser/)
|
|
44
|
+
* [Public Repository](https://github.com/apeiros/literal_parser)
|
|
45
|
+
* [Bug Reporting](https://github.com/apeiros/literal_parser/issues)
|
|
46
|
+
* [RubyGems Site](https://rubygems.org/gems/literal_parser)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
License
|
|
50
|
+
-------
|
|
51
|
+
|
|
52
|
+
You can use this code under the {file:LICENSE.txt BSD-2-Clause License}, free of charge.
|
|
53
|
+
If you need a different license, please ask the author.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../rake/lib', __FILE__))
|
|
2
|
+
Dir.glob(File.expand_path('../rake/tasks/**/*.{rake,task,rb}', __FILE__)) do |task_file|
|
|
3
|
+
begin
|
|
4
|
+
import task_file
|
|
5
|
+
rescue LoadError => e
|
|
6
|
+
warn "Failed to load task file #{task_file}"
|
|
7
|
+
warn " #{e.class} #{e.message}"
|
|
8
|
+
warn " #{e.backtrace.first}"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
require 'literal_parser/version'
|
|
6
|
+
require 'strscan'
|
|
7
|
+
require 'bigdecimal'
|
|
8
|
+
require 'date'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# LiteralParser
|
|
13
|
+
#
|
|
14
|
+
# Parse Strings containing ruby literals.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# LiteralParser.parse("nil") # => nil
|
|
18
|
+
# LiteralParser.parse(":foo") # => :foo
|
|
19
|
+
# LiteralParser.parse("123") # => 123
|
|
20
|
+
# LiteralParser.parse("1.5") # => 1.5
|
|
21
|
+
# LiteralParser.parse("1.5", use_big_decimal: true) # => #<BigDecimal:…,'0.15E1',18(18)>
|
|
22
|
+
# LiteralParser.parse("[1, 2, 3]") # => [1, 2, 3]
|
|
23
|
+
# LiteralParser.parse("{:a => 1, :b => 2}") # => {:a => 1, :b => 2}
|
|
24
|
+
#
|
|
25
|
+
# LiteralParser recognizes constants and the following literals:
|
|
26
|
+
#
|
|
27
|
+
# nil # nil
|
|
28
|
+
# true # true
|
|
29
|
+
# false # false
|
|
30
|
+
# -123 # Fixnum/Bignum (decimal)
|
|
31
|
+
# 0b1011 # Fixnum/Bignum (binary)
|
|
32
|
+
# 0755 # Fixnum/Bignum (octal)
|
|
33
|
+
# 0xff # Fixnum/Bignum (hexadecimal)
|
|
34
|
+
# 120.30 # Float (optional: BigDecimal)
|
|
35
|
+
# 1e0 # Float
|
|
36
|
+
# "foo" # String, no interpolation, but \t etc. work
|
|
37
|
+
# 'foo' # String, only \\ and \' are escaped
|
|
38
|
+
# /foo/ # Regexp
|
|
39
|
+
# :foo # Symbol
|
|
40
|
+
# :"foo" # Symbol
|
|
41
|
+
# 2012-05-20 # Date
|
|
42
|
+
# 2012-05-20T18:29:52 # DateTime
|
|
43
|
+
# [Any, Literals, Here] # Array
|
|
44
|
+
# {Any => Literals} # Hash
|
|
45
|
+
#
|
|
46
|
+
#
|
|
47
|
+
# @note Limitations
|
|
48
|
+
#
|
|
49
|
+
# * LiteralParser does not support ruby 1.9's `{key: value}` syntax.
|
|
50
|
+
# * LiteralParser does not currently support all of rubys escape sequences in strings
|
|
51
|
+
# and symbols, e.g. "\C-…" type sequences don't work.
|
|
52
|
+
# * Trailing commas in Array and Hash are not supported.
|
|
53
|
+
#
|
|
54
|
+
# @note BigDecimals
|
|
55
|
+
#
|
|
56
|
+
# You can instruct LiteralParser to parse "12.5" as a bigdecimal and use "12.5e" to have
|
|
57
|
+
# it parsed as float (short for "12.5e0", equivalent to "1.25e1")
|
|
58
|
+
#
|
|
59
|
+
# @note Date & Time
|
|
60
|
+
#
|
|
61
|
+
# LiteralParser supports a subset of ISO-8601 for Date and Time which are not actual
|
|
62
|
+
# valid ruby literals. The form YYYY-MM-DD (e.g. 2012-05-20) is translated to a Date
|
|
63
|
+
# object, and YYYY-MM-DD"T"HH:MM:SS (e.g. 2012-05-20T18:29:52) is translated to a
|
|
64
|
+
# Time object.
|
|
65
|
+
#
|
|
66
|
+
class LiteralParser
|
|
67
|
+
|
|
68
|
+
# Raised when a String could not be parsed
|
|
69
|
+
class SyntaxError < StandardError; end
|
|
70
|
+
|
|
71
|
+
# @private
|
|
72
|
+
# All the expressions used to parse the literals
|
|
73
|
+
module Expressions
|
|
74
|
+
|
|
75
|
+
RArrayBegin = /\[/ # Match begin of an array
|
|
76
|
+
|
|
77
|
+
RArrayVoid = /\s*/ # Match whitespace between elements in an array
|
|
78
|
+
|
|
79
|
+
RArraySeparator = /#{RArrayVoid},#{RArrayVoid}/ # Match the separator of array elements
|
|
80
|
+
|
|
81
|
+
RArrayEnd = /\]/ # Match end of an array
|
|
82
|
+
|
|
83
|
+
RHashBegin = /\{/ # Match begin of a hash
|
|
84
|
+
|
|
85
|
+
RHashVoid = /\s*/ # Match whitespace between elements in a hash
|
|
86
|
+
|
|
87
|
+
RHashSeparator = /#{RHashVoid},#{RHashVoid}/ # Match the separator of hash key/value pairs
|
|
88
|
+
|
|
89
|
+
RHashArrow = /#{RHashVoid}=>#{RHashVoid}/ # Match the separator between a key and a value in a hash
|
|
90
|
+
|
|
91
|
+
RHashEnd = /\}/ # Match end of a hash
|
|
92
|
+
|
|
93
|
+
RConstant = /[A-Z]\w*(?:::[A-Z]\w*)*/ # Match constant names (with nesting)
|
|
94
|
+
|
|
95
|
+
RNil = /nil/ # Match nil
|
|
96
|
+
|
|
97
|
+
RFalse = /false/ # Match false
|
|
98
|
+
|
|
99
|
+
RTrue = /true/ # Match true
|
|
100
|
+
|
|
101
|
+
RInteger = /[+-]?\d[\d_]*/ # Match an Integer in decimal notation
|
|
102
|
+
|
|
103
|
+
RBinaryInteger = /[+-]?0b[01][01_]*/ # Match an Integer in binary notation
|
|
104
|
+
|
|
105
|
+
RHexInteger = /[+-]?0x[A-Fa-f\d][A-Fa-f\d_]*/ # Match an Integer in hexadecimal notation
|
|
106
|
+
|
|
107
|
+
ROctalInteger = /[+-]?0[0-7][0-7'_,]*/ # Match an Integer in octal notation
|
|
108
|
+
|
|
109
|
+
RBigDecimal = /#{RInteger}\.\d+/ # Match a decimal number (Float or BigDecimal)
|
|
110
|
+
|
|
111
|
+
RFloat = /#{RBigDecimal}(?:f|e#{RInteger})/ # Match a decimal number in scientific notation
|
|
112
|
+
|
|
113
|
+
RSString = /'(?:[^\\']+|\\.)*'/ # Match a single quoted string
|
|
114
|
+
|
|
115
|
+
RDString = /"(?:[^\\"]+|\\.)*"/ # Match a double quoted string
|
|
116
|
+
|
|
117
|
+
RRegexp = %r{/((?:[^\\/]+|\\.)*)/([imxnNeEsSuU]*)} # Match a regular expression
|
|
118
|
+
|
|
119
|
+
RSymbol = /:\w+|:#{RSString}|:#{RDString}/ # Match a symbol
|
|
120
|
+
|
|
121
|
+
RDate = /(\d{4})-(\d{2})-(\d{2})/ # Match a date
|
|
122
|
+
|
|
123
|
+
RTimeZone = /(Z|[A-Z]{3,4}|[+-]\d{4})/ # Match a timezone
|
|
124
|
+
|
|
125
|
+
RTime = /(\d{2}):(\d{2}):(\d{2})(?:RTimeZone)?/ # Match a time (without date)
|
|
126
|
+
|
|
127
|
+
RDateTime = /#{RDate}T#{RTime}/ # Match a datetime
|
|
128
|
+
|
|
129
|
+
# Map escape sequences in double quoted strings
|
|
130
|
+
DStringEscapes = {
|
|
131
|
+
'\\\\' => "\\",
|
|
132
|
+
"\\'" => "'",
|
|
133
|
+
'\\"' => '"',
|
|
134
|
+
'\t' => "\t",
|
|
135
|
+
'\f' => "\f",
|
|
136
|
+
'\r' => "\r",
|
|
137
|
+
'\n' => "\n",
|
|
138
|
+
}
|
|
139
|
+
256.times do |i|
|
|
140
|
+
DStringEscapes["\\%o" % i] = i.chr
|
|
141
|
+
DStringEscapes["\\%03o" % i] = i.chr
|
|
142
|
+
DStringEscapes["\\x%02x" % i] = i.chr
|
|
143
|
+
DStringEscapes["\\x%02X" % i] = i.chr
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
include Expressions
|
|
147
|
+
|
|
148
|
+
# Parse a String, returning the object which it contains.
|
|
149
|
+
#
|
|
150
|
+
# @example
|
|
151
|
+
# LiteralParser.parse(":foo") # => :foo
|
|
152
|
+
#
|
|
153
|
+
# @param [String] string
|
|
154
|
+
# The string which should be parsed
|
|
155
|
+
# @param [nil, Hash] options
|
|
156
|
+
# An options-hash
|
|
157
|
+
#
|
|
158
|
+
# @option options [Boolean] :use_big_decimal
|
|
159
|
+
# Whether to use BigDecimal instead of Float for objects like "1.23".
|
|
160
|
+
# Defaults to false.
|
|
161
|
+
# @option options [Boolean] :constant_base
|
|
162
|
+
# Determines from what constant other constants are searched.
|
|
163
|
+
# Defaults to Object (nil is treated as Object too, Object is the toplevel-namespace).
|
|
164
|
+
#
|
|
165
|
+
# @return [Object] The object in the string.
|
|
166
|
+
#
|
|
167
|
+
# @raise [LiteralParser::SyntaxError]
|
|
168
|
+
# If the String does not contain exactly one valid literal, a SyntaxError is raised.
|
|
169
|
+
def self.parse(string, options=nil)
|
|
170
|
+
parser = new(string, options)
|
|
171
|
+
value = parser.scan_value
|
|
172
|
+
raise SyntaxError, "Unexpected superfluous data: #{parser.rest.inspect}" unless parser.end_of_string?
|
|
173
|
+
|
|
174
|
+
value
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# @return [Module, nil]
|
|
178
|
+
# Where to lookup constants. Nil is toplevel (equivalent to Object).
|
|
179
|
+
attr_reader :constant_base
|
|
180
|
+
|
|
181
|
+
# @return [Boolean]
|
|
182
|
+
# True if "1.25" should be parsed into a big-decimal, false if it should be parsed as
|
|
183
|
+
# Float.
|
|
184
|
+
attr_reader :use_big_decimal
|
|
185
|
+
|
|
186
|
+
#
|
|
187
|
+
# Parse a String, returning the object which it contains.
|
|
188
|
+
#
|
|
189
|
+
# @param [String] string
|
|
190
|
+
# The string which should be parsed
|
|
191
|
+
# @param [nil, Hash] options
|
|
192
|
+
# An options-hash
|
|
193
|
+
#
|
|
194
|
+
# @option options [Boolean] :use_big_decimal
|
|
195
|
+
# Whether to use BigDecimal instead of Float for objects like "1.23".
|
|
196
|
+
# Defaults to false.
|
|
197
|
+
# @option options [Boolean] :constant_base
|
|
198
|
+
# Determines from what constant other constants are searched.
|
|
199
|
+
# Defaults to Object (nil is treated as Object too, Object is the toplevel-namespace).
|
|
200
|
+
def initialize(string, options=nil)
|
|
201
|
+
options = options ? options.dup : {}
|
|
202
|
+
@constant_base = options[:constant_base] # nil means toplevel
|
|
203
|
+
@use_big_decimal = options.delete(:use_big_decimal) { false }
|
|
204
|
+
@string = string
|
|
205
|
+
@scanner = StringScanner.new(string)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# @return [Integer] The position of the scanner in the string
|
|
209
|
+
def position
|
|
210
|
+
@scanner.pos
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Moves the scanners position to the given character-index.
|
|
214
|
+
#
|
|
215
|
+
# @param [Integer] value
|
|
216
|
+
# The new position of the scanner
|
|
217
|
+
def position=(value)
|
|
218
|
+
@scanner.pos = value
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# @return [Boolean] Whether the scanner reached the end of the string.
|
|
222
|
+
def end_of_string?
|
|
223
|
+
@scanner.eos?
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# @return [String] The currently unprocessed rest of the string.
|
|
227
|
+
def rest
|
|
228
|
+
@scanner.rest
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
# Scans the string for a single value and advances the parsers position
|
|
232
|
+
#
|
|
233
|
+
# @return [Object] the scanned value
|
|
234
|
+
#
|
|
235
|
+
# @raise [LiteralParser::SyntaxError]
|
|
236
|
+
# When no valid ruby object could be scanned at the given position, a
|
|
237
|
+
# LiteralParser::SyntaxError is raised.
|
|
238
|
+
def scan_value
|
|
239
|
+
case
|
|
240
|
+
when @scanner.scan(RArrayBegin) then
|
|
241
|
+
value = []
|
|
242
|
+
@scanner.scan(RArrayVoid)
|
|
243
|
+
if @scanner.scan(RArrayEnd)
|
|
244
|
+
value
|
|
245
|
+
else
|
|
246
|
+
value << scan_value
|
|
247
|
+
while @scanner.scan(RArraySeparator)
|
|
248
|
+
value << scan_value
|
|
249
|
+
end
|
|
250
|
+
raise SyntaxError, "Expected ]" unless @scanner.scan(RArrayVoid) && @scanner.scan(RArrayEnd)
|
|
251
|
+
|
|
252
|
+
value
|
|
253
|
+
end
|
|
254
|
+
when @scanner.scan(RHashBegin) then
|
|
255
|
+
value = {}
|
|
256
|
+
@scanner.scan(RHashVoid)
|
|
257
|
+
if @scanner.scan(RHashEnd)
|
|
258
|
+
value
|
|
259
|
+
else
|
|
260
|
+
key = scan_value
|
|
261
|
+
raise SyntaxError, "Expected =>" unless @scanner.scan(RHashArrow)
|
|
262
|
+
val = scan_value
|
|
263
|
+
value[key] = val
|
|
264
|
+
while @scanner.scan(RHashSeparator)
|
|
265
|
+
key = scan_value
|
|
266
|
+
raise SyntaxError, "Expected =>" unless @scanner.scan(RHashArrow)
|
|
267
|
+
val = scan_value
|
|
268
|
+
value[key] = val
|
|
269
|
+
end
|
|
270
|
+
raise SyntaxError, "Expected }" unless @scanner.scan(RHashVoid) && @scanner.scan(RHashEnd)
|
|
271
|
+
|
|
272
|
+
value
|
|
273
|
+
end
|
|
274
|
+
when @scanner.scan(RConstant) then eval("#{@constant_base}::#{@scanner[0]}") # yes, I know it's evil, but it's sane due to the regex, also it's less annoying than deep_const_get
|
|
275
|
+
when @scanner.scan(RNil) then nil
|
|
276
|
+
when @scanner.scan(RTrue) then true
|
|
277
|
+
when @scanner.scan(RFalse) then false
|
|
278
|
+
when @scanner.scan(RDateTime) then
|
|
279
|
+
Time.mktime(@scanner[1], @scanner[2], @scanner[3], @scanner[4], @scanner[5], @scanner[6])
|
|
280
|
+
when @scanner.scan(RDate) then
|
|
281
|
+
date = @scanner[1].to_i, @scanner[2].to_i, @scanner[3].to_i
|
|
282
|
+
Date.civil(*date)
|
|
283
|
+
when @scanner.scan(RTime) then
|
|
284
|
+
now = Time.now
|
|
285
|
+
Time.mktime(now.year, now.month, now.day, @scanner[1].to_i, @scanner[2].to_i, @scanner[3].to_i)
|
|
286
|
+
when @scanner.scan(RFloat) then Float(@scanner.matched.delete('^0-9.e-'))
|
|
287
|
+
when @scanner.scan(RBigDecimal) then
|
|
288
|
+
data = @scanner.matched.delete('^0-9.-')
|
|
289
|
+
@use_big_decimal ? BigDecimal(data) : Float(data)
|
|
290
|
+
when @scanner.scan(ROctalInteger) then Integer(@scanner.matched.delete('^0-9-'))
|
|
291
|
+
when @scanner.scan(RHexInteger) then Integer(@scanner.matched.delete('^xX0-9A-Fa-f-'))
|
|
292
|
+
when @scanner.scan(RBinaryInteger) then Integer(@scanner.matched.delete('^bB01-'))
|
|
293
|
+
when @scanner.scan(RInteger) then @scanner.matched.delete('^0-9-').to_i
|
|
294
|
+
when @scanner.scan(RRegexp) then
|
|
295
|
+
source = @scanner[1]
|
|
296
|
+
flags = 0
|
|
297
|
+
lang = nil
|
|
298
|
+
if @scanner[2] then
|
|
299
|
+
flags |= Regexp::IGNORECASE if @scanner[2].include?('i')
|
|
300
|
+
flags |= Regexp::EXTENDED if @scanner[2].include?('m')
|
|
301
|
+
flags |= Regexp::MULTILINE if @scanner[2].include?('x')
|
|
302
|
+
lang = @scanner[2].delete('^nNeEsSuU')[-1,1]
|
|
303
|
+
end
|
|
304
|
+
Regexp.new(source, flags, lang)
|
|
305
|
+
when @scanner.scan(RSymbol) then
|
|
306
|
+
case @scanner.matched[1,1]
|
|
307
|
+
when '"'
|
|
308
|
+
@scanner.matched[2..-2].gsub(/\\(?:[0-3]?\d\d?|x[A-Fa-f\d]{2}|.)/) { |m|
|
|
309
|
+
DStringEscapes[m]
|
|
310
|
+
}.to_sym
|
|
311
|
+
when "'"
|
|
312
|
+
@scanner.matched[2..-2].gsub(/\\'/, "'").gsub(/\\\\/, "\\").to_sym
|
|
313
|
+
else
|
|
314
|
+
@scanner.matched[1..-1].to_sym
|
|
315
|
+
end
|
|
316
|
+
when @scanner.scan(RSString) then
|
|
317
|
+
@scanner.matched[1..-2].gsub(/\\'/, "'").gsub(/\\\\/, "\\")
|
|
318
|
+
when @scanner.scan(RDString) then
|
|
319
|
+
@scanner.matched[1..-2].gsub(/\\(?:[0-3]?\d\d?|x[A-Fa-f\d]{2}|.)/) { |m| DStringEscapes[m] }
|
|
320
|
+
else raise SyntaxError, "Unrecognized pattern: #{@scanner.rest.inspect}"
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'rubygems/version' # newer rubygems use this
|
|
5
|
+
rescue LoadError
|
|
6
|
+
require 'gem/version' # older rubygems use this
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
class LiteralParser
|
|
10
|
+
|
|
11
|
+
# The version of the LiteralParser library
|
|
12
|
+
Version = Gem::Version.new("1.0.0")
|
|
13
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = "literal_parser"
|
|
5
|
+
s.version = "1.0.0"
|
|
6
|
+
s.authors = "Stefan Rusterholz"
|
|
7
|
+
s.email = "stefan.rusterholz@gmail.com"
|
|
8
|
+
s.homepage = "https://github.com/apeiros/literal_parser"
|
|
9
|
+
|
|
10
|
+
s.description = <<-DESCRIPTION.gsub(/^ /, '').chomp
|
|
11
|
+
Parse Strings containing ruby literals and return a proper ruby object.
|
|
12
|
+
DESCRIPTION
|
|
13
|
+
s.summary = <<-SUMMARY.gsub(/^ /, '').chomp
|
|
14
|
+
Parse Strings containing ruby literals and return a proper ruby object.
|
|
15
|
+
SUMMARY
|
|
16
|
+
|
|
17
|
+
s.files =
|
|
18
|
+
Dir['bin/**/*'] +
|
|
19
|
+
Dir['lib/**/*'] +
|
|
20
|
+
Dir['rake/**/*'] +
|
|
21
|
+
Dir['test/**/*'] +
|
|
22
|
+
Dir['*.gemspec'] +
|
|
23
|
+
%w[
|
|
24
|
+
Rakefile
|
|
25
|
+
README.markdown
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
if File.directory?('bin') then
|
|
29
|
+
executables = Dir.chdir('bin') { Dir.glob('**/*').select { |f| File.executable?(f) } }
|
|
30
|
+
s.executables = executables unless executables.empty?
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1")
|
|
34
|
+
s.rubygems_version = "1.3.1"
|
|
35
|
+
s.specification_version = 3
|
|
36
|
+
end
|
data/test/lib/helper.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
|
|
3
|
+
module TestSuite
|
|
4
|
+
attr_accessor :name
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module Kernel
|
|
8
|
+
def suite(name, &block)
|
|
9
|
+
klass = Class.new(Test::Unit::TestCase, &block)
|
|
10
|
+
klass.extend TestSuite
|
|
11
|
+
klass.name = "Suite #{name}"
|
|
12
|
+
|
|
13
|
+
klass
|
|
14
|
+
end
|
|
15
|
+
module_function :suite
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Test::Unit::TestCase
|
|
19
|
+
def self.test(desc, &impl)
|
|
20
|
+
define_method("test #{desc}", &impl)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def capture_stdout
|
|
24
|
+
captured = StringIO.new
|
|
25
|
+
$stdout = captured
|
|
26
|
+
yield
|
|
27
|
+
captured.string
|
|
28
|
+
ensure
|
|
29
|
+
$stdout = STDOUT
|
|
30
|
+
end
|
|
31
|
+
end
|
data/test/runner.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# run with `ruby test/runner.rb`
|
|
2
|
+
# if you only want to run a single test-file: `ruby test/runner.rb testfile.rb`
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
|
|
5
|
+
$LOAD_PATH << File.expand_path('../../test/lib', __FILE__)
|
|
6
|
+
TEST_DIR = File.expand_path('../../test', __FILE__)
|
|
7
|
+
|
|
8
|
+
require 'test/unit'
|
|
9
|
+
require 'helper'
|
|
10
|
+
|
|
11
|
+
if ENV['COVERAGE']
|
|
12
|
+
require 'simplecov'
|
|
13
|
+
SimpleCov.start
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
units = ARGV.empty? ? Dir["#{TEST_DIR}/unit/**/*.rb"] : ARGV
|
|
17
|
+
|
|
18
|
+
units.each do |unit|
|
|
19
|
+
load unit
|
|
20
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
|
|
3
|
+
require 'literal_parser'
|
|
4
|
+
|
|
5
|
+
suite "LiteralParser" do
|
|
6
|
+
test "Parse nil" do
|
|
7
|
+
assert_equal nil, LiteralParser.parse('nil')
|
|
8
|
+
end
|
|
9
|
+
test "Parse true" do
|
|
10
|
+
assert_equal true, LiteralParser.parse('true')
|
|
11
|
+
end
|
|
12
|
+
test "Parse false" do
|
|
13
|
+
assert_equal false, LiteralParser.parse('false')
|
|
14
|
+
end
|
|
15
|
+
test "Parse Integers" do
|
|
16
|
+
assert_equal 123, LiteralParser.parse('123')
|
|
17
|
+
assert_equal -123, LiteralParser.parse('-123')
|
|
18
|
+
assert_equal 1_234_567, LiteralParser.parse('1_234_567')
|
|
19
|
+
assert_equal 0b1011, LiteralParser.parse('0b1011')
|
|
20
|
+
assert_equal 0xe3, LiteralParser.parse('0xe3')
|
|
21
|
+
end
|
|
22
|
+
test "Parse Floats" do
|
|
23
|
+
assert_equal 12.37, LiteralParser.parse('12.37')
|
|
24
|
+
assert_equal -31.59, LiteralParser.parse('-31.59')
|
|
25
|
+
assert_equal 1.2e5, LiteralParser.parse('1.2e5')
|
|
26
|
+
assert_equal 1.2e-5, LiteralParser.parse('1.2e-5')
|
|
27
|
+
assert_equal -1.2e5, LiteralParser.parse('-1.2e5')
|
|
28
|
+
assert_equal -1.2e-5, LiteralParser.parse('-1.2e-5')
|
|
29
|
+
end
|
|
30
|
+
test "Parse BigDecimals" do
|
|
31
|
+
assert_equal BigDecimal("12.37"), LiteralParser.parse('12.37', use_big_decimal: true)
|
|
32
|
+
end
|
|
33
|
+
test "Parse Symbols" do
|
|
34
|
+
assert_equal :simple_symbol, LiteralParser.parse(':simple_symbol')
|
|
35
|
+
assert_equal :"double quoted symbol", LiteralParser.parse(':"double quoted symbol"')
|
|
36
|
+
assert_equal :'single quoted symbol', LiteralParser.parse(":'single quoted symbol'")
|
|
37
|
+
end
|
|
38
|
+
test "Parse Strings" do
|
|
39
|
+
assert_equal 'Single Quoted String', LiteralParser.parse(%q{'Single Quoted String'})
|
|
40
|
+
assert_equal "Double Quoted String", LiteralParser.parse(%q{"Double Quoted String"})
|
|
41
|
+
assert_equal 'Single Quoted String \t\n\r\'\"', LiteralParser.parse(%q{'Single Quoted String \t\n\r\'\"'})
|
|
42
|
+
assert_equal "Double Quoted String \t\n\r\'\"", LiteralParser.parse(%q{"Double Quoted String \t\n\r\'\""})
|
|
43
|
+
end
|
|
44
|
+
test "Parse Regexes" do
|
|
45
|
+
assert_equal /some_regex/, LiteralParser.parse('/some_regex/')
|
|
46
|
+
assert_equal /some_regex/imx, LiteralParser.parse('/some_regex/imx')
|
|
47
|
+
end
|
|
48
|
+
test "Parse Date" do
|
|
49
|
+
assert_equal Date.civil(2012,5,20), LiteralParser.parse('2012-05-20')
|
|
50
|
+
end
|
|
51
|
+
test "Parse Time" do
|
|
52
|
+
assert_equal Time.mktime(2012,5,20,18,29,52), LiteralParser.parse('2012-05-20T18:29:52')
|
|
53
|
+
end
|
|
54
|
+
test "Parse Array" do
|
|
55
|
+
array_string = '[nil, false, true, 123, 12.5, 2012-05-20, :sym, "str"]'
|
|
56
|
+
expected_array = [nil, false, true, 123, 12.5, Date.civil(2012,5,20), :sym, "str"]
|
|
57
|
+
assert_equal expected_array, LiteralParser.parse(array_string)
|
|
58
|
+
end
|
|
59
|
+
test "Parse Hash" do
|
|
60
|
+
array_string = '{nil => false, true => 123, 12.5 => 2012-05-20, :sym => "str"}'
|
|
61
|
+
expected_array = {nil => false, true => 123, 12.5 => Date.civil(2012,5,20), :sym => "str"}
|
|
62
|
+
assert_equal expected_array, LiteralParser.parse(array_string)
|
|
63
|
+
end
|
|
64
|
+
test "Parse Constants" do
|
|
65
|
+
assert_equal Time, LiteralParser.parse('Time')
|
|
66
|
+
end
|
|
67
|
+
test "Perform manual parsing" do
|
|
68
|
+
parser = LiteralParser.new("'hello'\n12\ntrue")
|
|
69
|
+
assert_equal 0, parser.position
|
|
70
|
+
assert_equal 'hello', parser.scan_value
|
|
71
|
+
assert_equal "\n12\ntrue", parser.rest
|
|
72
|
+
assert_equal 7, parser.position
|
|
73
|
+
assert_equal false, parser.end_of_string?
|
|
74
|
+
|
|
75
|
+
parser.position += 1
|
|
76
|
+
assert_equal 8, parser.position
|
|
77
|
+
assert_equal 12, parser.scan_value
|
|
78
|
+
assert_equal "\ntrue", parser.rest
|
|
79
|
+
assert_equal 10, parser.position
|
|
80
|
+
assert_equal false, parser.end_of_string?
|
|
81
|
+
|
|
82
|
+
parser.position += 1
|
|
83
|
+
assert_equal 11, parser.position
|
|
84
|
+
assert_equal true, parser.scan_value
|
|
85
|
+
assert_equal "", parser.rest
|
|
86
|
+
assert_equal 15, parser.position
|
|
87
|
+
assert_equal true, parser.end_of_string?
|
|
88
|
+
end
|
|
89
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: literal_parser
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Stefan Rusterholz
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2012-05-20 00:00:00.000000000 Z
|
|
13
|
+
dependencies: []
|
|
14
|
+
description: Parse Strings containing ruby literals and return a proper ruby object.
|
|
15
|
+
email: stefan.rusterholz@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- lib/literal_parser/version.rb
|
|
21
|
+
- lib/literal_parser.rb
|
|
22
|
+
- test/lib/helper.rb
|
|
23
|
+
- test/runner.rb
|
|
24
|
+
- test/unit/lib/literal_parser.rb
|
|
25
|
+
- literal_parser.gemspec
|
|
26
|
+
- Rakefile
|
|
27
|
+
- README.markdown
|
|
28
|
+
homepage: https://github.com/apeiros/literal_parser
|
|
29
|
+
licenses: []
|
|
30
|
+
post_install_message:
|
|
31
|
+
rdoc_options: []
|
|
32
|
+
require_paths:
|
|
33
|
+
- lib
|
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
35
|
+
none: false
|
|
36
|
+
requirements:
|
|
37
|
+
- - ! '>='
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
41
|
+
none: false
|
|
42
|
+
requirements:
|
|
43
|
+
- - ! '>'
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: 1.3.1
|
|
46
|
+
requirements: []
|
|
47
|
+
rubyforge_project:
|
|
48
|
+
rubygems_version: 1.8.24
|
|
49
|
+
signing_key:
|
|
50
|
+
specification_version: 3
|
|
51
|
+
summary: Parse Strings containing ruby literals and return a proper ruby object.
|
|
52
|
+
test_files: []
|
|
53
|
+
has_rdoc:
|