#ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif int main() { return 0; } #ifdef DISABLE #include #include #include #include #include #include #include #ifdef TEST #include static void testing(const char *message) { fprintf(stderr, "testing: %s...", message); } void test_ok() { fprintf(stderr, " OK.\n"); } #endif #ifdef FALLIBLE #include #endif static void print_usage(FILE *stream, char *given_name) { char *name = "remembering"; if (given_name && given_name[0]) { name = given_name; } fprintf(stream, "Usage: %s -p PROFILE -c 'COMMAND'\n", name); } static void print_help(FILE *stream) { const char *help_text = "\n" "Options:\n" " -p PROFILE profile to be used for gathering and storing data\n" " -c 'COMMAND' command to be run, reading from STDIN, writing to " "STDOUT\n" " -h show this help\n" " -V print program version\n" "See manpages for more information.\n"; fprintf(stream, "%s", help_text); } static void print_missing(FILE *stream, char *text) { fprintf(stream, "Missing option: %s\n", text); } static int get_options(int argc, char *argv[], char **command, char **profile) { for (int i = 0; i < argc; i++) { if (!strcmp(argv[i], "--help")) { print_usage(stdout, argv[0]); print_help(stdout); exit(EXIT_SUCCESS); } } char *profilearg = NULL; char *commandarg = NULL; int option; while ((option = getopt(argc, argv, "c:p:hV")) != -1) { switch (option) { case 'c': commandarg = optarg; break; case 'p': profilearg = optarg; break; case 'h': print_usage(stdout, argv[0]); print_help(stdout); exit(EXIT_SUCCESS); case 'V': fprintf(stdout, "remembering-%s %s\n", VERSION, DATE); exit(EXIT_SUCCESS); } } if (commandarg == NULL) { print_missing(stderr, "-c 'COMMAND'"); print_usage(stderr, argv[0]); return 2; } if (profilearg == NULL) { print_missing(stderr, "-p 'PROFILE'"); print_usage(stderr, argv[0]); return 2; } *command = malloc(strlen(commandarg) + 1); if (!*command) { return 1; } *profile = malloc(strlen(profilearg) + 1); if (!*profile) { free(*command); return 1; } strcpy(*command, commandarg); strcpy(*profile, profilearg); return 0; } static int mkdir_p(char *path, mode_t mode) { struct stat s; if (stat(path, &s) == 0 && S_ISDIR(s.st_mode)) { return 0; } char *parent = dirname(path); int ret = mkdir_p(parent, mode); if (ret) { return ret; } return mkdir(path, mode); } // add fallible variation? where are the tests to stress it? static char *expand_profile_name(const char *const profile_name) { char *prefix = NULL; char *env_prefix = getenv("XDG_DATA_HOME"); if (env_prefix) { prefix = strdup(env_prefix); if (!prefix) { return NULL; } } else { char *home = getenv("HOME"); char *path = "/.local/share/remembering"; prefix = malloc(strlen(home) + strlen(path) + 1); if (!prefix) { return NULL; } strcpy(prefix, home); strcat(prefix, path); } char *separator = "/"; char *expanded_profile = malloc(strlen(prefix) + strlen(separator) + strlen(profile_name) + 1); if (!expanded_profile) { free(prefix); return NULL; } strcpy(expanded_profile, prefix); strcat(expanded_profile, separator); strcat(expanded_profile, profile_name); free(prefix); errno = 0; int ret = mkdir_p(dirname(expanded_profile), 0755); if (ret) { return NULL; } return expanded_profile; } static const size_t RANKINGS_INITIAL_SIZE = 100; static const int RANKINGS_GROWTH_MULTIPLIER = 2; struct Rankings { size_t count; size_t size; char **values; int *ranks; }; static struct Rankings *rankings_new() { struct Rankings *r = malloc(sizeof(struct Rankings)); if (!r) { return NULL; } r->count = 0; r->size = RANKINGS_INITIAL_SIZE; r->values = malloc(r->size * sizeof(char *)); if (!r->values) { free(r); return NULL; } r->ranks = malloc(r->size * sizeof(int)); if (!r->ranks) { free(r->values); free(r); return NULL; } return r; } #ifdef FALLIBLE static struct Rankings *fallible_rankings_new(const char *const filename, int lineno) { if (fallible_should_fail(filename, lineno)) { return NULL; } return rankings_new(); } #define rankings_new() fallible_rankings_new(__FILE__, __LINE__) #endif static void rankings_free(struct Rankings *r) { if (!r) { return; } for (size_t i = 0; i < r->count; i++) { free(r->values[i]); } free(r->values); free(r->ranks); free(r); } static int rankings_insert(struct Rankings *r, char *value, int rank) { if (r->count == r->size) { r->size *= RANKINGS_GROWTH_MULTIPLIER; char **new_values = realloc(r->values, r->size * sizeof(char *)); if (!new_values) { return -1; } r->values = new_values; int *new_ranks = realloc(r->ranks, r->size * sizeof(int)); if (!new_ranks) { return -1; } r->ranks = new_ranks; } r->values[r->count] = value; r->ranks[r->count] = rank; r->count++; return 0; } #ifdef FALLIBLE static int fallible_rankings_insert(struct Rankings *r, char *value, int rank, const char *const filename, int lineno) { if (fallible_should_fail(filename, lineno)) { return -1; } return rankings_insert(r, value, rank); } #define rankings_insert(r, value, rank) fallible_rankings_insert(r, value, rank, __FILE__, __LINE__) #endif #ifdef TEST static void rankings_test() { { testing("struct Rankings expands its size when needed"); struct Rankings *r = rankings_new(); size_t some_limit = RANKINGS_INITIAL_SIZE + 1; int ret; for (size_t i = 0; i < some_limit; i++) { ret = rankings_insert(r, strdup("some string"), i); /* char *s = strdup("some string"); ret = rankings_insert(r, s, i); if (ret) { free(s); rankings_free(r); exit(EXIT_FAILURE); } */ } assert(r->size == RANKINGS_INITIAL_SIZE * RANKINGS_GROWTH_MULTIPLIER); assert(r->count == some_limit); rankings_free(r); test_ok(); } { testing("an empty Rankings doesn't leak"); struct Rankings *r = rankings_new(); assert(r->size == RANKINGS_INITIAL_SIZE); assert(r->count == 0); rankings_free(r); test_ok(); } } #endif static const char RANKING_DELIMITER = ':'; int parse_ranked_line(FILE *stream, const char *entry, char **value, int *rank) { char *value_substr = strchr(entry, RANKING_DELIMITER); if (value_substr == NULL) { fprintf(stream, "WARN: Missing delimiter ('%c') in line: %s\n", RANKING_DELIMITER, entry); return -1; } int rank_strlen = value_substr - entry; char *rank_str = malloc(rank_strlen + 1); strncpy(rank_str, entry, rank_strlen); rank_str[rank_strlen] = '\0'; *rank = atoi(rank_str); free(rank_str); *value = malloc(strlen(value_substr) + 1); strcpy(*value, entry + (rank_strlen + 1 /* RANKING_DELIMITER */)); return 0; } #ifdef FALLIBLE int fallible_parse_ranked_line(FILE *stream, const char *entry, char **value, int *rank, const char *const filename, int lineno) { if (fallible_should_fail(filename, lineno)) { return -1; } return parse_ranked_line(stream, entry, value, rank); } #define parse_ranked_line(stream, entry, value, rank) fallible_parse_ranked_line(stream, entry, value, rank, __FILE__, __LINE__) #endif #ifdef TEST void parse_ranked_line_test() { { testing("parse_ranked_line with an empty string"); char *value; int rank; char filename[] = "remembering-test.XXXXXX"; int fd; if ((fd = mkstemp(filename)) == -1) { fprintf(stderr, "\nERR: Can't create test tempfile.\n"); exit(EXIT_FAILURE); } FILE *f = fdopen(fd, "w"); int ret = parse_ranked_line(f, "", &value, &rank); assert(ret == -1); fclose(f); test_ok(); } { testing("parse_ranked_line when RANKING DELIMITER is missing"); char *value; int rank; char filename[] = "remembering-test.XXXXXX"; int fd; if ((fd = mkstemp(filename)) == -1) { fprintf(stderr, "\nERR: Can't create test tempfile.\n"); exit(EXIT_FAILURE); } FILE *f = fdopen(fd, "w"); int ret = parse_ranked_line(f, "0 command", &value, &rank); assert(ret == -1); fclose(f); test_ok(); } { testing("parse_ranked_line with a happy path examples"); char *value; int rank; int ret; ret = parse_ranked_line(stderr, "0:command", &value, &rank); assert(ret == 0); assert(strcmp(value, "command") == 0); assert(rank == 0); free(value); ret = parse_ranked_line(stderr, "10:another command", &value, &rank); assert(ret == 0); assert(strcmp(value, "another command") == 0); assert(rank == 10); free(value); ret = parse_ranked_line(stderr, "123:123", &value, &rank); assert(ret == 0); assert(strcmp(value, "123") == 0); assert(rank == 123); free(value); ret = parse_ranked_line(stderr, "0:0", &value, &rank); assert(ret == 0); assert(strcmp(value, "0") == 0); assert(rank == 0); free(value); ret = parse_ranked_line(stderr, "0:command with : in the middle", &value, &rank); assert(ret == 0); assert(strcmp(value, "command with : in the middle") == 0); assert(rank == 0); free(value); ret = parse_ranked_line(stderr, "0:::command:with:multiple:::in:the:middle:", &value, &rank); assert(ret == 0); assert(strcmp(value, "::command:with:multiple:::in:the:middle:") == 0); assert(rank == 0); free(value); test_ok(); } { testing("parse_ranked_line with an empty command"); char *value; int rank; int ret = parse_ranked_line(stderr, "0:", &value, &rank); assert(ret == 0); assert(strcmp(value, "") == 0); assert(rank == 0); free(value); test_ok(); } { testing("parse_ranked_line with a bad rank numbers"); char *value; int rank; int ret; ret = parse_ranked_line(stderr, ":command", &value, &rank); assert(ret == 0); assert(strcmp(value, "command") == 0); assert(rank == 0); free(value); ret = parse_ranked_line(stderr, "1 2 3:command", &value, &rank); assert(ret == 0); assert(strcmp(value, "command") == 0); assert(rank == 1); free(value); ret = parse_ranked_line(stderr, ".1:command", &value, &rank); assert(ret == 0); assert(strcmp(value, "command") == 0); assert(rank == 0); free(value); ret = parse_ranked_line(stderr, ":5:command", &value, &rank); assert(ret == 0); assert(strcmp(value, "5:command") == 0); assert(rank == 0); free(value); ret = parse_ranked_line(stderr, "command:command", &value, &rank); assert(ret == 0); assert(strcmp(value, "command") == 0); assert(rank == 0); free(value); test_ok(); } } #endif struct GetlineParams { char *line; size_t len; ssize_t read; FILE *stream; }; int get_stdin(struct GetlineParams *s_params, struct GetlineParams *p_params, struct Rankings *p_rankings) { s_params->read = getline(&s_params->line, &s_params->len, s_params->stream); if (s_params->read == -1) { while ((p_params->read = getline(&p_params->line, &p_params->len, p_params->stream)) != -1) { char *value; int rank; int ret; if ((ret = parse_ranked_line(stderr, p_params->line, &value, &rank)) == -1) { return ret; } if ((ret = rankings_insert(p_rankings, value, rank)) == -1) { return ret; } } return 1; } return 0; } int get_profile(struct GetlineParams *s_params, struct GetlineParams *p_params, struct Rankings *s_rankings, struct Rankings *p_rankings) { p_params->read = getline(&p_params->line, &p_params->len, p_params->stream); if (p_params->read == -1) { while ((s_params->read = getline(&s_params->line, &s_params->len, s_params->stream)) != -1) { int len = strlen(s_params->line) + 1; char *s_value_copy = malloc(len); if (!s_value_copy) { return -1; } char *p_value_copy = malloc(len); if (!p_value_copy) { free(s_value_copy); return -1; } strcpy(s_value_copy, s_params->line); strcpy(p_value_copy, s_params->line); int ret; if ((ret = rankings_insert(s_rankings, s_value_copy, 0)) == -1) { free(s_value_copy); free(p_value_copy); return -1; } // profile grows with stdin entries if ((ret = rankings_insert(p_rankings, p_value_copy, 0)) == -1) { free(s_value_copy); free(p_value_copy); return -1; } } return 1; } return 0; } /* int merge_stdin_with_profile(char *profile_name, struct Rankings *s_rankings, struct Rankings *p_rankings) { FILE *profile = fopen(profile_name, "r"); struct GetlineParams s_params = { .line = NULL, .len = 0, .read = 0, .stream = stdin}; struct GetlineParams p_params = { .line = NULL, .len = 0, .read = 0, .stream = profile}; int stop = get_stdin(&s_params, &p_params, p_rankings) || get_profile(&s_params, &p_params, s_rankings, p_rankings); while (!stop) { struct Tuple t; parse_ranked_line(p_params.line, &t); int cmp = strcmp(s_params.line, t.first); if (cmp == 0) { rankings_insert(s_rankings, t.first, t.second); rankings_insert(p_rankings, t.first, t.second); stop = get_stdin(&s_params, &p_params, p_rankings) || get_profile(&s_params, &p_params, s_rankings, p_rankings); tuple_free(&t); continue; } if (cmp < 0) { char *value_copy = malloc(strlen(s_params.line) + 1); strcpy(value_copy, s_params.line); int *rank = malloc(sizeof(int)); *rank = 0; rankings_insert(s_rankings, value_copy, rank); rankings_insert(p_rankings, value_copy, rank); stop = get_stdin(&s_params, &p_params, p_rankings); tuple_free(&t); continue; } if (cmp > 0) { rankings_insert(p_rankings, t.first, t.second); stop = get_profile(&s_params, &p_params, s_rankings, p_rankings); tuple_free(&t); continue; } } free(s_params.line); free(p_params.line); fclose(profile); return 0; } */ #ifdef TEST void unit_tests() { rankings_test(); parse_ranked_line_test(); } #endif int main(int argc, char *argv[]) { #ifdef TEST unit_tests(); return 0; #endif int ret; char *command; char *profile; // FIXME: single line ret = get_options(argc, argv, &command, &profile); if (ret) { goto err; expand_profile_name("oijoI"); } err: return ret; } #endif