Ever burned a cake?
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

623 lines
13KB

  1. /* bake.c - Ever burned a cake?
  2. * Copyright 2023 Emil Williams
  3. *
  4. * Licensed under the GNU Public License version 3 only, see LICENSE.
  5. *
  6. * @BAKE cc -std=c89 -O2 @FILENAME -o @{@SHORT} @ARGS @STOP
  7. */
  8. #define _GNU_SOURCE
  9. #define _POSIX_C_SOURCE 200809L
  10. #include <assert.h>
  11. #include <ctype.h>
  12. #include <errno.h>
  13. #include <fcntl.h>
  14. #include <locale.h>
  15. #include <stdarg.h>
  16. #include <stddef.h>
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #include <sys/mman.h>
  21. #include <sys/stat.h>
  22. #include <sys/wait.h>
  23. #include <unistd.h>
  24. #include <limits.h>
  25. #include "config.h"
  26. #define VERSION "20240413"
  27. #define HELP \
  28. BOLD "[option] target-file" RESET " [" GREEN "arguments" RESET " ...]\n" \
  29. "Use the format `" BOLD "@BAKE" RESET " cmd ...' within the target-file, this will execute the\n" \
  30. "rest of line, or if found within the file, until the " BOLD "@STOP" RESET " marker.\n"
  31. #define DESC \
  32. "Options [Must always be put first, may be merged together]\n" \
  33. "\t" DIM "-v --version" RESET ", " DIM "-h --help" RESET ", " \
  34. BOLD "-n --dry-run" RESET ", " BOLD "-x --expunge\n" RESET ", " \
  35. BOLD "-c --color" RESET \
  36. "Expansions\n" \
  37. "\t" YELLOW "@FILENAME" RESET " returns target-file (abc.x.txt)\n" \
  38. "\t" YELLOW "@SHORT " RESET " returns target-file without suffix (^-> abc.x)\n" \
  39. "\t" YELLOW "@ARGS " RESET " returns " GREEN "arguments" RESET "\n" \
  40. "Additional Features And Notes\n" \
  41. "\t" YELLOW "@{" RESET BOLD "EXPUNGE_THIS_FILE" YELLOW "}" RESET \
  42. " inline region to delete this or many files or directories,\n" \
  43. "\tnon-recursive, only one file per block, removed from left to right. This has no\n" \
  44. "\tinfluence on the normal command execution.\n" \
  45. "\t" YELLOW "\\" RESET \
  46. "SPECIAL_NAME will result in SPECIAL_NAME in the executed shell command.\n" \
  47. "Backslashing is applicable to all meaningful symbols in Bake, it is ignored otherwise."
  48. #define COPYRIGHT "2023 Emil Williams"
  49. #define LICENSE "Licensed under the GNU Public License version 3 only, see LICENSE."
  50. #define FILENAME_LIMIT (FILENAME_MAX)
  51. #define BAKE_ERROR 127
  52. enum {
  53. BAKE_UNRECOGNIZED,
  54. BAKE_MISSING_SUFFIX
  55. };
  56. enum {
  57. BAKE_RUN = 0,
  58. BAKE_NORUN = (1 << 0),
  59. BAKE_EXPUNGE = (1 << 1)
  60. };
  61. #define ARRLEN(a) (sizeof(a) / sizeof(a[0]))
  62. #if INCLUDE_AUTONOMOUS_COMPILE
  63. __attribute__((__section__(".text"))) static char autonomous_compile[] =
  64. "@BAKE cc -std=c89 -O2 $@.c -o $@ $+ @STOP";
  65. #endif
  66. static int bake_errno;
  67. typedef struct {
  68. size_t len;
  69. char * buf;
  70. } string_t;
  71. typedef string_t map_t;
  72. /*** nocolor printf ***/
  73. #if ENABLE_COLOR
  74. # define color_printf(...) color_fprintf(stdout, __VA_ARGS__)
  75. /* not perfect, too simple, doesn't work with a var, only a literal. */
  76. # define color_puts(msg) color_fprintf(stdout, msg "\n")
  77. int color = ENABLE_COLOR;
  78. static char * expand(char * buf, char * macro, char * with);
  79. color_fprintf(FILE * fp, char * format, ...) {
  80. va_list ap;
  81. char * buf;
  82. va_start(ap, format);
  83. if (!color) {
  84. vasprintf(&buf, format, ap);
  85. if (buf) {
  86. expand(buf, RED, "");
  87. expand(buf, GREEN, "");
  88. expand(buf, YELLOW, "");
  89. expand(buf, DIM, "");
  90. expand(buf, BOLD, "");
  91. expand(buf, RESET, "");
  92. fwrite(buf, strlen(buf), 1, fp);
  93. }
  94. free(buf);
  95. } else {
  96. vfprintf(fp, format, ap);
  97. }
  98. va_end(ap);
  99. }
  100. #else
  101. # define color_printf(...) fprintf(stdout, __VA_ARGS__)
  102. # define color_puts(msg) puts(msg)
  103. #endif
  104. /*** root ***/
  105. static void
  106. swap(char * a, char * b) {
  107. *a ^= *b;
  108. *b ^= *a;
  109. *a ^= *b;
  110. }
  111. static int
  112. root(char ** rootp) {
  113. char x[1] = {'\0'};
  114. char * root = *rootp;
  115. size_t len = strlen(root);
  116. int ret;
  117. while (len && root[len] != '/') {
  118. --len;
  119. }
  120. if (!len) {
  121. return 0;
  122. }
  123. swap(root + len, x);
  124. ret = chdir(root);
  125. swap(root + len, x);
  126. *rootp += len + 1;
  127. return ret;
  128. }
  129. /*** find region in file ***/
  130. static map_t
  131. map(char * fn) {
  132. struct stat s;
  133. int fd;
  134. map_t m;
  135. m.buf = NULL;
  136. m.len = 0;
  137. fd = open(fn, O_RDONLY);
  138. if (fd != -1) {
  139. if (!fstat(fd, &s)
  140. && s.st_mode & S_IFREG
  141. && s.st_size) {
  142. m.len = (size_t) s.st_size;
  143. m.buf = (char *) mmap(NULL, m.len, PROT_READ, MAP_SHARED, fd, 0);
  144. }
  145. close(fd);
  146. }
  147. return m;
  148. }
  149. static char *
  150. find(char * buf, char * x, char * end) {
  151. size_t xlen = strlen(x);
  152. char * start = buf;
  153. for (; buf <= end - xlen; ++buf) {
  154. if (!strncmp(buf, x, xlen)) {
  155. if (start < buf && buf[-1] == '\\') {
  156. continue;
  157. } else {
  158. return buf;
  159. }
  160. }
  161. }
  162. return NULL;
  163. }
  164. static char *
  165. get_region(string_t m, char * findstart, char * findstop) {
  166. char * buf = NULL, * start, * stop, * end = m.len + m.buf;
  167. start = find(m.buf, findstart, end);
  168. if (start) {
  169. start += strlen(findstart);
  170. #ifdef REQUIRE_SPACE
  171. if (!isspace(*start)) {
  172. bake_errno = BAKE_MISSING_SUFFIX;
  173. return NULL;
  174. }
  175. #endif /* REQUIRE_SPACE */
  176. stop = find(start, findstop, end);
  177. if (!stop) {
  178. stop = start;
  179. while (stop < end && *stop != '\n') {
  180. if (stop[0] == '\\'
  181. && stop[1] == '\n') {
  182. stop += 2;
  183. }
  184. ++stop;
  185. }
  186. }
  187. buf = strndup(start, (size_t)(stop - start));
  188. }
  189. return buf;
  190. }
  191. static char *
  192. file_get_region(char * fn, char * start, char * stop) {
  193. map_t m = map(fn);
  194. char * buf = NULL;
  195. if (m.buf) {
  196. buf = get_region(m, start, stop);
  197. munmap(m.buf, m.len);
  198. }
  199. return buf;
  200. }
  201. /*** g_short, g_all ***/
  202. static char *
  203. shorten(char * fn) {
  204. size_t i, last, len;
  205. static char sh[FILENAME_LIMIT];
  206. len = strlen(fn);
  207. for (last = i = 0; i < len; ++i) {
  208. if (fn[i] == '.') {
  209. last = i;
  210. }
  211. }
  212. last = last ? last : i;
  213. strncpy(sh, fn, last);
  214. sh[last] = '\0';
  215. return sh;
  216. }
  217. static char *
  218. all_args(size_t argc, char ** argv) {
  219. char * all = NULL;
  220. if (argc > 0) {
  221. size_t i, len = argc;
  222. for (i = 0; i < argc; ++i) {
  223. len += strlen(argv[i]);
  224. }
  225. all = malloc(len + 1);
  226. all[len] = '\0';
  227. for (len = 0, i = 0; i < argc; ++i) {
  228. strcpy(all + len, argv[i]);
  229. len += strlen(argv[i]) + 1;
  230. if (i + 1 < argc) {
  231. all[len - 1] = ' ';
  232. }
  233. }
  234. }
  235. return all ? all : calloc(1, 1);
  236. }
  237. /*** insert, expand, bake_expand ***/
  238. static void
  239. insert(char * str, char * new, size_t slen, size_t nlen, size_t shift) {
  240. memmove(str + nlen, str + shift, slen + 1 - shift);
  241. memcpy(str, new, nlen);
  242. }
  243. static char *
  244. expand(char * buf, char * macro, char * with) {
  245. ssize_t i,
  246. blen = strlen(buf),
  247. mlen = strlen(macro),
  248. wlen = strlen(with),
  249. nlen;
  250. fflush(stdout);
  251. for (i = 0; i < blen - mlen + 1; ++i) {
  252. if (!strncmp(buf + i, macro, mlen)) {
  253. if (i && buf[i - 1] == '\\') {
  254. memmove(buf + i - 1, buf + i, blen - i);
  255. buf[blen - 1] = '\0';
  256. } else {
  257. nlen = wlen - mlen + 1;
  258. if (nlen > 0) {
  259. buf = realloc(buf, blen + nlen);
  260. }
  261. insert(buf + i, with, blen - i, wlen, mlen);
  262. blen += (nlen > 0) * nlen;
  263. }
  264. }
  265. }
  266. return buf;
  267. }
  268. static char *
  269. bake_expand(char * buf, char * filename, int argc, char ** argv) {
  270. enum {
  271. MACRO_FILENAME,
  272. MACRO_SHORT,
  273. MACRO_ARGS,
  274. MACRO_STOP,
  275. MACRO_NONE
  276. };
  277. char * macro[MACRO_NONE],
  278. * macro_old[MACRO_STOP],
  279. * global[MACRO_NONE];
  280. size_t i;
  281. macro[MACRO_FILENAME] = "@FILENAME";
  282. macro[MACRO_SHORT ] = "@SHORT";
  283. macro[MACRO_ARGS ] = "@ARGS";
  284. macro[MACRO_STOP ] = "@STOP";
  285. macro_old[MACRO_FILENAME] = "$@";
  286. macro_old[MACRO_SHORT ] = "$*";
  287. macro_old[MACRO_ARGS ] = "$+";
  288. global[MACRO_FILENAME] = filename;
  289. global[MACRO_SHORT ] = shorten(filename);
  290. global[MACRO_ARGS ] = all_args((size_t) argc, argv);
  291. global[MACRO_STOP ] = "";
  292. #if NEW_MACROS
  293. for (i = 0; i < ARRLEN(macro); ++i) {
  294. buf = expand(buf, macro[i], global[i]);
  295. }
  296. #endif
  297. #if OLD_MACROS
  298. for (i = 0; i < ARRLEN(macro_old); ++i) {
  299. buf = expand(buf, macro_old[i], global[i]);
  300. }
  301. #endif
  302. free(global[MACRO_ARGS]);
  303. return buf;
  304. }
  305. static void
  306. remove_expand(char * buf, char * argv0, int rm, char * start, char * stop) {
  307. size_t i, f, end = strlen(buf);
  308. size_t startlen = strlen(start), stoplen = strlen(stop);
  309. char x[1] = {'\0'};
  310. for (i = 0; i < end; ++i) {
  311. if (!strncmp(buf + i, start, startlen)) {
  312. if (buf + i > buf && buf[i - 1] == '\\') {
  313. continue;
  314. }
  315. for (f = i; f < end; ++f) {
  316. if (!strncmp(buf + f, stop, stoplen)) {
  317. if (buf + f > buf && buf[f - 1] == '\\') {
  318. continue;
  319. }
  320. insert(buf + f, "", end - f, 0, 1);
  321. i += startlen;
  322. if (rm & BAKE_EXPUNGE) {
  323. swap(buf + i + (f - i), x);
  324. #if !ENABLE_EXPUNGE_REMOVE
  325. color_printf("%s: %sremoving '%s'\n",
  326. argv0, rm & BAKE_NORUN ? "not " : "", buf + i);
  327. if (!(rm & BAKE_NORUN)) {
  328. remove(buf + i);
  329. }
  330. #else
  331. color_printf("%s: not removing '%s'\n", argv0, buf + i);
  332. #endif
  333. swap(buf + i + (f - i), x);
  334. }
  335. goto next;
  336. }
  337. }
  338. goto stop;
  339. }
  340. next:;
  341. }
  342. stop:
  343. expand(buf, start, "");
  344. }
  345. /*** strip, run ***/
  346. /* Strips all prefixing and leading whitespace.
  347. * Except if the last character beforehand is a newline. */
  348. static size_t
  349. strip(char * buf) {
  350. size_t i = strlen(buf);
  351. if (!i) {
  352. return 0;
  353. }
  354. while (i && isspace(buf[i - 1])) {
  355. --i;
  356. }
  357. buf[i] = '\0';
  358. for (i = 0; isspace(buf[i]); ++i);
  359. if (i && buf[i - 1] == '\n') {
  360. --i;
  361. }
  362. return i;
  363. }
  364. static int
  365. run(char * buf, char * argv0) {
  366. pid_t pid;
  367. color_puts(BOLD GREEN "output" RESET ":\n");
  368. if ((pid = fork()) == 0) {
  369. execl("/bin/sh", "sh", "-c", buf, NULL);
  370. return 0; /* execl overwrites the process anyways */
  371. } else if (pid == -1) {
  372. color_fprintf(stderr, BOLD RED "%s" RESET ": %s, %s\n",
  373. argv0, "Fork Error", strerror(errno));
  374. return BAKE_ERROR;
  375. } else {
  376. int status;
  377. if (waitpid(pid, &status, 0) < 0) {
  378. color_fprintf(stderr, BOLD RED "%s" RESET ": %s, %s\n",
  379. argv0, "Wait PID Error", strerror(errno));
  380. return BAKE_ERROR;
  381. }
  382. if (!WIFEXITED(status)) {
  383. return BAKE_ERROR;
  384. }
  385. return WEXITSTATUS(status);
  386. }
  387. }
  388. /*** main ***/
  389. int
  390. main(int argc, char ** argv) {
  391. int ret = BAKE_RUN;
  392. char * buf = NULL,
  393. * filename,
  394. * argv0;
  395. argv0 = argv[0];
  396. if (argc < 2) {
  397. goto help;
  398. }
  399. while (++argv, --argc && argv[0][0] == '-') {
  400. ++argv[0];
  401. if (argv[0][1] == '-') {
  402. ++argv[0];
  403. if (!strcmp(argv[0], "help")) {
  404. goto help;
  405. } else if (!strcmp(argv[0], "version")) {
  406. goto version;
  407. } else if (!strcmp(argv[0], "expunge")) {
  408. ret |= BAKE_EXPUNGE;
  409. } else if (!strcmp(argv[0], "dry-run")) {
  410. ret |= BAKE_NORUN;
  411. } else if (!strcmp(argv[0], "color")) {
  412. #if ENABLE_COLOR
  413. color = 0;
  414. #endif
  415. } else {
  416. goto help;
  417. }
  418. } else do {
  419. switch (argv[0][0]) {
  420. case 'h':
  421. goto help;
  422. case 'v':
  423. goto version;
  424. case 'x':
  425. ret |= BAKE_EXPUNGE;
  426. break;
  427. case 'n':
  428. ret |= BAKE_NORUN;
  429. break;
  430. #if ENABLE_COLOR
  431. case 'c':
  432. color = 0;
  433. break;
  434. #endif
  435. case 0 :
  436. goto next;
  437. default :
  438. color_puts("UNKNOWN");
  439. goto help;
  440. }
  441. } while (++(argv[0]));
  442. next:;
  443. }
  444. filename = argv[0];
  445. ++argv, --argc;
  446. if (strlen(filename) > FILENAME_LIMIT) {
  447. color_fprintf(stderr, BOLD RED "%s" RESET
  448. ": Filename too long (exceeds %d)\n",
  449. argv0,
  450. FILENAME_LIMIT);
  451. return BAKE_ERROR;
  452. }
  453. root(&filename);
  454. buf = file_get_region(filename, START, STOP);
  455. if (!buf) {
  456. char * error[2];
  457. error[0] = "File Unrecognized";
  458. error[1] = "Found start without suffix spacing";
  459. color_fprintf(stderr, BOLD RED "%s" RESET ": '" BOLD "%s" RESET "' %s.\n",
  460. argv0, filename, errno ? strerror(errno) : error[bake_errno]);
  461. return BAKE_ERROR;
  462. }
  463. buf = bake_expand(buf, filename, argc, argv);
  464. color_printf(BOLD GREEN "%s" RESET ": %s\n", argv0, buf + strip(buf));
  465. remove_expand(buf, argv0, ret, EXPUNGE_START, EXPUNGE_STOP);
  466. if (!ret) {
  467. ret = run(buf, argv0);
  468. if (ret) {
  469. color_printf(BOLD RED "result" RESET ": " BOLD "%d\n" RESET, ret);
  470. }
  471. } else { ret = 0; }
  472. free(buf);
  473. return ret;
  474. help:
  475. color_fprintf(stderr, YELLOW "%s" RESET ": %s\n", argv0, HELP DESC);
  476. return BAKE_ERROR;
  477. version:
  478. color_fprintf(stderr,
  479. YELLOW "%s" RESET ": v" VERSION "\n"
  480. "Copyright " COPYRIGHT "\n" LICENSE "\n",
  481. argv0);
  482. return BAKE_ERROR;
  483. }