kwalify 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (219) hide show
  1. data/CHANGES.txt +232 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.txt +16 -19
  4. data/bin/kwalify +3 -3
  5. data/contrib/inline-require +6 -4
  6. data/contrib/kwalify +3719 -2427
  7. data/doc-api/classes/CommandOptionError.html +17 -17
  8. data/doc-api/classes/CommandOptionParser.html +63 -63
  9. data/doc-api/classes/Kwalify.html +29 -7
  10. data/doc-api/classes/Kwalify/AssertionError.html +9 -9
  11. data/doc-api/classes/Kwalify/BaseError.html +72 -71
  12. data/doc-api/classes/Kwalify/BaseParser.html +461 -0
  13. data/doc-api/classes/Kwalify/CommandOptionError.html +11 -11
  14. data/doc-api/classes/Kwalify/ErrorHelper.html +51 -46
  15. data/doc-api/classes/Kwalify/HashInterface.html +13 -135
  16. data/doc-api/classes/Kwalify/Json.html +105 -0
  17. data/doc-api/classes/Kwalify/Main.html +129 -126
  18. data/doc-api/classes/Kwalify/MetaValidator.html +248 -232
  19. data/doc-api/classes/Kwalify/Parser.html +12 -12
  20. data/doc-api/classes/Kwalify/PlainYamlParser.html +166 -163
  21. data/doc-api/classes/Kwalify/PlainYamlParser/Alias.html +11 -11
  22. data/doc-api/classes/Kwalify/Rule.html +152 -130
  23. data/doc-api/classes/Kwalify/SchemaError.html +10 -10
  24. data/doc-api/classes/Kwalify/SyntaxError.html +185 -0
  25. data/doc-api/classes/Kwalify/Types.html +26 -25
  26. data/doc-api/classes/Kwalify/Util.html +389 -0
  27. data/doc-api/classes/Kwalify/Util/HashLike.html +246 -0
  28. data/doc-api/classes/Kwalify/Util/OrderedHash.html +330 -0
  29. data/doc-api/classes/Kwalify/ValidationError.html +10 -10
  30. data/doc-api/classes/Kwalify/Validator.html +153 -86
  31. data/doc-api/classes/Kwalify/Yaml.html +181 -0
  32. data/doc-api/classes/Kwalify/Yaml/Parser.html +1538 -0
  33. data/doc-api/classes/Kwalify/YamlParser.html +190 -183
  34. data/doc-api/classes/Kwalify/YamlSyntaxError.html +8 -57
  35. data/doc-api/created.rid +1 -1
  36. data/doc-api/files/__/README_txt.html +17 -22
  37. data/doc-api/files/kwalify/errors_rb.html +2 -2
  38. data/doc-api/files/kwalify/main_rb.html +4 -3
  39. data/doc-api/files/kwalify/messages_rb.html +2 -2
  40. data/doc-api/files/kwalify/meta-validator_rb.html +3 -3
  41. data/doc-api/files/kwalify/{util/yaml-helper_rb.html → parser/base_rb.html} +8 -6
  42. data/doc-api/files/kwalify/parser/yaml_rb.html +117 -0
  43. data/doc-api/files/kwalify/rule_rb.html +2 -2
  44. data/doc-api/files/kwalify/types_rb.html +2 -2
  45. data/doc-api/files/kwalify/util/assert-text-equal_rb.html +2 -2
  46. data/doc-api/files/kwalify/util/hash-interface_rb.html +9 -2
  47. data/doc-api/files/kwalify/util/hashlike_rb.html +107 -0
  48. data/doc-api/files/kwalify/util/option-parser_rb.html +2 -2
  49. data/doc-api/files/kwalify/util/ordered-hash_rb.html +107 -0
  50. data/doc-api/files/kwalify/util/testcase-helper_rb.html +2 -2
  51. data/doc-api/files/kwalify/util_rb.html +107 -0
  52. data/doc-api/files/kwalify/validator_rb.html +2 -2
  53. data/doc-api/files/kwalify/yaml-parser_rb.html +2 -2
  54. data/doc-api/files/kwalify_rb.html +3 -2
  55. data/doc-api/fr_class_index.html +8 -1
  56. data/doc-api/fr_file_index.html +5 -1
  57. data/doc-api/fr_method_index.html +128 -69
  58. data/doc/img/fig01.png +0 -0
  59. data/doc/users-guide.html +882 -717
  60. data/examples/address-book/address-book.schema.yaml +2 -2
  61. data/examples/data-binding/BABEL.data.yaml +63 -0
  62. data/examples/data-binding/BABEL.schema.yaml +31 -0
  63. data/examples/data-binding/Makefile +8 -0
  64. data/examples/data-binding/Rakefile +13 -0
  65. data/examples/data-binding/main.rb +27 -0
  66. data/examples/invoice/invoice.schema.yaml +3 -3
  67. data/examples/tapkit/tapkit.schema.yaml +2 -2
  68. data/lib/kwalify.rb +41 -4
  69. data/lib/kwalify/errors.rb +118 -96
  70. data/lib/kwalify/kwalify.schema.yaml +58 -0
  71. data/lib/kwalify/main.rb +384 -377
  72. data/lib/kwalify/messages.rb +41 -27
  73. data/lib/kwalify/meta-validator.rb +251 -331
  74. data/lib/kwalify/parser/base.rb +127 -0
  75. data/lib/kwalify/parser/yaml.rb +837 -0
  76. data/lib/kwalify/rule.rb +545 -487
  77. data/lib/kwalify/templates/genclass-java.eruby +189 -162
  78. data/lib/kwalify/templates/genclass-php.eruby +104 -0
  79. data/lib/kwalify/templates/genclass-ruby.eruby +95 -66
  80. data/lib/kwalify/types.rb +107 -106
  81. data/lib/kwalify/util.rb +157 -0
  82. data/lib/kwalify/util/assert-text-equal.rb +33 -31
  83. data/lib/kwalify/util/hash-interface.rb +11 -30
  84. data/lib/kwalify/util/hashlike.rb +51 -0
  85. data/lib/kwalify/util/option-parser.rb +144 -144
  86. data/lib/kwalify/util/ordered-hash.rb +57 -0
  87. data/lib/kwalify/util/testcase-helper.rb +3 -3
  88. data/lib/kwalify/validator.rb +267 -212
  89. data/lib/kwalify/yaml-parser.rb +822 -768
  90. data/setup.rb +861 -607
  91. data/test/Rookbook.yaml +10 -0
  92. data/test/{tmp.dir/Context.java → data/users-guide/AddressBook.java.expected} +11 -11
  93. data/test/data/users-guide/BABEL.data.yaml +24 -0
  94. data/test/data/users-guide/BABEL.schema.yaml +30 -0
  95. data/test/data/users-guide/ExampleAddressBook.java +47 -0
  96. data/test/{tmp.dir/Group.java → data/users-guide/Group.java.expected} +2 -11
  97. data/test/data/users-guide/Person.java.expected +44 -0
  98. data/test/data/users-guide/address_book.rb +52 -0
  99. data/test/data/users-guide/address_book.schema.yaml +28 -0
  100. data/test/data/users-guide/address_book.yaml +27 -0
  101. data/test/data/users-guide/answers-schema.yaml +12 -0
  102. data/test/data/users-guide/answers-validator.rb +52 -0
  103. data/test/data/users-guide/babel_genclass.result +26 -0
  104. data/test/data/users-guide/config.schema.yaml +7 -0
  105. data/test/data/users-guide/config.yaml +4 -0
  106. data/test/{tmp.dir/silent1.document → data/users-guide/document01a.yaml} +0 -0
  107. data/test/data/users-guide/document01b.yaml +3 -0
  108. data/test/data/users-guide/document02a.yaml +4 -0
  109. data/test/data/users-guide/document02b.yaml +4 -0
  110. data/test/data/users-guide/document03a.yaml +6 -0
  111. data/test/data/users-guide/document03b.yaml +6 -0
  112. data/test/data/users-guide/document04a.yaml +9 -0
  113. data/test/data/users-guide/document04b.yaml +9 -0
  114. data/test/data/users-guide/document05a.yaml +11 -0
  115. data/test/data/users-guide/document05b.yaml +12 -0
  116. data/test/data/users-guide/document06a.yaml +15 -0
  117. data/test/data/users-guide/document06b.yaml +16 -0
  118. data/test/data/users-guide/document07a.yaml +9 -0
  119. data/test/data/users-guide/document07b.yaml +7 -0
  120. data/test/data/users-guide/document12a.json +10 -0
  121. data/test/data/users-guide/document12b.json +6 -0
  122. data/test/data/users-guide/document13a.yaml +17 -0
  123. data/test/data/users-guide/document14a.yaml +3 -0
  124. data/test/data/users-guide/document14b.yaml +3 -0
  125. data/test/data/users-guide/document15a.yaml +6 -0
  126. data/test/data/users-guide/document15b.yaml +5 -0
  127. data/test/data/users-guide/example_address_book.rb +10 -0
  128. data/test/data/users-guide/example_address_book_java.result +32 -0
  129. data/test/data/users-guide/example_address_book_ruby.result +31 -0
  130. data/test/data/users-guide/genclass_java.result +4 -0
  131. data/test/data/users-guide/howto-validation-with-parsing.rb +28 -0
  132. data/test/data/users-guide/howto-validation.rb +25 -0
  133. data/test/data/users-guide/howto3.rb +6 -0
  134. data/test/data/users-guide/howto3.result +5 -0
  135. data/test/data/users-guide/howto3.yaml +8 -0
  136. data/test/data/users-guide/howto5_databinding.result +111 -0
  137. data/test/data/users-guide/invalid01.result +3 -0
  138. data/test/data/users-guide/invalid02.result +5 -0
  139. data/test/data/users-guide/invalid03.result +5 -0
  140. data/test/data/users-guide/invalid04.result +4 -0
  141. data/test/data/users-guide/invalid05.result +11 -0
  142. data/test/data/users-guide/invalid06.result +4 -0
  143. data/test/data/users-guide/invalid07.result +3 -0
  144. data/test/data/users-guide/invalid08.result +3 -0
  145. data/test/data/users-guide/invalid12.json +8 -0
  146. data/test/data/users-guide/invalid14.result +4 -0
  147. data/test/data/users-guide/invalid15.result +4 -0
  148. data/test/data/users-guide/loadbabel.rb +27 -0
  149. data/test/data/users-guide/loadconfig.rb +15 -0
  150. data/test/data/users-guide/loadconfig.result +2 -0
  151. data/test/data/users-guide/models.rb +22 -0
  152. data/test/data/users-guide/option_ha.result +6 -0
  153. data/test/data/users-guide/option_ha_genclass_java.result +7 -0
  154. data/test/{tmp.dir/meta1.schema → data/users-guide/schema01.yaml} +0 -0
  155. data/test/data/users-guide/schema02.yaml +12 -0
  156. data/test/data/users-guide/schema03.yaml +9 -0
  157. data/test/data/users-guide/schema04.yaml +20 -0
  158. data/test/data/users-guide/schema05.yaml +29 -0
  159. data/test/data/users-guide/schema06.yaml +11 -0
  160. data/test/data/users-guide/schema12.json +12 -0
  161. data/test/data/users-guide/schema13.yaml +13 -0
  162. data/test/data/users-guide/schema14.yaml +5 -0
  163. data/test/data/users-guide/schema15.yaml +21 -0
  164. data/test/data/users-guide/valid01.result +2 -0
  165. data/test/data/users-guide/valid02.result +2 -0
  166. data/test/data/users-guide/valid03.result +2 -0
  167. data/test/data/users-guide/valid04.result +2 -0
  168. data/test/data/users-guide/valid05.result +2 -0
  169. data/test/data/users-guide/valid06.result +2 -0
  170. data/test/data/users-guide/valid07.result +2 -0
  171. data/test/data/users-guide/valid08.result +2 -0
  172. data/test/data/users-guide/valid12.result +2 -0
  173. data/test/data/users-guide/valid13.result +2 -0
  174. data/test/data/users-guide/valid14.result +2 -0
  175. data/test/data/users-guide/valid15.result +2 -0
  176. data/test/data/users-guide/validate08.rb +37 -0
  177. data/test/test-action.rb +78 -0
  178. data/test/test-action.yaml +738 -0
  179. data/test/test-databinding.rb +80 -0
  180. data/test/test-databinding.yaml +301 -0
  181. data/test/test-main.rb +129 -150
  182. data/test/test-main.yaml +126 -321
  183. data/test/test-metavalidator.rb +47 -47
  184. data/test/test-metavalidator.yaml +77 -21
  185. data/test/test-parser-yaml.rb +57 -0
  186. data/test/test-parser-yaml.yaml +1749 -0
  187. data/test/test-rule.rb +14 -15
  188. data/test/test-rule.yaml +6 -3
  189. data/test/test-users-guide.rb +75 -0
  190. data/test/test-validator.rb +77 -52
  191. data/test/test-validator.yaml +168 -6
  192. data/test/test-yaml-parser.rb +47 -0
  193. data/test/{test-yamlparser.yaml → test-yaml-parser.yaml} +159 -52
  194. data/test/test.rb +37 -21
  195. metadata +136 -37
  196. data/COPYING +0 -340
  197. data/ChangeLog +0 -70
  198. data/doc-api/classes/YamlHelper.html +0 -259
  199. data/lib/kwalify/util/yaml-helper.rb +0 -82
  200. data/test/test-yamlparser.rb +0 -58
  201. data/test/tmp.dir/User.java +0 -43
  202. data/test/tmp.dir/action1.document +0 -18
  203. data/test/tmp.dir/action1.schema +0 -32
  204. data/test/tmp.dir/action2.document +0 -18
  205. data/test/tmp.dir/action2.schema +0 -32
  206. data/test/tmp.dir/emacs.document +0 -6
  207. data/test/tmp.dir/emacs.schema +0 -6
  208. data/test/tmp.dir/meta1.document +0 -0
  209. data/test/tmp.dir/meta2.document +0 -0
  210. data/test/tmp.dir/meta2.schema +0 -3
  211. data/test/tmp.dir/silent1.schema +0 -3
  212. data/test/tmp.dir/silent2.document +0 -7
  213. data/test/tmp.dir/silent2.schema +0 -3
  214. data/test/tmp.dir/stream.invalid +0 -8
  215. data/test/tmp.dir/stream.schema +0 -3
  216. data/test/tmp.dir/stream.valid +0 -8
  217. data/test/tmp.dir/untabify.document +0 -5
  218. data/test/tmp.dir/untabify.schema +0 -10
  219. data/todo.txt +0 -34
