bake/bake.c

394 lines
12 KiB
C
Raw Normal View History

2024-08-31 19:05:09 -04:00
/* @BAKE cc -std=c99 -O2 -Wall -Wextra -Wpedantic -Wno-implicit-fallthrough -o @SHORT @FILENAME @ARGS @STOP */
/* @BAKE cc -std=c99 -O2 -Wall -Wextra -Wpedantic -Wno-implicit-fallthrough -o '@{\} @{\}@SHORT\}}' @FILENAME @ARGS @STOP */
/* @BAKE cc -std=c99 -O2 -Wall -Wextra -Wpedantic -Wno-implicit-fallthrough -o '@{\}}' @FILENAME @ARGS @STOP */
/* @BAKE cc -std=c99 -O2 -Wall -Wextra -Wpedantic -Wno-implicit-fallthrough -o '@{a}' @FILENAME @ARGS @STOP */
#define _GNU_SOURCE
2023-09-28 14:32:53 -04:00
#include <ctype.h>
2023-09-27 01:28:54 -04:00
#include <errno.h>
2023-09-29 03:05:40 -04:00
#include <fcntl.h>
#include <limits.h>
2023-09-27 01:28:54 -04:00
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
2023-09-29 03:05:40 -04:00
#include <sys/mman.h>
2023-09-27 01:28:54 -04:00
#include <sys/stat.h>
2024-02-26 18:34:36 -05:00
#include <sys/wait.h>
2023-09-28 14:32:53 -04:00
#include <unistd.h>
#define BUFFER_SIZE (1 << 12)
2024-08-31 19:05:09 -04:00
#define START "@" "BAKE" " "
#define STOP "@" "STOP"
#define AUTONOMOUS_COMPILE 0
#define ENABLE_COLOR 1
#if ENABLE_COLOR == 1
# define RED "\033[91m"
# define GREEN "\033[92m"
# define YELLOW "\033[93m"
# define BOLD "\033[1m"
# define RESET "\033[0m"
#elif ENABLE_COLOR
# define RED "\033[91;5m"
# define GREEN "\033[96;7m"
# define YELLOW "\033[94m"
# define BOLD "\033[1;4m"
# define RESET "\033[0m"
#else
# define RED
# define GREEN
# define YELLOW
# define BOLD
# define RESET
#endif
#define ARRLEN(x) (sizeof (x) / sizeof (x [0]))
2024-08-31 19:05:09 -04:00
__attribute__((__section__(".text"))) static volatile char autonomous_compile [] =
"@BAKE cc -std=c99 -O2 -Wall -Wextra -Wpedantic -Wno-implicit-fallthrough -o @SHORT @FILENAME.c @ARGS @STOP";
2024-02-26 18:34:36 -05:00
2023-09-27 01:28:54 -04:00
static void
swap (char * a, char * b) {
2023-09-27 01:28:54 -04:00
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
static int
root (char ** rootp) {
char x [1] = {'\0'};
char * root = *rootp;
size_t len = strlen (root);
int ret;
while (len && root [len] != '/') { --len; }
if (!len) { return 0; }
swap (root + len, x);
ret = chdir (root);
swap (root + len, x);
*rootp += len + 1;
return ret;
2023-09-27 01:28:54 -04:00
}
static char *
shorten (char * filename) {
2024-03-02 05:13:24 -05:00
size_t i, last, len;
static char sh [FILENAME_MAX];
len = strlen (filename);
2024-03-02 05:13:24 -05:00
for (last = i = 0; i < len; ++i) {
if (filename [i] == '.') { last = i; }
2024-03-02 05:13:24 -05:00
}
last = last ? last : i;
strncpy (sh, filename, last);
sh [last] = '\0';
2024-03-02 05:13:24 -05:00
return sh;
}
static char *
all_args (size_t argc, char ** argv) {
static char buffer [BUFFER_SIZE] = {0};
2024-04-04 22:08:50 -04:00
if (argc > 0) {
2024-03-02 05:13:24 -05:00
size_t i, len = argc;
for (i = 0; i < argc; ++i) { len += strlen (argv [i]); }
buffer [len] = '\0';
2024-04-04 22:08:50 -04:00
for (len = 0, i = 0; i < argc; ++i) {
strcpy (buffer + len, argv [i]);
len += strlen (argv [i]) + 1;
if (i + 1 < argc) { buffer [len - 1] = ' '; }
}}
return buffer;
2024-03-02 05:13:24 -05:00
}
static size_t
lines (char * buffer, size_t off) {
size_t line = 1;
char * end = buffer + off;
while (buffer < end) {
if (*buffer == '\n') { ++line; }
++buffer;
}
return line;
}
2023-11-13 00:13:35 -05:00
static char *
2024-08-31 19:05:09 -04:00
expand_buffer(char * expanded, char * buffer, size_t length, char ** pairs, size_t count) {
size_t old, new, i, f, off = 0;
/* I need to test the bounds checking here, it'll crash normally if this provision doesn't do anything, though. */
length &= (1 << 12) - 1;
for (f = 0; f < length; ++f) {
for (i = 0; i < count; i += 2) {
old = strlen (pairs [i]);
new = strlen (pairs [i + 1]);
if (memcmp (buffer + f - off, pairs [i], old) == 0) {
if (f && buffer [f - off - 1] == '\\')
{ --f; --off; break; }
memcpy (expanded + f, pairs [i + 1], new);
f += new;
length += new - old;
off += new - old;
break;
}}
expanded [f] = buffer [f - off];
}
expanded [f] = '\0';
return expanded;
}
2023-11-13 00:13:35 -05:00
static char *
2024-08-31 19:05:09 -04:00
expand (char * buffer, size_t length, char ** pairs, size_t count) {
static char expanded [BUFFER_SIZE] = {0};
return expand_buffer (expanded, buffer, length, pairs, count);
}
static char *
expunge (char * to, char * from, size_t length, char ** pair, size_t count) {
size_t i, f;
(void)count; /* count isn't actually used... */
static char expunge_me [FILENAME_MAX] = {0};
for (i = 0; i < length; ++i) {
if (memcmp(from + i, pair[0], strlen(pair[0])) == 0) {
if (from[i - 1] == '\\') { --i; memmove(from + i, from + i + 1, length - i); i+=1+strlen(pair[0]); --length; continue; }
for (f = i + strlen(pair[0]); f < length; ++f) {
if (memcmp(from + f, pair[1], strlen(pair[1])) == 0) {
if (from[f - 1] == '\\') { --f; memmove(from + f, from + f + 1, length - f); --length; continue; }
memmove(to + i, from + i + strlen(pair[0]), length - i - strlen(pair[0]));
memmove(to + f - strlen(pair[1]), from + f - 1, length - f);
/* memcpy(to + f - 2, " ", strlen(pair[1])); */
memcpy(expunge_me, to + i, f - i - 1 - strlen(pair[1]));
goto end;
}
}
}
}
end:
return expunge_me;
}
static char *
getmap (char * filename, size_t * length) {
char * buffer = NULL;
int fd = open (filename, O_RDONLY);
if (fd != -1) {
struct stat s;
if (!fstat (fd, &s)
&& s.st_mode & S_IFREG
&& s.st_size) {
*length = (size_t) s.st_size;
buffer = (char *) mmap (NULL, s.st_size, PROT_READ, MAP_SHARED, fd, 0);
2024-04-06 19:36:07 -04:00
}
close (fd);
2024-04-06 19:36:07 -04:00
}
return buffer;
2024-04-06 19:36:07 -04:00
}
2024-04-04 22:08:50 -04:00
# define color_printf(...) color_fprintf (stdout, __VA_ARGS__)
/* not perfect, too simple, doesn't work with a var, only a literal. */
# define color_fputs(fp, msg) color_fprintf (fp, msg "\n")
# define color_puts(msg) color_fputs (stdout, msg)
2024-04-04 22:08:50 -04:00
static int color = ENABLE_COLOR;
2024-04-04 22:08:50 -04:00
static void
color_fprintf (FILE * fp, char * format, ...) {
va_list ap;
char * buf;
2024-04-04 22:08:50 -04:00
va_start (ap, format);
2024-04-04 22:08:50 -04:00
if (color) {
vfprintf (fp, format, ap);
va_end (ap);
return;
}
vasprintf (&buf, format, ap);
if (buf) {
char * expanded, * colors [] = {
YELLOW, "",
GREEN, "",
RED, "",
BOLD, "",
RESET, ""
};
size_t count = ARRLEN (colors);
expanded = expand (buf, strlen (buf), colors, count - 2);
expanded = expand (expanded, strlen (buf), colors + count - 2, 2);
fwrite (expanded, strlen (expanded), 1, fp);
2024-04-04 22:08:50 -04:00
}
free (buf);
va_end (ap);
2023-09-28 14:32:53 -04:00
}
2024-08-31 19:05:09 -04:00
/* -- */
2023-09-28 14:32:53 -04:00
int main (int argc, char ** argv) {
void help (void);
2024-08-31 19:05:09 -04:00
int off, run = 1, list = 0, ex = 0, select_input, select = 1;
size_t length, begin = 0, end = 0;
2024-03-02 05:13:24 -05:00
pid_t pid;
2024-08-31 19:05:09 -04:00
char line [10], expanded [BUFFER_SIZE], * buffer, * expandedp, * args, * shortened, * filename = NULL,
* paren [] = { "@{", "}" }, * expunge_me = NULL, * bake_begin = START, * bake_end = STOP;
/* opts */
for (off = 1; off < argc; ++off) {
if (argv [off][0] != '-') { filename = argv [off]; ++off; break; }
if (argv [off][1] != '-') {
while (*(++argv [off]))
switch (argv [off][0]) {
2024-08-31 19:05:09 -04:00
select: case 's':
select = atoi (argv [off] + 1);
if (!select) {
++off;
if (off >= argc) { help (); }
select = atoi (argv [off]);
}
if (select) { goto next; }
2024-08-31 19:05:09 -04:00
default: case 'h': help ();
case 'l': list = 1;
case 'n': run = 0; break;
case 'c': color = 0; break;
case 'x': ex = 1; break;
case 'q': fclose(stderr); fclose(stdout); break;
}
continue;
}
argv[off] += 2;
2024-08-31 19:05:09 -04:00
if (strcmp(argv[off], "select") == 0) { goto select; }
else if (strcmp(argv[off], "list") == 0) { list = 1; run = 0; }
else if (strcmp(argv[off], "dry-run") == 0) { run = 0; }
else if (strcmp(argv[off], "color") == 0) { color = 0; }
else if (strcmp(argv[off], "expunge") == 0) { ex = 1; }
else if (strcmp(argv[off], "quiet") == 0) { fclose(stderr); fclose(stdout); }
else if (strcmp(argv[off], "help") == 0) { help(); }
next:;
2024-02-26 18:34:36 -05:00
}
if (argc == 1 || !filename) { help (); }
select_input = select;
/* roots to directory of filename and mutates filename with the change */
root (&filename);
buffer = getmap (filename, &length);
if (!buffer) {
color_fprintf (stderr, RED "%s" RESET ": Could not access '" BOLD "%s" RESET "'\n", argv [0], filename);
return 1;
2024-04-06 19:36:07 -04:00
}
2024-08-31 19:05:09 -04:00
for (begin = 0; (list || select) && begin < length - strlen (bake_begin); ++begin) {
if (memcmp (buffer + begin, bake_begin, strlen (bake_begin)) == 0) {
size_t stop = begin;
end = begin;
again: while (end < length && buffer[end] != '\n') { ++end; }
if (buffer[end - 1] == '\\') { ++end; goto again; }
2024-08-31 19:05:09 -04:00
while (stop < length - strlen(bake_end)) {
if (memcmp(buffer + stop, bake_end, strlen(bake_end)) == 0) {
if (stop && buffer[stop - 1] == '\\') { ++stop; continue; }
end = stop;
break;
2024-04-04 22:08:50 -04:00
}
++stop;
}
if (list) {
color_printf (GREEN "%d,%d" RESET ": " BOLD, select++, lines (buffer, begin));
fwrite (buffer + begin, 1, end - begin, stdout);
color_puts (RESET);
} else { --select; }
}}
2024-08-31 19:05:09 -04:00
begin += strlen (bake_begin) - 1;
if (list) { return 0; }
if (end < begin) {
color_fprintf (stderr, RED "%s" RESET ": " BOLD "%d" RESET " is out of range\n", argv [0], select_input);
return 1;
2023-09-29 03:05:40 -04:00
}
2023-09-28 02:14:17 -04:00
/* expansion */
args = all_args (argc - off, argv + off);
shortened = shorten (filename);
char * pair [] = {
"@FILENAME", filename,
"@FILE", filename,
"$@", filename,
"@SHORT", shortened,
"$*", shortened,
"@ARGS", args,
"$+", args,
"@LINE", line
2024-08-31 19:05:09 -04:00
};
snprintf (line, 16, "%lu", lines (buffer, begin));
2024-08-31 19:05:09 -04:00
expandedp = expand (buffer + begin, end - begin, pair, ARRLEN (pair));
memcpy(expanded, expandedp, BUFFER_SIZE);
2024-08-31 19:05:09 -04:00
expunge_me = expunge (expanded, expanded, strlen(expanded), paren, ARRLEN (paren));
munmap (buffer, length);
/* print and execute */
2024-08-31 19:05:09 -04:00
color_fprintf (stderr, GREEN "%s" RESET ": " BOLD "%s" RESET, argv [0], expanded);
if (expanded[strlen(expanded)] != '\n') { puts(""); }
2024-08-31 19:05:09 -04:00
if (ex) {
color_fprintf (stderr, GREEN "%s" RESET ": removing '%s'\n", argv [0], expunge_me);
char * jin = ".c";
if (expunge_me
&& run
&& /* just in case */
memcmp(expunge_me + strlen(expunge_me) - strlen(jin), jin, strlen(jin)) != 0)
{ remove(expunge_me); }
return 0;
}
if (!run) { return 0; }
2024-08-31 19:05:09 -04:00
color_fprintf (stderr, GREEN "output" RESET ":\n");
2024-08-31 19:05:09 -04:00
fflush(stdout);
if ((pid = fork ()) == 0) {
execl ("/bin/sh", "sh", "-c", expanded, NULL);
return 0; /* execl overwrites the process anyways */
}
if (pid == -1) {
color_fprintf (stderr, GREEN "%s" RESET ": %s, %s\n",
argv [0], "Fork Error", strerror (errno));
return 1;
2024-03-02 05:13:24 -05:00
}
/* reuse of run as status return */
if (waitpid (pid, &run, 0) < 0) {
color_fprintf (stderr, GREEN "%s" RESET ": " RED "%s" RESET ", %s\n",
argv [0], "Wait PID Error", strerror (errno));
return 1;
2023-09-29 03:05:40 -04:00
}
if (!WIFEXITED (run)) {
return 1;
}
return WEXITSTATUS (run);
}
2023-09-28 02:14:17 -04:00
void help (void) {
char * help =
BOLD "bake [-chln] [-s <n>] <FILENAME> [ARGS...]; version 20240804\n\n" RESET
GREEN "Bake" RESET " is a simple tool meant to execute embedded shell commands within\n"
"any file. It executes with /bin/sh the command after a \"" BOLD "@BAKE" RESET " \" to\n"
"the end of the line (a UNIX newline: '\\n').\n\n"
"It expands some macros,\n"
YELLOW "\t@NAME " RESET "- filename\n"
YELLOW "\t@SHORT " RESET "- shortened filename\n"
YELLOW "\t@ARGS " RESET "- other arguments to the program\n"
YELLOW "\t@LINE " RESET "- line number at the selected " BOLD "@BAKE" RESET "\n\n"
"All macros can be exempted by prefixing them with a backslash,\n"
"which'll be subtracted in the expansion. multi-line commands may be\n"
"done by a leading backslash, which are NOT subtracted.\n\n"
"It has five options, this message (-h, --help); prevents the execution\n"
"of the shell command (-n, --dry-run); disable color (-c, --color); list\n"
"(-l, --list) and select (-s<n>, --select <n>) which respectively lists\n"
"all " BOLD "@BAKE" RESET " commands and select & run the Nth command.\n\n"
2024-08-31 19:05:09 -04:00
"It roots the shell execution in the directory of the given file.\n"
"Expunge with @{filename...}, using (-x, --expunge), \\@{} or @{\\}}.\n\n"
"Licensed under the public domain.\n";
color_printf ("%s", help);
exit (1);
2023-09-27 01:28:54 -04:00
}