Skip to content

Commit 1ae8408

Browse files
feat: Generate README table of contents (#137)
* Test references * Test * Test indentation * Test indentation * Add angle brackets * Update target * Trailing underscore * Try lowercase * Try nested contents * Try unordered list * Test without new line * Add more contents * Add new lines * Add script for generating TOC * Generate TOC * Add generate TOC to postgenerate script * Fix the hyperlinks produced by the generate TOC script * Improve script's readability * More readability improvements * Update package.json Co-authored-by: Evan Sosenko <evan@getseam.com> --------- Co-authored-by: Evan Sosenko <evan@getseam.com>
1 parent a561bbf commit 1ae8408

File tree

3 files changed

+148
-0
lines changed

3 files changed

+148
-0
lines changed

README.rst

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,61 @@ accurate and fully typed.
2626

2727
.. _seam_home: https://www.seam.co
2828

29+
Contents
30+
--------
31+
32+
* `Installation <Installation_>`_
33+
34+
* `Usage <Usage_>`_
35+
36+
* `Examples <Examples_>`_
37+
38+
* `List devices <List devices_>`_
39+
40+
* `Unlock a door <Unlock a door_>`_
41+
42+
* `Authentication Method <Authentication Method_>`_
43+
44+
* `API Key <API Key_>`_
45+
46+
* `Personal Access Token <Personal Access Token_>`_
47+
48+
* `Action Attempts <Action Attempts_>`_
49+
50+
* `Interacting with Multiple Workspaces <Interacting with Multiple Workspaces_>`_
51+
52+
* `Webhooks <Webhooks_>`_
53+
54+
* `Advanced Usage <Advanced Usage_>`_
55+
56+
* `Setting the endpoint <Setting the endpoint_>`_
57+
58+
* `Development and Testing <Development and Testing_>`_
59+
60+
* `Quickstart <Quickstart_>`_
61+
62+
* `Source Code <Source Code_>`_
63+
64+
* `Requirements <Requirements_>`_
65+
66+
* `Tests <Tests_>`_
67+
68+
* `Publishing <Publishing_>`_
69+
70+
* `Automatic <Automatic_>`_
71+
72+
* `Manual <Manual_>`_
73+
74+
* `GitHub Actions <GitHub Actions_>`_
75+
76+
* `Secrets for Optional GitHub Actions <Secrets for Optional GitHub Actions_>`_
77+
78+
* `Contributing <Contributing_>`_
79+
80+
* `License <License_>`_
81+
82+
* `Warranty <Warranty_>`_
83+
2984
Installation
3085
------------
3186

generate-readme-toc.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import fs from 'fs/promises'
2+
3+
const README_FILE = 'README.rst'
4+
const CONTENTS_HEADING = 'Contents'
5+
const CONTENTS_UNDERLINE = '--------'
6+
const TOC_INDENT_LEVELS = {
7+
'-': 1,
8+
'~': 2,
9+
'^': 3,
10+
}
11+
const HEADING_UNDERLINE_REGEX = /^[-~^]+$/
12+
13+
async function generateTableOfContents(content) {
14+
const lines = content.split('\n')
15+
const headings = []
16+
let contentsHeadingIndex = -1
17+
let parsingHeadings = false
18+
19+
for (let i = 0; i < lines.length; i++) {
20+
const line = lines[i].trim()
21+
const nextLine = lines[i + 1]?.trim() || ''
22+
23+
if (line === CONTENTS_HEADING && nextLine.startsWith(CONTENTS_UNDERLINE)) {
24+
contentsHeadingIndex = i
25+
parsingHeadings = true // Start parsing headings after the "Contents" section
26+
i++ // Skip the underline
27+
continue
28+
}
29+
30+
if (
31+
parsingHeadings &&
32+
nextLine &&
33+
nextLine.match(HEADING_UNDERLINE_REGEX)
34+
) {
35+
const level = TOC_INDENT_LEVELS[nextLine[0]] || 0
36+
37+
if (level > 0 && line) {
38+
headings.push({ text: line, level })
39+
}
40+
41+
i++
42+
}
43+
}
44+
45+
// Generate table of contents
46+
const toc = ['']
47+
headings.forEach((heading) => {
48+
const indent = ' '.repeat(heading.level - 1)
49+
50+
toc.push(`${indent}* \`${heading.text} <${heading.text}_>\`_`)
51+
toc.push('')
52+
})
53+
54+
return { toc, contentsHeadingIndex }
55+
}
56+
57+
function findTocEndIndex(lines, startIndex) {
58+
return lines.findIndex(
59+
(line, index) =>
60+
index > startIndex + 2 &&
61+
!line.trim().startsWith('*') &&
62+
line.trim() !== '',
63+
)
64+
}
65+
66+
async function updateReadme() {
67+
try {
68+
const content = await fs.readFile(README_FILE, 'utf8')
69+
const { toc, contentsHeadingIndex } = await generateTableOfContents(content)
70+
71+
if (contentsHeadingIndex === -1) {
72+
throw new Error('Contents heading not found in the README.')
73+
}
74+
75+
const lines = content.split('\n')
76+
const tocEndIndex = findTocEndIndex(lines, contentsHeadingIndex)
77+
78+
const newContent = [
79+
...lines.slice(0, contentsHeadingIndex + 2), // Include content before "Contents" heading, the heading itself and its underline
80+
...toc,
81+
...lines.slice(tocEndIndex),
82+
].join('\n')
83+
84+
await fs.writeFile(README_FILE, newContent)
85+
console.log(`Table of Contents has been updated in ${README_FILE}`)
86+
} catch (error) {
87+
console.error('Error updating README:', error.message)
88+
process.exit(1)
89+
}
90+
}
91+
92+
updateReadme()

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"type": "module",
55
"scripts": {
66
"generate": "node generate-routes.js",
7+
"pregenerate": "node generate-readme-toc.js",
78
"postgenerate": "make format",
89
"format": "prettier --write --ignore-path .gitignore .",
910
"start": "fake-seam-connect --seed"

0 commit comments

Comments
 (0)