aboutsummaryrefslogtreecommitdiff
path: root/src/remembering.c
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2021-03-04 00:22:23 -0300
committerEuAndreh <eu@euandre.org>2021-03-04 00:22:23 -0300
commit268809869b5edce73fa205ca3d9798ee43d73d33 (patch)
treeae0528202251cf5873f3970cdbbb920fd3bc0a64 /src/remembering.c
parentTODOs.md: Add #task-9291a8c1-4ac3-409d-b490-872b29a719cc (diff)
downloadremembering-268809869b5edce73fa205ca3d9798ee43d73d33.tar.gz
remembering-268809869b5edce73fa205ca3d9798ee43d73d33.tar.xz
WIP: work on C implementation
Diffstat (limited to 'src/remembering.c')
-rw-r--r--src/remembering.c469
1 files changed, 331 insertions, 138 deletions
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 <stdlib.h>
#include <string.h>
#include <unistd.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <libgen.h>
#ifdef TEST
#include <assert.h>
+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;
}