rack-sanitizer 2.0.3 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63f52a6acb6c9e0d966c5adf830bff54689e56eb8cea9c91a0fe7cc05a6786b4
4
- data.tar.gz: 54f2453179ad918e01b70dc949d1c8685e77f74311fb14b1465b76c1a3ce6130
3
+ metadata.gz: fca030fbcd503f8e7a4f61ebd1286552baf24d3b1a8607af3c02ca12dd9533e0
4
+ data.tar.gz: 44770acabfcea4af68db609520fd158986c72e5a033151352384a08f16174cda
5
5
  SHA512:
6
- metadata.gz: f1139b845eea460cdccb33e34acaa750b755a4a55729bc593a54650c0052f1a041e3d20987686abec16b2d2cf492128e0d0d9e9451756c54be97ee70eb07b032
7
- data.tar.gz: bfe792b1c548442f0272ac47d975779a4f43b1ba958614bf5df4674f61cc9284e3ba6ed701629b2696aa9fd0b62b2240f3c5e0c8accba1ea8dca543bcd0c6d0e
6
+ metadata.gz: 0a88c1f9a8f7aa00cf4b7a6c2ad9306f53218d467177bc154efcb12a9cebf7c2bb79a7404dbd0c1a813929b426a5169d714149488e92318ce4b0b0c80067971e
7
+ data.tar.gz: 0fc65bc666ac425913aa02abb117545e29dc193961e41a91cf4f704be14567f2271e8e028527a3e0872c2d129029500880959a3058d36eedd3150b079fc4d781
@@ -10,15 +10,22 @@ jobs:
10
10
  strategy:
11
11
  fail-fast: false
12
12
  matrix:
13
- ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", ruby-head, jruby-9.2, jruby-9.3, jruby-head]
13
+ ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "3.4", ruby-head, jruby-9.3, jruby-head]
14
+ rack: ["2.2", "3.0", "3.1", "3.2"]
15
+ include:
16
+ - ruby: "3.4"
17
+ rubyopt: "--enable-frozen-string-literal --debug-frozen-string-literal"
18
+
19
+ env:
20
+ BUNDLE_GEMFILE: gemfiles/rack_${{ matrix.rack }}.gemfile
14
21
 
15
22
  steps:
16
- - uses: actions/checkout@v4
23
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17
24
  - name: Set up Ruby
18
- uses: ruby/setup-ruby@v1
25
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
19
26
  with:
20
27
  bundler-cache: true # 'bundle install' and cache gems
21
28
  ruby-version: ${{ matrix.ruby }}
22
29
  bundler: latest
