Add bash_completion to swiftclient

This patch basically follows the bash completion
model that other OpenStack clients use. It creates
a new command to swiftclient called `bash_completion`.

The `bash_completion` command by default will print
all base flags and exsiting commands. If you pass
it a command, it'll print out all base flags and
any flags that command accepts. So as you type out
your swift command and auto-complete, only the current
available flags are offered to you.

This is used by the swift.bash_completion script to
allow swift commands to be bash completed.

To make it work, place the swift.bash_completion file
into /etc/bash_completion.d and source it:

  cp tools/swift.bash_completion /etc/bash_completion.d/swift
  source /etc/bash_completion.d/swift

Because swiftclient itself is creating this flag/command output
it should automatically add anything we add to the swiftclient
CLI.

Change-Id: I5609a19018269762b4640403daae5827bb9ad724
This commit is contained in:
Matthew Oliver 2018-06-29 11:07:00 +10:00 committed by John Dickinson
parent 25e23988b3
commit 45ed21c6c4
2 changed files with 245 additions and 99 deletions

View File

@ -51,7 +51,7 @@ except ImportError:
BASENAME = 'swift' BASENAME = 'swift'
commands = ('delete', 'download', 'list', 'post', 'copy', 'stat', 'upload', commands = ('delete', 'download', 'list', 'post', 'copy', 'stat', 'upload',
'capabilities', 'info', 'tempurl', 'auth') 'capabilities', 'info', 'tempurl', 'auth', 'bash_completion')
def immediate_exit(signum, frame): def immediate_exit(signum, frame):
@ -90,7 +90,7 @@ Optional arguments:
'''.strip("\n") '''.strip("\n")
def st_delete(parser, args, output_manager): def st_delete(parser, args, output_manager, return_parser=False):
parser.add_argument( parser.add_argument(
'-a', '--all', action='store_true', dest='yes_all', '-a', '--all', action='store_true', dest='yes_all',
default=False, help='Delete all containers and objects.') default=False, help='Delete all containers and objects.')
@ -114,6 +114,11 @@ def st_delete(parser, args, output_manager):
'--container-threads', type=int, '--container-threads', type=int,
default=10, help='Number of threads to use for deleting containers. ' default=10, help='Number of threads to use for deleting containers. '
'Its value must be a positive integer. Default is 10.') 'Its value must be a positive integer. Default is 10.')
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
args = args[1:] args = args[1:]
if (not args and not options['yes_all']) or (args and options['yes_all']): if (not args and not options['yes_all']) or (args and options['yes_all']):
@ -281,7 +286,7 @@ Optional arguments:
'''.strip("\n") '''.strip("\n")
def st_download(parser, args, output_manager): def st_download(parser, args, output_manager, return_parser=False):
parser.add_argument( parser.add_argument(
'-a', '--all', action='store_true', dest='yes_all', '-a', '--all', action='store_true', dest='yes_all',
default=False, help='Indicates that you really want to download ' default=False, help='Indicates that you really want to download '
@ -344,6 +349,11 @@ def st_download(parser, args, output_manager):
'to store the access and modified timestamp for the downloaded file. ' 'to store the access and modified timestamp for the downloaded file. '
'With this option, the header is ignored and the timestamps are ' 'With this option, the header is ignored and the timestamps are '
'created freshly.') 'created freshly.')
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
args = args[1:] args = args[1:]
if options['out_file'] == '-': if options['out_file'] == '-':
@ -494,7 +504,7 @@ Optional arguments:
'''.strip('\n') '''.strip('\n')
def st_list(parser, args, output_manager): def st_list(parser, args, output_manager, return_parser=False):
def _print_stats(options, stats, human): def _print_stats(options, stats, human):
total_count = total_bytes = 0 total_count = total_bytes = 0
@ -571,6 +581,11 @@ def st_list(parser, args, output_manager):
'-H', '--header', action='append', dest='header', '-H', '--header', action='append', dest='header',
default=[], default=[],
help='Adds a custom request header to use for listing.') help='Adds a custom request header to use for listing.')
# We return the parser to build up the bash_completion
if return_parser:
return parser
options, args = parse_args(parser, args) options, args = parse_args(parser, args)
args = args[1:] args = args[1:]
if options['delimiter'] and not args: if options['delimiter'] and not args:
@ -629,7 +644,7 @@ Optional arguments:
'''.strip('\n') '''.strip('\n')
def st_stat(parser, args, output_manager): def st_stat(parser, args, output_manager, return_parser=False):
parser.add_argument( parser.add_argument(
'--lh', dest='human', action='store_true', default=False, '--lh', dest='human', action='store_true', default=False,
help='Report sizes in human readable format similar to ls -lh.') help='Report sizes in human readable format similar to ls -lh.')
@ -638,6 +653,10 @@ def st_stat(parser, args, output_manager):
default=[], default=[],
help='Adds a custom request header to use for stat.') help='Adds a custom request header to use for stat.')
# We return the parser to build up the bash_completion
if return_parser:
return parser
options, args = parse_args(parser, args) options, args = parse_args(parser, args)
args = args[1:] args = args[1:]
@ -725,7 +744,7 @@ Optional arguments:
'''.strip('\n') '''.strip('\n')
def st_post(parser, args, output_manager): def st_post(parser, args, output_manager, return_parser=False):
parser.add_argument( parser.add_argument(
'-r', '--read-acl', dest='read_acl', help='Read ACL for containers. ' '-r', '--read-acl', dest='read_acl', help='Read ACL for containers. '
'Quick summary of ACL syntax: .r:*, .r:-.example.com, ' 'Quick summary of ACL syntax: .r:*, .r:-.example.com, '
@ -750,6 +769,11 @@ def st_post(parser, args, output_manager):
'This option may be repeated. ' 'This option may be repeated. '
'Example: -H "content-type:text/plain" ' 'Example: -H "content-type:text/plain" '
'-H "Content-Length: 4000"') '-H "Content-Length: 4000"')
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
args = args[1:] args = args[1:]
if (options['read_acl'] or options['write_acl'] or options['sync_to'] or if (options['read_acl'] or options['write_acl'] or options['sync_to'] or
@ -822,7 +846,7 @@ Optional arguments:
'''.strip('\n') '''.strip('\n')
def st_copy(parser, args, output_manager): def st_copy(parser, args, output_manager, return_parser=False):
parser.add_argument( parser.add_argument(
'-d', '--destination', help='The container and name of the ' '-d', '--destination', help='The container and name of the '
'destination object') 'destination object')
@ -839,6 +863,11 @@ def st_copy(parser, args, output_manager):
'This option may be repeated. ' 'This option may be repeated. '
'Example: -H "content-type:text/plain" ' 'Example: -H "content-type:text/plain" '
'-H "Content-Length: 4000"') '-H "Content-Length: 4000"')
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
args = args[1:] args = args[1:]
@ -948,7 +977,7 @@ Optional arguments:
'''.strip('\n') '''.strip('\n')
def st_upload(parser, args, output_manager): def st_upload(parser, args, output_manager, return_parser=False):
DEFAULT_STDIN_SEGMENT = 10 * 1024 * 1024 DEFAULT_STDIN_SEGMENT = 10 * 1024 * 1024
parser.add_argument( parser.add_argument(
@ -1006,6 +1035,11 @@ def st_upload(parser, args, output_manager):
parser.add_argument( parser.add_argument(
'--ignore-checksum', dest='checksum', default=True, '--ignore-checksum', dest='checksum', default=True,
action='store_false', help='Turn off checksum validation for uploads.') action='store_false', help='Turn off checksum validation for uploads.')
# We return the parser to build up the bash_completion
if return_parser:
return parser
options, args = parse_args(parser, args) options, args = parse_args(parser, args)
args = args[1:] args = args[1:]
if len(args) < 2: if len(args) < 2:
@ -1185,7 +1219,7 @@ Optional arguments:
st_info_help = st_capabilities_help st_info_help = st_capabilities_help
def st_capabilities(parser, args, output_manager): def st_capabilities(parser, args, output_manager, return_parser=False):
def _print_compo_cap(name, capabilities): def _print_compo_cap(name, capabilities):
for feature, options in sorted(capabilities.items(), for feature, options in sorted(capabilities.items(),
key=lambda x: x[0]): key=lambda x: x[0]):
@ -1198,6 +1232,11 @@ def st_capabilities(parser, args, output_manager):
parser.add_argument('--json', action='store_true', parser.add_argument('--json', action='store_true',
help='print capability information in json') help='print capability information in json')
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
if args and len(args) > 2: if args and len(args) > 2:
output_manager.error('Usage: %s capabilities %s\n%s', output_manager.error('Usage: %s capabilities %s\n%s',
@ -1246,7 +1285,12 @@ Display auth related authentication variables in shell friendly format.
'''.strip('\n') '''.strip('\n')
def st_auth(parser, args, thread_manager): def st_auth(parser, args, thread_manager, return_parser=False):
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
if options['verbose'] > 1: if options['verbose'] > 1:
if options['auth_version'] in ('1', '1.0'): if options['auth_version'] in ('1', '1.0'):
@ -1330,7 +1374,7 @@ Optional arguments:
'''.strip('\n') '''.strip('\n')
def st_tempurl(parser, args, thread_manager): def st_tempurl(parser, args, thread_manager, return_parser=False):
parser.add_argument( parser.add_argument(
'--absolute', action='store_true', '--absolute', action='store_true',
dest='absolute_expiry', default=False, dest='absolute_expiry', default=False,
@ -1357,6 +1401,10 @@ def st_tempurl(parser, args, thread_manager):
"given ip or ip range."), "given ip or ip range."),
) )
# We return the parser to build up the bash_completion
if return_parser:
return parser
(options, args) = parse_args(parser, args) (options, args) = parse_args(parser, args)
args = args[1:] args = args[1:]
if len(args) < 4: if len(args) < 4:
@ -1388,6 +1436,65 @@ def st_tempurl(parser, args, thread_manager):
thread_manager.print_msg(url) thread_manager.print_msg(url)
st_bash_completion_help = '''Retrieve command specific flags used by bash_completion.
Optional positional arguments:
<command> Swift client command to filter the flags by.
'''.strip('\n')
st_bash_completion_options = '''[command]
'''
def st_bash_completion(parser, args, thread_manager, return_parser=False):
if return_parser:
return parser
global commands
com = args[1] if len(args) > 1 else None
if com:
if com in commands:
fn_commands = ["st_%s" % com]
else:
print("")
return
else:
fn_commands = [fn for fn in globals().keys()
if fn.startswith('st_') and not fn.endswith('_options')
and not fn.endswith('_help')]
subparsers = parser.add_subparsers()
subcommands = {}
if not com:
subcommands['base'] = parser
for command in fn_commands:
cmd = command[3:]
if com:
subparser = subparsers.add_parser(
cmd, help=globals()['%s_help' % command])
add_default_args(subparser)
subparser = globals()[command](
subparser, args, thread_manager, True)
subcommands[cmd] = subparser
else:
subcommands[cmd] = None
cmds = set()
opts = set()
for sc_str, sc in list(subcommands.items()):
cmds.add(sc_str)
if sc:
for option in sc._optionals._option_string_actions:
opts.add(option)
for cmd_to_remove in (com, 'bash_completion', 'base'):
if cmd_to_remove in cmds:
cmds.remove(cmd_to_remove)
print(' '.join(cmds | opts))
class HelpFormatter(argparse.HelpFormatter): class HelpFormatter(argparse.HelpFormatter):
def _format_action_invocation(self, action): def _format_action_invocation(self, action):
if not action.option_strings: if not action.option_strings:
@ -1508,94 +1615,7 @@ adding "-V 2" is necessary for this.'''.strip('\n'))
return options, args return options, args
def main(arguments=None): def add_default_args(parser):
argv = sys_argv if arguments is None else arguments
argv = [a if isinstance(a, text_type) else a.decode('utf-8') for a in argv]
version = client_version
parser = argparse.ArgumentParser(
add_help=False, formatter_class=HelpFormatter, usage='''
%(prog)s [--version] [--help] [--os-help] [--snet] [--verbose]
[--debug] [--info] [--quiet] [--auth <auth_url>]
[--auth-version <auth_version> |
--os-identity-api-version <auth_version> ]
[--user <username>]
[--key <api_key>] [--retries <num_retries>]
[--os-username <auth-user-name>]
[--os-password <auth-password>]
[--os-user-id <auth-user-id>]
[--os-user-domain-id <auth-user-domain-id>]
[--os-user-domain-name <auth-user-domain-name>]
[--os-tenant-id <auth-tenant-id>]
[--os-tenant-name <auth-tenant-name>]
[--os-project-id <auth-project-id>]
[--os-project-name <auth-project-name>]
[--os-project-domain-id <auth-project-domain-id>]
[--os-project-domain-name <auth-project-domain-name>]
[--os-auth-url <auth-url>]
[--os-auth-token <auth-token>]
[--os-storage-url <storage-url>]
[--os-region-name <region-name>]
[--os-service-type <service-type>]
[--os-endpoint-type <endpoint-type>]
[--os-cacert <ca-certificate>]
[--insecure]
[--os-cert <client-certificate-file>]
[--os-key <client-certificate-key-file>]
[--no-ssl-compression]
[--force-auth-retry]
[--prompt]
<subcommand> [--help] [<subcommand options>]
Command-line interface to the OpenStack Swift API.
Positional arguments:
<subcommand>
delete Delete a container or objects within a container.
download Download objects from containers.
list Lists the containers for the account or the objects
for a container.
post Updates meta information for the account, container,
or object; creates containers if not present.
copy Copies object, optionally adds meta
stat Displays information for the account, container,
or object.
upload Uploads files or directories to the given container.
capabilities List cluster capabilities.
tempurl Create a temporary URL.
auth Display auth related environment variables.
Examples:
%(prog)s download --help
%(prog)s -A https://api.example.com/v1.0 \\
-U user -K api_key stat -v
%(prog)s --os-auth-url https://api.example.com/v2.0 \\
--os-tenant-name tenant \\
--os-username user --os-password password list
%(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
--os-project-name project1 --os-project-domain-name domain1 \\
--os-username user --os-user-domain-name domain1 \\
--os-password password list
%(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
--os-project-id 0123456789abcdef0123456789abcdef \\
--os-user-id abcdef0123456789abcdef0123456789 \\
--os-password password list
%(prog)s --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \\
--os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \\
list
%(prog)s list --lh
'''.strip('\n'))
parser.add_argument('--version', action='version',
version='python-swiftclient %s' % version)
parser.add_argument('-h', '--help', action='store_true')
default_auth_version = '1.0' default_auth_version = '1.0'
for k in ('ST_AUTH_VERSION', 'OS_AUTH_VERSION', 'OS_IDENTITY_API_VERSION'): for k in ('ST_AUTH_VERSION', 'OS_AUTH_VERSION', 'OS_IDENTITY_API_VERSION'):
try: try:
@ -1808,6 +1828,100 @@ Examples:
default=environ.get('OS_KEY'), default=environ.get('OS_KEY'),
help='Specify a client certificate key file (for ' help='Specify a client certificate key file (for '
'client auth). Defaults to env[OS_KEY].') 'client auth). Defaults to env[OS_KEY].')
def main(arguments=None):
argv = sys_argv if arguments is None else arguments
argv = [a if isinstance(a, text_type) else a.decode('utf-8') for a in argv]
parser = argparse.ArgumentParser(
add_help=False, formatter_class=HelpFormatter, usage='''
%(prog)s [--version] [--help] [--os-help] [--snet] [--verbose]
[--debug] [--info] [--quiet] [--auth <auth_url>]
[--auth-version <auth_version> |
--os-identity-api-version <auth_version> ]
[--user <username>]
[--key <api_key>] [--retries <num_retries>]
[--os-username <auth-user-name>]
[--os-password <auth-password>]
[--os-user-id <auth-user-id>]
[--os-user-domain-id <auth-user-domain-id>]
[--os-user-domain-name <auth-user-domain-name>]
[--os-tenant-id <auth-tenant-id>]
[--os-tenant-name <auth-tenant-name>]
[--os-project-id <auth-project-id>]
[--os-project-name <auth-project-name>]
[--os-project-domain-id <auth-project-domain-id>]
[--os-project-domain-name <auth-project-domain-name>]
[--os-auth-url <auth-url>]
[--os-auth-token <auth-token>]
[--os-storage-url <storage-url>]
[--os-region-name <region-name>]
[--os-service-type <service-type>]
[--os-endpoint-type <endpoint-type>]
[--os-cacert <ca-certificate>]
[--insecure]
[--os-cert <client-certificate-file>]
[--os-key <client-certificate-key-file>]
[--no-ssl-compression]
[--force-auth-retry]
<subcommand> [--help] [<subcommand options>]
Command-line interface to the OpenStack Swift API.
Positional arguments:
<subcommand>
delete Delete a container or objects within a container.
download Download objects from containers.
list Lists the containers for the account or the objects
for a container.
post Updates meta information for the account, container,
or object; creates containers if not present.
copy Copies object, optionally adds meta
stat Displays information for the account, container,
or object.
upload Uploads files or directories to the given container.
capabilities List cluster capabilities.
tempurl Create a temporary URL.
auth Display auth related environment variables.
bash_completion Outputs option and flag cli data ready for
bash_completion.
Examples:
%(prog)s download --help
%(prog)s -A https://api.example.com/v1.0 \\
-U user -K api_key stat -v
%(prog)s --os-auth-url https://api.example.com/v2.0 \\
--os-tenant-name tenant \\
--os-username user --os-password password list
%(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
--os-project-name project1 --os-project-domain-name domain1 \\
--os-username user --os-user-domain-name domain1 \\
--os-password password list
%(prog)s --os-auth-url https://api.example.com/v3 --auth-version 3\\
--os-project-id 0123456789abcdef0123456789abcdef \\
--os-user-id abcdef0123456789abcdef0123456789 \\
--os-password password list
%(prog)s --os-auth-token 6ee5eb33efad4e45ab46806eac010566 \\
--os-storage-url https://10.1.5.2:8080/v1/AUTH_ced809b6a4baea7aeab61a \\
list
%(prog)s list --lh
'''.strip('\n'))
version = client_version
parser.add_argument('--version', action='version',
version='python-swiftclient %s' % version)
parser.add_argument('-h', '--help', action='store_true')
add_default_args(parser)
options, args = parse_args(parser, argv[1:], enforce_requires=False) options, args = parse_args(parser, argv[1:], enforce_requires=False)
if options['help'] or options['os_help']: if options['help'] or options['os_help']:

View File

@ -0,0 +1,32 @@
declare -a _swift_opts # lazy init
_swift_get_current_opt()
{
local opt
for opt in ${_swift_opts[@]} ; do
if [[ $(echo ${COMP_WORDS[*]} |grep -c " $opt\$") > 0 ]] || [[ $(echo ${COMP_WORDS[*]} |grep -c " $opt ") > 0 ]] ; then
echo $opt
return 0
fi
done
echo ""
return 0
}
_swift()
{
local opt cur prev sflags
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
if [ "x$_swift_opts" == "x" ] ; then
_swift_opts=(`swift bash_completion "$sbc" | sed -e "s/-[-A-Za-z0-9_]*//g" -e "s/ */ /g"`)
fi
opt="$(_swift_get_current_opt)"
COMPREPLY=($(compgen -W "$(swift bash_completion $opt)" -- ${cur}))
return 0
}
complete -F _swift swift