@redpanda-data/docs-extensions-and-macros 5.2.3 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "5.2.3",
3
+ "version": "5.2.4",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -1,13 +1,15 @@
1
1
  'use strict';
2
2
 
3
3
  const { extractMetadata } = require('../metadata-utils.js');
4
+ const { normalizeMetadataBlock } = require('../normalize-metadata.js');
4
5
 
5
6
  /**
6
7
  * Handlebars helper: return the `== Metadata` block extracted from a connector
7
- * description, or '' when there is none. Used by the metadata partial template.
8
+ * description with consistent field-list formatting, or '' when there is none.
9
+ * Used by the metadata partial template.
8
10
  * @param {string} description
9
11
  * @returns {string}
10
12
  */
11
13
  module.exports = function renderConnectMetadata (description) {
12
- return extractMetadata(description);
14
+ return normalizeMetadataBlock(extractMetadata(description));
13
15
  };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Normalize the formatting of an extracted `== Metadata` block so the generated
5
+ * metadata partials are consistent regardless of how each connector authored
6
+ * its metadata section upstream.
7
+ *
8
+ * Two inconsistencies are normalized:
9
+ * 1. Some connectors wrap their field list in a fenced code block
10
+ * (```text ... ``` or ``` ... ```) of bare names; others use an AsciiDoc
11
+ * bullet list with the field name in inline code. We strip the fences
12
+ * around a field list and render the fields as a plain bullet list.
13
+ * 2. Within a field bullet, the field name (a lowercase snake_case
14
+ * identifier) is wrapped in inline code. Descriptive bullets such as
15
+ * "All headers (only first values are taken)" are left as prose, and
16
+ * bullets that already contain inline code are left untouched.
17
+ *
18
+ * Everything else (intro sentences, notes, `===` subheadings that legitimately
19
+ * group metadata by operation, non-field-list fenced blocks, and AsciiDoc
20
+ * `----` literal blocks) is preserved verbatim.
21
+ */
22
+
23
+ const FENCE_OPEN = /^(\s*)(```|~~~)(.*)$/;
24
+ const BULLET = /^(\s*[-*]\s+)(.*)$/;
25
+ // AsciiDoc literal/listing block delimiter. Content inside `----` blocks is
26
+ // rendered verbatim, so backticks would show up literally — such regions must
27
+ // pass through untouched (no field-name inline-coding).
28
+ const BLOCK_DELIMITER = /^-{4,}$/;
29
+ // A metadata field bullet: the leading token is a lowercase snake_case
30
+ // identifier (gcs_key, http_server_verb, header), optionally followed by a
31
+ // parenthetical annotation (for example "(RFC3339)"), and optionally a
32
+ // description separated by ":" or " - ". This distinguishes real field bullets
33
+ // from descriptive ones like "All headers ..." (which start with a capital).
34
+ // The description may itself contain inline code, so it is matched loosely.
35
+ const FIELD_BULLET = /^([a-z][a-z0-9_]*)((?:\s*\([^)]*\))?(?:\s*(?::|-)\s.*)?)$/s;
36
+
37
+ function normalizeBullet (prefix, content) {
38
+ // Skip only when the field name itself is already inline-coded; a backtick
39
+ // later in the description must not prevent coding the field name.
40
+ if (content.startsWith('`')) return prefix + content;
41
+ const m = content.match(FIELD_BULLET);
42
+ if (!m) return prefix + content; // descriptive, leave as prose
43
+ return `${prefix}\`${m[1]}\`${m[2]}`; // inline-code the field name
44
+ }
45
+
46
+ /** True when a fenced block's info string and content are a bare field list. */
47
+ function isFieldListFence (infoString, contentLines) {
48
+ const info = infoString.trim();
49
+ if (info !== '' && info !== 'text') return false;
50
+ const nonBlank = contentLines.filter((l) => l.trim() !== '');
51
+ if (nonBlank.length === 0 || !nonBlank.every((l) => BULLET.test(l))) return false;
52
+ // Require at least one real field-name bullet. A fenced block of purely
53
+ // descriptive bullets (no snake_case field name) is not a field list, so its
54
+ // fences must be preserved rather than stripped.
55
+ return nonBlank.some((l) => {
56
+ const b = l.match(BULLET);
57
+ return b && FIELD_BULLET.test(b[2]);
58
+ });
59
+ }
60
+
61
+ /**
62
+ * @param {string} block the extracted `== Metadata` block
63
+ * @returns {string} the block with consistent field-list formatting
64
+ */
65
+ function normalizeMetadataBlock (block) {
66
+ if (!block || typeof block !== 'string') return block;
67
+ const lines = block.split('\n');
68
+ const out = [];
69
+
70
+ for (let i = 0; i < lines.length; i++) {
71
+ // AsciiDoc literal/listing block: pass the whole `----` … `----` region
72
+ // through verbatim. Field-name lines inside are rendered literally, so they
73
+ // must not be inline-coded (backticks would appear as visible characters).
74
+ if (BLOCK_DELIMITER.test(lines[i].trim())) {
75
+ out.push(lines[i]);
76
+ let j = i + 1;
77
+ for (; j < lines.length; j++) {
78
+ out.push(lines[j]);
79
+ if (BLOCK_DELIMITER.test(lines[j].trim())) break;
80
+ }
81
+ i = j;
82
+ continue;
83
+ }
84
+
85
+ const fence = lines[i].match(FENCE_OPEN);
86
+ if (fence) {
87
+ // Collect the fenced content up to a closing fence of the SAME marker
88
+ // (a ```-opened block is not closed by ~~~, and vice versa).
89
+ const marker = fence[2];
90
+ const content = [];
91
+ let j = i + 1;
92
+ let closed = false;
93
+ for (; j < lines.length; j++) {
94
+ if (lines[j].trim() === marker) { closed = true; break; }
95
+ content.push(lines[j]);
96
+ }
97
+ if (closed && isFieldListFence(fence[3], content)) {
98
+ // Drop the fences and render the fields as a normal bullet list.
99
+ for (const c of content) {
100
+ const b = c.match(BULLET);
101
+ out.push(b ? normalizeBullet(b[1], b[2]) : c);
102
+ }
103
+ } else {
104
+ // Not a field list (e.g. a YAML example) — keep verbatim.
105
+ out.push(lines[i]);
106
+ for (const c of content) out.push(c);
107
+ if (closed) out.push(lines[j]);
108
+ }
109
+ i = closed ? j : lines.length;
110
+ } else {
111
+ // Bare bullet outside a fence: normalize the field name too.
112
+ const b = lines[i].match(BULLET);
113
+ out.push(b ? normalizeBullet(b[1], b[2]) : lines[i]);
114
+ }
115
+ }
116
+
117
+ return out.join('\n');
118
+ }
119
+
120
+ module.exports = { normalizeMetadataBlock };