kwalify 0.6.1 → 0.7.0

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