File Coverage

blib/lib/Protocol/Redis.pm
Criterion Covered Total %
statement 76 79 96.2
branch 29 34 85.2
condition 4 4 100.0
subroutine 10 10 100.0
pod 6 6 100.0
total 125 133 93.9


line stmt bran cond sub pod time code
1             package Protocol::Redis;
2              
3 1     1   7 use strict;
  1         2  
  1         32  
4 1     1   6 use warnings;
  1         1  
  1         28  
5 1     1   18 use 5.008_001;
  1         3  
6              
7             our $VERSION = '1.0010';
8              
9             require Carp;
10              
11             sub new {
12 1     1 1 49 my $class = shift;
13 1 50       5 $class = ref $class if ref $class;
14              
15 1         3 my $self = {@_};
16              
17 1 50       3 return unless $self->{api} == '1';
18              
19 1         3 bless $self, $class;
20              
21 1         18 $self->on_message(delete $self->{on_message});
22 1         2 $self->{_messages} = [];
23              
24 1         3 $self;
25             }
26              
27             sub api {
28 1     1 1 3 my $self = shift;
29              
30 1         6 $self->{api};
31             }
32              
33             my %simple_types = ('+' => 1, '-' => 1, ':' => 1);
34             my $rn = "\r\n";
35              
36             sub encode {
37 11     11 1 25 my $self = shift;
38              
39 11         90 my $encoded_message = '';
40 11         30 while (@_) {
41 17         23 my $message = shift;
42              
43             # Bulk string
44 17 100       53 if ($message->{type} eq '$') {
    100          
    50          
45 9 100       38 if (defined $message->{data}) {
46 7         26 $encoded_message .= '$' . length($message->{data}) . "\r\n" . $message->{data} . "\r\n";
47             }
48             else {
49 2         9 $encoded_message .= "\$-1\r\n";
50             }
51             }
52             # Array (multi bulk)
53             elsif ($message->{type} eq '*') {
54 5 100       12 if (defined $message->{data}) {
55 4         6 $encoded_message .= '*' . scalar(@{$message->{data}}) . "\r\n";
  4         12  
56 4         8 unshift @_, @{$message->{data}};
  4         11  
57             }
58             else {
59 1         7 $encoded_message .= "*-1\r\n";
60             }
61             }
62             # String, error, integer
63             elsif (exists $simple_types{$message->{type}}) {
64 3         12 $encoded_message .= $message->{type} . $message->{data} . "\r\n";
65             }
66             else {
67 0         0 Carp::croak(qq/Unknown message type $message->{type}/);
68             }
69             }
70              
71 11         52 return $encoded_message;
72             }
73              
74             sub get_message {
75 29     29 1 61 shift @{$_[0]->{_messages}};
  29         246  
76             }
77              
78             sub on_message {
79 4     4 1 9 my ($self, $cb) = @_;
80 4   100     27 $self->{_on_message_cb} = $cb || \&_gather_messages;
81             }
82              
83             sub parse {
84 37     37 1 149 my $self = shift;
85 37         73 $self->{_buffer}.= shift;
86              
87 37   100     90 my $message = $self->{_message} ||= {};
88 37         61 my $buffer = \$self->{_buffer};
89              
90             CHUNK:
91 37         127 while ((my $pos = index($$buffer, "\r\n")) != -1) {
92             # Check our state: are we parsing new message or completing existing
93 53 100       112 if (!$message->{type}) {
94 50 50       86 if ($pos < 1) {
95 0         0 Carp::croak(qq/Unexpected input "$$buffer"/);
96             }
97              
98 50         141 $message->{type} = substr $$buffer, 0, 1;
99 50         98 $message->{_argument} = substr $$buffer, 1, $pos - 1;
100 50         92 substr $$buffer, 0, $pos + 2, ''; # Remove type + argument + \r\n
101             }
102              
103             # Simple Strings, Errors, Integers
104 53 100       162 if (exists $simple_types{$message->{type}}) {
    100          
    50          
105 14         30 $message->{data} = delete $message->{_argument};
106             }
107             # Bulk Strings
108             elsif ($message->{type} eq '$') {
109 29 100       104 if ($message->{_argument} eq '-1') {
    100          
110 4         10 $message->{data} = undef;
111             }
112             elsif (length($$buffer) >= $message->{_argument} + 2) {
113 22         47 $message->{data} = substr $$buffer, 0, $message->{_argument}, '';
114 22         38 substr $$buffer, 0, 2, ''; # Remove \r\n
115             }
116             else {
117             return # Wait more data
118 3         8 }
119             }
120             # Arrays
121             elsif ($message->{type} eq '*') {
122 10 100       21 if ($message->{_argument} eq '-1') {
123 1         2 $message->{data} = undef;
124             } else {
125 9         14 $message->{data} = [];
126 9 100       56 if ($message->{_argument} > 0) {
127 8         20 $message = $self->{_message} = {_parent => $message};
128 8         24 next;
129             }
130             }
131             }
132             # Invalid input
133             else {
134 0         0 Carp::croak(qq/Unexpected input "$self->{_message}{type}"/);
135             }
136              
137 42         99 delete $message->{_argument};
138 42         56 delete $self->{_message};
139              
140             # Fill parents with data
141 42         97 while (my $parent = delete $message->{_parent}) {
142 18         23 push @{$parent->{data}}, $message;
  18         34  
143              
144 18 100       25 if (@{$parent->{data}} < $parent->{_argument}) {
  18         48  
145 10         20 $message = $self->{_message} = {_parent => $parent};
146 10         33 next CHUNK;
147             }
148             else {
149 8         11 $message = $parent;
150 8         24 delete $parent->{_argument};
151             }
152             }
153              
154 32         76 $self->{_on_message_cb}->($self, $message);
155 32         119 $message = $self->{_message} = {};
156             }
157             }
158              
159             sub _gather_messages {
160 28     28   54 push @{$_[0]->{_messages}}, $_[1];
  28         97  
161             }
162              
163             1;
164             __END__