23
- - name: Run tests
24
- run: bundle exec rake
30
+ - name: Run tests on Rack ${{ matrix.rack }} ${{ matrix.rubyopt }}
31
+ run: bundle exec rake RUBYOPT="${{ matrix.rubyopt }}"
@@ -0,0 +1,24 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ release:
9
+ permissions:
10
+ contents: write
11
+ id-token: write
12
+
13
+ environment: release
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
21
+ with:
22
+ bundler-cache: true
23
+ ruby-version: 3.4.4
24
+ - uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ /gemfiles/*.lock
2
3
  *.rbc
3
4
  .bundle
4
5
  .config
data/.rubocop.yml ADDED
@@ -0,0 +1,12 @@
1
+
2
+ inherit_gem:
3
+ rubocop-shopify: rubocop.yml
4
+
5
+ AllCops:
6
+ SuggestExtensions: false
7
+ TargetRubyVersion: 2.5
8
+ Exclude:
9
+ - bin/bacon
10
+
11
+ Lint/Void:
12
+ Enabled: false
data/Gemfile CHANGED
@@ -1,4 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in rack-sanitizer.gemspec
4
6
  gemspec
7
+
8
+ gem 'rubocop-shopify'
9
+ gem 'rake'
10
+ gem 'ostruct'
data/Gemfile.lock CHANGED
@@ -1,26 +1,57 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack-sanitizer (2.0.3)
4
+ rack-sanitizer (2.1.0)
5
5
  rack (>= 1.0, < 4.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
- bacon (1.2.0)
11
- bacon-colored_output (1.1.1)
12
- bacon
13
- rack (3.0.10)
10
+ ast (2.4.3)
11
+ json (2.21.2)
12
+ language_server-protocol (3.17.0.6)
13
+ lint_roller (1.1.0)
14
+ ostruct (0.6.1)
15
+ parallel (2.1.0)
16
+ parser (3.3.12.0)
17
+ ast (~> 2.4.1)
18
+ racc
19
+ prism (1.9.0)
20
+ racc (1.8.1)
21
+ rack (3.1.21)
22
+ rainbow (3.1.1)
14
23
  rake (13.1.0)
24
+ regexp_parser (2.12.0)
25
+ rubocop (1.90.0)
26
+ json (>= 2.3)
27
+ language_server-protocol (~> 3.17.0.2)
28
+ lint_roller (~> 1.1.0)
29
+ parallel (>= 1.10)
30
+ parser (>= 3.3.0.2)
31
+ rainbow (>= 2.2.2, < 4.0)
32
+ regexp_parser (>= 2.9.3, < 3.0)
33
+ rubocop-ast (>= 1.49.0, < 2.0)
34
+ ruby-progressbar (~> 1.7)
35
+ unicode-display_width (>= 2.4.0, < 4.0)
36
+ rubocop-ast (1.50.0)
37
+ parser (>= 3.3.7.2)
38
+ prism (~> 1.7)
39
+ rubocop-shopify (3.0.2)
40
+ lint_roller
41
+ rubocop (~> 1.72, >= 1.72.1)
42
+ ruby-progressbar (1.13.0)
43
+ unicode-display_width (3.2.0)
44
+ unicode-emoji (~> 4.1)
45
+ unicode-emoji (4.2.0)
15
46
 
16
47
  PLATFORMS
17
48
  ruby
18
49
 
19
50
  DEPENDENCIES
20
- bacon
21
- bacon-colored_output
51
+ ostruct
22
52
  rack-sanitizer!
23
53
  rake
54
+ rubocop-shopify
24
55
 
25
56
  BUNDLED WITH
26
57
  2.5.7
data/LICENSE.txt CHANGED
@@ -1,3 +1,4 @@
1
+ Copyright (c) 2026 Shopify Inc.
1
2
  Copyright (c) 2013 Peter Zotov
2
3
 
3
4
  MIT License
@@ -19,4 +20,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
20
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
21
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
22
  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -44,7 +44,7 @@ The generic sanitization algorithm is as follows:
44
44
  1. Force the encoding to ASCII8-BIT.
45
45
  2. Re-encode it as UTF-8, replacing invalid and undefined characters as U+FFFD.
46
46
 
47
- For fields with "raw data", the algorithm is applied once and the (UTF-8 encoded) result is left in the environment.
47
+ For fields with "raw data", the algorithm is applied once. ASCII-only results remain UTF-8. Results with non-ASCII characters use ASCII-8BIT encoding to satisfy the Rack environment specification.
48
48
 
49
49
  For fields with "percent-encoded data", the algorithm is applied twice to catch both invalid characters appearing as-is and invalid characters appearing in the percent encoding. The percent encoded, ASCII-8BIT encoded result is left in the environment.
50
50
 
data/Rakefile CHANGED
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
 
3
- task :default => :spec
5
+ task default: :spec
4
6
 
5
7
  desc "Run tests"
6
8
  task :spec do
7
- sh 'bacon -a'
9
+ sh 'bin/bacon -a'
8
10
  end
data/bin/bacon ADDED
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ # -*- ruby -*-
4
+
5
+ # Bacon -- small RSpec clone.
6
+ #
7
+ # "Truth will sooner come out from error than from confusion." ---Francis Bacon
8
+
9
+ # Copyright (C) 2007, 2008, 2012 Christian Neukirchen <purl.org/net/chneukirchen>
10
+ #
11
+ # Bacon is freely distributable under the terms of an MIT-style license.
12
+ # See COPYING or http://www.opensource.org/licenses/mit-license.php.
13
+
14
+ module Bacon
15
+ VERSION = "1.2"
16
+
17
+ Counter = Hash.new(0)
18
+ ErrorLog = +""
19
+ Shared = Hash.new { |_, name|
20
+ raise NameError, "no such context: #{name.inspect}"
21
+ }
22
+
23
+ RestrictName = // unless defined? RestrictName
24
+ RestrictContext = // unless defined? RestrictContext
25
+
26
+ Backtraces = true unless defined? Backtraces
27
+
28
+ def self.summary_on_exit
29
+ return if Counter[:installed_summary] > 0
30
+ @timer = Time.now
31
+ at_exit {
32
+ handle_summary
33
+ if $!
34
+ raise $!
35
+ elsif Counter[:errors] + Counter[:failed] > 0
36
+ exit 1
37
+ end
38
+ }
39
+ Counter[:installed_summary] += 1
40
+ end
41
+ class <<self; alias summary_at_exit summary_on_exit; end
42
+
43
+ module SpecDoxOutput
44
+ def handle_specification(name)
45
+ puts spaces + name
46
+ yield
47
+ puts if Counter[:context_depth] == 1
48
+ end
49
+
50
+ def handle_requirement(description)
51
+ print "#{spaces} - #{description}"
52
+ error = yield
53
+ puts error.empty? ? "" : " [#{error}]"
54
+ end
55
+
56
+ def handle_summary
57
+ print ErrorLog if Backtraces
58
+ puts "%d specifications (%d requirements), %d failures, %d errors" %
59
+ Counter.values_at(:specifications, :requirements, :failed, :errors)
60
+ end
61
+
62
+ def spaces
63
+ " " * (Counter[:context_depth] - 1)
64
+ end
65
+ end
66
+
67
+ module TestUnitOutput
68
+ def handle_specification(name) yield end
69
+
70
+ def handle_requirement(description)
71
+ error = yield
72
+ if error.empty?
73
+ print "."
74
+ else
75
+ print error[0..0]
76
+ end
77
+ end
78
+
79
+ def handle_summary
80
+ puts "", "Finished in #{Time.now - @timer} seconds."
81
+ puts ErrorLog if Backtraces
82
+ puts "%d tests, %d assertions, %d failures, %d errors" %
83
+ Counter.values_at(:specifications, :requirements, :failed, :errors)
84
+ end
85
+ end
86
+
87
+ module TapOutput
88
+ def handle_specification(name) yield end
89
+
90
+ def handle_requirement(description)
91
+ ErrorLog.replace ""
92
+ error = yield
93
+ if error.empty?
94
+ puts "ok %-3d - %s" % [Counter[:specifications], description]
95
+ else
96
+ puts "not ok %d - %s: %s" %
97
+ [Counter[:specifications], description, error]
98
+ puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
99
+ end
100
+ end
101
+
102
+ def handle_summary
103
+ puts "1..#{Counter[:specifications]}"
104
+ puts "# %d tests, %d assertions, %d failures, %d errors" %
105
+ Counter.values_at(:specifications, :requirements, :failed, :errors)
106
+ end
107
+ end
108
+
109
+ module KnockOutput
110
+ def handle_specification(name) yield end
111
+
112
+ def handle_requirement(description)
113
+ ErrorLog.replace ""
114
+ error = yield
115
+ if error.empty?
116
+ puts "ok - %s" % [description]
117
+ else
118
+ puts "not ok - %s: %s" % [description, error]
119
+ puts ErrorLog.strip.gsub(/^/, '# ') if Backtraces
120
+ end
121
+ end
122
+
123
+ def handle_summary; end
124
+ end
125
+
126
+ extend SpecDoxOutput # default
127
+
128
+ class Error < RuntimeError
129
+ attr_accessor :count_as
130
+
131
+ def initialize(count_as, message)
132
+ @count_as = count_as
133
+ super message
134
+ end
135
+ end
136
+
137
+ class Context
138
+ attr_reader :name, :block
139
+
140
+ def initialize(name, &block)
141
+ @name = name
142
+ @before, @after = [], []
143
+ @block = block
144
+ end
145
+
146
+ def run
147
+ return unless name =~ RestrictContext
148
+ Counter[:context_depth] += 1
149
+ Bacon.handle_specification(name) { instance_eval(&block) }
150
+ Counter[:context_depth] -= 1
151
+ self
152
+ end
153
+
154
+ def before(&block); @before << block; end
155
+ def after(&block); @after << block; end
156
+
157
+ def behaves_like(*names)
158
+ names.each { |name| instance_eval(&Shared[name]) }
159
+ end
160
+
161
+ def it(description, &block)
162
+ return unless description =~ RestrictName
163
+ block ||= lambda { should.flunk "not implemented" }
164
+ Counter[:specifications] += 1
165
+ run_requirement description, block
166
+ end
167
+
168
+ def should(*args, &block)
169
+ if Counter[:depth]==0
170
+ it('should '+args.first,&block)
171
+ else
172
+ super(*args,&block)
173
+ end
174
+ end
175
+
176
+ def run_requirement(description, spec)
177
+ Bacon.handle_requirement description do
178
+ begin
179
+ Counter[:depth] += 1
180
+ rescued = false
181
+ begin
182
+ @before.each { |block| instance_eval(&block) }
183
+ prev_req = Counter[:requirements]
184
+ instance_eval(&spec)
185
+ rescue Object => e
186
+ rescued = true
187
+ raise e
188
+ ensure
189
+ if Counter[:requirements] == prev_req and not rescued
190
+ raise Error.new(:missing,
191
+ "empty specification: #{@name} #{description}")
192
+ end
193
+ begin
194
+ @after.each { |block| instance_eval(&block) }
195
+ rescue Object => e
196
+ raise e unless rescued
197
+ end
198
+ end
199
+ rescue Object => e
200
+ ErrorLog << "#{e.class}: #{e.message}\n"
201
+ e.backtrace.find_all { |line| line !~ /bin\/bacon|\/bacon\.rb:\d+/ }.
202
+ each_with_index { |line, i|
203
+ ErrorLog << "\t#{line}#{i==0 ? ": #@name - #{description}" : ""}\n"
204
+ }
205
+ ErrorLog << "\n"
206
+
207
+ if e.kind_of? Error
208
+ Counter[e.count_as] += 1
209
+ e.count_as.to_s.upcase
210
+ else
211
+ Counter[:errors] += 1
212
+ "ERROR: #{e.class}"
213
+ end
214
+ else
215
+ ""
216
+ ensure
217
+ Counter[:depth] -= 1
218
+ end
219
+ end
220
+ end
221
+
222
+ def describe(*args, &block)
223
+ context = Bacon::Context.new(args.join(' '), &block)
224
+ (parent_context = self).methods(false).each {|e|
225
+ class<<context; self end.send(:define_method, e) {|*args| parent_context.send(e, *args)}
226
+ }
227
+ @before.each { |b| context.before(&b) }
228
+ @after.each { |b| context.after(&b) }
229
+ context.run
230
+ end
231
+
232
+ def raise?(*args, &block); block.raise?(*args); end
233
+ def throw?(*args, &block); block.throw?(*args); end
234
+ def change?(&block); lambda{}.change?(&block); end
235
+ end
236
+
237
+ module ColoredOutput
238
+ def handle_requirement(*args)
239
+ error = yield
240
+
241
+ print error.empty? ? color("\e[32m") : color("\e[1;31m")
242
+ super(*args) { error }
243
+ print color("\e[0m")
244
+ end
245
+
246
+ def color(escape_seq)
247
+ if $stdout.respond_to?(:tty?) && $stdout.tty?
248
+ escape_seq
249
+ else
250
+ ""
251
+ end
252
+ end
253
+ end
254
+
255
+ extend ColoredOutput
256
+ end
257
+
258
+
259
+ class Object
260
+ def true?; false; end
261
+ def false?; false; end
262
+ end
263
+
264
+ class TrueClass
265
+ def true?; true; end
266
+ end
267
+
268
+ class FalseClass
269
+ def false?; true; end
270
+ end
271
+
272
+ class Proc
273
+ def raise?(*exceptions)
274
+ call
275
+ rescue *(exceptions.empty? ? RuntimeError : exceptions) => e
276
+ e
277
+ else
278
+ false
279
+ end
280
+
281
+ def throw?(sym)
282
+ catch(sym) {
283
+ call
284
+ return false
285
+ }
286
+ return true
287
+ end
288
+
289
+ def change?
290
+ pre_result = yield
291
+ call
292
+ post_result = yield
293
+ pre_result != post_result
294
+ end
295
+ end
296
+
297
+ class Numeric
298
+ def close?(to, delta)
299
+ (to.to_f - self).abs <= delta.to_f rescue false
300
+ end
301
+ end
302
+
303
+
304
+ class Object
305
+ def should(*args, &block) Should.new(self).be(*args, &block) end
306
+ end
307
+
308
+ module Kernel
309
+ private
310
+ def describe(*args, &block) Bacon::Context.new(args.join(' '), &block).run end
311
+ def shared(name, &block) Bacon::Shared[name] = block end
312
+ end
313
+
314
+ class Should
315
+ # Kills ==, ===, =~, eql?, equal?, frozen?, instance_of?, is_a?,
316
+ # kind_of?, nil?, respond_to?, tainted?
317
+ instance_methods.each { |name| undef_method name if name =~ /\?|^\W+$/ }
318
+
319
+ def initialize(object)
320
+ @object = object
321
+ @negated = false
322
+ end
323
+
324
+ def not(*args, &block)
325
+ @negated = !@negated
326
+
327
+ if args.empty?
328
+ self
329
+ else
330
+ be(*args, &block)
331
+ end
332
+ end
333
+
334
+ def be(*args, &block)
335
+ if args.empty?
336
+ self
337
+ else
338
+ block = args.shift unless block_given?
339
+ satisfy(*args, &block)
340
+ end
341
+ end
342
+
343
+ alias a be
344
+ alias an be
345
+
346
+ def satisfy(description="", &block)
347
+ r = yield(@object)
348
+ if Bacon::Counter[:depth] > 0
349
+ Bacon::Counter[:requirements] += 1
350
+ raise Bacon::Error.new(:failed, description) unless @negated ^ r
351
+ r
352
+ else
353
+ @negated ? !r : !!r
354
+ end
355
+ end
356
+
357
+ def method_missing(name, *args, &block)
358
+ name = "#{name}?" if name.to_s =~ /\w[^?]\z/
359
+
360
+ desc = @negated ? "not ".dup : "".dup
361
+ desc << @object.inspect << "." << name.to_s
362
+ desc << "(" << args.map{|x|x.inspect}.join(", ") << ") failed"
363
+
364
+ satisfy(desc) { |x| x.__send__(name, *args, &block) }
365
+ end
366
+
367
+ def equal(value) self == value end
368
+ def match(value) self =~ value end
369
+ def identical_to(value) self.equal? value end
370
+ alias same_as identical_to
371
+
372
+ def flunk(reason="Flunked")
373
+ raise Bacon::Error.new(:failed, reason)
374
+ end
375
+ end
376
+
377
+ require 'optparse'
378
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib/')
379
+ module Bacon; end
380
+
381
+ automatic = false
382
+ output = 'SpecDoxOutput'
383
+
384
+ opts = OptionParser.new("", 24, ' ') { |opts|
385
+ opts.banner = "Usage: bacon [options] [files | -a] [-- untouched arguments]"
386
+
387
+ opts.separator ""
388
+ opts.separator "Ruby options:"
389
+
390
+ lineno = 1
391
+ opts.on("-e", "--eval LINE", "evaluate a LINE of code") { |line|
392
+ eval line, TOPLEVEL_BINDING, "-e", lineno
393
+ lineno += 1
394
+ }
395
+
396
+ opts.on("-d", "--debug", "set debugging flags (set $DEBUG to true)") {
397
+ $DEBUG = true
398
+ }
399
+ opts.on("-w", "--warn", "turn warnings on for your script") {
400
+ $-w = true
401
+ }
402
+
403
+ opts.on("-I", "--include PATH",
404
+ "specify $LOAD_PATH (may be used more than once)") { |path|
405
+ $LOAD_PATH.unshift(*path.split(":"))
406
+ }
407
+
408
+ opts.on("-r", "--require LIBRARY",
409
+ "require the library, before executing your script") { |library|
410
+ require library
411
+ }
412
+
413
+ opts.separator ""
414
+ opts.separator "bacon options:"
415
+
416
+ opts.on("-s", "--specdox", "do AgileDox-like output (default)") {
417
+ output = 'SpecDoxOutput'
418
+ }
419
+ opts.on("-q", "--quiet", "do Test::Unit-like non-verbose output") {
420
+ output = 'TestUnitOutput'
421
+ }
422
+ opts.on("-p", "--tap", "do TAP (Test Anything Protocol) output") {
423
+ output = 'TapOutput'
424
+ }
425
+ opts.on("-k", "--knock", "do Knock output") {
426
+ output = 'KnockOutput'
427
+ }
428
+
429
+ opts.on("-o", "--output FORMAT",
430
+ "do FORMAT (SpecDox/TestUnit/Tap) output") { |format|
431
+ output = format + "Output"
432
+ }
433
+ opts.on("-Q", "--no-backtrace", "don't print backtraces") {
434
+ Bacon.const_set :Backtraces, false
435
+ }
436
+
437
+ opts.on("-a", "--automatic", "gather tests from ./test/, include ./lib/") {
438
+ $LOAD_PATH.unshift "lib" if File.directory? "lib"
439
+ automatic = true
440
+ }
441
+
442
+ opts.on('-n', '--name NAME', String,
443
+ "runs tests matching regexp NAME") { |n|
444
+ Bacon.const_set :RestrictName, Regexp.new(n)
445
+ }
446
+
447
+ opts.on('-t', '--testcase TESTCASE', String,
448
+ "runs tests in TestCases matching regexp TESTCASE") { |t|
449
+ Bacon.const_set :RestrictContext, Regexp.new(t)
450
+ }
451
+
452
+ opts.separator ""
453
+ opts.separator "Common options:"
454
+
455
+ opts.on_tail("-h", "--help", "Show this message") do
456
+ puts opts
457
+ exit
458
+ end
459
+
460
+ opts.on_tail("--version", "Show version") do
461
+ require 'bacon'
462
+ puts "bacon #{Bacon::VERSION}"
463
+ exit
464
+ end
465
+
466
+ opts.parse! ARGV
467
+ }
468
+
469
+ files = ARGV
470
+
471
+ if automatic
472
+ files.concat Dir["test/**/test_*.rb"]
473
+ files.concat Dir["test/**/spec_*.rb"]
474
+ files.concat Dir["spec/**/spec_*.rb"]
475
+ files.concat Dir["spec/**/*_spec.rb"]
476
+ end
477
+
478
+ if files.empty?
479
+ puts opts.banner
480
+ exit 1
481
+ end
482
+
483
+ Bacon.extend Bacon.const_get(output) rescue abort "No such formatter: #{output}"
484
+ Bacon.summary_on_exit
485
+
486
+ files.each { |file|
487
+ load file
488
+ }