├── spec ├── example │ ├── ln_dir │ ├── lib.so │ ├── corge.rb │ ├── qux │ ├── Rakefile │ ├── quux.py │ ├── _darcs │ │ ├── baz.rb │ │ └── corge.rb │ ├── shebang │ ├── dir1 │ │ └── bar.rb │ └── foo.rb ├── example2 │ └── snowman.txt ├── spec_helper.rb ├── help_spec.rb └── rak_spec.rb ├── Gemfile ├── lib └── rak.rb ├── .gitignore ├── web ├── shot1.png ├── shot2.png ├── shot3.png ├── index.ad └── index.html ├── .travis.yml ├── Manifest.txt ├── History.txt ├── rak.gemspec ├── README.txt └── bin └── rak /spec/example/ln_dir: -------------------------------------------------------------------------------- 1 | _darcs/ -------------------------------------------------------------------------------- /spec/example/lib.so: -------------------------------------------------------------------------------- 1 | foo foo 2 | -------------------------------------------------------------------------------- /spec/example/corge.rb: -------------------------------------------------------------------------------- 1 | _darcs/corge.rb -------------------------------------------------------------------------------- /spec/example/qux: -------------------------------------------------------------------------------- 1 | qux qux qux Libris qux qux qux -------------------------------------------------------------------------------- /spec/example2/snowman.txt: -------------------------------------------------------------------------------- 1 | 2 | # ☃ 3 | 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /spec/example/Rakefile: -------------------------------------------------------------------------------- 1 | rakefile rakefile Canceron rakefile -------------------------------------------------------------------------------- /lib/rak.rb: -------------------------------------------------------------------------------- 1 | 2 | class Rak 3 | VERSION = "1.5" 4 | end 5 | -------------------------------------------------------------------------------- /spec/example/quux.py: -------------------------------------------------------------------------------- 1 | quux quux quux quux Virgon quux quux 2 | -------------------------------------------------------------------------------- /spec/example/_darcs/baz.rb: -------------------------------------------------------------------------------- 1 | 2 | baz baz baz Aerelon baz baz baz 3 | -------------------------------------------------------------------------------- /spec/example/_darcs/corge.rb: -------------------------------------------------------------------------------- 1 | corge corge corge Sagitarron corge 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | pkg/* 3 | tags 4 | .DS_store 5 | *.gem 6 | Gemfile.lock 7 | -------------------------------------------------------------------------------- /web/shot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/rak/HEAD/web/shot1.png -------------------------------------------------------------------------------- /web/shot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/rak/HEAD/web/shot2.png -------------------------------------------------------------------------------- /web/shot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danlucraft/rak/HEAD/web/shot3.png -------------------------------------------------------------------------------- /spec/example/shebang: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | goo goo goo Aquaria goo goo 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.3 5 | - 2.0.0 6 | script: bundle exec rspec spec 7 | -------------------------------------------------------------------------------- /spec/example/dir1/bar.rb: -------------------------------------------------------------------------------- 1 | 2 | bar bar bar bar Pikon bar 3 | 4 | 5 | 6 | 7 | 8 | 9 | bar bar Pikon bar bar bar -------------------------------------------------------------------------------- /Manifest.txt: -------------------------------------------------------------------------------- 1 | History.txt 2 | Manifest.txt 3 | README.txt 4 | Rakefile 5 | bin/rak 6 | lib/rak.rb 7 | spec/rak_spec.rb 8 | spec/help_spec.rb 9 | -------------------------------------------------------------------------------- /spec/example/foo.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | foo foo foo Caprica foo foo foo 4 | foo Capsicum foo foo foo foo foo 5 | 6 | foo foo foo foo foo Pikon foo foo 7 | 8 | foo Pikon foo foo foo foo foo foo 9 | 10 | foo foo Six foo foo foo Six foo 11 | foo foo foo foo Six foo foo foo 12 | 13 | foo foo foo Gemenon foo foo foo 14 | -------------------------------------------------------------------------------- /History.txt: -------------------------------------------------------------------------------- 1 | 2 | == 1.0 / 2009-09-27 3 | 4 | * MinGW is a valid Windows platform type. 5 | * Can use ~bang and ~ques in a regex and they will be turned into ! and ? 6 | This is to get around bash. 7 | * Added -k negative file match option (Contributed by Juozas Gaigalas') 8 | * Added .cc and .hpp to the C++ file types 9 | * Added Vala file types 10 | * 20% faster on average 11 | * Moved to Github 12 | 13 | == 0.9 / 2008-02-03 14 | 15 | * Added .rake to ruby files 16 | * Colouring works on Win32 if win32console gem is installed. 17 | * Checks that file is readable by the current user. 18 | * Ignores socket files. 19 | * Added .erb and .haml to Ruby filetypes. 20 | * Added search at end-of/start-of line options 21 | * Fixed searching up the directory tree when passed '.' 22 | 23 | == 0.8.0 / 2007-10-30 24 | 25 | * Initial release. 26 | 27 | 28 | -------------------------------------------------------------------------------- /rak.gemspec: -------------------------------------------------------------------------------- 1 | 2 | Gem::Specification.new do |s| 3 | s.name = %q{rak} 4 | s.version = "1.5" 5 | 6 | s.authors = ["Daniel Lucraft"] 7 | s.date = %q{2012-07-30} 8 | s.default_executable = %q{rak} 9 | s.description = <<-END 10 | Grep replacement, recursively scans directories to match a given Ruby regular expression. Prints highlighted results. 11 | Based on the Perl tool 'ack' by Andy Lester. 12 | END 13 | s.email = %q{dan@fluentradical.com} 14 | s.executables = ["rak"] 15 | s.files = ["History.txt", "Manifest.txt", "README.txt", "bin/rak", "lib/rak.rb", "spec/rak_spec.rb", "spec/help_spec.rb"] 16 | s.homepage = %q{http://rak.rubyforge.org} 17 | s.require_paths = ["lib"] 18 | s.rubyforge_project = %q{rak} 19 | s.rubygems_version = %q{1.4.0} 20 | s.summary = %q{A grep replacement in Ruby, type "rak pattern".} 21 | 22 | s.add_development_dependency "rspec" 23 | end 24 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'rbconfig' 3 | require "pathname" 4 | 5 | HERE = Pathname(__FILE__).parent.expand_path 6 | require HERE.join("../lib/rak").to_s 7 | 8 | class String 9 | def shell_escape 10 | return "''" if empty? 11 | return dup unless self =~ /[^0-9A-Za-z+,.\/:=@_-]/ 12 | gsub(/(')|[^']+/) { $1 ? "\\'" : "'#{$&}'"} 13 | end 14 | 15 | def lines 16 | result = [] 17 | each_line do |line| 18 | result << line 19 | end 20 | result 21 | end 22 | end 23 | 24 | def ruby_bin 25 | File.join( 26 | Config::CONFIG["bindir"], 27 | Config::CONFIG["ruby_install_name"] + Config::CONFIG["EXEEXT"] 28 | ) 29 | end 30 | 31 | def bin_rak 32 | HERE.parent.join("bin/rak") 33 | end 34 | 35 | def rak(args="", opts={}) 36 | begin 37 | unless args.is_a?(String) 38 | args = args.map{|str| str.shell_escape}.join(" ") 39 | end 40 | cmd = "#{ruby_bin} #{bin_rak} --sort-files #{args}" 41 | cmd = "#{opts[:pipe]} | #{cmd}" if opts[:pipe] 42 | ENV['RAK_TEST'] = "true" unless opts[:test_mode] == false 43 | if opts[:dir] 44 | dir = HERE + opts[:dir] 45 | else 46 | dir = HERE + "example" 47 | end 48 | output = Dir.chdir(dir) do 49 | %x{#{cmd}} 50 | end 51 | output = output.gsub(/(?:\033\[(?:\d;)?\d+m)+/, opts[:ansi]||'*') 52 | output.gsub(/^(?!$)/, " ") 53 | ensure 54 | ENV.delete('RAK_TEST') 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /spec/help_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | def extract_options_from_help_message(msg) 4 | msg.scan(/\B--?(?:\[no\]|no)?([a-zA-Z0-9\-]+)/).flatten.sort.uniq 5 | end 6 | 7 | describe "Rak", "help and errors" do 8 | it "--version prints version information" do 9 | rak("--version").should == <<-END 10 | rak #{Rak::VERSION} 11 | 12 | Copyright 2008-#{Time.now.year} Daniel Lucraft, all rights reserved. 13 | Based on the perl tool 'ack' by Andy Lester. 14 | 15 | This program is free software; you can redistribute it and/or modify it 16 | under the same terms as Ruby. 17 | END 18 | end 19 | 20 | it "prints unknown type errors" do 21 | rak("Virg --type=pyth").should == <<-END 22 | rak: Unknown --type "pyth" 23 | rak: See rak --help types 24 | END 25 | end 26 | 27 | it "--help prints help information" do 28 | rak("Virg --help").lines[0].should == <<-END 29 | Usage: rak [OPTION]... PATTERN [FILES] 30 | END 31 | end 32 | 33 | it "--help types prints type information" do 34 | rak("--help types").lines[2].should == <<-END 35 | The following is the list of filetypes supported by rak. You can 36 | END 37 | end 38 | 39 | it "no options or patterns prints the usage info" do 40 | rak.lines[0].should == <<-END 41 | Usage: rak [OPTION]... PATTERN [FILES] 42 | END 43 | end 44 | 45 | it "prints a nice message for unknown options" do 46 | rak("foo --asfdasfd 2>/dev/null").should include <<-END 47 | rak: see rak --help for usage. 48 | END 49 | end 50 | 51 | it "should mention every option it supports in help" do 52 | expected = extract_options_from_help_message(File.readlines(bin_rak).grep(/GetoptLong/).join) - ['color'] 53 | actual = extract_options_from_help_message(rak()) - ['ruby'] 54 | actual.should == expected 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | rak 2 | http://rubyforge.org/projects/rak 3 | Daniel Lucraft (http://danlucraft.com/blog/) 4 | 5 | == DESCRIPTION: 6 | 7 | Replacement for grep. Recursively scans directories to match a given 8 | Ruby regular expression. Prints highlighted results. 9 | 10 | Based on the Perl tool 'ack' by Andy Lester. 11 | 12 | Examples with similar grep: 13 | 14 | $ rak pattern 15 | $ grep pattern $(find . | grep -v .svn) 16 | 17 | $ rak --ruby pattern 18 | $ grep pattern $(find . -name '*.rb' | grep -v .svn) 19 | 20 | == FEATURES/PROBLEMS: 21 | 22 | * Ruby regular expression syntax (uses oniguruma gem if installed). 23 | * Highlighted output. 24 | * Automatically recurses down the current directory or any given 25 | directories. 26 | * Skips version control directories, backups like '~' and '#' and your 27 | * ruby project's pkg directory. 28 | * Allows inclusion and exclusion of files based on types. 29 | * Many options similar to grep. 30 | 31 | == SYNOPSIS: 32 | 33 | See 'rak --help' for usage information. 34 | 35 | == REQUIREMENTS: 36 | 37 | * Ruby 38 | 39 | == INSTALL: 40 | 41 | * gem install rak 42 | 43 | == LICENSE: 44 | 45 | (The MIT License) 46 | 47 | Copyright (c) 2007 Daniel Lucraft 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining 50 | a copy of this software and associated documentation files (the 51 | 'Software'), to deal in the Software without restriction, including 52 | without limitation the rights to use, copy, modify, merge, publish, 53 | distribute, sublicense, and/or sell copies of the Software, and to 54 | permit persons to whom the Software is furnished to do so, subject to 55 | the following conditions: 56 | 57 | The above copyright notice and this permission notice shall be 58 | included in all copies or substantial portions of the Software. 59 | 60 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 61 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 62 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 63 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 64 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 65 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 66 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 67 | -------------------------------------------------------------------------------- /web/index.ad: -------------------------------------------------------------------------------- 1 | Rak 2 | === 3 | Daniel Lucraft 4 | v0.9, Jan 2008 5 | 6 | Rak is a grep replacement in pure Ruby. It accepts Ruby syntax regular 7 | expressions and automatically recurses directories, skipping .svn/, 8 | .cvs/, pkg/ and more things you don't care about. It is based on the 9 | Perl tool link:http://petdance.com/ack/[ack] by Andy Lester. 10 | 11 | Installing 12 | ---------- 13 | 14 | gem install rak 15 | 16 | Or clone from the git repo: 17 | 18 | git clone git://github.com/danlucraft/rak.git 19 | 20 | To get coloured output on Windows install the win32console gem. 21 | 22 | Please email comments and patches to dan@fluentradical.com. 23 | 24 | Examples 25 | -------- 26 | 27 | Searching for 'require' in the rak source tree: 28 | 29 | image::shot1.png[] 30 | 31 | Finding the first 10 strings in the bin/rak file: 32 | 33 | image::shot2.png[] 34 | 35 | Taking those strings and reversing them with Ruby: 36 | 37 | image::shot3.png[] 38 | 39 | Usage 40 | ----- 41 | 42 | Usage: rak [OPTION]... PATTERN [FILES] 43 | 44 | Search for PATTERN in each source file in the tree from cwd on down. 45 | 46 | If [FILES] is specified, then only those files/directories are checked. 47 | rak will search STDIN only if no FILES are specified. 48 | 49 | Example: rak -i select 50 | 51 | Searching: 52 | -i, --ignore-case Ignore case distinctions 53 | -v, --invert-match Invert match: select non-matching lines 54 | -w, --word-regexp Force PATTERN to match only whole words 55 | -x, --line-regexp Force PATTERN to match only whole lines 56 | -Q, --literal Quote all metacharacters; expr is literal 57 | -s, --line-start Match only at the start of a line 58 | -e, --line-start Match only at the end of a line 59 | 60 | Search output: 61 | -l, --files-with-matches 62 | Only print filenames containing matches 63 | -L, --files-without-match 64 | Only print filenames with no match 65 | -o Show only the part of a line matching PATTERN 66 | (turns off text highlighting) 67 | --passthru Print all lines, whether matching or not 68 | --output=expr Output the evaluation of expr for each line 69 | (turns off text highlighting) 70 | -m, --max-count=NUM Stop searching in a file after NUM matches 71 | -H, --with-filename Print the filename for each match 72 | -h, --no-filename Suppress the prefixing filename on output 73 | -c, --count Show number of lines matching per file 74 | 75 | --group Group matches by file name. 76 | (default: on when used interactively) 77 | --nogroup One result per line, including filename, like grep 78 | (default: on when the output is redirected) 79 | 80 | --[no]colour Highlight the matching text (default: on unless 81 | output is redirected, or on Windows) 82 | 83 | -A NUM, --after-context=NUM 84 | Print NUM lines of trailing context after matching 85 | lines. 86 | -B NUM, --before-context=NUM 87 | Print NUM lines of leading context before matching 88 | lines. 89 | -C [NUM], --context[=NUM] 90 | Print NUM lines (default 2) of output context. 91 | 92 | File finding: 93 | -f Only print the files found, without searching. 94 | The PATTERN must not be specified. 95 | --sort-files Sort the found files lexically. 96 | 97 | File inclusion/exclusion: 98 | -n No descending into subdirectories 99 | -g REGEX Only search in file matching REGEX. 100 | -a, --all All files, regardless of extension (but still skips 101 | blib, pkg, CVS, _darcs, .git, .pc, RCS, SCCS and .svn dirs) 102 | --ruby Include only Ruby files. 103 | --type=ruby Include only Ruby files. 104 | --noruby Exclude Ruby files. 105 | --type=noruby Exclude Ruby files. 106 | See "rak --help type" for supported filetypes. 107 | --[no]follow Follow symlinks. Default is off. 108 | 109 | Miscellaneous: 110 | --help This help 111 | --version Display version & copyright 112 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 301 | Rak 302 | 303 | 304 | 311 |
312 |
313 |

