about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2020-12-18 05:05:09 +0000
committerEric Wong <e@80x24.org>2020-12-19 09:32:08 +0000
commit2fe6af26d737773e0a7cafa5902360ab1309c807 (patch)
tree0bb1de157dca675d8f9857c32a0077e0bea7292d
parentf2c7b911a1c4a7520091ba7224773c30e409c337 (diff)
downloadpublic-inbox-2fe6af26d737773e0a7cafa5902360ab1309c807.tar.gz
Much work still needs to be done, but that goes for this
entire project :P
-rw-r--r--MANIFEST1
-rw-r--r--contrib/completion/lei-completion.bash11
-rw-r--r--lib/PublicInbox/LEI.pm61
3 files changed, 72 insertions, 1 deletions
diff --git a/MANIFEST b/MANIFEST
index 8e870c22..1834e7bb 100644
--- a/MANIFEST
+++ b/MANIFEST
@@ -62,6 +62,7 @@ ci/README
 ci/deps.perl
 ci/profiles.sh
 ci/run.sh
+contrib/completion/lei-completion.bash
 contrib/css/216dark.css
 contrib/css/216light.css
 contrib/css/README
diff --git a/contrib/completion/lei-completion.bash b/contrib/completion/lei-completion.bash
new file mode 100644
index 00000000..67cdd3ed
--- /dev/null
+++ b/contrib/completion/lei-completion.bash
@@ -0,0 +1,11 @@
+# Copyright (C) 2020 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
+
+# preliminary bash completion support for lei (Local Email Interface)
+# Needs a lot of work, see `lei__complete' in lib/PublicInbox::LEI.pm
+_lei() {
+        COMPREPLY=($(compgen -W "$(lei _complete ${COMP_WORDS[@]})" \
+                        -- "${COMP_WORDS[COMP_CWORD]}"))
+        return 0
+}
+complete -o filenames -o bashdefault -F _lei lei
diff --git a/lib/PublicInbox/LEI.pm b/lib/PublicInbox/LEI.pm
index fd412324..7004e9d7 100644
--- a/lib/PublicInbox/LEI.pm
+++ b/lib/PublicInbox/LEI.pm
@@ -132,7 +132,11 @@ our %CMD = ( # sorted in order of importance/use:
 
 'reorder-local-store-and-break-history' => [ '[REFNAME]',
         'rewrite git history in an attempt to improve compression',
-        'gc!' ]
+        'gc!' ],
+
+# internal commands are prefixed with '_'
+'_complete' => [ '[...]', 'internal shell completion helper',
+                pass_through('everything') ],
 ); # @CMD
 
 # switch descriptions, try to keep consistent across commands
@@ -209,6 +213,10 @@ my %OPTDESC = (
         'unset matching NAME, may be specified multiple times'],
 ); # %OPTDESC
 
+my %CONFIG_KEYS = (
+        'leistore.dir' => 'top-level storage location',
+);
+
 sub x_it ($$) { # pronounced "exit"
         my ($self, $code) = @_;
         if (my $sig = ($code & 127)) {
@@ -223,6 +231,8 @@ sub x_it ($$) { # pronounced "exit"
         }
 }
 
+sub puts ($;@) { print { shift->{1} } map { "$_\n" } @_ }
+
 sub emit {
         my ($self, $channel) = @_; # $buf = $_[2]
         print { $self->{$channel} } $_[2] or die "print FD[$channel]: $!";
@@ -522,6 +532,55 @@ sub lei_daemon_env {
 
 sub lei_help { _help($_[0]) }
 
+# Shell completion helper.  Used by lei-completion.bash and hopefully
+# other shells.  Try to do as much here as possible to avoid redundancy
+# and improve maintainability.
+sub lei__complete {
+        my ($self, @argv) = @_; # argv = qw(lei and any other args...)
+        shift @argv; # ignore "lei", the entire command is sent
+        @argv or return puts $self, grep(!/^_/, keys %CMD);
+        my $cmd = shift @argv;
+        my $info = $CMD{$cmd} // do { # filter matching commands
+                @argv or puts $self, grep(/\A\Q$cmd\E/, keys %CMD);
+                return;
+        };
+        my ($proto, undef, @spec) = @$info;
+        my $cur = pop @argv;
+        my $re = defined($cur) ? qr/\A\Q$cur\E/ : qr/./;
+        if (substr($cur // '-', 0, 1) eq '-') { # --switches
+                # gross special case since the only git-config options
+                # Consider moving to a table if we need more special cases
+                # we use Getopt::Long for are the ones we reject, so these
+                # are the ones we don't reject:
+                if ($cmd eq 'config') {
+                        puts $self, grep(/$re/, keys %CONFIG_KEYS);
+                        @spec = qw(add z|null get get-all unset unset-all
+                                replace-all get-urlmatch
+                                remove-section rename-section
+                                name-only list|l edit|e
+                                get-color-name get-colorbool);
+                        # fall-through
+                }
+                # TODO: arg support
+                puts $self, grep(/$re/, map { # generate short/long names
+                        my $eq = '';
+                        if (s/=.+\z//) { # required arg, e.g. output|o=i
+                                $eq = '=';
+                        } elsif (s/:.+\z//) { # optional arg, e.g. mid:s
+                        } else { # negation: solve! => no-solve|solve
+                                s/\A(.+)!\z/no-$1|$1/;
+                        }
+                        map {
+                                length > 1 ? "--$_$eq" : "-$_"
+                        } split(/\|/, $_, -1) # help|h
+                } grep { !ref } @spec); # filter out $GLP_PASS ref
+        } elsif ($cmd eq 'config' && !@argv && !$CONFIG_KEYS{$cur}) {
+                puts $self, grep(/$re/, keys %CONFIG_KEYS);
+        }
+        # TODO: URLs, pathnames, OIDs, MIDs, etc...  See optparse() for
+        # proto parsing.
+}
+
 sub reap_exec { # dwaitpid callback
         my ($self, $pid) = @_;
         x_it($self, $?);