time_up 0.0.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +8 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +20 -0
- data/README.md +184 -0
- data/Rakefile +11 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/lib/time_up.rb +136 -0
- data/lib/time_up/version.rb +3 -0
- data/time_up.gemspec +23 -0
- metadata +58 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2814efda7871308dcafe0196407a3bbea424770069d9b998c8728f974a641413
|
4
|
+
data.tar.gz: 791e65a388e0d62ece0166a747a20cd41f37259d6283e197ff020a05b6eb3e70
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a697b5da2199813b882d8a1b3994670dde64c8ce5adb95b321a8bef3c8892cda9110e778c505e0fd4c327f1956f474be07db9f5fc008926093af9c60c4a8accf
|
7
|
+
data.tar.gz: 1b7089abf119e10a4cff97f880d9a05536a960b112ba59a57f6c6d8642771e566b697db88f6d7c7e2fdca73ff0549803864314de7601d0643e0c018cfbc3731e
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.1
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.15
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
time_up (0.0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
ast (2.4.2)
|
10
|
+
minitest (5.14.4)
|
11
|
+
parallel (1.20.1)
|
12
|
+
parser (3.0.2.0)
|
13
|
+
ast (~> 2.4.1)
|
14
|
+
rainbow (3.0.0)
|
15
|
+
rake (13.0.6)
|
16
|
+
regexp_parser (2.1.1)
|
17
|
+
rexml (3.2.5)
|
18
|
+
rubocop (1.18.3)
|
19
|
+
parallel (~> 1.10)
|
20
|
+
parser (>= 3.0.0.0)
|
21
|
+
rainbow (>= 2.2.2, < 4.0)
|
22
|
+
regexp_parser (>= 1.8, < 3.0)
|
23
|
+
rexml
|
24
|
+
rubocop-ast (>= 1.7.0, < 2.0)
|
25
|
+
ruby-progressbar (~> 1.7)
|
26
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
27
|
+
rubocop-ast (1.8.0)
|
28
|
+
parser (>= 3.0.1.1)
|
29
|
+
rubocop-performance (1.11.4)
|
30
|
+
rubocop (>= 1.7.0, < 2.0)
|
31
|
+
rubocop-ast (>= 0.4.0)
|
32
|
+
ruby-progressbar (1.11.0)
|
33
|
+
standard (1.1.5)
|
34
|
+
rubocop (= 1.18.3)
|
35
|
+
rubocop-performance (= 1.11.4)
|
36
|
+
unicode-display_width (2.0.0)
|
37
|
+
|
38
|
+
PLATFORMS
|
39
|
+
arm64-darwin-20
|
40
|
+
|
41
|
+
DEPENDENCIES
|
42
|
+
minitest
|
43
|
+
rake
|
44
|
+
standard
|
45
|
+
time_up!
|
46
|
+
|
47
|
+
BUNDLED WITH
|
48
|
+
2.2.15
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2021 Test Double, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# time_up
|
2
|
+
|
3
|
+
Ever need to measure the elapsed wall-time for one or more bits of Ruby code,
|
4
|
+
but don't necessarily want to reach for
|
5
|
+
[Benchmark](https://ruby-doc.org/stdlib-3.0.1/libdoc/benchmark/rdoc/Benchmark.html) or roll your own ad hoc measurement code?
|
6
|
+
Try `time_up`!
|
7
|
+
|
8
|
+
This gem is especially useful for long-running processes (like test suites) that
|
9
|
+
have several time-intensive operations that are repeated over time and that you
|
10
|
+
want to measure and aggregate. (For example, to see how much time your
|
11
|
+
test suite spends creating factories, truncating the database, or invoking a
|
12
|
+
critical code path.)
|
13
|
+
|
14
|
+
## Install
|
15
|
+
|
16
|
+
Just run `gem install time_up` or add time_up to your Gemfile:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
gem "time_up"
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Starting a timer is easy! Just call `TimeUp.start`:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
# Pass a block and time_up will only count the time while the block executes:
|
28
|
+
TimeUp.start(:factories) do
|
29
|
+
# … create some factories
|
30
|
+
end
|
31
|
+
|
32
|
+
# Without a block, a started timer will run until you stop it:
|
33
|
+
TimeUp.start(:truncation)
|
34
|
+
# … truncate some stuff
|
35
|
+
TimeUp.stop(:truncation)
|
36
|
+
```
|
37
|
+
|
38
|
+
To get the total time that's elapsed while the timer has been running, call
|
39
|
+
`elapsed`:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
slow_time = TimeUp.elapsed(:slow_code_path)
|
43
|
+
```
|
44
|
+
|
45
|
+
Which will return a `Float` representing the number of seconds that have elapsed
|
46
|
+
while the timer is running.
|
47
|
+
|
48
|
+
Timers aggregate total elapsed time running, just like a digital stopwatch. That
|
49
|
+
means if you start a timer after it's been stopped, the additional time will be
|
50
|
+
_added_ to the previous elapsed time:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
TimeUp.start :eggs
|
54
|
+
sleep 1
|
55
|
+
puts TimeUp.stop :eggs # => ~1.0
|
56
|
+
|
57
|
+
TimeUp.start :eggs
|
58
|
+
sleep 1
|
59
|
+
# To get the elapsed time without necessarily stopping the timer, call `elapsed`
|
60
|
+
puts TimeUp.elapsed :eggs # => ~2.0
|
61
|
+
|
62
|
+
# To reset a timer to 0, call `reset` (if it's running, it'll keep running!)
|
63
|
+
TimeUp.reset :eggs
|
64
|
+
sleep 5
|
65
|
+
puts TimeUp.stop :eggs # => ~5.0
|
66
|
+
```
|
67
|
+
|
68
|
+
`TimeUp.start` also returns an instance of the named timer, which has its own
|
69
|
+
`start`, `stop`, `elaped`, and `reset` methods. If you want to find that
|
70
|
+
instance later, you can also call `TimeUp.timer(:some_name)`. So the above
|
71
|
+
example could be rewritten as:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
egg_timer = TimeUp.start :eggs
|
75
|
+
sleep 1
|
76
|
+
puts egg_timer.stop # => ~1.0
|
77
|
+
|
78
|
+
egg_timer.start
|
79
|
+
sleep 1
|
80
|
+
|
81
|
+
# To get the elapsed time without necessarily stopping the timer, call `elapsed`
|
82
|
+
puts egg_timer.elapsed # => ~2.0
|
83
|
+
|
84
|
+
# To reset a timer to 0, call `reset` (if it's running, it'll keep running!)
|
85
|
+
egg_timer.reset
|
86
|
+
sleep 5
|
87
|
+
puts egg_timer.stop # => ~5.0
|
88
|
+
```
|
89
|
+
|
90
|
+
Finally, if you're juggling a bunch of timers, you can get a summary report of
|
91
|
+
them printed for you, like so:
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
TimeUp.start :roast
|
95
|
+
sleep 0.03
|
96
|
+
TimeUp.start :veggies
|
97
|
+
sleep 0.02
|
98
|
+
TimeUp.start :pasta
|
99
|
+
sleep 0.01
|
100
|
+
TimeUp.stop_all
|
101
|
+
|
102
|
+
TimeUp.start :souffle
|
103
|
+
|
104
|
+
TimeUp.print_summary
|
105
|
+
```
|
106
|
+
|
107
|
+
Which will output something like:
|
108
|
+
|
109
|
+
```
|
110
|
+
TimeUp timers summary
|
111
|
+
========================
|
112
|
+
:roast 0.07267s
|
113
|
+
:veggies 0.03760s
|
114
|
+
:pasta 0.01257s
|
115
|
+
:souffle* 0.00003s
|
116
|
+
|
117
|
+
* Denotes that the timer is still active
|
118
|
+
```
|
119
|
+
|
120
|
+
## API
|
121
|
+
|
122
|
+
This gem defines a bunch of public methods but they're all pretty short and
|
123
|
+
straightforward, so I'd encourage you to [read the code](/lib/time_up.rb).
|
124
|
+
|
125
|
+
### `TimeUp` module
|
126
|
+
|
127
|
+
`TimeUp.start(name, [&blk])` - Starts (or restarts) and returns a named `Timer`
|
128
|
+
|
129
|
+
`TimeUp.timer(name)` - Returns any a `Timer` instance named `name` or `nil`
|
130
|
+
|
131
|
+
`TimeUp.stop(name)` - Stops the named timer or raises if it's not defined
|
132
|
+
|
133
|
+
`TimeUp.elapsed(name)` - Returns a `Float` of the total elapsed seconds that the
|
134
|
+
named timer has been running (and raises if no timer is defined with the given
|
135
|
+
`name`)
|
136
|
+
|
137
|
+
`TimeUp.reset(name)` - Resets the named timer's elapsed time to 0, effectively
|
138
|
+
restarting it if it's currently running. Raises if the timer isn't defined.
|
139
|
+
|
140
|
+
`TimeUp.total_elapsed` - Returns a `Float` of the sum of `elapsed` for all the
|
141
|
+
timers you've created
|
142
|
+
|
143
|
+
`TimeUp.all_elapsed` - Returns a hash of timer name keys mapped to their
|
144
|
+
`elapsed` values. Handy for grabbing a snapshot of the state of things at a
|
145
|
+
particular point in time without stopping all your timers
|
146
|
+
|
147
|
+
`TimeUp.active_timers` - Returns an array of all timers that are currently
|
148
|
+
running. Useful for detecting cases where you might be counting the same time in
|
149
|
+
multiple places simultaneously
|
150
|
+
|
151
|
+
`TimeUp.print_summary([IO])` - Pretty-prints a multi-line summary of all your
|
152
|
+
timers to STDOUT (or the provided IO)
|
153
|
+
|
154
|
+
`TimeUp.stop_all` - Stops all timers
|
155
|
+
|
156
|
+
`TimeUp.reset_all` - Resets all timers
|
157
|
+
|
158
|
+
`TimeUp.delete_all` - Stops and resets all timers and deletes any internal
|
159
|
+
reference to them
|
160
|
+
|
161
|
+
### `TimeUp::Timer` class
|
162
|
+
|
163
|
+
`start` - Starts the timer
|
164
|
+
|
165
|
+
`stop` - Stops the timer
|
166
|
+
|
167
|
+
`elapsed` - A `Float` of the total elapsed seconds the timer has been running
|
168
|
+
|
169
|
+
`active?` - Returns `true` if the timer is running
|
170
|
+
|
171
|
+
`reset(force: false)` - Resets the timer to 0 elapsed seconds. If `force` is
|
172
|
+
true, will also stop the timer if it's running
|
173
|
+
|
174
|
+
## Code of Conduct
|
175
|
+
|
176
|
+
This project follows Test Double's [code of
|
177
|
+
conduct](https://testdouble.com/code-of-conduct) for all community interactions,
|
178
|
+
including (but not limited to) one-on-one communications, public posts/comments,
|
179
|
+
code reviews, pull requests, and GitHub issues. If violations occur, Test Double
|
180
|
+
will take any action they deem appropriate for the infraction, up to and
|
181
|
+
including blocking a user from the organization's repositories.
|
182
|
+
|
183
|
+
|
184
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "bundler/setup"
|
3
|
+
require "time_up"
|
4
|
+
|
5
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
6
|
+
# with your gem easier. You can also use a different console, if you like.
|
7
|
+
|
8
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
9
|
+
# require "pry"
|
10
|
+
# Pry.start
|
11
|
+
|
12
|
+
require "irb"
|
13
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/time_up.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require_relative "time_up/version"
|
2
|
+
|
3
|
+
module TimeUp
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
@timers = {}
|
7
|
+
def self.start(name, &blk)
|
8
|
+
raise Error.new("Timer name must be a String or Symbol") unless name.is_a?(Symbol) || name.is_a?(String)
|
9
|
+
timer = @timers[name] ||= Timer.new(name)
|
10
|
+
timer.start
|
11
|
+
if blk
|
12
|
+
blk.call
|
13
|
+
timer.stop
|
14
|
+
end
|
15
|
+
timer
|
16
|
+
end
|
17
|
+
|
18
|
+
# Delegate methods
|
19
|
+
def self.timer(name)
|
20
|
+
@timers[name]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.stop(name)
|
24
|
+
__ensure_timer(name)
|
25
|
+
@timers[name].stop
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.elapsed(name)
|
29
|
+
__ensure_timer(name)
|
30
|
+
@timers[name].elapsed
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.reset(name)
|
34
|
+
__ensure_timer(name)
|
35
|
+
@timers[name].reset
|
36
|
+
end
|
37
|
+
|
38
|
+
# Interrogative methods
|
39
|
+
def self.total_elapsed
|
40
|
+
@timers.values.sum(&:elapsed)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.all_elapsed
|
44
|
+
@timers.values.map { |timer|
|
45
|
+
[timer.name, timer.elapsed]
|
46
|
+
}.to_h
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.active_timers
|
50
|
+
@timers.values.select(&:active?)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.print_summary(io = $stdout)
|
54
|
+
longest_name_length = @timers.values.map { |t| t.name.inspect.size }.max
|
55
|
+
summaries = @timers.values.map { |timer|
|
56
|
+
name = "#{timer.name.inspect}#{"*" if timer.active?}".ljust(longest_name_length + 1)
|
57
|
+
"#{name}\t#{"%.5f" % timer.elapsed}s"
|
58
|
+
}
|
59
|
+
io.puts <<~SUMMARY
|
60
|
+
|
61
|
+
TimeUp timers summary
|
62
|
+
========================
|
63
|
+
#{summaries.join("\n")}
|
64
|
+
|
65
|
+
#{"* Denotes that the timer is still active\n" if @timers.values.any?(&:active?)}
|
66
|
+
SUMMARY
|
67
|
+
end
|
68
|
+
|
69
|
+
# Iterative methods
|
70
|
+
def self.stop_all
|
71
|
+
@timers.values.each(&:stop)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.reset_all
|
75
|
+
@timers.values.each(&:reset)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.delete_all
|
79
|
+
@timers.values.each { |t| t.reset(force: true) }
|
80
|
+
@timers = {}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Internal methods
|
84
|
+
def self.__ensure_timer(name)
|
85
|
+
raise Error.new("No timer named #{name.inspect}") unless @timers[name]
|
86
|
+
end
|
87
|
+
|
88
|
+
class Timer
|
89
|
+
attr_reader :name
|
90
|
+
|
91
|
+
def initialize(name)
|
92
|
+
@name = name
|
93
|
+
@start_time = nil
|
94
|
+
@elapsed = 0.0
|
95
|
+
end
|
96
|
+
|
97
|
+
def start
|
98
|
+
@start_time ||= now
|
99
|
+
end
|
100
|
+
|
101
|
+
def stop
|
102
|
+
if @start_time
|
103
|
+
@elapsed += now - @start_time
|
104
|
+
@start_time = nil
|
105
|
+
end
|
106
|
+
@elapsed
|
107
|
+
end
|
108
|
+
|
109
|
+
def elapsed
|
110
|
+
if active?
|
111
|
+
@elapsed + (now - @start_time)
|
112
|
+
else
|
113
|
+
@elapsed
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def active?
|
118
|
+
!!@start_time
|
119
|
+
end
|
120
|
+
|
121
|
+
def reset(force: false)
|
122
|
+
if force
|
123
|
+
@start_time = nil
|
124
|
+
elsif !@start_time.nil?
|
125
|
+
@start_time = now
|
126
|
+
end
|
127
|
+
@elapsed = 0.0
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def now
|
133
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
data/time_up.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative "lib/time_up/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "time_up"
|
5
|
+
spec.version = TimeUp::VERSION
|
6
|
+
spec.authors = ["Justin Searls"]
|
7
|
+
spec.email = ["searls@gmail.com"]
|
8
|
+
|
9
|
+
spec.summary = "A little library for managing multiple named timers"
|
10
|
+
spec.homepage = "https://github.com/testdouble/time_up"
|
11
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
|
12
|
+
|
13
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
14
|
+
spec.metadata["source_code_uri"] = "https://github.com/testdouble/time_up"
|
15
|
+
spec.metadata["changelog_uri"] = "https://github.com/testdouble/time_up/blob/main/CHANGELOG.md"
|
16
|
+
|
17
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: time_up
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Searls
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-07-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
- searls@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".github/workflows/main.yml"
|
21
|
+
- ".gitignore"
|
22
|
+
- CHANGELOG.md
|
23
|
+
- Gemfile
|
24
|
+
- Gemfile.lock
|
25
|
+
- LICENSE.txt
|
26
|
+
- README.md
|
27
|
+
- Rakefile
|
28
|
+
- bin/console
|
29
|
+
- bin/setup
|
30
|
+
- lib/time_up.rb
|
31
|
+
- lib/time_up/version.rb
|
32
|
+
- time_up.gemspec
|
33
|
+
homepage: https://github.com/testdouble/time_up
|
34
|
+
licenses: []
|
35
|
+
metadata:
|
36
|
+
homepage_uri: https://github.com/testdouble/time_up
|
37
|
+
source_code_uri: https://github.com/testdouble/time_up
|
38
|
+
changelog_uri: https://github.com/testdouble/time_up/blob/main/CHANGELOG.md
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
require_paths:
|
42
|
+
- lib
|
43
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.4.0
|
48
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubygems_version: 3.2.15
|
55
|
+
signing_key:
|
56
|
+
specification_version: 4
|
57
|
+
summary: A little library for managing multiple named timers
|
58
|
+
test_files: []
|