docutils-ts
Version:
Port of the Python Docutils library to TypeScript
1,142 lines (1,139 loc) • 63.7 kB
JavaScript
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