Skip to content

Commit 1ab87ef

Browse files
Add help sections
Add help sections
2 parents 8c641cb + 054d677 commit 1ab87ef

File tree

8 files changed

+121
-17
lines changed

8 files changed

+121
-17
lines changed

.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 0.0.116
2+
current_version = 0.0.117
33
commit = True
44
tag = True
55

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
year = "2019"
2929
author = "mario contributors"
3030
copyright = "{0}, {1}".format(year, author)
31-
version = release = "0.0.116"
31+
version = release = "0.0.117"
3232

3333
pygments_style = "trac"
3434
templates_path = ["."]

requirements.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ toml
1111
asks
1212
marshmallow>=3.0rc1
1313
pyrsistent
14-
trio-typing
14+
trio_typing

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
setuptools.setup(
1616
name="mario",
17-
version="version=0.0.116",
17+
version="version=0.0.117",
1818
description="Shell pipes for Python.",
1919
long_description=open(PROJECT_ROOT / "README.rst").read(),
2020
long_description_content_type="text/x-rst",

src/mario/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.0.116"
1+
__version__ = "0.0.117"

src/mario/cli.py

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,105 @@
2020
)
2121

2222

23+
class SectionedFormatter(click.formatting.HelpFormatter):
24+
def __init__(self, *args, sections, **kwargs):
25+
self.sections = sections
26+
super().__init__(*args, **kwargs)
27+
28+
def write_dl(self, rows, *args, **kwargs):
29+
30+
cmd_to_section = {}
31+
for section, commands in self.sections.items():
32+
for command in commands:
33+
cmd_to_section[command] = section
34+
35+
sections = {}
36+
for subcommand, help in rows:
37+
sections.setdefault(
38+
cmd_to_section.get(subcommand, "Custom commands"), []
39+
).append((subcommand, help))
40+
41+
for section_name, rows in sections.items():
42+
if rows[0][0][0] == "-":
43+
# with super().section('XXX'):
44+
super().write_dl(rows)
45+
else:
46+
with super().section(section_name):
47+
super().write_dl(rows)
48+
49+
50+
class SectionedContext(click.Context):
51+
def __init__(self, *args, sections, **kwargs):
52+
self.sections = sections
53+
super().__init__(*args, **kwargs)
54+
55+
def make_formatter(self):
56+
"""Creates the formatter for the help and usage output."""
57+
return SectionedFormatter(
58+
sections=self.sections,
59+
width=self.terminal_width,
60+
max_width=self.max_content_width,
61+
)
62+
63+
64+
class SectionedGroup(click.Group):
65+
def __init__(self, *args, sections, **kwargs):
66+
self.sections = sections
67+
super().__init__(self, *args, **kwargs)
68+
69+
def make_context(self, info_name, args, parent=None, **extra):
70+
"""This function when given an info name and arguments will kick
71+
off the parsing and create a new :class:`Context`. It does not
72+
invoke the actual command callback though.
73+
74+
:param info_name: the info name for this invokation. Generally this
75+
is the most descriptive name for the script or
76+
command. For the toplevel script it's usually
77+
the name of the script, for commands below it it's
78+
the name of the script.
79+
:param args: the arguments to parse as list of strings.
80+
:param parent: the parent context if available.
81+
:param extra: extra keyword arguments forwarded to the context
82+
constructor.
83+
"""
84+
for key, value in click._compat.iteritems(self.context_settings):
85+
if key not in extra:
86+
extra[key] = value
87+
ctx = SectionedContext(
88+
self, info_name=info_name, parent=parent, sections=self.sections, **extra
89+
)
90+
with ctx.scope(cleanup=False):
91+
self.parse_args(ctx, args)
92+
return ctx
93+
94+
def format_commands(self, ctx, formatter):
95+
"""Extra format methods for multi methods that adds all the commands
96+
after the options.
97+
"""
98+
commands = []
99+
for subcommand in self.list_commands(ctx):
100+
cmd = self.get_command(ctx, subcommand)
101+
# What is this, the tool lied about a command. Ignore it
102+
if cmd is None:
103+
continue
104+
if cmd.hidden:
105+
continue
106+
107+
commands.append((subcommand, cmd))
108+
109+
# allow for 3 times the default spacing
110+
if len(commands):
111+
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
112+
113+
rows = []
114+
for subcommand, cmd in commands:
115+
help = cmd.get_short_help_str(limit)
116+
rows.append((subcommand, help))
117+
118+
if rows:
119+
formatter.write_dl(rows)
120+
121+
23122
CONTEXT_SETTINGS = {"default_map": config.DEFAULTS}
24123

25124
doc = f"""\
@@ -33,7 +132,17 @@
33132
Python modules: {config.get_config_dir() / 'modules/*.py'}
34133
35134
"""
36-
basics = click.Group(commands=plug.global_registry.cli_functions)
135+
SECTIONS = {
136+
"Traversals": ["map", "filter", "apply", "stack", "eval", "reduce", "chain"],
137+
"Async traversals": [
138+
"async-map",
139+
"async-apply",
140+
"async-filter",
141+
"async-chain",
142+
"async-map-unordered",
143+
],
144+
}
145+
basics = SectionedGroup(commands=plug.global_registry.cli_functions, sections=SECTIONS)
37146
ALIASES = plug.global_registry.aliases
38147

39148

@@ -94,7 +203,7 @@ def run(ctx, **cli_params):
94203
COMMANDS[k] = build_stages(v)
95204

96205

97-
cli = click.Group(
206+
cli = SectionedGroup(
98207
result_callback=cli_main,
99208
chain=True,
100209
context_settings=CONTEXT_SETTINGS,
@@ -121,4 +230,5 @@ def run(ctx, **cli_params):
121230
],
122231
help=doc,
123232
commands=COMMANDS,
233+
sections=SECTIONS,
124234
)

tests/test_app.py

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ def test_raises_on_nonexistent_option(option, runner):
4444
in_stream = "a.b.c\n"
4545

4646
result = runner.invoke(mario.cli.cli, args, input=in_stream)
47-
48-
assert_exception_equal(result.exception, SystemExit(2))
47+
assert result.exception
4948

5049

5150
def test_eval_main(capsys):
@@ -87,13 +86,8 @@ def test_exec_before():
8786

8887
def test_cli_version(runner):
8988
args = ["--version"]
90-
91-
result = runner.invoke(mario.cli.cli, args)
92-
93-
assert result.output == f"mario, version {mario.__version__}\n"
94-
assert result.output.rstrip()[-1].isdigit()
95-
assert not result.exception
96-
assert result.exit_code == 0
89+
result = helpers.run(args).decode()
90+
assert result == f"mario, version {mario.__version__}\n"
9791

9892

9993
def test_config_file(tmp_path):

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
skipsdist = True
3-
envlist = test,check,simple
3+
envlist = coverage,check,docs,simple
44

55
[testenv]
66

0 commit comments

Comments
 (0)