From d38f82f6462af4e5aad6a2c776f5c00ce5b13c87 Mon Sep 17 00:00:00 2001 From: Linnnus Date: Thu, 1 Feb 2024 22:59:38 +0100 Subject: feat: initial commit Here is a small overview of the state of the project at this first commit. I have basic Git Repo -> HTML working, and a plan for how setting up an actual server would work (mainly, NGINX + a git hook to rebuild). The main thing I'm working on right now is parsing WikiCreole, though I am starting to wonder if this is the right langauge. WikiCreole is pretty irregular and has a lot of edge cases (e.g. around emphasis). --- .gitignore | 10 + Makefile | 47 ++ doc/simplewiki.1 | 24 + flake.lock | 61 ++ flake.nix | 66 ++ readme.txt | 16 + references/regular-recursive.html | 632 ++++++++++++++++ references/smu.c | 805 +++++++++++++++++++++ src/arena.c | 56 ++ src/arena.h | 43 ++ src/creole-test.c | 273 +++++++ src/creole.c | 111 +++ src/creole.h | 15 + src/die.c | 58 ++ src/die.h | 31 + src/main.c | 174 +++++ src/strutil.c | 58 ++ src/strutil.h | 26 + test/>h6.input.txt | 1 + test/>h6.output.txt | 1 + test/basic-paragraph-markup.input.txt | 1 + test/basic-paragraph-markup.output.txt | 1 + test/difficult-emphasis-#1.input.txt | 1 + test/difficult-emphasis-#1.output.txt | 1 + test/difficult-emphasis-#10.input.txt | 1 + test/difficult-emphasis-#10.output.txt | 1 + test/difficult-emphasis-#11.input.txt | 1 + test/difficult-emphasis-#11.output.txt | 1 + test/difficult-emphasis-#12.input.txt | 1 + test/difficult-emphasis-#12.output.txt | 1 + test/difficult-emphasis-#13.input.txt | 1 + test/difficult-emphasis-#13.output.txt | 1 + test/difficult-emphasis-#14.input.txt | 1 + test/difficult-emphasis-#14.output.txt | 1 + test/difficult-emphasis-#15.input.txt | 1 + test/difficult-emphasis-#15.output.txt | 1 + test/difficult-emphasis-#16.input.txt | 1 + test/difficult-emphasis-#16.output.txt | 1 + test/difficult-emphasis-#2.input.txt | 1 + test/difficult-emphasis-#2.output.txt | 1 + test/difficult-emphasis-#3.input.txt | 1 + test/difficult-emphasis-#3.output.txt | 1 + test/difficult-emphasis-#4.input.txt | 1 + test/difficult-emphasis-#4.output.txt | 1 + test/difficult-emphasis-#5.input.txt | 1 + test/difficult-emphasis-#5.output.txt | 1 + test/difficult-emphasis-#6.input.txt | 1 + test/difficult-emphasis-#6.output.txt | 1 + test/difficult-emphasis-#7.input.txt | 1 + test/difficult-emphasis-#7.output.txt | 1 + test/difficult-emphasis-#8.input.txt | 1 + test/difficult-emphasis-#8.output.txt | 1 + test/difficult-emphasis-#9.input.txt | 1 + test/difficult-emphasis-#9.output.txt | 1 + test/dummy-without-corresponding-output.input.txt | 0 test/emphasis.input.txt | 1 + test/emphasis.output.txt | 1 + test/h1.input.txt | 1 + test/h1.output.txt | 1 + test/h2.input.txt | 1 + test/h2.output.txt | 1 + test/h3.input.txt | 1 + test/h3.output.txt | 1 + test/h4.input.txt | 1 + test/h4.output.txt | 1 + test/h5.input.txt | 1 + test/h5.output.txt | 1 + test/h6.input.txt | 1 + test/h6.output.txt | 1 + test/horizontal-rule.input.txt | 3 + test/horizontal-rule.output.txt | 1 + test/image.input.txt | 1 + test/image.output.txt | 1 + test/inline-tt.input.txt | 1 + test/inline-tt.output.txt | 1 + test/multi-line-emphasis.input.txt | 6 + test/multi-line-emphasis.output.txt | 6 + test/named-link.input.txt | 1 + test/named-link.output.txt | 1 + test/named-url.input.txt | 1 + test/named-url.output.txt | 1 + test/ordered-item-with-ordered-sublist.input.txt | 2 + test/ordered-item-with-ordered-sublist.output.txt | 2 + test/ordered-sublist-without-initial-tag.input.txt | 1 + .../ordered-sublist-without-initial-tag.output.txt | 1 + test/preformatted-block.input.txt | 3 + test/preformatted-block.output.txt | 2 + test/raw-url.input.txt | 1 + test/raw-url.output.txt | 1 + test/simple-ordered-list.input.txt | 2 + test/simple-ordered-list.output.txt | 2 + test/simple-unordered-list.input.txt | 2 + test/simple-unordered-list.output.txt | 2 + test/strong.input.txt | 1 + test/strong.output.txt | 1 + test/tables.input.txt | 2 + test/tables.output.txt | 1 + test/two-preformatted-blocks.input.txt | 4 + test/two-preformatted-blocks.output.txt | 2 + test/unnamed-link.input.txt | 1 + test/unnamed-link.output.txt | 1 + test/unnamed-url.input.txt | 1 + test/unnamed-url.output.txt | 1 + test/unordered-item-with-ordered-sublist.input.txt | 2 + .../unordered-item-with-ordered-sublist.output.txt | 2 + ...unordered-item-with-unordered-sublist.input.txt | 2 + ...nordered-item-with-unordered-sublist.output.txt | 2 + ...unordered-sublist-without-initial-tag.input.txt | 1 + ...nordered-sublist-without-initial-tag.output.txt | 1 + "test/url\342\210\225emphasis-ambiguity.input.txt" | 1 + .../url\342\210\225emphasis-ambiguity.output.txt" | 1 + 111 files changed, 2628 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 doc/simplewiki.1 create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 readme.txt create mode 100644 references/regular-recursive.html create mode 100644 references/smu.c create mode 100644 src/arena.c create mode 100644 src/arena.h create mode 100644 src/creole-test.c create mode 100644 src/creole.c create mode 100644 src/creole.h create mode 100644 src/die.c create mode 100644 src/die.h create mode 100644 src/main.c create mode 100644 src/strutil.c create mode 100644 src/strutil.h create mode 100644 test/>h6.input.txt create mode 100644 test/>h6.output.txt create mode 100644 test/basic-paragraph-markup.input.txt create mode 100644 test/basic-paragraph-markup.output.txt create mode 100644 test/difficult-emphasis-#1.input.txt create mode 100644 test/difficult-emphasis-#1.output.txt create mode 100644 test/difficult-emphasis-#10.input.txt create mode 100644 test/difficult-emphasis-#10.output.txt create mode 100644 test/difficult-emphasis-#11.input.txt create mode 100644 test/difficult-emphasis-#11.output.txt create mode 100644 test/difficult-emphasis-#12.input.txt create mode 100644 test/difficult-emphasis-#12.output.txt create mode 100644 test/difficult-emphasis-#13.input.txt create mode 100644 test/difficult-emphasis-#13.output.txt create mode 100644 test/difficult-emphasis-#14.input.txt create mode 100644 test/difficult-emphasis-#14.output.txt create mode 100644 test/difficult-emphasis-#15.input.txt create mode 100644 test/difficult-emphasis-#15.output.txt create mode 100644 test/difficult-emphasis-#16.input.txt create mode 100644 test/difficult-emphasis-#16.output.txt create mode 100644 test/difficult-emphasis-#2.input.txt create mode 100644 test/difficult-emphasis-#2.output.txt create mode 100644 test/difficult-emphasis-#3.input.txt create mode 100644 test/difficult-emphasis-#3.output.txt create mode 100644 test/difficult-emphasis-#4.input.txt create mode 100644 test/difficult-emphasis-#4.output.txt create mode 100644 test/difficult-emphasis-#5.input.txt create mode 100644 test/difficult-emphasis-#5.output.txt create mode 100644 test/difficult-emphasis-#6.input.txt create mode 100644 test/difficult-emphasis-#6.output.txt create mode 100644 test/difficult-emphasis-#7.input.txt create mode 100644 test/difficult-emphasis-#7.output.txt create mode 100644 test/difficult-emphasis-#8.input.txt create mode 100644 test/difficult-emphasis-#8.output.txt create mode 100644 test/difficult-emphasis-#9.input.txt create mode 100644 test/difficult-emphasis-#9.output.txt create mode 100644 test/dummy-without-corresponding-output.input.txt create mode 100644 test/emphasis.input.txt create mode 100644 test/emphasis.output.txt create mode 100644 test/h1.input.txt create mode 100644 test/h1.output.txt create mode 100644 test/h2.input.txt create mode 100644 test/h2.output.txt create mode 100644 test/h3.input.txt create mode 100644 test/h3.output.txt create mode 100644 test/h4.input.txt create mode 100644 test/h4.output.txt create mode 100644 test/h5.input.txt create mode 100644 test/h5.output.txt create mode 100644 test/h6.input.txt create mode 100644 test/h6.output.txt create mode 100644 test/horizontal-rule.input.txt create mode 100644 test/horizontal-rule.output.txt create mode 100644 test/image.input.txt create mode 100644 test/image.output.txt create mode 100644 test/inline-tt.input.txt create mode 100644 test/inline-tt.output.txt create mode 100644 test/multi-line-emphasis.input.txt create mode 100644 test/multi-line-emphasis.output.txt create mode 100644 test/named-link.input.txt create mode 100644 test/named-link.output.txt create mode 100644 test/named-url.input.txt create mode 100644 test/named-url.output.txt create mode 100644 test/ordered-item-with-ordered-sublist.input.txt create mode 100644 test/ordered-item-with-ordered-sublist.output.txt create mode 100644 test/ordered-sublist-without-initial-tag.input.txt create mode 100644 test/ordered-sublist-without-initial-tag.output.txt create mode 100644 test/preformatted-block.input.txt create mode 100644 test/preformatted-block.output.txt create mode 100644 test/raw-url.input.txt create mode 100644 test/raw-url.output.txt create mode 100644 test/simple-ordered-list.input.txt create mode 100644 test/simple-ordered-list.output.txt create mode 100644 test/simple-unordered-list.input.txt create mode 100644 test/simple-unordered-list.output.txt create mode 100644 test/strong.input.txt create mode 100644 test/strong.output.txt create mode 100644 test/tables.input.txt create mode 100644 test/tables.output.txt create mode 100644 test/two-preformatted-blocks.input.txt create mode 100644 test/two-preformatted-blocks.output.txt create mode 100644 test/unnamed-link.input.txt create mode 100644 test/unnamed-link.output.txt create mode 100644 test/unnamed-url.input.txt create mode 100644 test/unnamed-url.output.txt create mode 100644 test/unordered-item-with-ordered-sublist.input.txt create mode 100644 test/unordered-item-with-ordered-sublist.output.txt create mode 100644 test/unordered-item-with-unordered-sublist.input.txt create mode 100644 test/unordered-item-with-unordered-sublist.output.txt create mode 100644 test/unordered-sublist-without-initial-tag.input.txt create mode 100644 test/unordered-sublist-without-initial-tag.output.txt create mode 100644 "test/url\342\210\225emphasis-ambiguity.input.txt" create mode 100644 "test/url\342\210\225emphasis-ambiguity.output.txt" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9689c2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# Build artifacts +result +build/ + +# Test data (can't figure out nested git repos) +example-wiki/ +example-wiki.git/ + +# Application output +public_html/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afc9dda --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.POSIX: +.PHONY: all install uninstall clean + +CC := cc +CFLAGS := -W -O $(shell pkg-config --cflags libgit2) +CFLAGS += -g3 -O0 -fsanitize=address,undefined -fsanitize-trap +CFLAGS += -Wall -Wextra -Wconversion -Wdouble-promotion \ + -Wno-unused-parameter -Wno-unused-function -Wno-sign-conversion +LDLIBS := -lm $(shell pkg-config --libs libgit2) +PREFIX ?= /usr/local + +all: build/simplewiki + +install: build/simplewiki + mkdir -p $(PREFIX)/bin + mkdir -p $(PREFIX)/share/man/man1 + cp -f build/simplewiki $(PREFIX)/bin + gzip $(PREFIX)/share/man/man1/simplewiki.1.gz + +uninstall: + rm -f $(PREFIX)/bin/simplewiki + rm -f $(PREFIX)/share/man/man1/simplewiki.1.gz + rmdir $(PREFIX)/bin >/dev/null 2>&1 || true + rmdir $(PREFIX)/share/man/man1 >/dev/null 2>&1 || true + +build/simplewiki: build/main.o build/die.o build/arena.o build/strutil.o build/creole.o + $(CC) $(LDFLAGS) -o build/simplewiki $^ $(LDLIBS) + +build/creole-test: build/creole-test.o build/creole.o + $(CC) -o $@ $^ + +build/creole-test.o: src/creole-test.c +build/main.o: src/main.c src/arena.h src/die.h src/strutil.h src/creole.h +build/arena.o: src/arena.c src/arena.h +build/die.o: src/die.c src/die.h +build/strutil.o: src/strutil.c src/strutil.h src/arena.h +build/creole.o: src/creole.c + +build/%.o: src/%.c | build/ + $(CC) $(CFLAGS) -c -o $@ $< + +build/: + mkdir -p build/ + +clean: + rm -f build/ + diff --git a/doc/simplewiki.1 b/doc/simplewiki.1 new file mode 100644 index 0000000..9217f00 --- /dev/null +++ b/doc/simplewiki.1 @@ -0,0 +1,24 @@ +.\" Process this file with +.\" groff -man -Tascii foo.1 +.\" +.TH SIMPLEWIKI 1 "NOVEMBER 2023" Linux "User Manuals" +.SH NAME +simplewiki \- a minimal and composable wiki system +.SH SYNOPSIS +.B simplewiki +.I bare-git-repo otuput-directory +.SH DESCRIPTION +.B simplewiki +renders the contents of the git repository at +.I bare-git-repo +to HTML, storing the results in +.I output-diretory. +Note that +.I bare-git-repo +should point to the actual directory and not the working tree. +.I output-directory +is created if it does not exists. +.SH AUTHOR +Linus +.SH "SEE ALSO" +.BR git (1) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..b5966a1 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1685566663, + "narHash": "sha256-btHN1czJ6rzteeCuE/PNrdssqYD2nIA4w48miQAFloM=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "4ecab3273592f27479a583fb6d975d4aba3486fe", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "23.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..75ab105 --- /dev/null +++ b/flake.nix @@ -0,0 +1,66 @@ +{ + description = "Flake utils demo"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/23.05"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + simple-wiki = pkgs.stdenv.mkDerivation { + pname = "simple-wiki"; + version = "0.0.0"; + + src = ./.; + + nativeBuildInputs = with pkgs; [ pkg-config ]; + buildInputs = with pkgs; [ libgit2 ]; + + installPhase = '' + mkdir -p $out + make PREFIX=$out install + ''; + }; + + creole-test = pkgs.stdenv.mkDerivation rec { + pname = "creole-test"; + version = simple-wiki.version; + + src = ./.; + + nativeBuildInputs = with pkgs; [ pkg-config ]; + buildInputs = with pkgs; [ libgit2 ]; + + installPhase = '' + mkdir -p $out/bin + make build/creole-test + mv build/creole-test $out/bin/.creole-test-wrapped + + cat >$out/bin/creole-test < + + + + Creole v0.4 Live Preview + + + + + +
+
+

