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.
- data/COPYING +20 -0
- data/ChangeLog +29 -0
- data/README +452 -0
- data/Rakefile +115 -0
- data/_irbrc +27 -0
- data/dylansm-wirble.gemspec +40 -0
- data/lib/wirble.rb +536 -0
- data/setup.rb +1596 -0
- 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
|
+
|