Skip to content

Commit 33b0a45

Browse files
Added new scripts
1 parent 82d07a2 commit 33b0a45

File tree

3 files changed

+385
-0
lines changed

3 files changed

+385
-0
lines changed

scripts/boldify-subheadings.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env node
2+
/**
3+
* boldify-subheadings.js
4+
*
5+
* Recursively finds .md files in a given folder (relative to docs/),
6+
* finds lines that are likely sub-headings (standalone, not already formatted, not a heading/bullet/image/link/code, not ending with punctuation, and 8 words or fewer),
7+
* and wraps them in double asterisks for bold.
8+
*
9+
* Usage:
10+
* node scripts/boldify-subheadings.js <relative-folder>
11+
* Example:
12+
* node scripts/boldify-subheadings.js accessanalyzer/12.0/install
13+
*/
14+
15+
const fs = require('fs');
16+
const path = require('path');
17+
18+
const DOCS_ROOT = path.join(__dirname, '..', 'docs');
19+
20+
function getAllMarkdownFiles(dir) {
21+
let results = [];
22+
const list = fs.readdirSync(dir);
23+
list.forEach(function(file) {
24+
const filePath = path.join(dir, file);
25+
const stat = fs.statSync(filePath);
26+
if (stat && stat.isDirectory()) {
27+
results = results.concat(getAllMarkdownFiles(filePath));
28+
} else if (file.endsWith('.md')) {
29+
results.push(filePath);
30+
}
31+
});
32+
return results;
33+
}
34+
35+
function isSubheading(line) {
36+
const trimmed = line.trim();
37+
if (!trimmed) return false;
38+
// Not a heading, bullet, image, link, code, or already formatted
39+
if (/^(#|\* |\- |\+ |!\[|\[|\d+\.|`| {4,})/.test(trimmed)) return false;
40+
if (/\*\*|__|`/.test(trimmed)) return false; // Only skip if already bold, code, or double-underscore
41+
if (/(\s|^)\*[^*]+\*(\s|$)/.test(trimmed)) return false; // skip if *italic*
42+
if (/(\s|^)_[^_]+_(\s|$)/.test(trimmed)) return false; // skip if _italic_
43+
// Not ending with . : ;
44+
if (/[.:;]$/.test(trimmed)) return false;
45+
// 8 words or fewer
46+
if (trimmed.split(/\s+/).length > 8) return false;
47+
return true;
48+
}
49+
50+
function isBolded(line) {
51+
const trimmed = line.trim();
52+
return /^\*\*[^*]+\*\*$/.test(trimmed) || /^__[^_]+__$/.test(trimmed);
53+
}
54+
55+
function boldifySubheadings(content) {
56+
const lines = content.split(/\r?\n/);
57+
const newLines = [...lines];
58+
let lastWasBolded = false;
59+
for (let i = 0; i < lines.length; i++) {
60+
const prevEmpty = i === 0 || lines[i-1].trim() === '';
61+
const nextEmpty = i === lines.length-1 || lines[i+1].trim() === '';
62+
if (prevEmpty && nextEmpty && isSubheading(lines[i]) && !lastWasBolded) {
63+
newLines[i] = `**${lines[i].trim()}**`;
64+
lastWasBolded = true;
65+
} else if (lines[i].trim() !== '') {
66+
lastWasBolded = isBolded(lines[i]);
67+
}
68+
}
69+
return newLines.join('\n');
70+
}
71+
72+
function processFile(filePath) {
73+
const content = fs.readFileSync(filePath, 'utf8');
74+
const newContent = boldifySubheadings(content);
75+
if (newContent !== content) {
76+
fs.writeFileSync(filePath, newContent, 'utf8');
77+
console.log(`Updated: ${filePath}`);
78+
}
79+
}
80+
81+
function main() {
82+
const relInput = process.argv[2];
83+
if (!relInput) {
84+
console.error('Usage: node scripts/boldify-subheadings.js <relative-folder>');
85+
process.exit(1);
86+
}
87+
const inputDir = path.join(DOCS_ROOT, relInput);
88+
if (!fs.existsSync(inputDir)) {
89+
console.error(`Directory does not exist: ${inputDir}`);
90+
process.exit(1);
91+
}
92+
const mdFiles = getAllMarkdownFiles(inputDir);
93+
mdFiles.forEach(processFile);
94+
}
95+
96+
if (require.main === module) {
97+
main();
98+
}

scripts/convert-admonitions.js

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
#!/usr/bin/env node
2+
/**
3+
* convert-admonitions.js
4+
*
5+
* Recursively finds .md files in a given folder (relative to docs/),
6+
* replaces paragraphs starting with **NOTE:** with Docusaurus admonition blocks.
7+
*
8+
* Usage:
9+
* node scripts/convert-admonitions.js <relative-folder>
10+
* Example:
11+
* node scripts/convert-admonitions.js accessanalyzer/12.0/install
12+
*
13+
* Extendable for other admonitions (see the ADMONITION_MAP below).
14+
*/
15+
16+
const fs = require('fs');
17+
const path = require('path');
18+
19+
const DOCS_ROOT = path.join(__dirname, '..', 'docs');
20+
const ADMONITION_MAP = {
21+
NOTE: 'note',
22+
CAUTION: 'warning',
23+
RECOMMENDED: 'info',
24+
// Add more mappings here, e.g. 'CAUTION': 'caution', 'TIP': 'tip'
25+
};
26+
27+
function getAllMarkdownFiles(dir) {
28+
let results = [];
29+
const list = fs.readdirSync(dir);
30+
list.forEach(function(file) {
31+
const filePath = path.join(dir, file);
32+
const stat = fs.statSync(filePath);
33+
if (stat && stat.isDirectory()) {
34+
results = results.concat(getAllMarkdownFiles(filePath));
35+
} else if (file.endsWith('.md')) {
36+
results.push(filePath);
37+
}
38+
});
39+
return results;
40+
}
41+
42+
function extractTables(content) {
43+
const tables = [];
44+
let i = 0;
45+
content = content.replace(/((?:^\|.*\n?)+)/gm, match => {
46+
tables.push(match);
47+
return `__TABLE_BLOCK_${i++}__`;
48+
});
49+
return { content, tables };
50+
}
51+
52+
function restoreTables(content, tables) {
53+
tables.forEach((table, i) => {
54+
content = content.replace(`__TABLE_BLOCK_${i}__`, table);
55+
});
56+
return content;
57+
}
58+
59+
function convertNoteCaution(content) {
60+
// NOTE and CAUTION: bold only (**NOTE:**, **CAUTION:**)
61+
return content.replace(
62+
/(^|\r?\n)([ \t]*)\*\*(NOTE|CAUTION)\:\*\*[ \t]*([\s\S]*?)(?=(\r?\n\s*\r?\n|$))/gi,
63+
(match, p1, indent, type, text) => {
64+
const admonition = ADMONITION_MAP[type.toUpperCase()];
65+
const cleanedText = text.replace(/^\s+/gm, '');
66+
const blockLines = [
67+
`:::${admonition}`,
68+
...cleanedText.split('\n'),
69+
':::'
70+
];
71+
const block = blockLines.map(line => indent + line).join('\n');
72+
return `${p1}${block}\n`;
73+
}
74+
);
75+
}
76+
77+
function convertRecommended(content) {
78+
// RECOMMENDED: bold+italics only (**_RECOMMENDED:_** or __*RECOMMENDED:*__)
79+
return content.replace(
80+
/(^|\r?\n)([ \t]*)(\*\*|__)(\*|_)(RECOMMENDED:)\4\3[ \t]*([\s\S]*?)(?=(\r?\n\s*\r?\n|$))/gi,
81+
(match, p1, indent, bold, ital, type, text) => {
82+
const admonition = ADMONITION_MAP[type.replace(':', '').toUpperCase()];
83+
const cleanedText = text.replace(/^\s+/gm, '');
84+
const blockLines = [
85+
`:::${admonition}`,
86+
...cleanedText.split('\n'),
87+
':::'
88+
];
89+
const block = blockLines.map(line => indent + line).join('\n');
90+
return `${p1}${block}\n`;
91+
}
92+
);
93+
}
94+
95+
function convertRememberTips(content) {
96+
// Remember: italics only (*Remember,* or _Remember,_)
97+
return content.replace(
98+
/(^|\r?\n)([ \t]*)[\*_]Remember,[\*_][ \t]*([\s\S]*?)(?=(\r?\n\s*\r?\n|$))/g,
99+
(match, p1, indent, text) => {
100+
const cleanedText = text.replace(/^\s+/gm, '');
101+
const blockLines = [
102+
':::tip',
103+
`${indent}Remember,${cleanedText ? ' ' + cleanedText : ''}`.trimEnd(),
104+
':::'
105+
];
106+
const block = blockLines.map(line => indent + line).join('\n');
107+
return `${p1}${block}\n`;
108+
}
109+
);
110+
}
111+
112+
function convertAdmonitions(content) {
113+
content = convertNoteCaution(content);
114+
content = convertRecommended(content);
115+
content = convertRememberTips(content);
116+
return content;
117+
}
118+
119+
function processFile(filePath) {
120+
const content = fs.readFileSync(filePath, 'utf8');
121+
// Extract tables and replace with placeholders
122+
const { content: contentNoTables, tables } = extractTables(content);
123+
// Convert admonitions in non-table content
124+
let newContent = convertAdmonitions(contentNoTables);
125+
// Restore tables
126+
newContent = restoreTables(newContent, tables);
127+
if (newContent !== content) {
128+
fs.writeFileSync(filePath, newContent, 'utf8');
129+
console.log(`Updated: ${filePath}`);
130+
}
131+
}
132+
133+
function main() {
134+
const relInput = process.argv[2];
135+
if (!relInput) {
136+
console.error('Usage: node scripts/convert-admonitions.js <relative-folder>');
137+
process.exit(1);
138+
}
139+
const inputDir = path.join(DOCS_ROOT, relInput);
140+
if (!fs.existsSync(inputDir)) {
141+
console.error(`Directory does not exist: ${inputDir}`);
142+
process.exit(1);
143+
}
144+
const mdFiles = getAllMarkdownFiles(inputDir);
145+
mdFiles.forEach(processFile);
146+
}
147+
148+
if (require.main === module) {
149+
main();
150+
}

scripts/convert-table-lists.js

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env node
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
function insertBrBeforeNote(cell) {
6+
// Insert <br /> before any **NOTE:**, **CAUTION:**, or **_RECOMMENDED:_** not at the start of a line or after <br />
7+
// (Don't double-insert if already at start of line or after <br />)
8+
return cell
9+
.replace(/([^\n<])\*\*NOTE:\*\*/g, '$1<br />**NOTE:**')
10+
.replace(/([^\n<])\*\*CAUTION:\*\*/g, '$1<br />**CAUTION:**')
11+
.replace(/([^\n<])\*\*_RECOMMENDED:_\*\*/g, '$1<br />**_RECOMMENDED:_**');
12+
}
13+
14+
function convertDashListToUl(cell) {
15+
const trimmed = cell.trim();
16+
// If the cell starts with '- ', treat as a list (even if only one item)
17+
if (trimmed.startsWith('- ')) {
18+
const items = cell.split(/ - /).filter(Boolean).map(item => item.trim());
19+
if (items.length === 0) return insertBrBeforeNote(cell);
20+
// Remove leading dash from the first item
21+
items[0] = items[0].replace(/^- /, '').trim();
22+
let ul = '<ul>' + items.map(item => `<li>${item}</li>`).join('') + '</ul>';
23+
ul = insertBrBeforeNote(ul);
24+
return ul;
25+
}
26+
// Otherwise, if there are at least two '- ' items, treat as a list
27+
const dashCount = (cell.match(/(^| )- /g) || []).length;
28+
if (dashCount < 2) return insertBrBeforeNote(cell);
29+
let intro = '';
30+
let listPart = cell;
31+
const firstDash = cell.indexOf(' - ');
32+
if (firstDash !== -1) {
33+
intro = cell.slice(0, firstDash).trim();
34+
listPart = cell.slice(firstDash + 1);
35+
}
36+
const items = listPart.split(/ - /).filter(Boolean).map(item => item.trim()).filter(Boolean);
37+
if (items.length < 1) return insertBrBeforeNote(cell);
38+
// Remove leading dash from the first item if present
39+
items[0] = items[0].replace(/^- /, '').trim();
40+
let ul = '<ul>' + items.map(item => `<li>${item}</li>`).join('') + '</ul>';
41+
let result = intro ? `${intro} ${ul}` : ul;
42+
result = insertBrBeforeNote(result);
43+
return result;
44+
}
45+
46+
function splitRow(row) {
47+
let cells = row.split('|').map(cell => cell.trim());
48+
if (cells[0] === '') cells = cells.slice(1);
49+
if (cells[cells.length - 1] === '') cells = cells.slice(0, -1);
50+
return cells;
51+
}
52+
53+
function getCellMaxLineLength(cell) {
54+
return Math.max(...cell.split(/\r?\n/).map(line => line.length));
55+
}
56+
57+
function tableHasDashList(rows) {
58+
for (let i = 2; i < rows.length; i++) {
59+
for (const cell of rows[i]) {
60+
const trimmed = cell.trim();
61+
if (trimmed.startsWith('- ')) return true;
62+
if ((cell.match(/(^| )- /g) || []).length >= 2) return true;
63+
}
64+
}
65+
return false;
66+
}
67+
68+
function formatTable(table) {
69+
const rows = table.trim().split(/\r?\n/).map(splitRow);
70+
if (!tableHasDashList(rows)) {
71+
// No dash lists, but still apply NOTE br feature
72+
return rows.map(row =>
73+
'| ' + row.map(cell => insertBrBeforeNote(cell)).join(' | ') + ' |'
74+
).join('\n');
75+
}
76+
for (let i = 2; i < rows.length; i++) {
77+
rows[i] = rows[i].map(cell => convertDashListToUl(cell));
78+
}
79+
const colWidths = [];
80+
rows.forEach(row => row.forEach((cell, i) => {
81+
const maxLen = getCellMaxLineLength(cell);
82+
colWidths[i] = Math.max(colWidths[i] || 0, maxLen);
83+
}));
84+
const separatorRow = colWidths.map(w => '-'.repeat(w)).map(dash => dash.padEnd(dash.length, '-'));
85+
const paddedRows = rows.map((row, rowIdx) => {
86+
if (rowIdx === 1) {
87+
return separatorRow;
88+
}
89+
return row.map((cell, i) => {
90+
const lines = cell.split(/\r?\n/);
91+
return lines.map(line => line.padEnd(colWidths[i])).join('\n');
92+
});
93+
});
94+
return paddedRows.map(row =>
95+
'| ' + row.join(' | ') + ' |'
96+
).join('\n');
97+
}
98+
99+
function formatMarkdownTables(content) {
100+
const tableRegex = /((?:^\|.*(?:\r?\n|$))+)/gm;
101+
return content.replace(tableRegex, match => {
102+
const lines = match.trim().split(/\r?\n/);
103+
if (lines.length < 2) return match;
104+
const separator = lines[1].replace(/\|/g, '').trim();
105+
if (!/^[-: ]+$/.test(separator)) return match;
106+
return formatTable(match);
107+
});
108+
}
109+
110+
function processFile(file) {
111+
const content = fs.readFileSync(file, 'utf8');
112+
const formatted = formatMarkdownTables(content);
113+
fs.writeFileSync(file, formatted);
114+
console.log('Processed:', file);
115+
}
116+
117+
function processDir(dir) {
118+
fs.readdirSync(dir, { withFileTypes: true }).forEach(entry => {
119+
const fullPath = path.join(dir, entry.name);
120+
if (entry.isDirectory()) {
121+
processDir(fullPath);
122+
} else if (entry.isFile() && fullPath.endsWith('.md')) {
123+
processFile(fullPath);
124+
}
125+
});
126+
}
127+
128+
if (require.main === module) {
129+
const inputDir = process.argv[2];
130+
if (!inputDir) {
131+
console.error('Usage: node convert-table-lists.js <input-folder-relative-to-docs>');
132+
process.exit(1);
133+
}
134+
const docsRoot = path.join(__dirname, '..', 'docs');
135+
const targetDir = path.resolve(docsRoot, inputDir);
136+
processDir(targetDir);
137+
}

0 commit comments

Comments
 (0)