ddao-kwalify 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) hide show
  1. data/CHANGES.txt +243 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.txt +61 -0
  4. data/bin/kwalify +13 -0
  5. data/contrib/inline-require +153 -0
  6. data/contrib/kwalify +4159 -0
  7. data/doc-api/classes/CommandOptionError.html +184 -0
  8. data/doc-api/classes/CommandOptionParser.html +325 -0
  9. data/doc-api/classes/Kwalify.html +292 -0
  10. data/doc-api/classes/Kwalify/AssertionError.html +148 -0
  11. data/doc-api/classes/Kwalify/BaseError.html +297 -0
  12. data/doc-api/classes/Kwalify/BaseParser.html +461 -0
  13. data/doc-api/classes/Kwalify/CommandOptionError.html +168 -0
  14. data/doc-api/classes/Kwalify/ErrorHelper.html +223 -0
  15. data/doc-api/classes/Kwalify/HashInterface.html +118 -0
  16. data/doc-api/classes/Kwalify/Json.html +105 -0
  17. data/doc-api/classes/Kwalify/KwalifyError.html +111 -0
  18. data/doc-api/classes/Kwalify/Main.html +339 -0
  19. data/doc-api/classes/Kwalify/MetaValidator.html +448 -0
  20. data/doc-api/classes/Kwalify/Parser.html +155 -0
  21. data/doc-api/classes/Kwalify/PlainYamlParser.html +523 -0
  22. data/doc-api/classes/Kwalify/PlainYamlParser/Alias.html +165 -0
  23. data/doc-api/classes/Kwalify/Rule.html +433 -0
  24. data/doc-api/classes/Kwalify/SchemaError.html +148 -0
  25. data/doc-api/classes/Kwalify/SyntaxError.html +185 -0
  26. data/doc-api/classes/Kwalify/Types.html +302 -0
  27. data/doc-api/classes/Kwalify/Util.html +389 -0
  28. data/doc-api/classes/Kwalify/Util/HashLike.html +246 -0
  29. data/doc-api/classes/Kwalify/Util/OrderedHash.html +330 -0
  30. data/doc-api/classes/Kwalify/ValidationError.html +148 -0
  31. data/doc-api/classes/Kwalify/Validator.html +381 -0
  32. data/doc-api/classes/Kwalify/Yaml.html +194 -0
  33. data/doc-api/classes/Kwalify/Yaml/Parser.html +1538 -0
  34. data/doc-api/classes/Kwalify/YamlParser.html +542 -0
  35. data/doc-api/classes/Kwalify/YamlSyntaxError.html +119 -0
  36. data/doc-api/classes/Test.html +107 -0
  37. data/doc-api/classes/Test/Unit.html +101 -0
  38. data/doc-api/created.rid +1 -0
  39. data/doc-api/files/__/README_txt.html +172 -0
  40. data/doc-api/files/kwalify/errors_rb.html +114 -0
  41. data/doc-api/files/kwalify/main_rb.html +118 -0
  42. data/doc-api/files/kwalify/messages_rb.html +107 -0
  43. data/doc-api/files/kwalify/meta-validator_rb.html +117 -0
  44. data/doc-api/files/kwalify/parser/base_rb.html +116 -0
  45. data/doc-api/files/kwalify/parser/yaml_rb.html +117 -0
  46. data/doc-api/files/kwalify/rule_rb.html +116 -0
  47. data/doc-api/files/kwalify/types_rb.html +114 -0
  48. data/doc-api/files/kwalify/util/assert-text-equal_rb.html +115 -0
  49. data/doc-api/files/kwalify/util/hash-interface_rb.html +114 -0
  50. data/doc-api/files/kwalify/util/hashlike_rb.html +107 -0
  51. data/doc-api/files/kwalify/util/option-parser_rb.html +107 -0
  52. data/doc-api/files/kwalify/util/ordered-hash_rb.html +107 -0
  53. data/doc-api/files/kwalify/util/testcase-helper_rb.html +115 -0
  54. data/doc-api/files/kwalify/util_rb.html +107 -0
  55. data/doc-api/files/kwalify/validator_rb.html +117 -0
  56. data/doc-api/files/kwalify/yaml-parser_rb.html +117 -0
  57. data/doc-api/files/kwalify_rb.html +121 -0
  58. data/doc-api/fr_class_index.html +57 -0
  59. data/doc-api/fr_file_index.html +45 -0
  60. data/doc-api/fr_method_index.html +168 -0
  61. data/doc-api/index.html +24 -0
  62. data/doc-api/rdoc-style.css +208 -0
  63. data/doc/docstyle.css +188 -0
  64. data/doc/img/fig01.png +0 -0
  65. data/doc/users-guide.html +2050 -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.rb +67 -0
  82. data/lib/kwalify/errors.rb +128 -0
  83. data/lib/kwalify/kwalify.schema.yaml +58 -0
  84. data/lib/kwalify/main.rb +442 -0
  85. data/lib/kwalify/messages.rb +173 -0
  86. data/lib/kwalify/meta-validator.rb +276 -0
  87. data/lib/kwalify/parser/base.rb +127 -0
  88. data/lib/kwalify/parser/yaml.rb +841 -0
  89. data/lib/kwalify/rule.rb +560 -0
  90. data/lib/kwalify/templates/genclass-java.eruby +222 -0
  91. data/lib/kwalify/templates/genclass-php.eruby +104 -0
  92. data/lib/kwalify/templates/genclass-ruby.eruby +113 -0
  93. data/lib/kwalify/types.rb +156 -0
  94. data/lib/kwalify/util.rb +157 -0
  95. data/lib/kwalify/util/assert-text-equal.rb +46 -0
  96. data/lib/kwalify/util/hash-interface.rb +18 -0
  97. data/lib/kwalify/util/hashlike.rb +51 -0
  98. data/lib/kwalify/util/option-parser.rb +220 -0
  99. data/lib/kwalify/util/ordered-hash.rb +57 -0
  100. data/lib/kwalify/util/testcase-helper.rb +112 -0
  101. data/lib/kwalify/validator.rb +282 -0
  102. data/lib/kwalify/yaml-parser.rb +870 -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 +384 -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-validator.rb +95 -0
  204. data/test/test-validator.yaml +986 -0
  205. data/test/test-yaml-parser.rb +47 -0
  206. data/test/test-yaml-parser.yaml +1226 -0
  207. data/test/test.rb +60 -0
  208. metadata +261 -0
