kwalify 0.3.0 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
 
2
2
  all:
3
- kwalify -m address-book.schema.yaml
4
- kwalify -f address-book.schema.yaml address-book.yaml
3
+ kwalify -lm address-book.schema.yaml
4
+ kwalify -lf address-book.schema.yaml address-book.yaml
5
5
 
@@ -1,7 +1,7 @@
1
1
  ##
2
2
  ## Kwalify schema example for address book
3
3
  ##
4
- ## $Release: 0.3.0 $
4
+ ## $Release: 0.4.0 $
5
5
  ## copyright(c) 2005 kuwata-lab all rights reserved.
6
6
  ##
7
7
  ##
@@ -1,4 +1,4 @@
1
1
  all:
2
- kwalify -m invoice.schema.yaml
3
- kwalify -f invoice.schema.yaml invoice.yaml
2
+ kwalify -lm invoice.schema.yaml
3
+ kwalify -lf invoice.schema.yaml invoice.yaml
4
4
 
@@ -2,7 +2,7 @@
2
2
  ### Kwalify schema example for invoice
3
3
  ###
4
4
  ### $Rev: 20 $
5
- ### $Release: 0.3.0 $
5
+ ### $Release: 0.4.0 $
6
6
  ### copyright(c) 2005 kuwata-lab all rights reserved.
7
7
  ###
8
8
 
@@ -1,5 +1,5 @@
1
1
 
2
2
  all:
3
- kwalify -m tapkit.schema.yaml
4
- kwalify -f tapkit.schema.yaml tapkit.yaml
3
+ kwalify -lm tapkit.schema.yaml
4
+ kwalify -lf tapkit.schema.yaml tapkit.yaml
5
5
 
@@ -1,7 +1,7 @@
1
1
  ##
2
2
  ## Kwalify schema example for TapKit
3
3
  ##
4
- ## $Release: 0.3.0 $
4
+ ## $Release: 0.4.0 $
5
5
  ## copyright(c) 2005 kuwata-lab all rights reserved.
6
6
  ##
7
7
 
data/lib/kwalify.rb CHANGED
@@ -1,13 +1,13 @@
1
1
  ###
2
- ### $Rev: 18 $
3
- ### $Release: 0.3.0 $
2
+ ### $Rev: 26 $
3
+ ### $Release: 0.4.0 $
4
4
  ### copyright(c) 2005 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
7
7
 
8
8
  module Kwalify
9
9
 
10
- RELEASE = ("$Release: 0.3.0 $" =~ /[.\d]+/) && $&
10
+ RELEASE = ("$Release: 0.4.0 $" =~ /[.\d]+/) && $&
11
11
 
12
12
  end
13
13
 
@@ -17,3 +17,4 @@ require 'kwalify/errors'
17
17
  require 'kwalify/rule'
18
18
  require 'kwalify/validator'
19
19
  require 'kwalify/meta-validator'
20
+ require 'kwalify/parser'
@@ -1,6 +1,6 @@
1
1
  ###
2
- ### $Rev: 18 $
3
- ### $Release: 0.3.0 $
2
+ ### $Rev: 27 $
3
+ ### $Release: 0.4.0 $
4
4
  ### copyright(c) 2005 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
@@ -28,6 +28,7 @@ module Kwalify
28
28
  @value = value
29
29
  end
30
30
  attr_reader :error_symbol, :rule, :path, :value
31
+ attr_accessor :linenum
31
32
 
32
33
  def path
33
34
  return @path == '' ? "/" : @path
@@ -59,6 +60,15 @@ module Kwalify
59
60
  end
60
61
 
61
62
 
63
+ class ParseError < KwalifyError
64
+ def initialize(msg, linenum)
65
+ super("line #{linenum}: #{msg}")
66
+ @linenum = linenum
67
+ end
68
+ attr_accessor :linenum
69
+ end
70
+
71
+
62
72
  module ErrorHelper
63
73
 
64
74
  def assert_error(message="")
@@ -77,7 +87,7 @@ module Kwalify
77
87
  msg = Kwalify.msg(error_symbol)
78
88
  assert_error("error_symbol=#{error_symbol.inspect}") unless msg
79
89
  msg = msg % args if args
80
- msg = "'#{val.to_s.gsub(/\n/, '\n')}': #{msg}" if val != nil && Kwalify.scalar_class?(val.class)
90
+ msg = "'#{val.to_s.gsub(/\n/, '\n')}': #{msg}" if val != nil && Kwalify.scalar?(val)
81
91
  return error_klass.new(msg, path, val, rule, error_symbol)
82
92
  end
83
93
 
@@ -1,6 +1,6 @@
1
1
  ###
2
- ### $Rev: 18 $
3
- ### $Release: 0.3.0 $
2
+ ### $Rev: 26 $
3
+ ### $Release: 0.4.0 $
4
4
  ### copyright(c) 2005 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
@@ -62,6 +62,19 @@ module Kwalify
62
62
  private
