Mercurial > irccd
changeset 935:b0451fc0a17d
irccd: add subst.h header
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sun, 10 Jan 2021 17:29:30 +0100 |
parents | 243f9f51b0ff |
children | 6866d0d0e360 |
files | .hgignore Makefile irccd/irccd irccd/subst.c irccd/subst.h tests/test-subst.c |
diffstat | 6 files changed, 1038 insertions(+), 1 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgignore Sun Jan 10 16:56:32 2021 +0100 +++ b/.hgignore Sun Jan 10 17:29:30 2021 +0100 @@ -23,6 +23,7 @@ \.d$ # tests. +^tests/test-subst$ ^tests/test-util$ # macOS specific.
--- a/Makefile Sun Jan 10 16:56:32 2021 +0100 +++ b/Makefile Sun Jan 10 17:29:30 2021 +0100 @@ -25,11 +25,13 @@ IRCCD= irccd/irccd IRCCD_SRCS= extern/libduktape/duktape.c \ + irccd/subst.c \ irccd/util.c IRCCD_OBJS= ${IRCCD_SRCS:.c=.o} IRCCD_DEPS= ${IRCCD_SRCS:.c=.d} -TESTS= tests/test-util.c +TESTS= tests/test-util.c \ + tests/test-subst.c TESTS_OBJS= ${TESTS:.c=} FLAGS= -D_BSD_SOURCE \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/irccd/subst.c Sun Jan 10 17:29:30 2021 +0100 @@ -0,0 +1,485 @@ +/* + * subst.c -- pattern substitution + * + * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> + * + * 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 <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "subst.h" + +struct pair { + const char *key; + const char *value; +}; + +struct attributes { + char fg[16]; + char bg[16]; + char attrs[4][16]; + size_t attrsz; +}; + +static const struct pair irc_colors[] = { + { "white", "0" }, + { "black", "1" }, + { "blue", "2" }, + { "green", "3" }, + { "red", "4" }, + { "brown", "5" }, + { "purple", "6" }, + { "orange", "7" }, + { "yellow", "8" }, + { "lightgreen", "9" }, + { "cyan", "10" }, + { "lightcyan", "11" }, + { "lightblue", "12" }, + { "pink", "13" }, + { "grey", "14" }, + { "lightgrey", "15" }, + { NULL, NULL } +}; + +static const struct pair irc_attrs[] = { + { "bold", "\x02" }, + { "italic", "\x09" }, + { "reverse", "\x16" }, + { "strike", "\x13" }, + { "underline", "\x15" }, + { "underline2", "\x1f" }, + { NULL, NULL } +}; + +static const struct pair shell_fg[] = { + { "black", "30" }, + { "red", "31" }, + { "green", "32" }, + { "orange", "33" }, + { "blue", "34" }, + { "purple", "35" }, + { "cyan", "36" }, + { "white", "37" }, + { "default", "39" }, + { NULL, NULL } +}; + +static const struct pair shell_bg[] = { + { "black", "40" }, + { "red", "41" }, + { "green", "42" }, + { "orange", "43" }, + { "blue", "44" }, + { "purple", "45" }, + { "cyan", "46" }, + { "white", "47" }, + { "default", "49" }, + { NULL, NULL } +}; + +static const struct pair shell_attrs[] = { + { "bold", "1" }, + { "dim", "2" }, + { "underline", "4" }, + { "blink", "5" }, + { "reverse", "7" }, + { "hidden", "8" }, + { NULL, NULL } +}; + +static inline bool +is_reserved(char token) +{ + return token == '#' || token == '@' || token == '$' || token == '!'; +} + +static inline bool +scat(char **out, size_t *outsz, const char *value) +{ + size_t written; + + if ((written = strlcpy(*out, value, *outsz)) >= *outsz) { + errno = ENOMEM; + return false; + } + + *out += written; + *outsz -= written; + + return true; +} + +static inline bool +ccat(char **out, size_t *outsz, char c) +{ + if (*outsz == 0) + return false; + + *(*out)++ = c; + *(outsz) -= 1; + + return true; +} + +static inline void +attributes_parse(const char *key, struct attributes *attrs) +{ + char attributes[64] = {0}; + + memset(attrs, 0, sizeof (*attrs)); + sscanf(key, "%15[^,],%15[^,],%63s", attrs->fg, attrs->bg, attributes); + + for (char *attr = attributes; *attr; ) { + char *p = strchr(attr, ','); + + if (p) + *p = 0; + + strlcpy(attrs->attrs[attrs->attrsz++], attr, sizeof (attrs->attrs[0])); + + if (p) + attr = p + 1; + else + *attr = '\0'; + } +} + +static inline const char * +find(const struct pair *pairs, const char *key) +{ + for (const struct pair *pair = pairs; pair->key; ++pair) + if (strcmp(pair->key, key) == 0) + return pair->value; + + return NULL; +} + +static bool +subst_date(char *out, size_t outsz, const char *input, const struct irc_subst *subst) +{ + struct tm *tm; + + if (!(subst->flags & IRC_SUBST_DATE)) + return true; + + tm = localtime(&subst->time); + + if (strftime(out, outsz, input, tm) == 0) { + errno = ENOMEM; + return false; + } + + return true; +} + +static bool +subst_keyword(const char *key, char **out, size_t *outsz, const struct irc_subst *subst) +{ + const char *value = NULL; + + for (size_t i = 0; i < subst->keywordsz; ++i) { + if (strcmp(subst->keywords[i].key, key) == 0) { + value = subst->keywords[i].value; + break; + } + } + + if (!value) + return true; + + return scat(out, outsz, value); +} + +static bool +subst_env(const char *key, char **out, size_t *outsz) +{ + const char *value; + + if (!(value = getenv(key))) + return true; + + return scat(out, outsz, value); +} + +static bool +subst_shell(const char *key, char **out, size_t *outsz) +{ + FILE *fp; + size_t written; + + /* Accept silently. */ + if (!(fp = popen(key, "r"))) + return true; + + /* + * Since we cannot determine the number of bytes that must be read, read until the end of + * the output string and cut at the number of bytes read if lesser. + */ + if ((written = fread(*out, 1, *outsz - 1, fp)) > 0) { + /* Remove '\r\n' */ + char *end; + + if ((end = memchr(*out, '\r', written)) || (end = memchr(*out, '\n', written))) + *end = '\0'; + else + end = *out + written; + + *outsz -= end - *out; + *out = end; + } + + pclose(fp); + + return true; +} + +static bool +subst_irc_attrs(const char *key, char **out, size_t *outsz) +{ + const char *value; + struct attributes attrs; + + if (!key[0]) + return ccat(out, outsz, '\x03'); + + attributes_parse(key, &attrs); + + if (attrs.fg[0] || attrs.attrs[0]) { + if (!ccat(out, outsz, '\x03')) + return false; + + /* Foreground. */ + if ((value = find(irc_colors, attrs.fg)) && !scat(out, outsz, value)) + return false; + + /* Background. */ + if (attrs.bg[0]) { + if (!ccat(out, outsz, ',')) + return false; + if ((value = find(irc_colors, attrs.bg)) && !scat(out, outsz, value)) + return false; + } + + /* Attributes. */ + for (size_t i = 0; i < attrs.attrsz; ++i) + if ((value = find(irc_attrs, attrs.attrs[i])) && !scat(out, outsz, value)) + return false; + } + + return true; +} + +static bool +subst_shell_attrs(char *key, char **out, size_t *outsz) +{ + const char *value; + struct attributes attrs; + + /* Empty attributes means reset: @{}. */ + if (!key[0]) + return scat(out, outsz, "\033[0m"); + + attributes_parse(key, &attrs); + + if (!scat(out, outsz, "\033[")) + return false; + + /* Attributes first. */ + for (size_t i = 0; i < attrs.attrsz; ++i) { + if ((value = find(shell_attrs, attrs.attrs[i])) && !scat(out, outsz, value)) + return false; + + /* Need to append ; if we have still more attributes or colors next. */ + if ((i < attrs.attrsz || attrs.fg[0] || attrs.bg[0]) && !ccat(out, outsz, ';')) + return false; + } + + /* Foreground. */ + if (attrs.fg[0]) { + if ((value = find(shell_fg, attrs.fg)) && !scat(out, outsz, value)) + return false; + if (attrs.bg[0] && !ccat(out, outsz, ';')) + return false; + } + + /* Background. */ + if (attrs.bg[0]) { + if ((value = find(shell_bg, attrs.bg)) && !scat(out, outsz, value)) + return false; + } + + return ccat(out, outsz, 'm'); +} + +static bool +subst_default(const char **p, char **out, size_t *outsz, const char *key) +{ + return ccat(out, outsz, (*p)[-2]) && + ccat(out, outsz, '{') && + scat(out, outsz, key) && + ccat(out, outsz, '}'); +} + +static bool +substitute(const char **p, char **out, size_t *outsz, const struct irc_subst *subst) +{ + char key[64] = {0}; + size_t keysz; + char *end; + bool replaced = true; + + if (!**p) + return true; + + /* Find end of construction. */ + if (!(end = strchr(*p, '}'))) { + errno = EINVAL; + return false; + } + + /* Copy key. */ + if ((keysz = end - *p) >= sizeof (key)) { + errno = ENOMEM; + return false; + } + + memcpy(key, *p, keysz); + + switch ((*p)[-2]) { + case '@': + /* attributes */ + if (subst->flags & IRC_SUBST_IRC_ATTRS) { + if (!subst_irc_attrs(key, out, outsz)) + return false; + } else if (subst->flags & IRC_SUBST_SHELL_ATTRS) { + if (!subst_shell_attrs(key, out, outsz)) + return false; + } else + replaced = false; + break; + case '#': + /* keyword */ + if (subst->flags & IRC_SUBST_KEYWORDS) { + if (!subst_keyword(key, out, outsz, subst)) + return false; + } else + replaced = false; + break; + case '$': + /* environment variable */ + if (subst->flags & IRC_SUBST_ENV) { + if (!subst_env(key, out, outsz)) + return false; + } else + replaced = false; + break; + case '!': + /* shell */ + if (subst->flags & IRC_SUBST_SHELL) { + if (!subst_shell(key, out, outsz)) + return false; + } else + replaced = false; + break; + default: + break; + } + + /* If substitution was disabled, put the token verbatim. */ + if (!replaced && !subst_default(p, out, outsz, key)) + return false; + + /* Move after '}' */ + *p = end + 1; + + return true; +} + +ssize_t +irc_subst(char *out, size_t outsz, const char *input, const struct irc_subst *subst) +{ + assert(out); + assert(subst); + + char *o = out; + + if (!outsz) + return true; + + /* Always start with the date first. */ + if (!subst_date(out, outsz, input, subst)) + goto err; + + for (const char *i = input; *i && outsz; ) { + /* + * Check if this is a reserved character, if it isn't go to the next character to + * see if it's valid otherwise we print it as last token. + * + * Example: + * "#{abc}" -> keyword sequence + * "abc #" -> keyword sequence interrupted, kept as-is. + */ + if (!is_reserved(*i)) { + if (!ccat(&o, &outsz, *i++)) + goto err; + continue; + } + + /* + * Test if after the reserved token we have the opening { construct. If it's the + * case we start substitution. + * + * Otherwise depending on what's after: + * If it is the same reserved token, it is "escaped" and printed + * If it is something else, we print the token and skip iteration. + * + * Examples: + * ## => # + * #@ => #@ + * ##{foo} => #{foo} + * #{foo} => value + */ + if (*++i == '{') { + /* Skip '{'. */ + ++i; + + if (!substitute(&i, &o, &outsz, subst)) + goto err; + } else { + if (*i == i[-1]) + ++i; + if (!ccat(&o, &outsz, i[-1])) + goto err; + } + } + + if (outsz < 1) { + errno = ENOMEM; + goto err; + } + + *o = '\0'; + + return o - out; + +err: + out[0] = '\0'; + + return -1; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/irccd/subst.h Sun Jan 10 17:29:30 2021 +0100 @@ -0,0 +1,50 @@ +/* + * subst.h -- pattern substitution + * + * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> + * + * 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. + */ + +#ifndef IRCCD_SUBST_H +#define IRCCD_SUBST_H + +#include <sys/types.h> +#include <stddef.h> +#include <time.h> + +enum irc_subst_flags { + IRC_SUBST_DATE = (1 << 0), + IRC_SUBST_KEYWORDS = (1 << 1), + IRC_SUBST_ENV = (1 << 2), + IRC_SUBST_SHELL = (1 << 3), + IRC_SUBST_IRC_ATTRS = (1 << 4), + IRC_SUBST_SHELL_ATTRS = (1 << 5) +}; + +struct irc_subst_keyword { + const char *key; + const char *value; +}; + +struct irc_subst { + time_t time; + enum irc_subst_flags flags; + const struct irc_subst_keyword *keywords; + size_t keywordsz; +}; + +ssize_t +irc_subst(char *, size_t, const char *, const struct irc_subst *); + +#endif /* !IRCCD_SUBST_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-subst.c Sun Jan 10 17:29:30 2021 +0100 @@ -0,0 +1,499 @@ +/* + * test-subst.c -- test subst.h functions + * + * Copyright (c) 2013-2021 David Demelier <markand@malikania.fr> + * + * 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 <errno.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/subst.h> +#include <irccd/util.h> + +GREATEST_TEST +basics_test(void) +{ + struct irc_subst params = {0}; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello world!", ¶ms), 12); + GREATEST_ASSERT_STR_EQ("hello world!", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_escape(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "hello" } + }; + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS, + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "$@#", ¶ms), 3); + GREATEST_ASSERT_STR_EQ("$@#", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), " $ @ # ", ¶ms), 7); + GREATEST_ASSERT_STR_EQ(" $ @ # ", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "#", ¶ms), 1); + GREATEST_ASSERT_STR_EQ("#", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), " # ", ¶ms), 3); + GREATEST_ASSERT_STR_EQ(" # ", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "#@", ¶ms), 2); + GREATEST_ASSERT_STR_EQ("#@", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "##", ¶ms), 1); + GREATEST_ASSERT_STR_EQ("#", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "#!", ¶ms), 2); + GREATEST_ASSERT_STR_EQ("#!", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "##{target}", ¶ms), 9); + GREATEST_ASSERT_STR_EQ("#{target}", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@#{target}", ¶ms), 6); + GREATEST_ASSERT_STR_EQ("@hello", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "#{target}#", ¶ms), 6); + GREATEST_ASSERT_STR_EQ("hello#", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "abc##xyz", ¶ms), 7); + GREATEST_ASSERT_STR_EQ("abc#xyz", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "abc###xyz", ¶ms), 8); + GREATEST_ASSERT_STR_EQ("abc##xyz", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "#{failure", ¶ms), -1); + GREATEST_ASSERT_EQ(errno, EINVAL); + GREATEST_ASSERT_STR_EQ("", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +disable_date(void) +{ + struct irc_subst params = {0}; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "%H:%M", ¶ms), 5); + GREATEST_ASSERT_STR_EQ("%H:%M", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +disable_keywords(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "hello" } + }; + struct irc_subst params = { + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "#{target}", ¶ms), 9); + GREATEST_ASSERT_STR_EQ("#{target}", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +disable_env(void) +{ + struct irc_subst params = {0}; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "${HOME}", ¶ms), 7); + GREATEST_ASSERT_STR_EQ("${HOME}", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +disable_shell(void) +{ + struct irc_subst params = {0}; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "!{hostname}", ¶ms), 11); + GREATEST_ASSERT_STR_EQ("!{hostname}", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +keywords_simple(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "irccd" } + }; + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS, + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello #{target}!", ¶ms), 12); + GREATEST_ASSERT_STR_EQ("hello irccd!", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +keywords_multiple(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "irccd" }, + { "source", "nightmare" } + }; + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS, + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello #{target} from #{source}!", ¶ms), 27); + GREATEST_ASSERT_STR_EQ("hello irccd from nightmare!", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +keywords_adj_twice(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "irccd" } + }; + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS, + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello #{target}#{target}!", ¶ms), 17); + GREATEST_ASSERT_STR_EQ("hello irccdirccd!", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +keywords_missing(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello #{target}!", ¶ms), 7); + GREATEST_ASSERT_STR_EQ("hello !", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +keywords_enomem(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "irccd" } + }; + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS, + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[10] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello #{target}!", ¶ms), -1); + GREATEST_ASSERT_EQ(ENOMEM, errno); + GREATEST_ASSERT_STR_EQ("", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +keywords_einval(void) +{ + struct irc_subst_keyword kw[] = { + { "target", "irccd" } + }; + struct irc_subst params = { + .flags = IRC_SUBST_KEYWORDS, + .keywords = kw, + .keywordsz = IRC_UTIL_SIZE(kw) + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello #{target!", ¶ms), -1); + GREATEST_ASSERT_EQ(EINVAL, errno); + GREATEST_ASSERT_STR_EQ("", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +env_simple(void) +{ + const char *home = getenv("HOME"); + char tmp[1024]; + + if (home) { + struct irc_subst params = { + .flags = IRC_SUBST_ENV, + }; + char buf[1024] = {0}; + + snprintf(tmp, sizeof (tmp), "my home is %s", home); + + GREATEST_ASSERT_EQ((size_t)irc_subst(buf, sizeof (buf), "my home is ${HOME}", ¶ms), strlen(tmp)); + GREATEST_ASSERT_STR_EQ(tmp, buf); + } + + GREATEST_PASS(); +} + +GREATEST_TEST +env_missing(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_ENV, + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "value is ${HOPE_THIS_VAR_NOT_EXIST}", ¶ms), 9); + GREATEST_ASSERT_STR_EQ("value is ", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +env_enomem(void) +{ + const char *home = getenv("HOME"); + + if (home) { + struct irc_subst params = { + .flags = IRC_SUBST_ENV + }; + char buf[10]; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "value is ${HOME}", ¶ms), -1); + GREATEST_ASSERT_EQ(ENOMEM, errno); + GREATEST_ASSERT_STR_EQ("", buf); + } + + GREATEST_PASS(); +} + +GREATEST_TEST +shell_simple(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_SHELL + }; + char buf[1024] = {0}; + char tmp[1024] = {0}; + time_t now = time(NULL); + struct tm *cal = localtime(&now); + + strftime(tmp, sizeof (tmp), "year: %Y", cal); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "year: !{date +%Y}", ¶ms), 10); + GREATEST_ASSERT_STR_EQ(tmp, buf); + GREATEST_PASS(); +} + +GREATEST_TEST +shell_no_new_line(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_SHELL + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "hello !{printf world}", ¶ms), 11); + GREATEST_ASSERT_STR_EQ("hello world", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +shattrs_simple(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_SHELL_ATTRS + }; + char buf[1024] = {0}; + + /* On shell attributes, all components are optional. */ + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red}red@{}", ¶ms), 12); + GREATEST_ASSERT_STR_EQ("\033[31mred\033[0m", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red,blue}red on blue@{}", ¶ms), 23); + GREATEST_ASSERT_STR_EQ("\033[31;44mred on blue\033[0m", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red,blue,bold}bold red on blue@{}", ¶ms), 30); + GREATEST_ASSERT_STR_EQ("\033[1;31;44mbold red on blue\033[0m", buf); + + GREATEST_PASS(); +} + +GREATEST_TEST +shattrs_enomem(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_SHELL_ATTRS + }; + char buf[10] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red}hello world in red@{}", ¶ms), -1); + GREATEST_ASSERT_EQ(ENOMEM, errno); + GREATEST_ASSERT_STR_EQ("", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +shattrs_invalid_color(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_SHELL_ATTRS + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{invalid}standard@{}", ¶ms), 15); + GREATEST_ASSERT_STR_EQ("\033[mstandard\033[0m", buf); + + GREATEST_PASS(); +} + +GREATEST_TEST +ircattrs_simple(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_IRC_ATTRS + }; + char buf[1024] = {0}; + + /* In IRC the foreground is required if the background is desired. */ + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red}red@{}", ¶ms), 6); + GREATEST_ASSERT_STR_EQ("\x03""4red\x03", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red,blue}red on blue@{}", ¶ms), 16); + GREATEST_ASSERT_STR_EQ("\x03""4,2red on blue\x03", buf); + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red,blue,bold}bold red on blue@{}", ¶ms), 22); + GREATEST_ASSERT_STR_EQ("\x03" "4,2" "\x02" "bold red on blue\x03", buf); + + GREATEST_PASS(); +} + +GREATEST_TEST +ircattrs_enomem(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_IRC_ATTRS + }; + char buf[10] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{red}hello world in red@{}", ¶ms), -1); + GREATEST_ASSERT_EQ(ENOMEM, errno); + GREATEST_ASSERT_STR_EQ("", buf); + GREATEST_PASS(); +} + +GREATEST_TEST +ircattrs_invalid_color(void) +{ + struct irc_subst params = { + .flags = IRC_SUBST_IRC_ATTRS + }; + char buf[1024] = {0}; + + GREATEST_ASSERT_EQ(irc_subst(buf, sizeof (buf), "@{invalid}standard@{}", ¶ms), 10); + GREATEST_ASSERT_STR_EQ("\x03" "standard" "\x03", buf); + + GREATEST_PASS(); +} + + +GREATEST_SUITE(suite_basics) +{ + GREATEST_RUN_TEST(basics_test); + GREATEST_RUN_TEST(basics_escape); +} + +GREATEST_SUITE(suite_disable) +{ + GREATEST_RUN_TEST(disable_date); + GREATEST_RUN_TEST(disable_keywords); + GREATEST_RUN_TEST(disable_env); + GREATEST_RUN_TEST(disable_shell); +} + +GREATEST_SUITE(suite_keywords) +{ + GREATEST_RUN_TEST(keywords_simple); + GREATEST_RUN_TEST(keywords_multiple); + GREATEST_RUN_TEST(keywords_adj_twice); + GREATEST_RUN_TEST(keywords_missing); + GREATEST_RUN_TEST(keywords_enomem); + GREATEST_RUN_TEST(keywords_einval); +} + +GREATEST_SUITE(suite_env) +{ + GREATEST_RUN_TEST(env_simple); + GREATEST_RUN_TEST(env_missing); + GREATEST_RUN_TEST(env_enomem); +} + +GREATEST_SUITE(suite_shell) +{ + GREATEST_RUN_TEST(shell_simple); + GREATEST_RUN_TEST(shell_no_new_line); +} + +GREATEST_SUITE(suite_shattrs) +{ + GREATEST_RUN_TEST(shattrs_simple); + GREATEST_RUN_TEST(shattrs_enomem); + GREATEST_RUN_TEST(shattrs_invalid_color); +} + +GREATEST_SUITE(suite_ircattrs) +{ + GREATEST_RUN_TEST(ircattrs_simple); + GREATEST_RUN_TEST(ircattrs_enomem); + GREATEST_RUN_TEST(ircattrs_invalid_color); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_RUN_SUITE(suite_disable); + GREATEST_RUN_SUITE(suite_keywords); + GREATEST_RUN_SUITE(suite_env); + GREATEST_RUN_SUITE(suite_shell); + GREATEST_RUN_SUITE(suite_shattrs); + GREATEST_RUN_SUITE(suite_ircattrs); + GREATEST_MAIN_END(); + + return 0; +}