stubbornly 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1205ff4f8568bb88aca04e7f902a002865684100594a40dd3b1b2ad34c3e2590
4
- data.tar.gz: 48b4637aa7db561154399bb69c0f0b1b289700c606d3550a11f139daa4bedffb
3
+ metadata.gz: 61541dc62beb9db0f9aeefe99bf5dd84d1773efe2af2994a445f1b5f33a102b0
4
+ data.tar.gz: e69c526fd99e7ac41e1e5b813511d24d0bf67948ca0ffd5cc7c5ce0d1adf202f
5
5
  SHA512:
6
- metadata.gz: 8c0522171461dccbaf95ad5286d78296201bc3a9bfbf8a06b9a22499a1c74eea32a81c424cca81feba69dd96f269099b36971f9f3d61e1b24bf8e45595d9a06c
7
- data.tar.gz: 0ceedfba3af82388158b7d848650b226384d8337d553361a0e9759703c781e8d56601410dd436d993a3cd675a2b55be6fa072bcc5b9a3bfa5592cb3063c0382f
6
+ metadata.gz: ffeaf2ef474a9f22d0fff5d17dc9d03d1c08dc94862f9cf48b74181501bdace3f65d798ef03aec92d9e3732204b20fce3f72570c5bf9e5394169e7ad046568c3
7
+ data.tar.gz: e99e74cbe1ba5081f6db301e267ca7fc8b900fc3445782fbf7c0384c224684e06589a918b69e265f17bee9f21641f148ae4a0fb818a00e159b03c90d3ddf2338
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2018-07-01 13:37:09 +0200 using RuboCop version 0.57.2.
3
+ # on 2018-07-01 19:37:52 +0200 using RuboCop version 0.57.2.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -8,18 +8,22 @@
8
8
 
9
9
  # Offense count: 1
10
10
  Metrics/AbcSize:
11
- Max: 22
11
+ Max: 27
12
12
 
13
- # Offense count: 1
13
+ # Offense count: 2
14
14
  Metrics/CyclomaticComplexity:
15
15
  Max: 7
16
16
 
17
17
  # Offense count: 2
18
18
  # Configuration parameters: CountComments.
19
19
  Metrics/MethodLength:
20
- Max: 24
20
+ Max: 27
21
21
 
22
22
  # Offense count: 1
23
23
  Security/Eval:
24
24
  Exclude:
25
- - 'spec/unit/examples_spec.rb'
25
+ - 'spec/system/readme_spec.rb'
26
+
27
+ Style/MutableConstant:
28
+ Exclude:
29
+ - 'lib/stubbornly/version.rb'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- stubbornly (0.1.0)
4
+ stubbornly (0.2.0)
5
5
  null-logger
6
6
 
7
7
  GEM
@@ -101,6 +101,7 @@ GEM
101
101
  unf_ext
102
102
  unf_ext (0.0.7.5)
103
103
  unicode-display_width (1.4.0)
104
+ yard (0.9.14)
104
105
 
105
106
  PLATFORMS
106
107
  ruby
@@ -117,6 +118,7 @@ DEPENDENCIES
117
118
  rspec
118
119
  rubocop
119
120
  stubbornly!
121
+ yard
120
122
 
121
123
  BUNDLED WITH
122
124
  1.16.2
data/Guardfile CHANGED
@@ -17,5 +17,7 @@ guard :rspec, cmd: 'bundle exec rspec' do
17
17
  watch(rspec.spec_helper) { rspec.spec_dir }
18
18
  watch(rspec.spec_files)
