#include #include #include #include #include #include #include #include #include "toml.h" #define BOOKI_FILE_REL ".local/share/booki/books.toml" #define MAX_SEARCH_OPTS 5 char* BOOKI_FILE; const char* get_last_word(const char* str) { const char* last_space = strrchr(str, ' '); if ((last_space - str) > 0) return last_space + 1; else return 0; } bool regex_match(const char* pattern, const char* text) { // empty pattern matches everything if (!*pattern) return true; // get lengths int pattern_length = strlen(pattern); int text_length = strlen(text); if (pattern_length > text_length) return false; // we only need to compare while remaining text is // as long or longer than pattern (without special) for (int i = 0; i <= (text_length - pattern_length); i++) { if (strncasecmp(pattern, text + i, pattern_length) == 0) return true; } return false; } toml_table_t* get_toml_data(const char* filename) { FILE* fp; char errbuf[200]; fp = fopen(filename, "r"); if (!fp) { printf("can't open file :("); return 0; } toml_table_t* conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); fclose(fp); return conf; } static struct option search_options[] = { {"show", no_argument, 0, 's'}, {0, 0, 0, 0} // marks the end of the array }; struct search_opt { int show; int count; char* opts[MAX_SEARCH_OPTS]; char* args[MAX_SEARCH_OPTS]; }; struct search_opt parse_search_options(int argc, char* argv[]) { // return struct struct search_opt opt_out; int count = 0; int show = false; // opt options int opt; int opt_idx = 0; opterr = 0; // turn off getopt error messages // look at each option while ((opt = getopt_long_only(argc, argv, "", search_options, &opt_idx)) != -1) { switch(opt) { case 's': show = true; break; case '?': // optind points at the argument (one past the option) opt_out.opts[count] = argv[optind-1] + 2; // '--example' -> 'example' opt_out.args[count] = argv[optind]; count++; break; default: printf("something went wrong with parsing!\n"); break; } } // set the count/show values opt_out.count = count; opt_out.show = show; return opt_out; } char** search(int argc, char* argv[]) { toml_table_t* data = get_toml_data(BOOKI_FILE); if (!data) { return NULL; } struct search_opt search_opts = parse_search_options(argc, argv); bool print = false; // get the books array toml_array_t* books = toml_array_in(data, "books"); if (!books) { printf("no such array: 'books'"); return NULL; } // book loop for (int i = 0; ; i++) { toml_table_t* book = toml_table_at(books, i); if (!book) break; if (search_opts.count == 0) { print = true; } else { toml_datum_t datapoint; char* field; bool inner_print = true; // loop through all options int i; for (i = 0; i < search_opts.count; i++) { field = search_opts.opts[i]; // if the key doesn't exist, we won't print if (!toml_key_exists(book, field)) { break; } // try and get a string datapoint = toml_string_in(book, field); if (datapoint.ok) { if (!regex_match(search_opts.args[i], datapoint.u.s)) { free(datapoint.u.s); break; } free(datapoint.u.s); } else { // try and get an integer datapoint = toml_int_in(book, field); int ret; if (datapoint.ok) { ret = atoi(search_opts.args[i]); if (ret == 0) { printf("not an int: %s\n", search_opts.args[i]); break; } else if (ret != datapoint.u.i) { break; } } else { break; } } } // if we made it all the way through the loop, // we should print print = i == search_opts.count; } // get title and author for printing toml_datum_t book_title = toml_string_in(book, "title"); if (!book_title.ok) { continue; } // author can be a string or a list toml_datum_t book_author = toml_string_in(book, "author"); char author_str[100]; char* tmp; if (!book_author.ok) { toml_array_t* book_author_array = toml_array_in(book, "author"); if (!book_author_array) { continue; } int len = toml_array_nelem(book_author_array); tmp = toml_string_at(book_author_array, 0).u.s; strcpy(author_str, tmp); free(tmp); for (int i = 1; i < len; i++) { book_author = toml_string_at(book_author_array, i); tmp = book_author.u.s; if (i == len-1) { strcat(author_str, " and "); } else { strcat(author_str, ", "); } strcat(author_str, book_author.u.s); free(tmp); } } else { strcpy(author_str, book_author.u.s); free(book_author.u.s); } // print! if (print) { printf("%s by %s\n", book_title.u.s, author_str); if (search_opts.show) { //toml_datum_t d; for (int i = 0; ; i++) { const char* key = toml_key_in(book, i); if (!key) break; /* if (strcmp("pages", key) == 0) { d = toml_int_in(book, key); if (d.ok) { printf(" - %s: %d\n", } } */ // currently we support strings and ints toml_datum_t s = toml_string_in(book, key); if (s.ok) { printf(" - %s: %s\n", key, s.u.s); free(s.u.s); } toml_datum_t i = toml_int_in(book, key); if (i.ok) { printf(" - %s: %d\n", key, i.u.i); } } } } free(book_title.u.s); //free(book_author.u.s); } toml_free(data); return argv + optind; } void open() { char* home = getenv("HOME"); if (!home) { printf("no home?\n"); home = "/"; // no way } char* editor = getenv("EDITOR"); if (!editor) editor = "nano"; pid_t pid; int status; switch ((pid = fork())) { case -1: printf("fork has failed!\n"); break; case 0: execlp(editor, editor, BOOKI_FILE, NULL); printf("child failed :(\n"); break; default: // wait for the child to finish pid = wait(&status); break; } } void help(bool err) { printf("booki! it's a thing\n"); if (err) { printf("you did something wrong\n"); } } int main(int argc, char* argv[]) { char* home = getenv("HOME"); char booki_file[strlen(home) + strlen(BOOKI_FILE_REL) + 1]; sprintf(booki_file, "%s/%s", home, BOOKI_FILE_REL); BOOKI_FILE = booki_file; char** remaining = NULL; if (argc == 1) { help(false); return 0; } else if (strcmp(argv[1], "open") == 0) { open(); } else if (strcmp(argv[1], "search") == 0) { remaining = search(argc - 1, argv + 1); } return 0; }