acoc 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/BUGS +62 -0
- data/Changelog +648 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +340 -0
- data/README.md +117 -0
- data/Rakefile +1 -0
- data/TODO +11 -0
- data/acoc.conf +226 -0
- data/acoc.gemspec +35 -0
- data/bin/acoc +75 -0
- data/lib/acoc.rb +307 -0
- data/lib/acoc/config.rb +3 -0
- data/lib/acoc/program.rb +12 -0
- data/lib/acoc/rule.rb +12 -0
- data/lib/acoc/version.rb +3 -0
- metadata +113 -0
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/TODO
ADDED
data/acoc.conf
ADDED
@@ -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
|
data/acoc.gemspec
ADDED
@@ -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
|
data/bin/acoc
ADDED
@@ -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
|
+
|
data/lib/acoc.rb
ADDED
@@ -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
|
+
|