Skip to content

Commit

Permalink
Merge pull request #85 from BrianPugh/docstring-format
Browse files Browse the repository at this point in the history
App.help_format initial commit.
  • Loading branch information
BrianPugh authored Jan 27, 2024
2 parents 84825be + eaab5c0 commit 366a036
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 133 deletions.
10 changes: 8 additions & 2 deletions cyclopts/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from copy import copy
from functools import partial
from pathlib import Path
from typing import Callable, Dict, Iterable, List, Optional, Tuple, Union
from typing import Callable, Dict, Iterable, List, Literal, Optional, Tuple, Union

try:
from pydantic import ValidationError as PydanticValidationError
Expand Down Expand Up @@ -178,6 +178,7 @@ class App:
converter=to_tuple_converter,
kw_only=True,
)
help_format: Union[None, Literal["plaintext", "markdown", "md", "restructuredtext", "rst"]] = None

# This can ONLY ever be Tuple[Union[Group, str], ...] due to converter.
# The other types is to make mypy happy for Cyclopts users.
Expand Down Expand Up @@ -693,7 +694,12 @@ def help_print(
console.print(executing_app.usage + "\n")

# Print the App/Command's Doc String.
console.print(format_doc(self, executing_app))
# Resolve help_format; None fallsback to parent; non-None overwrites parent.
help_format = "restructuredtext"
for app in apps:
if app.help_format is not None:
help_format = app.help_format
console.print(format_doc(self, executing_app, help_format))

def walk_apps():
# Iterates from deepest to shallowest meta-apps
Expand Down
16 changes: 14 additions & 2 deletions cyclopts/help.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def format_usage(
return Text(" ".join(usage) + "\n", style="bold")


def format_doc(root_app, app: "App"):
def format_doc(root_app, app: "App", format: str = "restructuredtext"):
from cyclopts.core import App # noqa: F811

raw_doc_string = app.help
Expand All @@ -174,7 +174,19 @@ def format_doc(root_app, app: "App"):
components.append(("\n", "default"))
components.append((parsed.long_description + "\n", "info"))

return Text.assemble(*components)
format = format.lower()
if format == "plaintext":
return Text.assemble(*components)
elif format in ("markdown", "md"):
from rich.markdown import Markdown

return console.Group(Markdown("".join(x[0] for x in components)), Text(""))
elif format in ("restructuredtext", "rst"):
from rich_rst import RestructuredText

return RestructuredText("".join(x[0] for x in components))
else:
raise ValueError(f'Unknown help_format "{format}"')


def _get_choices(type_: Type) -> str:
Expand Down
8 changes: 8 additions & 0 deletions docs/source/_static/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@
.wy-table-responsive {
overflow: visible !important;
}

.custom-code-block {
border: 1px solid #CCCCCC;
background-color: #F9F9F9;
padding: 10px;
font-family: monospace;
overflow: auto;
}
8 changes: 8 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ API
Defaults to ``["--help", "-h"]``.
Cannot be changed after instantiating the app.

.. attribute:: help_format
:type: Optional[Literal["plaintext", "markdown", "md", "restructuredtext", "rst"]]
:value: None

The markup language of docstring function descriptions.
If ``None``, fallback to parenting :attr:`~.App.help_format`.
If no :attr:`~.App.help_format` is defined, falls back to ``"restructuredtext"``.

.. attribute:: usage
:type: Optional[str]
:value: None
Expand Down
154 changes: 152 additions & 2 deletions docs/source/help.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,167 @@ The source resolution order is as follows (as applicable):
def bar():
"""This is the primary application docstring."""
-------------
Markup Format
-------------
The standard markup language for docstrings in python is reStructuredText (see `PEP-0287`_).
By default, Cyclopts parses docstring descriptions as restructuredtext and renders it appropriately.
To change the markup format, set the :attr:`.App.help_format` field accordingly.

The ``--help`` flags can be changed to different name(s) via the ``help_flags`` parameter.
Subapps inherit their parent's :attr:`.App.help_format` unless explicitly overridden. I.e. you only need
to set :attr:`.App.help_format` in your main root application for all docstrings to be parsed appropriately.

^^^^^^^^^
PlainText
^^^^^^^^^
Do not perform any additional parsing, display supplied text as-is.

.. code-block:: python
app = App(help_format="plaintext")
@app.default
def default():
"""My application summary.
This is a pretty standard docstring; if there's a really long sentence
I should probably wrap it because people don't like code that is more
than 80 columns long.
In this new paragraph, I would like to discuss the benefits of relaxing 80 cols to 120 cols.
More text in this paragraph.
Some new paragraph.
"""
.. code-block:: text
Usage: default COMMAND
My application summary.
This is a pretty standard docstring; if there's a really long
sentence
I should probably wrap it because people don't like code that is
more
than 80 columns long.
In this new paragraph, I would like to discuss the benefits of
relaxing 80 cols to 120 cols.
More text in this paragraph.
Some new paragraph.
╭─ Commands ─────────────────────────────────────────────────────╮
│ --help,-h Display this message and exit. │
│ --version Display application version. │
╰────────────────────────────────────────────────────────────────╯
Most noteworthy, is no additional text reflow is performed; newlines are presented as-is.

^^^^^^^^^^^^^^^^
ReStructuredText
^^^^^^^^^^^^^^^^
ReStructuredText is the default parsing behavior of Cyclopts, so `help_format` won't need to be explicitly set.

.. code-block:: python
app = App(help_format="restructuredtext") # or "rst"
# or don't supply help_format at all; rst is default.
@app.default
def default():
"""My application summary.
We can do RST things like have **bold text**.
More words in this paragraph.
This is a new paragraph with some bulletpoints below:
* bullet point 1.
* bullet point 2.
"""
Resulting help:

.. raw:: html

<div class="custom-code-block"><pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-style: italic">Usage: default COMMAND

My application summary.

We can do RST things like have <span style="font-weight: bold">bold text</span>. More words in this
paragraph.

This is a new paragraph with some bulletpoints below:

• bullet point 1.
• bullet point 2.

╭─ Commands ──────────────────────────────────────────────────────────╮
│ --help,-h Display this message and exit. │
│ --version Display application version. │
╰─────────────────────────────────────────────────────────────────────╯
</pre></div>

Under most circumstances, plaintext (without any additional markup) looks prettier and reflows better when interpreted as restructuredtext (or markdown, for that matter).

^^^^^^^^^
Markdown
^^^^^^^^^
Markdown is another popular markup language that Cyclopts can render.

.. code-block:: python
app = App(help_format="markdown") # or "md"
@app.default
def default():
"""My application summary.
We can do markdown things like have **bold text**.
[Hyperlinks work as well.](https://cyclopts.readthedocs.io)
"""
Resulting help:

.. raw:: html

<div class="custom-code-block"><pre style="font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-style: italic">Usage: default COMMAND

My application summary.

We can do markdown things like have <span style="font-weight: bold">bold text</span>. <a href="https://cyclopts.readthedocs.io">Hyperlinks work as well</a>.

╭─ Commands ──────────────────────────────────────────────────────────╮
│ --help,-h Display this message and exit. │
│ --version Display application version. │
╰─────────────────────────────────────────────────────────────────────╯
</pre></div>


----------
Help Flags
----------
The default ``--help`` flags can be changed to different name(s) via the ``help_flags`` parameter.

.. code-block:: python
app = cyclopts.App(help_flags="--show-help")
app = cyclopts.App(help_flags=["--send-help", "--send-help-plz", "-h"])
To disable the help-page, set ``help_flags`` to an empty string or iterable.
To disable the help-page entirely, set ``help_flags`` to an empty string or iterable.

.. code-block:: python
app = cyclopts.App(help_flags="")
app = cyclopts.App(help_flags=[])
.. _PEP-0257: https://peps.python.org/pep-0257/
.. _PEP-0287: https://peps.python.org/pep-0287/
Loading

0 comments on commit 366a036

Please sign in to comment.