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