Skip to content

Commit 32b714a

Browse files
jsonvillanuevapre-commit-ci[bot]behacklnaveen521kk
authored
Improved CLI help page styling (#1975)
* First draft of cloup/click update * Added some customizability via cfg files * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fixed test_command epilogs * Adjusted logic for overriding theme * Update main help page, no_args_is_help, and documentation * Removed help shorthand '-h' * Apply suggestions from code review Co-authored-by: Benjamin Hackl <devel@benjamin-hackl.at> * Update poetry lock again, update DefaultGroup snippet Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Benjamin Hackl <devel@benjamin-hackl.at> Co-authored-by: Naveen M K <naveen521kk@gmail.com>
1 parent 5f490a9 commit 32b714a

16 files changed

+305
-133
lines changed

Diff for: docs/source/tutorials/configuration.rst

+38
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,41 @@ A list of all config options
363363
'upto_animation_number', 'use_opengl_renderer', 'verbosity', 'video_dir',
364364
'window_position', 'window_monitor', 'window_size', 'write_all', 'write_to_movie',
365365
'enable_wireframe', 'force_window']
366+
367+
368+
Accessing CLI command options
369+
*****************************
370+
371+
Entering ``manim``, or ``manim --help``, will open the main help page.
372+
373+
.. code::
374+
375+
Usage: manim [OPTIONS] COMMAND [ARGS]...
376+
377+
Animation engine for explanatory math videos.
378+
379+
Options:
380+
--version Show version and exit.
381+
--help Show this message and exit.
382+
383+
Commands:
384+
cfg Manages Manim configuration files.
385+
init Sets up a new project in current working directory with default
386+
settings.
387+
388+
It copies files from templates directory and pastes them in the
389+
current working dir.
390+
new Create a new project or insert a new scene.
391+
plugins Manages Manim plugins.
392+
render Render SCENE(S) from the input FILE.
393+
394+
See 'manim <command>' to read about a specific subcommand.
395+
396+
Made with <3 by Manim Community developers.
397+
398+
Each of the subcommands has its own help page which can be accessed similarly:
399+
400+
.. code::
401+
402+
manim render
403+
manim render --help

Diff for: manim/__main__.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import sys
44

55
import click
6-
from click_default_group import DefaultGroup
6+
import cloup
77

8-
from . import __version__, console
8+
from . import __version__, cli_ctx_settings, console
99
from .cli.cfg.group import cfg
10+
from .cli.default_group import DefaultGroup
1011
from .cli.init.commands import init
1112
from .cli.new.group import new
1213
from .cli.plugins.commands import plugins
@@ -22,12 +23,13 @@ def exit_early(ctx, param, value):
2223
console.print(f"Manim Community [green]v{__version__}[/green]\n")
2324

2425

25-
@click.group(
26+
@cloup.group(
27+
context_settings=cli_ctx_settings,
2628
cls=DefaultGroup,
2729
default="render",
2830
no_args_is_help=True,
29-
help="Animation engine for explanatory math videos",
30-
epilog=EPILOG,
31+
help="Animation engine for explanatory math videos.",
32+
epilog="See 'manim <command>' to read about a specific subcommand.\n\n" + EPILOG,
3133
)
3234
@click.option(
3335
"--version",

Diff for: manim/_config/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
from contextlib import _GeneratorContextManager, contextmanager
77

8+
from .cli_colors import parse_cli_ctx
89
from .logger_utils import make_logger
910
from .utils import ManimConfig, ManimFrame, make_config_parser
1011

@@ -15,6 +16,7 @@
1516
"config",
1617
"frame",
1718
"tempconfig",
19+
"cli_ctx_settings",
1820
]
1921

2022
parser = make_config_parser()
@@ -28,6 +30,7 @@
2830
parser["logger"],
2931
parser["CLI"]["verbosity"],
3032
)
33+
cli_ctx_settings = parse_cli_ctx(parser["CLI_CTX"])
3134
# TODO: temporary to have a clean terminal output when working with PIL or matplotlib
3235
logging.getLogger("PIL").setLevel(logging.INFO)
3336
logging.getLogger("matplotlib").setLevel(logging.INFO)

