Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand documentation for -M flag. #12685

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
6 changes: 5 additions & 1 deletion doc/man/sphinx-build.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@ sphinx-build
Synopsis
--------

**sphinx-build** [*options*] <*sourcedir*> <*outputdir*> [*filenames* ...]
| **sphinx-build** -M <*builder*> <*sourcedir*> <*outputdir*> [*options*]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer having the -M as an alternative. Without -M is much more common I think.

| **sphinx-build** [*options*] <*sourcedir*> <*outputdir*> [*filenames* ...]

Description
-----------

:program:`sphinx-build` generates documentation from the files in
``<sourcedir>`` and places it in the ``<outputdir>``.

Please note that there are 2 signatures for 2 usage scenarios. ``-M`` flag
invokes a :ref:`make mode <make_mode>`, otherwise it works in a *build mode*.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Please note that there are 2 signatures for 2 usage scenarios. ``-M`` flag
invokes a :ref:`make mode <make_mode>`, otherwise it works in a *build mode*.
The available modes are the *build-mode* via :option:`sphinx-build -b`
and the *make-mode* via :option:`sphinx-build -M`.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also suggest adding at least a mention of the fact that signatures for those mods are different. This fact is obvious for long time users while often gets completely missed by new users and leads to hours of wasted time. A couple extra words in exchange for hours saved is IMO a good trade off.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can add this note at the end of sentence, e.g., "Note that these modes have a different signature. The make-mode always requires the -M argument to comes first."


:program:`sphinx-build` looks for ``<sourcedir>/conf.py`` for the configuration
settings. :manpage:`sphinx-quickstart(1)` may be used to generate template
files, including ``conf.py``.
Expand Down
8 changes: 8 additions & 0 deletions doc/tutorial/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ the documentation as HTML for the first time. To do that, run this command:

(.venv) $ sphinx-build -M html docs/source/ docs/build/

.. note::

Please note that ``-M`` flag is a *make-mode* flag. ``sphinx-build`` also has
a *build-mode* that provides more control over cache directories but has less
builders available and has a different signature. It is invoked by using a
``-b`` flag. These flags are mutually exclusive. You can find out more in
:doc:`sphinx-build's manual </man/sphinx-build>`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Please note that ``-M`` flag is a *make-mode* flag. ``sphinx-build`` also has
a *build-mode* that provides more control over cache directories but has less
builders available and has a different signature. It is invoked by using a
``-b`` flag. These flags are mutually exclusive. You can find out more in
:doc:`sphinx-build's manual </man/sphinx-build>`.
:option:`sphinx-build -M` uses the *make-mode* but :program:`sphinx-build`
also supports a *build-mode* via :option:`sphinx-build -b` to provide a
finer control over the generated artifacts.
See the :ref:`sphinx-build's manual </man/sphinx-build>` for details.


And finally, open ``docs/build/html/index.html`` in your browser. You should see
something like this:

Expand Down
52 changes: 49 additions & 3 deletions sphinx/cmd/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
import multiprocessing
import os
import pdb # NoQA: T100
import re
import sys
import traceback
from os import path
from typing import TYPE_CHECKING, Any, TextIO
from typing import IO, TYPE_CHECKING, Any, TextIO

from docutils.utils import SystemMessage

Expand Down Expand Up @@ -111,9 +112,47 @@ def jobs_argument(value: str) -> int:
return jobs


class ParagraphFormatter(argparse.HelpFormatter):
"""Wraps help text as a default formatter but keeps paragraps separated."""

_paragraph_matcher = re.compile(r"\n\n+")

def _fill_text(self, text: str, width: int, indent: str) -> str:
import textwrap
result: list[str] = []
for p in self._paragraph_matcher.split(text):
p = self._whitespace_matcher.sub(' ', p).strip()
p = textwrap.fill(p, width,
initial_indent=indent,
subsequent_indent=indent)
result.append(p)
return '\n\n'.join(result)


class ArgParser(argparse.ArgumentParser):
"""Wraps standard ArgumentParser to add sphinx-specefic flags to help."""

