From a858b596320a237cb0ea927e96b603d913ef1c09 Mon Sep 17 00:00:00 2001
From: Kubat <mael.martin31@gmail.com>
Date: Sun, 2 May 2021 10:38:41 +0200
Subject: [PATCH] LKT & CMD: Handle in a more cleaner way the options for cmd.h

Name those options 'env', as they are global options that are common to
all the commands (normally). Not using the POSIX env for now for that,
but they are setted in a same-fashion way:

lkt port=6601 queue 10
    \         \     \_ options passed to the command
     \         \______ the commands
      \_______________ the env, or global options
---
 .clang-format       |  1 +
 inc/lektor/cmd.h    | 46 ++++++++++++++++++++++--
 inc/lektor/common.h |  8 ++++-
 src/base/cmd.c      | 57 ++++++++++++++++++++++++++++++
 src/main/lkt.c      | 85 +++++++++++++++++++++------------------------
 5 files changed, 147 insertions(+), 50 deletions(-)

diff --git a/.clang-format b/.clang-format
index 6ba67db5..f292a0f2 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 39f27694..b37d64fe 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 0c391461..36e007e7 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 25715fea..2b3fae4e 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 b3fb82ff..b30af8bc 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);
 }
-- 
GitLab