time_up 0.0.1 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.standard.yml +1 -0
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +1 -1
- data/README.md +95 -27
- data/lib/time_up.rb +177 -44
- data/lib/time_up/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6241f3e0e1e66bc75c67a7e7cfc76424ae4e711c5a535b87da3bdf4fa78c153
|
4
|
+
data.tar.gz: 33c77eb409cb4d4d4142207ff93a938a1124b87768047f29be0d51057c4eb5fb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df9e90669ceef219dcea0bf8b21548252b97abe40b950b922f6c9ee17987494d145fdd130b5c4735a6e57daffaf5ba2d915057044f424ba68656d9b55b5f7820
|
7
|
+
data.tar.gz: ad014a9ec0471bfbc3f315b673988d7d5b5115ad29d0cc664ad63eaa81547086bff539de50e311078261c99a9616a0e683c75f9e552bf0a254d26e14d0ef702d
|
data/.standard.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby_version: 2.4
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,26 @@
|
|
1
|
+
# 0.0.5
|
2
|
+
|
3
|
+
- Add `median` and `percentile` timer statistics, and added them to
|
4
|
+
`print_detailed_summary`
|
5
|
+
|
6
|
+
# 0.0.4
|
7
|
+
|
8
|
+
- Add `TimeUp.print_detailed_summary`
|
9
|
+
|
10
|
+
# 0.0.3
|
11
|
+
|
12
|
+
- Change the return value of TimeUp.start when passed a block to be the
|
13
|
+
evaluated value of the block (for easier insertion into existing code without
|
14
|
+
adding a bunch of new assignment and returns)
|
15
|
+
- Allow timer instances' `start` method to be called with a block
|
16
|
+
- Add `timings`, `count`, `min`, `max`, and `mean` methods for basic stats
|
17
|
+
tracking
|
18
|
+
- Add `TimeUp.all_stats` to roll up all these
|
19
|
+
|
20
|
+
# 0.0.2
|
21
|
+
|
22
|
+
- Switch from a module method to Thread.current variable
|
23
|
+
|
1
24
|
# 0.0.1
|
2
25
|
|
3
26
|
- Make the gem
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,10 +6,13 @@ but don't necessarily want to reach for
|
|
6
6
|
Try `time_up`!
|
7
7
|
|
8
8
|
This gem is especially useful for long-running processes (like test suites) that
|
9
|
-
have several time-intensive operations that are repeated over
|
10
|
-
want to measure
|
11
|
-
test suite spends creating factories, truncating the database, or
|
12
|
-
critical code path.)
|
9
|
+
have several time-intensive operations that are repeated over the life of the
|
10
|
+
process and that you want to measure in aggregate. (For example, to see how
|
11
|
+
much time your test suite spends creating factories, truncating the database, or
|
12
|
+
invoking a critical code path.)
|
13
|
+
|
14
|
+
Here's a [blog post about time_up](https://blog.testdouble.com/posts/2021-07-19-benchmarking-your-ruby-with-time_up/) and
|
15
|
+
a [great example of when it can be useful](https://gist.github.com/searls/feee0b0eac7c329b390fed90c4714afb).
|
13
16
|
|
14
17
|
## Install
|
15
18
|
|
@@ -65,10 +68,10 @@ sleep 5
|
|
65
68
|
puts TimeUp.stop :eggs # => ~5.0
|
66
69
|
```
|
67
70
|
|
68
|
-
`TimeUp.start`
|
69
|
-
`start`, `stop`, `elaped`, and `reset` methods. If you want to
|
70
|
-
instance later, you can also call `TimeUp.timer(:some_name)`. So the
|
71
|
-
example could be rewritten as:
|
71
|
+
When passes without a block, `TimeUp.start` returns an instance of the timer,
|
72
|
+
which has its own `start`, `stop`, `elaped`, and `reset` methods. If you want to
|
73
|
+
find that instance later, you can also call `TimeUp.timer(:some_name)`. So the
|
74
|
+
above example could be rewritten as:
|
72
75
|
|
73
76
|
```ruby
|
74
77
|
egg_timer = TimeUp.start :eggs
|
@@ -107,7 +110,7 @@ TimeUp.print_summary
|
|
107
110
|
Which will output something like:
|
108
111
|
|
109
112
|
```
|
110
|
-
TimeUp
|
113
|
+
TimeUp summary
|
111
114
|
========================
|
112
115
|
:roast 0.07267s
|
113
116
|
:veggies 0.03760s
|
@@ -117,39 +120,87 @@ TimeUp timers summary
|
|
117
120
|
* Denotes that the timer is still active
|
118
121
|
```
|
119
122
|
|
123
|
+
And if you're calling the timers multiple times and want to see some basic
|
124
|
+
statistics in the print-out, you can call `TimeUp.print_detailed_summary`, which
|
125
|
+
will produce this:
|
126
|
+
|
127
|
+
```
|
128
|
+
=============================================================================
|
129
|
+
Name | Elapsed | Count | Min | Max | Mean | Median | 95th %
|
130
|
+
-----------------------------------------------------------------------------
|
131
|
+
:roast | 0.08454 | 3 | 0.00128 | 0.07280 | 0.02818 | 0.01046 | 0.06657
|
132
|
+
:veggies | 0.03779 | 1 | 0.03779 | 0.03779 | 0.03779 | 0.03779 | 0.03779
|
133
|
+
:pasta | 0.01260 | 11 | 0.00000 | 0.01258 | 0.00115 | 0.00000 | 0.00630
|
134
|
+
:souffle* | 0.00024 | 1 | 0.00024 | 0.00025 | 0.00025 | 0.00025 | 0.00026
|
135
|
+
|
136
|
+
* Denotes that the timer is still active
|
137
|
+
```
|
138
|
+
|
120
139
|
## API
|
121
140
|
|
122
141
|
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
|
142
|
+
straightforward, so when in doubt, I'd encourage you to [read the
|
143
|
+
code](/lib/time_up.rb).
|
124
144
|
|
125
145
|
### `TimeUp` module
|
126
146
|
|
127
|
-
`TimeUp.
|
147
|
+
`TimeUp.timer(name)` - Returns the `Timer` instance named `name` (creating it,
|
148
|
+
if it doesn't exist)
|
128
149
|
|
129
|
-
`TimeUp.
|
150
|
+
`TimeUp.start(name, [&blk])` - Starts (or restarts) a named
|
151
|
+
[Timer](#timeuptimer-class). If passed a block, will return whatever the block
|
152
|
+
evaluates to. If called without a block, it will return the timer object
|
130
153
|
|
131
|
-
`TimeUp.stop(name)` - Stops the named timer
|
154
|
+
`TimeUp.stop(name)` - Stops the named timer
|
155
|
+
|
156
|
+
`TimeUp.reset(name)` - Resets the named timer's elapsed time to 0, effectively
|
157
|
+
restarting it if it's currently running
|
132
158
|
|
133
159
|
`TimeUp.elapsed(name)` - Returns a `Float` of the total elapsed seconds that the
|
134
|
-
named timer has been running
|
135
|
-
`name`)
|
160
|
+
named timer has been running
|
136
161
|
|
137
|
-
`TimeUp.
|
138
|
-
|
162
|
+
`TimeUp.timings(name)` - Returns an array of each recorded start-to-stop
|
163
|
+
duration (including the current one, if the timer is running) of the named timer
|
164
|
+
|
165
|
+
`TimeUp.count(name)` - The number of times the timer has been started (including
|
166
|
+
the current timing, if the timer is running)
|
167
|
+
|
168
|
+
`TimeUp.min(name)` - The shortest recording by the timer
|
169
|
+
|
170
|
+
`TimeUp.max(name)` - The longest recording by the timer
|
139
171
|
|
140
|
-
`TimeUp.
|
141
|
-
timers you've created
|
172
|
+
`TimeUp.mean(name)` - The arithmetic mean of all recordings by the timer
|
142
173
|
|
143
|
-
`TimeUp.
|
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
|
174
|
+
`TimeUp.median(name)` - The median of all recordings by the timer
|
146
175
|
|
147
|
-
`TimeUp.
|
148
|
-
|
149
|
-
|
176
|
+
`TimeUp.percentile(name, percent)` - The timing for the given
|
177
|
+
[percentile](https://en.wikipedia.org/wiki/Percentile) of all recordings by the
|
178
|
+
timer
|
150
179
|
|
151
|
-
`TimeUp.
|
152
|
-
timers
|
180
|
+
`TimeUp.total_elapsed` - Returns a `Float` of the sum of `elapsed` across all
|
181
|
+
the timers you've created (note that because you can easily run multiple logical
|
182
|
+
timers simultaneously, this figure may exceed the total time spent by the
|
183
|
+
computer)
|
184
|
+
|
185
|
+
`TimeUp.all_elapsed` - Returns a Hash of timer name keys mapped to their
|
186
|
+
`elapsed` values. Handy for grabbing a reference to a snapshot of the state of
|
187
|
+
things without requiring you to stop your timers
|
188
|
+
|
189
|
+
`TimeUp.all_stats` - Returns a Hash of timer name keys mapped to another
|
190
|
+
hash of their basic statistics (`elapsed`, `count`, `min`, `max`,
|
191
|
+
and `mean`)
|
192
|
+
|
193
|
+
`TimeUp.active_timers` - Returns an Array of all timers that are currently
|
194
|
+
running. Useful for detecting cases where you might be keeping time in multiple
|
195
|
+
places simultaneously
|
196
|
+
|
197
|
+
`TimeUp.print_summary([io])` - Pretty-prints a multi-line summary of all your
|
198
|
+
timers' total elapsed times to standard output (or the provided
|
199
|
+
[IO](https://ruby-doc.org/core-3.0.1/IO.html))
|
200
|
+
|
201
|
+
`TimeUp.print_detailed_summary([io])` - Pretty-prints a multi-line summary of
|
202
|
+
all your timers' elapsed times and basic statistics to standard output (or the
|
203
|
+
provided [IO](https://ruby-doc.org/core-3.0.1/IO.html))
|
153
204
|
|
154
205
|
`TimeUp.stop_all` - Stops all timers
|
155
206
|
|
@@ -166,6 +217,23 @@ reference to them
|
|
166
217
|
|
167
218
|
`elapsed` - A `Float` of the total elapsed seconds the timer has been running
|
168
219
|
|
220
|
+
`timings` - Returns an Array of each recorded start-to-stop duration of the
|
221
|
+
timer (including the current one, if the timer is running)
|
222
|
+
|
223
|
+
`count` - The number of times the timer has been started and stopped
|
224
|
+
|
225
|
+
`min` - The shortest recording of the timer
|
226
|
+
|
227
|
+
`max` - The longest recording of the timer
|
228
|
+
|
229
|
+
`mean` - The arithmetic mean of all recorded durations of the timer
|
230
|
+
|
231
|
+
`median(name)` - The median of all recordings by the timer
|
232
|
+
|
233
|
+
`percentile(name, percent)` - The timing for the given
|
234
|
+
[percentile](https://en.wikipedia.org/wiki/Percentile) of all recordings by the
|
235
|
+
timer
|
236
|
+
|
169
237
|
`active?` - Returns `true` if the timer is running
|
170
238
|
|
171
239
|
`reset(force: false)` - Resets the timer to 0 elapsed seconds. If `force` is
|
data/lib/time_up.rb
CHANGED
@@ -3,132 +3,265 @@ require_relative "time_up/version"
|
|
3
3
|
module TimeUp
|
4
4
|
class Error < StandardError; end
|
5
5
|
|
6
|
-
|
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
|
6
|
+
Thread.current[:time_up_timers] = {}
|
17
7
|
|
18
|
-
# Delegate methods
|
19
8
|
def self.timer(name)
|
20
|
-
|
9
|
+
__timers[name] ||= Timer.new(name)
|
21
10
|
end
|
22
11
|
|
23
|
-
|
24
|
-
|
25
|
-
|
12
|
+
# Delegate methods
|
13
|
+
def self.start(name, &blk)
|
14
|
+
timer(name).start(&blk)
|
26
15
|
end
|
27
16
|
|
28
|
-
|
29
|
-
|
30
|
-
|
17
|
+
[
|
18
|
+
:stop,
|
19
|
+
:reset,
|
20
|
+
:elapsed,
|
21
|
+
:timings,
|
22
|
+
:count,
|
23
|
+
:min,
|
24
|
+
:max,
|
25
|
+
:mean,
|
26
|
+
:median
|
27
|
+
].each do |method_name|
|
28
|
+
define_singleton_method method_name do |name|
|
29
|
+
__ensure_timer(name)
|
30
|
+
__timers[name].send(method_name)
|
31
|
+
end
|
31
32
|
end
|
32
33
|
|
33
|
-
def self.
|
34
|
+
def self.percentile(name, percentage)
|
34
35
|
__ensure_timer(name)
|
35
|
-
|
36
|
+
__timers[name].percentile(percentage)
|
36
37
|
end
|
37
38
|
|
38
39
|
# Interrogative methods
|
39
40
|
def self.total_elapsed
|
40
|
-
|
41
|
+
__timers.values.sum(&:elapsed)
|
41
42
|
end
|
42
43
|
|
43
44
|
def self.all_elapsed
|
44
|
-
|
45
|
+
__timers.values.map { |timer|
|
45
46
|
[timer.name, timer.elapsed]
|
46
47
|
}.to_h
|
47
48
|
end
|
48
49
|
|
50
|
+
def self.all_stats
|
51
|
+
__timers.values.map { |timer|
|
52
|
+
[timer.name, {
|
53
|
+
elapsed: timer.elapsed,
|
54
|
+
count: timer.count,
|
55
|
+
min: timer.min,
|
56
|
+
max: timer.max,
|
57
|
+
mean: timer.mean,
|
58
|
+
median: timer.median,
|
59
|
+
"95th": timer.percentile(95)
|
60
|
+
}]
|
61
|
+
}.to_h
|
62
|
+
end
|
63
|
+
|
49
64
|
def self.active_timers
|
50
|
-
|
65
|
+
__timers.values.select(&:active?)
|
51
66
|
end
|
52
67
|
|
53
68
|
def self.print_summary(io = $stdout)
|
54
|
-
longest_name_length =
|
55
|
-
summaries =
|
69
|
+
longest_name_length = __timers.values.map { |t| t.name.inspect.size }.max
|
70
|
+
summaries = __timers.values.map { |timer|
|
56
71
|
name = "#{timer.name.inspect}#{"*" if timer.active?}".ljust(longest_name_length + 1)
|
57
72
|
"#{name}\t#{"%.5f" % timer.elapsed}s"
|
58
73
|
}
|
59
74
|
io.puts <<~SUMMARY
|
60
75
|
|
61
|
-
TimeUp
|
76
|
+
TimeUp summary
|
62
77
|
========================
|
63
78
|
#{summaries.join("\n")}
|
64
79
|
|
65
|
-
#{"* Denotes that the timer is still active\n" if
|
80
|
+
#{"* Denotes that the timer is still active\n" if __timers.values.any?(&:active?)}
|
81
|
+
SUMMARY
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.print_detailed_summary(io = $stdout)
|
85
|
+
cols = {
|
86
|
+
names: ["Name"],
|
87
|
+
elapsed: ["Elapsed"],
|
88
|
+
count: ["Count"],
|
89
|
+
min: ["Min"],
|
90
|
+
max: ["Max"],
|
91
|
+
mean: ["Mean"],
|
92
|
+
median: ["Median"],
|
93
|
+
"95th": ["95th %"]
|
94
|
+
}
|
95
|
+
__timers.values.each { |timer|
|
96
|
+
cols[:names] << "#{timer.name.inspect}#{"*" if timer.active?}"
|
97
|
+
cols[:elapsed] << "%.5f" % timer.elapsed
|
98
|
+
cols[:count] << timer.count.to_s
|
99
|
+
cols[:min] << "%.5f" % timer.min
|
100
|
+
cols[:max] << "%.5f" % timer.max
|
101
|
+
cols[:mean] << "%.5f" % timer.mean
|
102
|
+
cols[:median] << "%.5f" % timer.median
|
103
|
+
cols[:"95th"] << "%.5f" % timer.percentile(95)
|
104
|
+
}
|
105
|
+
|
106
|
+
widths = cols.map { |name, vals|
|
107
|
+
[name, vals.map(&:length).max]
|
108
|
+
}.to_h
|
109
|
+
|
110
|
+
rows = cols[:names].size.times.map { |i|
|
111
|
+
if i == 0
|
112
|
+
cols.keys.map { |name|
|
113
|
+
cols[name][i].center(widths[name])
|
114
|
+
}
|
115
|
+
else
|
116
|
+
cols.keys.map { |name|
|
117
|
+
cols[name][i].ljust(widths[name])
|
118
|
+
}
|
119
|
+
end
|
120
|
+
}
|
121
|
+
|
122
|
+
full_width = widths.values.sum + (rows[0].size - 1) * 3
|
123
|
+
io.puts <<~SUMMARY
|
124
|
+
|
125
|
+
#{"=" * full_width}
|
126
|
+
#{rows[0].join(" | ")}
|
127
|
+
#{"-" * full_width}
|
128
|
+
#{rows[1..-1].map { |row| row.join(" | ") }.join("\n")}
|
129
|
+
|
130
|
+
#{"* Denotes that the timer is still active\n" if __timers.values.any?(&:active?)}
|
66
131
|
SUMMARY
|
67
132
|
end
|
68
133
|
|
69
134
|
# Iterative methods
|
70
135
|
def self.stop_all
|
71
|
-
|
136
|
+
__timers.values.each(&:stop)
|
72
137
|
end
|
73
138
|
|
74
139
|
def self.reset_all
|
75
|
-
|
140
|
+
__timers.values.each(&:reset)
|
76
141
|
end
|
77
142
|
|
78
143
|
def self.delete_all
|
79
|
-
|
80
|
-
|
144
|
+
__timers.values.each { |t| t.reset(force: true) }
|
145
|
+
Thread.current[:time_up_timers] = {}
|
81
146
|
end
|
82
147
|
|
83
148
|
# Internal methods
|
149
|
+
def self.__timers
|
150
|
+
Thread.current[:time_up_timers]
|
151
|
+
end
|
152
|
+
|
84
153
|
def self.__ensure_timer(name)
|
85
|
-
raise Error.new("No timer named #{name.inspect}") unless
|
154
|
+
raise Error.new("No timer named #{name.inspect}") unless __timers[name]
|
86
155
|
end
|
87
156
|
|
88
157
|
class Timer
|
89
158
|
attr_reader :name
|
90
159
|
|
91
160
|
def initialize(name)
|
161
|
+
validate!(name)
|
92
162
|
@name = name
|
93
163
|
@start_time = nil
|
94
|
-
@
|
164
|
+
@total_elapsed = 0.0
|
165
|
+
@past_timings = []
|
95
166
|
end
|
96
167
|
|
97
|
-
def start
|
168
|
+
def start(&blk)
|
98
169
|
@start_time ||= now
|
170
|
+
if blk
|
171
|
+
blk.call.tap do
|
172
|
+
stop
|
173
|
+
end
|
174
|
+
else
|
175
|
+
self
|
176
|
+
end
|
99
177
|
end
|
100
178
|
|
101
179
|
def stop
|
102
180
|
if @start_time
|
103
|
-
|
181
|
+
duration = now - @start_time
|
182
|
+
@past_timings.push(duration)
|
183
|
+
@total_elapsed += duration
|
184
|
+
|
104
185
|
@start_time = nil
|
105
186
|
end
|
106
|
-
@
|
187
|
+
@total_elapsed
|
107
188
|
end
|
108
189
|
|
109
190
|
def elapsed
|
110
191
|
if active?
|
111
|
-
@
|
192
|
+
@total_elapsed + (now - @start_time)
|
112
193
|
else
|
113
|
-
@
|
194
|
+
@total_elapsed
|
114
195
|
end
|
115
196
|
end
|
116
197
|
|
117
|
-
def active?
|
118
|
-
!!@start_time
|
119
|
-
end
|
120
|
-
|
121
198
|
def reset(force: false)
|
122
199
|
if force
|
123
200
|
@start_time = nil
|
124
201
|
elsif !@start_time.nil?
|
125
202
|
@start_time = now
|
126
203
|
end
|
127
|
-
@
|
204
|
+
@total_elapsed = 0.0
|
205
|
+
@past_timings = []
|
206
|
+
end
|
207
|
+
|
208
|
+
def count
|
209
|
+
timings.size
|
210
|
+
end
|
211
|
+
|
212
|
+
def min
|
213
|
+
timings.min
|
214
|
+
end
|
215
|
+
|
216
|
+
def max
|
217
|
+
timings.max
|
218
|
+
end
|
219
|
+
|
220
|
+
def mean
|
221
|
+
times = timings
|
222
|
+
return if times.empty?
|
223
|
+
times.sum / times.size
|
224
|
+
end
|
225
|
+
|
226
|
+
def median
|
227
|
+
times = timings.sort
|
228
|
+
return if times.empty?
|
229
|
+
(times[(times.size - 1) / 2] + times[times.size / 2]) / 2.0
|
230
|
+
end
|
231
|
+
|
232
|
+
def percentile(percent)
|
233
|
+
times = timings.sort
|
234
|
+
return if times.empty?
|
235
|
+
return 0 if percent <= 0
|
236
|
+
return max if percent >= 100
|
237
|
+
return times.first if times.size == 1
|
238
|
+
position = (percent / 100.0) * (times.size - 1)
|
239
|
+
|
240
|
+
partial_ratio = position - position.floor
|
241
|
+
whole, partial = times[position.floor, 2]
|
242
|
+
whole + (partial - whole) * partial_ratio
|
243
|
+
end
|
244
|
+
|
245
|
+
def timings
|
246
|
+
if active?
|
247
|
+
@past_timings + [now - @start_time]
|
248
|
+
else
|
249
|
+
@past_timings
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def active?
|
254
|
+
!!@start_time
|
128
255
|
end
|
129
256
|
|
130
257
|
private
|
131
258
|
|
259
|
+
def validate!(name)
|
260
|
+
unless name.is_a?(Symbol) || name.is_a?(String)
|
261
|
+
raise Error.new("Timer name must be a String or Symbol")
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
132
265
|
def now
|
133
266
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
134
267
|
end
|
data/lib/time_up/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_up
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Justin Searls
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-20 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -19,6 +19,7 @@ extra_rdoc_files: []
|
|
19
19
|
files:
|
20
20
|
- ".github/workflows/main.yml"
|
21
21
|
- ".gitignore"
|
22
|
+
- ".standard.yml"
|
22
23
|
- CHANGELOG.md
|
23
24
|
- Gemfile
|
24
25
|
- Gemfile.lock
|