cli: Add a new CLI wrapper

This new CLI wrapper introduces a command mode,  like we are used from
Git for instance. The first argument specifies the command mode, which
is `prog` for the classic flashprog CLI.  As an alternative to a first
argument,  it can be called as `flashprog-cmd`, `flashcmd`, or `fcmd`,
via symbolic links for instance. Splitting CLI functions will allow us
to add more CLI features, that can be developed independently from the
classic CLI.

For instance, flashprog could then be called like this:

  $ flashprog -p ch341a_spi
  $ fprog -p ch341a_spi

For the future "config" CLI, more aliases are possible, e.g.:

  $ flashprog config -p ch341a_spi
  $ flashprog-config -p ch341a_spi
  $ flashcfg -p ch341a_spi
  $ fconfig -p ch341a_spi

Change-Id: I98cb110b47ebce52daf2e0972fc4565ef9d40242
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/72988
diff --git a/Makefile b/Makefile
index 79601fc..a75c280 100644
--- a/Makefile
+++ b/Makefile
@@ -401,7 +401,7 @@
 ###############################################################################
 # Frontend related stuff.
 
-CLI_OBJS = cli_classic.o cli_output.o cli_common.o print.o
+CLI_OBJS = cli.o cli_classic.o cli_output.o cli_common.o print.o
 
 # By default version information will be fetched from Git if available.
 # Otherwise, versioninfo.inc stores the metadata required to build a
diff --git a/cli.c b/cli.c
new file mode 100644
index 0000000..7bf94cc
--- /dev/null
+++ b/cli.c
@@ -0,0 +1,108 @@
+/*
+ * This file is part of the flashprog project.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "flash.h"
+#include "cli.h"
+
+static const char *const command_prefixes[] = { "flashprog-", "flash", "f" };
+
+static const struct {
+	const char *name;
+	int (*main)(int argc, char *argv[]);
+} commands[] = {
+	{ "prog",		flashprog_classic_main },
+};
+
+static void usage(const char *const name)
+{
+	fprintf(stderr, "\nUsage: %s [<command>] [<argument>...]\n", name);
+	fprintf(stderr, "\nWhere <command> can be\n\n"
+			" prog                     Standard memory operations\n"
+			"                          (read/erase/write/verify)\n"
+			"\n"
+			"The default is 'prog'. See `%s <command> --help`\n"
+			"for further instructions.\n\n", name);
+	exit(1);
+}
+
+static int combine_argv01(char *argv[])
+{
+	const size_t len = strlen(argv[0]) + 1 + strlen(argv[1]) + 1;
+	char *const argv0 = malloc(len);
+	if (!argv0) {
+		fprintf(stderr, "Out of memory!\n");
+		return 1;
+	}
+	snprintf(argv0, len, "%s %s", argv[0], argv[1]);
+	argv[1] = argv0;
+	return 0;
+}
+
+int main(int argc, char *argv[])
+{
+	const char *cmd;
+	size_t i;
+
+	print_version();
+	print_banner();
+
+	if (argc < 1)
+		usage("flashprog");
+
+	/* Turn something like `./flashprog-cmd` into `flashprog-cmd`: */
+	const char *const slash = strrchr(argv[0], '/');
+	if (slash)
+		cmd = slash + 1;
+	else
+		cmd = argv[0];
+
+	if (strcmp(cmd, "flashprog")) {
+		/* Strip command prefix, i.e. `flashprog-cmd` becomes `cmd`: */
+		for (i = 0; i < ARRAY_SIZE(command_prefixes); ++i) {
+			if (!strncmp(cmd, command_prefixes[i],
+				     strlen(command_prefixes[i]))) {
+				cmd += strlen(command_prefixes[i]);
+				break;
+			}
+		}
+	}
+
+	/* Run `cmd` if found: */
+	for (i = 0; i < ARRAY_SIZE(commands); ++i) {
+		if (!strcmp(cmd, commands[i].name))
+			return commands[i].main(argc, argv);
+	}
+
+	/* Try to find command as first argument in argv[1]: */
+	for (i = 0; argc >= 2 && i < ARRAY_SIZE(commands); ++i) {
+		if (!strcmp(argv[1], commands[i].name)) {
+			/* Squash argv[0] into argv[1]: */
+			if (combine_argv01(argv))
+				return 1;
+			return commands[i].main(argc - 1, argv + 1);
+		}
+	}
+
+	/* Bail if first argument looks like an
+	   unknown command (i.e. not starting with `-'): */
+	if (argc >= 2 && argv[1][0] != '-')
+		usage(argv[0]);
+
+	/* We're still here? Fall back to classic cli: */
+	return flashprog_classic_main(argc, argv);
+}
diff --git a/cli_classic.c b/cli_classic.c
index 2f16ef6..26253dc 100644
--- a/cli_classic.c
+++ b/cli_classic.c
@@ -181,7 +181,7 @@
 	return true;
 }
 
-int main(int argc, char *argv[])
+int flashprog_classic_main(int argc, char *argv[])
 {
 	const struct flashchip *chip = NULL;
 	/* Probe for up to eight flash chips. */
diff --git a/flashprog.8.tmpl b/flashprog.8.tmpl
index 0598b84..54daef1 100644
--- a/flashprog.8.tmpl
+++ b/flashprog.8.tmpl
@@ -44,6 +44,14 @@
 .SH NAME
 flashprog \- detect, read, write, verify and erase flash chips
 .SH SYNOPSIS
+Flashprog supports multiple command modes:
+.sp
+.B flashprog \fR([\fBprog\fR])
+.sp
+With
+.B prog
+being the default and described in this manual.
+.sp
 .B flashprog \fR[\fB\-h\fR|\fB\-R\fR|\fB\-L\fR|\fB\-z\fR|
           \fB\-p\fR <programmername>[:<parameters>] [\fB\-c\fR <chipname>]
             (\fB\-\-flash\-name\fR|\fB\-\-flash\-size\fR|
diff --git a/include/cli.h b/include/cli.h
index b7a0340..1ba442b 100644
--- a/include/cli.h
+++ b/include/cli.h
@@ -62,4 +62,6 @@
 
 int cli_init(void);
 
+int flashprog_classic_main(int argc, char *argv[]);
+
 #endif
diff --git a/meson.build b/meson.build
index 7321169..f308ec6 100644
--- a/meson.build
+++ b/meson.build
@@ -605,6 +605,7 @@
   executable(
     'flashprog',
     files(
+      'cli.c',
       'cli_classic.c',
       'cli_common.c',
       'cli_output.c',