Skip to content

Commit 5bfbb01

Browse files
authored
Support customizable URI templates in llms.txt (#48)
1 parent e2a80fa commit 5bfbb01

File tree

9 files changed

+298
-5
lines changed

9 files changed

+298
-5
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
0.7.0
5+
-----
6+
7+
- Add :confval:`llms_txt_uri_template` configuration option to control the link behavior in :confval:`llms_txt_filename`.
8+
`#48 <https://github.com/jdillard/sphinx-llms-txt/pull/48>`_
9+
410
0.6.0
511
-----
612

docs/source/advanced-configuration.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,37 @@ If you want to include absolute URLs for resources in your documentation, you ca
261261
262262
When this option is set, all resolved paths in directives will be prefixed with this URL, creating absolute paths in the generated files.
263263

264+
.. _customizing_uri_links:
265+
266+
Customizing URI Links in llms.txt
267+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
268+
269+
By default, the ``llms.txt`` file links to source files in the ``_sources`` directory when available, falling back to HTML pages when sources aren't available.
270+
You can customize this behavior using URI templates with :confval:`llms_txt_uri_template`:
271+
272+
.. code-block:: python
273+
274+
# Default: Link to source files, if _sources exists
275+
llms_txt_uri_template = "{base_url}_sources/{docname}{suffix}{sourcelink_suffix}"
276+
277+
# Default: Link to HTML pages instead, if _sources doesn't exist
278+
llms_txt_uri_template = "{base_url}{docname}.html"
279+
280+
# Manual: Link to a custom markdown build
281+
llms_txt_uri_template = "{base_url}{docname}.md"
282+
283+
.. _available_template_variables:
284+
285+
Available Template Variables
286+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
287+
288+
Your URI template can use the following variables:
289+
290+
- ``{base_url}`` - The base URL from ``html_baseurl`` configuration (includes trailing slash)
291+
- ``{docname}`` - The document name (e.g., ``index``, ``guide/intro``)
292+
- ``{suffix}`` - The source file suffix (e.g., ``.rst``, ``.md``) - may be empty if no source file exists
293+
- ``{sourcelink_suffix}`` - The suffix from ``html_sourcelink_suffix`` configuration (e.g., ``.txt``)
294+
264295
.. _integration_examples:
265296

266297
Integration Examples
@@ -285,6 +316,7 @@ Here's a complete example showing multiple :doc:`configuration-values`:
285316
This is a comprehensive documentation set for our project.
286317
It includes API references, usage examples, and tutorials.
287318
"""
319+
llms_txt_uri_template = "{base_url}{docname}.md"
288320
289321
# Path handling
290322
html_baseurl = "https://docs.example.com/"

docs/source/conf.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
project = "sphinx-llms-txt"
1616
copyright = "Jared Dillard"
1717
author = "Jared Dillard"
18+
llms_txt_uri_template = "{base_url}{docname}.md"
1819
llms_txt_code_files = ["+:../../sphinx_llms_txt/*.py"]
1920
llms_txt_summary = """
2021
A Sphinx extension that generates a summary llms.txt file,written in Markdown,
@@ -88,7 +89,7 @@
8889
"source_directory": "docs/source/",
8990
}
9091

91-
html_baseurl = "https://sphinx-llms-txt.readthedocs.org/"
92+
html_baseurl = "https://sphinx-llms-txt.readthedocs.org/en/latest/"
9293

9394

9495
# -- Options for HTMLHelp output ---------------------------------------------

docs/source/configuration-values.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ Project Configuration Values
5858

5959
.. versionadded:: 0.2.0
6060

61+
.. confval:: llms_txt_uri_template
62+
63+
- **Type**: string or ``None``
64+
- **Default**: ``None``
65+
- **Description**: Template string for generating URIs in ``llms.txt``.
66+
See :ref:`customizing_uri_links`.
67+
68+
.. versionadded:: 0.7.0
69+
6170
.. confval:: llms_txt_directives
6271

6372
- **Type**: list of strings

sphinx_llms_txt/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from .processor import DocumentProcessor
2222
from .writer import FileWriter
2323

24-
__version__ = "0.6.0"
24+
__version__ = "0.7.0"
2525

2626
# Export classes needed by tests
2727
__all__ = [
@@ -85,6 +85,7 @@ def build_finished(app: Sphinx, exception):
8585
config = {
8686
"llms_txt_file": app.config.llms_txt_file,
8787
"llms_txt_filename": app.config.llms_txt_filename,
88+
"llms_txt_uri_template": app.config.llms_txt_uri_template,
8889
"llms_txt_title": app.config.llms_txt_title,
8990
"llms_txt_summary": summary,
9091
"llms_txt_full_file": app.config.llms_txt_full_file,
@@ -115,6 +116,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
115116

116117
app.add_config_value("llms_txt_file", True, "env")
117118
app.add_config_value("llms_txt_filename", "llms.txt", "env")
119+
app.add_config_value("llms_txt_uri_template", None, "env")
118120
app.add_config_value("llms_txt_full_file", True, "env")
119121
app.add_config_value("llms_txt_full_filename", "llms-full.txt", "env")
120122
app.add_config_value("llms_txt_full_max_size", None, "env")

sphinx_llms_txt/manager.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,7 @@ def combine_sources(self, outdir: str, srcdir: str):
223223
filtered_page_order,
224224
self.collector.page_titles,
225225
0, # No line count since no llms-full.txt
226+
sources_dir,
226227
)
227228

228229
# Only warn if user explicitly wants llms-full.txt
@@ -523,6 +524,7 @@ def combine_sources(self, outdir: str, srcdir: str):
523524
filtered_page_order,
524525
self.collector.page_titles,
525526
total_line_count,
527+
sources_dir,
526528
)
527529
return
528530
elif action == "note":
@@ -536,6 +538,7 @@ def combine_sources(self, outdir: str, srcdir: str):
536538
filtered_page_order,
537539
self.collector.page_titles,
538540
total_line_count,
541+
sources_dir,
539542
)
540543
return
541544
elif action == "keep":
@@ -554,7 +557,10 @@ def combine_sources(self, outdir: str, srcdir: str):
554557
if success and self.config.get("llms_txt_file"):
555558
filtered_page_order = self._filter_ignored_pages(page_order)
556559
self.writer.write_verbose_info_to_file(
557-
filtered_page_order, self.collector.page_titles, total_line_count
560+
filtered_page_order,
561+
self.collector.page_titles,
562+
total_line_count,
563+
sources_dir,
558564
)
559565

