Compare commits

..

No commits in common. "90f1b0d8dd06280c88895a5b107dcc3ea8d89ad9" and "1bd489cdc51fbdd7a9913209becf89216e3c4f92" have entirely different histories.

3 changed files with 92 additions and 187 deletions

View File

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

View File

@ -1,4 +1,4 @@
# calathea v1.0-beta
# calathea v0.1
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
- Rendering wikilinks
- Ability to keep track of incoming links
- Named wikilinks (i.e. `[[link title|actual page]]`)
## Features
- [x] Rendering wikilinks
- [x] 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,7 +21,6 @@ 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
@ -29,27 +28,8 @@ 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,11 +5,8 @@
#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];
@ -38,7 +35,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 + 1);
char *buffer = malloc(fileLength);
if (buffer == NULL) {
printf("Warning: Failed to allocate enough memory for %s\n", filename);
@ -47,19 +44,12 @@ 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,
@ -75,69 +65,33 @@ int helper_hash_polynomial(
}
}
int hash_polynomial(int capacity, char key[]) {
return helper_hash_polynomial(key, 0, strlen(key), capacity, 0);
int hash_polynomial(int mapSize, char key[]) {
return helper_hash_polynomial(key, 0, strlen(key), mapSize, 0);
}
char * to_lower_case(char str[]) {
char * lower = malloc((strlen(str) + 1) * sizeof(char));
void to_lower_case(char dest[], char str[]) {
int i = 0;
for (; str[i] != '\0'; i++) {
lower[i] = tolower(str[i]);
dest[i] = tolower(str[i]);
}
lower[i] = 0;
return lower;
dest[i] = 0;
}
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;
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;
}
void map_free(struct PageMap *map) {
free(map->pages);
free(map);
}
struct Page * map_get(struct Page **map, int mapSize, char title[]) {
char lowercased[80];
to_lower_case(lowercased, title);
void map_put(struct PageMap *map, char title[], struct Page *page) {
char *lowercased = to_lower_case(title);
int index = hash_polynomial(mapSize, lowercased);
struct Page *page = map[index];
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;
}
@ -220,51 +174,46 @@ char * substitute_string(char dest[], char sub[], char *start, char *end) {
return compiled;
}
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 }
};
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 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);
/*** 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]);
}
}
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) {
@ -281,7 +230,8 @@ int main(int argc, char *argv[]) {
return 1;
}
struct PageMap *pageMap = map(100);
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);
@ -350,7 +300,9 @@ int main(int argc, char *argv[]) {
// Save the content string for later by mallocing it
char *contentBuffer = endOfFirstLine;
currentPage->content = calloc(strlen(buffer), sizeof(char));
currentPage->content = malloc(
sizeof(char) * (strlen(buffer) - titleLength + 1)
);
strcpy(currentPage->content, contentBuffer);
// Copy the first line (title) into its respective field
@ -358,7 +310,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, currentPage->title, currentPage);
map_put(pageMap, mapSize, currentPage->title, currentPage);
// Get ready to process the next page
fileEntry = readdir(pagesDir);
@ -377,7 +329,7 @@ int main(int argc, char *argv[]) {
}
// Create the directory if it doesn't exist
char *createOutputDir = concat_strings(3, "mkdir ", outputDirectoryName, " 2> /dev/null");
char *createOutputDir = concat_strings(2, "mkdir ", outputDirectoryName);
system(createOutputDir);
free(createOutputDir);
@ -398,23 +350,13 @@ int main(int argc, char *argv[]) {
int linkLength = nextLinkEnd - nextLinkStart;
char *nextVerticalBar = strchr(nextLinkStart, '|');
// Determine the exact title of the link
char title[linkLength - 3];
char *linkTitle = calloc(linkLength - 3, sizeof(char));
char *linkPageTitle = NULL;
strncpy(title, nextLinkStart + 2, (linkLength - 2)*sizeof(char));
title[linkLength - 2] = 0;
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);
struct Page *linkedPage = map_get(pageMap, mapSize, title);
char *compiledLink = NULL;
@ -422,12 +364,12 @@ int main(int argc, char *argv[]) {
// i.e. the page does not exist
compiledLink = concat_strings(3,
"<a class=\"calathea-404\" href=\"#\">",
linkTitle,
title,
"</a>"
);
} else {
page_list_insert(linkedPage->incoming, currentPage);
compiledLink = concat_strings(5, "[", linkTitle, "](", linkedPage->permalink, ")");
compiledLink = concat_strings(5, "[", title, "](", linkedPage->permalink, ")");
}
char *newContent = substitute_string(
@ -436,10 +378,6 @@ 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
@ -478,38 +416,28 @@ int main(int argc, char *argv[]) {
char *incomingTagStart = strstr(renderedPage, "{{incoming}}");
if (incomingTagStart != NULL) {
// Build the incoming links list
// <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);
}
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);
}
char *incomingLinksList = malloc((incomingListSize + 1) * sizeof(char));
memset(incomingLinksList, 0, (incomingListSize + 1) * sizeof(char));
strcpy(incomingLinksList, "<ul class=\"calathea-incoming\">\n");
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);
}
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");
@ -543,7 +471,6 @@ int main(int argc, char *argv[]) {
currentPage = currentPage->next;
}
printf("Pages built successfully in %s\n", outputDirectoryName);
/*** Deallocation and whatnot ***/
currentPage = firstPage;
@ -557,7 +484,8 @@ int main(int argc, char *argv[]) {
}
closedir(pagesDir);
free(templateContent);
map_free(pageMap);
// Deallocate all the pages
return 0;
}