Skip to content

Commit

Permalink
bash-completion: add completion script
Browse files Browse the repository at this point in the history
  • Loading branch information
audeoudh authored and ebiggers committed Nov 30, 2020
1 parent cc933cd commit b74cd30
Show file tree
Hide file tree
Showing 2 changed files with 296 additions and 3 deletions.
13 changes: 10 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ lint: $(BIN)/golint $(BIN)/staticcheck $(BIN)/misspell
go list ./... | xargs -L1 golint -set_exit_status
staticcheck ./...
misspell -source=text $(FILES)
shellcheck -s bash cmd/fscrypt/fscrypt_bash_completion
( cd cli-tests && shellcheck -x *.sh)

clean:
Expand Down Expand Up @@ -158,8 +159,8 @@ coverage.out: $(BIN)/gocovmerge $(COVERAGE_FILES)
@go test -coverpkg=./... -covermode=count -coverprofile=$@ -p 1 ./$* 2> /dev/null

###### Installation Commands (require sudo) #####
.PHONY: install install-bin install-pam uninstall
install: install-bin install-pam
.PHONY: install install-bin install-pam uninstall install-completion
install: install-bin install-pam install-completion

PREFIX := /usr/local
BINDIR := $(PREFIX)/bin
Expand All @@ -181,10 +182,16 @@ install-pam: $(PAM_MODULE)
install -d $(DESTDIR)$(PAM_CONFIG_DIR)
install $(PAM_CONFIG) $(DESTDIR)$(PAM_CONFIG_DIR)/$(NAME)

COMPLETION_INSTALL_DIR := $(PREFIX)/share/bash-completion/completions

install-completion: cmd/fscrypt/fscrypt_bash_completion
install -Dm644 $< $(DESTDIR)$(COMPLETION_INSTALL_DIR)/fscrypt

uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(NAME) \
$(DESTDIR)$(PAM_INSTALL_PATH) \
$(DESTDIR)$(PAM_CONFIG_DIR)/$(NAME)
$(DESTDIR)$(PAM_CONFIG_DIR)/$(NAME) \
$(DESTDIR)$(COMPLETION_INSTALL_DIR)/fscrypt

#### Tool Building Commands ####
TOOLS := $(addprefix $(BIN)/,protoc golint protoc-gen-go goimports staticcheck gocovmerge misspell)
Expand Down
286 changes: 286 additions & 0 deletions cmd/fscrypt/fscrypt_bash_completion
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# fscrypt_bash_completion
#
# Copyright 2017 Google Inc.
# Author: Henry-Joseph Audéoud (h.audeoud@gmail.com)
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License. You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.


# Prefer completion script style COMPREPLY=($(...)) assignment.
# shellcheck disable=SC2207
true # To apply shellcheck directive to all file


# Output list of possible mount points
_fscrypt_mountpoints()
{
# shellcheck disable=SC2016
fscrypt status 2>/dev/null | \
command awk 'substr($0, 1, 1) == "/" && $5 == "Yes" { print $1 }'
}


# Complete with all possible mountpoints
_fscrypt_complete_mountpoint()
{
COMPREPLY=($(compgen -W "$(_fscrypt_mountpoints)" -- "${cur}"))
}


# Output list of possible policy or protector IDs
# $1: the mount point on which policies are looked for.
# $2: the section (policy or protector) to retrieve
_fscrypt_status_section()
{
local section=${2^^}
# shellcheck disable=SC2016
fscrypt status "$1" 2>/dev/null | \
command awk '/^[[:xdigit:]]{16}/ && section == "'"$section"'" { print $1; next; }
{ section = $1 }'
}


# Complete with policies or protectors
_fscrypt_complete_policy_or_protector()
{
local status_section="$1"
if [[ $cur = *:* ]]; then
# Complete with IDs of the given mountpoint
local mountpoint="${cur%:*}" id="${cur#*:}"
COMPREPLY=($(compgen \
-W "$(_fscrypt_status_section "${mountpoint}" "${status_section}")" \
-- "${id}"))
else
# Complete with mountpoints, with colon and without ending space
COMPREPLY=($(compgen -W "$(_fscrypt_mountpoints)" \
-- "${cur}" | sed s/\$/:/))
compopt -o nospace
fi
}


# Complete with all arguments of that function
_fscrypt_complete_word()
{
COMPREPLY=($(compgen -W "$*" -- "${cur}"))
}


# Complete with all arguments of that function, plus global options
_fscrypt_complete_option()
{
local additional_opts=( "$@" )
# Add global options, always correct
additional_opts+=( --verbose --quiet --help )
COMPREPLY=($(compgen -W "${additional_opts[*]}" -- "${cur}"))
}


