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.

371 lines
8.2KB

  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=c99 -O2 $@ -o $* $+ # @STOP
  7. */
  8. #define _POSIX_C_SOURCE 200809L
  9. #include <assert.h>
  10. #include <ctype.h>
  11. #include <errno.h>
  12. #include <fcntl.h>
  13. #include <locale.h>
  14. #include <stdarg.h>
  15. #include <stddef.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <sys/mman.h>
  20. #include <sys/stat.h>
  21. #include <sys/wait.h>
  22. #include <unistd.h>
  23. #include "config.h"
  24. #define HELP \
  25. BOLD "[option] target-file" RESET " [" GREEN "arguments" RESET " ...]\n" \
  26. "Use the format `" BOLD "@BAKE" RESET " cmd ...' within the target-file, this will execute the\n" \
  27. "rest of line, or if found within the file, until the " BOLD "@STOP" RESET " marker.\n" \
  28. #define DESC \
  29. "Options [Must always be first]\n" \
  30. "\t" DIM "-h --help" RESET", " BOLD "-n --dry-run\n" RESET \
  31. "Expansions\n" \
  32. "\t" YELLOW "$@" RESET " returns target-file (abc.x.txt)\n" \
  33. "\t" YELLOW "$*" RESET " returns target-file without suffix (^-> abc.x)\n" \
  34. "\t" YELLOW "$+" RESET " returns " GREEN "arguments" RESET "\n"
  35. typedef struct {
  36. size_t len;
  37. char * buf;
  38. } string_t;
  39. typedef string_t map_t;
  40. static string_t START = { 5, "@BAKE" };
  41. static string_t STOP = { 5, "@STOP" };
  42. /*** Utility functions ***/
  43. #define strneq(a,b,n) (!strncmp(a,b,n))
  44. #define streq(a,b) (!strcmp(a,b))
  45. static void
  46. swap(char * a, char * b) {
  47. *a ^= *b;
  48. *b ^= *a;
  49. *a ^= *b;
  50. }
  51. static char *
  52. find(string_t x, char * buf, char * end) {
  53. for (; (buf < end) && x.len < (size_t)(end - buf); ++buf) {
  54. if (!strncmp(buf, x.buf, x.len))
  55. { return buf; }
  56. }
  57. return NULL;
  58. }
  59. static void
  60. insert(char * str, size_t slen, char * new, size_t len, size_t shift) {
  61. memmove(str + len, str + shift, slen - len - shift);
  62. memcpy(str, new, len);
  63. }
  64. /*** g_short, g_all Functions ***/
  65. #define GLOBALS_COUNT 3
  66. static string_t globals[GLOBALS_COUNT];
  67. #define g_filename globals[0]
  68. #define g_short globals[1]
  69. #define g_all globals[2]
  70. static string_t
  71. shorten(string_t s) {
  72. size_t i, last = 0, len = s.len;
  73. char * sh, * fn = s.buf;
  74. sh = malloc(len);
  75. if (sh) {
  76. for (i = 0; i < len; ++i) {
  77. if (fn[i] == '.') { last = i; }
  78. }
  79. last = last ? last : i;
  80. strncpy(sh, fn, last);
  81. sh[last] = '\0';
  82. }
  83. return (string_t) { last, sh ? sh : calloc(0,0) };
  84. }
  85. static string_t
  86. all_args(int argc, char ** argv) {
  87. char * all = NULL;
  88. size_t len = 0;
  89. if (argc > 2) {
  90. int i;
  91. len = (size_t) argc;
  92. for (i = 2; i < argc; ++i)
  93. { len += strlen(argv[i]); }
  94. all = malloc(len);
  95. if (all) {
  96. all[len - 1] = '\0';
  97. for (len = 0, i = 2; i < argc; ++i) {
  98. strcpy(all + len, argv[i]);
  99. len += strlen(argv[i]);
  100. len += (i + 1 < argc);
  101. if (i + 1 < argc) { all[len - 1] = ' '; }
  102. }
  103. }
  104. }
  105. return (string_t) { len, all ? all : calloc(0,0) };
  106. }
  107. #if 0
  108. static string_t
  109. all_args(int argc, char ** argv) {
  110. string_t s = (string_t) { 0, NULL };
  111. if (argc > 2) {
  112. size_t i;
  113. for (i = (size_t) argc; i > 2; --i) {
  114. s.len += strlen(argv[i]) + 1;
  115. }
  116. s.buf = malloc(s.len + (size_t) argc);
  117. if (s.buf) {
  118. size_t len;
  119. s.buf[s.len - 1] = '\0';
  120. for (len = 0, i = (size_t) argc; i > 2; --i) {
  121. strcpy(s.buf + len, argv[i]);
  122. len += strlen(argv[i]) + 1;
  123. s.buf[len - 1] = ' ';
  124. }
  125. }
  126. }
  127. return s;
  128. }
  129. #endif
  130. /*** Map ***/
  131. static map_t
  132. map(char * fn) {
  133. struct stat s;
  134. int fd;
  135. map_t m = (map_t) {0};
  136. fd = open(fn, O_RDONLY);
  137. if (fd != -1) {
  138. if (!fstat(fd,&s)
  139. && s.st_mode & S_IFREG
  140. && s.st_size) {
  141. m.len = (size_t) s.st_size;
  142. m.buf = (char *) mmap(NULL, m.len, PROT_READ, MAP_SHARED, fd, 0);
  143. }
  144. close(fd);
  145. }
  146. return m;
  147. }
  148. /*** Important Functions ***/
  149. static string_t
  150. find_region(map_t m, string_t startsym, string_t stopsym) {
  151. extern char * strndup(const char * s, size_t n); /* for splint */
  152. char * buf = NULL, * start, * stop;
  153. size_t len;
  154. start = find(startsym, m.buf, m.buf + m.len);
  155. if (start) {
  156. start += startsym.len;
  157. #ifdef REQUIRE_SPACE
  158. if (!isspace(*start)) {
  159. fprintf(stderr, RED "%s" RESET ": Found start without suffix spacing.\n", g_filename.buf);
  160. return (string_t) { 0 , buf };
  161. }
  162. #endif /* REQUIRE_SPACE */
  163. stop = find(stopsym, start, start + m.len - (start - m.buf));
  164. if (!stop) {
  165. stop = start;
  166. while (*stop && *stop != '\n') {
  167. if (stop[0] == '\\'
  168. && stop[1] == '\n') { stop += 2; }
  169. ++stop;
  170. }
  171. }
  172. if (stop)
  173. {
  174. len = (size_t) (stop - m.buf) - (start - m.buf);
  175. buf = strndup(start, len);
  176. }
  177. }
  178. return (string_t) { len, buf };
  179. }
  180. static string_t
  181. file_find_region(char * fn, string_t start, string_t stop) {
  182. string_t s = { 0, NULL };
  183. map_t m = map(fn);
  184. if (m.buf) {
  185. s = find_region(m, start, stop);
  186. munmap(m.buf, m.len);
  187. }
  188. return s;
  189. }
  190. static int
  191. root(string_t s) {
  192. char x[1] = {'\0'};
  193. char * root = s.buf;
  194. size_t len = s.len;
  195. int ret;
  196. while (len && root[len] != '/')
  197. { --len; }
  198. if (!len) { return 0; }
  199. swap(root + len, x);
  200. ret = chdir(root);
  201. swap(root + len, x);
  202. /* *rootp += len + 1; */
  203. return ret;
  204. }
  205. #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
  206. static string_t
  207. expand(string_t s) {
  208. enum {
  209. MACRO_FILENAME = 0,
  210. MACRO_SHORT = 1,
  211. MACRO_ARGS = 2,
  212. };
  213. string_t macro[] = {
  214. [MACRO_FILENAME] = { 2, "$@" },
  215. [MACRO_SHORT ] = { 2, "$*" },
  216. [MACRO_ARGS ] = { 2, "$+" },
  217. };
  218. size_t i, f;
  219. {
  220. size_t max = s.len;
  221. for (i = 0; i < s.len; ++i) {
  222. for (f = 0; f < ARRLEN(macro); ++f) {
  223. if (!strncmp(s.buf + i, macro[f].buf, macro[f].len)) {
  224. max += globals[f].len;
  225. }
  226. }
  227. }
  228. s.buf = realloc(s.buf, max + 27); /* I don't know, man */
  229. if (!s.buf) { return (string_t) { 0, NULL}; }
  230. memset(s.buf + s.len, 0, max - s.len);
  231. s.len = max;
  232. }
  233. for (i = 0; i < s.len; ++i) {
  234. for (f = 0; f < ARRLEN(macro); ++f) {
  235. if (!strncmp(s.buf + i, macro[f].buf, macro[f].len)) {
  236. insert(s.buf + i, s.len - i, globals[f].buf, globals[f].len, 2);
  237. }
  238. }
  239. }
  240. return s;
  241. }
  242. /* Strips all prefixing and leading whitespace.
  243. * Except if the last character beforehand is a newline. */
  244. static size_t
  245. strip(string_t s) {
  246. size_t i = s.len;
  247. if (!i)
  248. { return 0; }
  249. while (isspace(s.buf[i]))
  250. { --i; }
  251. s.buf[i] = '\0';
  252. for (i = 0; isspace(s.buf[i]); ++i);
  253. return i - (s.buf[i - 1] == '\n');
  254. }
  255. static int
  256. run(char * buf) {
  257. int ret = 127;
  258. fputs(GREEN "output" RESET ":\n", stderr);
  259. pid_t pid = fork();
  260. if (!pid) {
  261. execl("/bin/sh", "sh", "-c", buf, NULL);
  262. } else {
  263. int status;
  264. waitpid(pid, &status, 0);
  265. if (!WIFEXITED(status)) { ret = 126; }
  266. else { ret = WEXITSTATUS(status); }
  267. }
  268. return ret;
  269. }
  270. int
  271. main(int argc, char ** argv) {
  272. int ret = 0;
  273. string_t s = { 0, NULL };
  274. if (argc < 2
  275. || streq(argv[1], "-h")
  276. || streq(argv[1], "--help"))
  277. { goto help; }
  278. g_filename = (string_t) { strlen(argv[1]), argv[1] };
  279. if (streq(argv[1], "-n")
  280. || streq(argv[1], "--dry-run")) {
  281. if (argc > 2) {
  282. ret = 1;
  283. g_filename = (string_t) { strlen(argv[2]), argv[2] };
  284. }
  285. else { goto help; }
  286. }
  287. s = file_find_region(g_filename.buf, START, STOP);
  288. if (!s.buf) {
  289. if (errno)
  290. { fprintf(stderr, BOLD RED "%s" RESET ": %s\n", g_filename.buf, strerror(errno)); }
  291. else
  292. { fprintf(stderr, BOLD RED "%s" RESET ": File unrecognized.\n", g_filename.buf); }
  293. return 1;
  294. }
  295. g_all = all_args(argc, argv);
  296. g_short = shorten(g_filename);
  297. root(g_filename);
  298. s = expand(s);
  299. free(g_short.buf); free(g_all.buf);
  300. if (!s.buf) { return 1; }
  301. fprintf(stderr, GREEN "%s" RESET ": %s\n", argv[0], s.buf + strip(s));
  302. ret = ret ? 0 : run(s.buf);
  303. if (ret)
  304. { fprintf(stderr, RED "result" RESET ": " BOLD "%d\n" RESET, ret); }
  305. free(s.buf);
  306. return ret;
  307. help:
  308. fprintf(stderr, YELLOW "%s" RESET ": %s", argv[0], HELP DESC);
  309. return 1;
  310. }