+
+
Javascript disabled
+
+
+ + diff --git a/references/smu.c b/references/smu.c new file mode 100644 index 0000000..1174167 --- /dev/null +++ b/references/smu.c @@ -0,0 +1,805 @@ +/* smu - simple markup + * Copyright (C) <2007, 2008> Enno Boland + * 2019-2022 Karl Bartel + * 2022 bzt + * + * See LICENSE for further informations + */ +#include +#include +#include +#include +#include +#include + +#define LENGTH(x) sizeof(x)/sizeof(x[0]) +#define ADDC(b,i) if (i % BUFSIZ == 0) { b = realloc(b, (i + BUFSIZ) * sizeof(char)); if (!b) eprint("Malloc failed."); } b[i] + +typedef int (*Parser)(const char *, const char *, int); +typedef struct { + char *search; + int process; + char *before, *after; +} Tag; + +static int docomment(const char *begin, const char *end, int newblock); /* Parser for html-comments */ +static int docodefence(const char *begin, const char *end, int newblock); /* Parser for code fences */ +static int dohtml(const char *begin, const char *end, int newblock); /* Parser for html */ +static int dolineprefix(const char *begin, const char *end, int newblock);/* Parser for line prefix tags */ +static int dolink(const char *begin, const char *end, int newblock); /* Parser for links and images */ +static int dolist(const char *begin, const char *end, int newblock); /* Parser for lists */ +static int dotable(const char *begin, const char *end, int newblock); /* Parser for tables */ +static int doparagraph(const char *begin, const char *end, int newblock); /* Parser for paragraphs */ +static int doreplace(const char *begin, const char *end, int newblock); /* Parser for simple replaces */ +static int doshortlink(const char *begin, const char *end, int newblock); /* Parser for links and images */ +static int dosurround(const char *begin, const char *end, int newblock); /* Parser for surrounding tags */ +static int dounderline(const char *begin, const char *end, int newblock); /* Parser for underline tags */ +static void *ereallocz(void *p, size_t size); +static void hprint(const char *begin, const char *end); /* escapes HTML and prints it to output */ +static void process(const char *begin, const char *end, int isblock); /* Processes range between begin and end. */ + +/* list of parsers */ +static Parser parsers[] = { dounderline, docomment, docodefence, dolineprefix, + dolist, dotable, doparagraph, dosurround, dolink, + doshortlink, dohtml, doreplace }; +static int nohtml = 0; +static int in_paragraph = 0; + +regex_t p_end_regex; /* End of paragraph */ + +static Tag lineprefix[] = { + { " ", 0, "
", "\n
" }, + { "\t", 0, "
", "\n
" }, + { ">", 2, "
", "
" }, + { "###### ", 1, "
", "
" }, + { "##### ", 1, "
", "
" }, + { "#### ", 1, "

", "

" }, + { "### ", 1, "

", "

" }, + { "## ", 1, "

", "

" }, + { "# ", 1, "

", "

" }, + { "- - -\n", 1, "
", ""}, + { "---\n", 1, "
", ""}, +}; + +static Tag underline[] = { + { "=", 1, "

", "

\n" }, + { "-", 1, "

", "

