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 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