ptolemy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +7 -0
- data/LICENSE +20 -0
- data/README.md +5 -0
- data/Rakefile +9 -0
- data/lib/ptolemy/exceptions.rb +7 -0
- data/lib/ptolemy/nodes.rb +142 -0
- data/lib/ptolemy/parser.rb +39 -0
- data/lib/ptolemy/toml.tt +73 -0
- data/lib/ptolemy/version.rb +5 -0
- data/lib/ptolemy.rb +19 -0
- data/ptolemy.gemspec +26 -0
- data/spec/example.toml +36 -0
- data/spec/hard_example.toml +21 -0
- data/spec/parser_spec.rb +122 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/matchers/have_value.rb +18 -0
- data/spec/toml_parser_spec.rb +235 -0
- metadata +153 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Natansh Verma
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'ptolemy/exceptions'
|
6
|
+
|
7
|
+
module Ptolemy
|
8
|
+
module TOML
|
9
|
+
|
10
|
+
class Toml < Treetop:: Runtime::SyntaxNode
|
11
|
+
def to_value
|
12
|
+
result = {}
|
13
|
+
# Keep track of location under which all the key value pairs must be
|
14
|
+
# stored. It gets modified when a key group is encountered.
|
15
|
+
current = result
|
16
|
+
# Store all key groups detected so that duplicates can be discovered.
|
17
|
+
key_group_set = Set.new
|
18
|
+
|
19
|
+
list.elements.map do |item|
|
20
|
+
elem = item.elem
|
21
|
+
if elem.is_a? KeyGroup
|
22
|
+
# Reset current to root level. Key value groups always specify
|
23
|
+
# nesting from root
|
24
|
+
current = result
|
25
|
+
key_group = elem.to_value
|
26
|
+
if key_group_set.include? key_group
|
27
|
+
raise ParseError, "Already defined [#{key_group}] before."
|
28
|
+
end
|
29
|
+
key_group_set.add key_group
|
30
|
+
# If the key group is x.y.z.w, create the whole nested structure
|
31
|
+
# in case it doesn't exist already.
|
32
|
+
key_group.each do |key|
|
33
|
+
current[key] = {} if current[key].nil?
|
34
|
+
current = current[key]
|
35
|
+
end
|
36
|
+
else
|
37
|
+
key, value = elem.to_value
|
38
|
+
# Set value in hash, if it hasn't been set already.
|
39
|
+
if current[key].nil?
|
40
|
+
current[key] = value
|
41
|
+
else
|
42
|
+
raise ParseError, "Duplicate value for key:#{key}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class KeyGroup < Treetop::Runtime::SyntaxNode
|
51
|
+
def to_value
|
52
|
+
result = [keys.key.to_value]
|
53
|
+
keys.remaining_keys.elements.each do |elem|
|
54
|
+
result << elem.key.to_value
|
55
|
+
end
|
56
|
+
result
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class KeyValue < Treetop::Runtime::SyntaxNode
|
61
|
+
def to_value
|
62
|
+
[key.to_value, value.to_value]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Key < Treetop::Runtime::SyntaxNode
|
67
|
+
def to_value
|
68
|
+
text_value
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Comment < Treetop::Runtime::SyntaxNode
|
73
|
+
end
|
74
|
+
|
75
|
+
class ArrayLiteral < Treetop::Runtime::SyntaxNode
|
76
|
+
def to_value
|
77
|
+
result = list.elements.map do|elem|
|
78
|
+
elem.item.to_value
|
79
|
+
end
|
80
|
+
unless last.empty?
|
81
|
+
result << last.item.to_value
|
82
|
+
end
|
83
|
+
result
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class IntegerLiteral < Treetop::Runtime::SyntaxNode
|
88
|
+
def to_value
|
89
|
+
text_value.to_i
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class FloatLiteral < Treetop::Runtime::SyntaxNode
|
94
|
+
def to_value
|
95
|
+
text_value.to_f
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class BooleanLiteral < Treetop::Runtime::SyntaxNode
|
100
|
+
def to_value
|
101
|
+
text_value == 'true'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class StringLiteral < Treetop::Runtime::SyntaxNode
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
@@unescape = {
|
110
|
+
'\b' => "\b",
|
111
|
+
'\t' => "\t",
|
112
|
+
'\n' => "\n",
|
113
|
+
'\\' => "\\",
|
114
|
+
'\f' => "\f",
|
115
|
+
'\r' => "\r",
|
116
|
+
'\"' => "\"",
|
117
|
+
'\/' => "\/"
|
118
|
+
}
|
119
|
+
|
120
|
+
public
|
121
|
+
|
122
|
+
def to_value
|
123
|
+
elem = string.text_value
|
124
|
+
# Unescape unicode characters of the form \uXXXX
|
125
|
+
# Pack the XXXX string as hexadecimal ---> Unpack to array of integers
|
126
|
+
# ---> Pack back into unicode
|
127
|
+
elem.gsub!(/\\u([\da-fA-F]{4})/) {|m| [$1].pack("H*").unpack("n*").pack("U*")}
|
128
|
+
# Unescape known escape characters
|
129
|
+
elem.gsub!(/(\\[btn\\fr"\/])/) {|m| @@unescape[m]}
|
130
|
+
elem
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
class DateLiteral < Treetop::Runtime::SyntaxNode
|
135
|
+
def to_value
|
136
|
+
args = [year, month, day, hour, min, sec].map(&:text_value).map(&:to_i)
|
137
|
+
DateTime.new(*args)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'treetop'
|
4
|
+
|
5
|
+
require 'ptolemy/nodes'
|
6
|
+
require 'ptolemy/exceptions'
|
7
|
+
|
8
|
+
module Ptolemy
|
9
|
+
|
10
|
+
class Parser
|
11
|
+
|
12
|
+
# Load TOML grammar into Treetop
|
13
|
+
base_path = File.expand_path File.dirname(__FILE__)
|
14
|
+
Treetop.load File.join(base_path, 'toml.tt')
|
15
|
+
|
16
|
+
# Treetop would've generated a parser for TOML based on the grammar
|
17
|
+
@@parser = TOMLParser.new
|
18
|
+
|
19
|
+
def self.parse data
|
20
|
+
# Data should be a valid UTF-8 encoded string.
|
21
|
+
if data.encoding != Encoding::UTF_8
|
22
|
+
raise ParseError, "Input is not UTF-8 encoded"
|
23
|
+
end
|
24
|
+
unless data.valid_encoding?
|
25
|
+
raise ParseError, "Input contains invalid UTF-8 byte sequence"
|
26
|
+
end
|
27
|
+
|
28
|
+
tree = @@parser.parse data
|
29
|
+
|
30
|
+
if tree.nil?
|
31
|
+
raise ParseError, "Parse error at offset: #{@@parser.index}"
|
32
|
+
end
|
33
|
+
|
34
|
+
tree.to_value
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/ptolemy/toml.tt
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
module Ptolemy
|
2
|
+
|
3
|
+
grammar TOML
|
4
|
+
|
5
|
+
rule toml
|
6
|
+
list:((separator* elem:(key_group / key_value))*) separator* <Toml>
|
7
|
+
end
|
8
|
+
|
9
|
+
rule key_group
|
10
|
+
'[' keys:(key remaining_keys:('.' key)*) ']' <KeyGroup>
|
11
|
+
end
|
12
|
+
|
13
|
+
rule key_value
|
14
|
+
key space? '=' space? value <KeyValue>
|
15
|
+
end
|
16
|
+
|
17
|
+
rule key
|
18
|
+
[a-zA-Z_] [a-zA-Z0-9_?#!]* <Key>
|
19
|
+
end
|
20
|
+
|
21
|
+
rule value
|
22
|
+
(boolean / date / float / integer / string / array)
|
23
|
+
end
|
24
|
+
|
25
|
+
rule separator
|
26
|
+
comment / space / newline
|
27
|
+
end
|
28
|
+
|
29
|
+
rule newline
|
30
|
+
"\n" / "\r\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
rule comment
|
34
|
+
'#' (!newline .)* <Comment>
|
35
|
+
end
|
36
|
+
|
37
|
+
rule array
|
38
|
+
'[' list:(separator* item:string separator* ',')* separator* last:(separator* item:string separator*)? ']' <ArrayLiteral> /
|
39
|
+
'[' list:(separator* item:date separator* ',')* separator* last:(separator* item:date separator*)? ']' <ArrayLiteral> /
|
40
|
+
'[' list:(separator* item:float separator* ',')* separator* last:(separator* item:float separator*)? ']' <ArrayLiteral> /
|
41
|
+
'[' list:(separator* item:integer separator* ',')* separator* last:(separator* item:integer separator*)? ']' <ArrayLiteral> /
|
42
|
+
'[' list:(separator* item:boolean separator* ',')* separator* last:(separator* item:boolean separator*)? ']' <ArrayLiteral> /
|
43
|
+
'[' list:(separator* item:array separator* ',')* separator* last:(separator* item:array separator*)? ']' <ArrayLiteral>
|
44
|
+
end
|
45
|
+
|
46
|
+
rule string
|
47
|
+
'"' string:([^"\\] / "\\" . )* '"' <StringLiteral>
|
48
|
+
end
|
49
|
+
|
50
|
+
rule date
|
51
|
+
year:([0-9] 4..4) '-' month:([0-9] 2..2) '-' day:([0-9] 2..2)
|
52
|
+
'T' hour:([0-9] 2..2) ':' min:([0-9] 2..2) ':' sec:([0-9] 2..2) 'Z' <DateLiteral>
|
53
|
+
end
|
54
|
+
|
55
|
+
rule float
|
56
|
+
('+' / '-')? [0-9]+ ('.' [0-9]+) <FloatLiteral>
|
57
|
+
end
|
58
|
+
|
59
|
+
rule integer
|
60
|
+
('+' / '-')? [0-9]+ <IntegerLiteral>
|
61
|
+
end
|
62
|
+
|
63
|
+
rule boolean
|
64
|
+
('true' <BooleanLiteral> / 'false' <BooleanLiteral>)
|
65
|
+
end
|
66
|
+
|
67
|
+
rule space
|
68
|
+
[ \t]+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
data/lib/ptolemy.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require "ptolemy/version"
|
4
|
+
require "ptolemy/parser"
|
5
|
+
|
6
|
+
module Ptolemy
|
7
|
+
|
8
|
+
def self.parse data
|
9
|
+
Parser.parse data
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse_file filename
|
13
|
+
File.open filename, 'r:utf-8' do |file|
|
14
|
+
# TODO: Should the check for valid UTF-8 be done over here?
|
15
|
+
return Parser.parse file.read
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/ptolemy.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/ptolemy/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Natansh Verma"]
|
6
|
+
gem.email = ["natansh.verma@gmail.com"]
|
7
|
+
gem.description = %q{Ptolemy is a TOML parser.}
|
8
|
+
gem.summary = %q{Ptolemy is a TOML parser.}
|
9
|
+
gem.homepage = "http://www.github.com/natansh/ptolemy"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\) - %w(.gitignore ptolemy.jpg)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "ptolemy"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Ptolemy::VERSION
|
17
|
+
|
18
|
+
gem.required_ruby_version = ">= 1.9.3"
|
19
|
+
|
20
|
+
gem.add_dependency "treetop"
|
21
|
+
|
22
|
+
gem.add_development_dependency "rspec"
|
23
|
+
gem.add_development_dependency "rake"
|
24
|
+
gem.add_development_dependency "guard"
|
25
|
+
gem.add_development_dependency "guard-rspec"
|
26
|
+
end
|
data/spec/example.toml
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# By Tom Preston-Werner (@mojombo)
|
2
|
+
# This is a TOML document. Boom.
|
3
|
+
|
4
|
+
title = "TOML Example"
|
5
|
+
|
6
|
+
[owner]
|
7
|
+
name = "Tom Preston-Werner"
|
8
|
+
organization = "GitHub"
|
9
|
+
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
10
|
+
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
11
|
+
|
12
|
+
[database]
|
13
|
+
server = "192.168.1.1"
|
14
|
+
ports = [ 8001, 8001, 8002 ]
|
15
|
+
connection_max = 5000
|
16
|
+
enabled = true
|
17
|
+
|
18
|
+
[servers]
|
19
|
+
|
20
|
+
# You can indent as you please. Tabs or spaces. TOML don't care.
|
21
|
+
[servers.alpha]
|
22
|
+
ip = "10.0.0.1"
|
23
|
+
dc = "eqdc10"
|
24
|
+
|
25
|
+
[servers.beta]
|
26
|
+
ip = "10.0.0.2"
|
27
|
+
dc = "eqdc10"
|
28
|
+
|
29
|
+
[clients]
|
30
|
+
data = [ ["gamma", "delta"], [1, 2] ]
|
31
|
+
|
32
|
+
# Line breaks are OK when inside arrays
|
33
|
+
hosts = [
|
34
|
+
"alpha",
|
35
|
+
"omega"
|
36
|
+
]
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Test file for TOML
|
2
|
+
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
3
|
+
# This part you'll really hate
|
4
|
+
|
5
|
+
[the]
|
6
|
+
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
7
|
+
|
8
|
+
[the.hard]
|
9
|
+
test_array = [ "] ", " # "] # ] There you go, parse this!
|
10
|
+
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
11
|
+
# You didn't think it'd as easy as chucking out the last #, did you?
|
12
|
+
another_test_string = " Same thing, but with a string #"
|
13
|
+
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
14
|
+
# Things will get harder
|
15
|
+
|
16
|
+
[the.hard.bit#]
|
17
|
+
what? = "You don't think some user won't do that?"
|
18
|
+
multi_line_array = [
|
19
|
+
"]",
|
20
|
+
# ] Oh yes I did
|
21
|
+
]
|
data/spec/parser_spec.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative 'spec_helper'
|
4
|
+
|
5
|
+
describe Ptolemy::Parser do
|
6
|
+
|
7
|
+
describe "#parse" do
|
8
|
+
describe 'Valid TOML input' do
|
9
|
+
it 'should parse TOML specification file' do
|
10
|
+
filename = 'example.toml'
|
11
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), filename))
|
12
|
+
result = Ptolemy.parse_file(path)
|
13
|
+
|
14
|
+
result['title'].should eql('TOML Example')
|
15
|
+
|
16
|
+
owner = result['owner']
|
17
|
+
owner.should be_a_kind_of(Hash)
|
18
|
+
owner.should eql({
|
19
|
+
'name' => 'Tom Preston-Werner',
|
20
|
+
'organization' => 'GitHub',
|
21
|
+
'bio' => "GitHub Cofounder & CEO\nLikes tater tots and beer.",
|
22
|
+
# Test for Dates
|
23
|
+
'dob' => DateTime.parse('1979-05-27T07:32:00Z')
|
24
|
+
})
|
25
|
+
|
26
|
+
database = result['database']
|
27
|
+
database.should be_a_kind_of(Hash)
|
28
|
+
database['ports'].should be_a_kind_of(Array)
|
29
|
+
database.should eql({
|
30
|
+
'server' => "192.168.1.1",
|
31
|
+
'ports' => [ 8001, 8001, 8002 ],
|
32
|
+
'connection_max' => 5000,
|
33
|
+
'enabled' => true
|
34
|
+
})
|
35
|
+
|
36
|
+
servers = result['servers']
|
37
|
+
servers.should be_a_kind_of(Hash)
|
38
|
+
servers.keys.sort!.should eql ['alpha', 'beta']
|
39
|
+
|
40
|
+
alpha = servers['alpha']
|
41
|
+
alpha.should be_a_kind_of(Hash)
|
42
|
+
|
43
|
+
beta = servers['beta']
|
44
|
+
beta.should be_a_kind_of(Hash)
|
45
|
+
|
46
|
+
clients = result['clients']
|
47
|
+
clients['data'].should eql([["gamma", "delta"], [1, 2]])
|
48
|
+
|
49
|
+
hosts = clients['hosts']
|
50
|
+
hosts.should eql(["alpha", "omega"])
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should parse hard TOML example file' do
|
54
|
+
filename = 'hard_example.toml'
|
55
|
+
path = File.expand_path(File.join(File.dirname(__FILE__), filename))
|
56
|
+
result = Ptolemy.parse_file(path)
|
57
|
+
|
58
|
+
the = result['the']
|
59
|
+
the.should be_a_kind_of(Hash)
|
60
|
+
the['test_string'].should eql("You'll hate me after this - #")
|
61
|
+
hard = the['hard']
|
62
|
+
hard['test_array'].should eql(["] ", " # "])
|
63
|
+
hard['test_array2'].should eql([ "Test #11 ]proved that",
|
64
|
+
"Experiment #9 was a success" ])
|
65
|
+
hard['another_test_string'].should eql(" Same thing, but with a string #")
|
66
|
+
hard['harder_test_string'].should eql(
|
67
|
+
" And when \"'s are in the string, along with # \"")
|
68
|
+
|
69
|
+
bit = hard['bit#']
|
70
|
+
bit.should be_a_kind_of(Hash)
|
71
|
+
bit['what?'].should eql("You don't think some user won't do that?")
|
72
|
+
bit['multi_line_array'].should eql([ "]"])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'Invalid TOML input' do
|
77
|
+
it 'should give error if anything other than tabs/space/nl/comment'\
|
78
|
+
'is present after key group on line' do
|
79
|
+
input = "[error] if you didn't catch this, your parser is broken"
|
80
|
+
expect{Ptolemy.parse(input)}.to raise_error(Ptolemy::ParseError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should give error if anything other than tabs/space/nl/comment'\
|
84
|
+
'is present after key value pair on line' do
|
85
|
+
input = 'string = "Hello World!" Have fun'
|
86
|
+
expect{Ptolemy.parse(input)}.to raise_error(Ptolemy::ParseError)
|
87
|
+
input = "number = 3.14 pi <--again forgot the #"
|
88
|
+
expect{Ptolemy.parse(input)}.to raise_error(Ptolemy::ParseError)
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should give an error for a malformed multiline array' do
|
92
|
+
input = <<END
|
93
|
+
array = [
|
94
|
+
"This might most likely happen in multiline arrays",
|
95
|
+
Like here,
|
96
|
+
"or here,
|
97
|
+
and here"
|
98
|
+
] End of array comment, forgot the #
|
99
|
+
END
|
100
|
+
expect{Ptolemy.parse(input)}.to raise_error(Ptolemy::ParseError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'Encoding' do
|
105
|
+
it 'should give an error if input is not UTF-8 encoded' do
|
106
|
+
input = 'hello = "world"'
|
107
|
+
input.encode!(Encoding::ASCII)
|
108
|
+
input.encoding.name.should eql('US-ASCII')
|
109
|
+
expect{Ptolemy.parse(input)}.to \
|
110
|
+
raise_error(Ptolemy::ParseError, "Input is not UTF-8 encoded")
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should give an error if input has invalid byte sequence' do
|
114
|
+
input = "hello = 'hi \255'"
|
115
|
+
input.encoding.name.should eql('UTF-8')
|
116
|
+
expect{Ptolemy.parse(input)}.to \
|
117
|
+
raise_error(Ptolemy::ParseError, "Input contains invalid UTF-8 byte sequence")
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
RSpec::Matchers.define :have_value do |expected|
|
2
|
+
match do |actual|
|
3
|
+
actual.respond_to?(:to_value) && actual.to_value == expected
|
4
|
+
end
|
5
|
+
|
6
|
+
failure_message_for_should do |actual|
|
7
|
+
"Expected that #{actual.class}(text value: #{actual.text_value}, value: #{actual.to_value}) should have value #{expected}"
|
8
|
+
end
|
9
|
+
|
10
|
+
failure_message_for_should_not do |actual|
|
11
|
+
"Expected that #{actual.class}(text value: #{actual.text_value}, value: #{actual.to_value}) should not have value #{expected}"
|
12
|
+
end
|
13
|
+
|
14
|
+
description do
|
15
|
+
"have the value #{expected}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative 'spec_helper'
|
4
|
+
|
5
|
+
describe Ptolemy::TOMLParser do
|
6
|
+
|
7
|
+
before :all do
|
8
|
+
@parser = Ptolemy::TOMLParser.new
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'Literals' do
|
12
|
+
describe 'String' do
|
13
|
+
it 'should parse an empty string' do
|
14
|
+
result = @parser.parse('""', root: :string)
|
15
|
+
result.should_not be_nil
|
16
|
+
result.should be_a_kind_of(Ptolemy::TOML::StringLiteral)
|
17
|
+
result.should have_value("")
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should parse a simple string' do
|
21
|
+
result = @parser.parse('"This is a string"', root: :string)
|
22
|
+
result.should_not be_nil
|
23
|
+
result.should be_a_kind_of(Ptolemy::TOML::StringLiteral)
|
24
|
+
result.should have_value("This is a string")
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should parse a string having escaped characters' do
|
28
|
+
result = @parser.parse('"This is an \n\t \bescaped string."', root: :string)
|
29
|
+
result.should_not be_nil
|
30
|
+
result.should be_a_kind_of(Ptolemy::TOML::StringLiteral)
|
31
|
+
result.should have_value("This is an \n\t \bescaped string.")
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should parse a string having unicode symbols' do
|
35
|
+
result = @parser.parse('"This is a string containing úƞĩƈōƌě symbols."', root: :string)
|
36
|
+
result.should_not be_nil
|
37
|
+
result.should be_a_kind_of(Ptolemy::TOML::StringLiteral)
|
38
|
+
result.should have_value( "This is a string containing úƞĩƈōƌě symbols.")
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should parse a string having escaped unicode characters' do
|
42
|
+
result = @parser.parse('"This is a unicode string containing linefeed as \u000A."', root: :string)
|
43
|
+
result.should_not be_nil
|
44
|
+
result.should be_a_kind_of(Ptolemy::TOML::StringLiteral)
|
45
|
+
result.should have_value( "This is a unicode string containing linefeed as \n.")
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should not parse a string with single quotes' do
|
49
|
+
result = @parser.parse("'Hello World!'", root: :string)
|
50
|
+
result.should be_nil
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should parse a correct float' do
|
55
|
+
['-0.0', '1.4', '123.2', '+1.1', '+123.9' ].each do |float|
|
56
|
+
result = @parser.parse(float, root: :float)
|
57
|
+
result.should_not be_nil
|
58
|
+
result.should be_a_kind_of(Ptolemy::TOML::FloatLiteral)
|
59
|
+
result.should have_value(float.to_f)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should parse a correct integers' do
|
64
|
+
['-1', '1', '123', '+1', '+123' ].each do |integer|
|
65
|
+
result = @parser.parse(integer, root: :integer)
|
66
|
+
result.should_not be_nil
|
67
|
+
result.should be_a_kind_of(Ptolemy::TOML::IntegerLiteral)
|
68
|
+
result.should have_value(integer.to_i)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should parse a correct boolean' do
|
73
|
+
['true', 'false' ].each do |boolean|
|
74
|
+
result = @parser.parse(boolean, root: :boolean)
|
75
|
+
result.should_not be_nil
|
76
|
+
result.should be_a_kind_of(Ptolemy::TOML::BooleanLiteral)
|
77
|
+
result.should have_value((boolean == 'true'))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should parse a correct date' do
|
82
|
+
result = @parser.parse('1979-05-27T07:32:00Z', root: :date)
|
83
|
+
result.should_not be_nil
|
84
|
+
result.should be_a_kind_of(Ptolemy::TOML::DateLiteral)
|
85
|
+
result.should have_value(DateTime.new(1979, 5, 27, 7, 32, 0))
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should not parse an incorrect date' do
|
89
|
+
result = @parser.parse('1979-05-27T07:32:00', root: :date)
|
90
|
+
result.should be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'Array' do
|
95
|
+
it 'should parse a simple array' do
|
96
|
+
array_string = '[1, 2, 3]'
|
97
|
+
result = @parser.parse(array_string, root: :array)
|
98
|
+
result.should_not be_nil
|
99
|
+
result.should be_a_kind_of(Ptolemy::TOML::ArrayLiteral)
|
100
|
+
result.should have_value([1, 2, 3])
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should parse a complex multi-line array' do
|
104
|
+
array_string = <<AS_END
|
105
|
+
[ # Evil, must say
|
106
|
+
1,
|
107
|
+
|
108
|
+
2 ,
|
109
|
+
# Wait, you can put comments anywhere?
|
110
|
+
|
111
|
+
4 ,
|
112
|
+
# What the... is this right?
|
113
|
+
]
|
114
|
+
AS_END
|
115
|
+
array_string.chomp! #Heredoc introduces a newline at the end
|
116
|
+
|
117
|
+
result = @parser.parse(array_string, root: :array)
|
118
|
+
result.should_not be_nil
|
119
|
+
result.should be_a_kind_of(Ptolemy::TOML::ArrayLiteral)
|
120
|
+
result.should have_value([1, 2, 4])
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'should parse a nested array' do
|
124
|
+
array_string = <<AS_END
|
125
|
+
[
|
126
|
+
[1, 2,
|
127
|
+
# Nested comment, yeah!
|
128
|
+
3 ],
|
129
|
+
["hello", "world"
|
130
|
+
# Now this is a doozy!
|
131
|
+
]
|
132
|
+
]
|
133
|
+
AS_END
|
134
|
+
array_string.chomp! #Heredoc introduces a newline at the end
|
135
|
+
|
136
|
+
result = @parser.parse(array_string, root: :array)
|
137
|
+
result.should_not be_nil
|
138
|
+
result.should be_a_kind_of(Ptolemy::TOML::ArrayLiteral)
|
139
|
+
result.should have_value([[1, 2, 3], ["hello", "world"]])
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'comment' do
|
144
|
+
it 'should parse a correct comment' do
|
145
|
+
result = @parser.parse('# This is a comment', root: :comment)
|
146
|
+
result.should_not be_nil
|
147
|
+
result.text_value.should eql '# This is a comment'
|
148
|
+
result.should be_a_kind_of(Ptolemy::TOML::Comment)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe 'Constructs' do
|
153
|
+
it 'should parse a key' do
|
154
|
+
result = @parser.parse('hello', root: :key)
|
155
|
+
result.should_not be_nil
|
156
|
+
result.should be_a_kind_of(Ptolemy::TOML::Key)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should parse a value' do
|
160
|
+
examples = {
|
161
|
+
'"hello"' => [Ptolemy::TOML::StringLiteral, 'hello'],
|
162
|
+
'-1.0' => [Ptolemy::TOML::FloatLiteral, -1.0],
|
163
|
+
'1' => [Ptolemy::TOML::IntegerLiteral, 1],
|
164
|
+
'true' => [Ptolemy::TOML::BooleanLiteral, true],
|
165
|
+
'1979-05-27T07:32:00Z' => [Ptolemy::TOML::DateLiteral, DateTime.new(1979, 5, 27, 7, 32, 0)]
|
166
|
+
}
|
167
|
+
|
168
|
+
examples.each do |input, details|
|
169
|
+
klass, value = details
|
170
|
+
result = @parser.parse(input, root: :value)
|
171
|
+
result.should_not be_nil
|
172
|
+
result.should be_a_kind_of(klass)
|
173
|
+
result.should have_value(value)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should parse a correct key value' do
|
178
|
+
examples = {
|
179
|
+
'string' => '"Hello"',
|
180
|
+
'date' => '1979-05-27T07:32:00Z',
|
181
|
+
'integer' => '114',
|
182
|
+
'float' => '1.0'
|
183
|
+
}
|
184
|
+
examples.each do |key, value|
|
185
|
+
result = @parser.parse("#{key} = \t #{value}", root: :key_value)
|
186
|
+
result.should_not be_nil
|
187
|
+
result.key.text_value.should eql key
|
188
|
+
result.value.text_value.should eql value
|
189
|
+
result.should be_a_kind_of(Ptolemy::TOML::KeyValue)
|
190
|
+
result.should respond_to(:to_value)
|
191
|
+
result.to_value[0].should eql key
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should parse a correct key group' do
|
196
|
+
result = @parser.parse('[key.hello.while]', root: :key_group)
|
197
|
+
result.should_not be_nil
|
198
|
+
result.should be_a_kind_of(Ptolemy::TOML::KeyGroup)
|
199
|
+
result.should have_value(['key', 'hello', 'while'])
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'should not parse a key group having empty key' do
|
203
|
+
result = @parser.parse('[key..while]', root: :key_group)
|
204
|
+
result.should be_nil
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should parse valid toml' do
|
208
|
+
toml_string = <<TS_END
|
209
|
+
description = "This is valid TOML."
|
210
|
+
[personal.details]
|
211
|
+
name = "John Doe"
|
212
|
+
height = 186
|
213
|
+
date_of_birth = 1990-04-12T05:30:00Z
|
214
|
+
favorites = ["sports", "gaming"]
|
215
|
+
TS_END
|
216
|
+
result = @parser.parse(toml_string)
|
217
|
+
result.should_not be_nil
|
218
|
+
result.should be_a_kind_of(Ptolemy::TOML::Toml)
|
219
|
+
result.should respond_to(:to_value)
|
220
|
+
|
221
|
+
toml = result.to_value
|
222
|
+
toml['description'].should eql('This is valid TOML.')
|
223
|
+
toml['personal'].should be_a_kind_of(Hash)
|
224
|
+
details = toml['personal']['details']
|
225
|
+
details.should_not be_nil
|
226
|
+
details.should be_a_kind_of(Hash)
|
227
|
+
details['name'].should eql('John Doe')
|
228
|
+
details['height'].should eql(186)
|
229
|
+
details['date_of_birth'].should eql(DateTime.new(1990, 4, 12, 5, 30, 0))
|
230
|
+
details['favorites'].should be_a_kind_of(Array)
|
231
|
+
details['favorites'].should eql(["sports", "gaming"])
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
metadata
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ptolemy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Natansh Verma
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-09-12 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: treetop
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rake
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description: Ptolemy is a TOML parser.
|
95
|
+
email:
|
96
|
+
- natansh.verma@gmail.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .travis.yml
|
102
|
+
- Gemfile
|
103
|
+
- Guardfile
|
104
|
+
- LICENSE
|
105
|
+
- README.md
|
106
|
+
- Rakefile
|
107
|
+
- lib/ptolemy.rb
|
108
|
+
- lib/ptolemy/exceptions.rb
|
109
|
+
- lib/ptolemy/nodes.rb
|
110
|
+
- lib/ptolemy/parser.rb
|
111
|
+
- lib/ptolemy/toml.tt
|
112
|
+
- lib/ptolemy/version.rb
|
113
|
+
- ptolemy.gemspec
|
114
|
+
- spec/example.toml
|
115
|
+
- spec/hard_example.toml
|
116
|
+
- spec/parser_spec.rb
|
117
|
+
- spec/spec_helper.rb
|
118
|
+
- spec/support/matchers/have_value.rb
|
119
|
+
- spec/toml_parser_spec.rb
|
120
|
+
homepage: http://www.github.com/natansh/ptolemy
|
121
|
+
licenses: []
|
122
|
+
post_install_message:
|
123
|
+
rdoc_options: []
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 1.9.3
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
segments:
|
139
|
+
- 0
|
140
|
+
hash: -1717741281203789886
|
141
|
+
requirements: []
|
142
|
+
rubyforge_project:
|
143
|
+
rubygems_version: 1.8.24
|
144
|
+
signing_key:
|
145
|
+
specification_version: 3
|
146
|
+
summary: Ptolemy is a TOML parser.
|
147
|
+
test_files:
|
148
|
+
- spec/example.toml
|
149
|
+
- spec/hard_example.toml
|
150
|
+
- spec/parser_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/matchers/have_value.rb
|
153
|
+
- spec/toml_parser_spec.rb
|