groonga-command 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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