glark 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/glark/help.rb ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ # Glark help.
5
+
6
+ class GlarkHelp
7
+
8
+ def initialize
9
+ puts "Usage: glark [options] expression file..."
10
+ puts "Search for expression in each file or standard input."
11
+ puts "Example: glark --and=3 'try' 'catch' *.java"
12
+ puts ""
13
+
14
+ puts "Input:"
15
+ puts " -0[nnn] Use \\nnn as the input record separator"
16
+ puts " -d, --directories=ACTION Process directories as read, skip, or recurse"
17
+ puts " --binary-files=TYPE Treat binary files as TYPE"
18
+ puts " --[with-]basename, "
19
+ puts " --[with-]name EXPR Search only files with base names matching EXPR"
20
+ puts " --without-basename, "
21
+ puts " --without-name EXPR Ignore files with base names matching EXPR"
22
+ puts " --[with-]fullname, "
23
+ puts " --[with-]path EXPR Search only files with full names matching EXPR"
24
+ puts " --without-fullname, "
25
+ puts " --without-path EXPR Ignore files with full names matching EXPR"
26
+ puts " -M, --exclude-matching Ignore files with names matching the expression"
27
+ puts " -r, --recurse Recurse through directories"
28
+ puts " --size-limit=SIZE Search only files no larger than SIZE"
29
+ puts ""
30
+
31
+ puts "Matching:"
32
+ puts " -a, --and=NUM EXPR1 EXPR2 Match both expressions, within NUM lines"
33
+ puts " -b, --before NUM[%] Restrict the search to the top % or lines"
34
+ puts " --after NUM[%] Restrict the search to after the given location"
35
+ puts " -f, --file=FILE Use the lines in the given file as expressions"
36
+ puts " -i, --ignore-case Ignore case for matching regular expressions"
37
+ puts " -m, --match-limit=NUM Find only the first NUM matches in each file"
38
+ puts " -o, --or EXPR1 EXPR2 Match either of the two expressions"
39
+ puts " -R, --range NUM[%] NUM[%] Restrict the search to the given range of lines"
40
+ puts " -v, --invert-match Show lines not matching the expression"
41
+ puts " -w, --word, --word-regexp Put word boundaries around each pattern"
42
+ puts " -x, --line-regexp Select entire line matching pattern"
43
+ puts " --xor EXPR1 EXPR2 Match either expression, but not both"
44
+ puts ""
45
+
46
+ puts "Output:"
47
+ puts " -A, --after-context=NUM Print NUM lines of trailing context"
48
+ puts " -B, --before-context=NUM Print NUM lines of leading context"
49
+ puts " -C, -NUM, --context[=NUM] Output NUM lines of context"
50
+ puts " -c, --count Display only the match count per file"
51
+ puts " -F, --file-color COLOR Specify the highlight color for file names"
52
+ puts " --no-filter Display the entire file"
53
+ puts " -g, --grep Produce output like the grep default"
54
+ puts " -h, --no-filename Do not display the names of matching files"
55
+ puts " -H, --with-filename Display the names of matching files"
56
+ puts " -l, --files-with-matches Print only names of matching file"
57
+ puts " -L, --files-without-match Print only names of file not matching"
58
+ puts " --label=NAME Use NAME as output file name"
59
+ puts " -n, --line-number Display line numbers"
60
+ puts " -N, --no-line-number Do not display line numbers"
61
+ puts " --line-number-color COLOR Specify the highlight color for line numbers"
62
+ # puts " --output=FORMAT Produce output in the format (ansi, grep)"
63
+ puts " -T, --text-color COLOR Specify the highlight color for text"
64
+ puts " --text-color-NUM COLOR Specify the highlight color for regexp capture NUM"
65
+ puts " -u, --highlight[=FORMAT] Enable highlighting. Format is single or multi"
66
+ puts " -U, --no-highlight Disable highlighting"
67
+ puts " -y, --extract-matches Display only the matching region, not the entire line"
68
+ puts " -Z, --null In -l mode, write file names followed by NULL"
69
+ puts ""
70
+
71
+ puts "Debugging/Errors:"
72
+ puts " --conf Write the current options in RC file format"
73
+ puts " --dump Write all options and expressions"
74
+ puts " --explain Write the expression in a more legible format"
75
+ puts " -q, --quiet Suppress warnings"
76
+ puts " -Q, --no-quiet Enable warnings"
77
+ puts " -s, --no-messages Suppress warnings"
78
+ puts " -V, --version Display version information"
79
+ puts " --verbose Display normally suppressed output"
80
+
81
+ puts ""
82
+ puts "See the man page for more information."
83
+ end
84
+
85
+ end
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/ruby -w
2
+ #!ruby -w
3
+ # vim: set filetype=ruby : set sw=2
4
+
5
+ # Glark input.
6
+
7
+ require 'rubygems'
8
+ require 'riel'
9
+
10
+ # A thing that can be grepped.
11
+ class InputFile
12
+ include Loggable
13
+
14
+ attr_reader :fname, :stati
15
+ attr_accessor :count, :output, :invert_match
16
+
17
+ # cross-platform end of line: DOS UNIX MAC
18
+ ANY_END_OF_LINE = Regexp.new('(?:\r\n|\n|\r)')
19
+
20
+ WRITTEN = "written"
21
+
22
+ def initialize(fname, io, args = GlarkOptions.instance)
23
+ @fname = fname
24
+ @io = io
25
+ @stati = Array.new # index = line number, value = context character
26
+ @count = nil
27
+ @extracted = nil
28
+ @regions = nil
29
+ @modlines = nil
30
+ @invert_match = false
31
+ @linecount = nil
32
+ @readall = $/ != "\n"
33
+ @lines = @readall ? IO.readlines(@fname) : Array.new
34
+
35
+ @after = args[:after]
36
+ @before = args[:before]
37
+ @output = args[:output]
38
+
39
+ @matched = false
40
+ end
41
+
42
+ def linecount
43
+ @linecount ||= begin
44
+ IO.readlines(@fname).size
45
+ end
46
+ end
47
+
48
+ def matched?
49
+ @matched
50
+ end
51
+
52
+ def each_line
53
+ if @readall
54
+ @lines.each do |line|
55
+ yield line
56
+ end
57
+ else
58
+ while (line = @io.gets) && line.length > 0
59
+ @lines << line
60
+ yield line
61
+ end
62
+ end
63
+ end
64
+
65
+ def set_status(from, to, ch, force = false)
66
+ from.upto(to) do |ln|
67
+ if (not @stati[ln]) || (@stati[ln] != WRITTEN && force)
68
+ @stati[ln] = ch
69
+ end
70
+ end
71
+ end
72
+
73
+ def mark_as_match(start_line, end_line = start_line)
74
+ @matched = true
75
+
76
+ # even with multi-line matches (--and expressions), we'll display
77
+ # only the first matching line, not the range between the matches.
78
+
79
+ if @output == "grep"
80
+ end_line = start_line
81
+ end
82
+
83
+ if @count
84
+ @count += 1
85
+ else
86
+ st = [0, start_line - @before].max
87
+ set_status(st, start_line - 1, "-")
88
+ set_status(start_line, end_line, ":", true)
89
+ set_status(end_line + 1, end_line + @after, "+")
90
+ end
91
+ end
92
+
93
+ def write_matches(matching, from = nil, to = nil)
94
+ @output.write_matches(matching, from, to)
95
+ end
96
+
97
+ def write_all
98
+ @output.write_all
99
+ end
100
+
101
+ # Returns the lines for this file, separated by end of line sequences.
102
+ def get_lines
103
+ if $/ == "\n"
104
+ @lines
105
+ else
106
+ @extracted ||= begin
107
+ # This is much easier. Just resplit the whole thing at end of line
108
+ # sequences.
109
+
110
+ eoline = "\n" # should be OS-dependent
111
+ srclines = @lines
112
+ reallines = @lines.join("").split(ANY_END_OF_LINE)
113
+
114
+ # "\n" after all but the last line
115
+ extracted = (0 ... (reallines.length - 1)).collect do |lnum|
116
+ reallines[lnum] + eoline
117
+ end
118
+ extracted << reallines[-1]
119
+
120
+ if Log.verbose
121
+ extracted.each_with_index do |line, idx|
122
+ log "extracted[#{idx}]: #{@extracted[idx]}"
123
+ end
124
+ end
125
+ extracted
126
+ end
127
+ end
128
+ end
129
+
130
+ # Returns the given line for this file. For this method, a line ends with a
131
+ # CR, as opposed to the "lines" method, which ends with $/.
132
+ def get_line(lnum)
133
+ log { "lnum: #{lnum}" }
134
+ ln = get_lines()[lnum]
135
+ log { "ln: #{ln}" }
136
+ ln
137
+ end
138
+
139
+ # returns the range that is represented by the region number
140
+ def get_range(rnum)
141
+ if $/ == "\n"
142
+ # easy case: range is the range number, unless it is out of range.
143
+ rnum < @lines.length ? (rnum .. rnum) : nil
144
+ else
145
+ unless @regions
146
+ srclines = @modlines ? @modlines : @lines
147
+
148
+ @regions = [] # keys = region number; values = range of lines
149
+
150
+ lstart = 0
151
+ srclines.each do |line|
152
+ lend = lstart
153
+ line.scan(ANY_END_OF_LINE).each do |cr|
154
+ lend += 1
155
+ end
156
+
157
+ @regions << Range.new(lstart, lend - 1)
158
+
159
+ lstart = lend
160
+ end
161
+ end
162
+
163
+ @regions[rnum]
164
+ end
165
+ end
166
+ end
167
+
168
+
169
+ # -------------------------------------------------------
170
+ # Binary input file
171
+ # -------------------------------------------------------
172
+
173
+ class BinaryFile < InputFile
174
+
175
+ def write_matches(matching, from, to)
176
+ if count
177
+ write_count(matching)
178
+ else
179
+ puts "Binary file " + @fname + " matches"
180
+ end
181
+ end
182
+
183
+ end
@@ -0,0 +1,757 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- ruby -*-
3
+
4
+ require 'pathname'
5
+
6
+ require 'rubygems'
7
+ require 'riel/text'
8
+ require 'riel/log'
9
+ require 'riel/env'
10
+ require 'riel/optproc'
11
+ require 'riel'
12
+
13
+
14
+ # -------------------------------------------------------
15
+ # Options
16
+ # -------------------------------------------------------
17
+
18
+ class GlarkInputOptions
19
+ include Loggable, Singleton
20
+
21
+ end
22
+
23
+
24
+ class GlarkOptions
25
+ include Loggable, Singleton
26
+
27
+ attr_accessor :after
28
+ attr_accessor :before
29
+ attr_accessor :binary_files
30
+ attr_accessor :count
31
+ attr_accessor :directory
32
+ attr_accessor :exclude_matching
33
+ attr_accessor :explain
34
+ attr_accessor :expr
35
+ attr_accessor :extended
36
+ attr_accessor :extract_matches
37
+ attr_accessor :file_highlight
38
+ attr_accessor :file_names_only
39
+ attr_accessor :filter
40
+ attr_accessor :highlight
41
+ attr_accessor :highlighter
42
+ attr_accessor :invert_match
43
+ attr_accessor :label
44
+ attr_accessor :line_number_highlight
45
+ attr_accessor :local_config_files
46
+ attr_accessor :match_limit
47
+ attr_accessor :multiline
48
+ attr_accessor :nocase
49
+ attr_accessor :out
50
+ attr_accessor :output
51
+ attr_accessor :quiet
52
+ attr_accessor :range_end
53
+ attr_accessor :range_start
54
+ attr_accessor :show_break
55
+ attr_accessor :show_file_names
56
+ attr_accessor :show_line_numbers
57
+ attr_accessor :size_limit
58
+ attr_accessor :split_as_path
59
+ attr_accessor :text_highlights
60
+ attr_accessor :verbose
61
+ attr_accessor :version
62
+ attr_accessor :whole_lines
63
+ attr_accessor :whole_words
64
+ attr_accessor :with_basename
65
+ attr_accessor :with_fullname
66
+ attr_accessor :without_basename
67
+ attr_accessor :without_fullname
68
+ attr_accessor :write_null
69
+
70
+ def initialize
71
+ optdata = [
72
+ {
73
+ :tags => %w{ -C --context },
74
+ :res => %r{ ^ - ([1-9]\d*) $ }x,
75
+ :arg => [ :optional, :integer ],
76
+ :set => Proc.new { |val, opt, args| @after = @before = val || 2 },
77
+ :rc => %w{ context },
78
+ },
79
+ {
80
+ :tags => %w{ --after-context -A },
81
+ :arg => [ :integer ],
82
+ :set => Proc.new { |val| @after = val },
83
+ :rc => %w{ after-context },
84
+ },
85
+ {
86
+ :tags => %w{ --before-context -B },
87
+ :arg => [ :integer ],
88
+ :set => Proc.new { |val| @before = val },
89
+ :rc => %w{ before-context },
90
+ },
91
+ {
92
+ :tags => %w{ -V --version },
93
+ :set => Proc.new { show_version }
94
+ },
95
+ {
96
+ :tags => %w{ --verbose --verbosity },
97
+ :arg => [ :integer, :optional ],
98
+ :set => Proc.new { |val| Log.verbose = val || 1 }
99
+ },
100
+ {
101
+ :tags => %w{ -v --invert-match },
102
+ :set => Proc.new { @invert_match = true }
103
+ },
104
+ {
105
+ :tags => %w{ -i --ignore-case },
106
+ :set => Proc.new { @nocase = true }
107
+ },
108
+ {
109
+ :tags => %w{ --filter },
110
+ :set => Proc.new { @filter = true }
111
+ },
112
+ {
113
+ :tags => %w{ --no-filter --nofilter },
114
+ :set => Proc.new { @filter = false }
115
+ },
116
+ {
117
+ :tags => %w{ -U --no-highlight },
118
+ :set => Proc.new { set_highlight(nil) }
119
+ },
120
+ {
121
+ :tags => %w{ -g --grep },
122
+ :set => Proc.new { set_output_style("grep") }
123
+ },
124
+ {
125
+ :tags => %w{ -? --help },
126
+ :set => Proc.new { require 'glark/help'; GlarkHelp.new; exit 0 }
127
+ },
128
+ {
129
+ :tags => %w{ --explain },
130
+ :set => Proc.new { @explain = true }
131
+ },
132
+ {
133
+ :tags => %w{ -n --line-number },
134
+ :set => Proc.new { @show_line_numbers = true }
135
+ },
136
+ {
137
+ :tags => %w{ -N --no-line-number },
138
+ :set => Proc.new { @show_line_numbers = false }
139
+ },
140
+ {
141
+ :tags => %w{ --line-number-color },
142
+ :arg => [ :string ],
143
+ :set => Proc.new { |val| @line_number_highlight = make_highlight("line-number-color", val) },
144
+ },
145
+ {
146
+ :tags => %w{ -q -s --quiet --messages },
147
+ :set => Proc.new { Log.quiet = @quiet = true }
148
+ },
149
+ {
150
+ :tags => %w{ -Q -S --no-quiet --no-messages },
151
+ :set => Proc.new { Log.quiet = @quiet = false }
152
+ },
153
+ {
154
+ :tags => %w{ -w --word --word-regexp },
155
+ :set => Proc.new { @whole_words = true }
156
+ },
157
+ {
158
+ :tags => %w{ -x --line-regexp },
159
+ :set => Proc.new { @whole_lines = true }
160
+ },
161
+ {
162
+ :tags => %w{ -l --files-with-matches },
163
+ :set => Proc.new { @file_names_only = true; @invert_match = false }
164
+ },
165
+ {
166
+ :tags => %w{ -L --files-without-match },
167
+ :set => Proc.new { @file_names_only = true; @invert_match = true }
168
+ },
169
+ {
170
+ :tags => %w{ --extended },
171
+ :set => Proc.new { @extended = true }
172
+ },
173
+ {
174
+ :tags => %w{ --multiline },
175
+ :set => Proc.new { @multiline = true }
176
+ },
177
+ {
178
+ :tags => %w{ -c --count },
179
+ :set => Proc.new { @count = true }
180
+ },
181
+ {
182
+ :tags => %w{ -Z --null },
183
+ :set => Proc.new { @write_null = true }
184
+ },
185
+ {
186
+ :tags => %w{ -M --exclude-matching },
187
+ :set => Proc.new { @exclude_matching = true }
188
+ },
189
+ {
190
+ :tags => %w{ -d },
191
+ :arg => [ :string ],
192
+ :set => Proc.new { |val| @directory = val }
193
+ },
194
+ {
195
+ :tags => %w{ -r --recurse },
196
+ :set => Proc.new { @directory = "recurse" }
197
+ },
198
+ {
199
+ :tags => %w{ -y --extract-matches },
200
+ :set => Proc.new { @extract_matches = true }
201
+ },
202
+ {
203
+ :tags => %w{ --conf },
204
+ :set => Proc.new { write_configuration; exit }
205
+ },
206
+ {
207
+ :tags => %w{ --dump },
208
+ :set => Proc.new { dump_all_fields; exit 0 }
209
+ },
210
+ {
211
+ :tags => %w{ --no-split-as-path },
212
+ :set => Proc.new { @split_as_path = false }
213
+ },
214
+ {
215
+ :tags => %w{ --split-as-path },
216
+ :arg => [ :boolean, :optional ],
217
+ :set => Proc.new { |val| @split_as_path = val }
218
+ },
219
+ {
220
+ :tags => %w{ --directories },
221
+ :arg => [ :string ],
222
+ :set => Proc.new { |val| @directory = val }
223
+ },
224
+ {
225
+ :tags => %w{ -H --with-filename },
226
+ :set => Proc.new { @show_file_names = true }
227
+ },
228
+ {
229
+ :tags => %w{ -h --no-filename },
230
+ :set => Proc.new { @show_file_names = false }
231
+ },
232
+ {
233
+ :tags => %w{ --label },
234
+ :arg => [ :string ],
235
+ :set => Proc.new { |val| @label = val }
236
+ },
237
+ {
238
+ :tags => %w{ -m --match-limit },
239
+ :arg => [ :integer ],
240
+ :set => Proc.new { |val| @match_limit = val }
241
+ },
242
+ {
243
+ :tags => %w{ -u --highlight },
244
+ :arg => [ :optional, :regexp, %r{ ^ (?:(multi|single)|none) $ }x ],
245
+ :set => Proc.new { |md| val = md ? md[1] : "multi"; set_highlight(val) }
246
+ },
247
+ {
248
+ :tags => %w{ --basename --name --with-basename --with-name },
249
+ :arg => [ :string ],
250
+ :set => Proc.new { |pat| @with_basename = Regexp.create(pat) }
251
+ },
252
+ {
253
+ :tags => %w{ --without-basename --without-name },
254
+ :arg => [ :string ],
255
+ :set => Proc.new { |pat| @without_basename = Regexp.create(pat) }
256
+ },
257
+ {
258
+ :tags => %w{ --fullname --path --with-fullname --with-path },
259
+ :arg => [ :string ],
260
+ :set => Proc.new { |pat| @with_fullname = Regexp.create(pat) }
261
+ },
262
+ {
263
+ :tags => %w{ --without-fullname --without-path },
264
+ :arg => [ :string ],
265
+ :set => Proc.new { |pat| @without_fullname = Regexp.create(pat) }
266
+ },
267
+ {
268
+ :tags => %w{ --after },
269
+ :arg => [ :required, :regexp, %r{ (\d+%?) $ }x ],
270
+ :set => Proc.new { |md| @range_start = md[1] }
271
+ },
272
+ {
273
+ :tags => %w{ --before },
274
+ :arg => [ :required, :regexp, %r{ (\d+%?) $ }x ],
275
+ :set => Proc.new { |md| @range_end = md[1] }
276
+ },
277
+ {
278
+ :tags => %w{ -R --range },
279
+ :arg => [ :required, :regexp, %r{ ^ (\d+%?),(\d+%?) $ }x ],
280
+ :set => Proc.new do |md, opt, args|
281
+ @range_start, @range_end = if md && md[1] && md[2]
282
+ [ md[1], md[2] ]
283
+ else
284
+ [ args.shift, args.shift ]
285
+ end
286
+ end
287
+ },
288
+ {
289
+ :tags => %w{ --binary-files },
290
+ :arg => [ :required, :regexp, %r{ ^ [\'\"]? (text|without\-match|binary) [\'\"]? $ }x ],
291
+ :set => Proc.new { |md| @binary_files = md[1] },
292
+ :rc => %w{ binary-files },
293
+ },
294
+ {
295
+ :tags => %w{ --size-limit },
296
+ :arg => [ :integer ],
297
+ :set => Proc.new { |val| @size_limit = val }
298
+ },
299
+ {
300
+ :tags => %w{ -T --text-color },
301
+ :arg => [ :string ],
302
+ :set => Proc.new { |val| @text_highlights = [ make_highlight("text-color", val) ] }
303
+ },
304
+ # no longer supported:
305
+ # {
306
+ # :res => [ %r{ ^ --text-color- (\d+) (?:=(.+))? $ }x ],
307
+ # :set => Proc.new do |md, opt, args|
308
+ # idx = md[1].to_i
309
+ # thl = md[2] || args.shift
310
+ # @text_highlights[idx] = make_highlight(opt, thl)
311
+ # end
312
+ # },
313
+ {
314
+ :tags => %w{ -F --file-color },
315
+ :arg => [ :string ],
316
+ :set => Proc.new { |val| @file_highlight = make_highlight("file-color", val) }
317
+ },
318
+ {
319
+ :tags => %w{ -f --file },
320
+ :arg => [ :string ],
321
+ :set => Proc.new { |fname| @expr = ExpressionFactory.new.read_file(fname) }
322
+ },
323
+ {
324
+ :tags => %w{ -o -a },
325
+ :set => Proc.new do |md, opt, args|
326
+ args.unshift opt
327
+ @expr = ExpressionFactory.new.make_expression(args)
328
+ end
329
+ },
330
+ {
331
+ :res => [ Regexp.new('^ -0 (\d{1,3})? $ ', Regexp::EXTENDED) ],
332
+ :set => Proc.new { |md| rs = md ? md[1] : 0; set_record_separator(rs) }
333
+ }
334
+ ]
335
+
336
+ @optset = OptProc::OptionSet.new(optdata)
337
+
338
+ reset
339
+ end
340
+
341
+ def [](name)
342
+ instance_eval("@" + name.to_s)
343
+ end
344
+
345
+ def reset
346
+ @after = 0 # lines of context before the match
347
+ @before = 0 # lines of context after the match
348
+ @binary_files = "binary" #
349
+ @count = false # just count the lines
350
+ @directory = "read" # read, skip, or recurse, a la grep
351
+ @expr = nil # the expression to be evaluated
352
+ @exclude_matching = false # exclude files whose names match the expression
353
+ @explain = false # display a legible version of the expression
354
+ @extended = false # whether to use extended regular expressions
355
+ @extract_matches = false # whether to show _only_ the part that matched
356
+ @file_names_only = false # display only the file names
357
+ @filter = true # display only matches
358
+ @invert_match = false # display non-matching lines
359
+ @nocase = false # match case
360
+ @match_limit = nil # the maximum number of matches to display per file
361
+ @multiline = false # whether to use multiline regexps
362
+ @local_config_files = false # use local .glarkrc files
363
+ @quiet = false # minimize warnings
364
+ @range_end = nil # range to stop searching; nil => the entire file
365
+ @range_start = nil # range to begin searching; nil => the entire file
366
+ @show_line_numbers = true # display numbers of matching lines
367
+ @show_file_names = nil # show the names of matching files; nil == > 1; true == >= 1; false means never
368
+ @verbose = nil # display debugging output
369
+ @whole_lines = false # true means patterns must match the entire line
370
+ @whole_words = false # true means all patterns are '\b'ed front and back
371
+ @write_null = false # in @file_names_only mode, write '\0' instead of '\n'
372
+ @with_basename = nil # match files with this basename
373
+ @without_basename = nil # match files without this basename
374
+ @with_fullname = nil # match files with this fullname
375
+ @without_fullname = nil # match files without this fullname
376
+ @show_break = false # whether to show the break between sections
377
+ @split_as_path = true # whether to split arguments that include the path separator
378
+
379
+ @highlight = "multi" # highlight matches (using ANSI codes)
380
+
381
+ @text_highlights = []
382
+ @file_highlight = nil
383
+ @line_number_highlight = nil
384
+
385
+ @label = nil
386
+ @size_limit = nil
387
+ @out = $stdout
388
+
389
+ $/ = "\n"
390
+
391
+ set_output_style("ansi")
392
+
393
+ reset_colors
394
+ end
395
+
396
+ def make_colors(limit = -1)
397
+ Text::Highlighter::DEFAULT_COLORS[0 .. limit].collect { |color| @highlighter.make(color) }
398
+ end
399
+
400
+ def multi_colors
401
+ make_colors
402
+ end
403
+
404
+ def single_color
405
+ make_colors(0)
406
+ end
407
+
408
+ def highlight?(str)
409
+ %w{ multi on true yes }.detect { |x| str == x }
410
+ end
411
+
412
+ def reset_colors
413
+ if @highlight && @highlighter
414
+ @text_highlights = case @highlight
415
+ when highlight?(@highlight), true
416
+ multi_colors
417
+ when "single"
418
+ single_color
419
+ when "none", "off", "false", "no", nil, false
420
+ []
421
+ else
422
+ warn "highlight format '" + @highlight.to_s + "' not recognized"
423
+ single_color
424
+ end
425
+ @file_highlight = @highlighter.make("reverse bold")
426
+ @line_number_highlight = nil
427
+ else
428
+ @text_highlights = []
429
+ @file_highlight = nil
430
+ @line_number_highlight = nil
431
+ end
432
+ end
433
+
434
+ def set_highlight(type)
435
+ @highlight = type
436
+ reset_colors
437
+ end
438
+
439
+ def set_output_style(output)
440
+ @output = output
441
+
442
+ # log { sprintf("%s: %s\n", "text_highlights", @text_highlights.collect { |hl| hl.highlight("text") }.join(", ")) }
443
+
444
+ @highlighter = case @output
445
+ when "ansi", "xterm"
446
+ Text::ANSIHighlighter
447
+ when "grep"
448
+ @highlight = false
449
+ @show_line_numbers = false
450
+ @after = 0
451
+ @before = 0
452
+ nil
453
+ when "text", "match"
454
+ @highlight = nil
455
+ nil
456
+ end
457
+
458
+ reset_colors
459
+ end
460
+
461
+ def run(args)
462
+ @args = args
463
+
464
+ @exprfact = ExpressionFactory.new
465
+
466
+ if hd = Env.home_directory
467
+ hd = Pathname.new(hd)
468
+ homerc = hd + ".glarkrc"
469
+ read_rcfile(homerc)
470
+ end
471
+
472
+ if @local_config_files
473
+ dir = Pathname.new(".").expand_path
474
+ while !dir.root? && dir != hd
475
+ rcfile = dir + ".glarkrc"
476
+ if rcfile.exist?
477
+ read_rcfile(rcfile)
478
+ break
479
+ else
480
+ dir = dir.dirname
481
+ end
482
+ end
483
+ end
484
+
485
+ read_environment_variable
486
+
487
+ # honor thy EMACS; go to grep mode
488
+ if ENV["EMACS"]
489
+ set_output_style("grep")
490
+ end
491
+
492
+ read_options
493
+
494
+ validate
495
+ end
496
+
497
+ def set_record_separator(sep)
498
+ log { "sep: #{sep}" }
499
+ $/ = if sep && sep.to_i > 0
500
+ begin
501
+ sep.oct.chr
502
+ rescue RangeError => e
503
+ # out of range (e.g., 777) means nil:
504
+ nil
505
+ end
506
+ else
507
+ log { "setting to paragraph" }
508
+ "\n\n"
509
+ end
510
+
511
+ log { "record separator set to #{$/.inspect}" }
512
+ end
513
+
514
+ def read_rcfile(rcfile)
515
+ if rcfile.exist?
516
+ rcfile.readlines.each do |line|
517
+ line.sub!(/\s*#.*/, "")
518
+ line.chomp!
519
+ name, value = line.split(/\s*[=:]\s*/)
520
+ next unless name && value
521
+
522
+ # rc association is somewhat supported:
523
+ @optset.options.each do |option|
524
+ if option.match_rc?(name)
525
+ val = option.convert_value(value)
526
+ option.set(val)
527
+ next
528
+ end
529
+ end
530
+
531
+ case name
532
+ when "expression"
533
+ # this should be more intelligent than just splitting on whitespace:
534
+ @expr = ExpressionFactory.new.make_expression(value.split(/\s+/))
535
+ when "file-color"
536
+ @file_highlight = make_highlight(name, value)
537
+ when "filter"
538
+ @filter = to_boolean(value)
539
+ when "grep"
540
+ set_output_style("grep") if to_boolean(value)
541
+ when "highlight"
542
+ @highlight = value
543
+ when "ignore-case"
544
+ @nocase = to_boolean(value)
545
+ when "known-nontext-files"
546
+ value.split.each do |ext|
547
+ FileTester.set_nontext(ext)
548
+ end
549
+ when "known-text-files"
550
+ value.split.each do |ext|
551
+ FileTester.set_text(ext)
552
+ end
553
+ when "local-config-files"
554
+ @local_config_files = to_boolean(value)
555
+ when "line-number-color"
556
+ @line_number_highlight = make_highlight(name, value)
557
+ when "output"
558
+ set_output_style(value)
559
+ when "show-break"
560
+ @show_break = to_boolean(value)
561
+ when "quiet"
562
+ Log.quiet = @quiet = to_boolean(value)
563
+ when "text-color"
564
+ @text_highlights = [ make_highlight(name, value) ]
565
+ when %r{^text\-color\-(\d+)$}
566
+ @text_highlights[$1.to_i] = make_highlight(name, value)
567
+ when "verbose"
568
+ Log.verbose = @verbose = to_boolean(value) ? 1 : nil
569
+ when "verbosity"
570
+ Log.verbose = @verbose = value.to_i
571
+ when "split-as-path"
572
+ @split_as_path = to_boolean(value)
573
+ when "size-limit"
574
+ @size_limit = value.to_i
575
+ end
576
+ end
577
+ end
578
+ end
579
+
580
+ # creates a color for the given option, based on its value
581
+ def make_highlight(opt, value)
582
+ if hl = GlarkOptions.instance.highlighter
583
+ if value
584
+ hl.make(value)
585
+ else
586
+ error opt + " requires a color"
587
+ exit 2
588
+ end
589
+ else
590
+ log { "no highlighter defined" }
591
+ end
592
+ end
593
+
594
+ # returns whether the value matches a true value, such as "yes", "true", or "on".
595
+ def to_boolean(value)
596
+ [ "yes", "true", "on" ].include?(value.downcase)
597
+ end
598
+
599
+ def read_environment_variable
600
+ options = Env.split("GLARKOPTS")
601
+ while options.size > 0
602
+ @optset.process_option(options)
603
+ end
604
+ end
605
+
606
+ def read_options
607
+ nargs = @args.size
608
+
609
+ # solitary "-v" means "--version", not --invert-match
610
+ if nargs == 1 && @args[0] == "-v"
611
+ show_version
612
+ end
613
+
614
+ @expr = nil
615
+
616
+ while @args.size > 0 && @optset.process_option(@args)
617
+ # nothing
618
+ end
619
+
620
+ if @expr
621
+ # we already have an expression
622
+ else
623
+ if @args.size > 0
624
+ known_end = false
625
+ if @args[0] == "--"
626
+ log { "end of options" }
627
+ @args.shift
628
+ known_end = true
629
+ else
630
+ # log { "not an option: #{@args[0]}" }
631
+ end
632
+
633
+ if @args && @args.size > 0
634
+ @expr = ExpressionFactory.new.make_expression(@args, !known_end)
635
+ end
636
+ end
637
+
638
+ unless @expr
639
+ if nargs > 0
640
+ error "No expression provided."
641
+ end
642
+
643
+ $stderr.puts "Usage: glark [options] expression file..."
644
+ $stderr.puts "Try `glark --help' for more information."
645
+ exit 1
646
+ end
647
+ end
648
+ end
649
+
650
+ def write_configuration
651
+ fields = {
652
+ "after-context" => @after,
653
+ "before-context" => @before,
654
+ "binary-files" => @binary_files,
655
+ "file-color" => @file_highlight,
656
+ "filter" => @filter,
657
+ "highlight" => @highlight,
658
+ "ignore-case" => @nocase,
659
+ "known-nontext-files" => FileTester.nontext_extensions.sort.join(' '),
660
+ "known-text-files" => FileTester.text_extensions.sort.join(' '),
661
+ "line-number-color" => @line_number_highlight,
662
+ "local-config-files" => @local_config_files,
663
+ "output" => @output,
664
+ "quiet" => @quiet,
665
+ "show-break" => @show_break,
666
+ "size-limit" => @size_limit,
667
+ "split-as-path" => @split_as_path,
668
+ "text-color" => @text_highlights.join(' '),
669
+ "verbose" => @verbose,
670
+ }
671
+
672
+ fields.keys.sort.each do |fname|
673
+ puts
674
+ puts "#{fname}: #{fields[fname]}"
675
+ end
676
+ end
677
+
678
+ def dump_all_fields
679
+ fields = {
680
+ "after" => @after,
681
+ "before" => @before,
682
+ "binary_files" => @binary_files,
683
+ "count" => @count,
684
+ "directory" => @directory,
685
+ "exclude_matching" => @exclude_matching,
686
+ "explain" => @explain,
687
+ "expr" => @expr,
688
+ "extract_matches" => @extract_matches,
689
+ "file_highlight" => @file_highlight ? @file_highlight.highlight("filename") : "filename",
690
+ "file_names_only" => @file_names_only,
691
+ "filter" => @filter,
692
+ "highlight" => @highlight,
693
+ "invert_match" => @invert_match,
694
+ "known_nontext_files" => FileTester.nontext_extensions.join(", "),
695
+ "known_text_files" => FileTester.text_extensions.join(", "),
696
+ "label" => @label,
697
+ "line_number_highlight" => @line_number_highlight ? @line_number_highlight.highlight("12345") : "12345",
698
+ "local_config_files" => @local_config_files,
699
+ "match_limit" => @match_limit,
700
+ "nocase" => @nocase,
701
+ "output" => @output,
702
+ "quiet" => @quiet,
703
+ "range_end" => @range_end,
704
+ "range_start" => @range_start,
705
+ "ruby version" => RUBY_VERSION,
706
+ "show_break" => @show_break,
707
+ "show_file_names" => @show_file_names,
708
+ "show_line_numbers" => @show_line_numbers,
709
+ "text_highlights" => @text_highlights.compact.collect { |hl| hl.highlight("text") }.join(", "),
710
+ "verbose" => @verbose,
711
+ "version" => $VERSION,
712
+ "whole_lines" => @whole_lines,
713
+ "whole_words" => @whole_words,
714
+ "with-basename" => @with_basename,
715
+ "without-basename" => @without_basename,
716
+ "with-fullname" => @with_fullname,
717
+ "without-fullname" => @without_fullname,
718
+ "write_null" => @write_null,
719
+ }
720
+
721
+ len = fields.keys.collect { |f| f.length }.max
722
+
723
+ fields.keys.sort.each do |field|
724
+ printf "%*s : %s\n", len, field, fields[field]
725
+ end
726
+ end
727
+
728
+ # check options for collisions/data validity
729
+ def validate
730
+ if @range_start && @range_end
731
+ pctre = Regexp.new('([\.\d]+)%')
732
+ smd = pctre.match(@range_start)
733
+ emd = pctre.match(@range_end)
734
+
735
+ # both or neither are percentages:
736
+ if !smd == !emd
737
+ if smd
738
+ if smd[1].to_f > emd[1].to_f
739
+ error "range start (#{smd}) follows range end (#{emd})"
740
+ exit 2
741
+ end
742
+ elsif @range_start.to_i > @range_end.to_i
743
+ error "range start (#{@range_start}) follows range end (#{@range_end})"
744
+ exit 2
745
+ end
746
+ end
747
+ end
748
+ end
749
+
750
+ def show_version
751
+ puts $PACKAGE + ", version " + $VERSION
752
+ puts "Written by Jeff Pace (jpace@incava.org)."
753
+ puts "Released under the Lesser GNU Public License."
754
+ exit 0
755
+ end
756
+
757
+ end