\n" }, +}; + +static Tag surround[] = { + { "```", 0, "", "" }, + { "``", 0, "", "" }, + { "`", 0, "", "" }, + { "___", 1, "", "" }, + { "***", 1, "", "" }, + { "__", 1, "", "" }, + { "**", 1, "", "" }, + { "_", 1, "", "" }, + { "*", 1, "", "" }, +}; + +static const char *replace[][2] = { + /* Backslash escapes */ + { "\\\\", "\\" }, + { "\\`", "`" }, + { "\\*", "*" }, + { "\\_", "_" }, + { "\\{", "{" }, + { "\\}", "}" }, + { "\\[", "[" }, + { "\\]", "]" }, + { "\\(", "(" }, + { "\\)", ")" }, + { "\\#", "#" }, + { "\\+", "+" }, + { "\\-", "-" }, + { "\\.", "." }, + { "\\!", "!" }, + { "\\\"", """ }, + { "\\$", "$" }, + { "\\%", "%" }, + { "\\&", "&" }, + { "\\'", "'" }, + { "\\,", "," }, + { "\\-", "-" }, + { "\\.", "." }, + { "\\/", "/" }, + { "\\:", ":" }, + { "\\;", ";" }, + { "\\<", "<" }, + { "\\>", ">" }, + { "\\=", "=" }, + { "\\?", "?" }, + { "\\@", "@" }, + { "\\^", "^" }, + { "\\|", "|" }, + { "\\~", "~" }, + /* HTML syntax symbols that need to be turned into entities */ + { "<", "<" }, + { ">", ">" }, + { "&", "&" }, /* Avoid replacing the & in & */ + { "&", "&" }, + /* Preserve newlines with two spaces before linebreak */ + { " \n", "
\n" }, +}; + +static const char *code_fence = "```"; + +void +eprint(const char *format, ...) { + va_list ap; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +void end_paragraph(void) { + if (in_paragraph) { + fputs("

\n", stdout); + in_paragraph = 0; + } +} + +int +docomment(const char *begin, const char *end, int newblock) { + char *p; + + if (nohtml || strncmp(""); + if (!p || p + 3 >= end) + return 0; + fprintf(stdout, "%.*s\n", (int)(p + 3 - begin), begin); + return (p + 3 - begin) * (newblock ? -1 : 1); +} + +int +docodefence(const char *begin, const char *end, int newblock) { + const char *p, *start, *stop, *lang_start, *lang_stop; + unsigned int l = strlen(code_fence); + + if (!newblock) + return 0; + + if (strncmp(begin, code_fence, l) != 0) + return 0; + + /* Find start of content and read language string */ + start = begin + l; + lang_start = start; + while (start[0] != '\n') + start++; + lang_stop = start; + start++; + + /* Find end of fence */ + p = start - 1; + do { + stop = p; + p = strstr(p + 1, code_fence); + } while (p && p[-1] == '\\'); + if (p && p[-1] != '\\') + stop = p; + + /* No closing code fence means the rest of file is code (CommonMark) */ + if (!p) + stop = end; + + /* Print output */ + if (lang_start == lang_stop) { + fputs("
", stdout);
+	} else {
+		fputs("
", stdout);
+	}
+	hprint(start, stop);
+	fputs("
\n", stdout); + return -(stop - begin + l); +} + +int +dohtml(const char *begin, const char *end, int newblock) { + const char *p, *tag, *tagend; + + if (nohtml || begin + 2 >= end) + return 0; + p = begin; + if (p[0] != '<' || !isalpha(p[1])) + return 0; + p++; + tag = p; + for (; isalnum(*p) && p < end; p++); + tagend = p; + if (p > end || tag == tagend) + return 0; + while ((p = strstr(p, "') { + p++; + fwrite(begin, sizeof(char), p - begin + tagend - tag, stdout); + return p - begin + tagend - tag; + } + } + p = strchr(tagend, '>'); + if (p) { + fwrite(begin, sizeof(char), p - begin + 2, stdout); + return p - begin + 2; + } + else + return 0; +} + +int +dolineprefix(const char *begin, const char *end, int newblock) { + unsigned int i, j, l; + char *buffer; + const char *p; + int consumed_input = 0; + + if (newblock) + p = begin; + else if (*begin == '\n') { + p = begin + 1; + consumed_input += 1; + } else + return 0; + for (i = 0; i < LENGTH(lineprefix); i++) { + l = strlen(lineprefix[i].search); + if (end - p + 1 < l) + continue; + if (strncmp(lineprefix[i].search, p, l)) + continue; + if (*begin == '\n') + fputc('\n', stdout); + + /* All line prefixes add a block element. These are not allowed + * inside paragraphs, so we must end the paragraph first. */ + end_paragraph(); + + fputs(lineprefix[i].before, stdout); + if (lineprefix[i].search[l-1] == '\n') { + fputc('\n', stdout); + return l - 1 + consumed_input; + } + if (!(buffer = malloc(BUFSIZ))) + eprint("Malloc failed."); + buffer[0] = '\0'; + + /* Collect lines into buffer while they start with the prefix */ + j = 0; + while ((strncmp(lineprefix[i].search, p, l) == 0) && p + l < end) { + p += l; + + /* Special case for blockquotes: optional space after > */ + if (lineprefix[i].search[0] == '>' && *p == ' ') { + p++; + } + + while (p < end) { + ADDC(buffer, j) = *p; + j++; + if (*(p++) == '\n') + break; + } + } + + /* Skip empty lines in block */ + while (*(buffer + j - 1) == '\n') { + j--; + } + + ADDC(buffer, j) = '\0'; + if (lineprefix[i].process) + process(buffer, buffer + strlen(buffer), lineprefix[i].process >= 2); + else + hprint(buffer, buffer + strlen(buffer)); + puts(lineprefix[i].after); + free(buffer); + return -(p - begin); + } + return 0; +} + +int +dolink(const char *begin, const char *end, int newblock) { + int img, len, sep, parens_depth = 1; + const char *desc, *link, *p, *q, *descend, *linkend; + const char *title = NULL, *titleend = NULL; + + if (*begin == '[') + img = 0; + else if (strncmp(begin, "![", 2) == 0) + img = 1; + else + return 0; + p = desc = begin + 1 + img; + if (!(p = strstr(desc, "](")) || p > end) + return 0; + for (q = strstr(desc, "!["); q && q < end && q < p; q = strstr(q + 1, "![")) + if (!(p = strstr(p + 1, "](")) || p > end) + return 0; + descend = p; + link = p + 2; + + /* find end of link while handling nested parens */ + q = link; + while (parens_depth) { + if (!(q = strpbrk(q, "()")) || q > end) + return 0; + if (*q == '(') + parens_depth++; + else + parens_depth--; + if (parens_depth && q < end) + q++; + } + + if ((p = strpbrk(link, "\"'")) && p < end && q > p) { + sep = p[0]; /* separator: can be " or ' */ + title = p + 1; + /* strip trailing whitespace */ + for (linkend = p; linkend > link && isspace(*(linkend - 1)); linkend--); + for (titleend = q - 1; titleend > link && isspace(*(titleend)); titleend--); + if (titleend < title || *titleend != sep) { + return 0; + } + } + else { + linkend = q; + } + + /* Links can be given in angular brackets */ + if (*link == '<' && *(linkend - 1) == '>') { + link++; + linkend--; + } + + len = q + 1 - begin; + if (img) { + fputs("\"",", stdout); + } + else { + fputs("", stdout); + process(desc, descend, 0); + fputs("", stdout); + } + return len; +} + +int +dolist(const char *begin, const char *end, int newblock) { + unsigned int i, j, indent, run, isblock, start_number; + const char *p, *q, *num_start; + char *buffer = NULL; + char marker = '\0'; /* Bullet symbol or \0 for unordered lists */ + + isblock = 0; + if (newblock) + p = begin; + else if (*begin == '\n') + p = begin + 1; + else + return 0; + q = p; + if (*p == '-' || *p == '*' || *p == '+') { + marker = *p; + } else { + num_start = p; + for (; p < end && *p >= '0' && *p <= '9'; p++); + if (p >= end || *p != '.') + return 0; + start_number = atoi(num_start); + } + p++; + if (p >= end || !(*p == ' ' || *p == '\t')) + return 0; + + end_paragraph(); + + for (p++; p != end && (*p == ' ' || *p == '\t'); p++); + indent = p - q; + buffer = ereallocz(buffer, BUFSIZ); + if (!newblock) + fputc('\n', stdout); + + if (marker) { + fputs("
    \n", stdout); + } else if (start_number == 1) { + fputs("
      \n", stdout); + } else { + printf("
        \n", start_number); + } + run = 1; + for (; p < end && run; p++) { + for (i = 0; p < end && run; p++, i++) { + if (*p == '\n') { + if (p + 1 == end) + break; + else { + /* Handle empty lines */ + for (q = p + 1; (*q == ' ' || *q == '\t') && q < end; q++); + if (*q == '\n') { + ADDC(buffer, i) = '\n'; + i++; + run = 0; + isblock++; + p = q; + } + } + q = p + 1; + j = 0; + if (marker && *q == marker) + j = 1; + else { + for (; q + j != end && q[j] >= '0' && q[j] <= '9' && j < indent; j++); + if (q + j == end) + break; + if (j > 0 && q[j] == '.') + j++; + else + j = 0; + } + if (q + indent < end) + for (; (q[j] == ' ' || q[j] == '\t') && j < indent; j++); + if (j == indent) { + ADDC(buffer, i) = '\n'; + i++; + p += indent; + run = 1; + if (*q == ' ' || *q == '\t') + p++; + else + break; + } + else if (j < indent) + run = 0; + } + ADDC(buffer, i) = *p; + } + ADDC(buffer, i) = '\0'; + fputs("
      1. ", stdout); + process(buffer, buffer + i, isblock > 1 || (isblock == 1 && run)); + fputs("
      2. \n", stdout); + } + fputs(marker ? "
\n" : "\n", stdout); + free(buffer); + p--; + while (*(--p) == '\n'); + return -(p - begin + 1); +} + +int +dotable(const char *begin, const char *end, int newblock) { + /* table state */ + static signed char intable, inrow, incell; + static unsigned long int calign; + static const char *align_table[] = { + "", + " style=\"text-align: left\"", + " style=\"text-align: right\"", + " style=\"text-align: center\"", + }; + + const char *p; + int i, l = (int)sizeof(calign) * 4; + + if(*begin != '|') + return 0; + if (intable == 2) { /* in alignment row, skip it. */ + ++intable; + for (p = begin; p < end && *p != '\n'; ++p); + return p - begin + 1; + } + if(inrow && (begin + 1 >= end || begin[1] == '\n')) { /* close cell and row and if ends, table too */ + fprintf(stdout, "", inrow == -1 ? 'h' : 'd'); + if (inrow == -1) + intable = 2; + inrow = 0; + if(end - begin <= 2 || begin[2] == '\n') { + intable = 0; + fputs("\n\n", stdout); + } + return 1; + } + + if(!intable) { /* open table */ + intable = 1; inrow = -1; incell = 0; calign = 0; + for (p = begin; p < end && *p != '\n'; ++p); + if(*p == '\n') { /* load alignment from 2nd line */ + for(i = -1, ++p; p < end && *p != '\n'; p++) { + if(*p == '|') { + i++; + do { p++; } while(p < end && (*p == ' ' || *p == '\t')); + if(i < l && *p == ':') + calign |= 1ul << (i * 2); + if (*p == '\n') + break; + } else if(i < l && *p == ':') { + calign |= 1ul << (i * 2 + 1); + } + } + } + fputs("\n", stdout); + } + if(!inrow) { /* open row */ + inrow = 1; incell = 0; + fputs("", stdout); + } + if(incell) /* close cell */ + fprintf(stdout, "", inrow == -1 ? 'h' : 'd'); + l = incell < l ? (calign >> (incell * 2)) & 3 : 0; /* open cell */ + fprintf(stdout, "", inrow == -1 ? 'h' : 'd', align_table[l]); + incell++; + for(p = begin + 1; p < end && *p == ' '; p++); + return p - begin; +} + +int +doparagraph(const char *begin, const char *end, int newblock) { + const char *p; + regmatch_t match; + + if (!newblock) + return 0; + if (regexec(&p_end_regex, begin + 1, 1, &match, 0)) { + p = end; + } else { + p = begin + 1 + match.rm_so; + } + + fputs("

", stdout); + in_paragraph = 1; + process(begin, p, 0); + end_paragraph(); + + return -(p - begin); +} + +int +doreplace(const char *begin, const char *end, int newblock) { + unsigned int i, l; + + for (i = 0; i < LENGTH(replace); i++) { + l = strlen(replace[i][0]); + if (end - begin < l) + continue; + if (strncmp(replace[i][0], begin, l) == 0) { + fputs(replace[i][1], stdout); + return l; + } + } + return 0; +} + +int +doshortlink(const char *begin, const char *end, int newblock) { + const char *p, *c; + int ismail = 0; + + if (*begin != '<') + return 0; + for (p = begin + 1; p != end; p++) { + switch (*p) { + case ' ': + case '\t': + case '\n': + return 0; + case '#': + case ':': + ismail = -1; + break; + case '@': + if (ismail == 0) + ismail = 1; + break; + case '>': + if (ismail == 0) + return 0; + fputs("", stdout); + for (c = begin + 1; *c != '>'; c++) + fprintf(stdout, "&#%u;", *c); + } + else { + hprint(begin + 1, p); + fputs("\">", stdout); + hprint(begin + 1, p); + } + fputs("", stdout); + return p - begin + 1; + } + } + return 0; +} + +int +dosurround(const char *begin, const char *end, int newblock) { + unsigned int i, l; + const char *p, *start, *stop; + + for (i = 0; i < LENGTH(surround); i++) { + l = strlen(surround[i].search); + if (end - begin < 2*l || strncmp(begin, surround[i].search, l) != 0) + continue; + start = begin + l; + p = start; + do { + stop = p; + p = strstr(p + 1, surround[i].search); + } while (p && p[-1] == '\\'); + if (p && p[-1] != '\\') + stop = p; + if (!stop || stop < start || stop >= end) + continue; + fputs(surround[i].before, stdout); + + /* Single space at start and end are ignored */ + if (start[0] == ' ' && stop[-1] == ' ' && start < stop - 1) { + start++; + stop--; + l++; + } + + if (surround[i].process) + process(start, stop, 0); + else + hprint(start, stop); + fputs(surround[i].after, stdout); + return stop - start + 2 * l; + } + return 0; +} + +int +dounderline(const char *begin, const char *end, int newblock) { + unsigned int i, j, l; + const char *p; + + if (!newblock) + return 0; + p = begin; + for (l = 0; p + l != end && p[l] != '\n'; l++); + p += l + 1; + if (l == 0) + return 0; + for (i = 0; i < LENGTH(underline); i++) { + for (j = 0; p + j < end && p[j] != '\n' && p[j] == underline[i].search[0]; j++); + if (j >= 3) { + fputs(underline[i].before, stdout); + if (underline[i].process) + process(begin, begin + l, 0); + else + hprint(begin, begin + l); + fputs(underline[i].after, stdout); + return -(j + p - begin); + } + } + return 0; +} + +void * +ereallocz(void *p, size_t size) { + void *res; + res = realloc(p, size); + if (!res) + eprint("realloc: %zu bytes\n", size); + return res; +} + +void +hprint(const char *begin, const char *end) { + const char *p; + + for (p = begin; p != end; p++) { + if (*p == '&') + fputs("&", stdout); + else if (*p == '"') + fputs(""", stdout); + else if (*p == '>') + fputs(">", stdout); + else if (*p == '<') + fputs("<", stdout); + else + fputc(*p, stdout); + } +} + +void +process(const char *begin, const char *end, int newblock) { + const char *p; + int affected; + unsigned int i; + + for (p = begin; p < end;) { + if (newblock) + while (*p == '\n') + if (++p == end) + return; + + for (i = 0; i < LENGTH(parsers); i++) + if ((affected = parsers[i](p, end, newblock))) + break; + if (affected) + p += abs(affected); + else + fputc(*p++, stdout); + + /* Don't print single newline at end */ + if (p + 1 == end && *p == '\n') + return; + + if (p[0] == '\n' && p + 1 != end && p[1] == '\n') + newblock = 1; + else + newblock = affected < 0; + } +} + +int +main(int argc, char *argv[]) { + char *buffer = NULL; + int s, i; + unsigned long len, bsize; + FILE *source = stdin; + + regcomp(&p_end_regex, "(\n\n|(^|\n)```)", REG_EXTENDED); + + for (i = 1; i < argc; i++) { + if (!strcmp("-v", argv[i])) + eprint("simple markup %s (C) Enno Boland\n",VERSION); + else if (!strcmp("-n", argv[i])) + nohtml = 1; + else if (argv[i][0] != '-') + break; + else if (!strcmp("--", argv[i])) { + i++; + break; + } + else + eprint("Usage %s [-n] [file]\n -n escape html strictly\n", argv[0]); + } + if (i < argc && !(source = fopen(argv[i], "r"))) + eprint("Cannot open file `%s`\n",argv[i]); + bsize = 2 * BUFSIZ; + buffer = ereallocz(buffer, bsize); + len = 0; + while ((s = fread(buffer + len, 1, BUFSIZ, source))) { + len += s; + if (BUFSIZ + len + 1 > bsize) { + bsize += BUFSIZ; + if (!(buffer = realloc(buffer, bsize))) + eprint("realloc failed."); + } + } + buffer[len] = '\0'; + process(buffer, buffer + len, 1); + fclose(source); + free(buffer); + return EXIT_SUCCESS; +} diff --git a/src/arena.c b/src/arena.c new file mode 100644 index 0000000..3782b79 --- /dev/null +++ b/src/arena.c @@ -0,0 +1,56 @@ +#include "arena.h" + +#include // assert +#include // uintptr_t +#include // fprintf +#include // abort, malloc +#include // noreturn +#include // memset + +static noreturn void arena_panic(const char *reason) { + fprintf(stderr, "Memory allocation failed: %s", reason); + abort(); +} + +struct arena arena_create(size_t capacity) { + struct arena arena = { + .root = malloc(capacity), + .capacity = capacity, + .used = 0, + }; + if (arena.root == NULL) { + arena_panic("cannot allocate system memory"); + } + return arena; +} + +void *arena_alloc(struct arena *arena, size_t size, size_t alignment, unsigned flags) { + // Alignment must be a power of two. + // See: https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 + assert(alignment != 0 && (alignment & (alignment - 1)) == 0); + size_t padding = -(uintptr_t)(arena->root + arena->used) & (alignment - 1); + + // If no more memory is available, we fall back to our error strategy. + assert(arena->capacity >= arena->used); + if (arena->used + padding + size > arena->capacity) { + if (flags & ARENA_NO_PANIC) { + return NULL; + } else { + arena_panic("out of preallocated memory"); + } + } + + // Reserve memory from arena. + void *ptr = arena->root + arena->used + padding; + arena->used += padding + size; + + if (~flags & ARENA_NO_ZERO) { + memset(ptr, 0, size); + } + + return ptr; +} + +void arena_destroy(struct arena *arena) { + free(arena->root); +} diff --git a/src/arena.h b/src/arena.h new file mode 100644 index 0000000..cd4aa17 --- /dev/null +++ b/src/arena.h @@ -0,0 +1,43 @@ +#ifndef ARENA_H +#define ARENA_H + +// +// This module defines a simple, fixed-size arena allocator. +// + +#include // size_t + +struct arena { + void *root; + size_t capacity; + size_t used; +}; + +// Initialize an arena with the given `capacity`. +// Panics on failure to allocate. +struct arena arena_create(size_t capacity); + +// These flags control the behavior of `arena_alloc`. +#define ARENA_NO_ZERO 1 +#define ARENA_NO_PANIC 2 + +// Allocate `size` bytes in `arena`. +// The resulting memory is zeroed unless ARENA_NO_ZERO is passed. +// Unless ARENA_NO_PANIC is specified, the resulting pointer is always valid. +void *arena_alloc(struct arena *arena, size_t size, size_t alignment, unsigned flags); + +// Free the memory associated with the arena using the underlying allocator. +void arena_destroy(struct arena *arena); + +// +// The `new` macro makes the basic allocation case simple. It uses a bit of +// preprocessor magic to simulate default argument values. +// + +#define new(...) newx(__VA_ARGS__,new4,new3,new2)(__VA_ARGS__) +#define newx(a1, a2, a3, a4, a5, ...) a5 +#define new2(arena, typ) (typ *)arena_alloc(arena, sizeof(typ), _Alignof(typ), 0) +#define new3(arena, typ, count) (typ *)arena_alloc(arena, count * sizeof(typ), _Alignof(typ), 0) +#define new4(arena, typ, count, flags) (typ *)arena_alloc(arena, count * sizeof(typ), _Alignof(typ), flags) + +#endif diff --git a/src/creole-test.c b/src/creole-test.c new file mode 100644 index 0000000..18d24a2 --- /dev/null +++ b/src/creole-test.c @@ -0,0 +1,273 @@ +#include "creole.h" + +#include +#include +#include +#include +#include +#include +#include +#include +//#include + +#define RED "\x1b[31m" +#define GREEN "\x1b[32m" +#define YELLOW "\x1b[33m" +#define CYAN "\x1b[96m" +#define BOLD "\x1b[39;1m" +#define DIM "\x1b[39;2m" +#define CLEAR "\x1b[0m" + +#define LENGTH(a) (sizeof(a)/sizeof((a)[0])) + +#define CHUNK_SIZE 5 + +int read_file(const char *file_path, char **out_buffer, size_t *out_length) { + assert(out_buffer != NULL && *out_buffer == NULL); + assert(file_path != NULL); + + FILE *fp = fopen(file_path, "r"); + if (fp == NULL) { + return -1; + } + + char *buffer = NULL; + size_t allocated = 0; + size_t used = 0; + while (true) { + // Grow buffer, if needed. + if (used + CHUNK_SIZE > allocated) { + // Grow exponentially to guarantee O(log(n)) performance. + allocated = (allocated == 0) ? CHUNK_SIZE : allocated * 2; + + // Overflow check. Some ANSI C compilers may optimize this away, though. + if (allocated <= used) { + free(buffer); + fclose(fp); + errno = EOVERFLOW; + return -1; + } + + char *temp = realloc(buffer, allocated); + if (temp == NULL) { + int old_errno = errno; + free(buffer); // free() may not set errno + fclose(fp); // fclose() may set errno + errno = old_errno; + return -1; + } + buffer = temp; + } + + size_t nread = fread(buffer + used, 1, CHUNK_SIZE, fp); + if (nread == 0) { + // End-of-file or errnor has occured. + // FIXME: Should we be checking (nread < CHUNK_SIZE)? + // https://stackoverflow.com/a/39322170 + break; + } + used += nread; + } + + if (ferror(fp)) { + int old_errno = errno; + free(buffer); // free() may not set errno + fclose(fp); // fclose() may set errno + errno = old_errno; + return -1; + } + + // Reallocate to optimal size. + char *temp = realloc(buffer, used + 1); + if (temp == NULL) { + int old_errno = errno; + free(buffer); // free() may not set errno + fclose(fp); // fclose() may set errno + errno = old_errno; + return -1; + } + buffer = temp; + + // Null-terminate the buffer. Note that buffers may still contain \0, + // so strlen(buffer) == length may not be true. + buffer[used] = '\0'; + + // Return buffer. + *out_buffer = buffer; + if (out_length != NULL) { + *out_length = used; + } + fclose(fp); + return 0; +} + +// https://stackoverflow.com/a/779960 +char *replace(const char *orig, char *rep, char *with) { + assert(orig != NULL); + assert(rep != NULL); + + char *tmp; // varies + + int len_rep = strlen(rep); // length of rep (the string to remove) + if (len_rep == 0) { + errno = EINVAL; // empty rep causes infinite loop during count + return NULL; + } + + int len_with; // length of with (the string to replace rep with) + if (with == NULL) + with = ""; + len_with = strlen(with); + + // count the number of replacements needed + const char *ins; // the next insert point + int count; // number of replacements + ins = orig; + for (count = 0; (tmp = strstr(ins, rep)) != NULL; ++count) { + ins = tmp + len_rep; + } + + char *result; // the return string + tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1); + if (!result) { + return NULL; + } + + // first time through the loop, all the variable are set correctly + // from here on, + // tmp points to the end of the result string + // ins points to the next occurrence of rep in orig + // orig points to the remainder of orig after "end of rep" + while (count--) { + ins = strstr(orig, rep); + int len_front = ins - orig; + tmp = strncpy(tmp, orig, len_front) + len_front; + tmp = strcpy(tmp, with) + len_with; + orig += len_front + len_rep; // move to next "end of rep" + } + strcpy(tmp, orig); + return result; +} + +int print_escaped(FILE *fp, const char *string, size_t length) { + static struct { + char from; + const char *to; + } replacements[] = { + { '\t', "\\t" }, + { '\n', "\\n" }, + { '"', "\\\"" }, + }; + + if (fputc('"', fp) == EOF) { + return -1; + } + + for (size_t i = 0; i < length; ++i) { + for (size_t j = 0; j < LENGTH(replacements); ++j) { + if (string[i] == replacements[j].from) { + if (fprintf(fp, "%s", replacements[j].to) < 0) { + return -1; + } + goto next_char; + } + } + if (fputc(string[i], fp) == EOF) { + return -1; + } +next_char: + ; + } + + if (fputc('"', fp) == EOF) { + return -1; + } + + return 0; +} + +int main(int argc, char *argv[]) { + if (argc != 1) { + fprintf(stderr, "Usage: %s\n", argv[0]); + fprintf(stderr, "Takes no arguments, must be invoked in parent of test dir.\n"); + return EXIT_FAILURE; + } + + glob_t glob_result; + if (glob("./test/*.input.txt", GLOB_ERR, NULL, &glob_result) < 0) { + perror("Glob failed"); + return EXIT_FAILURE; + } + + unsigned ok = 0; + unsigned failures = 0; + unsigned errors = 0; + + for (int i = 0; i < glob_result.gl_matchc; ++i) { + char *input_name = glob_result.gl_pathv[i]; + + int input_name_len = strlen(input_name); + int prefix_len = strlen("./test/"); + int sufix_len = strlen(".input.txt"); + printf("Running: " BOLD "%.*s" CLEAR "... ", input_name_len - prefix_len - sufix_len, input_name + prefix_len); + + char *input_buffer = NULL; + size_t input_length; + if (read_file(input_name, &input_buffer, &input_length) < 0) { + printf(RED "internal error!" CLEAR "\n " CYAN "error:" CLEAR " reading %s: %s\n", input_name, strerror(errno)); + errors += 1; + goto fail_input_buffer; + } + + // TODO: replace() is a bit overkill. Just strcpy to buffer of size (strlen(input_name) - strlen(".input.txt") + strlen(".output.txt")). + char *output_name = replace(input_name, ".input.txt", ".output.txt"); + if (output_name == NULL) { + printf(RED "internal error!" CLEAR "\n " CYAN "error:" CLEAR " generating output name: %s\n", strerror(errno)); + errors += 1; + goto fail_output_name; + } + char *output_buffer = NULL; + size_t output_length; + if (read_file(output_name, &output_buffer, &output_length) < 0) { + printf(RED "internal error!" CLEAR "\n " CYAN "error:" CLEAR " reading %s: %s\n", output_name, strerror(errno)); + errors += 1; + goto fail_output_buffer; + } + + // Do actual render. + static char buffer[1024]; + FILE *fp = fmemopen(buffer, sizeof(buffer), "wb"); + render_creole(fp, input_buffer, input_length); + long buffer_length = ftell(fp); + fclose(fp); + + bool success = strcmp(output_buffer, buffer) == 0; + if (success) { + ok += 1; + printf(GREEN "ok" CLEAR "\n"); + } else { + failures += 1; + printf(RED "unexpected output!" CLEAR); + printf(CYAN "\n input: " CLEAR); + print_escaped(stdout, input_buffer, input_length); + printf(CYAN "\n want: " CLEAR); + print_escaped(stdout, output_buffer, output_length); + printf(CYAN"\n got: " CLEAR); + print_escaped(stdout, buffer, buffer_length); // TODO: rendered + putchar('\n'); + } + + free(output_buffer); +fail_output_buffer: + free(output_name); +fail_output_name: + free(input_buffer); +fail_input_buffer: + ; + } + + printf("Summary: " YELLOW "%u" CLEAR " errors, " RED "%u" CLEAR " failures and " GREEN "%u" CLEAR " successes\n", errors, failures, ok); + + globfree(&glob_result); + return (failures == 0 && errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/creole.c b/src/creole.c new file mode 100644 index 0000000..f69c543 --- /dev/null +++ b/src/creole.c @@ -0,0 +1,111 @@ +#include "creole.h" + +#include +#include +#include +#include +#include +#include + +#define LENGTH(x) (sizeof(x)/sizeof((x)[0])) + +#define DEBUG(...) (fprintf(stderr, __VA_ARGS__), fflush(stderr)) + +void process(const char *begin, const char *end, bool new_block, FILE *out); +int do_headers(const char *begin, const char *end, bool new_block, FILE *out); + +// A parser takes a (sub)string and returns the number of characters consumed, if any. +// +// The parameter `new_block` determines whether `begin` points to the beginning of a new block. +// The sign of the return value determines whether a new block should begin, after the consumed text. +typedef int (* parser_t)(const char *begin, const char *end, bool new_block, FILE *out); + +static parser_t parsers[] = { do_headers }; + +int do_headers(const char *begin, const char *end, bool new_block, FILE *out) { + if (!new_block) { // Headers are block-level elements. + return 0; + } + + if (*begin != '=') { + return 0; + } + + unsigned level = 0; + while (*begin == '=') { + level += 1; + begin += 1; + } + DEBUG("level %d\n", level); + + while (isspace(*begin)) { + begin += 1; + } + + const char *stop = end; + while (stop + 1 != end && stop[1] != '\n') { + stop += 1; + } + while (*stop == '=') { + stop -= 1; + } + + fprintf(out, "", level); + process(begin, stop, false, out); + fprintf(out, "", level); + + return -(stop - begin); +} + +void process(const char *begin, const char *end, bool new_block, FILE *out) { + const char *p = begin; + while (p < end) { + // Eat all newlines if we're starting a block. + if (new_block) { + while (*p == '\n') { + p += 1; + if (p == end) { + return; + } + } + } + + // Greedily try all parsers. + int affected; + for (unsigned i = 0; i < LENGTH(parsers); ++i) { + DEBUG("%p\n", parsers[i]); + affected = parsers[i](p, end, new_block, out); + if (affected) { + break; + } + } + if (affected) { + p += abs(affected); + } else { + fputc(*p, out); + p += 1; + } + + if (p + 1 == end) { + // Don't print single newline at end. + if (*p == '\n') { + return; + } + } else { + // Determine whether we've reached a new block. + if (p[0] == '\n' && p[1] == '\n') { + // Double newline characters separate blocks; + // if we've found them, we're starting a new block + new_block = true; + } else { + // ...otherwise the parser gets to decide. + new_block = affected < 0; + } + } + } +} + +void render_creole(FILE *out, const char *source, size_t source_length) +{ + process(source, source + source_length, true, out); +} diff --git a/src/creole.h b/src/creole.h new file mode 100644 index 0000000..ac3e706 --- /dev/null +++ b/src/creole.h @@ -0,0 +1,15 @@ +#ifndef CREOLE_H +#define CREOLE_H + +// Defines a module for rendering Wiki Creole [1] to a file. This functionality +// of this module is based on the formal grammar [2] of Wiki Creole. +// +// [1]: http://www.wikicreole.org/wiki/Home +// [2]: http://www.wikicreole.org/wiki/EBNFGrammarForWikiCreole1.0 + +#include // size_t +#include // FILE + +void render_creole(FILE *out, const char *source, size_t length); + +#endif diff --git a/src/die.c b/src/die.c new file mode 100644 index 0000000..529eb9b --- /dev/null +++ b/src/die.c @@ -0,0 +1,58 @@ +#include "die.h" + +#include // assert +#include // fprintf, vfprintf, fputc, stderr +#include // exit, EXIT_FAILURE +#include // va_* +#include // git_* +#include // strerror +#include // errno + +void die(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + + fputc('\n', stderr); + +#ifndef NDEBUG + git_libgit2_shutdown(); +#endif + exit(EXIT_FAILURE); +} + +// Die but include the last git error. +void noreturn die_git(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + + const git_error *e = git_error_last(); + assert(e != NULL && "die_git called without error"); + fprintf(stderr, ": %s\n", e->message); + +#ifndef NDEBUG + git_libgit2_shutdown(); +#endif + exit(EXIT_FAILURE); +} + +// Die but include errno information. +void noreturn die_errno(const char *msg, ...) +{ + va_list ap; + va_start(ap, msg); + vfprintf(stderr, msg, ap); + va_end(ap); + + fprintf(stderr, ": %s\n", strerror(errno)); + +#ifndef NDEBUG + git_libgit2_shutdown(); +#endif + exit(EXIT_FAILURE); +} diff --git a/src/die.h b/src/die.h new file mode 100644 index 0000000..891d10f --- /dev/null +++ b/src/die.h @@ -0,0 +1,31 @@ +#ifndef DIE_H +#define DIE_H + +// +// This module defines various utilities for ending program execution +// abnormally. +// + +#include // noreturn + +#ifdef __GNUC__ +#define _DIE_PRINTF_ATTR __attribute__((format(printf, 1, 2))) +#else +#define _DIE_PRINTF_ATTR +#endif + +// Exit the program, displaying no extra information. +_DIE_PRINTF_ATTR +noreturn void die(const char *msg, ...); + +// Exit the program, displaying the last libgit error. +// It is an error to invoke this if there has been no libgit error. +_DIE_PRINTF_ATTR +noreturn void die_git(const char *msg, ...); + +// Exit the program, displaying errno message. +// It is NOT an error to invoke this if errno is 0, just pretty weird. +_DIE_PRINTF_ATTR +noreturn void die_errno(const char *msg, ...); + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..a6b438f --- /dev/null +++ b/src/main.c @@ -0,0 +1,174 @@ +#include "arena.h" +#include "die.h" +#include "strutil.h" +#include "creole.h" + +// #include +#include // errno, EEXIST +#include // git_* +#include // false +#include +#include // EXIT_SUCCESS +#include // mkdir +#include // mode_t + +void xmkdir(const char *path, mode_t mode, bool exist_ok) { + if (mkdir(path, mode) < 0) { + if (exist_ok && errno == EEXIST) { + return; + } else { + die_errno("failed to mkdir %s", path); + } + } +} + +void process_other_file(const char *path, const char *source, size_t source_len) { + FILE *out = fopen(path, "w"); + if (out == NULL) { + die_errno("failed to open %s for writing", path); + } + if (fwrite(source, 1, source_len, out) < source_len) { + die_errno("failed to write content to %s\n", path); + } + fclose(out); +} + +void process_markup_file(const char *path, const char *source, size_t source_len) { + FILE *out = fopen(path, "w"); + if (out == NULL) { + die_errno("failed to open %s for writing", path); + } + int status = render_creole(out, source, source_len); + if (status != 0) { + fprintf(stderr, "warning: failed to parse: %s (status %d)\n", path, status); + } + fclose(out); +} + +void process_dir(const char *path) { + xmkdir(path, 0755, false); +} + +void list_tree(struct arena *a, struct git_repository *repo, struct git_tree *tree, const char *prefix) { + // Grab a snapshot of the arena. + // All memory allocated within the arena in this subcalltree will be freed. + // This is effectively the same as allocating a new arena for each call to list_tree. + struct arena snapshot = *a; + + size_t tree_count = git_tree_entrycount(tree); + for (size_t i = 0; i < tree_count; ++i) { + // Read the entry. + const struct git_tree_entry *entry; + if ((entry = git_tree_entry_byindex(tree, i)) == NULL) { + die("read tree item"); + } + + // Construct path to entry. + const char *entry_out_path = joinpath(a, prefix, git_tree_entry_name(entry)); + printf("Generating: %s\n", entry_out_path); + + // entry->obj fail on submodules. just ignore them. + struct git_object *obj; + if (git_tree_entry_to_object(&obj, repo, entry) == 0) { + git_object_t type = git_object_type(obj); + switch (type) { + case GIT_OBJECT_BLOB: { + struct git_blob *blob = (struct git_blob *)obj; + const char *source = git_blob_rawcontent(blob); + if (source == NULL) { + die_git("get source for blob %s", git_oid_tostr_s(git_object_id(obj))); + } + size_t source_len = git_blob_rawsize(blob); + if (endswith(entry_out_path, ".md") && !git_blob_is_binary(blob)) { + process_markup_file(entry_out_path, source, source_len); + } else { + process_other_file(entry_out_path, source, source_len); + } + git_object_free(obj); + } break; + case GIT_OBJECT_TREE: { + process_dir(entry_out_path); + list_tree(a, repo, (struct git_tree *)obj, entry_out_path); + git_object_free(obj); + } break; + default: { + // Ignore whatever weird thing this is. + git_object_free(obj); + } break; + } + } + } + + // Restore snapshot. + *a = snapshot; +} + +int main(int argc, char *argv[]) +{ + if (argc != 3) { + die("Usage: %s git-path out-path", argv[0]); + } + char *git_path = argv[1]; + char *out_path = argv[2]; + + // Initialize libgit. Note that calling git_libgit2_shutdown is not + // necessary, as per this snippet from the documentation: + // + // > Usually you don’t need to call the shutdown function as the operating + // > system will take care of reclaiming resources, but if your + // > application uses libgit2 in some areas which are not usually active, + // > you can use + // + // That's good news! + if (git_libgit2_init() < 0) { + die_git("initialize libgit"); + } + + // Do not search outside the git repository. GIT_CONFIG_LEVEL_APP is the highest level currently. + // for (int i = 1; i <= GIT_CONFIG_LEVEL_APP; i++) { + // if (git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, i, "") < 0) { + // die_git("set search path"); + // } + // } + + // Don't require the repository to be owned by the current user. + git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, 0); + + struct git_repository *repo; + if (git_repository_open_ext(&repo, git_path, GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) < 0) { + die_git("open repository"); + } + + // Find HEAD. + struct git_object *head; + const struct git_oid *head_id; + if (git_revparse_single(&head, repo, "HEAD") < 0) { + die_git("parse HEAD"); + } + head_id = git_object_id(head); + git_object_free(head); + + // Get a handle to the tree at head. + struct git_commit *commit; + if (git_commit_lookup(&commit, repo, head_id) < 0) { + die_git("look up head commit"); + } + struct git_tree *tree; + if (git_commit_tree(&tree, commit) < 0) { + die_git("get tree for commit %s", git_oid_tostr_s(git_commit_id(commit))); + } + + // Create the initial output directory. + xmkdir(out_path, 0755, true); + + struct arena a = arena_create(1024); + list_tree(&a, repo, tree, out_path); +#ifndef NDEBUG + arena_destroy(&a); +#endif + +#ifndef NDEBUG + git_libgit2_shutdown(); +#endif + return EXIT_SUCCESS; +} diff --git a/src/strutil.c b/src/strutil.c new file mode 100644 index 0000000..9192989 --- /dev/null +++ b/src/strutil.c @@ -0,0 +1,58 @@ +#include "strutil.h" + +#include "arena.h" // struct arena, new +#include // assert +#include // va_* +#include // bool, false +#include // vsnprintf +#include // strlen, strncmp + +int aprintf(struct arena *a, char **out, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int ret = vaprintf(a, out, fmt, ap); + va_end(ap); + return ret; +} + +int vaprintf(struct arena *a, char **out, const char *fmt, va_list args) { + // Calculate size. + va_list tmp; + va_copy(tmp, args); + int size = vsnprintf(NULL, 0, fmt, args); + va_end(tmp); + + // If e.g. the format string was broken, we cannot continue. + if (size < 0) { + return -1; + } + + // Arena allocation cannot fail. + *out = new(a, char, size + 1); + + int t = vsnprintf(*out, size + 1, fmt, args); + assert(t == size); + + return size; +} + +char *joinpath(struct arena *a, const char *path_a, const char *path_b) { + char *out; + int ret = aprintf(a, &out, "%s/%s", path_a, path_b); + assert(ret > 0 && "should be infallible"); + return out; +} + +bool endswith(const char *haystack, const char *needle) { + assert(haystack != NULL); + assert(needle != NULL); + + size_t haystack_len = strlen(haystack); + size_t needle_len = strlen(needle); + + if (needle_len > haystack_len) { + return false; + } + + return strncmp(haystack + (haystack_len - needle_len), needle, needle_len) == 0; +} diff --git a/src/strutil.h b/src/strutil.h new file mode 100644 index 0000000..03f8294 --- /dev/null +++ b/src/strutil.h @@ -0,0 +1,26 @@ +#ifndef STRUTIL_H +#define STRUTIL_H + +// +// Defines various utilities for working with strings. +// + +#include "arena.h" // struct arena +#include // bool +#include // va_list + +// Like asprintf except the allocation is made inside the given arena. +// Panics on allocation failure. +int aprintf(struct arena *a, char **out, const char *fmt, ...); + +// Same as aprintf, except takes a varargs list. +int vaprintf(struct arena *a, char **out, const char *fmt, va_list args); + +// Join the two paths with a directory separator. +// Result is allocated in arena. +char *joinpath(struct arena *a, const char *path_a, const char *path_b); + +// Returns boolean indicating if `haystack` ends with `needle`. +bool endswith(const char *haystack, const char *needle); + +#endif diff --git a/test/>h6.input.txt b/test/>h6.input.txt new file mode 100644 index 0000000..f422da3 --- /dev/null +++ b/test/>h6.input.txt @@ -0,0 +1 @@ +======= Header = \ No newline at end of file diff --git a/test/>h6.output.txt b/test/>h6.output.txt new file mode 100644 index 0000000..1ab8a75 --- /dev/null +++ b/test/>h6.output.txt @@ -0,0 +1 @@ +

======= Header =

\ No newline at end of file diff --git a/test/basic-paragraph-markup.input.txt b/test/basic-paragraph-markup.input.txt new file mode 100644 index 0000000..30d69f7 --- /dev/null +++ b/test/basic-paragraph-markup.input.txt @@ -0,0 +1 @@ +Basic paragraph test with <, >, & and " \ No newline at end of file diff --git a/test/basic-paragraph-markup.output.txt b/test/basic-paragraph-markup.output.txt new file mode 100644 index 0000000..24cc6d6 --- /dev/null +++ b/test/basic-paragraph-markup.output.txt @@ -0,0 +1 @@ +

Basic paragraph test with <, >, & and "

\ No newline at end of file diff --git a/test/difficult-emphasis-#1.input.txt b/test/difficult-emphasis-#1.input.txt new file mode 100644 index 0000000..f40a5e6 --- /dev/null +++ b/test/difficult-emphasis-#1.input.txt @@ -0,0 +1 @@ +// http://www.link.org // \ No newline at end of file diff --git a/test/difficult-emphasis-#1.output.txt b/test/difficult-emphasis-#1.output.txt new file mode 100644 index 0000000..4b94283 --- /dev/null +++ b/test/difficult-emphasis-#1.output.txt @@ -0,0 +1 @@ +

http://www.link.org

\ No newline at end of file diff --git a/test/difficult-emphasis-#10.input.txt b/test/difficult-emphasis-#10.input.txt new file mode 100644 index 0000000..e2fff46 --- /dev/null +++ b/test/difficult-emphasis-#10.input.txt @@ -0,0 +1 @@ +// ftp // \ No newline at end of file diff --git a/test/difficult-emphasis-#10.output.txt b/test/difficult-emphasis-#10.output.txt new file mode 100644 index 0000000..d4e6163 --- /dev/null +++ b/test/difficult-emphasis-#10.output.txt @@ -0,0 +1 @@ +

ftp

\ No newline at end of file diff --git a/test/difficult-emphasis-#11.input.txt b/test/difficult-emphasis-#11.input.txt new file mode 100644 index 0000000..c990a82 --- /dev/null +++ b/test/difficult-emphasis-#11.input.txt @@ -0,0 +1 @@ +// fttpfptftpft // \ No newline at end of file diff --git a/test/difficult-emphasis-#11.output.txt b/test/difficult-emphasis-#11.output.txt new file mode 100644 index 0000000..a633f70 --- /dev/null +++ b/test/difficult-emphasis-#11.output.txt @@ -0,0 +1 @@ +

fttpfptftpft

\ No newline at end of file diff --git a/test/difficult-emphasis-#12.input.txt b/test/difficult-emphasis-#12.input.txt new file mode 100644 index 0000000..eb6c6be --- /dev/null +++ b/test/difficult-emphasis-#12.input.txt @@ -0,0 +1 @@ +// ftp: // \ No newline at end of file diff --git a/test/difficult-emphasis-#12.output.txt b/test/difficult-emphasis-#12.output.txt new file mode 100644 index 0000000..bc36f4f --- /dev/null +++ b/test/difficult-emphasis-#12.output.txt @@ -0,0 +1 @@ +

ftp:

\ No newline at end of file diff --git a/test/difficult-emphasis-#13.input.txt b/test/difficult-emphasis-#13.input.txt new file mode 100644 index 0000000..a2613a2 --- /dev/null +++ b/test/difficult-emphasis-#13.input.txt @@ -0,0 +1 @@ +// ftp:// \ No newline at end of file diff --git a/test/difficult-emphasis-#13.output.txt b/test/difficult-emphasis-#13.output.txt new file mode 100644 index 0000000..d152870 --- /dev/null +++ b/test/difficult-emphasis-#13.output.txt @@ -0,0 +1 @@ +

// ftp://

\ No newline at end of file diff --git a/test/difficult-emphasis-#14.input.txt b/test/difficult-emphasis-#14.input.txt new file mode 100644 index 0000000..8fcc0a8 --- /dev/null +++ b/test/difficult-emphasis-#14.input.txt @@ -0,0 +1 @@ +// ftp://// \ No newline at end of file diff --git a/test/difficult-emphasis-#14.output.txt b/test/difficult-emphasis-#14.output.txt new file mode 100644 index 0000000..2e8d283 --- /dev/null +++ b/test/difficult-emphasis-#14.output.txt @@ -0,0 +1 @@ +

ftp://

\ No newline at end of file diff --git a/test/difficult-emphasis-#15.input.txt b/test/difficult-emphasis-#15.input.txt new file mode 100644 index 0000000..fac5c31 --- /dev/null +++ b/test/difficult-emphasis-#15.input.txt @@ -0,0 +1 @@ +//fttpfptftpftt// \ No newline at end of file diff --git a/test/difficult-emphasis-#15.output.txt b/test/difficult-emphasis-#15.output.txt new file mode 100644 index 0000000..f69d27c --- /dev/null +++ b/test/difficult-emphasis-#15.output.txt @@ -0,0 +1 @@ +

fttpfptftpftt

\ No newline at end of file diff --git a/test/difficult-emphasis-#16.input.txt b/test/difficult-emphasis-#16.input.txt new file mode 100644 index 0000000..69a62c9 --- /dev/null +++ b/test/difficult-emphasis-#16.input.txt @@ -0,0 +1 @@ +//ftp://link.org// \ No newline at end of file diff --git a/test/difficult-emphasis-#16.output.txt b/test/difficult-emphasis-#16.output.txt new file mode 100644 index 0000000..b639d39 --- /dev/null +++ b/test/difficult-emphasis-#16.output.txt @@ -0,0 +1 @@ +

ftp://link.org

\ No newline at end of file diff --git a/test/difficult-emphasis-#2.input.txt b/test/difficult-emphasis-#2.input.txt new file mode 100644 index 0000000..4164319 --- /dev/null +++ b/test/difficult-emphasis-#2.input.txt @@ -0,0 +1 @@ +// http // \ No newline at end of file diff --git a/test/difficult-emphasis-#2.output.txt b/test/difficult-emphasis-#2.output.txt new file mode 100644 index 0000000..775aeaf --- /dev/null +++ b/test/difficult-emphasis-#2.output.txt @@ -0,0 +1 @@ +

http

\ No newline at end of file diff --git a/test/difficult-emphasis-#3.input.txt b/test/difficult-emphasis-#3.input.txt new file mode 100644 index 0000000..63b3f61 --- /dev/null +++ b/test/difficult-emphasis-#3.input.txt @@ -0,0 +1 @@ +// httphpthtpht // \ No newline at end of file diff --git a/test/difficult-emphasis-#3.output.txt b/test/difficult-emphasis-#3.output.txt new file mode 100644 index 0000000..2475a98 --- /dev/null +++ b/test/difficult-emphasis-#3.output.txt @@ -0,0 +1 @@ +

httphpthtpht

\ No newline at end of file diff --git a/test/difficult-emphasis-#4.input.txt b/test/difficult-emphasis-#4.input.txt new file mode 100644 index 0000000..59df379 --- /dev/null +++ b/test/difficult-emphasis-#4.input.txt @@ -0,0 +1 @@ +// http: // \ No newline at end of file diff --git a/test/difficult-emphasis-#4.output.txt b/test/difficult-emphasis-#4.output.txt new file mode 100644 index 0000000..6ec00bc --- /dev/null +++ b/test/difficult-emphasis-#4.output.txt @@ -0,0 +1 @@ +

http:

\ No newline at end of file diff --git a/test/difficult-emphasis-#5.input.txt b/test/difficult-emphasis-#5.input.txt new file mode 100644 index 0000000..6b00e37 --- /dev/null +++ b/test/difficult-emphasis-#5.input.txt @@ -0,0 +1 @@ +// http:// \ No newline at end of file diff --git a/test/difficult-emphasis-#5.output.txt b/test/difficult-emphasis-#5.output.txt new file mode 100644 index 0000000..f0a2bf3 --- /dev/null +++ b/test/difficult-emphasis-#5.output.txt @@ -0,0 +1 @@ +

// http://

\ No newline at end of file diff --git a/test/difficult-emphasis-#6.input.txt b/test/difficult-emphasis-#6.input.txt new file mode 100644 index 0000000..482f676 --- /dev/null +++ b/test/difficult-emphasis-#6.input.txt @@ -0,0 +1 @@ +// http://// \ No newline at end of file diff --git a/test/difficult-emphasis-#6.output.txt b/test/difficult-emphasis-#6.output.txt new file mode 100644 index 0000000..5f4d7e4 --- /dev/null +++ b/test/difficult-emphasis-#6.output.txt @@ -0,0 +1 @@ +

http://

\ No newline at end of file diff --git a/test/difficult-emphasis-#7.input.txt b/test/difficult-emphasis-#7.input.txt new file mode 100644 index 0000000..75d6014 --- /dev/null +++ b/test/difficult-emphasis-#7.input.txt @@ -0,0 +1 @@ +//httphpthtphtt// \ No newline at end of file diff --git a/test/difficult-emphasis-#7.output.txt b/test/difficult-emphasis-#7.output.txt new file mode 100644 index 0000000..d22e67a --- /dev/null +++ b/test/difficult-emphasis-#7.output.txt @@ -0,0 +1 @@ +

httphpthtphtt

\ No newline at end of file diff --git a/test/difficult-emphasis-#8.input.txt b/test/difficult-emphasis-#8.input.txt new file mode 100644 index 0000000..b3db2a6 --- /dev/null +++ b/test/difficult-emphasis-#8.input.txt @@ -0,0 +1 @@ +//http://link.org// \ No newline at end of file diff --git a/test/difficult-emphasis-#8.output.txt b/test/difficult-emphasis-#8.output.txt new file mode 100644 index 0000000..336af88 --- /dev/null +++ b/test/difficult-emphasis-#8.output.txt @@ -0,0 +1 @@ +

http://link.org

\ No newline at end of file diff --git a/test/difficult-emphasis-#9.input.txt b/test/difficult-emphasis-#9.input.txt new file mode 100644 index 0000000..36d9ea2 --- /dev/null +++ b/test/difficult-emphasis-#9.input.txt @@ -0,0 +1 @@ +// ftp://www.link.org // \ No newline at end of file diff --git a/test/difficult-emphasis-#9.output.txt b/test/difficult-emphasis-#9.output.txt new file mode 100644 index 0000000..0608ab1 --- /dev/null +++ b/test/difficult-emphasis-#9.output.txt @@ -0,0 +1 @@ +

ftp://www.link.org

\ No newline at end of file diff --git a/test/dummy-without-corresponding-output.input.txt b/test/dummy-without-corresponding-output.input.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/emphasis.input.txt b/test/emphasis.input.txt new file mode 100644 index 0000000..032289b --- /dev/null +++ b/test/emphasis.input.txt @@ -0,0 +1 @@ +//Emphasis// \ No newline at end of file diff --git a/test/emphasis.output.txt b/test/emphasis.output.txt new file mode 100644 index 0000000..93eaeba --- /dev/null +++ b/test/emphasis.output.txt @@ -0,0 +1 @@ +

Emphasis

\ No newline at end of file diff --git a/test/h1.input.txt b/test/h1.input.txt new file mode 100644 index 0000000..2000784 --- /dev/null +++ b/test/h1.input.txt @@ -0,0 +1 @@ += Header = \ No newline at end of file diff --git a/test/h1.output.txt b/test/h1.output.txt new file mode 100644 index 0000000..689a688 --- /dev/null +++ b/test/h1.output.txt @@ -0,0 +1 @@ +

Header

\ No newline at end of file diff --git a/test/h2.input.txt b/test/h2.input.txt new file mode 100644 index 0000000..a2bc486 --- /dev/null +++ b/test/h2.input.txt @@ -0,0 +1 @@ +== Header = \ No newline at end of file diff --git a/test/h2.output.txt b/test/h2.output.txt new file mode 100644 index 0000000..7369147 --- /dev/null +++ b/test/h2.output.txt @@ -0,0 +1 @@ +

Header

\ No newline at end of file diff --git a/test/h3.input.txt b/test/h3.input.txt new file mode 100644 index 0000000..4bb4cc0 --- /dev/null +++ b/test/h3.input.txt @@ -0,0 +1 @@ +=== Header = \ No newline at end of file diff --git a/test/h3.output.txt b/test/h3.output.txt new file mode 100644 index 0000000..8634f69 --- /dev/null +++ b/test/h3.output.txt @@ -0,0 +1 @@ +

Header

\ No newline at end of file diff --git a/test/h4.input.txt b/test/h4.input.txt new file mode 100644 index 0000000..0439f7f --- /dev/null +++ b/test/h4.input.txt @@ -0,0 +1 @@ +==== Header = \ No newline at end of file diff --git a/test/h4.output.txt b/test/h4.output.txt new file mode 100644 index 0000000..74ac160 --- /dev/null +++ b/test/h4.output.txt @@ -0,0 +1 @@ +

Header

\ No newline at end of file diff --git a/test/h5.input.txt b/test/h5.input.txt new file mode 100644 index 0000000..cbe5f09 --- /dev/null +++ b/test/h5.input.txt @@ -0,0 +1 @@ +===== Header \ No newline at end of file diff --git a/test/h5.output.txt b/test/h5.output.txt new file mode 100644 index 0000000..91c8302 --- /dev/null +++ b/test/h5.output.txt @@ -0,0 +1 @@ +
Header
\ No newline at end of file diff --git a/test/h6.input.txt b/test/h6.input.txt new file mode 100644 index 0000000..f333b14 --- /dev/null +++ b/test/h6.input.txt @@ -0,0 +1 @@ +====== Header = \ No newline at end of file diff --git a/test/h6.output.txt b/test/h6.output.txt new file mode 100644 index 0000000..1ce38f7 --- /dev/null +++ b/test/h6.output.txt @@ -0,0 +1 @@ +
Header
\ No newline at end of file diff --git a/test/horizontal-rule.input.txt b/test/horizontal-rule.input.txt new file mode 100644 index 0000000..b86aa52 --- /dev/null +++ b/test/horizontal-rule.input.txt @@ -0,0 +1,3 @@ +Some text +---- +Some more text \ No newline at end of file diff --git a/test/horizontal-rule.output.txt b/test/horizontal-rule.output.txt new file mode 100644 index 0000000..05809bf --- /dev/null +++ b/test/horizontal-rule.output.txt @@ -0,0 +1 @@ +

Some text


Some more text

\ No newline at end of file diff --git a/test/image.input.txt b/test/image.input.txt new file mode 100644 index 0000000..82de5f6 --- /dev/null +++ b/test/image.input.txt @@ -0,0 +1 @@ +{{image.gif|my image}} \ No newline at end of file diff --git a/test/image.output.txt b/test/image.output.txt new file mode 100644 index 0000000..421ea11 --- /dev/null +++ b/test/image.output.txt @@ -0,0 +1 @@ +

my image

\ No newline at end of file diff --git a/test/inline-tt.input.txt b/test/inline-tt.input.txt new file mode 100644 index 0000000..668057e --- /dev/null +++ b/test/inline-tt.input.txt @@ -0,0 +1 @@ +Inline {{{tt}}} example {{{here}}}! \ No newline at end of file diff --git a/test/inline-tt.output.txt b/test/inline-tt.output.txt new file mode 100644 index 0000000..4b26296 --- /dev/null +++ b/test/inline-tt.output.txt @@ -0,0 +1 @@ +

Inline tt example here!

\ No newline at end of file diff --git a/test/multi-line-emphasis.input.txt b/test/multi-line-emphasis.input.txt new file mode 100644 index 0000000..7dbeaa4 --- /dev/null +++ b/test/multi-line-emphasis.input.txt @@ -0,0 +1,6 @@ +Bold and italics should //be +able// to cross lines. + +But, should //not be... + +...able// to cross paragraphs. \ No newline at end of file diff --git a/test/multi-line-emphasis.output.txt b/test/multi-line-emphasis.output.txt new file mode 100644 index 0000000..751f62e --- /dev/null +++ b/test/multi-line-emphasis.output.txt @@ -0,0 +1,6 @@ +

Bold and italics should be +able to cross lines. +

+But, should //not be... +

+...able// to cross paragraphs.

\ No newline at end of file diff --git a/test/named-link.input.txt b/test/named-link.input.txt new file mode 100644 index 0000000..59ef88e --- /dev/null +++ b/test/named-link.input.txt @@ -0,0 +1 @@ +[[MyPage|My page]] \ No newline at end of file diff --git a/test/named-link.output.txt b/test/named-link.output.txt new file mode 100644 index 0000000..912d393 --- /dev/null +++ b/test/named-link.output.txt @@ -0,0 +1 @@ +

My page

\ No newline at end of file diff --git a/test/named-url.input.txt b/test/named-url.input.txt new file mode 100644 index 0000000..a71274b --- /dev/null +++ b/test/named-url.input.txt @@ -0,0 +1 @@ +[[http://example.com/examplepage|Example Page]] \ No newline at end of file diff --git a/test/named-url.output.txt b/test/named-url.output.txt new file mode 100644 index 0000000..ff2c2a6 --- /dev/null +++ b/test/named-url.output.txt @@ -0,0 +1 @@ +

Example Page

\ No newline at end of file diff --git a/test/ordered-item-with-ordered-sublist.input.txt b/test/ordered-item-with-ordered-sublist.input.txt new file mode 100644 index 0000000..181b2f8 --- /dev/null +++ b/test/ordered-item-with-ordered-sublist.input.txt @@ -0,0 +1,2 @@ +# Item +## Subitem \ No newline at end of file diff --git a/test/ordered-item-with-ordered-sublist.output.txt b/test/ordered-item-with-ordered-sublist.output.txt new file mode 100644 index 0000000..79cd7c0 --- /dev/null +++ b/test/ordered-item-with-ordered-sublist.output.txt @@ -0,0 +1,2 @@ +
  1. Item
      +
    1. Subitem
\ No newline at end of file diff --git a/test/ordered-sublist-without-initial-tag.input.txt b/test/ordered-sublist-without-initial-tag.input.txt new file mode 100644 index 0000000..8e45942 --- /dev/null +++ b/test/ordered-sublist-without-initial-tag.input.txt @@ -0,0 +1 @@ +## Sublist item \ No newline at end of file diff --git a/test/ordered-sublist-without-initial-tag.output.txt b/test/ordered-sublist-without-initial-tag.output.txt new file mode 100644 index 0000000..0587284 --- /dev/null +++ b/test/ordered-sublist-without-initial-tag.output.txt @@ -0,0 +1 @@ +

## Sublist item

\ No newline at end of file diff --git a/test/preformatted-block.input.txt b/test/preformatted-block.input.txt new file mode 100644 index 0000000..0ce854b --- /dev/null +++ b/test/preformatted-block.input.txt @@ -0,0 +1,3 @@ +{{{ +Preformatted block +}}} \ No newline at end of file diff --git a/test/preformatted-block.output.txt b/test/preformatted-block.output.txt new file mode 100644 index 0000000..42b0a91 --- /dev/null +++ b/test/preformatted-block.output.txt @@ -0,0 +1,2 @@ +
Preformatted block
+
\ No newline at end of file diff --git a/test/raw-url.input.txt b/test/raw-url.input.txt new file mode 100644 index 0000000..8ebd56b --- /dev/null +++ b/test/raw-url.input.txt @@ -0,0 +1 @@ +http://example.com/examplepage \ No newline at end of file diff --git a/test/raw-url.output.txt b/test/raw-url.output.txt new file mode 100644 index 0000000..b0ec525 --- /dev/null +++ b/test/raw-url.output.txt @@ -0,0 +1 @@ +

http://example.com/examplepage

\ No newline at end of file diff --git a/test/simple-ordered-list.input.txt b/test/simple-ordered-list.input.txt new file mode 100644 index 0000000..9cde6b0 --- /dev/null +++ b/test/simple-ordered-list.input.txt @@ -0,0 +1,2 @@ +# list item +#list item 2 \ No newline at end of file diff --git a/test/simple-ordered-list.output.txt b/test/simple-ordered-list.output.txt new file mode 100644 index 0000000..34fb177 --- /dev/null +++ b/test/simple-ordered-list.output.txt @@ -0,0 +1,2 @@ +
  1. list item
  2. +
  3. list item 2
\ No newline at end of file diff --git a/test/simple-unordered-list.input.txt b/test/simple-unordered-list.input.txt new file mode 100644 index 0000000..019ac85 --- /dev/null +++ b/test/simple-unordered-list.input.txt @@ -0,0 +1,2 @@ +* list item +*list item 2 \ No newline at end of file diff --git a/test/simple-unordered-list.output.txt b/test/simple-unordered-list.output.txt new file mode 100644 index 0000000..5634c4b --- /dev/null +++ b/test/simple-unordered-list.output.txt @@ -0,0 +1,2 @@ +
  • list item
  • +
  • list item 2
\ No newline at end of file diff --git a/test/strong.input.txt b/test/strong.input.txt new file mode 100644 index 0000000..e5d3ce8 --- /dev/null +++ b/test/strong.input.txt @@ -0,0 +1 @@ +**Strong** \ No newline at end of file diff --git a/test/strong.output.txt b/test/strong.output.txt new file mode 100644 index 0000000..7ded6c8 --- /dev/null +++ b/test/strong.output.txt @@ -0,0 +1 @@ +

Strong

\ No newline at end of file diff --git a/test/tables.input.txt b/test/tables.input.txt new file mode 100644 index 0000000..1038fa2 --- /dev/null +++ b/test/tables.input.txt @@ -0,0 +1,2 @@ +| A | B | +| //C// | **D** \\ E | \ No newline at end of file diff --git a/test/tables.output.txt b/test/tables.output.txt new file mode 100644 index 0000000..de0276a --- /dev/null +++ b/test/tables.output.txt @@ -0,0 +1 @@ +
A B
C D
E
\ No newline at end of file diff --git a/test/two-preformatted-blocks.input.txt b/test/two-preformatted-blocks.input.txt new file mode 100644 index 0000000..636f199 --- /dev/null +++ b/test/two-preformatted-blocks.input.txt @@ -0,0 +1,4 @@ +{{{ +Preformatted block +}}} +{{{Block 2}}} \ No newline at end of file diff --git a/test/two-preformatted-blocks.output.txt b/test/two-preformatted-blocks.output.txt new file mode 100644 index 0000000..5f16814 --- /dev/null +++ b/test/two-preformatted-blocks.output.txt @@ -0,0 +1,2 @@ +
Preformatted block
+
Block 2
\ No newline at end of file diff --git a/test/unnamed-link.input.txt b/test/unnamed-link.input.txt new file mode 100644 index 0000000..0b11afe --- /dev/null +++ b/test/unnamed-link.input.txt @@ -0,0 +1 @@ +[[MyPage]] \ No newline at end of file diff --git a/test/unnamed-link.output.txt b/test/unnamed-link.output.txt new file mode 100644 index 0000000..7ed65d2 --- /dev/null +++ b/test/unnamed-link.output.txt @@ -0,0 +1 @@ +

MyPage

\ No newline at end of file diff --git a/test/unnamed-url.input.txt b/test/unnamed-url.input.txt new file mode 100644 index 0000000..ea80e5d --- /dev/null +++ b/test/unnamed-url.input.txt @@ -0,0 +1 @@ +[[http://example.com/examplepage]] \ No newline at end of file diff --git a/test/unnamed-url.output.txt b/test/unnamed-url.output.txt new file mode 100644 index 0000000..b0ec525 --- /dev/null +++ b/test/unnamed-url.output.txt @@ -0,0 +1 @@ +

http://example.com/examplepage

\ No newline at end of file diff --git a/test/unordered-item-with-ordered-sublist.input.txt b/test/unordered-item-with-ordered-sublist.input.txt new file mode 100644 index 0000000..10ac242 --- /dev/null +++ b/test/unordered-item-with-ordered-sublist.input.txt @@ -0,0 +1,2 @@ +* Item +*# Subitem \ No newline at end of file diff --git a/test/unordered-item-with-ordered-sublist.output.txt b/test/unordered-item-with-ordered-sublist.output.txt new file mode 100644 index 0000000..57ed1da --- /dev/null +++ b/test/unordered-item-with-ordered-sublist.output.txt @@ -0,0 +1,2 @@ +
  • Item
      +
    1. Subitem
\ No newline at end of file diff --git a/test/unordered-item-with-unordered-sublist.input.txt b/test/unordered-item-with-unordered-sublist.input.txt new file mode 100644 index 0000000..b4d3ecd --- /dev/null +++ b/test/unordered-item-with-unordered-sublist.input.txt @@ -0,0 +1,2 @@ +* Item +** Subitem \ No newline at end of file diff --git a/test/unordered-item-with-unordered-sublist.output.txt b/test/unordered-item-with-unordered-sublist.output.txt new file mode 100644 index 0000000..9c4a46f --- /dev/null +++ b/test/unordered-item-with-unordered-sublist.output.txt @@ -0,0 +1,2 @@ +
  • Item
      +
    • Subitem
\ No newline at end of file diff --git a/test/unordered-sublist-without-initial-tag.input.txt b/test/unordered-sublist-without-initial-tag.input.txt new file mode 100644 index 0000000..f495ea5 --- /dev/null +++ b/test/unordered-sublist-without-initial-tag.input.txt @@ -0,0 +1 @@ +** Sublist item \ No newline at end of file diff --git a/test/unordered-sublist-without-initial-tag.output.txt b/test/unordered-sublist-without-initial-tag.output.txt new file mode 100644 index 0000000..59d4091 --- /dev/null +++ b/test/unordered-sublist-without-initial-tag.output.txt @@ -0,0 +1 @@ +

** Sublist item

\ No newline at end of file diff --git "a/test/url\342\210\225emphasis-ambiguity.input.txt" "b/test/url\342\210\225emphasis-ambiguity.input.txt" new file mode 100644 index 0000000..4c29ff9 --- /dev/null +++ "b/test/url\342\210\225emphasis-ambiguity.input.txt" @@ -0,0 +1 @@ +This is an //italic// text. This is a url: http://www.wikicreole.org. This is what can go wrong://this should be an italic text//. \ No newline at end of file diff --git "a/test/url\342\210\225emphasis-ambiguity.output.txt" "b/test/url\342\210\225emphasis-ambiguity.output.txt" new file mode 100644 index 0000000..9be7ec8 --- /dev/null +++ "b/test/url\342\210\225emphasis-ambiguity.output.txt" @@ -0,0 +1 @@ +

This is an italic text. This is a url: http://www.wikicreole.org. This is what can go wrong:this should be an italic text.

\ No newline at end of file -- cgit v1.2.3