@redpanda-data/docs-extensions-and-macros 4.8.0 → 4.8.1

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.
Files changed (25) hide show
  1. package/bin/doc-tools.js +88 -53
  2. package/package.json +1 -1
  3. package/tools/property-extractor/Makefile +62 -34
  4. package/tools/property-extractor/generate-handlebars-docs.js +344 -0
  5. package/tools/property-extractor/helpers/and.js +10 -0
  6. package/tools/property-extractor/helpers/eq.js +9 -0
  7. package/tools/property-extractor/helpers/formatPropertyValue.js +128 -0
  8. package/tools/property-extractor/helpers/formatUnits.js +26 -0
  9. package/tools/property-extractor/helpers/index.js +13 -0
  10. package/tools/property-extractor/helpers/join.js +18 -0
  11. package/tools/property-extractor/helpers/ne.js +9 -0
  12. package/tools/property-extractor/helpers/not.js +8 -0
  13. package/tools/property-extractor/helpers/or.js +10 -0
  14. package/tools/property-extractor/helpers/renderPropertyExample.js +42 -0
  15. package/tools/property-extractor/package-lock.json +77 -0
  16. package/tools/property-extractor/package.json +6 -0
  17. package/tools/property-extractor/property_extractor.py +1163 -20
  18. package/tools/property-extractor/requirements.txt +1 -0
  19. package/tools/property-extractor/templates/deprecated-properties.hbs +25 -0
  20. package/tools/property-extractor/templates/deprecated-property.hbs +7 -0
  21. package/tools/property-extractor/templates/property-page.hbs +22 -0
  22. package/tools/property-extractor/templates/property.hbs +70 -0
  23. package/tools/property-extractor/templates/topic-property.hbs +59 -0
  24. package/tools/property-extractor/transformers.py +80 -4
  25. package/tools/property-extractor/json-to-asciidoc/generate_docs.py +0 -491
