groonga-command-parser 1.0.3 → 1.0.4

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.
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: