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.

333 line
6.9KB

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