Rak is a grep replacement in pure Ruby. It accepts Ruby syntax regular 314 | expressions and automatically recurses directories, skipping .svn/, 315 | .cvs/, pkg/ and more things you don't care about. It is based on the 316 | Perl tool ack by Andy Lester.

317 |
318 |
319 |

Installing

320 |
321 |
322 |
323 |
gem install rak
324 |
325 |

Or clone from the git repo:

326 |
327 |
328 |
git clone git://github.com/danlucraft/rak.git
329 |
330 |

To get coloured output on Windows install the win32console gem.

331 |

Please email comments and patches to dan@fluentradical.com.

332 |
333 |

Examples

334 |
335 |

Searching for require in the rak source tree:

336 |
337 |
338 | shot1.png 339 |
340 |
341 |

Finding the first 10 strings in the bin/rak file:

342 |
343 |
344 | shot2.png 345 |
346 |
347 |

Taking those strings and reversing them with Ruby:

348 |
349 |
350 | shot3.png 351 |
352 |
353 |
354 |

Usage

355 |
356 |
357 |
358 |
Usage: rak [OPTION]... PATTERN [FILES]
359 |
360 |
361 |
362 |
Search for PATTERN in each source file in the tree from cwd on down.
363 |
364 |
365 |
366 |
If [FILES] is specified, then only those files/directories are checked.
367 | rak will search STDIN only if no FILES are specified.
368 |
369 |
370 |
371 |
Example: rak -i select
372 |
373 |
374 |
375 |
Searching:
376 |   -i, --ignore-case     Ignore case distinctions
377 |   -v, --invert-match    Invert match: select non-matching lines
378 |   -w, --word-regexp     Force PATTERN to match only whole words
379 |   -x, --line-regexp     Force PATTERN to match only whole lines
380 |   -Q, --literal         Quote all metacharacters; expr is literal
381 |   -s, --line-start      Match only at the start of a line
382 |   -e, --line-start      Match only at the end of a line
383 |
384 |
385 |
386 |
Search output:
387 |   -l, --files-with-matches
388 |                         Only print filenames containing matches
389 |   -L, --files-without-match
390 |                         Only print filenames with no match
391 |   -o                    Show only the part of a line matching PATTERN
392 |                         (turns off text highlighting)
393 |   --passthru            Print all lines, whether matching or not
394 |   --output=expr         Output the evaluation of expr for each line
395 |                         (turns off text highlighting)
396 |   -m, --max-count=NUM   Stop searching in a file after NUM matches
397 |   -H, --with-filename   Print the filename for each match
398 |   -h, --no-filename     Suppress the prefixing filename on output
399 |   -c, --count           Show number of lines matching per file
400 |
401 |
402 |
403 |
--group               Group matches by file name.
404 |                       (default: on when used interactively)
405 | --nogroup             One result per line, including filename, like grep
406 |                       (default: on when the output is redirected)
407 |
408 |
409 |
410 |
--[no]colour          Highlight the matching text (default: on unless
411 |                       output is redirected, or on Windows)
412 |
413 |
414 |
415 |
-A NUM, --after-context=NUM
416 |                       Print NUM lines of trailing context after matching
417 |                       lines.
418 | -B NUM, --before-context=NUM
419 |                       Print NUM lines of leading context before matching
420 |                       lines.
421 | -C [NUM], --context[=NUM]
422 |                       Print NUM lines (default 2) of output context.
423 |
424 |
425 |
426 |
File finding:
427 |   -f                    Only print the files found, without searching.
428 |                         The PATTERN must not be specified.
429 |   --sort-files          Sort the found files lexically.
430 |
431 |
432 |
433 |
File inclusion/exclusion:
434 |   -n                    No descending into subdirectories
435 |   -g REGEX              Only search in file matching REGEX.
436 |   -a, --all             All files, regardless of extension (but still skips
437 |                         blib, pkg, CVS, _darcs, .git, .pc, RCS, SCCS and .svn dirs)
438 |   --ruby                Include only Ruby files.
439 |   --type=ruby           Include only Ruby files.
440 |   --noruby              Exclude Ruby files.
441 |   --type=noruby         Exclude Ruby files.
442 |                         See "rak --help type" for supported filetypes.
443 |   --[no]follow          Follow symlinks.  Default is off.
444 |
445 |
446 |
447 |
Miscellaneous:
448 |   --help                This help
449 |   --version             Display version & copyright
450 |
451 |
452 | 458 | 459 | 460 | -------------------------------------------------------------------------------- /spec/rak_spec.rb: -------------------------------------------------------------------------------- 1 | #encoding: utf-8 2 | require "spec_helper" 3 | 4 | describe "Rak", "with no options" do 5 | it "prints all matches from files in the current directory" do 6 | rak("Cap.ic").should == <<-END 7 | *foo.rb* 8 | 3|foo foo foo *Capric*a foo foo foo 9 | 4|foo *Capsic*um foo foo foo foo foo 10 | END 11 | end 12 | 13 | it "prints all matches correctly" do 14 | rak("foo", :ansi => '').should == <<-END 15 | foo.rb 16 | 3|foo foo foo Caprica foo foo foo 17 | 4|foo Capsicum foo foo foo foo foo 18 | 6|foo foo foo foo foo Pikon foo foo 19 | 8|foo Pikon foo foo foo foo foo foo 20 | 10|foo foo Six foo foo foo Six foo 21 | 11|foo foo foo foo Six foo foo foo 22 | 13|foo foo foo Gemenon foo foo foo 23 | END 24 | end 25 | 26 | it "prints all matches from files in subdirectories" do 27 | rak("Pikon").should == <<-END 28 | *dir1/bar.rb* 29 | 2|bar bar bar bar *Pikon* bar 30 | 9|bar bar *Pikon* bar bar bar 31 | 32 | *foo.rb* 33 | 6|foo foo foo foo foo *Pikon* foo foo 34 | 8|foo *Pikon* foo foo foo foo foo foo 35 | END 36 | end 37 | 38 | it "prints multiple matches in a line" do 39 | rak("Six").should == <<-END 40 | *foo.rb* 41 | 10|foo foo *Six* foo foo foo *Six* foo 42 | 11|foo foo foo foo *Six* foo foo foo 43 | END 44 | end 45 | 46 | it "skips VC dirs" do 47 | rak("Aerelon").should == "" 48 | end 49 | 50 | it "does not follow symlinks" do 51 | rak("Sagitarron").should == "" 52 | end 53 | 54 | it "changes defaults when redirected" do 55 | rak("Six | cat", :test_mode => false).should == <<-END 56 | foo.rb:10:foo foo Six foo foo foo Six foo 57 | foo.rb:11:foo foo foo foo Six foo foo foo 58 | END 59 | end 60 | 61 | it "searches shebangs for valid inputs" do 62 | rak("Aquaria").should == <<-END 63 | *shebang* 64 | 3|goo goo goo *Aquaria* goo goo 65 | END 66 | end 67 | 68 | it "recognizes Makefiles and Rakefiles" do 69 | rak("Canceron").should == <<-END 70 | *Rakefile* 71 | 1|rakefile rakefile *Canceron* rakefile 72 | END 73 | end 74 | 75 | it "matches unicode snowmen" do 76 | rak("☃", :dir => "example2").should == <<-END 77 | *snowman.txt* 78 | 2|# *☃* 79 | END 80 | end 81 | end 82 | 83 | describe "Rak", "with FILE or STDIN inputs" do 84 | it "should only search in given files or directories" do 85 | rak("Pikon foo.rb").should == <<-END 86 | 6|foo foo foo foo foo *Pikon* foo foo 87 | 8|foo *Pikon* foo foo foo foo foo foo 88 | END 89 | rak("Pikon dir1/", :ansi => '').should == <<-END 90 | dir1/bar.rb 91 | 2|bar bar bar bar Pikon bar 92 | 9|bar bar Pikon bar bar bar 93 | END 94 | end 95 | 96 | it "should search in STDIN by default if no files are specified" do 97 | rak("Aere", :pipe => "cat _darcs/baz.rb").should == <<-END 98 | 2|baz baz baz *Aere*lon baz baz baz 99 | END 100 | end 101 | 102 | it "only searches STDIN when piped to" do 103 | rak("Cap", :pipe => "echo asdfCapasdf").should == <<-END 104 | 1|asdf*Cap*asdf 105 | END 106 | end 107 | 108 | it "should either match on rax x or rak -v x" do 109 | (rak('"\A.{0,100}\Z" dir1/bar.rb').length + rak('-v "\A.{0,100}\Z" dir1/bar.rb').length).should > 0 110 | end 111 | end 112 | 113 | describe "Rak", "options" do 114 | it "prints only files with --files" do 115 | t = <<-END 116 | Rakefile 117 | quux.py 118 | dir1/bar.rb 119 | foo.rb 120 | shebang 121 | END 122 | rak("-f").lines.sort.should == t.lines.sort 123 | end 124 | 125 | it "prints a maximum number of matches if --max-count=x is specified" do 126 | rak("Cap.ic -m 1").should == <<-END 127 | *foo.rb* 128 | 3|foo foo foo *Capric*a foo foo foo 129 | END 130 | end 131 | 132 | it "prints context of all matches if -m and -C are both specified, without further highlighting" do 133 | rak("Cap.ic -C2 -m 1").should == <<-END 134 | *foo.rb* 135 | 1| 136 | 2| 137 | 3|foo foo foo *Capric*a foo foo foo 138 | 4|foo Capsicum foo foo foo foo foo 139 | 5| 140 | END 141 | end 142 | 143 | it "prints the evaluated output for --output" do 144 | rak("Cap --output='$&'", :ansi => '').should == <<-END 145 | Cap 146 | Cap 147 | END 148 | end 149 | 150 | it "-v -c displays correct counts even with multiple matches per line" do 151 | rak("-v -c bar").lines.sort.join.should == <<-END 152 | Rakefile:1 153 | dir1/bar.rb:7 154 | foo.rb:13 155 | quux.py:1 156 | shebang:3 157 | END 158 | end 159 | 160 | it "-c prints only the number of matches found per file" do 161 | rak("Pik -c").lines.sort.join.should == <<-END 162 | dir1/bar.rb:2 163 | foo.rb:2 164 | END 165 | end 166 | 167 | it "-h suppresses filename and line number printing" do 168 | rak("Pik -h").should == <<-END 169 | bar bar bar bar *Pik*on bar 170 | bar bar *Pik*on bar bar bar 171 | foo foo foo foo foo *Pik*on foo foo 172 | foo *Pik*on foo foo foo foo foo foo 173 | END 174 | end 175 | 176 | it "ignores case with -i" do 177 | rak("six -i").should == <<-END 178 | *foo.rb* 179 | 10|foo foo *Six* foo foo foo *Six* foo 180 | 11|foo foo foo foo *Six* foo foo foo 181 | END 182 | end 183 | 184 | it "inverts the match with -v" do 185 | t1 = <<-END 186 | quux.py 187 | 1|quux quux quux quux Virgon quux quux 188 | END 189 | t12 = <<-END 190 | dir1/bar.rb 191 | 1| 192 | 2|bar bar bar bar Pikon bar 193 | 3| 194 | 4| 195 | 5| 196 | 6| 197 | 7| 198 | 8| 199 | 9|bar bar Pikon bar bar bar 200 | END 201 | t2 = <<-END 202 | foo.rb 203 | 1| 204 | 2| 205 | 5| 206 | 7| 207 | 9| 208 | 12| 209 | END 210 | t3 = <<-END 211 | shebang 212 | 1|#!/usr/bin/env ruby 213 | 2| 214 | 3|goo goo goo Aquaria goo goo 215 | END 216 | t4 = <<-END 217 | Rakefile 218 | 1|rakefile rakefile Canceron rakefile 219 | END 220 | r = rak("foo -v", :ansi => '') 221 | r.should include t1 222 | r.should include t12 223 | r.should include t2 224 | r.should include t3 225 | r.should include t4 226 | r.lines.length.should == 29 227 | end 228 | 229 | it "doesn't descend into subdirs with -n" do 230 | rak("Pikon -n", :ansi => '').should == <<-END 231 | foo.rb 232 | 6|foo foo foo foo foo Pikon foo foo 233 | 8|foo Pikon foo foo foo foo foo foo 234 | END 235 | end 236 | 237 | it "quotes meta-characters with -Q" do 238 | rak("Cap. -Q").should == "" 239 | end 240 | 241 | it "prints only the matching portion with -o" do 242 | rak("Cap -o", :ansi => '').should == <<-END 243 | Cap 244 | Cap 245 | END 246 | end 247 | 248 | it "matches whole words only with -w" do 249 | rak("'Cap|Hat' -w").should == "" 250 | end 251 | 252 | it "prints the file on each line with --nogroup" do 253 | rak("Cap --nogroup").should == <<-END 254 | *foo.rb*:3:foo foo foo *Cap*rica foo foo foo 255 | *foo.rb*:4:foo *Cap*sicum foo foo foo foo foo 256 | END 257 | end 258 | 259 | it "-l means only print filenames with matches" do 260 | rak("Caprica -l").should == <<-END 261 | foo.rb 262 | END 263 | end 264 | 265 | it "-L means only print filenames without matches" do 266 | t = <<-END 267 | quux.py 268 | dir1/bar.rb 269 | shebang 270 | Rakefile 271 | END 272 | rak("Caprica -L", :ansi => '').lines.sort.should == t.lines.sort 273 | end 274 | 275 | it "--passthru means print all lines whether matching or not" do 276 | t1 = <<-END 277 | quux quux quux quux Virgon quux quux 278 | END 279 | 280 | t2 = <<-END 281 | *foo.rb* 282 | 3|foo foo foo *Caprica* foo foo foo 283 | foo Capsicum foo foo foo foo foo 284 | 285 | foo foo foo foo foo Pikon foo foo 286 | 287 | foo Pikon foo foo foo foo foo foo 288 | 289 | foo foo Six foo foo foo Six foo 290 | foo foo foo foo Six foo foo foo 291 | 292 | foo foo foo Gemenon foo foo foo 293 | END 294 | t3 = <<-END 295 | #!/usr/bin/env ruby 296 | 297 | goo goo goo Aquaria goo goo 298 | END 299 | r = rak("Caprica --passthru -n") 300 | r.should include t1 301 | r.should include t2 302 | r.should include t3 303 | r.lines.length.should < (t1+t2+t3).lines.length+5 304 | end 305 | 306 | it "--nocolour means do not colourize the output" do 307 | rak("Cap --nocolour").should == <<-END 308 | foo.rb 309 | 3|foo foo foo Caprica foo foo foo 310 | 4|foo Capsicum foo foo foo foo foo 311 | END 312 | end 313 | 314 | it "-a means to search every file" do 315 | rak("Libris -a").should == <<-END 316 | *qux* 317 | 1|qux qux qux *Libris* qux qux qux 318 | END 319 | end 320 | 321 | it "--ruby means only ruby files" do 322 | rak("Virgon --ruby").should == "" 323 | end 324 | 325 | it "--python means only python files" do 326 | rak("Cap --python").should == "" 327 | end 328 | 329 | it "--noruby means exclude ruby files" do 330 | rak("Cap --noruby").should == "" 331 | end 332 | 333 | it "--type=ruby means only ruby files" do 334 | rak("Virgon --type=ruby").should == "" 335 | end 336 | 337 | it "--type=python means only python files" do 338 | rak("Cap --type=python").should == "" 339 | end 340 | 341 | it "--type=noruby means exclude ruby files" do 342 | rak("Cap --type=noruby").should == "" 343 | end 344 | 345 | it "--sort-files" do 346 | rak("-f --sort-files").should == <<-END 347 | dir1/bar.rb 348 | foo.rb 349 | quux.py 350 | Rakefile 351 | shebang 352 | END 353 | end 354 | 355 | it "--follow means follow symlinks" do 356 | rak("Sagitarron --follow", :ansi => '').should == <<-END 357 | corge.rb 358 | 1|corge corge corge Sagitarron corge 359 | 360 | ln_dir/corge.rb 361 | 1|corge corge corge Sagitarron corge 362 | END 363 | end 364 | 365 | it "-A NUM means show NUM lines after" do 366 | rak("Caps -A 2", :ansi => '').should == <<-END 367 | foo.rb 368 | 4|foo Capsicum foo foo foo foo foo 369 | 5| 370 | 6|foo foo foo foo foo Pikon foo foo 371 | END 372 | end 373 | 374 | it "-A should work when there are matches close together" do 375 | rak("foo -A 2", :ansi => '').should == <<-END 376 | foo.rb 377 | 3|foo foo foo Caprica foo foo foo 378 | 4|foo Capsicum foo foo foo foo foo 379 | 5| 380 | 6|foo foo foo foo foo Pikon foo foo 381 | 7| 382 | 8|foo Pikon foo foo foo foo foo foo 383 | 9| 384 | 10|foo foo Six foo foo foo Six foo 385 | 11|foo foo foo foo Six foo foo foo 386 | 12| 387 | 13|foo foo foo Gemenon foo foo foo 388 | END 389 | end 390 | 391 | it "-B NUM means show NUM lines before" do 392 | rak("Caps -B 2", :ansi => '').should == <<-END 393 | foo.rb 394 | 2| 395 | 3|foo foo foo Caprica foo foo foo 396 | 4|foo Capsicum foo foo foo foo foo 397 | END 398 | end 399 | 400 | it "-C means show 2 lines before and after" do 401 | rak("Caps -C", :ansi => '').should == <<-END 402 | foo.rb 403 | 2| 404 | 3|foo foo foo Caprica foo foo foo 405 | 4|foo Capsicum foo foo foo foo foo 406 | 5| 407 | 6|foo foo foo foo foo Pikon foo foo 408 | END 409 | end 410 | 411 | it "-C 1 means show 1 lines before and after" do 412 | rak("Caps -C 1", :ansi => '').should == <<-END 413 | foo.rb 414 | 3|foo foo foo Caprica foo foo foo 415 | 4|foo Capsicum foo foo foo foo foo 416 | 5| 417 | END 418 | end 419 | 420 | it "-C works correctly for nearby results" do 421 | rak("Pik -g foo -C", :ansi => '').should == <<-END 422 | foo.rb 423 | 4|foo Capsicum foo foo foo foo foo 424 | 5| 425 | 6|foo foo foo foo foo Pikon foo foo 426 | 7| 427 | 8|foo Pikon foo foo foo foo foo foo 428 | 9| 429 | 10|foo foo Six foo foo foo Six foo 430 | END 431 | end 432 | 433 | it "-g REGEX only searches in files matching REGEX" do 434 | rak("Pikon -g f.o").should == <<-END 435 | *foo.rb* 436 | 6|foo foo foo foo foo *Pikon* foo foo 437 | 8|foo *Pikon* foo foo foo foo foo foo 438 | END 439 | end 440 | 441 | it "-k REGEX only searches in files not matching REGEX" do 442 | rak("Pikon -k f.o").should == <<-END 443 | *dir1/bar.rb* 444 | 2|bar bar bar bar *Pikon* bar 445 | 9|bar bar *Pikon* bar bar bar 446 | END 447 | end 448 | 449 | it "-x means match only whole lines" do 450 | rak("Cap -x").should == "" 451 | rak('"foo|goo" -x').should == "" 452 | rak('"(foo )+Cap\w+( foo)+" -x').should == <<-END 453 | *foo.rb* 454 | 3|*foo foo foo Caprica foo foo foo* 455 | 4|*foo Capsicum foo foo foo foo foo* 456 | END 457 | end 458 | 459 | it "-s means match only at the start of a line" do 460 | rak('-s "foo Cap|Aquaria"').should == <<-END 461 | *foo.rb* 462 | 4|*foo Cap*sicum foo foo foo foo foo 463 | END 464 | end 465 | 466 | it "-e means match only at the end of a line" do 467 | rak('-e "Aquaria|kon foo foo"').should == <<-END 468 | *foo.rb* 469 | 6|foo foo foo foo foo Pi*kon foo foo* 470 | END 471 | end 472 | 473 | it "should not recurse down '..' when used with . " do 474 | rak("foo .", :dir=>HERE+"example/dir1").should == "" 475 | end 476 | end 477 | 478 | describe "Rak", "with combinations of options" do 479 | it "should process -c -v " do 480 | t1=<<-END 481 | quux.py:1 482 | dir1/bar.rb:7 483 | foo.rb:11 484 | shebang:3 485 | Rakefile:1 486 | END 487 | rak("Pikon -c -v", :ansi => '').lines.sort.should == t1.lines.sort 488 | end 489 | 490 | it "-h and redirection" do 491 | rak("Pik -h | cat", :test_mode => false).should == <<-END 492 | bar bar bar bar Pikon bar 493 | bar bar Pikon bar bar bar 494 | foo foo foo foo foo Pikon foo foo 495 | foo Pikon foo foo foo foo foo foo 496 | END 497 | end 498 | end 499 | 500 | describe "Rak", "with --eval" do 501 | it "should support next" do 502 | rak(%Q[--eval 'next unless $. == $_.split.size']).should == <<-END 503 | *foo.rb* 504 | 8|*foo Pikon foo foo foo foo foo foo* 505 | END 506 | end 507 | 508 | it "should support break" do 509 | rak(%Q[--eval 'break if $. == 2']).should == <<-END 510 | *dir1/bar.rb* 511 | 1|* 512 | 513 | *foo.rb* 514 | 1|* 515 | 516 | *quux.py* 517 | 1|*quux quux quux quux Virgon quux quux* 518 | 519 | *Rakefile* 520 | 1|*rakefile rakefile Canceron rakefile* 521 | 522 | *shebang* 523 | 1|*#!/usr/bin/env ruby* 524 | END 525 | end 526 | 527 | it "should support highlighting" do 528 | rak(%Q[--eval 'next unless $_ =~ /Caprica/']).should == <<-END 529 | *foo.rb* 530 | 3|foo foo foo *Caprica* foo foo foo 531 | END 532 | end 533 | 534 | it "should support ranges" do 535 | rak(%Q[--eval 'next unless $_[/Capsicum/]..$_[/Pikon/]']).should == <<-END 536 | *foo.rb* 537 | 4|*foo Capsicum foo foo foo foo foo* 538 | 5|* 539 | 6|foo foo foo foo foo *Pikon* foo foo 540 | END 541 | end 542 | 543 | it "should support next and contexts" do 544 | rak(%Q[-C 2 --eval 'next unless $_ =~ /Pikon/']).should == <<-END 545 | *dir1/bar.rb* 546 | 1| 547 | 2|bar bar bar bar *Pikon* bar 548 | 3| 549 | 4| 550 | 7| 551 | 8| 552 | 9|bar bar *Pikon* bar bar bar 553 | 554 | *foo.rb* 555 | 4|foo Capsicum foo foo foo foo foo 556 | 5| 557 | 6|foo foo foo foo foo *Pikon* foo foo 558 | 7| 559 | 8|foo *Pikon* foo foo foo foo foo foo 560 | 9| 561 | 10|foo foo Six foo foo foo Six foo 562 | END 563 | end 564 | 565 | it "should support break and contexts (and matches past break should get no highlighting)" do 566 | rak(%Q[-C 2 --eval 'next unless $_[/Pikon/]...(break; nil)']).should == <<-END 567 | *dir1/bar.rb* 568 | 1| 569 | 2|bar bar bar bar *Pikon* bar 570 | 3| 571 | 4| 572 | 573 | *foo.rb* 574 | 4|foo Capsicum foo foo foo foo foo 575 | 5| 576 | 6|foo foo foo foo foo *Pikon* foo foo 577 | 7| 578 | 8|foo Pikon foo foo foo foo foo foo 579 | END 580 | end 581 | 582 | it "should support $_ transformations" do 583 | rak(%Q[--eval '$_=$_.reverse; next unless $_ =~ /oof/']).should == <<-END 584 | *foo.rb* 585 | 3|*oof* oof oof acirpaC oof oof oof 586 | 4|*oof* oof oof oof oof mucispaC oof 587 | 6|*oof* oof nokiP oof oof oof oof oof 588 | 8|*oof* oof oof oof oof oof nokiP oof 589 | 10|*oof* xiS oof oof oof xiS oof oof 590 | 11|*oof* oof oof xiS oof oof oof oof 591 | 13|*oof* oof oof nonemeG oof oof oof 592 | END 593 | end 594 | 595 | it "should support multiple matches" do 596 | rak(%Q[--eval 'next unless $_ =~ /Pikon/; $_.scan(/\\b\\w{3}\\b/){ matches << $~ }']).should == <<-END 597 | *dir1/bar.rb* 598 | 2|*bar* *bar* *bar* *bar* Pikon *bar* 599 | 9|*bar* *bar* Pikon *bar* *bar* *bar* 600 | 601 | *foo.rb* 602 | 6|*foo* *foo* *foo* *foo* *foo* Pikon *foo* *foo* 603 | 8|*foo* Pikon *foo* *foo* *foo* *foo* *foo* *foo* 604 | END 605 | end 606 | end 607 | 608 | ## Other things --eval could support: 609 | # * per file BEGIN{} END{} 610 | # * global BEGIN{} END{} 611 | # * better way to highlight multiple matches 612 | # 613 | # There's also redo but I cannot think of any sensible use 614 | # (especially if $_ won't persist between runs) 615 | # (also redo in -nl/-pl is broken) 616 | -------------------------------------------------------------------------------- /bin/rak: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: binary 3 | 4 | require 'rubygems' 5 | require "pathname" 6 | 7 | if RUBY_PLATFORM =~ /mswin|mingw/ 8 | begin 9 | require 'win32console' 10 | rescue LoadError 11 | ARGV << "--nocolour" 12 | end 13 | end 14 | 15 | require 'getoptlong' 16 | 17 | begin 18 | require 'oniguruma' 19 | require 'inline' 20 | $use_onig = true 21 | rescue LoadError 22 | $use_onig = false 23 | end 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 | 33 | class Rak 34 | VERSION = "1.5" 35 | 36 | FILE_COLOUR = "\033[1;31m" 37 | MATCH_COLOUR = "\033[1;37m\033[41m" 38 | CLEAR_COLOURS = "\033[0m" 39 | 40 | VERSION_INFO=< %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 .sass .scss ), 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 .coffee ), 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 .ru Rakefile Gemfile Guardfile Procfile ), 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 ), 102 | } 103 | 104 | VC_DIRS = %w(blib CVS _darcs .git .hg .pc RCS SCCS .svn pkg) 105 | 106 | class << self 107 | attr_reader :opt 108 | end 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 | 118 | def self.search 119 | @opt = {} 120 | 121 | # file types 122 | opt[:includes] = [] 123 | opt[:excludes] = [] 124 | 125 | FILE_TYPES.each do |type, exts| 126 | if ARGV.delete('--'+type.to_s) 127 | exts.each do |ext| 128 | opt[:includes] << ext 129 | end 130 | end 131 | if ARGV.delete('--no'+type.to_s) 132 | exts.each do |ext| 133 | opt[:excludes] << ext 134 | end 135 | end 136 | end 137 | 138 | opts = GetoptLong.new( 139 | [ '--help', GetoptLong::OPTIONAL_ARGUMENT ], 140 | [ '--max-count', '-m', GetoptLong::REQUIRED_ARGUMENT ], 141 | [ '--files', '-f', GetoptLong::NO_ARGUMENT ], 142 | [ '--skipped', GetoptLong::NO_ARGUMENT ], 143 | [ '--output', GetoptLong::REQUIRED_ARGUMENT ], 144 | [ '--version', GetoptLong::NO_ARGUMENT ], 145 | [ '-c', '--count', GetoptLong::NO_ARGUMENT ], 146 | [ '-h', '--no-filename', GetoptLong::NO_ARGUMENT ], 147 | [ '-i', '--ignore-case', GetoptLong::NO_ARGUMENT ], 148 | [ '-v', '--invert-match', GetoptLong::NO_ARGUMENT ], 149 | [ '-n', GetoptLong::NO_ARGUMENT ], 150 | [ '-Q', '--literal', GetoptLong::NO_ARGUMENT ], 151 | [ '-o', GetoptLong::NO_ARGUMENT ], 152 | [ '-w', '--word-regexp', GetoptLong::NO_ARGUMENT ], 153 | [ '--group', GetoptLong::NO_ARGUMENT ], 154 | [ '--nogroup', GetoptLong::NO_ARGUMENT ], 155 | [ '-l', '--files-with-matches', GetoptLong::NO_ARGUMENT ], 156 | [ '-L', '--files-without-matches', GetoptLong::NO_ARGUMENT ], 157 | [ '--passthru', GetoptLong::NO_ARGUMENT ], 158 | [ '-H', '--with-filename', GetoptLong::NO_ARGUMENT ], 159 | [ '--colour', GetoptLong::NO_ARGUMENT ], 160 | [ '--nocolour', GetoptLong::NO_ARGUMENT ], 161 | [ '--color', GetoptLong::NO_ARGUMENT ], 162 | [ '--nocolor', GetoptLong::NO_ARGUMENT ], 163 | [ '-a', '--all', GetoptLong::NO_ARGUMENT ], 164 | [ '--type', GetoptLong::REQUIRED_ARGUMENT ], 165 | [ '--sort-files', GetoptLong::NO_ARGUMENT ], 166 | [ '--follow', GetoptLong::NO_ARGUMENT ], 167 | [ '--nofollow', GetoptLong::NO_ARGUMENT ], 168 | [ '--after-context', '-A', GetoptLong::REQUIRED_ARGUMENT ], 169 | [ '--before-context', '-B', GetoptLong::REQUIRED_ARGUMENT ], 170 | [ '--context', '-C', GetoptLong::OPTIONAL_ARGUMENT ], 171 | [ '-g', GetoptLong::REQUIRED_ARGUMENT ], 172 | [ '-k', GetoptLong::REQUIRED_ARGUMENT ], 173 | [ '-x', '--line-regexp', GetoptLong::NO_ARGUMENT ], 174 | [ '-s', '--line-start', GetoptLong::NO_ARGUMENT ], 175 | [ '-e', '--line-end', GetoptLong::NO_ARGUMENT ], 176 | [ '--eval', GetoptLong::REQUIRED_ARGUMENT ] 177 | ) 178 | 179 | opt[:max_count] = nil 180 | opt[:only_print_filelist] = false 181 | opt[:print_filename] = true 182 | opt[:print_line_number] = true 183 | opt[:print_output] = nil 184 | opt[:print_highlighted] = true 185 | opt[:print_num_matches] = false 186 | opt[:ignore_case] = false 187 | opt[:invert_match] = false 188 | opt[:descend] = true 189 | opt[:literal] = false 190 | opt[:print_match] = false 191 | opt[:match_whole_words] = false 192 | opt[:match_whole_lines] = false 193 | opt[:match_line_starts] = false 194 | opt[:match_line_ends] = false 195 | opt[:print_file_each_line] = false 196 | opt[:print_file_if_match] = false 197 | opt[:print_file_if_no_match] = false 198 | opt[:print_entire_line_if_no_match] = false 199 | opt[:colour] = true 200 | opt[:all_files] = false 201 | opt[:sort_files] = false 202 | opt[:follow_symlinks] = false 203 | opt[:after_context] = 0 204 | opt[:before_context] = 0 205 | opt[:collect_context] = false 206 | opt[:filename_regex] = nil 207 | opt[:neg_filename_regex] = nil 208 | opt[:reverse_relevance] = false 209 | opt[:eval] = nil 210 | 211 | # if redirected (RAK_TEST allows us to redirect in testing and still 212 | # get the non-redirected defaults). 213 | unless STDOUT.isatty or ENV['RAK_TEST'] == "true" 214 | opt[:colour] = false 215 | opt[:print_file_each_line] = true 216 | opt[:print_filename] = false 217 | opt[:print_line_number] = false 218 | end 219 | 220 | begin 221 | opts.each do |option, arg| 222 | case option 223 | when '--help' 224 | if arg == "" 225 | puts USAGE_HELP 226 | elsif arg == "types" or arg == "type" 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 231 | end 232 | exit 233 | when '--eval' 234 | opt[:eval] = arg 235 | when '--max-count' 236 | opt[:max_count] = arg.to_i 237 | when '--files' 238 | opt[:only_print_filelist] = true 239 | when '--skipped' 240 | opt[:reverse_relevance] = true 241 | opt[:only_print_filelist] = true 242 | when '--output' 243 | opt[:print_filename] = false 244 | opt[:print_line_number] = false 245 | opt[:print_output] = arg 246 | opt[:print_highlighted] = false 247 | when '--version' 248 | puts VERSION_INFO 249 | exit 250 | when '-c' 251 | opt[:print_num_matches] = true 252 | opt[:print_filename] = false 253 | opt[:print_line_number] = false 254 | opt[:print_highlighted] = false 255 | when '-h' 256 | opt[:print_filename] = false 257 | opt[:print_line_number] = false 258 | opt[:print_file_each_line] = false 259 | when '-i' 260 | opt[:ignore_case] = true 261 | when '-v' 262 | opt[:invert_match] = true 263 | when '-n' 264 | opt[:descend] = false 265 | when '-Q' 266 | opt[:literal] = true 267 | when '-o' 268 | opt[:print_match] = true 269 | opt[:print_filename] = false 270 | opt[:print_line_number] = false 271 | opt[:print_highlighted] = false 272 | when '-w' 273 | opt[:match_whole_words] = true 274 | when '--group' 275 | opt[:print_filename] = true 276 | opt[:print_file_each_line] = false 277 | when '--nogroup' 278 | opt[:print_file_each_line] = true 279 | opt[:print_filename] = false 280 | opt[:print_line_number] = false 281 | when '-l' 282 | opt[:print_filename] = false 283 | opt[:print_line_number] = false 284 | opt[:print_highlighted] = false 285 | opt[:print_file_if_match] = true 286 | when '-L' 287 | opt[:print_filename] = false 288 | opt[:print_line_number] = false 289 | opt[:print_highlighted] = false 290 | opt[:print_file_if_no_match] = true 291 | when '--passthru' 292 | opt[:print_entire_line_if_no_match] = true 293 | when '-H' 294 | opt[:print_filename] = true 295 | opt[:print_line_number] = true 296 | when '--nocolour', '--nocolor' 297 | opt[:colour] = false 298 | when '--colour', '--color' 299 | opt[:colour] = true 300 | when '-a' 301 | opt[:all_files] = true 302 | when '--type' 303 | if arg[0..1] == "no" 304 | type = arg[2..-1] 305 | arr = opt[:excludes] 306 | else 307 | type = arg 308 | arr = opt[:includes] 309 | end 310 | exts = FILE_TYPES[type.intern] 311 | unknown_type(type) unless exts 312 | exts.each do |ext| 313 | arr << ext 314 | end 315 | when '--sort-files' 316 | opt[:sort_files] = true 317 | when '--follow' 318 | opt[:follow_symlinks] = true 319 | when '--nofollow' 320 | opt[:follow_symlinks] = false 321 | when '--after-context' 322 | opt[:use_context] = true 323 | opt[:after_context] = arg.to_i 324 | when '--before-context' 325 | opt[:use_context] = true 326 | opt[:before_context] = arg.to_i 327 | when '--context' 328 | opt[:use_context] = true 329 | if arg == "" 330 | val = 2 331 | else 332 | val = arg.to_i 333 | end 334 | opt[:before_context] = val 335 | opt[:after_context] = val 336 | when '-g' 337 | opt[:filename_regex] = compile_regexp(arg) 338 | when '-k' 339 | opt[:neg_filename_regex] = compile_regexp(arg) 340 | when '-x' 341 | opt[:match_whole_lines] = true 342 | when '-s' 343 | opt[:match_line_starts] = true 344 | when '-e' 345 | opt[:match_line_ends] = true 346 | end 347 | end 348 | rescue GetoptLong::InvalidOption => ex 349 | puts "rak: see rak --help for usage." 350 | exit 351 | rescue SystemExit 352 | exit 353 | end 354 | 355 | unless opt[:colour] 356 | FILE_COLOUR.replace "" 357 | CLEAR_COLOURS.replace "" 358 | MATCH_COLOUR.replace "" 359 | end 360 | 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 382 | end 383 | end 384 | 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) 421 | end 422 | end 423 | end 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(".")] 430 | else 431 | opt[:print_filename] = false 432 | yield(STDIN) 433 | return 434 | end 435 | elsif todo.size == 1 and todo[0].file? 436 | opt[:print_filename] = false 437 | end 438 | 439 | if opt[:sort_files] 440 | sortme = [] 441 | todo.each do |item| 442 | find_all_files(item) do |fn| 443 | sortme << fn 444 | end 445 | end 446 | sortme.sort_by{|fn|fn.downcase}.each(&blk) 447 | else 448 | todo.each do |item| 449 | find_all_files(item, &blk) 450 | end 451 | end 452 | end 453 | 454 | def self.shebang_matches(fn) 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" 462 | else 463 | ".other" 464 | end 465 | elsif line =~ /^<\?xml\b/ 466 | ".xml" 467 | else 468 | false 469 | end 470 | rescue 471 | nil 472 | end 473 | end 474 | 475 | def self.compile_pattern(str) 476 | if opt[:literal] 477 | str = Regexp.quote(str) 478 | end 479 | if opt[:match_whole_words] 480 | str = "\\b(?:" + str + ")\\b" 481 | end 482 | if opt[:match_whole_lines] 483 | str = "^(?:" + str + ")$" 484 | end 485 | if opt[:match_line_starts] 486 | str = "^(?:" + str + ")" 487 | end 488 | if opt[:match_line_ends] 489 | str = "(?:" + str + ")$" 490 | end 491 | str = str.gsub("~bang", "!").gsub("~ques", "?") 492 | if str.respond_to?(:force_encoding) 493 | str.force_encoding("ASCII-8BIT") 494 | end 495 | if $use_onig 496 | flags = opt[:ignore_case] ? Oniguruma::OPTION_IGNORECASE : nil 497 | Oniguruma::ORegexp.new(str, :options => flags) 498 | else 499 | flags = opt[:ignore_case] ? Regexp::IGNORECASE : nil 500 | Regexp.new(str, flags) 501 | end 502 | end 503 | 504 | # this is tricky thing. Ignore the "code <<" to see the 505 | # logic. Because the options never change we conditionally compile 506 | # the match method based on them. This is gives a 2x speedup over 507 | # the alternative. 508 | def self.compile_match_file 509 | needs_no_more_matches = (opt[:max_count] or opt[:eval]) 510 | needs_inverse_count = (opt[:print_num_matches] and opt[:invert_match]) 511 | 512 | code = [] 513 | code << %{def self.match_file(re, fn, file_separator) } 514 | code << %{ displayed_filename = false } 515 | code << %{ count = 0 } 516 | if needs_inverse_count 517 | code << %{ inverse_count = 0 } 518 | end 519 | code << %{ print_num = 0 } 520 | if needs_no_more_matches 521 | code << %{ no_more_matches = false } 522 | end 523 | if opt[:print_file_each_line] 524 | code << %{ fn_str = (fn.is_a?(String) ? FILE_COLOUR + fn + CLEAR_COLOURS + ":" : "") } 525 | end 526 | if opt[:before_context] > 0 527 | code << %{ before_context = [] } 528 | end 529 | code << %{ if fn.is_a? String } 530 | code << %{ f = File.open(fn, "rb") } 531 | code << %{ elsif fn.is_a? IO } 532 | code << %{ f = fn } 533 | code << %{ end } 534 | 535 | code << %{ f.each_line do |line| } 536 | code << %{ matches = [] } 537 | 538 | code << %{ unless no_more_matches } if needs_no_more_matches 539 | if opt[:eval] 540 | code << %{ $_ = line.chomp } 541 | code << %{ $~ = nil } 542 | code << %{ no_more_matches = true } 543 | code << %{ eval_executed = false } 544 | code << %{ loop do } 545 | code << %{ if eval_executed } 546 | code << %{ no_more_matches = false } 547 | code << %{ break } 548 | code << %{ end } 549 | code << %{ eval_executed = true } 550 | code << %{ #{opt[:eval]} } 551 | code << %{ if matches.empty? } 552 | code << %{ $_ =~ /^.*$/ unless $~ } 553 | code << %{ matches << $~ } 554 | code << %{ end } 555 | code << %{ line = $_ } 556 | code << %{ no_more_matches = false } 557 | code << %{ break } 558 | code << %{ end } 559 | else 560 | if opt[:print_output] 561 | code << %{ line.scan(re){ matches << eval(opt[:print_output]) } } 562 | else 563 | code << %{ line.scan(re){ matches << $~ } } 564 | end 565 | end 566 | if opt[:print_match] 567 | code << %{ matches.each{|md| puts md.to_s } } 568 | end 569 | code << %{ count += matches.size } 570 | code << %{ end } if needs_no_more_matches 571 | if opt[:invert_match] 572 | if opt[:print_filename] 573 | code << %{ unless displayed_filename } 574 | code << %{ print file_separator } 575 | code << %{ file_separator.replace("\\n") } 576 | code << %{ puts FILE_COLOUR + fn + CLEAR_COLOURS } 577 | code << %{ displayed_filename = true } 578 | code << %{ end } 579 | end 580 | code << %{ if matches.empty? } 581 | if needs_inverse_count 582 | code << %{ inverse_count += 1 } 583 | end 584 | if opt[:print_highlighted] 585 | if opt[:print_line_number] 586 | code << %{ print "\#{$..to_s.rjust(4)}|" } 587 | end 588 | code << %{ puts line.expand_tabs } 589 | end 590 | code << %{ end } 591 | else 592 | code << %{ if matches.empty? } 593 | if opt[:print_entire_line_if_no_match] 594 | code << %{ puts line } 595 | end 596 | if opt[:use_context] 597 | if opt[:before_context] > 0 598 | code << %{ if print_num == 0 } 599 | code << %{ before_context << [$., line] } 600 | code << %{ if before_context.length > #{opt[:before_context]} } 601 | code << %{ before_context.shift } 602 | code << %{ end } 603 | code << %{ end } 604 | end 605 | code << %{ if print_num > 0 } 606 | code << %{ print_num -= 1 } 607 | if opt[:print_highlighted] 608 | if opt[:print_line_number] 609 | code << %{ print "\#{$..to_s.rjust(4)}|" } 610 | end 611 | code << %{ puts line.expand_tabs } 612 | code << %{ end } 613 | end 614 | end 615 | code << %{ else } 616 | code << %{ print_num = opt[:after_context] } 617 | if opt[:print_filename] 618 | code << %{ unless displayed_filename } 619 | code << %{ print file_separator } 620 | code << %{ file_separator.replace("\\n") } 621 | code << %{ puts FILE_COLOUR + fn + CLEAR_COLOURS } 622 | code << %{ displayed_filename = true } 623 | code << %{ end } 624 | end 625 | if opt[:before_context] > 0 626 | code << %{ before_context.each do |before_i, before_line| } 627 | if opt[:print_line_number] 628 | code << %{ print "\#{before_i.to_s.rjust(4)}|" } 629 | end 630 | code << %{ puts before_line.expand_tabs } 631 | code << %{ end } 632 | code << %{ before_context = [] } 633 | end 634 | if opt[:print_output] 635 | code << %{ matches.each {|m| puts m} } 636 | end 637 | if opt[:print_highlighted] 638 | if opt[:print_line_number] 639 | code << %{ print "\#{$..to_s.rjust(4)}|" } 640 | elsif opt[:print_file_each_line] 641 | code << %{ print fn_str + "\#{$.}:" } 642 | end 643 | code << %{ print_highlighted(line, matches) } 644 | end 645 | code << %{ end } 646 | if opt[:max_count] 647 | code << %{ no_more_matches = true if count >= opt[:max_count] } 648 | end 649 | if needs_no_more_matches and not needs_inverse_count 650 | code << %{ break if no_more_matches and print_num == 0 } 651 | end 652 | end 653 | code << %{ end } 654 | code << %{ f.close if f === File } 655 | if opt[:print_num_matches] 656 | if opt[:invert_match] 657 | code << %{ puts "\#{fn}:\#{inverse_count}" } 658 | else 659 | code << %{ puts "\#{fn}:\#{count}" if count > 0 } 660 | end 661 | end 662 | if opt[:print_file_if_match] 663 | code << %{ puts fn if count > 0 } 664 | end 665 | if opt[:print_file_if_no_match] 666 | code << %{ puts fn if count == 0 } 667 | end 668 | code << %{end } 669 | module_eval code.join("\n"), "#{__FILE__} (#{__LINE__})" 670 | end 671 | 672 | def self.print_highlighted(line, matches) 673 | matches = matches.map{|md| md.offset(0)} 674 | cuts = [0, matches, line.size].flatten 675 | column = 0 676 | 0.upto(cuts.size-2) do |i| 677 | part = line[cuts[i]...cuts[i+1]] 678 | part = part.expand_tabs(column) 679 | column += part.size 680 | print MATCH_COLOUR if i%2 == 1 681 | print part 682 | print CLEAR_COLOURS if i%2 == 1 683 | end 684 | print "\n" unless line[-1,1] == "\n" 685 | end 686 | 687 | def self.unknown_type(type) 688 | puts "rak: Unknown --type \"#{type}\"" 689 | puts "rak: See rak --help types" 690 | exit 691 | end 692 | end 693 | 694 | USAGE_HELP=<