summaryrefslogtreecommitdiff
path: root/usr.bin/tic
diff options
context:
space:
mode:
authorroy <roy@NetBSD.org>2010-02-03 15:16:32 +0000
committerroy <roy@NetBSD.org>2010-02-03 15:16:32 +0000
commit4ca00e00d84c6d6fe8dddcfc20bbb3204c93d655 (patch)
treee8e1b66706a875a6b407549c011b79ab00fc0428 /usr.bin/tic
parente8182c96d53ec50682eac9eb0f376e95571b7305 (diff)
Import my terminfo implementation.
This uses the ncurses terminal definitions. OK: core@, jdc@
Diffstat (limited to 'usr.bin/tic')
-rw-r--r--usr.bin/tic/Makefile13
-rw-r--r--usr.bin/tic/tic.1108
-rw-r--r--usr.bin/tic/tic.c982
3 files changed, 1103 insertions, 0 deletions
diff --git a/usr.bin/tic/Makefile b/usr.bin/tic/Makefile
new file mode 100644
index 00000000000..06096204591
--- /dev/null
+++ b/usr.bin/tic/Makefile
@@ -0,0 +1,13 @@
+# $NetBSD: Makefile,v 1.1 2010/02/03 15:16:32 roy Exp $
+
+PROG= tic
+WARNS= 4
+
+CPPFLAGS+= -I${.CURDIR}/../../lib/libterminfo
+
+.ifndef HOSTPROG
+LDADD+= -lterminfo
+DPADD+= ${LIBTERMINFO}
+.endif
+
+.include <bsd.prog.mk>
diff --git a/usr.bin/tic/tic.1 b/usr.bin/tic/tic.1
new file mode 100644
index 00000000000..d31841de3c4
--- /dev/null
+++ b/usr.bin/tic/tic.1
@@ -0,0 +1,108 @@
+.\" $NetBSD: tic.1,v 1.1 2010/02/03 15:16:32 roy Exp $
+.\"
+.\" Copyright (c) 2009 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Roy Marples.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+.\" POSSIBILITY OF SUCH DAMAGE.
+.\"
+.Dd July 17, 2009
+.Dt TIC 1
+.Os
+.Sh NAME
+.Nm tic
+.Nd terminfo compiler
+.Sh SYNOPSIS
+.Nm tic
+.Op Fl acsx
+.Op Fl o Ar file
+.Ar source
+.Sh DESCRIPTION
+The
+.Nm
+utility compiles
+.Xr terminfo 5
+source into a database for use by other programs.
+The created database path name is the same as the source but with .db appended.
+.Pp
+The following options are available:
+.Bl -tag -width Fl
+.It Fl a
+Do not discard commented out capabilities.
+This also sets the
+.Fl x
+flag as
+.Nm
+retains commented out capabilities as non standard.
+.It Fl c
+Only check for errors, don't write the final database.
+.It Fl o Ar file
+Write the database to
+.Ar file
+.Ns .db
+instead of
+.Ar source
+.Ns .db.
+.It Fl s
+Display the number of terminal descritions written to the database.
+.It Fl x
+Include non standard capabilities defined in the
+.Ar source .
+.El
+.Ss Extensions To Terminfo
+When
+.Nm
+discovers a
+.Sy use Ns = Ns Va term
+capability, the terminal description for
+.Va term
+is merged in.
+Capabilities do not overwrite previously disovered ones and capabilities
+ending with @ are marked as absent so the terminal does not inherit the
+capability from
+.Sy use Ns d
+terminals.
+.Sh EXIT STATUS
+The
+.Nm
+utility exits 0 on success, and >0 if an error occurs.
+.Sh EXAMPLES
+To maintain your private terminfo definitions, if the system supplied
+ones do not support your terminal:
+.Bd -literal -offset indent
+.Ic tic ~/.terminfo
+.Ed
+.Sh SEE ALSO
+.Xr infocmp 1 ,
+.Xr tput 1 ,
+.Xr curses 3 ,
+.Xr terminfo 5
+.Sh STANDARDS
+The
+.Nm
+utility works with terminfo files that conform to the
+.St -xcurses4.2
+standard.
+.Sh AUTHORS
+.An Roy Marples Aq roy@NetBSD.org
diff --git a/usr.bin/tic/tic.c b/usr.bin/tic/tic.c
new file mode 100644
index 00000000000..4a310b6e79b
--- /dev/null
+++ b/usr.bin/tic/tic.c
@@ -0,0 +1,982 @@
+/* $NetBSD: tic.c,v 1.1 2010/02/03 15:16:32 roy Exp $ */
+
+/*
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Roy Marples.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_NBTOOL_CONFIG_H
+#include "nbtool_config.h"
+#endif
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: tic.c,v 1.1 2010/02/03 15:16:32 roy Exp $");
+
+#include <sys/types.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <ndbm.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <term_private.h>
+#include <term.h>
+
+#define UINT16_T_MAX 0xffff
+
+typedef struct tbuf {
+ char *buf;
+ size_t buflen;
+ size_t bufpos;
+ size_t entries;
+} TBUF;
+
+typedef struct tic {
+ char *name;
+ char *desc;
+ TBUF flags;
+ TBUF nums;
+ TBUF strs;
+ TBUF extras;
+} TIC;
+
+/* We store the full list of terminals we have instead of iterating
+ through the database as the sequential iterator doesn't work
+ the the data size stored changes N amount which ours will. */
+typedef struct term {
+ struct term *next;
+ char *name;
+ char type;
+ TIC tic;
+} TERM;
+static TERM *terms;
+
+static int error_exit;
+static int aflag, xflag;
+static char *dbname;
+
+static TBUF scratch;
+
+static void
+do_unlink(void)
+{
+
+ if (dbname != NULL)
+ unlink(dbname);
+}
+
+static void __attribute__((__format__(__printf__, 1, 2)))
+dowarn(const char *fmt, ...)
+{
+ va_list va;
+
+ error_exit = 1;
+ va_start(va, fmt);
+ vwarnx(fmt, va);
+ va_end(va);
+}
+
+static char *
+grow_tbuf(TBUF *tbuf, size_t len)
+{
+ char *buf;
+ size_t l;
+
+ l = tbuf->bufpos + len;
+ if (l > tbuf->buflen) {
+ if (tbuf->bufpos == 0) {
+ buf = malloc(l);
+ if (buf == NULL)
+ err(1, "malloc (%zu bytes)", l);
+ } else {
+ buf = realloc(tbuf->buf, l);
+ if (buf == NULL)
+ err(1, "realloc (%zu bytes)", l);
+ }
+ tbuf->buf = buf;
+ tbuf->buflen = l;
+ }
+ return tbuf->buf;
+}
+
+static char *
+find_cap(TBUF *tbuf, char type, short ind)
+{
+ size_t n;
+ short num;
+ char *cap;
+
+ cap = tbuf->buf;
+ for (n = tbuf->entries; n > 0; n--) {
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ if (num == ind)
+ return cap;
+ switch (type) {
+ case 'f':
+ cap++;
+ break;
+ case 'n':
+ cap += sizeof(uint16_t);
+ break;
+ case 's':
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ cap += num;
+ break;
+ }
+ }
+ return NULL;
+}
+
+static char *
+find_extra(TBUF *tbuf, const char *code)
+{
+ size_t n;
+ short num;
+ char *cap;
+
+ cap = tbuf->buf;
+ for (n = tbuf->entries; n > 0; n--) {
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ if (strcmp(cap, code) == 0)
+ return cap + num;
+ cap += num;
+ switch (*cap++) {
+ case 'f':
+ cap++;
+ break;
+ case 'n':
+ cap += sizeof(uint16_t);
+ break;
+ case 's':
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ cap += num;
+ break;
+ }
+ }
+ return NULL;
+}
+
+static size_t
+store_extra(TIC *tic, int wrn, char *id, char type, char flag, short num,
+ char *str, size_t strl)
+{
+ size_t l;
+
+ if (strcmp(id, "use") != 0) {
+ if (find_extra(&tic->extras, id) != NULL)
+ return 0;
+ if (xflag == 0) {
+ if (wrn != 0)
+ dowarn("%s: %s: unknown capability",
+ tic->name, id);
+ return 0;
+ }
+ }
+
+ l = strlen(id) + 1;
+ if (l > UINT16_T_MAX) {
+ dowarn("%s: %s: cap name is too long", tic->name, id);
+ return 0;
+ }
+
+ grow_tbuf(&tic->extras, l + strl + (sizeof(uint16_t) * 2) + 1);
+ le16enc(tic->extras.buf + tic->extras.bufpos, l);
+ tic->extras.bufpos += sizeof(uint16_t);
+ memcpy(tic->extras.buf + tic->extras.bufpos, id, l);
+ tic->extras.bufpos += l;
+ tic->extras.buf[tic->extras.bufpos++] = type;
+ switch (type) {
+ case 'f':
+ tic->extras.buf[tic->extras.bufpos++] = flag;
+ break;
+ case 'n':
+ le16enc(tic->extras.buf + tic->extras.bufpos, num);
+ tic->extras.bufpos += sizeof(uint16_t);
+ break;
+ case 's':
+ le16enc(tic->extras.buf + tic->extras.bufpos, strl);
+ tic->extras.bufpos += sizeof(uint16_t);
+ memcpy(tic->extras.buf + tic->extras.bufpos, str, strl);
+ tic->extras.bufpos += strl;
+ break;
+ }
+ tic->extras.entries++;
+ return 1;
+}
+
+static int
+save_term(DBM *db, TERM *term)
+{
+ size_t buflen, len, dlen;
+ char *cap;
+ datum key, value;
+ TIC *tic;
+
+ scratch.bufpos = 0;
+ tic = &term->tic;
+ len = strlen(tic->name) + 1;
+ if (tic->desc == NULL)
+ dlen = 0;
+ else
+ dlen = strlen(tic->desc) + 1;
+ buflen = sizeof(char) +
+ sizeof(uint16_t) + len +
+ sizeof(uint16_t) + dlen +
+ (sizeof(uint16_t) * 2) + tic->flags.bufpos +
+ (sizeof(uint16_t) * 2) + tic->nums.bufpos +
+ (sizeof(uint16_t) * 2) + tic->strs.bufpos +
+ (sizeof(uint16_t) * 2) + tic->extras.bufpos;
+ grow_tbuf(&scratch, buflen);
+ cap = scratch.buf;
+ if (term->type == 'a')
+ *cap++ = 0;
+ else
+ *cap++ = 1; /* version */
+ le16enc(cap, len);
+ cap += sizeof(uint16_t);
+ memcpy(cap, tic->name, len);
+ cap += len;
+ if (term->type != 'a') {
+ le16enc(cap, dlen);
+ cap += sizeof(uint16_t);
+ if (tic->desc != NULL) {
+ memcpy(cap, tic->desc, dlen);
+ cap += dlen;
+ }
+
+ if (tic->flags.entries == 0) {
+ le16enc(cap, 0);
+ cap += sizeof(uint16_t);
+ } else {
+ le16enc(cap, (tic->flags.bufpos + sizeof(uint16_t)));
+ cap += sizeof(uint16_t);
+ le16enc(cap, tic->flags.entries);
+ cap += sizeof(uint16_t);
+ memcpy(cap, tic->flags.buf, tic->flags.bufpos);
+ cap += tic->flags.bufpos;
+ }
+
+ if (tic->nums.entries == 0) {
+ le16enc(cap, 0);
+ cap += sizeof(uint16_t);
+ } else {
+ le16enc(cap, (tic->nums.bufpos + sizeof(uint16_t)));
+ cap += sizeof(uint16_t);
+ le16enc(cap, tic->nums.entries);
+ cap += sizeof(uint16_t);
+ memcpy(cap, tic->nums.buf, tic->nums.bufpos);
+ cap += tic->nums.bufpos;
+ }
+
+ if (tic->strs.entries == 0) {
+ le16enc(cap, 0);
+ cap += sizeof(uint16_t);
+ } else {
+ le16enc(cap, (tic->strs.bufpos + sizeof(uint16_t)));
+ cap += sizeof(uint16_t);
+ le16enc(cap, tic->strs.entries);
+ cap += sizeof(uint16_t);
+ memcpy(cap, tic->strs.buf, tic->strs.bufpos);
+ cap += tic->strs.bufpos;
+ }
+
+ if (tic->extras.entries == 0) {
+ le16enc(cap, 0);
+ cap += sizeof(uint16_t);
+ } else {
+ le16enc(cap, (tic->extras.bufpos + sizeof(uint16_t)));
+ cap += sizeof(uint16_t);
+ le16enc(cap, tic->extras.entries);
+ cap += sizeof(uint16_t);
+ memcpy(cap, tic->extras.buf, tic->extras.bufpos);
+ cap += tic->extras.bufpos;
+ }
+ }
+
+ key.dptr = term->name;
+ key.dsize = strlen(term->name);
+ value.dptr = scratch.buf;
+ value.dsize = cap - scratch.buf;
+ if (dbm_store(db, key, value, DBM_REPLACE) == -1)
+ err(1, "dbm_store");
+ return 0;
+}
+
+static TERM *
+find_term(const char *name)
+{
+ TERM *term;
+
+ for (term = terms; term != NULL; term = term->next)
+ if (strcmp(term->name, name) == 0)
+ return term;
+ return NULL;
+}
+
+static TERM *
+store_term(const char *name, char type)
+{
+ TERM *term;
+
+ term = calloc(1, sizeof(*term));
+ if (term == NULL)
+ errx(1, "malloc");
+ term->name = strdup(name);
+ term->type = type;
+ if (term->name == NULL)
+ errx(1, "malloc");
+ term->next = terms;
+ terms = term;
+ return term;
+}
+
+static void
+encode_string(const char *term, const char *cap, TBUF *tbuf, const char *str)
+{
+ int slash, i, num;
+ char ch, *p, *s, last;
+
+ grow_tbuf(tbuf, strlen(str) + 1);
+ p = s = tbuf->buf + tbuf->bufpos;
+ slash = 0;
+ last = '\0';
+ /* Convert escape codes */
+ while ((ch = *str++) != '\0') {
+ if (slash == 0 && ch == '\\') {
+ slash = 1;
+ continue;
+ }
+ if (slash == 0) {
+ if (last != '%' && ch == '^') {
+ ch = *str++;
+ if (((unsigned char)ch) >= 128)
+ dowarn("%s: %s: illegal ^ character",
+ term, cap);
+ if (ch == '\0')
+ break;
+ if (ch == '?')
+ ch = '\177';
+ else if ((ch &= 037) == 0)
+ ch = 128;
+ }
+ *p++ = ch;
+ last = ch;
+ continue;
+ }
+ slash = 0;
+ if (ch >= '0' && ch <= '7') {
+ num = ch - '0';
+ for (i = 0; i < 2; i++) {
+ if (*str < '0' || *str > '7') {
+ if (isdigit((unsigned char)*str))
+ dowarn("%s: %s: non octal"
+ " digit", term, cap);
+ else
+ break;
+ }
+ num = num * 8 + *str++ - '0';
+ }
+ if (num == 0)
+ num = 0200;
+ *p++ = (char)num;
+ continue;
+ }
+ switch (ch) {
+ case 'a':
+ *p++ = '\a';
+ break;
+ case 'b':
+ *p++ = '\b';
+ break;
+ case 'e': /* FALLTHROUGH */
+ case 'E':
+ *p++ = '\033';
+ break;
+ case 'f':
+ *p++ = '\014';
+ break;
+ case 'l': /* FALLTHROUGH */
+ case 'n':
+ *p++ = '\n';
+ break;
+ case 'r':
+ *p++ = '\r';
+ break;
+ case 's':
+ *p++ = ' ';
+ break;
+ case 't':
+ *p++ = '\t';
+ break;
+ default:
+
+ /* We should warn here */
+ case '^':
+ case ',':
+ case ':':
+ case '|':
+ *p++ = ch;
+ break;
+ }
+ last = ch;
+ }
+ *p++ = '\0';
+ tbuf->bufpos += p - s;
+}
+
+static int
+process_entry(TBUF *buf)
+{
+ char *cap, *capstart, *p, *e, *name, *desc, *alias, flag;
+ long num;
+ int slash;
+ ssize_t ind;
+ size_t len;
+ TERM *term;
+ TIC *tic;
+
+ if (buf->bufpos == 0)
+ return 0;
+ /* Terminate the string */
+ buf->buf[buf->bufpos - 1] = '\0';
+ /* First rewind the buffer for new entries */
+ buf->bufpos = 0;
+
+ if (isspace((unsigned char)*buf->buf))
+ return 0;
+
+ cap = strchr(buf->buf, '\n');
+ if (cap == NULL)
+ return 0;
+ e = cap - 1;
+ if (*e == ',')
+ *e = '\0';
+ *cap++ = '\0';
+
+ name = buf->buf;
+ desc = strrchr(buf->buf, '|');
+ if (desc != NULL)
+ *desc++ = '\0';
+ alias = strchr(buf->buf, '|');
+ if (alias != NULL)
+ *alias++ = '\0';
+
+ if (*e != '\0')
+ dowarn("%s: description missing separator", buf->buf);
+
+ /* If we already have this term, abort */
+ if (find_term(name) != NULL) {
+ dowarn("%s: duplicate entry", name);
+ return 0;
+ }
+ term = store_term(name, 't');
+ tic = &term->tic;
+ tic->name = strdup(name);
+ if (tic->name == NULL)
+ err(1, "malloc");
+ if (desc != NULL) {
+ tic->desc = strdup(desc);
+ if (tic->desc == NULL)
+ err(1, "malloc");
+ }
+
+ do {
+ while (isspace((unsigned char)*cap))
+ cap++;
+ if (*cap == '\0')
+ break;
+ slash = 0;
+ for (capstart = cap;
+ *cap != '\0' && (slash == 1 || *cap != ',');
+ cap++)
+ {
+ if (slash == 0) {
+ if (*cap == '\\')
+ slash = 1;
+ } else
+ slash = 0;
+ continue;
+ }
+ *cap++ = '\0';
+
+ /* Skip commented caps */
+ if (aflag == 0 && capstart[0] == '.')
+ continue;
+
+ /* Obsolete entries */
+ if (capstart[0] == 'O' && capstart[1] == 'T') {
+ if (xflag == 0)
+ continue;
+ capstart += 2;
+ }
+
+ /* str cap */
+ p = strchr(capstart, '=');
+ if (p != NULL) {
+ *p++ = '\0';
+ /* Don't use the string if we already have it */
+ ind = _ti_strindex(capstart);
+ if (ind != -1 &&
+ find_cap(&tic->strs, 's', ind) != NULL)
+ continue;
+
+ /* Encode the string to our scratch buffer */
+ scratch.bufpos = 0;
+ encode_string(tic->name, capstart, &scratch, p);
+ if (scratch.bufpos > UINT16_T_MAX) {
+ dowarn("%s: %s: string is too long",
+ tic->name, capstart);
+ continue;
+ }
+ if (!VALID_STRING(scratch.buf)) {
+ dowarn("%s: %s: invalid string",
+ tic->name, capstart);
+ continue;
+ }
+
+ if (ind == -1)
+ store_extra(tic, 1, capstart, 's', -1, -2,
+ scratch.buf, scratch.bufpos);
+ else {
+ grow_tbuf(&tic->strs, (sizeof(uint16_t) * 2) +
+ scratch.bufpos);
+ le16enc(tic->strs.buf + tic->strs.bufpos, ind);
+ tic->strs.bufpos += sizeof(uint16_t);
+ le16enc(tic->strs.buf + tic->strs.bufpos,
+ scratch.bufpos);
+ tic->strs.bufpos += sizeof(uint16_t);
+ memcpy(tic->strs.buf + tic->strs.bufpos,
+ scratch.buf, scratch.bufpos);
+ tic->strs.bufpos += scratch.bufpos;
+ tic->strs.entries++;
+ }
+ continue;
+ }
+
+ /* num cap */
+ p = strchr(capstart, '#');
+ if (p != NULL) {
+ *p++ = '\0';
+ /* Don't use the number if we already have it */
+ ind = _ti_numindex(capstart);
+ if (ind != -1 &&
+ find_cap(&tic->nums, 'n', ind) != NULL)
+ continue;
+
+ num = strtol(p, &e, 0);
+ if (*e != '\0') {
+ dowarn("%s: %s: not a number",
+ tic->name, capstart);
+ continue;
+ }
+ if (!VALID_NUMERIC(num)) {
+ dowarn("%s: %s: number out of range",
+ tic->name, capstart);
+ continue;
+ }
+ if (ind == -1)
+ store_extra(tic, 1, capstart, 'n', -1,
+ num, NULL, 0);
+ else {
+ grow_tbuf(&tic->nums, sizeof(uint16_t) * 2);
+ le16enc(tic->nums.buf + tic->nums.bufpos, ind);
+ tic->nums.bufpos += sizeof(uint16_t);
+ le16enc(tic->nums.buf + tic->nums.bufpos, num);
+ tic->nums.bufpos += sizeof(uint16_t);
+ tic->nums.entries++;
+ }
+ continue;
+ }
+
+ flag = 1;
+ len = strlen(capstart) - 1;
+ if (capstart[len] == '@') {
+ flag = CANCELLED_BOOLEAN;
+ capstart[len] = '\0';
+ }
+ ind = _ti_flagindex(capstart);
+ if (ind == -1 && flag == CANCELLED_BOOLEAN) {
+ if ((ind = _ti_numindex(capstart)) != -1) {
+ if (find_cap(&tic->nums, 'n', ind) != NULL)
+ continue;
+ grow_tbuf(&tic->nums, sizeof(uint16_t) * 2);
+ le16enc(tic->nums.buf + tic->nums.bufpos, ind);
+ tic->nums.bufpos += sizeof(uint16_t);
+ le16enc(tic->nums.buf + tic->nums.bufpos,
+ CANCELLED_NUMERIC);
+ tic->nums.bufpos += sizeof(uint16_t);
+ tic->nums.entries++;
+ continue;
+ } else if ((ind = _ti_strindex(capstart)) != -1) {
+ if (find_cap(&tic->strs, 's', ind) != NULL)
+ continue;
+ grow_tbuf(&tic->strs,
+ (sizeof(uint16_t) * 2) + 1);
+ le16enc(tic->strs.buf + tic->strs.bufpos, ind);
+ tic->strs.bufpos += sizeof(uint16_t);
+ le16enc(tic->strs.buf + tic->strs.bufpos, 0);
+ tic->strs.bufpos += sizeof(uint16_t);
+ tic->strs.entries++;
+ continue;
+ }
+ }
+ if (ind == -1)
+ store_extra(tic, 1, capstart, 'f', flag, 0, NULL, 0);
+ else if (find_cap(&tic->flags, 'f', ind) == NULL) {
+ grow_tbuf(&tic->flags, sizeof(uint16_t) + 1);
+ le16enc(tic->flags.buf + tic->flags.bufpos, ind);
+ tic->flags.bufpos += sizeof(uint16_t);
+ tic->flags.buf[tic->flags.bufpos++] = flag;
+ tic->flags.entries++;
+ }
+ } while (*cap == ',' || isspace((unsigned char)*cap));
+
+ /* Create aliased terms */
+ if (alias != NULL) {
+ while (alias != NULL && *alias != '\0') {
+ desc = strchr(alias, '|');
+ if (desc != NULL)
+ *desc++ = '\0';
+ if (find_term(alias) != NULL) {
+ dowarn("%s: has alias for already assigned"
+ " term %s", tic->name, alias);
+ } else {
+ term = store_term(alias, 'a');
+ term->tic.name = strdup(tic->name);
+ if (term->tic.name == NULL)
+ err(1, "malloc");
+ }
+ alias = desc;
+ }
+ }
+
+ return 0;
+}
+
+static void
+merge(TIC *rtic, TIC *utic)
+{
+ char *cap, flag, *code, type, *str;
+ short ind, num;
+ size_t n;
+
+ cap = utic->flags.buf;
+ for (n = utic->flags.entries; n > 0; n--) {
+ ind = le16dec(cap);
+ cap += sizeof(uint16_t);
+ flag = *cap++;
+ if (VALID_BOOLEAN(flag) &&
+ find_cap(&rtic->flags, 'f', ind) == NULL)
+ {
+ grow_tbuf(&rtic->flags, sizeof(uint16_t) + 1);
+ le16enc(rtic->flags.buf + rtic->flags.bufpos, ind);
+ rtic->flags.bufpos += sizeof(uint16_t);
+ rtic->flags.buf[rtic->flags.bufpos++] = flag;
+ rtic->flags.entries++;
+ }
+ }
+
+ cap = utic->nums.buf;
+ for (n = utic->nums.entries; n > 0; n--) {
+ ind = le16dec(cap);
+ cap += sizeof(uint16_t);
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ if (VALID_NUMERIC(num) &&
+ find_cap(&rtic->nums, 'n', ind) == NULL)
+ {
+ grow_tbuf(&rtic->nums, sizeof(uint16_t) * 2);
+ le16enc(rtic->nums.buf + rtic->nums.bufpos, ind);
+ rtic->nums.bufpos += sizeof(uint16_t);
+ le16enc(rtic->nums.buf + rtic->nums.bufpos, num);
+ rtic->nums.bufpos += sizeof(uint16_t);
+ rtic->nums.entries++;
+ }
+ }
+
+ cap = utic->strs.buf;
+ for (n = utic->strs.entries; n > 0; n--) {
+ ind = le16dec(cap);
+ cap += sizeof(uint16_t);
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ if (num > 0 &&
+ find_cap(&rtic->strs, 's', ind) == NULL)
+ {
+ grow_tbuf(&rtic->strs, (sizeof(uint16_t) * 2) + num);
+ le16enc(rtic->strs.buf + rtic->strs.bufpos, ind);
+ rtic->strs.bufpos += sizeof(uint16_t);
+ le16enc(rtic->strs.buf + rtic->strs.bufpos, num);
+ rtic->strs.bufpos += sizeof(uint16_t);
+ memcpy(rtic->strs.buf + rtic->strs.bufpos,
+ cap, num);
+ rtic->strs.bufpos += num;
+ rtic->strs.entries++;
+ }
+ cap += num;
+ }
+
+ cap = utic->extras.buf;
+ for (n = utic->extras.entries; n > 0; n--) {
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ code = cap;
+ cap += num;
+ type = *cap++;
+ flag = 0;
+ str = NULL;
+ switch (type) {
+ case 'f':
+ flag = *cap++;
+ if (!VALID_BOOLEAN(flag))
+ continue;
+ break;
+ case 'n':
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ if (!VALID_NUMERIC(num))
+ continue;
+ break;
+ case 's':
+ num = le16dec(cap);
+ cap += sizeof(uint16_t);
+ str = cap;
+ cap += num;
+ if (num == 0)
+ continue;
+ break;
+ }
+ store_extra(rtic, 0, code, type, flag, num, str, num);
+ }
+}
+
+static size_t
+merge_use(void)
+{
+ size_t skipped, merged, memn;
+ char *cap, *scap;
+ uint16_t num;
+ TIC *rtic, *utic;
+ TERM *term, *uterm;;
+
+ skipped = merged = 0;
+ for (term = terms; term != NULL; term = term->next) {
+ if (term->type == 'a')
+ continue;
+ rtic = &term->tic;
+ while ((cap = find_extra(&rtic->extras, "use")) != NULL) {
+ if (*cap++ != 's') {
+ dowarn("%s: use is not string", rtic->name);
+ break;
+ }
+ cap += sizeof(uint16_t);
+ if (strcmp(rtic->name, cap) == 0) {
+ dowarn("%s: uses itself", rtic->name);
+ goto remove;
+ }
+ uterm = find_term(cap);
+ if (uterm != NULL && uterm->type == 'a')
+ uterm = find_term(uterm->tic.name);
+ if (uterm == NULL) {
+ dowarn("%s: no use record for %s",
+ rtic->name, cap);
+ goto remove;
+ }
+ utic = &uterm->tic;
+ if (strcmp(utic->name, rtic->name) == 0) {
+ dowarn("%s: uses itself", rtic->name);
+ goto remove;
+ }
+ if (find_extra(&utic->extras, "use") != NULL) {
+ skipped++;
+ break;
+ }
+ cap = find_extra(&rtic->extras, "use");
+ merge(rtic, utic);
+ remove:
+ /* The pointers may have changed, find the use again */
+ cap = find_extra(&rtic->extras, "use");
+ if (cap == NULL)
+ dowarn("%s: use no longer exists - impossible",
+ rtic->name);
+ else {
+ scap = cap - (4 + sizeof(uint16_t));
+ cap++;
+ num = le16dec(cap);
+ cap += sizeof(uint16_t) + num;
+ memn = rtic->extras.bufpos -
+ (cap - rtic->extras.buf);
+ memcpy(scap, cap, memn);
+ rtic->extras.bufpos -= cap - scap;
+ cap = scap;
+ rtic->extras.entries--;
+ merged++;
+ }
+ }
+ }
+
+ if (merged == 0 && skipped != 0)
+ dowarn("circular use detected");
+ return merged;
+}
+
+int
+main(int argc, char **argv)
+{
+ int ch, cflag, sflag;
+ char *source, *p, *buf, *ofile;
+ FILE *f;
+ DBM *db;
+ size_t len, buflen, nterm, nalias;
+ TBUF tbuf;
+ TERM *term;
+
+ cflag = sflag = 0;
+ ofile = NULL;
+ while ((ch = getopt(argc, argv, "aco:sx")) != -1)
+ switch (ch) {
+ case 'a':
+ aflag++;
+ xflag++;
+ break;
+ case 'c':
+ cflag++;
+ break;
+ case 'o':
+ ofile = optarg;
+ break;
+ case 's':
+ sflag++;
+ break;
+ case 'x':
+ xflag++;
+ break;
+ case '?': /* FALLTHROUGH */
+ default:
+ fprintf(stderr, "usage: %s [-acsx] [-o file] source\n",
+ getprogname());
+ return EXIT_FAILURE;
+ }
+
+ if (optind == argc)
+ errx(1, "No source file given");
+ source = argv[optind++];
+ f = fopen(source, "r");
+ if (f == NULL)
+ err(1, "fopen: %s", source);
+ if (cflag == 0) {
+ if (ofile == NULL)
+ ofile = source;
+ len = strlen(ofile) + 9;
+ dbname = malloc(len + 4); /* For adding .db after open */
+ if (dbname == NULL)
+ err(1, "malloc");
+ snprintf(dbname, len, "%s.tmp", ofile);
+ db = dbm_open(dbname, O_CREAT | O_RDWR | O_TRUNC, DEFFILEMODE);
+ if (db == NULL)
+ err(1, "dbopen: %s", source);
+ p = dbname + strlen(dbname);
+ *p++ = '.';
+ *p++ = 'd';
+ *p++ = 'b';
+ *p++ = '\0';
+ atexit(do_unlink);
+ } else
+ db = NULL; /* satisfy gcc warning */
+
+ tbuf.buflen = tbuf.bufpos = 0;
+ while ((buf = fgetln(f, &buflen)) != NULL) {
+ /* Skip comments */
+ if (*buf == '#')
+ continue;
+ if (buf[buflen - 1] != '\n') {
+ process_entry(&tbuf);
+ dowarn("last line is not a comment"
+ " and does not end with a newline");
+ continue;
+ }
+ /*
+ If the first char is space not a space then we have a
+ new entry, so process it.
+ */
+ if (!isspace((unsigned char)*buf) && tbuf.bufpos != 0)
+ process_entry(&tbuf);
+
+ /* Grow the buffer if needed */
+ grow_tbuf(&tbuf, buflen);
+ /* Append the string */
+ memcpy(tbuf.buf + tbuf.bufpos, buf, buflen);
+ tbuf.bufpos += buflen;
+ }
+ /* Process the last entry if not done already */
+ process_entry(&tbuf);
+
+ /* Merge use entries until we have merged all we can */
+ while (merge_use() != 0)
+ ;
+
+ if (cflag != 0)
+ return error_exit;
+
+ /* Save the terms */
+ nterm = nalias = 0;
+ for (term = terms; term != NULL; term = term->next) {
+ save_term(db, term);
+ if (term->type == 'a')
+ nalias++;
+ else
+ nterm++;
+ }
+
+ /* done! */
+ dbm_close(db);
+
+ /* Rename the tmp db to the real one now */
+ len = strlen(ofile) + 4;
+ p = malloc(len);
+ if (p == NULL)
+ err(1, "malloc");
+ snprintf(p, len, "%s.db", ofile);
+ if (rename(dbname, p) == -1)
+ err(1, "rename");
+ free(dbname);
+ dbname = NULL;
+
+ if (sflag != 0)
+ fprintf(stderr, "%zu entries and %zu aliases written to %s\n",
+ nterm, nalias, p);
+
+ return EXIT_SUCCESS;
+}