You've already forked httpie-cli
mirror of
https://github.com/httpie/cli.git
synced 2025-06-27 00:51:16 +02:00
[Major] UI Enhancements (#1321)
* Refactor tests to use a text-based standard output. (#1318) * Implement new style `--help` (#1316) * Implement man page generation (#1317) * Implement rich progress bars. (#1324) * Man page deployment & isolation. (#1325) * Remove all unsorted usages in the CLI docs * Implement isolated mode for man page generation * Add a CI job for autogenerated files * Distribute man pages through PyPI * Pin the date for man pages. (#1326) * Hide suppressed arguments from --help/man pages (#1329) * Change download spinner to line (#1328) * Regenerate autogenerated files when pushed against to master. (#1339) * Highlight options (#1340) * Additional man page enhancements (#1341) * Group options by the parent category & highlight -o/--o * Display (and underline) the METAVAR on man pages. * Make help message processing more robust (#1342) * Inherit `help` from `short_help` * Don't mirror short_help directly. * Fixup the serialization * Use `pager` and `man` on `--manual` when applicable (#1343) * Run `man $program` on --manual * Page the output of `--manual` for systems that lack man pages * Improvements over progress bars (separate bar, status line, etc.) (#1346) * Redesign the --help layout. * Make our usage of rich compatible with 9.10.0 * Add `HTTPIE_NO_MAN_PAGES` * Make tests also patch os.get_terminal_size * Generate CLI spec from HTTPie & Man Page Hook (#1354) * Generate CLI spec from HTTPie & add man page hook * Use the full command space for the option headers
This commit is contained in:
156
extras/scripts/generate_man_pages.py
Normal file
156
extras/scripts/generate_man_pages.py
Normal file
@ -0,0 +1,156 @@
|
||||
import re
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Optional, Iterator, Iterable
|
||||
|
||||
import httpie
|
||||
from httpie.cli.definition import options as core_options
|
||||
from httpie.cli.options import ParserSpec
|
||||
from httpie.manager.cli import options as manager_options
|
||||
from httpie.output.ui.rich_help import OptionsHighlighter, to_usage
|
||||
from httpie.output.ui.rich_utils import render_as_string
|
||||
from httpie.utils import split
|
||||
|
||||
|
||||
# Escape certain characters so they are rendered properly on
|
||||
# all terminals.
|
||||
ESCAPE_MAP = {
|
||||
"'": "\\'",
|
||||
'~': '\\~',
|
||||
'’': "\\'",
|
||||
'\\': '\\\\',
|
||||
}
|
||||
ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}
|
||||
|
||||
EXTRAS_DIR = Path(__file__).parent.parent
|
||||
MAN_PAGE_PATH = EXTRAS_DIR / 'man'
|
||||
|
||||
OPTION_HIGHLIGHT_RE = re.compile(
|
||||
OptionsHighlighter.highlights[0]
|
||||
)
|
||||
|
||||
class ManPageBuilder:
|
||||
def __init__(self):
|
||||
self.source = []
|
||||
|
||||
def title_line(
|
||||
self,
|
||||
full_name: str,
|
||||
program_name: str,
|
||||
program_version: str,
|
||||
last_edit_date: str,
|
||||
) -> None:
|
||||
self.source.append(
|
||||
f'.TH {program_name} 1 "{last_edit_date}" '
|
||||
f'"{full_name} {program_version}" "{full_name} Manual"'
|
||||
)
|
||||
|
||||
def set_name(self, program_name: str) -> None:
|
||||
with self.section('NAME'):
|
||||
self.write(program_name)
|
||||
|
||||
def write(self, text: str, *, bold: bool = False) -> None:
|
||||
if bold:
|
||||
text = '.B ' + text
|
||||
self.source.append(text)
|
||||
|
||||
def separate(self) -> None:
|
||||
self.source.append('.PP')
|
||||
|
||||
def add_options(self, options: Iterable[str], *, metavar: Optional[str] = None) -> None:
|
||||
text = ", ".join(map(self.boldify, options))
|
||||
if metavar:
|
||||
text += f' {self.underline(metavar)}'
|
||||
self.write(f'.IP "{text}"')
|
||||
|
||||
def build(self) -> str:
|
||||
return '\n'.join(self.source)
|
||||
|
||||
@contextmanager
|
||||
def section(self, section_name: str) -> Iterator[None]:
|
||||
self.write(f'.SH {section_name}')
|
||||
self.in_section = True
|
||||
yield
|
||||
self.in_section = False
|
||||
|
||||
def underline(self, text: str) -> str:
|
||||
return r'\fI\,{}\/\fR'.format(text)
|
||||
|
||||
def boldify(self, text: str) -> str:
|
||||
return r'\fB\,{}\/\fR'.format(text)
|
||||
|
||||
|
||||
def _escape_and_dedent(text: str) -> str:
|
||||
lines = []
|
||||
for should_act, line in enumerate(text.splitlines()):
|
||||
# Only dedent after the first line.
|
||||
if should_act:
|
||||
if line.startswith(' '):
|
||||
line = line[4:]
|
||||
|
||||
lines.append(line)
|
||||
return '\n'.join(lines).translate(ESCAPE_MAP)
|
||||
|
||||
|
||||
def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
builder = ManPageBuilder()
|
||||
|
||||
builder.title_line(
|
||||
full_name='HTTPie',
|
||||
program_name=program_name,
|
||||
program_version=httpie.__version__,
|
||||
last_edit_date=httpie.__date__,
|
||||
)
|
||||
builder.set_name(program_name)
|
||||
|
||||
with builder.section('SYNOPSIS'):
|
||||
builder.write(render_as_string(to_usage(spec, program_name=program_name)))
|
||||
|
||||
with builder.section('DESCRIPTION'):
|
||||
builder.write(spec.description)
|
||||
|
||||
for index, group in enumerate(spec.groups, 1):
|
||||
with builder.section(group.name):
|
||||
if group.description:
|
||||
builder.write(group.description)
|
||||
|
||||
for argument in group.arguments:
|
||||
if argument.is_hidden:
|
||||
continue
|
||||
|
||||
raw_arg = argument.serialize(isolation_mode=True)
|
||||
|
||||
metavar = raw_arg.get('metavar')
|
||||
if raw_arg.get('is_positional'):
|
||||
# In case of positional arguments, metavar is always equal
|
||||
# to the list of options (e.g `METHOD`).
|
||||
metavar = None
|
||||
builder.add_options(raw_arg['options'], metavar=metavar)
|
||||
|
||||
description = _escape_and_dedent(raw_arg.get('description', ''))
|
||||
description = OPTION_HIGHLIGHT_RE.sub(
|
||||
lambda match: builder.boldify(match['option']),
|
||||
description
|
||||
)
|
||||
builder.write('\n' + description + '\n')
|
||||
|
||||
builder.separate()
|
||||
|
||||
|
||||
|
||||
return builder.build()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
for program_name, spec in [
|
||||
('http', core_options),
|
||||
('https', core_options),
|
||||
('httpie', manager_options),
|
||||
]:
|
||||
with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream:
|
||||
stream.write(to_man_page(program_name, spec))
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Reference in New Issue
Block a user