rspec-legacy_formatters 1.0.0.rc1
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
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +1 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/.yardopts +7 -0
- data/Changelog.md +3 -0
- data/Gemfile +30 -0
- data/License.txt +22 -0
- data/README.md +41 -0
- data/Rakefile +27 -0
- data/cucumber.yml +6 -0
- data/features/custom_formatter.feature +28 -0
- data/features/regression_tests_for_built_in_formatters.feature +86 -0
- data/features/regression_tests_for_custom_formatters.feature +94 -0
- data/features/step_definitions/additional_cli_steps.rb +4 -0
- data/features/support/env.rb +13 -0
- data/lib/rspec/legacy_formatters.rb +59 -0
- data/lib/rspec/legacy_formatters/adaptor.rb +230 -0
- data/lib/rspec/legacy_formatters/base_formatter.rb +248 -0
- data/lib/rspec/legacy_formatters/base_text_formatter.rb +330 -0
- data/lib/rspec/legacy_formatters/documentation_formatter.rb +69 -0
- data/lib/rspec/legacy_formatters/helpers.rb +108 -0
- data/lib/rspec/legacy_formatters/html_formatter.rb +157 -0
- data/lib/rspec/legacy_formatters/html_printer.rb +412 -0
- data/lib/rspec/legacy_formatters/json_formatter.rb +71 -0
- data/lib/rspec/legacy_formatters/progress_formatter.rb +31 -0
- data/lib/rspec/legacy_formatters/snippet_extractor.rb +92 -0
- data/lib/rspec/legacy_formatters/version.rb +9 -0
- data/maintenance-branch +1 -0
- data/rspec-legacy_formatters.gemspec +43 -0
- data/script/functions.sh +144 -0
- data/script/run_build +13 -0
- data/spec/rspec/legacy_formatters_spec.rb +184 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/formatter_support.rb +83 -0
- data/spec/support/legacy_formatter_using_sub_classing_example.rb +87 -0
- data/spec/support/old_style_formatter_example.rb +69 -0
- metadata +243 -0
- metadata.gz.sig +2 -0
@@ -0,0 +1,330 @@
|
|
1
|
+
require 'rspec/legacy_formatters/base_formatter'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module RSpec
|
5
|
+
module Core
|
6
|
+
module Formatters
|
7
|
+
remove_const :BaseTextFormatter
|
8
|
+
|
9
|
+
# Base for all of RSpec's built-in formatters. See RSpec::Core::Formatters::BaseFormatter
|
10
|
+
# to learn more about all of the methods called by the reporter.
|
11
|
+
#
|
12
|
+
# @see RSpec::Core::Formatters::BaseFormatter
|
13
|
+
# @see RSpec::Core::Reporter
|
14
|
+
class BaseTextFormatter < BaseFormatter
|
15
|
+
def message(message)
|
16
|
+
output.puts message
|
17
|
+
end
|
18
|
+
|
19
|
+
def dump_failures
|
20
|
+
return if failed_examples.empty?
|
21
|
+
output.puts
|
22
|
+
output.puts "Failures:"
|
23
|
+
failed_examples.each_with_index do |example, index|
|
24
|
+
output.puts
|
25
|
+
pending_fixed?(example) ? dump_pending_fixed(example, index) : dump_failure(example, index)
|
26
|
+
dump_backtrace(example)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @api public
|
31
|
+
#
|
32
|
+
# Colorizes the output red for failure, yellow for
|
33
|
+
# pending, and green otherwise.
|
34
|
+
#
|
35
|
+
# @param [String] string
|
36
|
+
def colorise_summary(summary)
|
37
|
+
if failure_count > 0
|
38
|
+
color(summary, RSpec.configuration.failure_color)
|
39
|
+
elsif pending_count > 0
|
40
|
+
color(summary, RSpec.configuration.pending_color)
|
41
|
+
else
|
42
|
+
color(summary, RSpec.configuration.success_color)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def dump_summary(duration, example_count, failure_count, pending_count)
|
47
|
+
super(duration, example_count, failure_count, pending_count)
|
48
|
+
dump_profile unless mute_profile_output?(failure_count)
|
49
|
+
output.puts "\nFinished in #{format_duration(duration)}\n"
|
50
|
+
output.puts colorise_summary(summary_line(example_count, failure_count, pending_count))
|
51
|
+
dump_commands_to_rerun_failed_examples
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api public
|
55
|
+
#
|
56
|
+
# Outputs commands which can be used to re-run failed examples.
|
57
|
+
#
|
58
|
+
def dump_commands_to_rerun_failed_examples
|
59
|
+
return if failed_examples.empty?
|
60
|
+
output.puts
|
61
|
+
output.puts("Failed examples:")
|
62
|
+
output.puts
|
63
|
+
|
64
|
+
failed_examples.each do |example|
|
65
|
+
output.puts(failure_color("rspec #{RSpec::Core::Metadata::relative_path(example.location)}") + " " + detail_color("# #{example.full_description}"))
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# @api public
|
70
|
+
#
|
71
|
+
# Outputs the slowest examples and example groups in a report when using `--profile COUNT` (default 10).
|
72
|
+
#
|
73
|
+
def dump_profile
|
74
|
+
dump_profile_slowest_examples
|
75
|
+
dump_profile_slowest_example_groups
|
76
|
+
end
|
77
|
+
|
78
|
+
def dump_profile_slowest_examples
|
79
|
+
number_of_examples = RSpec.configuration.profile_examples
|
80
|
+
sorted_examples = examples.sort_by {|example|
|
81
|
+
example.execution_result.run_time }.reverse.first(number_of_examples)
|
82
|
+
|
83
|
+
total, slows = [examples, sorted_examples].map {|exs|
|
84
|
+
exs.inject(0.0) {|i, e| i + e.execution_result.run_time }}
|
85
|
+
|
86
|
+
time_taken = slows / total
|
87
|
+
percentage = '%.1f' % ((time_taken.nan? ? 0.0 : time_taken) * 100)
|
88
|
+
|
89
|
+
output.puts "\nTop #{sorted_examples.size} slowest examples (#{format_seconds(slows)} seconds, #{percentage}% of total time):\n"
|
90
|
+
|
91
|
+
sorted_examples.each do |example|
|
92
|
+
output.puts " #{example.full_description}"
|
93
|
+
output.puts detail_color(" #{failure_color(format_seconds(example.execution_result.run_time))} #{failure_color("seconds")} #{format_caller(example.location)}")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def dump_profile_slowest_example_groups
|
98
|
+
number_of_examples = RSpec.configuration.profile_examples
|
99
|
+
example_groups = {}
|
100
|
+
|
101
|
+
examples.each do |example|
|
102
|
+
location = example.example_group.parent_groups.last.metadata[:location]
|
103
|
+
|
104
|
+
example_groups[location] ||= Hash.new(0)
|
105
|
+
example_groups[location][:total_time] += example.execution_result.run_time
|
106
|
+
example_groups[location][:count] += 1
|
107
|
+
example_groups[location][:description] = example.example_group.top_level_description unless example_groups[location].has_key?(:description)
|
108
|
+
end
|
109
|
+
|
110
|
+
# stop if we've only one example group
|
111
|
+
return if example_groups.keys.length <= 1
|
112
|
+
|
113
|
+
example_groups.each do |loc, hash|
|
114
|
+
hash[:average] = hash[:total_time].to_f / hash[:count]
|
115
|
+
end
|
116
|
+
|
117
|
+
sorted_groups = example_groups.sort_by {|_, hash| -hash[:average]}.first(number_of_examples)
|
118
|
+
|
119
|
+
output.puts "\nTop #{sorted_groups.size} slowest example groups:"
|
120
|
+
sorted_groups.each do |loc, hash|
|
121
|
+
average = "#{failure_color(format_seconds(hash[:average]))} #{failure_color("seconds")} average"
|
122
|
+
total = "#{format_seconds(hash[:total_time])} seconds"
|
123
|
+
count = pluralize(hash[:count], "example")
|
124
|
+
output.puts " #{hash[:description]}"
|
125
|
+
output.puts detail_color(" #{average} (#{total} / #{count}) #{loc}")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# @api public
|
130
|
+
#
|
131
|
+
# Outputs summary with number of examples, failures and pending.
|
132
|
+
#
|
133
|
+
def summary_line(example_count, failure_count, pending_count)
|
134
|
+
summary = pluralize(example_count, "example")
|
135
|
+
summary << ", " << pluralize(failure_count, "failure")
|
136
|
+
summary << ", #{pending_count} pending" if pending_count > 0
|
137
|
+
summary
|
138
|
+
end
|
139
|
+
|
140
|
+
def dump_pending
|
141
|
+
unless pending_examples.empty?
|
142
|
+
output.puts
|
143
|
+
output.puts "Pending:"
|
144
|
+
pending_examples.each do |pending_example|
|
145
|
+
output.puts pending_color(" #{pending_example.full_description}")
|
146
|
+
output.puts detail_color(" # #{pending_example.execution_result.pending_message}")
|
147
|
+
output.puts detail_color(" # #{format_caller(pending_example.location)}")
|
148
|
+
if pending_example.execution_result.exception \
|
149
|
+
&& RSpec.configuration.show_failures_in_pending_blocks?
|
150
|
+
dump_failure_info(pending_example)
|
151
|
+
dump_backtrace(pending_example)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def seed(number)
|
158
|
+
output.puts
|
159
|
+
output.puts "Randomized with seed #{number}"
|
160
|
+
output.puts
|
161
|
+
end
|
162
|
+
|
163
|
+
def close
|
164
|
+
output.close if IO === output && output != $stdout
|
165
|
+
end
|
166
|
+
|
167
|
+
VT100_COLORS = {
|
168
|
+
:black => 30,
|
169
|
+
:red => 31,
|
170
|
+
:green => 32,
|
171
|
+
:yellow => 33,
|
172
|
+
:blue => 34,
|
173
|
+
:magenta => 35,
|
174
|
+
:cyan => 36,
|
175
|
+
:white => 37
|
176
|
+
}
|
177
|
+
|
178
|
+
VT100_COLOR_CODES = VT100_COLORS.values.to_set
|
179
|
+
|
180
|
+
def color_code_for(code_or_symbol)
|
181
|
+
if VT100_COLOR_CODES.include?(code_or_symbol)
|
182
|
+
code_or_symbol
|
183
|
+
else
|
184
|
+
VT100_COLORS.fetch(code_or_symbol) do
|
185
|
+
color_code_for(:white)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def colorize(text, code_or_symbol)
|
191
|
+
"\e[#{color_code_for(code_or_symbol)}m#{text}\e[0m"
|
192
|
+
end
|
193
|
+
|
194
|
+
protected
|
195
|
+
|
196
|
+
def bold(text)
|
197
|
+
color_enabled? ? "\e[1m#{text}\e[0m" : text
|
198
|
+
end
|
199
|
+
|
200
|
+
def color(text, color_code)
|
201
|
+
color_enabled? ? colorize(text, color_code) : text
|
202
|
+
end
|
203
|
+
|
204
|
+
def failure_color(text)
|
205
|
+
color(text, RSpec.configuration.failure_color)
|
206
|
+
end
|
207
|
+
|
208
|
+
def success_color(text)
|
209
|
+
color(text, RSpec.configuration.success_color)
|
210
|
+
end
|
211
|
+
|
212
|
+
def pending_color(text)
|
213
|
+
color(text, RSpec.configuration.pending_color)
|
214
|
+
end
|
215
|
+
|
216
|
+
def fixed_color(text)
|
217
|
+
color(text, RSpec.configuration.fixed_color)
|
218
|
+
end
|
219
|
+
|
220
|
+
def detail_color(text)
|
221
|
+
color(text, RSpec.configuration.detail_color)
|
222
|
+
end
|
223
|
+
|
224
|
+
def default_color(text)
|
225
|
+
color(text, RSpec.configuration.default_color)
|
226
|
+
end
|
227
|
+
|
228
|
+
def red(text)
|
229
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#red", :replacement => "#failure_color")
|
230
|
+
color(text, :red)
|
231
|
+
end
|
232
|
+
|
233
|
+
def green(text)
|
234
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#green", :replacement => "#success_color")
|
235
|
+
color(text, :green)
|
236
|
+
end
|
237
|
+
|
238
|
+
def yellow(text)
|
239
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#yellow", :replacement => "#pending_color")
|
240
|
+
color(text, :yellow)
|
241
|
+
end
|
242
|
+
|
243
|
+
def blue(text)
|
244
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#blue", :replacement => "#fixed_color")
|
245
|
+
color(text, :blue)
|
246
|
+
end
|
247
|
+
|
248
|
+
def magenta(text)
|
249
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#magenta")
|
250
|
+
color(text, :magenta)
|
251
|
+
end
|
252
|
+
|
253
|
+
def cyan(text)
|
254
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#cyan", :replacement => "#detail_color")
|
255
|
+
color(text, :cyan)
|
256
|
+
end
|
257
|
+
|
258
|
+
def white(text)
|
259
|
+
RSpec.deprecate("RSpec::Core::Formatters::BaseTextFormatter#white", :replacement => "#default_color")
|
260
|
+
color(text, :white)
|
261
|
+
end
|
262
|
+
|
263
|
+
def short_padding
|
264
|
+
' '
|
265
|
+
end
|
266
|
+
|
267
|
+
def long_padding
|
268
|
+
' '
|
269
|
+
end
|
270
|
+
|
271
|
+
private
|
272
|
+
|
273
|
+
def format_caller(caller_info)
|
274
|
+
backtrace_line(caller_info.to_s.split(':in `block').first)
|
275
|
+
end
|
276
|
+
|
277
|
+
def dump_backtrace(example)
|
278
|
+
format_backtrace(example.execution_result.exception.backtrace, example).each do |backtrace_info|
|
279
|
+
output.puts detail_color("#{long_padding}# #{backtrace_info}")
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def dump_pending_fixed(example, index)
|
284
|
+
output.puts "#{short_padding}#{index.next}) #{example.full_description} FIXED"
|
285
|
+
output.puts fixed_color("#{long_padding}Expected pending '#{example.execution_result.pending_message}' to fail. No Error was raised.")
|
286
|
+
end
|
287
|
+
|
288
|
+
def pending_fixed?(example)
|
289
|
+
example.execution_result.pending_fixed?
|
290
|
+
end
|
291
|
+
|
292
|
+
def dump_failure(example, index)
|
293
|
+
output.puts "#{short_padding}#{index.next}) #{example.full_description}"
|
294
|
+
dump_failure_info(example)
|
295
|
+
end
|
296
|
+
|
297
|
+
def dump_failure_info(example)
|
298
|
+
exception = example.execution_result.exception
|
299
|
+
exception_class_name = exception_class_name_for(exception)
|
300
|
+
output.puts "#{long_padding}#{failure_color("Failure/Error:")} #{failure_color(read_failed_line(exception, example).strip)}"
|
301
|
+
output.puts "#{long_padding}#{failure_color(exception_class_name)}:" unless exception_class_name =~ /RSpec/
|
302
|
+
exception.message.to_s.split("\n").each { |line| output.puts "#{long_padding} #{failure_color(line)}" } if exception.message
|
303
|
+
|
304
|
+
if shared_group = find_shared_group(example)
|
305
|
+
dump_shared_failure_info(shared_group)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def exception_class_name_for(exception)
|
310
|
+
name = exception.class.name.to_s
|
311
|
+
name ="(anonymous error class)" if name == ''
|
312
|
+
name
|
313
|
+
end
|
314
|
+
|
315
|
+
def dump_shared_failure_info(group)
|
316
|
+
output.puts "#{long_padding}Shared Example Group: \"#{group.metadata[:shared_group_name]}\" called from " +
|
317
|
+
"#{backtrace_line(group.metadata[:location])}"
|
318
|
+
end
|
319
|
+
|
320
|
+
def find_shared_group(example)
|
321
|
+
group_and_parent_groups(example).find {|group| group.metadata[:shared_group_name]}
|
322
|
+
end
|
323
|
+
|
324
|
+
def group_and_parent_groups(example)
|
325
|
+
example.example_group.parent_groups + [example.example_group]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rspec/legacy_formatters/base_text_formatter'
|
2
|
+
|
3
|
+
module RSpec
|
4
|
+
module Core
|
5
|
+
module Formatters
|
6
|
+
remove_const :DocumentationFormatter
|
7
|
+
|
8
|
+
class DocumentationFormatter < BaseTextFormatter
|
9
|
+
def initialize(output)
|
10
|
+
super(output)
|
11
|
+
@group_level = 0
|
12
|
+
end
|
13
|
+
|
14
|
+
def example_group_started(example_group)
|
15
|
+
super(example_group)
|
16
|
+
|
17
|
+
output.puts if @group_level == 0
|
18
|
+
output.puts "#{current_indentation}#{example_group.description.strip}"
|
19
|
+
|
20
|
+
@group_level += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def example_group_finished(example_group)
|
24
|
+
@group_level -= 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def example_passed(example)
|
28
|
+
super(example)
|
29
|
+
output.puts passed_output(example)
|
30
|
+
end
|
31
|
+
|
32
|
+
def example_pending(example)
|
33
|
+
super(example)
|
34
|
+
output.puts pending_output(example, example.execution_result.pending_message)
|
35
|
+
end
|
36
|
+
|
37
|
+
def example_failed(example)
|
38
|
+
super(example)
|
39
|
+
output.puts failure_output(example, example.execution_result.exception)
|
40
|
+
end
|
41
|
+
|
42
|
+
def failure_output(example, exception)
|
43
|
+
failure_color("#{current_indentation}#{example.description.strip} (FAILED - #{next_failure_index})")
|
44
|
+
end
|
45
|
+
|
46
|
+
def next_failure_index
|
47
|
+
@next_failure_index ||= 0
|
48
|
+
@next_failure_index += 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def passed_output(example)
|
52
|
+
success_color("#{current_indentation}#{example.description.strip}")
|
53
|
+
end
|
54
|
+
|
55
|
+
def pending_output(example, message)
|
56
|
+
pending_color("#{current_indentation}#{example.description.strip} (PENDING: #{message})")
|
57
|
+
end
|
58
|
+
|
59
|
+
def current_indentation
|
60
|
+
' ' * @group_level
|
61
|
+
end
|
62
|
+
|
63
|
+
def example_group_chain
|
64
|
+
example_group.parent_groups.reverse
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Core
|
3
|
+
module LegacyBacktraceFormatter
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def format_backtrace(backtrace, options = {})
|
7
|
+
return "" unless backtrace
|
8
|
+
return backtrace if options[:full_backtrace] == true
|
9
|
+
|
10
|
+
cleansed = backtrace.map { |line| backtrace_line(line) }.compact
|
11
|
+
cleansed.empty? ? backtrace : cleansed
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def backtrace_line(line)
|
17
|
+
return nil if RSpec.configuration.backtrace_formatter.exclude?(line)
|
18
|
+
RSpec::Core::Metadata::relative_path(line)
|
19
|
+
rescue SecurityError
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Formatters
|
25
|
+
module Helpers
|
26
|
+
include LegacyBacktraceFormatter
|
27
|
+
|
28
|
+
remove_const :SUB_SECOND_PRECISION
|
29
|
+
remove_const :DEFAULT_PRECISION
|
30
|
+
SUB_SECOND_PRECISION = 5
|
31
|
+
DEFAULT_PRECISION = 2
|
32
|
+
|
33
|
+
# @api private
|
34
|
+
#
|
35
|
+
# Formats seconds into a human-readable string.
|
36
|
+
#
|
37
|
+
# @param [Float, Fixnum] duration in seconds
|
38
|
+
# @return [String] human-readable time
|
39
|
+
#
|
40
|
+
# @example
|
41
|
+
# format_duration(1) #=> "1 minute 1 second"
|
42
|
+
# format_duration(135.14) #=> "2 minutes 15.14 seconds"
|
43
|
+
def format_duration(duration)
|
44
|
+
precision = case
|
45
|
+
when duration < 1; SUB_SECOND_PRECISION
|
46
|
+
when duration < 120; DEFAULT_PRECISION
|
47
|
+
when duration < 300; 1
|
48
|
+
else 0
|
49
|
+
end
|
50
|
+
|
51
|
+
if duration > 60
|
52
|
+
minutes = (duration.to_i / 60).to_i
|
53
|
+
seconds = duration - minutes * 60
|
54
|
+
|
55
|
+
"#{pluralize(minutes, 'minute')} #{pluralize(format_seconds(seconds, precision), 'second')}"
|
56
|
+
else
|
57
|
+
pluralize(format_seconds(duration, precision), 'second')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
#
|
63
|
+
# Formats seconds to have 5 digits of precision with trailing zeros removed if the number
|
64
|
+
# is less than 1 or with 2 digits of precision if the number is greater than zero.
|
65
|
+
#
|
66
|
+
# @param [Float] float
|
67
|
+
# @return [String] formatted float
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# format_seconds(0.000006) #=> "0.00001"
|
71
|
+
# format_seconds(0.020000) #=> "0.02"
|
72
|
+
# format_seconds(1.00000000001) #=> "1"
|
73
|
+
#
|
74
|
+
# The precision used is set in {Helpers::SUB_SECOND_PRECISION} and {Helpers::DEFAULT_PRECISION}.
|
75
|
+
#
|
76
|
+
# @see #strip_trailing_zeroes
|
77
|
+
def format_seconds(float, precision = nil)
|
78
|
+
precision ||= (float < 1) ? SUB_SECOND_PRECISION : DEFAULT_PRECISION
|
79
|
+
formatted = sprintf("%.#{precision}f", float)
|
80
|
+
strip_trailing_zeroes(formatted)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @api private
|
84
|
+
#
|
85
|
+
# Remove trailing zeros from a string.
|
86
|
+
#
|
87
|
+
# @param [String] string string with trailing zeros
|
88
|
+
# @return [String] string with trailing zeros removed
|
89
|
+
def strip_trailing_zeroes(string)
|
90
|
+
stripped = string.sub(/[^1-9]+$/, '')
|
91
|
+
stripped.empty? ? "0" : stripped
|
92
|
+
end
|
93
|
+
|
94
|
+
# @api private
|
95
|
+
#
|
96
|
+
# Pluralize a word based on a count.
|
97
|
+
#
|
98
|
+
# @param [Fixnum] count number of objects
|
99
|
+
# @param [String] string word to be pluralized
|
100
|
+
# @return [String] pluralized word
|
101
|
+
def pluralize(count, string)
|
102
|
+
"#{count} #{string}#{'s' unless count.to_f == 1}"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|