Diff for: manim/_config/cli_colors.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import configparser
2+
3+
from cloup import Context, HelpFormatter, HelpTheme, Style
4+
5+
6+
def parse_cli_ctx(parser: configparser.ConfigParser) -> Context:
7+
formatter_settings = {
8+
"indent_increment": int(parser["indent_increment"]),
9+
"width": int(parser["width"]),
10+
"col1_max_width": int(parser["col1_max_width"]),
11+
"col2_min_width": int(parser["col2_min_width"]),
12+
"col_spacing": int(parser["col_spacing"]),
13+
"row_sep": parser["row_sep"] if parser["row_sep"] else None,
14+
}
15+
theme_settings = {}
16+
theme_keys = {
17+
"command_help",
18+
"invoked_command",
19+
"heading",
20+
"constraint",
21+
"section_help",
22+
"col1",
23+
"col2",
24+
"epilog",
25+
}
26+
for k, v in parser.items():
27+
if k in theme_keys and v:
28+
theme_settings.update({k: Style(v)})
29+
30+
formatter = {}
31+
theme = parser["theme"] if parser["theme"] else None
32+
if theme is None:
33+
formatter = HelpFormatter().settings(
34+
theme=HelpTheme(**theme_settings), **formatter_settings
35+
)
36+
elif theme.lower() == "dark":
37+
formatter = HelpFormatter().settings(
38+
theme=HelpTheme.dark().with_(**theme_settings), **formatter_settings
39+
)
40+
elif theme.lower() == "light":
41+
formatter = HelpFormatter().settings(
42+
theme=HelpTheme.light().with_(**theme_settings), **formatter_settings
43+
)
44+
45+
return Context.settings(
46+
align_option_groups=parser["align_option_groups"].lower() == "true",
47+
align_sections=parser["align_sections"].lower() == "true",
48+
show_constraints=True,
49+
formatter_settings=formatter,
50+
)

Diff for: manim/_config/default.cfg

+30
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,36 @@ tex_template =
159159
# manim will load that plugin if it specified here.
160160
plugins =
161161

162+
# CLI Context/Formatter
163+
# Visit the cloup documentation to understand the formatting options available:
164+
# https://cloup.readthedocs.io/en/latest/index.html#a-simple-example
165+
[CLI_CTX]
166+
# CTX settings
167+
align_option_groups = True
168+
align_sections = True
169+
show_constraints = True
170+
171+
# Formatter settings
172+
indent_increment = 2
173+
width = 80
174+
col1_max_width = 30
175+
col2_min_width = 35
176+
col_spacing = 2
177+
row_sep =
178+
179+
# Dark/Light, or leave empty
180+
theme =
181+
182+
# Theme Settings - The following options override the theme colors.
183+
command_help =
184+
invoked_command =
185+
heading =
186+
constraint =
187+
section_help =
188+
col1 =
189+
col2 =
190+
epilog =
191+
162192
# Overrides the default output folders, NOT the output file names. Note that
163193
# if the custom_folders flag is present, the Tex and text files will not be put
164194
# under media_dir, as is the default.

Diff for: manim/cli/cfg/group.py

+8-7
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@
1111
from ast import literal_eval
1212

1313
import click
14+
import cloup
1415
from rich.errors import StyleSyntaxError
1516
from rich.style import Style
1617

17-
from ... import console
18+
from ... import cli_ctx_settings, console
1819
from ..._config.utils import config_file_paths, make_config_parser
19-
from ...constants import CONTEXT_SETTINGS, EPILOG
20+
from ...constants import EPILOG
2021
from ...utils.file_ops import guarantee_existence, open_file
2122

