parsr 0.0.1
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/.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
|