Auto-variable substitution moves out of test.c into its own unit-tested module (autovars.c) with an explicit context for $jobid, $? and $jobcount, keeping today's token rule, messages and buffer handling. New: a token $jobid[...] expands $jobid and keeps the rest of the token, so $jobid[3] names element 3 of the array just submitted; it fails like an unset $jobid when none is set. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
126 lines
3.9 KiB
C
126 lines
3.9 KiB
C
/*
|
|
* autovars.c -- xce_test auto-variable substitution (see include/autovars.h).
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "autovars.h"
|
|
|
|
static void set_err(char* errbuf, size_t errlen, const char* msg)
|
|
{
|
|
if ((errbuf != NULL) && (errlen > 0)) {
|
|
snprintf(errbuf, errlen, "%s", msg);
|
|
}
|
|
}
|
|
|
|
int autovar_substitute(const char* line, const autovar_ctx_t* ctx, char* out,
|
|
size_t outlen, char* errbuf, size_t errlen)
|
|
{
|
|
const char* p = line;
|
|
const char* first;
|
|
size_t o = 0;
|
|
|
|
if ((errbuf != NULL) && (errlen > 0)) {
|
|
errbuf[0] = '\0';
|
|
}
|
|
if ((line == NULL) || (ctx == NULL) || (out == NULL) || (outlen == 0)) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* `!` shell-escape and `#` comment lines are left untouched. */
|
|
first = line;
|
|
while ((*first == ' ') || (*first == '\t')) {
|
|
first++;
|
|
}
|
|
if ((*first == '!') || (*first == '#')) {
|
|
size_t len = strlen(line);
|
|
|
|
if (len >= outlen) {
|
|
return -ENOSPC;
|
|
}
|
|
memcpy(out, line, len + 1);
|
|
return 0;
|
|
}
|
|
|
|
while (*p != '\0') {
|
|
/* A `$` starts a candidate auto-var only when it begins a token, i.e.
|
|
* the previous char is the line start or whitespace. */
|
|
int at_token_start
|
|
= (p == line) || (*(p - 1) == ' ') || (*(p - 1) == '\t');
|
|
|
|
if ((*p == '$') && at_token_start) {
|
|
const char* name = p + 1;
|
|
const char* end = name;
|
|
const char* repl = NULL;
|
|
const char* stop; /* where the replaced text ends */
|
|
char numbuf[32];
|
|
|
|
/* Scan to the token end (whitespace or NUL). */
|
|
while ((*end != '\0') && (*end != ' ') && (*end != '\t')) {
|
|
end++;
|
|
}
|
|
|
|
size_t namelen = (size_t)(end - name);
|
|
/* `$jobid[...]` (Req 4.10): only `$jobid` is replaced; the rest
|
|
* of the token, from the `[`, is copied unchanged. */
|
|
int jobid_index
|
|
= (namelen > 5) && (strncmp(name, "jobid[", 6) == 0);
|
|
|
|
stop = end;
|
|
if ((namelen == 1) && (name[0] == '?')) {
|
|
snprintf(numbuf, sizeof(numbuf), "%d", ctx->last_status);
|
|
repl = numbuf;
|
|
} else if (jobid_index
|
|
|| ((namelen == 5)
|
|
&& (strncmp(name, "jobid", 5) == 0))) {
|
|
/* Req 4.11: an unset $jobid fails `$jobid[...]` the same
|
|
* way as `$jobid`. */
|
|
if ((ctx->jobid == NULL) || (ctx->jobid[0] == '\0')) {
|
|
set_err(errbuf, errlen,
|
|
"autovar: $jobid referenced before set");
|
|
return -EINVAL;
|
|
}
|
|
repl = ctx->jobid;
|
|
stop = name + 5;
|
|
} else if ((namelen == 8) && (strncmp(name, "jobcount", 8) == 0)) {
|
|
if (ctx->jobcount < 0) {
|
|
set_err(errbuf, errlen,
|
|
"autovar: $jobcount referenced before set");
|
|
return -EINVAL;
|
|
}
|
|
snprintf(numbuf, sizeof(numbuf), "%d", ctx->jobcount);
|
|
repl = numbuf;
|
|
}
|
|
|
|
if (repl != NULL) {
|
|
size_t rlen = strlen(repl);
|
|
|
|
if ((o + rlen) >= outlen) {
|
|
return -ENOSPC;
|
|
}
|
|
memcpy(out + o, repl, rlen);
|
|
o += rlen;
|
|
p = stop; /* consume `$name`; a `[...]` rest is copied */
|
|
continue;
|
|
}
|
|
|
|
/* Not a recognized auto-var: copy the whole token verbatim. */
|
|
while (p < end) {
|
|
if ((o + 1) >= outlen) {
|
|
return -ENOSPC;
|
|
}
|
|
out[o++] = *p++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if ((o + 1) >= outlen) {
|
|
return -ENOSPC;
|
|
}
|
|
out[o++] = *p++;
|
|
}
|
|
|
|
out[o] = '\0';
|
|
return 0;
|
|
}
|