Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
build/
dist/
output/
sphinxcontrib/__pycache__
__pycache__
sphinxcontrib_restbuilder.egg-info
.tox/
63 changes: 63 additions & 0 deletions sphinxcontrib/builders/singlerst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
"""
sphinxcontrib.builders.singlerst
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Single ReST file Sphinx builder.

:copyright: Copyright 2012-2021 by Freek Dijkstra and contributors.
:license: BSD, see LICENSE.txt for details.
"""

from __future__ import (print_function, unicode_literals, absolute_import)

from sphinx.util.nodes import inline_all_toctrees

from .rst import RstBuilder
from ..writers.rst import RstWriter


class SingleRstBuilder(RstBuilder):
"""
A builder that combines all documents into a single RST file.
"""
name = 'singlerst'

def get_outdated_docs(self):
"""
Return an iterable of input files that are outdated.
"""
# Since all documents are combined into one, we always rebuild.
return 'all documents'

def get_target_uri(self, docname, typ=None):
if docname == self.config.root_doc:
return ''
return '#document-' + docname

def assemble_doctree(self):
"""Assemble all documents into a single doctree."""
root_doc = self.config.root_doc
tree = self.env.get_doctree(root_doc)
# Inline all toctrees to merge all documents into the root doctree.
tree = inline_all_toctrees(
self, set(), root_doc, tree, colorfunc=lambda x: x, traversed=[]
)
tree['docname'] = root_doc
# Resolve all references now that all documents are combined.
self.env.resolve_references(tree, root_doc, self)
return tree

def prepare_writing(self, docnames):
self.writer = RstWriter(self)

def write(self, build_docnames, updated_docnames, method='update'):
# This method overrides the base builder's write() to combine
# all documents into a single file instead of writing each separately.
docnames = self.env.all_docs
self.prepare_writing(docnames)
doctree = self.assemble_doctree()
self.write_doc(self.config.root_doc, doctree)

def finish(self):
pass
2 changes: 2 additions & 0 deletions sphinxcontrib/restbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ def setup(app):
# even if Sphinx is not yet installed.
from sphinx.writers.text import STDINDENT
from .builders.rst import RstBuilder # loads RstWriter as well.
from .builders.singlerst import SingleRstBuilder

app.require_sphinx('1.4')
app.add_builder(RstBuilder)
app.add_builder(SingleRstBuilder)
app.add_config_value('rst_file_suffix', ".rst", False)
"""This is the file name suffix for reST files"""
app.add_config_value('rst_link_suffix', None, False)
Expand Down
9 changes: 9 additions & 0 deletions sphinxcontrib/writers/rst.py
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,15 @@ def visit_raw(self, node):
def visit_docinfo(self, node):
raise nodes.SkipNode

def visit_start_of_file(self, node):
# Emit anchor target for document boundary (used by singlerst builder)
self.new_state(0)
self.add_text('.. _document-%s:' % node['docname'])
self.end_state(wrap=False)

def depart_start_of_file(self, node):
pass

def unknown_visit(self, node):
self.log_unknown(node.__class__.__name__, node)

Expand Down
45 changes: 45 additions & 0 deletions tests/test_singlerst.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from os.path import join, exists
import io

from tests.utils import build_singlerst


def test_singlerst_combines_documents(src_dir, output_dir):
"""Test that singlerst builder combines multiple documents into one file."""
test_src = join(src_dir, 'sphinx-directives/toctree')
test_output = join(output_dir, 'singlerst/toctree')

build_singlerst(test_src, test_output)

# Should produce a single index.rst file
output_file = join(test_output, 'index.rst')
assert exists(output_file), "singlerst should create index.rst"

# Read the output and verify it contains content from all documents
with io.open(output_file, encoding='utf-8') as f:
content = f.read()

# Check that content from doc1 and doc2 is included
assert 'Doc 1' in content, "Output should contain Doc 1 heading"
assert 'This is document 1' in content, "Output should contain doc1 content"
assert 'Doc 2' in content, "Output should contain Doc 2 heading"
assert 'This is document 2' in content, "Output should contain doc2 content"

# Check that document boundary anchors are generated
assert '.. _document-doc1:' in content, "Output should have doc1 anchor"
assert '.. _document-doc2:' in content, "Output should have doc2 anchor"


def test_singlerst_single_file_only(src_dir, output_dir):
"""Test that singlerst builder produces only one RST file."""
test_src = join(src_dir, 'sphinx-directives/toctree')
test_output = join(output_dir, 'singlerst/toctree-single')

build_singlerst(test_src, test_output)

# Should NOT produce separate doc1.rst and doc2.rst files
assert not exists(join(test_output, 'doc1.rst')), "singlerst should not create doc1.rst"
assert not exists(join(test_output, 'doc2.rst')), "singlerst should not create doc2.rst"

# Should only have index.rst
assert exists(join(test_output, 'index.rst')), "singlerst should create index.rst"
24 changes: 24 additions & 0 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,30 @@ def docutils_namespace():
roles._roles = _roles


def build_singlerst(src_dir, output_dir, config={}):
"""Build using the singlerst builder instead of rst."""
doctrees_dir = join(output_dir, '.doctrees')

default_config = {
'extensions': ['sphinxcontrib.restbuilder'],
'master_doc': 'index',
}
default_config.update(config)
config = default_config

with docutils_namespace():
app = Sphinx(
src_dir,
None,
output_dir,
doctrees_dir,
'singlerst',
confoverrides=config,
verbosity=0,
)
app.build(force_all=True)


def build_sphinx(src_dir, output_dir, files=None, config={}):
doctrees_dir = join(output_dir, '.doctrees')

Expand Down
Loading