aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autistdraw.c68
-rw-r--r--utils.c349
2 files changed, 400 insertions, 17 deletions
diff --git a/autistdraw.c b/autistdraw.c
index 191570a..c42f9bd 100644
--- a/autistdraw.c
+++ b/autistdraw.c
@@ -32,6 +32,7 @@
#include "termo.h"
#include "config.h"
+#include "utils.c"
#define PALETTE_WIDTH 9 ///< Width of the palette
#define TOP_BAR_CUTOFF 3 ///< Height of the top bar
@@ -226,7 +227,7 @@ make_place_for_point (app_context_t *app, int x, int y)
while (new_bitmap_y + new_bitmap_h <= y)
new_bitmap_h += BITMAP_BLOCK_SIZE;
- uint8_t *new_bitmap = calloc (new_bitmap_w * new_bitmap_h,
+ uint8_t *new_bitmap = xcalloc (new_bitmap_w * new_bitmap_h,
sizeof *new_bitmap);
if (app->bitmap)
{
@@ -539,15 +540,6 @@ on_key (app_context_t *app, termo_key_t *key)
return true;
}
-static bool
-xstrtoul (unsigned long *out, const char *s, int base)
-{
- char *end;
- errno = 0;
- *out = strtoul (s, &end, base);
- return errno == 0 && !*end && end != s;
-}
-
static void
on_winch (uv_signal_t *handle, int signum)
{
@@ -609,15 +601,60 @@ on_tty_readable (uv_poll_t *handle, int status, int events)
on_key_timer, termo_get_waittime (app->tk), 0);
}
+static void
+parse_program_arguments (app_context_t *app, int argc, char **argv)
+{
+ (void) app;
+
+ static const struct opt opts[] =
+ {
+ { 'h', "help", NULL, 0, "display this help and exit" },
+ { 'V', "version", NULL, 0, "output version information and exit" },
+ { 0, NULL, NULL, 0, NULL }
+ };
+
+ struct opt_handler oh;
+ opt_handler_init (&oh, argc, argv, opts,
+ NULL, "Terminal drawing for NEET autists.");
+
+ int c;
+ while ((c = opt_handler_get (&oh)) != -1)
+ switch (c)
+ {
+ case 'h':
+ opt_handler_usage (&oh, stdout);
+ exit (EXIT_SUCCESS);
+ case 'V':
+ printf (PROJECT_NAME " " PROJECT_VERSION "\n");
+ exit (EXIT_SUCCESS);
+ default:
+ fprintf (stderr, "%s: %s", "error", "wrong options");
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc)
+ {
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ opt_handler_free (&oh);
+}
+
int
main (int argc, char *argv[])
{
- (void) argc;
- (void) argv;
-
TERMO_CHECK_VERSION;
setlocale (LC_CTYPE, "");
+ app_context_t app;
+ app_init (&app);
+ parse_program_arguments (&app, argc, argv);
+
termo_t *tk = termo_new (STDIN_FILENO, NULL, 0);
if (!tk)
{
@@ -625,6 +662,7 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
+ app.tk = tk;
termo_set_mouse_proto (tk, termo_guess_mouse_proto (tk));
termo_set_mouse_tracking_mode (tk, TERMO_MOUSE_TRACKING_DRAG);
@@ -635,10 +673,6 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
- app_context_t app;
- app_init (&app);
- app.tk = tk;
-
uv_loop_t *loop = uv_default_loop ();
loop->data = &app;
diff --git a/utils.c b/utils.c
new file mode 100644
index 0000000..27d9e8d
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,349 @@
+/*
+ * utils.c: utilities
+ *
+ * Copyright (c) 2014, Přemysl Janouch <p.janouch@gmail.com>
+ * All rights reserved.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include <getopt.h>
+
+#if defined __GNUC__
+#define ATTRIBUTE_PRINTF(x, y) __attribute__ ((format (printf, x, y)))
+#else // ! __GNUC__
+#define ATTRIBUTE_PRINTF(x, y)
+#endif // ! __GNUC__
+
+#if defined __GNUC__ && __GNUC__ >= 4
+#define ATTRIBUTE_SENTINEL __attribute__ ((sentinel))
+#else // ! __GNUC__ || __GNUC__ < 4
+#define ATTRIBUTE_SENTINEL
+#endif // ! __GNUC__ || __GNUC__ < 4
+
+#define N_ELEMENTS(a) (sizeof (a) / sizeof ((a)[0]))
+
+// --- Safe memory management --------------------------------------------------
+
+// When a memory allocation fails and we need the memory, we're usually pretty
+// much fucked. Use the non-prefixed versions when there's a legitimate
+// worry that an unrealistic amount of memory may be requested for allocation.
+
+static void *
+xmalloc (size_t n)
+{
+ void *p = malloc (n);
+ if (!p)
+ {
+ perror ("malloc");
+ exit (EXIT_FAILURE);
+ }
+ return p;
+}
+
+static void *
+xcalloc (size_t n, size_t m)
+{
+ void *p = calloc (n, m);
+ if (!p && n && m)
+ {
+ perror ("calloc");
+ exit (EXIT_FAILURE);
+ }
+ return p;
+}
+
+static void *
+xrealloc (void *o, size_t n)
+{
+ void *p = realloc (o, n);
+ if (!p && n)
+ {
+ perror ("realloc");
+ exit (EXIT_FAILURE);
+ }
+ return p;
+}
+
+// --- Dynamically allocated strings -------------------------------------------
+
+// Basically a string builder to abstract away manual memory management.
+
+struct str
+{
+ char *str; ///< String data, null terminated
+ size_t alloc; ///< How many bytes are allocated
+ size_t len; ///< How long the string actually is
+};
+
+/// We don't care about allocations that are way too large for the content, as
+/// long as the allocation is below the given threshold. (Trivial heuristics.)
+#define STR_SHRINK_THRESHOLD (1 << 20)
+
+static void
+str_init (struct str *self)
+{
+ self->alloc = 16;
+ self->len = 0;
+ self->str = strcpy (xmalloc (self->alloc), "");
+}
+
+static void
+str_free (struct str *self)
+{
+ free (self->str);
+ self->str = NULL;
+ self->alloc = 0;
+ self->len = 0;
+}
+
+static char *
+str_steal (struct str *self)
+{
+ char *str = self->str;
+ self->str = NULL;
+ str_free (self);
+ return str;
+}
+
+static void
+str_ensure_space (struct str *self, size_t n)
+{
+ // We allocate at least one more byte for the terminating null character
+ size_t new_alloc = self->alloc;
+ while (new_alloc <= self->len + n)
+ new_alloc <<= 1;
+ if (new_alloc != self->alloc)
+ self->str = xrealloc (self->str, (self->alloc = new_alloc));
+}
+
+static void
+str_append_data (struct str *self, const void *data, size_t n)
+{
+ str_ensure_space (self, n);
+ memcpy (self->str + self->len, data, n);
+ self->len += n;
+ self->str[self->len] = '\0';
+}
+
+static void
+str_append_c (struct str *self, char c)
+{
+ str_append_data (self, &c, 1);
+}
+
+static void
+str_append (struct str *self, const char *s)
+{
+ str_append_data (self, s, strlen (s));
+}
+
+static int
+str_append_vprintf (struct str *self, const char *fmt, va_list va)
+{
+ va_list ap;
+ int size;
+
+ va_copy (ap, va);
+ size = vsnprintf (NULL, 0, fmt, ap);
+ va_end (ap);
+
+ if (size < 0)
+ return -1;
+
+ va_copy (ap, va);
+ str_ensure_space (self, size);
+ size = vsnprintf (self->str + self->len, self->alloc - self->len, fmt, ap);
+ va_end (ap);
+
+ if (size > 0)
+ self->len += size;
+
+ return size;
+}
+
+static int
+str_append_printf (struct str *self, const char *fmt, ...)
+ ATTRIBUTE_PRINTF (2, 3);
+
+static int
+str_append_printf (struct str *self, const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start (ap, fmt);
+ int size = str_append_vprintf (self, fmt, ap);
+ va_end (ap);
+ return size;
+}
+
+// --- Utilities ---------------------------------------------------------------
+
+static bool
+xstrtoul (unsigned long *out, const char *s, int base)
+{
+ char *end;
+ errno = 0;
+ *out = strtoul (s, &end, base);
+ return errno == 0 && !*end && end != s;
+}
+
+// --- Option handler ----------------------------------------------------------
+
+// Simple wrapper for the getopt_long API to make it easier to use and maintain.
+
+#define OPT_USAGE_ALIGNMENT_COLUMN 30 ///< Alignment for option descriptions
+
+enum
+{
+ OPT_OPTIONAL_ARG = (1 << 0), ///< The argument is optional
+ OPT_LONG_ONLY = (1 << 1) ///< Ignore the short name in opt_string
+};
+
+// All options need to have both a short name, and a long name. The short name
+// is what is returned from opt_handler_get(). It is possible to define a value
+// completely out of the character range combined with the OPT_LONG_ONLY flag.
+//
+// When `arg_hint' is defined, the option is assumed to have an argument.
+
+struct opt
+{
+ int short_name; ///< The single-letter name
+ const char *long_name; ///< The long name
+ const char *arg_hint; ///< Option argument hint
+ int flags; ///< Option flags
+ const char *description; ///< Option description
+};
+
+struct opt_handler
+{
+ int argc; ///< The number of program arguments
+ char **argv; ///< Program arguments
+
+ const char *arg_hint; ///< Program arguments hint
+ const char *description; ///< Description of the program
+
+ const struct opt *opts; ///< The list of options
+ size_t opts_len; ///< The length of the option array
+
+ struct option *options; ///< The list of options for getopt
+ char *opt_string; ///< The `optstring' for getopt
+};
+
+static void
+opt_handler_free (struct opt_handler *self)
+{
+ free (self->options);
+ free (self->opt_string);
+}
+
+static void
+opt_handler_init (struct opt_handler *self, int argc, char **argv,
+ const struct opt *opts, const char *arg_hint, const char *description)
+{
+ memset (self, 0, sizeof *self);
+ self->argc = argc;
+ self->argv = argv;
+ self->arg_hint = arg_hint;
+ self->description = description;
+
+ size_t len = 0;
+ for (const struct opt *iter = opts; iter->long_name; iter++)
+ len++;
+
+ self->opts = opts;
+ self->opts_len = len;
+ self->options = xcalloc (len + 1, sizeof *self->options);
+
+ struct str opt_string;
+ str_init (&opt_string);
+
+ for (size_t i = 0; i < len; i++)
+ {
+ const struct opt *opt = opts + i;
+ struct option *mapped = self->options + i;
+
+ mapped->name = opt->long_name;
+ if (!opt->arg_hint)
+ mapped->has_arg = no_argument;
+ else if (opt->flags & OPT_OPTIONAL_ARG)
+ mapped->has_arg = optional_argument;
+ else
+ mapped->has_arg = required_argument;
+ mapped->val = opt->short_name;
+
+ if (opt->flags & OPT_LONG_ONLY)
+ continue;
+
+ str_append_c (&opt_string, opt->short_name);
+ if (opt->arg_hint)
+ {
+ str_append_c (&opt_string, ':');
+ if (opt->flags & OPT_OPTIONAL_ARG)
+ str_append_c (&opt_string, ':');
+ }
+ }
+
+ self->opt_string = str_steal (&opt_string);
+}
+
+static void
+opt_handler_usage (struct opt_handler *self, FILE *stream)
+{
+ struct str usage;
+ str_init (&usage);
+
+ str_append_printf (&usage, "Usage: %s [OPTION]... %s\n",
+ self->argv[0], self->arg_hint ? self->arg_hint : "");
+ str_append_printf (&usage, "%s\n\n", self->description);
+
+ for (size_t i = 0; i < self->opts_len; i++)
+ {
+ struct str row;
+ str_init (&row);
+
+ const struct opt *opt = self->opts + i;
+ if (!(opt->flags & OPT_LONG_ONLY))
+ str_append_printf (&row, " -%c, ", opt->short_name);
+ else
+ str_append (&row, " ");
+ str_append_printf (&row, "--%s", opt->long_name);
+ if (opt->arg_hint)
+ str_append_printf (&row, (opt->flags & OPT_OPTIONAL_ARG)
+ ? " [%s]" : " %s", opt->arg_hint);
+
+ // TODO: keep the indent if there are multiple lines
+ if (row.len + 2 <= OPT_USAGE_ALIGNMENT_COLUMN)
+ {
+ str_append (&row, " ");
+ str_append_printf (&usage, "%-*s%s\n",
+ OPT_USAGE_ALIGNMENT_COLUMN, row.str, opt->description);
+ }
+ else
+ str_append_printf (&usage, "%s\n%-*s%s\n", row.str,
+ OPT_USAGE_ALIGNMENT_COLUMN, "", opt->description);
+
+ str_free (&row);
+ }
+
+ fputs (usage.str, stream);
+ str_free (&usage);
+}
+
+static int
+opt_handler_get (struct opt_handler *self)
+{
+ return getopt_long (self->argc, self->argv,
+ self->opt_string, self->options, NULL);
+}