63
63
 
64
64
 
65
+ def _load_yaml(str)
66
+ str = YamlHelper.untabify(tr) if @options[:untabify]
67
+ if @options[:linenum]
68
+ @parser = Kwalify::Parser.new(str)
69
+ doc = @parser.parse()
70
+ else
71
+ doc = YAML.load(str)
72
+ doc = doc.value if doc.is_a?(YAML::Syck::DomainType)
73
+ end
74
+ return doc
75
+ end
76
+
77
+
65
78
  def _meta_validate(filenames)
66
79
  filenames = [ nil ] if filenames.empty?
67
80
  meta_validator = @options[:meta2] ? nil : Kwalify.meta_validator()
@@ -76,9 +89,7 @@ module Kwalify
76
89
 
77
90
  def __meta_validate(meta_validator, filename=nil)
78
91
  str = filename ? File.open(filename) { |f| f.read() } : $stdin.read()
79
- str = YamlHelper.untabify(tr) if @options[:untabify]
80
- schema = YAML.load(str)
81
- schema = schema.value if schema.is_a?(YAML::Syck::DomainType)
92
+ schema = _load_yaml(str)
82
93
  filename ||= "(stdin)"
83
94
  if !schema || schema.empty?
84
95
  #* key=:meta_schema_empty msg="%s: empty.\n"
@@ -101,7 +112,25 @@ module Kwalify
101
112
  else
102
113
  #* key=:meta_validation_invalid msg="%s: NG!\n"
103
114
  s << (Kwalify.msg(:meta_validation_invalid) % filename)
104
- errors.each { |error| s << " - [#{error.path}] #{error.message}\n" }
115
+ s << _errors_to_str(errors)
116
+ end
117
+ return s
118
+ end
119
+
120
+
121
+ def _errors_to_str(errors)
122
+ s = ''
123
+ flag_linenum = @options[:linenum]
124
+ if flag_linenum
125
+ @parser.set_error_linenums(errors)
126
+ errors = errors.sort { |e1, e2| e1.linenum <=> e2.linenum }
127
+ end
128
+ errors.each do |error|
129
+ if flag_linenum
130
+ s << " - (line #{error.linenum}) [#{error.path}] #{error.message}\n"
131
+ else
132
+ s << " - [#{error.path}] #{error.message}\n"
133
+ end
105
134
  end
106
135
  return s
107
136
  end
@@ -110,9 +139,7 @@ module Kwalify
110
139
  def _validate(filenames)
111
140
  schema_filename = @options[:schema]
112
141
  str = File.open(schema_filename) { |f| f.read() }
113
- str = YamlHelper.untabify(str) if @options[:untabify]
114
- schema = YAML.load(str)
115
- schema = schema.value if schema.is_a?(YAML::Syck::DomainType)
142
+ schema = _load_yaml(str)
116
143
  validator = Kwalify::Validator.new(schema)
117
144
  #
118
145
  filenames = [ nil ] if filenames.empty?
@@ -126,14 +153,11 @@ module Kwalify
126
153
 
127
154
  def __validate(validator, filename=nil)
128
155
  str = filename ? File.open(filename) { |f| f.read() } : $stdin.read()
129
- str = YamlHelper.untabify(str) if @options[:untabify]
130
- stream = YAML::load_stream(str)
131
156
  filename ||= "(stdin)"
132
- i = -1 # or 0? *index*
133
157
  s = ''
134
- stream.documents.each do |document|
158
+ i = -1
159
+ _each_yaml_documents(str) do |document|
135
160
  i += 1
136
- document = document.value if document.is_a?(YAML::Syck::DomainType)
137
161
  if !document || document.empty?
138
162
  #* key=:schema_empty msg="%s#%d: empty.\n"
139
163
  s << (Kwalify.msg(:schema_empty) % i)
@@ -146,19 +170,32 @@ module Kwalify
146
170
  else
147
171
  #* key=:validation_invalid msg="%s#%d: INVALID\n"
148
172
  s << (Kwalify.msg(:validation_invalid) % [filename, i])
149
- errors.each { |error| s << " - [#{error.path}] #{error.message}\n" }
173
+ s << _errors_to_str(errors)
150
174
  end
151
175
  end
152
176
  return s
153
177
  end
154
178
 
155
179
 
180
+ def _each_yaml_documents(str, &block)
181
+ str = YamlHelper.untabify(str) if @options[:untabify]
182
+ if @options[:linenum]
183
+ @parser = Kwalify::Parser.new(str)
184
+ doc = @parser.parse()
185
+ yield(doc)
186
+ else
187
+ YAML::load_documents(str) do |doc|
188
+ doc = doc.value if doc.is_a?(YAML::Syck::DomainType)
189
+ yield(doc)
190
+ end
191
+ end
192
+ end
193
+
194
+
156
195
  def _inspect_schema(schema_filename)
157
196
  filename = schema_filename
