aboutsummaryrefslogtreecommitdiff
path: root/src/remembering.c
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2021-09-01 10:41:36 -0300
committerEuAndreh <eu@euandre.org>2021-09-01 10:41:36 -0300
commit2f8fbbccd0cc11db0c151a621236247e4dd3b8e3 (patch)
tree73395c4b08ee8fd9c44eed2f7b8c1d4a046eb0ba /src/remembering.c
parentMakefile: s/run-tests/remembering-tests/ (diff)
downloadremembering-2f8fbbccd0cc11db0c151a621236247e4dd3b8e3.tar.gz
remembering-2f8fbbccd0cc11db0c151a621236247e4dd3b8e3.tar.xz
mv src/remembering-c.c src/remembering.c
Diffstat (limited to 'src/remembering.c')
-rw-r--r--src/remembering.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/src/remembering.c b/src/remembering.c
new file mode 100644
index 0000000..108d246
--- /dev/null
+++ b/src/remembering.c
@@ -0,0 +1,395 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <sys/stat.h>
+
+
+#ifdef TEST
+#include "unit-test.h"
+#include <stdbool.h>
+#include <errno.h>
+#endif
+
+
+const int EXIT_ERROR = 1;
+const int EXIT_USAGE = 2;
+
+static int usage(FILE *stream) {
+ const char *const msg = "Usage: remembering -p PROFILE -c COMMAND\n";
+ if (fprintf(stream, msg) < 0) {
+ perror("usage() - fprintf()");
+ return -1;
+ }
+ return 0;
+}
+
+static int help(FILE *stream) {
+ const char *const msg =
+ "Options:\n"
+ " -p, PROFILE profile to be used for gathering and storing data\n"
+ " -c, COMMAND commant to be run, reading from STDIN, writing to STDOUT\n"
+ " -h, --help show this help\n"
+ " -V, --version print the version number\n"
+ "\nSee \"man remembering\" for more information\n";
+ if (fprintf(stream, msg) < 0) {
+ perror("help() - fprint()");
+ return -1;
+ }
+ return 0;
+}
+
+static int version(FILE *stream) {
+ const char *const msg = "remembering-" VERSION " " DATE "\n";
+ if (fprintf(stream, msg) < 0) {
+ perror("version() - fprintf()");
+ return -1;
+ }
+ return 0;
+}
+
+static int missing(FILE *stream, const char *const argument) {
+ const char *const msg = "Missing option: %s\n";
+ if (fprintf(stream, msg, argument) < 0) {
+ perror("missing() - fprintf()");
+ return -1;
+ }
+ return 0;
+}
+
+static char *get_profile_file(const char *const profile, FILE *stream) {
+ char *home = getenv("XDG_DATA_HOME");
+ if (!home || (strcmp(home, "") == 0)) {
+ if (!(home = getenv("HOME"))) {
+ fprintf(stream, "Unable to get $XDG_DATA_HOME or $HOME environment variables\n");
+ return NULL;
+ }
+ const char *const suffix = "/.local/share/remembering";
+ const size_t home_suffix_size =
+ strlen(home) +
+ strlen(suffix) +
+ sizeof(char);
+ char *const home_with_suffix = malloc(home_suffix_size);
+ if (!home_with_suffix) {
+ perror("get_profile_file() - malloc() - home_with_suffix");
+ return NULL;
+ }
+ strcpy(home_with_suffix, home);
+ strcat(home_with_suffix, suffix);
+ home = home_with_suffix;
+ } else {
+ if (!(home = strdup(home))) {
+ perror("get_profile_file() - strdup()");
+ return NULL;
+ }
+ }
+
+ size_t size =
+ strlen(home) +
+ strlen("/") +
+ strlen(profile) +
+ sizeof(char);
+ char *const profile_file = malloc(size);
+ if (!profile_file) {
+ perror("get_profile_file() - malloc() - profile_file");
+ free(home);
+ return NULL;
+ }
+ strcpy(profile_file, home);
+ strcat(profile_file, "/");
+ strcat(profile_file, profile);
+
+ free(home);
+ return profile_file;
+}
+
+#ifdef TEST
+static void test_get_profile_file() {
+ #define EXPECTED_FMT "\nexpected: %s\ngot: %s\n"
+ FILE *f = fopen("/dev/null", "w");
+ assert(f);
+
+ {
+ testing("get_profile_file() when $XDG_DATA_HOME is *unset*");
+ const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME");
+ unsetenv("XDG_DATA_HOME");
+
+ const char *const HOME = getenv("HOME");
+ const char *const SUFFIX = "/.local/share/remembering/";
+ const char *const A_PROFILE = "A-PROFILE-NAME";
+ assert(HOME);
+
+ const size_t expected_size =
+ strlen(HOME) +
+ strlen(SUFFIX) +
+ strlen(A_PROFILE) +
+ sizeof(char);
+ char *const expected = malloc(expected_size);
+ assert(expected);
+ strcpy(expected, HOME);
+ strcat(expected, SUFFIX);
+ strcat(expected, A_PROFILE);
+
+ char *const profile_file = get_profile_file(A_PROFILE, f);
+ assert(profile_file);
+ assertf(
+ strcmp(profile_file, expected) == 0,
+ EXPECTED_FMT,
+ expected,
+ profile_file
+ );
+
+ free(profile_file);
+ free(expected);
+ if (previous_xdg_data_home) {
+ setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
+ }
+ test_ok();
+ }
+ {
+ testing("get_profile_file() when $XDG_DATA_HOME is *empty*");
+ const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME");
+ setenv("XDG_DATA_HOME", "", true);
+
+ const char *const HOME = getenv("HOME");
+ const char *const SUFFIX = "/.local/share/remembering/";
+ const char *const A_PROFILE = "ANOTHER-PROFILE-NAME";
+ assert(HOME);
+
+ const size_t expected_size =
+ strlen(HOME) +
+ strlen(SUFFIX) +
+ strlen(A_PROFILE) +
+ sizeof(char);
+ char *const expected = malloc(expected_size);
+ assert(expected);
+ strcpy(expected, HOME);
+ strcat(expected, SUFFIX);
+ strcat(expected, A_PROFILE);
+
+ char *const profile_file = get_profile_file(A_PROFILE, f);
+ assert(profile_file);
+ assertf(
+ strcmp(profile_file, expected) == 0,
+ EXPECTED_FMT,
+ expected,
+ profile_file
+ );
+
+ free(profile_file);
+ free(expected);
+ if (previous_xdg_data_home) {
+ setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
+ } else {
+ unsetenv("XDG_DATA_HOME");
+ }
+ test_ok();
+ }
+ {
+ testing("get_profile_file() when $XDG_DATA_HOME is *set*");
+ const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME");
+
+ const char *const XDG_DATA_HOME = "/a/custom/path";
+ const char *const A_PROFILE = "YET-ANOTHER-PROFILE-NAME";
+ const char *const EXPECTED = "/a/custom/path/YET-ANOTHER-PROFILE-NAME";
+
+ setenv("XDG_DATA_HOME", XDG_DATA_HOME, true);
+
+ char *const profile_file = get_profile_file(A_PROFILE, f);
+ assert(profile_file);
+ assertf(
+ strcmp(profile_file, EXPECTED) == 0,
+ EXPECTED_FMT,
+ EXPECTED,
+ profile_file
+ );
+
+ free(profile_file);
+ if (previous_xdg_data_home) {
+ setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
+ } else {
+ unsetenv("XDG_DATA_HOME");
+ }
+ test_ok();
+ }
+ {
+ testing("get_profile_file() when $HOME is *unset*");
+ const char *const previous_home = getenv("HOME");
+ const char *const previous_xdg_data_home = getenv("XDG_DATA_HOME");
+ unsetenv("XDG_DATA_HOME");
+ unsetenv("HOME");
+
+ assert(get_profile_file("ANY-PROFILE", f) == NULL);
+
+ setenv("HOME", previous_home, true);
+ if (previous_xdg_data_home) {
+ setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
+ }
+ test_ok();
+ }
+
+ fclose(f);
+}
+#endif
+
+static int mkdir_p(const char *const path, mode_t mode) {
+ struct stat s;
+ int ret;
+
+ if (stat(path, &s) == 0 && S_ISDIR(s.st_mode)) {
+ return 0;
+ }
+
+ char *const path_dup = strdup(path);
+ if (!path_dup) {
+ perror("mkdir_p() - strdup()");
+ return -1;
+ }
+
+ const char *const parent = dirname(path_dup);
+ if ((ret = mkdir_p(parent, mode))) {
+ free(path_dup);
+ return ret;
+ }
+
+ free(path_dup);
+ return mkdir(path, mode);
+}
+
+#ifdef TEST
+static void test_mkdir_p() {
+ {
+ testing("mkdir_p() with an existing directory is a noop");
+ struct stat s;
+ assert(stat("/tmp", &s) == 0 && S_ISDIR(s.st_mode));
+ assert(mkdir_p("/tmp", S_IRWXU | S_IRWXG | S_IRWXO) == 0);
+ assert(stat("/tmp", &s) == 0 && S_ISDIR(s.st_mode));
+ test_ok();
+ }
+ {
+ testing("mkdir_p() with a new directory creates it");
+ struct stat s;
+ const char *const SUBDIRECTORY_SUFFIX = "/a/b/c/d/e";
+ char template[] = "/tmp/remembering.XXXXXX";
+ const char *const prefix = mkdtemp(template);
+ assert(prefix);
+
+ assert(stat(prefix, &s) == 0 && S_ISDIR(s.st_mode));
+
+ const size_t subdirectory_size =
+ strlen(prefix) +
+ strlen(SUBDIRECTORY_SUFFIX) +
+ sizeof(char);
+ char *const subdirectory = malloc(subdirectory_size);
+ assert(subdirectory);
+ strcpy(subdirectory, prefix);
+ strcat(subdirectory, SUBDIRECTORY_SUFFIX);
+
+ assert(stat(subdirectory, &s) == -1 && errno == ENOENT);
+ assert(mkdir_p(subdirectory, S_IRWXU | S_IRWXG | S_IRWXO) == 0);
+ assert(stat(subdirectory, &s) == 0 && S_ISDIR(s.st_mode));
+
+ free(subdirectory);
+ test_ok();
+ }
+}
+#endif
+
+#ifdef TEST
+static void unit_tests() {
+ test_get_profile_file();
+ test_mkdir_p();
+}
+#endif
+
+int main(int argc, char *argv[]) {
+#ifdef TEST
+ unit_tests();
+ return EXIT_SUCCESS;
+#endif
+
+ for (int i = 0; i < argc; i++) {
+ if (strcmp("--", argv[i]) == 0) {
+ break;
+ } else if (strcmp("--help", argv[i]) == 0) {
+ if (usage(stdout)) return EXIT_ERROR;
+ if (help(stdout)) return EXIT_ERROR;
+ return EXIT_SUCCESS;
+ } else if (strcmp("--version", argv[i]) == 0) {
+ if (version(stdout)) return EXIT_ERROR;
+ return EXIT_SUCCESS;
+ }
+ }
+
+ int option;
+ const char *profile = NULL;
+ const char *command = NULL;
+ while ((option = getopt(argc, argv, "p:c:hV")) != -1) {
+ switch (option) {
+ case 'p':
+ profile = optarg;
+ break;
+ case 'c':
+ command = optarg;
+ break;
+ case 'h':
+ if (usage(stdout)) return EXIT_ERROR;
+ if (help(stdout)) return EXIT_ERROR;
+ return EXIT_SUCCESS;
+ case 'V':
+ if (version(stdout)) return EXIT_ERROR;
+ return EXIT_SUCCESS;
+ default:
+ if (usage(stderr)) return EXIT_ERROR;
+ return EXIT_USAGE;
+ }
+ }
+
+ if (!profile) {
+ if (missing(stderr, "-p PROFILE")) return EXIT_ERROR;
+ if (usage(stderr)) return EXIT_ERROR;
+ return EXIT_USAGE;
+ }
+
+ if (!command) {
+ if (missing(stderr, "-c COMMAND")) return EXIT_ERROR;
+ if (usage(stderr)) return EXIT_ERROR;
+ return EXIT_USAGE;
+ }
+
+ /* End getopt() */
+
+
+ int ret = EXIT_SUCCESS;
+
+ char *profile_file = NULL;
+ if (!(profile_file = get_profile_file(profile, stderr))) {
+ ret = EXIT_ERROR;
+ goto cleanup;
+ }
+
+ char *profile_file_dup = NULL;
+ if (!(profile_file_dup = strdup(profile_file))) {
+ perror("main() - strdup()");
+ ret = EXIT_ERROR;
+ goto cleanup;
+ }
+
+ if ((ret = mkdir_p(dirname(profile_file_dup), S_IRWXU | S_IRWXG | S_IRWXO))) {
+ goto cleanup;
+ }
+
+ printf("profile: %s\ncommand: %s\n", profile, command);
+ printf("profile_file: %s\n", profile_file);
+
+
+cleanup:
+ free(profile_file_dup);
+ free(profile_file);
+ return ret;
+}