ptolemy 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/.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
|