├── test ├── codeblock_with_language │ ├── test.mup │ └── test.html ├── strong │ ├── test.mup │ └── test.html ├── code │ ├── test.mup │ └── test.html ├── underline │ ├── test.mup │ └── test.html ├── emphasis │ ├── test.mup │ └── test.html ├── image │ ├── test.mup │ └── test.html ├── link │ ├── test.mup │ └── test.html ├── escape │ ├── test.mup │ └── test.html ├── codeblock │ ├── test.mup │ └── test.html ├── bulletlist │ ├── test.mup │ └── test.html ├── sortedlist │ ├── test.mup │ └── test.html ├── unsanitized │ ├── test.mup │ └── test.html ├── head │ ├── test.mup │ └── test.html └── blockquote │ ├── test.mup │ └── test.html ├── package.json ├── autotest.js ├── test.js ├── bin └── mortup.js ├── index.js └── readme.md /test/codeblock_with_language/test.mup: -------------------------------------------------------------------------------- 1 | ``` javascript 2 | console.log("I'm javascript"); 3 | ``` 4 | -------------------------------------------------------------------------------- /test/strong/test.mup: -------------------------------------------------------------------------------- 1 | This line has *some* text that is *bold*. 2 | *This line is all bold text.* 3 | -------------------------------------------------------------------------------- /test/code/test.mup: -------------------------------------------------------------------------------- 1 | This text has `inline code` in it, `multiple places`. 2 | `This line is all inline code.` 3 | -------------------------------------------------------------------------------- /test/underline/test.mup: -------------------------------------------------------------------------------- 1 | This line has _some_ text that is _underlined_. 2 | _This line is all underlined._ 3 | -------------------------------------------------------------------------------- /test/emphasis/test.mup: -------------------------------------------------------------------------------- 1 | This line has //some// text that is //emphasized//. 2 | //This line is all emphasized.// 3 | -------------------------------------------------------------------------------- /test/codeblock_with_language/test.html: -------------------------------------------------------------------------------- 1 |

2 | 	console.log("I'm javascript");
3 | 
4 | -------------------------------------------------------------------------------- /test/emphasis/test.html: -------------------------------------------------------------------------------- 1 | This line has some text that is emphasized.
2 | This line is all emphasized.
-------------------------------------------------------------------------------- /test/underline/test.html: -------------------------------------------------------------------------------- 1 | This line has some text that is underlined.
2 | This line is all underlined.
3 | -------------------------------------------------------------------------------- /test/image/test.mup: -------------------------------------------------------------------------------- 1 | This line !(Some Image) contains an image. 2 | !(Another Image) 3 | -------------------------------------------------------------------------------- /test/code/test.html: -------------------------------------------------------------------------------- 1 | This text has inline code in it, multiple places.
2 | This line is all inline code.
-------------------------------------------------------------------------------- /test/strong/test.html: -------------------------------------------------------------------------------- 1 | This line has some text that is bold.
2 | This line is all bold text.
-------------------------------------------------------------------------------- /test/link/test.mup: -------------------------------------------------------------------------------- 1 | This contains an (inline link). 2 | (This entire line is a link.) 3 | -------------------------------------------------------------------------------- /test/image/test.html: -------------------------------------------------------------------------------- 1 | This line Some Image contains an image.
2 | Another Image
-------------------------------------------------------------------------------- /test/escape/test.mup: -------------------------------------------------------------------------------- 1 | This tests HTML escaping. 2 | We don't
want people to have unsanitized HTML in their documents. 3 | 4 | -------------------------------------------------------------------------------- /test/link/test.html: -------------------------------------------------------------------------------- 1 | This contains an inline link.
2 | This entire line is a link.
-------------------------------------------------------------------------------- /test/escape/test.html: -------------------------------------------------------------------------------- 1 | This tests <strong>HTML</strong> escaping.
2 | We don't <div>want people to</span> have unsanitized HTML in their documents.
3 | <script> alert("yo") </script>
4 | -------------------------------------------------------------------------------- /test/codeblock/test.mup: -------------------------------------------------------------------------------- 1 | ``` 2 | There be *dragons* and //things// here! 3 | This should `not be inline code`, because *that* would be silly. 4 | if (:D): 5 | die(); 6 | 7 | This line ``` 8 | ```doesn't end the code block. 9 | ``` 10 | -------------------------------------------------------------------------------- /test/bulletlist/test.mup: -------------------------------------------------------------------------------- 1 | *this is a test of *unsorted lists* 2 | * any amount of whitespace should be allowed 3 | * whitespaaace 4 | * `oh, and code` 5 | 6 | Then we have some random text. 7 | 8 | * And now another list, briefly. 9 | -------------------------------------------------------------------------------- /test/sortedlist/test.mup: -------------------------------------------------------------------------------- 1 | 1. This is a list starting at 1. 2 | 2. Continuing at *2*, with `code` and //things//. 3 | 4 | This is some random text. 5 | 6 | 50. This list starts at 50. 7 | 65. Then continues on to 65. 8 | 9 | More random text. 10 | -------------------------------------------------------------------------------- /test/codeblock/test.html: -------------------------------------------------------------------------------- 1 |

 2 | There be *dragons* and //things// here!
 3 | This should `not be inline code`, because *that* would be silly.
 4 | if (:D):
 5 | 	die();
 6 | 
 7 | This line ```
 8 | ```doesn't end the code block.
 9 | 
