home *** CD-ROM | disk | FTP | other *** search
- #!/usr/local/bin/perl -- -*-perl-*-
- #
- # ``Wee have also Shelles, thee Lyke of whych you knowe not, wherein
- # thee User may with thee merest Presse of thee Tabbe-Keye expande
- # or compleat al Maner of Wordes and such-like Diversities.''
- # - Francis Bacon, `New Atlantis' (or not).
- #
- # Convert tcsh "complete" statements to zsh "compctl" statements.
- # Runs as a filter. Should ignore anything which isn't a "complete".
- # It expects each "complete" statement to be the first thing on a line.
- # All the examples in the tcsh manual give sensible results.
- # Author: Peter Stephenson <pws@s-a.amtp.liv.ac.uk>
- #
- # Option:
- # -x (exact): only applies in the case of command disambiguation (is
- # that really a word?) If you have lines like
- # complete '-co*' 'p/0/(compress)'
- # (which makes co<TAB> always complete to `compress') then the
- # resulting "compctl" statements will produce one of two behaviours:
- # (1) By default (like tcsh), com<TAB> etc. will also complete to
- # "compress" and nothing else.
- # (2) With -x, com<TAB> does ordinary command completion: this is
- # more flexible.
- # I don't understand what the hyphen in complete does and I've ignored it.
- #
- # Notes:
- # (1) The -s option is the way to do backquote expansion. In zsh,
- # "compctl -s '`users`' talk" works (duplicates are removed).
- # (2) Complicated backquote completions should definitely be rewritten as
- # shell functions (compctl's "-K func" option). Although most of
- # these will be translated correctly, differences in shell syntax
- # are not handled.
- # (3) Replacement of $:n with the n'th word on the current line with
- # backquote expansion now works; it is not necessarily the most
- # efficient way of doing it in any given case, however.
- # (4) I have made use of zsh's more sophisticated globbing to change
- # things like ^foo.{a,b,c,d} to ^foo.(a|b|c|d), which works better.
- # It's just possible in some cases you may want to change it back.
- # (5) Make sure all command names with wildcards are processed together --
- # they need to be lumped into one "compctl -C" or "compctl -D"
- # statement for zsh.
-
- # Handle options
- ($ARGV[0] eq '-x') && shift && ($opt_x = 1);
- ($ARGV[0] =~ /^-+$/) && shift;
-
- # Function names used (via magic autoincrement) when cmdline words are needed
- $funcnam = 'compfn001';
-
- # Read next word on command line
- sub getword {
- local($word, $word2, $ret);
- ($_) = /^\s*(.*)$/;
- while ($_ =~ /^\S/) {
- if (/^[\']/) {
- ($word, $_) = /^\'([^\']*).(.*)$/;
- } elsif (/^[\"]/) {
- ($word, $_) = /^\"([^\"]*).(.*)$/;
- while ($word =~ /\\$/) {
- chop($word);
- ($word2, $_) = /^([^\"]*).(.*)$/;
- $word .= '"' . $word2;
- }
- } elsif (/\S/) {
- ($word, $_) = /^([^\s\\\'\"#;]*)(.*)$/;
- # Backslash: literal next character
- /^\\(.)/ && (($word .= substr($_,1,1)),
- ($_ = substr($_,2)));
- # Rest of line quoted or end of command
- /^[#;]/ && ($_ = '');
- } else {
- return undef;
- }
- length($word) && ($ret = $ret . $word);
- }
- $ret;
- }
-
- # Interpret the x and arg in 'x/arg/type/'
- sub getpat {
- local($pat,$arg) = @_;
- local($ret,$i);
- if ($pat eq 'p') {
- $ret = "p[$arg]";
- } elsif ($pat eq 'n' || $pat eq 'N') {
- $let = ($arg =~ /[*?|]/) ? 'C' : 'c';
- $num = ($pat eq 'N') ? 2 : 1;
- $ret = "${let}[-${num},$arg]";
- } elsif ($pat eq 'c' || $pat eq 'C') {
- # A few tricks to get zsh to ignore up to the end of
- # any matched pattern.
- if (($pat eq 'c' && $arg =~ /^\*([^*?]*)$/)) {
- $ret = "n[-1,$1]";
- } elsif ($arg =~ /[*?]([^*?]*)$/) {
- length($1) && ($ret = " n[-1,$1]");
- $ret = "C[0,$arg] $ret";
- } else {
- $let = ($pat eq 'c') ? 's' : 'S';
- $ret = "${let}[$arg]";
- }
- }
- $ret =~ s/'/'\\''/g;
- $ret;
- }
-
- # Interpret the type in 'x/arg/type/'
- sub gettype {
- local ($_) = @_;
- local($qual,$c,$glob,$ret,$b,$m,$e,@m);
- $c = substr($_,0,1);
- ($c =~ /\w/) && (substr($_,1,1) eq ':') && ($glob = substr($_,2));
- # Nothing (n) can be handled by returning nothing. (C.f. King Lear, I.i.)
- if ($c =~ /[abcjuv]/) {
- $ret = "-$c";
- } elsif ($c eq 'S') {
- $ret = '-k signals';
- } elsif ($c eq 'd') {
- $qual = '/';
- } elsif ($c eq 'e') {
- $ret = '-E';
- } elsif ($c eq 'f' && !$glob) {
- $ret = '-f';
- } elsif ($c eq 'l') {
- $ret = qq
- -k '(cputime filesize datasize stacksize coredumpsize resident descriptors)'
- ;
- } elsif ($c eq 'p') {
- # Use globbing, but make sure there's a star at the end
- ($glob =~ /\*$/) || ($glob .= '*');
- } elsif ($c eq 's') {
- $ret = '-p';
- } elsif ($c eq 't') {
- $qual = '.';
- } elsif ($c eq 'x') {
- $glob =~ s/'/'\\''/g;
- $ret = "-X '$glob'";
- undef($glob);
- } elsif ($c eq '$') { # '{
- $ret = "-k " . substr($_,1);
- } elsif ($c eq '(') {
- s/'/'\\''/g;
- $ret = "-k '$_'";
- } elsif ($c eq '`') {
- # this took some working out...
- if (s/\$:(\d+)/$foo=$1+1,"\${word[$foo]}"/ge) {
- $ret = "-K $funcnam";
- $genfunc .= <<"HERE";
- function $funcnam {
- local word
- read -cA word
- reply=($_)
- }
- HERE
- $funcnam++;
- } else {
- s/'/'\\''/g;
- $ret = "-s '$_'";
- }
- }
-
- # foo{bar,ba,blak,sheap} -> foo(bar|ba|blak|sheap).
- # This saves a lot of mess, since in zsh brace expansion occurs
- # before globbing. I'm sorry, but I don't trust $` and $'.
- while ((($b,$m,$e) = ($glob =~ /^(.*)\{(.*)\}(.*)$/))
- && ($m =~ /,/)) {
- @m = split(/,/, $m);
- for ($i = 0; $i < @m; $i++) {
- while ($m[$i] =~ /\\$/) {
- substr($m[$i],-1,1) = "";
- splice(@m,$i,2,"$m[$i]\\,$m[$i+1]");
- }
- }
- $glob = $b . "(" . join('|',@m) . ")" . $e;
- }
-
- if ($qual) {
- $glob || ($glob = '*');
- $glob .= "($qual)";
- }
- $glob && (($glob =~ s/'/'\\''/g),($glob = "-g '$glob'"));
-
- defined($ret) && defined($glob) && ($ret .= " $glob");
- defined($ret) ? $ret : $glob;
- }
-
- # Quoted array separator for extended completions
- $" = " - ";
-
- while (<>) {
- if (/^\s*complete\s/) {
- $wc = 0;
- undef(@stuff); undef($default);
- $_ = $';
- while (/\\$/) {
- # Remove backslashed newlines: in principle these should become
- # real newlines inside quotes, but what the hell.
- ($_) = /^(.*)\\$/;
- $_ .= <>;
- }
- $command = &getword;
- if ($command =~ /^-/ || $command =~ /[*?]/) {
- # E.g. complete -co* ...
- $defmatch = $command;
- ($defmatch =~ /^-/) && ($defmatch = substr($defmatch,1));
- } else {
- undef($defmatch);
- }
- while (defined($word = &getword)) {
- # Loop over remaining arguments to "complete".
- $sep = substr($word,1,1);
- $sep =~ s/(\W)/\\$1/g;
- @split = split(/$sep/,$word);
- for ($i = 0; $i < 3; $i++) {
- while ($split[i] =~ /\\$/) {
- substr($split[i],-1,1) = "";
- splice(@split,$i,2,"$split[i]\\$sep$split[i+1]");
- }
- }
- ($pat,$arg,$type,$suffix,$crap) = @split;
- ($suffix =~ /^\s*$/) && undef($suffix);
- if (($word =~ /^n${sep}\*${sep}/) &&
- (!defined($defmatch))) {
- # The "complete" catch-all: treat this as compctl\'s
- # default (requiring no pattern matching).
- $default .= &gettype($type) . ' ';
- defined($suffix) && ($defsuf .= $suffix);
- } else {
- $pat = &getpat($pat,$arg);
- $type = &gettype($type);
- if (defined($defmatch)) {
- # The command is a pattern: use either -C or -D option.
- if ($pat eq 'p[0]') {
- # Command word (-C): 'p[0]' is redundant.
- if ($defmatch eq '*') {
- $defcommand = $type;
- } else {
- ($defmatch =~ /\*$/) && chop($defmatch);
- if ($opt_x) {
- $c = ($defmatch =~ /[*?]/) ? 'C' : c;
- $pat = "${c}[0,${defmatch}]";
- } else {
- $pat = ($defmatch =~ /[*?]/) ?
- "C[0,${defmatch}]" : "S[${defmatch}]";
- }
- push(@commandword,defined($suffix) ?
- "'$pat' $type -S '$suffix'" : "'$pat' $type");
- }
- } elsif ($pat eq "C[-1,*]") {
- # Not command word completion, but match
- # command word (only)
- if ($defmatch eq "*") {
- # any word of any command
- $defaultdefault .= " $type";
- } else {
- $pat = "W[0,$defmatch]";
- push(@defaultword,defined($suffix) ?
- "'$pat' $type -S '$suffix'" : "'$pat' $type");
- }
- } else {
- # Not command word completion, but still command
- # word with pattern
- ($defmatch = '*') || ($pat = "W[0,$defmatch] $pat");
- push(@defaultword,defined($suffix) ?
- "'$pat' $type -S '$suffix'" : "'$pat' $type");
- }
- } else {
- # Ordinary command
- push(@stuff,defined($suffix) ?
- "'$pat' $type -S '$suffix'" : "'$pat' $type");
- }
- }
- }
- if (!defined($defmatch)) {
- # Ordinary commands with no pattern
- print("compctl $default");
- defined($defsuf) && print("-S '$defsuf' ") && undef($defsuf);
- defined(@stuff) && print("-x @stuff -- ");
- print("$command\n");
- }
- if (defined($genfunc)) {
- print $genfunc;
- undef($genfunc);
- }
- }
- }
-
- (defined(@commandword) || defined($defcommand)) &&
- print("compctl -C ",
- defined($defcommand) ? $defcommand : '-c',
- defined(@commandword) ? " -x @commandword\n" : "\n");
-
- if (defined($defaultdefault) || defined(@defaultword)) {
- defined($defaultdefault) || ($defaultdefault = "-f");
- print "compctl -D $defaultdefault";
- defined(@defaultword) && print(" -x @defaultword");
- print "\n";
- }
-
- __END__
-