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