glark 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/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
|
data/lib/glark/input.rb
ADDED
@@ -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
|