#include #include #include #include #include #include #include #include #define BOOKI_FILE_REL ".local/share/booki/books.toml" #define MAX_SEARCH_OPTS 5 // STRUCTS struct es { int len; char* ptr; }; struct es default_es = { 0, NULL }; struct Book { struct es id; struct es title; struct es author; int pages; struct es isbn; struct es language; struct es translator; int published; // maybe? //struct es authors[MAX_AUTHORS]; //struct es translators[MAX_TRANSLATORS]; }; struct Book new_book() { struct Book book; book.id = default_es; book.title = default_es; book.author = default_es; book.pages = 0; book.isbn = default_es; struct es language = { 7, "English" }; // default language book.language = language; book.translator = default_es; book.published = 0; return book; } char* BOOKI_FILE; int PARSE_LINE = 1; 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; } struct Book parse_book(char* current_pos) { struct Book book = new_book(); char* attr; char* value; char c = *current_pos; // loop until we hit the extra newline while (c != '\n') { // we start at the beginning of a line attr = current_pos; while ((c = *current_pos) != '=') { current_pos++; } // attr should be the name of the attribute, with (possibly) trailing spaces if (strncmp(attr, "title", 5) == 0) { // leading spaces while ((c = *current_pos) != '"') { current_pos++; } // go past the quote and set the position of the start of value current_pos++; value = current_pos; // until the next quote while ((c = *current_pos) != '"') { current_pos++; } // set the attribute struct es title; title.len = current_pos - value; title.ptr = value; book.title = title; } else if (strncmp(attr, "author", 6) == 0) { // leading spaces while ((c = *current_pos) != '"') { current_pos++; } // go past the quote and set the position of the start of value current_pos++; value = current_pos; // until the next quote while ((c = *current_pos) != '"') { current_pos++; } // set the attribute struct es author; author.len = current_pos - value; author.ptr = value; book.author = author; } // go to (and then past) the newline while ((c = *current_pos) != '\n') { current_pos++; } PARSE_LINE += 1; c = *(++current_pos); } return book; } char* next_book(char* current_pos) { char c; while(*current_pos) { // first opening bracket c = *current_pos++; if (c != '[') { continue; } // second opening bracket c = *current_pos++; if (c != '[') { continue; } if (strncmp(current_pos, "books", 5) != 0) { printf("thought i had it, but it's not a book\n"); continue; } current_pos += 5; // seek past the closing brackets and the next newline current_pos += 2; if (*current_pos != '\n') { printf("expecting a newline...\n"); continue; } PARSE_LINE += 1; current_pos++; return current_pos; } return NULL; } bool regex_match(const char* pattern, const struct es text) { // empty pattern matches everything if (!*pattern) return true; // get lengths int pattern_length = strlen(pattern); if (pattern_length > text.len) 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.len - pattern_length); i++) { if (strncasecmp(pattern, text.ptr + i, pattern_length) == 0) return true; } return false; } char* load_file(char* filename) { // open the file FILE* fp = fopen(filename, "r"); if (!fp) { printf("bad file\n"); return NULL; } // seek to the end fseek(fp, 0, SEEK_END); int size = ftell(fp); rewind(fp); char* data = malloc(size); if (!data) { printf("couldn't malloc\n"); return NULL; } int read_size = fread(data, 1, size, fp); if (read_size != size) { printf("didn't read everything -- %d read of %d\n", read_size, size); return NULL; } fclose(fp); return data; } 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; } void print_book(struct Book book, bool all_fields) { printf("%.*s by %.*s\n", book.title.len, book.title.ptr, book.author.len, book.author.ptr); if (all_fields) { char* esfmt = " - %s: %.*s\n"; char* intfmt = " - %s: %d\n"; if (book.isbn.ptr) printf(esfmt, "isbn", book.isbn.len, book.isbn.ptr); if (book.pages != 0) printf(intfmt, "pages", book.pages); if (book.published) printf(esfmt, "published", book.published); if (book.language.ptr) printf(esfmt, "language", book.language.len, book.language.ptr); if (book.translator.ptr) printf(esfmt, "translator", book.translator.len, book.translator.ptr); } } char** search(int argc, char* argv[]) { struct search_opt search_opts = parse_search_options(argc, argv); bool print = false; // get the books array char* data = load_file(BOOKI_FILE); if (!data) { printf("no such array: 'books'"); return NULL; } // book loop struct Book book; char* cur_data = data; while ((cur_data = next_book(cur_data)) != NULL) { book = parse_book(cur_data); char* field; int i; bool print = true; for (i = 0; i < search_opts.count; i++) { field = search_opts.opts[i]; // compare fields if (strcmp(field, "title") == 0) { if (!regex_match(search_opts.args[i], book.title)) break; } else if (strcmp(field, "author") == 0) { if (!regex_match(search_opts.args[i], book.author)) break; } else { printf("unsupported field: %s\n", field); break; } } print = i == search_opts.count; if (print) { print_book(book, search_opts.show); } } 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; }