timeloop 1.0.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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