mirror of
https://github.com/beetbox/beets.git
synced 2025-12-21 16:13:38 +01:00
Remove home-made bash completion infrastructure
In favor of Click's built-in support. The script is now generated using `_BEET_COMPLETE=source beet` instead of `beet completion`. We should strongly consider restoring the `beet completion` command just for backwards compatibility. All of this needs to be heavily publicized in the changelog.
This commit is contained in:
parent
944808016e
commit
b762eed171
6 changed files with 4 additions and 519 deletions
|
|
@ -29,8 +29,5 @@ global-exclude .DS_Store
|
|||
# Include default config
|
||||
include beets/config_default.yaml
|
||||
|
||||
# Shell completion template
|
||||
include beets/ui/completion_base.sh
|
||||
|
||||
# Include extra bits
|
||||
recursive-include extra *
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ interface.
|
|||
from __future__ import division, absolute_import, print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
from platform import python_version
|
||||
from collections import namedtuple, Counter
|
||||
from itertools import chain
|
||||
|
|
@ -39,7 +38,6 @@ from beets.util import syspath, normpath, ancestry, displayable_path
|
|||
from beets import library
|
||||
from beets import config
|
||||
from beets import logging
|
||||
from beets.util.confit import _package_path
|
||||
import six
|
||||
|
||||
VARIOUS_ARTISTS = u'Various Artists'
|
||||
|
|
@ -1660,111 +1658,3 @@ config_cmd.parser.add_option(
|
|||
)
|
||||
config_cmd.func = config_func
|
||||
default_commands.append(config_cmd)
|
||||
|
||||
|
||||
# completion: print completion script
|
||||
|
||||
def print_completion(*args):
|
||||
for line in completion_script(default_commands + plugins.commands()):
|
||||
print_(line, end=u'')
|
||||
if not any(map(os.path.isfile, BASH_COMPLETION_PATHS)):
|
||||
log.warning(u'Warning: Unable to find the bash-completion package. '
|
||||
u'Command line completion might not work.')
|
||||
|
||||
BASH_COMPLETION_PATHS = map(syspath, [
|
||||
u'/etc/bash_completion',
|
||||
u'/usr/share/bash-completion/bash_completion',
|
||||
u'/usr/local/share/bash-completion/bash_completion',
|
||||
# SmartOS
|
||||
u'/opt/local/share/bash-completion/bash_completion',
|
||||
# Homebrew (before bash-completion2)
|
||||
u'/usr/local/etc/bash_completion',
|
||||
])
|
||||
|
||||
|
||||
def completion_script(commands):
|
||||
"""Yield the full completion shell script as strings.
|
||||
|
||||
``commands`` is alist of ``ui.Subcommand`` instances to generate
|
||||
completion data for.
|
||||
"""
|
||||
base_script = os.path.join(_package_path('beets.ui'), 'completion_base.sh')
|
||||
with open(base_script, 'r') as base_script:
|
||||
yield util.text_string(base_script.read())
|
||||
|
||||
options = {}
|
||||
aliases = {}
|
||||
command_names = []
|
||||
|
||||
# Collect subcommands
|
||||
for cmd in commands:
|
||||
name = cmd.name
|
||||
command_names.append(name)
|
||||
|
||||
for alias in cmd.aliases:
|
||||
if re.match(r'^\w+$', alias):
|
||||
aliases[alias] = name
|
||||
|
||||
options[name] = {u'flags': [], u'opts': []}
|
||||
for opts in cmd.parser._get_all_options()[1:]:
|
||||
if opts.action in ('store_true', 'store_false'):
|
||||
option_type = u'flags'
|
||||
else:
|
||||
option_type = u'opts'
|
||||
|
||||
options[name][option_type].extend(
|
||||
opts._short_opts + opts._long_opts
|
||||
)
|
||||
|
||||
# Add global options
|
||||
options['_global'] = {
|
||||
u'flags': [u'-v', u'--verbose'],
|
||||
u'opts':
|
||||
u'-l --library -c --config -d --directory -h --help'.split(u' ')
|
||||
}
|
||||
|
||||
# Add flags common to all commands
|
||||
options['_common'] = {
|
||||
u'flags': [u'-h', u'--help']
|
||||
}
|
||||
|
||||
# Start generating the script
|
||||
yield u"_beet() {\n"
|
||||
|
||||
# Command names
|
||||
yield u" local commands='%s'\n" % ' '.join(command_names)
|
||||
yield u"\n"
|
||||
|
||||
# Command aliases
|
||||
yield u" local aliases='%s'\n" % ' '.join(aliases.keys())
|
||||
for alias, cmd in aliases.items():
|
||||
yield u" local alias__%s=%s\n" % (alias, cmd)
|
||||
yield u'\n'
|
||||
|
||||
# Fields
|
||||
yield u" fields='%s'\n" % ' '.join(
|
||||
set(
|
||||
list(library.Item._fields.keys()) +
|
||||
list(library.Album._fields.keys())
|
||||
)
|
||||
)
|
||||
|
||||
# Command options
|
||||
for cmd, opts in options.items():
|
||||
for option_type, option_list in opts.items():
|
||||
if option_list:
|
||||
option_list = u' '.join(option_list)
|
||||
yield u" local %s__%s='%s'\n" % (
|
||||
option_type, cmd, option_list)
|
||||
|
||||
yield u' _beet_dispatch\n'
|
||||
yield u'}\n'
|
||||
|
||||
|
||||
completion_cmd = ui.Subcommand(
|
||||
'completion',
|
||||
help=u'print shell script that provides command line completion'
|
||||
)
|
||||
completion_cmd.func = print_completion
|
||||
completion_cmd.hide = True
|
||||
default_commands.append(completion_cmd)
|
||||
|
|
|
|||
|
|
@ -1,162 +0,0 @@
|
|||
# This file is part of beets.
|
||||
# Copyright (c) 2014, Thomas Scholtes.
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining
|
||||
# a copy of this software and associated documentation files (the
|
||||
# "Software"), to deal in the Software without restriction, including
|
||||
# without limitation the rights to use, copy, modify, merge, publish,
|
||||
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||
# permit persons to whom the Software is furnished to do so, subject to
|
||||
# the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
|
||||
|
||||
|
||||
# Completion for the `beet` command
|
||||
# =================================
|
||||
#
|
||||
# Load this script to complete beets subcommands, options, and
|
||||
# queries.
|
||||
#
|
||||
# If a beets command is found on the command line it completes filenames and
|
||||
# the subcommand's options. Otherwise it will complete global options and
|
||||
# subcommands. If the previous option on the command line expects an argument,
|
||||
# it also completes filenames or directories. Options are only
|
||||
# completed if '-' has already been typed on the command line.
|
||||
#
|
||||
# Note that completion of plugin commands only works for those plugins
|
||||
# that were enabled when running `beet completion`. It does not check
|
||||
# plugins dynamically
|
||||
#
|
||||
# Currently, only Bash 3.2 and newer is supported and the
|
||||
# `bash-completion` package is requied.
|
||||
#
|
||||
# TODO
|
||||
# ----
|
||||
#
|
||||
# * There are some issues with arguments that are quoted on the command line.
|
||||
#
|
||||
# * Complete arguments for the `--format` option by expanding field variables.
|
||||
#
|
||||
# beet ls -f "$tit[TAB]
|
||||
# beet ls -f "$title
|
||||
#
|
||||
# * Support long options with `=`, e.g. `--config=file`. Debian's bash
|
||||
# completion package can handle this.
|
||||
#
|
||||
|
||||
|
||||
# Determines the beets subcommand and dispatches the completion
|
||||
# accordingly.
|
||||
_beet_dispatch() {
|
||||
local cur prev cmd=
|
||||
|
||||
COMPREPLY=()
|
||||
_get_comp_words_by_ref -n : cur prev
|
||||
|
||||
# Look for the beets subcommand
|
||||
local arg
|
||||
for (( i=1; i < COMP_CWORD; i++ )); do
|
||||
arg="${COMP_WORDS[i]}"
|
||||
if _list_include_item "${opts___global}" $arg; then
|
||||
((i++))
|
||||
elif [[ "$arg" != -* ]]; then
|
||||
cmd="$arg"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Replace command shortcuts
|
||||
if [[ -n $cmd ]] && _list_include_item "$aliases" "$cmd"; then
|
||||
eval "cmd=\$alias__$cmd"
|
||||
fi
|
||||
|
||||
case $cmd in
|
||||
help)
|
||||
COMPREPLY+=( $(compgen -W "$commands" -- $cur) )
|
||||
;;
|
||||
list|remove|move|update|write|stats)
|
||||
_beet_complete_query
|
||||
;;
|
||||
"")
|
||||
_beet_complete_global
|
||||
;;
|
||||
*)
|
||||
_beet_complete
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
# Adds option and file completion to COMPREPLY for the subcommand $cmd
|
||||
_beet_complete() {
|
||||
if [[ $cur == -* ]]; then
|
||||
local opts flags completions
|
||||
eval "opts=\$opts__$cmd"
|
||||
eval "flags=\$flags__$cmd"
|
||||
completions="${flags___common} ${opts} ${flags}"
|
||||
COMPREPLY+=( $(compgen -W "$completions" -- $cur) )
|
||||
else
|
||||
_filedir
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# Add global options and subcommands to the completion
|
||||
_beet_complete_global() {
|
||||
case $prev in
|
||||
-h|--help)
|
||||
# Complete commands
|
||||
COMPREPLY+=( $(compgen -W "$commands" -- $cur) )
|
||||
return
|
||||
;;
|
||||
-l|--library|-c|--config)
|
||||
# Filename completion
|
||||
_filedir
|
||||
return
|
||||
;;
|
||||
-d|--directory)
|
||||
# Directory completion
|
||||
_filedir -d
|
||||
return
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $cur == -* ]]; then
|
||||
local completions="$opts___global $flags___global"
|
||||
COMPREPLY+=( $(compgen -W "$completions" -- $cur) )
|
||||
elif [[ -n $cur ]] && _list_include_item "$aliases" "$cur"; then
|
||||
local cmd
|
||||
eval "cmd=\$alias__$cur"
|
||||
COMPREPLY+=( "$cmd" )
|
||||
else
|
||||
COMPREPLY+=( $(compgen -W "$commands" -- $cur) )
|
||||
fi
|
||||
}
|
||||
|
||||
_beet_complete_query() {
|
||||
local opts
|
||||
eval "opts=\$opts__$cmd"
|
||||
|
||||
if [[ $cur == -* ]] || _list_include_item "$opts" "$prev"; then
|
||||
_beet_complete
|
||||
elif [[ $cur != \'* && $cur != \"* &&
|
||||
$cur != *:* ]]; then
|
||||
# Do not complete quoted queries or those who already have a field
|
||||
# set.
|
||||
compopt -o nospace
|
||||
COMPREPLY+=( $(compgen -S : -W "$fields" -- $cur) )
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
# Returns true if the space separated list $1 includes $2
|
||||
_list_include_item() {
|
||||
[[ " $1 " == *[[:space:]]$2[[:space:]]* ]]
|
||||
}
|
||||
|
||||
# This is where beets dynamically adds the _beet function. This
|
||||
# function sets the variables $flags, $opts, $commands, and $aliases.
|
||||
complete -o filenames -F _beet beet
|
||||
|
|
@ -404,10 +404,10 @@ Beets includes support for shell command completion. The command ``beet
|
|||
completion`` prints out a `bash`_ 3.2 script; to enable completion put a line
|
||||
like this into your ``.bashrc`` or similar file::
|
||||
|
||||
eval "$(beet completion)"
|
||||
eval "$(_BEET_COMPLETE=source beet)"
|
||||
|
||||
Or, to avoid slowing down your shell startup time, you can pipe the ``beet
|
||||
completion`` output to a file and source that instead.
|
||||
Or, to avoid slowing down your shell startup time, you can pipe the
|
||||
``_BEET_COMPLETE=source beet`` output to a file and source that instead.
|
||||
|
||||
You will also need to source the `bash-completion`_ script, which is probably
|
||||
available via your package manager. On OS X, you can install it via Homebrew
|
||||
|
|
@ -429,10 +429,6 @@ accepts a query, the script will also complete field names. ::
|
|||
(Don't worry about the slash in front of the colon: this is a escape
|
||||
sequence for the shell and won't be seen by beets.)
|
||||
|
||||
Completion of plugin commands only works for those plugins
|
||||
that were enabled when running ``beet completion``. If you add a plugin
|
||||
later on you will want to re-generate the script.
|
||||
|
||||
zsh
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,185 +0,0 @@
|
|||
# Function stub
|
||||
compopt() { return 0; }
|
||||
|
||||
initcli() {
|
||||
COMP_WORDS=( "beet" "$@" )
|
||||
let COMP_CWORD=${#COMP_WORDS[@]}-1
|
||||
COMP_LINE="${COMP_WORDS[@]}"
|
||||
let COMP_POINT=${#COMP_LINE}
|
||||
_beet
|
||||
}
|
||||
|
||||
completes() {
|
||||
for word in "$@"; do
|
||||
[[ " ${COMPREPLY[@]} " == *[[:space:]]$word[[:space:]]* ]] || return 1
|
||||
done
|
||||
}
|
||||
|
||||
COMMANDS='fields import list update remove
|
||||
stats version modify move write
|
||||
help'
|
||||
|
||||
HELP_OPTS='-h --help'
|
||||
|
||||
|
||||
test_commands() {
|
||||
initcli '' &&
|
||||
completes $COMMANDS &&
|
||||
|
||||
initcli -v '' &&
|
||||
completes $COMMANDS &&
|
||||
|
||||
initcli -l help '' &&
|
||||
completes $COMMANDS &&
|
||||
|
||||
initcli -d list '' &&
|
||||
completes $COMMANDS &&
|
||||
|
||||
initcli -h '' &&
|
||||
completes $COMMANDS &&
|
||||
true
|
||||
}
|
||||
|
||||
test_command_aliases() {
|
||||
initcli ls &&
|
||||
completes list &&
|
||||
|
||||
initcli l &&
|
||||
! completes ls &&
|
||||
|
||||
initcli im &&
|
||||
completes import &&
|
||||
true
|
||||
}
|
||||
|
||||
test_global_opts() {
|
||||
initcli - &&
|
||||
completes \
|
||||
-l --library \
|
||||
-d --directory \
|
||||
-h --help \
|
||||
-c --config \
|
||||
-v --verbose &&
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
test_global_file_opts() {
|
||||
# FIXME somehow file completion only works when the completion
|
||||
# function is called by the shell completion utilities. So we can't
|
||||
# test it here
|
||||
initcli --library '' &&
|
||||
completes $(compgen -d) &&
|
||||
|
||||
initcli -l '' &&
|
||||
completes $(compgen -d) &&
|
||||
|
||||
initcli --config '' &&
|
||||
completes $(compgen -d) &&
|
||||
|
||||
initcli -c '' &&
|
||||
completes $(compgen -d) &&
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
test_global_dir_opts() {
|
||||
initcli --directory '' &&
|
||||
completes $(compgen -d) &&
|
||||
|
||||
initcli -d '' &&
|
||||
completes $(compgen -d) &&
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
test_fields_command() {
|
||||
initcli fields - &&
|
||||
completes -h --help &&
|
||||
|
||||
initcli fields '' &&
|
||||
completes $(compgen -d) &&
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
test_import_files() {
|
||||
initcli import '' &&
|
||||
completes $(compgen -d) &&
|
||||
|
||||
initcli import --copy -P '' &&
|
||||
completes $(compgen -d) &&
|
||||
|
||||
initcli import --log '' &&
|
||||
completes $(compgen -d) &&
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
test_import_options() {
|
||||
initcli imp -
|
||||
completes \
|
||||
-h --help \
|
||||
-c --copy -C --nocopy \
|
||||
-w --write -W --nowrite \
|
||||
-a --autotag -A --noautotag \
|
||||
-p --resume -P --noresume \
|
||||
-l --log --flat
|
||||
}
|
||||
|
||||
|
||||
test_list_options() {
|
||||
initcli list -
|
||||
completes \
|
||||
-h --help \
|
||||
-a --album \
|
||||
-p --path
|
||||
}
|
||||
|
||||
test_list_query() {
|
||||
initcli list 'x' &&
|
||||
[[ -z "${COMPREPLY[@]}" ]] &&
|
||||
|
||||
initcli list 'art' &&
|
||||
completes \
|
||||
'artist:' \
|
||||
'artpath:' &&
|
||||
|
||||
initcli list 'artits:x' &&
|
||||
[[ -z "${COMPREPLY[@]}" ]] &&
|
||||
true
|
||||
}
|
||||
|
||||
test_help_command() {
|
||||
initcli help '' &&
|
||||
completes $COMMANDS &&
|
||||
true
|
||||
}
|
||||
|
||||
test_plugin_command() {
|
||||
initcli te &&
|
||||
completes test &&
|
||||
|
||||
initcli test - &&
|
||||
completes -o --option &&
|
||||
true
|
||||
}
|
||||
|
||||
run_tests() {
|
||||
local tests=$(set | \
|
||||
grep --extended-regexp --only-matching '^test_[a-zA-Z_]* \(\) $' |\
|
||||
grep --extended-regexp --only-matching '[a-zA-Z_]*'
|
||||
)
|
||||
local fail=0
|
||||
|
||||
if [[ -n $@ ]]; then
|
||||
tests="$@"
|
||||
fi
|
||||
|
||||
for t in $tests; do
|
||||
$t || { fail=1 && echo "$t failed" >&2; }
|
||||
done
|
||||
return $fail
|
||||
}
|
||||
|
||||
run_tests "$@" && echo "completion tests passed"
|
||||
|
|
@ -20,7 +20,6 @@ from __future__ import division, absolute_import, print_function
|
|||
import os
|
||||
import shutil
|
||||
import re
|
||||
import subprocess
|
||||
import platform
|
||||
from copy import deepcopy
|
||||
import six
|
||||
|
|
@ -28,7 +27,7 @@ import six
|
|||
from mock import patch, Mock
|
||||
from test import _common
|
||||
from test._common import unittest
|
||||
from test.helper import capture_stdout, has_program, TestHelper, control_stdin
|
||||
from test.helper import capture_stdout, TestHelper, control_stdin
|
||||
|
||||
from beets import library
|
||||
from beets import ui
|
||||
|
|
@ -1110,56 +1109,6 @@ class PluginTest(_common.TestCase, TestHelper):
|
|||
self.run_command('test', lib=None)
|
||||
|
||||
|
||||
@_common.slow_test()
|
||||
class CompletionTest(_common.TestCase, TestHelper):
|
||||
def test_completion(self):
|
||||
# Load plugin commands
|
||||
config['pluginpath'] = [_common.PLUGINPATH]
|
||||
config['plugins'] = ['test']
|
||||
|
||||
# Do not load any other bash completion scripts on the system.
|
||||
env = dict(os.environ)
|
||||
env['BASH_COMPLETION_DIR'] = os.devnull
|
||||
env['BASH_COMPLETION_COMPAT_DIR'] = os.devnull
|
||||
|
||||
# Open a `bash` process to run the tests in. We'll pipe in bash
|
||||
# commands via stdin.
|
||||
cmd = os.environ.get('BEETS_TEST_SHELL', '/bin/bash --norc').split()
|
||||
if not has_program(cmd[0]):
|
||||
self.skipTest(u'bash not available')
|
||||
tester = subprocess.Popen(cmd, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, env=env)
|
||||
|
||||
# Load bash_completion library.
|
||||
for path in commands.BASH_COMPLETION_PATHS:
|
||||
if os.path.exists(util.syspath(path)):
|
||||
bash_completion = path
|
||||
break
|
||||
else:
|
||||
self.skipTest(u'bash-completion script not found')
|
||||
try:
|
||||
with open(util.syspath(bash_completion), 'rb') as f:
|
||||
tester.stdin.writelines(f)
|
||||
except IOError:
|
||||
self.skipTest(u'could not read bash-completion script')
|
||||
|
||||
# Load completion script.
|
||||
self.io.install()
|
||||
self.run_command('completion', lib=None)
|
||||
completion_script = self.io.getoutput().encode('utf-8')
|
||||
self.io.restore()
|
||||
tester.stdin.writelines(completion_script.splitlines(True))
|
||||
|
||||
# Load test suite.
|
||||
test_script_name = os.path.join(_common.RSRC, b'test_completion.sh')
|
||||
with open(test_script_name, 'rb') as test_script_file:
|
||||
tester.stdin.writelines(test_script_file)
|
||||
out, err = tester.communicate()
|
||||
if tester.returncode != 0 or out != b'completion tests passed\n':
|
||||
print(out.decode('utf-8'))
|
||||
self.fail(u'test/test_completion.sh did not execute properly')
|
||||
|
||||
|
||||
class CommonOptionsParserCliTest(unittest.TestCase, TestHelper):
|
||||
"""Test CommonOptionsParser and formatting LibModel formatting on 'list'
|
||||
command.
|
||||
|
|
|
|||
Loading…
Reference in a new issue