redis-protocol 0.1.2 → 0.1.3

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.1.3
@@ -1,10 +1,8 @@
1
1
  # start with *, $, alpha-numeric, \r
2
2
 
3
3
  module RedisProtocol
4
- class Response
5
-
6
- end
7
4
  end
8
5
 
6
+ require 'redis-protocol/unified_protocol'
9
7
  require 'redis-protocol/request'
10
- require 'redis-protocol/static_request'
8
+ require 'redis-protocol/response'
@@ -1,134 +1,38 @@
1
+ require_relative 'unified_protocol'
2
+
1
3
  class RedisProtocol::Request
2
- attr_reader :raw_data, :type, :component_count, :components
4
+ attr_reader :raw_data, :type, :components
3
5
 
4
6
  def initialize(data)
5
7
  @raw_data = data
6
- @type, @components, @component_count, @current = nil, [], 0, ''
8
+ @type, @components= nil, []
7
9
  @next_length = 0
8
10
  parse
9
11
  end
10
12
 
13
+ def component_count
14
+ @components.length
15
+ end
16
+
11
17
  private
12
18
 
13
19
  def parse
14
- @raw_data.chars.each do |c|
15
- @type ||= c.eql?('*') ? :standard : :inline
20
+ @type ||= @raw_data[0].eql?('*') ? :standard : :inline
16
21
 
17
- send(@type, c)
18
- end
19
-
20
- complete_token
21
- @component_count += @components.count if @type == :inline
22
+ @components = send(@type)
22
23
  end
23
24
  =begin
24
25
  Inline Parser
25
26
  =end
26
- def inline(char)
27
- c = char.strip
28
-
29
- if c.empty? and not @current.empty?
30
- complete_token
31
- else
32
- @current += c
33
- end
27
+ def inline
28
+ @raw_data.split
34
29
  end
35
30
 
36
31
  =begin
37
32
  Standard Parser
38
33
  =end
39
34
 
40
- def standard(char)
41
- @token_type ||= detect_token_type char
42
- @doubt_str = ''
43
-
44
- send(@token_type, char)
45
- end
46
-
47
- def args_counts(char)
48
- return if char.eql? '*'
49
-
50
- tokenize char do
51
- @component_count = @current.to_i
52
- clean_context
53
- end
54
- end
55
-
56
- def length(char)
57
- return if char.eql? '$'
58
-
59
- tokenize char do
60
- @next_length = @current.to_i
61
- clean_context
62
- end
63
- end
64
-
65
- def payload(char)
66
- return if char.eql? '$'
67
-
68
- @next_length = 0 if @current.length == @next_length
69
-
70
- tokenize char do
71
- @next_length = 0
72
- complete_token
73
- end
74
- end
75
-
76
- def tokenize(char, &block)
77
- reach_delimiter = consume_delimiter(char)
78
-
79
- if reach_delimiter
80
- if @doubt_str.empty?
81
- block.call
82
- end
83
- return
84
- end
85
-
86
- @current += char
87
- end
88
-
89
- =begin
90
- Common Utilities
91
- =end
92
-
93
- def complete_token
94
- @components << @current
95
- clean_context
96
- end
97
-
98
- def clean_context
99
- @current = ''
100
- @token_type = nil
101
- end
102
-
103
- def consume_delimiter(char)
104
- return false if @next_length != 0
105
-
106
- if @doubt and char.eql? "\n"
107
- @doubt_str = ''
108
- @doubt = false
109
- return true
110
- end
111
-
112
- if not @doubt and char.eql? "\r"
113
- @doubt_str = "\r"
114
- @doubt = true
115
- return true
116
- end
117
-
118
- @doubt = false
119
- @current += @doubt_str unless @doubt_str.empty?
120
- @doubt_str = ''
121
- return false
122
- end
123
-
124
- def detect_token_type(char)
125
- case char
126
- when '*'
127
- :args_counts
128
- when '$'
129
- :length
130
- else
131
- :payload
132
- end
35
+ def standard
36
+ RedisProtocol::UnifiedProtocol.parse @raw_data
133
37
  end
134
38
  end
