| Server IP : 3.96.16.70 / Your IP : 216.73.216.15 Web Server : Apache System : Linux ip-172-31-26-103.ca-central-1.compute.internal 6.1.163-186.299.amzn2023.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Feb 24 16:35:42 UTC 2026 x86_64 User : ec2-user ( 1000) PHP Version : 8.4.18 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /lib/python3.9/site-packages/awscli/autocomplete/local/ |
Upload File : |
# Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
import os
import re
from awscli.autocomplete import LazyClientCreator
from awscli.autocomplete.completer import BaseCompleter, CompletionResult
from awscli.autocomplete.filters import startswith_filter
def strip_html_tags_and_newlines(text):
clean = re.compile('<.*?>')
return re.sub(clean, '', text).replace('\n', '')
class ProfileCompleter(BaseCompleter):
def __init__(
self,
session=None,
response_filter=startswith_filter,
session_creator=None,
):
self._session = session
self._filter = response_filter
self._session_creator = session_creator
if self._session_creator is None:
self._session_creator = LazyClientCreator()
def complete(self, parsed):
if (
parsed.current_param == 'profile'
and parsed.current_fragment is not None
):
return self._filter(parsed.current_fragment, self._get_profiles())
def _get_profiles(self):
return map(
lambda x: CompletionResult(name=x),
self._get_session().available_profiles,
)
def _get_session(self):
if self._session is None:
self._session = self._session_creator.create_session()
return self._session
class RegionCompleter(BaseCompleter):
def __init__(
self,
session=None,
response_filter=startswith_filter,
session_creator=None,
):
self._session = session
self._filter = response_filter
self._session_creator = session_creator
if self._session_creator is None:
self._session_creator = LazyClientCreator()
def complete(self, parsed):
if (
parsed.current_param == 'region'
and parsed.current_fragment is not None
):
if len(parsed.lineage) > 1:
service_name = parsed.lineage[1]
else:
service_name = 'ec2'
return self._filter(
parsed.current_fragment,
self._get_region_completions(service_name),
)
def _get_region_completions(self, service_name):
return map(
lambda x: CompletionResult(name=x), self._get_regions(service_name)
)
def _get_regions(self, service_name):
if self._session is None:
self._session = self._session_creator.create_session()
return self._session.get_available_regions(service_name=service_name)
class FilePathCompleter(BaseCompleter):
def __init__(self, path_completer=None, response_filter=startswith_filter):
self._path_completer = path_completer
self._filter = response_filter
@property
def path_completer(self):
if self._path_completer is None:
from prompt_toolkit.completion import PathCompleter
self._path_completer = PathCompleter(expanduser=True)
return self._path_completer
def complete(self, parsed):
if parsed.current_fragment and parsed.current_fragment.startswith(
('file://', 'fileb://')
):
from prompt_toolkit.document import Document
for prefix in ['file://', 'fileb://']:
if parsed.current_fragment.startswith(prefix):
filename_part = parsed.current_fragment[len(prefix) :]
break
# PathCompleter makes really strange suggestions for lonely ~
# "username/", so in this case we handle it by ourselves
if filename_part == '~':
return [CompletionResult(''.join([prefix, '~%s' % os.sep]))]
dirname = os.path.dirname(filename_part)
if dirname and dirname != os.sep:
dirname = f'{dirname}{os.sep}'
document = Document(text=dirname, cursor_position=len(dirname))
completions = self.path_completer.get_completions(document, None)
results = [
CompletionResult(
f'{prefix}'
f'{os.path.join(dirname, completion.display[0][1])}',
display_text=completion.display[0][1],
)
for completion in completions
]
return self._filter(os.path.basename(filename_part), results)
class ModelIndexCompleter(BaseCompleter):
def __init__(
self, index, cli_driver_fetcher=None, response_filter=startswith_filter
):
self._index = index
self._cli_driver_fetcher = cli_driver_fetcher
self._filter = response_filter
def complete(self, parsed):
are_unparsed_items_paths = [
bool(re.search('[./\\\\:]|(--)', item))
for item in parsed.unparsed_items
]
if parsed.unparsed_items and all(are_unparsed_items_paths):
# If all the unparsed items are file paths, then we auto-complete
# options for the current fragment. This is to provide
# auto-completion options for commands that take file paths, such
# as `aws s3 mv ./path/to/file1 s3://path/to/file2`. We make an
# exception for the last unparsed item if it starts with '--',
# which indicates that the user is looking to complete an --option.
#
# Note that parsed.current_fragment may contain an empty string, so
# we provide auto-completion options for the current_command
# instead.
if not parsed.current_fragment:
parsed.current_fragment = parsed.current_command
return self._filter(
parsed.current_fragment, self._complete_options(parsed)
)
elif (
parsed.unparsed_items
or parsed.current_fragment is None
or parsed.current_param
):
# If there's ever any unparsed items, then the parser
# encountered something it didn't understand. We won't
# attempt to auto-complete anything here.
return
current_fragment = parsed.current_fragment
if current_fragment.startswith('--'):
# We could technically offer completion of options
# if the last fragment is an empty string, but to avoid
# dumping too much information back to the user, we only
# offer completions for options if the value starts with
# '--'.
prefix = parsed.current_fragment
if prefix == '--':
prefix = ''
return self._filter(prefix, self._complete_options(parsed))
else:
# We only offer completion of options if there are no
# more commands to complete.
commands = self._complete_command(parsed)
if not commands:
return self._filter(
parsed.current_fragment, self._complete_options(parsed)
)
return self._filter(parsed.current_fragment, commands)
def _complete_command(self, parsed):
lineage = parsed.lineage + [parsed.current_command]
offset = -len(parsed.current_fragment)
result = [
CompletionResult(name, help_text=full_name, starting_index=offset)
for name, full_name in self._index.commands_with_full_name(lineage)
]
return result
def _outfile_filter(self, completion):
if completion.name == '--outfile':
completion.name = 'outfile'
completion.display_text = 'outfile (required)'
return completion
def _complete_options(self, parsed):
# '--endpoint' -> 'endpoint'
offset = -len(parsed.current_fragment)
is_in_global_scope = (
parsed.lineage == [] and parsed.current_command == 'aws'
)
arg_names = self._index.arg_names(
lineage=parsed.lineage, command_name=parsed.current_command
)
results = []
if not is_in_global_scope:
for arg_name in arg_names:
arg_data = self._index.get_argument_data(
lineage=parsed.lineage,
command_name=parsed.current_command,
arg_name=arg_name,
)
help_text = None
if self._cli_driver_fetcher:
help_text = strip_html_tags_and_newlines(
self._cli_driver_fetcher.get_argument_documentation(
parsed.lineage, parsed.current_command, arg_name
)
)
results.append(
self._outfile_filter(
CompletionResult(
'--%s' % arg_name,
starting_index=offset,
required=arg_data.required,
cli_type_name=arg_data.type_name,
help_text=help_text,
)
)
)
# Global params apply to any scope
self._inject_global_params(parsed, results)
return [
result
for result in results
if result.name.strip('--')
not in (list(parsed.parsed_params) + list(parsed.global_params))
]
def _inject_global_params(self, parsed, results):
offset = -len(parsed.current_fragment)
arg_data = self._index.get_global_arg_data()
global_param_completions = []
for arg_name, type_name, *_, help_text in arg_data:
help_text = None
if self._cli_driver_fetcher:
help_text = strip_html_tags_and_newlines(
self._cli_driver_fetcher.get_global_arg_documentation(
arg_name
)
)
global_param_completions.append(
CompletionResult(
'--%s' % arg_name,
starting_index=offset,
required=False,
cli_type_name=type_name,
help_text=help_text,
)
)
results.extend(global_param_completions)
class ShorthandCompleter(BaseCompleter):
_PARENS = {"[": "]", "{": "}"}
_DUMMY_KEY_VALUE = 'cli_placeholder_for_key_value_replacement'
_DUMMY_VALUE = 'cli_placeholder_for_value_replacement'
_DUMMY_EQ_VALUE = 'cli_placeholder_for_key_eq_replacement'
_VALUE_PREFIXES = {'structure': '{', 'list': '[', 'map': '{'}
def __init__(
self, cli_driver_fetcher=None, response_filter=startswith_filter
):
self._filter = response_filter
self._cli_driver_fetcher = cli_driver_fetcher
self._shorthand_parser = None
@property
def shorthand_parser(self):
if self._shorthand_parser is None:
from awscli.shorthand import ShorthandParser
self._shorthand_parser = ShorthandParser()
return self._shorthand_parser
def complete(self, parsed):
if parsed.current_param and self._cli_driver_fetcher:
results = []
arg_model = self._cli_driver_fetcher.get_argument_model(
parsed.lineage, parsed.current_command, parsed.current_param
)
if arg_model is None:
results = self._get_prompt_for_global_arg(
parsed.current_param, parsed.current_fragment
)
return results
parsed_input = self._parse_fragment(parsed.current_fragment)
if parsed_input is not None:
results = self._get_completion(arg_model, parsed_input)
# prefix all the completions name property with the
# current_fragment to make correct insert into the user
# input line
fragment = parsed.current_fragment or ''
results = self._set_results_name(results, fragment)
brackets_completion = self._get_close_brackets_completion(
parsed.current_fragment
)
if brackets_completion is not None:
results.append(brackets_completion)
return results or None
def _get_prompt_for_global_arg(self, arg_name, prefix):
choices = self._cli_driver_fetcher.get_global_arg_choices(arg_name)
if choices and prefix is not None:
results = self._filter(
prefix,
[
CompletionResult(prefix, display_text=choice)
for choice in choices
],
)
return self._set_results_name(results, prefix)
def _set_results_name(self, results, fragment):
for result in results:
name_part_len = len(fragment) - len(result.name)
result.name = "%s%s" % (
fragment[:name_part_len],
result.display_text,
)
return results
def _close_brackets(self, fragment):
# If there any unclosed brackets in the text we try to close them
# and we return part with closing brackets if they are "closable"
stack = []
for char in fragment:
if char in self._PARENS.keys():
stack.append(char)
elif char in self._PARENS.values():
if stack and self._PARENS[stack[-1]] == char:
stack.pop()
else:
return ''
return ''.join(self._PARENS[paren] for paren in reversed(stack))
def _parse_fragment(self, fragment, attempt=1):
# Try complete fragment to make it parsable and we handle such cases
#
# you just started typing input it will return a dict with input as key
# --option foo -> {'foo': ''}
#
# you typed a comma
# --option foo=1, -> foo=1,DUMMY_KEY_VALUE=DUMMY_KEY_VALUE
#
# you typed equal sign
# --option foo= -> foo=DUMMY_VALUE
#
# after that we close all the brackets if needed and try to parse
# if we get a parsing error it can be that you have some partially
# entered nested structure in such a case we'll try to complete it
# and make one more attempt
# --option foo={bar -> foo={bar=DUMMY_EQ_VALUE
from awscli.shorthand import ShorthandParseError
if fragment is None:
return None
if fragment == '':
return {}
if '=' not in fragment:
return {fragment: None}
if fragment.endswith(','):
fragment += f'{self._DUMMY_KEY_VALUE}={self._DUMMY_KEY_VALUE}'
elif fragment.endswith('='):
fragment += self._DUMMY_VALUE
try:
return self.shorthand_parser.parse(
f'{fragment}{self._close_brackets(fragment)}'
)
except ShorthandParseError:
if attempt == 1:
return self._parse_fragment(
f'{fragment}={self._DUMMY_EQ_VALUE}', attempt + 1
)
# if we get here it means that we can't make it parsable and can't
# suggest anything so the only solution is to wait till user enter more
def _get_completion(self, arg_model, parsed_input):
completion = getattr(
self, "_get_prompt_for_%s" % arg_model.type_name, self._no_prompt
)(arg_model, parsed_input)
if completion is None:
return []
return completion
def _get_last_key_value(self, parsed_input):
# returns last key and its value in top level structure
# from parsed_input
# - if value is not None the next step will be to autopropmt value
# if possible, value='' - means that user typed '=' but hasn't
# started typing value yet
# - if value is None but key is not None the next step will be to
# autopropmt key if possible, key='' - means that user
# typed ',' after previous key=value pair but hasn't started typing
# key yet
# - if both None - it means it's not a dict (maybe string or integer)
# and we won't prompt anything
if not isinstance(parsed_input, dict):
return None, None
if not parsed_input:
return '', None
key = list(parsed_input)[-1]
value = parsed_input[key]
# if get in situation when foo=a,bar it will parsed as
# foo=['a','bar'] and we will assume that "bar" is the next key
if value and isinstance(value, list):
key = value[-1]
value = None
# remove dummy_parts if they were added before
elif value == key == self._DUMMY_KEY_VALUE:
key = ''
value = None
elif value == self._DUMMY_EQ_VALUE:
value = None
return key, value
def _no_prompt(self, *args, **kwargs):
# for all types we do not support
return None
def _get_close_brackets_completion(self, fragment):
if not fragment:
return
close_brackets = self._close_brackets(fragment)
if close_brackets:
return CompletionResult(
f'{fragment}{close_brackets}',
display_text='Autoclose brackets',
)
def _get_prompt_for_string(self, arg_model, parsed_input):
# If string shape is a value for a list of enums it'll be an instance
# of dict because parsing method tries to convert it to a valid
# shorthand expression. In such a case we need to take the first and
# only key of the dict if it has keys or an empty string
if getattr(arg_model, 'enum', False):
prefix = parsed_input or ''
if isinstance(prefix, dict):
prefix = list(prefix.keys())[0]
if prefix == self._DUMMY_VALUE:
prefix = ''
return self._filter(
prefix,
[
CompletionResult(prefix, display_text=enum)
for enum in arg_model.enum
],
)
def _get_prompt_for_boolean(self, arg_model, parsed_input):
all_results = ['true', 'false']
prefix = parsed_input
if prefix == self._DUMMY_VALUE:
prefix = ''
return self._filter(
prefix,
[
CompletionResult(prefix, display_text=result)
for result in all_results
],
)
def _get_prompt_for_list(self, arg_model, parsed_input):
# we have two way we can enter lists:
# - if it's a top level structure it will be space separated and
# parsed_input will contain the last element of the list
# - if it's a nested structure we'll get the whole list as an input
# and take the last item
# - if list is empty we'll pass None to the next completer
if isinstance(parsed_input, list):
if parsed_input:
parsed_input = parsed_input[-1]
else:
parsed_input = None
return self._get_completion(arg_model.member, parsed_input)
def _get_prompt_for_map(self, arg_model, parsed_input):
last_key, last_value = self._get_last_key_value(parsed_input)
if last_value is not None:
return self._get_completion(arg_model.value, last_value)
return self._get_completion(arg_model.key, last_key)
def _get_prompt_for_structure(self, arg_model, parsed_input):
last_key, last_value = self._get_last_key_value(parsed_input)
if last_key is None and last_value is None:
return None
# if we have a last_value it can be nested structure so let's try
# to get suggestion for it
if last_key and last_value is not None:
if not arg_model.members.get(last_key):
# key exists but we don't have such key in model
return None
return self._get_completion(
arg_model.members[last_key], last_value
)
entered_keys = set(parsed_input) - set([last_key])
return self._get_struct_keys_completions(
arg_model, entered_keys, last_key
)
def _get_struct_keys_completions(self, arg_model, entered_keys, last_key):
# get suggestions for the structure keys, as CompletionResult.name
# we return only part of the suggestion that has not been entered yet
results = []
for member_name, member in arg_model.members.items():
if member_name not in entered_keys:
display_text = '%s=%s' % (
member_name,
self._VALUE_PREFIXES.get(member.type_name, ''),
)
results.append(
CompletionResult(
last_key,
help_text=strip_html_tags_and_newlines(
member.documentation
),
cli_type_name=member.type_name,
display_text=display_text,
)
)
return self._filter(last_key, results)
class QueryCompleter(BaseCompleter):
def __init__(
self, cli_driver_fetcher=None, response_filter=startswith_filter
):
self._filter = response_filter
self._cli_driver_fetcher = cli_driver_fetcher
self._argument_generator = None
self._jmespath = None
@property
def jmespath(self):
if self._jmespath is None:
import jmespath
self._jmespath = jmespath
return self._jmespath
@property
def argument_generator(self):
if self._argument_generator is None:
from botocore.utils import ArgumentGenerator
self._argument_generator = ArgumentGenerator
return self._argument_generator
def complete(self, parsed):
if self._cli_driver_fetcher is None:
return
if (
parsed.current_param == 'query'
and parsed.current_fragment is not None
):
operation_model = self._cli_driver_fetcher.get_operation_model(
parsed.lineage, parsed.current_command
)
if operation_model:
return self._get_completions(
parsed.current_fragment, operation_model
)
def _get_query_and_last_key(self, query):
# Because output example has only 1 element in any list if
# user enters query like Groups[2] jmespath will return "null"
# and we won't be able to prompting so we change all the numbers
# in brackets to 0 to be able to get the shape of the nested structure
query = re.sub(r'([\{\[])\d+?([\}\]])', r'\g<1>0\g<2>', query)
return query.rsplit('.', 1)
def _create_completions(self, results, last_key, fragment):
completions = self._filter(
last_key,
[
CompletionResult(last_key, display_text=result)
for result in results
],
)
for completion in completions:
name_part_len = len(fragment) - len(completion.name)
completion.name = "%s%s" % (
fragment[:name_part_len],
completion.display_text,
)
return completions
def _is_field_expression(self, expression):
is_last_child_field = False
is_field = expression.parsed['type'] == 'field'
if expression.parsed['children']:
is_last_child_field = (
expression.parsed['children'][-1]['type'] == 'field'
)
return is_field or is_last_child_field
def _get_completions(self, fragment, operation_model):
results = []
last_key = fragment
if operation_model.output_shape:
argument_generator = self.argument_generator(use_member_names=True)
response = argument_generator.generate_skeleton(
operation_model.output_shape
)
if '.' not in fragment:
if isinstance(response, dict):
results = response.keys()
else:
try:
query, last_key = self._get_query_and_last_key(fragment)
expression = self.jmespath.compile(query)
parsed_response = expression.search(response)
if (
parsed_response
and isinstance(parsed_response, list)
and not self._is_field_expression(expression)
):
parsed_response = parsed_response[0]
if isinstance(parsed_response, dict):
results = parsed_response.keys()
except self.jmespath.exceptions.JMESPathError:
pass
return self._create_completions(results, last_key, fragment)