minitest-bender 0.0.3 → 1.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 +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
|
[](https://badge.fury.io/rb/minitest-bender)
|
4
4
|
[](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
|