qup 1.4.0 → 1.4.1

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/lib/qup.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Qup
2
2
  # The Current Version of the library
3
- VERSION = '1.4.0'
3
+ VERSION = '1.4.1'
4
4
 
5
5
  class Error < StandardError; end
6
6
 
@@ -28,6 +28,12 @@ class Qup::Adapter::Kestrel
28
28
  @client.flush(@name)
29
29
  end
30
30
 
31
+ # Internal: Remove the Queue if possible
32
+ #
33
+ # Returns nothing
34
+ def destroy
35
+ @client.delete(@name)
36
+ end
31
37
 
32
38
  # Internal: return the number of Messages on the Queue
33
39
  #
@@ -32,6 +32,16 @@ class Qup::Adapter::Kestrel
32
32
  ::Qup::Subscriber.new( self, subscriber_queue( name ) )
33
33
  end
34
34
 
35
+ # Internal: Destroy the topic
36
+ #
37
+ # Returns nothing
38
+ def destroy
39
+ subscribers.each do |name, sub|
40
+ sub.destroy
41
+ end
42
+ @client.delete( @name )
43
+ end
44
+
35
45
 
36
46
  # Internal: Return the number of Subscribers to this Topic
37
47
  #
@@ -43,15 +53,7 @@ class Qup::Adapter::Kestrel
43
53
  #
44
54
  # Returns integer
45
55
  def subscriber_count
