feat: add version generation and CLI parsing

Add auto-generated version header and implement full command-line
argument parsing with getopt_long support.
This commit is contained in:
lohhiiccc 2026-03-01 13:55:11 +01:00
parent c29336ea81
commit b786e79287
7 changed files with 182 additions and 25 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ build/
build.mk build.mk
.cache/ .cache/
compile_commands.json compile_commands.json
includes/version_gen.h

View file

@ -15,6 +15,9 @@ endif
NAME = ft_ping NAME = ft_ping
TEST_BIN = ft_ping.test TEST_BIN = ft_ping.test
VERSION := 0.0.1
BUILD_DATE := $(shell date -u '+%Y-%m-%d %H:%M:%S UTC')
GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "nogit")
.DEFAULT_GOAL := all .DEFAULT_GOAL := all
MAKEFLAGS += --no-print-directory MAKEFLAGS += --no-print-directory
@ -35,8 +38,30 @@ DEPS = $(OBJS:.o=.d)
# Build Rules # Build Rules
VERSION_HEADER = includes/version_gen.h
$(VERSION_HEADER): Makefile
@echo "/* Auto-generated - DO NOT EDIT */" > $@
@echo "#ifndef PING_VERSION_GEN_H" >> $@
@echo "#define PING_VERSION_GEN_H" >> $@
@echo "#define PING_VERSION \"$(VERSION)\"" >> $@
@echo "#define PING_BUILD_DATE \"$(BUILD_DATE)\"" >> $@
@echo "#define PING_GIT_COMMIT \"$(GIT_COMMIT)\"" >> $@
@echo "" >> $@
@echo "typedef struct s_prog_name" >> $@
@echo "{" >> $@
@echo " char *alloc;" >> $@
@echo " const char *name;" >> $@
@echo "} t_prog_name;" >> $@
@echo "" >> $@
@echo "extern t_prog_name g_prog_name;" >> $@
@echo "" >> $@
@echo "#endif" >> $@
@echo "[OK] Version header generated"
.PHONY: all .PHONY: all
all: $(LIBICMP_DEP) $(NAME) all: $(LIBICMP_DEP) $(VERSION_HEADER) $(NAME)
# Build local libicmp if needed # Build local libicmp if needed
ifeq ($(BUILD_LOCAL_LIBICMP),yes) ifeq ($(BUILD_LOCAL_LIBICMP),yes)
@ -125,6 +150,7 @@ endif
.PHONY: clean .PHONY: clean
clean: clean:
@echo "[CLEAN] Cleaning object files..." @echo "[CLEAN] Cleaning object files..."
$(RM) $(VERSION_HEADER)
$(RM) -r $(OBJ_DIR) $(RM) -r $(OBJ_DIR)
ifeq ($(BUILD_LOCAL_LIBICMP),yes) ifeq ($(BUILD_LOCAL_LIBICMP),yes)
@echo "[CLEAN] Cleaning libicmp..." @echo "[CLEAN] Cleaning libicmp..."

2
configure vendored
View file

