From b89a69ed5068ce164a31b60a763e5a1547080f38 Mon Sep 17 00:00:00 2001 From: Squibid Date: Thu, 19 Dec 2024 00:59:22 -0600 Subject: initial commit --- .gitignore | 2 + Makefile | 22 ++++++++ XD.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ config.mk | 18 ++++++ 4 files changed, 228 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 XD.c create mode 100644 config.mk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc8581b --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +XD diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..62a9a16 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +include config.mk + +# flags and incs +PKGS = $(GITLIB) +CFLAGS = -DVERSION=\"$(VERSION)\" -Wall -O1 $(GIT) $(ERR) +LIBS = `$(PKG_CONFIG) --libs --cflags $(PKGS)` + +all: XD +XD: XD.o + $(CC) *.o $(CFLAGS) $(LIBS) -o $@ +XD.o: XD.c + +clean: + rm -f XD *.o + +install: XD + mkdir -p $(PREFIX)/bin + cp -f XD $(PREFIX)/bin + chmod 755 $(PREFIX)/bin/XD + +uninstall: XD + rm -f $(PREFIX)/bin/XD diff --git a/XD.c b/XD.c new file mode 100644 index 0000000..c07ad6c --- /dev/null +++ b/XD.c @@ -0,0 +1,186 @@ +#include +#include +#ifdef ERR +#include +#include +#endif + +#ifdef GIT +#include +#include +#include +#include +#include +#include +#include +#endif + +enum face { EYES, NOSE, MOUTH }; + +void +l(const char *fmt, ...) +{ + #ifdef ERR + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[0] && fmt[strlen(fmt) - 1] == ':') { + fputc(' ', stderr); + perror(NULL); + } else { + fputc('\n', stderr); + } + #endif +} + +#ifdef GIT +/** + * @brief open git repo if one is available at the current path + * + * @return a pointer to the git repo object + */ +git_repository +*init_git() +{ + git_buf buf = GIT_BUF_INIT_CONST(NULL, 0); + git_repository *repo; + + if (git_libgit2_init() < 0) { + l("Failed to initalize libgit2, proceeding without git functionality enabled."); + return NULL; + } + + if (git_repository_discover(&buf, ".", 0, NULL) < 0) { + l("Failed to discover git repo: %s", git_error_last()->message); + return NULL; + } + + if (git_repository_open(&repo, buf.ptr) < 0) { + l("Failed to open git repo: %s", git_error_last()->message); + git_buf_dispose(&buf); + return NULL; + } + + /* get rid of object containing git repo path and return the repo */ + git_buf_dispose(&buf); + return repo; +} + +/** + * @brief check for any existing stashes in the provided git repo + * + * @param repo git repo to check for existing stashes + * @return 1 if stashes found 0 otherwise + */ +int +has_stashes(git_repository *repo) +{ + git_reference *stash = NULL; + int e; + + e = git_reference_lookup(&stash, repo, "refs/stash"); + if (e == GIT_ENOTFOUND) { + git_error_clear(); + return 0; + } else if (e < GIT_OK) { + l("Error looking up stash reference: %s", git_error_last()->message); + return 0; + } else { + e = 1; + } + + git_reference_free(stash); + return e; +} + +/** + * @brief check for any untracked changes in the current git repo + * + * @param repo git repository object + * @return 1 if any untracked changes have been found 0 otherwise + */ +int +has_untracked(git_repository *repo) +{ + git_status_options opts = GIT_STATUS_OPTIONS_INIT; + git_status_list *list = NULL; + int r = 0; + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | + GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX; + + if (git_status_list_new(&list, repo, &opts) < 0) { + l("Error checking for untacked changes: %s", git_error_last()->message); + return 0; + } + + /* if any changes are found return 1 */ + if (git_status_list_entrycount(list) > 0) { + r = 1; + } + + git_status_list_free(list); + return r; +} +#endif + +int +main(int argc, char *argv[]) +{ + int code = -1; + char face[] = { ':', 0, '|' }; + + #ifdef GIT + git_repository *repo; + int git_ok = 0; + + if ((repo = init_git())) { + git_ok = 1; + } + + if (git_ok) { + /* change the eyes depending on the current git repo's status */ + if (has_stashes(repo)) { + face[EYES] = '8'; /* goggle eyes if we have some stashed changes */ + } else if (git_repository_is_empty(repo)) { + face[EYES] = 'B'; /* sunglasses if we're in a new repo with no HEAD */ + } else { + face[EYES] = ';'; /* wink when we're in a git repo */ + } + + /* change the nose depending on the current git repo's status */ + if (has_untracked(repo)) { + face[NOSE] = '^'; /* add a little nose when there are untracked changes in the repo */ + } else if (git_repository_head_detached(repo)) { + face[NOSE] = '-'; /* add a minus nose when the HEAD is detached */ + } + } + #endif + + /* get exit code from user args */ + code = atoi(argv[1] ? argv[1] : "-1"); + + /* change mouth based on exit code */ + if (code >= 0) { + switch (code) { + case 0: face[MOUTH] = ')'; break; /* all good */ + case 130: face[MOUTH] = 'O'; break; /* Ctrl-c pressed (SIGTERM) */ + case 126: face[MOUTH] = 'P'; break; /* permission denied */ + case 127: face[MOUTH] = '/'; break; /* command not found */ + default: face[MOUTH] = '('; break; /* all other codes (usually the program saying it has failed) */ + } + } else { + face[MOUTH] = '|'; /* no code info */ + } + + #ifdef GIT + git_repository_free(repo); + git_libgit2_shutdown(); + #endif + + printf("%c%c%c", face[EYES], face[NOSE], face[MOUTH]); +} diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..7ec485d --- /dev/null +++ b/config.mk @@ -0,0 +1,18 @@ +VERSION = 1.0 + +PKG_CONFIG = pkg-config + +# paths +PREFIX = /usr/local + +GIT = +GITLIB = +# comment to disable git support +GIT = -DGIT +GITLIB = libgit2 + +ERR = +# uncomment to enable errors +# ERR = -DERR + +CC = cc -- cgit v1.2.1