App-Ariza.git | runner/src/ | env.c
/* Paths, token expansion, and the one list operation PATH needs.
*
* Note what is NOT here: no RAKULIB, no notcurses, no SQLCipher, no
* knowledge of what any variable a bundle sets is for. A directive's
* value arrives with `{root}` in it and leaves with the resolved root in
* it, and that is the entire extent of the runner's opinion about a
* bundle's environment. The values themselves are the renderer's
* (resources/templates/launcher-windows.ariza.j2), which is what lets a
* bundle gain a native dependency without a new runner.
*
* Nothing here consults the filesystem or the environment. Strings go
* in, strings come out, and the platform layer decides what to do with
* them — which is what makes all of it testable off Windows. */
#include <stdlib.h>
#include "ariza_runner.h"
static int is_sep(arz_char c)
{
return c == ARZ_T('\\') || c == ARZ_T('/');
}
arz_char *arz_path_join(const arz_char *root, const arz_char *rel)
{
arz_char *out;
size_t rootlen;
size_t at = 0;
size_t i;
if (root == NULL)
return arz_dup(rel);
if (rel == NULL)
return arz_dup(root);
rootlen = arz_len(root);
/* One separator between the two, however many each brought. */
while (rootlen > 0 && is_sep(root[rootlen - 1]))
rootlen--;
while (is_sep(*rel))
rel++;
out = malloc((rootlen + 1 + arz_len(rel) + 1) * sizeof(*out));
if (out == NULL)
return NULL;
for (i = 0; i < rootlen; i++)
out[at++] = root[i];
if (rootlen > 0 && rel[0] != ARZ_T('\0'))
out[at++] = ARZ_T('\\');
for (i = 0; rel[i] != ARZ_T('\0'); i++)
out[at++] = rel[i];
out[at] = ARZ_T('\0');
return out;
}
arz_char *arz_parent_dir(const arz_char *path)
{
arz_char *out;
size_t n;
size_t i;
if (path == NULL)
return NULL;
n = arz_len(path);
while (n > 0 && is_sep(path[n - 1]))
n--;
while (n > 0 && !is_sep(path[n - 1]))
n--;
if (n == 0)
return NULL;
/* Keep the separator only where dropping it would change the path
* from a root ("C:\") into a drive-relative one ("C:"). */
while (n > 1 && is_sep(path[n - 1]) && !is_sep(path[n - 2]) &&
path[n - 2] != ARZ_T(':'))
n--;
out = malloc((n + 1) * sizeof(*out));
if (out == NULL)
return NULL;
for (i = 0; i < n; i++)
out[i] = path[i];
out[n] = ARZ_T('\0');
return out;
}
arz_char *arz_expand(const arz_char *value, const arz_char *root)
{
size_t rootlen = arz_len(root);
size_t i;
size_t out_len = 0;
size_t at = 0;
arz_char *out;
if (value == NULL)
return NULL;
/* Measure, then fill: two passes over a few dozen units, and no
* arithmetic anybody has to trust. */
for (i = 0; value[i] != ARZ_T('\0'); ) {
if (value[i] == ARZ_T('{') && value[i + 1] == ARZ_T('{')) {
out_len += 1;
i += 2;
} else if (value[i] == ARZ_T('{') && value[i + 1] == ARZ_T('r') &&
value[i + 2] == ARZ_T('o') && value[i + 3] == ARZ_T('o') &&
value[i + 4] == ARZ_T('t') && value[i + 5] == ARZ_T('}')) {
out_len += rootlen;
i += 6;
} else {
out_len += 1;
i++;
}
}
out = malloc((out_len + 1) * sizeof(*out));
if (out == NULL)
return NULL;
for (i = 0; value[i] != ARZ_T('\0'); ) {
if (value[i] == ARZ_T('{') && value[i + 1] == ARZ_T('{')) {
out[at++] = ARZ_T('{');
i += 2;
} else if (value[i] == ARZ_T('{') && value[i + 1] == ARZ_T('r') &&
value[i + 2] == ARZ_T('o') && value[i + 3] == ARZ_T('o') &&
value[i + 4] == ARZ_T('t') && value[i + 5] == ARZ_T('}')) {
size_t j;
for (j = 0; j < rootlen; j++)
out[at++] = root[j];
i += 6;
} else {
out[at++] = value[i++];
}
}
out[at] = ARZ_T('\0');
return out;
}
arz_char *arz_env_path_prepend(const arz_char *dir, const arz_char *current)
{
if (dir == NULL)
return arz_dup(current);
/* No trailing ';' when there is nothing to append: an empty PATH
* entry is the current directory to Windows, which is exactly the
* kind of accidental search path a bundle exists to not have. */
if (current == NULL || current[0] == ARZ_T('\0'))
return arz_dup(dir);
return arz_cat(dir, ARZ_T(";"), current);
}
arz_char *arz_interpreter_path(const arz_char *root)
{
return arz_path_join(root, ARZ_T("rakudo\\bin\\raku.exe"));
}