Line data Source code
1 : #include "../fd_config.h"
2 : #include "../fd_action.h"
3 :
4 : #include <string.h>
5 : #include <unistd.h>
6 :
7 : extern action_t * ACTIONS[];
8 :
9 0 : #define HELP_ARG_INDENT (3UL)
10 0 : #define HELP_ARG_GAP (4UL) /* spaces between the flag column and its description */
11 : #define HELP_ARG_MAX (64UL) /* most arguments any single action emits */
12 :
13 : /* fd_action_help is the builder passed to an action's args_help
14 : callback. The callback emits arguments by calling fd_action_help_arg,
15 : which simply records them here; fd_action_help_print then formats the
16 : collected arguments into an aligned column. */
17 :
18 : struct fd_action_help {
19 : ulong cnt;
20 : struct {
21 : char const * name;
22 : char const * value;
23 : char const * description;
24 : } args[ HELP_ARG_MAX ];
25 : };
26 :
27 : void
28 : fd_action_help_arg( fd_action_help_t * help,
29 : char const * name,
30 : char const * value,
31 0 : char const * description ) {
32 0 : if( FD_UNLIKELY( help->cnt>=HELP_ARG_MAX ) ) FD_LOG_ERR(( "too many help arguments (max %lu); increase HELP_ARG_MAX", HELP_ARG_MAX ));
33 0 : help->args[ help->cnt ].name = name;
34 0 : help->args[ help->cnt ].value = value;
35 0 : help->args[ help->cnt ].description = description;
36 0 : help->cnt++;
37 0 : }
38 :
39 : /* help_print_desc prints desc starting at the current column, wrapping
40 : any embedded newlines so continuation lines align under the first.
41 : col is the flag column width chosen for this command. */
42 :
43 : static void
44 : help_print_desc( char const * desc,
45 0 : ulong col ) {
46 0 : ulong pad = HELP_ARG_INDENT + col + HELP_ARG_GAP;
47 0 : for(;;) {
48 0 : char const * nl = strchr( desc, '\n' );
49 0 : if( FD_LIKELY( !nl ) ) {
50 0 : FD_LOG_STDOUT(( "%s\n", desc ));
51 0 : break;
52 0 : }
53 0 : FD_LOG_STDOUT(( "%.*s\n%*s", (int)(nl-desc), desc, (int)pad, "" ));
54 0 : desc = nl+1UL;
55 0 : }
56 0 : }
57 :
58 : /* help_print_args renders a collected set of arguments under the given
59 : section header, sizing the flag column to the widest flag so the
60 : descriptions line up. Does nothing if help is empty. */
61 :
62 : static void
63 : help_print_args( char const * header,
64 0 : fd_action_help_t const * help ) {
65 0 : if( FD_UNLIKELY( !help->cnt ) ) return;
66 :
67 0 : FD_LOG_STDOUT(( "%s\n", header ));
68 :
69 0 : ulong col = 0UL;
70 0 : for( ulong i=0UL; i<help->cnt; i++ ) {
71 0 : ulong len = strlen( help->args[ i ].name );
72 0 : if( help->args[ i ].value ) len += 1UL + strlen( help->args[ i ].value ); /* space + value */
73 0 : if( len>col ) col = len;
74 0 : }
75 :
76 0 : for( ulong i=0UL; i<help->cnt; i++ ) {
77 0 : char flag[ 64 ];
78 0 : if( help->args[ i ].value ) FD_TEST( fd_cstr_printf_check( flag, sizeof( flag ), NULL, "%s %s", help->args[ i ].name, help->args[ i ].value ) );
79 0 : else FD_TEST( fd_cstr_printf_check( flag, sizeof( flag ), NULL, "%s", help->args[ i ].name ) );
80 0 : FD_LOG_STDOUT(( "%*s%-*s%*s", (int)HELP_ARG_INDENT, "", (int)col, flag, (int)HELP_ARG_GAP, "" ));
81 0 : help_print_desc( help->args[ i ].description, col );
82 0 : }
83 0 : }
84 :
85 : /* help_print_derived_usage prints a "Usage" line derived from the
86 : action's arguments. Used when the action does not supply an explicit
87 : .usage field. Positional arguments are listed in order so required
88 : positionals are never hidden, followed by a trailing `[OPTIONS]`. An
89 : action only needs to set .usage field when it has structure this
90 : simple default cannot express (e.g. a `<a|b|c>` subcommand choice). */
91 : static void
92 : help_print_derived_usage( action_t const * action,
93 0 : fd_action_help_t const * help ) {
94 0 : char usage[ 256 ];
95 0 : char * p = fd_cstr_init( usage );
96 0 : char * end = usage + sizeof(usage);
97 :
98 0 : for( ulong i=0UL; i<help->cnt; i++ ) {
99 0 : char const * name = help->args[ i ].name;
100 0 : if( name[ 0 ]=='-' ) continue; /* a flag, covered by the [OPTIONS] below */
101 :
102 0 : char piece[ 96 ];
103 0 : ulong piece_len;
104 0 : if( help->args[ i ].value ) FD_TEST( fd_cstr_printf_check( piece, sizeof(piece), &piece_len, " %s %s", name, help->args[ i ].value ) );
105 0 : else FD_TEST( fd_cstr_printf_check( piece, sizeof(piece), &piece_len, " %s", name ) );
106 0 : FD_TEST( piece_len < (ulong)(end-p) );
107 0 : p = fd_cstr_append_cstr( p, piece );
108 0 : }
109 :
110 0 : FD_TEST( 10UL < (ulong)(end-p) );
111 0 : p = fd_cstr_append_cstr( p, " [OPTIONS]" );
112 0 : fd_cstr_fini( p );
113 :
114 0 : FD_LOG_STDOUT(( "Usage: %s %s%s\n\n", FD_BINARY_NAME, action->name, usage ));
115 0 : }
116 :
117 : void
118 0 : fd_action_help_print( action_t const * action ) {
119 0 : FD_LOG_STDOUT(( "%s\n", action->description ));
120 :
121 0 : if( FD_LIKELY( action->detail ) ) FD_LOG_STDOUT(( "\n%s\n", action->detail ));
122 :
123 0 : FD_LOG_STDOUT(( "\n" ));
124 :
125 0 : fd_action_help_t help[1] = {0};
126 0 : if( FD_LIKELY( action->args_help ) ) action->args_help( help );
127 :
128 0 : if( FD_LIKELY( action->usage ) ) FD_LOG_STDOUT(( "Usage: %s %s\n\n", FD_BINARY_NAME, action->usage ));
129 0 : else help_print_derived_usage( action, help );
130 :
131 0 : fd_action_help_t global[1] = {0};
132 0 : fd_global_options_help( global );
133 0 : help_print_args( "GLOBAL OPTIONS:", global );
134 :
135 0 : if( FD_LIKELY( help->cnt ) ) {
136 0 : FD_LOG_STDOUT(( "\n" ));
137 0 : help_print_args( "ARGUMENTS:", help );
138 0 : }
139 0 : }
140 :
141 : void
142 : help_cmd_fn( args_t * args FD_PARAM_UNUSED,
143 0 : config_t * config FD_PARAM_UNUSED ) {
144 0 : FD_LOG_STDOUT(( "%s control binary\n\n", FD_APP_NAME ));
145 0 : FD_LOG_STDOUT(( "Usage: %s <SUBCOMMAND> [OPTIONS]\n\n", FD_BINARY_NAME ));
146 :
147 0 : fd_action_help_t global[1] = {0};
148 0 : fd_global_options_help( global );
149 0 : help_print_args( "OPTIONS:", global );
150 :
151 0 : FD_LOG_STDOUT(( "\nSUBCOMMANDS:\n" ));
152 0 : for( ulong i=0UL; ACTIONS[ i ]; i++ ) {
153 : FD_LOG_STDOUT(( " %20s %s\n", ACTIONS[ i ]->name, ACTIONS[ i ]->description ));
154 0 : }
155 0 : }
156 :
157 : action_t fd_action_help = {
158 : .name = "help",
159 : .args = NULL,
160 : .fn = help_cmd_fn,
161 : .perm = NULL,
162 : .description = "Print this help message",
163 : .is_help = 1,
164 : .is_immediate = 1,
165 : .is_diagnostic = 1,
166 : };
|