dc-kwalify 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. data/CHANGES.txt +252 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.txt +61 -0
  4. data/bin/kwalify +13 -0
  5. data/contrib/inline-require +179 -0
  6. data/contrib/kwalify +4160 -0
  7. data/doc/docstyle.css +188 -0
  8. data/doc/img/fig01.png +0 -0
  9. data/doc/users-guide.html +2050 -0
  10. data/doc-api/classes/CommandOptionError.html +184 -0
  11. data/doc-api/classes/CommandOptionParser.html +325 -0
  12. data/doc-api/classes/Kwalify/AssertionError.html +148 -0
  13. data/doc-api/classes/Kwalify/BaseError.html +297 -0
  14. data/doc-api/classes/Kwalify/BaseParser.html +461 -0
  15. data/doc-api/classes/Kwalify/CommandOptionError.html +168 -0
  16. data/doc-api/classes/Kwalify/ErrorHelper.html +223 -0
  17. data/doc-api/classes/Kwalify/HashInterface.html +118 -0
  18. data/doc-api/classes/Kwalify/Json.html +105 -0
  19. data/doc-api/classes/Kwalify/KwalifyError.html +111 -0
  20. data/doc-api/classes/Kwalify/Main.html +339 -0
  21. data/doc-api/classes/Kwalify/MetaValidator.html +448 -0
  22. data/doc-api/classes/Kwalify/Parser.html +155 -0
  23. data/doc-api/classes/Kwalify/PlainYamlParser/Alias.html +165 -0
  24. data/doc-api/classes/Kwalify/PlainYamlParser.html +523 -0
  25. data/doc-api/classes/Kwalify/Rule.html +433 -0
  26. data/doc-api/classes/Kwalify/SchemaError.html +148 -0
  27. data/doc-api/classes/Kwalify/SyntaxError.html +185 -0
  28. data/doc-api/classes/Kwalify/Types.html +302 -0
  29. data/doc-api/classes/Kwalify/Util/HashLike.html +246 -0
  30. data/doc-api/classes/Kwalify/Util/OrderedHash.html +330 -0
  31. data/doc-api/classes/Kwalify/Util.html +390 -0
  32. data/doc-api/classes/Kwalify/ValidationError.html +148 -0
  33. data/doc-api/classes/Kwalify/Validator.html +381 -0
  34. data/doc-api/classes/Kwalify/Yaml/Parser.html +1538 -0
  35. data/doc-api/classes/Kwalify/Yaml.html +194 -0
  36. data/doc-api/classes/Kwalify/YamlParser.html +542 -0
  37. data/doc-api/classes/Kwalify/YamlSyntaxError.html +119 -0
  38. data/doc-api/classes/Kwalify.html +292 -0
  39. data/doc-api/classes/Test/Unit.html +101 -0
  40. data/doc-api/classes/Test.html +107 -0
  41. data/doc-api/created.rid +1 -0
  42. data/doc-api/files/__/README_txt.html +172 -0
  43. data/doc-api/files/kwalify/errors_rb.html +114 -0
  44. data/doc-api/files/kwalify/main_rb.html +118 -0
  45. data/doc-api/files/kwalify/messages_rb.html +107 -0
  46. data/doc-api/files/kwalify/meta-validator_rb.html +117 -0
  47. data/doc-api/files/kwalify/parser/base_rb.html +116 -0
  48. data/doc-api/files/kwalify/parser/yaml_rb.html +117 -0
  49. data/doc-api/files/kwalify/rule_rb.html +116 -0
  50. data/doc-api/files/kwalify/types_rb.html +114 -0
  51. data/doc-api/files/kwalify/util/assert-text-equal_rb.html +115 -0
  52. data/doc-api/files/kwalify/util/hash-interface_rb.html +114 -0
  53. data/doc-api/files/kwalify/util/hashlike_rb.html +107 -0
  54. data/doc-api/files/kwalify/util/option-parser_rb.html +107 -0
  55. data/doc-api/files/kwalify/util/ordered-hash_rb.html +107 -0
  56. data/doc-api/files/kwalify/util/testcase-helper_rb.html +115 -0
  57. data/doc-api/files/kwalify/util_rb.html +107 -0
  58. data/doc-api/files/kwalify/validator_rb.html +117 -0
  59. data/doc-api/files/kwalify/yaml-parser_rb.html +117 -0
  60. data/doc-api/files/kwalify_rb.html +121 -0
  61. data/doc-api/fr_class_index.html +57 -0
  62. data/doc-api/fr_file_index.html +45 -0
  63. data/doc-api/fr_method_index.html +168 -0
  64. data/doc-api/index.html +24 -0
  65. data/doc-api/rdoc-style.css +208 -0
  66. data/examples/address-book/Makefile +10 -0
  67. data/examples/address-book/address-book.schema.yaml +45 -0
  68. data/examples/address-book/address-book.yaml +36 -0
  69. data/examples/data-binding/BABEL.data.yaml +63 -0
  70. data/examples/data-binding/BABEL.schema.yaml +31 -0
  71. data/examples/data-binding/Makefile +8 -0
  72. data/examples/data-binding/Rakefile +13 -0
  73. data/examples/data-binding/main.rb +27 -0
  74. data/examples/invoice/Makefile +9 -0
  75. data/examples/invoice/invoice.schema.yaml +43 -0
  76. data/examples/invoice/invoice.yaml +32 -0
  77. data/examples/tapkit/Makefile +10 -0
  78. data/examples/tapkit/main.rb +7 -0
  79. data/examples/tapkit/tapkit.schema.yaml +146 -0
  80. data/examples/tapkit/tapkit.yaml +85 -0
  81. data/lib/kwalify/errors.rb +127 -0
  82. data/lib/kwalify/kwalify.schema.yaml +58 -0
  83. data/lib/kwalify/main.rb +442 -0
  84. data/lib/kwalify/messages.rb +173 -0
  85. data/lib/kwalify/meta-validator.rb +275 -0
  86. data/lib/kwalify/parser/base.rb +127 -0
  87. data/lib/kwalify/parser/yaml.rb +841 -0
  88. data/lib/kwalify/rule.rb +559 -0
  89. data/lib/kwalify/templates/genclass-java.eruby +222 -0
  90. data/lib/kwalify/templates/genclass-php.eruby +104 -0
  91. data/lib/kwalify/templates/genclass-ruby.eruby +113 -0
  92. data/lib/kwalify/types.rb +156 -0
  93. data/lib/kwalify/util/assert-text-equal.rb +46 -0
  94. data/lib/kwalify/util/hash-interface.rb +18 -0
  95. data/lib/kwalify/util/hashlike.rb +51 -0
  96. data/lib/kwalify/util/option-parser.rb +220 -0
  97. data/lib/kwalify/util/ordered-hash.rb +57 -0
  98. data/lib/kwalify/util/testcase-helper.rb +112 -0
  99. data/lib/kwalify/util.rb +158 -0
  100. data/lib/kwalify/validator.rb +282 -0
  101. data/lib/kwalify/yaml-parser.rb +870 -0
  102. data/lib/kwalify.rb +67 -0
  103. data/setup.rb +1585 -0
  104. data/test/Rookbook.yaml +10 -0
  105. data/test/data/users-guide/AddressBook.java.expected +40 -0
  106. data/test/data/users-guide/BABEL.data.yaml +24 -0
  107. data/test/data/users-guide/BABEL.schema.yaml +30 -0
  108. data/test/data/users-guide/ExampleAddressBook.java +47 -0
  109. data/test/data/users-guide/Group.java.expected +24 -0
  110. data/test/data/users-guide/Person.java.expected +44 -0
  111. data/test/data/users-guide/address_book.rb +52 -0
  112. data/test/data/users-guide/address_book.schema.yaml +28 -0
  113. data/test/data/users-guide/address_book.yaml +27 -0
  114. data/test/data/users-guide/answers-schema.yaml +12 -0
  115. data/test/data/users-guide/answers-validator.rb +52 -0
  116. data/test/data/users-guide/babel_genclass.result +26 -0
  117. data/test/data/users-guide/config.schema.yaml +7 -0
  118. data/test/data/users-guide/config.yaml +4 -0
  119. data/test/data/users-guide/document01a.yaml +3 -0
  120. data/test/data/users-guide/document01b.yaml +3 -0
  121. data/test/data/users-guide/document02a.yaml +4 -0
  122. data/test/data/users-guide/document02b.yaml +4 -0
  123. data/test/data/users-guide/document03a.yaml +6 -0
  124. data/test/data/users-guide/document03b.yaml +6 -0
  125. data/test/data/users-guide/document04a.yaml +9 -0
  126. data/test/data/users-guide/document04b.yaml +9 -0
  127. data/test/data/users-guide/document05a.yaml +11 -0
  128. data/test/data/users-guide/document05b.yaml +12 -0
  129. data/test/data/users-guide/document06a.yaml +15 -0
  130. data/test/data/users-guide/document06b.yaml +16 -0
  131. data/test/data/users-guide/document07a.yaml +9 -0
  132. data/test/data/users-guide/document07b.yaml +7 -0
  133. data/test/data/users-guide/document12a.json +10 -0
  134. data/test/data/users-guide/document12b.json +6 -0
  135. data/test/data/users-guide/document13a.yaml +17 -0
  136. data/test/data/users-guide/document14a.yaml +3 -0
  137. data/test/data/users-guide/document14b.yaml +3 -0
  138. data/test/data/users-guide/document15a.yaml +6 -0
  139. data/test/data/users-guide/document15b.yaml +5 -0
  140. data/test/data/users-guide/example_address_book.rb +10 -0
  141. data/test/data/users-guide/example_address_book_java.result +32 -0
  142. data/test/data/users-guide/example_address_book_ruby.result +31 -0
  143. data/test/data/users-guide/genclass_java.result +4 -0
  144. data/test/data/users-guide/howto-validation-with-parsing.rb +28 -0
  145. data/test/data/users-guide/howto-validation.rb +25 -0
  146. data/test/data/users-guide/howto3.rb +6 -0
  147. data/test/data/users-guide/howto3.result +5 -0
  148. data/test/data/users-guide/howto3.yaml +8 -0
  149. data/test/data/users-guide/howto5_databinding.result +111 -0
  150. data/test/data/users-guide/invalid01.result +3 -0
  151. data/test/data/users-guide/invalid02.result +5 -0
  152. data/test/data/users-guide/invalid03.result +5 -0
  153. data/test/data/users-guide/invalid04.result +4 -0
  154. data/test/data/users-guide/invalid05.result +11 -0
  155. data/test/data/users-guide/invalid06.result +4 -0
  156. data/test/data/users-guide/invalid07.result +3 -0
  157. data/test/data/users-guide/invalid08.result +3 -0
  158. data/test/data/users-guide/invalid12.json +8 -0
  159. data/test/data/users-guide/invalid14.result +4 -0
  160. data/test/data/users-guide/invalid15.result +4 -0
  161. data/test/data/users-guide/loadbabel.rb +27 -0
  162. data/test/data/users-guide/loadconfig.rb +16 -0
  163. data/test/data/users-guide/loadconfig.result +6 -0
  164. data/test/data/users-guide/models.rb +22 -0
  165. data/test/data/users-guide/option_ha.result +6 -0
  166. data/test/data/users-guide/option_ha_genclass_java.result +7 -0
  167. data/test/data/users-guide/schema01.yaml +3 -0
  168. data/test/data/users-guide/schema02.yaml +12 -0
  169. data/test/data/users-guide/schema03.yaml +9 -0
  170. data/test/data/users-guide/schema04.yaml +20 -0
  171. data/test/data/users-guide/schema05.yaml +29 -0
  172. data/test/data/users-guide/schema06.yaml +11 -0
  173. data/test/data/users-guide/schema12.json +12 -0
  174. data/test/data/users-guide/schema13.yaml +13 -0
  175. data/test/data/users-guide/schema14.yaml +5 -0
  176. data/test/data/users-guide/schema15.yaml +21 -0
  177. data/test/data/users-guide/valid01.result +2 -0
  178. data/test/data/users-guide/valid02.result +2 -0
  179. data/test/data/users-guide/valid03.result +2 -0
  180. data/test/data/users-guide/valid04.result +2 -0
  181. data/test/data/users-guide/valid05.result +2 -0
  182. data/test/data/users-guide/valid06.result +2 -0
  183. data/test/data/users-guide/valid07.result +2 -0
  184. data/test/data/users-guide/valid08.result +2 -0
  185. data/test/data/users-guide/valid12.result +2 -0
  186. data/test/data/users-guide/valid13.result +2 -0
  187. data/test/data/users-guide/valid14.result +2 -0
  188. data/test/data/users-guide/valid15.result +2 -0
  189. data/test/data/users-guide/validate08.rb +37 -0
  190. data/test/test-action.rb +78 -0
  191. data/test/test-action.yaml +738 -0
  192. data/test/test-databinding.rb +83 -0
  193. data/test/test-databinding.yaml +339 -0
  194. data/test/test-main.rb +157 -0
  195. data/test/test-main.yaml +415 -0
  196. data/test/test-metavalidator.rb +80 -0
  197. data/test/test-metavalidator.yaml +1179 -0
  198. data/test/test-parser-yaml.rb +57 -0
  199. data/test/test-parser-yaml.yaml +1749 -0
  200. data/test/test-rule.rb +26 -0
  201. data/test/test-rule.yaml +317 -0
  202. data/test/test-users-guide.rb +75 -0
  203. data/test/test-util.rb +125 -0
  204. data/test/test-validator.rb +95 -0
  205. data/test/test-validator.yaml +986 -0
  206. data/test/test-yaml-parser.rb +47 -0
  207. data/test/test-yaml-parser.yaml +1226 -0
  208. data/test/test.rb +61 -0
  209. metadata +274 -0
