Ever burned a cake?
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

570 行
12KB

  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 EXPUNGE_START "@{"
  28. #define EXPUNGE_STOP "}"
  29. #define VERSION "20240408"
  30. #define HELP \
  31. BOLD "[option] target-file" RESET " [" GREEN "arguments" RESET " ...]\n" \
  32. "Use the format `" BOLD "@BAKE" RESET " cmd ...' within the target-file, this will execute the\n" \
  33. "rest of line, or if found within the file, until the " BOLD "@STOP" RESET " marker.\n" \
  34. #define DESC \
  35. "Options [Must always be put first, may be merged together]\n" \
  36. "\t" DIM "-v --version" RESET ", " DIM "-h --help" RESET ", " \
  37. BOLD "-n --dry-run" RESET ", " BOLD "-x --expunge\n" RESET \
  38. "Expansions\n" \
  39. "\t" YELLOW "@FILENAME" RESET " returns target-file (abc.x.txt)\n" \
  40. "\t" YELLOW "@SHORT " RESET " returns target-file without suffix (^-> abc.x)\n" \
  41. "\t" YELLOW "@ARGS " RESET " returns " GREEN "arguments" RESET "\n" \
  42. "Additional Features And Notes\n" \
  43. "\t" YELLOW "@{" RESET BOLD "EXPUNGE_THIS_FILE" YELLOW "}" RESET \
  44. " inline region to delete this or many files or directories,\n" \
  45. "\tnon-recursive, only one file per block, removed from left to right. This has no\n" \
  46. "\tinfluence on the normal command execution.\n" \
  47. "\t" YELLOW "\\" RESET \
  48. "SPECIAL_NAME will result in SPECIAL_NAME in the executed shell command.\n" \
  49. "Backslashing is applicable to all meaningful symbols in Bake, it is ignored otherwise."
  50. #define COPYRIGHT "2023 Emil Williams"
  51. #define LICENSE "Licensed under the GNU Public License version 3 only, see LICENSE."
  52. #define FILENAME_LIMIT (FILENAME_MAX)
  53. #define BAKE_ERROR 127
  54. enum {
  55. BAKE_UNRECOGNIZED,
  56. BAKE_MISSING_SUFFIX,
  57. };
  58. enum {
  59. BAKE_RUN = 0,
  60. BAKE_NORUN = (1 << 0),
  61. BAKE_EXPUNGE = (1 << 1),
  62. };
  63. #define ARRLEN(a) (sizeof(a) / sizeof(a[0]))
  64. #if INCLUDE_AUTONOMOUS_COMPILE
  65. __attribute__((__section__(".text"))) static char autonomous_compile[] =
  66. "@BAKE cc -std=c89 -O2 $@.c -o $@ $+ @STOP";
  67. #endif
  68. static int bake_errno;
  69. typedef struct {
  70. size_t len;
  71. char * buf;
  72. } string_t;
  73. typedef string_t map_t;
  74. /*** root ***/
  75. static void
  76. swap(char * a, char * b) {
  77. *a ^= *b;
  78. *b ^= *a;
  79. *a ^= *b;
  80. }
  81. static int
  82. root(char ** rootp) {
  83. char x[1] = {'\0'};
  84. char * root = *rootp;
  85. size_t len = strlen(root);
  86. int ret;
  87. while (len && root[len] != '/') {
  88. --len;
  89. }
  90. if (!len) {
  91. return 0;
  92. }
  93. swap(root + len, x);
  94. ret = chdir(root);
  95. swap(root + len, x);
  96. *rootp += len + 1;
  97. return ret;
  98. }
  99. /*** find region in file ***/
  100. static map_t
  101. map(char * fn) {
  102. struct stat s;
  103. int fd;
  104. map_t m = (map_t) {
  105. 0
  106. };
  107. fd = open(fn, O_RDONLY);
  108. if (fd != -1) {
  109. if (!fstat(fd, &s)
  110. && s.st_mode & S_IFREG
  111. && s.st_size) {
  112. m.len = (size_t) s.st_size;
  113. m.buf = (char *) mmap(NULL, m.len, PROT_READ, MAP_SHARED, fd, 0);
  114. }
  115. close(fd);
  116. }
  117. return m;
  118. }
  119. static char *
  120. find(char * buf, char * x, char * end) {
  121. size_t xlen = strlen(x);
  122. char * start = buf;
  123. for (; buf <= end - xlen; ++buf) {
  124. if (!strncmp(buf, x, xlen)) {
  125. if (start < buf && buf[-1] == '\\') {
  126. continue;
  127. } else {
  128. return buf;
  129. }
  130. }
  131. }
  132. return NULL;
  133. }
  134. static char *
  135. get_region(string_t m, char * findstart, char * findstop) {
  136. char * buf = NULL, * start, * stop, * end = m.len + m.buf;
  137. start = find(m.buf, findstart, end);
  138. if (start) {
  139. start += strlen(findstart);
  140. #ifdef REQUIRE_SPACE
  141. if (!isspace(*start)) {
  142. bake_errno = BAKE_MISSING_SUFFIX;
  143. return NULL;
  144. }
  145. #endif /* REQUIRE_SPACE */
  146. stop = find(start, findstop, end);
  147. if (!stop) {
  148. stop = start;
  149. while (stop < end && *stop != '\n') {
  150. if (stop[0] == '\\'
  151. && stop[1] == '\n') {
  152. stop += 2;
  153. }
  154. ++stop;
  155. }
  156. }
  157. buf = strndup(start, (size_t)(stop - start));
  158. }
  159. return buf;
  160. }
  161. static char *
  162. file_get_region(char * fn, char * start, char * stop) {
  163. map_t m = map(fn);
  164. char * buf = NULL;
  165. if (m.buf) {
  166. buf = get_region(m, start, stop);
  167. munmap(m.buf, m.len);
  168. }
  169. return buf;
  170. }
  171. /*** g_short, g_all ***/
  172. static char *
  173. shorten(char * fn) {
  174. size_t i, last, len;
  175. static char sh[FILENAME_LIMIT];
  176. len = strlen(fn);
  177. for (last = i = 0; i < len; ++i) {
  178. if (fn[i] == '.') {
  179. last = i;
  180. }
  181. }
  182. last = last ? last : i;
  183. strncpy(sh, fn, last);
  184. sh[last] = '\0';
  185. return sh;
  186. }
  187. static char *
  188. all_args(size_t argc, char ** argv) {
  189. char * all = NULL;
  190. if (argc > 0) {
  191. size_t i, len = argc;
  192. for (i = 0; i < argc; ++i) {
  193. len += strlen(argv[i]);
  194. }
  195. all = malloc(len + 1);
  196. all[len] = '\0';
  197. for (len = 0, i = 0; i < argc; ++i) {
  198. strcpy(all + len, argv[i]);
  199. len += strlen(argv[i]) + 1;
  200. if (i + 1 < argc) {
  201. all[len - 1] = ' ';
  202. }
  203. }
  204. }
  205. return all ? all : calloc(1, 1);
  206. }
  207. /*** insert, expand, bake_expand ***/
  208. static void
  209. insert(char * str, char * new, size_t slen, size_t nlen, size_t shift) {
  210. memmove(str + nlen, str + shift, slen + 1 - shift);
  211. memcpy(str, new, nlen);
  212. }
  213. static char *
  214. expand(char * buf, char * macro, char * with) {
  215. ssize_t i,
  216. blen = strlen(buf),
  217. mlen = strlen(macro),
  218. wlen = strlen(with),
  219. nlen;
  220. fflush(stdout);
  221. for (i = 0; i < blen - mlen + 1; ++i) {
  222. if (!strncmp(buf + i, macro, mlen)) {
  223. if (i && buf[i - 1] == '\\') {
  224. memmove(buf + i - 1, buf + i, blen - i);
  225. buf[blen - 1] = '\0';
  226. } else {
  227. nlen = wlen - mlen + 1;
  228. if (nlen > 0) {
  229. buf = realloc(buf, blen + nlen);
  230. }
  231. insert(buf + i, with, blen - i, wlen, mlen);
  232. blen += (nlen > 0) * nlen;
  233. }
  234. }
  235. }
  236. return buf;
  237. }
  238. static char *
  239. bake_expand(char * buf, char * filename, int argc, char ** argv) {
  240. enum {
  241. MACRO_FILENAME,
  242. MACRO_SHORT,
  243. MACRO_ARGS,
  244. MACRO_STOP,
  245. MACRO_NONE
  246. };
  247. char * macro[MACRO_NONE],
  248. * macro_old[MACRO_STOP];
  249. size_t i;
  250. macro[MACRO_FILENAME] = "@FILENAME";
  251. macro[MACRO_SHORT ] = "@SHORT";
  252. macro[MACRO_ARGS ] = "@ARGS";
  253. macro[MACRO_STOP ] = "@STOP";
  254. macro_old[MACRO_FILENAME] = "$@";
  255. macro_old[MACRO_SHORT ] = "$*";
  256. macro_old[MACRO_ARGS ] = "$+";
  257. char * global[4];
  258. global[MACRO_FILENAME] = filename;
  259. global[MACRO_SHORT ] = shorten(filename);
  260. global[MACRO_ARGS ] = all_args((size_t) argc, argv);
  261. global[MACRO_STOP ] = "";
  262. #if NEW_MACROS
  263. for (i = 0; i < ARRLEN(macro); ++i) {
  264. buf = expand(buf, macro[i], global[i]);
  265. }
  266. #endif
  267. #if OLD_MACROS
  268. for (i = 0; i < ARRLEN(macro_old); ++i) {
  269. buf = expand(buf, macro_old[i], global[i]);
  270. }
  271. #endif
  272. free(global[MACRO_ARGS]);
  273. return buf;
  274. }
  275. static void
  276. remove_expand(char * buf, char * argv0, int rm, char * start, char * stop) {
  277. size_t i, f, end = strlen(buf);
  278. size_t startlen = strlen(start), stoplen = strlen(stop);
  279. char x[1] = {'\0'};
  280. for (i = 0; i < end; ++i) {
  281. if (!strncmp(buf + i, start, startlen)) {
  282. if (buf + i > buf && buf[i - 1] == '\\') {
  283. continue;
  284. }
  285. for (f = i; f < end; ++f) {
  286. if (!strncmp(buf + f, stop, stoplen)) {
  287. if (buf + f > buf && buf[f - 1] == '\\') {
  288. continue;
  289. }
  290. insert(buf + f, "", end - f, 0, 1);
  291. i += startlen;
  292. if (rm & BAKE_EXPUNGE) {
  293. swap(buf + i + (f - i), x);
  294. printf("%s: %sremoving '%s'\n",
  295. argv0, rm & BAKE_NORUN ? "not " : "", buf + i);
  296. if (!(rm & BAKE_NORUN)) {
  297. remove(buf + i);
  298. }
  299. swap(buf + i + (f - i), x);
  300. }
  301. goto next;
  302. }
  303. }
  304. goto stop;
  305. }
  306. next:
  307. }
  308. stop:
  309. expand(buf, start, "");
  310. }
  311. /*** strip, run ***/
  312. /* Strips all prefixing and leading whitespace.
  313. * Except if the last character beforehand is a newline. */
  314. static size_t
  315. strip(char * buf) {
  316. size_t i = strlen(buf);
  317. if (!i) {
  318. return 0;
  319. }
  320. while (i && isspace(buf[i - 1])) {
  321. --i;
  322. }
  323. buf[i] = '\0';
  324. for (i = 0; isspace(buf[i]); ++i);
  325. if (i && buf[i - 1] == '\n') {
  326. --i;
  327. }
  328. return i;
  329. }
  330. static int
  331. run(char * buf, char * argv0) {
  332. puts(BOLD GREEN "output" RESET ":\n");
  333. pid_t pid;
  334. if ((pid = fork()) == 0) {
  335. execl("/bin/sh", "sh", "-c", buf, NULL);
  336. return 0; /* execl overwrites the process anyways */
  337. } else if (pid == -1) {
  338. fprintf(stderr, BOLD RED "%s" RESET ": %s, %s\n",
  339. argv0, "Fork Error", strerror(errno));
  340. return BAKE_ERROR;
  341. } else {
  342. int status;
  343. if (waitpid(pid, &status, 0) < 0) {
  344. fprintf(stderr, BOLD RED "%s" RESET ": %s, %s\n",
  345. argv0, "Wait PID Error", strerror(errno));
  346. return BAKE_ERROR;
  347. }
  348. if (!WIFEXITED(status)) {
  349. return BAKE_ERROR;
  350. }
  351. return WEXITSTATUS(status);
  352. }
  353. }
  354. /*** main ***/
  355. int
  356. main(int argc, char ** argv) {
  357. int ret = BAKE_RUN;
  358. char * buf = NULL,
  359. * filename,
  360. * argv0;
  361. argv0 = argv[0];
  362. if (argc < 2) {
  363. goto help;
  364. }
  365. while (++argv, --argc && argv[0][0] == '-') {
  366. ++argv[0];
  367. if (argv[0][1] == '-') {
  368. ++argv[0];
  369. if (!strcmp(argv[0], "help")) {
  370. goto help;
  371. } else if (!strcmp(argv[0], "version")) {
  372. goto version;
  373. } else if (!strcmp(argv[0], "expunge")) {
  374. ret |= BAKE_EXPUNGE;
  375. } else if (!strcmp(argv[0], "dry-run")) {
  376. ret |= BAKE_NORUN;
  377. } else {
  378. puts("UNKNOWN");
  379. goto help;
  380. }
  381. } else do {
  382. switch (argv[0][0]) {
  383. case 'h':
  384. goto help;
  385. case 'v':
  386. goto version;
  387. case 'x':
  388. ret |= BAKE_EXPUNGE;
  389. break;
  390. case 'n':
  391. ret |= BAKE_NORUN;
  392. break;
  393. case 0 :
  394. goto next;
  395. default :
  396. puts("UNKNOWN");
  397. goto help;
  398. }
  399. } while (++(argv[0]));
  400. next:
  401. }
  402. filename = argv[0];
  403. ++argv, --argc;
  404. if (strlen(filename) > FILENAME_LIMIT) {
  405. fprintf(stderr, BOLD RED "%s" RESET
  406. ": Filename too long (exceeds %d)\n",
  407. argv0,
  408. FILENAME_LIMIT);
  409. return BAKE_ERROR;
  410. }
  411. root(&filename);
  412. buf = file_get_region(filename, START, STOP);
  413. if (!buf) {
  414. char * error[2];
  415. error[0] = "File Unrecognized";
  416. error[1] = "Found start without suffix spacing";
  417. fprintf(stderr, BOLD RED "%s" RESET ": '" BOLD "%s" RESET "' %s.\n",
  418. argv0, filename, errno ? strerror(errno) : error[bake_errno]);
  419. return BAKE_ERROR;
  420. }
  421. buf = bake_expand(buf, filename, argc, argv);
  422. printf(BOLD GREEN "%s" RESET ": %s\n", argv0, buf + strip(buf));
  423. remove_expand(buf, argv0, ret, EXPUNGE_START, EXPUNGE_STOP);
  424. if (!ret) {
  425. ret = run(buf, argv0);
  426. if (ret) {
  427. printf(BOLD RED "result" RESET ": " BOLD "%d\n" RESET, ret);
  428. }
  429. } else { ret = 0; }
  430. free(buf);
  431. return ret;
  432. help:
  433. fprintf(stderr, YELLOW "%s" RESET ": %s\n", argv0, HELP DESC);
  434. return BAKE_ERROR;
  435. version:
  436. fprintf(stderr,
  437. YELLOW "%s" RESET ": v" VERSION "\n"
  438. "Copyright " COPYRIGHT "\n" LICENSE "\n",
  439. argv0);
  440. return BAKE_ERROR;
  441. }