forked from pyinstaller/pyinstaller
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhelp2rst.py
More file actions
executable file
·134 lines (103 loc) · 4.54 KB
/
help2rst.py
File metadata and controls
executable file
·134 lines (103 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
#-----------------------------------------------------------------------------
# Copyright (c) 2015-2023, PyInstaller Development Team.
# Copyright (c) 2015-2020, Hartmut Goebel.
#
# Distributed under the terms of the GNU General Public License (version 2
# or later) with exception for distributing the bootloader.
#
# The full license is in the file COPYING.txt, distributed with this software.
#
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
#-----------------------------------------------------------------------------
"""
This script reformats the output of `myprog --help` to decent rst.
There are two sphinx plugins which could replace this eventually:
- https://github.com/ashb/sphinx-argparse
- https://github.com/gaborbernat/sphinx-argparse-cli
The former's output looks really nice, but lacks support for cross-referencing arguments. The latter supports
cross-referencing, but its output looks hideous. Hopefully either one of them will eventually evolve into something
we can use.
The main functions here are parser_to_rst() and help_to_rst(). Throughout this code the, **cross_references** option
controls whether or not section headings and options should be given cross reference targets.
"""
from textwrap import indent, wrap, dedent
import re
from argparse import ArgumentParser
from functools import partial
def parser_to_rst(parser: ArgumentParser, cross_references=True):
"""
Extract the ``--help`` output from an argparse parser and convert it to restructured text.
"""
# On python >= 3.14, disable colored output, as injected color codes interfere with the parser. On earlier python
# versions, this is no-op (we are setting a non-existent property).
parser.color = False
help = parser.format_help()
return help_to_rst(help, cross_references)
# Matches headings followed by indented blocks.
SECTION_REGEX = re.compile(
r"""
# A non-empty line with no indentation.
^\S.*\n
# Followed by a non-zero number of either blank lines or indented lines.
(?:(?:\ +.*)?\n)+
""", re.MULTILINE | re.VERBOSE
)
def help_to_rst(help: str, cross_references=True):
"""
Convert the output of a ``cli --help`` call to rst.
"""
summary, *sections = SECTION_REGEX.findall(help)
# We could stick the summary (``usage: pyinstaller [-h] [--help] ...``) into a code block, but it is pretty
# unhelpful, so I choose to omit it.
sections = "\n".join(section_to_rst(section, cross_references) for section in sections)
return sections
def section_to_rst(section: str, cross_references=True) -> str:
"""
Convert a single option group's ``--help`` output to rst.
This generates a heading for the option group followed by each option within that group.
"""
title, body = section.split("\n", maxsplit=1)
rst_title = rst_headerise(title, cross_references)
rst_body = OPTION_REGEX.sub(partial(option_to_rst, cross_references=cross_references), body)
return rst_title + rst_body
OPTION_REGEX = re.compile(
r"""
# Matches:
# --name, --other-name, -n VALUE Some description
# and some more description.
# An option name prefixed with at least 1 space.
^(\ +)(.*?)
# Optionally followed by at least 2 spaces and the start of the description.
(?:\ {2,}(.*))?\n
# More lines of description.
# Each line starts with more spaces than which prefixed the option name (so as to avoid picking up the next option).
# Blank lines are allowed.
(((?:\1\ +(.*))?\n)*)
""", re.MULTILINE | re.VERBOSE
)
def option_to_rst(m: re.Match, cross_references=True) -> str:
"""
Convert a single option to rst.
The output should look like::
.. option:: --option-name -n
The help for that option nicely text-wrapped.
"""
name = m.group(2)
assert name
body = " ".join(i for i in m.group(3, 4) if i)
# Escape characters which turn into invalid rst.
body = body.replace("*", r"\*")
# Re-wrap the help block.
body = "\n".join(wrap(dedent(body), width=75, break_on_hyphens=False, break_long_words=False))
template = ".. option:: {}\n\n{}\n\n" if cross_references else "{}\n\n{}\n\n"
return template.format(name, indent(body, " "))
def rst_headerise(title: str, cross_references=True) -> str:
"""
Create a title with the correct length '---' underline.
"""
title = title.strip(" \n:").title()
out = f"{title}\n{'-' * len(title)}\n\n"
if cross_references:
out = f".. _`{title}`:\n\n" + out
return out