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