groonga-command-parser 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a842fe9087d59d4d37a94dba22f419576d192b09
4
- data.tar.gz: 60a05fe87c274df64c6abd2e9d14c33162999d9d
3
+ metadata.gz: 653da7d4f65f66a98a4fdc5300f1c40b768d99bf
4
+ data.tar.gz: 3be534841fc2a4e8ee175262107ce0336d81c287
5
5
  SHA512:
6
- metadata.gz: a4bee897857636e1286a8e49bd6b1a1ba49d6e1351cff6280dbb75a447855482efeea89de190ce899fdaa169020fcdd59dd6691a93ce67c0d89794d7a729d1c9
7
- data.tar.gz: 79ef8b01fd136994a4782e3137c65b7a59159318e2c640dd3e6ebdf98a5ef1bb29bba036561514e665701c5558dee15144e370e46bb4fa9b739aafa875b3e7fb
6
+ metadata.gz: 4c12a30d319639ac368a1610080cfef25c47198565245453e56265db91508b58664fe6886e1cb80bc6436beef2252ec61734337274740ed26c2ebe1e87201c14
7
+ data.tar.gz: 0a541136772e374816b60ceb5052b1848c9d7a0e1f05c5526a575f17e8d16ce570084a70fcc30ad6909311c9f70b6fad517605add4423c48468f1d409228ca47
data/.yardopts CHANGED
@@ -2,6 +2,8 @@
2
2
  --no-private
3
3
  --output doc/reference/en
4
4
  --title "groonga-command-parser API Reference"
5
+ --markup markdown
6
+ --markup-provider kramdown
5
7
  --po-dir doc/po
6
8
  -
