Skip to content

Commit f1bb14e

Browse files
committed
add github action for autogenerating table of contents files
1 parent 53bfcce commit f1bb14e

File tree

3 files changed

+160
-43
lines changed

3 files changed

+160
-43
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# This GitHub Action is used for triggering updates of
2+
# the toc.json files present in any directory that
3+
# needs an automatically generated table of contents.
4+
5+
name: Generate Table of Contents files
6+
7+
on:
8+
push:
9+
branches: ["main"]
10+
schedule:
11+
- cron: '0 0 * * *' # Run daily at midnight
12+
13+
jobs:
14+
generate_toc_formats:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v3
18+
19+
# Step 1 - Cache directory contents
20+
- name: Cache directory contents # Generating the TOC if there are no files added/removed is wasteful
21+
uses: actions/cache@v3
22+
with:
23+
path: |
24+
docs/en/interfaces/formats
25+
key: toc-cache-${{ hashFiles('docs/en/interfaces/formats/**')}}
26+
27+
# Step 2 - Check if Cache was hit (files have not changed) generate the TOC
28+
- name: Generate Format Table Of Contents
29+
if: steps.Cache.outputs.cache-hit != 'true' # If there's no changes
30+
id: toc_gen
31+
run: |
32+
# Step 2.1 - Setup Python
33+
- name: Set up Python
34+
uses: actions/setup-python@v3
35+
with:
36+
python-version: '3.x'
37+
38+
# Step 2.2: Install Python dependencies
39+
- name: Install dependencies
40+
run: |
41+
python -m pip install --upgrade pip
42+
pip install -r 'scripts/knowledgebase-checker/requirements.txt'
43+
44+
# Step 2.3: Run scripts to generate TOCs:
45+
- name: Generate TOCs
46+
run: |
47+
./scripts/table-of-contents-generator/toc_gen.py --kb-dir="docs/en/interfaces/formats" --single-toc
48+
continue-on-error: true
49+
50+
# Step 6: Fail the build if any script returns exit code 1
51+
- name: Check exit code
52+
run: |
53+
if [[ "${{ steps.toc_gen.outcome }}" == "failure" ]]; then
54+
echo "Ran into trouble generating a table of contents. See the logs for details."
55+
exit 1
56+
fi

scripts/autogenerate_table_of_contents.py

Lines changed: 0 additions & 43 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
"""
2+
This script can be used to automatically generate a table of contents (JSON file) from the markdown files in a directory,
3+
or multiple directories.
4+
"""
5+
6+
#!/usr/bin/env python3
7+
8+
import json
9+
import os
10+
import argparse
11+
import sys
12+
13+
def parse_args() -> argparse.Namespace:
14+
parser = argparse.ArgumentParser(
15+
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
16+
description="Script to generate .json table of contents from YAML frontmatter title, description and slug",
17+
)
18+
parser.add_argument(
19+
"--single-toc",
20+
action="store_true",
21+
help="Generates a single TOC for all files in all sub-directories of provided directory. By default, generates TOC per folder.",
22+
)
23+
parser.add_argument(
24+
"--dir",
25+
help="Path to a folder containing markdown (.md, .mdx) documents containing YAML with title, description, slug."
26+
)
27+
return parser.parse_args()
28+
29+
def extract_title_description_slug(filename):
30+
with open(filename, "r") as f:
31+
lines = f.readlines()
32+
33+
title, description, slug = None, None, None
34+
for line in lines:
35+
if line.startswith("title:"):
36+
title = line.strip().split(": ")[1]
37+
if line.startswith("description:"):
38+
description = line.strip().split(": ")[1]
39+
elif line.startswith("slug:"):
40+
slug = line.strip().split(": ")[1]
41+
if title and slug and description:
42+
return {"title": title, "description": description, "slug": slug}
43+
return None
44+
45+
def walk_dirs(root_dir):
46+
for root, dirs, files in os.walk(root_dir):
47+
yield root
48+
49+
def write_to_file(json_array, output_path):
50+
try:
51+
os.makedirs(os.path.dirname(output_path), exist_ok=True) # Create directories if they don't exist
52+
with open(output_path, "w") as f:
53+
json.dump(json_array, f, indent=4)
54+
f.write('\n')
55+
except OSError as e:
56+
if e.errno == 21:
57+
print(f"Directory already exists: {e}")
58+
else:
59+
print(f"An error occurred creating directory: {e}")
60+
61+
def main():
62+
63+
# Extract script arguments
64+
args = parse_args()
65+
root_dir = args.dir
66+
if root_dir is None:
67+
print("Please provide a directory with argument --dir")
68+
sys.exit(1)
69+
70+
if args.single_toc:
71+
json_items = [] # single list for all directories
72+
73+
for directory in walk_dirs(root_dir): # Walk directories
74+
75+
if not args.single_toc:
76+
json_items = [] # new list for each directory
77+
78+
for filename in os.listdir(directory): # for each directory
79+
full_path = os.path.join(directory, filename)
80+
if os.path.isfile(full_path) is False:
81+
continue
82+
else:
83+
# index.md is ignored as we expect this to be the page for the table of contents
84+
if (filename.endswith(".md") or filename.endswith(".mdx")) and filename != "index.md":
85+
result = extract_title_description_slug(full_path)
86+
if result is not None:
87+
json_items.append(result)
88+
89+
if not args.single_toc:
90+
json_array = sorted(json_items, key=lambda x: x.get("title"))
91+
92+
# don't write toc.json for empty folders
93+
if len(json_items) != 0:
94+
write_to_file(json_items, directory+"/toc.json")
95+
96+
if args.single_toc:
97+
json_array = sorted(json_items, key=lambda x: x.get("title"))
98+
# don't write toc.json for empty folders
99+
if len(json_items) != 0:
100+
write_to_file(json_items, root_dir+"/toc.json")
101+
102+
if __name__ == "__main__":
103+
main()
104+

0 commit comments

Comments
 (0)