#include #include #include #include #include #include #include #include #include #include #include // #define WIKIROOT "~/wiki/" #define DATE_FORMAT "%a %d %b. %Y %H:%M:%S %Z" #if __STDC_VERSION__ >= 202311L #else #define typeof __typeof__ #endif /** Calculate length of a static array */ #define ARR_LEN(arr) ((size_t) (sizeof(arr) / sizeof((arr)[0]))) #define ARRAY(x, ...) ((typeof(x)[]) {x, __VA_ARGS__}) /* Call a procedure with a list of strings */ #define CALL(proc, ...) proc(ARR_LEN(ARRAY(__VA_ARGS__)), ARRAY(__VA_ARGS__)) typedef char **strlist; /** * Collect all wikis under WIKIROOT. * @param buf * Location where the wikis should be stored. * @returns -1 on failure (and print an error to stderr) * Number of found wikis otherwise (if 0 a message will also be * printed to stdout). */ int gather_wikis(const char *wiki_root, strlist *buf); /** Error callback for glob. */ int errfunc(const char *path, int err); int commit(const char *msg); int ammend(const char *msg); /** * Dispatch a git command through a shellout. * @param argc - length of argv * @param argv - arguments appended to the base git command. * The base git command is configured thruogh the variable `cmdline_base` */ int git(size_t argc, const char *argv[]); /** * Base command when calling git */ static char *cmdline_base[] = { "git", "-c", "color.status=always", "-c", "color.ui=always", }; /** * Compare a fixed string to an aribtary string. */ bool cmp(const char *fix, const char *var) { return strncmp(fix, var, strlen(fix)) == 0; } /** * Collects the globaly available wikis, and print a pretty list of them */ void print_available_wikis(); /** Print help string for the program to stdout */ void print_help(); /** * Find the directory to search for wikis in. * Returns a string which must be freed. * */ char *get_wikiroot(); /* * Concatenate all the strings in arr into a newly allocated string. */ char *strconcat(int count, const char *arr[]); /** * Primary dispatcher of commands. * @param wiki_root - Directory in which to find the given wiki * @param wiki - Which wiki to operate on * @param command - what to do * First member should probably either be the name of a git * subcommand, or one of our special commands. * Remaining member will be used as arguments to that subcommand. */ void wiki_do(const char *wiki_root, const char *wiki, const char *command[]); int main (int argc, char *argv[]) { /* At most every other argument can be a requested wiki */ strlist wiki_list = malloc(sizeof(*wiki_list) * argc / 2); int wiki_list_ptr = 0; int arg = 1; for (; arg < argc; arg++) { if (cmp("--wiki", argv[arg]) || cmp("-w", argv[arg])) { /* realpath */ char *buf = realpath(argv[++arg], NULL); if (buf == NULL) { printf("Failed opening %s: %s\n", argv[arg - 1], strerror(errno)); } else { wiki_list[wiki_list_ptr++] = buf; } continue; } else if (cmp("--list", argv[arg]) || cmp("-l", argv[arg])) { print_available_wikis(); exit(0); } else if (cmp("--help", argv[arg]) || cmp("-l", argv[arg]) || cmp("-?", argv[arg])) { print_help(); exit(0); } else { break; } } char *wiki_root; if (wiki_list_ptr == 0) { free(wiki_list); wiki_root = get_wikiroot(); gather_wikis(wiki_root, &wiki_list); } free(wiki_root); /* If no further arguments are given, edit the given wiki */ #if 0 if (arg == argc) { char *wiki; strlist wikis = NULL; if (wiki_list_ptr != 0) { wiki = wiki_list[0]; } else { int wiki_count = gather_wikis(&wikis); if (wiki_count < 0) { exit(1); } if (wiki_count == 0) { fprintf(stderr, "No wikis found\n"); } wiki = wikis[0]; } // TODO // glob("${WIKIROOT}/${wiki}/index.*") // execp("vim", "vim", if (wikis) free(wikis); } else { /* If further arguments are given */ const char *cmd = argv[arg]; strlist final_list; int wiki_count; if (wiki_list_ptr != 0) { final_list = wiki_list; wiki_count = wiki_list_ptr; } else { free(wiki_list); wiki_count = gather_wikis(&final_list); if (wiki_count < 0) { /* TODO handle error */ } } for (int i = 0; i < wiki_count; i++) { /* TODO args */ wiki_do(wiki_root, final_list[i], args); } } #endif free(wiki_list); } int gather_wikis(const char *wiki_root, strlist *wiki_list) { int ret = 0; glob_t pglob = { 0 }; ret = glob(CALL(strconcat, wiki_root, "/*"), GLOB_TILDE, &errfunc, &pglob); switch (ret) { case 0: break; case GLOB_NOSPACE: fprintf(stderr, "Glob ran out of space\n"); ret = -1; goto gather_wikis_end; case GLOB_ABORTED: fprintf(stderr, "Glob was aborted\n"); ret = -1; goto gather_wikis_end; case GLOB_NOMATCH: printf("No Wikis found\n"); goto gather_wikis_end; default: fprintf(stderr, "Unknown glob error. errno: %s\n", strerror(errno)); ret = -1; goto gather_wikis_end; } *wiki_list = malloc(sizeof(*wiki_list) * pglob.gl_matchc); int idx = 0; for (int i = 0; i < pglob.gl_matchc; i++) { if (strstr(pglob.gl_pathv[i], "html") != NULL) { (*wiki_list)[idx++] = basename(pglob.gl_pathv[i]); } } ret = idx; gather_wikis_end: return ret; } /** * Error callback for glob. * Simply print the error and continue. */ int errfunc(const char *path, int err) { fprintf(stderr, "Failed opening %s: %s\n", path, strerror(err)); return 0; } int commit_ammend (const char* action, const char *msg) { char *buf = NULL; if (msg == NULL) { time_t now = time(NULL); struct tm result; if (localtime_r(&now, &result) == NULL) { msg = "Now"; } buf = malloc(1024); /* TODO check result */ strftime(buf, 1024, DATE_FORMAT, &result); msg = buf; } /* "add" has type (char (*)[4]) (pointer to array of length 4. * This just forces the type... */ CALL(git, (const char *) "add", "-A"); CALL(git, (const char *) "commit", "-m", msg); if (buf) free(buf); return 0; } int git(size_t argc, const char *argv[]) { pid_t pid; int ret; switch (pid = fork()) { case -1: break; case 0: /* child */ size_t cmdline_len = ARR_LEN(cmdline_base) + argc; char **commandline = malloc((cmdline_len + 1) * sizeof(*commandline)); for (size_t i = 0; i < ARR_LEN(cmdline_base); i++) { commandline[i] = cmdline_base[i]; } for (size_t i = 0; i < argc; i++) { commandline[i + ARR_LEN(cmdline_base)] = commandline[i]; } commandline[cmdline_len] = NULL; ret = execvp("git", commandline); if (ret) break; fprintf(stderr, "Exec "); for (size_t i = 0; i < cmdline_len; i++) { fprintf(stderr, " %s", commandline[i]); } fprintf(stderr, " failed: %s\n", strerror(errno)); exit(1); break; default: /* parent */ break; } return ret; } void print_available_wikis() { printf( "Available Wikis:\n" "================\n"); strlist wikis; char *root = get_wikiroot(); int wiki_count = gather_wikis(root, &wikis); free(root); if (wiki_count < 0) { return; } for (int i = 0; i < wiki_count; i++) { printf("%i. %s\n", i, wikis[i]); free(wikis[i]); } free(wikis); } void print_help() { printf( "Wiki helper. Usage:\n" "--help | -h :: Display this help\n" "--list | -l :: Show available wikis\n" "--wiki | -w :: specify a specific wiki for operation\n" "\n" "Default acts on all wikis.\n" "\n" "wi commit [msg] :: Create a git commit on specified wikis\n" "wi ammend [msg] :: Change last commit\n" "wi g [msg] :: wi commit [msg]; wi push\n" "wi [git-command] :: run git commands on wikis\n"); } void wiki_do(const char *wiki_root, const char *wiki, const char *command[]) { if (wiki == NULL) return; char *chdir_dest; if (wiki[0] == '/') { chdir_dest = strdup(wiki); } else { chdir_dest = CALL(strconcat, wiki_root, "/", wiki); } if (chdir(chdir_dest)) { printf("failed chdir to %s : %s\n", chdir_dest, strerror(errno)); exit(1); } free(chdir_dest); if (cmp("commit", command[0])) { // (command + 1) } else if (cmp("ammend", command[0])) { // (command + 1) } else if (cmp("grep", command[0])) { // (command + 1) } else if (cmp("go", command[0]) || cmp("g", command[0])) { // (command + 1) } else if (cmp("echo", command[0])) { // (command + 1) } else { // (command + 1) } } char *get_wikiroot() { { const char *result = getenv("WIKIROOT");; if (result != NULL) { return strdup(result); } } const char *home = getenv("HOME"); struct passwd *pw; if (home == NULL) { pw = getpwuid(geteuid()); if (pw == NULL) { home = "/"; } else { home = pw->pw_dir; } } return CALL(strconcat, home, "/wiki"); } char *strconcat(int count, const char *arr[]) { size_t allocated = 100; size_t used = 0; char *dest = malloc(allocated); for (int i = 0; i < count; i++) { size_t len = strlen(arr[i]); if (used + len >= allocated) { allocated <<= 2; char *tmp = realloc(dest, allocated); /* TODO should the old buffer be freed? */ if (tmp == NULL) { fprintf(stderr, "Realloc failed\n"); exit(1); } dest = tmp; } memcpy(dest + used, arr[i], len); used += len; } dest[used] = '\0'; return dest; }