diff options
author | Linnnus <[email protected]> | 2024-02-01 22:59:38 +0100 |
---|---|---|
committer | Linnnus <[email protected]> | 2024-02-04 09:58:06 +0100 |
commit | d38f82f6462af4e5aad6a2c776f5c00ce5b13c87 (patch) | |
tree | 01a222792dfb10473ae4370b4fc90f3a48e1a499 |
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).
111 files changed, 2628 insertions, 0 deletions
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 <doc/simplewiki.1 >$(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 <linus (at) linus dot onl> +.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 <<EOF + #!${pkgs.bash}/bin/bash + cd ${src} + exec $out/bin/.creole-test-wrapped + EOF + chmod +x $out/bin/creole-test + ''; + }; + + devShell = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ pkg-config ]; + buildInputs = with pkgs; [ libgit2 ]; + }; + in + { + packages = { + inherit simple-wiki creole-test; + default = simple-wiki; + }; + + devShells.default = devShell; + } + ); +} diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..c4e1e00 --- /dev/null +++ b/readme.txt @@ -0,0 +1,16 @@ +My Simple wiki system +===================== + +This project is an attempt to make the simplest wiki possible. It uses a proper +version control system to avoid the inner platform effect [1] and it does +simple, stateless rendering of a basic markup language [3]. + +The hope is that this simplicity leads to composability and extensibility. In a +way, these files represent my effort to understand and fully appreaciate The +Unix Philosophy™ [2]. + +[1]: https://thedailywtf.com/articles/The_Enterprise_Rules_Engine +[2]: http://www.catb.org/~esr/writings/taoup/html/ch01s06.html +[3]: http://www.wikicreole.org/ + + vi: ft=plaintext tw=80 diff --git a/references/regular-recursive.html b/references/regular-recursive.html new file mode 100644 index 0000000..69584ab --- /dev/null +++ b/references/regular-recursive.html @@ -0,0 +1,632 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> + <head> + <title>Creole v0.4 Live Preview</title> + <meta http-equiv="Content-Type" content="text/xhtml; charset=utf-8" /> + <style type="text/css"> + .WikiText { display: block; width: 100%; height: 33%; } + table, td { border: solid 1px; } + tr { vertical-align: middle; text-align: center; } + div#UnitTest { position: absolute; right: 1em; + width: 15em; padding: 0; } + div#UnitTest h1 { font-size: large; text-align: center; + text-transform: uppercase; } + div#Main { padding: 0 1em 0 1em; margin: 0px; + position: absolute; top: 0px; left: 0px; right: 16em; } + table.unit-test { border: thin solid black; border-collapse: collapse; } + table.unit-test th { border: thin black; border-style: solid dashed; } + table.unit-test td { border: thin dashed black; } + table.unit-test strong { color: red; } + </style> + <script type="text/javascript"> +// <![CDATA[ +// Copyright (c) 2007 Chris Purcell. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +// + +function $(element) { + if (document.getElementById) + return document.getElementById(element); + else if (document.all) + return document.all[element]; + else + return null; +} + +String.prototype.replaceEvalGl = function(regex, fn) { + var head = ""; + var tail = "" + this; + while (m = tail.match(regex)) { + head += tail.substring(0,m.index) + fn(m); + tail = tail.substring(m.index + m[0].length); + } + return head + tail; +} + +//// The markup rules //////////////////////////////////////////////////////// +MarkupRule = function(regex, rule) { + this.regex = regex; + this.rule = rule; + this.children = [ ]; +} +MarkupRule.prototype.clone = function() { + var objectClone = new this.constructor(); + for (var property in this) + objectClone[property] = this[property]; + return objectClone; +} +MarkupRule.prototype.setChildren = function(children) { + this.children = children; +} +ElementRule = function(params) { + return new MarkupRule(params["regex"], function (r) { + var text = ""; + if ("capture" in params) + text = r[params["capture"]]; + if (text) { + if ("replaceRegex" in params) + text = text.replace(params["replaceRegex"], params["replaceString"]); + var tag = "<" + params["tag"] + ">"; + var endtag = "</" + params["tag"] + ">"; + if (!("tag" in params)) + tag = endtag = ""; + return tag + this.markUp(text) + endtag; + } else if ("tag" in params) + return "<" + params["tag"] + " />"; + else + return ""; + }); +} + +function toXHTML(wikiText) { + wikiText = wikiText.replace(/&/g, "&"); + wikiText = wikiText.replace(/</g, "<"); + wikiText = wikiText.replace(/>/g, ">"); + wikiText = wikiText.replace(/"/g, """); + return toXHTML.root.markUp(wikiText); +} + + // A header is text within equals signs (=) +toXHTML.h1 = new ElementRule({ tag: "h1", capture: 2, + regex: /(^|\n)[ \t]*={1}[ \t](.+?)[ \t]*=*\s*(\n|$)/ }); +toXHTML.h2 = new ElementRule({ tag: "h2", capture: 2, + regex: /(^|\n)[ \t]*={2}[ \t](.+?)[ \t]*=*\s*(\n|$)/ }); +toXHTML.h3 = new ElementRule({ tag: "h3", capture: 2, + regex: /(^|\n)[ \t]*={3}[ \t](.+?)[ \t]*=*\s*(\n|$)/ }); +toXHTML.h4 = new ElementRule({ tag: "h4", capture: 2, + regex: /(^|\n)[ \t]*={4}[ \t](.+?)[ \t]*=*\s*(\n|$)/ }); +toXHTML.h5 = new ElementRule({ tag: "h5", capture: 2, + regex: /(^|\n)[ \t]*={5}[ \t](.+?)[ \t]*=*\s*(\n|$)/ }); +toXHTML.h6 = new ElementRule({ tag: "h6", capture: 2, + regex: /(^|\n)[ \t]*={6}[ \t](.+?)[ \t]*=*\s*(\n|$)/ }); + + // hr is a line of 4 dashes (-) +toXHTML.hr = new ElementRule({ tag: "hr", regex: /(^|\n)\s*----\s*(\n|$)/ }); + + // br is two backslashes (\) +toXHTML.br = new ElementRule({ tag: "br", regex: /\\\\/ }); + + // Preformatted blocks are wrapped in {{{...}}} +toXHTML.preBlock = new ElementRule({ tag: "pre", capture: 2, + regex: /(^|\n){{{\n?(.*?(\n.*?)*?)}}}(\n|$)/ }); + + // tt inlines are also wrapped in {{{...}}} +toXHTML.tt = new ElementRule({ tag: "tt", + regex: /{{{(.*?(?:\n.*?)*?)}}}/, capture: 1 }); + + // Unordered and ordered lists start with * or # +toXHTML.ulist = new ElementRule({ tag: "ul", + regex: /(^|\n)(\*[^*#].*(\n|$)([*#]{2}.*(\n|$))*)+/, capture: 0, + replaceRegex: /(^|\n)[*#]/g, replaceString: "$1" }); +toXHTML.olist = new ElementRule({ tag: "ol", + regex: /(^|\n)(#[^*#].*(\n|$)([*#]{2}.*(\n|$))*)+/, capture: 0, + replaceRegex: /(^|\n)[*#]/g, replaceString: "$1" }); +toXHTML.li = new ElementRule({tag:"li",regex:/.+(\n[*#].+)*/,capture:0}); + + // Tables +toXHTML.table = new ElementRule({ tag: "table", + regex: /(^|\n)(\|.*\|[ \t]*(\n|$))+/, capture: 0 }); +toXHTML.tr = new ElementRule({ tag: "tr", + regex: /(^|\n)(\|.*)\|[ \t]*(\n|$)/, capture: 2 }); +toXHTML.td = new ElementRule({ tag: "td", + regex: /[|]+([^|]*)/, capture: 1 }); + + // Kinds of text block: + // - paragraph is the fallback for the root rule + // and consists of blocks of text separated by blank lines + // - singleLine is used within lists +toXHTML.singleLine = new ElementRule({ regex: /.+/, capture: 0 }); +toXHTML.paragraph = new ElementRule({ tag: "p", + regex: /(^|\n)([ \t]*[^\s].*(\n|$))+/, capture: 0 }); + + // Strongly emphasised text is surrounded by double-* characters +toXHTML.strong = new ElementRule({ tag: "strong", capture: 1, + regex:/\*\*([^*]*(?:\*[^*]+)*)\*\*/ }); + + // Emphasised text is surrounded by double-/ characters + // It must skip http:// or ftp:// internally + // (This would be a lot easier to write with negative lookbehind!) +toXHTML.em = new ElementRule({ tag: "em", capture: 1, + regex:"\\/\\/(" + // Starts with a double-/ + "[^\\/hf]*(?:" + + "\\/?(?:http:\\/?|ftp:\\/?)*(?:" + + "h(?:t(?:tp?)?)?" + "|" + + "f(?:tp?)?" + "|" + + "(?:" + + "h[^t\\/hf]" + "|" + + "ht[^t\\/hf]" + "|" + + "htt[^p\\/hf]" + "|" + + "http[^:\\/hf]" + "|" + + "http:[^\\/hf]" + "|" + + "http:\\/[^\\/hf]" + "|" + + "http:\\/\\/" + "|" + + "f[^t\\/hf]" + "|" + + "ft[^p\\/hf]" + "|" + + "ftp[^:\\/hf]" + "|" + + "ftp:[^\\/hf]" + "|" + + "ftp:\\/[^\\/hf]" + "|" + + "ftp:\\/\\/" + + ")" + + "[^\\/hf]*" + + ")" + "|" + + "\\/[^\\/hf][^\\/hf]*" + + ")*" + + ")" + + "\\/\\/" // Ends with a double-/ +}); + + // Links +toXHTML.linkPattern = "[^\\]|\\n]*(?:\\][^\\]|\\n]+)*"; +toXHTML.urlProtocols = "(?:http|https|ftp|afs|news|nntp|mid|cid|mailto|" + + "wais|prospero|telnet|gopher)"; +toXHTML.urlPattern = toXHTML.urlProtocols + ":" + + "[^\\]|\\n]*(?:\\][^\\]|\\n]+)*"; +toXHTML.loneURLPattern = "(?:" + toXHTML.urlProtocols + + ":[\\$-:=\\?-Z_a-z~]+[\\$-+\\/-Z_a-z~-])"; + +toXHTML.rawURL = new MarkupRule( "(" + toXHTML.loneURLPattern + ")", + function(r) { + return "<a href=\"" + r[1] + "\">" + r[1] + "</a>"; + } +); +toXHTML.unnamedURL = new MarkupRule( + "\\[\\[(" + toXHTML.urlPattern + ")\\]\\]", + function(r) { + return "<a href=\"" + r[1] + "\">" + r[1] + "</a>"; + } +); +toXHTML.unnamedLink = new MarkupRule( + "\\[\\[(" + toXHTML.linkPattern + ")\\]\\]", + function(r) { + return "<a href=\"" + r[1] + "\">" + r[1] + "</a>"; + } +); +toXHTML.namedURL = new MarkupRule( + "\\[\\[(" + toXHTML.urlPattern + ")\\|(.*?)\\]\\]", + function(r) { + return "<a href=\"" + r[1] + "\">" + r[2] + "</a>"; + } +); +toXHTML.namedLink = new MarkupRule( + "\\[\\[(" + toXHTML.linkPattern + ")\\|(.*?)\\]\\]", + function(r) { + return "<a href=\"" + r[1] + "\">" + r[2] + "</a>"; + } +); + + // Images +toXHTML.img = new MarkupRule( + "{{([^|\\n{}][^|\\n}]*(?:}[^|\\n}]+)*)\\|([^|\\n}]*(?:}[^|\\n}]+)*)}}", + function(r) { + return "<img src=\"" + r[1] + "\" alt=\"" + r[2] + "\"/>"; + } +); + + // Children of lists +toXHTML.ulist.children = toXHTML.olist.children = [ toXHTML.li ]; +toXHTML.li.children = [ toXHTML.olist, toXHTML.ulist, toXHTML.singleLine ]; + + // Children of table items +toXHTML.table.children = [ toXHTML.tr ]; +toXHTML.tr.children = [ toXHTML.td ]; +toXHTML.td.children = [ toXHTML.singleLine ]; + + // Children within blocks +toXHTML.singleLine.children = toXHTML.paragraph.children = + toXHTML.strong.children = toXHTML.em.children = toXHTML.tt.children = + [ toXHTML.strong, toXHTML.em, toXHTML.br, toXHTML.rawURL, + toXHTML.unnamedURL, toXHTML.unnamedLink, toXHTML.namedURL, + toXHTML.namedLink, toXHTML.tt, toXHTML.img ]; + + + // The root rule used to start the parser +toXHTML.root = new MarkupRule(); +toXHTML.root.children = [ toXHTML.h1, toXHTML.h2, toXHTML.h3, + toXHTML.h4, toXHTML.h5, toXHTML.h6, + toXHTML.hr, toXHTML.olist, + toXHTML.ulist, toXHTML.preBlock, + toXHTML.table ]; +toXHTML.root.fallback = new MarkupRule(); +toXHTML.root.fallback.children = [ toXHTML.paragraph ]; + + +//// Do the rendering //////////////////////////////////////////////////////// +// Apply each rule, and use whichever matches first in the text +// If there is a tie, use whichever is first in the list of rules +MarkupRule.prototype.markUp = function(text) { + var head = ""; + var tail = "" + text; + var matches = [ ]; + for (var i = 0; i < this.children.length; i++) { + matches[i] = tail.match(this.children[i].regex); + } + var best = false; + var b_i = false; + for (var i = 0; i < this.children.length; i++) + if (matches[i] && (!best || best.index > matches[i].index)) { + best = matches[i]; + b_i = i; + } + while (best) { + if ((best.index > 0) && (this.fallback)) + head += this.fallback.markUp(tail.substring(0,best.index)); + else + head += tail.substring(0,best.index); + head += this.children[b_i].rule(best); + var chopped = best.index + best[0].length; + tail = tail.substring(chopped); + for (var i = 0; i < this.children.length; i++) + if (matches[i]) + if (matches[i].index >= chopped) + matches[i].index -= chopped; + else + matches[i] = tail.match(this.children[i].regex); + best = false; + for (var i = 0; i < this.children.length; i++) + if (matches[i] && (!best || best.index > matches[i].index)) { + best = matches[i]; + b_i = i; + } + } + if (tail.length > 0 && this.fallback) + tail = this.fallback.markUp(tail); + return head + tail; +} + +//// Test the renderer /////////////////////////////////////////////////////// +toXHTML.UnitTest = function() { + var results = "<table class=\"unit-test\"><tr><th>Name</th><th>Status</th>"+ + "</tr>"; + for (var i = 0; i < toXHTML.UnitTest.tests.length; i++) { + var test = toXHTML.UnitTest.tests[i]; + var input = test.input; + var expected = test.output; + var actual = toXHTML(input); + results += "<tr><td>" + test.name + "</td><td>"; + if (expected == actual) + results += "Success"; + else { + results += "<strong>Failure</strong>" + "</td><td>"; + results += actual.replace(/&/g, "&").replace(/</g, "<"). + replace(/\n/g, "\\n"); + } + results += "</td></tr>"; + } + results += "</table>"; + return results; +} +toXHTML.UnitTest.tests = [ + { + name: "Basic paragraph markup", + input: "Basic paragraph test with <, >, & and \"", + output: "<p>Basic paragraph test with <, >, & and "</p>" + }, + { + name: "Simple unordered list", + input: "* list item\n*list item 2", + output: "<ul><li> list item</li>\n<li>list item 2</li></ul>" + }, + { + name: "Simple ordered list", + input: "# list item\n#list item 2", + output: "<ol><li> list item</li>\n<li>list item 2</li></ol>" + }, + { // Test an ul item with a sublist + name: "Unordered item with unordered sublist", + input: "* Item\n** Subitem", + output: "<ul><li> Item<ul>\n<li> Subitem</li></ul></li></ul>" + }, + { // Test a sublist without an initial tag (should not make a list) + name: "Unordered sublist without initial tag", + input: "** Sublist item", + output: "<p>** Sublist item</p>" + }, + { // Test an ol item with a sublist + name: "Ordered item with ordered sublist", + input: "# Item\n## Subitem", + output: "<ol><li> Item<ol>\n<li> Subitem</li></ol></li></ol>" + }, + { // Test a sublist without an initial tag (should not make a list) + name: "Ordered sublist without initial tag", + input: "## Sublist item", + output: "<p>## Sublist item</p>" + }, + { // Test an unordered list with an ordered sublist + name: "Unordered item with ordered sublist", + input: "* Item\n*# Subitem", + output: "<ul><li> Item<ol>\n<li> Subitem</li></ol></li></ul>" + }, + { // Test hr + name: "Horizontal rule", + input: "Some text\n----\nSome more text", + output: "<p>Some text</p><hr /><p>Some more text</p>" + }, + { // Test pre block + name: "Preformatted block", + input: "{{{\nPreformatted block\n}}}", + output: "<pre>Preformatted block\n</pre>" + }, + { // Test two pre blocks + name: "Two preformatted blocks", + input: "{{{\nPreformatted block\n}}}\n{{{Block 2}}}", + output: "<pre>Preformatted block\n</pre><pre>Block 2</pre>" + }, + { // Test h1 + name: "h1", + input: "= Header =", + output: "<h1>Header</h1>" + }, + { // Test h2 + name: "h2", + input: "== Header =", + output: "<h2>Header</h2>" + }, + { // Test h3 + name: "h3", + input: "=== Header =", + output: "<h3>Header</h3>" + }, + { // Test h4 + name: "h4", + input: "==== Header =", + output: "<h4>Header</h4>" + }, + { // Test h5 + name: "h5", + input: "===== Header", + output: "<h5>Header</h5>" + }, + { // Test h6 + name: "h6", + input: "====== Header =", + output: "<h6>Header</h6>" + }, + { // Test above h6 (should be ignored) + name: ">h6", + input: "======= Header =", + output: "<p>======= Header =</p>" + }, + { // Test tables + name: "Tables", + input: "| A | B |\n| //C// | **D** \\\\ E |", + output: "<table><tr><td> A </td><td> B </td></tr>" + + "<tr><td> <em>C</em> </td>" + + "<td> <strong>D</strong> <br /> E </td></tr></table>" + }, + { // Test raw URL + name: "Raw URL", + input: "http://example.com/examplepage", + output: "<p><a href=\"http://example.com/examplepage\">" + + "http://example.com/examplepage</a></p>" + }, + { // Test unnamed URL + name: "Unnamed URL", + input: "[[http://example.com/examplepage]]", + output: "<p><a href=\"http://example.com/examplepage\">" + + "http://example.com/examplepage</a></p>" + }, + { // Test named URL + name: "Named URL", + input: "[[http://example.com/examplepage|Example Page]]", + output: "<p>" + + "<a href=\"http://example.com/examplepage\">Example Page</a></p>" + }, + { // Test unnamed link + name: "Unnamed link", + input: "[[MyPage]]", + output: "<p><a href=\"MyPage\">MyPage</a></p>" + }, + { // Test named link + name: "Named link", + input: "[[MyPage|My page]]", + output: "<p><a href=\"MyPage\">My page</a></p>" + }, + { // Test images + name: "Image", + input: "{{image.gif|my image}}", + output: "<p><img src=\"image.gif\" alt=\"my image\"/></p>" + }, + { // Test inline tt + name: "Inline tt", + input: "Inline {{{tt}}} example {{{here}}}!", + output: "<p>Inline <tt>tt</tt> example <tt>here</tt>!</p>" + }, + { // Test **strong** + name: "Strong", + input: "**Strong**", + output: "<p><strong>Strong</strong></p>" + }, + { // Test //emphasis// + name: "Emphasis", + input: "//Emphasis//", + output: "<p><em>Emphasis</em></p>" + }, + + //// WikiCreole tests + { // Tests multi-line emphasis behaviour + name: "Multi-line emphasis", + input: "Bold and italics should //be\nable// to cross lines.\n\n" + + "But, should //not be...\n\n...able// to cross paragraphs.", + output: "<p>Bold and italics should <em>be\nable</em> to cross lines." + + "\n</p>" + "<p>\nBut, should //not be...\n</p>" + + "<p>\n...able// to cross paragraphs.</p>" + }, + { // Tests URL/emphasis ambiguity handling + name: "URL/emphasis ambiguity", + input: "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//.", + output: "<p>This is an <em>italic</em> text. This is a url: " + + "<a href=\"http://www.wikicreole.org\">" + + "http://www.wikicreole.org</a>. This is what can go wrong:" + + "<em>this should be an italic text</em>.</p>" + }, + + //// Awkward emphasis edge cases + { + name: "Difficult emphasis #1", + input: "// http://www.link.org //", + output: "<p><em> <a href=\"http://www.link.org\">" + + "http://www.link.org</a> </em></p>" + }, + { + name: "Difficult emphasis #2", + input: "// http //", + output: "<p><em> http </em></p>" + }, + { + name: "Difficult emphasis #3", + input: "// httphpthtpht //", + output: "<p><em> httphpthtpht </em></p>" + }, + { + name: "Difficult emphasis #4", + input: "// http: //", + output: "<p><em> http: </em></p>" + }, + { + name: "Difficult emphasis #5", + input: "// http://", + output: "<p>// <a href=\"http://\">http://</a></p>" + }, + { + name: "Difficult emphasis #6", + input: "// http:////", + output: "<p><em> <a href=\"http://\">http://</a></em></p>" + }, + { + name: "Difficult emphasis #7", + input: "//httphpthtphtt//", + output: "<p><em>httphpthtphtt</em></p>" + }, + { + name: "Difficult emphasis #8", + input: "//http://link.org//", + output: "<p><em><a href=\"http://link.org\">" + + "http://link.org</a></em></p>" + }, + { + name: "Difficult emphasis #9", + input: "// ftp://www.link.org //", + output: "<p><em> <a href=\"ftp://www.link.org\">" + + "ftp://www.link.org</a> </em></p>" + }, + { + name: "Difficult emphasis #10", + input: "// ftp //", + output: "<p><em> ftp </em></p>" + }, + { + name: "Difficult emphasis #11", + input: "// fttpfptftpft //", + output: "<p><em> fttpfptftpft </em></p>" + }, + { + name: "Difficult emphasis #12", + input: "// ftp: //", + output: "<p><em> ftp: </em></p>" + }, + { + name: "Difficult emphasis #13", + input: "// ftp://", + output: "<p>// <a href=\"ftp://\">ftp://</a></p>" + }, + { + name: "Difficult emphasis #14", + input: "// ftp:////", + output: "<p><em> <a href=\"ftp://\">ftp://</a></em></p>" + }, + { + name: "Difficult emphasis #15", + input: "//fttpfptftpftt//", + output: "<p><em>fttpfptftpftt</em></p>" + }, + { + name: "Difficult emphasis #16", + input: "//ftp://link.org//", + output: "<p><em><a href=\"ftp://link.org\">" + + "ftp://link.org</a></em></p>" + } +]; + + //// Install the renderer ////////////////////////////////////////////// + function updateRender() { + $("Html").innerHTML = toXHTML($("Text").value); + } + function installRenderer() { + element = $("Text"); + element.onkeyup = element.onkeypress = element.ondrop = + element.onchange = updateRender; + updateRender(); + $("UnitTest").innerHTML = "<h1>Unit Testing</h1>" + + toXHTML.UnitTest(); + } + window.onload = installRenderer; +// ]]> + </script> + </head> + <body> + <div id="Main"> + <form action=""> + <p><textarea name="Text" id="Text" class="WikiText" rows="20" cols="50"> += Creole + +{{http://www.wikicreole.org/attach/LeftMenu/viki.png|Creole}}\\ +Creole is a common wiki markup language intended to be used across many different wikis. Its aim is not to replace existing markup, but instead to enable wiki users to transfer basic content seamlessly across wikis, and lower the barrier to entry for novice users. + += Regular Language–Recursive Descent Parser + +This text has been formatted using a //regular language–recursive descent// (RLRD) parser design. That is, rules are applied in a recursive descent that matches the ultimate XML output; at each level of the descent, a set of regular expressions define the text that each child rule can "consume". Rules are applied greedily (i.e. earliest-match first). Unlike standard markup designs, this makes edge-cases between rules explicit, and allows a parser to be certified XHTML-compliant. + +The parser is written in Javascript, allowing greater flexibility in the deployment of the parser. The underlying RLRD design can be implemented in any language. + += Live Preview + +This document demonstrates a live Javascript preview, using this RLRD renderer. Editing the above text area will change this text. + +The markup follows the basic rules of [[http://www.wikicreole.org/wiki/Creole0.4|Creole v0.4]].</textarea></p> + </form> + <div id="Html" class="RenderedText">Javascript disabled</div> + </div> + <div id="UnitTest"></div> + </body> +</html> 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 <g s01 de> + * 2019-2022 Karl Bartel <[email protected]> + * 2022 bzt + * + * See LICENSE for further informations + */ +#include <ctype.h> +#include <regex.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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, "<pre><code>", "\n</code></pre>" }, + { "\t", 0, "<pre><code>", "\n</code></pre>" }, + { ">", 2, "<blockquote>", "</blockquote>" }, + { "###### ", 1, "<h6>", "</h6>" }, + { "##### ", 1, "<h5>", "</h5>" }, + { "#### ", 1, "<h4>", "</h4>" }, + { "### ", 1, "<h3>", "</h3>" }, + { "## ", 1, "<h2>", "</h2>" }, + { "# ", 1, "<h1>", "</h1>" }, + { "- - -\n", 1, "<hr />", ""}, + { "---\n", 1, "<hr />", ""}, +}; + +static Tag underline[] = { + { "=", 1, "<h1>", "</h1>\n" }, + { "-", 1, "<h2>", "</h2>\n" }, +}; + +static Tag surround[] = { + { "```", 0, "<code>", "</code>" }, + { "``", 0, "<code>", "</code>" }, + { "`", 0, "<code>", "</code>" }, + { "___", 1, "<strong><em>", "</em></strong>" }, + { "***", 1, "<strong><em>", "</em></strong>" }, + { "__", 1, "<strong>", "</strong>" }, + { "**", 1, "<strong>", "</strong>" }, + { "_", 1, "<em>", "</em>" }, + { "*", 1, "<em>", "</em>" }, +}; + +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", "<br />\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("</p>\n", stdout); + in_paragraph = 0; + } +} + +int +docomment(const char *begin, const char *end, int newblock) { + char *p; + + if (nohtml || strncmp("<!--", begin, 4)) + return 0; + p = strstr(begin, "-->"); + 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("<pre><code>", stdout); + } else { + fputs("<pre><code class=\"language-", stdout); + hprint(lang_start, lang_stop); + fputs("\">", stdout); + } + hprint(start, stop); + fputs("</code></pre>\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 < end) { + p += 2; + if (strncmp(p, tag, tagend - tag) == 0 && p[tagend - tag] == '>') { + 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, ") || p > end) + return 0; + for (q = strstr(desc, ") || 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("<img src=\"", stdout); + hprint(link, linkend); + fputs("\" alt=\"", stdout); + hprint(desc, descend); + fputs("\" ", stdout); + if (title && titleend) { + fputs("title=\"", stdout); + hprint(title, titleend); + fputs("\" ", stdout); + } + fputs("/>", stdout); + } + else { + fputs("<a href=\"", stdout); + hprint(link, linkend); + fputs("\"", stdout); + if (title && titleend) { + fputs(" title=\"", stdout); + hprint(title, titleend); + fputs("\"", stdout); + } + fputs(">", stdout); + process(desc, descend, 0); + fputs("</a>", 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("<ul>\n", stdout); + } else if (start_number == 1) { + fputs("<ol>\n", stdout); + } else { + printf("<ol start=\"%d\">\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("<li>", stdout); + process(buffer, buffer + i, isblock > 1 || (isblock == 1 && run)); + fputs("</li>\n", stdout); + } + fputs(marker ? "</ul>\n" : "</ol>\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, "</t%c></tr>", inrow == -1 ? 'h' : 'd'); + if (inrow == -1) + intable = 2; + inrow = 0; + if(end - begin <= 2 || begin[2] == '\n') { + intable = 0; + fputs("\n</table>\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("<table>\n<tr>", stdout); + } + if(!inrow) { /* open row */ + inrow = 1; incell = 0; + fputs("<tr>", stdout); + } + if(incell) /* close cell */ + fprintf(stdout, "</t%c>", inrow == -1 ? 'h' : 'd'); + l = incell < l ? (calign >> (incell * 2)) & 3 : 0; /* open cell */ + fprintf(stdout, "<t%c%s>", 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("<p>", 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("<a href=\"", stdout); + if (ismail == 1) { + /* mailto: */ + fputs("mailto:", stdout); + for (c = begin + 1; *c != '>'; c++) + fprintf(stdout, "&#%u;", *c); + fputs("\">", stdout); + for (c = begin + 1; *c != '>'; c++) + fprintf(stdout, "&#%u;", *c); + } + else { + hprint(begin + 1, p); + fputs("\">", stdout); + hprint(begin + 1, p); + } + fputs("</a>", 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.h> // assert +#include <stdint.h> // uintptr_t +#include <stdio.h> // fprintf +#include <stdlib.h> // abort, malloc +#include <stdnoreturn.h> // noreturn +#include <string.h> // 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 <stddef.h> // 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 <assert.h> +#include <dirent.h> +#include <stdio.h> +#include <glob.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +//#include <ftw.h> + +#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 <assert.h> +#include <ctype.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> + +#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, "<h%u>", level); + process(begin, stop, false, out); + fprintf(out, "</h%u>", 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 <stddef.h> // size_t +#include <stdio.h> // 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.h> // assert +#include <stdio.h> // fprintf, vfprintf, fputc, stderr +#include <stdlib.h> // exit, EXIT_FAILURE +#include <stdarg.h> // va_* +#include <git2.h> // git_* +#include <string.h> // strerror +#include <errno.h> // 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 <stdnoreturn.h> // 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 <assert.h> +#include <errno.h> // errno, EEXIST +#include <git2.h> // git_* +#include <stdbool.h> // false +#include <stdio.h> +#include <stdlib.h> // EXIT_SUCCESS +#include <sys/stat.h> // mkdir +#include <sys/types.h> // 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.h> // assert +#include <stdarg.h> // va_* +#include <stdbool.h> // bool, false +#include <stdio.h> // vsnprintf +#include <string.h> // 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 <stdbool.h> // bool +#include <stdarg.h> // 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 @@ +<p>======= Header =</p>
\ 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 @@ +<p>Basic paragraph test with <, >, & and "</p>
\ 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 @@ +<p><em> <a href="http://www.link.org">http://www.link.org</a> </em></p>
\ 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 @@ +<p><em> ftp </em></p>
\ 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 @@ +<p><em> fttpfptftpft </em></p>
\ 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 @@ +<p><em> ftp: </em></p>
\ 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 @@ +<p>// <a href="ftp://">ftp://</a></p>
\ 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 @@ +<p><em> <a href="ftp://">ftp://</a></em></p>
\ 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 @@ +<p><em>fttpfptftpftt</em></p>
\ 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 @@ +<p><em><a href="ftp://link.org">ftp://link.org</a></em></p>
\ 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 @@ +<p><em> http </em></p>
\ 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 @@ +<p><em> httphpthtpht </em></p>
\ 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 @@ +<p><em> http: </em></p>
\ 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 @@ +<p>// <a href="http://">http://</a></p>
\ 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 @@ +<p><em> <a href="http://">http://</a></em></p>
\ 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 @@ +<p><em>httphpthtphtt</em></p>
\ 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 @@ +<p><em><a href="http://link.org">http://link.org</a></em></p>
\ 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 @@ +<p><em> <a href="ftp://www.link.org">ftp://www.link.org</a> </em></p>
\ 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 --- /dev/null +++ b/test/dummy-without-corresponding-output.input.txt 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 @@ +<p><em>Emphasis</em></p>
\ 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 @@ +<h1>Header</h1>
\ 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 @@ +<h2>Header</h2>
\ 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 @@ +<h3>Header</h3>
\ 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 @@ +<h4>Header</h4>
\ 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 @@ +<h5>Header</h5>
\ 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 @@ +<h6>Header</h6>
\ 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 @@ +<p>Some text</p><hr /><p>Some more text</p>
\ 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 @@ +<p><img src="image.gif" alt="my image"/></p>
\ 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 @@ +<p>Inline <tt>tt</tt> example <tt>here</tt>!</p>
\ 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 @@ +<p>Bold and italics should <em>be +able</em> to cross lines. +</p><p> +But, should //not be... +</p><p> +...able// to cross paragraphs.</p>
\ 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 @@ +<p><a href="MyPage">My page</a></p>
\ 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 @@ +<p><a href="http://example.com/examplepage">Example Page</a></p>
\ 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 @@ +<ol><li> Item<ol> +<li> Subitem</li></ol></li></ol>
\ 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 @@ +<p>## Sublist item</p>
\ 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 @@ +<pre>Preformatted block +</pre>
\ 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 @@ +<p><a href="http://example.com/examplepage">http://example.com/examplepage</a></p>
\ 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 @@ +<ol><li> list item</li> +<li>list item 2</li></ol>
\ 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 @@ +<ul><li> list item</li> +<li>list item 2</li></ul>
\ 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 @@ +<p><strong>Strong</strong></p>
\ 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 @@ +<table><tr><td> A </td><td> B </td></tr><tr><td> <em>C</em> </td><td> <strong>D</strong> <br /> E </td></tr></table>
\ 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 @@ +<pre>Preformatted block +</pre><pre>Block 2</pre>
\ 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 @@ +<p><a href="MyPage">MyPage</a></p>
\ 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 @@ +<p><a href="http://example.com/examplepage">http://example.com/examplepage</a></p>
\ 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 @@ +<ul><li> Item<ol> +<li> Subitem</li></ol></li></ul>
\ 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 @@ +<ul><li> Item<ul> +<li> Subitem</li></ul></li></ul>
\ 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 @@ +<p>** Sublist item</p>
\ No newline at end of file diff --git a/test/url∕emphasis-ambiguity.input.txt b/test/url∕emphasis-ambiguity.input.txt new file mode 100644 index 0000000..4c29ff9 --- /dev/null +++ b/test/url∕emphasis-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∕emphasis-ambiguity.output.txt b/test/url∕emphasis-ambiguity.output.txt new file mode 100644 index 0000000..9be7ec8 --- /dev/null +++ b/test/url∕emphasis-ambiguity.output.txt @@ -0,0 +1 @@ +<p>This is an <em>italic</em> text. This is a url: <a href="http://www.wikicreole.org">http://www.wikicreole.org</a>. This is what can go wrong:<em>this should be an italic text</em>.</p>
\ No newline at end of file |