158
197
  str = filename ? File.open(filename) { |f| f.read() } : $stdin.read()
159
- str = YamlHelper.untabify(tr) if @options[:untabify]
160
- schema = YAML.load(str)
161
- schema = schema.value if schema.is_a?(YAML::Syck::DomainType)
198
+ schema = _load_yaml(str)
162
199
  return nil if !schema || schema.empty?
163
200
  validator = Kwalify::Validator.new(schema) # error raised when schema is wrong
164
201
  s = validator._inspect()
@@ -169,14 +206,15 @@ module Kwalify
169
206
 
170
207
  def _usage()
171
208
  s = ''
172
- s << "Usage1: #{@command} [-hvst] -f schema.yaml document.yaml [document2.yaml ...]\n"
173
- s << "Usage2: #{@command} [-hvst] -m schema.yaml [schema2.yaml ...]\n"
209
+ s << "Usage1: #{@command} [-hvstl] -f schema.yaml document.yaml [document2.yaml ...]\n"
210
+ s << "Usage2: #{@command} [-hvstl] -m schema.yaml [schema2.yaml ...]\n"
174
211
  s << " -h, --help : help\n"
175
212
  s << " -v : version\n"
176
213
  s << " -s : silent\n"
177
214
  s << " -f schema.yaml : schema definition file\n"
178
215
  s << " -m : meta-validation mode\n"
179
216
  s << " -t : expand tab character automatically\n"
217
+ s << " -l : show linenumber when errored (experimental)\n"
180
218
  return s
181
219
  end
182
220
 
@@ -187,7 +225,7 @@ module Kwalify
187
225
 
188
226
 
189
227
  def _parse_argv(argv)
190
- opt_parser = CommandOptionParser.new("hvstmMD", "f")
228
+ opt_parser = CommandOptionParser.new("hvstlmMD", "f")
191
229
  #opts, props = opt_parser.parse(argv)
192
230
  #properties = {}
193
231
  #props.each do |pname, pvalue|
@@ -202,6 +240,7 @@ module Kwalify
202
240
  options[:version] = opts[?v] || properties[:version]
203
241
  options[:silent] = opts[?s]
204
242
  options[:schema] = opts[?f]
243
+ options[:linenum] = opts[?l]
205
244
  options[:meta] = opts[?m]
206
245
  options[:meta2] = opts[?M]
207
246
  options[:untabify] = opts[?t]
@@ -1,6 +1,6 @@
1
1
  ###
2
2
  ### $Rev: 22 $
3
- ### $Release: 0.3.0 $
3
+ ### $Release: 0.4.0 $
4
4
  ### copyright(c) 2005 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
@@ -1,6 +1,6 @@
1
1
  ###
2
- ### $Rev: 21 $
3
- ### $Release: 0.3.0 $
2
+ ### $Rev: 35 $
3
+ ### $Release: 0.4.0 $
4
4
  ### copyright(c) 2005 kuwata-lab all rights reserved.
5
5
  ###
6
6
 
@@ -98,7 +98,7 @@ mapping: &main-rule
98
98
  name: MAPPING
99
99
  type: map
100
100
  mapping:
101
- '*':
101
+ =:
102
102
  type: map
103
103
  mapping: *main-rule
104
104
  name: MAIN
@@ -110,7 +110,7 @@ END
110
110
  ## ex.
111
111
  ## meta_validator = Kwalify.meta_validator()
112
112
  ## schema = File.load_file('schema.yaml')
113
- ## errors << meta_validator.validate(schema)
113
+ ## errors = meta_validator.validate(schema)
114
114
  ## if !errors.empty?
115
115
  ## errors.each do |error|
116
116
  ## puts "[#{error.path}] #{error.message}"
@@ -269,9 +269,9 @@ END
269
269
  if hash.key?('mapping')
270
270
  val = hash['mapping']
271
271
  if val != nil && !val.is_a?(Hash)
272
- errors << validate_error(:mapping_notmap, rule, "#{path}/mapping", val)
273
- elsif val == nil || val.empty?
274
- errors << validate_error(:mapping_noelem, rule, "#{path}/mapping", val)
272
+ errors << validate_error(:mapping_notmap, rule, "#{path}/mapping", val)
273
+ elsif val == nil || (val.empty? && !val.default)
274
+ errors << validate_error(:mapping_noelem, rule, "#{path}/mapping", val)
275
275
  end
276
276
  end
277
277
  #
