UNPKG

antsibull-docs

Version:

TypeScript library for processing Ansible documentation markup

395 lines 14.1 kB
"use strict"; /* Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) SPDX-FileCopyrightText: Ansible Project SPDX-License-Identifier: BSD-2-Clause */ Object.defineProperty(exports, "__esModule", { value: true }); exports.processWhitespace = processWhitespace; exports.composeCommandMap = composeCommandMap; exports.composeCommandRE = composeCommandRE; exports.parseString = parseString; exports.parse = parse; const ansible_1 = require("./ansible"); const parser_impl_1 = require("./parser-impl"); const dom_1 = require("./dom"); function repr(text) { return JSON.stringify(text); } const IGNORE_MARKER = 'ignore:'; function parseOptionLike(text, opts, type, source) { let value; const eq = text.indexOf('='); if (eq >= 0) { value = text.substring(eq + 1, text.length); text = text.substring(0, eq); } const m = /^([^.]+\.[^.]+\.[^#]+)#([^:]+):(.*)$/.exec(text); let plugin; let entrypoint; if (m) { const pluginFqcn = m[1]; const pluginType = m[2]; if (!(0, ansible_1.isFQCN)(pluginFqcn)) { throw Error(`Plugin name ${repr(pluginFqcn)} is not a FQCN`); } if (!(0, ansible_1.isPluginType)(pluginType)) { throw Error(`Plugin type ${repr(pluginType)} is not valid`); } plugin = { fqcn: pluginFqcn, type: pluginType }; text = m[3]; } else if (text.startsWith(IGNORE_MARKER)) { plugin = undefined; text = text.substring(IGNORE_MARKER.length, text.length); } else { plugin = opts.currentPlugin; entrypoint = opts.roleEntrypoint; } if ((plugin === null || plugin === void 0 ? void 0 : plugin.type) === 'role') { const idx = text.indexOf(':'); if (idx >= 0) { entrypoint = text.substr(0, idx); text = text.substr(idx + 1); } if (entrypoint === undefined) { throw Error('Role reference is missing entrypoint'); } } if (/[:#]/.test(text)) { throw Error(`Invalid option/return value name ${repr(text)}`); } return { type: type, plugin: plugin, entrypoint: entrypoint, link: text.replace(/\[([^\]]*)\]/g, '').split('.'), name: text, value: value, source: source, }; } function addWhitespace(result, ws, whitespace, noNewlines) { if (whitespace === 'keep_single_newlines' && !noNewlines && ws.search(/[\n\r]/) >= 0) { result.push('\n'); } else { result.push(' '); } } function processWhitespace(text, whitespace, codeEnvironment, noNewlines) { if (whitespace === 'ignore') { return text; } const length = text.length; let index = 0; const result = []; const whitespaces = /([\s]+)/g; // The 'no-misleading-character-class' warning reported by eslint below can be safely // ignored since we have a list of distinct Unicode codepoints, and we don't rely on // them being part of the adjacent codepoints. /* eslint-disable-next-line no-misleading-character-class */ const spacesToKeep = /([\u00A0\u202F\u2007\u2060\u200B\u200C\u200D\uFEFF]+)/g; while (index < length) { whitespaces.lastIndex = index; const m = whitespaces.exec(text); if (!m) { result.push(text.slice(index)); break; } if (m.index > index) { result.push(text.slice(index, m.index)); } const ws = m[0]; const ws_length = ws.length; if (codeEnvironment) { result.push(ws.replace(/[\t\n\r]/g, ' ')); } else { let ws_index = 0; while (ws_index < ws_length) { spacesToKeep.lastIndex = ws_index; const wsm = spacesToKeep.exec(ws); if (!wsm) { addWhitespace(result, ws.slice(ws_index), whitespace, noNewlines); break; } if (wsm.index > ws_index) { addWhitespace(result, ws.slice(ws_index, wsm.index), whitespace, noNewlines); } result.push(wsm[0]); ws_index = wsm.index + wsm[0].length; } } index = m.index + ws_length; } return result.join(''); } const PARSER = [ // Classic Ansible docs markup: { command: 'I', parameters: 1, old_markup: true, process: (args, _, source, whitespace) => { const text = processWhitespace(args[0], whitespace, false, true); return { type: dom_1.PartType.ITALIC, text: text, source: source }; }, }, { command: 'B', parameters: 1, old_markup: true, process: (args, _, source, whitespace) => { const text = processWhitespace(args[0], whitespace, false, true); return { type: dom_1.PartType.BOLD, text: text, source: source }; }, }, { command: 'M', parameters: 1, old_markup: true, process: (args, _, source, whitespace) => { const fqcn = processWhitespace(args[0], whitespace, false, true); if (!(0, ansible_1.isFQCN)(fqcn)) { throw Error(`Module name ${repr(fqcn)} is not a FQCN`); } return { type: dom_1.PartType.MODULE, fqcn: fqcn, source: source }; }, }, { command: 'U', parameters: 1, old_markup: true, process: (args, _, source, whitespace) => { const url = processWhitespace(args[0], whitespace, false, true); return { type: dom_1.PartType.URL, url: url, source: source }; }, }, { command: 'L', parameters: 2, old_markup: true, process: (args, _, source, whitespace) => { const text = processWhitespace(args[0], whitespace, false, true); const url = processWhitespace(args[1], whitespace, false, true); return { type: dom_1.PartType.LINK, text: text, url: url, source: source }; }, }, { command: 'R', parameters: 2, old_markup: true, process: (args, _, source, whitespace) => { const text = processWhitespace(args[0], whitespace, false, true); const ref = processWhitespace(args[1], whitespace, false, true); return { type: dom_1.PartType.RST_REF, text: text, ref: ref, source: source }; }, }, { command: 'C', parameters: 1, old_markup: true, process: (args, _, source, whitespace) => { const text = processWhitespace(args[0], whitespace, true, true); return { type: dom_1.PartType.CODE, text: text, source: source }; }, }, { command: 'HORIZONTALLINE', parameters: 0, stripSurroundingWhitespace: true, old_markup: true, process: (_, __, source) => { return { type: dom_1.PartType.HORIZONTAL_LINE, source: source }; }, }, // Semantic Ansible docs markup: { command: 'P', parameters: 1, escapedArguments: true, process: (args, _, source, whitespace) => { const plugin = processWhitespace(args[0], whitespace, false, true); const m = /^([^#]*)#(.*)$/.exec(plugin); if (!m) { throw Error(`Parameter ${repr(args[0])} is not of the form FQCN#type`); } const fqcn = m[1]; if (!(0, ansible_1.isFQCN)(fqcn)) { throw Error(`Plugin name ${repr(fqcn)} is not a FQCN`); } const type = m[2]; if (!(0, ansible_1.isPluginType)(type)) { throw Error(`Plugin type ${repr(type)} is not valid`); } return { type: dom_1.PartType.PLUGIN, plugin: { fqcn: fqcn, type: type }, source: source }; }, }, { command: 'E', parameters: 1, escapedArguments: true, process: (args, _, source, whitespace) => { const env = processWhitespace(args[0], whitespace, true, true); return { type: dom_1.PartType.ENV_VARIABLE, name: env, source: source }; }, }, { command: 'V', parameters: 1, escapedArguments: true, process: (args, _, source, whitespace) => { const value = processWhitespace(args[0], whitespace, true, true); return { type: dom_1.PartType.OPTION_VALUE, value: value, source: source }; }, }, { command: 'O', parameters: 1, escapedArguments: true, process: (args, opts, source, whitespace) => { const value = processWhitespace(args[0], whitespace, true, true); return parseOptionLike(value, opts, dom_1.PartType.OPTION_NAME, source); }, }, { command: 'RV', parameters: 1, escapedArguments: true, process: (args, opts, source, whitespace) => { const value = processWhitespace(args[0], whitespace, true, true); return parseOptionLike(value, opts, dom_1.PartType.RETURN_VALUE, source); }, }, ]; function composeCommandMap(commands) { const result = new Map(); commands.forEach((cmd) => result.set(cmd.command, cmd)); return result; } function commandRE(command) { return '\\b' + command.command + (command.parameters === 0 ? '\\b' : '\\('); } function composeCommandRE(commands) { return new RegExp('(' + commands.map(commandRE).join('|') + ')', 'g'); } const PARSER_COMMANDS = composeCommandMap(PARSER); const COMMAND_RE = composeCommandRE(PARSER); const CLASSIC_COMMAND_RE = composeCommandRE(PARSER.filter((cmd) => cmd.old_markup)); function parseString(input, commandRE, commands, opts, where) { var _a; // TODO: handle opts.whitespace! const result = []; const whitespace = opts.whitespace || 'ignore'; const length = input.length; let index = 0; while (index < length) { commandRE.lastIndex = index; const match = commandRE.exec(input); if (!match) { if (index < length) { const text = input.slice(index); result.push({ type: dom_1.PartType.TEXT, text: processWhitespace(text, whitespace, false, false), source: opts.addSource ? text : undefined, }); } break; } const prevIndex = index; index = match.index; let cmd = match[0]; let endIndex = index + cmd.length; if (cmd.endsWith('(')) { cmd = cmd.slice(0, cmd.length - 1); } const command = commands.get(cmd); if (!command) { throw Error(`Internal error: unknown command ${repr(cmd)}`); } if (match.index > prevIndex) { let text = input.slice(prevIndex, match.index); if (command.stripSurroundingWhitespace) { let end = text.length; while (end > 0 && /[ \t]/.test(text[end - 1])) { end -= 1; } if (end < text.length) { text = text.slice(0, end); } } result.push({ type: dom_1.PartType.TEXT, text: processWhitespace(text, whitespace, false, false), source: opts.addSource ? text : undefined, }); } let args; let error; if (command.parameters === 0) { args = []; } else if (command.escapedArguments) { [args, endIndex, error] = (0, parser_impl_1.parseEscapedArgs)(input, endIndex, command.parameters); } else { [args, endIndex, error] = (0, parser_impl_1.parseUnescapedArgs)(input, endIndex, command.parameters); } const source = opts.addSource ? input.slice(index, endIndex) : undefined; if (error === undefined) { try { result.push(command.process(args, opts, source, whitespace)); /* eslint-disable-next-line @typescript-eslint/no-explicit-any */ } catch (exc) { error = `${exc}`; if ((exc === null || exc === void 0 ? void 0 : exc.message) !== undefined) { error = `${exc.message}`; } } } if (error !== undefined) { const errorSource = ((_a = opts.helpfulErrors) !== null && _a !== void 0 ? _a : true) ? repr(input.slice(index, endIndex)) : `${cmd}${command.parameters > 0 ? '()' : ''}`; error = `While parsing ${errorSource} at index ${match.index + 1}${where}: ${error}`; switch (opts.errors || 'message') { case 'ignore': break; case 'message': result.push({ type: dom_1.PartType.ERROR, message: error, source: source, }); break; case 'exception': throw Error(error); } } index = endIndex; if (command.stripSurroundingWhitespace) { while (index < length && /[ \t]/.test(input[index])) { index += 1; } } } return result; } /** Parses a string or a list of strings to a list of paragraphs. */ function parse(input, opts) { let hasParagraphs = true; if (!Array.isArray(input)) { input = input ? [input] : []; hasParagraphs = false; } const opts_ = opts || {}; const commandRE = opts_.onlyClassicMarkup ? CLASSIC_COMMAND_RE : COMMAND_RE; return input.map((par, index) => parseString('' + par, commandRE, PARSER_COMMANDS, opts_, hasParagraphs ? ` of paragraph ${index + 1}` : '')); } //# sourceMappingURL=parser.js.map