Ever burned a cake?
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

347 satır
7.8KB

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