tryouts 0.8.8 → 2.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.
data/lib/tryouts.rb CHANGED
@@ -1,349 +1,434 @@
1
+ #require 'pathname'
2
+ #p Pathname(caller.last.split(':').first)
3
+ require 'ostruct'
1
4
 
2
- require 'time'
3
- require 'digest/sha1'
4
-
5
- require 'attic'
6
- require 'sysinfo'
7
- require 'yaml'
5
+ unless defined?(TRYOUTS_LIB_HOME)
6
+ TRYOUTS_LIB_HOME = File.expand_path File.dirname(__FILE__)
7
+ end
8
8
 
9
- ## NOTE: Don't require rye here so
10
- ## we can still run tryouts on the
11
- ## development version.
9
+ class Tryouts
10
+ module VERSION
11
+ def self.to_s
12
+ load_config
13
+ [@version[:MAJOR], @version[:MINOR], @version[:PATCH]].join('.')
14
+ end
15
+ def self.inspect
16
+ load_config
17
+ [@version[:MAJOR], @version[:MINOR], @version[:PATCH], @version[:BUILD]].join('.')
18
+ end
19
+ def self.load_config
20
+ require 'yaml'
21
+ @version ||= YAML.load_file(File.join(TRYOUTS_LIB_HOME, '..', 'VERSION.yml'))
22
+ end
23
+ end
24
+ end
12
25
 
13
- begin; require 'json'; rescue LoadError; end # json may not be installed
14
26
 
