aboutsummaryrefslogtreecommitdiffstats
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rw-r--r--cli/bash-completion.sh74
-rwxr-xr-xcli/icevault89
2 files changed, 77 insertions, 86 deletions
diff --git a/cli/bash-completion.sh b/cli/bash-completion.sh
index bbef124..e1c6424 100644
--- a/cli/bash-completion.sh
+++ b/cli/bash-completion.sh
@@ -20,15 +20,53 @@ _icevault() {
COMPREPLY=()
declare -a files
- local opts="--debug -h --help -p --show-passwords -s --socket= --version"
- local args="fill insert dump clip edit ls"
- local OPTIND IFS
- # find out if our $cur is an optional argument or a command
- for (( OPTIND = 1; OPTIND < cword; OPTIND++ )); do
+ local cmd firstopt=2 opts nargs
+ if [ $cword -eq 1 -a -z "$cur" ] || [[ "${words[1]}" =~ :// ]] || [[ "${words[1]}" =~ ^- ]]; then
+ firstopt=1
+ cmd=fill # default command
+ elif [ $cword -gt 1 ]; then
+ cmd="${words[1]}"
+ fi
+
+ local commands='fill clip cp dump edit git insert ls mv reencrypt rm'
+ case "$cmd" in
+ fill) opts='-f --force -p --show-passwords -s --socket='; nargs=1;;
+ clip) opts=; nargs=1 ;;
+ cp) opts='-f --force'; nargs=2;;
+ dump) opts='-p --show-passwords'; nargs=1;;
+ edit) opts=; nargs=1 ;;
+ git) opts= ;;
+ insert) opts='-f --force -s --socket='; nargs=0;;
+ ls) opts='-0 --zero -r --recursive'; nargs=-1;;
+ mv) opts='-f --force'; nargs=2;;
+ reencrypt) opts=; nargs=-1 ;;
+ rm) opts='-f --force -r --recursive'; nargs=-1;;
+ esac
+ opts="${opts:+$opts }--debug -? --help --version"
+
+ if [ "$cmd" = git ]; then
+ # use bash completion for git by ignoring the first word ('icevault')
+ [ -f /usr/share/bash-completion/completions/git ] && . /usr/share/bash-completion/completions/git || return
+ COMP_CWORD=$(( $COMP_CWORD - 1 ))
+ COMP_WORDS=( "${COMP_WORDS[@]:1}" )
+ local GIT_DIR GIT_WORK_TREE # fetch store and git-dir from the config-file
+ GIT_WORK_TREE=$(sed -n '/^store\s*=\s*/{s///p;q}' "${XDG_CONFIG_HOME:-$HOME/.config}/icevault")
+ GIT_WORK_TREE=$( echo "${GIT_WORK_TREE:-icevault}" | sed -re "s@^~/@$HOME@; t; s@^/@/@; t; s@^@${XDG_DATA_HOME:-$HOME/.local/share}/@" )
+ GIT_DIR=$(sed -n '/^git-dir\s*=\s*/{s///p;q}' "${XDG_CONFIG_HOME:-$HOME/.config}/icevault")
+ GIT_DIR=$( echo "${GIT_DIR:-.git}" | sed -re "s@^~/@$HOME@; t; s@^/@/@; t; s@^@$GIT_WORK_TREE/@" )
+ export GIT_DIR GIT_WORK_TREE
+ _git
+ return
+ fi
+
+ local OPTIND socket
+ # find out if our $cur is an optional argument or not
+ for (( OPTIND = firstopt; OPTIND < cword; OPTIND++ )); do
case "${words[OPTIND]}" in
- -s) (( OPTIND++ ));;
+ -s) (( OPTIND++ )); [ $OPTIND -lt $cword ] && socket="${words[OPTIND]}";;
--) break;;
+ --socket=*) [ $OPTIND -lt $cword ] && socket="${words[OPTIND]#--socket=}";;
-*) ;;
*) break;;
esac
@@ -45,20 +83,19 @@ _icevault() {
[ -d "$p" -o -S "$p" ] && COMPREPLY+=( "$p" )
done
return
+ elif [[ $OPTIND -eq $cword && "$cur" =~ ^- ]]; then
+ COMPREPLY+=( $(compgen -W "$opts" -- "$cur") )
+ [ "${#COMPREPLY[@]}" -eq 1 -a "${COMPREPLY[0]}" = '--socket=' ] && compopt -o nospace
+ return
fi
- # complete options and commands
- if [ $OPTIND -eq $cword -a "$cur" ]; then
- COMPREPLY+=( $(compgen -W "$opts $args" -- "$cur") )
- [ "${#COMPREPLY[@]}" -eq 1 -a "${COMPREPLY[0]}" = --socket= ] && compopt -o nospace
- fi
- [ $(($OPTIND + 1)) -lt $cword ] && return
-
local trim=
- if [ -z "$cur" -a $(($OPTIND + 1)) -eq $cword -a "${words[OPTIND]}" = insert ]; then
+ if [ "$cmd" = fill -a \( $OPTIND -eq $cword -o $(( $OPTIND + 1)) -eq $cword \) -a -z "$cur" ]; then
+ cur="$(icevault _geturi ${socket:+--socket="$socket"})"/ # get URI from webpage
+ elif [ -z "$cmd" -a "$cword" -eq 1 -a "$cur" ]; then
+ # autocomplete command
+ COMPREPLY+=( $(compgen -W "$commands" -- "$cur") )
return
- elif [ -z "$cur" -a $OPTIND -eq $cword ] || [ -z "$cur" -a $(($OPTIND + 1)) -eq $cword -a "${words[OPTIND]}" = fill ]; then
- cur="$(icevault _geturi)"/ # get URI from webpage
else
cur=$(dequote "$cur")
# trim words with : or = in $COMP_WORDBREAKS; see __ltrim_colon_completions
@@ -72,13 +109,14 @@ _icevault() {
fi
local uri
- if [[ $OPTIND -eq $cword || ! "${words[OPTIND]}" =~ :// ]]; then
+ # autocomplete with identities as long as we don't exeed $nargs
+ if [ $nargs -lt 0 -o $(($cword - $OPTIND + 1)) -le $nargs ]; then
compopt -o filenames -o noquote
while read -r -d $'\0' uri; do
# quote manually (so we don't quote the : and =)
uri=$( echo "${uri#$trim}" | sed "s/[][\\{}*?~<>;'\"|&()\!$\` \t]/\\\\&/g" )
COMPREPLY+=( "$uri" )
- done < <(icevault -0 _complete "$cur")
+ done < <(icevault _complete -0 "$cur" 2>/dev/null)
[ "${#COMPREPLY[@]}" -eq 1 -a "${COMPREPLY[0]: -1:1}" = / ] && compopt -o nospace
return 0
fi
diff --git a/cli/icevault b/cli/icevault
index be7902f..1f662bf 100755
--- a/cli/icevault
+++ b/cli/icevault
@@ -308,73 +308,6 @@ sub matches($) {
return @matches;
}
-# Get all identities with the given $prefix. If there are multiple
-# matches and $all is false, limit the output to one depth more than the
-# longuest common prefix.
-sub complete($;$) {
- my $prefix = shift // '';
- my $all = shift;
-
- my ($s, $h, $i); # extract URI components from the prefix
- if ($prefix =~ /\A([A-Za-z0-9-]+):\/\/([^\P{Graph}:\/]+(?::\d+)?)\/([^\P{Print}\/]*)\z/) {
- ($s, $h, $i) = ($1, $2, $3);
- } elsif ($prefix =~ /\A([A-Za-z0-9-]+):\/\/([^\P{Graph}\/]*)\z/) {
- ($s, $h, $i) = ($1, $2, undef);
- } elsif ($prefix =~ /\A([A-Za-z0-9-]*)(:\/?)?\z/) {
- ($s, $h, $i) = ($1, (defined $2 ? '' : undef), undef);
- } else {
- exit;
- }
-
- # construct a glob pattern with these URI components
- my ($gs, $gh, $gi) = ($s, $h, $i);
- s/([\\\[\]\{\}\*\?\~])/\\$1/g foreach grep defined, ($gs, $gh, $gi); # escape meta chars
-
- # add trailing wildcards
- $gs .= '*' if defined $gs and !defined $gh;
- $gh .= '*' if defined $gh and !defined $gi;
- $gi .= '*' if defined $gi;
-
- # construct regexp to extract the URI compontents of the matching URIs
- my ($ps, $ph, $pi) = ($s, $h, $i);
- $ps = defined $h ? qr/(?<s>\Q$s\E)/ : (defined $s and $s ne '') ? qr/(?<s>\Q$s\E[A-Za-z0-9-]*)/ : qr/(?<s>[A-Za-z0-9-]+)/;
- $ph = defined $i ? qr/(?<h>\Q$h\E)/ : (defined $h and $h ne '') ? qr/(?<h>\Q$h\E[^\P{Graph}\/]*)/ : qr/(?<h>[^\P{Graph}\/]+)/;
- $pi = (defined $i and $i ne '') ? qr/(?<i>\Q$i\E[^\P{Print}\/]*)/ : qr/(?<i>[^\P{Print}\/]+)/;
-
- my $store = $LOCALE->encode($CONFIG{store});
- my $template = $LOCALE->encode($CONFIG{template});
- $template =~ s/(\%.)([^\%]*)\z/$1.quotemeta($2)/e;
- $template =~ s{(.*?)\%(.)}{$2 eq '%' ? '%' :
- $2 eq 's' ? quotemeta($1).$ps :
- $2 eq 'h' ? quotemeta($1).$ph :
- $2 eq 'i' ? quotemeta($1).$pi :
- die "Invalid placeholder %$1"}ge;
- my $pattern = qr/\A\Q$store\/\E$template\z/;
- myprintf \*STDERR, "Using regexp C<%s>\n", "$pattern" if $CONFIG{debug};
-
- my @matches;
- foreach my $filename (myglob($gs, $gh, $gi)) {
- next unless -f $filename;
- $LOCALE->decode($filename) =~ $pattern or die "$filename doesn't match $pattern";
- push @matches, "$+{s}://$+{h}/$+{i}";
- }
- return @matches if $all or $#matches < 1;
-
- # find the longest common prefix to determine the depth level of completion
- $matches[0] =~ /\A([A-Za-z0-9-]+):\/\/([^\P{Graph}:\/]+(?::\d+)?)\// or die;
- ($s, $h) = ($1, $2);
-
- if (all { /\A\Q$s\E:\/\/\Q$h\E\// } @matches) { # common host: list all ids
- } elsif (all { /\A\Q$s\E:\/\// } @matches) { # common scheme: list only hosts
- s#/[^\P{Print}\/]+\z#/# foreach @matches;
- } else { # no common scheme: list only schemes
- s#://[^\P{Graph}\/]+/[^\P{Print}\/]+\z#://# foreach @matches;
- }
-
- my %matches = map {( $_ => 1 )} @matches;
- return sort keys %matches;
-}
-
# Redact passwords, unless $CONFIG{'show-passwords'} is set.
sub safeValue($;$) {
my $field = shift;
@@ -776,15 +709,35 @@ sub getopts(%) {
if ($COMMAND eq '_complete') {
# used internaly for auto-completion
GetOptions(\%CONFIG, qw/zero|0/) or die;
+ loadConfig();
die unless $#ARGV == 0;
+
+ $CONFIG{recursive} = 1;
+ my @matches = grep defined,
+ map { $_->{id} =~ /\A\Q$ARGV[0]\E/ ? $_->{id} : undef}
+ list( $ARGV[0] =~ /\A(.*\/)/ ? $1 : $ARGV[0] =~ /\A([A-Za-z0-9-]+):\z/ ? "$1://" : () );
+
+ # find the longest common prefix to determine the depth level of completion
+ $matches[0] =~ /\A([A-Za-z0-9-]+):\/\/([^\P{Graph}:\/]+(?::\d+)?)\// or die;
+ my ($s, $h) = ($1, $2);
+
+ if (all { /\A\Q$s\E:\/\/\Q$h\E\// } @matches) { # common host: list all ids
+ } elsif (all { /\A\Q$s\E:\/\// } @matches) { # common scheme: list only hosts
+ s#/[^\P{Print}\/]+\z#/# foreach @matches;
+ } else { # no common scheme: list only schemes
+ s#://[^\P{Graph}\/]+/[^\P{Print}\/]+\z#://# foreach @matches;
+ }
+ my %matches = map {($_ => 1)} grep defined, @matches;
+
my $delim = $CONFIG{zero} ? "\0" : "\n";
- print $LOCALE->encode($_), $delim foreach complete(shift @ARGV);
+ print $LOCALE->encode($_), $delim foreach sort keys %matches;
exit;
}
elsif ($COMMAND eq '_geturi') {
# used internaly for auto-completion
GetOptions(\%CONFIG, qw/socket|s=s/) or die;
+ loadConfig();
die if @ARGV;
print $LOCALE->encode( &connect($CONFIG{socket}) ), "\n";
sendCommand 'QUIT';