glark 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +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
|