fraggel 0.1.0
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/LICENSE +22 -0
- data/README.md +61 -0
- data/example/get.rb +9 -0
- data/example/watch.rb +13 -0
- data/lib/fraggel.rb +238 -0
- data/lib/fraggel/decoder.rb +133 -0
- data/lib/fraggel/encoder.rb +33 -0
- data/lib/fraggel/responder.rb +46 -0
- data/test/fraggel_decoder_test.rb +124 -0
- data/test/fraggel_encoder_test.rb +49 -0
- data/test/fraggel_responder_test.rb +91 -0
- data/test/fraggel_test.rb +522 -0
- metadata +84 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Blake Mizerany, Keith Rarick, Chris Moos
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# Fraggel
|
2
|
+
**An EventMachine based Doozer client**
|
3
|
+
|
4
|
+
## Install
|
5
|
+
|
6
|
+
$ gem install fraggel
|
7
|
+
|
8
|
+
## Use
|
9
|
+
|
10
|
+
require 'rubygems'
|
11
|
+
require 'eventmachine'
|
12
|
+
require 'fraggel'
|
13
|
+
|
14
|
+
EM.start do
|
15
|
+
client = Fraggel.connect "127.0.0.1", 8046
|
16
|
+
|
17
|
+
## Setting a key
|
18
|
+
client.set "/foo", "bar", :missing do |cas, err|
|
19
|
+
if err != nil
|
20
|
+
cas # => "123"
|
21
|
+
cas.dir? # => false
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
client.get "/foo" do |body, cas, err|
|
26
|
+
if err != nil
|
27
|
+
body # => "bar"
|
28
|
+
cas # => "123"
|
29
|
+
cas.dir? # => false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
watch = client.watch "/foo" do |path, body, cas, err|
|
34
|
+
# The event has:
|
35
|
+
# ------------------------
|
36
|
+
# NOTE: `err` will be set iff the glob is bad
|
37
|
+
# err # => nil
|
38
|
+
# path # => "/foo"
|
39
|
+
# body # => "bar"
|
40
|
+
# cas # => "123"
|
41
|
+
# cas.set? # => true
|
42
|
+
# cas.del? # => false
|
43
|
+
# ------------------------
|
44
|
+
|
45
|
+
# Phoney check for example
|
46
|
+
if can_stop_watching?(path)
|
47
|
+
client.close(watch)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
## Develop
|
55
|
+
|
56
|
+
**Clone**
|
57
|
+
$ git clone http://github.com/bmizerany/fraggel.git
|
58
|
+
|
59
|
+
**Test**
|
60
|
+
$ gem install turn
|
61
|
+
$ turn
|
data/example/get.rb
ADDED
data/example/watch.rb
ADDED
data/lib/fraggel.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'fraggel/decoder'
|
3
|
+
require 'fraggel/encoder'
|
4
|
+
require 'fraggel/responder'
|
5
|
+
|
6
|
+
module Fraggel
|
7
|
+
include Encoder
|
8
|
+
|
9
|
+
# Flags
|
10
|
+
Valid = 1
|
11
|
+
Done = 2
|
12
|
+
|
13
|
+
# Cas
|
14
|
+
Dir = "dir"
|
15
|
+
Missing = "0"
|
16
|
+
Clobber = ""
|
17
|
+
|
18
|
+
# Create an unique object to test for
|
19
|
+
# set or not-set
|
20
|
+
None = Object.new
|
21
|
+
|
22
|
+
module Cas
|
23
|
+
def dir?
|
24
|
+
self == Dir
|
25
|
+
end
|
26
|
+
|
27
|
+
def del?
|
28
|
+
self == "0"
|
29
|
+
end
|
30
|
+
|
31
|
+
def dummy?
|
32
|
+
empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def set?
|
36
|
+
! del? && ! dummy?
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.connect(port, host="127.0.0.1")
|
42
|
+
EM.connect(host, port, self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def post_init
|
46
|
+
@callbacks = {}
|
47
|
+
@opid = 0
|
48
|
+
|
49
|
+
@responder = Responder.new do |value|
|
50
|
+
receive_response(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
@decoder = Decoder.new do |name, value|
|
54
|
+
@responder.receive_event(name, value)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def receive_data(data)
|
59
|
+
@decoder.receive_data(data)
|
60
|
+
end
|
61
|
+
|
62
|
+
def receive_response(response)
|
63
|
+
opid, flags, value = response
|
64
|
+
|
65
|
+
if blk = @callbacks[opid]
|
66
|
+
if (flags & Valid) > 0
|
67
|
+
blk.call(value)
|
68
|
+
end
|
69
|
+
|
70
|
+
if (flags & Done) > 0
|
71
|
+
blk.call(:done)
|
72
|
+
@callbacks.delete(opid)
|
73
|
+
end
|
74
|
+
else
|
75
|
+
# TODO: Log something? Raise error?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def call(verb, args=None, &blk)
|
80
|
+
@opid += 1
|
81
|
+
@callbacks[@opid] = blk
|
82
|
+
|
83
|
+
request = [@opid, verb.to_s]
|
84
|
+
if args != None
|
85
|
+
request << args
|
86
|
+
end
|
87
|
+
|
88
|
+
encoded = encode(request)
|
89
|
+
|
90
|
+
# TODO: Chunk with next_tick
|
91
|
+
send_data(encoded)
|
92
|
+
|
93
|
+
@opid
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(path, snap_id=0, &blk)
|
97
|
+
call :GET, [path, snap_id] do |res|
|
98
|
+
case res
|
99
|
+
when StandardError
|
100
|
+
blk.call(nil, nil, res)
|
101
|
+
when :done
|
102
|
+
# Do nothing
|
103
|
+
else
|
104
|
+
# Add sugar to the CAS token
|
105
|
+
res[1].extend Cas
|
106
|
+
blk.call(*res)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def set(path, body, cas, &blk)
|
112
|
+
call :SET, [path, body, casify(cas)] do |res|
|
113
|
+
case res
|
114
|
+
when StandardError
|
115
|
+
blk.call(nil, res)
|
116
|
+
when :done
|
117
|
+
# Do nothing
|
118
|
+
else
|
119
|
+
res.extend Cas
|
120
|
+
blk.call(res)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def sett(path, i, cas, &blk)
|
126
|
+
call :SETT, [path, i, casify(cas)] do |res|
|
127
|
+
case res
|
128
|
+
when StandardError
|
129
|
+
blk.call(nil, nil, res)
|
130
|
+
when :done
|
131
|
+
# Do nothing
|
132
|
+
else
|
133
|
+
res[1].extend Cas
|
134
|
+
blk.call(*res)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def close(opid, &blk)
|
140
|
+
call :CLOSE, opid do |res|
|
141
|
+
case res
|
142
|
+
when StandardError
|
143
|
+
blk.call(res)
|
144
|
+
when :done
|
145
|
+
# Do nothing
|
146
|
+
else
|
147
|
+
blk.call(nil)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def del(path, cas, &blk)
|
153
|
+
call :DEL, [path, cas] do |res|
|
154
|
+
case res
|
155
|
+
when StandardError
|
156
|
+
blk.call(res)
|
157
|
+
when :done
|
158
|
+
# Do nothing
|
159
|
+
else
|
160
|
+
blk.call(nil)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def noop(&blk)
|
166
|
+
call :NOOP do |res|
|
167
|
+
case res
|
168
|
+
when StandardError
|
169
|
+
blk.call(res)
|
170
|
+
when :done
|
171
|
+
# Do nothing
|
172
|
+
else
|
173
|
+
blk.call(nil)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def snap(&blk)
|
179
|
+
call :SNAP do |res|
|
180
|
+
case res
|
181
|
+
when StandardError
|
182
|
+
blk.call(nil, res)
|
183
|
+
when :done
|
184
|
+
# Do nothing
|
185
|
+
else
|
186
|
+
blk.call(res, nil)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def delsnap(&blk)
|
192
|
+
call :DELSNAP do |res|
|
193
|
+
case res
|
194
|
+
when StandardError
|
195
|
+
blk.call(nil, res)
|
196
|
+
when :done
|
197
|
+
# Do nothing
|
198
|
+
else
|
199
|
+
blk.call(res, nil)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def walk(glob, sid=0, &blk)
|
205
|
+
call :WALK, [glob, sid] do |res|
|
206
|
+
case res
|
207
|
+
when StandardError, :done
|
208
|
+
blk.call(nil, nil, nil, res)
|
209
|
+
else
|
210
|
+
res[2].extend Cas
|
211
|
+
blk.call(*res)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def watch(glob, &blk)
|
217
|
+
call :WATCH, glob do |res|
|
218
|
+
case res
|
219
|
+
when StandardError, :done
|
220
|
+
blk.call(nil, nil, nil, res)
|
221
|
+
else
|
222
|
+
res[2].extend Cas
|
223
|
+
blk.call(*res)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def casify(cas)
|
231
|
+
case cas
|
232
|
+
when :missing: "0"
|
233
|
+
when :clobber: ""
|
234
|
+
else cas
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
module Fraggel
|
2
|
+
|
3
|
+
class Decoder
|
4
|
+
|
5
|
+
class Poisioned < StandardError ; end
|
6
|
+
|
7
|
+
def initialize(&blk)
|
8
|
+
@receiver = blk
|
9
|
+
end
|
10
|
+
|
11
|
+
def receive_data(data)
|
12
|
+
@buf ||= ""
|
13
|
+
@buf << data
|
14
|
+
|
15
|
+
if ! @dcs
|
16
|
+
read_type
|
17
|
+
else
|
18
|
+
@dcs.call
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def dcs(&blk)
|
23
|
+
@dcs = blk
|
24
|
+
@dcs.call
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_type
|
28
|
+
dcs do
|
29
|
+
case @buf.slice!(0)
|
30
|
+
when nil
|
31
|
+
# Wait for next byte
|
32
|
+
when ?\r
|
33
|
+
finish do
|
34
|
+
read_type
|
35
|
+
end
|
36
|
+
when ?:
|
37
|
+
read_integer do |i|
|
38
|
+
@receiver.call(:value, i)
|
39
|
+
read_type
|
40
|
+
end
|
41
|
+
when ?$
|
42
|
+
read_string do |s|
|
43
|
+
@receiver.call(:value, s)
|
44
|
+
read_type
|
45
|
+
end
|
46
|
+
when ?+
|
47
|
+
read_line do |msg|
|
48
|
+
@receiver.call(:status, msg)
|
49
|
+
read_type
|
50
|
+
end
|
51
|
+
when ?-
|
52
|
+
read_line do |msg|
|
53
|
+
@receiver.call(:error, msg)
|
54
|
+
read_type
|
55
|
+
end
|
56
|
+
when ?*
|
57
|
+
read_integer do |count|
|
58
|
+
@receiver.call(:array, count)
|
59
|
+
read_type
|
60
|
+
end
|
61
|
+
else
|
62
|
+
raise Poisioned
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def finish(&blk)
|
68
|
+
dcs do
|
69
|
+
c = @buf.slice!(0)
|
70
|
+
case c
|
71
|
+
when nil
|
72
|
+
# Wait for next byte
|
73
|
+
when ?\n
|
74
|
+
blk.call
|
75
|
+
else
|
76
|
+
raise Poisioned
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def read_integer(&blk)
|
82
|
+
@int = ""
|
83
|
+
dcs do
|
84
|
+
while c = @buf.slice!(0)
|
85
|
+
case c
|
86
|
+
when ?0..?9
|
87
|
+
@int << c.chr
|
88
|
+
when ?\r
|
89
|
+
finish do
|
90
|
+
blk.call(Integer(@int))
|
91
|
+
end
|
92
|
+
else
|
93
|
+
raise Poisioned
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def read_string(&blk)
|
100
|
+
read_integer do |count|
|
101
|
+
dcs do
|
102
|
+
if @buf.length >= count
|
103
|
+
string = @buf.slice!(0, count)
|
104
|
+
dcs do
|
105
|
+
case @buf.slice!(0)
|
106
|
+
when nil
|
107
|
+
# Wait for next byte
|
108
|
+
when ?\r
|
109
|
+
finish do
|
110
|
+
blk.call(string)
|
111
|
+
end
|
112
|
+
else
|
113
|
+
raise Poisioned
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def read_line(&blk)
|
122
|
+
dcs do
|
123
|
+
if line = @buf.slice!(/.*\r/)
|
124
|
+
finish do
|
125
|
+
blk.call(line.chomp)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|