Add a small string formatting utility to opr
[openafs.git] / src / opr / fmt.c
1 /*
2  * Copyright 2014, Nathaniel Wesley Filardo.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to permit
9  * persons to whom the Software is furnished to do so, subject to the
10  * following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included
13  * in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
18  * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
21  * USE OR OTHER DEALINGS IN THE SOFTWARE.
22  */
23
24 #include <afsconfig.h>
25 #include <afs/param.h>
26 #include <roken.h>
27 #include <afs/opr.h>
28 #include "fmt.h"
29
30 struct opr_fmt_ctx_priv_s {
31     const char *inp;
32     char *outp;
33     int outleft;
34     int written;
35 };
36
37 static void
38 opr_fmt_cb(opr_fmt_ctx *x, char c) {
39     x->priv->written++;
40
41     if(x->priv->outleft > 0) {
42         *(x->priv->outp++) = c;
43         x->priv->outleft--;
44     }
45 }
46
47 static int
48 opr_fmt_internal(opr_fmt_ctx *ctx, va_list va)
49 {
50     opr_fmt_ctx_priv *priv = ctx->priv;
51     char c;
52     char sawpct = 0;
53     while((c = *(priv->inp++))) {
54         if(sawpct == 0) {
55             if(c != '%') {
56                 opr_fmt_cb(ctx, c);
57             } else {
58                 sawpct = 1;
59             }
60         } else {
61             opr_fmtr fmtr;
62             if((fmtr = ctx->fmtrs[(unsigned int) c]) != NULL) {
63                 switch(fmtr(ctx, c, va)) {
64                 case  1: break;
65                 case  0: sawpct = 0; break;
66                 case -1: return -1;
67                 default: opr_Assert(0);
68                 }
69             } else {
70                 opr_fmt_cb(ctx, c);
71                 sawpct = 0;
72             }
73         }
74     }
75     opr_fmt_cb(ctx, '\0'); priv->written--;
76
77     return priv->written;
78 }
79
80 /**
81  * Carry out a %-escaped variable interpolation according to a given set of
82  * callbacks, user data, and format string.  Output is placed into the given
83  * buffer, and no more than the indicated number of bytes will be written.
84  *
85  * Each formatter callback receives
86  *   - the formatting context
87  *   - the escape character being processed
88  *   - the suffix of the varargs passed to opr_fmt
89  * To write to the output buffer, it should invoke ctx->put with ctx and the
90  * desired emission character.  It should return one of 0 to indicate
91  * success and exit from escape state; 1 to indicate success and a desire
92  * to continue in escape state (e.g. while scanning for modifiers as in
93  * %0x); or -1 to indicate failure, which will abort the whole opr_fmt call.
94  *
95  * @param fmtrs Is a 256-entry array of callbacks, one for each possible
96  *              unsigned char value. (char is cast to an unsigned int,
97  *              internally.)
98  * @param user Passed through to the callbacks for callee's use.
99  * @param out Output buffer.  Will always be properly nul-terminated on
100  *            successful (i.e. non-negative) return.
101  * @param n Maximum number of bytes to write to the output buffer.
102  * @param fstr The format string.  Additional arguments are passed to the
103  *             callbacks for their use ala printf.
104  *
105  * @return The number of bytes that were (if >= 0 and < n) or would have been
106  *         (if >= n) written to the buffer, excluding the terminating nul
107  *         byte; in the latter case, the buffer is full of the first n-1
108  *         bytes of the string (and is properly nul-terminated).  (This
109  *         follows C99's printf conventions and may allow user code to
110  *         repeat the call with a big enough buffer the next time, rather
111  *         than needing to probe for a large enough buffer.)  If the return
112  *         value is < 0, an error occurred during one of the callbacks'
113  *         operation.
114  */
115 int
116 opr_fmt(opr_fmtr *fmtrs, void *user, char *out, int n, const char *fstr, ...)
117 {
118     opr_fmt_ctx_priv priv;
119     opr_fmt_ctx ctx;
120     int ret;
121     va_list va;
122
123     priv.inp = fstr;
124     priv.outp = out;
125     priv.outleft = n;
126     priv.written = 0;
127
128     ctx.fmtrs = fmtrs;
129     ctx.user  = user ;
130     ctx.put   = opr_fmt_cb;
131     ctx.priv  = &priv;
132
133     va_start(va, fstr);
134     ret = opr_fmt_internal(&ctx, va);
135     va_end(va);
136
137     if (ret > 0 && ret >= n) {
138       out[n-1] = '\0';
139     }
140
141     return ret;
142 }