kwalify 0.3.0 → 0.4.0

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