_fscrypt()
{
# Initialize completion: compute some local variables to easily
# detect what is written on the command line. -s is for splitting
# long options on `=`, and -n is for splitting them also on `:`
# (used in the protectors/policies `MOUNTPOINT:ID` forms).
#
# `split` is set by `_init_completion -s`, we must declare it local
# even if we don't use it, not to modify the environment.
# shellcheck disable=SC2034
local cur prev words cword split
_init_completion -s -n : || return

# Complete the options with argument here, if previous word were such
# an option. It would be too difficult to check if they take place in
# the correct command (such as `fscrypt status # --key ...`)—and that
# is the command's job—so just complete them first.
case $prev in
--key)
# Any file is accepted
_filedir
return ;;
--name)
# New value, nothing to complete
return ;;
--policy|--protector|--unlock-with)
local p_or_p="${prev#--}"
[[ $p_or_p = unlock-with ]] && p_or_p=protector
_fscrypt_complete_policy_or_protector "${p_or_p}"
return ;;
--source)
# Complete with keywords
_fscrypt_complete_word \
pam_passphrase custom_passphrase raw_key
return ;;
--time)
# It's a time, hard to complete a number…
return ;;
--user)
# Complete with a user
COMPREPLY=($(compgen -u -- "${cur}"))
return ;;
esac

# Fetch positional arguments (i.e. subcommands)
local positional
positional=()
local iword
for ((iword = 1; iword < ${#words[@]} - 1; iword++)); do
[[ ${words[iword - 1]} == --@(key|name|policy|protector|unlock-with|source|time|user) ]] \
&& continue # Argument of previous option, skip
[[ ${words[iword]} == -* ]] && continue # Option, skip
positional+=("${words[iword]}")
done

# If completing the first positional, complete with all possible commands
if [[ ${#positional[@]} == 0 ]]; then
if [[ $cur == -* ]]; then
_fscrypt_complete_option
else
_fscrypt_complete_word \
encrypt lock metadata purge setup status unlock
fi
return
fi

# Complete according to that provided
case ${positional[0]-} in
encrypt) # Directory or option
if [[ $cur == -* ]]; then
_fscrypt_complete_option \
--policy= --unlock-with= --protector= --source= \
--user= --name= --key= --skip-unlock --no-recovery
else
_filedir -d
fi ;;
lock) # Directory or option
if [[ $cur == -* ]]; then
_fscrypt_complete_option --user= --all-users
else
_filedir -d
fi ;;
purge) # Mountpoint or options
if [[ $cur == -* ]]; then
_fscrypt_complete_option --user= --force
else
_fscrypt_complete_mountpoint
fi ;;
setup) # Mountpoint or options
if [[ $cur == -* ]]; then
_fscrypt_complete_option --time= --force
else
_fscrypt_complete_mountpoint
fi ;;
status) # Directory (only global options for this command)
if [[ $cur == -* ]]; then
_fscrypt_complete_option
else
_filedir -d
fi ;;
unlock) # Directory or option
if [[ $cur == -* ]]; then
_fscrypt_complete_option --unlock-with= --user= --key=
else
_filedir -d
fi ;;
metadata)
# This command has subcommands
if [[ ${#positional[@]} = 1 ]]; then
if [[ $cur = -* ]]; then
_fscrypt_complete_option
else
# Still no subcommand, complete with them
_fscrypt_complete_word \
add-protector-to-policy create change-passphrase \
destroy dump remove-protector-from-policy
fi
return
fi
# We have a subcommand, complete according to it
case ${positional[1]-} in
add-protector-to-policy) # Options only
_fscrypt_complete_option \
--protector= --policy= --unlock-with= --key=
;;
change-passphrase) # Options only
_fscrypt_complete_option --protector=
;;
destroy) # Mountpoint or option
if [[ $cur == -* ]]; then
_fscrypt_complete_option \
--protector= --policy= --force
else
_fscrypt_complete_mountpoint
fi ;;
dump) # Options only
_fscrypt_complete_option --protector= --policy=
;;
remove-protector-from-policy) # Options only
_fscrypt_complete_option \
--protector= --policy= --force
;;
create)
# This subcommand has subsubcommands
if [[ ${#positional[@]} = 2 ]]; then
if [[ $cur = -* ]]; then
_fscrypt_complete_option
else
# Still no subcommand, complete with them
_fscrypt_complete_word protector policy
fi
return
fi
# We have a subsubcommand, complete according to it
case ${positional[2]-} in
policy) # Mountpoint or option
if [[ $cur = -* ]]; then
_fscrypt_complete_option --protector= --key=
else
_fscrypt_complete_mountpoint
fi ;;
protector) # Mountpoint or option
if [[ $cur = -* ]]; then
_fscrypt_complete_option \
--source= --name= --key= --user=
else
_fscrypt_complete_mountpoint
fi ;;
*)
# Unrecognized subsubcommand… Suppose a new
# unknown subsubcommand and complete with
# global options only
_fscrypt_complete_option
;;
esac
;;
*)
# Unrecognized subcommand… Suppose a new unknown
# subcommand and complete with global options only
_fscrypt_complete_option
;;
esac
;;
*)
# Unrecognized command… Suppose a new unknown command and
# complete with global options only
_fscrypt_complete_option
;;
esac

# When the sole offered completion is --*=, do not put a space after
# the equal sign as we wait for the argument value.
[[ ${#COMPREPLY[@]} == 1 ]] && [[ ${COMPREPLY[0]} == "--"*"=" ]] \
&& compopt -o nospace
} &&
complete -F _fscrypt fscrypt

# ex: filetype=bash

0 comments on commit b74cd30

Please sign in to comment.