Ever burned a cake?
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

392 рядки
8.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=c89 -O2 @FILENAME -o @SHORT @ARGS @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 <limits.h>
  24. #include "config.h"
  25. #define START "@BAKE"
  26. #define STOP "@STOP"
  27. #define VERSION "20240302"
  28. #define HELP \
  29. BOLD "[option] target-file" RESET " [" GREEN "arguments" RESET " ...]\n" \
  30. "Use the format `" BOLD "@BAKE" RESET " cmd ...' within the target-file, this will execute the\n" \
  31. "rest of line, or if found within the file, until the " BOLD "@STOP" RESET " marker.\n" \
  32. #define DESC \
  33. "Options [Must always be first]\n" \
  34. "\t" DIM "-h --help" RESET", " BOLD "-n --dry-run\n" RESET \
  35. "Expansions\n" \
  36. "\t" YELLOW "@FILENAME" RESET " returns target-file (abc.x.txt)\n" \
  37. "\t" YELLOW "@SHORT " RESET " returns target-file without suffix (^-> abc.x)\n" \
  38. "\t" YELLOW "@ARGS " RESET " returns " GREEN "arguments" RESET "\n"
  39. #define FILENAME_LIMIT (FILENAME_MAX)
  40. #define BAKE_ERROR 127
  41. enum {
  42. BAKE_UNRECOGNIZED,
  43. BAKE_MISSING_SUFFIX,
  44. };
  45. #define ARRLEN(a) (sizeof(a) / sizeof(a[0]))
  46. #if INCLUDE_AUTONOMOUS_COMPILE
  47. __attribute__((__section__(".text"))) static char autonomous_compile[] = "@BAKE cc -std=c89 -O2 $@.c -o $@ $+ @STOP";
  48. #endif
  49. static int bake_errno;
  50. typedef struct {
  51. char * buf;
  52. size_t len;
  53. } map_t;
  54. /*** root ***/
  55. static void
  56. swap(char * a, char * b) {
  57. *a ^= *b;
  58. *b ^= *a;
  59. *a ^= *b;
  60. }
  61. static int
  62. root(char ** rootp) {
  63. char x[1] = {'\0'};
  64. char * root = *rootp;
  65. size_t len = strlen(root);
  66. int ret;
  67. while (len && root[len] != '/')
  68. { --len; }
  69. if (!len) { return 0; }
  70. swap(root + len, x);
  71. ret = chdir(root);
  72. swap(root + len, x);
  73. *rootp += len + 1;
  74. return ret;
  75. }
  76. /*** find region in file ***/
  77. static map_t
  78. map(char * fn) {
  79. struct stat s;
  80. int fd;
  81. map_t m = (map_t) {0};
  82. fd = open(fn, O_RDONLY);
  83. if (fd != -1) {
  84. if (!fstat(fd,&s)
  85. && s.st_mode & S_IFREG
  86. && s.st_size) {
  87. m.len = (size_t) s.st_size;
  88. m.buf = (char *) mmap(NULL, m.len, PROT_READ, MAP_SHARED, fd, 0);
  89. }
  90. close(fd);
  91. }
  92. return m;
  93. }
  94. static char *
  95. find(char * buf, char * x, char * end) {
  96. size_t xlen = strlen(x);
  97. char * start = buf;
  98. for (; buf < end - xlen; ++buf) {
  99. if (!strncmp(buf, x, xlen)) {
  100. if (start < buf && buf[-1] == '\\') { continue; }
  101. else { return buf; }
  102. }
  103. }
  104. return NULL;
  105. }
  106. static char *
  107. find_region(map_t m, char * findstart, char * findstop) {
  108. char * buf = NULL, * start, * stop, * end = m.len + m.buf;
  109. start = find(m.buf, findstart, end);
  110. if (start) {
  111. start += strlen(findstart);
  112. #ifdef REQUIRE_SPACE
  113. if (!isspace(*start)) {
  114. bake_errno = BAKE_MISSING_SUFFIX;
  115. return NULL;
  116. }
  117. #endif /* REQUIRE_SPACE */
  118. stop = find(start, findstop, end);
  119. if (!stop) {
  120. stop = start;
  121. while (stop < end && *stop != '\n') {
  122. if (stop[0] == '\\'
  123. && stop[1] == '\n') { stop += 2; }
  124. ++stop;
  125. }
  126. }
  127. if (stop)
  128. { buf = strndup(start, (size_t) (stop - m.buf) - (start - m.buf)); }
  129. }
  130. return buf;
  131. }
  132. static char *
  133. file_find_region(char * fn, char * start, char * stop) {
  134. char * buf = NULL;
  135. map_t m = map(fn);
  136. if (m.buf) {
  137. buf = find_region(m, start, stop);
  138. munmap(m.buf, m.len);
  139. }
  140. return buf;
  141. }
  142. /*** g_short, g_all ***/
  143. static char *
  144. shorten(char * fn) {
  145. size_t i, last, len;
  146. static char sh[FILENAME_LIMIT];
  147. len = strlen(fn);
  148. for (last = i = 0; i < len; ++i) {
  149. if (fn[i] == '.') { last = i; }
  150. }
  151. last = last ? last : i;
  152. strncpy(sh, fn, last);
  153. sh[last] = '\0';
  154. return sh;
  155. }
  156. static char *
  157. all_args(size_t argc, char ** argv) {
  158. char * all = NULL;
  159. if (argc > 2) {
  160. size_t i, len = argc;
  161. for (i = 2; i < argc; ++i)
  162. { len += strlen(argv[i]); }
  163. all = malloc(len + 1);
  164. all[len] = '\0';
  165. for (len = 0, i = 2; i < argc; ++i) {
  166. strcpy(all + len, argv[i]);
  167. len += strlen(argv[i]) + 1;
  168. if (i + 1 < argc) { all[len - 1] = ' '; }
  169. }
  170. }
  171. return all ? all : calloc(1,1);
  172. }
  173. /*** insert, expand, bake_expand ***/
  174. static void
  175. insert(char * str, char * new, size_t slen, size_t nlen, size_t shift) {
  176. memmove(str + nlen, str + shift, slen + 1 - shift);
  177. memcpy(str, new, nlen);
  178. }
  179. static char *
  180. expand(char * buf, char * macro, char * with) {
  181. ssize_t i,
  182. blen = strlen(buf),
  183. mlen = strlen(macro),
  184. wlen = strlen(with),
  185. nlen;
  186. fflush(stdout);
  187. for (i = 0; i < blen - mlen + 1; ++i) {
  188. if (!strncmp(buf + i, macro, mlen)) {
  189. if (i && buf[i - 1] == '\\') {
  190. memmove(buf + i - 1, buf + i, blen - i);
  191. buf[blen - 1] = '\0';
  192. } else {
  193. nlen = wlen - mlen + 1;
  194. if (nlen > 0) {
  195. buf = realloc(buf, blen + nlen);
  196. }
  197. insert(buf + i, with, blen - i, wlen, mlen);
  198. blen += (nlen > 0) * nlen;
  199. }
  200. }
  201. }
  202. return buf;
  203. }
  204. static char *
  205. bake_expand(char * buf, char * filename, int argc, char ** argv) {
  206. enum {
  207. MACRO_FILENAME,
  208. MACRO_SHORT,
  209. MACRO_ARGS,
  210. MACRO_STOP,
  211. MACRO_NONE
  212. };
  213. char * macro[MACRO_NONE],
  214. * macro_old[MACRO_STOP];
  215. size_t i;
  216. macro[MACRO_FILENAME] = "@FILENAME";
  217. macro[MACRO_SHORT ] = "@SHORT";
  218. macro[MACRO_ARGS ] = "@ARGS";
  219. macro[MACRO_STOP ] = "@STOP";
  220. macro_old[MACRO_FILENAME] = "$@";
  221. macro_old[MACRO_SHORT ] = "$*";
  222. macro_old[MACRO_ARGS ] = "$+";
  223. char * global[4];
  224. global[MACRO_FILENAME] = filename;
  225. global[MACRO_SHORT ] = shorten(filename);
  226. global[MACRO_ARGS ] = all_args((size_t) argc, argv);
  227. global[MACRO_STOP ] = "";
  228. #if NEW_MACROS
  229. for (i = 0; i < ARRLEN(macro); ++i) {
  230. buf = expand(buf, macro[i], global[i]);
  231. }
  232. #endif
  233. #if OLD_MACROS
  234. for (i = 0; i < ARRLEN(macro_old); ++i) {
  235. buf = expand(buf, macro_old[i], global[i]);
  236. }
  237. #endif
  238. free(global[MACRO_ARGS]);
  239. return buf;
  240. }
  241. /*** strip, run ***/
  242. /* Strips all prefixing and leading whitespace.
  243. * Except if the last character beforehand is a newline. */
  244. static size_t
  245. strip(char * buf) {
  246. size_t i = strlen(buf);
  247. if (!i) { return 0; }
  248. while (i && isspace(buf[i - 1])) { --i; }
  249. buf[i] = '\0';
  250. for (i = 0; isspace(buf[i]); ++i);
  251. if (i && buf[i - 1] == '\n') { --i; }
  252. return i;
  253. }
  254. static int
  255. run(char * buf, char * argv0) {
  256. puts(BOLD GREEN "output" RESET ":\n");
  257. pid_t pid;
  258. if ((pid = fork()) == 0) {
  259. execl("/bin/sh", "sh", "-c", buf, NULL);
  260. return 0; /* execl overwrites the process anyways */
  261. } else if (pid == -1) {
  262. fprintf(stderr, BOLD RED "%s" RESET ": %s, %s\n", argv0, "Fork Error", strerror(errno));
  263. return BAKE_ERROR;
  264. } else {
  265. int status;
  266. if (waitpid(pid, &status, 0) < 0) {
  267. fprintf(stderr, BOLD RED "%s" RESET ": %s, %s\n", argv0, "Wait PID Error", strerror(errno));
  268. return BAKE_ERROR;
  269. }
  270. if (!WIFEXITED(status)) { return BAKE_ERROR; }
  271. return WEXITSTATUS(status);
  272. }
  273. }
  274. /*** main ***/
  275. int
  276. main(int argc, char ** argv) {
  277. int ret = 0;
  278. char * buf = NULL,
  279. * filename,
  280. * argv0;
  281. argv0 = argv[0];
  282. if (argc < 2
  283. || !strcmp(argv[1], "-h")
  284. || !strcmp(argv[1], "--help"))
  285. { goto help; }
  286. if (!strcmp(argv[1], "-v")
  287. || !strcmp(argv[1], "--version"))
  288. { goto version; }
  289. if (!strcmp(argv[1], "-n")
  290. || !strcmp(argv[1], "--dry-run")) {
  291. if (argc > 2) {
  292. ret = 1;
  293. --argc;
  294. ++argv;
  295. }
  296. else { goto help; }
  297. }
  298. filename = argv[1];
  299. if (strlen(filename) > FILENAME_LIMIT) {
  300. fprintf(stderr, BOLD RED "%s" RESET ": Filename too long (exceeds %d)\n", argv0, FILENAME_LIMIT);
  301. return BAKE_ERROR;
  302. }
  303. root(&filename);
  304. buf = file_find_region(filename, START, STOP);
  305. char * error[2];
  306. error[0] = "File Unrecognized";
  307. error[1] = "Found start without suffix spacing";
  308. if (!buf) {
  309. fprintf(stderr, BOLD RED "%s" RESET ": '" BOLD "%s" RESET "' %s.\n",
  310. argv0, filename, errno ? strerror(errno) : error[bake_errno]);
  311. return BAKE_ERROR;
  312. }
  313. buf = bake_expand(buf, filename, argc, argv);
  314. printf(BOLD GREEN "%s" RESET ": %s\n", argv0, buf + strip(buf));
  315. ret = ret ? 0 : run(buf,argv0);
  316. if (ret)
  317. { printf(BOLD RED "result" RESET ": " BOLD "%d\n" RESET, ret); }
  318. free(buf);
  319. return ret;
  320. help:
  321. fprintf(stderr, YELLOW "%s" RESET ": %s", argv0, HELP DESC);
  322. return BAKE_ERROR;
  323. version:
  324. fprintf(stderr, YELLOW "%s" RESET ": v%s\n", argv0, VERSION);
  325. return BAKE_ERROR;
  326. }