UNPKG

docutils-ts

Version:

Port of the Python Docutils library to TypeScript

1,038 lines (1,031 loc) 41.3 kB
/** * Command-line and common processing for Docutils front-end tools. Exports the following classes: * `OptionParser`: Standard Docutils command-line processing. * `Option`: Customized version of `optparse.Option`; validation support. * `Values`: Runtime settings; objects are simple structs (``object.attribute``). Supports cumulative list settings (attributes). * `ConfigParser`: Standard Docutils config file processing. Also exports the following functions: * Option callbacks: `store_multiple`, `read_config_file`. * Setting validators: `validate_encoding`, `validate_encoding_error_handler`, `validate_encoding_and_error_handler`, `validate_boolean`, `validate_ternary`, `validate_threshold`, `validate_colon_separated_string_list`, `validate_comma_separated_string_list`, `validate_dependency_file`. * `make_paths_absolute`. * SettingSpec manipulation: `filter_settings_spec`. */ import camelcase from 'camelcase'; import { ArgumentParser } from 'argparse'; // import path from 'path'; // TODO : Make sure not needed import { dateDatestampFormat, timeAndDateDatestampFormat } from './constants.js'; import { _getCallerFileAndLine } from './utils.js'; import { InvalidStateError } from './exceptions.js'; import { ActionCallback } from './callback.js'; import { ActionValidating } from './validating.js'; /* """Check/normalize boolean settings: True: '1', 'on', 'yes', 'true' False: '0', 'off', 'no','false', '' """ */ /**Lookup table for boolean configuration file settings.*/ const booleans = { '1': true, 'on': true, 'yes': true, 'true': true, '0': false, 'off': false, 'no': false, 'false': false, '': false }; export function validateBoolean(parser, namespace, values, optionString) { return false; } /* if isinstance(value, bool): return value try: return option_parser.booleans[value.strip().lower()] except KeyError: raise (LookupError('unknown boolean value: "%s"' % value), None, sys.exc_info()[2]) */ /** Store multiple values in `parser.values`. (Option callback.) Store `None` for each attribute named in `args`, and store the value for each key (attribute name) in `kwargs`. */ function storeMultiple(parser, namespace, values, optionString, args = [], kwargs = {}) { args.forEach((arg) => { namespace[arg] = undefined; }); Object.keys(kwargs).forEach((key) => { namespace[key] = kwargs[key]; }); } /** Read a configuration file during option processing. (Option callback.) */ function readConfigFile(parser, namespace, values, optionString, args = [], kwargs = {}) { let newSettings; try { newSettings = parser.getConfigFileSettings(values[0]); } catch (error) { parser.error(error); } if (newSettings !== undefined) { Object.keys(newSettings).forEach((key) => { namespace[key] = newSettings[key]; }); } } function validateEncoding() { /* try: codecs.lookup(value) except LookupError: raise (LookupError('setting "%s": unknown encoding: "%s"' % (setting, value)), None, sys.exc_info()[2]) return value def validate_encoding_error_handler(setting, value, option_parser, config_parser=None, config_section=None): try: codecs.lookup_error(value) except LookupError: raise (LookupError( 'unknown encoding error handler: "%s" (choices: ' '"strict", "ignore", "replace", "backslashreplace", ' '"xmlcharrefreplace", and possibly others; see documentation for ' 'the Python ``codecs`` module)' % value), None, sys.exc_info()[2]) return value */ } /** Side-effect: if an error handler is included in the value, it is inserted into the appropriate place as if it was a separate setting/option. */ function validateEncodingAndErrorHandler() { /* if ':' in value: encoding, handler = value.split(':') validate_encoding_error_handler( setting + '_error_handler', handler, option_parser, config_parser, config_section) if config_parser: config_parser.set(config_section, setting + '_error_handler', handler) else: setattr(option_parser.values, setting + '_error_handler', handler) else: encoding = value validate_encoding(setting, encoding, option_parser, config_parser, config_section) return encoding */ } /** Check/normalize three-value settings: True: '1', 'on', 'yes', 'true' False: '0', 'off', 'no','false', '' any other value: returned as-is. */ export function validateTernary(parser, namespace, values, optionString) { if (typeof values[0] === 'boolean' || values[0] === undefined) { return values[0]; } const v = values[0].trim().toLowerCase(); if (v in booleans) { return booleans[v]; } return values[0]; } function validateNonnegativeInt() { /* value = int(value) if value < 0: raise ValueError('negative value; must be positive or zero') return value */ } function validateThreshold() { } export class OptionParser extends ArgumentParser { constructor(args) { super({ usage: args.usage, description: args.description }); this.settingsSpec = [ [ "General Docutils Options", null, [ [ "Specify the document title as metadata.", [ "--title" ], {} ], [ "Include a \"Generated by Docutils\" credit and link.", [ "--generator", "-g" ], { "action": "store_true", "validator": "validate_boolean" } ], [ "Do not include a generator credit.", [ "--no-generator" ], { "action": "store_false", "dest": "generator" } ], [ "Include the date at the end of the document (UTC).", [ "--date", "-d" ], { "action": "store_const", "const": dateDatestampFormat, "dest": "datestamp" } ], [ "Include the time & date (UTC).", [ "--time", "-t" ], { "action": "store_const", "const": timeAndDateDatestampFormat, "dest": "datestamp" } ], [ "Do not include a datestamp of any kind.", [ "--no-datestamp" ], { "action": "store_const", "const": null, "dest": "datestamp" } ], [ "Include a \"View document source\" link.", [ "--source-link", "-s" ], { "action": "store_true", "validator": "validate_boolean" } ], [ "Use <URL> for a source link; implies --source-link.", [ "--source-url" ], { "metavar": "<URL>" } ], [ "Do not include a \"View document source\" link.", [ "--no-source-link" ], { "action": "callback", "callback": storeMultiple, "callbackArgs": [ "source_link", "source_url" ] } ], [ "Link from section headers to TOC entries. (default)", [ "--toc-entry-backlinks" ], { "dest": "toc_backlinks", "action": "store_const", "const": "entry", "default": "entry" } ], [ "Link from section headers to the top of the TOC.", [ "--toc-top-backlinks" ], { "dest": "toc_backlinks", "action": "store_const", "const": "top" } ], [ "Disable backlinks to the table of contents.", [ "--no-toc-backlinks" ], { "dest": "toc_backlinks", "action": "store_false" } ], [ "Link from footnotes/citations to references. (default)", [ "--footnote-backlinks" ], { "action": "store_true", "default": 1, "validator": "validate_boolean" } ], [ "Disable backlinks from footnotes and citations.", [ "--no-footnote-backlinks" ], { "dest": "footnote_backlinks", "action": "store_false" } ], [ "Enable section numbering by Docutils. (default)", [ "--section-numbering" ], { "action": "store_true", "dest": "sectnum_xform", "default": 1, "validator": "validate_boolean" } ], [ "Disable section numbering by Docutils.", [ "--no-section-numbering" ], { "action": "store_false", "dest": "sectnum_xform" } ], [ "Remove comment elements from the document tree.", [ "--strip-comments" ], { "action": "store_true", "validator": "validate_boolean" } ], [ "Leave comment elements in the document tree. (default)", [ "--leave-comments" ], { "action": "store_false", "dest": "strip_comments" } ], [ "Remove all elements with classes=\"<class>\" from the document tree. Warning: potentially dangerous; use with caution. (Multiple-use option.)", [ "--strip-elements-with-class" ], { "action": "append", "dest": "strip_elements_with_classes", "metavar": "<class>", "validator": "validate_strip_class" } ], [ "Remove all classes=\"<class>\" attributes from elements in the document tree. Warning: potentially dangerous; use with caution. (Multiple-use option.)", [ "--strip-class" ], { "action": "append", "dest": "strip_classes", "metavar": "<class>", "validator": "validate_strip_class" } ], [ "Report system messages at or higher than <level>: \"info\" or \"1\", \"warning\"/\"2\" (default), \"error\"/\"3\", \"severe\"/\"4\", \"none\"/\"5\"", [ "--report", "-r" ], { "choices": [ "info", "1", "warning", "2", "error", "3", "severe", "4", "none", "5" ], "default": 2, "dest": "report_level", "metavar": "<level>", "validator": "validate_threshold" } ], [ "Report all system messages. (Same as \"--report=1\".)", [ "--verbose", "-v" ], { "action": "store_const", "const": 1, "dest": "report_level" } ], [ "Report no system messages. (Same as \"--report=5\".)", [ "--quiet", "-q" ], { "action": "store_const", "const": 5, "dest": "report_level" } ], [ "Halt execution at system messages at or above <level>. Levels as in --report. Default: 4 (severe).", [ "--halt" ], { "choices": [ "info", "1", "warning", "2", "error", "3", "severe", "4", "none", "5" ], "dest": "halt_level", "default": 4, "metavar": "<level>", "validator": "validate_threshold" } ], [ "Halt at the slightest problem. Same as \"--halt=info\".", [ "--strict" ], { "action": "store_const", "const": 1, "dest": "halt_level" } ], [ "Enable a non-zero exit status for non-halting system messages at or above <level>. Default: 5 (disabled).", [ "--exit-status" ], { "choices": [ "info", "1", "warning", "2", "error", "3", "severe", "4", "none", "5" ], "dest": "exit_status_level", "default": 5, "metavar": "<level>", "validator": "validate_threshold" } ], [ "Enable debug-level system messages and diagnostics.", [ "--debug" ], { "action": "store_true", "validator": "validate_boolean" } ], [ "Disable debug output. (default)", [ "--no-debug" ], { "action": "store_false", "dest": "debug" } ], [ "Send the output of system messages to <file>.", [ "--warnings" ], { "dest": "warning_stream", "metavar": "<file>" } ], [ "Enable Javascript tracebacks when Docutils is halted.", [ "--traceback" ], { "action": "store_true", "default": null, "validator": "validate_boolean" } ], [ "Disable Python tracebacks. (default)", [ "--no-traceback" ], { "dest": "traceback", "action": "store_false" } ], [ "Specify the encoding and optionally the error handler of input text. Default: <locale-dependent>:strict.", [ "--input-encoding", "-i" ], { "metavar": "<name[:handler]>", "validator": "validate_encoding_and_error_handler" } ], [ "Specify the error handler for undecodable characters. Choices: \"strict\" (default), \"ignore\", and \"replace\".", [ "--input-encoding-error-handler" ], { "default": "strict", "validator": "validate_encoding_error_handler" } ], [ "Specify the text encoding and optionally the error handler for output. Default: UTF-8:strict.", [ "--output-encoding", "-o" ], { "metavar": "<name[:handler]>", "default": "utf-8", "validator": "validate_encoding_and_error_handler" } ], [ "Specify error handler for unencodable output characters; \"strict\" (default), \"ignore\", \"replace\", \"xmlcharrefreplace\", \"backslashreplace\".", [ "--output-encoding-error-handler" ], { "default": "strict", "validator": "validate_encoding_error_handler" } ], [ "Specify text encoding and error handler for error output. Default: UTF-8:backslashreplace.", [ "--error-encoding", "-e" ], { "metavar": "<name[:handler]>", "default": "UTF-8", "validator": "validate_encoding_and_error_handler" } ], [ "Specify the error handler for unencodable characters in error output. Default: backslashreplace.", [ "--error-encoding-error-handler" ], { "default": "backslashreplace", "validator": "validate_encoding_error_handler" } ], [ "Specify the language (as BCP 47 language tag). Default: en.", [ "--language", "-l" ], { "dest": "language_code", "default": "en", "metavar": "<name>" } ], [ "Write output file dependencies to <file>.", [ "--record-dependencies" ], { "metavar": "<file>", "validator": "validate_dependency_file", "default": null } ], /* [ "Read configuration settings from <file>, if it exists.", [ "--config" ], { "metavar": "<file>", "type": "string", "action": "callback", "callback": readConfigFile } ], */ [ "Show this program's version number and exit.", [ "--version", "-V" ], { "action": "version" } ], [ "Show this help message and exit.", [ "--help", "-h" ], { "action": "help" } ], [ "SUPPRESSHELP", [ "--id-prefix" ], { "default": "" } ], [ "SUPPRESSHELP", [ "--auto-id-prefix" ], { "default": "id" } ], [ "SUPPRESSHELP", [ "--dump-settings" ], { "action": "store_true" } ], [ "SUPPRESSHELP", [ "--dump-internals" ], { "action": "store_true" } ], [ "SUPPRESSHELP", [ "--dump-transforms" ], { "action": "store_true" } ], [ "SUPPRESSHELP", [ "--dump-pseudo-xml" ], { "action": "store_true" } ], [ "SUPPRESSHELP", [ "--expose-internal-attribute" ], { "action": "append", "dest": "expose_internals", "validator": "validate_colon_separated_string_list" } ], [ "SUPPRESSHELP", [ "--strict-visitor" ], { "action": "store_true" } ] ] ] ]; this.configSectionDependencies = []; this.configFiles = []; this.defaults = {}; /** Docutils configuration files, using ConfigParser syntax. Filenames * will be tilde-expanded later. Later files override earlier ones. */ this.standardConfigFiles = [ '/etc/docutils.conf', // system-wide './docutils.conf', // project-specific '~/.docutils' ]; // user-specific /** Possible inputs for for --report and --halt threshold values. */ this.thresholdChoices = ['info', '1', 'warning', '2', 'error', '3', 'severe', '4', 'none', '5']; /**Lookup table for --report and --halt threshold values.*/ this.thresholds = { 'info': 1, 'warning': 2, 'error': 3, 'severe': 4, 'none': 5, }; this.defaultErrorEncoding = ''; /*getattr(sys.stderr, 'encoding', None) or locale_encoding or 'ascii'*/ this.defaultErrorEncodingErrorHandler = 'backslashreplace'; /** Defaults for settings that don't have command-line option equivalents. */ this.settingsDefaults = { '_disableConfig': undefined, '_source': undefined, '_destination': undefined, '_configFiles': undefined }; this.relativePathSettings = ['warning_stream']; this.configSection = 'general'; this.components = []; /** Default version message. */ this.versionTemplate = ''; this.logger = args.logger; // TODO ? What are those 'register' calls? ('register' exist on ArgumentParser!) //@ts-ignore this.register('action', 'callback', ActionCallback); //@ts-ignore this.register('action', 'validating', ActionValidating); /** Set of list-type settings. */ this.lists = {}; /** List of paths of applied configuration files. */ this.configFiles = []; // TODO : unused argParser const argParser = new ArgumentParser({ description: args.description }); if (!this.version) { // is this correct? this.version = this.versionTemplate; } // Make an instance copy (it will be modified): ?? this.relativePathSettings = [...this.relativePathSettings]; this.logger.silly('constructing component list'); this.logger.silly(`adding 'this' to component list`); this.components = [this]; if (args && args.components) { this.logger.silly(`tacking on supplied components: ${args.components.map(c => c !== undefined ? c.toString() : 'undefined').join(', ')}`); if (args.components.indexOf(undefined) !== -1) { throw new InvalidStateError('received undefined component'); } this.components.push(...args.components); } this.populateFromComponents(this.components); this.setDefaultsFromDict(args.defaults || {}); if (args.readConfigFiles && !this.defaults._disableConfig) { let configSettings; try { configSettings = this.getStandardConfigSettings(); } catch (error) { throw error; //except ValueError, error: self.error(SafeString(error)) } this.setDefaultsFromDict(configSettings); } } /** * For each component, first populate from the `SettingsSpec.settings_spec` * structure, then from the `SettingsSpec.settingsDefaults` dictionary. * After all components have been processed, check for and populate from * each component's `SettingsSpec.settingsDefaultOverrides` dictionary. */ populateFromComponents(components) { this.logger.silly('Frontend.populateFromComponents', { callerInfo: _getCallerFileAndLine() }); components.forEach((component) => { if (!component || !component.settingsSpec) { this.logger.silly('component undefined'); return; } this.logger.silly(`component is ${component.toString()} ${component.constructor.name}`); const settingsSpec = component.settingsSpec; this.relativePathSettings.push(...component.relativePathSettings); settingsSpec.forEach((spec) => { const [title, description, optionSpec] = spec; optionSpec.forEach(([helpText, optionStrings, optionArgs]) => { if (optionStrings.indexOf('--help') !== -1) { } else { const help = helpText.replace(/%/g, '%%'); let action; let newArgs = { help }; if (Object.prototype.hasOwnProperty.call(optionArgs, 'dest')) { let dest = camelcase(optionArgs.dest, { pascalCase: false }); newArgs.dest = dest; } else { newArgs.dest = camelcase(optionStrings[0], { pascalCase: false }); } if (Object.prototype.hasOwnProperty.call(optionArgs, 'default')) { newArgs.defaultValue = optionArgs['default']; } let a = ''; if (Object.prototype.hasOwnProperty.call(optionArgs, 'action')) { a = optionArgs.action; } if (a === 'store_const') { action = 'storeConst'; newArgs.constant = optionArgs.const; } else if (a === 'store_true') { action = 'storeTrue'; // newArgs.nargs = 0; } else if (a === 'append') { action = 'append'; } else if (a === 'store_false') { action = 'storeFalse'; // newArgs.nargs = 0; } else if (a === 'append') { action = a; } else if (a === 'version') { action = a; } else if (a === 'callback') { action = 'callback'; newArgs.callback = optionArgs.callback; newArgs.callbackArgs = optionArgs.callbackArgs; newArgs.nargs = 0; } else if (a === 'version') { action = 'version'; } else if (a === 'validating') { action = 'validating'; newArgs.nargs = optionArgs.nargs, newArgs.validator = optionArgs.validator; newArgs.delegatedAction = optionArgs.delegatedAction; } else { action = 'store'; } newArgs.action = action; this.addArgument(optionStrings, newArgs); // addArgument does not implement the following from 'add_option ' in optparse.py, so implement it here /* if option.dest is not None: # option has a dest, we need a default if option.default is not NO_DEFAULT: self.defaults[option.dest] = option.default elif option.dest not in self.defaults: self.defaults[option.dest] = None */ if (newArgs.dest) { if (newArgs.defaultValue != null) { this.logger.silly(`setting default for ${newArgs.dest} is ${newArgs.defaultValue}`); this.defaults[newArgs.dest] = newArgs.defaultValue; } else if (newArgs.dest in this.defaults) { this.logger.silly(`setting default for ${newArgs.dest} is ${this.defaults[newArgs.dest]}`); } else { this.logger.silly(`setting default for ${newArgs.dest} is undefined`); this.defaults[newArgs.dest] = undefined; } } } }); }); if (component.settingsDefaults) { this.logger.silly('updating defaults from component.settingsDefaults'); Object.keys(component.settingsDefaults).forEach((key) => { this.logger.silly(`setting default for ${key} is ${component.settingsDefaults[key]}`); this.defaults[key] = component.settingsDefaults[key]; }); } }); } /* for component in components: if component and component.settings_default_overrides: self.defaults.update(component.settings_default_overrides)*/ /** Return list of config files, from environment or standard. */ getStandardConfigFiles() { const configFiles = this.standardConfigFiles; return configFiles; } getStandardConfigSettings() { /* settings = Values() for filename in self.get_standard_config_files(): settings.update(self.get_config_file_settings(filename), self) return settings */ return {}; } /** Returns a dictionary containing appropriate config file settings. */ getConfigFileSettings(configFile) { return {}; /* parser = ConfigParser() parser.read(config_file, self) self.configFiles.extend(parser._files) base_path = os.path.dirname(config_file) applied = {} settings = Values() for component in self.components: if not component: continue for section in (tuple(component.config_section_dependencies or ()) + (component.config_section,)): if section in applied: continue applied[section] = 1 settings.update(parser.get_section(section), self) make_paths_absolute( settings.__dict__, self.relative_path_settings, base_path) return settings.__dict__ */ } /** Store positional arguments as runtime settings. */ checkValues(values, args) { this.logger.silly('checkValues'); [values._source, values._destination] = this.checkArgs(args); this.logger.silly('source and dest', { source: values._source, destination: values._destination }); //makePathsAbsolute(values, this.relativePathSettings); values._configFiles = this.configFiles; return values; } checkArgs(args) { let source; let destination; if (args.length) { source = args.shift(); if (source === '-') { // means stdin source = undefined; } } if (args.length) { destination = args.shift(); if (destination === '-') { // means stdout destination = undefined; } } if (args.length) { this.error('Maximum 2 arguments allowed.'); } if (source && source === destination) { this.error('Do not specify the same file for both source and ' + 'destination. It will clobber the source file.'); } return [source, destination]; } setDefaultsFromDict(defaults) { Object.keys(defaults).forEach((key) => { this.defaults[key] = defaults[key]; }); } getDefaultValues() { const defaults = this.defaults; defaults._configFiles = this.configFiles; return defaults; } /** * Get an option by its dest. * * If you're supplying a dest which is shared by several options, * it is undefined which option of those is returned. * * A KeyError is raised if there is no option with the supplied * dest. **/ getOptionByDest(dest) { throw new Error('unimplemented'); /* for group in self.optionGroups + [self]: for option in group.option_list: if option.dest == dest: return option raise KeyError('No option with dest == %r.' % dest) */ } toString() { return '<Frontend.OptionParser>'; } }