rak 1.0 → 1.2

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 (6) hide show
  1. data/bin/rak +281 -351
  2. data/lib/rak.rb +1 -1
  3. data/spec/help_spec.rb +33 -27
  4. data/spec/rak_spec.rb +435 -363
  5. metadata +31 -61
  6. data/Rakefile +0 -14
data/bin/rak CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # encoding: binary
2
3
 
3
4
  require 'rubygems'
5
+ require "pathname"
4
6
 
5
7
  if RUBY_PLATFORM =~ /mswin|mingw/
6
8
  begin
@@ -20,8 +22,16 @@ rescue LoadError
20
22
  $use_onig = false
21
23
  end
22
24
 
25
+ class String
26
+ def expand_tabs(shift=0)
27
+ expanded = dup
28
+ 1 while expanded.sub!(/\t+/){ " "*($&.size*8 - ($`.size+shift)%8) }
29
+ expanded
30
+ end
31
+ end
32
+
23
33
  class Rak
24
- VERSION = "0.9"
34
+ VERSION = "1.2"
25
35
 
26
36
  FILE_COLOUR = "\033[1;31m"
27
37
  MATCH_COLOUR = "\033[1;37m\033[41m"
@@ -30,7 +40,7 @@ class Rak
30
40
  VERSION_INFO=<<END
31
41
  rak #{VERSION}
32
42
 
33
- Copyright 2008 Daniel Lucraft, all rights reserved.
43
+ Copyright 2008-#{Time.now.year} Daniel Lucraft, all rights reserved.
34
44
  Based on the perl tool 'ack' by Andy Lester.
35
45
 
36
46
  This program is free software; you can redistribute it and/or modify it
@@ -38,40 +48,57 @@ under the same terms as Ruby.
38
48
  END
39
49
 
40
50
  FILE_TYPES = {
41
- :asm => %w( .s .S ),
42
- :cc => %w( .c .h .xs ),
43
- :cpp => %w( .cpp .cc .m .h .hpp .C .H ),
44
- :csharp => %w( .cs ),
45
- :css => %w( .css ),
46
- :elisp => %w( .el ),
47
- :haskell => %w( .hs .lhs ),
48
- :hh => %w( .h ),
49
- :html => %w( .htm .html .shtml ),
50
- :lisp => %w( .lisp ),
51
- :java => %w( .java properties ),
52
- :js => %w( .js ),
53
- :jsp => %w( .jsp .jspx .jhtm .jhtml ),
54
- :make => %w( Makefile ),
55
- :mason => %w( .mas .mhtml .mpl .mtxt ),
56
- :ocaml => %w( .ml .mli ),
57
- :parrot => %w( .pir .pasm .pmc .ops .pod .pg .tg ),
58
- :perl => %w( .pl .pm .pod .t ),
59
- :php => %w( .php .phpt .php3 .php4 .php5 ),
60
- :prolog => %w( .pl .ecl ),
61
- :python => %w( .py ),
62
- :ruby => %w( .rb .rhtml .rjs .rxml Rakefile .rake .erb .haml),
63
- :scheme => %w( .scm ),
64
- :shell => %w( .sh .bash .csh .ksh .zsh ),
65
- :sql => %w( .sql .ctl ),
66
- :tcl => %w( .tcl ),
67
- :tex => %w( .tex .cls .sty ),
68
- :text => %w( .txt .text ),
69
- :tt => %w( .tt .tt2 .ttml ),
70
- :vala => %w( .vala .vapi ),
71
- :vb => %w( .bas .cls .frm .ctl .vb .resx ),
72
- :vim => %w( .vim ),
73
- :yaml => %w( .yaml .yml ),
74
- :xml => %w( .xml .dtd .xslt )
51
+ :actionscript => %w( .as .mxml ),
52
+ :ada => %w( .ada .adb .ads ),
53
+ :asm => %w( .S .asm .s ),
54
+ :awk => %w( .awk ),
55
+ :batch => %w( .bat .cmd ),
56
+ :cc => %w( .c .h .l .xs .y ),
57
+ :cfmx => %w( .cfc .cfm .cfml ),
58
+ :cpp => %w( .C .H .cc .cpp .cxx .h .hh .hpp .hxx .m ),
59
+ :csharp => %w( .cs ),
60
+ :css => %w( .css ),
61
+ :elisp => %w( .el ),
62
+ :erlang => %w( .erl .hrl ),
63
+ :fortran => %w( .f .f03 .f77 .f90 .f95 .for .fpp .ftn ),
64
+ :haskell => %w( .hs .lhs ),
65
+ :hh => %w( .h ),
66
+ :html => %w( .htm .html .shtml .xhtml ),
67
+ :java => %w( .java .properties properties ),
68
+ :js => %w( .js ),
69
+ :jsp => %w( .jhtm .jhtml .jsp .jspx ),
70
+ :lisp => %w( .lisp .lsp ),
71
+ :lua => %w( .lua ),
72
+ :make => %w( .mk Makefile ),
73
+ :mason => %w( .mas .mhtml .mpl .mtxt ),
74
+ :matlab => %w( .m .oct ),
75
+ :objc => %w( .h .m ),
76
+ :objcpp => %w( .h .mm ),
77
+ :ocaml => %w( .ml .mli .mll .mly ),
78
+ :parrot => %w( .ops .pasm .pg .pir .pmc .pod .tg ),
79
+ :perl => %w( .pl .pm .pod .t ),
80
+ :php => %w( .php .php3 .php4 .php5 .phpt .phtml ),
81
+ :plone => %w( .cpt .cpy .metadata .pt .py ),
82
+ :prolog => %w( .ecl .pl ),
83
+ :python => %w( .py ),
84
+ :ruby => %w( .erb .haml .rake .rb .rhtml .rjs .rxml Rakefile ),
85
+ :scala => %w( .scala ),
86
+ :scheme => %w( .scm .ss ),
87
+ :sed => %w( .sed ),
88
+ :shell => %w( .bash .csh .ksh .sh .tcsh .zsh ),
89
+ :smalltalk => %w( .st ),
90
+ :sml => %w( .sml .cm .sig ),
91
+ :sql => %w( .ctl .sql ),
92
+ :tcl => %w( .itcl .itk .tcl ),
93
+ :tex => %w( .cls .sty .tex ),
94
+ :text => %w( .text .txt README ),
95
+ :tt => %w( .tt .tt2 .ttml ),
96
+ :vala => %w( .vala .vapi ),
97
+ :vb => %w( .bas .cls .ctl .frm .resx .vb ),
98
+ :verilog => %w( .v .verilog ),
99
+ :vim => %w( .vim ),
100
+ :xml => %w( .dtd .ent .xml .xslt ),
101
+ :yaml => %w( .yaml .yml ),
75
102
  }
76
103
 
77
104
  VC_DIRS = %w(blib CVS _darcs .git .pc RCS SCCS .svn pkg)
@@ -80,6 +107,14 @@ END
80
107
  attr_reader :opt
81
108
  end
82
109
 
110
+ def self.compile_regexp(str)
111
+ if $use_onig
112
+ Oniguruma::ORegexp.new(str)
113
+ else
114
+ Regexp.new(str)
115
+ end
116
+ end
117
+
83
118
  def self.search
84
119
  @opt = {}
85
120
 
@@ -104,6 +139,7 @@ END
104
139
  [ '--help', GetoptLong::OPTIONAL_ARGUMENT ],
105
140
  [ '--max-count', '-m', GetoptLong::REQUIRED_ARGUMENT ],
106
141
  [ '--files', '-f', GetoptLong::NO_ARGUMENT ],
142
+ [ '--skipped', GetoptLong::NO_ARGUMENT ],
107
143
  [ '--output', GetoptLong::REQUIRED_ARGUMENT ],
108
144
  [ '--version', GetoptLong::NO_ARGUMENT ],
109
145
  [ '-c', '--count', GetoptLong::NO_ARGUMENT ],
@@ -136,16 +172,14 @@ END
136
172
  [ '-k', GetoptLong::REQUIRED_ARGUMENT ],
137
173
  [ '-x', '--line-regexp', GetoptLong::NO_ARGUMENT ],
138
174
  [ '-s', '--line-start', GetoptLong::NO_ARGUMENT ],
139
- [ '-e', '--line-end', GetoptLong::NO_ARGUMENT ]
175
+ [ '-e', '--line-end', GetoptLong::NO_ARGUMENT ],
176
+ [ '--eval', GetoptLong::REQUIRED_ARGUMENT ]
140
177
  )
141
178
 
142
- dir = nil
143
-
144
179
  opt[:max_count] = nil
145
- opt[:do_search] = true
180
+ opt[:only_print_filelist] = false
146
181
  opt[:print_filename] = true
147
182
  opt[:print_line_number] = true
148
- opt[:print_filelist] = false
149
183
  opt[:print_output] = nil
150
184
  opt[:print_highlighted] = true
151
185
  opt[:print_num_matches] = false
@@ -171,10 +205,12 @@ END
171
205
  opt[:collect_context] = false
172
206
  opt[:filename_regex] = nil
173
207
  opt[:neg_filename_regex] = nil
208
+ opt[:reverse_relevance] = false
209
+ opt[:eval] = nil
174
210
 
175
211
  # if redirected (RAK_TEST allows us to redirect in testing and still
176
212
  # get the non-redirected defaults).
177
- if STDOUT.isatty == false and ENV['RAK_TEST'] != "true"
213
+ unless STDOUT.isatty or ENV['RAK_TEST'] == "true"
178
214
  opt[:colour] = false
179
215
  opt[:print_file_each_line] = true
180
216
  opt[:print_filename] = false
@@ -189,13 +225,20 @@ END
189
225
  puts USAGE_HELP
190
226
  elsif arg == "types" or arg == "type"
191
227
  puts TYPES_HELP
228
+ FILE_TYPES.sort_by{|type,exts| type.to_s}.each do |type, exts|
229
+ puts " --[no]%-13s %s\n" % [type, exts.join(" ")]
230
+ end
192
231
  end
193
232
  exit
233
+ when '--eval'
234
+ opt[:eval] = arg
194
235
  when '--max-count'
195
236
  opt[:max_count] = arg.to_i
196
237
  when '--files'
197
- opt[:do_search] = false
198
- opt[:print_filelist] = true
238
+ opt[:only_print_filelist] = true
239
+ when '--skipped'
240
+ opt[:reverse_relevance] = true
241
+ opt[:only_print_filelist] = true
199
242
  when '--output'
200
243
  opt[:print_filename] = false
201
244
  opt[:print_line_number] = false
@@ -291,9 +334,9 @@ END
291
334
  opt[:before_context] = val
292
335
  opt[:after_context] = val
293
336
  when '-g'
294
- opt[:filename_regex] = arg
337
+ opt[:filename_regex] = compile_regexp(arg)
295
338
  when '-k'
296
- opt[:neg_filename_regex] = arg
339
+ opt[:neg_filename_regex] = compile_regexp(arg)
297
340
  when '-x'
298
341
  opt[:match_whole_lines] = true
299
342
  when '-s'
@@ -309,230 +352,149 @@ END
309
352
  exit
310
353
  end
311
354
 
312
- if ARGV.empty? and opt[:print_filelist] == false
313
- puts USAGE_HELP
314
- exit
315
- end
316
-
317
- files = get_files
318
-
319
- compile_match_file
320
-
321
355
  unless opt[:colour]
322
356
  FILE_COLOUR.replace ""
323
357
  CLEAR_COLOURS.replace ""
324
358
  MATCH_COLOUR.replace ""
325
359
  end
326
360
 
327
- print_files(files) if opt[:print_filelist]
328
- search2(ARGV[0], files) if opt[:do_search]
329
- end
330
-
331
- def self.print_files(files)
332
- files.each do |fn|
333
- puts fn
361
+ if opt[:only_print_filelist]
362
+ each_file(ARGV) do |fn|
363
+ puts fn
364
+ end
365
+ elsif ARGV.empty? and !opt[:eval]
366
+ puts USAGE_HELP
367
+ exit
368
+ else
369
+ unless opt[:eval]
370
+ re = compile_pattern(ARGV.shift)
371
+ end
372
+ compiled = false
373
+ file_separator = ""
374
+ each_file(ARGV) do |fn|
375
+ # each_file might turn off printing file name, but only before first yield
376
+ unless compiled
377
+ compile_match_file
378
+ compiled = true
379
+ end
380
+ match_file(re, fn, file_separator)
381
+ end
334
382
  end
335
383
  end
336
384
 
337
- def self.get_all_files(files=[], dir="./")
338
- Dir[dir+"*"].each do |fn|
339
- next if fn == ".."
340
- if File.directory? fn
341
- if opt[:descend]
342
- if File.symlink? fn
343
- if opt[:follow_symlinks]
344
- get_all_files(files, fn+"/")
345
- end
346
- else
347
- get_all_files(files, fn+"/")
348
- end
349
- end
350
- else
351
- if !File.socket? fn and File.readable? fn
352
- if File.symlink? fn
353
- if opt[:follow_symlinks]
354
- files << fn
355
- end
356
- else
357
- files << fn
358
- end
359
- end
385
+ def self.extension_regexp(extensions)
386
+ return nil if extensions.empty?
387
+ Regexp.compile('(?:' + extensions.map{|x| Regexp.escape(x)}.join("|") + ')\z')
388
+ end
389
+
390
+ def self.file_relevant?(fn)
391
+ # These don't change at this point
392
+ @types_rx ||= extension_regexp(FILE_TYPES.values.flatten)
393
+ @includes_rx ||= extension_regexp(opt[:includes])
394
+ @excludes_rx ||= extension_regexp(opt[:excludes])
395
+
396
+ ext = fn.basename.to_s
397
+ ext = shebang_matches(fn) unless ext =~ @types_rx
398
+
399
+ return false if !opt[:all_files] and !ext or fn.to_s =~ /[~#]\z/
400
+ return false if @includes_rx and (ext||"") !~ @includes_rx
401
+ return false if @excludes_rx and (ext||"") =~ @excludes_rx
402
+ return false if opt[:filename_regex] and fn.to_s !~ opt[:filename_regex]
403
+ return false if opt[:neg_filename_regex] and fn.to_s =~ opt[:neg_filename_regex]
404
+ return true
405
+ end
406
+
407
+ def self.find_all_files(path, &blk)
408
+ return if path.socket?
409
+ return unless path.readable?
410
+
411
+ if path.file?
412
+ relevant = file_relevant?(path)
413
+ relevant = !relevant if opt[:reverse_relevance]
414
+ yield(path.to_s.sub(/\A\.\/+/, "").gsub(/\/+/, "/")) if relevant
415
+ elsif path.directory?
416
+ path.children.each do |fn|
417
+ next if VC_DIRS.any?{|vc| vc == fn.basename.to_s}
418
+ next if fn.directory? and not opt[:descend]
419
+ next if fn.symlink? and not opt[:follow_symlinks]
420
+ find_all_files(fn, &blk)
360
421
  end
361
422
  end
362
- files
363
423
  end
364
-
365
- def self.get_files(dir="./")
366
- types_files = FILE_TYPES.invert
367
- types = types_files.keys.flatten
368
- files_and_dirs = ARGV[1..-1]
369
- io_files = []
370
- if !files_and_dirs or files_and_dirs.empty?
371
- if STDIN.isatty == false
372
- io_files << STDIN
373
- opt[:print_filename] = false
374
- files = []
424
+
425
+ def self.each_file(todo, &blk)
426
+ todo = todo.map{|path| Pathname(path)}
427
+ if todo.empty?
428
+ if STDIN.isatty
429
+ todo = [Pathname(".")]
375
430
  else
376
- files = get_all_files
377
- end
378
- else
379
- files = []
380
- files_and_dirs.each do |thing|
381
- if File.file? thing
382
- files << thing
383
- end
384
- if File.directory? thing
385
- get_all_files(files, thing)
386
- end
387
- end
388
- if files_and_dirs.length == 1 and
389
- File.file? files_and_dirs[0]
390
431
  opt[:print_filename] = false
432
+ yield(STDIN)
433
+ return
391
434
  end
435
+ elsif todo.size == 1 and todo[0].file?
436
+ opt[:print_filename] = false
392
437
  end
393
438
 
394
- # strip out based on extension
395
- shebang_files = []
396
- unless opt[:all_files]
397
- files = files.select do |fn|
398
- fn[-1..-1] != "~" and
399
- fn[-1..-1] != "#"
400
- end
401
- new_files = []
402
- files.each do |fn|
403
- if types.any? {|ext| File.extname(fn) == ext or fn.split("/").last == ext}
404
- new_files << fn
405
- elsif ext = shebang_matches(fn)
406
- shebang_files << [fn, ext]
407
- end
408
- end
409
- files = new_files
410
- end
411
-
412
- # select based on file type
413
- if not opt[:includes].empty?
414
- files = files.select do |fn|
415
- opt[:includes].any? {|ext| fn.include? ext}
416
- end
417
- shebang_files2 = []
418
- shebang_files.each do |fn, ext|
419
- if opt[:includes].include? ext
420
- shebang_files2 << [fn, ext]
439
+ if opt[:sort_files]
440
+ sortme = []
441
+ todo.each do |item|
442
+ find_all_files(item) do |fn|
443
+ sortme << fn
421
444
  end
422
445
  end
446
+ sortme.sort_by{|fn|fn.downcase}.each(&blk)
423
447
  else
424
- shebang_files2 = shebang_files
425
- end
426
-
427
- if not opt[:excludes].empty?
428
- files = files.select do |fn|
429
- not opt[:excludes].any? {|ext| fn.include? ext}
430
- end
431
- shebang_files3 = []
432
- shebang_files2.each do |fn, ext|
433
- unless opt[:excludes].any? {|ext| fn.include? ext}
434
- shebang_files3 << [fn, ext]
435
- end
448
+ todo.each do |item|
449
+ find_all_files(item, &blk)
436
450
  end
437
- else
438
- shebang_files3 = shebang_files2
439
- end
440
-
441
- files += shebang_files3.map {|fn, ext| fn}
442
-
443
- # reject version control dirs
444
- files.reject! do |fn|
445
- VC_DIRS.any? {|vc| fn.include? vc}
446
451
  end
447
-
448
- # filter based on -g=REGEX
449
- if opt[:filename_regex]
450
- if $use_onig
451
- fn_re = Oniguruma::ORegexp.new(opt[:filename_regex])
452
- else
453
- fn_re = Regexp.new(opt[:filename_regex])
454
- end
455
- files = files.select {|fn| fn =~ fn_re }
456
- end
457
-
458
- # filter based on -k=REGEX
459
- if opt[:neg_filename_regex]
460
- if $use_onig
461
- fn_re = Oniguruma::ORegexp.new(opt[:neg_filename_regex])
462
- else
463
- fn_re = Regexp.new(opt[:neg_filename_regex])
464
- end
465
- files = files.reject {|fn| fn =~ fn_re }
466
- end
467
-
468
- # remove the "./"
469
- files.map! {|fn| fn[0..1] == "./" ? fn[2..-1] : fn}
470
-
471
- if opt[:sort_files]
472
- files = files.sort_by {|fn| fn.downcase}
473
- end
474
-
475
- files + io_files
476
452
  end
477
-
453
+
478
454
  def self.shebang_matches(fn)
479
- File.open(fn) do |f|
480
- begin
481
- line = f.readline
482
- rescue
483
- line = ""
484
- end
485
- begin
486
- if line =~ /^#!/
487
- if line =~ /\b(ruby|perl|php|python)\b/
488
- return "."+FILE_TYPES[$1.intern].first
489
- elsif line =~ /\b(bash|csh|ksh|zsh)\b/
490
- return ".sh"
491
- end
455
+ begin
456
+ line = fn.open.readline
457
+ if line =~ /^#!/
458
+ if line =~ /\b(ruby|perl|php|python|make)[0-9.]*\b/i
459
+ FILE_TYPES[$1.downcase.intern].first
460
+ elsif line =~ /\b(sh|bash|csh|ksh|zsh)\b/
461
+ ".sh"
492
462
  else
493
- if line =~ /\Q<?xml /
494
- return ".xml"
495
- end
463
+ ".other"
496
464
  end
497
- rescue
498
- return nil
465
+ elsif line =~ /^<\?xml\b/
466
+ ".xml"
467
+ else
468
+ false
499
469
  end
500
- return false
470
+ rescue
471
+ nil
501
472
  end
502
473
  end
503
474
 
504
- def self.search2(str, files)
475
+ def self.compile_pattern(str)
505
476
  if opt[:literal]
506
477
  str = Regexp.quote(str)
507
478
  end
508
479
  if opt[:match_whole_words]
509
- str = "\\b" + str + "\\b"
480
+ str = "\\b(?:" + str + ")\\b"
510
481
  end
511
482
  if opt[:match_whole_lines]
512
- str = "^" + str + "$"
483
+ str = "^(?:" + str + ")$"
513
484
  end
514
485
  if opt[:match_line_starts]
515
- str = "^" + str
486
+ str = "^(?:" + str + ")"
516
487
  end
517
488
  if opt[:match_line_ends]
518
- str = str + "$"
489
+ str = "(?:" + str + ")$"
519
490
  end
520
491
  str = str.gsub("~bang", "!").gsub("~ques", "?")
521
492
  if $use_onig
522
- if opt[:ignore_case]
523
- re = Oniguruma::ORegexp.new(str, :options => Oniguruma::OPTION_IGNORECASE)
524
- else
525
- re = Oniguruma::ORegexp.new(str)
526
- end
493
+ flags = opt[:ignore_case] ? Oniguruma::OPTION_IGNORECASE : nil
494
+ Oniguruma::ORegexp.new(str, :options => flags)
527
495
  else
528
- if opt[:ignore_case]
529
- re = Regexp.new(str, Regexp::IGNORECASE)
530
- else
531
- re = Regexp.new(str)
532
- end
533
- end
534
- files.each do |fn|
535
- match_file(re, fn)
496
+ flags = opt[:ignore_case] ? Regexp::IGNORECASE : nil
497
+ Regexp.new(str, flags)
536
498
  end
537
499
  end
538
500
 
@@ -541,67 +503,88 @@ END
541
503
  # the match method based on them. This is gives a 2x speedup over
542
504
  # the alternative.
543
505
  def self.compile_match_file
506
+ needs_no_more_matches = (opt[:max_count] or opt[:eval])
507
+ needs_inverse_count = (opt[:print_num_matches] and opt[:invert_match])
508
+
544
509
  code = []
545
- code << %{def self.match_file(re, fn) }
510
+ code << %{def self.match_file(re, fn, file_separator) }
546
511
  code << %{ displayed_filename = false }
547
512
  code << %{ count = 0 }
548
- code << %{ i = 0 }
549
- code << %{ matches = [] }
513
+ if needs_inverse_count
514
+ code << %{ inverse_count = 0 }
515
+ end
550
516
  code << %{ print_num = 0 }
551
-
517
+ if needs_no_more_matches
518
+ code << %{ no_more_matches = false }
519
+ end
520
+ if opt[:print_file_each_line]
521
+ code << %{ fn_str = (fn.is_a?(String) ? FILE_COLOUR + fn + CLEAR_COLOURS + ":" : "") }
522
+ end
552
523
  if opt[:before_context] > 0
553
524
  code << %{ before_context = [] }
554
- code << %{ before_context_size = opt[:before_context] }
555
525
  end
556
526
  code << %{ if fn.is_a? String }
557
- code << %{ f = File.open(fn, "r") }
527
+ code << %{ f = File.open(fn, "rb") }
558
528
  code << %{ elsif fn.is_a? IO }
559
529
  code << %{ f = fn }
560
530
  code << %{ end }
561
-
562
- code << %{ all = f.read }
563
- # code << %{ p fn }
564
- # code << %{ all =~ re rescue p all }
565
- code << %{ if all !~ re and not opt[:invert_match] and not opt[:print_entire_line_if_no_match] }
566
- if opt[:print_file_if_no_match]
567
- code << %{ if count == 0 }
568
- code << %{ puts fn }
569
- code << %{ end }
570
- end
571
- code << %{ return }
572
- code << %{ end }
573
531
 
574
- code << %{ all.each_line do |line| }
575
- code << %{ i += 1 }
576
- code << %{ rest = line }
577
- code << %{ while md = re.match(rest) }
578
- code << %{ rest = md.post_match }
579
- if opt[:print_output]
580
- code << %{ matches << eval(opt[:print_output]) }
532
+ code << %{ f.each_line do |line| }
533
+ code << %{ matches = [] }
534
+
535
+ code << %{ unless no_more_matches } if needs_no_more_matches
536
+ if opt[:eval]
537
+ code << %{ $_ = line.chomp }
538
+ code << %{ $~ = nil }
539
+ code << %{ no_more_matches = true }
540
+ code << %{ eval_executed = false }
541
+ code << %{ loop do }
542
+ code << %{ if eval_executed }
543
+ code << %{ no_more_matches = false }
544
+ code << %{ break }
545
+ code << %{ end }
546
+ code << %{ eval_executed = true }
547
+ code << %{ #{opt[:eval]} }
548
+ code << %{ if matches.empty? }
549
+ code << %{ $_ =~ /^.*$/ unless $~ }
550
+ code << %{ matches << $~ }
551
+ code << %{ end }
552
+ code << %{ line = $_ }
553
+ code << %{ no_more_matches = false }
554
+ code << %{ break }
555
+ code << %{ end }
581
556
  else
582
- code << %{ matches << md }
557
+ if opt[:print_output]
558
+ code << %{ line.scan(re){ matches << eval(opt[:print_output]) } }
559
+ else
560
+ code << %{ line.scan(re){ matches << $~ } }
561
+ end
583
562
  end
584
563
  if opt[:print_match]
585
- code << %{ puts md.to_s }
564
+ code << %{ matches.each{|md| puts md.to_s } }
586
565
  end
587
- code << %{ count += 1 }
588
- code << %{ end }
566
+ code << %{ count += matches.size }
567
+ code << %{ end } if needs_no_more_matches
589
568
  if opt[:invert_match]
590
569
  if opt[:print_filename]
591
570
  code << %{ unless displayed_filename }
571
+ code << %{ print file_separator }
572
+ code << %{ file_separator.replace("\\n") }
592
573
  code << %{ puts FILE_COLOUR + fn + CLEAR_COLOURS }
593
574
  code << %{ displayed_filename = true }
594
575
  code << %{ end }
595
576
  end
596
577
  code << %{ if matches.empty? }
578
+ if needs_inverse_count
579
+ code << %{ inverse_count += 1 }
580
+ end
597
581
  if opt[:print_highlighted]
598
582
  if opt[:print_line_number]
599
- code << %{ print "\#{i.to_s.rjust(4)}|" }
583
+ code << %{ print "\#{$..to_s.rjust(4)}|" }
600
584
  end
601
- code << %{ puts "\#{line}" }
585
+ code << %{ puts line.expand_tabs }
602
586
  end
603
587
  code << %{ end }
604
- code << %{ matches.clear }
605
588
  else
606
589
  code << %{ if matches.empty? }
607
590
  if opt[:print_entire_line_if_no_match]
@@ -610,9 +593,9 @@ END
610
593
  if opt[:use_context]
611
594
  if opt[:before_context] > 0
612
595
  code << %{ if print_num == 0 }
613
- code << %{ before_context << [i, line] }
614
- code << %{ if before_context.length == before_context_size+1 }
615
- code << %{ before_context = before_context[1..-1] }
596
+ code << %{ before_context << [$., line] }
597
+ code << %{ if before_context.length > #{opt[:before_context]} }
598
+ code << %{ before_context.shift }
616
599
  code << %{ end }
617
600
  code << %{ end }
618
601
  end
@@ -620,9 +603,9 @@ END
620
603
  code << %{ print_num -= 1 }
621
604
  if opt[:print_highlighted]
622
605
  if opt[:print_line_number]
623
- code << %{ print "\#{i.to_s.rjust(4)}|" }
606
+ code << %{ print "\#{$..to_s.rjust(4)}|" }
624
607
  end
625
- code << %{ puts "\#{line}" }
608
+ code << %{ puts line.expand_tabs }
626
609
  code << %{ end }
627
610
  end
628
611
  end
@@ -630,6 +613,8 @@ END
630
613
  code << %{ print_num = opt[:after_context] }
631
614
  if opt[:print_filename]
632
615
  code << %{ unless displayed_filename }
616
+ code << %{ print file_separator }
617
+ code << %{ file_separator.replace("\\n") }
633
618
  code << %{ puts FILE_COLOUR + fn + CLEAR_COLOURS }
634
619
  code << %{ displayed_filename = true }
635
620
  code << %{ end }
@@ -639,7 +624,7 @@ END
639
624
  if opt[:print_line_number]
640
625
  code << %{ print "\#{before_i.to_s.rjust(4)}|" }
641
626
  end
642
- code << %{ puts before_line }
627
+ code << %{ puts before_line.expand_tabs }
643
628
  code << %{ end }
644
629
  code << %{ before_context = [] }
645
630
  end
@@ -648,76 +633,52 @@ END
648
633
  end
649
634
  if opt[:print_highlighted]
650
635
  if opt[:print_line_number]
651
- code << %{ prefix = "\#{i.to_s.rjust(4)}|" }
652
- else
653
- if opt[:print_file_each_line]
654
- code << %{ fn_str = (fn.is_a?(String) ? FILE_COLOUR + fn + CLEAR_COLOURS + " " : "") }
655
- code << %{ prefix = fn_str + "\#{i.to_s.rjust(4)}|" }
656
- else
657
- code << %{ prefix = "" }
658
- end
636
+ code << %{ print "\#{$..to_s.rjust(4)}|" }
637
+ elsif opt[:print_file_each_line]
638
+ code << %{ print fn_str + "\#{$.}:" }
659
639
  end
660
- code << %{ print_highlighted(prefix, line, matches) }
640
+ code << %{ print_highlighted(line, matches) }
661
641
  end
662
642
  code << %{ end }
663
643
  if opt[:max_count]
664
- code << %{ if count >= opt[:max_count] }
665
- code << %{ break }
666
- code << %{ end }
644
+ code << %{ no_more_matches = true if count >= opt[:max_count] }
645
+ end
646
+ if needs_no_more_matches and not needs_inverse_count
647
+ code << %{ break if no_more_matches and print_num == 0 }
667
648
  end
668
- code << %{ matches.clear }
669
649
  end
670
650
  code << %{ end }
671
- if opt[:print_highlighted] and opt[:print_filename]
672
- code << %{ if count > 0 }
673
- code << %{ puts }
674
- code << %{ end }
675
- end
676
651
  code << %{ f.close if f === File }
677
652
  if opt[:print_num_matches]
678
653
  if opt[:invert_match]
679
- code << %{ puts "\#{fn}:\#{i-count}" }
654
+ code << %{ puts "\#{fn}:\#{inverse_count}" }
680
655
  else
681
- code << %{ if count > 0 }
682
- code << %{ puts "\#{fn}:\#{count}" }
683
- code << %{ end }
656
+ code << %{ puts "\#{fn}:\#{count}" if count > 0 }
684
657
  end
685
658
  end
686
659
  if opt[:print_file_if_match]
687
- code << %{ if count > 0 }
688
- code << %{ puts fn }
689
- code << %{ end }
660
+ code << %{ puts fn if count > 0 }
690
661
  end
691
-
692
662
  if opt[:print_file_if_no_match]
693
- code << %{ if count == 0 }
694
- code << %{ puts fn }
695
- code << %{ end }
663
+ code << %{ puts fn if count == 0 }
696
664
  end
697
665
  code << %{end }
698
- module_eval code.join("\n")
666
+ module_eval code.join("\n"), "#{__FILE__} (#{__LINE__})"
699
667
  end
700
668
 
701
- def self.print_highlighted(prefix, line, matches)
702
- from1 = matches[0].begin(0)
703
- pre = ""
704
- pre = line[0..(from1-1)] if from1 > 0
705
- print prefix
706
- print pre
707
- ptr = 0
708
- 0.upto(matches.length-1) do |n|
709
- from = matches[n].begin(0)
710
- to = matches[n].end(0)
711
- print MATCH_COLOUR +
712
- line[(from+ptr)..(to+ptr-1)] +
713
- CLEAR_COLOURS
714
- ptr += to
715
- if n == matches.length - 1
716
- puts matches.last.post_match
717
- else
718
- print line[ptr..(matches[n+1].begin(0)-1+ptr)]
719
- end
669
+ def self.print_highlighted(line, matches)
670
+ matches = matches.map{|md| md.offset(0)}
671
+ cuts = [0, matches, line.size].flatten
672
+ column = 0
673
+ 0.upto(cuts.size-2) do |i|
674
+ part = line[cuts[i]...cuts[i+1]]
675
+ part = part.expand_tabs(column)
676
+ column += part.size
677
+ print MATCH_COLOUR if i%2 == 1
678
+ print part
679
+ print CLEAR_COLOURS if i%2 == 1
720
680
  end
681
+ print "\n" unless line[-1,1] == "\n"
721
682
  end
722
683
 
723
684
  def self.unknown_type(type)
@@ -743,12 +704,15 @@ Searching:
743
704
  -x, --line-regexp Force PATTERN to match only whole lines
744
705
  -Q, --literal Quote all metacharacters; expr is literal
745
706
  -s, --line-start Match only at the start of a line
746
- -e, --line-start Match only at the end of a line
707
+ -e, --line-end Match only at the end of a line
708
+
709
+ --eval CODE Match with Ruby code instead of regex.
710
+ [Highly experimental]
747
711
 
748
712
  Search output:
749
713
  -l, --files-with-matches
750
714
  Only print filenames containing matches
751
- -L, --files-without-match
715
+ -L, --files-without-matches
752
716
  Only print filenames with no match
753
717
  -o Show only the part of a line matching PATTERN
754
718
  (turns off text highlighting)
@@ -778,8 +742,9 @@ Search output:
778
742
  Print NUM lines (default 2) of output context.
779
743
 
780
744
  File finding:
781
- -f Only print the files found, without searching.
745
+ -f, --files Only print the files found, without searching.
782
746
  The PATTERN must not be specified.
747
+ --skipped Like -f but print files that were skipped.
783
748
  --sort-files Sort the found files lexically.
784
749
 
785
750
  File inclusion/exclusion:
@@ -810,41 +775,6 @@ format. For example, both --type=ruby and --ruby work.
810
775
  Note that some extensions may appear in multiple types. For example,
811
776
  .pod files are both Perl and Parrot.
812
777
 
813
- --[no]asm .s
814
- --[no]cc .c .h .xs
815
- --[no]cpp .cpp .m .h
816
- --[no]csharp .cs
817
- --[no]css .css
818
- --[no]elisp .el
819
- --[no]erlang .erl
820
- --[no]fortran .f .f77 .f90 .f95 .f03 .for .ftn .fpp
821
- --[no]haskell .hs .lhs
822
- --[no]hh .h
823
- --[no]html .htm .html .shtml
824
- --[no]java .java .properties
825
- --[no]js .js
826
- --[no]jsp .jsp .jspx .jhtm .jhtml
827
- --[no]lisp .lisp
828
- --[no]make Makefile
829
- --[no]mason .mas .mhtml .mpl .mtxt
830
- --[no]ocaml .ml .mli
831
- --[no]parrot .pir .pasm .pmc .ops .pod .pg .tg
832
- --[no]perl .pl .pm .pod .t
833
- --[no]php .php .phpt .php3 .php4 .php5
834
- --[no]prolog .pl .ecl
835
- --[no]python .py
836
- --[no]ruby .rb .rhtml .rjs .rxml Rakefile .rake .erb .haml
837
- --[no]scheme .scm
838
- --[no]shell .sh .bash .csh .ksh .zsh
839
- --[no]sql .sql .ctl
840
- --[no]tcl .tcl
841
- --[no]tex .tex .cls .sty
842
- --[no]text .txt .text
843
- --[no]tt .tt .tt2 .ttml
844
- --[no]vb .bas .cls .frm .ctl .vb .resx
845
- --[no]vim .vim
846
- --[no]xml .xml .dtd .xslt
847
- --[no]yaml .yaml .yml
848
778
  END
849
779
 
850
780
  Rak.search