kdl 1.0.6 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +7 -1
- data/.gitignore +1 -0
- data/.gitmodules +4 -0
- data/Gemfile +6 -1
- data/README.md +51 -7
- data/Rakefile +6 -1
- data/bin/kdl +1 -1
- data/kdl.gemspec +2 -2
- data/lib/kdl/document.rb +58 -2
- data/lib/kdl/kdl.tab.rb +303 -228
- data/lib/kdl/kdl.yy +57 -49
- data/lib/kdl/node.rb +113 -12
- data/lib/kdl/parser_common.rb +26 -0
- data/lib/kdl/string_dumper.rb +30 -33
- data/lib/kdl/tokenizer.rb +350 -113
- data/lib/kdl/types/base64.rb +1 -1
- data/lib/kdl/types/country/iso3166_countries.rb +1 -1
- data/lib/kdl/types/country/iso3166_subdivisions.rb +1 -1
- data/lib/kdl/types/country.rb +2 -2
- data/lib/kdl/types/currency/iso4217_currencies.rb +1 -1
- data/lib/kdl/types/currency.rb +1 -1
- data/lib/kdl/types/date_time.rb +3 -3
- data/lib/kdl/types/decimal.rb +1 -1
- data/lib/kdl/types/duration/iso8601_parser.rb +1 -1
- data/lib/kdl/types/duration.rb +1 -1
- data/lib/kdl/types/email/parser.rb +1 -1
- data/lib/kdl/types/email.rb +1 -1
- data/lib/kdl/types/hostname/validator.rb +1 -1
- data/lib/kdl/types/hostname.rb +1 -1
- data/lib/kdl/types/ip.rb +1 -1
- data/lib/kdl/types/irl/parser.rb +1 -1
- data/lib/kdl/types/irl.rb +1 -1
- data/lib/kdl/types/regex.rb +1 -1
- data/lib/kdl/types/url.rb +1 -1
- data/lib/kdl/types/url_template.rb +1 -1
- data/lib/kdl/types/uuid.rb +1 -1
- data/lib/kdl/v1/document.rb +17 -0
- data/lib/kdl/v1/kdl.tab.rb +594 -0
- data/lib/kdl/v1/kdl.yy +89 -0
- data/lib/kdl/v1/node.rb +30 -0
- data/lib/kdl/v1/string_dumper.rb +28 -0
- data/lib/kdl/v1/tokenizer.rb +296 -0
- data/lib/kdl/v1/value.rb +89 -0
- data/lib/kdl/v1.rb +11 -0
- data/lib/kdl/value.rb +81 -12
- data/lib/kdl/version.rb +1 -1
- data/lib/kdl.rb +40 -1
- metadata +13 -4
data/lib/kdl/kdl.yy
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
class KDL::Parser
|
2
2
|
options no_result_var
|
3
|
-
token IDENT
|
4
|
-
STRING RAWSTRING
|
3
|
+
token IDENT STRING RAWSTRING
|
5
4
|
INTEGER FLOAT TRUE FALSE NULL
|
6
5
|
WS NEWLINE
|
7
6
|
LBRACE RBRACE
|
@@ -10,57 +9,66 @@ class KDL::Parser
|
|
10
9
|
SEMICOLON
|
11
10
|
EOF
|
12
11
|
SLASHDASH
|
13
|
-
ESCLINE
|
14
12
|
rule
|
15
|
-
document : nodes {
|
16
|
-
| linespaces {
|
13
|
+
document : nodes { @output_module::Document.new(val[0]) }
|
14
|
+
| linespaces { @output_module::Document.new([]) }
|
17
15
|
|
18
|
-
nodes
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
node
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
16
|
+
nodes : none { [] }
|
17
|
+
| linespaces node { [val[1]] }
|
18
|
+
| linespaces empty_node { [] }
|
19
|
+
| nodes node { [*val[0], val[1]] }
|
20
|
+
| nodes empty_node { val[0] }
|
21
|
+
node : unterm_node node_term { val[0] }
|
22
|
+
unterm_node : untyped_node { val[0] }
|
23
|
+
| type untyped_node { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
|
24
|
+
untyped_node : node_decl { val[0].tap { |x| x.children = [] } }
|
25
|
+
| node_decl node_children { val[0].tap { |x| x.children = val[1] } }
|
26
|
+
| node_decl empty_childrens { val[0].tap { |x| x.children = [] } }
|
27
|
+
| node_decl empty_childrens node_children { val[0].tap { |x| x.children = val[2] } }
|
28
|
+
| node_decl node_children empty_childrens { val[0].tap { |x| x.children = val[1] } }
|
29
|
+
| node_decl empty_childrens node_children empty_childrens { val[0].tap { |x| x.children = val[2] } }
|
30
|
+
node_decl : identifier { @output_module::Node.new(val[0]) }
|
31
|
+
| node_decl ws_plus value { val[0].tap { |x| x.arguments << val[2] } }
|
32
|
+
| node_decl ws_plus slashdash value { val[0] }
|
33
|
+
| node_decl ws_plus property { val[0].tap { |x| x.properties[val[2][0]] = val[2][1] } }
|
34
|
+
| node_decl ws_plus slashdash property { val[0] }
|
35
|
+
| node_decl ws_plus { val[0] }
|
36
|
+
empty_node : slashdash node
|
37
|
+
node_children : ws_star LBRACE nodes RBRACE { val[2] }
|
38
|
+
| ws_star LBRACE linespaces RBRACE { [] }
|
39
|
+
| ws_star LBRACE nodes unterm_node ws_star RBRACE { [*val[2], val[3]] }
|
40
|
+
| ws_star LBRACE linespaces unterm_node ws_star RBRACE { [val[3]] }
|
41
|
+
empty_children : slashdash node_children
|
42
|
+
| ws_plus empty_children
|
43
|
+
empty_childrens: empty_children | empty_childrens empty_children
|
38
44
|
node_term: linespaces | semicolon_term
|
39
45
|
semicolon_term: SEMICOLON | SEMICOLON linespaces
|
46
|
+
slashdash: SLASHDASH | slashdash ws_plus | slashdash linespaces
|
40
47
|
|
41
|
-
type : LPAREN identifier RPAREN { val[
|
48
|
+
type : LPAREN ws_star identifier ws_star RPAREN ws_star { val[2] }
|
42
49
|
|
43
|
-
identifier: IDENT { val[0].value }
|
44
|
-
|
45
|
-
|
50
|
+
identifier : IDENT { val[0].value }
|
51
|
+
| STRING { val[0].value }
|
52
|
+
| RAWSTRING { val[0].value }
|
46
53
|
|
47
|
-
property: identifier EQUALS value { [val[0], val[2]] }
|
54
|
+
property : identifier EQUALS value { [val[0], val[2]] }
|
48
55
|
|
49
56
|
value : untyped_value
|
50
57
|
| type untyped_value { val[1].as_type(val[0], @type_parsers.fetch(val[0], nil)) }
|
51
58
|
|
52
|
-
untyped_value :
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
untyped_value : IDENT { @output_module::Value::String.new(val[0].value) }
|
60
|
+
| STRING { @output_module::Value::String.new(val[0].value) }
|
61
|
+
| RAWSTRING { @output_module::Value::String.new(val[0].value) }
|
62
|
+
| INTEGER { @output_module::Value::Int.new(val[0].value) }
|
63
|
+
| FLOAT { @output_module::Value::Float.new(val[0].value, format: val[0].meta[:format]) }
|
64
|
+
| boolean { @output_module::Value::Boolean.new(val[0]) }
|
65
|
+
| NULL { @output_module::Value::Null }
|
58
66
|
|
59
67
|
boolean : TRUE { true }
|
60
68
|
| FALSE { false }
|
61
69
|
|
62
|
-
|
63
|
-
ws_star: none |
|
70
|
+
ws_plus: WS | WS ws_plus
|
71
|
+
ws_star: none | ws_plus
|
64
72
|
linespace: WS | NEWLINE | EOF
|
65
73
|
linespaces: linespace | linespaces linespace
|
66
74
|
|
@@ -68,18 +76,18 @@ nodes : none { [] }
|
|
68
76
|
|
69
77
|
---- inner
|
70
78
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@type_parsers = {}
|
76
|
-
end
|
77
|
-
@tokenizer = ::KDL::Tokenizer.new(str)
|
78
|
-
do_parse
|
79
|
+
include KDL::ParserCommon
|
80
|
+
|
81
|
+
def initialize(**options)
|
82
|
+
init(**options)
|
79
83
|
end
|
80
84
|
|
81
|
-
|
85
|
+
def parser_version
|
86
|
+
2
|
87
|
+
end
|
82
88
|
|
83
|
-
def
|
84
|
-
@tokenizer.
|
89
|
+
def parse(str)
|
90
|
+
@tokenizer = ::KDL::Tokenizer.new(str)
|
91
|
+
check_version
|
92
|
+
do_parse
|
85
93
|
end
|
data/lib/kdl/node.rb
CHANGED
@@ -1,32 +1,114 @@
|
|
1
1
|
module KDL
|
2
2
|
class Node
|
3
|
+
class Custom < Node
|
4
|
+
def self.call(node, type)
|
5
|
+
new(node.name, arguments: node.arguments, properties: node.properties, children: node.children, type:)
|
6
|
+
end
|
7
|
+
|
8
|
+
def version
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_v1
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_v2
|
17
|
+
self
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
include Enumerable
|
22
|
+
|
3
23
|
attr_accessor :name, :arguments, :properties, :children, :type
|
4
24
|
|
5
|
-
def initialize(name,
|
25
|
+
def initialize(name, _args = [], _props = {}, _children = [],
|
26
|
+
arguments: _args,
|
27
|
+
properties: _props,
|
28
|
+
children: _children,
|
29
|
+
type: nil
|
30
|
+
)
|
6
31
|
@name = name
|
7
32
|
@arguments = arguments
|
8
|
-
@properties = properties
|
33
|
+
@properties = properties.transform_keys(&:to_s)
|
9
34
|
@children = children
|
10
35
|
@type = type
|
11
36
|
end
|
12
37
|
|
13
|
-
def
|
38
|
+
def [](key)
|
39
|
+
case key
|
40
|
+
when Integer
|
41
|
+
arguments[key]&.value
|
42
|
+
when String, Symbol
|
43
|
+
properties[key.to_s]&.value
|
44
|
+
else
|
45
|
+
raise ArgumentError, "node can only be indexed by Integer, String, or Symbol"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def child(key)
|
50
|
+
case key
|
51
|
+
when Integer
|
52
|
+
children[key]
|
53
|
+
when String, Symbol
|
54
|
+
children.find { _1.name == key.to_s }
|
55
|
+
else
|
56
|
+
raise ArgumentError, "node can only be indexed by Integer, String, or Symbol"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def arg(key)
|
61
|
+
child(key)&.arguments&.first&.value
|
62
|
+
end
|
63
|
+
|
64
|
+
def args(key)
|
65
|
+
child(key)&.arguments&.map(&:value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def each_arg(key, &block)
|
69
|
+
args(key)&.each(&block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def dash_vals(key)
|
73
|
+
child(key)
|
74
|
+
&.children
|
75
|
+
&.select { _1.name == "-" }
|
76
|
+
&.map { _1.arguments.first&.value }
|
77
|
+
end
|
78
|
+
|
79
|
+
def each_dash_val(key, &block)
|
80
|
+
dash_vals(key)&.each(&block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def each(&block)
|
84
|
+
children.each(&block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def <=>(other)
|
88
|
+
name <=> other.name
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_s(level = 0, m = :to_s)
|
14
92
|
indent = ' ' * level
|
15
|
-
s = "#{indent}#{type ? "(#{id_to_s type})" : ''}#{id_to_s name}"
|
93
|
+
s = "#{indent}#{type ? "(#{id_to_s type, m })" : ''}#{id_to_s name, m}"
|
16
94
|
unless arguments.empty?
|
17
|
-
s += " #{arguments.map(
|
95
|
+
s += " #{arguments.map(&m).join(' ')}"
|
18
96
|
end
|
19
97
|
unless properties.empty?
|
20
|
-
s += " #{properties.map { |k, v| "#{id_to_s k}=#{v}" }.join(' ')}"
|
98
|
+
s += " #{properties.map { |k, v| "#{id_to_s k, m}=#{v.public_send(m)}" }.join(' ')}"
|
21
99
|
end
|
22
100
|
unless children.empty?
|
23
101
|
s += " {\n"
|
24
|
-
s += children.map { |c| "#{c.
|
25
|
-
s += "#{indent}}"
|
102
|
+
s += children.map { |c| "#{c.public_send(m, level + 1)}" }.join("\n")
|
103
|
+
s += "\n#{indent}}"
|
26
104
|
end
|
27
105
|
s
|
28
106
|
end
|
29
107
|
|
108
|
+
def inspect(level = 0)
|
109
|
+
to_s(level, :inspect)
|
110
|
+
end
|
111
|
+
|
30
112
|
def ==(other)
|
31
113
|
return false unless other.is_a?(Node)
|
32
114
|
|
@@ -45,18 +127,37 @@ module KDL
|
|
45
127
|
|
46
128
|
return self.as_type(type) if result.nil?
|
47
129
|
|
48
|
-
unless result.is_a?(::KDL::Node)
|
49
|
-
raise ArgumentError, "expected parser to return an instance of ::KDL::Node, got `#{result.class}'"
|
130
|
+
unless result.is_a?(::KDL::Node::Custom)
|
131
|
+
raise ArgumentError, "expected parser to return an instance of ::KDL::Node::Custom, got `#{result.class}'"
|
50
132
|
end
|
51
133
|
|
52
134
|
result
|
53
135
|
end
|
54
136
|
end
|
55
137
|
|
138
|
+
def version
|
139
|
+
2
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_v2
|
143
|
+
self
|
144
|
+
end
|
145
|
+
|
146
|
+
def to_v1
|
147
|
+
::KDL::V1::Node.new(name,
|
148
|
+
arguments: arguments.map(&:to_v1),
|
149
|
+
properties: properties.transform_values(&:to_v1),
|
150
|
+
children: children.map(&:to_v1),
|
151
|
+
type: type
|
152
|
+
)
|
153
|
+
end
|
154
|
+
|
56
155
|
private
|
57
156
|
|
58
|
-
def id_to_s(id)
|
59
|
-
|
157
|
+
def id_to_s(id, m = :to_s)
|
158
|
+
return id.public_send(m) unless m == :to_s
|
159
|
+
|
160
|
+
StringDumper.call(id.to_s)
|
60
161
|
end
|
61
162
|
end
|
62
163
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module KDL
|
2
|
+
module ParserCommon
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def init(parse_types: true, type_parsers: {}, output_module: ::KDL)
|
7
|
+
@output_module = output_module
|
8
|
+
if parse_types
|
9
|
+
@type_parsers = ::KDL::Types::MAPPING.merge(type_parsers)
|
10
|
+
else
|
11
|
+
@type_parsers = {}
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def next_token
|
16
|
+
@tokenizer.next_token
|
17
|
+
end
|
18
|
+
|
19
|
+
def check_version
|
20
|
+
return unless doc_version = @tokenizer.version_directive
|
21
|
+
if doc_version != parser_version
|
22
|
+
raise ParseError, "Version mismatch, document specified v#{doc_version}, but this is a v#{parser_version} parser"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/kdl/string_dumper.rb
CHANGED
@@ -1,45 +1,42 @@
|
|
1
1
|
module KDL
|
2
2
|
module StringDumper
|
3
|
-
|
4
|
-
|
5
|
-
%("#{string.each_char.map { |char| escape(char) }.join}")
|
6
|
-
end
|
7
|
-
|
8
|
-
def stringify_identifier(ident)
|
9
|
-
if bare_identifier?(ident)
|
10
|
-
ident
|
11
|
-
else
|
12
|
-
call(ident)
|
13
|
-
end
|
14
|
-
end
|
3
|
+
def call(string)
|
4
|
+
return string if bare_identifier?(string)
|
15
5
|
|
16
|
-
|
6
|
+
%("#{string.each_char.map { |char| escape(char) }.join}")
|
7
|
+
end
|
17
8
|
|
18
|
-
|
19
|
-
' ' <= char && char <= '\x7e'
|
20
|
-
end
|
9
|
+
private
|
21
10
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
end
|
11
|
+
def escape(char)
|
12
|
+
case char
|
13
|
+
when "\n" then '\n'
|
14
|
+
when "\r" then '\r'
|
15
|
+
when "\t" then '\t'
|
16
|
+
when '\\' then '\\\\'
|
17
|
+
when '"' then '\"'
|
18
|
+
when "\b" then '\b'
|
19
|
+
when "\f" then '\f'
|
20
|
+
else char
|
33
21
|
end
|
22
|
+
end
|
34
23
|
|
35
|
-
|
36
|
-
|
37
|
-
|
24
|
+
FORBIDDEN =
|
25
|
+
Tokenizer::SYMBOLS.keys +
|
26
|
+
Tokenizer::WHITESPACE +
|
27
|
+
Tokenizer::NEWLINES +
|
28
|
+
"()[]/\\\"#".chars +
|
29
|
+
("\x0".."\x20").to_a
|
38
30
|
|
39
|
-
|
40
|
-
|
41
|
-
|
31
|
+
def bare_identifier?(name)
|
32
|
+
case name
|
33
|
+
when '', 'true', 'fase', 'null', '#true', '#false', '#null', /\A\.?\d/
|
34
|
+
false
|
35
|
+
else
|
36
|
+
!name.each_char.any? { |c| FORBIDDEN.include?(c) }
|
42
37
|
end
|
43
38
|
end
|
39
|
+
|
40
|
+
extend self
|
44
41
|
end
|
45
42
|
end
|