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