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\
mkdir $(BUILD_DIR);\
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:
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
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
over explain every line of code.
## Features
- [x] Rendering wikilinks
- [x] Ability to keep track of incoming links
- [ ] Named wikilinks (i.e. `[[link title|actual page]]`)
## features
- Rendering wikilinks
- Ability to keep track of incoming links
- Named wikilinks (i.e. `[[link title|actual page]]`)
## installation
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 install
```
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.,
```
$ 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
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 <math.h>
#include <ctype.h>
#include <argp.h>
#include <cmark.h>
static const char *VERSION = "1.0.0-beta.1";
// Structure defining the content and metadata of a single page
struct Page {
char title[80];
@ -35,7 +38,7 @@ char * read_file(char *filename) {
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);
char *buffer = malloc(fileLength + 1);
if (buffer == NULL) {
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);
buffer[fileLength] = 0;
fclose(file);
return buffer;
}
/*** Hash map implementation ***/
struct PageMap {
struct Page **pages;
int capacity;
int size;
};
int helper_hash_polynomial(
char string[],
int i,
@ -65,33 +75,69 @@ int helper_hash_polynomial(
}
}
int hash_polynomial(int mapSize, char key[]) {
return helper_hash_polynomial(key, 0, strlen(key), mapSize, 0);
int hash_polynomial(int capacity, char key[]) {
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;
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) {
char lowercased[80];
to_lower_case(lowercased, title);
int index = hash_polynomial(mapSize, lowercased);
map[index] = page;
struct PageMap * map(int capacity) {
struct PageMap *map = malloc(sizeof(struct PageMap));
map->pages = calloc(capacity, sizeof(struct Page *));
map->capacity = capacity;
map->size = 0;
return map;
}
struct Page * map_get(struct Page **map, int mapSize, char title[]) {
char lowercased[80];
to_lower_case(lowercased, title);
void map_free(struct PageMap *map) {
free(map->pages);
free(map);
}
int index = hash_polynomial(mapSize, lowercased);
struct Page *page = map[index];
void map_put(struct PageMap *map, char title[], struct Page *page) {
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;
}
@ -174,46 +220,51 @@ char * substitute_string(char dest[], char sub[], char *start, char *end) {
return compiled;
}
int main(int argc, char *argv[]) {
char pagesLocation[256] = "./pages";
int mapSize = 1000;
int initialInboundCapacity = 2;
char templateFileName[256] = "./template.html";
char outputDirectoryName[256] = "./build";
static struct argp_option options[] = {
{ "src", 's', "dir", 0, "Source directory of pages", 0 },
{ "version", 'v', 0, 0, "Print the version", 0 },
{ "template", 't', "file", 0, "Template file path", 0 },
{ "output", 'o', "dir", 0, "Output directory", 0 },
{ 0 }
};
/*** 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 if (strcmp(argv[i], "--incoming-cap") == 0) {
if (i + 1 < argc) {
i++;
initialInboundCapacity = atoi(argv[i]);
}
} else {
printf("Unknown argument: %s\n", argv[i]);
static char *pagesLocation = "./pages";
static char *templateFileName = "./template.html";
static char *outputDirectoryName = "./build";
static int parse_opt(int key, char *arg, struct argp_state *state) {
// Suppress unused parameter warnings
(void) state;
switch (key) {
case 's': {
pagesLocation = arg;
break;
}
case 't': {
templateFileName = arg;
break;
}
case 'o': {
outputDirectoryName = arg;
break;
}
case 'v': {
printf("v%s\n", VERSION);
exit(0);
}
}
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);
if (templateContent == NULL) {
@ -230,8 +281,7 @@ int main(int argc, char *argv[]) {
return 1;
}
struct Page ** pageMap = malloc(mapSize * sizeof(struct Page *));
memset(pageMap, 0, mapSize * sizeof(struct Page *));
struct PageMap *pageMap = map(100);
// Contains some information about the current file picked from 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
char *contentBuffer = endOfFirstLine;
currentPage->content = malloc(
sizeof(char) * (strlen(buffer) - titleLength + 1)
);
currentPage->content = calloc(strlen(buffer), sizeof(char));
strcpy(currentPage->content, contentBuffer);
// 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;
// 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
fileEntry = readdir(pagesDir);
@ -329,7 +377,7 @@ int main(int argc, char *argv[]) {
}
// 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);
free(createOutputDir);
@ -350,13 +398,23 @@ int main(int argc, char *argv[]) {
int linkLength = nextLinkEnd - nextLinkStart;
// Determine the exact title of the link
char title[linkLength - 3];
char *nextVerticalBar = strchr(nextLinkStart, '|');
strncpy(title, nextLinkStart + 2, (linkLength - 2)*sizeof(char));
title[linkLength - 2] = 0;
char *linkTitle = calloc(linkLength - 3, sizeof(char));
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;
@ -364,12 +422,12 @@ int main(int argc, char *argv[]) {
// i.e. the page does not exist
compiledLink = concat_strings(3,
"<a class=\"calathea-404\" href=\"#\">",
title,
linkTitle,
"</a>"
);
} else {
page_list_insert(linkedPage->incoming, currentPage);
compiledLink = concat_strings(5, "[", title, "](", linkedPage->permalink, ")");
compiledLink = concat_strings(5, "[", linkTitle, "](", linkedPage->permalink, ")");
}
char *newContent = substitute_string(
@ -378,6 +436,10 @@ int main(int argc, char *argv[]) {
currentPage->content = newContent;
free(compiledLink);
if (linkTitle != linkPageTitle) {
free(linkPageTitle);
}
free(linkTitle);
// Move to the next chunk of the file
// 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}}");
if (incomingTagStart != NULL) {
// Build the incoming links list
int incomingListSize = 37; // <ul class="calathea-incoming">\n</ul>\n
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);
// <ul class="calathea-incoming">\n</ul>\n
int incomingListSize = 37;
if (currentPage->incoming->length == 0) {
// ` <li>none</li>\n`
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));
memset(incomingLinksList, 0, (incomingListSize + 1) * sizeof(char));
strcpy(incomingLinksList, "<ul class=\"calathea-incoming\">\n");
for (int i = 0; i < currentPage->incoming->length; i++) {
struct Page *page = currentPage->incoming->pages[i];
char *link = concat_strings(5,
" <li><a href=\"",
page->permalink,
"\">",
page->title,
"</a></li>\n"
);
strcat(incomingLinksList, link);
free(link);
if (currentPage->incoming->length == 0) {
strcat(incomingLinksList, " <li>none</li>\n");
} else {
for (int i = 0; i < currentPage->incoming->length; i++) {
struct Page *page = currentPage->incoming->pages[i];
char *link = concat_strings(5,
" <li><a href=\"",
page->permalink,
"\">",
page->title,
"</a></li>\n"
);
strcat(incomingLinksList, link);
free(link);
}
}
strcat(incomingLinksList, "</ul>\n");
@ -471,6 +543,7 @@ int main(int argc, char *argv[]) {
currentPage = currentPage->next;
}
printf("Pages built successfully in %s\n", outputDirectoryName);
/*** Deallocation and whatnot ***/
currentPage = firstPage;
@ -484,8 +557,7 @@ int main(int argc, char *argv[]) {
}
closedir(pagesDir);
free(templateContent);
// Deallocate all the pages
map_free(pageMap);
return 0;
}