lib.App: Add root command slot #66

Merged
Jan Lindemann merged 2 commits from jan/feature/20260817-lib-app-add-cmd-to-parser-make-sub-parser into master 2026-08-17 13:02:01 +02:00 AGit

Up to now, options and run() of an Application object derived from lib.App have to be defined by reimplementation in inheriting classes. What subcommands do, OTOH, is defined in classes derived from lib.AbstractCmd. This PR makes it possible to use classes derived from lib.AbstractCmd as defining source of what an application does at its root.

lib.App.add_cmd_to_parser -> .make_sub_parser()

Code beautification: add_cmd_to_parser() isn't very telling about its return type and the fact that it creates an object, hence the name change. Also, annotate its argument with a private argparse type to avoid a cast. My concern that argparse will break the private type at some point in the future is outweighed by the gained clode clarity in this function.

lib.App: Add a root command slot

The application's top-level behavior is defined by overriding App._add_arguments() and App._run(). The lightweight run-and-options unit, Cmd, can already be mounted at any node of the command tree, but the root is reserved for the application itself. An application that wants to host a plain command at the top level therefore has to subclass App and carry its full lifecycle implementation.

Add a root parameter to App.init(). When it is given a command class, App instantiates it and uses it as the top level: the command's options are registered on the top-level parser, it becomes the parent of the top-level subcommands, and App._run() delegates the run to it. The command's children are wired as the top-level subcommands, so the same Cmd can now occupy the root node. When root is not given, the previous auto-discovery behavior is preserved unchanged.

Keep the top-level subcommand heading as plain "Available subcommands" whether it is hosted by the application or by a root command, while nested command levels continue to qualify the heading with the parent name. Add a unit test that mounts a root command hosting a child and checks option registration, dispatch, setup and teardown, and resolution of the application through the parent chain.

Up to now, options and run() of an Application object derived from lib.App have to be defined by reimplementation in inheriting classes. What subcommands do, OTOH, is defined in classes derived from lib.AbstractCmd. This PR makes it possible to use classes derived from lib.AbstractCmd as defining source of what an application does at its root. #### lib.App.add_cmd_to_parser -> .make_sub_parser() Code beautification: add_cmd_to_parser() isn't very telling about its return type and the fact that it creates an object, hence the name change. Also, annotate its argument with a private argparse type to avoid a cast. My concern that argparse will break the private type at some point in the future is outweighed by the gained clode clarity in this function. #### lib.App: Add a root command slot The application's top-level behavior is defined by overriding App._add_arguments() and App._run(). The lightweight run-and-options unit, Cmd, can already be mounted at any node of the command tree, but the root is reserved for the application itself. An application that wants to host a plain command at the top level therefore has to subclass App and carry its full lifecycle implementation. Add a root parameter to App.__init__(). When it is given a command class, App instantiates it and uses it as the top level: the command's options are registered on the top-level parser, it becomes the parent of the top-level subcommands, and App._run() delegates the run to it. The command's children are wired as the top-level subcommands, so the same Cmd can now occupy the root node. When root is not given, the previous auto-discovery behavior is preserved unchanged. Keep the top-level subcommand heading as plain "Available subcommands" whether it is hosted by the application or by a root command, while nested command levels continue to qualify the heading with the parent name. Add a unit test that mounts a root command hosting a child and checks option registration, dispatch, setup and teardown, and resolution of the application through the parent chain.
Code beautification: add_cmd_to_parser() isn't very telling about its
return type and the fact that it creates an object, hence the name
change. Also, annotate its argument with a private argparse type to
avoid a cast. My concern that argparse will break the private type at
some point in the future is outweighed by the gained clode clarity in
this function.

Signed-off-by: Jan Lindemann <jan@janware.com>
lib.App: Add a root command slot
All checks were successful
CI / Packaging - Kali Linux (pull_request) Successful in 4m23s
CI / Packaging - OpenSUSE Tumbleweed (pull_request) Successful in 4m28s
CI / Packaging test (pull_request) Successful in 0s
CI / Packaging - Kali Linux (push) Successful in 3m58s
CI / Packaging - OpenSUSE Tumbleweed (push) Successful in 4m16s
CI / Packaging test (push) Successful in 0s
6b4bcdfaf8
The application's top-level behavior is defined by overriding
App._add_arguments() and App._run(). The lightweight run-and-options
unit, Cmd, can already be mounted at any node of the command tree, but
the root is reserved for the application itself. An application that
wants to host a plain command at the top level therefore has to
subclass App and carry its full lifecycle implementation.

Add a root parameter to App.__init__(). When it is given a command
class, App instantiates it and uses it as the top level: the command's
options are registered on the top-level parser, it becomes the parent
of the top-level subcommands, and App._run() delegates the run to it.
The command's children are wired as the top-level subcommands, so the
same Cmd can now occupy the root node. When root is not given, the
previous auto-discovery behavior is preserved unchanged.

Keep the top-level subcommand heading as plain "Available subcommands"
whether it is hosted by the application or by a root command, while
nested command levels continue to qualify the heading with the parent
name. Add a unit test that mounts a root command hosting a child and
checks option registration, dispatch, setup and teardown, and
resolution of the application through the parent chain.

Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2
Signed-off-by: Jan Lindemann <jan@janware.com>
Jan Lindemann scheduled this pull request to auto merge when all checks succeed 2026-08-17 12:53:07 +02:00
Jan Lindemann changed title from lib.App.add_cmd_to_parser -> .make_sub_parser() to lib.App: Add root command slot 2026-08-17 13:04:19 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
janware/jw-pkg!66
No description provided.