hirc

IRC client
Log | Files | Refs

commit 0c655c6feaffd0c6d4a22c8b5e49a5aeed068360
parent de6f994e5eff48db8bc3738ad3a7cb48a8348a95
Author: hhvn <dev@hhvn.uk>
Date:   Sat, 14 May 2022 18:28:02 +0100

yacc parser for formats

Diffstat:
MMakefile | 5+++--
Mconfigure | 16++++++++++++++++
Mdoc/hirc.1.header | 25++++++++++++++++---------
Msrc/config.c | 18++++++++++++++++++
Msrc/data/config.h | 416++++++++++++++++++++++++++++++++++++++++----------------------------------------
Dsrc/format.c | 553-------------------------------------------------------------------------------
Asrc/format.y | 540+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/hirc.h | 3+--
Asrc/openbsd/strlcat.c | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/struct.h | 1+
Msrc/ui.c | 69+++++++++++++++++++++++++++++++--------------------------------------
11 files changed, 891 insertions(+), 812 deletions(-)

diff --git a/Makefile b/Makefile @@ -18,10 +18,11 @@ PREFIX = /usr/local BINDIR = $(PREFIX)/bin MANDIR = $(PREFIX)/share/man BIN = hirc +PARSE = src/format.y SRC = src/main.c src/mem.c src/handle.c src/hist.c \ src/nick.c src/chan.c src/serv.c src/ui.c \ - src/format.c src/complete.c src/commands.c \ - src/config.c src/str.c src/params.c + src/complete.c src/commands.c src/config.c \ + src/str.c src/params.c $(PARSE:.y=.c) OBJ = $(SRC:.c=.o) MAN = doc/hirc.1 COMMIT = $(shell git log HEAD...HEAD~1 --pretty=format:%h) diff --git a/configure b/configure @@ -74,6 +74,22 @@ ${CC} -o test test.c >/dev/null 2>/dev/null && ./test >/dev/null 2>/dev/null && EOF } +printf '%s' "checking for strlcat... " +cat > test.c <<- EOF + #include <string.h> + int main(void) { char a[3] = {'a', '\0', '\0'}; strlcat(a, "bc", sizeof(a)); return 0; } +EOF +${CC} -o test test.c >/dev/null 2>/dev/null && ./test >/dev/null 2>/dev/null && { + printf '%s\n' "yes" +} || { + printf '%s\n' "no" + cat >> config.mk <<- EOF + # linking an included version of strlcat, as your system doesn't have it + CFLAGS += -DHIRC_STRLCAT + SRC += src/openbsd/strlcat.c + EOF +} + printf '%s' "checking for wcslcpy... " cat > test.c <<- EOF #include <wchar.h> diff --git a/doc/hirc.1.header b/doc/hirc.1.header @@ -43,9 +43,14 @@ executing any that are begin with '/' as commands. Any line beginning with a character other than '/' are ignored. .Ss Formats Variables beginning with format.* are used to customize the way that messages and ui elements are formatted. -The parser is designed to never report any errors, and simply print verbatim if it cannot understand the format. -The following styling may be used: -.Bl -tag -compact -width "%{c:fg[,bg]}" + +Previously a custom parser was used, but now one generated by +.Xr yacc 1 +is used. This means that formats are now checked for syntax errors on /set or /format, +although I tried to make it as lenient as possible to mimic the old parser. + +The following styling may be used (variables and other styles may be passed as arguments): +.Bl -tag -compact -width "%{split:n,c,s}" .It %{b} Set/reset boldness. .It %{c:fg[,bg]} @@ -61,12 +66,6 @@ Reverse foreground/background. Set/reset underline. .It %{=} Place divider here. If disabled, acts like space. -.El - -The following formats behave the same as above, -but the string 's' is re-evaluated, -and can so contain other stylings and variables within: -.Bl -tag -compact -width "%{split:n,c,s}" .It %{nick:s} Set foreground colour to the string's hashed colour. .It %{pad:n,s} @@ -138,6 +137,14 @@ hello world If a variable does not exist, it will be printed verbatim. If a variable does exist, but is empty, nothing is printed. + +Symbols may be escaped by placing a '\e' before them. +This includes '$' and '%' starting variables and styling, respectively, +as well as ':' and ',' seperating arguments to stylings. + +These characters do not always have to be escaped. +For example "$a" prints "$a" as it is not followed by a brace, +and any attempt to escape a character that does not need to be escaped will result in a backslash being printed. .Ss CTCP CTCP control characters are automatically stripped when using ${n} and ${n-} formats. diff --git a/src/config.c b/src/config.c @@ -24,6 +24,12 @@ #include <limits.h> #include "hirc.h" +/* + * Handlers. + * These can be attached to a config variable in order to performs actions + * when changing the value, or validate the new value before setting. + * A non-zero return value sets the variable, whilst a 0 keeps the old value. + */ static int config_window_hide(struct Config *conf, long num); static int config_window_location(struct Config *conf, long num); static int config_window_width(struct Config *conf, long num); @@ -31,6 +37,7 @@ static int config_nickcolour_self(struct Config *conf, long num); static int config_nickcolour_range(struct Config *conf, long a, long b); static int config_redrawl(struct Config *conf, long num); static int config_redraws(struct Config *conf, char *str); +static int config_formats(struct Config *conf, char *str); static char *strbool[] = { "true", "false", NULL }; static char *strlocation[] = { @@ -321,6 +328,7 @@ config_window_hide(struct Config *conf, long num) { return 1; } +/* prevent nicklist/buflist being drawn in same location */ static int config_window_location(struct Config *conf, long num) { struct Config *otherloc, *otherhide; @@ -387,3 +395,13 @@ config_redrawl(struct Config *conf, long num) { ui_redraw(); return 1; } + +/* Don't set formats with syntax errors */ +static int +config_formats(struct Config *conf, char *str) { + if (format(NULL, str, NULL)) { + ui_redraw(); + return 1; + } + return 0; +} diff --git a/src/data/config.h b/src/data/config.h @@ -222,1053 +222,1053 @@ struct Config config[] = { "Turn on/off timestamps", NULL}}, {"format.ui.timestamp", 1, Val_string, .str = "%{c:92}%{time:%H:%M:%S,${time}}%{o} ", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of timestamps", "Only shown if timestamp.toggle is on.", "This format is special as it is included in others.", NULL}}, {"format.ui.topic", 1, Val_string, .str = "%{c:99,89}${topic}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of topic at top of main window", NULL}}, {"format.ui.error", 1, Val_string, .str = "%{c:28}%{b}${4} %{b}(${3} at ${1}:${2})", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_ERROR messages", NULL}}, {"format.ui.misc", 1, Val_string, .str = "${1}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_UI messages", NULL}}, {"format.ui.connectlost", 1, Val_string, .str = "Connection to ${1} (${2}:${3}) lost: ${4}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_CONNECTLOST messages", NULL}}, {"format.ui.connecting", 1, Val_string, .str = "Connecting to ${1}:${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_CONNECTING messages", NULL}}, {"format.ui.connected", 1, Val_string, .str = "Connection to ${1} established", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_CONNECTED messages", NULL}}, {"format.ui.lookupfail", 1, Val_string, .str = "Failed to lookup ${2}: ${4}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_LOOKUPFAIL messages", NULL}}, {"format.ui.connectfail", 1, Val_string, .str = "Failed to connect to ${2}:${3}: ${4}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_CONNECTFAIL messages", NULL}}, #ifndef TLS {"format.ui.tls.notcompiled", 1, Val_string, .str = "TLS not compiled into hirc", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of SELF_TLSNOTCOMPILED messages", NULL}}, #else {"format.ui.tls.version", 1, Val_string, .str = "Protocol: %{b}${2}%{b} (%{b}${3}%{b} bits, %{b}${4}%{b})", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "TLS version and crypto information.", NULL}}, {"format.ui.tls.sni", 1, Val_string, .str = "SNI name: %{b}${2}%{b}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "TLS server name indication.", NULL}}, {"format.ui.tls.issuer", 1, Val_string, .str = "Cert issuer: %{b}${2}%{b}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "TLS certificate issuer.", NULL}}, {"format.ui.tls.subject", 1, Val_string, .str = "Cert subject: %{b}${2}%{b}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "TLS certificate subject.", NULL}}, #endif /* TLS */ {"format.ui.keybind", 1, Val_string, .str = " ${1}: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of /bind output", NULL}}, {"format.ui.keybind.start", 1, Val_string, .str = "Keybindings:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of header of /bind output", NULL}}, {"format.ui.keybind.end", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of footer of /bind output", NULL}}, {"format.ui.autocmds", 1, Val_string, .str = " ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of /server -auto output", NULL}}, {"format.ui.autocmds.start", 1, Val_string, .str = "Autocmds for ${1}:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of header of /server -auto output", NULL}}, {"format.ui.autocmds.end", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of footer of /server -auto output", NULL}}, {"format.ui.logrestore", 1, Val_string, .str = "%{c:93}---%{=}%{c:93}Restored log up until %{b}%{time:%c,${1}}%{b} ---", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of log restore footer.", NULL}}, {"format.ui.unread", 1, Val_string, .str = "%{c:93}---%{=}%{c:93}%{b}${1}%{b} unread (%{b}${2}%{b} ignored) ---", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of unread message indicator.", NULL}}, {"format.ui.newday", 1, Val_string, .str = "%{c:93}---%{=}%{c:93}%{b}%{time:%A %d %B %Y,${1}}%{b} ---", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of indicator placed between messages on different days.", NULL}}, {"format.ui.ignores.start", 1, Val_string, .str = "Ignoring:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ignore list header.", NULL}}, {"format.ui.ignores", 1, Val_string, .str = " %{pad:-3,${1}} (server: ${2}, noact: ${3}, format: ${4}, regex: ${5}) ${6}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ignore list messages.", NULL}}, {"format.ui.ignores.end", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ignore list footer.", NULL}}, {"format.ui.ignores.added", 1, Val_string, .str = "Ignore added: ${5} (server: ${1}, noact: ${2}, format: ${3}, regex: ${4})", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of new ignores.", NULL}}, {"format.ui.grep.start", 1, Val_string, .str = "%{b}%{c:94}Results of ${1}:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of start of /grep output", NULL}}, {"format.ui.grep.end", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of end of /grep output", NULL}}, {"format.ui.alias", 1, Val_string, .str = " ${1}: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of /alias output", NULL}}, {"format.ui.alias.start", 1, Val_string, .str = "Aliases:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of header of /alias output", NULL}}, {"format.ui.alias.end", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of footer of /alias output", NULL}}, {"format.ui.help", 1, Val_string, .str = " ${1}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of /alias output", NULL}}, {"format.ui.help.start", 1, Val_string, .str = "${1} help:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of header of /alias output", NULL}}, {"format.ui.help.end", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of footer of /alias output", NULL}}, {"format.ui.buflist.old", 1, Val_string, .str = "%{c:91}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Indicator for disconnected servers or parted channels", NULL}}, {"format.ui.buflist.activity.none", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Indicator for buffer with activity of level `none`", NULL}}, {"format.ui.buflist.activity.status", 1, Val_string, .str = "%{c:95}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Indicator for buffer with activity of level `status`", NULL}}, {"format.ui.buflist.activity.error", 1, Val_string, .str = "%{c:28}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Indicator for buffer with activity of level `error`", NULL}}, {"format.ui.buflist.activity.message", 1, Val_string, .str = "%{c:45}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Indicator for buffer with activity of level `message`", NULL}}, {"format.ui.buflist.activity.hilight", 1, Val_string, .str = "%{c:45}%{r}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Indicator for buffer with activity of level `hilight`", NULL}}, {"format.ui.buflist.more", 1, Val_string, .str = "%{c:92}...", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Shown if there are more nicks that must be scrolled to see.", NULL}}, {"format.ui.nicklist.more", 1, Val_string, .str = "%{c:92}...", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Shown if there are more nicks that must be scrolled to see.", NULL}}, {"format.ui.separator.vertical", 1, Val_string, .str = "│", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Used for vertical line seperating windows", NULL}}, {"format.ui.separator.split.left", 1, Val_string, .str = "├", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Joins left vertical separator to input seperator", NULL}}, {"format.ui.separator.split.right", 1, Val_string, .str = "┤", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Joins right vertical separator to input seperator", NULL}}, {"format.ui.separator.horizontal", 1, Val_string, .str = "─", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Used for horizontal line separating input and main window", NULL}}, {"format.privmsg", 1, Val_string, .str = "%{nick:${nick}}${priv}${nick}%{o}%{=}${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of messages", NULL}}, {"format.action", 1, Val_string, .str = "%{nick:${nick}}*%{b}${nick}%{b}%{=}${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of actions", NULL}}, {"format.ctcp.request", 1, Val_string, .str = "%{nick:${nick}}${nick}%{o} %{c:94}%{b}q%{o}%{=}${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of CTCP requests", NULL}}, {"format.ctcp.answer", 1, Val_string, .str = "%{nick:${nick}}${nick}%{o} %{c:94}%{b}a%{o}%{=}${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of CTCP answers", NULL}}, {"format.notice", 1, Val_string, .str = "%{nick:${nick}}-${priv}${nick}-%{o}%{=}${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of notices", NULL}}, {"format.nick", 1, Val_string, .str = "%{nick:${nick}}${nick}%{o}%{=}is now known as %{nick:${1}}${1}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of NICK messages", NULL}}, {"format.join", 1, Val_string, .str = "%{b}%{c:44}+%{o}%{=}%{nick:${nick}}${nick}%{o} (${ident}@${host})", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of JOIN messages", NULL}}, {"format.quit", 1, Val_string, .str = "%{b}%{c:40}<%{o}%{=}%{nick:${nick}}${nick}%{o} (${ident}@${host}): ${1}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of QUIT messages", NULL}}, {"format.part", 1, Val_string, .str = "%{b}%{c:40}-%{o}%{=}%{nick:${nick}}${nick}%{o} (${ident}@${host}): ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of PART messages", NULL}}, {"format.kick", 1, Val_string, .str = "%{b}%{c:40}!%{o}%{=}%{nick:${2}}${2}${o} by %{nick:${nick}}${nick}%{o} (${ident}@${host}): ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of PART messages", NULL}}, {"format.mode.nick.self", 1, Val_string, .str = "${1} set %{c:94}${2-}%{o}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of modes being set on self by server/self", NULL}}, {"format.mode.nick", 1, Val_string, .str = "${1} set %{c:94}${2-}%{o} by ${nick} (${ident}@${host})", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of modes being on nicks", NULL}}, {"format.mode.channel", 1, Val_string, .str = "mode%{=}%{c:94}${2-}%{o} by %{nick:${nick}}${nick}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of modes being set on channels", NULL}}, {"format.topic", 1, Val_string, .str = "topic%{=}${2}\\nset by %{nick:${nick}}${nick}%{o} now", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of topic being set", NULL}}, {"format.invite", 1, Val_string, .str = "%{nick:${nick}}${nick}%{o} invited you to ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of an invitation being received.", NULL}}, {"format.pong", 1, Val_string, .str = "PONG from %{nick:${nick}}${nick}%{o}: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of replies to /ping", NULL}}, {"format.error", 1, Val_string, .str = "%{c:28}ERROR:%{o} ${1}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of generic ERROR messages.", "Most commonly seen when being /kill'd.", NULL}}, /* Generic numerics (bit boring) */ {"format.rpl.welcome", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WELCOME (001) numeric", NULL}}, {"format.rpl.yourhost", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_YOURHOST (002) numeric", NULL}}, {"format.rpl.created", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_CREATED (003) numeric", NULL}}, {"format.rpl.myinfo", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_MYINFO (004) numeric", NULL}}, {"format.rpl.isupport", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_MYSUPPORT (005) numeric", NULL}}, {"format.rpl.map", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_MAP (006) numeric", NULL}}, {"format.rpl.mapend", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_MAPEND (007) numeric", NULL}}, /* START: misc/rpl-conf-gen.awk */ {"format.rpl.tracelink", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACELINK (200) numeric", NULL}}, {"format.rpl.traceconnecting", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACECONNECTING (201) numeric", NULL}}, {"format.rpl.tracehandshake", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACEHANDSHAKE (202) numeric", NULL}}, {"format.rpl.traceunknown", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACEUNKNOWN (203) numeric", NULL}}, {"format.rpl.traceoperator", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACEOPERATOR (204) numeric", NULL}}, {"format.rpl.traceuser", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACEUSER (205) numeric", NULL}}, {"format.rpl.traceserver", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACESERVER (206) numeric", NULL}}, {"format.rpl.tracenewtype", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACENEWTYPE (208) numeric", NULL}}, {"format.rpl.traceclass", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACECLASS (209) numeric", NULL}}, {"format.rpl.statslinkinfo", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSLINKINFO (211) numeric", NULL}}, {"format.rpl.statscommands", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSCOMMANDS (212) numeric", NULL}}, {"format.rpl.statscline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSCLINE (213) numeric", NULL}}, {"format.rpl.statsnline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSNLINE (214) numeric", NULL}}, {"format.rpl.statsiline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSILINE (215) numeric", NULL}}, {"format.rpl.statskline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSKLINE (216) numeric", NULL}}, {"format.rpl.statsyline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSYLINE (218) numeric", NULL}}, {"format.rpl.endofstats", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFSTATS (219) numeric", NULL}}, {"format.rpl.umodeis", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_UMODEIS (221) numeric", NULL}}, {"format.rpl.serviceinfo", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_SERVICEINFO (231) numeric", NULL}}, {"format.rpl.service", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_SERVICE (233) numeric", NULL}}, {"format.rpl.servlistend", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_SERVLISTEND (235) numeric", NULL}}, {"format.rpl.statslline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSLLINE (241) numeric", NULL}}, {"format.rpl.statsuptime", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSUPTIME (242) numeric", NULL}}, {"format.rpl.statsoline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSOLINE (243) numeric", NULL}}, {"format.rpl.statshline", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_STATSHLINE (244) numeric", NULL}}, {"format.rpl.luserclient", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LUSERCLIENT (251) numeric", NULL}}, {"format.rpl.luserop", 1, Val_string, .str = "There are ${2} opers online", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LUSEROP (252) numeric", NULL}}, {"format.rpl.luserunknown", 1, Val_string, .str = "There are ${2} unknown connections", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LUSERUNKNOWN (253) numeric", NULL}}, {"format.rpl.luserchannels", 1, Val_string, .str = "There are ${2} channels formed", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LUSERCHANNELS (254) numeric", NULL}}, {"format.rpl.luserme", 1, Val_string, .str = "There are %{split:3, ,${2}} clients and %{split:6, ,${2}} servers connected to this server", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LUSERME (255) numeric", NULL}}, {"format.rpl.adminme", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ADMINME (256) numeric", NULL}}, {"format.rpl.adminloc1", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ADMINLOC1 (257) numeric", NULL}}, {"format.rpl.adminloc2", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ADMINLOC2 (258) numeric", NULL}}, {"format.rpl.adminemail", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ADMINEMAIL (259) numeric", NULL}}, {"format.rpl.tracelog", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TRACELOG (261) numeric", NULL}}, {"format.rpl.none", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_NONE (300) numeric", NULL}}, {"format.rpl.away", 1, Val_string, .str = "away%{=}%{nick:${2}}${2}%{o}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_AWAY (301) numeric", NULL}}, {"format.rpl.userhost", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_USERHOST (302) numeric", NULL}}, {"format.rpl.ison", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ISON (303) numeric", NULL}}, {"format.rpl.unaway", 1, Val_string, .str = "%{c:40}<--%{o}%{=}No longer %{b}away%{b}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_UNAWAY (305) numeric", NULL}}, {"format.rpl.nowaway", 1, Val_string, .str = "%{c:32}-->%{o}%{=}Set %{b}away%{b}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_NOWAWAY (306) numeric", NULL}}, {"format.rpl.whoisuser", 1, Val_string, .str = "%{b}${2}!${3}@${4}%{b} (${6}):", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISUSER (311) numeric", NULL}}, {"format.rpl.whoisserver", 1, Val_string, .str = " %{b}server %{b}: ${3} (${4})", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISSERVER (312) numeric", NULL}}, {"format.rpl.whoisoperator", 1, Val_string, .str = " %{b}oper %{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISOPERATOR (313) numeric", NULL}}, {"format.rpl.whowasuser", 1, Val_string, .str = "%{b}${2}!${3}@${4}%{b} (${6}) was on:", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOWASUSER (314) numeric", NULL}}, {"format.rpl.endofwho", 1, Val_string, .str = "End of WHO results for ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFWHO (315) numeric", NULL}}, {"format.rpl.whoisidle", 1, Val_string, .str = " %{b}signon %{b}: %{time:%c,${4}}, idle: %{rdate:${3}}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISIDLE (317) numeric", NULL}}, {"format.rpl.endofwhois", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFWHOIS (318) numeric", NULL}}, {"format.rpl.whoischannels", 1, Val_string, .str = " %{b}channels%{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISCHANNELS (319) numeric", NULL}}, {"format.rpl.liststart", 1, Val_string, .str = "%{pad:-15,Channel} %{pad:-5,Nicks} Topic", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LISTSTART (321) numeric", NULL}}, {"format.rpl.list", 1, Val_string, .str = "%{pad:-15,${2}} %{pad:-5,${3}} ${4}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LIST (322) numeric", NULL}}, {"format.rpl.listend", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LISTEND (323) numeric", NULL}}, {"format.rpl.channelmodeis", 1, Val_string, .str = "mode%{=}%{c:94}${3-}%{o}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_CHANNELMODEIS (324) numeric", NULL}}, {"format.rpl.notopic", 1, Val_string, .str = "topic%{=}no topic set", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_NOTOPIC (331) numeric", NULL}}, {"format.rpl.topic", 1, Val_string, .str = "topic%{=}${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TOPIC (332) numeric", NULL}}, {"format.rpl.inviting", 1, Val_string, .str = "invite%{=}${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_INVITING (341) numeric", NULL}}, {"format.rpl.summoning", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_SUMMONING (342) numeric", NULL}}, {"format.rpl.version", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_VERSION (351) numeric", NULL}}, {"format.rpl.whoreply", 1, Val_string, .str = "%{b}${6}!${3}@${4}%{b} (%{split:2, ,${8}}): ${7} %{split:1, ,${8}}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOREPLY (352) numeric", NULL}}, {"format.rpl.namreply", 1, Val_string, .str = "names%{=}${4-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_NAMREPLY (353) numeric", NULL}}, {"format.rpl.closing", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_CLOSING (362) numeric", NULL}}, {"format.rpl.links", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_LINKS (364) numeric", NULL}}, {"format.rpl.endoflinks", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFLINKS (365) numeric", NULL}}, {"format.rpl.endofnames", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFNAMES (366) numeric", NULL}}, {"format.rpl.banlist", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_BANLIST (367) numeric", NULL}}, {"format.rpl.endofbanlist", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFBANLIST (368) numeric", NULL}}, {"format.rpl.endofwhowas", 1, Val_string, .str = "", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFWHOWAS (369) numeric", NULL}}, {"format.rpl.info", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_INFO (371) numeric", NULL}}, {"format.rpl.motd", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_MOTD (372) numeric", NULL}}, {"format.rpl.infostart", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_INFOSTART (373) numeric", NULL}}, {"format.rpl.endofinfo", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFINFO (374) numeric", NULL}}, {"format.rpl.motdstart", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_MOTDSTART (375) numeric", NULL}}, {"format.rpl.endofmotd", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFMOTD (376) numeric", NULL}}, {"format.rpl.youreoper", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_YOUREOPER (381) numeric", NULL}}, {"format.rpl.rehashing", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_REHASHING (382) numeric", NULL}}, {"format.rpl.time", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TIME (391) numeric", NULL}}, {"format.rpl.usersstart", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_USERSSTART (392) numeric", NULL}}, {"format.rpl.users", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_USERS (393) numeric", NULL}}, {"format.rpl.endofusers", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_ENDOFUSERS (394) numeric", NULL}}, {"format.rpl.nousers", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_NOUSERS (395) numeric", NULL}}, {"format.err.nosuchnick", 1, Val_string, .str = "No such nick: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOSUCHNICK (401) numeric", NULL}}, {"format.err.nosuchserver", 1, Val_string, .str = "No such server: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOSUCHSERVER (402) numeric", NULL}}, {"format.err.nosuchchannel", 1, Val_string, .str = "No such channel: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOSUCHCHANNEL (403) numeric", NULL}}, {"format.err.cannotsendtochan", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_CANNOTSENDTOCHAN (404) numeric", NULL}}, {"format.err.toomanychannels", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_TOOMANYCHANNELS (405) numeric", NULL}}, {"format.err.wasnosuchnick", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_WASNOSUCHNICK (406) numeric", NULL}}, {"format.err.toomanytargets", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_TOOMANYTARGETS (407) numeric", NULL}}, {"format.err.noorigin", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOORIGIN (409) numeric", NULL}}, {"format.err.norecipient", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NORECIPIENT (411) numeric", NULL}}, {"format.err.notexttosend", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOTEXTTOSEND (412) numeric", NULL}}, {"format.err.notoplevel", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOTOPLEVEL (413) numeric", NULL}}, {"format.err.wildtoplevel", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_WILDTOPLEVEL (414) numeric", NULL}}, {"format.err.unknowncommand", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_UNKNOWNCOMMAND (421) numeric", NULL}}, {"format.err.nomotd", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOMOTD (422) numeric", NULL}}, {"format.err.noadmininfo", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOADMININFO (423) numeric", NULL}}, {"format.err.fileerror", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_FILEERROR (424) numeric", NULL}}, {"format.err.nonicknamegiven", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NONICKNAMEGIVEN (431) numeric", NULL}}, {"format.err.erroneusnickname", 1, Val_string, .str = "Erroneous nickname: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_ERRONEUSNICKNAME (432) numeric", NULL}}, {"format.err.nicknameinuse", 1, Val_string, .str = "Nickname already in use: ${2}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NICKNAMEINUSE (433) numeric", NULL}}, {"format.err.nickcollision", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NICKCOLLISION (436) numeric", NULL}}, {"format.err.usernotinchannel", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_USERNOTINCHANNEL (441) numeric", NULL}}, {"format.err.notonchannel", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOTONCHANNEL (442) numeric", NULL}}, {"format.err.useronchannel", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_USERONCHANNEL (443) numeric", NULL}}, {"format.err.nologin", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOLOGIN (444) numeric", NULL}}, {"format.err.summondisabled", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_SUMMONDISABLED (445) numeric", NULL}}, {"format.err.usersdisabled", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_USERSDISABLED (446) numeric", NULL}}, {"format.err.notregistered", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOTREGISTERED (451) numeric", NULL}}, {"format.err.needmoreparams", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NEEDMOREPARAMS (461) numeric", NULL}}, {"format.err.alreadyregistred", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_ALREADYREGISTRED (462) numeric", NULL}}, {"format.err.nopermforhost", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOPERMFORHOST (463) numeric", NULL}}, {"format.err.passwdmismatch", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_PASSWDMISMATCH (464) numeric", NULL}}, {"format.err.yourebannedcreep", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_YOUREBANNEDCREEP (465) numeric", NULL}}, {"format.err.youwillbebanned", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_YOUWILLBEBANNED (466) numeric", NULL}}, {"format.err.keyset", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_KEYSET (467) numeric", NULL}}, {"format.err.channelisfull", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_CHANNELISFULL (471) numeric", NULL}}, {"format.err.unknownmode", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_UNKNOWNMODE (472) numeric", NULL}}, {"format.err.inviteonlychan", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_INVITEONLYCHAN (473) numeric", NULL}}, {"format.err.bannedfromchan", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_BANNEDFROMCHAN (474) numeric", NULL}}, {"format.err.badchannelkey", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_BADCHANNELKEY (475) numeric", NULL}}, {"format.err.noprivileges", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOPRIVILEGES (481) numeric", NULL}}, {"format.err.chanoprivsneeded", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_CHANOPRIVSNEEDED (482) numeric", NULL}}, {"format.err.cantkillserver", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_CANTKILLSERVER (483) numeric", NULL}}, {"format.err.nooperhost", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOOPERHOST (491) numeric", NULL}}, {"format.err.noservicehost", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_NOSERVICEHOST (492) numeric", NULL}}, {"format.err.umodeunknownflag", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_UMODEUNKNOWNFLAG (501) numeric", NULL}}, {"format.err.usersdontmatch", 1, Val_string, .str = "${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of ERR_USERSDONTMATCH (502) numeric", NULL}}, /* END: misc/rpl-conf-gen.awk */ /* Modern numerics */ {"format.rpl.localusers", 1, Val_string, .str = "There are ${2} current local users, record of ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPLS_LOCALUSERS (265) numeric", NULL}}, {"format.rpl.globalusers", 1, Val_string, .str = "There are ${2} current global users, record of ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_GLOBALUSERS (266) numeric", NULL}}, {"format.rpl.whoisspecial", 1, Val_string, .str = " %{b}info %{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISSPECIAL (320) numeric", NULL}}, {"format.rpl.whoisaccount", 1, Val_string, .str = " %{b}account %{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISACCOUNT (330) numeric", NULL}}, {"format.rpl.topicwhotime", 1, Val_string, .str = "set by %{nick:${3}}${3}%{o} at %{time:%Y-%m-%d %H:%M:%S,${4}}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_TOPICWHOTIME (333) numeric", NULL}}, {"format.rpl.whoisactually", 1, Val_string, .str = " %{b}actually%{b}: ${3-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISACTUALLY (338) numeric", NULL}}, {"format.rpl.whoishost", 1, Val_string, .str = " %{b}info %{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISHOST (378) numeric", NULL}}, {"format.rpl.whoismodes", 1, Val_string, .str = " %{b}modes %{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISMODES (379) numeric", NULL}}, {"format.rpl.whoissecure", 1, Val_string, .str = " %{b}secure %{b}: ${3}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of RPL_WHOISSECURE (671) numeric", NULL}}, /* Default formats */ {"format.rpl.other", 1, Val_string, .str = "${cmd} ${2-}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of numerics without formats", NULL}}, {"format.other", 1, Val_string, .str = "${raw}", - .strhandle = config_redraws, + .strhandle = config_formats, .description = { "Format of other messages without formats", NULL}}, {NULL}, diff --git a/src/format.c b/src/format.c @@ -1,553 +0,0 @@ -/* - * src/format.c from hirc - * - * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk> - * - * Permission to use, copy, modify, and 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 <string.h> -#include <stdlib.h> -#include <ctype.h> -#include "hirc.h" -#include "data/formats.h" - -char * -format_get_bufact(int activity) { - switch (activity) { - case Activity_status: - return format(NULL, config_gets("format.ui.buflist.activity.status"), NULL); - case Activity_error: - return format(NULL, config_gets("format.ui.buflist.activity.error"), NULL); - case Activity_message: - return format(NULL, config_gets("format.ui.buflist.activity.message"), NULL); - case Activity_hilight: - return format(NULL, config_gets("format.ui.buflist.activity.hilight"), NULL); - default: - return format(NULL, config_gets("format.ui.buflist.activity.none"), NULL); - } - - return NULL; /* shouldn't be possible *shrug*/ -} - -char * -format_get(struct History *hist) { - char *cmd, *p1, *p2; - int i; - - assert_warn(hist, NULL); - - if (!hist->params) - goto raw; - - cmd = *(hist->params); - p1 = *(hist->params+1); - p2 = *(hist->params+2); - - if (strcmp_n(cmd, "MODE") == 0) { - if (p1 && serv_ischannel(hist->origin->server, p1)) - cmd = "MODE-CHANNEL"; - else if (hist->from && nick_isself(hist->from) && strcmp_n(hist->from->nick, p1) == 0) - cmd = "MODE-NICK-SELF"; - else - cmd = "MODE-NICK"; - } else if (strcmp_n(cmd, "PRIVMSG") == 0) { - /* ascii 1 is ^A */ - if (*p2 == 1 && strncmp(p2 + 1, "ACTION", CONSTLEN("ACTION")) == 0) - cmd = "PRIVMSG-ACTION"; - else if (*p2 == 1) - cmd = "PRIVMSG-CTCP"; - } else if (strcmp_n(cmd, "NOTICE") == 0 && *p2 == 1) { - cmd = "NOTICE-CTCP"; - } - - for (i=0; formatmap[i].cmd; i++) - if (formatmap[i].format && strcmp_n(formatmap[i].cmd, cmd) == 0) - return formatmap[i].format; - - if (isdigit(*cmd) && isdigit(*(cmd+1)) && isdigit(*(cmd+2)) && !*(cmd+3)) - return "format.rpl.other"; - -raw: - return "format.other"; -} - -static char * -format_get_content(char *sstr, int nesting) { - static char ret[8192]; - int layer, rc; - - for (layer = 0, rc = 0; sstr && *sstr && rc < sizeof(ret); sstr++) { - switch (*sstr) { - case '}': - if (nesting && layer) { - ret[rc++] = '}'; - layer--; - } else { - goto end; - } - break; - case '{': - if (nesting) - layer++; - ret[rc++] = '{'; - break; - default: - ret[rc++] = *sstr; - break; - } - } - -end: - ret[rc] = '\0'; - return ret; -} - -char * -format_(struct Window *window, char *format, struct History *hist, int recursive) { - static char *ret; - struct Nick *nick; - size_t rs = BUFSIZ; - size_t rc, pc; - int escape, i; - long long pn; - int rhs = 0; - int divider = 0; - char **params; - char *content, *p, *p2; - char *ts, *save; - char colourbuf[2][3]; - char priv[2]; - char chs[2]; - size_t len; - enum { - sub_raw, - sub_cmd, - sub_nick, - sub_ident, - sub_host, - sub_priv, - sub_channel, - sub_topic, - sub_server, - sub_time, - }; - struct { - char *name; - char *val; - } subs[] = { - [sub_raw] = {"raw", NULL}, - [sub_cmd] = {"cmd", NULL}, - [sub_nick] = {"nick", NULL}, - [sub_ident] = {"ident", NULL}, - [sub_host] = {"host", NULL}, - [sub_priv] = {"priv", NULL}, - [sub_channel] = {"channel", NULL}, - [sub_topic] = {"topic", NULL}, - [sub_server] = {"server", NULL}, - [sub_time] = {"time", NULL}, - {NULL, NULL}, - }; - - if (!format) - format = config_gets(format_get(hist)); - assert_warn(format, NULL); - - pfree(&ret); - ret = emalloc(rs); - - subs[sub_channel].val = selected.channel ? selected.channel->name : NULL; - subs[sub_topic].val = selected.channel ? selected.channel->topic : NULL; - subs[sub_server].val = selected.server ? selected.server->name : NULL; - - if (hist) { - subs[sub_raw].val = hist->raw; - subs[sub_nick].val = hist->from ? hist->from->nick : NULL; - subs[sub_ident].val = hist->from ? hist->from->ident : NULL; - subs[sub_host].val = hist->from ? hist->from->host : NULL; - - if (hist->from) { - priv[0] = hist->from->priv; - priv[priv[0] != ' '] = '\0'; - subs[sub_priv].val = priv; - } - - if (hist->origin) { - if (hist->origin->channel) { - if (!recursive) - divider = config_getl("divider.toggle"); - subs[sub_channel].val = hist->origin->channel->name; - subs[sub_topic].val = hist->origin->channel->topic; - } - if (hist->origin->server) { - subs[sub_server].val = hist->origin->server->name; - } - } - - len = snprintf(subs[sub_time].val, 0, "%lld", (long long)hist->timestamp) + 1; - subs[sub_time].val = emalloc(len); - snprintf(subs[sub_time].val, len, "%lld", (long long)hist->timestamp); - - params = hist->params; - subs[sub_cmd].val = *params; - params++; - } - - if (!recursive && hist && config_getl("timestamp.toggle")) { - ts = estrdup(format_(NULL, config_gets("format.ui.timestamp"), hist, 1)); - } else { - ts = ""; - } - - for (escape = 0, rc = 0; format && *format && rc < rs; ) { -outcont: - if (rc > rs / 2) { - rs *= 2; - ret = erealloc(ret, rs); - } - - if (!escape && *format == '$' && *(format+1) == '{' && strchr(format, '}')) { - escape = 0; - content = format_get_content(format+2, 0); - - for (p = content; *p && isdigit(*p); p++); - /* If all are digits, *p == '\0' */ - if (!*p && hist) { - pn = strtol(content, NULL, 10) - 1; - if (pn >= 0 && param_len(params) > pn) { - if (**(params+pn) == 1 && strncmp((*(params+pn))+1, "ACTION", CONSTLEN("ACTION")) == 0 && strchr(*(params+pn), ' ')) - rc += snprintf(&ret[rc], rs - rc, "%s", struntil(strchr(*(params+pn), ' ') + 1, 1)); - else if (**(params+pn) == 1) - rc += snprintf(&ret[rc], rs - rc, "%s", struntil((*(params+pn)) + 1, 1)); - else - rc += snprintf(&ret[rc], rs - rc, "%s", *(params+pn)); - format = strchr(format, '}') + 1; - continue; - } - } - /* All are digits except a trailing '-' */ - if (*p == '-' && *(p+1) == '\0' && hist) { - pn = strtol(content, NULL, 10) - 1; - if (pn >= 0 && param_len(params) > pn) { - for (; *(params+pn) != NULL; pn++) { - if (**(params+pn) == 1 && strncmp((*(params+pn))+1, "ACTION", CONSTLEN("ACTION")) == 0 && strchr(*(params+pn), ' ')) { - rc += snprintf(&ret[rc], rs - rc, "%s%s", - struntil(strchr(*(params+pn), ' ') + 1, 1), - *(params+pn+1) ? " " : ""); - } else if (**(params+pn) == 1) { - rc += snprintf(&ret[rc], rs - rc, "%s%s", - struntil((*(params+pn)) + 1, 1), - *(params+pn+1) ? " " : ""); - } else { - rc += snprintf(&ret[rc], rs - rc, "%s%s", - *(params+pn), *(params+pn+1) ? " " : ""); - } - } - format = strchr(format, '}') + 1; - continue; - } - } - - for (i=0; subs[i].name; i++) { - if (strcmp_n(subs[i].name, content) == 0) { - if (subs[i].val) - rc += snprintf(&ret[rc], rs - rc, "%s", subs[i].val); - format = strchr(format, '}') + 1; - goto outcont; /* unfortunately, need to use a goto as we are already in a loop */ - } - } - } - - if (!escape && *format == '%' && *(format+1) == '{' && strchr(format, '}')) { - escape = 0; - content = format_get_content(format+2, 0); - - switch (*content) { - case 'b': - case 'B': - ret[rc++] = 2; /* ^B */ - format = strchr(format, '}') + 1; - continue; - case 'c': - case 'C': - if (*(content+1) == ':' && isdigit(*(content+2))) { - content += 2; - memset(colourbuf, 0, sizeof(colourbuf)); - colourbuf[0][0] = *content; - content++; - if (isdigit(*content)) { - colourbuf[0][1] = *content; - content += 1; - } - if (*content == ',' && isdigit(*(content+1))) { - colourbuf[1][0] = *(content+1); - content += 2; - } - if (colourbuf[1][0] && isdigit(*content)) { - colourbuf[1][1] = *(content); - content += 1; - } - if (*content == '\0') { - rc += snprintf(&ret[rc], rs - rc, "%c%02d,%02d", 3 /* ^C */, - atoi(colourbuf[0]), colourbuf[1][0] ? atoi(colourbuf[1]) : 99); - format = strchr(format, '}') + 1; - continue; - } - } - break; - case 'i': - case 'I': - if (*(content+1) == '\0') { - ret[rc++] = 9; /* ^I */ - format = strchr(format, '}') + 1; - continue; - } - break; - case 'o': - case 'O': - if (*(content+1) == '\0') { - ret[rc++] = 15; /* ^O */ - format = strchr(format, '}') + 1; - continue; - } - break; - case 'r': - case 'R': - if (*(content+1) == '\0') { - ret[rc++] = 18; /* ^R */ - format = strchr(format, '}') + 1; - continue; - } - break; - case 'u': - case 'U': - if (*(content+1) == '\0') { - ret[rc++] = 21; /* ^U */ - format = strchr(format, '}') + 1; - continue; - } - break; - case '=': - if (*(content+1) == '\0' && divider) { - rhs = 1; - ret[rc] = '\0'; - /* strlen(ret) - ui_strlenc(window, ret, NULL) should get - * the length of hidden characters. Add this onto the - * margin to pad out properly. */ - /* Save ret for use in snprintf */ - save = estrdup(ret); - rc = snprintf(ret, rs, "%1$*3$s%2$s", save, config_gets("divider.string"), - (int)(config_getl("divider.margin") + (strlen(ret) - ui_strlenc(window, ret, NULL)))); - pfree(&save); - format = strchr(format, '}') + 1; - continue; - } else if (*(content+1) == '\0') { - ret[rc++] = ' '; - format = strchr(format, '}') + 1; - continue; - } - break; - } - - /* pad, nick, split, rdate and time, must then continue as they modify content - * - * These styling formatters are quite ugly and repetitive. - * %{nick:...} was implemented first, and has the most (all of them :)) comments */ - if (strncmp(content, "pad:", CONSTLEN("pad:")) == 0 && strchr(content, ',')) { - pn = strtol(content + CONSTLEN("pad:"), NULL, 10); - content = estrdup(format_get_content(strchr(format+2+CONSTLEN("pad:"), ',') + 1, 1)); - save = ret; - ret = NULL; - format_(NULL, content, hist, 1); - rc += snprintf(&save[rc], rs - rc, "%1$*2$s", ret, (int)pn); - pfree(&ret); - ret = save; - format = strchr(format+2+CONSTLEN("pad:"), ',') + strlen(content) + 2; - - pfree(&content); - continue; - } - - if (strncmp(content, "rdate:", CONSTLEN("rdate:")) == 0) { - content = estrdup(format_get_content(format+2+CONSTLEN("rdate:"), 1)); - save = ret; - ret = NULL; - format_(NULL, content, hist, 1); - pn = strtoll(ret, NULL, 10); - rc += snprintf(&save[rc], rs - rc, "%s", strrdate((time_t)pn)); - format += 3 + CONSTLEN("rdate:") + strlen(content); - - pfree(&ret); - ret = save; - pfree(&content); - continue; - } - - if (strncmp(content, "time:", CONSTLEN("time:")) == 0 && strchr(content, ',')) { - content = estrdup(format_get_content(strchr(format+2+CONSTLEN("time:"), ',') + 1, 1)); - save = ret; - ret = NULL; - format_(NULL, content, hist, 1); - pn = strtoll(ret, NULL, 10); - p = struntil(format+2+CONSTLEN("time:"), ','); - - rc += strftime(&save[rc], rs - rc, p, localtime((time_t *)&pn)); - format = strchr(format+2+CONSTLEN("time:"), ',') + strlen(content) + 2; - - pfree(&ret); - ret = save; - pfree(&content); - continue; - } - - /* second comma ptr - second comma ptr = distance. - * If the distance is 2, then there is one non-comma char between. */ - p = strchr(content, ','); - if (p) - p2 = strchr(p + 1, ','); - if (strncmp(content, "split:", CONSTLEN("split:")) == 0 && p2 - p == 2) { - pn = strtol(content + CONSTLEN("split:"), NULL, 10); - chs[0] = *(strchr(content, ',') + 1); - chs[1] = '\0'; - content = estrdup(format_get_content( - strchr( - strchr(format+2+CONSTLEN("split:"), ',') + 1, - ',') + 1, - 1)); - save = ret; - ret = NULL; - format_(NULL, content, hist, 1); - rc += snprintf(&save[rc], rs - rc, "%s", strntok(ret, chs, pn)); - format = strchr( - strchr(format+2+CONSTLEN("split:"), ',') + 1, - ',') + strlen(content) + 2; - - pfree(&ret); - ret = save; - pfree(&content); - continue; - } - - if (hist && !recursive && strncmp(content, "nick:", CONSTLEN("nick:")) == 0) { - content = estrdup(format_get_content(format+2+CONSTLEN("nick:"), 1)); - save = ret; - ret = NULL; - format_(NULL, content, hist, 1); - nick = nick_create(ret, ' ', hist->origin ? hist->origin->server : NULL); - rc += snprintf(&save[rc], rs - rc, "%c%02d", 3 /* ^C */, nick_getcolour(nick)); - format += 3 + CONSTLEN("nick:") + strlen(content); - - pfree(&ret); - ret = save; - nick_free(nick); - pfree(&content); - continue; - } - } - - if (escape && *format == 'n') { - ret[rc++] = '\n'; - rc += snprintf(&ret[rc], rs - rc, "%1$*3$s%2$s", "", config_gets("divider.string"), - (int)(ui_strlenc(NULL, ts, NULL) + config_getl("divider.margin"))); - escape = 0; - format++; - continue; - } - - if (escape && (*format == '%' || *format == '$') && *(format+1) == '{' && strchr(format, '}')) - escape = 0; - - if (escape) { - ret[rc++] = '\\'; - escape = 0; - } - - if (*format == '\\') { - escape = 1; - format++; - } else { - ret[rc++] = *format; - format++; - } - } - - ret[rc] = '\0'; - if (!recursive && divider && !rhs) { - save = estrdup(ret); - rc = snprintf(ret, rs, "%1$*4$s%2$s%3$s", "", config_gets("divider.string"), save, (int)config_getl("divider.margin")); - pfree(&save); - } - - save = estrdup(ret); - rc = snprintf(ret, rs, "%s%s", ts, save); - pfree(&save); - - if (!recursive && window) { - for (p = ret, pc = 0; p && p <= (ret + rs); p++) { - /* lifted from ui_strlenc */ - switch (*p) { - case 2: /* ^B */ - case 9: /* ^I */ - case 15: /* ^O */ - case 18: /* ^R */ - case 21: /* ^U */ - break; - case 3: /* ^C */ - if (*p && isdigit(*(p+1))) - p += 1; - if (*p && isdigit(*(p+1))) - p += 1; - if (*p && *(p+1) == ',' && isdigit(*(p+2))) - p += 2; - if (*p && *(p-1) == ',' && isdigit(*(p+1))) - p += 1; - break; - default: - /* naive utf-8 handling: - * the 2-nth byte always - * follows 10xxxxxxx, so - * don't count it. */ - if ((*p & 0xC0) != 0x80) - pc++; - - if (*p == '\n') { - p++; - pc = 0; - } - - if (pc == window->w) { - save = estrdup(p); - - if (divider) { - p += snprintf(p, rs - ((size_t)(p - ret)), "%1$*4$s %2$s%3$s", - "", config_gets("divider.string"), save, - (int)config_getl("divider.margin") + ui_strlenc(NULL, ts, NULL)); - } else { - p += snprintf(p, rs - ((size_t)(p - ret)), "%1$*3$s %2$s", "", save, ui_strlenc(NULL, ts, NULL)); - } - - pfree(&save); - pc = 0; - } - } - } - } - - if (subs[sub_time].val) - pfree(&subs[sub_time].val); - if (ts[0] != '\0') - pfree(&ts); - - return ret; -} diff --git a/src/format.y b/src/format.y @@ -0,0 +1,540 @@ +%{ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <ctype.h> +#include "hirc.h" +#include "data/formats.h" + +#define YYSTYPE char * + +static void parse_append(char **dest, char *str); +static char *parse_dup(char *str); +static char *parse_printf(char *fmt, ...); +static void yyerror(char *fmt, ...); +static int yylex(void); + +#define BRACEMAX 16 +#define RETURN(s) do {if (s == STRING) fprintf(stderr, "[lex \"%s\"]\n", yylval); else fprintf(stderr, "[lex %s]\n", #s); prev = s; return s;} while (0) +#define ISSPECIAL(s) isspecial(s, prev, bracelvl, bracetype, styleseg) + +enum { + PARSE_TIME, + PARSE_LEFT, + PARSE_RIGHT, + PARSE_LAST +}; + +static char *parse_in = NULL; +static char *parse_out[PARSE_LAST] = {NULL, NULL, NULL}; +static int parse_pos = PARSE_LEFT; +static char **parse_params = NULL; + +enum { + var_raw, + var_cmd, + var_nick, + var_ident, + var_host, + var_priv, + var_channel, + var_topic, + var_server, + var_time, +}; +static struct { + char *name; + char *val; +} vars[] = { + [var_raw] = {"raw", NULL}, + [var_cmd] = {"cmd", NULL}, + [var_nick] = {"nick", NULL}, + [var_ident] = {"ident", NULL}, + [var_host] = {"host", NULL}, + [var_priv] = {"priv", NULL}, + [var_channel] = {"channel", NULL}, + [var_topic] = {"topic", NULL}, + [var_server] = {"server", NULL}, + [var_time] = {"time", NULL}, + {NULL, NULL}, +}; +%} + +%token VAR STYLE LBRACE RBRACE COLON COMMA +%token STRING + +%% + +grammar: /* empty */ { parse_append(&parse_out[parse_pos], ""); } + | grammar var { parse_append(&parse_out[parse_pos], $2); } + | grammar style { parse_append(&parse_out[parse_pos], $2); } + | grammar STRING { parse_append(&parse_out[parse_pos], $2); } + ; + +var: VAR LBRACE STRING RBRACE { + char buf[8192]; + int i, num; + if (strisnum($3, 0) && (num = strtoll($3, NULL, 10)) && param_len(parse_params) >= num) { + $$ = *(parse_params + num - 1); + goto varfin; + } + fprintf(stderr, "{var: %s}\n", $3); + if (*$3 && *($3 + strlen($3) - 1) == '-') { + *($3 + strlen($3) - 1) = '\0'; + fprintf(stderr, "{var without -: %s}\n", $3); + if (strisnum($3, 0) && (num = strtoll($3, NULL, 10)) && param_len(parse_params) >= num) { + buf[0] = '\0'; + for (i = num; i <= param_len(parse_params); i++) { + strlcat(buf, *(parse_params + i - 1), sizeof(buf)); + strlcat(buf, " ", sizeof(buf)); + } + $$ = parse_dup(buf); + goto varfin; + } else { + *($3 + strlen($3) - 1) = '\0'; + } + } + for (i = 0; vars[i].name; i++) { + if (vars[i].val && strcmp(vars[i].name, $3) == 0) { + $$ = parse_printf("%s", vars[i].val); + goto varfin; + } + } + $$ = parse_printf("${%s}", $3); +varfin: + ((void)0); + } + ; + +style: STYLE LBRACE STRING RBRACE { + if (strcmp($3, "b") == 0) { + $$ = parse_printf("%c", 2 /* ^B */); + } else if (strcmp($3, "c") == 0) { + $$ = parse_printf("%c99,99", 3 /* ^C */); + } else if (strcmp($3, "i") == 0) { + $$ = parse_printf("%c", 9 /* ^I */); + } else if (strcmp($3, "o") == 0) { + $$ = parse_printf("%c", 15 /* ^O */); + } else if (strcmp($3, "r") == 0) { + $$ = parse_printf("%c", 18 /* ^R */); + } else if (strcmp($3, "u") == 0) { + $$ = parse_printf("%c", 21 /* ^U */); + } else if (strcmp($3, "=") == 0) { + if (parse_pos == PARSE_LEFT) { + parse_pos = PARSE_RIGHT; + $$ = parse_dup(""); + } else { + $$ = parse_dup(" "); + } + } else if (strcmp($3, "_time") == 0) { /* special style to mark end of timestamp */ + if (parse_pos == PARSE_TIME) { + parse_pos = PARSE_LEFT; /* let's hope no-one puts it in format.ui.timestamp */ + $$ = parse_dup(""); + } else { + $$ = parse_dup("%{_time}"); /* in the unlikely event some uses it somewhere else.. */ + } + } else { + $$ = parse_printf("%%{%s}", $3); + } + } + | STYLE LBRACE STRING COLON sstring RBRACE { + struct Nick *nick; + if (strcmp($3, "c") == 0) { + if (strlen($5) <= 2 && isdigit(*$5) && (!*($5+1) || isdigit(*($5+1)))) + $$ = parse_printf("%c%02d", 3 /* ^C */, atoi($5)); + } else if (strcmp($3, "nick") == 0) { + nick = nick_create($5, ' ', NULL); + $$ = parse_printf("%c%02d", 3 /* ^C */, nick_getcolour(nick)); + nick_free(nick); + } else if (strcmp($3, "rdate") == 0) { + if (strisnum($5, 0)) + $$ = parse_printf("%s", strrdate((time_t)strtoll($5, NULL, 10))); + else + $$ = parse_printf("%%{%s:%s}", $3, $5); + } else { + $$ = parse_printf("%%{%s:%s}", $3, $5); + } + } + | STYLE LBRACE STRING COLON sstring COMMA sstring RBRACE { + char stime[1024]; + time_t dtime; + if (strcmp($3, "c") == 0) { + if (strlen($5) <= 2 && isdigit(*$5) && (!*($5+1) || isdigit(*($5+1))) && + strlen($7) <= 2 && isdigit(*$7) && (!*($7+1) || isdigit(*($5+1)))) + $$ = parse_printf("%c%02d,%02d", 3 /* ^C */, atoi($5), atoi($7)); + else + $$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7); + } else if (strcmp($3, "pad") == 0) { + if (strisnum($5, 1)) + $$ = parse_printf("%1$*2$s", $7, (int)strtoll($5, NULL, 0)); + else + $$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7); + } else if (strcmp($3, "time") == 0) { + if (strisnum($7, 0)) { + dtime = (time_t)strtoll($7, NULL, 0); + strftime(stime, sizeof(stime), $5, localtime(&dtime)); + $$ = parse_dup(stime); + } else { + $$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7); + } + } else { + $$ = parse_printf("%%{%s:%s,%s}", $3, $5, $7); + } + } + | STYLE LBRACE STRING COLON sstring COMMA sstring COMMA sstring RBRACE { + int num; + char *val; + if (strcmp($3, "split") == 0) { + num = strtoll($5, NULL, 10); + val = strntok($9, $7, num); + if (strisnum($5, 0) && val) { + $$ = parse_dup(val); + } else { + $$ = parse_printf("%%{%s:%s,%s,%s}", $3, $5, $7, $9); + } + } else { + $$ = parse_printf("%%{%s:%s,%s,%s}", $3, $5, $7, $9); + } + } + ; + +sstring: STRING + | var + | style + ; + +%% + +static void +yyerror(char *fmt, ...) { + char msg[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + + pfree(&parse_out); /* format() will return NULL */ + ui_error("parsing '%s': %s", parse_in, msg); +} + +/* Keep in mind: parse_append doesn't use parse_dup. */ +static void +parse_append(char **dest, char *str) { + size_t size; + size_t len; + + if (*dest) + len = strlen(*dest); + else + len = 0; + + size = len + strlen(str) + 1; + if (!*dest) + *dest = emalloc(size); + else + *dest = erealloc(*dest, size); + + (len ? strlcat : strlcpy)(*dest, str, size); +} + +/* alloc memory to be free'd all at once when parsing is complete + * pfree() must not be called on anything returned */ +static char * +parse_dup(char *str) { + static void **mema = NULL; + static size_t mems = 0; + void *mem = NULL; + size_t i; + + if (str) { + mem = strdup(str); + if (!mems) + mema = emalloc((sizeof(char *)) * (mems + 1)); + else + mema = erealloc(mema, (sizeof(char *)) * (mems + 1)); + + *(mema + mems) = mem; + mems++; + } else if (mema && mems) { + for (i = 0; i < mems; i++) + pfree(mema + i); /* already a double pointer */ + pfree(&mema); + mems = 0; + mema = NULL; + } + + return mem; +} + +static char * +parse_printf(char *format, ...) { + va_list ap; + char buf[8192]; + + va_start(ap, format); + vsnprintf(buf, sizeof(buf), format, ap); + va_end(ap); + return parse_dup(buf); +} + +static int +isspecial(char *s, int prev, int bracelvl, int bracetype[static BRACEMAX], int styleseg[static BRACEMAX]) { + if ((*s == '$' && (*(s+1) == '{' && bracelvl != BRACEMAX)) || + (*s == '%' && (*(s+1) == '{' && bracelvl != BRACEMAX)) || + (*s == '{' && (prev == VAR || prev == STYLE)) || + (*s == '}' && (bracelvl)) || + (*s == ',' && (bracelvl && bracetype[bracelvl - 1] == STYLE && styleseg[bracelvl - 1])) || + (*s == ':' && (bracelvl && bracetype[bracelvl - 1] == STYLE && !styleseg[bracelvl - 1]))) + return 1; + else + return 0; +} + +static int +yylex(void) { + static char *s = NULL, *p = NULL; + static int bracelvl; + static int bracetype[BRACEMAX]; + static int styleseg[BRACEMAX]; + static int prev = 0; + char strlval[8192]; + int i; + + if (!s || prev == 0) { + s = parse_in; + bracelvl = 0; + prev = -1; + } + + if (!*s) + RETURN(0); + + fprintf(stderr, "s (lex): %s\n", s); + + if (ISSPECIAL(s)) { + switch (*s) { + case '$': + s++; + RETURN(VAR); + case '%': + s++; + RETURN(STYLE); + case '{': + bracelvl++; + bracetype[bracelvl - 1] = prev; + if (prev == STYLE) + styleseg[bracelvl - 1] = 0; + s++; + RETURN(LBRACE); + case '}': + bracelvl--; + s++; + RETURN(RBRACE); + case ',': + styleseg[bracelvl - 1]++; + s++; + RETURN(COMMA); + case ':': + styleseg[bracelvl - 1]++; + s++; + RETURN(COLON); + } + } + + /* first char guaranteed due to previous ISSPECIAL() */ + strlval[0] = *s; + + for (p = s + 1, i = 1; *p && i < sizeof(strlval); p++) { + fprintf(stderr, "p (lex): %s\n", s); + if (ISSPECIAL(p)) { + fprintf(stderr, "<breaking in lex due to special>\n"); + break; + } + if (*p == '\\' && ISSPECIAL(p+1)) { + fprintf(stderr, "<escaping special in lex>\n"); + strlval[i++] = *(p+1); + p++; + } else if (*p == '\\' && *(p+1) == 'n') { + strlval[i++] = '\n'; + p++; + } else { + strlval[i++] = *p; + } + } + + strlval[i] = '\0'; + yylval = parse_dup(strlval); + s = p; + RETURN(STRING); +} + +/* + * Exposed functions + */ + +char * +format_get(struct History *hist) { + char *cmd, *p1, *p2; + int i; + + assert_warn(hist, NULL); + + if (!hist->params) + goto raw; + + cmd = *(hist->params); + p1 = *(hist->params+1); + p2 = *(hist->params+2); + + if (strcmp_n(cmd, "MODE") == 0) { + if (p1 && serv_ischannel(hist->origin->server, p1)) + cmd = "MODE-CHANNEL"; + else if (hist->from && nick_isself(hist->from) && strcmp_n(hist->from->nick, p1) == 0) + cmd = "MODE-NICK-SELF"; + else + cmd = "MODE-NICK"; + } else if (strcmp_n(cmd, "PRIVMSG") == 0) { + /* ascii 1 is ^A */ + if (*p2 == 1 && strncmp(p2 + 1, "ACTION", CONSTLEN("ACTION")) == 0) + cmd = "PRIVMSG-ACTION"; + else if (*p2 == 1) + cmd = "PRIVMSG-CTCP"; + } else if (strcmp_n(cmd, "NOTICE") == 0 && *p2 == 1) { + cmd = "NOTICE-CTCP"; + } + + for (i=0; formatmap[i].cmd; i++) + if (formatmap[i].format && strcmp_n(formatmap[i].cmd, cmd) == 0) + return formatmap[i].format; + + if (isdigit(*cmd) && isdigit(*(cmd+1)) && isdigit(*(cmd+2)) && !*(cmd+3)) + return "format.rpl.other"; + +raw: + return "format.other"; +} + +char * +format(struct Window *window, char *format, struct History *hist) { + static char *ret; + char *ts; + char *rformat; + size_t len; + int clen[PARSE_LAST]; /* ui_strlenc */ + int alen[PARSE_LAST]; /* strlen */ + int divlen = config_getl("divider.margin"); + int divbool = 0; + char *divstr = config_gets("divider.string"); + char priv[2]; + int i; + + assert_warn(format || hist, NULL); + if (!format) + format = config_gets(format_get(hist)); + assert_warn(format, NULL); + + if (hist && config_getl("timestamp.toggle")) { + ts = config_gets("format.ui.timestamp"); + len = strlen(ts) + strlen(format) + CONSTLEN("%{_time}") + 1; + rformat = emalloc(len); + snprintf(rformat, len, "%s%%{_time}%s", ts, format); + parse_pos = PARSE_TIME; + } else { + rformat = strdup(format); + parse_pos = PARSE_LEFT; + } + + vars[var_channel].val = selected.channel ? selected.channel->name : NULL; + vars[var_topic].val = selected.channel ? selected.channel->topic : NULL; + vars[var_server].val = selected.server ? selected.server->name : NULL; + + if (hist) { + vars[var_raw].val = hist->raw; + vars[var_nick].val = hist->from ? hist->from->nick : NULL; + vars[var_ident].val = hist->from ? hist->from->ident : NULL; + vars[var_host].val = hist->from ? hist->from->host : NULL; + + if (hist->from) { + priv[0] = hist->from->priv; + priv[priv[0] != ' '] = '\0'; + vars[var_priv].val = priv; + } + + if (hist->origin) { + if (hist->origin->channel) { + divbool = config_getl("divider.toggle"); + vars[var_channel].val = hist->origin->channel->name; + vars[var_topic].val = hist->origin->channel->topic; + } + if (hist->origin->server) { + vars[var_server].val = hist->origin->server->name; + } + } + + len = snprintf(vars[var_time].val, 0, "%lld", (long long)hist->timestamp) + 1; + vars[var_time].val = emalloc(len); + snprintf(vars[var_time].val, len, "%lld", (long long)hist->timestamp); + + vars[var_cmd].val = *hist->params; + if (hist->params) + parse_params = hist->params + 1; + } else parse_params = NULL; + + fprintf(stderr, "---\nrformat: %s\n", rformat); + + parse_dup(0); /* free memory in use for last parse_ */ + for (i = 0; i < PARSE_LAST; i++) + pfree(&parse_out[i]); + pfree(&ret); + parse_in = rformat; + + yyparse(); + + if (config_getl("timestamp.toggle")) + pfree(&rformat); + + /* TODO: remove DEBUG */ + fprintf(stderr, "format: %s\n", format); + if (hist) { + fprintf(stderr, "hist->raw: %s\n", hist->raw); + for (i = 0; i < param_len(hist->params); i++) + fprintf(stderr, "hist->params[%d]: %s\n", i, *(hist->params + i)); + } + fprintf(stderr, "parse_out[PARSE_TIME]: %s\n", parse_out[PARSE_TIME]); + fprintf(stderr, "parse_out[PARSE_LEFT]: %s\n", parse_out[PARSE_LEFT]); + fprintf(stderr, "parse_out[PARSE_RIGHT]: %s\n", parse_out[PARSE_RIGHT]); + + /* If there is no %{=}, then it's on the right */ + if (hist && parse_out[PARSE_LEFT] && !parse_out[PARSE_RIGHT]) { + parse_out[PARSE_RIGHT] = parse_out[PARSE_LEFT]; + parse_out[PARSE_LEFT] = NULL; + } + + for (i = 0; i < PARSE_LAST; i++) { + if (parse_out[i]) { + clen[i] = ui_strlenc(&windows[Win_main], parse_out[i], NULL); + alen[i] = strlen(parse_out[i]); + } + } + + if (divbool) { + ret = estrdup(parse_printf("%1$s %2$*3$s%4$s%5$s", + parse_out[PARSE_TIME] ? parse_out[PARSE_TIME] : "", + parse_out[PARSE_LEFT] ? parse_out[PARSE_LEFT] : "", divlen + alen[PARSE_LEFT] - clen[PARSE_LEFT], + divstr, + parse_out[PARSE_RIGHT])); + } else { + if (parse_out[PARSE_TIME]) + parse_append(&ret, parse_out[PARSE_TIME]); + if (parse_out[PARSE_LEFT]) + parse_append(&ret, parse_out[PARSE_LEFT]); + if (parse_out[PARSE_RIGHT]) + parse_append(&ret, parse_out[PARSE_RIGHT]); + } + + fprintf(stderr, "ret: %s\n", ret); + + return ret; +} diff --git a/src/hirc.h b/src/hirc.h @@ -187,8 +187,7 @@ void ui_tls_error_(char *file, int line, const char *func, struct tls *ctx, cha /* format.c */ char * format_get_bufact(int activity); char * format_get(struct History *hist); -char * format_(struct Window *window, char *format, struct History *hist, int recursive); -#define format(window, format, hist) format_(window, format, hist, 0) +char * format(struct Window *window, char *format, struct History *hist); /* commands.c */ void command_eval(struct Server *server, char *str); diff --git a/src/openbsd/strlcat.c b/src/openbsd/strlcat.c @@ -0,0 +1,57 @@ +#ifndef __OpenBSD__ +/* $OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org> + * + * Permission to use, copy, modify, and 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 <sys/types.h> +#include <string.h> + +/* + * Appends src to string dst of size dsize (unlike strncat, dsize is the + * full size of dst, not space left). At most dsize-1 characters + * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). + * Returns strlen(src) + MIN(dsize, strlen(initial dst)). + * If retval >= dsize, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t dsize) +{ + const char *odst = dst; + const char *osrc = src; + size_t n = dsize; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end. */ + while (n-- != 0 && *dst != '\0') + dst++; + dlen = dst - odst; + n = dsize - dlen; + + if (n-- == 0) + return(dlen + strlen(src)); + while (*src != '\0') { + if (n != 0) { + *dst++ = *src; + n--; + } + src++; + } + *dst = '\0'; + + return(dlen + (src - osrc)); /* count does not include NUL */ +} +#endif diff --git a/src/struct.h b/src/struct.h @@ -44,6 +44,7 @@ enum Activity { Activity_error = 3, Activity_message = 4, Activity_hilight = 5, + Activity_last = 6 }; enum HistOpt { diff --git a/src/ui.c b/src/ui.c @@ -54,12 +54,12 @@ struct Selected selected; struct Keybind *keybinds = NULL; void -ui_error_(char *file, int line, const char *func, char *format, ...) { +ui_error_(char *file, int line, const char *func, char *fmt, ...) { char msg[1024]; va_list ap; - va_start(ap, format); - vsnprintf(msg, sizeof(msg), format, ap); + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); va_end(ap); hist_format(selected.history, Activity_error, HIST_UI|HIST_ERR|HIST_NIGN, @@ -286,7 +286,7 @@ ui_read(void) { void ui_redraw(void) { struct History *p; - char *format; + char *fmt; long nicklistwidth, buflistwidth; int x = 0, rx = 0; int i; @@ -337,34 +337,34 @@ ui_redraw(void) { windows[Win_dummy].h = LINES; windows[Win_dummy].w = COLS; - format = format(NULL, config_gets("format.ui.separator.horizontal"), NULL); + fmt = format(NULL, config_gets("format.ui.separator.horizontal"), NULL); for (i = x; i <= COLS - rx; i++) { wmove(windows[Win_dummy].window, LINES - 2, i); - ui_wprintc(&windows[Win_dummy], 1, "%s", format); + ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); } if (x) { - format = format(NULL, config_gets("format.ui.separator.vertical"), NULL); + fmt = format(NULL, config_gets("format.ui.separator.vertical"), NULL); for (i = 0; i <= LINES; i++) { wmove(windows[Win_dummy].window, i, x - 1); - ui_wprintc(&windows[Win_dummy], 1, "%s", format); + ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); } - format = format(NULL, config_gets("format.ui.separator.split.left"), NULL); + fmt = format(NULL, config_gets("format.ui.separator.split.left"), NULL); wmove(windows[Win_dummy].window, LINES - 2, x - 1); - ui_wprintc(&windows[Win_dummy], 1, "%s", format); + ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); } if (rx) { - format = format(NULL, config_gets("format.ui.separator.vertical"), NULL); + fmt = format(NULL, config_gets("format.ui.separator.vertical"), NULL); for (i = 0; i <= LINES; i++) { wmove(windows[Win_dummy].window, i, COLS - rx); - ui_wprintc(&windows[Win_dummy], 1, "%s", format); + ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); } - format = format(NULL, config_gets("format.ui.separator.split.right"), NULL); + fmt = format(NULL, config_gets("format.ui.separator.split.right"), NULL); wmove(windows[Win_dummy].window, LINES - 2, COLS - rx); - ui_wprintc(&windows[Win_dummy], 1, "%s", format); + ui_wprintc(&windows[Win_dummy], 1, "%s", fmt); } refresh(); @@ -520,7 +520,15 @@ ui_draw_buflist(void) { struct Server *sp; struct Channel *chp, *prp; int i = 1, scroll; - char *indicator; + char *actind[Activity_last]; + char *oldind, *indicator; + + oldind = estrdup(format(NULL, config_gets("format.ui.buflist.old"), NULL)); + actind[Activity_none] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.none"), NULL)); + actind[Activity_status] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.status"), NULL)); + actind[Activity_error] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.error"), NULL)); + actind[Activity_message] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.message"), NULL)); + actind[Activity_hilight] = estrdup(format(NULL, config_gets("format.ui.buflist.activity.hilight"), NULL)); werase(windows[Win_buflist].window); @@ -546,12 +554,7 @@ ui_draw_buflist(void) { if (scroll < i - 1) { if (selected.server == sp && !selected.channel) wattron(windows[Win_buflist].window, A_BOLD); - - if (sp->status == ConnStatus_notconnected) - indicator = format(NULL, config_gets("format.ui.buflist.old"), NULL); - else - indicator = format_get_bufact(sp->history->activity); - + indicator = (sp->status == ConnStatus_notconnected) ? oldind : actind[sp->history->activity]; ui_wprintc(&windows[Win_buflist], 1, "%02d: %s─ %s%s\n", i, sp->next ? "├" : "└", indicator, sp->name); wattrset(windows[Win_buflist].window, A_NORMAL); } @@ -561,12 +564,7 @@ ui_draw_buflist(void) { if (scroll < i - 1) { if (selected.channel == chp) wattron(windows[Win_buflist].window, A_BOLD); - - if (chp->old) - indicator = format(NULL, config_gets("format.ui.buflist.old"), NULL); - else - indicator = format_get_bufact(chp->history->activity); - + indicator = (chp->old) ? oldind : actind[chp->history->activity]; ui_wprintc(&windows[Win_buflist], 1, "%02d: %s %s─ %s%s\n", i, sp->next ? "│" : " ", chp->next || sp->queries ? "├" : "└", indicator, chp->name); wattrset(windows[Win_buflist].window, A_NORMAL); @@ -578,12 +576,7 @@ ui_draw_buflist(void) { if (scroll < i - 1) { if (selected.channel == prp) wattron(windows[Win_buflist].window, A_BOLD); - - if (prp->old) - indicator = format(NULL, config_gets("format.ui.buflist.old"), NULL); - else - indicator = format_get_bufact(prp->history->activity); - + indicator = (prp->old) ? oldind : actind[prp->history->activity]; ui_wprintc(&windows[Win_buflist], 1, "%02d: %s %s─ %s%s\n", i, sp->next ? "│" : " ", prp->next ? "├" : "└", indicator, prp->name); wattrset(windows[Win_buflist].window, A_NORMAL); @@ -600,7 +593,7 @@ ui_draw_buflist(void) { } int -ui_wprintc(struct Window *window, int lines, char *format, ...) { +ui_wprintc(struct Window *window, int lines, char *fmt, ...) { char *str; wchar_t *wcs, *s; va_list ap; @@ -616,13 +609,13 @@ ui_wprintc(struct Window *window, int lines, char *format, ...) { int reverse = 0; int italic = 0; - va_start(ap, format); - ret = vsnprintf(str, 0, format, ap) + 1; + va_start(ap, fmt); + ret = vsnprintf(str, 0, fmt, ap) + 1; va_end(ap); str = emalloc(ret); - va_start(ap, format); - ret = vsnprintf(str, ret, format, ap); + va_start(ap, fmt); + ret = vsnprintf(str, ret, fmt, ap); va_end(ap); if (ret < 0) return ret;