From 268809869b5edce73fa205ca3d9798ee43d73d33 Mon Sep 17 00:00:00 2001 From: EuAndreh Date: Thu, 4 Mar 2021 00:22:23 -0300 Subject: WIP: work on C implementation --- .gitignore | 1 + src/remembering.c | 469 ++++++++++++++++++++++++++++++++++++++---------------- 2 files changed, 332 insertions(+), 138 deletions(-) diff --git a/.gitignore b/.gitignore index 1b9cb6f..d8cd57a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ /remembering /remembering-c /remembering-sh +/remembering-test.* /run-tests /fallible* diff --git a/src/remembering.c b/src/remembering.c index 36c63f5..516cc01 100644 --- a/src/remembering.c +++ b/src/remembering.c @@ -8,9 +8,17 @@ int main() { return 0; } #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 @@ -42,17 +50,12 @@ static void print_missing(FILE *stream, char *text) { fprintf(stream, "Missing option: %s\n", text); } -struct Pair { - char *first; - char *second; -}; - -static int get_options(int argc, char *argv[], struct Pair *pair) { +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(0); + exit(EXIT_SUCCESS); } } @@ -70,10 +73,10 @@ static int get_options(int argc, char *argv[], struct Pair *pair) { case 'h': print_usage(stdout, argv[0]); print_help(stdout); - exit(0); + exit(EXIT_SUCCESS); case 'V': fprintf(stdout, "remembering-%s %s\n", VERSION, DATE); - exit(0); + exit(EXIT_SUCCESS); } } @@ -89,17 +92,36 @@ static int get_options(int argc, char *argv[], struct Pair *pair) { return 2; } - pair->first = malloc(strlen(commandarg) + 1); - pair->second = malloc(strlen(profilearg) + 1); - if (!pair->first || !pair->second) { + *command = malloc(strlen(commandarg) + 1); + if (!*command) { + return 1; + } + *profile = malloc(strlen(profilearg) + 1); + if (!*profile) { + free(*command); return 1; } - strcpy(pair->first, commandarg); - strcpy(pair->second, profilearg); + 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"); @@ -131,80 +153,22 @@ static char *expand_profile_name(const char *const profile_name) { free(prefix); - return expanded_profile; -} - -#ifdef TEST -char *set_test_home(const char *new_home) { - size_t size = _PC_PATH_MAX == -1 ? 4096 : _PC_PATH_MAX; - char *buf = malloc(size + 1); - if (!buf) { - return NULL; - } - - char *cwd = getcwd(buf, size); - if (!cwd) { - free(buf); - return NULL; - } - - const char *suffix = "/tests/test-profiles/"; - char *home = malloc(strlen(cwd) + strlen(suffix) + 1); - if (!new_home) { - free(buf); + errno = 0; + int ret = mkdir_p(dirname(expanded_profile), 0755); + if (ret) { return NULL; } - - strcpy(new_home, buf); - strcat(new_home, suffix); - - char *previous_home = getenv("HOME"); - setenv("HOME", new_home, 1); - return previous_home; -} - -void expand_profile_name_test() { - { - testing("expanding profile name without setting $XDG_DATA_HOME"); - // FIXME: this can fail - const char *new_home = sharedc_random_string("test-expand-profile-name-"); - const char *previous_home = set_test_home(new_home); - - int ret = setenv("HOME", previous_home, 1); - assert(ret == 0); - - char *n = expand_profile_name(new_home); - assert(n); - test_ok(); - } -} -#endif - -/* -static void ensure_profile(char *profile) { - char *prefix = dirname(profile); - struct stat sb; - if (!(stat(prefix, &sb) == 0 && S_ISDIR(sb.st_mode))) { - mkdir_p(prefix, 0755); - } - // FILE *f = fopen(profile, "w+"); - // fclose(f); -} - -#ifdef TEST -static void test_ensure_profile() { - { - testing("xablau"); - test_ok(); - } + return expanded_profile; } -#endif -static const char RANKING_DELIMITER = ':'; +static const size_t RANKINGS_INITIAL_SIZE = 100; +static const int RANKINGS_GROWTH_MULTIPLIER = 2; struct Rankings { - struct Vector *values; - struct Vector *ranks; + size_t count; + size_t size; + char **values; + int *ranks; }; static struct Rankings *rankings_new() { @@ -212,48 +176,98 @@ static struct Rankings *rankings_new() { if (!r) { return NULL; } - r->values = vector_new(); + r->count = 0; + r->size = RANKINGS_INITIAL_SIZE; + r->values = malloc(r->size * sizeof(char *)); if (!r->values) { free(r); return NULL; } - r->ranks = vector_new(); + r->ranks = malloc(r->size * sizeof(int)); if (!r->ranks) { - vector_free(r->values); + 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) { - vector_free(r->values); - vector_free(r->ranks); + 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 bool rankings_insert(struct Rankings *r, char *value, int *rank) { - return vector_push_back(r->values, value) && vector_push_back(r->ranks, rank); +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 test_rankings() { +static void rankings_test() { { - testing("struct Rankings grows values and ranks at the same rate"); + testing("struct Rankings expands its size when needed"); struct Rankings *r = rankings_new(); - int some_limit = 123; - for (int i = 0; i < some_limit; i++) { - char *s = strdup("some text"); - - int *p = malloc(sizeof(int)); - assert(p); - *p = i; - - rankings_insert(r, s, p); + 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->values->count == r->ranks->count); - assert(r->values->size == r->ranks->size); + assert(r->size == RANKINGS_INITIAL_SIZE * RANKINGS_GROWTH_MULTIPLIER); + assert(r->count == some_limit); rankings_free(r); test_ok(); @@ -262,55 +276,207 @@ static void test_rankings() { { 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 -/ * - -struct GetlineParams { - char *line; - size_t len; - ssize_t read; - FILE *stream; -}; +static const char RANKING_DELIMITER = ':'; -int parse_ranked_line(char *entry, struct Tuple *t) { +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(stderr, "Missing delimiter ('%c') in line:\n%s\n", + fprintf(stream, "WARN: Missing delimiter ('%c') in line: %s\n", RANKING_DELIMITER, entry); - return 1; + return -1; } - int ranking_chars_count = value_substr - entry; - char *ranking_chars = malloc(ranking_chars_count + 1); - strncpy(ranking_chars, entry, ranking_chars_count); - int *ranking = malloc(sizeof(int)); // how to handle fail to parse? - *ranking = atoi(ranking_chars); - free(ranking_chars); + 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_substr++; // drop included delimiter - char *value_copy = malloc(strlen(value_substr) + 1); - strcpy(value_copy, entry); - - t->first = value_copy; - t->second = ranking; + *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) { - struct Tuple t; - parse_ranked_line(p_params->line, &t); - rankings_insert(p_rankings, t.first, t.second); + 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; } @@ -323,19 +489,39 @@ int get_profile(struct GetlineParams *s_params, struct GetlineParams *p_params, if (p_params->read == -1) { while ((s_params->read = getline(&s_params->line, &s_params->len, s_params->stream)) != -1) { - 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); + 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"); @@ -388,7 +574,10 @@ int merge_stdin_with_profile(char *profile_name, struct Rankings *s_rankings, */ #ifdef TEST -void unit_tests() {} +void unit_tests() { + rankings_test(); + parse_ranked_line_test(); +} #endif int main(int argc, char *argv[]) { @@ -398,12 +587,16 @@ int main(int argc, char *argv[]) { #endif int ret; - struct Pair p; - ret = get_options(argc, argv, &p); + 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; } -- cgit v1.2.3