diff --git a/.clang-format b/.clang-format index 6ba67db58dcae48b7df410b5f441f625a8809aec..f292a0f219069123dea60222d9fc5c4b6e9e7efd 100644 --- a/.clang-format +++ b/.clang-format @@ -62,6 +62,7 @@ ForEachMacros: - 'FOR_EVER' - 'FOR_EVER_IF' - 'FOR_EVER_UNTIL' + - 'FOR_EACH_FLAT_LIST_ITEM' IncludeCategories: - Regex: '.*' diff --git a/inc/lektor/cmd.h b/inc/lektor/cmd.h index 39f27694f79ec856f4f4156c786c21713b7a7526..b37d64fe9720ebfff20526feffa2dd831d11114b 100644 --- a/inc/lektor/cmd.h +++ b/inc/lektor/cmd.h @@ -15,29 +15,69 @@ extern "C" { { \ .name = NULL, .call = func \ } +#define CMD_ENV_NULL \ + { \ + .name = NULL, .value = NULL, .handler = NULL, \ + } +#define CMD_ENV(n, hndl) \ + { \ + .name = n, .handler = hndl \ + } +#define CMD_ENV_DEFAULT(n, v, hndl) \ + { \ + .name = n, .value = v, .handler = hndl \ + } +/* The arguments passed to a command */ struct cmd_args { int argc; /* The number of arguments passed. */ const char **argv; /* An array of argument passed. */ }; +/* Callback to call on command, takes the arguments */ typedef void (*cmd_callback)(struct cmd_args *); +/* Callback for the 'apply_env' function. */ +typedef void (*cmd_env_callback)(const char *name, const char *value, void *user); + +/* The 'option' command struct, this is how a new command is registered for the + * cmd_parse command. The list must end with the CMD_OPT_NULL or + * CMD_OPT_DEFAULT macros (set the NULL at the end of a list with no + * length...). The command names MUST NOT include the = or : characters! They + * also must be distinguishable, i.e. one command name must not be simply + * included in another name: + * - play, playlist => bad, because play is included in playlist + * - status, start => good, no inclusion between the names */ struct cmd_opt { const char *name; cmd_callback call; }; +/* Options or env of the command. Don't depend on the POSIX env variables for + * now and do it ourself. */ +struct cmd_env { + const char *name; + const char *value; + cmd_env_callback handler; +}; + +/* Parse the 'env' of the command, i.e. the options before the command, like: + * ./a.out opt1=foo opt2=bar sub-command com1 + * The cmd_apply function is here to do the handle stuff for the options to be + * registered in a user specific way (callback + user pointer). */ +void cmd_parse_env(struct cmd_env *env, int *argc, const char ***argv); +void cmd_apply_env(struct cmd_env *env, void *user); + /* Parse the command line with the list of options. No parameter may be NULL. - Does not return. */ + * Does not return. */ EXIT_FUNCTION cmd_parse(struct cmd_opt *opts, int argc, const char **argv); /* Must be setted for the lkt_cmd_parse function to display the correct help - in case of errors. */ + * in case of errors. */ void cmd_set_executable_name(const char *); const char *cmd_get_executable_name(void); -/* Prints the help and exit */ +/* Prints the help if possible and exit. */ EXIT_FUNCTION print_help(void); #if defined(__cplusplus) diff --git a/inc/lektor/common.h b/inc/lektor/common.h index 0c3914611297ef358e0863ead361ae5079b6cb86..36e007e73b248adead994bcba72b54fa9017454b 100644 --- a/inc/lektor/common.h +++ b/inc/lektor/common.h @@ -58,8 +58,11 @@ extern EXIT_FUNCTION ___not_implemented(const char *func, char *file, int line); err_flag = errno != 0 || endptr == str; \ }) +/* Get a generic function pointer from any function pointer. */ #define FUNCTION_POINTER(func) ((void (*)(void))(func)) -#define FOR_EVER for (;;) + +/* Forever macros */ +#define FOR_EVER for (;;) #define FOR_EVER_IF(expr) \ FOR_EVER { \ if (!(expr)) \ @@ -67,6 +70,9 @@ extern EXIT_FUNCTION ___not_implemented(const char *func, char *file, int line); } #define FOR_EVER_UNTIL(expr) FOR_EVER_IF (!(expr)) +/* For each, for flat-list. Need to be null terminated on the normally non-null field. */ +#define FOR_EACH_FLAT_LIST_ITEM(it, nnull_field) for (; (it)->nnull_field != NULL; it++) + /* Custom defined assert. */ extern void (*___lkt_assert)(const char *file, int line, const char *func, const char *msg); #ifdef assert diff --git a/src/base/cmd.c b/src/base/cmd.c index 25715fea4eb46520dae098c60026af56838cbd9d..2b3fae4e36adee6b277385c853f5b60f3b72d810 100644 --- a/src/base/cmd.c +++ b/src/base/cmd.c @@ -59,6 +59,63 @@ print_help(void) } } +PRIVATE_FUNCTION size_t +___get_max_options(struct cmd_env *env) +{ + struct cmd_env *it = env; + size_t ret = 0; + + FOR_EACH_FLAT_LIST_ITEM (it, name) { + ret++; + } + + return ret; +} + +PRIVATE_FUNCTION bool +___find_and_set_option(struct cmd_env *env, const char *name, size_t name_len, const char *value) +{ + struct cmd_env *it = env; + + FOR_EACH_FLAT_LIST_ITEM (it, name) { + if (STR_NMATCH(it->name, name, name_len)) { + it->value = value; + return true; + } + } + + return false; +} + +void +cmd_apply_env(struct cmd_env *env, void *user) +{ + struct cmd_env *it = env; + + FOR_EACH_FLAT_LIST_ITEM (it, name) { + it->handler(it->name, it->value, user); + } +} + +void +cmd_parse_env(struct cmd_env *env, int *argc, const char ***argv) +{ + assert((*argv) >= 0); + + size_t max_options = ___get_max_options(env); + size_t len = 0; + int got = 0; + + for (size_t i = 1; i < (size_t)(*argc) && i < max_options; ++i) { + len = strcspn((*argv)[i], "=:"); + got += ___find_and_set_option(env, (*argv)[i], len, (*argv)[i] + len + 1); + } + + /* Skip the gotten options from the command line. */ + *argv = &(*argv)[got + 1]; + *argc = (*argc) - (got + 1); +} + EXIT_FUNCTION cmd_parse(struct cmd_opt *opts, int argc, const char **argv) { diff --git a/src/main/lkt.c b/src/main/lkt.c index b3fb82ff06546c1c7a4bc6ff263585ee3676b919..b30af8bc447a1c62171b4ea125c572af122a5509 100644 --- a/src/main/lkt.c +++ b/src/main/lkt.c @@ -45,7 +45,7 @@ row_printer row_print = NULL; #define LKT_MAX_OPTIONS 4 /* Number of options that can be passed to lkt before the commands. */ static const char *host; static const char *port; -static const char *password; +static const char *pwd; static const char *row_format; /* Default queue length to query */ @@ -54,7 +54,7 @@ static const char *LKT_QUEUE_DEFAULT[] = { "10" }; /* Small screen column count, to autoset the 'pretty' printer. The standard * 80x25 terminal is small! */ #define LKT_SMALL_SCREEN_THRESHOLD 82 -#define IS_SMALL_SCREEN (___column_count <= LKT_SMALL_SCREEN_THRESHOLD) +#define IS_SMALL_SCREEN (___column_count <= LKT_SMALL_SCREEN_THRESHOLD) static unsigned ___column_count = 0; CONSTRUCTOR_FUNCTION setup_column_count(void) @@ -108,7 +108,7 @@ ___row_printer_pretty(char *line) /* Get the author */ char *author = strstr(title, " ["); - *author = '\0'; + *author = '\0'; author += strlen(" ["); FAIL_IF(author == NULL, "Invalid row, faild to find the author"); last = strstr(author, "]"); @@ -410,10 +410,10 @@ just_send(queue_flatten__, "__flat\n"); EXIT_FUNCTION simple_send_with_password__(const char *cmd) { - FAIL_UNLESS(password, "Password not provided"); + FAIL_UNLESS(pwd, "Password not provided"); FILE *sock = lkt_connect(); write_socket(sock, "command_list_begin\n"); - write_socket(sock, "password %s\n", password); + write_socket(sock, "password %s\n", pwd); write_socket(sock, "%s\n", cmd); write_socket(sock, "command_list_end\n"); exit(EXIT_SUCCESS); @@ -436,13 +436,13 @@ kill__(struct cmd_args *args) EXIT_FUNCTION rescan_or_update__(struct cmd_args *args, const char *cmd) { - FAIL_UNLESS(password, "Password not provided"); + FAIL_UNLESS(pwd, "Password not provided"); FILE *sock = lkt_connect(); /* All the db */ if (args->argc == 0) { write_socket(sock, "command_list_begin\n"); - write_socket(sock, "password %s\n", password); + write_socket(sock, "password %s\n", pwd); write_socket(sock, "%s\n", cmd); write_socket(sock, "command_list_end\n"); } @@ -453,7 +453,7 @@ rescan_or_update__(struct cmd_args *args, const char *cmd) memset(buff, 0, LKT_MESSAGE_MAX * sizeof(char)); FAIL_IF(lkt_get_query_type(args, buff, LKT_MESSAGE_MAX), "Query is not valid"); write_socket(sock, "command_list_begin\n"); - write_socket(sock, "password %s\n", password); + write_socket(sock, "password %s\n", pwd); write_socket(sock, "%s %s\n", cmd, buff); write_socket(sock, "command_list_end\n"); } @@ -1314,33 +1314,38 @@ sigpipe__(int sig) exit(EXIT_FAILURE); } -/* Functions declarations. */ +/* Handle the options switches */ -PRIVATE_FUNCTION void -parse_args(args_t *args, int argc, const char **argv) -{ - int i, got = 0; - size_t len; - - for (i = 1; i < argc && i < LKT_MAX_OPTIONS; ++i) { - len = strcspn(argv[i], "=:"); - -#define ___get_args(name) \ - if (STR_NMATCH(#name, argv[i], len)) { \ - args->name = (argv[i] + len + 1); \ - ++got; \ - } - ___get_args(host); - ___get_args(port); - ___get_args(pwd); - ___get_args(row_format); -#undef ___get_args +#define ___DEFINE_SETTER(what) \ + PRIVATE_FUNCTION void ___USE_SETTER(what)(const char UNUSED *name, const char *value, void UNUSED *user) \ + { \ + what = value; \ } +#define ___USE_SETTER(what) ___set_env_##what + +___DEFINE_SETTER(host); +___DEFINE_SETTER(port); +___DEFINE_SETTER(pwd); - args->argv = &argv[got + 1]; - args->argc = argc - (got + 1); +PRIVATE_FUNCTION void ___USE_SETTER(row_format)(const char UNUSED *name, const char *value, void UNUSED *user) +{ + row_format = value; + setup_row_printer(); } +// clang-format off +static struct cmd_env env_[] = { + CMD_ENV_DEFAULT("host", "localhost", ___USE_SETTER(host)), + CMD_ENV_DEFAULT("port", "6600", ___USE_SETTER(port)), + CMD_ENV("pwd", ___USE_SETTER(pwd)), + CMD_ENV("row_format", ___USE_SETTER(row_format)), + CMD_ENV_NULL, +}; +// clang-format on + +#undef ___USE_SETTER +#undef ___DEFINE_SETTER + int main(int argc, const char **argv) { @@ -1352,20 +1357,8 @@ main(int argc, const char **argv) if (signal(SIGPIPE, sigpipe__)) LOG_ERROR("SYS", "Failed to install handler for SIGPIPE signal (you may be using php...)"); - args_t args = { - .host = "localhost", - .port = "6600", - }; - - parse_args(&args, argc, argv); - - host = args.host; - port = args.port; - password = args.pwd; - row_format = args.row_format; - - setup_row_printer(); - - /* Communication with lektor. */ - cmd_parse(options_, args.argc, args.argv); + /* Parse */ + cmd_parse_env(env_, &argc, &argv); + cmd_apply_env(env_, NULL); + cmd_parse(options_, argc, argv); }