feat(cli): add error functions and duplicate option tracking for CLI arguments

This commit is contained in:
lohhiiccc 2026-03-05 19:54:32 +01:00
parent 78ca539b99
commit de872facd3
10 changed files with 87 additions and 31 deletions

View file

@ -4,4 +4,9 @@
void print_help(void); void print_help(void);
void print_version(void); void print_version(void);
void error_unknown_opt(const char *current_opt);
void error_invalid_arg(const char *current_opt);
void error_invalid_opt(const char *current_opt);
void error_duplicate_opt(const char *current_opt);
#endif #endif

View file

@ -1,6 +1,20 @@
#ifndef PING_CLI_OPT_H #ifndef PING_CLI_OPT_H
#define PING_CLI_OPT_H #define PING_CLI_OPT_H
#include <getopt.h>
#include "ft_ping_flags.h"
#define TRACK_DUP_OPT(descriptor, tracker, opt_name, opts_array) \
do { \
bitmask_index = (size_t)((descriptor) - (opts_array)); \
if (HAS_FLAG(tracker, (1ULL << bitmask_index))) \
{ \
error_duplicate_opt(opt_name); \
return CLI_ERROR; \
} \
SET_FLAG(tracker, (1ULL << bitmask_index)); \
} while (0)
#define CLI_OPTIONS_LIST \ #define CLI_OPTIONS_LIST \
X( \ X( \
'h', \ 'h', \
@ -87,7 +101,6 @@
#define X(short_opt, long_opt, has_arg, handler, arg_type, desc) + 1 #define X(short_opt, long_opt, has_arg, handler, arg_type, desc) + 1
enum { CLI_OPT_LEN = (0 CLI_OPTIONS_LIST) }; enum { CLI_OPT_LEN = (0 CLI_OPTIONS_LIST) };
#include <getopt.h>
#undef X #undef X
#define X(short_opt, long_opt, has_arg, handler, arg_type, desc) \ #define X(short_opt, long_opt, has_arg, handler, arg_type, desc) \
+ (1 * (has_arg == no_argument) \ + (1 * (has_arg == no_argument) \

View file

@ -18,6 +18,7 @@ SRCS = $(SRC_DIR)/main.c \
$(SRC_DIR)/cli/parse_utils/get_optstr.c \ $(SRC_DIR)/cli/parse_utils/get_optstr.c \
$(SRC_DIR)/cli/messages/help.c \ $(SRC_DIR)/cli/messages/help.c \
$(SRC_DIR)/cli/messages/version.c \ $(SRC_DIR)/cli/messages/version.c \
$(SRC_DIR)/cli/messages/error.c \
TESTS_DIR = tests TESTS_DIR = tests
TESTS = $(TESTS_DIR)/test_main.c \ TESTS = $(TESTS_DIR)/test_main.c \

View file

@ -1,4 +1,3 @@
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include "cli.h" #include "cli.h"
@ -11,11 +10,7 @@ cli_handle_count(const char *arg, t_ping_config *config)
uint64_t count; uint64_t count;
if (0 != cli_parse_uint64(arg, &count)) if (0 != cli_parse_uint64(arg, &count))
{
dprintf(STDERR_FILENO, "Invalide count\n");
return CLI_ERROR; return CLI_ERROR;
}
config->count = count; config->count = count;
return CLI_SUCCESS; return CLI_SUCCESS;
} }

View file

@ -3,7 +3,6 @@
#include "internal/cli/parse_utils.h" #include "internal/cli/parse_utils.h"
#include <unistd.h> #include <unistd.h>
#include <stdio.h>
int int
cli_handle_interval(const char *arg, t_ping_config *config) cli_handle_interval(const char *arg, t_ping_config *config)
@ -11,10 +10,7 @@ cli_handle_interval(const char *arg, t_ping_config *config)
float interval; float interval;
if (0 != cli_parse_float(arg, &interval)) if (0 != cli_parse_float(arg, &interval))
{
dprintf(STDERR_FILENO, "Invalide interval\n");
return CLI_ERROR; return CLI_ERROR;
}
config->interval = interval; config->interval = interval;
return CLI_SUCCESS; return CLI_SUCCESS;

View file

@ -2,7 +2,6 @@
#include "internal/cli/arg_handler.h" #include "internal/cli/arg_handler.h"
#include "internal/cli/parse_utils.h" #include "internal/cli/parse_utils.h"
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
int int
@ -11,10 +10,7 @@ cli_handle_size(const char *arg, t_ping_config *config)
uint64_t size; uint64_t size;
if (0 != cli_parse_uint64(arg, &size) || size > (65535 - 8)) if (0 != cli_parse_uint64(arg, &size) || size > (65535 - 8))
{
dprintf(STDERR_FILENO, "Invalide size\n");
return CLI_ERROR; return CLI_ERROR;
}
config->packet_size = size; config->packet_size = size;
return CLI_SUCCESS; return CLI_SUCCESS;

View file

@ -3,7 +3,6 @@
#include "internal/cli/parse_utils.h" #include "internal/cli/parse_utils.h"
#include <unistd.h> #include <unistd.h>
#include <stdio.h>
int int
cli_handle_timeout(const char *arg, t_ping_config *config) cli_handle_timeout(const char *arg, t_ping_config *config)
@ -11,10 +10,7 @@ cli_handle_timeout(const char *arg, t_ping_config *config)
float TO; float TO;
if (0 != cli_parse_float(arg, &TO)) if (0 != cli_parse_float(arg, &TO))
{
dprintf(STDERR_FILENO, "Invalide timeout\n");
return CLI_ERROR; return CLI_ERROR;
}
config->timeout = TO; config->timeout = TO;
return CLI_SUCCESS; return CLI_SUCCESS;

View file

@ -2,7 +2,6 @@
#include "internal/cli/arg_handler.h" #include "internal/cli/arg_handler.h"
#include "internal/cli/parse_utils.h" #include "internal/cli/parse_utils.h"
#include <stdio.h>
#include <unistd.h> #include <unistd.h>
int int
@ -11,10 +10,7 @@ cli_handle_ttl(const char *arg, t_ping_config *config)
uint64_t ttl; uint64_t ttl;
if (0 != cli_parse_uint64(arg, &ttl) || ttl > 255) if (0 != cli_parse_uint64(arg, &ttl) || ttl > 255)
{
dprintf(STDERR_FILENO, "Invalide ttl\n");
return CLI_ERROR; return CLI_ERROR;
}
config->ttl = (uint8_t)ttl; config->ttl = (uint8_t)ttl;
return CLI_SUCCESS; return CLI_SUCCESS;

View file

@ -0,0 +1,43 @@
#include <unistd.h>
#include <stdio.h>
#include "version_gen.h"
#define CMD_NAME g_prog_name.name
/* Forward declarations */
void static inline print_suggest_help(void);
/* -------------------- */
void
error_unknown_opt(const char *current_opt)
{
dprintf(STDERR_FILENO, "%s: unknown option -- '%s'\n", CMD_NAME, current_opt);
print_suggest_help();
}
void
error_invalid_arg(const char *current_opt)
{
dprintf(STDERR_FILENO, "%s: option '%s' requires an argument\n", CMD_NAME, current_opt);
print_suggest_help();
}
void
error_invalid_opt(const char *current_opt)
{
dprintf(STDERR_FILENO, "%s: invalid option '%s'\n", CMD_NAME, current_opt);
print_suggest_help();
}
void
error_duplicate_opt(const char *current_opt)
{
dprintf(STDERR_FILENO, "%s: duplicate option '%s'\n", CMD_NAME, current_opt);
print_suggest_help();
}
void static inline
print_suggest_help(void)
{
dprintf(STDERR_FILENO, "Try '%s --help' for more information.\n", CMD_NAME);
}

View file

@ -1,10 +1,13 @@
#include <stddef.h> #include <stddef.h>
#include <stdio.h>
#include <string.h> #include <string.h>
#include <getopt.h> #include <getopt.h>
#include <unistd.h>
#include "cli.h" #include "cli.h"
#include "internal/cli/arg_handler.h" #include "internal/cli/messages.h"
#include "internal/cli/options.h" #include "internal/cli/options.h"
#include "internal/cli/arg_handler.h"
#include "ft_ping_const.h" #include "ft_ping_const.h"
/* Map -? to -h to support help shortcut */ /* Map -? to -h to support help shortcut */
@ -18,14 +21,15 @@
static void init_config(t_ping_config *config); static void init_config(t_ping_config *config);
static void build_long_options(struct option *long_opts, size_t nb_opts, static void build_long_options(struct option *long_opts, size_t nb_opts,
const t_option_descriptor *src); const t_option_descriptor *src);
static const t_option_descriptor *find_option_handler(int opt); static inline const t_option_descriptor *find_option_handler(int opt);
static int handle_one_option(int opt, char *arg, t_ping_config *config); static int handle_one_option(int opt, char **argv, t_ping_config *config, uint64_t *opt_tacker);
/* ------------------- */ /* ------------------- */
enum e_cli_code enum e_cli_code
cli_parse_arguments(int argc, char **argv, t_ping_config *config) cli_parse_arguments(int argc, char **argv, t_ping_config *config)
{ {
struct option long_opts[CLI_OPT_LEN + 1]; struct option long_opts[CLI_OPT_LEN + 1];
uint64_t tracker = 0;
int opt; int opt;
int res; int res;
@ -34,7 +38,7 @@ cli_parse_arguments(int argc, char **argv, t_ping_config *config)
while (-1 != (opt = getopt_long(argc, argv, cli_get_optstr(), long_opts, NULL))) while (-1 != (opt = getopt_long(argc, argv, cli_get_optstr(), long_opts, NULL)))
{ {
HANDLE_QUESTION_MARK(opt); HANDLE_QUESTION_MARK(opt);
res = handle_one_option(opt, optarg, config); res = handle_one_option(opt, argv, config, &tracker);
if (0 != res) if (0 != res)
return res; return res;
} }
@ -42,19 +46,30 @@ cli_parse_arguments(int argc, char **argv, t_ping_config *config)
} }
static int static int
handle_one_option(int opt, char *arg, t_ping_config *config) handle_one_option(int opt, char **argv, t_ping_config *config, uint64_t *opt_tracker)
{ {
const t_option_descriptor *desc = find_option_handler(opt); const t_option_descriptor *desc = find_option_handler(opt);
const char *current_opt = argv[optind - 1];
int res; int res;
size_t bitmask_index;
switch (opt)
{
case '?': error_unknown_opt(current_opt); return CLI_ERROR;
case ':': error_invalid_arg(current_opt); return CLI_ERROR;
default: break;
}
if ('?' == opt)
return CLI_ERROR;
if (NULL != desc) if (NULL != desc)
{ {
res = desc->handler(arg, config); TRACK_DUP_OPT(desc, *opt_tracker, current_opt, g_options);
res = desc->handler(optarg, config);
if (0 != res) if (0 != res)
{
error_invalid_opt(current_opt);
return res; return res;
} }
}
return CLI_SUCCESS; return CLI_SUCCESS;
} }
@ -82,7 +97,7 @@ build_long_options(struct option *long_opts, size_t nb_opts,
long_opts[nb_opts] = (struct option){0}; long_opts[nb_opts] = (struct option){0};
} }
static const t_option_descriptor static inline const t_option_descriptor
*find_option_handler(int opt) *find_option_handler(int opt)
{ {
for (size_t i = 0; i < CLI_OPT_LEN; ++i) for (size_t i = 0; i < CLI_OPT_LEN; ++i)