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.

343 lines
7.4KB

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