@datalackey/update-markdown-toc 1.0.1 → 1.0.2
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/README.md +18 -1
- package/bin/parseCli.js +4 -0
- package/bin/update-markdown-toc.js +33 -20
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<!-- TOC:START -->
|
|
4
4
|
- [update-markdown-toc](#update-markdown-toc)
|
|
5
5
|
- [Introduction](#introduction)
|
|
6
|
+
- [Why not Some Other Markdown TOC Generator ?](#why-not-some-other-markdown-toc-generator-)
|
|
6
7
|
- [Installation](#installation)
|
|
7
8
|
- [Usage](#usage)
|
|
8
9
|
- [Using npx (recommended)](#using-npx-recommended)
|
|
@@ -29,6 +30,18 @@ A Node.js command-line **documentation helper** which automatically:
|
|
|
29
30
|
- avoids gratuitous reformatting or changes of any kind outside of regions marked by the aforementioned [TOC markers](#toc-markers)
|
|
30
31
|
- avoids updating files when the generated TOC is already correct
|
|
31
32
|
- provides a `--check` mode which flags Markdown files with stale TOCs (intended for CI)
|
|
33
|
+
- generates TOC links with GitHub’s Markdown renderer.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Why not Some Other Markdown TOC Generator ?
|
|
38
|
+
|
|
39
|
+
Most Markdown TOC tools (e.g., [markdown-toc](https://github.com/jonschlinkert/markdown-toc),
|
|
40
|
+
[md-toc-cli](https://github.com/eugene-khyst/md-toc-cli))
|
|
41
|
+
operate on a **single file at a time**, a mode which our tool also supports.
|
|
42
|
+
But we are aware of no other alternative which supports our tool's main distinguishing feature:
|
|
43
|
+
the ability to search for, check and update all Markdown documents within an entire folder hierarchy.
|
|
44
|
+
|
|
32
45
|
|
|
33
46
|
|
|
34
47
|
|
|
@@ -103,6 +116,10 @@ Options:
|
|
|
103
116
|
-h, --help Show this help message and exit
|
|
104
117
|
```
|
|
105
118
|
|
|
119
|
+
When using --check, a target file or a recursive folder must be specified
|
|
120
|
+
explicitly. Unlike normal operation, --check does not default to README.md.
|
|
121
|
+
|
|
122
|
+
|
|
106
123
|
## TOC Markers
|
|
107
124
|
|
|
108
125
|
The tool operates only on files containing **both** start and end markers,
|
|
@@ -184,7 +201,7 @@ In the case of the latter mode, we assume some files may not yet have TOC marker
|
|
|
184
201
|
#### Single-File Processing (Strict Mode)
|
|
185
202
|
|
|
186
203
|
|
|
187
|
-
When a single Markdown file is explicitly specified (or when the default README.md is used),
|
|
204
|
+
When a single Markdown file is explicitly specified (or when the default README.md is used and --check not specified),
|
|
188
205
|
the tool operates in strict mode.
|
|
189
206
|
In this mode, any of the following conditions cause an immediate error and a non-zero exit code:
|
|
190
207
|
|
package/bin/parseCli.js
CHANGED
|
@@ -89,5 +89,9 @@ function printHelp() {
|
|
|
89
89
|
-q, --quiet Suppress all non-error output
|
|
90
90
|
-d, --debug Print debug diagnostics to stderr
|
|
91
91
|
-h, --help Show this help message and exit
|
|
92
|
+
|
|
93
|
+
When using --check, a target file or a recursive folder must be specified
|
|
94
|
+
explicitly. Unlike normal operation, --check does not default to README.md.
|
|
95
|
+
|
|
92
96
|
`);
|
|
93
97
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import { parseCli } from "./parseCli.js";
|
|
6
|
+
import GithubSlugger from "github-slugger";
|
|
6
7
|
|
|
7
8
|
/* ============================================================
|
|
8
9
|
* Constants
|
|
@@ -39,6 +40,10 @@ function debugLog(msg) {
|
|
|
39
40
|
* Helpers
|
|
40
41
|
* ============================================================ */
|
|
41
42
|
|
|
43
|
+
function detectLineEnding(text) {
|
|
44
|
+
return text.includes("\r\n") ? "\r\n" : "\n";
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
function collectMarkdownFiles(dir) {
|
|
43
48
|
const results = [];
|
|
44
49
|
|
|
@@ -56,6 +61,8 @@ function collectMarkdownFiles(dir) {
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
function generateTOC(content) {
|
|
64
|
+
const lineEnding = detectLineEnding(content);
|
|
65
|
+
|
|
59
66
|
const hasStart = content.includes(START);
|
|
60
67
|
const hasEnd = content.includes(END);
|
|
61
68
|
|
|
@@ -72,28 +79,27 @@ function generateTOC(content) {
|
|
|
72
79
|
const startIndex = content.indexOf(START);
|
|
73
80
|
const endIndex = content.indexOf(END);
|
|
74
81
|
|
|
75
|
-
const before = content.slice(0, startIndex
|
|
76
|
-
const after = content.slice(endIndex);
|
|
82
|
+
const before = content.slice(0, startIndex);
|
|
83
|
+
const after = content.slice(endIndex + END.length);
|
|
77
84
|
|
|
85
|
+
// Preserve exactly one line boundary for parsing
|
|
78
86
|
const contentWithoutTOC =
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
before.replace(/\s*$/, "") +
|
|
88
|
+
lineEnding +
|
|
89
|
+
after.replace(/^\s*/, "");
|
|
81
90
|
|
|
82
|
-
const lines = contentWithoutTOC.split(
|
|
91
|
+
const lines = contentWithoutTOC.split(lineEnding);
|
|
83
92
|
const headings = [];
|
|
84
93
|
|
|
94
|
+
const slugger = new GithubSlugger();
|
|
95
|
+
|
|
85
96
|
for (const line of lines) {
|
|
86
97
|
const m = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
87
98
|
if (!m) continue;
|
|
88
99
|
|
|
89
100
|
const level = m[1].length;
|
|
90
101
|
const title = m[2].trim();
|
|
91
|
-
|
|
92
|
-
const anchor = title
|
|
93
|
-
.toLowerCase()
|
|
94
|
-
.replace(/[^\w\s-]/g, "")
|
|
95
|
-
.replace(/\s/g, "-")
|
|
96
|
-
.replace(/^-|-$/g, "");
|
|
102
|
+
const anchor = slugger.slug(title);
|
|
97
103
|
|
|
98
104
|
headings.push({ level, title, anchor });
|
|
99
105
|
}
|
|
@@ -108,10 +114,22 @@ function generateTOC(content) {
|
|
|
108
114
|
return `${indent}- [${h.title}](#${h.anchor})`;
|
|
109
115
|
});
|
|
110
116
|
|
|
111
|
-
const tocBlock =
|
|
112
|
-
|
|
117
|
+
const tocBlock =
|
|
118
|
+
lineEnding +
|
|
119
|
+
tocLines.join(lineEnding) +
|
|
120
|
+
lineEnding;
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
before +
|
|
124
|
+
START +
|
|
125
|
+
tocBlock +
|
|
126
|
+
END +
|
|
127
|
+
after
|
|
128
|
+
);
|
|
113
129
|
}
|
|
114
130
|
|
|
131
|
+
|
|
132
|
+
|
|
115
133
|
/* ============================================================
|
|
116
134
|
* File processing
|
|
117
135
|
* ============================================================ */
|
|
@@ -135,7 +153,7 @@ function processFile(filePath) {
|
|
|
135
153
|
debugLog("result: skipped (no markers)");
|
|
136
154
|
return { status: "skipped" };
|
|
137
155
|
}
|
|
138
|
-
throw err;
|
|
156
|
+
throw err;
|
|
139
157
|
}
|
|
140
158
|
throw err;
|
|
141
159
|
}
|
|
@@ -165,15 +183,11 @@ function maybePrintStatus(status, filePath) {
|
|
|
165
183
|
if (quiet) return;
|
|
166
184
|
|
|
167
185
|
if (checkMode) {
|
|
168
|
-
// In --check mode we ALWAYS report stale files (unless --quiet).
|
|
169
|
-
// This makes CI output actionable without requiring --verbose.
|
|
170
186
|
if (status === "stale") {
|
|
171
187
|
console.log(`Stale: ${filePath}`);
|
|
172
188
|
return;
|
|
173
189
|
}
|
|
174
190
|
|
|
175
|
-
// In recursive mode, files without markers are intentionally ignored.
|
|
176
|
-
// They should not cause failures and do not need reporting by default.
|
|
177
191
|
if (status === "skipped") {
|
|
178
192
|
if (verbose) {
|
|
179
193
|
console.log(`Skipped (no markers): ${filePath}`);
|
|
@@ -207,7 +221,6 @@ function maybePrintStatus(status, filePath) {
|
|
|
207
221
|
}
|
|
208
222
|
}
|
|
209
223
|
|
|
210
|
-
|
|
211
224
|
/* ============================================================
|
|
212
225
|
* Execution
|
|
213
226
|
* ============================================================ */
|
|
@@ -227,7 +240,7 @@ if (recursivePath) {
|
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
files = collectMarkdownFiles(resolved);
|
|
230
|
-
files.sort();
|
|
243
|
+
files.sort();
|
|
231
244
|
} else {
|
|
232
245
|
const resolved = path.resolve(
|
|
233
246
|
process.cwd(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@datalackey/update-markdown-toc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Auto-generate Table of Contents for a Markdown file (or files, recursively from a top level folder)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"homepage": "https://github.com/datalackey/build-tools/tree/main/javascript/update-markdown-toc",
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"github-slugger": "^2.0.0",
|
|
40
41
|
"ts-dedent": "^2.2.0"
|
|
41
42
|
}
|
|
42
43
|
}
|