rak-eugeneching 1.5

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/bin/rak ADDED
@@ -0,0 +1,827 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: binary
3
+
4
+ # ------------------------------------------------------------------------------
5
+ # Rak Gem.
6
+ #
7
+ # Rak is a grep replacement in pure Ruby. It accepts Ruby syntax regular
8
+ # expressions and automatically recurses directories, skipping .svn/, .cvs/,
9
+ # pkg/ and more things you don't care about. It is based on the Perl tool
10
+ # ack by Andy Lester.
11
+ #
12
+ # Rak was originally implemented by Daniel Lucraft, whose blog may be found
13
+ # at http://danlucraft.com/blog/. Full credits to the original version to
14
+ # him. His project may be found at https://github.com/danlucraft/rak.
15
+ #
16
+ # This is my improvement of the original implementation of Rak, for
17
+ # stability, as well as UI improvements. I also added column handling so
18
+ # that editors interacting with Rak, or otherwise, may point directly to
19
+ # the line and word that was Rak'ed for. Column is only shown for the first
20
+ # matching word, if there are multiple, for a given line. Finally,
21
+ # ignore-case mode is default, as I feel that such a case is more common.
22
+ #
23
+ # -- Author
24
+ #
25
+ # Eugene Ching (codejury)
26
+ # Email: eugene@enegue.com
27
+ # Web: www.codejury.com
28
+ # Twitter: @eugeneching
29
+ #
30
+ # ------------------------------------------------------------------------------
31
+
32
+ require 'rubygems'
33
+ require 'colorize'
34
+ require "pathname"
35
+
36
+ if RUBY_PLATFORM =~ /mswin|mingw/
37
+ begin
38
+ require 'win32console'
39
+ rescue LoadError
40
+ # ARGV << "--nocolour"
41
+ end
42
+ end
43
+
44
+ require 'getoptlong'
45
+
46
+ begin
47
+ require 'oniguruma'
48
+ require 'inline'
49
+ $use_onig = true
50
+ rescue LoadError
51
+ $use_onig = false
52
+ end
53
+
54
+ class String
55
+ def expand_tabs(shift=0)
56
+ expanded = dup
57
+ 1 while expanded.sub!(/\t+/){ " "*($&.size*8 - ($`.size+shift)%8) }
58
+ expanded
59
+ end
60
+ end
61
+
62
+ class Rak
63
+ VERSION = "1.4"
64
+ VERSION_INFO=<<END
65
+ rak #{VERSION}
66
+
67
+ Copyright 2008-#{Time.now.year} Daniel Lucraft, all rights reserved.
68
+ Based on the perl tool 'ack' by Andy Lester.
69
+
70
+ This program is free software; you can redistribute it and/or modify it
71
+ under the same terms as Ruby.
72
+ END
73
+
74
+ FILE_TYPES = {
75
+ :actionscript => %w( .as .mxml ),
76
+ :ada => %w( .ada .adb .ads ),
77
+ :asm => %w( .S .asm .s ),
78
+ :awk => %w( .awk ),
79
+ :batch => %w( .bat .cmd ),
80
+ :cc => %w( .c .h .l .xs .y ),
81
+ :cfmx => %w( .cfc .cfm .cfml ),
82
+ :cpp => %w( .C .H .cc .cpp .cxx .h .hh .hpp .hxx .m ),
83
+ :csharp => %w( .cs ),
84
+ :css => %w( .css ),
85
+ :elisp => %w( .el ),
86
+ :erlang => %w( .erl .hrl ),
87
+ :fortran => %w( .f .f03 .f77 .f90 .f95 .for .fpp .ftn ),
88
+ :haskell => %w( .hs .lhs ),
89
+ :hh => %w( .h ),
90
+ :html => %w( .htm .html .shtml .xhtml ),
91
+ :java => %w( .java .properties properties ),
92
+ :js => %w( .js ),
93
+ :jsp => %w( .jhtm .jhtml .jsp .jspx ),
94
+ :lisp => %w( .lisp .lsp ),
95
+ :lua => %w( .lua ),
96
+ :make => %w( .mk Makefile ),
97
+ :mason => %w( .mas .mhtml .mpl .mtxt ),
98
+ :matlab => %w( .m .oct ),
99
+ :objc => %w( .h .m ),
100
+ :objcpp => %w( .h .mm ),
101
+ :ocaml => %w( .ml .mli .mll .mly ),
102
+ :parrot => %w( .ops .pasm .pg .pir .pmc .pod .tg ),
103
+ :perl => %w( .pl .pm .pod .t ),
104
+ :php => %w( .php .php3 .php4 .php5 .phpt .phtml ),
105
+ :plone => %w( .cpt .cpy .metadata .pt .py ),
106
+ :prolog => %w( .ecl .pl ),
107
+ :python => %w( .py ),
108
+ :ruby => %w( .erb .haml .rake .rb .rhtml .rjs .rxml Rakefile Gemfile ),
109
+ :scala => %w( .scala ),
110
+ :scheme => %w( .scm .ss ),
111
+ :sed => %w( .sed ),
112
+ :shell => %w( .bash .csh .ksh .sh .tcsh .zsh ),
113
+ :smalltalk => %w( .st ),
114
+ :sml => %w( .sml .cm .sig ),
115
+ :sql => %w( .ctl .sql ),
116
+ :tcl => %w( .itcl .itk .tcl ),
117
+ :tex => %w( .cls .sty .tex ),
118
+ :text => %w( .text .txt README ),
119
+ :tt => %w( .tt .tt2 .ttml ),
120
+ :vala => %w( .vala .vapi ),
121
+ :vb => %w( .bas .cls .ctl .frm .resx .vb ),
122
+ :verilog => %w( .v .verilog ),
123
+ :vim => %w( .vim ),
124
+ :xml => %w( .dtd .ent .xml .xslt ),
125
+ :yaml => %w( .yaml .yml ),
126
+ }
127
+
128
+ VC_DIRS = %w(blib CVS _darcs .git .pc RCS SCCS .svn pkg)
129
+
130
+ class << self
131
+ attr_reader :opt
132
+ end
133
+
134
+ def self.compile_regexp(str)
135
+ if $use_onig
136
+ Oniguruma::ORegexp.new(str)
137
+ else
138
+ Regexp.new(str)
139
+ end
140
+ end
141
+
142
+ def self.search
143
+ @opt = {}
144
+
145
+ # file types
146
+ opt[:includes] = []
147
+ opt[:excludes] = []
148
+
149
+ FILE_TYPES.each do |type, exts|
150
+ if ARGV.delete('--'+type.to_s)
151
+ exts.each do |ext|
152
+ opt[:includes] << ext
153
+ end
154
+ end
155
+ if ARGV.delete('--no'+type.to_s)
156
+ exts.each do |ext|
157
+ opt[:excludes] << ext
158
+ end
159
+ end
160
+ end
161
+
162
+ opts = GetoptLong.new(
163
+ [ '--help', GetoptLong::OPTIONAL_ARGUMENT ],
164
+ [ '--max-count', '-m', GetoptLong::REQUIRED_ARGUMENT ],
165
+ [ '--files', '-f', GetoptLong::NO_ARGUMENT ],
166
+ [ '--skipped', GetoptLong::NO_ARGUMENT ],
167
+ [ '--output', GetoptLong::REQUIRED_ARGUMENT ],
168
+ [ '--version', GetoptLong::NO_ARGUMENT ],
169
+ [ '-c', '--count', GetoptLong::NO_ARGUMENT ],
170
+ [ '-h', '--no-filename', GetoptLong::NO_ARGUMENT ],
171
+ [ '-i', '--ignore-case', GetoptLong::NO_ARGUMENT ],
172
+ [ '-I', '--case-sensitive', GetoptLong::NO_ARGUMENT ],
173
+ [ '-v', '--invert-match', GetoptLong::NO_ARGUMENT ],
174
+ [ '-n', GetoptLong::NO_ARGUMENT ],
175
+ [ '-Q', '--literal', GetoptLong::NO_ARGUMENT ],
176
+ [ '-o', GetoptLong::NO_ARGUMENT ],
177
+ [ '-w', '--word-regexp', GetoptLong::NO_ARGUMENT ],
178
+ [ '--group', GetoptLong::NO_ARGUMENT ],
179
+ [ '--nogroup', GetoptLong::NO_ARGUMENT ],
180
+ [ '-l', '--files-with-matches', GetoptLong::NO_ARGUMENT ],
181
+ [ '-L', '--files-without-matches', GetoptLong::NO_ARGUMENT ],
182
+ [ '--passthru', GetoptLong::NO_ARGUMENT ],
183
+ [ '-H', '--with-filename', GetoptLong::NO_ARGUMENT ],
184
+ [ '--colour', GetoptLong::NO_ARGUMENT ],
185
+ [ '--nocolour', GetoptLong::NO_ARGUMENT ],
186
+ [ '--color', GetoptLong::NO_ARGUMENT ],
187
+ [ '--nocolor', GetoptLong::NO_ARGUMENT ],
188
+ [ '-a', '--all', GetoptLong::NO_ARGUMENT ],
189
+ [ '--type', GetoptLong::REQUIRED_ARGUMENT ],
190
+ [ '--sort-files', GetoptLong::NO_ARGUMENT ],
191
+ [ '--follow', GetoptLong::NO_ARGUMENT ],
192
+ [ '--nofollow', GetoptLong::NO_ARGUMENT ],
193
+ [ '--after-context', '-A', GetoptLong::REQUIRED_ARGUMENT ],
194
+ [ '--before-context', '-B', GetoptLong::REQUIRED_ARGUMENT ],
195
+ [ '--context', '-C', GetoptLong::OPTIONAL_ARGUMENT ],
196
+ [ '-g', GetoptLong::REQUIRED_ARGUMENT ],
197
+ [ '-k', GetoptLong::REQUIRED_ARGUMENT ],
198
+ [ '-x', '--line-regexp', GetoptLong::NO_ARGUMENT ],
199
+ [ '-s', '--line-start', GetoptLong::NO_ARGUMENT ],
200
+ [ '-e', '--line-end', GetoptLong::NO_ARGUMENT ],
201
+ [ '--eval', GetoptLong::REQUIRED_ARGUMENT ]
202
+ )
203
+
204
+ opt[:max_count] = nil
205
+ opt[:only_print_filelist] = false
206
+ opt[:print_filename] = true
207
+ opt[:print_line_number] = true
208
+ opt[:print_output] = nil
209
+ opt[:print_highlighted] = true
210
+ opt[:print_num_matches] = false
211
+ opt[:ignore_case] = true
212
+ opt[:invert_match] = false
213
+ opt[:descend] = true
214
+ opt[:literal] = false
215
+ opt[:print_match] = false
216
+ opt[:match_whole_words] = false
217
+ opt[:match_whole_lines] = false
218
+ opt[:match_line_starts] = false
219
+ opt[:match_line_ends] = false
220
+ opt[:print_file_each_line] = false
221
+ opt[:print_file_if_match] = false
222
+ opt[:print_file_if_no_match] = false
223
+ opt[:print_entire_line_if_no_match] = false
224
+ opt[:colour] = true
225
+ opt[:all_files] = false
226
+ opt[:sort_files] = false
227
+ opt[:follow_symlinks] = false
228
+ opt[:after_context] = 0
229
+ opt[:before_context] = 0
230
+ opt[:collect_context] = false
231
+ opt[:filename_regex] = nil
232
+ opt[:neg_filename_regex] = nil
233
+ opt[:reverse_relevance] = false
234
+ opt[:eval] = nil
235
+
236
+ # if redirected (RAK_TEST allows us to redirect in testing and still
237
+ # get the non-redirected defaults).
238
+ unless STDOUT.isatty or ENV['RAK_TEST'] == "true"
239
+ opt[:colour] = false
240
+ opt[:print_file_each_line] = true
241
+ opt[:print_filename] = false
242
+ opt[:print_line_number] = false
243
+ end
244
+
245
+ begin
246
+ opts.each do |option, arg|
247
+ case option
248
+ when '--help'
249
+ if arg == ""
250
+ puts USAGE_HELP
251
+ elsif arg == "types" or arg == "type"
252
+ puts TYPES_HELP
253
+ FILE_TYPES.sort_by{|type,exts| type.to_s}.each do |type, exts|
254
+ puts " --[no]%-13s %s\n" % [type, exts.join(" ")]
255
+ end
256
+ end
257
+ exit
258
+ when '--eval'
259
+ opt[:eval] = arg
260
+ when '--max-count'
261
+ opt[:max_count] = arg.to_i
262
+ when '--files'
263
+ opt[:only_print_filelist] = true
264
+ when '--skipped'
265
+ opt[:reverse_relevance] = true
266
+ opt[:only_print_filelist] = true
267
+ when '--output'
268
+ opt[:print_filename] = false
269
+ opt[:print_line_number] = false
270
+ opt[:print_output] = arg
271
+ opt[:print_highlighted] = false
272
+ when '--version'
273
+ puts VERSION_INFO
274
+ exit
275
+ when '-c'
276
+ opt[:print_num_matches] = true
277
+ opt[:print_filename] = false
278
+ opt[:print_line_number] = false
279
+ opt[:print_highlighted] = false
280
+ when '-h'
281
+ opt[:print_filename] = false
282
+ opt[:print_line_number] = false
283
+ opt[:print_file_each_line] = false
284
+ when '-i'
285
+ opt[:ignore_case] = true
286
+ when '-I'
287
+ opt[:ignore_case] = false
288
+ when '-v'
289
+ opt[:invert_match] = true
290
+ when '-n'
291
+ opt[:descend] = false
292
+ when '-Q'
293
+ opt[:literal] = true
294
+ when '-o'
295
+ opt[:print_match] = true
296
+ opt[:print_filename] = false
297
+ opt[:print_line_number] = false
298
+ opt[:print_highlighted] = false
299
+ when '-w'
300
+ opt[:match_whole_words] = true
301
+ when '--group'
302
+ opt[:print_filename] = true
303
+ opt[:print_file_each_line] = false
304
+ when '--nogroup'
305
+ opt[:print_file_each_line] = true
306
+ opt[:print_filename] = false
307
+ opt[:print_line_number] = false
308
+ when '-l'
309
+ opt[:print_filename] = false
310
+ opt[:print_line_number] = false
311
+ opt[:print_highlighted] = false
312
+ opt[:print_file_if_match] = true
313
+ when '-L'
314
+ opt[:print_filename] = false
315
+ opt[:print_line_number] = false
316
+ opt[:print_highlighted] = false
317
+ opt[:print_file_if_no_match] = true
318
+ when '--passthru'
319
+ opt[:print_entire_line_if_no_match] = true
320
+ when '-H'
321
+ opt[:print_filename] = true
322
+ opt[:print_line_number] = true
323
+ when '--nocolour', '--nocolor'
324
+ opt[:colour] = false
325
+ when '--colour', '--color'
326
+ opt[:colour] = true
327
+ when '-a'
328
+ opt[:all_files] = true
329
+ when '--type'
330
+ if arg[0..1] == "no"
331
+ type = arg[2..-1]
332
+ arr = opt[:excludes]
333
+ else
334
+ type = arg
335
+ arr = opt[:includes]
336
+ end
337
+ exts = FILE_TYPES[type.intern]
338
+ unknown_type(type) unless exts
339
+ exts.each do |ext|
340
+ arr << ext
341
+ end
342
+ when '--sort-files'
343
+ opt[:sort_files] = true
344
+ when '--follow'
345
+ opt[:follow_symlinks] = true
346
+ when '--nofollow'
347
+ opt[:follow_symlinks] = false
348
+ when '--after-context'
349
+ opt[:use_context] = true
350
+ opt[:after_context] = arg.to_i
351
+ when '--before-context'
352
+ opt[:use_context] = true
353
+ opt[:before_context] = arg.to_i
354
+ when '--context'
355
+ opt[:use_context] = true
356
+ if arg == ""
357
+ val = 2
358
+ else
359
+ val = arg.to_i
360
+ end
361
+ opt[:before_context] = val
362
+ opt[:after_context] = val
363
+ when '-g'
364
+ opt[:filename_regex] = compile_regexp(arg)
365
+ when '-k'
366
+ opt[:neg_filename_regex] = compile_regexp(arg)
367
+ when '-x'
368
+ opt[:match_whole_lines] = true
369
+ when '-s'
370
+ opt[:match_line_starts] = true
371
+ when '-e'
372
+ opt[:match_line_ends] = true
373
+ end
374
+ end
375
+ rescue GetoptLong::InvalidOption => ex
376
+ puts "rak: see rak --help for usage."
377
+ exit
378
+ rescue SystemExit
379
+ exit
380
+ end
381
+
382
+ if opt[:only_print_filelist]
383
+ each_file(ARGV) do |fn|
384
+ puts fn
385
+ end
386
+ elsif ARGV.empty? and !opt[:eval]
387
+ puts USAGE_HELP
388
+ exit
389
+ else
390
+ unless opt[:eval]
391
+ re = compile_pattern(ARGV.shift)
392
+ end
393
+ compiled = false
394
+ file_separator = ""
395
+ each_file(ARGV) do |fn|
396
+ # each_file might turn off printing file name, but only before first yield
397
+ unless compiled
398
+ compile_match_file
399
+ compiled = true
400
+ end
401
+ match_file(re, fn, file_separator)
402
+ end
403
+ end
404
+ end
405
+
406
+ def self.extension_regexp(extensions)
407
+ return nil if extensions.empty?
408
+ Regexp.compile('(?:' + extensions.map{|x| Regexp.escape(x)}.join("|") + ')\z')
409
+ end
410
+
411
+ def self.file_relevant?(fn)
412
+ # These don't change at this point
413
+ @types_rx ||= extension_regexp(FILE_TYPES.values.flatten)
414
+ @includes_rx ||= extension_regexp(opt[:includes])
415
+ @excludes_rx ||= extension_regexp(opt[:excludes])
416
+
417
+ ext = fn.basename.to_s
418
+ ext = shebang_matches(fn) unless ext =~ @types_rx
419
+
420
+ return false if !opt[:all_files] and !ext or fn.to_s =~ /[~#]\z/
421
+ return false if @includes_rx and (ext||"") !~ @includes_rx
422
+ return false if @excludes_rx and (ext||"") =~ @excludes_rx
423
+ return false if opt[:filename_regex] and fn.to_s !~ opt[:filename_regex]
424
+ return false if opt[:neg_filename_regex] and fn.to_s =~ opt[:neg_filename_regex]
425
+ return true
426
+ end
427
+
428
+ def self.find_all_files(path, &blk)
429
+ return if path.socket?
430
+ return unless path.readable?
431
+
432
+ if path.file?
433
+ relevant = file_relevant?(path)
434
+ relevant = !relevant if opt[:reverse_relevance]
435
+ yield(path.to_s.sub(/\A\.\/+/, "").gsub(/\/+/, "/")) if relevant
436
+ elsif path.directory?
437
+ path.children.each do |fn|
438
+ next if VC_DIRS.any?{|vc| vc == fn.basename.to_s}
439
+ next if fn.directory? and not opt[:descend]
440
+ next if fn.symlink? and not opt[:follow_symlinks]
441
+ find_all_files(fn, &blk)
442
+ end
443
+ end
444
+ end
445
+
446
+ def self.each_file(todo, &blk)
447
+ todo = todo.map{|path| Pathname(path)}
448
+ if todo.empty?
449
+ if STDIN.isatty
450
+ todo = [Pathname(".")]
451
+ else
452
+ opt[:print_filename] = false
453
+ yield(STDIN)
454
+ return
455
+ end
456
+ elsif todo.size == 1 and todo[0].file?
457
+ opt[:print_filename] = false
458
+ end
459
+
460
+ if opt[:sort_files]
461
+ sortme = []
462
+ todo.each do |item|
463
+ find_all_files(item) do |fn|
464
+ sortme << fn
465
+ end
466
+ end
467
+ sortme.sort_by{|fn|fn.downcase}.each(&blk)
468
+ else
469
+ todo.each do |item|
470
+ find_all_files(item, &blk)
471
+ end
472
+ end
473
+ end
474
+
475
+ def self.shebang_matches(fn)
476
+ begin
477
+ line = fn.open.readline
478
+ if line =~ /^#!/
479
+ if line =~ /\b(ruby|perl|php|python|make)[0-9.]*\b/i
480
+ FILE_TYPES[$1.downcase.intern].first
481
+ elsif line =~ /\b(sh|bash|csh|ksh|zsh)\b/
482
+ ".sh"
483
+ else
484
+ ".other"
485
+ end
486
+ elsif line =~ /^<\?xml\b/
487
+ ".xml"
488
+ else
489
+ false
490
+ end
491
+ rescue
492
+ nil
493
+ end
494
+ end
495
+
496
+ def self.compile_pattern(str)
497
+ if opt[:literal]
498
+ str = Regexp.quote(str)
499
+ end
500
+ if opt[:match_whole_words]
501
+ str = "\\b(?:" + str + ")\\b"
502
+ end
503
+ if opt[:match_whole_lines]
504
+ str = "^(?:" + str + ")$"
505
+ end
506
+ if opt[:match_line_starts]
507
+ str = "^(?:" + str + ")"
508
+ end
509
+ if opt[:match_line_ends]
510
+ str = "(?:" + str + ")$"
511
+ end
512
+ str = str.gsub("~bang", "!").gsub("~ques", "?")
513
+ if str.respond_to?(:force_encoding)
514
+ str.force_encoding("ASCII-8BIT")
515
+ end
516
+ if $use_onig
517
+ flags = opt[:ignore_case] ? Oniguruma::OPTION_IGNORECASE : nil
518
+ Oniguruma::ORegexp.new(str, :options => flags)
519
+ else
520
+ flags = opt[:ignore_case] ? Regexp::IGNORECASE : nil
521
+ Regexp.new(str, flags)
522
+ end
523
+ end
524
+
525
+ # this is tricky thing. Ignore the "code <<" to see the
526
+ # logic. Because the options never change we conditionally compile
527
+ # the match method based on them. This is gives a 2x speedup over
528
+ # the alternative.
529
+ def self.compile_match_file
530
+ needs_no_more_matches = (opt[:max_count] or opt[:eval])
531
+ needs_inverse_count = (opt[:print_num_matches] and opt[:invert_match])
532
+
533
+ code = []
534
+ code << %{def self.match_file(re, fn, file_separator) }
535
+ code << %{ displayed_filename = false }
536
+ code << %{ count = 0 }
537
+ if needs_inverse_count
538
+ code << %{ inverse_count = 0 }
539
+ end
540
+ code << %{ print_num = 0 }
541
+ if needs_no_more_matches
542
+ code << %{ no_more_matches = false }
543
+ end
544
+ if opt[:print_file_each_line]
545
+ code << %{ fn_str = (fn.is_a?(String) ? fn + ":" : "") }
546
+ end
547
+ if opt[:before_context] > 0
548
+ code << %{ before_context = [] }
549
+ end
550
+ code << %{ if fn.is_a? String }
551
+ code << %{ f = File.open(fn, "rb") }
552
+ code << %{ elsif fn.is_a? IO }
553
+ code << %{ f = fn }
554
+ code << %{ end }
555
+
556
+ code << %{ f.each_line do |line| }
557
+ code << %{ matches = [] }
558
+
559
+ code << %{ unless no_more_matches } if needs_no_more_matches
560
+ if opt[:eval]
561
+ code << %{ $_ = line.chomp }
562
+ code << %{ $~ = nil }
563
+ code << %{ no_more_matches = true }
564
+ code << %{ eval_executed = false }
565
+ code << %{ loop do }
566
+ code << %{ if eval_executed }
567
+ code << %{ no_more_matches = false }
568
+ code << %{ break }
569
+ code << %{ end }
570
+ code << %{ eval_executed = true }
571
+ code << %{ #{opt[:eval]} }
572
+ code << %{ if matches.empty? }
573
+ code << %{ $_ =~ /^.*$/ unless $~ }
574
+ code << %{ matches << $~ }
575
+ code << %{ end }
576
+ code << %{ line = $_ }
577
+ code << %{ no_more_matches = false }
578
+ code << %{ break }
579
+ code << %{ end }
580
+ else
581
+ if opt[:print_output]
582
+ code << %{ line.scan(re){ matches << eval(opt[:print_output]) } }
583
+ else
584
+ code << %{ line.scan(re){ matches << $~ } }
585
+ end
586
+ end
587
+ if opt[:print_match]
588
+ code << %{ matches.each{|md| puts md.to_s} }
589
+ end
590
+ code << %{ count += matches.size }
591
+ code << %{ end } if needs_no_more_matches
592
+ if opt[:invert_match]
593
+ if opt[:print_filename]
594
+ code << %{ unless displayed_filename }
595
+ code << %{ print file_separator }
596
+ code << %{ file_separator.replace("\\n") }
597
+ code << %{ puts fn.cyan if opt[:colour] }
598
+ code << %{ puts fn if not opt[:colour] }
599
+ code << %{ displayed_filename = true }
600
+ code << %{ end }
601
+ end
602
+ code << %{ if matches.empty? }
603
+ if needs_inverse_count
604
+ code << %{ inverse_count += 1 }
605
+ end
606
+ if opt[:print_highlighted]
607
+ if opt[:print_line_number]
608
+ code << %{ print "\#{$..to_s.rjust(4)} ".yellow + " | ".yellow if opt[:colour] }
609
+ code << %{ print "\#{$..to_s.rjust(4)} " + " | " if not opt[:colour] }
610
+ end
611
+ code << %{ puts line.expand_tabs }
612
+ end
613
+ code << %{ end }
614
+ else
615
+ code << %{ if matches.empty? }
616
+ if opt[:print_entire_line_if_no_match]
617
+ code << %{ puts line }
618
+ end
619
+ if opt[:use_context]
620
+
621
+ # Handle the before-context
622
+
623
+ if opt[:before_context] > 0
624
+ code << %{ if print_num == 0 }
625
+ code << %{ before_context << [$., line] }
626
+ code << %{ if before_context.length > #{opt[:before_context]} }
627
+ code << %{ before_context.shift }
628
+ code << %{ end }
629
+ code << %{ end }
630
+ end
631
+ code << %{ if print_num > 0 }
632
+ code << %{ print_num -= 1 }
633
+ if opt[:print_highlighted]
634
+
635
+ # Handle the after-context
636
+
637
+ if opt[:print_line_number]
638
+ code << %{ print "\#{$..to_s.rjust(4)} ".yellow + " " + "v ".yellow if opt[:colour] }
639
+ code << %{ print "\#{$..to_s.rjust(4)} " + " " + "v " if not opt[:colour] }
640
+ end
641
+ code << %{ puts line.expand_tabs }
642
+ code << %{ end }
643
+ end
644
+ end
645
+ code << %{ else }
646
+ code << %{ print_num = opt[:after_context] }
647
+ if opt[:print_filename]
648
+ code << %{ unless displayed_filename }
649
+ code << %{ print file_separator }
650
+ code << %{ file_separator.replace("\\n") }
651
+ code << %{ puts fn.cyan if opt[:colour] }
652
+ code << %{ puts fn if not opt[:colour] }
653
+ code << %{ displayed_filename = true }
654
+ code << %{ end }
655
+ end
656
+ if opt[:before_context] > 0
657
+ code << %{ before_context.each do |before_i, before_line| }
658
+ if opt[:print_line_number]
659
+ code << %{ print "\#{before_i.to_s.rjust(4)}".yellow + " " + " ^ ".yellow if opt[:colour] }
660
+ code << %{ print "\#{before_i.to_s.rjust(4)}" + " " + " ^ ".ljust(5) if not opt[:colour] }
661
+ end
662
+ code << %{ puts before_line.expand_tabs }
663
+ code << %{ end }
664
+ code << %{ before_context = [] }
665
+ end
666
+ if opt[:print_output]
667
+ code << %{ matches.each {|m| puts m} }
668
+ end
669
+ if opt[:print_highlighted]
670
+
671
+ # Print all information on individual lines. This acts like a "raw mode"
672
+ # that is easily parsable.
673
+
674
+ if opt[:print_line_number]
675
+ code << %{ first_column_of_match = matches[0].begin(0) + 1}
676
+ code << %{ print "\#{$..to_s}".rjust(4).yellow + ":" + "\#{first_column_of_match}".ljust(4).blue + " | ".yellow if opt[:colour] }
677
+ code << %{ print "\#{$..to_s}".rjust(4) + ":" + "\#{first_column_of_match}".ljust(4) + " | " if not opt[:colour] }
678
+ elsif opt[:print_file_each_line]
679
+ code << %{ first_column_of_match = matches[0].begin(0) + 1}
680
+ code << %{ print (fn_str.cyan + ("\#{$.}".yellow + ":" + "\#{first_column_of_match}".blue)) + " | ".yellow if opt[:colour] }
681
+ code << %{ print (fn_str + ("\#{$.}" + ":" + "\#{first_column_of_match}")) + " | " if not opt[:colour] }
682
+ end
683
+ code << %{ print_highlighted(line, matches) }
684
+ end
685
+ code << %{ end }
686
+ if opt[:max_count]
687
+ code << %{ no_more_matches = true if count >= opt[:max_count] }
688
+ end
689
+ if needs_no_more_matches and not needs_inverse_count
690
+ code << %{ break if no_more_matches and print_num == 0 }
691
+ end
692
+ end
693
+ code << %{ end }
694
+ code << %{ f.close if f === File }
695
+ if opt[:print_num_matches]
696
+ if opt[:invert_match]
697
+ code << %{ puts "\#{fn}: \#{inverse_count} " }
698
+ else
699
+ code << %{ puts "\#{fn}: \#{count} " if count > 0 }
700
+ end
701
+ end
702
+ if opt[:print_file_if_match]
703
+ code << %{ puts fn if count > 0 }
704
+ end
705
+ if opt[:print_file_if_no_match]
706
+ code << %{ puts fn if count == 0 }
707
+ end
708
+ code << %{end }
709
+ module_eval code.join("\n"), "#{__FILE__} (#{__LINE__} )"
710
+ end
711
+
712
+ def self.print_highlighted(line, matches)
713
+ matches = matches.map{|md| md.offset(0)}
714
+ cuts = [0, matches, line.size].flatten
715
+
716
+ column = 0
717
+ final_string = ''
718
+ matches.each do |str_start, str_end|
719
+ matched_string ||= line[str_start...str_end]
720
+ final_string << line[column...str_start]
721
+ final_string << matched_string.red if opt[:colour]
722
+ final_string << matched_string if not opt[:colour]
723
+ column = str_end
724
+ end
725
+ final_string << line[column..line.size]
726
+
727
+ puts final_string
728
+ print "\n" unless line[-1,1] == "\n"
729
+ end
730
+
731
+ def self.unknown_type(type)
732
+ puts "rak: Unknown --type \"#{type}\""
733
+ puts "rak: See rak --help types"
734
+ exit
735
+ end
736
+ end
737
+
738
+ USAGE_HELP=<<END
739
+ Usage: rak [OPTION]... PATTERN [FILES]
740
+
741
+ Search for PATTERN in each source file in the tree from cwd on down.
742
+ If [FILES] is specified, then only those files/directories are checked.
743
+ rak will search STDIN only if no FILES are specified.
744
+
745
+ Example: rak -i select
746
+
747
+ Searching:
748
+ -i, --ignore-case Ignore case distinctions
749
+ -v, --invert-match Invert match: select non-matching lines
750
+ -w, --word-regexp Force PATTERN to match only whole words
751
+ -x, --line-regexp Force PATTERN to match only whole lines
752
+ -Q, --literal Quote all metacharacters; expr is literal
753
+ -s, --line-start Match only at the start of a line
754
+ -e, --line-end Match only at the end of a line
755
+
756
+ --eval CODE Match with Ruby code instead of regex.
757
+ [Highly experimental]
758
+
759
+ Search output:
760
+ -l, --files-with-matches
761
+ Only print filenames containing matches
762
+ -L, --files-without-matches
763
+ Only print filenames with no match
764
+ -o Show only the part of a line matching PATTERN
765
+ (turns off text highlighting)
766
+ --passthru Print all lines, whether matching or not
767
+ --output=expr Output the evaluation of expr for each line
768
+ (turns off text highlighting)
769
+ -m, --max-count=NUM Stop searching in a file after NUM matches
770
+ -H, --with-filename Print the filename for each match
771
+ -h, --no-filename Suppress the prefixing filename on output
772
+ -c, --count Show number of lines matching per file
773
+
774
+ --group Group matches by file name.
775
+ (default: on when used interactively)
776
+ --nogroup One result per line, including filename, like grep
777
+ (default: on when the output is redirected)
778
+
779
+ --[no]colour Highlight the matching text (default: on unless
780
+ output is redirected, or on Windows)
781
+
782
+ -A NUM, --after-context=NUM
783
+ Print NUM lines of trailing context after matching
784
+ lines.
785
+ -B NUM, --before-context=NUM
786
+ Print NUM lines of leading context before matching
787
+ lines.
788
+ -C [NUM], --context[=NUM]
789
+ Print NUM lines (default 2) of output context.
790
+
791
+ File finding:
792
+ -f, --files Only print the files found, without searching.
793
+ The PATTERN must not be specified.
794
+ --skipped Like -f but print files that were skipped.
795
+ --sort-files Sort the found files lexically.
796
+
797
+ File inclusion/exclusion:
798
+ -n No descending into subdirectories
799
+ -g REGEX Only search in files matching REGEX.
800
+ -k REGEX Only search in files not matching REGEX.
801
+ -a, --all All files, regardless of extension (but still skips
802
+ blib, pkg, CVS, _darcs, .git, .pc, RCS, SCCS and .svn dirs)
803
+ --ruby Include only Ruby files.
804
+ --type=ruby Include only Ruby files.
805
+ --noruby Exclude Ruby files.
806
+ --type=noruby Exclude Ruby files.
807
+ See "rak --help type" for supported filetypes.
808
+ --[no]follow Follow symlinks. Default is off.
809
+
810
+ Miscellaneous:
811
+ --help This help
812
+ --version Display version & copyright
813
+ END
814
+
815
+ TYPES_HELP=<<END
816
+ Usage: rak [OPTION]... PATTERN [FILES]
817
+
818
+ The following is the list of filetypes supported by rak. You can
819
+ specify a file type with the --type=TYPE format, or the --TYPE
820
+ format. For example, both --type=ruby and --ruby work.
821
+
822
+ Note that some extensions may appear in multiple types. For example,
823
+ .pod files are both Perl and Parrot.
824
+
825
+ END
826
+
827
+ Rak.search