1# -*- coding: utf-8 -*- 2from __future__ import unicode_literals 3 4import json 5 6from typing import Any 7from typing import Iterable 8 9from clikit.api.args.format import Argument 10from clikit.api.args.format import ArgsFormat 11from clikit.api.args.format import Option 12from clikit.api.io import IO 13 14from clikit.ui import Component 15from clikit.ui.components import EmptyLine 16from clikit.ui.components import LabeledParagraph 17from clikit.ui.components import Paragraph 18from clikit.ui.layout import BlockLayout 19 20 21class AbstractHelp(Component): 22 """ 23 Base class for rendering help pages. 24 """ 25 26 def render(self, io, indentation=0): # type: (IO, int) -> None 27 layout = BlockLayout() 28 29 self._render_help(layout) 30 31 layout.render(io, indentation) 32 33 def _render_help(self, layout): # type: (BlockLayout) -> None 34 raise NotImplementedError() 35 36 def _render_arguments( 37 self, layout, arguments 38 ): # type: (BlockLayout, Iterable[Argument]) -> None 39 layout.add(Paragraph("<b>ARGUMENTS</b>")) 40 41 with layout.block(): 42 for argument in arguments: 43 self._render_argument(layout, argument) 44 45 layout.add(EmptyLine()) 46 47 def _render_argument( 48 self, layout, argument 49 ): # type: (BlockLayout, Argument) -> None 50 description = argument.description 51 name = "<c1><{}></c1>".format(argument.name) 52 default = argument.default 53 54 if default is not None and (not isinstance(default, list) or len(default) > 0): 55 description += " <b>{}</b>".format(self._format_value(default)) 56 57 layout.add(LabeledParagraph(name, description)) 58 59 def _render_options( 60 self, layout, options 61 ): # type: (BlockLayout, Iterable[Option]) -> None 62 layout.add(Paragraph("<b>OPTIONS</b>")) 63 64 with layout.block(): 65 for option in options: 66 self._render_option(layout, option) 67 68 layout.add(EmptyLine()) 69 70 def _render_global_options( 71 self, layout, options 72 ): # type: (BlockLayout, Iterable[Option]) -> None 73 layout.add(Paragraph("<b>GLOBAL OPTIONS</b>")) 74 75 with layout.block(): 76 for option in options: 77 self._render_option(layout, option) 78 79 layout.add(EmptyLine()) 80 81 def _render_option(self, layout, option): # type: (BlockLayout, Option) -> None 82 description = option.description 83 default = option.default 84 85 alternative_name = None 86 if option.is_long_name_preferred(): 87 preferred_name = "--{}".format(option.long_name) 88 if option.short_name: 89 alternative_name = "-{}".format(option.short_name) 90 else: 91 preferred_name = "-{}".format(option.short_name) 92 alternative_name = "--{}".format(option.long_name) 93 94 name = "<c1>{}</c1>".format(preferred_name) 95 if alternative_name: 96 name += " ({})".format(alternative_name) 97 98 if ( 99 option.accepts_value() 100 and default is not None 101 and (not isinstance(default, list) or len(default) > 0) 102 ): 103 description += " <b>(default: {})</b>".format(self._format_value(default)) 104 105 if option.is_multi_valued(): 106 description += " <b>(multiple values allowed)</b>" 107 108 layout.add(LabeledParagraph(name, description)) 109 110 def _render_synopsis( 111 self, layout, args_format, app_name, prefix="", last_optional=False 112 ): # type: (BlockLayout, ArgsFormat, str, str, bool) -> None 113 name_parts = [] 114 argument_parts = [] 115 116 name_parts.append("<u>{}</u>".format(app_name or "console")) 117 118 for command_name in args_format.get_command_names(): 119 name_parts.append("<u>{}</u>".format(command_name.string)) 120 121 for command_option in args_format.get_command_options(): 122 if command_option.is_long_name_preferred(): 123 name_parts.append("--{}".format(command_option.long_name)) 124 else: 125 name_parts.append("-{}".format(command_option.short_name)) 126 127 if last_optional: 128 name_parts[-1] = "[{}]".format(name_parts[-1]) 129 130 for option in args_format.get_options(False).values(): 131 # \xC2\xA0 is a non-breaking space 132 if option.is_value_required(): 133 fmt = "{}\u00A0<{}>" 134 elif option.is_value_optional(): 135 fmt = "{}\u00A0[<{}>]" 136 else: 137 fmt = "{}" 138 139 if option.is_long_name_preferred(): 140 option_name = "--{}".format(option.long_name) 141 else: 142 option_name = "-{}".format(option.short_name) 143 144 argument_parts.append( 145 "[{}]".format(fmt.format(option_name, option.value_name)) 146 ) 147 148 for argument in args_format.get_arguments().values(): 149 arg_name = argument.name 150 151 argument_parts.append( 152 ("<{}>" if argument.is_required() else "[<{}>]").format( 153 arg_name + str(int(argument.is_multi_valued()) or "") 154 ) 155 ) 156 157 if argument.is_multi_valued(): 158 argument_parts.append("... [<{}N>]".format(arg_name)) 159 160 args_opts = " ".join(argument_parts) 161 name = " ".join(name_parts) 162 163 layout.add(LabeledParagraph(prefix + name, args_opts, 1, False)) 164 165 def _format_value(self, value): # type: (Any) -> str 166 return json.dumps(value) 167