timeloop 1.0.3 → 2.0.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,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- MTMzN2I2ZmE3MGY1MjMyMzY5ZWQxOTNkNDVhMDljYjc0OWQ0MTk5NQ==
5
- data.tar.gz: !binary |-
6
- ODYxODZmNWFlOTM5Njc0OTkxZjc5YTg3OWFhOTNjZjZlNjVkY2ZiNQ==
2
+ SHA1:
3
+ metadata.gz: f87b83fb56ecfca6fa89255b53b1e07da53d9963
4
+ data.tar.gz: a6f76643df4419b586da4df4726003db238b6ae8
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- OGQwMDk2Y2YyMThkNDc3MmEyNWJmZmJmZTM1ZjkxYmQ3NGM0NDk5YWMxMjg5
10
- ZDgyNDI4OWRhYWY0NzJiNTRmYTYxMTMxZTZhNGIwZTg5ZTdiOTdhZmJhMjFi
11
- NGNjZjYyMjRiZDc2ODRmMzA0MmViMjI5NmE0NGU3YmY0ZDUwNjA=
12
- data.tar.gz: !binary |-
13
- YzUxOTg2ZGJkNjFiZGZlM2VkNTUxNmRjNDMwMGRhY2IyMmJmMTkwN2ZhOTI5
14
- YzgwYzZkNWJjNzAwYzRkNzNlNWNkNGRhZWVlYjcxNTY4ODY4NzMxNTk1ZWUy
15
- YTFhNjMyYWViZWRhNmMwMDAzYTJjYzM5OTBmNmMwNWJjMjk0NGQ=
6
+ metadata.gz: 68ebd000097745cfb4f9de25a4ba3152a44876a9987b5a6361eec557f3600a6da5f943e0e5c45ffad39faa2dcf08a90ccf33da5e3ff8c9f72d4a8372016f6b8a
7
+ data.tar.gz: df2377a796a83f2cb6431fd7ef607f9432e7c48e80cc52e6776d314bbf6168313a2402d31ca8226dd8d636f997d7bf136a25e85e7b428b62e97be56fb5536fc6
data/.travis.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  language: ruby
2
2
  script: bundle exec rake
3
+ sudo: false
3
4
  rvm:
4
5
  - 1.9.3