@@ -0,0 +1,127 @@
1
+ ###
2
+ ### $Rev: 92 $
3
+ ### $Release: 0.7.0 $
4
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
5
+ ###
6
+
7
+ require 'strscan'
8
+ require 'kwalify/errors'
9
+ require 'kwalify/util'
10
+
11
+
12
+ ##
13
+ ## base class for Yaml::Parser
14
+ ##
15
+ class Kwalify::BaseParser
16
+
17
+
18
+ def reset(input, filename=nil, untabify=false)
19
+ input = Kwalify::Util.untabify(input) if untabify
20
+ @scanner = StringScanner.new(input)
21
+ @filename = filename
22
+ @linenum = 1
23
+ @column = 1
24
+ end
25
+ attr_reader :filename, :linenum, :column
26
+
27
+
28
+ def scan(regexp)
29
+ ret = @scanner.scan(regexp)
30
+ return nil if ret.nil?
31
+ _set_column_and_linenum(ret)
32
+ return ret
33
+ end
34
+
35
+
36
+ def _set_column_and_linenum(s)
37
+ pos = s.rindex(?\n)
38
+ if pos
39
+ @column = s.length - pos
40
+ @linenum += s.count("\n")
41
+ else
42
+ @column += s.length
43
+ end
44
+ end
45
+
46
+
47
+ def match?(regexp)
48
+ return @scanner.match?(regexp)
49
+ end
50
+
51
+
52
+ def group(n)
53
+ return @scanner[n]
54
+ end
55
+
56
+
57
+ def eos?
58
+ return @scanner.eos?
59
+ end
60
+
61
+
62
+ def peep(n=1)
63
+ return @scanner.peep(n)
64
+ end
65
+
66
+
67
+ def _getch
68
+ ch = @scanner.getch()
69
+ if ch == "\n"
70
+ @linenum += 1
71
+ @column = 0
72
+ else
73
+ @column += 1
74
+ end
75
+ return ch
76
+ end
77
+
78
+
79
+ CHAR_TABLE = { "\""=>"\"", "\\"=>"\\", "n"=>"\n", "r"=>"\r", "t"=>"\t", "b"=>"\b" }
80
+
81
+ def scan_string
82
+ ch = _getch()
83
+ ch == '"' || ch == "'" or raise "assertion error"
84
+ endch = ch
85
+ s = ''
86
+ while !(ch = _getch()).nil? && ch != endch
87
+ if ch != '\\'
88
+ s << ch
89
+ elsif (ch = _getch()).nil?
90
+ raise _syntax_error("%s: string is not closed." % (endch == '"' ? "'\"'" : '"\'"'))
91
+ elsif endch == '"'
92
+ if CHAR_TABLE.key?(ch)
93
+ s << CHAR_TABLE[ch]
94
+ elsif ch == 'u'
95
+ ch2 = scan(/(?:[0-9a-f][0-9a-f]){1,4}/)
96
+ unless ch2
97
+ raise _syntax_error("\\x: invalid unicode format.")
98
+ end
99
+ s << [ch2.hex].pack('U*')
100
+ elsif ch == 'x'
101
+ ch2 = scan(/[0-9a-zA-Z][0-9a-zA-Z]/)
102
+ unless ch2
103
+ raise _syntax_error("\\x: invalid binary format.")
104
+ end
105
+ s << [ch2].pack('H2')
106
+ else
107
+ s << "\\" << ch
108
+ end
109
+ elsif endch == "'"
110
+ ch == '\'' || ch == '\\' ? s << ch : s << '\\' << ch
111
+ else
112
+ raise "unreachable"
113
+ end
114
+ end
115
+ #_getch()
116
+ return s
117
+ end
118
+
119
+
120
+ def _syntax_error(message, path=nil, linenum=@linenum, column=@column)
121
+ #message = _build_message(message_key)
122
+ return _error(Kwalify::SyntaxError, message.to_s, path, linenum, column)
123
+ end
124
+ protected :_syntax_error
125
+
126
+
127
+ end
@@ -0,0 +1,837 @@
1
+ ###
2
+ ### $Rev: 93 $
3
+ ### $Release: 0.7.0 $
4
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
5
+ ###
6
+
7
+ require 'kwalify/validator'
8
+ require 'kwalify/errors'
9
+ require 'kwalify/util'
10
+ require 'kwalify/parser/base'
11
+
12
+
13
+
14
+ module Kwalify
15
+
16
+ module Yaml
17
+ end
18
+
19
+ end
20
+
21
+
22
+ ##
23
+ ## YAML parser with validator
24
+ ##
25
+ ## ex.
26
+ ## schema = YAML.load_file('schema.yaml')
27
+ ## require 'kwalify'
28
+ ## validator = Kwalify::Validator.new(schema)
29
+ ## parser = Kwalify::Yaml::Parser.new(validator) # validator is optional
30
+ ## #parser.preceding_alias = true # optional
31
+ ## #parser.data_binding = true # optional
32
+ ## ydoc = parser.parse_file('data.yaml')
33
+ ## errors = parser.errors
34
+ ## if errors && !errors.empty?
35
+ ## errors.each do |e|
36
+ ## puts "line=#{e.linenum}, path=#{e.path}, mesg=#{e.message}"
37
+ ## end
38
+ ## end
39
+ ##
40
+ class Kwalify::Yaml::Parser < Kwalify::BaseParser
41
+
42
+
43
+ alias reset_scanner reset
44
+
45
+
46
+ def initialize(validator=nil, properties={})
47
+ @validator = validator.is_a?(Hash) ? Kwalify::Validator.new(validator) : validator
48
+ @data_binding = properties[:data_binding] # enable data binding or not
49
+ @preceding_alias = properties[:preceding_alias] # allow preceding alias or not
50
+ @sequence_class = properties[:sequence_class] || Array
51
+ @mapping_class = properties[:mapping_class] || Hash
52
+ end
53
+ attr_accessor :validator # Validator
54
+ attr_accessor :data_binding # boolean
55
+ attr_accessor :preceding_alias # boolean
56
+ attr_accessor :sequence_class # Class
57
+ attr_accessor :mapping_class # Class
58
+
59
+
60
+ def reset_parser()
61
+ @anchors = {}
62
+ @errors = []
63
+ @done = {}
64
+ @preceding_aliases = []
65
+ @location_table = {} # object_id -> sequence or mapping
66
+ @doc = nil
67
+ end
68
+ attr_reader :errors
69
+
70
+
71
+ def _error(klass, message, path, linenum, column)
72
+ ex = klass.new(message)
73
+ ex.path = path.is_a?(Array) ? '/' + path.join('/') : path
74
+ ex.linenum = linenum
75
+ ex.column = column
76
+ ex.filename = @filename
77
+ return ex
78
+ end
79
+ private :_error
80
+
81
+
82
+ # def _validate_error(message, path, linenum=@linenum, column=@column)
83
+ # #message = _build_message(message_key)
84
+ # error = _error(ValidationError, message.to_s, path, linenum, column)
85
+ # @errors << error
86
+ # end
87
+ # private :_validate_error
88
+
89
+
90
+ def _set_error_info(linenum=@linenum, column=@column, &block)
91
+ len = @errors.length
92
+ yield
93
+ n = @errors.length - len
94
+ (1..n).each do |i|
95
+ error = @errors[-i]
96
+ error.linenum ||= linenum
97
+ error.column ||= column
98
+ error.filename ||= @filename
99
+ end if n > 0
100
+ end
101
+
102
+
103
+ def skip_spaces_and_comments()
104
+ scan(/\s+/)
105
+ while match?(/\#/)
106
+ scan(/.*?\n/)
107
+ scan(/\s+/)
108
+ end
109
+ end
110
+
111
+
112
+ def document_start?()
113
+ return match?(/---\s/) && @column == 1
114
+ end
115
+
116
+
117
+ def stream_end?()
118
+ return match?(/\.\.\.\s/) && @column == 1
119
+ end
120
+
121
+
122
+ def has_next?()
123
+ return !(eos? || stream_end?)
124
+ end
125
+
126
+
127
+ def parse(input=nil, opts={})
128
+ reset_scanner(input, opts[:filename], opts[:untabify]) if input
129
+ return parse_next()
130
+ end
131
+
132
+
133
+ def parse_file(filename, opts={})
134
+ opts[:filename] = filename
135
+ return parse(File.read(filename), opts)
136
+ end
137
+
138
+
139
+ def parse_next()
140
+ reset_parser()
141
+ path = []
142
+ skip_spaces_and_comments()
143
+ if document_start?()
144
+ scan(/.*\n/)
145
+ skip_spaces_and_comments()
146
+ end
147
+ _linenum = @linenum #*V
148
+ _column = @column #*V
149
+ rule = @validator ? @validator.rule : nil #*V
150
+ uniq_table = nil #*V
151
+ parent = nil #*V
152
+ val = parse_block_value(0, rule, path, uniq_table, parent)
153
+ _set_error_info(_linenum, _column) do #*V
154
+ @validator._validate(val, rule, [], @errors, @done, uniq_table, false) #*V
155
+ end if rule #*V
156
+ resolve_preceding_aliases(val) if @preceding_alias
157
+ unless eos? || document_start?() || stream_end?()
158
+ raise _syntax_error("document end expected (maybe invalid tab char found).", path)
159
+ end
160
+ @doc = val
161
+ @location_table[-1] = [_linenum, _column]
162
+ return val
163
+ end
164
+
165
+
166
+ def parse_stream(input, opts={}, &block)
167
+ reset_scanner(input, opts[:filename], opts[:untabify])
168
+ ydocs = block_given? ? nil : []
169
+ while true
170
+ ydoc = parse_next()
171
+ ydocs ? (ydocs << ydoc) : (yield ydoc)
172
+ break if eos? || stream_end?()
173
+ document_start?() or raise "** internal error"
174
+ scan(/.*\n/)
175
+ end
176
+ return ydocs
177
+ end
178
+
179
+ alias parse_documents parse_stream
180
+
181
+
182
+ MAPKEY_PATTERN = /([\w.][-\w.:]*\*?|".*?"|'.*?'|:\w+|=|<<)[ \t]*:\s+/ # :nodoc:
183
+
184
+ PRECEDING_ALIAS_PLACEHOLDER = Object.new # :nodoc:
185
+
186
+
187
+ def parse_anchor(rule, path, uniq_table, container)
188
+ name = group(1)
189
+ if @anchors.key?(name)
190
+ raise _syntax_error("&#{name}: anchor duplicated.", path,
191
+ @linenum, @column - name.length)
192
+ end
193
+ skip_spaces_and_comments()
194
+ return name
195
+ end
196
+
197
+
198
+ def parse_alias(rule, path, uniq_table, container)
199
+ name = group(1)
200
+ if @anchors.key?(name)
201
+ val = @anchors[name]
202
+ elsif @preceding_alias
203
+ @preceding_aliases << [name, rule, path.dup, container,
204
+ @linenum, @column - name.length - 1]
205
+ val = PRECEDING_ALIAS_PLACEHOLDER
206
+ else
207
+ raise _syntax_error("*#{name}: anchor not found.", path,
208
+ @linenum, @column - name.length - 1)
209
+ end
210
+ skip_spaces_and_comments()
211
+ return val
212
+ end
213
+
214
+
215
+ def resolve_preceding_aliases(val)
216
+ @preceding_aliases.each do |name, rule, path, container, _linenum, _column|
217
+ unless @anchors.key?(name)
218
+ raise _syntax_error("*#{name}: anchor not found.", path, _linenum, _column)
219
+ end
220
+ key = path[-1]
221
+ val = @anchors[name]
222
+ raise unless !container.respond_to?('[]') || container[key].equal?(PRECEDING_ALIAS_PLACEHOLDER)
223
+ if container.is_a?(Array)
224
+ container[key] = val
225
+ else
226
+ put_to_map(rule, container, key, val, _linenum, _column)
227
+ end
228
+ _set_error_info(_linenum, _column) do #*V
229
+ @validator._validate(val, rule, path, @errors, @done, false) #*V
230
+ end if rule #*V
231
+ end
232
+ end
233
+
234
+
235
+ def parse_block_value(level, rule, path, uniq_table, container)
236
+ skip_spaces_and_comments()
237
+ ## nil
238
+ return nil if @column <= level || eos?
239
+ ## anchor and alias
240
+ name = nil
241
+ if scan(/\&([-\w]+)/)
242
+ name = parse_anchor(rule, path, uniq_table, container)
243
+ elsif scan(/\*([-\w]+)/)
244
+ return parse_alias(rule, path, uniq_table, container)
245
+ end
246
+ ## type
247
+ if scan(/!!?\w+/)
248
+ skip_spaces_and_comments()
249
+ end
250
+ ## sequence
251
+ if match?(/-\s+/)
252
+ if rule && !rule.sequence
253
+ #_validate_error("sequence is not expected.", path)
254
+ rule = nil
255
+ end
256
+ seq = create_sequence(rule, @linenum, @column)
257
+ @anchors[name] = seq if name
258
+ parse_block_seq(seq, rule, path, uniq_table)
259
+ return seq
260
+ end
261
+ ## mapping
262
+ if match?(MAPKEY_PATTERN)
263
+ if rule && !rule.mapping
264
+ #_validate_error("mapping is not expected.", path)
265
+ rule = nil
266
+ end
267
+ map = create_mapping(rule, @linenum, @column)
268
+ @anchors[name] = map if name
269
+ parse_block_map(map, rule, path, uniq_table)
270
+ return map
271
+ end
272
+ ## sequence (flow-style)
273
+ if match?(/\[/)
274
+ if rule && !rule.sequence
275
+ #_validate_error("sequence is not expected.", path)
276
+ rule = nil
277
+ end
278
+ seq = create_sequence(rule, @linenum, @column)
279
+ @anchors[name] = seq if name
280
+ parse_flow_seq(seq, rule, path, uniq_table)
281
+ return seq
282
+ end
283
+ ## mapping (flow-style)
284
+ if match?(/\{/)
285
+ if rule && !rule.mapping
286
+ #_validate_error("mapping is not expected.", path)
287
+ rule = nil
288
+ end
289
+ map = create_mapping(rule, @linenum, @column)
290
+ @anchors[name] = map if name
291
+ parse_flow_map(map, rule, path, uniq_table)
292
+ return map
293
+ end
294
+ ## block text
295
+ if match?(/[|>]/)
296
+ text = parse_block_text(level, rule, path, uniq_table)
297
+ @anchors[name] = text if name
298
+ return text
299
+ end
300
+ ## scalar
301
+ scalar = parse_block_scalar(rule, path, uniq_table)
302
+ @anchors[name] = scalar if name
303
+ return scalar
304
+ end
305
+
306
+
307
+ def parse_block_seq(seq, seq_rule, path, uniq_table)
308
+ level = @column
309
+ rule = seq_rule ? seq_rule.sequence[0] : nil
310
+ path.push(nil)
311
+ i = 0
312
+ _linenum = @linenum #*V
313
+ _column = @column #*V
314
+ uniq_table = rule ? rule._uniqueness_check_table() : nil #*V
315
+ while level == @column && scan(/-\s+/)
316
+ path[-1] = i
317
+ skip_spaces_and_comments() #*V
318
+ _linenum2 = @linenum
319
+ _column2 = @column
320
+ val = parse_block_value(level, rule, path, uniq_table, seq)
321
+ add_to_seq(rule, seq, val, _linenum2, _column2) # seq << val
322
+ _set_error_info(_linenum, _column) do #*V
323
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
324
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
325
+ skip_spaces_and_comments()
326
+ i += 1
327
+ _linenum = @linenum #*V
328
+ _column = @column #*V
329
+ end
330
+ path.pop()
331
+ return seq
332
+ end
333
+
334
+
335
+ def _parse_map_value(map, map_rule, path, level, key, is_merged, uniq_table,
336
+ _linenum, _column, _linenum2, _column2) #:nodoc:
337
+ key = to_mapkey(key)
338
+ path[-1] = key
339
+ if map.respond_to?('key?') && map.key?(key) && !is_merged
340
+ raise _syntax_error("mapping key is duplicated.", path)
341
+ end
342
+ #
343
+ if key == '=' # default
344
+ val = level ? parse_block_value(level, nil, path, uniq_table, map) \
345
+ : parse_flow_value(nil, path, uniq_table, map)
346
+ map.default = val
347
+ elsif key == '<<' # merge
348
+ classobj = nil
349
+ if map_rule && map_rule.classname
350
+ map_rule = map_rule.dup()
351
+ classobj = map_rule.classobj
352
+ map_rule.classname = nil
353
+ map_rule.classobj = nil
354
+ end
355
+ val = level ? parse_block_value(level, map_rule, path, uniq_table, map) \
356
+ : parse_flow_value(map_rule, path, uniq_table, map)
357
+ if val.is_a?(Array)
358
+ val.each_with_index do |v, i|
359
+ unless v.is_a?(Hash) || (classobj && val.is_a?(classobj))
360
+ raise _syntax_error("'<<': mapping required.", path + [i])
361
+ end
362
+ end
363
+ values = val
364
+ elsif val.is_a?(Hash) || (classobj && val.is_a?(classobj))
365
+ values = [val]
366
+ else
367
+ raise _syntax_error("'<<': mapping (or sequence of mapping) required.", path)
368
+ end
369
+ #
370
+ values.each do |hash|
371
+ if !hash.is_a?(Hash)
372
+ assert_error "hash=#{hash.inspect}" unless classobj && hash.is_a?(classobj)
373
+ obj = hash
374
+ hash = {}
375
+ obj.instance_variables.each do |name|
376
+ key = name[1..-1] # '@foo' => 'foo'
377
+ val = obj.instane_variable_get(name)
378
+ hash[key] = val
379
+ end
380
+ end
381
+ for key, val in hash
382
+ path[-1] = key #*V
383
+ rule = map_rule ? map_rule.mapping[key] : nil #*V
384
+ utable = uniq_table ? uniq_table[key] : nil #*V
385
+ _validate_map_value(map, map_rule, rule, path, utable, #*V
386
+ key, val, _linenum, _column) #*V
387
+ put_to_map(rule, map, key, val, _linenum2, _column2)
388
+ end
389
+ end
390
+ is_merged = true
391
+ else # other
392
+ rule = map_rule ? map_rule.mapping[key] : nil #*V
393
+ utable = uniq_table ? uniq_table[key] : nil #*V
394
+ val = level ? parse_block_value(level, rule, path, utable, map) \
395
+ : parse_flow_value(rule, path, utable, map)
396
+ _validate_map_value(map, map_rule, rule, path, utable, key, val, #*V
397
+ _linenum, _column) #*V
398
+ put_to_map(rule, map, key, val, _linenum2, _column2)
399
+ end
400
+ return is_merged
401
+ end
402
+
403
+
404
+ def _validate_map_value(map, map_rule, rule, path, uniq_table, key, val, #*V
405
+ _linenum, _column) #*V
406
+ if map_rule && !rule #*V
407
+ #_validate_error("unknown mapping key.", path) #*V
408
+ _set_error_info(_linenum, _column) do #*V
409
+ error = Kwalify::ErrorHelper.validate_error(:key_undefined, #*V
410
+ rule, path, map, ["#{key}:"]) #*V
411
+ @errors << error #*V
412
+ #error.linenum = _linenum #*V
413
+ #error.column = _column #*V
414
+ end #*V
415
+ end #*V
416
+ _set_error_info(_linenum, _column) do #*V
417
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
418
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
419
+ end
420
+
421
+
422
+ def parse_block_map(map, map_rule, path, uniq_table)
423
+ _start_linenum = @linenum #*V
424
+ _start_column = @column #*V
425
+ level = @column
426
+ path.push(nil)
427
+ is_merged = false
428
+ while true
429
+ _linenum = @linenum #*V
430
+ _column = @column #*V
431
+ break unless level == @column && scan(MAPKEY_PATTERN)
432
+ key = group(1)
433
+ skip_spaces_and_comments() #*V
434
+ _linenum2 = @linenum #*V
435
+ _column2 = @column #*V
436
+ is_merged = _parse_map_value(map, map_rule, path, level, key, is_merged,
437
+ uniq_table, _linenum, _column, _linenum2, _column2)
438
+ #skip_spaces_and_comments()
439
+ end
440
+ path.pop()
441
+ _set_error_info(_start_linenum, _start_column) do #*V
442
+ @validator._validate_mapping_required_keys(map, map_rule, #*V
443
+ path, @errors) #*V
444
+ end if map_rule #*V
445
+ return map
446
+ end
447
+
448
+
449
+ def to_mapkey(str)
450
+ if str[0] == ?" || str[0] == ?'
451
+ return str[1..-2]
452
+ else
453
+ return to_scalar(str)
454
+ end
455
+ end
456
+ private :to_mapkey
457
+
458
+
459
+ def parse_block_scalar(rule, path, uniq_table)
460
+ _linenum = @linenum #*V
461
+ _column = @column #*V
462
+ ch = peep(1)
463
+ if ch == '"' || ch == "'"
464
+ val = scan_string()
465
+ scan(/[ \t]*(?:\#.*)?$/)
466
+ else
467
+ scan(/(.*?)[ \t]*(?:\#.*)?$/)
468
+ #str.rstrip!
469
+ val = to_scalar(group(1))
470
+ end
471
+ val = create_scalar(rule, val, _linenum, _column) #*V
472
+ #_set_error_info(_linenum, _column) do #*V
473
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
474
+ #end if uniq_table #*V
475
+ skip_spaces_and_comments()
476
+ return val
477
+ end
478
+
479
+
480
+ def parse_block_text(column, rule, path, uniq_table)
481
+ _linenum = @linenum #*V
482
+ _column = @column #*V
483
+ indicator = scan(/[|>]/)
484
+ chomping = scan(/[-+]/)
485
+ num = scan(/\d+/)
486
+ indent = num ? column + num.to_i - 1 : nil
487
+ unless scan(/[ \t]*(.*?)(\#.*)?\r?\n/) # /[ \t]*(\#.*)?\r?\n/
488
+ raise _syntax_error("Syntax Error (line break or comment are expected)", path)
489
+ end
490
+ s = group(1)
491
+ is_folded = false
492
+ while match?(/( *)(.*?)(\r?\n)/)
493
+ spaces = group(1)
494
+ text = group(2)
495
+ nl = group(3)
496
+ if indent.nil?
497
+ if spaces.length >= column
498
+ indent = spaces.length
499
+ elsif text.empty?
500
+ s << nl
501
+ scan(/.*?\n/)
502
+ next
503
+ else
504
+ @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.'
505
+ break
506
+ end
507
+ else
508
+ if spaces.length < indent && !text.empty?
509
+ @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.'
510
+ break
511
+ end
512
+ end
513
+ scan(/.*?\n/)
514
+ if indicator == '|'
515
+ s << spaces[indent..-1] if spaces.length >= indent
516
+ s << text << nl
517
+ else # indicator == '>'
518
+ if !text.empty? && spaces.length == indent
519
+ if s.sub!(/\r?\n((\r?\n)+)\z/, '\1')
520
+ nil
521
+ elsif is_folded
522
+ s.sub!(/\r?\n\z/, ' ')
523
+ end
524
+ #s.sub!(/\r?\n\z/, '') if !s.sub!(/\r?\n(\r?\n)+\z/, '\1') && is_folded
525
+ is_folded = true
526
+ else
527
+ is_folded = false
528
+ s << spaces[indent..-1] if spaces.length > indent
529
+ end
530
+ s << text << nl
531
+ end
532
+ end
533
+ ## chomping
534
+ if chomping == '+'
535
+ nil
536
+ elsif chomping == '-'
537
+ s.sub!(/(\r?\n)+\z/, '')
538
+ else
539
+ s.sub!(/(\r?\n)(\r?\n)+\z/, '\1')
540
+ end
541
+ #
542
+ skip_spaces_and_comments()
543
+ val = s
544
+ #_set_error_info(_linenum, _column) do #*V
545
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
546
+ #end if uniq_table #*V
547
+ return val
548
+ end
549
+
550
+
551
+ def parse_flow_value(rule, path, uniq_table, container)
552
+ skip_spaces_and_comments()
553
+ ## anchor and alias
554
+ name = nil
555
+ if scan(/\&([-\w]+)/)
556
+ name = parse_anchor(rule, path, uniq_table, container)
557
+ elsif scan(/\*([-\w]+)/)
558
+ return parse_alias(rule, path, uniq_table, container)
559
+ end
560
+ ## type
561
+ if scan(/!!?\w+/)
562
+ skip_spaces_and_comments()
563
+ end
564
+ ## sequence
565
+ if match?(/\[/)
566
+ if rule && !rule.sequence #*V
567
+ #_validate_error("sequence is not expected.", path) #*V
568
+ rule = nil #*V
569
+ end #*V
570
+ seq = create_sequence(rule, @linenum, @column)
571
+ @anchors[name] = seq if name
572
+ parse_flow_seq(seq, rule, path, uniq_table)
573
+ return seq
574
+ end
575
+ ## mapping
576
+ if match?(/\{/)
577
+ if rule && !rule.mapping #*V
578
+ #_validate_error("mapping is not expected.", path) #*V
579
+ rule = nil #*V
580
+ end #*V
581
+ map = create_mapping(rule, @linenum, @column)
582
+ @anchors[name] = map if name
583
+ parse_flow_map(map, rule, path, uniq_table)
584
+ return map
585
+ end
586
+ ## scalar
587
+ scalar = parse_flow_scalar(rule, path, uniq_table)
588
+ @anchors[name] = scalar if name
589
+ return scalar
590
+ end
591
+
592
+
593
+ def parse_flow_seq(seq, seq_rule, path, uniq_table)
594
+ #scan(/\[\s*/)
595
+ scan(/\[/)
596
+ skip_spaces_and_comments()
597
+ if scan(/\]/)
598
+ nil
599
+ else
600
+ rule = seq_rule ? seq_rule.sequence[0] : nil #*V
601
+ uniq_table = rule ? rule._uniqueness_check_table() : nil #*V
602
+ path.push(nil)
603
+ i = 0
604
+ while true
605
+ path[-1] = i
606
+ _linenum = @linenum #*V
607
+ _column = @column #*V
608
+ val = parse_flow_value(rule, path, uniq_table, seq)
609
+ add_to_seq(rule, seq, val, _linenum, _column) # seq << val
610
+ _set_error_info(_linenum, _column) do #*V
611
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
612
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
613
+ skip_spaces_and_comments()
614
+ break unless scan(/,\s+/)
615
+ i += 1
616
+ if match?(/\]/)
617
+ raise _syntax_error("sequence item required (or last comma is extra).", path)
618
+ end
619
+ end
620
+ path.pop()
621
+ unless scan(/\]/)
622
+ raise _syntax_error("flow sequence is not closed by ']'.", path)
623
+ end
624
+ end
625
+ skip_spaces_and_comments()
626
+ return seq
627
+ end
628
+
629
+
630
+ def parse_flow_map(map, map_rule, path, uniq_table)
631
+ #scan(/\{\s*/) # not work?
632
+ _start_linenum = @linenum #*V
633
+ _start_column = @column #*V
634
+ scan(/\{/)
635
+ skip_spaces_and_comments()
636
+ if scan(/\}/)
637
+ nil
638
+ else
639
+ path.push(nil)
640
+ is_merged = false
641
+ while true
642
+ _linenum = @linenum #*V
643
+ _column = @column #*V
644
+ unless scan(MAPKEY_PATTERN)
645
+ raise _syntax_error("mapping key is expected.", path)
646
+ end
647
+ key = group(1)
648
+ skip_spaces_and_comments()
649
+ _linenum2 = @linenum #*V
650
+ _column2 = @column #*V
651
+ is_merged = _parse_map_value(map, map_rule, path, nil, key, is_merged,
652
+ uniq_table, _linenum, _column, _linenum2, _column2)
653
+ #skip_spaces_and_comments()
654
+ break unless scan(/,\s+/)
655
+ end
656
+ path.pop()
657
+ unless scan(/\}/)
658
+ raise _syntax_error("flow mapping is not closed by '}'.", path)
659
+ end
660
+ end
661
+ skip_spaces_and_comments()
662
+ _set_error_info(_start_linenum, _start_column) do #*V
663
+ @validator._validate_mapping_required_keys(map, map_rule, path, @errors) #*V
664
+ end if map_rule #*V
665
+ return map
666
+ end
667
+
668
+
669
+ def parse_flow_scalar(rule, path, uniq_table)
670
+ ch = peep(1)
671
+ _linenum = @linenum #*V
672
+ _column = @column #*V
673
+ if ch == '"' || ch == "'"
674
+ val = scan_string()
675
+ else
676
+ str = scan(/[^,\]\}\#]*/)
677
+ if match?(/,\S/)
678
+ while match?(/,\S/)
679
+ str << scan(/./)
680
+ str << scan(/[^,\]\}\#]*/)
681
+ end
682
+ end
683
+ str.rstrip!
684
+ val = to_scalar(str)
685
+ end
686
+ val = create_scalar(rule, val, _linenum, _column) #*V
687
+ #_set_error_info(_linenum, _column) do #*V
688
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
689
+ #end if uniq_table #*V
690
+ skip_spaces_and_comments()
691
+ return val
692
+ end
693
+
694
+
695
+ ####
696
+
697
+
698
+ def to_scalar(str)
699
+ case str
700
+ when nil ; val = nil
701
+ when /\A-?\d+\.\d+\z/ ; val = str.to_f
702
+ when /\A-?\d+\z/ ; val = str.to_i
703
+ when /\A(true|yes)\z/ ; val = true
704
+ when /\A(false|no)\z/ ; val = false
705
+ when /\A(null|~)\z/ ; val = nil
706
+ when /\A"(.*)"\z/ ; val = $1
707
+ when /\A'(.*)'\z/ ; val = $1
708
+ when /\A:(\w+)\z/ ; val = $1.intern
709
+ when /\A(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d):(\d\d))?\z/
710
+ year, month, day, hour, min, sec = $1, $2, $3, $4, $5, $6
711
+ if hour
712
+ val = Time.mktime(year, month, day, hour, min, sec)
713
+ else
714
+ val = Date.new(year.to_i, month.to_i, day.to_i)
715
+ end
716
+ ## or
717
+ #params = [$1, $2, $3, $4, $5, $6]
718
+ #val = Time.mktime(*params)
719
+ else
720
+ val = str.empty? ? nil : str
721
+ end
722
+ skip_spaces_and_comments()
723
+ return val
724
+ end
725
+
726
+
727
+ ##
728
+
729
+ protected
730
+
731
+
732
+ def create_sequence(rule, linenum, column)
733
+ seq = @sequence_class.new
734
+ @location_table[seq.__id__] = []
735
+ return seq
736
+ end
737
+
738
+
739
+ def create_mapping(rule, linenum, column)
740
+ if rule && rule.classobj && @data_binding
741
+ classobj = rule.classobj
742
+ map = classobj.new
743
+ else
744
+ classobj = nil
745
+ map = @mapping_class.new # {}
746
+ end
747
+ @location_table[map.__id__] = hash = {}
748
+ hash[:classobj] = classobj if classobj
749
+ return map
750
+ end
751
+
752
+
753
+ def create_scalar(rule, value, linenum, column)
754
+ return value
755
+ end
756
+
757
+
758
+ def add_to_seq(rule, seq, val, linenum, column)
759
+ seq << val
760
+ @location_table[seq.__id__] << [linenum, column]
761
+ end
762
+
763
+
764
+ def put_to_map(rule, map, key, val, linenum, column)
765
+ #if map.is_a?(Hash)
766
+ # map[key] = val
767
+ #elsif map.respond_to?(name="#{key}=")
768
+ # map.__send__(name, val)
769
+ #elsif map.respond_to?('[]=')
770
+ # map[key] = val
771
+ #else
772
+ # map.instance_variable_set("@#{key}", val)
773
+ #end
774
+ map[key] = val
775
+ @location_table[map.__id__][key] = [linenum, column]
776
+ end
777
+
778
+
779
+ def _getclass(classname)
780
+ mod = Object
781
+ classname.split(/::/).each do |modname|
782
+ mod = mod.const_get(modname) # raises NameError when module not found
783
+ end
784
+ return mod
785
+ end
786
+
787
+
788
+ public
789
+
790
+
791
+ def location(path)
792
+ if path.empty? || path == '/'
793
+ return @location_table[-1] # return value is [linenum, column]
794
+ end
795
+ if path.is_a?(Array)
796
+ items = path.collect { |item| to_scalar(item) }
797
+ elsif path.is_a?(String)
798
+ items = path.split('/').collect { |item| to_scalar(item) }
799
+ items.shift if path[0] == ?/ # delete empty string on head
800
+ else
801
+ raise ArgumentError.new("path should be Array or String.")
802
+ end
803
+ last_item = items.pop()
804
+ c = @doc # collection
805
+ items.each do |item|
806
+ if c.is_a?(Array)
807
+ c = c[item.to_i]
808
+ elsif c.is_a?(Hash)
809
+ c = c[item]
810
+ elsif (table = @location_table[c.__id__]) && table[:classobj]
811
+ if c.respond_to?(item)
812
+ c = c.__send__(item)
813
+ elsif c.respond_to?("[]=")
814
+ c = c[item]
815
+ else
816
+ assert false
817
+ end
818
+ else
819
+ #assert false
820
+ raise ArgumentError.new("#{path.inspect}: invalid path.")
821
+ end
822
+ end
823
+ collection = @location_table[c.__id__]
824
+ return nil if collection.nil?
825
+ index = c.is_a?(Array) ? last_item.to_i : last_item
826
+ return collection[index] # return value is [linenum, column]
827
+ end
828
+
829
+
830
+ def set_errors_linenum(errors)
831
+ errors.each do |error|
832
+ error.linenum, error.column = location(error.path)
833
+ end
834
+ end
835
+
836
+
837
+ end