codekingpro/portable-devtools
114k
1import re2import textwrap3from typing import AbstractSet, Iterable, Optional, Tuple4 5from rich.console import RenderableType6from rich.highlighter import RegexHighlighter7from rich.padding import Padding8from rich.table import Table9from rich.text import Text10 11from httpie.cli.constants import SEPARATOR_GROUP_ALL_ITEMS12from httpie.cli.options import Argument, ParserSpec, Qualifiers13from httpie.output.ui.palette import GenericColor14 15SEPARATORS = '|'.join(map(re.escape, SEPARATOR_GROUP_ALL_ITEMS))16 17STYLE_METAVAR = GenericColor.YELLOW18STYLE_SWITCH = GenericColor.GREEN19STYLE_PROGRAM_NAME = GenericColor.GREEN # .boldify()20STYLE_USAGE_OPTIONAL = GenericColor.GREY21STYLE_USAGE_REGULAR = GenericColor.WHITE22STYLE_USAGE_ERROR = GenericColor.RED23STYLE_USAGE_MISSING = GenericColor.YELLOW24STYLE_BOLD = 'bold'25 26MAX_CHOICE_CHARS = 8027 28LEFT_PADDING_2 = (0, 0, 0, 2)29LEFT_PADDING_3 = (0, 0, 0, 3)30LEFT_PADDING_4 = (0, 0, 0, 4)31LEFT_PADDING_5 = (0, 0, 0, 4)32 33LEFT_INDENT_2 = (1, 0, 0, 2)34LEFT_INDENT_3 = (1, 0, 0, 3)35LEFT_INDENT_BOTTOM_3 = (0, 0, 1, 3)36 37MORE_INFO_COMMANDS = """38To learn more, you can try:39 -> running 'http --manual'40 -> visiting our full documentation at https://httpie.io/docs/cli41"""42 43 44class OptionsHighlighter(RegexHighlighter):45 highlights = [46 r'(^|\W)(?P<option>\-{1,2}[\w|-]+)(?![a-zA-Z0-9])',47 r'(?P<bold>HTTPie)',48 ]49 50 51options_highlighter = OptionsHighlighter()52 53 54def unpack_argument(55 argument: Argument,56) -> Tuple[Text, Text]:57 opt1 = opt2 = ''58 59 style = None60 if argument.aliases:61 if len(argument.aliases) >= 2:62 opt2, opt1 = argument.aliases63 else:64 (opt1,) = argument.aliases65 else:66 opt1 = argument.metavar67 style = STYLE_USAGE_REGULAR68 69 return Text(opt1, style=style), Text(opt2)70 71 72def to_usage(73 spec: ParserSpec,74 *,75 program_name: Optional[str] = None,76 whitelist: AbstractSet[str] = frozenset()77) -> RenderableType:78 shown_arguments = [79 argument80 for group in spec.groups81 for argument in group.arguments82 if (not argument.aliases or whitelist.intersection(argument.aliases))83 ]84 85 # Sort the shown_arguments so that --dash options are86 # shown first87 shown_arguments.sort(key=lambda argument: argument.aliases, reverse=True)88 89 text = Text(program_name or spec.program, style=STYLE_BOLD)90 for argument in shown_arguments:91 text.append(' ')92 93 is_whitelisted = whitelist.intersection(argument.aliases)94 if argument.aliases:95 name = '/'.join(sorted(argument.aliases, key=len))96 else:97 name = argument.metavar98 99 nargs = argument.configuration.get('nargs')100 if nargs is Qualifiers.OPTIONAL:101 text.append('[' + name + ']', style=STYLE_USAGE_OPTIONAL)102 elif nargs is Qualifiers.ZERO_OR_MORE:103 text.append(104 '[' + name + ' ...]',105 style=STYLE_USAGE_OPTIONAL,106 )107 else:108 text.append(109 name,110 style=STYLE_USAGE_ERROR111 if is_whitelisted112 else STYLE_USAGE_REGULAR,113 )114 115 raw_form = argument.serialize()116 if raw_form.get('choices'):117 text.append(' ')118 text.append(119 '{' + ', '.join(raw_form['choices']) + '}',120 style=STYLE_USAGE_MISSING,121 )122 123 return text124 125 126# This part is loosely based on the rich-click's help message127# generation.128def to_help_message(129 spec: ParserSpec,130) -> Iterable[RenderableType]:131 yield Padding(132 options_highlighter(spec.description),133 LEFT_INDENT_2,134 )135 136 yield Padding(137 Text('Usage', style=STYLE_SWITCH),138 LEFT_INDENT_2,139 )140 yield Padding(to_usage(spec), LEFT_INDENT_3)141 142 group_rows = {}143 for group in spec.groups:144 options_rows = []145 146 for argument in group.arguments:147 if argument.is_hidden:148 continue149 150 opt1, opt2 = unpack_argument(argument)151 if opt2:152 opt1.append('/')153 opt1.append(opt2)154 155 # Column for a metavar, if we have one156 metavar = Text(style=STYLE_METAVAR)157 metavar.append(argument.configuration.get('metavar', ''))158 159 if opt1 == metavar:160 metavar = Text('')161 162 raw_form = argument.serialize()163 desc = raw_form.get('short_description', '')164 if raw_form.get('choices'):165 desc += ' (choices: '166 desc += textwrap.shorten(167 ', '.join(raw_form.get('choices')),168 MAX_CHOICE_CHARS,169 )170 desc += ')'171 172 rows = [173 Padding(174 options_highlighter(opt1),175 LEFT_PADDING_2,176 ),177 metavar,178 options_highlighter(desc),179 ]180 181 options_rows.append(rows)182 if argument.configuration.get('nested_options'):183 options_rows.extend(184 [185 (186 Padding(187 Text(188 key,189 style=STYLE_USAGE_OPTIONAL,190 ),191 LEFT_PADDING_4,192 ),193 value,194 dec,195 )196 for key, value, dec in argument.nested_options197 ]198 )199 200 group_rows[group.name] = options_rows201 202 options_table = Table(highlight=False, box=None, show_header=False)203 for group_name, options_rows in group_rows.items():204 options_table.add_row(Text(), Text(), Text())205 options_table.add_row(206 Text(group_name, style=STYLE_SWITCH),207 Text(),208 Text(),209 )210 options_table.add_row(Text(), Text(), Text())211 for row in options_rows:212 options_table.add_row(*row)213 214 yield Padding(215 Text('Options', style=STYLE_SWITCH),216 LEFT_INDENT_2,217 )218 yield Padding(options_table, LEFT_PADDING_2)219 yield Padding(220 Text('More Information', style=STYLE_SWITCH),221 LEFT_INDENT_2,222 )223 yield Padding(224 MORE_INFO_COMMANDS.rstrip('\n'),225 LEFT_PADDING_3226 )227 yield Padding(228 spec.epilog.rstrip('\n'),229 LEFT_INDENT_BOTTOM_3,230 )231 