dylansm-wirble 0.1.3.1

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.
Files changed (9) hide show
  1. data/COPYING +20 -0
  2. data/ChangeLog +29 -0
  3. data/README +452 -0
  4. data/Rakefile +115 -0
  5. data/_irbrc +27 -0
  6. data/dylansm-wirble.gemspec +40 -0
  7. data/lib/wirble.rb +536 -0
  8. data/setup.rb +1596 -0
  9. metadata +70 -0
data/lib/wirble.rb ADDED
@@ -0,0 +1,536 @@
1
+ require 'ostruct'
2
+
3
+ #
4
+ # Wirble: A collection of useful Irb features.
5
+ #
6
+ # To use, add the following to your ~/.irbrc:
7
+ #
8
+ # require 'rubygems'
9
+ # require 'wirble'
10
+ # Wirble.init
11
+ #
12
+ # If you want color in Irb, add this to your ~/.irbrc as well:
13
+ #
14
+ # Wirble.colorize
15
+ #
16
+ # Note: I spent a fair amount of time documenting this code in the
17
+ # README. If you've installed via RubyGems, root around your cache a
18
+ # little bit (or fire up gem_server) and read it before you tear your
19
+ # hair out sifting through the code below.
20
+ #
21
+ module Wirble
22
+ VERSION = '0.1.3'
23
+
24
+ #
25
+ # Load internal Ruby features, including pp, tab-completion,
26
+ # and a simple prompt.
27
+ #
28
+ module Internals
29
+ # list of internal libraries to automatically load
30
+ LIBRARIES = %w{pp irb/completion}
31
+
32
+ #
33
+ # load libraries
34
+ #
35
+ def self.init_libraries
36
+ LIBRARIES.each do |lib|
37
+ begin
38
+ require lib
39
+ rescue LoadError
40
+ nil
41
+ end
42
+ end
43
+ end
44
+
45
+ #
46
+ # Set a simple prompt, unless a custom one has been specified.
47
+ #
48
+ def self.init_prompt
49
+ # set the prompt
50
+ if IRB.conf[:PROMPT_MODE] == :DEFAULT
51
+ IRB.conf[:PROMPT_MODE] = :SIMPLE
52
+ end
53
+ end
54
+
55
+ #
56
+ # Load all Ruby internal features.
57
+ #
58
+ def self.init(opt = nil)
59
+ init_libraries unless opt && opt[:skip_libraries]
60
+ init_prompt unless opt && opt[:skip_prompt]
61
+ end
62
+ end
63
+
64
+ #
65
+ # Basic IRB history support. This is based on the tips from
66
+ # http://wiki.rubygarden.org/Ruby/page/show/Irb/TipsAndTricks
67
+ #
68
+ class History
69
+ DEFAULTS = {
70
+ :history_path => ENV['IRB_HISTORY_FILE'] || "~/.irb_history",
71
+ :history_size => (ENV['IRB_HISTORY_SIZE'] || 1000).to_i,
72
+ :history_perms => File::WRONLY | File::CREAT | File::TRUNC,
73
+ }
74
+
75
+ private
76
+
77
+ def say(*args)
78
+ puts(*args) if @verbose
79
+ end
80
+
81
+ def cfg(key)
82
+ @opt["history_#{key}".intern]
83
+ end
84
+
85
+ def save_history
86
+ path, max_size, perms = %w{path size perms}.map { |v| cfg(v) }
87
+
88
+ # read lines from history, and truncate the list (if necessary)
89
+ # lines = Readline::HISTORY.to_a.uniq
90
+ lines = Readline::HISTORY.to_a.reverse.uniq.reverse
91
+
92
+ lines = lines[-max_size, -1] if lines.size > max_size
93
+
94
+ # write the history file
95
+ real_path = File.expand_path(path)
96
+ File.open(real_path, perms) { |fh| fh.puts lines }
97
+ say 'Saved %d lines to history file %s.' % [lines.size, path]
98
+ end
99
+
100
+ def load_history
101
+ # expand history file and make sure it exists
102
+ real_path = File.expand_path(cfg('path'))
103
+ unless File.exist?(real_path)
104
+ say "History file #{real_path} doesn't exist."
105
+ return
106
+ end
107
+
108
+ # read lines from file and add them to history
109
+ lines = File.readlines(real_path).map { |line| line.chomp }
110
+ Readline::HISTORY.push(*lines)
111
+
112
+ say 'Read %d lines from history file %s' % [lines.size, cfg('path')]
113
+ end
114
+
115
+ public
116
+
117
+ def initialize(opt = nil)
118
+ @opt = DEFAULTS.merge(opt || {})
119
+ return unless defined? Readline::HISTORY
120
+ load_history
121
+ Kernel.at_exit { save_history }
122
+ end
123
+ end
124
+
125
+ #
126
+ # Add color support to IRB.
127
+ #
128
+ module Colorize
129
+ #
130
+ # Tokenize an inspection string.
131
+ #
132
+ module Tokenizer
133
+ def self.tokenize(str)
134
+ raise 'missing block' unless block_given?
135
+ chars = str.split(//)
136
+
137
+ # $stderr.puts "DEBUG: chars = #{chars.join(',')}"
138
+
139
+ state, val, i, lc = [], '', 0, nil
140
+ while i <= chars.size
141
+ repeat = false
142
+ c = chars[i]
143
+
144
+ # $stderr.puts "DEBUG: state = #{state}"
145
+
146
+ case state[-1]
147
+ when nil
148
+ case c
149
+ when ':'
150
+ state << :symbol
151
+ when '"'
152
+ state << :string
153
+ when '#'
154
+ state << :object
155
+ when /[a-z]/i
156
+ state << :keyword
157
+ repeat = true
158
+ when /[0-9-]/
159
+ state << :number
160
+ repeat = true
161
+ when '{'
162
+ yield :open_hash, '{'
163
+ when '['
164
+ yield :open_array, '['
165
+ when ']'
166
+ yield :close_array, ']'
167
+ when '}'
168
+ yield :close_hash, '}'
169
+ when /\s/
170
+ yield :whitespace, c
171
+ when ','
172
+ yield :comma, ','
173
+ when '>'
174
+ yield :refers, '=>' if lc == '='
175
+ when '.'
176
+ yield :range, '..' if lc == '.'
177
+ when '='
178
+ # ignore these, they're used elsewhere
179
+ nil
180
+ else
181
+ # $stderr.puts "DEBUG: ignoring char #{c}"
182
+ end
183
+ when :symbol
184
+ case c
185
+ # XXX: should have =, but that messes up foo=>bar
186
+ when /[a-z0-9_!?]/
187
+ val << c
188
+ else
189
+ yield :symbol_prefix, ':'
190
+ yield state[-1], val
191
+ state.pop; val = ''
192
+ repeat = true
193
+ end
194
+ when :string
195
+ case c
196
+ when '"'
197
+ if lc == "\\"
198
+ val[-1] = ?"
199
+ else
200
+ yield :open_string, '"'
201
+ yield state[-1], val
202
+ state.pop; val = ''
203
+ yield :close_string, '"'
204
+ end
205
+ else
206
+ val << c
207
+ end
208
+ when :keyword
209
+ case c
210
+ when /[a-z0-9_]/i
211
+ val << c
212
+ else
213
+ # is this a class?
214
+ st = val =~ /^[A-Z]/ ? :class : state[-1]
215
+
216
+ yield st, val
217
+ state.pop; val = ''
218
+ repeat = true
219
+ end
220
+ when :number
221
+ case c
222
+ when /[0-9e-]/
223
+ val << c
224
+ when '.'
225
+ if lc == '.'
226
+ val[/\.$/] = ''
227
+ yield state[-1], val
228
+ state.pop; val = ''
229
+ yield :range, '..'
230
+ else
231
+ val << c
232
+ end
233
+ else
234
+ yield state[-1], val
235
+ state.pop; val = ''
236
+ repeat = true
237
+ end
238
+ when :object
239
+ case c
240
+ when '<'
241
+ yield :open_object, '#<'
242
+ state << :object_class
243
+ when ':'
244
+ state << :object_addr
245
+ when '@'
246
+ state << :object_line
247
+ when '>'
248
+ yield :close_object, '>'
249
+ state.pop; val = ''
250
+ end
251
+ when :object_class
252
+ case c
253
+ when ':'
254
+ yield state[-1], val
255
+ state.pop; val = ''
256
+ repeat = true
257
+ else
258
+ val << c
259
+ end
260
+ when :object_addr
261
+ case c
262
+ when '>'
263
+ when '@'
264
+ yield :object_addr_prefix, ':'
265
+ yield state[-1], val
266
+ state.pop; val = ''
267
+ repeat = true
268
+ else
269
+ val << c
270
+ end
271
+ when :object_line
272
+ case c
273
+ when '>'
274
+ yield :object_line_prefix, '@'
275
+ yield state[-1], val
276
+ state.pop; val = ''
277
+ repeat = true
278
+ else
279
+ val << c
280
+ end
281
+ else
282
+ raise "unknown state #{state}"
283
+ end
284
+
285
+ unless repeat
286
+ i += 1
287
+ lc = c
288
+ end
289
+ end
290
+ end
291
+ end
292
+
293
+ #
294
+ # Terminal escape codes for colors.
295
+ #
296
+ module Color
297
+ COLORS = {
298
+ :nothing => '0;0',
299
+ :black => '0;30',
300
+ :red => '0;31',
301
+ :green => '0;32',
302
+ :brown => '0;33',
303
+ :blue => '0;34',
304
+ :cyan => '0;36',
305
+ :purple => '0;35',
306
+ :light_gray => '0;37',
307
+ :dark_gray => '1;30',
308
+ :light_red => '1;31',
309
+ :light_green => '1;32',
310
+ :yellow => '1;33',
311
+ :light_blue => '1;34',
312
+ :light_cyan => '1;36',
313
+ :light_purple => '1;35',
314
+ :white => '1;37',
315
+ }
316
+
317
+ #
318
+ # Return the escape code for a given color.
319
+ #
320
+ def self.escape(key)
321
+ COLORS.key?(key) && "\033[#{COLORS[key]}m"
322
+ end
323
+ end
324
+
325
+ #
326
+ # Default Wirble color scheme.
327
+ #
328
+ DEFAULT_COLORS = {
329
+ # delimiter colors
330
+ :comma => :blue,
331
+ :refers => :blue,
332
+
333
+ # container colors (hash and array)
334
+ :open_hash => :green,
335
+ :close_hash => :green,
336
+ :open_array => :green,
337
+ :close_array => :green,
338
+
339
+ # object colors
340
+ :open_object => :light_red,
341
+ :object_class => :white,
342
+ :object_addr_prefix => :blue,
343
+ :object_line_prefix => :blue,
344
+ :close_object => :light_red,
345
+
346
+ # symbol colors
347
+ :symbol => :yellow,
348
+ :symbol_prefix => :yellow,
349
+
350
+ # string colors
351
+ :open_string => :red,
352
+ :string => :cyan,
353
+ :close_string => :red,
354
+
355
+ # misc colors
356
+ :number => :cyan,
357
+ :keyword => :green,
358
+ :class => :light_green,
359
+ :range => :red,
360
+ }
361
+
362
+ #
363
+ # Fruity testing colors.
364
+ #
365
+ TESTING_COLORS = {
366
+ :comma => :red,
367
+ :refers => :red,
368
+ :open_hash => :blue,
369
+ :close_hash => :blue,
370
+ :open_array => :green,
371
+ :close_array => :green,
372
+ :open_object => :light_red,
373
+ :object_class => :light_green,
374
+ :object_addr => :purple,
375
+ :object_line => :light_purple,
376
+ :close_object => :light_red,
377
+ :symbol => :yellow,
378
+ :symbol_prefix => :yellow,
379
+ :number => :cyan,
380
+ :string => :cyan,
381
+ :keyword => :white,
382
+ :range => :light_blue,
383
+ }
384
+
385
+ #
386
+ # Set color map to hash
387
+ #
388
+ def self.colors=(hash)
389
+ @colors = hash
390
+ end
391
+
392
+ #
393
+ # Get current color map
394
+ #
395
+ def self.colors
396
+ @colors ||= {}.update(DEFAULT_COLORS)
397
+ end
398
+
399
+ #
400
+ # Return a string with the given color.
401
+ #
402
+ def self.colorize_string(str, color)
403
+ col, nocol = [color, :nothing].map { |key| Color.escape(key) }
404
+ col ? "#{col}#{str}#{nocol}" : str
405
+ end
406
+
407
+ #
408
+ # Colorize the results of inspect
409
+ #
410
+ def self.colorize(str)
411
+ begin
412
+ ret, nocol = '', Color.escape(:nothing)
413
+ Tokenizer.tokenize(str) do |tok, val|
414
+ # c = Color.escape(colors[tok])
415
+ ret << colorize_string(val, colors[tok])
416
+ end
417
+ ret
418
+ rescue
419
+ # catch any errors from the tokenizer (just in case)
420
+ str
421
+ end
422
+ end
423
+
424
+ #
425
+ # Enable colorized IRB results.
426
+ #
427
+ def self.enable(custom_colors = nil)
428
+ # if there's a better way to do this, I'm all ears.
429
+ ::IRB::Irb.class_eval do
430
+ alias :non_color_output_value :output_value
431
+
432
+ def output_value
433
+ if @context.inspect?
434
+ val = Colorize.colorize(@context.last_value.inspect)
435
+ printf @context.return_format, val
436
+ else
437
+ printf @context.return_format, @context.last_value
438
+ end
439
+ end
440
+ end
441
+
442
+ colors = custom_colors if custom_colors
443
+ end
444
+
445
+ #
446
+ # Disable colorized IRB results.
447
+ #
448
+ def self.disable
449
+ ::IRB::Irb.class_eval do
450
+ alias :output_value :non_color_output_value
451
+ end
452
+ end
453
+ end
454
+
455
+ #
456
+ # Convenient shortcut methods.
457
+ #
458
+ module Shortcuts
459
+ #
460
+ # Print object methods, sorted by name. (excluding methods that
461
+ # exist in the class Object) .
462
+ #
463
+ def po(o)
464
+ o.methods.sort - Object.methods
465
+ end
466
+
467
+ #
468
+ # Print object constants, sorted by name.
469
+ #
470
+ def poc(o)
471
+ o.constants.sort
472
+ end
473
+ end
474
+
475
+ #
476
+ # Convenient shortcut for ri
477
+ #
478
+ module RiShortcut
479
+ def self.init
480
+ Kernel.class_eval {
481
+ def ri(arg)
482
+ puts `ri '#{arg}'`
483
+ end
484
+ }
485
+
486
+ Module.instance_eval {
487
+ def ri(meth=nil)
488
+ if meth
489
+ if instance_methods(false).include? meth.to_s
490
+ puts `ri #{self}##{meth}`
491
+ else
492
+ super
493
+ end
494
+ else
495
+ puts `ri #{self}`
496
+ end
497
+ end
498
+ }
499
+ end
500
+ end
501
+
502
+
503
+
504
+ #
505
+ # Enable color results.
506
+ #
507
+ def self.colorize(custom_colors = nil)
508
+ Colorize.enable(custom_colors)
509
+ end
510
+
511
+ #
512
+ # Load everything except color.
513
+ #
514
+ def self.init(opt = nil)
515
+ # make sure opt isn't nil
516
+ opt ||= {}
517
+
518
+ # load internal irb/ruby features
519
+ Internals.init(opt) unless opt && opt[:skip_internals]
520
+
521
+ # load the history
522
+ History.new(opt) unless opt && opt[:skip_history]
523
+
524
+ # load shortcuts
525
+ unless opt && opt[:skip_shortcuts]
526
+ # load ri shortcuts
527
+ RiShortcut.init
528
+
529
+ # include common shortcuts
530
+ Object.class_eval { include Shortcuts }
531
+ end
532
+
533
+ colorize(opt[:colors]) if opt && opt[:init_colors]
534
+ end
535
+ end
536
+