@@ -0,0 +1,759 @@
1
+ ###
2
+ ### $Rev: 35 $
3
+ ### $Release: 0.4.0 $
4
+ ### copyright(c) 2005 kuwata-lab all rights reserved.
5
+ ###
6
+
7
+ require 'kwalify/messages'
8
+ require 'kwalify/errors'
9
+ require 'kwalify/types'
10
+
11
+ require 'date'
12
+
13
+
14
+ module Kwalify
15
+
16
+
17
+ ##
18
+ ## ex.
19
+ ## str = ARGF.read()
20
+ ## parser = Kwalify::PlainParser.new(str)
21
+ ## doc = parser.parse()
22
+ ## p doc
23
+ ##
24
+ class PlainParser
25
+
26
+ class Alias
27
+ def initialize(label, linenum)
28
+ @label = label
29
+ @linenum = linenum
30
+ end
31
+ attr_reader :label, :linenum
32
+ end
33
+
34
+
35
+ def initialize(yaml_str)
36
+ @lines = yaml_str.to_a()
37
+ @line = nil
38
+ @linenum = 0
39
+ @anchors = {}
40
+ @aliases = {}
41
+ end
42
+
43
+
44
+ def parse()
45
+ data = parse_child(0)
46
+ if data == nil && @end_flag == '---'
47
+ data = parse_child(0)
48
+ end
49
+ resolve_aliases(data) unless @aliases.empty?
50
+ return data
51
+ end
52
+
53
+
54
+ protected
55
+
56
+
57
+ def create_sequence(linenum=nil)
58
+ return []
59
+ end
60
+
61
+ def add_to_seq(seq, value, linenum)
62
+ seq << value
63
+ end
64
+
65
+ def set_seq_at(seq, i, value, linenum)
66
+ seq[i] = value
67
+ end
68
+
69
+ def create_mapping(linenum=nil)
70
+ return {}
71
+ end
72
+
73
+ def add_to_map(map, key, value, linenum)
74
+ map[key] = value
75
+ end
76
+
77
+ def set_map_with(map, key, value, linenum)
78
+ map[key] = value
79
+ end
80
+
81
+ def set_default(map, value, linenum)
82
+ map.value = value
83
+ end
84
+
85
+ def merge_map(map, map2, linenum)
86
+ map2.each do |key, val|
87
+ map[key] = value unless map.key?(key)
88
+ end
89
+ end
90
+
91
+ def create_scalar(value, linenum=nil)
92
+ return value
93
+ end
94
+
95
+
96
+ def current_line
97
+ return @line
98
+ end
99
+
100
+ def current_linenum
101
+ return @linenum
102
+ end
103
+
104
+
105
+ private
106
+
107
+
108
+ def getline
109
+ line = _getline()
110
+ line = _getline() while line && line =~ /^\s*($|\#)/
111
+ return line
112
+ end
113
+
114
+ def _getline
115
+ @line = @lines[@linenum]
116
+ @linenum += 1
117
+ case @line
118
+ when nil ; @end_flag = 'EOF'
119
+ when /^\.\.\.$/ ; @end_flag = '...'; @line = nil
120
+ when /^---(\s+.*)?$/ ; @end_flag = '---'; @line = nil
121
+ else ; @end_flag = nil
122
+ end
123
+ return @line
124
+ end
125
+
126
+
127
+ def reset_sbuf(str)
128
+ @sbuf = str[-1] == ?\n ? str : str + "\n"
129
+ @index = -1
130
+ end
131
+
132
+
133
+ def _getchar
134
+ @index += 1
135
+ ch = @sbuf[@index]
136
+ while ch == nil
137
+ break if (line = getline()) == nil
138
+ reset_sbuf(line)
139
+ @index += 1
140
+ ch = @sbuf[@index]
141
+ end
142
+ return ch
143
+ end
144
+
145
+ def getchar
146
+ ch = _getchar()
147
+ ch = _getchar() while ch && white?(ch)
148
+ return ch
149
+ end
150
+
151
+ def getchar_or_nl
152
+ ch = _getchar()
153
+ ch = _getchar() while ch && white?(ch) && ch != ?\n
154
+ return ch
155
+ end
156
+
157
+ def current_char
158
+ return @sbuf[@index]
159
+ end
160
+
161
+
162
+ def syntax_error(msg, linenum=@linenum)
163
+ return Kwalify::ParseError.new(msg, linenum)
164
+ end
165
+
166
+
167
+ def parse_child(column)
168
+ line = getline()
169
+ return create_scalar(nil) if !line
170
+ line =~ /^( *)(.*)/
171
+ indent = $1.length
172
+ return create_scalar(nil) if indent < column
173
+ value = $2
174
+ return parse_value(column, value, indent)
175
+ end
176
+
177
+
178
+ def parse_value(column, value, value_start_column)
179
+ case value
180
+ when /^-( |$)/
181
+ data = parse_sequence(value_start_column, value)
182
+ when /^(:?[-.\w]+|'.*?'|".*?"|=|<<) *:( |$)/
183
+ #when /^:?["']?[-.\w]+["']? *:( |$)/ #'
184
+ data = parse_mapping(value_start_column, value)
185
+ when /^\[/, /^\{/
186
+ data = parse_flowstyle(column, value)
187
+ when /^\&[-\w]+( |$)/
188
+ data = parse_anchor(column, value)
189
+ when /^\*[-\w]+( |$)/
190
+ data = parse_alias(column, value)
191
+ when /^[|>]/
192
+ data = parse_block_text(column, value)
193
+ when /^!/
194
+ data = parse_tag(column, value)
195
+ when /^\#/
196
+ data = parse_child(column)
197
+ else
198
+ data = parse_scalar(column, value)
199
+ end
200
+ return data
201
+ end
202
+
203
+ def white?(ch)
204
+ return ch == ?\ || ch == ?\t || ch == ?\n || ch == ?\r
205
+ end
206
+
207
+
208
+ ##
209
+ ## flowstyle ::= flow_seq | flow_map | flow_scalar
210
+ ##
211
+ ## flow_seq ::= '[' [ flow_seq_item { ',' sp flow_seq_item } ] ']'
212
+ ## flow_seq_item ::= flowstyle
213
+ ##
214
+ ## flow_map ::= '{' [ flow_map_item { ',' sp flow_map_item } ] '}'
215
+ ## flow_map_item ::= flowstyle ':' sp flowstyle
216
+ ##
217
+ ## flow_scalar ::= string | number | boolean | symbol | date
218
+ ##
219
+
220
+ def parse_flowstyle(column, value)
221
+ reset_sbuf(value)
222
+ getchar()
223
+ data = parse_flow(0)
224
+ ch = current_char
225
+ assert ch == ?] || ch == ?}
226
+ ch = getchar_or_nl()
227
+ unless ch == ?\n || ch == ?# || ch == nil
228
+ raise syntax_error("flow style sequence is closed but got '#{ch.chr}'.")
229
+ end
230
+ getline() if ch != nil
231
+ return data
232
+ end
233
+
234
+ def parse_flow(depth)
235
+ ch = current_char()
236
+ #ch = getchar()
237
+ if ch == nil
238
+ rase syntax_error("found EOF when parsing flow style.")
239
+ end
240
+ if ch == ?[
241
+ data = parse_flow_seq(depth)
242
+ elsif ch == ?{
243
+ data = parse_flow_map(depth)
244
+ else
245
+ data = parse_flow_scalar(depth)
246
+ end
247
+ return data
248
+ end
249
+
250
+ def parse_flow_seq(depth)
251
+ assert current_char() == ?[
252
+ seq = create_sequence() # []
253
+ ch = getchar()
254
+ if ch != ?}
255
+ linenum = current_linenum()
256
+ #seq << parse_flow_seq_item(depth + 1)
257
+ add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
258
+ while (ch = current_char()) == ?,
259
+ ch = getchar()
260
+ if ch == ?]
261
+ raise syntax_error("sequence item required (or last comma is extra).")
262
+ end
263
+ #break if ch == ?]
264
+ linenum = current_linenum()
265
+ #seq << parse_flow_seq_item(depth + 1)
266
+ add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
267
+ end
268
+ end
269
+ unless current_char() == ?]
270
+ raise syntax_error("flow style sequence requires ']'.")
271
+ end
272
+ getchar() if depth > 0
273
+ return seq
274
+ end
275
+
276
+ def parse_flow_seq_item(depth)
277
+ return parse_flow(depth)
278
+ end
279
+
280
+ def parse_flow_map(depth)
281
+ assert current_char() == ?{ #}
282
+ map = create_mapping() # {}
283
+ ch = getchar()
284
+ if ch != ?}
285
+ linenum = current_linenum()
286
+ key, value = parse_flow_map_item(depth + 1)
287
+ #map[key] = value
288
+ add_to_map(map, key, value, linenum)
289
+ while (ch = current_char()) == ?,
290
+ ch = getchar()
291
+ if ch == ?}
292
+ raise syntax_error("mapping item required (or last comma is extra).")
293
+ end
294
+ #break if ch == ?}
295
+ linenum = current_linenum()
296
+ key, value = parse_flow_map_item(depth + 1)
297
+ #map[key] = value
298
+ add_to_map(map, key, value, linenum)
299
+ end
300
+ end
301
+ unless current_char() == ?}
302
+ raise syntax_error("flow style mapping requires '}'.")
303
+ end
304
+ getchar() if depth > 0
305
+ return map
306
+ end
307
+
308
+ def parse_flow_map_item(depth)
309
+ key = parse_flow(depth)
310
+ unless (ch = current_char()) == ?:
311
+ s = ch ? "'#{ch.chr}'" : "EOF"
312
+ raise syntax_error("':' expected but got #{s}.")
313
+ end
314
+ getchar()
315
+ value = parse_flow(depth)
316
+ return key, value
317
+ end
318
+
319
+ def parse_flow_scalar(depth)
320
+ case ch = current_char()
321
+ when ?", ?' #"
322
+ endch = ch
323
+ s = ''
324
+ while (ch = _getchar()) != nil && ch != endch
325
+ s << ch.chr
326
+ end
327
+ getchar()
328
+ scalar = s
329
+ else
330
+ s = ch.chr
331
+ while (ch = _getchar()) != nil && ch != ?: && ch != ?, && ch != ?] && ch != ?}
332
+ s << ch.chr
333
+ end
334
+ scalar = to_scalar(s.strip)
335
+ end
336
+ return create_scalar(scalar)
337
+ end
338
+
339
+
340
+ def parse_tag(column, value)
341
+ assert value =~ /^!\S+/
342
+ value =~ /^!(\S+)((\s+)(.*))?$/
343
+ tag = $1
344
+ space = $3
345
+ value2 = $4
346
+ if value2 && !value2.empty?
347
+ value_start_column = column + 1 + tag.length + space.length
348
+ data = parse_value(column, value2, value_start_column)
349
+ else
350
+ data = parse_child(column)
351
+ end
352
+ return data
353
+ end
354
+
355
+
356
+ def parse_anchor(column, value)
357
+ assert value =~ /^\&([-\w]+)(( *)(.*))?$/
358
+ label = $1
359
+ space = $3
360
+ value2 = $4
361
+ if value2 && !value2.empty?
362
+ #column2 = column + 1 + label.length + space.length
363
+ #data = parse_value(column2, value2)
364
+ value_start_column = column + 1 + label.length + space.length
365
+ data = parse_value(column, value2, value_start_column)
366
+ else
367
+ #column2 = column + 1
368
+ #data = parse_child(column2)
369
+ data = parse_child(column)
370
+ end
371
+ register_anchor(label, data)
372
+ return data
373
+ end
374
+
375
+ def register_anchor(label, data)
376
+ if @anchors[label]
377
+ raise syntax_error("anchor '#{label}' is already used.")
378
+ end
379
+ @anchors[label] = data
380
+ end
381
+
382
+ def parse_alias(column, value)
383
+ assert value =~ /^\*([-\w]+)(( *)(.*))?$/
384
+ label = $1
385
+ space = $3
386
+ value2 = $4
387
+ if value2 && !value2.empty? && value2[0] != ?\#
388
+ raise syntax_error("alias cannot take any data.")
389
+ end
390
+ data = @anchors[label]
391
+ unless data
392
+ data = register_alias(label)
393
+ #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
394
+ end
395
+ getline()
396
+ return data
397
+ end
398
+
399
+ def register_alias(label)
400
+ @aliases[label] ||= 0
401
+ @aliases[label] += 1
402
+ return Alias.new(label, @linenum)
403
+ end
404
+
405
+
406
+ def resolve_aliases(data)
407
+ @resolved ||= {}
408
+ return if @resolved[data.__id__]
409
+ @resolved[data.__id__] = data
410
+ case data
411
+ when Array
412
+ seq = data
413
+ seq.each_with_index do |val, i|
414
+ if val.is_a?(Alias)
415
+ anchor = val
416
+ if @anchors.key?(anchor.label)
417
+ #seq[i] = @anchors[anchor.label]
418
+ set_seq_at(seq, i, @anchors[anchor.label], anchor.linenum)
419
+ else
420
+ raise syntax_error("anchor '#{val.label}' not found", val.linenum)
421
+ end
422
+ elsif val.is_a?(Array) || val.is_a?(Hash)
423
+ resolve_aliases(val)
424
+ end
425
+ end
426
+ when Hash
427
+ map = data
428
+ map.each do |key, val|
429
+ if val.is_a?(Alias)
430
+ if @anchors.key?(val.label)
431
+ anchor = val
432
+ #map[key] = @anchors[anchor.label]
433
+ set_map_with(map, key, @anchors[anchor.label], anchor.linenum)
434
+ else
435
+ raise syntax_error("anchor '#{val.label}' not found", val.linenum)
436
+ end
437
+ elsif val.is_a?(Array) || val.is_a?(Hash)
438
+ resolve_aliases(val)
439
+ end
440
+ end
441
+ else
442
+ assert !data.is_a?(Alias)
443
+ end
444
+ end
445
+
446
+
447
+ def parse_block_text(column, value)
448
+ assert value =~ /^[>|\|]/
449
+ value =~ /^([>|\|])([-+]?)\s*(.*)$/
450
+ char = $1
451
+ indicator = $2
452
+ sep = char == "|" ? "\n" : " "
453
+ #text = $3.empty? ? '' : $3 + sep
454
+ text = $3
455
+ s = ''
456
+ empty = ''
457
+ min_indent = -1
458
+ while line = _getline()
459
+ line =~ /^( *)(.*)/
460
+ indent = $1.length
461
+ if $2.empty?
462
+ empty << "\n"
463
+ elsif indent < column
464
+ break
465
+ else
466
+ min_indent = indent if min_indent < 0 || min_indent > indent
467
+ s << empty << line
468
+ empty = ''
469
+ end
470
+ end
471
+ s << empty if indicator == '+'
472
+ s[-1] = "" if indicator == '-'
473
+ if min_indent > 0
474
+ sp = ' ' * min_indent
475
+ s.gsub!(/^#{sp}/, '')
476
+ end
477
+ if char == '>'
478
+ s.gsub!(/([^\n])\n([^\n])/, '\1 \2')
479
+ s.gsub!(/\n(\n+)/, '\1')
480
+ end
481
+ getline() if current_line() =~ /^\s*\#/
482
+ return create_scalar(text + s)
483
+ end
484
+
485
+
486
+ def parse_sequence(column, value)
487
+ assert value =~ /^-(( +)(.*))?$/
488
+ seq = create_sequence() # []
489
+ while true
490
+ unless value =~ /^-(( +)(.*))?$/
491
+ raise syntax_error("sequence item is expected.")
492
+ end
493
+ value2 = $3
494
+ space = $2
495
+ column2 = column + 1
496
+ linenum = current_linenum()
497
+ #
498
+ if !value2 || value2.empty?
499
+ elem = parse_child(column2)
500
+ else
501
+ value_start_column = column2 + space.length
502
+ elem = parse_value(column2, value2, value_start_column)
503
+ end
504
+ add_to_seq(seq, elem, linenum) #seq << elem
505
+ #
506
+ line = current_line()
507
+ break unless line
508
+ line =~ /^( *)(.*)/
509
+ indent = $1.length
510
+ if indent < column
511
+ break
512
+ elsif indent > column
513
+ raise syntax_error("invalid indent of sequence.")
514
+ end
515
+ value = $2
516
+ end
517
+ return seq
518
+ end
519
+
520
+
521
+ def parse_mapping(column, value)
522
+ #assert value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/ #'
523
+ assert value =~ /^((?::?[-.\w]+|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
524
+ map = create_mapping() # {}
525
+ while true
526
+ #unless value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/ #'
527
+ unless value =~ /^((?::?[-.\w]+|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
528
+ raise syntax_error("mapping item is expected.")
529
+ end
530
+ v = $1.strip
531
+ key = to_scalar(v)
532
+ value2 = $4
533
+ column2 = column + 1
534
+ linenum = current_linenum()
535
+ #
536
+ if !value2 || value2.empty?
537
+ elem = parse_child(column2)
538
+ else
539
+ value_start_column = column2 + $1.length + $3.length
540
+ elem = parse_value(column2, value2, value_start_column)
541
+ end
542
+ case v
543
+ when '='
544
+ set_default(map, elem, linenum)
545
+ when '<<'
546
+ merge_map(map, elem, linenum)
547
+ else
548
+ add_to_map(map, key, elem, linenum) # map[key] = elem
549
+ end
550
+ #
551
+ line = current_line()
552
+ break unless line
553
+ line =~ /^( *)(.*)/
554
+ indent = $1.length
555
+ if indent < column
556
+ break
557
+ elsif indent > column
558
+ raise syntax_error("invalid indent of mapping.")
559
+ end
560
+ value = $2
561
+ end
562
+ return map
563
+ end
564
+
565
+
566
+ def parse_scalar(indent, value)
567
+ data = create_scalar(to_scalar(value))
568
+ getline()
569
+ return data
570
+ end
571
+
572
+
573
+ def to_scalar(str)
574
+ case str
575
+ when /^"(.*)"([ \t]*\#.*$)?/ ; return $1
576
+ when /^'(.*)'([ \t]*\#.*$)?/ ; return $1
577
+ when /^(.*\S)[ \t]*\#/ ; str = $1
578
+ end
579
+
580
+ case str
581
+ when /^-?\d+$/ ; return str.to_i # integer
582
+ when /^-?\d+\.\d+$/ ; return str.to_f # float
583
+ when "true", "yes", "on" ; return true # true
584
+ when "false", "no", "off" ; return false # false
585
+ when "null", "~" ; return nil # nil
586
+ #when /^"(.*)"$/ ; return $1 # "string"
587
+ #when /^'(.*)'$/ ; return $1 # 'string'
588
+ when /^:(\w+)$/ ; return $1.intern # :symbol
589
+ when /^(\d\d\d\d)-(\d\d)-(\d\d)$/ # date
590
+ year, month, day = $1.to_i, $2.to_i, $3.to_i
591
+ return Date.new(year, month, day)
592
+ when /^(\d\d\d\d)-(\d\d)-(\d\d)(?:[Tt]|[ \t]+)(\d\d?):(\d\d):(\d\d)(\.\d*)?(?:Z|[ \t]*([-+]\d\d?)(?::(\d\d))?)?$/
593
+ year, mon, mday, hour, min, sec, usec, tzone_h, tzone_m = $1, $2, $3, $4, $5, $6, $7, $8, $9
594
+ #Time.utc(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
595
+ #t = Time.utc(sec, min, hour, mday, mon, year, nil, nil, nil, nil)
596
+ #Time.utc(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
597
+ time = Time.utc(year, mon, day, hour, min, sec, usec)
598
+ if tzone_h
599
+ diff_sec = tzone_h.to_i * 60 * 60
600
+ if tzone_m
601
+ if diff_sec > 0 ; diff_sec += tzone_m.to_i * 60
602
+ else ; diff_sec -= tzone_m.to_i * 60
603
+ end
604
+ end
605
+ p diff_sec
606
+ time -= diff_sec
607
+ end
608
+ return time
609
+ end
610
+ return str
611
+ end
612
+
613
+
614
+ def assert(bool_expr)
615
+ raise "*** assertion error" unless bool_expr
616
+ end
617
+
618
+ end
619
+
620
+
621
+
622
+ ##
623
+ ## ex.
624
+ ## # load document with Parser
625
+ ## str = ARGF.read()
626
+ ## parser = Kwalify::Parser.new(str)
627
+ ## document = parser.parse()
628
+ ##
629
+ ## # validate document
630
+ ## schema = YAML.load(File.read('schema.yaml'))
631
+ ## validator = Kwalify::Validator.new(schema)
632
+ ## errors = validator.validate(document)
633
+ ##
634
+ ## # print validation result
635
+ ## if errors && !errors.empty?
636
+ ## parser.set_error_linenums(errors)
637
+ ## errors.sort { |e1, e2| e1.linenum <=> e2.linenum }.each do |error|
638
+ ## print "line %d: path %s: %s" % [error.linenum, error.path, error.message]
639
+ ## end
640
+ ## end
641
+ ##
642
+ class Parser < PlainParser
643
+
644
+ def initialize(*args)
645
+ super
646
+ @linenums_table = {} # object_id -> hash or array
647
+ end
648
+
649
+ def parse()
650
+ @doc = super()
651
+ return @doc
652
+ end
653
+
654
+ def path_linenum(path)
655
+ return 1 if path.empty? || path == '/'
656
+ elems = path.split('/')
657
+ elems.shift if path[0] == ?/ # delete empty string on head
658
+ last_elem = elems.pop
659
+ c = @doc # collection
660
+ elems.each do |elem|
661
+ if c.is_a?(Array)
662
+ c = c[elem.to_i]
663
+ elsif c.is_a?(Hash)
664
+ c = c[elem]
665
+ else
666
+ assert false
667
+ end
668
+ end
669
+ linenums = @linenums_table[c.__id__]
670
+ if c.is_a?(Array)
671
+ linenum = linenums[last_elem.to_i]
672
+ elsif c.is_a?(Hash)
673
+ linenum = linenums[last_elem]
674
+ end
675
+ return linenum
676
+ end
677
+
678
+ def set_error_linenums(errors)
679
+ errors.each do |error|
680
+ error.linenum = path_linenum(error.path)
681
+ end
682
+ end
683
+
684
+ protected
685
+
686
+ def create_sequence(linenum=current_linenum())
687
+ seq = []
688
+ @linenums_table[seq.__id__] = []
689
+ return seq
690
+ end
691
+
692
+ def add_to_seq(seq, value, linenum)
693
+ seq << value
694
+ @linenums_table[seq.__id__] << linenum
695
+ end
696
+
697
+ def set_seq_at(seq, i, value, linenum)
698
+ seq[i] = value
699
+ @linenums_table[seq.__id__][i] = linenum
700
+ end
701
+
702
+ def create_mapping(linenum=current_linenum())
703
+ map = {}
704
+ @linenums_table[map.__id__] = {}
705
+ return map
706
+ end
707
+
708
+ def add_to_map(map, key, value, linenum)
709
+ map[key] = value
710
+ @linenums_table[map.__id__][key] = linenum
711
+ end
712
+
713
+ def set_map_with(map, key, value, linenum)
714
+ map[key] = value
715
+ @linenums_table[map.__id__][key] = linenum
716
+ end
717
+
718
+ def set_default(map, value, linenum)
719
+ map.default = value
720
+ @linenums_table[map.__id__][:'='] = linenum
721
+ end
722
+
723
+ def merge_map(map, collection, linenum)
724
+ t = @linenums_table[map.__id__]
725
+ list = collection.is_a?(Array) ? collection : [ collection ]
726
+ list.each do |m|
727
+ t2 = @linenums_table[m.__id__]
728
+ m.each do |key, val|
729
+ unless map.key?(key)
730
+ map[key] = val
731
+ t[key] = t2[key]
732
+ end
733
+ end
734
+ end
735
+ end
736
+
737
+ def create_scalar(value, linenum=current_linenum())
738
+ data = super(value)
739
+ #return Scalar.new(data, linenum)
740
+ return data
741
+ end
742
+
743
+ end
744
+
745
+ end
746
+
747
+
748
+ if __FILE__ == $0
749
+ require 'yaml'
750
+ require 'pp'
751
+ if $plain
752
+ parser = Kwalify::PlainParser.new(ARGF.read())
753
+ else
754
+ parser = Kwalify::Parser.new(ARGF.read())
755
+ end
756
+ doc = parser.parse()
757
+ pp doc
758
+ #y doc
759
+ end