def print_help(self, file: IO[str] | None = None) -> None:
from gettext import gettext as _
# we inject -M flag action to positionals before printing help
# so that there is no risk of side effects on actual execution
for action_group in self._action_groups:
if action_group.title != _('positional arguments'):
continue
m_flag = argparse.Action(
["-M"], "BUILDER", # ugly but works
help=__('please refer to usage and main help section')
)
action_group._group_actions.insert(0, m_flag)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like this. The argparse module is very fragile, so please find
an alternative instead of this hack (especially when it comes to groups).

If you want to keep this hack, restore the attributes that were mutated after printing as well.

Copy link
Author

@Ulibos Ulibos Aug 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to push back on this specific comment. argparse is what is used by the original code and there is no way to replace it without changing a whole lot of things. Since @AA-Turner seems to build a new version of a cli (at least it looks like that from his last commits), there isn't much sense to me to reinvent a second bicycle.
As to reverting back the attributes after the injection: it is performed specifically in a call of print_help. This call always ends with a termination of a program so there is no point in reverting the changes. It is exactly the reason I have implemented it in the call itself.
As for your other comments - thank you for your input, I will try to implement and push those as soon as I can. Is there a preferred way to sync branches for sphinx specifically? I prefer using rebase but it would cause your comments to break after force push which I'm not sure is a good thing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmh. Let's live with this hack then.

We prefer not to have force pushes since it's hard to review commits. We always squash merge at the end so you just push commits one by one. I'd suggest rebasing locally if you want to squash your own commits but avoid force-pushing in general.

break
return super().print_help(file)


def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]',
parser = ArgParser(
usage=('%(prog)s -M BUILDER SOURCEDIR OUTPUTDIR [OPTIONS]\n '
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the Make mode as the second mode.

'%(prog)s [OPTIONS] SOURCEDIR OUTPUTDIR [FILENAMES...]'),
formatter_class=ParagraphFormatter,
epilog=__('For more information, visit <https://www.sphinx-doc.org/>.'),
description=__("""
Generate documentation from source files.
Expand All @@ -123,6 +162,13 @@ def get_parser() -> argparse.ArgumentParser:
settings. The 'sphinx-quickstart' tool may be used to generate template files,
including 'conf.py'

PLEASE NOTE that there are 2 signatures for 2 usage scenarios. -M flag is a
special flag that must always come first if used! If present, it alters the
way the tool works. It can be thought of as a "one-click" solution. It allows
to build several formats of docs into the same OUTPUTDIR without mixing them
up (it creates a dedicated directory for each builder). If more control over
paths is needed then -b flag is the way to go.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
PLEASE NOTE that there are 2 signatures for 2 usage scenarios. -M flag is a
special flag that must always come first if used! If present, it alters the
way the tool works. It can be thought of as a "one-click" solution. It allows
to build several formats of docs into the same OUTPUTDIR without mixing them
up (it creates a dedicated directory for each builder). If more control over
paths is needed then -b flag is the way to go.
Use the '-M' option to simultaneously build several formats into
the same OUTPUTDIR. When specified, this option MUST be the first
command-line argument. If a finer control over the output is needed,
use the '-b/--builder' option instead.


sphinx-build can create documentation in different formats. A format is
selected by specifying the builder name on the command line; it defaults to
HTML. Builders can also perform other tasks related to documentation
Expand Down
8 changes: 4 additions & 4 deletions sphinx/cmd/make_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import sphinx
from sphinx.cmd.build import build_main
from sphinx.util.console import blue, bold, color_terminal, nocolor
from sphinx.util.console import bold, color_terminal, nocolor, yellow
from sphinx.util.osutil import rmtree

if sys.version_info >= (3, 11):
Expand Down Expand Up @@ -86,14 +86,14 @@ def build_clean(self) -> int:
return 0

def build_help(self) -> None:
if not color_terminal():
if not color_terminal() or '--no-color' in sys.argv or '-N' in sys.argv:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use self.opts instead of sys.argv. I think they should be equivalent.

nocolor()

print(bold("Sphinx v%s" % sphinx.__display_version__))
print("Please use `make %s' where %s is one of" % ((blue('target'),) * 2))
print("Please use `make %s' where %s is one of" % ((yellow('target'),) * 2))
for osname, bname, description in BUILDERS:
if not osname or os.name == osname:
print(f' {blue(bname.ljust(10))} {description}')
print(f' {yellow(bname.ljust(10))} {description}')

def build_latexpdf(self) -> int:
if self.run_generic_build('latex') > 0:
Expand Down