Add support for page and block level ignores (#33)

This commit is contained in:
Jared Dillard
2025-08-09 01:32:13 -07:00
committed by GitHub
parent b4cab5ab52
commit ebd0e13594
12 changed files with 471 additions and 31 deletions
+7
View File
@@ -33,6 +33,13 @@ def doctree_resolved(app: Sphinx, doctree, docname: str):
"""Called when a docname has been resolved to a document."""
global _root_first_paragraph
# Check for llms-txt-ignore metadata at the page level
if hasattr(app.env, "metadata") and docname in app.env.metadata:
metadata = app.env.metadata[docname]
if metadata.get("llms-txt-ignore", "").lower() in ("true", "1", "yes"):
_manager.mark_page_ignored(docname)
return
# Extract title from the document
title = None
# findall() returns a generator, convert to list to check if it has elements
+45 -4
View File
@@ -5,7 +5,7 @@ Main manager module for sphinx-llms-txt.
import glob
import subprocess
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Tuple, Union
from sphinx.application import Sphinx
from sphinx.environment import BuildEnvironment
@@ -129,6 +129,7 @@ class LLMSFullManager:
self.srcdir: Optional[str] = None
self.outdir: Optional[str] = None
self.app: Optional[Sphinx] = None
self.ignored_pages: set = set()
def set_master_doc(self, master_doc: str):
"""Set the master document name."""
@@ -144,6 +145,27 @@ class LLMSFullManager:
"""Update the title for a page."""
self.collector.update_page_title(docname, title)
def mark_page_ignored(self, docname: str):
"""Mark a page as ignored due to llms-txt-ignore metadata."""
self.ignored_pages.add(docname)
def _filter_ignored_pages(
self, page_order: Union[List[str], List[Tuple[str, str]]]
) -> Union[List[str], List[Tuple[str, str]]]:
"""Filter out ignored pages from page_order."""
filtered_pages = []
for item in page_order:
# Handle both old format (str) and new format (tuple)
if isinstance(item, tuple):
docname, _ = item
else:
docname = item
if docname not in self.ignored_pages:
filtered_pages.append(item)
return filtered_pages
def set_config(self, config: Dict[str, Any]):
"""Set configuration options."""
self.config = config
@@ -286,6 +308,11 @@ class LLMSFullManager:
should_abort_early = size_policy_action in ["skip", "note"]
for docname, _ in page_order:
# Skip pages marked as ignored
if docname in self.ignored_pages:
logger.debug(f"sphinx-llms-txt: Skipping ignored page: {docname}")
continue
if docname in docname_to_file:
file_path = docname_to_file[docname]
content, line_count = self._read_source_file(file_path, docname)
@@ -383,6 +410,13 @@ class LLMSFullManager:
if docname is None:
continue
# Skip pages marked as ignored
if docname in self.ignored_pages:
logger.debug(
f"sphinx-llms-txt: Skipping ignored remaining file: {docname}"
)
continue
# Skip excluded docnames
if exclude_patterns and any(
self.collector._match_exclude_pattern(docname, pattern)
@@ -468,8 +502,11 @@ class LLMSFullManager:
logger.info(f"sphinx-llms-txt: Skipping {filename} generation")
# Log summary information if requested
if self.config.get("llms_txt_file"):
filtered_page_order = self._filter_ignored_pages(page_order)
self.writer.write_verbose_info_to_file(
page_order, self.collector.page_titles, total_line_count
filtered_page_order,
self.collector.page_titles,
total_line_count,
)
return
elif action == "note":
@@ -478,8 +515,11 @@ class LLMSFullManager:
# Log summary information if requested
if self.config.get("llms_txt_file"):
filtered_page_order = self._filter_ignored_pages(page_order)
self.writer.write_verbose_info_to_file(
page_order, self.collector.page_titles, total_line_count
filtered_page_order,
self.collector.page_titles,
total_line_count,
)
return
elif action == "keep":
@@ -496,8 +536,9 @@ class LLMSFullManager:
# Log summary information if requested
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(
page_order, self.collector.page_titles, total_line_count
filtered_page_order, self.collector.page_titles, total_line_count
)
def _read_source_file(self, file_path: Path, docname: str) -> Tuple[str, int]:
+37 -1
View File
@@ -44,7 +44,10 @@ class DocumentProcessor:
Returns:
Processed content with directives properly resolved
"""
# First process include directives
# First process llms-txt-ignore blocks
content = self._process_ignore_blocks(content)
# Then process include directives
content = self._process_includes(content, source_path)
# Then process path directives (image, figure, etc.)
@@ -337,3 +340,36 @@ class DocumentProcessor:
# Replace all includes with their content
processed_content = include_pattern.sub(replace_include, content)
return processed_content
def _process_ignore_blocks(self, content: str) -> str:
"""Process llms-txt-ignore-start/end blocks by removing their content.
Args:
content: The source content to process
Returns:
Processed content with ignore blocks removed
"""
# Process ignore blocks iteratively to handle nested cases correctly
while True:
# Pattern to match ignore blocks - handles whitespace and indentation
ignore_pattern = re.compile(
r"^\s*\.\.\s+llms-txt-ignore-start\s*\n" # Start directive line
r"(.*?)" # Content to ignore (non-greedy)
r"^\s*\.\.\s+llms-txt-ignore-end\s*$", # End directive line
re.MULTILINE | re.DOTALL,
)
# Find and remove one ignore block at a time
match = ignore_pattern.search(content)
if not match:
break
# Remove the matched block
content = content[: match.start()] + content[match.end() :]
# Clean up any extra blank lines that might be left
# Replace multiple consecutive newlines with at most 2 newlines
processed_content = re.sub(r"\n\n\n+", "\n\n", content)
return processed_content