mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2025-03-29 04:23:34 +01:00
Optimized subfolder explorer
This commit is contained in:
parent
9232db4ef7
commit
8b0308589d
@ -1,72 +1,98 @@
|
|||||||
import os
|
import os
|
||||||
import re
|
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from .nav_utils import natural_sort_key, extract_headings_from_file, group_headings, sort_tree, MAX_HEADING_LEVEL, DEFAULT_MAX_NAV_DEPTH
|
from .nav_utils import extract_headings_from_file
|
||||||
|
MAX_HEADING_LEVEL = 0
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def collect_subfolder_tree(dir_path, base_url, current_depth, max_depth):
|
def collect_folder_tree(dir_path, base_url):
|
||||||
"""
|
"""
|
||||||
Recursively collects navigation items from subdirectories.
|
Recursively collects the folder tree starting from the given directory.
|
||||||
For each subfolder, it looks for a candidate file (prefer "index.rst" then "README.md").
|
|
||||||
Only subfolders with such a file will be included.
|
For each folder:
|
||||||
If a candidate is found, the first level‑1 heading from that file is used as the title;
|
- It is ignored if it is hidden.
|
||||||
if no heading is found, the folder name is used.
|
- If a representative file (index.rst/index.md or readme.md/readme.rst) exists,
|
||||||
The link is built pointing directly to the candidate file (by its base name) rather than the folder.
|
its first heading is used as the folder title.
|
||||||
Returns a list representing the subfolder tree.
|
- Folders without such a representative file are skipped.
|
||||||
|
- All Markdown and reStructuredText files (except the representative file)
|
||||||
|
are listed without sub-headings, using the first heading as their title.
|
||||||
"""
|
"""
|
||||||
items = []
|
# Ignore hidden directories
|
||||||
|
if os.path.basename(dir_path).startswith('.'):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# List all files in 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 ['index.rst', 'index.md', 'readme.md', 'readme.rst']:
|
||||||
|
for f in files:
|
||||||
|
if f.lower() == candidate:
|
||||||
|
rep_file = f
|
||||||
|
break
|
||||||
|
if rep_file:
|
||||||
|
break
|
||||||
|
|
||||||
|
# If no representative file, skip this folder
|
||||||
|
if not rep_file:
|
||||||
|
return None
|
||||||
|
|
||||||
|
rep_path = os.path.join(dir_path, rep_file)
|
||||||
|
# If MAX_HEADING_LEVEL is 0, use an effectively infinite level (e.g., 9999)
|
||||||
|
effective_max = MAX_HEADING_LEVEL if MAX_HEADING_LEVEL != 0 else 9999
|
||||||
|
headings = extract_headings_from_file(rep_path, max_level=effective_max)
|
||||||
|
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
|
||||||
|
files.remove(rep_file)
|
||||||
|
|
||||||
|
# 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=effective_max)
|
||||||
|
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()):
|
for item in sorted(os.listdir(dir_path), key=lambda s: s.lower()):
|
||||||
full_path = os.path.join(dir_path, item)
|
full_path = os.path.join(dir_path, item)
|
||||||
if os.path.isdir(full_path):
|
if os.path.isdir(full_path) and not item.startswith('.'):
|
||||||
candidate = None
|
subtree = collect_folder_tree(full_path, os.path.join(base_url, item))
|
||||||
for cand in ['index.rst', 'README.md']:
|
if subtree:
|
||||||
candidate_path = os.path.join(full_path, cand)
|
dir_items.append(subtree)
|
||||||
if os.path.isfile(candidate_path):
|
|
||||||
candidate = candidate_path
|
# Combine files and subdirectories as children of the current folder
|
||||||
break
|
children = file_items + dir_items
|
||||||
# Only include the folder if a candidate file was found.
|
|
||||||
if candidate:
|
return {
|
||||||
headings = extract_headings_from_file(candidate, max_level=MAX_HEADING_LEVEL)
|
'text': folder_title,
|
||||||
title = headings[0]['text'] if headings else item
|
'link': folder_link,
|
||||||
# Use the candidate file's base name as link target.
|
'children': children,
|
||||||
candidate_base = os.path.splitext(os.path.basename(candidate))[0]
|
'filename': os.path.basename(dir_path)
|
||||||
link = os.path.join(base_url, item, candidate_base)
|
}
|
||||||
entry = {
|
|
||||||
'level': 1,
|
|
||||||
'text': title,
|
|
||||||
'link': link,
|
|
||||||
'anchor': '',
|
|
||||||
'priority': 0,
|
|
||||||
'filename': item
|
|
||||||
}
|
|
||||||
if current_depth < max_depth:
|
|
||||||
children = collect_subfolder_tree(full_path, os.path.join(base_url, item), current_depth + 1, max_depth)
|
|
||||||
if children:
|
|
||||||
entry['children'] = children
|
|
||||||
items.append(entry)
|
|
||||||
return items
|
|
||||||
|
|
||||||
def add_local_subfolders(app, pagename, templatename, context, doctree):
|
def add_local_subfolders(app, pagename, templatename, context, doctree):
|
||||||
"""
|
"""
|
||||||
Collects a tree of subfolder navigation items from the current directory.
|
Sets the 'local_subfolders' context variable with the entire folder tree
|
||||||
For each subfolder, the title is determined by scanning for a candidate file
|
starting from app.srcdir.
|
||||||
(prefer "index.rst" then "README.md") and extracting its first level‑1 heading,
|
|
||||||
or using the folder name if none is found.
|
|
||||||
The resulting tree is stored in context['local_subfolders'].
|
|
||||||
"""
|
"""
|
||||||
srcdir = app.srcdir
|
root_dir = app.srcdir
|
||||||
directory = os.path.dirname(pagename)
|
folder_tree = collect_folder_tree(root_dir, '')
|
||||||
abs_dir = os.path.join(srcdir, directory)
|
context['local_subfolders'] = [folder_tree] if folder_tree else []
|
||||||
if not os.path.isdir(abs_dir):
|
|
||||||
logger.warning(f"Directory {abs_dir} not found for page {pagename}.")
|
|
||||||
context['local_subfolders'] = []
|
|
||||||
return
|
|
||||||
|
|
||||||
max_nav_depth = getattr(app.config, 'local_nav_max_depth', DEFAULT_MAX_NAV_DEPTH)
|
|
||||||
subfolder_tree = collect_subfolder_tree(abs_dir, directory, current_depth=0, max_depth=max_nav_depth)
|
|
||||||
sort_tree(subfolder_tree)
|
|
||||||
context['local_subfolders'] = subfolder_tree
|
|
||||||
|
|
||||||
def setup(app):
|
def setup(app):
|
||||||
app.connect('html-page-context', add_local_subfolders)
|
app.connect('html-page-context', add_local_subfolders)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user