UNPKG

docutils-ts

Version:

Port of the Python Docutils library to TypeScript

1,142 lines (1,139 loc) 63.7 kB
import RSTState from "./rstState.js"; import * as RegExps from "../regExps.js"; import nodesFactory from '../../../nodesFactory.js'; import * as nodes from "../../../nodes.js"; import MarkupError from "../markupError.js"; import { escape2null, extractExtensionOptions, isIterable, pySplit, splitEscapedWhitespace } from "../../../utils.js"; import StringList from "../../../stringList.js"; import * as tableparser from "../tableparser.js"; import { ApplicationError, InvalidStateError } from "../../../exceptions.js"; import TransitionCorrection from "../../../transitionCorrection.js"; import * as directives from "../directives.js"; import UnexpectedIndentationError from "../../../error/unexpectedIndentationError.js"; import { fullyNormalizeName } from "../../../nodeUtils.js"; const nonWhitespaceEscapeBefore = RegExps.nonWhitespaceEscapeBefore; const simplename = RegExps.simplename; function _LoweralphaToInt(s) { return s.charCodeAt(0) - 'a'.charCodeAt(0) + 1; } function _UpperalphaToInt(s) { return s.charCodeAt(0) - 'A'.charCodeAt(0) + 1; } function _LowerromanToInt() { throw new Error('_LowerromanToInt implementation missing'); } function _UpperromanToInt() { throw new Error('_UpperromanToInt implementation missing'); } ; /** * Generic classifier of the first line of a block. */ class Body extends RSTState { constructor(stateMachine, debug = false) { super(stateMachine, debug); this.initialTransitions = ["bullet", "enumerator", "field_marker", "option_marker", "doctest", "line_block", "grid_table_top", "simple_table_top", "explicit_markup", "anonymous", "line", "text"]; // this.doubleWidthPadChar = tableparser.TableParser.doubleWidthPadChar const enum_ = {}; // @ts-ignore enum_.formatinfo = { parens: { prefix: "\\(", suffix: "\\)", start: 1, end: 1 }, rparen: { prefix: "", suffix: "\\)", start: 0, end: -1 }, period: { prefix: "", suffix: "\\.", start: 0, end: -1 } }; // @ts-ignore enum_.formats = Object.keys(enum_.formatinfo); // @ts-ignore enum_.sequences = ["arabic", "loweralpha", "upperalpha", "lowerroman", "upperroman"]; // @ts-ignore enum_.sequencepats = { arabic: "[0-9]+", loweralpha: "[a-z]", upperalpha: "[A-Z]", lowerroman: "[ivxlcdm]+", upperroman: "[IVXLCDM]+" }; // @ts-ignore enum_.converters = { arabic: parseInt, loweralpha: _LoweralphaToInt, upperalpha: _UpperalphaToInt, lowerroman: _LowerromanToInt, upperroman: _UpperromanToInt }; // @ts-ignore enum_.sequenceregexps = {}; // @ts-ignore enum_.sequences.forEach((sequence) => { // @ts-ignore enum_.sequenceregexps[sequence] = new RegExp(`${enum_.sequencepats[sequence]}$`); }); this.enum = enum_; this.gridTableTopPat = new RegExp("\\+-[-+]+-\\+ *$"); this.simpleTableTopPat = new RegExp("=+( +=+)+ *$"); const pats = {}; pats.nonalphanum7bit = "[!-/:-@[-`{-~]"; pats.alpha = "[a-zA-Z]"; pats.alphanum = "[a-zA-Z0-9]"; pats.alphanumplus = "[a-zA-Z0-9_-]"; pats.enum = `^(${enum_.sequences.filter((name) => name !== undefined).map((name) => enum_.sequencepats[name]).join('|')}|#)`; pats.optname = `${pats.alphanum}${pats.alphanumplus}*`; pats.optarg = `(${pats.alpha}${pats.alphanumplus}*|<[^<>]+>)`; pats.shortopt = `(-|\\+)${pats.alphanum}( ?${pats.optarg})?`; pats.longopt = `(--|/)${pats.optname}([ =]${pats.optarg})?`; pats.option = `(${pats.shortopt}|${pats.longopt})`; this.pats = pats; // @ts-ignore enum_.formats.forEach((format) => { // @ts-ignore pats[format] = `(${[enum_.formatinfo[format].prefix, pats.enum, enum_.formatinfo[format].suffix].join("")})`; }); this.patterns = { bullet: new RegExp("^[-+*\\u2022\\u2023\\u2043]( +|$)"), enumerator: new RegExp(`^(${pats.parens}|${pats.rparen}|${pats.period})( +|$)`), field_marker: new RegExp("^:(?![: ])([^:\\\\]|\\\\.|:(?!([ `]|$)))*(?<! ):( +|$)"), grid_table_top: this.gridTableTopPat, option_marker: new RegExp(`^${pats.option}(, ${pats.option})*( +| ?$)`), doctest: new RegExp("^>>>( +|$)"), line_block: new RegExp("^\\|( +|$)"), simple_table_top: this.simpleTableTopPat, explicit_markup: new RegExp("^\\.\\.( +|$)"), anonymous: new RegExp("^__( +|)"), line: new RegExp(`^(${pats.nonalphanum7bit})\\1* *$`), text: new RegExp(""), }; this.explicit = { patterns: { target: new RegExp(`^(_|(?!_)(\`?)(?![ \`])(.+?)${nonWhitespaceEscapeBefore})(?<!(?<!\\x00):)${nonWhitespaceEscapeBefore}[ ]?:([ ]+|$)`), reference: new RegExp(`^((${simplename})_|\`(?![ ])(.+?)${nonWhitespaceEscapeBefore}\`_)$`), // ((?P<simple>%(simplename)s)_|`(?![ ])(?P<phrase>.+?)%(non_whitespace_escape_before)s`_)$'), substitution: new RegExp(`((?![ ])(.+?)${nonWhitespaceEscapeBefore}\\|)([ ]+|$)`) }, constructs: [ [this.footnote.bind(this), new RegExp(`\\.\\.[ ]+\\[([0-9]+|\\#|\\#${simplename}|\\*)\\]([ ]+|$)`)], [this.citation.bind(this), new RegExp(`\\.\\.[ ]+\\[(${simplename})\\]([ ]+|$)`)], [this.hyperlink_target.bind(this), new RegExp("\\.\\.[ ]+_(?![ ]|$)")], [this.substitution_def.bind(this), new RegExp("\\.\\.[ ]+\\|(?![ ]|$)")], [this.directive.bind(this), new RegExp(`\\.\\.[ ]+(${simplename})[ ]?::([ ]+|$)`)] ] }; } footnote(match) { const [src, srcline] = this.rstStateMachine.getSourceAndLine(); const [indented, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.index + match[0].length }); const label = match[1]; let name = fullyNormalizeName(label); const footnote = nodesFactory.footnote(indented.join("\n")); if (src !== undefined) { footnote.source = src; } if (srcline !== undefined) { footnote.line = srcline; } if (name[0] === "#") { // auto-numbered name = name.substring(1); // autonumber label footnote.attributes.auto = 1; if (name) { footnote.attributes.names.push(name); } this.document.noteAutofootnote(footnote); } else if (name === "*") { // auto-symbol name = ""; footnote.attributes.auto = "*"; this.document.noteSymbolFootnote(footnote); } else { // manually numbered footnote.add(nodesFactory.label("", label)); footnote.attributes.names.push(name); this.document.noteFootnote(footnote); } if (name) { this.document.noteExplicitTarget(footnote, footnote); } else { this.document.setId(footnote, footnote); } /* istanbul ignore else */ if (indented && indented.length) { this.nestedParse(indented, offset, footnote); } return [[footnote], blankFinish]; } citation(match) { const [src, srcline] = this.rstStateMachine.getSourceAndLine(); const [indented, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.index + match[0].length }); const label = match[1]; const name = fullyNormalizeName(label); const citation = nodesFactory.citation(indented.join("\n")); citation.source = src; if (srcline !== undefined) { citation.line = srcline; } citation.add(nodesFactory.label("", label)); citation.attributes.names.push(name); this.document.noteCitation(citation); this.document.noteExplicitTarget(citation, citation); /* istanbul ignore else */ if (indented && indented.length) { this.nestedParse(indented, offset, citation); } return [[citation], blankFinish]; } hyperlink_target(match) { if (this.explicit === undefined) { throw new InvalidStateError('explicit undefined'); } const pattern = this.explicit.patterns.target; const lineno = this.rstStateMachine.absLineNumber(); const [block, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.index + match[0].length, untilBlank: true, stripIndent: false }); const blocktext = match.input.substring(0, match.index + match[0].length) + block.join("\n"); const block2 = new StringList([]); block.forEach((line) => { block2.push(escape2null(line)); }); let escaped = block2[0]; let blockindex = 0; let targetmatch; while (true) { targetmatch = pattern.exec(escaped); if (targetmatch) { break; } blockindex += 1; if (blockindex === block2.length) { throw new MarkupError("malformed hyperlink target."); } escaped += block2[blockindex]; } block2.splice(0, blockindex); block2[0] = (`${block2[0]} `).substring(targetmatch.index + targetmatch[0].length - escape.length + 1).trim(); const target = this.make_target(block2, blocktext, lineno, targetmatch[3]); if (target === undefined) { throw new InvalidStateError('target should be defined'); } return [[target], blankFinish]; } make_target(block, blockText, lineno, target_name) { const [targetType, data, node] = this.parse_target(block, blockText, lineno); // console.log(`target type if ${targetType} and data is ${data}`); if (targetType === "refname") { const target = nodesFactory.target(blockText, "", [], { refname: fullyNormalizeName(data) }); target.indirectReferenceName = data; this.add_target(target_name, "", target, lineno); this.document.noteIndirectTarget(target); return target; } if (targetType === "refuri") { const target = nodesFactory.target(blockText, ""); this.add_target(target_name, data, target, lineno); return target; } return node; } /** Determine the type of reference of a target. :Return: A 2-tuple, one of: - 'refname' and the indirect reference name - 'refuri' and the URI - 'malformed' and a system_message node */ parse_target(block, blockText, lineno) { if (block.length && block[block.length - 1].trim().endsWith("_")) { const reference = splitEscapedWhitespace(block.join(' ')).map((part) => pySplit(unescape(part)).join('')).join(' '); const refname = this.is_reference(reference); if (refname) { return ["refname", refname]; } } const refParts = splitEscapedWhitespace(block.join(" ")); const reference = refParts.map((part) => pySplit(unescape(part)).join("")).join(" "); return ["refuri", reference]; } is_reference(reference) { if (this.explicit === undefined) { throw new InvalidStateError('explicit undefined'); } const match = this.explicit.patterns.reference.exec(`^${nodes.whitespaceNormalizeName(reference)}`); if (!match) { return undefined; } return unescape(match[2] ? match[2] : match[3]); } add_target(targetname, refuri, target, lineno) { target.line = lineno; if (targetname) { const name = fullyNormalizeName(unescape(targetname)); target.attributes.names.push(name); if (refuri) { const uri = this.inliner.adjustUri(refuri); /* istanbul ignore else */ if (uri) { target.attributes.refuri = uri; } else { throw new ApplicationError(`problem with URI: ${refuri}`); } } this.document.noteExplicitTarget(target, this.parent); } else { // # anonymous target // istanbul ignore else if (refuri) { target.attributes.refuri = refuri; } target.attributes.anonymous = 1; this.document.noteAnonymousTarget(target); } } substitution_def(match) { if (this.explicit === undefined) { throw new InvalidStateError('explicit undefined'); } const pattern = this.explicit.patterns.substitution; const [src, srcline] = this.rstStateMachine.getSourceAndLine(); const matchEnd = match.index + match[0].length; let myBlankFinish; const [block, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: matchEnd, stripIndent: false }); myBlankFinish = blankFinish; let myOffset = offset; // unuseD? fixme const blockText = (match.input.substring(0, matchEnd) + block.join("\n")); block.disconnect(); let escaped = escape2null(block[0].trimRight()); let blockIndex = 0; let subDefMatch; let done = false; while (!done) { subDefMatch = pattern.exec(escaped); if (subDefMatch) { done = true; } else { blockIndex += 1; try { escaped = `${escaped} ${escape2null(block[blockIndex].trim())}`; } catch (error) { throw new MarkupError("malformed substitution definition."); } } } // @ts-ignore const subDefMatchEnd = subDefMatch.index + subDefMatch[0].length; block.splice(0, blockIndex); // strip out the substitution marker const tmpLine = `${block[0].trim()} `; block[0] = tmpLine.substring(subDefMatchEnd - escaped.length - 1, tmpLine.length - 1); if (!block[0]) { block.splice(0, 1); myOffset += 1; } while (block.length && !block[block.length - 1].trim()) { block.pop(); } // @ts-ignore const subname = subDefMatch[2]; const substitutionNode = nodesFactory.substitution_definition(blockText); substitutionNode.source = src; if (srcline !== undefined) { substitutionNode.line = srcline; } if (!block.length) { const msg = this.reporter.warning(`Substitution definition "${subname}" missing contents.`, [nodesFactory.literal_block(blockText, blockText)], { source: src, line: srcline }); return [[msg], myBlankFinish]; } block[0] = block[0].trim(); substitutionNode.attributes.names.push(nodes.whitespaceNormalizeName(subname)); const [newAbsOffset, blankFinish2] = this.nestedListParse(block, { inputOffset: myOffset, node: substitutionNode, initialState: "SubstitutionDef", blankFinish: myBlankFinish }); myBlankFinish = blankFinish2; let i = 0; substitutionNode.getChildren().slice().forEach((node) => { // this is a mixin check!! if (!(node.isInline() || node instanceof nodes.Text)) { this.parent.add(substitutionNode.getChild(0)); substitutionNode.removeChild(i); } else { i += 1; } }); const result = substitutionNode.traverse({ condition: nodes.Element }).map((node) => { if (this.disallowedInsideSubstitutionDefinitions(node)) { const pformat = nodesFactory.literal_block("", node.pformat().trimRight()); const msg = this.reporter.error(`Substitution definition contains illegal element <${node.tagname}>:`, [pformat, nodesFactory.literal_block(blockText, blockText)], { source: src, line: srcline }); return [[msg], blankFinish]; } return undefined; }).filter((x) => x !== undefined); if (result.length) { return result[0]; } if (!substitutionNode.hasChildren()) { const msg = this.reporter.warning(`Substitution definition "${subname}" empty or invalid.`, [nodesFactory.literal_block(blockText, blockText)], { source: src, line: srcline }); return [[msg], blankFinish]; } this.document.noteSubstitutionDef(substitutionNode, subname, this.parent); return [[substitutionNode], blankFinish]; } disallowedInsideSubstitutionDefinitions(node) { if (((node.attributes && node.attributes.ids && node.attributes.ids.length) || node instanceof nodes.reference || node instanceof nodes.footnote_reference) && node.attributes.auto) { return true; } return false; } /** Returns a 2-tuple: list of nodes, and a "blank finish" boolean. */ directive(match, optionPresets) { const typeName = match[1]; if (typeof typeName === "undefined") { throw new Error("need typename"); } let language = this.memo && this.memo.language; const [directiveClass, messages] = directives.directive(typeName, this.document, language); this.parent.add(messages); if (directiveClass) { return this.runDirective(directiveClass, match, typeName, optionPresets); } return this.unknown_directive(typeName); } /** * Parse a directive then run its directive function. * * Parameters: * * - `directive`: The class implementing the directive. Must be * a subclass of `rst.Directive`. * * - `match`: A regular expression match object which matched the first * line of the directive. * * - `typeName`: The directive name, as used in the source text. * * - `option_presets`: A dictionary of preset options, defaults for the * directive options. Currently, only an "alt" option is passed by * substitution definitions (value: the substitution name), which may * be used by an embedded image directive. * * Returns a 2-tuple: list of nodes, and a "blank finish" boolean. **/ runDirective(directiveClass, match, typeName, option_presets) { /* if isinstance(directive, (FunctionType, MethodType)): from docutils.parsers.rst import convert_directive_function directive = convert_directive_function(directive) */ const lineno = this.rstStateMachine.absLineNumber(); const initialLineOffset = this.rstStateMachine.lineOffset; const [indented, indent, lineOffset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.index + match[0].length, stripTop: false }); const blockText = this.rstStateMachine.inputLines.slice(initialLineOffset, this.rstStateMachine.lineOffset + 1).join('\n'); let args = []; let options; let content; let contentOffset; /* try {*/ // @ts-ignore [args, options, content, contentOffset] = this.parseDirectiveBlock(indented, lineOffset, directiveClass, option_presets); /* } catch (error) { if (error instanceof MarkupError) { const err = this.reporter!.error(`Error in "${typeName}" directive:\n${error.args.join(' ')}`, [nodesFactory.literal_block(blockText, blockText)], { line: lineno }); return [[err], blankFinish]; } } */ const directiveInstance = new directiveClass(typeName, args, options, content, lineno, contentOffset, blockText, this, this.rstStateMachine); let result; try { result = directiveInstance.run(); } catch (error) { let level = 3; // Default error level let msg = 'Unknown error occurred'; // Check if it's the expected error type if (error && typeof error === 'object' && 'level' in error && 'msg' in error) { level = error.level; msg = error.msg; } else if (error instanceof Error) { msg = error.message; } const msgNode = this.reporter.systemMessage(level, msg, [], { line: lineno }); msgNode.add(nodesFactory.literal_block(blockText, blockText)); result = [msgNode]; } /* assert isinstance(result, list), \ 'Directive "%s" must return a list of nodes.' % typeName for i in range(len(result)): assert isinstance(result[i], nodes.Node), \ ('Directive "%s" returned non-Node object (index %s): %r' % (typeName, i, result[i])) */ return [result, blankFinish || this.rstStateMachine.isNextLineBlank()]; } unknown_directive(typeName) { const lineno = this.rstStateMachine.absLineNumber(); const [indented, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: 0, stripIndent: false }); const text = indented.join("\n"); const error = this.reporter.error(`Unknown directive type "${typeName}".`, [nodesFactory.literal_block(text, text)], { line: lineno }); return [[error], blankFinish]; } comment(match) { const matchEnd = match.result.index + match.result[0].length; if (!match.result.input.substring(matchEnd).trim() && this.rstStateMachine.isNextLineBlank()) { // # an empty comment? return [[nodesFactory.comment()], true]; // "A tiny but practical wart." } const [indented, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: matchEnd }); while (indented && indented.length && !indented[indented.length - 1].trim()) { indented.trimEnd(); } const text = indented.join("\n"); return [[nodesFactory.comment(text, text)], blankFinish]; } /** Footnotes, hyperlink targets, directives, comments. */ explicit_markup(match, context, nextState) { const r = this.explicit_construct(match); /* istanbul ignore if */ if (!isIterable(r)) { throw new Error(""); } const [nodelist, blankFinish] = r; this.parent.add(nodelist); this.explicit_list(blankFinish); return [[], nextState, []]; } /** Determine which explicit construct this is, parse & return it. */ explicit_construct(match) { if (this.explicit === undefined) { throw new InvalidStateError('explicit undefined'); } const errors = []; if (Object.keys(this.explicit).length === 0) { throw new Error(`invalid state!`); } if (this.explicit.constructs === undefined || this.explicit.constructs.map === undefined) { throw new Error('invalid state'); } const r = this.explicit.constructs.map( // @ts-ignore ([method, pattern]) => [method, pattern, pattern.exec(match.result.input)]); const r2 = r .find((x) => x[2] && x[0] !== undefined); if (r2) { const [method, pattern, expmatch] = r2; try { // direct return of method result - returns ParseResult // @ts-ignore return method(expmatch); } catch (error) { if (error instanceof MarkupError) { const lineno = this.rstStateMachine.absLineNumber(); const message = error.message; //args ? error.args.join(" ") : ""; errors.push(this.reporter.warning(message, [], { line: lineno })); } else { throw error; } } } const [nodelist, blankFinish] = this.comment(match); return [[...nodelist, ...errors], blankFinish]; } /** * Create a nested state machine for a series of explicit markup * constructs (including anonymous hyperlink targets). */ explicit_list(blankFinish) { const offset = this.rstStateMachine.lineOffset + 1; // next line const [newlineOffset, blankFinish1] = this.nestedListParse(this.rstStateMachine.inputLines.slice(offset), { inputOffset: this.rstStateMachine.absLineOffset() + 1, node: this.parent, initialState: "Explicit", blankFinish, matchTitles: this.rstStateMachine.matchTitles, }); this.gotoLine(newlineOffset); if (!blankFinish1) { this.parent.add(this.unindentWarning("Explicit markup")); } } /** Anonymous hyperlink targets. */ anonymous(match, context, nextState) { const [nodelist, blankFinish] = this.anonymous_target(match); this.parent.add(nodelist); this.explicit_list(blankFinish); return [[], nextState, []]; } anonymous_target(match) { const lineno = this.rstStateMachine.absLineNumber(); const [block, indent, offset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.result.index + match.result[0].length, untilBlank: true }); const blocktext = match.result.input.substring(0, match.result.index + match.result[0].length) + block.join("\n"); const blockLines = []; block.forEach((line) => { blockLines.push(escape2null(line)); }); const block2 = new StringList(blockLines); const target = this.make_target(block2, blocktext, lineno, ""); return [target !== undefined ? [target] : [], blankFinish]; } indent(match, context, nextState) { const [indented, indent, lineOffset, blankFinish] = this.rstStateMachine.getIndented({}); /* istanbul ignore if */ if (indented === undefined) { throw new Error(); } const elements = this.block_quote(indented, lineOffset); this.parent.add(elements); if (!blankFinish) { this.parent.add(this.unindentWarning("Block quote")); } return [context, nextState, []]; } block_quote(indented, lineOffset) { /* istanbul ignore if */ if (!indented) { throw new Error(); } const elements = []; while (indented && indented.length) { const [blockquoteLines, attributionLines, attributionOffset, outIndented, newLineOffset] = this.split_attribution(indented, lineOffset); const blockquote = nodesFactory.block_quote(); indented = outIndented; this.nestedParse(blockquoteLines, lineOffset, blockquote); elements.push(blockquote); if (attributionLines) { // fixme // @ts-ignore const [attribution, messages] = this.parse_attribution(attributionLines, attributionOffset); blockquote.add(attribution); // @ts-ignore elements.push(...messages); } lineOffset = newLineOffset; while (indented && indented.length && !indented[0]) { indented = indented.slice(1); lineOffset += 1; } } return elements; } split_attribution(indented, lineOffset) { this.attribution_pattern = new RegExp("(---?(?!-)|\\u2014) *(?=[^ \\n])"); let blank; let nonblankSeen = false; for (let i = 0; i < indented.length; i += 1) { const line = indented[i].trimRight(); if (line) { if (nonblankSeen && blank === i - 1) { const match = this.attribution_pattern.exec(line); if (match) { const [attributionEnd, indent] = this.check_attribution(indented, i); if (attributionEnd) { const aLines = indented.slice(i, attributionEnd); aLines.trimLeft(match.index + match[0].length, undefined, 1); aLines.trimLeft(indent, 1); return [ indented.slice(0, i), aLines, i, indented.slice(attributionEnd), lineOffset + attributionEnd ]; } } } nonblankSeen = true; } else { blank = i; } } return [indented, undefined, undefined, undefined, undefined]; } check_attribution(indented, attributionStart) { let indent = null; let i; for (i = attributionStart + 1; i < indented.length; i += 1) { const line = indented[i].trimRight(); if (!line) { break; } if (indent == null) { indent = line.length - line.trimLeft().length; } else if ((line.length - line.lstrip().length) !== indent) { return [null, null]; // bad shape; not an attribution } } if (i === indented.length) { i += 1; } return [i, indent || 0]; } /** Enumerated List Item */ enumerator(match, context, nextState) { // @ts-ignore const [format, sequence, text, ordinal] = this.parseEnumerator(match); // @ts-ignore if (!this.isEnumeratedListItem(ordinal, sequence, format)) { throw new TransitionCorrection("text"); } const enumlist = nodesFactory.enumerated_list(); this.parent.add(enumlist); if (sequence === "#") { enumlist.enumtype = "arabic"; } else { enumlist.enumtype = sequence; } enumlist.prefix = this.enum.formatinfo[format].prefix; enumlist.suffix = this.enum.formatinfo[format].suffix; if (ordinal !== 1) { enumlist.start = ordinal; const msg = this.reporter.info(`Enumerated list start value not ordinal-1: "${text}" (ordinal ${ordinal})`); this.parent.add(msg); } const [listitem, blankFinish1] = this.list_item(match.result.index + match.result[0].length); let blankFinish = blankFinish1; enumlist.add(listitem); const offset = this.rstStateMachine.lineOffset + 1; // next line const [newlineOffset, blankFinish2] = this.nestedListParse(this.rstStateMachine.inputLines.slice(offset), { inputOffset: this.rstStateMachine.absLineOffset() + 1, node: enumlist, initialState: "EnumeratedList", blankFinish, extraSettings: { lastordinal: ordinal, format, auto: sequence === "#" } }); blankFinish = blankFinish2; this.gotoLine(newlineOffset); if (!blankFinish) { this.parent.add(this.unindentWarning("Enumerated list")); } return [[], nextState, []]; } parse_attribution(indented, lineOffset) { const text = indented.join("\n").trimRight(); const lineno = this.rstStateMachine.absLineNumber() + lineOffset; const [textnodes, messages] = this.inline_text(text, lineno); const anode = nodesFactory.attribution(text, "", textnodes); const [source, line] = this.rstStateMachine.getSourceAndLine(lineno); anode.source = source; if (line !== undefined) { anode.line = line; } return [anode, messages]; } bullet(match, context, nextState) { const bulletlist = nodesFactory.bullet_list(); let sourceAndLine = this.rstStateMachine.getSourceAndLine(); bulletlist.source = sourceAndLine[0]; if (sourceAndLine[1] !== undefined) { bulletlist.line = sourceAndLine[1]; } /* istanbul ignore if */ if (!this.parent) { throw new Error("no parent"); } this.parent.add(bulletlist); bulletlist.attributes.bullet = match.result[0].substring(0, 1); const [i, blankFinish1] = this.list_item(match.pattern.lastIndex + match.result[0].length); /* -1 ? */ let blankFinish = blankFinish1; /* istanbul ignore if */ if (!i) { throw new Error("no node"); } bulletlist.append(i); const offset = this.rstStateMachine.lineOffset + 1; const [newLineOffset, blankFinish2] = this.nestedListParse(this.rstStateMachine.inputLines.slice(offset), { inputOffset: this.rstStateMachine.absLineOffset() + 1, node: bulletlist, initialState: "BulletList", blankFinish }); blankFinish = blankFinish2; this.gotoLine(newLineOffset); if (!blankFinish) { this.parent.add(this.unindentWarning("Bullet list")); } return [[], nextState, []]; } list_item(indent) { // console.log(`in list_item (indent=${indent})`); /* istanbul ignore if */ if (indent == null) { throw new Error("Need indent"); } let indented; let lineOffset; let blankFinish; let outIndent; if (this.rstStateMachine.line.length > indent) { // console.log(`get known indentd`); [indented, lineOffset, blankFinish] = this.rstStateMachine.getKnownIndented({ indent }); } else { [indented, outIndent, lineOffset, blankFinish] = (this.rstStateMachine.getFirstKnownIndented({ indent })); } const listitem = nodesFactory.list_item(indented.join("\n")); if (indented && indented.length) { // fixme equivalent? this.nestedParse(indented, lineOffset, listitem); } return [listitem, blankFinish]; } /** Construct and return the next enumerated list item marker, and an auto-enumerator ("#" instead of the regular enumerator). Return ``None`` for invalid (out of range) ordinals. */ make_enumerator(ordinal, sequence, format) { /* let enumerator: string|undefined; if(sequence === '#') { enumerator = '#' else if(sequence === 'arabic') { enumerator = ordinal.toString(); }else { if(sequence.endsWith('alpha')) { if(ordinal > 26) { return undefined; } // enumerator = chr(ordinal + ord('a') - 1) } else if(sequence.endsWith('roman')) { try { try: enumerator = roman.toRoman(ordinal) except roman.RomanError: return None else: # shouldn't happen raise ParserError('unknown enumerator sequence: "%s"' % sequence) if sequence.startswith('lower'): enumerator = enumerator.lower() elif sequence.startswith('upper'): enumerator = enumerator.upper() else: # shouldn't happen raise ParserError('unknown enumerator sequence: "%s"' % sequence) formatinfo = self.enum.formatinfo[format] next_enumerator = (formatinfo.prefix + enumerator + formatinfo.suffix + ' ') auto_enumerator = formatinfo.prefix + '#' + formatinfo.suffix + ' ' return next_enumerator, auto_enumerator */ return undefined; } /** * Transition function for field_maker. Performs a nested list parse. */ field_marker(match, context, nextState) { const fieldList = nodesFactory.field_list(); this.parent.add(fieldList); const [field, blankFinish1] = this.field(match); let blankFinish = blankFinish1; fieldList.add(field); const offset = this.rstStateMachine.lineOffset + 1; const [newlineOffset, blankFinish2] = this.nestedListParse(this.rstStateMachine.inputLines.slice(offset), { inputOffset: this.rstStateMachine.absLineOffset() + 1, node: fieldList, initialState: "FieldList", blankFinish, }); blankFinish = blankFinish2; this.gotoLine(newlineOffset); if (!blankFinish) { this.parent.add(this.unindentWarning("Field list")); } return [[], nextState, []]; } field(match) { const name = this.parse_field_marker(match); const [src, srcline] = this.rstStateMachine.getSourceAndLine(); const lineno = this.rstStateMachine.absLineNumber(); const [indented, indent, lineOffset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.result.index + match.result[0].length }); const fieldNode = nodesFactory.field(); fieldNode.source = src; if (srcline !== undefined) { fieldNode.line = srcline; } const [nameNodes, nameMessages] = this.inline_text(name, lineno); fieldNode.add(nodesFactory.field_name(name, "", nameNodes, {})); const fieldBody = nodesFactory.field_body(indented.join("\n"), nameMessages, {}); fieldNode.add(fieldBody); if (indented && indented.length) { this.parse_field_body(indented, lineOffset, fieldBody); } return [fieldNode, blankFinish]; } /** Extract & return field name from a field marker match. */ parse_field_marker(match) { let field = match.result[0].substring(1); field = field.substring(0, field.lastIndexOf(":")); return field; } parse_field_body(indented, offset, node) { this.nestedParse(indented, offset, node); } /** Option list item. */ option_marker(match, context, nextState) { const optionlist = nodesFactory.option_list(); const [source, line] = this.rstStateMachine.getSourceAndLine(); let listitem; let blankFinish; try { [listitem, blankFinish] = this.option_list_item(match); } catch (error) { if (error instanceof MarkupError) { // This shouldn't happen; pattern won't match. const msg = this.reporter.error(`Invalid option list marker: ${error}`); this.parent.add(msg); const [indented, indent, lineOffset, blankFinish2] = this.rstStateMachine.getFirstKnownIndented({ indent: match.result.index + match.result[0].length }); blankFinish = blankFinish2; const elements = this.block_quote(indented, lineOffset); this.parent.add(elements); if (!blankFinish) { this.parent.add(this.unindentWarning("Option list")); } return [[], nextState, []]; } throw error; } this.parent.add(optionlist); optionlist.add(listitem); const offset = this.rstStateMachine.lineOffset + 1; // next line const [newlineOffset, blankFinish3] = this.nestedListParse(this.rstStateMachine.inputLines.slice(offset), { inputOffset: this.rstStateMachine.absLineOffset() + 1, node: optionlist, initialState: "OptionList", blankFinish }); blankFinish = blankFinish3; this.gotoLine(newlineOffset); if (!blankFinish) { this.parent.add(this.unindentWarning("Option list")); } return [[], nextState, []]; } option_list_item(match) { const offset = this.rstStateMachine.absLineOffset(); const options = this.parse_option_marker(match); const [indented, indent, lineOffset, blankFinish] = this.rstStateMachine.getFirstKnownIndented({ indent: match.result.index + match.result[0].length }); if (!indented || !indented.length) { // not an option list item this.gotoLine(offset); throw new TransitionCorrection("text"); } const optionGroup = nodesFactory.option_group("", options); const description = nodesFactory.description(indented.join("\n")); const optionListItem = nodesFactory.option_list_item("", [optionGroup, description]); if (indented && indented.length) { this.nestedParse(indented, lineOffset, description); } return [optionListItem, blankFinish]; } /** * Return a list of `node.option` and `node.option_argument` objects, * parsed from an option marker match. * * :Exception: `MarkupError` for invalid option markers. */ parse_option_marker(match) { const optlist = []; const optionstrings = match.result[0].trimRight().split(", "); optionstrings.forEach((optionstring) => { const tokens = optionstring.split(/s+/); let delimiter = " "; const firstopt = tokens[0].split("=", 2); if (firstopt.length > 1) { // "--opt=value" form tokens.splice(0, 1, ...firstopt); // fixme check delimiter = "="; } else if (tokens[0].length > 2 && ((tokens[0].indexOf("-") === 0 && tokens[0].indexOf("--") !== 0) || tokens[0].indexOf("+") === 0)) { // "-ovalue" form tokens.splice(0, 1, tokens[0].substring(0, 2), tokens[0].substring(2)); delimiter = ""; } if ((tokens.length > 1) && (tokens[1].startsWith("<") && tokens[-1].endsWith(">"))) { // "-o <value1 value2>" form; join all values into one token tokens.splice(1, tokens.length, tokens.slice(1).join("")); } if ((tokens.length > 0) && (tokens.length <= 2)) { const option = nodesFactory.option(optionstring); option.add(nodesFactory.option_string(tokens[0], tokens[0])); if (tokens.length > 1) { option.add(nodesFactory.option_argument(tokens[1], tokens[1], [], { delimiter })); } optlist.push(option); } else { throw new MarkupError(`wrong number of option tokens (=${tokens.length}), should be 1 or 2: "${optionstring}"`); } }); return optlist; } doctest(match, context, nextState) { const data = this.rstStateMachine.getTextBlock().join("\n"); // TODO: prepend class value ['pycon'] (Python Console) // parse with `directives.body.CodeBlock` (returns literal-block // with class "code" and syntax highlight markup). this.parent.add(nodesFactory.doctest_block(data, data)); return [[], nextState, []]; } /** First line of a line block. */ line_block(match, context, nextState) { const block = nodesFactory.line_block(); this.parent.add(block); const lineno = this.rstStateMachine.absLineNumber(); const [line, messages, blankFinish1] = this.line_block_line(match, lineno); let blankFinish = blankFinish1; block.add(line); this.parent.add(messages); if (!blankFinish) { const offset = this.rstStateMachine.lineOffset + 1; // next line const [newLineOffset, blankFinish2] = this.nestedListParse(this.rstStateMachine.inputLines.slice(offset), { inputOffset: this.rstStateMachine.absLineOffset() + 1, node: block, initialState: "LineBlock", blankFinish: false }); blankFinish = blankFinish2; this.gotoLine(newLineOffset); } if (!blankFinish) { this.parent.add(this.reporter.warning("Line block ends without a blank line.", [], { line: lineno + 1 })); } if (block.hasChildren()) { const child = block.getChild(0); // is null something we'll get here?? fixme if (child.attributes.indent == null) { child.attributes.indent = 0; } this.nest_line_block_lines(block); } return [[], nextState, []]; } /** Return one line element of a line_block. */ line_block_line(match, lineno) { const [indented, indent, lineOffset, blankFinish] = this .rstStateMachine.getFirstKnownIndented({ indent: match.result.index + match.result[0].length, untilBlank: true }); const text = indented.join("\n"); const [textNodes, messages] = this.inline_text(text, lineno); const line = nodesFactory.line(text, "", textNodes); if (match.result.input.trimRight() !== "|") { line.indent = match.result[1].length - 1; } return [line, messages, blankFinish]; } nest_line_block_lines(block) { for (let i = 1; i < block.getNumChildren(); i += 1) { const child = block.getChild(i); if (child.indent === undefined && i !== 0) { child.indent = block.getChild(i - 1).indent; } } this.nest_line_block_segment(block); } nest_line_block_segment(block) { const indents = []; let least; for (let i = 0; i < block.getNumChildren(); i += 1) { const child = block.getChild(i); const indent = child.indent; if (least === undefined || indent < least) { least = indent; } indents.push(child.indent); } const newItems = []; let newBlock = nodesFactory.line_block(); for (let i = 0; i < block.getNumChildren(); i += 1) { const item = block.getChild(i); if (item.indent > least) { newBlock.add(item); } else { if (newBlock.hasChildren()) { this.nest_line_block_segment(newBlock); newItems.push(newBlock); newBlock = nodesFactory.line_block(); } newItems.push(item); } } if (newBlock.hasChildren()) { this.nest_line_block_segment(newBlock); newItems.push(newBlock); } for (let i = 0; i < newItems.length; i += 1) { block.append(newItems[i]); } } /** Top border of a full table. */ grid_table_top(match, context, nextState) { return this.table_top(match, context, nextState, this.isolate_grid_table.bind(this), tableparser.GridTableParser); } /** Top border of a simple table. */ simple_table_top(match, context, nextState) { return this.table_top(match, context, nextState, this.isolate_simple_table.bind(this), tableparser.SimpleTableParser); } /* Top border of a generic table. */ table_top(match, context, nextState, isolate_function, parser_class) { const [nodelist, blankFinish] = this.table(isolate_function, parser_class); this.parent.add(nodelist); if (!blankFinish) { const msg = this.reporter.warning("Blank line required after table.", [], { line: this.rstStateMachine.absLineNumber() + 1 }); this.parent.add(msg); } return [[], nextState, []]; } /** Parse a table. */ table(isolateFunction, parserClass) { const r = isolateFunction(); if (!isIterable(r)) { throw new Error(); } const [block, messages, blankFinish] = r; let nodelist; if (block && block.length) { try { const parser = new parserClass(); const tabledata = parser.parse(block); const tableline = (this.rstStateMachine.absLineNumber() - block.length + 1); const table = this.build_table(tabledata, tableline); nodelist = [table, ...messages]; } catch (error) { if (error instanceof tableparser.TableMarkupError) { nodelist = [...this.malformed_table(block, error.message, error.offset), ...messages]; } else { throw error; } } } else { nodelist = messages; } return [nodelist, blankFinish]; } isolate_grid_table() { const messages = []; let block; let blankFinish = 1; try { block = this.rstStateMachine.getTextBlock(true); } catch (error) { if (error instanceof UnexpectedIndentationError) { // const block2 = error.block; const src = error.source; const srcline = error.lineno; messages.push(this.reporter.error("Unexpected indentation.", [], { source: src, line: srcl