618 lines
23 KiB
Python
618 lines
23 KiB
Python
|
"""Quickly setup documentation source to work with Sphinx."""
|
||
|
|
||
|
from __future__ import annotations
|
||
|
|
||
|
import argparse
|
||
|
import locale
|
||
|
import os
|
||
|
import sys
|
||
|
import time
|
||
|
from os import path
|
||
|
from typing import TYPE_CHECKING, Any, Callable
|
||
|
|
||
|
# try to import readline, unix specific enhancement
|
||
|
try:
|
||
|
import readline
|
||
|
if TYPE_CHECKING and sys.platform == "win32": # always false, for type checking
|
||
|
raise ImportError
|
||
|
READLINE_AVAILABLE = True
|
||
|
if readline.__doc__ and 'libedit' in readline.__doc__:
|
||
|
readline.parse_and_bind("bind ^I rl_complete")
|
||
|
USE_LIBEDIT = True
|
||
|
else:
|
||
|
readline.parse_and_bind("tab: complete")
|
||
|
USE_LIBEDIT = False
|
||
|
except ImportError:
|
||
|
READLINE_AVAILABLE = False
|
||
|
USE_LIBEDIT = False
|
||
|
|
||
|
from docutils.utils import column_width
|
||
|
|
||
|
import sphinx.locale
|
||
|
from sphinx import __display_version__, package_dir
|
||
|
from sphinx.locale import __
|
||
|
from sphinx.util.console import ( # type: ignore[attr-defined]
|
||
|
bold,
|
||
|
color_terminal,
|
||
|
colorize,
|
||
|
nocolor,
|
||
|
red,
|
||
|
)
|
||
|
from sphinx.util.osutil import ensuredir
|
||
|
from sphinx.util.template import SphinxRenderer
|
||
|
|
||
|
if TYPE_CHECKING:
|
||
|
from collections.abc import Sequence
|
||
|
|
||
|
EXTENSIONS = {
|
||
|
'autodoc': __('automatically insert docstrings from modules'),
|
||
|
'doctest': __('automatically test code snippets in doctest blocks'),
|
||
|
'intersphinx': __('link between Sphinx documentation of different projects'),
|
||
|
'todo': __('write "todo" entries that can be shown or hidden on build'),
|
||
|
'coverage': __('checks for documentation coverage'),
|
||
|
'imgmath': __('include math, rendered as PNG or SVG images'),
|
||
|
'mathjax': __('include math, rendered in the browser by MathJax'),
|
||
|
'ifconfig': __('conditional inclusion of content based on config values'),
|
||
|
'viewcode': __('include links to the source code of documented Python objects'),
|
||
|
'githubpages': __('create .nojekyll file to publish the document on GitHub pages'),
|
||
|
}
|
||
|
|
||
|
DEFAULTS = {
|
||
|
'path': '.',
|
||
|
'sep': False,
|
||
|
'dot': '_',
|
||
|
'language': None,
|
||
|
'suffix': '.rst',
|
||
|
'master': 'index',
|
||
|
'makefile': True,
|
||
|
'batchfile': True,
|
||
|
}
|
||
|
|
||
|
PROMPT_PREFIX = '> '
|
||
|
|
||
|
if sys.platform == 'win32':
|
||
|
# On Windows, show questions as bold because of color scheme of PowerShell (refs: #5294).
|
||
|
COLOR_QUESTION = 'bold'
|
||
|
else:
|
||
|
COLOR_QUESTION = 'purple'
|
||
|
|
||
|
|
||
|
# function to get input from terminal -- overridden by the test suite
|
||
|
def term_input(prompt: str) -> str:
|
||
|
if sys.platform == 'win32':
|
||
|
# Important: On windows, readline is not enabled by default. In these
|
||
|
# environment, escape sequences have been broken. To avoid the
|
||
|
# problem, quickstart uses ``print()`` to show prompt.
|
||
|
print(prompt, end='')
|
||
|
return input('')
|
||
|
else:
|
||
|
return input(prompt)
|
||
|
|
||
|
|
||
|
class ValidationError(Exception):
|
||
|
"""Raised for validation errors."""
|
||
|
|
||
|
|
||
|
def is_path(x: str) -> str:
|
||
|
x = path.expanduser(x)
|
||
|
if not path.isdir(x):
|
||
|
raise ValidationError(__("Please enter a valid path name."))
|
||
|
return x
|
||
|
|
||
|
|
||
|
def is_path_or_empty(x: str) -> str:
|
||
|
if x == '':
|
||
|
return x
|
||
|
return is_path(x)
|
||
|
|
||
|
|
||
|
def allow_empty(x: str) -> str:
|
||
|
return x
|
||
|
|
||
|
|
||
|
def nonempty(x: str) -> str:
|
||
|
if not x:
|
||
|
raise ValidationError(__("Please enter some text."))
|
||
|
return x
|
||
|
|
||
|
|
||
|
def choice(*l: str) -> Callable[[str], str]:
|
||
|
def val(x: str) -> str:
|
||
|
if x not in l:
|
||
|
raise ValidationError(__('Please enter one of %s.') % ', '.join(l))
|
||
|
return x
|
||
|
return val
|
||
|
|
||
|
|
||
|
def boolean(x: str) -> bool:
|
||
|
if x.upper() not in ('Y', 'YES', 'N', 'NO'):
|
||
|
raise ValidationError(__("Please enter either 'y' or 'n'."))
|
||
|
return x.upper() in ('Y', 'YES')
|
||
|
|
||
|
|
||
|
def suffix(x: str) -> str:
|
||
|
if not (x[0:1] == '.' and len(x) > 1):
|
||
|
raise ValidationError(__("Please enter a file suffix, e.g. '.rst' or '.txt'."))
|
||
|
return x
|
||
|
|
||
|
|
||
|
def ok(x: str) -> str:
|
||
|
return x
|
||
|
|
||
|
|
||
|
def do_prompt(
|
||
|
text: str, default: str | None = None, validator: Callable[[str], Any] = nonempty,
|
||
|
) -> str | bool:
|
||
|
while True:
|
||
|
if default is not None:
|
||
|
prompt = PROMPT_PREFIX + f'{text} [{default}]: '
|
||
|
else:
|
||
|
prompt = PROMPT_PREFIX + text + ': '
|
||
|
if USE_LIBEDIT:
|
||
|
# Note: libedit has a problem for combination of ``input()`` and escape
|
||
|
# sequence (see #5335). To avoid the problem, all prompts are not colored
|
||
|
# on libedit.
|
||
|
pass
|
||
|
elif READLINE_AVAILABLE:
|
||
|
# pass input_mode=True if readline available
|
||
|
prompt = colorize(COLOR_QUESTION, prompt, input_mode=True)
|
||
|
else:
|
||
|
prompt = colorize(COLOR_QUESTION, prompt, input_mode=False)
|
||
|
x = term_input(prompt).strip()
|
||
|
if default and not x:
|
||
|
x = default
|
||
|
try:
|
||
|
x = validator(x)
|
||
|
except ValidationError as err:
|
||
|
print(red('* ' + str(err)))
|
||
|
continue
|
||
|
break
|
||
|
return x
|
||
|
|
||
|
|
||
|
class QuickstartRenderer(SphinxRenderer):
|
||
|
def __init__(self, templatedir: str = '') -> None:
|
||
|
self.templatedir = templatedir
|
||
|
super().__init__()
|
||
|
|
||
|
def _has_custom_template(self, template_name: str) -> bool:
|
||
|
"""Check if custom template file exists.
|
||
|
|
||
|
Note: Please don't use this function from extensions.
|
||
|
It will be removed in the future without deprecation period.
|
||
|
"""
|
||
|
template = path.join(self.templatedir, path.basename(template_name))
|
||
|
return bool(self.templatedir) and path.exists(template)
|
||
|
|
||
|
def render(self, template_name: str, context: dict[str, Any]) -> str:
|
||
|
if self._has_custom_template(template_name):
|
||
|
custom_template = path.join(self.templatedir, path.basename(template_name))
|
||
|
return self.render_from_file(custom_template, context)
|
||
|
else:
|
||
|
return super().render(template_name, context)
|
||
|
|
||
|
|
||
|
def ask_user(d: dict[str, Any]) -> None:
|
||
|
"""Ask the user for quickstart values missing from *d*.
|
||
|
|
||
|
Values are:
|
||
|
|
||
|
* path: root path
|
||
|
* sep: separate source and build dirs (bool)
|
||
|
* dot: replacement for dot in _templates etc.
|
||
|
* project: project name
|
||
|
* author: author names
|
||
|
* version: version of project
|
||
|
* release: release of project
|
||
|
* language: document language
|
||
|
* suffix: source file suffix
|
||
|
* master: master document name
|
||
|
* extensions: extensions to use (list)
|
||
|
* makefile: make Makefile
|
||
|
* batchfile: make command file
|
||
|
"""
|
||
|
|
||
|
print(bold(__('Welcome to the Sphinx %s quickstart utility.')) % __display_version__)
|
||
|
print()
|
||
|
print(__('Please enter values for the following settings (just press Enter to\n'
|
||
|
'accept a default value, if one is given in brackets).'))
|
||
|
|
||
|
if 'path' in d:
|
||
|
print()
|
||
|
print(bold(__('Selected root path: %s')) % d['path'])
|
||
|
else:
|
||
|
print()
|
||
|
print(__('Enter the root path for documentation.'))
|
||
|
d['path'] = do_prompt(__('Root path for the documentation'), '.', is_path)
|
||
|
|
||
|
while path.isfile(path.join(d['path'], 'conf.py')) or \
|
||
|
path.isfile(path.join(d['path'], 'source', 'conf.py')):
|
||
|
print()
|
||
|
print(bold(__('Error: an existing conf.py has been found in the '
|
||
|
'selected root path.')))
|
||
|
print(__('sphinx-quickstart will not overwrite existing Sphinx projects.'))
|
||
|
print()
|
||
|
d['path'] = do_prompt(__('Please enter a new root path (or just Enter to exit)'),
|
||
|
'', is_path_or_empty)
|
||
|
if not d['path']:
|
||
|
raise SystemExit(1)
|
||
|
|
||
|
if 'sep' not in d:
|
||
|
print()
|
||
|
print(__('You have two options for placing the build directory for Sphinx output.\n'
|
||
|
'Either, you use a directory "_build" within the root path, or you separate\n'
|
||
|
'"source" and "build" directories within the root path.'))
|
||
|
d['sep'] = do_prompt(__('Separate source and build directories (y/n)'), 'n', boolean)
|
||
|
|
||
|
if 'dot' not in d:
|
||
|
print()
|
||
|
print(__('Inside the root directory, two more directories will be created; "_templates"\n' # noqa: E501
|
||
|
'for custom HTML templates and "_static" for custom stylesheets and other static\n' # noqa: E501
|
||
|
'files. You can enter another prefix (such as ".") to replace the underscore.')) # noqa: E501
|
||
|
d['dot'] = do_prompt(__('Name prefix for templates and static dir'), '_', ok)
|
||
|
|
||
|
if 'project' not in d:
|
||
|
print()
|
||
|
print(__('The project name will occur in several places in the built documentation.'))
|
||
|
d['project'] = do_prompt(__('Project name'))
|
||
|
if 'author' not in d:
|
||
|
d['author'] = do_prompt(__('Author name(s)'))
|
||
|
|
||
|
if 'version' not in d:
|
||
|
print()
|
||
|
print(__('Sphinx has the notion of a "version" and a "release" for the\n'
|
||
|
'software. Each version can have multiple releases. For example, for\n'
|
||
|
'Python the version is something like 2.5 or 3.0, while the release is\n'
|
||
|
"something like 2.5.1 or 3.0a1. If you don't need this dual structure,\n"
|
||
|
'just set both to the same value.'))
|
||
|
d['version'] = do_prompt(__('Project version'), '', allow_empty)
|
||
|
if 'release' not in d:
|
||
|
d['release'] = do_prompt(__('Project release'), d['version'], allow_empty)
|
||
|
|
||
|
if 'language' not in d:
|
||
|
print()
|
||
|
print(__(
|
||
|
'If the documents are to be written in a language other than English,\n'
|
||
|
'you can select a language here by its language code. Sphinx will then\n'
|
||
|
'translate text that it generates into that language.\n'
|
||
|
'\n'
|
||
|
'For a list of supported codes, see\n'
|
||
|
'https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-language.',
|
||
|
))
|
||
|
d['language'] = do_prompt(__('Project language'), 'en')
|
||
|
if d['language'] == 'en':
|
||
|
d['language'] = None
|
||
|
|
||
|
if 'suffix' not in d:
|
||
|
print()
|
||
|
print(__('The file name suffix for source files. Commonly, this is either ".txt"\n'
|
||
|
'or ".rst". Only files with this suffix are considered documents.'))
|
||
|
d['suffix'] = do_prompt(__('Source file suffix'), '.rst', suffix)
|
||
|
|
||
|
if 'master' not in d:
|
||
|
print()
|
||
|
print(__('One document is special in that it is considered the top node of the\n'
|
||
|
'"contents tree", that is, it is the root of the hierarchical structure\n'
|
||
|
'of the documents. Normally, this is "index", but if your "index"\n'
|
||
|
'document is a custom template, you can also set this to another filename.'))
|
||
|
d['master'] = do_prompt(__('Name of your master document (without suffix)'), 'index')
|
||
|
|
||
|
while path.isfile(path.join(d['path'], d['master'] + d['suffix'])) or \
|
||
|
path.isfile(path.join(d['path'], 'source', d['master'] + d['suffix'])):
|
||
|
print()
|
||
|
print(bold(__('Error: the master file %s has already been found in the '
|
||
|
'selected root path.') % (d['master'] + d['suffix'])))
|
||
|
print(__('sphinx-quickstart will not overwrite the existing file.'))
|
||
|
print()
|
||
|
d['master'] = do_prompt(__('Please enter a new file name, or rename the '
|
||
|
'existing file and press Enter'), d['master'])
|
||
|
|
||
|
if 'extensions' not in d:
|
||
|
print(__('Indicate which of the following Sphinx extensions should be enabled:'))
|
||
|
d['extensions'] = []
|
||
|
for name, description in EXTENSIONS.items():
|
||
|
if do_prompt(f'{name}: {description} (y/n)', 'n', boolean):
|
||
|
d['extensions'].append('sphinx.ext.%s' % name)
|
||
|
|
||
|
# Handle conflicting options
|
||
|
if {'sphinx.ext.imgmath', 'sphinx.ext.mathjax'}.issubset(d['extensions']):
|
||
|
print(__('Note: imgmath and mathjax cannot be enabled at the same time. '
|
||
|
'imgmath has been deselected.'))
|
||
|
d['extensions'].remove('sphinx.ext.imgmath')
|
||
|
|
||
|
if 'makefile' not in d:
|
||
|
print()
|
||
|
print(__('A Makefile and a Windows command file can be generated for you so that you\n'
|
||
|
"only have to run e.g. `make html' instead of invoking sphinx-build\n"
|
||
|
'directly.'))
|
||
|
d['makefile'] = do_prompt(__('Create Makefile? (y/n)'), 'y', boolean)
|
||
|
|
||
|
if 'batchfile' not in d:
|
||
|
d['batchfile'] = do_prompt(__('Create Windows command file? (y/n)'), 'y', boolean)
|
||
|
print()
|
||
|
|
||
|
|
||
|
def generate(
|
||
|
d: dict, overwrite: bool = True, silent: bool = False, templatedir: str | None = None,
|
||
|
) -> None:
|
||
|
"""Generate project based on values in *d*."""
|
||
|
template = QuickstartRenderer(templatedir or '')
|
||
|
|
||
|
if 'mastertoctree' not in d:
|
||
|
d['mastertoctree'] = ''
|
||
|
if 'mastertocmaxdepth' not in d:
|
||
|
d['mastertocmaxdepth'] = 2
|
||
|
|
||
|
d['root_doc'] = d['master']
|
||
|
d['now'] = time.asctime()
|
||
|
d['project_underline'] = column_width(d['project']) * '='
|
||
|
d.setdefault('extensions', [])
|
||
|
d['copyright'] = time.strftime('%Y') + ', ' + d['author']
|
||
|
|
||
|
d["path"] = os.path.abspath(d['path'])
|
||
|
ensuredir(d['path'])
|
||
|
|
||
|
srcdir = path.join(d['path'], 'source') if d['sep'] else d['path']
|
||
|
|
||
|
ensuredir(srcdir)
|
||
|
if d['sep']:
|
||
|
builddir = path.join(d['path'], 'build')
|
||
|
d['exclude_patterns'] = ''
|
||
|
else:
|
||
|
builddir = path.join(srcdir, d['dot'] + 'build')
|
||
|
exclude_patterns = map(repr, [
|
||
|
d['dot'] + 'build',
|
||
|
'Thumbs.db', '.DS_Store',
|
||
|
])
|
||
|
d['exclude_patterns'] = ', '.join(exclude_patterns)
|
||
|
ensuredir(builddir)
|
||
|
ensuredir(path.join(srcdir, d['dot'] + 'templates'))
|
||
|
ensuredir(path.join(srcdir, d['dot'] + 'static'))
|
||
|
|
||
|
def write_file(fpath: str, content: str, newline: str | None = None) -> None:
|
||
|
if overwrite or not path.isfile(fpath):
|
||
|
if 'quiet' not in d:
|
||
|
print(__('Creating file %s.') % fpath)
|
||
|
with open(fpath, 'w', encoding='utf-8', newline=newline) as f:
|
||
|
f.write(content)
|
||
|
else:
|
||
|
if 'quiet' not in d:
|
||
|
print(__('File %s already exists, skipping.') % fpath)
|
||
|
|
||
|
conf_path = os.path.join(templatedir, 'conf.py_t') if templatedir else None
|
||
|
if not conf_path or not path.isfile(conf_path):
|
||
|
conf_path = os.path.join(package_dir, 'templates', 'quickstart', 'conf.py_t')
|
||
|
with open(conf_path, encoding="utf-8") as f:
|
||
|
conf_text = f.read()
|
||
|
|
||
|
write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d))
|
||
|
|
||
|
masterfile = path.join(srcdir, d['master'] + d['suffix'])
|
||
|
if template._has_custom_template('quickstart/master_doc.rst_t'):
|
||
|
msg = ('A custom template `master_doc.rst_t` found. It has been renamed to '
|
||
|
'`root_doc.rst_t`. Please rename it on your project too.')
|
||
|
print(colorize('red', msg))
|
||
|
write_file(masterfile, template.render('quickstart/master_doc.rst_t', d))
|
||
|
else:
|
||
|
write_file(masterfile, template.render('quickstart/root_doc.rst_t', d))
|
||
|
|
||
|
if d.get('make_mode') is True:
|
||
|
makefile_template = 'quickstart/Makefile.new_t'
|
||
|
batchfile_template = 'quickstart/make.bat.new_t'
|
||
|
else:
|
||
|
makefile_template = 'quickstart/Makefile_t'
|
||
|
batchfile_template = 'quickstart/make.bat_t'
|
||
|
|
||
|
if d['makefile'] is True:
|
||
|
d['rsrcdir'] = 'source' if d['sep'] else '.'
|
||
|
d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build'
|
||
|
# use binary mode, to avoid writing \r\n on Windows
|
||
|
write_file(path.join(d['path'], 'Makefile'),
|
||
|
template.render(makefile_template, d), '\n')
|
||
|
|
||
|
if d['batchfile'] is True:
|
||
|
d['rsrcdir'] = 'source' if d['sep'] else '.'
|
||
|
d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build'
|
||
|
write_file(path.join(d['path'], 'make.bat'),
|
||
|
template.render(batchfile_template, d), '\r\n')
|
||
|
|
||
|
if silent:
|
||
|
return
|
||
|
print()
|
||
|
print(bold(__('Finished: An initial directory structure has been created.')))
|
||
|
print()
|
||
|
print(__('You should now populate your master file %s and create other documentation\n'
|
||
|
'source files. ') % masterfile, end='')
|
||
|
if d['makefile'] or d['batchfile']:
|
||
|
print(__('Use the Makefile to build the docs, like so:\n'
|
||
|
' make builder'))
|
||
|
else:
|
||
|
print(__('Use the sphinx-build command to build the docs, like so:\n'
|
||
|
' sphinx-build -b builder %s %s') % (srcdir, builddir))
|
||
|
print(__('where "builder" is one of the supported builders, '
|
||
|
'e.g. html, latex or linkcheck.'))
|
||
|
print()
|
||
|
|
||
|
|
||
|
def valid_dir(d: dict) -> bool:
|
||
|
dir = d['path']
|
||
|
if not path.exists(dir):
|
||
|
return True
|
||
|
if not path.isdir(dir):
|
||
|
return False
|
||
|
|
||
|
if {'Makefile', 'make.bat'} & set(os.listdir(dir)):
|
||
|
return False
|
||
|
|
||
|
if d['sep']:
|
||
|
dir = os.path.join('source', dir)
|
||
|
if not path.exists(dir):
|
||
|
return True
|
||
|
if not path.isdir(dir):
|
||
|
return False
|
||
|
|
||
|
reserved_names = [
|
||
|
'conf.py',
|
||
|
d['dot'] + 'static',
|
||
|
d['dot'] + 'templates',
|
||
|
d['master'] + d['suffix'],
|
||
|
]
|
||
|
if set(reserved_names) & set(os.listdir(dir)):
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def get_parser() -> argparse.ArgumentParser:
|
||
|
description = __(
|
||
|
"\n"
|
||
|
"Generate required files for a Sphinx project.\n"
|
||
|
"\n"
|
||
|
"sphinx-quickstart is an interactive tool that asks some questions about your\n"
|
||
|
"project and then generates a complete documentation directory and sample\n"
|
||
|
"Makefile to be used with sphinx-build.\n",
|
||
|
)
|
||
|
parser = argparse.ArgumentParser(
|
||
|
usage='%(prog)s [OPTIONS] <PROJECT_DIR>',
|
||
|
epilog=__("For more information, visit <https://www.sphinx-doc.org/>."),
|
||
|
description=description)
|
||
|
|
||
|
parser.add_argument('-q', '--quiet', action='store_true', dest='quiet',
|
||
|
default=None,
|
||
|
help=__('quiet mode'))
|
||
|
parser.add_argument('--version', action='version', dest='show_version',
|
||
|
version='%%(prog)s %s' % __display_version__)
|
||
|
|
||
|
parser.add_argument('path', metavar='PROJECT_DIR', default='.', nargs='?',
|
||
|
help=__('project root'))
|
||
|
|
||
|
group = parser.add_argument_group(__('Structure options'))
|
||
|
group.add_argument('--sep', action='store_true', dest='sep', default=None,
|
||
|
help=__('if specified, separate source and build dirs'))
|
||
|
group.add_argument('--no-sep', action='store_false', dest='sep',
|
||
|
help=__('if specified, create build dir under source dir'))
|
||
|
group.add_argument('--dot', metavar='DOT', default='_',
|
||
|
help=__('replacement for dot in _templates etc.'))
|
||
|
|
||
|
group = parser.add_argument_group(__('Project basic options'))
|
||
|
group.add_argument('-p', '--project', metavar='PROJECT', dest='project',
|
||
|
help=__('project name'))
|
||
|
group.add_argument('-a', '--author', metavar='AUTHOR', dest='author',
|
||
|
help=__('author names'))
|
||
|
group.add_argument('-v', metavar='VERSION', dest='version', default='',
|
||
|
help=__('version of project'))
|
||
|
group.add_argument('-r', '--release', metavar='RELEASE', dest='release',
|
||
|
help=__('release of project'))
|
||
|
group.add_argument('-l', '--language', metavar='LANGUAGE', dest='language',
|
||
|
help=__('document language'))
|
||
|
group.add_argument('--suffix', metavar='SUFFIX', default='.rst',
|
||
|
help=__('source file suffix'))
|
||
|
group.add_argument('--master', metavar='MASTER', default='index',
|
||
|
help=__('master document name'))
|
||
|
group.add_argument('--epub', action='store_true', default=False,
|
||
|
help=__('use epub'))
|
||
|
|
||
|
group = parser.add_argument_group(__('Extension options'))
|
||
|
for ext in EXTENSIONS:
|
||
|
group.add_argument('--ext-%s' % ext, action='append_const',
|
||
|
const='sphinx.ext.%s' % ext, dest='extensions',
|
||
|
help=__('enable %s extension') % ext)
|
||
|
group.add_argument('--extensions', metavar='EXTENSIONS', dest='extensions',
|
||
|
action='append', help=__('enable arbitrary extensions'))
|
||
|
|
||
|
group = parser.add_argument_group(__('Makefile and Batchfile creation'))
|
||
|
group.add_argument('--makefile', action='store_true', dest='makefile', default=True,
|
||
|
help=__('create makefile'))
|
||
|
group.add_argument('--no-makefile', action='store_false', dest='makefile',
|
||
|
help=__('do not create makefile'))
|
||
|
group.add_argument('--batchfile', action='store_true', dest='batchfile', default=True,
|
||
|
help=__('create batchfile'))
|
||
|
group.add_argument('--no-batchfile', action='store_false',
|
||
|
dest='batchfile',
|
||
|
help=__('do not create batchfile'))
|
||
|
group.add_argument('-m', '--use-make-mode', action='store_true',
|
||
|
dest='make_mode', default=True,
|
||
|
help=__('use make-mode for Makefile/make.bat'))
|
||
|
group.add_argument('-M', '--no-use-make-mode', action='store_false',
|
||
|
dest='make_mode',
|
||
|
help=__('do not use make-mode for Makefile/make.bat'))
|
||
|
|
||
|
group = parser.add_argument_group(__('Project templating'))
|
||
|
group.add_argument('-t', '--templatedir', metavar='TEMPLATEDIR',
|
||
|
dest='templatedir',
|
||
|
help=__('template directory for template files'))
|
||
|
group.add_argument('-d', metavar='NAME=VALUE', action='append',
|
||
|
dest='variables',
|
||
|
help=__('define a template variable'))
|
||
|
|
||
|
return parser
|
||
|
|
||
|
|
||
|
def main(argv: Sequence[str] = (), /) -> int:
|
||
|
locale.setlocale(locale.LC_ALL, '')
|
||
|
sphinx.locale.init_console()
|
||
|
|
||
|
if not color_terminal():
|
||
|
nocolor()
|
||
|
|
||
|
# parse options
|
||
|
parser = get_parser()
|
||
|
try:
|
||
|
args = parser.parse_args(argv or sys.argv[1:])
|
||
|
except SystemExit as err:
|
||
|
return err.code # type: ignore[return-value]
|
||
|
|
||
|
d = vars(args)
|
||
|
# delete None or False value
|
||
|
d = {k: v for k, v in d.items() if v is not None}
|
||
|
|
||
|
# handle use of CSV-style extension values
|
||
|
d.setdefault('extensions', [])
|
||
|
for ext in d['extensions'][:]:
|
||
|
if ',' in ext:
|
||
|
d['extensions'].remove(ext)
|
||
|
d['extensions'].extend(ext.split(','))
|
||
|
|
||
|
try:
|
||
|
if 'quiet' in d:
|
||
|
if not {'project', 'author'}.issubset(d):
|
||
|
print(__('"quiet" is specified, but any of "project" or '
|
||
|
'"author" is not specified.'))
|
||
|
return 1
|
||
|
|
||
|
if {'quiet', 'project', 'author'}.issubset(d):
|
||
|
# quiet mode with all required params satisfied, use default
|
||
|
d.setdefault('version', '')
|
||
|
d.setdefault('release', d['version'])
|
||
|
d2 = DEFAULTS.copy()
|
||
|
d2.update(d)
|
||
|
d = d2
|
||
|
|
||
|
if not valid_dir(d):
|
||
|
print()
|
||
|
print(bold(__('Error: specified path is not a directory, or sphinx'
|
||
|
' files already exist.')))
|
||
|
print(__('sphinx-quickstart only generate into a empty directory.'
|
||
|
' Please specify a new root path.'))
|
||
|
return 1
|
||
|
else:
|
||
|
ask_user(d)
|
||
|
except (KeyboardInterrupt, EOFError):
|
||
|
print()
|
||
|
print('[Interrupted.]')
|
||
|
return 130 # 128 + SIGINT
|
||
|
|
||
|
for variable in d.get('variables', []):
|
||
|
try:
|
||
|
name, value = variable.split('=')
|
||
|
d[name] = value
|
||
|
except ValueError:
|
||
|
print(__('Invalid template variable: %s') % variable)
|
||
|
|
||
|
generate(d, overwrite=False, templatedir=args.templatedir)
|
||
|
return 0
|
||
|
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
raise SystemExit(main(sys.argv[1:]))
|