acoc 0.7.1

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.
@@ -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
+