1from typing import Dict
2from typing import Iterable
3from typing import List
4from typing import Optional
5from typing import Tuple
6from typing import Union
7
8from clikit.utils._compat import OrderedDict
9
10from ..exceptions import CannotAddArgumentException
11from ..exceptions import CannotAddOptionException
12from ..exceptions import NoSuchArgumentException
13from ..exceptions import NoSuchOptionException
14from .args_format import ArgsFormat
15from .argument import Argument
16from .option import Option
17from .command_name import CommandName
18from .command_option import CommandOption
19
20
21class ArgsFormatBuilder(object):
22    """
23    A builder for ArgsFormat instances.
24    """
25
26    def __init__(self, base_format=None):  # type: (Optional[ArgsFormat]) -> None
27        self._base_format = base_format
28        self._command_names = []
29        self._command_options = OrderedDict()
30        self._command_options_by_short_name = OrderedDict()
31        self._arguments = OrderedDict()
32        self._options = OrderedDict()
33        self._options_by_short_name = OrderedDict()
34        self._has_multi_valued_arg = False
35        self._hash_optional_arg = False
36
37    @property
38    def base_format(self):  # type: () -> Optional[ArgsFormat]
39        return self._base_format
40
41    def set_command_names(
42        self, *command_names
43    ):  # type: (Tuple[CommandName]) -> ArgsFormatBuilder
44        self._command_names = []
45
46        self.add_command_names(*command_names)
47
48        return self
49
50    def add_command_names(
51        self, *command_names
52    ):  # type: (Tuple[CommandName]) -> ArgsFormatBuilder
53        for command_name in command_names:
54            self.add_command_name(command_name)
55
56        return self
57
58    def add_command_name(
59        self, command_name
60    ):  # type: (CommandName) -> ArgsFormatBuilder
61        self._command_names.append(command_name)
62
63        return self
64
65    def has_command_names(self, include_base=True):  # type: (bool) -> bool
66        if self._command_names:
67            return True
68
69        if include_base and self._base_format:
70            return self._base_format.has_command_names()
71
72        return False
73
74    def get_command_names(self, include_base=True):  # type: (bool) -> List[CommandName]
75        command_names = self._command_names
76
77        if include_base and self._base_format:
78            command_names = self._base_format.get_command_names() + command_names
79
80        return command_names
81
82    def set_command_options(
83        self, *command_options
84    ):  # type: (Tuple[CommandOption]) -> ArgsFormatBuilder
85        self._command_options = {}
86        self._command_options_by_short_name = {}
87
88        self.add_command_options(*command_options)
89
90    def add_command_options(
91        self, *command_options
92    ):  # type: (Tuple[CommandOption]) -> ArgsFormatBuilder
93        for command_option in command_options:
94            self.add_command_option(command_option)
95
96    def add_command_option(
97        self, command_option
98    ):  # type: (CommandOption) -> ArgsFormatBuilder
99        long_name = command_option.long_name
100        short_name = command_option.short_name
101        long_aliases = command_option.long_aliases
102        short_aliases = command_option.short_aliases
103
104        if self.has_option(long_name) or self.has_command_option(long_name):
105            raise CannotAddOptionException.already_exists(long_name)
106
107        for long_alias in long_aliases:
108            if self.has_option(long_alias) or self.has_command_option(long_alias):
109                raise CannotAddOptionException.already_exists(long_alias)
110
111        if self.has_option(short_name) or self.has_command_option(short_name):
112            raise CannotAddOptionException.already_exists(short_name)
113
114        for short_alias in short_aliases:
115            if self.has_option(short_alias) or self.has_command_option(short_alias):
116                raise CannotAddOptionException.already_exists(short_alias)
117
118        self._command_options[long_name] = command_option
119
120        if short_name:
121            self._command_options_by_short_name[short_name] = command_option
122
123        for long_alias in long_aliases:
124            self._command_options[long_alias] = command_option
125
126        for short_alias in short_aliases:
127            self._command_options_by_short_name[short_alias] = command_option
128
129        return self
130
131    def has_command_option(self, name, include_base=True):  # type: (str, bool) -> bool
132        if name in self._command_options or name in self._command_options_by_short_name:
133            return True
134
135        if include_base and self._base_format:
136            return self._base_format.has_command_option(name)
137
138        return False
139
140    def has_command_options(self, include_base=True):  # type: (bool) -> bool
141        if self._command_options:
142            return True
143
144        if include_base and self._base_format:
145            return self._base_format.has_command_options()
146
147        return False
148
149    def get_command_option(
150        self, name, include_base=True
151    ):  # type: (str, bool) -> CommandOption
152        if name in self._command_options:
153            return self._command_options[name]
154
155        if name in self._command_options_by_short_name:
156            return self._command_options_by_short_name[name]
157
158        if include_base and self._base_format:
159            return self._base_format.get_command_option(name)
160
161        raise NoSuchOptionException(name)
162
163    def get_command_options(
164        self, include_base=True
165    ):  # type: (bool) -> Iterable[CommandOption]
166        command_options = list(self._command_options.values())
167
168        if include_base and self._base_format:
169            command_options += self._base_format.get_command_options()
170
171        return command_options
172
173    def set_arguments(
174        self, *arguments
175    ):  # type: (Iterable[Argument]) -> ArgsFormatBuilder
176        self._arguments = {}
177        self._has_multi_valued_arg = False
178        self._hash_optional_arg = False
179
180        self.add_arguments(*arguments)
181
182        return self
183
184    def add_arguments(
185        self, *arguments
186    ):  # type: (Iterable[Argument]) -> ArgsFormatBuilder
187        for argument in arguments:
188            self.add_argument(argument)
189
190        return self
191
192    def add_argument(self, argument):  # type: (Argument) -> ArgsFormatBuilder
193        name = argument.name
194
195        if self.has_argument(name):
196            raise CannotAddArgumentException.already_exists(name)
197
198        if self.has_multi_valued_argument():
199            raise CannotAddArgumentException.cannot_add_after_multi_valued()
200
201        if argument.is_required() and self.has_optional_argument():
202            raise CannotAddArgumentException.cannot_add_required_after_optional()
203
204        if argument.is_multi_valued():
205            self._has_multi_valued_arg = True
206
207        if argument.is_optional():
208            self._hash_optional_arg = True
209
210        self._arguments[name] = argument
211
212        return self
213
214    def has_argument(
215        self, name, include_base=True
216    ):  # type: (Union[str, int], bool) -> bool
217        arguments = self.get_arguments(include_base)
218
219        if isinstance(name, int):
220            return name < len(arguments)
221
222        return name in arguments
223
224    def has_multi_valued_argument(self, include_base=True):  # type: (bool) -> bool
225        if self._has_multi_valued_arg:
226            return True
227
228        if include_base and self._base_format:
229            return self._base_format.has_multi_valued_argument()
230
231        return False
232
233    def has_optional_argument(self, include_base=True):  # type: (bool) -> bool
234        if self._hash_optional_arg:
235            return True
236
237        if include_base and self._base_format:
238            return self._base_format.has_optional_argument()
239
240        return False
241
242    def has_required_argument(self, include_base=True):  # type: (bool) -> bool
243        if not self._hash_optional_arg and self._arguments:
244            return True
245
246        if include_base and self._base_format:
247            return self._base_format.has_required_argument()
248
249        return False
250
251    def has_arguments(self, include_base=True):  # type: (bool) -> bool
252        if self._arguments:
253            return True
254
255        if include_base and self._base_format:
256            return self._base_format.has_arguments()
257
258        return False
259
260    def get_argument(
261        self, name, include_base=True
262    ):  # type: (Union[str, int], bool) -> Argument
263        if isinstance(name, int):
264            arguments = list(self.get_arguments(include_base).values())
265
266            if name >= len(arguments):
267                raise NoSuchArgumentException(name)
268        else:
269            arguments = self.get_arguments(include_base)
270
271            if name not in arguments:
272                raise NoSuchArgumentException(name)
273
274        return arguments[name]
275
276    def get_arguments(self, include_base=True):  # type: (bool) -> Dict[str, Argument]
277        arguments = self._arguments.copy()
278
279        if include_base and self._base_format:
280            arguments.update(self._base_format.get_arguments())
281
282        return arguments
283
284    def set_options(self, *options):  # type: (Iterable[Option]) -> ArgsFormatBuilder
285        self._options = {}
286        self._options_by_short_name = {}
287
288        self.add_options(*options)
289
290    def add_options(self, *options):  # type: (Iterable[Option]) -> ArgsFormatBuilder
291        for option in options:
292            self.add_option(option)
293
294    def add_option(self, option):  # type: (Option) -> ArgsFormatBuilder
295        long_name = option.long_name
296        short_name = option.short_name
297
298        if self.has_option(long_name) or self.has_command_option(long_name):
299            raise CannotAddOptionException.already_exists(long_name)
300
301        if self.has_option(short_name) or self.has_command_option(short_name):
302            raise CannotAddOptionException.already_exists(short_name)
303
304        self._options[long_name] = option
305
306        if short_name:
307            self._options_by_short_name[short_name] = option
308
309        return self
310
311    def has_option(self, name, include_base=True):  # type: (str, bool) -> bool
312        if name in self._options or name in self._options_by_short_name:
313            return True
314
315        if include_base and self._base_format:
316            return self._base_format.has_option(name)
317
318        return False
319
320    def has_options(self, include_base=True):  # type: (bool) -> bool
321        if self._options:
322            return True
323
324        if include_base and self._base_format:
325            return self._base_format.has_options()
326
327        return False
328
329    def get_option(self, name, include_base=True):  # type: (str, bool) -> Option
330        if name in self._options:
331            return self._options[name]
332
333        if name in self._options_by_short_name:
334            return self._options_by_short_name[name]
335
336        if include_base and self._base_format:
337            return self._base_format.get_option(name)
338
339        raise NoSuchOptionException(name)
340
341    def get_options(self, include_base=True):  # type: (bool) -> Dict[str, Option]
342        options = self._options.copy()
343
344        if include_base and self._base_format:
345            options.update(self._base_format.get_options())
346
347        return options
348
349    @property
350    def format(self):  # type: () -> ArgsFormat
351        return ArgsFormat(self, self._base_format)
352