diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1164ed6..bf5a9de 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,12 @@ Changelog ========= +0.6.0 +----- + +- Improve _sources directory handling + `#47 `_ + 0.5.3 ----- diff --git a/sphinx_llms_txt/__init__.py b/sphinx_llms_txt/__init__.py index 41b11bb..8a14a20 100644 --- a/sphinx_llms_txt/__init__.py +++ b/sphinx_llms_txt/__init__.py @@ -21,7 +21,7 @@ from .manager import LLMSFullManager from .processor import DocumentProcessor from .writer import FileWriter -__version__ = "0.5.3" +__version__ = "0.6.0" # Export classes needed by tests __all__ = [ diff --git a/sphinx_llms_txt/manager.py b/sphinx_llms_txt/manager.py index 2215c85..bd46a7d 100644 --- a/sphinx_llms_txt/manager.py +++ b/sphinx_llms_txt/manager.py @@ -204,25 +204,42 @@ class LLMSFullManager: sources_dir = path break - if not sources_dir: - logger.warning( - "Could not find _sources directory, skipping llms-full creation" - ) - return - - # Get the correct page order with source suffixes + # Get the correct page order (with or without source suffixes) page_order = self.collector.get_page_order(sources_dir) if not page_order: - logger.warning( - "Could not determine page order, skipping llms-full creation" - ) + logger.warning("Could not determine page order, skipping file generation") return # Apply exclusion filter if configured page_order = self.collector.filter_excluded_pages(page_order) - # Determine output file name and location + # If no sources directory, only generate llms.txt and return early + if not sources_dir: + # Generate llms.txt 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( + filtered_page_order, + self.collector.page_titles, + 0, # No line count since no llms-full.txt + ) + + # Only warn if user explicitly wants llms-full.txt + if self.config.get("llms_txt_full_file"): + # Check if html_copy_source is False + if self.app and not self.app.config.html_copy_source: + logger.warning( + "Could not find _sources directory, skipping llms-full.txt." + "Set html_copy_source = True in conf.py to enable." + ) + else: + logger.warning( + "Could not find _sources directory, skipping llms-full.txt" + ) + return + + # Determine output file name and location for llms-full.txt output_filename = self.config.get("llms_txt_full_filename") output_path = Path(outdir) / output_filename diff --git a/tests/test_llms_txt.py b/tests/test_llms_txt.py index 1ddf856..3be6fb3 100644 --- a/tests/test_llms_txt.py +++ b/tests/test_llms_txt.py @@ -1031,3 +1031,128 @@ def test_code_files_ignored_patterns(tmp_path, caplog): assert ( "Code file pattern 'docs/**/*.rst' ignored." in captured_warnings[0] ), f"Warning message should contain expected text. Got: {captured_warnings[0]}" + + +def test_llms_txt_generated_without_sources_dir(tmp_path): + """Test that llms.txt is generated even when _sources directory doesn't exist.""" + + from sphinx_llms_txt.manager import LLMSFullManager + + # Create manager + manager = LLMSFullManager() + + # Set config to enable llms.txt + config = { + "llms_txt_file": True, + "llms_txt_filename": "llms.txt", + "llms_txt_full_file": True, + "llms_txt_full_filename": "llms-full.txt", + "llms_txt_exclude": [], + "llms_txt_directives": [], + } + manager.set_config(config) + + # Create directories (but no _sources) + outdir = tmp_path / "build" + srcdir = tmp_path / "source" + outdir.mkdir() + srcdir.mkdir() + + # Mock env with documents + class MockEnv: + all_docs = {"index": None, "about": None} + titles = { + "index": type("TitleNode", (), {"astext": lambda self: "Home"})(), + "about": type("TitleNode", (), {"astext": lambda self: "About"})(), + } + toctree_includes = {"index": ["about"]} + + manager.set_env(MockEnv()) + manager.set_master_doc("index") + + # Update page titles directly in the collector + manager.update_page_title("index", "Home") + manager.update_page_title("about", "About") + + # Call combine_sources - should generate llms.txt even without _sources + manager.combine_sources(str(outdir), str(srcdir)) + + # Verify llms.txt was created + llms_txt = outdir / "llms.txt" + assert llms_txt.exists(), "llms.txt should be generated even without _sources" + + # Verify llms-full.txt was NOT created (since no _sources) + llms_full_txt = outdir / "llms-full.txt" + assert ( + not llms_full_txt.exists() + ), "llms-full.txt should not be generated without _sources" + + # Read llms.txt and verify it has content + with open(llms_txt, "r", encoding="utf-8") as f: + content = f.read() + + # Should contain page titles and links + assert "Home" in content + assert "About" in content + assert "index.html" in content + assert "about.html" in content + + +def test_llms_txt_no_warning_when_full_file_disabled(tmp_path, caplog): + """ + Test that no warning is logged when llms_txt_full_file=False and + _sources doesn't exist. + """ + from unittest.mock import patch + + from sphinx_llms_txt.manager import LLMSFullManager + + # Create manager + manager = LLMSFullManager() + + # Set config with llms_txt_full_file=False + config = { + "llms_txt_file": True, + "llms_txt_filename": "llms.txt", + "llms_txt_full_file": False, # User doesn't want llms-full.txt + "llms_txt_full_filename": "llms-full.txt", + "llms_txt_exclude": [], + "llms_txt_directives": [], + } + manager.set_config(config) + + # Create directories (but no _sources) + outdir = tmp_path / "build" + srcdir = tmp_path / "source" + outdir.mkdir() + srcdir.mkdir() + + # Mock env with documents + class MockEnv: + all_docs = {"index": None} + titles = {"index": type("TitleNode", (), {"astext": lambda self: "Home"})()} + toctree_includes = {"index": []} + + manager.set_env(MockEnv()) + manager.set_master_doc("index") + manager.update_page_title("index", "Home") + + # Capture warnings + captured_warnings = [] + + def capture_warning(message, *args, **kwargs): + if "_sources" in str(message): + captured_warnings.append(message) + + with patch("sphinx_llms_txt.manager.logger.warning", side_effect=capture_warning): + # Call combine_sources + manager.combine_sources(str(outdir), str(srcdir)) + + # Verify NO warning was logged since llms_txt_full_file=False + assert ( + len(captured_warnings) == 0 + ), "No warning should be logged when llms_txt_full_file=False" + + # Verify llms.txt was still created + llms_txt = outdir / "llms.txt" + assert llms_txt.exists()