groonga-command-parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "groonga/command"
20
+
21
+ module Groonga
22
+ module Command
23
+ class Parser
24
+ class Error < Command::Error
25
+ attr_reader :reason, :location
26
+ def initialize(reason, before, after)
27
+ @reason = reason
28
+ @location = compute_location(before, after)
29
+ super("#{@reason}:\n#{@location}")
30
+ end
31
+
32
+ private
33
+ def compute_location(before, after)
34
+ location = ""
35
+ if before[-1] == ?\n
36
+ location << before
37
+ location << after
38
+ location << "^"
39
+ elsif after[0] == ?\n
40
+ location << before
41
+ location << "\n"
42
+ location << " " * before.bytesize + "^"
43
+ location << after
44
+ else
45
+ location << before
46
+ location << after
47
+ location << " " * before.bytesize + "^"
48
+ end
49
+ location
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ module Groonga
20
+ module Command
21
+ class Parser
22
+ VERSION = "1.0.0"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,102 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ require "cgi"
20
+ require "stringio"
21
+
22
+ require "groonga/command/parser"
23
+
24
+ module GroongaCommandParserTestUtils
25
+ module CommandParser
26
+ private
27
+ def command(name, arguments)
28
+ Groonga::Command.find(name).new(name, arguments)
29
+ end
30
+
31
+ def parse_http_path(command, arguments, options={})
32
+ path = "/d/#{command}"
33
+ case options[:output_type]
34
+ when false
35
+ when nil
36
+ path << ".json"
37
+ else
38
+ path << ".#{options[:output_type]}"
39
+ end
40
+
41
+ unless arguments.empty?
42
+ uri_arguments = arguments.collect do |key, value|
43
+ [CGI.escape(key.to_s), CGI.escape(value.to_s)].join("=")
44
+ end
45
+ path << "?"
46
+ path << uri_arguments.join("&")
47
+ end
48
+
49
+ Groonga::Command::Parser.parse(path)
50
+ end
51
+
52
+ def parse_command_line(command, arguments, options={})
53
+ command_line = "#{command}"
54
+ case options[:output_type]
55
+ when false
56
+ when nil
57
+ command_line << " --output_type json"
58
+ else
59
+ command_line << " --output_type #{options[:output_type]}"
60
+ end
61
+
62
+ if arguments.is_a?(Hash)
63
+ arguments.each do |key, value|
64
+ escaped_value = escape_command_line_value(value)
65
+ command_line << " --#{key} #{escaped_value}"
66
+ end
67
+ else
68
+ arguments.each do |argument|
69
+ command_line << " #{escape_command_line_value(argument)}"
70
+ end
71
+ end
72
+
73
+ Groonga::Command::Parser.parse(command_line)
74
+ end
75
+
76
+ def escape_command_line_value(value)
77
+ if /"| / =~ value
78
+ '"' + value.gsub(/"/, '\"') + '"'
79
+ else
80
+ value
81
+ end
82
+ end
83
+ end
84
+
85
+ module HTTPCommandParser
86
+ include CommandParser
87
+
88
+ private
89
+ def parse(command, arguments={}, options={})
90
+ parse_http_path(command, arguments, options)
91
+ end
92
+ end
93
+
94
+ module CommandLineCommandParser
95
+ include CommandParser
96
+
97
+ private
98
+ def parse(command, arguments={}, options={})
99
+ parse_command_line(command, arguments, options)
100
+ end
101
+ end
102
+ end
data/test/run-test.rb ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyright (C) 2012-2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ $VERBOSE = true
20
+
21
+ base_dir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
22
+ lib_dir = File.join(base_dir, "lib")
23
+ test_dir = File.join(base_dir, "test")
24
+
25
+ require "test-unit"
26
+ require "test/unit/notify"
27
+
28
+ Test::Unit::Priority.enable
29
+
30
+ groonga_command_dir = File.join(base_dir, "..", "groonga-command")
31
+ groonga_command_dir = File.expand_path(groonga_command_dir)
32
+ if File.exist?(groonga_command_dir)
33
+ $LOAD_PATH.unshift(File.join(groonga_command_dir, "lib"))
34
+ end
35
+
36
+ $LOAD_PATH.unshift(lib_dir)
37
+ $LOAD_PATH.unshift(test_dir)
38
+
39
+ require "groonga-command-parser-test-utils"
40
+
41
+ ENV["TEST_UNIT_MAX_DIFF_TARGET_STRING_SIZE"] ||= "5000"
42
+
43
+ exit Test::Unit::AutoRunner.run(true, test_dir)
@@ -0,0 +1,377 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License as published by the Free Software Foundation; either
8
+ # version 2.1 of the License, or (at your option) any later version.
9
+ #
10
+ # This library is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ # Lesser General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Lesser General Public
16
+ # License along with this library; if not, write to the Free Software
17
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
+
19
+ class ParserTest < Test::Unit::TestCase
20
+ module ParseTests
21
+ def test_parameters
22
+ select = parse("select",
23
+ :table => "Users",
24
+ :filter => "age<=30")
25
+ assert_equal(command("select",
26
+ "table" => "Users",
27
+ "filter" => "age<=30",
28
+ "output_type" => "json"),
29
+ select)
30
+ end
31
+ end
32
+
33
+ class HTTPTest < self
34
+ include GroongaCommandParserTestUtils::HTTPCommandParser
35
+
36
+ def test_uri_format?
37
+ command = parse("status")
38
+ assert_predicate(command, :uri_format?)
39
+ end
40
+
41
+ def test_command_format?
42
+ command = parse("status")
43
+ assert_not_predicate(command, :command_format?)
44
+ end
45
+
46
+ def test_no_value
47
+ path = "/d/select?table=Users&key_only"
48
+ command = Groonga::Command::Parser.parse(path)
49
+ assert_equal({:table => "Users"}, command.arguments)
50
+ end
51
+
52
+ class ParseTest < self
53
+ include ParseTests
54
+ end
55
+ end
56
+
57
+ class CommandLineTest < self
58
+ include GroongaCommandParserTestUtils::CommandLineCommandParser
59
+
60
+ def test_uri_format?
61
+ command = parse("status")
62
+ assert_not_predicate(command, :uri_format?)
63
+ end
64
+
65
+ def test_command_format?
66
+ command = parse("status")
67
+ assert_predicate(command, :command_format?)
68
+ end
69
+
70
+ class ParseTest < self
71
+ include ParseTests
72
+ end
73
+
74
+ class EventTest < self
75
+ def setup
76
+ @parser = Groonga::Command::Parser.new
77
+ end
78
+
79
+ class CommandTest < self
80
+ def test_newline
81
+ parsed_command = nil
82
+ @parser.on_command do |command|
83
+ parsed_command = command
84
+ end
85
+
86
+ @parser << "status"
87
+ assert_nil(parsed_command)
88
+ @parser << "\n"
89
+ assert_equal("status", parsed_command.name)
90
+ end
91
+
92
+ def test_finish
93
+ parsed_command = nil
94
+ @parser.on_command do |command|
95
+ parsed_command = command
96
+ end
97
+
98
+ @parser << "status"
99
+ assert_nil(parsed_command)
100
+ @parser.finish
101
+ assert_equal("status", parsed_command.name)
102
+ end
103
+
104
+ def test_empty_line
105
+ parsed_command = nil
106
+ @parser.on_command do |command|
107
+ parsed_command = command
108
+ end
109
+
110
+ @parser << "\n"
111
+ assert_nil(parsed_command)
112
+
113
+ @parser << "status\n"
114
+ assert_equal("status", parsed_command.name)
115
+ end
116
+
117
+ def test_multi_lines
118
+ parsed_commands = []
119
+ @parser.on_command do |command|
120
+ parsed_commands << command
121
+ end
122
+
123
+ @parser << <<-COMMAND_LIST.chomp
124
+ table_list
125
+ status
126
+ COMMAND_LIST
127
+ assert_equal(["table_list"],
128
+ parsed_commands.collect(&:name))
129
+
130
+ @parser.finish
131
+ assert_equal(["table_list", "status"],
132
+ parsed_commands.collect(&:name))
133
+ end
134
+ end
135
+
136
+ class LoadTest < self
137
+ def setup
138
+ super
139
+ @events = []
140
+ @parser.on_load_start do |command|
141
+ @events << [:load_start, command.original_source.dup]
142
+ end
143
+ @parser.on_load_columns do |command, header|
144
+ @events << [:load_columns, command.original_source.dup, header]
145
+ end
146
+ @parser.on_load_value do |command, value|
147
+ @events << [:load_value, command.original_source.dup, value]
148
+ end
149
+ @parser.on_load_complete do |command|
150
+ @events << [:load_complete, command.original_source.dup]
151
+ end
152
+ end
153
+
154
+ class InlineTest < self
155
+ class BracketTest < self
156
+ def test_have_columns
157
+ command_line =
158
+ "load " +
159
+ "--columns '_key, name' " +
160
+ "--values '[[\"alice\", \"Alice\"]]' " +
161
+ "--table Users"
162
+ @parser << command_line
163
+ assert_equal([], @events)
164
+ @parser << "\n"
165
+ assert_equal([
166
+ [:load_start, command_line],
167
+ [:load_columns, command_line, ["_key", "name"]],
168
+ [:load_value, command_line, ["alice", "Alice"]],
169
+ [:load_complete, command_line],
170
+ ],
171
+ @events)
172
+ end
173
+
174
+ def test_no_columns
175
+ command_line = "load --values '[[\"_key\"], [1]]' --table IDs"
176
+ @parser << command_line
177
+ assert_equal([], @events)
178
+ @parser << "\n"
179
+ assert_equal([
180
+ [:load_start, command_line],
181
+ [:load_columns, command_line, ["_key"]],
182
+ [:load_value, command_line, [1]],
183
+ [:load_complete, command_line],
184
+ ],
185
+ @events)
186
+ end
187
+ end
188
+
189
+ def test_brace
190
+ command_line = "load --values '[{\"_key\": 1}]' --table IDs"
191
+ @parser << command_line
192
+ assert_equal([], @events)
193
+ @parser << "\n"
194
+ assert_equal([
195
+ [:load_start, command_line],
196
+ [:load_value, command_line, {"_key" => 1}],
197
+ [:load_complete, command_line],
198
+ ],
199
+ @events)
200
+ end
201
+ end
202
+
203
+ class MultiLineTest < self
204
+ class BracketTest < self
205
+ def test_have_columns
206
+ @parser << <<-EOC
207
+ load --table Users --columns "_key, name"
208
+ [
209
+ ["alice", "Alice"]
210
+ ]
211
+ EOC
212
+ expected_events = []
213
+ expected_events << [:load_start, <<-EOC.chomp]
214
+ load --table Users --columns "_key, name"
215
+ EOC
216
+ expected_events << [:load_columns, <<-EOC.chomp, ["_key", "name"]]
217
+ load --table Users --columns "_key, name"
218
+ EOC
219
+ expected_events << [:load_value, <<-EOC.chomp, ["alice", "Alice"]]
220
+ load --table Users --columns "_key, name"
221
+ [
222
+ ["alice", "Alice"]
223
+ EOC
224
+ expected_events << [:load_complete, <<-EOC.chomp]
225
+ load --table Users --columns "_key, name"
226
+ [
227
+ ["alice", "Alice"]
228
+ ]
229
+ EOC
230
+ assert_equal(expected_events, @events)
231
+ end
232
+
233
+ def test_no_columns
234
+ @parser << <<-EOC
235
+ load --table Users
236
+ [
237
+ ["_key", "name"],
238
+ ["alice", "Alice"]
239
+ ]
240
+ EOC
241
+ expected_events = []
242
+ expected_events << [:load_start, <<-EOC.chomp]
243
+ load --table Users
244
+ EOC
245
+ expected_events << [:load_columns, <<-EOC.chomp, ["_key", "name"]]
246
+ load --table Users
247
+ [
248
+ ["_key", "name"]
249
+ EOC
250
+ expected_events << [:load_value, <<-EOC.chomp, ["alice", "Alice"]]
251
+ load --table Users
252
+ [
253
+ ["_key", "name"],
254
+ ["alice", "Alice"]
255
+ EOC
256
+ expected_events << [:load_complete, <<-EOC.chomp]
257
+ load --table Users
258
+ [
259
+ ["_key", "name"],
260
+ ["alice", "Alice"]
261
+ ]
262
+ EOC
263
+ assert_equal(expected_events, @events)
264
+ end
265
+ end
266
+
267
+ def test_brace
268
+ @parser << <<-EOC
269
+ load --table Users
270
+ [
271
+ {"_key": "alice", "name": "Alice"},
272
+ {"_key": "bob", "name": "Bob"}
273
+ ]
274
+ EOC
275
+ expected_events = []
276
+ expected_events << [:load_start, <<-EOC.chomp]
277
+ load --table Users
278
+ EOC
279
+ value = {"_key" => "alice", "name" => "Alice"}
280
+ expected_events << [:load_value, <<-EOC.chomp, value]
281
+ load --table Users
282
+ [
283
+ {"_key": "alice", "name": "Alice"}
284
+ EOC
285
+ value = {"_key" => "bob", "name" => "Bob"}
286
+ expected_events << [:load_value, <<-EOC.chomp, value]
287
+ load --table Users
288
+ [
289
+ {"_key": "alice", "name": "Alice"},
290
+ {"_key": "bob", "name": "Bob"}
291
+ EOC
292
+ expected_events << [:load_complete, <<-EOC.chomp]
293
+ load --table Users
294
+ [
295
+ {"_key": "alice", "name": "Alice"},
296
+ {"_key": "bob", "name": "Bob"}
297
+ ]
298
+ EOC
299
+ assert_equal(expected_events, @events)
300
+ end
301
+ end
302
+
303
+ class ErrorTest < self
304
+ def test_location
305
+ message = "record separate comma is missing"
306
+ before = "{\"_key\": \"alice\", \"name\": \"Alice\"}"
307
+ after = "\n{\"_key\": \"bob\""
308
+ error = Groonga::Command::Parser::Error.new(message, before, after)
309
+ assert_equal(<<-EOS.chomp, error.message)
310
+ record separate comma is missing:
311
+ {"_key": "alice", "name": "Alice"}
312
+ ^
313
+ {"_key": "bob"
314
+ EOS
315
+ end
316
+
317
+ def test_no_record_separate_comma
318
+ message = "record separate comma is missing"
319
+ before = "{\"_key\": \"alice\", \"name\": \"Alice\"}"
320
+ after = "\n{\"_key\": \"bob\""
321
+ error = Groonga::Command::Parser::Error.new(message, before, after)
322
+ assert_raise(error) do
323
+ @parser << <<-EOC
324
+ load --table Users
325
+ [
326
+ {"_key": "alice", "name": "Alice"}
327
+ {"_key": "bob", "name": "Bob"}
328
+ EOC
329
+ end
330
+ end
331
+
332
+ def test_garbage_before_json
333
+ message = "there are garbages before JSON"
334
+ before = "load --table Users\n"
335
+ after = "XXX\n"
336
+ error = Groonga::Command::Parser::Error.new(message, before, after)
337
+ assert_raise(error) do
338
+ @parser << <<-EOC
339
+ load --table Users
340
+ XXX
341
+ [
342
+ {"_key": "alice", "name": "Alice"}
343
+ ]
344
+ EOC
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ class CommentTest < self
351
+ def test_newline
352
+ parsed_comment = nil
353
+ @parser.on_comment do |comment|
354
+ parsed_comment = comment
355
+ end
356
+
357
+ @parser << "# status"
358
+ assert_nil(parsed_comment)
359
+ @parser << "\n"
360
+ assert_equal(" status", parsed_comment)
361
+ end
362
+
363
+ def test_finish
364
+ parsed_comment = nil
365
+ @parser.on_comment do |comment|
366
+ parsed_comment = comment
367
+ end
368
+
369
+ @parser << "# status"
370
+ assert_nil(parsed_comment)
371
+ @parser.finish
372
+ assert_equal(" status", parsed_comment)
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end