toby 1.0.0.pre.rc2

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,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: []