db2c 0.0.4 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -6
- data/bin/db2c +22 -3
- data/lib/db2c.rb +6 -1
- data/lib/db2c/command.rb +44 -19
- data/lib/db2c/editor.rb +33 -0
- data/lib/db2c/run.rb +21 -7
- data/man/db2c.1 +70 -11
- data/man/db2c.1.html +34 -11
- data/man/db2c.1.ronn +39 -10
- data/rlwrap/filters/RlwrapFilter.pm +816 -0
- data/rlwrap/filters/pipes +74 -0
- data/spec/db2c/command_spec.rb +46 -14
- data/spec/db2c/run_spec.rb +5 -8
- metadata +8 -4
data/man/db2c.1.ronn
CHANGED
@@ -3,7 +3,7 @@ db2c(1) - A DB2 console with with history and autocomplete support, and few othe
|
|
3
3
|
|
4
4
|
## SYNOPSIS
|
5
5
|
|
6
|
-
`db2c` <
|
6
|
+
`db2c` [<options>] [dbname]
|
7
7
|
|
8
8
|
## DESCRIPTION && EXAMPLES
|
9
9
|
|
@@ -27,32 +27,61 @@ DB2 console mode does not support readline and autocomplete, this is a wrapper f
|
|
27
27
|
db2(testdb) => select * from xyz.tab[double-tab]
|
28
28
|
</pre>
|
29
29
|
|
30
|
-
* **You can pipe**:
|
31
|
-
<pre>
|
32
|
-
db2(testdb) => select * from large.table | grep -v something
|
33
|
-
db2(testdb) => select * from large.table | less
|
34
|
-
</pre>
|
35
|
-
|
36
30
|
* For example, if a command outputs long lines, **less -S** is your friend:
|
37
31
|
<pre>
|
38
32
|
db2(testdb) => select * from table.with.many.columns | less -S
|
39
33
|
(you can scorll horizontally with left/right arrows)
|
40
34
|
</pre>
|
41
35
|
|
42
|
-
* You can use
|
36
|
+
* **You can edit last statement or use the editor to write a new multiline statement**:
|
37
|
+
<pre>
|
38
|
+
db2(testdb) => edit
|
39
|
+
db2(testdb) => emacs
|
40
|
+
</pre>
|
41
|
+
|
42
|
+
* **Any !command will be sent to the shell as is**:
|
43
|
+
<pre>
|
44
|
+
db2(testdb) => !clear
|
45
|
+
</pre>
|
46
|
+
|
47
|
+
* You can use _some_ common shell commands directly:
|
48
|
+
<pre>
|
49
|
+
db2(testdb) => ls ~
|
50
|
+
db2(testdb) => less ~/somefile
|
51
|
+
db2(testdb) => top
|
52
|
+
db2(testdb) => irb
|
53
|
+
</pre>
|
54
|
+
|
55
|
+
* You can use _some psql-like_ meta commands
|
43
56
|
<pre>
|
44
|
-
db2(testdb) => **\d schema.table**
|
45
|
-
== describe table schema.table
|
46
57
|
db2(testdb) => **\h**
|
47
58
|
== To see this help from within the console
|
59
|
+
db2(testdb) => **\l**
|
60
|
+
== list database directory
|
61
|
+
db2(testdb) => **\lt [all|schema]**
|
62
|
+
== list tables for all or one schema using db2 list command
|
63
|
+
db2(testdb) => **\l[t|v|a|s] [schema]**
|
64
|
+
== list tables|views|aliases|summary-tables for all or one schema using syscat.tables
|
65
|
+
db2(testdb) => **\d schema.table**
|
66
|
+
== describe table schema.table
|
48
67
|
db2(testdb) => **\q**
|
49
68
|
</pre>
|
50
69
|
|
70
|
+
* **You can pipe**:
|
71
|
+
<pre>
|
72
|
+
db2(testdb) => \dv | grep -v SYS
|
73
|
+
db2(testdb) => select * from large.table | less
|
74
|
+
</pre>
|
75
|
+
|
76
|
+
|
51
77
|
## OPTIONS
|
52
78
|
|
53
79
|
* `-h`, `--help`, `--man`:
|
54
80
|
Displays this help page.
|
55
81
|
|
82
|
+
* `-t`:
|
83
|
+
Commands will be executed when you enter a terminating semicolon (;)
|
84
|
+
|
56
85
|
* `--debug`:
|
57
86
|
Displays annoying messages.
|
58
87
|
|
@@ -0,0 +1,816 @@
|
|
1
|
+
package RlwrapFilter;
|
2
|
+
|
3
|
+
use strict;
|
4
|
+
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK $AUTOLOAD);
|
5
|
+
|
6
|
+
sub when_defined($@);
|
7
|
+
my $previous_tag = -1;
|
8
|
+
my $last_cumulative_output = "";
|
9
|
+
|
10
|
+
require Exporter;
|
11
|
+
require AutoLoader;
|
12
|
+
@ISA = qw(Exporter AutoLoader);
|
13
|
+
|
14
|
+
@EXPORT = qw(TAG_INPUT TAG_OUTPUT TAG_HISTORY TAG_COMPLETION TAG_PROMPT);
|
15
|
+
$VERSION = '0.01';
|
16
|
+
|
17
|
+
use Carp;
|
18
|
+
|
19
|
+
# constants for every tag we know about
|
20
|
+
use constant TAG_INPUT => 0;
|
21
|
+
use constant TAG_OUTPUT => 1;
|
22
|
+
use constant TAG_HISTORY => 2;
|
23
|
+
use constant TAG_COMPLETION => 3;
|
24
|
+
use constant TAG_PROMPT => 4;
|
25
|
+
use constant TAG_IGNORE => 251;
|
26
|
+
use constant TAG_ADD_TO_COMPLETION_LIST => 252;
|
27
|
+
use constant TAG_REMOVE_FROM_COMPLETION_LIST => 253;
|
28
|
+
use constant TAG_OUTPUT_OUT_OF_BAND => 254;
|
29
|
+
use constant TAG_ERROR => 255;
|
30
|
+
|
31
|
+
use constant REJECT_PROMPT => "_THIS_CANNOT_BE_A_PROMPT_";
|
32
|
+
|
33
|
+
# we want to behave differently when running outside rlwrap
|
34
|
+
my $we_are_running_under_rlwrap = defined $ENV{RLWRAP_COMMAND_PID};
|
35
|
+
|
36
|
+
|
37
|
+
# die() and warn() must communicate via rlwrap, not via STDERR
|
38
|
+
$SIG{__DIE__} = \&die_with_error_message;
|
39
|
+
$SIG{__WARN__} = \&warn_with_info_message;
|
40
|
+
|
41
|
+
# automagically have a setter/getter for every key of %$self
|
42
|
+
sub AUTOLOAD {
|
43
|
+
my $self = shift;
|
44
|
+
my $type = ref($self)
|
45
|
+
or croak "$self is not an object";
|
46
|
+
|
47
|
+
my $name = $AUTOLOAD;
|
48
|
+
$name =~ s/.*://; # strip fully-qualified portion
|
49
|
+
|
50
|
+
unless (exists $self->{$name} ) {
|
51
|
+
croak "There is no `$name' setter/getter in class $type";
|
52
|
+
}
|
53
|
+
|
54
|
+
if (@_) {
|
55
|
+
return $self->{$name} = shift;
|
56
|
+
} else {
|
57
|
+
return $self->{$name};
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
# open communication lines with rlwrap (or with the terminal when not running under rlwrap)
|
62
|
+
if ($we_are_running_under_rlwrap) {
|
63
|
+
|
64
|
+
open CMD_IN, ">&" . $ENV{RLWRAP_MASTER_PTY_FD};
|
65
|
+
open CMD_OUT, "<&" . $ENV{RLWRAP_MASTER_PTY_FD};
|
66
|
+
|
67
|
+
open FILTER_IN, "<&" . $ENV{RLWRAP_INPUT_PIPE_FD};
|
68
|
+
open FILTER_OUT, ">&" . $ENV{RLWRAP_OUTPUT_PIPE_FD};
|
69
|
+
} else {
|
70
|
+
open CMD_IN, ">&STDOUT";
|
71
|
+
open CMD_OUT, "<&STDIN";
|
72
|
+
|
73
|
+
open FILTER_IN, "<&STDIN";
|
74
|
+
open FILTER_OUT, ">&STDOUT";
|
75
|
+
}
|
76
|
+
|
77
|
+
|
78
|
+
# create filter object
|
79
|
+
sub new {
|
80
|
+
my ($this, %init) = @_;
|
81
|
+
my $class = ref($this) || $this;
|
82
|
+
my $self = {};
|
83
|
+
my @accessors = qw(initialiser help_text input_handler
|
84
|
+
output_handler prompt_handler echo_handler
|
85
|
+
message_handler history_handler completion_handler
|
86
|
+
echo_handler message_handler cloak_and_dagger_verbose
|
87
|
+
cumulative_output prompts_are_never_empty
|
88
|
+
minimal_rlwrap_version);
|
89
|
+
foreach my $acc (@accessors) {
|
90
|
+
$self->{$acc} = "";
|
91
|
+
}
|
92
|
+
bless $self, $class;
|
93
|
+
foreach my $key (keys %init) {
|
94
|
+
croak "There is no `$key' attribute in class $class" unless defined $self->{$key};
|
95
|
+
$self -> {$key} = $init{$key};
|
96
|
+
$self -> minimal_rlwrap_version($self->{$key}) if $key eq "minimal_rlwrap_version";
|
97
|
+
}
|
98
|
+
return $self;
|
99
|
+
}
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
# event loop
|
104
|
+
sub run {
|
105
|
+
my ($self) = @_;
|
106
|
+
|
107
|
+
if($ENV{RLWRAP_COMMAND_PID} == 0) { # when called as rlwrap -z <filter> (with no command) ..
|
108
|
+
write_message(TAG_OUTPUT_OUT_OF_BAND, $self -> help_text . "\n"); # ... send help text
|
109
|
+
}
|
110
|
+
|
111
|
+
while(1) {
|
112
|
+
my ($tag, $message) = read_message();
|
113
|
+
|
114
|
+
$message = when_defined $self -> message_handler, "$message", $tag; # ignore return value
|
115
|
+
my $response;
|
116
|
+
|
117
|
+
if ($tag == TAG_INPUT) {
|
118
|
+
$response = when_defined $self -> input_handler, "$message";
|
119
|
+
} elsif ($tag == TAG_OUTPUT) {
|
120
|
+
$response = $self -> handle_output($message);
|
121
|
+
} elsif ($tag == TAG_HISTORY) {
|
122
|
+
$response = when_defined $self -> history_handler, "$message";
|
123
|
+
} elsif ($tag == TAG_COMPLETION) {
|
124
|
+
my ($line, $prefix, $completions, @completions);
|
125
|
+
if ($self -> completion_handler) {
|
126
|
+
$message =~ s/[ ]+$//; # eat final space
|
127
|
+
($line, $prefix, $completions) = split /\t/, $message;
|
128
|
+
@completions = split / /, $completions;
|
129
|
+
@completions = &{$self -> completion_handler}($line, $prefix, @completions);
|
130
|
+
$response = "$line\t$prefix\t". (join ' ', @completions) . " ";
|
131
|
+
} else {
|
132
|
+
$response = $message;
|
133
|
+
}
|
134
|
+
} elsif ($tag == TAG_PROMPT) {
|
135
|
+
if ($message eq REJECT_PROMPT or
|
136
|
+
($self -> {prompts_are_never_empty} and $message eq "")) {
|
137
|
+
write_message($tag,REJECT_PROMPT);
|
138
|
+
# don't update <previous_tag> and don't reset <cumulative_input>
|
139
|
+
next;
|
140
|
+
}
|
141
|
+
$self->{cumulative_output} =~ s/(?<![^\n])[^\n]*$// # s/[^\n]*$// takes way too long on big strings, what is the optimal regex to do this?
|
142
|
+
if $ENV{RLWRAP_IMPATIENT}; # chop off prompt from cumulative_output
|
143
|
+
|
144
|
+
$response = when_defined $self -> prompt_handler, "$message";
|
145
|
+
croak "prompts may not contain newlines!" if $response =~ /\n/;
|
146
|
+
} else {
|
147
|
+
$response = $message; # No error message, compatible with future rlwrap
|
148
|
+
# versions that may define new tag types
|
149
|
+
}
|
150
|
+
|
151
|
+
unless (out_of_band($tag) and ($tag == TAG_PROMPT and $response eq REJECT_PROMPT)) {
|
152
|
+
$self -> {previous_tag} = $tag;
|
153
|
+
$self -> {previous_message} = $message;
|
154
|
+
}
|
155
|
+
write_message($tag, $response);
|
156
|
+
|
157
|
+
}
|
158
|
+
}
|
159
|
+
|
160
|
+
|
161
|
+
# when_defined \&f, x, y, ... returns f(x, y, ...) if f is defined, x otherwise
|
162
|
+
sub when_defined($@) {
|
163
|
+
my $maybe_ref_to_sub = shift;
|
164
|
+
local $_ = $_[0] ; # convenient when using anonymous subs as handlers: $filter -> blah_handler(sub{$_ if /blah/});
|
165
|
+
if ($maybe_ref_to_sub) {
|
166
|
+
if ((my $type = ref($maybe_ref_to_sub)) ne 'CODE') {
|
167
|
+
croak "improper handler <$maybe_ref_to_sub> of type $type (expected a ref to a sub)";
|
168
|
+
}
|
169
|
+
return &{$maybe_ref_to_sub}(@_);
|
170
|
+
} else {
|
171
|
+
return $_;
|
172
|
+
}
|
173
|
+
}
|
174
|
+
|
175
|
+
sub out_of_band {
|
176
|
+
my($tag) = @_;
|
177
|
+
return $tag > 128;
|
178
|
+
}
|
179
|
+
|
180
|
+
# split output in echo and the rest and call the appropriate handlers on them
|
181
|
+
sub handle_output {
|
182
|
+
my ($self, $message) = @_;
|
183
|
+
my ($echo, $handled_echo, $sep);
|
184
|
+
if (defined $self -> {previous_tag} and $self -> {previous_tag} == TAG_INPUT) {
|
185
|
+
$self->{cumulative_output} = "";
|
186
|
+
($echo, $sep, $message) = ($message =~ /^([^\n\r]*)(\r?\n)?(.*)?/s); #@@@ This doesn't work for multi-line input!
|
187
|
+
$handled_echo = when_defined $self -> echo_handler, "$echo";
|
188
|
+
}
|
189
|
+
$self->{cumulative_output} .= $message;
|
190
|
+
return $handled_echo . $sep .(when_defined $self -> output_handler, "$message");
|
191
|
+
}
|
192
|
+
|
193
|
+
sub read_until { # read chunks from pty pointed to by $fh until either inactive for $timeout or
|
194
|
+
# $stoptext is seen at end-of-chunk
|
195
|
+
my ($fh, $stoptext, $timeout) = @_;
|
196
|
+
my ($res);
|
197
|
+
while (1){
|
198
|
+
my $chunk = read_chunk($fh, $timeout);
|
199
|
+
return $res unless $chunk; # got "" back: timeout
|
200
|
+
$res .= $chunk;
|
201
|
+
return $res if $res =~ /$stoptext$/;
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
|
206
|
+
# read chunk from pty pointed to by $fh with timeout $timeout
|
207
|
+
sub read_chunk {
|
208
|
+
my ($fh, $timeout) = @_;
|
209
|
+
my ($rin, $rout, $chunk);
|
210
|
+
vec($rin, fileno($fh), 1) = 1;
|
211
|
+
my ($nfound, undef) = select($rout=$rin, undef, undef, $timeout);
|
212
|
+
if ($nfound > 0) {
|
213
|
+
my $nread = sysread($fh, $chunk, 256);
|
214
|
+
if ($nread > 0) {
|
215
|
+
return $chunk;
|
216
|
+
}
|
217
|
+
}
|
218
|
+
return "";
|
219
|
+
}
|
220
|
+
|
221
|
+
|
222
|
+
# keep reading until $count total bytes were read from filehandle $fh
|
223
|
+
sub read_patiently {
|
224
|
+
my($fh, $count) = @_;
|
225
|
+
my $already_read = 0;
|
226
|
+
my $result;
|
227
|
+
while($already_read < $count) {
|
228
|
+
my $nread = sysread($fh, $result, $count-$already_read, $already_read);
|
229
|
+
if ($nread == 0) {
|
230
|
+
exit 0;
|
231
|
+
} elsif ($nread < 0) {
|
232
|
+
die_with_errormessage("error reading: $!");
|
233
|
+
}
|
234
|
+
$already_read += $nread;
|
235
|
+
}
|
236
|
+
return $result;
|
237
|
+
}
|
238
|
+
|
239
|
+
# keep writing until all bytes from $buffer were written to $fh
|
240
|
+
sub write_patiently {
|
241
|
+
my($fh, $buffer) = @_;
|
242
|
+
my $already_written = 0;
|
243
|
+
my $count = length($buffer);
|
244
|
+
while($already_written < $count) {
|
245
|
+
my $nwritten = syswrite($fh, $buffer, $count-$already_written, $already_written);
|
246
|
+
if ($nwritten <= 0) {
|
247
|
+
die_with_errormessage("error writing: $!");
|
248
|
+
}
|
249
|
+
$already_written += $nwritten;
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
|
254
|
+
# read message (tag, length word and contents) from FILTER_IN
|
255
|
+
sub read_message {
|
256
|
+
return read_from_stdin() unless $we_are_running_under_rlwrap;
|
257
|
+
|
258
|
+
my $tag = unpack("C", read_patiently(*FILTER_IN,1));
|
259
|
+
my $length = unpack("L",read_patiently(*FILTER_IN,4));
|
260
|
+
my $message = read_patiently(*FILTER_IN, $length);
|
261
|
+
$message =~ s/\n$//;
|
262
|
+
return ($tag, $message);
|
263
|
+
}
|
264
|
+
|
265
|
+
|
266
|
+
sub write_message {
|
267
|
+
my($tag, $message) = @_;
|
268
|
+
return write_to_stdout($tag, $message) unless $we_are_running_under_rlwrap;
|
269
|
+
|
270
|
+
$message ||= ""; # allow undefined messages
|
271
|
+
|
272
|
+
write_patiently(*FILTER_OUT, pack("C", $tag));
|
273
|
+
write_patiently(*FILTER_OUT, pack("L", (length $message) + 1));
|
274
|
+
write_patiently(*FILTER_OUT, "$message\n");
|
275
|
+
}
|
276
|
+
|
277
|
+
sub read_from_stdin {
|
278
|
+
my ($tag, $prompt, $tagname, $message);
|
279
|
+
while (not defined $tag) {
|
280
|
+
print $prompt;
|
281
|
+
($tagname, $message) = (<STDIN> =~ /(\S+) (.*?)\r?\n/);
|
282
|
+
exit unless $tagname;
|
283
|
+
$tag = name2tag(undef, $tagname); # call as function, not method
|
284
|
+
$prompt = "again > ";
|
285
|
+
}
|
286
|
+
return ($tag, $message)
|
287
|
+
}
|
288
|
+
|
289
|
+
sub write_to_stdout {
|
290
|
+
my($tag, $message) = @_;
|
291
|
+
print tag2name(undef, $tag) . " $message\n";
|
292
|
+
}
|
293
|
+
|
294
|
+
|
295
|
+
sub add_to_completion_list {
|
296
|
+
my ($self, @words) = @_;
|
297
|
+
write_message(TAG_ADD_TO_COMPLETION_LIST, join(' ', @words));
|
298
|
+
}
|
299
|
+
|
300
|
+
sub remove_from_completion_list {
|
301
|
+
my ($self, @words) = @_;
|
302
|
+
write_message(TAG_REMOVE_FROM_COMPLETION_LIST, join(' ', @words));
|
303
|
+
}
|
304
|
+
|
305
|
+
|
306
|
+
sub cwd {
|
307
|
+
my ($self) = @_;
|
308
|
+
my $command_pid = $ENV{RLWRAP_COMMAND_PID};
|
309
|
+
my $pwd = "/proc/$command_pid/cwd";
|
310
|
+
croak "cannot read commands working directory as $pwd doesn't exist" unless -e $pwd;
|
311
|
+
return (-l $pwd ? readlink ($pwd) : $pwd);
|
312
|
+
}
|
313
|
+
|
314
|
+
|
315
|
+
|
316
|
+
# have a private chat with the rlwrapped command. This relies very much om the assumption that command stops
|
317
|
+
# talking, and only listens, when it has displayed the $prompt
|
318
|
+
sub cloak_and_dagger {
|
319
|
+
my ($self, $question, $prompt, $timeout) = @_;
|
320
|
+
$prompt ||= $self -> last('prompt');
|
321
|
+
write_patiently(*CMD_IN, "$question\n");
|
322
|
+
$self -> send_output_oob("cloak_and_dagger question: $question\n") if $self -> {cloak_and_dagger_verbose};
|
323
|
+
my $response = read_until(*CMD_OUT, $prompt, $timeout);
|
324
|
+
$response =~ s/.*?\n//; # chop off echoed question;
|
325
|
+
$response =~ s/$prompt$//; # chop off prompt;
|
326
|
+
$self -> send_output_oob("cloak_and_dagger response: $response\n") if $self -> {cloak_and_dagger_verbose};
|
327
|
+
return $response;
|
328
|
+
}
|
329
|
+
|
330
|
+
|
331
|
+
|
332
|
+
sub tag2name {
|
333
|
+
my ($self, $tag) = @_;
|
334
|
+
for my $name (qw(TAG_REMOVE_FROM_COMPLETION_LIST TAG_ADD_TO_COMPLETION_LIST TAG_INPUT TAG_PROMPT TAG_COMPLETION
|
335
|
+
TAG_HISTORY TAG_OUTPUT_OUT_OF_BAND TAG_ERROR TAG_IGNORE TAG_OUTPUT)) {
|
336
|
+
return $name if (eval "$tag == $name");
|
337
|
+
}
|
338
|
+
croak "unknown tag $tag";
|
339
|
+
}
|
340
|
+
|
341
|
+
sub name2tag {
|
342
|
+
my ($self, $name ) = @_;
|
343
|
+
my $tag = eval uc $name;
|
344
|
+
#croak "unknown tagname $name " if $@;
|
345
|
+
return $tag;
|
346
|
+
}
|
347
|
+
|
348
|
+
sub send_output_oob {
|
349
|
+
my ($self, $text) = @_;
|
350
|
+
write_message(TAG_OUTPUT_OUT_OF_BAND, $text);
|
351
|
+
}
|
352
|
+
|
353
|
+
|
354
|
+
|
355
|
+
sub send_ignore_oob {
|
356
|
+
my ($self, $text) = @_;
|
357
|
+
write_message(TAG_IGNORE, $text);
|
358
|
+
}
|
359
|
+
|
360
|
+
sub die_with_error_message {
|
361
|
+
die $@ if $^S; # make die() within eval do the right thing
|
362
|
+
my ($error_message) = @_;
|
363
|
+
my $myself = $0;
|
364
|
+
$myself =~ s#^.*/([^.]+)$#$1#;
|
365
|
+
write_message(TAG_ERROR, "$myself: $error_message");
|
366
|
+
sleep 2;
|
367
|
+
exit 1;
|
368
|
+
}
|
369
|
+
|
370
|
+
|
371
|
+
sub warn_with_info_message {
|
372
|
+
my ($warning) = @_;
|
373
|
+
my $myself = $0;
|
374
|
+
$myself =~ s#^.*/([^.]+)$#$1#;
|
375
|
+
write_message(TAG_OUTPUT_OUT_OF_BAND, "$myself: $warning");
|
376
|
+
|
377
|
+
}
|
378
|
+
|
379
|
+
sub minimal_rlwrap_version {
|
380
|
+
my ($self, $wanted) = @_;
|
381
|
+
my $found = $ENV{RLWRAP_VERSION} || "0.34";
|
382
|
+
die "This filter requires rlwrap version $wanted or newer!\n"
|
383
|
+
unless !$we_are_running_under_rlwrap or $wanted le $found;
|
384
|
+
}
|
385
|
+
|
386
|
+
|
387
|
+
sub command_line {
|
388
|
+
my $commandline = $ENV{RLWRAP_COMMAND_LINE};
|
389
|
+
return (wantarray ? split /\s+/, $commandline : $commandline);
|
390
|
+
}
|
391
|
+
|
392
|
+
sub running_under_rlwrap {
|
393
|
+
return $we_are_running_under_rlwrap;
|
394
|
+
}
|
395
|
+
|
396
|
+
sub prompt_rejected {
|
397
|
+
my ($self) = @_;
|
398
|
+
$self->minimal_rlwrap_version("0.35");
|
399
|
+
return REJECT_PROMPT;
|
400
|
+
}
|
401
|
+
|
402
|
+
sub name {
|
403
|
+
my ($name) = ($0 =~ m#([^/]+)$#);
|
404
|
+
$name ||= $0;
|
405
|
+
return $name;
|
406
|
+
}
|
407
|
+
|
408
|
+
|
409
|
+
1
|
410
|
+
|
411
|
+
__END__
|
412
|
+
|
413
|
+
|
414
|
+
=head1 NAME
|
415
|
+
|
416
|
+
RlwrapFilter - Perl class for B<rlwrap> filters
|
417
|
+
|
418
|
+
=head1 SYNOPSIS
|
419
|
+
|
420
|
+
use lib $ENV{RLWRAP_FILTERDIR};
|
421
|
+
use RlwrapFilter;
|
422
|
+
|
423
|
+
$filter = new RlwrapFilter;
|
424
|
+
|
425
|
+
$filter -> output_handler(sub {s/apple/orange/; $_}); # re-write output
|
426
|
+
$filter -> prompt_handler(\&pimp_the_prompt); # change prompt
|
427
|
+
$filter -> history_handler(sub {s/with password \w+/with password ****/; $_}); # keep passwords out of history
|
428
|
+
|
429
|
+
$filter -> run;
|
430
|
+
|
431
|
+
=head1 DESCRIPTION
|
432
|
+
|
433
|
+
B<rlwrap> (1) (L<http://utopia.knoware.nl/~hlub/uck/rlwrap>) is a tiny
|
434
|
+
utility that sits between the user and any console command, in order
|
435
|
+
to bestow readline capabilities (line editing, history recall) to
|
436
|
+
commands that don't have them.
|
437
|
+
|
438
|
+
Since version 0.32, rlwrap can use filters to script almost every
|
439
|
+
aspect of rlwrap's interaction with the user: changing the history,
|
440
|
+
re-writing output and input, calling a pager or computing completion
|
441
|
+
word lists from the current input.
|
442
|
+
|
443
|
+
B<RlwrapFilter> makes it very simple to write rlwrap
|
444
|
+
filters in perl. A filter only needs to instantiate a RlwrapFilter
|
445
|
+
object, change a few of its default handlers and then call its 'run'
|
446
|
+
method.
|
447
|
+
|
448
|
+
=head1 PUBLIC METHODS
|
449
|
+
|
450
|
+
=head2 CONSTRUCTOR
|
451
|
+
|
452
|
+
=over 4
|
453
|
+
|
454
|
+
=item $f = new RlwrapFilter
|
455
|
+
|
456
|
+
=item $f = RlwrapFilter -> new(prompt_handler => sub {"Hi! > "}, minimal_rlwrap_version => "0.35", ...)
|
457
|
+
|
458
|
+
Return a new RlwrapFilter object.
|
459
|
+
|
460
|
+
=back
|
461
|
+
|
462
|
+
=head2 SETTING/GETTING HANDLERS
|
463
|
+
|
464
|
+
Handlers are user-defined callbacks that get called from the
|
465
|
+
'run' method with a message (i.e. the un-filtered input,
|
466
|
+
output, prompt) as their first argument. For convenience, $_ is set to the same
|
467
|
+
value. They should return the re-written message text. They get called
|
468
|
+
in a fixed cyclic order: prompt, completion, history, input, echo,
|
469
|
+
output, prompt, ... etc ad infinitum. Rlwrap may always skip a handler when in direct mode,
|
470
|
+
on the other hand, completion and output handlers may get called more
|
471
|
+
than once in succession. If a handler is left undefined, the result is
|
472
|
+
as if the message text were returned unaltered.
|
473
|
+
|
474
|
+
It is important to note that the filter, and hence all its handlers,
|
475
|
+
are bypassed when I<command> is in direct mode, i.e. when it asks for
|
476
|
+
single keystrokes (and also, for security reasons, when it doesn't
|
477
|
+
echo, e.g. when asking for a password). If you don't want this to happen, use
|
478
|
+
B<rlwrap -a> to force B<rlwrap> to remain in readline mode and to
|
479
|
+
apply the filter to I<all> of I<command>'s in- and output. This will
|
480
|
+
make editors and pagers (which respond to single keystrokes) unusable,
|
481
|
+
unless you use rlwrap's B<-N> option (linux only)
|
482
|
+
|
483
|
+
|
484
|
+
The getters/setters for the respective handlers are listed below:
|
485
|
+
|
486
|
+
=over 4
|
487
|
+
|
488
|
+
|
489
|
+
|
490
|
+
=item $handler = $f -> prompt_handler, $f -> prompt_handler(\&handler)
|
491
|
+
|
492
|
+
The prompt handler re-writes prompts and gets called when rlwrap
|
493
|
+
decides it is time to "cook" the prompt, by default some 40 ms after
|
494
|
+
the last output has arrived. Of course, B<rlwrap> cannot read the mind
|
495
|
+
of I<command>, so what looks like a prompt to B<rlwrap> may actually
|
496
|
+
be the beginning of an output line that took I<command> a little
|
497
|
+
longer to formulate. If this is a problem, specify a longer "cooking"
|
498
|
+
time with rlwrap's B<-w> option, use the B<prompts_are_never_empty>
|
499
|
+
method or "reject" the prompt (cf. the B<prompt_rejected> method)
|
500
|
+
|
501
|
+
|
502
|
+
=item $handler = $f -> completion_handler, $f -> completion_handler(\&handler)
|
503
|
+
|
504
|
+
The completion handler gets called with the the entire input line, the
|
505
|
+
prefix (partial word to complete), and rlwrap's own completion list as
|
506
|
+
arguments. It should return a (possibly revised) list of completions. As
|
507
|
+
an example, suppose the user has typed "She played for
|
508
|
+
AE<lt>TABE<gt>". The handler will be called like this:
|
509
|
+
|
510
|
+
myhandler("She played for A", "A", "Arsenal", "Arendal", "Anderlecht")
|
511
|
+
|
512
|
+
it could then return a list of stronger clubs: ("Ajax", "AZ67", "Arnhem")
|
513
|
+
|
514
|
+
=item $handler = $f -> history_handler, $f -> history_handler(\&handler)
|
515
|
+
|
516
|
+
Every input line is submitted to this handler, the return value is put
|
517
|
+
in rlwrap's history. Returning an empty or undefined value will keep
|
518
|
+
the input line out of the history.
|
519
|
+
|
520
|
+
=item $handler = $f -> input_handler, $f -> input_handler(\&handler)
|
521
|
+
|
522
|
+
Every input line is submitted to this handler, The handler's return
|
523
|
+
value is written to I<command>'s pty (pseudo-terminal).
|
524
|
+
|
525
|
+
=item $handler = $f -> echo_handler, $f -> echo_handler(\&handler)
|
526
|
+
|
527
|
+
The first line of output that is read back from I<command>'s pty is
|
528
|
+
the echo'ed input line. If your input handler alters the input line,
|
529
|
+
it is the altered input that will be echo'ed back. If you don't want
|
530
|
+
to confuse the user, use an echo handler that returns your original
|
531
|
+
input.
|
532
|
+
|
533
|
+
If you use rlwrap in --multi-line mode, additional echo lines will
|
534
|
+
have to be handled by the output handler
|
535
|
+
|
536
|
+
|
537
|
+
=item $handler = $f -> output_handler, $f -> output_handler(\&handler)
|
538
|
+
|
539
|
+
All I<command> output after the echo line is submitted to the output
|
540
|
+
handler (including newlines). This handler may get called many times in succession,
|
541
|
+
dependent on the size of I<command>'s write() calls, and the whims of
|
542
|
+
your system's scheduler. Therefore your handler should be prepared to
|
543
|
+
rewrite your output in "chunks", where you even don't have the
|
544
|
+
guarantee that the chunks contain entire unbroken lines.
|
545
|
+
|
546
|
+
If you want to handle I<command>'s entire output in one go, you can
|
547
|
+
specify an output handler that returns an empty string, and then use
|
548
|
+
$filter -> cumulative_output in your prompt handler to send the
|
549
|
+
re-written output "out-of-band" just before the prompt:
|
550
|
+
|
551
|
+
$filter -> output_handler(sub {""});
|
552
|
+
|
553
|
+
$filter -> prompt_handler(
|
554
|
+
sub{ $filter -> send_output_oob(mysub($filter -> cumulative_output));
|
555
|
+
"Hi there > "
|
556
|
+
});
|
557
|
+
|
558
|
+
|
559
|
+
Note that when rlwrap is run in --multi-line mode the echo handler will still
|
560
|
+
only handle the first echo line. The remainder will generally
|
561
|
+
be echoed back preceded by a continuation prompt; it is up to the
|
562
|
+
output handler what to do with it.
|
563
|
+
|
564
|
+
|
565
|
+
=item $handler = $f -> message_handler, $f -> message_handler(\&handler)
|
566
|
+
|
567
|
+
This handler gets called (as handler($message, $tag)) for every
|
568
|
+
incoming message, and every tag (including out-of-band tags), before
|
569
|
+
all other handlers. Its return value is ignored, but it may be useful
|
570
|
+
for logging and debugging purposes. The $tag is an integer that can be
|
571
|
+
converted to a tag name by the 'tag2name' method
|
572
|
+
|
573
|
+
=back
|
574
|
+
|
575
|
+
=head2 OTHER METHODS
|
576
|
+
|
577
|
+
=over 4
|
578
|
+
|
579
|
+
=item $f -> help_text("Usage...")
|
580
|
+
|
581
|
+
Set the help text for this filter. It will be displayed by rlwrap -z
|
582
|
+
<filter>. The second line of the help text is used by C<rlwrap -z listing>;
|
583
|
+
it should be a short description of what the filter does.
|
584
|
+
|
585
|
+
=item $f -> minimal_rlwrap_version("x.yy")
|
586
|
+
|
587
|
+
Die unless rlwrap is version x.yy or newer
|
588
|
+
|
589
|
+
=item $dir = $f -> cwd
|
590
|
+
|
591
|
+
return the name of I<command>'s current working directory. This uses
|
592
|
+
the /proc filesystem, and may only work on newer linux systems (on
|
593
|
+
older linux and on Solaris, it will return something like
|
594
|
+
"/proc/12345/cwd", useful to find the contents of I<command>'s working
|
595
|
+
directory, but not its name)
|
596
|
+
|
597
|
+
|
598
|
+
=item $text = $f -> cumulative_output
|
599
|
+
|
600
|
+
return the current cumulative output. All (untreated) output gets
|
601
|
+
appended to the cumulative output after the output_handler has been
|
602
|
+
called. The cumulative output starts with a fresh slate with every
|
603
|
+
OUTPUT message that directly follows an INPUT message (ignoring out-of-band
|
604
|
+
messages and rejected prompts)
|
605
|
+
|
606
|
+
When necessary (i.e. when B<rlwrap> is in "impatient mode") the prompt
|
607
|
+
is removed from $filter->cumulative_output by the time the prompt
|
608
|
+
handler is called.
|
609
|
+
|
610
|
+
=item $tag = $f -> previous_tag
|
611
|
+
|
612
|
+
The tag of the last preceding in-band message. A tag is an integer between 0 and
|
613
|
+
255, its name can be found with the following method:
|
614
|
+
|
615
|
+
=item $name = $f -> tag2name($tag)
|
616
|
+
|
617
|
+
Convert the tag (an integer) to its name (e.g. "TAG_PROMPT")
|
618
|
+
|
619
|
+
=item $name = $f -> name2tag($tag)
|
620
|
+
|
621
|
+
Convert a valid tag name like "TAG_PROMPT" to a tag (an integer)
|
622
|
+
|
623
|
+
=item $f -> send_output_oob($text)
|
624
|
+
|
625
|
+
Make rlwrap display C<$text>. C<$text> is sent "out-of-band":
|
626
|
+
B<rlwrap> will not see it until just after it has sent the next
|
627
|
+
message to the filter
|
628
|
+
|
629
|
+
=item $f -> send_ignore_oob($text)
|
630
|
+
|
631
|
+
Send an out-of-band TAG_IGNORE message to rlwrap. B<rlwrap> will silently
|
632
|
+
discard it, but it can be useful when debugging filters
|
633
|
+
|
634
|
+
=item $f -> add_to_completion_list(@words)
|
635
|
+
|
636
|
+
=item $f -> remove_from_completion_list(@words)
|
637
|
+
|
638
|
+
Permanently add or remove the words in C<@words> to/from rlwrap's completion list.
|
639
|
+
|
640
|
+
=item $f -> cloak_and_dagger($question, $prompt, $timeout);
|
641
|
+
|
642
|
+
Send C<$question> to I<command>'s input and read back everything that
|
643
|
+
comes back until C<$prompt> is seen at "end-of-chunk", or no new
|
644
|
+
chunks arrive for $timeout seconds, whichever comes first. Return the
|
645
|
+
response (without the final C<$prompt>). B<rlwrap> remains completely
|
646
|
+
unaware of this conversation.
|
647
|
+
|
648
|
+
=item $f -> cloak_and_dagger_verbose($verbosity)
|
649
|
+
|
650
|
+
If $verbosity evaluates to a true value, make rlwrap print all
|
651
|
+
questions sent to I<command> by the C<cloak_and_dagger> method, and
|
652
|
+
I<command>'s responses. By default, $verbosity = 0; setting it to
|
653
|
+
1 will mess up the screen but greatly facilitate the (otherwise rather tricky) use of
|
654
|
+
C<cloak_and_dagger>
|
655
|
+
|
656
|
+
=item $self -> prompt_rejected
|
657
|
+
|
658
|
+
A special text ("_THIS_CANNOT_BE_A_PROMPT_") to be returned by a
|
659
|
+
prompt handler to "reject" the prompt. This will make rlwrap skip
|
660
|
+
cooking the prompt. $self->previous_tag and $self->cumulative_output
|
661
|
+
will not be touched.
|
662
|
+
|
663
|
+
=item $text = $f -> prompts_are_never_empty($val)
|
664
|
+
|
665
|
+
If $val evaluates to a true value, automatically reject empty prompts.
|
666
|
+
|
667
|
+
=item $f -> command_line
|
668
|
+
|
669
|
+
In scalar context: the rlwrapped command and its arguments as a string ("command -v blah")
|
670
|
+
in list context: the same as a list ("command", "-v", "blah")
|
671
|
+
|
672
|
+
=item $f -> running_under_rlwrap
|
673
|
+
|
674
|
+
Whether the filter is run by B<rlwrap>, or directly from the command line
|
675
|
+
|
676
|
+
=item $f -> run
|
677
|
+
|
678
|
+
Start an event loop that reads rlwrap's messages from the input pipe,
|
679
|
+
calls the appropriate handlers and writes the result to the output
|
680
|
+
pipe. This method never returns.
|
681
|
+
|
682
|
+
=back
|
683
|
+
|
684
|
+
|
685
|
+
|
686
|
+
=head1 LOW LEVEL PROTOCOL
|
687
|
+
|
688
|
+
B<rlwrap> communicates with a filter through messages consisting of a tag
|
689
|
+
byte (TAG_OUTPUT, TAG_PROMPT etc. - to inform the filter of what is
|
690
|
+
being sent), an unsigned 32-bit integer containing the length of the
|
691
|
+
message, the message text and an extra newline. For every message
|
692
|
+
sent, rlwrap expects, and waits for an answer message with the same
|
693
|
+
tag. Sending back a different (in-band) tag is an error and instantly
|
694
|
+
kills rlwrap, though filters may precede their answer message with
|
695
|
+
"out-of-band" messages to output text (TAG_OUTPUT_OUT_OF_BAND), report
|
696
|
+
errors (TAG_ERROR), and to manipulate the completion word list
|
697
|
+
(TAG_ADD_TO_COMPLETION_LIST and TAG_REMOVE_FROM_COMPLETION_LIST)
|
698
|
+
Out-of-band messages are not serviced by B<rlwrap> until right after
|
699
|
+
it has sent the next in-band message - the communication with the
|
700
|
+
filter is synchronous and driven by rlwrap.
|
701
|
+
|
702
|
+
Messages are received and sent via two pipes. STDIN, STDOUT and STDERR
|
703
|
+
are still connected to the user's terminal, and you can read and write
|
704
|
+
them directly, though this may mess up the screen and confuse the user
|
705
|
+
unless you are careful. A filter can even communicate with the
|
706
|
+
rlwrapped command behind rlwrap's back (cf the cloak_and_dagger()
|
707
|
+
method)
|
708
|
+
|
709
|
+
The protocol uses the following tags (tags E<gt> 128 are out-of-band)
|
710
|
+
|
711
|
+
TAG_INPUT 0
|
712
|
+
TAG_OUTPUT 1
|
713
|
+
TAG_HISTORY 2
|
714
|
+
TAG_COMPLETION 3
|
715
|
+
TAG_PROMPT 4
|
716
|
+
|
717
|
+
TAG_IGNORE 251
|
718
|
+
TAG_ADD_TO_COMPLETION_LIST 252
|
719
|
+
TAG_REMOVE_FROM_COMPLETION_LIST 253
|
720
|
+
TAG_OUTPUT_OUT_OF_BAND 254
|
721
|
+
TAG_ERROR 255
|
722
|
+
|
723
|
+
|
724
|
+
To see how this works, you can eavesdrop on the protocol
|
725
|
+
using the 'logger' filter.
|
726
|
+
|
727
|
+
The constants TAG_INPUT, ... are exported by the RlwrapFilter.pm module.
|
728
|
+
|
729
|
+
=head1 SIGNALS
|
730
|
+
|
731
|
+
As STDIN is still connected to the users teminal, one might expect the filter
|
732
|
+
to receive SIGINT, SIGTERM, SIGTSTP directly from the terminal driver if
|
733
|
+
the user presses CTRL-C, CTRL-Z etc Normally, we don't want this - it
|
734
|
+
would confuse rlwrap, and the user (who thinks she is talking straight
|
735
|
+
to the rlwapped command) probably meant those signals to be sent to
|
736
|
+
the command itself. For this reason the filter starts with all signals blocked.
|
737
|
+
|
738
|
+
Filters that interact with the users terminal (e.g. to run a pager)
|
739
|
+
should unblock signals like SIGTERM, SIGWINCH.
|
740
|
+
|
741
|
+
=head1 FILTER LIFETIME
|
742
|
+
|
743
|
+
The filter is started by B<rlwrap> after I<command>, and stays alive
|
744
|
+
as long as B<rlwrap> runs. Filter methods are immediately usable. When
|
745
|
+
I<command> exits, the filter stays around for a little longer in order
|
746
|
+
to process I<command>'s last words. As calling the cwd and
|
747
|
+
cloak_and_dagger methods at that time will make the filter die with an
|
748
|
+
error, it may be advisable to wrap those calls in eval{}
|
749
|
+
|
750
|
+
If a filter calls die() it will send an (out-of-band) TAG_ERROR
|
751
|
+
message to rlwrap before exiting. rlwrap will then report the message
|
752
|
+
and exit (just after its next in-band message - out-of-band messages
|
753
|
+
are not always processed immediately)
|
754
|
+
|
755
|
+
die() within an eval() sets $@ as usual.
|
756
|
+
|
757
|
+
=head1 ENVIRONMENT
|
758
|
+
|
759
|
+
Before calling a filter, B<rlwrap> sets the following environment variables:
|
760
|
+
|
761
|
+
RLWRAP_FILTERDIR directory where RlwrapFilter.pm and most filters live (set by B<rlwrap>, can be
|
762
|
+
overridden by the user before calling rlwrap)
|
763
|
+
|
764
|
+
PATH rlwrap automatically adds $RLWRAP_FILTERDIR to the front of filter's PATH
|
765
|
+
|
766
|
+
RLWRAP_VERSION rlwrap version (e.g. "0.35")
|
767
|
+
|
768
|
+
RLWRAP_COMMAND_PID process ID of the rlwrapped command
|
769
|
+
|
770
|
+
RLWRAP_COMMAND_LINE command line of the rlwrapped command
|
771
|
+
|
772
|
+
RLWRAP_IMPATIENT whether rlwrap is in "impatient mode" (cf B<rlwrap (1)>). In impatient mode,
|
773
|
+
the candidate prompt is filtered through the output handler (and displayed before
|
774
|
+
being overwritten by the cooked prompt).
|
775
|
+
|
776
|
+
RLWRAP_INPUT_PIPE_FD File descriptor of input pipe. For internal use only
|
777
|
+
|
778
|
+
RLWRAP_OUTPUT_PIPE_FD File descriptor of output pipe. For internal use only
|
779
|
+
|
780
|
+
RLWRAP_MASTER_PTY_FD File descriptor of I<command>'s pty.
|
781
|
+
|
782
|
+
|
783
|
+
=head1 DEBUGGING FILTERS
|
784
|
+
|
785
|
+
While RlwrapFilter.pm makes it easy to write simple filters, debugging
|
786
|
+
them can be a problem. A couple of useful tricks:
|
787
|
+
|
788
|
+
=head2 LOGGING
|
789
|
+
|
790
|
+
When running a filter, the in- and outgoing messages can be logged by
|
791
|
+
the B<logger> filter, using a pipeline:
|
792
|
+
|
793
|
+
rlwrap -z 'pipeline logger incoming : my_filter : logger outgoing' command
|
794
|
+
|
795
|
+
|
796
|
+
=head2 RUNNING WITHOUT B<rlwrap>
|
797
|
+
|
798
|
+
When called by rlwrap, filters get their input from
|
799
|
+
$RLWRAP_INPUT_PIPE_FD and write their output to
|
800
|
+
$RLWRAP_OUTPUT_PIPE_FD, and expect and write messages consisting of a
|
801
|
+
tag byte, a 32-bit length and the message proper. This is not terribly
|
802
|
+
useful when running a filter directly from the command line (outside
|
803
|
+
rlwrap), even if we set the RLWRAP_*_FD ourselves.
|
804
|
+
|
805
|
+
Therfore, when run directly from the command line, a filter expects
|
806
|
+
input messages on its standard input of the form
|
807
|
+
|
808
|
+
TAG_PROMPT >
|
809
|
+
|
810
|
+
(i.a. a tag name, one space and a message) and it will respond in the
|
811
|
+
same way on its standard output
|
812
|
+
|
813
|
+
|
814
|
+
=head1 SEE ALSO
|
815
|
+
|
816
|
+
B<rlwrap> (1), B<readline> (3)
|