toml-rb-hs 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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