PotatoSalad-rak 0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,16 @@
1
+
2
+ == 0.9 / 2008-02-03
3
+
4
+ * Added .rake to ruby files
5
+ * Colouring works on Win32 if win32console gem is installed.
6
+ * Checks that file is readable by the current user.
7
+ * Ignores socket files.
8
+ * Added .erb and .haml to Ruby filetypes.
9
+ * Added search at end-of/start-of line options
10
+ * Fixed searching up the directory tree when passed '.'
11
+
12
+ == 0.8.0 / 2007-10-30
13
+
14
+ * Initial release.
15
+
16
+
@@ -0,0 +1,8 @@
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
@@ -0,0 +1,66 @@
1
+ rak
2
+ by Daniel Lucraft
3
+ http://rubyforge.org/projects/rak
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.
@@ -0,0 +1,14 @@
1
+
2
+ require 'rubygems'
3
+ require 'hoe'
4
+ require 'lib/rak'
5
+
6
+ Hoe.new('rak', Rak::VERSION) do |p|
7
+ p.rubyforge_name = 'rak'
8
+ p.author = 'Daniel Lucraft'
9
+ p.email = 'dan@fluentradical.com'
10
+ p.summary = 'A grep replacement in Ruby, type "$rak pattern".'
11
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
12
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
13
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
14
+ end
data/bin/rak ADDED
@@ -0,0 +1,861 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+
5
+ if RUBY_PLATFORM =~ /mswin/
6
+ begin
7
+ require 'win32console'
8
+ rescue LoadError
9
+ ARGV << "--nocolour"
10
+ end
11
+ end
12
+
13
+ require 'getoptlong'
14
+
15
+ begin
16
+ require 'oniguruma'
17
+ require 'inline'
18
+ $use_onig = true
19
+ rescue LoadError
20
+ $use_onig = false
21
+ end
22
+
23
+ $use_onig = false
24
+
25
+ class Rak
26
+ VERSION = "0.9"
27
+
28
+ FILE_COLOUR = "\033[1;31m"
29
+ MATCH_COLOUR = "\033[1;37m\033[41m"
30
+ CLEAR_COLOURS = "\033[0m"
31
+
32
+ VERSION_INFO=<<END
33
+ rak #{VERSION}
34
+
35
+ Copyright 2008 Daniel Lucraft, all rights reserved.
36
+ Based on the perl tool 'ack' by Andy Lester.
37
+
38
+ This program is free software; you can redistribute it and/or modify it
39
+ under the same terms as Ruby.
40
+ END
41
+
42
+ FILE_TYPES = {
43
+ :asm => %w( .s .S ),
44
+ :cc => %w( .c .h .xs ),
45
+ :cpp => %w( .cpp .m .h .C .H ),
46
+ :csharp => %w( .cs ),
47
+ :css => %w( .css ),
48
+ :elisp => %w( .el ),
49
+ :haskell => %w( .hs .lhs ),
50
+ :hh => %w( .h ),
51
+ :html => %w( .htm .html .shtml ),
52
+ :lisp => %w( .lisp ),
53
+ :java => %w( .java properties ),
54
+ :js => %w( .js ),
55
+ :jsp => %w( .jsp .jspx .jhtm .jhtml ),
56
+ :make => %w( Makefile ),
57
+ :mason => %w( .mas .mhtml .mpl .mtxt ),
58
+ :ocaml => %w( .ml .mli ),
59
+ :parrot => %w( .pir .pasm .pmc .ops .pod .pg .tg ),
60
+ :perl => %w( .pl .pm .pod .t ),
61
+ :php => %w( .php .phpt .php3 .php4 .php5 ),
62
+ :prolog => %w( .pl .ecl ),
63
+ :python => %w( .py ),
64
+ :ruby => %w( .rb .rhtml .rjs .rxml Rakefile .rake .erb .haml),
65
+ :scheme => %w( .scm ),
66
+ :shell => %w( .sh .bash .csh .ksh .zsh ),
67
+ :sql => %w( .sql .ctl ),
68
+ :tcl => %w( .tcl ),
69
+ :tex => %w( .tex .cls .sty ),
70
+ :text => %w( .txt .text ),
71
+ :tt => %w( .tt .tt2 .ttml ),
72
+ :vb => %w( .bas .cls .frm .ctl .vb .resx ),
73
+ :vim => %w( .vim ),
74
+ :yaml => %w( .yaml .yml ),
75
+ :xml => %w( .xml .dtd .xslt )
76
+ }
77
+
78
+ VC_DIRS = %w(blib CVS _darcs .git .pc RCS SCCS .svn pkg)
79
+
80
+ class << self
81
+ attr_reader :opt
82
+ end
83
+
84
+ def self.search
85
+ @opt = {}
86
+
87
+ # file types
88
+ opt[:includes] = []
89
+ opt[:excludes] = []
90
+
91
+ FILE_TYPES.each do |type, exts|
92
+ if ARGV.delete('--'+type.to_s)
93
+ exts.each do |ext|
94
+ opt[:includes] << ext
95
+ end
96
+ end
97
+ if ARGV.delete('--no'+type.to_s)
98
+ exts.each do |ext|
99
+ opt[:excludes] << ext
100
+ end
101
+ end
102
+ end
103
+
104
+ opts = GetoptLong.new(
105
+ [ '--help', GetoptLong::OPTIONAL_ARGUMENT ],
106
+ [ '--max-count', '-m', GetoptLong::REQUIRED_ARGUMENT ],
107
+ [ '--files', '-f', GetoptLong::NO_ARGUMENT ],
108
+ [ '--output', GetoptLong::REQUIRED_ARGUMENT ],
109
+ [ '--version', GetoptLong::NO_ARGUMENT ],
110
+ [ '-c', '--count', GetoptLong::NO_ARGUMENT ],
111
+ [ '-h', '--no-filename', GetoptLong::NO_ARGUMENT ],
112
+ [ '-i', '--ignore-case', GetoptLong::NO_ARGUMENT ],
113
+ [ '-v', '--invert-match', GetoptLong::NO_ARGUMENT ],
114
+ [ '-n', GetoptLong::NO_ARGUMENT ],
115
+ [ '-Q', '--literal', GetoptLong::NO_ARGUMENT ],
116
+ [ '-o', GetoptLong::NO_ARGUMENT ],
117
+ [ '-w', '--word-regexp', GetoptLong::NO_ARGUMENT ],
118
+ [ '--pager', GetoptLong::REQUIRED_ARGUMENT ],
119
+ [ '--nopager', GetoptLong::NO_ARGUMENT ],
120
+ [ '--group', GetoptLong::NO_ARGUMENT ],
121
+ [ '--nogroup', GetoptLong::NO_ARGUMENT ],
122
+ [ '-l', '--files-with-matches', GetoptLong::NO_ARGUMENT ],
123
+ [ '-L', '--files-without-matches', GetoptLong::NO_ARGUMENT ],
124
+ [ '--passthru', GetoptLong::NO_ARGUMENT ],
125
+ [ '-H', '--with-filename', GetoptLong::NO_ARGUMENT ],
126
+ [ '--colour', GetoptLong::NO_ARGUMENT ],
127
+ [ '--nocolour', GetoptLong::NO_ARGUMENT ],
128
+ [ '--color', GetoptLong::NO_ARGUMENT ],
129
+ [ '--nocolor', GetoptLong::NO_ARGUMENT ],
130
+ [ '-a', '--all', GetoptLong::NO_ARGUMENT ],
131
+ [ '--type', GetoptLong::REQUIRED_ARGUMENT ],
132
+ [ '--sort-files', GetoptLong::NO_ARGUMENT ],
133
+ [ '--follow', GetoptLong::NO_ARGUMENT ],
134
+ [ '--nofollow', GetoptLong::NO_ARGUMENT ],
135
+ [ '--after-context', '-A', GetoptLong::REQUIRED_ARGUMENT ],
136
+ [ '--before-context', '-B', GetoptLong::REQUIRED_ARGUMENT ],
137
+ [ '--context', '-C', GetoptLong::OPTIONAL_ARGUMENT ],
138
+ [ '-g', GetoptLong::REQUIRED_ARGUMENT ],
139
+ [ '-x', '--line-regexp', GetoptLong::NO_ARGUMENT ],
140
+ [ '-s', '--line-start', GetoptLong::NO_ARGUMENT ],
141
+ [ '-e', '--line-end', GetoptLong::NO_ARGUMENT ]
142
+ )
143
+
144
+ dir = nil
145
+
146
+ opt[:max_count] = nil
147
+ opt[:do_search] = true
148
+ opt[:print_filename] = true
149
+ opt[:print_line_number] = true
150
+ opt[:print_filelist] = false
151
+ opt[:print_output] = nil
152
+ opt[:print_highlighted] = true
153
+ opt[:print_num_matches] = false
154
+ opt[:ignore_case] = false
155
+ opt[:invert_match] = false
156
+ opt[:descend] = true
157
+ opt[:literal] = false
158
+ opt[:print_match] = false
159
+ opt[:match_whole_words] = false
160
+ opt[:match_whole_lines] = false
161
+ opt[:match_line_starts] = false
162
+ opt[:match_line_ends] = false
163
+ opt[:pager] = nil
164
+ opt[:nopager] = false
165
+ opt[:print_file_each_line] = false
166
+ opt[:print_file_if_match] = false
167
+ opt[:print_file_if_no_match] = false
168
+ opt[:print_entire_line_if_no_match] = false
169
+ opt[:colour] = true
170
+ opt[:all_files] = false
171
+ opt[:sort_files] = false
172
+ opt[:follow_symlinks] = false
173
+ opt[:after_context] = 0
174
+ opt[:before_context] = 0
175
+ opt[:collect_context] = false
176
+ opt[:filename_regex] = nil
177
+
178
+ # if redirected (RAK_TEST allows us to redirect in testing and still
179
+ # get the non-redirected defaults).
180
+ if STDOUT.isatty == false and ENV['RAK_TEST'] != "true"
181
+ opt[:colour] = false
182
+ opt[:print_file_each_line] = true
183
+ opt[:print_filename] = false
184
+ opt[:print_line_number] = false
185
+ end
186
+
187
+ begin
188
+ opts.each do |option, arg|
189
+ case option
190
+ when '--help'
191
+ if arg == ""
192
+ puts USAGE_HELP
193
+ elsif arg == "types" or arg == "type"
194
+ puts TYPES_HELP
195
+ end
196
+ exit
197
+ when '--max-count'
198
+ opt[:max_count] = arg.to_i
199
+ when '--files'
200
+ opt[:do_search] = false
201
+ opt[:print_filelist] = true
202
+ when '--output'
203
+ opt[:print_filename] = false
204
+ opt[:print_line_number] = false
205
+ opt[:print_output] = arg
206
+ opt[:print_highlighted] = false
207
+ when '--version'
208
+ puts VERSION_INFO
209
+ exit
210
+ when '-c'
211
+ opt[:print_num_matches] = true
212
+ opt[:print_filename] = false
213
+ opt[:print_line_number] = false
214
+ opt[:print_highlighted] = false
215
+ when '-h'
216
+ opt[:print_filename] = false
217
+ opt[:print_line_number] = false
218
+ opt[:print_file_each_line] = false
219
+ when '-i'
220
+ opt[:ignore_case] = true
221
+ when '-v'
222
+ opt[:invert_match] = true
223
+ when '-n'
224
+ opt[:descend] = false
225
+ when '-Q'
226
+ opt[:literal] = true
227
+ when '-o'
228
+ opt[:print_match] = true
229
+ opt[:print_filename] = false
230
+ opt[:print_line_number] = false
231
+ opt[:print_highlighted] = false
232
+ when '-w'
233
+ opt[:match_whole_words] = true
234
+ when '--pager'
235
+ opt[:pager] = arg.to_s
236
+ when '--nopager'
237
+ opt[:nopager] = true
238
+ when '--group'
239
+ opt[:print_filename] = true
240
+ opt[:print_file_each_line] = false
241
+ when '--nogroup'
242
+ opt[:print_file_each_line] = true
243
+ opt[:print_filename] = false
244
+ opt[:print_line_number] = false
245
+ when '-l'
246
+ opt[:print_filename] = false
247
+ opt[:print_line_number] = false
248
+ opt[:print_highlighted] = false
249
+ opt[:print_file_if_match] = true
250
+ when '-L'
251
+ opt[:print_filename] = false
252
+ opt[:print_line_number] = false
253
+ opt[:print_highlighted] = false
254
+ opt[:print_file_if_no_match] = true
255
+ when '--passthru'
256
+ opt[:print_entire_line_if_no_match] = true
257
+ when '-H'
258
+ opt[:print_filename] = true
259
+ opt[:print_line_number] = true
260
+ when '--nocolour', '--nocolor'
261
+ opt[:colour] = false
262
+ when '--colour', '--color'
263
+ opt[:colour] = true
264
+ when '-a'
265
+ opt[:all_files] = true
266
+ when '--type'
267
+ if arg[0..1] == "no"
268
+ type = arg[2..-1]
269
+ arr = opt[:excludes]
270
+ else
271
+ type = arg
272
+ arr = opt[:includes]
273
+ end
274
+ exts = FILE_TYPES[type.intern]
275
+ unknown_type(type) unless exts
276
+ exts.each do |ext|
277
+ arr << ext
278
+ end
279
+ when '--sort-files'
280
+ opt[:sort_files] = true
281
+ when '--follow'
282
+ opt[:follow_symlinks] = true
283
+ when '--nofollow'
284
+ opt[:follow_symlinks] = false
285
+ when '--after-context'
286
+ opt[:use_context] = true
287
+ opt[:after_context] = arg.to_i
288
+ when '--before-context'
289
+ opt[:use_context] = true
290
+ opt[:before_context] = arg.to_i
291
+ when '--context'
292
+ opt[:use_context] = true
293
+ if arg == ""
294
+ val = 2
295
+ else
296
+ val = arg.to_i
297
+ end
298
+ opt[:before_context] = val
299
+ opt[:after_context] = val
300
+ when '-g'
301
+ opt[:filename_regex] = arg
302
+ when '-x'
303
+ opt[:match_whole_lines] = true
304
+ when '-s'
305
+ opt[:match_line_starts] = true
306
+ when '-e'
307
+ opt[:match_line_ends] = true
308
+ end
309
+ end
310
+ rescue GetoptLong::InvalidOption => ex
311
+ puts "rak: see rak --help for usage."
312
+ exit
313
+ rescue SystemExit
314
+ exit
315
+ end
316
+
317
+ if ARGV.empty? and opt[:print_filelist] == false
318
+ puts USAGE_HELP
319
+ exit
320
+ end
321
+
322
+ files = get_files
323
+
324
+ compile_match_file
325
+
326
+ unless opt[:colour]
327
+ FILE_COLOUR.replace ""
328
+ CLEAR_COLOURS.replace ""
329
+ MATCH_COLOUR.replace ""
330
+ end
331
+
332
+ run_pager(opt[:pager]) unless opt[:nopager]
333
+ print_files(files) if opt[:print_filelist]
334
+ search2(ARGV[0], files) if opt[:do_search]
335
+ end
336
+
337
+ def self.print_files(files)
338
+ files.each do |fn|
339
+ puts fn
340
+ end
341
+ end
342
+
343
+ def self.get_all_files(files=[], dir="./")
344
+ Dir[dir+"*"].each do |fn|
345
+ next if fn == ".."
346
+ if File.directory? fn
347
+ if opt[:descend]
348
+ if File.symlink? fn
349
+ if opt[:follow_symlinks]
350
+ get_all_files(files, fn+"/")
351
+ end
352
+ else
353
+ get_all_files(files, fn+"/")
354
+ end
355
+ end
356
+ else
357
+ if !File.socket? fn and File.readable? fn
358
+ if File.symlink? fn
359
+ if opt[:follow_symlinks]
360
+ files << fn
361
+ end
362
+ else
363
+ files << fn
364
+ end
365
+ end
366
+ end
367
+ end
368
+ files
369
+ end
370
+
371
+ def self.get_files(dir="./")
372
+ types_files = FILE_TYPES.invert
373
+ types = types_files.keys.flatten
374
+ files_and_dirs = ARGV[1..-1]
375
+ io_files = []
376
+ if !files_and_dirs or files_and_dirs.empty?
377
+ if STDIN.isatty == false
378
+ io_files << STDIN
379
+ opt[:print_filename] = false
380
+ files = []
381
+ else
382
+ files = get_all_files
383
+ end
384
+ else
385
+ files = []
386
+ files_and_dirs.each do |thing|
387
+ if File.file? thing
388
+ files << thing
389
+ end
390
+ if File.directory? thing
391
+ get_all_files(files, thing)
392
+ end
393
+ end
394
+ if files_and_dirs.length == 1 and
395
+ File.file? files_and_dirs[0]
396
+ opt[:print_filename] = false
397
+ end
398
+ end
399
+
400
+ # strip out based on extension
401
+ shebang_files = []
402
+ unless opt[:all_files]
403
+ files = files.select do |fn|
404
+ fn[-1..-1] != "~" and
405
+ fn[-1..-1] != "#"
406
+ end
407
+ new_files = []
408
+ files.each do |fn|
409
+ if types.any? {|ext| File.extname(fn) == ext or fn.split("/").last == ext}
410
+ new_files << fn
411
+ elsif ext = shebang_matches(fn)
412
+ shebang_files << [fn, ext]
413
+ end
414
+ end
415
+ files = new_files
416
+ end
417
+
418
+ # select based on file type
419
+ if not opt[:includes].empty?
420
+ files = files.select do |fn|
421
+ opt[:includes].any? {|ext| fn.include? ext}
422
+ end
423
+ shebang_files2 = []
424
+ shebang_files.each do |fn, ext|
425
+ if opt[:includes].include? ext
426
+ shebang_files2 << [fn, ext]
427
+ end
428
+ end
429
+ else
430
+ shebang_files2 = shebang_files
431
+ end
432
+
433
+ if not opt[:excludes].empty?
434
+ files = files.select do |fn|
435
+ not opt[:excludes].any? {|ext| fn.include? ext}
436
+ end
437
+ shebang_files3 = []
438
+ shebang_files2.each do |fn, ext|
439
+ unless opt[:excludes].any? {|ext| fn.include? ext}
440
+ shebang_files3 << [fn, ext]
441
+ end
442
+ end
443
+ else
444
+ shebang_files3 = shebang_files2
445
+ end
446
+
447
+ files += shebang_files3.map {|fn, ext| fn}
448
+
449
+ # reject version control dirs
450
+ files.reject! do |fn|
451
+ VC_DIRS.any? {|vc| fn.include? vc}
452
+ end
453
+
454
+ # filter based on -g=REGEX
455
+ if opt[:filename_regex]
456
+ if $use_onig
457
+ fn_re = Oniguruma::ORegexp.new(opt[:filename_regex])
458
+ else
459
+ fn_re = Regexp.new(opt[:filename_regex])
460
+ end
461
+ files = files.select {|fn| fn =~ fn_re }
462
+ end
463
+
464
+ # remove the "./"
465
+ files.map! {|fn| fn[0..1] == "./" ? fn[2..-1] : fn}
466
+
467
+ if opt[:sort_files]
468
+ files = files.sort_by {|fn| fn.downcase}
469
+ end
470
+
471
+ files + io_files
472
+ end
473
+
474
+ def self.shebang_matches(fn)
475
+ File.open(fn) do |f|
476
+ begin
477
+ line = f.readline
478
+ rescue
479
+ line = ""
480
+ end
481
+ if line =~ /^#!/
482
+ if line =~ /\b(ruby|perl|php|python)\b/
483
+ return "."+FILE_TYPES[$1.intern].first
484
+ elsif line =~ /\b(bash|csh|ksh|zsh)\b/
485
+ return ".sh"
486
+ end
487
+ else
488
+ if line =~ /\Q<?xml /
489
+ return ".xml"
490
+ end
491
+ end
492
+ return false
493
+ end
494
+ end
495
+
496
+ def self.search2(str, files)
497
+ if opt[:literal]
498
+ str = Regexp.quote(str)
499
+ end
500
+ if opt[:match_whole_words]
501
+ str = "\\b" + str + "\\b"
502
+ end
503
+ if opt[:match_whole_lines]
504
+ str = "^" + str + "$"
505
+ end
506
+ if opt[:match_line_starts]
507
+ str = "^" + str
508
+ end
509
+ if opt[:match_line_ends]
510
+ str = str + "$"
511
+ end
512
+ if $use_onig
513
+ if opt[:ignore_case]
514
+ re = Oniguruma::ORegexp.new(str, :options => Oniguruma::OPTION_IGNORECASE)
515
+ else
516
+ re = Oniguruma::ORegexp.new(str)
517
+ end
518
+ else
519
+ if opt[:ignore_case]
520
+ re = Regexp.new(str, Regexp::IGNORECASE)
521
+ else
522
+ re = Regexp.new(str)
523
+ end
524
+ end
525
+ files.each do |fn|
526
+ match_file(re, fn)
527
+ end
528
+ end
529
+
530
+ # this is tricky thing. Ignore the "code <<" to see the
531
+ # logic. Because the options never change we conditionally compile
532
+ # the match method based on them. This is gives a 2x speedup over
533
+ # the alternative.
534
+ def self.compile_match_file
535
+ code = ""
536
+ def code.<<(str)
537
+ self.concat(str + "\n")
538
+ end
539
+ code << %{def self.match_file(re, fn) }
540
+ code << %{ displayed_filename = false }
541
+ code << %{ count = 0 }
542
+ code << %{ i = 0 }
543
+ code << %{ matches = [] }
544
+ code << %{ print_num = 0 }
545
+
546
+ if opt[:before_context] > 0
547
+ code << %{ before_context = [] }
548
+ code << %{ before_context_size = opt[:before_context] }
549
+ end
550
+ code << %{ if fn.is_a? String }
551
+ code << %{ f = File.open(fn, "r") }
552
+ code << %{ elsif fn.is_a? IO }
553
+ code << %{ f = fn }
554
+ code << %{ end }
555
+
556
+ code << %{ f.each_line do |line| }
557
+ code << %{ i += 1 }
558
+ code << %{ rest = line }
559
+ code << %{ while md = re.match(rest) }
560
+ code << %{ rest = md.post_match }
561
+ if opt[:print_output]
562
+ code << %{ matches << eval(opt[:print_output]) }
563
+ else
564
+ code << %{ matches << md }
565
+ end
566
+ if opt[:print_match]
567
+ code << %{ puts md.to_s }
568
+ end
569
+ code << %{ count += 1 }
570
+ code << %{ end }
571
+ if opt[:invert_match]
572
+ if opt[:print_filename]
573
+ code << %{ unless displayed_filename }
574
+ code << %{ puts FILE_COLOUR + fn + CLEAR_COLOURS }
575
+ code << %{ displayed_filename = true }
576
+ code << %{ end }
577
+ end
578
+ code << %{ if matches.empty? }
579
+ if opt[:print_highlighted]
580
+ if opt[:print_line_number]
581
+ code << %{ print "\#{i.to_s.rjust(4)}|" }
582
+ end
583
+ code << %{ puts "\#{line}" }
584
+ end
585
+ code << %{ end }
586
+ code << %{ matches.clear }
587
+ else
588
+ code << %{ if matches.empty? }
589
+ if opt[:print_entire_line_if_no_match]
590
+ code << %{ puts line }
591
+ end
592
+ if opt[:use_context]
593
+ if opt[:before_context] > 0
594
+ code << %{ if print_num == 0 }
595
+ code << %{ before_context << [i, line] }
596
+ code << %{ if before_context.length == before_context_size+1 }
597
+ code << %{ before_context = before_context[1..-1] }
598
+ code << %{ end }
599
+ code << %{ end }
600
+ end
601
+ code << %{ if print_num > 0 }
602
+ code << %{ print_num -= 1 }
603
+ if opt[:print_highlighted]
604
+ if opt[:print_line_number]
605
+ code << %{ print "\#{i.to_s.rjust(4)}|" }
606
+ end
607
+ code << %{ puts "\#{line}" }
608
+ code << %{ end }
609
+ end
610
+ end
611
+ code << %{ else }
612
+ code << %{ print_num = opt[:after_context] }
613
+ if opt[:print_filename]
614
+ code << %{ unless displayed_filename }
615
+ code << %{ puts FILE_COLOUR + fn + CLEAR_COLOURS }
616
+ code << %{ displayed_filename = true }
617
+ code << %{ end }
618
+ end
619
+ if opt[:before_context] > 0
620
+ code << %{ before_context.each do |before_i, before_line| }
621
+ if opt[:print_line_number]
622
+ code << %{ print "\#{before_i.to_s.rjust(4)}|" }
623
+ end
624
+ code << %{ puts before_line }
625
+ code << %{ end }
626
+ code << %{ before_context = [] }
627
+ end
628
+ if opt[:print_output]
629
+ code << %{ matches.each {|m| puts m} }
630
+ end
631
+ if opt[:print_highlighted]
632
+ if opt[:print_line_number]
633
+ code << %{ prefix = "\#{i.to_s.rjust(4)}|" }
634
+ else
635
+ if opt[:print_file_each_line]
636
+ code << %{ fn_str = (fn.is_a?(String) ? FILE_COLOUR + fn + CLEAR_COLOURS + " " : "") }
637
+ code << %{ prefix = fn_str + "\#{i.to_s.rjust(4)}|" }
638
+ else
639
+ code << %{ prefix = "" }
640
+ end
641
+ end
642
+ code << %{ print_highlighted(prefix, line, matches) }
643
+ end
644
+ code << %{ end }
645
+ if opt[:max_count]
646
+ code << %{ if count >= opt[:max_count] }
647
+ code << %{ break }
648
+ code << %{ end }
649
+ end
650
+ code << %{ matches.clear }
651
+ end
652
+ code << %{ end }
653
+ if opt[:print_highlighted] and opt[:print_filename]
654
+ code << %{ if count > 0 }
655
+ code << %{ puts }
656
+ code << %{ end }
657
+ end
658
+ code << %{ f.close if f === File }
659
+ if opt[:print_num_matches]
660
+ if opt[:invert_match]
661
+ code << %{ puts "\#{fn}:\#{i-count}" }
662
+ else
663
+ code << %{ puts "\#{fn}:\#{count}" }
664
+ end
665
+ end
666
+ if opt[:print_file_if_match]
667
+ code << %{ if count > 0 }
668
+ code << %{ puts fn }
669
+ code << %{ end }
670
+ end
671
+
672
+ if opt[:print_file_if_no_match]
673
+ code << %{ if count == 0 }
674
+ code << %{ puts fn }
675
+ code << %{ end }
676
+ end
677
+ code << %{end }
678
+ module_eval code
679
+ end
680
+
681
+ def self.print_highlighted(prefix, line, matches)
682
+ from1 = matches[0].begin(0)
683
+ pre = ""
684
+ pre = line[0..(from1-1)] if from1 > 0
685
+ print prefix
686
+ print pre
687
+ ptr = 0
688
+ 0.upto(matches.length-1) do |n|
689
+ from = matches[n].begin(0)
690
+ to = matches[n].end(0)
691
+ print MATCH_COLOUR +
692
+ line[(from+ptr)..(to+ptr-1)] +
693
+ CLEAR_COLOURS
694
+ ptr += to
695
+ if n == matches.length - 1
696
+ puts matches.last.post_match
697
+ else
698
+ print line[ptr..(matches[n+1].begin(0)-1+ptr)]
699
+ end
700
+ end
701
+ end
702
+
703
+ def self.unknown_type(type)
704
+ puts "rak: Unknown --type \"#{type}\""
705
+ puts "rak: See rak --help types"
706
+ exit
707
+ end
708
+
709
+ def self.run_pager(opt_pager = nil)
710
+ return if PLATFORM =~ /win32/
711
+ return unless STDOUT.tty?
712
+
713
+ read, write = IO.pipe
714
+
715
+ unless Kernel.fork # Child process
716
+ STDOUT.reopen(write)
717
+ STDERR.reopen(write) if STDERR.tty?
718
+ read.close
719
+ write.close
720
+ return
721
+ end
722
+
723
+ # Parent process, become pager
724
+ STDIN.reopen(read)
725
+ read.close
726
+ write.close
727
+
728
+ ENV['LESS'] = 'FSRX' # Don't page if the input is short enough
729
+
730
+ # wait until we have input before we start the pager
731
+ Kernel.select [STDIN]
732
+ pager = opt_pager || ENV['PAGER'] || 'less -r'
733
+ exec pager rescue exec "/bin/sh", "-c", pager
734
+ rescue
735
+ end
736
+ end
737
+
738
+ USAGE_HELP=<<END
739
+ Usage: rak [OPTION]... PATTERN [FILES]
740
+
741
+ Search for PATTERN in each source file in the tree from cwd on down.
742
+ If [FILES] is specified, then only those files/directories are checked.
743
+ rak will search STDIN only if no FILES are specified.
744
+
745
+ Example: rak -i select
746
+
747
+ Searching:
748
+ -i, --ignore-case Ignore case distinctions
749
+ -v, --invert-match Invert match: select non-matching lines
750
+ -w, --word-regexp Force PATTERN to match only whole words
751
+ -x, --line-regexp Force PATTERN to match only whole lines
752
+ -Q, --literal Quote all metacharacters; expr is literal
753
+ -s, --line-start Match only at the start of a line
754
+ -e, --line-start Match only at the end of a line
755
+
756
+ Search output:
757
+ -l, --files-with-matches
758
+ Only print filenames containing matches
759
+ -L, --files-without-match
760
+ Only print filenames with no match
761
+ -o Show only the part of a line matching PATTERN
762
+ (turns off text highlighting)
763
+ --passthru Print all lines, whether matching or not
764
+ --output=expr Output the evaluation of expr for each line
765
+ (turns off text highlighting)
766
+ -m, --max-count=NUM Stop searching in a file after NUM matches
767
+ -H, --with-filename Print the filename for each match
768
+ -h, --no-filename Suppress the prefixing filename on output
769
+ -c, --count Show number of lines matching per file
770
+
771
+ --pager=COMMAND Pipes all rak output through COMMAND.
772
+ (default: ENV['PAGER'] or "less -r")
773
+ --nopager Do not send output through a pager.
774
+
775
+ --group Group matches by file name.
776
+ (default: on when used interactively)
777
+ --nogroup One result per line, including filename, like grep
778
+ (default: on when the output is redirected)
779
+
780
+ --[no]colour Highlight the matching text (default: on unless
781
+ output is redirected, or on Windows)
782
+
783
+ -A NUM, --after-context=NUM
784
+ Print NUM lines of trailing context after matching
785
+ lines.
786
+ -B NUM, --before-context=NUM
787
+ Print NUM lines of leading context before matching
788
+ lines.
789
+ -C [NUM], --context[=NUM]
790
+ Print NUM lines (default 2) of output context.
791
+
792
+ File finding:
793
+ -f Only print the files found, without searching.
794
+ The PATTERN must not be specified.
795
+ --sort-files Sort the found files lexically.
796
+
797
+ File inclusion/exclusion:
798
+ -n No descending into subdirectories
799
+ -g REGEX Only search in file matching REGEX.
800
+ -a, --all All files, regardless of extension (but still skips
801
+ blib, pkg, CVS, _darcs, .git, .pc, RCS, SCCS and .svn dirs)
802
+ --ruby Include only Ruby files.
803
+ --type=ruby Include only Ruby files.
804
+ --noruby Exclude Ruby files.
805
+ --type=noruby Exclude Ruby files.
806
+ See "rak --help type" for supported filetypes.
807
+ --[no]follow Follow symlinks. Default is off.
808
+
809
+ Miscellaneous:
810
+ --help This help
811
+ --version Display version & copyright
812
+ END
813
+
814
+ TYPES_HELP=<<END
815
+ Usage: rak [OPTION]... PATTERN [FILES]
816
+
817
+ The following is the list of filetypes supported by rak. You can
818
+ specify a file type with the --type=TYPE format, or the --TYPE
819
+ format. For example, both --type=ruby and --ruby work.
820
+
821
+ Note that some extensions may appear in multiple types. For example,
822
+ .pod files are both Perl and Parrot.
823
+
824
+ --[no]asm .s
825
+ --[no]cc .c .h .xs
826
+ --[no]cpp .cpp .m .h
827
+ --[no]csharp .cs
828
+ --[no]css .css
829
+ --[no]elisp .el
830
+ --[no]erlang .erl
831
+ --[no]fortran .f .f77 .f90 .f95 .f03 .for .ftn .fpp
832
+ --[no]haskell .hs .lhs
833
+ --[no]hh .h
834
+ --[no]html .htm .html .shtml
835
+ --[no]java .java .properties
836
+ --[no]js .js
837
+ --[no]jsp .jsp .jspx .jhtm .jhtml
838
+ --[no]lisp .lisp
839
+ --[no]make Makefile
840
+ --[no]mason .mas .mhtml .mpl .mtxt
841
+ --[no]ocaml .ml .mli
842
+ --[no]parrot .pir .pasm .pmc .ops .pod .pg .tg
843
+ --[no]perl .pl .pm .pod .t
844
+ --[no]php .php .phpt .php3 .php4 .php5
845
+ --[no]prolog .pl .ecl
846
+ --[no]python .py
847
+ --[no]ruby .rb .rhtml .rjs .rxml Rakefile .rake .erb .haml
848
+ --[no]scheme .scm
849
+ --[no]shell .sh .bash .csh .ksh .zsh
850
+ --[no]sql .sql .ctl
851
+ --[no]tcl .tcl
852
+ --[no]tex .tex .cls .sty
853
+ --[no]text .txt .text
854
+ --[no]tt .tt .tt2 .ttml
855
+ --[no]vb .bas .cls .frm .ctl .vb .resx
856
+ --[no]vim .vim
857
+ --[no]xml .xml .dtd .xslt
858
+ --[no]yaml .yaml .yml
859
+ END
860
+
861
+ Rak.search