* There is a NOT a one-to-one correspondence between format strings and
* format instructions; some functions have side effects that can result
* in multiple instructions being generated. The exact list of instructions
- * generated by a format string can be seem with the nmh fmtdump utility.
+ * generated by a format string can be seem with the nmh fmttest utility.
*
* A list of format instructions can be found in fmt_compile.h.
*
*
* - Add the code in fmt_scan.c to handle your new function.
*
- * - Add code to fmtdump.c to display your new function.
+ * - Add code to fmttest.c to display your new function.
*
* - Document the new function in the mh-format(5) man page.
*
#include <h/fmt_scan.h>
#include <h/fmt_compile.h>
#include <h/mts.h>
+#include <h/utils.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#define TF_MYNAME 9 /* special - get current name of user */
#define TF_MYHOST 10 /* special - get "local" hostname */
#define TF_LMBOX 11 /* special - get full local mailbox */
+#define TF_BOLD 12 /* special - enter terminal bold mode */
+#define TF_UNDERLN 13 /* special - enter underline mode */
+#define TF_STNDOUT 14 /* special - enter underline mode */
+#define TF_RESET 15 /* special - reset terminal modes */
+#define TF_HASCLR 16 /* special - terminal have color? */
+#define TF_FGCOLR 17 /* special - foreground term color */
+#define TF_BGCOLR 18 /* special - background term color */
/* ftable->flags */
/* NB that TFL_PUTS is also used to decide whether the test
* what maps a particular function name into a format instruction.
* type - The type of argument this function expects. Those types are
* listed above (with the TF_ prefix). This affects what gets
- * placed in the format instruction (the f_un union).
+ * placed in the format instruction (the f_un union). Also,
+ * instructions that require special handling are distinguished
+ * here (TF_MYMBOX is one example).
* f_type - The instruction corresponding to this function (from the list
* in fmt_compile.h).
* extra - Used by some functions to provide extra data to the compiler.
{ "friendly", TF_COMP, FT_LS_FRIENDLY, FT_PARSEADDR, TFL_PUTS },
{ "mymbox", TF_COMP, FT_LV_COMPFLAG, FT_MYMBOX, TFL_PUTN },
- { "addtoseq", TF_STR, FT_ADDTOSEQ, 0, 0 },
- { "unquote", TF_EXPR, FT_LS_UNQUOTE, 0, TFL_PUTS},
+ { "unquote", TF_EXPR, FT_LS_UNQUOTE, 0, TFL_PUTS },
+
+ { "bold", TF_BOLD, FT_LS_LIT, 0, TFL_PUTS },
+ { "underline", TF_UNDERLN,FT_LS_LIT, 0, TFL_PUTS },
+ { "standout", TF_STNDOUT,FT_LS_LIT, 0, TFL_PUTS },
+ { "resetterm", TF_RESET, FT_LS_LIT, 0, TFL_PUTS },
+ { "hascolor", TF_HASCLR, FT_LV_LIT, 0, 0 },
+ { "fgcolor", TF_FGCOLR, FT_LS_LIT, 0, TFL_PUTS },
+ { "bgcolor", TF_BGCOLR, FT_LS_LIT, 0, TFL_PUTS },
{ NULL, 0, 0, 0, 0 }
};
+/*
+ * A mapping of color names to terminfo color numbers.
+ *
+ * There are two sets of terminal-setting codes: 'setaf/setab' (ANSI) and
+ * 'setf/setb'. Different terminals support different capabilities, so
+ * we provide a mapping for both. I'm not crazy about putting numbers
+ * directly in here, but it seems these are well defined by terminfo
+ * so it should be okay.
+ */
+
+struct colormap {
+ char *colorname; /* Name of color */
+ int ansinum; /* The ANSI escape color number */
+ int nonansinum; /* The non-ANSI escape color number */
+};
+
+static struct colormap colortable[] = {
+ { "black", 0, 0 },
+ { "red", 1, 4 },
+ { "green", 2, 2 },
+ { "yellow", 3, 6 },
+ { "blue", 4, 1 },
+ { "magenta", 5, 5 },
+ { "cyan", 6, 3 },
+ { "white", 7, 7 },
+ { NULL, 0, 0 }
+};
+
/*
* Hash function for component name. The function should be
* case independent and probably shouldn't involve a routine
#define NEWCOMP(cm,name) do { \
cm = ((struct comp *) calloc(1, sizeof (struct comp)));\
cm->c_name = getcpy(name);\
+ cm->c_refcount++;\
ncomp++;\
i = CHASH(name);\
cm->c_next = wantcomp[i];\
} while (0)
#define LV(type, value) do { NEW(type,0,0); fp->f_value = (value); } while (0)
-#define LS(type, str) do { NEW(type,0,0); fp->f_text = (str); fp->f_flags |= FF_STRALLOC; } while (0)
+#define LS(type, str) do { NEW(type,0,0); fp->f_text = getcpy(str); fp->f_flags |= FF_STRALLOC; } while (0)
#define PUTCOMP(comp) do { NEW(FT_COMP,0,0); ADDC(comp); } while (0)
-#define PUTLIT(str) do { NEW(FT_LIT,0,0); fp->f_text = getcpy(str); } while (0)
+#define PUTLIT(str) do { NEW(FT_LIT,0,0); fp->f_text = getcpy(str); fp->f_flags |= FF_STRALLOC; } while (0)
#define PUTC(c) do { NEW(FT_CHAR,0,0); fp->f_char = (c); } while (0)
static char *format_string;
-static unsigned char *usr_fstring; /* for CERROR */
+static char *usr_fstring; /* for CERROR */
#define CERROR(str) compile_error (str, cp)
static void free_component(struct comp *);
static void free_comptable(void);
-
/*
* Lookup a function name in the functable
*/
usr_fstring[errpos] = '\0';
for (i = errpos-errctx; i < errpos; i++) {
-#ifdef LOCALE
- if (iscntrl(usr_fstring[i]))
-#else
- if (usr_fstring[i] < 32)
-#endif
+ if (iscntrl((unsigned char) usr_fstring[i]))
usr_fstring[i] = '_';
}
comptable_initialized = 1;
}
- /* init the component hash table. */
- for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++)
- wantcomp[i] = 0;
-
memset((char *) &fmt_mnull, 0, sizeof(fmt_mnull));
/* it takes at least 4 char to generate one format so we
if (next_fp == NULL)
adios (NULL, "unable to allocate format storage");
- ncomp = 0;
infunction = 0;
cp = compile(format_string);
LS(t->f_type, getlocalmbox());
break;
+ case TF_BOLD:
+ LS(t->f_type, get_term_stringcap("bold"));
+ break;
+
+ case TF_UNDERLN:
+ LS(t->f_type, get_term_stringcap("smul"));
+ break;
+
+ case TF_STNDOUT:
+ LS(t->f_type, get_term_stringcap("smso"));
+ break;
+
+ case TF_RESET:
+ LS(t->f_type, get_term_stringcap("sgr0"));
+ break;
+
+ case TF_HASCLR:
+ LV(t->f_type, get_term_numcap("colors") > 1);
+ break;
+
+ case TF_FGCOLR:
+ case TF_BGCOLR: {
+ struct colormap *cmap = colortable;
+ char *code;
+
+ sp = cp - 1;
+ while (c && c != ')')
+ c = *cp++;
+ cp[-1] = '\0';
+
+ while (cmap->colorname != NULL) {
+ if (strcasecmp(sp, cmap->colorname) == 0)
+ break;
+ cmap++;
+ }
+
+ if (cmap->colorname == NULL) {
+ CERROR("Unknown color name");
+ break;
+ }
+
+ code = get_term_stringparm(t->type == TF_FGCOLR ? "setaf" : "setab",
+ cmap->ansinum, 0);
+
+ /*
+ * If this doesn't have anything, try falling back to setf/setb
+ */
+
+ if (! code)
+ code = get_term_stringparm(t->type == TF_FGCOLR ? "setf" : "setb",
+ cmap->nonansinum, 0);
+
+ LS(t->f_type, code);
+ break;
+ }
+
case TF_NOW:
LV(t->f_type, time((time_t *) 0));
break;
* There is no support for this in the format engine, so right now if
* you try using it you will reach the FT_DONE and simply stop. I'm leaving
* this here in case someone wants to continue the work.
+ *
+ * Okay, got some more information on this from John L. Romine! From an
+ * email he sent to the nmh-workers mailing list on December 2, 2010, he
+ * explains it thusly:
+ *
+ * In this case (scan, formatsbr) it has to do with an extension to
+ * the mh-format syntax to allow for looping.
+ *
+ * The scan format is processed once for each message. Those #ifdef
+ * JLR changes allowed for the top part of the format file to be
+ * processed once, then a second, looping part to be processed
+ * once per message. As I recall, there were new mh-format escape
+ * sequences to delimit the loop. This would have allowed for things
+ * like per-format column headings in the scan output.
+ *
+ * Since existing format files didn't include the scan listing
+ * header (it was hard-coded in scan.c) it would not have been
+ * backward-compatible. All existing format files (including any
+ * local ones) would have needed to be changed to include the format
+ * codes for a header. The practice at the time was not to introduce
+ * incompatible changes in a minor release, and I never managed to
+ * put out a newer major release.
+ *
+ * I can see how this would work, and I suspect part of the motivation was
+ * because the format compiler routines (at the time) couldn't really be
+ * called multiple times on the same message because the memory management
+ * was so lousy. That's been reworked and things are now a lot cleaner,
+ * so I suspect if we're going to allow a format string to be used for the
+ * scan header it might be simpler to have a separate format string just
+ * for the header. But I'll leave this code in for now just in case we
+ * decide that we want some kind of looping support.
*/
static char *
do_loop(char *sp)
free(fp->f_text);
if (fp->f_flags & FF_COMPREF)
free_component(fp->f_comp);
+ fp++;
}
free(fmt);
}
free_comptable();
}
+/*
+ * Free just the text strings from all of the component hash table entries
+ */
+
+void
+fmt_freecomptext(void)
+{
+ unsigned int i;
+ struct comp *cm;
+
+ for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++)
+ for (cm = wantcomp[i]; cm; cm = cm->c_next)
+ if (cm->c_text) {
+ free(cm->c_text);
+ cm->c_text = NULL;
+ }
+}
+
/*
* Find a component in our hash table. This is just a public interface to
* the FINDCOMP macro, so we don't have to expose our hash table.
return cm;
}
+/*
+ * Like fmt_findcomp, but case-insensitive.
+ */
+
+struct comp *
+fmt_findcasecomp(char *component)
+{
+ struct comp *cm;
+
+ for (cm = wantcomp[CHASH(component)]; cm; cm = cm->c_next)
+ if (strcasecmp(component, cm->c_name ? cm->c_name : "") == 0)
+ break;
+
+ return cm;
+}
+
+/*
+ * Add an entry to the component hash table
+ *
+ * Returns true if the component was added, 0 if it already existed.
+ *
+ */
+
+int
+fmt_addcompentry(char *component)
+{
+ struct comp *cm;
+ int i;
+
+ FINDCOMP(cm, component);
+
+ if (cm)
+ return 0;
+
+ NEWCOMP(cm, component);
+
+ /*
+ * ncomp is really meant for fmt_compile() and this function is
+ * meant to be used outside of it. So decrement it just to be safe
+ * (internal callers should be using NEWCOMP()).
+ */
+
+ ncomp--;
+
+ return 1;
+}
+
+/*
+ * Add a string to a component hash table entry.
+ *
+ * Note the special handling for components marked with CT_ADDR. The comments
+ * in fmt_scan.h explain this in more detail.
+ */
+
+int
+fmt_addcomptext(char *component, char *text)
+{
+ int i, found = 0, bucket = CHASH(component);
+ struct comp *cptr = wantcomp[bucket];
+ char *cp;
+
+ while (cptr) {
+ if (strcasecmp(component, cptr->c_name ? cptr->c_name : "") == 0) {
+ found++;
+ if (! cptr->c_text) {
+ cptr->c_text = getcpy(text);
+ } else {
+ i = strlen(cp = cptr->c_text) - 1;
+ if (cp[i] == '\n') {
+ if (cptr->c_type & CT_ADDR) {
+ cp[i] = '\0';
+ cp = add(",\n\t", cp);
+ } else {
+ cp = add("\t", cp);
+ }
+ }
+ cptr->c_text = add(text, cp);
+ }
+ }
+ cptr = cptr->c_next;
+ }
+
+ return found ? bucket : -1;
+}
+
+/*
+ * Append text to a component we've already found. See notes in fmt_scan.h
+ * for more information.
+ */
+
+void
+fmt_appendcomp(int bucket, char *component, char *text)
+{
+ struct comp *cptr;
+
+ if (bucket != -1) {
+ for (cptr = wantcomp[bucket]; cptr; cptr = cptr->c_next)
+ if (strcasecmp(component, cptr->c_name ? cptr->c_name : "") == 0)
+ cptr->c_text = add(text, cptr->c_text);
+ }
+}
+
/*
* Free and reset our component hash table
*/
static void
free_comptable(void)
{
- int i;
+ unsigned int i;
struct comp *cm, *cm2;
for (i = 0; i < sizeof(wantcomp)/sizeof(wantcomp[0]); i++) {
}
wantcomp[i] = 0;
}
+
+ ncomp = 0;
}
/*