2223
RICH_COLOUR_INSTRUCTIONS: str = """
@@ -114,8 +115,8 @@ def replace_keys(default: dict) -> dict:
114115
return default
115116

116117

117-
@click.group(
118-
context_settings=CONTEXT_SETTINGS,
118+
@cloup.group(
119+
context_settings=cli_ctx_settings,
119120
invoke_without_command=True,
120121
no_args_is_help=True,
121122
epilog=EPILOG,
@@ -127,7 +128,7 @@ def cfg(ctx):
127128
pass
128129

129130

130-
@cfg.command(context_settings=CONTEXT_SETTINGS, no_args_is_help=True)
131+
@cfg.command(context_settings=cli_ctx_settings, no_args_is_help=True)
131132
@click.option(
132133
"-l",
133134
"--level",
@@ -237,7 +238,7 @@ def write(level: str = None, openfile: bool = False) -> None:
237238
open_file(cfg_file_path)
238239

239240

240-
@cfg.command(context_settings=CONTEXT_SETTINGS)
241+
@cfg.command(context_settings=cli_ctx_settings)
241242
def show():
242243
parser = make_config_parser()
243244
rich_non_style_entries = [a.replace(".", "_") for a in RICH_NON_STYLE_ENTRIES]
@@ -255,7 +256,7 @@ def show():
255256
console.print("\n")
256257

257258

258-
@cfg.command(context_settings=CONTEXT_SETTINGS)
259+
@cfg.command(context_settings=cli_ctx_settings)
259260
@click.option("-d", "--directory", default=os.getcwd())
260261
@click.pass_context
261262
def export(ctx, directory):

Diff for: manim/cli/default_group.py

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""DefaultGroup allows a subcommand to act as the main command
2+
3+
In particular, this class is what allows ``manim`` to act as ``manim render``.
4+
"""
5+
import cloup
6+
7+
from .. import logger
8+
9+
__all__ = ["DefaultGroup"]
10+
11+
12+
class DefaultGroup(cloup.Group):
13+
"""Invokes a subcommand marked with ``default=True`` if any subcommand not
14+
chosen.
15+
"""
16+
17+
def __init__(self, *args, **kwargs):
18+
# To resolve as the default command.
19+
if not kwargs.get("ignore_unknown_options", True):
20+
raise ValueError("Default group accepts unknown options")
21+
self.ignore_unknown_options = True
22+
self.default_cmd_name = kwargs.pop("default", None)
23+
self.default_if_no_args = kwargs.pop("default_if_no_args", False)
24+
super().__init__(*args, **kwargs)
25+
26+
def set_default_command(self, command):
27+
"""Sets a command function as the default command."""
28+
cmd_name = command.name
29+
self.add_command(command)
30+
self.default_cmd_name = cmd_name
31+
32+
def parse_args(self, ctx, args):
33+
if not args and self.default_if_no_args:
34+
args.insert(0, self.default_cmd_name)
35+
return super().parse_args(ctx, args)
36+
37+
def get_command(self, ctx, cmd_name):
38+
if cmd_name not in self.commands:
39+
# No command name matched.
40+
ctx.arg0 = cmd_name
41+
cmd_name = self.default_cmd_name
42+
return super().get_command(ctx, cmd_name)
43+
44+
def resolve_command(self, ctx, args):
45+
base = super()
46+
cmd_name, cmd, args = base.resolve_command(ctx, args)
47+
if hasattr(ctx, "arg0"):
48+
args.insert(0, ctx.arg0)
49+
cmd_name = cmd.name
50+
return cmd_name, cmd, args
51+
52+
def command(self, *args, **kwargs):
53+
default = kwargs.pop("default", False)
54+
decorator = super().command(*args, **kwargs)
55+
if not default:
56+
return decorator
57+
logger.log(
58+
"Use default param of DefaultGroup or " "set_default_command() instead",
59+
DeprecationWarning,
60+
)
61+
62+
def _decorator(f):
63+
cmd = decorator(f)
64+
self.set_default_command(cmd)
65+
return cmd
66+
67+
return _decorator

Diff for: manim/cli/init/commands.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99

1010
from pathlib import Path
1111

12-
import click
12+
import cloup
1313

1414
from ...constants import CONTEXT_SETTINGS, EPILOG
1515
from ...utils.file_ops import copy_template_files
1616

1717

18-
@click.command(
18+
@cloup.command(
1919
context_settings=CONTEXT_SETTINGS,
2020
epilog=EPILOG,
2121
short_help="""Sets up a new project in current working directory with default settings.\n

Diff for: manim/cli/new/group.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pathlib import Path
55

66
import click
7+
import cloup
78

89
from ... import console
910
from ...constants import CONTEXT_SETTINGS, EPILOG, QUALITIES
@@ -70,12 +71,12 @@ def update_cfg(cfg_dict, project_cfg_path):
7071
config.write(conf)
7172

7273

73-
@click.command(
74+
@cloup.command(
7475
context_settings=CONTEXT_SETTINGS,
7576
epilog=EPILOG,
7677
)
77-
@click.argument("project_name", type=Path, required=False)
78-
@click.option(
78+
@cloup.argument("project_name", type=Path, required=False)
79+
@cloup.option(
7980
"-d",
8081
"--default",
8182
"default_settings",
@@ -127,13 +128,13 @@ def project(default_settings, **args):
127128
update_cfg(CFG_DEFAULTS, new_cfg_path)
128129

129130

130-
@click.command(
131+
@cloup.command(
131132
context_settings=CONTEXT_SETTINGS,
132133
no_args_is_help=True,
133134
epilog=EPILOG,
134135
)
135-
@click.argument("scene_name", type=str, required=True)
136-
@click.argument("file_name", type=str, required=False)
136+
@cloup.argument("scene_name", type=str, required=True)
137+
@cloup.argument("file_name", type=str, required=False)
137138
def scene(**args):
138139
"""Inserts a SCENE to an existing FILE or creates a new FILE.
139140
@@ -174,14 +175,14 @@ def scene(**args):
174175
f.write("\n\n\n" + scene)
175176

176177

177-
@click.group(
178+
@cloup.group(
178179
context_settings=CONTEXT_SETTINGS,
179180
invoke_without_command=True,
180181
no_args_is_help=True,
181182
epilog=EPILOG,
182183
help="Create a new project or insert a new scene.",
183184
)
184-
@click.pass_context
185+
@cloup.pass_context
185186
def new(ctx):
186187
pass
187188

0 commit comments

Comments
 (0)