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