glark 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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,4 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'glark/glark'
@@ -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