@@ -0,0 +1,344 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const handlebars = require('handlebars');
6
+ const helpers = require('./helpers');
7
+
8
+ // Register all helpers
9
+ Object.entries(helpers).forEach(([name, fn]) => {
10
+ if (typeof fn !== 'function') {
11
+ console.error(`❌ Helper "${name}" is not a function`);
12
+ process.exit(1);
13
+ }
14
+ handlebars.registerHelper(name, fn);
15
+ });
16
+
17
+ /**
18
+ * Configuration mapping for different property types
19
+ */
20
+ const PROPERTY_CONFIG = {
21
+ broker: {
22
+ pageTitle: 'Broker Configuration Properties',
23
+ pageAliases: ['reference:node-properties.adoc', 'reference:node-configuration-sample.adoc'],
24
+ description: 'Reference of broker configuration properties.',
25
+ intro: `Broker configuration properties are applied individually to each broker in a cluster. You can find and modify these properties in the \`redpanda.yaml\` configuration file.
26
+
27
+ For information on how to edit broker properties, see xref:manage:cluster-maintenance/node-property-configuration.adoc[].
28
+
29
+ NOTE: All broker properties require that you restart Redpanda for any update to take effect.`,
30
+ sectionTitle: 'Broker configuration',
31
+ groups: [
32
+ {
33
+ filter: (prop) => prop.config_scope === 'broker' && !prop.is_deprecated
34
+ }
35
+ ],
36
+ filename: 'broker-properties.adoc'
37
+ },
38
+ cluster: {
39
+ pageTitle: 'Cluster Configuration Properties',
40
+ pageAliases: ['reference:tunable-properties.adoc', 'reference:cluster-properties.adoc'],
41
+ description: 'Cluster configuration properties list.',
42
+ intro: `Cluster configuration properties are the same for all brokers in a cluster, and are set at the cluster level.
43
+
44
+ For information on how to edit cluster properties, see xref:manage:cluster-maintenance/cluster-property-configuration.adoc[] or xref:manage:kubernetes/k-cluster-property-configuration.adoc[].
45
+
46
+ NOTE: Some cluster properties require that you restart the cluster for any updates to take effect. See the specific property details to identify whether or not a restart is required.`,
47
+ sectionTitle: 'Cluster configuration',
48
+ groups: [
49
+ {
50
+ filter: (prop) => prop.config_scope === 'cluster' && !prop.is_deprecated
51
+ }
52
+ ],
53
+ filename: 'cluster-properties.adoc'
54
+ },
55
+ 'object-storage': {
56
+ pageTitle: 'Object Storage Properties',
57
+ description: 'Reference of object storage properties.',
58
+ intro: `Object storage properties are a type of cluster property. For information on how to edit cluster properties, see xref:manage:cluster-maintenance/cluster-property-configuration.adoc[].
59
+
60
+ NOTE: Some object storage properties require that you restart the cluster for any updates to take effect. See the specific property details to identify whether or not a restart is required.`,
61
+ sectionTitle: 'Object storage configuration',
62
+ sectionIntro: 'Object storage properties should only be set if you enable xref:manage:tiered-storage.adoc[Tiered Storage].',
63
+ groups: [
64
+ {
65
+ filter: (prop) => prop.name && (
66
+ prop.name.includes('cloud_storage') ||
67
+ prop.name.includes('s3_') ||
68
+ prop.name.includes('azure_') ||
69
+ prop.name.includes('gcs_') ||
70
+ prop.name.includes('archival_') ||
71
+ prop.name.includes('remote_') ||
72
+ prop.name.includes('tiered_')
73
+ ) && !prop.is_deprecated
74
+ }
75
+ ],
76
+ filename: 'object-storage-properties.adoc'
77
+ },
78
+ topic: {
79
+ pageTitle: 'Topic Configuration Properties',
80
+ pageAliases: ['reference:topic-properties.adoc'],
81
+ description: 'Reference of topic configuration properties.',
82
+ intro: `A topic-level property sets a Redpanda or Kafka configuration for a particular topic.
83
+
84
+ Many topic-level properties have corresponding xref:manage:cluster-maintenance/cluster-property-configuration.adoc[cluster properties] that set a default value for all topics of a cluster. To customize the value for a topic, you can set a topic-level property that overrides the value of the corresponding cluster property.
85
+
86
+ NOTE: All topic properties take effect immediately after being set.`,
87
+ sectionTitle: 'Topic configuration',
88
+ groups: [
89
+ {
90
+ filter: (prop) => prop.config_scope === 'topic' && !prop.is_deprecated,
91
+ template: 'topic-property'
92
+ }
93
+ ],
94
+ filename: 'topic-properties.adoc'
95
+ }
96
+ };
97
+
98
+ // "src/v/kafka/server/handlers/topics/types.cc": "topic"
99
+
100
+ /**
101
+ * Gets template path, checking environment variables for custom paths first
102
+ */
103
+ function getTemplatePath(defaultPath, envVar) {
104
+ const customPath = process.env[envVar];
105
+ if (customPath && fs.existsSync(customPath)) {
106
+ console.log(`📄 Using custom template: ${customPath}`);
107
+ return customPath;
108
+ }
109
+ return defaultPath;
110
+ }
111
+
112
+ /**
113
+ * Registers Handlebars partials from template files
114
+ */
115
+ function registerPartials() {
116
+ const templatesDir = path.join(__dirname, 'templates');
117
+
118
+ // Register property partial
119
+ const propertyTemplatePath = getTemplatePath(
120
+ path.join(templatesDir, 'property.hbs'),
121
+ 'TEMPLATE_PROPERTY'
122
+ );
123
+ const propertyTemplate = fs.readFileSync(propertyTemplatePath, 'utf8');
124
+ handlebars.registerPartial('property', propertyTemplate);
125
+
126
+ // Register topic property partial
127
+ const topicPropertyTemplatePath = getTemplatePath(
128
+ path.join(templatesDir, 'topic-property.hbs'),
129
+ 'TEMPLATE_TOPIC_PROPERTY'
130
+ );
131
+ const topicPropertyTemplate = fs.readFileSync(topicPropertyTemplatePath, 'utf8');
132
+ handlebars.registerPartial('topic-property', topicPropertyTemplate);
133
+
134
+ // Register deprecated property partial
135
+ const deprecatedPropertyTemplatePath = getTemplatePath(
136
+ path.join(templatesDir, 'deprecated-property.hbs'),
137
+ 'TEMPLATE_DEPRECATED_PROPERTY'
138
+ );
139
+ const deprecatedPropertyTemplate = fs.readFileSync(deprecatedPropertyTemplatePath, 'utf8');
140
+ handlebars.registerPartial('deprecated-property', deprecatedPropertyTemplate);
141
+ }
142
+
143
+ /**
144
+ * Generates documentation for a specific property type
145
+ */
146
+ function generatePropertyDocs(properties, config, outputDir) {
147
+ const templatePath = getTemplatePath(
148
+ path.join(__dirname, 'templates', 'property-page.hbs'),
149
+ 'TEMPLATE_PROPERTY_PAGE'
150
+ );
151
+ const template = handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
152
+
153
+ // Filter and group properties according to configuration
154
+ const groups = config.groups.map(group => {
155
+ const filteredProperties = Object.values(properties)
156
+ .filter(prop => group.filter(prop))
157
+ .sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
158
+
159
+ return {
160
+ title: group.title,
161
+ intro: group.intro,
162
+ properties: filteredProperties,
163
+ template: group.template || 'property' // Default to 'property' template
164
+ };
165
+ }).filter(group => group.properties.length > 0);
166
+
167
+ const data = {
168
+ ...config,
169
+ groups
170
+ };
171
+
172
+ const output = template(data);
173
+ const outputPath = path.join(outputDir, config.filename);
174
+
175
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
176
+ fs.writeFileSync(outputPath, output, 'utf8');
177
+
178
+ console.log(`✅ Generated ${outputPath}`);
179
+ return groups.reduce((total, group) => total + group.properties.length, 0);
180
+ }
181
+
182
+ /**
183
+ * Generates deprecated properties documentation
184
+ */
185
+ function generateDeprecatedDocs(properties, outputDir) {
186
+ const templatePath = getTemplatePath(
187
+ path.join(__dirname, 'templates', 'deprecated-properties.hbs'),
188
+ 'TEMPLATE_DEPRECATED'
189
+ );
190
+ const template = handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
191
+
192
+ const deprecatedProperties = Object.values(properties).filter(prop => prop.is_deprecated);
193
+
194
+ const brokerProperties = deprecatedProperties
195
+ .filter(prop => prop.config_scope === 'broker')
196
+ .sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
197
+
198
+ const clusterProperties = deprecatedProperties
199
+ .filter(prop => prop.config_scope === 'cluster')
200
+ .sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
201
+
202
+ const data = {
203
+ deprecated: deprecatedProperties.length > 0,
204
+ brokerProperties: brokerProperties.length > 0 ? brokerProperties : null,
205
+ clusterProperties: clusterProperties.length > 0 ? clusterProperties : null
206
+ };
207
+
208
+ const output = template(data);
209
+ const outputPath = path.join(outputDir, 'deprecated', 'partials', 'deprecated-properties.adoc');
210
+
211
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
212
+ fs.writeFileSync(outputPath, output, 'utf8');
213
+
214
+ console.log(`✅ Generated ${outputPath}`);
215
+ return deprecatedProperties.length;
216
+ }
217
+
218
+ /**
219
+ * Main function to generate all property documentation
220
+ */
221
+ function generateAllDocs(inputFile, outputDir) {
222
+ // Register partials
223
+ registerPartials();
224
+
225
+ // Read input JSON
226
+ const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
227
+ const properties = data.properties || {};
228
+
229
+ let totalProperties = 0;
230
+ let totalBrokerProperties = 0;
231
+ let totalClusterProperties = 0;
232
+ let totalObjectStorageProperties = 0;
233
+ let totalTopicProperties = 0;
234
+
235
+ // Generate each type of documentation
236
+ for (const [type, config] of Object.entries(PROPERTY_CONFIG)) {
237
+ const count = generatePropertyDocs(properties, config, path.join(outputDir, 'pages'));
238
+ totalProperties += count;
239
+
240
+ if (type === 'broker') totalBrokerProperties = count;
241
+ else if (type === 'cluster') totalClusterProperties = count;
242
+ else if (type === 'object-storage') totalObjectStorageProperties = count;
243
+ else if (type === 'topic') totalTopicProperties = count;
244
+ }
245
+
246
+ // Generate deprecated properties documentation
247
+ const deprecatedCount = generateDeprecatedDocs(properties, path.join(outputDir, 'pages'));
248
+
249
+ // Generate summary file
250
+ const allPropertiesContent = Object.keys(properties).sort().join('\n');
251
+ fs.writeFileSync(path.join(outputDir, 'all_properties.txt'), allPropertiesContent, 'utf8');
252
+
253
+ // Generate error reports
254
+ generateErrorReports(properties, outputDir);
255
+
256
+ console.log(`📊 Generation Summary:`);
257
+ console.log(` Total properties read: ${Object.keys(properties).length}`);
258
+ console.log(` Total Broker properties: ${totalBrokerProperties}`);
259
+ console.log(` Total Cluster properties: ${totalClusterProperties}`);
260
+ console.log(` Total Object Storage properties: ${totalObjectStorageProperties}`);
261
+ console.log(` Total Topic properties: ${totalTopicProperties}`);
262
+ console.log(` Total Deprecated properties: ${deprecatedCount}`);
263
+
264
+ return {
265
+ totalProperties: Object.keys(properties).length,
266
+ brokerProperties: totalBrokerProperties,
267
+ clusterProperties: totalClusterProperties,
268
+ objectStorageProperties: totalObjectStorageProperties,
269
+ topicProperties: totalTopicProperties,
270
+ deprecatedProperties: deprecatedCount
271
+ };
272
+ }
273
+
274
+ /**
275
+ * Generate error reports for properties with missing or invalid data
276
+ */
277
+ function generateErrorReports(properties, outputDir) {
278
+ const errorDir = path.join(outputDir, 'error');
279
+ fs.mkdirSync(errorDir, { recursive: true });
280
+
281
+ const emptyDescriptions = [];
282
+ const deprecatedProperties = [];
283
+
284
+ Object.values(properties).forEach(prop => {
285
+ if (!prop.description || prop.description.trim() === '') {
286
+ emptyDescriptions.push(prop.name);
287
+ }
288
+ if (prop.is_deprecated) {
289
+ deprecatedProperties.push(prop.name);
290
+ }
291
+ });
292
+
293
+ // Write error reports
294
+ if (emptyDescriptions.length > 0) {
295
+ fs.writeFileSync(
296
+ path.join(errorDir, 'empty_description.txt'),
297
+ emptyDescriptions.join('\n'),
298
+ 'utf8'
299
+ );
300
+ const percentage = ((emptyDescriptions.length / Object.keys(properties).length) * 100).toFixed(2);
301
+ console.log(`You have ${emptyDescriptions.length} properties with empty description. Percentage of errors: ${percentage}%. Data written in 'empty_description.txt'.`);
302
+ }
303
+
304
+ if (deprecatedProperties.length > 0) {
305
+ fs.writeFileSync(
306
+ path.join(errorDir, 'deprecated_properties.txt'),
307
+ deprecatedProperties.join('\n'),
308
+ 'utf8'
309
+ );
310
+ const percentage = ((deprecatedProperties.length / Object.keys(properties).length) * 100).toFixed(2);
311
+ console.log(`You have ${deprecatedProperties.length} deprecated properties. Percentage of errors: ${percentage}%. Data written in 'deprecated_properties.txt'.`);
312
+ }
313
+ }
314
+
315
+ module.exports = {
316
+ generateAllDocs,
317
+ generatePropertyDocs,
318
+ generateDeprecatedDocs,
319
+ PROPERTY_CONFIG
320
+ };
321
+
322
+ // CLI interface
323
+ if (require.main === module) {
324
+ const args = process.argv.slice(2);
325
+ if (args.length < 2) {
326
+ console.error('Usage: node generate-handlebars-docs.js <input-file> <output-dir>');
327
+ process.exit(1);
328
+ }
329
+
330
+ const [inputFile, outputDir] = args;
331
+
332
+ if (!fs.existsSync(inputFile)) {
333
+ console.error(`❌ Input file not found: ${inputFile}`);
334
+ process.exit(1);
335
+ }
336
+
337
+ try {
338
+ generateAllDocs(inputFile, outputDir);
339
+ console.log('✅ Documentation generation completed successfully');
340
+ } catch (error) {
341
+ console.error(`❌ Error generating documentation: ${error.message}`);
342
+ process.exit(1);
343
+ }
344
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Handlebars helper for logical AND
3
+ * @param {...*} args - Values to check
4
+ * @returns {boolean} True if all values are truthy per JavaScript semantics
5
+ */
6
+ module.exports = function and(...args) {
7
+ // Remove the last argument which is the Handlebars options object
8
+ const values = args.slice(0, -1);
9
+ return values.every(val => Boolean(val));
10
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Handlebars helper for equality comparison
3
+ * @param {*} a - First value
4
+ * @param {*} b - Second value
5
+ * @returns {boolean} True if values are equal
6
+ */
7
+ module.exports = function eq(a, b) {
8
+ return a === b;
9
+ };
@@ -0,0 +1,128 @@
1
+ const handlebars = require('handlebars');
2
+
3
+ /**
4
+ * Helper function to format a value (used in object/array formatting)
5
+ */
6
+ function formatValue(val) {
7
+ if (typeof val === 'string') {
8
+ return `"${val}"`;
9
+ } else if (typeof val === 'boolean') {
10
+ return val ? 'true' : 'false';
11
+ } else if (val === null || val === undefined) {
12
+ return 'null';
13
+ } else if (Array.isArray(val)) {
14
+ return "[" + val.map(v => formatValue(v)).join(", ") + "]";
15
+ } else if (typeof val === 'object' && val !== null) {
16
+ return JSON.stringify(val);
17
+ } else {
18
+ return String(val);
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Process C++ internal representations and convert them to user-friendly formats
24
+ * This matches the Python process_defaults function logic
25
+ */
26
+ function processDefaults(inputString, suffix) {
27
+ if (typeof inputString !== 'string') {
28
+ return inputString;
29
+ }
30
+
31
+ // Test for ip:port in vector: std::vector<net::unresolved_address>({{...}})
32
+ const vectorMatch = inputString.match(/std::vector<net::unresolved_address>\(\{\{("([\d.]+)",\s*(\d+))\}\}\)/);
33
+ if (vectorMatch) {
34
+ const ip = vectorMatch[2];
35
+ const port = vectorMatch[3];
36
+ return [`${ip}:${port}`];
37
+ }
38
+
39
+ // Test for ip:port in single-string: net::unresolved_address("127.0.0.1", 9092)
40
+ const brokerMatch = inputString.match(/net::unresolved_address\("([\d.]+)",\s*(\d+)\)/);
41
+ if (brokerMatch) {
42
+ const ip = brokerMatch[1];
43
+ const port = brokerMatch[2];
44
+ return `${ip}:${port}`;
45
+ }
46
+
47
+ // Handle std::nullopt
48
+ if (inputString.includes('std::nullopt')) {
49
+ return inputString.replace(/std::nullopt/g, 'null');
50
+ }
51
+
52
+ // Handle time units and other patterns would go here...
53
+ // For now, return the original string
54
+ return inputString;
55
+ }
56
+
57
+ /**
58
+ * Formats a property value for display, matching Python legacy format exactly
59
+ * @param {*} value - The value to format
60
+ * @param {string} type - The property type
61
+ * @returns {handlebars.SafeString} Formatted value
62
+ */
63
+ module.exports = function formatPropertyValue(value, type) {
64
+ if (value === null || value === undefined || value === '') {
65
+ return new handlebars.SafeString('null');
66
+ }
67
+
68
+ if (typeof value === 'boolean') {
69
+ return new handlebars.SafeString(value ? 'true' : 'false');
70
+ }
71
+
72
+ if (typeof value === 'object' && !Array.isArray(value)) {
73
+ // Format object defaults with Python-style syntax: {key: "value", key2: value2}
74
+ const pairs = [];
75
+ for (const [k, v] of Object.entries(value)) {
76
+ // Process each value for C++ representations
77
+ let processedValue = v;
78
+ if (typeof v === 'string') {
79
+ processedValue = processDefaults(v, null);
80
+ }
81
+ pairs.push(`${k}: ${formatValue(processedValue)}`);
82
+ }
83
+ return new handlebars.SafeString(`{${pairs.join(', ')}}`);
84
+ }
85
+
86
+ if (Array.isArray(value)) {
87
+ // Handle array defaults to match Python format
88
+ if (value.length === 0) {
89
+ return new handlebars.SafeString('[]');
90
+ } else {
91
+ // Format each array element
92
+ const formattedElements = [];
93
+ for (const item of value) {
94
+ if (typeof item === 'object' && item !== null) {
95
+ // Format object within array
96
+ const pairs = [];
97
+ for (const [k, v] of Object.entries(item)) {
98
+ // Process each value for C++ representations
99
+ let processedValue = v;
100
+ if (typeof v === 'string') {
101
+ processedValue = processDefaults(v, null);
102
+ }
103
+ pairs.push(`${k}: ${formatValue(processedValue)}`);
104
+ }
105
+ formattedElements.push(`{${pairs.join(', ')}}`);
106
+ } else {
107
+ formattedElements.push(String(item));
108
+ }
109
+ }
110
+ return new handlebars.SafeString(`[${formattedElements.join(', ')}]`);
111
+ }
112
+ }
113
+
114
+ // For other types, handle strings vs non-strings differently
115
+ let result;
116
+ if (typeof value === 'string') {
117
+ // Keep strings as-is (preserve original characters and casing)
118
+ result = value;
119
+ } else {
120
+ // Convert non-string values to String without altering case/quotes
121
+ result = String(value);
122
+ }
123
+
124
+ // Apply C++ processing
125
+ result = processDefaults(result, null);
126
+
127
+ return new handlebars.SafeString(result);
128
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Formats units for display based on property name suffix
3
+ * @param {string} name - Property name that might contain unit suffixes
4
+ * @returns {string} Formatted unit description
5
+ */
6
+ module.exports = function formatUnits(name) {
7
+ const suffixToUnit = {
8
+ 'ms': 'milliseconds',
9
+ 'sec': 'seconds',
10
+ 'seconds': 'seconds',
11
+ 'bytes': 'bytes',
12
+ 'buf': 'bytes',
13
+ 'partitions': 'number of partitions per topic',
14
+ 'percent': 'percent',
15
+ 'bps': 'bytes per second',
16
+ 'fraction': 'fraction'
17
+ };
18
+
19
+ if (!name) return '';
20
+
21
+ // Extract the last part after splitting on underscores (like Python implementation)
22
+ const parts = name.split('_');
23
+ const suffix = parts[parts.length - 1];
24
+
25
+ return suffixToUnit[suffix] || '';
26
+ };
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ join: require('./join.js'),
5
+ eq: require('./eq.js'),
6
+ ne: require('./ne.js'),
7
+ and: require('./and.js'),
8
+ or: require('./or.js'),
9
+ not: require('./not.js'),
10
+ formatPropertyValue: require('./formatPropertyValue.js'),
11
+ renderPropertyExample: require('./renderPropertyExample.js'),
12
+ formatUnits: require('./formatUnits.js'),
13
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Handlebars helper to join an array with a separator
3
+ * @param {Array} array - The array to join
4
+ * @param {string} separator - The separator to use
5
+ * @returns {string} The joined string
6
+ */
7
+ module.exports = function join(array, separator) {
8
+ if (!Array.isArray(array)) {
9
+ return '';
10
+ }
11
+
12
+ // Detect and ignore Handlebars options object
13
+ if (separator && typeof separator === 'object' && (separator.hash !== undefined || separator.data !== undefined)) {
14
+ separator = undefined;
15
+ }
16
+
17
+ return array.join(separator || ', ');
18
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Handlebars helper for inequality comparison
3
+ * @param {*} a - First value
4
+ * @param {*} b - Second value
5
+ * @returns {boolean} True if values are not equal
6
+ */
7
+ module.exports = function ne(a, b) {
8
+ return a !== b;
9
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Handlebars helper for logical NOT
3
+ * @param {*} value - Value to negate
4
+ * @returns {boolean} True if value is falsy
5
+ */
6
+ module.exports = function not(value) {
7
+ return !value;
8
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Handlebars helper for logical OR
3
+ * @param {...*} args - Values to check
4
+ * @returns {boolean} True if any value is truthy
5
+ */
6
+ module.exports = function or(...args) {
7
+ // Remove the last argument which is the Handlebars options object
8
+ const values = args.slice(0, -1);
9
+ return values.some(val => !!val);
10
+ };
@@ -0,0 +1,42 @@
1
+ const handlebars = require('handlebars');
2
+
3
+ /**
4
+ * Renders an example for a property based on its format
5
+ * @param {Object} property - The property object containing example data
6
+ * @returns {handlebars.SafeString} Formatted example block
7
+ */
8
+ module.exports = function renderPropertyExample(property) {
9
+ if (!property.example) {
10
+ return new handlebars.SafeString('');
11
+ }
12
+
13
+ let exampleContent = '';
14
+
15
+ // Handle different example formats
16
+ if (typeof property.example === 'string') {
17
+ // Check if it's already a complete AsciiDoc example
18
+ if (property.example.includes('.Example') || property.example.includes('[,yaml]')) {
19
+ exampleContent = property.example;
20
+ } else {
21
+ // Simple string example - wrap it
22
+ exampleContent = `.Example\n[,yaml]\n----\n${property.name}: ${property.example}\n----`;
23
+ }
24
+ } else if (Array.isArray(property.example)) {
25
+ // Multiline array example
26
+ exampleContent = property.example.join('\n');
27
+ } else if (typeof property.example === 'object' && property.example.title) {
28
+ // Structured example with title and content
29
+ exampleContent = `.${property.example.title}\n`;
30
+ if (property.example.description) {
31
+ exampleContent += `${property.example.description}\n\n`;
32
+ }
33
+ if (property.example.config) {
34
+ exampleContent += `[,yaml]\n----\n${JSON.stringify(property.example.config, null, 2)}\n----`;
35
+ }
36
+ } else {
37
+ // Fallback: JSON stringify the example
38
+ exampleContent = `.Example\n[,yaml]\n----\n${property.name}: ${JSON.stringify(property.example, null, 2)}\n----`;
39
+ }
40
+
41
+ return new handlebars.SafeString('\n' + exampleContent + '\n');
42
+ };