"""Test the sphinx.quickstart module.""" from __future__ import annotations import time from io import StringIO from typing import TYPE_CHECKING import pytest from sphinx._cli.util.colour import disable_colour, enable_colour from sphinx.cmd import quickstart as qs from sphinx.testing.util import SphinxTestApp if TYPE_CHECKING: from collections.abc import Callable from pathlib import Path from typing import Any warnfile = StringIO() def setup_module(): disable_colour() def mock_input( answers: dict[str, str], needanswer: bool = False ) -> Callable[[str], str]: called = set() def input_(prompt: str) -> str: if prompt in called: raise AssertionError( 'answer for %r missing and no default present' % prompt ) called.add(prompt) for question, answer in answers.items(): if prompt.startswith(qs.PROMPT_PREFIX + question): return answer if needanswer: raise AssertionError('answer for %r missing' % prompt) return '' return input_ real_input: Callable[[str], str] = input def teardown_module(): qs.term_input = real_input enable_colour() def test_do_prompt(): answers = { 'Q2': 'v2', 'Q3': 'v3', 'Q4': 'yes', 'Q5': 'no', 'Q6': 'foo', } qs.term_input = mock_input(answers) assert qs.do_prompt('Q1', default='v1') == 'v1' assert qs.do_prompt('Q3', default='v3_default') == 'v3' assert qs.do_prompt('Q2') == 'v2' assert qs.do_prompt('Q4', validator=qs.boolean) is True assert qs.do_prompt('Q5', validator=qs.boolean) is False with pytest.raises(AssertionError): qs.do_prompt('Q6', validator=qs.boolean) def test_do_prompt_inputstrip(): answers = { 'Q1': 'Y', 'Q2': ' Yes ', 'Q3': 'N', 'Q4': 'N ', } qs.term_input = mock_input(answers) assert qs.do_prompt('Q1') == 'Y' assert qs.do_prompt('Q2') == 'Yes' assert qs.do_prompt('Q3') == 'N' assert qs.do_prompt('Q4') == 'N' def test_do_prompt_with_nonascii(): answers = { 'Q1': '\u30c9\u30a4\u30c4', } qs.term_input = mock_input(answers) result = qs.do_prompt('Q1', default='\u65e5\u672c') assert result == '\u30c9\u30a4\u30c4' def test_quickstart_defaults(tmp_path): answers = { 'Root path': str(tmp_path), 'Project name': 'Sphinx Test', 'Author name': 'Georg Brandl', 'Project version': '0.1', } qs.term_input = mock_input(answers) d: dict[str, Any] = {} qs.ask_user(d) qs.generate(d) conffile = tmp_path / 'conf.py' assert conffile.is_file() ns: dict[str, Any] = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 assert ns['extensions'] == [] assert ns['templates_path'] == ['_templates'] assert ns['project'] == 'Sphinx Test' assert ns['copyright'] == '%s, Georg Brandl' % time.strftime('%Y') assert ns['version'] == '0.1' assert ns['release'] == '0.1' assert ns['html_static_path'] == ['_static'] assert (tmp_path / '_static').is_dir() assert (tmp_path / '_templates').is_dir() assert (tmp_path / 'index.rst').is_file() assert (tmp_path / 'Makefile').is_file() assert (tmp_path / 'make.bat').is_file() def test_quickstart_all_answers(tmp_path): answers = { 'Root path': str(tmp_path), 'Separate source and build': 'y', 'Name prefix for templates': '.', 'Project name': 'STASI™', 'Author name': "Wolfgang Schäuble & G'Beckstein", 'Project version': '2.0', 'Project release': '2.0.1', 'Project language': 'de', 'Source file suffix': '.txt', 'Name of your master document': 'contents', 'autodoc': 'y', 'doctest': 'yes', 'intersphinx': 'no', 'todo': 'y', 'coverage': 'no', 'imgmath': 'N', 'mathjax': 'no', 'ifconfig': 'no', 'viewcode': 'no', 'githubpages': 'no', 'Create Makefile': 'no', 'Create Windows command file': 'no', 'Do you want to use the epub builder': 'yes', } qs.term_input = mock_input(answers, needanswer=True) d: dict[str, Any] = {} qs.ask_user(d) qs.generate(d) conffile = tmp_path / 'source' / 'conf.py' assert conffile.is_file() ns: dict[str, Any] = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 assert ns['extensions'] == [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', ] assert ns['templates_path'] == ['.templates'] assert ns['source_suffix'] == '.txt' assert ns['root_doc'] == 'contents' assert ns['project'] == 'STASI™' assert ns['copyright'] == "%s, Wolfgang Schäuble & G'Beckstein" % time.strftime( '%Y' ) assert ns['version'] == '2.0' assert ns['release'] == '2.0.1' assert ns['todo_include_todos'] is True assert ns['html_static_path'] == ['.static'] assert (tmp_path / 'build').is_dir() assert (tmp_path / 'source' / '.static').is_dir() assert (tmp_path / 'source' / '.templates').is_dir() assert (tmp_path / 'source' / 'contents.txt').is_file() def test_generated_files_eol(tmp_path): answers = { 'Root path': str(tmp_path), 'Project name': 'Sphinx Test', 'Author name': 'Georg Brandl', 'Project version': '0.1', } qs.term_input = mock_input(answers) d: dict[str, Any] = {} qs.ask_user(d) qs.generate(d) def assert_eol(filename: Path, eol: str) -> None: content = filename.read_bytes().decode() assert all(l[-len(eol) :] == eol for l in content.splitlines(keepends=True)) assert_eol(tmp_path / 'make.bat', '\r\n') assert_eol(tmp_path / 'Makefile', '\n') def test_quickstart_and_build(tmp_path): answers = { 'Root path': str(tmp_path), 'Project name': 'Fullwidth characters: \u30c9\u30a4\u30c4', 'Author name': 'Georg Brandl', 'Project version': '0.1', } qs.term_input = mock_input(answers) d: dict[str, Any] = {} qs.ask_user(d) qs.generate(d) app = SphinxTestApp('html', srcdir=tmp_path, warning=warnfile) app.build(force_all=True) app.cleanup() warnings = warnfile.getvalue() assert not warnings def test_default_filename(tmp_path): answers = { 'Root path': str(tmp_path), 'Project name': '\u30c9\u30a4\u30c4', # Fullwidth characters only 'Author name': 'Georg Brandl', 'Project version': '0.1', } qs.term_input = mock_input(answers) d: dict[str, Any] = {} qs.ask_user(d) qs.generate(d) conffile = tmp_path / 'conf.py' assert conffile.is_file() ns: dict[str, Any] = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 def test_extensions(tmp_path): qs.main([ '-q', '-p', 'project_name', '-a', 'author', '--extensions', 'foo,bar,baz', str(tmp_path), ]) conffile = tmp_path / 'conf.py' assert conffile.is_file() ns: dict[str, Any] = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 assert ns['extensions'] == ['foo', 'bar', 'baz'] def test_exits_when_existing_confpy(monkeypatch): # The code detects existing conf.py with path.is_file() # so we mock it as True with pytest's monkeypatch monkeypatch.setattr('os.path.isfile', lambda path: True) qs.term_input = mock_input({ 'Please enter a new root path (or just Enter to exit)': '', }) d: dict[str, Any] = {} with pytest.raises(SystemExit): qs.ask_user(d)