meerkat 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in meerkat.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,43 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ meerkat (0.2.0)
5
+ em-hiredis
6
+ eventmachine
7
+ pg
8
+ yajl-ruby
9
+
10
+ GEM
11
+ remote: http://rubygems.org/
12
+ specs:
13
+ daemons (1.1.4)
14
+ em-hiredis (0.1.0)
15
+ hiredis (~> 0.3.0)
16
+ em-minitest-spec (1.1.1)
17
+ eventmachine
18
+ eventmachine (0.12.10)
19
+ hiredis (0.3.2)
20
+ minitest (2.6.2)
21
+ pg (0.11.0)
22
+ rack (1.3.5)
23
+ rack-test (0.6.1)
24
+ rack (>= 1.0)
25
+ thin (1.2.11)
26
+ daemons (>= 1.0.9)
27
+ eventmachine (>= 0.12.6)
28
+ rack (>= 1.0.0)
29
+ thin-async-test (1.0.0)
30
+ thin_async (0.1.1)
31
+ thin (>= 1.2.1)
32
+ yajl-ruby (1.0.0)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ em-minitest-spec
39
+ meerkat!
40
+ minitest
41
+ rack-test
42
+ thin-async-test
43
+ thin_async
data/README.md ADDED
@@ -0,0 +1,81 @@
1
+ Meerkat
2
+ =======
3
+
4
+ Rack middleware for [Server-Sent Events (HTML5 SSE)](http://www.html5rocks.com/en/tutorials/eventsource/basics/).
5
+
6
+ Requires an evented server, like [Thin](http://code.macournoyer.com/thin/).
7
+
8
+ Supported backends:
9
+
10
+ * In memory, using [EventMachine Channels](http://eventmachine.rubyforge.org/EventMachine/Channel.html), good for single server usage.
11
+ * Redis, using [em-hiredis](https://github.com/mloughran/em-hiredis#readme) and the [Pub/Sub API](http://redis.io/topics/pubsub).
12
+ * Postgres, using the [Notify/Listen API](http://www.postgresql.org/docs/9.1/static/sql-notify.html). Note, this is totally async, no polling.
13
+
14
+ Usage
15
+ -----
16
+
17
+ config.ru:
18
+
19
+ ```ruby
20
+ require 'bundler/setup'
21
+ require 'meerkat'
22
+ require './app'
23
+
24
+ #Meerkat.backend = Meerkat::Backend::InMemory.new
25
+ #Meerkat.backend = Meerkat::Backend::Redis.new 'redis://localhost/0'
26
+ Meerkat.backend = Meerkat::Backend::PG.new :dbname => 'postgres'
27
+ map '/' do
28
+ run App
29
+ end
30
+ map '/stream' do
31
+ run Meerkat::RackAdapter.new
32
+ end
33
+ ```
34
+
35
+ On the client:
36
+
37
+ ```javascript
38
+ var source = new EventSource('/stream/mychannel');
39
+ var streamList = document.getElementById('stream');
40
+ source.addEventListener('message', function(e) {
41
+ var li = document.createElement('li');
42
+ li.innerHTML = JSON.parse(e.data);
43
+ streamList.appendChild(li);
44
+ }, false);
45
+ ```
46
+
47
+ To push things:
48
+
49
+ ```ruby
50
+ Meerkat.publish "/mychannel", {:any => hash}
51
+ Meerkat.publish "/mychannel/2", 'any string'
52
+ Meerkat.publish "/mychannel/3", any_object
53
+ ```
54
+
55
+ The published objects will be JSON serialized (with [Yajl](https://github.com/brianmario/yajl-ruby)) before sent to the backend. Deserialize it in the client.
56
+
57
+ Read more about Server-Sent Events and the EventSource API on [HTML5Rocks](http://www.html5rocks.com/en/tutorials/eventsource/basics/).
58
+
59
+ License
60
+ -------
61
+ (MIT license)
62
+
63
+ Copyright (C) 2011 by Carl Hörberg
64
+
65
+ Permission is hereby granted, free of charge, to any person obtaining a copy
66
+ of this software and associated documentation files (the "Software"), to deal
67
+ in the Software without restriction, including without limitation the rights
68
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
69
+ copies of the Software, and to permit persons to whom the Software is
70
+ furnished to do so, subject to the following conditions:
71
+
72
+ The above copyright notice and this permission notice shall be included in
73
+ all copies or substantial portions of the Software.
74
+
75
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
76
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
77
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
78
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
79
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
80
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
81
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:spec) do |t|
6
+ t.pattern = "spec/**/*_spec.rb"
7
+ end
data/examples/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ gem 'thin'
4
+ gem 'sinatra'
5
+ gem 'haml'
6
+ gem 'meerkat', :path => '..'
data/examples/app.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'sinatra/base'
2
+ require 'haml'
3
+
4
+ class App < Sinatra::Base
5
+ get '*' do
6
+ @route = params[:splat].join
7
+ haml :index
8
+ end
9
+ post '*' do
10
+ Meerkat.publish params[:splat].join, params[:message]
11
+ 204
12
+ end
13
+ end
14
+
@@ -0,0 +1,13 @@
1
+ require 'bundler/setup'
2
+ require 'meerkat'
3
+ require './app'
4
+
5
+ #Meerkat.backend = Meerkat::Backend::InMemory.new
6
+ #Meerkat.backend = Meerkat::Backend::Redis.new
7
+ Meerkat.backend = Meerkat::Backend::PG.new :dbname => 'postgres'
8
+ map '/' do
9
+ run App
10
+ end
11
+ map '/stream' do
12
+ run Meerkat::RackAdapter.new
13
+ end
@@ -0,0 +1,32 @@
1
+ !!!
2
+ %head
3
+ %title Meerkat test
4
+ %script{:src=>'https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js', :type => 'text/javascript'}
5
+ %body
6
+ %fieldset
7
+ %legend Publish
8
+ %form#pub{:method => :post}
9
+ %input{:name => 'message'}
10
+ %input{:type=>'submit', :value => 'Send'}
11
+
12
+ %fieldset
13
+ %legend Subscribe
14
+ %p
15
+ Subscribing to
16
+ %strong= @route
17
+ %ul#stream
18
+ :javascript
19
+ var source = new EventSource('/stream#{@route}');
20
+ var stream = document.getElementById('stream');
21
+ source.addEventListener('message', function(e) {
22
+ var li = document.createElement('li');
23
+ li.innerHTML = JSON.parse(e.data);
24
+ stream.appendChild(li);
25
+ }, false);
26
+
27
+ $(function() {
28
+ $("#pub").submit(function() {
29
+ $.post(this.action, $(this).serialize())
30
+ return false
31
+ });
32
+ })
@@ -0,0 +1,26 @@
1
+ require 'eventmachine'
2
+
3
+ module Meerkat
4
+ module Backend
5
+ class InMemory
6
+ def initialize
7
+ @channel = EventMachine::Channel.new
8
+ end
9
+
10
+ def publish(route, json)
11
+ @channel.push({:route => route, :json => json})
12
+ end
13
+
14
+ def subscribe(route, &callback)
15
+ @channel.subscribe do |msg|
16
+ callback.call(msg[:json]) if msg[:route] == route
17
+ end
18
+ end
19
+
20
+ def unsubscribe(sid)
21
+ @channel.unsubscribe(sid)
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,43 @@
1
+ require 'pg'
2
+
3
+ module Meerkat
4
+ module Backend
5
+ class PG
6
+ def initialize(pg_uri = nil)
7
+ @pg_uri = pg_uri
8
+ @pg = PGconn.connect pg_uri
9
+ end
10
+
11
+ def publish(route, json)
12
+ @pg.exec "SELECT pg_notify($1, $2)", [route, json]
13
+ end
14
+
15
+ def subscribe(route, &callback)
16
+ pg = PGconn.connect @pg_uri
17
+ pg.exec "LISTEN #{PGconn.quote_ident route}"
18
+ EM.watch(pg.socket, SubscribeClient, pg, callback) { |c| c.notify_readable = true }
19
+ end
20
+
21
+ def unsubscribe(pg)
22
+ pg.detach
23
+ end
24
+
25
+ module SubscribeClient
26
+ def initialize(pg, cb)
27
+ @pg = pg
28
+ @cb = cb
29
+ end
30
+ def notify_readable
31
+ @pg.consume_input
32
+ msg = @pg.notifies
33
+ @cb.call(msg[:extra]) if msg
34
+ end
35
+
36
+ def unbind
37
+ @pg.close
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,31 @@
1
+ require 'em-hiredis'
2
+
3
+ module Meerkat
4
+ module Backend
5
+ class Redis
6
+ def initialize(redis_uri = nil)
7
+ @redis_uri = redis_uri
8
+ EM.next_tick {
9
+ @pub = EM::Hiredis.connect redis_uri
10
+ }
11
+ end
12
+
13
+ def publish(route, json)
14
+ @pub.publish route, json
15
+ end
16
+
17
+ def subscribe(route, &callback)
18
+ sub = EM::Hiredis.connect @redis_uri
19
+ sub.subscribe route
20
+ sub.on :message do |channel, message|
21
+ callback.call(message)
22
+ end
23
+ sub
24
+ end
25
+
26
+ def unsubscribe(sub)
27
+ sub.close_connection
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ module Meerkat
2
+ class RackAdapter
3
+ attr_accessor :keep_alive
4
+ attr_accessor :retry
5
+ attr_accessor :timeout
6
+
7
+ def initialize(app = nil, &blk)
8
+ blk.call(self) if blk
9
+ end
10
+
11
+ def call(env)
12
+ body = DeferrableBody.new
13
+
14
+ EM.next_tick {
15
+ env['async.callback'].call [200, {'Content-Type' => 'text/event-stream'}, body]
16
+ }
17
+
18
+ EM.next_tick {
19
+ body << "retry: #{@retry || 3000}\n"
20
+ }
21
+
22
+ path_info = Rack::Utils.unescape env["PATH_INFO"]
23
+ sub = Meerkat.subscribe(path_info) do |message|
24
+ body << "data: #{message}\n\n"
25
+ end
26
+ body.errback {
27
+ Meerkat.unsubscribe sub
28
+ }
29
+
30
+ EM.add_periodic_timer(@keep_alive || 20) do
31
+ body << ":\n"
32
+ end
33
+
34
+ EM.add_timer(@timeout) { body.succeed } if @timeout
35
+
36
+ [-1, {}, []]
37
+ end
38
+
39
+ class DeferrableBody
40
+ include EventMachine::Deferrable
41
+
42
+ def call(body)
43
+ body.each do |chunk|
44
+ @body_callback.call(chunk)
45
+ end
46
+ end
47
+
48
+ def <<(str)
49
+ call([str])
50
+ end
51
+
52
+ def each(&blk)
53
+ @body_callback = blk
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,3 @@
1
+ module Meerkat
2
+ VERSION = "0.2.0"
3
+ end
data/lib/meerkat.rb ADDED
@@ -0,0 +1,29 @@
1
+ require_relative 'meerkat/version'
2
+ require_relative 'meerkat/rackadapter'
3
+ require_relative 'meerkat/backend/inmemory'
4
+ require_relative 'meerkat/backend/redis'
5
+ require_relative 'meerkat/backend/pg'
6
+
7
+ require 'yajl'
8
+
9
+ module Meerkat
10
+ extend self
11
+
12
+ def backend=(backend)
13
+ @backend = backend
14
+ end
15
+
16
+ def publish(route, message)
17
+ json = Yajl::Encoder.encode message
18
+ @backend.publish(route, json)
19
+ end
20
+
21
+ def subscribe(route, &callback)
22
+ @backend.subscribe(route, &callback)
23
+ end
24
+
25
+ def unsubscribe(sid)
26
+ @backend.unsubscribe(sid)
27
+ end
28
+ end
29
+
data/meerkat.gemspec ADDED
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "meerkat/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "meerkat"
7
+ s.version = Meerkat::VERSION
8
+ s.authors = ["Carl Hörberg"]
9
+ s.email = ["carl.hoerberg@gmail.com"]
10
+ s.homepage = "https://github.com/carlhoerberg/meerkat"
11
+ s.summary = %q{Rack middleware for HTML5 Server-Sent Events, with swappable backends}
12
+ s.description = %q{Requires an evented Ruby dispatcher, like Thin}
13
+
14
+ s.rubyforge_project = "meerkat"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "minitest"
22
+ s.add_development_dependency "rack-test"
23
+ s.add_development_dependency "thin_async"
24
+ s.add_development_dependency "thin-async-test"
25
+ s.add_development_dependency "em-minitest-spec"
26
+
27
+ s.add_runtime_dependency "yajl-ruby"
28
+ s.add_runtime_dependency "eventmachine"
29
+ s.add_runtime_dependency "em-hiredis"
30
+ s.add_runtime_dependency "pg"
31
+ end
@@ -0,0 +1,30 @@
1
+ require 'minitest/autorun'
2
+ require 'em/minitest/spec'
3
+ require './lib/meerkat/backend/inmemory'
4
+
5
+ describe 'The in memory backend' do
6
+ include EM::MiniTest::Spec
7
+
8
+ it 'can publish and subscribe' do
9
+ im = Meerkat::Backend::InMemory.new
10
+ im.subscribe 'route' do |msg|
11
+ @recivied = msg
12
+ end
13
+ im.publish 'route', 'messsage'
14
+ assert_equal 'messsage', @recivied
15
+ end
16
+ it 'can does only subscribe to specific routes' do
17
+ im = Meerkat::Backend::InMemory.new
18
+ im.subscribe 'route' do |msg|
19
+ @recivied = msg
20
+ end
21
+ im.publish 'route2', 'messsage'
22
+ assert_equal nil, @recivied
23
+ end
24
+ it 'can unbsubscribe' do
25
+ im = Meerkat::Backend::InMemory.new
26
+ sid = im.subscribe 'route' do |msg|
27
+ end
28
+ im.unsubscribe sid
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ require 'minitest/autorun'
2
+ require 'em/minitest/spec'
3
+ require './lib/meerkat/backend/pg'
4
+
5
+ describe 'Postgres backend' do
6
+ include EM::MiniTest::Spec
7
+
8
+ it 'can publish and subscribe' do
9
+ b = Meerkat::Backend::PG.new :dbname => 'postgres'
10
+ b.subscribe '/' do |msg|
11
+ assert_equal 'messsage', msg
12
+ done!
13
+ end
14
+ b.publish '/', 'messsage'
15
+ wait!
16
+ end
17
+
18
+ it 'can unsubscribe' do
19
+ b = Meerkat::Backend::PG.new :dbname => 'postgres'
20
+ sid = b.subscribe 'route' do |msg|
21
+ end
22
+ b.unsubscribe sid
23
+ end
24
+ end
25
+
@@ -0,0 +1,28 @@
1
+ require 'minitest/autorun'
2
+ require 'em/minitest/spec'
3
+ require './lib/meerkat/backend/redis'
4
+
5
+ describe 'Redis backend' do
6
+ include EM::MiniTest::Spec
7
+
8
+ it 'can publish and subscribe' do
9
+ b = Meerkat::Backend::Redis.new
10
+ b.subscribe '/' do |msg|
11
+ assert_equal 'messsage', msg
12
+ done!
13
+ end
14
+ EM.next_tick {
15
+ b.publish '/', 'messsage'
16
+ }
17
+ wait!
18
+ end
19
+
20
+ it 'can unsubscribe' do
21
+ b = Meerkat::Backend::Redis.new
22
+ sid = b.subscribe 'route' do |msg|
23
+ @recivied = msg
24
+ end
25
+ b.unsubscribe sid
26
+ end
27
+ end
28
+
@@ -0,0 +1,36 @@
1
+ require 'bundler/setup'
2
+ require 'minitest/autorun'
3
+ require 'rack/test'
4
+ require 'thin/async/test'
5
+ require './lib/meerkat'
6
+
7
+ describe 'Meerkat' do
8
+ include Rack::Test::Methods
9
+
10
+ def app
11
+ Meerkat.backend = Meerkat::Backend::InMemory.new
12
+ Rack::Builder.new {
13
+ meerkat = Meerkat::RackAdapter.new do |m|
14
+ m.keep_alive = 0.1
15
+ m.timeout = 0.2
16
+ end
17
+ run Thin::Async::Test.new(meerkat)
18
+ }.to_app
19
+ end
20
+
21
+ it 'should return status 200 and content-type text/event-stream' do
22
+ get '/'
23
+ assert_equal 200, last_response.status
24
+ assert_equal 'text/event-stream', last_response.headers['Content-Type']
25
+ end
26
+
27
+ it 'first return a retry value' do
28
+ get '/'
29
+ assert_equal "retry: 3000\n", last_response.body.lines.first
30
+ end
31
+
32
+ it 'should periodically emit a comment to keep alive the connection' do
33
+ get '/'
34
+ assert_equal ":", last_response.body.split("\n")[1]
35
+ end
36
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: meerkat
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Carl Hörberg
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-10-25 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: minitest
16
+ requirement: &70275010936200 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70275010936200
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack-test
27
+ requirement: &70275010935740 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70275010935740
36
+ - !ruby/object:Gem::Dependency
37
+ name: thin_async
38
+ requirement: &70275010935300 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70275010935300
47
+ - !ruby/object:Gem::Dependency
48
+ name: thin-async-test
49
+ requirement: &70275010934840 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70275010934840
58
+ - !ruby/object:Gem::Dependency
59
+ name: em-minitest-spec
60
+ requirement: &70275010934400 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70275010934400
69
+ - !ruby/object:Gem::Dependency
70
+ name: yajl-ruby
71
+ requirement: &70275010933940 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: *70275010933940
80
+ - !ruby/object:Gem::Dependency
81
+ name: eventmachine
82
+ requirement: &70275010933500 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :runtime
89
+ prerelease: false
90
+ version_requirements: *70275010933500
91
+ - !ruby/object:Gem::Dependency
92
+ name: em-hiredis
93
+ requirement: &70275010933040 !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ type: :runtime
100
+ prerelease: false
101
+ version_requirements: *70275010933040
102
+ - !ruby/object:Gem::Dependency
103
+ name: pg
104
+ requirement: &70275010932600 !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :runtime
111
+ prerelease: false
112
+ version_requirements: *70275010932600
113
+ description: Requires an evented Ruby dispatcher, like Thin
114
+ email:
115
+ - carl.hoerberg@gmail.com
116
+ executables: []
117
+ extensions: []
118
+ extra_rdoc_files: []
119
+ files:
120
+ - .gitignore
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - README.md
124
+ - Rakefile
125
+ - examples/Gemfile
126
+ - examples/app.rb
127
+ - examples/config.ru
128
+ - examples/views/index.haml
129
+ - lib/meerkat.rb
130
+ - lib/meerkat/backend/inmemory.rb
131
+ - lib/meerkat/backend/pg.rb
132
+ - lib/meerkat/backend/redis.rb
133
+ - lib/meerkat/rackadapter.rb
134
+ - lib/meerkat/version.rb
135
+ - meerkat.gemspec
136
+ - spec/backend/inmemory_spec.rb
137
+ - spec/backend/pg_spec.rb
138
+ - spec/backend/redis_spec.rb
139
+ - spec/meerkat_spec.rb
140
+ homepage: https://github.com/carlhoerberg/meerkat
141
+ licenses: []
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ! '>='
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ segments:
153
+ - 0
154
+ hash: 4297295389839479768
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ none: false
157
+ requirements:
158
+ - - ! '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ segments:
162
+ - 0
163
+ hash: 4297295389839479768
164
+ requirements: []
165
+ rubyforge_project: meerkat
166
+ rubygems_version: 1.8.6
167
+ signing_key:
168
+ specification_version: 3
169
+ summary: Rack middleware for HTML5 Server-Sent Events, with swappable backends
170
+ test_files:
171
+ - spec/backend/inmemory_spec.rb
172
+ - spec/backend/pg_spec.rb
173
+ - spec/backend/redis_spec.rb
174
+ - spec/meerkat_spec.rb