@ -486,7 +486,7 @@ generate_build_mk() {
# Prepare compiler flags # Prepare compiler flags
local cflags="-Wall -Wextra -Werror -pipe" local cflags="-Wall -Wextra -Werror -pipe"
local cppflags="-std=c99 -I includes" local cppflags="-std=c99 -I includes -D_GNU_SOURCE"
local ldflags="" local ldflags=""
local debug_flags="" local debug_flags=""
local sanitizer_flags="" local sanitizer_flags=""

View file

@ -5,6 +5,30 @@
typedef int (*t_option_handler)(const char *arg, t_ping_config *config); typedef int (*t_option_handler)(const char *arg, t_ping_config *config);
typedef enum e_option_arg_type
{
OPT_ARG_NONE = 0,
OPT_ARG_INT,
OPT_ARG_UINT,
OPT_ARG_SECONDS,
OPT_ARG_BYTES,
OPT_ARG_TTL,
OPT_ARG_STRING
} t_option_arg_type;
typedef struct s_option_descriptor
{
int short_opt;
const char *long_opt;
int has_arg;
t_option_handler handler;
t_option_arg_type arg_type;
const char *description;
} t_option_descriptor;
extern const t_option_descriptor g_options[];
extern const size_t g_options_len;
int cli_handle_count(const char *arg, t_ping_config *config); int cli_handle_count(const char *arg, t_ping_config *config);
int cli_handle_flood(const char *arg, t_ping_config *config); int cli_handle_flood(const char *arg, t_ping_config *config);
int cli_handle_help(const char *arg, t_ping_config *config); int cli_handle_help(const char *arg, t_ping_config *config);

View file

@ -2,25 +2,51 @@
#include "cli.h" #include "cli.h"
#include <getopt.h> #include <getopt.h>
typedef struct s_option_descriptor
{
int short_opt;
const char *long_opt;
int has_arg;
t_option_handler handler;
const char *description;
} t_option_descriptor;
const t_option_descriptor g_options[] = { const t_option_descriptor g_options[] = {
{'h', "help", no_argument, cli_handle_help, "Display this help and exit"}, {
{'V', "version", no_argument, cli_handle_version, "Display version information and exit"}, 'h', "help", no_argument, cli_handle_help, OPT_ARG_NONE,
{'v', "verbose", no_argument, cli_handle_verbose, "Verbose output"}, "Display this help and exit"
{'q', "quiet", no_argument, cli_handle_quiet, "Quiet mode (only show summary)"}, },
{'c', "count", required_argument, cli_handle_count, "Stop after sending N packets"}, {
{'i', "interval", required_argument, cli_handle_interval, "Wait N seconds between packets"}, 'V', "version", no_argument, cli_handle_version, OPT_ARG_NONE,
{'t', "ttl", required_argument, cli_handle_ttl, "Set Time To Live"}, "Display version information and exit"
{'s', "size", required_argument, cli_handle_size, "Packet size in bytes"}, },
{'W', "timeout", required_argument, cli_handle_timeout, "Timeout for replies in seconds"}, {
{'f', "flood", no_argument, cli_handle_flood, "Flood mode"}, 'v', "verbose", no_argument, cli_handle_verbose, OPT_ARG_NONE,
{0, NULL, 0, NULL, NULL} "Verbose output"
},
{
'q', "quiet", no_argument, cli_handle_quiet, OPT_ARG_NONE,
"Quiet mode (only show summary)"
},
{
'c', "count", required_argument, cli_handle_count, OPT_ARG_UINT,
"Stop after sending N packets"
},
{
'i', "interval", required_argument, cli_handle_interval, OPT_ARG_SECONDS,
"Wait N seconds between packets"
},
{
't', "ttl", required_argument, cli_handle_ttl, OPT_ARG_TTL,
"Set Time To Live"
},
{
's', "size", required_argument, cli_handle_size, OPT_ARG_BYTES,
"Packet size in bytes"
},
{
'W', "timeout", required_argument, cli_handle_timeout, OPT_ARG_SECONDS,
"Timeout for replies in seconds"
},
{
'f', "flood", no_argument, cli_handle_flood, OPT_ARG_NONE,
"Flood mode"
},
{
0, NULL, 0, NULL, OPT_ARG_NONE,
NULL
}
}; };
const size_t g_options_len = ((sizeof(g_options) / sizeof(*g_options)) - 1);

View file

@ -1,18 +1,51 @@
#include <stddef.h>
#include <string.h> #include <string.h>
#include <getopt.h> #include <getopt.h>
#include "cli.h"
#include "ft_ping.h" #include "ft_ping.h"
#include "ft_ping_const.h" #include "ft_ping_const.h"
/* Forward declatation */ /* Forward declatation */
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,
const t_option_descriptor *src);
static const t_option_descriptor *find_option_handler(int opt);
static int hanlde_one_option(int opt, char *optarg, t_ping_config *config);
/* ------------------- */ /* ------------------- */
int int
cli_parse_arguments(int argc, char **argv, t_ping_config *config) cli_parse_arguments(int argc, char **argv, t_ping_config *config)
{ {
(void)argc; struct option long_opts[g_options_len + 1];
(void)argv; const char *opt_str = "hVvq:c:i:t:s:W:f";
int opt;
int res;
init_config(config); init_config(config);
build_long_options(long_opts, g_options_len, g_options);
while (-1 != (opt = getopt_long(argc, argv, opt_str, long_opts, NULL)))
{
res = hanlde_one_option(opt, optarg, config);
if (0 != res)
return res;
}
return 0;
}
static int
hanlde_one_option(int opt, char *optarg, t_ping_config *config)
{
const t_option_descriptor *desc = find_option_handler(opt);
int res;
if ('?' == opt)
return 1;
if (NULL != desc)
{
res = desc->handler(optarg, config);
if (0 != res)
return res;
}
return 0; return 0;
} }
@ -27,3 +60,26 @@ init_config(t_ping_config *config)
config->timeout = DEFAULT_TIMEOUT; config->timeout = DEFAULT_TIMEOUT;
} }
static void
build_long_options(struct option *long_opts, size_t nb_opts,
const t_option_descriptor *src)
{
for (size_t i = 0; i < nb_opts; ++i) {
long_opts[i].name = src[i].long_opt;
long_opts[i].has_arg = src[i].has_arg;
long_opts[i].flag = NULL;
long_opts[i].val = src[i].short_opt;
}
long_opts[nb_opts] = (struct option){0};
}
static const t_option_descriptor
*find_option_handler(int opt)
{
for (size_t i = 0; i < g_options_len; ++i)
{
if (g_options[i].short_opt == opt)
return &g_options[i];
}
return NULL;
}

View file

@ -1,6 +1,15 @@
#include <stdio.h> #include <stdio.h>
#include <libgen.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "ft_ping.h" #include "ft_ping.h"
#include "version_gen.h"
/* Forward declarations */
static int init_prog_name(char *name);
t_prog_name g_prog_name = {NULL, NULL};
/* -------------------- */
int int
main(int argc, char **argv) main(int argc, char **argv)
@ -8,11 +17,26 @@ main(int argc, char **argv)
t_ping_config config; t_ping_config config;
int ret; int ret;
if (0 != init_prog_name(argv[0]))
return EXIT_FAILURE;
ret = cli_parse_arguments(argc, argv, &config); ret = cli_parse_arguments(argc, argv, &config);
if (ret != 0) if (ret != 0)
return (ret < 0) ? EXIT_FAILURE : EXIT_SUCCESS; return (ret > 0) ? EXIT_FAILURE : EXIT_SUCCESS;
printf("PING: Not yet implemented\n"); printf("PING: Not yet implemented\n");
free(g_prog_name.alloc);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
static int
init_prog_name(char *name)
{
char *path_dup = strdup(name);
if (NULL == path_dup)
return 1;
g_prog_name.alloc = path_dup;
g_prog_name.name = basename(path_dup);
return 0;
}