@@ -0,0 +1,22 @@
1
+ require_relative 'unified_protocol'
2
+
3
+ class RedisProtocol::Response
4
+ attr_reader :result, :raw_data
5
+
6
+ def <<(value)
7
+ @result += value.result
8
+ unless value.result.empty?
9
+ start = @raw_data.index('*') + 1
10
+ finish = @raw_data.index(RedisProtocol::UnifiedProtocol::DELIMITER)
11
+ @raw_data[start...finish] = @result.length.to_s
12
+
13
+ @raw_data += value.raw_data[(value.raw_data.index(RedisProtocol::UnifiedProtocol::DELIMITER) + RedisProtocol::UnifiedProtocol::DELIMITER.length)..-1]
14
+ end
15
+ end
16
+
17
+ def initialize(data)
18
+ @raw_data = data
19
+ @raw_data += RedisProtocol::UnifiedProtocol::DELIMITER unless @raw_data.end_with? RedisProtocol::UnifiedProtocol::DELIMITER
20
+ @result = RedisProtocol::UnifiedProtocol.parse @raw_data
21
+ end
22
+ end
@@ -0,0 +1,56 @@
1
+ class RedisProtocol::UnifiedProtocol
2
+ DELIMITER="\r\n"
3
+ class << self
4
+ def parse(data)
5
+ processed, index = 0, data.index(DELIMITER)
6
+ index ||= data.length
7
+ result = case data[processed]
8
+ when '*'
9
+ parse_multi_chunked data
10
+ when '$'
11
+ parse_chunked data
12
+ when '+'
13
+ parse_status data
14
+ when '-'
15
+ parse_error data
16
+ when ':'
17
+ parse_integer data
18
+ end
19
+ end
20
+
21
+ def parse_multi_chunked(data)
22
+ index = data.index DELIMITER
23
+ count = data[1...index].to_i
24
+ result = []
25
+ start = index + DELIMITER.length
26
+ 1.upto(count) do |_|
27
+ chunk, length = parse_chunked data, start
28
+ start = length + DELIMITER.length
29
+ result += chunk
30
+ end
31
+
32
+ result
33
+ end
34
+
35
+ def parse_chunked(data, start=0)
36
+ index = data.index DELIMITER, start
37
+ length = data[(start+1)...index].to_i
38
+ return nil if length == -1
39
+ result = [data[index+DELIMITER.length, length]]
40
+
41
+ start == 0 ? result : [result, index+DELIMITER.length + length]
42
+ end
43
+
44
+ def parse_status(data)
45
+ [true, data[1..-1]]
46
+ end
47
+
48
+ def parse_error(data)
49
+ [false, data[1..-1]]
50
+ end
51
+
52
+ def parse_integer(data)
53
+ [data[1..-1].to_i]
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,22 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe RedisProtocol::Response do
4
+ it 'should be parse response' do
5
+ result = RedisProtocol::Response.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n"
6
+ result.result[0].should eql 'set'
7
+ result.result[1].should eql 'test'
8
+ result.result[2].should eql 'hello'
9
+ end
10
+
11
+ it 'should be parse aggregated response' do
12
+ result = RedisProtocol::Response.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n"
13
+ additional = RedisProtocol::Response.new "*2\r\n$5\r\nworld\r\n$6\r\nturtle\r\n"
14
+ result << additional
15
+ result.result[0].should eql 'set'
16
+ result.result[1].should eql 'test'
17
+ result.result[2].should eql 'hello'
18
+ result.result[3].should eql 'world'
19
+ result.result[4].should eql 'turtle'
20
+ result.raw_data.should eql "*5\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello\r\n$5\r\nworld\r\n$6\r\nturtle\r\n"
21
+ end
22
+ end
@@ -0,0 +1,34 @@
1
+ require_relative '../spec_helper'
2
+
3
+ describe RedisProtocol::UnifiedProtocol do
4
+ it 'should parse a redis unified protocol based message' do
5
+ result = RedisProtocol::UnifiedProtocol.parse "$4\r\nping"
6
+ result[0].should eql 'ping'
7
+ end
8
+
9
+ it 'should parse multi-chunked format' do
10
+ result = RedisProtocol::UnifiedProtocol.parse "*1\r\n$4\r\nping"
11
+ result[0].should eql 'ping'
12
+ result = RedisProtocol::UnifiedProtocol.parse "*2\r\n$4\r\nkeys\r\n$1\r\n*\r\n"
13
+ result[0].should eql 'keys'
14
+ result[1].should eql '*'
15
+ end
16
+
17
+ it 'should parse status ok' do
18
+ result = RedisProtocol::UnifiedProtocol.parse '+ok'
19
+ result[0].should be true
20
+ result[1].should eql 'ok'
21
+ end
22
+
23
+ it 'should parse error with message' do
24
+ result = RedisProtocol::UnifiedProtocol.parse "-ERR unknown command 'foobar'"
25
+ result[0].should be false
26
+ result[1].should eql "ERR unknown command 'foobar'"
27
+ end
28
+
29
+ it 'should parse integer only return' do
30
+ result = RedisProtocol::UnifiedProtocol.parse ":1000\r\n"
31
+ result[0].should be 1000
32
+ RedisProtocol::UnifiedProtocol.parse(':-30')[0].should be -30
33
+ end
34
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-protocol
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-12 00:00:00.000000000 Z
12
+ date: 2013-05-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -127,9 +127,11 @@ files:
127
127
  - VERSION
128
128
  - lib/redis-protocol.rb
129
129
  - lib/redis-protocol/request.rb
130
- - lib/redis-protocol/static_request.rb
130
+ - lib/redis-protocol/response.rb
131
+ - lib/redis-protocol/unified_protocol.rb
131
132
  - spec/redis-protocol/request_spec.rb
132
- - spec/redis-protocol/static_request_spec.rb
133
+ - spec/redis-protocol/response_spec.rb
134
+ - spec/redis-protocol/unified_protocol_spec.rb
133
135
  - spec/redis-protocol_spec.rb
