From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-Status: No, score=-4.0 required=3.0 tests=ALL_TRUSTED,BAYES_00 shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from localhost (dcvr.yhbt.net [127.0.0.1]) by dcvr.yhbt.net (Postfix) with ESMTP id BD4521FFAC for ; Fri, 18 Dec 2020 12:09:52 +0000 (UTC) From: Eric Wong To: meta@public-inbox.org Subject: [PATCH 22/26] lei: start working on bash completion Date: Fri, 18 Dec 2020 12:09:46 +0000 Message-Id: <20201218120950.23272-23-e@80x24.org> In-Reply-To: <20201218120950.23272-1-e@80x24.org> References: <20201218120950.23272-1-e@80x24.org> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit List-Id: Much work still needs to be done, but that goes for this entire project :P --- MANIFEST | 1 + contrib/completion/lei-completion.bash | 11 +++++ lib/PublicInbox/LEI.pm | 61 +++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 contrib/completion/lei-completion.bash 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 +# License: AGPL-3.0+ + +# 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, $?);