10 | -------------------------------------------------------------------------------- /test/unsanitized/test.mup: -------------------------------------------------------------------------------- 1 | This is
sanitized HTML. 2 | 3 | !!!! 4 | This is
unsanitized HTML. 5 | 6 | It doesn't *care* about _formatting_. 7 | !!!! 8 | Back to . 9 | -------------------------------------------------------------------------------- /test/unsanitized/test.html: -------------------------------------------------------------------------------- 1 | This is <div>sanitized HTML.</span>
2 |
3 | 4 | This is
unsanitized HTML. 5 | 6 | It doesn't *care* about _formatting_. 7 | 8 | Back to <script>sanitized HTML</script>.
9 | -------------------------------------------------------------------------------- /test/head/test.mup: -------------------------------------------------------------------------------- 1 | #This is a h1. 2 | ## This is also a header. 3 | ### Another header, this time h3. 4 | #### I have so many headers. 5 | ##### Another header, this time h5. 6 | ###### Header, both *bold* and //emphasized//, with `code`. 7 | 8 | # #### This is a h1, despite the extra hashes. 9 | 10 | -------------------------------------------------------------------------------- /test/bulletlist/test.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • this is a test of unsorted lists
  • 3 |
  • any amount of whitespace should be allowed
  • 4 |
  • whitespaaace
  • 5 |
  • oh, and code
  • 6 |
7 | 8 | Then we have some random text.
9 |
10 |
    11 |
  • And now another list, briefly.
  • 12 |
-------------------------------------------------------------------------------- /test/blockquote/test.mup: -------------------------------------------------------------------------------- 1 | >>> 2 | This is a quote. 3 | >>> 4 | 5 | >>> 6 | This is a rather long quote. 7 | It goes over multiple lines. 8 | >>> 9 | 10 | >>> 11 | Now we are testing *formatting in block quotes*. 12 | //This is interesting for sure.// 13 | ``` 14 | We can have code in here. 15 | ``` 16 | We can also have `inline code`. 17 | >>> 18 | -------------------------------------------------------------------------------- /test/head/test.html: -------------------------------------------------------------------------------- 1 |

This is a h1.

2 |

This is also a header.

3 |

Another header, this time h3.

4 |

I have so many headers.

5 |
Another header, this time h5.
6 |
Header, both bold and emphasized, with code.
7 |
8 |

#### This is a h1, despite the extra hashes.

-------------------------------------------------------------------------------- /test/sortedlist/test.html: -------------------------------------------------------------------------------- 1 |
    2 |
  1. This is a list starting at 1.
  2. 3 |
  3. Continuing at 2, with code and things.
  4. 4 |
5 | 6 | This is some random text.
7 |
8 |
    9 |
  1. This list starts at 50.
  2. 10 |
  3. Then continues on to 65.
  4. 11 |
