commit 0206052b5660cb77cdd0a0ac3c83dd4c3d996007
parent c999204365695799c9b7d79f4973d307421afecb
Author: William Casarin <jb55@jb55.com>
Date: Mon, 9 Jul 2018 15:48:55 -0700
started on positioning + command structure
Diffstat:
17 files changed, 1204 insertions(+), 24 deletions(-)
diff --git a/Makefile b/Makefile
@@ -5,10 +5,12 @@ LDFLAGS=-lXft -lfontconfig
BIN=cmdtree
DEPS = $(wildcard ccan/*/*.c)
+DEPS += $(wildcard ccan/tal/str/*.c)
OBJS += drw.o
OBJS += util.o
OBJS += cmdtree.o
+OBJS += command.o
OBJS += $(DEPS:.c=.o)
diff --git a/TODO.org b/TODO.org
@@ -1,2 +1,7 @@
+* TODO command struct test data
+
* TODO column layout
+
+* DONE vertical layout
+CLOSED: [2018-07-09 Mon 15:25]
diff --git a/ccan/tal/str/LICENSE b/ccan/tal/str/LICENSE
@@ -0,0 +1 @@
+../../../licenses/BSD-MIT+
\ No newline at end of file
diff --git a/ccan/tal/str/_info b/ccan/tal/str/_info
@@ -0,0 +1,59 @@
+#include "config.h"
+#include <stdio.h>
+#include <string.h>
+
+/**
+ * tal/str - string helper routines which use tal
+ *
+ * This is a grab bag of functions for string operations, designed to enhance
+ * the standard string.h; these are separated from the non-tal-needing
+ * string utilities in "str.h".
+ *
+ * Example:
+ * #include <ccan/tal/str/str.h>
+ * #include <ccan/tal/grab_file/grab_file.h>
+ * #include <err.h>
+ *
+ * // Dumb demo program to double-linespace a file.
+ * int main(int argc, char *argv[])
+ * {
+ * char *textfile;
+ * char **lines;
+ *
+ * if (argc > 2)
+ * errx(1, "Takes 0 or 1 arguments");
+ * // Grab lines in file.
+ * textfile = grab_file(NULL, argv[1]);
+ * if (!textfile)
+ * err(1, "Failed reading %s", argv[1]);
+ * lines = tal_strsplit(textfile, textfile, "\n", STR_EMPTY_OK);
+ *
+ * // Join them back together with two linefeeds.
+ * printf("%s", tal_strjoin(textfile, lines, "\n\n", STR_TRAIL));
+ *
+ * // Free everything, just because we can.
+ * tal_free(textfile);
+ * return 0;
+ * }
+ *
+ * License: BSD-MIT
+ * Author: Rusty Russell <rusty@rustcorp.com.au>
+ */
+int main(int argc, char *argv[])
+{
+ if (argc != 2)
+ return 1;
+
+ if (strcmp(argv[1], "depends") == 0) {
+ printf("ccan/str\n");
+#ifdef TAL_USE_TALLOC
+ printf("ccan/tal/talloc\n");
+#else
+ printf("ccan/tal\n");
+#endif
+ printf("ccan/take\n");
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/ccan/tal/str/str.c b/ccan/tal/str/str.c
@@ -0,0 +1,309 @@
+/* Licensed under BSD-MIT - see LICENSE file for details */
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+#include "str.h"
+#include <sys/types.h>
+#include <regex.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ccan/str/str.h>
+
+char *tal_strdup(const tal_t *ctx, const char *p)
+{
+ /* We have to let through NULL for take(). */
+ return tal_dup_(ctx, p, 1, p ? strlen(p) + 1: 1, 0, false,
+ TAL_LABEL(char, "[]"));
+}
+
+char *tal_strndup(const tal_t *ctx, const char *p, size_t n)
+{
+ size_t len;
+ char *ret;
+
+ /* We have to let through NULL for take(). */
+ if (likely(p))
+ len = strnlen(p, n);
+ else
+ len = n;
+
+ ret = tal_dup_(ctx, p, 1, len, 1, false, TAL_LABEL(char, "[]"));
+ if (ret)
+ ret[len] = '\0';
+ return ret;
+}
+
+char *tal_fmt(const tal_t *ctx, const char *fmt, ...)
+{
+ va_list ap;
+ char *ret;
+
+ va_start(ap, fmt);
+ ret = tal_vfmt(ctx, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+static bool do_vfmt(char **buf, size_t off, const char *fmt, va_list ap)
+{
+ /* A decent guess to start. */
+ size_t max = strlen(fmt) * 2 + 1;
+ bool ok;
+
+ for (;;) {
+ va_list ap2;
+ int ret;
+
+ if (!tal_resize(buf, off + max)) {
+ ok = false;
+ break;
+ }
+
+ va_copy(ap2, ap);
+ ret = vsnprintf(*buf + off, max, fmt, ap2);
+ va_end(ap2);
+
+ if (ret < max) {
+ ok = true;
+ break;
+ }
+ max *= 2;
+ }
+
+ if (taken(fmt))
+ tal_free(fmt);
+ return ok;
+}
+
+char *tal_vfmt(const tal_t *ctx, const char *fmt, va_list ap)
+{
+ char *buf;
+
+ if (!fmt && taken(fmt))
+ return NULL;
+
+ /* A decent guess to start. */
+ buf = tal_arr(ctx, char, strlen(fmt) * 2);
+ if (!do_vfmt(&buf, 0, fmt, ap))
+ buf = tal_free(buf);
+ return buf;
+}
+
+bool tal_append_vfmt(char **baseptr, const char *fmt, va_list ap)
+{
+ if (!fmt && taken(fmt))
+ return false;
+
+ return do_vfmt(baseptr, strlen(*baseptr), fmt, ap);
+}
+
+bool tal_append_fmt(char **baseptr, const char *fmt, ...)
+{
+ va_list ap;
+ bool ret;
+
+ va_start(ap, fmt);
+ ret = tal_append_vfmt(baseptr, fmt, ap);
+ va_end(ap);
+
+ return ret;
+}
+
+char *tal_strcat(const tal_t *ctx, const char *s1, const char *s2)
+{
+ size_t len1, len2;
+ char *ret;
+
+ if (unlikely(!s2) && taken(s2)) {
+ if (taken(s1))
+ tal_free(s1);
+ return NULL;
+ }
+ /* We have to let through NULL for take(). */
+ len1 = s1 ? strlen(s1) : 0;
+ len2 = strlen(s2);
+
+ /* We use tal_dup_ here to avoid attaching a length property. */
+ ret = tal_dup_(ctx, s1, 1, len1, len2 + 1, false,
+ TAL_LABEL(char, "[]"));
+ if (likely(ret))
+ memcpy(ret + len1, s2, len2 + 1);
+
+ if (taken(s2))
+ tal_free(s2);
+ return ret;
+}
+
+char **tal_strsplit(const tal_t *ctx,
+ const char *string, const char *delims, enum strsplit flags)
+{
+ char **parts, *str;
+ size_t max = 64, num = 0;
+
+ parts = tal_arr(ctx, char *, max + 1);
+ if (unlikely(!parts)) {
+ if (taken(string))
+ tal_free(string);
+ if (taken(delims))
+ tal_free(delims);
+ return NULL;
+ }
+ str = tal_strdup(parts, string);
+ if (unlikely(!str))
+ goto fail;
+ if (unlikely(!delims) && is_taken(delims))
+ goto fail;
+
+ if (flags == STR_NO_EMPTY)
+ str += strspn(str, delims);
+
+ while (*str != '\0') {
+ size_t len = strcspn(str, delims), dlen;
+
+ parts[num] = str;
+ dlen = strspn(str + len, delims);
+ parts[num][len] = '\0';
+ if (flags == STR_EMPTY_OK && dlen)
+ dlen = 1;
+ str += len + dlen;
+ if (++num == max && !tal_resize(&parts, max*=2 + 1))
+ goto fail;
+ }
+ parts[num] = NULL;
+
+ /* Ensure that tal_count() is correct. */
+ if (unlikely(!tal_resize(&parts, num+1)))
+ goto fail;
+
+ if (taken(delims))
+ tal_free(delims);
+ return parts;
+
+fail:
+ tal_free(parts);
+ if (taken(delims))
+ tal_free(delims);
+ return NULL;
+}
+
+char *tal_strjoin(const tal_t *ctx,
+ char *strings[], const char *delim, enum strjoin flags)
+{
+ unsigned int i;
+ char *ret = NULL;
+ size_t totlen = 0, dlen;
+
+ if (unlikely(!strings) && is_taken(strings))
+ goto fail;
+
+ if (unlikely(!delim) && is_taken(delim))
+ goto fail;
+
+ dlen = strlen(delim);
+ ret = tal_arr(ctx, char, dlen*2+1);
+ if (!ret)
+ goto fail;
+
+ ret[0] = '\0';
+ for (i = 0; strings[i]; i++) {
+ size_t len = strlen(strings[i]);
+
+ if (flags == STR_NO_TRAIL && !strings[i+1])
+ dlen = 0;
+ if (!tal_resize(&ret, totlen + len + dlen + 1))
+ goto fail;
+ memcpy(ret + totlen, strings[i], len);
+ totlen += len;
+ memcpy(ret + totlen, delim, dlen);
+ totlen += dlen;
+ }
+ ret[totlen] = '\0';
+out:
+ if (taken(strings))
+ tal_free(strings);
+ if (taken(delim))
+ tal_free(delim);
+ return ret;
+fail:
+ ret = tal_free(ret);
+ goto out;
+}
+
+static size_t count_open_braces(const char *string)
+{
+#if 1
+ size_t num = 0, esc = 0;
+
+ while (*string) {
+ if (*string == '\\')
+ esc++;
+ else {
+ /* An odd number of \ means it's escaped. */
+ if (*string == '(' && (esc & 1) == 0)
+ num++;
+ esc = 0;
+ }
+ string++;
+ }
+ return num;
+#else
+ return strcount(string, "(");
+#endif
+}
+
+bool tal_strreg(const tal_t *ctx, const char *string, const char *regex, ...)
+{
+ size_t nmatch = 1 + count_open_braces(regex);
+ regmatch_t matches[nmatch];
+ regex_t r;
+ bool ret = false;
+ unsigned int i;
+ va_list ap;
+
+ if (unlikely(!regex) && is_taken(regex))
+ goto fail_no_re;
+
+ if (regcomp(&r, regex, REG_EXTENDED) != 0)
+ goto fail_no_re;
+
+ if (unlikely(!string) && is_taken(string))
+ goto fail;
+
+ if (regexec(&r, string, nmatch, matches, 0) != 0)
+ goto fail;
+
+ ret = true;
+ va_start(ap, regex);
+ for (i = 1; i < nmatch; i++) {
+ char **arg = va_arg(ap, char **);
+ if (arg) {
+ /* eg. ([a-z])? can give "no match". */
+ if (matches[i].rm_so == -1)
+ *arg = NULL;
+ else {
+ *arg = tal_strndup(ctx,
+ string + matches[i].rm_so,
+ matches[i].rm_eo
+ - matches[i].rm_so);
+ /* FIXME: If we fail, we set some and leak! */
+ if (!*arg) {
+ ret = false;
+ break;
+ }
+ }
+ }
+ }
+ va_end(ap);
+fail:
+ regfree(&r);
+fail_no_re:
+ if (taken(regex))
+ tal_free(regex);
+ if (taken(string))
+ tal_free(string);
+ return ret;
+}
diff --git a/ccan/tal/str/str.h b/ccan/tal/str/str.h
@@ -0,0 +1,192 @@
+/* Licensed under BSD-MIT - see LICENSE file for details */
+#ifndef CCAN_STR_TAL_H
+#define CCAN_STR_TAL_H
+#ifdef TAL_USE_TALLOC
+#include <ccan/tal/talloc/talloc.h>
+#else
+#include <ccan/tal/tal.h>
+#endif
+#include <string.h>
+#include <stdbool.h>
+
+/**
+ * tal_strdup - duplicate a string
+ * @ctx: NULL, or tal allocated object to be parent.
+ * @p: the string to copy (can be take()).
+ */
+char *tal_strdup(const tal_t *ctx, const char *p TAKES);
+
+/**
+ * tal_strndup - duplicate a limited amount of a string.
+ * @ctx: NULL, or tal allocated object to be parent.
+ * @p: the string to copy (can be take()).
+ * @n: the maximum length to copy.
+ *
+ * Always gives a nul-terminated string, with strlen() <= @n.
+ */
+char *tal_strndup(const tal_t *ctx, const char *p TAKES, size_t n);
+
+/**
+ * tal_fmt - allocate a formatted string
+ * @ctx: NULL, or tal allocated object to be parent.
+ * @fmt: the printf-style format (can be take()).
+ */
+char *tal_fmt(const tal_t *ctx, const char *fmt TAKES, ...) PRINTF_FMT(2,3);
+
+/**
+ * tal_vfmt - allocate a formatted string (va_list version)
+ * @ctx: NULL, or tal allocated object to be parent.
+ * @fmt: the printf-style format (can be take()).
+ * @va: the va_list containing the format args.
+ */
+char *tal_vfmt(const tal_t *ctx, const char *fmt TAKES, va_list ap)
+ PRINTF_FMT(2,0);
+
+/**
+ * tal_append_fmt - append a formatted string to a talloc string.
+ * @baseptr: a pointer to the tal string to be appended to.
+ * @fmt: the printf-style format (can be take()).
+ *
+ * Returns false on allocation failure.
+ */
+bool tal_append_fmt(char **baseptr, const char *fmt TAKES, ...) PRINTF_FMT(2,3);
+
+/**
+ * tal_append_vfmt - append a formatted string to a talloc string (va_list)
+ * @baseptr: a pointer to the tal string to be appended to.
+ * @fmt: the printf-style format (can be take()).
+ * @va: the va_list containing the format args.
+ *
+ * Returns false on allocation failure.
+ */
+bool tal_append_vfmt(char **baseptr, const char *fmt TAKES, va_list ap);
+
+/**
+ * tal_strcat - join two strings together
+ * @ctx: NULL, or tal allocated object to be parent.
+ * @s1: the first string (can be take()).
+ * @s2: the second string (can be take()).
+ */
+char *tal_strcat(const tal_t *ctx, const char *s1 TAKES, const char *s2 TAKES);
+
+enum strsplit {
+ STR_EMPTY_OK,
+ STR_NO_EMPTY
+};
+
+/**
+ * tal_strsplit - Split string into an array of substrings
+ * @ctx: the context to tal from (often NULL).
+ * @string: the string to split (can be take()).
+ * @delims: delimiters where lines should be split (can be take()).
+ * @flags: whether to include empty substrings.
+ *
+ * This function splits a single string into multiple strings.
+ *
+ * If @string is take(), the returned array will point into the
+ * mangled @string.
+ *
+ * Multiple delimiters result in empty substrings. By definition, no
+ * delimiters will appear in the substrings.
+ *
+ * The final char * in the array will be NULL, and tal_count() will
+ * return the number of elements plus 1 (for that NULL).
+ *
+ * Example:
+ * #include <ccan/tal/str/str.h>
+ * ...
+ * static unsigned int count_long_lines(const char *string)
+ * {
+ * char **lines;
+ * unsigned int i, long_lines = 0;
+ *
+ * // Can only fail on out-of-memory.
+ * lines = tal_strsplit(NULL, string, "\n", STR_NO_EMPTY);
+ * for (i = 0; lines[i] != NULL; i++)
+ * if (strlen(lines[i]) > 80)
+ * long_lines++;
+ * tal_free(lines);
+ * return long_lines;
+ * }
+ */
+char **tal_strsplit(const tal_t *ctx,
+ const char *string TAKES,
+ const char *delims TAKES,
+ enum strsplit flag);
+
+enum strjoin {
+ STR_TRAIL,
+ STR_NO_TRAIL
+};
+
+/**
+ * tal_strjoin - Join an array of substrings into one long string
+ * @ctx: the context to tal from (often NULL).
+ * @strings: the NULL-terminated array of strings to join (can be take())
+ * @delim: the delimiter to insert between the strings (can be take())
+ * @flags: whether to add a delimieter to the end
+ *
+ * This function joins an array of strings into a single string. The
+ * return value is allocated using tal. Each string in @strings is
+ * followed by a copy of @delim.
+ *
+ * Example:
+ * // Append the string "--EOL" to each line.
+ * static char *append_to_all_lines(const char *string)
+ * {
+ * char **lines, *ret;
+ *
+ * lines = tal_strsplit(NULL, string, "\n", STR_EMPTY_OK);
+ * ret = tal_strjoin(NULL, lines, "-- EOL\n", STR_TRAIL);
+ * tal_free(lines);
+ * return ret;
+ * }
+ */
+char *tal_strjoin(const void *ctx,
+ char *strings[] TAKES,
+ const char *delim TAKES,
+ enum strjoin flags);
+
+/**
+ * tal_strreg - match/extract from a string via (extended) regular expressions.
+ * @ctx: the context to tal from (often NULL)
+ * @string: the string to try to match (can be take())
+ * @regex: the regular expression to match (can be take())
+ * ...: pointers to strings to allocate for subexpressions.
+ *
+ * Returns true if we matched, in which case any parenthesized
+ * expressions in @regex are allocated and placed in the char **
+ * arguments following @regex. NULL arguments mean the match is not
+ * saved. The order of the strings is the order
+ * of opening braces in the expression: in the case of repeated
+ * expressions (eg "([a-z])*") the last one is saved, in the case of
+ * non-existent matches (eg "([a-z]*)?") the pointer is set to NULL.
+ *
+ * Allocation failures or malformed regular expressions return false.
+ *
+ * See Also:
+ * regcomp(3), regex(3).
+ *
+ * Example:
+ * // Given "My name is Rusty" outputs "Hello Rusty!\n"
+ * // Given "my first name is Rusty Russell" outputs "Hello Rusty Russell!\n"
+ * // Given "My name isnt Rusty Russell" outputs "Hello there!\n"
+ * int main(int argc, char *argv[])
+ * {
+ * char *person, *input;
+ *
+ * (void)argc;
+ * // Join args and trim trailing space.
+ * input = tal_strjoin(NULL, argv+1, " ", STR_NO_TRAIL);
+ * if (tal_strreg(NULL, input,
+ * "[Mm]y (first )?name is ([A-Za-z ]+)",
+ * NULL, &person))
+ * printf("Hello %s!\n", person);
+ * else
+ * printf("Hello there!\n");
+ * return 0;
+ * }
+ */
+bool tal_strreg(const void *ctx, const char *string TAKES,
+ const char *regex TAKES, ...);
+#endif /* CCAN_STR_TAL_H */
diff --git a/ccan/tal/str/test/helper.h b/ccan/tal/str/test/helper.h
@@ -0,0 +1,22 @@
+/* tal/talloc can't implement tal_first/tal_next. */
+#ifdef TAL_USE_TALLOC
+static inline bool no_children(const void *ctx)
+{
+ return talloc_total_blocks(ctx) == 1;
+}
+
+static inline bool single_child(const void *ctx, const void *child)
+{
+ return talloc_total_blocks(ctx) == 2 && tal_parent(child) == ctx;
+}
+#else
+static inline bool no_children(const void *ctx)
+{
+ return !tal_first(ctx);
+}
+
+static inline bool single_child(const void *ctx, const void *child)
+{
+ return tal_first(ctx) == child && !tal_next(child) && !tal_first(child);
+}
+#endif
diff --git a/ccan/tal/str/test/run-fmt-terminate.c b/ccan/tal/str/test/run-fmt-terminate.c
@@ -0,0 +1,22 @@
+#include <ccan/tal/str/str.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ccan/tal/str/str.c>
+#include <ccan/tap/tap.h>
+#include "helper.h"
+
+/* Empty format string: should still terminate! */
+int main(void)
+{
+ char *str;
+ const char *fmt = "";
+
+ plan_tests(1);
+ /* GCC complains about empty format string, complains about non-literal
+ * with no args... */
+ str = tal_fmt(NULL, fmt, "");
+ ok1(!strcmp(str, ""));
+ tal_free(str);
+
+ return exit_status();
+}
diff --git a/ccan/tal/str/test/run-string.c b/ccan/tal/str/test/run-string.c
@@ -0,0 +1,90 @@
+#include <ccan/tal/str/str.h>
+#include <ccan/tal/str/str.c>
+#include <ccan/tap/tap.h>
+#include "helper.h"
+
+int main(void)
+{
+ char *parent, *c;
+
+ plan_tests(32);
+
+ parent = tal(NULL, char);
+ ok1(parent);
+
+ c = tal_strdup(parent, "hello");
+ ok1(strcmp(c, "hello") == 0);
+ ok1(tal_parent(c) == parent);
+ tal_free(c);
+
+ c = tal_strndup(parent, "hello", 3);
+ ok1(strcmp(c, "hel") == 0);
+ ok1(tal_parent(c) == parent);
+ tal_free(c);
+
+#ifdef TAL_USE_TALLOC
+ c = tal_talloc_typechk_(parent, char *);
+#else
+ c = tal_typechk_(parent, char *);
+#endif
+ c = tal_dup_arr(parent, char, "hello", 6, 0);
+ ok1(strcmp(c, "hello") == 0);
+ ok1(strcmp(tal_name(c), "char[]") == 0);
+ ok1(tal_parent(c) == parent);
+ tal_free(c);
+
+ /* Now with an extra byte. */
+ c = tal_dup_arr(parent, char, "hello", 6, 1);
+ ok1(strcmp(c, "hello") == 0);
+ ok1(strcmp(tal_name(c), "char[]") == 0);
+ ok1(tal_parent(c) == parent);
+ strcat(c, "x");
+ tal_free(c);
+
+ c = tal_fmt(parent, "hello %s", "there");
+ ok1(strcmp(c, "hello there") == 0);
+ ok1(tal_parent(c) == parent);
+ tal_free(c);
+
+ c = tal_strcat(parent, "hello ", "there");
+ ok1(strcmp(c, "hello there") == 0);
+ ok1(tal_parent(c) == parent);
+
+ /* Make sure take works correctly. */
+ c = tal_strcat(parent, take(c), " again");
+ ok1(strcmp(c, "hello there again") == 0);
+ ok1(tal_parent(c) == parent);
+ ok1(single_child(parent, c));
+
+ c = tal_strcat(parent, "And ", take(c));
+ ok1(strcmp(c, "And hello there again") == 0);
+ ok1(tal_parent(c) == parent);
+ ok1(single_child(parent, c));
+
+ /* NULL pass through works... */
+ c = tal_strcat(parent, take(NULL), take(c));
+ ok1(!c);
+ ok1(no_children(parent));
+
+ c = tal_strcat(parent, take(tal_strdup(parent, "hi")),
+ take(NULL));
+ ok1(!c);
+ ok1(no_children(parent));
+
+ c = tal_strcat(parent, take(NULL), take(NULL));
+ ok1(!c);
+ ok1(no_children(parent));
+
+ /* Appending formatted strings. */
+ c = tal_strdup(parent, "hi");
+ ok1(tal_append_fmt(&c, "%s %s", "there", "world"));
+ ok1(strcmp(c, "hithere world") == 0);
+ ok1(tal_parent(c) == parent);
+
+ ok1(!tal_append_fmt(&c, take(NULL), "there", "world"));
+ ok1(strcmp(c, "hithere world") == 0);
+
+ tal_free(parent);
+
+ return exit_status();
+}
diff --git a/ccan/tal/str/test/run-strndup.c b/ccan/tal/str/test/run-strndup.c
@@ -0,0 +1,22 @@
+#include <ccan/tal/str/str.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ccan/tal/str/str.c>
+#include <ccan/tap/tap.h>
+#include "helper.h"
+
+int main(void)
+{
+ char *str, *copy;
+
+ plan_tests(1);
+ str = malloc(5);
+ memcpy(str, "hello", 5);
+ /* We should be fine to strndup src without nul terminator. */
+ copy = tal_strndup(NULL, str, 5);
+ ok1(!strcmp(copy, "hello"));
+ tal_free(copy);
+ free(str);
+
+ return exit_status();
+}
diff --git a/ccan/tal/str/test/run-strreg.c b/ccan/tal/str/test/run-strreg.c
@@ -0,0 +1,124 @@
+#include <ccan/tal/str/str.h>
+#include <ccan/tal/str/str.c>
+#include <ccan/tap/tap.h>
+#include "helper.h"
+
+static bool find_parent(tal_t *child, tal_t *parent)
+{
+ tal_t *i;
+
+ for (i = child; i; i = tal_parent(i))
+ if (i == parent)
+ return true;
+
+ return false;
+}
+
+int main(void)
+{
+ void *ctx = tal_strdup(NULL, "toplevel");
+ char *a, *b;
+ /* If it accesses this, it will crash. */
+ char **invalid = (char **)1L;
+
+ plan_tests(41);
+ /* Simple matching. */
+ ok1(tal_strreg(ctx, "hello world!", "hello") == true);
+ ok1(tal_strreg(ctx, "hello world!", "hi") == false);
+
+ /* No parentheses means we don't use any extra args. */
+ ok1(tal_strreg(ctx, "hello world!", "hello", invalid) == true);
+ ok1(tal_strreg(ctx, "hello world!", "hi", invalid) == false);
+
+ ok1(tal_strreg(ctx, "hello world!", "[a-z]+", invalid) == true);
+ ok1(tal_strreg(ctx, "hello world!", "([a-z]+)", &a, invalid) == true);
+ /* Found string */
+ ok1(streq(a, "hello"));
+ /* Allocated off ctx */
+ ok1(find_parent(a, ctx));
+ tal_free(a);
+
+ ok1(tal_strreg(ctx, "hello world!", "([a-z]*) ([a-z]+)",
+ &a, &b, invalid) == true);
+ ok1(streq(a, "hello"));
+ ok1(streq(b, "world"));
+ ok1(find_parent(a, ctx));
+ ok1(find_parent(b, ctx));
+ tal_free(a);
+ tal_free(b);
+
+ /* * after parentheses returns last match. */
+ ok1(tal_strreg(ctx, "hello world!", "([a-z])* ([a-z]+)",
+ &a, &b, invalid) == true);
+ ok1(streq(a, "o"));
+ ok1(streq(b, "world"));
+ tal_free(a);
+ tal_free(b);
+
+ /* Nested parentheses are ordered by open brace. */
+ ok1(tal_strreg(ctx, "hello world!", "(([a-z]*) world)",
+ &a, &b, invalid) == true);
+ ok1(streq(a, "hello world"));
+ ok1(streq(b, "hello"));
+ tal_free(a);
+ tal_free(b);
+
+ /* Nested parentheses are ordered by open brace. */
+ ok1(tal_strreg(ctx, "hello world!", "(([a-z]*) world)",
+ &a, &b, invalid) == true);
+ ok1(streq(a, "hello world"));
+ ok1(streq(b, "hello"));
+ tal_free(a);
+ tal_free(b);
+
+ /* NULL means we're not interested. */
+ ok1(tal_strreg(ctx, "hello world!", "((hello|goodbye) world)",
+ &a, NULL, invalid) == true);
+ ok1(streq(a, "hello world"));
+ tal_free(a);
+
+ /* No leaks! */
+ ok1(no_children(ctx));
+
+ /* NULL arg with take means always fail. */
+ ok1(tal_strreg(ctx, take(NULL), "((hello|goodbye) world)",
+ &b, NULL, invalid) == false);
+
+ /* Take string. */
+ a = tal_strdup(ctx, "hello world!");
+ ok1(tal_strreg(ctx, take(a), "([a-z]+)", &b, invalid) == true);
+ ok1(streq(b, "hello"));
+ ok1(tal_parent(b) == ctx);
+ tal_free(b);
+ ok1(no_children(ctx));
+
+ /* Take regex. */
+ a = tal_strdup(ctx, "([a-z]+)");
+ ok1(tal_strreg(ctx, "hello world!", take(a), &b, invalid) == true);
+ ok1(streq(b, "hello"));
+ ok1(tal_parent(b) == ctx);
+ tal_free(b);
+ ok1(no_children(ctx));
+
+ /* Take both. */
+ a = tal_strdup(ctx, "([a-z]+)");
+ ok1(tal_strreg(ctx, take(tal_strdup(ctx, "hello world!")),
+ take(a), &b, invalid) == true);
+ ok1(streq(b, "hello"));
+ ok1(tal_parent(b) == ctx);
+ tal_free(b);
+ ok1(no_children(ctx));
+
+ /* ... even if we fail to match. */
+ a = tal_strdup(ctx, "([a-z]+)");
+ ok1(tal_strreg(ctx, take(tal_strdup(ctx, "HELLO WORLD!")),
+ take(a), &b, invalid) == false);
+ ok1(no_children(ctx));
+ tal_free(ctx);
+
+ /* Don't get fooled by \(! */
+ ok1(tal_strreg(ctx, "(hello) (world)!", "\\([a-z]*\\) \\([a-z]+\\)",
+ invalid) == true);
+
+ return exit_status();
+}
diff --git a/ccan/tal/str/test/run-take.c b/ccan/tal/str/test/run-take.c
@@ -0,0 +1,48 @@
+#include <ccan/tal/str/str.h>
+#include <ccan/tal/str/str.c>
+#include <ccan/tap/tap.h>
+#include "helper.h"
+
+int main(void)
+{
+ char *parent, *c;
+
+ plan_tests(14);
+
+ parent = tal(NULL, char);
+ ok1(parent);
+
+ c = tal_strdup(parent, "hello");
+
+ c = tal_strdup(parent, take(c));
+ ok1(strcmp(c, "hello") == 0);
+ ok1(tal_parent(c) == parent);
+
+ c = tal_strndup(parent, take(c), 5);
+ ok1(strcmp(c, "hello") == 0);
+ ok1(tal_parent(c) == parent);
+
+ c = tal_strndup(parent, take(c), 3);
+ ok1(strcmp(c, "hel") == 0);
+ ok1(tal_parent(c) == parent);
+ tal_free(c);
+
+ c = tal_strdup(parent, "hello %s");
+ c = tal_fmt(parent, take(c), "there");
+ ok1(strcmp(c, "hello there") == 0);
+ ok1(tal_parent(c) == parent);
+ /* No leftover allocations. */
+ tal_free(c);
+ ok1(no_children(parent));
+
+ tal_free(parent);
+ ok1(!taken_any());
+
+ /* NULL pass-through. */
+ c = NULL;
+ ok1(tal_strdup(NULL, take(c)) == NULL);
+ ok1(tal_strndup(NULL, take(c), 5) == NULL);
+ ok1(tal_fmt(NULL, take(c), 0) == NULL);
+
+ return exit_status();
+}
diff --git a/ccan/tal/str/test/run.c b/ccan/tal/str/test/run.c
@@ -0,0 +1,158 @@
+#include <ccan/tal/str/str.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <ccan/tal/str/str.c>
+#include <ccan/tap/tap.h>
+#include "helper.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+
+static const char *substrings[]
+= { "far", "bar", "baz", "b", "ba", "z", "ar", NULL };
+
+int main(void)
+{
+ char **split, *str;
+ void *ctx;
+
+ plan_tests(69);
+ split = tal_strsplit(NULL, "hello world", " ", STR_EMPTY_OK);
+ ok1(!strcmp(split[0], "hello"));
+ ok1(!strcmp(split[1], ""));
+ ok1(!strcmp(split[2], "world"));
+ ok1(split[3] == NULL);
+ ok1(tal_count(split) == 4);
+ tal_free(split);
+
+ split = tal_strsplit(NULL, "hello world", " ", STR_NO_EMPTY);
+ ok1(!strcmp(split[0], "hello"));
+ ok1(!strcmp(split[1], "world"));
+ ok1(split[2] == NULL);
+ ok1(tal_count(split) == 3);
+ tal_free(split);
+
+ split = tal_strsplit(NULL, " hello world", " ", STR_NO_EMPTY);
+ ok1(!strcmp(split[0], "hello"));
+ ok1(!strcmp(split[1], "world"));
+ ok1(split[2] == NULL);
+ ok1(tal_count(split) == 3);
+ tal_free(split);
+
+ split = tal_strsplit(NULL, "hello world", "o ", STR_EMPTY_OK);
+ ok1(!strcmp(split[0], "hell"));
+ ok1(!strcmp(split[1], ""));
+ ok1(!strcmp(split[2], ""));
+ ok1(!strcmp(split[3], "w"));
+ ok1(!strcmp(split[4], "rld"));
+ ok1(split[5] == NULL);
+ ok1(tal_count(split) == 6);
+
+ ctx = split;
+ split = tal_strsplit(ctx, "hello world", "o ", STR_EMPTY_OK);
+ ok1(tal_parent(split) == ctx);
+ tal_free(ctx);
+
+ str = tal_strjoin(NULL, (char **)substrings, ", ", STR_TRAIL);
+ ok1(!strcmp(str, "far, bar, baz, b, ba, z, ar, "));
+ ctx = str;
+ str = tal_strjoin(ctx, (char **)substrings, "", STR_TRAIL);
+ ok1(!strcmp(str, "farbarbazbbazar"));
+ ok1(tal_parent(str) == ctx);
+ str = tal_strjoin(ctx, (char **)substrings, ", ", STR_NO_TRAIL);
+ ok1(tal_parent(str) == ctx);
+ ok1(!strcmp(str, "far, bar, baz, b, ba, z, ar"));
+ str = tal_strjoin(ctx, (char **)substrings, "", STR_NO_TRAIL);
+ ok1(!strcmp(str, "farbarbazbbazar"));
+ ok1(tal_parent(str) == ctx);
+ tal_free(ctx);
+
+ ctx = tal_strdup(NULL, "context");
+ /* Pass through NULLs from take. */
+ ok1(tal_strsplit(NULL, take(NULL), " ", STR_EMPTY_OK) == NULL);
+ ok1(tal_strsplit(NULL, "foo", take(NULL), STR_EMPTY_OK) == NULL);
+
+ /* tal_strsplit take string. It reallocs it to same size, but
+ * that sometimes causes a move, so we can't directly check
+ * that split[0] == str. */
+ str = tal_strdup(ctx, "hello world");
+ ok1(tal_check(ctx, NULL));
+ ok1(tal_check(str, NULL));
+ split = tal_strsplit(ctx, take(str), " ", STR_EMPTY_OK);
+ ok1(tal_parent(split) == ctx);
+ ok1(!strcmp(split[0], "hello"));
+ ok1(!strcmp(split[1], "world"));
+ ok1(split[2] == NULL);
+ ok1(tal_check(split, NULL));
+ ok1(tal_check(ctx, NULL));
+ tal_free(split);
+ /* Previous free should get rid of str */
+ ok1(no_children(ctx));
+
+ /* tal_strsplit take delims */
+ str = tal_strdup(ctx, " ");
+ split = tal_strsplit(ctx, "hello world", take(str), STR_EMPTY_OK);
+ ok1(tal_parent(split) == ctx);
+ ok1(!strcmp(split[0], "hello"));
+ ok1(!strcmp(split[1], "world"));
+ ok1(split[2] == NULL);
+ ok1(tal_check(split, NULL));
+ ok1(tal_check(ctx, NULL));
+ tal_free(split);
+ /* str is gone... */
+ ok1(no_children(ctx));
+
+ /* tal_strsplit takes both. */
+ split = tal_strsplit(ctx, take(tal_strdup(NULL, "hello world")),
+ take(tal_strdup(NULL, " ")), STR_EMPTY_OK);
+ ok1(tal_parent(split) == ctx);
+ ok1(!strcmp(split[0], "hello"));
+ ok1(!strcmp(split[1], "world"));
+ ok1(split[2] == NULL);
+ ok1(tal_check(split, NULL));
+ ok1(tal_check(ctx, NULL));
+ tal_free(split);
+ /* temp allocs are gone... */
+ ok1(no_children(ctx));
+
+ /* tal_strjoin passthrough taken NULLs OK. */
+ ok1(tal_strjoin(ctx, take(NULL), "", STR_TRAIL) == NULL);
+ ok1(tal_strjoin(ctx, take(NULL), "", STR_NO_TRAIL) == NULL);
+ ok1(tal_strjoin(ctx, split, take(NULL), STR_TRAIL) == NULL);
+ ok1(tal_strjoin(ctx, split, take(NULL), STR_NO_TRAIL) == NULL);
+
+ /* tal_strjoin take strings[] */
+ split = tal_strsplit(ctx, "hello world", " ", STR_EMPTY_OK);
+ str = tal_strjoin(ctx, take(split), " there ", STR_NO_TRAIL);
+ ok1(!strcmp(str, "hello there world"));
+ ok1(tal_parent(str) == ctx);
+ /* split is gone... */
+ ok1(single_child(ctx, str));
+ tal_free(str);
+ ok1(no_children(ctx));
+
+ /* tal_strjoin take delim */
+ split = tal_strsplit(ctx, "hello world", " ", STR_EMPTY_OK);
+ str = tal_strjoin(ctx, split, take(tal_strdup(ctx, " there ")),
+ STR_NO_TRAIL);
+ ok1(!strcmp(str, "hello there world"));
+ ok1(tal_parent(str) == ctx);
+ tal_free(split);
+ /* tmp alloc is gone, str is only remainder. */
+ ok1(single_child(ctx, str));
+ tal_free(str);
+ ok1(no_children(ctx));
+
+ /* tal_strjoin take both. */
+ str = tal_strjoin(ctx, take(tal_strsplit(ctx, "hello world", " ",
+ STR_EMPTY_OK)),
+ take(tal_strdup(ctx, " there ")), STR_NO_TRAIL);
+ ok1(!strcmp(str, "hello there world"));
+ ok1(tal_parent(str) == ctx);
+ /* tmp allocs are gone, str is only remainder. */
+ ok1(single_child(ctx, str));
+ tal_free(str);
+ ok1(no_children(ctx));
+ tal_free(ctx);
+
+ return exit_status();
+}
diff --git a/cfg.def.h b/cfg.def.h
@@ -1,7 +1,18 @@
/* See LICENSE file for copyright and license details. */
/* Default settings; can be overriden by command line. */
-static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
+enum position {
+ POSITION_TOP,
+ POSITION_LEFT,
+ POSITION_RIGHT,
+ POSITION_BOTTOM
+};
+
+static int xpad = 6;
+static int ypad = 2;
+
+static enum position position = POSITION_RIGHT; /* -b option; if 0, dmenu appears at bottom */
+
static const char *separator = " → ";
@@ -17,17 +28,11 @@ static struct scheme schemes[SchemeLast] = {
.name = "#bbbbbb"
},
- [SchemeSel] = { .bg = "#005577",
- .bind = "#eeeeee",
- .arrow = "#666666",
- .name = "#eeeeee"
- },
-
- [SchemeOut] = { .bg = "#00ffff",
- .bind = "#000000",
- .arrow = "#666666",
- .name = "#000000"
- },
+ [SchemePrefix] = { .bg = "#005577",
+ .bind = "#eeeeee",
+ .arrow = "#666666",
+ .name = "#eeeeee"
+ },
};
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
diff --git a/cmdtree.c b/cmdtree.c
@@ -14,14 +14,15 @@
#include "drw.h"
#include "util.h"
+#include "command.h"
enum {
SchemeNorm,
- SchemeSel,
- SchemeOut,
+ SchemePrefix,
SchemeLast,
}; /* color schemes */
+static struct command *rootcmds;
static Window root, parentwin, win;
static int screen;
static Display *display;
@@ -68,12 +69,33 @@ setup(Drw *drw)
if (!XGetWindowAttributes(display, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
parentwin);
- x = 0;
- y = topbar ? 0 : wa.height - mh;
+ int vertwidth = 200;
mw = wa.width;
+ mh = (lines + 1) * bh;
+ switch (position) {
+ case POSITION_BOTTOM:
+ y = wa.height - mh;
+ x = 0;
+ break;
+ case POSITION_TOP:
+ x = 0;
+ y = 0;
+ break;
+ case POSITION_LEFT:
+ x = 0;
+ y = 0;
+ // TODO: calc vertwidth = max(textwidth(nodes)))
+ mw = vertwidth;
+ mh = wa.height;
+ break;
+ case POSITION_RIGHT:
+ x = wa.width - vertwidth;
+ y = 0;
+ mw = vertwidth;
+ mh = wa.height;
+ }
lines = 3;
lines = MAX(lines, 0);
- mh = (lines + 1) * bh;
sep_width = drw_fontset_getwidth(drw, separator);
swa.override_redirect = True;
@@ -143,19 +165,53 @@ draw_command(Drw *drw, int x, int y, const char *name, const char *binding) {
return x;
}
+
/* static void */
/* calc_tree_exts(struct node *nodes, int num_nodes, int *rows, int *cols) { */
/* } */
+static void
+draw_tree_vertical(Drw *drw, int x, int y, int w, int h) {
+ int i;
+ char buf[512];
+ char smallbuf[32];
+ int colw = 0;
+
+ x += xpad;
+ y += ypad;
+
+ drw_setscheme(drw, &schemes[SchemeNorm].bg_clr,
+ &schemes[SchemeNorm].bg_clr);
+ drw_rect(drw, 0, 0, w, h, 1, 1);
+
+ int c = '0';
+ for (i = 0; i < 40; ++i, ++c, y += bh) {
+ if (i % 2 == 0)
+ snprintf(buf, 512, "item-long-%d", i);
+ else if (i % 6 == 0)
+ snprintf(buf, 512, "herpderp-%d", i);
+ else if (i % 7 == 0)
+ snprintf(buf, 512, "ksdfsdjhfsdf-%d", i);
+ else
+ snprintf(buf, 512, "hi-%d", i);
+ if (c > '~') c = '0';
+ sprintf(smallbuf, "%c", c);
+
+ if (y >= mh) {
+ x = colw;
+ y = 0;
+ colw = 0;
+ }
+
+ colw = MAX(draw_command(drw, x, y, buf, smallbuf) + 20, colw);
+ }
+}
static void
-draw_tree(Drw *drw, int x, int y, int w, int h) {
+draw_tree_horizontal(Drw *drw, int x, int y, int w, int h) {
int i, dx = x, dy = y;
char buf[512];
char smallbuf[32];
- if (!drw)
- return;
-
drw_setscheme(drw, &schemes[SchemeNorm].bg_clr,
&schemes[SchemeNorm].bg_clr);
drw_rect(drw, 0, 0, w, h, 1, 1);
@@ -178,6 +234,15 @@ draw_tree(Drw *drw, int x, int y, int w, int h) {
dy += bh;
}
}
+}
+
+static void
+draw_tree(Drw *drw, int x, int y, int w, int h) {
+ if (!drw)
+ return;
+
+ /* draw_tree_horizontal(drw, x, y, w, h); */
+ draw_tree_vertical(drw, x, y, w, h);
XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
XSync(drw->dpy, False);
@@ -238,6 +303,7 @@ int main(void) {
/* parentwin = root; */
parentwin = root;
+ rootcmds = test_root_commands(NULL);
if (!XGetWindowAttributes(display, parentwin, &wa))
die("could not get embedding window attributes: 0x%lx",
@@ -248,7 +314,7 @@ int main(void) {
if (!drw_fontset_create(drw, fonts, LENGTH(fonts)))
die("no fonts could be loaded.");
- /* grabkeyboard(); */
+ grabkeyboard();
setup(drw);
run(drw);
diff --git a/command.c b/command.c
@@ -0,0 +1,40 @@
+
+#include "command.h"
+
+#include "ccan/tal/tal.h"
+#include "ccan/tal/str/str.h"
+
+void
+command_init(struct command *cmd) {
+ cmd->children = NULL;
+}
+
+int
+command_is_prefix(struct command *cmd) {
+ size_t count = tal_count(cmd->children);
+ return count > 0;
+}
+
+
+struct command *
+test_root_commands(tal_t *ctx) {
+ unsigned long i, j;
+ struct command *cmds = NULL;
+ struct command *child = NULL;
+
+ cmds = tal_arr(ctx, struct command, 5);
+
+ const unsigned long c = 'a';
+ for (i = 0; i < tal_count(cmds); i++) {
+ cmds[i].name = tal_fmt(cmds, "hello-%d", (int)i);
+ cmds[i].bind = tal_fmt(cmds, "%c", (int)(c+i));
+ child = cmds[i].children = tal_arr(cmds, struct command, i % 2);
+ for (j = 0; j < tal_count(child); j++) {
+ child[j].name = tal_fmt(child, "child-%d-%d", (int)i, (int)j);
+ child[j].bind = tal_fmt(child, "%c", (int)(c+j));
+ child[j].children = NULL;
+ }
+ }
+
+ return cmds;
+}
diff --git a/command.h b/command.h
@@ -2,10 +2,24 @@
#ifndef CMDTREE_COMMAND_H
#define CMDTREE_COMMAND_H
+#include <ccan/tal/tal.h>
-struct node {
+struct command {
char *name;
- struct node **children;
+ char *bind;
+ char *exec;
+ struct command *children;
};
+
+void
+command_init(struct command *cmd);
+
+int
+command_is_prefix(struct command *cmd);
+
+
+struct command *
+test_root_commands(tal_t *ctx);
+
#endif /* CMDTREE_COMMAND_H */