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.

369 line
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]) + 1;
  100. if (i + 1 < argc) { all[len - 1] = ' '; }
  101. }
  102. }
  103. }
  104. return (string_t) { len, all ? all : calloc(0,0) };
  105. }
  106. #if 0
  107. static string_t
  108. all_args(int argc, char ** argv) {
  109. string_t s = (string_t) { 0, NULL };
  110. if (argc > 2) {
  111. size_t i;
  112. for (i = (size_t) argc; i > 2; --i) {
  113. s.len += strlen(argv[i]) + 1;
  114. }
  115. s.buf = malloc(s.len + (size_t) argc);
  116. if (s.buf) {
  117. size_t len;
  118. s.buf[s.len - 1] = '\0';
  119. for (len = 0, i = (size_t) argc; i > 2; --i) {
  120. strcpy(s.buf + len, argv[i]);
  121. len += strlen(argv[i]) + 1;
  122. s.buf[len - 1] = ' ';
  123. }
  124. }
  125. }
  126. return s;
  127. }
  128. #endif
  129. /*** Map ***/
  130. static map_t
  131. map(char * fn) {
  132. struct stat s;
  133. int fd;
  134. map_t m = (map_t) {0};
  135. fd = open(fn, O_RDONLY);
  136. if (fd != -1) {
  137. if (!fstat(fd,&s)
  138. && s.st_mode & S_IFREG
  139. && s.st_size) {
  140. m.len = (size_t) s.st_size;
  141. m.buf = (char *) mmap(NULL, m.len, PROT_READ, MAP_SHARED, fd, 0);
  142. }
  143. close(fd);
  144. }
  145. return m;
  146. }
  147. /*** Important Functions ***/
  148. static string_t
  149. find_region(map_t m, string_t startsym, string_t stopsym) {
  150. extern char * strndup(const char * s, size_t n); /* for splint */
  151. char * buf = NULL, * start, * stop;
  152. size_t len;
  153. start = find(startsym, m.buf, m.buf + m.len);
  154. if (start) {
  155. start += startsym.len;
  156. #ifdef REQUIRE_SPACE
  157. if (!isspace(*start)) {
  158. fprintf(stderr, RED "%s" RESET ": Found start without suffix spacing.\n", g_filename.buf);
  159. return (string_t) { 0 , buf };
  160. }
  161. #endif /* REQUIRE_SPACE */
  162. stop = find(stopsym, start, start + m.len - (start - m.buf));
  163. if (!stop) {
  164. stop = start;
  165. while (*stop && *stop != '\n') {
  166. if (stop[0] == '\\'
  167. && stop[1] == '\n') { stop += 2; }
  168. ++stop;
  169. }
  170. }
  171. if (stop)
  172. {
  173. len = (size_t) (stop - m.buf) - (start - m.buf);
  174. buf = strndup(start, len);
  175. }
  176. }
  177. return (string_t) { len, buf };
  178. }
  179. static string_t
  180. file_find_region(char * fn, string_t start, string_t stop) {
  181. string_t s = { 0, NULL };
  182. map_t m = map(fn);
  183. if (m.buf) {
  184. s = find_region(m, start, stop);
  185. munmap(m.buf, m.len);
  186. }
  187. return s;
  188. }
  189. static int
  190. root(string_t s) {
  191. char x[1] = {'\0'};
  192. char * root = s.buf;
  193. size_t len = s.len;
  194. int ret;
  195. while (len && root[len] != '/')
  196. { --len; }
  197. if (!len) { return 0; }
  198. swap(root + len, x);
  199. ret = chdir(root);
  200. swap(root + len, x);
  201. /* *rootp += len + 1; */
  202. return ret;
  203. }
  204. #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
  205. static string_t
  206. expand(string_t s) {
  207. enum {
  208. MACRO_FILENAME = 0,
  209. MACRO_SHORT = 1,
  210. MACRO_ARGS = 2,
  211. };
  212. string_t macro[] = {
  213. [MACRO_FILENAME] = { 2, "$@" },
  214. [MACRO_SHORT ] = { 2, "$*" },
  215. [MACRO_ARGS ] = { 2, "$+" },
  216. };
  217. size_t i, f;
  218. {
  219. size_t max = s.len;
  220. for (i = 0; i < s.len; ++i) {
  221. for (f = 0; f < ARRLEN(macro); ++f) {
  222. if (!strncmp(s.buf + i, macro[f].buf, macro[f].len)) {
  223. max += globals[f].len;
  224. }
  225. }
  226. }
  227. s.buf = realloc(s.buf, max + 27); /* I don't know, man */
  228. if (!s.buf) { return (string_t) { 0, NULL}; }
  229. memset(s.buf + s.len, 0, max - s.len);
  230. s.len = max;
  231. }
  232. for (i = 0; i < s.len; ++i) {
  233. for (f = 0; f < ARRLEN(macro); ++f) {
  234. if (!strncmp(s.buf + i, macro[f].buf, macro[f].len)) {
  235. insert(s.buf + i, s.len - i, globals[f].buf, globals[f].len, 2);
  236. }
  237. }
  238. }
  239. return s;
  240. }
  241. /* Strips all prefixing and leading whitespace.
  242. * Except if the last character beforehand is a newline. */
  243. static size_t
  244. strip(string_t s) {
  245. size_t i = s.len;
  246. if (!i)
  247. { return 0; }
  248. while (isspace(s.buf[i]))
  249. { --i; }
  250. s.buf[i] = '\0';
  251. for (i = 0; isspace(s.buf[i]); ++i);
  252. return i - (s.buf[i - 1] == '\n');
  253. }
  254. static int
  255. run(char * buf) {
  256. int ret = 127;
  257. fputs(GREEN "output" RESET ":\n", stderr);
  258. pid_t pid = fork();
  259. if (!pid) {
  260. execl("/bin/sh", "sh", "-c", buf, NULL);
  261. } else {
  262. int status;
  263. waitpid(pid, &status, 0);
  264. if (!WIFEXITED(status)) { ret = 126; }
  265. else { ret = WEXITSTATUS(status); }
  266. }
  267. return ret;
  268. }
  269. int
  270. main(int argc, char ** argv) {
  271. int ret = 0;
  272. string_t s = { 0, NULL };
  273. if (argc < 2
  274. || streq(argv[1], "-h")
  275. || streq(argv[1], "--help"))
  276. { goto help; }
  277. g_filename = (string_t) { strlen(argv[1]), argv[1] };
  278. if (streq(argv[1], "-n")
  279. || streq(argv[1], "--dry-run")) {
  280. if (argc > 2) {
  281. ret = 1;
  282. g_filename = (string_t) { strlen(argv[2]), argv[2] };
  283. }
  284. else { goto help; }
  285. }
  286. s = file_find_region(g_filename.buf, START, STOP);
  287. if (!s.buf) {
  288. if (errno)
  289. { fprintf(stderr, BOLD RED "%s" RESET ": %s\n", g_filename.buf, strerror(errno)); }
  290. else
  291. { fprintf(stderr, BOLD RED "%s" RESET ": File unrecognized.\n", g_filename.buf); }
  292. return 1;
  293. }
  294. g_all = all_args(argc, argv);
  295. g_short = shorten(g_filename);
  296. root(g_filename);
  297. s = expand(s);
  298. free(g_short.buf); free(g_all.buf);
  299. if (!s.buf) { return 1; }
  300. fprintf(stderr, GREEN "%s" RESET ": %s\n", argv[0], s.buf + strip(s));
  301. ret = ret ? 0 : run(s.buf);
  302. if (ret)
  303. { fprintf(stderr, RED "result" RESET ": " BOLD "%d, %s\n" RESET, errno, strerror(errno)); }
  304. free(s.buf);
  305. return ret;
  306. help:
  307. fprintf(stderr, YELLOW "%s" RESET ": %s", argv[0], HELP DESC);
  308. return 1;
  309. }