5
6
  - 2.0.0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Timeloop [![Code Climate](https://codeclimate.com/github/pinoss/timeloop.png)](https://codeclimate.com/github/pinoss/timeloop) [![Build Status](https://travis-ci.org/pinoss/timeloop.svg?branch=master)](https://travis-ci.org/pinoss/timeloop) [![Coverage Status](https://coveralls.io/repos/pinoss/timeloop/badge.png?branch=master)](https://coveralls.io/r/pinoss/timeloop?branch=master) [![Gem Version](https://badge.fury.io/rb/timeloop.svg)](http://badge.fury.io/rb/timeloop)
2
2
 
3
- Timeloop is a simple Ruby gem that provides loop with time interval inspired on 'every' method from whenever gem.
3
+ Timeloop is a simple Ruby gem that provides loop with time interval inspired on 'every' method from whenever gem.
4
4
 
5
5
  ## Installation
6
6
 
@@ -23,7 +23,7 @@ Require gem in you Ruby file:
23
23
  ```ruby
24
24
  require 'timeloop'
25
25
 
26
- every 10.seconds do
26
+ Timeloop.every 10.seconds do
27
27
  puts '10 seconds delay'
28
28
  end
29
29
 
@@ -34,7 +34,7 @@ end
34
34
  # 10 seconds delay
35
35
  # ...
36
36
 
37
- every 3.hours do
37
+ Timeloop.every 3.hours do
38
38
  Mail.deliver do
39
39
  from 'me@test.email'
40
40
  to 'you@test.email'
@@ -43,7 +43,7 @@ every 3.hours do
43
43
  end
44
44
  end
45
45
 
46
- every :minute do
46
+ Timeloop.every :minute do
47
47
  product = 2 * 2
48
48
  puts "and 2 * 2 is still #{product}"
49
49
  end
@@ -59,7 +59,7 @@ end
59
59
  You can also specify how many times your block will be evaluated:
60
60
 
61
61
  ```ruby
62
- every 10.seconds, maximum: 4.times do |i|
62
+ Timeloop.every 10.seconds, maximum: 4.times do |i|
63
63
  puts i
64
64
  end
65
65
 
@@ -71,7 +71,7 @@ end
71
71
  # 3
72
72
  # => 4
73
73
 
74
- every 'second', maximum: 3 do |i|
74
+ Timeloop.every 'second', maximum: 3 do |i|
75
75
  puts 'You will see me only 3 times.'
76
76
  end
77
77
 
@@ -83,6 +83,21 @@ end
83
83
  # => 3
84
84
  ```
85
85
 
86
+ If you want to include the `every` method in your own classes simply
87
+ include the `Timeloop::Helper` module.
88
+
89
+ ```ruby
90
+ class MyClass
91
+ include Timeloop::Helper
92
+
93
+ def doit
94
+ every 10.seconds, max: 4 do |i|
95
+ puts i
96
+ end
97
+ end
98
+ end
99
+ ```
100
+
86
101
  ## Contributing
87
102
 
88
103
  1. Fork it ( https://github.com/pinoss/timeloop/fork )
data/lib/timeloop.rb CHANGED
@@ -1,28 +1,69 @@
1
1
  require 'timeloop/core_ext'
2
2
  require 'timeloop/version'
3
3
 
4
- module Kernel
5
- def every(value, opts = {})
6
- maximum = parse_maximum_value(opts.fetch(:maximum)) if opts.key? :maximum
7
- frequency = parse_frequency(value)
8
-
9
- if maximum.nil?
10
- i = 0
11
- loop do
12
- yield(i) if block_given?
13
- sleep frequency
14
-
15
- i += 1
16
- end
17
- else
18
- maximum.times do |i|
19
- yield(i) if block_given?
20
- sleep frequency
21
- end
4
+ # Execute a block of code periodically. The runtime of the block is
5
+ # taken in to account so a delay in one run does not impact the start
6
+ # times of future runs.
7
+ class Timeloop
8
+ # Runs provided block periodically.
9
+ #
10
+ # Yields Integer index of the iteration to the provide block every `period`.
11
+ def loop
12
+ i = -1
13
+ super() do
14
+ run_started_at = Time.now
15
+ i += 1
16
+ logger.debug("#{to_s}: starting #{i}th run")
17
+ yield(i) if block_given?
18
+
19
+ break if i+1 >= max
20
+
21
+ sleep til_next_start_time(Time.now - run_started_at)
22
+ .tap{|s| logger.debug "#{to_s}: sleeping #{s} seconds until next run" }
22
23
  end
23
24
  end
24
25
 
25
- private
26
+ # Returns string representation of self,
27
+ def to_s
28
+ ["#<Timeloop period=#{period}",
29
+ max < Float::INFINITY ? ", max=#{max}" : "",
30
+ ">"].join
31
+ end
32
+
33
+ protected
34
+
35
+ attr_reader :period, :max, :logger
36
+
37
+ # Initialize a new Timeloop object.
38
+ #
39
+ # Options
40
+ # period - seconds or name of time period (eg, 'second', 'hour')
41
+ # between iterations
42
+ # max - the maximum number of executions of the block. Default:
43
+ # INFINITY
44
+ # maximum - synonym of `max`
45
+ # logger - logger to which debugging info should be sent. Default:
46
+ # no logging
47
+ def initialize(opts)
48
+ @period = parse_frequency(opts.fetch(:period))
49
+ @max = parse_maximum_value(opts.fetch(:maximum){opts.fetch(:max, Float::INFINITY)})
50
+ @logger = opts.fetch(:logger) { NULL_LOGGER }
51
+ end
52
+
53
+ def til_next_start_time(current_run_duration)
54
+ [0, period - current_run_duration].max
55
+ end
56
+
57
+ def parse_maximum_value(maximum)
58
+ case maximum
59
+ when Enumerator
60
+ maximum.count
61
+ when Integer, Float::INFINITY
62
+ maximum
63
+ else
64
+ fail ArgumentError.new('wrong type of argument (should be an Enumerator or Integer)')
65
+ end
66
+ end
26
67
 
27
68
  def parse_frequency(frequency)
28
69
  case frequency
@@ -30,7 +71,7 @@ module Kernel
30
71
  frequency
31
72
  when :second, 'second'
32
73
  1.second
33
- when :minute, 'second'
74
+ when :minute, 'minute'
34
75
  1.minute
35
76
  when :hour, 'hour'
36
77
  1.hour
@@ -45,18 +86,34 @@ module Kernel
45
86
  end
46
87
  end
47
88
 
48
- def parse_maximum_value(maximum)
49
- case maximum
50
- when Enumerator
51
- maximum.count
52
- when Integer
53
- maximum
54
- else
55
- fail ArgumentError.new('wrong type of argument (should be an Enumerator or Integer)')
89
+ # Useful to avoid conditionals in logging code.
90
+ NULL_LOGGER = Class.new do
91
+ def debug(*args); end
92
+ end.new
93
+
94
+ # Conveniences methods that provide access to the Timeloop
95
+ # functionality to be included in other classes and modules.
96
+ module Helper
97
+ # Provides a way to execute a block of code periodically. The
98
+ # runtime of the block is taken in to account so a delay in one run
99
+ # does not impact the start times of future runs.
100
+ #
101
+ # Arguments
102
+ # period - seconds or name of time period (eg,
103
+ # 'second', 'hour') between iterations
104
+ #
105
+ # Options
106
+ # max - the maximum number of executions of the block. Default:
107
+ # INFINITY
108
+ # maximum - synonym of `max`
109
+ # logger - logger to which debugging info should be sent. Default:
110
+ # no logging
111
+ #
112
+ # Yields Integer index of the iteration to the provide block every `period`.
113
+ def every(period, opts = {}, &blk)
114
+ Timeloop.new(opts.merge(period: period)).loop(&blk)
56
115
  end
57
116
  end
58
- end
59
117
 
60
- class Object
61
- include Kernel
118
+ extend Helper
62
119
  end
@@ -1,3 +1,3 @@
1
- module Timeloop
2
- VERSION = '1.0.3'
1
+ class Timeloop
2
+ VERSION = '2.0.0'
3
3
  end
@@ -0,0 +1,129 @@
1
+ require 'spec_helper'
2
+ require 'logger'
3
+
4
+ describe Timeloop do
5
+ describe ".new" do
6
+ it "accepts all the options" do
7
+ expect(described_class.new(period: 0.1, maximum: 3, logger: a_logger))
8
+ .to be_kind_of described_class
9
+ end
10
+
11
+ it "works with minimal options set" do
12
+ expect(described_class.new(period: 0.1))
13
+ .to be_kind_of described_class
14
+ end
15
+
16
+ it "understands english periods" do
17
+ expect(described_class.new(period: :second))
18
+ .to be_kind_of described_class
19
+ end
20
+
21
+ it "rejects bogus periods" do
22
+ expect{described_class.new(period: :circle)}
23
+ .to raise_error ArgumentError
24
+ end
25
+ end
26
+
27
+ describe ".every(period, max: 3)" do
28
+ it "invokes block on the specified period" do
29
+ start_times = []
30
+ Timeloop.every(0.1, max: 3) { start_times << Time.now }
31
+
32
+ expect(start_times[1] - start_times[0]).to be_within(0.01).of(0.1)
33
+ expect(start_times[2] - start_times[1]).to be_within(0.01).of(0.1)
34
+ end
35
+ end
36
+
37
+ describe ".every(period, maximum: 3)" do
38
+ it "invokes block on the specified period" do
39
+ start_times = []
40
+ Timeloop.every(0.1, maximum: 3) { start_times << Time.now }
41
+
42
+ expect(start_times[1] - start_times[0]).to be_within(0.01).of(0.1)
43
+ expect(start_times[2] - start_times[1]).to be_within(0.01).of(0.1)
44
+ end
45
+ end
46
+
47
+ describe ".every(period)" do
48
+ it "loops forever" do
49
+ expect {
50
+ Timeout.timeout(1) do
51
+ expect{|b| Timeloop.every(0.1, &b)}.to yield_control.exactly(10).times
52
+ end
53
+ }.to raise_error(Timeout::Error)
54
+ end
55
+ end
56
+
57
+ subject { described_class.new(period: 0.1, maximum: 3, logger: a_logger) }
58
+
59
+ describe "logging" do
60
+ it "records sleeps" do
61
+ subject.loop {}
62
+
63
+ expect(a_logger).to have_received(:debug).with(/sleeping/i).exactly(2)
64
+ end
65
+
66
+ it "records starts" do
67
+ subject.loop {}
68
+
69
+ expect(a_logger).to have_received(:debug).with(/starting/i).exactly(3)
70
+ expect(a_logger).to have_received(:debug).with(/starting.*0/i)
71
+ expect(a_logger).to have_received(:debug).with(/starting.*1/i)
72
+ expect(a_logger).to have_received(:debug).with(/starting.*2/i)
73
+ end
74
+ end
75
+
76
+ it "invokes block on the specified period" do
77
+ start_times = []
78
+ subject.loop { start_times << Time.now }
79
+
80
+ expect(start_times[1] - start_times[0]).to be_within(0.01).of(0.1)
81
+ expect(start_times[2] - start_times[1]).to be_within(0.01).of(0.1)
82
+ end
83
+
84
+ it "invokes block on the specified period even if block takes a (relatively) long time" do
85
+ start_times = []
86
+ subject.loop { start_times << Time.now; sleep 0.08 }
87
+
88
+ expect(start_times[1] - start_times[0]).to be_within(0.01).of(0.1)
89
+ expect(start_times[2] - start_times[1]).to be_within(0.01).of(0.1)
90
+ end
91
+
92
+ it "invokes block continuously without any delays if block takes longer than period" do
93
+ start_times = []
94
+ subject.loop { start_times << Time.now; sleep 0.18 }
95
+
96
+ expect(start_times[1] - start_times[0]).to be_within(0.01).of(0.18)
97
+ expect(start_times[2] - start_times[1]).to be_within(0.01).of(0.18)
98
+ end
99
+
100
+ it "provides run count on iteration" do
101
+ expect{|b| subject.loop(&b)}.to yield_successive_args(0, 1, 2)
102
+ end
103
+
104
+ context "w/ max" do
105
+ specify { expect(subject.to_s).to eq "#<Timeloop period=0.1, max=3>" }
106
+
107
+ it "stops looping at maximum number of time" do
108
+ expect{|b| subject.loop(&b)}.to yield_control.exactly(3).times
109
+ end
110
+ end
111
+
112
+ context "w/o max" do
113
+ subject { described_class.new(period: 0.1) }
114
+
115
+ specify { expect(subject.to_s).to eq "#<Timeloop period=0.1>" }
116
+
117
+ it "loops forever" do
118
+ expect {
119
+ Timeout.timeout(1) do
120
+ subject.loop { }
121
+ end
122
+ }.to raise_error(Timeout::Error)
123
+ end
124
+ end
125
+
126
+ # Background
127
+
128
+ let(:a_logger) { spy(instance_double(Logger)) }
129
+ end
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timeloop
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciej Paruszewski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-23 00:00:00.000000000 Z
11
+ date: 2015-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ~>
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.6'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ~>
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.6'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ! '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ! '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ! '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ! '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: coveralls
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ! '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ! '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description:
@@ -73,9 +73,9 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
- - .gitignore
77
- - .rspec
78
- - .travis.yml
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
79
  - Gemfile
80
80
  - LICENSE.txt
81
81
  - README.md
@@ -90,6 +90,7 @@ files:
90
90
  - spec/spec_helper.rb
91
91
  - spec/unit/core_ext/integer_spec.rb
92
92
  - spec/unit/core_ext/numeric_spec.rb
93
+ - spec/unit/timeloop_spec.rb
93
94
  - timeloop.gemspec
94
95
  homepage: https://github.com/pinoss/timeloop
95
96
  licenses:
@@ -101,17 +102,17 @@ require_paths:
101
102
  - lib
102
103
  required_ruby_version: !ruby/object:Gem::Requirement
103
104
  requirements:
104
- - - ! '>='
105
+ - - ">="
105
106
  - !ruby/object:Gem::Version
106
107
  version: '0'
107
108
  required_rubygems_version: !ruby/object:Gem::Requirement
108
109
  requirements:
109
- - - ! '>='
110
+ - - ">="
110
111
  - !ruby/object:Gem::Version
111
112
  version: '0'
112
113
  requirements: []
113
114
  rubyforge_project:
114
- rubygems_version: 2.2.2
115
+ rubygems_version: 2.4.6
115
116
  signing_key:
116
117
  specification_version: 4
117
118
  summary: Timeloop is a simple Ruby gem that provides loop with time interval.
@@ -119,3 +120,4 @@ test_files:
119
120
  - spec/spec_helper.rb
120
121
  - spec/unit/core_ext/integer_spec.rb
121
122
  - spec/unit/core_ext/numeric_spec.rb
123
+ - spec/unit/timeloop_spec.rb