Compare commits
10 Commits
1bd489cdc5
...
90f1b0d8dd
Author | SHA1 | Date |
---|---|---|
Nat | 90f1b0d8dd | |
Nat | c062aa8a2e | |
Nat | 7f78f1db3c | |
Nat | 8dd7876ebc | |
Nat | c87debfbae | |
Nat | db0b922e11 | |
Nat | dd9a450042 | |
Nat | 6c402c771f | |
Nat | 02c7492f74 | |
Nat | f4e04b0dfd |
5
Makefile
5
Makefile
|
@ -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/*
|
||||
|
|
32
README.md
32
README.md
|
@ -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]]`.
|
||||
|
|
242
calathea.c
242
calathea.c
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue