glark 1.9.0
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.
- data/README +0 -0
- data/bin/glark +37 -0
- data/bin/jlark +63 -0
- data/lib/glark.rb +4 -0
- data/lib/glark/expression.rb +440 -0
- data/lib/glark/exprfactory.rb +248 -0
- data/lib/glark/glark.rb +297 -0
- data/lib/glark/help.rb +85 -0
- data/lib/glark/input.rb +183 -0
- data/lib/glark/options.rb +757 -0
- data/lib/glark/output.rb +266 -0
- data/test/lib/glark/glark_test.rb +317 -0
- data/test/lib/glark/options_test.rb +891 -0
- metadata +95 -0
data/README
ADDED
File without changes
|
data/bin/glark
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# exec ruby -w -x $0 ${1+"$@"} # -*- ruby -*-
|
3
|
+
#!ruby -w
|
4
|
+
# vim: set filetype=ruby : set sw=2
|
5
|
+
|
6
|
+
# An extended grep, with extended functionality including full regular
|
7
|
+
# expressions, contextual output, highlighting, detection and exclusion of
|
8
|
+
# nontext files, and complex matching criteria.
|
9
|
+
|
10
|
+
dir = File.dirname(File.dirname(File.expand_path(__FILE__)))
|
11
|
+
|
12
|
+
libpath = dir + "/lib"
|
13
|
+
$:.unshift libpath
|
14
|
+
|
15
|
+
require 'glark/glark'
|
16
|
+
|
17
|
+
Glark.main
|
18
|
+
|
19
|
+
__END__
|
20
|
+
# prototype of forthcoming feature:
|
21
|
+
|
22
|
+
# multi-pass execution:
|
23
|
+
./glark --run=2 '/(\w+)\s*=\s*\d+/' *.c
|
24
|
+
|
25
|
+
# means extract twice:
|
26
|
+
|
27
|
+
first run:
|
28
|
+
matches = Array.new
|
29
|
+
GlarkOptions.matches = matches
|
30
|
+
glark = Glark.new(ARGV, :write => false)
|
31
|
+
glark.search($files)
|
32
|
+
|
33
|
+
second run:
|
34
|
+
GlarkOptions.matches = nil
|
35
|
+
expr = MultiOrExpression.new(matches)
|
36
|
+
glark = Glark.new(ARGV, :expr => expr)
|
37
|
+
glark.search($files)
|
data/bin/jlark
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
exec ruby -w -x $0 ${1+"$@"} # -*- ruby -*-
|
3
|
+
#!ruby -w
|
4
|
+
# vim: set filetype=ruby : set sw=2
|
5
|
+
|
6
|
+
# Extends glark into Java archives (jar and zip files)
|
7
|
+
|
8
|
+
# $Id$
|
9
|
+
|
10
|
+
dir = File.dirname(File.dirname(File.expand_path(__FILE__)))
|
11
|
+
|
12
|
+
libpath = dir + "/share"
|
13
|
+
$:.unshift libpath
|
14
|
+
|
15
|
+
require 'glark/glark'
|
16
|
+
|
17
|
+
$PACKAGE = "jlark"
|
18
|
+
|
19
|
+
class Jlark < Glark
|
20
|
+
|
21
|
+
def search(name)
|
22
|
+
case name[-4 .. -1]
|
23
|
+
when '.jar'
|
24
|
+
search_jar_file(name)
|
25
|
+
when '.zip'
|
26
|
+
search_zip_file(name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def search_archive_file(fname, command)
|
31
|
+
if skipped?(fname)
|
32
|
+
log { "skipping file: #{fname}" }
|
33
|
+
else
|
34
|
+
IO.popen(command) do |io|
|
35
|
+
ifile_args = {
|
36
|
+
:after => @after,
|
37
|
+
:before => @before,
|
38
|
+
:output => @output
|
39
|
+
}
|
40
|
+
|
41
|
+
input = InputFile.new(fname, io, ifile_args)
|
42
|
+
search_file(input)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def search_jar_file(fname)
|
48
|
+
search_archive_file(fname, "jar tvf #{fname}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def search_zip_file(fname)
|
52
|
+
search_archive_file(fname, "unzip -l #{fname}")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# and use a cached file for .jar and .zip files
|
57
|
+
|
58
|
+
# the pattern is always -r ., with --classpath optional
|
59
|
+
class JlarkOptions < GlarkOptions
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
Jlark.main
|
data/lib/glark.rb
ADDED
@@ -0,0 +1,440 @@
|
|
1
|
+
#!/usr/bin/ruby -w
|
2
|
+
#!ruby -w
|
3
|
+
# vim: set filetype=ruby : set sw=2
|
4
|
+
|
5
|
+
# Extended regular-expression-based expressions.
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'riel'
|
9
|
+
|
10
|
+
require 'glark/options'
|
11
|
+
|
12
|
+
# A function object, which can be applied (processed) against a InputFile.
|
13
|
+
class FuncObj
|
14
|
+
include Loggable
|
15
|
+
|
16
|
+
attr_accessor :match_line_number, :file, :matches, :invert_match
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@match_line_number = nil
|
20
|
+
@matches = Array.new
|
21
|
+
|
22
|
+
opts = GlarkOptions.instance
|
23
|
+
@invert_match = opts.invert_match
|
24
|
+
@display_matches = !opts.file_names_only && opts.filter && !opts.count
|
25
|
+
@range_start = opts.range_start
|
26
|
+
@range_end = opts.range_end
|
27
|
+
@file_names_only = opts.file_names_only
|
28
|
+
@match_limit = opts.match_limit
|
29
|
+
@write_null = opts.write_null
|
30
|
+
@filter = opts.filter
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_match(lnum)
|
34
|
+
@matches.push(lnum)
|
35
|
+
end
|
36
|
+
|
37
|
+
def start_position
|
38
|
+
match_line_number
|
39
|
+
end
|
40
|
+
|
41
|
+
def end_position
|
42
|
+
start_position
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset_file(file)
|
46
|
+
@match_line_number = nil
|
47
|
+
@file = file
|
48
|
+
@matches = Array.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def range(var, infile)
|
52
|
+
if var
|
53
|
+
if var.index(/([\.\d]+)%/)
|
54
|
+
count = infile.linecount
|
55
|
+
count * $1.to_f / 100
|
56
|
+
else
|
57
|
+
var.to_f
|
58
|
+
end
|
59
|
+
else
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def process(infile)
|
65
|
+
got_match = false
|
66
|
+
reset_file(infile.fname)
|
67
|
+
|
68
|
+
rgstart = range(@range_start, infile)
|
69
|
+
rgend = range(@range_end, infile)
|
70
|
+
|
71
|
+
lastmatch = 0
|
72
|
+
nmatches = 0
|
73
|
+
lnum = 0
|
74
|
+
infile.each_line do |line|
|
75
|
+
if ((!rgstart || lnum >= rgstart) &&
|
76
|
+
(!rgend || lnum <= rgend) &&
|
77
|
+
evaluate(line, lnum, infile))
|
78
|
+
|
79
|
+
mark_as_match(infile)
|
80
|
+
got_match = true
|
81
|
+
nmatches += 1
|
82
|
+
|
83
|
+
if @display_matches
|
84
|
+
infile.write_matches(!@invert_match, lastmatch, lnum)
|
85
|
+
lastmatch = lnum + 1
|
86
|
+
elsif @file_names_only
|
87
|
+
# we don't need to match more than once
|
88
|
+
break
|
89
|
+
end
|
90
|
+
|
91
|
+
if @match_limit && nmatches >= @match_limit
|
92
|
+
# we've found the match limit
|
93
|
+
break
|
94
|
+
end
|
95
|
+
end
|
96
|
+
lnum += 1
|
97
|
+
end
|
98
|
+
|
99
|
+
if @file_names_only
|
100
|
+
if got_match != @invert_match
|
101
|
+
if @write_null
|
102
|
+
print infile.fname + "\0"
|
103
|
+
else
|
104
|
+
puts infile.fname
|
105
|
+
end
|
106
|
+
end
|
107
|
+
elsif @filter
|
108
|
+
if @invert_match
|
109
|
+
infile.write_matches(false, 0, lnum)
|
110
|
+
elsif got_match
|
111
|
+
infile.write_matches(true, 0, lnum)
|
112
|
+
end
|
113
|
+
else
|
114
|
+
infile.write_all
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def mark_as_match(infile)
|
119
|
+
infile.mark_as_match(start_position)
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_s
|
123
|
+
str = inspect
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# -------------------------------------------------------
|
130
|
+
# Regular expression function object
|
131
|
+
# -------------------------------------------------------
|
132
|
+
|
133
|
+
# Applies a regular expression against a InputFile.
|
134
|
+
class RegexpFuncObj < FuncObj
|
135
|
+
|
136
|
+
attr_reader :re
|
137
|
+
|
138
|
+
def initialize(re, hlidx, args = Hash.new)
|
139
|
+
@re = re
|
140
|
+
@file = nil
|
141
|
+
if @highlight = args[:highlight]
|
142
|
+
@text_highlights = args[:text_highlights]
|
143
|
+
@hlidx = if @text_highlights.length > 0 && args[:highlight] == "multi"
|
144
|
+
hlidx % @text_highlights.length
|
145
|
+
else
|
146
|
+
0
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
@extract_matches = args[:extract_matches]
|
151
|
+
|
152
|
+
super()
|
153
|
+
end
|
154
|
+
|
155
|
+
def <=>(other)
|
156
|
+
@re <=> other.re
|
157
|
+
end
|
158
|
+
|
159
|
+
def ==(other)
|
160
|
+
@re == other.re
|
161
|
+
end
|
162
|
+
|
163
|
+
def inspect
|
164
|
+
@re.inspect
|
165
|
+
end
|
166
|
+
|
167
|
+
def match?(line)
|
168
|
+
@re.match(line)
|
169
|
+
end
|
170
|
+
|
171
|
+
def evaluate(line, lnum, file)
|
172
|
+
if Log.verbose
|
173
|
+
log { "evaluating <<<#{line[0 .. -2]}>>>" }
|
174
|
+
end
|
175
|
+
|
176
|
+
if md = match?(line)
|
177
|
+
log { "matched" }
|
178
|
+
if @extract_matches
|
179
|
+
if md.kind_of?(MatchData)
|
180
|
+
line.replace(md[-1] + "\n")
|
181
|
+
else
|
182
|
+
warn "--not does not work with -v"
|
183
|
+
end
|
184
|
+
else
|
185
|
+
# log { "NOT replacing line" }
|
186
|
+
end
|
187
|
+
|
188
|
+
@match_line_number = lnum
|
189
|
+
|
190
|
+
if @highlight
|
191
|
+
highlight_match(lnum, file)
|
192
|
+
end
|
193
|
+
|
194
|
+
add_match(lnum)
|
195
|
+
true
|
196
|
+
else
|
197
|
+
false
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def explain(level = 0)
|
202
|
+
" " * level + to_s + "\n"
|
203
|
+
end
|
204
|
+
|
205
|
+
def highlight_match(lnum, file)
|
206
|
+
log { "lnum: #{lnum}; file: #{file}" }
|
207
|
+
|
208
|
+
lnums = file.get_range(lnum)
|
209
|
+
log { "lnums(#{lnum}): #{lnums}" }
|
210
|
+
if lnums
|
211
|
+
lnums.each do |ln|
|
212
|
+
str = file.output.formatted[ln] || file.get_line(ln)
|
213
|
+
if Log.verbose
|
214
|
+
log { "file.output.formatted[#{ln}]: #{file.output.formatted[ln]}" }
|
215
|
+
log { "file.get_line(#{ln}): #{file.get_line(ln)}" }
|
216
|
+
log { "highlighting: #{str}" }
|
217
|
+
end
|
218
|
+
|
219
|
+
file.output.formatted[ln] = str.gsub(@re) do |m|
|
220
|
+
lastcapts = Regexp.last_match.captures
|
221
|
+
# find the index of the first non-nil capture:
|
222
|
+
miidx = (0 ... lastcapts.length).find { |mi| lastcapts[mi] } || @hlidx
|
223
|
+
|
224
|
+
@text_highlights[miidx].highlight(m)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
# -------------------------------------------------------
|
234
|
+
# Compound expression function object
|
235
|
+
# -------------------------------------------------------
|
236
|
+
|
237
|
+
# Associates a pair of expressions.
|
238
|
+
class CompoundExpression < FuncObj
|
239
|
+
|
240
|
+
attr_reader :ops
|
241
|
+
|
242
|
+
def initialize(*ops)
|
243
|
+
@ops = ops
|
244
|
+
@file = nil
|
245
|
+
super()
|
246
|
+
end
|
247
|
+
|
248
|
+
def reset_file(file)
|
249
|
+
@ops.each do |op|
|
250
|
+
op.reset_file(file)
|
251
|
+
end
|
252
|
+
super
|
253
|
+
end
|
254
|
+
|
255
|
+
def start_position
|
256
|
+
@last_start
|
257
|
+
end
|
258
|
+
|
259
|
+
def ==(other)
|
260
|
+
self.class == other.class && @ops == other.ops
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
|
266
|
+
# -------------------------------------------------------
|
267
|
+
# Multi-Or expression function object
|
268
|
+
# -------------------------------------------------------
|
269
|
+
|
270
|
+
# Evaluates both expressions.
|
271
|
+
class MultiOrExpression < CompoundExpression
|
272
|
+
|
273
|
+
def evaluate(line, lnum, file)
|
274
|
+
matched_ops = @ops.select do |op|
|
275
|
+
op.evaluate(line, lnum, file)
|
276
|
+
end
|
277
|
+
|
278
|
+
if is_match?(matched_ops)
|
279
|
+
lastmatch = matched_ops[-1]
|
280
|
+
@last_start = lastmatch.start_position
|
281
|
+
@last_end = lastmatch.end_position
|
282
|
+
@match_line_number = lnum
|
283
|
+
|
284
|
+
add_match(lnum)
|
285
|
+
true
|
286
|
+
else
|
287
|
+
false
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def inspect
|
292
|
+
"(" + @ops.collect { |op| op.to_s }.join(" " + operator + " ") + ")"
|
293
|
+
end
|
294
|
+
|
295
|
+
def end_position
|
296
|
+
@last_end
|
297
|
+
end
|
298
|
+
|
299
|
+
def explain(level = 0)
|
300
|
+
str = " " * level + criteria + ":\n"
|
301
|
+
str += @ops.collect { |op| op.explain(level + 4) }.join(" " * level + operator + "\n")
|
302
|
+
str
|
303
|
+
end
|
304
|
+
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
# -------------------------------------------------------
|
309
|
+
# Inclusive or expression function object
|
310
|
+
# -------------------------------------------------------
|
311
|
+
|
312
|
+
# Evaluates the expressions, and is satisfied when one return true.
|
313
|
+
class InclusiveOrExpression < MultiOrExpression
|
314
|
+
|
315
|
+
def is_match?(matched_ops)
|
316
|
+
return matched_ops.size > 0
|
317
|
+
end
|
318
|
+
|
319
|
+
def operator
|
320
|
+
"or"
|
321
|
+
end
|
322
|
+
|
323
|
+
def criteria
|
324
|
+
ops.size == 2 ? "either" : "any of"
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
|
330
|
+
# -------------------------------------------------------
|
331
|
+
# Exclusive or expression function object
|
332
|
+
# -------------------------------------------------------
|
333
|
+
|
334
|
+
# Evaluates the expressions, and is satisfied when only one returns true.
|
335
|
+
class ExclusiveOrExpression < MultiOrExpression
|
336
|
+
|
337
|
+
def is_match?(matched_ops)
|
338
|
+
return matched_ops.size == 1
|
339
|
+
end
|
340
|
+
|
341
|
+
def operator
|
342
|
+
"xor"
|
343
|
+
end
|
344
|
+
|
345
|
+
def criteria
|
346
|
+
"only one of"
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|
350
|
+
|
351
|
+
|
352
|
+
# -------------------------------------------------------
|
353
|
+
# And expression function object
|
354
|
+
# -------------------------------------------------------
|
355
|
+
|
356
|
+
# Evaluates both expressions, and is satisfied when both return true.
|
357
|
+
class AndExpression < CompoundExpression
|
358
|
+
|
359
|
+
def initialize(dist, op1, op2)
|
360
|
+
@dist = dist
|
361
|
+
super(op1, op2)
|
362
|
+
end
|
363
|
+
|
364
|
+
def mark_as_match(infile)
|
365
|
+
infile.mark_as_match(start_position, end_position)
|
366
|
+
end
|
367
|
+
|
368
|
+
def match_within_distance(op, lnum)
|
369
|
+
stack "op: #{op}; lnum: #{lnum}"
|
370
|
+
op.matches.size > 0 and (op.matches[-1] - lnum <= @dist)
|
371
|
+
end
|
372
|
+
|
373
|
+
def inspect
|
374
|
+
str = "("+ @ops[0].to_s
|
375
|
+
if @dist == 0
|
376
|
+
str += " same line as "
|
377
|
+
elsif @dist.kind_of?(Float) && @dist.infinite?
|
378
|
+
str += " same file as "
|
379
|
+
else
|
380
|
+
str += " within " + @dist.to_s + " lines of "
|
381
|
+
end
|
382
|
+
str += @ops[1].to_s + ")"
|
383
|
+
str
|
384
|
+
end
|
385
|
+
|
386
|
+
def match?(line, lnum, file)
|
387
|
+
matches = (0 ... @ops.length).select do |oi|
|
388
|
+
@ops[oi].evaluate(line, lnum, file)
|
389
|
+
end
|
390
|
+
|
391
|
+
matches.each do |mi|
|
392
|
+
oidx = (1 + mi) % @ops.length
|
393
|
+
other = @ops[oidx]
|
394
|
+
if match_within_distance(other, lnum)
|
395
|
+
# search for the maximum match within the distance limit
|
396
|
+
other.matches.each do |m|
|
397
|
+
if lnum - m <= @dist
|
398
|
+
log { "match: #{m} within range #{@dist} of #{lnum}" }
|
399
|
+
@last_start = m
|
400
|
+
return true
|
401
|
+
end
|
402
|
+
end
|
403
|
+
log { "other matches out of range" }
|
404
|
+
return false
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
return false
|
409
|
+
end
|
410
|
+
|
411
|
+
def end_position
|
412
|
+
@ops.collect { |op| op.end_position }.max
|
413
|
+
end
|
414
|
+
|
415
|
+
def evaluate(line, lnum, file)
|
416
|
+
if match?(line, lnum, file)
|
417
|
+
@match_line_number = lnum
|
418
|
+
true
|
419
|
+
else
|
420
|
+
false
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def explain(level = 0)
|
425
|
+
str = ""
|
426
|
+
if @dist == 0
|
427
|
+
str += " " * level + "on the same line:\n"
|
428
|
+
elsif @dist.kind_of?(Float) && @dist.infinite?
|
429
|
+
str += " " * level + "in the same file:\n"
|
430
|
+
else
|
431
|
+
lnstr = @dist == 1 ? "line" : "lines"
|
432
|
+
str += " " * level + "within #{@dist} #{lnstr} of each other:\n"
|
433
|
+
end
|
434
|
+
str += @ops[0].explain(level + 4)
|
435
|
+
str += " " * level + "and\n"
|
436
|
+
str += @ops[1].explain(level + 4)
|
437
|
+
str
|
438
|
+
end
|
439
|
+
|
440
|
+
end
|