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