Skip to content

Commit 84b4497

Browse files
committed
[Feature] Printing subcommand list on command execution that cannot be executed directly
1 parent 8c6b262 commit 84b4497

File tree

5 files changed

+201
-3
lines changed

5 files changed

+201
-3
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
- Printing subcommand list on command execution that cannot be executed directly.
11+
1012
## [0.1.0] - 2025-07-02
1113

1214
### Added

includes/argus/internal/display.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,22 @@ void display_usage(argus_t *argus, const argus_option_t *command);
4848
*/
4949
void display_version(argus_t *argus);
5050

51+
/**
52+
* display_available_subcommands - Display available subcommands for a command that cannot be
53+
* executed by itself
54+
*
55+
* @param argus Argus context
56+
* @param command Command that triggered the error (must have suboptions)
57+
*/
58+
void display_available_subcommands(argus_t *argus, const argus_option_t *command);
59+
60+
/**
61+
* display_all_commands - Display all available commands when no command is specified
62+
*
63+
* @param argus Argus context
64+
*/
65+
void display_all_commands(argus_t *argus);
66+
5167
/**
5268
* get_default_helper_config - Get default helper configuration
5369
*

source/api/argus_exec.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
#include "argus/errors.h"
11+
#include "argus/internal/display.h"
1112
#include "argus/types.h"
1213
#include <stddef.h>
1314

@@ -23,12 +24,11 @@ int argus_exec(argus_t *argus, void *data)
2324
command = argus->subcommand_stack[argus->subcommand_depth - 1];
2425

2526
if (command == NULL) {
26-
ARGUS_PARSING_ERROR(argus, ARGUS_ERROR_NO_COMMAND, "Internal error: No command to execute");
27+
display_all_commands(argus);
2728
return ARGUS_ERROR_NO_COMMAND;
2829
}
2930
if (command->action == NULL) {
30-
ARGUS_PARSING_ERROR(argus, ARGUS_ERROR_INVALID_HANDLER,
31-
"The %s command cannot be executed by itself", command->name);
31+
display_available_subcommands(argus, command);
3232
return ARGUS_ERROR_INVALID_HANDLER;
3333
}
3434
return (command->action(argus, data));

source/display/command_display.c

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* MIT License
3+
*
4+
* Copyright (c) 2025 lucocozz
5+
*
6+
* This file is part of Argus.
7+
* See LICENSE file in the project root for full license information.
8+
*/
9+
10+
#define _GNU_SOURCE // NOLINT
11+
12+
#include <stdbool.h>
13+
#include <stdio.h>
14+
#include <string.h>
15+
16+
#include "argus/internal/display.h"
17+
#include "argus/types.h"
18+
19+
#define COMMAND_BUFFER_SIZE 512
20+
#define FULL_PATH_BUFFER_SIZE 1024
21+
22+
static void print_command_line(const char *command_path, const char *description, argus_t *argus)
23+
{
24+
printf(" %s", command_path);
25+
26+
size_t padding = 0;
27+
size_t name_len = strlen(command_path);
28+
if (argus->helper.config.description_column > name_len + 2)
29+
padding = argus->helper.config.description_column - name_len - 2;
30+
else
31+
padding = 2;
32+
33+
for (size_t i = 0; i < padding; ++i)
34+
printf(" ");
35+
36+
if (description != NULL)
37+
printf("- %s", description);
38+
39+
printf("\n");
40+
}
41+
42+
static void print_subcommand_paths(const argus_option_t *options, const char *base_path,
43+
argus_t *argus, bool *first_in_group)
44+
{
45+
char path_buffer[COMMAND_BUFFER_SIZE];
46+
bool current_group_started = false;
47+
48+
for (int i = 0; options[i].type != TYPE_NONE; ++i) {
49+
const argus_option_t *option = &options[i];
50+
51+
if (option->type != TYPE_SUBCOMMAND)
52+
continue;
53+
54+
if (!current_group_started && !*first_in_group)
55+
printf("\n");
56+
current_group_started = true;
57+
*first_in_group = false;
58+
59+
snprintf(path_buffer, sizeof(path_buffer), "%s %s", base_path, option->name);
60+
print_command_line(path_buffer, option->help, argus);
61+
62+
if (option->sub_options != NULL) {
63+
char sub_path_buffer[COMMAND_BUFFER_SIZE];
64+
snprintf(sub_path_buffer, sizeof(sub_path_buffer), "%s %s", base_path, option->name);
65+
bool nested_first = true;
66+
print_subcommand_paths(option->sub_options, sub_path_buffer, argus, &nested_first);
67+
}
68+
}
69+
}
70+
71+
void display_available_subcommands(argus_t *argus, const argus_option_t *command)
72+
{
73+
printf("The '%s' command cannot be executed by itself.\n\n", command->name);
74+
printf("Available commands:\n");
75+
76+
bool first_group = true;
77+
if (command->sub_options != NULL)
78+
print_subcommand_paths(command->sub_options, command->name, argus, &first_group);
79+
80+
printf("\nRun '");
81+
printf("%s", argus->program_name);
82+
83+
for (size_t i = 0; i < argus->subcommand_depth; ++i)
84+
printf(" %s", argus->subcommand_stack[i]->name);
85+
86+
printf(" SUBCOMMAND --help' for more information.\n");
87+
}
88+
89+
static bool has_executable_subcommands(const argus_option_t *option)
90+
{
91+
if (option->sub_options == NULL)
92+
return false;
93+
94+
for (int i = 0; option->sub_options[i].type != TYPE_NONE; ++i) {
95+
const argus_option_t *sub_option = &option->sub_options[i];
96+
97+
if (sub_option->type == TYPE_SUBCOMMAND && sub_option->action != NULL)
98+
return true;
99+
}
100+
101+
return false;
102+
}
103+
104+
static bool is_effectively_simple_command(const argus_option_t *option)
105+
{
106+
return (option->type == TYPE_SUBCOMMAND && option->action != NULL &&
107+
!has_executable_subcommands(option));
108+
}
109+
110+
static bool print_simple_commands_group(argus_t *argus)
111+
{
112+
bool printed_any = false;
113+
114+
for (int i = 0; argus->options[i].type != TYPE_NONE; ++i) {
115+
const argus_option_t *option = &argus->options[i];
116+
117+
if (is_effectively_simple_command(option)) {
118+
char command_path[COMMAND_BUFFER_SIZE];
119+
snprintf(command_path, sizeof(command_path), "%s %s", argus->program_name,
120+
option->name);
121+
print_command_line(command_path, option->help, argus);
122+
printed_any = true;
123+
}
124+
}
125+
126+
return printed_any;
127+
}
128+
129+
static void print_command_families(argus_t *argus, bool printed_simple_commands)
130+
{
131+
bool first_family = true;
132+
133+
for (int i = 0; argus->options[i].type != TYPE_NONE; ++i) {
134+
const argus_option_t *option = &argus->options[i];
135+
136+
if (option->type != TYPE_SUBCOMMAND || !has_executable_subcommands(option))
137+
continue;
138+
139+
if (printed_simple_commands || !first_family)
140+
printf("\n");
141+
142+
first_family = false;
143+
144+
char family_base_path[COMMAND_BUFFER_SIZE];
145+
snprintf(family_base_path, sizeof(family_base_path), "%s %s", argus->program_name,
146+
option->name);
147+
148+
if (option->action != NULL)
149+
print_command_line(family_base_path, option->help, argus);
150+
151+
for (int j = 0; option->sub_options[j].type != TYPE_NONE; ++j) {
152+
const argus_option_t *sub_option = &option->sub_options[j];
153+
154+
if (sub_option->type != TYPE_SUBCOMMAND || sub_option->action == NULL)
155+
continue;
156+
157+
char full_command_path[FULL_PATH_BUFFER_SIZE];
158+
snprintf(full_command_path, sizeof(full_command_path), "%s %s", family_base_path,
159+
sub_option->name);
160+
print_command_line(full_command_path, sub_option->help, argus);
161+
}
162+
163+
printed_simple_commands = false;
164+
}
165+
}
166+
167+
void display_all_commands(argus_t *argus)
168+
{
169+
printf("No command specified.\n\n");
170+
printf("Available commands:\n");
171+
172+
if (argus->options != NULL) {
173+
bool printed_simple_commands = print_simple_commands_group(argus);
174+
print_command_families(argus, printed_simple_commands);
175+
}
176+
177+
printf("\nRun '%s COMMAND --help' for more information on a specific command.\n",
178+
argus->program_name);
179+
}

source/display/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ subdir('help')
22

33
display_sources = files([
44
'help_display.c',
5+
'command_display.c',
56
'usage_display.c',
67
'version_display.c',
78
]) + help_sources

0 commit comments

Comments
 (0)