Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/awscli/argparser.py
1566 views
1
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License"). You
4
# may not use this file except in compliance with the License. A copy of
5
# the License is located at
6
#
7
# http://aws.amazon.com/apache2.0/
8
#
9
# or in the "license" file accompanying this file. This file is
10
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
# ANY KIND, either express or implied. See the License for the specific
12
# language governing permissions and limitations under the License.
13
import argparse
14
import sys
15
from difflib import get_close_matches
16
17
AWS_CLI_V2_MESSAGE = (
18
'Note: AWS CLI version 2, the latest major version '
19
'of the AWS CLI, is now stable and recommended for general '
20
'use. For more information, see the AWS CLI version 2 '
21
'installation instructions at: https://docs.aws.amazon.com/cli/'
22
'latest/userguide/install-cliv2.html'
23
)
24
25
HELP_BLURB = (
26
"To see help text, you can run:\n"
27
"\n"
28
" aws help\n"
29
" aws <command> help\n"
30
" aws <command> <subcommand> help\n"
31
)
32
USAGE = (
33
"\r%s\n\n"
34
"usage: aws [options] <command> <subcommand> "
35
"[<subcommand> ...] [parameters]\n"
36
"%s" % (AWS_CLI_V2_MESSAGE, HELP_BLURB)
37
)
38
39
40
class CommandAction(argparse.Action):
41
"""Custom action for CLI command arguments
42
43
Allows the choices for the argument to be mutable. The choices
44
are dynamically retrieved from the keys of the referenced command
45
table
46
"""
47
48
def __init__(self, option_strings, dest, command_table, **kwargs):
49
self.command_table = command_table
50
super().__init__(
51
option_strings, dest, choices=self.choices, **kwargs
52
)
53
54
def __call__(self, parser, namespace, values, option_string=None):
55
setattr(namespace, self.dest, values)
56
57
@property
58
def choices(self):
59
return list(self.command_table.keys())
60
61
@choices.setter
62
def choices(self, val):
63
# argparse.Action will always try to set this value upon
64
# instantiation, but this value should be dynamically
65
# generated from the command table keys. So make this a
66
# NOOP if argparse.Action tries to set this value.
67
pass
68
69
70
class CLIArgParser(argparse.ArgumentParser):
71
Formatter = argparse.RawTextHelpFormatter
72
73
# When displaying invalid choice error messages,
74
# this controls how many options to show per line.
75
ChoicesPerLine = 2
76
77
def _check_value(self, action, value):
78
"""
79
It's probably not a great idea to override a "hidden" method
80
but the default behavior is pretty ugly and there doesn't
81
seem to be any other way to change it.
82
"""
83
# converted value must be one of the choices (if specified)
84
if action.choices is not None and value not in action.choices:
85
msg = ['Invalid choice, valid choices are:\n']
86
for i in range(len(action.choices))[:: self.ChoicesPerLine]:
87
current = []
88
for choice in action.choices[i : i + self.ChoicesPerLine]:
89
current.append('%-40s' % choice)
90
msg.append(' | '.join(current))
91
possible = get_close_matches(value, action.choices, cutoff=0.8)
92
if possible:
93
extra = ['\n\nInvalid choice: %r, maybe you meant:\n' % value]
94
for word in possible:
95
extra.append(' * %s' % word)
96
msg.extend(extra)
97
raise argparse.ArgumentError(action, '\n'.join(msg))
98
99
def parse_known_args(self, args, namespace=None):
100
parsed, remaining = super().parse_known_args(
101
args, namespace
102
)
103
terminal_encoding = getattr(sys.stdin, 'encoding', 'utf-8')
104
if terminal_encoding is None:
105
# In some cases, sys.stdin won't have an encoding set,
106
# (e.g if it's set to a StringIO). In this case we just
107
# default to utf-8.
108
terminal_encoding = 'utf-8'
109
for arg, value in vars(parsed).items():
110
if isinstance(value, bytes):
111
setattr(parsed, arg, value.decode(terminal_encoding))
112
elif isinstance(value, list):
113
encoded = []
114
for v in value:
115
if isinstance(v, bytes):
116
encoded.append(v.decode(terminal_encoding))
117
else:
118
encoded.append(v)
119
setattr(parsed, arg, encoded)
120
return parsed, remaining
121
122
123
class MainArgParser(CLIArgParser):
124
Formatter = argparse.RawTextHelpFormatter
125
126
def __init__(
127
self,
128
command_table,
129
version_string,
130
description,
131
argument_table,
132
prog=None,
133
):
134
super().__init__(
135
formatter_class=self.Formatter,
136
add_help=False,
137
conflict_handler='resolve',
138
description=description,
139
usage=USAGE,
140
prog=prog,
141
)
142
self._build(command_table, version_string, argument_table)
143
144
def _create_choice_help(self, choices):
145
help_str = ''
146
for choice in sorted(choices):
147
help_str += '* %s\n' % choice
148
return help_str
149
150
def _build(self, command_table, version_string, argument_table):
151
for argument_name in argument_table:
152
argument = argument_table[argument_name]
153
argument.add_to_parser(self)
154
self.add_argument(
155
'--version',
156
action="version",
157
version=version_string,
158
help='Display the version of this tool',
159
)
160
self.add_argument(
161
'command', action=CommandAction, command_table=command_table
162
)
163
164
165
class ServiceArgParser(CLIArgParser):
166
def __init__(self, operations_table, service_name):
167
super().__init__(
168
formatter_class=argparse.RawTextHelpFormatter,
169
add_help=False,
170
conflict_handler='resolve',
171
usage=USAGE,
172
)
173
self._build(operations_table)
174
self._service_name = service_name
175
176
def _build(self, operations_table):
177
self.add_argument(
178
'operation', action=CommandAction, command_table=operations_table
179
)
180
181
182
class ArgTableArgParser(CLIArgParser):
183
"""CLI arg parser based on an argument table."""
184
185
def __init__(self, argument_table, command_table=None):
186
# command_table is an optional subcommand_table. If it's passed
187
# in, then we'll update the argparse to parse a 'subcommand' argument
188
# and populate the choices field with the command table keys.
189
super().__init__(
190
formatter_class=self.Formatter,
191
add_help=False,
192
usage=USAGE,
193
conflict_handler='resolve',
194
)
195
if command_table is None:
196
command_table = {}
197
self._build(argument_table, command_table)
198
199
def _build(self, argument_table, command_table):
200
for arg_name in argument_table:
201
argument = argument_table[arg_name]
202
argument.add_to_parser(self)
203
if command_table:
204
self.add_argument(
205
'subcommand',
206
action=CommandAction,
207
command_table=command_table,
208
nargs='?',
209
)
210
211
def parse_known_args(self, args, namespace=None):
212
if len(args) == 1 and args[0] == 'help':
213
namespace = argparse.Namespace()
214
namespace.help = 'help'
215
return namespace, []
216
else:
217
return super().parse_known_args(
218
args, namespace
219
)
220
221