Files

180 lines
6.8 KiB
Python

"""
File writer module for sphinx-llms-txt.
"""
from pathlib import Path
from typing import Any, Dict, List, Tuple, Union
from sphinx.application import Sphinx
from sphinx.util import logging
logger = logging.getLogger(__name__)
class FileWriter:
"""Handles writing processed content to output files."""
def __init__(self, config: Dict[str, Any], outdir: str = None, app: Sphinx = None):
self.config = config
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:
"""Write the combined content to a file.
Args:
content_parts: List of content strings to combine
output_path: Path to write the output file
total_line_count: Total number of lines in the content
Returns:
True if successful, False otherwise
"""
try:
with open(output_path, "w", encoding="utf-8") as f:
f.write("\n".join(content_parts))
logger.info(
f"sphinx-llms-txt: Created {output_path} with {len(content_parts)}"
f" sources and {total_line_count} lines"
)
return True
except Exception as e:
logger.error(f"sphinx-llms-txt: Error writing combined sources file: {e}")
return False
def write_verbose_info_to_file(
self,
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.
Args:
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
"""
if not self.outdir:
logger.warning(
"sphinx-llms-txt: Cannot write verbose info to file: outdir not set"
)
return False
output_path = Path(self.outdir) / self.config.get("llms_txt_filename")
try:
with open(output_path, "w", encoding="utf-8") as f:
project_name = "llms-txt Summary"
# First priority: use title from config if available
if self.config.get("llms_txt_title"):
project_name = self.config.get("llms_txt_title")
# Second priority: use project name from Sphinx app if available
elif (
self.app
and hasattr(self.app, "config")
and hasattr(self.app.config, "project")
):
project_name = self.app.config.project
f.write(f"# {project_name}\n\n")
# Add description if available
description = self.config.get("llms_txt_summary", "")
if description:
# Trim leading and trailing whitespace
description = description.strip()
if description:
# Only add blockquote if description is not empty
# Replace newlines with newline + blockquote marker to maintain
# blockquote formatting
description = description.replace("\n", "\n> ")
f.write(f"> {description}\n\n")
f.write("## Docs\n\n")
# Get base URL from config
base_url = self.config.get("html_baseurl", "/")
# Ensure base_url ends with a trailing slash
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, suffix = item
else:
docname = item
suffix = None
title = page_titles.get(docname, docname)
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
except Exception as e:
logger.error(f"sphinx-llms-txt: Error writing verbose info to file: {e}")
return False