mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-12-13 12:44:38 +00:00
Moved sphinx files
This commit is contained in:
130
docs/extensions/local_subfolders.py
Normal file
130
docs/extensions/local_subfolders.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import os
|
||||
from sphinx.util import logging
|
||||
from .nav_utils import extract_headings_from_file, MAX_HEADING_LEVEL
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CANDIDATES = ['index.rst', 'readme.md', 'main.rst']
|
||||
|
||||
def collect_folder_tree(dir_path, base_url):
|
||||
"""
|
||||
Recursively collects the folder tree starting from the given directory.
|
||||
|
||||
For each folder:
|
||||
- Hidden folders (names starting with a dot) are skipped.
|
||||
- A folder is processed only if it contains one of the representative files:
|
||||
index.rst, index.md, readme.md, or readme.rst.
|
||||
- The first heading of the representative file is used as the folder title.
|
||||
- The representative file is not listed as a file in the folder.
|
||||
- All other Markdown and reStructuredText files are listed without sub-headings,
|
||||
using their first heading as the file title.
|
||||
"""
|
||||
# Skip hidden directories
|
||||
if os.path.basename(dir_path).startswith('.'):
|
||||
return None
|
||||
|
||||
# List all files in the current directory with .md or .rst extension
|
||||
files = [f for f in os.listdir(dir_path)
|
||||
if os.path.isfile(os.path.join(dir_path, f))
|
||||
and (f.endswith('.md') or f.endswith('.rst'))]
|
||||
|
||||
# Find representative file for folder title using index or readme
|
||||
rep_file = None
|
||||
for candidate in CANDIDATES:
|
||||
for f in files:
|
||||
if f.lower() == candidate:
|
||||
rep_file = f
|
||||
break
|
||||
if rep_file:
|
||||
break
|
||||
|
||||
# Skip this folder if no representative file exists
|
||||
if not rep_file:
|
||||
return None
|
||||
|
||||
rep_path = os.path.join(dir_path, rep_file)
|
||||
headings = extract_headings_from_file(rep_path, max_level=MAX_HEADING_LEVEL)
|
||||
folder_title = headings[0]['text'] if headings else os.path.basename(dir_path)
|
||||
folder_link = os.path.join(base_url, os.path.splitext(rep_file)[0])
|
||||
|
||||
# Remove the representative file from the list to avoid duplication,
|
||||
# and filter out any additional "readme.md" or "index.rst" files.
|
||||
files.remove(rep_file)
|
||||
files = [f for f in files if f.lower() not in CANDIDATES]
|
||||
|
||||
# Process the remaining files in the current directory
|
||||
file_items = []
|
||||
for file in sorted(files, key=lambda s: s.lower()):
|
||||
file_path = os.path.join(dir_path, file)
|
||||
file_headings = extract_headings_from_file(file_path, max_level=MAX_HEADING_LEVEL)
|
||||
file_title = file_headings[0]['text'] if file_headings else file
|
||||
file_base = os.path.splitext(file)[0]
|
||||
file_link = os.path.join(base_url, file_base)
|
||||
file_items.append({
|
||||
'level': 1,
|
||||
'text': file_title,
|
||||
'link': file_link,
|
||||
'anchor': '',
|
||||
'priority': 1,
|
||||
'filename': file
|
||||
})
|
||||
|
||||
# Process subdirectories (ignoring hidden ones)
|
||||
dir_items = []
|
||||
for item in sorted(os.listdir(dir_path), key=lambda s: s.lower()):
|
||||
full_path = os.path.join(dir_path, item)
|
||||
if os.path.isdir(full_path) and not item.startswith('.'):
|
||||
subtree = collect_folder_tree(full_path, os.path.join(base_url, item))
|
||||
if subtree:
|
||||
dir_items.append(subtree)
|
||||
|
||||
# Combine files and subdirectories as children of the current folder
|
||||
children = file_items + dir_items
|
||||
|
||||
return {
|
||||
'text': folder_title,
|
||||
'link': folder_link,
|
||||
'children': children,
|
||||
'filename': os.path.basename(dir_path)
|
||||
}
|
||||
|
||||
def mark_current(node, active):
|
||||
"""
|
||||
Recursively mark nodes as current if the active page (pagename)
|
||||
matches the node's link or is a descendant of it.
|
||||
|
||||
The function sets node['current'] = True if:
|
||||
- The node's link matches the active page exactly, or
|
||||
- The active page begins with the node's link plus a separator (indicating a child).
|
||||
Additionally, if any child node is current, the parent is marked as current.
|
||||
"""
|
||||
is_current = False
|
||||
node_link = node.get('link', '').rstrip('/')
|
||||
active = active.rstrip('/')
|
||||
if node_link and (active == node_link or active.startswith(node_link + '/')):
|
||||
is_current = True
|
||||
|
||||
# Recurse into children if they exist
|
||||
children = node.get('children', [])
|
||||
for child in children:
|
||||
if mark_current(child, active):
|
||||
is_current = True
|
||||
|
||||
node['current'] = is_current
|
||||
return is_current
|
||||
|
||||
def add_local_subfolders(app, pagename, templatename, context, doctree):
|
||||
"""
|
||||
Sets the 'local_subfolders' context variable with the entire folder tree
|
||||
starting from app.srcdir, and marks the tree with the 'current' flag up
|
||||
to the active page.
|
||||
"""
|
||||
root_dir = app.srcdir
|
||||
folder_tree = collect_folder_tree(root_dir, '')
|
||||
if folder_tree:
|
||||
mark_current(folder_tree, pagename)
|
||||
context['local_subfolders'] = [folder_tree] if folder_tree else []
|
||||
|
||||
def setup(app):
|
||||
app.connect('html-page-context', add_local_subfolders)
|
||||
return {'version': '0.1', 'parallel_read_safe': True}
|
||||
Reference in New Issue
Block a user