parsr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/lib/parsr.rb +91 -0
- data/lib/parsr/array_rule.rb +44 -0
- data/lib/parsr/float_rule.rb +15 -0
- data/lib/parsr/hash_rule.rb +46 -0
- data/lib/parsr/integer_rule.rb +15 -0
- data/lib/parsr/raw_string_rule.rb +38 -0
- data/lib/parsr/string_rule.rb +40 -0
- data/lib/parsr/symbol_rule.rb +15 -0
- data/lib/parsr/token.rb +9 -0
- data/lib/parsr/version.rb +3 -0
- data/parsr.gemspec +19 -0
- data/spec/parsr/array_rule_spec.rb +75 -0
- data/spec/parsr/float_rule_spec.rb +38 -0
- data/spec/parsr/hash_rule_spec.rb +67 -0
- data/spec/parsr/integer_rule_spec.rb +37 -0
- data/spec/parsr/raw_string_rule_spec.rb +53 -0
- data/spec/parsr/string_rule_spec.rb +53 -0
- data/spec/parsr/symbol_rule_spec.rb +17 -0
- data/spec/parsr_spec.rb +55 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/support/rule_shared_context.rb +8 -0
- metadata +91 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/parsr.rb
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'strscan'
|
2
|
+
|
3
|
+
class Parsr
|
4
|
+
|
5
|
+
BASE_PATH = File.expand_path(File.join(File.dirname(__FILE__), 'parsr'))
|
6
|
+
|
7
|
+
autoload :ArrayRule, "#{BASE_PATH}/array_rule"
|
8
|
+
autoload :FloatRule, "#{BASE_PATH}/float_rule"
|
9
|
+
autoload :HashRule, "#{BASE_PATH}/hash_rule"
|
10
|
+
autoload :IntegerRule, "#{BASE_PATH}/integer_rule"
|
11
|
+
autoload :RawStringRule, "#{BASE_PATH}/raw_string_rule"
|
12
|
+
autoload :StringRule, "#{BASE_PATH}/string_rule"
|
13
|
+
autoload :SymbolRule, "#{BASE_PATH}/symbol_rule"
|
14
|
+
autoload :Token, "#{BASE_PATH}/token"
|
15
|
+
autoload :VERSION, "#{BASE_PATH}/version"
|
16
|
+
|
17
|
+
Error = Class.new(Exception)
|
18
|
+
IllegalValue = Class.new(Error)
|
19
|
+
|
20
|
+
class SyntaxError < Error
|
21
|
+
|
22
|
+
attr_reader :scanner
|
23
|
+
|
24
|
+
def self.message(message=nil)
|
25
|
+
@message = "Syntax error, #{message}" if message
|
26
|
+
@message
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(scanner)
|
30
|
+
@scanner = scanner
|
31
|
+
super(self.class.message % context)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
def context
|
36
|
+
if scanner.eos? || scanner.rest =~ /^\s*$/
|
37
|
+
rest = 'end of string'
|
38
|
+
else
|
39
|
+
scanner.rest.scan(/^\s*([^\s])/)
|
40
|
+
rest = $1
|
41
|
+
end
|
42
|
+
{:rest => rest}
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
|
49
|
+
def safe_literal_eval(string)
|
50
|
+
@safe_eval_parser ||= self.new(
|
51
|
+
ArrayRule,
|
52
|
+
HashRule,
|
53
|
+
SymbolRule,
|
54
|
+
FloatRule,
|
55
|
+
IntegerRule,
|
56
|
+
RawStringRule,
|
57
|
+
StringRule
|
58
|
+
)
|
59
|
+
@safe_eval_parser.parse(string)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
attr_accessor :rules
|
65
|
+
|
66
|
+
def initialize(*rules)
|
67
|
+
@rules = [rules].flatten
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse(string)
|
71
|
+
scanner = StringScanner.new(string)
|
72
|
+
token = parse_value(scanner)
|
73
|
+
raise IllegalValue unless token.is_a?(Parsr::Token)
|
74
|
+
token.value
|
75
|
+
end
|
76
|
+
|
77
|
+
protected
|
78
|
+
def parse_value(scanner)
|
79
|
+
trim_space(scanner)
|
80
|
+
rules.each do |rule|
|
81
|
+
token = rule.match(scanner){ parse_value(scanner) }
|
82
|
+
return token if token.is_a?(Parsr::Token)
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def trim_space(scanner)
|
88
|
+
scanner.scan(/\s+/)
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Parsr::ArrayRule
|
2
|
+
|
3
|
+
class Unterminated < Parsr::SyntaxError
|
4
|
+
message "unexpected '%{rest}', expecting ']'"
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def match(scanner, &block)
|
10
|
+
if scanner.scan(/\[\s*/)
|
11
|
+
array = []
|
12
|
+
begin
|
13
|
+
if scanner.scan(/[a-zA-Z_][0-9a-zA-Z_]*\:/)
|
14
|
+
token = Parsr::Token.new(scanner.matched.chomp(':').to_sym)
|
15
|
+
array << parse_unclosed_hash(scanner, token, &block)
|
16
|
+
break
|
17
|
+
end
|
18
|
+
|
19
|
+
token = yield
|
20
|
+
if token.is_a?(Parsr::Token) && scanner.scan(/\s*=>\s*/)
|
21
|
+
array << parse_unclosed_hash(scanner, token, &block)
|
22
|
+
break
|
23
|
+
end
|
24
|
+
|
25
|
+
array << token.value if token.is_a?(Parsr::Token)
|
26
|
+
end while scanner.scan(/\s*,\s*/)
|
27
|
+
raise Unterminated.new(scanner) unless scanner.scan(/\s*\]/)
|
28
|
+
Parsr::Token.new(array)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def parse_unclosed_hash(scanner, token, &block)
|
33
|
+
hash = []
|
34
|
+
while pair = Parsr::HashRule.parse_pair(scanner, token, &block)
|
35
|
+
token = nil
|
36
|
+
hash << pair
|
37
|
+
break unless scanner.scan(/\s*\,\s*/)
|
38
|
+
end
|
39
|
+
Hash[hash]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Parsr::HashRule
|
2
|
+
|
3
|
+
class MissingValue < Parsr::SyntaxError
|
4
|
+
message "unexpected '%{rest}'"
|
5
|
+
end
|
6
|
+
|
7
|
+
class MissingSeparator < Parsr::SyntaxError
|
8
|
+
message "unexpected '%{rest}', expecting '=>'"
|
9
|
+
end
|
10
|
+
|
11
|
+
class Unterminated < Parsr::SyntaxError
|
12
|
+
message "unexpected '%{rest}', expecting '}'"
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
def match(scanner, &block)
|
18
|
+
if scanner.scan(/\{\s*/)
|
19
|
+
hash = []
|
20
|
+
while pair = parse_pair(scanner, &block)
|
21
|
+
hash << pair
|
22
|
+
break unless scanner.scan(/\s*\,\s*/)
|
23
|
+
end
|
24
|
+
raise Unterminated.new(scanner) unless scanner.scan(/\s*\}\s*/)
|
25
|
+
Parsr::Token.new(Hash[hash])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def parse_pair(scanner, key=nil)
|
30
|
+
unless key.is_a?(Parsr::Token)
|
31
|
+
if scanner.scan(/[a-zA-Z_][0-9a-zA-Z_]*\:/)
|
32
|
+
key = Parsr::Token.new(scanner.matched.chomp(':').to_sym)
|
33
|
+
elsif key = yield and key.is_a?(Parsr::Token)
|
34
|
+
raise MissingSeparator.new(scanner) unless scanner.scan(/\s*=>\s*/)
|
35
|
+
else
|
36
|
+
return false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
value = yield
|
40
|
+
raise MissingValue.new(scanner) unless value && value.is_a?(Parsr::Token)
|
41
|
+
[key, value].map(&:value)
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Parsr::RawStringRule
|
2
|
+
|
3
|
+
class Unterminated < Parsr::SyntaxError
|
4
|
+
message 'unterminated string meets end of file'
|
5
|
+
end
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def match(scanner)
|
10
|
+
if scanner.scan(/'/)
|
11
|
+
buffer = ''
|
12
|
+
while chunk = (parse_content(scanner) || parse_escape(scanner))
|
13
|
+
buffer << chunk
|
14
|
+
end
|
15
|
+
raise Unterminated.new(scanner) unless terminated?(scanner)
|
16
|
+
return Parsr::Token.new(buffer)
|
17
|
+
end
|
18
|
+
nil
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
def parse_content(scanner)
|
23
|
+
scanner.matched if scanner.scan(/[^\\']+/)
|
24
|
+
end
|
25
|
+
|
26
|
+
def parse_escape(scanner)
|
27
|
+
return %q{'} if scanner.scan(/\\'/)
|
28
|
+
return %q{\\} if scanner.scan(/\\\\/)
|
29
|
+
return scanner.matched if scanner.scan(%r{\\[^'\\/]})
|
30
|
+
end
|
31
|
+
|
32
|
+
def terminated?(scanner)
|
33
|
+
scanner.scan(/'/)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Parsr::StringRule
|
2
|
+
|
3
|
+
ESCAPED_CHARS = {'\n' => "\n", '\b' => "\b", '\f' => "\f", '\r' => "\r", '\t' => "\t"}
|
4
|
+
|
5
|
+
class Unterminated < Parsr::SyntaxError
|
6
|
+
message 'unterminated string meets end of file'
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
def match(scanner)
|
12
|
+
if scanner.scan(/"/)
|
13
|
+
buffer = ''
|
14
|
+
while chunk = (parse_content(scanner) || parse_escape(scanner))
|
15
|
+
buffer << chunk
|
16
|
+
end
|
17
|
+
raise Unterminated.new(scanner) unless terminated?(scanner)
|
18
|
+
return Parsr::Token.new(buffer)
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def parse_content(scanner)
|
25
|
+
scanner.matched if scanner.scan(/[^\\"]+/)
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_escape(scanner)
|
29
|
+
return %q{"} if scanner.scan(/\\"/)
|
30
|
+
return %q{\\} if scanner.scan(/\\\\/)
|
31
|
+
return ESCAPED_CHARS[scanner.matched] if scanner.scan(/\\[bfnrt]/)
|
32
|
+
return [Integer("0x#{scanner.matched[2..-1]}")].pack('U') if scanner.scan(/\\u[0-9a-fA-F]{4}/)
|
33
|
+
end
|
34
|
+
|
35
|
+
def terminated?(scanner)
|
36
|
+
scanner.scan(/"/)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/lib/parsr/token.rb
ADDED
data/parsr.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "parsr"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "parsr"
|
7
|
+
s.version = Parsr::VERSION
|
8
|
+
s.authors = ["Jean Boussier"]
|
9
|
+
s.email = ["jean.boussier@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Simple parser to safe eval ruby literals}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_development_dependency "rspec"
|
19
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parsr::ArrayRule do
|
4
|
+
include_context 'rule'
|
5
|
+
|
6
|
+
describe '.match' do
|
7
|
+
|
8
|
+
it 'should be able to parse an empty array' do
|
9
|
+
match('[]').value.should be == []
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should yield to get its content values' do
|
13
|
+
values = [3, 2, 1]
|
14
|
+
|
15
|
+
result = match('[1,2,3]') do |scanner|
|
16
|
+
val = values.pop
|
17
|
+
scanner.scan(Regexp.new(val.to_s))
|
18
|
+
Parsr::Token.new(val)
|
19
|
+
end
|
20
|
+
|
21
|
+
result.value.should be == [1, 2, 3]
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should not be disturbed by a trailing comma' do
|
25
|
+
values = [3, 2, 1]
|
26
|
+
|
27
|
+
result = match('[1,2,3,]') do |scanner|
|
28
|
+
val = values.pop
|
29
|
+
scanner.scan(Regexp.new(val.to_s))
|
30
|
+
Parsr::Token.new(val) if val
|
31
|
+
end
|
32
|
+
|
33
|
+
result.value.should be == [1, 2, 3]
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should not be disturbed by spaces around commas' do
|
37
|
+
values = [3, 2, 1]
|
38
|
+
|
39
|
+
result = match("[ 1 ,\t2\r\n, 3 ]") do |scanner|
|
40
|
+
val = values.pop
|
41
|
+
scanner.scan(Regexp.new(val.to_s))
|
42
|
+
Parsr::Token.new(val)
|
43
|
+
end
|
44
|
+
|
45
|
+
result.value.should be == [1, 2, 3]
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should be able to parse a final unenclosed Hash' do
|
49
|
+
values = [6, 5, 4, 3, 2, 1]
|
50
|
+
|
51
|
+
result = match("[1, 2, 3 => 4, 5=>6]") do |scanner|
|
52
|
+
val = values.pop
|
53
|
+
scanner.scan(Regexp.new(val.to_s))
|
54
|
+
Parsr::Token.new(val) if val
|
55
|
+
end
|
56
|
+
|
57
|
+
result.value.should be == [1, 2, {3 => 4, 5 => 6}]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should be able to parse a final unenclosed Ruby1.9 Hash' do
|
61
|
+
values = [6, 5, 4, 2, 1]
|
62
|
+
|
63
|
+
result = match("[1, 2, foo: 4, 5=>6]") do |scanner|
|
64
|
+
val = values.pop
|
65
|
+
scanner.scan(/\s*/)
|
66
|
+
scanner.scan(Regexp.new(val.to_s))
|
67
|
+
Parsr::Token.new(val) if val
|
68
|
+
end
|
69
|
+
|
70
|
+
result.value.should be == [1, 2, {:foo => 4, 5 => 6}]
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parsr::FloatRule do
|
4
|
+
include_context 'rule'
|
5
|
+
|
6
|
+
describe '.match' do
|
7
|
+
|
8
|
+
it 'should not match integers' do
|
9
|
+
match('42').should be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should match a simple float' do
|
13
|
+
match('42.42').value.should be == 42.42
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should match positive signed floats' do
|
17
|
+
match('+42.42').value.should be == 42.42
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should match negative signed floats' do
|
21
|
+
match('-42.42').value.should be == -42.42
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should match floats with a implicit 0 unit' do
|
25
|
+
match('.42').value.should be == 0.42
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should match positive signed floats with a implicit 0 unit' do
|
29
|
+
match('+.42').value.should be == 0.42
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should match negative signed floats with a implicit 0 unit' do
|
33
|
+
match('-.42').value.should be == -0.42
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parsr::HashRule do
|
4
|
+
include_context 'rule'
|
5
|
+
|
6
|
+
describe '.match' do
|
7
|
+
|
8
|
+
it 'should be able to parse an empty hash' do
|
9
|
+
match('{}').value.should be == {}
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should yield to get its content values' do
|
13
|
+
values = [1, 2, 3, 4]
|
14
|
+
|
15
|
+
result = match("{1 => 2, 3 => 4}") do |scanner|
|
16
|
+
val = values.shift
|
17
|
+
scanner.scan(Regexp.new(Regexp.escape(val.inspect)))
|
18
|
+
Parsr::Token.new(val) if val
|
19
|
+
end
|
20
|
+
|
21
|
+
result.value.should be == {1 => 2, 3 => 4}
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should not be disturbed by a trailing comma' do
|
25
|
+
values = [1, 2, 3, 4]
|
26
|
+
|
27
|
+
result = match("{1 => 2, 3 => 4, }") do |scanner|
|
28
|
+
val = values.shift
|
29
|
+
scanner.scan(/\s*/)
|
30
|
+
scanner.scan(Regexp.new(Regexp.escape(val.inspect)))
|
31
|
+
scanner.scan(/\s*/)
|
32
|
+
Parsr::Token.new(val) if val
|
33
|
+
end
|
34
|
+
|
35
|
+
result.value.should be == {1 => 2, 3 => 4}
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should not be disturbed by spaces around commas' do
|
39
|
+
values = [1, 2, 3, 4]
|
40
|
+
|
41
|
+
result = match("{1\t=>\n\r 2, 3 => 4, }") do |scanner|
|
42
|
+
val = values.shift
|
43
|
+
scanner.scan(/\s*/)
|
44
|
+
scanner.scan(Regexp.new(Regexp.escape(val.inspect)))
|
45
|
+
scanner.scan(/\s*/)
|
46
|
+
Parsr::Token.new(val) if val
|
47
|
+
end
|
48
|
+
|
49
|
+
result.value.should be == {1 => 2, 3 => 4}
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should support Ruby 1.9 hash syntax' do
|
53
|
+
values = [1, 2, 4]
|
54
|
+
|
55
|
+
result = match("{1 => 2, foo: 4}") do |scanner|
|
56
|
+
val = values.shift
|
57
|
+
scanner.scan(/\s*/)
|
58
|
+
scanner.scan(Regexp.new(val.inspect))
|
59
|
+
Parsr::Token.new(val) if val
|
60
|
+
end
|
61
|
+
|
62
|
+
result.value.should be == {1 => 2, :foo => 4}
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parsr::IntegerRule do
|
4
|
+
include_context 'rule'
|
5
|
+
|
6
|
+
describe '.match' do
|
7
|
+
|
8
|
+
it 'should match digits' do
|
9
|
+
result = match('42')
|
10
|
+
result.should be_a(Parsr::Token)
|
11
|
+
result.value.should be == 42
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should match only digits' do
|
15
|
+
result = match('42abcd')
|
16
|
+
result.should be_a(Parsr::Token)
|
17
|
+
result.value.should be == 42
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should also match prepended sign' do
|
21
|
+
result = match('+42')
|
22
|
+
result.should be_a(Parsr::Token)
|
23
|
+
result.value.should be == 42
|
24
|
+
|
25
|
+
result = match('-42')
|
26
|
+
result.should be_a(Parsr::Token)
|
27
|
+
result.value.should be == -42
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should not return a Token if it does not match' do
|
31
|
+
result = match('AZIJjkdjfs42|')
|
32
|
+
result.should_not be_a(Parsr::Token)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Parsr::RawStringRule do
|
5
|
+
include_context 'rule'
|
6
|
+
|
7
|
+
describe '.match' do
|
8
|
+
|
9
|
+
it 'should match an empty string' do
|
10
|
+
match(%q{''}).value.should be == ''
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should match a simple word' do
|
14
|
+
match(%q{'foo'}).value.should be == 'foo'
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should match a simple sentence' do
|
18
|
+
match(%q{'foo bar'}).value.should be == 'foo bar'
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should match a simple sentence with punctuation' do
|
22
|
+
match(%q{'foo bar.,;$?!'}).value.should be == 'foo bar.,;$?!'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should match a simple string with unicode characters' do
|
26
|
+
match(%q{'éùäüûꀢ©◊√'}).value.should be == 'éùäüûꀢ©◊√'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should match a string containing double quotes' do
|
30
|
+
match(%q{'"'}).value.should be == '"'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should match a string containing a backslash' do
|
34
|
+
match(%q{'\n'}).value.should be == '\n'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should match a string containing an escaped backslash' do
|
38
|
+
match(%q{'\\\\'}).value.should be == '\\'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should match a string containing an escaped single quote' do
|
42
|
+
match(%q{'\\''}).value.should be == "'"
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should raise an Parsr::RawStringRule::Unterminated if raw string is not terminated' do
|
46
|
+
expect{
|
47
|
+
match(%q{'bryan\\'s kitchen})
|
48
|
+
}.to raise_error(Parsr::RawStringRule::Unterminated)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Parsr::StringRule do
|
5
|
+
include_context 'rule'
|
6
|
+
|
7
|
+
describe '.match' do
|
8
|
+
|
9
|
+
it 'should match an empty string' do
|
10
|
+
match(%q{""}).value.should be == ""
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should match a simple word' do
|
14
|
+
match(%q{"foo"}).value.should be == "foo"
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should match a simple sentence' do
|
18
|
+
match(%q{"foo bar"}).value.should be == "foo bar"
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should match a simple sentence with punctuation' do
|
22
|
+
match(%q{"foo bar.,;$?!"}).value.should be == "foo bar.,;$?!"
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should match a simple string with unicode characters' do
|
26
|
+
match(%{"éùäüûꀢ©◊√"}).value.should be == "éùäüûꀢ©◊√"
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should match a string containing a simple quotes' do
|
30
|
+
match(%q{"'"}).value.should be == "'"
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should match a string containing a backslash' do
|
34
|
+
match(%q{"\n"}).value.should be == "\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should match a string containing an escaped backslash' do
|
38
|
+
match(%q{"\\\\"}).value.should be == "\\"
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should match a string containing an escaped double quote' do
|
42
|
+
match('"\\""').value.should be == '"'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should raise an Parsr::RawStringRule::Unterminated if raw string is not terminated' do
|
46
|
+
expect{
|
47
|
+
match(%q{"bryan\\"s kitchen})
|
48
|
+
}.to raise_error
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parsr::SymbolRule do
|
4
|
+
include_context 'rule'
|
5
|
+
|
6
|
+
it 'should match identifiers' do
|
7
|
+
match(':_').value.should be == :_
|
8
|
+
match(':foo').value.should be == :foo
|
9
|
+
match(':bar42').value.should be == :bar42
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should not match invalid identitifers' do
|
13
|
+
match(':42').should be_nil
|
14
|
+
match(':-').should be_nil
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/spec/parsr_spec.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Parsr do
|
4
|
+
|
5
|
+
it 'should have a VERSION' do
|
6
|
+
Parsr::VERSION.should be_a(String)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#parse' do
|
10
|
+
|
11
|
+
let(:first_rule) { mock(:rule) }
|
12
|
+
|
13
|
+
let(:second_rule) { mock(:rule) }
|
14
|
+
|
15
|
+
let(:rules) { [first_rule, second_rule] }
|
16
|
+
|
17
|
+
subject{ Parsr.new(*rules) }
|
18
|
+
|
19
|
+
context 'call #match on all its rules' do
|
20
|
+
|
21
|
+
it 'should raise an IllegalValue if none of theses return a token' do
|
22
|
+
first_rule.should_receive(:match).and_return(true)
|
23
|
+
second_rule.should_receive(:match).and_return(false)
|
24
|
+
expect{
|
25
|
+
subject.parse('foo')
|
26
|
+
}.to raise_error(Parsr::IllegalValue)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should return first matched token value' do
|
30
|
+
first_rule.should_receive(:match).and_return(true)
|
31
|
+
second_rule.should_receive(:match).and_return(Parsr::Token.new(42))
|
32
|
+
subject.parse('foo').should be == 42
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '.safe_literal_eval' do
|
40
|
+
|
41
|
+
it 'should be able to parse some complexes literals' do
|
42
|
+
Parsr::safe_literal_eval(%q{
|
43
|
+
[1, "2", foo: {egg: 'spam', 'bar,' => 'plop'}, 3 => 4.2]
|
44
|
+
}).should be == [1, "2", {:foo => {:egg => 'spam', 'bar,' => 'plop'}, 3 => 4.2}]
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should be able to parse an array included in another array' do
|
48
|
+
Parsr::safe_literal_eval(%q{
|
49
|
+
[1, [2], 3]
|
50
|
+
}).should be == [1, [2], 3]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: parsr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jean Boussier
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-13 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70315696184660 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70315696184660
|
25
|
+
description:
|
26
|
+
email:
|
27
|
+
- jean.boussier@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files: []
|
31
|
+
files:
|
32
|
+
- .gitignore
|
33
|
+
- .rspec
|
34
|
+
- Gemfile
|
35
|
+
- Rakefile
|
36
|
+
- lib/parsr.rb
|
37
|
+
- lib/parsr/array_rule.rb
|
38
|
+
- lib/parsr/float_rule.rb
|
39
|
+
- lib/parsr/hash_rule.rb
|
40
|
+
- lib/parsr/integer_rule.rb
|
41
|
+
- lib/parsr/raw_string_rule.rb
|
42
|
+
- lib/parsr/string_rule.rb
|
43
|
+
- lib/parsr/symbol_rule.rb
|
44
|
+
- lib/parsr/token.rb
|
45
|
+
- lib/parsr/version.rb
|
46
|
+
- parsr.gemspec
|
47
|
+
- spec/parsr/array_rule_spec.rb
|
48
|
+
- spec/parsr/float_rule_spec.rb
|
49
|
+
- spec/parsr/hash_rule_spec.rb
|
50
|
+
- spec/parsr/integer_rule_spec.rb
|
51
|
+
- spec/parsr/raw_string_rule_spec.rb
|
52
|
+
- spec/parsr/string_rule_spec.rb
|
53
|
+
- spec/parsr/symbol_rule_spec.rb
|
54
|
+
- spec/parsr_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
- spec/support/rule_shared_context.rb
|
57
|
+
homepage: ''
|
58
|
+
licenses: []
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ! '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ! '>='
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 1.8.6
|
78
|
+
signing_key:
|
79
|
+
specification_version: 3
|
80
|
+
summary: Simple parser to safe eval ruby literals
|
81
|
+
test_files:
|
82
|
+
- spec/parsr/array_rule_spec.rb
|
83
|
+
- spec/parsr/float_rule_spec.rb
|
84
|
+
- spec/parsr/hash_rule_spec.rb
|
85
|
+
- spec/parsr/integer_rule_spec.rb
|
86
|
+
- spec/parsr/raw_string_rule_spec.rb
|
87
|
+
- spec/parsr/string_rule_spec.rb
|
88
|
+
- spec/parsr/symbol_rule_spec.rb
|
89
|
+
- spec/parsr_spec.rb
|
90
|
+
- spec/spec_helper.rb
|
91
|
+
- spec/support/rule_shared_context.rb
|