134
136
  - spec/spec_helper.rb
135
137
  homepage: http://github.com/astral1/redis-protocol
@@ -147,7 +149,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
147
149
  version: '0'
148
150
  segments:
149
151
  - 0
150
- hash: 3700473079050196087
152
+ hash: -1569199417732497117
151
153
  required_rubygems_version: !ruby/object:Gem::Requirement
152
154
  none: false
153
155
  requirements:
@@ -1,77 +0,0 @@
1
- class RedisProtocol::StaticRequest
2
- attr_reader :raw_data, :type, :components, :component_count
3
- OP_DELIMITER = "\r\n"
4
-
5
- def initialize(data)
6
- @raw_data = data
7
- parse_request
8
- end
9
-
10
- def parse_request
11
- @type = check_request_type
12
-
13
- send @type
14
- end
15
-
16
- def self.recognize(data)
17
- (check_request_type == :inline) ? data.split[0] : data.split(OP_DELIMITER)[2]
18
- end
19
-
20
- def check_request_type
21
- (@raw_data.start_with? '*') ? :standard : :inline
22
- end
23
-
24
- def standard
25
- components = []
26
- data = @raw_data.dup
27
- operand_length, payload = operand_length_for data.split(OP_DELIMITER)
28
- 1.upto(operand_length).each do |_|
29
- data, payload = unpack_payload(payload)
30
- components << data
31
- end
32
- @component_count = components.count
33
- @components = components
34
- end
35
-
36
- def operand_length_for(data)
37
- operand_length, payload = next_token data
38
- raise "invalid packet : #{operand_length}" unless operand_length.start_with? '*'
39
- operand_length = operand_length[1..-1].to_i
40
-
41
- [operand_length, payload]
42
- end
43
-
44
- def unpack_payload(payload)
45
- field_length, payload = next_token payload
46
- raise "invalid length format : #{field_length}" unless field_length.start_with? '$'
47
- field_length = field_length[1..-1].to_i
48
- next_token payload, field_length
49
- end
50
-
51
- def inline
52
- @components = @raw_data.split
53
- @component_count = @components.count
54
- @components
55
- end
56
-
57
- def next_token(data, length = 0)
58
- if data[0].length == length || length == 0
59
- [data[0], data[1..-1]]
60
- else
61
- multiline_token(data, length)
62
- end
63
- end
64
-
65
- def multiline_token(data, length)
66
- val, index = '', 0
67
- data.each do |token|
68
- val.empty? ? val = token : val += "\r\n#{token}"
69
- index += 1
70
-
71
- break if val.length == length
72
- end
73
- [val, data[index..-1]]
74
- end
75
-
76
- private :next_token, :standard, :inline, :operand_length_for, :unpack_payload, :multiline_token
77
- end
@@ -1,54 +0,0 @@
1
- require_relative '../spec_helper'
2
-
3
- describe RedisProtocol::StaticRequest do
4
- it 'initialize with data' do
5
- RedisProtocol::StaticRequest.new 'ping'
6
- end
7
-
8
- it 'should get raw data as original' do
9
- request = RedisProtocol::StaticRequest.new 'ping'
10
- request.raw_data.should eql 'ping'
11
- end
12
-
13
- it 'should parse inline command' do
14
- request = RedisProtocol::StaticRequest.new 'ping'
15
- request.components[0].should eql 'ping'
16
- request.component_count.should be 1
17
- end
18
-
19
- it 'should parse inline command with parameter' do
20
- request = RedisProtocol::StaticRequest.new 'keys *'
21
- request.components[0].should eql 'keys'
22
- request.component_count.should be 2
23
- end
24
-
25
- it 'should parse inline command with multi whitespaces' do
26
- request = RedisProtocol::StaticRequest.new 'keys *'
27
- request.components[0].should eql 'keys'
28
- request.component_count.should be 2
29
- end
30
-
31
- it 'should parse standard command' do
32
- request = RedisProtocol::StaticRequest.new "*1\r\n$4\r\nping"
33
- request.components[0].should eql 'ping'
34
- request.component_count.should be 1
35
- end
36
-
37
- it 'should parse standard command with parameter' do
38
- request = RedisProtocol::StaticRequest.new "*2\r\n$4\r\nkeys\r\n$1\r\n*"
39
- request.components[0].should eql 'keys'
40
- request.component_count.should be 2
41
- end
42
-
43
- it 'should parse standard command with multiple parameters' do
44
- request = RedisProtocol::StaticRequest.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$5\r\nhello"
45
- request.components[0].should eql 'set'
46
- request.component_count.should be 3
47
- end
48
-
49
- it 'should parse standard command containing delimiter' do
50
- request = RedisProtocol::StaticRequest.new "*3\r\n$3\r\nset\r\n$4\r\ntest\r\n$12\r\nhello\r\nworld"
51
- request.components[2].should eql "hello\r\nworld"
52
- request.component_count.should be 3
53
- end
54
- end