Add bash completion (#4534) (#4604)

* Add bash completion (#4534)
This commit is contained in:
Johannes Lorenz
2018-09-24 03:17:39 +02:00
committed by Tres Finocchiaro
parent 9fe74c2730
commit 93dc557c56
4 changed files with 433 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
# A wrapper around pkg-config-provided and cmake-provided bash completion that
# will have dynamic behavior at INSTALL() time to allow both root-level
# INSTALL() as well as user-level INSTALL().
#
# See also https://github.com/scop/bash-completion
#
# Copyright (c) 2018, Tres Finocchiaro, <tres.finocchiaro@gmail.com>
# Redistribution and use is allowed according to the terms of the BSD license.
# For details see the accompanying COPYING-CMAKE-SCRIPTS file.
#
# Usage:
# INCLUDE(BashCompletion)
# BASHCOMP_INSTALL(foo)
# ... where "foo" is a shell script adjacent to the CMakeLists.txt
#
# How it determines BASHCOMP_PKG_PATH, in order:
# 1. Uses BASHCOMP_PKG_PATH if already set (e.g. -DBASHCOMP_PKG_PATH=...)
# a. If not, uses pkg-config's PKG_CHECK_MODULES to determine path
# b. Fallback to cmake's FIND_PACKAGE(bash-completion) path
# c. Fallback to hard-coded /usr/share/bash-completion/completions
# 2. Final fallback to ${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions if
# detected path is unwritable.
# - Windows does not support bash completion
# - macOS support should eventually be added for Homebrew (TODO)
IF(WIN32)
MESSAGE(STATUS "Bash competion is not supported on this platform.")
ELSEIF(APPLE)
MESSAGE(STATUS "Bash completion is not yet implemented for this platform.")
ELSE()
INCLUDE(FindUnixCommands)
# Honor manual override if provided
IF(NOT BASHCOMP_PKG_PATH)
# First, use pkg-config, which is the most reliable
FIND_PACKAGE(PkgConfig QUIET)
IF(PKGCONFIG_FOUND)
PKG_CHECK_MODULES(BASH_COMPLETION bash-completion)
PKG_GET_VARIABLE(BASHCOMP_PKG_PATH bash-completion completionsdir)
ELSE()
# Second, use cmake (preferred but less common)
FIND_PACKAGE(bash-completion QUIET)
IF(BASH_COMPLETION_FOUND)
SET(BASHCOMP_PKG_PATH "${BASH_COMPLETION_COMPLETIONSDIR}")
ENDIF()
ENDIF()
# Third, use a hard-coded fallback value
IF("${BASHCOMP_PKG_PATH}" STREQUAL "")
SET(BASHCOMP_PKG_PATH "/usr/share/bash-completion/completions")
ENDIF()
ENDIF()
# Always provide a fallback for non-root INSTALL()
SET(BASHCOMP_USER_PATH "${CMAKE_INSTALL_PREFIX}/share/bash-completion/completions")
# Cmake doesn't allow easy use of conditional logic at INSTALL() time
# this is a problem because ${BASHCOMP_PKG_PATH} may not be writable and we
# need sane fallback behavior for bundled INSTALL() (e.g. .AppImage, etc).
#
# The reason this can't be detected by cmake is that it's fairly common to
# run "cmake" as a one user (i.e. non-root) and "make install" as another user
# (i.e. root).
#
# - Creates a script called "install_${SCRIPT_NAME}_completion.sh" into the
# working binary directory and invokes this script at install.
# - Script handles INSTALL()-time conditional logic for sane ballback behavior
# when ${BASHCOMP_PKG_PATH} is unwritable (i.e. non-root); Something cmake
# can't handle on its own at INSTALL() time)
MACRO(BASHCOMP_INSTALL SCRIPT_NAME)
# A shell script for wrapping conditionl logic
SET(BASHCOMP_SCRIPT "${CMAKE_CURRENT_BINARY_DIR}/install_${SCRIPT_NAME}_completion.sh")
FILE(WRITE ${BASHCOMP_SCRIPT} "\
#!${BASH}\n\
set -e\n\
BASHCOMP_PKG_PATH=\"${BASHCOMP_USER_PATH}\"\n\
if [ -w \"${BASHCOMP_PKG_PATH}\" ]; then\n\
BASHCOMP_PKG_PATH=\"${BASHCOMP_PKG_PATH}\"\n\
fi\n\
echo -e \"\\nInstalling bash completion...\\n\"\n\
mkdir -p \"\$BASHCOMP_PKG_PATH\"\n\
cp \"${CMAKE_CURRENT_SOURCE_DIR}/${SCRIPT_NAME}\" \"\$BASHCOMP_PKG_PATH\"\n\
chmod a+r \"\$BASHCOMP_PKG_PATH/${SCRIPT_NAME}\"\n\
echo -e \"Bash completion for ${SCRIPT_NAME} has been installed to \$BASHCOMP_PKG_PATH/${SCRIPT_NAME}\"\n\
")
INSTALL(CODE "EXECUTE_PROCESS(COMMAND chmod u+x \"install_${SCRIPT_NAME}_completion.sh\" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} )")
INSTALL(CODE "EXECUTE_PROCESS(COMMAND \"./install_${SCRIPT_NAME}_completion.sh\" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} )")
MESSAGE(STATUS "Bash completion script for ${SCRIPT_NAME} will be installed to ${BASHCOMP_PKG_PATH} or fallback to ${BASHCOMP_USER_PATH} if unwritable.")
ENDMACRO()
ENDIF()

View File

@@ -15,3 +15,6 @@ if(DOXYGEN_FOUND)
COMMENT "Generating API documentation with Doxygen"
SOURCES Doxyfile.in)
endif(DOXYGEN_FOUND)
ADD_SUBDIRECTORY(bash-completion)

View File

@@ -0,0 +1,4 @@
INCLUDE(BashCompletion)
IF(COMMAND BASHCOMP_INSTALL)
BASHCOMP_INSTALL(lmms)
ENDIF()

334
doc/bash-completion/lmms Normal file
View File

@@ -0,0 +1,334 @@
# lmms(1) completion -*- shell-script -*-
# use shellcheck: "shellcheck -e bash <filename>"
_lmms_array_contains ()
{
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
_lmms_long_param_of()
{
case "$1" in
-a)
echo "float"
;;
-b)
echo "bitrate"
;;
-c)
echo "config"
;;
-f)
echo "format"
;;
-i)
echo "interpolation"
;;
-l)
echo "loop"
;;
-m)
echo "mode"
;;
-o)
echo "output"
;;
-p)
echo "profile"
;;
-s)
echo "samplerate"
;;
-x)
echo "oversampling"
;;
*)
echo ""
;;
esac
}
_lmms_conv_old_action ()
{
case "$1" in
-d|--dump)
echo "dump"
;;
-r|--render)
echo "render"
;;
--rendertracks)
echo "rendertracks"
;;
-u|--upgrade)
echo "upgrade"
;;
*)
echo ""
;;
esac
}
_lmms()
{
local cword=$COMP_CWORD
local cur="${COMP_WORDS[COMP_CWORD]}"
# call routine provided by bash-completion
_init_completion || return
local params filemode filetypes
local i # counter variable
local pars_global pars_noaction pars_render actions shortargs
pars_global=(--allowroot --config --help --version)
pars_noaction=(--geometry --import)
pars_render=(--float --bitrate --format --interpolation)
pars_render+=(--loop --mode --output --profile)
pars_render+=(--samplerate --oversampling)
actions=(dump render rendertracks upgrade)
actions_old=(-d --dump -r --render --rendertracks -u --upgrade)
shortargs+=(-a -b -c -f -h -i -l -m -o -p -s -v -x)
local prev prev2
if [ "$cword" -gt 1 ]
then
prev=${COMP_WORDS[cword-1]}
fi
if [ "$cword" -gt 2 ]
then
prev2=${COMP_WORDS[cword-2]}
fi
# don't show shortargs, but complete them when entered
if [[ $cur =~ ^-[^-]$ ]]
then
if _lmms_array_contains "$cur" "${shortargs[@]}"
then
COMPREPLY=( "$cur" )
fi
return
fi
#
# please keep those in order like def_pars_args above
#
case $prev in
--bitrate|-b)
params="64 96 128 160 192 256 320"
;;
--config|-c)
filetypes='xml'
filemode='existing_files'
;;
--format|-f)
params='wav ogg mp3'
;;
--geometry)
# we can not name all possibilities, but this helps the user
# by showing them how the format is
params='0x0+0+0'
;;
--interpolation|-i)
params='linear sincfastest sincmedium sincbest'
;;
--import)
filetypes='mid|midi|MID|MIDI|rmi|RMI|h2song|H2SONG'
filemode='existing_files'
;;
--mode|-m)
params='s j m'
;;
--output|-o)
# default assumption: could be both
local render=1 rendertracks=1
for i in "${!COMP_WORDS[@]}"
do
if [[ ${COMP_WORDS[i]} =~ ^(render|-r|--render)$ ]]
then
rendertracks=
elif [[ ${COMP_WORDS[i]} =~ ^(rendertracks|--rendertracks)$ ]]
then
render=
fi
done
if [ "$rendertracks" ]
then
filemode='existing_directories'
fi
if [ "$render" ]
then
# filemode files is a superset of "existing directories"
# so it's OK to overwrite the filemode='existing_directories'
# from above
filetypes='wav|ogg|mp3'
filemode='files'
fi
;;
--profile|-p)
filemode='files'
;;
--samplerate|-s)
# these are the ones suggested for zyn
# if you think more are required,
# remove this comment and write a justification
params='44100 48000 96000 192000'
;;
--oversampling|-x)
params='1 2 4 8'
;;
*)
local action_found
# Is an action specified?
if [ "$cword" -gt 1 ]
then
local wrd
for wrd in "${COMP_WORDS[@]}"
do
# action named explicitly?
if _lmms_array_contains "$wrd" "${actions[@]}"
then
action_found=$wrd
break
# deprecated action name?
elif _lmms_array_contains "$wrd" "${actions_old[@]}"
then
action_found="$(_lmms_conv_old_action "$wrd")"
break
# no-action params found?
elif _lmms_array_contains "$wrd" "${pars_noaction[@]}"
then
action_found=none
break
fi
done
fi
if [[ $prev =~ -e|--help|-h|-version|-v ]]
then
# the -e flag (from --import) and help/version
# always mark the end of arguments
return
fi
if [[ "$action_found" =~ dump|none|^$ ]] && [[ $prev =~ \.mmpz? ]]
then
# mmp(z) mark the end of arguments for those actions
return
fi
local savefiletypes='mmpz|mmp'
local params_array
# find parameters/filetypes/dirtypes depending on actions
if ! [ "$action_found" ]
then
params_array=( "${actions[@]}" "${pars_global[@]}" "${pars_noaction[@]}")
filemode="existing_files"
filetypes="$savefiletypes"
elif [ "$action_found" == "none" ]
then
params_array=( "${pars_noaction[@]}" )
filemode="existing_files"
filetypes="$savefiletypes"
elif [ "$action_found" == "dump" ]
then
filemode="existing_files"
filetypes="mmpz"
elif [ "$action_found" == "upgrade" ]
then
if [ "$prev" == "upgrade" ]
then
filemode="existing_files"
filetypes="$savefiletypes"
elif [ "$prev2" == "upgrade" ]
then
filemode="files"
filetypes="$savefiletypes"
fi
elif [[ "$action_found" =~ render(tracks)? ]]
then
if [[ "$prev" =~ render(tracks)? ]]
then
filemode="existing_files"
filetypes="$savefiletypes"
else
params_array=( "${pars_render[@]}" )
fi
fi
# add params_array to params, but also check the history of comp words
local param
for param in "${params_array[@]}"
do
local do_append=1
for i in "${!COMP_WORDS[@]}"
do
if [ "$i" -ne 0 ] && [ "$i" -ne "$cword" ]
then
# disallow double long parameters
if [ "${COMP_WORDS[$i]}" == "$param" ]
then
do_append=
# disallow double short parameters
elif [ "--$(_lmms_long_param_of "${COMP_WORDS[$i]}")" == "$param" ]
then
do_append=
# --help or --version must be the first parameters
elif [ "$cword" -gt 1 ] && [[ $param =~ --help|--version ]]
then
do_append=
fi
fi
done
if [ "$do_append" ]
then
params+="$param "
fi
done
;;
esac
case $filemode in
# use completion routine provided by bash-completion
# to fill $COMPREPLY
existing_files)
_filedir "@($filetypes)"
;;
existing_directories)
_filedir -d
;;
files)
# non existing files complete like directories...
_filedir -d
# ...except for non-completing files with the right file type
if [ ${#COMPREPLY[@]} -eq 0 ]
then
if ! [[ "$cur" =~ /$ ]] && [ "$filetypes" ] && [[ "$cur" =~ \.($filetypes)$ ]]
then
# file ending fits, we seem to be done
COMPREPLY=( "$cur" )
fi
fi
;;
esac
if [ "$params" ]
then
# none of our parameters contain spaces, so deactivate shellcheck's warning
# shellcheck disable=SC2207
COMPREPLY+=( $(compgen -W "${params}" -- "${cur}") )
fi
}
complete -F _lmms lmms