12 | 13 | More random text.
-------------------------------------------------------------------------------- /test/blockquote/test.html: -------------------------------------------------------------------------------- 1 |
2 | This is a quote.
3 |
4 |
5 |
6 | This is a rather long quote.
7 | It goes over multiple lines.
8 |
9 |
10 |
11 | Now we are testing formatting in block quotes.
12 | This is interesting for sure.
13 |

14 | 	We can have code in here.
15 | 
16 | We can also have inline code.
17 |
18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mortup", 3 | "version": "1.6.0", 4 | "description": "A simple markup language.", 5 | "main": "index.js", 6 | "bin": "./bin/mortup.js", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mortie/mortup.git" 13 | }, 14 | "scripts": { 15 | "test": "node test.js" 16 | }, 17 | "keywords": [ 18 | "markup" 19 | ], 20 | "author": { 21 | "name": "Martin Dørum Nygaard", 22 | "email": "martid0311@gmail.com", 23 | "url": "http://mort.coffee" 24 | }, 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /autotest.js: -------------------------------------------------------------------------------- 1 | var mortup = require("./index.js"); 2 | var fs = require("fs"); 3 | 4 | var passedTests = 0; 5 | var failedTests = 0; 6 | 7 | var dirs = fs.readdirSync("test"); 8 | dirs.forEach(function(dir) 9 | { 10 | if (fs.existsSync("test/"+dir+"/test.html")) 11 | return console.log(dir+"/test.html exists. Skipping."); 12 | 13 | fs.readFile("test/"+dir+"/test.mup", "utf8", function(err, res) 14 | { 15 | if (err) throw err; 16 | 17 | var html = mortup(res.trim(), {allow_unsanitized: true}); 18 | fs.writeFile("test/"+dir+"/test.html", html, function(err) 19 | { 20 | if (err) throw err; 21 | 22 | console.log("Made "+dir+"/test.html."); 23 | }); 24 | }); 25 | }); 26 | 27 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var mortup = require("./index.js"); 2 | var fs = require("fs"); 3 | 4 | var passedTests = 0; 5 | var failedTests = 0; 6 | var testsDone = 0; 7 | var numTests; 8 | 9 | var dirs = fs.readdirSync("test"); 10 | numTests = dirs.length; 11 | dirs.forEach(function(dir) 12 | { 13 | var cbs = 2; 14 | var mup; 15 | var html; 16 | 17 | fs.readFile("test/"+dir+"/test.mup", "utf8", function(err, res) 18 | { 19 | if (err) throw err; 20 | mup = res.trim(); 21 | cbs -= 1; 22 | if (cbs === 0) 23 | test(dir, mup, html); 24 | }); 25 | 26 | fs.readFile("test/"+dir+"/test.html", "utf8", function(err, res) 27 | { 28 | if (err) throw err; 29 | html = res.trim(); 30 | cbs -= 1; 31 | if (cbs === 0) 32 | test(dir, mup, html); 33 | }); 34 | }); 35 | 36 | function test(name, mup, html) 37 | { 38 | testhtml = mortup(mup, {allow_unsanitized: true}); 39 | if (testhtml !== html) 40 | { 41 | console.log("########## Test "+name+" failed! ##########"); 42 | console.log("Input:\n\n'"+mup+"'\n"); 43 | console.log("Should become:\n\n'"+html+"'\n"); 44 | console.log("Became:\n\n'"+testhtml+"'\n\n"); 45 | failedTests += 1; 46 | } 47 | else 48 | { 49 | console.log("Passed "+name); 50 | passedTests += 1; 51 | } 52 | 53 | testsDone += 1; 54 | if (testsDone === numTests) 55 | conclude(); 56 | } 57 | 58 | function conclude() 59 | { 60 | console.log(); 61 | console.log("Tests total: ", numTests); 62 | console.log("Tests passed: ", passedTests); 63 | console.log("Tests failed: ", failedTests); 64 | } 65 | -------------------------------------------------------------------------------- /bin/mortup.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | var mortup = require('../'); 5 | 6 | function usage() { 7 | console.error(`Usage: mortup [-hu] [file] 8 | 9 | Options: 10 | \t-h - show this help message. 11 | \t-u - allow unsanitized text. 12 | 13 | mortup will read text from stdin if no file given.`); 14 | } 15 | 16 | var options = { 17 | allow_unsanitized: false 18 | }; 19 | 20 | var filename = null; 21 | 22 | // Cut the first two arguments, the interpreter (/usr/bin/node) and the filename of this script 23 | process.argv.splice(0, 2); 24 | 25 | while (process.argv.length) { 26 | var arg = process.argv.shift(); 27 | 28 | // Parse flag 29 | if (arg.indexOf('-') === 0) { 30 | // Split multiple flags 31 | // Multiple flags like -hu 32 | if (arg.length > 2) { 33 | process.argv = arg.substring(1).split('').map(function(ch) { 34 | return '-' + ch; 35 | }).concat(process.argv); 36 | arg = process.argv.shift(); 37 | } 38 | } 39 | 40 | switch (arg) { 41 | case '-h': 42 | usage(); 43 | process.exit(0); 44 | break; 45 | case '-u': 46 | options.allow_unsanitized = true; 47 | break; 48 | default: 49 | if (arg.charAt(0) === '-') { 50 | usage(); 51 | process.exit(1); 52 | } 53 | 54 | filename = arg; 55 | break; 56 | } 57 | } 58 | 59 | var file 60 | 61 | if (typeof filename == "string") { 62 | file = fs.createReadStream(filename); 63 | } else { 64 | // Read from stdin if no file given. 65 | file = process.stdin; 66 | } 67 | 68 | var readline = require('readline'); 69 | var rd = readline.createInterface({ 70 | input: file, 71 | output: process.stdout, 72 | terminal: false 73 | }); 74 | var str = "" 75 | 76 | rd.on('line', function(line) { 77 | str += line + '\n'; 78 | }); 79 | 80 | rd.on('close', function() { 81 | console.log(mortup(str, options)); 82 | }); 83 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var patterns = 2 | [ 3 | { 4 | name: "raw_end", 5 | regex: /^\!\!\!\!$/, 6 | state: "raw", 7 | endstate: "raw", 8 | last: true 9 | }, { 10 | name: "raw_start", 11 | regex: /^\!\!\!\!$/, 12 | startstate: "raw", 13 | last: true 14 | }, { 15 | name: "raw", 16 | regex: /(.*)/, 17 | state: "raw", 18 | last: true 19 | }, 20 | { 21 | name: "codeblock_end", 22 | regex: /^```$/, 23 | state: "codeblock", 24 | endstate: "codeblock", 25 | last: true 26 | }, { 27 | name: "codeblock_start", 28 | regex: /^```(?:\s+([^\s]+))?$/, 29 | startstate: "codeblock", 30 | last: true 31 | }, { 32 | name: "codeblock", 33 | regex: /(.*)/, 34 | state: "codeblock", 35 | last: true 36 | }, 37 | { 38 | name: "emphasis", 39 | regex: /(.*)\/\/(.+)\/\/(.*)/ 40 | }, { 41 | name: "strong", 42 | regex: /(.*)\*(.+)\*(.*)/ 43 | }, { 44 | name: "underline", 45 | regex: /(.*)_(.+)_(.*)/ 46 | }, { 47 | name: "code", 48 | regex: /(.*)`(.+)`(.*)/ 49 | }, { 50 | name: "image", 51 | regex: /(.*)\!\((.*)\)<(.*)>(.*)/ 52 | }, { 53 | name: "link", 54 | regex: /(.*)\((.*)\)<(.*)>(.*)/ 55 | }, 56 | { 57 | name: "head", 58 | regex: /^(#+)\s*(.+)/, 59 | last: true 60 | }, 61 | { 62 | name: "blockquote_end", 63 | regex: /^>>>$/, 64 | state: "blockquote", 65 | endstate: "blockquote", 66 | last: true 67 | }, { 68 | name: "blockquote_start", 69 | regex: /^>>>$/, 70 | startstate: "blockquote", 71 | last: true 72 | }, 73 | { 74 | name: "sortedlist", 75 | regex: /^\s*([0-9]+)\.\s+(.*)/, 76 | state: "sortedlist", 77 | last: true 78 | }, { 79 | name: "sortedlist_start", 80 | regex: /^\s*([0-9]+)\.\s+(.*)/, 81 | startstate: "sortedlist", 82 | last: true 83 | }, { 84 | name: "sortedlist_end", 85 | regex: /(.*)/, 86 | state: "sortedlist", 87 | endstate: "sortedlist", 88 | last: true, 89 | lastrun: true 90 | }, 91 | { 92 | name: "bulletlist", 93 | regex: /^\s*\*\s*(.*)/, 94 | state: "bulletlist", 95 | last: true 96 | }, { 97 | name: "bulletlist_start", 98 | regex: /^\s*\*\s*(.*)/, 99 | startstate: "bulletlist", 100 | last: true 101 | }, { 102 | name: "bulletlist_end", 103 | regex: /(.*)/, 104 | state: "bulletlist", 105 | endstate: "bulletlist", 106 | last: true, 107 | lastrun: true 108 | }, 109 | { 110 | name: "newline", 111 | regex: /(.*)/, 112 | last: true 113 | }, 114 | { 115 | name: "blockquote", 116 | regex: /(.*)/, 117 | state: "blockquote", 118 | last: true 119 | } 120 | ] 121 | 122 | var states = { 123 | raw: 0, 124 | codeblock: 0, 125 | blockquote: 0, 126 | sortedlist: 0, 127 | bulletlist: 0 128 | } 129 | 130 | module.exports = function(str, opts) 131 | { 132 | var res = ""; 133 | var prevpattern = "NONE"; 134 | str = str.trim(); 135 | str += "\n"; 136 | 137 | str.split("\n").map(function(line, linenum, arr) 138 | { 139 | var p, i, m, j, t, shouldbreak; 140 | var compiled = line.replace(//g, ">"); 141 | var prevcompiled = line; 142 | var firstrun = false; 143 | var lastrun = (linenum + 1 === arr.length); 144 | 145 | while (true) 146 | { 147 | //Find the first matching pattern 148 | for (i = 0; i < patterns.length; ++i) 149 | { 150 | p = patterns[i]; 151 | t = p.regex.test(compiled); 152 | if (t && (!lastrun || p.lastrun)) 153 | { 154 | m = compiled.match(p.regex); 155 | 156 | if (p.state === undefined) 157 | break; 158 | 159 | shouldbreak = false; 160 | if (states[p.state] > 0) 161 | break; 162 | } 163 | else 164 | { 165 | m = false; 166 | } 167 | } 168 | 169 | if (p.startstate) 170 | states[p.startstate] += 1; 171 | if (p.endstate && states[p.endstate] > 0) 172 | states[p.endstate] -= 1; 173 | 174 | //Stop if there is no match 175 | if (!m) 176 | break; 177 | 178 | prevpattern = p.name; 179 | 180 | //Compile 181 | compiled = compile(p.name, m, opts); 182 | 183 | if (compiled === prevcompiled || p.last) 184 | break; 185 | 186 | prevcompiled = compiled; 187 | firstrun = true; 188 | } 189 | 190 | res += compiled; 191 | if (!lastrun) 192 | res += "\n"; 193 | }); 194 | 195 | return res.substring(0, res.length - 1); 196 | } 197 | 198 | function compile(p, match, opts) 199 | { 200 | switch (p) 201 | { 202 | case "strong": 203 | return match[1]+""+match[2]+""+match[3]; 204 | case "emphasis": 205 | return match[1]+""+match[2]+""+match[3]; 206 | case "underline": 207 | return match[1]+""+match[2]+""+match[3]; 208 | case "code": 209 | return match[1]+""+match[2]+""+match[3]; 210 | case "image": 211 | return match[1]+"\""+match[2]+"\""+match[4]; 212 | case "link": 213 | return match[1]+""+match[2]+""+match[4]; 214 | 215 | case "head": 216 | var n = match[1].length; 217 | return ""+match[2]+""; 218 | 219 | case "raw_start": 220 | if (opts.allow_unsanitized === true) 221 | return ""; 222 | else 223 | throw new Error("Unsanitized code is disabled."); 224 | case "raw": 225 | if (opts.allow_unsanitized === true) 226 | return match[1].replace(/>/g, ">").replace(/</g, "<"); 227 | else 228 | return match[1]; 229 | case "raw_end": 230 | return ""; 231 | 232 | case "codeblock_start": 233 | if (match[1]) 234 | return "
";
235 | 		else
236 | 			return "
";
237 | 	case "codeblock":
238 | 		return match[1];
239 | 	case "codeblock_end":
240 | 		return "
"; 241 | 242 | case "blockquote_start": 243 | return "
"; 244 | case "blockquote": 245 | return match[1]; 246 | case "blockquote_end": 247 | return "
"; 248 | 249 | case "sortedlist_start": 250 | return "
    \n
  1. "+match[2]+"
  2. "; 251 | case "sortedlist": 252 | return "
  3. "+match[2]+"
  4. "; 253 | case "sortedlist_end": 254 | return "
