Add a small string formatting utility to opr
authorNathaniel Wesley Filardo <nwfilardo@gmail.com>
Sun, 30 Mar 2014 02:56:21 +0000 (22:56 -0400)
committerD Brashear <shadow@your-file-system.com>
Wed, 21 May 2014 11:22:41 +0000 (07:22 -0400)
This is to be used by the (coming next) vos-foreach utility, but it seemed
sufficiently general and useful to break out into its own free-standing
component.

Change-Id: I92c3a615fecb80e1766f78492b229a826a23e18a
Reviewed-on: http://gerrit.openafs.org/10965
Tested-by: BuildBot <buildbot@rampaginggeek.com>
Reviewed-by: Jeffrey Altman <jaltman@your-file-system.com>
Reviewed-by: D Brashear <shadow@your-file-system.com>

src/opr/Makefile.in
src/opr/NTMakefile
src/opr/fmt.c [new file with mode: 0644]
src/opr/fmt.h [new file with mode: 0644]
src/opr/liboafs_opr.la.sym
tests/TESTS
tests/opr/Makefile.in
tests/opr/fmt-t.c [new file with mode: 0644]

index a896dbc..af6a61b 100644 (file)
@@ -3,12 +3,13 @@ include @TOP_OBJDIR@/src/config/Makefile.config
 include @TOP_OBJDIR@/src/config/Makefile.pthread
 include @TOP_OBJDIR@/src/config/Makefile.libtool
 
-LT_objs = assert.lo casestrcpy.lo dict.lo proc.lo rbtree.lo uuid.lo
+LT_objs = assert.lo casestrcpy.lo dict.lo fmt.lo proc.lo rbtree.lo uuid.lo
 LT_libs = $(LIB_hcrypto) $(LIB_roken)
 
 HEADERS = $(TOP_INCDIR)/afs/opr.h \
          $(TOP_INCDIR)/afs/opr_assert.h \
          $(TOP_INCDIR)/opr/dict.h \
+         $(TOP_INCDIR)/opr/fmt.h \
          $(TOP_INCDIR)/opr/jhash.h \
          $(TOP_INCDIR)/opr/lock.h \
          $(TOP_INCDIR)/opr/lockstub.h \
@@ -35,7 +36,6 @@ $(TOP_LIBDIR)/libopr.a: libopr.a
 $(TOP_LIBDIR)/libopr_pic.a: libopr_pic.la
        $(INSTALL_DATA) .libs/libopr_pic.a $@
 
-
 $(TOP_INCDIR)/afs/opr.h: opr.h
        $(INSTALL_DATA) $? $@
 
@@ -45,6 +45,9 @@ $(TOP_INCDIR)/afs/opr_assert.h: ${srcdir}/opr_assert.h
 $(TOP_INCDIR)/opr/dict.h: ${srcdir}/dict.h
        $(INSTALL_DATA) $? $@
 
+$(TOP_INCDIR)/opr/fmt.h: ${srcdir}/fmt.h
+       $(INSTALL_DATA) $? $@
+
 $(TOP_INCDIR)/opr/jhash.h: ${srcdir}/jhash.h
        $(INSTALL_DATA) $? $@
 
index 9fc9b09..7f49d3b 100644 (file)
@@ -14,6 +14,7 @@ INCFILES = \
        $(INCFILEDIR)\opr.h \
        $(INCFILEDIR)\opr_assert.h \
        $(DESTDIR)\include\opr\dict.h \
+       $(DESTDIR)\include\opr\fmt.h \
        $(DESTDIR)\include\opr\jhash.h \
        $(DESTDIR)\include\opr\proc.h \
        $(DESTDIR)\include\opr\queue.h \
@@ -35,6 +36,7 @@ LIBOBJS = \
        $(OUT)\assert.obj \
        $(OUT)\casestrcpy.obj \
        $(OUT)\dict.obj \
+       $(OUT)\fmt.obj \
        $(OUT)\proc.obj \
        $(OUT)\rbtree.obj \
        $(OUT)\uuid.obj \
