├── .editorconfig ├── .github └── workflows │ ├── bb.yml │ └── main.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .remarkignore ├── index.d.ts ├── index.js ├── lib ├── handlers.js ├── index.js ├── to-roff.js └── util │ ├── escape.js │ ├── inline.js │ ├── macro.js │ ├── quote.js │ ├── text-decoration.js │ └── url.js ├── license ├── package.json ├── readme.md ├── test ├── fixtures │ ├── adjacent-top-level-headings.6 │ │ ├── input.md │ │ └── output.roff │ ├── anchor-links.7 │ │ ├── input.md │ │ └── output.roff │ ├── blockquote.8 │ │ ├── input.md │ │ └── output.roff │ ├── description-em-dash.7 │ │ ├── input.md │ │ └── output.roff │ ├── description-only.7 │ │ ├── input.md │ │ └── output.roff │ ├── entities.7 │ │ ├── input.md │ │ └── output.roff │ ├── hard-breaks.8 │ │ ├── input.md │ │ └── output.roff │ ├── horizontal-rule.8 │ │ ├── input.md │ │ └── output.roff │ ├── image-as-heading.6 │ │ ├── input.md │ │ └── output.roff │ ├── invalid.8 │ │ ├── input.md │ │ └── output.roff │ ├── link.8 │ │ ├── input.md │ │ └── output.roff │ ├── list.8 │ │ ├── input.md │ │ └── output.roff │ ├── mdast.1 │ │ ├── input.md │ │ └── output.roff │ ├── mdast.3 │ │ ├── input.md │ │ └── output.roff │ ├── mdastconfig.7 │ │ ├── input.md │ │ └── output.roff │ ├── mdastignore.5 │ │ ├── input.md │ │ └── output.roff │ ├── mdastrc.5 │ │ ├── input.md │ │ └── output.roff │ ├── missing-heading.8 │ │ ├── input.md │ │ └── output.roff │ ├── missing-titles.2 │ │ ├── input.md │ │ └── output.roff │ ├── multiple-titles.2 │ │ ├── input.md │ │ └── output.roff │ ├── nested-font-styles.1 │ │ ├── input.md │ │ └── output.roff │ ├── nesting.3 │ │ ├── input.md │ │ └── output.roff │ ├── nothing │ │ ├── input.md │ │ └── output.roff │ ├── overwrite.9 │ │ ├── config.json │ │ ├── input.md │ │ └── output.roff │ ├── strikethrough.5 │ │ ├── input.md │ │ └── output.roff │ └── tables.5 │ │ ├── input.md │ │ └── output.roff └── index.js └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.github/workflows/bb.yml: -------------------------------------------------------------------------------- 1 | name: bb 2 | on: 3 | issues: 4 | types: [opened, reopened, edited, closed, labeled, unlabeled] 5 | pull_request_target: 6 | types: [opened, reopened, edited, closed, labeled, unlabeled] 7 | jobs: 8 | main: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: unifiedjs/beep-boop-beta@main 12 | with: 13 | repo-token: ${{secrets.GITHUB_TOKEN}} 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | on: 3 | - pull_request 4 | - push 5 | jobs: 6 | main: 7 | name: ${{matrix.node}} 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: ${{matrix.node}} 14 | - run: npm install 15 | - run: npm test 16 | - uses: codecov/codecov-action@v3 17 | strategy: 18 | matrix: 19 | node: 20 | - lts/gallium 21 | - node 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | node_modules/ 3 | .DS_Store 4 | *.d.ts 5 | *.log 6 | yarn.lock 7 | !/index.d.ts 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ignore-scripts=true 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.md 3 | -------------------------------------------------------------------------------- /.remarkignore: -------------------------------------------------------------------------------- 1 | /test/fixtures/ 2 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import type {Root} from 'mdast' 2 | import type {Plugin} from 'unified' 3 | import type {Options} from './lib/index.js' 4 | 5 | export type {Options} from './lib/index.js' 6 | 7 | /** 8 | * Turn markdown into a man page. 9 | * 10 | * @param options 11 | * Configuration (optional). 12 | * @returns 13 | * Nothing. 14 | */ 15 | declare const remarkMan: Plugin<[(Options | null | undefined)?], Root, string> 16 | export default remarkMan 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Note: types exposed from `index.d.ts`. 2 | export {default} from './lib/index.js' 3 | -------------------------------------------------------------------------------- /lib/handlers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('mdast').Blockquote} Blockquote 3 | * @typedef {import('mdast').Code} Code 4 | * @typedef {import('mdast').Delete} Delete 5 | * @typedef {import('mdast').Emphasis} Emphasis 6 | * @typedef {import('mdast').Heading} Heading 7 | * @typedef {import('mdast').Image} Image 8 | * @typedef {import('mdast').ImageReference} ImageReference 9 | * @typedef {import('mdast').InlineCode} InlineCode 10 | * @typedef {import('mdast').Link} Link 11 | * @typedef {import('mdast').LinkReference} LinkReference 12 | * @typedef {import('mdast').List} List 13 | * @typedef {import('mdast').ListItem} ListItem 14 | * @typedef {import('mdast').Nodes} Nodes 15 | * @typedef {import('mdast').Paragraph} Paragraph 16 | * @typedef {import('mdast').Root} Root 17 | * @typedef {import('mdast').Strong} Strong 18 | * @typedef {import('mdast').Table} Table 19 | * @typedef {import('mdast').Text} Text 20 | * @typedef {import('./to-roff.js').Handle} Handle 21 | * @typedef {import('./to-roff.js').State} State 22 | */ 23 | 24 | import {escape} from './util/escape.js' 25 | import {inline} from './util/inline.js' 26 | import {macro} from './util/macro.js' 27 | import {quote} from './util/quote.js' 28 | import {textDecoration} from './util/text-decoration.js' 29 | import {url} from './util/url.js' 30 | 31 | const p = macro('P', '\n') 32 | 33 | /** @satisfies {Record} */ 34 | export const handlers = { 35 | blockquote, 36 | break: break_, 37 | code, 38 | definition: ignore, 39 | delete: delete_, 40 | emphasis, 41 | // To do: figure out how to handle this. 42 | footnoteDefinition: ignore, 43 | footnoteReference: ignore, 44 | heading, 45 | html: ignore, 46 | image, 47 | imageReference, 48 | inlineCode, 49 | link, 50 | linkReference, 51 | list, 52 | listItem, 53 | paragraph, 54 | root, 55 | strong, 56 | table, 57 | // Ignored because `table` takes care of them. 58 | tableCell: ignore, 59 | tableRow: ignore, 60 | text, 61 | thematicBreak, 62 | yaml: ignore 63 | } 64 | 65 | /** 66 | * Handle explicitly ignored nodes. 67 | * 68 | * @satisfies {Handle} 69 | * @returns {undefined} 70 | * Nothing. 71 | */ 72 | function ignore() {} 73 | 74 | /** 75 | * Handle block quote. 76 | * 77 | * @satisfies {Handle} 78 | * @param {Blockquote} node 79 | * Node. 80 | * @param {State} state 81 | * Info passed around. 82 | * @returns {string} 83 | * Roff. 84 | */ 85 | function blockquote(node, state) { 86 | state.level++ 87 | const value = state.containerFlow(node) 88 | state.level-- 89 | 90 | return '.RS ' + (state.level ? 4 : 0) + '\n' + value + '\n.RE 0\n' 91 | } 92 | 93 | /** 94 | * Handle break. 95 | * 96 | * @satisfies {Handle} 97 | * @returns {string} 98 | * Roff. 99 | */ 100 | function break_() { 101 | return '\n' + macro('br') + '\n' 102 | } 103 | 104 | /** 105 | * Handle code. 106 | * 107 | * @satisfies {Handle} 108 | * @param {Code} node 109 | * Node. 110 | * @returns {string} 111 | * Roff. 112 | */ 113 | function code(node) { 114 | return '.P\n.RS 2\n.nf\n' + escape(node.value) + '\n.fi\n.RE' 115 | } 116 | 117 | /** 118 | * Handle GFM delete. 119 | * 120 | * @satisfies {Handle} 121 | * @param {Delete} node 122 | * Node. 123 | * @param {State} state 124 | * Info passed around. 125 | * @returns {string} 126 | * Roff. 127 | */ 128 | function delete_(node, state) { 129 | return inline('I', node, state) 130 | } 131 | 132 | /** 133 | * Handle emphasis. 134 | * 135 | * @satisfies {Handle} 136 | * @param {Emphasis} node 137 | * Node. 138 | * @param {State} state 139 | * Info passed around. 140 | * @returns {string} 141 | * Roff. 142 | */ 143 | function emphasis(node, state) { 144 | return inline('I', node, state) 145 | } 146 | 147 | /** 148 | * Handle heading. 149 | * 150 | * @satisfies {Handle} 151 | * @param {Heading} node 152 | * Node. 153 | * @param {State} state 154 | * Info passed around. 155 | * @returns {string | undefined} 156 | * Roff. 157 | */ 158 | function heading(node, state) { 159 | if (node === state.mainHeading) { 160 | return 161 | } 162 | 163 | let value = state.containerPhrasing(node) 164 | const depth = node.depth + (state.increaseDepth ? 1 : 0) 165 | 166 | // Convert top-level section names to ALL-CAPS. 167 | if (depth === 2) { 168 | value = value.toUpperCase() 169 | } 170 | 171 | return macro(depth === 2 ? 'SH' : 'SS', quote(value)) 172 | } 173 | 174 | /** 175 | * Handle image. 176 | * 177 | * @satisfies {Handle} 178 | * @param {Image} node 179 | * Node. 180 | * @param {State} state 181 | * Info passed around. 182 | * @returns {string} 183 | * Roff. 184 | */ 185 | function image(node, state) { 186 | return url(node.alt || '', node.url, state) 187 | } 188 | 189 | /** 190 | * Handle image reference. 191 | * 192 | * @satisfies {Handle} 193 | * @param {ImageReference} node 194 | * Node. 195 | * @param {State} state 196 | * Info passed around. 197 | * @returns {string} 198 | * Roff. 199 | */ 200 | function imageReference(node, state) { 201 | const definition = state.definitions(node.identifier) 202 | return url( 203 | node.alt || '', 204 | /* c8 ignore next -- verbose to test, means plugins injected references w/o definitions. */ 205 | definition ? definition.url : '', 206 | state 207 | ) 208 | } 209 | 210 | /** 211 | * Handle code (text). 212 | * 213 | * @satisfies {Handle} 214 | * @param {InlineCode} node 215 | * Node. 216 | * @param {State} state 217 | * Info passed around. 218 | * @returns {string} 219 | * Roff. 220 | */ 221 | function inlineCode(node, state) { 222 | return textDecoration( 223 | 'B', 224 | escape(node.value), 225 | state.textStyle[state.textStyle.length - 1] 226 | ) 227 | } 228 | 229 | /** 230 | * Handle link. 231 | * 232 | * @satisfies {Handle} 233 | * @param {Link} node 234 | * Node. 235 | * @param {State} state 236 | * Info passed around. 237 | * @returns {string} 238 | * Roff. 239 | */ 240 | function link(node, state) { 241 | return url(state.containerPhrasing(node), node.url, state) 242 | } 243 | 244 | /** 245 | * Handle link reference. 246 | * 247 | * @satisfies {Handle} 248 | * @param {LinkReference} node 249 | * Node. 250 | * @param {State} state 251 | * Info passed around. 252 | * @returns {string} 253 | * Roff. 254 | */ 255 | function linkReference(node, state) { 256 | const definition = state.definitions(node.identifier) 257 | return url( 258 | state.containerPhrasing(node), 259 | /* c8 ignore next -- verbose to test, means plugins injected references w/o definitions. */ 260 | definition ? definition.url : '', 261 | state 262 | ) 263 | } 264 | 265 | /** 266 | * Handle list. 267 | * 268 | * @satisfies {Handle} 269 | * @param {List} node 270 | * Node. 271 | * @param {State} state 272 | * Info passed around. 273 | * @returns {string} 274 | * Roff. 275 | */ 276 | function list(node, state) { 277 | const start = node.ordered ? node.start : undefined 278 | /** @type {Array} */ 279 | const values = [] 280 | let index = -1 281 | 282 | state.level++ 283 | 284 | while (++index < node.children.length) { 285 | values.push( 286 | listItem( 287 | node.children[index], 288 | state, 289 | typeof start === 'number' ? start + index + '.' : '\\(bu' 290 | ) 291 | ) 292 | } 293 | 294 | state.level-- 295 | 296 | return ['.RS ' + (state.level ? 4 : 0), ...values, '.RE 0', ''].join('\n') 297 | } 298 | 299 | /** 300 | * Handle list item. 301 | * 302 | * @satisfies {Handle} 303 | * @param {ListItem} node 304 | * Node. 305 | * @param {State} state 306 | * Info passed around. 307 | * @param {string | undefined} [marker] 308 | * Marker (if parent is list). 309 | * @returns {string} 310 | * Roff. 311 | */ 312 | function listItem(node, state, marker = '') { 313 | return '.IP ' + marker + ' 4\n' + state.containerFlow(node).slice(p.length) 314 | } 315 | 316 | /** 317 | * Handle paragraph. 318 | * 319 | * @satisfies {Handle} 320 | * @param {Paragraph} node 321 | * Node. 322 | * @param {State} state 323 | * Info passed around. 324 | * @returns {string} 325 | * Roff. 326 | */ 327 | function paragraph(node, state) { 328 | return macro('P', '\n' + state.containerPhrasing(node)) 329 | } 330 | 331 | /** 332 | * Handle root. 333 | * 334 | * @satisfies {Handle} 335 | * @param {Root} node 336 | * Node. 337 | * @param {State} state 338 | * Info passed around. 339 | * @returns {string} 340 | * Roff. 341 | */ 342 | function root(node, state) { 343 | return state.containerFlow(node) 344 | } 345 | 346 | /** 347 | * Handle strong. 348 | * 349 | * @satisfies {Handle} 350 | * @param {Strong} node 351 | * Node. 352 | * @param {State} state 353 | * Info passed around. 354 | * @returns {string} 355 | * Roff. 356 | */ 357 | function strong(node, state) { 358 | return inline('B', node, state) 359 | } 360 | 361 | /** 362 | * Handle GFM table. 363 | * 364 | * @satisfies {Handle} 365 | * @param {Table} node 366 | * Node. 367 | * @param {State} state 368 | * Info passed around. 369 | * @returns {string} 370 | * Roff. 371 | */ 372 | function table(node, state) { 373 | /** @type {Array} */ 374 | const result = [] 375 | /* c8 ignore next -- always generated by remark */ 376 | const align = node.align || [] 377 | let index = -1 378 | 379 | while (++index < node.children.length) { 380 | const row = node.children[index] 381 | const cells = row.children 382 | /** @type {Array} */ 383 | const out = [] 384 | let cellIndex = -1 385 | 386 | while (++cellIndex < align.length) { 387 | const cell = cells[cellIndex] 388 | if (result) out.push(state.containerPhrasing(cell)) 389 | } 390 | 391 | result[index] = out.join('@') 392 | } 393 | 394 | /** @type {Array} */ 395 | const alignHeading = [] 396 | /** @type {Array} */ 397 | const alignRow = [] 398 | index = -1 399 | 400 | while (++index < align.length) { 401 | alignHeading.push('cb') 402 | alignRow.push((align[index] || 'l').charAt(0)) 403 | } 404 | 405 | return macro( 406 | 'TS', 407 | [ 408 | '', 409 | 'tab(@) allbox;', 410 | alignHeading.join(' '), 411 | alignRow.join(' ') + ' .', 412 | ...result, 413 | '.TE' 414 | ].join('\n') 415 | ) 416 | } 417 | 418 | /** 419 | * Handle text. 420 | * 421 | * @satisfies {Handle} 422 | * @param {Text} node 423 | * Node. 424 | * @returns {string} 425 | * Roff. 426 | */ 427 | function text(node) { 428 | return escape(node.value.replace(/[\n ]+/g, ' ')) 429 | } 430 | 431 | /** 432 | * Handle thematic break. 433 | * 434 | * @satisfies {Handle} 435 | * @returns {string} 436 | * Roff. 437 | */ 438 | function thematicBreak() { 439 | return '\n\\(em\\(em\\(em' 440 | } 441 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('mdast').Root} Root 3 | * @typedef {import('unified').Compiler} Compiler 4 | * @typedef {import('./to-roff.js').Options} Options 5 | */ 6 | 7 | import {toRoff} from './to-roff.js' 8 | 9 | /** 10 | * Turn markdown into a man page. 11 | * 12 | * @param {Readonly | null | undefined} [options] 13 | * Configuration (optional). 14 | * @returns {undefined} 15 | * Nothing. 16 | */ 17 | export default function remarkMan(options) { 18 | // @ts-expect-error: TypeScript doesn’t handle `this` well. 19 | // eslint-disable-next-line unicorn/no-this-assignment 20 | const self = /** @type {Processor} */ (this) 21 | 22 | self.compiler = compiler 23 | 24 | /** @type {Compiler} */ 25 | function compiler(tree, file) { 26 | return toRoff(tree, file, options) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/to-roff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('mdast').Heading} Heading 3 | * @typedef {import('mdast').Parents} Parents 4 | * @typedef {import('mdast').PhrasingContent} PhrasingContent 5 | * @typedef {import('mdast').Root} Root 6 | * @typedef {import('mdast').TableCell} TableCell 7 | * @typedef {import('mdast').TableRow} TableRow 8 | * @typedef {import('mdast-util-definitions').GetDefinition} GetDefinition 9 | * @typedef {import('unist').Node} UnistNode 10 | * @typedef {import('vfile').VFile} VFile 11 | */ 12 | 13 | /** 14 | * @typedef {Exclude} FlowParents 15 | * @typedef {Parents extends {children: Array} ? PhrasingContent extends T ? Parents : never : never} PhrasingParents 16 | */ 17 | 18 | /** 19 | * @callback ContainerFlow 20 | * Handle a parent. 21 | * @param {FlowParents} node 22 | * mdast phrasing parent. 23 | * @returns {string} 24 | * Serialized roff. 25 | * 26 | * @callback ContainerPhrasing 27 | * Handle a parent. 28 | * @param {PhrasingParents} node 29 | * mdast phrasing parent. 30 | * @returns {string} 31 | * Serialized roff. 32 | * 33 | * @callback Handle 34 | * Handle a particular node. 35 | * @param {any} node 36 | * Expected mdast node. 37 | * @param {State} state 38 | * Info passed around about the current state. 39 | * @returns {string | undefined} 40 | * Serialized roff representing `node`. 41 | * 42 | * @typedef Options 43 | * Configuration. 44 | * @property {Readonly | number | string | null | undefined} [date] 45 | * Date of page (default: `new Date()`); 46 | * given to `new Date(x)`; 47 | * dates are centered in the footer line of the displayed page. 48 | * @property {string | null | undefined} [description] 49 | * Description of page (optional); 50 | * inferred from the main heading: `# hello-world(7) -- Two common words` 51 | * defaults to `'Two common words'`. 52 | * @property {string | null | undefined} [manual] 53 | * Manual of page (optional); 54 | * manuals are centered in the header line of the displayed page. 55 | * @property {string | null | undefined} [name] 56 | * Title of the page (optional); 57 | * inferried from the main heading (`# hello-world(7)` defaults to 58 | * `'hello-world'`) or the file name (`hello-world.1.md` defaults to 59 | * `'hello-world'`). 60 | * @property {number | string | null | undefined} [section] 61 | * Manual section of page (optional); 62 | * inferred from the main heading (`# hello-world(7)` defaults to `7`) or the 63 | * file name (`hello-world.1.md` defaults to `1`). 64 | * @property {string | null | undefined} [version] 65 | * Version of page; 66 | * versions are positioned at the left of the footer line of the displayed 67 | * page. 68 | * 69 | * @typedef State 70 | * Info passed around. 71 | * @property {ContainerFlow} containerFlow 72 | * Serialize children in a flow parent. 73 | * @property {ContainerPhrasing} containerPhrasing 74 | * Serialize children in a phrasing parent. 75 | * @property {GetDefinition} definitions 76 | * Get a definition. 77 | * @property {Map} headings 78 | * Headings by GH slug. 79 | * @property {boolean} increaseDepth 80 | * Whether to act as if one extra heading depth is used. 81 | * @property {number} level 82 | * Current indent level. 83 | * @property {Heading | undefined} mainHeading 84 | * Primary heading. 85 | * @property {Array} textStyle 86 | * Current text style stack. 87 | * 88 | * @typedef {'B' | 'I' | 'R'} TextStyle 89 | * Text style. 90 | */ 91 | 92 | import GitHubSlugger from 'github-slugger' 93 | import {definitions} from 'mdast-util-definitions' 94 | import {toString} from 'mdast-util-to-string' 95 | // @ts-expect-error: untyped. 96 | import months_ from 'months' 97 | import {visit} from 'unist-util-visit' 98 | import {zwitch} from 'zwitch' 99 | import {escape} from './util/escape.js' 100 | import {macro} from './util/macro.js' 101 | import {quote} from './util/quote.js' 102 | import {textDecoration} from './util/text-decoration.js' 103 | import {handlers} from './handlers.js' 104 | 105 | /** @type {Array} */ 106 | const months = months_ 107 | 108 | // Heading expressions. 109 | const manExpression = /([\w_.[\]~+=@:-]+)\s*\((\d\w*)\)(?:\s*[-—–]+\s*(.*))?/ 110 | 111 | /** @type {Readonly} */ 112 | const emptyOptions = {} 113 | 114 | /** @type {Handle} */ 115 | const handle = zwitch('type', {handlers, invalid, unknown}) 116 | 117 | /** 118 | * @param {Root} tree 119 | * Tree. 120 | * @param {VFile} file 121 | * File. 122 | * @param {Readonly | null | undefined} [options] 123 | * Configuration (optional). 124 | * @returns 125 | * Compiler. 126 | */ 127 | // eslint-disable-next-line complexity 128 | export function toRoff(tree, file, options) { 129 | const settings = options || emptyOptions 130 | const slugger = new GitHubSlugger() 131 | /** @type {Record} */ 132 | const config = {} 133 | let heading1 = false 134 | 135 | /** @type {State} */ 136 | const state = { 137 | containerFlow(node) { 138 | return all(node, this).join('\n') 139 | }, 140 | containerPhrasing(node) { 141 | return all(node, this).join('') 142 | }, 143 | definitions: definitions(tree), 144 | headings: new Map(), 145 | increaseDepth: false, 146 | level: 0, 147 | mainHeading: undefined, 148 | textStyle: ['R'] 149 | } 150 | 151 | // Check if there is one or more main headings. 152 | visit(tree, 'heading', function (node) { 153 | if (node.depth === 1) { 154 | if (heading1) { 155 | state.increaseDepth = true 156 | } else { 157 | state.mainHeading = node 158 | } 159 | 160 | heading1 = true 161 | } 162 | 163 | state.headings.set(slugger.slug(toString(node)), node) 164 | }) 165 | 166 | if (state.mainHeading) { 167 | const value = toString(state.mainHeading) 168 | const match = manExpression.exec(value) 169 | 170 | if (match) { 171 | config.name = match[1] 172 | config.section = match[2] 173 | config.description = match[3] 174 | } else { 175 | config.title = value 176 | } 177 | } else if (file.stem) { 178 | const value = file.stem.split('.') 179 | const match = value.length > 1 && value.pop() 180 | 181 | if (match && match.length === 1) { 182 | config.section = match 183 | config.name = value.join('.') 184 | } 185 | } 186 | 187 | const name = config.name || settings.name || '' 188 | const description = 189 | config.description || settings.description || config.title || '' 190 | 191 | let result = 192 | macro( 193 | 'TH', 194 | [ 195 | quote(escape(name.toUpperCase())), 196 | quote(String(config.section || settings.section || '')), 197 | quote(toDate(settings.date || new Date())), 198 | quote(settings.version || ''), 199 | quote(settings.manual || '') 200 | ].join(' ') 201 | ) + '\n' 202 | 203 | if (name) { 204 | result += 205 | macro('SH', quote('NAME')) + '\n' + textDecoration('B', escape(name), 'R') 206 | } 207 | 208 | result += escape(name && description ? ' - ' + description : description) 209 | 210 | result += '\n' + handle(tree, state) 211 | 212 | // Ensure a final eof eol is added. 213 | if (result.charAt(result.length - 1) !== '\n') { 214 | result += '\n' 215 | } 216 | 217 | return result 218 | } 219 | 220 | /** 221 | * Non-nodes. 222 | * 223 | * @param {unknown} node 224 | */ 225 | /* c8 ignore next 3 -- remark produces valid nodes. */ 226 | function invalid(node) { 227 | throw new Error('Expected node, not `' + node + '`') 228 | } 229 | 230 | /** 231 | * Unknown nodes. 232 | * 233 | * @param {unknown} value 234 | */ 235 | function unknown(value) { 236 | // Runtime guarantees it has a `type`. 237 | const node = /** @type {UnistNode} */ (value) 238 | throw new Error('Cannot compile `' + node.type + '` node') 239 | } 240 | 241 | /** 242 | * Create a man-style date. 243 | * 244 | * @param {Date | string | number} value 245 | * @returns {string} 246 | */ 247 | function toDate(value) { 248 | const date = new Date(value) 249 | return months[date.getMonth()] + ' ' + date.getFullYear() 250 | } 251 | 252 | /** 253 | * Serialize children. 254 | * 255 | * @param {Parents} node 256 | * Parent. 257 | * @param {State} state 258 | * Info passed around. 259 | * @returns {Array} 260 | * Chunks for each child. 261 | */ 262 | function all(node, state) { 263 | const children = node.children 264 | /** @type {Array} */ 265 | const results = [] 266 | let index = -1 267 | 268 | while (++index < children.length) { 269 | const result = handle(children[index], state) 270 | if (result) results.push(result) 271 | } 272 | 273 | return results 274 | } 275 | -------------------------------------------------------------------------------- /lib/util/escape.js: -------------------------------------------------------------------------------- 1 | import {groffEscape} from 'groff-escape' 2 | 3 | /** @type {Record} */ 4 | const escapes = { 5 | '\\': 'rs', 6 | '[': 'lB', 7 | ']': 'rB' 8 | } 9 | 10 | const expression = init() 11 | 12 | /** 13 | * Escape a value for roff output. 14 | * 15 | * @param {string} value 16 | * @returns {string} 17 | */ 18 | export function escape(value) { 19 | return value.replace(expression, function ($0) { 20 | return '\\[' + escapes[$0] + ']' 21 | }) 22 | } 23 | 24 | // Create a regex from the escapes. 25 | function init() { 26 | const keys = ['\\\\', '\\[', '\\]'] 27 | /** @type {keyof groffEscape} */ 28 | let key 29 | 30 | for (key in groffEscape) { 31 | if (Object.hasOwn(groffEscape, key)) { 32 | keys.push(key) 33 | escapes[key] = groffEscape[key] 34 | } 35 | } 36 | 37 | return new RegExp( 38 | keys 39 | .sort( 40 | /** Longest first. */ 41 | function (a, b) { 42 | return a.length > b.length ? -1 : 1 43 | } 44 | ) 45 | .join('|'), 46 | 'g' 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /lib/util/inline.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('mdast').Parents} Parents 3 | * @typedef {import('mdast').PhrasingContent} PhrasingContent 4 | * @typedef {import('../to-roff.js').State} State 5 | * @typedef {import('../to-roff.js').TextStyle} TextStyle 6 | */ 7 | 8 | import {textDecoration} from './text-decoration.js' 9 | 10 | /** 11 | * Wrap a node in an inline roff command. 12 | * 13 | * @param {TextStyle} decoration 14 | * @param {Extract} node 15 | * @param {State} state 16 | * @returns {string} 17 | */ 18 | export function inline(decoration, node, state) { 19 | const currentStyle = state.textStyle[state.textStyle.length - 1] 20 | 21 | state.textStyle.push(decoration) 22 | 23 | const result = textDecoration( 24 | decoration, 25 | state.containerPhrasing(node), 26 | currentStyle 27 | ) 28 | 29 | state.textStyle.pop() 30 | 31 | return result 32 | } 33 | -------------------------------------------------------------------------------- /lib/util/macro.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Compile a roff macro. 3 | * 4 | * @param {string} name 5 | * @param {string | undefined} [value=''] 6 | * @returns {string} 7 | */ 8 | export function macro(name, value = '') { 9 | if (value && value.charAt(0) !== '\n') { 10 | value = ' ' + value 11 | } 12 | 13 | return '.' + name + value 14 | } 15 | -------------------------------------------------------------------------------- /lib/util/quote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrap `value` with double quotes and escape internal double quotes. 3 | * 4 | * @param {string} value 5 | * @returns {string} 6 | */ 7 | export function quote(value) { 8 | return '"' + String(value).replace(/"/g, '\\"') + '"' 9 | } 10 | -------------------------------------------------------------------------------- /lib/util/text-decoration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../to-roff.js').TextStyle} TextStyle 3 | */ 4 | 5 | /** 6 | * Wrap a value in a text decoration. 7 | * 8 | * @param {TextStyle} enter 9 | * @param {string} value 10 | * @param {TextStyle} exit 11 | * @returns {string} 12 | */ 13 | export function textDecoration(enter, value, exit) { 14 | return '\\f' + enter + value + '\\f' + exit 15 | } 16 | -------------------------------------------------------------------------------- /lib/util/url.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('../to-roff.js').State} State 3 | */ 4 | 5 | import {toString} from 'mdast-util-to-string' 6 | import {escape} from './escape.js' 7 | import {textDecoration} from './text-decoration.js' 8 | 9 | /** 10 | * @param {string} text 11 | * @param {string} href 12 | * @param {State} state 13 | * @returns {string} 14 | */ 15 | export function url(text, href, state) { 16 | let value = text 17 | let url = href 18 | const heading = 19 | url.charAt(0) === '#' ? state.headings.get(url.slice(1)) : undefined 20 | 21 | if (heading) { 22 | url = toString(heading) 23 | } 24 | 25 | if (url.slice(0, 'mailto:'.length) === 'mailto:') { 26 | url = url.slice('mailto:'.length) 27 | } 28 | 29 | url = escape(url) 30 | value = escape(value) 31 | 32 | if (url === value) { 33 | value = '' 34 | } 35 | 36 | const currentStyle = state.textStyle[state.textStyle.length - 1] 37 | 38 | return ( 39 | (value ? textDecoration('B', value, currentStyle) : '') + 40 | (value && url ? ' ' : '') + 41 | (url 42 | ? textDecoration( 43 | 'I', 44 | (heading ? '(' : '\\(la') + escape(url) + (heading ? ')' : '\\(ra'), 45 | currentStyle 46 | ) 47 | : '') 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2015 Titus Wormer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remark-man", 3 | "version": "9.0.0", 4 | "description": "remark plugin to compile markdown to man pages", 5 | "license": "MIT", 6 | "keywords": [ 7 | "man", 8 | "manual", 9 | "markdown", 10 | "mdast", 11 | "plugin", 12 | "remark", 13 | "remark-plugin", 14 | "roff", 15 | "ronn", 16 | "unified" 17 | ], 18 | "repository": "remarkjs/remark-man", 19 | "bugs": "https://github.com/remarkjs/remark-man/issues", 20 | "funding": { 21 | "type": "opencollective", 22 | "url": "https://opencollective.com/unified" 23 | }, 24 | "author": "Titus Wormer (https://wooorm.com)", 25 | "contributors": [ 26 | "Titus Wormer (https://wooorm.com)" 27 | ], 28 | "sideEffects": false, 29 | "type": "module", 30 | "exports": "./index.js", 31 | "files": [ 32 | "lib/", 33 | "index.d.ts", 34 | "index.js" 35 | ], 36 | "dependencies": { 37 | "@types/mdast": "^4.0.0", 38 | "@types/unist": "^3.0.0", 39 | "github-slugger": "^2.0.0", 40 | "groff-escape": "^2.0.0", 41 | "mdast-util-definitions": "^6.0.0", 42 | "mdast-util-to-string": "^4.0.0", 43 | "months": "^2.0.0", 44 | "unified": "^11.0.0", 45 | "unist-util-visit": "^5.0.0", 46 | "zwitch": "^2.0.0" 47 | }, 48 | "devDependencies": { 49 | "@types/node": "^20.0.0", 50 | "c8": "^8.0.0", 51 | "prettier": "^3.0.0", 52 | "remark-cli": "^11.0.0", 53 | "remark-frontmatter": "^5.0.0", 54 | "remark-gfm": "^4.0.0", 55 | "remark-parse": "^11.0.0", 56 | "remark-preset-wooorm": "^9.0.0", 57 | "type-coverage": "^2.0.0", 58 | "typescript": "^5.0.0", 59 | "vfile": "^6.0.0", 60 | "xo": "^0.56.0" 61 | }, 62 | "scripts": { 63 | "build": "tsc --build --clean && tsc --build && type-coverage", 64 | "format": "remark . --frail --output --quiet && prettier . --log-level warn --write && xo --fix", 65 | "prepack": "npm run build && npm run format", 66 | "test": "npm run build && npm run format && npm run test-coverage", 67 | "test-api": "node --conditions development test/index.js", 68 | "test-coverage": "c8 --100 --reporter lcov npm run test-api" 69 | }, 70 | "prettier": { 71 | "bracketSpacing": false, 72 | "singleQuote": true, 73 | "semi": false, 74 | "tabWidth": 2, 75 | "trailingComma": "none", 76 | "useTabs": false 77 | }, 78 | "remarkConfig": { 79 | "plugins": [ 80 | "remark-preset-wooorm" 81 | ] 82 | }, 83 | "typeCoverage": { 84 | "atLeast": 100, 85 | "detail": true, 86 | "ignoreCatch": true, 87 | "#": "needed `any`s", 88 | "ignoreFiles": [ 89 | "lib/to-roff.d.ts" 90 | ], 91 | "strict": true 92 | }, 93 | "xo": { 94 | "overrides": [ 95 | { 96 | "files": [ 97 | "**/*.ts" 98 | ], 99 | "rules": { 100 | "@typescript-eslint/ban-types": "off" 101 | } 102 | }, 103 | { 104 | "files": [ 105 | "test/**/*.js" 106 | ], 107 | "rules": { 108 | "no-await-in-loop": "off" 109 | } 110 | } 111 | ], 112 | "prettier": true, 113 | "rules": { 114 | "unicorn/prefer-at": "off", 115 | "unicorn/prefer-string-replace-all": "off" 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # remark-man 2 | 3 | [![Build][build-badge]][build] 4 | [![Coverage][coverage-badge]][coverage] 5 | [![Downloads][downloads-badge]][downloads] 6 | [![Size][size-badge]][size] 7 | [![Sponsors][sponsors-badge]][collective] 8 | [![Backers][backers-badge]][collective] 9 | [![Chat][chat-badge]][chat] 10 | 11 | **[remark][]** plugin to turn markdown to man pages. 12 | 13 | ## Contents 14 | 15 | * [What is this?](#what-is-this) 16 | * [When should I use this?](#when-should-i-use-this) 17 | * [Install](#install) 18 | * [Use](#use) 19 | * [API](#api) 20 | * [`unified().use(remarkMan[, options])`](#unifieduseremarkman-options) 21 | * [`Options`](#options) 22 | * [Types](#types) 23 | * [Compatibility](#compatibility) 24 | * [Security](#security) 25 | * [Related](#related) 26 | * [Contribute](#contribute) 27 | * [License](#license) 28 | 29 | ## What is this? 30 | 31 | This package is a [unified][] ([remark][]) plugin that compiles markdown 32 | (mdast) to roff/groff/troff (the format used for man pages). 33 | 34 | It adds a compiler for unified that turns the final markdown (mdast) syntax 35 | tree into roff/groff/troff (the format used for man pages). 36 | 37 | ## When should I use this? 38 | 39 | Use this when you know a bit about remark and ASTs and need mang pages. 40 | This plugin combined with remark is quite good at turning markdown into man 41 | pages. 42 | It has good unicode support, proper support for nested lists and block quotes, 43 | supports tables, and more. 44 | 45 | ## Install 46 | 47 | This package is [ESM only][esm]. 48 | In Node.js (version 16+), install with [npm][]: 49 | 50 | ```sh 51 | npm install remark-man 52 | ``` 53 | 54 | In Deno with [`esm.sh`][esmsh]: 55 | 56 | ```js 57 | import remarkMan from 'https://esm.sh/remark-man@9' 58 | ``` 59 | 60 | In browsers with [`esm.sh`][esmsh]: 61 | 62 | ```html 63 | 66 | ``` 67 | 68 | ## Use 69 | 70 | Say we have the following file `example.md`: 71 | 72 | ```markdown 73 | # ls(1) -- list directory contents 74 | 75 | ## SYNOPSIS 76 | 77 | `ls` \[`-ABCFGHLOPRSTUW@abcdefghiklmnopqrstuwx1`] \[*file* *...*] 78 | ``` 79 | 80 | …and a module `example.js`: 81 | 82 | ```js 83 | import remarkMan from 'remark-man' 84 | import remarkParse from 'remark-parse' 85 | import {read, write} from 'to-vfile' 86 | import {unified} from 'unified' 87 | 88 | const file = await unified() 89 | .use(remarkParse) 90 | .use(remarkMan) 91 | .process(await read('example.md')) 92 | 93 | file.extname = '.1' 94 | await write(file) 95 | ``` 96 | 97 | …then running `node example.js` generates an `example.1` file, which contains: 98 | 99 | ```roff 100 | .TH "LS" "1" "September 2023" "" "" 101 | .SH "NAME" 102 | \fBls\fR - list directory contents 103 | .SH "SYNOPSIS" 104 | .P 105 | \fBls\fR \[lB]\fB-ABCFGHLOPRSTUW@abcdefghiklmnopqrstuwx1\fR\[rB] \[lB]\fIfile\fR \fI...\fR\[rB] 106 | ``` 107 | 108 | Running `man ./example.1` opens that in a manual viewer, which interprets it. 109 | 110 | ## API 111 | 112 | This package exports no identifiers. 113 | The default export is [`remarkMan`][api-remark-man]. 114 | 115 | ### `unified().use(remarkMan[, options])` 116 | 117 | Turn markdown into a man page. 118 | 119 | ###### Parameters 120 | 121 | * `options` ([`Options`][api-options], optional) 122 | — configuration 123 | 124 | ###### Returns 125 | 126 | Transform ([`Transformer`][unified-transformer]). 127 | 128 | ### `Options` 129 | 130 | Configuration (TypeScript type). 131 | 132 | ###### Fields 133 | 134 | * `date` (`Date | number | string`, default: `new Date()`) 135 | — date of page; 136 | given to `new Date(x)`; 137 | dates are centered in the footer line of the displayed page 138 | * `description` (`string`, optional) 139 | — description of page; 140 | inferried from the main heading: `# hello-world(7) -- Two common words` 141 | defaults to `'Two common words'` 142 | * `manual` (`string`, optional) 143 | — manual of page; 144 | manuals are centered in the header line of the displayed page 145 | * `name` (`string`, optional) 146 | — title of the page; 147 | inferried from the main heading (`# hello-world(7)` defaults to 148 | `'hello-world'`) or the file name (`hello-world.1.md` defaults to 149 | `'hello-world'`) 150 | * `section` (`number`, optional) 151 | — [manual section][wiki-man-section] of page; 152 | inferred from the main heading (`# hello-world(7)` defaults to `7`) or the 153 | file name (`hello-world.1.md` defaults to `1`) 154 | * `version` (`string`, optional) 155 | — version of page; 156 | versions are positioned at the left of the footer line of the displayed 157 | page 158 | 159 | ## Types 160 | 161 | This package is fully typed with [TypeScript][]. 162 | It exports the additional type [`Options`][api-options]. 163 | 164 | ## Compatibility 165 | 166 | Projects maintained by the unified collective are compatible with maintained 167 | versions of Node.js. 168 | 169 | When we cut a new major release, we drop support for unmaintained versions of 170 | Node. 171 | This means we try to keep the current release line, `remark-man@^9`, compatible 172 | with Node.js 16. 173 | 174 | This plugin works with `unified` version 11+ and `remark` version 15+. 175 | 176 | ## Security 177 | 178 | Use of `remark-man` does not involve **[rehype][]** (**[hast][]**) or user 179 | content so there are no openings for [cross-site scripting (XSS)][wiki-xss] 180 | attacks. 181 | 182 | ## Related 183 | 184 | * [`remark-rehype`](https://github.com/remarkjs/remark-rehype) 185 | — turn markdown into HTML to support rehype 186 | * [`remark-stringify`](https://github.com/remarkjs/remark/tree/main/packages/remark-stringify) 187 | — compile markdown 188 | 189 | ## Contribute 190 | 191 | See [`contributing.md`][contributing] in [`remarkjs/.github`][health] for ways 192 | to get started. 193 | See [`support.md`][support] for ways to get help. 194 | 195 | This project has a [code of conduct][coc]. 196 | By interacting with this repository, organization, or community you agree to 197 | abide by its terms. 198 | 199 | ## License 200 | 201 | [MIT][license] © [Titus Wormer][author] 202 | 203 | 204 | 205 | [build-badge]: https://github.com/remarkjs/remark-man/workflows/main/badge.svg 206 | 207 | [build]: https://github.com/remarkjs/remark-man/actions 208 | 209 | [coverage-badge]: https://img.shields.io/codecov/c/github/remarkjs/remark-man.svg 210 | 211 | [coverage]: https://codecov.io/github/remarkjs/remark-man 212 | 213 | [downloads-badge]: https://img.shields.io/npm/dm/remark-man.svg 214 | 215 | [downloads]: https://www.npmjs.com/package/remark-man 216 | 217 | [size-badge]: https://img.shields.io/bundlejs/size/remark-man 218 | 219 | [size]: https://bundlejs.com/?q=remark-man 220 | 221 | [sponsors-badge]: https://opencollective.com/unified/sponsors/badge.svg 222 | 223 | [backers-badge]: https://opencollective.com/unified/backers/badge.svg 224 | 225 | [collective]: https://opencollective.com/unified 226 | 227 | [chat-badge]: https://img.shields.io/badge/chat-discussions-success.svg 228 | 229 | [chat]: https://github.com/remarkjs/remark/discussions 230 | 231 | [npm]: https://docs.npmjs.com/cli/install 232 | 233 | [esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c 234 | 235 | [esmsh]: https://esm.sh 236 | 237 | [health]: https://github.com/remarkjs/.github 238 | 239 | [contributing]: https://github.com/remarkjs/.github/blob/HEAD/contributing.md 240 | 241 | [support]: https://github.com/remarkjs/.github/blob/HEAD/support.md 242 | 243 | [coc]: https://github.com/remarkjs/.github/blob/HEAD/code-of-conduct.md 244 | 245 | [license]: license 246 | 247 | [author]: https://wooorm.com 248 | 249 | [hast]: https://github.com/syntax-tree/hast 250 | 251 | [rehype]: https://github.com/rehypejs/rehype 252 | 253 | [remark]: https://github.com/remarkjs/remark 254 | 255 | [unified]: https://github.com/unifiedjs/unified 256 | 257 | [unified-transformer]: https://github.com/unifiedjs/unified#transformer 258 | 259 | [wiki-man-section]: https://en.wikipedia.org/wiki/Man_page#Manual_sections 260 | 261 | [wiki-xss]: https://en.wikipedia.org/wiki/Cross-site_scripting 262 | 263 | [typescript]: https://www.typescriptlang.org 264 | 265 | [api-options]: #options 266 | 267 | [api-remark-man]: #unifieduseremarkman-options 268 | -------------------------------------------------------------------------------- /test/fixtures/adjacent-top-level-headings.6/input.md: -------------------------------------------------------------------------------- 1 | # One 2 | 3 | # Two 4 | -------------------------------------------------------------------------------- /test/fixtures/adjacent-top-level-headings.6/output.roff: -------------------------------------------------------------------------------- 1 | .TH "" "" "February 2016" "" "" 2 | One 3 | .SH "TWO" 4 | -------------------------------------------------------------------------------- /test/fixtures/anchor-links.7/input.md: -------------------------------------------------------------------------------- 1 | # anchors(7) 2 | 3 | [Read more](#bugs). 4 | 5 | [Read more](#see-also). 6 | 7 | # BUGS 8 | -------------------------------------------------------------------------------- /test/fixtures/anchor-links.7/output.roff: -------------------------------------------------------------------------------- 1 | .TH "ANCHORS" "7" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBanchors\fR 4 | .P 5 | \fBRead more\fR \fI(BUGS)\fR. 6 | .P 7 | \fBRead more\fR \fI\(la#see-also\(ra\fR. 8 | .SH "BUGS" 9 | -------------------------------------------------------------------------------- /test/fixtures/blockquote.8/input.md: -------------------------------------------------------------------------------- 1 | # blockquote(8) -- Blockquote. 2 | 3 | ## DESCRIPTION 4 | 5 | This is a paragraph. 6 | 7 | > This is a very standard block quote. 8 | > 9 | > But, it has a list: 10 | > 11 | > * Alpha; 12 | > * Bravo; 13 | > * Charlie. 14 | > 15 | > > With another block quote. 16 | > > 17 | > > > And other. 18 | 19 | This is also paragraph. 20 | -------------------------------------------------------------------------------- /test/fixtures/blockquote.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "BLOCKQUOTE" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBblockquote\fR - Blockquote. 4 | .SH "DESCRIPTION" 5 | .P 6 | This is a paragraph. 7 | .RS 0 8 | .P 9 | This is a very standard block quote. 10 | .P 11 | But, it has a list: 12 | .RS 4 13 | .IP \(bu 4 14 | Alpha; 15 | .IP \(bu 4 16 | Bravo; 17 | .IP \(bu 4 18 | Charlie. 19 | .RE 0 20 | 21 | .RS 4 22 | .P 23 | With another block quote. 24 | .RS 4 25 | .P 26 | And other. 27 | .RE 0 28 | 29 | .RE 0 30 | 31 | .RE 0 32 | 33 | .P 34 | This is also paragraph. 35 | -------------------------------------------------------------------------------- /test/fixtures/description-em-dash.7/input.md: -------------------------------------------------------------------------------- 1 | # Alpha(1) — Bravo 2 | 3 | Charlie Delta Echo Foxtrot Golf Hotel India Juliet Lima Kilo Mike 4 | November Oscar Papa Quebec Romeo Sierra Tango Whiskey X-ray Yankee 5 | Zulu. 6 | -------------------------------------------------------------------------------- /test/fixtures/description-em-dash.7/output.roff: -------------------------------------------------------------------------------- 1 | .TH "ALPHA" "1" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBAlpha\fR - Bravo 4 | .P 5 | Charlie Delta Echo Foxtrot Golf Hotel India Juliet Lima Kilo Mike November Oscar Papa Quebec Romeo Sierra Tango Whiskey X-ray Yankee Zulu. 6 | -------------------------------------------------------------------------------- /test/fixtures/description-only.7/input.md: -------------------------------------------------------------------------------- 1 | # This document just has a description 2 | 3 | This document is missing a heading, it should be detected from the file name. 4 | -------------------------------------------------------------------------------- /test/fixtures/description-only.7/output.roff: -------------------------------------------------------------------------------- 1 | .TH "" "" "February 2016" "" "" 2 | This document just has a description 3 | .P 4 | This document is missing a heading, it should be detected from the file name. 5 | -------------------------------------------------------------------------------- /test/fixtures/entities.7/input.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # roff_entities(7) -- list of roffable entities 4 | 5 | * `"` 6 | * `#` 7 | * `$` 8 | * `'` 9 | * `+` 10 | * `/` 11 | * `=` 12 | * `≠` 13 | * `@` 14 | * `À` 15 | * `Á` 16 | * `Â` 17 | * `Ã` 18 | * `Ä` 19 | * `Å` 20 | * `Ć` 21 | * `Ç` 22 | * `È` 23 | * `É` 24 | * `Ê` 25 | * `Ë` 26 | * `Ì` 27 | * `Í` 28 | * `Î` 29 | * `Ï` 30 | * `Ñ` 31 | * `Ò` 32 | * `Ó` 33 | * `Ô` 34 | * `Õ` 35 | * `Ö` 36 | * `Š` 37 | * `Ù` 38 | * `Ú` 39 | * `Û` 40 | * `Ü` 41 | * `Ý` 42 | * `Ÿ` 43 | * `Ž` 44 | * `[` 45 | * `\` 46 | * `]` 47 | * `^` 48 | * `_` 49 | * `` ` `` 50 | * `à` 51 | * `á` 52 | * `â` 53 | * `ã` 54 | * `ä` 55 | * `å` 56 | * `ć` 57 | * `ç` 58 | * `è` 59 | * `é` 60 | * `ê` 61 | * `ë` 62 | * `ffi` 63 | * `ffl` 64 | * `ì` 65 | * `í` 66 | * `î` 67 | * `ï` 68 | * `ñ` 69 | * `ò` 70 | * `ó` 71 | * `ô` 72 | * `õ` 73 | * `ö` 74 | * `š` 75 | * `ù` 76 | * `ú` 77 | * `û` 78 | * `ü` 79 | * `ý` 80 | * `ÿ` 81 | * `ž` 82 | * `{` 83 | * `|` 84 | * `}` 85 | * `~` 86 | * `¡` 87 | * `¢` 88 | * `£` 89 | * `¤` 90 | * `¥` 91 | * `¦` 92 | * `§` 93 | * `¨` 94 | * `©` 95 | * `ª` 96 | * `«` 97 | * `¬` 98 | * `®` 99 | * `¯` 100 | * `°` 101 | * `±` 102 | * `²` 103 | * `³` 104 | * `´` 105 | * `µ` 106 | * `¶` 107 | * `·` 108 | * `¸` 109 | * `¹` 110 | * `º` 111 | * `»` 112 | * `¼` 113 | * `½` 114 | * `¾` 115 | * `¿` 116 | * `Æ` 117 | * `Ð` 118 | * `×` 119 | * `Ø` 120 | * `Þ` 121 | * `ß` 122 | * `æ` 123 | * `ð` 124 | * `÷` 125 | * `ø` 126 | * `þ` 127 | * `ı` 128 | * `IJ` 129 | * `ij` 130 | * `Ł` 131 | * `ł` 132 | * `Œ` 133 | * `œ` 134 | * `ƒ` 135 | * `ȷ` 136 | * `ˇ` 137 | * `˘` 138 | * `˙` 139 | * `˚` 140 | * `˛` 141 | * `Α` 142 | * `Β` 143 | * `Γ` 144 | * `Δ` 145 | * `Ε` 146 | * `Ζ` 147 | * `Η` 148 | * `Θ` 149 | * `Ι` 150 | * `Κ` 151 | * `Λ` 152 | * `Μ` 153 | * `Ν` 154 | * `Ξ` 155 | * `Ο` 156 | * `Π` 157 | * `Ρ` 158 | * `Σ` 159 | * `Τ` 160 | * `Υ` 161 | * `Φ` 162 | * `Χ` 163 | * `Ψ` 164 | * `Ω` 165 | * `α` 166 | * `β` 167 | * `γ` 168 | * `δ` 169 | * `ε` 170 | * `ζ` 171 | * `η` 172 | * `θ` 173 | * `ι` 174 | * `κ` 175 | * `λ` 176 | * `μ` 177 | * `ν` 178 | * `ξ` 179 | * `ο` 180 | * `π` 181 | * `ρ` 182 | * `ς` 183 | * `σ` 184 | * `τ` 185 | * `υ` 186 | * `φ` 187 | * `χ` 188 | * `ψ` 189 | * `ω` 190 | * `ϑ` 191 | * `ϕ` 192 | * `ϖ` 193 | * `ϵ` 194 | * `‐` 195 | * `–` 196 | * `—` 197 | * `‘` 198 | * `’` 199 | * `‚` 200 | * `“` 201 | * `”` 202 | * `„` 203 | * `†` 204 | * `‡` 205 | * `•` 206 | * `‰` 207 | * `′` 208 | * `″` 209 | * `‹` 210 | * `›` 211 | * `‾` 212 | * `⁄` 213 | * `€` 214 | * `ℏ` 215 | * `ℑ` 216 | * `℘` 217 | * `ℜ` 218 | * `™` 219 | * `ℵ` 220 | * `⅛` 221 | * `⅜` 222 | * `⅝` 223 | * `⅞` 224 | * `←` 225 | * `↑` 226 | * `→` 227 | * `↓` 228 | * `↔` 229 | * `↕` 230 | * `↵` 231 | * `⇐` 232 | * `⇑` 233 | * `⇒` 234 | * `⇓` 235 | * `⇔` 236 | * `⇕` 237 | * `∀` 238 | * `∂` 239 | * `∃` 240 | * `∅` 241 | * `∇` 242 | * `∈` 243 | * `∉` 244 | * `∋` 245 | * `∏` 246 | * `∐` 247 | * `∑` 248 | * `−` 249 | * `∓` 250 | * `∗` 251 | * `√` 252 | * `∝` 253 | * `∞` 254 | * `∠` 255 | * `∧` 256 | * `∨` 257 | * `∩` 258 | * `∪` 259 | * `∫` 260 | * `∴` 261 | * `∼` 262 | * `≃` 263 | * `≅` 264 | * `≈` 265 | * `≡` 266 | * `≢` 267 | * `≤` 268 | * `≥` 269 | * `≪` 270 | * `≫` 271 | * `⊂` 272 | * `⊄` 273 | * `⊃` 274 | * `⊅` 275 | * `⊆` 276 | * `⊇` 277 | * `⊕` 278 | * `⊗` 279 | * `⊥` 280 | * `⋅` 281 | * `⌈` 282 | * `⌉` 283 | * `⌊` 284 | * `⌋` 285 | * `⎛` 286 | * `⎜` 287 | * `⎝` 288 | * `⎞` 289 | * `⎟` 290 | * `⎠` 291 | * `⎢` 292 | * `⎥` 293 | * `⎧` 294 | * `⎨` 295 | * `⎩` 296 | * `⎪` 297 | * `⎫` 298 | * `⎬` 299 | * `⎭` 300 | * `⎯` 301 | * `│` 302 | * `□` 303 | * `◊` 304 | * `○` 305 | * `☜` 306 | * `☞` 307 | * `♠` 308 | * `♣` 309 | * `♥` 310 | * `♦` 311 | * `✓` 312 | * `⟨` 313 | * `⟩` 314 | -------------------------------------------------------------------------------- /test/fixtures/entities.7/output.roff: -------------------------------------------------------------------------------- 1 | .TH "ROFF_ENTITIES" "7" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBroff_entities\fR - list of roffable entities 4 | .RS 0 5 | .IP \(bu 4 6 | \fB"\fR 7 | .IP \(bu 4 8 | \fB#\fR 9 | .IP \(bu 4 10 | \fB$\fR 11 | .IP \(bu 4 12 | \fB'\fR 13 | .IP \(bu 4 14 | \fB+\fR 15 | .IP \(bu 4 16 | \fB/\fR 17 | .IP \(bu 4 18 | \fB=\fR 19 | .IP \(bu 4 20 | \fB\[!=]\fR 21 | .IP \(bu 4 22 | \fB@\fR 23 | .IP \(bu 4 24 | \fB\[`A]\fR 25 | .IP \(bu 4 26 | \fB\['A]\fR 27 | .IP \(bu 4 28 | \fB\[^A]\fR 29 | .IP \(bu 4 30 | \fB\[~A]\fR 31 | .IP \(bu 4 32 | \fB\[:A]\fR 33 | .IP \(bu 4 34 | \fB\[oA]\fR 35 | .IP \(bu 4 36 | \fB\['C]\fR 37 | .IP \(bu 4 38 | \fB\[,C]\fR 39 | .IP \(bu 4 40 | \fB\[`E]\fR 41 | .IP \(bu 4 42 | \fB\['E]\fR 43 | .IP \(bu 4 44 | \fB\[^E]\fR 45 | .IP \(bu 4 46 | \fB\[:E]\fR 47 | .IP \(bu 4 48 | \fB\[`I]\fR 49 | .IP \(bu 4 50 | \fB\['I]\fR 51 | .IP \(bu 4 52 | \fB\[^I]\fR 53 | .IP \(bu 4 54 | \fB\[:I]\fR 55 | .IP \(bu 4 56 | \fB\[~N]\fR 57 | .IP \(bu 4 58 | \fB\[`O]\fR 59 | .IP \(bu 4 60 | \fB\['O]\fR 61 | .IP \(bu 4 62 | \fB\[^O]\fR 63 | .IP \(bu 4 64 | \fB\[~O]\fR 65 | .IP \(bu 4 66 | \fB\[:O]\fR 67 | .IP \(bu 4 68 | \fB\[vS]\fR 69 | .IP \(bu 4 70 | \fB\[`U]\fR 71 | .IP \(bu 4 72 | \fB\['U]\fR 73 | .IP \(bu 4 74 | \fB\[^U]\fR 75 | .IP \(bu 4 76 | \fB\[:U]\fR 77 | .IP \(bu 4 78 | \fB\['Y]\fR 79 | .IP \(bu 4 80 | \fB\[:Y]\fR 81 | .IP \(bu 4 82 | \fB\[vZ]\fR 83 | .IP \(bu 4 84 | \fB\[lB]\fR 85 | .IP \(bu 4 86 | \fB\[rs]\fR 87 | .IP \(bu 4 88 | \fB\[rB]\fR 89 | .IP \(bu 4 90 | \fB^\fR 91 | .IP \(bu 4 92 | \fB_\fR 93 | .IP \(bu 4 94 | \fB`\fR 95 | .IP \(bu 4 96 | \fB\[`a]\fR 97 | .IP \(bu 4 98 | \fB\['a]\fR 99 | .IP \(bu 4 100 | \fB\[^a]\fR 101 | .IP \(bu 4 102 | \fB\[~a]\fR 103 | .IP \(bu 4 104 | \fB\[:a]\fR 105 | .IP \(bu 4 106 | \fB\[oa]\fR 107 | .IP \(bu 4 108 | \fB\['c]\fR 109 | .IP \(bu 4 110 | \fB\[,c]\fR 111 | .IP \(bu 4 112 | \fB\[`e]\fR 113 | .IP \(bu 4 114 | \fB\['e]\fR 115 | .IP \(bu 4 116 | \fB\[^e]\fR 117 | .IP \(bu 4 118 | \fB\[:e]\fR 119 | .IP \(bu 4 120 | \fBffi\fR 121 | .IP \(bu 4 122 | \fBffl\fR 123 | .IP \(bu 4 124 | \fB\[`i]\fR 125 | .IP \(bu 4 126 | \fB\['i]\fR 127 | .IP \(bu 4 128 | \fB\[^i]\fR 129 | .IP \(bu 4 130 | \fB\[:i]\fR 131 | .IP \(bu 4 132 | \fB\[~n]\fR 133 | .IP \(bu 4 134 | \fB\[`o]\fR 135 | .IP \(bu 4 136 | \fB\['o]\fR 137 | .IP \(bu 4 138 | \fB\[^o]\fR 139 | .IP \(bu 4 140 | \fB\[~o]\fR 141 | .IP \(bu 4 142 | \fB\[:o]\fR 143 | .IP \(bu 4 144 | \fB\[vs]\fR 145 | .IP \(bu 4 146 | \fB\[`u]\fR 147 | .IP \(bu 4 148 | \fB\['u]\fR 149 | .IP \(bu 4 150 | \fB\[^u]\fR 151 | .IP \(bu 4 152 | \fB\[:u]\fR 153 | .IP \(bu 4 154 | \fB\['y]\fR 155 | .IP \(bu 4 156 | \fB\[:y]\fR 157 | .IP \(bu 4 158 | \fB\[vz]\fR 159 | .IP \(bu 4 160 | \fB{\fR 161 | .IP \(bu 4 162 | \fB|\fR 163 | .IP \(bu 4 164 | \fB}\fR 165 | .IP \(bu 4 166 | \fB~\fR 167 | .IP \(bu 4 168 | \fB\[r!]\fR 169 | .IP \(bu 4 170 | \fB\[ct]\fR 171 | .IP \(bu 4 172 | \fB\[Po]\fR 173 | .IP \(bu 4 174 | \fB\[Cs]\fR 175 | .IP \(bu 4 176 | \fB\[Ye]\fR 177 | .IP \(bu 4 178 | \fB\[bb]\fR 179 | .IP \(bu 4 180 | \fB\[sc]\fR 181 | .IP \(bu 4 182 | \fB\[ad]\fR 183 | .IP \(bu 4 184 | \fB\[co]\fR 185 | .IP \(bu 4 186 | \fB\[Of]\fR 187 | .IP \(bu 4 188 | \fB\[Fo]\fR 189 | .IP \(bu 4 190 | \fB\[no]\fR 191 | .IP \(bu 4 192 | \fB\[rg]\fR 193 | .IP \(bu 4 194 | \fB\[a-]\fR 195 | .IP \(bu 4 196 | \fB\[de]\fR 197 | .IP \(bu 4 198 | \fB\[+-]\fR 199 | .IP \(bu 4 200 | \fB\[S2]\fR 201 | .IP \(bu 4 202 | \fB\[S3]\fR 203 | .IP \(bu 4 204 | \fB\[aa]\fR 205 | .IP \(bu 4 206 | \fB\[mc]\fR 207 | .IP \(bu 4 208 | \fB\[ps]\fR 209 | .IP \(bu 4 210 | \fB\[pc]\fR 211 | .IP \(bu 4 212 | \fB\[ac]\fR 213 | .IP \(bu 4 214 | \fB\[S1]\fR 215 | .IP \(bu 4 216 | \fB\[Om]\fR 217 | .IP \(bu 4 218 | \fB\[Fc]\fR 219 | .IP \(bu 4 220 | \fB\[14]\fR 221 | .IP \(bu 4 222 | \fB\[12]\fR 223 | .IP \(bu 4 224 | \fB\[34]\fR 225 | .IP \(bu 4 226 | \fB\[r?]\fR 227 | .IP \(bu 4 228 | \fB\[AE]\fR 229 | .IP \(bu 4 230 | \fB\[-D]\fR 231 | .IP \(bu 4 232 | \fB\[mu]\fR 233 | .IP \(bu 4 234 | \fB\[/O]\fR 235 | .IP \(bu 4 236 | \fB\[TP]\fR 237 | .IP \(bu 4 238 | \fB\[ss]\fR 239 | .IP \(bu 4 240 | \fB\[ae]\fR 241 | .IP \(bu 4 242 | \fB\[Sd]\fR 243 | .IP \(bu 4 244 | \fB\[di]\fR 245 | .IP \(bu 4 246 | \fB\[/o]\fR 247 | .IP \(bu 4 248 | \fB\[Tp]\fR 249 | .IP \(bu 4 250 | \fB\[.i]\fR 251 | .IP \(bu 4 252 | \fB\[IJ]\fR 253 | .IP \(bu 4 254 | \fB\[ij]\fR 255 | .IP \(bu 4 256 | \fB\[/L]\fR 257 | .IP \(bu 4 258 | \fB\[/l]\fR 259 | .IP \(bu 4 260 | \fB\[OE]\fR 261 | .IP \(bu 4 262 | \fB\[oe]\fR 263 | .IP \(bu 4 264 | \fB\[Fn]\fR 265 | .IP \(bu 4 266 | \fB\[.j]\fR 267 | .IP \(bu 4 268 | \fB\[ah]\fR 269 | .IP \(bu 4 270 | \fB\[ab]\fR 271 | .IP \(bu 4 272 | \fB\[a.]\fR 273 | .IP \(bu 4 274 | \fB\[ao]\fR 275 | .IP \(bu 4 276 | \fB\[ho]\fR 277 | .IP \(bu 4 278 | \fB\[*A]\fR 279 | .IP \(bu 4 280 | \fB\[*B]\fR 281 | .IP \(bu 4 282 | \fB\[*G]\fR 283 | .IP \(bu 4 284 | \fB\[*D]\fR 285 | .IP \(bu 4 286 | \fB\[*E]\fR 287 | .IP \(bu 4 288 | \fB\[*Z]\fR 289 | .IP \(bu 4 290 | \fB\[*Y]\fR 291 | .IP \(bu 4 292 | \fB\[*H]\fR 293 | .IP \(bu 4 294 | \fB\[*I]\fR 295 | .IP \(bu 4 296 | \fB\[*K]\fR 297 | .IP \(bu 4 298 | \fB\[*L]\fR 299 | .IP \(bu 4 300 | \fB\[*M]\fR 301 | .IP \(bu 4 302 | \fB\[*N]\fR 303 | .IP \(bu 4 304 | \fB\[*C]\fR 305 | .IP \(bu 4 306 | \fB\[*O]\fR 307 | .IP \(bu 4 308 | \fB\[*P]\fR 309 | .IP \(bu 4 310 | \fB\[*R]\fR 311 | .IP \(bu 4 312 | \fB\[*S]\fR 313 | .IP \(bu 4 314 | \fB\[*T]\fR 315 | .IP \(bu 4 316 | \fB\[*U]\fR 317 | .IP \(bu 4 318 | \fB\[*F]\fR 319 | .IP \(bu 4 320 | \fB\[*X]\fR 321 | .IP \(bu 4 322 | \fB\[*Q]\fR 323 | .IP \(bu 4 324 | \fB\[*W]\fR 325 | .IP \(bu 4 326 | \fB\[*a]\fR 327 | .IP \(bu 4 328 | \fB\[*b]\fR 329 | .IP \(bu 4 330 | \fB\[*g]\fR 331 | .IP \(bu 4 332 | \fB\[*d]\fR 333 | .IP \(bu 4 334 | \fB\[*e]\fR 335 | .IP \(bu 4 336 | \fB\[*z]\fR 337 | .IP \(bu 4 338 | \fB\[*y]\fR 339 | .IP \(bu 4 340 | \fB\[*h]\fR 341 | .IP \(bu 4 342 | \fB\[*i]\fR 343 | .IP \(bu 4 344 | \fB\[*k]\fR 345 | .IP \(bu 4 346 | \fB\[*l]\fR 347 | .IP \(bu 4 348 | \fB\[*m]\fR 349 | .IP \(bu 4 350 | \fB\[*n]\fR 351 | .IP \(bu 4 352 | \fB\[*c]\fR 353 | .IP \(bu 4 354 | \fB\[*o]\fR 355 | .IP \(bu 4 356 | \fB\[*p]\fR 357 | .IP \(bu 4 358 | \fB\[*r]\fR 359 | .IP \(bu 4 360 | \fB\[ts]\fR 361 | .IP \(bu 4 362 | \fB\[*s]\fR 363 | .IP \(bu 4 364 | \fB\[*t]\fR 365 | .IP \(bu 4 366 | \fB\[*u]\fR 367 | .IP \(bu 4 368 | \fB\[+f]\fR 369 | .IP \(bu 4 370 | \fB\[*x]\fR 371 | .IP \(bu 4 372 | \fB\[*q]\fR 373 | .IP \(bu 4 374 | \fB\[*w]\fR 375 | .IP \(bu 4 376 | \fB\[+h]\fR 377 | .IP \(bu 4 378 | \fB\[*f]\fR 379 | .IP \(bu 4 380 | \fB\[+p]\fR 381 | .IP \(bu 4 382 | \fB\[+e]\fR 383 | .IP \(bu 4 384 | \fB\[hy]\fR 385 | .IP \(bu 4 386 | \fB\[en]\fR 387 | .IP \(bu 4 388 | \fB\[em]\fR 389 | .IP \(bu 4 390 | \fB\[oq]\fR 391 | .IP \(bu 4 392 | \fB\[cq]\fR 393 | .IP \(bu 4 394 | \fB\[bq]\fR 395 | .IP \(bu 4 396 | \fB\[lq]\fR 397 | .IP \(bu 4 398 | \fB\[rq]\fR 399 | .IP \(bu 4 400 | \fB\[Bq]\fR 401 | .IP \(bu 4 402 | \fB\[dg]\fR 403 | .IP \(bu 4 404 | \fB\[dd]\fR 405 | .IP \(bu 4 406 | \fB\[bu]\fR 407 | .IP \(bu 4 408 | \fB\[%0]\fR 409 | .IP \(bu 4 410 | \fB\[fm]\fR 411 | .IP \(bu 4 412 | \fB\[sd]\fR 413 | .IP \(bu 4 414 | \fB\[fo]\fR 415 | .IP \(bu 4 416 | \fB\[fc]\fR 417 | .IP \(bu 4 418 | \fB\[rn]\fR 419 | .IP \(bu 4 420 | \fB\[f/]\fR 421 | .IP \(bu 4 422 | \fB\[Eu]\fR 423 | .IP \(bu 4 424 | \fB\[-h]\fR 425 | .IP \(bu 4 426 | \fB\[Im]\fR 427 | .IP \(bu 4 428 | \fB\[wp]\fR 429 | .IP \(bu 4 430 | \fB\[Re]\fR 431 | .IP \(bu 4 432 | \fB\[tm]\fR 433 | .IP \(bu 4 434 | \fB\[Ah]\fR 435 | .IP \(bu 4 436 | \fB\[18]\fR 437 | .IP \(bu 4 438 | \fB\[38]\fR 439 | .IP \(bu 4 440 | \fB\[58]\fR 441 | .IP \(bu 4 442 | \fB\[78]\fR 443 | .IP \(bu 4 444 | \fB\[<-]\fR 445 | .IP \(bu 4 446 | \fB\[ua]\fR 447 | .IP \(bu 4 448 | \fB\[->]\fR 449 | .IP \(bu 4 450 | \fB\[da]\fR 451 | .IP \(bu 4 452 | \fB\[<>]\fR 453 | .IP \(bu 4 454 | \fB\[va]\fR 455 | .IP \(bu 4 456 | \fB\[CR]\fR 457 | .IP \(bu 4 458 | \fB\[lA]\fR 459 | .IP \(bu 4 460 | \fB\[uA]\fR 461 | .IP \(bu 4 462 | \fB\[rA]\fR 463 | .IP \(bu 4 464 | \fB\[dA]\fR 465 | .IP \(bu 4 466 | \fB\[hA]\fR 467 | .IP \(bu 4 468 | \fB\[vA]\fR 469 | .IP \(bu 4 470 | \fB\[fa]\fR 471 | .IP \(bu 4 472 | \fB\[pd]\fR 473 | .IP \(bu 4 474 | \fB\[te]\fR 475 | .IP \(bu 4 476 | \fB\[es]\fR 477 | .IP \(bu 4 478 | \fB\[gr]\fR 479 | .IP \(bu 4 480 | \fB\[mo]\fR 481 | .IP \(bu 4 482 | \fB\[nm]\fR 483 | .IP \(bu 4 484 | \fB\[st]\fR 485 | .IP \(bu 4 486 | \fB\[product]\fR 487 | .IP \(bu 4 488 | \fB\[coproduct]\fR 489 | .IP \(bu 4 490 | \fB\[sum]\fR 491 | .IP \(bu 4 492 | \fB\[mi]\fR 493 | .IP \(bu 4 494 | \fB\[-+]\fR 495 | .IP \(bu 4 496 | \fB\[**]\fR 497 | .IP \(bu 4 498 | \fB\[sr]\fR 499 | .IP \(bu 4 500 | \fB\[pt]\fR 501 | .IP \(bu 4 502 | \fB\[if]\fR 503 | .IP \(bu 4 504 | \fB\[/_]\fR 505 | .IP \(bu 4 506 | \fB\[AN]\fR 507 | .IP \(bu 4 508 | \fB\[OR]\fR 509 | .IP \(bu 4 510 | \fB\[ca]\fR 511 | .IP \(bu 4 512 | \fB\[cu]\fR 513 | .IP \(bu 4 514 | \fB\[is]\fR 515 | .IP \(bu 4 516 | \fB\[tf]\fR 517 | .IP \(bu 4 518 | \fB\[ap]\fR 519 | .IP \(bu 4 520 | \fB\[|=]\fR 521 | .IP \(bu 4 522 | \fB\[=~]\fR 523 | .IP \(bu 4 524 | \fB\[~~]\fR 525 | .IP \(bu 4 526 | \fB\[==]\fR 527 | .IP \(bu 4 528 | \fB\[ne]\fR 529 | .IP \(bu 4 530 | \fB\[<=]\fR 531 | .IP \(bu 4 532 | \fB\[>=]\fR 533 | .IP \(bu 4 534 | \fB\[>>]\fR 535 | .IP \(bu 4 536 | \fB\[<<]\fR 537 | .IP \(bu 4 538 | \fB\[sb]\fR 539 | .IP \(bu 4 540 | \fB\[nb]\fR 541 | .IP \(bu 4 542 | \fB\[sp]\fR 543 | .IP \(bu 4 544 | \fB\[nc]\fR 545 | .IP \(bu 4 546 | \fB\[ib]\fR 547 | .IP \(bu 4 548 | \fB\[ip]\fR 549 | .IP \(bu 4 550 | \fB\[c+]\fR 551 | .IP \(bu 4 552 | \fB\[c*]\fR 553 | .IP \(bu 4 554 | \fB\[pp]\fR 555 | .IP \(bu 4 556 | \fB\[md]\fR 557 | .IP \(bu 4 558 | \fB\[lc]\fR 559 | .IP \(bu 4 560 | \fB\[rc]\fR 561 | .IP \(bu 4 562 | \fB\[lf]\fR 563 | .IP \(bu 4 564 | \fB\[rf]\fR 565 | .IP \(bu 4 566 | \fB\[parenlefttp]\fR 567 | .IP \(bu 4 568 | \fB\[parenleftex]\fR 569 | .IP \(bu 4 570 | \fB\[parenleftbt]\fR 571 | .IP \(bu 4 572 | \fB\[parenrighttp]\fR 573 | .IP \(bu 4 574 | \fB\[parenrightex]\fR 575 | .IP \(bu 4 576 | \fB\[parenrightbt]\fR 577 | .IP \(bu 4 578 | \fB\[bracketleftex]\fR 579 | .IP \(bu 4 580 | \fB\[bracketrightex]\fR 581 | .IP \(bu 4 582 | \fB\[lt]\fR 583 | .IP \(bu 4 584 | \fB\[lk]\fR 585 | .IP \(bu 4 586 | \fB\[lb]\fR 587 | .IP \(bu 4 588 | \fB\[bv]\fR 589 | .IP \(bu 4 590 | \fB\[rt]\fR 591 | .IP \(bu 4 592 | \fB\[rk]\fR 593 | .IP \(bu 4 594 | \fB\[rb]\fR 595 | .IP \(bu 4 596 | \fB\[an]\fR 597 | .IP \(bu 4 598 | \fB\[br]\fR 599 | .IP \(bu 4 600 | \fB\[sq]\fR 601 | .IP \(bu 4 602 | \fB\[lz]\fR 603 | .IP \(bu 4 604 | \fB\[ci]\fR 605 | .IP \(bu 4 606 | \fB\[lh]\fR 607 | .IP \(bu 4 608 | \fB\[rh]\fR 609 | .IP \(bu 4 610 | \fB\[SP]\fR 611 | .IP \(bu 4 612 | \fB\[CL]\fR 613 | .IP \(bu 4 614 | \fB\[HE]\fR 615 | .IP \(bu 4 616 | \fB\[DI]\fR 617 | .IP \(bu 4 618 | \fB\[OK]\fR 619 | .IP \(bu 4 620 | \fB\[la]\fR 621 | .IP \(bu 4 622 | \fB\[ra]\fR 623 | .RE 0 624 | -------------------------------------------------------------------------------- /test/fixtures/hard-breaks.8/input.md: -------------------------------------------------------------------------------- 1 | # hard-breaks(8) -- testing ’em hard-breaks 2 | 3 | ## DESCRIPTION 4 | 5 | This is a hard 6 | break. 7 | -------------------------------------------------------------------------------- /test/fixtures/hard-breaks.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "HARD-BREAKS" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBhard-breaks\fR - testing \[cq]em hard-breaks 4 | .SH "DESCRIPTION" 5 | .P 6 | This is a hard 7 | .br 8 | break. 9 | -------------------------------------------------------------------------------- /test/fixtures/horizontal-rule.8/input.md: -------------------------------------------------------------------------------- 1 | # horizontal-rule(8) -- testing ’em rulez 2 | 3 | ## DESCRIPTION 4 | 5 | This is is one part. 6 | 7 | * * * 8 | 9 | This is another. 10 | -------------------------------------------------------------------------------- /test/fixtures/horizontal-rule.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "HORIZONTAL-RULE" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBhorizontal-rule\fR - testing \[cq]em rulez 4 | .SH "DESCRIPTION" 5 | .P 6 | This is is one part. 7 | 8 | \(em\(em\(em 9 | .P 10 | This is another. 11 | -------------------------------------------------------------------------------- /test/fixtures/image-as-heading.6/input.md: -------------------------------------------------------------------------------- 1 | # ![image](http:\[sl]\[sl]example.comfavicon.ico)(7) -- Is’nt this awesome? ![](yes.png) 2 | 3 | Woop woop! 4 | -------------------------------------------------------------------------------- /test/fixtures/image-as-heading.6/output.roff: -------------------------------------------------------------------------------- 1 | .TH "IMAGE" "7" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBimage\fR - Is\[cq]nt this awesome? 4 | .P 5 | Woop woop! 6 | -------------------------------------------------------------------------------- /test/fixtures/invalid.8/input.md: -------------------------------------------------------------------------------- 1 | --- 2 | here is: some YAML 3 | --- 4 | 5 | # invalid(8) -- invalid nodes 6 | 7 | ## DESCRIPTION 8 | 9 | This is a paragraph[^1]. 10 | 11 | [^1]: This is a footnote. 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/invalid.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "INVALID" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBinvalid\fR - invalid nodes 4 | .SH "DESCRIPTION" 5 | .P 6 | This is a paragraph. 7 | -------------------------------------------------------------------------------- /test/fixtures/link.8/input.md: -------------------------------------------------------------------------------- 1 | # link(8) -- testing ’em H-refs 2 | 3 | ## DESCRIPTION 4 | 5 | This is an auto-link: . 6 | 7 | This is a [normal link](http://example.com). 8 | 9 | This is a [normal link with title](http://example.com "Example"). 10 | 11 | This is an empty link (WEIRD!) [](http://example.com "Example"). 12 | 13 | This is a ![normal image](http://example.com/favicon.ico). 14 | 15 | This is a ![normal image with title](http://example.com/favicon.ico "Example"). 16 | 17 | This is an empty image ![](http://example.com/favicon.ico "Example"). 18 | 19 | This is a [link reference][1]. 20 | 21 | This is an ![image reference][2]. 22 | 23 | This is an empty image reference ![][2]. 24 | 25 | This is an [invalid reference][3]. 26 | 27 | An empty link: [](). Or [like so](). 28 | 29 | [1]: http://example.com 30 | 31 | [2]: http://example.com/favicon.ico 32 | -------------------------------------------------------------------------------- /test/fixtures/link.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "LINK" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBlink\fR - testing \[cq]em H-refs 4 | .SH "DESCRIPTION" 5 | .P 6 | This is an auto-link: \fI\(lahttp://example.com\(ra\fR. 7 | .P 8 | This is a \fBnormal link\fR \fI\(lahttp://example.com\(ra\fR. 9 | .P 10 | This is a \fBnormal link with title\fR \fI\(lahttp://example.com\(ra\fR. 11 | .P 12 | This is an empty link (WEIRD!) \fI\(lahttp://example.com\(ra\fR. 13 | .P 14 | This is a \fBnormal image\fR \fI\(lahttp://example.com/favicon.ico\(ra\fR. 15 | .P 16 | This is a \fBnormal image with title\fR \fI\(lahttp://example.com/favicon.ico\(ra\fR. 17 | .P 18 | This is an empty image \fI\(lahttp://example.com/favicon.ico\(ra\fR. 19 | .P 20 | This is a \fBlink reference\fR \fI\(lahttp://example.com\(ra\fR. 21 | .P 22 | This is an \fBimage reference\fR \fI\(lahttp://example.com/favicon.ico\(ra\fR. 23 | .P 24 | This is an empty image reference \fI\(lahttp://example.com/favicon.ico\(ra\fR. 25 | .P 26 | This is an \[lB]invalid reference\[rB]\[lB]3\[rB]. 27 | .P 28 | An empty link: . Or \fBlike so\fR. 29 | -------------------------------------------------------------------------------- /test/fixtures/list.8/input.md: -------------------------------------------------------------------------------- 1 | # list(8) -- testing ’em lists 2 | 3 | ## DESCRIPTION 4 | 5 | 1. This is an ordered list 6 | 7 | * This is an unordered list 8 | 9 | * With a sublist 10 | 11 | * And other sublist! 12 | 13 | * * * 14 | 15 | 9. This is also an ordered list; 16 | 10. And this. 17 | -------------------------------------------------------------------------------- /test/fixtures/list.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "LIST" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBlist\fR - testing \[cq]em lists 4 | .SH "DESCRIPTION" 5 | .RS 0 6 | .IP 1. 4 7 | This is an ordered list 8 | .RS 4 9 | .IP \(bu 4 10 | This is an unordered list 11 | .RS 4 12 | .IP \(bu 4 13 | With a sublist 14 | .RS 4 15 | .IP \(bu 4 16 | And other sublist! 17 | .RE 0 18 | 19 | .RE 0 20 | 21 | .RE 0 22 | 23 | .RE 0 24 | 25 | 26 | \(em\(em\(em 27 | .RS 0 28 | .IP 9. 4 29 | This is also an ordered list; 30 | .IP 10. 4 31 | And this. 32 | .RE 0 33 | -------------------------------------------------------------------------------- /test/fixtures/mdast.1/input.md: -------------------------------------------------------------------------------- 1 | # mdast(1) -- Markdown processor 2 | 3 | ## SYNOPSIS 4 | 5 | `mdast` \[`options`\] _file|dir_ _..._ 6 | 7 | ## DESCRIPTION 8 | 9 | **mdast** is speedy Markdown parser (and stringifier) for multipurpose 10 | analysis in JavaScript. Node.js and browser. Lots of tests. 100% 11 | coverage. 12 | 13 | Logs verbose debugging information when `$DEBUG` is set to `"mdast"`. 14 | 15 | Options are as follows: 16 | 17 | * `-h`, `--help`: Output usage information; 18 | 19 | * `-V`, `--version`: Output version number; 20 | 21 | * `-o`, `--output` _path_: Specify output. When _path_ is omitted, input 22 | files are overwritten; 23 | 24 | * `-c`, `--config-path` _path_: specify configuration location; 25 | 26 | * `-i`, `--ignore-path` _path_: specify ignore location; 27 | 28 | * `-s`, `--setting` _settings_: specify settings; 29 | 30 | * `-u`, `--use` _plugin_: use a transform plugin, optionally with options; 31 | 32 | * `-e`, `--ext` _extensions_: specify file extensions to look for; 33 | 34 | * `-a`, `--ast`: output AST information; 35 | 36 | * `-q`, `--quiet`: output only warnings and errors; 37 | 38 | * `-S`, `--silent`: output only errors; 39 | 40 | * `--file-path `: specify file path to process as 41 | 42 | * `--no-color`: disable color in output; 43 | 44 | * `--no-rc`: disable configuration from _.mdastrc_; 45 | 46 | * `--no-ignore`: disable ignoring from _.mdastignore_. 47 | 48 | A `--` argument tells the cli parser to stop reading flags. 49 | 50 | ## DIAGNOSTICS 51 | 52 | `mdast` exits 0 on success, and 1 otherwise. 53 | 54 | ## BUGS 55 | 56 | 57 | 58 | ## SEE ALSO 59 | 60 | **mdastrc**(5), **mdastignore**(5), **mdastconfig**(7), **mdast**(3). 61 | 62 | ## AUTHOR 63 | 64 | Written by Titus Wormer 65 | -------------------------------------------------------------------------------- /test/fixtures/mdast.1/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MDAST" "1" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmdast\fR - Markdown processor 4 | .SH "SYNOPSIS" 5 | .P 6 | \fBmdast\fR \[lB]\fBoptions\fR\[rB] \fIfile|dir\fR \fI...\fR 7 | .SH "DESCRIPTION" 8 | .P 9 | \fBmdast\fR is speedy Markdown parser (and stringifier) for multipurpose analysis in JavaScript. Node.js and browser. Lots of tests. 100% coverage. 10 | .P 11 | Logs verbose debugging information when \fB$DEBUG\fR is set to \fB"mdast"\fR. 12 | .P 13 | Options are as follows: 14 | .RS 0 15 | .IP \(bu 4 16 | \fB-h\fR, \fB--help\fR: Output usage information; 17 | .IP \(bu 4 18 | \fB-V\fR, \fB--version\fR: Output version number; 19 | .IP \(bu 4 20 | \fB-o\fR, \fB--output\fR \fIpath\fR: Specify output. When \fIpath\fR is omitted, input files are overwritten; 21 | .IP \(bu 4 22 | \fB-c\fR, \fB--config-path\fR \fIpath\fR: specify configuration location; 23 | .IP \(bu 4 24 | \fB-i\fR, \fB--ignore-path\fR \fIpath\fR: specify ignore location; 25 | .IP \(bu 4 26 | \fB-s\fR, \fB--setting\fR \fIsettings\fR: specify settings; 27 | .IP \(bu 4 28 | \fB-u\fR, \fB--use\fR \fIplugin\fR: use a transform plugin, optionally with options; 29 | .IP \(bu 4 30 | \fB-e\fR, \fB--ext\fR \fIextensions\fR: specify file extensions to look for; 31 | .IP \(bu 4 32 | \fB-a\fR, \fB--ast\fR: output AST information; 33 | .IP \(bu 4 34 | \fB-q\fR, \fB--quiet\fR: output only warnings and errors; 35 | .IP \(bu 4 36 | \fB-S\fR, \fB--silent\fR: output only errors; 37 | .IP \(bu 4 38 | \fB--file-path \fR: specify file path to process as 39 | .IP \(bu 4 40 | \fB--no-color\fR: disable color in output; 41 | .IP \(bu 4 42 | \fB--no-rc\fR: disable configuration from \fI.mdastrc\fR; 43 | .IP \(bu 4 44 | \fB--no-ignore\fR: disable ignoring from \fI.mdastignore\fR. 45 | .RE 0 46 | 47 | .P 48 | A \fB--\fR argument tells the cli parser to stop reading flags. 49 | .SH "DIAGNOSTICS" 50 | .P 51 | \fBmdast\fR exits 0 on success, and 1 otherwise. 52 | .SH "BUGS" 53 | .P 54 | \fI\(lahttps://github.com/wooorm/mdast/issues\(ra\fR 55 | .SH "SEE ALSO" 56 | .P 57 | \fBmdastrc\fR(5), \fBmdastignore\fR(5), \fBmdastconfig\fR(7), \fBmdast\fR(3). 58 | .SH "AUTHOR" 59 | .P 60 | Written by Titus Wormer \fI\(latituswormer@gmail.com\(ra\fR 61 | -------------------------------------------------------------------------------- /test/fixtures/mdast.3/input.md: -------------------------------------------------------------------------------- 1 | # mdast(3) -- Markdown processor 2 | 3 | ## SYNOPSIS 4 | 5 | ```javascript 6 | var mdast = require('mdast'); 7 | var yamlConfig = require('mdast-yaml-config'); 8 | 9 | // Use a plugin. mdast-yaml-config allows settings in YAML frontmatter. 10 | var processor = mdast().use(yamlConfig); 11 | 12 | // Parse, modify, and stringify the document: 13 | var doc = processor.process( 14 | '---\n' + 15 | 'mdast:\n' + 16 | ' commonmark: true\n' + 17 | '---\n' + 18 | '\n' + 19 | '2) Some *emphasis*, **strongness**, and `code`.\n' 20 | ); 21 | ``` 22 | 23 | ## DESCRIPTION 24 | 25 | This is the application programming interface documentation for **mdast**. 26 | To find documentation for the command line interface, see `man 1 mdast`. 27 | 28 | ### mdast.use(plugin, options?) 29 | 30 | Change the way **mdast** functions by using a plugin. Plugins are documented 31 | at . 32 | 33 | **Signatures** 34 | 35 | * `processor = mdast.use(plugin, options?)`; 36 | * `processor = mdast.use(plugins)`. 37 | 38 | **Parameters** 39 | 40 | * `plugin` (`Function`) -- Plugin. 41 | * `plugins` (`Array.`) -- List of plugins. 42 | * `options` (`Object?`) -- Passed to plugin. Specified by its documentation. 43 | 44 | **Returns** 45 | 46 | `Object` -- An instance of **mdast**. The instance functions just like the 47 | **mdast** library itself (it has the same methods), but caches the `use`d 48 | plugins. 49 | 50 | ### mdast.parse(file, options?) 51 | 52 | Parse a markdown document into an abstract syntax tree. 53 | 54 | **Signatures** 55 | 56 | * `ast = mdast.parse(file|value, options?)`. 57 | 58 | **Parameters** 59 | 60 | * `file` (`File`) -- File object. 61 | * `value` (`string`) -- Source of a (virtual) file. 62 | * `options` (`Object`) -- Settings. See `man 7 mdastconfig`. 63 | 64 | **Returns** 65 | 66 | `Object` -- Node. Nodes are documented at 67 | . 68 | 69 | ### mdast.run(ast, file, done?) 70 | 71 | Modify an abstract syntax tree by applying plugins to it. 72 | 73 | **Signatures** 74 | 75 | * `ast = mdast.run(ast, file|value?, done?)`; 76 | * `ast = mdast.run(ast, done?)`. 77 | 78 | **Parameters** 79 | 80 | * `ast` (`Object`) -- Syntax tree as returned by `parse()`; 81 | * `file` (`File`) -- File object representing the input file; 82 | * `value` (`string`) -- Source of the (virtual) input file; 83 | * `done` (`function done(err?, doc?, file?)`). 84 | 85 | **Returns** 86 | 87 | `Object` -- Given AST. 88 | 89 | ### mdast.stringify(ast, options?) 90 | 91 | Compile an abstract syntax tree into a document. 92 | 93 | **Signatures** 94 | 95 | * `doc = mdast.stringify(ast, options?)`. 96 | 97 | **Parameters** 98 | 99 | * `ast` (`Object`) -- Syntax tree as returned by `parse()`; 100 | * `options` (`Object`) -- Settings. See `man 7 mdastconfig`. 101 | 102 | **Returns** 103 | 104 | `string` -- Document. Formatted in markdown by default, or in whatever a 105 | plugin generates. 106 | 107 | ### mdast.process(file, options?, done?) 108 | 109 | Parse, modify, and compile a markdown document it into something else. 110 | 111 | **Signatures** 112 | 113 | * `doc = mdast.process(file|value, options?, done?)`. 114 | 115 | **Parameters** 116 | 117 | * `file` (`File`) -- File object. 118 | * `value` (`string`) -- Source of a (virtual) file. 119 | * `options` (`Object`) -- Settings. See `man 7 mdastconfig`. 120 | * `done` (`function done(err?, doc?, file?)`). 121 | 122 | **Returns** 123 | 124 | `string` -- Document. Formatted in markdown by default, or in whatever a 125 | plugin generates. 126 | 127 | ### function done(err?, doc?, file?) 128 | 129 | Invoked when processing is complete. 130 | 131 | **Signatures** 132 | 133 | * `function done(err)`; 134 | * `function done(null, doc, file)`. 135 | 136 | **Parameters** 137 | 138 | * `exception` (`Error`) -- Failure; 139 | * `doc` (`string`) -- Document generated by the process; 140 | * `file` (`File`) -- File object representing the input file; 141 | 142 | **Returns** 143 | 144 | `string` -- Document. Formatted in markdown by default, or in whatever a 145 | plugin generates. 146 | 147 | ### File() 148 | 149 | File objects make it easy to change the directory, name, or extension of a 150 | file: let's say multiple markdown files are converted to HTML. Instead of 151 | overwriting the markdown sources, file objects make it easy to output files 152 | with a different (`"html"`) extension. In addition, files expose the raw 153 | source to plugins. 154 | 155 | **Signatures** 156 | 157 | * `file = File(file|value|options?)`. 158 | 159 | **Parameters** 160 | 161 | * `value` (`string`) -- Contents of the file; 162 | 163 | * `file` (`File`) -- Existing representation, immediately returned; 164 | 165 | * `options` (`Object`): Parts: 166 | 167 | * `directory` (`string`, default: `''`) -- Parent directory; 168 | * `filename` (`string?`, default: `null`) -- Name, without extension; 169 | * `extension` (`string`, default: `'md'`) -- Extension, without dot; 170 | * `contents` (`string`, default: `''`) -- Raw value. 171 | 172 | **Returns** 173 | 174 | `File` -- Instance. 175 | 176 | **Notes** 177 | 178 | `File` exposes an interface compatible with ESLint's formatters. For example, 179 | to expose warnings using ESLint's `compact` formatter, execute the following: 180 | 181 | ```javascript 182 | var compact = require('eslint/lib/formatters/compact'); 183 | var File = require('mdast/lib/file'); 184 | 185 | var file = new File({ 186 | 'directory': '~', 187 | 'filename': 'Hello', 188 | 'extension': 'markdown' 189 | }); 190 | 191 | file.warn('Woops, something happened!'); 192 | 193 | console.log(compact([file])); 194 | ``` 195 | 196 | Which would yield the following: 197 | 198 | ```text 199 | ~/Hello.markdown: line 0, col 0, Warning - Woops, something happened! 200 | 201 | 1 problem 202 | ``` 203 | 204 | ### File#toString() 205 | 206 | Getter for internal `contents` property. 207 | 208 | **Signatures** 209 | 210 | * `value = file.toString()`. 211 | 212 | **Returns** 213 | 214 | `string` -- Contents. 215 | 216 | ### File#messages 217 | 218 | A list of warnings and errors associated with the file. 219 | 220 | **Signature** 221 | 222 | * `Array.`. 223 | 224 | Where `Message` has the following properties: 225 | 226 | * `fatal` (`boolean?`) -- `true` when an exception occurred making 227 | the file no longer processable; 228 | 229 | * `message` (`string`) -- Error reason; 230 | 231 | * `line` (`number`) -- Starting line of exception; 232 | 233 | * `column` (`number`) -- Starting column of exception. 234 | 235 | **Notes** 236 | 237 | `File#exception()`, and in turn `File#warn()` and `File#fail()`, 238 | return `Error` objects that comply with this schema. Its results 239 | can be added to `messages`. 240 | 241 | ### File#hasFailed() 242 | 243 | Check if a fatal exception occurred making the file no longer processable. 244 | 245 | **Signatures** 246 | 247 | * `hasFailed = file.hasFailed()`. 248 | 249 | **Returns** 250 | 251 | `boolean` -- `true` if at least one of `file`s `message`s has a `fatal` 252 | property set to `true`. 253 | 254 | ### File#exception(reason, position?) 255 | 256 | Create an error. 257 | 258 | **Signatures** 259 | 260 | * `err = file.exception(err|reason, node|location|position?)`. 261 | 262 | **Parameters** 263 | 264 | * `err` (`Error`) -- Original error, whose stack is copied and message 265 | is used; 266 | 267 | * `reason` (`string`) -- Failure reason; 268 | 269 | * `node` (`Node`) -- Syntax tree object; 270 | 271 | * `location` (`Object`) -- Syntax tree location (found at `node.position`); 272 | 273 | * `position` (`Object`) -- Syntax tree position (found at 274 | `node.position.start`). 275 | 276 | **Returns** 277 | 278 | `Error` -- Pretty error with location information. 279 | 280 | This object has the following properties: 281 | 282 | * `file` (`string?`) -- Filename (including directory and extension), if 283 | applicable; 284 | 285 | * `reason` (`string`) -- Failure reason; 286 | 287 | * `line` (`number`) -- Starting line of exception; 288 | 289 | * `column` (`number`) -- Starting column of exception. 290 | 291 | ### File#warn(reason, position?) 292 | 293 | Creates an exception by passing its arguments to `File#exception()`, sets 294 | `fatal: false` on it, and adds it to `file`s `messages`. Then, it returns 295 | the exception. 296 | 297 | **See** 298 | 299 | * `File#exception(reason, position?)` 300 | 301 | ### File#fail(reason, position?) 302 | 303 | Creates an exception by passing its arguments to `File#exception()`, sets 304 | `fatal: true` on it, and adds it to `file`s `messages`. Then, it returns 305 | the exception. 306 | 307 | If `file` has a falsey `quiet` property, `File#fail()` throws the exception. 308 | 309 | **See** 310 | 311 | * `File#exception(reason, position?)` 312 | 313 | ### File#filePath() 314 | 315 | Get the filename, with extension and directory, if applicable. 316 | 317 | **Signatures** 318 | 319 | * `filename? = file.filePath()`. 320 | 321 | **Returns** 322 | 323 | `string` -- If the `file` has a `filename`, it will be prefixed with the 324 | directory (slashed), if applicable, and suffixed with the (dotted) extension 325 | (if applicable). Otherwise, an empty string is returned. 326 | 327 | ## BUGS 328 | 329 | 330 | 331 | ## SEE ALSO 332 | 333 | **mdast**(1), **mdastconfig**(7). 334 | 335 | ## AUTHOR 336 | 337 | Written by Titus Wormer 338 | -------------------------------------------------------------------------------- /test/fixtures/mdast.3/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MDAST" "3" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmdast\fR - Markdown processor 4 | .SH "SYNOPSIS" 5 | .P 6 | .RS 2 7 | .nf 8 | var mdast = require('mdast'); 9 | var yamlConfig = require('mdast-yaml-config'); 10 | 11 | // Use a plugin. mdast-yaml-config allows settings in YAML frontmatter. 12 | var processor = mdast().use(yamlConfig); 13 | 14 | // Parse, modify, and stringify the document: 15 | var doc = processor.process( 16 | '---\[rs]n' + 17 | 'mdast:\[rs]n' + 18 | ' commonmark: true\[rs]n' + 19 | '---\[rs]n' + 20 | '\[rs]n' + 21 | '2) Some *emphasis*, **strongness**, and `code`.\[rs]n' 22 | ); 23 | .fi 24 | .RE 25 | .SH "DESCRIPTION" 26 | .P 27 | This is the application programming interface documentation for \fBmdast\fR. To find documentation for the command line interface, see \fBman 1 mdast\fR. 28 | .SS "mdast.use(plugin, options?)" 29 | .P 30 | Change the way \fBmdast\fR functions by using a plugin. Plugins are documented at \fI\(lahttps://github.com/wooorm/mdast/blob/HEAD/doc/plugins.md\(ra\fR. 31 | .P 32 | \fBSignatures\fR 33 | .RS 0 34 | .IP \(bu 4 35 | \fBprocessor = mdast.use(plugin, options?)\fR; 36 | .IP \(bu 4 37 | \fBprocessor = mdast.use(plugins)\fR. 38 | .RE 0 39 | 40 | .P 41 | \fBParameters\fR 42 | .RS 0 43 | .IP \(bu 4 44 | \fBplugin\fR (\fBFunction\fR) -- Plugin. 45 | .IP \(bu 4 46 | \fBplugins\fR (\fBArray.\fR) -- List of plugins. 47 | .IP \(bu 4 48 | \fBoptions\fR (\fBObject?\fR) -- Passed to plugin. Specified by its documentation. 49 | .RE 0 50 | 51 | .P 52 | \fBReturns\fR 53 | .P 54 | \fBObject\fR -- An instance of \fBmdast\fR. The instance functions just like the \fBmdast\fR library itself (it has the same methods), but caches the \fBuse\fRd plugins. 55 | .SS "mdast.parse(file, options?)" 56 | .P 57 | Parse a markdown document into an abstract syntax tree. 58 | .P 59 | \fBSignatures\fR 60 | .RS 0 61 | .IP \(bu 4 62 | \fBast = mdast.parse(file|value, options?)\fR. 63 | .RE 0 64 | 65 | .P 66 | \fBParameters\fR 67 | .RS 0 68 | .IP \(bu 4 69 | \fBfile\fR (\fBFile\fR) -- File object. 70 | .IP \(bu 4 71 | \fBvalue\fR (\fBstring\fR) -- Source of a (virtual) file. 72 | .IP \(bu 4 73 | \fBoptions\fR (\fBObject\fR) -- Settings. See \fBman 7 mdastconfig\fR. 74 | .RE 0 75 | 76 | .P 77 | \fBReturns\fR 78 | .P 79 | \fBObject\fR -- Node. Nodes are documented at \fI\(lahttps://github.com/wooorm/mdast/blob/HEAD/doc/nodes.md\(ra\fR. 80 | .SS "mdast.run(ast, file, done?)" 81 | .P 82 | Modify an abstract syntax tree by applying plugins to it. 83 | .P 84 | \fBSignatures\fR 85 | .RS 0 86 | .IP \(bu 4 87 | \fBast = mdast.run(ast, file|value?, done?)\fR; 88 | .IP \(bu 4 89 | \fBast = mdast.run(ast, done?)\fR. 90 | .RE 0 91 | 92 | .P 93 | \fBParameters\fR 94 | .RS 0 95 | .IP \(bu 4 96 | \fBast\fR (\fBObject\fR) -- Syntax tree as returned by \fBparse()\fR; 97 | .IP \(bu 4 98 | \fBfile\fR (\fBFile\fR) -- File object representing the input file; 99 | .IP \(bu 4 100 | \fBvalue\fR (\fBstring\fR) -- Source of the (virtual) input file; 101 | .IP \(bu 4 102 | \fBdone\fR (\fBfunction done(err?, doc?, file?)\fR). 103 | .RE 0 104 | 105 | .P 106 | \fBReturns\fR 107 | .P 108 | \fBObject\fR -- Given AST. 109 | .SS "mdast.stringify(ast, options?)" 110 | .P 111 | Compile an abstract syntax tree into a document. 112 | .P 113 | \fBSignatures\fR 114 | .RS 0 115 | .IP \(bu 4 116 | \fBdoc = mdast.stringify(ast, options?)\fR. 117 | .RE 0 118 | 119 | .P 120 | \fBParameters\fR 121 | .RS 0 122 | .IP \(bu 4 123 | \fBast\fR (\fBObject\fR) -- Syntax tree as returned by \fBparse()\fR; 124 | .IP \(bu 4 125 | \fBoptions\fR (\fBObject\fR) -- Settings. See \fBman 7 mdastconfig\fR. 126 | .RE 0 127 | 128 | .P 129 | \fBReturns\fR 130 | .P 131 | \fBstring\fR -- Document. Formatted in markdown by default, or in whatever a plugin generates. 132 | .SS "mdast.process(file, options?, done?)" 133 | .P 134 | Parse, modify, and compile a markdown document it into something else. 135 | .P 136 | \fBSignatures\fR 137 | .RS 0 138 | .IP \(bu 4 139 | \fBdoc = mdast.process(file|value, options?, done?)\fR. 140 | .RE 0 141 | 142 | .P 143 | \fBParameters\fR 144 | .RS 0 145 | .IP \(bu 4 146 | \fBfile\fR (\fBFile\fR) -- File object. 147 | .IP \(bu 4 148 | \fBvalue\fR (\fBstring\fR) -- Source of a (virtual) file. 149 | .IP \(bu 4 150 | \fBoptions\fR (\fBObject\fR) -- Settings. See \fBman 7 mdastconfig\fR. 151 | .IP \(bu 4 152 | \fBdone\fR (\fBfunction done(err?, doc?, file?)\fR). 153 | .RE 0 154 | 155 | .P 156 | \fBReturns\fR 157 | .P 158 | \fBstring\fR -- Document. Formatted in markdown by default, or in whatever a plugin generates. 159 | .SS "function done(err?, doc?, file?)" 160 | .P 161 | Invoked when processing is complete. 162 | .P 163 | \fBSignatures\fR 164 | .RS 0 165 | .IP \(bu 4 166 | \fBfunction done(err)\fR; 167 | .IP \(bu 4 168 | \fBfunction done(null, doc, file)\fR. 169 | .RE 0 170 | 171 | .P 172 | \fBParameters\fR 173 | .RS 0 174 | .IP \(bu 4 175 | \fBexception\fR (\fBError\fR) -- Failure; 176 | .IP \(bu 4 177 | \fBdoc\fR (\fBstring\fR) -- Document generated by the process; 178 | .IP \(bu 4 179 | \fBfile\fR (\fBFile\fR) -- File object representing the input file; 180 | .RE 0 181 | 182 | .P 183 | \fBReturns\fR 184 | .P 185 | \fBstring\fR -- Document. Formatted in markdown by default, or in whatever a plugin generates. 186 | .SS "File()" 187 | .P 188 | File objects make it easy to change the directory, name, or extension of a file: let's say multiple markdown files are converted to HTML. Instead of overwriting the markdown sources, file objects make it easy to output files with a different (\fB"html"\fR) extension. In addition, files expose the raw source to plugins. 189 | .P 190 | \fBSignatures\fR 191 | .RS 0 192 | .IP \(bu 4 193 | \fBfile = File(file|value|options?)\fR. 194 | .RE 0 195 | 196 | .P 197 | \fBParameters\fR 198 | .RS 0 199 | .IP \(bu 4 200 | \fBvalue\fR (\fBstring\fR) -- Contents of the file; 201 | .IP \(bu 4 202 | \fBfile\fR (\fBFile\fR) -- Existing representation, immediately returned; 203 | .IP \(bu 4 204 | \fBoptions\fR (\fBObject\fR): Parts: 205 | .RS 4 206 | .IP \(bu 4 207 | \fBdirectory\fR (\fBstring\fR, default: \fB''\fR) -- Parent directory; 208 | .IP \(bu 4 209 | \fBfilename\fR (\fBstring?\fR, default: \fBnull\fR) -- Name, without extension; 210 | .IP \(bu 4 211 | \fBextension\fR (\fBstring\fR, default: \fB'md'\fR) -- Extension, without dot; 212 | .IP \(bu 4 213 | \fBcontents\fR (\fBstring\fR, default: \fB''\fR) -- Raw value. 214 | .RE 0 215 | 216 | .RE 0 217 | 218 | .P 219 | \fBReturns\fR 220 | .P 221 | \fBFile\fR -- Instance. 222 | .P 223 | \fBNotes\fR 224 | .P 225 | \fBFile\fR exposes an interface compatible with ESLint's formatters. For example, to expose warnings using ESLint's \fBcompact\fR formatter, execute the following: 226 | .P 227 | .RS 2 228 | .nf 229 | var compact = require('eslint/lib/formatters/compact'); 230 | var File = require('mdast/lib/file'); 231 | 232 | var file = new File({ 233 | 'directory': '~', 234 | 'filename': 'Hello', 235 | 'extension': 'markdown' 236 | }); 237 | 238 | file.warn('Woops, something happened!'); 239 | 240 | console.log(compact(\[lB]file\[rB])); 241 | .fi 242 | .RE 243 | .P 244 | Which would yield the following: 245 | .P 246 | .RS 2 247 | .nf 248 | ~/Hello.markdown: line 0, col 0, Warning - Woops, something happened! 249 | 250 | 1 problem 251 | .fi 252 | .RE 253 | .SS "File#toString()" 254 | .P 255 | Getter for internal \fBcontents\fR property. 256 | .P 257 | \fBSignatures\fR 258 | .RS 0 259 | .IP \(bu 4 260 | \fBvalue = file.toString()\fR. 261 | .RE 0 262 | 263 | .P 264 | \fBReturns\fR 265 | .P 266 | \fBstring\fR -- Contents. 267 | .SS "File#messages" 268 | .P 269 | A list of warnings and errors associated with the file. 270 | .P 271 | \fBSignature\fR 272 | .RS 0 273 | .IP \(bu 4 274 | \fBArray.\fR. 275 | .RE 0 276 | 277 | .P 278 | Where \fBMessage\fR has the following properties: 279 | .RS 0 280 | .IP \(bu 4 281 | \fBfatal\fR (\fBboolean?\fR) -- \fBtrue\fR when an exception occurred making the file no longer processable; 282 | .IP \(bu 4 283 | \fBmessage\fR (\fBstring\fR) -- Error reason; 284 | .IP \(bu 4 285 | \fBline\fR (\fBnumber\fR) -- Starting line of exception; 286 | .IP \(bu 4 287 | \fBcolumn\fR (\fBnumber\fR) -- Starting column of exception. 288 | .RE 0 289 | 290 | .P 291 | \fBNotes\fR 292 | .P 293 | \fBFile#exception()\fR, and in turn \fBFile#warn()\fR and \fBFile#fail()\fR, return \fBError\fR objects that comply with this schema. Its results can be added to \fBmessages\fR. 294 | .SS "File#hasFailed()" 295 | .P 296 | Check if a fatal exception occurred making the file no longer processable. 297 | .P 298 | \fBSignatures\fR 299 | .RS 0 300 | .IP \(bu 4 301 | \fBhasFailed = file.hasFailed()\fR. 302 | .RE 0 303 | 304 | .P 305 | \fBReturns\fR 306 | .P 307 | \fBboolean\fR -- \fBtrue\fR if at least one of \fBfile\fRs \fBmessage\fRs has a \fBfatal\fR property set to \fBtrue\fR. 308 | .SS "File#exception(reason, position?)" 309 | .P 310 | Create an error. 311 | .P 312 | \fBSignatures\fR 313 | .RS 0 314 | .IP \(bu 4 315 | \fBerr = file.exception(err|reason, node|location|position?)\fR. 316 | .RE 0 317 | 318 | .P 319 | \fBParameters\fR 320 | .RS 0 321 | .IP \(bu 4 322 | \fBerr\fR (\fBError\fR) -- Original error, whose stack is copied and message is used; 323 | .IP \(bu 4 324 | \fBreason\fR (\fBstring\fR) -- Failure reason; 325 | .IP \(bu 4 326 | \fBnode\fR (\fBNode\fR) -- Syntax tree object; 327 | .IP \(bu 4 328 | \fBlocation\fR (\fBObject\fR) -- Syntax tree location (found at \fBnode.position\fR); 329 | .IP \(bu 4 330 | \fBposition\fR (\fBObject\fR) -- Syntax tree position (found at \fBnode.position.start\fR). 331 | .RE 0 332 | 333 | .P 334 | \fBReturns\fR 335 | .P 336 | \fBError\fR -- Pretty error with location information. 337 | .P 338 | This object has the following properties: 339 | .RS 0 340 | .IP \(bu 4 341 | \fBfile\fR (\fBstring?\fR) -- Filename (including directory and extension), if applicable; 342 | .IP \(bu 4 343 | \fBreason\fR (\fBstring\fR) -- Failure reason; 344 | .IP \(bu 4 345 | \fBline\fR (\fBnumber\fR) -- Starting line of exception; 346 | .IP \(bu 4 347 | \fBcolumn\fR (\fBnumber\fR) -- Starting column of exception. 348 | .RE 0 349 | 350 | .SS "File#warn(reason, position?)" 351 | .P 352 | Creates an exception by passing its arguments to \fBFile#exception()\fR, sets \fBfatal: false\fR on it, and adds it to \fBfile\fRs \fBmessages\fR. Then, it returns the exception. 353 | .P 354 | \fBSee\fR 355 | .RS 0 356 | .IP \(bu 4 357 | \fBFile#exception(reason, position?)\fR 358 | .RE 0 359 | 360 | .SS "File#fail(reason, position?)" 361 | .P 362 | Creates an exception by passing its arguments to \fBFile#exception()\fR, sets \fBfatal: true\fR on it, and adds it to \fBfile\fRs \fBmessages\fR. Then, it returns the exception. 363 | .P 364 | If \fBfile\fR has a falsey \fBquiet\fR property, \fBFile#fail()\fR throws the exception. 365 | .P 366 | \fBSee\fR 367 | .RS 0 368 | .IP \(bu 4 369 | \fBFile#exception(reason, position?)\fR 370 | .RE 0 371 | 372 | .SS "File#filePath()" 373 | .P 374 | Get the filename, with extension and directory, if applicable. 375 | .P 376 | \fBSignatures\fR 377 | .RS 0 378 | .IP \(bu 4 379 | \fBfilename? = file.filePath()\fR. 380 | .RE 0 381 | 382 | .P 383 | \fBReturns\fR 384 | .P 385 | \fBstring\fR -- If the \fBfile\fR has a \fBfilename\fR, it will be prefixed with the directory (slashed), if applicable, and suffixed with the (dotted) extension (if applicable). Otherwise, an empty string is returned. 386 | .SH "BUGS" 387 | .P 388 | \fI\(lahttps://github.com/wooorm/mdast/issues\(ra\fR 389 | .SH "SEE ALSO" 390 | .P 391 | \fBmdast\fR(1), \fBmdastconfig\fR(7). 392 | .SH "AUTHOR" 393 | .P 394 | Written by Titus Wormer \fI\(latituswormer@gmail.com\(ra\fR 395 | -------------------------------------------------------------------------------- /test/fixtures/mdastconfig.7/input.md: -------------------------------------------------------------------------------- 1 | # mdastconfig(7) -- mdast configuration 2 | 3 | ## SYNOPSIS 4 | 5 | **mdast**(1), **mdast**(3), **mdastrc**(5) 6 | 7 | ## DESCRIPTION 8 | 9 | **mdast** is configured from multiple sources: 10 | 11 | * **mdast**(3) accepts configuration as a parameter to its `parse()`, 12 | `run()`, and `stringify()` methods; 13 | 14 | * **mdast**(1) accepts configuration as command line flags through 15 | `-s`, `--setting` _settings_; 16 | 17 | * **mdast**(1) additionally accepts configuration through a `settings` 18 | key in **mdastrc**(5) configuration files; 19 | 20 | * Plug-ins can configure mdast, for example, **mdast-yaml-config** allows 21 | per-file configuration to be set through YAML front-matter. 22 | 23 | For a list of available configuration options, see the SETTINGS section 24 | below or . 25 | 26 | ### SETTINGS FOR `PROCESS`, `PARSE()`, AND `STRINGIFY()` 27 | 28 | To configure the programatic interface of **mdast**, pass an object as a 29 | second parameter to `process()`, `parse()`, and `stringify()`. 30 | 31 | ### COMMAND LINE SETTINGS 32 | 33 | To configure the shell interface of **mdast**, pass a string to the 34 | `--setting` (or `-s`) flag. 35 | 36 | Command line settings are just JSON, with two exceptions: 37 | 38 | * Keys do not need to be escaped, thus, both `"foo": "bar"` and 39 | `foo: "bar"` are considered equal; 40 | 41 | * The surrounding braces must not be used: `"foo": 1`, is first 42 | wrapped as `{"foo": 1}`, and then passed to `JSON.parse`. 43 | 44 | Valid examples are: 45 | 46 | ```bash 47 | mdast --setting "foo:true" --setting "\"bar\": \"baz\"" 48 | mdast --setting "foo-bar:-2" 49 | mdast --setting "foo: false, bar-baz: [\"qux\", 1]" 50 | ``` 51 | 52 | Command Line Settings can be specified both in camel- and dash-case: 53 | `foo-bar: true` and `fooBar: true` are treated equally. 54 | 55 | ### CONFIGURATION FILES 56 | 57 | Specify directory specific settings with `.mdastrc` and `package.json` 58 | files. See **mdastrc**(5) for more information. 59 | 60 | ## SETTINGS 61 | 62 | ### PARSE 63 | 64 | See 65 | for a description of these settings. 66 | 67 | * `position` (boolean, default: true); 68 | * `gfm` (boolean, default: true); 69 | * `yaml` (boolean, default: true); 70 | * `pedantic` (boolean, default: false); 71 | * `commonmark` (boolean, default: false); 72 | * `breaks` (boolean, default: false); 73 | * `footnotes` (boolean, default: false). 74 | 75 | ### STRINGIFY 76 | 77 | See 78 | for a description of these settings. 79 | 80 | * `entities` (`false`, `true`, or `"numbers"`, default: `false`); 81 | * `setext` (boolean, default: `false`); 82 | * `closeAtx` (boolean, default: `false`); 83 | * `looseTable` (boolean, default: `false`); 84 | * `spacedTable` (boolean, default: `true`); 85 | * `incrementListMarker` (boolean, default: `true`); 86 | * `fences` (boolean, default: `false`); 87 | * `fence` (`"~"` or ``"`"``, default: ``"`"``); 88 | * `bullet` (`"-"`, `"*"`, or `"+"`, default: `"-"`); 89 | * `rule` (`"-"`, `"\*"`, or `"_"`, default: `"*"`); 90 | * `ruleRepetition` (number, default: `3`); 91 | * `ruleSpaces` (boolean, default: `false`); 92 | * `strong` (`"_"` or `"\*"`, default: `"*"`); 93 | * `emphasis` (`"\_"` or `"*"`, default: `"_"`). 94 | 95 | ## BUGS 96 | 97 | 98 | 99 | ## SEE ALSO 100 | 101 | **mdast**(1), **mdast**(3), **mdastrc**(5), **mdastignore**(5). 102 | 103 | ## AUTHOR 104 | 105 | Written by Titus Wormer 106 | -------------------------------------------------------------------------------- /test/fixtures/mdastconfig.7/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MDASTCONFIG" "7" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmdastconfig\fR - mdast configuration 4 | .SH "SYNOPSIS" 5 | .P 6 | \fBmdast\fR(1), \fBmdast\fR(3), \fBmdastrc\fR(5) 7 | .SH "DESCRIPTION" 8 | .P 9 | \fBmdast\fR is configured from multiple sources: 10 | .RS 0 11 | .IP \(bu 4 12 | \fBmdast\fR(3) accepts configuration as a parameter to its \fBparse()\fR, \fBrun()\fR, and \fBstringify()\fR methods; 13 | .IP \(bu 4 14 | \fBmdast\fR(1) accepts configuration as command line flags through \fB-s\fR, \fB--setting\fR \fIsettings\fR; 15 | .IP \(bu 4 16 | \fBmdast\fR(1) additionally accepts configuration through a \fBsettings\fR key in \fBmdastrc\fR(5) configuration files; 17 | .IP \(bu 4 18 | Plug-ins can configure mdast, for example, \fBmdast-yaml-config\fR allows per-file configuration to be set through YAML front-matter. 19 | .RE 0 20 | 21 | .P 22 | For a list of available configuration options, see the SETTINGS section below or \fI\(lahttps://github.com/wooorm/mdast/blob/HEAD/doc/options.md\(ra\fR. 23 | .SS "SETTINGS FOR \fBPROCESS\fR, \fBPARSE()\fR, AND \fBSTRINGIFY()\fR" 24 | .P 25 | To configure the programatic interface of \fBmdast\fR, pass an object as a second parameter to \fBprocess()\fR, \fBparse()\fR, and \fBstringify()\fR. 26 | .SS "COMMAND LINE SETTINGS" 27 | .P 28 | To configure the shell interface of \fBmdast\fR, pass a string to the \fB--setting\fR (or \fB-s\fR) flag. 29 | .P 30 | Command line settings are just JSON, with two exceptions: 31 | .RS 0 32 | .IP \(bu 4 33 | Keys do not need to be escaped, thus, both \fB"foo": "bar"\fR and \fBfoo: "bar"\fR are considered equal; 34 | .IP \(bu 4 35 | The surrounding braces must not be used: \fB"foo": 1\fR, is first wrapped as \fB{"foo": 1}\fR, and then passed to \fBJSON.parse\fR. 36 | .RE 0 37 | 38 | .P 39 | Valid examples are: 40 | .P 41 | .RS 2 42 | .nf 43 | mdast --setting "foo:true" --setting "\[rs]"bar\[rs]": \[rs]"baz\[rs]"" 44 | mdast --setting "foo-bar:-2" 45 | mdast --setting "foo: false, bar-baz: \[lB]\[rs]"qux\[rs]", 1\[rB]" 46 | .fi 47 | .RE 48 | .P 49 | Command Line Settings can be specified both in camel- and dash-case: \fBfoo-bar: true\fR and \fBfooBar: true\fR are treated equally. 50 | .SS "CONFIGURATION FILES" 51 | .P 52 | Specify directory specific settings with \fB.mdastrc\fR and \fBpackage.json\fR files. See \fBmdastrc\fR(5) for more information. 53 | .SH "SETTINGS" 54 | .SS "PARSE" 55 | .P 56 | See \fI\(lahttps://github.com/wooorm/mdast/blob/HEAD/doc/options.md#parse\(ra\fR for a description of these settings. 57 | .RS 0 58 | .IP \(bu 4 59 | \fBposition\fR (boolean, default: true); 60 | .IP \(bu 4 61 | \fBgfm\fR (boolean, default: true); 62 | .IP \(bu 4 63 | \fByaml\fR (boolean, default: true); 64 | .IP \(bu 4 65 | \fBpedantic\fR (boolean, default: false); 66 | .IP \(bu 4 67 | \fBcommonmark\fR (boolean, default: false); 68 | .IP \(bu 4 69 | \fBbreaks\fR (boolean, default: false); 70 | .IP \(bu 4 71 | \fBfootnotes\fR (boolean, default: false). 72 | .RE 0 73 | 74 | .SS "STRINGIFY" 75 | .P 76 | See \fI\(lahttps://github.com/wooorm/mdast/blob/HEAD/doc/options.md#stringify\(ra\fR for a description of these settings. 77 | .RS 0 78 | .IP \(bu 4 79 | \fBentities\fR (\fBfalse\fR, \fBtrue\fR, or \fB"numbers"\fR, default: \fBfalse\fR); 80 | .IP \(bu 4 81 | \fBsetext\fR (boolean, default: \fBfalse\fR); 82 | .IP \(bu 4 83 | \fBcloseAtx\fR (boolean, default: \fBfalse\fR); 84 | .IP \(bu 4 85 | \fBlooseTable\fR (boolean, default: \fBfalse\fR); 86 | .IP \(bu 4 87 | \fBspacedTable\fR (boolean, default: \fBtrue\fR); 88 | .IP \(bu 4 89 | \fBincrementListMarker\fR (boolean, default: \fBtrue\fR); 90 | .IP \(bu 4 91 | \fBfences\fR (boolean, default: \fBfalse\fR); 92 | .IP \(bu 4 93 | \fBfence\fR (\fB"~"\fR or \fB"`"\fR, default: \fB"`"\fR); 94 | .IP \(bu 4 95 | \fBbullet\fR (\fB"-"\fR, \fB"*"\fR, or \fB"+"\fR, default: \fB"-"\fR); 96 | .IP \(bu 4 97 | \fBrule\fR (\fB"-"\fR, \fB"\[rs]*"\fR, or \fB"_"\fR, default: \fB"*"\fR); 98 | .IP \(bu 4 99 | \fBruleRepetition\fR (number, default: \fB3\fR); 100 | .IP \(bu 4 101 | \fBruleSpaces\fR (boolean, default: \fBfalse\fR); 102 | .IP \(bu 4 103 | \fBstrong\fR (\fB"_"\fR or \fB"\[rs]*"\fR, default: \fB"*"\fR); 104 | .IP \(bu 4 105 | \fBemphasis\fR (\fB"\[rs]_"\fR or \fB"*"\fR, default: \fB"_"\fR). 106 | .RE 0 107 | 108 | .SH "BUGS" 109 | .P 110 | \fI\(lahttps://github.com/wooorm/mdast/issues\(ra\fR 111 | .SH "SEE ALSO" 112 | .P 113 | \fBmdast\fR(1), \fBmdast\fR(3), \fBmdastrc\fR(5), \fBmdastignore\fR(5). 114 | .SH "AUTHOR" 115 | .P 116 | Written by Titus Wormer \fI\(latituswormer@gmail.com\(ra\fR 117 | -------------------------------------------------------------------------------- /test/fixtures/mdastignore.5/input.md: -------------------------------------------------------------------------------- 1 | # mdastignore(5) -- mdast ignore files 2 | 3 | ## SYNOPSIS 4 | 5 | **.mdastignore** 6 | 7 | ## DESCRIPTION 8 | 9 | When **mdast**(1) searches for applicable files, you can tell it to ignore 10 | certain globs by placing an _.mdastignore_ file in the current working 11 | directory or its ancestral directories. 12 | 13 | Unlike **mdastrc**(5) configuration files, **mdastignore**(5) files do not 14 | cascade: when one is found (or given), the program stops looking for further 15 | files. 16 | 17 | ## FILES 18 | 19 | Each line in a **mdastignore**(5) file provides a pattern which describes to 20 | **mdast** whether or not to process a given path. 21 | 22 | * Lines are trimmed of initial and final white space; 23 | 24 | * Empty lines are ignored; 25 | 26 | * Lines which start with an octothorp (`#`) are ignored; 27 | 28 | * Lines which start with a interrogation-mark (`!`) negate, thus re-adding 29 | a previously ignored file path; 30 | 31 | For documentation regarding the glob engine itself, such as wild-cards 32 | (`*`, `?`), brace expressions (`{one,two}`), see **minimist**(1). 33 | 34 | You can pass a **gitignore**(5) file to **mdast**(1), because it has the same 35 | format as **mdastignore**(5): 36 | 37 | ```bash 38 | mdast --ignore-path .gitignore 39 | ``` 40 | 41 | **mdast**(1) searches for files with _.md_, _.mkd_, _.mkdn_, _.mkdown_, 42 | _.markdown_, or _.ron_ as extension. Other files can be explicitly provided 43 | to **mdast**(1), or an `extension` can be given to **mdast**(1) using the 44 | `--extension, -e` flag. 45 | 46 | In addition to patterns in **mdastignore**(5) files, _node_modules/\*\*_ are 47 | always excluded. 48 | 49 | Unless provided directly to **mdast**(1), hidden directories (such as _.git_) 50 | are excluded. 51 | 52 | ## BUGS 53 | 54 | 55 | 56 | ## SEE ALSO 57 | 58 | **mdast**(1), **mdastrc**(5), **mdastconfig**(7). 59 | 60 | ## AUTHOR 61 | 62 | Written by Titus Wormer 63 | -------------------------------------------------------------------------------- /test/fixtures/mdastignore.5/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MDASTIGNORE" "5" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmdastignore\fR - mdast ignore files 4 | .SH "SYNOPSIS" 5 | .P 6 | \fB.mdastignore\fR 7 | .SH "DESCRIPTION" 8 | .P 9 | When \fBmdast\fR(1) searches for applicable files, you can tell it to ignore certain globs by placing an \fI.mdastignore\fR file in the current working directory or its ancestral directories. 10 | .P 11 | Unlike \fBmdastrc\fR(5) configuration files, \fBmdastignore\fR(5) files do not cascade: when one is found (or given), the program stops looking for further files. 12 | .SH "FILES" 13 | .P 14 | Each line in a \fBmdastignore\fR(5) file provides a pattern which describes to \fBmdast\fR whether or not to process a given path. 15 | .RS 0 16 | .IP \(bu 4 17 | Lines are trimmed of initial and final white space; 18 | .IP \(bu 4 19 | Empty lines are ignored; 20 | .IP \(bu 4 21 | Lines which start with an octothorp (\fB#\fR) are ignored; 22 | .IP \(bu 4 23 | Lines which start with a interrogation-mark (\fB!\fR) negate, thus re-adding a previously ignored file path; 24 | .RE 0 25 | 26 | .P 27 | For documentation regarding the glob engine itself, such as wild-cards (\fB*\fR, \fB?\fR), brace expressions (\fB{one,two}\fR), see \fBminimist\fR(1). 28 | .P 29 | You can pass a \fBgitignore\fR(5) file to \fBmdast\fR(1), because it has the same format as \fBmdastignore\fR(5): 30 | .P 31 | .RS 2 32 | .nf 33 | mdast --ignore-path .gitignore 34 | .fi 35 | .RE 36 | .P 37 | \fBmdast\fR(1) searches for files with \fI.md\fR, \fI.mkd\fR, \fI.mkdn\fR, \fI.mkdown\fR, \fI.markdown\fR, or \fI.ron\fR as extension. Other files can be explicitly provided to \fBmdast\fR(1), or an \fBextension\fR can be given to \fBmdast\fR(1) using the \fB--extension, -e\fR flag. 38 | .P 39 | In addition to patterns in \fBmdastignore\fR(5) files, \fInode_modules/**\fR are always excluded. 40 | .P 41 | Unless provided directly to \fBmdast\fR(1), hidden directories (such as \fI.git\fR) are excluded. 42 | .SH "BUGS" 43 | .P 44 | \fI\(lahttps://github.com/wooorm/mdast/issues\(ra\fR 45 | .SH "SEE ALSO" 46 | .P 47 | \fBmdast\fR(1), \fBmdastrc\fR(5), \fBmdastconfig\fR(7). 48 | .SH "AUTHOR" 49 | .P 50 | Written by Titus Wormer \fI\(latituswormer@gmail.com\(ra\fR 51 | -------------------------------------------------------------------------------- /test/fixtures/mdastrc.5/input.md: -------------------------------------------------------------------------------- 1 | # mdastrc(5) -- mdast config files 2 | 3 | ## SYNOPSIS 4 | 5 | **.mdastrc**, **package.json** 6 | 7 | ## DESCRIPTION 8 | 9 | **mdast** gets its configuration from the command line and **mdastrc** files. 10 | 11 | For a list of available configuration options, see **mdast**(1) or . 12 | 13 | ## FILES 14 | 15 | All **mdastrc**(5) configuration files are in JSON. 16 | 17 | Automatically detected files named `package.json` use the `mdastConfig` 18 | field, whereas other files are used as a whole. 19 | 20 | ## FIELDS 21 | 22 | ### settings 23 | 24 | ```json 25 | { 26 | "settings": { 27 | "commonmark": true, 28 | "bullet": "*" 29 | } 30 | } 31 | ``` 32 | 33 | Settings contains an object mapping a setting to a value. 34 | See `man 7 mdastconfig` for available settings. 35 | 36 | ### plugins 37 | 38 | List: 39 | 40 | ```json 41 | { 42 | "plugins": [ 43 | "toc" 44 | ] 45 | } 46 | ``` 47 | 48 | Options: 49 | 50 | ```json 51 | { 52 | "plugins": { 53 | "github": { 54 | "repository": "foo/bar" 55 | } 56 | } 57 | } 58 | ``` 59 | 60 | The `plugins` field contains either an array of plugins, or an object mapping 61 | plugins to their options. 62 | 63 | When a plugin is prefixed with `mdast-` (which is recommended), the prefix 64 | can be omitted in the plugin list or map. 65 | 66 | ## CASCADE 67 | 68 | Precedence is as follows: 69 | 70 | * Plug-ins and settings passed to **mdast**(1); 71 | 72 | * Files passed to **mdast**(1); 73 | 74 | * Files named `.mdastrc` and `mdastConfig` fields in `package.json` in the 75 | directory of the processed file, and in ancestral directories; 76 | 77 | * If no `.mdastrc` and `package.json` were detected in the directory of 78 | the file or its ancestral directories, a per-user config file (`~/.mdastrc`) 79 | is used; 80 | 81 | If both `.mdastrc` and `package.json` exist in a directory, the file named 82 | `.mdastrc` takes precedence in the cascade over `package.json`. 83 | 84 | For example, for the following project: 85 | 86 | ```text 87 | project 88 | |-- docs 89 | | |-- .mdastrc 90 | | |-- doc.md 91 | | 92 | |-- .mdastrc 93 | |-- package.json 94 | |-- readme.md 95 | ``` 96 | 97 | Where `docs/.mdastrc` looks as follows: 98 | 99 | ```json 100 | { 101 | "settings": { 102 | "bullet": "+" 103 | } 104 | } 105 | ``` 106 | 107 | And `package.json` contains: 108 | 109 | ```json 110 | { 111 | "mdastConfig": { 112 | "settings": { 113 | "bullet": "*" 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | And `.mdastrc` contains: 120 | 121 | ```json 122 | { 123 | "settings": { 124 | "bullet": "-" 125 | } 126 | } 127 | ``` 128 | 129 | Then, when stringifying `docs/doc.md`, **mdast**(1) would use `bullet: "+"` 130 | because `docs/.mdastrc` takes precedence over `.mdastrc` and `package.json`. 131 | 132 | When stringifying `readme.md`, **mdast**(1) would use `bullet: "-"`, because 133 | `.mdastrc` takes precedence over `package.json`. 134 | 135 | ## BUGS 136 | 137 | 138 | 139 | ## SEE ALSO 140 | 141 | **mdast**(1), **mdastignore**(5), **mdastconfig**(7). 142 | 143 | ## AUTHOR 144 | 145 | Written by Titus Wormer 146 | -------------------------------------------------------------------------------- /test/fixtures/mdastrc.5/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MDASTRC" "5" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmdastrc\fR - mdast config files 4 | .SH "SYNOPSIS" 5 | .P 6 | \fB.mdastrc\fR, \fBpackage.json\fR 7 | .SH "DESCRIPTION" 8 | .P 9 | \fBmdast\fR gets its configuration from the command line and \fBmdastrc\fR files. 10 | .P 11 | For a list of available configuration options, see \fBmdast\fR(1) or \fI\(lahttps://github.com/wooorm/mdast/blob/HEAD/doc/options.md\(ra\fR. 12 | .SH "FILES" 13 | .P 14 | All \fBmdastrc\fR(5) configuration files are in JSON. 15 | .P 16 | Automatically detected files named \fBpackage.json\fR use the \fBmdastConfig\fR field, whereas other files are used as a whole. 17 | .SH "FIELDS" 18 | .SS "settings" 19 | .P 20 | .RS 2 21 | .nf 22 | { 23 | "settings": { 24 | "commonmark": true, 25 | "bullet": "*" 26 | } 27 | } 28 | .fi 29 | .RE 30 | .P 31 | Settings contains an object mapping a setting to a value. See \fBman 7 mdastconfig\fR for available settings. 32 | .SS "plugins" 33 | .P 34 | List: 35 | .P 36 | .RS 2 37 | .nf 38 | { 39 | "plugins": \[lB] 40 | "toc" 41 | \[rB] 42 | } 43 | .fi 44 | .RE 45 | .P 46 | Options: 47 | .P 48 | .RS 2 49 | .nf 50 | { 51 | "plugins": { 52 | "github": { 53 | "repository": "foo/bar" 54 | } 55 | } 56 | } 57 | .fi 58 | .RE 59 | .P 60 | The \fBplugins\fR field contains either an array of plugins, or an object mapping plugins to their options. 61 | .P 62 | When a plugin is prefixed with \fBmdast-\fR (which is recommended), the prefix can be omitted in the plugin list or map. 63 | .SH "CASCADE" 64 | .P 65 | Precedence is as follows: 66 | .RS 0 67 | .IP \(bu 4 68 | Plug-ins and settings passed to \fBmdast\fR(1); 69 | .IP \(bu 4 70 | Files passed to \fBmdast\fR(1); 71 | .IP \(bu 4 72 | Files named \fB.mdastrc\fR and \fBmdastConfig\fR fields in \fBpackage.json\fR in the directory of the processed file, and in ancestral directories; 73 | .IP \(bu 4 74 | If no \fB.mdastrc\fR and \fBpackage.json\fR were detected in the directory of the file or its ancestral directories, a per-user config file (\fB~/.mdastrc\fR) is used; 75 | .RE 0 76 | 77 | .P 78 | If both \fB.mdastrc\fR and \fBpackage.json\fR exist in a directory, the file named \fB.mdastrc\fR takes precedence in the cascade over \fBpackage.json\fR. 79 | .P 80 | For example, for the following project: 81 | .P 82 | .RS 2 83 | .nf 84 | project 85 | |-- docs 86 | | |-- .mdastrc 87 | | |-- doc.md 88 | | 89 | |-- .mdastrc 90 | |-- package.json 91 | |-- readme.md 92 | .fi 93 | .RE 94 | .P 95 | Where \fBdocs/.mdastrc\fR looks as follows: 96 | .P 97 | .RS 2 98 | .nf 99 | { 100 | "settings": { 101 | "bullet": "+" 102 | } 103 | } 104 | .fi 105 | .RE 106 | .P 107 | And \fBpackage.json\fR contains: 108 | .P 109 | .RS 2 110 | .nf 111 | { 112 | "mdastConfig": { 113 | "settings": { 114 | "bullet": "*" 115 | } 116 | } 117 | } 118 | .fi 119 | .RE 120 | .P 121 | And \fB.mdastrc\fR contains: 122 | .P 123 | .RS 2 124 | .nf 125 | { 126 | "settings": { 127 | "bullet": "-" 128 | } 129 | } 130 | .fi 131 | .RE 132 | .P 133 | Then, when stringifying \fBdocs/doc.md\fR, \fBmdast\fR(1) would use \fBbullet: "+"\fR because \fBdocs/.mdastrc\fR takes precedence over \fB.mdastrc\fR and \fBpackage.json\fR. 134 | .P 135 | When stringifying \fBreadme.md\fR, \fBmdast\fR(1) would use \fBbullet: "-"\fR, because \fB.mdastrc\fR takes precedence over \fBpackage.json\fR. 136 | .SH "BUGS" 137 | .P 138 | \fI\(lahttps://github.com/wooorm/mdast/issues\(ra\fR 139 | .SH "SEE ALSO" 140 | .P 141 | \fBmdast\fR(1), \fBmdastignore\fR(5), \fBmdastconfig\fR(7). 142 | .SH "AUTHOR" 143 | .P 144 | Written by Titus Wormer \fI\(latituswormer@gmail.com\(ra\fR 145 | -------------------------------------------------------------------------------- /test/fixtures/missing-heading.8/input.md: -------------------------------------------------------------------------------- 1 | This document is missing a heading, it should be detected from the file name. 2 | -------------------------------------------------------------------------------- /test/fixtures/missing-heading.8/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MISSING-HEADING" "8" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmissing-heading\fR 4 | .P 5 | This document is missing a heading, it should be detected from the file name. 6 | -------------------------------------------------------------------------------- /test/fixtures/missing-titles.2/input.md: -------------------------------------------------------------------------------- 1 | ## Level two 2 | 3 | Some text. 4 | 5 | ### Level three 6 | 7 | Some text. 8 | 9 | ## Level two 10 | 11 | Some text. 12 | 13 | ### Level three 14 | -------------------------------------------------------------------------------- /test/fixtures/missing-titles.2/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MISSING-TITLES" "2" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmissing-titles\fR 4 | .SH "LEVEL TWO" 5 | .P 6 | Some text. 7 | .SS "Level three" 8 | .P 9 | Some text. 10 | .SH "LEVEL TWO" 11 | .P 12 | Some text. 13 | .SS "Level three" 14 | -------------------------------------------------------------------------------- /test/fixtures/multiple-titles.2/input.md: -------------------------------------------------------------------------------- 1 | ## Level two 2 | 3 | Some text. 4 | 5 | # multiple-heading(2) -- ...but this is the first 6 | 7 | Some text. 8 | 9 | ### Level three 10 | 11 | Some text. 12 | 13 | # THIS DOCUMENT HAS MULTIPLE TITLES 14 | 15 | Some text. 16 | 17 | ### Level three 18 | -------------------------------------------------------------------------------- /test/fixtures/multiple-titles.2/output.roff: -------------------------------------------------------------------------------- 1 | .TH "MULTIPLE-HEADING" "2" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBmultiple-heading\fR - ...but this is the first 4 | .SS "Level two" 5 | .P 6 | Some text. 7 | .P 8 | Some text. 9 | .SS "Level three" 10 | .P 11 | Some text. 12 | .SH "THIS DOCUMENT HAS MULTIPLE TITLES" 13 | .P 14 | Some text. 15 | .SS "Level three" 16 | -------------------------------------------------------------------------------- /test/fixtures/nested-font-styles.1/input.md: -------------------------------------------------------------------------------- 1 | # nested-font-styles(1) 2 | 3 | **some bold _with some italic_ and such**. 4 | 5 | _some italic **with some bold** and such_. 6 | 7 | _some italic `with some code` and such_. 8 | 9 | # BUGS 10 | -------------------------------------------------------------------------------- /test/fixtures/nested-font-styles.1/output.roff: -------------------------------------------------------------------------------- 1 | .TH "NESTED-FONT-STYLES" "1" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBnested-font-styles\fR 4 | .P 5 | \fBsome bold \fIwith some italic\fB and such\fR. 6 | .P 7 | \fIsome italic \fBwith some bold\fI and such\fR. 8 | .P 9 | \fIsome italic \fBwith some code\fI and such\fR. 10 | .SH "BUGS" 11 | -------------------------------------------------------------------------------- /test/fixtures/nesting.3/input.md: -------------------------------------------------------------------------------- 1 | ***strong and emphasis *and* more `and` ~strikethrough~.*** 2 | -------------------------------------------------------------------------------- /test/fixtures/nesting.3/output.roff: -------------------------------------------------------------------------------- 1 | .TH "NESTING" "3" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBnesting\fR 4 | .P 5 | \fI\fBstrong and emphasis \fIand\fB more \fBand\fB \fIstrikethrough\fB.\fI\fR 6 | -------------------------------------------------------------------------------- /test/fixtures/nothing/input.md: -------------------------------------------------------------------------------- 1 | Nothing! 2 | -------------------------------------------------------------------------------- /test/fixtures/nothing/output.roff: -------------------------------------------------------------------------------- 1 | .TH "" "" "February 2016" "" "" 2 | 3 | .P 4 | Nothing! 5 | -------------------------------------------------------------------------------- /test/fixtures/overwrite.9/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "underwrite", 3 | "section": 7, 4 | "description": "Underwrite all the things!", 5 | "date": "2014-09-09", 6 | "version": "0.0.1", 7 | "manual": "The Manual" 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/overwrite.9/input.md: -------------------------------------------------------------------------------- 1 | # overwrite(8) -- Values which are overwritten 2 | -------------------------------------------------------------------------------- /test/fixtures/overwrite.9/output.roff: -------------------------------------------------------------------------------- 1 | .TH "OVERWRITE" "8" "September 2014" "0.0.1" "The Manual" 2 | .SH "NAME" 3 | \fBoverwrite\fR - Values which are overwritten 4 | -------------------------------------------------------------------------------- /test/fixtures/strikethrough.5/input.md: -------------------------------------------------------------------------------- 1 | Hello, ~pluto~! 2 | -------------------------------------------------------------------------------- /test/fixtures/strikethrough.5/output.roff: -------------------------------------------------------------------------------- 1 | .TH "STRIKETHROUGH" "5" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBstrikethrough\fR 4 | .P 5 | Hello, \fIpluto\fR! 6 | -------------------------------------------------------------------------------- /test/fixtures/tables.5/input.md: -------------------------------------------------------------------------------- 1 | # tables(5) -- tabula 2 | 3 | | Alpha Bravo | Charlie Delta | Echo Foxtrott | Golf Hotel | 4 | | :---------- | :-----------: | ------------: | ---------- | 5 | | A | sim@ple | table | **h**eader | 6 | -------------------------------------------------------------------------------- /test/fixtures/tables.5/output.roff: -------------------------------------------------------------------------------- 1 | .TH "TABLES" "5" "February 2016" "" "" 2 | .SH "NAME" 3 | \fBtables\fR - tabula 4 | .TS 5 | tab(@) allbox; 6 | cb cb cb cb 7 | l c r l . 8 | Alpha Bravo@Charlie Delta@Echo Foxtrott@Golf Hotel 9 | A@sim@ple@table@\fBh\fReader 10 | .TE 11 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {import('remark-man').Options} Options 3 | */ 4 | 5 | import assert from 'node:assert/strict' 6 | import fs from 'node:fs/promises' 7 | import process from 'node:process' 8 | import test from 'node:test' 9 | import remarkFrontmatter from 'remark-frontmatter' 10 | import remarkGfm from 'remark-gfm' 11 | import remarkMan from 'remark-man' 12 | import remarkParse from 'remark-parse' 13 | import {unified} from 'unified' 14 | import {VFile} from 'vfile' 15 | 16 | // Hack so the tests don’t need updating everytime… 17 | const ODate = global.Date 18 | 19 | // @ts-expect-error: good enough for our tests. 20 | global.Date = function (/** @type {string | number | undefined} */ value) { 21 | // Timestamp of . 22 | return new ODate(value || 1_454_861_068_000) 23 | } 24 | 25 | process.on('exit', function () { 26 | global.Date = ODate 27 | }) 28 | 29 | test('remarkMan', async function (t) { 30 | await t.test('should expose the public api', async function () { 31 | assert.deepEqual(Object.keys(await import('remark-man')).sort(), [ 32 | 'default' 33 | ]) 34 | }) 35 | 36 | await t.test('should work without filename', async function () { 37 | assert.equal( 38 | String( 39 | await unified() 40 | .use(remarkParse) 41 | .use(remarkFrontmatter) 42 | .use(remarkGfm) 43 | .use(remarkMan) 44 | .process( 45 | await fs.readFile( 46 | new URL('fixtures/nothing/input.md', import.meta.url) 47 | ) 48 | ) 49 | ), 50 | String( 51 | await fs.readFile( 52 | new URL('fixtures/nothing/output.roff', import.meta.url) 53 | ) 54 | ) 55 | ) 56 | }) 57 | 58 | await t.test('should throw on unknown nodes', async function () { 59 | try { 60 | await unified().use(remarkMan).stringify( 61 | // @ts-expect-error: unknown node. 62 | {type: 'toml', value: 'x'} 63 | ) 64 | assert.fail() 65 | } catch (error) { 66 | assert.match(String(error), /Cannot compile `toml` node/) 67 | } 68 | }) 69 | }) 70 | 71 | test('fixtures', async function (t) { 72 | const base = new URL('fixtures/', import.meta.url) 73 | const folders = await fs.readdir(base) 74 | 75 | let index = -1 76 | 77 | while (++index < folders.length) { 78 | const folder = folders[index] 79 | 80 | if (folder.startsWith('.')) continue 81 | 82 | await t.test(folder, async function () { 83 | const folderUrl = new URL(folder + '/', base) 84 | const inputUrl = new URL('input.md', folderUrl) 85 | const outputUrl = new URL('output.roff', folderUrl) 86 | const configUrl = new URL('config.json', folderUrl) 87 | 88 | const input = String(await fs.readFile(inputUrl)) 89 | const file = new VFile({path: folder + '.md', value: input}) 90 | 91 | /** @type {Readonly | undefined} */ 92 | let config 93 | /** @type {string} */ 94 | let output 95 | 96 | try { 97 | config = JSON.parse(String(await fs.readFile(configUrl))) 98 | } catch {} 99 | 100 | const actual = String( 101 | await unified() 102 | .use(remarkParse) 103 | .use(remarkFrontmatter) 104 | .use(remarkGfm) 105 | .use(remarkMan, config) 106 | .process(file) 107 | ) 108 | 109 | try { 110 | if ('UPDATE' in process.env) { 111 | throw new Error('Updating…') 112 | } 113 | 114 | output = String(await fs.readFile(outputUrl)) 115 | } catch { 116 | output = actual 117 | await fs.writeFile(outputUrl, actual) 118 | } 119 | 120 | assert.equal(actual, output) 121 | }) 122 | } 123 | }) 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "checkJs": true, 4 | "customConditions": ["development"], 5 | "declaration": true, 6 | "emitDeclarationOnly": true, 7 | "exactOptionalPropertyTypes": true, 8 | "lib": ["es2022"], 9 | "module": "node16", 10 | "strict": true, 11 | "target": "es2022" 12 | }, 13 | "exclude": ["coverage/", "node_modules/"], 14 | "include": ["**/*.js", "index.d.ts"] 15 | } 16 | --------------------------------------------------------------------------------