Support customizable URI templates in llms.txt (#48)

This commit is contained in:
Jared Dillard
2025-10-27 17:41:04 -07:00
committed by GitHub
parent e2a80faf04
commit 5bfbb0168f
9 changed files with 298 additions and 5 deletions
+3 -1
View File
@@ -21,7 +21,7 @@ from .manager import LLMSFullManager
from .processor import DocumentProcessor
from .writer import FileWriter
__version__ = "0.6.0"
__version__ = "0.7.0"
# Export classes needed by tests
__all__ = [
@@ -85,6 +85,7 @@ def build_finished(app: Sphinx, exception):
config = {
"llms_txt_file": app.config.llms_txt_file,
"llms_txt_filename": app.config.llms_txt_filename,
"llms_txt_uri_template": app.config.llms_txt_uri_template,
"llms_txt_title": app.config.llms_txt_title,
"llms_txt_summary": summary,
"llms_txt_full_file": app.config.llms_txt_full_file,
@@ -115,6 +116,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value("llms_txt_file", True, "env")
app.add_config_value("llms_txt_filename", "llms.txt", "env")
app.add_config_value("llms_txt_uri_template", None, "env")
app.add_config_value("llms_txt_full_file", True, "env")
app.add_config_value("llms_txt_full_filename", "llms-full.txt", "env")
app.add_config_value("llms_txt_full_max_size", None, "env")
+7 -1
View File
@@ -223,6 +223,7 @@ class LLMSFullManager:
filtered_page_order,
self.collector.page_titles,
0, # No line count since no llms-full.txt
sources_dir,
)
# Only warn if user explicitly wants llms-full.txt
@@ -523,6 +524,7 @@ class LLMSFullManager:
filtered_page_order,
self.collector.page_titles,
total_line_count,
sources_dir,
)
return
elif action == "note":
@@ -536,6 +538,7 @@ class LLMSFullManager:
filtered_page_order,
self.collector.page_titles,
total_line_count,
sources_dir,
)
return
elif action == "keep":
@@ -554,7 +557,10 @@ class LLMSFullManager:
if success and self.config.get("llms_txt_file"):
filtered_page_order = self._filter_ignored_pages(page_order)
self.writer.write_verbose_info_to_file(
filtered_page_order, self.collector.page_titles, total_line_count
filtered_page_order,
self.collector.page_titles,
total_line_count,
sources_dir,
)
def _read_source_file(self, file_path: Path, docname: str) -> Tuple[str, int]:
+63 -2
View File
@@ -19,6 +19,42 @@ class FileWriter:
self.outdir = outdir
self.app = app
def _resolve_uri_template(self, sources_dir: Path = None) -> str:
"""Resolve which URI template to use based on configuration and sources_dir.
Args:
sources_dir: Path to _sources directory (None if not found)
Returns:
The template string to use for generating URIs
"""
# If custom template exists
custom_template = self.config.get("llms_txt_uri_template")
if custom_template:
# Validate user's template by checking for valid variable names
try:
# Try formatting with test valid values to validate syntax
test_values = {
"base_url": "http://example.com/",
"docname": "test",
"suffix": ".rst",
"sourcelink_suffix": ".txt",
}
custom_template.format(**test_values)
return custom_template
except (KeyError, ValueError) as e:
logger.warning(
f"sphinx-llms-txt: Invalid llms_txt_uri_template: {e}. "
f"Falling back to default."
)
# Else, use one of the default templates
if sources_dir:
return "{base_url}_sources/{docname}{suffix}{sourcelink_suffix}"
else:
return "{base_url}{docname}.html"
def write_combined_file(
self, content_parts: List[str], output_path: Path, total_line_count: int
) -> bool:
@@ -50,6 +86,7 @@ class FileWriter:
page_order: Union[List[str], List[Tuple[str, str]]],
page_titles: Dict[str, str],
total_line_count: int = 0,
sources_dir: Path = None,
) -> bool:
"""Write summary information to the llms.txt file.
@@ -57,6 +94,7 @@ class FileWriter:
page_order: Ordered list of document names or (docname, suffix) tuples
page_titles: Dictionary mapping docnames to titles
total_line_count: Total number of lines in the combined content
sources_dir: Path to _sources directory (None if not found)
Returns:
True if successful, False otherwise
@@ -102,14 +140,37 @@ class FileWriter:
if not base_url.endswith("/"):
base_url += "/"
# Get sourcelink suffix from Sphinx config
sourcelink_suffix = ""
if self.app and hasattr(self.app.config, "html_sourcelink_suffix"):
sourcelink_suffix = self.app.config.html_sourcelink_suffix
# Handle empty string case specially
if sourcelink_suffix == "":
sourcelink_suffix = "" # Keep it empty
elif not sourcelink_suffix.startswith("."):
sourcelink_suffix = "." + sourcelink_suffix
# Resolve which template to use
uri_template = self._resolve_uri_template(sources_dir)
for item in page_order:
# Handle both old format (str) and new format (tuple)
if isinstance(item, tuple):
docname, _ = item
docname, suffix = item
else:
docname = item
suffix = None
title = page_titles.get(docname, docname)
f.write(f"- [{title}]({base_url}{docname}.html)\n")
uri = uri_template.format(
base_url=base_url,
docname=docname,
suffix=suffix or "",
sourcelink_suffix=sourcelink_suffix,
)
f.write(f"- [{title}]({uri})\n")
logger.info(f"sphinx-llms-txt: created {output_path}")
return True