19
19
  watch(%r{^lib/.*/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
20
+ watch('README.markdown') { 'spec/system/examples_spec.rb' }
21
+ watch(%r{^examples/(.+)}) { |m| "spec/system/examples/#{m[1]}_spec.rb" }
20
22
  dsl.watch_spec_files_for(dsl.ruby.lib_files)
21
23
  end
data/README.markdown CHANGED
@@ -2,20 +2,20 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/suhlig/stubbornly.svg?branch=master)](https://travis-ci.org/suhlig/stubbornly)
4
4
 
5
- Stubbornly retries a given block until the maximum number of attempts or timeout has been reached.
5
+ Stubbornly retries a given block until the maximum number of attempts or a timeout has been reached.
6
6
 
7
- ## Example
7
+ ## Examples
8
+
9
+ ### Timeout
8
10
 
9
11
  ```ruby
10
12
  require 'stubbornly'
11
13
  require 'http'
12
- require 'logger'
13
14
 
14
15
  class Checker
15
16
  def initialize(url)
16
17
  @url = url
17
- @logger = Logger.new(STDOUT)
18
- @stubbornly = Stubbornly.new(logger: @logger)
18
+ @stubbornly = Stubbornly.new
19
19
  end
20
20
 
21
21
  def up?
@@ -25,10 +25,37 @@ class Checker
25
25
  end
26
26
  end
27
27
 
28
- if Checker.new('http://localhost:8765').up?
29
- puts "Website is up"
30
- else
31
- warn "We are DOWN!"
28
+ begin
29
+ Checker.new('http://localhost:8765').up?
30
+ rescue => e
31
+ warn "Error: #{e.message}"
32
+ end
33
+ ```
34
+
35
+ ### Maximum number of attempts
36
+
37
+ ```ruby
38
+ require 'stubbornly'
39
+ require 'http'
40
+
41
+ class Checker
42
+ def initialize(url)
43
+ @url = url
44
+ @stubbornly = Stubbornly.new
45
+ end
46
+
47
+ def up?
48
+ @stubbornly.retry(attempts: 3) do
49
+ response = HTTP.get(@url)
50
+ puts "The site at #{@url} is up 👍"
51
+ end
52
+ end
53
+ end
54
+
55
+ begin
56
+ Checker.new('http://localhost:8765').up?
57
+ rescue => e
58
+ warn "Error: #{e.message}"
32
59
  end
33
60
  ```
34
61
 
data/Rakefile CHANGED
@@ -8,4 +8,8 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  require 'rubocop/rake_task'
9
9
  RuboCop::RakeTask.new
10
10
 
11
+ require 'yard'
12
+ require 'yard/rake/yardoc_task'
13
+ YARD::Rake::YardocTask.new
14
+
11
15
  task default: %i[rubocop spec]
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'stubbornly'
5
+ require 'http'
6
+ require 'logger'
7
+
8
+ #
9
+ # Checks a URL
10
+ #
11
+ class Checker
12
+ def initialize(url)
13
+ @url = url
14
+ @logger = Logger.new(STDERR, progname: self.class)
15
+ @stubbornly = Stubbornly.new(logger: Logger.new(STDERR, progname: Stubbornly))
16
+ end
17
+
18
+ #
19
+ # Return the status code of the given URL.
20
+ # Retry if failed, but
21
+ # - not more often than specified
22
+ # - only within the given timeout.
23
+ #
24
+ def status
25
+ @logger.info "Checking status of #{@url}"
26
+ @stubbornly.retry(timeout: 10, attempts: 4) do
27
+ HTTP.get(@url).code.tap do |result|
28
+ @logger.info "Result is #{result}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ url = ARGV.shift || 'http://example.com'
35
+
36
+ begin
37
+ puts Checker.new(url).status
38
+ rescue StandardError => e
39
+ warn "Error: #{e.message}"
40
+ exit 1
41
+ end
data/lib/stubbornly.rb CHANGED
@@ -4,37 +4,70 @@ require 'null_logger'
4
4
  require 'English'
5
5
 
6
6
  class Stubbornly
7
- def initialize(logger: NullLogger.new)
7
+ #
8
+ # Constructs a new `Stubbornly` instance.
9
+ #
10
+ # If a block is passed, it will be called to calculate the sleep time (in
11
+ # seconds) before the next attempt is made. The attempt counter is passed
12
+ # as a block argument.
13
+ #
14
+ # This is useful to determine the backoff characteristics. The result of the
15
+ # block will be the sleep time in seconds. For instance, the following code
16
+ # would implement an exponential backoff with retries after 1, 3, 7, 15,
17
+ # 31, ... seconds:
18
+ #
19
+ # Stubbornly.new {|attempt| 2**attempt - 1 }
20
+ #
21
+ # The default backoff is 1 (constant), i.e. the attempts are equidistant at 1s.
22
+ #
23
+ def initialize(logger: NullLogger.new, &block)
8
24
  @logger = logger
25
+ @backoff = block || proc { 1 }
9
26
  end
10
27
 
11
- def retry(timeout: Float::INFINITY, attempts: Float::MAX)
12
- @logger.debug("Starting first of not more than #{attempts} attempts") if attempts < Float::MAX
28
+ #
29
+ # Calls the given block, rescuing its errors, and retries until either the
30
+ # given timeout or the maximum number of attempts was exceeded.
31
+ #
32
+ # When an attempt raises an error, this method will sleep before it retries.
33
+ # The number of seconds to sleep is determined by the block passed to {#initialize}.
34
+ #
35
+ # If the block succeeds, its value is returned.
36
+ #
37
+ def retry(timeout: Float::INFINITY, attempts: Float::INFINITY, &block)
38
+ @logger.debug("Attempting not more than #{attempts} times") unless attempts.infinite?
13
39
  @logger.debug("Will time out after #{timeout}s") unless timeout.infinite?
14
40
 
15
- attempt = 0
41
+ attempt = 1
16
42
  start = Time.now
17
43
 
18
44
  begin
19
- @logger.info "Attempt ##{attempt}"
20
- yield.tap do
21
- @logger.info "Success after #{elapsed_since(start)}s and #{attempt} attempts"
45
+ @logger.info "Attempt ##{attempt} #{after_elapsed_since(start)}"
46
+
47
+ if block
48
+ yield(attempt, elapsed_since(start)).tap do
49
+ @logger.info "Success #{after_elapsed_since(start)} and #{attempt} attempts"
50
+ end
22
51
  end
23
- rescue StandardError
24
- attempt += 1
25
- retry_after = 2**attempt - 1
26
- @logger.warn "#{$ERROR_INFO.message}. Trying again in #{retry_after}s."
52
+ rescue StandardError => error
53
+ @logger.warn error.message
27
54
 
28
55
  if attempt >= attempts
29
- @logger.error "Maximum number of attempts (#{attempt}) reached"
30
- return false
56
+ @logger.error "Maximum number of attempts (#{attempts}) reached (#{after_elapsed_since(start)})"
57
+ raise
31
58
  end
32
59
 
60
+ # TODO: Give up NOW if retry_after would exceed timeout
61
+
33
62
  if elapsed_since(start) >= timeout
34
- @logger.info "Timed out after #{elapsed_since(start)}s"
35
- return false
63
+ @logger.info "Timed out #{after_elapsed_since(start)} (#{attempt} attempts)"
64
+ raise
36
65
  end
37
66
 
67
+ attempt += 1
68
+ retry_after = @backoff.call(attempt)
69
+
70
+ @logger.warn "Trying again in #{retry_after}s."
38
71
  sleep(retry_after)
39
72
  retry
40
73
  end
@@ -45,4 +78,8 @@ class Stubbornly
45
78
  def elapsed_since(start)
46
79
  Time.now - start
47
80
  end
81
+
82
+ def after_elapsed_since(start)
83
+ "after #{elapsed_since(start).round(2)}s"
84
+ end
48
85
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Stubbornly
4
- VERSION = '0.1.0'.freeze
4
+ VERSION = '0.2.0'
5
5
  end
data/stubbornly.gemspec CHANGED
@@ -28,10 +28,11 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency 'guard'
29
29
  spec.add_development_dependency 'guard-bundler'
30
30
  spec.add_development_dependency 'guard-rspec'
31
- spec.add_development_dependency 'http'
31
+ spec.add_development_dependency 'http' # examples
32
32
  spec.add_development_dependency 'pry'
33
33
  spec.add_development_dependency 'pry-byebug'
34
34
  spec.add_development_dependency 'rake'
35
35
  spec.add_development_dependency 'rspec'
36
36
  spec.add_development_dependency 'rubocop'
37
+ spec.add_development_dependency 'yard'
37
38
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stubbornly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steffen Uhlig
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: yard
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  description: Stubbornly retries a given block until the maximum number of attempts
168
182
  or timeout has been reached.
169
183
  email:
@@ -183,6 +197,7 @@ files:
183
197
  - LICENSE.txt
184
198
  - README.markdown
185
199
  - Rakefile
200
+ - examples/http_status
186
201
  - lib/stubbornly.rb
187
202
  - lib/stubbornly/version.rb
188
203
  - stubbornly.gemspec