Compare commits

...

10 Commits

Author SHA1 Message Date
Nat 90f1b0d8dd
idk why this works but I can now build on debian :) 2023-06-20 09:15:05 -07:00
Nat c062aa8a2e
Fix minor typo in the README 2023-06-20 08:57:17 -07:00
Nat 7f78f1db3c
Make incoming link list display none if there's nothing 2023-01-20 18:48:32 -08:00
Nat 8dd7876ebc
Add version option 2023-01-20 18:43:52 -08:00
Nat c87debfbae
Improve docs and add install script 2023-01-20 17:33:47 -08:00
Nat db0b922e11
Implement actual arg parsing 2023-01-20 17:23:48 -08:00
Nat dd9a450042
Clean up stdout 2023-01-20 16:52:39 -08:00
Nat 6c402c771f
Fix #2
I love handling null terminated strings :))))

I have no idea how this didn't come up earlier
2023-01-20 16:45:08 -08:00
Nat 02c7492f74
Expand the hash table implementation
Our implementation now resizes automatically and supports hashing by
open addressing.
2023-01-20 16:11:19 -08:00
Nat f4e04b0dfd
Add support for named wikilinks 2023-01-20 15:43:53 -08:00
3 changed files with 187 additions and 92 deletions

View File

@ -3,7 +3,10 @@ calathea: calathea.c
if [ ! -d "$(BUILD_DIR)" ]; then\ if [ ! -d "$(BUILD_DIR)" ]; then\
mkdir $(BUILD_DIR);\ mkdir $(BUILD_DIR);\
fi fi
gcc -Wall -Wextra -pedantic -lm -lcmark calathea.c -o build/calathea gcc calathea.c -Wall -Wextra -pedantic -lm -lcmark -o build/calathea
install:
cp ./build/calathea /usr/bin/calathea
clean: clean:
rm build/* rm build/*

View File

@ -1,4 +1,4 @@
# calathea v0.1 # calathea v1.0-beta
calathea is a small C program used to generate static wikis. It takes a calathea is a small C program used to generate static wikis. It takes a
directory of Common Markdown pages with `[[wikilinks]]` and renders them to directory of Common Markdown pages with `[[wikilinks]]` and renders them to
@ -10,10 +10,10 @@ proved to be a serious trial by fire in learning how to effectively work with
zero-terminated strings. There will likely be memoryleaks and comments that zero-terminated strings. There will likely be memoryleaks and comments that
over explain every line of code. over explain every line of code.
## Features ## features
- [x] Rendering wikilinks - Rendering wikilinks
- [x] Ability to keep track of incoming links - Ability to keep track of incoming links
- [ ] Named wikilinks (i.e. `[[link title|actual page]]`) - Named wikilinks (i.e. `[[link title|actual page]]`)
## installation ## installation
This tool requires [cmark](https://github.com/commonmark/cmark) to be This tool requires [cmark](https://github.com/commonmark/cmark) to be
@ -21,6 +21,7 @@ installed. Once that's set up, clone the repository and run:
``` ```
$ make $ make
$ make install
``` ```
If GCC complains it can't find cmark, then try running `ldconfig`. If it If GCC complains it can't find cmark, then try running `ldconfig`. If it
@ -28,8 +29,27 @@ still doesn't work, then run `echo $LD_LIBRARY_PATH`. If it doesn't show
anything, you've got to add the right directory to the path, i.e., anything, you've got to add the right directory to the path, i.e.,
``` ```
$ export $LD_LIBRARY_PATH=/usr/local/lib64 $ export LD_LIBRARY_PATH=/usr/local/lib64
``` ```
Or wherever it got installed as per the output of running `make install` for Or wherever it got installed as per the output of running `make install` for
cmark. cmark.
## usage
To see a list of options, run:
```
$ calathea --help
```
Running calathea builds pages (by default, any filies in `./pages`) to HTML files outputted in the specified output directory (by default, `./build`). To do this, it renders the pages and inserts them into your template file (by default, `./template.html`).
When building the files, calathea will look for two pseudo-Moustache templates: `{{content}}` and `{{incoming}}`. `{{content}}` will be replaced with the page content and `{{incoming}}` will be replaced with an HTML `ul` list of links to all pages that link to the given page.
The first line of every file in your pages directory should just be the title of the page. For example:
```
Page Title
Here is some page content
```
The content of the above page will be "Here is some page content" and the page title will be "Page Title". You can then link to it from other pages with `[[page title]]`.

View File

@ -5,8 +5,11 @@
#include <dirent.h> #include <dirent.h>
#include <math.h> #include <math.h>
#include <ctype.h> #include <ctype.h>
#include <argp.h>
#include <cmark.h> #include <cmark.h>
static const char *VERSION = "1.0.0-beta.1";
// Structure defining the content and metadata of a single page // Structure defining the content and metadata of a single page
struct Page { struct Page {
char title[80]; char title[80];
@ -35,7 +38,7 @@ char * read_file(char *filename) {
rewind(file); // Go back to the beginning of the file rewind(file); // Go back to the beginning of the file
// Allocate enough space in our buffer to hold the entire file. // Allocate enough space in our buffer to hold the entire file.
char *buffer = malloc(fileLength); char *buffer = malloc(fileLength + 1);
if (buffer == NULL) { if (buffer == NULL) {
printf("Warning: Failed to allocate enough memory for %s\n", filename); printf("Warning: Failed to allocate enough memory for %s\n", filename);
@ -44,12 +47,19 @@ char * read_file(char *filename) {
} }
fread(buffer, 1, fileLength, file); fread(buffer, 1, fileLength, file);
buffer[fileLength] = 0;
fclose(file); fclose(file);
return buffer; return buffer;
} }
/*** Hash map implementation ***/ /*** Hash map implementation ***/
struct PageMap {
struct Page **pages;
int capacity;
int size;
};
int helper_hash_polynomial( int helper_hash_polynomial(
char string[], char string[],
int i, int i,
@ -65,33 +75,69 @@ int helper_hash_polynomial(
} }
} }
int hash_polynomial(int mapSize, char key[]) { int hash_polynomial(int capacity, char key[]) {
return helper_hash_polynomial(key, 0, strlen(key), mapSize, 0); return helper_hash_polynomial(key, 0, strlen(key), capacity, 0);
} }
void to_lower_case(char dest[], char str[]) { char * to_lower_case(char str[]) {
char * lower = malloc((strlen(str) + 1) * sizeof(char));
int i = 0; int i = 0;
for (; str[i] != '\0'; i++) { for (; str[i] != '\0'; i++) {
dest[i] = tolower(str[i]); lower[i] = tolower(str[i]);
} }
dest[i] = 0; lower[i] = 0;
return lower;
} }
void map_put(struct Page **map, int mapSize, char title[], struct Page *page) { struct PageMap * map(int capacity) {
char lowercased[80]; struct PageMap *map = malloc(sizeof(struct PageMap));
to_lower_case(lowercased, title); map->pages = calloc(capacity, sizeof(struct Page *));
map->capacity = capacity;
int index = hash_polynomial(mapSize, lowercased); map->size = 0;
map[index] = page; return map;
} }
struct Page * map_get(struct Page **map, int mapSize, char title[]) { void map_free(struct PageMap *map) {
char lowercased[80]; free(map->pages);
to_lower_case(lowercased, title); free(map);
}
int index = hash_polynomial(mapSize, lowercased); void map_put(struct PageMap *map, char title[], struct Page *page) {
struct Page *page = map[index]; char *lowercased = to_lower_case(title);
int index = hash_polynomial(map->capacity, lowercased);
while (map->pages[index] != NULL) index++;
map->pages[index] = page;
map->size++;
// Resize the map if we're running low on space
if (map->size/map->capacity > 0.75) {
map->pages = realloc(map->pages, map->capacity * 2);
for (int i = map->capacity; i < map->capacity * 2; i++) {
map->pages[i] = 0;
}
map->capacity *= 2;
}
free(lowercased);
}
struct Page * map_get(struct PageMap *map, char title[]) {
char *lowercased = to_lower_case(title);
int index = hash_polynomial(map->capacity, lowercased);
struct Page *page = map->pages[index];
char *lowercasedFoundTitle = to_lower_case(page->title);
while (strcmp(lowercasedFoundTitle, lowercased) != 0) {
page = map->pages[++index];
free(lowercasedFoundTitle);
lowercasedFoundTitle = to_lower_case(page->title);
}
free(lowercased);
free(lowercasedFoundTitle);
return page; return page;
} }
@ -174,46 +220,51 @@ char * substitute_string(char dest[], char sub[], char *start, char *end) {
return compiled; return compiled;
} }
int main(int argc, char *argv[]) { static struct argp_option options[] = {
char pagesLocation[256] = "./pages"; { "src", 's', "dir", 0, "Source directory of pages", 0 },
int mapSize = 1000; { "version", 'v', 0, 0, "Print the version", 0 },
int initialInboundCapacity = 2; { "template", 't', "file", 0, "Template file path", 0 },
char templateFileName[256] = "./template.html"; { "output", 'o', "dir", 0, "Output directory", 0 },
char outputDirectoryName[256] = "./build"; { 0 }
};
/*** Argument Parsing ***/ static char *pagesLocation = "./pages";
for (int i = 1; i < argc; i++) { static char *templateFileName = "./template.html";
if (strcmp(argv[i], "--pages") == 0) { static char *outputDirectoryName = "./build";
// We only want to do this if a directory was actually supplied
if (i + 1 < argc) { static int parse_opt(int key, char *arg, struct argp_state *state) {
i++; // Suppress unused parameter warnings
strcpy(pagesLocation, argv[i]); (void) state;
}
} else if (strcmp(argv[i], "--table-size") == 0) { switch (key) {
if (i + 1 < argc) { case 's': {
i++; pagesLocation = arg;
mapSize = atoi(argv[i]); break;
} }
} else if (strcmp(argv[i], "--template") == 0) { case 't': {
if (i + 1 < argc) { templateFileName = arg;
i++; break;
strcpy(templateFileName, argv[i]); }
} case 'o': {
} else if (strcmp(argv[i], "--output-dir") == 0) { outputDirectoryName = arg;
if (i + 1 < argc) { break;
i++; }
strcpy(outputDirectoryName, argv[i]); case 'v': {
} printf("v%s\n", VERSION);
} else if (strcmp(argv[i], "--incoming-cap") == 0) { exit(0);
if (i + 1 < argc) {
i++;
initialInboundCapacity = atoi(argv[i]);
}
} else {
printf("Unknown argument: %s\n", argv[i]);
} }
} }
return 0;
}
int main(int argc, char *argv[]) {
int initialInboundCapacity = 2;
struct argp argp = {options, parse_opt, 0, 0, 0, 0, 0 };
argp_parse(&argp, argc, argv, 0, 0, 0);
char *templateContent = read_file(templateFileName); char *templateContent = read_file(templateFileName);
if (templateContent == NULL) { if (templateContent == NULL) {
@ -230,8 +281,7 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
struct Page ** pageMap = malloc(mapSize * sizeof(struct Page *)); struct PageMap *pageMap = map(100);
memset(pageMap, 0, mapSize * sizeof(struct Page *));
// Contains some information about the current file picked from pagesDir // Contains some information about the current file picked from pagesDir
struct dirent *fileEntry = readdir(pagesDir); struct dirent *fileEntry = readdir(pagesDir);
@ -300,9 +350,7 @@ int main(int argc, char *argv[]) {
// Save the content string for later by mallocing it // Save the content string for later by mallocing it
char *contentBuffer = endOfFirstLine; char *contentBuffer = endOfFirstLine;
currentPage->content = malloc( currentPage->content = calloc(strlen(buffer), sizeof(char));
sizeof(char) * (strlen(buffer) - titleLength + 1)
);
strcpy(currentPage->content, contentBuffer); strcpy(currentPage->content, contentBuffer);
// Copy the first line (title) into its respective field // Copy the first line (title) into its respective field
@ -310,7 +358,7 @@ int main(int argc, char *argv[]) {
currentPage->title[min(titleLength, 80)] = 0; currentPage->title[min(titleLength, 80)] = 0;
// Insert it into the hash map for lookup later // Insert it into the hash map for lookup later
map_put(pageMap, mapSize, currentPage->title, currentPage); map_put(pageMap, currentPage->title, currentPage);
// Get ready to process the next page // Get ready to process the next page
fileEntry = readdir(pagesDir); fileEntry = readdir(pagesDir);
@ -329,7 +377,7 @@ int main(int argc, char *argv[]) {
} }
// Create the directory if it doesn't exist // Create the directory if it doesn't exist
char *createOutputDir = concat_strings(2, "mkdir ", outputDirectoryName); char *createOutputDir = concat_strings(3, "mkdir ", outputDirectoryName, " 2> /dev/null");
system(createOutputDir); system(createOutputDir);
free(createOutputDir); free(createOutputDir);
@ -350,13 +398,23 @@ int main(int argc, char *argv[]) {
int linkLength = nextLinkEnd - nextLinkStart; int linkLength = nextLinkEnd - nextLinkStart;
// Determine the exact title of the link char *nextVerticalBar = strchr(nextLinkStart, '|');
char title[linkLength - 3];
strncpy(title, nextLinkStart + 2, (linkLength - 2)*sizeof(char)); char *linkTitle = calloc(linkLength - 3, sizeof(char));
title[linkLength - 2] = 0; char *linkPageTitle = NULL;
struct Page *linkedPage = map_get(pageMap, mapSize, title); if (nextVerticalBar != NULL && nextVerticalBar < nextLinkEnd) {
// The link is in the from [[link title|page title]]
strncpy(linkTitle, nextLinkStart + 2, (nextVerticalBar - 1) - (nextLinkStart + 1));
linkPageTitle = calloc((nextLinkEnd - 2) - nextVerticalBar, sizeof(char));
strncpy(linkPageTitle, nextVerticalBar + 1, (nextLinkEnd - 1) - nextVerticalBar);
} else {
// The link is of the form [[page title]]
linkPageTitle = linkTitle;
strncpy(linkTitle, nextLinkStart + 2, (linkLength - 2)*sizeof(char));
}
struct Page *linkedPage = map_get(pageMap, linkPageTitle);
char *compiledLink = NULL; char *compiledLink = NULL;
@ -364,12 +422,12 @@ int main(int argc, char *argv[]) {
// i.e. the page does not exist // i.e. the page does not exist
compiledLink = concat_strings(3, compiledLink = concat_strings(3,
"<a class=\"calathea-404\" href=\"#\">", "<a class=\"calathea-404\" href=\"#\">",
title, linkTitle,
"</a>" "</a>"
); );
} else { } else {
page_list_insert(linkedPage->incoming, currentPage); page_list_insert(linkedPage->incoming, currentPage);
compiledLink = concat_strings(5, "[", title, "](", linkedPage->permalink, ")"); compiledLink = concat_strings(5, "[", linkTitle, "](", linkedPage->permalink, ")");
} }
char *newContent = substitute_string( char *newContent = substitute_string(
@ -378,6 +436,10 @@ int main(int argc, char *argv[]) {
currentPage->content = newContent; currentPage->content = newContent;
free(compiledLink); free(compiledLink);
if (linkTitle != linkPageTitle) {
free(linkPageTitle);
}
free(linkTitle);
// Move to the next chunk of the file // Move to the next chunk of the file
// NOTE: This is suboptimal, because we search for "[[" from the // NOTE: This is suboptimal, because we search for "[[" from the
@ -416,28 +478,38 @@ int main(int argc, char *argv[]) {
char *incomingTagStart = strstr(renderedPage, "{{incoming}}"); char *incomingTagStart = strstr(renderedPage, "{{incoming}}");
if (incomingTagStart != NULL) { if (incomingTagStart != NULL) {
// Build the incoming links list // Build the incoming links list
int incomingListSize = 37; // <ul class="calathea-incoming">\n</ul>\n // <ul class="calathea-incoming">\n</ul>\n
for (int i = 0; i < currentPage->incoming->length; i++) { int incomingListSize = 37;
// ` <li><a href=\"[permalink]\">[title]</a></li>\n` if (currentPage->incoming->length == 0) {
struct Page *page = currentPage->incoming->pages[i]; // ` <li>none</li>\n`
incomingListSize += 27 + strlen(page->title) + strlen(page->permalink); incomingListSize += 16;
} else {
for (int i = 0; i < currentPage->incoming->length; i++) {
// ` <li><a href=\"[permalink]\">[title]</a></li>\n`
struct Page *page = currentPage->incoming->pages[i];
incomingListSize += 27 + strlen(page->title) + strlen(page->permalink);
}
} }
char *incomingLinksList = malloc((incomingListSize + 1) * sizeof(char)); char *incomingLinksList = malloc((incomingListSize + 1) * sizeof(char));
memset(incomingLinksList, 0, (incomingListSize + 1) * sizeof(char)); memset(incomingLinksList, 0, (incomingListSize + 1) * sizeof(char));
strcpy(incomingLinksList, "<ul class=\"calathea-incoming\">\n"); strcpy(incomingLinksList, "<ul class=\"calathea-incoming\">\n");
for (int i = 0; i < currentPage->incoming->length; i++) { if (currentPage->incoming->length == 0) {
struct Page *page = currentPage->incoming->pages[i]; strcat(incomingLinksList, " <li>none</li>\n");
char *link = concat_strings(5, } else {
" <li><a href=\"", for (int i = 0; i < currentPage->incoming->length; i++) {
page->permalink, struct Page *page = currentPage->incoming->pages[i];
"\">", char *link = concat_strings(5,
page->title, " <li><a href=\"",
"</a></li>\n" page->permalink,
); "\">",
strcat(incomingLinksList, link); page->title,
free(link); "</a></li>\n"
);
strcat(incomingLinksList, link);
free(link);
}
} }
strcat(incomingLinksList, "</ul>\n"); strcat(incomingLinksList, "</ul>\n");
@ -471,6 +543,7 @@ int main(int argc, char *argv[]) {
currentPage = currentPage->next; currentPage = currentPage->next;
} }
printf("Pages built successfully in %s\n", outputDirectoryName);
/*** Deallocation and whatnot ***/ /*** Deallocation and whatnot ***/
currentPage = firstPage; currentPage = firstPage;
@ -484,8 +557,7 @@ int main(int argc, char *argv[]) {
} }
closedir(pagesDir); closedir(pagesDir);
free(templateContent); free(templateContent);
map_free(pageMap);
// Deallocate all the pages
return 0; return 0;
} }