1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
# SPDX-License-Identifier: GPL-3.0-or-later
# Copyright (C) 2025-2026 Benjamin Abendroth <braph93@gmx.de>
'''Code for creating the subcommand switch code in Bash'''
from collections import OrderedDict
from . import utils
from . import shell
from . import bash_patterns
from .str_utils import indent
def get_subcommand_path(commandline):
'''Return the path for `commandline`.'''
commandlines = commandline.get_parents(include_self=True)[1:]
prognames = ['root'] + [c.prog for c in commandlines]
return ':'.join(prognames)
def make_subcommand_switch_code(commandline):
'''
Generate Bash code that tracks and updates the current subcommand path.
This function walks through all known command lines (starting from the
given top-level `commandline`) and generates conditional Bash code that
appends the currently matched subcommand to the `cmd` variable. This
allows the generated completion script to keep track of the full
subcommand hierarchy as the user types.
While tracking, all variations of a subcommand are considered, including
defined aliases and any supported abbreviations. This ensures that the
resulting `cmd` variable always contains the canonical subcommand name,
regardless of how the user typed it.
'''
assert commandline.parent is None, \
"This function should only be used on top-level command lines"
cases = []
for cmdline in commandline.get_all_commandlines():
if not cmdline.get_subcommands():
continue
positional_num = cmdline.get_subcommands().get_positional_num()
command_path = get_subcommand_path(cmdline)
switch_code = _make_switch_case(cmdline)
cases.append(('%s|%d' % (command_path, positional_num), switch_code))
if not cases:
return None
r = 'case "$cmd|${#POSITIONALS[@]}" in\n'
for case in cases:
if '\n' not in case[1]:
r += ' %s) %s;;\n' % (shell.quote(case[0]), case[1])
else:
r += ' %s)\n%s;;\n' % (shell.quote(case[0]), indent(case[1], 4))
r += 'esac'
return r
def _make_switch_case(cmdline):
subcommand_aliases = OrderedDict()
for subcommand in cmdline.get_subcommands().subcommands:
aliases = utils.get_all_command_variations(subcommand)
aliases.remove(subcommand.prog)
if aliases:
subcommand_aliases[subcommand.prog] = aliases
# We have no aliases or abbreviations, just add $arg to $cmd
if not subcommand_aliases:
return 'cmd+=":$arg"'
r = 'case "$arg" in\n'
for command, aliases in subcommand_aliases.items():
r += ' %s) cmd+=":%s";;\n' % (bash_patterns.make_pattern(aliases), command)
r += ' *) cmd+=":$arg";;\n'
r += 'esac'
return r
|