From 5842088e1ca69461f44fbd5b6bd4481ed5241ce4 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sat, 29 Jun 2024 23:05:17 +0200 Subject: [PATCH] Fix handling of empty markup parameters for RST. (#262) --- changelogs/fragments/262-rst.yml | 2 ++ src/rst.spec.ts | 5 ++- src/rst.ts | 52 +++++++++++++++++++------------- test-vectors.yaml | 21 +++++++++++++ 4 files changed, 58 insertions(+), 22 deletions(-) create mode 100644 changelogs/fragments/262-rst.yml diff --git a/changelogs/fragments/262-rst.yml b/changelogs/fragments/262-rst.yml new file mode 100644 index 0000000..20e4c6d --- /dev/null +++ b/changelogs/fragments/262-rst.yml @@ -0,0 +1,2 @@ +bugfixes: + - "Fix handling of empty markup parameters for RST (https://github.com/ansible-community/antsibull-docs-ts/pull/262)." diff --git a/src/rst.spec.ts b/src/rst.spec.ts index 75b670c..71ace4e 100644 --- a/src/rst.spec.ts +++ b/src/rst.spec.ts @@ -21,7 +21,10 @@ describe('quoteRST tests', (): void => { expect(quoteRST(' foo ', false, true)).toBe(' foo \\ '); }); it('simple string, escape spacing', (): void => { - expect(quoteRST(' foo ', true, true)).toBe('\\ foo \\ '); + expect(quoteRST(' foo ', true, true, true)).toBe('\\ foo \\ '); + }); + it('simple string, escape empty', (): void => { + expect(quoteRST('', true, true, true)).toBe('\\ '); }); it('more complex', (): void => { expect(quoteRST('\\<_>`*<_>*`\\')).toBe('\\\\\\<\\_\\>\\`\\*\\<\\_\\>\\*\\`\\\\'); diff --git a/src/rst.ts b/src/rst.ts index 33db907..7fd9fb7 100644 --- a/src/rst.ts +++ b/src/rst.ts @@ -8,7 +8,12 @@ import { RSTOptions, AllFormatOptions, mergeOpts } from './opts'; import { OptionNamePart, Paragraph, ReturnValuePart } from './dom'; import { addToDestination } from './format'; -export function quoteRST(text: string, escape_starting_whitespace = false, escape_ending_whitespace = false): string { +export function quoteRST( + text: string, + escape_starting_whitespace = false, + escape_ending_whitespace = false, + must_not_be_empty = false, +): string { text = text.replace(/([\\<>_*`])/g, '\\$1'); if (escape_ending_whitespace && text.endsWith(' ')) { @@ -17,6 +22,9 @@ export function quoteRST(text: string, escape_starting_whitespace = false, escap if (escape_starting_whitespace && text.startsWith(' ')) { text = '\\ ' + text; } + if (must_not_be_empty && text === '') { + text = '\\ '; + } return text; } @@ -37,7 +45,7 @@ function formatAntsibullOptionLike(part: OptionNamePart | ReturnValuePart, role: result.push('='); result.push(part.value); } - return `\\ :${role}:\`${quoteRST(result.join(''), true, true)}\`\\ `; + return `\\ :${role}:\`${quoteRST(result.join(''), true, true, true)}\`\\ `; } function formatPlainOptionLike(part: OptionNamePart | ReturnValuePart): string { @@ -63,43 +71,45 @@ function formatPlainOptionLike(part: OptionNamePart | ReturnValuePart): string { value = `${value}=${part.value}`; } const pluginText = plugin.length ? ` (of ${plugin.join('')})` : ''; - const mainText = `:literal:\`${quoteRST(value, true, true)}\``; + const mainText = `:literal:\`${quoteRST(value, true, true, true)}\``; return `\\ ${mainText}${pluginText}\\ `; } const DEFAULT_FORMATTER: AllFormatOptions = { - formatError: (part) => `\\ :strong:\`ERROR while parsing\`\\ : ${quoteRST(part.message, true, true)}\\ `, - formatBold: (part) => `\\ :strong:\`${quoteRST(part.text, true, true)}\`\\ `, - formatCode: (part) => `\\ :literal:\`${quoteRST(part.text, true, true)}\`\\ `, + formatError: (part) => `\\ :strong:\`ERROR while parsing\`\\ : ${quoteRST(part.message, true, true, true)}\\ `, + formatBold: (part) => `\\ :strong:\`${quoteRST(part.text, true, true, true)}\`\\ `, + formatCode: (part) => `\\ :literal:\`${quoteRST(part.text, true, true, true)}\`\\ `, formatHorizontalLine: () => '\n\n.. raw:: html\n\n
\n\n', - formatItalic: (part) => `\\ :emphasis:\`${quoteRST(part.text, true, true)}\`\\ `, - formatLink: (part) => `\\ \`${quoteRST(part.text)} <${encodeURI(part.url)}>\`__\\ `, - formatModule: (part) => `\\ :ref:\`${quoteRST(part.fqcn)} \`\\ `, - formatRSTRef: (part) => `\\ :ref:\`${quoteRST(part.text)} <${part.ref}>\`\\ `, + formatItalic: (part) => `\\ :emphasis:\`${quoteRST(part.text, true, true, true)}\`\\ `, + formatLink: (part) => (part.text === '' ? '' : `\\ \`${quoteRST(part.text)} <${encodeURI(part.url)}>\`__\\ `), + formatModule: (part) => + `\\ :ref:\`${quoteRST(part.fqcn, true, true, true)} \`\\ `, + formatRSTRef: (part) => `\\ :ref:\`${quoteRST(part.text, true, true, true)} <${part.ref}>\`\\ `, formatURL: (part) => `\\ ${encodeURI(part.url)}\\ `, formatText: (part) => quoteRST(part.text), - formatEnvVariable: (part) => `\\ :envvar:\`${quoteRST(part.name, true, true)}\`\\ `, + formatEnvVariable: (part) => `\\ :envvar:\`${quoteRST(part.name, true, true, true)}\`\\ `, formatOptionName: (part) => formatAntsibullOptionLike(part, 'ansopt'), - formatOptionValue: (part) => `\\ :ansval:\`${quoteRST(part.value, true, true)}\`\\ `, + formatOptionValue: (part) => `\\ :ansval:\`${quoteRST(part.value, true, true, true)}\`\\ `, formatPlugin: (part) => `\\ :ref:\`${quoteRST(part.plugin.fqcn)} \`\\ `, formatReturnValue: (part) => formatAntsibullOptionLike(part, 'ansretval'), }; const PLAIN_FORMATTER: AllFormatOptions = { - formatError: (part) => `\\ :strong:\`ERROR while parsing\`\\ : ${quoteRST(part.message, true, true)}\\ `, - formatBold: (part) => `\\ :strong:\`${quoteRST(part.text, true, true)}\`\\ `, - formatCode: (part) => `\\ :literal:\`${quoteRST(part.text, true, true)}\`\\ `, + formatError: (part) => `\\ :strong:\`ERROR while parsing\`\\ : ${quoteRST(part.message, true, true, true)}\\ `, + formatBold: (part) => `\\ :strong:\`${quoteRST(part.text, true, true, true)}\`\\ `, + formatCode: (part) => `\\ :literal:\`${quoteRST(part.text, true, true, true)}\`\\ `, formatHorizontalLine: () => '\n\n------------\n\n', - formatItalic: (part) => `\\ :emphasis:\`${quoteRST(part.text, true, true)}\`\\ `, - formatLink: (part) => `\\ \`${quoteRST(part.text)} <${encodeURI(part.url)}>\`__\\ `, - formatModule: (part) => `\\ :ref:\`${quoteRST(part.fqcn)} \`\\ `, - formatRSTRef: (part) => `\\ :ref:\`${quoteRST(part.text)} <${part.ref}>\`\\ `, + formatItalic: (part) => `\\ :emphasis:\`${quoteRST(part.text, true, true, true)}\`\\ `, + formatLink: (part) => (part.text === '' ? '' : `\\ \`${quoteRST(part.text)} <${encodeURI(part.url)}>\`__\\ `), + formatModule: (part) => + `\\ :ref:\`${quoteRST(part.fqcn, true, true, true)} \`\\ `, + formatRSTRef: (part) => `\\ :ref:\`${quoteRST(part.text, true, true, true)} <${part.ref}>\`\\ `, formatURL: (part) => `\\ ${encodeURI(part.url)}\\ `, formatText: (part) => quoteRST(part.text), - formatEnvVariable: (part) => `\\ :envvar:\`${quoteRST(part.name, true, true)}\`\\ `, + formatEnvVariable: (part) => `\\ :envvar:\`${quoteRST(part.name, true, true, true)}\`\\ `, formatOptionName: (part) => formatPlainOptionLike(part), - formatOptionValue: (part) => `\\ :literal:\`${quoteRST(part.value, true, true)}\`\\ `, + formatOptionValue: (part) => `\\ :literal:\`${quoteRST(part.value, true, true, true)}\`\\ `, formatPlugin: (part) => `\\ :ref:\`${quoteRST(part.plugin.fqcn)} \`\\ `, formatReturnValue: (part) => formatPlainOptionLike(part), diff --git a/test-vectors.yaml b/test-vectors.yaml index 16f1ebd..0805285 100644 --- a/test-vectors.yaml +++ b/test-vectors.yaml @@ -34,6 +34,27 @@ test_vectors: ansible_doc_text: This is a `test' `module' *markup*. rst_plain: This is a \ :literal:`test`\ \ :emphasis:`module`\ \ :strong:`markup`\ . + empty_tags: + source: |- + C() I() B() C() U() L(,) R(,) V() O() RV() E() + html:

+ + +

+ html_plain:

+

+ md: []() []() + + rst: '\ :literal:`\ `\ \ :emphasis:`\ `\ \ :strong:`\ `\ \ :literal:`\ `\ \ + \ \ :ref:`\ <>`\ \ :ansval:`\ `\ \ :ansopt:`\ `\ \ :ansretval:`\ `\ \ + :envvar:`\ `\ ' + rst_plain: '\ :literal:`\ `\ \ :emphasis:`\ `\ \ :strong:`\ `\ \ :literal:`\ + `\ \ \ \ :ref:`\ <>`\ \ :literal:`\ `\ \ :literal:`\ `\ \ :literal:`\ + `\ \ :envvar:`\ `\ ' + ansible_doc_text: "`' `' ** `' <> `' `' `' `'" module: source: |- The M(a.b.c) module.