acoc 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/TODO ADDED
@@ -0,0 +1,11 @@
1
+ $Id: TODO,v 1.12 2005/02/27 01:05:20 ianmacd Exp $
2
+
3
+ - add many more sample rules to acoc.conf
4
+
5
+ - fix pseudo-tty handling to be bidirectional
6
+
7
+
8
+ Maybes
9
+ ------
10
+
11
+ - new config file format with more flexible syntax
@@ -0,0 +1,226 @@
1
+ # sample configuration file for acoc
2
+ #
3
+ # $Id: acoc.conf,v 1.35 2005/02/27 02:08:43 ianmacd Exp $
4
+
5
+ #[default]
6
+ #
7
+ # default rules go here
8
+
9
+ # all kinds of diffs
10
+ [diff,rcsdiff/t,cvs diff/t,p4 diff/t]
11
+ /^([+>*].*)/ green
12
+ /^([-<].*)/ red
13
+ /^(@@.*|diff.*|[\d,acd]+)/ blue
14
+
15
+ [ping/ap]
16
+ # hostname and IP address
17
+ /from (\S+) \(((?:\d?\d?\d\.){3}\d?\d?\d)/ bold
18
+ # < 100 ms RTT
19
+ /(\d\d?\.\d+ ms)/ green
20
+ # > 100 ms RTT
21
+ /[^.](1\d\d ms)/ yellow
22
+ # > 200 ms RTT
23
+ /[^.]([2-9]\d\d\d? ms)/ red
24
+ # end report
25
+ /(\d+) packets transmitted, (\d+) received, (\d+%) packet loss, time (\d+ms)/ bold
26
+ @rtt min/avg/max/mdev = (\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+)/(\d+\.\d+ ms)@ bold
27
+
28
+ [traceroute/a]
29
+ # < 100 ms RTT
30
+ /[^\d]\d\d?\.\d* ms/g green
31
+ # > 100 ms RTT
32
+ /1\d\d\.\d* ms/g yellow
33
+ # > 200 ms RTT
34
+ /[2-9]\d\d\d?\.\d* ms/g red
35
+ # no response or TTL < 1
36
+ /(\*|![HNPSFXVC]?)/g red
37
+
38
+ # compilation and package building
39
+ [make,gmake,rpmbuild -b,rpmbuild --rebuild,rpm -b,rpm --rebuild]
40
+ /^(make.*)$/ yellow
41
+
42
+ [configure,rpmbuild -b,rpmbuild --rebuild,rpm -b,rpm --rebuild]
43
+ /^(checking .+\.\.\.) (yes)$/ yellow,green
44
+ /^(checking .+\.\.\.) (no)$/ yellow,red
45
+ /^(checking .+\.\.\.) ?(?!yes|no)(.+)$/ yellow,magenta
46
+
47
+ [make,gmake,cc,gcc,rpmbuild -b,rpmbuild --rebuild,rpm -b,rpm --rebuild]
48
+ /(\S+\.c)\b/ cyan
49
+ /-o\s+(\S+(?:\.o)?)\b/ green
50
+ /\s(-l\S+)\b/g magenta
51
+ /^([^:]+): (In function `)(.+)(':)/ green,yellow,cyan,yellow
52
+ /^([^:]+):(\d+): (warning: [^`]+`)([^']+)(.+)$/ green,bold,yellow,cyan,yellow
53
+ /^([^:]+):(\d+): (warning: [^`]+)$/ green,bold,yellow
54
+
55
+ # package building
56
+ [rpmbuild -b/ae,rpmbuild --rebuild/ae,rpm -b/ae,rpm --rebuild/ae]
57
+ /^(\+.*)$/ bold
58
+ /^(Executing)([^:]+): (.+)$/ yellow,cyan,green
59
+ /^(Patch #\d+) \((.+)\):$/ yellow,green
60
+ /^(Wrote: )(.+)/ yellow,green
61
+
62
+ # package querying
63
+ [rpm -.*(q.*i|i.*q)/a,rpm -.*(q.*p.*i|q.*i.*p|p.*i.*q|p.*q.*i|i.*p.*q|i.*q.*p)/a]
64
+ /^(Name|Version|Release|Install Date|Group|Size)\s*: (.+)\s\s/ yellow,green
65
+ /(^Signature|^Packager|^URL|^Summary|Relocations|Vendor|Build (?:Date|Host)|Source RPM|License)\s*: (.+)$/ yellow,green
66
+
67
+ [w/a]
68
+ /^(\S+)\s+(\S+)\s+(\S+)\s+(.{7})\s+\S+\s+(\S+)\s+(\S+)\s+(.+)$/ bold,blue,bold,yellow,green,green,magenta
69
+ /(\d+days)/ red
70
+
71
+ #[irb]
72
+ # irb doesn't function correctly
73
+ #/^irb\(([^)]+)\):(\d+):(\d+)(.)/ bold,yellow,cyan,green
74
+
75
+ # Highlight Ruby source files, etc., when there's an exception.
76
+ #[ruby/er]
77
+ # message and error class
78
+ #/:\d+:in `.*?': (.*) \((\w+)\)\s*$/ bold,red+bold
79
+ # file name and line number
80
+ #/^(?:\s+from)?(.*?):(\d+)/ green+bold,yellow+bold
81
+ # method name
82
+ #/:in `(.*?)'/ cyan+bold
83
+
84
+ [vmstat]
85
+ # load
86
+ /^\s+([5-9]\d*).*/ yellow
87
+ /^\s+([1-9]\d+).*/ red
88
+ # free RAM
89
+ /^\s+\S+\s+\S+\s+\S+\s+(\d\d)\s+.*/ red
90
+
91
+ [top/a]
92
+ # load average < 0.70
93
+ /0\.[0-6]\d(?!\s+\w)/g green+bold
94
+ # load average 0.99 < load < 0.70
95
+ /0\.[7-9]\d(?!\s+\w)/g yellow+bold
96
+ # load average > 1.00
97
+ /[^0]\.\d\d(?!\s+\w)/g red+bold
98
+ # process information
99
+ /^(\d+) processes\D+(\d+)\D+(\d+)\D+(\d+)\D+(\d)/ cyan+bold
100
+ # header percentages, except for idle
101
+ /\d+\.\d+% (?!idle)/g cyan+bold
102
+ # idle percentage 0 - 19%
103
+ /((?:1\d|[^\d]\d)\.\d%) idle/ red+bold
104
+ # idle percentage 20 - 49%
105
+ /([2-4]\d\.\d%) idle/ yellow+bold
106
+ # idle percentage 50 - 100%
107
+ /(1?[5-9]\d\.\d%) idle/ green+bold
108
+ # memory
109
+ /\d+k/g cyan+bold
110
+ # root processes
111
+ /root\s+(?:\S+\s+){9}(?:\d\s+)?(.+)$/ red
112
+ # stat names (doesn't quite work)
113
+ /\b(up|load average|processes|sleeping|running|zombie|stopped|user|system|nice|iowait|av|used|free|shrd|buff|(?:in)?active|cached)\b/g magenta+bold
114
+ # zombies and processes in uninterruptible sleep
115
+ /\b([ZD])\b/ red
116
+
117
+ [df]
118
+ # 91 - 95% full
119
+ /.*(9[1-5]%)\s+(.+)$/ yellow,bold
120
+ # 96 - 100% full
121
+ /.*(9[6-9]%|100%)\s+(.+)$/ red,bold
122
+
123
+ [ifconfig,route]
124
+ # IP addresses
125
+ /(?:\d?\d?\d\.){3}\d?\d?\d/g cyan
126
+
127
+ [route/a]
128
+ # interface
129
+ /([a-z]+\d+|lo)$/ magenta
130
+
131
+ [ifconfig/ae]
132
+ /^(\S+).*((?:[\dA-F][\dA-F]:){5}[\dA-F][\dA-F])/ magenta
133
+ /[RT]X packets:(\d+).*(?:frame|carrier):(\d+)/ green
134
+ /errors:([^0]+)\s+dropped:([^0]+)\s+overruns:([^0]+)/ red
135
+ /collisions:([^0][\d\s]*)/ red
136
+ /RX bytes:(\d+)\s+\(([^)]+)\)\s+TX bytes:(\d+)\s+\(([^)]+)\)/ green
137
+
138
+ [tcpdump/t]
139
+ # ARP
140
+ /^(\d\d:\d\d:\d\d\.\d+)\s+(arp\s+\S+)\s+(\S+)\s+(\S+)\s+(\S+)$/ green,bold,cyan,bold,cyan
141
+ # everything else
142
+ /^(\d\d:\d\d:\d\d\.\d+)\s+(\S+)\.(\S+)\s+>\s+(\S+)\.(\S+):(.+)$/ green,cyan,yellow,cyan,yellow,bold
143
+
144
+ [ldd/t]
145
+ /(\S+) => (\S+) (\S+)/ yellow,bold,magenta
146
+
147
+ [nm/t]
148
+ /^(\S+) ([[:lower:]]) (\S+)/ yellow,green+bold,magenta
149
+ /^(\S+) ([[:upper:]]) (\S+)/ yellow,cyan+bold,magenta
150
+ # undefined symbols
151
+ /([Uu] \w+?@@\S+)/ red
152
+
153
+ [strace/aet]
154
+ # calls to open
155
+ /^(open)\("([^"]+)", (\S+)/ yellow,green,cyan
156
+ # return value
157
+ /= (.+)/ bold
158
+
159
+ [ltrace/et]
160
+ # syscalls
161
+ /^([^(]+)\(/ green
162
+
163
+ [id]
164
+ /\d+/g bold
165
+
166
+ # ls needs a pseudo-terminal
167
+ [ls/ap]
168
+ # setuid
169
+ /^-[r-][w-]([Ss])/ bold
170
+ # setgid
171
+ /^-[rwx-]{5}([Ss])/ bold
172
+ # sticky bit
173
+ /^d[rwx-]{8}(t)/ bold
174
+ /^(?:\S+)\s+(\d+)\s+(\w+)\s+(\w+)\s+(\d+(?:\.?\d?[KM]?))\s+(\S+\s+\S+\s+\S+)/ green,cyan,yellow,magenta,blue
175
+
176
+ [ps .*a]
177
+ /^\s*(\d+)\s+(\S+)\s+([^DZ\s]+)\s+(\S+)/ magenta,yellow,green,cyan
178
+ # zombies and uninterruptible sleep
179
+ /^\s*(\d+)\s+(\S+)\s+([DZ]+)\s+(\S+)/ magenta,yellow,red,cyan
180
+
181
+ [ps -.*(e.*f|f.*e)]
182
+ /^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)/ green,magenta,magenta,blue,cyan,yellow,cyan
183
+
184
+ # Apt package utilities
185
+ [apt-cache search]
186
+ /^(.*?) - .*/ green
187
+
188
+ [apt-cache show]
189
+ /^Package: (.*)/ green
190
+ /^Priority: (required)/ red
191
+ /^Priority: (standard)/ green
192
+ /^Priority: (optional)/ yellow
193
+ /^Description: (.*)/ yellow
194
+ /^Version: (.*)/ blue
195
+ /^Size: (.*)/ magenta
196
+ /^Conflicts: (.*)/ red
197
+
198
+ [apt-get install]
199
+ /^ +(.*)/ green
200
+ /^.*\[(.*)\]$/ magenta
201
+
202
+ [apt-get remove]
203
+ /^ +(.*)/ red
204
+ /^.*\[(.*)\]$/ magenta
205
+
206
+ # some network utilities
207
+ [nmap]
208
+ /^.*\((.*)\).*/ green
209
+ /^(\d+)/(\w+)\s+(open)\s+(\S+)/ magenta,bold,green,cyan
210
+ /^(\d+)/(\w+)\s+(filtered)\s+(\S+)/ magenta,bold,yellow,cyan
211
+ /^(\d+)/(\w+)\s+(closed)\s+(\S+)/ magenta,red,cyan
212
+
213
+ [netstat]
214
+ /^(\w+)\s+\d+\s+\d+\s+(\S+)\s+(.+)\s+(ESTABLISHED)/ bold,cyan,green,bold
215
+ /^(\w+)\s+\d+\s+\d+\s+(\S+)\s+(.+)\s+(TIME_WAIT)/ bold,cyan,green,yellow
216
+ /^(\w+)\s+\d+\s+\d+\s+(\S+)\s+(.+)\s+(SYN_(?:SENT|RECV))/ bold,cyan,green,magenta
217
+ /^(\w+)\s+\d+\s+\d+\s+(\S+)\s+(.+)\s+(CLOSE(?:_WAIT|ING))/ bold,cyan,green,red
218
+ /^(\w+)\s+\d+\s+\d+\s+(\S+)\s+(.+)\s+(LAST_ACK|FIN_WAIT[12])/ bold,cyan,green,red
219
+ /^(\w+)\s+\d+\s+\d+\s+(\S+)\s+(.+)\s+(CLOSED)/ bold,cyan,green,blue
220
+
221
+ # some utilities
222
+ [lsmod]
223
+ /^(\w+)\s+(\d+)\s+(\d+)/ green,magenta,bold
224
+
225
+ [whereis]
226
+ /^(.+): (.+)/ yellow,bold
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'acoc/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "acoc"
8
+ spec.version = ACOC::VERSION
9
+ spec.authors = ["Ian Macdonald"]
10
+ spec.email = ["ian@caliban.org"]
11
+ spec.description = <<END
12
+ `acoc` is a regular expression based colour formatter for programs
13
+ that display output on the command-line.
14
+
15
+ It works as a wrapper around the target program, executing it
16
+ and capturing the stdout stream. Optionally, stderr can
17
+ be redirected to stdout, so that it, too, can be manipulated.
18
+
19
+ `acoc` then applies matching rules to patterns in the output
20
+ and applies colour sets to those matches.
21
+ END
22
+ spec.summary = 'Arbitrary Command Output Colourer'
23
+ spec.homepage = "http://caliban.org/ruby/acoc.shtml"
24
+ spec.license = "GPL-2.0"
25
+
26
+ spec.files = `git ls-files`.split($/)
27
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
28
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "term-ansicolor"
32
+
33
+ spec.add_development_dependency "bundler", "~> 1.3"
34
+ spec.add_development_dependency "rake"
35
+ end
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env ruby
2
+ # __ __ ___ __
3
+ # / /\ / /` / / \ / /`
4
+ # /_/--\ \_\_, \_\_/ \_\_,
5
+ #
6
+ # acoc - Arbitrary Command Output Colorer
7
+ # $Id: acoc,v 1.67 2005/02/27 01:02:24 ianmacd Exp $
8
+ #
9
+ # Version : 0.7.1
10
+ # Author : Ian Macdonald <ian@caliban.org>
11
+ #
12
+ # Copyright (C) 2003-2005 Ian Macdonald
13
+ #
14
+ # This program is free software; you can redistribute it and/or modify
15
+ # it under the terms of the GNU General Public License as published by
16
+ # the Free Software Foundation; either version 2, or (at your option)
17
+ # any later version.
18
+ #
19
+ # This program is distributed in the hope that it will be useful,
20
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
21
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
+ # GNU General Public License for more details.
23
+ #
24
+ # You should have received a copy of the GNU General Public License
25
+ # along with this program; if not, write to the Free Software Foundation,
26
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27
+
28
+ require 'acoc'
29
+
30
+ begin
31
+ ACOC.initialise
32
+
33
+ if File.lstat($0).symlink? # we're being invoked via a symlink
34
+ # Remove symlink's directory from PATH,
35
+ # multiple times if necessary
36
+ # Otherwise, acoc will reinvoke itself.
37
+ ENV['PATH'] = ENV['PATH'].gsub(/#{File.dirname($0)}:?/, '')
38
+
39
+ # prefix command line with symlink's name
40
+ ARGV.unshift 'acoc' # ARGV can now be either exec'ed or popen'ed
41
+ end
42
+
43
+ ACOC.usage if ARGV.empty? || %w[-h --help].include?(ARGV[0])
44
+ ACOC.version if %w[-v --version].include?(ARGV[0])
45
+
46
+ # sort the keys to ensure we find the longest (i.e. most specific)
47
+ # match for the command line, e.g. a config section for [ps ax]
48
+ # will match before one for [ps a]
49
+ prog = nil
50
+ ACOC::cmd.keys.sort { |a,b| b.length <=> a.length }.each do |key|
51
+ if ARGV.join(' ').match(/^#{key}/)
52
+ prog = key
53
+ break
54
+ end
55
+ end
56
+
57
+ # if there's no config section for this command and no 'default'
58
+ # section, simply execute it normally.
59
+ # Do same if $ACOC set to 'none'.
60
+ ACOC.run(ARGV) if ENV['ACOC'] == 'none' || ! (prog || ACOC::cmd.include?('default'))
61
+
62
+ # use default section if no program-specific section available
63
+ prog ||= 'default'
64
+
65
+ # if there's a config section for the command, but no rules to
66
+ # accompany it, simply execute it normally.
67
+ # Likewise if STDOUT is not a tty and the 't' flag is
68
+ # not specified.
69
+ ACOC.run(ARGV) if ACOC::cmd[prog].specs.empty? ||
70
+ ! ($stdout.tty? || ACOC::cmd[prog].flags.include?('t'))
71
+
72
+ # colour program output
73
+ ACOC.colour(prog, *ARGV)
74
+ end
75
+
@@ -0,0 +1,307 @@
1
+
2
+ require 'English'
3
+ require 'fileutils'
4
+ require 'acoc/version'
5
+ require 'acoc/program'
6
+ require 'acoc/config'
7
+ require 'acoc/rule'
8
+
9
+ require 'term/ansicolor'
10
+ include Term::ANSIColor
11
+
12
+ # Optionally requiring Ruby/TPty
13
+ # TODO: How to do it cleanly on `gemspec`,
14
+ # since it's not a gem.
15
+ begin
16
+ require 'tpty'
17
+ rescue LoadError
18
+
19
+ module ACOC
20
+ module_function
21
+
22
+ # All options from the configuration file, in a Hash
23
+ def cmd
24
+ @@cmd = Config.new unless defined? @@cmd
25
+
26
+ @@cmd
27
+ end
28
+
29
+ # set things up
30
+ #
31
+ def initialise
32
+ # Queen's or Dubya's English?
33
+ if ENV['LANG'] == "en_US" || ENV['LC_ALL'] == "en_US"
34
+ @@colour = "color"
35
+ else
36
+ @@colour = "colour"
37
+ end
38
+
39
+ config_files = %w(/etc/acoc.conf /usr/local/etc/acoc.conf)
40
+ config_files << ENV['HOME'] + "/.acoc.conf"
41
+ config_files << ENV['ACOCRC'] if ENV['ACOCRC']
42
+
43
+ # If there's no config file on user's home directory,
44
+ # we'll place our default one there.
45
+ #
46
+ # The default one lies on the same directory as the
47
+ # rest of the source code.
48
+ # Since __FILE__ is inside lib/, we'll need to jump
49
+ # above.
50
+ #
51
+ if not File.exists? File.expand_path '~/.acoc.conf'
52
+ fixed_config = File.expand_path(File.dirname(__FILE__) + '/../acoc.conf')
53
+
54
+ FileUtils.cp(fixed_config, File.expand_path('~/.acoc.conf'))
55
+ end
56
+
57
+ if parse_config(*config_files) == 0
58
+ $stderr.puts "No readable config files found."
59
+ exit 1
60
+ end
61
+ end
62
+
63
+ # display usage message and exit
64
+ #
65
+ def usage(code = 0)
66
+ $stderr.puts <<EOF
67
+ Usage: acoc command [arg1 .. argN]
68
+ acoc [-h|--help|-v|--version]
69
+ EOF
70
+
71
+ exit code
72
+ end
73
+
74
+ # display version and copyright message, then exit
75
+ #
76
+ def version
77
+ $stderr.puts <<EOF
78
+ acoc #{ACOC::VERSION}
79
+
80
+ Copyright 2003-2005 Ian Macdonald <ian@caliban.org>
81
+ This is free software; see the source for copying conditions.
82
+ There is NO warranty; not even for MERCHANTABILITY or FITNESS
83
+ FOR A PARTICULAR PURPOSE, to the extent permitted by law.
84
+ EOF
85
+
86
+ exit
87
+ end
88
+
89
+ # get configuration data
90
+ #
91
+ def parse_config(*files)
92
+ @@cmd = Config.new
93
+ parsed = 0
94
+
95
+ files.each do |file|
96
+ $stderr.printf("Attempting to read config file: %s\n", file) if $DEBUG
97
+ next unless file && FileTest::file?(file) && FileTest::readable?(file)
98
+
99
+ begin
100
+ f = File.open(file) do |f|
101
+ while line = f.gets do
102
+ next if line =~ /^(#|$)/ # skip blank lines and comments
103
+
104
+ if line =~ /^[@\[]([^\]]+)[@\]]$/ # start of program section
105
+ # get program invocation
106
+ progs = $1.split(/\s*,\s*/)
107
+ progs.each do |prog|
108
+ invocation, flags = prog.split(%r(/))
109
+
110
+ if ! flags.nil? && flags.include?('r')
111
+ # remove matching entries for this program
112
+ program = invocation.sub(/\s.*$/, '')
113
+ @@cmd.each_key do |key|
114
+ @@cmd.delete(key) if key =~ /^#{program}\b/
115
+ end
116
+ flags.delete 'r'
117
+ end
118
+
119
+ # create entry for this program
120
+ if @@cmd.has_key?(invocation)
121
+ @@cmd[invocation].flags += flags unless flags.nil?
122
+ else
123
+ @@cmd[invocation] = Program.new(flags)
124
+ end
125
+ prog.sub!(%r(/\w+), '')
126
+ end
127
+ next
128
+ end
129
+
130
+ begin
131
+ regex, flags, colours =
132
+ /^(.)([^\1]*)\1(g?)\s+(.*)/.match(line)[2..4]
133
+ rescue
134
+ $stderr.puts "Ignoring bad config line #{$NR}: #{line}"
135
+ end
136
+
137
+ colours = colours.split(/\s*,\s*/)
138
+ colours.join(' ').split(/[+\s]+/).each do |colour|
139
+ raise "#{@@colour} is not a supported #{@@colour}" \
140
+ unless attributes.collect { |a| a.to_s }.include? colour
141
+ end
142
+
143
+ progs.each do |prog|
144
+ @@cmd[prog].specs << Rule.new(Regexp.new(regex), flags, colours)
145
+ end
146
+ end
147
+ end
148
+ rescue Errno::ENOENT
149
+ $stderr.puts "Failed to open config file: #{$ERROR_INFO}"
150
+ exit 1
151
+ rescue
152
+ $stderr.puts "Error while parsing config file #{file} @ line #{$NR}: #{$ERROR_INFO}"
153
+ exit 2
154
+ end
155
+
156
+ parsed += 1
157
+ end
158
+
159
+ $stderr.printf("Action data: %s\n", @@cmd.inspect) if $DEBUG
160
+
161
+ parsed
162
+ end
163
+
164
+ def trap_signal(signals)
165
+ signals.each do |signal|
166
+ trap(signal) do
167
+ # make sure terminal is never left in a coloured state
168
+ begin
169
+ print reset
170
+ rescue Errno::EPIPE # Errno::EPIPE can occur when we're being piped
171
+ end
172
+
173
+ # reap the child and collect its exit status
174
+ # WNOHANG is needed to prevent hang when pty is used
175
+ begin
176
+ pid, status = Process.waitpid2(-1, Process::WNOHANG)
177
+
178
+ rescue Errno::ECHILD # Errno::ECHILD can occur in waitpid2
179
+
180
+ ensure
181
+ # exit must be wrapped in at_exit to make sure all output buffers
182
+ # are flushed. Shift 8 bits to convert exit/signal to just exit status
183
+ at_exit { exit status.nil? ? 0 : status >> 8 }
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ def run(args)
190
+ exec(*args)
191
+ rescue Errno::ENOENT => reason
192
+ # can't find the program we're supposed to run
193
+ $stderr.puts reason
194
+ exit Errno::ENOENT::Errno
195
+ end
196
+
197
+ # match and colour an individual line
198
+ #
199
+ def colour_line(prog, line)
200
+ matched = false
201
+
202
+ # act on only the first match unless the /a flag was given
203
+ return if matched && ! @@cmd[prog].flags.include?('a')
204
+
205
+ # get a pattern and attribute set pairing for this command
206
+ @@cmd[prog].specs.each do |spec|
207
+
208
+ if r = spec.regex.match(line) # line matches this regex
209
+ matched = true
210
+ if spec.flags.include? 'g' # global flag
211
+ matches = 0
212
+
213
+ # perform global substitution
214
+ line.gsub!(spec.regex) do |match|
215
+ index = [matches, spec.colours.size - 1].min
216
+ spec.colours[index].split(/[+\s]+/).each do |colour|
217
+ match = match.send(colour)
218
+ end
219
+ matches += 1
220
+ match
221
+ end
222
+
223
+ else # colour each match separately
224
+ # work from right to left, bracketing each match
225
+ (r.size - 1).downto(1) do |i|
226
+ start = r.begin(i)
227
+ length = r.end(i) - start
228
+ index = [i - 1, spec.colours.size - 1].min
229
+ ansi_offset = 0
230
+ spec.colours[index].split(/[+\s]+/).each do |colour|
231
+ line[start + ansi_offset, length] =
232
+ line[start + ansi_offset, length].send(colour)
233
+ # when applying multiple colours, we apply them one at a
234
+ # time, so we need to compensate for the start of the string
235
+ # moving to the right as the colour codes are applied
236
+ ansi_offset += send(colour).length
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
242
+
243
+ line
244
+ end
245
+
246
+ # process program output, one line at a time
247
+ #
248
+ def colour(prog, *cmd_line)
249
+ block = proc do |f|
250
+ while ! f.eof?
251
+ begin
252
+ line = f.gets
253
+ rescue # why do we need rescue here?
254
+ exit # why the Errno::EIO when running ls(1)?
255
+ end
256
+
257
+ coloured_line = colour_line(prog, line)
258
+
259
+ begin
260
+ print coloured_line
261
+ rescue Errno::EPIPE => reason # catch broken pipes
262
+ $stderr.puts reason
263
+ exit Errno::EPIPE::Errno
264
+ end
265
+
266
+ end
267
+ end
268
+
269
+ # take care of any embedded single quotes in args expanded from globs
270
+ cmd_line.map! { |arg| arg.gsub(/'/, %q('"'"')) }
271
+
272
+ # prepare command line: requote each argument for the shell
273
+ cmd_line = "'" << cmd_line.join(%q(' ')) << "'"
274
+
275
+ # redirect stderr to stdout if /e flag given
276
+ cmd_line << " 2>&1" if @@cmd[prog].flags.include? 'e'
277
+
278
+ # make sure we don't buffer output when stdout is connected to a pipe
279
+ $stdout.sync = true
280
+
281
+ # install signal handler
282
+ trap_signal(%w(HUP INT QUIT CLD))
283
+
284
+ if @@cmd[prog].flags.include?('p') && $LOADED_FEATURES.include?('tpty.so')
285
+ # allocate program a pseudo-terminal and run through that
286
+ pty = TPty.new do |s,|
287
+ fork do
288
+ # redirect child streams to slave
289
+ STDIN.reopen(s)
290
+ STDOUT.reopen(s)
291
+ #STDERR.reopen(s)
292
+ s.close
293
+ run(cmd_line)
294
+ end
295
+ end
296
+
297
+ # no buffering on pty
298
+ # pty.master.sync = true
299
+ block.call(pty.master)
300
+ else
301
+ # execute command
302
+ IO.popen(cmd_line) { |f| block.call(f) }
303
+ end
304
+ end
305
+ end
306
+ end
307
+