#include #include #include #include #include #include #include // Structure defining the content and metadata of a single page struct Page { char title[80]; char *permalink; struct Page *next; char *content; }; /*** Helper functions ***/ int min(int x, int y) { return x > y ? y : x; } char * read_file(char *filename) { FILE *file = fopen(filename, "rb"); if (file == NULL) { printf("Warning: Failed to open %s\n", filename); return NULL; } // First, we calculate the length of the file fseek(file, 0, SEEK_END); // Traverse to the end of the file int fileLength = ftell(file); // Get the current position in the file rewind(file); // Go back to the beginning of the file // Allocate enough space in our buffer to hold the entire file. char *buffer = malloc(fileLength); if (buffer == NULL) { printf("Warning: Failed to allocate enough memory for %s\n", filename); fclose(file); return NULL; } fread(buffer, 1, fileLength, file); fclose(file); return buffer; } /*** Hash map implementation ***/ int helper_hash_polynomial( char string[], int i, int length, int tableSize, unsigned long long acc ) { if (i == length) { return acc % tableSize; } else { return helper_hash_polynomial(string, i + 1, length, tableSize, acc + string[i] * pow(33, length-i-1)); } } int hash_polynomial(int mapSize, char key[]) { return helper_hash_polynomial(key, 0, strlen(key), mapSize, 0); } void to_lower_case(char dest[], char str[]) { int i = 0; for (; str[i] != '\0'; i++) { dest[i] = tolower(str[i]); } dest[i] = 0; } void map_put(struct Page **map, int mapSize, char title[], struct Page *page) { char lowercased[80]; to_lower_case(lowercased, title); int index = hash_polynomial(mapSize, lowercased); map[index] = page; } struct Page * map_get(struct Page **map, int mapSize, char title[]) { char lowercased[80]; to_lower_case(lowercased, title); int index = hash_polynomial(mapSize, lowercased); struct Page *page = map[index]; return page; } /*** Templating ***/ char * substitute_string(char dest[], char sub[], char *start, char *end) { int startIndex = start - dest; int newLength = strlen(dest) - (end - start) + strlen(sub) + 1; char * compiled = malloc(newLength * sizeof(char)); compiled[0] = 0; strncpy(compiled, dest, startIndex); strcat(compiled, sub); strcat(compiled, end); return compiled; } int main(int argc, char *argv[]) { char pagesLocation[256] = "./pages"; int mapSize = 1000; char templateFileName[256] = "./template.html"; char outputDirectoryName[256] = "./build"; /*** Argument Parsing ***/ for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--pages") == 0) { // We only want to do this if a directory was actually supplied if (i + 1 < argc) { i++; strcpy(pagesLocation, argv[i]); } } else if (strcmp(argv[i], "--table-size") == 0) { if (i + 1 < argc) { i++; mapSize = atoi(argv[i]); } } else if (strcmp(argv[i], "--template") == 0) { if (i + 1 < argc) { i++; strcpy(templateFileName, argv[i]); } } else if (strcmp(argv[i], "--output-dir") == 0) { if (i + 1 < argc) { i++; strcpy(outputDirectoryName, argv[i]); } } else { printf("Unknown argument: %s\n", argv[i]); } } char *templateContent = read_file(templateFileName); if (templateContent == NULL) { // If no template is given, we'll just dump the rendered markdown into a // plaintext file templateContent = malloc(sizeof(char) * 12); strcpy(templateContent, "{{content}}\0"); } DIR *pagesDir = opendir(pagesLocation); if (pagesDir == NULL) { fprintf(stderr, "Unable to open directory: %s\n", pagesLocation); return 1; } struct Page ** pageMap = malloc(mapSize * sizeof(struct Page *)); memset(pageMap, 0, mapSize * sizeof(struct Page *)); // Contains some information about the current file picked from pagesDir struct dirent *fileEntry = readdir(pagesDir); struct Page *currentPage = malloc(sizeof(struct Page)); struct Page *firstPage = currentPage; while (fileEntry != NULL) { // Ignore hidden files, ".", and ".." on Unix if (fileEntry->d_name[0] == '.') { fileEntry = readdir(pagesDir); continue; } // Determine the base name of the file int filenameLength = strlen(fileEntry->d_name); char fileBasename[filenameLength]; memset(fileBasename, 0, filenameLength); unsigned char foundPoint = 0; for (int i = filenameLength - 1; i >= 0; i--) { if (foundPoint) { // Start writing the base name into the string fileBasename[i] = fileEntry->d_name[i]; } else if (fileEntry->d_name[i] == '.') { // Start writing on the next iteration foundPoint = 1; } else { // Write zeros where the extension would have been fileBasename[i] = 0; } } // Build the page's permalink currentPage->permalink = malloc(strlen(fileBasename) + 6); strcpy(currentPage->permalink, fileBasename); strcat(currentPage->permalink, ".html"); // Construct the relative path // The two accounts for the slash and the terminal zero char relativePath[strlen(pagesLocation) + filenameLength + 2]; memset(relativePath, 0, strlen(pagesLocation) + filenameLength + 2); strcpy(relativePath, pagesLocation); strcat(relativePath, "/"); strcat(relativePath,fileEntry->d_name); relativePath[strlen(pagesLocation) + filenameLength + 1] = 0; char *buffer = read_file(relativePath); if (buffer == NULL) { fileEntry = readdir(pagesDir); continue; } // Get a pointer to the start of the content part of the page char *endOfFirstLine = strchr(buffer, '\n'); if (endOfFirstLine == NULL) { printf("Warning: First line in %s/%s must be the title\n", pagesLocation, fileEntry->d_name); free(buffer); fileEntry = readdir(pagesDir); continue; } // We subtract the buffer pointer from the pointer to the end of the // first line to get the length of the title int titleLength = (endOfFirstLine - buffer)/sizeof(char); // Save the content string for later by mallocing it char *contentBuffer = endOfFirstLine; currentPage->content = malloc( sizeof(char) * (strlen(buffer) - titleLength + 1) ); strcpy(currentPage->content, contentBuffer); // Copy the first line (title) into its respective field strncpy(currentPage->title, buffer, min(titleLength, 80)); currentPage->title[min(titleLength, 80)] = 0; // Insert it into the hash map for lookup later map_put(pageMap, mapSize, currentPage->title, currentPage); // Get ready to process the next page fileEntry = readdir(pagesDir); if (fileEntry != NULL) { struct Page *nextPage = malloc(sizeof(struct Page)); // Swap the pages currentPage->next = nextPage; currentPage = nextPage; } else { currentPage->next = NULL; } free(buffer); } // Create the directory if it doesn't exist char *createOutputDir = malloc( (7 + strlen(outputDirectoryName)) * sizeof(char) ); strcpy(createOutputDir, "mkdir \0"); strcat(createOutputDir, outputDirectoryName); system(createOutputDir); free(createOutputDir); /*** Page Processing ***/ currentPage = firstPage; while (currentPage != NULL) { // Scan the file for links // This pointer is updated upon each iteration char *nextLinkStart = strstr(currentPage->content, "[["); while (nextLinkStart != NULL) { char *nextLinkEnd = strstr(nextLinkStart, "]]"); if (nextLinkEnd == NULL) { // This link is broken printf("Warning: \"%s\" contains a broken link", currentPage->title); break; } int linkLength = nextLinkEnd - nextLinkStart; // Determine the exact title of the link char title[linkLength - 3]; strncpy(title, nextLinkStart + 2, (linkLength - 2)*sizeof(char)); title[linkLength - 2] = 0; struct Page *linkedPage = map_get(pageMap, mapSize, title); char *compiledLink; if (linkedPage == NULL) { // i.e. the page does not exist compiledLink = malloc(strlen(title) + 38); strcpy(compiledLink, ""); strcat(compiledLink, title); strcat(compiledLink, "\0"); } else { compiledLink = malloc(strlen(title) + strlen(linkedPage->permalink) + 5); strcpy(compiledLink, "["); strcat(compiledLink, title); strcat(compiledLink, "]("); strcat(compiledLink, linkedPage->permalink); strcat(compiledLink, ")\0"); } char *newContent = substitute_string( currentPage->content, compiledLink, nextLinkStart, nextLinkEnd + 2); free(currentPage->content); currentPage->content = newContent; free(compiledLink); // Move to the next chunk of the file // NOTE: This is suboptimal, because we search for "[[" from the // beginning of the content at each iteration. We could start from // the nextLinkEnd pointer, but since the memory is reallocated, we'd // need to take into account that this pointer now points to a chunk // of deallocated memory nextLinkStart = strstr(currentPage->content, "[["); } // Compile the markdown currentPage->content = cmark_markdown_to_html( currentPage->content, strlen(currentPage->content), (1 << 17) // Disables safe mode, allowing raw HTML ); char *templateTagStart = strstr(templateContent, "{{content}}"); char *renderedPageContent = substitute_string( templateContent, currentPage->content, templateTagStart, templateTagStart + 11 ); // Output the page to the file char *outputFileName = malloc( (strlen(outputDirectoryName) + strlen(currentPage->permalink) + 2)); strcpy(outputFileName, outputDirectoryName); strcat(outputFileName, "/\0"); strcat(outputFileName, currentPage->permalink); FILE *outputFile = fopen(outputFileName, "w"); if (outputFile != NULL) { fputs(renderedPageContent, outputFile); fclose(outputFile); } else { printf("Warning: failed to create %s\n", outputFileName); } free(outputFileName); free(renderedPageContent); currentPage = currentPage->next; } /*** Deallocation and whatnot ***/ currentPage = firstPage; while (currentPage != NULL) { free(currentPage->content); free(currentPage->permalink); struct Page *next = currentPage->next; free(currentPage); currentPage = next; } closedir(pagesDir); free(templateContent); // Deallocate all the pages return 0; }