560566
def _read_source_file(self, file_path: Path, docname: str) -> Tuple[str, int]:

sphinx_llms_txt/writer.py

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,42 @@ def __init__(self, config: Dict[str, Any], outdir: str = None, app: Sphinx = Non
1919
self.outdir = outdir
2020
self.app = app
2121

22+
def _resolve_uri_template(self, sources_dir: Path = None) -> str:
23+
"""Resolve which URI template to use based on configuration and sources_dir.
24+
25+
Args:
26+
sources_dir: Path to _sources directory (None if not found)
27+
28+
Returns:
29+
The template string to use for generating URIs
30+
"""
31+
# If custom template exists
32+
custom_template = self.config.get("llms_txt_uri_template")
33+
34+
if custom_template:
35+
# Validate user's template by checking for valid variable names
36+
try:
37+
# Try formatting with test valid values to validate syntax
38+
test_values = {
39+
"base_url": "http://example.com/",
40+
"docname": "test",
41+
"suffix": ".rst",
42+
"sourcelink_suffix": ".txt",
43+
}
44+
custom_template.format(**test_values)
45+
return custom_template
46+
except (KeyError, ValueError) as e:
47+
logger.warning(
48+
f"sphinx-llms-txt: Invalid llms_txt_uri_template: {e}. "
49+
f"Falling back to default."
50+
)
51+
52+
# Else, use one of the default templates
53+
if sources_dir:
54+
return "{base_url}_sources/{docname}{suffix}{sourcelink_suffix}"
55+
else:
56+
return "{base_url}{docname}.html"
57+
2258
def write_combined_file(
2359
self, content_parts: List[str], output_path: Path, total_line_count: int
2460
) -> bool:
@@ -50,13 +86,15 @@ def write_verbose_info_to_file(
5086
page_order: Union[List[str], List[Tuple[str, str]]],
5187
page_titles: Dict[str, str],
5288
total_line_count: int = 0,
89+
sources_dir: Path = None,
5390
) -> bool:
5491
"""Write summary information to the llms.txt file.
5592
5693
Args:
5794
page_order: Ordered list of document names or (docname, suffix) tuples
5895
page_titles: Dictionary mapping docnames to titles
5996
total_line_count: Total number of lines in the combined content
97+
sources_dir: Path to _sources directory (None if not found)
6098
6199
Returns:
62100
True if successful, False otherwise
@@ -102,14 +140,37 @@ def write_verbose_info_to_file(
102140
if not base_url.endswith("/"):
103141
base_url += "/"
104142

143+
# Get sourcelink suffix from Sphinx config
144+
sourcelink_suffix = ""
145+
if self.app and hasattr(self.app.config, "html_sourcelink_suffix"):
146+
sourcelink_suffix = self.app.config.html_sourcelink_suffix
147+
# Handle empty string case specially
148+
if sourcelink_suffix == "":
149+
sourcelink_suffix = "" # Keep it empty
150+
elif not sourcelink_suffix.startswith("."):
151+
sourcelink_suffix = "." + sourcelink_suffix
152+
153+
# Resolve which template to use
154+
uri_template = self._resolve_uri_template(sources_dir)
155+
105156
for item in page_order:
106157
# Handle both old format (str) and new format (tuple)
107158
if isinstance(item, tuple):
108-
docname, _ = item
159+
docname, suffix = item
109160
else:
110161
docname = item
162+
suffix = None
163+
111164
title = page_titles.get(docname, docname)
112-
f.write(f"- [{title}]({base_url}{docname}.html)\n")
165+
166+
uri = uri_template.format(
167+
base_url=base_url,
168+
docname=docname,
169+
suffix=suffix or "",
170+
sourcelink_suffix=sourcelink_suffix,
171+
)
172+
173+
f.write(f"- [{title}]({uri})\n")
113174

114175
logger.info(f"sphinx-llms-txt: created {output_path}")
115176
return True

tests/test_llms_txt.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ class Config:
794794
llms_txt_summary = None # Not configured
795795
llms_txt_file = True
796796
llms_txt_filename = "llms.txt"
797+
llms_txt_uri_template = None
797798
llms_txt_title = None
798799
llms_txt_full_file = True
799800
llms_txt_full_filename = "llms-full.txt"

0 commit comments

Comments
 (0)