data/contrib/kwalify ADDED
@@ -0,0 +1,4160 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ###
4
+ ### $Rev$
5
+ ### $Release: 0.7.2 $
6
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
7
+ ###
8
+
9
+ #--begin of require 'kwalify'
10
+ ###
11
+ ### $Rev$
12
+ ### $Release: 0.7.2 $
13
+ ### copyright(c) 2006 kuwata-lab.com all rights reserved.
14
+ ###
15
+
16
+
17
+ module Kwalify
18
+
19
+ RELEASE = ("$Release: 0.7.2 $" =~ /[.\d]+/) && $&
20
+
21
+ end
22
+
23
+ #--begin of require 'kwalify/types'
24
+ ###
25
+ ### $Rev$
26
+ ### $Release: 0.7.2 $
27
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
28
+ ###
29
+
30
+ require 'date'
31
+
32
+
33
+ module Kwalify
34
+ module Boolean # :nodoc:
35
+ end
36
+ end
37
+ class TrueClass # :nodoc:
38
+ include Kwalify::Boolean
39
+ end
40
+ class FalseClass # :nodoc:
41
+ include Kwalify::Boolean
42
+ end
43
+ #module Boolean; end
44
+ #class TrueClass
45
+ # include Boolean
46
+ #end
47
+ #class FalseClass
48
+ # include Boolean
49
+ #end
50
+
51
+
52
+ module Kwalify
53
+ module Text # :nodoc:
54
+ end
55
+ end
56
+ class String # :nodoc:
57
+ include Kwalify::Text
58
+ end
59
+ class Numeric # :nodoc:
60
+ include Kwalify::Text
61
+ end
62
+ #module Text; end
63
+ #class String
64
+ # include Text
65
+ #end
66
+ #class Numeric
67
+ # include Text
68
+ #end
69
+
70
+
71
+ module Kwalify
72
+ module Scalar # :nodoc:
73
+ end
74
+ end
75
+ class String # :nodoc:
76
+ include Kwalify::Scalar
77
+ end
78
+ class Numeric # :nodoc:
79
+ include Kwalify::Scalar
80
+ end
81
+ class Date # :nodoc:
82
+ include Kwalify::Scalar
83
+ end
84
+ class Time # :nodoc:
85
+ include Kwalify::Scalar
86
+ end
87
+ class TrueClass # :nodoc:
88
+ include Kwalify::Scalar
89
+ end
90
+ class FalseClass # :nodoc:
91
+ include Kwalify::Scalar
92
+ end
93
+ class NilClass # :nodoc:
94
+ include Kwalify::Scalar
95
+ end
96
+ module Kwalify
97
+ module Text # :nodoc:
98
+ include Kwalify::Scalar
99
+ end
100
+ end
101
+
102
+
103
+ module Kwalify
104
+
105
+
106
+ module Types
107
+
108
+
109
+ DEFAULT_TYPE = "str" ## use "str" as default of @type
110
+
111
+ @@type_table = {
112
+ "seq" => Array,
113
+ "map" => Hash,
114
+ "str" => String,
115
+ #"string" => String,
116
+ "text" => Text,
117
+ "int" => Integer,
118
+ #"integer" => Integer,
119
+ "float" => Float,
120
+ "number" => Numeric,
121
+ #"numeric" => Numeric,
122
+ "date" => Date,
123
+ "time" => Time,
124
+ "timestamp" => Time,
125
+ "bool" => Boolean,
126
+ #"boolean" => Boolean,
127
+ #"object" => Object,
128
+ "any" => Object,
129
+ "scalar" => Scalar,
130
+ }
131
+
132
+ def self.type_table
133
+ return @@type_table
134
+ end
135
+
136
+ def self.type_class(type)
137
+ klass = @@type_table[type]
138
+ #assert_error('type=#{type.inspect}') unless klass
139
+ return klass
140
+ end
141
+
142
+ def self.get_type_class(type)
143
+ return type_class(type)
144
+ end
145
+
146
+
147
+
148
+ #--
149
+ #def collection_class?(klass)
150
+ # return klass.is_a?(Array) || klass.is_a?(Hash)
151
+ #end
152
+ #
153
+ #def scalar_class?(klass)
154
+ # return !klass.is_a?(Array) && !klass.is_a?(Hash) && klass != Object
155
+ #end
156
+
157
+ def collection?(val)
158
+ return val.is_a?(Array) || val.is_a?(Hash)
159
+ end
160
+
161
+ def scalar?(val)
162
+ #return !val.is_a?(Array) && !val.is_a?(Hash) && val.class != Object
163
+ return val.is_a?(Kwalify::Scalar) #&& val.class != Object
164
+ end
165
+
166
+ def collection_type?(type)
167
+ return type == 'seq' || type == 'map'
168
+ end
169
+
170
+ def scalar_type?(type)
171
+ return type != 'seq' && type != 'map' && type == 'any'
172
+ end
173
+
174
+ module_function 'collection?', 'scalar?', 'collection_type?', 'scalar_type?'
175
+ end
176
+
177
+ extend Types
178
+
179
+ end
180
+ #--end of require 'kwalify/types'
181
+ #--begin of require 'kwalify/messages'
182
+ ###
183
+ ### $Rev$
184
+ ### $Release: 0.7.2 $
185
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
186
+ ###
187
+
188
+ module Kwalify
189
+
190
+ @@messages = {}
191
+
192
+ def self.msg(key)
193
+ return @@messages[key]
194
+ end
195
+
196
+
197
+
198
+ @@messages[:command_help] = <<END
199
+ kwalify - schema validator and data binding tool for YAML and JSON
200
+ ## Usage1: validate yaml document
201
+ kwalify [..options..] -f schema.yaml doc.yaml [doc2.yaml ...]
202
+ ## Usage2: validate schema definition
203
+ kwalify [..options..] -m schema.yaml [schema2.yaml ...]
204
+ ## Usage3: do action
205
+ kwalify [..options..] -a action -f schema.yaml [schema2.yaml ...]
206
+ -h, --help : help
207
+ -v : version
208
+ -q : quiet
209
+ -s : silent (obsolete, use '-q' instead)
210
+ -f schema.yaml : schema definition file
211
+ -m : meta-validation mode
212
+ -t : expand tab characters
213
+ -l : show linenumber when errored (experimental)
214
+ -E : show errors in emacs-style (experimental, implies '-l')
215
+ -a action : action ('genclass-ruby', 'genclass-php', 'genclass-java')
216
+ (try '-ha genclass-ruby' for details)
217
+ -I path : template path (for '-a')
218
+ -P : allow preceding alias
219
+ END
220
+ # -z : syntax checking of schema file
221
+ # -I path : path for template of action
222
+
223
+
224
+
225
+ ##----- begin
226
+ # filename: lib/kwalify/main.rb
227
+ @@messages[:command_option_actionnoschema] = "schema filename is not specified."
228
+ @@messages[:command_option_noaction] = "command-line option '-f' or '-m' required."
229
+ @@messages[:command_option_notemplate] = "%s: invalid action (template not found).\n"
230
+ @@messages[:schema_empty] = "%s: empty schema.\n"
231
+ @@messages[:validation_empty] = "%s#%d: empty."
232
+ @@messages[:validation_empty] = "%s#%d: empty.\n"
233
+ @@messages[:validation_valid] = "%s#%d: valid."
234
+ @@messages[:command_option_schema_required] = "-%s: schema filename required."
235
+ @@messages[:command_option_action_required] = "-%s: action required."
236
+ @@messages[:command_option_tpath_required] = "-%s: template path required."
237
+ @@messages[:command_property_invalid] = "%s: invalid property."
238
+ @@messages[:command_option_invalid] = "-%s: invalid command option."
239
+ # --
240
+ # filename: lib/kwalify/rule.rb
241
+ @@messages[:schema_notmap] = "schema definition is not a mapping."
242
+ @@messages[:key_unknown] = "unknown key."
243
+ @@messages[:type_notstr] = "not a string."
244
+ @@messages[:type_unknown] = "unknown type."
245
+ @@messages[:class_notmap] = "available only with map type."
246
+ @@messages[:required_notbool] = "not a boolean."
247
+ @@messages[:pattern_notstr] = "not a string (or regexp)"
248
+ @@messages[:pattern_notmatch] = "should be '/..../'."
249
+ @@messages[:pattern_syntaxerr] = "has regexp error."
250
+ @@messages[:enum_notseq] = "not a sequence."
251
+ @@messages[:enum_notscalar] = "not available with seq or map."
252
+ @@messages[:enum_type_unmatch] = "%s type expected."
253
+ @@messages[:enum_duplicate] = "duplicated enum value."
254
+ @@messages[:assert_notstr] = "not a string."
255
+ @@messages[:assert_noval] = "'val' is not used."
256
+ @@messages[:assert_syntaxerr] = "expression syntax error."
257
+ @@messages[:range_notmap] = "not a mapping."
258
+ @@messages[:range_notscalar] = "is available only with scalar type."
259
+ @@messages[:range_type_unmatch] = "not a %s."
260
+ @@messages[:range_undefined] = "undefined key."
261
+ @@messages[:range_twomax] = "both 'max' and 'max-ex' are not available at once."
262
+ @@messages[:range_twomin] = "both 'min' and 'min-ex' are not available at once."
263
+ @@messages[:range_maxltmin] = "max '%s' is less than min '%s'."
264
+ @@messages[:range_maxleminex] = "max '%s' is less than or equal to min-ex '%s'."
265
+ @@messages[:range_maxexlemin] = "max-ex '%s' is less than or equal to min '%s'."
266
+ @@messages[:range_maxexleminex] = "max-ex '%s' is less than or equal to min-ex '%s'."
267
+ @@messages[:length_notmap] = "not a mapping."
268
+ @@messages[:length_nottext] = "is available only with string or text."
269
+ @@messages[:length_notint] = "not an integer."
270
+ @@messages[:length_undefined] = "undefined key."
271
+ @@messages[:length_twomax] = "both 'max' and 'max-ex' are not available at once."
272
+ @@messages[:length_twomin] = "both 'min' and 'min-ex' are not available at once."
273
+ @@messages[:length_maxltmin] = "max '%s' is less than min '%s'."
274
+ @@messages[:length_maxleminex] = "max '%s' is less than or equal to min-ex '%s'."
275
+ @@messages[:length_maxexlemin] = "max-ex '%s' is less than or equal to min '%s'."
276
+ @@messages[:length_maxexleminex] = "max-ex '%s' is less than or equal to min-ex '%s'."
277
+ @@messages[:ident_notbool] = "not a boolean."
278
+ @@messages[:ident_notscalar] = "is available only with a scalar type."
279
+ @@messages[:ident_onroot] = "is not available on root element."
280
+ @@messages[:ident_notmap] = "is available only with an element of mapping."
281
+ @@messages[:unique_notbool] = "not a boolean."
282
+ @@messages[:unique_notscalar] = "is available only with a scalar type."
283
+ @@messages[:unique_onroot] = "is not available on root element."
284
+ @@messages[:default_nonscalarval] = "not a scalar."
285
+ @@messages[:default_notscalar] = "is available only with a scalar type."
286
+ @@messages[:default_unmatch] = "not a %s."
287
+ @@messages[:sequence_notseq] = "not a sequence."
288
+ @@messages[:sequence_noelem] = "required one element."
289
+ @@messages[:sequence_toomany] = "required just one element."
290
+ @@messages[:mapping_notmap] = "not a mapping."
291
+ @@messages[:mapping_noelem] = "required at least one element."
292
+ @@messages[:seq_nosequence] = "type 'seq' requires 'sequence:'."
293
+ @@messages[:seq_conflict] = "not available with sequence."
294
+ @@messages[:map_nomapping] = "type 'map' requires 'mapping:'."
295
+ @@messages[:map_conflict] = "not available with mapping."
296
+ @@messages[:scalar_conflict] = "not available with scalar type."
297
+ @@messages[:enum_conflict] = "not available with 'enum:'."
298
+ @@messages[:default_conflict] = "not available when 'required:' is true."
299
+ # --
300
+ # filename: lib/kwalify/validator.rb
301
+ @@messages[:required_novalue] = "value required but none."
302
+ @@messages[:type_unmatch] = "not a %s."
303
+ @@messages[:key_undefined] = "key '%s' is undefined."
304
+ @@messages[:required_nokey] = "key '%s:' is required."
305
+ @@messages[:value_notunique] = "is already used at '%s'."
306
+ @@messages[:assert_failed] = "assertion expression failed (%s)."
307
+ @@messages[:enum_notexist] = "invalid %s value."
308
+ @@messages[:pattern_unmatch] = "not matched to pattern %s."
309
+ @@messages[:range_toolarge] = "too large (> max %s)."
310
+ @@messages[:range_toosmall] = "too small (< min %s)."
311
+ @@messages[:range_toolargeex] = "too large (>= max %s)."
312
+ @@messages[:range_toosmallex] = "too small (<= min %s)."
313
+ @@messages[:length_toolong] = "too long (length %d > max %d)."
314
+ @@messages[:length_tooshort] = "too short (length %d < min %d)."
315
+ @@messages[:length_toolongex] = "too long (length %d >= max %d)."
316
+ @@messages[:length_tooshortex] = "too short (length %d <= min %d)."
317
+ # --
318
+ # filename: lib/kwalify/yaml-parser.rb
319
+ @@messages[:flow_hastail] = "flow style sequence is closed but got '%s'."
320
+ @@messages[:flow_eof] = "found EOF when parsing flow style."
321
+ @@messages[:flow_alias_label] = "alias name expected."
322
+ @@messages[:flow_anchor_label] = "anchor name expected."
323
+ @@messages[:flow_noseqitem] = "sequence item required (or last comma is extra)."
324
+ @@messages[:flow_seqnotclosed] = "flow style sequence requires ']'."
325
+ @@messages[:flow_mapnoitem] = "mapping item required (or last comma is extra)."
326
+ @@messages[:flow_mapnotclosed] = "flow style mapping requires '}'."
327
+ @@messages[:flow_nocolon] = "':' expected but got %s."
328
+ @@messages[:flow_str_notclosed] = "%s: string not closed."
329
+ @@messages[:anchor_duplicated] = "anchor '%s' is already used."
330
+ @@messages[:alias_extradata] = "alias cannot take any data."
331
+ @@messages[:anchor_notfound] = "anchor '%s' not found"
332
+ @@messages[:sequence_noitem] = "sequence item is expected."
333
+ @@messages[:sequence_badindent] = "illegal indent of sequence."
334
+ @@messages[:mapping_noitem] = "mapping item is expected."
335
+ @@messages[:mapping_badindent] = "illegal indent of mapping."
336
+ # --
337
+ ##----- end
338
+
339
+
340
+
341
+
342
+ @@words = {}
343
+
344
+ def self.word(key)
345
+ return @@words[key] || key
346
+ end
347
+
348
+ @@words['str'] = 'string'
349
+ @@words['int'] = 'integer'
350
+ @@words['bool'] = 'boolean'
351
+ @@words['seq'] = 'sequence'
352
+ @@words['map'] = 'mapping'
353
+
354
+ end
355
+ #--end of require 'kwalify/messages'
356
+ #--begin of require 'kwalify/errors'
357
+ ###
358
+ ### $Rev$
359
+ ### $Release: 0.7.2 $
360
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
361
+ ###
362
+
363
+ #--already included require 'kwalify/messages'
364
+
365
+ module Kwalify
366
+
367
+ class KwalifyError < StandardError
368
+ end
369
+
370
+
371
+ class AssertionError < KwalifyError
372
+ def initialize(msg)
373
+ super("*** assertion error: " + msg)
374
+ end
375
+ end
376
+
377
+
378
+ class BaseError < KwalifyError
379
+ def initialize(message="", path=nil, value=nil, rule=nil, error_symbol=nil)
380
+ super(message)
381
+ @path = path.is_a?(Array) ? '/'+path.join('/') : path
382
+ @rule = rule
383
+ @value = value
384
+ @error_symbol = error_symbol
385
+ end
386
+ attr_accessor :error_symbol, :rule, :path, :value
387
+ attr_accessor :filename, :linenum, :column
388
+
389
+ def path
390
+ return @path == '' ? "/" : @path
391
+ end
392
+
393
+ alias _to_s to_s
394
+ alias message to_s
395
+
396
+ def to_s
397
+ s = ''
398
+ s << @filename << ":" if @filename
399
+ s << "#{@linenum}:#{@column} " if @linenum
400
+ s << "[#{path()}] " if @path
401
+ s << _to_s()
402
+ return s
403
+ end
404
+
405
+ def <=>(ex)
406
+ #return @linenum <=> ex.linenum
407
+ v = 0
408
+ v = @linenum <=> ex.linenum if @linenum && ex.linenum
409
+ v = @column <=> ex.column if v == 0 && @column && ex.column
410
+ v = @path <=> ex.path if v == 0
411
+ return v
412
+ end
413
+ end
414
+
415
+
416
+ class SchemaError < BaseError
417
+ def initialize(message="", path=nil, rule=nil, value=nil, error_symbol=nil)
418
+ super(message, path, rule, value, error_symbol)
419
+ end
420
+ end
421
+
422
+
423
+ class ValidationError < BaseError
424
+ def initialize(message="", path=nil, rule=nil, value=nil, error_symbol=nil)
425
+ super(message, path, rule, value, error_symbol)
426
+ end
427
+ end
428
+
429
+
430
+ ## syntax error for YAML and JSON
431
+ class SyntaxError < BaseError #KwalifyError
432
+ def initialize(msg, linenum=nil, error_symbol=nil)
433
+ super(linenum ? "line #{linenum}: #{msg}" : msg)
434
+ @linenum = linenum
435
+ @error_symbol = error_symbol
436
+ end
437
+ #attr_accessor :linenum, :error_symbol
438
+ def message
439
+ "file: #{@filename}, line #{@linenum}: #{super}"
440
+ end
441
+ end
442
+
443
+
444
+ ## (obsolete) use Kwalify::SyntaxError instead
445
+ class YamlSyntaxError < SyntaxError
446
+ end
447
+
448
+
449
+ module ErrorHelper
450
+
451
+ #module_function
452
+
453
+ def assert_error(message="")
454
+ raise AssertionError.new(message)
455
+ end
456
+
457
+ def validate_error(error_symbol, rule, path, val, args=nil)
458
+ msg = _build_message(error_symbol, val, args);
459
+ path = '/'+path.join('/') if path.is_a?(Array)
460
+ return ValidationError.new(msg, path, val, rule, error_symbol)
461
+ end
462
+ module_function :validate_error
463
+
464
+ def schema_error(error_symbol, rule, path, val, args=nil)
465
+ msg = _build_message(error_symbol, val, args);
466
+ path = '/'+path.join('/') if path.is_a?(Array)
467
+ return SchemaError.new(msg, path, val, rule, error_symbol)
468
+ end
469
+
470
+ def _build_message(message_key, val, args)
471
+ msg = Kwalify.msg(message_key)
472
+ assert_error("message_key=#{message_key.inspect}") unless msg
473
+ msg = msg % args if args
474
+ msg = "'#{val.to_s.gsub(/\n/, '\n')}': #{msg}" if !val.nil? && Types.scalar?(val)
475
+ return msg;
476
+ end
477
+ module_function :_build_message
478
+
479
+ end
480
+
481
+ extend ErrorHelper
482
+
483
+ end
484
+ #--end of require 'kwalify/errors'
485
+ #--begin of require 'kwalify/rule'
486
+ ###
487
+ ### $Rev$
488
+ ### $Release: 0.7.2 $
489
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
490
+ ###
491
+
492
+ #--already included require 'kwalify/messages'
493
+ #--already included require 'kwalify/errors'
494
+ #--already included require 'kwalify/types'
495
+
496
+
497
+ module Kwalify
498
+
499
+
500
+ class Rule
501
+ include Kwalify::ErrorHelper
502
+
503
+ attr_accessor :parent
504
+ attr_reader :name
505
+ attr_reader :desc
506
+ attr_reader :enum
507
+ attr_reader :required
508
+ attr_reader :type
509
+ attr_reader :type_class
510
+ attr_reader :pattern
511
+ attr_reader :regexp
512
+ attr_reader :sequence
513
+ attr_reader :mapping
514
+ attr_reader :assert
515
+ attr_reader :assert_proc
516
+ attr_reader :range
517
+ attr_reader :length
518
+ attr_reader :ident
519
+ attr_reader :unique
520
+ attr_reader :default
521
+ attr_reader :classname
522
+ attr_reader :classobj
523
+
524
+
525
+ def initialize(hash=nil, parent=nil)
526
+ _init(hash, "", {}) if hash
527
+ @parent = parent
528
+ end
529
+
530
+
531
+ def _init(hash, path="", rule_table={})
532
+ unless hash.is_a?(Hash)
533
+ #* key=:schema_notmap msg="schema definition is not a mapping."
534
+ raise Kwalify.schema_error(:schema_notmap, nil, (!path || path.empty? ? "/" : path), nil)
535
+ end
536
+ rule = self
537
+ rule_table[hash.__id__] = rule
538
+ ## 'type:' entry
539
+ curr_path = "#{path}/type"
540
+ _init_type_value(hash['type'], rule, curr_path)
541
+ ## other entries
542
+ hash.each do |key, val|
543
+ curr_path = "#{path}/#{key}"
544
+ sym = key.intern
545
+ method = get_init_method(sym)
546
+ unless method
547
+ #* key=:key_unknown msg="unknown key."
548
+ raise schema_error(:key_unknown, rule, curr_path, "#{key}:")
549
+ end
550
+ if sym == :sequence || sym == :mapping
551
+ __send__(method, val, rule, curr_path, rule_table)
552
+ else
553
+ __send__(method, val, rule, curr_path)
554
+ end
555
+ end
556
+ _check_confliction(hash, rule, path)
557
+ return self
558
+ end
559
+
560
+
561
+ keys = %w[type name desc required pattern enum assert range length
562
+ ident unique default sequence mapping class]
563
+ #table = keys.inject({}) {|h, k| h[k.intern] = "_init_#{k}_value".intern; h }
564
+ table = {}; keys.each {|k| table[k.intern] = "_init_#{k}_value".intern }
565
+ @@dispatch_table = table
566
+
567
+
568
+ protected
569
+
570
+
571
+ def get_init_method(sym)
572
+ @_dispatch_table ||= @@dispatch_table
573
+ return @_dispatch_table[sym]
574
+ end
575
+
576
+
577
+ private
578
+
579
+
580
+ def _init_type_value(val, rule, path)
581
+ @type = val
582
+ @type = Types::DEFAULT_TYPE if @type.nil?
583
+ unless @type.is_a?(String)
584
+ #* key=:type_notstr msg="not a string."
585
+ raise schema_error(:type_notstr, rule, path, @type.to_s)
586
+ end
587
+ @type_class = Types.type_class(@type)
588
+ #if @type_class.nil?
589
+ # begin
590
+ # @type_class = Kernel.const_get(@type)
591
+ # rescue NameError
592
+ # end
593
+ #end
594
+ unless @type_class
595
+ #* key=:type_unknown msg="unknown type."
596
+ raise schema_error(:type_unknown, rule, path, @type.to_s)
597
+ end
598
+ end
599
+
600
+
601
+ def _init_class_value(val, rule, path)
602
+ @classname = val
603
+ unless @type == 'map'
604
+ #* key=:class_notmap msg="available only with map type."
605
+ raise schema_error(:class_notmap, rule, path, 'class:')
606
+ end
607
+ begin
608
+ @classobj = Util.get_class(val)
609
+ rescue NameError
610
+ @classobj = nil
611
+ end
612
+ end
613
+
614
+
615
+ def _init_name_value(val, rule, path)
616
+ @name = val
617
+ end
618
+
619
+
620
+ def _init_desc_value(val, rule, path)
621
+ @desc = val
622
+ end
623
+
624
+
625
+ def _init_required_value(val, rule, path)
626
+ @required = val
627
+ unless val.is_a?(Boolean) #|| val.nil?
628
+ #* key=:required_notbool msg="not a boolean."
629
+ raise schema_error(:required_notbool, rule, path, val)
630
+ end
631
+ end
632
+
633
+
634
+ def _init_pattern_value(val, rule, path)
635
+ @pattern = val
636
+ unless val.is_a?(String) || val.is_a?(Regexp)
637
+ #* key=:pattern_notstr msg="not a string (or regexp)"
638
+ raise schema_error(:pattern_notstr, rule, path, val)
639
+ end
640
+ unless val =~ /\A\/(.*)\/([mi]?[mi]?)\z/
641
+ #* key=:pattern_notmatch msg="should be '/..../'."
642
+ raise schema_error(:pattern_notmatch, rule, path, val)
643
+ end
644
+ pat = $1; opt = $2
645
+ flag = 0
646
+ flag += Regexp::IGNORECASE if opt.include?("i")
647
+ flag += Regexp::MULTILINE if opt.include?("m")
648
+ begin
649
+ @regexp = Regexp.compile(pat, flag)
650
+ rescue RegexpError => ex
651
+ #* key=:pattern_syntaxerr msg="has regexp error."
652
+ raise schema_error(:pattern_syntaxerr, rule, path, val)
653
+ end
654
+ end
655
+
656
+
657
+ def _init_enum_value(val, rule, path)
658
+ @enum = val
659
+ unless val.is_a?(Array)
660
+ #* key=:enum_notseq msg="not a sequence."
661
+ raise schema_error(:enum_notseq, rule, path, val)
662
+ end
663
+ if Types.collection_type?(@type) # unless Kwalify.scalar_class?(@type_class)
664
+ #* key=:enum_notscalar msg="not available with seq or map."
665
+ raise schema_error(:enum_notscalar, rule, File.dirname(path), 'enum:')
666
+ end
667
+ elem_table = {}
668
+ @enum.each do |elem|
669
+ unless elem.is_a?(@type_class)
670
+ #* key=:enum_type_unmatch msg="%s type expected."
671
+ raise schema_error(:enum_type_unmatch, rule, path, elem, [Kwalify.word(@type)])
672
+ end
673
+ if elem_table[elem]
674
+ #* key=:enum_duplicate msg="duplicated enum value."
675
+ raise schema_error(:enum_duplicate, rule, path, elem.to_s)
676
+ end
677
+ elem_table[elem] = true
678
+ end
679
+ end
680
+
681
+
682
+ def _init_assert_value(val, rule, path)
683
+ @assert = val
684
+ unless val.is_a?(String)
685
+ #* key=:assert_notstr msg="not a string."
686
+ raise schema_error(:assert_notstr, rule, path, val)
687
+ end
688
+ unless val =~ /\bval\b/
689
+ #* key=:assert_noval msg="'val' is not used."
690
+ raise schema_error(:assert_noval, rule, path, val)
691
+ end
692
+ begin
693
+ @assert_proc = eval "proc { |val| #{val} }"
694
+ rescue ::SyntaxError => ex
695
+ #* key=:assert_syntaxerr msg="expression syntax error."
696
+ raise schema_error(:assert_syntaxerr, rule, path, val)
697
+ end
698
+ end
699
+
700
+
701
+ def _init_range_value(val, rule, path)
702
+ @range = val
703
+ unless val.is_a?(Hash)
704
+ #* key=:range_notmap msg="not a mapping."
705
+ raise schema_error(:range_notmap, rule, path, val)
706
+ end
707
+ if Types.collection_type?(@type) || @type == 'bool'
708
+ #* key=:range_notscalar msg="is available only with scalar type."
709
+ raise schema_error(:range_notscalar, rule, File.dirname(path), 'range:')
710
+ end
711
+ val.each do |k, v|
712
+ case k
713
+ when 'max', 'min', 'max-ex', 'min-ex'
714
+ unless v.is_a?(@type_class)
715
+ typename = Kwalify.word(@type) || @type
716
+ #* key=:range_type_unmatch msg="not a %s."
717
+ raise schema_error(:range_type_unmatch, rule, "#{path}/#{k}", v, [typename])
718
+ end
719
+ else
720
+ #* key=:range_undefined msg="undefined key."
721
+ raise schema_error(:range_undefined, rule, "#{path}/#{k}", "#{k}:")
722
+ end
723
+ end
724
+ if val.key?('max') && val.key?('max-ex')
725
+ #* key=:range_twomax msg="both 'max' and 'max-ex' are not available at once."
726
+ raise schema_error(:range_twomax, rule, path, nil)
727
+ end
728
+ if val.key?('min') && val.key?('min-ex')
729
+ #* key=:range_twomin msg="both 'min' and 'min-ex' are not available at once."
730
+ raise schema_error(:range_twomin, rule, path, nil)
731
+ end
732
+ max, min, max_ex, min_ex = val['max'], val['min'], val['max-ex'], val['min-ex']
733
+ if max
734
+ if min && max < min
735
+ #* key=:range_maxltmin msg="max '%s' is less than min '%s'."
736
+ raise validate_error(:range_maxltmin, rule, path, nil, [max, min])
737
+ elsif min_ex && max <= min_ex
738
+ #* key=:range_maxleminex msg="max '%s' is less than or equal to min-ex '%s'."
739
+ raise validate_error(:range_maxleminex, rule, path, nil, [max, min_ex])
740
+ end
741
+ elsif max_ex
742
+ if min && max_ex <= min
743
+ #* key=:range_maxexlemin msg="max-ex '%s' is less than or equal to min '%s'."
744
+ raise validate_error(:range_maxexlemin, rule, path, nil, [max_ex, min])
745
+ elsif min_ex && max_ex <= min_ex
746
+ #* key=:range_maxexleminex msg="max-ex '%s' is less than or equal to min-ex '%s'."
747
+ raise validate_error(:range_maxexleminex, rule, path, nil, [max_ex, min_ex])
748
+ end
749
+ end
750
+ end
751
+
752
+
753
+ def _init_length_value(val, rule, path)
754
+ @length = val
755
+ unless val.is_a?(Hash)
756
+ #* key=:length_notmap msg="not a mapping."
757
+ raise schema_error(:length_notmap, rule, path, val)
758
+ end
759
+ unless @type == 'str' || @type == 'text'
760
+ #* key=:length_nottext msg="is available only with string or text."
761
+ raise schema_error(:length_nottext, rule, File.dirname(path), 'length:')
762
+ end
763
+ val.each do |k, v|
764
+ case k
765
+ when 'max', 'min', 'max-ex', 'min-ex'
766
+ unless v.is_a?(Integer)
767
+ #* key=:length_notint msg="not an integer."
768
+ raise schema_error(:length_notint, rule, "#{path}/#{k}", v)
769
+ end
770
+ else
771
+ #* key=:length_undefined msg="undefined key."
772
+ raise schema_error(:length_undefined, rule, "#{path}/#{k}", "#{k}:")
773
+ end
774
+ end
775
+ if val.key?('max') && val.key?('max-ex')
776
+ #* key=:length_twomax msg="both 'max' and 'max-ex' are not available at once."
777
+ raise schema_error(:length_twomax, rule, path, nil)
778
+ end
779
+ if val.key?('min') && val.key?('min-ex')
780
+ #* key=:length_twomin msg="both 'min' and 'min-ex' are not available at once."
781
+ raise schema_error(:length_twomin, rule, path, nil)
782
+ end
783
+ max, min, max_ex, min_ex = val['max'], val['min'], val['max-ex'], val['min-ex']
784
+ if max
785
+ if min && max < min
786
+ #* key=:length_maxltmin msg="max '%s' is less than min '%s'."
787
+ raise validate_error(:length_maxltmin, rule, path, nil, [max, min])
788
+ elsif min_ex && max <= min_ex
789
+ #* key=:length_maxleminex msg="max '%s' is less than or equal to min-ex '%s'."
790
+ raise validate_error(:length_maxleminex, rule, path, nil, [max, min_ex])
791
+ end
792
+ elsif max_ex
793
+ if min && max_ex <= min
794
+ #* key=:length_maxexlemin msg="max-ex '%s' is less than or equal to min '%s'."
795
+ raise validate_error(:length_maxexlemin, rule, path, nil, [max_ex, min])
796
+ elsif min_ex && max_ex <= min_ex
797
+ #* key=:length_maxexleminex msg="max-ex '%s' is less than or equal to min-ex '%s'."
798
+ raise validate_error(:length_maxexleminex, rule, path, nil, [max_ex, min_ex])
799
+ end
800
+ end
801
+ end
802
+
803
+
804
+ def _init_ident_value(val, rule, path)
805
+ @ident = val
806
+ @required = true
807
+ unless val.is_a?(Boolean)
808
+ #* key=:ident_notbool msg="not a boolean."
809
+ raise schema_error(:ident_notbool, rule, path, val)
810
+ end
811
+ if @type == 'map' || @type == 'seq'
812
+ #* key=:ident_notscalar msg="is available only with a scalar type."
813
+ raise schema_error(:ident_notscalar, rule, File.dirname(path), "ident:")
814
+ end
815
+ if File.dirname(path) == "/"
816
+ #* key=:ident_onroot msg="is not available on root element."
817
+ raise schema_error(:ident_onroot, rule, "/", "ident:")
818
+ end
819
+ unless @parent && @parent.type == 'map'
820
+ #* key=:ident_notmap msg="is available only with an element of mapping."
821
+ raise schema_error(:ident_notmap, rule, File.dirname(path), "ident:")
822
+ end
823
+ end
824
+
825
+
826
+ def _init_unique_value(val, rule, path)
827
+ @unique = val
828
+ unless val.is_a?(Boolean)
829
+ #* key=:unique_notbool msg="not a boolean."
830
+ raise schema_error(:unique_notbool, rule, path, val)
831
+ end
832
+ if @type == 'map' || @type == 'seq'
833
+ #* key=:unique_notscalar msg="is available only with a scalar type."
834
+ raise schema_error(:unique_notscalar, rule, File.dirname(path), "unique:")
835
+ end
836
+ if File.dirname(path) == "/"
837
+ #* key=:unique_onroot msg="is not available on root element."
838
+ raise schema_error(:unique_onroot, rule, "/", "unique:")
839
+ end
840
+ end
841
+
842
+
843
+ def _init_default_value(val, rule, path)
844
+ @default = val
845
+ unless Types.scalar?(val)
846
+ #* key=:default_nonscalarval msg="not a scalar."
847
+ raise schema_error(:default_nonscalarval, rule, path, val)
848
+ end
849
+ if @type == 'map' || @type == 'seq'
850
+ #* key=:default_notscalar msg="is available only with a scalar type."
851
+ raise schema_error(:default_notscalar, rule, File.dirname(path), "default:")
852
+ end
853
+ unless val.nil? || val.is_a?(@type_class)
854
+ #* key=:default_unmatch msg="not a %s."
855
+ raise schema_error(:default_unmatch, rule, path, val, [Kwalify.word(@type)])
856
+ end
857
+ end
858
+
859
+
860
+ def _init_sequence_value(val, rule, path, rule_table)
861
+ if !val.nil? && !val.is_a?(Array)
862
+ #* key=:sequence_notseq msg="not a sequence."
863
+ raise schema_error(:sequence_notseq, rule, path, val)
864
+ elsif val.nil? || val.empty?
865
+ #* key=:sequence_noelem msg="required one element."
866
+ raise schema_error(:sequence_noelem, rule, path, val)
867
+ elsif val.length > 1
868
+ #* key=:sequence_toomany msg="required just one element."
869
+ raise schema_error(:sequence_toomany, rule, path, val)
870
+ else
871
+ elem = val[0]
872
+ elem ||= {}
873
+ i = 0 # or 1? *index*
874
+ rule = rule_table[elem.__id__]
875
+ rule ||= Rule.new(nil, self)._init(elem, "#{path}/#{i}", rule_table)
876
+ @sequence = [ rule ]
877
+ end
878
+ end
879
+
880
+
881
+ def _init_mapping_value(val, rule, path, rule_table)
882
+ if !val.nil? && !val.is_a?(Hash)
883
+ #* key=:mapping_notmap msg="not a mapping."
884
+ raise schema_error(:mapping_notmap, rule, path, val)
885
+ elsif val.nil? || (val.empty? && !val.default)
886
+ #* key=:mapping_noelem msg="required at least one element."
887
+ raise schema_error(:mapping_noelem, rule, path, val)
888
+ else
889
+ @mapping = {}
890
+ if val.default
891
+ elem = val.default # hash
892
+ rule = rule_table[elem.__id__]
893
+ rule ||= Rule.new(nil, self)._init(elem, "#{path}/=", rule_table)
894
+ @mapping.default = rule
895
+ end
896
+ val.each do |k, v|
897
+ ##* key=:key_duplicate msg="key duplicated."
898
+ #raise schema_error(:key_duplicate, rule, path, key) if @mapping.key?(key)
899
+ v ||= {}
900
+ rule = rule_table[v.__id__]
901
+ rule ||= Rule.new(nil, self)._init(v, "#{path}/#{k}", rule_table)
902
+ if k == '='
903
+ @mapping.default = rule
904
+ else
905
+ @mapping[k] = rule
906
+ end
907
+ end if val
908
+ end
909
+ end
910
+
911
+
912
+ def _check_confliction(hash, rule, path)
913
+ if @type == 'seq'
914
+ #* key=:seq_nosequence msg="type 'seq' requires 'sequence:'."
915
+ raise schema_error(:seq_nosequence, rule, path, nil) unless hash.key?('sequence')
916
+ #* key=:seq_conflict msg="not available with sequence."
917
+ raise schema_error(:seq_conflict, rule, path, 'enum:') if @enum
918
+ raise schema_error(:seq_conflict, rule, path, 'pattern:') if @pattern
919
+ raise schema_error(:seq_conflict, rule, path, 'mapping:') if @mapping
920
+ raise schema_error(:seq_conflict, rule, path, 'range:') if @range
921
+ raise schema_error(:seq_conflict, rule, path, 'length:') if @length
922
+ elsif @type == 'map'
923
+ #* key=:map_nomapping msg="type 'map' requires 'mapping:'."
924
+ raise schema_error(:map_nomapping, rule, path, nil) unless hash.key?('mapping')
925
+ #* key=:map_conflict msg="not available with mapping."
926
+ raise schema_error(:map_conflict, rule, path, 'enum:') if @enum
927
+ raise schema_error(:map_conflict, rule, path, 'pattern:') if @pattern
928
+ raise schema_error(:map_conflict, rule, path, 'sequence:') if @sequence
929
+ raise schema_error(:map_conflict, rule, path, 'range:') if @range
930
+ raise schema_error(:map_conflict, rule, path, 'length:') if @length
931
+ else
932
+ #* key=:scalar_conflict msg="not available with scalar type."
933
+ raise schema_error(:scalar_conflict, rule, path, 'sequence:') if @sequence
934
+ raise schema_error(:scalar_conflict, rule, path, 'mapping:') if @mapping
935
+ if @enum
936
+ #* key=:enum_conflict msg="not available with 'enum:'."
937
+ raise schema_error(:enum_conflict, rule, path, 'range:') if @range
938
+ raise schema_error(:enum_conflict, rule, path, 'length:') if @length
939
+ raise schema_error(:enum_conflict, rule, path, 'pattern:') if @pattern
940
+ end
941
+ unless @default.nil?
942
+ #* key=:default_conflict msg="not available when 'required:' is true."
943
+ raise schema_error(:default_conflict, rule, path, 'default:') if @required
944
+ end
945
+ end
946
+ end
947
+
948
+ #def inspect()
949
+ # str = ""; level = 0; done = {}
950
+ # _inspect(str, level, done)
951
+ # return str
952
+ #end
953
+
954
+
955
+ protected
956
+
957
+
958
+ def _inspect(str="", level=0, done={})
959
+ done[self.__id__] = true
960
+ str << " " * level << "name: #{@name}\n" unless @name.nil?
961
+ str << " " * level << "desc: #{@desc}\n" unless @desc.nil?
962
+ str << " " * level << "type: #{@type}\n" unless @type.nil?
963
+ str << " " * level << "klass: #{@type_class.name}\n" unless @type_class.nil?
964
+ str << " " * level << "required: #{@required}\n" unless @required.nil?
965
+ str << " " * level << "pattern: #{@regexp.inspect}\n" unless @pattern.nil?
966
+ str << " " * level << "assert: #{@assert}\n" unless @assert.nil?
967
+ str << " " * level << "ident: #{@ident}\n" unless @ident.nil?
968
+ str << " " * level << "unique: #{@unique}\n" unless @unique.nil?
969
+ if !@enum.nil?
970
+ str << " " * level << "enum:\n"
971
+ @enum.each do |item|
972
+ str << " " * (level+1) << "- #{item}\n"
973
+ end
974
+ end
975
+ if !@range.nil?
976
+ str << " " * level
977
+ str << "range: { "
978
+ colon = ""
979
+ %w[max max-ex min min-ex].each do |key|
980
+ val = @range[key]
981
+ unless val.nil?
982
+ str << colon << "#{key}: #{val.inspect}"
983
+ colon = ", "
984
+ end
985
+ end
986
+ str << " }\n"
987
+ end
988
+ if !@length.nil?
989
+ str << " " * level
990
+ str << "length: { "
991
+ colon = ""
992
+ %w[max max-ex min min-ex].each do |key|
993
+ val = @length[key]
994
+ if !val.nil?
995
+ str << colon << "#{key}: #{val.inspect}"
996
+ colon = ", "
997
+ end
998
+ end
999
+ str << " }\n"
1000
+ end
1001
+ @sequence.each do |rule|
1002
+ if done[rule.__id__]
1003
+ str << " " * (level+1) << "- ...\n"
1004
+ else
1005
+ str << " " * (level+1) << "- \n"
1006
+ rule._inspect(str, level+2, done)
1007
+ end
1008
+ end if @sequence
1009
+ @mapping.each do |key, rule|
1010
+ if done[rule.__id__]
1011
+ str << ' ' * (level+1) << '"' << key << "\": ...\n"
1012
+ else
1013
+ str << ' ' * (level+1) << '"' << key << "\":\n"
1014
+ rule._inspect(str, level+2, done)
1015
+ end
1016
+ end if @mapping
1017
+ return str
1018
+ end
1019
+
1020
+
1021
+ public
1022
+
1023
+
1024
+ def _uniqueness_check_table() # :nodoc:
1025
+ uniq_table = nil
1026
+ if @type == 'map'
1027
+ @mapping.keys.each do |key|
1028
+ rule = @mapping[key]
1029
+ if rule.unique || rule.ident
1030
+ uniq_table ||= {}
1031
+ uniq_table[key] = {}
1032
+ end
1033
+ end
1034
+ elsif @unique || @ident
1035
+ uniq_table = {}
1036
+ end
1037
+ return uniq_table
1038
+ end
1039
+
1040
+
1041
+ end
1042
+
1043
+
1044
+ end
1045
+ #--end of require 'kwalify/rule'
1046
+ #--begin of require 'kwalify/validator'
1047
+ ###
1048
+ ### $Rev$
1049
+ ### $Release: 0.7.2 $
1050
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
1051
+ ###
1052
+
1053
+ #--already included require 'kwalify/messages'
1054
+ #--already included require 'kwalify/errors'
1055
+ #--already included require 'kwalify/types'
1056
+ #--already included require 'kwalify/rule'
1057
+
1058
+ module Kwalify
1059
+
1060
+ ##
1061
+ ## validate YAML document
1062
+ ##
1063
+ ## ex1. validate yaml document
1064
+ ## schema = YAML.load_file('schema.yaml')
1065
+ ## validator = Kwalify::Validator.new(schema)
1066
+ ## document = YAML.load_file('document.yaml')
1067
+ ## erros = validator.validate(document)
1068
+ ## if errors && !errors.empty?
1069
+ ## errors.each do |err|
1070
+ ## puts "- [#{err.path}] #{err.message}"
1071
+ ## end
1072
+ ## end
1073
+ ##
1074
+ ## ex2. validate with parsing
1075
+ ## schema = YAML.load_file('schema.yaml')
1076
+ ## validator = Kwalify::Validator.new(schema)
1077
+ ## parser = Kwalify::Yaml::Parser.new(validator)
1078
+ ## document = parser.parse(File.read('document.yaml'))
1079
+ ## errors = parser.errors
1080
+ ## if errors && errors.empty?
1081
+ ## errors.each do |e|
1082
+ ## puts "#{e.linenum}:#{e.column} [#{e.path}] #{e.message}"
1083
+ ## end
1084
+ ## end
1085
+ ##
1086
+ class Validator
1087
+ include Kwalify::ErrorHelper
1088
+
1089
+
1090
+ def initialize(hash_or_rule, &block)
1091
+ obj = hash_or_rule
1092
+ @rule = (obj.nil? || obj.is_a?(Rule)) ? obj : Rule.new(obj)
1093
+ @block = block
1094
+ end
1095
+ attr_reader :rule
1096
+
1097
+
1098
+ def _inspect
1099
+ @rule._inspect
1100
+ end
1101
+
1102
+
1103
+ def validate(value)
1104
+ path = ''; errors = []; done = {}; uniq_table = nil
1105
+ _validate(value, @rule, path, errors, done, uniq_table)
1106
+ return errors
1107
+ end
1108
+
1109
+
1110
+ protected
1111
+
1112
+
1113
+ def validate_hook(value, rule, path, errors)
1114
+ ## may be overrided by subclass
1115
+ end
1116
+
1117
+
1118
+ public
1119
+
1120
+
1121
+ def _validate(value, rule, path, errors, done, uniq_table, recursive=true)
1122
+ #if Types.collection?(value)
1123
+ if !Types.scalar?(value)
1124
+ #if done[value.__id__]
1125
+ # rule2 = done[value.__id__]
1126
+ # if rule2.is_a?(Rule)
1127
+ # return if rule.equal?(rule2)
1128
+ # done[value.__id__] = [rule2, rule]
1129
+ # elsif rule2.is_a?(Array)
1130
+ # return if rule2.any? {|r| r.equal?(rule)}
1131
+ # done[value.__id__] << rule
1132
+ # else
1133
+ # raise "unreachable"
1134
+ # end
1135
+ #end
1136
+ return if done[value.__id__] # avoid infinite loop
1137
+ done[value.__id__] = rule
1138
+ end
1139
+ if rule.required && value.nil?
1140
+ #* key=:required_novalue msg="value required but none."
1141
+ errors << validate_error(:required_novalue, rule, path, value)
1142
+ return
1143
+ end
1144
+ if rule.type_class && !value.nil? && !value.is_a?(rule.type_class)
1145
+ unless rule.classobj && value.is_a?(rule.classobj)
1146
+ #* key=:type_unmatch msg="not a %s."
1147
+ errors << validate_error(:type_unmatch, rule, path, value, [Kwalify.word(rule.type)])
1148
+ return
1149
+ end
1150
+ end
1151
+ #
1152
+ n = errors.length
1153
+ if rule.sequence
1154
+ _validate_sequence(value, rule, path, errors, done, uniq_table, recursive)
1155
+ elsif rule.mapping
1156
+ _validate_mapping(value, rule, path, errors, done, uniq_table, recursive)
1157
+ else
1158
+ _validate_scalar(value, rule, path, errors, done, uniq_table)
1159
+ end
1160
+ return unless errors.length == n
1161
+ #
1162
+ #path_str = path.is_a?(Array) ? '/'+path.join('/') : path
1163
+ #validate_hook(value, rule, path_str, errors)
1164
+ #@block.call(value, rule, path_str, errors) if @block
1165
+ validate_hook(value, rule, path, errors)
1166
+ @block.call(value, rule, path, errors) if @block
1167
+ end
1168
+
1169
+
1170
+ private
1171
+
1172
+
1173
+ def _validate_sequence(list, seq_rule, path, errors, done, uniq_table, recursive=true)
1174
+ assert_error("seq_rule.sequence.class==#{seq_rule.sequence.class.name} (expected Array)") unless seq_rule.sequence.is_a?(Array)
1175
+ assert_error("seq_rule.sequence.length==#{seq_rule.sequence.length} (expected 1)") unless seq_rule.sequence.length == 1
1176
+ return if list.nil? || !recursive
1177
+ rule = seq_rule.sequence[0]
1178
+ uniq_table = rule._uniqueness_check_table()
1179
+ list.each_with_index do |val, i|
1180
+ child_path = path.is_a?(Array) ? path + [i] : "#{path}/#{i}"
1181
+ _validate(val, rule, child_path, errors, done, uniq_table) ## validate recursively
1182
+ end
1183
+ end
1184
+
1185
+
1186
+ def _validate_mapping(hash, map_rule, path, errors, done, uniq_table, recursive=true)
1187
+ assert_error("map_rule.mapping.class==#{map_rule.mapping.class.name} (expected Hash)") unless map_rule.mapping.is_a?(Hash)
1188
+ return if hash.nil?
1189
+ return if !recursive
1190
+ _validate_mapping_required_keys(hash, map_rule, path, errors)
1191
+ hash.each do |key, val|
1192
+ rule = map_rule.mapping[key]
1193
+ child_path = path.is_a?(Array) ? path+[key] : "#{path}/#{key}"
1194
+ unless rule
1195
+ #* key=:key_undefined msg="key '%s' is undefined."
1196
+ errors << validate_error(:key_undefined, rule, child_path, hash, ["#{key}:"])
1197
+ ##* key=:key_undefined msg="undefined key."
1198
+ #errors << validate_error(:key_undefined, rule, child_path, "#{key}:")
1199
+ else
1200
+ _validate(val, rule, child_path, errors, done,
1201
+ uniq_table ? uniq_table[key] : nil) ## validate recursively
1202
+ end
1203
+ end
1204
+ end
1205
+
1206
+
1207
+ def _validate_mapping_required_keys(hash, map_rule, path, errors) #:nodoc:
1208
+ map_rule.mapping.each do |key, rule|
1209
+ #next unless rule.required
1210
+ #val = hash.is_a?(Hash) ? hash[key] : hash.instance_variable_get("@#{key}")
1211
+ #if val.nil?
1212
+ if rule.required && hash[key].nil? # or !hash.key?(key)
1213
+ #* key=:required_nokey msg="key '%s:' is required."
1214
+ errors << validate_error(:required_nokey, rule, path, hash, [key])
1215
+ end
1216
+ end
1217
+ end
1218
+ public :_validate_mapping_required_keys
1219
+
1220
+
1221
+ def _validate_scalar(value, rule, path, errors, done, uniq_table)
1222
+ assert_error("rule.sequence.class==#{rule.sequence.class.name} (expected NilClass)") if rule.sequence
1223
+ assert_error("rule.mapping.class==#{rule.mapping.class.name} (expected NilClass)") if rule.mapping
1224
+ _validate_assert( value, rule, path, errors) if rule.assert_proc
1225
+ _validate_enum( value, rule, path, errors) if rule.enum
1226
+ return if value.nil?
1227
+ _validate_pattern(value, rule, path, errors) if rule.pattern
1228
+ _validate_range( value, rule, path, errors) if rule.range
1229
+ _validate_length( value, rule, path, errors) if rule.length
1230
+ _validate_unique( value, rule, path, errors, uniq_table) if uniq_table
1231
+ end
1232
+
1233
+
1234
+ def _validate_unique(value, rule, path, errors, uniq_table)
1235
+ assert_error "uniq_table=#{uniq_table.inspect}" unless rule.unique || rule.ident
1236
+ if uniq_table.key?(value)
1237
+ exist_at = uniq_table[value]
1238
+ exist_at = "/#{exist_at.join('/')}" if exist_at.is_a?(Array)
1239
+ #* key=:value_notunique msg="is already used at '%s'."
1240
+ errors << validate_error(:value_notunique, rule, path, value, exist_at)
1241
+ else
1242
+ uniq_table[value] = path.dup
1243
+ end
1244
+ end
1245
+ public :_validate_unique
1246
+
1247
+
1248
+ def _validate_assert(value, rule, path, errors)
1249
+ assert_error("rule=#{rule._inspect}") unless rule.assert_proc
1250
+ unless rule.assert_proc.call(value)
1251
+ #* key=:assert_failed msg="assertion expression failed (%s)."
1252
+ errors << validate_error(:assert_failed, rule, path, value, [rule.assert])
1253
+ end
1254
+ end
1255
+
1256
+
1257
+ def _validate_enum(value, rule, path, errors)
1258
+ assert_error("rule=#{rule._inspect}") unless rule.enum
1259
+ unless rule.enum.include?(value)
1260
+ keyname = path.is_a?(Array) ? path[-1] : File.basename(path)
1261
+ keyname = 'enum' if keyname =~ /\A\d+\z/
1262
+ #* key=:enum_notexist msg="invalid %s value."
1263
+ errors << validate_error(:enum_notexist, rule, path, value, [keyname])
1264
+ end
1265
+ end
1266
+
1267
+
1268
+ def _validate_pattern(value, rule, path, errors)
1269
+ assert_error("rule=#{rule._inspect}") unless rule.pattern
1270
+ unless value.to_s =~ rule.regexp
1271
+ #* key=:pattern_unmatch msg="not matched to pattern %s."
1272
+ errors << validate_error(:pattern_unmatch, rule, path, value, [rule.pattern])
1273
+ end
1274
+ end
1275
+
1276
+
1277
+ def _validate_range(value, rule, path, errors)
1278
+ assert_error("rule=#{rule._inspect}") unless rule.range
1279
+ assert_error("value.class=#{value.class.name}") unless Types.scalar?(value)
1280
+ h = rule.range
1281
+ max, min, max_ex, min_ex = h['max'], h['min'], h['max-ex'], h['min-ex']
1282
+ if max && max < value
1283
+ #* key=:range_toolarge msg="too large (> max %s)."
1284
+ errors << validate_error(:range_toolarge, rule, path, value, [max.to_s])
1285
+ end
1286
+ if min && min > value
1287
+ #* key=:range_toosmall msg="too small (< min %s)."
1288
+ errors << validate_error(:range_toosmall, rule, path, value, [min.to_s])
1289
+ end
1290
+ if max_ex && max_ex <= value
1291
+ #* key=:range_toolargeex msg="too large (>= max %s)."
1292
+ errors << validate_error(:range_toolargeex, rule, path, value, [max_ex.to_s])
1293
+ end
1294
+ if min_ex && min_ex >= value
1295
+ #* key=:range_toosmallex msg="too small (<= min %s)."
1296
+ errors << validate_error(:range_toosmallex, rule, path, value, [min_ex.to_s])
1297
+ end
1298
+ end
1299
+
1300
+
1301
+ def _validate_length(value, rule, path, errors)
1302
+ assert_error("rule=#{rule._inspect}") unless rule.length
1303
+ assert_error("value.class=#{value.class.name}") unless value.is_a?(String) || value.is_a?(Text)
1304
+ len = value.to_s.length
1305
+ h = rule.length
1306
+ max, min, max_ex, min_ex = h['max'], h['min'], h['max-ex'], h['min-ex']
1307
+ if max && max < len
1308
+ #* key=:length_toolong msg="too long (length %d > max %d)."
1309
+ errors << validate_error(:length_toolong, rule, path, value, [len, max])
1310
+ end
1311
+ if min && min > len
1312
+ #* key=:length_tooshort msg="too short (length %d < min %d)."
1313
+ errors << validate_error(:length_tooshort, rule, path, value, [len, min])
1314
+ end
1315
+ if max_ex && max_ex <= len
1316
+ #* key=:length_toolongex msg="too long (length %d >= max %d)."
1317
+ errors << validate_error(:length_toolongex, rule, path, value, [len, max_ex])
1318
+ end
1319
+ if min_ex && min_ex >= len
1320
+ #* key=:length_tooshortex msg="too short (length %d <= min %d)."
1321
+ errors << validate_error(:length_tooshortex, rule, path, value, [len, min_ex])
1322
+ end
1323
+ end
1324
+
1325
+
1326
+ end
1327
+
1328
+ end
1329
+ #--end of require 'kwalify/validator'
1330
+ #--begin of require 'kwalify/meta-validator'
1331
+ ###
1332
+ ### $Rev$
1333
+ ### $Release: 0.7.2 $
1334
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
1335
+ ###
1336
+
1337
+ #--already included require 'kwalify/errors'
1338
+ #--already included require 'kwalify/rule'
1339
+ #--already included require 'kwalify/validator'
1340
+ #--begin of require 'kwalify/parser/yaml'
1341
+ ###
1342
+ ### $Rev$
1343
+ ### $Release: 0.7.2 $
1344
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
1345
+ ###
1346
+
1347
+ #--already included require 'kwalify/validator'
1348
+ #--already included require 'kwalify/errors'
1349
+ #--begin of require 'kwalify/util'
1350
+ ###
1351
+ ### $Rev$
1352
+ ### $Release: 0.7.2 $
1353
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
1354
+ ###
1355
+
1356
+ module Kwalify
1357
+
1358
+ module Util
1359
+
1360
+ module_function
1361
+
1362
+ ##
1363
+ ## expand tab character to spaces
1364
+ ##
1365
+ ## ex.
1366
+ ## untabified_str = YamlHelper.untabify(tabbed_str)
1367
+ ##
1368
+ def untabify(str, width=8)
1369
+ return str if str.nil?
1370
+ list = str.split(/\t/, -1) # if 2nd arg is negative then split() doesn't remove tailing empty strings
1371
+ last = list.pop
1372
+ sb = ''
1373
+ list.each do |s|
1374
+ column = (n = s.rindex(?\n)) ? s.length - n - 1 : s.length
1375
+ n = width - (column % width)
1376
+ sb << s << (' ' * n)
1377
+ end
1378
+ sb << last if last
1379
+ return sb
1380
+ end
1381
+
1382
+
1383
+ ## traverse schema
1384
+ ##
1385
+ ## ex.
1386
+ ## schema = YAML.load_file('myschema.yaml')
1387
+ ## Kwalify::Util.traverse_schema(schema) do |rulehash|
1388
+ ## ## add module prefix to class name
1389
+ ## if rulehash['class']
1390
+ ## rulehash['class'] = 'MyModule::' + rulehash['class']
1391
+ ## end
1392
+ ## end
1393
+ def traverse_schema(schema, &block) #:yield: rulehash
1394
+ hash = schema
1395
+ _done = {}
1396
+ _traverse_schema(hash, _done, &block)
1397
+ end
1398
+
1399
+ def _traverse_schema(hash, _done={}, &block)
1400
+ return if _done.key?(hash.__id__)
1401
+ _done[hash.__id__] = hash
1402
+ yield hash
1403
+ if hash['mapping']
1404
+ hash['mapping'].each {|k, v| _traverse_schema(v, _done, &block) }
1405
+ elsif hash['sequence']
1406
+ _traverse_schema(hash['sequence'][0], _done, &block)
1407
+ end
1408
+ end
1409
+ private :_traverse_schema
1410
+
1411
+
1412
+ ## traverse rule
1413
+ ##
1414
+ ## ex.
1415
+ ## schema = YAML.load_file('myschema.yaml')
1416
+ ## validator = Kwalify::Validator.new(schema)
1417
+ ## Kwalify::Util.traverse_rule(validator) do |rule|
1418
+ ## p rule if rule.classname
1419
+ ## end
1420
+ def traverse_rule(validator, &block) #:yield: rule
1421
+ rule = validator.is_a?(Rule) ? validator : validator.rule
1422
+ _done = {}
1423
+ _traverse_rule(rule, _done, &block)
1424
+ end
1425
+
1426
+ def _traverse_rule(rule, _done={}, &block)
1427
+ return if _done.key?(rule.__id__)
1428
+ _done[rule.__id__] = rule
1429
+ yield rule
1430
+ rule.sequence.each do |seq_rule|
1431
+ _traverse_rule(seq_rule, _done, &block)
1432
+ end if rule.sequence
1433
+ rule.mapping.each do |name, map_rule|
1434
+ _traverse_rule(map_rule, _done, &block)
1435
+ end if rule.mapping
1436
+ end
1437
+ private :_traverse_rule
1438
+
1439
+
1440
+ ##
1441
+ ## get class object. if not found, NameError raised.
1442
+ ##
1443
+ def get_class(classname)
1444
+ klass = Object
1445
+ classname.split('::').each do |name|
1446
+ klass = klass.const_get(name)
1447
+ end
1448
+ return klass
1449
+ end
1450
+
1451
+
1452
+ ##
1453
+ ## create a hash table from list of hash with primary key.
1454
+ ##
1455
+ ## ex.
1456
+ ## hashlist = [
1457
+ ## { "name"=>"Foo", "gender"=>"M", "age"=>20, },
1458
+ ## { "name"=>"Bar", "gender"=>"F", "age"=>25, },
1459
+ ## { "name"=>"Baz", "gender"=>"M", "age"=>30, },
1460
+ ## ]
1461
+ ## hashtable = YamlHelper.create_hashtable(hashlist, "name")
1462
+ ## p hashtable
1463
+ ## # => { "Foo" => { "name"=>"Foo", "gender"=>"M", "age"=>20, },
1464
+ ## # "Bar" => { "name"=>"Bar", "gender"=>"F", "age"=>25, },
1465
+ ## # "Baz" => { "name"=>"Baz", "gender"=>"M", "age"=>30, }, }
1466
+ ##
1467
+ def create_hashtable(hashlist, primarykey, flag_duplicate_check=true)
1468
+ hashtable = {}
1469
+ hashlist.each do |hash|
1470
+ key = hash[primarykey]
1471
+ unless key
1472
+ riase "primary key '#{key}' not found."
1473
+ end
1474
+ if flag_duplicate_check && hashtable.key?(key)
1475
+ raise "primary key '#{key}' duplicated (value '#{hashtable[key]}')"
1476
+ end
1477
+ hashtable[key] = hash
1478
+ end if hashlist
1479
+ return hashtable
1480
+ end
1481
+
1482
+
1483
+ ##
1484
+ ## get nested value directly.
1485
+ ##
1486
+ ## ex.
1487
+ ## val = YamlHelper.get_value(obj, ['aaa', 0, 'xxx'])
1488
+ ##
1489
+ ## This is equal to the following:
1490
+ ## begin
1491
+ ## val = obj['aaa'][0]['xxx']
1492
+ ## rescue NameError
1493
+ ## val = nil
1494
+ ## end
1495
+ ##
1496
+ def get_value(obj, path)
1497
+ val = obj
1498
+ path.each do |key|
1499
+ return nil unless val.is_a?(Hash) || val.is_a?(Array)
1500
+ val = val[key]
1501
+ end if path
1502
+ return val
1503
+ end
1504
+
1505
+ end
1506
+
1507
+ end
1508
+ #--end of require 'kwalify/util'
1509
+ #--begin of require 'kwalify/parser/base'
1510
+ ###
1511
+ ### $Rev$
1512
+ ### $Release: 0.7.2 $
1513
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
1514
+ ###
1515
+
1516
+ require 'strscan'
1517
+ #--already included require 'kwalify/errors'
1518
+ #--already included require 'kwalify/util'
1519
+
1520
+
1521
+ ##
1522
+ ## base class for Yaml::Parser
1523
+ ##
1524
+ class Kwalify::BaseParser
1525
+
1526
+
1527
+ def reset(input, filename=nil, untabify=false)
1528
+ input = Kwalify::Util.untabify(input) if untabify
1529
+ @scanner = StringScanner.new(input)
1530
+ @filename = filename
1531
+ @linenum = 1
1532
+ @column = 1
1533
+ end
1534
+ attr_reader :filename, :linenum, :column
1535
+
1536
+
1537
+ def scan(regexp)
1538
+ ret = @scanner.scan(regexp)
1539
+ return nil if ret.nil?
1540
+ _set_column_and_linenum(ret)
1541
+ return ret
1542
+ end
1543
+
1544
+
1545
+ def _set_column_and_linenum(s)
1546
+ pos = s.rindex(?\n)
1547
+ if pos
1548
+ @column = s.length - pos
1549
+ @linenum += s.count("\n")
1550
+ else
1551
+ @column += s.length
1552
+ end
1553
+ end
1554
+
1555
+
1556
+ def match?(regexp)
1557
+ return @scanner.match?(regexp)
1558
+ end
1559
+
1560
+
1561
+ def group(n)
1562
+ return @scanner[n]
1563
+ end
1564
+
1565
+
1566
+ def eos?
1567
+ return @scanner.eos?
1568
+ end
1569
+
1570
+
1571
+ def peep(n=1)
1572
+ return @scanner.peep(n)
1573
+ end
1574
+
1575
+
1576
+ def _getch
1577
+ ch = @scanner.getch()
1578
+ if ch == "\n"
1579
+ @linenum += 1
1580
+ @column = 0
1581
+ else
1582
+ @column += 1
1583
+ end
1584
+ return ch
1585
+ end
1586
+
1587
+
1588
+ CHAR_TABLE = { "\""=>"\"", "\\"=>"\\", "n"=>"\n", "r"=>"\r", "t"=>"\t", "b"=>"\b" }
1589
+
1590
+ def scan_string
1591
+ ch = _getch()
1592
+ ch == '"' || ch == "'" or raise "assertion error"
1593
+ endch = ch
1594
+ s = ''
1595
+ while !(ch = _getch()).nil? && ch != endch
1596
+ if ch != '\\'
1597
+ s << ch
1598
+ elsif (ch = _getch()).nil?
1599
+ raise _syntax_error("%s: string is not closed." % (endch == '"' ? "'\"'" : '"\'"'))
1600
+ elsif endch == '"'
1601
+ if CHAR_TABLE.key?(ch)
1602
+ s << CHAR_TABLE[ch]
1603
+ elsif ch == 'u'
1604
+ ch2 = scan(/(?:[0-9a-f][0-9a-f]){1,4}/)
1605
+ unless ch2
1606
+ raise _syntax_error("\\x: invalid unicode format.")
1607
+ end
1608
+ s << [ch2.hex].pack('U*')
1609
+ elsif ch == 'x'
1610
+ ch2 = scan(/[0-9a-zA-Z][0-9a-zA-Z]/)
1611
+ unless ch2
1612
+ raise _syntax_error("\\x: invalid binary format.")
1613
+ end
1614
+ s << [ch2].pack('H2')
1615
+ else
1616
+ s << "\\" << ch
1617
+ end
1618
+ elsif endch == "'"
1619
+ ch == '\'' || ch == '\\' ? s << ch : s << '\\' << ch
1620
+ else
1621
+ raise "unreachable"
1622
+ end
1623
+ end
1624
+ #_getch()
1625
+ return s
1626
+ end
1627
+
1628
+
1629
+ def _syntax_error(message, path=nil, linenum=@linenum, column=@column)
1630
+ #message = _build_message(message_key)
1631
+ return _error(Kwalify::SyntaxError, message.to_s, path, linenum, column)
1632
+ end
1633
+ protected :_syntax_error
1634
+
1635
+
1636
+ end
1637
+ #--end of require 'kwalify/parser/base'
1638
+
1639
+
1640
+
1641
+ module Kwalify
1642
+
1643
+ module Yaml
1644
+ end
1645
+
1646
+ end
1647
+
1648
+
1649
+ ##
1650
+ ## YAML parser with validator
1651
+ ##
1652
+ ## ex.
1653
+ ## schema = YAML.load_file('schema.yaml')
1654
+ ## require 'kwalify'
1655
+ ## validator = Kwalify::Validator.new(schema)
1656
+ ## parser = Kwalify::Yaml::Parser.new(validator) # validator is optional
1657
+ ## #parser.preceding_alias = true # optional
1658
+ ## #parser.data_binding = true # optional
1659
+ ## ydoc = parser.parse_file('data.yaml')
1660
+ ## errors = parser.errors
1661
+ ## if errors && !errors.empty?
1662
+ ## errors.each do |e|
1663
+ ## puts "line=#{e.linenum}, path=#{e.path}, mesg=#{e.message}"
1664
+ ## end
1665
+ ## end
1666
+ ##
1667
+ class Kwalify::Yaml::Parser < Kwalify::BaseParser
1668
+
1669
+
1670
+ alias reset_scanner reset
1671
+
1672
+
1673
+ def initialize(validator=nil, properties={})
1674
+ @validator = validator.is_a?(Hash) ? Kwalify::Validator.new(validator) : validator
1675
+ @data_binding = properties[:data_binding] # enable data binding or not
1676
+ @preceding_alias = properties[:preceding_alias] # allow preceding alias or not
1677
+ @sequence_class = properties[:sequence_class] || Array
1678
+ @mapping_class = properties[:mapping_class] || Hash
1679
+ end
1680
+ attr_accessor :validator # Validator
1681
+ attr_accessor :data_binding # boolean
1682
+ attr_accessor :preceding_alias # boolean
1683
+ attr_accessor :sequence_class # Class
1684
+ attr_accessor :mapping_class # Class
1685
+
1686
+
1687
+ def reset_parser()
1688
+ @anchors = {}
1689
+ @errors = []
1690
+ @done = {}
1691
+ @preceding_aliases = []
1692
+ @location_table = {} # object_id -> sequence or mapping
1693
+ @doc = nil
1694
+ end
1695
+ attr_reader :errors
1696
+
1697
+
1698
+ def _error(klass, message, path, linenum, column)
1699
+ ex = klass.new(message)
1700
+ ex.path = path.is_a?(Array) ? '/' + path.join('/') : path
1701
+ ex.linenum = linenum
1702
+ ex.column = column
1703
+ ex.filename = @filename
1704
+ return ex
1705
+ end
1706
+ private :_error
1707
+
1708
+
1709
+ # def _validate_error(message, path, linenum=@linenum, column=@column)
1710
+ # #message = _build_message(message_key)
1711
+ # error = _error(ValidationError, message.to_s, path, linenum, column)
1712
+ # @errors << error
1713
+ # end
1714
+ # private :_validate_error
1715
+
1716
+
1717
+ def _set_error_info(linenum=@linenum, column=@column, &block)
1718
+ len = @errors.length
1719
+ yield
1720
+ n = @errors.length - len
1721
+ (1..n).each do |i|
1722
+ error = @errors[-i]
1723
+ error.linenum ||= linenum
1724
+ error.column ||= column
1725
+ error.filename ||= @filename
1726
+ end if n > 0
1727
+ end
1728
+
1729
+
1730
+ def skip_spaces_and_comments()
1731
+ scan(/\s+/)
1732
+ while match?(/\#/)
1733
+ scan(/.*?\n/)
1734
+ scan(/\s+/)
1735
+ end
1736
+ end
1737
+
1738
+
1739
+ def document_start?()
1740
+ return match?(/---\s/) && @column == 1
1741
+ end
1742
+
1743
+
1744
+ def stream_end?()
1745
+ return match?(/\.\.\.\s/) && @column == 1
1746
+ end
1747
+
1748
+
1749
+ def has_next?()
1750
+ return !(eos? || stream_end?)
1751
+ end
1752
+
1753
+
1754
+ def parse(input=nil, opts={})
1755
+ reset_scanner(input, opts[:filename], opts[:untabify]) if input
1756
+ return parse_next()
1757
+ end
1758
+
1759
+
1760
+ def parse_file(filename, opts={})
1761
+ opts[:filename] = filename
1762
+ return parse(File.read(filename), opts)
1763
+ end
1764
+
1765
+
1766
+ def parse_next()
1767
+ reset_parser()
1768
+ path = []
1769
+ skip_spaces_and_comments()
1770
+ if document_start?()
1771
+ scan(/.*\n/)
1772
+ skip_spaces_and_comments()
1773
+ end
1774
+ _linenum = @linenum #*V
1775
+ _column = @column #*V
1776
+ rule = @validator ? @validator.rule : nil #*V
1777
+ uniq_table = nil #*V
1778
+ parent = nil #*V
1779
+ val = parse_block_value(0, rule, path, uniq_table, parent)
1780
+ _set_error_info(_linenum, _column) do #*V
1781
+ @validator._validate(val, rule, [], @errors, @done, uniq_table, false) #*V
1782
+ end if rule #*V
1783
+ resolve_preceding_aliases(val) if @preceding_alias
1784
+ unless eos? || document_start?() || stream_end?()
1785
+ raise _syntax_error("document end expected (maybe invalid tab char found).", path)
1786
+ end
1787
+ @doc = val
1788
+ @location_table[-1] = [_linenum, _column]
1789
+ return val
1790
+ end
1791
+
1792
+
1793
+ def parse_stream(input, opts={}, &block)
1794
+ reset_scanner(input, opts[:filename], opts[:untabify])
1795
+ ydocs = block_given? ? nil : []
1796
+ while true
1797
+ ydoc = parse_next()
1798
+ ydocs ? (ydocs << ydoc) : (yield ydoc)
1799
+ break if eos? || stream_end?()
1800
+ document_start?() or raise "** internal error"
1801
+ scan(/.*\n/)
1802
+ end
1803
+ return ydocs
1804
+ end
1805
+
1806
+ alias parse_documents parse_stream
1807
+
1808
+
1809
+ MAPKEY_PATTERN = /([\w.][-\w.:]*\*?|".*?"|'.*?'|:\w+|=|<<)[ \t]*:\s+/ # :nodoc:
1810
+
1811
+ PRECEDING_ALIAS_PLACEHOLDER = Object.new # :nodoc:
1812
+
1813
+
1814
+ def parse_anchor(rule, path, uniq_table, container)
1815
+ name = group(1)
1816
+ if @anchors.key?(name)
1817
+ raise _syntax_error("&#{name}: anchor duplicated.", path,
1818
+ @linenum, @column - name.length)
1819
+ end
1820
+ skip_spaces_and_comments()
1821
+ return name
1822
+ end
1823
+
1824
+
1825
+ def parse_alias(rule, path, uniq_table, container)
1826
+ name = group(1)
1827
+ if @anchors.key?(name)
1828
+ val = @anchors[name]
1829
+ elsif @preceding_alias
1830
+ @preceding_aliases << [name, rule, path.dup, container,
1831
+ @linenum, @column - name.length - 1]
1832
+ val = PRECEDING_ALIAS_PLACEHOLDER
1833
+ else
1834
+ raise _syntax_error("*#{name}: anchor not found.", path,
1835
+ @linenum, @column - name.length - 1)
1836
+ end
1837
+ skip_spaces_and_comments()
1838
+ return val
1839
+ end
1840
+
1841
+
1842
+ def resolve_preceding_aliases(val)
1843
+ @preceding_aliases.each do |name, rule, path, container, _linenum, _column|
1844
+ unless @anchors.key?(name)
1845
+ raise _syntax_error("*#{name}: anchor not found.", path, _linenum, _column)
1846
+ end
1847
+ key = path[-1]
1848
+ val = @anchors[name]
1849
+ raise unless !container.respond_to?('[]') || container[key].equal?(PRECEDING_ALIAS_PLACEHOLDER)
1850
+ if container.is_a?(Array)
1851
+ container[key] = val
1852
+ else
1853
+ put_to_map(rule, container, key, val, _linenum, _column)
1854
+ end
1855
+ _set_error_info(_linenum, _column) do #*V
1856
+ @validator._validate(val, rule, path, @errors, @done, false) #*V
1857
+ end if rule #*V
1858
+ end
1859
+ end
1860
+
1861
+
1862
+ def parse_block_value(level, rule, path, uniq_table, container)
1863
+ skip_spaces_and_comments()
1864
+ ## nil
1865
+ return nil if @column <= level || eos?
1866
+ ## anchor and alias
1867
+ name = nil
1868
+ if scan(/\&([-\w]+)/)
1869
+ name = parse_anchor(rule, path, uniq_table, container)
1870
+ elsif scan(/\*([-\w]+)/)
1871
+ return parse_alias(rule, path, uniq_table, container)
1872
+ end
1873
+ ## type
1874
+ if scan(/!!?\w+/)
1875
+ skip_spaces_and_comments()
1876
+ end
1877
+ ## sequence
1878
+ if match?(/-\s+/)
1879
+ if rule && !rule.sequence
1880
+ #_validate_error("sequence is not expected.", path)
1881
+ rule = nil
1882
+ end
1883
+ seq = create_sequence(rule, @linenum, @column)
1884
+ @anchors[name] = seq if name
1885
+ parse_block_seq(seq, rule, path, uniq_table)
1886
+ return seq
1887
+ end
1888
+ ## mapping
1889
+ if match?(MAPKEY_PATTERN)
1890
+ if rule && !rule.mapping
1891
+ #_validate_error("mapping is not expected.", path)
1892
+ rule = nil
1893
+ end
1894
+ map = create_mapping(rule, @linenum, @column)
1895
+ @anchors[name] = map if name
1896
+ parse_block_map(map, rule, path, uniq_table)
1897
+ return map
1898
+ end
1899
+ ## sequence (flow-style)
1900
+ if match?(/\[/)
1901
+ if rule && !rule.sequence
1902
+ #_validate_error("sequence is not expected.", path)
1903
+ rule = nil
1904
+ end
1905
+ seq = create_sequence(rule, @linenum, @column)
1906
+ @anchors[name] = seq if name
1907
+ parse_flow_seq(seq, rule, path, uniq_table)
1908
+ return seq
1909
+ end
1910
+ ## mapping (flow-style)
1911
+ if match?(/\{/)
1912
+ if rule && !rule.mapping
1913
+ #_validate_error("mapping is not expected.", path)
1914
+ rule = nil
1915
+ end
1916
+ map = create_mapping(rule, @linenum, @column)
1917
+ @anchors[name] = map if name
1918
+ parse_flow_map(map, rule, path, uniq_table)
1919
+ return map
1920
+ end
1921
+ ## block text
1922
+ if match?(/[|>]/)
1923
+ text = parse_block_text(level, rule, path, uniq_table)
1924
+ @anchors[name] = text if name
1925
+ return text
1926
+ end
1927
+ ## scalar
1928
+ scalar = parse_block_scalar(rule, path, uniq_table)
1929
+ @anchors[name] = scalar if name
1930
+ return scalar
1931
+ end
1932
+
1933
+
1934
+ def parse_block_seq(seq, seq_rule, path, uniq_table)
1935
+ level = @column
1936
+ rule = seq_rule ? seq_rule.sequence[0] : nil
1937
+ path.push(nil)
1938
+ i = 0
1939
+ _linenum = @linenum #*V
1940
+ _column = @column #*V
1941
+ uniq_table = rule ? rule._uniqueness_check_table() : nil #*V
1942
+ while level == @column && scan(/-\s+/)
1943
+ path[-1] = i
1944
+ skip_spaces_and_comments() #*V
1945
+ _linenum2 = @linenum
1946
+ _column2 = @column
1947
+ val = parse_block_value(level, rule, path, uniq_table, seq)
1948
+ add_to_seq(rule, seq, val, _linenum2, _column2) # seq << val
1949
+ _set_error_info(_linenum, _column) do #*V
1950
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
1951
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
1952
+ skip_spaces_and_comments()
1953
+ i += 1
1954
+ _linenum = @linenum #*V
1955
+ _column = @column #*V
1956
+ end
1957
+ path.pop()
1958
+ return seq
1959
+ end
1960
+
1961
+
1962
+ def _parse_map_value(map, map_rule, path, level, key, is_merged, uniq_table,
1963
+ _linenum, _column, _linenum2, _column2) #:nodoc:
1964
+ key = to_mapkey(key)
1965
+ path[-1] = key
1966
+ #if map.is_a?(Hash) && map.key?(key) && !is_merged
1967
+ if map.respond_to?('key?') && map.key?(key) && !is_merged
1968
+ rule = map_rule.mapping[key]
1969
+ unless rule && rule.default
1970
+ raise _syntax_error("mapping key is duplicated.", path)
1971
+ end
1972
+ end
1973
+ #
1974
+ if key == '=' # default
1975
+ val = level ? parse_block_value(level, nil, path, uniq_table, map) \
1976
+ : parse_flow_value(nil, path, uniq_table, map)
1977
+ map.default = val
1978
+ elsif key == '<<' # merge
1979
+ classobj = nil
1980
+ if map_rule && map_rule.classname
1981
+ map_rule = map_rule.dup()
1982
+ classobj = map_rule.classobj
1983
+ map_rule.classname = nil
1984
+ map_rule.classobj = nil
1985
+ end
1986
+ val = level ? parse_block_value(level, map_rule, path, uniq_table, map) \
1987
+ : parse_flow_value(map_rule, path, uniq_table, map)
1988
+ if val.is_a?(Array)
1989
+ val.each_with_index do |v, i|
1990
+ unless v.is_a?(Hash) || (classobj && val.is_a?(classobj))
1991
+ raise _syntax_error("'<<': mapping required.", path + [i])
1992
+ end
1993
+ end
1994
+ values = val
1995
+ elsif val.is_a?(Hash) || (classobj && val.is_a?(classobj))
1996
+ values = [val]
1997
+ else
1998
+ raise _syntax_error("'<<': mapping (or sequence of mapping) required.", path)
1999
+ end
2000
+ #
2001
+ values.each do |hash|
2002
+ if !hash.is_a?(Hash)
2003
+ assert_error "hash=#{hash.inspect}" unless classobj && hash.is_a?(classobj)
2004
+ obj = hash
2005
+ hash = {}
2006
+ obj.instance_variables.each do |name|
2007
+ key = name[1..-1] # '@foo' => 'foo'
2008
+ val = obj.instane_variable_get(name)
2009
+ hash[key] = val
2010
+ end
2011
+ end
2012
+ for key, val in hash
2013
+ path[-1] = key #*V
2014
+ rule = map_rule ? map_rule.mapping[key] : nil #*V
2015
+ utable = uniq_table ? uniq_table[key] : nil #*V
2016
+ _validate_map_value(map, map_rule, rule, path, utable, #*V
2017
+ key, val, _linenum, _column) #*V
2018
+ put_to_map(rule, map, key, val, _linenum2, _column2)
2019
+ end
2020
+ end
2021
+ is_merged = true
2022
+ else # other
2023
+ rule = map_rule ? map_rule.mapping[key] : nil #*V
2024
+ utable = uniq_table ? uniq_table[key] : nil #*V
2025
+ val = level ? parse_block_value(level, rule, path, utable, map) \
2026
+ : parse_flow_value(rule, path, utable, map)
2027
+ _validate_map_value(map, map_rule, rule, path, utable, key, val, #*V
2028
+ _linenum, _column) #*V
2029
+ put_to_map(rule, map, key, val, _linenum2, _column2)
2030
+ end
2031
+ return is_merged
2032
+ end
2033
+
2034
+
2035
+ def _validate_map_value(map, map_rule, rule, path, uniq_table, key, val, #*V
2036
+ _linenum, _column) #*V
2037
+ if map_rule && !rule #*V
2038
+ #_validate_error("unknown mapping key.", path) #*V
2039
+ _set_error_info(_linenum, _column) do #*V
2040
+ error = Kwalify::ErrorHelper.validate_error(:key_undefined, #*V
2041
+ rule, path, map, ["#{key}:"]) #*V
2042
+ @errors << error #*V
2043
+ #error.linenum = _linenum #*V
2044
+ #error.column = _column #*V
2045
+ end #*V
2046
+ end #*V
2047
+ _set_error_info(_linenum, _column) do #*V
2048
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
2049
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
2050
+ end
2051
+
2052
+
2053
+ def parse_block_map(map, map_rule, path, uniq_table)
2054
+ _start_linenum = @linenum #*V
2055
+ _start_column = @column #*V
2056
+ level = @column
2057
+ path.push(nil)
2058
+ is_merged = false
2059
+ while true
2060
+ _linenum = @linenum #*V
2061
+ _column = @column #*V
2062
+ break unless level == @column && scan(MAPKEY_PATTERN)
2063
+ key = group(1)
2064
+ skip_spaces_and_comments() #*V
2065
+ _linenum2 = @linenum #*V
2066
+ _column2 = @column #*V
2067
+ is_merged = _parse_map_value(map, map_rule, path, level, key, is_merged,
2068
+ uniq_table, _linenum, _column, _linenum2, _column2)
2069
+ #skip_spaces_and_comments()
2070
+ end
2071
+ path.pop()
2072
+ _set_error_info(_start_linenum, _start_column) do #*V
2073
+ @validator._validate_mapping_required_keys(map, map_rule, #*V
2074
+ path, @errors) #*V
2075
+ end if map_rule #*V
2076
+ return map
2077
+ end
2078
+
2079
+
2080
+ def to_mapkey(str)
2081
+ if str[0] == ?" || str[0] == ?'
2082
+ return str[1..-2]
2083
+ else
2084
+ return to_scalar(str)
2085
+ end
2086
+ end
2087
+ private :to_mapkey
2088
+
2089
+
2090
+ def parse_block_scalar(rule, path, uniq_table)
2091
+ _linenum = @linenum #*V
2092
+ _column = @column #*V
2093
+ ch = peep(1)
2094
+ if ch == '"' || ch == "'"
2095
+ val = scan_string()
2096
+ scan(/[ \t]*(?:\#.*)?$/)
2097
+ else
2098
+ scan(/(.*?)[ \t]*(?:\#.*)?$/)
2099
+ #str.rstrip!
2100
+ val = to_scalar(group(1))
2101
+ end
2102
+ val = create_scalar(rule, val, _linenum, _column) #*V
2103
+ #_set_error_info(_linenum, _column) do #*V
2104
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
2105
+ #end if uniq_table #*V
2106
+ skip_spaces_and_comments()
2107
+ return val
2108
+ end
2109
+
2110
+
2111
+ def parse_block_text(column, rule, path, uniq_table)
2112
+ _linenum = @linenum #*V
2113
+ _column = @column #*V
2114
+ indicator = scan(/[|>]/)
2115
+ chomping = scan(/[-+]/)
2116
+ num = scan(/\d+/)
2117
+ indent = num ? column + num.to_i - 1 : nil
2118
+ unless scan(/[ \t]*(.*?)(\#.*)?\r?\n/) # /[ \t]*(\#.*)?\r?\n/
2119
+ raise _syntax_error("Syntax Error (line break or comment are expected)", path)
2120
+ end
2121
+ s = group(1)
2122
+ is_folded = false
2123
+ while match?(/( *)(.*?)(\r?\n)/)
2124
+ spaces = group(1)
2125
+ text = group(2)
2126
+ nl = group(3)
2127
+ if indent.nil?
2128
+ if spaces.length >= column
2129
+ indent = spaces.length
2130
+ elsif text.empty?
2131
+ s << nl
2132
+ scan(/.*?\n/)
2133
+ next
2134
+ else
2135
+ @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.'
2136
+ break
2137
+ end
2138
+ else
2139
+ if spaces.length < indent && !text.empty?
2140
+ @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.'
2141
+ break
2142
+ end
2143
+ end
2144
+ scan(/.*?\n/)
2145
+ if indicator == '|'
2146
+ s << spaces[indent..-1] if spaces.length >= indent
2147
+ s << text << nl
2148
+ else # indicator == '>'
2149
+ if !text.empty? && spaces.length == indent
2150
+ if s.sub!(/\r?\n((\r?\n)+)\z/, '\1')
2151
+ nil
2152
+ elsif is_folded
2153
+ s.sub!(/\r?\n\z/, ' ')
2154
+ end
2155
+ #s.sub!(/\r?\n\z/, '') if !s.sub!(/\r?\n(\r?\n)+\z/, '\1') && is_folded
2156
+ is_folded = true
2157
+ else
2158
+ is_folded = false
2159
+ s << spaces[indent..-1] if spaces.length > indent
2160
+ end
2161
+ s << text << nl
2162
+ end
2163
+ end
2164
+ ## chomping
2165
+ if chomping == '+'
2166
+ nil
2167
+ elsif chomping == '-'
2168
+ s.sub!(/(\r?\n)+\z/, '')
2169
+ else
2170
+ s.sub!(/(\r?\n)(\r?\n)+\z/, '\1')
2171
+ end
2172
+ #
2173
+ skip_spaces_and_comments()
2174
+ val = s
2175
+ #_set_error_info(_linenum, _column) do #*V
2176
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
2177
+ #end if uniq_table #*V
2178
+ return val
2179
+ end
2180
+
2181
+
2182
+ def parse_flow_value(rule, path, uniq_table, container)
2183
+ skip_spaces_and_comments()
2184
+ ## anchor and alias
2185
+ name = nil
2186
+ if scan(/\&([-\w]+)/)
2187
+ name = parse_anchor(rule, path, uniq_table, container)
2188
+ elsif scan(/\*([-\w]+)/)
2189
+ return parse_alias(rule, path, uniq_table, container)
2190
+ end
2191
+ ## type
2192
+ if scan(/!!?\w+/)
2193
+ skip_spaces_and_comments()
2194
+ end
2195
+ ## sequence
2196
+ if match?(/\[/)
2197
+ if rule && !rule.sequence #*V
2198
+ #_validate_error("sequence is not expected.", path) #*V
2199
+ rule = nil #*V
2200
+ end #*V
2201
+ seq = create_sequence(rule, @linenum, @column)
2202
+ @anchors[name] = seq if name
2203
+ parse_flow_seq(seq, rule, path, uniq_table)
2204
+ return seq
2205
+ end
2206
+ ## mapping
2207
+ if match?(/\{/)
2208
+ if rule && !rule.mapping #*V
2209
+ #_validate_error("mapping is not expected.", path) #*V
2210
+ rule = nil #*V
2211
+ end #*V
2212
+ map = create_mapping(rule, @linenum, @column)
2213
+ @anchors[name] = map if name
2214
+ parse_flow_map(map, rule, path, uniq_table)
2215
+ return map
2216
+ end
2217
+ ## scalar
2218
+ scalar = parse_flow_scalar(rule, path, uniq_table)
2219
+ @anchors[name] = scalar if name
2220
+ return scalar
2221
+ end
2222
+
2223
+
2224
+ def parse_flow_seq(seq, seq_rule, path, uniq_table)
2225
+ #scan(/\[\s*/)
2226
+ scan(/\[/)
2227
+ skip_spaces_and_comments()
2228
+ if scan(/\]/)
2229
+ nil
2230
+ else
2231
+ rule = seq_rule ? seq_rule.sequence[0] : nil #*V
2232
+ uniq_table = rule ? rule._uniqueness_check_table() : nil #*V
2233
+ path.push(nil)
2234
+ i = 0
2235
+ while true
2236
+ path[-1] = i
2237
+ _linenum = @linenum #*V
2238
+ _column = @column #*V
2239
+ val = parse_flow_value(rule, path, uniq_table, seq)
2240
+ add_to_seq(rule, seq, val, _linenum, _column) # seq << val
2241
+ _set_error_info(_linenum, _column) do #*V
2242
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
2243
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
2244
+ skip_spaces_and_comments()
2245
+ break unless scan(/,\s+/)
2246
+ i += 1
2247
+ if match?(/\]/)
2248
+ raise _syntax_error("sequence item required (or last comma is extra).", path)
2249
+ end
2250
+ end
2251
+ path.pop()
2252
+ unless scan(/\]/)
2253
+ raise _syntax_error("flow sequence is not closed by ']'.", path)
2254
+ end
2255
+ end
2256
+ skip_spaces_and_comments()
2257
+ return seq
2258
+ end
2259
+
2260
+
2261
+ def parse_flow_map(map, map_rule, path, uniq_table)
2262
+ #scan(/\{\s*/) # not work?
2263
+ _start_linenum = @linenum #*V
2264
+ _start_column = @column #*V
2265
+ scan(/\{/)
2266
+ skip_spaces_and_comments()
2267
+ if scan(/\}/)
2268
+ nil
2269
+ else
2270
+ path.push(nil)
2271
+ is_merged = false
2272
+ while true
2273
+ _linenum = @linenum #*V
2274
+ _column = @column #*V
2275
+ unless scan(MAPKEY_PATTERN)
2276
+ raise _syntax_error("mapping key is expected.", path)
2277
+ end
2278
+ key = group(1)
2279
+ skip_spaces_and_comments()
2280
+ _linenum2 = @linenum #*V
2281
+ _column2 = @column #*V
2282
+ is_merged = _parse_map_value(map, map_rule, path, nil, key, is_merged,
2283
+ uniq_table, _linenum, _column, _linenum2, _column2)
2284
+ #skip_spaces_and_comments()
2285
+ break unless scan(/,\s+/)
2286
+ end
2287
+ path.pop()
2288
+ unless scan(/\}/)
2289
+ raise _syntax_error("flow mapping is not closed by '}'.", path)
2290
+ end
2291
+ end
2292
+ skip_spaces_and_comments()
2293
+ _set_error_info(_start_linenum, _start_column) do #*V
2294
+ @validator._validate_mapping_required_keys(map, map_rule, path, @errors) #*V
2295
+ end if map_rule #*V
2296
+ return map
2297
+ end
2298
+
2299
+
2300
+ def parse_flow_scalar(rule, path, uniq_table)
2301
+ ch = peep(1)
2302
+ _linenum = @linenum #*V
2303
+ _column = @column #*V
2304
+ if ch == '"' || ch == "'"
2305
+ val = scan_string()
2306
+ else
2307
+ str = scan(/[^,\]\}\#]*/)
2308
+ if match?(/,\S/)
2309
+ while match?(/,\S/)
2310
+ str << scan(/./)
2311
+ str << scan(/[^,\]\}\#]*/)
2312
+ end
2313
+ end
2314
+ str.rstrip!
2315
+ val = to_scalar(str)
2316
+ end
2317
+ val = create_scalar(rule, val, _linenum, _column) #*V
2318
+ #_set_error_info(_linenum, _column) do #*V
2319
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
2320
+ #end if uniq_table #*V
2321
+ skip_spaces_and_comments()
2322
+ return val
2323
+ end
2324
+
2325
+
2326
+ ####
2327
+
2328
+
2329
+ def to_scalar(str)
2330
+ case str
2331
+ when nil ; val = nil
2332
+ when /\A-?\d+\.\d+\z/ ; val = str.to_f
2333
+ when /\A-?\d+\z/ ; val = str.to_i
2334
+ when /\A(true|yes)\z/ ; val = true
2335
+ when /\A(false|no)\z/ ; val = false
2336
+ when /\A(null|~)\z/ ; val = nil
2337
+ when /\A"(.*)"\z/ ; val = $1
2338
+ when /\A'(.*)'\z/ ; val = $1
2339
+ when /\A:(\w+)\z/ ; val = $1.intern
2340
+ when /\A(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d):(\d\d))?\z/
2341
+ year, month, day, hour, min, sec = $1, $2, $3, $4, $5, $6
2342
+ if hour
2343
+ val = Time.mktime(year, month, day, hour, min, sec)
2344
+ else
2345
+ val = Date.new(year.to_i, month.to_i, day.to_i)
2346
+ end
2347
+ ## or
2348
+ #params = [$1, $2, $3, $4, $5, $6]
2349
+ #val = Time.mktime(*params)
2350
+ else
2351
+ val = str.empty? ? nil : str
2352
+ end
2353
+ skip_spaces_and_comments()
2354
+ return val
2355
+ end
2356
+
2357
+
2358
+ ##
2359
+
2360
+ protected
2361
+
2362
+
2363
+ def create_sequence(rule, linenum, column)
2364
+ seq = @sequence_class.new
2365
+ @location_table[seq.__id__] = []
2366
+ return seq
2367
+ end
2368
+
2369
+
2370
+ def create_mapping(rule, linenum, column)
2371
+ if rule && rule.classobj && @data_binding
2372
+ classobj = rule.classobj
2373
+ map = classobj.new
2374
+ else
2375
+ classobj = nil
2376
+ map = @mapping_class.new # {}
2377
+ end
2378
+ @location_table[map.__id__] = hash = {}
2379
+ hash[:classobj] = classobj if classobj
2380
+ return map
2381
+ end
2382
+
2383
+
2384
+ def create_scalar(rule, value, linenum, column)
2385
+ return value
2386
+ end
2387
+
2388
+
2389
+ def add_to_seq(rule, seq, val, linenum, column)
2390
+ seq << val
2391
+ @location_table[seq.__id__] << [linenum, column]
2392
+ end
2393
+
2394
+
2395
+ def put_to_map(rule, map, key, val, linenum, column)
2396
+ #if map.is_a?(Hash)
2397
+ # map[key] = val
2398
+ #elsif map.respond_to?(name="#{key}=")
2399
+ # map.__send__(name, val)
2400
+ #elsif map.respond_to?('[]=')
2401
+ # map[key] = val
2402
+ #else
2403
+ # map.instance_variable_set("@#{key}", val)
2404
+ #end
2405
+ map[key] = val
2406
+ @location_table[map.__id__][key] = [linenum, column]
2407
+ end
2408
+
2409
+
2410
+ def _getclass(classname)
2411
+ mod = Object
2412
+ classname.split(/::/).each do |modname|
2413
+ mod = mod.const_get(modname) # raises NameError when module not found
2414
+ end
2415
+ return mod
2416
+ end
2417
+
2418
+
2419
+ public
2420
+
2421
+
2422
+ def location(path)
2423
+ if path.empty? || path == '/'
2424
+ return @location_table[-1] # return value is [linenum, column]
2425
+ end
2426
+ if path.is_a?(Array)
2427
+ items = path.collect { |item| to_scalar(item) }
2428
+ elsif path.is_a?(String)
2429
+ items = path.split('/').collect { |item| to_scalar(item) }
2430
+ items.shift if path[0] == ?/ # delete empty string on head
2431
+ else
2432
+ raise ArgumentError.new("path should be Array or String.")
2433
+ end
2434
+ last_item = items.pop()
2435
+ c = @doc # collection
2436
+ items.each do |item|
2437
+ if c.is_a?(Array)
2438
+ c = c[item.to_i]
2439
+ elsif c.is_a?(Hash)
2440
+ c = c[item]
2441
+ elsif (table = @location_table[c.__id__]) && table[:classobj]
2442
+ if c.respond_to?(item)
2443
+ c = c.__send__(item)
2444
+ elsif c.respond_to?("[]=")
2445
+ c = c[item]
2446
+ else
2447
+ assert false
2448
+ end
2449
+ else
2450
+ #assert false
2451
+ raise ArgumentError.new("#{path.inspect}: invalid path.")
2452
+ end
2453
+ end
2454
+ collection = @location_table[c.__id__]
2455
+ return nil if collection.nil?
2456
+ index = c.is_a?(Array) ? last_item.to_i : last_item
2457
+ return collection[index] # return value is [linenum, column]
2458
+ end
2459
+
2460
+
2461
+ def set_errors_linenum(errors)
2462
+ errors.each do |error|
2463
+ error.linenum, error.column = location(error.path)
2464
+ end
2465
+ end
2466
+
2467
+
2468
+ end
2469
+ #--end of require 'kwalify/parser/yaml'
2470
+ #require 'yaml'
2471
+
2472
+ module Kwalify
2473
+
2474
+
2475
+ ##
2476
+ ## ex.
2477
+ ## meta_validator = Kwalify::MetaValidator.instance()
2478
+ ## schema = File.load_file('schema.yaml')
2479
+ ## errors = meta_validator.validate(schema)
2480
+ ## if !errors.empty?
2481
+ ## errors.each do |error|
2482
+ ## puts "[#{error.path}] #{error.message}"
2483
+ ## end
2484
+ ## end
2485
+ ##
2486
+ class MetaValidator < Validator
2487
+
2488
+ filename = File.join(File.dirname(__FILE__), 'kwalify.schema.yaml')
2489
+ META_SCHEMA = File.read(filename)
2490
+
2491
+ def self.instance()
2492
+ unless @instance
2493
+ schema = Kwalify::Yaml::Parser.new().parse(META_SCHEMA)
2494
+ @instance = MetaValidator.new(schema)
2495
+ end
2496
+ return @instance
2497
+ end
2498
+
2499
+ def initialize(schema, &block)
2500
+ super
2501
+ end
2502
+
2503
+ def validate_hook(value, rule, path, errors)
2504
+ return if value.nil? ## realy?
2505
+ return unless rule.name == "MAIN"
2506
+ #
2507
+ hash = value
2508
+ type = hash['type']
2509
+ type = Types::DEFAULT_TYPE if type.nil?
2510
+ klass = Types.type_class(type)
2511
+ #unless klass
2512
+ # errors << validate_error(:type_unknown, rule, "#{path}/type", type)
2513
+ #end
2514
+ #
2515
+ if hash.key?('class')
2516
+ val = hash['class']
2517
+ unless val.nil? || type == 'map'
2518
+ errors << validate_error(:class_notmap, rule, "#{path}/class", 'class:')
2519
+ end
2520
+ end
2521
+ #
2522
+ if hash.key?('pattern')
2523
+ val = hash['pattern']
2524
+ pat = (val =~ /\A\/(.*)\/([mi]?[mi]?)\z/ ? $1 : val)
2525
+ begin
2526
+ Regexp.compile(pat)
2527
+ rescue RegexpError => ex
2528
+ errors << validate_error(:pattern_syntaxerr, rule, "#{path}/pattern", val)
2529
+ end
2530
+ end
2531
+ #
2532
+ if hash.key?('enum')
2533
+ if Types.collection_type?(type)
2534
+ errors << validate_error(:enum_notscalar, rule, path, 'enum:')
2535
+ else
2536
+ #elem_table = {}
2537
+ hash['enum'].each do |elem|
2538
+ #if elem_table[elem]
2539
+ # errors << validate_error(:enum_duplicate, rule, "#{path}/enum", elem.to_s)
2540
+ #end
2541
+ #elem_table[elem] = true
2542
+ unless elem.is_a?(klass)
2543
+ errors << validate_error(:enum_type_unmatch, rule, "#{path}/enum", elem, [Kwalify.word(type)])
2544
+ end
2545
+ end
2546
+ end
2547
+ end
2548
+ #
2549
+ if hash.key?('assert')
2550
+ val = hash['assert']
2551
+ #val =~ /\bval\b/ or errors << validate_error(:assert_noval, rule, "#{path}/assert", val)
2552
+ begin
2553
+ eval "proc { |val| #{val} }"
2554
+ rescue ::SyntaxError => ex
2555
+ errors << validate_error(:assert_syntaxerr, rule, "#{path}/assert", val)
2556
+ end
2557
+ end
2558
+ #
2559
+ if hash.key?('range')
2560
+ val = hash['range']
2561
+ curr_path = path + "/range"
2562
+ #if ! val.is_a?(Hash)
2563
+ # errors << validate_error(:range_notmap, rule, curr_path, val)
2564
+ #elsif ...
2565
+ if Types.collection_type?(type) || type == 'bool' || type == 'any'
2566
+ errors << validate_error(:range_notscalar, rule, path, 'range:')
2567
+ else
2568
+ val.each do |rkey, rval|
2569
+ #case rkey
2570
+ #when 'max', 'min', 'max-ex', 'min-ex'
2571
+ unless rval.is_a?(klass)
2572
+ typename = Kwalify.word(type) || type
2573
+ errors << validate_error(:range_type_unmatch, rule, "#{curr_path}/#{rkey}", rval, [typename])
2574
+ end
2575
+ #else
2576
+ # errors << validate_error(:range_undefined, rule, curr_path, "#{rkey}:")
2577
+ #end
2578
+ end
2579
+ end
2580
+ if val.key?('max') && val.key?('max-ex')
2581
+ errors << validate_error(:range_twomax, rule, curr_path, nil)
2582
+ end
2583
+ if val.key?('min') && val.key?('min-ex')
2584
+ errors << validate_error(:range_twomin, rule, curr_path, nil)
2585
+ end
2586
+ max, min, max_ex, min_ex = val['max'], val['min'], val['max-ex'], val['min-ex']
2587
+ if max
2588
+ if min && max < min
2589
+ errors << validate_error(:range_maxltmin, rule, curr_path, nil, [max, min])
2590
+ elsif min_ex && max <= min_ex
2591
+ errors << validate_error(:range_maxleminex, rule, curr_path, nil, [max, min_ex])
2592
+ end
2593
+ elsif max_ex
2594
+ if min && max_ex <= min
2595
+ errors << validate_error(:range_maxexlemin, rule, curr_path, nil, [max_ex, min])
2596
+ elsif min_ex && max_ex <= min_ex
2597
+ errors << validate_error(:range_maxexleminex, rule, curr_path, nil, [max_ex, min_ex])
2598
+ end
2599
+ end
2600
+ end
2601
+ #
2602
+ if hash.key?('length')
2603
+ val = hash['length']
2604
+ curr_path = path + "/length"
2605
+ #val.is_a?(Hash) or errors << validate_error(:length_notmap, rule, curr_path, val)
2606
+ unless type == 'str' || type == 'text'
2607
+ errors << validate_error(:length_nottext, rule, path, 'length:')
2608
+ end
2609
+ #val.each do |lkey, lval|
2610
+ # #case lkey
2611
+ # #when 'max', 'min', 'max-ex', 'min-ex'
2612
+ # unless lval.is_a?(Integer)
2613
+ # errors << validate_error(:length_notint, rule, "#{curr_path}/#{lkey}", lval)
2614
+ # end
2615
+ # #else
2616
+ # # errors << validate_error(:length_undefined, rule, curr_path, "#{lkey}:")
2617
+ # #end
2618
+ #end
2619
+ if val.key?('max') && val.key?('max-ex')
2620
+ errors << validate_error(:length_twomax, rule, curr_path, nil)
2621
+ end
2622
+ if val.key?('min') && val.key?('min-ex')
2623
+ errors << validate_error(:length_twomin, rule, curr_path, nil)
2624
+ end
2625
+ max, min, max_ex, min_ex = val['max'], val['min'], val['max-ex'], val['min-ex']
2626
+ if max
2627
+ if min && max < min
2628
+ errors << validate_error(:length_maxltmin, rule, curr_path, nil, [max, min])
2629
+ elsif min_ex && max <= min_ex
2630
+ errors << validate_error(:length_maxleminex, rule, curr_path, nil, [max, min_ex])
2631
+ end
2632
+ elsif max_ex
2633
+ if min && max_ex <= min
2634
+ errors << validate_error(:length_maxexlemin, rule, curr_path, nil, [max_ex, min])
2635
+ elsif min_ex && max_ex <= min_ex
2636
+ errors << validate_error(:length_maxexleminex, rule, curr_path, nil, [max_ex, min_ex])
2637
+ end
2638
+ end
2639
+ end
2640
+ #
2641
+ if hash.key?('unique')
2642
+ if hash['unique'] && Types.collection_type?(type)
2643
+ errors << validate_error(:unique_notscalar, rule, path, "unique:")
2644
+ end
2645
+ if path.empty?
2646
+ errors << validate_error(:unique_onroot, rule, "/", "unique:")
2647
+ end
2648
+ end
2649
+ #
2650
+ if hash.key?('ident')
2651
+ if hash['ident'] && Types.collection_type?(type)
2652
+ errors << validate_error(:ident_notscalar, rule, path, "ident:")
2653
+ end
2654
+ if path.empty?
2655
+ errors << validate_error(:ident_onroot, rule, "/", "ident:")
2656
+ end
2657
+ end
2658
+ #
2659
+ if hash.key?('default')
2660
+ val = hash['default']
2661
+ if Types.collection_type?(type)
2662
+ errors << validate_error(:default_notscalar, rule, path, "default:")
2663
+ elsif !val.nil? && !val.is_a?(klass)
2664
+ errors << validate_error(:default_unmatch, rule, "#{path}/default", val, [Kwalify.word(type)])
2665
+ end
2666
+ end
2667
+ #
2668
+ if hash.key?('sequence')
2669
+ val = hash['sequence']
2670
+ #if !val.nil? && !val.is_a?(Array)
2671
+ # errors << validate_error(:sequence_notseq, rule, "#{path}/sequence", val)
2672
+ #elsif ...
2673
+ if val.nil? || val.empty?
2674
+ errors << validate_error(:sequence_noelem, rule, "#{path}/sequence", val)
2675
+ elsif val.length > 1
2676
+ errors << validate_error(:sequence_toomany, rule, "#{path}/sequence", val)
2677
+ else
2678
+ elem = val[0]
2679
+ assert_error("elem.class=#{elem.class}") unless elem.is_a?(Hash)
2680
+ if elem['ident'] && elem['type'] != 'map'
2681
+ errors << validate_error(:ident_notmap, nil, "#{path}/sequence/0", 'ident:')
2682
+ end
2683
+ end
2684
+ end
2685
+ #
2686
+ if hash.key?('mapping')
2687
+ val = hash['mapping']
2688
+ if !val.nil? && !val.is_a?(Hash)
2689
+ errors << validate_error(:mapping_notmap, rule, "#{path}/mapping", val)
2690
+ elsif val.nil? || (val.empty? && !val.default)
2691
+ errors << validate_error(:mapping_noelem, rule, "#{path}/mapping", val)
2692
+ end
2693
+ end
2694
+ #
2695
+ if type == 'seq'
2696
+ errors << validate_error(:seq_nosequence, rule, path, nil) unless hash.key?('sequence')
2697
+ #errors << validate_error(:seq_conflict, rule, path, 'enum:') if hash.key?('enum')
2698
+ errors << validate_error(:seq_conflict, rule, path, 'pattern:') if hash.key?('pattern')
2699
+ errors << validate_error(:seq_conflict, rule, path, 'mapping:') if hash.key?('mapping')
2700
+ #errors << validate_error(:seq_conflict, rule, path, 'range:') if hash.key?('range')
2701
+ #errors << validate_error(:seq_conflict, rule, path, 'length:') if hash.key?('length')
2702
+ elsif type == 'map'
2703
+ errors << validate_error(:map_nomapping, rule, path, nil) unless hash.key?('mapping')
2704
+ #errors << validate_error(:map_conflict, rule, path, 'enum:') if hash.key?('enum')
2705
+ errors << validate_error(:map_conflict, rule, path, 'pattern:') if hash.key?('pattern')
2706
+ errors << validate_error(:map_conflict, rule, path, 'sequence:') if hash.key?('sequence')
2707
+ #errors << validate_error(:map_conflict, rule, path, 'range:') if hash.key?('range')
2708
+ #errors << validate_error(:map_conflict, rule, path, 'length:') if hash.key?('length')
2709
+ else
2710
+ errors << validate_error(:scalar_conflict, rule, path, 'sequence:') if hash.key?('sequence')
2711
+ errors << validate_error(:scalar_conflict, rule, path, 'mapping:') if hash.key?('mapping')
2712
+ if hash.key?('enum')
2713
+ errors << validate_error(:enum_conflict, rule, path, 'range:') if hash.key?('range')
2714
+ errors << validate_error(:enum_conflict, rule, path, 'length:') if hash.key?('length')
2715
+ errors << validate_error(:enum_conflict, rule, path, 'pattern:') if hash.key?('pattern')
2716
+ end
2717
+ if hash.key?('default')
2718
+ errors << validate_error(:default_conflict, rule, path, 'default:') if hash['required']
2719
+ end
2720
+ end
2721
+
2722
+ end # end of def validate_hook()
2723
+
2724
+
2725
+ end # end of class MetaValidator
2726
+
2727
+
2728
+ META_VALIDATOR = MetaValidator.instance()
2729
+
2730
+ def self.meta_validator # obsolete
2731
+ return META_VALIDATOR
2732
+ end
2733
+
2734
+ end
2735
+ #--end of require 'kwalify/meta-validator'
2736
+ #--begin of require 'kwalify/yaml-parser'
2737
+ ###
2738
+ ### $Rev$
2739
+ ### $Release: 0.7.2 $
2740
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
2741
+ ###
2742
+
2743
+ #--already included require 'kwalify/messages'
2744
+ #--already included require 'kwalify/errors'
2745
+ #--already included require 'kwalify/types'
2746
+
2747
+ require 'date'
2748
+
2749
+
2750
+ module Kwalify
2751
+
2752
+ ##
2753
+ ## base class of yaml parser
2754
+ ##
2755
+ ## ex.
2756
+ ## str = ARGF.read()
2757
+ ## parser = Kwalify::PlainYamlParser.new(str)
2758
+ ## doc = parser.parse()
2759
+ ## p doc
2760
+ ##
2761
+ class PlainYamlParser
2762
+
2763
+ class Alias
2764
+ def initialize(label, linenum)
2765
+ @label = label
2766
+ @linenum = linenum
2767
+ end
2768
+ attr_reader :label, :linenum
2769
+ end
2770
+
2771
+
2772
+ def initialize(yaml_str)
2773
+ @lines = yaml_str.to_a()
2774
+ @line = nil
2775
+ @linenum = 0
2776
+ @anchors = {}
2777
+ @aliases = {}
2778
+ end
2779
+
2780
+
2781
+ def parse()
2782
+ data = parse_child(0)
2783
+ if data.nil? && @end_flag == '---'
2784
+ data = parse_child(0)
2785
+ end
2786
+ resolve_aliases(data) unless @aliases.empty?
2787
+ return data
2788
+ end
2789
+
2790
+
2791
+ def has_next?
2792
+ return @end_flag != 'EOF'
2793
+ end
2794
+
2795
+
2796
+ def parse_all
2797
+ list = []
2798
+ while has_next()
2799
+ doc = parse()
2800
+ list << doc
2801
+ end
2802
+ return list
2803
+ end
2804
+
2805
+
2806
+ protected
2807
+
2808
+
2809
+ def create_sequence(linenum=nil)
2810
+ return []
2811
+ end
2812
+
2813
+ def add_to_seq(seq, value, linenum)
2814
+ seq << value
2815
+ end
2816
+
2817
+ def set_seq_at(seq, i, value, linenum)
2818
+ seq[i] = value
2819
+ end
2820
+
2821
+ def create_mapping(linenum=nil)
2822
+ return {}
2823
+ end
2824
+
2825
+ def add_to_map(map, key, value, linenum)
2826
+ map[key] = value
2827
+ end
2828
+
2829
+ def set_map_with(map, key, value, linenum)
2830
+ map[key] = value
2831
+ end
2832
+
2833
+ def set_default(map, value, linenum)
2834
+ map.value = value
2835
+ end
2836
+
2837
+ def merge_map(map, map2, linenum)
2838
+ map2.each do |key, val|
2839
+ map[key] = value unless map.key?(key)
2840
+ end
2841
+ end
2842
+
2843
+ def create_scalar(value, linenum=nil)
2844
+ return value
2845
+ end
2846
+
2847
+
2848
+ def current_line
2849
+ return @line
2850
+ end
2851
+
2852
+ def current_linenum
2853
+ return @linenum
2854
+ end
2855
+
2856
+
2857
+ private
2858
+
2859
+
2860
+ def getline
2861
+ line = _getline()
2862
+ line = _getline() while line && line =~ /^\s*($|\#)/
2863
+ return line
2864
+ end
2865
+
2866
+ def _getline
2867
+ @line = @lines[@linenum]
2868
+ @linenum += 1
2869
+ case @line
2870
+ when nil ; @end_flag = 'EOF'
2871
+ when /^\.\.\.$/ ; @end_flag = '...'; @line = nil
2872
+ when /^---(\s+.*)?$/ ; @end_flag = '---'; @line = nil
2873
+ else ; @end_flag = nil
2874
+ end
2875
+ return @line
2876
+ end
2877
+
2878
+
2879
+ def reset_sbuf(str)
2880
+ @sbuf = str[-1] == ?\n ? str : str + "\n"
2881
+ @index = -1
2882
+ end
2883
+
2884
+
2885
+ def _getchar
2886
+ @index += 1
2887
+ ch = @sbuf[@index]
2888
+ while ch.nil?
2889
+ break if (line = getline()).nil?
2890
+ reset_sbuf(line)
2891
+ @index += 1
2892
+ ch = @sbuf[@index]
2893
+ end
2894
+ return ch
2895
+ end
2896
+
2897
+ def getchar
2898
+ ch = _getchar()
2899
+ ch = _getchar() while ch && white?(ch)
2900
+ return ch
2901
+ end
2902
+
2903
+ def getchar_or_nl
2904
+ ch = _getchar()
2905
+ ch = _getchar() while ch && white?(ch) && ch != ?\n
2906
+ return ch
2907
+ end
2908
+
2909
+ def current_char
2910
+ return @sbuf[@index]
2911
+ end
2912
+
2913
+ def getlabel
2914
+ if @sbuf[@index..-1] =~ /\A\w[-\w]*/
2915
+ label = $&
2916
+ @index += label.length
2917
+ else
2918
+ label = nil
2919
+ end
2920
+ return label
2921
+ end
2922
+
2923
+ #--
2924
+ #def syntax_error(error_symbol, linenum=@linenum)
2925
+ # msg = Kwalify.msg(error_symbol) % [linenum]
2926
+ # return Kwalify::YamlSyntaxError.new(msg, linenum,error_symbol)
2927
+ #end
2928
+ #++
2929
+ def syntax_error(error_symbol, arg=nil, linenum=@linenum)
2930
+ msg = Kwalify.msg(error_symbol)
2931
+ msg = msg % arg.to_a unless arg.nil?
2932
+ return Kwalify::YamlSyntaxError.new(msg, linenum, error_symbol)
2933
+ end
2934
+
2935
+ def parse_child(column)
2936
+ line = getline()
2937
+ return create_scalar(nil) if !line
2938
+ line =~ /^( *)(.*)/
2939
+ indent = $1.length
2940
+ return create_scalar(nil) if indent < column
2941
+ value = $2
2942
+ return parse_value(column, value, indent)
2943
+ end
2944
+
2945
+
2946
+ def parse_value(column, value, value_start_column)
2947
+ case value
2948
+ when /^-( |$)/
2949
+ data = parse_sequence(value_start_column, value)
2950
+ when /^(:?:?[-.\w]+\*?|'.*?'|".*?"|=|<<) *:( |$)/
2951
+ #when /^:?["']?[-.\w]+["']? *:( |$)/ #'
2952
+ data = parse_mapping(value_start_column, value)
2953
+ when /^\[/, /^\{/
2954
+ data = parse_flowstyle(column, value)
2955
+ when /^\&[-\w]+( |$)/
2956
+ data = parse_anchor(column, value)
2957
+ when /^\*[-\w]+( |$)/
2958
+ data = parse_alias(column, value)
2959
+ when /^[|>]/
2960
+ data = parse_block_text(column, value)
2961
+ when /^!/
2962
+ data = parse_tag(column, value)
2963
+ when /^\#/
2964
+ data = parse_child(column)
2965
+ else
2966
+ data = parse_scalar(column, value)
2967
+ end
2968
+ return data
2969
+ end
2970
+
2971
+ def white?(ch)
2972
+ return ch == ?\ || ch == ?\t || ch == ?\n || ch == ?\r
2973
+ end
2974
+
2975
+
2976
+ ##
2977
+ ## flowstyle ::= flow_seq | flow_map | flow_scalar | alias
2978
+ ##
2979
+ ## flow_seq ::= '[' [ flow_seq_item { ',' sp flow_seq_item } ] ']'
2980
+ ## flow_seq_item ::= flowstyle
2981
+ ##
2982
+ ## flow_map ::= '{' [ flow_map_item { ',' sp flow_map_item } ] '}'
2983
+ ## flow_map_item ::= flowstyle ':' sp flowstyle
2984
+ ##
2985
+ ## flow_scalar ::= string | number | boolean | symbol | date
2986
+ ##
2987
+
2988
+ def parse_flowstyle(column, value)
2989
+ reset_sbuf(value)
2990
+ getchar()
2991
+ data = parse_flow(0)
2992
+ ch = current_char
2993
+ assert ch == ?] || ch == ?}
2994
+ ch = getchar_or_nl()
2995
+ unless ch == ?\n || ch == ?# || ch.nil?
2996
+ #* key=:flow_hastail msg="flow style sequence is closed but got '%s'."
2997
+ raise syntax_error(:flow_hastail, [ch.chr])
2998
+ end
2999
+ getline() if !ch.nil?
3000
+ return data
3001
+ end
3002
+
3003
+ def parse_flow(depth)
3004
+ ch = current_char()
3005
+ #ch = getchar()
3006
+ if ch.nil?
3007
+ #* key=:flow_eof msg="found EOF when parsing flow style."
3008
+ rase syntax_error(:flow_eof)
3009
+ end
3010
+ ## alias
3011
+ if ch == ?*
3012
+ _getchar()
3013
+ label = getlabel()
3014
+ unless label
3015
+ #* key=:flow_alias_label msg="alias name expected."
3016
+ rase syntax_error(:flow_alias_label)
3017
+ end
3018
+ data = @anchors[label]
3019
+ unless data
3020
+ data = register_alias(label)
3021
+ #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
3022
+ end
3023
+ return data
3024
+ end
3025
+ ## anchor
3026
+ label = nil
3027
+ if ch == ?&
3028
+ _getchar()
3029
+ label = getlabel()
3030
+ unless label
3031
+ #* key=:flow_anchor_label msg="anchor name expected."
3032
+ rase syntax_error(:flow_anchor_label)
3033
+ end
3034
+ ch = current_char()
3035
+ ch = getchar() if white?(ch)
3036
+ end
3037
+ ## flow data
3038
+ if ch == ?[
3039
+ data = parse_flow_seq(depth)
3040
+ elsif ch == ?{
3041
+ data = parse_flow_map(depth)
3042
+ else
3043
+ data = parse_flow_scalar(depth)
3044
+ end
3045
+ ## register anchor
3046
+ register_anchor(label, data) if label
3047
+ return data
3048
+ end
3049
+
3050
+ def parse_flow_seq(depth)
3051
+ assert current_char() == ?[
3052
+ seq = create_sequence() # []
3053
+ ch = getchar()
3054
+ if ch != ?}
3055
+ linenum = current_linenum()
3056
+ #seq << parse_flow_seq_item(depth + 1)
3057
+ add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
3058
+ while (ch = current_char()) == ?,
3059
+ ch = getchar()
3060
+ if ch == ?]
3061
+ #* key=:flow_noseqitem msg="sequence item required (or last comma is extra)."
3062
+ raise syntax_error(:flow_noseqitem)
3063
+ end
3064
+ #break if ch == ?]
3065
+ linenum = current_linenum()
3066
+ #seq << parse_flow_seq_item(depth + 1)
3067
+ add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
3068
+ end
3069
+ end
3070
+ unless current_char() == ?]
3071
+ #* key=:flow_seqnotclosed msg="flow style sequence requires ']'."
3072
+ raise syntax_error(:flow_seqnotclosed)
3073
+ end
3074
+ getchar() if depth > 0
3075
+ return seq
3076
+ end
3077
+
3078
+ def parse_flow_seq_item(depth)
3079
+ return parse_flow(depth)
3080
+ end
3081
+
3082
+ def parse_flow_map(depth)
3083
+ assert current_char() == ?{ #}
3084
+ map = create_mapping() # {}
3085
+ ch = getchar()
3086
+ if ch != ?}
3087
+ linenum = current_linenum()
3088
+ key, value = parse_flow_map_item(depth + 1)
3089
+ #map[key] = value
3090
+ add_to_map(map, key, value, linenum)
3091
+ while (ch = current_char()) == ?,
3092
+ ch = getchar()
3093
+ if ch == ?}
3094
+ #* key=:flow_mapnoitem msg="mapping item required (or last comma is extra)."
3095
+ raise syntax_error(:flow_mapnoitem)
3096
+ end
3097
+ #break if ch == ?}
3098
+ linenum = current_linenum()
3099
+ key, value = parse_flow_map_item(depth + 1)
3100
+ #map[key] = value
3101
+ add_to_map(map, key, value, linenum)
3102
+ end
3103
+ end
3104
+ unless current_char() == ?}
3105
+ #* key=:flow_mapnotclosed msg="flow style mapping requires '}'."
3106
+ raise syntax_error(:flow_mapnotclosed)
3107
+ end
3108
+ getchar() if depth > 0
3109
+ return map
3110
+ end
3111
+
3112
+ def parse_flow_map_item(depth)
3113
+ key = parse_flow(depth)
3114
+ unless (ch = current_char()) == ?:
3115
+ $stderr.puts "*** debug: key=#{key.inspect}"
3116
+ s = ch ? "'#{ch.chr}'" : "EOF"
3117
+ #* key=:flow_nocolon msg="':' expected but got %s."
3118
+ raise syntax_error(:flow_nocolon, [s])
3119
+ end
3120
+ getchar()
3121
+ value = parse_flow(depth)
3122
+ return key, value
3123
+ end
3124
+
3125
+ def parse_flow_scalar(depth)
3126
+ case ch = current_char()
3127
+ when ?", ?' #"
3128
+ endch = ch
3129
+ s = ''
3130
+ while (ch = _getchar()) != nil && ch != endch
3131
+ if ch == ?\\
3132
+ ch = _getchar()
3133
+ if ch.nil?
3134
+ #* key=:flow_str_notclosed msg="%s: string not closed."
3135
+ raise syntax_error(:flow_str_notclosed, endch == ?" ? "'\"'" : '"\'"')
3136
+ end
3137
+ if endch == ?"
3138
+ case ch
3139
+ when ?\\ ; s << "\\"
3140
+ when ?" ; s << "\""
3141
+ when ?n ; s << "\n"
3142
+ when ?r ; s << "\r"
3143
+ when ?t ; s << "\t"
3144
+ when ?b ; s << "\b"
3145
+ else ; s << "\\" << ch.chr
3146
+ end
3147
+ elsif endch == ?'
3148
+ case ch
3149
+ when ?\\ ; s << '\\'
3150
+ when ?' ; s << '\''
3151
+ else ; s << '\\' << ch.chr
3152
+ end
3153
+ end
3154
+ else
3155
+ s << ch.chr
3156
+ end
3157
+ end
3158
+ getchar()
3159
+ scalar = s
3160
+ else
3161
+ s = ch.chr
3162
+ while (ch = _getchar()) != nil && ch != ?: && ch != ?, && ch != ?] && ch != ?}
3163
+ s << ch.chr
3164
+ end
3165
+ scalar = to_scalar(s.strip)
3166
+ end
3167
+ return create_scalar(scalar)
3168
+ end
3169
+
3170
+
3171
+ def parse_tag(column, value)
3172
+ assert value =~ /^!\S+/
3173
+ value =~ /^!(\S+)((\s+)(.*))?$/
3174
+ tag = $1
3175
+ space = $3
3176
+ value2 = $4
3177
+ if value2 && !value2.empty?
3178
+ value_start_column = column + 1 + tag.length + space.length
3179
+ data = parse_value(column, value2, value_start_column)
3180
+ else
3181
+ data = parse_child(column)
3182
+ end
3183
+ return data
3184
+ end
3185
+
3186
+
3187
+ def parse_anchor(column, value)
3188
+ assert value =~ /^\&([-\w]+)(( *)(.*))?$/
3189
+ label = $1
3190
+ space = $3
3191
+ value2 = $4
3192
+ if value2 && !value2.empty?
3193
+ #column2 = column + 1 + label.length + space.length
3194
+ #data = parse_value(column2, value2)
3195
+ value_start_column = column + 1 + label.length + space.length
3196
+ data = parse_value(column, value2, value_start_column)
3197
+ else
3198
+ #column2 = column + 1
3199
+ #data = parse_child(column2)
3200
+ data = parse_child(column)
3201
+ end
3202
+ register_anchor(label, data)
3203
+ return data
3204
+ end
3205
+
3206
+ def register_anchor(label, data)
3207
+ if @anchors[label]
3208
+ #* key=:anchor_duplicated msg="anchor '%s' is already used."
3209
+ raise syntax_error(:anchor_duplicated, [label])
3210
+ end
3211
+ @anchors[label] = data
3212
+ end
3213
+
3214
+ def parse_alias(column, value)
3215
+ assert value =~ /^\*([-\w]+)(( *)(.*))?$/
3216
+ label = $1
3217
+ space = $3
3218
+ value2 = $4
3219
+ if value2 && !value2.empty? && value2[0] != ?\#
3220
+ #* key=:alias_extradata msg="alias cannot take any data."
3221
+ raise syntax_error(:alias_extradata)
3222
+ end
3223
+ data = @anchors[label]
3224
+ unless data
3225
+ data = register_alias(label)
3226
+ #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
3227
+ end
3228
+ getline()
3229
+ return data
3230
+ end
3231
+
3232
+ def register_alias(label)
3233
+ @aliases[label] ||= 0
3234
+ @aliases[label] += 1
3235
+ return Alias.new(label, @linenum)
3236
+ end
3237
+
3238
+
3239
+ def resolve_aliases(data)
3240
+ @resolved ||= {}
3241
+ return if @resolved[data.__id__]
3242
+ @resolved[data.__id__] = data
3243
+ case data
3244
+ when Array
3245
+ seq = data
3246
+ seq.each_with_index do |val, i|
3247
+ if val.is_a?(Alias)
3248
+ anchor = val
3249
+ if @anchors.key?(anchor.label)
3250
+ #seq[i] = @anchors[anchor.label]
3251
+ set_seq_at(seq, i, @anchors[anchor.label], anchor.linenum)
3252
+ else
3253
+ #* key=:anchor_notfound msg="anchor '%s' not found"
3254
+ raise syntax_error(:anchor_notfound, [anchor.label], val.linenum)
3255
+ end
3256
+ elsif val.is_a?(Array) || val.is_a?(Hash)
3257
+ resolve_aliases(val)
3258
+ end
3259
+ end
3260
+ when Hash
3261
+ map = data
3262
+ map.each do |key, val|
3263
+ if val.is_a?(Alias)
3264
+ if @anchors.key?(val.label)
3265
+ anchor = val
3266
+ #map[key] = @anchors[anchor.label]
3267
+ set_map_with(map, key, @anchors[anchor.label], anchor.linenum)
3268
+ else
3269
+ ## :anchor_notfound is already defined on above
3270
+ raise syntax_error(:anchor_notfound, [val.label], val.linenum)
3271
+ end
3272
+ elsif val.is_a?(Array) || val.is_a?(Hash)
3273
+ resolve_aliases(val)
3274
+ end
3275
+ end
3276
+ else
3277
+ assert !data.is_a?(Alias)
3278
+ end
3279
+ end
3280
+
3281
+
3282
+ def parse_block_text(column, value)
3283
+ assert value =~ /^[>|\|]/
3284
+ value =~ /^([>|\|])([-+]?)(\d+)?\s*(.*)$/
3285
+ char = $1
3286
+ indicator = $2
3287
+ sep = char == "|" ? "\n" : " "
3288
+ margin = $3 && !$3.empty? ? $3.to_i : nil
3289
+ #text = $4.empty? ? '' : $4 + sep
3290
+ text = $4
3291
+ s = ''
3292
+ empty = ''
3293
+ min_indent = -1
3294
+ while line = _getline()
3295
+ line =~ /^( *)(.*)/
3296
+ indent = $1.length
3297
+ if $2.empty?
3298
+ empty << "\n"
3299
+ elsif indent < column
3300
+ break
3301
+ else
3302
+ min_indent = indent if min_indent < 0 || min_indent > indent
3303
+ s << empty << line
3304
+ empty = ''
3305
+ end
3306
+ end
3307
+ s << empty if indicator == '+' && char != '>'
3308
+ s[-1] = "" if indicator == '-'
3309
+ min_indent = column + margin - 1 if margin
3310
+ if min_indent > 0
3311
+ sp = ' ' * min_indent
3312
+ s.gsub!(/^#{sp}/, '')
3313
+ end
3314
+ if char == '>'
3315
+ s.gsub!(/([^\n])\n([^\n])/, '\1 \2')
3316
+ s.gsub!(/\n(\n+)/, '\1')
3317
+ s << empty if indicator == '+'
3318
+ end
3319
+ getline() if current_line() =~ /^\s*\#/
3320
+ return create_scalar(text + s)
3321
+ end
3322
+
3323
+
3324
+ def parse_sequence(column, value)
3325
+ assert value =~ /^-(( +)(.*))?$/
3326
+ seq = create_sequence() # []
3327
+ while true
3328
+ unless value =~ /^-(( +)(.*))?$/
3329
+ #* key=:sequence_noitem msg="sequence item is expected."
3330
+ raise syntax_error(:sequence_noitem)
3331
+ end
3332
+ value2 = $3
3333
+ space = $2
3334
+ column2 = column + 1
3335
+ linenum = current_linenum()
3336
+ #
3337
+ if !value2 || value2.empty?
3338
+ elem = parse_child(column2)
3339
+ else
3340
+ value_start_column = column2 + space.length
3341
+ elem = parse_value(column2, value2, value_start_column)
3342
+ end
3343
+ add_to_seq(seq, elem, linenum) #seq << elem
3344
+ #
3345
+ line = current_line()
3346
+ break unless line
3347
+ line =~ /^( *)(.*)/
3348
+ indent = $1.length
3349
+ if indent < column
3350
+ break
3351
+ elsif indent > column
3352
+ #* key=:sequence_badindent msg="illegal indent of sequence."
3353
+ raise syntax_error(:sequence_badindent)
3354
+ end
3355
+ value = $2
3356
+ end
3357
+ return seq
3358
+ end
3359
+
3360
+
3361
+ def parse_mapping(column, value)
3362
+ #assert value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/ #'
3363
+ assert value =~ /^((?::?[-.\w]+\*?|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
3364
+ map = create_mapping() # {}
3365
+ while true
3366
+ #unless value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/ #'
3367
+ unless value =~ /^((?::?[-.\w]+\*?|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
3368
+ #* key=:mapping_noitem msg="mapping item is expected."
3369
+ raise syntax_error(:mapping_noitem)
3370
+ end
3371
+ v = $1.strip
3372
+ key = to_scalar(v)
3373
+ value2 = $4
3374
+ column2 = column + 1
3375
+ linenum = current_linenum()
3376
+ #
3377
+ if !value2 || value2.empty?
3378
+ elem = parse_child(column2)
3379
+ else
3380
+ value_start_column = column2 + $1.length + $3.length
3381
+ elem = parse_value(column2, value2, value_start_column)
3382
+ end
3383
+ case v
3384
+ when '='
3385
+ set_default(map, elem, linenum)
3386
+ when '<<'
3387
+ merge_map(map, elem, linenum)
3388
+ else
3389
+ add_to_map(map, key, elem, linenum) # map[key] = elem
3390
+ end
3391
+ #
3392
+ line = current_line()
3393
+ break unless line
3394
+ line =~ /^( *)(.*)/
3395
+ indent = $1.length
3396
+ if indent < column
3397
+ break
3398
+ elsif indent > column
3399
+ #* key=:mapping_badindent msg="illegal indent of mapping."
3400
+ raise syntax_error(:mapping_badindent)
3401
+ end
3402
+ value = $2
3403
+ end
3404
+ return map
3405
+ end
3406
+
3407
+
3408
+ def parse_scalar(indent, value)
3409
+ data = create_scalar(to_scalar(value))
3410
+ getline()
3411
+ return data
3412
+ end
3413
+
3414
+
3415
+ def to_scalar(str)
3416
+ case str
3417
+ when /^"(.*)"([ \t]*\#.*$)?/ ; return $1
3418
+ when /^'(.*)'([ \t]*\#.*$)?/ ; return $1
3419
+ when /^(.*\S)[ \t]*\#/ ; str = $1
3420
+ end
3421
+
3422
+ case str
3423
+ when /^-?\d+$/ ; return str.to_i # integer
3424
+ when /^-?\d+\.\d+$/ ; return str.to_f # float
3425
+ when "true", "yes", "on" ; return true # true
3426
+ when "false", "no", "off" ; return false # false
3427
+ when "null", "~" ; return nil # nil
3428
+ #when /^"(.*)"$/ ; return $1 # "string"
3429
+ #when /^'(.*)'$/ ; return $1 # 'string'
3430
+ when /^:(\w+)$/ ; return $1.intern # :symbol
3431
+ when /^(\d\d\d\d)-(\d\d)-(\d\d)$/ # date
3432
+ year, month, day = $1.to_i, $2.to_i, $3.to_i
3433
+ return Date.new(year, month, day)
3434
+ when /^(\d\d\d\d)-(\d\d)-(\d\d)(?:[Tt]|[ \t]+)(\d\d?):(\d\d):(\d\d)(\.\d*)?(?:Z|[ \t]*([-+]\d\d?)(?::(\d\d))?)?$/
3435
+ year, mon, mday, hour, min, sec, usec, tzone_h, tzone_m = $1, $2, $3, $4, $5, $6, $7, $8, $9
3436
+ #Time.utc(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
3437
+ #t = Time.utc(sec, min, hour, mday, mon, year, nil, nil, nil, nil)
3438
+ #Time.utc(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
3439
+ time = Time.utc(year, mon, day, hour, min, sec, usec)
3440
+ if tzone_h
3441
+ diff_sec = tzone_h.to_i * 60 * 60
3442
+ if tzone_m
3443
+ if diff_sec > 0 ; diff_sec += tzone_m.to_i * 60
3444
+ else ; diff_sec -= tzone_m.to_i * 60
3445
+ end
3446
+ end
3447
+ p diff_sec
3448
+ time -= diff_sec
3449
+ end
3450
+ return time
3451
+ end
3452
+ return str
3453
+ end
3454
+
3455
+
3456
+ def assert(bool_expr)
3457
+ raise "*** assertion error" unless bool_expr
3458
+ end
3459
+
3460
+ end
3461
+
3462
+
3463
+
3464
+ ##
3465
+ ## (OBSOLETE) yaml parser
3466
+ ##
3467
+ ## this class has been obsoleted. use Kwalify::Yaml::Parser instead.
3468
+ ##
3469
+ ## ex.
3470
+ ## # load document with YamlParser
3471
+ ## str = ARGF.read()
3472
+ ## parser = Kwalify::YamlParser.new(str)
3473
+ ## document = parser.parse()
3474
+ ##
3475
+ ## # validate document
3476
+ ## schema = YAML.load(File.read('schema.yaml'))
3477
+ ## validator = Kwalify::Validator.new(schema)
3478
+ ## errors = validator.validate(document)
3479
+ ##
3480
+ ## # print validation result
3481
+ ## if errors && !errors.empty?
3482
+ ## parser.set_errors_linenum(errors)
3483
+ ## errors.sort.each do |error|
3484
+ ## print "line %d: path %s: %s" % [error.linenum, error.path, error.message]
3485
+ ## end
3486
+ ## end
3487
+ ##
3488
+ class YamlParser < PlainYamlParser
3489
+
3490
+ def initialize(*args)
3491
+ super
3492
+ @linenums_table = {} # object_id -> hash or array
3493
+ end
3494
+
3495
+ def parse()
3496
+ @doc = super()
3497
+ return @doc
3498
+ end
3499
+
3500
+ def path_linenum(path)
3501
+ return 1 if path.empty? || path == '/'
3502
+ elems = path.split('/')
3503
+ elems.shift if path[0] == ?/ # delete empty string on head
3504
+ last_elem = elems.pop
3505
+ c = @doc # collection
3506
+ elems.each do |elem|
3507
+ if c.is_a?(Array)
3508
+ c = c[elem.to_i]
3509
+ elsif c.is_a?(Hash)
3510
+ c = c[elem]
3511
+ else
3512
+ assert false
3513
+ end
3514
+ end
3515
+ linenums = @linenums_table[c.__id__]
3516
+ if c.is_a?(Array)
3517
+ linenum = linenums[last_elem.to_i]
3518
+ elsif c.is_a?(Hash)
3519
+ linenum = linenums[last_elem]
3520
+ end
3521
+ return linenum
3522
+ end
3523
+
3524
+ def set_errors_linenum(errors)
3525
+ errors.each do |error|
3526
+ error.linenum = path_linenum(error.path)
3527
+ end
3528
+ end
3529
+
3530
+ def set_error_linenums(errors)
3531
+ warn "*** Kwalify::YamlParser#set_error_linenums() is obsolete. You should use set_errors_linenum() instead."
3532
+ set_errors_linenum(errors)
3533
+ end
3534
+
3535
+ protected
3536
+
3537
+ def create_sequence(linenum=current_linenum())
3538
+ seq = []
3539
+ @linenums_table[seq.__id__] = []
3540
+ return seq
3541
+ end
3542
+
3543
+ def add_to_seq(seq, value, linenum)
3544
+ seq << value
3545
+ @linenums_table[seq.__id__] << linenum
3546
+ end
3547
+
3548
+ def set_seq_at(seq, i, value, linenum)
3549
+ seq[i] = value
3550
+ @linenums_table[seq.__id__][i] = linenum
3551
+ end
3552
+
3553
+ def create_mapping(linenum=current_linenum())
3554
+ map = {}
3555
+ @linenums_table[map.__id__] = {}
3556
+ return map
3557
+ end
3558
+
3559
+ def add_to_map(map, key, value, linenum)
3560
+ map[key] = value
3561
+ @linenums_table[map.__id__][key] = linenum
3562
+ end
3563
+
3564
+ def set_map_with(map, key, value, linenum)
3565
+ map[key] = value
3566
+ @linenums_table[map.__id__][key] = linenum
3567
+ end
3568
+
3569
+ def set_default(map, value, linenum)
3570
+ map.default = value
3571
+ @linenums_table[map.__id__][:'='] = linenum
3572
+ end
3573
+
3574
+ def merge_map(map, collection, linenum)
3575
+ t = @linenums_table[map.__id__]
3576
+ list = collection.is_a?(Array) ? collection : [ collection ]
3577
+ list.each do |m|
3578
+ t2 = @linenums_table[m.__id__]
3579
+ m.each do |key, val|
3580
+ unless map.key?(key)
3581
+ map[key] = val
3582
+ t[key] = t2[key]
3583
+ end
3584
+ end
3585
+ end
3586
+ end
3587
+
3588
+ def create_scalar(value, linenum=current_linenum())
3589
+ data = super(value)
3590
+ #return Scalar.new(data, linenum)
3591
+ return data
3592
+ end
3593
+
3594
+ end
3595
+
3596
+
3597
+ ## alias of YamlParser class
3598
+ class Parser < YamlParser
3599
+ def initialize(yaml_str)
3600
+ super(yaml_str)
3601
+ #warn "*** class Kwalify::Parser is obsolete. Please use Kwalify::YamlParser instead."
3602
+ end
3603
+ end
3604
+
3605
+
3606
+ end
3607
+ #--end of require 'kwalify/yaml-parser'
3608
+ #require 'kwalify/parser/base'
3609
+ #require 'kwalify/parser/yaml'
3610
+
3611
+
3612
+ module Kwalify
3613
+
3614
+ module Util
3615
+
3616
+ autoload :HashLike, 'kwalify/util/hashlike'
3617
+
3618
+ end
3619
+
3620
+ module Yaml
3621
+
3622
+ autoload :Parser, 'kwalify/parser/yaml'
3623
+
3624
+ ## read yaml_str, parse it, and return yaml document.
3625
+ ##
3626
+ ## opts:
3627
+ ## ::validator: Kwalify::Validator object
3628
+ ## ::preceding_aliass: allow preceding alias if true
3629
+ ## ::data_binding: enable data binding if true
3630
+ ## ::untabify: expand tab chars if true
3631
+ ## ::filename: filename
3632
+ def self.load(yaml_str, opts={})
3633
+ #require 'kwalify/parser/yaml'
3634
+ parser = Kwalify::Yaml::Parser.new(opts[:validator])
3635
+ parser.preceding_alias = true if opts[:preceding_alias]
3636
+ parser.data_binding = true if opts[:data_binding]
3637
+ yaml_str = Kwalify::Util.untabify(yaml_str) if opts[:untabify]
3638
+ ydoc = parser.parse(yaml_str, :filename=>opts[:filename])
3639
+ return ydoc
3640
+ end
3641
+
3642
+ ## read file, parse it, and return yaml document.
3643
+ ## see Kwalify::Yaml::Parser.load()
3644
+ def self.load_file(filename, opts={})
3645
+ opts[:filename] = filename
3646
+ return self.load(File.read(filename), opts)
3647
+ end
3648
+
3649
+ end
3650
+
3651
+ module Json
3652
+ end
3653
+
3654
+ end
3655
+ #--end of require 'kwalify'
3656
+ #--begin of require 'kwalify/main'
3657
+ ###
3658
+ ### $Rev$
3659
+ ### $Release: 0.7.2 $
3660
+ ### copyright(c) 2005-2010 kuwata-lab all rights reserved.
3661
+ ###
3662
+
3663
+ require 'yaml'
3664
+ require 'erb'
3665
+ #--already included require 'kwalify'
3666
+ #--already included require 'kwalify/util'
3667
+ #--begin of require 'kwalify/util/ordered-hash'
3668
+ ###
3669
+ ### $Rev$
3670
+ ### 0.7.2
3671
+ ### $COPYRIGHT$
3672
+ ###
3673
+
3674
+ module Kwalify
3675
+
3676
+ module Util
3677
+
3678
+ class OrderedHash < Hash
3679
+
3680
+ def initialize(*args, &block)
3681
+ super
3682
+ @_keys = []
3683
+ end
3684
+
3685
+ alias __set__ []=
3686
+
3687
+ def put(key, val)
3688
+ @_keys << key unless self.key?(key)
3689
+ __set__(key, val)
3690
+ end
3691
+
3692
+ def add(key, val)
3693
+ @_keys.delete_at(@_keys.index(key)) if self.key?(key)
3694
+ @_keys << key
3695
+ __set__(key, val)
3696
+ end
3697
+
3698
+ alias []= put
3699
+ #alias []= add
3700
+
3701
+ def keys
3702
+ return @_keys.dup
3703
+ end
3704
+
3705
+ def values
3706
+ return @_keys.collect {|key| self[key] }
3707
+ end
3708
+
3709
+ def delete(key)
3710
+ @_keys.delete_at(@_keys.index(key)) if self.key?(key)
3711
+ super
3712
+ end
3713
+
3714
+ def each
3715
+ @_keys.each do |key|
3716
+ yield key, self[key]
3717
+ end
3718
+ end
3719
+
3720
+ end
3721
+
3722
+ end
3723
+
3724
+ end
3725
+ #--end of require 'kwalify/util/ordered-hash'
3726
+
3727
+
3728
+ module Kwalify
3729
+
3730
+
3731
+ class CommandOptionError < KwalifyError
3732
+ def initialize(message, option, error_symbol)
3733
+ super(message)
3734
+ @option = option
3735
+ @error_symbol = error_symbol
3736
+ end
3737
+ attr_reader :option, :error_symbol
3738
+ end
3739
+
3740
+
3741
+ ##
3742
+ ## ex.
3743
+ ## command = File.basename($0)
3744
+ ## begin
3745
+ ## main = Kwalify::Main.new(command)
3746
+ ## s = main.execute
3747
+ ## print s if s
3748
+ ## rescue Kwalify::CommandOptionError => ex
3749
+ ## $stderr.puts "ERROR: #{ex.message}"
3750
+ ## exit 1
3751
+ ## rescue Kwalify::KwalifyError => ex
3752
+ ## $stderr.puts "ERROR: #{ex.message}"
3753
+ ## exit 1
3754
+ ## end
3755
+ ##
3756
+ class Main
3757
+
3758
+
3759
+ def initialize(command=nil)
3760
+ @command = command || File.basename($0)
3761
+ @options = {}
3762
+ @properties = {}
3763
+ @template_path = []
3764
+ $:.each do |path|
3765
+ tpath = "#{path}/kwalify/templates"
3766
+ @template_path << tpath if test(?d, tpath)
3767
+ end
3768
+ end
3769
+
3770
+
3771
+ def debug?
3772
+ @options[:debug]
3773
+ end
3774
+
3775
+
3776
+ def _inspect()
3777
+ sb = []
3778
+ sb << "command: #{@command}\n"
3779
+ sb << "options:\n"
3780
+ @options.keys.sort {|k1,k2| k1.to_s<=>k2.to_s }.each do |key|
3781
+ sb << " - #{key}: #{@options[key]}\n"
3782
+ end
3783
+ sb << "properties:\n"
3784
+ @properties.keys.sort_by {|k| k.to_s}.each do |key|
3785
+ sb << " - #{key}: #{@properties[key]}\n"
3786
+ end
3787
+ #sb << "template_path:\n"
3788
+ #@template_path.each do |path|
3789
+ # sb << " - #{path}\n"
3790
+ #end
3791
+ return sb.join
3792
+ end
3793
+
3794
+
3795
+ def execute(argv=ARGV)
3796
+ ## parse command-line options
3797
+ filenames = _parse_argv(argv)
3798
+
3799
+ ## help or version
3800
+ if @options[:help] || @options[:version]
3801
+ action = @options[:action]
3802
+ s = ''
3803
+ s << _version() << "\n" if @options[:version]
3804
+ s << _usage() if @options[:help] && !action
3805
+ s << _describe_properties(action) if @options[:help] && action
3806
+ puts s
3807
+ return
3808
+ end
3809
+
3810
+ # validation
3811
+ if @options[:meta2]
3812
+ validate_schemafiles2(filenames)
3813
+ elsif @options[:meta]
3814
+ validate_schemafiles(filenames)
3815
+ elsif @options[:action]
3816
+ unless @options[:schema]
3817
+ #* key=:command_option_actionnoschema msg="schema filename is not specified."
3818
+ raise option_error(:command_option_actionnoschema, @options[:action])
3819
+ end
3820
+ perform_action(@options[:action], @options[:schema])
3821
+ elsif @options[:schema]
3822
+ if @options[:debug]
3823
+ inspect_schema(@options[:schema])
3824
+ else
3825
+ validate_files(filenames, @options[:schema])
3826
+ end
3827
+ else
3828
+ #* key=:command_option_noaction msg="command-line option '-f' or '-m' required."
3829
+ raise option_error(:command_option_noaction, @command)
3830
+ end
3831
+ return
3832
+ end
3833
+
3834
+
3835
+ def self.main(command, argv=ARGV)
3836
+ begin
3837
+ main = Kwalify::Main.new(command)
3838
+ s = main.execute(argv)
3839
+ print s if s
3840
+ rescue Kwalify::CommandOptionError => ex
3841
+ raise ex if main.debug?
3842
+ $stderr.puts ex.message
3843
+ exit 1
3844
+ rescue Kwalify::KwalifyError => ex
3845
+ raise ex if main.debug?
3846
+ $stderr.puts "ERROR: #{ex.to_s}"
3847
+ exit 1
3848
+ #rescue => ex
3849
+ # if main.debug?
3850
+ # raise ex
3851
+ # else
3852
+ # $stderr.puts ex.message
3853
+ # exit 1
3854
+ # end
3855
+ end
3856
+ end
3857
+
3858
+
3859
+ private
3860
+
3861
+
3862
+ def option_error(error_symbol, arg)
3863
+ msg = Kwalify.msg(error_symbol) % arg
3864
+ return CommandOptionError.new(msg, arg, error_symbol)
3865
+ end
3866
+
3867
+
3868
+ def _find_template(action)
3869
+ template_filename = action + '.eruby'
3870
+ unless test(?f, template_filename)
3871
+ pathlist = []
3872
+ pathlist.concat(@options[:tpath].split(/,/)) if @options[:tpath]
3873
+ pathlist.concat(@template_path)
3874
+ tpath = pathlist.find {|path| test(?f, "#{path}/#{template_filename}") }
3875
+ #* key=:command_option_notemplate msg="%s: invalid action (template not found).\n"
3876
+ raise option_error(:command_option_notemplate, action) unless tpath
3877
+ template_filename = "#{tpath}/#{action}.eruby"
3878
+ end
3879
+ return template_filename
3880
+ end
3881
+
3882
+
3883
+ def apply_template(template_filename, hash)
3884
+ template = File.read(template_filename)
3885
+ trim_mode = 1
3886
+ erb = ERB.new(template, nil, trim_mode)
3887
+ context = Object.new
3888
+ hash.each do |key, val|
3889
+ context.instance_variable_set("@#{key}", val)
3890
+ end
3891
+ s = context.instance_eval(erb.src, template_filename)
3892
+ return s
3893
+ end
3894
+
3895
+
3896
+ def _describe_properties(action)
3897
+ template_filename = _find_template(action)
3898
+ s = apply_template(template_filename, :describe=>true)
3899
+ return s
3900
+ end
3901
+
3902
+
3903
+ def perform_action(action, schema_filename, describe=false)
3904
+ template_filename = _find_template(action)
3905
+ schema = _load_schemafile(schema_filename, ordered=true)
3906
+ validator = Kwalify::Validator.new(schema)
3907
+ @properties[:schema_filename] = schema_filename
3908
+ hash = { :validator=>validator, :schema=>schema, :properties=>@properties }
3909
+ s = apply_template(template_filename, hash)
3910
+ puts s if s && !s.empty?
3911
+ end
3912
+
3913
+
3914
+ def inspect_schema(schema_filename)
3915
+ schema = _load_schemafile(schema_filename)
3916
+ if schema.nil?
3917
+ puts "nil"
3918
+ else
3919
+ validator = Kwalify::Validator.new(schema) # error raised when schema is wrong
3920
+ puts validator._inspect()
3921
+ end
3922
+ end
3923
+
3924
+
3925
+ ## -f schemafile datafile
3926
+ def validate_files(filenames, schema_filename)
3927
+ schema = _load_schemafile(schema_filename)
3928
+ validator = Kwalify::Validator.new(schema)
3929
+ _validate_files(validator, filenames)
3930
+ end
3931
+
3932
+
3933
+ def _load_schemafile(schema_filename, ordered=false)
3934
+ str = File.read(schema_filename)
3935
+ if str.empty?
3936
+ #* key=:schema_empty msg="%s: empty schema.\n"
3937
+ msg = Kwalify.msg(:schema_emtpy) % filename
3938
+ raise CommandOptionError.new(msg)
3939
+ end
3940
+ str = Util.untabify(str) if @options[:untabify]
3941
+ parser = Kwalify::Yaml::Parser.new()
3942
+ parser.preceding_alias = true if @options[:preceding]
3943
+ parser.mapping_class = Kwalify::Util::OrderedHash if ordered
3944
+ schema = parser.parse(str, :filename=>schema_filename) # or YAML.load(str)
3945
+ return schema
3946
+ end
3947
+
3948
+
3949
+ ## -m schemafile
3950
+ def validate_schemafiles(schema_filenames)
3951
+ meta_validator = Kwalify::MetaValidator.instance()
3952
+ _validate_files(meta_validator, schema_filenames)
3953
+ end
3954
+
3955
+
3956
+ ## -M schemafile
3957
+ def validate_schemafiles2(schema_filenames)
3958
+ parser = Kwalify::Yaml::Parser.new()
3959
+ parser.preceding_alias = true if @options[:preceding]
3960
+ for schema_filename in schema_filenames
3961
+ str = File.read(schema_filename)
3962
+ str = Util.untabify(str) if @options[:untabify]
3963
+ schema = parser.parse(str, :filename=>schema_filename)
3964
+ Kwalify::Validator.new(schema) # exception raised when schema has errors
3965
+ end
3966
+ end
3967
+
3968
+
3969
+ def _validate_files(validator, filenames)
3970
+ ## parser
3971
+ if @options[:linenum] || @options[:preceding]
3972
+ parser = Kwalify::Yaml::Parser.new(validator)
3973
+ parser.preceding_alias = true if @options[:preceding]
3974
+ else
3975
+ parser = nil
3976
+ end
3977
+ ## filenames
3978
+ if filenames.empty?
3979
+ filenames = [ $stdin ]
3980
+ end
3981
+ for filename in filenames
3982
+ ## read input
3983
+ if filename.is_a?(IO)
3984
+ input = filename.read()
3985
+ filename = '(stdin)'
3986
+ else
3987
+ input = File.read(filename)
3988
+ end
3989
+ if input.empty?
3990
+ #* key=:validation_empty msg="%s#%d: empty."
3991
+ puts kwalify.msg(:validation_empty) % [filename, i]
3992
+ #puts "#{filename}##{i}: empty."
3993
+ next
3994
+ end
3995
+ input = Util.untabify(input) if @options[:untabify]
3996
+ ## parse input
3997
+ if parser
3998
+ #i = 0
3999
+ #ydoc = parser.parse(input, :filename=>filename)
4000
+ #_show_errors(filename, i, ydoc, parser.errors)
4001
+ #while parser.has_next?
4002
+ # i += 1
4003
+ # ydoc = parser.parse_next()
4004
+ # _show_errors(filename, i, ydoc, parser.errors)
4005
+ #end
4006
+ i = 0
4007
+ parser.parse_stream(input, :filename=>filename) do |ydoc|
4008
+ _show_errors(filename, i, ydoc, parser.errors)
4009
+ i += 1
4010
+ end
4011
+ else
4012
+ i = 0
4013
+ YAML.load_documents(input) do |ydoc|
4014
+ errors = validator.validate(ydoc)
4015
+ _show_errors(filename, i, ydoc, errors)
4016
+ i += 1
4017
+ end
4018
+ end
4019
+ end
4020
+ end
4021
+
4022
+
4023
+ def _show_errors(filename, i, ydoc, errors, ok_label="valid.", ng_label="INVALID")
4024
+ if errors && !errors.empty?
4025
+ puts "#{filename}##{i}: #{ng_label}"
4026
+ errors.sort!
4027
+ for error in errors
4028
+ e = error
4029
+ if @options[:emacs]
4030
+ raise unless @options[:linenum]
4031
+ puts "#{filename}:#{e.linenum}:#{e.column} [#{e.path}] #{e.message}\n"
4032
+ elsif @options[:linenum]
4033
+ puts " - (line #{e.linenum}) [#{e.path}] #{e.message}\n"
4034
+ else
4035
+ puts " - [#{e.path}] #{e.message}\n"
4036
+ end
4037
+ end
4038
+ elsif ydoc.nil?
4039
+ #* key=:validation_empty msg="%s#%d: empty.\n"
4040
+ puts Kwalify.msg(:validation_empty) % [filename, i]
4041
+ else
4042
+ #* key=:validation_valid msg="%s#%d: valid."
4043
+ puts Kwalify.msg(:validation_valid) % [filename, i] unless @options[:quiet]
4044
+ #puts "#{filename}##{i} - #{ok_label}" unless @options[:quiet]
4045
+ end
4046
+ end
4047
+
4048
+
4049
+ def _usage()
4050
+ #msg = Kwalify.msg(:command_help) % [@command, @command, @command]
4051
+ msg = Kwalify.msg(:command_help)
4052
+ return msg
4053
+ end
4054
+
4055
+
4056
+ def _version()
4057
+ return RELEASE
4058
+ end
4059
+
4060
+
4061
+ def _to_value(str)
4062
+ case str
4063
+ when nil, "null", "nil" ; return nil
4064
+ when "true", "yes" ; return true
4065
+ when "false", "no" ; return false
4066
+ when /\A\d+\z/ ; return str.to_i
4067
+ when /\A\d+\.\d+\z/ ; return str.to_f
4068
+ when /\/(.*)\// ; return Regexp.new($1)
4069
+ when /\A'.*'\z/, /\A".*"\z/ ; return eval(str)
4070
+ else ; return str
4071
+ end
4072
+ end
4073
+
4074
+
4075
+ def _parse_argv(argv)
4076
+ option_table = {
4077
+ ?h => :help,
4078
+ ?v => :version,
4079
+ ?q => :quiet,
4080
+ ?s => :quiet,
4081
+ ?t => :untabify,
4082
+ #?z => :meta,
4083
+ ?m => :meta,
4084
+ ?M => :meta2,
4085
+ ?E => :emacs,
4086
+ ?l => :linenum,
4087
+ ?f => :schema,
4088
+ ?D => :debug,
4089
+ ?a => :action,
4090
+ ?I => :tpath,
4091
+ ?P => :preceding,
4092
+ }
4093
+
4094
+ errcode_table = {
4095
+ #* key=:command_option_schema_required msg="-%s: schema filename required."
4096
+ ?f => :command_option_schema_required,
4097
+ #* key=:command_option_action_required msg="-%s: action required."
4098
+ ?a => :command_option_action_required,
4099
+ #* key=:command_option_tpath_required msg="-%s: template path required."
4100
+ ?I => :command_option_tpath_required,
4101
+ }
4102
+
4103
+ while argv[0] && argv[0][0] == ?-
4104
+ optstr = argv.shift
4105
+ optstr = optstr[1, optstr.length-1]
4106
+ ## property
4107
+ if optstr[0] == ?-
4108
+ unless optstr =~ /\A\-([-\w]+)(?:=(.*))?\z/
4109
+ #* key=:command_property_invalid msg="%s: invalid property."
4110
+ raise option_error(:command_property_invalid, optstr)
4111
+ end
4112
+ prop_name = $1; prop_value = $2
4113
+ key = prop_name.gsub(/-/, '_').intern
4114
+ value = prop_value.nil? ? true : _to_value(prop_value)
4115
+ @properties[key] = value
4116
+ ## option
4117
+ else
4118
+ while optstr && !optstr.empty?
4119
+ optchar = optstr[0]
4120
+ optstr[0,1] = ""
4121
+ unless option_table.key?(optchar)
4122
+ #* key=:command_option_invalid msg="-%s: invalid command option."
4123
+ raise option_error(:command_option_invalid, optchar.chr)
4124
+ end
4125
+ optkey = option_table[optchar]
4126
+ case optchar
4127
+ when ?f, ?a, ?I
4128
+ arg = optstr.empty? ? argv.shift : optstr
4129
+ raise option_error(errcode_table[optchar], optchar.chr) unless arg
4130
+ @options[optkey] = arg
4131
+ optstr = ''
4132
+ else
4133
+ @options[optkey] = true
4134
+ end
4135
+ end
4136
+ end
4137
+ end # end of while
4138
+ #
4139
+ @options[:linenum] = true if @options[:emacs]
4140
+ @options[:help] = true if @properties[:help]
4141
+ @options[:version] = true if @properties[:version]
4142
+ filenames = argv
4143
+ return filenames
4144
+ end
4145
+
4146
+
4147
+ def _domain_type?(doc)
4148
+ klass = defined?(YAML::DomainType) ? YAML::DomainType : YAML::Syck::DomainType
4149
+ return doc.is_a?(klass)
4150
+ end
4151
+
4152
+
4153
+ end
4154
+
4155
+
4156
+ end
4157
+ #--end of require 'kwalify/main'
4158
+
4159
+ command = File.basename($0)
4160
+ Kwalify::Main.main(command, ARGV)