Ever burned a cake?
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

567 行
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 VERSION "20240408"
  26. #define HELP \
  27. BOLD "[option] target-file" RESET " [" GREEN "arguments" RESET " ...]\n" \
  28. "Use the format `" BOLD "@BAKE" RESET " cmd ...' within the target-file, this will execute the\n" \
  29. "rest of line, or if found within the file, until the " BOLD "@STOP" RESET " marker.\n" \
  30. #define DESC \
  31. "Options [Must always be put first, may be merged together]\n" \
  32. "\t" DIM "-v --version" RESET ", " DIM "-h --help" RESET ", " \
  33. BOLD "-n --dry-run" RESET ", " BOLD "-x --expunge\n" RESET \
  34. "Expansions\n" \
  35. "\t" YELLOW "@FILENAME" RESET " returns target-file (abc.x.txt)\n" \
  36. "\t" YELLOW "@SHORT " RESET " returns target-file without suffix (^-> abc.x)\n" \
  37. "\t" YELLOW "@ARGS " RESET " returns " GREEN "arguments" RESET "\n" \
  38. "Additional Features And Notes\n" \
  39. "\t" YELLOW "@{" RESET BOLD "EXPUNGE_THIS_FILE" YELLOW "}" RESET \
  40. " inline region to delete this or many files or directories,\n" \
  41. "\tnon-recursive, only one file per block, removed from left to right. This has no\n" \
  42. "\tinfluence on the normal command execution.\n" \
  43. "\t" YELLOW "\\" RESET \
  44. "SPECIAL_NAME will result in SPECIAL_NAME in the executed shell command.\n" \
  45. "Backslashing is applicable to all meaningful symbols in Bake, it is ignored otherwise."
  46. #define COPYRIGHT "2023 Emil Williams"
  47. #define LICENSE "Licensed under the GNU Public License version 3 only, see LICENSE."
  48. #define FILENAME_LIMIT (FILENAME_MAX)
  49. #define BAKE_ERROR 127
  50. enum {
  51. BAKE_UNRECOGNIZED,
  52. BAKE_MISSING_SUFFIX
  53. };
  54. enum {
  55. BAKE_RUN = 0,
  56. BAKE_NORUN = (1 << 0),
  57. BAKE_EXPUNGE = (1 << 1)
  58. };
  59. #define ARRLEN(a) (sizeof(a) / sizeof(a[0]))
  60. #if INCLUDE_AUTONOMOUS_COMPILE
  61. __attribute__((__section__(".text"))) static char autonomous_compile[] =
  62. "@BAKE cc -std=c89 -O2 $@.c -o $@ $+ @STOP";
  63. #endif
  64. static int bake_errno;
  65. typedef struct {
  66. size_t len;
  67. char * buf;
  68. } string_t;
  69. typedef string_t map_t;
  70. /*** root ***/
  71. static void
  72. swap(char * a, char * b) {
  73. *a ^= *b;
  74. *b ^= *a;
  75. *a ^= *b;
  76. }
  77. static int
  78. root(char ** rootp) {
  79. char x[1] = {'\0'};
  80. char * root = *rootp;
  81. size_t len = strlen(root);
  82. int ret;
  83. while (len && root[len] != '/') {
  84. --len;
  85. }
  86. if (!len) {
  87. return 0;
  88. }
  89. swap(root + len, x);
  90. ret = chdir(root);
  91. swap(root + len, x);
  92. *rootp += len + 1;
  93. return ret;
  94. }
  95. /*** find region in file ***/
  96. static map_t
  97. map(char * fn) {
  98. struct stat s;
  99. int fd;
  100. map_t m;
  101. m.buf = NULL;
  102. m.len = 0;
  103. fd = open(fn, O_RDONLY);
  104. if (fd != -1) {
  105. if (!fstat(fd, &s)
  106. && s.st_mode & S_IFREG
  107. && s.st_size) {
  108. m.len = (size_t) s.st_size;
  109. m.buf = (char *) mmap(NULL, m.len, PROT_READ, MAP_SHARED, fd, 0);
  110. }
  111. close(fd);
  112. }
  113. return m;
  114. }
  115. static char *
  116. find(char * buf, char * x, char * end) {
  117. size_t xlen = strlen(x);
  118. char * start = buf;
  119. for (; buf <= end - xlen; ++buf) {
  120. if (!strncmp(buf, x, xlen)) {
  121. if (start < buf && buf[-1] == '\\') {
  122. continue;
  123. } else {
  124. return buf;
  125. }
  126. }
  127. }
  128. return NULL;
  129. }
  130. static char *
  131. get_region(string_t m, char * findstart, char * findstop) {
  132. char * buf = NULL, * start, * stop, * end = m.len + m.buf;
  133. start = find(m.buf, findstart, end);
  134. if (start) {
  135. start += strlen(findstart);
  136. #ifdef REQUIRE_SPACE
  137. if (!isspace(*start)) {
  138. bake_errno = BAKE_MISSING_SUFFIX;
  139. return NULL;
  140. }
  141. #endif /* REQUIRE_SPACE */
  142. stop = find(start, findstop, end);
  143. if (!stop) {
  144. stop = start;
  145. while (stop < end && *stop != '\n') {
  146. if (stop[0] == '\\'
  147. && stop[1] == '\n') {
  148. stop += 2;
  149. }
  150. ++stop;
  151. }
  152. }
  153. buf = strndup(start, (size_t)(stop - start));
  154. }
  155. return buf;
  156. }
  157. static char *
  158. file_get_region(char * fn, char * start, char * stop) {
  159. map_t m = map(fn);
  160. char * buf = NULL;
  161. if (m.buf) {
  162. buf = get_region(m, start, stop);
  163. munmap(m.buf, m.len);
  164. }
  165. return buf;
  166. }
  167. /*** g_short, g_all ***/
  168. static char *
  169. shorten(char * fn) {
  170. size_t i, last, len;
  171. static char sh[FILENAME_LIMIT];
  172. len = strlen(fn);
  173. for (last = i = 0; i < len; ++i) {
  174. if (fn[i] == '.') {
  175. last = i;
  176. }
  177. }
  178. last = last ? last : i;
  179. strncpy(sh, fn, last);
  180. sh[last] = '\0';
  181. return sh;
  182. }
  183. static char *
  184. all_args(size_t argc, char ** argv) {
  185. char * all = NULL;
  186. if (argc > 0) {
  187. size_t i, len = argc;
  188. for (i = 0; i < argc; ++i) {
  189. len += strlen(argv[i]);
  190. }
  191. all = malloc(len + 1);
  192. all[len] = '\0';
  193. for (len = 0, i = 0; i < argc; ++i) {
  194. strcpy(all + len, argv[i]);
  195. len += strlen(argv[i]) + 1;
  196. if (i + 1 < argc) {
  197. all[len - 1] = ' ';
  198. }
  199. }
  200. }
  201. return all ? all : calloc(1, 1);
  202. }
  203. /*** insert, expand, bake_expand ***/
  204. static void
  205. insert(char * str, char * new, size_t slen, size_t nlen, size_t shift) {
  206. memmove(str + nlen, str + shift, slen + 1 - shift);
  207. memcpy(str, new, nlen);
  208. }
  209. static char *
  210. expand(char * buf, char * macro, char * with) {
  211. ssize_t i,
  212. blen = strlen(buf),
  213. mlen = strlen(macro),
  214. wlen = strlen(with),
  215. nlen;
  216. fflush(stdout);
  217. for (i = 0; i < blen - mlen + 1; ++i) {
  218. if (!strncmp(buf + i, macro, mlen)) {
  219. if (i && buf[i - 1] == '\\') {
  220. memmove(buf + i - 1, buf + i, blen - i);
  221. buf[blen - 1] = '\0';
  222. } else {
  223. nlen = wlen - mlen + 1;
  224. if (nlen > 0) {
  225. buf = realloc(buf, blen + nlen);
  226. }
  227. insert(buf + i, with, blen - i, wlen, mlen);
  228. blen += (nlen > 0) * nlen;
  229. }
  230. }
  231. }
  232. return buf;
  233. }
  234. static char *
  235. bake_expand(char * buf, char * filename, int argc, char ** argv) {
  236. enum {
  237. MACRO_FILENAME,
  238. MACRO_SHORT,
  239. MACRO_ARGS,
  240. MACRO_STOP,
  241. MACRO_NONE
  242. };
  243. char * macro[MACRO_NONE],
  244. * macro_old[MACRO_STOP],
  245. * global[MACRO_NONE];
  246. size_t i;
  247. macro[MACRO_FILENAME] = "@FILENAME";
  248. macro[MACRO_SHORT ] = "@SHORT";
  249. macro[MACRO_ARGS ] = "@ARGS";
  250. macro[MACRO_STOP ] = "@STOP";
  251. macro_old[MACRO_FILENAME] = "$@";
  252. macro_old[MACRO_SHORT ] = "$*";
  253. macro_old[MACRO_ARGS ] = "$+";
  254. global[MACRO_FILENAME] = filename;
  255. global[MACRO_SHORT ] = shorten(filename);
  256. global[MACRO_ARGS ] = all_args((size_t) argc, argv);
  257. global[MACRO_STOP ] = "";
  258. #if NEW_MACROS
  259. for (i = 0; i < ARRLEN(macro); ++i) {
  260. buf = expand(buf, macro[i], global[i]);
  261. }
  262. #endif
  263. #if OLD_MACROS
  264. for (i = 0; i < ARRLEN(macro_old); ++i) {
  265. buf = expand(buf, macro_old[i], global[i]);
  266. }
  267. #endif
  268. free(global[MACRO_ARGS]);
  269. return buf;
  270. }
  271. static void
  272. remove_expand(char * buf, char * argv0, int rm, char * start, char * stop) {
  273. size_t i, f, end = strlen(buf);
  274. size_t startlen = strlen(start), stoplen = strlen(stop);
  275. char x[1] = {'\0'};
  276. for (i = 0; i < end; ++i) {
  277. if (!strncmp(buf + i, start, startlen)) {
  278. if (buf + i > buf && buf[i - 1] == '\\') {
  279. continue;
  280. }
  281. for (f = i; f < end; ++f) {
  282. if (!strncmp(buf + f, stop, stoplen)) {
  283. if (buf + f > buf && buf[f - 1] == '\\') {
  284. continue;
  285. }
  286. insert(buf + f, "", end - f, 0, 1);
  287. i += startlen;
  288. if (rm & BAKE_EXPUNGE) {
  289. swap(buf + i + (f - i), x);
  290. #if !ENABLE_EXPUNGE_REMOVE
  291. printf("%s: %sremoving '%s'\n",
  292. argv0, rm & BAKE_NORUN ? "not " : "", buf + i);
  293. if (!(rm & BAKE_NORUN)) {
  294. remove(buf + i);
  295. }
  296. #else
  297. printf("%s: not removing '%s'\n", argv0, buf + i);
  298. #endif
  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. pid_t pid;
  333. puts(BOLD GREEN "output" RESET ":\n");
  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. }