\n"+match[1]; 255 | 256 | case "bulletlist_start": 257 | return "
    \n
  • "+match[1]+"
  • "; 258 | case "bulletlist": 259 | return "
  • "+match[1]+"
  • "; 260 | case "bulletlist_end": 261 | return "
\n"+match[1]; 262 | 263 | case "newline": 264 | return match[1]+"
"; 265 | 266 | default: 267 | throw new Error("Pattern "+p+" doesn't exist."); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Mortup Markup Language 2 | 3 | This is a library for node.js for compiling mortup to HTML. The reason I've made it is that while Markdown is nice enough, it does have some issues which I dislike. 4 | 5 | ## Usage 6 | 7 | Install: `npm install mortup` 8 | 9 | Use: 10 | 11 | ``` 12 | var mortup = require("mortup"); 13 | 14 | console.log(mortup("Some *mortup* text", { 15 | allow_unsanitized: false 16 | })); 17 | ``` 18 | 19 | ## The Mortup Language 20 | 21 | ### Why this language exists 22 | 23 | Here are the features with Markdown which annoy me, and the reasons I decided to create my own format. 24 | 25 | * **Blockquotes**. With Markdown, I can't really figure out a nice way to quote paragraphs. You either have to insert hard newlines and prefix each line with '>', which makes the plaintext markdown look pretty but is usually a lot of unnecessary work, or leave the markdown looking ugly. I fix this by making blockquotes work like code blocks, except with ">>>" instead of "```". 26 | * **Bold and italics**. In markdown, both `__something__` and `**something**` creates strong text (bold), while both `_something_` and `*something*` creates emphasized text (italics). I find this to be redundant and silly, and decided to make `*something*` strong, `//something//` emphasized, and `_something`_ underlined. 27 | * **Links**. Links in markdown looks like this: `[Some link text](http://example.com)`. I find that to be backwards. In text, citations are often in brackets, and even Markdown's footnotes use brackets. Therefore, I made the link syntax `(Some link text)`. The new image syntax is `!(Some alt text)`. 28 | * **Ordered lists**. With Markdown, regardless of how your plaintext markdown text looks like, sorted lists will always start at 1, and each new element will always increment by 1. This is an unnecessary constraint, as HTML has a native way of dealing with non-sequential ordered lists, and even "ordered" lists where the numbers aren't ordered. 29 | * **Two spaces at the end of the line** creates a hard newline in Markdown. I find that to be ridiculous. That means two lines can look exactly identical, yet produce different HTML. It's also easy to accidentally include those two spaces where they're not intended. I fixed this by just inserting a `
` anywhere the page breaks. I know there are some people who disagree with this, but I feel that it's a decent solution, and more intuitive than what Markdown does. 30 | * **Code blocks**. In Markdown, there are many ways to denote that something is a code block; one is to indent consecutive lines, which is hard in a `