forked from Azure/azure-cli-extensions
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request Azure#19 from Azure/master
Sync with the official repo
- Loading branch information
Showing
110 changed files
with
16,856 additions
and
3,280 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,3 +145,5 @@ | |
/src/attestation/ @YalinLi0312 @bim-msft | ||
|
||
/src/guestconfig/ @gehuan | ||
|
||
/src/swiftlet/ @qwordy |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
src/ai-did-you-mean-this/azext_ai_did_you_mean_this/_cli_command.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from azext_ai_did_you_mean_this.arguments import Arguments | ||
from azext_ai_did_you_mean_this._types import ArgumentsType | ||
|
||
|
||
class CliCommand(): | ||
parameters = Arguments('parameters', delim=',') | ||
arguments = Arguments('arguments', delim='♠') | ||
|
||
def __init__(self, command: str, parameters: ArgumentsType = '', arguments: ArgumentsType = ''): | ||
self.command_only = parameters == '' and arguments == '' | ||
self.command = command | ||
self.parameters = parameters | ||
self.arguments = arguments | ||
|
||
arguments_len = len(self.arguments) | ||
parameters_len = len(self.parameters) | ||
if arguments_len < parameters_len: | ||
missing_argument_count = parameters_len - arguments_len | ||
for _ in range(missing_argument_count): | ||
self.arguments.append('') | ||
elif arguments_len > parameters_len: | ||
raise ValueError(f'Got more arguments ({arguments_len}) than parameters ({parameters_len}).') | ||
|
||
def __str__(self): | ||
buffer = [] | ||
|
||
if not self.command_only: | ||
for (param, arg) in zip(self.parameters, self.arguments): | ||
if not buffer: | ||
buffer.append('') | ||
if arg: | ||
buffer.append(' '.join((param, arg))) | ||
else: | ||
buffer.append(param) | ||
|
||
return f"{self.command}{' '.join(buffer)}" | ||
|
||
def __eq__(self, value): | ||
return (self.command == value.command and | ||
self.parameters == value.parameters and | ||
self.arguments == value.arguments) | ||
|
||
def __hash__(self): | ||
return hash((self.command, self.parameters, self.arguments)) |
109 changes: 109 additions & 0 deletions
109
src/ai-did-you-mean-this/azext_ai_did_you_mean_this/_command.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from typing import List, Tuple, Union | ||
|
||
from azure.cli.core.commands import AzCliCommand | ||
|
||
from azext_ai_did_you_mean_this._logging import get_logger | ||
from azext_ai_did_you_mean_this._parameter import (GLOBAL_PARAM_BLOCKLIST, | ||
GLOBAL_PARAM_LOOKUP_TBL, | ||
Parameter, parameter_gen) | ||
from azext_ai_did_you_mean_this._types import ParameterTableType | ||
|
||
logger = get_logger(__name__) | ||
|
||
|
||
class Command(): | ||
|
||
def __init__(self, command: str, parameters: List[Parameter]): | ||
self.command: str = command | ||
self.parameters = parameters | ||
self.parameter_lookup_table = {} | ||
self.parameter_lookup_table.update(GLOBAL_PARAM_LOOKUP_TBL) | ||
|
||
for parameter in self.parameters: | ||
self.parameter_lookup_table[parameter.standard_form] = None | ||
|
||
for alias in parameter.aliases: | ||
self.parameter_lookup_table[alias] = parameter.standard_form | ||
|
||
@classmethod | ||
def normalize(cls, command: Union[None, 'Command'], *parameters: Tuple[str]): | ||
normalized_parameters = [] | ||
unrecognized_parameters = [] | ||
parameter_lookup_table = command.parameter_lookup_table if command else GLOBAL_PARAM_LOOKUP_TBL.copy() | ||
terms = parameter_lookup_table.keys() | ||
|
||
def is_recognized(parameter: str) -> bool: | ||
return parameter in parameter_lookup_table | ||
|
||
def match_prefix(parameter: str) -> str: | ||
matches: List[str] = [term for term in terms if term.startswith(parameter)] | ||
if len(matches) == 1: | ||
return matches[0] | ||
|
||
return parameter | ||
|
||
def get_normalized_form(parameter) -> str: | ||
normalized_form = None | ||
|
||
if not is_recognized(parameter): | ||
parameter = match_prefix(parameter) | ||
|
||
normalized_form = parameter_lookup_table.get(parameter, None) or parameter | ||
return normalized_form | ||
|
||
for parameter in parameters: | ||
normalized_form = get_normalized_form(parameter) | ||
|
||
if normalized_form in GLOBAL_PARAM_BLOCKLIST: | ||
continue | ||
if is_recognized(normalized_form): | ||
normalized_parameters.append(normalized_form) | ||
else: | ||
unrecognized_parameters.append(normalized_form) | ||
|
||
return sorted(set(normalized_parameters)), sorted(set(unrecognized_parameters)) | ||
|
||
@classmethod | ||
def get_parameter_table(cls, command_table: dict, command: str, | ||
recurse: bool = True) -> Tuple[ParameterTableType, str]: | ||
az_cli_command: Union[AzCliCommand, None] = command_table.get(command, None) | ||
parameter_table: ParameterTableType = az_cli_command.arguments if az_cli_command else {} | ||
partial_match = True | ||
|
||
if not az_cli_command: | ||
partial_match = any(cmd for cmd in command_table if cmd.startswith(command)) | ||
|
||
# if the specified command was not found and no similar command exists and recursive search is enabled... | ||
if not az_cli_command and not partial_match and recurse: | ||
# if there are at least two tokens separated by whitespace, remove the last token | ||
last_delim_idx = command.rfind(' ') | ||
if last_delim_idx != -1: | ||
logger.debug('Removing unknown token "%s" from command.', command[last_delim_idx + 1:]) | ||
# try to find the truncated command. | ||
parameter_table, command = cls.get_parameter_table( | ||
command_table, | ||
command[:last_delim_idx], | ||
recurse=False | ||
) | ||
|
||
return parameter_table, command | ||
|
||
@staticmethod | ||
def parse(command_table: dict, command: str, recurse: bool = True) -> Tuple['Command', str]: | ||
instance: 'Command' = None | ||
(parameter_table, command) = Command.get_parameter_table( | ||
command_table, | ||
command, | ||
recurse | ||
) | ||
|
||
if parameter_table: | ||
parameters = [parameter for parameter in parameter_gen(parameter_table)] | ||
instance = Command(command, parameters) | ||
|
||
return instance, command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
src/ai-did-you-mean-this/azext_ai_did_you_mean_this/_logging.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
import logging | ||
|
||
from knack.log import get_logger as get_knack_logger | ||
|
||
from azext_ai_did_you_mean_this._const import THOTH_LOG_PREFIX | ||
|
||
LOG_PREFIX_KEY = 'extension_log_prefix' | ||
|
||
|
||
class ExtensionLoggerAdapter(logging.LoggerAdapter): | ||
def process(self, msg, kwargs): | ||
buffer = [msg] | ||
|
||
if LOG_PREFIX_KEY in self.extra: | ||
buffer.insert(0, f'{self.extra[LOG_PREFIX_KEY]}:') | ||
|
||
return ' '.join(buffer), kwargs | ||
|
||
|
||
def get_logger(module_name: str) -> logging.LoggerAdapter: | ||
logger = get_knack_logger(module_name) | ||
adapter = ExtensionLoggerAdapter(logger, {LOG_PREFIX_KEY: THOTH_LOG_PREFIX}) | ||
return adapter |
89 changes: 89 additions & 0 deletions
89
src/ai-did-you-mean-this/azext_ai_did_you_mean_this/_parameter.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
# -------------------------------------------------------------------------------------------- | ||
# Copyright (c) Microsoft Corporation. All rights reserved. | ||
# Licensed under the MIT License. See License.txt in the project root for license information. | ||
# -------------------------------------------------------------------------------------------- | ||
|
||
from collections import defaultdict | ||
from typing import Iterator, List | ||
|
||
from azext_ai_did_you_mean_this._logging import get_logger | ||
from azext_ai_did_you_mean_this._types import (ParameterTableType, OptionListType) | ||
|
||
logger = get_logger(__name__) | ||
|
||
GLOBAL_PARAM_SHORTHAND_LOOKUP_TBL = { | ||
'-h': '--help', | ||
'-o': '--output', | ||
} | ||
|
||
GLOBAL_PARAM_LOOKUP_TBL = { | ||
**GLOBAL_PARAM_SHORTHAND_LOOKUP_TBL, | ||
'--only-show-errors': None, | ||
'--help': None, | ||
'--output': None, | ||
'--query': None, | ||
'--debug': None, | ||
'--verbose': None | ||
} | ||
|
||
GLOBAL_PARAM_BLOCKLIST = { | ||
'--only-show-errors', | ||
'--help', | ||
'--debug', | ||
'--verbose', | ||
} | ||
|
||
SUPPRESSED_STR = "==SUPPRESS==" | ||
|
||
|
||
def has_len_op(value): | ||
return hasattr(value, '__len__') and callable(value.__len__) | ||
|
||
|
||
class Parameter(): | ||
|
||
DEFAULT_STATE = defaultdict( | ||
None, | ||
options_list=[], | ||
choices=[], | ||
required=False, | ||
) | ||
|
||
def __init__(self, alias: str, **kwargs): | ||
self._options = None | ||
|
||
self.state = defaultdict(None, Parameter.DEFAULT_STATE) | ||
self.state.update(**kwargs) | ||
self.alias = alias | ||
|
||
self.options = self.state.get('options_list', []) | ||
|
||
sorted_options = sorted(self.options, key=len, reverse=True) | ||
self.standard_form = next(iter(sorted_options), None) | ||
self.aliases = set(self.options) - set((self.standard_form,)) | ||
|
||
@property | ||
def configurable(self) -> bool: | ||
return self.state['configured_default'] is not None | ||
|
||
@property | ||
def suppressed(self) -> bool: | ||
return self.state['help'] == SUPPRESSED_STR | ||
|
||
@property | ||
def options(self) -> List[str]: | ||
return self._options | ||
|
||
@options.setter | ||
def options(self, option_list: OptionListType): | ||
self._options = [option for option in option_list if has_len_op(option)] | ||
|
||
|
||
def parameter_gen(parameter_table: ParameterTableType) -> Iterator[Parameter]: | ||
for alias, argument in parameter_table.items(): | ||
parameter = Parameter(alias, **argument.type.settings) | ||
|
||
if not parameter.suppressed: | ||
yield parameter | ||
else: | ||
logger.debug('Discarding supressed parameter "%s"', alias) |
Oops, something went wrong.