15
- GYMNASIUM_HOME = File.join(Dir.pwd, '{tryouts,try}') ## also check try (for rye)
16
- GYMNASIUM_GLOB = File.join(GYMNASIUM_HOME, '**', '*_tryouts.rb')
27
+ class Tryouts
28
+ @debug = false
29
+ @container = Class.new
30
+ @cases = []
31
+ @sysinfo = nil
32
+ class << self
33
+ attr_accessor :debug, :container
34
+ attr_reader :cases
35
+
36
+ def sysinfo
37
+ require 'sysinfo'
38
+ @sysinfo ||= SysInfo.new
39
+ @sysinfo
40
+ end
41
+
42
+ def debug?() @debug == true end
43
+
44
+ def run_all *paths
45
+ batches = paths.collect do |path|
46
+ run path
47
+ end
48
+
49
+ all, skipped_tests, failed_tests = 0, 0, 0
50
+ skipped_batches, failed_batches = 0, 0
51
+
52
+ msg 'Ruby %s @ %-40s' % [RUBY_VERSION, Time.now], $/
53
+
54
+ batches.each do |batch|
55
+ if !batch.run?
56
+ skipped_batches += 1
57
+ status = "SKIP"
58
+ elsif batch.failed?
59
+ failed_batches += 1
60
+ status = Console.color(:red, "FAIL").bright
61
+ else
62
+ status = Console.color(:green, "PASS").bright
63
+ end
64
+
65
+ path = batch.path.gsub(/#{Dir.pwd}\/?/, '')
66
+
67
+ msg '%-60s %s' % [path, status]
68
+ batch.each do |t|
69
+ if t.failed? && failed_tests == 0
70
+ #msg Console.reverse(" %-60s" % 'Errors')
71
+ end
72
+
73
+ all += 1
74
+ skipped_tests += 1 unless t.run?
17
75
 
76
+ if t.failed?
77
+ msg if (failed_tests += 1) == 1
78
+ msg Console.reverse(' %-58s ' % [t.desc.to_s])
79
+ msg t.test.inspect, t.exps.inspect
80
+ msg Console.color(:red, t.failed.join($/)), $/
81
+ end
82
+ end
83
+ end
84
+ msg
85
+ if all > 0
86
+ suffix = 'tests passed'
87
+ suffix << " (#{skipped_tests} skipped)" if skipped_tests > 0
88
+ msg cformat(all-failed_tests-skipped_tests, all-skipped_tests, suffix) if all-skipped_tests > 0
89
+ end
90
+ if batches.size > 1
91
+ if batches.size-skipped_batches > 0
92
+ suffix = "batches passed"
93
+ suffix << " (#{skipped_batches} skipped)" if skipped_batches > 0
94
+ msg cformat(batches.size-skipped_batches-failed_batches, batches.size-skipped_batches, suffix)
95
+ end
96
+ end
97
+
98
+ failed_tests # 0 means success
99
+ end
100
+
101
+ def cformat(*args)
102
+ Console.bright '%3d of %d %s' % args
103
+ end
104
+
105
+ def run path
106
+ batch = parse path
107
+ batch.run
108
+ batch
109
+ end
110
+
111
+ def parse path
112
+ #debug "Loading #{path}"
113
+ lines = File.readlines path
114
+ skip_ahead = 0
115
+ batch = TestBatch.new path, lines
116
+ lines.size.times do |idx|
117
+ skip_ahead -= 1 and next if skip_ahead > 0
118
+ line = lines[idx].chomp
119
+ #debug('%-4d %s' % [idx, line])
120
+ if expectation? line
121
+ offset = 0
122
+ exps = Section.new(path, idx+1)
123
+ exps << line.chomp
124
+ while (idx+offset < lines.size)
125
+ offset += 1
126
+ this_line = lines[idx+offset]
127
+ break if ignore?(this_line)
128
+ if expectation?(this_line)
129
+ exps << this_line.chomp
130
+ skip_ahead += 1
131
+ end
132
+ exps.last += 1
133
+ end
134
+
135
+ offset = 0
136
+ buffer, desc = Section.new(path), Section.new(path)
137
+ test = Section.new(path, idx) # test start the line before the exp.
138
+ blank_buffer = Section.new(path)
139
+ while (idx-offset >= 0)
140
+ offset += 1
141
+ this_line = lines[idx-offset].chomp
142
+ buffer.unshift this_line if ignore?(this_line)
143
+ if comment?(this_line)
144
+ buffer.unshift this_line
145
+ end
146
+ if test?(this_line)
147
+ test.unshift(*buffer) && buffer.clear
148
+ test.unshift this_line
149
+ end
150
+ if test_begin?(this_line)
151
+ while test_begin?(lines[idx-(offset+1)].chomp)
152
+ offset += 1
153
+ buffer.unshift lines[idx-offset].chomp
154
+ end
155
+ end
156
+ if test_begin?(this_line) || idx-offset == 0 || expectation?(this_line)
157
+ adjust = expectation?(this_line) ? 2 : 1
158
+ test.first = idx-offset+buffer.size+adjust
159
+ desc.unshift *buffer
160
+ desc.last = test.first-1
161
+ desc.first = desc.last-desc.size+1
162
+ # remove empty lines between the description
163
+ # and the previous expectation
164
+ while !desc.empty? && desc[0].empty?
165
+ desc.shift
166
+ desc.first += 1
167
+ end
168
+ break
169
+ end
170
+ end
171
+
172
+ batch << TestCase.new(desc, test, exps)
173
+ end
174
+ end
18
175
 
19
- # = Tryouts
20
- #
21
- # This class has three purposes:
22
- # * It represents the Tryouts object which is a group of Tryout objects.
23
- # * The tryouts and dreams DSLs are executed within its namespace. In general the
24
- # class methods are the handlers for the DSL syntax (some instance getter methods
25
- # are modified to support DSL syntax by acting like setters when given arguments)
26
- # * It stores all known instances of Tryouts objects in a class variable @@instances.
27
- #
28
- # ==== Are you ready to run some drills?
29
- #
30
- # May all your dreams come true!
31
- #
32
- class Tryouts
33
- # = Exception
34
- # A generic exception which all other Tryouts exceptions inherit from.
35
- class Exception < RuntimeError; end
36
- # = BadDreams
37
- # Raised when there is a problem loading or parsing a Tryouts::Drill::Dream object
38
- class BadDream < Tryouts::Exception; end
39
- class TooManyArgs < Tryouts::Exception; end
40
- class NoDrillType < Tryouts::Exception
41
- attr_accessor :tname
42
- def initialize(t); @tname = t; end
43
- def message
44
- vdt = Tryouts::Drill.valid_dtypes
45
- "Tryout '#{@tname}' has no drill type. Should be: #{vdt.join(', ')}"
176
+ batch
46
177
  end
47
- end
48
178
 
49
- VERSION = "0.8.8"
50
-
51
- require 'tryouts/mixins'
52
- require 'tryouts/tryout'
53
- require 'tryouts/drill'
54
-
55
- autoload :Stats, 'tryouts/stats'
56
-
57
- unless defined?(HASH_TYPE)
58
- if RUBY_VERSION =~ /1.8/
59
- require 'tryouts/orderedhash'
60
- HASH_TYPE = Tryouts::OrderedHash
61
- else
62
- HASH_TYPE = Hash
179
+ def print str
180
+ STDOUT.print str
181
+ STDOUT.flush
63
182
  end
64
- end
65
-
66
- # An Array of +_tryouts.rb+ file paths that have been loaded.
67
- @@loaded_files = []
68
- # An Hash of Tryouts instances stored under the name of the Tryouts subclass.
69
- @@instances = HASH_TYPE.new
70
- # An instance of SysInfo
71
- @@sysinfo = nil
72
-
73
- @@debug = false
74
- @@verbose = 0
75
- # This will be true if any error occurred during any of the drills or parsing.
76
- @@failed = false
77
-
78
- def self.debug?; @@debug; end
79
- def self.enable_debug; @@debug = true; end
80
- def self.disable_debug; @@debug = false; end
81
-
82
- def self.verbose; @@verbose; end
83
- def self.verbose=(v); @@verbose = (v == true) ? 1 : v; end
84
-
85
- def self.failed?; @@failed; end
86
- def self.failed=(v); @@failed = v; end
87
-
88
- # Returns +@@instances+
89
- def self.instances; @@instances; end
90
- # Returns +@@sysinfo+
91
- def self.sysinfo
92
- @@sysinfo = SysInfo.new if @@sysinfo.nil?
93
- @@sysinfo
94
- end
95
-
96
- # The name of this group of Tryout objects
97
- attr_accessor :group
98
- # A Symbol representing the default drill type. One of: :cli, :api
99
- attr_accessor :dtype
100
- # An Array of file paths which populated this instance of Tryouts
101
- attr_accessor :paths
102
- # An Array of Tryout objects
103
- attr_accessor :tryouts
104
- # A Symbol representing the command taking part in the tryouts. For @dtype :cli only.
105
- attr_accessor :command
106
- # A Symbol representing the name of the library taking part in the tryouts. For @dtype :api only.
107
- attr_accessor :library
108
- # An Array of exceptions that were raised during the tryouts that were not captured by a drill.
109
- attr_reader :errors
110
-
111
- def initialize(group=nil)
112
- @group = group || "Default Group"
113
- @tryouts = HASH_TYPE.new
114
- @paths, @errors = [], []
115
- @command = nil
116
- end
117
-
118
- # Populate this Tryouts from a block. The block should contain calls to
119
- # the external DSL methods: tryout, command, library, group
120
- def from_block(b, &inline)
121
- instance_eval &b
122
- end
123
-
124
- # Execute Tryout#report for each Tryout in +@tryouts+
125
- def report
126
- successes = []
127
- @tryouts.each_pair { |n,to| successes << to.report }
128
- puts $/, "All your dreams came true" unless successes.member?(false)
129
- end
130
-
131
- # Execute Tryout#run for each Tryout in +@tryouts+
132
- def run; @tryouts.each_pair { |n,to| to.run }; end
133
-
134
- # Add a shell command to Rye::Cmd and save the command name
135
- # in @@commands so it can be used as the default for drills
136
- def command(name=nil, path=nil)
137
- return @command if name.nil?
138
- require 'rye'
139
- @command = name.to_sym
140
- @dtype = :cli
141
- Rye::Cmd.module_eval do
142
- define_method(name) do |*args|
143
- cmd(path || name, *args)
183
+
184
+ def msg *msg
185
+ STDOUT.puts *msg
186
+ end
187
+
188
+ def err *msg
189
+ msg.each do |line|
190
+ STDERR.puts Console.color :red, line
144
191
  end
145
192
  end
146
- @command
147
- end
148
- # Calls Tryouts#command on the current instance of Tryouts
149
- #
150
- # NOTE: this is a standalone DSL-syntax method.
151
- def self.command(*args)
152
- @@instances.last.command(*args)
153
- end
154
-
155
- # Require +name+. If +path+ is supplied, it will "require path".
156
- # * +name+ The name of the library in question (required). Stored as a Symbol to +@library+.
157
- # * +path+ Add a path to the front of $LOAD_PATH (optional). Use this if you want to load
158
- # a specific copy of the library. Otherwise, it loads from the system path. If the path
159
- # in specified in multiple arguments they are joined and expanded.
160
- #
161
- # library '/an/absolute/path'
162
- # library __FILE__, '..', 'lib'
163
- #
164
- def library(name=nil, *path)
165
- return @library if name.nil?
166
- @library, @dtype = name.to_sym, :api
167
- path = File.expand_path(File.join(*path))
168
- $LOAD_PATH.unshift path unless path.nil?
169
- begin
170
- require @library.to_s
171
- rescue LoadError => ex
172
- newex = Tryouts::Exception.new(ex.message)
173
- trace = ex.backtrace
174
- trace.unshift @paths.last
175
- newex.set_backtrace trace
176
- @errors << newex
177
- Tryouts.failed = true
178
- rescue SyntaxError, Exception, TypeError,
179
- RuntimeError, NoMethodError, NameError => ex
180
- @errors << ex
181
- Tryouts.failed = true
193
+
194
+ def debug *msg
195
+ STDERR.puts *msg if @debug
196
+ end
197
+
198
+ def eval(str, path, line)
199
+ begin
200
+ Kernel.eval str, @container.send(:binding), path, line
201
+ rescue SyntaxError, LoadError => ex
202
+ Tryouts.err Console.color(:red, ex.message),
203
+ Console.color(:red, ex.backtrace.first)
204
+ nil
205
+ end
182
206
  end
183
- end
184
- # Calls Tryouts#library on the current instance of Tryouts
185
- #
186
- # NOTE: this is a standalone DSL-syntax method.
187
- def self.library(*args)
188
- @@instances.last.library(*args)
189
- end
190
-
191
- def group(name=nil)
192
- return @group if name.nil?
193
- @group = name unless name.nil?
194
- @group
195
- end
196
- # Raises a Tryouts::Exception. +group+ is not support in the standalone syntax
197
- # because the group name is taken from the name of the class. See inherited.
198
- #
199
- # NOTE: this is a standalone DSL-syntax method.
200
- def self.group(*args)
201
- raise "Group is already set: #{@@instances.last.group}"
202
- end
203
-
204
- # Create a new Tryout object and add it to the list for this Tryouts class.
205
- # * +name+ is the name of the Tryout
206
- # * +dtype+ is the default drill type for the Tryout.
207
- # * +command+ when type is :cli, this is the name of the Rye::Box method that we're testing. Otherwise ignored.
208
- # * +b+ is a block definition for the Tryout. See Tryout#from_block
209
- #
210
- # NOTE: This is a DSL-only method and is not intended for OO use.
211
- def tryout(name, dtype=nil, command=nil, &block)
212
- return if name.nil?
213
- dtype ||= @dtype
214
- command ||= @command if dtype == :cli
215
207
 
216
- raise NoDrillType, name if dtype.nil?
208
+ private
217
209
 
218
- to = find_tryout(name, dtype)
219
- if to.nil?
220
- to = Tryouts::Tryout.new(name, dtype, command)
221
- @tryouts[name] = to
210
+ def expectation? str
211
+ !ignore?(str) && str.strip.match(/^\#\s*=>/)
222
212
  end
223
213
 
224
- # Process the rest of the DSL
225
- begin
226
- to.from_block block if block
227
- rescue SyntaxError, LoadError, Exception, TypeError,
228
- RuntimeError, NoMethodError, NameError => ex
229
- @errors << ex
230
- Tryouts.failed = true
231
- end
232
- to
233
- end
234
- # Calls Tryouts#tryout on the current instance of Tryouts
235
- #
236
- # NOTE: this is a standalone DSL-syntax method.
237
- def self.tryout(*args, &block)
238
- @@instances.last.tryout(*args, &block)
239
- end
214
+ def comment? str
215
+ !str.strip.match(/^\#+/).nil? && !expectation?(str)
216
+ end
217
+
218
+ def test? str
219
+ !ignore?(str) && !expectation?(str) && !comment?(str)
220
+ end
221
+
222
+ def ignore? str
223
+ str.to_s.strip.chomp.empty?
224
+ end
225
+
226
+ def test_begin? str
227
+ ret = !str.strip.match(/^\#+\s*TEST/i).nil? ||
228
+ !str.strip.match(/^\#\#+[\s\w]+/i).nil?
229
+ ret
230
+ end
240
231
 
241
- # Find matching Tryout objects by +name+ and filter by
242
- # +dtype+ if specified. Returns a Tryout object or nil.
243
- def find_tryout(name, dtype=nil)
244
- by_name = @tryouts.values.select { |t| t.name == name }
245
- by_name = by_name.select { |t| t.dtype == dtype } if dtype
246
- by_name.first # by_name is an Array. We just want the Object.
247
- end
248
-
249
- # This method does nothing. It provides a quick way to disable a tryout.
250
- #
251
- # NOTE: This is a DSL-only method and is not intended for OO use.
252
- def xtryout(*args, &block); end
253
- # This method does nothing. It provides a quick way to disable a tryout.
254
- #
255
- # NOTE: this is a standalone DSL-syntax method.
256
- def self.xtryout(*args, &block); end
257
-
258
- # Returns +@tryouts+.
259
- #
260
- # Also acts as a stub for Tryouts#tryout in case someone
261
- # specifies "tryouts 'name' do ..." in the DSL.
262
- def tryouts(*args, &block)
263
- return tryout(*args, &block) unless args.empty?
264
- @tryouts
265
- end
266
- # An alias for Tryouts.tryout.
267
- def self.tryouts(*args, &block)
268
- tryout(args, &block)
232
+
269
233
  end
270
234
 
271
- # This method does nothing. It provides a quick way to disable a tryout.
272
- #
273
- # NOTE: This is a DSL-only method and is not intended for OO use.
274
- def xtryouts(*args, &block); end
275
- # This method does nothing. It provides a quick way to disable a tryout.
276
- #
277
- # NOTE: this is a standalone DSL-syntax method.
278
- def self.xtryouts(*args, &block); end
279
-
280
-
281
- # Parse a +_tryouts.rb+ file. See Tryouts::CLI::Run for an example.
282
- #
283
- # NOTE: this is an OO syntax method
284
- def self.parse_file(fpath)
285
- raise "No such file: #{fpath}" unless File.exists?(fpath)
286
- file_content = File.read(fpath)
287
- to = Tryouts.new
288
- begin
289
- to.paths << fpath
290
- to.instance_eval file_content, fpath
291
- # After parsing the DSL, we'll know the group name.
292
- # If a Tryouts object already exists for that group
293
- # we'll use that instead and re-parse the DSL.
294
- if @@instances.has_key? to.group
295
- to = @@instances[to.group]
296
- to.instance_eval file_content, fpath
235
+ class TestBatch < Array
236
+ class Container
237
+ def metaclass
238
+ class << self; end
297
239
  end
298
- rescue SyntaxError, LoadError, Exception, TypeError,
299
- RuntimeError, NoMethodError, NameError => ex
300
- to.errors << ex
301
- Tryouts.failed = true
302
- # It's helpful to display the group name
303
- file_content.match(/^group (.+?)$/) do |x,t|
304
- # We use eval as a quick cheat so we don't have
305
- # to parse all the various kinds of quotes.
306
- to.group = eval x.captures.first
240
+ end
241
+ attr_reader :path
242
+ attr_reader :failed
243
+ attr_reader :lines
244
+ def initialize(p,l)
245
+ @path, @lines = p, l
246
+ @container = Container.new.metaclass
247
+ @run = false
248
+ end
249
+ def run
250
+ return if empty?
251
+ setup
252
+ ret = self.select { |tc| !tc.run } # select failed
253
+ @failed = ret.size
254
+ @run = true
255
+ clean
256
+ !failed?
257
+ end
258
+ def failed?
259
+ !@failed.nil? && @failed > 0
260
+ end
261
+ def setup
262
+ return if empty?
263
+ start = first.desc.nil? ? first.test.first : first.desc.first-1
264
+ Tryouts.eval lines[0..start-1].join, path, 0 if start > 0
265
+ end
266
+ def clean
267
+ return if empty?
268
+ last = first.exps.last+1
269
+ if last < lines.size
270
+ Tryouts.eval lines[last..-1].join, path, last
307
271
  end
308
272
  end
309
- @@instances[to.group] = to
310
- to
273
+ def run?
274
+ @run
275
+ end
311
276
  end
312
-
313
- # Run all Tryout objects in +@tryouts+
314
- #
315
- # NOTE: this is an OO syntax method
316
- def self.run
317
- @@instances.each_pair do |group, inst|
318
- inst.tryouts.each_pair do |name,to|
319
- to.run
320
- to.report
321
- end
277
+ class TestCase
278
+ attr_reader :desc, :test, :exps, :failed, :passed
279
+ def initialize(d,t,e)
280
+ @desc, @test, @exps, @path = d,t,e
281
+ end
282
+ def inspect
283
+ [@desc.inspect, @test.inspect, @exps.inspect].join
284
+ end
285
+ def to_s
286
+ [@desc.to_s, @test.to_s, @exps.to_s].join
287
+ end
288
+ def run
289
+ Tryouts.debug '%s:%d' % [@test.path, @test.first]
290
+ Tryouts.debug inspect, $/
291
+ test_value = Tryouts.eval @test.to_s, @test.path, @test.first
292
+ @passed, @failed = [], []
293
+ exps.each_with_index { |exp,idx|
294
+ exp =~ /\#+\s*=>\s*(.+)$/
295
+ exp_value = Tryouts.eval($1, @exps.path, @exps.first+idx)
296
+ ret = test_value == exp_value
297
+ if ret
298
+ @passed << ' %s == %s' % [test_value.inspect, exp_value.inspect]
299
+ else
300
+ @failed << ' %s != %s' % [test_value.inspect, exp_value.inspect]
301
+ end
302
+ ret
303
+ }
304
+ Tryouts.debug
305
+ @failed.empty?
306
+ end
307
+ def run?
308
+ !@failed.nil?
309
+ end
310
+ def failed?
311
+ !@failed.nil? && !@failed.empty?
312
+ end
313
+ private
314
+ def create_proc str, path, line
315
+ eval("Proc.new {\n #{str}\n}", binding, path, line)
322
316
  end
323
317
  end
324
-
325
- # Called when a new class inherits from Tryouts. This creates a new instance
326
- # of Tryouts, sets group to the name of the new class, and adds the instance
327
- # to +@@instances+.
328
- #
329
- # NOTE: this is a standalone DSL-syntax method.
330
- def self.inherited(klass)
331
- to = @@instances[ klass ]
332
- to ||= Tryouts.new
333
- to.paths << __FILE__
334
- to.group = klass
335
- @@instances[to.group] = to
318
+ class Section < Array
319
+ attr_accessor :path, :first, :last
320
+ def initialize path, start=0
321
+ @path = path
322
+ @first, @last = start, start
323
+ end
324
+ def range
325
+ @first..@last
326
+ end
327
+ def inspect
328
+ range.to_a.zip(self).collect do |line|
329
+ "%-4d %s\n" % line
330
+ end.join
331
+ end
332
+ def to_s
333
+ self.join($/)
334
+ end
336
335
  end
336
+
337
337
 
338
-
339
- ##---
340
- ## Is this wacky syntax useful for anything?
341
- ## t2 :set .
342
- ## run = "poop"
343
- ## def self.t2(*args)
344
- ## OpenStruct.new
345
- ## end
346
- ##+++
347
-
338
+ module Console
339
+
340
+ # ANSI escape sequence numbers for text attributes
341
+ ATTRIBUTES = {
342
+ :normal => 0,
343
+ :bright => 1,
344
+ :dim => 2,
345
+ :underline => 4,
346
+ :blink => 5,
347
+ :reverse => 7,
348
+ :hidden => 8,
349
+ :default => 0,
350
+ }.freeze unless defined? ATTRIBUTES
351
+
352
+ # ANSI escape sequence numbers for text colours
353
+ COLOURS = {
354
+ :black => 30,
355
+ :red => 31,
356
+ :green => 32,
357
+ :yellow => 33,
358
+ :blue => 34,
359
+ :magenta => 35,
360
+ :cyan => 36,
361
+ :white => 37,
362
+ :default => 39,
363
+ :random => 30 + rand(10).to_i
364
+ }.freeze unless defined? COLOURS
365
+
366
+ # ANSI escape sequence numbers for background colours
367
+ BGCOLOURS = {
368
+ :black => 40,
369
+ :red => 41,
370
+ :green => 42,
371
+ :yellow => 43,
372
+ :blue => 44,
373
+ :magenta => 45,
374
+ :cyan => 46,
375
+ :white => 47,
376
+ :default => 49,
377
+ :random => 40 + rand(10).to_i
378
+ }.freeze unless defined? BGCOLOURS
379
+
380
+ module InstanceMethods
381
+ def bright
382
+ Console.bright(self)
383
+ end
384
+ def reverse
385
+ Console.reverse(self)
386
+ end
387
+ def color(col)
388
+ Console.color(col, self)
389
+ end
390
+ def att(col)
391
+ Console.att(col, self)
392
+ end
393
+ def bgcolor(col)
394
+ Console.bgcolor(col, self)
395
+ end
396
+ end
397
+
398
+ def self.bright(str)
399
+ str = [style(ATTRIBUTES[:bright]), str, default_style].join
400
+ str.extend Console::InstanceMethods
401
+ str
402
+ end
403
+ def self.reverse(str)
404
+ str = [style(ATTRIBUTES[:reverse]), str, default_style].join
405
+ str.extend Console::InstanceMethods
406
+ str
407
+ end
408
+ def self.color(col, str)
409
+ str = [style(COLOURS[col]), str, default_style].join
410
+ str.extend Console::InstanceMethods
411
+ str
412
+ end
413
+ def self.att(name, str)
414
+ str = [style(ATTRIBUTES[name]), str, default_style].join
415
+ str.extend Console::InstanceMethods
416
+ str
417
+ end
418
+ def self.bgcolor(col, str)
419
+ str = [style(ATTRIBUTES[col]), str, default_style].join
420
+ str.extend Console::InstanceMethods
421
+ str
422
+ end
423
+ private
424
+ def self.style(*att)
425
+ # => \e[8;34;42m
426
+ "\e[%sm" % att.join(';')
427
+ end
428
+ def self.default_style
429
+ style(ATTRIBUTES[:default], ATTRIBUTES[:COLOURS], ATTRIBUTES[:BGCOLOURS])
430
+ end
431
+ end
348
432
 
349
433
  end
434
+