kdl 1.0.6 → 2.0.1

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