toml-rb-hs 1.1.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.tar.gz.sig +0 -0
- data/README.md +101 -0
- data/Rakefile +6 -0
- data/lib/toml-rb.rb +106 -0
- data/lib/toml-rb/array.rb +14 -0
- data/lib/toml-rb/dumper.rb +103 -0
- data/lib/toml-rb/errors.rb +17 -0
- data/lib/toml-rb/grammars/array.citrus +39 -0
- data/lib/toml-rb/grammars/document.citrus +40 -0
- data/lib/toml-rb/grammars/helper.citrus +17 -0
- data/lib/toml-rb/grammars/primitive.citrus +119 -0
- data/lib/toml-rb/inline_table.rb +72 -0
- data/lib/toml-rb/keygroup.rb +41 -0
- data/lib/toml-rb/keyvalue.rb +49 -0
- data/lib/toml-rb/parser.rb +36 -0
- data/lib/toml-rb/string.rb +68 -0
- data/lib/toml-rb/table_array.rb +50 -0
- data/test/dumper_test.rb +100 -0
- data/test/errors_test.rb +94 -0
- data/test/example-v0.4.0.toml +244 -0
- data/test/example.toml +49 -0
- data/test/grammar_test.rb +246 -0
- data/test/hard_example.toml +46 -0
- data/test/helper.rb +8 -0
- data/test/toml_examples.rb +203 -0
- data/test/toml_test.rb +131 -0
- data/toml-rb-hs.gemspec +24 -0
- metadata +142 -0
- metadata.gz.sig +1 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
grammar TomlRB::Primitive
|
2
|
+
include TomlRB::Helper
|
3
|
+
|
4
|
+
rule primitive
|
5
|
+
string | bool | datetime | number
|
6
|
+
end
|
7
|
+
|
8
|
+
##
|
9
|
+
# String rules
|
10
|
+
##
|
11
|
+
|
12
|
+
rule string
|
13
|
+
multiline_string | multiline_literal | basic_string | literal_string
|
14
|
+
end
|
15
|
+
|
16
|
+
rule basic_string
|
17
|
+
(/(["])(?:\\?.)*?\1/ space) <TomlRB::BasicString>
|
18
|
+
end
|
19
|
+
|
20
|
+
rule literal_string
|
21
|
+
(/(['])(?:\\?.)*?\1/ space) <TomlRB::LiteralString>
|
22
|
+
end
|
23
|
+
|
24
|
+
rule multiline_string
|
25
|
+
('"""' line_break* (text:~('"""' !'"')|'') '"""' space) <TomlRB::MultilineString>
|
26
|
+
end
|
27
|
+
|
28
|
+
rule multiline_literal
|
29
|
+
("'''" line_break* (text:~("'''" !"'")|'') "'''" space) <TomlRB::MultilineLiteral>
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Date time rules
|
34
|
+
##
|
35
|
+
|
36
|
+
rule datetime
|
37
|
+
( skeleton:(datetime_skeleton | date_skeleton) ("Z" | date_offset | [,\.] frac:(/\d/1*6) date_offset?)? ) {
|
38
|
+
skeleton = captures[:skeleton].first
|
39
|
+
y,m,d,h,mi,s = skeleton.value
|
40
|
+
offset = captures[:date_offset].first || "+00:00"
|
41
|
+
sec_frac = captures[:frac].first || 0
|
42
|
+
s = "#{s}.#{sec_frac}".to_f
|
43
|
+
|
44
|
+
Time.new(*[y,m,d,h,mi,s,offset.to_s])
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
rule datetime_skeleton
|
49
|
+
(y:/\d\d\d\d/ "-" m:/\d\d/ "-" d:/\d\d/ "T" h:/\d\d/ ":" mi:/\d\d/ ":" s:/\d\d/) {
|
50
|
+
[:y,:m,:d,:h,:mi,:s].map{ |s| capture(s).value }
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
rule date_offset
|
55
|
+
offset:(sign /\d\d/ ":" /\d\d/)
|
56
|
+
end
|
57
|
+
|
58
|
+
rule date_skeleton
|
59
|
+
(y:/\d\d\d\d/ "-" m:/\d\d/ "-" d:/\d\d/) {
|
60
|
+
[:y,:m,:d].map{ |s| capture(s).value } + [0, 0, 0]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Number rules
|
66
|
+
##
|
67
|
+
|
68
|
+
rule number
|
69
|
+
exponent | float | integer
|
70
|
+
end
|
71
|
+
|
72
|
+
rule float
|
73
|
+
(integer '.' integer) { to_str.to_f }
|
74
|
+
end
|
75
|
+
|
76
|
+
rule exponent
|
77
|
+
( (float | integer) [eE] integer) { to_str.to_f }
|
78
|
+
end
|
79
|
+
|
80
|
+
rule integer
|
81
|
+
(sign? [0-9_]+) { to_str.to_i }
|
82
|
+
end
|
83
|
+
|
84
|
+
rule sign
|
85
|
+
'+' | '-'
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# Boolean rules
|
90
|
+
##
|
91
|
+
|
92
|
+
rule bool
|
93
|
+
true | false
|
94
|
+
end
|
95
|
+
|
96
|
+
rule true
|
97
|
+
'true' { true }
|
98
|
+
end
|
99
|
+
|
100
|
+
rule false
|
101
|
+
'false' { false }
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Key rules
|
106
|
+
##
|
107
|
+
|
108
|
+
rule key
|
109
|
+
quoted_key | bare_key
|
110
|
+
end
|
111
|
+
|
112
|
+
rule bare_key
|
113
|
+
[a-zA-Z0-9_-]+
|
114
|
+
end
|
115
|
+
|
116
|
+
rule quoted_key
|
117
|
+
string
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module TomlRB
|
2
|
+
class InlineTable
|
3
|
+
attr_reader :symbolize_keys
|
4
|
+
|
5
|
+
def initialize(keyvalue_pairs)
|
6
|
+
@pairs = keyvalue_pairs
|
7
|
+
@symbolize_keys = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def value(symbolize_keys = false)
|
11
|
+
if (@symbolize_keys = symbolize_keys)
|
12
|
+
tuple = lambda {|kv| [kv.key.to_sym, visit_value(kv.value)] }
|
13
|
+
else
|
14
|
+
tuple = lambda {|kv| [kv.key, visit_value(kv.value)] }
|
15
|
+
end
|
16
|
+
|
17
|
+
Hash[@pairs.map(&tuple)]
|
18
|
+
end
|
19
|
+
|
20
|
+
def visit_inline_table(inline_table)
|
21
|
+
result = {}
|
22
|
+
|
23
|
+
inline_table.value(@symbolize_keys).each do |k, v|
|
24
|
+
result[key k] = visit_value v
|
25
|
+
end
|
26
|
+
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
def accept_visitor(keyvalue)
|
31
|
+
keyvalue.visit_inline_table self
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def visit_value(a_value)
|
37
|
+
return a_value unless a_value.respond_to? :accept_visitor
|
38
|
+
|
39
|
+
a_value.accept_visitor self
|
40
|
+
end
|
41
|
+
|
42
|
+
def key(a_key)
|
43
|
+
symbolize_keys ? a_key.to_sym : a_key
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class InlineTableArray
|
48
|
+
def initialize(inline_tables)
|
49
|
+
@inline_tables = inline_tables
|
50
|
+
end
|
51
|
+
|
52
|
+
def value(symbolize_keys = false)
|
53
|
+
@inline_tables.map { |it| it.value(symbolize_keys) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
module InlineTableParser
|
58
|
+
def value
|
59
|
+
TomlRB::InlineTable.new captures[:keyvalue].map(&:value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module InlineTableArrayParser
|
64
|
+
def value
|
65
|
+
tables = captures[:inline_table_array_elements].map do |x|
|
66
|
+
x.captures[:inline_table]
|
67
|
+
end
|
68
|
+
|
69
|
+
TomlRB::InlineTableArray.new(tables.flatten.map(&:value)).value
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module TomlRB
|
2
|
+
class Keygroup
|
3
|
+
def initialize(nested_keys)
|
4
|
+
@nested_keys = nested_keys
|
5
|
+
end
|
6
|
+
|
7
|
+
def navigate_keys(hash, visited_keys, symbolize_keys = false)
|
8
|
+
ensure_key_not_defined(visited_keys)
|
9
|
+
@nested_keys.each do |key|
|
10
|
+
key = symbolize_keys ? key.to_sym : key
|
11
|
+
hash[key] = {} unless hash.key?(key)
|
12
|
+
element = hash[key]
|
13
|
+
hash = element.is_a?(Array) ? element.last : element
|
14
|
+
# check that key has not been defined before as a scalar value
|
15
|
+
fail ValueOverwriteError.new(key) unless hash.is_a?(Hash)
|
16
|
+
end
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
|
20
|
+
# Fail if the key was already defined with a ValueOverwriteError
|
21
|
+
def ensure_key_not_defined(visited_keys)
|
22
|
+
fail ValueOverwriteError.new(full_key) if visited_keys.include?(full_key)
|
23
|
+
visited_keys << full_key
|
24
|
+
end
|
25
|
+
|
26
|
+
def accept_visitor(parser)
|
27
|
+
parser.visit_keygroup self
|
28
|
+
end
|
29
|
+
|
30
|
+
def full_key
|
31
|
+
@nested_keys.join('.')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Used in document.citrus
|
36
|
+
module KeygroupParser
|
37
|
+
def value
|
38
|
+
TomlRB::Keygroup.new(captures[:stripped_key].map(&:value))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module TomlRB
|
2
|
+
class Keyvalue
|
3
|
+
attr_reader :value, :symbolize_keys
|
4
|
+
|
5
|
+
def initialize(key, value)
|
6
|
+
@key = key
|
7
|
+
@value = value
|
8
|
+
@symbolize_keys = false
|
9
|
+
end
|
10
|
+
|
11
|
+
def assign(hash, symbolize_keys = false)
|
12
|
+
@symbolize_keys = symbolize_keys
|
13
|
+
fail ValueOverwriteError.new(key) if hash.key?(key)
|
14
|
+
hash[key] = visit_value @value
|
15
|
+
end
|
16
|
+
|
17
|
+
def visit_inline_table(inline_table)
|
18
|
+
result = {}
|
19
|
+
|
20
|
+
inline_table.value(@symbolize_keys).each do |k, v|
|
21
|
+
result[key k] = visit_value v
|
22
|
+
end
|
23
|
+
|
24
|
+
result
|
25
|
+
end
|
26
|
+
|
27
|
+
def key(a_key = @key)
|
28
|
+
symbolize_keys ? a_key.to_sym : a_key
|
29
|
+
end
|
30
|
+
|
31
|
+
def accept_visitor(parser)
|
32
|
+
parser.visit_keyvalue self
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def visit_value(a_value)
|
38
|
+
return a_value unless a_value.respond_to? :accept_visitor
|
39
|
+
|
40
|
+
a_value.accept_visitor self
|
41
|
+
end
|
42
|
+
end
|
43
|
+
# Used in document.citrus
|
44
|
+
module KeyvalueParser
|
45
|
+
def value
|
46
|
+
TomlRB::Keyvalue.new(capture(:stripped_key).value, capture(:v).value)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module TomlRB
|
2
|
+
class Parser
|
3
|
+
attr_reader :hash
|
4
|
+
|
5
|
+
def initialize(content, options = {})
|
6
|
+
@hash = {}
|
7
|
+
@visited_keys = []
|
8
|
+
@current = @hash
|
9
|
+
@symbolize_keys = options[:symbolize_keys]
|
10
|
+
|
11
|
+
begin
|
12
|
+
parsed = TomlRB::Document.parse(content)
|
13
|
+
parsed.matches.map(&:value).compact.each { |m| m.accept_visitor(self) }
|
14
|
+
rescue Citrus::ParseError => e
|
15
|
+
raise TomlRB::ParseError.new(e.message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Read about the Visitor pattern
|
20
|
+
# http://en.wikipedia.org/wiki/Visitor_pattern
|
21
|
+
def visit_table_array(table_array)
|
22
|
+
table_array_key = table_array.full_key
|
23
|
+
@visited_keys.reject! { |k| k.start_with? table_array_key }
|
24
|
+
|
25
|
+
@current = table_array.navigate_keys @hash, @symbolize_keys
|
26
|
+
end
|
27
|
+
|
28
|
+
def visit_keygroup(keygroup)
|
29
|
+
@current = keygroup.navigate_keys @hash, @visited_keys, @symbolize_keys
|
30
|
+
end
|
31
|
+
|
32
|
+
def visit_keyvalue(keyvalue)
|
33
|
+
keyvalue.assign @current, @symbolize_keys
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module TomlRB
|
2
|
+
# Used in primitive.citrus
|
3
|
+
module BasicString
|
4
|
+
SPECIAL_CHARS = {
|
5
|
+
'\\0' => "\0",
|
6
|
+
'\\t' => "\t",
|
7
|
+
'\\b' => "\b",
|
8
|
+
'\\f' => "\f",
|
9
|
+
'\\n' => "\n",
|
10
|
+
'\\r' => "\r",
|
11
|
+
'\\"' => '"',
|
12
|
+
'\\\\' => '\\'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def value
|
16
|
+
aux = TomlRB::BasicString.transform_escaped_chars first.value
|
17
|
+
|
18
|
+
aux[1...-1]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Replace the unicode escaped characters with the corresponding character
|
22
|
+
# e.g. \u03B4 => ?
|
23
|
+
def self.decode_unicode(str)
|
24
|
+
[str[2..-1].to_i(16)].pack('U')
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.transform_escaped_chars(str)
|
28
|
+
str.gsub(/\\(u[\da-fA-F]{4}|U[\da-fA-F]{8}|.)/) do |m|
|
29
|
+
if m.size == 2
|
30
|
+
SPECIAL_CHARS[m] || parse_error(m)
|
31
|
+
else
|
32
|
+
decode_unicode(m).force_encoding('UTF-8')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.parse_error(m)
|
38
|
+
fail ParseError.new "Escape sequence #{m} is reserved"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module LiteralString
|
43
|
+
def value
|
44
|
+
first.value[1...-1]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module MultilineString
|
49
|
+
def value
|
50
|
+
return '' if captures[:text].empty?
|
51
|
+
aux = captures[:text].first.value
|
52
|
+
|
53
|
+
# Remove spaces on multilined Singleline strings
|
54
|
+
aux.gsub!(/\\\r?\n[\n\t\r ]*/, '')
|
55
|
+
|
56
|
+
TomlRB::BasicString.transform_escaped_chars aux
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module MultilineLiteral
|
61
|
+
def value
|
62
|
+
return '' if captures[:text].empty?
|
63
|
+
aux = captures[:text].first.value
|
64
|
+
|
65
|
+
aux.gsub(/\\\r?\n[\n\t\r ]*/, '')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module TomlRB
|
2
|
+
class TableArray
|
3
|
+
def initialize(nested_keys)
|
4
|
+
@nested_keys = nested_keys
|
5
|
+
end
|
6
|
+
|
7
|
+
def navigate_keys(hash, symbolize_keys = false)
|
8
|
+
last_key = @nested_keys.pop
|
9
|
+
last_key = last_key.to_sym if symbolize_keys
|
10
|
+
|
11
|
+
# Go over the parent keys
|
12
|
+
@nested_keys.each do |key|
|
13
|
+
key = symbolize_keys ? key.to_sym : key
|
14
|
+
hash[key] = {} unless hash[key]
|
15
|
+
|
16
|
+
if hash[key].is_a? Array
|
17
|
+
hash[key] << {} if hash[key].empty?
|
18
|
+
hash = hash[key].last
|
19
|
+
else
|
20
|
+
hash = hash[key]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Define Table Array
|
25
|
+
if hash[last_key].is_a? Hash
|
26
|
+
fail TomlRB::ParseError,
|
27
|
+
"#{last_key} was defined as hash but is now redefined as a table!"
|
28
|
+
end
|
29
|
+
hash[last_key] = [] unless hash[last_key]
|
30
|
+
hash[last_key] << {}
|
31
|
+
|
32
|
+
hash[last_key].last
|
33
|
+
end
|
34
|
+
|
35
|
+
def accept_visitor(parser)
|
36
|
+
parser.visit_table_array self
|
37
|
+
end
|
38
|
+
|
39
|
+
def full_key
|
40
|
+
@nested_keys.join('.')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Used in document.citrus
|
45
|
+
module TableArrayParser
|
46
|
+
def value
|
47
|
+
TomlRB::TableArray.new(captures[:stripped_key].map(&:value))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/test/dumper_test.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
unless defined? require_relative
|
2
|
+
def require_relative(path)
|
3
|
+
require path
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative 'helper'
|
8
|
+
|
9
|
+
class DumperTest < Minitest::Test
|
10
|
+
def test_dump_empty
|
11
|
+
dumped = TomlRB.dump({})
|
12
|
+
assert_equal('', dumped)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_dump_types
|
16
|
+
dumped = TomlRB.dump(string: 'TomlRB "dump"')
|
17
|
+
assert_equal("string = \"TomlRB \\\"dump\\\"\"\n", dumped)
|
18
|
+
|
19
|
+
dumped = TomlRB.dump(float: -13.24)
|
20
|
+
assert_equal("float = -13.24\n", dumped)
|
21
|
+
|
22
|
+
dumped = TomlRB.dump(int: 1234)
|
23
|
+
assert_equal("int = 1234\n", dumped)
|
24
|
+
|
25
|
+
dumped = TomlRB.dump(true: true)
|
26
|
+
assert_equal("true = true\n", dumped)
|
27
|
+
|
28
|
+
dumped = TomlRB.dump(false: false)
|
29
|
+
assert_equal("false = false\n", dumped)
|
30
|
+
|
31
|
+
dumped = TomlRB.dump(array: [1, 2, 3])
|
32
|
+
assert_equal("array = [1, 2, 3]\n", dumped)
|
33
|
+
|
34
|
+
dumped = TomlRB.dump(array: [[1, 2], %w(weird one)])
|
35
|
+
assert_equal("array = [[1, 2], [\"weird\", \"one\"]]\n", dumped)
|
36
|
+
|
37
|
+
dumped = TomlRB.dump(datetime: Time.utc(1986, 8, 28, 15, 15))
|
38
|
+
assert_equal("datetime = 1986-08-28T15:15:00Z\n", dumped)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_dump_nested_attributes
|
42
|
+
hash = { nested: { hash: { deep: true } } }
|
43
|
+
dumped = TomlRB.dump(hash)
|
44
|
+
assert_equal("[nested.hash]\ndeep = true\n", dumped)
|
45
|
+
|
46
|
+
hash[:nested].merge!(other: 12)
|
47
|
+
dumped = TomlRB.dump(hash)
|
48
|
+
assert_equal("[nested]\nother = 12\n[nested.hash]\ndeep = true\n", dumped)
|
49
|
+
|
50
|
+
hash[:nested].merge!(nest: { again: 'it never ends' })
|
51
|
+
dumped = TomlRB.dump(hash)
|
52
|
+
toml = <<-EOS.gsub(/^ {6}/, '')
|
53
|
+
[nested]
|
54
|
+
other = 12
|
55
|
+
[nested.hash]
|
56
|
+
deep = true
|
57
|
+
[nested.nest]
|
58
|
+
again = "it never ends"
|
59
|
+
EOS
|
60
|
+
|
61
|
+
assert_equal(toml, dumped)
|
62
|
+
|
63
|
+
hash = { non: { 'bare."keys"' => { "works" => true } } }
|
64
|
+
dumped = TomlRB.dump(hash)
|
65
|
+
assert_equal("[non.\"bare.\\\"keys\\\"\"]\nworks = true\n", dumped)
|
66
|
+
|
67
|
+
hash = { hola: [{ chau: 4 }, { chau: 3 }] }
|
68
|
+
dumped = TomlRB.dump(hash)
|
69
|
+
assert_equal("[[hola]]\nchau = 4\n[[hola]]\nchau = 3\n", dumped)
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_print_empty_tables
|
73
|
+
hash = { plugins: { cpu: { foo: "bar", baz: 1234 }, disk: {}, io: {} } }
|
74
|
+
dumped = TomlRB.dump(hash)
|
75
|
+
toml = <<-EOS.gsub(/^ {6}/, '')
|
76
|
+
[plugins.cpu]
|
77
|
+
baz = 1234
|
78
|
+
foo = "bar"
|
79
|
+
[plugins.disk]
|
80
|
+
[plugins.io]
|
81
|
+
EOS
|
82
|
+
|
83
|
+
assert_equal toml, dumped
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_dump_array_tables
|
87
|
+
hash = { fruit: [{ physical: { color: "red" } }, { physical: { color: "blue" } }] }
|
88
|
+
dumped = TomlRB.dump(hash)
|
89
|
+
toml = <<-EOS.gsub(/^ {6}/, '')
|
90
|
+
[[fruit]]
|
91
|
+
[fruit.physical]
|
92
|
+
color = "red"
|
93
|
+
[[fruit]]
|
94
|
+
[fruit.physical]
|
95
|
+
color = "blue"
|
96
|
+
EOS
|
97
|
+
|
98
|
+
assert_equal toml, dumped
|
99
|
+
end
|
100
|
+
end
|