sidekiq-failures 0.1.0 → 0.2.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/.gitignore CHANGED
@@ -3,7 +3,6 @@
3
3
  .bundle
4
4
  .config
5
5
  .yardoc
6
- Gemfile.lock
7
6
  InstalledFiles
8
7
  _yardoc
9
8
  coverage
data/CHANGELOG.md CHANGED
@@ -1,4 +1,14 @@
1
1
  ## Unreleased
2
+
3
+ ## 0.2.0
4
+ * Added processor identity to failure data (@krasnoukhov)
5
+ * Handle Sidekiq::Shutdown exceptions (@krasnoukhov)
6
+ * Add failures maximum count option (@mathieulaporte)
7
+ * User Expception#message instead of Exception#to_s (@supaspoida)
8
+ * Removed web depencies (@LongMan)
9
+ * Stop overloading find_template (@zquestz)
10
+
11
+ ## 0.1.0
2
12
  * Allow per worker configuration of failure tracking mode. Thanks to
3
13
  @kbaum for most of the work.
4
14
  * Prevent sidekiq-failures from loading up sidekiq/processor (and thus
data/Gemfile.lock ADDED
@@ -0,0 +1,56 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ sidekiq-failures (0.2.0)
5
+ sidekiq (>= 2.2.1)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ celluloid (0.14.0)
11
+ timers (>= 1.0.0)
12
+ connection_pool (1.0.0)
13
+ hike (1.2.1)
14
+ json (1.8.0)
15
+ multi_json (1.7.3)
16
+ rack (1.4.1)
17
+ rack-protection (1.2.0)
18
+ rack
19
+ rack-test (0.6.2)
20
+ rack (>= 1.0)
21
+ rake (0.9.2.2)
22
+ redis (3.0.4)
23
+ redis-namespace (1.3.0)
24
+ redis (~> 3.0.0)
25
+ sidekiq (2.12.0)
26
+ celluloid (>= 0.14.0)
27
+ connection_pool (>= 1.0.0)
28
+ json
29
+ redis (>= 3.0)
30
+ redis-namespace
31
+ sinatra (1.3.3)
32
+ rack (~> 1.3, >= 1.3.6)
33
+ rack-protection (~> 1.2)
34
+ tilt (~> 1.3, >= 1.3.3)
35
+ slim (1.3.4)
36
+ temple (~> 0.5.5)
37
+ tilt (~> 1.3.3)
38
+ sprockets (2.8.1)
39
+ hike (~> 1.2)
40
+ multi_json (~> 1.0)
41
+ rack (~> 1.0)
42
+ tilt (~> 1.1, != 1.3.0)
43
+ temple (0.5.5)
44
+ tilt (1.3.3)
45
+ timers (1.1.0)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ rack-test
52
+ rake
53
+ sidekiq-failures!
54
+ sinatra
55
+ slim
56
+ sprockets
data/README.md CHANGED
@@ -5,9 +5,7 @@ them. Makes use of Sidekiq's custom tabs and middleware chain.
5
5
 
6
6
  It mimics the way Resque keeps track of failures.
7
7
 
8
- TIP: Note that each failed job/retry might create a new failed job that will
9
- only be removed by you manually. This might result in a pretty big failures list
10
- depending on how you configure failures tracking in your workers.
8
+ WARNING: by default sidekiq-failures will keep up to 1000 failures. See [Maximum Tracked Failures](https://github.com/mhfs/sidekiq-failures#maximum-tracked-failures) below.
11
9
 
12
10
  ## Installation
13
11
 
@@ -17,19 +15,47 @@ Add this line to your application's Gemfile:
17
15
  gem 'sidekiq-failures'
18
16
  ```
19
17
 
20
- ## Dependencies
18
+ ## Usage
21
19
 
22
- Depends on Sidekiq >= 2.2.1
20
+ Simply having the gem in your Gemfile is enough to get you started. Your failed
21
+ jobs will be visible via a Failures tab in the Web UI.
22
+
23
+ ## Configuring
24
+
25
+ ### Maximum Tracked Failures
26
+
27
+ Since each failed job/retry creates a new failure entry that will only be removed
28
+ by you manually, your failures list might consume more resources than you have
29
+ available.
30
+
31
+ To avoid this sidekiq-failures adopts a default of 1000 maximum tracked failures.
32
+
33
+ To change the maximum amount:
34
+
35
+ ```ruby
36
+ Sidekiq.configure_server do |config|
37
+ config.failures_max_count = 5000
38
+ end
39
+ ```
40
+
41
+ To disable the limit entirely:
23
42
 
24
- ## Usage and Modes
43
+ ```ruby
44
+ Sidekiq.configure_server do |config|
45
+ config.failures_max_count = false
46
+ end
47
+ ```
25
48
 
26
- Simply having the gem in your Gemfile is enough to get you started. Your failed jobs will be visible via a Failures tab in the Web UI.
49
+ ### Failures Tracking Mode
27
50
 
28
51
  Sidekiq-failures offers three failures tracking options (per worker):
29
52
 
30
- ### all (default)
31
53
 
32
- Tracks failures everytime a background job fails. This mean a job with 25 retries enabled might generate up to 25 failure entries. If the worker has retry disabled only one failure will be tracked.
54
+ #### :all (default)
55
+
56
+ Tracks failures every time a background job fails. This mean a job with 25 retries
57
+ enabled might generate up to 25 failure entries. If the worker has retry disabled
58
+ only one failure will be tracked.
33
59
 
34
60
  This is the default behavior but can be made explicit with:
35
61
 
@@ -43,9 +69,10 @@ class MyWorker
43
69
  end
44
70
  ```
45
71
 
46
- ### exhausted
72
+ #### :exhausted
47
73
 
48
- Only track failures if the job exhausts all its retries (or doesn't have retries enabled).
74
+ Only track failures if the job exhausts all its retries (or doesn't have retries
75
+ enabled).
49
76
 
50
77
  You can set this mode as follows:
51
78
 
@@ -59,7 +86,7 @@ class MyWorker
59
86
  end
60
87
  ```
61
88
 
62
- ### off
89
+ #### :off
63
90
 
64
91
  You can also completely turn off failures tracking for a given worker as follows:
65
92
 
@@ -73,9 +100,10 @@ class MyWorker
73
100
  end
74
101
  ```
75
102
 
76
- ### Change the default mode
103
+ #### Change the default mode
77
104
 
78
- You can also change the default of all your workers at once by setting the following server config:
105
+ You can also change the default of all your workers at once by setting the following
106
+ server config:
79
107
 
80
108
  ```ruby
81
109
  Sidekiq.configure_server do |config|
@@ -85,6 +113,10 @@ end
85
113
 
86
114
  The valid modes are `:all`, `:exhausted` or `:off`.
87
115
 
116
+ ## Dependencies
117
+
118
+ Depends on Sidekiq >= 2.2.1
119
+
88
120
  ## TODO
89
121
 
90
122
  * Allow triggering retry of specific failed jobs via Web UI.
@@ -1,11 +1,15 @@
1
1
  module Sidekiq
2
2
  module Failures
3
+
3
4
  class Middleware
5
+ include Sidekiq::Util
4
6
  attr_accessor :msg
5
7
 
6
8
  def call(worker, msg, queue)
7
9
  self.msg = msg
8
10
  yield
11
+ rescue Sidekiq::Shutdown
12
+ raise
9
13
  rescue => e
10
14
  raise e if skip_failure?
11
15
 
@@ -13,13 +17,19 @@ module Sidekiq
13
17
  :failed_at => Time.now.strftime("%Y/%m/%d %H:%M:%S %Z"),
14
18
  :payload => msg,
15
19
  :exception => e.class.to_s,
16
- :error => e.to_s,
20
+ :error => e.message,
17
21
  :backtrace => e.backtrace,
18
22
  :worker => msg['class'],
23
+ :processor => "#{hostname}:#{process_id}-#{Thread.current.object_id}",
19
24
  :queue => queue
20
25
  }
21
26
 
22
- Sidekiq.redis { |conn| conn.lpush(:failed, Sidekiq.dump_json(data)) }
27
+ Sidekiq.redis do |conn|
28
+ conn.rpush(:failed, Sidekiq.dump_json(data))
29
+ unless Sidekiq.failures_max_count == false
30
+ conn.ltrim(:failed, (-Sidekiq.failures_max_count), -1)
31
+ end
32
+ end
23
33
 
24
34
  raise e
25
35
  end
@@ -1,5 +1,5 @@
1
1
  module Sidekiq
2
2
  module Failures
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
@@ -25,6 +25,8 @@ header.row
25
25
  td style="overflow: auto; padding: 10px;"
26
26
  a.backtrace href="#" onclick="$(this).next().toggle(); return false" = "#{msg['exception']}: #{msg['error']}"
27
27
  pre style="display: none; background: none; border: 0; width: 100%; max-height: 30em; font-size: 0.8em; white-space: nowrap;" == msg['backtrace'].join("<br />")
28
+ p
29
+ span Processor: #{msg['processor']}
28
30
 
29
31
  div.row
30
32
  .span5
@@ -3,20 +3,14 @@ module Sidekiq
3
3
  module WebExtension
4
4
 
5
5
  def self.registered(app)
6
- app.helpers do
7
- def find_template(view, *a, &b)
8
- dir = File.expand_path("../views/", __FILE__)
9
- super(dir, *a, &b)
10
- super
11
- end
12
- end
13
-
14
6
  app.get "/failures" do
7
+ view_path = File.join(File.expand_path("..", __FILE__), "views")
8
+
15
9
  @count = (params[:count] || 25).to_i
16
10
  (@current_page, @total_size, @messages) = page("failed", params[:page], @count)
17
11
  @messages = @messages.map { |msg| Sidekiq.load_json(msg) }
18
12
 
19
- slim :failures
13
+ render(:slim, File.read(File.join(view_path, "failures.slim")))
20
14
  end
21
15
 
22
16
  app.post "/failures/remove" do
@@ -1,4 +1,9 @@
1
- require "sidekiq/web"
1
+ begin
2
+ require "sidekiq/web"
3
+ rescue LoadError
4
+ # client-only usage
5
+ end
6
+
2
7
  require "sidekiq/failures/version"
3
8
  require "sidekiq/failures/middleware"
4
9
  require "sidekiq/failures/web_extension"
@@ -26,17 +31,26 @@ module Sidekiq
26
31
  @failures_default_mode || :all
27
32
  end
28
33
 
29
- module Failures
34
+ # Sets the maximum number of failures to track
35
+ #
36
+ # If the number of failures exceeds this number the list will be trimmed (oldest
37
+ # failures will be purged).
38
+ #
39
+ # Defaults to 1000
40
+ # Set to false to disable rotation
41
+ def self.failures_max_count=(value)
42
+ @failures_max_count = value
30
43
  end
31
- end
32
44
 
33
- Sidekiq::Web.register Sidekiq::Failures::WebExtension
45
+ # Fetches the failures max count value
46
+ def self.failures_max_count
47
+ return 1000 if @failures_max_count.nil?
48
+
49
+ @failures_max_count
50
+ end
34
51
 
35
- if Sidekiq::Web.tabs.is_a?(Array)
36
- # For sidekiq < 2.5
37
- Sidekiq::Web.tabs << "failures"
38
- else
39
- Sidekiq::Web.tabs["Failures"] = "failures"
52
+ module Failures
53
+ end
40
54
  end
41
55
 
42
56
  Sidekiq.configure_server do |config|
@@ -44,3 +58,14 @@ Sidekiq.configure_server do |config|
44
58
  chain.add Sidekiq::Failures::Middleware
45
59
  end
46
60
  end
61
+
62
+ if defined?(Sidekiq::Web)
63
+ Sidekiq::Web.register Sidekiq::Failures::WebExtension
64
+
65
+ if Sidekiq::Web.tabs.is_a?(Array)
66
+ # For sidekiq < 2.5
67
+ Sidekiq::Web.tabs << "failures"
68
+ else
69
+ Sidekiq::Web.tabs["Failures"] = "failures"
70
+ end
71
+ end
@@ -16,10 +16,10 @@ Gem::Specification.new do |gem|
16
16
  gem.version = Sidekiq::Failures::VERSION
17
17
 
18
18
  gem.add_dependency "sidekiq", ">= 2.2.1"
19
- gem.add_dependency "slim"
20
- gem.add_dependency "sinatra"
21
- gem.add_dependency "sprockets"
22
19
 
23
20
  gem.add_development_dependency "rake"
24
21
  gem.add_development_dependency "rack-test"
22
+ gem.add_development_dependency "sprockets"
23
+ gem.add_development_dependency "sinatra"
24
+ gem.add_development_dependency "slim"
25
25
  end
@@ -5,8 +5,8 @@ module Sidekiq
5
5
  describe "Middleware" do
6
6
  before do
7
7
  $invokes = 0
8
- boss = MiniTest::Mock.new
9
- @processor = ::Sidekiq::Processor.new(boss)
8
+ @boss = MiniTest::Mock.new
9
+ @processor = ::Sidekiq::Processor.new(@boss)
10
10
  Sidekiq.server_middleware {|chain| chain.add Sidekiq::Failures::Middleware }
11
11
  Sidekiq.redis = REDIS
12
12
  Sidekiq.redis { |c| c.flushdb }
@@ -14,12 +14,14 @@ module Sidekiq
14
14
  end
15
15
 
16
16
  TestException = Class.new(StandardError)
17
+ ShutdownException = Class.new(Sidekiq::Shutdown)
17
18
 
18
19
  class MockWorker
19
20
  include Sidekiq::Worker
20
21
 
21
22
  def perform(args)
22
23
  $invokes += 1
24
+ raise ShutdownException.new if args == "shutdown"
23
25
  raise TestException.new("failed!")
24
26
  end
25
27
  end
@@ -35,12 +37,12 @@ module Sidekiq
35
37
  end
36
38
 
37
39
  it 'records all failures by default' do
38
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'])
40
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'])
39
41
 
40
42
  assert_equal 0, failures_count
41
43
 
42
44
  assert_raises TestException do
43
- @processor.process(msg, 'default')
45
+ @processor.process(msg)
44
46
  end
45
47
 
46
48
  assert_equal 1, failures_count
@@ -48,25 +50,40 @@ module Sidekiq
48
50
  end
49
51
 
50
52
  it 'records all failures if explicitly told to' do
51
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => true)
53
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => true)
52
54
 
53
55
  assert_equal 0, failures_count
54
56
 
55
57
  assert_raises TestException do
56
- @processor.process(msg, 'default')
58
+ @processor.process(msg)
57
59
  end
58
60
 
59
61
  assert_equal 1, failures_count
60
62
  assert_equal 1, $invokes
61
63
  end
62
64
 
65
+ it "doesn't record internal shutdown failure" do
66
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['shutdown'], 'failures' => true)
67
+
68
+ assert_equal 0, failures_count
69
+
70
+ actor = MiniTest::Mock.new
71
+ actor.expect(:processor_done, nil, [@processor])
72
+ @boss.expect(:async, actor, [])
73
+ @processor.process(msg)
74
+ @boss.verify
75
+
76
+ assert_equal 0, failures_count
77
+ assert_equal 1, $invokes
78
+ end
79
+
63
80
  it "doesn't record failure if failures disabled" do
64
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => false)
81
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => false)
65
82
 
66
83
  assert_equal 0, failures_count
67
84
 
68
85
  assert_raises TestException do
69
- @processor.process(msg, 'default')
86
+ @processor.process(msg)
70
87
  end
71
88
 
72
89
  assert_equal 0, failures_count
@@ -76,12 +93,12 @@ module Sidekiq
76
93
  it "doesn't record failure if going to be retired again and configured to track exhaustion by default" do
77
94
  Sidekiq.failures_default_mode = :exhausted
78
95
 
79
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'] )
96
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'] )
80
97
 
81
98
  assert_equal 0, failures_count
82
99
 
83
100
  assert_raises TestException do
84
- @processor.process(msg, 'default')
101
+ @processor.process(msg)
85
102
  end
86
103
 
87
104
  assert_equal 0, failures_count
@@ -90,12 +107,12 @@ module Sidekiq
90
107
 
91
108
 
92
109
  it "doesn't record failure if going to be retired again and configured to track exhaustion" do
93
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => 'exhausted')
110
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'failures' => 'exhausted')
94
111
 
95
112
  assert_equal 0, failures_count
96
113
 
97
114
  assert_raises TestException do
98
- @processor.process(msg, 'default')
115
+ @processor.process(msg)
99
116
  end
100
117
 
101
118
  assert_equal 0, failures_count
@@ -103,12 +120,12 @@ module Sidekiq
103
120
  end
104
121
 
105
122
  it "records failure if failing last retry and configured to track exhaustion" do
106
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry_count' => 24, 'failures' => 'exhausted')
123
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry_count' => 24, 'failures' => 'exhausted')
107
124
 
108
125
  assert_equal 0, failures_count
109
126
 
110
127
  assert_raises TestException do
111
- @processor.process(msg, 'default')
128
+ @processor.process(msg)
112
129
  end
113
130
 
114
131
  assert_equal 1, failures_count
@@ -118,26 +135,47 @@ module Sidekiq
118
135
  it "records failure if failing last retry and configured to track exhaustion by default" do
119
136
  Sidekiq.failures_default_mode = 'exhausted'
120
137
 
121
- msg = create_message('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry_count' => 24)
138
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'], 'retry_count' => 24)
122
139
 
123
140
  assert_equal 0, failures_count
124
141
 
125
142
  assert_raises TestException do
126
- @processor.process(msg, 'default')
143
+ @processor.process(msg)
127
144
  end
128
145
 
129
146
  assert_equal 1, failures_count
130
147
  assert_equal 1, $invokes
131
148
  end
132
149
 
150
+ it "removes old failures when failures_max_count has been reached" do
151
+ assert_equal 1000, Sidekiq.failures_max_count
152
+ Sidekiq.failures_max_count = 2
133
153
 
154
+ msg = create_work('class' => MockWorker.to_s, 'args' => ['myarg'])
155
+
156
+ assert_equal 0, failures_count
157
+
158
+ 3.times do
159
+ assert_raises TestException do
160
+ ::Sidekiq::Processor.new(MiniTest::Mock.new).process(msg)
161
+ end
162
+ end
163
+
164
+ assert_equal 2, failures_count
165
+
166
+ Sidekiq.failures_max_count = false
167
+ assert Sidekiq.failures_max_count == false
168
+
169
+ Sidekiq.failures_max_count = nil
170
+ assert_equal 1000, Sidekiq.failures_max_count
171
+ end
134
172
 
135
173
  def failures_count
136
174
  Sidekiq.redis { |conn|conn.llen('failed') } || 0
137
175
  end
138
176
 
139
- def create_message(params)
140
- Sidekiq.dump_json(params)
177
+ def create_work(msg)
178
+ Sidekiq::BasicFetch::UnitOfWork.new('default', Sidekiq.dump_json(msg))
141
179
  end
142
180
  end
143
181
  end
data/test/test_helper.rb CHANGED
@@ -14,9 +14,12 @@ end
14
14
 
15
15
  require "rack/test"
16
16
 
17
+ require "celluloid"
17
18
  require "sidekiq"
18
19
  require "sidekiq-failures"
19
20
  require "sidekiq/processor"
21
+ require "sidekiq/fetch"
22
+ require "sidekiq/cli"
20
23
 
21
24
  Celluloid.logger = nil
22
25
  Sidekiq.logger.level = Logger::ERROR
@@ -1,4 +1,5 @@
1
1
  require "test_helper"
2
+ require "sidekiq/web"
2
3
 
3
4
  module Sidekiq
4
5
  describe "WebExtension" do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sidekiq-failures
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
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: 2012-12-30 00:00:00.000000000 Z
12
+ date: 2013-06-12 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sidekiq
@@ -28,14 +28,14 @@ dependencies:
28
28
  - !ruby/object:Gem::Version
29
29
  version: 2.2.1
30
30
  - !ruby/object:Gem::Dependency
31
- name: slim
31
+ name: rake
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  none: false
34
34
  requirements:
35
35
  - - ! '>='
36
36
  - !ruby/object:Gem::Version
37
37
  version: '0'
38
- type: :runtime
38
+ type: :development
39
39
  prerelease: false
40
40
  version_requirements: !ruby/object:Gem::Requirement
41
41
  none: false
@@ -44,14 +44,14 @@ dependencies:
44
44
  - !ruby/object:Gem::Version
45
45
  version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: sinatra
47
+ name: rack-test
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  none: false
50
50
  requirements:
51
51
  - - ! '>='
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
- type: :runtime
54
+ type: :development
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  none: false
@@ -67,7 +67,7 @@ dependencies:
67
67
  - - ! '>='
68
68
  - !ruby/object:Gem::Version
69
69
  version: '0'
70
- type: :runtime
70
+ type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  none: false
@@ -76,7 +76,7 @@ dependencies:
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  - !ruby/object:Gem::Dependency
79
- name: rake
79
+ name: sinatra
80
80
  requirement: !ruby/object:Gem::Requirement
81
81
  none: false
82
82
  requirements:
@@ -92,7 +92,7 @@ dependencies:
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
94
  - !ruby/object:Gem::Dependency
95
- name: rack-test
95
+ name: slim
96
96
  requirement: !ruby/object:Gem::Requirement
97
97
  none: false
98
98
  requirements:
@@ -117,6 +117,7 @@ files:
117
117
  - .gitignore
118
118
  - CHANGELOG.md
119
119
  - Gemfile
120
+ - Gemfile.lock
120
121
  - LICENSE
121
122
  - README.md
122
123
  - Rakefile
@@ -144,7 +145,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
145
  version: '0'
145
146
  segments:
146
147
  - 0
147
- hash: -4591242293469057094
148
+ hash: -4370758603814256180
148
149
  required_rubygems_version: !ruby/object:Gem::Requirement
149
150
  none: false
150
151
  requirements:
@@ -153,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
153
154
  version: '0'
154
155
  segments:
155
156
  - 0
156
- hash: -4591242293469057094
157
+ hash: -4370758603814256180
157
158
  requirements: []
158
159
  rubyforge_project:
159
160
  rubygems_version: 1.8.23