From 3ce865f4859587a1bf9a5ae16e791b7b58c29ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hugo=20H=C3=B6rnquist?= Date: Tue, 22 Aug 2023 21:28:55 +0200 Subject: tis 22 aug 2023 21:28:55 CEST --- wiki.c | 440 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 wiki.c diff --git a/wiki.c b/wiki.c new file mode 100644 index 0000000..b4e6b65 --- /dev/null +++ b/wiki.c @@ -0,0 +1,440 @@ +#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; +} -- cgit v1.2.3