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.

329 lines
7.0KB

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