minitest-bender 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -1
- data/Gemfile +1 -1
- data/README.md +37 -6
- data/lib/minitest-bender/colorizer.rb +61 -0
- data/lib/minitest-bender/configuration.rb +174 -0
- data/lib/minitest-bender/printers/plain.rb +29 -0
- data/lib/minitest-bender/printers/with_progress_bar.rb +115 -0
- data/lib/minitest-bender/recorders/grouped_icons.rb +36 -0
- data/lib/minitest-bender/recorders/icons.rb +26 -0
- data/lib/minitest-bender/recorders/none.rb +17 -0
- data/lib/minitest-bender/recorders/progress.rb +25 -0
- data/lib/minitest-bender/recorders/progress_groups.rb +48 -0
- data/lib/minitest-bender/recorders/progress_groups_and_issues.rb +55 -0
- data/lib/minitest-bender/recorders/progress_issues.rb +32 -0
- data/lib/minitest-bender/recorders/progress_verbose.rb +34 -0
- data/lib/minitest-bender/result_context.rb +39 -0
- data/lib/minitest-bender/result_factory.rb +5 -0
- data/lib/minitest-bender/results/base.rb +94 -38
- data/lib/minitest-bender/results/expectation.rb +10 -8
- data/lib/minitest-bender/results/test.rb +21 -8
- data/lib/minitest-bender/sections/activity.rb +95 -0
- data/lib/minitest-bender/sections/issues.rb +22 -0
- data/lib/minitest-bender/sections/sorted_overview.rb +115 -0
- data/lib/minitest-bender/sections/suite_status.rb +72 -0
- data/lib/minitest-bender/sections/time_ranking.rb +49 -0
- data/lib/minitest-bender/states/base.rb +76 -19
- data/lib/minitest-bender/states/failing.rb +11 -8
- data/lib/minitest-bender/states/passing.rb +19 -8
- data/lib/minitest-bender/states/raising.rb +42 -20
- data/lib/minitest-bender/states/skipped.rb +12 -9
- data/lib/minitest-bender/utils.rb +26 -0
- data/lib/minitest-bender/version.rb +1 -1
- data/lib/minitest/bender.rb +166 -77
- data/lib/minitest/bender_plugin.rb +49 -3
- data/lib/minitest_bender.rb +23 -5
- data/minitest-bender.gemspec +6 -6
- metadata +39 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 49e6e3621dfce3e4fea0607c005dba7865a7619e62d7391b715ac883cf2a43a5
|
4
|
+
data.tar.gz: 7566d1731c3ad37d5124ba48f87300fb1875b63108681c06f34966fa36e06100
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 511ca6879fa694f7195a86bdea865c213796a8e5bf2d3d05c438b6a1bb028fd9a57c7d5c103bee7572c1515603b279d71cc8018f28fe28067c46c516f2629549
|
7
|
+
data.tar.gz: b2c7a9517be2b69455620ba1c4a871d921d53dc09c37a9d27a11009035a7112ff528e1fe4bb2be147774114320235839eebd53034391e6f31ee4813739fe8dd9
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,19 @@ All notable changes to this project will be documented in this file. This projec
|
|
6
6
|
|
7
7
|
* Your contribution here!
|
8
8
|
|
9
|
+
## [1.0.0][] (2020-07-19)
|
10
|
+
|
11
|
+
Thanks to [cboos](https://github.com/cboos) for contributing some of these enhancements!
|
12
|
+
|
13
|
+
* Two-step activation for better interoperability with other reporters
|
14
|
+
* Seven different recorders to choose from, five of which display a progress bar
|
15
|
+
* New "overview" section with sorted test results, enabled by default
|
16
|
+
* Skips, failures and errors are consistently sorted
|
17
|
+
* Irrelevant portions of backtraces are hidden by default
|
18
|
+
* Many configurable options such as visible sections and custom colors
|
19
|
+
* Graceful degradation of colors in console environments with poor support for them
|
20
|
+
* Smarter rerun command generation
|
21
|
+
|
9
22
|
## [0.0.3][] (2019-06-09)
|
10
23
|
|
11
24
|
* Fixed running time format of slow tests
|
@@ -20,6 +33,7 @@ All notable changes to this project will be documented in this file. This projec
|
|
20
33
|
* Initial release
|
21
34
|
|
22
35
|
[Semver]: http://semver.org
|
23
|
-
[Unreleased]: https://github.com/eugeniobruno/minitest-bender/compare/
|
36
|
+
[Unreleased]: https://github.com/eugeniobruno/minitest-bender/compare/v1.0.0...HEAD
|
37
|
+
[1.0.0]: https://github.com/eugeniobruno/minitest-bender/compare/v0.0.3...v1.0.0
|
24
38
|
[0.0.3]: https://github.com/eugeniobruno/minitest-bender/compare/v0.0.2...v0.0.3
|
25
39
|
[0.0.2]: https://github.com/eugeniobruno/minitest-bender/compare/v0.0.1...v0.0.2
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/minitest-bender.svg)](https://badge.fury.io/rb/minitest-bender)
|
4
4
|
[![Code Climate](https://codeclimate.com/github/eugeniobruno/minitest-bender.svg)](https://codeclimate.com/github/eugeniobruno/minitest-bender)
|
5
5
|
|
6
|
-
|
6
|
+
A comprehensive Minitest reporter.
|
7
7
|
|
8
8
|
## Installation
|
9
9
|
|
@@ -23,24 +23,55 @@ Or install it yourself as:
|
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
Require this plugin right after Minitest:
|
26
|
+
Require this plugin right after Minitest, and then enable it explicitly:
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
require 'minitest/autorun'
|
30
30
|
require 'minitest/bender'
|
31
|
+
Minitest::Bender.enable!
|
31
32
|
```
|
32
33
|
|
33
34
|
That's it! The next time you run your tests, a new report format will be used instead of the default one.
|
34
35
|
|
36
|
+
Instead of calling the `enable!` method, you can also specify the `--bender` test option, e.g.
|
37
|
+
|
38
|
+
$ rake test TESTOPTS="--bender"
|
39
|
+
|
40
|
+
In both of these cases, Bender is activated with its default configuration. You can refer to [bender_plugin.rb](https://github.com/eugeniobruno/minitest-bender/blob/master/lib/minitest/bender_plugin.rb) or [configuration.rb](https://github.com/eugeniobruno/minitest-bender/blob/master/lib/minitest-bender/configuration.rb) to explore the multitude of options available to customize the output.
|
41
|
+
|
42
|
+
As an example, let's say your test suite takes several minutes to run, so you want to see detailed output in realtime. This behaviour is provided by a particular recorder called "progress_verbose", which is not the default one. In order to select this recorder for your app/gem, you can activate Bender like this instead:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
Minitest::Bender.enable!({ recorder: :progress_verbose })
|
46
|
+
```
|
47
|
+
|
48
|
+
You can also set configuration options via environment variables:
|
49
|
+
|
50
|
+
$ BENDER_RECORDER=progress_verbose rake test
|
51
|
+
|
52
|
+
Finally, command-line options also work:
|
53
|
+
|
54
|
+
$ rake test TESTOPTS="--bender-recorder=progress_verbose"
|
55
|
+
|
56
|
+
Bear in mind that setting a configuration option does not automatically enable the reporter, so remember to either call the `enable!` method, or to include the `--bender` argument.
|
57
|
+
|
58
|
+
If you use [minitest-reporters](https://github.com/kern/minitest-reporters) and have it installed and activated, you can select the `BenderReporter` as (one of the) reporters:
|
59
|
+
|
60
|
+
$ MINITEST_REPORTER=BenderReporter,HtmlReporter rake test
|
61
|
+
|
62
|
+
|
35
63
|
## Features
|
36
64
|
|
37
|
-
|
65
|
+
Originally based on [minitest-colorin](https://github.com/gabynaiman/minitest-colorin/), the minitest-bender reporter offers you, out of the box, colored output including:
|
38
66
|
|
39
|
-
*
|
40
|
-
*
|
41
|
-
* Details of
|
67
|
+
* A progress bar, mostly useful for long-running suites, as the default of many different recorders
|
68
|
+
* Status, running time, name and message for each test/expectation, grouped by class/context and sorted by name
|
69
|
+
* Details of skips, failures and errors, with diffs, backtraces and commands to rerun each single test
|
70
|
+
* Details of the slowest tests, if they may be relevant
|
42
71
|
* The same basic statistics of the default reporter
|
43
72
|
|
73
|
+
If the NO_COLOR environment variable is set, Bender will output non-colored text honoring [this standard](https://no-color.org/), thanks to the [paint gem](https://github.com/janlelis/paint/) dependency.
|
74
|
+
|
44
75
|
|
45
76
|
## Development
|
46
77
|
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ENV['ANSICON'] ||= 'Y' if ENV['ConEmuANSI'] != 'ON'
|
4
|
+
require 'paint'
|
5
|
+
|
6
|
+
module MinitestBender
|
7
|
+
class Colorizer
|
8
|
+
COLORS = { # Xterm No. - Xterm Name
|
9
|
+
pass: '87ff87', # 120 - LightGreen
|
10
|
+
skip: '5fd7ff', # 81 - SteelBlue1
|
11
|
+
fail: 'ff5f5f', # 203 - IndianRed1
|
12
|
+
error: 'ffd75f', # 221 - LightGoldenrod2
|
13
|
+
tests: '5fafaf', # 73 - CadetBlue
|
14
|
+
assertions: 'd75fd7', # 170 - Orchid
|
15
|
+
time: '878787', # 102 - Grey53
|
16
|
+
number: '5fafaf', # 73 - CadetBlue
|
17
|
+
backtrace: 'af8787' # 138 - RosyBrown
|
18
|
+
}
|
19
|
+
|
20
|
+
# In compatibility modes, colors that are mapped to black are avoided.
|
21
|
+
SAFE_COLORS = {
|
22
|
+
pass: '00ff5f', # 47 - SpringGreen2
|
23
|
+
tests: 'blue',
|
24
|
+
time: 'gray',
|
25
|
+
number: 'gray',
|
26
|
+
backtrace: 'gray'
|
27
|
+
}
|
28
|
+
COLORS.merge!(SAFE_COLORS) if Paint.mode < 256
|
29
|
+
|
30
|
+
COLORS.freeze
|
31
|
+
|
32
|
+
class << self
|
33
|
+
def custom_colors=(custom_colors)
|
34
|
+
@custom_colors = custom_colors
|
35
|
+
end
|
36
|
+
|
37
|
+
def colorize(string, color, *args)
|
38
|
+
if color == :normal
|
39
|
+
Paint[string, *args]
|
40
|
+
else
|
41
|
+
color_value = colors.fetch(color)
|
42
|
+
Paint[string, color_value, *args]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def color_keys
|
47
|
+
COLORS.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def colors
|
53
|
+
@colors ||= COLORS.merge(custom_colors)
|
54
|
+
end
|
55
|
+
|
56
|
+
def custom_colors
|
57
|
+
@custom_colors || {}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
class Configuration
|
3
|
+
DEFAULT_CONFIG = {
|
4
|
+
mode: :oblivious,
|
5
|
+
recorder: :progress,
|
6
|
+
sections: [:overview, :time_ranking, :issues, :activity, :suite_status],
|
7
|
+
sections_blacklist: [],
|
8
|
+
overview_sort_key: :name,
|
9
|
+
time_ranking_size: 5,
|
10
|
+
backtrace_view: :user,
|
11
|
+
rerun_command_stem: defined?(Rake) ? 'rake' : 'ruby',
|
12
|
+
custom_colors: {}
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@client_config = {}
|
17
|
+
@options_config = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_client_config(config)
|
21
|
+
validate_config(config)
|
22
|
+
client_config.merge!(config)
|
23
|
+
end
|
24
|
+
|
25
|
+
def mode=(mode)
|
26
|
+
options_config[:mode] = mode
|
27
|
+
end
|
28
|
+
|
29
|
+
def recorder=(recorder)
|
30
|
+
options_config[:recorder] = recorder
|
31
|
+
end
|
32
|
+
|
33
|
+
def sections=(sections)
|
34
|
+
options_config[:sections] = sections
|
35
|
+
end
|
36
|
+
|
37
|
+
def sections_blacklist=(sections_blacklist)
|
38
|
+
options_config[:sections_blacklist] = sections_blacklist
|
39
|
+
end
|
40
|
+
|
41
|
+
def overview_sort_key=(overview_sort_key)
|
42
|
+
options_config[:overview_sort_key] = overview_sort_key
|
43
|
+
end
|
44
|
+
|
45
|
+
def time_ranking_size=(time_ranking_size)
|
46
|
+
options_config[:time_ranking_size] = time_ranking_size
|
47
|
+
end
|
48
|
+
|
49
|
+
def backtrace_view=(backtrace_view)
|
50
|
+
options_config[:backtrace_view] = backtrace_view
|
51
|
+
end
|
52
|
+
|
53
|
+
def rerun_command_stem=(rerun_command_stem)
|
54
|
+
options_config[:rerun_command_stem] = rerun_command_stem
|
55
|
+
end
|
56
|
+
|
57
|
+
def set_custom_color(color_key, color)
|
58
|
+
options_config[:custom_colors] ||= {}
|
59
|
+
options_config[:custom_colors][color_key] = color
|
60
|
+
end
|
61
|
+
|
62
|
+
def cooperative?
|
63
|
+
final_config.fetch(:mode) == :cooperative
|
64
|
+
end
|
65
|
+
|
66
|
+
def recorder
|
67
|
+
final_config.fetch(:recorder)
|
68
|
+
end
|
69
|
+
|
70
|
+
def sections
|
71
|
+
sections_whitelist - sections_blacklist
|
72
|
+
end
|
73
|
+
|
74
|
+
def overview_sort_key
|
75
|
+
final_config.fetch(:overview_sort_key)
|
76
|
+
end
|
77
|
+
|
78
|
+
def time_ranking_size
|
79
|
+
final_config.fetch(:time_ranking_size)
|
80
|
+
end
|
81
|
+
|
82
|
+
def backtrace_view
|
83
|
+
final_config.fetch(:backtrace_view)
|
84
|
+
end
|
85
|
+
|
86
|
+
def rerun_command_stem
|
87
|
+
final_config.fetch(:rerun_command_stem)
|
88
|
+
end
|
89
|
+
|
90
|
+
def custom_colors
|
91
|
+
final_config.fetch(:custom_colors)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
attr_reader :client_config, :options_config
|
97
|
+
|
98
|
+
def validate_config(config)
|
99
|
+
invalid_options = config.keys.map(&:to_sym) - valid_options
|
100
|
+
unless invalid_options.empty?
|
101
|
+
first_invalid_option = invalid_options.first
|
102
|
+
message = "invalid option: '#{first_invalid_option}'"
|
103
|
+
raise ArgumentError, message
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def valid_options
|
108
|
+
default_config.keys
|
109
|
+
end
|
110
|
+
|
111
|
+
def parsed_list(list)
|
112
|
+
strings = list.is_a?(Array) ? list.map(&:to_s) : list.split(',')
|
113
|
+
strings.map { |s| s.strip.to_sym }
|
114
|
+
end
|
115
|
+
|
116
|
+
def default_config
|
117
|
+
DEFAULT_CONFIG
|
118
|
+
end
|
119
|
+
|
120
|
+
def env_config
|
121
|
+
{
|
122
|
+
mode: ENV['BENDER_MODE'],
|
123
|
+
recorder: ENV['BENDER_RECORDER'],
|
124
|
+
sections: ENV['BENDER_SECTIONS'],
|
125
|
+
sections_blacklist: ENV['BENDER_SECTIONS_BLACKLIST'],
|
126
|
+
overview_sort_key: ENV['BENDER_OVERVIEW_SORT_KEY'],
|
127
|
+
time_ranking_size: ENV['BENDER_TIME_RANKING_SIZE'],
|
128
|
+
backtrace_view: ENV['BENDER_BACKTRACE_VIEW'],
|
129
|
+
rerun_command_stem: ENV['BENDER_RERUN_COMMAND_STEM'],
|
130
|
+
custom_colors: custom_colors_env_config
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def custom_colors_env_config
|
135
|
+
Colorizer.color_keys.each_with_object({}) do |color_key, h|
|
136
|
+
h[color_key] = ENV["BENDER_#{color_key.upcase}_COLOR"]
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def merged_config
|
141
|
+
[default_config, client_config, env_config, options_config].reduce do |acum, config|
|
142
|
+
proper_config = Utils.with_symbolized_keys(Utils.without_nil_values(config))
|
143
|
+
acum.merge(proper_config) do |key, old_val, new_val|
|
144
|
+
if key == :custom_colors
|
145
|
+
old_val.merge(Utils.with_symbolized_keys(Utils.without_nil_values(new_val)))
|
146
|
+
else
|
147
|
+
new_val
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def final_config
|
154
|
+
merged_config.tap do |config|
|
155
|
+
config[:mode] = config[:mode].to_sym
|
156
|
+
config[:recorder] = config[:recorder].to_sym
|
157
|
+
config[:sections] = parsed_list(config[:sections])
|
158
|
+
config[:sections_blacklist] = parsed_list(config[:sections_blacklist])
|
159
|
+
config[:overview_sort_key] = config[:overview_sort_key].to_sym
|
160
|
+
config[:time_ranking_size] = config[:time_ranking_size].to_i
|
161
|
+
config[:rerun_command_stem] = config[:rerun_command_stem].to_s
|
162
|
+
config[:backtrace_view] = config[:backtrace_view].to_sym
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def sections_whitelist
|
167
|
+
final_config.fetch(:sections)
|
168
|
+
end
|
169
|
+
|
170
|
+
def sections_blacklist
|
171
|
+
final_config.fetch(:sections_blacklist)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MinitestBender
|
2
|
+
module Printers
|
3
|
+
class Plain
|
4
|
+
def initialize(io)
|
5
|
+
@io = io
|
6
|
+
end
|
7
|
+
|
8
|
+
def print(string)
|
9
|
+
io.print(string)
|
10
|
+
end
|
11
|
+
|
12
|
+
def print_line(line = '')
|
13
|
+
io.puts(line)
|
14
|
+
end
|
15
|
+
|
16
|
+
def print_lines(lines)
|
17
|
+
lines.each { |line| print_line(line) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def advance
|
21
|
+
# do nothing
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :io
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'tty-progressbar'
|
5
|
+
|
6
|
+
module MinitestBender
|
7
|
+
module Printers
|
8
|
+
class WithProgressBar
|
9
|
+
COMPLETE_ICON = ' '
|
10
|
+
HEAD_ICON = 'ᗧ'
|
11
|
+
INCOMPLETE_ICON = '•'
|
12
|
+
ELAPSED_ICON = '⏱'
|
13
|
+
ETA_ICON = '⌛'
|
14
|
+
|
15
|
+
def initialize(io, total)
|
16
|
+
@io = io
|
17
|
+
@total = total
|
18
|
+
@bar = new_bar
|
19
|
+
end
|
20
|
+
|
21
|
+
def print(string)
|
22
|
+
io.print(string)
|
23
|
+
end
|
24
|
+
|
25
|
+
def print_line(line = '')
|
26
|
+
if io.tty?
|
27
|
+
bar.log(line)
|
28
|
+
else
|
29
|
+
io.puts(line)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def print_lines(lines)
|
34
|
+
lines.each { |line| print_line(line) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def advance
|
38
|
+
bar.update({ head: head })
|
39
|
+
bar.advance(1, { counters_sym => counters })
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :io, :bar, :total
|
45
|
+
|
46
|
+
def new_bar
|
47
|
+
TTY::ProgressBar.new(bar_format_string, {
|
48
|
+
total: total,
|
49
|
+
width: [total, TTY::ProgressBar.max_columns].max,
|
50
|
+
complete: complete_icon,
|
51
|
+
head: head_icon,
|
52
|
+
incomplete: incomplete_icon
|
53
|
+
})
|
54
|
+
end
|
55
|
+
|
56
|
+
def bar_format_string
|
57
|
+
":bar #{Colorizer.colorize(':current/:total', :tests)} :#{counters_sym} #{Colorizer.colorize(elapsed_icon + ' :elapsed', :time)} #{Colorizer.colorize(eta_icon + ':eta', :time)} #{Colorizer.colorize(':percent', :normal, :bold)}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def counters
|
61
|
+
states.map do |state|
|
62
|
+
state.colored_icon_with_count(counters_padding_right)
|
63
|
+
end.join(' ')
|
64
|
+
end
|
65
|
+
|
66
|
+
def counters_sym
|
67
|
+
('c' * counters_sym_length).to_sym
|
68
|
+
end
|
69
|
+
|
70
|
+
def counters_sym_length
|
71
|
+
( 4 * (total.to_s.size + 1) ) + 6
|
72
|
+
end
|
73
|
+
|
74
|
+
def head
|
75
|
+
Colorizer.colorize(head_icon, head_color)
|
76
|
+
end
|
77
|
+
|
78
|
+
def complete_icon
|
79
|
+
COMPLETE_ICON
|
80
|
+
end
|
81
|
+
|
82
|
+
def head_icon
|
83
|
+
HEAD_ICON
|
84
|
+
end
|
85
|
+
|
86
|
+
def incomplete_icon
|
87
|
+
INCOMPLETE_ICON
|
88
|
+
end
|
89
|
+
|
90
|
+
def elapsed_icon
|
91
|
+
ELAPSED_ICON
|
92
|
+
end
|
93
|
+
|
94
|
+
def eta_icon
|
95
|
+
ETA_ICON
|
96
|
+
end
|
97
|
+
|
98
|
+
def head_color
|
99
|
+
reverse_states.find { |s| !s.results.empty? }.color
|
100
|
+
end
|
101
|
+
|
102
|
+
def states
|
103
|
+
@states ||= MinitestBender.states.values
|
104
|
+
end
|
105
|
+
|
106
|
+
def reverse_states
|
107
|
+
@reverse_states ||= states.reverse
|
108
|
+
end
|
109
|
+
|
110
|
+
def counters_padding_right
|
111
|
+
@counters_padding_right ||= total.to_s.size + 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|