diff options
-rw-r--r-- | autistdraw.c | 68 | ||||
-rw-r--r-- | utils.c | 349 |
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; @@ -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); +} |