@@ -0,0 +1,4159 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ ###
4
+ ### $Rev: 83 $
5
+ ### $Release: 0.7.1 $
6
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
7
+ ###
8
+
9
+ #--begin of require 'kwalify'
10
+ ###
11
+ ### $Rev: 99 $
12
+ ### $Release: 0.7.1 $
13
+ ### copyright(c) 2006 kuwata-lab.com all rights reserved.
14
+ ###
15
+
16
+
17
+ module Kwalify
18
+
19
+ RELEASE = ("$Release: 0.7.1 $" =~ /[.\d]+/) && $&
20
+
21
+ end
22
+
23
+ #--begin of require 'kwalify/types'
24
+ ###
25
+ ### $Rev: 87 $
26
+ ### $Release: 0.7.1 $
27
+ ### copyright(c) 2005-2008 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: 95 $
184
+ ### $Release: 0.7.1 $
185
+ ### copyright(c) 2005-2008 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: 99 $
359
+ ### $Release: 0.7.1 $
360
+ ### copyright(c) 2005-2008 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: 95 $
488
+ ### $Release: 0.7.1 $
489
+ ### copyright(c) 2005-2008 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: 99 $
1049
+ ### $Release: 0.7.1 $
1050
+ ### copyright(c) 2005-2008 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: 95 $
1333
+ ### $Release: 0.7.1 $
1334
+ ### copyright(c) 2005-2008 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: 99 $
1343
+ ### $Release: 0.7.1 $
1344
+ ### copyright(c) 2005-2008 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: 88 $
1352
+ ### $Release: 0.7.1 $
1353
+ ### copyright(c) 2005-2008 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
+ list = str.split(/\t/)
1370
+ last = list.pop
1371
+ sb = ''
1372
+ list.each do |s|
1373
+ column = (n = s.rindex(?\n)) ? s.length - n - 1 : s.length
1374
+ n = width - (column % width)
1375
+ sb << s << (' ' * n)
1376
+ end
1377
+ sb << last if last
1378
+ return sb
1379
+ end
1380
+
1381
+
1382
+ ## traverse schema
1383
+ ##
1384
+ ## ex.
1385
+ ## schema = YAML.load_file('myschema.yaml')
1386
+ ## Kwalify::Util.traverse_schema(schema) do |rulehash|
1387
+ ## ## add module prefix to class name
1388
+ ## if rulehash['class']
1389
+ ## rulehash['class'] = "MyModule::' + rulehash['class']
1390
+ ## end
1391
+ ## end
1392
+ def traverse_schema(schema, &block) #:yield: rulehash
1393
+ hash = schema
1394
+ _done = {}
1395
+ _traverse_schema(hash, _done, &block)
1396
+ end
1397
+
1398
+ def _traverse_schema(hash, _done={}, &block)
1399
+ return if _done.key?(hash.__id__)
1400
+ _done[hash.__id__] = hash
1401
+ yield hash
1402
+ if hash['mapping']
1403
+ hash['mapping'].each {|k, v| _traverse_schema(v, _done, &block) }
1404
+ elsif hash['sequence']
1405
+ _traverse_schema(hash['sequence'][0], _done, &block)
1406
+ end
1407
+ end
1408
+ private :_traverse_schema
1409
+
1410
+
1411
+ ## traverse rule
1412
+ ##
1413
+ ## ex.
1414
+ ## schema = YAML.load_file('myschema.yaml')
1415
+ ## validator = Kwalify::Validator.new(schema)
1416
+ ## Kwalify::Util.traverse_rule(validator) do |rule|
1417
+ ## p rule if rule.classname
1418
+ ## end
1419
+ def traverse_rule(validator, &block) #:yield: rule
1420
+ rule = validator.is_a?(Rule) ? validator : validator.rule
1421
+ _done = {}
1422
+ _traverse_rule(rule, _done, &block)
1423
+ end
1424
+
1425
+ def _traverse_rule(rule, _done={}, &block)
1426
+ return if _done.key?(rule.__id__)
1427
+ _done[rule.__id__] = rule
1428
+ yield rule
1429
+ rule.sequence.each do |seq_rule|
1430
+ _traverse_rule(seq_rule, _done, &block)
1431
+ end if rule.sequence
1432
+ rule.mapping.each do |name, map_rule|
1433
+ _traverse_rule(map_rule, _done, &block)
1434
+ end if rule.mapping
1435
+ end
1436
+ private :_traverse_rule
1437
+
1438
+
1439
+ ##
1440
+ ## get class object. if not found, NameError raised.
1441
+ ##
1442
+ def get_class(classname)
1443
+ klass = Object
1444
+ classname.split('::').each do |name|
1445
+ klass = klass.const_get(name)
1446
+ end
1447
+ return klass
1448
+ end
1449
+
1450
+
1451
+ ##
1452
+ ## create a hash table from list of hash with primary key.
1453
+ ##
1454
+ ## ex.
1455
+ ## hashlist = [
1456
+ ## { "name"=>"Foo", "gender"=>"M", "age"=>20, },
1457
+ ## { "name"=>"Bar", "gender"=>"F", "age"=>25, },
1458
+ ## { "name"=>"Baz", "gender"=>"M", "age"=>30, },
1459
+ ## ]
1460
+ ## hashtable = YamlHelper.create_hashtable(hashlist, "name")
1461
+ ## p hashtable
1462
+ ## # => { "Foo" => { "name"=>"Foo", "gender"=>"M", "age"=>20, },
1463
+ ## # "Bar" => { "name"=>"Bar", "gender"=>"F", "age"=>25, },
1464
+ ## # "Baz" => { "name"=>"Baz", "gender"=>"M", "age"=>30, }, }
1465
+ ##
1466
+ def create_hashtable(hashlist, primarykey, flag_duplicate_check=true)
1467
+ hashtable = {}
1468
+ hashlist.each do |hash|
1469
+ key = hash[primarykey]
1470
+ unless key
1471
+ riase "primary key '#{key}' not found."
1472
+ end
1473
+ if flag_duplicate_check && hashtable.key?(key)
1474
+ raise "primary key '#{key}' duplicated (value '#{hashtable[key]}')"
1475
+ end
1476
+ hashtable[key] = hash
1477
+ end if hashlist
1478
+ return hashtable
1479
+ end
1480
+
1481
+
1482
+ ##
1483
+ ## get nested value directly.
1484
+ ##
1485
+ ## ex.
1486
+ ## val = YamlHelper.get_value(obj, ['aaa', 0, 'xxx'])
1487
+ ##
1488
+ ## This is equal to the following:
1489
+ ## begin
1490
+ ## val = obj['aaa'][0]['xxx']
1491
+ ## rescue NameError
1492
+ ## val = nil
1493
+ ## end
1494
+ ##
1495
+ def get_value(obj, path)
1496
+ val = obj
1497
+ path.each do |key|
1498
+ return nil unless val.is_a?(Hash) || val.is_a?(Array)
1499
+ val = val[key]
1500
+ end if path
1501
+ return val
1502
+ end
1503
+
1504
+ end
1505
+
1506
+ end
1507
+ #--end of require 'kwalify/util'
1508
+ #--begin of require 'kwalify/parser/base'
1509
+ ###
1510
+ ### $Rev: 92 $
1511
+ ### $Release: 0.7.1 $
1512
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
1513
+ ###
1514
+
1515
+ require 'strscan'
1516
+ #--already included require 'kwalify/errors'
1517
+ #--already included require 'kwalify/util'
1518
+
1519
+
1520
+ ##
1521
+ ## base class for Yaml::Parser
1522
+ ##
1523
+ class Kwalify::BaseParser
1524
+
1525
+
1526
+ def reset(input, filename=nil, untabify=false)
1527
+ input = Kwalify::Util.untabify(input) if untabify
1528
+ @scanner = StringScanner.new(input)
1529
+ @filename = filename
1530
+ @linenum = 1
1531
+ @column = 1
1532
+ end
1533
+ attr_reader :filename, :linenum, :column
1534
+
1535
+
1536
+ def scan(regexp)
1537
+ ret = @scanner.scan(regexp)
1538
+ return nil if ret.nil?
1539
+ _set_column_and_linenum(ret)
1540
+ return ret
1541
+ end
1542
+
1543
+
1544
+ def _set_column_and_linenum(s)
1545
+ pos = s.rindex(?\n)
1546
+ if pos
1547
+ @column = s.length - pos
1548
+ @linenum += s.count("\n")
1549
+ else
1550
+ @column += s.length
1551
+ end
1552
+ end
1553
+
1554
+
1555
+ def match?(regexp)
1556
+ return @scanner.match?(regexp)
1557
+ end
1558
+
1559
+
1560
+ def group(n)
1561
+ return @scanner[n]
1562
+ end
1563
+
1564
+
1565
+ def eos?
1566
+ return @scanner.eos?
1567
+ end
1568
+
1569
+
1570
+ def peep(n=1)
1571
+ return @scanner.peep(n)
1572
+ end
1573
+
1574
+
1575
+ def _getch
1576
+ ch = @scanner.getch()
1577
+ if ch == "\n"
1578
+ @linenum += 1
1579
+ @column = 0
1580
+ else
1581
+ @column += 1
1582
+ end
1583
+ return ch
1584
+ end
1585
+
1586
+
1587
+ CHAR_TABLE = { "\""=>"\"", "\\"=>"\\", "n"=>"\n", "r"=>"\r", "t"=>"\t", "b"=>"\b" }
1588
+
1589
+ def scan_string
1590
+ ch = _getch()
1591
+ ch == '"' || ch == "'" or raise "assertion error"
1592
+ endch = ch
1593
+ s = ''
1594
+ while !(ch = _getch()).nil? && ch != endch
1595
+ if ch != '\\'
1596
+ s << ch
1597
+ elsif (ch = _getch()).nil?
1598
+ raise _syntax_error("%s: string is not closed." % (endch == '"' ? "'\"'" : '"\'"'))
1599
+ elsif endch == '"'
1600
+ if CHAR_TABLE.key?(ch)
1601
+ s << CHAR_TABLE[ch]
1602
+ elsif ch == 'u'
1603
+ ch2 = scan(/(?:[0-9a-f][0-9a-f]){1,4}/)
1604
+ unless ch2
1605
+ raise _syntax_error("\\x: invalid unicode format.")
1606
+ end
1607
+ s << [ch2.hex].pack('U*')
1608
+ elsif ch == 'x'
1609
+ ch2 = scan(/[0-9a-zA-Z][0-9a-zA-Z]/)
1610
+ unless ch2
1611
+ raise _syntax_error("\\x: invalid binary format.")
1612
+ end
1613
+ s << [ch2].pack('H2')
1614
+ else
1615
+ s << "\\" << ch
1616
+ end
1617
+ elsif endch == "'"
1618
+ ch == '\'' || ch == '\\' ? s << ch : s << '\\' << ch
1619
+ else
1620
+ raise "unreachable"
1621
+ end
1622
+ end
1623
+ #_getch()
1624
+ return s
1625
+ end
1626
+
1627
+
1628
+ def _syntax_error(message, path=nil, linenum=@linenum, column=@column)
1629
+ #message = _build_message(message_key)
1630
+ return _error(Kwalify::SyntaxError, message.to_s, path, linenum, column)
1631
+ end
1632
+ protected :_syntax_error
1633
+
1634
+
1635
+ end
1636
+ #--end of require 'kwalify/parser/base'
1637
+
1638
+
1639
+
1640
+ module Kwalify
1641
+
1642
+ module Yaml
1643
+ end
1644
+
1645
+ end
1646
+
1647
+
1648
+ ##
1649
+ ## YAML parser with validator
1650
+ ##
1651
+ ## ex.
1652
+ ## schema = YAML.load_file('schema.yaml')
1653
+ ## require 'kwalify'
1654
+ ## validator = Kwalify::Validator.new(schema)
1655
+ ## parser = Kwalify::Yaml::Parser.new(validator) # validator is optional
1656
+ ## #parser.preceding_alias = true # optional
1657
+ ## #parser.data_binding = true # optional
1658
+ ## ydoc = parser.parse_file('data.yaml')
1659
+ ## errors = parser.errors
1660
+ ## if errors && !errors.empty?
1661
+ ## errors.each do |e|
1662
+ ## puts "line=#{e.linenum}, path=#{e.path}, mesg=#{e.message}"
1663
+ ## end
1664
+ ## end
1665
+ ##
1666
+ class Kwalify::Yaml::Parser < Kwalify::BaseParser
1667
+
1668
+
1669
+ alias reset_scanner reset
1670
+
1671
+
1672
+ def initialize(validator=nil, properties={})
1673
+ @validator = validator.is_a?(Hash) ? Kwalify::Validator.new(validator) : validator
1674
+ @data_binding = properties[:data_binding] # enable data binding or not
1675
+ @preceding_alias = properties[:preceding_alias] # allow preceding alias or not
1676
+ @sequence_class = properties[:sequence_class] || Array
1677
+ @mapping_class = properties[:mapping_class] || Hash
1678
+ end
1679
+ attr_accessor :validator # Validator
1680
+ attr_accessor :data_binding # boolean
1681
+ attr_accessor :preceding_alias # boolean
1682
+ attr_accessor :sequence_class # Class
1683
+ attr_accessor :mapping_class # Class
1684
+
1685
+
1686
+ def reset_parser()
1687
+ @anchors = {}
1688
+ @errors = []
1689
+ @done = {}
1690
+ @preceding_aliases = []
1691
+ @location_table = {} # object_id -> sequence or mapping
1692
+ @doc = nil
1693
+ end
1694
+ attr_reader :errors
1695
+
1696
+
1697
+ def _error(klass, message, path, linenum, column)
1698
+ ex = klass.new(message)
1699
+ ex.path = path.is_a?(Array) ? '/' + path.join('/') : path
1700
+ ex.linenum = linenum
1701
+ ex.column = column
1702
+ ex.filename = @filename
1703
+ return ex
1704
+ end
1705
+ private :_error
1706
+
1707
+
1708
+ # def _validate_error(message, path, linenum=@linenum, column=@column)
1709
+ # #message = _build_message(message_key)
1710
+ # error = _error(ValidationError, message.to_s, path, linenum, column)
1711
+ # @errors << error
1712
+ # end
1713
+ # private :_validate_error
1714
+
1715
+
1716
+ def _set_error_info(linenum=@linenum, column=@column, &block)
1717
+ len = @errors.length
1718
+ yield
1719
+ n = @errors.length - len
1720
+ (1..n).each do |i|
1721
+ error = @errors[-i]
1722
+ error.linenum ||= linenum
1723
+ error.column ||= column
1724
+ error.filename ||= @filename
1725
+ end if n > 0
1726
+ end
1727
+
1728
+
1729
+ def skip_spaces_and_comments()
1730
+ scan(/\s+/)
1731
+ while match?(/\#/)
1732
+ scan(/.*?\n/)
1733
+ scan(/\s+/)
1734
+ end
1735
+ end
1736
+
1737
+
1738
+ def document_start?()
1739
+ return match?(/---\s/) && @column == 1
1740
+ end
1741
+
1742
+
1743
+ def stream_end?()
1744
+ return match?(/\.\.\.\s/) && @column == 1
1745
+ end
1746
+
1747
+
1748
+ def has_next?()
1749
+ return !(eos? || stream_end?)
1750
+ end
1751
+
1752
+
1753
+ def parse(input=nil, opts={})
1754
+ reset_scanner(input, opts[:filename], opts[:untabify]) if input
1755
+ return parse_next()
1756
+ end
1757
+
1758
+
1759
+ def parse_file(filename, opts={})
1760
+ opts[:filename] = filename
1761
+ return parse(File.read(filename), opts)
1762
+ end
1763
+
1764
+
1765
+ def parse_next()
1766
+ reset_parser()
1767
+ path = []
1768
+ skip_spaces_and_comments()
1769
+ if document_start?()
1770
+ scan(/.*\n/)
1771
+ skip_spaces_and_comments()
1772
+ end
1773
+ _linenum = @linenum #*V
1774
+ _column = @column #*V
1775
+ rule = @validator ? @validator.rule : nil #*V
1776
+ uniq_table = nil #*V
1777
+ parent = nil #*V
1778
+ val = parse_block_value(0, rule, path, uniq_table, parent)
1779
+ _set_error_info(_linenum, _column) do #*V
1780
+ @validator._validate(val, rule, [], @errors, @done, uniq_table, false) #*V
1781
+ end if rule #*V
1782
+ resolve_preceding_aliases(val) if @preceding_alias
1783
+ unless eos? || document_start?() || stream_end?()
1784
+ raise _syntax_error("document end expected (maybe invalid tab char found).", path)
1785
+ end
1786
+ @doc = val
1787
+ @location_table[-1] = [_linenum, _column]
1788
+ return val
1789
+ end
1790
+
1791
+
1792
+ def parse_stream(input, opts={}, &block)
1793
+ reset_scanner(input, opts[:filename], opts[:untabify])
1794
+ ydocs = block_given? ? nil : []
1795
+ while true
1796
+ ydoc = parse_next()
1797
+ ydocs ? (ydocs << ydoc) : (yield ydoc)
1798
+ break if eos? || stream_end?()
1799
+ document_start?() or raise "** internal error"
1800
+ scan(/.*\n/)
1801
+ end
1802
+ return ydocs
1803
+ end
1804
+
1805
+ alias parse_documents parse_stream
1806
+
1807
+
1808
+ MAPKEY_PATTERN = /([\w.][-\w.:]*\*?|".*?"|'.*?'|:\w+|=|<<)[ \t]*:\s+/ # :nodoc:
1809
+
1810
+ PRECEDING_ALIAS_PLACEHOLDER = Object.new # :nodoc:
1811
+
1812
+
1813
+ def parse_anchor(rule, path, uniq_table, container)
1814
+ name = group(1)
1815
+ if @anchors.key?(name)
1816
+ raise _syntax_error("&#{name}: anchor duplicated.", path,
1817
+ @linenum, @column - name.length)
1818
+ end
1819
+ skip_spaces_and_comments()
1820
+ return name
1821
+ end
1822
+
1823
+
1824
+ def parse_alias(rule, path, uniq_table, container)
1825
+ name = group(1)
1826
+ if @anchors.key?(name)
1827
+ val = @anchors[name]
1828
+ elsif @preceding_alias
1829
+ @preceding_aliases << [name, rule, path.dup, container,
1830
+ @linenum, @column - name.length - 1]
1831
+ val = PRECEDING_ALIAS_PLACEHOLDER
1832
+ else
1833
+ raise _syntax_error("*#{name}: anchor not found.", path,
1834
+ @linenum, @column - name.length - 1)
1835
+ end
1836
+ skip_spaces_and_comments()
1837
+ return val
1838
+ end
1839
+
1840
+
1841
+ def resolve_preceding_aliases(val)
1842
+ @preceding_aliases.each do |name, rule, path, container, _linenum, _column|
1843
+ unless @anchors.key?(name)
1844
+ raise _syntax_error("*#{name}: anchor not found.", path, _linenum, _column)
1845
+ end
1846
+ key = path[-1]
1847
+ val = @anchors[name]
1848
+ raise unless !container.respond_to?('[]') || container[key].equal?(PRECEDING_ALIAS_PLACEHOLDER)
1849
+ if container.is_a?(Array)
1850
+ container[key] = val
1851
+ else
1852
+ put_to_map(rule, container, key, val, _linenum, _column)
1853
+ end
1854
+ _set_error_info(_linenum, _column) do #*V
1855
+ @validator._validate(val, rule, path, @errors, @done, false) #*V
1856
+ end if rule #*V
1857
+ end
1858
+ end
1859
+
1860
+
1861
+ def parse_block_value(level, rule, path, uniq_table, container)
1862
+ skip_spaces_and_comments()
1863
+ ## nil
1864
+ return nil if @column <= level || eos?
1865
+ ## anchor and alias
1866
+ name = nil
1867
+ if scan(/\&([-\w]+)/)
1868
+ name = parse_anchor(rule, path, uniq_table, container)
1869
+ elsif scan(/\*([-\w]+)/)
1870
+ return parse_alias(rule, path, uniq_table, container)
1871
+ end
1872
+ ## type
1873
+ if scan(/!!?\w+/)
1874
+ skip_spaces_and_comments()
1875
+ end
1876
+ ## sequence
1877
+ if match?(/-\s+/)
1878
+ if rule && !rule.sequence
1879
+ #_validate_error("sequence is not expected.", path)
1880
+ rule = nil
1881
+ end
1882
+ seq = create_sequence(rule, @linenum, @column)
1883
+ @anchors[name] = seq if name
1884
+ parse_block_seq(seq, rule, path, uniq_table)
1885
+ return seq
1886
+ end
1887
+ ## mapping
1888
+ if match?(MAPKEY_PATTERN)
1889
+ if rule && !rule.mapping
1890
+ #_validate_error("mapping is not expected.", path)
1891
+ rule = nil
1892
+ end
1893
+ map = create_mapping(rule, @linenum, @column)
1894
+ @anchors[name] = map if name
1895
+ parse_block_map(map, rule, path, uniq_table)
1896
+ return map
1897
+ end
1898
+ ## sequence (flow-style)
1899
+ if match?(/\[/)
1900
+ if rule && !rule.sequence
1901
+ #_validate_error("sequence is not expected.", path)
1902
+ rule = nil
1903
+ end
1904
+ seq = create_sequence(rule, @linenum, @column)
1905
+ @anchors[name] = seq if name
1906
+ parse_flow_seq(seq, rule, path, uniq_table)
1907
+ return seq
1908
+ end
1909
+ ## mapping (flow-style)
1910
+ if match?(/\{/)
1911
+ if rule && !rule.mapping
1912
+ #_validate_error("mapping is not expected.", path)
1913
+ rule = nil
1914
+ end
1915
+ map = create_mapping(rule, @linenum, @column)
1916
+ @anchors[name] = map if name
1917
+ parse_flow_map(map, rule, path, uniq_table)
1918
+ return map
1919
+ end
1920
+ ## block text
1921
+ if match?(/[|>]/)
1922
+ text = parse_block_text(level, rule, path, uniq_table)
1923
+ @anchors[name] = text if name
1924
+ return text
1925
+ end
1926
+ ## scalar
1927
+ scalar = parse_block_scalar(rule, path, uniq_table)
1928
+ @anchors[name] = scalar if name
1929
+ return scalar
1930
+ end
1931
+
1932
+
1933
+ def parse_block_seq(seq, seq_rule, path, uniq_table)
1934
+ level = @column
1935
+ rule = seq_rule ? seq_rule.sequence[0] : nil
1936
+ path.push(nil)
1937
+ i = 0
1938
+ _linenum = @linenum #*V
1939
+ _column = @column #*V
1940
+ uniq_table = rule ? rule._uniqueness_check_table() : nil #*V
1941
+ while level == @column && scan(/-\s+/)
1942
+ path[-1] = i
1943
+ skip_spaces_and_comments() #*V
1944
+ _linenum2 = @linenum
1945
+ _column2 = @column
1946
+ val = parse_block_value(level, rule, path, uniq_table, seq)
1947
+ add_to_seq(rule, seq, val, _linenum2, _column2) # seq << val
1948
+ _set_error_info(_linenum, _column) do #*V
1949
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
1950
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
1951
+ skip_spaces_and_comments()
1952
+ i += 1
1953
+ _linenum = @linenum #*V
1954
+ _column = @column #*V
1955
+ end
1956
+ path.pop()
1957
+ return seq
1958
+ end
1959
+
1960
+
1961
+ def _parse_map_value(map, map_rule, path, level, key, is_merged, uniq_table,
1962
+ _linenum, _column, _linenum2, _column2) #:nodoc:
1963
+ key = to_mapkey(key)
1964
+ path[-1] = key
1965
+ #if map.is_a?(Hash) && map.key?(key) && !is_merged
1966
+ if map.respond_to?('key?') && map.key?(key) && !is_merged
1967
+ rule = map_rule.mapping[key]
1968
+ unless rule && rule.default
1969
+ raise _syntax_error("mapping key is duplicated.", path)
1970
+ end
1971
+ end
1972
+ #
1973
+ if key == '=' # default
1974
+ val = level ? parse_block_value(level, nil, path, uniq_table, map) \
1975
+ : parse_flow_value(nil, path, uniq_table, map)
1976
+ map.default = val
1977
+ elsif key == '<<' # merge
1978
+ classobj = nil
1979
+ if map_rule && map_rule.classname
1980
+ map_rule = map_rule.dup()
1981
+ classobj = map_rule.classobj
1982
+ map_rule.classname = nil
1983
+ map_rule.classobj = nil
1984
+ end
1985
+ val = level ? parse_block_value(level, map_rule, path, uniq_table, map) \
1986
+ : parse_flow_value(map_rule, path, uniq_table, map)
1987
+ if val.is_a?(Array)
1988
+ val.each_with_index do |v, i|
1989
+ unless v.is_a?(Hash) || (classobj && val.is_a?(classobj))
1990
+ raise _syntax_error("'<<': mapping required.", path + [i])
1991
+ end
1992
+ end
1993
+ values = val
1994
+ elsif val.is_a?(Hash) || (classobj && val.is_a?(classobj))
1995
+ values = [val]
1996
+ else
1997
+ raise _syntax_error("'<<': mapping (or sequence of mapping) required.", path)
1998
+ end
1999
+ #
2000
+ values.each do |hash|
2001
+ if !hash.is_a?(Hash)
2002
+ assert_error "hash=#{hash.inspect}" unless classobj && hash.is_a?(classobj)
2003
+ obj = hash
2004
+ hash = {}
2005
+ obj.instance_variables.each do |name|
2006
+ key = name[1..-1] # '@foo' => 'foo'
2007
+ val = obj.instane_variable_get(name)
2008
+ hash[key] = val
2009
+ end
2010
+ end
2011
+ for key, val in hash
2012
+ path[-1] = key #*V
2013
+ rule = map_rule ? map_rule.mapping[key] : nil #*V
2014
+ utable = uniq_table ? uniq_table[key] : nil #*V
2015
+ _validate_map_value(map, map_rule, rule, path, utable, #*V
2016
+ key, val, _linenum, _column) #*V
2017
+ put_to_map(rule, map, key, val, _linenum2, _column2)
2018
+ end
2019
+ end
2020
+ is_merged = true
2021
+ else # other
2022
+ rule = map_rule ? map_rule.mapping[key] : nil #*V
2023
+ utable = uniq_table ? uniq_table[key] : nil #*V
2024
+ val = level ? parse_block_value(level, rule, path, utable, map) \
2025
+ : parse_flow_value(rule, path, utable, map)
2026
+ _validate_map_value(map, map_rule, rule, path, utable, key, val, #*V
2027
+ _linenum, _column) #*V
2028
+ put_to_map(rule, map, key, val, _linenum2, _column2)
2029
+ end
2030
+ return is_merged
2031
+ end
2032
+
2033
+
2034
+ def _validate_map_value(map, map_rule, rule, path, uniq_table, key, val, #*V
2035
+ _linenum, _column) #*V
2036
+ if map_rule && !rule #*V
2037
+ #_validate_error("unknown mapping key.", path) #*V
2038
+ _set_error_info(_linenum, _column) do #*V
2039
+ error = Kwalify::ErrorHelper.validate_error(:key_undefined, #*V
2040
+ rule, path, map, ["#{key}:"]) #*V
2041
+ @errors << error #*V
2042
+ #error.linenum = _linenum #*V
2043
+ #error.column = _column #*V
2044
+ end #*V
2045
+ end #*V
2046
+ _set_error_info(_linenum, _column) do #*V
2047
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
2048
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
2049
+ end
2050
+
2051
+
2052
+ def parse_block_map(map, map_rule, path, uniq_table)
2053
+ _start_linenum = @linenum #*V
2054
+ _start_column = @column #*V
2055
+ level = @column
2056
+ path.push(nil)
2057
+ is_merged = false
2058
+ while true
2059
+ _linenum = @linenum #*V
2060
+ _column = @column #*V
2061
+ break unless level == @column && scan(MAPKEY_PATTERN)
2062
+ key = group(1)
2063
+ skip_spaces_and_comments() #*V
2064
+ _linenum2 = @linenum #*V
2065
+ _column2 = @column #*V
2066
+ is_merged = _parse_map_value(map, map_rule, path, level, key, is_merged,
2067
+ uniq_table, _linenum, _column, _linenum2, _column2)
2068
+ #skip_spaces_and_comments()
2069
+ end
2070
+ path.pop()
2071
+ _set_error_info(_start_linenum, _start_column) do #*V
2072
+ @validator._validate_mapping_required_keys(map, map_rule, #*V
2073
+ path, @errors) #*V
2074
+ end if map_rule #*V
2075
+ return map
2076
+ end
2077
+
2078
+
2079
+ def to_mapkey(str)
2080
+ if str[0] == ?" || str[0] == ?'
2081
+ return str[1..-2]
2082
+ else
2083
+ return to_scalar(str)
2084
+ end
2085
+ end
2086
+ private :to_mapkey
2087
+
2088
+
2089
+ def parse_block_scalar(rule, path, uniq_table)
2090
+ _linenum = @linenum #*V
2091
+ _column = @column #*V
2092
+ ch = peep(1)
2093
+ if ch == '"' || ch == "'"
2094
+ val = scan_string()
2095
+ scan(/[ \t]*(?:\#.*)?$/)
2096
+ else
2097
+ scan(/(.*?)[ \t]*(?:\#.*)?$/)
2098
+ #str.rstrip!
2099
+ val = to_scalar(group(1))
2100
+ end
2101
+ val = create_scalar(rule, val, _linenum, _column) #*V
2102
+ #_set_error_info(_linenum, _column) do #*V
2103
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
2104
+ #end if uniq_table #*V
2105
+ skip_spaces_and_comments()
2106
+ return val
2107
+ end
2108
+
2109
+
2110
+ def parse_block_text(column, rule, path, uniq_table)
2111
+ _linenum = @linenum #*V
2112
+ _column = @column #*V
2113
+ indicator = scan(/[|>]/)
2114
+ chomping = scan(/[-+]/)
2115
+ num = scan(/\d+/)
2116
+ indent = num ? column + num.to_i - 1 : nil
2117
+ unless scan(/[ \t]*(.*?)(\#.*)?\r?\n/) # /[ \t]*(\#.*)?\r?\n/
2118
+ raise _syntax_error("Syntax Error (line break or comment are expected)", path)
2119
+ end
2120
+ s = group(1)
2121
+ is_folded = false
2122
+ while match?(/( *)(.*?)(\r?\n)/)
2123
+ spaces = group(1)
2124
+ text = group(2)
2125
+ nl = group(3)
2126
+ if indent.nil?
2127
+ if spaces.length >= column
2128
+ indent = spaces.length
2129
+ elsif text.empty?
2130
+ s << nl
2131
+ scan(/.*?\n/)
2132
+ next
2133
+ else
2134
+ @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.'
2135
+ break
2136
+ end
2137
+ else
2138
+ if spaces.length < indent && !text.empty?
2139
+ @diagnostic = 'text indent in block text may be shorter than that of first line or specified column.'
2140
+ break
2141
+ end
2142
+ end
2143
+ scan(/.*?\n/)
2144
+ if indicator == '|'
2145
+ s << spaces[indent..-1] if spaces.length >= indent
2146
+ s << text << nl
2147
+ else # indicator == '>'
2148
+ if !text.empty? && spaces.length == indent
2149
+ if s.sub!(/\r?\n((\r?\n)+)\z/, '\1')
2150
+ nil
2151
+ elsif is_folded
2152
+ s.sub!(/\r?\n\z/, ' ')
2153
+ end
2154
+ #s.sub!(/\r?\n\z/, '') if !s.sub!(/\r?\n(\r?\n)+\z/, '\1') && is_folded
2155
+ is_folded = true
2156
+ else
2157
+ is_folded = false
2158
+ s << spaces[indent..-1] if spaces.length > indent
2159
+ end
2160
+ s << text << nl
2161
+ end
2162
+ end
2163
+ ## chomping
2164
+ if chomping == '+'
2165
+ nil
2166
+ elsif chomping == '-'
2167
+ s.sub!(/(\r?\n)+\z/, '')
2168
+ else
2169
+ s.sub!(/(\r?\n)(\r?\n)+\z/, '\1')
2170
+ end
2171
+ #
2172
+ skip_spaces_and_comments()
2173
+ val = s
2174
+ #_set_error_info(_linenum, _column) do #*V
2175
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
2176
+ #end if uniq_table #*V
2177
+ return val
2178
+ end
2179
+
2180
+
2181
+ def parse_flow_value(rule, path, uniq_table, container)
2182
+ skip_spaces_and_comments()
2183
+ ## anchor and alias
2184
+ name = nil
2185
+ if scan(/\&([-\w]+)/)
2186
+ name = parse_anchor(rule, path, uniq_table, container)
2187
+ elsif scan(/\*([-\w]+)/)
2188
+ return parse_alias(rule, path, uniq_table, container)
2189
+ end
2190
+ ## type
2191
+ if scan(/!!?\w+/)
2192
+ skip_spaces_and_comments()
2193
+ end
2194
+ ## sequence
2195
+ if match?(/\[/)
2196
+ if rule && !rule.sequence #*V
2197
+ #_validate_error("sequence is not expected.", path) #*V
2198
+ rule = nil #*V
2199
+ end #*V
2200
+ seq = create_sequence(rule, @linenum, @column)
2201
+ @anchors[name] = seq if name
2202
+ parse_flow_seq(seq, rule, path, uniq_table)
2203
+ return seq
2204
+ end
2205
+ ## mapping
2206
+ if match?(/\{/)
2207
+ if rule && !rule.mapping #*V
2208
+ #_validate_error("mapping is not expected.", path) #*V
2209
+ rule = nil #*V
2210
+ end #*V
2211
+ map = create_mapping(rule, @linenum, @column)
2212
+ @anchors[name] = map if name
2213
+ parse_flow_map(map, rule, path, uniq_table)
2214
+ return map
2215
+ end
2216
+ ## scalar
2217
+ scalar = parse_flow_scalar(rule, path, uniq_table)
2218
+ @anchors[name] = scalar if name
2219
+ return scalar
2220
+ end
2221
+
2222
+
2223
+ def parse_flow_seq(seq, seq_rule, path, uniq_table)
2224
+ #scan(/\[\s*/)
2225
+ scan(/\[/)
2226
+ skip_spaces_and_comments()
2227
+ if scan(/\]/)
2228
+ nil
2229
+ else
2230
+ rule = seq_rule ? seq_rule.sequence[0] : nil #*V
2231
+ uniq_table = rule ? rule._uniqueness_check_table() : nil #*V
2232
+ path.push(nil)
2233
+ i = 0
2234
+ while true
2235
+ path[-1] = i
2236
+ _linenum = @linenum #*V
2237
+ _column = @column #*V
2238
+ val = parse_flow_value(rule, path, uniq_table, seq)
2239
+ add_to_seq(rule, seq, val, _linenum, _column) # seq << val
2240
+ _set_error_info(_linenum, _column) do #*V
2241
+ @validator._validate(val, rule, path, @errors, @done, uniq_table, false) #*V
2242
+ end if rule && !val.equal?(PRECEDING_ALIAS_PLACEHOLDER) #*V
2243
+ skip_spaces_and_comments()
2244
+ break unless scan(/,\s+/)
2245
+ i += 1
2246
+ if match?(/\]/)
2247
+ raise _syntax_error("sequence item required (or last comma is extra).", path)
2248
+ end
2249
+ end
2250
+ path.pop()
2251
+ unless scan(/\]/)
2252
+ raise _syntax_error("flow sequence is not closed by ']'.", path)
2253
+ end
2254
+ end
2255
+ skip_spaces_and_comments()
2256
+ return seq
2257
+ end
2258
+
2259
+
2260
+ def parse_flow_map(map, map_rule, path, uniq_table)
2261
+ #scan(/\{\s*/) # not work?
2262
+ _start_linenum = @linenum #*V
2263
+ _start_column = @column #*V
2264
+ scan(/\{/)
2265
+ skip_spaces_and_comments()
2266
+ if scan(/\}/)
2267
+ nil
2268
+ else
2269
+ path.push(nil)
2270
+ is_merged = false
2271
+ while true
2272
+ _linenum = @linenum #*V
2273
+ _column = @column #*V
2274
+ unless scan(MAPKEY_PATTERN)
2275
+ raise _syntax_error("mapping key is expected.", path)
2276
+ end
2277
+ key = group(1)
2278
+ skip_spaces_and_comments()
2279
+ _linenum2 = @linenum #*V
2280
+ _column2 = @column #*V
2281
+ is_merged = _parse_map_value(map, map_rule, path, nil, key, is_merged,
2282
+ uniq_table, _linenum, _column, _linenum2, _column2)
2283
+ #skip_spaces_and_comments()
2284
+ break unless scan(/,\s+/)
2285
+ end
2286
+ path.pop()
2287
+ unless scan(/\}/)
2288
+ raise _syntax_error("flow mapping is not closed by '}'.", path)
2289
+ end
2290
+ end
2291
+ skip_spaces_and_comments()
2292
+ _set_error_info(_start_linenum, _start_column) do #*V
2293
+ @validator._validate_mapping_required_keys(map, map_rule, path, @errors) #*V
2294
+ end if map_rule #*V
2295
+ return map
2296
+ end
2297
+
2298
+
2299
+ def parse_flow_scalar(rule, path, uniq_table)
2300
+ ch = peep(1)
2301
+ _linenum = @linenum #*V
2302
+ _column = @column #*V
2303
+ if ch == '"' || ch == "'"
2304
+ val = scan_string()
2305
+ else
2306
+ str = scan(/[^,\]\}\#]*/)
2307
+ if match?(/,\S/)
2308
+ while match?(/,\S/)
2309
+ str << scan(/./)
2310
+ str << scan(/[^,\]\}\#]*/)
2311
+ end
2312
+ end
2313
+ str.rstrip!
2314
+ val = to_scalar(str)
2315
+ end
2316
+ val = create_scalar(rule, val, _linenum, _column) #*V
2317
+ #_set_error_info(_linenum, _column) do #*V
2318
+ # @validator._validate_unique(val, rule, path, @errors, uniq_table) #*V
2319
+ #end if uniq_table #*V
2320
+ skip_spaces_and_comments()
2321
+ return val
2322
+ end
2323
+
2324
+
2325
+ ####
2326
+
2327
+
2328
+ def to_scalar(str)
2329
+ case str
2330
+ when nil ; val = nil
2331
+ when /\A-?\d+\.\d+\z/ ; val = str.to_f
2332
+ when /\A-?\d+\z/ ; val = str.to_i
2333
+ when /\A(true|yes)\z/ ; val = true
2334
+ when /\A(false|no)\z/ ; val = false
2335
+ when /\A(null|~)\z/ ; val = nil
2336
+ when /\A"(.*)"\z/ ; val = $1
2337
+ when /\A'(.*)'\z/ ; val = $1
2338
+ when /\A:(\w+)\z/ ; val = $1.intern
2339
+ when /\A(\d\d\d\d)-(\d\d)-(\d\d)(?: (\d\d):(\d\d):(\d\d))?\z/
2340
+ year, month, day, hour, min, sec = $1, $2, $3, $4, $5, $6
2341
+ if hour
2342
+ val = Time.mktime(year, month, day, hour, min, sec)
2343
+ else
2344
+ val = Date.new(year.to_i, month.to_i, day.to_i)
2345
+ end
2346
+ ## or
2347
+ #params = [$1, $2, $3, $4, $5, $6]
2348
+ #val = Time.mktime(*params)
2349
+ else
2350
+ val = str.empty? ? nil : str
2351
+ end
2352
+ skip_spaces_and_comments()
2353
+ return val
2354
+ end
2355
+
2356
+
2357
+ ##
2358
+
2359
+ protected
2360
+
2361
+
2362
+ def create_sequence(rule, linenum, column)
2363
+ seq = @sequence_class.new
2364
+ @location_table[seq.__id__] = []
2365
+ return seq
2366
+ end
2367
+
2368
+
2369
+ def create_mapping(rule, linenum, column)
2370
+ if rule && rule.classobj && @data_binding
2371
+ classobj = rule.classobj
2372
+ map = classobj.new
2373
+ else
2374
+ classobj = nil
2375
+ map = @mapping_class.new # {}
2376
+ end
2377
+ @location_table[map.__id__] = hash = {}
2378
+ hash[:classobj] = classobj if classobj
2379
+ return map
2380
+ end
2381
+
2382
+
2383
+ def create_scalar(rule, value, linenum, column)
2384
+ return value
2385
+ end
2386
+
2387
+
2388
+ def add_to_seq(rule, seq, val, linenum, column)
2389
+ seq << val
2390
+ @location_table[seq.__id__] << [linenum, column]
2391
+ end
2392
+
2393
+
2394
+ def put_to_map(rule, map, key, val, linenum, column)
2395
+ #if map.is_a?(Hash)
2396
+ # map[key] = val
2397
+ #elsif map.respond_to?(name="#{key}=")
2398
+ # map.__send__(name, val)
2399
+ #elsif map.respond_to?('[]=')
2400
+ # map[key] = val
2401
+ #else
2402
+ # map.instance_variable_set("@#{key}", val)
2403
+ #end
2404
+ map[key] = val
2405
+ @location_table[map.__id__][key] = [linenum, column]
2406
+ end
2407
+
2408
+
2409
+ def _getclass(classname)
2410
+ mod = Object
2411
+ classname.split(/::/).each do |modname|
2412
+ mod = mod.const_get(modname) # raises NameError when module not found
2413
+ end
2414
+ return mod
2415
+ end
2416
+
2417
+
2418
+ public
2419
+
2420
+
2421
+ def location(path)
2422
+ if path.empty? || path == '/'
2423
+ return @location_table[-1] # return value is [linenum, column]
2424
+ end
2425
+ if path.is_a?(Array)
2426
+ items = path.collect { |item| to_scalar(item) }
2427
+ elsif path.is_a?(String)
2428
+ items = path.split('/').collect { |item| to_scalar(item) }
2429
+ items.shift if path[0] == ?/ # delete empty string on head
2430
+ else
2431
+ raise ArgumentError.new("path should be Array or String.")
2432
+ end
2433
+ last_item = items.pop()
2434
+ c = @doc # collection
2435
+ items.each do |item|
2436
+ if c.is_a?(Array)
2437
+ c = c[item.to_i]
2438
+ elsif c.is_a?(Hash)
2439
+ c = c[item]
2440
+ elsif (table = @location_table[c.__id__]) && table[:classobj]
2441
+ if c.respond_to?(item)
2442
+ c = c.__send__(item)
2443
+ elsif c.respond_to?("[]=")
2444
+ c = c[item]
2445
+ else
2446
+ assert false
2447
+ end
2448
+ else
2449
+ #assert false
2450
+ raise ArgumentError.new("#{path.inspect}: invalid path.")
2451
+ end
2452
+ end
2453
+ collection = @location_table[c.__id__]
2454
+ return nil if collection.nil?
2455
+ index = c.is_a?(Array) ? last_item.to_i : last_item
2456
+ return collection[index] # return value is [linenum, column]
2457
+ end
2458
+
2459
+
2460
+ def set_errors_linenum(errors)
2461
+ errors.each do |error|
2462
+ error.linenum, error.column = location(error.path)
2463
+ end
2464
+ end
2465
+
2466
+
2467
+ end
2468
+ #--end of require 'kwalify/parser/yaml'
2469
+ #require 'yaml'
2470
+
2471
+ module Kwalify
2472
+
2473
+
2474
+ ##
2475
+ ## ex.
2476
+ ## meta_validator = Kwalify::MetaValidator.instance()
2477
+ ## schema = File.load_file('schema.yaml')
2478
+ ## errors = meta_validator.validate(schema)
2479
+ ## if !errors.empty?
2480
+ ## errors.each do |error|
2481
+ ## puts "[#{error.path}] #{error.message}"
2482
+ ## end
2483
+ ## end
2484
+ ##
2485
+ class MetaValidator < Validator
2486
+
2487
+ filename = File.join(File.dirname(__FILE__), 'kwalify.schema.yaml')
2488
+ META_SCHEMA = File.read(filename)
2489
+
2490
+ def self.instance()
2491
+ unless @instance
2492
+ schema = Kwalify::Yaml::Parser.new().parse(META_SCHEMA)
2493
+ @instance = MetaValidator.new(schema)
2494
+ end
2495
+ return @instance
2496
+ end
2497
+
2498
+ def initialize(schema, &block)
2499
+ super
2500
+ end
2501
+
2502
+ def validate_hook(value, rule, path, errors)
2503
+ return if value.nil? ## realy?
2504
+ return unless rule.name == "MAIN"
2505
+ #
2506
+ hash = value
2507
+ type = hash['type']
2508
+ type = Types::DEFAULT_TYPE if type.nil?
2509
+ klass = Types.type_class(type)
2510
+ #unless klass
2511
+ # errors << validate_error(:type_unknown, rule, "#{path}/type", type)
2512
+ #end
2513
+ #
2514
+ if hash.key?('class')
2515
+ val = hash['class']
2516
+ unless val.nil? || type == 'map'
2517
+ errors << validate_error(:class_notmap, rule, "#{path}/class", 'class:')
2518
+ end
2519
+ end
2520
+ #
2521
+ if hash.key?('pattern')
2522
+ val = hash['pattern']
2523
+ pat = (val =~ /\A\/(.*)\/([mi]?[mi]?)\z/ ? $1 : val)
2524
+ begin
2525
+ Regexp.compile(pat)
2526
+ rescue RegexpError => ex
2527
+ errors << validate_error(:pattern_syntaxerr, rule, "#{path}/pattern", val)
2528
+ end
2529
+ end
2530
+ #
2531
+ if hash.key?('enum')
2532
+ if Types.collection_type?(type)
2533
+ errors << validate_error(:enum_notscalar, rule, path, 'enum:')
2534
+ else
2535
+ #elem_table = {}
2536
+ hash['enum'].each do |elem|
2537
+ #if elem_table[elem]
2538
+ # errors << validate_error(:enum_duplicate, rule, "#{path}/enum", elem.to_s)
2539
+ #end
2540
+ #elem_table[elem] = true
2541
+ unless elem.is_a?(klass)
2542
+ errors << validate_error(:enum_type_unmatch, rule, "#{path}/enum", elem, [Kwalify.word(type)])
2543
+ end
2544
+ end
2545
+ end
2546
+ end
2547
+ #
2548
+ if hash.key?('assert')
2549
+ val = hash['assert']
2550
+ #val =~ /\bval\b/ or errors << validate_error(:assert_noval, rule, "#{path}/assert", val)
2551
+ begin
2552
+ eval "proc { |val| #{val} }"
2553
+ rescue ::SyntaxError => ex
2554
+ errors << validate_error(:assert_syntaxerr, rule, "#{path}/assert", val)
2555
+ end
2556
+ end
2557
+ #
2558
+ if hash.key?('range')
2559
+ val = hash['range']
2560
+ curr_path = path + "/range"
2561
+ #if ! val.is_a?(Hash)
2562
+ # errors << validate_error(:range_notmap, rule, curr_path, val)
2563
+ #elsif ...
2564
+ if Types.collection_type?(type) || type == 'bool' || type == 'any'
2565
+ errors << validate_error(:range_notscalar, rule, path, 'range:')
2566
+ else
2567
+ val.each do |rkey, rval|
2568
+ #case rkey
2569
+ #when 'max', 'min', 'max-ex', 'min-ex'
2570
+ unless rval.is_a?(klass)
2571
+ typename = Kwalify.word(type) || type
2572
+ errors << validate_error(:range_type_unmatch, rule, "#{curr_path}/#{rkey}", rval, [typename])
2573
+ end
2574
+ #else
2575
+ # errors << validate_error(:range_undefined, rule, curr_path, "#{rkey}:")
2576
+ #end
2577
+ end
2578
+ end
2579
+ if val.key?('max') && val.key?('max-ex')
2580
+ errors << validate_error(:range_twomax, rule, curr_path, nil)
2581
+ end
2582
+ if val.key?('min') && val.key?('min-ex')
2583
+ errors << validate_error(:range_twomin, rule, curr_path, nil)
2584
+ end
2585
+ max, min, max_ex, min_ex = val['max'], val['min'], val['max-ex'], val['min-ex']
2586
+ if max
2587
+ if min && max < min
2588
+ errors << validate_error(:range_maxltmin, rule, curr_path, nil, [max, min])
2589
+ elsif min_ex && max <= min_ex
2590
+ errors << validate_error(:range_maxleminex, rule, curr_path, nil, [max, min_ex])
2591
+ end
2592
+ elsif max_ex
2593
+ if min && max_ex <= min
2594
+ errors << validate_error(:range_maxexlemin, rule, curr_path, nil, [max_ex, min])
2595
+ elsif min_ex && max_ex <= min_ex
2596
+ errors << validate_error(:range_maxexleminex, rule, curr_path, nil, [max_ex, min_ex])
2597
+ end
2598
+ end
2599
+ end
2600
+ #
2601
+ if hash.key?('length')
2602
+ val = hash['length']
2603
+ curr_path = path + "/length"
2604
+ #val.is_a?(Hash) or errors << validate_error(:length_notmap, rule, curr_path, val)
2605
+ unless type == 'str' || type == 'text'
2606
+ errors << validate_error(:length_nottext, rule, path, 'length:')
2607
+ end
2608
+ #val.each do |lkey, lval|
2609
+ # #case lkey
2610
+ # #when 'max', 'min', 'max-ex', 'min-ex'
2611
+ # unless lval.is_a?(Integer)
2612
+ # errors << validate_error(:length_notint, rule, "#{curr_path}/#{lkey}", lval)
2613
+ # end
2614
+ # #else
2615
+ # # errors << validate_error(:length_undefined, rule, curr_path, "#{lkey}:")
2616
+ # #end
2617
+ #end
2618
+ if val.key?('max') && val.key?('max-ex')
2619
+ errors << validate_error(:length_twomax, rule, curr_path, nil)
2620
+ end
2621
+ if val.key?('min') && val.key?('min-ex')
2622
+ errors << validate_error(:length_twomin, rule, curr_path, nil)
2623
+ end
2624
+ max, min, max_ex, min_ex = val['max'], val['min'], val['max-ex'], val['min-ex']
2625
+ if max
2626
+ if min && max < min
2627
+ errors << validate_error(:length_maxltmin, rule, curr_path, nil, [max, min])
2628
+ elsif min_ex && max <= min_ex
2629
+ errors << validate_error(:length_maxleminex, rule, curr_path, nil, [max, min_ex])
2630
+ end
2631
+ elsif max_ex
2632
+ if min && max_ex <= min
2633
+ errors << validate_error(:length_maxexlemin, rule, curr_path, nil, [max_ex, min])
2634
+ elsif min_ex && max_ex <= min_ex
2635
+ errors << validate_error(:length_maxexleminex, rule, curr_path, nil, [max_ex, min_ex])
2636
+ end
2637
+ end
2638
+ end
2639
+ #
2640
+ if hash.key?('unique')
2641
+ if hash['unique'] && Types.collection_type?(type)
2642
+ errors << validate_error(:unique_notscalar, rule, path, "unique:")
2643
+ end
2644
+ if path.empty?
2645
+ errors << validate_error(:unique_onroot, rule, "/", "unique:")
2646
+ end
2647
+ end
2648
+ #
2649
+ if hash.key?('ident')
2650
+ if hash['ident'] && Types.collection_type?(type)
2651
+ errors << validate_error(:ident_notscalar, rule, path, "ident:")
2652
+ end
2653
+ if path.empty?
2654
+ errors << validate_error(:ident_onroot, rule, "/", "ident:")
2655
+ end
2656
+ end
2657
+ #
2658
+ if hash.key?('default')
2659
+ val = hash['default']
2660
+ if Types.collection_type?(type)
2661
+ errors << validate_error(:default_notscalar, rule, path, "default:")
2662
+ elsif !val.nil? && !val.is_a?(klass)
2663
+ errors << validate_error(:default_unmatch, rule, "#{path}/default", val, [Kwalify.word(type)])
2664
+ end
2665
+ end
2666
+ #
2667
+ if hash.key?('sequence')
2668
+ val = hash['sequence']
2669
+ #if !val.nil? && !val.is_a?(Array)
2670
+ # errors << validate_error(:sequence_notseq, rule, "#{path}/sequence", val)
2671
+ #elsif ...
2672
+ if val.nil? || val.empty?
2673
+ errors << validate_error(:sequence_noelem, rule, "#{path}/sequence", val)
2674
+ elsif val.length > 1
2675
+ errors << validate_error(:sequence_toomany, rule, "#{path}/sequence", val)
2676
+ else
2677
+ elem = val[0]
2678
+ assert_error("elem.class=#{elem.class}") unless elem.is_a?(Hash)
2679
+ if elem['ident'] && elem['type'] != 'map'
2680
+ errors << validate_error(:ident_notmap, nil, "#{path}/sequence/0", 'ident:')
2681
+ end
2682
+ end
2683
+ end
2684
+ #
2685
+ if hash.key?('mapping')
2686
+ val = hash['mapping']
2687
+ if !val.nil? && !val.is_a?(Hash)
2688
+ errors << validate_error(:mapping_notmap, rule, "#{path}/mapping", val)
2689
+ elsif val.nil? || (val.empty? && !val.default)
2690
+ errors << validate_error(:mapping_noelem, rule, "#{path}/mapping", val)
2691
+ end
2692
+ end
2693
+ #
2694
+ if type == 'seq'
2695
+ errors << validate_error(:seq_nosequence, rule, path, nil) unless hash.key?('sequence')
2696
+ #errors << validate_error(:seq_conflict, rule, path, 'enum:') if hash.key?('enum')
2697
+ errors << validate_error(:seq_conflict, rule, path, 'pattern:') if hash.key?('pattern')
2698
+ errors << validate_error(:seq_conflict, rule, path, 'mapping:') if hash.key?('mapping')
2699
+ #errors << validate_error(:seq_conflict, rule, path, 'range:') if hash.key?('range')
2700
+ #errors << validate_error(:seq_conflict, rule, path, 'length:') if hash.key?('length')
2701
+ elsif type == 'map'
2702
+ errors << validate_error(:map_nomapping, rule, path, nil) unless hash.key?('mapping')
2703
+ #errors << validate_error(:map_conflict, rule, path, 'enum:') if hash.key?('enum')
2704
+ errors << validate_error(:map_conflict, rule, path, 'pattern:') if hash.key?('pattern')
2705
+ errors << validate_error(:map_conflict, rule, path, 'sequence:') if hash.key?('sequence')
2706
+ #errors << validate_error(:map_conflict, rule, path, 'range:') if hash.key?('range')
2707
+ #errors << validate_error(:map_conflict, rule, path, 'length:') if hash.key?('length')
2708
+ else
2709
+ errors << validate_error(:scalar_conflict, rule, path, 'sequence:') if hash.key?('sequence')
2710
+ errors << validate_error(:scalar_conflict, rule, path, 'mapping:') if hash.key?('mapping')
2711
+ if hash.key?('enum')
2712
+ errors << validate_error(:enum_conflict, rule, path, 'range:') if hash.key?('range')
2713
+ errors << validate_error(:enum_conflict, rule, path, 'length:') if hash.key?('length')
2714
+ errors << validate_error(:enum_conflict, rule, path, 'pattern:') if hash.key?('pattern')
2715
+ end
2716
+ if hash.key?('default')
2717
+ errors << validate_error(:default_conflict, rule, path, 'default:') if hash['required']
2718
+ end
2719
+ end
2720
+
2721
+ end # end of def validate_hook()
2722
+
2723
+
2724
+ end # end of class MetaValidator
2725
+
2726
+
2727
+ META_VALIDATOR = MetaValidator.instance()
2728
+
2729
+ def self.meta_validator # obsolete
2730
+ return META_VALIDATOR
2731
+ end
2732
+
2733
+ end
2734
+ #--end of require 'kwalify/meta-validator'
2735
+ #--begin of require 'kwalify/yaml-parser'
2736
+ ###
2737
+ ### $Rev: 81 $
2738
+ ### $Release: 0.7.1 $
2739
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
2740
+ ###
2741
+
2742
+ #--already included require 'kwalify/messages'
2743
+ #--already included require 'kwalify/errors'
2744
+ #--already included require 'kwalify/types'
2745
+
2746
+ require 'date'
2747
+
2748
+
2749
+ module Kwalify
2750
+
2751
+ ##
2752
+ ## base class of yaml parser
2753
+ ##
2754
+ ## ex.
2755
+ ## str = ARGF.read()
2756
+ ## parser = Kwalify::PlainYamlParser.new(str)
2757
+ ## doc = parser.parse()
2758
+ ## p doc
2759
+ ##
2760
+ class PlainYamlParser
2761
+
2762
+ class Alias
2763
+ def initialize(label, linenum)
2764
+ @label = label
2765
+ @linenum = linenum
2766
+ end
2767
+ attr_reader :label, :linenum
2768
+ end
2769
+
2770
+
2771
+ def initialize(yaml_str)
2772
+ @lines = yaml_str.to_a()
2773
+ @line = nil
2774
+ @linenum = 0
2775
+ @anchors = {}
2776
+ @aliases = {}
2777
+ end
2778
+
2779
+
2780
+ def parse()
2781
+ data = parse_child(0)
2782
+ if data.nil? && @end_flag == '---'
2783
+ data = parse_child(0)
2784
+ end
2785
+ resolve_aliases(data) unless @aliases.empty?
2786
+ return data
2787
+ end
2788
+
2789
+
2790
+ def has_next?
2791
+ return @end_flag != 'EOF'
2792
+ end
2793
+
2794
+
2795
+ def parse_all
2796
+ list = []
2797
+ while has_next()
2798
+ doc = parse()
2799
+ list << doc
2800
+ end
2801
+ return list
2802
+ end
2803
+
2804
+
2805
+ protected
2806
+
2807
+
2808
+ def create_sequence(linenum=nil)
2809
+ return []
2810
+ end
2811
+
2812
+ def add_to_seq(seq, value, linenum)
2813
+ seq << value
2814
+ end
2815
+
2816
+ def set_seq_at(seq, i, value, linenum)
2817
+ seq[i] = value
2818
+ end
2819
+
2820
+ def create_mapping(linenum=nil)
2821
+ return {}
2822
+ end
2823
+
2824
+ def add_to_map(map, key, value, linenum)
2825
+ map[key] = value
2826
+ end
2827
+
2828
+ def set_map_with(map, key, value, linenum)
2829
+ map[key] = value
2830
+ end
2831
+
2832
+ def set_default(map, value, linenum)
2833
+ map.value = value
2834
+ end
2835
+
2836
+ def merge_map(map, map2, linenum)
2837
+ map2.each do |key, val|
2838
+ map[key] = value unless map.key?(key)
2839
+ end
2840
+ end
2841
+
2842
+ def create_scalar(value, linenum=nil)
2843
+ return value
2844
+ end
2845
+
2846
+
2847
+ def current_line
2848
+ return @line
2849
+ end
2850
+
2851
+ def current_linenum
2852
+ return @linenum
2853
+ end
2854
+
2855
+
2856
+ private
2857
+
2858
+
2859
+ def getline
2860
+ line = _getline()
2861
+ line = _getline() while line && line =~ /^\s*($|\#)/
2862
+ return line
2863
+ end
2864
+
2865
+ def _getline
2866
+ @line = @lines[@linenum]
2867
+ @linenum += 1
2868
+ case @line
2869
+ when nil ; @end_flag = 'EOF'
2870
+ when /^\.\.\.$/ ; @end_flag = '...'; @line = nil
2871
+ when /^---(\s+.*)?$/ ; @end_flag = '---'; @line = nil
2872
+ else ; @end_flag = nil
2873
+ end
2874
+ return @line
2875
+ end
2876
+
2877
+
2878
+ def reset_sbuf(str)
2879
+ @sbuf = str[-1] == ?\n ? str : str + "\n"
2880
+ @index = -1
2881
+ end
2882
+
2883
+
2884
+ def _getchar
2885
+ @index += 1
2886
+ ch = @sbuf[@index]
2887
+ while ch.nil?
2888
+ break if (line = getline()).nil?
2889
+ reset_sbuf(line)
2890
+ @index += 1
2891
+ ch = @sbuf[@index]
2892
+ end
2893
+ return ch
2894
+ end
2895
+
2896
+ def getchar
2897
+ ch = _getchar()
2898
+ ch = _getchar() while ch && white?(ch)
2899
+ return ch
2900
+ end
2901
+
2902
+ def getchar_or_nl
2903
+ ch = _getchar()
2904
+ ch = _getchar() while ch && white?(ch) && ch != ?\n
2905
+ return ch
2906
+ end
2907
+
2908
+ def current_char
2909
+ return @sbuf[@index]
2910
+ end
2911
+
2912
+ def getlabel
2913
+ if @sbuf[@index..-1] =~ /\A\w[-\w]*/
2914
+ label = $&
2915
+ @index += label.length
2916
+ else
2917
+ label = nil
2918
+ end
2919
+ return label
2920
+ end
2921
+
2922
+ #--
2923
+ #def syntax_error(error_symbol, linenum=@linenum)
2924
+ # msg = Kwalify.msg(error_symbol) % [linenum]
2925
+ # return Kwalify::YamlSyntaxError.new(msg, linenum,error_symbol)
2926
+ #end
2927
+ #++
2928
+ def syntax_error(error_symbol, arg=nil, linenum=@linenum)
2929
+ msg = Kwalify.msg(error_symbol)
2930
+ msg = msg % arg.to_a unless arg.nil?
2931
+ return Kwalify::YamlSyntaxError.new(msg, linenum, error_symbol)
2932
+ end
2933
+
2934
+ def parse_child(column)
2935
+ line = getline()
2936
+ return create_scalar(nil) if !line
2937
+ line =~ /^( *)(.*)/
2938
+ indent = $1.length
2939
+ return create_scalar(nil) if indent < column
2940
+ value = $2
2941
+ return parse_value(column, value, indent)
2942
+ end
2943
+
2944
+
2945
+ def parse_value(column, value, value_start_column)
2946
+ case value
2947
+ when /^-( |$)/
2948
+ data = parse_sequence(value_start_column, value)
2949
+ when /^(:?:?[-.\w]+\*?|'.*?'|".*?"|=|<<) *:( |$)/
2950
+ #when /^:?["']?[-.\w]+["']? *:( |$)/ #'
2951
+ data = parse_mapping(value_start_column, value)
2952
+ when /^\[/, /^\{/
2953
+ data = parse_flowstyle(column, value)
2954
+ when /^\&[-\w]+( |$)/
2955
+ data = parse_anchor(column, value)
2956
+ when /^\*[-\w]+( |$)/
2957
+ data = parse_alias(column, value)
2958
+ when /^[|>]/
2959
+ data = parse_block_text(column, value)
2960
+ when /^!/
2961
+ data = parse_tag(column, value)
2962
+ when /^\#/
2963
+ data = parse_child(column)
2964
+ else
2965
+ data = parse_scalar(column, value)
2966
+ end
2967
+ return data
2968
+ end
2969
+
2970
+ def white?(ch)
2971
+ return ch == ?\ || ch == ?\t || ch == ?\n || ch == ?\r
2972
+ end
2973
+
2974
+
2975
+ ##
2976
+ ## flowstyle ::= flow_seq | flow_map | flow_scalar | alias
2977
+ ##
2978
+ ## flow_seq ::= '[' [ flow_seq_item { ',' sp flow_seq_item } ] ']'
2979
+ ## flow_seq_item ::= flowstyle
2980
+ ##
2981
+ ## flow_map ::= '{' [ flow_map_item { ',' sp flow_map_item } ] '}'
2982
+ ## flow_map_item ::= flowstyle ':' sp flowstyle
2983
+ ##
2984
+ ## flow_scalar ::= string | number | boolean | symbol | date
2985
+ ##
2986
+
2987
+ def parse_flowstyle(column, value)
2988
+ reset_sbuf(value)
2989
+ getchar()
2990
+ data = parse_flow(0)
2991
+ ch = current_char
2992
+ assert ch == ?] || ch == ?}
2993
+ ch = getchar_or_nl()
2994
+ unless ch == ?\n || ch == ?# || ch.nil?
2995
+ #* key=:flow_hastail msg="flow style sequence is closed but got '%s'."
2996
+ raise syntax_error(:flow_hastail, [ch.chr])
2997
+ end
2998
+ getline() if !ch.nil?
2999
+ return data
3000
+ end
3001
+
3002
+ def parse_flow(depth)
3003
+ ch = current_char()
3004
+ #ch = getchar()
3005
+ if ch.nil?
3006
+ #* key=:flow_eof msg="found EOF when parsing flow style."
3007
+ rase syntax_error(:flow_eof)
3008
+ end
3009
+ ## alias
3010
+ if ch == ?*
3011
+ _getchar()
3012
+ label = getlabel()
3013
+ unless label
3014
+ #* key=:flow_alias_label msg="alias name expected."
3015
+ rase syntax_error(:flow_alias_label)
3016
+ end
3017
+ data = @anchors[label]
3018
+ unless data
3019
+ data = register_alias(label)
3020
+ #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
3021
+ end
3022
+ return data
3023
+ end
3024
+ ## anchor
3025
+ label = nil
3026
+ if ch == ?&
3027
+ _getchar()
3028
+ label = getlabel()
3029
+ unless label
3030
+ #* key=:flow_anchor_label msg="anchor name expected."
3031
+ rase syntax_error(:flow_anchor_label)
3032
+ end
3033
+ ch = current_char()
3034
+ ch = getchar() if white?(ch)
3035
+ end
3036
+ ## flow data
3037
+ if ch == ?[
3038
+ data = parse_flow_seq(depth)
3039
+ elsif ch == ?{
3040
+ data = parse_flow_map(depth)
3041
+ else
3042
+ data = parse_flow_scalar(depth)
3043
+ end
3044
+ ## register anchor
3045
+ register_anchor(label, data) if label
3046
+ return data
3047
+ end
3048
+
3049
+ def parse_flow_seq(depth)
3050
+ assert current_char() == ?[
3051
+ seq = create_sequence() # []
3052
+ ch = getchar()
3053
+ if ch != ?}
3054
+ linenum = current_linenum()
3055
+ #seq << parse_flow_seq_item(depth + 1)
3056
+ add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
3057
+ while (ch = current_char()) == ?,
3058
+ ch = getchar()
3059
+ if ch == ?]
3060
+ #* key=:flow_noseqitem msg="sequence item required (or last comma is extra)."
3061
+ raise syntax_error(:flow_noseqitem)
3062
+ end
3063
+ #break if ch == ?]
3064
+ linenum = current_linenum()
3065
+ #seq << parse_flow_seq_item(depth + 1)
3066
+ add_to_seq(seq, parse_flow_seq_item(depth + 1), linenum)
3067
+ end
3068
+ end
3069
+ unless current_char() == ?]
3070
+ #* key=:flow_seqnotclosed msg="flow style sequence requires ']'."
3071
+ raise syntax_error(:flow_seqnotclosed)
3072
+ end
3073
+ getchar() if depth > 0
3074
+ return seq
3075
+ end
3076
+
3077
+ def parse_flow_seq_item(depth)
3078
+ return parse_flow(depth)
3079
+ end
3080
+
3081
+ def parse_flow_map(depth)
3082
+ assert current_char() == ?{ #}
3083
+ map = create_mapping() # {}
3084
+ ch = getchar()
3085
+ if ch != ?}
3086
+ linenum = current_linenum()
3087
+ key, value = parse_flow_map_item(depth + 1)
3088
+ #map[key] = value
3089
+ add_to_map(map, key, value, linenum)
3090
+ while (ch = current_char()) == ?,
3091
+ ch = getchar()
3092
+ if ch == ?}
3093
+ #* key=:flow_mapnoitem msg="mapping item required (or last comma is extra)."
3094
+ raise syntax_error(:flow_mapnoitem)
3095
+ end
3096
+ #break if ch == ?}
3097
+ linenum = current_linenum()
3098
+ key, value = parse_flow_map_item(depth + 1)
3099
+ #map[key] = value
3100
+ add_to_map(map, key, value, linenum)
3101
+ end
3102
+ end
3103
+ unless current_char() == ?}
3104
+ #* key=:flow_mapnotclosed msg="flow style mapping requires '}'."
3105
+ raise syntax_error(:flow_mapnotclosed)
3106
+ end
3107
+ getchar() if depth > 0
3108
+ return map
3109
+ end
3110
+
3111
+ def parse_flow_map_item(depth)
3112
+ key = parse_flow(depth)
3113
+ unless (ch = current_char()) == ?:
3114
+ $stderr.puts "*** debug: key=#{key.inspect}"
3115
+ s = ch ? "'#{ch.chr}'" : "EOF"
3116
+ #* key=:flow_nocolon msg="':' expected but got %s."
3117
+ raise syntax_error(:flow_nocolon, [s])
3118
+ end
3119
+ getchar()
3120
+ value = parse_flow(depth)
3121
+ return key, value
3122
+ end
3123
+
3124
+ def parse_flow_scalar(depth)
3125
+ case ch = current_char()
3126
+ when ?", ?' #"
3127
+ endch = ch
3128
+ s = ''
3129
+ while (ch = _getchar()) != nil && ch != endch
3130
+ if ch == ?\\
3131
+ ch = _getchar()
3132
+ if ch.nil?
3133
+ #* key=:flow_str_notclosed msg="%s: string not closed."
3134
+ raise syntax_error(:flow_str_notclosed, endch == ?" ? "'\"'" : '"\'"')
3135
+ end
3136
+ if endch == ?"
3137
+ case ch
3138
+ when ?\\ ; s << "\\"
3139
+ when ?" ; s << "\""
3140
+ when ?n ; s << "\n"
3141
+ when ?r ; s << "\r"
3142
+ when ?t ; s << "\t"
3143
+ when ?b ; s << "\b"
3144
+ else ; s << "\\" << ch.chr
3145
+ end
3146
+ elsif endch == ?'
3147
+ case ch
3148
+ when ?\\ ; s << '\\'
3149
+ when ?' ; s << '\''
3150
+ else ; s << '\\' << ch.chr
3151
+ end
3152
+ end
3153
+ else
3154
+ s << ch.chr
3155
+ end
3156
+ end
3157
+ getchar()
3158
+ scalar = s
3159
+ else
3160
+ s = ch.chr
3161
+ while (ch = _getchar()) != nil && ch != ?: && ch != ?, && ch != ?] && ch != ?}
3162
+ s << ch.chr
3163
+ end
3164
+ scalar = to_scalar(s.strip)
3165
+ end
3166
+ return create_scalar(scalar)
3167
+ end
3168
+
3169
+
3170
+ def parse_tag(column, value)
3171
+ assert value =~ /^!\S+/
3172
+ value =~ /^!(\S+)((\s+)(.*))?$/
3173
+ tag = $1
3174
+ space = $3
3175
+ value2 = $4
3176
+ if value2 && !value2.empty?
3177
+ value_start_column = column + 1 + tag.length + space.length
3178
+ data = parse_value(column, value2, value_start_column)
3179
+ else
3180
+ data = parse_child(column)
3181
+ end
3182
+ return data
3183
+ end
3184
+
3185
+
3186
+ def parse_anchor(column, value)
3187
+ assert value =~ /^\&([-\w]+)(( *)(.*))?$/
3188
+ label = $1
3189
+ space = $3
3190
+ value2 = $4
3191
+ if value2 && !value2.empty?
3192
+ #column2 = column + 1 + label.length + space.length
3193
+ #data = parse_value(column2, value2)
3194
+ value_start_column = column + 1 + label.length + space.length
3195
+ data = parse_value(column, value2, value_start_column)
3196
+ else
3197
+ #column2 = column + 1
3198
+ #data = parse_child(column2)
3199
+ data = parse_child(column)
3200
+ end
3201
+ register_anchor(label, data)
3202
+ return data
3203
+ end
3204
+
3205
+ def register_anchor(label, data)
3206
+ if @anchors[label]
3207
+ #* key=:anchor_duplicated msg="anchor '%s' is already used."
3208
+ raise syntax_error(:anchor_duplicated, [label])
3209
+ end
3210
+ @anchors[label] = data
3211
+ end
3212
+
3213
+ def parse_alias(column, value)
3214
+ assert value =~ /^\*([-\w]+)(( *)(.*))?$/
3215
+ label = $1
3216
+ space = $3
3217
+ value2 = $4
3218
+ if value2 && !value2.empty? && value2[0] != ?\#
3219
+ #* key=:alias_extradata msg="alias cannot take any data."
3220
+ raise syntax_error(:alias_extradata)
3221
+ end
3222
+ data = @anchors[label]
3223
+ unless data
3224
+ data = register_alias(label)
3225
+ #raise syntax_error("anchor '#{label}' not found (cannot refer to backward or child anchor).")
3226
+ end
3227
+ getline()
3228
+ return data
3229
+ end
3230
+
3231
+ def register_alias(label)
3232
+ @aliases[label] ||= 0
3233
+ @aliases[label] += 1
3234
+ return Alias.new(label, @linenum)
3235
+ end
3236
+
3237
+
3238
+ def resolve_aliases(data)
3239
+ @resolved ||= {}
3240
+ return if @resolved[data.__id__]
3241
+ @resolved[data.__id__] = data
3242
+ case data
3243
+ when Array
3244
+ seq = data
3245
+ seq.each_with_index do |val, i|
3246
+ if val.is_a?(Alias)
3247
+ anchor = val
3248
+ if @anchors.key?(anchor.label)
3249
+ #seq[i] = @anchors[anchor.label]
3250
+ set_seq_at(seq, i, @anchors[anchor.label], anchor.linenum)
3251
+ else
3252
+ #* key=:anchor_notfound msg="anchor '%s' not found"
3253
+ raise syntax_error(:anchor_notfound, [anchor.label], val.linenum)
3254
+ end
3255
+ elsif val.is_a?(Array) || val.is_a?(Hash)
3256
+ resolve_aliases(val)
3257
+ end
3258
+ end
3259
+ when Hash
3260
+ map = data
3261
+ map.each do |key, val|
3262
+ if val.is_a?(Alias)
3263
+ if @anchors.key?(val.label)
3264
+ anchor = val
3265
+ #map[key] = @anchors[anchor.label]
3266
+ set_map_with(map, key, @anchors[anchor.label], anchor.linenum)
3267
+ else
3268
+ ## :anchor_notfound is already defined on above
3269
+ raise syntax_error(:anchor_notfound, [val.label], val.linenum)
3270
+ end
3271
+ elsif val.is_a?(Array) || val.is_a?(Hash)
3272
+ resolve_aliases(val)
3273
+ end
3274
+ end
3275
+ else
3276
+ assert !data.is_a?(Alias)
3277
+ end
3278
+ end
3279
+
3280
+
3281
+ def parse_block_text(column, value)
3282
+ assert value =~ /^[>|\|]/
3283
+ value =~ /^([>|\|])([-+]?)(\d+)?\s*(.*)$/
3284
+ char = $1
3285
+ indicator = $2
3286
+ sep = char == "|" ? "\n" : " "
3287
+ margin = $3 && !$3.empty? ? $3.to_i : nil
3288
+ #text = $4.empty? ? '' : $4 + sep
3289
+ text = $4
3290
+ s = ''
3291
+ empty = ''
3292
+ min_indent = -1
3293
+ while line = _getline()
3294
+ line =~ /^( *)(.*)/
3295
+ indent = $1.length
3296
+ if $2.empty?
3297
+ empty << "\n"
3298
+ elsif indent < column
3299
+ break
3300
+ else
3301
+ min_indent = indent if min_indent < 0 || min_indent > indent
3302
+ s << empty << line
3303
+ empty = ''
3304
+ end
3305
+ end
3306
+ s << empty if indicator == '+' && char != '>'
3307
+ s[-1] = "" if indicator == '-'
3308
+ min_indent = column + margin - 1 if margin
3309
+ if min_indent > 0
3310
+ sp = ' ' * min_indent
3311
+ s.gsub!(/^#{sp}/, '')
3312
+ end
3313
+ if char == '>'
3314
+ s.gsub!(/([^\n])\n([^\n])/, '\1 \2')
3315
+ s.gsub!(/\n(\n+)/, '\1')
3316
+ s << empty if indicator == '+'
3317
+ end
3318
+ getline() if current_line() =~ /^\s*\#/
3319
+ return create_scalar(text + s)
3320
+ end
3321
+
3322
+
3323
+ def parse_sequence(column, value)
3324
+ assert value =~ /^-(( +)(.*))?$/
3325
+ seq = create_sequence() # []
3326
+ while true
3327
+ unless value =~ /^-(( +)(.*))?$/
3328
+ #* key=:sequence_noitem msg="sequence item is expected."
3329
+ raise syntax_error(:sequence_noitem)
3330
+ end
3331
+ value2 = $3
3332
+ space = $2
3333
+ column2 = column + 1
3334
+ linenum = current_linenum()
3335
+ #
3336
+ if !value2 || value2.empty?
3337
+ elem = parse_child(column2)
3338
+ else
3339
+ value_start_column = column2 + space.length
3340
+ elem = parse_value(column2, value2, value_start_column)
3341
+ end
3342
+ add_to_seq(seq, elem, linenum) #seq << elem
3343
+ #
3344
+ line = current_line()
3345
+ break unless line
3346
+ line =~ /^( *)(.*)/
3347
+ indent = $1.length
3348
+ if indent < column
3349
+ break
3350
+ elsif indent > column
3351
+ #* key=:sequence_badindent msg="illegal indent of sequence."
3352
+ raise syntax_error(:sequence_badindent)
3353
+ end
3354
+ value = $2
3355
+ end
3356
+ return seq
3357
+ end
3358
+
3359
+
3360
+ def parse_mapping(column, value)
3361
+ #assert value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/ #'
3362
+ assert value =~ /^((?::?[-.\w]+\*?|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
3363
+ map = create_mapping() # {}
3364
+ while true
3365
+ #unless value =~ /^(:?["']?[-.\w]+["']? *):(( +)(.*))?$/ #'
3366
+ unless value =~ /^((?::?[-.\w]+\*?|'.*?'|".*?"|=|<<) *):(( +)(.*))?$/
3367
+ #* key=:mapping_noitem msg="mapping item is expected."
3368
+ raise syntax_error(:mapping_noitem)
3369
+ end
3370
+ v = $1.strip
3371
+ key = to_scalar(v)
3372
+ value2 = $4
3373
+ column2 = column + 1
3374
+ linenum = current_linenum()
3375
+ #
3376
+ if !value2 || value2.empty?
3377
+ elem = parse_child(column2)
3378
+ else
3379
+ value_start_column = column2 + $1.length + $3.length
3380
+ elem = parse_value(column2, value2, value_start_column)
3381
+ end
3382
+ case v
3383
+ when '='
3384
+ set_default(map, elem, linenum)
3385
+ when '<<'
3386
+ merge_map(map, elem, linenum)
3387
+ else
3388
+ add_to_map(map, key, elem, linenum) # map[key] = elem
3389
+ end
3390
+ #
3391
+ line = current_line()
3392
+ break unless line
3393
+ line =~ /^( *)(.*)/
3394
+ indent = $1.length
3395
+ if indent < column
3396
+ break
3397
+ elsif indent > column
3398
+ #* key=:mapping_badindent msg="illegal indent of mapping."
3399
+ raise syntax_error(:mapping_badindent)
3400
+ end
3401
+ value = $2
3402
+ end
3403
+ return map
3404
+ end
3405
+
3406
+
3407
+ def parse_scalar(indent, value)
3408
+ data = create_scalar(to_scalar(value))
3409
+ getline()
3410
+ return data
3411
+ end
3412
+
3413
+
3414
+ def to_scalar(str)
3415
+ case str
3416
+ when /^"(.*)"([ \t]*\#.*$)?/ ; return $1
3417
+ when /^'(.*)'([ \t]*\#.*$)?/ ; return $1
3418
+ when /^(.*\S)[ \t]*\#/ ; str = $1
3419
+ end
3420
+
3421
+ case str
3422
+ when /^-?\d+$/ ; return str.to_i # integer
3423
+ when /^-?\d+\.\d+$/ ; return str.to_f # float
3424
+ when "true", "yes", "on" ; return true # true
3425
+ when "false", "no", "off" ; return false # false
3426
+ when "null", "~" ; return nil # nil
3427
+ #when /^"(.*)"$/ ; return $1 # "string"
3428
+ #when /^'(.*)'$/ ; return $1 # 'string'
3429
+ when /^:(\w+)$/ ; return $1.intern # :symbol
3430
+ when /^(\d\d\d\d)-(\d\d)-(\d\d)$/ # date
3431
+ year, month, day = $1.to_i, $2.to_i, $3.to_i
3432
+ return Date.new(year, month, day)
3433
+ when /^(\d\d\d\d)-(\d\d)-(\d\d)(?:[Tt]|[ \t]+)(\d\d?):(\d\d):(\d\d)(\.\d*)?(?:Z|[ \t]*([-+]\d\d?)(?::(\d\d))?)?$/
3434
+ year, mon, mday, hour, min, sec, usec, tzone_h, tzone_m = $1, $2, $3, $4, $5, $6, $7, $8, $9
3435
+ #Time.utc(sec, min, hour, mday, mon, year, wday, yday, isdst, zone)
3436
+ #t = Time.utc(sec, min, hour, mday, mon, year, nil, nil, nil, nil)
3437
+ #Time.utc(year[, mon[, day[, hour[, min[, sec[, usec]]]]]])
3438
+ time = Time.utc(year, mon, day, hour, min, sec, usec)
3439
+ if tzone_h
3440
+ diff_sec = tzone_h.to_i * 60 * 60
3441
+ if tzone_m
3442
+ if diff_sec > 0 ; diff_sec += tzone_m.to_i * 60
3443
+ else ; diff_sec -= tzone_m.to_i * 60
3444
+ end
3445
+ end
3446
+ p diff_sec
3447
+ time -= diff_sec
3448
+ end
3449
+ return time
3450
+ end
3451
+ return str
3452
+ end
3453
+
3454
+
3455
+ def assert(bool_expr)
3456
+ raise "*** assertion error" unless bool_expr
3457
+ end
3458
+
3459
+ end
3460
+
3461
+
3462
+
3463
+ ##
3464
+ ## (OBSOLETE) yaml parser
3465
+ ##
3466
+ ## this class has been obsoleted. use Kwalify::Yaml::Parser instead.
3467
+ ##
3468
+ ## ex.
3469
+ ## # load document with YamlParser
3470
+ ## str = ARGF.read()
3471
+ ## parser = Kwalify::YamlParser.new(str)
3472
+ ## document = parser.parse()
3473
+ ##
3474
+ ## # validate document
3475
+ ## schema = YAML.load(File.read('schema.yaml'))
3476
+ ## validator = Kwalify::Validator.new(schema)
3477
+ ## errors = validator.validate(document)
3478
+ ##
3479
+ ## # print validation result
3480
+ ## if errors && !errors.empty?
3481
+ ## parser.set_errors_linenum(errors)
3482
+ ## errors.sort.each do |error|
3483
+ ## print "line %d: path %s: %s" % [error.linenum, error.path, error.message]
3484
+ ## end
3485
+ ## end
3486
+ ##
3487
+ class YamlParser < PlainYamlParser
3488
+
3489
+ def initialize(*args)
3490
+ super
3491
+ @linenums_table = {} # object_id -> hash or array
3492
+ end
3493
+
3494
+ def parse()
3495
+ @doc = super()
3496
+ return @doc
3497
+ end
3498
+
3499
+ def path_linenum(path)
3500
+ return 1 if path.empty? || path == '/'
3501
+ elems = path.split('/')
3502
+ elems.shift if path[0] == ?/ # delete empty string on head
3503
+ last_elem = elems.pop
3504
+ c = @doc # collection
3505
+ elems.each do |elem|
3506
+ if c.is_a?(Array)
3507
+ c = c[elem.to_i]
3508
+ elsif c.is_a?(Hash)
3509
+ c = c[elem]
3510
+ else
3511
+ assert false
3512
+ end
3513
+ end
3514
+ linenums = @linenums_table[c.__id__]
3515
+ if c.is_a?(Array)
3516
+ linenum = linenums[last_elem.to_i]
3517
+ elsif c.is_a?(Hash)
3518
+ linenum = linenums[last_elem]
3519
+ end
3520
+ return linenum
3521
+ end
3522
+
3523
+ def set_errors_linenum(errors)
3524
+ errors.each do |error|
3525
+ error.linenum = path_linenum(error.path)
3526
+ end
3527
+ end
3528
+
3529
+ def set_error_linenums(errors)
3530
+ warn "*** Kwalify::YamlParser#set_error_linenums() is obsolete. You should use set_errors_linenum() instead."
3531
+ set_errors_linenum(errors)
3532
+ end
3533
+
3534
+ protected
3535
+
3536
+ def create_sequence(linenum=current_linenum())
3537
+ seq = []
3538
+ @linenums_table[seq.__id__] = []
3539
+ return seq
3540
+ end
3541
+
3542
+ def add_to_seq(seq, value, linenum)
3543
+ seq << value
3544
+ @linenums_table[seq.__id__] << linenum
3545
+ end
3546
+
3547
+ def set_seq_at(seq, i, value, linenum)
3548
+ seq[i] = value
3549
+ @linenums_table[seq.__id__][i] = linenum
3550
+ end
3551
+
3552
+ def create_mapping(linenum=current_linenum())
3553
+ map = {}
3554
+ @linenums_table[map.__id__] = {}
3555
+ return map
3556
+ end
3557
+
3558
+ def add_to_map(map, key, value, linenum)
3559
+ map[key] = value
3560
+ @linenums_table[map.__id__][key] = linenum
3561
+ end
3562
+
3563
+ def set_map_with(map, key, value, linenum)
3564
+ map[key] = value
3565
+ @linenums_table[map.__id__][key] = linenum
3566
+ end
3567
+
3568
+ def set_default(map, value, linenum)
3569
+ map.default = value
3570
+ @linenums_table[map.__id__][:'='] = linenum
3571
+ end
3572
+
3573
+ def merge_map(map, collection, linenum)
3574
+ t = @linenums_table[map.__id__]
3575
+ list = collection.is_a?(Array) ? collection : [ collection ]
3576
+ list.each do |m|
3577
+ t2 = @linenums_table[m.__id__]
3578
+ m.each do |key, val|
3579
+ unless map.key?(key)
3580
+ map[key] = val
3581
+ t[key] = t2[key]
3582
+ end
3583
+ end
3584
+ end
3585
+ end
3586
+
3587
+ def create_scalar(value, linenum=current_linenum())
3588
+ data = super(value)
3589
+ #return Scalar.new(data, linenum)
3590
+ return data
3591
+ end
3592
+
3593
+ end
3594
+
3595
+
3596
+ ## alias of YamlParser class
3597
+ class Parser < YamlParser
3598
+ def initialize(yaml_str)
3599
+ super(yaml_str)
3600
+ #warn "*** class Kwalify::Parser is obsolete. Please use Kwalify::YamlParser instead."
3601
+ end
3602
+ end
3603
+
3604
+
3605
+ end
3606
+ #--end of require 'kwalify/yaml-parser'
3607
+ #require 'kwalify/parser/base'
3608
+ #require 'kwalify/parser/yaml'
3609
+
3610
+
3611
+ module Kwalify
3612
+
3613
+ module Util
3614
+
3615
+ autoload :HashLike, 'kwalify/util/hashlike'
3616
+
3617
+ end
3618
+
3619
+ module Yaml
3620
+
3621
+ autoload :Parser, 'kwalify/parser/yaml'
3622
+
3623
+ ## read yaml_str, parse it, and return yaml document.
3624
+ ##
3625
+ ## opts:
3626
+ ## ::validator: Kwalify::Validator object
3627
+ ## ::preceding_aliass: allow preceding alias if true
3628
+ ## ::data_binding: enable data binding if true
3629
+ ## ::untabify: expand tab chars if true
3630
+ ## ::filename: filename
3631
+ def self.load(yaml_str, opts={})
3632
+ #require 'kwalify/parser/yaml'
3633
+ parser = Kwalify::Yaml::Parser.new(opts[:validator])
3634
+ parser.preceding_alias = true if opts[:preceding_alias]
3635
+ parser.data_binding = true if opts[:data_binding]
3636
+ yaml_str = Kwalify::Util.untabify(yaml_str) if opts[:untabify]
3637
+ ydoc = parser.parse(yaml_str, :filename=>opts[:filename])
3638
+ return ydoc
3639
+ end
3640
+
3641
+ ## read file, parse it, and return yaml document.
3642
+ ## see Kwalify::Yaml::Parser.load()
3643
+ def self.load_file(filename, opts={})
3644
+ opts[:filename] = filename
3645
+ return self.load(File.read(filename), opts)
3646
+ end
3647
+
3648
+ end
3649
+
3650
+ module Json
3651
+ end
3652
+
3653
+ end
3654
+ #--end of require 'kwalify'
3655
+ #--begin of require 'kwalify/main'
3656
+ ###
3657
+ ### $Rev: 95 $
3658
+ ### $Release: 0.7.1 $
3659
+ ### copyright(c) 2005-2008 kuwata-lab all rights reserved.
3660
+ ###
3661
+
3662
+ require 'yaml'
3663
+ require 'erb'
3664
+ #--already included require 'kwalify'
3665
+ #--already included require 'kwalify/util'
3666
+ #--begin of require 'kwalify/util/ordered-hash'
3667
+ ###
3668
+ ### $Rev: 84 $
3669
+ ### 0.7.1
3670
+ ### $COPYRIGHT$
3671
+ ###
3672
+
3673
+ module Kwalify
3674
+
3675
+ module Util
3676
+
3677
+ class OrderedHash < Hash
3678
+
3679
+ def initialize(*args, &block)
3680
+ super
3681
+ @_keys = []
3682
+ end
3683
+
3684
+ alias __set__ []=
3685
+
3686
+ def put(key, val)
3687
+ @_keys << key unless self.key?(key)
3688
+ __set__(key, val)
3689
+ end
3690
+
3691
+ def add(key, val)
3692
+ @_keys.delete_at(@_keys.index(key)) if self.key?(key)
3693
+ @_keys << key
3694
+ __set__(key, val)
3695
+ end
3696
+
3697
+ alias []= put
3698
+ #alias []= add
3699
+
3700
+ def keys
3701
+ return @_keys.dup
3702
+ end
3703
+
3704
+ def values
3705
+ return @_keys.collect {|key| self[key] }
3706
+ end
3707
+
3708
+ def delete(key)
3709
+ @_keys.delete_at(@_keys.index(key)) if self.key?(key)
3710
+ super
3711
+ end
3712
+
3713
+ def each
3714
+ @_keys.each do |key|
3715
+ yield key, self[key]
3716
+ end
3717
+ end
3718
+
3719
+ end
3720
+
3721
+ end
3722
+
3723
+ end
3724
+ #--end of require 'kwalify/util/ordered-hash'
3725
+
3726
+
3727
+ module Kwalify
3728
+
3729
+
3730
+ class CommandOptionError < KwalifyError
3731
+ def initialize(message, option, error_symbol)
3732
+ super(message)
3733
+ @option = option
3734
+ @error_symbol = error_symbol
3735
+ end
3736
+ attr_reader :option, :error_symbol
3737
+ end
3738
+
3739
+
3740
+ ##
3741
+ ## ex.
3742
+ ## command = File.basename($0)
3743
+ ## begin
3744
+ ## main = Kwalify::Main.new(command)
3745
+ ## s = main.execute
3746
+ ## print s if s
3747
+ ## rescue Kwalify::CommandOptionError => ex
3748
+ ## $stderr.puts "ERROR: #{ex.message}"
3749
+ ## exit 1
3750
+ ## rescue Kwalify::KwalifyError => ex
3751
+ ## $stderr.puts "ERROR: #{ex.message}"
3752
+ ## exit 1
3753
+ ## end
3754
+ ##
3755
+ class Main
3756
+
3757
+
3758
+ def initialize(command=nil)
3759
+ @command = command || File.basename($0)
3760
+ @options = {}
3761
+ @properties = {}
3762
+ @template_path = []
3763
+ $:.each do |path|
3764
+ tpath = "#{path}/kwalify/templates"
3765
+ @template_path << tpath if test(?d, tpath)
3766
+ end
3767
+ end
3768
+
3769
+
3770
+ def debug?
3771
+ @options[:debug]
3772
+ end
3773
+
3774
+
3775
+ def _inspect()
3776
+ sb = []
3777
+ sb << "command: #{@command}\n"
3778
+ sb << "options:\n"
3779
+ @options.keys.sort {|k1,k2| k1.to_s<=>k2.to_s }.each do |key|
3780
+ sb << " - #{key}: #{@options[key]}\n"
3781
+ end
3782
+ sb << "properties:\n"
3783
+ @properties.keys.sort_by {|k| k.to_s}.each do |key|
3784
+ sb << " - #{key}: #{@properties[key]}\n"
3785
+ end
3786
+ #sb << "template_path:\n"
3787
+ #@template_path.each do |path|
3788
+ # sb << " - #{path}\n"
3789
+ #end
3790
+ return sb.join
3791
+ end
3792
+
3793
+
3794
+ def execute(argv=ARGV)
3795
+ ## parse command-line options
3796
+ filenames = _parse_argv(argv)
3797
+
3798
+ ## help or version
3799
+ if @options[:help] || @options[:version]
3800
+ action = @options[:action]
3801
+ s = ''
3802
+ s << _version() << "\n" if @options[:version]
3803
+ s << _usage() if @options[:help] && !action
3804
+ s << _describe_properties(action) if @options[:help] && action
3805
+ puts s
3806
+ return
3807
+ end
3808
+
3809
+ # validation
3810
+ if @options[:meta2]
3811
+ validate_schemafiles2(filenames)
3812
+ elsif @options[:meta]
3813
+ validate_schemafiles(filenames)
3814
+ elsif @options[:action]
3815
+ unless @options[:schema]
3816
+ #* key=:command_option_actionnoschema msg="schema filename is not specified."
3817
+ raise option_error(:command_option_actionnoschema, @options[:action])
3818
+ end
3819
+ perform_action(@options[:action], @options[:schema])
3820
+ elsif @options[:schema]
3821
+ if @options[:debug]
3822
+ inspect_schema(@options[:schema])
3823
+ else
3824
+ validate_files(filenames, @options[:schema])
3825
+ end
3826
+ else
3827
+ #* key=:command_option_noaction msg="command-line option '-f' or '-m' required."
3828
+ raise option_error(:command_option_noaction, @command)
3829
+ end
3830
+ return
3831
+ end
3832
+
3833
+
3834
+ def self.main(command, argv=ARGV)
3835
+ begin
3836
+ main = Kwalify::Main.new(command)
3837
+ s = main.execute(argv)
3838
+ print s if s
3839
+ rescue Kwalify::CommandOptionError => ex
3840
+ raise ex if main.debug?
3841
+ $stderr.puts ex.message
3842
+ exit 1
3843
+ rescue Kwalify::KwalifyError => ex
3844
+ raise ex if main.debug?
3845
+ $stderr.puts "ERROR: #{ex.to_s}"
3846
+ exit 1
3847
+ #rescue => ex
3848
+ # if main.debug?
3849
+ # raise ex
3850
+ # else
3851
+ # $stderr.puts ex.message
3852
+ # exit 1
3853
+ # end
3854
+ end
3855
+ end
3856
+
3857
+
3858
+ private
3859
+
3860
+
3861
+ def option_error(error_symbol, arg)
3862
+ msg = Kwalify.msg(error_symbol) % arg
3863
+ return CommandOptionError.new(msg, arg, error_symbol)
3864
+ end
3865
+
3866
+
3867
+ def _find_template(action)
3868
+ template_filename = action + '.eruby'
3869
+ unless test(?f, template_filename)
3870
+ pathlist = []
3871
+ pathlist.concat(@options[:tpath].split(/,/)) if @options[:tpath]
3872
+ pathlist.concat(@template_path)
3873
+ tpath = pathlist.find {|path| test(?f, "#{path}/#{template_filename}") }
3874
+ #* key=:command_option_notemplate msg="%s: invalid action (template not found).\n"
3875
+ raise option_error(:command_option_notemplate, action) unless tpath
3876
+ template_filename = "#{tpath}/#{action}.eruby"
3877
+ end
3878
+ return template_filename
3879
+ end
3880
+
3881
+
3882
+ def apply_template(template_filename, hash)
3883
+ template = File.read(template_filename)
3884
+ trim_mode = 1
3885
+ erb = ERB.new(template, nil, trim_mode)
3886
+ context = Object.new
3887
+ hash.each do |key, val|
3888
+ context.instance_variable_set("@#{key}", val)
3889
+ end
3890
+ s = context.instance_eval(erb.src, template_filename)
3891
+ return s
3892
+ end
3893
+
3894
+
3895
+ def _describe_properties(action)
3896
+ template_filename = _find_template(action)
3897
+ s = apply_template(template_filename, :describe=>true)
3898
+ return s
3899
+ end
3900
+
3901
+
3902
+ def perform_action(action, schema_filename, describe=false)
3903
+ template_filename = _find_template(action)
3904
+ schema = _load_schemafile(schema_filename, ordered=true)
3905
+ validator = Kwalify::Validator.new(schema)
3906
+ @properties[:schema_filename] = schema_filename
3907
+ hash = { :validator=>validator, :schema=>schema, :properties=>@properties }
3908
+ s = apply_template(template_filename, hash)
3909
+ puts s if s && !s.empty?
3910
+ end
3911
+
3912
+
3913
+ def inspect_schema(schema_filename)
3914
+ schema = _load_schemafile(schema_filename)
3915
+ if schema.nil?
3916
+ puts "nil"
3917
+ else
3918
+ validator = Kwalify::Validator.new(schema) # error raised when schema is wrong
3919
+ puts validator._inspect()
3920
+ end
3921
+ end
3922
+
3923
+
3924
+ ## -f schemafile datafile
3925
+ def validate_files(filenames, schema_filename)
3926
+ schema = _load_schemafile(schema_filename)
3927
+ validator = Kwalify::Validator.new(schema)
3928
+ _validate_files(validator, filenames)
3929
+ end
3930
+
3931
+
3932
+ def _load_schemafile(schema_filename, ordered=false)
3933
+ str = File.read(schema_filename)
3934
+ if str.empty?
3935
+ #* key=:schema_empty msg="%s: empty schema.\n"
3936
+ msg = Kwalify.msg(:schema_emtpy) % filename
3937
+ raise CommandOptionError.new(msg)
3938
+ end
3939
+ str = Util.untabify(str) if @options[:untabify]
3940
+ parser = Kwalify::Yaml::Parser.new()
3941
+ parser.preceding_alias = true if @options[:preceding]
3942
+ parser.mapping_class = Kwalify::Util::OrderedHash if ordered
3943
+ schema = parser.parse(str, :filename=>schema_filename) # or YAML.load(str)
3944
+ return schema
3945
+ end
3946
+
3947
+
3948
+ ## -m schemafile
3949
+ def validate_schemafiles(schema_filenames)
3950
+ meta_validator = Kwalify::MetaValidator.instance()
3951
+ _validate_files(meta_validator, schema_filenames)
3952
+ end
3953
+
3954
+
3955
+ ## -M schemafile
3956
+ def validate_schemafiles2(schema_filenames)
3957
+ parser = Kwalify::Yaml::Parser.new()
3958
+ parser.preceding_alias = true if @options[:preceding]
3959
+ for schema_filename in schema_filenames
3960
+ str = File.read(schema_filename)
3961
+ str = Util.untabify(str) if @options[:untabify]
3962
+ schema = parser.parse(str, :filename=>schema_filename)
3963
+ Kwalify::Validator.new(schema) # exception raised when schema has errors
3964
+ end
3965
+ end
3966
+
3967
+
3968
+ def _validate_files(validator, filenames)
3969
+ ## parser
3970
+ if @options[:linenum] || @options[:preceding]
3971
+ parser = Kwalify::Yaml::Parser.new(validator)
3972
+ parser.preceding_alias = true if @options[:preceding]
3973
+ else
3974
+ parser = nil
3975
+ end
3976
+ ## filenames
3977
+ if filenames.empty?
3978
+ filenames = [ $stdin ]
3979
+ end
3980
+ for filename in filenames
3981
+ ## read input
3982
+ if filename.is_a?(IO)
3983
+ input = filename.read()
3984
+ filename = '(stdin)'
3985
+ else
3986
+ input = File.read(filename)
3987
+ end
3988
+ if input.empty?
3989
+ #* key=:validation_empty msg="%s#%d: empty."
3990
+ puts kwalify.msg(:validation_empty) % [filename, i]
3991
+ #puts "#{filename}##{i}: empty."
3992
+ next
3993
+ end
3994
+ input = Util.untabify(input) if @options[:untabify]
3995
+ ## parse input
3996
+ if parser
3997
+ #i = 0
3998
+ #ydoc = parser.parse(input, :filename=>filename)
3999
+ #_show_errors(filename, i, ydoc, parser.errors)
4000
+ #while parser.has_next?
4001
+ # i += 1
4002
+ # ydoc = parser.parse_next()
4003
+ # _show_errors(filename, i, ydoc, parser.errors)
4004
+ #end
4005
+ i = 0
4006
+ parser.parse_stream(input, :filename=>filename) do |ydoc|
4007
+ _show_errors(filename, i, ydoc, parser.errors)
4008
+ i += 1
4009
+ end
4010
+ else
4011
+ i = 0
4012
+ YAML.load_documents(input) do |ydoc|
4013
+ errors = validator.validate(ydoc)
4014
+ _show_errors(filename, i, ydoc, errors)
4015
+ i += 1
4016
+ end
4017
+ end
4018
+ end
4019
+ end
4020
+
4021
+
4022
+ def _show_errors(filename, i, ydoc, errors, ok_label="valid.", ng_label="INVALID")
4023
+ if errors && !errors.empty?
4024
+ puts "#{filename}##{i}: #{ng_label}"
4025
+ errors.sort!
4026
+ for error in errors
4027
+ e = error
4028
+ if @options[:emacs]
4029
+ raise unless @options[:linenum]
4030
+ puts "#{filename}:#{e.linenum}:#{e.column} [#{e.path}] #{e.message}\n"
4031
+ elsif @options[:linenum]
4032
+ puts " - (line #{e.linenum}) [#{e.path}] #{e.message}\n"
4033
+ else
4034
+ puts " - [#{e.path}] #{e.message}\n"
4035
+ end
4036
+ end
4037
+ elsif ydoc.nil?
4038
+ #* key=:validation_empty msg="%s#%d: empty.\n"
4039
+ puts kwalify.msg(:validation_empty) % [filename, i]
4040
+ else
4041
+ #* key=:validation_valid msg="%s#%d: valid."
4042
+ puts Kwalify.msg(:validation_valid) % [filename, i] unless @options[:quiet]
4043
+ #puts "#{filename}##{i} - #{ok_label}" unless @options[:quiet]
4044
+ end
4045
+ end
4046
+
4047
+
4048
+ def _usage()
4049
+ #msg = Kwalify.msg(:command_help) % [@command, @command, @command]
4050
+ msg = Kwalify.msg(:command_help)
4051
+ return msg
4052
+ end
4053
+
4054
+
4055
+ def _version()
4056
+ return RELEASE
4057
+ end
4058
+
4059
+
4060
+ def _to_value(str)
4061
+ case str
4062
+ when nil, "null", "nil" ; return nil
4063
+ when "true", "yes" ; return true
4064
+ when "false", "no" ; return false
4065
+ when /\A\d+\z/ ; return str.to_i
4066
+ when /\A\d+\.\d+\z/ ; return str.to_f
4067
+ when /\/(.*)\// ; return Regexp.new($1)
4068
+ when /\A'.*'\z/, /\A".*"\z/ ; return eval(str)
4069
+ else ; return str
4070
+ end
4071
+ end
4072
+
4073
+
4074
+ def _parse_argv(argv)
4075
+ option_table = {
4076
+ ?h => :help,
4077
+ ?v => :version,
4078
+ ?q => :quiet,
4079
+ ?s => :quiet,
4080
+ ?t => :untabify,
4081
+ #?z => :meta,
4082
+ ?m => :meta,
4083
+ ?M => :meta2,
4084
+ ?E => :emacs,
4085
+ ?l => :linenum,
4086
+ ?f => :schema,
4087
+ ?D => :debug,
4088
+ ?a => :action,
4089
+ ?I => :tpath,
4090
+ ?P => :preceding,
4091
+ }
4092
+
4093
+ errcode_table = {
4094
+ #* key=:command_option_schema_required msg="-%s: schema filename required."
4095
+ ?f => :command_option_schema_required,
4096
+ #* key=:command_option_action_required msg="-%s: action required."
4097
+ ?a => :command_option_action_required,
4098
+ #* key=:command_option_tpath_required msg="-%s: template path required."
4099
+ ?I => :command_option_tpath_required,
4100
+ }
4101
+
4102
+ while argv[0] && argv[0][0] == ?-
4103
+ optstr = argv.shift
4104
+ optstr = optstr[1, optstr.length-1]
4105
+ ## property
4106
+ if optstr[0] == ?-
4107
+ unless optstr =~ /\A\-([-\w]+)(?:=(.*))?\z/
4108
+ #* key=:command_property_invalid msg="%s: invalid property."
4109
+ raise option_error(:command_property_invalid, optstr)
4110
+ end
4111
+ prop_name = $1; prop_value = $2
4112
+ key = prop_name.gsub(/-/, '_').intern
4113
+ value = prop_value.nil? ? true : _to_value(prop_value)
4114
+ @properties[key] = value
4115
+ ## option
4116
+ else
4117
+ while optstr && !optstr.empty?
4118
+ optchar = optstr[0]
4119
+ optstr[0,1] = ""
4120
+ unless option_table.key?(optchar)
4121
+ #* key=:command_option_invalid msg="-%s: invalid command option."
4122
+ raise option_error(:command_option_invalid, optchar.chr)
4123
+ end
4124
+ optkey = option_table[optchar]
4125
+ case optchar
4126
+ when ?f, ?a, ?I
4127
+ arg = optstr.empty? ? argv.shift : optstr
4128
+ raise option_error(errcode_table[optchar], optchar.chr) unless arg
4129
+ @options[optkey] = arg
4130
+ optstr = ''
4131
+ else
4132
+ @options[optkey] = true
4133
+ end
4134
+ end
4135
+ end
4136
+ end # end of while
4137
+ #
4138
+ @options[:linenum] = true if @options[:emacs]
4139
+ @options[:help] = true if @properties[:help]
4140
+ @options[:version] = true if @properties[:version]
4141
+ filenames = argv
4142
+ return filenames
4143
+ end
4144
+
4145
+
4146
+ def _domain_type?(doc)
4147
+ klass = defined?(YAML::DomainType) ? YAML::DomainType : YAML::Syck::DomainType
4148
+ return doc.is_a?(klass)
4149
+ end
4150
+
4151
+
4152
+ end
4153
+
4154
+
4155
+ end
4156
+ #--end of require 'kwalify/main'
4157
+
4158
+ command = File.basename($0)
4159
+ Kwalify::Main.main(command, ARGV)