├── deno.json ├── test ├── fixtures │ ├── empty-file.rmu │ ├── hello-rimu.html │ ├── hello-rimu.rmu │ ├── prepend-file-2.rmu │ └── prepend-file.rmu ├── deps.ts ├── rimu_test.ts ├── rimuc_test.ts └── validate-rimu-ports.ts ├── src ├── resources │ ├── plain-footer.rmu │ ├── plain-header.rmu │ ├── flex-footer.rmu │ ├── classic-footer.rmu │ ├── sequel-footer.rmu │ ├── v8-footer.rmu │ ├── manpage.txt │ └── v8-header.rmu ├── node │ ├── README │ └── rimuc.ts └── deno │ ├── deps.ts │ ├── rimu.ts │ ├── document.ts │ ├── io.ts │ ├── utils.ts │ ├── quotes.ts │ ├── options.ts │ ├── replacements.ts │ ├── blockattributes.ts │ ├── lists.ts │ ├── macros.ts │ ├── rimuc.ts │ ├── spans.ts │ └── lineblocks.ts ├── mod.ts ├── docs ├── favicon.ico ├── image-1.jpg ├── image-2.jpg ├── plain-example.html ├── classic-legend-no-toc-example.html ├── classic-vintage-no-toc-example.html ├── classic-graystone-no-toc-example.html ├── flex-legend-no-toc-example.html ├── flex-vintage-no-toc-example.html ├── flex-graystone-no-toc-example.html └── sequel-legend-no-toc-example.html ├── docsrc ├── image-1.jpg ├── image-2.jpg ├── gallery-example-template.rmu ├── gallery.rmu ├── rimuplayground.rmu ├── manpage.rmu └── doc-header.rmu ├── .ignore ├── .gitignore ├── examples ├── nodejs-minimal-example.ts ├── deno-minimal-example.ts ├── index.html ├── vim │ ├── example-rimurc.vim │ └── rimu.vim └── example-rimurc.rmu ├── tsconfig-cjs.json ├── tsconfig.json ├── .editorconfig ├── package.json ├── .github └── workflows │ └── test.yml ├── LICENSE └── README.md /deno.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/empty-file.rmu: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/hello-rimu.html: -------------------------------------------------------------------------------- 1 | Hello *Rimu*! -------------------------------------------------------------------------------- /test/fixtures/hello-rimu.rmu: -------------------------------------------------------------------------------- 1 | Hello *Rimu*! 2 | -------------------------------------------------------------------------------- /test/fixtures/prepend-file-2.rmu: -------------------------------------------------------------------------------- 1 | {x} = 'X2' 2 | -------------------------------------------------------------------------------- /test/fixtures/prepend-file.rmu: -------------------------------------------------------------------------------- 1 | {x} = 'X' 2 | -------------------------------------------------------------------------------- /src/resources/plain-footer.rmu: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- 1 | // Rimu Deno module. 2 | 3 | export * from "./src/deno/rimu.ts"; 4 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/rimu/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /docs/image-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/rimu/HEAD/docs/image-1.jpg -------------------------------------------------------------------------------- /docs/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/rimu/HEAD/docs/image-2.jpg -------------------------------------------------------------------------------- /docsrc/image-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/rimu/HEAD/docsrc/image-1.jpg -------------------------------------------------------------------------------- /docsrc/image-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srackham/rimu/HEAD/docsrc/image-2.jpg -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | # Honored by fd and rg and consequentially by the Neovim Telescope find and grep commands. 2 | # Takes precedence over .gitignore 3 | !tmp/ 4 | -------------------------------------------------------------------------------- /src/node/README: -------------------------------------------------------------------------------- 1 | Rimu Node.js TypeScript source files are generated from the same-named Deno 2 | TypeScript source files by the Drakefile.ts 'build-node' task. -------------------------------------------------------------------------------- /src/deno/deps.ts: -------------------------------------------------------------------------------- 1 | export * as path from "https://deno.land/std@0.173.0/path/mod.ts"; 2 | export { readAll } from "https://deno.land/std@0.173.0/streams/mod.ts"; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitattributes 2 | .npmrc 3 | .vscode/ 4 | .drake.cache.json 5 | tmp/ 6 | node_modules/ 7 | lib/ 8 | src/node/ 9 | !src/node/rimuc.ts 10 | !src/node/README 11 | -------------------------------------------------------------------------------- /examples/nodejs-minimal-example.ts: -------------------------------------------------------------------------------- 1 | // Minimal Rimu Node.js example. 2 | 3 | import * as rimu from "rimu"; 4 | 5 | console.log(rimu.render("Hello *Rimu*!", { safeMode: 1 })); 6 | -------------------------------------------------------------------------------- /tsconfig-cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "outDir": "./lib/cjs", 6 | "sourceMap": true 7 | }, 8 | } -------------------------------------------------------------------------------- /examples/deno-minimal-example.ts: -------------------------------------------------------------------------------- 1 | // Minimal Rimu Deno example. 2 | 3 | import * as rimu from "https://deno.land/x/rimu@11.4.0/mod.ts"; 4 | 5 | console.log(rimu.render("Hello *Rimu*!")); 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "ES6", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "useUnknownInCatchVariables": false, 8 | "declaration": true, 9 | "outDir": "./lib/esm" 10 | }, 11 | "include": [ 12 | "./src/node" 13 | ] 14 | } -------------------------------------------------------------------------------- /test/deps.ts: -------------------------------------------------------------------------------- 1 | export { 2 | assert, 3 | assertEquals, 4 | assertNotEquals, 5 | } from "https://deno.land/std@0.173.0/testing/asserts.ts"; 6 | export { 7 | env, 8 | readFile, 9 | shCapture, 10 | } from "https://deno.land/x/drake@v1.7.0/lib.ts"; 11 | export type { ShOutput } from "https://deno.land/x/drake@v1.7.0/lib.ts"; 12 | -------------------------------------------------------------------------------- /src/resources/plain-header.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Used by rimuc `--layout plain` option. 3 | */ 4 | 5 | {--lang?} = '' 6 | {--title?} = 'Title' 7 | {--meta?} = '' 8 | // Additional element children. 9 | {--head?} = '' 10 | 11 | 12 | {--lang=} 13 | {--lang!} 14 | 15 | {--meta} 16 | {--title} 17 | 18 | {--head} 19 | 20 | 21 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Defaults 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | trim_trailing_whitespace = true 11 | 12 | [*.{js,ts}] 13 | indent_size = 2 14 | 15 | # The indent size used in the `package.json` file cannot be changed 16 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 17 | [*.{json,yml,yaml}] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/vim/example-rimurc.vim: -------------------------------------------------------------------------------- 1 | " 2 | " Custom Vim highlighting for additional custom syntax defined in 3 | " example-rimurc file. Rename this file to ~/.vim/after/syntax/rimu.vim and 4 | " it will be automatically sourced by Vim. 5 | " 6 | " NOTE: This file is sourced after the default ~/.vim/syntax/rimu.vim 7 | " syntax file (which is distributed in ./examples/vim/). 8 | " 9 | 10 | " Admonishments. 11 | syn match rimuAdmonition /^\\\@", 4 | "version": "11.4.0", 5 | "description": "Readable text to HTML markup language", 6 | "keywords": [ 7 | "rimu", 8 | "markup", 9 | "asciidoc", 10 | "markdown" 11 | ], 12 | "license": "MIT", 13 | "homepage": "https://srackham.github.io/rimu/", 14 | "main": "lib/cjs/rimu.js", 15 | "types": "lib/cjs/rimu.d.ts", 16 | "module": "lib/esm/rimu.js", 17 | "bin": { 18 | "rimuc": "lib/cjs/rimuc.js" 19 | }, 20 | "files": [ 21 | "LICENSE", 22 | "README.md", 23 | "lib/" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/srackham/rimu.git" 28 | }, 29 | "devDependencies": { 30 | "@types/node": "^22.10.7", 31 | "html-validator-cli": "^7.0.1", 32 | "rollup": "^4.30.1", 33 | "terser": "^5.37.0", 34 | "typescript": "^5.7.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build-test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | deno-version: [2.1.4] 15 | node-version: [20.x] 16 | os: [ubuntu-latest, macos-latest, windows-latest] 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v4 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Use Deno ${{ matrix.deno-version }} 25 | uses: denoland/setup-deno@v1 26 | with: 27 | deno-version: v${{ matrix.deno-version }} 28 | - name: Run tests 29 | run: | 30 | npm install 31 | deno cache src/deno/rimuc.ts 32 | deno test -A test/rimu_test.ts 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Stuart Rackham 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /src/deno/document.ts: -------------------------------------------------------------------------------- 1 | import { BlockAttributes } from "./blockattributes.ts"; 2 | import * as DelimitedBlocks from "./delimitedblocks.ts"; 3 | import * as Io from "./io.ts"; 4 | import * as LineBlocks from "./lineblocks.ts"; 5 | import * as Lists from "./lists.ts"; 6 | import * as Macros from "./macros.ts"; 7 | import * as Options from "./options.ts"; 8 | import * as Quotes from "./quotes.ts"; 9 | import * as Replacements from "./replacements.ts"; 10 | 11 | export function render(source: string): string { 12 | const reader = new Io.Reader(source); 13 | const writer = new Io.Writer(); 14 | while (!reader.eof()) { 15 | reader.skipBlankLines(); 16 | if (reader.eof()) break; 17 | if (LineBlocks.render(reader, writer)) continue; 18 | if (Lists.render(reader, writer)) continue; 19 | if (DelimitedBlocks.render(reader, writer)) continue; 20 | // This code should never be executed (normal paragraphs should match anything). 21 | Options.panic("no matching delimited block found"); 22 | } 23 | return writer.toString(); 24 | } 25 | 26 | // Set API to default state. 27 | export function init(): void { 28 | BlockAttributes.init(); 29 | Options.init(); 30 | DelimitedBlocks.init(); 31 | Macros.init(); 32 | Quotes.init(); 33 | Replacements.init(); 34 | } 35 | -------------------------------------------------------------------------------- /examples/example-rimurc.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Custom Rimu Markup definitions for use by the rimuc command. 3 | Rename this file to ~/.rimurc and it will be loaded automatically by rimurc. 4 | */ 5 | 6 | 7 | /* 8 | Replacements 9 | */ 10 | // Typographic replacements. 11 | // NOTE: — and → replacements generate false positives in inline HTML comment tags. 12 | /\\?--/ = '—' 13 | /\\?\.{3}/ = '…' 14 | /\\?->/ = '→' 15 | /\\?<-/ = '←' 16 | /\\?\([cC]\)/ = '©' 17 | /\\?\((tm|TM)\)/ = '™' 18 | /\\?\+-/ = '±' 19 | 20 | // Admonishments. 21 | /\\?\bTODO\b/ = 'TODO' 22 | // DEPRECATED: Prefer rimuc important, note, tip and warning layout styles. 23 | /^\\?(NOTES?|IMPORTANT|WARNINGS?|TIPS?):/ = '$1:' 24 | 25 | // Horizontal rule (3 or more underscore, asterisk or hyphen characters). 26 | /^(_{3,}|\*{3,}|-{3,})$/ = '
' 27 | 28 | // Keyword comments. 29 | /\\?@(deprecated|fixme|important|note|todo|warning)\b.*/ = '' 30 | 31 | 32 | /* 33 | Macros 34 | */ 35 | // Text formatting e.g. H{sub|2}O. 36 | {mark} = '$1' 37 | {del} = '$1' 38 | {ins} = '$1' 39 | {sup} = '$1' 40 | {sub} = '$1' 41 | 42 | 43 | /* 44 | Quotes 45 | */ 46 | // Underline (overrides built-in strong quote). 47 | __ = '|' 48 | // Left and right double-quote characters. 49 | " = '“|”' 50 | // Don't break code quotes. 51 | //` = '|' -------------------------------------------------------------------------------- /docsrc/gallery-example-template.rmu: -------------------------------------------------------------------------------- 1 | # rimuc layout and theme example 2 | 3 | .no-auto-toc 4 | ### `{gallery-options}` 5 | 6 | ## Ultricies lundium 7 | 8 | Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 9 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 10 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 11 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 12 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut. 13 | 14 | ### Est vel montes. 15 | - Dictumst sed duis sagittis odio scelerisque penatibus dolor, 16 | scelerisque penatibus. 17 | 18 | - Porta magna tincidunt a rhoncus tortor sit integer pulvinar. 19 | 20 | * Et sit proin penatibus scelerisque porttitor in? Auctor et. 21 | 22 | * Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat. 23 | 24 | .sidebar 25 | .. 26 | #### Parturient ultrices lorem 27 | Tempor, integer ridiculus a rhoncus. Ac turpis massa enim pulvinar 28 | facilisis in dignissim mauris, lacus? Turpis cum, auctor velit? 29 | Turpis, dapibus, velit vel ultrices mattis, tempor, urna natoque diam. 30 | Aliquam! Cum augue est elementum. Parturient ultrices lorem, montes 31 | risus eros nunc, mattis, odio turpis porttitor nunc augue? Natoque 32 | lacus. 33 | .. 34 | 35 | Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 36 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 37 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 38 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 39 | habitasse. 40 | 41 | `` 42 | Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing 43 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna 44 | porta natoque elementum velit placerat aenean dis tincidunt. 45 | `` 46 | -------------------------------------------------------------------------------- /docs/plain-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 |

rimuc layout and theme example

9 | 10 |

Ultricies lundium

11 |

Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 12 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 13 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 14 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 15 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

16 |

Est vel montes.

17 | 25 |

Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 26 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 27 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 28 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 29 | habitasse.

