Skip to content

Commit

Permalink
app: support alias commands
Browse files Browse the repository at this point in the history
Replace west commands with alias set in configuration files

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
  • Loading branch information
pdgendt committed Sep 4, 2024
1 parent 0612a6d commit 8fdcb3b
Showing 1 changed file with 73 additions and 6 deletions.
79 changes: 73 additions & 6 deletions src/west/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import os
from pathlib import Path, PurePath
import platform
import shlex
import shutil
import signal
import sys
Expand Down Expand Up @@ -183,6 +184,7 @@ def __init__(self):
self.mle = None # saved exception if load_manifest() fails
self.builtins = {} # command name -> WestCommand instance
self.extensions = {} # extension command name -> spec
self.aliases = {} # alias -> WestCommand instance
self.builtin_groups = OrderedDict() # group name -> WestCommand list
self.extension_groups = OrderedDict() # project path -> ext spec list
self.west_parser = None # a WestArgumentParser
Expand Down Expand Up @@ -243,6 +245,7 @@ def run(self, argv):
# Set self.manifest and self.extensions.
self.load_manifest()
self.load_extension_specs()
self.load_aliases()

# Set up initial argument parsers. This requires knowing
# self.extensions, so it can't happen before now.
Expand Down Expand Up @@ -402,6 +405,14 @@ def load_extension_specs(self):

self.extension_groups[path] = filtered

def load_aliases(self):
if not self.config:
return

self.aliases = {
k[6:]: Alias(k[6:], v) for k, v in self.config.items() if k.startswith('alias.')
}

def handle_extension_command_error(self, ece):
if self.cmd is not None:
msg = f"extension command \"{self.cmd.name}\" couldn't be run"
Expand All @@ -424,9 +435,11 @@ def setup_parsers(self):
# Set up and install command-line argument parsers.

west_parser, subparser_gen = self.make_parsers()
command_names = set()

# Add sub-parsers for the built-in commands.
for command in self.builtins.values():
for name, command in self.builtins.items():
command_names.add(name)
command.add_parser(subparser_gen)

# Add stub parsers for extensions.
Expand All @@ -436,10 +449,18 @@ def setup_parsers(self):
# extension's code, which we won't do unless parse_known_args()
# says to run that extension.
if self.extensions:
for path, specs in self.extension_groups.items():
for specs in self.extension_groups.values():
for spec in specs:
command_names.add(spec.name)
subparser_gen.add_parser(spec.name, add_help=False)

# Add aliases, but skip aliases that shadow other commands
# The help parser requires unique commands to be added
if self.aliases:
for name, alias in self.aliases.items():
if name not in command_names:
alias.add_parser(subparser_gen)

# Save the instance state.
self.west_parser = west_parser
self.subparser_gen = subparser_gen
Expand Down Expand Up @@ -491,6 +512,24 @@ def run_command(self, argv, early_args):
# If we're running an extension, instantiate it from its
# spec and re-parse arguments before running.

if not early_args.help and early_args.command_name != "help":
# Recursively replace alias command(s) if set
aliases = self.aliases.copy()
while early_args.command_name in aliases:
# Make sure we don't end up in an infinite loop
alias = aliases.pop(early_args.command_name)

self.queued_io.append(lambda cmd, alias=alias: cmd.dbg(
f'Replacing alias {alias.name} with {alias.args}'
))

# Find the occurrence of the command name and skip any other early args like -v
for i, arg in enumerate(argv):
if arg == early_args.command_name:
argv = argv[:i] + alias.args + argv[i + 1:]
break
early_args = early_args._replace(command_name=alias.args[0])

self.handle_early_arg_errors(early_args)
args, unknown = self.west_parser.parse_known_args(args=argv)

Expand Down Expand Up @@ -578,10 +617,12 @@ def handle_early_arg_errors(self, early_args):
# gracefully. This provides more user-friendly output than
# argparse can do on its own.

if (early_args.command_name and
(early_args.command_name not in self.builtins and
(not self.extensions or
early_args.command_name not in self.extensions))):
if (
not early_args.help and
early_args.command_name and
early_args.command_name not in self.builtins and
(not self.extensions or early_args.command_name not in self.extensions)
):
self.handle_unknown_command(early_args.command_name)

def handle_unknown_command(self, command_name):
Expand Down Expand Up @@ -825,6 +866,8 @@ def do_run(self, args, ignored):
# exception handling block in app.run_command is in a
# parent stack frame.
app.run_extension(name, [name, '--help'])
elif app.aliases is not None and name in app.aliases:
app.aliases[name].parser.print_help()
else:
self.wrn(f'unknown command "{name}"')
app.west_parser.print_help(top_level=True)
Expand All @@ -833,6 +876,24 @@ def do_run(self, args, ignored):
'which may be causing this issue.\n'
' Try running "west update" or fixing the manifest.')

class Alias(WestCommand):
# An alias command, it does not run itself

def __init__(self, cmd, args):
super().__init__(cmd, args, f'An alias that expands to: {args}')

self.args = shlex.split(args)

def do_add_parser(self, parser_adder):
parser = parser_adder.add_parser(
self.name, help=self.help, description=self.description, add_help=False,
formatter_class=argparse.RawDescriptionHelpFormatter)

return parser

def do_run(self, args, ignored):
assert False

class WestHelpAction(argparse.Action):

def __call__(self, parser, namespace, values, option_string=None):
Expand Down Expand Up @@ -944,6 +1005,12 @@ def append(*strings):
self.format_extension_spec(append, spec, width)
append('')

if self.west_app.aliases:
append('aliases:')
for alias in self.west_app.aliases.values():
self.format_command(append, alias, width)
append('')

if self.epilog:
append(self.epilog)

Expand Down

0 comments on commit 8fdcb3b

Please sign in to comment.