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.
@@ -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
@@ -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