/*
 * substvars.c:
 * Function for substituting variables in strings.
 *
 * Copyright (c) 2001 Chris Lightfoot. All rights reserved.
 *
 */

static const char rcsid[] = "$Id: substvars.c,v 1.5 2002/03/18 23:47:34 chris Exp $";

#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */

#include <sys/types.h>

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "util.h"

/* xstrncat:
 * Catenate one string onto another, reallocating space for the first. */
char *xstrncat(char *pfx, const char *sfx, const size_t n) {
    char *s;
    s = xmalloc(strlen(pfx) + n + 1);
    if (!s) return NULL;
    strcpy(s, pfx);
    strncat(s, sfx, n);
    xfree(pfx);
    return s;
}

/* substitute_variables:
 * Substitute variables of the form $(foo) or $(bar[2]) in a string. */
#define SET_ERR(cde, txt, off) do {                                  \
                                    if (err) {                       \
                                        err->code = cde;             \
                                        err->msg = txt;              \
                                        err->offset = (off_t)(off);  \
                                    }                                \
                                    xfree(res);                      \
                                    res = NULL;                      \
                                } while (0)

char *substitute_variables(const char *spec, struct sverr *err, const int nvars, ...) {
    const char **var, **val;
    const char *s, *t;
    int n;
    va_list ap;
    char *res;
    
    var = xcalloc(nvars, sizeof *var);
    val = xcalloc(nvars, sizeof *val);
    res = xstrdup("");

    va_start(ap, nvars);
    for (n = 0; n < nvars; ++n) {
        var[n] = va_arg(ap, const char *);
        val[n] = va_arg(ap, const char *);
    }
    va_end(ap);

    s = spec;
    do {
        t = strstr(s, "$(");
        if (!t) {
            res = xstrncat(res, s, strlen(s));
            s += strlen(s);
        } else {
            const char **u, **u_val;
            res = xstrncat(res, s, t - s);
            for (u = var, u_val = val; u < var + nvars; ++u, ++u_val) {
                if (strncmp(t + 2, *u, strlen(*u)) == 0) {
                    /* Found a variable $(.... */
                    const char *c = t + 2 + strlen(*u); /* Character after variable name. */
                    if (*c == '[') {
                        /* Could be an index into the string. */
                        const char *v;
                        char *w;
                        long off;
                        v = c + 1; /* Start of index. */
                        off = strtol(v, &w, 10);
                        if (v == w || *w != ']' || *(w + 1) != ')') {
                            /* Index was invalid. */
                            SET_ERR(sv_syntax, _("Syntax error in character index"), t - spec);
                            goto fail;
                        }

                        if (!*u_val) {
                            /* Null value of variable. */
                            SET_ERR(sv_nullvalue, _("Variable has null value"), t - spec);
                            goto fail;
                        }

                        /* Negative indices correspond to the end of the string. */
                        if (off < 0) off += strlen(*u_val);

                        if (off < 0 || off >= strlen(*u_val)) {
                            /* String was too short. */
                            SET_ERR(sv_range, _("Character index out of range"), t - spec);
                            goto fail;
                        }

                        res = xstrncat(res, *u_val + off, 1);
                        s = w + 2;
                        break;      /* Successful substitution. */
                    } else if (*c == ')') {
                        /* Simple substitution. */
                        if (!*u_val) {
                            /* Null value of variable. */
                            SET_ERR(sv_nullvalue, _("Variable has null value"), t - spec);
                            goto fail;
                        }
                        
                        res = xstrncat(res, *u_val, strlen(*u_val));
                        s = c + 1;
                        break;      /* Successful substitution. */
                    }
                }
            }
            if (u == var + nvars) {
                /* No substitution. */
                SET_ERR(sv_unknown, _("Syntax error or unknown variable"), t - spec);
                goto fail;
            }
        }
    } while (*s);

fail:
    xfree(var);
    xfree(val);

    return res;
}

#undef SET_ERR


#if 0
/* Simple test program. */
int main(int argc, char **argv) {
    struct sverr foo;
    char *x;
    char *sstring = argv[1];
    x = substitute_variables(sstring, &foo, 4, "user", "chris", "domain", "ex-parrot.com", "home", "/home/chris", "homer", "simpson");
    if (x) printf("x = %s\n", x);
    else printf("err = %s near %.16s\n", foo.msg, sstring + foo.offset);
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1