You've already forked httpie-cli
mirror of
https://github.com/httpie/cli.git
synced 2025-06-27 00:51:16 +02:00
Man page fixes (#1364)
- Highlighting for options (-x, --x) now doesn't strip the prefix (may be whitespace). - Escape sequences are now cross-platform compatible (directly taken by groff/troff [man's renderer]) - Now we check for the section before displaying the man pages. - On MacOS, there is HTTP(n) which is different from our HTTP(1). This used to conflict with it, and we showed the wrong page. Now we specifically ask foir HTTP(1). - Errors that might happen (e.g non executable man command) is now suppressed. So in the worst case (if anything regarding man execution goes wrong), we'll always display the manual. - Docs for man pages. - HTTPie man pages. - Epilog for the man pages (see also) - Auto-generated comments.
This commit is contained in:
@ -14,16 +14,19 @@ from httpie.utils import split
|
||||
|
||||
# Escape certain characters so they are rendered properly on
|
||||
# all terminals.
|
||||
# https://man7.org/linux/man-pages/man7/groff_char.7.html
|
||||
ESCAPE_MAP = {
|
||||
"'": "\\'",
|
||||
'~': '\\~',
|
||||
'’': "\\'",
|
||||
'\\': '\\\\',
|
||||
'"': '\[dq]',
|
||||
"'": '\[aq]',
|
||||
'~': '\(ti',
|
||||
'’': "\(ga",
|
||||
'\\': '\e',
|
||||
}
|
||||
ESCAPE_MAP = {ord(key): value for key, value in ESCAPE_MAP.items()}
|
||||
|
||||
EXTRAS_DIR = Path(__file__).parent.parent
|
||||
MAN_PAGE_PATH = EXTRAS_DIR / 'man'
|
||||
PROJECT_ROOT = EXTRAS_DIR.parent
|
||||
|
||||
OPTION_HIGHLIGHT_RE = re.compile(
|
||||
OptionsHighlighter.highlights[0]
|
||||
@ -57,6 +60,18 @@ class ManPageBuilder:
|
||||
def separate(self) -> None:
|
||||
self.source.append('.PP')
|
||||
|
||||
def format_desc(self, desc: str) -> str:
|
||||
description = _escape_and_dedent(desc)
|
||||
description = OPTION_HIGHLIGHT_RE.sub(
|
||||
# Boldify the option part, but don't remove the prefix (start of the match).
|
||||
lambda match: match[1] + self.boldify(match['option']),
|
||||
description
|
||||
)
|
||||
return description
|
||||
|
||||
def add_comment(self, comment: str) -> None:
|
||||
self.source.append(f'.\\" {comment}')
|
||||
|
||||
def add_options(self, options: Iterable[str], *, metavar: Optional[str] = None) -> None:
|
||||
text = ", ".join(map(self.boldify, options))
|
||||
if metavar:
|
||||
@ -92,8 +107,13 @@ def _escape_and_dedent(text: str) -> str:
|
||||
return '\n'.join(lines).translate(ESCAPE_MAP)
|
||||
|
||||
|
||||
def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
def to_man_page(program_name: str, spec: ParserSpec, *, is_top_level_cmd: bool = False) -> str:
|
||||
builder = ManPageBuilder()
|
||||
builder.add_comment(
|
||||
f"This file is auto-generated from the parser declaration "
|
||||
+ (f"in {Path(spec.source_file).relative_to(PROJECT_ROOT)} " if spec.source_file else "")
|
||||
+ f"by {Path(__file__).relative_to(PROJECT_ROOT)}."
|
||||
)
|
||||
|
||||
builder.title_line(
|
||||
full_name='HTTPie',
|
||||
@ -104,10 +124,19 @@ def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
builder.set_name(program_name)
|
||||
|
||||
with builder.section('SYNOPSIS'):
|
||||
builder.write(render_as_string(to_usage(spec, program_name=program_name)))
|
||||
# `http` and `https` are commands that can be directly used, so they can have
|
||||
# have a valid usage. But `httpie` is a top-level command with multiple sub commands,
|
||||
# so for the synopsis we'll only reference the `httpie` name.
|
||||
if is_top_level_cmd:
|
||||
synopsis = program_name
|
||||
else:
|
||||
synopsis = render_as_string(to_usage(spec, program_name=program_name))
|
||||
builder.write(synopsis)
|
||||
|
||||
with builder.section('DESCRIPTION'):
|
||||
builder.write(spec.description)
|
||||
if spec.man_page_hint:
|
||||
builder.write(spec.man_page_hint)
|
||||
|
||||
for index, group in enumerate(spec.groups, 1):
|
||||
with builder.section(group.name):
|
||||
@ -127,28 +156,26 @@ def to_man_page(program_name: str, spec: ParserSpec) -> str:
|
||||
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')
|
||||
desc = builder.format_desc(raw_arg.get('description', ''))
|
||||
builder.write('\n' + desc + '\n')
|
||||
|
||||
builder.separate()
|
||||
|
||||
if spec.epilog:
|
||||
with builder.section('SEE ALSO'):
|
||||
builder.write(builder.format_desc(spec.epilog))
|
||||
|
||||
|
||||
return builder.build()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
for program_name, spec in [
|
||||
('http', core_options),
|
||||
('https', core_options),
|
||||
('httpie', manager_options),
|
||||
for program_name, spec, config in [
|
||||
('http', core_options, {}),
|
||||
('https', core_options, {}),
|
||||
('httpie', manager_options, {'is_top_level_cmd': True}),
|
||||
]:
|
||||
with open((MAN_PAGE_PATH / program_name).with_suffix('.1'), 'w') as stream:
|
||||
stream.write(to_man_page(program_name, spec))
|
||||
stream.write(to_man_page(program_name, spec, **config))
|
||||
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user