groonga-command 1.0.4 → 1.0.5

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.
@@ -1,449 +0,0 @@
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 "shellwords"
20
- require "cgi"
21
-
22
- require "yajl"
23
-
24
- require "groonga/command/error"
25
-
26
- require "groonga/command/base"
27
-
28
- require "groonga/command/dump"
29
- require "groonga/command/get"
30
- require "groonga/command/select"
31
- require "groonga/command/suggest"
32
- require "groonga/command/load"
33
- require "groonga/command/table-create"
34
- require "groonga/command/table-remove"
35
- require "groonga/command/table-rename"
36
- require "groonga/command/column-create"
37
- require "groonga/command/column-remove"
38
- require "groonga/command/column-rename"
39
- require "groonga/command/delete"
40
- require "groonga/command/truncate"
41
- require "groonga/command/register"
42
-
43
- module Groonga
44
- module Command
45
- class ParseError < Error
46
- attr_reader :reason, :location
47
- def initialize(reason, before, after)
48
- @reason = reason
49
- @location = compute_location(before, after)
50
- super("#{@reason}:\n#{@location}")
51
- end
52
-
53
- private
54
- def compute_location(before, after)
55
- location = ""
56
- if before[-1] == ?\n
57
- location << before
58
- location << after
59
- location << "^"
60
- elsif after[0] == ?\n
61
- location << before
62
- location << "\n"
63
- location << " " * before.bytesize + "^"
64
- location << after
65
- else
66
- location << before
67
- location << after
68
- location << " " * before.bytesize + "^"
69
- end
70
- location
71
- end
72
- end
73
-
74
- class Parser
75
- class << self
76
-
77
- # parses groonga command or HTTP (starts with "/d/") command.
78
- # @overload parse(data)
79
- # @!macro [new] parser.parse.argument
80
- # @param [String] data parsed command.
81
- # @return [Groonga::Command] Returns
82
- # {Groonga::Command} including parsed data.
83
- # @!macro parser.parse.argument
84
- # @overload parse(data, &block)
85
- # @!macro parser.parse.argument
86
- def parse(data, &block)
87
- if block_given?
88
- event_parse(data, &block)
89
- else
90
- stand_alone_parse(data)
91
- end
92
- end
93
-
94
- private
95
- def event_parse(data)
96
- parser = new
97
-
98
- parser.on_command do |command|
99
- yield(:on_command, command)
100
- end
101
- parser.on_load_start do |command|
102
- yield(:on_load_start, command)
103
- end
104
- parser.on_load_columns do |command, header|
105
- yield(:on_load_columns, command, header)
106
- end
107
- parser.on_load_value do |command, value|
108
- yield(:on_load_value, command, value)
109
- end
110
- parser.on_load_complete do |command|
111
- yield(:on_load_complete, command)
112
- end
113
- parser.on_comment do |comment|
114
- yield(:on_comment, comment)
115
- end
116
-
117
- consume_data(parser, data)
118
- end
119
-
120
- def stand_alone_parse(data)
121
- parsed_command = nil
122
-
123
- parser = new
124
- parser.on_command do |command|
125
- parsed_command = command
126
- end
127
- parser.on_load_columns do |command, columns|
128
- command[:columns] ||= columns.join(",")
129
- end
130
- values = []
131
- parser.on_load_value do |_, value|
132
- values << value
133
- end
134
- parser.on_load_complete do |command|
135
- parsed_command = command
136
- parsed_command[:values] ||= Yajl::Encoder.encode(values)
137
- end
138
-
139
- consume_data(parser, data)
140
- if parsed_command.nil?
141
- raise ParseError.new("not completed", data.lines.to_a.last, "")
142
- end
143
-
144
- parsed_command
145
- end
146
-
147
- def consume_data(parser, data)
148
- if data.respond_to?(:each)
149
- data.each do |chunk|
150
- parser << chunk
151
- end
152
- else
153
- parser << data
154
- end
155
- parser.finish
156
- end
157
- end
158
-
159
- def initialize
160
- reset
161
- initialize_hooks
162
- end
163
-
164
- # Streaming parsing command.
165
- # @param [String] chunk parsed chunk of command.
166
- def <<(chunk)
167
- @buffer << chunk
168
- consume_buffer
169
- end
170
-
171
- # Finishes parsing. If Parser is loading values specified "load"
172
- # command, this method raises {ParseError}.
173
- def finish
174
- if @loading
175
- raise ParseError.new("not completed",
176
- @command.original_source.lines.to_a.last,
177
- "")
178
- else
179
- catch do |tag|
180
- parse_line(@buffer)
181
- end
182
- end
183
- end
184
-
185
- # @overload on_command(command)
186
- # @overload on_command {|command| }
187
- def on_command(*arguments, &block)
188
- if block_given?
189
- @on_command_hook = block
190
- else
191
- @on_command_hook.call(*arguments) if @on_command_hook
192
- end
193
- end
194
-
195
- # @overload on_load_start(command)
196
- # @overload on_load_start {|command| }
197
- def on_load_start(*arguments, &block)
198
- if block_given?
199
- @on_load_start_hook = block
200
- else
201
- @on_load_start_hook.call(*arguments) if @on_load_start_hook
202
- end
203
- end
204
-
205
- # @overload on_load_columns(command)
206
- # @overload on_load_columns {|command| }
207
- def on_load_columns(*arguments, &block)
208
- if block_given?
209
- @on_load_columns_hook = block
210
- else
211
- @on_load_columns_hook.call(*arguments) if @on_load_columns_hook
212
- end
213
- end
214
-
215
- # @overload on_load_value(command)
216
- # @overload on_load_value {|command| }
217
- def on_load_value(*arguments, &block)
218
- if block_given?
219
- @on_load_value_hook = block
220
- else
221
- @on_load_value_hook.call(*arguments) if @on_load_value_hook
222
- end
223
- end
224
-
225
- # @overload on_load_complete(command)
226
- # @overload on_load_complete(command) { }
227
- def on_load_complete(*arguments, &block)
228
- if block_given?
229
- @on_load_complete_hook = block
230
- else
231
- @on_load_complete_hook.call(*arguments) if @on_load_complete_hook
232
- end
233
- end
234
-
235
- # @overload on_comment(comment)
236
- # @overload on_comment {|comment| }
237
- def on_comment(*arguments, &block)
238
- if block_given?
239
- @on_comment_hook = block
240
- else
241
- @on_comment_hook.call(*arguments) if @on_comment_hook
242
- end
243
- end
244
-
245
- private
246
- def consume_buffer
247
- catch do |tag|
248
- loop do
249
- if @loading
250
- consume_load_values(tag)
251
- else
252
- parse_line(consume_line(tag))
253
- end
254
- end
255
- end
256
- end
257
-
258
- def consume_load_values(tag)
259
- if @in_load_values
260
- json, separator, rest = @buffer.partition(/[\]},]/)
261
- if @load_value_completed
262
- throw(tag) if separator.empty?
263
- if separator == ","
264
- if /\A\s*\z/ =~ json
265
- @command.original_source << json << separator
266
- @buffer = rest
267
- @load_value_completed = false
268
- return
269
- else
270
- raise ParseError.new("record separate comma is missing",
271
- @command.original_source.lines.to_a.last,
272
- json)
273
- end
274
- elsif separator == "]"
275
- if /\A\s*\z/ =~ json
276
- @command.original_source << json << separator
277
- @buffer = rest
278
- on_load_complete(@command)
279
- reset
280
- return
281
- end
282
- end
283
- end
284
- @buffer = rest
285
- parse_json(json)
286
- if separator.empty?
287
- throw(tag)
288
- else
289
- @load_value_completed = false
290
- parse_json(separator)
291
- end
292
- else
293
- spaces, start_json, rest = @buffer.partition("[")
294
- unless /\A\s*\z/ =~ spaces
295
- raise ParseError.new("there are garbages before JSON",
296
- @command.original_source.lines.to_a.last,
297
- spaces)
298
- end
299
- if start_json.empty?
300
- @command.original_source << @buffer
301
- @buffer.clear
302
- throw(tag)
303
- else
304
- @command.original_source << spaces << start_json
305
- @buffer = rest
306
- @json_parser = Yajl::Parser.new
307
- @json_parser.on_parse_complete = lambda do |object|
308
- if object.is_a?(::Array) and @command.columns.nil?
309
- @command.columns = object
310
- on_load_columns(@command, object)
311
- else
312
- on_load_value(@command, object)
313
- end
314
- @load_value_completed = true
315
- end
316
- @in_load_values = true
317
- end
318
- end
319
- end
320
-
321
- def parse_json(json)
322
- @command.original_source << json
323
- begin
324
- @json_parser << json
325
- rescue Yajl::ParseError
326
- before_json = @command.original_source[0..(-json.bytesize)]
327
- message = "invalid JSON: #{$!.message}: <#{json}>:\n"
328
- message << before_json
329
- raise ParseError.new(message, before_json, json)
330
- end
331
- end
332
-
333
- def consume_line(tag)
334
- current_line, separator, rest = @buffer.partition(/\r?\n/)
335
- throw(tag) if separator.empty?
336
-
337
- if current_line.end_with?("\\")
338
- @buffer.sub!(/\\\r?\n/, "")
339
- consume_line(tag)
340
- else
341
- @buffer = rest
342
- current_line
343
- end
344
- end
345
-
346
- def parse_line(line)
347
- case line
348
- when /\A\s*\z/
349
- # ignore empty line
350
- when /\A\#/
351
- on_comment($POSTMATCH)
352
- else
353
- @command = parse_command(line)
354
- @command.original_source = line
355
- process_command
356
- end
357
- end
358
-
359
- def process_command
360
- if @command.name == "load"
361
- on_load_start(@command)
362
- if @command.columns
363
- on_load_columns(@command, @command.columns)
364
- end
365
- if @command[:values]
366
- values = Yajl::Parser.parse(@command[:values])
367
- if @command.columns.nil? and values.first.is_a?(::Array)
368
- header = values.shift
369
- @command.columns = header
370
- on_load_columns(@command, header)
371
- end
372
- values.each do |value|
373
- on_load_value(@command, value)
374
- end
375
- on_load_complete(@command)
376
- reset
377
- else
378
- @command.original_source << "\n"
379
- @loading = true
380
- end
381
- else
382
- on_command(@command)
383
- @command = nil
384
- end
385
- end
386
-
387
- def parse_command(input)
388
- if input.start_with?("/d/")
389
- parse_uri_path(input)
390
- else
391
- parse_command_line(input)
392
- end
393
- end
394
-
395
- def parse_uri_path(path)
396
- name, arguments_string = path.split(/\?/, 2)
397
- arguments = {}
398
- if arguments_string
399
- arguments_string.split(/&/).each do |argument_string|
400
- key, value = argument_string.split(/\=/, 2)
401
- next if value.nil?
402
- arguments[key] = CGI.unescape(value)
403
- end
404
- end
405
- name = name.gsub(/\A\/d\//, '')
406
- name, output_type = name.split(/\./, 2)
407
- arguments["output_type"] = output_type if output_type
408
- command_class = Command.find(name)
409
- command = command_class.new(name, arguments)
410
- command.original_format = :uri
411
- command
412
- end
413
-
414
- def parse_command_line(command_line)
415
- name, *arguments = Shellwords.shellwords(command_line)
416
- pair_arguments = {}
417
- ordered_arguments = []
418
- until arguments.empty?
419
- argument = arguments.shift
420
- if argument.start_with?("--")
421
- pair_arguments[argument.sub(/\A--/, "")] = arguments.shift
422
- else
423
- ordered_arguments << argument
424
- end
425
- end
426
- command_class = Command.find(name)
427
- command = command_class.new(name, pair_arguments, ordered_arguments)
428
- command.original_format = :command
429
- command
430
- end
431
-
432
- def reset
433
- @command = nil
434
- @loading = false
435
- @in_load_values = false
436
- @load_value_completed = false
437
- @buffer = "".force_encoding("ASCII-8BIT")
438
- end
439
-
440
- def initialize_hooks
441
- @on_command_hook = nil
442
- @on_load_start_hook = nil
443
- @on_load_columns_hook = nil
444
- @on_load_value_hook = nil
445
- @on_load_complete_hook = nil
446
- end
447
- end
448
- end
449
- end