about summary refs log tree commit diff
path: root/Misc/lete2ctl
diff options
context:
space:
mode:
Diffstat (limited to 'Misc/lete2ctl')
-rwxr-xr-xMisc/lete2ctl301
1 files changed, 301 insertions, 0 deletions
diff --git a/Misc/lete2ctl b/Misc/lete2ctl
new file mode 100755
index 000000000..ca00b8aee
--- /dev/null
+++ b/Misc/lete2ctl
@@ -0,0 +1,301 @@
+#!/usr/local/bin/perl -w
+#
+#   ``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.
+#
+# 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
+if (@ARGV) {
+    ($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 = defined($ret) ? $ret . $word : $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') {
+	if (defined($glob)) {
+	    $qual = '-/';
+	} else {
+	    $ret = '-/';
+	}
+    } elsif ($c eq 'e') {
+	$ret = '-E';
+    } elsif ($c eq 'f' && !$glob) {
+	$ret = '-f';
+    } elsif ($c eq 'l') {
+	$ret = q!-k "(`limit | awk '{print $1}'`)"!;
+    } elsif ($c eq 'p') {
+	$ret = "-W $glob -f", undef($glob) if defined($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 (defined($glob) && (($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/) {
+	undef(@stuff); 
+	$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) = @split;
+	    defined($suffix) && ($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 eq '*') || ($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__