docutils-ts
Version:
Port of the Python Docutils library to TypeScript
164 lines (163 loc) • 6.41 kB
JavaScript
import { InvalidArgumentsError } from "../exceptions.js";
import UnknownTransitionError from "../error/unknownTransitionError.js";
import DuplicateTransitionError from "../error/duplicateTransitionError.js";
import NestedStateMachine from "../parsers/rst/nestedStateMachine.js";
/**
* State superclass. Contains a list of transitions, and transition methods.
*
* Transition methods all have the same signature. They take 3 parameters:
*
* - An `re` match object. ``match.string`` contains the matched input line,
* ``match.start()`` gives the start index of the match, and
* ``match.end()`` gives the end index.
* - A context object, whose meaning is application-defined (initial value
* ``None``). It can be used to store any information required by the state
* machine, and the retured context is passed on to the next transition
* method unchanged.
* - The name of the next state, a string, taken from the transitions list;
* normally it is returned unchanged, but it may be altered by the
* transition method if necessary.
*
* Transition methods all return a 3-tuple:
*
* - A context object, as (potentially) modified by the transition method.
* - The next state name (a return value of ``None`` means no state change).
* - The processing result, a list, which is accumulated by the state
* machine.
*
* Transition methods may raise an `EOFError` to cut processing short.
*
* There are two implicit transitions, and corresponding transition methods
* are defined: `bof()` handles the beginning-of-file, and `eof()` handles
* the end-of-file. These methods have non-standard signatures and return
* values. `bof()` returns the initial context and results, and may be used
* to return a header string, or do any other processing needed. `eof()`
* should handle any remaining context and wrap things up; it returns the
* final processing result.
*
* Typical applications need only subclass `State` (or a subclass), set the
* `patterns` and `initial_transitions` class attributes, and provide
* corresponding transition methods. The default object initialization will
* take care of constructing the list of transitions.
*
*/
class State {
constructor(stateMachine, debug = false) {
/**
* {Name: pattern} mapping, used by `make_transition()`. Each pattern may
* be a string or a compiled `re` pattern. Override in subclasses.
*/
this.patterns = {};
//protected knownIndentSmKwargs: any;
//protected indentSmKwargs: any;
this.transitionOrder = [];
this.transitions = {};
this.stateName = '';
this.stateMachine = stateMachine;
this.logger = stateMachine.logger;
this.debug = debug;
if (this.createNestedStateMachine === undefined) {
this.createNestedStateMachine = () => NestedStateMachine.createStateMachine(this.stateMachine, undefined, this.stateMachine.stateFactory.withStateClasses(["QuotedLiteralBlock"]));
}
}
runtimeInit() {
/* empty */
}
unlink() {
this.stateMachine = undefined;
}
addInitialTransitions() {
//this.logger.silly('addInitialTransitions');
if (this.initialTransitions) {
//this.logger.silly('got initial transitions', { value: this.initialTransitions});
const [names, transitions] = this.makeTransitions(this.initialTransitions);
this.addTransitions(names, transitions);
}
}
addTransitions(names, transitions) {
//this.logger.silly('addTransitions', { value: {names,transitions}});
names.forEach(((name) => {
if (name in this.transitions) {
throw new DuplicateTransitionError(name);
}
if (!(name in transitions)) {
throw new UnknownTransitionError(name);
}
}));
this.transitionOrder.splice(0, 0, ...names);
Object.keys(transitions).forEach((key) => {
//this.logger.silly('addTransition', { value:key});
this.transitions[key] = transitions[key];
});
//this.logger.silly('done addTransitions');
}
addTransition(name, transition) {
this.transitionOrder.splice(0, 0, name);
this.transitions[name] = transition;
}
removeTransition(name) {
delete this.transitions[name];
this.transitionOrder.splice(this.transitionOrder.indexOf(name), 1);
}
makeTransition(name, nextState) {
if (name == null) {
throw new InvalidArgumentsError('need transition name');
}
if (nextState === undefined) {
nextState = this.constructor.name;
}
// @ts-ignore
let pattern = this.patterns[name];
if (!(pattern instanceof RegExp)) {
try {
pattern = new RegExp(`^${pattern}`);
}
catch (error) {
throw error;
}
}
// @ts-ignore
if (typeof (this[name]) !== 'function') {
throw new Error(`cant find method ${name} on ${this.constructor.name}`);
}
// @ts-ignore
const method = this[name];
return [pattern, method, nextState];
}
makeTransitions(nameList) {
const names = [];
const transitions = {};
if (!Array.isArray(nameList)) {
// console.log('warning, not an array');
throw new Error(`not array ${nameList}`);
}
/* check what happens with throw inside here */
nameList.forEach((namestate) => {
if (namestate == null) {
throw new InvalidArgumentsError('nameList contains null');
}
if (!Array.isArray(namestate)) {
transitions[namestate.toString()] = this.makeTransition(namestate);
names.push(namestate);
}
else {
transitions[namestate[0]] = this.makeTransition(namestate[0], namestate[1]);
names.push(namestate[0]);
}
});
return [names, transitions];
}
noMatch(context, transitions) {
return [context, undefined, []];
}
bof(context) {
return [context, []];
}
eof(context) {
return [];
}
nop(match, context, nextState) {
return [context, nextState, []];
}
}
export default State;