cmdtree

A trie command launcher for X11
git clone git://jb55.com/cmdtree
Log | Files | Refs | README | LICENSE

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:
MMakefile | 2++
MTODO.org | 5+++++
Accan/tal/str/LICENSE | 2++
Accan/tal/str/_info | 59+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Accan/tal/str/str.c | 309+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Accan/tal/str/str.h | 192+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Accan/tal/str/test/helper.h | 22++++++++++++++++++++++
Accan/tal/str/test/run-fmt-terminate.c | 22++++++++++++++++++++++
Accan/tal/str/test/run-string.c | 90+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Accan/tal/str/test/run-strndup.c | 22++++++++++++++++++++++
Accan/tal/str/test/run-strreg.c | 124+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Accan/tal/str/test/run-take.c | 48++++++++++++++++++++++++++++++++++++++++++++++++
Accan/tal/str/test/run.c | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcfg.def.h | 29+++++++++++++++++------------
Mcmdtree.c | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Acommand.c | 40++++++++++++++++++++++++++++++++++++++++
Mcommand.h | 18++++++++++++++++--
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 */