toby 1.0.0.pre.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f1e0acda45cfe119c3d8d9eccf8219e20483f45edf9b03f512d6cbed270bf11c
4
+ data.tar.gz: 107c270acc1369b4573a55288355ea14fd391560af8263a65d153544109382f8
5
+ SHA512:
6
+ metadata.gz: 1aa4a9b1d89719d9fbc70853a8f1ba9b2d2d2a77e40f6f1111ef82bcfbd686a7ef2d94cb0fe220b2c44e2ed169128352e84751771930bba03ed80ce06ea6a631
7
+ data.tar.gz: b6e7b8136f32fd4a7c64b4eb04a52b036593d25b7238a788c318e5b0e8d33a87b3803daeb4ebbbcfc2c280db194a19f7efab900341f4edd8ebb7e494b9367733
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'citrus'
4
+ require 'delegate'
5
+ require 'stringio'
6
+ require 'json'
7
+
8
+ # Contains all of the code for the Toby gem.
9
+ module Toby; end
10
+
11
+ module Toby
12
+ # Contains all of code for TOML objects.
13
+ module TOML; end
14
+ end
15
+
16
+ module Toby
17
+ # Contains all of the code used for parsing TOML files.
18
+ # @api private
19
+ module Parser; end
20
+ end
21
+
22
+ require_relative 'toby/toml/array'
23
+ require_relative 'toby/toml/inline_table'
24
+ require_relative 'toby/toml/key_value'
25
+ require_relative 'toby/toml/table'
26
+ require_relative 'toby/toml/toml_file'
27
+
28
+ require_relative 'toby/parser/string'
29
+ require_relative 'toby/parser/datetime'
30
+ require_relative 'toby/parser/match_modules'
31
+ require_relative 'toby/parser/numbers'
32
+
33
+ # The root directory of Toby
34
+ ROOT = File.dirname(File.expand_path(__FILE__))
35
+ Citrus.load "#{ROOT}/toby/grammars/helper.citrus"
36
+ Citrus.load "#{ROOT}/toby/grammars/primitive.citrus"
37
+ Citrus.load "#{ROOT}/toby/grammars/document.citrus"
@@ -0,0 +1,49 @@
1
+ grammar Toby::Document
2
+ include Toby::Primitive
3
+
4
+ rule document
5
+ (comment | table_array | table | keyvalue | line_break)*
6
+ end
7
+
8
+ rule table_array
9
+ (space? '[[' stripped_key ']]' comment?) <Toby::Parser::Match::ArrayTable>
10
+ end
11
+
12
+ rule table
13
+ (space? '[' stripped_key ']' comment?) <Toby::Parser::Match::Table>
14
+ end
15
+
16
+ rule keyvalue
17
+ (stripped_key '=' space? v:(toml_values) comment?) <Toby::Parser::Match::KeyValue>
18
+ end
19
+
20
+ rule inline_table
21
+ (space? '{' (keyvalue? (',' keyvalue)*)? space? '}' ) <Toby::Parser::Match::InlineTable>
22
+ end
23
+
24
+ rule array_comments
25
+ (indent? (comment indent?)*)
26
+ end
27
+
28
+ rule array
29
+ ("[" array_comments (array_elements)? space ","? array_comments "]" indent?) <Toby::Parser::Match::Array>
30
+ end
31
+
32
+ rule array_element
33
+ primitive | inline_table | array
34
+ end
35
+
36
+ rule array_elements
37
+ (array_element (space "," array_comments array_element)*) {
38
+ captures[:array_element].map(&:value)
39
+ }
40
+ end
41
+
42
+ rule toml_values
43
+ primitive | inline_table | array
44
+ end
45
+
46
+ rule stripped_key
47
+ (space? key space?) { captures[:key].first.value }
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ grammar Toby::Helper
2
+ rule comment
3
+ (space? "#" (~line_break)* line_break?) <Toby::Parser::Match::Comment>
4
+ end
5
+
6
+ rule space
7
+ [ \t]*
8
+ end
9
+
10
+ rule indent
11
+ [ \t\r\n]*
12
+ end
13
+
14
+ rule line_break
15
+ (space? "\n" | space? "\r\n") { nil }
16
+ end
17
+ end
@@ -0,0 +1,176 @@
1
+ grammar Toby::Primitive
2
+ include Toby::Helper
3
+
4
+ rule primitive
5
+ datetime | bool | number | string
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) <Toby::Parser::BasicString>
18
+ end
19
+
20
+ rule literal_string
21
+ (/(['])(?:\\?.)*?\1/ space) <Toby::Parser::LiteralString>
22
+ end
23
+
24
+ rule multiline_string
25
+ ('"""' line_break* (text:~('"""' !'"')|'') '"""' space) <Toby::Parser::MultilineString>
26
+ end
27
+
28
+ rule multiline_literal
29
+ ("'''" line_break* (text:~("'''" !"'")|'') "'''" space) <Toby::Parser::MultilineLiteral>
30
+ end
31
+
32
+ ##
33
+ # Date time rules
34
+ ##
35
+
36
+ rule datetime
37
+ offset_datetime | local_datetime | local_date | local_time
38
+ end
39
+
40
+ rule offset_datetime
41
+ ( skeleton:datetime_skeleton ("Z" | date_offset) ) <Toby::Parser::Match::OffsetDateTime>
42
+ end
43
+
44
+ rule local_datetime
45
+ datetime_skeleton <Toby::Parser::Match::LocalDateTime>
46
+ end
47
+
48
+ rule local_date
49
+ (date_skeleton space) <Toby::Parser::Match::LocalDate>
50
+ end
51
+
52
+ rule local_time
53
+ (time_skeleton space) <Toby::Parser::Match::LocalTime>
54
+ end
55
+
56
+ rule datetime_skeleton
57
+ (date_skeleton ("T"|" ") time_skeleton) {
58
+ capture(:date_skeleton).value + capture(:time_skeleton).value
59
+ }
60
+ end
61
+
62
+ rule date_skeleton
63
+ (year:/\d\d\d\d/ "-" mon:/\d\d/ "-" day:/\d\d/) {
64
+ [:year,:mon,:day].map{ |s| capture(s).value }
65
+ }
66
+ end
67
+
68
+ rule time_skeleton
69
+ ( hour:([0-2][0-9]) ":" mim:([0-6][0-9]) ":" sec:([0-6][0-9]) ([,\.] sec_frac:(/\d/1*6))? ) {
70
+ [:hour,:mim,:sec].map{ |s| capture(s).value } + [capture(:sec_frac) || '0']
71
+ }
72
+ end
73
+
74
+ rule date_offset
75
+ offset:(sign /\d\d/ ":" /\d\d/)
76
+ end
77
+
78
+ ##
79
+ # Number rules
80
+ ##
81
+
82
+ rule number
83
+ float | integer
84
+ end
85
+
86
+ rule float
87
+ inf | nan | exponential_float | fractional_float
88
+ end
89
+
90
+ rule inf
91
+ (s:sign? 'inf') {
92
+ sign = (capture(:s).value != '-') ? 1 : -1
93
+ sign * Float::INFINITY
94
+ }
95
+ end
96
+
97
+ rule nan
98
+ (sign? 'nan') { Float::NAN }
99
+ end
100
+
101
+ rule exponential_float
102
+ ((fractional_float | demical_integer) [eE] demical_integer) { to_str.to_f }
103
+ end
104
+
105
+ rule fractional_float
106
+ (demical_integer '.' [0-9_]+) {
107
+ to_str.to_f
108
+ }
109
+ end
110
+
111
+ rule integer
112
+ hexadecimal_integer | octal_integer | binary_integer | demical_integer
113
+ end
114
+
115
+ rule hexadecimal_integer
116
+ ('0x' [a-fA-F0-9_]+) <Toby::Parser::Match::Hexadecimal>
117
+ end
118
+
119
+ rule octal_integer
120
+ ('0o' [0-7_]+) <Toby::Parser::Match::Octal>
121
+ end
122
+
123
+ rule binary_integer
124
+ ('0b' [01_]+) <Toby::Parser::Match::Binary>
125
+ end
126
+
127
+ rule demical_integer
128
+ (sign? [0-9_]+) { to_str.to_i }
129
+ end
130
+
131
+ rule sign
132
+ '+' | '-'
133
+ end
134
+
135
+ ##
136
+ # Boolean rules
137
+ ##
138
+
139
+ rule bool
140
+ true | false
141
+ end
142
+
143
+ rule true
144
+ 'true' { true }
145
+ end
146
+
147
+ rule false
148
+ 'false' { false }
149
+ end
150
+
151
+ ##
152
+ # Key rules
153
+ ##
154
+
155
+ rule key
156
+ dotted_key
157
+ end
158
+
159
+ rule bare_key
160
+ [a-zA-Z0-9_-]+
161
+ end
162
+
163
+ rule quoted_key
164
+ basic_string | literal_string
165
+ end
166
+
167
+ rule single_key
168
+ quoted_key | bare_key
169
+ end
170
+
171
+ rule dotted_key
172
+ (space? single_key space? ("." space? single_key space?)* ) {
173
+ captures[:single_key].map(&:value)
174
+ }
175
+ end
176
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toby
4
+ module Parser
5
+ # @see https://toml.io/en/v1.0.0-rc.3#offset-date-time
6
+ class OffsetDateTime < Time
7
+ def to_s
8
+ strftime('%Y-%m-%dT%H:%M:%S%z')
9
+ end
10
+ end
11
+
12
+ # @see https://toml.io/en/v1.0.0-rc.3#local-date-time
13
+ class LocalDateTime < Time
14
+ def to_s
15
+ strftime('%Y-%m-%dT%H:%M:%S')
16
+ end
17
+ end
18
+
19
+ # @see https://toml.io/en/v1.0.0-rc.3#local-date
20
+ class LocalDate < Time
21
+ def to_s
22
+ strftime('%Y-%m-%d')
23
+ end
24
+ end
25
+
26
+ # @see https://toml.io/en/v1.0.0-rc.3#local-time
27
+ class LocalTime < Time
28
+ def to_s
29
+ utc.strftime('%H:%M:%S.%L')
30
+ end
31
+ end
32
+
33
+ module Match
34
+ # @see https://toml.io/en/v1.0.0-rc.3#offset-date-time
35
+ module OffsetDateTime
36
+ def value
37
+ skeleton = captures[:datetime_skeleton].first
38
+ year, mon, day, hour, min, sec, sec_frac = skeleton.value
39
+ offset = captures[:date_offset].first || '+00:00'
40
+ sec = "#{sec}.#{sec_frac}".to_f
41
+
42
+ Toby::Parser::OffsetDateTime.new(year, mon, day, hour, min, sec, offset.to_s)
43
+ end
44
+ end
45
+
46
+ # @see https://toml.io/en/v1.0.0-rc.3#local-date-time
47
+ module LocalDateTime
48
+ def value
49
+ year, mon, day = captures[:date_skeleton].first.value
50
+ hour, min, sec, sec_frac = captures[:time_skeleton].first.value
51
+ usec = sec_frac.to_s.ljust(6, '0')
52
+
53
+ Toby::LocalDateTime.local(year, mon, day, hour, min, sec, usec)
54
+ end
55
+ end
56
+
57
+ # @see https://toml.io/en/v1.0.0-rc.3#local-date
58
+ module LocalDate
59
+ def value
60
+ year, mon, day = captures[:date_skeleton].first.value
61
+ Toby::Parser::LocalDate.local(year, mon, day)
62
+ end
63
+ end
64
+
65
+ # @see https://toml.io/en/v1.0.0-rc.3#local-time
66
+ module LocalTime
67
+ def value
68
+ hour, min, sec, sec_frac = captures[:time_skeleton].first.value
69
+ usec = sec_frac.to_s.ljust(6, '0')
70
+
71
+ Toby::Parser::LocalTime.at(3600 * hour.to_i + 60 * min.to_i + sec.to_i, usec.to_i)
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toby
4
+ module Parser
5
+ module Match
6
+ # @see https://toml.io/en/v1.0.0-rc.3#inline-table
7
+ module InlineTable
8
+ def value
9
+ kv_array = []
10
+
11
+ captures(:keyvalue).each do |kv|
12
+ kv_array << Toby::TOML::KeyValue.new(kv.keys, kv.value, nil)
13
+ end
14
+
15
+ Toby::TOML::InlineTable.new kv_array
16
+ end
17
+ end
18
+
19
+ # @see https://toml.io/en/v1.0.0-rc.3#array
20
+ module Array
21
+ def value
22
+ Toby::TOML::Array.new capture(:array_elements).value
23
+ end
24
+ end
25
+
26
+ # @see https://toml.io/en/v1.0.0-rc.3#keyvalue-pair
27
+ module KeyValue
28
+ def keys
29
+ capture(:stripped_key).value
30
+ end
31
+
32
+ def value
33
+ capture(:v).value
34
+ end
35
+
36
+ def comment
37
+ capture(:comment)&.stripped_comment
38
+ end
39
+
40
+ def toml_object
41
+ Toby::TOML::KeyValue.new(
42
+ keys,
43
+ value,
44
+ comment
45
+ )
46
+ end
47
+ end
48
+
49
+ # @see https://toml.io/en/v1.0.0-rc.3#table
50
+ module Table
51
+ def toml_object
52
+ Toby::TOML::Table.new(
53
+ capture(:stripped_key).value,
54
+ capture(:comment)&.stripped_comment,
55
+ false
56
+ )
57
+ end
58
+ end
59
+
60
+ # @see https://toml.io/en/v1.0.0-rc.3#array-of-tables
61
+ module ArrayTable
62
+ def toml_object
63
+ Toby::TOML::Table.new(
64
+ capture(:stripped_key).value,
65
+ capture(:comment)&.stripped_comment,
66
+ true
67
+ )
68
+ end
69
+ end
70
+
71
+ # @see https://toml.io/en/v1.0.0-rc.3#comment
72
+ module Comment
73
+ def stripped_comment
74
+ value.sub('#', '').strip!
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toby
4
+ module Parser
5
+ # @see https://toml.io/en/v1.0.0-rc.3#integer
6
+ class Hexadecimal < DelegateClass(Integer)
7
+ def to_s
8
+ hex = to_i.to_s(16)
9
+ "0x#{hex}"
10
+ end
11
+
12
+ def inspect
13
+ to_s
14
+ end
15
+ end
16
+
17
+ # @see https://toml.io/en/v1.0.0-rc.3#integer
18
+ class Binary < DelegateClass(Integer)
19
+ def to_s
20
+ binary = to_i.to_s(2)
21
+ "0b#{binary}"
22
+ end
23
+
24
+ def inspect
25
+ to_s
26
+ end
27
+ end
28
+
29
+ # @see https://toml.io/en/v1.0.0-rc.3#integer
30
+ class Octal < DelegateClass(Integer)
31
+ def to_s
32
+ octal = to_i.to_s(8)
33
+ "0b#{octal}"
34
+ end
35
+
36
+ def inspect
37
+ to_s
38
+ end
39
+ end
40
+
41
+ module Match
42
+ # @see https://toml.io/en/v1.0.0-rc.3#integer
43
+ module Hexadecimal
44
+ def value
45
+ Toby::Parser::Hexadecimal.new(to_str.to_i(16))
46
+ end
47
+ end
48
+
49
+ # @see https://toml.io/en/v1.0.0-rc.3#integer
50
+ module Binary
51
+ def value
52
+ Toby::Parser::Binary.new(to_str.to_i(2))
53
+ end
54
+ end
55
+
56
+ # @see https://toml.io/en/v1.0.0-rc.3#integer
57
+ module Octal
58
+ def value
59
+ Toby::Parser::Octal.new(to_str.to_i(8))
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toby
4
+ module Parser
5
+ # Used in primitive.citrus
6
+ module BasicString
7
+ SPECIAL_CHARS = {
8
+ '\\0' => "\0",
9
+ '\\t' => "\t",
10
+ '\\b' => "\b",
11
+ '\\f' => "\f",
12
+ '\\n' => "\n",
13
+ '\\r' => "\r",
14
+ '\\"' => '"',
15
+ '\\\\' => '\\'
16
+ }.freeze
17
+
18
+ def value
19
+ aux = Toby::Parser::BasicString.transform_escaped_chars first.value
20
+
21
+ aux[1...-1]
22
+ end
23
+
24
+ # Replace the unicode escaped characters with the corresponding character
25
+ # e.g. \u03B4 => ?
26
+ def self.decode_unicode(str)
27
+ [str[2..-1].to_i(16)].pack('U')
28
+ end
29
+
30
+ def self.transform_escaped_chars(str)
31
+ str.gsub(/\\(u[\da-fA-F]{4}|U[\da-fA-F]{8}|.)/) do |m|
32
+ if m.size == 2
33
+ SPECIAL_CHARS[m] || parse_error(m)
34
+ else
35
+ decode_unicode(m).force_encoding('UTF-8')
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.parse_error(sequence)
41
+ raise ParseError, "Escape sequence #{sequence} is reserved"
42
+ end
43
+ end
44
+
45
+ # @see https://toml.io/en/v1.0.0-rc.3#string
46
+ module LiteralString
47
+ def value
48
+ first.value[1...-1]
49
+ end
50
+ end
51
+
52
+ # @see https://toml.io/en/v1.0.0-rc.3#string
53
+ module MultilineString
54
+ def value
55
+ return '' if captures[:text].empty?
56
+
57
+ aux = captures[:text].first.value
58
+
59
+ # Remove spaces on multilined Singleline strings
60
+ aux.gsub!(/\\\r?\n[\n\t\r ]*/, '')
61
+
62
+ Toby::Parser::BasicString.transform_escaped_chars aux
63
+ end
64
+ end
65
+
66
+ # @see https://toml.io/en/v1.0.0-rc.3#string
67
+ module MultilineLiteral
68
+ def value
69
+ return '' if captures[:text].empty?
70
+
71
+ aux = captures[:text].first.value
72
+
73
+ aux.gsub(/\\\r?\n[\n\t\r ]*/, '')
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: false
2
+
3
+ module Toby
4
+ module TOML
5
+ # Represents an array value
6
+ # @see https://toml.io/en/v1.0.0-rc.3#array
7
+ class Array < ::Array
8
+ # @return [String] Array in valid TOML format
9
+ def dump
10
+ output = StringIO.new
11
+
12
+ output.print '[ '
13
+
14
+ dumped_array = map do |val|
15
+ val.respond_to?(:dump) ? val.dump : val
16
+ end
17
+
18
+ output.print dumped_array.join(', ')
19
+ output.print ' ]'
20
+
21
+ output.string
22
+ end
23
+
24
+ # @param options [Hash] The options hash for the object's #to_hash method when applicable
25
+ # (see Toby::TOML::TOMLFile#to_hash)
26
+ # @return [Array] Returns the value of #value, the value of #to_hash,
27
+ # or the object itself for every object in the Toby::TOML::Array
28
+ def to_hash(options = {})
29
+ map do |obj|
30
+ if obj.respond_to?(:value)
31
+ obj.value
32
+ elsif obj.respond_to?(:to_hash)
33
+ obj.to_hash(options)
34
+ else
35
+ obj
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: false
2
+
3
+ module Toby
4
+ module TOML
5
+ # Represents an inline-table value
6
+ # @see https://toml.io/en/v1.0.0-rc.3#inline-table
7
+ class InlineTable < Array
8
+ # @return [String] Inline table in valid TOML format
9
+ def dump
10
+ output = StringIO.new
11
+
12
+ output.print '{'
13
+
14
+ each do |kv|
15
+ dumped_value = kv.value.respond_to?(:dump) ? kv.value.dump : kv.value
16
+
17
+ dotted_keys = kv.split_keys.map { |key| kv.bare_key?(key) ? key : kv.quote_key(key) }.join('.')
18
+
19
+ output.print " #{dotted_keys} = #{dumped_value},"
20
+ end
21
+
22
+ output.string.gsub(/,$/, ' }')
23
+ end
24
+
25
+ # @return [Hash] TOML table represented as a hash.
26
+ # @option options [TrueClass, FalseClass] :dotted_keys (false) If true, dotted keys are not expanded/nested.
27
+ # @example
28
+ # Given the following TOML:
29
+ # table = {a.b.c = 123}
30
+ #
31
+ # #to_hash returns { "table" => {"a" => { "b" => { "c" => 123 } } } }
32
+ # #to_hash(dotted_keys: true) returns {'some.dotted.keys' => {'some.value'} => 123}
33
+ def to_hash(options = {})
34
+ if options[:dotted_keys]
35
+ to_dotted_keys_hash
36
+ else
37
+ to_split_keys_hash
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # @api private
44
+ def to_split_keys_hash
45
+ output_hash = {}
46
+
47
+ last_hash = output_hash
48
+ last_last_hash = nil
49
+
50
+ each do |kv|
51
+ kv.split_keys.each_with_index do |key, i|
52
+ last_last_hash = last_hash
53
+
54
+ if i < (kv.split_keys.size - 1) # not the last key
55
+ last_hash[key] ||= {}
56
+ last_last_hash = last_hash
57
+ last_hash = last_hash[key]
58
+ else
59
+ last_hash[key] = kv.value.respond_to?(:to_hash) ? kv.value.to_hash(options) : kv.value
60
+ end
61
+ end
62
+ end
63
+
64
+ output_hash
65
+ end
66
+
67
+ # @api private
68
+ def to_dotted_keys_hash
69
+ output_hash = {}
70
+
71
+ each do |kv|
72
+ output_hash[kv.key] = kv.value.respond_to?(:to_hash) ? kv.value.to_hash(options) : kv.value
73
+ end
74
+
75
+ output_hash
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: false
2
+
3
+ module Toby
4
+ module TOML
5
+ # Represents a TOML key-value pair
6
+ # @see https://toml.io/en/v1.0.0-rc.3#keyvalue-pair
7
+ class KeyValue
8
+ # @return [String] The key of the key-value pair.
9
+ attr_reader :key
10
+
11
+ # @return [::Array] The dotted keys of the key-value pair.
12
+ attr_reader :split_keys
13
+
14
+ # @return [String, Integer, Float, Time, Toby::TOML::Array, Toby::TOML::InlineTable] The value of the
15
+ # key-value pair.
16
+ attr_accessor :value
17
+
18
+ # @return [Toby::TOML::Table] The table the key-value pair belongs to.
19
+ attr_accessor :table
20
+
21
+ # @return [::Array<String>] The header comments above the key-value pair.
22
+ attr_accessor :header_comments
23
+
24
+ # @return [String] The comment in-line with the key-value pair
25
+ attr_accessor :inline_comment
26
+
27
+ # @param split_keys [::Array] Dotted keys of the key-value pair.
28
+ # @param value [String, Integer, Float, Time, Toby::TOML::Array, Toby::TOML::InlineTable] The value of the
29
+ # key-value pair.
30
+ # @param inline_comment [String] The comment in-line with the key-value pair.
31
+ def initialize(split_keys, value, inline_comment)
32
+ @split_keys = split_keys
33
+ @key = split_keys.join('.')
34
+
35
+ @value = value
36
+ @header_comments = []
37
+ @inline_comment = inline_comment
38
+ @table = table
39
+ end
40
+
41
+ # @return [String] The key-value pair in valid TOML
42
+ def dump
43
+ output = StringIO.new
44
+
45
+ dumped_value = value.respond_to?(:dump) ? value.dump : value
46
+
47
+ dotted_keys = split_keys.map { |key| bare_key?(key) ? key : quote_key(key) }.join('.')
48
+
49
+ output.puts "\n##{header_comments.join("\n#")}" unless header_comments.empty?
50
+
51
+ output.puts "#{dotted_keys} = #{dumped_value}#{" ##{inline_comment}" if inline_comment}"
52
+
53
+ output.string
54
+ end
55
+
56
+ # @api private
57
+ # from toml-rb
58
+ # https://github.com/emancu/toml-rb/blob/ca5bf9563f1ef2c467bd43eec1d035e83b61ac88/lib/toml-rb/dumper.rb
59
+ def bare_key?(key)
60
+ !!key.to_s.match(/^[a-zA-Z0-9_-]*$/)
61
+ end
62
+
63
+ # @api private
64
+ # from toml-rb
65
+ # https://github.com/emancu/toml-rb/blob/ca5bf9563f1ef2c467bd43eec1d035e83b61ac88/lib/toml-rb/dumper.rb
66
+ def quote_key(key)
67
+ "\"#{key.gsub('"', '\\"')}\""
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: false
2
+
3
+ module Toby
4
+ module TOML
5
+ # Represents a TOML table
6
+ # @see https://toml.io/en/v1.0.0-rc.3#table
7
+ # @see https://toml.io/en/v1.0.0-rc.3#array-of-tables
8
+ class Table
9
+ # @return [::Array] The dotted keys of the table name.
10
+ attr_reader :split_keys
11
+
12
+ # @return [String] The name of table.
13
+ attr_reader :name
14
+
15
+ # @return [::Array<Toby::TOML::KeyValue>] The key-value pairs within the table.
16
+ attr_accessor :key_values
17
+
18
+ # @return [::Array<String>] The header comments above the table.
19
+ attr_accessor :header_comments
20
+
21
+ # @return [String] The comment in-line with the table name.
22
+ attr_accessor :inline_comment
23
+
24
+ # @param split_keys [::Array] Dotted keys of the table name.
25
+ # @param inline_comment [String] The comment in-line with the key-value pair.
26
+ # @param is_array_table [TrueClass, FalseClass] Whether the table is an array-table or not
27
+ def initialize(split_keys, inline_comment, is_array_table)
28
+ @split_keys = split_keys
29
+ @name = split_keys&.join('.')
30
+ @inline_comment = inline_comment
31
+ @is_array_table = is_array_table
32
+
33
+ @header_comments = []
34
+ @key_values = []
35
+ end
36
+
37
+ # @return [TrueClass, FalseClass] Whether the table is an array-table or not
38
+ def array_table?
39
+ @is_array_table
40
+ end
41
+
42
+ # @return [Hash] TOML table represented as a hash.
43
+ # @option options [TrueClass, FalseClass] :dotted_keys (false) If true, dotted keys are not expanded/nested.
44
+ # @example
45
+ # Given the following TOML:
46
+ # [some.dotted.keys]
47
+ # some.value = 123
48
+ #
49
+ # #to_hash returns {'some' => {'dotted' => {'keys' => {'some' => {'value' => 123} } } } } }
50
+ # #to_hash(dotted_keys: true) returns in {'some.dotted.keys' => {'some.value'} => 123}
51
+ def to_hash(options = {})
52
+ if options[:dotted_keys]
53
+ to_dotted_keys_hash(options)
54
+ else
55
+ to_split_keys_hash(options)
56
+ end
57
+ end
58
+
59
+ # @return [String] Table in valid TOML format
60
+ def dump
61
+ output = StringIO.new
62
+
63
+ dotted_keys = split_keys.map { |key| bare_key?(key) ? key : quote_key(key) }.join('.')
64
+
65
+ output.puts "\n##{header_comments.join("\n#")}" unless header_comments.empty?
66
+
67
+ if array_table?
68
+ output.puts "[[#{dotted_keys}]]#{" ##{inline_comment}" if inline_comment}"
69
+ else
70
+ output.puts "[#{dotted_keys}]#{" ##{inline_comment}" if inline_comment}"
71
+ end
72
+
73
+ key_values.each do |kv|
74
+ output.puts kv.dump
75
+ end
76
+
77
+ output.string
78
+ end
79
+
80
+ # @api private
81
+ # from toml-rb
82
+ # https://github.com/emancu/toml-rb/blob/ca5bf9563f1ef2c467bd43eec1d035e83b61ac88/lib/toml-rb/dumper.rb
83
+ def bare_key?(key)
84
+ !!key.to_s.match(/^[a-zA-Z0-9_-]*$/)
85
+ end
86
+
87
+ # @api private
88
+ # from toml-rb
89
+ # https://github.com/emancu/toml-rb/blob/ca5bf9563f1ef2c467bd43eec1d035e83b61ac88/lib/toml-rb/dumper.rb
90
+ def quote_key(key)
91
+ "\"#{key.gsub('"', '\\"')}\""
92
+ end
93
+
94
+ private
95
+
96
+ # @api private
97
+ def to_dotted_keys_hash(options)
98
+ output_hash = {}
99
+
100
+ key_values.each do |kv|
101
+ output_hash[kv.key] = kv.value.respond_to?(:to_hash) ? kv.value.to_hash(options) : kv.value
102
+ end
103
+
104
+ output_hash
105
+ end
106
+
107
+ # @api private
108
+ def to_split_keys_hash(options)
109
+ output_hash = {}
110
+
111
+ key_values.each do |kv|
112
+ last_hash = output_hash
113
+
114
+ kv.split_keys.each_with_index do |key, i|
115
+ if i < (kv.split_keys.size - 1) # not the last key
116
+ last_hash[key] ||= {}
117
+ last_hash = last_hash[key]
118
+ else
119
+ last_hash[key] = kv.value.respond_to?(:to_hash) ? kv.value.to_hash(options) : kv.value
120
+ end
121
+ end
122
+ end
123
+
124
+ output_hash
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: false
2
+
3
+ module Toby
4
+ module TOML
5
+ # Represents an entire TOML file
6
+ # @see https://toml.io/en/v1.0.0-rc.3
7
+ class TOMLFile < Toby::TOML::Table
8
+ # @return [Array<Toby::TOML::Table>] The tables in the TOML file
9
+ attr_reader :tables
10
+
11
+ # @param input_string [String] The TOML file to parse.
12
+ def initialize(input_string)
13
+ super(nil, '', false)
14
+
15
+ @tables = [self]
16
+ matches = Toby::Document.parse(input_string).matches
17
+
18
+ @comment_buffer = []
19
+
20
+ matches.each do |m|
21
+ if m.respond_to? :toml_object
22
+ toml_object_handler(m.toml_object)
23
+
24
+ elsif m.respond_to? :stripped_comment
25
+ @comment_buffer << m.stripped_comment
26
+
27
+ end
28
+ end
29
+ end
30
+
31
+ # @return [Hash] TOML table represented as a hash.
32
+ # @option options [TrueClass, FalseClass] :dotted_keys (false) If true, dotted keys are not expanded/nested.
33
+ # @example
34
+ # Given the following TOML:
35
+ # [some.dotted.keys]
36
+ # some.value = 123
37
+ #
38
+ # #to_hash returns {'some' => {'dotted' => {'keys' => {'some' => {'value' => 123} } } } } }
39
+ # #to_hash(dotted_keys: true) returns {'some.dotted.keys' => {'some.value'} => 123}
40
+ def to_hash(options = {})
41
+ if options[:dotted_keys]
42
+ to_dotted_keys_hash(options)
43
+ else
44
+ to_split_keys_hash(options)
45
+ end
46
+ end
47
+
48
+ # @return [String] The entire TOML file in valid TOML format.
49
+ def dump
50
+ output = StringIO.new
51
+
52
+ key_values.each do |kv|
53
+ output.puts kv.dump
54
+ end
55
+
56
+ tables.each do |table|
57
+ next if table.is_a? Toby::TOML::TOMLFile
58
+
59
+ output.puts table.dump
60
+ end
61
+
62
+ output.string
63
+ end
64
+
65
+ # @return [String] The TOML file in valid JSON in accordance with the TOML spec
66
+ def to_json(*_args)
67
+ to_hash.to_json
68
+ end
69
+
70
+ # @return [NilClass] A file can't have an inline comment, so this will always return nil
71
+ def inline_comment
72
+ nil
73
+ end
74
+
75
+ private
76
+
77
+ # @api private
78
+ def to_dotted_keys_hash(options)
79
+ output_hash = {}
80
+
81
+ tables.each do |tbl|
82
+ if tbl.name.nil?
83
+ output_hash = super
84
+
85
+ elsif tbl.array_table?
86
+ output_hash[tbl.name] ||= []
87
+ output_hash[tbl.name] << tbl.to_hash(options)
88
+
89
+ else
90
+ output_hash[tbl.name] = tbl.to_hash(options)
91
+ end
92
+ end
93
+
94
+ output_hash
95
+ end
96
+
97
+ # @api private
98
+ def to_split_keys_hash(options)
99
+ output_hash = {}
100
+
101
+ tables.each do |tbl|
102
+ if tbl.name.nil?
103
+ output_hash = super
104
+
105
+ elsif tbl.array_table?
106
+ last_hash = output_hash
107
+
108
+ tbl.split_keys.each_with_index do |key, i|
109
+ if i < (tbl.split_keys.size - 1) # not the last key
110
+ last_hash[key] ||= {}
111
+ last_hash = last_hash[key]
112
+ elsif last_hash.is_a? ::Array
113
+ last_hash.last[key] ||= []
114
+ last_hash.last[key] << tbl.to_hash(options)
115
+ else
116
+ last_hash[key] ||= []
117
+ last_hash[key] << tbl.to_hash(options)
118
+ end
119
+ end
120
+
121
+ else
122
+
123
+ last_hash = output_hash
124
+ last_last_hash = nil
125
+
126
+ tbl.split_keys.each_with_index do |key, i|
127
+ last_last_hash = last_hash
128
+ if i < (tbl.split_keys.size - 1) # not the last key
129
+ last_hash[key] ||= {}
130
+ last_last_hash = last_hash
131
+ last_hash = last_hash[key]
132
+ elsif last_hash.is_a? ::Array
133
+ last_last_hash.last[key] = tbl.to_hash(options)
134
+ else
135
+ last_hash[key] = tbl.to_hash(options)
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ output_hash
142
+ end
143
+
144
+ # @api private
145
+ def toml_object_handler(obj)
146
+ if obj.respond_to?(:header_comments) && !@comment_buffer.empty?
147
+ obj.header_comments = @comment_buffer
148
+ @comment_buffer = []
149
+ end
150
+
151
+ case obj
152
+ when Toby::TOML::Table
153
+ @tables << obj
154
+
155
+ when Toby::TOML::KeyValue
156
+ @tables.last.key_values << obj
157
+
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre.rc2
5
+ platform: ruby
6
+ authors:
7
+ - Joe Polny
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-01-31 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Toby is a TOML 1.0.0 parser for Ruby that allows the parsing, editing,
14
+ and dumping of the TOML file format (including comments). Toby also supports conversion
15
+ from TOML to a Ruby Hash or JSON.
16
+ email:
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/toby.rb
22
+ - lib/toby/grammars/document.citrus
23
+ - lib/toby/grammars/helper.citrus
24
+ - lib/toby/grammars/primitive.citrus
25
+ - lib/toby/parser/datetime.rb
26
+ - lib/toby/parser/match_modules.rb
27
+ - lib/toby/parser/numbers.rb
28
+ - lib/toby/parser/string.rb
29
+ - lib/toby/toml/array.rb
30
+ - lib/toby/toml/inline_table.rb
31
+ - lib/toby/toml/key_value.rb
32
+ - lib/toby/toml/table.rb
33
+ - lib/toby/toml/toml_file.rb
34
+ homepage: https://github.com/joe-p/toby
35
+ licenses:
36
+ - MIT
37
+ metadata:
38
+ source_code_uri: https://github.com/joe-p/toby
39
+ bug_tracker_uri: https://github.com/joe-p/toby/issues
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">"
52
+ - !ruby/object:Gem::Version
53
+ version: 1.3.1
54
+ requirements: []
55
+ rubygems_version: 3.1.4
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: A TOML Parser for Ruby. TOML + Ruby = Toby.
59
+ test_files: []