30 |
Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
31 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
32 | porta natoque elementum velit placerat aenean dis tincidunt.
33 | 34 | -------------------------------------------------------------------------------- /test/rimu_test.ts: -------------------------------------------------------------------------------- 1 | import * as rimu from "../mod.ts"; 2 | import { assert, assertEquals, env, readFile } from "./deps.ts"; 3 | 4 | env("--abort-exits", false); 5 | 6 | interface RenderOptions { 7 | safeMode?: number; 8 | htmlReplacement?: string; 9 | reset?: boolean; 10 | callback?: CallbackFunction; 11 | } 12 | 13 | interface CallbackMessage { 14 | type: string; 15 | text: string; 16 | } 17 | 18 | type CallbackFunction = (message: CallbackMessage) => void; 19 | 20 | type RimuTest = { 21 | description: string; 22 | input: string; 23 | expectedOutput: string; 24 | expectedCallback: string; 25 | options: { reset: boolean; callback: CallbackFunction }; 26 | }; 27 | 28 | function catchLint(message: CallbackMessage): never { 29 | // Should never be called. 30 | console.log(message.type + ": " + message.text); 31 | throw new Error(); 32 | } 33 | 34 | Deno.test("rimuApiTest", function (): void { 35 | assert(rimu.render.constructor === Function, "Rimu.render is a function"); 36 | }); 37 | 38 | Deno.test("rimuTest", function (): void { 39 | // Execute tests specified in JSON file. 40 | const data = readFile("./test/rimu-tests.json"); 41 | const tests: RimuTest[] = JSON.parse(data); 42 | let i = 0; 43 | for (const test of tests) { 44 | i++; 45 | const msg = `${i}: ${test.description}`; 46 | console.log(msg); 47 | let callbackMsg = ""; 48 | if (test.expectedCallback === "") { 49 | test.options.callback = catchLint; 50 | } else { 51 | test.options.callback = function (message: CallbackMessage): void { 52 | callbackMsg += message.type + ": " + message.text + "\n"; 53 | }; 54 | } 55 | const rendered = rimu.render(test.input, test.options); 56 | assertEquals( 57 | rendered, 58 | test.expectedOutput, 59 | `${test.description}: actual: "${rendered}": expected: "${test.expectedOutput}"`, 60 | ); 61 | if (test.expectedCallback !== "") { 62 | assertEquals( 63 | callbackMsg.trim(), 64 | test.expectedCallback, 65 | `${test.description}: actual: "${callbackMsg.trimEnd()}": expected: "${test.expectedCallback}"`, 66 | ); 67 | } 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /src/deno/io.ts: -------------------------------------------------------------------------------- 1 | export class Reader { 2 | lines: string[]; 3 | pos: number; // Line index of current line. 4 | 5 | constructor(text: string) { 6 | text = text.replace("\u0000", " "); // Used internally by spans package. 7 | text = text.replace("\u0001", " "); // Used internally by spans package. 8 | text = text.replace("\u0002", " "); // Used internally by macros package. 9 | // Split lines on newline boundaries. 10 | // http://stackoverflow.com/questions/1155678/javascript-string-newline-character 11 | // Split is broken on IE8 e.g. 'X\n\nX'.split(/\n/g).length) returns 2 but should return 3. 12 | this.lines = text.split(/\r\n|\r|\n/g); 13 | this.pos = 0; 14 | } 15 | 16 | get cursor(): string { 17 | console.assert(!this.eof()); 18 | return this.lines[this.pos]; 19 | } 20 | 21 | set cursor(value: string) { 22 | console.assert(!this.eof()); 23 | this.lines[this.pos] = value; 24 | } 25 | 26 | // Return true if the cursor has advanced over all input lines. 27 | eof(): boolean { 28 | return this.pos >= this.lines.length; 29 | } 30 | 31 | // Move cursor to next input line. 32 | next(): void { 33 | if (!this.eof()) this.pos++; 34 | } 35 | 36 | // Read to the first line matching the re. 37 | // Return the array of lines preceding the match plus a line containing 38 | // the $1 match group (if it exists). 39 | // If an EOF is encountered return all lines. 40 | // Exit with the reader pointing to the line containing the matched line. 41 | readTo(find: RegExp): string[] { 42 | const result: string[] = []; 43 | let match: string[] | null = null; 44 | while (!this.eof()) { 45 | match = this.cursor.match(find); 46 | if (match) { 47 | if (match[1] !== undefined) { 48 | result.push(match[1]); // $1 49 | } 50 | break; 51 | } 52 | result.push(this.cursor); 53 | this.next(); 54 | } 55 | return result; 56 | } 57 | 58 | skipBlankLines(): void { 59 | while (!this.eof() && this.cursor.trim() === "") { 60 | this.next(); 61 | } 62 | } 63 | } 64 | 65 | export class Writer { 66 | buffer: string[]; // Appending an array is faster than string concatenation. 67 | 68 | constructor() { 69 | this.buffer = []; 70 | } 71 | 72 | write(s: string): void { 73 | this.buffer.push(s); 74 | } 75 | 76 | toString(): string { 77 | return this.buffer.join(""); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/resources/flex-footer.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Used by rimuc.js --styled option. 3 | */ 4 | 5 | // Close article div. 6 | 7 | 8 | {--highlightjs=}.+skip 9 | {--highlightjs-scripts} 10 | 11 | {--mathjax!}{--mathjax-scripts} 12 | 13 | .+skip 14 | {--no-toc=}.-skip 15 | {--header-links!}.-skip 16 | 26 | 27 | {--header-links=}.+skip 28 | 39 | 40 | {--no-toc!}.+skip 41 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /docsrc/gallery.rmu: -------------------------------------------------------------------------------- 1 | # Rimu Gallery 2 | 3 | Here are some examples of styled HTML documents generated using the 4 | {rimuc} command `--layout` option. 5 | 6 | Click the options links to view the generated documents. 7 | 8 | See [Built-in layouts]({reference}#built-in-layouts) for more information. 9 | 10 | 11 | ## sequel layout 12 | 13 | ### legend theme 14 | - [`--layout sequel --theme legend`](sequel-legend-example.html) 15 | - [`--layout sequel --theme legend --no-toc`](sequel-legend-no-toc-example.html) 16 | 17 | ### vintage theme 18 | - [`--layout sequel --theme vintage`](sequel-vintage-example.html) 19 | - [`--layout sequel --theme vintage --no-toc`](sequel-vintage-no-toc-example.html) 20 | 21 | ### graystone theme 22 | - [`--layout sequel --theme graystone`](sequel-graystone-example.html) 23 | - [`--layout sequel --theme graystone --no-toc`](sequel-graystone-no-toc-example.html) 24 | 25 | 26 | ## classic layout 27 | 28 | ### legend theme 29 | - [`--layout classic --theme legend`](classic-legend-example.html) 30 | - [`--layout classic --theme legend --prepend "\{--dropdown-toc}='yes'"`](classic-legend-dropdown-toc-example.html) 31 | - [`--layout classic --theme legend --no-toc`](classic-legend-no-toc-example.html) 32 | 33 | ### vintage theme 34 | - [`--layout classic --theme vintage`](classic-vintage-example.html) 35 | - [`--layout classic --theme vintage --prepend "\{--dropdown-toc}='yes'"`](classic-vintage-dropdown-toc-example.html) 36 | - [`--layout classic --theme vintage --no-toc`](classic-vintage-no-toc-example.html) 37 | 38 | ### graystone theme 39 | - [`--layout classic --theme graystone`](classic-graystone-example.html) 40 | - [`--layout classic --theme graystone --prepend "\{--dropdown-toc}='yes'"`](classic-graystone-dropdown-toc-example.html) 41 | - [`--layout classic --theme graystone --no-toc`](classic-graystone-no-toc-example.html) 42 | 43 | 44 | ## flex layout 45 | 46 | ### legend theme 47 | - [`--layout flex --theme legend`](flex-legend-example.html) 48 | - [`--layout flex --theme legend --no-toc`](flex-legend-no-toc-example.html) 49 | 50 | ### vintage theme 51 | - [`--layout flex --theme vintage`](flex-vintage-example.html) 52 | - [`--layout flex --theme vintage --no-toc`](flex-vintage-no-toc-example.html) 53 | 54 | ### graystone theme 55 | - [`--layout flex --theme graystone`](flex-graystone-example.html) 56 | - [`--layout flex --theme graystone --no-toc`](flex-graystone-no-toc-example.html) 57 | 58 | 59 | ## plain layout 60 | - [`--layout plain --no-toc`](plain-example.html) 61 | 62 | -------------------------------------------------------------------------------- /src/resources/classic-footer.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Used by rimuc.js --styled option. 3 | */ 4 | 5 | // Close article div. 6 | 7 | 8 | {--highlightjs=}.+skip 9 | {--highlightjs-scripts} 10 | 11 | {--mathjax!}{--mathjax-scripts} 12 | 13 | .+skip 14 | {--no-toc=}.-skip 15 | {--header-links!}.-skip 16 | 25 | 26 | {--header-links=}.+skip 27 | 38 | 39 | {--no-toc!}.+skip 40 | 59 | 60 | {--dropdown-toc=}.+skip 61 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/resources/sequel-footer.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Used by rimuc `--layout sequel` option. 3 | */ 4 | 5 | // Close main and article divs. 6 | 7 | 8 | 9 | {--highlightjs=}.+skip 10 | {--highlightjs-scripts} 11 | 12 | {--mathjax!}{--mathjax-scripts} 13 | 14 | {--no-toc!}.+skip 15 | 28 | 29 | {--header-links=}.+skip 30 | 41 | 42 | {--no-toc!}.+skip 43 | 62 | 63 | {--no-toc!}.+skip 64 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /docsrc/rimuplayground.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Rimu Playground 3 | */ 4 | 5 | // Show/Hide button. 6 | {showhide} = '' 7 | 8 | // NOTE: HTML special characters are escaped. 9 | {examples} = ' 10 | Some **bold text**, some _emphasized text_, some `monospaced text` and 11 | some ~~deleted text~~. 12 | 13 | A [link to the Rimu website](https://srackham.github.io/rimu/), an auto-encoded 14 | link https://srackham.github.io/rimu/. 15 | 16 | > Quote paragraph. 17 | 18 | `` 19 | Code block. 20 | `` 21 | 22 | - A bullet list item. 23 | - A 2nd bullet list item. 24 | . Nested numbered list item. 25 | ' 26 | 27 | {box-shadow} = 'box-shadow: 0 0 10px #9ecaed;' 28 | 29 | 30 | # Rimu Playground 31 | 32 | ## {showhide|playground-source} Rimu markup 33 | .#playground-source 34 | .. 35 | Edit these examples or add your own: 36 | 37 |
38 | 40 |
41 | 42 | Diagnostic messages: 43 | 44 | .#playground-diagnostics "display: none; {box-shadow} color: #d61123; background-color: #f8d7da; border: 1px solid #f5c6cb; font-weight: bold; margin-top: 0.5rem" 45 | `` 46 | `` 47 | .. 48 | 49 | 50 | ## {showhide|playground-preview} HTML preview 51 | .#playground-preview "border: 1px solid #dddddd; margin-top: 0.5rem; padding: 4px; {box-shadow}" 52 | .. 53 | .. 54 | 55 | 56 | ## {showhide|playground-html} HTML markup 57 | .#playground-html "margin-top: 0.5rem; {box-shadow}" 58 | `` 59 | `` 60 | 61 | 62 | 100 | -------------------------------------------------------------------------------- /src/deno/utils.ts: -------------------------------------------------------------------------------- 1 | // deno-lint-ignore-file no-explicit-any 2 | 3 | import * as Macros from "./macros.ts"; 4 | import * as Options from "./options.ts"; 5 | import * as Spans from "./spans.ts"; 6 | 7 | export interface ExpansionOptions { 8 | [key: string]: boolean | undefined; 9 | 10 | // Processing priority (highest to lowest): container, skip, spans and specials. 11 | // If spans is true then both spans and specials are processed. 12 | // They are assumed false if they are not explicitly defined. 13 | // If a custom filter is specified their use depends on the filter. 14 | macros?: boolean; 15 | container?: boolean; 16 | skip?: boolean; 17 | spans?: boolean; // Span substitution also expands special characters. 18 | specials?: boolean; 19 | } 20 | 21 | // http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript 22 | export function escapeRegExp(s: string): string { 23 | return s.replace(/[\-\/\\^$*+?.()|\[\]{}]/g, "\\$&"); 24 | } 25 | 26 | export function replaceSpecialChars(s: string): string { 27 | return s.replace(/&/g, "&") 28 | .replace(/>/g, ">") 29 | .replace(/ 8 | 9 | {--highlightjs!} 10 | 11 | {--mathjax!} 12 | 13 | 42 | 43 | .+skip 44 | {--sidebar-toc!}.-skip 45 | {--dropdown-toc!}.-skip 46 | 64 | 65 | {--dropdown-toc=}.+skip 66 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/deno/quotes.ts: -------------------------------------------------------------------------------- 1 | import * as Utils from "./utils.ts"; 2 | 3 | export interface Definition { 4 | quote: string; // Single quote character. 5 | openTag: string; 6 | closeTag: string; 7 | spans: boolean; // Allow span elements inside quotes. 8 | } 9 | 10 | let defs: Definition[] // Mutable definitions initialized by DEFAULT_DEFS. 11 | ; 12 | 13 | const DEFAULT_DEFS: Definition[] = [ 14 | { 15 | quote: "**", 16 | openTag: "", 17 | closeTag: "", 18 | spans: true, 19 | }, 20 | { 21 | quote: "*", 22 | openTag: "", 23 | closeTag: "", 24 | spans: true, 25 | }, 26 | { 27 | quote: "__", 28 | openTag: "", 29 | closeTag: "", 30 | spans: true, 31 | }, 32 | { 33 | quote: "_", 34 | openTag: "", 35 | closeTag: "", 36 | spans: true, 37 | }, 38 | { 39 | quote: "``", 40 | openTag: "", 41 | closeTag: "", 42 | spans: false, 43 | }, 44 | { 45 | quote: "`", 46 | openTag: "", 47 | closeTag: "", 48 | spans: false, 49 | }, 50 | { 51 | quote: "~~", 52 | openTag: "", 53 | closeTag: "", 54 | spans: true, 55 | }, 56 | ]; 57 | 58 | export let quotesRe: RegExp // Searches for quoted text. 59 | ; 60 | let unescapeRe: RegExp // Searches for escaped quotes. 61 | ; 62 | 63 | // Reset definitions to defaults. 64 | export function init(): void { 65 | defs = DEFAULT_DEFS.map((def) => Utils.copy(def)); 66 | initializeRegExps(); 67 | } 68 | 69 | // Synthesise re's to find and unescape quotes. 70 | export function initializeRegExps(): void { 71 | const quotes = defs.map((def) => Utils.escapeRegExp(def.quote)); 72 | // $1 is quote character(s), $2 is quoted text. 73 | // Quoted text cannot begin or end with whitespace. 74 | // Quoted can span multiple lines. 75 | // Quoted text cannot end with a backslash. 76 | quotesRe = RegExp( 77 | "\\\\?(" + quotes.join("|") + ")([^\\s\\\\]|\\S[\\s\\S]*?[^\\s\\\\])\\1", 78 | "g", 79 | ); 80 | // $1 is quote character(s). 81 | unescapeRe = RegExp("\\\\(" + quotes.join("|") + ")", "g"); 82 | } 83 | 84 | // Return the quote definition corresponding to 'quote' character, return undefined if not found. 85 | export function getDefinition(quote: string): Definition { 86 | return defs.filter((def) => def.quote === quote)[0]; 87 | } 88 | 89 | // Strip backslashes from quote characters. 90 | export function unescape(s: string): string { 91 | return s.replace(unescapeRe, "$1"); 92 | } 93 | 94 | // Update existing or add new quote definition. 95 | export function setDefinition(def: Definition): void { 96 | for (const d of defs) { 97 | if (d.quote === def.quote) { 98 | // Update existing definition. 99 | d.openTag = def.openTag; 100 | d.closeTag = def.closeTag; 101 | d.spans = def.spans; 102 | return; 103 | } 104 | } 105 | // Double-quote definitions are prepended to the array so they are matched 106 | // before single-quote definitions (which are appended to the array). 107 | if (def.quote.length === 2) { 108 | defs.unshift(def); 109 | } else { 110 | defs.push(def); 111 | } 112 | initializeRegExps(); 113 | } 114 | -------------------------------------------------------------------------------- /test/rimuc_test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | assert, 3 | assertEquals, 4 | assertNotEquals, 5 | env, 6 | readFile, 7 | shCapture, 8 | ShOutput, 9 | } from "./deps.ts"; 10 | 11 | type RimucTest = { 12 | description: string; 13 | args: string; 14 | input: string; 15 | expectedOutput: string; 16 | exitCode?: number; 17 | predicate: string; 18 | layouts?: boolean; 19 | }; 20 | 21 | env("--abort-exits", false); 22 | 23 | type BuiltTarget = "deno" | "node" | undefined; 24 | 25 | async function runTest(test: RimucTest, buildTarget: BuiltTarget): Promise< 26 | void 27 | > { 28 | let shout: ShOutput; 29 | if (!buildTarget || buildTarget === "node") { 30 | shout = await shCapture( 31 | `node ./lib/cjs/rimuc.js --no-rimurc ${test.args ?? ""}`, 32 | { input: test.input, stderr: "piped" }, 33 | ); 34 | testShOut(shout, test); 35 | } 36 | if (!buildTarget || buildTarget === "deno") { 37 | shout = await shCapture( 38 | `deno run -A --quiet src/deno/rimuc.ts --no-rimurc ${ 39 | test 40 | .args ?? "" 41 | }`, 42 | { input: test.input, stderr: "piped" }, 43 | ); 44 | testShOut(shout, test); 45 | } 46 | } 47 | 48 | function testShOut( 49 | shout: ShOutput, 50 | test: RimucTest, 51 | ): void { 52 | const out = shout.error + shout.output; 53 | const msg = `${test.description}: ${JSON.stringify(test)}: ${ 54 | JSON.stringify( 55 | shout, 56 | ) 57 | }`; 58 | switch (test.predicate) { 59 | case "equals": 60 | assertEquals(out, test.expectedOutput, msg); 61 | break; 62 | case "!equals": 63 | assertNotEquals(out, test.expectedOutput, msg); 64 | break; 65 | case "contains": 66 | assert(out.indexOf(test.expectedOutput) >= 0, msg); 67 | break; 68 | case "!contains": 69 | assert(out.indexOf(test.expectedOutput) === -1, msg); 70 | break; 71 | case "startsWith": 72 | assert(out.startsWith(test.expectedOutput), msg); 73 | break; 74 | case "exitCode": 75 | assertEquals(out, test.expectedOutput, msg); 76 | assert(shout.code === test.exitCode, msg); 77 | break; 78 | default: 79 | assert( 80 | false, 81 | `${test.description}: illegal predicate: ${test.predicate}`, 82 | ); 83 | } 84 | } 85 | 86 | Deno.test("rimucTest", async function (): Promise { 87 | const buildTarget: BuiltTarget = Deno.env.get( 88 | "RIMU_BUILD_TARGET", 89 | ) as BuiltTarget; 90 | console.log(`platform: ${buildTarget ?? "deno, node"}`); 91 | // Execute tests specified in JSON file. 92 | const data = readFile("./test/rimuc-tests.json"); 93 | const tests: RimucTest[] = JSON.parse(data); 94 | let i = 0; 95 | for (const test of tests) { 96 | i++; 97 | const msg = `${i}: ${test.description}`; 98 | console.log(msg); 99 | if (test.layouts) { 100 | // Run the test on built-in layouts. 101 | const t = { ...test }; 102 | for (const layout of ["classic", "flex", "sequel"]) { 103 | t.args = `--layout ${layout} ${test.args}`; 104 | t.description = `${layout} layout: ${test.description}`; 105 | await runTest(t, buildTarget); 106 | } 107 | } else { 108 | await runTest(test, buildTarget); 109 | } 110 | } 111 | }); 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rimu Markup 2 | 3 | Rimu is a readable-text to HTML markup language inspired by AsciiDoc 4 | and Markdown. 5 | 6 | At its core Rimu is a simple readable-text markup similar in scope to 7 | Markdown, but with two additional areas of functionality (both built 8 | into the Rimu markup syntax): 9 | 10 | 1. Markup generation can be customized and extended. 11 | 2. Rimu includes a simple, flexible macro language. 12 | 13 | In addition, a subset of Rimu is compatible with a [subset of Markdown](https://srackham.github.io/rimu/tips.html#markdown-compatible) 14 | and Rimu has been ported to a number of [languages and runtime environments](https://srackham.github.io/rimu/reference.html#rimu-implementations). 15 | 16 | ## Learn more 17 | 18 | [Read the documentation](https://srackham.github.io/rimu/) and experiment with Rimu 19 | in the [Rimu Playground](https://srackham.github.io/rimu/rimuplayground.html) or 20 | open the `rimuplayground.html` file locally in your browser. 21 | 22 | See the Rimu [Change Log](https://srackham.github.io/rimu/changelog.html) for 23 | the latest changes. 24 | 25 | **NOTE**: The remainder of this document is specific to the 26 | [TypeScript implementation](https://github.com/srackham/rimu) for 27 | Node.js, Deno, and browser platforms. 28 | 29 | ## Quick start 30 | 31 | Try the Rimu library on the npm Runkit page: 32 | 33 | 1. Open the [Rimu npm Runkit page](https://npm.runkit.com/rimu) in your browser. 34 | 2. Paste in this code then press the _Run_ button. 35 | 36 | ```javascript 37 | const rimu = require("rimu"); 38 | const html = rimu.render("Hello *Rimu*!"); 39 | ``` 40 | 41 | This will output `"

Hello Rimu!

"`. 42 | 43 | ## Installing and using Rimu 44 | 45 | **Node.js** 46 | 47 | Use `npm` to install the Node.js Rimu library module and the `rimuc` 48 | CLI: 49 | 50 | npm install -g rimu 51 | 52 | Run a test from the command prompt to check the `rimuc` CLI command is 53 | working: 54 | 55 | echo 'Hello *Rimu*!' | rimuc 56 | 57 | This should print: 58 | 59 |

Hello Rimu!

60 | 61 | **Deno** 62 | 63 | Deno modules don't need explicit installation just import the module 64 | URL, for example: 65 | 66 | ```javascript 67 | import * as rimu from "https://deno.land/x/rimu@11.4.0/mod.ts"; 68 | 69 | console.log(rimu.render("Hello *Rimu*!")); 70 | ``` 71 | 72 | Use the Deno `install` command to install the Rimu CLI executable. 73 | The following example creates the CLI executable named `rimudeno` 74 | in `$HOME/.deno/bin/rimudeno`: 75 | 76 | deno install -A --name rimudeno https://deno.land/x/rimu@11.4.0/src/deno/rimuc.ts 77 | 78 | **Browser** 79 | 80 | Rimu builds JavaScript ES module files in the `./lib/esm` directory along with a 81 | bundled version `./lib/esm/rimu.min.js`. The `rimu.min.js` ES module file was 82 | bundled by [Rollup](https://github.com/rollup/rollup) and minimized with 83 | [terser](https://github.com/terser/terser). Example usage: 84 | 85 | ```html 86 | 90 | ``` 91 | 92 | ## Building Rimu and the Rimu documentation 93 | 94 | To build Rimu you need to have [Deno](https://deno.land/) and 95 | [Node.js](https://nodejs.org/) installed. 96 | 97 | 1. Install the Git repository from [Github](https://github.com/srackham/rimu). 98 | 99 | git clone https://github.com/srackham/rimu.git 100 | 101 | 2. Install dependencies: 102 | 103 | cd rimu 104 | npm install 105 | deno cache --reload src/deno/rimuc.ts 106 | 107 | 3. Use the [Drake](https://github.com/srackham/drake) task runner 108 | module to build and test Rimu library modules and CLIs for Deno and Node.js 109 | platforms: 110 | 111 | deno run -A Drakefile.ts build test 112 | -------------------------------------------------------------------------------- /src/deno/options.ts: -------------------------------------------------------------------------------- 1 | import * as Document from "./document.ts"; 2 | import * as Utils from "./utils.ts"; 3 | 4 | /** 5 | * An object with zero or more optional properties to control Rimu Markup 6 | * translation in the render() API. 7 | */ 8 | export interface RenderOptions { 9 | safeMode?: number; 10 | htmlReplacement?: string; 11 | reset?: boolean; 12 | callback?: CallbackFunction; 13 | } 14 | 15 | export interface CallbackMessage { 16 | type: string; 17 | text: string; 18 | } 19 | 20 | export type CallbackFunction = (message: CallbackMessage) => void; 21 | 22 | // Global option values. 23 | let safeMode: number; 24 | let htmlReplacement: string; 25 | let callback: CallbackFunction | undefined; 26 | 27 | // Reset options to default values. 28 | export function init(): void { 29 | safeMode = 0; 30 | htmlReplacement = "replaced HTML"; 31 | callback = undefined; 32 | } 33 | 34 | // Return true if safeMode is non-zero. 35 | export function isSafeModeNz(): boolean { 36 | return safeMode !== 0; 37 | } 38 | 39 | export function getSafeMode(): number { 40 | return safeMode; 41 | } 42 | 43 | // Return true if Macro Definitions are ignored. 44 | export function skipMacroDefs(): boolean { 45 | /* tslint:disable:no-bitwise */ 46 | return safeMode !== 0 && (safeMode & 0x8) === 0; 47 | /* tslint:enable:no-bitwise */ 48 | } 49 | 50 | // Return true if Block Attribute elements are ignored. 51 | export function skipBlockAttributes(): boolean { 52 | /* tslint:disable:no-bitwise */ 53 | return (safeMode & 0x4) !== 0; 54 | /* tslint:enable:no-bitwise */ 55 | } 56 | 57 | function setSafeMode(value: number | string | undefined): void { 58 | const n = Number(value); 59 | if (isNaN(n) || n < 0 || n > 15) { 60 | errorCallback("illegal safeMode API option value: " + value); 61 | return; 62 | } 63 | safeMode = n; 64 | } 65 | 66 | function setHtmlReplacement(value: string | undefined): void { 67 | if (value === undefined) return; 68 | htmlReplacement = value; 69 | } 70 | 71 | function setReset(value: boolean | string | undefined): void { 72 | if (value === false || value === "false") { 73 | return; 74 | } else if (value === true || value === "true") { 75 | Document.init(); 76 | } else { 77 | errorCallback("illegal reset API option value: " + value); 78 | } 79 | } 80 | 81 | export function updateOptions(options: RenderOptions): void { 82 | for (const key in options) { 83 | switch (key) { 84 | case "reset": 85 | case "safeMode": 86 | case "htmlReplacement": 87 | case "callback": 88 | break; 89 | default: 90 | errorCallback("illegal API option name: " + key); 91 | return; 92 | } 93 | } 94 | if ("callback" in options) callback = options.callback; // Install callback first to ensure option errors are logged. 95 | if ("reset" in options) setReset(options.reset); // Reset takes priority. 96 | if ("callback" in options) callback = options.callback; // Install callback again in case it has been reset. 97 | if ("safeMode" in options) setSafeMode(options.safeMode); 98 | if ("htmlReplacement" in options) { 99 | setHtmlReplacement( 100 | options.htmlReplacement, 101 | ); 102 | } 103 | } 104 | 105 | // Set named option value. 106 | // deno-lint-ignore no-explicit-any 107 | export function setOption(name: string, value: any): void { 108 | // deno-lint-ignore no-explicit-any 109 | const option: any = {}; 110 | option[name] = value; 111 | updateOptions(option); 112 | } 113 | 114 | // Filter HTML based on current safeMode. 115 | export function htmlSafeModeFilter(html: string): string { 116 | /* tslint:disable:no-bitwise */ 117 | switch (safeMode & 0x3) { 118 | /* tslint:enable:no-bitwise */ 119 | case 0: // Raw HTML (default behavior). 120 | return html; 121 | case 1: // Drop HTML. 122 | return ""; 123 | case 2: // Replace HTML with 'htmlReplacement' option string. 124 | return htmlReplacement; 125 | case 3: // Render HTML as text. 126 | return Utils.replaceSpecialChars(html); 127 | default: 128 | return ""; 129 | } 130 | } 131 | 132 | export function errorCallback(message: string): void { 133 | if (callback) { 134 | callback({ type: "error", text: message }); 135 | } 136 | } 137 | 138 | // Called when an unexpected program error occurs. 139 | export function panic(message: string): void { 140 | const msg = "panic: " + message; 141 | console.error(msg); 142 | errorCallback(msg); 143 | } 144 | -------------------------------------------------------------------------------- /src/deno/replacements.ts: -------------------------------------------------------------------------------- 1 | import * as Options from "./options.ts"; 2 | import * as Utils from "./utils.ts"; 3 | 4 | export interface Definition { 5 | match: RegExp; 6 | replacement: string; 7 | filter?: (match: RegExpExecArray) => string; 8 | } 9 | 10 | export let defs: Definition[] // Mutable definitions initialized by DEFAULT_DEFS. 11 | ; 12 | 13 | const DEFAULT_DEFS: Definition[] = [ 14 | // Begin match with \\? to allow the replacement to be escaped. 15 | // Global flag must be set on match re's so that the RegExp lastIndex property is set. 16 | // Replacements and special characters are expanded in replacement groups ($1..). 17 | // Replacement order is important. 18 | 19 | // DEPRECATED as of 3.4.0. 20 | // Anchor: <<#id>> 21 | { 22 | match: /\\?<<#([a-zA-Z][\w\-]*)>>/g, 23 | replacement: '', 24 | filter: function (match: RegExpExecArray): string { 25 | if (Options.skipBlockAttributes()) { 26 | return ""; 27 | } 28 | // Default (non-filter) replacement processing. 29 | return Utils.replaceMatch(match, this.replacement); 30 | }, 31 | }, 32 | // Image: 33 | // src = $1, alt = $2 34 | { 35 | match: /\\?/g, 36 | replacement: '$2', 37 | }, 38 | // Image: 39 | // src = $1, alt = $1 40 | { 41 | match: /\\?/g, 42 | replacement: '$1', 43 | }, 44 | // Image: ![alt](url) 45 | // alt = $1, url = $2 46 | { 47 | match: /\\?!\[([^[]*?)]\((\S+?)\)/g, 48 | replacement: '$1', 49 | }, 50 | // Email: 51 | // address = $1, caption = $2 52 | { 53 | match: /\\?<(\S+@[\w.\-]+)\|([^]+?)>/g, 54 | replacement: '$$2', 55 | }, 56 | // Email:
57 | // address = $1, caption = $1 58 | { 59 | match: /\\?<(\S+@[\w.\-]+)>/g, 60 | replacement: '$1', 61 | }, 62 | // Open link in new window: ^[caption](url) 63 | // caption = $1, url = $2 64 | { 65 | match: /\\?\^\[([^[]*?)]\((\S+?)\)/g, 66 | replacement: '$$1', 67 | }, 68 | // Link: [caption](url) 69 | // caption = $1, url = $2 70 | { 71 | match: /\\?\[([^[]*?)]\((\S+?)\)/g, 72 | replacement: '$$1', 73 | }, 74 | // Link: 75 | // url = $1, caption = $2 76 | { 77 | match: /\\?<(\S+?)\|([^]*?)>/g, 78 | replacement: '$$2', 79 | }, 80 | // HTML inline tags. 81 | // Match HTML comment or HTML tag. 82 | // $1 = tag, $2 = tag name 83 | { 84 | match: /\\?(|<\/?([a-z][a-z0-9]*)(?:\s+[^<>&]+)?>)/ig, 85 | replacement: "", 86 | filter: function (match: RegExpExecArray): string { 87 | return Options.htmlSafeModeFilter(match[1]); // Matched HTML comment or inline tag. 88 | }, 89 | }, 90 | // Link: 91 | // url = $1 92 | { 93 | match: /\\?<([^|\s]+?)>/g, 94 | replacement: '$1', 95 | }, 96 | // Auto-encode (most) raw HTTP URLs as links. 97 | { 98 | match: /\\?((?:http|https):\/\/[^\s"']*[A-Za-z0-9/#])/g, 99 | replacement: '$1', 100 | }, 101 | // Character entity. 102 | { 103 | match: /\\?(&[\w#][\w]+;)/g, 104 | replacement: "", 105 | filter: function (match: RegExpExecArray): string { 106 | return match[1]; // Pass the entity through verbatim. 107 | }, 108 | }, 109 | // Line-break (space followed by \ at end of line). 110 | { 111 | match: /[\\ ]\\(\n|$)/g, 112 | replacement: "
$1", 113 | }, 114 | // This hack ensures backslashes immediately preceding closing code quotes are rendered 115 | // verbatim (Markdown behaviour). 116 | // Works by finding escaped closing code quotes and replacing the backslash and the character 117 | // preceding the closing quote with itself. 118 | { 119 | match: /(\S\\)(?=`)/g, 120 | replacement: "$1", 121 | }, 122 | // This hack ensures underscores within words rendered verbatim and are not treated as 123 | // underscore emphasis quotes (GFM behaviour). 124 | { 125 | match: /([a-zA-Z0-9]_)(?=[a-zA-Z0-9])/g, 126 | replacement: "$1", 127 | }, 128 | ]; 129 | 130 | // Reset definitions to defaults. 131 | export function init(): void { 132 | defs = DEFAULT_DEFS.map((def) => Utils.copy(def)); 133 | } 134 | 135 | // Update existing or add new replacement definition. 136 | export function setDefinition( 137 | regexp: string, 138 | flags: string, 139 | replacement: string, 140 | ): void { 141 | if (!/g/.test(flags)) { 142 | flags += "g"; 143 | } 144 | for (const def of defs) { 145 | if (def.match.source === regexp) { 146 | // Update existing definition. 147 | // Flag properties are read-only so have to create new RegExp. 148 | def.match = new RegExp(regexp, flags); 149 | def.replacement = replacement; 150 | return; 151 | } 152 | } 153 | // Append new definition to end of defs list (custom definitions have lower precedence). 154 | defs.push({ match: new RegExp(regexp, flags), replacement: replacement }); 155 | } 156 | -------------------------------------------------------------------------------- /src/deno/blockattributes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Exports BlockAttributes singleton. 3 | */ 4 | import { ExpansionOptions, replaceInline } from "./utils.ts"; 5 | import * as Options from "./options.ts"; 6 | import * as DelimitedBlocks from "./delimitedblocks.ts"; 7 | 8 | class BlockAttributesSingleton { 9 | private static instance: BlockAttributesSingleton; 10 | 11 | public classes = ""; // Space separated HTML class names. 12 | public id = ""; // HTML element id. 13 | public css = ""; // HTML CSS styles. 14 | public attributes = ""; // Other HTML element attributes. 15 | public options: ExpansionOptions = {}; 16 | public ids: string[] = []; // List of allocated HTML ids. 17 | 18 | constructor() { 19 | if (BlockAttributesSingleton.instance) { 20 | throw new Error( 21 | "BlockAttributesSingleton instantiation failed: use getInstance() instead of new", 22 | ); 23 | } 24 | BlockAttributesSingleton.instance = this; 25 | } 26 | 27 | static getInstance(): BlockAttributesSingleton { 28 | BlockAttributesSingleton.instance = BlockAttributesSingleton.instance || 29 | new BlockAttributesSingleton(); 30 | return BlockAttributesSingleton.instance; 31 | } 32 | 33 | init(): void { 34 | this.classes = ""; 35 | this.id = ""; 36 | this.css = ""; 37 | this.attributes = ""; 38 | this.options = {}; 39 | this.ids = []; 40 | } 41 | 42 | parse(match: RegExpExecArray): boolean { 43 | // Parse Block Attributes. 44 | // class names = $1, id = $2, css-properties = $3, html-attributes = $4, block-options = $5 45 | let text = match[0]; 46 | text = replaceInline(text, { macros: true }); 47 | const m = 48 | /^\\?\.((?:[a-zA-Z][\w-]*\s*)+)?(#[a-zA-Z][\w-]*)?(?:\s*"([^"]+?)")?(?:\s*\[([^\]]+)\])?(\s*[+-][\w\s+-]+)?$/ 49 | .exec(text); 50 | if (!m) { 51 | return false; 52 | } 53 | if (!Options.skipBlockAttributes()) { 54 | if (m[1]) { // HTML element class names. 55 | this.classes += " " + m[1].trim(); 56 | this.classes = this.classes.trim(); 57 | } 58 | if (m[2]) { // HTML element id. 59 | this.id = m[2].trim().slice(1); 60 | } 61 | if (m[3]) { // CSS properties. 62 | if (this.css && this.css.slice(-1) !== ";") this.css += ";"; 63 | this.css += " " + m[3].trim(); 64 | this.css = this.css.trim(); 65 | } 66 | if (m[4] && !Options.isSafeModeNz()) { // HTML attributes. 67 | this.attributes += " " + m[4]; 68 | this.attributes = this.attributes.trim(); 69 | } 70 | DelimitedBlocks.setBlockOptions(this.options, m[5]); 71 | } 72 | return true; 73 | } 74 | 75 | // Inject HTML attributes from attrs into the opening tag. 76 | // Consume HTML attributes unless the 'tag' argument is blank. 77 | inject(tag: string, consume = true): string { 78 | if (!tag) { 79 | return tag; 80 | } 81 | let attrs = ""; 82 | if (this.classes) { 83 | const re = /^(<[^>]*class=")(.*?)"/i; 84 | if (re.test(tag)) { 85 | // Inject class names into first existing class attribute in first tag. 86 | tag = tag.replace(re, `$1${this.classes} $2"`); 87 | } else { 88 | attrs = `class="${this.classes}"`; 89 | } 90 | } 91 | if (this.id) { 92 | this.id = this.id.toLowerCase(); 93 | const hasId = /^<[^<]*id=".*?"/i.test(tag); 94 | if (hasId || this.ids.indexOf(this.id) > -1) { 95 | Options.errorCallback(`duplicate 'id' attribute: ${this.id}`); 96 | } else { 97 | this.ids.push(this.id); 98 | } 99 | if (!hasId) { 100 | attrs += ` id="${this.id}"`; 101 | } 102 | } 103 | if (this.css) { 104 | const re = /^(<[^>]*style=")(.*?)"/i; 105 | if (re.test(tag)) { 106 | // Inject CSS styles into first existing style attribute in first tag. 107 | tag = tag.replace( 108 | re, 109 | (_match: string, p1: string, p2: string): string => { 110 | p2 = p2.trim(); 111 | if (p2 && p2.slice(-1) !== ";") p2 += ";"; 112 | return `${p1}${p2} ${this.css}"`; 113 | }, 114 | ); 115 | } else { 116 | attrs += ` style="${this.css}"`; 117 | } 118 | } 119 | if (this.attributes) { 120 | attrs += " " + this.attributes; 121 | } 122 | attrs = attrs.trim(); 123 | if (attrs) { 124 | const match = tag.match(/^<([a-zA-Z]+|h[1-6])(?=[ >])/); 125 | if (match) { 126 | const before = tag.slice(0, match[0].length); 127 | const after = tag.slice(match[0].length); 128 | tag = before + " " + attrs + after; 129 | } 130 | } 131 | // Consume the attributes. 132 | if (consume) { 133 | this.classes = ""; 134 | this.id = ""; 135 | this.css = ""; 136 | this.attributes = ""; 137 | } 138 | return tag; 139 | } 140 | 141 | slugify(text: string): string { 142 | let slug = text.replace(/\W+/g, "-") // Replace non-alphanumeric characters with dashes. 143 | .replace(/-+/g, "-") // Replace multiple dashes with single dash. 144 | .replace(/(^-)|(-$)/g, "") // Trim leading and trailing dashes. 145 | .toLowerCase(); 146 | if (!slug) slug = "x"; 147 | if (this.ids.indexOf(slug) > -1) { // Another element already has that id. 148 | let i = 2; 149 | while (this.ids.indexOf(slug + "-" + i) > -1) { 150 | i++; 151 | } 152 | slug += "-" + i; 153 | } 154 | return slug; 155 | } 156 | } 157 | 158 | export const BlockAttributes = BlockAttributesSingleton.getInstance(); 159 | -------------------------------------------------------------------------------- /src/resources/manpage.txt: -------------------------------------------------------------------------------- 1 | NAME 2 | rimuc - convert Rimu source to HTML 3 | 4 | SYNOPSIS 5 | rimuc [OPTIONS...] [FILES...] 6 | 7 | DESCRIPTION 8 | Reads Rimu source markup from stdin, converts them to HTML 9 | then writes the HTML to stdout. If FILES are specified 10 | the Rimu source is read from FILES. The contents of files 11 | with an .html extension are passed directly to the output. 12 | An input file named '-' is read from stdin. 13 | 14 | If a file named .rimurc exists in the user's home directory 15 | then its contents is processed (with --safe-mode 0). 16 | This behavior can be disabled with the --no-rimurc option. 17 | 18 | Inputs are processed in the following order: .rimurc file then 19 | --prepend-file option files then --prepend option source and 20 | finally FILES... 21 | 22 | OPTIONS 23 | -h, --help 24 | Display help message. 25 | 26 | --html-replacement TEXT 27 | Embedded HTML is replaced by TEXT when --safe-mode is set to 2. 28 | Defaults to 'replaced HTML'. 29 | 30 | --layout LAYOUT 31 | Generate a styled HTML document. rimuc includes the 32 | following built-in document layouts: 33 | 34 | 'classic': Desktop-centric layout. 35 | 'flex': Flexbox mobile layout (experimental). 36 | 'plain': Unstyled HTML layout. 37 | 'sequel': Responsive cross-device layout. 38 | 39 | If only one source file is specified and the --output 40 | option is not specified then the output is written to a 41 | same-named file with an .html extension. 42 | This option enables --header-ids. 43 | 44 | -s, --styled 45 | Style output using default layout. 46 | Shortcut for '--layout sequel --header-ids --no-toc' 47 | 48 | -o, --output OUTFILE 49 | Write output to file OUTFILE instead of stdout. 50 | If OUTFILE is a hyphen '-' write to stdout. 51 | 52 | --pass 53 | Pass the stdin input verbatim to the output. 54 | 55 | -p, --prepend SOURCE 56 | Process the Rimu SOURCE text (immediately after --prepend-file 57 | option files). Rendered with --safe-mode 0. This option can be 58 | specified multiple times. 59 | 60 | --prepend-file PREPEND_FILE 61 | Process the PREPEND_FILE contents (immediately after .rimurc file). 62 | Rendered with --safe-mode 0. This option can be specified 63 | multiple times. 64 | 65 | --no-rimurc 66 | Do not process .rimurc from the user's home directory. 67 | 68 | --safe-mode NUMBER 69 | Non-zero safe modes ignore: Definition elements; API option elements; 70 | HTML attributes in Block Attributes elements. 71 | Also specifies how to process HTML elements: 72 | 73 | --safe-mode 0 renders HTML (default). 74 | --safe-mode 1 ignores HTML. 75 | --safe-mode 2 replaces HTML with --html-replacement option value. 76 | --safe-mode 3 renders HTML as text. 77 | 78 | Add 4 to --safe-mode to ignore Block Attribute elements. 79 | Add 8 to --safe-mode to allow Macro Definitions. 80 | 81 | --theme THEME, --lang LANG, --title TITLE, --highlightjs, --mathjax, 82 | --no-toc, --custom-toc, --section-numbers, --header-ids, --header-links 83 | Shortcuts for the following prepended macro definitions: 84 | 85 | --prepend "{--custom-toc}='true'" 86 | --prepend "{--header-ids}='true'" 87 | --prepend "{--header-links}='true'" 88 | --prepend "{--highlightjs}='true'" 89 | --prepend "{--lang}='LANG'" 90 | --prepend "{--mathjax}='true'" 91 | --prepend "{--no-toc}='true'" 92 | --prepend "{--section-numbers}='true'" 93 | --prepend "{--theme}='THEME'" 94 | --prepend "{--title}='TITLE'" 95 | 96 | --version 97 | Print version number. 98 | 99 | LAYOUT OPTIONS 100 | The following options are available when the --layout option 101 | is used: 102 | 103 | Option Description 104 | _______________________________________________________________ 105 | --custom-toc Set to a non-blank value if a custom table 106 | of contents is used. 107 | --header-links Set to a non-blank value to generate h2 and 108 | h3 header header links. 109 | --highlightjs Set to non-blank value to enable syntax 110 | highlighting with Highlight.js. 111 | --lang HTML document language attribute value. 112 | --mathjax Set to a non-blank value to enable MathJax. 113 | --no-toc Set to a non-blank value to suppress table 114 | of contents generation. 115 | --section-numbers Apply h2 and h3 section numbering. 116 | --theme Styling theme. Theme names: 117 | 'legend', 'graystone', 'vintage'. 118 | --title HTML document title. 119 | _______________________________________________________________ 120 | These options are translated by rimuc to corresponding layout 121 | macro definitions using the --prepend option. 122 | 123 | LAYOUT CLASSES 124 | The following CSS classes can be used to style Rimu block 125 | elements in conjunction with the --layout option: 126 | 127 | CSS class Description 128 | _______________________________________________________________ 129 | align-center Align element content center. 130 | align-left Align element content left. 131 | align-right Align element content right. 132 | bordered Add borders to table element. 133 | cite Quote and verse attribution. 134 | dl-horizontal Format labeled lists horizontally. 135 | dl-numbered Number labeled list items. 136 | dl-counter Prepend dl item counter to element content. 137 | ol-counter Prepend ol item counter to element content. 138 | ul-counter Prepend ul item counter to element content. 139 | no-auto-toc Exclude heading from table of contents. 140 | no-page-break Avoid page break inside the element. 141 | no-print Do not print element. 142 | page-break Force a page break before the element. 143 | preserve-breaks Honor line breaks in element content. 144 | sidebar Paragraph and division block style. 145 | verse Paragraph and division block style. 146 | important Paragraph and division block style. 147 | note Paragraph and division block style. 148 | tip Paragraph and division block style. 149 | warning Paragraph and division block style. 150 | _______________________________________________________________ 151 | 152 | PREDEFINED MACROS 153 | Macro name Description 154 | _______________________________________________________________ 155 | -- Blank macro (empty string). 156 | The Blank macro cannot be redefined. 157 | --header-ids Set to a non-blank value to generate h1, h2 158 | and h3 header id attributes. 159 | _______________________________________________________________ -------------------------------------------------------------------------------- /docsrc/manpage.rmu: -------------------------------------------------------------------------------- 1 | // Generated automatically by DrakeFile.ts, do not edit. 2 | .-macros 3 | {manpage} = ' 4 | `` 5 | NAME 6 | rimuc - convert Rimu source to HTML 7 | 8 | SYNOPSIS 9 | rimuc [OPTIONS...] [FILES...] 10 | 11 | DESCRIPTION 12 | Reads Rimu source markup from stdin, converts them to HTML 13 | then writes the HTML to stdout. If FILES are specified 14 | the Rimu source is read from FILES. The contents of files 15 | with an .html extension are passed directly to the output. 16 | An input file named '-' is read from stdin. 17 | 18 | If a file named .rimurc exists in the user's home directory 19 | then its contents is processed (with --safe-mode 0). 20 | This behavior can be disabled with the --no-rimurc option. 21 | 22 | Inputs are processed in the following order: .rimurc file then 23 | --prepend-file option files then --prepend option source and 24 | finally FILES... 25 | 26 | OPTIONS 27 | -h, --help 28 | Display help message. 29 | 30 | --html-replacement TEXT 31 | Embedded HTML is replaced by TEXT when --safe-mode is set to 2. 32 | Defaults to 'replaced HTML'. 33 | 34 | --layout LAYOUT 35 | Generate a styled HTML document. rimuc includes the 36 | following built-in document layouts: 37 | 38 | 'classic': Desktop-centric layout. 39 | 'flex': Flexbox mobile layout (experimental). 40 | 'plain': Unstyled HTML layout. 41 | 'sequel': Responsive cross-device layout. 42 | 43 | If only one source file is specified and the --output 44 | option is not specified then the output is written to a 45 | same-named file with an .html extension. 46 | This option enables --header-ids. 47 | 48 | -s, --styled 49 | Style output using default layout. 50 | Shortcut for '--layout sequel --header-ids --no-toc'\ 51 | 52 | -o, --output OUTFILE 53 | Write output to file OUTFILE instead of stdout. 54 | If OUTFILE is a hyphen '-' write to stdout. 55 | 56 | --pass 57 | Pass the stdin input verbatim to the output. 58 | 59 | -p, --prepend SOURCE 60 | Process the Rimu SOURCE text (immediately after --prepend-file 61 | option files). Rendered with --safe-mode 0. This option can be 62 | specified multiple times. 63 | 64 | --prepend-file PREPEND_FILE 65 | Process the PREPEND_FILE contents (immediately after .rimurc file). 66 | Rendered with --safe-mode 0. This option can be specified 67 | multiple times. 68 | 69 | --no-rimurc 70 | Do not process .rimurc from the user's home directory. 71 | 72 | --safe-mode NUMBER 73 | Non-zero safe modes ignore: Definition elements; API option elements; 74 | HTML attributes in Block Attributes elements. 75 | Also specifies how to process HTML elements: 76 | 77 | --safe-mode 0 renders HTML (default). 78 | --safe-mode 1 ignores HTML. 79 | --safe-mode 2 replaces HTML with --html-replacement option value. 80 | --safe-mode 3 renders HTML as text. 81 | 82 | Add 4 to --safe-mode to ignore Block Attribute elements. 83 | Add 8 to --safe-mode to allow Macro Definitions. 84 | 85 | --theme THEME, --lang LANG, --title TITLE, --highlightjs, --mathjax, 86 | --no-toc, --custom-toc, --section-numbers, --header-ids, --header-links 87 | Shortcuts for the following prepended macro definitions: 88 | 89 | --prepend "{--custom-toc}='true'" 90 | --prepend "{--header-ids}='true'" 91 | --prepend "{--header-links}='true'" 92 | --prepend "{--highlightjs}='true'" 93 | --prepend "{--lang}='LANG'" 94 | --prepend "{--mathjax}='true'" 95 | --prepend "{--no-toc}='true'" 96 | --prepend "{--section-numbers}='true'" 97 | --prepend "{--theme}='THEME'" 98 | --prepend "{--title}='TITLE'" 99 | 100 | --version 101 | Print version number. 102 | 103 | LAYOUT OPTIONS 104 | The following options are available when the --layout option 105 | is used: 106 | 107 | Option Description 108 | _______________________________________________________________ 109 | --custom-toc Set to a non-blank value if a custom table 110 | of contents is used. 111 | --header-links Set to a non-blank value to generate h2 and 112 | h3 header header links. 113 | --highlightjs Set to non-blank value to enable syntax 114 | highlighting with Highlight.js. 115 | --lang HTML document language attribute value. 116 | --mathjax Set to a non-blank value to enable MathJax. 117 | --no-toc Set to a non-blank value to suppress table 118 | of contents generation. 119 | --section-numbers Apply h2 and h3 section numbering. 120 | --theme Styling theme. Theme names: 121 | 'legend', 'graystone', 'vintage'. 122 | --title HTML document title. 123 | _______________________________________________________________ 124 | These options are translated by rimuc to corresponding layout 125 | macro definitions using the --prepend option. 126 | 127 | LAYOUT CLASSES 128 | The following CSS classes can be used to style Rimu block 129 | elements in conjunction with the --layout option: 130 | 131 | CSS class Description 132 | _______________________________________________________________ 133 | align-center Align element content center. 134 | align-left Align element content left. 135 | align-right Align element content right. 136 | bordered Add borders to table element. 137 | cite Quote and verse attribution. 138 | dl-horizontal Format labeled lists horizontally. 139 | dl-numbered Number labeled list items. 140 | dl-counter Prepend dl item counter to element content. 141 | ol-counter Prepend ol item counter to element content. 142 | ul-counter Prepend ul item counter to element content. 143 | no-auto-toc Exclude heading from table of contents. 144 | no-page-break Avoid page break inside the element. 145 | no-print Do not print element. 146 | page-break Force a page break before the element. 147 | preserve-breaks Honor line breaks in element content. 148 | sidebar Paragraph and division block style. 149 | verse Paragraph and division block style. 150 | important Paragraph and division block style. 151 | note Paragraph and division block style. 152 | tip Paragraph and division block style. 153 | warning Paragraph and division block style. 154 | _______________________________________________________________ 155 | 156 | PREDEFINED MACROS 157 | Macro name Description 158 | _______________________________________________________________ 159 | -- Blank macro (empty string). 160 | The Blank macro cannot be redefined. 161 | --header-ids Set to a non-blank value to generate h1, h2 162 | and h3 header id attributes. 163 | _______________________________________________________________ 164 | `` 165 | ' 166 | -------------------------------------------------------------------------------- /examples/vim/rimu.vim: -------------------------------------------------------------------------------- 1 | " Vim syntax file 2 | " Language: Rimu Markup 3 | " Author: Stuart Rackham 4 | " URL: http://srackham.github.io/rimu/ 5 | " Licence: MIT 6 | " Limitations: 7 | " - An indented paragraph preceded by a non-blank line is not highlighted. 8 | " - Nested quoted text formatting is highlighted according to the outer 9 | " format. 10 | 11 | if exists("b:current_syntax") 12 | finish 13 | endif 14 | 15 | set maxmempattern=2000000 16 | 17 | syn clear 18 | syn sync linebreaks=100 19 | syn sync minlines=100 20 | 21 | syn keyword rimuTodo TODO FIXME XXX ZZZ DEPRECATED @deprecated @fixme @important @note @todo @warning 22 | 23 | syn match rimuParamSeparator /|/ contained containedin=rimuURLParams,rimuDefinitionParams 24 | syn match rimuParamSeparator /?/ contained containedin=rimuDefinitionParams 25 | syn match rimuBackslash /\\\@/ contains=rimuURLParams 29 | syn match rimuURLParams /|\_[^>]*/ contained contains=rimuSpanQuote.* 30 | syn match rimuSpanURL /[\\]\@/ 33 | syn match rimuMacroInvocation /\\\@>/ 37 | syn match rimuSpanRawURL /[\\<]\@\s*\S/ end=/\n\(\.\.\|""\|\n\)\@=/ keepend contains=rimuSpan.*,rimuQuotePrefix 52 | syn match rimuQuotePrefix /^\\\@/ contained 53 | syn region rimuHTMLBlock start=/\n]\?\)/ end=/\n\n/ contains=rimuSpanHTML keepend 54 | 55 | syn match rimuMacroDefinition /^{[0-9A-Za-z_-]\+}\s*=\s*\(['`]\)\_.\{-}\1\n/ 56 | syn match rimuReplacementDefinition /^\/.\+\/[igm]*\s*=\s*'\_.\{-}'\n/ 57 | syn match rimuReplacementRegExp /^\/.\+\/[igm]*[\t =]\@=/ contained containedin=rimuReplacementDefinition 58 | syn match rimuDelimitedBlockDefinition /^|[0-9A-Za-z-]\+|*\s*=\s*'\_.\{-}'\n/ 59 | syn match rimuDelimitedBlockDefinitionName /^|[0-9A-Za-z-]\+|[\t =]\@=/ contained containedin=rimuDelimitedBlockDefinition 60 | 61 | syn match rimuBlockAttributes /^\.[a-zA-Z#"\[+\-].*$/ 62 | syn match rimuComment "^\\\@\\s\\+\\\\|^\\s*[a-zA-Z.]\\.\\s\\+\\\\|^\\s*[ivxIVX]\\+\\.\\s\\+ 117 | setlocal comments=s1:/*,ex:*/,://,b:#,:%,:XCOMM,fb:-,fb:*,fb:+,fb:.,fb:> 118 | 119 | " vim: wrap et sw=2 sts=2: 120 | -------------------------------------------------------------------------------- /src/deno/lists.ts: -------------------------------------------------------------------------------- 1 | import { BlockAttributes } from "./blockattributes.ts"; 2 | import * as DelimitedBlocks from "./delimitedblocks.ts"; 3 | import * as Io from "./io.ts"; 4 | import * as LineBlocks from "./lineblocks.ts"; 5 | import * as Options from "./options.ts"; 6 | import * as Utils from "./utils.ts"; 7 | 8 | interface Definition { 9 | match: RegExp; 10 | listOpenTag: string; 11 | listCloseTag: string; 12 | itemOpenTag: string; 13 | itemCloseTag: string; 14 | termOpenTag?: string; // Definition lists only. 15 | termCloseTag?: string; // Definition lists only. 16 | } 17 | 18 | // Information about a matched list item element. 19 | interface ItemInfo { 20 | match: RegExpExecArray; 21 | def: Definition; 22 | id: string; // List ID. 23 | } 24 | 25 | const defs: Definition[] = [ 26 | // Prefix match with backslash to allow escaping. 27 | 28 | // Unordered lists. 29 | // $1 is list ID $2 is item text. 30 | { 31 | match: /^\\?\s*(-|\+|\*{1,4})\s+(.*)$/, 32 | listOpenTag: "
    ", 33 | listCloseTag: "
", 34 | itemOpenTag: "
  • ", 35 | itemCloseTag: "
  • ", 36 | }, 37 | // Ordered lists. 38 | // $1 is list ID $2 is item text. 39 | { 40 | match: /^\\?\s*(?:\d*)(\.{1,4})\s+(.*)$/, 41 | listOpenTag: "
      ", 42 | listCloseTag: "
    ", 43 | itemOpenTag: "
  • ", 44 | itemCloseTag: "
  • ", 45 | }, 46 | // Definition lists. 47 | // $1 is term, $2 is list ID, $3 is definition. 48 | { 49 | match: /^\\?\s*(.*[^:])(:{2,4})(|\s+.*)$/, 50 | listOpenTag: "
    ", 51 | listCloseTag: "
    ", 52 | itemOpenTag: "
    ", 53 | itemCloseTag: "
    ", 54 | termOpenTag: "
    ", 55 | termCloseTag: "
    ", 56 | }, 57 | ]; 58 | 59 | let ids: string[] // Stack of open list IDs. 60 | ; 61 | 62 | export function render(reader: Io.Reader, writer: Io.Writer): boolean { 63 | if (reader.eof()) Options.panic("premature eof"); 64 | let startItem: ItemInfo | null; 65 | if (!(startItem = matchItem(reader))) { 66 | return false; 67 | } 68 | ids = []; 69 | renderList(startItem, reader, writer); 70 | // ids should now be empty. 71 | if (ids.length !== 0) Options.panic("list stack failure"); 72 | return true; 73 | } 74 | 75 | function renderList( 76 | item: ItemInfo, 77 | reader: Io.Reader, 78 | writer: Io.Writer, 79 | ): ItemInfo | null { 80 | ids.push(item.id); 81 | writer.write(BlockAttributes.inject(item.def.listOpenTag)); 82 | let nextItem: ItemInfo | null; 83 | while (true) { 84 | nextItem = renderListItem(item, reader, writer); 85 | if (!nextItem || nextItem.id !== item.id) { 86 | // End of list or next item belongs to parent list. 87 | writer.write(item.def.listCloseTag); 88 | ids.pop(); 89 | return nextItem; 90 | } 91 | item = nextItem; 92 | } 93 | } 94 | 95 | // Render the current list item, return the next list item or null if there are no more items. 96 | function renderListItem( 97 | item: ItemInfo, 98 | reader: Io.Reader, 99 | writer: Io.Writer, 100 | ): ItemInfo | null { 101 | const def = item.def; 102 | const match = item.match; 103 | let text: string; 104 | if (match.length === 4) { // 3 match groups => definition list. 105 | writer.write(BlockAttributes.inject(def.termOpenTag as string, false)); 106 | BlockAttributes.id = ""; // Only applied to term tag. 107 | text = Utils.replaceInline(match[1], { macros: true, spans: true }); 108 | writer.write(text); 109 | writer.write(def.termCloseTag as string); 110 | } 111 | writer.write(BlockAttributes.inject(def.itemOpenTag)); 112 | // Process item text from first line. 113 | const itemLines = new Io.Writer(); 114 | text = match[match.length - 1]; 115 | itemLines.write(text + "\n"); 116 | // Process remainder of list item i.e. item text, optional attached block, optional child list. 117 | reader.next(); 118 | const attachedLines = new Io.Writer(); 119 | let blankLines: number; 120 | let attachedDone = false; 121 | let nextItem: ItemInfo | null; 122 | while (true) { 123 | blankLines = consumeBlockAttributes(reader, attachedLines); 124 | if (blankLines >= 2 || blankLines === -1) { 125 | // EOF or two or more blank lines terminates list. 126 | nextItem = null; 127 | break; 128 | } 129 | nextItem = matchItem(reader); 130 | if (nextItem) { 131 | if (ids.indexOf(nextItem.id) !== -1) { 132 | // Next item belongs to current list or a parent list. 133 | } else { 134 | // Render child list. 135 | nextItem = renderList(nextItem, reader, attachedLines); 136 | } 137 | break; 138 | } 139 | if (attachedDone) { 140 | break; // Multiple attached blocks are not permitted. 141 | } 142 | if (blankLines === 0) { 143 | const savedIds = ids; 144 | ids = []; 145 | if ( 146 | DelimitedBlocks.render( 147 | reader, 148 | attachedLines, 149 | ["comment", "code", "division", "html", "quote"], 150 | ) 151 | ) { 152 | attachedDone = true; 153 | } else { 154 | // Item body line. 155 | itemLines.write(reader.cursor + "\n"); 156 | reader.next(); 157 | } 158 | ids = savedIds; 159 | } else if (blankLines === 1) { 160 | if ( 161 | DelimitedBlocks.render( 162 | reader, 163 | attachedLines, 164 | ["indented", "quote-paragraph"], 165 | ) 166 | ) { 167 | attachedDone = true; 168 | } else { 169 | break; 170 | } 171 | } 172 | } 173 | // Write item text. 174 | text = itemLines.toString().trim(); 175 | text = Utils.replaceInline(text, { macros: true, spans: true }); 176 | writer.write(text); 177 | // Write attachment and child list. 178 | writer.buffer = [...writer.buffer, ...attachedLines.buffer]; 179 | // Close list item. 180 | writer.write(def.itemCloseTag); 181 | return nextItem; 182 | } 183 | 184 | // Consume blank lines and Block Attributes. 185 | // Return number of blank lines read or -1 if EOF. 186 | function consumeBlockAttributes(reader: Io.Reader, writer: Io.Writer): number { 187 | let blanks = 0; 188 | while (true) { 189 | if (reader.eof()) { 190 | return -1; 191 | } 192 | if (LineBlocks.render(reader, writer, ["attributes"])) { 193 | continue; 194 | } 195 | if (reader.cursor !== "") { 196 | return blanks; 197 | } 198 | blanks++; 199 | reader.next(); 200 | } 201 | } 202 | 203 | // Check if the line at the reader cursor matches a list related element. 204 | // Unescape escaped list items in reader. 205 | // If it does not match a list related element return null. 206 | function matchItem(reader: Io.Reader): ItemInfo | null { 207 | // Check if the line matches a List definition. 208 | if (reader.eof()) return null; 209 | const item = {} as ItemInfo; // ItemInfo factory. 210 | // Check if the line matches a list item. 211 | for (const def of defs) { 212 | const match = def.match.exec(reader.cursor); 213 | if (match) { 214 | if (match[0][0] === "\\") { 215 | reader.cursor = reader.cursor.slice(1); // Drop backslash. 216 | return null; 217 | } 218 | item.match = match; 219 | item.def = def; 220 | item.id = match[match.length - 2]; // The second to last match group is the list ID. 221 | return item; 222 | } 223 | } 224 | return null; 225 | } 226 | -------------------------------------------------------------------------------- /src/node/rimuc.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Command-lne app to convert Rimu source to HTML. 3 | Run 'node rimu.js --help' for details. 4 | */ 5 | 6 | import * as fs from "fs"; 7 | import * as path from "path"; 8 | import * as rimu from "./rimu"; 9 | import { resources } from "./resources"; 10 | 11 | const VERSION = "11.4.0"; 12 | const STDIN = "/dev/stdin"; 13 | const HOME_DIR = 14 | process.env[(process.platform === "win32") ? "USERPROFILE" : "HOME"]; 15 | const RIMURC = path.resolve(HOME_DIR || "", ".rimurc"); 16 | 17 | // Helpers. 18 | function die(message: string): void { 19 | console.error(message); 20 | process.exit(1); 21 | } 22 | 23 | function readResourceFile(name: string): string { 24 | if (!(name in resources)) { 25 | die(`missing resource: ${name}`); 26 | } 27 | // Restore all backticks. 28 | let result = resources[name].split("\\x60").join("`"); 29 | // Restore all backslashes. 30 | result = resources[name].split("\\x5C").join("\\"); 31 | return result; 32 | } 33 | 34 | let safeMode = 0; 35 | let htmlReplacement: string | undefined; 36 | let layout = ""; 37 | let noRimurc = false; 38 | let prependFiles: string[] = []; 39 | let pass = false; 40 | 41 | // Skip executable and script paths. 42 | process.argv.shift(); // Skip executable path. 43 | process.argv.shift(); // Skip rimuc script path. 44 | 45 | // Parse command-line options. 46 | let prepend = ""; 47 | let outfile: string | undefined; 48 | let arg: string | undefined; 49 | outer: 50 | while (!!(arg = process.argv.shift())) { 51 | switch (arg) { 52 | case "--help": 53 | case "-h": 54 | console.log("\n" + readResourceFile("manpage.txt")); 55 | process.exit(); 56 | break; 57 | case "--version": 58 | console.log(VERSION); 59 | process.exit(); 60 | break; 61 | case "--lint": // Deprecated in Rimu 10.0.0 62 | case "-l": 63 | break; 64 | case "--output": 65 | case "-o": 66 | outfile = process.argv.shift(); 67 | if (!outfile) { 68 | die("missing --output file name"); 69 | } 70 | break; 71 | case "--pass": 72 | pass = true; 73 | break; 74 | case "--prepend": 75 | case "-p": 76 | prepend += process.argv.shift() + "\n"; 77 | break; 78 | case "--prepend-file": 79 | let prependFile = process.argv.shift(); 80 | if (!prependFile) { 81 | die("missing --prepend-file file name"); 82 | } 83 | prependFiles.push(prependFile!); 84 | break; 85 | case "--no-rimurc": 86 | noRimurc = true; 87 | break; 88 | case "--safe-mode": 89 | case "--safeMode": // Deprecated in Rimu 7.1.0. 90 | safeMode = parseInt(process.argv.shift() || "99", 10); 91 | if (safeMode < 0 || safeMode > 15) { 92 | die("illegal --safe-mode option value"); 93 | } 94 | break; 95 | case "--html-replacement": 96 | case "--htmlReplacement": // Deprecated in Rimu 7.1.0. 97 | htmlReplacement = process.argv.shift(); 98 | break; 99 | // Styling macro definitions shortcut options. 100 | case "--highlightjs": 101 | case "--mathjax": 102 | case "--section-numbers": 103 | case "--theme": 104 | case "--title": 105 | case "--lang": 106 | case "--toc": // Deprecated in Rimu 8.0.0 107 | case "--no-toc": 108 | case "--sidebar-toc": // Deprecated in Rimu 10.0.0 109 | case "--dropdown-toc": // Deprecated in Rimu 10.0.0 110 | case "--custom-toc": 111 | case "--header-ids": 112 | case "--header-links": 113 | let macroValue = ["--lang", "--title", "--theme"].indexOf(arg) > -1 114 | ? process.argv.shift() 115 | : "true"; 116 | prepend += "{" + arg + "}='" + macroValue + "'\n"; 117 | break; 118 | case "--layout": 119 | case "--styled-name": // Deprecated in Rimu 10.0.0 120 | layout = process.argv.shift() || ""; 121 | if (!layout) { 122 | die("missing --layout"); 123 | } 124 | prepend += "{--header-ids}='true'\n"; 125 | break; 126 | case "--styled": 127 | case "-s": 128 | prepend += "{--header-ids}='true'\n"; 129 | prepend += "{--no-toc}='true'\n"; 130 | layout = "sequel"; 131 | break; 132 | default: 133 | process.argv.unshift(arg); // argv contains source file names. 134 | break outer; 135 | } 136 | } 137 | // process.argv contains the list of source files. 138 | let files = process.argv; 139 | if (files.length === 0) { 140 | files.push(STDIN); 141 | } else if ( 142 | files.length === 1 && layout !== "" && files[0] !== "-" && !outfile 143 | ) { 144 | // Use the source file name with .html extension for the output file. 145 | outfile = files[0].substr(0, files[0].lastIndexOf(".")) + ".html"; 146 | } 147 | const RESOURCE_TAG = "resource:"; // Tag for resource files. 148 | const PREPEND = "--prepend options"; 149 | if (layout !== "") { 150 | // Envelope source files with header and footer. 151 | files.unshift(`${RESOURCE_TAG}${layout}-header.rmu`); 152 | files.push(`${RESOURCE_TAG}${layout}-footer.rmu`); 153 | } 154 | // Prepend $HOME/.rimurc file if it exists. 155 | if (!noRimurc && fs.existsSync(RIMURC)) { 156 | prependFiles.unshift(RIMURC); 157 | } 158 | if (prepend !== "") { 159 | prependFiles.push(PREPEND); 160 | } 161 | files = [...prependFiles, ...files]; 162 | // Convert Rimu source files to HTML. 163 | let output = ""; 164 | let errors = 0; 165 | let options: rimu.Options = {}; 166 | if (htmlReplacement !== undefined) { 167 | options.htmlReplacement = htmlReplacement; 168 | } 169 | for (let infile of files) { 170 | if (infile === "-") { 171 | infile = STDIN; 172 | } 173 | let source = ""; 174 | if (infile.startsWith(RESOURCE_TAG)) { 175 | infile = infile.substr(RESOURCE_TAG.length); 176 | source = readResourceFile(infile); 177 | options.safeMode = 0; // Resources are trusted. 178 | } else if (infile === PREPEND) { 179 | source = prepend; 180 | options.safeMode = 0; // --prepend options are trusted. 181 | } else { 182 | if (infile === STDIN) { 183 | try { 184 | source = fs.readFileSync(0, "utf-8"); 185 | } catch (e) { 186 | if (e.code !== "EOF") { // EOF error thrown on Windows if stdin is empty. 187 | die(`error reading stdin: ${e.message}`); 188 | } 189 | } 190 | } else { 191 | if (!fs.existsSync(infile)) { 192 | die("source file does not exist: " + infile); 193 | } 194 | try { 195 | source = fs.readFileSync(infile).toString(); 196 | } catch (e) { 197 | die("source file permission denied: " + infile); 198 | } 199 | } 200 | // Prepended and ~/.rimurc files are trusted. 201 | options.safeMode = (prependFiles.indexOf(infile) > -1) ? 0 : safeMode; 202 | } 203 | let ext = infile.split(".").pop(); 204 | // Skip .html and pass-through inputs. 205 | if (!(ext === "html" || (pass && infile === STDIN))) { 206 | options.callback = function (message): void { 207 | let msg = message.type + ": " + infile + ": " + message.text; 208 | if (msg.length > 120) { 209 | msg = msg.slice(0, 117) + "..."; 210 | } 211 | console.error(msg); 212 | if (message.type === "error") { 213 | errors += 1; 214 | } 215 | }; 216 | source = rimu.render(source, options); 217 | } 218 | source = source.trim(); 219 | if (source !== "") { 220 | output += source + "\n"; 221 | } 222 | } 223 | output = output.trim(); 224 | if (!outfile || outfile === "-") { 225 | process.stdout.write(output); 226 | } else { 227 | fs.writeFileSync(outfile, output); 228 | } 229 | if (errors) { 230 | process.exit(1); 231 | } 232 | -------------------------------------------------------------------------------- /src/deno/macros.ts: -------------------------------------------------------------------------------- 1 | import * as Options from "./options.ts"; 2 | import * as Spans from "./spans.ts"; 3 | 4 | // Matches a line starting with a macro invocation. $1 = macro invocation. 5 | export const MATCH_LINE = /^({(?:[\w\-]+)(?:[!=|?](?:|.*?[^\\]))?}).*$/; 6 | // Match single-line macro definition. $1 = name, $2 = delimiter, $3 = value. 7 | export const LINE_DEF = /^\\?{([\w\-]+\??)}\s*=\s*(['`])(.*)\2$/; 8 | // Match multi-line macro definition literal value open delimiter. $1 is first line of macro. 9 | export const LITERAL_DEF_OPEN = /^\\?{[\w\-]+\??}\s*=\s*'(.*)$/; 10 | export const LITERAL_DEF_CLOSE = /^(.*)'$/; 11 | // Match multi-line macro definition expression value open delimiter. $1 is first line of macro. 12 | export const EXPRESSION_DEF_OPEN = /^\\?{[\w\-]+\??}\s*=\s*`(.*)$/; 13 | export const EXPRESSION_DEF_CLOSE = /^(.*)`$/; 14 | 15 | export interface Macro { 16 | name: string; 17 | value: string; 18 | } 19 | 20 | export let defs: Macro[] = []; 21 | 22 | // Reset definitions to defaults. 23 | export function init(): void { 24 | // Initialize predefined macros. 25 | defs = [ 26 | { name: "--", value: "" }, 27 | { name: "--header-ids", value: "" }, 28 | ]; 29 | } 30 | 31 | // Return named macro value or null if it doesn't exist. 32 | export function getValue(name: string): string | null { 33 | for (const def of defs) { 34 | if (def.name === name) { 35 | return def.value; 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | // Set named macro value or add it if it doesn't exist. 42 | // If the name ends with '?' then don't set the macro if it already exists. 43 | // `quote` is a single character: ' if a literal value, ` if an expression value. 44 | export function setValue(name: string, value: string, quote: string): void { 45 | if (Options.skipMacroDefs()) { 46 | return; // Skip if a safe mode is set. 47 | } 48 | let existential = false; 49 | if (name.slice(-1) === "?") { 50 | name = name.slice(0, -1); 51 | existential = true; 52 | } 53 | if (name === "--" && value !== "") { 54 | Options.errorCallback( 55 | "the predefined blank '--' macro cannot be redefined", 56 | ); 57 | return; 58 | } 59 | if (quote === "`") { 60 | try { 61 | value = eval(value); // tslint:disable-line no-eval 62 | } catch (e) { 63 | const msg = e instanceof Error ? e.message : String(e); 64 | Options.errorCallback(`illegal macro expression: ${msg}: ${value}`); 65 | } 66 | } 67 | for (const def of defs) { 68 | if (def.name === name) { 69 | if (!existential) { 70 | def.value = value; 71 | } 72 | return; 73 | } 74 | } 75 | defs.push({ name: name, value: value }); 76 | } 77 | 78 | // Render macro invocations in text string. 79 | // Render Simple invocations first, followed by Parametized, Inclusion and Exclusion invocations. 80 | export function render(text: string, silent = false): string { 81 | const MATCH_COMPLEX = /\\?{([\w\-]+)([!=|?](?:|[^]*?[^\\]))}/g; // Parametrized, Inclusion and Exclusion invocations. 82 | const MATCH_SIMPLE = /\\?{([\w\-]+)()}/g; // Simple macro invocation. 83 | let result = text; 84 | [MATCH_SIMPLE, MATCH_COMPLEX].forEach((find) => { 85 | result = result.replace( 86 | find, 87 | function (match: string, ...submatches: string[]): string { 88 | if (match[0] === "\\") { 89 | return match.slice(1); 90 | } 91 | const name = submatches[0]; 92 | let params = submatches[1] || ""; 93 | if (params[0] === "?") { 94 | // DEPRECATED: Existential macro invocation. 95 | if (!silent) { 96 | Options.errorCallback( 97 | "existential macro invocations are deprecated: " + match, 98 | ); 99 | } 100 | return match; 101 | } 102 | let value = getValue(name); // Macro value is null if macro is undefined. 103 | if (value === null) { 104 | if (!silent) { 105 | Options.errorCallback("undefined macro: " + match + ": " + text); 106 | } 107 | return match; 108 | } 109 | if (find === MATCH_SIMPLE) { 110 | return value; 111 | } 112 | params = params.replace(/\\}/g, "}"); // Unescape escaped } characters. 113 | switch (params[0]) { 114 | // deno-lint-ignore no-case-declarations 115 | case "|": // Parametrized macro. 116 | const paramsList = params.slice(1).split("|"); 117 | // Substitute macro parameters. 118 | // Matches macro definition formal parameters [$]$[[\]:$] 119 | // [$]$ = 1st match group; (1, 2..) = 2nd match group; 120 | // :[\]$ = 3rd match group; = 4th match group. 121 | const PARAM_RE = /\\?(\$\$?)(\d+)(\\?:(|[^]*?[^\\])\$)?/g; 122 | value = (value || "").replace( 123 | PARAM_RE, 124 | function ( 125 | match: string, 126 | p1: string, 127 | p2: string, 128 | p3: string | undefined, 129 | p4: string, 130 | ): string { 131 | if (match[0] === "\\") { 132 | // Unescape escaped macro parameters. 133 | return match.slice(1); 134 | } 135 | if (Number(p2) === 0) { 136 | return match; // $0 is not a valid parameter name. 137 | } 138 | let param: string | undefined = paramsList[Number(p2) - 1]; 139 | param = param === undefined ? "" : param; // Unassigned parameters are replaced with a blank string. 140 | if (p3 !== undefined) { 141 | if (p3[0] === "\\") { 142 | // Unescape escaped default parameter. 143 | param += p3.slice(1); 144 | } else { 145 | if (param === "") { 146 | param = p4; // Assign default parameter value. 147 | param = param.replace(/\\\$/g, "$"); // Unescape escaped $ characters in the default value. 148 | } 149 | } 150 | } 151 | if (p1 === "$$") { 152 | param = Spans.render(param); 153 | } 154 | return param; 155 | }, 156 | ); 157 | return value; 158 | // Exclusion/inclusion macros. 159 | case "!": /* falls through */ 160 | // deno-lint-ignore no-case-declarations 161 | case "=": 162 | const pattern = params.slice(1); 163 | let skip = false; 164 | try { 165 | skip = !RegExp("^" + pattern + "$").test(value || ""); 166 | } catch { 167 | if (!silent) { 168 | Options.errorCallback( 169 | "illegal macro regular expression: " + pattern + ": " + text, 170 | ); 171 | } 172 | return match; 173 | } 174 | if (params[0] === "!") { 175 | skip = !skip; 176 | } 177 | return skip ? "\u0002" : ""; // Flag line for deletion. 178 | default: 179 | Options.errorCallback("illegal macro syntax: " + match[0]); 180 | return ""; 181 | } 182 | }, 183 | ); 184 | }); 185 | // Delete lines flagged by Inclusion/Exclusion macros. 186 | if (result.indexOf("\u0002") !== -1) { 187 | result = result 188 | .split("\n") 189 | .filter((line) => line.indexOf("\u0002") === -1) 190 | .join("\n"); 191 | } 192 | return result; 193 | } 194 | -------------------------------------------------------------------------------- /docsrc/doc-header.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Documentation specific HTML, styling and macros. 3 | */ 4 | 5 | // By default examples generation is disabled. 6 | {generate-examples?} = '' 7 | 8 | // Examples styles. 9 | {generate-examples=}.+skip 10 | 64 | 65 | // Examples handlers. 66 | {generate-examples=}.+skip 67 | 124 | 125 | // Clickable icon buttons to edit and preview the example. 126 | {example-edit-glyph} = '✎' 127 | {example-edit-button} = '{example-edit-glyph}' 128 | 129 | {example-preview-glyph} = '🔎' 130 | {example-preview-button} = '{example-preview-glyph}' 131 | 132 | // Macro to generate Rimu markup and rendered example (stacked horizontally). 133 | // $1 = name of example content macro. 134 | // $2 = title of examples (optional). 135 | // $3 = if non-blank then do not allow editing (optional). 136 | {generate-examples=}.+skip 137 | .-macros 138 | {generate-rendered-example-1} = '#### $2:Examples$ 139 | 140 | 141 | 144 | 147 | 148 | 149 | 157 |
    142 | {--=$3}{example-edit-button|$1} 143 | Rimu Markup 145 | {--=$3}{example-preview-button|$1} 146 | HTML Preview
    150 | 151 | .example-source nohighlight #$1-source +macros 152 | ``` 153 | {$1} 154 | ``` 155 | 156 | 158 |
    159 |
    160 | 161 | {$1} 162 | 163 |
    164 |
    165 | 166 | ' 167 | 168 | // Macro to generate Rimu markup and rendered example (stacked vertically). 169 | // $1 = name of example content macro. 170 | // $2 = title of examples (optional). 171 | // $3 = if non-blank then do not allow editing (optional). 172 | {generate-examples=}.+skip 173 | .-macros 174 | {generate-rendered-example-2} = '#### $2:Examples$ 175 | 176 | 177 | 180 | 188 | 189 | 192 |
    178 | {--=$3}{example-edit-button|$1} 179 | Rimu Markup 181 | 182 | .example-source nohighlight #$1-source +macros 183 | ``` 184 | {$1} 185 | ``` 186 | 187 |
    190 | {--=$3}{example-preview-button|$1} 191 | HTML Preview 193 |
    194 |
    195 | 196 | {$1} 197 | 198 |
    199 |
    200 | 201 | ' 202 | 203 | // Documentation Web pages. 204 | {homepage} = 'index.html' 205 | {playground} = 'rimuplayground.html' 206 | {changelog} = 'changelog.html' 207 | {reference} = 'reference.html' 208 | {tips} = 'tips.html' 209 | {gallery} = 'gallery.html' 210 | 211 | // Documentation links. 212 | {rimuc} = '[rimuc]({reference}#rimuc-command)' 213 | {hindsite} = '[hindsite](https://srackham.github.io/hindsite/)' 214 | 215 | // External Web pages. 216 | {github-rimu} = 'https://github.com/srackham/rimu' 217 | {github-ts} = '{github-rimu}' 218 | {github-go} = 'https://github.com/srackham/go-rimu' 219 | {github-kt} = 'https://github.com/srackham/rimu-kt' 220 | {github-dart} = 'https://github.com/srackham/rimu-dart' 221 | {pubdev-rimu} = 'https://pub.dev/packages/rimu' 222 | {github-py} = 'https://github.com/srackham/rimu-py' 223 | {github-v} = 'https://github.com/srackham/v-rimu' 224 | {pypi-rimu} = 'https://pypi.org/project/rimu/' 225 | {npm-rimu} = 'https://npmjs.org/package/rimu' 226 | {example-rimurc} = 'https://github.com/srackham/rimu/blob/master/examples/example-rimurc.rmu' 227 | 228 | // Custom table of contents. 229 | {--no-toc!}.+skip 230 |
    231 |

    Links

    232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 |

    Table of Contents

    241 |
    242 |
    243 | -------------------------------------------------------------------------------- /src/deno/rimuc.ts: -------------------------------------------------------------------------------- 1 | /* 2 | Command-lne app to convert Rimu source to HTML. 3 | */ 4 | 5 | import { path, readAll } from "./deps.ts"; 6 | import { resources } from "./resources.ts"; 7 | import * as rimu from "./rimu.ts"; 8 | 9 | const VERSION = "11.4.0"; 10 | const STDIN = "/dev/stdin"; 11 | const HOME_DIR = Deno.env.get( 12 | Deno.build.os === "windows" ? "USERPROFILE" : "HOME", 13 | ); 14 | const RIMURC = path.resolve(HOME_DIR || "", ".rimurc"); 15 | 16 | /* 17 | * Helpers. 18 | */ 19 | 20 | function die(message: string): void { 21 | console.error(message); 22 | Deno.exit(1); 23 | } 24 | 25 | function readResourceFile(name: string): string { 26 | if (!(name in resources)) { 27 | die(`missing resource: ${name}`); 28 | } 29 | // Restore all backticks. 30 | let result = resources[name].split("\\x60").join("`"); 31 | // Restore all backslashes. 32 | result = resources[name].split("\\x5C").join("\\"); 33 | return result; 34 | } 35 | 36 | // Synchronous check for regular file. 37 | function fileExists(filename: string): boolean { 38 | try { 39 | return !!Deno.statSync(filename)?.isFile; 40 | } catch (err) { 41 | if (err instanceof Deno.errors.NotFound) { 42 | return false; 43 | } else { 44 | throw err; 45 | } 46 | } 47 | } 48 | 49 | let safeMode = 0; 50 | let htmlReplacement: string | undefined; 51 | let layout = ""; 52 | let noRimurc = false; 53 | const prependFiles: string[] = []; 54 | let pass = false; 55 | 56 | // Parse command-line options. 57 | let prepend = ""; 58 | let outfile: string | undefined; 59 | let arg: string | undefined; 60 | const argv = [...Deno.args]; 61 | outer: while ((arg = argv.shift())) { 62 | switch (arg) { 63 | case "--": // Ignore this option (see https://github.com/denoland/deno/issues/3795). 64 | break; 65 | case "--help": 66 | case "-h": 67 | console.log("\n" + readResourceFile("manpage.txt")); 68 | Deno.exit(); 69 | /* falls through */ 70 | case "--version": 71 | console.log(VERSION); 72 | Deno.exit(); 73 | /* falls through */ 74 | case "--lint": // Deprecated in Rimu 10.0.0 75 | case "-l": 76 | break; 77 | case "--output": 78 | case "-o": 79 | outfile = argv.shift(); 80 | if (!outfile) { 81 | die("missing --output file name"); 82 | } 83 | break; 84 | case "--pass": 85 | pass = true; 86 | break; 87 | case "--prepend": 88 | case "-p": 89 | prepend += argv.shift() + "\n"; 90 | break; 91 | // deno-lint-ignore no-case-declarations 92 | case "--prepend-file": 93 | const prependFile = argv.shift(); 94 | if (!prependFile) { 95 | die("missing --prepend-file file name"); 96 | } 97 | prependFiles.push(prependFile!); 98 | break; 99 | case "--no-rimurc": 100 | noRimurc = true; 101 | break; 102 | case "--safe-mode": 103 | case "--safeMode": // Deprecated in Rimu 7.1.0. 104 | safeMode = parseInt(argv.shift() || "99", 10); 105 | if (safeMode < 0 || safeMode > 15) { 106 | die("illegal --safe-mode option value"); 107 | } 108 | break; 109 | case "--html-replacement": 110 | case "--htmlReplacement": // Deprecated in Rimu 7.1.0. 111 | htmlReplacement = argv.shift(); 112 | break; 113 | // Styling macro definitions shortcut options. 114 | case "--highlightjs": 115 | case "--mathjax": 116 | case "--section-numbers": 117 | case "--theme": 118 | case "--title": 119 | case "--lang": 120 | case "--toc": // Deprecated in Rimu 8.0.0 121 | case "--no-toc": 122 | case "--sidebar-toc": // Deprecated in Rimu 10.0.0 123 | case "--dropdown-toc": // Deprecated in Rimu 10.0.0 124 | case "--custom-toc": 125 | case "--header-ids": /* falls through */ 126 | // deno-lint-ignore no-case-declarations 127 | case "--header-links": 128 | const macroValue = ["--lang", "--title", "--theme"].indexOf(arg) > -1 129 | ? argv.shift() 130 | : "true"; 131 | prepend += "{" + arg + "}='" + macroValue + "'\n"; 132 | break; 133 | case "--layout": 134 | case "--styled-name": // Deprecated in Rimu 10.0.0 135 | layout = argv.shift() || ""; 136 | if (!layout) { 137 | die("missing --layout"); 138 | } 139 | prepend += "{--header-ids}='true'\n"; 140 | break; 141 | case "--styled": 142 | case "-s": 143 | prepend += "{--header-ids}='true'\n"; 144 | prepend += "{--no-toc}='true'\n"; 145 | layout = "sequel"; 146 | break; 147 | default: 148 | argv.unshift(arg); // argv contains source file names. 149 | break outer; 150 | } 151 | } 152 | // argv contains the list of source files. 153 | let files = argv; 154 | if (files.length === 0) { 155 | files.push(STDIN); 156 | } else if ( 157 | files.length === 1 && 158 | layout !== "" && 159 | files[0] !== "-" && 160 | !outfile 161 | ) { 162 | // Use the source file name with .html extension for the output file. 163 | outfile = files[0].substr(0, files[0].lastIndexOf(".")) + ".html"; 164 | } 165 | const RESOURCE_TAG = "resource:"; // Tag for resource files. 166 | const PREPEND = "--prepend options"; 167 | if (layout !== "") { 168 | // Envelope source files with header and footer. 169 | files.unshift(`${RESOURCE_TAG}${layout}-header.rmu`); 170 | files.push(`${RESOURCE_TAG}${layout}-footer.rmu`); 171 | } 172 | // Prepend $HOME/.rimurc file if it exists. 173 | if (!noRimurc && fileExists(RIMURC)) { 174 | prependFiles.unshift(RIMURC); 175 | } 176 | if (prepend !== "") { 177 | prependFiles.push(PREPEND); 178 | } 179 | files = [...prependFiles, ...files]; 180 | // Convert Rimu source files to HTML. 181 | let output = ""; 182 | let errors = 0; 183 | const options: rimu.Options = {}; 184 | if (htmlReplacement !== undefined) { 185 | options.htmlReplacement = htmlReplacement; 186 | } 187 | for (let infile of files) { 188 | if (infile === "-") { 189 | infile = STDIN; 190 | } 191 | let source = ""; 192 | if (infile.startsWith(RESOURCE_TAG)) { 193 | infile = infile.substr(RESOURCE_TAG.length); 194 | source = readResourceFile(infile); 195 | options.safeMode = 0; // Resources are trusted. 196 | } else if (infile === PREPEND) { 197 | source = prepend; 198 | options.safeMode = 0; // --prepend options are trusted. 199 | } else { 200 | if (infile === STDIN) { 201 | try { 202 | source = new TextDecoder().decode(await readAll(Deno.stdin)); 203 | } catch (e) { 204 | if (e instanceof Error) { 205 | die(`error reading stdin: ${e.message}`); 206 | } else { 207 | die(`error reading stdin: ${String(e)}`); 208 | } 209 | } 210 | } else { 211 | if (!fileExists(infile)) { 212 | die("source file does not exist: " + infile); 213 | } 214 | try { 215 | source = Deno.readTextFileSync(infile); 216 | } catch { 217 | die("source file permission denied: " + infile); 218 | } 219 | } 220 | // Prepended and ~/.rimurc files are trusted. 221 | options.safeMode = prependFiles.indexOf(infile) > -1 ? 0 : safeMode; 222 | } 223 | const ext = infile.split(".").pop(); 224 | // Skip .html and pass-through inputs. 225 | if (!(ext === "html" || (pass && infile === STDIN))) { 226 | options.callback = function (message): void { 227 | let msg = message.type + ": " + infile + ": " + message.text; 228 | if (msg.length > 120) { 229 | msg = msg.slice(0, 117) + "..."; 230 | } 231 | console.error(msg); 232 | if (message.type === "error") { 233 | errors += 1; 234 | } 235 | }; 236 | source = rimu.render(source, options); 237 | } 238 | source = source.trim(); 239 | if (source !== "") { 240 | output += source + "\n"; 241 | } 242 | } 243 | output = output.trim(); 244 | if (!outfile || outfile === "-") { 245 | Deno.stdout.writeSync(new TextEncoder().encode(output)); 246 | } else { 247 | Deno.writeTextFileSync(outfile, output); 248 | } 249 | if (errors) { 250 | Deno.exit(1); 251 | } 252 | -------------------------------------------------------------------------------- /src/deno/spans.ts: -------------------------------------------------------------------------------- 1 | /* 2 | This module renders inline text containing Quote and Replacement elements. 3 | 4 | Quote and replacement processing involves splitting the source text into 5 | fragments where at the points where quotes and replacements occur then splicing fragments 6 | containing output markup into the breaks. A fragment is flagged as 'done' to 7 | exclude it from further processing. 8 | 9 | Once all quotes and replacements are processed fragments not yet flagged as 10 | 'done' have special characters (&, <, >) replaced with corresponding special 11 | character entities. The fragments are then reassembled (defraged) into a 12 | resultant HTML string. 13 | */ 14 | 15 | import * as Quotes from "./quotes.ts"; 16 | import * as Replacements from "./replacements.ts"; 17 | import * as Utils from "./utils.ts"; 18 | 19 | interface Fragment { 20 | text: string; 21 | done: boolean; 22 | verbatim?: string; // Replacements text rendered verbatim. 23 | } 24 | 25 | export function render(source: string): string { 26 | let result: string; 27 | result = preReplacements(source); 28 | let fragments: Fragment[] = [{ text: result, done: false }]; 29 | fragments = fragQuotes(fragments); 30 | fragSpecials(fragments); 31 | result = defrag(fragments); 32 | return postReplacements(result); 33 | } 34 | 35 | // Converts fragments to a string. 36 | function defrag(fragments: Fragment[]): string { 37 | return fragments.reduce((result, fragment) => result + fragment.text, ""); 38 | } 39 | 40 | // Fragment quotes in all fragments and return resulting fragments array. 41 | function fragQuotes(fragments: Fragment[]): Fragment[] { 42 | const result: Fragment[] = []; 43 | fragments.forEach((fragment) => { 44 | result.push.apply(result, fragQuote(fragment)); 45 | }); 46 | // Strip backlash from escaped quotes in non-done fragments. 47 | result 48 | .filter((fragment) => !fragment.done) 49 | .forEach((fragment) => fragment.text = Quotes.unescape(fragment.text)); 50 | return result; 51 | } 52 | 53 | // Fragment quotes in a single fragment and return resulting fragments array. 54 | function fragQuote(fragment: Fragment): Fragment[] { 55 | if (fragment.done) { 56 | return [fragment]; 57 | } 58 | const quotesRe = Quotes.quotesRe; 59 | let match: RegExpExecArray | null; 60 | quotesRe.lastIndex = 0; 61 | while (true) { 62 | match = quotesRe.exec(fragment.text); 63 | if (!match) { 64 | return [fragment]; 65 | } 66 | // Check if quote is escaped. 67 | if (match[0][0] === "\\") { 68 | // Restart search after escaped opening quote. 69 | quotesRe.lastIndex = match.index + match[1].length + 1; 70 | continue; 71 | } 72 | break; 73 | } 74 | const result: Fragment[] = []; 75 | // Arrive here if we have a matched quote. 76 | // The quote splits the input fragment into 5 or more output fragments: 77 | // Text before the quote, left quote tag, quoted text, right quote tag and text after the quote. 78 | const def = Quotes.getDefinition(match[1]); 79 | // Check for same closing quote one character further to the right. 80 | while (fragment.text[quotesRe.lastIndex] === match[1][0]) { 81 | // Move to closing quote one character to right. 82 | match[2] += match[1][0]; 83 | quotesRe.lastIndex += 1; 84 | } 85 | const before = match.input.slice(0, match.index); 86 | let quoted = match[2]; 87 | const after = match.input.slice(quotesRe.lastIndex); 88 | result.push({ text: before, done: false }); 89 | result.push({ text: def.openTag, done: true }); 90 | if (!def.spans) { 91 | // Spans are disabled so render the quoted text verbatim. 92 | quoted = Utils.replaceSpecialChars(quoted); 93 | // deno-lint-ignore no-control-regex 94 | quoted = quoted.replace(/\u0000/g, "\u0001"); // Flag replacements as verbatim. 95 | result.push({ text: quoted, done: true }); 96 | } else { 97 | // Recursively process the quoted text. 98 | result.push.apply(result, fragQuote({ text: quoted, done: false })); 99 | } 100 | result.push({ text: def.closeTag, done: true }); 101 | // Recursively process the following text. 102 | result.push.apply(result, fragQuote({ text: after, done: false })); 103 | return result; 104 | } 105 | 106 | // Stores placeholder replacement fragments saved by `preReplacements()` and restored by `postReplacements()`. 107 | let savedReplacements: Fragment[]; 108 | 109 | // Return text with replacements replaced with a placeholder character (see `postReplacements()`): 110 | // '\u0000' is placeholder for expanded replacement text. 111 | // '\u0001' is placeholder for unexpanded replacement text (replacements that occur within quotes are rendered verbatim). 112 | function preReplacements(text: string): string { 113 | savedReplacements = []; 114 | const fragments = fragReplacements([{ text: text, done: false }]); 115 | // Reassemble text with replacement placeholders. 116 | return fragments.reduce((result, fragment) => { 117 | if (fragment.done) { 118 | savedReplacements.push(fragment); // Save replaced text. 119 | return result + "\u0000"; // Placeholder for replaced text. 120 | } else { 121 | return result + fragment.text; 122 | } 123 | }, ""); 124 | } 125 | 126 | // Replace replacements placeholders with replacements text from savedReplacements[]. 127 | function postReplacements(text: string): string { 128 | // deno-lint-ignore no-control-regex 129 | return text.replace(/[\u0000\u0001]/g, function (match): string { 130 | const fragment = savedReplacements.shift() as Fragment; 131 | return (match === "\u0000") 132 | ? fragment.text 133 | : Utils.replaceSpecialChars(fragment.verbatim as string); 134 | }); 135 | } 136 | 137 | // Fragment replacements in all fragments and return resulting fragments array. 138 | function fragReplacements(fragments: Fragment[]): Fragment[] { 139 | let result: Fragment[]; 140 | Replacements.defs.forEach((def: Replacements.Definition) => { 141 | result = []; 142 | fragments.forEach((fragment) => { 143 | result.push.apply(result, fragReplacement(fragment, def)); 144 | }); 145 | fragments = result; 146 | }); 147 | return fragments; 148 | } 149 | 150 | // Fragment replacements in a single fragment for a single replacement definition. 151 | // Return resulting fragments array. 152 | function fragReplacement( 153 | fragment: Fragment, 154 | def: Replacements.Definition, 155 | ): Fragment[] { 156 | if (fragment.done) { 157 | return [fragment]; 158 | } 159 | const replacementRe = def.match; 160 | replacementRe.lastIndex = 0; 161 | const match: RegExpExecArray | null = replacementRe.exec(fragment.text); 162 | if (!match) { 163 | return [fragment]; 164 | } 165 | const result: Fragment[] = []; 166 | // Arrive here if we have a matched replacement. 167 | // The replacement splits the input fragment into 3 output fragments: 168 | // Text before the replacement, replaced text and text after the replacement. 169 | // NOTE: Because this function is called recursively must ensure mutable index and 170 | // lastIndex properties are read before the recursive call. 171 | const before: string = match.input.slice(0, match.index); 172 | const after: string = match.input.slice(replacementRe.lastIndex); 173 | result.push({ text: before, done: false }); 174 | let replacement: string; 175 | if (match[0][0] === "\\") { 176 | // Remove leading backslash. 177 | replacement = Utils.replaceSpecialChars(match[0].slice(1)); 178 | } else { 179 | if (!def.filter) { 180 | replacement = Utils.replaceMatch(match, def.replacement); 181 | } else { 182 | replacement = def.filter(match); 183 | } 184 | } 185 | result.push({ text: replacement, done: true, verbatim: match[0] }); 186 | // Recursively process the remaining text. 187 | result.push.apply( 188 | result, 189 | fragReplacement({ text: after, done: false }, def), 190 | ); 191 | return result; 192 | } 193 | 194 | function fragSpecials(fragments: Fragment[]): void { 195 | // Replace special characters in all non-done fragments. 196 | fragments 197 | .filter((fragment) => !fragment.done) 198 | .forEach((fragment) => 199 | fragment.text = Utils.replaceSpecialChars(fragment.text) 200 | ); 201 | } 202 | -------------------------------------------------------------------------------- /src/deno/lineblocks.ts: -------------------------------------------------------------------------------- 1 | import { BlockAttributes } from "./blockattributes.ts"; 2 | import * as DelimitedBlocks from "./delimitedblocks.ts"; 3 | import * as Io from "./io.ts"; 4 | import * as Macros from "./macros.ts"; 5 | import * as Options from "./options.ts"; 6 | import * as Quotes from "./quotes.ts"; 7 | import * as Replacements from "./replacements.ts"; 8 | import * as Utils from "./utils.ts"; 9 | 10 | export interface Definition { 11 | match: RegExp; 12 | replacement?: string; 13 | name?: string; // Optional unique identifier. 14 | verify?: (match: RegExpExecArray, reader: Io.Reader) => boolean; // Additional match verification checks. 15 | filter?: (match: RegExpExecArray, reader: Io.Reader) => string; 16 | } 17 | 18 | const defs: Definition[] = [ 19 | // Prefix match with backslash to allow escaping. 20 | 21 | // Comment line. 22 | { 23 | match: /^\\?\/{2}(.*)$/, 24 | }, 25 | // Expand lines prefixed with a macro invocation prior to all other processing. 26 | // macro name = $1, macro value = $2 27 | { 28 | match: Macros.MATCH_LINE, 29 | verify: function (match: RegExpExecArray, reader: Io.Reader): boolean { 30 | if ( 31 | Macros.LITERAL_DEF_OPEN.test(match[0]) || 32 | Macros.EXPRESSION_DEF_OPEN.test(match[0]) 33 | ) { 34 | // Do not process macro definitions. 35 | return false; 36 | } 37 | // Silent because any macro expansion errors will be subsequently addressed downstream. 38 | const value = Macros.render(match[0], true); 39 | if ( 40 | value.substr(0, match[0].length) === match[0] || 41 | value.indexOf("\n" + match[0]) >= 0 42 | ) { 43 | // The leading macro invocation expansion failed or contains itself. 44 | // This stops infinite recursion. 45 | return false; 46 | } 47 | // Insert the macro value into the reader just ahead of the cursor. 48 | // deno-lint-ignore no-explicit-any 49 | const spliceArgs: [number, number, ...any[]] = [ 50 | reader.pos + 1, 51 | 0, 52 | ...value.split("\n"), 53 | ]; 54 | Array.prototype.splice.apply(reader.lines, spliceArgs); 55 | return true; 56 | }, 57 | filter: function (_match: RegExpExecArray, _reader: Io.Reader): string { 58 | return ""; // Already processed in the `verify` function. 59 | }, 60 | }, 61 | // Delimited Block definition. 62 | // name = $1, definition = $2 63 | { 64 | match: /^\\?\|([\w\-]+)\|\s*=\s*'(.*)'$/, 65 | filter: function (match: RegExpExecArray): string { 66 | if (Options.isSafeModeNz()) { 67 | return ""; // Skip if a safe mode is set. 68 | } 69 | match[2] = Utils.replaceInline(match[2], { macros: true }); 70 | DelimitedBlocks.setDefinition(match[1], match[2]); 71 | return ""; 72 | }, 73 | }, 74 | // Quote definition. 75 | // quote = $1, openTag = $2, separator = $3, closeTag = $4 76 | { 77 | match: /^(\S{1,2})\s*=\s*'([^|]*)(\|{1,2})(.*)'$/, 78 | filter: function (match: RegExpExecArray): string { 79 | if (Options.isSafeModeNz()) { 80 | return ""; // Skip if a safe mode is set. 81 | } 82 | Quotes.setDefinition({ 83 | quote: match[1], 84 | openTag: Utils.replaceInline(match[2], { macros: true }), 85 | closeTag: Utils.replaceInline(match[4], { macros: true }), 86 | spans: match[3] === "|", 87 | }); 88 | return ""; 89 | }, 90 | }, 91 | // Replacement definition. 92 | // pattern = $1, flags = $2, replacement = $3 93 | { 94 | match: /^\\?\/(.+)\/([igm]*)\s*=\s*'(.*)'$/, 95 | filter: function (match: RegExpExecArray): string { 96 | if (Options.isSafeModeNz()) { 97 | return ""; // Skip if a safe mode is set. 98 | } 99 | const pattern = match[1]; 100 | const flags = match[2]; 101 | let replacement = match[3]; 102 | replacement = Utils.replaceInline(replacement, { macros: true }); 103 | Replacements.setDefinition(pattern, flags, replacement); 104 | return ""; 105 | }, 106 | }, 107 | // Macro definition. 108 | // name = $1, value = $2 109 | { 110 | match: Macros.LINE_DEF, 111 | filter: function (match: RegExpExecArray): string { 112 | const name = match[1]; 113 | const quote = match[2]; 114 | let value = match[3]; 115 | value = Utils.replaceInline(value, { macros: true }); 116 | Macros.setValue(name, value, quote); 117 | return ""; 118 | }, 119 | }, 120 | // Headers. 121 | // $1 is ID, $2 is header text. 122 | { 123 | match: /^\\?([#=]{1,6})\s+(.+?)(?:\s+\1)?$/, 124 | replacement: "$$2", 125 | filter: function (match: RegExpExecArray): string { 126 | match[1] = match[1].length.toString(); // Replace $1 with header number. 127 | if (Macros.getValue("--header-ids") && BlockAttributes.id === "") { 128 | BlockAttributes.id = BlockAttributes.slugify(match[2]); 129 | } 130 | return Utils.replaceMatch( 131 | match, 132 | this.replacement as string, 133 | { macros: true }, 134 | ); 135 | }, 136 | }, 137 | // Block image: 138 | // src = $1, alt = $2 139 | { 140 | match: /^\\?$/, 141 | replacement: '$2', 142 | }, 143 | // Block image: 144 | // src = $1, alt = $1 145 | { 146 | match: /^\\?$/, 147 | replacement: '$1', 148 | }, 149 | // DEPRECATED as of 3.4.0. 150 | // Block anchor: <<#id>> 151 | // id = $1 152 | { 153 | match: /^\\?<<#([a-zA-Z][\w\-]*)>>$/, 154 | replacement: '
    ', 155 | filter: function (match: RegExpExecArray, _reader?: Io.Reader): string { 156 | if (Options.skipBlockAttributes()) { 157 | return ""; 158 | } else { 159 | // Default (non-filter) replacement processing. 160 | return Utils.replaceMatch( 161 | match, 162 | this.replacement as string, 163 | { macros: true }, 164 | ); 165 | } 166 | }, 167 | }, 168 | // Block Attributes. 169 | // Syntax: .class-names #id [html-attributes] block-options 170 | { 171 | name: "attributes", 172 | match: /^\\?\.[a-zA-Z#"\[+-].*$/, // A loose match because Block Attributes can contain macro references. 173 | verify: function (match: RegExpExecArray): boolean { 174 | return BlockAttributes.parse(match); 175 | }, 176 | }, 177 | // API Option. 178 | // name = $1, value = $2 179 | { 180 | match: /^\\?\.(\w+)\s*=\s*'(.*)'$/, 181 | filter: function (match: RegExpExecArray): string { 182 | if (!Options.isSafeModeNz()) { 183 | const value = Utils.replaceInline(match[2], { macros: true }); 184 | Options.setOption(match[1], value); 185 | } 186 | return ""; 187 | }, 188 | }, 189 | ]; 190 | 191 | // If the next element in the reader is a valid line block render it 192 | // and return true, else return false. 193 | export function render( 194 | reader: Io.Reader, 195 | writer: Io.Writer, 196 | allowed: string[] = [], 197 | ): boolean { 198 | if (reader.eof()) Options.panic("premature eof"); 199 | for (const def of defs) { 200 | if ( 201 | allowed.length > 0 && allowed.indexOf(def.name ? def.name : "") === -1 202 | ) { 203 | continue; 204 | } 205 | const match = def.match.exec(reader.cursor); 206 | if (match) { 207 | if (match[0][0] === "\\") { 208 | // Drop backslash escape and continue. 209 | reader.cursor = reader.cursor.slice(1); 210 | continue; 211 | } 212 | if (def.verify && !def.verify(match, reader)) { 213 | continue; 214 | } 215 | let text: string; 216 | if (!def.filter) { 217 | text = def.replacement 218 | ? Utils.replaceMatch(match, def.replacement, { macros: true }) 219 | : ""; 220 | } else { 221 | text = def.filter(match, reader); 222 | } 223 | if (text) { 224 | text = BlockAttributes.inject(text); 225 | writer.write(text); 226 | reader.next(); 227 | if (!reader.eof()) { 228 | writer.write("\n"); // Add a trailing '\n' if there are more lines. 229 | } 230 | } else { 231 | reader.next(); 232 | } 233 | return true; 234 | } 235 | } 236 | return false; 237 | } 238 | -------------------------------------------------------------------------------- /src/resources/v8-header.rmu: -------------------------------------------------------------------------------- 1 | /* 2 | Used by rimuc `--layout v8` option. 3 | DEPRECATED: This layout is no longer maintained, for Rimu version 8 compatibility. 4 | Styled using Bootstrap. 5 | Syntax highlighting with Highlight.js 6 | Bootstrap and Highlight.js sourced from CDNs. 7 | */ 8 | 9 | // Set macro default values. 10 | {--highlightjs?} = '' 11 | {--mathjax?} = '' 12 | {--section-numbers?} = '' 13 | {--lang?} = '' 14 | {--title?} = ' ' 15 | {--custom-toc?} = '' 16 | {--theme?} = 'default' 17 | {--sidebar-toc?} = '' 18 | {--dropdown-toc?} = '' 19 | 20 | // DEPRECATED --toc: If --toc is non-blank make --sidebar-toc non-blank. 21 | {--toc?} = '' 22 | {--sidebar-toc} = '{--toc}{--sidebar-toc}' 23 | 24 | 25 | 26 | 27 | {--!} Force IE into Standards mode. 28 | 29 | 30 | {--title} 31 | 32 | 33 | 34 | 198 | 199 | {--section-numbers=}.+skip 200 | 213 | 214 | {--sidebar-toc=}.+skip 215 | 231 | 232 | {--dropdown-toc=}.+skip 233 | 263 | 264 | // Common to --sidebar-toc and --dropdown-toc. 265 | .+skip 266 | {--sidebar-toc!}.-skip 267 | {--dropdown-toc!}.-skip 268 | 297 | 298 | {--theme!.*\bgraystone\b.*}.+skip 299 | 319 | 320 | 321 | 322 | 323 | // Include dropdown TOC button unless a custom TOC is specified. 324 | {--dropdown-toc=}.+skip 325 | {--custom-toc!}.+skip 326 |
    327 | 328 | // Include for sidebar and dropdown TOC unless a custom TOC is specified. 329 | .+skip 330 | {--sidebar-toc!}.-skip 331 | {--dropdown-toc!}.-skip 332 | {--custom-toc!}.+skip 333 |
    334 |
    335 |
    336 | 337 |
    338 | -------------------------------------------------------------------------------- /docs/classic-legend-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 290 | 297 | 313 | 314 | 315 |
    316 |

    rimuc layout and theme example

    317 | 318 |

    Ultricies lundium

    319 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 320 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 321 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 322 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 323 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    324 |

    Est vel montes.

    325 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 326 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    333 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 334 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 335 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 336 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 337 | habitasse.

    338 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    339 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    340 | porta natoque elementum velit placerat aenean dis tincidunt.
    341 |
    342 | 343 | -------------------------------------------------------------------------------- /docs/classic-vintage-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 290 | 297 | 313 | 314 | 315 |
    316 |

    rimuc layout and theme example

    317 | 318 |

    Ultricies lundium

    319 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 320 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 321 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 322 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 323 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    324 |

    Est vel montes.

    325 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 326 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    333 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 334 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 335 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 336 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 337 | habitasse.

    338 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    339 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    340 | porta natoque elementum velit placerat aenean dis tincidunt.
    341 |
    342 | 343 | -------------------------------------------------------------------------------- /test/validate-rimu-ports.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env -S deno run --allow-all 2 | 3 | import { existsSync } from "https://deno.land/std@0.173.0/fs/exists.ts"; 4 | import * as path from "https://deno.land/std@0.173.0/path/mod.ts"; 5 | import { 6 | abort, 7 | env, 8 | glob, 9 | makeDir, 10 | readFile, 11 | sh, 12 | } from "https://deno.land/x/drake@v1.7.0/lib.ts"; 13 | 14 | env("--abort-exits", true); 15 | 16 | const homeDir = Deno.env.get("HOME"); 17 | const ktDir = "../rimu-kt"; 18 | const goDir = "../go-rimu"; 19 | const dartDir = "../rimu-dart"; 20 | const pyDir = "../rimu-py"; 21 | const vDir = "../v-rimu"; 22 | 23 | if (Deno.args.includes("--help") || Deno.args.includes("-h")) { 24 | console.log(` 25 | NAME 26 | validate-rimu-ports - verify all Rimu ports are congruent. 27 | 28 | SYNOPSIS 29 | validate-rimu-ports.ts [--update-resources] [--nocheck-resources] [--benchmark] [--help] [PORTID] 30 | 31 | DESCRIPTION 32 | This script is used to test and verify all Rimu ports are congruent. 33 | 34 | - Builds, tests and benchmarks all Rimu ports or just one if the PORTID is specified 35 | (\`ts\`, \`deno\`, \`go\`, \`kt\`, \`dart\` or \`py\`) 36 | - Compares common resource files and test fixture files with those of the canonical 37 | rimu TypeScript port. If they don't compare there is an immediate error exit. 38 | - Compiles the Rimu documentation with each port and checks they are identical. 39 | 40 | OPTIONS 41 | - If invoked with \`--update-resources\` argument it copies common resource files 42 | and test fixtures from the Rimu TypeScript implementation to the other ports. 43 | - If invoked with \`--nocheck-resources\` argument the resources and test fixtures 44 | comparison is skipped. 45 | - If invoked with \`--benchmark\` argument then only the documentation compilation 46 | is executed (projects are not built or tested). 47 | `); 48 | Deno.exit(); 49 | } 50 | 51 | const isWindows = Deno.build.os === "windows"; 52 | 53 | const tmpDir = Deno.makeTempDirSync({ prefix: "rimu-validate-" }); 54 | 55 | type PortId = "ts" | "go" | "kt" | "dart" | "py" | "deno" | "v"; 56 | const portIds: PortId[] = ["ts", "deno", "go", "kt", "dart", "py", "v"]; 57 | interface Port { 58 | name: string; 59 | projectDir: string; 60 | fixtures: string[]; 61 | resourcesDir: string; 62 | make: () => void; 63 | rimucExe: () => string; 64 | } 65 | 66 | type Ports = { 67 | [id in PortId]: Port; 68 | }; 69 | 70 | const ports: Ports = { 71 | "ts": { 72 | name: "TypeScript", 73 | projectDir: ".", 74 | fixtures: [ 75 | "test/rimu-tests.json", 76 | "test/rimuc-tests.json", 77 | "examples/example-rimurc.rmu", 78 | ], 79 | resourcesDir: `src/resources`, 80 | make: async function () { 81 | await sh("deno run -A Drakefile.ts test"); 82 | }, 83 | rimucExe: () => "node lib/cjs/rimuc.js", 84 | }, 85 | 86 | "deno": { 87 | name: "Deno", 88 | projectDir: ".", 89 | fixtures: [], 90 | resourcesDir: "", 91 | make: function () { 92 | sh("deno run -A Drakefile.ts install-deno"); 93 | }, 94 | rimucExe: () => "deno run -A src/deno/rimuc.ts", 95 | }, 96 | 97 | "go": { 98 | name: "Go", 99 | projectDir: goDir, 100 | fixtures: [ 101 | "rimu/testdata/rimu-tests.json", 102 | "rimugo/testdata/rimuc-tests.json", 103 | "rimugo/testdata/example-rimurc.rmu", 104 | ], 105 | resourcesDir: "rimugo/resources", 106 | make: async function () { 107 | await sh("go install ./..."); 108 | await sh("go test ./..."); 109 | }, 110 | rimucExe: () => "rimugo", 111 | }, 112 | 113 | "kt": { 114 | name: "Kotlin", 115 | projectDir: ktDir, 116 | fixtures: [ 117 | "src/test/resources/rimu-tests.json", 118 | "src/test/resources/rimuc-tests.json", 119 | "src/test/fixtures/example-rimurc.rmu", 120 | ], 121 | resourcesDir: "src/main/resources/org/rimumarkup", 122 | make: async function () { 123 | await sh("./gradlew --console plain clean test installDist"); 124 | }, 125 | rimucExe: () => path.join(ktDir, "build/install/rimukt/bin/rimukt"), 126 | }, 127 | 128 | "dart": { 129 | name: "Dart", 130 | projectDir: dartDir, 131 | fixtures: [ 132 | "test/rimu-tests.json", 133 | "test/rimuc-tests.json", 134 | "test/fixtures/example-rimurc.rmu", 135 | ], 136 | resourcesDir: "lib/resources", 137 | make: async function () { 138 | makeDir("build"); 139 | await sh( 140 | `dart compile exe -o build/${ 141 | isWindows ? "rimuc.exe" : "rimuc" 142 | } bin/rimuc.dart`, 143 | ); 144 | await sh("dart test test/*.dart"); 145 | }, 146 | rimucExe: () => path.join(dartDir, "build/rimuc"), 147 | }, 148 | 149 | "py": { 150 | name: "Python", 151 | projectDir: pyDir, 152 | fixtures: [ 153 | "tests/rimu-tests.json", 154 | "tests/rimuc-tests.json", 155 | "tests/fixtures/example-rimurc.rmu", 156 | ], 157 | resourcesDir: "src/rimuc/resources", 158 | make: async function () { 159 | await sh(`conda run --name rimu-py make install`); 160 | }, 161 | rimucExe: () => 162 | path.join(homeDir ?? "", "miniconda3/envs/rimu-py/bin/rimupy"), 163 | }, 164 | 165 | "v": { 166 | name: "V", 167 | projectDir: vDir, 168 | fixtures: [ 169 | "testdata/rimu-tests.json", 170 | "testdata/rimuc-tests.json", 171 | "testdata/example-rimurc.rmu", 172 | ], 173 | resourcesDir: "resources", 174 | make: async function () { 175 | await sh("make test"); 176 | await sh("make build-rimuv-optimized"); 177 | }, 178 | rimucExe: () => path.join(vDir, "bin/rimuv"), 179 | }, 180 | }; 181 | 182 | if (Deno.args.length > 0) { 183 | const port = Deno.args[Deno.args.length - 1] as PortId; 184 | if (portIds.includes(port)) { 185 | // Populate portIds with "ts" (the canonical implementation) and the command-line port. 186 | portIds.length = 0; 187 | portIds.push("ts"); // Always process TypeScript 188 | if (port !== "ts") { 189 | portIds.push(port); 190 | } 191 | } 192 | } 193 | 194 | // Check for project directories for all ports. 195 | for (const id of portIds) { 196 | const port = ports[id]; 197 | if ( 198 | !existsSync(port.projectDir) || !Deno.statSync(port.projectDir).isDirectory 199 | ) { 200 | abort( 201 | `rimu ${port.name}: missing project directory is missing or is not a directory: ${port.projectDir}`, 202 | ); 203 | } 204 | } 205 | 206 | // Copy and validate test fixture and resource files. 207 | function copyAndCompare(srcFile: string, dstFile: string): void { 208 | if (Deno.args.includes("--update-resources")) { 209 | Deno.copyFileSync(srcFile, dstFile); 210 | } else if (!Deno.args.includes("--nocheck-resources")) { 211 | if (readFile(srcFile).trimEnd() !== readFile(dstFile).trimEnd()) { 212 | abort(`file contents differ: ${dstFile} ${srcFile}`); 213 | } 214 | } 215 | } 216 | 217 | if (!Deno.args.includes("--benchmark")) { 218 | const srcPort = ports["ts"]; 219 | for (const id of portIds) { 220 | const dstPort = ports[id]; 221 | if (id === "ts" || id == "deno") { 222 | continue; 223 | } 224 | 225 | // Copy and compare test fixtures. 226 | for (const i in srcPort.fixtures) { 227 | const srcFile = path.join(srcPort.projectDir, srcPort.fixtures[i]); 228 | const dstFile = path.join(dstPort.projectDir, dstPort.fixtures[i]); 229 | copyAndCompare(srcFile, dstFile); 230 | } 231 | 232 | // Copy and compare resources. 233 | for (const srcFile of glob(`${srcPort.resourcesDir}/*`)) { 234 | const dstFile = path.join( 235 | dstPort.projectDir, 236 | dstPort.resourcesDir, 237 | path.basename(srcFile), 238 | ); 239 | copyAndCompare(srcFile, dstFile); 240 | } 241 | } 242 | 243 | // Build and test all ports. 244 | for (const id of portIds) { 245 | const port = ports[id]; 246 | const savedCwd = Deno.cwd(); 247 | Deno.chdir(port.projectDir); 248 | try { 249 | await port.make(); 250 | } finally { 251 | Deno.chdir(savedCwd); 252 | } 253 | } 254 | } 255 | 256 | // Compile and compare documentation. 257 | let srcCount = 0; 258 | Deno.chdir(ports["ts"].projectDir); 259 | for (const id of portIds) { 260 | const port = ports[id]; 261 | const startTime = new Date().getTime(); 262 | for (const doc of ["reference", "tips", "changelog"]) { 263 | const srcFile = path.join("docsrc", `${doc}.rmu`); 264 | const dstFile = path.join(tmpDir, `${doc}-${id}.html`); 265 | const cmpFile = path.join(tmpDir, `${doc}-ts.html`); 266 | const args = 267 | `--no-rimurc --theme legend --custom-toc --header-links --layout sequel --lang en --title "Rimu Reference" --highlightjs --prepend "{generate-examples}='yes'" ./examples/example-rimurc.rmu ./docsrc/manpage.rmu ./docsrc/doc-header.rmu`; 268 | const cmd = `${port.rimucExe()} --output ${dstFile} ${args} ${srcFile}`; 269 | await sh(cmd); 270 | if (id === "ts") { 271 | srcCount += readFile(srcFile).split("\n").length - 1; 272 | } else { 273 | if (!Deno.args.includes("--benchmark")) { 274 | if (readFile(cmpFile) !== readFile(dstFile)) { 275 | abort(`file contents differ: ${cmpFile}: ${dstFile}`); 276 | } 277 | } 278 | } 279 | } 280 | if (id === "ts") { 281 | console.log( 282 | `Compiling ${srcCount} lines of Rimu Markup...`, 283 | ); 284 | } 285 | console.log( 286 | `${port.name.padEnd(12)} ${new Date().getTime() - startTime}ms`, 287 | ); 288 | } 289 | -------------------------------------------------------------------------------- /docs/classic-graystone-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 290 | 297 | 313 | 318 | 319 | 320 |
    321 |

    rimuc layout and theme example

    322 | 323 |

    Ultricies lundium

    324 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 325 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 326 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 327 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 328 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    329 |

    Est vel montes.

    330 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 331 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    338 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 339 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 340 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 341 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 342 | habitasse.

    343 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    344 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    345 | porta natoque elementum velit placerat aenean dis tincidunt.
    346 |
    347 | 348 | -------------------------------------------------------------------------------- /docs/flex-legend-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 290 | 305 | 315 | 316 | 317 |
    318 |

    rimuc layout and theme example

    319 | 320 |

    Ultricies lundium

    321 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 322 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 323 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 324 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 325 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    326 |

    Est vel montes.

    327 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 328 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    335 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 336 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 337 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 338 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 339 | habitasse.

    340 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    341 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    342 | porta natoque elementum velit placerat aenean dis tincidunt.
    343 |
    344 | 345 | -------------------------------------------------------------------------------- /docs/flex-vintage-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 290 | 305 | 315 | 316 | 317 |
    318 |

    rimuc layout and theme example

    319 | 320 |

    Ultricies lundium

    321 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 322 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 323 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 324 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 325 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    326 |

    Est vel montes.

    327 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 328 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    335 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 336 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 337 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 338 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 339 | habitasse.

    340 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    341 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    342 | porta natoque elementum velit placerat aenean dis tincidunt.
    343 |
    344 | 345 | -------------------------------------------------------------------------------- /docs/flex-graystone-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 290 | 305 | 315 | 320 | 321 | 322 |
    323 |

    rimuc layout and theme example

    324 | 325 |

    Ultricies lundium

    326 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 327 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 328 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 329 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 330 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    331 |

    Est vel montes.

    332 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 333 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    340 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 341 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 342 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 343 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 344 | habitasse.

    345 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    346 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    347 | porta natoque elementum velit placerat aenean dis tincidunt.
    348 |
    349 | 350 | -------------------------------------------------------------------------------- /docs/sequel-legend-no-toc-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |   8 | 293 | 306 | 320 | 327 | 328 | 329 |
    330 |
    331 |

    rimuc layout and theme example

    332 | 333 |

    Ultricies lundium

    334 |

    Arcu sed hac nisi duis porta, sociis pulvinar. Montes enim, facilisis 335 | urna? Augue vel, magnis montes sociis auctor, ut! Placerat porta 336 | etiam, aliquet? Et turpis magna mus nascetur arcu ultrices enim arcu 337 | aliquam sociis, integer nisi? Rhoncus a, scelerisque etiam magna 338 | natoque! Turpis in vel. Hac nec adipiscing, aenean ut.

    339 |

    Est vel montes.

    340 |
    • Dictumst sed duis sagittis odio scelerisque penatibus dolor, 341 | scelerisque penatibus.
    • Porta magna tincidunt a rhoncus tortor sit integer pulvinar.
      • Et sit proin penatibus scelerisque porttitor in? Auctor et.
      • Ac ridiculus. Rhoncus augue augue mauris in dignissim placerat.
    348 |

    Sociis. Ultricies lundium ut aenean odio cras est, porta a sagittis 349 | lorem et? Egestas integer cras! Nec eros nisi enim mid, vel rhoncus 350 | nisi a sed. Vel! Ac, porta, sociis elit vut elementum in mattis ut 351 | turpis magnis, nunc duis ultrices etiam ultricies vel? Turpis 352 | habitasse.

    353 |
    Mauris? Augue nec, nec pulvinar, odio augue elit tortor? Adipiscing
    354 | nec et dignissim vel, sit integer phasellus? Tempor lacus et magna
    355 | porta natoque elementum velit placerat aenean dis tincidunt.
    356 |
    357 |
    358 | 359 | --------------------------------------------------------------------------------