diff --git a/src/opr/fmt.c b/src/opr/fmt.c
new file mode 100644 (file)
index 0000000..2bd8ee8
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2014, Nathaniel Wesley Filardo.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <afsconfig.h>
+#include <afs/param.h>
+#include <roken.h>
+#include <afs/opr.h>
+#include "fmt.h"
+
+struct opr_fmt_ctx_priv_s {
+    const char *inp;
+    char *outp;
+    int outleft;
+    int written;
+};
+
+static void
+opr_fmt_cb(opr_fmt_ctx *x, char c) {
+    x->priv->written++;
+
+    if(x->priv->outleft > 0) {
+       *(x->priv->outp++) = c;
+       x->priv->outleft--;
+    }
+}
+
+static int
+opr_fmt_internal(opr_fmt_ctx *ctx, va_list va)
+{
+    opr_fmt_ctx_priv *priv = ctx->priv;
+    char c;
+    char sawpct = 0;
+    while((c = *(priv->inp++))) {
+       if(sawpct == 0) {
+           if(c != '%') {
+               opr_fmt_cb(ctx, c);
+           } else {
+               sawpct = 1;
+           }
+       } else {
+           opr_fmtr fmtr;
+           if((fmtr = ctx->fmtrs[(unsigned int) c]) != NULL) {
+               switch(fmtr(ctx, c, va)) {
+               case  1: break;
+               case  0: sawpct = 0; break;
+               case -1: return -1;
+               default: opr_Assert(0);
+               }
+           } else {
+               opr_fmt_cb(ctx, c);
+               sawpct = 0;
+           }
+       }
+    }
+    opr_fmt_cb(ctx, '\0'); priv->written--;
+
+    return priv->written;
+}
+
+/**
+ * Carry out a %-escaped variable interpolation according to a given set of
+ * callbacks, user data, and format string.  Output is placed into the given
+ * buffer, and no more than the indicated number of bytes will be written.
+ *
+ * Each formatter callback receives
+ *   - the formatting context
+ *   - the escape character being processed
+ *   - the suffix of the varargs passed to opr_fmt
+ * To write to the output buffer, it should invoke ctx->put with ctx and the
+ * desired emission character.  It should return one of 0 to indicate
+ * success and exit from escape state; 1 to indicate success and a desire
+ * to continue in escape state (e.g. while scanning for modifiers as in
+ * %0x); or -1 to indicate failure, which will abort the whole opr_fmt call.
+ *
+ * @param fmtrs Is a 256-entry array of callbacks, one for each possible
+ *              unsigned char value. (char is cast to an unsigned int,
+ *              internally.)
+ * @param user Passed through to the callbacks for callee's use.
+ * @param out Output buffer.  Will always be properly nul-terminated on
+ *            successful (i.e. non-negative) return.
+ * @param n Maximum number of bytes to write to the output buffer.
+ * @param fstr The format string.  Additional arguments are passed to the
+ *             callbacks for their use ala printf.
+ *
+ * @return The number of bytes that were (if >= 0 and < n) or would have been
+ *         (if >= n) written to the buffer, excluding the terminating nul
+ *         byte; in the latter case, the buffer is full of the first n-1
+ *         bytes of the string (and is properly nul-terminated).  (This
+ *         follows C99's printf conventions and may allow user code to
+ *         repeat the call with a big enough buffer the next time, rather
+ *         than needing to probe for a large enough buffer.)  If the return
+ *         value is < 0, an error occurred during one of the callbacks'
+ *         operation.
+ */
+int
+opr_fmt(opr_fmtr *fmtrs, void *user, char *out, int n, const char *fstr, ...)
+{
+    opr_fmt_ctx_priv priv;
+    opr_fmt_ctx ctx;
+    int ret;
+    va_list va;
+
+    priv.inp = fstr;
+    priv.outp = out;
+    priv.outleft = n;
+    priv.written = 0;
+
+    ctx.fmtrs = fmtrs;
+    ctx.user  = user ;
+    ctx.put   = opr_fmt_cb;
+    ctx.priv  = &priv;
+
+    va_start(va, fstr);
+    ret = opr_fmt_internal(&ctx, va);
+    va_end(va);
+
+    if (ret > 0 && ret >= n) {
+      out[n-1] = '\0';
+    }
+
+    return ret;
+}
diff --git a/src/opr/fmt.h b/src/opr/fmt.h
new file mode 100644 (file)
index 0000000..68f676b
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2014, Nathaniel Wesley Filardo.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _OPR_FMT_H_
+#define _OPR_FMT_H_
+
+typedef struct opr_fmt_ctx_priv_s opr_fmt_ctx_priv;
+typedef struct opr_fmt_ctx_s opr_fmt_ctx;
+
+typedef int (*opr_fmtr)(opr_fmt_ctx *, char, va_list);
+
+struct opr_fmt_ctx_s {
+    opr_fmtr *fmtrs;         /* Byte-wise dispatch table; 256 entries */
+    void *user;              /* User-controlled private data */
+    void (*put)(opr_fmt_ctx *, char); /* Write a character to the buffer */
+
+    opr_fmt_ctx_priv *priv;  /* Private for internals of opr_fmt */
+};
+
+int opr_fmt(opr_fmtr *fmtrs, void *user, char *out, int n, const char *fstr, ...);
+
+#endif
index d95b316..6ced340 100644 (file)
@@ -2,6 +2,7 @@ opr_AssertionFailed
 opr_dict_Free
 opr_dict_Init
 opr_lcstring