7
9
  doc/text/*
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # README
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/groonga-command-parser.svg)](http://badge.fury.io/rb/groonga-command-parser)
4
+ [![Build Status](https://travis-ci.org/groonga/groonga-command-parser.svg?branch=master)](https://travis-ci.org/groonga/groonga-command-parser)
5
+
3
6
  ## Name
4
7
 
5
8
  groonga-command-parser
data/doc/text/news.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # News
2
2
 
3
+ ## 1.0.4: 2015-05-23
4
+
5
+ ### Improvements
6
+
7
+ * Made quoted text handling Groonga compatible.
8
+ * Changed JSON parser to ffi-yajl from yajl-ruby.
9
+ * Supported JRuby by this change.
10
+ [GitHub#1] [Reported by Hiroyuki Sato]
11
+ * Improved performance for large load data.
12
+
13
+ ### Fixes
14
+
15
+ * Fixed encoding related parse error on Windows.
16
+
17
+ ### Thanks
18
+
19
+ * Hiroyuki Sato
20
+
3
21
  ## 1.0.3: 2014-12-12
4
22
 
5
23
  ### Improvements
@@ -1,6 +1,6 @@
1
1
  # -*- mode: ruby; coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2012-2014 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2012-2015 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -53,7 +53,8 @@ Gem::Specification.new do |spec|
53
53
  spec.require_paths = ["lib"]
54
54
 
55
55
  spec.add_runtime_dependency("groonga-command", ">= 1.0.9")
56
- spec.add_runtime_dependency("yajl-ruby")
56
+ spec.add_runtime_dependency("ffi")
57
+ spec.add_runtime_dependency("ffi-yajl")
57
58
 
58
59
  spec.add_development_dependency("test-unit")
59
60
  spec.add_development_dependency("test-unit-notify")
@@ -61,6 +62,6 @@ Gem::Specification.new do |spec|
61
62
  spec.add_development_dependency("bundler")
62
63
  spec.add_development_dependency("packnga")
63
64
  spec.add_development_dependency("yard")
64
- spec.add_development_dependency("redcarpet")
65
+ spec.add_development_dependency("kramdown")
65
66
  end
66
67
 
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2011-2015 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -17,14 +17,14 @@
17
17
  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
18
 
19
19
  require "English"
20
- require "shellwords"
21
20
  require "cgi"
22
-
23
- require "yajl"
21
+ require "json"
24
22
 
25
23
  require "groonga/command"
26
24
 
27
25
  require "groonga/command/parser/error"
26
+ require "groonga/command/parser/command-line-splitter"
27
+ require "groonga/command/parser/load-values-parser"
28
28
  require "groonga/command/parser/version"
29
29
 
30
30
  module Groonga
@@ -91,7 +91,7 @@ module Groonga
91
91
  end
92
92
  parser.on_load_complete do |command|
93
93
  parsed_command = command
94
- parsed_command[:values] ||= Yajl::Encoder.encode(values)
94
+ parsed_command[:values] ||= JSON.generate(values)
95
95
  end
96
96
 
97
97
  consume_data(parser, data)
@@ -117,12 +117,14 @@ module Groonga
117
117
  def initialize
118
118
  reset
119
119
  initialize_hooks
120
+ initialize_load_values_parser
120
121
  end
121
122
 
122
123
  # Streaming parsing command.
123
124
  # @param [String] chunk parsed chunk of command.
124
125
  def <<(chunk)
125
126
  @buffer << chunk
127
+ @buffer.force_encoding("ASCII-8BIT")
126
128
  consume_buffer
127
129
  end
128
130
 
@@ -214,78 +216,10 @@ module Groonga
214
216
  end
215
217
 
216
218
  def consume_load_values(tag)
217
- if @in_load_values
218
- json, separator, rest = @buffer.partition(/[\]},]/)
219
- if @load_value_completed
220
- throw(tag) if separator.empty?
221
- if separator == ","
222
- if /\A\s*\z/ =~ json
223
- @command.original_source << json << separator
224
- @buffer = rest
225
- @load_value_completed = false
226
- return
227
- else
228
- raise Error.new("record separate comma is missing",
229
- @command.original_source.lines.to_a.last,
230
- json)
231
- end
232
- elsif separator == "]"
233
- if /\A\s*\z/ =~ json
234
- @command.original_source << json << separator
235
- @buffer = rest
236
- on_load_complete(@command)
237
- reset
238
- return
239
- end
240
- end
241
- end
242
- @buffer = rest
243
- parse_json(json)
244
- if separator.empty?
245
- throw(tag)
246
- else
247
- @load_value_completed = false
248
- parse_json(separator)
249
- end
250
- else
251
- spaces, start_json, rest = @buffer.partition("[")
252
- unless /\A\s*\z/ =~ spaces
253
- raise Error.new("there are garbages before JSON",
254
- @command.original_source.lines.to_a.last,
255
- spaces)
256
- end
257
- if start_json.empty?
258
- @command.original_source << @buffer
259
- @buffer.clear
260
- throw(tag)
261
- else
262
- @command.original_source << spaces << start_json
263
- @buffer = rest
264
- @json_parser = Yajl::Parser.new
265
- @json_parser.on_parse_complete = lambda do |object|
266
- if object.is_a?(::Array) and @command.columns.nil?
267
- @command.columns = object
268
- on_load_columns(@command, object)
269
- else
270
- on_load_value(@command, object)
271
- end
272
- @load_value_completed = true
273
- end
274
- @in_load_values = true
275
- end
276
- end
277
- end
278
-
279
- def parse_json(json)
280
- @command.original_source << json
281
- begin
282
- @json_parser << json
283
- rescue Yajl::ParseError
284
- before_json = @command.original_source[0..(-json.bytesize)]
285
- message = "invalid JSON: #{$!.message}: <#{json}>:\n"
286
- message << before_json
287
- raise Error.new(message, before_json, json)
288
- end
219
+ throw(tag) if @buffer.empty?
220
+ @command.original_source << @buffer
221
+ @load_values_parser << @buffer
222
+ @buffer.clear
289
223
  end
290
224
 
291
225
  def consume_line(tag)
@@ -321,16 +255,7 @@ module Groonga
321
255
  on_load_columns(@command, @command.columns)
322
256
  end
323
257
  if @command[:values]
324
- values = Yajl::Parser.parse(@command[:values])
325
- if @command.columns.nil? and values.first.is_a?(::Array)
326
- header = values.shift
327
- @command.columns = header
328
- on_load_columns(@command, header)
329
- end
330
- values.each do |value|
331
- on_load_value(@command, value)
332
- end
333
- on_load_complete(@command)
258
+ @load_values_parser << @command[:values]
334
259
  reset
335
260
  else
336
261
  @command.original_source << "\n"
@@ -377,19 +302,8 @@ module Groonga
377
302
  end
378
303
 
379
304
  def parse_command_line(command_line)
380
- name, *arguments = Shellwords.shellwords(command_line)
381
- arguments = arguments.collect do |argument|
382
- # TODO: Groonga supports backslash escape in both single
383
- # quoted argument and double quoted argument ('...' and
384
- # "..."). We should not unescape except single quoted
385
- # argument ('...') because Shellwords.shellwords unescape it
386
- # in double quoted argument. But we can't determine whether
387
- # the argument is single quoted or not after
388
- # Shellwords.shellwords... And groonga supports '\n' -> new line
389
- # conversion. It isn't done by Shellwords.shellwords.
390
- # Should we implement Shellwords.shellwords by ourselves?
391
- unescape_argument_in_command_line(argument)
392
- end
305
+ splitter = CommandLineSplitter.new(command_line)
306
+ name, *arguments = splitter.split
393
307
  pair_arguments = {}
394
308
  ordered_arguments = []
395
309
  until arguments.empty?
@@ -406,31 +320,9 @@ module Groonga
406
320
  command
407
321
  end
408
322
 
409
- def unescape_argument_in_command_line(argument)
410
- argument.gsub(/\\(.)/) do
411
- character = $1
412
- case character
413
- when "b"
414
- "\b"
415
- when "f"
416
- "\f"
417
- when "n"
418
- "\n"
419
- when "r"
420
- "\r"
421
- when "t"
422
- "\t"
423
- else
424
- character
425
- end
426
- end
427
- end
428
-
429
323
  def reset
430
324
  @command = nil
431
325
  @loading = false
432
- @in_load_values = false
433
- @load_value_completed = false
434
326
  @buffer = "".force_encoding("ASCII-8BIT")
435
327
  end
436
328
 
@@ -441,6 +333,27 @@ module Groonga
441
333
  @on_load_value_hook = nil
442
334
  @on_load_complete_hook = nil
443
335
  end
336
+
337
+ def initialize_load_values_parser
338
+ @load_values_parser = LoadValuesParser.new
339
+ @load_values_parser.on_value = lambda do |value|
340
+ if value.is_a?(::Array) and @command.columns.nil?
341
+ @command.columns = value
342
+ on_load_columns(@command, value)
343
+ else
344
+ on_load_value(@command, value)
345
+ end
346
+ end
347
+ @load_values_parser.on_end = lambda do |rest|
348
+ if rest
349
+ original_source_size = @command.original_source.size
350
+ @command.original_source.slice!(original_source_size - rest.size,
351
+ rest.size)
352
+ end
353
+ on_load_complete(@command)
354
+ reset
355
+ end
356
+ end
444
357
  end
445
358
  end
446
359
  end
@@ -0,0 +1,91 @@
1
+ # Copyright (C) 2015 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ require "English"
18
+ require "strscan"
19
+
20
+ module Groonga
21
+ module Command
22
+ class Parser
23
+ class CommandLineSplitter
24
+ def initialize(command_line)
25
+ @command_line = command_line
26
+ end
27
+
28
+ def split
29
+ tokens = []
30
+ scanner = StringScanner.new(@command_line)
31
+ skip_spaces(scanner)
32
+ start_quote = nil
33
+ until scanner.eos?
34
+ if start_quote
35
+ token = ""
36
+ loop do
37
+ chunk = scanner.scan_until(/#{Regexp.escape(start_quote)}/)
38
+ if chunk.nil?
39
+ token = start_quote + token + scanner.rest
40
+ scanner.terminate
41
+ break
42
+ end
43
+ if chunk[-2] == "\\"
44
+ token << chunk
45
+ else
46
+ token << chunk.chomp(start_quote)
47
+ break
48
+ end
49
+ end
50
+ tokens << unescape(token)
51
+ start_quote = nil
52
+ skip_spaces(scanner)
53
+ else
54
+ start_quote = scanner.scan(/['"]/)
55
+ if start_quote.nil?
56
+ tokens << scanner.scan_until(/\S+/)
57
+ skip_spaces(scanner)
58
+ end
59
+ end
60
+ end
61
+ tokens
62
+ end
63
+
64
+ private
65
+ def skip_spaces(scanner)
66
+ scanner.scan(/\s+/)
67
+ end
68
+
69
+ def unescape(token)
70
+ token.gsub(/\\(.)/) do
71
+ character = $1
72
+ case character
73
+ when "b"
74
+ "\b"
75
+ when "f"
76
+ "\f"
77
+ when "n"
78
+ "\n"
79
+ when "r"
80
+ "\r"
81
+ when "t"
82
+ "\t"
83
+ else
84
+ character
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright (C) 2011-2013 Kouhei Sutou <kou@clear-code.com>
3
+ # Copyright (C) 2011-2015 Kouhei Sutou <kou@clear-code.com>
4
4
  #
5
5
  # This library is free software; you can redistribute it and/or
6
6
  # modify it under the terms of the GNU Lesser General Public
@@ -42,9 +42,22 @@ module Groonga
42
42
  location << " " * before.bytesize + "^"
43
43
  location << after
44
44
  else
45
- location << before
46
- location << after
47
- location << " " * before.bytesize + "^"
45
+ before_lines = before.lines.to_a
46
+ after_lines = after.lines.to_a
47
+ last_before_line = before_lines.last
48
+ if last_before_line
49
+ error_offset = last_before_line.bytesize
50
+ else
51
+ error_offset = 0
52
+ end
53
+ before_lines.each do |before_line|
54
+ location << before_line
55
+ end
56
+ location << after_lines.first
57
+ location << " " * error_offset + "^\n"
58
+ after_lines[1..-1].each do |after_line|
59
+ location << after_line
60
+ end
48
61
  end
49
62
  location
50
63
  end
@@ -0,0 +1,209 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2015 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
+ # TODO: Remove me when https://github.com/chef/ffi-yajl/pull/62 is merged.
20
+ require "stringio"
21
+ ENV["FORCE_FFI_YAJL"] = "ffi"
22
+ require "ffi_yajl"
23
+
24
+ module FFI_Yajl
25
+ attach_function :yajl_get_bytes_consumed, [:yajl_handle], :size_t
26
+ end
27
+
28
+ module Groonga
29
+ module Command
30
+ class Parser
31
+ class LoadValuesParser
32
+ attr_writer :on_value
33
+ attr_writer :on_end
34
+ def initialize
35
+ initialize_callbacks
36
+ @handle = nil
37
+ @callbacks_memory = nil
38
+ @on_value = nil
39
+ @on_end = nil
40
+ @containers = []
41
+ @keys = []
42
+ end
43
+
44
+ def <<(data)
45
+ data_size = data.bytesize
46
+ return self if data_size.zero?
47
+
48
+ ensure_handle
49
+
50
+ status = FFI_Yajl.yajl_parse(@handle, data, data_size)
51
+
52
+ if status != :yajl_status_ok
53
+ consumed = FFI_Yajl.yajl_get_bytes_consumed(@handle)
54
+ if consumed > 0
55
+ consumed -= 1
56
+ end
57
+ if @containers.empty?
58
+ message = "there are garbages before JSON"
59
+ else
60
+ message = FFI_Yajl.yajl_get_error(@handle, 0, nil, 0).chomp
61
+ end
62
+ begin
63
+ raise Error.new(message,
64
+ data[0, consumed],
65
+ data[consumed..-1])
66
+ ensure
67
+ finalize_handle
68
+ end
69
+ end
70
+
71
+ if @containers.empty?
72
+ consumed = FFI_Yajl.yajl_get_bytes_consumed(@handle)
73
+ begin
74
+ if consumed < data_size
75
+ @on_end.call(data[consumed..-1])
76
+ else
77
+ @on_end.call(nil)
78
+ end
79
+ ensure
80
+ finalize_handle
81
+ end
82
+ end
83
+
84
+ self
85
+ end
86
+
87
+ private
88
+ def callback(*arguments)
89
+ FFI::Function.new(:int, [:pointer, *arguments]) do |_, *values|
90
+ yield(*values)
91
+ 1
92
+ end
93
+ end
94
+
95
+ def initialize_callbacks
96
+ @null_callback = callback do
97
+ push_value(nil)
98
+ end
99
+ @boolean_callback = callback(:int) do |c_boolean|
100
+ push_value(c_boolean != 0)
101
+ end
102
+ @number_callback = callback(:string, :size_t) do |data, size|
103
+ number_data = data.slice(0, size)
104
+ if /[\.eE]/ =~ number_data
105
+ number = number_data.to_f
106
+ else
107
+ number = number_data.to_i
108
+ end
109
+ push_value(number)
110
+ end
111
+ @string_callback = callback(:string, :size_t) do |data, size|
112
+ string = data.slice(0, size)
113
+ string.force_encoding(Encoding::UTF_8)
114
+ push_value(string)
115
+ end
116
+ @start_map_callback = callback do
117
+ push_container({})
118
+ end
119
+ @map_key_callback = callback(:string, :size_t) do |data, size|
120
+ key = data.slice(0, size)
121
+ key.force_encoding(Encoding::UTF_8)
122
+ @keys.push(key)
123
+ end
124
+ @end_map_callback = callback do
125
+ pop_container
126
+ end
127
+ @start_array_callback = callback do
128
+ push_container([])
129
+ end
130
+ @end_array_callback = callback do
131
+ pop_container
132
+ end
133
+ end
134
+
135
+ def push_container(container)
136
+ @containers.push(container)
137
+ end
138
+
139
+ def pop_container
140
+ container = @containers.pop
141
+ if @containers.size == 1
142
+ @on_value.call(container)
143
+ else
144
+ push_value(container)
145
+ end
146
+ end
147
+
148
+ def push_value(value)
149
+ container = @containers.last
150
+ case container
151
+ when Hash
152
+ container[@keys.pop] = value
153
+ when Array
154
+ container.push(value)
155
+ end
156
+ end
157
+
158
+ def ensure_handle
159
+ return if @handle
160
+ initialize_handle
161
+ end
162
+
163
+ def initialize_handle
164
+ @callbacks_memory = FFI::MemoryPointer.new(FFI_Yajl::YajlCallbacks)
165
+ callbacks = FFI_Yajl::YajlCallbacks.new(@callbacks_memory)
166
+ callbacks[:yajl_null] = @null_callback
167
+ callbacks[:yajl_boolean] = @boolean_callback
168
+ callbacks[:yajl_integer] = nil
169
+ callbacks[:yajl_double] = nil
170
+ callbacks[:yajl_number] = @number_callback
171
+ callbacks[:yajl_string] = @string_callback
172
+ callbacks[:yajl_start_map] = @start_map_callback
173
+ callbacks[:yajl_map_key] = @map_key_callback
174
+ callbacks[:yajl_end_map] = @end_map_callback
175
+ callbacks[:yajl_start_array] = @start_array_callback
176
+ callbacks[:yajl_end_array] = @end_array_callback
177
+
178
+ @handle = FFI_Yajl.yajl_alloc(@callbacks_memory, nil, nil)
179
+ FFI_Yajl.yajl_config(@handle,
180
+ :yajl_allow_trailing_garbage,
181
+ :int,
182
+ 1)
183
+ @finalizer = Finalizer.new(@handle)
184
+ ObjectSpace.define_finalizer(self, @finalizer)
185
+ end
186
+
187
+ def finalize_handle
188
+ @callbacks_memory = nil
189
+ @finalizer.call(object_id)
190
+ ObjectSpace.undefine_finalizer(self)
191
+ @finalizer = nil
192
+ @handle = nil
193
+ end
194
+
195
+ class Finalizer
196
+ def initialize(handle)
197
+ @handle = handle
198
+ end
199
+
200
+ def call(object_id)
201
+ return if @handle.nil?
202
+ FFI_Yajl.yajl_free(@handle)
203
+ @handle = nil
204
+ end
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -19,7 +19,7 @@
19
19
  module Groonga
20
20
  module Command
21
21
  class Parser
22
- VERSION = "1.0.3"
22
+ VERSION = "1.0.4"
23
23
  end
24
24
  end
25
25
  end
data/test/run-test.rb CHANGED
@@ -36,6 +36,13 @@ end
36
36
  $LOAD_PATH.unshift(lib_dir)
37
37
  $LOAD_PATH.unshift(test_dir)
38
38
 
39
+ # TODO: Remove me when suppress warnings patches are merged int
40
+ # ffi_yajl.
41
+ require "stringio"
42
+ $VERBOSE = false
43
+ require "ffi_yajl/ffi"
44
+ $VERBOSE = true
45
+
39
46
  require "groonga-command-parser-test-utils"
40
47
 
41
48
  ENV["TEST_UNIT_MAX_DIFF_TARGET_STRING_SIZE"] ||= "5000"
@@ -0,0 +1,102 @@
1
+ # -*- coding: utf-8 -*-
2
+ #
3
+ # Copyright (C) 2015 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 CommandLineSplitterTest < Test::Unit::TestCase
20
+ def split(command_line)
21
+ splitter = Groonga::Command::Parser::CommandLineSplitter.new(command_line)
22
+ splitter.split
23
+ end
24
+
25
+ test "name only" do
26
+ assert_equal(["status"],
27
+ split("status"))
28
+ end
29
+
30
+ sub_test_case "arguments" do
31
+ sub_test_case "no quote" do
32
+ test "value" do
33
+ assert_equal(["select", "Logs"],
34
+ split("select Logs"))
35
+ end
36
+
37
+ test "key and value" do
38
+ assert_equal(["select", "--table", "Logs"],
39
+ split("select --table Logs"))
40
+ end
41
+
42
+ test "multibyte character" do
43
+ assert_equal(["select", "テーブル"],
44
+ split("select テーブル"))
45
+ end
46
+ end
47
+
48
+ sub_test_case "single quote" do
49
+ test "value" do
50
+ assert_equal(["select", "Logs"],
51
+ split("select 'Logs'"))
52
+ end
53
+
54
+ test "key and value" do
55
+ assert_equal(["select", "--table", "Logs"],
56
+ split("select '--table' 'Logs'"))
57
+ end
58
+
59
+ test "space" do
60
+ assert_equal(["select", "Logs Table"],
61
+ split("select 'Logs Table'"))
62
+ end
63
+
64
+ test "escape quote" do
65
+ assert_equal(["select", "Logs \' Table"],
66
+ split("select 'Logs \\' Table'"))
67
+ end
68
+
69
+ test "new line" do
70
+ assert_equal(["select", "Logs \n Table"],
71
+ split("select 'Logs \\n Table'"))
72
+ end
73
+ end
74
+
75
+ sub_test_case "double quote" do
76
+ test "value" do
77
+ assert_equal(["select", "Logs"],
78
+ split("select \"Logs\""))
79
+ end
80
+
81
+ test "key and value" do
82
+ assert_equal(["select", "--table", "Logs"],
83
+ split("select \"--table\" \"Logs\""))
84
+ end
85
+
86
+ test "space" do
87
+ assert_equal(["select", "Logs Table"],
88
+ split("select \"Logs Table\""))
89
+ end
90
+
91
+ test "escape quote" do
92
+ assert_equal(["select", "Logs \" Table"],
93
+ split("select \"Logs \\\" Table\""))
94
+ end
95
+
96
+ test "new line" do
97
+ assert_equal(["select", "Logs \n Table"],
98
+ split("select \"Logs \\n Table\""))
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,149 @@
1
+ # Copyright (C) 2015 Kouhei Sutou <kou@clear-code.com>
2
+ #
3
+ # This library is free software; you can redistribute it and/or
4
+ # modify it under the terms of the GNU Lesser General Public
5
+ # License as published by the Free Software Foundation; either
6
+ # version 2.1 of the License, or (at your option) any later version.
7
+ #
8
+ # This library is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
+ # Lesser General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU Lesser General Public
14
+ # License along with this library; if not, write to the Free Software
15
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
+
17
+ class LoadValuesParserTest < Test::Unit::TestCase
18
+ def setup
19
+ @values = []
20
+ @parser = Groonga::Command::Parser::LoadValuesParser.new
21
+ @parser.on_value = lambda do |value|
22
+ @values << value
23
+ end
24
+ @parser.on_end = lambda do |rest|
25
+ end
26
+ end
27
+
28
+ def parse(data)
29
+ data.each_line do |line|
30
+ @parser << line
31
+ end
32
+ @values
33
+ end
34
+
35
+ sub_test_case "array" do
36
+ test "empty" do
37
+ assert_equal([[]],
38
+ parse("[[]]"))
39
+ end
40
+
41
+ test "no container" do
42
+ assert_equal([[1, "abc", 2.9]],
43
+ parse(<<-JSON))
44
+ [
45
+ [1, "abc", 2.9]
46
+ ]
47
+ JSON
48
+ end
49
+
50
+ test "array" do
51
+ assert_equal([[[1, "abc", 2.9]]],
52
+ parse(<<-JSON))
53
+ [
54
+ [[1, "abc", 2.9]]
55
+ ]
56
+ JSON
57
+ end
58
+
59
+ test "object" do
60
+ assert_equal([
61
+ [
62
+ {
63
+ "number" => 1,
64
+ "string" => "abc",
65
+ "double" => 2.9,
66
+ },
67
+ ],
68
+ ],
69
+ parse(<<-JSON))
70
+ [
71
+ [
72
+ {
73
+ "number": 1,
74
+ "string": "abc",
75
+ "double": 2.9
76
+ }
77
+ ]
78
+ ]
79
+ JSON
80
+ end
81
+ end
82
+
83
+ sub_test_case "object" do
84
+ test "empty" do
85
+ assert_equal([{}],
86
+ parse("[{}]"))
87
+ end
88
+
89
+ test "no container" do
90
+ assert_equal([
91
+ {
92
+ "number" => 1,
93
+ "string" => "abc",
94
+ "double" => 2.9,
95
+ },
96
+ ],
97
+ parse(<<-JSON))
98
+ [
99
+ {
100
+ "number": 1,
101
+ "string": "abc",
102
+ "double": 2.9
103
+ }
104
+ ]
105
+ JSON
106
+ end
107
+
108
+ test "array" do
109
+ assert_equal([
110
+ {
111
+ "array" => [1, "abc", 2.9],
112
+ },
113
+ ],
114
+ parse(<<-JSON))
115
+ [
116
+ {
117
+ "array": [1, "abc", 2.9]
118
+ }
119
+ ]
120
+ JSON
121
+ end
122
+
123
+ test "object" do
124
+ assert_equal([
125
+ {
126
+ "object" => {
127
+ "number" => 1,
128
+ "string" => "abc",
129
+ "double" => 2.9,
130
+ },
131
+ },
132
+ ],
133
+ parse(<<-JSON))
134
+ [
135
+ {
136
+ "object": {
137
+ "number": 1,
138
+ "string": "abc",
139
+ "double": 2.9
140
+ }
141
+ }
142
+ ]
143
+ [
144
+ 1
145
+ ]
146
+ JSON
147
+ end
148
+ end
149
+ end
data/test/test-parser.rb CHANGED
@@ -228,10 +228,11 @@ EOC
228
228
  expected_events << [:load_columns, <<-EOC.chomp, ["_key", "name"]]
229
229
  load --table Users --columns "_key, name"
230
230
  EOC
231
- expected_events << [:load_value, <<-EOC.chomp, ["alice", "Alice"]]
231
+ expected_events << [:load_value, <<-EOC, ["alice", "Alice"]]
232
232
  load --table Users --columns "_key, name"
233
233
  [
234
234
  ["alice", "Alice"]
235
+ ]
235
236
  EOC
236
237
  expected_events << [:load_complete, <<-EOC.chomp]
237
238
  load --table Users --columns "_key, name"
@@ -254,16 +255,19 @@ EOC
254
255
  expected_events << [:load_start, <<-EOC.chomp]
255
256
  load --table Users
256
257
  EOC
257
- expected_events << [:load_columns, <<-EOC.chomp, ["_key", "name"]]
258
+ expected_events << [:load_columns, <<-EOC, ["_key", "name"]]
258
259
  load --table Users
259
260
  [
260
- ["_key", "name"]
261
+ ["_key", "name"],
262
+ ["alice", "Alice"]
263
+ ]
261
264
  EOC
262
- expected_events << [:load_value, <<-EOC.chomp, ["alice", "Alice"]]
265
+ expected_events << [:load_value, <<-EOC, ["alice", "Alice"]]
263
266
  load --table Users
264
267
  [
265
268
  ["_key", "name"],
266
269
  ["alice", "Alice"]
270
+ ]
267
271
  EOC
268
272
  expected_events << [:load_complete, <<-EOC.chomp]
269
273
  load --table Users
@@ -289,17 +293,20 @@ EOC
289
293
  load --table Users
290
294
  EOC
291
295
  value = {"_key" => "alice", "name" => "Alice"}
292
- expected_events << [:load_value, <<-EOC.chomp, value]
296
+ expected_events << [:load_value, <<-EOC, value]
293
297
  load --table Users
294
298
  [
295
- {"_key": "alice", "name": "Alice"}
299
+ {"_key": "alice", "name": "Alice"},
300
+ {"_key": "bob", "name": "Bob"}
301
+ ]
296
302
  EOC
297
303
  value = {"_key" => "bob", "name" => "Bob"}
298
- expected_events << [:load_value, <<-EOC.chomp, value]
304
+ expected_events << [:load_value, <<-EOC, value]
299
305
  load --table Users
300
306
  [
301
307
  {"_key": "alice", "name": "Alice"},
302
308
  {"_key": "bob", "name": "Bob"}
309
+ ]
303
310
  EOC
304
311
  expected_events << [:load_complete, <<-EOC.chomp]
305
312
  load --table Users
@@ -327,9 +334,14 @@ EOS
327
334
  end
328
335
 
329
336
  def test_no_record_separate_comma
330
- message = "record separate comma is missing"
331
- before = "{\"_key\": \"alice\", \"name\": \"Alice\"}"
332
- after = "\n{\"_key\": \"bob\""
337
+ message = "parse error: after array element, I expect ',' or ']'"
338
+ before = <<-BEFORE
339
+ [
340
+ {"_key": "alice", "name": "Alice"}
341
+ BEFORE
342
+ after = <<-AFTER
343
+ {"_key": "bob", "name": "Bob"}
344
+ AFTER
333
345
  error = Groonga::Command::Parser::Error.new(message, before, after)
334
346
  assert_raise(error) do
335
347
  @parser << <<-EOC
@@ -343,17 +355,22 @@ EOC
343
355
 
344
356
  def test_garbage_before_json
345
357
  message = "there are garbages before JSON"
346
- before = "load --table Users\n"
347
- after = "XXX\n"
358
+ before = ""
359
+ after = <<-AFTER
360
+ XXX
361
+ [
362
+ {"_key": "alice", "name": "Alice"}
363
+ ]
364
+ AFTER
348
365
  error = Groonga::Command::Parser::Error.new(message, before, after)
349
366
  assert_raise(error) do
350
- @parser << <<-EOC
367
+ @parser << <<-JSON
351
368
  load --table Users
352
369
  XXX
353
370
  [
354
371
  {"_key": "alice", "name": "Alice"}
355
372
  ]
356
- EOC
373
+ JSON
357
374
  end
358
375
  end
359
376
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groonga-command-parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kouhei Sutou
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-12 00:00:00.000000000 Z
11
+ date: 2015-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: groonga-command
@@ -25,7 +25,21 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.0.9
27
27
  - !ruby/object:Gem::Dependency
28
- name: yajl-ruby
28
+ name: ffi
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ffi-yajl
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
45
  - - ">="
@@ -123,7 +137,7 @@ dependencies:
123
137
  - !ruby/object:Gem::Version
124
138
  version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
- name: redcarpet
140
+ name: kramdown
127
141
  requirement: !ruby/object:Gem::Requirement
128
142
  requirements:
129
143
  - - ">="
@@ -153,11 +167,15 @@ files:
153
167
  - doc/text/news.md
154
168
  - groonga-command-parser.gemspec
155
169
  - lib/groonga/command/parser.rb
170
+ - lib/groonga/command/parser/command-line-splitter.rb
156
171
  - lib/groonga/command/parser/command/groonga-command-convert-format.rb
157
172
  - lib/groonga/command/parser/error.rb
173
+ - lib/groonga/command/parser/load-values-parser.rb
158
174
  - lib/groonga/command/parser/version.rb
159
175
  - test/groonga-command-parser-test-utils.rb
160
176
  - test/run-test.rb
177
+ - test/test-command-line-splitter.rb
178
+ - test/test-load-value-parser.rb
161
179
  - test/test-parser.rb
162
180
  homepage: https://github.com/groonga/groonga-command-parser
163
181
  licenses:
@@ -185,7 +203,9 @@ specification_version: 4
185
203
  summary: Groonga-command-parser is a Ruby library to parses [groonga](http://groonga.org/)'s
186
204
  command syntax. You can write a program to process groonga's command by using groonga-command-parser.
187
205
  test_files:
188
- - test/test-parser.rb
189
- - test/run-test.rb
190
206
  - test/groonga-command-parser-test-utils.rb
207
+ - test/run-test.rb
208
+ - test/test-command-line-splitter.rb
209
+ - test/test-load-value-parser.rb
210
+ - test/test-parser.rb
191
211
  has_rdoc: