@redpanda-data/docs-extensions-and-macros 4.8.0 → 4.9.0
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/bin/doc-tools.js +236 -54
- package/package.json +1 -1
- package/tools/property-extractor/Makefile +68 -50
- package/tools/property-extractor/cloud_config.py +594 -0
- package/tools/property-extractor/compare-properties.js +378 -0
- package/tools/property-extractor/generate-handlebars-docs.js +444 -0
- package/tools/property-extractor/helpers/and.js +10 -0
- package/tools/property-extractor/helpers/eq.js +9 -0
- package/tools/property-extractor/helpers/formatPropertyValue.js +128 -0
- package/tools/property-extractor/helpers/formatUnits.js +26 -0
- package/tools/property-extractor/helpers/index.js +13 -0
- package/tools/property-extractor/helpers/join.js +18 -0
- package/tools/property-extractor/helpers/ne.js +9 -0
- package/tools/property-extractor/helpers/not.js +8 -0
- package/tools/property-extractor/helpers/or.js +10 -0
- package/tools/property-extractor/helpers/renderPropertyExample.js +42 -0
- package/tools/property-extractor/package-lock.json +77 -0
- package/tools/property-extractor/package.json +6 -0
- package/tools/property-extractor/parser.py +27 -1
- package/tools/property-extractor/property_extractor.py +1428 -49
- package/tools/property-extractor/requirements.txt +2 -0
- package/tools/property-extractor/templates/deprecated-properties.hbs +25 -0
- package/tools/property-extractor/templates/deprecated-property.hbs +7 -0
- package/tools/property-extractor/templates/property-cloud.hbs +105 -0
- package/tools/property-extractor/templates/property-page.hbs +22 -0
- package/tools/property-extractor/templates/property.hbs +85 -0
- package/tools/property-extractor/templates/topic-property-cloud.hbs +97 -0
- package/tools/property-extractor/templates/topic-property.hbs +73 -0
- package/tools/property-extractor/transformers.py +178 -6
- package/tools/property-extractor/json-to-asciidoc/generate_docs.py +0 -491
|
@@ -0,0 +1,444 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Handlebars documentation generator for Redpanda configuration properties.
|
|
10
|
+
*
|
|
11
|
+
* Supports custom template overrides using environment variables:
|
|
12
|
+
* - TEMPLATE_PROPERTY_PAGE: Main property page template
|
|
13
|
+
* - TEMPLATE_PROPERTY: Individual property section template
|
|
14
|
+
* - TEMPLATE_TOPIC_PROPERTY: Individual topic property section template
|
|
15
|
+
* - TEMPLATE_DEPRECATED_PROPERTY: Individual deprecated property section template
|
|
16
|
+
* - TEMPLATE_DEPRECATED: Deprecated properties page template
|
|
17
|
+
*
|
|
18
|
+
* CLI Usage: node generate-handlebars-docs.js <input-file> <output-dir>
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// Register all helpers
|
|
22
|
+
Object.entries(helpers).forEach(([name, fn]) => {
|
|
23
|
+
if (typeof fn !== 'function') {
|
|
24
|
+
console.error(`❌ Helper "${name}" is not a function`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
handlebars.registerHelper(name, fn);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Configuration mapping for different property types
|
|
32
|
+
*/
|
|
33
|
+
const PROPERTY_CONFIG = {
|
|
34
|
+
broker: {
|
|
35
|
+
pageTitle: 'Broker Configuration Properties',
|
|
36
|
+
pageAliases: ['reference:node-properties.adoc', 'reference:node-configuration-sample.adoc'],
|
|
37
|
+
description: 'Reference of broker configuration properties.',
|
|
38
|
+
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.
|
|
39
|
+
|
|
40
|
+
For information on how to edit broker properties, see xref:manage:cluster-maintenance/node-property-configuration.adoc[].
|
|
41
|
+
|
|
42
|
+
NOTE: All broker properties require that you restart Redpanda for any update to take effect.`,
|
|
43
|
+
sectionTitle: 'Broker configuration',
|
|
44
|
+
groups: [
|
|
45
|
+
{
|
|
46
|
+
filter: (prop) => prop.config_scope === 'broker' && !prop.is_deprecated
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
filename: 'broker-properties.adoc'
|
|
50
|
+
},
|
|
51
|
+
cluster: {
|
|
52
|
+
pageTitle: 'Cluster Configuration Properties',
|
|
53
|
+
pageAliases: ['reference:tunable-properties.adoc', 'reference:cluster-properties.adoc'],
|
|
54
|
+
description: 'Cluster configuration properties list.',
|
|
55
|
+
intro: `Cluster configuration properties are the same for all brokers in a cluster, and are set at the cluster level.
|
|
56
|
+
|
|
57
|
+
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[].
|
|
58
|
+
|
|
59
|
+
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.`,
|
|
60
|
+
sectionTitle: 'Cluster configuration',
|
|
61
|
+
groups: [
|
|
62
|
+
{
|
|
63
|
+
filter: (prop) => prop.config_scope === 'cluster' && !prop.is_deprecated
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
filename: 'cluster-properties.adoc'
|
|
67
|
+
},
|
|
68
|
+
'object-storage': {
|
|
69
|
+
pageTitle: 'Object Storage Properties',
|
|
70
|
+
description: 'Reference of object storage properties.',
|
|
71
|
+
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[].
|
|
72
|
+
|
|
73
|
+
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.`,
|
|
74
|
+
sectionTitle: 'Object storage configuration',
|
|
75
|
+
sectionIntro: 'Object storage properties should only be set if you enable xref:manage:tiered-storage.adoc[Tiered Storage].',
|
|
76
|
+
groups: [
|
|
77
|
+
{
|
|
78
|
+
filter: (prop) => prop.name && (
|
|
79
|
+
prop.name.includes('cloud_storage') ||
|
|
80
|
+
prop.name.includes('s3_') ||
|
|
81
|
+
prop.name.includes('azure_') ||
|
|
82
|
+
prop.name.includes('gcs_') ||
|
|
83
|
+
prop.name.includes('archival_') ||
|
|
84
|
+
prop.name.includes('remote_') ||
|
|
85
|
+
prop.name.includes('tiered_')
|
|
86
|
+
) && !prop.is_deprecated
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
filename: 'object-storage-properties.adoc'
|
|
90
|
+
},
|
|
91
|
+
topic: {
|
|
92
|
+
pageTitle: 'Topic Configuration Properties',
|
|
93
|
+
pageAliases: ['reference:topic-properties.adoc'],
|
|
94
|
+
description: 'Reference of topic configuration properties.',
|
|
95
|
+
intro: `A topic-level property sets a Redpanda or Kafka configuration for a particular topic.
|
|
96
|
+
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
NOTE: All topic properties take effect immediately after being set.`,
|
|
100
|
+
sectionTitle: 'Topic configuration',
|
|
101
|
+
groups: [
|
|
102
|
+
{
|
|
103
|
+
filter: (prop) => prop.config_scope === 'topic' && !prop.is_deprecated,
|
|
104
|
+
template: 'topic-property'
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
filename: 'topic-properties.adoc'
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// "src/v/kafka/server/handlers/topics/types.cc": "topic"
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Gets template path, checking environment variables for custom paths first
|
|
115
|
+
*/
|
|
116
|
+
function getTemplatePath(defaultPath, envVar) {
|
|
117
|
+
const customPath = process.env[envVar];
|
|
118
|
+
if (customPath && fs.existsSync(customPath)) {
|
|
119
|
+
console.log(`📄 Using custom template: ${customPath}`);
|
|
120
|
+
return customPath;
|
|
121
|
+
}
|
|
122
|
+
return defaultPath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Register Handlebars partials used to render property documentation.
|
|
127
|
+
*
|
|
128
|
+
* Loads templates from the local templates directory (overridable via environment
|
|
129
|
+
* variables handled by getTemplatePath) and registers three partials:
|
|
130
|
+
* - "property" (uses cloud-aware `property-cloud.hbs` when enabled)
|
|
131
|
+
* - "topic-property" (uses cloud-aware `topic-property-cloud.hbs` when enabled)
|
|
132
|
+
* - "deprecated-property"
|
|
133
|
+
*
|
|
134
|
+
* @param {boolean} [hasCloudSupport=false] - If true, select cloud-aware templates for the `property` and `topic-property` partials.
|
|
135
|
+
* @throws {Error} If any required template file is missing or cannot be read; errors are rethrown after logging.
|
|
136
|
+
*/
|
|
137
|
+
function registerPartials(hasCloudSupport = false) {
|
|
138
|
+
const templatesDir = path.join(__dirname, 'templates');
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
console.log(`📝 Registering Handlebars templates (cloud support: ${hasCloudSupport ? 'enabled' : 'disabled'})`);
|
|
142
|
+
|
|
143
|
+
// Register property partial (choose cloud or regular version)
|
|
144
|
+
const propertyTemplateFile = hasCloudSupport ? 'property-cloud.hbs' : 'property.hbs';
|
|
145
|
+
const propertyTemplatePath = getTemplatePath(
|
|
146
|
+
path.join(templatesDir, propertyTemplateFile),
|
|
147
|
+
'TEMPLATE_PROPERTY'
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
if (!fs.existsSync(propertyTemplatePath)) {
|
|
151
|
+
throw new Error(`Property template not found: ${propertyTemplatePath}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const propertyTemplate = fs.readFileSync(propertyTemplatePath, 'utf8');
|
|
155
|
+
handlebars.registerPartial('property', propertyTemplate);
|
|
156
|
+
console.log(`✅ Registered property template: ${propertyTemplateFile}`);
|
|
157
|
+
|
|
158
|
+
// Register topic property partial (choose cloud or regular version)
|
|
159
|
+
const topicPropertyTemplateFile = hasCloudSupport ? 'topic-property-cloud.hbs' : 'topic-property.hbs';
|
|
160
|
+
const topicPropertyTemplatePath = getTemplatePath(
|
|
161
|
+
path.join(templatesDir, topicPropertyTemplateFile),
|
|
162
|
+
'TEMPLATE_TOPIC_PROPERTY'
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(topicPropertyTemplatePath)) {
|
|
166
|
+
throw new Error(`Topic property template not found: ${topicPropertyTemplatePath}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const topicPropertyTemplate = fs.readFileSync(topicPropertyTemplatePath, 'utf8');
|
|
170
|
+
handlebars.registerPartial('topic-property', topicPropertyTemplate);
|
|
171
|
+
console.log(`✅ Registered topic property template: ${topicPropertyTemplateFile}`);
|
|
172
|
+
|
|
173
|
+
// Register deprecated property partial
|
|
174
|
+
const deprecatedPropertyTemplatePath = getTemplatePath(
|
|
175
|
+
path.join(templatesDir, 'deprecated-property.hbs'),
|
|
176
|
+
'TEMPLATE_DEPRECATED_PROPERTY'
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
if (!fs.existsSync(deprecatedPropertyTemplatePath)) {
|
|
180
|
+
throw new Error(`Deprecated property template not found: ${deprecatedPropertyTemplatePath}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const deprecatedPropertyTemplate = fs.readFileSync(deprecatedPropertyTemplatePath, 'utf8');
|
|
184
|
+
handlebars.registerPartial('deprecated-property', deprecatedPropertyTemplate);
|
|
185
|
+
console.log(`✅ Registered deprecated property template`);
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('❌ Failed to register Handlebars templates:');
|
|
189
|
+
console.error(` Error: ${error.message}`);
|
|
190
|
+
console.error(' This indicates missing or corrupted template files.');
|
|
191
|
+
console.error(' Check that all .hbs files exist in tools/property-extractor/templates/');
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Generates documentation for a specific property type
|
|
198
|
+
*/
|
|
199
|
+
function generatePropertyDocs(properties, config, outputDir) {
|
|
200
|
+
const templatePath = getTemplatePath(
|
|
201
|
+
path.join(__dirname, 'templates', 'property-page.hbs'),
|
|
202
|
+
'TEMPLATE_PROPERTY_PAGE'
|
|
203
|
+
);
|
|
204
|
+
const template = handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
|
|
205
|
+
|
|
206
|
+
// Filter and group properties according to configuration
|
|
207
|
+
const groups = config.groups.map(group => {
|
|
208
|
+
const filteredProperties = Object.values(properties)
|
|
209
|
+
.filter(prop => group.filter(prop))
|
|
210
|
+
.sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
title: group.title,
|
|
214
|
+
intro: group.intro,
|
|
215
|
+
properties: filteredProperties,
|
|
216
|
+
template: group.template || 'property' // Default to 'property' template
|
|
217
|
+
};
|
|
218
|
+
}).filter(group => group.properties.length > 0);
|
|
219
|
+
|
|
220
|
+
const data = {
|
|
221
|
+
...config,
|
|
222
|
+
groups
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
const output = template(data);
|
|
226
|
+
const outputPath = path.join(outputDir, config.filename);
|
|
227
|
+
|
|
228
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
229
|
+
fs.writeFileSync(outputPath, output, 'utf8');
|
|
230
|
+
|
|
231
|
+
console.log(`✅ Generated ${outputPath}`);
|
|
232
|
+
return groups.reduce((total, group) => total + group.properties.length, 0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Generate an AsciiDoc fragment listing deprecated properties and write it to disk.
|
|
237
|
+
*
|
|
238
|
+
* Scans the provided properties map for entries with `is_deprecated === true`, groups
|
|
239
|
+
* them by `config_scope` ("broker" and "cluster"), sorts each group by property name,
|
|
240
|
+
* renders the `deprecated-properties` Handlebars template, and writes the output to
|
|
241
|
+
* `<outputDir>/deprecated/partials/deprecated-properties.adoc`.
|
|
242
|
+
*
|
|
243
|
+
* @param {Object.<string, Object>} properties - Map of property objects keyed by property name.
|
|
244
|
+
* Each property object may contain `is_deprecated`, `config_scope`, and `name` fields.
|
|
245
|
+
* @param {string} outputDir - Destination directory where the deprecated fragment will be written.
|
|
246
|
+
* @returns {number} The total number of deprecated properties found and written.
|
|
247
|
+
*/
|
|
248
|
+
function generateDeprecatedDocs(properties, outputDir) {
|
|
249
|
+
const templatePath = getTemplatePath(
|
|
250
|
+
path.join(__dirname, 'templates', 'deprecated-properties.hbs'),
|
|
251
|
+
'TEMPLATE_DEPRECATED'
|
|
252
|
+
);
|
|
253
|
+
const template = handlebars.compile(fs.readFileSync(templatePath, 'utf8'));
|
|
254
|
+
|
|
255
|
+
const deprecatedProperties = Object.values(properties).filter(prop => prop.is_deprecated);
|
|
256
|
+
|
|
257
|
+
const brokerProperties = deprecatedProperties
|
|
258
|
+
.filter(prop => prop.config_scope === 'broker')
|
|
259
|
+
.sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
|
|
260
|
+
|
|
261
|
+
const clusterProperties = deprecatedProperties
|
|
262
|
+
.filter(prop => prop.config_scope === 'cluster')
|
|
263
|
+
.sort((a, b) => String(a.name || '').localeCompare(String(b.name || '')));
|
|
264
|
+
|
|
265
|
+
const data = {
|
|
266
|
+
deprecated: deprecatedProperties.length > 0,
|
|
267
|
+
brokerProperties: brokerProperties.length > 0 ? brokerProperties : null,
|
|
268
|
+
clusterProperties: clusterProperties.length > 0 ? clusterProperties : null
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const output = template(data);
|
|
272
|
+
const outputPath = path.join(outputDir, 'deprecated', 'partials', 'deprecated-properties.adoc');
|
|
273
|
+
|
|
274
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
275
|
+
fs.writeFileSync(outputPath, output, 'utf8');
|
|
276
|
+
|
|
277
|
+
console.log(`✅ Generated ${outputPath}`);
|
|
278
|
+
return deprecatedProperties.length;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Determine whether any property includes cloud support metadata.
|
|
283
|
+
*
|
|
284
|
+
* Checks the provided map of properties and returns true if at least one
|
|
285
|
+
* property object has a `cloud_supported` own property (regardless of its value).
|
|
286
|
+
*
|
|
287
|
+
* @param {Object<string, Object>} properties - Map from property name to its metadata object.
|
|
288
|
+
* @return {boolean} True if any property has a `cloud_supported` attribute; otherwise false.
|
|
289
|
+
*/
|
|
290
|
+
function hasCloudSupportMetadata(properties) {
|
|
291
|
+
return Object.values(properties).some(prop =>
|
|
292
|
+
Object.prototype.hasOwnProperty.call(prop, 'cloud_supported')
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate all property documentation and write output files to disk.
|
|
298
|
+
*
|
|
299
|
+
* Reads properties from the provided JSON file, detects whether any property
|
|
300
|
+
* includes cloud support metadata to select cloud-aware templates, registers
|
|
301
|
+
* Handlebars partials accordingly, renders per-type property pages and a
|
|
302
|
+
* deprecated-properties partial, writes a flat list of all property names, and
|
|
303
|
+
* produces error reports.
|
|
304
|
+
*
|
|
305
|
+
* Generated artifacts are written under the given output directory (e.g.:
|
|
306
|
+
* pages/<type>/*.adoc, pages/deprecated/partials/deprecated-properties.adoc,
|
|
307
|
+
* all_properties.txt, and files under outputDir/error).
|
|
308
|
+
*
|
|
309
|
+
* @param {string} inputFile - Filesystem path to the input JSON containing a top-level `properties` object.
|
|
310
|
+
* @param {string} outputDir - Destination directory where generated pages and reports will be written.
|
|
311
|
+
* @returns {{totalProperties: number, brokerProperties: number, clusterProperties: number, objectStorageProperties: number, topicProperties: number, deprecatedProperties: number}} Summary counts for all properties and per-type totals.
|
|
312
|
+
*/
|
|
313
|
+
function generateAllDocs(inputFile, outputDir) {
|
|
314
|
+
// Read input JSON
|
|
315
|
+
const data = JSON.parse(fs.readFileSync(inputFile, 'utf8'));
|
|
316
|
+
const properties = data.properties || {};
|
|
317
|
+
|
|
318
|
+
// Check if cloud support is enabled
|
|
319
|
+
const hasCloudSupport = hasCloudSupportMetadata(properties);
|
|
320
|
+
if (hasCloudSupport) {
|
|
321
|
+
console.log('🌤️ Cloud support metadata detected, using cloud-aware templates');
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Register partials with cloud support detection
|
|
325
|
+
registerPartials(hasCloudSupport);
|
|
326
|
+
|
|
327
|
+
let totalProperties = 0;
|
|
328
|
+
let totalBrokerProperties = 0;
|
|
329
|
+
let totalClusterProperties = 0;
|
|
330
|
+
let totalObjectStorageProperties = 0;
|
|
331
|
+
let totalTopicProperties = 0;
|
|
332
|
+
|
|
333
|
+
// Generate each type of documentation
|
|
334
|
+
for (const [type, config] of Object.entries(PROPERTY_CONFIG)) {
|
|
335
|
+
const count = generatePropertyDocs(properties, config, path.join(outputDir, 'pages'));
|
|
336
|
+
totalProperties += count;
|
|
337
|
+
|
|
338
|
+
if (type === 'broker') totalBrokerProperties = count;
|
|
339
|
+
else if (type === 'cluster') totalClusterProperties = count;
|
|
340
|
+
else if (type === 'object-storage') totalObjectStorageProperties = count;
|
|
341
|
+
else if (type === 'topic') totalTopicProperties = count;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Generate deprecated properties documentation
|
|
345
|
+
const deprecatedCount = generateDeprecatedDocs(properties, path.join(outputDir, 'pages'));
|
|
346
|
+
|
|
347
|
+
// Generate summary file
|
|
348
|
+
const allPropertiesContent = Object.keys(properties).sort().join('\n');
|
|
349
|
+
fs.writeFileSync(path.join(outputDir, 'all_properties.txt'), allPropertiesContent, 'utf8');
|
|
350
|
+
|
|
351
|
+
// Generate error reports
|
|
352
|
+
generateErrorReports(properties, outputDir);
|
|
353
|
+
|
|
354
|
+
console.log(`📊 Generation Summary:`);
|
|
355
|
+
console.log(` Total properties read: ${Object.keys(properties).length}`);
|
|
356
|
+
console.log(` Total Broker properties: ${totalBrokerProperties}`);
|
|
357
|
+
console.log(` Total Cluster properties: ${totalClusterProperties}`);
|
|
358
|
+
console.log(` Total Object Storage properties: ${totalObjectStorageProperties}`);
|
|
359
|
+
console.log(` Total Topic properties: ${totalTopicProperties}`);
|
|
360
|
+
console.log(` Total Deprecated properties: ${deprecatedCount}`);
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
totalProperties: Object.keys(properties).length,
|
|
364
|
+
brokerProperties: totalBrokerProperties,
|
|
365
|
+
clusterProperties: totalClusterProperties,
|
|
366
|
+
objectStorageProperties: totalObjectStorageProperties,
|
|
367
|
+
topicProperties: totalTopicProperties,
|
|
368
|
+
deprecatedProperties: deprecatedCount
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Generate error reports for properties with missing or invalid data
|
|
374
|
+
*/
|
|
375
|
+
function generateErrorReports(properties, outputDir) {
|
|
376
|
+
const errorDir = path.join(outputDir, 'error');
|
|
377
|
+
fs.mkdirSync(errorDir, { recursive: true });
|
|
378
|
+
|
|
379
|
+
const emptyDescriptions = [];
|
|
380
|
+
const deprecatedProperties = [];
|
|
381
|
+
|
|
382
|
+
Object.values(properties).forEach(prop => {
|
|
383
|
+
if (!prop.description || prop.description.trim() === '') {
|
|
384
|
+
emptyDescriptions.push(prop.name);
|
|
385
|
+
}
|
|
386
|
+
if (prop.is_deprecated) {
|
|
387
|
+
deprecatedProperties.push(prop.name);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// Write error reports
|
|
392
|
+
const totalProperties = Object.keys(properties).length;
|
|
393
|
+
|
|
394
|
+
if (emptyDescriptions.length > 0) {
|
|
395
|
+
fs.writeFileSync(
|
|
396
|
+
path.join(errorDir, 'empty_description.txt'),
|
|
397
|
+
emptyDescriptions.join('\n'),
|
|
398
|
+
'utf8'
|
|
399
|
+
);
|
|
400
|
+
const percentage = totalProperties > 0 ? ((emptyDescriptions.length / totalProperties) * 100).toFixed(2) : '0.00';
|
|
401
|
+
console.log(`You have ${emptyDescriptions.length} properties with empty description. Percentage of errors: ${percentage}%. Data written in 'empty_description.txt'.`);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (deprecatedProperties.length > 0) {
|
|
405
|
+
fs.writeFileSync(
|
|
406
|
+
path.join(errorDir, 'deprecated_properties.txt'),
|
|
407
|
+
deprecatedProperties.join('\n'),
|
|
408
|
+
'utf8'
|
|
409
|
+
);
|
|
410
|
+
const percentage = totalProperties > 0 ? ((deprecatedProperties.length / totalProperties) * 100).toFixed(2) : '0.00';
|
|
411
|
+
console.log(`You have ${deprecatedProperties.length} deprecated properties. Percentage of errors: ${percentage}%. Data written in 'deprecated_properties.txt'.`);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
module.exports = {
|
|
416
|
+
generateAllDocs,
|
|
417
|
+
generatePropertyDocs,
|
|
418
|
+
generateDeprecatedDocs,
|
|
419
|
+
PROPERTY_CONFIG
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
// CLI interface
|
|
423
|
+
if (require.main === module) {
|
|
424
|
+
const args = process.argv.slice(2);
|
|
425
|
+
if (args.length < 2) {
|
|
426
|
+
console.error('Usage: node generate-handlebars-docs.js <input-file> <output-dir>');
|
|
427
|
+
process.exit(1);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const [inputFile, outputDir] = args;
|
|
431
|
+
|
|
432
|
+
if (!fs.existsSync(inputFile)) {
|
|
433
|
+
console.error(`❌ Input file not found: ${inputFile}`);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
generateAllDocs(inputFile, outputDir);
|
|
439
|
+
console.log('✅ Documentation generation completed successfully');
|
|
440
|
+
} catch (error) {
|
|
441
|
+
console.error(`❌ Error generating documentation: ${error.message}`);
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
@@ -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,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,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
|
+
};
|