rkwalify 1.4.0.pre.preview1

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