#include #include #include #include #include #include #include #include #define LINE_MAX UINT16_MAX // appended to the input directory to get the path to the feed's config const char* fmjconfp = ".fmjconf"; const char* fmj_extension = ".fmj"; // .fmjconf and .fmj option names const char* description = "description"; const char* title = "title"; const char* link = "link"; const char* default_author = "default_author"; const char* author = "author"; const char* category = "category"; const char* comments = "comments"; const char* pub_date = "pub_date"; // global for the default_author from the .fmjconf char* default_author_name = NULL; void print_usage(void) { puts("usage:\nfmj dir [output file]"); } // caller must free returned ptr char* join_path(const char* a, const char* b) { char* buf = malloc(strlen(a) + strlen(b) + 2); strcpy(buf, a); strncat(buf, "/", 2); strncat(buf, b, strlen(b) + 1); return buf; } // parses an option from `line` that is formatted like this: // "title = contents" // // it will ignore the whitespace around the "=", // and return a pointer to the beginning of "contents", // or return NULL if it didn't match. const char* parse_option(const char* line, const char* option) { size_t optlen = strlen(option); // if it doesn't have an "=" and then at least one // char of content then its not valid if (strlen(line) < optlen + 2) { return NULL; } if (strncmp(line, option, optlen) != 0) { return NULL; } const char* cur = line + optlen; bool foundequals = false; while (true) { if (isblank(*cur)) { cur++; continue; } if (!foundequals && *cur == '=') { foundequals = true; cur++; continue; } // empty option is invalid if (foundequals && *cur != '\n') { break; } // at this point any valid cases have been exhausted return NULL; } return cur; } void write_without_newline(const char* content, FILE* output) { while (*content != '\0' && *content != '\n') { fputc(*content, output); content++; } } /* broken old version with out of bounds read because of `||` xd void write_without_newline(FILE* output, const char* content) { while (*content != '\0' || *content != '\n') { fputc(*content, output); content++; } }*/ void write_feed_header(FILE* output, FILE* fmjconf) { fputs("", output); bool found_title = false; bool found_link = false; bool found_description = false; bool found_default_author = false; char buf[LINE_MAX]; if (fmjconf != NULL) { while (fgets(buf, LINE_MAX, fmjconf) != NULL) { const char* opt; if ((opt = parse_option(buf, title)) != NULL && !found_title) { fputs("", output); write_without_newline(opt, output); fputs("", output); found_title = true; continue; } if ((opt = parse_option(buf, link)) != NULL && !found_link) { fputs("", output); write_without_newline(opt, output); fputs("", output); found_link = true; continue; } if ((opt = parse_option(buf, description)) != NULL && !found_description) { fputs("", output); write_without_newline(opt, output); fputs("", output); found_description = true; continue; } if ((opt = parse_option(buf, default_author)) != NULL && !found_default_author) { default_author_name = malloc(strlen(opt) + 1); strcpy(default_author_name, opt); found_default_author = true; continue; } } } if (!found_title) { fputs("fmj generated feed", output); } if (!found_link) { fputs("http://example.com/", output); } if (!found_description) { fputs("an rss feed generated by fmj", output); } fputs("fmj", output); } void write_feed_item(FILE* output, const char* dir, const char* entryname) { if (dir == NULL || entryname == NULL) { return; } // only consider .fmj files size_t namelen = strlen(entryname); size_t extlen = strlen(fmj_extension); if (namelen < extlen) { return; } if (strncmp(entryname + namelen - extlen, fmj_extension, extlen) != 0) { return; } char* path = join_path(dir, entryname); FILE* file = fopen(path, "r"); if (file == NULL) { free(path); return; } char buf[LINE_MAX]; // make sure it has either a title or description bool has_requirements = false; while (fgets(buf, LINE_MAX, file) != NULL) { if (parse_option(buf, title) || parse_option(buf, description)) { has_requirements = true; break; } } if (!has_requirements) { return; } rewind(file); bool found_title = false; bool found_description = false; bool found_link = false; bool found_pub_date = false; bool found_author = false; fputs("", output); while (fgets(buf, LINE_MAX, file) != NULL) { const char* opt; if ((opt = parse_option(buf, title)) != NULL && !found_title) { fputs("", output); write_without_newline(opt, output); fputs("", output); } if ((opt = parse_option(buf, link)) != NULL && !found_link) { fputs("", output); write_without_newline(opt, output); fputs("", output); } if ((opt = parse_option(buf, description)) != NULL && !found_description) { fputs("", output); write_without_newline(opt, output); fputs("", output); } if ((opt = parse_option(buf, author)) != NULL && !found_author) { fputs("", output); write_without_newline(opt, output); fputs("", output); } if ((opt = parse_option(buf, pub_date)) != NULL && !found_pub_date) { fputs("", output); write_without_newline(opt, output); fputs("", output); } if ((opt = parse_option(buf, category)) != NULL) { fputs("", output); write_without_newline(opt, output); fputs("", output); } } if (!found_author && default_author_name != NULL) { fputs("", output); write_without_newline(default_author_name, output); fputs("", output); } fputs("", output); free(path); } void write_feed_footer(FILE* output) { fputs("", output); } int main(int argc, char** argv) { if (argc < 2) { print_usage(); return 1; } DIR* dir = opendir(argv[1]); if (dir == NULL) { puts("failed to open the input directory!!!"); return 1; } char* outfilepath = "rss.xml"; if (argc >= 3) { outfilepath = argv[2]; } FILE* output; if (*outfilepath == '-') { output = stdout; } else { output = fopen(outfilepath, "w"); if (output == NULL) { puts("failed to open output file!!!!"); return 1; } } char* fmjconfpath = join_path(argv[1], fmjconfp); FILE* fmjconf = fopen(fmjconfpath, "r"); write_feed_header(output, fmjconf); if (fmjconf != NULL) { fclose(fmjconf); } free(fmjconfpath); struct dirent* entry; while (true) { errno = 0; entry = readdir(dir); if (entry != NULL) { write_feed_item(output, argv[1], entry->d_name); } else { if (errno == 0) { closedir(dir); break; } // i am going to assume getting here is bad and exit puts("assertion failed, readdir errored."); return 1; } } write_feed_footer(output); if (output != stdout) { fclose(output); } }