46
- c = Set.new
47
-
48
- stats['queues'].keys.each do |k|
49
- next unless k =~ %r{\A#{@name}\+}
50
- parts = k.split("+")
51
- c << parts[1]
52
- end
53
-
54
- return c.size
56
+ subscriber_names.size
55
57
  end
56
58
 
57
59
  # Internal: Publish a Message to all the Subscribers
@@ -66,6 +68,26 @@ class Qup::Adapter::Kestrel
66
68
  #######
67
69
  private
68
70
  #######
71
+
72
+ def subscribers
73
+ subs = {}
74
+ subscriber_names.each do |sub_name|
75
+ subs[sub_name] = subscriber_queue( sub_name )
76
+ end
77
+ return subs
78
+ end
79
+
80
+ def subscriber_names
81
+ names = []
82
+
83
+ stats['queues'].keys.each do |k|
84
+ next unless k =~ %r{\A#{@name}\+}
85
+ parts = k.split("+")
86
+ names << parts[1]
87
+ end
88
+
89
+ return names
90
+ end
69
91
 
70
92
  def subscriber_queue( sub_name )
71
93
  sname = subscriber_queue_name( sub_name )
@@ -21,6 +21,8 @@ class Qup::Adapter::Maildir
21
21
  @name = name
22
22
  @queue_path = @root_path + @name
23
23
  @maildir = ::Maildir.new( @queue_path, true )
24
+ # FIXME: this is a -w for Maildir issue, remove when fixed upstream
25
+ @maildir.serializer = ::Maildir::Serializer::Base.new
24
26
  end
25
27
 
26
28
 
@@ -20,9 +20,9 @@ class Qup::Adapter::Maildir
20
20
  @root_path = ::Pathname.new( root_path )
21
21
  @name = name
22
22
  @topic_path = @root_path + @name
23
- @subscribers = Hash.new
24
23
 
25
24
  FileUtils.mkdir_p( @topic_path )
25
+ @topic_path_mtime = @topic_path.mtime
26
26
  end
27
27
 
28
28
  # Internal: Destroy the Topic
@@ -31,7 +31,7 @@ class Qup::Adapter::Maildir
31
31
  #
32
32
  # Returns nothing.
33
33
  def destroy
34
- @topic_path.rmtree
34
+ @topic_path.rmtree if @topic_path.directory?
35
35
  end
36
36
 
37
37
  # Internal: Creates a Publisher for the Topic
@@ -60,7 +60,7 @@ class Qup::Adapter::Maildir
60
60
  #
61
61
  # Returns integer
62
62
  def subscriber_count
63
- @subscribers.size
63
+ subscribers.size
64
64
  end
65
65
 
66
66
  # Internal: Publish a Message to all the Subscribers
@@ -69,7 +69,7 @@ class Qup::Adapter::Maildir
69
69
  #
70
70
  # Returns nothing
71
71
  def publish( message )
72
- @subscribers.each do |name, sub|
72
+ subscribers.each do |name, sub|
73
73
  sub.produce( message )
74
74
  end
75
75
  end
@@ -77,9 +77,22 @@ class Qup::Adapter::Maildir
77
77
  #######
78
78
  private
79
79
  #######
80
+
81
+ def subscribers
82
+ subs = {}
83
+ if @topic_path.directory? then
84
+ @topic_path.each_child do |child|
85
+ if child.directory?
86
+ name = child.basename
87
+ subs[name] = sub_queue( name )
88
+ end
89
+ end
90
+ end
91
+ return subs
92
+ end
80
93
 
81
94
  def sub_queue( name )
82
- @subscribers[name] ||= ::Qup::Adapter::Maildir::Queue.new( @topic_path, name )
95
+ ::Qup::Adapter::Maildir::Queue.new( @topic_path, name )
83
96
  end
84
97
  end
85
98
  end
@@ -33,6 +33,15 @@ class Qup::Adapter::Redis
33
33
  ::Qup::Subscriber.new( self, queue )
34
34
  end
35
35
 
36
+ # Internal: Destroy the topic and all of its subscribers
37
+ #
38
+ # Returns nothing
39
+ def destroy
40
+ subscribers.each do |name, subscriber|
41
+ subscriber.destroy
42
+ end
43
+ end
44
+
36
45
  # Internal: Return the number of Subscribers to this Topic
37
46
  #
38
47
  # Returns integer
@@ -46,8 +55,8 @@ class Qup::Adapter::Redis
46
55
  #
47
56
  # Returns nothing
48
57
  def publish( message )
49
- subscribers.each do |subscriber|
50
- @client.lpush subscriber, message
58
+ subscribers.each do |name, subscriber|
59
+ subscriber.produce( message )
51
60
  end
52
61
  end
53
62
 
@@ -55,12 +64,22 @@ class Qup::Adapter::Redis
55
64
  private
56
65
  #######
57
66
 
58
- # Private: retrieve the current list of subscribers
67
+ # Private: retrieve the current list of subscriber names
59
68
  #
60
69
  # Returns an array of subscriber queue names
61
- def subscribers
70
+ def subscriber_names
62
71
  @client.smembers @name
63
72
  end
64
73
 
74
+ # Private: return the current list of subscribers
75
+ #
76
+ # Return a Hash of subscriber names and queues
77
+ def subscribers
78
+ subs = {}
79
+ subscriber_names.each do |sname|
80
+ subs[sname] = ::Qup::Adapter::Redis::Queue.new(@uri, sname, @name)
81
+ end
82
+ return subs
83
+ end
65
84
  end
66
85
  end
@@ -19,11 +19,13 @@ shared_examples Qup::Adapter do
19
19
  q = adapter.queue( 'q' )
20
20
  q.should be_kind_of( Qup::QueueAPI )
21
21
  q.name.should eq 'q'
22
+ q.destroy
22
23
  end
23
24
 
24
25
  it 'can create a QueueAPI-like object' do
25
26
  t = adapter.topic( 't' )
26
27
  t.should be_kind_of( Qup::TopicAPI )
27
28
  t.name.should eq 't'
29
+ t.destroy
28
30
  end
29
31
  end
@@ -31,17 +31,29 @@ shared_examples Qup::TopicAPI do
31
31
  describe "subscribers" do
32
32
  before do
33
33
  @subs = []
34
+ @topic2 = adapter.topic( 'topic' )
35
+
34
36
  3.times do |x|
35
- @subs << @topic.subscriber( "sub-#{x}")
37
+ @subs << @topic2.subscriber( "sub-#{x}")
36
38
  end
37
39
  end
38
40
 
39
41
  after do
40
- @subs.each { |s| s.unsubscribe }
42
+ @topic2.destroy
43
+ @topic.subscriber_count.should eq 0
41
44
  end
42
45
 
43
- it "are counted" do
44
- @topic.subscriber_count.should eq 3
46
+ it "updates the publisher with the number of subscribers" do
47
+ start_count = @topic.subscriber_count
48
+ @topic2.subscriber_count.should eq start_count
49
+ current_count = start_count
50
+
51
+ 3.times do |x|
52
+ current_count += 1
53
+ @topic2.subscriber( "sub2-#{x}" )
54
+ @topic.subscriber_count.should eq current_count
55
+ @topic2.subscriber_count.should eq current_count
56
+ end
45
57
  end
46
58
 
47
59
  it "each receives a copy of the message" do
@@ -0,0 +1,269 @@
1
+ # vim: syntax=ruby
2
+ require 'rake/clean'
3
+ require 'digest'
4
+ #------------------------------------------------------------------------------
5
+ # If you want to Develop on this project just run 'rake develop' and you'll
6
+ # have all you need to get going. If you want to use bundler for development,
7
+ # then run 'rake develop:using_bundler'
8
+ #------------------------------------------------------------------------------
9
+ namespace :develop do
10
+
11
+ # Install all the development and runtime dependencies of this gem using the
12
+ # gemspec.
13
+ task :default do
14
+ require 'rubygems/dependency_installer'
15
+ installer = ::Gem::DependencyInstaller.new
16
+
17
+ This.set_coverage_gem
18
+
19
+ puts "Installing gem depedencies needed for development"
20
+ This.platform_gemspec.dependencies.each do |dep|
21
+ if dep.matching_specs.empty? then
22
+ puts "Installing : #{dep}"
23
+ installer.install dep
24
+ else
25
+ puts "Skipping : #{dep} -> already installed #{dep.matching_specs.first.full_name}"
26
+ end
27
+ end
28
+ puts "\n\nNow run 'rake test'"
29
+ end
30
+
31
+ # Create a Gemfile that just references the gemspec
32
+ file 'Gemfile' => :gemspec do
33
+ File.open( "Gemfile", "w+" ) do |f|
34
+ f.puts 'source "https://rubygems.org/"'
35
+ f.puts 'gemspec'
36
+ end
37
+ end
38
+
39
+ desc "Create a bundler Gemfile"
40
+ task :using_bundler => 'Gemfile' do
41
+ puts "Now you can 'bundle'"
42
+ end
43
+
44
+ # Gemfiles are build artifacts
45
+ CLOBBER << FileList['Gemfile*']
46
+ end
47
+ desc "Boostrap development"
48
+ task :develop => "develop:default"
49
+
50
+ #------------------------------------------------------------------------------
51
+ # RSpec - standard RSpec rake task
52
+ #------------------------------------------------------------------------------
53
+ begin
54
+ require 'rspec/core/rake_task'
55
+ RSpec::Core::RakeTask.new( :test ) do |t|
56
+ t.ruby_opts = %w[ -w ]
57
+ t.rspec_opts = %w[ --color --format documentation ]
58
+ end
59
+ task :default => :test
60
+ rescue LoadError
61
+ Util.task_warning( 'test' )
62
+ end
63
+
64
+
65
+ #------------------------------------------------------------------------------
66
+ # RDoc - standard rdoc rake task, although we must make sure to use a more
67
+ # recent version of rdoc since it is the one that has 'tomdoc' markup
68
+ #------------------------------------------------------------------------------
69
+ begin
70
+ gem 'rdoc' # otherwise we get the wrong task from stdlib
71
+ require 'rdoc/task'
72
+ RDoc::Task.new do |t|
73
+ t.markup = 'tomdoc'
74
+ t.rdoc_dir = 'doc'
75
+ t.main = 'README.md'
76
+ t.title = "#{This.name} #{This.version}"
77
+ t.rdoc_files.include( FileList['*.{rdoc,md,txt}'], FileList['ext/**/*.c'],
78
+ FileList['lib/**/*.rb'] )
79
+ end
80
+ rescue StandardError, LoadError
81
+ This.task_warning( 'rdoc' )
82
+ end
83
+
84
+ #------------------------------------------------------------------------------
85
+ # Coverage - optional code coverage, rcov for 1.8 and simplecov for 1.9, so
86
+ # for the moment only rcov is listed.
87
+ #------------------------------------------------------------------------------
88
+ if RUBY_VERSION < "1.9.0"
89
+ begin
90
+ require 'rcov/rcovtask'
91
+ Rcov::RcovTask.new( 'coverage' ) do |t|
92
+ t.libs << 'spec'
93
+ t.pattern = 'spec/**/*_spec.rb'
94
+ t.verbose = true
95
+ t.rcov_opts << "-x ^/" # remove all the global files
96
+ t.rcov_opts << "--sort coverage" # so we see the worst files at the top
97
+ end
98
+ rescue LoadError
99
+ This.task_warning( 'rcov' )
100
+ end
101
+ else
102
+ begin
103
+ require 'simplecov'
104
+ desc 'Run tests with code coverage'
105
+ task :coverage do
106
+ ENV['COVERAGE'] = 'true'
107
+ Rake::Task[:test].execute
108
+ end
109
+ CLOBBER << FileList["coverage"]
110
+ rescue LoadError
111
+ This.task_warning( 'simplecov' )
112
+ end
113
+ end
114
+
115
+ #------------------------------------------------------------------------------
116
+ # Manifest - We want an explicit list of thos files that are to be packaged in
117
+ # the gem. Most of this is from Hoe.
118
+ #------------------------------------------------------------------------------
119
+ namespace 'manifest' do
120
+ desc "Check the manifest"
121
+ task :check => :clean do
122
+ files = FileList["**/*", ".*"].exclude( This.exclude_from_manifest ).to_a.sort
123
+ files = files.select{ |f| File.file?( f ) }
124
+
125
+ tmp = "Manifest.tmp"
126
+ File.open( tmp, 'w' ) do |f|
127
+ f.puts files.join("\n")
128
+ end
129
+
130
+ begin
131
+ sh "diff -du Manifest.txt #{tmp}"
132
+ ensure
133
+ rm tmp
134
+ end
135
+ puts "Manifest looks good"
136
+ end
137
+
138
+ desc "Generate the manifest"
139
+ task :generate => :clean do
140
+ files = %x[ git ls-files ].split("\n").sort
141
+ files.reject! { |f| f =~ This.exclude_from_manifest }
142
+ File.open( "Manifest.txt", "w" ) do |f|
143
+ f.puts files.join("\n")
144
+ end
145
+ end
146
+ end
147
+
148
+ #------------------------------------------------------------------------------
149
+ # Fixme - look for fixmes and report them
150
+ #------------------------------------------------------------------------------
151
+ namespace :fixme do
152
+ task :default => 'manifest:check' do
153
+ This.manifest.each do |file|
154
+ next if file == __FILE__
155
+ next unless file =~ %r/(txt|rb|md|rdoc|css|html|xml|css)\Z/
156
+ puts "FIXME: Rename #{file}" if file =~ /fixme/i
157
+ IO.readlines( file ).each_with_index do |line, idx|
158
+ prefix = "FIXME: #{file}:#{idx+1}".ljust(42)
159
+ puts "#{prefix} => #{line.strip}" if line =~ /fixme/i
160
+ end
161
+ end
162
+ end
163
+
164
+ def fixme_project_root
165
+ This.project_path( '../fixme' )
166
+ end
167
+
168
+ def fixme_project_path( subtree )
169
+ fixme_project_root.join( subtree )
170
+ end
171
+
172
+ def local_fixme_files
173
+ This.manifest.select { |p| p =~ %r|^tasks/| }
174
+ end
175
+
176
+ def outdated_fixme_files
177
+ local_fixme_files.reject do |local|
178
+ upstream = fixme_project_path( local )
179
+ Digest::SHA256.file( local ) == Digest::SHA256.file( upstream )
180
+ end
181
+ end
182
+
183
+ def fixme_up_to_date?
184
+ outdated_fixme_files.empty?
185
+ end
186
+
187
+ desc "See if the fixme tools are outdated"
188
+ task :outdated => :release_check do
189
+ if fixme_up_to_date? then
190
+ puts "Fixme files are up to date."
191
+ else
192
+ outdated_fixme_files.each do |f|
193
+ puts "#{f} is outdated"
194
+ end
195
+ end
196
+ end
197
+
198
+ desc "Update outdated fixme files"
199
+ task :update => :release_check do
200
+ if fixme_up_to_date? then
201
+ puts "Fixme files are already up to date."
202
+ else
203
+ puts "Updating fixme files:"
204
+ outdated_fixme_files.each do |local|
205
+ upstream = fixme_project_path( local )
206
+ puts " * #{local}"
207
+ FileUtils.cp( upstream, local )
208
+ end
209
+ puts "Use your git commands as appropriate."
210
+ end
211
+ end
212
+ end
213
+ desc "Look for fixmes and report them"
214
+ task :fixme => "fixme:default"
215
+
216
+ #------------------------------------------------------------------------------
217
+ # Gem Specification
218
+ #------------------------------------------------------------------------------
219
+ # Really this is only here to support those who use bundler
220
+ desc "Build the #{This.name}.gemspec file"
221
+ task :gemspec do
222
+ File.open( This.gemspec_file, "wb+" ) do |f|
223
+ f.write This.platform_gemspec.to_ruby
224
+ end
225
+ end
226
+
227
+ # the gemspec is also a dev artifact and should not be kept around.
228
+ CLOBBER << This.gemspec_file.to_s
229
+
230
+ # .rbc files from ruby 2.0
231
+ CLOBBER << FileList["**/*.rbc"]
232
+
233
+ # The standard gem packaging task, everyone has it.
234
+ require 'rubygems/package_task'
235
+ ::Gem::PackageTask.new( This.platform_gemspec ) do
236
+ # nothing
237
+ end
238
+
239
+ #------------------------------------------------------------------------------
240
+ # Release - the steps we go through to do a final release, this is pulled from
241
+ # a compbination of mojombo's rakegem, hoe and hoe-git
242
+ #
243
+ # 1) make sure we are on the master branch
244
+ # 2) make sure there are no uncommitted items
245
+ # 3) check the manifest and make sure all looks good
246
+ # 4) build the gem
247
+ # 5) do an empty commit to have the commit message of the version
248
+ # 6) tag that commit as the version
249
+ # 7) push master
250
+ # 8) push the tag
251
+ # 7) pus the gem
252
+ #------------------------------------------------------------------------------
253
+ task :release_check do
254
+ unless `git branch` =~ /^\* master$/
255
+ abort "You must be on the master branch to release!"
256
+ end
257
+ unless `git status` =~ /^nothing to commit/m
258
+ abort "Nope, sorry, you have unfinished business"
259
+ end
260
+ end
261
+
262
+ desc "Create tag v#{This.version}, build and push #{This.platform_gemspec.full_name} to rubygems.org"
263
+ task :release => [ :release_check, 'manifest:check', :gem ] do
264
+ sh "git commit --allow-empty -a -m 'Release #{This.version}'"
265
+ sh "git tag -a -m 'v#{This.version}' v#{This.version}"
266
+ sh "git push origin master"
267
+ sh "git push origin v#{This.version}"
268
+ sh "gem push pkg/#{This.platform_gemspec.full_name}.gem"
269
+ end