def __init__(self, commands, global_params=True): # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of commands with parameters with multiple names (e.g. {'vm create':{-n: --name}}) self.same_param_doubles = commands.same_param_doubles or {} self._is_command = True self.branch = self.command_tree self.curr_command = "" self.global_param = commands.global_param if global_params else [] self.output_choices = commands.output_choices if global_params else [] self.output_options = commands.output_options if global_params else [] self.global_param_descriptions = commands.global_param_descriptions if global_params else [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) self.cmdtab = None
def __init__(self, commands, global_params=True, outstream=sys.stderr): # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of which parameters mean the same thing self.same_param_doubles = commands.same_param_doubles or {} self._is_command = True self.branch = self.command_tree self.curr_command = "" self.global_param = commands.global_param if global_params else [] self.output_choices = commands.output_choices if global_params else [] self.output_options = commands.output_options if global_params else [] self.global_param_descriptions = commands.global_param_descriptions if global_params else [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) from azclishell._dump_commands import CMD_TABLE self.cmdtab = CMD_TABLE self.parser.load_command_table(CMD_TABLE) self.argsfinder = ArgsFinder(self.parser, outstream)
def __init__(self, configuration=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': { 'x-ms-client-request-id': str(uuid.uuid1()) }, 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.configuration = configuration self.progress_controller = progress.ProgressHook()
def __init__(self, commands): # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of which parameters mean the same thing self.same_param_doubles = commands.same_param_doubles or {} self.global_parser = AzCliCommandParser(prog='az', add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) from azclishell._dump_commands import CMD_TABLE as cmd_table self.cmdtab = cmd_table self.parser.load_command_table(self.cmdtab)
def __init__(self, config=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': { 'x-ms-client-request-id': str(uuid.uuid1()) }, 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.initialize(config or Configuration([]))
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass command = CliCommand('command the-name', test_handler1) command2 = CliCommand('sub-command the-second-name', test_handler2) cmd_table = { 'command the-name': command, 'sub-command the-second-name': command2 } parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, test_handler1) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, test_handler2) AzCliCommandParser.error = VerifyError(self, ) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'command the-name', test_handler1) command2 = AzCliCommand(cli.loader, 'sub-command the-second-name', test_handler2) cmd_table = { 'command the-name': command, 'sub-command the-second-name': command2 } cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, command) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, command2) AzCliCommandParser.error = VerifyError(self, ) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_case_insensitive_enum_choices(self): from enum import Enum class TestEnum(Enum): # pylint: disable=too-few-public-methods opt1 = "ALL_CAPS" opt2 = "camelCase" opt3 = "snake_case" def test_handler(): pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('opt', '--opt', required=True, **enum_choice_list(TestEnum)) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --opt alL_cAps'.split()) self.assertEqual(args.opt, 'ALL_CAPS') args = parser.parse_args('test command --opt CAMELCASE'.split()) self.assertEqual(args.opt, 'camelCase') args = parser.parse_args('test command --opt sNake_CASE'.split()) self.assertEqual(args.opt, 'snake_case')
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass cli = TestCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'command the-name', test_handler1) command2 = AzCliCommand(cli.loader, 'sub-command the-second-name', test_handler2) cmd_table = {'command the-name': command, 'sub-command the-second-name': command2} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, command) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, command2) AzCliCommandParser.error = VerifyError(self,) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_case_insensitive_enum_choices(self): from enum import Enum class TestEnum(Enum): # pylint: disable=too-few-public-methods opt1 = "ALL_CAPS" opt2 = "camelCase" opt3 = "snake_case" def test_handler(): pass command = CliCommand('test command', test_handler) command.add_argument('opt', '--opt', required=True, **enum_choice_list(TestEnum)) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --opt alL_cAps'.split()) self.assertEqual(args.opt, 'ALL_CAPS') args = parser.parse_args('test command --opt CAMELCASE'.split()) self.assertEqual(args.opt, 'camelCase') args = parser.parse_args('test command --opt sNake_CASE'.split()) self.assertEqual(args.opt, 'snake_case')
def __init__(self, shell_ctx, commands, global_params=True): self.shell_ctx = shell_ctx self.started = False # dictionary of command to descriptions self.command_description = {} # a list of all the possible parameters self.completable_param = None # the command tree self.command_tree = None # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = None # a dictionary of command to examples of how to use it self.command_examples = None # a dictionary of commands with parameters with multiple names (e.g. {'vm create':{-n: --name}}) self.command_param_info = {} # information about what completions to generate self.current_command = '' self.unfinished_word = '' self.subtree = None self.leftover_args = None self.complete_command = False self.global_param = [] self.output_choices = [] self.output_options = [] self.global_param_descriptions = [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) self.argsfinder = ArgsFinder(self.parser) self.cmdtab = {} if commands: self.start(commands, global_params=global_params)
def __init__(self, configuration=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': {}, # the x-ms-client-request-id is generated before a command is to execute 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False, 'az_interactive_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.configuration = configuration self.progress_controller = progress.ProgressHook()
def test_nargs_parameter(self): def test_handler(): pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, test_handler) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_required_parameter(self): def test_handler(args): # pylint: disable=unused-argument pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep'.split()) self.assertIs(args.func, test_handler) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass command = CliCommand('test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass cli = TestCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} parser = AzCliCommandParser(cli) parser.load_command_table(cmd_table) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_required_parameter(self): def test_handler(args): # pylint: disable=unused-argument pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_register_simple_commands(self): def test_handler1(): pass def test_handler2(): pass command = CliCommand('command the-name', test_handler1) command2 = CliCommand('sub-command the-second-name', test_handler2) cmd_table = {'command the-name': command, 'sub-command the-second-name': command2} parser = AzCliCommandParser() parser.load_command_table(cmd_table) args = parser.parse_args('command the-name'.split()) self.assertIs(args.func, command) args = parser.parse_args('sub-command the-second-name'.split()) self.assertIs(args.func, command2) AzCliCommandParser.error = VerifyError(self,) parser.parse_args('sub-command'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass cli = DummyCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) with mock.patch('azure.cli.core.parser.AzCliCommandParser.error', new=VerifyError(self)): parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
def test_nargs_parameter(self): def test_handler(): pass cli = TestCli() cli.loader = mock.MagicMock() cli.loader.cli_ctx = cli command = AzCliCommand(cli.loader, 'test command', test_handler) command.add_argument('req', '--req', required=True, nargs=2) cmd_table = {'test command': command} cli.commands_loader.command_table = cmd_table parser = AzCliCommandParser(cli) parser.load_command_table(cli.commands_loader) args = parser.parse_args('test command --req yep nope'.split()) self.assertIs(args.func, command) AzCliCommandParser.error = VerifyError(self) parser.parse_args('test command -req yep'.split()) self.assertTrue(AzCliCommandParser.error.called)
class AzCompleter(Completer): """ Completes Azure CLI commands """ def __init__(self, shell_ctx, commands, global_params=True): self.shell_ctx = shell_ctx # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of commands with parameters with multiple names (e.g. {'vm create':{-n: --name}}) self.same_param_doubles = commands.same_param_doubles or {} self._is_command = True self.branch = self.command_tree self.curr_command = "" self.global_param = commands.global_param if global_params else [] self.output_choices = commands.output_choices if global_params else [] self.output_options = commands.output_options if global_params else [] self.global_param_descriptions = commands.global_param_descriptions if global_params else [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) self.argsfinder = ArgsFinder(self.parser) self.cmdtab = {} def validate_completion(self, param, words, text_before_cursor, check_double=True): """ validates that a param should be completed """ # validates the position of the parameter position = param.lower().startswith( words.lower()) and not text_before_cursor[-1].isspace() # cancels parameters that are already in the in line canceling_positions = param.lower() != words.lower( ) and param not in text_before_cursor.split() found_double = True # checks for aliasing of parameters if check_double: for double_sets in self.same_param_doubles.get( self.curr_command, []): # if the parameter is in any of the sets if param in double_sets: # if any of the other aliases are in the line already found_double = not any( alias in text_before_cursor.split() and alias != param for alias in double_sets) return position and canceling_positions and found_double def get_completions(self, document, complete_event): text = document.text_before_cursor self.branch = self.command_tree self.curr_command = '' self._is_command = True text = self.reformat_cmd(text) if text.split(): for comp in sort_completions( self.gen_cmd_and_param_completions(text)): yield comp for cmd in sort_completions(self.gen_cmd_completions(text)): yield cmd if self.cmdtab: for val in sort_completions(self.gen_dynamic_completions(text)): yield val for param in sort_completions(self.gen_global_param_completions(text)): yield param def gen_enum_completions(self, arg_name, text, started_param, prefix): """ generates dynamic enumeration completions """ try: # if enum completion for choice in self.cmdtab[ self.curr_command].arguments[arg_name].choices: if started_param: if choice.lower().startswith( prefix.lower()) and choice not in text.split(): yield Completion(choice, -len(prefix)) else: yield Completion(choice, -len(prefix)) except TypeError: # there is no choices option pass def get_arg_name(self, is_param, param): """ gets the argument name used in the command table for a parameter """ if self.curr_command in self.cmdtab and is_param: for arg in self.cmdtab[self.curr_command].arguments: for name in self.cmdtab[ self.curr_command].arguments[arg].options_list: if name == param: return arg return None def mute_parse_args(self, text): """ mutes the parser error when parsing, then puts it back """ error = AzCliCommandParser.error AzCliCommandParser.error = error_pass parse_args = self.argsfinder.get_parsed_args( parse_quotes(text, quotes=False, string=False)) AzCliCommandParser.error = error return parse_args # pylint: disable=too-many-branches def gen_dynamic_completions(self, text): """ generates the dynamic values, like the names of resource groups """ try: # pylint: disable=too-many-nested-blocks is_param, started_param, prefix, param = dynamic_param_logic(text) # command table specific name arg_name = self.get_arg_name(is_param, param) if arg_name and ( (text.split()[-1].startswith('-') and text[-1].isspace()) or text.split()[-2].startswith('-')): for comp in self.gen_enum_completions(arg_name, text, started_param, prefix): yield comp parse_args = self.mute_parse_args(text) # there are 3 formats for completers the cli uses # this try catches which format it is if self.cmdtab[ self.curr_command].arguments[arg_name].completer: try: for comp in self.cmdtab[self.curr_command].arguments[ arg_name].completer(prefix=prefix, action=None, parsed_args=parse_args): for completion in verify_dynamic_completion( comp, started_param, prefix, text): yield completion except TypeError: try: for comp in self.cmdtab[self.curr_command].\ arguments[arg_name].completer(prefix=prefix): for completion in verify_dynamic_completion( comp, started_param, prefix, text): yield completion except TypeError: try: for comp in self.cmdtab[self.curr_command].\ arguments[arg_name].completer(): for completion in verify_dynamic_completion( comp, started_param, prefix, text): yield completion except TypeError: pass # other completion method used # if the user isn't logged in except Exception: # pylint: disable=broad-except pass def gen_cmd_completions(self, text): """ whether is a space or no text typed, send the current branch """ # if nothing, so first level commands if not text.split() and self._is_command: if self.branch.children is not None: for com in self.branch.children: yield Completion(com.data) # if space show current level commands elif text.split() and text[-1].isspace() and self._is_command: if self.branch is not self.command_tree: for com in self.branch.children: yield Completion(com.data) def yield_param_completion(self, param, last_word): """ yields a parameter """ return Completion(param, -len(last_word), display_meta=self.param_description.get( self.curr_command + " " + str(param), '').replace('\n', '')) def gen_cmd_and_param_completions(self, text): """ generates command and parameter completions """ temp_command = str('') txtspt = text.split() for word in txtspt: if word.startswith("-"): self._is_command = False # building what the command is elif self._is_command: temp_command += ' ' + str(word) if temp_command else str(word) mid_val = text.find(word) + len(word) # moving down command tree if self.branch.has_child( word) and len(text) > mid_val and text[mid_val].isspace(): self.branch = self.branch.get_child(word, self.branch.children) if text and text[-1].isspace(): if in_tree(self.command_tree, temp_command): self.curr_command = temp_command else: self._is_command = False else: self.curr_command = temp_command last_word = txtspt[-1] # this is for single char parameters if last_word.startswith("-") and not last_word.startswith("--"): self._is_command = False if self.curr_command in self.command_parameters: for param in self.command_parameters[self.curr_command]: if self.validate_completion(param, last_word, text) and\ not param.startswith("--"): yield self.yield_param_completion(param, last_word) elif last_word.startswith("--"): # for regular parameters self._is_command = False if self.curr_command in self.command_parameters: # Everything should, map to empty list for param in self.command_parameters[self.curr_command]: if self.validate_completion(param, last_word, text): yield self.yield_param_completion(param, last_word) if self.branch.children and self._is_command: # all underneath commands for kid in self.branch.children: if self.validate_completion(kid.data, txtspt[-1], text, False): yield Completion(str(kid.data), -len(txtspt[-1])) elif self._is_command and self.curr_command.strip( ) in self.command_parameters: for param in self.command_parameters[self.curr_command.strip()]: if param.startswith('--'): yield self.yield_param_completion(param, '') def gen_global_param_completions(self, text): """ Global parameter stuff hard-coded in """ txtspt = text.split() if txtspt and txtspt: for param in self.global_param: # for single dash global parameters if txtspt[-1].startswith('-') \ and not txtspt[-1].startswith('--') and \ param.startswith('-') and not param.startswith('--') and\ self.validate_completion(param, txtspt[-1], text, check_double=False): yield Completion( param, -len(txtspt[-1]), display_meta=self.global_param_descriptions[param]) # for double dash global parameters elif txtspt[-1].startswith('--') and \ self.validate_completion(param, txtspt[-1], text, check_double=False): yield Completion( param, -len(txtspt[-1]), display_meta=self.global_param_descriptions[param]) # if there is an output, gets the options without user typing if txtspt[-1] in self.output_options: for opt in self.output_choices: yield Completion(opt) # if there is an output option, if they have started typing if len(txtspt) > 1 and\ txtspt[-2] in self.output_options: for opt in self.output_choices: if self.validate_completion(opt, txtspt[-1], text, check_double=False): yield Completion(opt, -len(txtspt[-1])) def is_completable(self, symbol): """ whether the word can be completed as a command or parameter """ return symbol in self.command_parameters or symbol in self.param_description.keys( ) def has_description(self, param): """ if a parameter has a description """ return param in self.param_description.keys() and \ not self.param_description[param].isspace() def reformat_cmd(self, text): """ reformat the text to be stripped of noise """ # remove az if there text = text.replace('az', '') # disregard defaulting symbols if text and SELECT_SYMBOL['scope'] == text[0:2]: text = text.replace(SELECT_SYMBOL['scope'], "") if self.shell_ctx.default_command: text = self.shell_ctx.default_command + ' ' + text return text
class Application(object): TRANSFORM_RESULT = 'Application.TransformResults' FILTER_RESULT = 'Application.FilterResults' GLOBAL_PARSER_CREATED = 'GlobalParser.Created' COMMAND_PARSER_LOADED = 'CommandParser.Loaded' COMMAND_PARSER_PARSING = 'CommandParser.Parsing' COMMAND_PARSER_PARSED = 'CommandParser.Parsed' COMMAND_TABLE_LOADED = 'CommandTable.Loaded' COMMAND_TABLE_PARAMS_LOADED = 'CommandTableParams.Loaded' def __init__(self, configuration=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': {}, # the x-ms-client-request-id is generated before a command is to execute 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False, 'az_interactive_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.configuration = configuration self.progress_controller = progress.ProgressHook() def get_progress_controller(self, det=False): self.progress_controller.init_progress(progress.get_progress_view(det)) return self.progress_controller def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): # pylint: disable=too-many-statements self.refresh_request_id() argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active']) def raise_event(self, name, **kwargs): '''Raise the event `name`. ''' data = truncate_text(str(kwargs), width=500) logger.debug("Application event '%s' with event data %s", name, data) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): '''Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].append(handler) logger.debug("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): '''Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].remove(handler) logger.debug("Removed application event handler '%s' at %s", name, handler) def refresh_request_id(self): """Assign a new randome GUID as x-ms-client-request-id The method must be invoked before each command execution in order to ensure unique client side request ID is generated. """ self.session['headers']['x-ms-client-request-id'] = str(uuid.uuid1()) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs['global_group'] global_group.add_argument('--output', '-o', dest='_output_format', choices=['json', 'tsv', 'table', 'jsonc'], default=az_config.get('core', 'output', fallback='json'), help='Output format', type=str.lower) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true', help='Increase logging verbosity. Use --debug for full debug logs.') global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true', help='Increase logging verbosity to show all debug logs.') @staticmethod def _maybe_load_file(arg): ix = arg.find('@') if ix == -1: # no @ found return arg poss_file = arg[ix + 1:] if not poss_file: # if nothing after @ then it can't be a file return arg elif ix == 0: return Application._load_file(poss_file) # if @ not at the start it can't be a file return arg @staticmethod def _expand_file_prefix(arg): arg_split = arg.split('=', 1) try: return '='.join([arg_split[0], Application._maybe_load_file(arg_split[1])]) except IndexError: return Application._maybe_load_file(arg_split[0]) @staticmethod def _expand_file_prefixed_files(argv): return list([Application._expand_file_prefix(arg) for arg in argv]) @staticmethod def _load_file(path): if path == '-': content = sys.stdin.read() else: content = read_file_content(os.path.expanduser(path), allow_binary=True) return content[0:-1] if content and content[-1] == '\n' else content def _handle_builtin_arguments(self, **kwargs): args = kwargs['args'] self.configuration.output_format = args._output_format # pylint: disable=protected-access del args._output_format
class AzCompleter(Completer): """ Completes Azure CLI commands """ def __init__(self, shell_ctx, commands, global_params=True): self.shell_ctx = shell_ctx self.started = False # dictionary of command to descriptions self.command_description = {} # a list of all the possible parameters self.completable_param = None # the command tree self.command_tree = None # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = None # a dictionary of command to examples of how to use it self.command_examples = None # a dictionary of commands with parameters with multiple names (e.g. {'vm create':{-n: --name}}) self.command_param_info = {} # information about what completions to generate self.current_command = '' self.unfinished_word = '' self.subtree = None self.leftover_args = None self.complete_command = False self.global_param = [] self.output_choices = [] self.output_options = [] self.global_param_descriptions = [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) self.argsfinder = ArgsFinder(self.parser) self.cmdtab = {} if commands: self.start(commands, global_params=global_params) def __bool__(self): return self.started def start(self, commands, global_params=True): self.started = True self.command_description = commands.descrip self.completable_param = commands.completable_param self.command_tree = commands.command_tree self.param_description = commands.param_descript self.command_examples = commands.command_example self.command_param_info = commands.command_param_info or self.command_param_info if global_params: self.global_param = commands.global_param self.output_choices = commands.output_choices self.output_options = commands.output_options self.global_param_descriptions = commands.global_param_descriptions def initialize_command_table_attributes(self): from ._dump_commands import FreshTable loader = FreshTable(self.shell_ctx).loader if loader and loader.command_table: self.cmdtab = loader.command_table self.parser.load_command_table(loader) self.argsfinder = ArgsFinder(self.parser) def validate_param_completion(self, param, leftover_args): """ validates that a param should be completed """ # validates param starts with unfinished word completes = self.validate_completion(param) # show parameter completions when started full_param = self.unfinished_word.startswith("--") and param.startswith("--") char_param = self.unfinished_word.startswith("-") and not param.startswith("--") # show full parameters before any are used new_param = not self.unfinished_word and not leftover_args and param.startswith("--") # checks for parameters already in the line as well as aliases no_doubles = True command_doubles = self.command_param_info.get(self.current_command, {}) for alias in command_doubles.get(param, []): if alias in leftover_args: no_doubles = False return completes and no_doubles and any((full_param, char_param, new_param)) def validate_completion(self, completion): return completion.lower().startswith(self.unfinished_word.lower()) def process_dynamic_completion(self, completion): """ how to validate and generate completion for dynamic params """ if len(completion.split()) > 1: completion = '\"' + completion + '\"' if self.validate_completion(completion): yield Completion(completion, -len(self.unfinished_word)) def get_completions(self, document, complete_event): if not self.started: return text = self.reformat_cmd(document.text_before_cursor) event_payload = { 'text': text } self.shell_ctx.cli_ctx.raise_event(EVENT_INTERACTIVE_PRE_COMPLETER_TEXT_PARSING, event_payload=event_payload) # Reload various attributes from event_payload text = event_payload.get('text', text) text_split = text.split() self.unfinished_word = '' new_word = text and text[-1].isspace() if not new_word and text_split: self.unfinished_word = text_split[-1] text_split = text_split[:-1] self.subtree, self.current_command, self.leftover_args = self.command_tree.get_sub_tree(text_split) self.shell_ctx.cli_ctx.raise_event(EVENT_INTERACTIVE_POST_SUB_TREE_CREATE, subtree=self.subtree) self.complete_command = not self.subtree.children for comp in sort_completions(self.gen_cmd_and_param_completions()): yield comp for comp in sort_completions(self.gen_global_params_and_arg_completions()): yield comp if self.complete_command and self.cmdtab and self.leftover_args and self.leftover_args[-1].startswith('-'): for comp in sort_completions(self.gen_dynamic_completions(text)): yield comp def gen_enum_completions(self, arg_name): """ generates dynamic enumeration completions """ try: # if enum completion for choice in self.cmdtab[self.current_command].arguments[arg_name].choices: if self.validate_completion(choice): yield Completion(choice, -len(self.unfinished_word)) except TypeError: # there is no choices option pass def get_arg_name(self, param): """ gets the argument name used in the command table for a parameter """ if self.current_command in self.cmdtab: for arg in self.cmdtab[self.current_command].arguments: for name in self.cmdtab[self.current_command].arguments[arg].options_list: if name == param: return arg return None # pylint: disable=protected-access def mute_parse_args(self, text): """ mutes the parser error when parsing, then puts it back """ error = AzCliCommandParser.error _check_value = AzCliCommandParser._check_value AzCliCommandParser.error = error_pass AzCliCommandParser._check_value = _check_value_muted # No exception is expected. However, we add this try-catch block, as this may have far-reaching effects. try: parse_args = self.argsfinder.get_parsed_args(parse_quotes(text, quotes=False, string=False)) except Exception: # pylint: disable=broad-except pass AzCliCommandParser.error = error AzCliCommandParser._check_value = _check_value return parse_args # pylint: disable=too-many-branches def gen_dynamic_completions(self, text): """ generates the dynamic values, like the names of resource groups """ try: # pylint: disable=too-many-nested-blocks param = self.leftover_args[-1] # command table specific name arg_name = self.get_arg_name(param) for comp in self.gen_enum_completions(arg_name): yield comp parsed_args = self.mute_parse_args(text) # there are 3 formats for completers the cli uses # this try catches which format it is if self.cmdtab[self.current_command].arguments[arg_name].completer: completions = [] try: completions = self.cmdtab[self.current_command].arguments[arg_name].completer( prefix=self.unfinished_word, action=None, parsed_args=parsed_args) except TypeError: try: completions = self.cmdtab[self.current_command].arguments[arg_name].completer( prefix=self.unfinished_word) except TypeError: try: completions = self.cmdtab[self.current_command].arguments[arg_name].completer() except TypeError: pass # other completion method used for comp in completions: for completion in self.process_dynamic_completion(comp): yield completion # if the user isn't logged in except Exception: # pylint: disable=broad-except pass def yield_param_completion(self, param, last_word): """ yields a parameter """ return Completion(param, -len(last_word), display_meta=self.param_description.get( self.current_command + " " + str(param), '').replace(os.linesep, '')) def gen_cmd_and_param_completions(self): """ generates command and parameter completions """ if self.complete_command: for param in self.command_param_info.get(self.current_command, []): if self.validate_param_completion(param, self.leftover_args): yield self.yield_param_completion(param, self.unfinished_word) elif not self.leftover_args: for child_command in self.subtree.children: if self.validate_completion(child_command): yield Completion(child_command, -len(self.unfinished_word)) def gen_global_params_and_arg_completions(self): # global parameters for param in self.global_param: if self.validate_param_completion(param, self.leftover_args) and self.unfinished_word: if param in self.output_options and not self.complete_command: continue yield Completion(param, -len(self.unfinished_word), display_meta=self.global_param_descriptions[param]) # global parameter args if self.leftover_args and self.leftover_args[-1] in self.output_options: for opt in self.output_choices: if self.validate_completion(opt): yield Completion(opt, -len(self.unfinished_word)) def is_completable(self, symbol): """ whether the word can be completed as a command or parameter """ return symbol in self.command_description or symbol in self.param_description def has_description(self, param): """ if a parameter has a description """ return param in self.param_description.keys() and \ not self.param_description[param].isspace() def reformat_cmd(self, text): """ reformat the text to be stripped of noise """ # remove az if there text = text.replace('az', '') # disregard defaulting symbols if text and SELECT_SYMBOL['scope'] == text[0:2]: text = text.replace(SELECT_SYMBOL['scope'], "") if self.shell_ctx.default_command: text = self.shell_ctx.default_command + ' ' + text return text
class AzCompleter(Completer): """ Completes Azure CLI commands """ def __init__(self, commands, global_params=True, outstream=sys.stderr): # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of which parameters mean the same thing self.same_param_doubles = commands.same_param_doubles or {} self._is_command = True self.branch = self.command_tree self.curr_command = "" self.global_param = commands.global_param if global_params else [] self.output_choices = commands.output_choices if global_params else [] self.output_options = commands.output_options if global_params else [] self.global_param_descriptions = commands.global_param_descriptions if global_params else [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) from azclishell._dump_commands import CMD_TABLE self.cmdtab = CMD_TABLE self.parser.load_command_table(CMD_TABLE) self.argsfinder = ArgsFinder(self.parser, outstream) def validate_completion(self, param, words, text_before_cursor, double=True): """ validates that a param should be completed """ return param.lower().startswith(words.lower()) and param.lower() != words.lower() and\ param not in text_before_cursor.split() and not \ text_before_cursor[-1].isspace() and\ (not (double and param in self.same_param_doubles) or self.same_param_doubles[param] not in text_before_cursor.split()) def get_completions(self, document, complete_event): text = document.text_before_cursor self.branch = self.command_tree self.curr_command = '' self._is_command = True text = reformat_cmd(text) if text.split(): for comp in sort_completions(self.gen_cmd_and_param_completions(text)): yield comp for cmd in sort_completions(self.gen_cmd_completions(text)): yield cmd for val in sort_completions(self.gen_dynamic_completions(text)): yield val for param in sort_completions(self.gen_global_param_completions(text)): yield param def gen_enum_completions(self, arg_name, text, started_param, prefix): """ generates dynamic enumeration completions """ try: # if enum completion for choice in self.cmdtab[ self.curr_command].arguments[arg_name].choices: if started_param: if choice.lower().startswith(prefix.lower())\ and choice not in text.split(): yield Completion(choice, -len(prefix)) else: yield Completion(choice, -len(prefix)) except TypeError: # there is no choices option pass def get_arg_name(self, is_param, param): """ gets the argument name used in the command table for a parameter """ if self.curr_command in self.cmdtab and is_param: for arg in self.cmdtab[self.curr_command].arguments: for name in self.cmdtab[self.curr_command].arguments[arg].options_list: if name == param: return arg def mute_parse_args(self, text): """ mutes the parser error when parsing, the puts it back """ error = AzCliCommandParser.error AzCliCommandParser.error = error_pass parse_args = self.argsfinder.get_parsed_args( parse_quotes(text, quotes=False, string=False)) AzCliCommandParser.error = error return parse_args # pylint: disable=too-many-branches def gen_dynamic_completions(self, text): """ generates the dynamic values, like the names of resource groups """ try: is_param, started_param, prefix, param = dynamic_param_logic(text) # command table specific name arg_name = self.get_arg_name(is_param, param) if arg_name and ((text.split()[-1].startswith('-') and text[-1].isspace()) or text.split()[-2].startswith('-')): for comp in self.gen_enum_completions(arg_name, text, started_param, prefix): yield comp parse_args = self.mute_parse_args(text) # there are 3 formats for completers the cli uses # this try catches which format it is if self.cmdtab[self.curr_command].arguments[arg_name].completer: try: for comp in self.cmdtab[self.curr_command].arguments[arg_name].completer( prefix=prefix, action=None, parsed_args=parse_args): for comp in gen_dyn_completion( comp, started_param, prefix, text): yield comp except TypeError: try: for comp in self.cmdtab[self.curr_command].\ arguments[arg_name].completer(prefix=prefix): for comp in gen_dyn_completion( comp, started_param, prefix, text): yield comp except TypeError: try: for comp in self.cmdtab[self.curr_command].\ arguments[arg_name].completer(): for comp in gen_dyn_completion( comp, started_param, prefix, text): yield comp except TypeError: pass # other completion method used # if the user isn't logged in except Exception: # pylint: disable=broad-except pass def gen_cmd_completions(self, text): """ whether is a space or no text typed, send the current branch """ # if nothing, so first level commands if not text.split() and self._is_command: if self.branch.children is not None: for com in self.branch.children: yield Completion(com.data) # if space show current level commands elif len(text.split()) > 0 and text[-1].isspace() and self._is_command: if self.branch is not self.command_tree: for com in self.branch.children: yield Completion(com.data) def yield_param_completion(self, param, last_word): """ yields a parameter """ return Completion(param, -len(last_word), display_meta=self.get_param_description( self.curr_command + " " + str(param)).replace('\n', '')) def gen_cmd_and_param_completions(self, text): """ generates command and parameter completions """ temp_command = str('') txtspt = text.split() for word in txtspt: if word.startswith("-"): self._is_command = False # building what the command is elif self._is_command: temp_command += ' ' + str(word) if temp_command else str(word) mid_val = text.find(word) + len(word) # moving down command tree if self.branch.has_child(word) and len(text) > mid_val and text[mid_val].isspace(): self.branch = self.branch.get_child(word, self.branch.children) if len(text) > 0 and text[-1].isspace(): if in_tree(self.command_tree, temp_command): self.curr_command = temp_command else: self._is_command = False else: self.curr_command = temp_command last_word = txtspt[-1] # this is for single char parameters if last_word.startswith("-") and not last_word.startswith("--"): self._is_command = False if self.has_parameters(self.curr_command): for param in self.command_parameters[self.curr_command]: if self.validate_completion(param, last_word, text) and\ not param.startswith("--"): yield self.yield_param_completion(param, last_word) elif last_word.startswith("--"): # for regular parameters self._is_command = False if self.has_parameters(self.curr_command): # Everything should, map to empty list for param in self.command_parameters[self.curr_command]: if self.validate_completion(param, last_word, text): yield self.yield_param_completion(param, last_word) if self.branch.children and self._is_command: # all underneath commands for kid in self.branch.children: if self.validate_completion(kid.data, txtspt[-1], text, False): yield Completion( str(kid.data), -len(txtspt[-1])) elif self._is_command and self.curr_command.strip() in self.command_parameters: for param in self.command_parameters[self.curr_command.strip()]: if param.startswith('--'): yield self.yield_param_completion(param, '') def gen_global_param_completions(self, text): """ Global parameter stuff hard-coded in """ txtspt = text.split() if txtspt and len(txtspt) > 0: for param in self.global_param: # for single dash global parameters if txtspt[-1].startswith('-') \ and not txtspt[-1].startswith('--') and \ param.startswith('-') and not param.startswith('--') and\ self.validate_completion(param, txtspt[-1], text, double=False): yield Completion( param, -len(txtspt[-1]), display_meta=self.global_param_descriptions[param]) # for double dash global parameters elif txtspt[-1].startswith('--') and \ self.validate_completion(param, txtspt[-1], text, double=False): yield Completion( param, -len(txtspt[-1]), display_meta=self.global_param_descriptions[param]) # if there is an output, gets the options without user typing if txtspt[-1] in self.output_options: for opt in self.output_choices: yield Completion(opt) # if there is an output option, if they have started typing if len(txtspt) > 1 and\ txtspt[-2] in self.output_options: for opt in self.output_choices: if self.validate_completion(opt, txtspt[-1], text, double=False): yield Completion(opt, -len(txtspt[-1])) def is_completable(self, symbol): """ whether the word can be completed as a command or parameter """ return self.has_parameters(symbol) or symbol in self.param_description.keys() def get_param_description(self, param): """ gets a description of an empty string """ if param in self.param_description: return self.param_description[param] else: return "" def has_parameters(self, command): """ returns whether given command is valid """ return command in self.command_parameters.keys() def has_description(self, param): """ if a parameter has a description """ return param in self.param_description.keys() and \ not self.param_description[param].isspace()
class Application(object): TRANSFORM_RESULT = "Application.TransformResults" FILTER_RESULT = "Application.FilterResults" GLOBAL_PARSER_CREATED = "GlobalParser.Created" COMMAND_PARSER_LOADED = "CommandParser.Loaded" COMMAND_PARSER_PARSED = "CommandParser.Parsed" COMMAND_TABLE_LOADED = "CommandTable.Loaded" COMMAND_TABLE_PARAMS_LOADED = "CommandTableParams.Loaded" def __init__(self, config=None): self._event_handlers = defaultdict(lambda: []) self.session = { "headers": {"x-ms-client-request-id": str(uuid.uuid1())}, "command": "unknown", "completer_active": ARGCOMPLETE_ENV_NAME in os.environ, "query_active": False, } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog="az", add_help=False) global_group = self.global_parser.add_argument_group("global", "Global Arguments") self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog="az", parents=[self.global_parser]) self.initialize(config or Configuration([])) def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table() self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if len(argv) == 0: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) log_telemetry("welcome") return None if argv[0].lower() == "help": argv[0] = "--help" # Rudimentary parsing to get the command nouns = [] for noun in argv: if noun[0] == "-": break nouns.append(noun) command = " ".join(nouns) if argv[-1] in ("--help", "-h") or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session["completer_active"]: enable_autocomplete(self.parser) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session["command"] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, "_parser", self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith("_")]) params.pop("subcommand", None) params.pop("func", None) params.pop("command", None) log_telemetry( expanded_arg.command, log_type="pageview", output_type=self.configuration.output_format, parameters=[p for p in unexpanded_argv if p.startswith("-")], ) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {"result": results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem( event_data["result"], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session["query_active"], ) def raise_event(self, name, **kwargs): """Raise the event `name`. """ logger.info("Application event '%s' with event data %s", name, kwargs) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): """Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. """ self._event_handlers[name].append(handler) logger.info("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): """Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. """ self._event_handlers[name].remove(handler) logger.info("Removed application event handler '%s' at %s", name, handler) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs["global_group"] global_group.add_argument( "--output", "-o", dest="_output_format", choices=["json", "tsv", "list", "table", "jsonc"], default=az_config.get("core", "output", fallback="json"), help="Output format", type=str.lower, ) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument( "--verbose", dest="_log_verbosity_verbose", action="store_true", help="Increase logging verbosity. Use --debug for full debug logs.", ) # pylint: disable=line-too-long global_group.add_argument( "--debug", dest="_log_verbosity_debug", action="store_true", help="Increase logging verbosity to show all debug logs.", ) @staticmethod def _expand_file_prefixed_files(argv): return list([Application._load_file(arg[1:]) if arg.startswith("@") else arg for arg in argv]) @staticmethod def _load_file(path): try: if path == "-": content = sys.stdin.read() else: with open(path, "r") as input_file: content = input_file.read() return content[0:-1] if content[-1] == "\n" else content except: raise CLIError("Failed to open file {}".format(path)) def _handle_builtin_arguments(self, **kwargs): args = kwargs["args"] self.configuration.output_format = args._output_format # pylint: disable=protected-access del args._output_format
class AzCompleter(Completer): """ Completes Azure CLI commands """ def __init__(self, commands, global_params=True, outstream=sys.stderr): # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of which parameters mean the same thing self.same_param_doubles = commands.same_param_doubles or {} self._is_command = True self.branch = self.command_tree self.curr_command = "" self.global_param = commands.global_param if global_params else [] self.output_choices = commands.output_choices if global_params else [] self.output_options = commands.output_options if global_params else [] self.global_param_descriptions = commands.global_param_descriptions if global_params else [] self.global_parser = AzCliCommandParser(add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(parents=[self.global_parser]) from azclishell._dump_commands import CMD_TABLE self.cmdtab = CMD_TABLE self.parser.load_command_table(CMD_TABLE) self.argsfinder = ArgsFinder(self.parser, outstream) def validate_completion(self, param, words, text_before_cursor, double=True): """ validates that a param should be completed """ return param.lower().startswith(words.lower()) and param.lower() != words.lower() and\ param not in text_before_cursor.split() and not \ text_before_cursor[-1].isspace() and\ (not (double and param in self.same_param_doubles) or self.same_param_doubles[param] not in text_before_cursor.split()) def get_completions(self, document, complete_event): text = document.text_before_cursor self.branch = self.command_tree self.curr_command = '' self._is_command = True text = reformat_cmd(text) if text.split(): for comp in sort_completions( self.gen_cmd_and_param_completions(text)): yield comp for cmd in sort_completions(self.gen_cmd_completions(text)): yield cmd for val in sort_completions(self.gen_dynamic_completions(text)): yield val for param in sort_completions(self.gen_global_param_completions(text)): yield param def gen_enum_completions(self, arg_name, text, started_param, prefix): """ generates dynamic enumeration completions """ try: # if enum completion for choice in self.cmdtab[ self.curr_command].arguments[arg_name].choices: if started_param: if choice.lower().startswith(prefix.lower())\ and choice not in text.split(): yield Completion(choice, -len(prefix)) else: yield Completion(choice, -len(prefix)) except TypeError: # there is no choices option pass def get_arg_name(self, is_param, param): """ gets the argument name used in the command table for a parameter """ if self.curr_command in self.cmdtab and is_param: for arg in self.cmdtab[self.curr_command].arguments: for name in self.cmdtab[ self.curr_command].arguments[arg].options_list: if name == param: return arg def mute_parse_args(self, text): """ mutes the parser error when parsing, the puts it back """ error = AzCliCommandParser.error AzCliCommandParser.error = error_pass parse_args = self.argsfinder.get_parsed_args( parse_quotes(text, quotes=False, string=False)) AzCliCommandParser.error = error return parse_args # pylint: disable=too-many-branches def gen_dynamic_completions(self, text): """ generates the dynamic values, like the names of resource groups """ try: is_param, started_param, prefix, param = dynamic_param_logic(text) # command table specific name arg_name = self.get_arg_name(is_param, param) if arg_name and ( (text.split()[-1].startswith('-') and text[-1].isspace()) or text.split()[-2].startswith('-')): for comp in self.gen_enum_completions(arg_name, text, started_param, prefix): yield comp parse_args = self.mute_parse_args(text) # there are 3 formats for completers the cli uses # this try catches which format it is if self.cmdtab[ self.curr_command].arguments[arg_name].completer: try: for comp in self.cmdtab[self.curr_command].arguments[ arg_name].completer(prefix=prefix, action=None, parsed_args=parse_args): for comp in gen_dyn_completion( comp, started_param, prefix, text): yield comp except TypeError: try: for comp in self.cmdtab[self.curr_command].\ arguments[arg_name].completer(prefix=prefix): for comp in gen_dyn_completion( comp, started_param, prefix, text): yield comp except TypeError: try: for comp in self.cmdtab[self.curr_command].\ arguments[arg_name].completer(): for comp in gen_dyn_completion( comp, started_param, prefix, text): yield comp except TypeError: pass # other completion method used # if the user isn't logged in except (CLIError, AttributeError, ValueError): # service client throws pass def gen_cmd_completions(self, text): """ whether is a space or no text typed, send the current branch """ # if nothing, so first level commands if not text.split() and self._is_command: if self.branch.children is not None: for com in self.branch.children: yield Completion(com.data) # if space show current level commands elif len(text.split()) > 0 and text[-1].isspace() and self._is_command: if self.branch is not self.command_tree: for com in self.branch.children: yield Completion(com.data) def yield_param_completion(self, param, last_word): """ yields a parameter """ return Completion( param, -len(last_word), display_meta=self.get_param_description(self.curr_command + " " + str(param)).replace( '\n', '')) def gen_cmd_and_param_completions(self, text): """ generates command and parameter completions """ temp_command = str('') txtspt = text.split() for word in txtspt: if word.startswith("-"): self._is_command = False # building what the command is elif self._is_command: temp_command += ' ' + str(word) if temp_command else str(word) mid_val = text.find(word) + len(word) # moving down command tree if self.branch.has_child( word) and len(text) > mid_val and text[mid_val].isspace(): self.branch = self.branch.get_child(word, self.branch.children) if len(text) > 0 and text[-1].isspace(): if in_tree(self.command_tree, temp_command): self.curr_command = temp_command else: self._is_command = False else: self.curr_command = temp_command last_word = txtspt[-1] # this is for single char parameters if last_word.startswith("-") and not last_word.startswith("--"): self._is_command = False if self.has_parameters(self.curr_command): for param in self.command_parameters[self.curr_command]: if self.validate_completion(param, last_word, text) and\ not param.startswith("--"): yield self.yield_param_completion(param, last_word) elif last_word.startswith("--"): # for regular parameters self._is_command = False if self.has_parameters( self.curr_command): # Everything should, map to empty list for param in self.command_parameters[self.curr_command]: if self.validate_completion(param, last_word, text): yield self.yield_param_completion(param, last_word) if self.branch.children and self._is_command: # all underneath commands for kid in self.branch.children: if self.validate_completion(kid.data, txtspt[-1], text, False): yield Completion(str(kid.data), -len(txtspt[-1])) elif self._is_command and self.curr_command.strip( ) in self.command_parameters: for param in self.command_parameters[self.curr_command.strip()]: if param.startswith('--'): yield self.yield_param_completion(param, '') def gen_global_param_completions(self, text): """ Global parameter stuff hard-coded in """ txtspt = text.split() if txtspt and len(txtspt) > 0: for param in self.global_param: # for single dash global parameters if txtspt[-1].startswith('-') \ and not txtspt[-1].startswith('--') and \ param.startswith('-') and not param.startswith('--') and\ self.validate_completion(param, txtspt[-1], text, double=False): yield Completion( param, -len(txtspt[-1]), display_meta=self.global_param_descriptions[param]) # for double dash global parameters elif txtspt[-1].startswith('--') and \ self.validate_completion(param, txtspt[-1], text, double=False): yield Completion( param, -len(txtspt[-1]), display_meta=self.global_param_descriptions[param]) # if there is an output, gets the options without user typing if txtspt[-1] in self.output_options: for opt in self.output_choices: yield Completion(opt) # if there is an output option, if they have started typing if len(txtspt) > 1 and\ txtspt[-2] in self.output_options: for opt in self.output_choices: if self.validate_completion(opt, txtspt[-1], text, double=False): yield Completion(opt, -len(txtspt[-1])) def is_completable(self, symbol): """ whether the word can be completed as a command or parameter """ return self.has_parameters( symbol) or symbol in self.param_description.keys() def get_param_description(self, param): """ gets a description of an empty string """ if param in self.param_description: return self.param_description[param] else: return "" def has_parameters(self, command): """ returns whether given command is valid """ return command in self.command_parameters.keys() def has_description(self, param): """ if a parameter has a description """ return param in self.param_description.keys() and \ not self.param_description[param].isspace()
class AzCompleter(Completer): """ Completes Azure CLI commands """ def __init__(self, commands): # dictionary of command to descriptions self.command_description = commands.descrip # from a command to a list of parameters self.command_parameters = commands.command_param # a list of all the possible parameters self.completable_param = commands.completable_param # the command tree self.command_tree = commands.command_tree # a dictionary of parameter (which is command + " " + parameter name) # to a description of what it does self.param_description = commands.param_descript # a dictionary of command to examples of how to use it self.command_examples = commands.command_example # a dictionary of which parameters mean the same thing self.same_param_doubles = commands.same_param_doubles or {} self.global_parser = AzCliCommandParser(prog='az', add_help=False) self.global_parser.add_argument_group('global', 'Global Arguments') self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) from azclishell._dump_commands import CMD_TABLE as cmd_table self.cmdtab = cmd_table self.parser.load_command_table(self.cmdtab) def validate_param_completion(self, param, words, text_before_cursor): """ validates that a param should be completed """ double_flag = True if param in self.same_param_doubles: double_flag = self.same_param_doubles[ param] not in text_before_cursor.split() return param.lower().startswith(words.lower()) and \ param.lower() != words.lower() and\ param not in text_before_cursor.split()\ and double_flag def get_completions(self, document, complete_event): text_before_cursor = document.text_before_cursor command = "" is_command = True branch = self.command_tree not_command = False if len(text_before_cursor.split()) > 0\ and text_before_cursor.split()[0] == 'az': # remove optional az text_before_cursor = ' '.join(text_before_cursor.split()[1:]) if SELECT_SYMBOL['default'] in text_before_cursor: text_before_cursor = text_before_cursor.replace( SELECT_SYMBOL['default'], "") if SELECT_SYMBOL['undefault'] in text_before_cursor: text_before_cursor = text_before_cursor.replace( SELECT_SYMBOL['undefault'], "") if default_command(): # print(DEFAULT_COMMAND) text_before_cursor = default_command() + ' ' + text_before_cursor if text_before_cursor.split(): if text_before_cursor.split(): for words in text_before_cursor.split(): if words.startswith("-") and not words.startswith("--"): is_command = False if self.has_parameters(command): for param in self.get_param(command): if self.validate_param_completion(param, words, text_before_cursor)\ and not param.startswith("--"): yield Completion(param, -len(words), display_meta=\ self.get_param_description( command + " " + str(param)).replace('\n', '')) elif words.startswith("--"): is_command = False if self.has_parameters( command ): # Everything should, map to empty list for param in self.get_param(command): if self.validate_param_completion( param, words, text_before_cursor): yield Completion(param, -len(words),\ display_meta=self.get_param_description( command + " " + str(param)).replace('\n', '')) else: if is_command: if command: command += " " + str(words) else: command += str(words) try: if branch.has_child(words): branch = branch.get_child( words, branch.children) elif text_before_cursor.split( )[0] in SELECT_SYMBOL.values(): # print('help') not_command = True except ValueError: continue # do something if branch.children is not None and not not_command: for kid in branch.children: if kid.data.lower().startswith( text_before_cursor.split()[-1].lower()): yield Completion(str(kid.data),\ -len(text_before_cursor.split()[-1])) if not text_before_cursor: if branch.children is not None: for com in branch.children: yield Completion(com.data) elif text_before_cursor[-1].isspace(): if branch is not self.command_tree: for com in branch.children: yield Completion(com.data) is_param = False started_param = False prefix = "" param = "" if text_before_cursor.split(): param = text_before_cursor.split()[-1] if param.startswith("-"): is_param = True elif len(text_before_cursor.split()) > 2 and text_before_cursor.split()[-2]\ and text_before_cursor.split()[-2].startswith('-'): is_param = True param = text_before_cursor.split()[-2] started_param = True prefix = text_before_cursor.split()[-1] arg_name = "" if command in self.cmdtab: if is_param: for arg in self.cmdtab[command].arguments: for name in self.cmdtab[command].arguments[ arg].options_list: if name == param: arg_name = arg break if arg_name: break if arg_name and (text_before_cursor.split()[-1].startswith('-') or\ text_before_cursor.split()[-2].startswith('-')): try: for choice in self.cmdtab[command].arguments[ arg_name].choices: if started_param: if choice.lower().startswith(prefix.lower())\ and choice not in text_before_cursor.split(): yield Completion(choice, -len(prefix)) else: yield Completion(choice, -len(prefix)) except TypeError: pass if self.cmdtab[command].arguments[arg_name].completer: try: for comp in self.cmdtab[command].\ arguments[arg_name].completer(prefix=prefix, action=None,\ parser=self.parser, parsed_args=None): if started_param: if comp.lower().startswith(prefix.lower())\ and comp not in text_before_cursor.split(): yield Completion(comp, -len(prefix)) else: yield Completion(comp, -len(prefix)) except TypeError: try: for comp in self.cmdtab[command].\ arguments[arg_name].completer(prefix): if started_param: if comp.lower().startswith(prefix.lower())\ and comp not in text_before_cursor.split(): yield Completion( comp, -len(prefix)) else: yield Completion(comp, -len(prefix)) except TypeError: try: for comp in self.cmdtab[command].\ arguments[arg_name].completer(): if started_param: if comp.lower().startswith(prefix.lower())\ and comp not in text_before_cursor.split(): yield Completion( comp, -len(prefix)) else: yield Completion( comp, -len(prefix)) except TypeError: print("TypeError: " + TypeError.message) def is_completable(self, symbol): """ whether the word can be completed as a command or parameter """ return self.has_parameters( symbol) or symbol in self.param_description.keys() def get_param(self, command): """ returns the parameters for a given command """ return self.command_parameters[command] def get_param_description(self, param): """ gets a description of an empty string """ if param in self.param_description: return self.param_description[param] else: return "" def get_description(self, command): """ returns the description for a given command """ return self.command_description[command] def has_parameters(self, command): """ returns whether given command is valid """ return command in self.command_parameters.keys() def has_description(self, param): """ if a parameter has a description """ return param in self.param_description.keys() and \ not self.param_description[param].isspace()
class Application(object): TRANSFORM_RESULT = 'Application.TransformResults' FILTER_RESULT = 'Application.FilterResults' GLOBAL_PARSER_CREATED = 'GlobalParser.Created' COMMAND_PARSER_LOADED = 'CommandParser.Loaded' COMMAND_PARSER_PARSING = 'CommandParser.Parsing' COMMAND_PARSER_PARSED = 'CommandParser.Parsed' COMMAND_TABLE_LOADED = 'CommandTable.Loaded' COMMAND_TABLE_PARAMS_LOADED = 'CommandTableParams.Loaded' def __init__(self, configuration=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': { 'x-ms-client-request-id': str(uuid.uuid1()) }, 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.configuration = configuration self.progress_controller = progress.ProgressHook() def get_progress_controller(self, det=False): self.progress_controller.init_progress(progress.get_progress_view(det)) return self.progress_controller def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): # pylint: disable=too-many-statements argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table(argv) self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if not argv: enable_autocomplete(self.parser) az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) # TODO: Question, is this needed? telemetry.set_command_details('az') telemetry.set_success(summary='welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' # Rudimentary parsing to get the command nouns = [] for i, current in enumerate(argv): try: if current[0] == '-': break except IndexError: pass argv[i] = current.lower() nouns.append(argv[i]) command = ' '.join(nouns) if argv[-1] in ('--help', '-h') or command in command_table: self.configuration.load_params(command) self.raise_event(self.COMMAND_TABLE_PARAMS_LOADED, command_table=command_table) self.parser.load_command_table(command_table) if self.session['completer_active']: enable_autocomplete(self.parser) self.raise_event(self.COMMAND_PARSER_PARSING, argv=argv) args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) telemetry.set_command_details(expanded_arg.command, self.configuration.output_format, [p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer=command_table[args.command].table_transformer, is_query_active=self.session['query_active']) def raise_event(self, name, **kwargs): '''Raise the event `name`. ''' data = truncate_text(str(kwargs), width=500) logger.debug("Application event '%s' with event data %s", name, data) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): '''Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].append(handler) logger.debug("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): '''Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].remove(handler) logger.debug("Removed application event handler '%s' at %s", name, handler) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs['global_group'] global_group.add_argument('--output', '-o', dest='_output_format', choices=['json', 'tsv', 'table', 'jsonc','pandas'], default=az_config.get('core', 'output', fallback='json'), help='Output format', type=str.lower) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true', help='Increase logging verbosity. Use --debug for full debug logs.') # pylint: disable=line-too-long global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true', help='Increase logging verbosity to show all debug logs.') @staticmethod def _maybe_load_file(arg): ix = arg.find('@') if ix == -1: # no @ found return arg poss_file = arg[ix + 1:] if not poss_file: # if nothing after @ then it can't be a file return arg elif ix == 0: return Application._load_file(poss_file) # if @ not at the start it can't be a file return arg @staticmethod def _expand_file_prefix(arg): arg_split = arg.split('=', 1) try: return '='.join([arg_split[0], Application._maybe_load_file(arg_split[1])]) except IndexError: return Application._maybe_load_file(arg_split[0]) @staticmethod def _expand_file_prefixed_files(argv): return list([Application._expand_file_prefix(arg) for arg in argv]) @staticmethod def _load_file(path): if path == '-': content = sys.stdin.read() else: content = read_file_content(os.path.expanduser(path), allow_binary=True) return content[0:-1] if content and content[-1] == '\n' else content def _handle_builtin_arguments(self, **kwargs): args = kwargs['args'] self.configuration.output_format = args._output_format # pylint: disable=protected-access del args._output_format
class Application(object): TRANSFORM_RESULT = 'Application.TransformResults' FILTER_RESULT = 'Application.FilterResults' GLOBAL_PARSER_CREATED = 'GlobalParser.Created' COMMAND_PARSER_LOADED = 'CommandParser.Loaded' COMMAND_PARSER_PARSED = 'CommandParser.Parsed' COMMAND_TABLE_LOADED = 'CommandTable.Loaded' def __init__(self, config=None): self._event_handlers = defaultdict(lambda: []) self.session = { 'headers': { 'x-ms-client-request-id': str(uuid.uuid1()) }, 'command': 'unknown', 'completer_active': ARGCOMPLETE_ENV_NAME in os.environ, 'query_active': False } # Register presence of and handlers for global parameters self.register(self.GLOBAL_PARSER_CREATED, Application._register_builtin_arguments) self.register(self.COMMAND_PARSER_PARSED, self._handle_builtin_arguments) # Let other extensions make their presence known azure.cli.core.extensions.register_extensions(self) self.global_parser = AzCliCommandParser(prog='az', add_help=False) global_group = self.global_parser.add_argument_group('global', 'Global Arguments') self.raise_event(self.GLOBAL_PARSER_CREATED, global_group=global_group) self.parser = AzCliCommandParser(prog='az', parents=[self.global_parser]) self.initialize(config or Configuration([])) def initialize(self, configuration): self.configuration = configuration def execute(self, unexpanded_argv): argv = Application._expand_file_prefixed_files(unexpanded_argv) command_table = self.configuration.get_command_table() self.raise_event(self.COMMAND_TABLE_LOADED, command_table=command_table) self.parser.load_command_table(command_table) self.raise_event(self.COMMAND_PARSER_LOADED, parser=self.parser) if len(argv) == 0: az_subparser = self.parser.subparsers[tuple()] _help.show_welcome(az_subparser) log_telemetry('welcome') return None if argv[0].lower() == 'help': argv[0] = '--help' args = self.parser.parse_args(argv) self.raise_event(self.COMMAND_PARSER_PARSED, command=args.command, args=args) results = [] for expanded_arg in _explode_list_args(args): self.session['command'] = expanded_arg.command try: _validate_arguments(expanded_arg) except CLIError: raise except: # pylint: disable=bare-except err = sys.exc_info()[1] getattr(expanded_arg, '_parser', self.parser).validation_error(str(err)) # Consider - we are using any args that start with an underscore (_) as 'private' # arguments and remove them from the arguments that we pass to the actual function. # This does not feel quite right. params = dict([(key, value) for key, value in expanded_arg.__dict__.items() if not key.startswith('_')]) params.pop('subcommand', None) params.pop('func', None) params.pop('command', None) log_telemetry(expanded_arg.command, log_type='pageview', output_type=self.configuration.output_format, parameters=[p for p in unexpanded_argv if p.startswith('-')]) result = expanded_arg.func(params) result = todict(result) results.append(result) if len(results) == 1: results = results[0] event_data = {'result': results} self.raise_event(self.TRANSFORM_RESULT, event_data=event_data) self.raise_event(self.FILTER_RESULT, event_data=event_data) return CommandResultItem(event_data['result'], table_transformer= command_table[args.command].table_transformer, is_query_active=self.session['query_active']) def raise_event(self, name, **kwargs): '''Raise the event `name`. ''' logger.info("Application event '%s' with event data %s", name, kwargs) for func in list(self._event_handlers[name]): # Make copy in case handler modifies the list func(**kwargs) def register(self, name, handler): '''Register a callable that will be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].append(handler) logger.info("Registered application event handler '%s' at %s", name, handler) def remove(self, name, handler): '''Remove a callable that is registered to be called when the event `name` is raised. param: name: The name of the event param: handler: Function that takes two parameters; name: name of the event raised event_data: `dict` with event specific data. ''' self._event_handlers[name].remove(handler) logger.info("Removed application event handler '%s' at %s", name, handler) @staticmethod def _register_builtin_arguments(**kwargs): global_group = kwargs['global_group'] global_group.add_argument('--subscription', dest='_subscription_id', help=argparse.SUPPRESS) global_group.add_argument('--output', '-o', dest='_output_format', choices=['json', 'tsv', 'list', 'table', 'jsonc'], default=az_config.get('core', 'output', fallback='json'), help='Output format', type=str.lower) # The arguments for verbosity don't get parsed by argparse but we add it here for help. global_group.add_argument('--verbose', dest='_log_verbosity_verbose', action='store_true', help='Increase logging verbosity. Use --debug for full debug logs.') #pylint: disable=line-too-long global_group.add_argument('--debug', dest='_log_verbosity_debug', action='store_true', help='Increase logging verbosity to show all debug logs.') @staticmethod def _expand_file_prefixed_files(argv): return list( [Application._load_file(arg[1:]) if arg.startswith('@') else arg for arg in argv] ) @staticmethod def _load_file(path): try: if path == '-': content = sys.stdin.read() else: with open(path, 'r') as input_file: content = input_file.read() return content[0:-1] if content[-1] == '\n' else content except: raise CLIError('Failed to open file {}'.format(path)) def _handle_builtin_arguments(self, **kwargs): args = kwargs['args'] self.configuration.output_format = args._output_format #pylint: disable=protected-access del args._output_format