+opr_fmt
 opr_procsize
 opr_rbtree_first
 opr_rbtree_init
index 2f30cbb..f8f4e1f 100644 (file)
@@ -6,6 +6,7 @@ auth/authcon
 auth/realms
 cmd/command
 opr/dict
+opr/fmt
 opr/jhash
 opr/queues
 opr/rbtree
index 30a4d95..5e7057c 100644 (file)
@@ -7,13 +7,16 @@ MODULE_CFLAGS = -I$(srcdir)/../..
 
 LIBS=../tap/libtap.a $(abs_top_builddir)/src/opr/liboafs_opr.la
 
-tests = dict-t jhash-t queues-t rbtree-t time-t uuid-t
+tests = dict-t fmt-t jhash-t queues-t rbtree-t time-t uuid-t
 
 all check test tests: $(tests)
 
 dict-t: dict-t.o
        $(LT_LDRULE_static) dict-t.o $(LIBS) $(XLIBS)
 
+fmt-t: fmt-t.o
+       $(LT_LDRULE_static) fmt-t.o $(LIBS) $(XLIBS)
+
 queues-t: queues-t.o
        $(LT_LDRULE_static) queues-t.o ../tap/libtap.a $(XLIBS)
 
diff --git a/tests/opr/fmt-t.c b/tests/opr/fmt-t.c
new file mode 100644 (file)
index 0000000..4aa57d5
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014, Nathaniel Wesley Filardo.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to permit
+ * persons to whom the Software is furnished to do so, subject to the
+ * following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+ * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+ * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+ * USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+
+#include <afsconfig.h>
+#include <afs/param.h>
+#include <roken.h>
+#include <tests/tap/basic.h>
+#include <opr/fmt.h>
+
+int
+testfmtr(opr_fmt_ctx *ctx, char c, va_list va) {
+    const char *str = va_arg(va, const char *);
+    while(*str) { ctx->put(ctx, *str++); }
+    return 0;
+}
+
+int
+testfmtr2(opr_fmt_ctx *ctx, char c, va_list va) {
+    char *str = ctx->user;
+    while (*str) { ctx->put(ctx, *str++); }
+    return 0;
+}
+
+int main()
+{
+  static opr_fmtr fmtrs[256] = { ['s'] = testfmtr, ['t'] = testfmtr2 };
+  static char buf[100];
+
+  plan(1);
+
+  opr_fmt(fmtrs, "EHLO", buf, 100, "FOO %s %t", "abc");
+  ok(0 == strcmp(buf,"FOO abc EHLO"), "Basic substitution test");
+
+  return 0;
+}