/* * runfile - rf * * a no-dependancy, minimal * POSIX C99 Make/justfile * alternative to run commands. * * czjstmax * * * check MAINTAINERS file */ #define _POSIX_C_SOURCE 200809L #define RUNFILE_VER_MAJ 1 #define RUNFILE_VER_MIN 3 #define RUNFILE_VER_FIX 0 /* *> rfile version system * VER_MAX : anything that changes how rfile are written in the slightest * between 0 and 999 * VER_MIN : anything that varies the UX of the program (i.e. CLI flags) * between 0 and 999 * VER_FIX : optimizations, slight logic tweaks, anything that is not * noticeable by the user but impacts the dev experience enough * to be important. between 0 and 9999 */ #include #include #include #include #include #include #include #include #define MAX_VARS 128 typedef struct { char *key; char *val; } var_t; var_t vars[MAX_VARS]; size_t var_count = 0; void usage(); void printBox(char *str); char *expand_vars(char *input); int main(int argc, char **argv) { char *section = NULL; bool in_section = false; if (argc >= 2) { for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { usage(); return 0; } } } FILE *f = fopen("rfile", "r"); if (!f) { fprintf(stderr, "no rfile found.\n"); return 1; } wordexp_t p; char *line = NULL; size_t len = 0; while (getline(&line, &len, f) != -1) { line[strcspn(line, "\n")] = '\0'; char *cmd = line; while (isspace(*cmd)) cmd++; if (*cmd == '\0') continue; else if (cmd[0] == '#') /* TODO: strchr() up to # and trim cmd to only skip the commented-out part to allow in-line comments */ continue; #if defined(RF_EXTRAS) || defined(RF_EXTRA_MORE_COMMENTS) else if (strncmp(cmd, "// ", 3) == 0) continue; else if (strncmp(cmd, "-- ", 3) == 0) continue; #endif else if (strcmp(cmd, "}") == 0 || strcmp(line, "}") == 0) { in_section = false; free(section); section = NULL; continue; } if (cmd[0] == '@' && strchr(cmd, '=')) { char *eq = strchr(cmd, '='); *eq = '\0'; char *key = cmd + 1; //skip '@' char *val = eq + 1; vars[var_count].key = strdup(key); vars[var_count].val = strdup(val); var_count++; continue; } char *brace = strchr(cmd, '{'); if (brace) { *brace = '\0'; size_t l = strlen(cmd); while (l > 0 && (cmd[l-1] == ' ' || cmd[l-1] == '\t')) { cmd[l-1] = '\0'; l--; } free(section); section = strdup(cmd); in_section = true; continue; } bool matches = (argc == 1 && !in_section) || (argc > 1 && section && strcmp(argv[1], section) == 0); if (!matches) continue; if (strncmp(cmd, "@: ", 3) == 0) { printf("\n%s\n", cmd); continue; } else if (strncmp(cmd, "@:! ", 4) == 0) { printBox(cmd); continue; } char *expanded = expand_vars(cmd); #if !defined(RF_HIDDEN_CMD) printf("$ %s\n", expanded); #endif if (wordexp(expanded, &p, 0) != 0) { fprintf(stderr, "wordexp failed\n"); continue; } pid_t pid = fork(); if (pid == 0) { execvp(p.we_wordv[0], p.we_wordv); perror("failed to run command"); fclose(f); _exit(1); } waitpid(pid, NULL, 0); wordfree(&p); } free(line); fclose(f); return 0; } void usage() { printf( "runfile usage - version %d.%d.%d\n" "syn: rf [section]\n" , RUNFILE_VER_MAJ, RUNFILE_VER_MIN, RUNFILE_VER_FIX ); } char *expand_vars(char *input) { static char buf[8192]; size_t bi = 0; for (size_t i = 0; input[i] && bi < sizeof(buf) - 1; i++) { if (input[i] == '@') { char name[128] = {0}; size_t ni = 0; size_t j = i + 1; while (input[j] && !isspace(input[j]) && input[j] != '@' && ni < sizeof(name) - 1) { name[ni++] = input[j++]; } name[ni] = '\0'; const char *replacement = NULL; for (size_t v = 0; v < var_count; v++) { if (strcmp(vars[v].key, name) == 0) { replacement = vars[v].val; break; } } if (replacement) { size_t k = 0; while (replacement[k] && bi < sizeof(buf) - 1) buf[bi++] = replacement[k++]; } else { buf[bi++] = '@'; for (size_t k = 0; name[k]; k++) buf[bi++] = name[k]; } i = j - 1; continue; } buf[bi++] = input[i]; } buf[bi] = '\0'; return buf; } void printBox(char *str) { printf("┌"); for (size_t i = 0; i < strlen(str) + 2; ++i) printf("─"); printf("┐\n"); printf("│ %s │\n", str); printf("└"); for (size_t i = 0; i < strlen(str) + 2; ++i) printf("─"); printf("┘\n"); }