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