rak 1.0 → 1.2

Sign up to get free protection for your applications and to get access to all the features.
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