def run(self): """ starts the REPL """ telemetry.start() from azure.cli.core.application import APPLICATION APPLICATION.get_progress_controller = self.progress_patch # refresh the cache and completer self.command_table_thread = LoadCommandTableThread( restart_completer, self) self.command_table_thread.start() from azclishell.configuration import SHELL_HELP self.cli.buffers['symbols'].reset( initial_document=Document(u'{}'.format(SHELL_HELP))) while True: try: try: document = self.cli.run(reset_current_buffer=True) text = document.text if not text: # not input self.set_prompt() continue cmd = text outside = False except AttributeError: # when the user pressed Control D break else: b_flag, c_flag, outside, cmd = self._special_cases( cmd, outside) if not self.default_command: self.history.append(text) if b_flag: break if c_flag: self.set_prompt() continue self.set_prompt() if outside: subprocess.Popen(cmd, shell=True).communicate() else: self.cli_execute(cmd) # because I catch the sys exit, I have to push out cli_telemetry.conclude() except (KeyboardInterrupt, ValueError): # CTRL C self.set_prompt() continue print('Have a lovely day!!', file=self.output) telemetry.conclude()
def run(self): """ starts the REPL """ telemetry.start() from azure.cli.core.application import APPLICATION APPLICATION.get_progress_controller = self.progress_patch # refresh the cache and completer self.command_table_thread = LoadCommandTableThread(restart_completer, self) self.command_table_thread.start() from azclishell.configuration import SHELL_HELP self.cli.buffers['symbols'].reset( initial_document=Document(u'{}'.format(SHELL_HELP))) while True: try: try: document = self.cli.run(reset_current_buffer=True) text = document.text if not text: # not input self.set_prompt() continue cmd = text outside = False except AttributeError: # when the user pressed Control D break else: b_flag, c_flag, outside, cmd = self._special_cases(cmd, outside) if not self.default_command: self.history.append(text) if b_flag: break if c_flag: self.set_prompt() continue self.set_prompt() if outside: subprocess.Popen(cmd, shell=True).communicate() else: self.cli_execute(cmd) # because I catch the sys exit, I have to push out cli_telemetry.conclude() except (KeyboardInterrupt, ValueError): # CTRL C self.set_prompt() continue print('Have a lovely day!!', file=self.output) telemetry.conclude()
class AzInteractiveShell(object): def __init__(self, cli_ctx, style=None, completer=None, styles=None, lexer=None, history=InMemoryHistory(), app=None, input_custom=sys.stdin, output_custom=None, user_feedback=False, intermediate_sleep=.25, final_sleep=4): from azclishell.color_styles import style_factory self.cli_ctx = cli_ctx self.config = Configuration(cli_ctx.config, style=style) self.config.set_style(style) self.styles = style or style_factory(self.config.get_style()) self.lexer = lexer or get_az_lexer(self.config) if self.styles else None try: from azclishell.gather_commands import GatherCommands from azclishell.az_completer import AzCompleter self.completer = completer or AzCompleter(self, GatherCommands(self.config)) except IOError: # if there is no cache self.completer = None self.history = history or FileHistory(os.path.join(self.config.config_dir, self.config.get_history())) os.environ[ENV_ADDITIONAL_USER_AGENT] = 'AZURECLISHELL/' + __version__ self.telemetry = Telemetry(self.cli_ctx) # OH WHAT FUN TO FIGURE OUT WHAT THESE ARE! self._cli = None self.refresh_cli = False self.layout = None self.description_docs = u'' self.param_docs = u'' self.example_docs = u'' self._env = os.environ self.last = None self.last_exit = 0 self.user_feedback = user_feedback self.input = input_custom self.output = output_custom self.config_default = "" self.default_command = "" self.threads = [] self.curr_thread = None self.spin_val = -1 self.intermediate_sleep = intermediate_sleep self.final_sleep = final_sleep # try to consolidate state information here... # These came from key bindings... self._section = 1 self.is_prompting = False self.is_example_repl = False self.is_showing_default = False self.is_symbols = True def __call__(self): if self.cli_ctx.data["az_interactive_active"]: logger.warning("You're in the interactive shell already.\n") return if self.config.BOOLEAN_STATES[self.config.config.get('DEFAULT', 'firsttime')]: self.config.firsttime() if not self.config.has_feedback() and frequency_heuristic(self): print("\n\nAny comments or concerns? You can use the \'feedback\' command!" + " We would greatly appreciate it.\n") self.cli_ctx.data["az_interactive_active"] = True self.run() self.cli_ctx.data["az_interactive_active"] = False @property def cli(self): """ Makes the interface or refreshes it """ if self._cli is None or self.refresh_cli: self._cli = self.create_interface() self.refresh_cli = False return self._cli def handle_cd(self, cmd): """changes dir """ if len(cmd) != 2: print("Invalid syntax: cd path", file=self.output) return path = os.path.expandvars(os.path.expanduser(cmd[1])) try: os.chdir(path) except OSError as ex: print("cd: %s\n" % ex, file=self.output) def on_input_timeout(self, cli): """ brings up the metadata for the command if there is a valid command already typed """ document = cli.current_buffer.document text = document.text text = text.replace('az ', '') if self.default_command: text = self.default_command + ' ' + text param_info, example = self.generate_help_text(text) self.param_docs = u'{}'.format(param_info) self.example_docs = u'{}'.format(example) self._update_default_info() cli.buffers['description'].reset( initial_document=Document(self.description_docs, cursor_position=0)) cli.buffers['parameter'].reset( initial_document=Document(self.param_docs)) cli.buffers['examples'].reset( initial_document=Document(self.example_docs)) cli.buffers['default_values'].reset( initial_document=Document( u'{}'.format(self.config_default if self.config_default else 'No Default Values'))) self._update_toolbar() cli.request_redraw() def _space_examples(self, list_examples, rows, section_value): """ makes the example text """ examples_with_index = [] for i, _ in list(enumerate(list_examples)): if len(list_examples[i]) > 1: examples_with_index.append("[" + str(i + 1) + "] " + list_examples[i][0] + list_examples[i][1]) example = "".join(exam for exam in examples_with_index) num_newline = example.count('\n') page_number = '' if num_newline > rows * PART_SCREEN_EXAMPLE and rows > PART_SCREEN_EXAMPLE * 10: len_of_excerpt = math.floor(float(rows) * PART_SCREEN_EXAMPLE) group = example.split('\n') end = int(section_value * len_of_excerpt) begin = int((section_value - 1) * len_of_excerpt) if end < num_newline: example = '\n'.join(group[begin:end]) + "\n" else: # default chops top off example = '\n'.join(group[begin:]) + "\n" while ((section_value - 1) * len_of_excerpt) > num_newline: self._section -= 1 page_number = '\n' + str(section_value) + "/" + str(int(math.ceil(num_newline / len_of_excerpt))) return example + page_number + ' CTRL+Y (^) CTRL+N (v)' def _update_toolbar(self): cli = self.cli _, cols = get_window_dim() cols = int(cols) empty_space = " " * cols delta = datetime.datetime.utcnow() - START_TIME if self.user_feedback and delta.seconds < DISPLAY_TIME: toolbar = [ ' Try out the \'feedback\' command', 'If refreshed disappear in: {}'.format(str(DISPLAY_TIME - delta.seconds))] elif self.command_table_thread.is_alive(): toolbar = [ ' Loading...', 'Hit [enter] to refresh' ] else: toolbar = self._toolbar_info() toolbar, empty_space = space_toolbar(toolbar, empty_space) cli.buffers['bottom_toolbar'].reset( initial_document=Document(u'{}{}{}'.format(NOTIFICATIONS, toolbar, empty_space))) def _toolbar_info(self): sub_name = "" try: profile = Profile(cli_ctx=self.cli_ctx) sub_name = profile.get_subscription()[_SUBSCRIPTION_NAME] except CLIError: pass curr_cloud = "Cloud: {}".format(self.cli_ctx.cloud.name) tool_val = 'Subscription: {}'.format(sub_name) if sub_name else curr_cloud settings_items = [ " [F1]Layout", "[F2]Defaults", "[F3]Keys", "[Ctrl+D]Quit", tool_val ] return settings_items def generate_help_text(self, text): """ generates the help text based on commands typed """ command = param_descrip = example = "" any_documentation = False is_command = True rows, _ = get_window_dim() rows = int(rows) if not self.completer: return param_descrip, example for word in text.split(): if word.startswith("-"): # any parameter is_command = False if is_command: command += str(word) + " " if self.completer and self.completer.is_completable(command.rstrip()): cmdstp = command.rstrip() any_documentation = True if word in self.completer.command_parameters[cmdstp] and self.completer.has_description( cmdstp + " " + word): param_descrip = word + ":\n" + \ self.completer.param_description.get(cmdstp + " " + word, '') self.description_docs = u'{}'.format( self.completer.command_description[cmdstp]) if cmdstp in self.completer.command_examples: string_example = "" for example in self.completer.command_examples[cmdstp]: for part in example: string_example += part example = self._space_examples( self.completer.command_examples[cmdstp], rows, self._section) if not any_documentation: self.description_docs = u'' return param_descrip, example def _update_default_info(self): try: options = self.cli_ctx.config.config_parser.options(DEFAULTS_SECTION) self.config_default = "" for opt in options: self.config_default += opt + ": " + self.cli_ctx.config.get(DEFAULTS_SECTION, opt) + " " except configparser.NoSectionError: self.config_default = "" def create_application(self, full_layout=True): """ makes the application object and the buffers """ if full_layout: layout = create_layout(self, self.lexer, ExampleLexer, ToolbarLexer) else: layout = create_tutorial_layout(self.lexer) buffers = { DEFAULT_BUFFER: Buffer(is_multiline=True), 'description': Buffer(is_multiline=True, read_only=True), 'parameter': Buffer(is_multiline=True, read_only=True), 'examples': Buffer(is_multiline=True, read_only=True), 'bottom_toolbar': Buffer(is_multiline=True), 'example_line': Buffer(is_multiline=True), 'default_values': Buffer(), 'symbols': Buffer(), 'progress': Buffer(is_multiline=False) } writing_buffer = Buffer( history=self.history, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True, completer=self.completer, complete_while_typing=Always() ) return Application( mouse_support=False, style=self.styles, buffer=writing_buffer, on_input_timeout=self.on_input_timeout, key_bindings_registry=InteractiveKeyBindings(self).registry, layout=layout, buffers=buffers, ) def create_interface(self): """ instantiates the interface """ from prompt_toolkit.interface import CommandLineInterface return CommandLineInterface( application=self.create_application(), eventloop=create_eventloop()) def set_prompt(self, prompt_command="", position=0): """ writes the prompt line """ self.description_docs = u'{}'.format(prompt_command) self.cli.current_buffer.reset( initial_document=Document( self.description_docs, cursor_position=position)) self.cli.request_redraw() def set_scope(self, value): """ narrows the scopes the commands """ set_scope(value) if self.default_command: self.default_command += ' ' + value else: self.default_command += value return value def handle_example(self, text, continue_flag): """ parses for the tutorial """ cmd = text.partition(SELECT_SYMBOL['example'])[0].rstrip() num = text.partition(SELECT_SYMBOL['example'])[2].strip() example = "" try: num = int(num) - 1 except ValueError: print("An Integer should follow the colon", file=self.output) return "" if cmd in self.completer.command_examples: if num >= 0 and num < len(self.completer.command_examples[cmd]): example = self.completer.command_examples[cmd][num][1] example = example.replace('\n', '') else: print('Invalid example number', file=self.output) return '', True example = example.replace('az', '') starting_index = None counter = 0 example_no_fill = "" flag_fill = True for word in example.split(): if flag_fill: example_no_fill += word + " " if word.startswith('-'): example_no_fill += word + " " if not starting_index: starting_index = counter flag_fill = False counter += 1 return self.example_repl(example_no_fill, example, starting_index, continue_flag) def example_repl(self, text, example, start_index, continue_flag): """ REPL for interactive tutorials """ from prompt_toolkit.interface import CommandLineInterface if start_index: start_index = start_index + 1 cmd = ' '.join(text.split()[:start_index]) example_cli = CommandLineInterface( application=self.create_application( full_layout=False), eventloop=create_eventloop()) example_cli.buffers['example_line'].reset( initial_document=Document(u'{}\n'.format( add_new_lines(example))) ) while start_index < len(text.split()): if self.default_command: cmd = cmd.replace(self.default_command + ' ', '') example_cli.buffers[DEFAULT_BUFFER].reset( initial_document=Document( u'{}'.format(cmd), cursor_position=len(cmd))) example_cli.request_redraw() answer = example_cli.run() if not answer: return "", True answer = answer.text if answer.strip('\n') == cmd.strip('\n'): continue else: if len(answer.split()) > 1: start_index += 1 cmd += " " + answer.split()[-1] + " " +\ u' '.join(text.split()[start_index:start_index + 1]) example_cli.exit() del example_cli else: cmd = text return cmd, continue_flag # pylint: disable=too-many-branches def _special_cases(self, cmd, outside): break_flag = False continue_flag = False args = parse_quotes(cmd) cmd_stripped = cmd.strip() if cmd_stripped and cmd.split(' ', 1)[0].lower() == 'az': self.telemetry.track_ssg('az', cmd) cmd = ' '.join(cmd.split()[1:]) if self.default_command: cmd = self.default_command + " " + cmd if cmd_stripped == "quit" or cmd_stripped == "exit": break_flag = True elif cmd_stripped == "clear-history": # clears the history, but only when you restart outside = True cmd = 'echo -n "" >' +\ os.path.join( self.config.config_dir(), self.config.get_history()) elif cmd_stripped == CLEAR_WORD: outside = True cmd = CLEAR_WORD if cmd_stripped: if cmd_stripped[0] == SELECT_SYMBOL['outside']: cmd = cmd_stripped[1:] outside = True if cmd.strip() and cmd.split()[0] == 'cd': self.handle_cd(parse_quotes(cmd)) continue_flag = True self.telemetry.track_ssg('outside', '') elif cmd_stripped[0] == SELECT_SYMBOL['exit_code']: meaning = "Success" if self.last_exit == 0 else "Failure" print(meaning + ": " + str(self.last_exit), file=self.output) continue_flag = True self.telemetry.track_ssg('exit code', '') elif SELECT_SYMBOL['query'] in cmd_stripped and self.last and self.last.result: continue_flag = self.handle_jmespath_query(args) self.telemetry.track_ssg('query', '') elif args[0] == '--version' or args[0] == '-v': try: continue_flag = True self.cli_ctx.show_version() except SystemExit: pass elif "|" in cmd or ">" in cmd: # anything I don't parse, send off outside = True cmd = "az " + cmd elif SELECT_SYMBOL['example'] in cmd: cmd, continue_flag = self.handle_example(cmd, continue_flag) self.telemetry.track_ssg('tutorial', cmd) elif len(cmd_stripped) > 2 and SELECT_SYMBOL['scope'] == cmd_stripped[0:2]: continue_flag, cmd = self.handle_scoping_input(continue_flag, cmd, cmd_stripped) return break_flag, continue_flag, outside, cmd def handle_jmespath_query(self, args): """ handles the jmespath query for injection or printing """ continue_flag = False query_symbol = SELECT_SYMBOL['query'] symbol_len = len(query_symbol) try: if len(args) == 1: # if arguments start with query_symbol, just print query result if args[0] == query_symbol: result = self.last.result elif args[0].startswith(query_symbol): result = jmespath.search(args[0][symbol_len:], self.last.result) print(json.dumps(result, sort_keys=True, indent=2), file=self.output) elif args[0].startswith(query_symbol): # print error message, user unsure of query shortcut usage print(("Usage Error: " + os.linesep + "1. Use {0} stand-alone to display previous result with optional filtering " "(Ex: {0}[jmespath query])" + os.linesep + "OR:" + os.linesep + "2. Use {0} to query the previous result for argument values " "(Ex: group show --name {0}[jmespath query])").format(query_symbol), file=self.output) else: # query, inject into cmd def jmespath_query(match): if match.group(0) == query_symbol: return str(self.last.result) query_result = jmespath.search(match.group(0)[symbol_len:], self.last.result) return str(query_result) def sub_result(arg): escaped_symbol = re.escape(query_symbol) # regex captures query symbol and all characters following it in the argument return json.dumps(re.sub(r'%s.*' % escaped_symbol, jmespath_query, arg)) cmd_base = ' '.join(map(sub_result, args)) self.cli_execute(cmd_base) continue_flag = True except (jmespath.exceptions.ParseError, CLIError) as e: print("Invalid Query Input: " + str(e), file=self.output) continue_flag = True return continue_flag def handle_scoping_input(self, continue_flag, cmd, text): """ handles what to do with a scoping gesture """ default_split = text.partition(SELECT_SYMBOL['scope'])[2].split() cmd = cmd.replace(SELECT_SYMBOL['scope'], '') continue_flag = True if not default_split: self.default_command = "" set_scope("", add=False) print('unscoping all', file=self.output) return continue_flag, cmd while default_split: if not text: value = '' else: value = default_split[0] if self.default_command: tree_val = self.default_command + " " + value else: tree_val = value if in_tree(self.completer.command_tree, tree_val.strip()): self.set_scope(value) print("defaulting: " + value, file=self.output) cmd = cmd.replace(SELECT_SYMBOL['scope'], '') self.telemetry.track_ssg('scope command', value) elif SELECT_SYMBOL['unscope'] == default_split[0] and \ len(self.default_command.split()) > 0: value = self.default_command.split()[-1] self.default_command = ' ' + ' '.join(self.default_command.split()[:-1]) if not self.default_command.strip(): self.default_command = self.default_command.strip() set_scope(self.default_command, add=False) print('unscoping: ' + value, file=self.output) elif SELECT_SYMBOL['unscope'] not in text: print("Scope must be a valid command", file=self.output) default_split = default_split[1:] return continue_flag, cmd def cli_execute(self, cmd): """ sends the command to the CLI to be executed """ try: args = parse_quotes(cmd) if len(args) > 0 and args[0] == 'feedback': self.config.set_feedback('yes') self.user_feedback = False azure_folder = get_config_dir() if not os.path.exists(azure_folder): os.makedirs(azure_folder) ACCOUNT.load(os.path.join(azure_folder, 'azureProfile.json')) CONFIG.load(os.path.join(azure_folder, 'az.json')) SESSION.load(os.path.join(azure_folder, 'az.sess'), max_age=3600) if '--progress' in args: args.remove('--progress') execute_args = [args] thread = Thread(target=self.app.execute, args=execute_args) thread.daemon = True thread.start() self.threads.append(thread) self.curr_thread = thread progress_args = [self] thread = Thread(target=progress_view, args=progress_args) thread.daemon = True thread.start() self.threads.append(thread) result = None else: invocation = self.cli_ctx.invocation_cls(cli_ctx=self.cli_ctx, parser_cls=self.cli_ctx.parser_cls, commands_loader_cls=self.cli_ctx.commands_loader_cls, help_cls=self.cli_ctx.help_cls) result = invocation.execute(args) self.last_exit = 0 if result and result.result is not None: from azure.cli.core._output import OutputProducer if self.output: self.output.write(result) self.output.flush() else: formatter = OutputProducer.get_formatter(self.cli_ctx.invocation.data['output']) OutputProducer(formatter=formatter).out(result) self.last = result except Exception as ex: # pylint: disable=broad-except self.last_exit = handle_exception(ex) except SystemExit as ex: self.last_exit = int(ex.code) def progress_patch(self, _=False): """ forces to use the Shell Progress """ from azclishell.progress import ShellProgressView self.cli_ctx.progress_controller.init_progress(ShellProgressView()) return self.cli_ctx.progress_controller def run(self): """ starts the REPL """ self.telemetry.start() self.cli_ctx.get_progress_controller = self.progress_patch self.command_table_thread = LoadCommandTableThread(restart_completer, self) self.command_table_thread.start() from azclishell.configuration import SHELL_HELP self.cli.buffers['symbols'].reset( initial_document=Document(u'{}'.format(SHELL_HELP))) while True: try: try: document = self.cli.run(reset_current_buffer=True) text = document.text if not text: # not input self.set_prompt() continue cmd = text outside = False except AttributeError: # when the user pressed Control D break else: b_flag, c_flag, outside, cmd = self._special_cases(cmd, outside) if not self.default_command: self.history.append(text) if b_flag: break if c_flag: self.set_prompt() continue self.set_prompt() if outside: subprocess.Popen(cmd, shell=True).communicate() else: self.cli_execute(cmd) # because I catch the sys exit, I have to push out self.telemetry.conclude() except (KeyboardInterrupt, ValueError): # CTRL C self.set_prompt() continue self.telemetry.conclude()
class Shell(object): """ represents the shell """ def __init__(self, completer=None, styles=None, lexer=None, history=InMemoryHistory(), app=None, input_custom=sys.stdin, output_custom=None, user_feedback=False, intermediate_sleep=.25, final_sleep=4): self.styles = styles if styles: self.lexer = lexer or AzLexer else: self.lexer = None self.app = app self.completer = completer self.history = history self._cli = None self.refresh_cli = False self.layout = None self.description_docs = u'' self.param_docs = u'' self.example_docs = u'' self._env = os.environ self.last = None self.last_exit = 0 self.user_feedback = user_feedback self.input = input_custom self.output = output_custom self.config_default = "" self.default_command = "" self.threads = [] self.curr_thread = None self.spin_val = -1 self.intermediate_sleep = intermediate_sleep # in seconds self.final_sleep = final_sleep # in seconds @property def cli(self): """ Makes the interface or refreshes it """ if self._cli is None or self.refresh_cli: self._cli = self.create_interface() self.refresh_cli = False return self._cli def handle_cd(self, cmd): """changes dir """ if len(cmd) != 2: print("Invalid syntax: cd path", file=self.output) return path = os.path.expandvars(os.path.expanduser(cmd[1])) try: os.chdir(path) except OSError as ex: print("cd: %s\n" % ex, file=self.output) def on_input_timeout(self, cli): """ brings up the metadata for the command if there is a valid command already typed """ document = cli.current_buffer.document text = document.text text = text.replace('az ', '') if self.default_command: text = self.default_command + ' ' + text param_info, example = self.generate_help_text(text) self.param_docs = u'{}'.format(param_info) self.example_docs = u'{}'.format(example) self._update_default_info() cli.buffers['description'].reset(initial_document=Document( self.description_docs, cursor_position=0)) cli.buffers['parameter'].reset( initial_document=Document(self.param_docs)) cli.buffers['examples'].reset( initial_document=Document(self.example_docs)) cli.buffers['default_values'].reset(initial_document=Document( u'{}'.format(self.config_default if self. config_default else 'No Default Values'))) self._update_toolbar() cli.request_redraw() def _update_toolbar(self): cli = self.cli _, cols = get_window_dim() cols = int(cols) empty_space = " " * cols delta = datetime.datetime.utcnow() - START_TIME if self.user_feedback and delta.seconds < DISPLAY_TIME: toolbar = [ ' Try out the \'feedback\' command', 'If refreshed disappear in: {}'.format( str(DISPLAY_TIME - delta.seconds)) ] elif self.command_table_thread.is_alive(): toolbar = [' Loading...', 'Hit [enter] to refresh'] else: toolbar = self._toolbar_info() toolbar, empty_space = space_toolbar(toolbar, empty_space) cli.buffers['bottom_toolbar'].reset(initial_document=Document( u'{}{}{}'.format(NOTIFICATIONS, toolbar, empty_space))) def _toolbar_info(self): sub_name = "" try: sub_name = PROFILE.get_subscription()[_SUBSCRIPTION_NAME] except CLIError: pass curr_cloud = "Cloud: {}".format(get_active_cloud_name()) tool_val = 'Subscription: {}'.format( sub_name) if sub_name else curr_cloud settings_items = [ " [F1]Layout", "[F2]Defaults", "[F3]Keys", "[Ctrl+D]Quit", tool_val ] return settings_items def generate_help_text(self, text): """ generates the help text based on commands typed """ command = param_descrip = example = "" any_documentation = False is_command = True rows, _ = get_window_dim() rows = int(rows) if not self.completer: return param_descrip, example for word in text.split(): if word.startswith("-"): # any parameter is_command = False if is_command: command += str(word) + " " if self.completer and self.completer.is_completable( command.rstrip()): cmdstp = command.rstrip() any_documentation = True if word in self.completer.command_parameters[ cmdstp] and self.completer.has_description(cmdstp + " " + word): param_descrip = word + ":\n" + \ self.completer.param_description.get(cmdstp + " " + word, '') self.description_docs = u'{}'.format( self.completer.command_description[cmdstp]) if cmdstp in self.completer.command_examples: string_example = "" for example in self.completer.command_examples[cmdstp]: for part in example: string_example += part example = space_examples( self.completer.command_examples[cmdstp], rows, get_section()) if not any_documentation: self.description_docs = u'' return param_descrip, example def _update_default_info(self): try: options = az_config.config_parser.options(DEFAULTS_SECTION) self.config_default = "" for opt in options: self.config_default += opt + ": " + az_config.get( DEFAULTS_SECTION, opt) + " " except configparser.NoSectionError: self.config_default = "" def create_application(self, full_layout=True): """ makes the application object and the buffers """ if full_layout: layout = create_layout(self.lexer, ExampleLexer, ToolbarLexer) else: layout = create_tutorial_layout(self.lexer) buffers = { DEFAULT_BUFFER: Buffer(is_multiline=True), 'description': Buffer(is_multiline=True, read_only=True), 'parameter': Buffer(is_multiline=True, read_only=True), 'examples': Buffer(is_multiline=True, read_only=True), 'bottom_toolbar': Buffer(is_multiline=True), 'example_line': Buffer(is_multiline=True), 'default_values': Buffer(), 'symbols': Buffer(), 'progress': Buffer(is_multiline=False) } writing_buffer = Buffer(history=self.history, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True, completer=self.completer, complete_while_typing=Always()) return Application( mouse_support=False, style=self.styles, buffer=writing_buffer, on_input_timeout=self.on_input_timeout, key_bindings_registry=registry, layout=layout, buffers=buffers, ) def create_interface(self): """ instantiates the intereface """ return CommandLineInterface(application=self.create_application(), eventloop=create_eventloop()) def set_prompt(self, prompt_command="", position=0): """ writes the prompt line """ self.description_docs = u'{}'.format(prompt_command) self.cli.current_buffer.reset(initial_document=Document( self.description_docs, cursor_position=position)) self.cli.request_redraw() def set_scope(self, value): """ narrows the scopes the commands """ set_scope(value) if self.default_command: self.default_command += ' ' + value else: self.default_command += value return value def handle_example(self, text, continue_flag): """ parses for the tutorial """ cmd = text.partition(SELECT_SYMBOL['example'])[0].rstrip() num = text.partition(SELECT_SYMBOL['example'])[2].strip() example = "" try: num = int(num) - 1 except ValueError: print("An Integer should follow the colon", file=self.output) return "" if cmd in self.completer.command_examples: if num >= 0 and num < len(self.completer.command_examples[cmd]): example = self.completer.command_examples[cmd][num][1] example = example.replace('\n', '') else: print('Invalid example number', file=self.output) return '', True example = example.replace('az', '') starting_index = None counter = 0 example_no_fill = "" flag_fill = True for word in example.split(): if flag_fill: example_no_fill += word + " " if word.startswith('-'): example_no_fill += word + " " if not starting_index: starting_index = counter flag_fill = False counter += 1 return self.example_repl(example_no_fill, example, starting_index, continue_flag) def example_repl(self, text, example, start_index, continue_flag): """ REPL for interactive tutorials """ if start_index: start_index = start_index + 1 cmd = ' '.join(text.split()[:start_index]) example_cli = CommandLineInterface( application=self.create_application(full_layout=False), eventloop=create_eventloop()) example_cli.buffers['example_line'].reset( initial_document=Document(u'{}\n'.format(add_new_lines( example)))) while start_index < len(text.split()): if self.default_command: cmd = cmd.replace(self.default_command + ' ', '') example_cli.buffers[DEFAULT_BUFFER].reset( initial_document=Document(u'{}'.format(cmd), cursor_position=len(cmd))) example_cli.request_redraw() answer = example_cli.run() if not answer: return "", True answer = answer.text if answer.strip('\n') == cmd.strip('\n'): continue else: if len(answer.split()) > 1: start_index += 1 cmd += " " + answer.split()[-1] + " " +\ u' '.join(text.split()[start_index:start_index + 1]) example_cli.exit() del example_cli else: cmd = text return cmd, continue_flag # pylint: disable=too-many-branches def _special_cases(self, text, cmd, outside): break_flag = False continue_flag = False args = parse_quotes(text) args_no_quotes = [] text_stripped = text.strip() for arg in args: args_no_quotes.append(arg.strip("/'").strip('/"')) if text and len(text.split()) > 0 and text.split()[0].lower() == 'az': telemetry.track_ssg('az', text) cmd = ' '.join(text.split()[1:]) if self.default_command: cmd = self.default_command + " " + cmd if text_stripped == "quit" or text_stripped == "exit": break_flag = True elif text_stripped == "clear-history": # clears the history, but only when you restart outside = True cmd = 'echo -n "" >' +\ os.path.join( SHELL_CONFIG_DIR(), SHELL_CONFIGURATION.get_history()) elif text_stripped == CLEAR_WORD: outside = True cmd = CLEAR_WORD if text_stripped: if text_stripped[0] == SELECT_SYMBOL['outside']: cmd = text_stripped[1:] outside = True if cmd.strip() and cmd.split()[0] == 'cd': self.handle_cd(parse_quotes(cmd)) continue_flag = True telemetry.track_ssg('outside', '') elif text_stripped[0] == SELECT_SYMBOL['exit_code']: meaning = "Success" if self.last_exit == 0 else "Failure" print(meaning + ": " + str(self.last_exit), file=self.output) continue_flag = True telemetry.track_ssg('exit code', '') elif validate_contains_query( args_no_quotes, SELECT_SYMBOL['query']) and self.last and self.last.result: continue_flag = self.handle_jmespath_query( args_no_quotes, continue_flag) telemetry.track_ssg('query', '') elif args[0] == '--version' or args[0] == '-v': try: continue_flag = True show_version_info_exit(self.output) except SystemExit: pass elif "|" in text or ">" in text: # anything I don't parse, send off outside = True cmd = "az " + cmd elif SELECT_SYMBOL['example'] in text: cmd, continue_flag = self.handle_example(cmd, continue_flag) telemetry.track_ssg('tutorial', text) elif len(text_stripped ) > 2 and SELECT_SYMBOL['scope'] == text_stripped[0:2]: continue_flag, cmd = self.handle_scoping_input( continue_flag, cmd, text_stripped) return break_flag, continue_flag, outside, cmd def handle_jmespath_query(self, args, continue_flag): """ handles the jmespath query for injection or printing """ if hasattr(self.last.result, '__dict__'): input_dict = dict(self.last.result) else: input_dict = self.last.result try: queries = [] results = [] injected_command = [] for arg in args: if arg.startswith(SELECT_SYMBOL['query']): query = arg[len(SELECT_SYMBOL['query']):] queries.append(query) results.append(jmespath.search(query, input_dict)) injected_command.append(SELECT_SYMBOL['query']) elif re.search(r'\-.*\=\%s.*' % SELECT_SYMBOL['query'], arg) is not None: parition = arg.partition(SELECT_SYMBOL['query']) base = parition[0] query = parition[2] queries.append(query) results.append(jmespath.search(query, input_dict)) injected_command.append(base + SELECT_SYMBOL['query']) else: injected_command.append(arg) if len(args) > 0 and args[0].startswith(SELECT_SYMBOL['query']): # then push out the query print(json.dumps(results[0], sort_keys=True, indent=2), file=self.output) continue_flag = True else: # inject into cmd cmd_base = ' '.join(injected_command) if all( isinstance(result, (six.text_type, six.string_types)) for result in results): for result in results: cmd_base = cmd_base.replace(SELECT_SYMBOL['query'], result, 1) self.cli_execute(cmd_base) continue_flag = True elif all(isinstance(result, list) for result in results): if len(results) > 1: for res_counter, _ in enumerate(results[0]): base = cmd_base skip = False for counter in range( cmd_base.count(SELECT_SYMBOL['query'])): if counter < len( results) and res_counter < len( results[counter]): try: base = base.replace( SELECT_SYMBOL['query'], results[counter][res_counter], 1) except TypeError: # because of bad input pass else: # if there aren't an even number of results, skip it skip = True if not skip: self.cli_execute(base) else: for res_counter, _ in enumerate(results[0]): base = cmd_base skip = False for counter in range( cmd_base.count(SELECT_SYMBOL['query'])): if counter < len( results) and res_counter < len( results[counter]): try: base = base.replace( SELECT_SYMBOL['query'], results[counter][res_counter], 1) except TypeError: # because of bad input pass else: # if there aren't an even number of results, skip it skip = True if not skip: self.cli_execute(base) continue_flag = True except (jmespath.exceptions.ParseError, CLIError): print("Invalid Query", file=self.output) continue_flag = True return continue_flag def handle_scoping_input(self, continue_flag, cmd, text): """ handles what to do with a scoping gesture """ default_split = text.partition(SELECT_SYMBOL['scope'])[2].split() cmd = cmd.replace(SELECT_SYMBOL['scope'], '') continue_flag = True if not default_split: self.default_command = "" set_scope("", add=False) print('unscoping all', file=self.output) return continue_flag, cmd while default_split: if not text: value = '' else: value = default_split[0] if self.default_command: tree_val = self.default_command + " " + value else: tree_val = value if in_tree(self.completer.command_tree, tree_val.strip()): self.set_scope(value) print("defaulting: " + value, file=self.output) cmd = cmd.replace(SELECT_SYMBOL['scope'], '') telemetry.track_ssg('scope command', value) elif SELECT_SYMBOL['unscope'] == default_split[0] and \ len(self.default_command.split()) > 0: value = self.default_command.split()[-1] self.default_command = ' ' + ' '.join( self.default_command.split()[:-1]) if not self.default_command.strip(): self.default_command = self.default_command.strip() set_scope(self.default_command, add=False) print('unscoping: ' + value, file=self.output) elif SELECT_SYMBOL['unscope'] not in text: print("Scope must be a valid command", file=self.output) default_split = default_split[1:] return continue_flag, cmd def cli_execute(self, cmd): """ sends the command to the CLI to be executed """ try: args = parse_quotes(cmd) azlogging.configure_logging(args) if len(args) > 0 and args[0] == 'feedback': SHELL_CONFIGURATION.set_feedback('yes') self.user_feedback = False azure_folder = get_config_dir() if not os.path.exists(azure_folder): os.makedirs(azure_folder) ACCOUNT.load(os.path.join(azure_folder, 'azureProfile.json')) CONFIG.load(os.path.join(azure_folder, 'az.json')) SESSION.load(os.path.join(azure_folder, 'az.sess'), max_age=3600) if '--progress' in args: args.remove('--progress') execute_args = [args] thread = Thread(target=self.app.execute, args=execute_args) thread.daemon = True thread.start() self.threads.append(thread) self.curr_thread = thread progress_args = [self] thread = Thread(target=progress_view, args=progress_args) thread.daemon = True thread.start() self.threads.append(thread) result = None else: result = self.app.execute(args) self.last_exit = 0 if result and result.result is not None: from azure.cli.core._output import OutputProducer if self.output: self.output.write(result) self.output.flush() else: formatter = OutputProducer.get_formatter( self.app.configuration.output_format) OutputProducer(formatter=formatter).out(result) self.last = result except Exception as ex: # pylint: disable=broad-except self.last_exit = handle_exception(ex) except SystemExit as ex: self.last_exit = int(ex.code) def progress_patch(self, _=False): """ forces to use the Shell Progress """ from azure.cli.core.application import APPLICATION from azclishell.progress import ShellProgressView APPLICATION.progress_controller.init_progress(ShellProgressView()) return APPLICATION.progress_controller def run(self): """ starts the REPL """ telemetry.start() from azure.cli.core.application import APPLICATION APPLICATION.get_progress_controller = self.progress_patch # refresh the cache and completer self.command_table_thread = LoadCommandTableThread( restart_completer, self) self.command_table_thread.start() from azclishell.configuration import SHELL_HELP self.cli.buffers['symbols'].reset( initial_document=Document(u'{}'.format(SHELL_HELP))) while True: try: try: document = self.cli.run(reset_current_buffer=True) text = document.text if not text: # not input self.set_prompt() continue cmd = text outside = False except AttributeError: # when the user pressed Control D break else: b_flag, c_flag, outside, cmd = self._special_cases( text, cmd, outside) if not self.default_command: self.history.append(text) if b_flag: break if c_flag: self.set_prompt() continue self.set_prompt() if outside: subprocess.Popen(cmd, shell=True).communicate() else: self.cli_execute(cmd) cli_telemetry.conclude( ) # because I catch the sys exit, I have to push out except (KeyboardInterrupt, ValueError): # CTRL C self.set_prompt() continue print('Have a lovely day!!', file=self.output) telemetry.conclude()
class Shell(object): """ represents the shell """ def __init__(self, completer=None, styles=None, lexer=None, history=InMemoryHistory(), app=None, input_custom=sys.stdin, output_custom=None, user_feedback=False, intermediate_sleep=.25, final_sleep=4): self.styles = styles if styles: self.lexer = lexer or AzLexer else: self.lexer = None self.app = app self.completer = completer self.history = history self._cli = None self.refresh_cli = False self.layout = None self.description_docs = u'' self.param_docs = u'' self.example_docs = u'' self._env = os.environ self.last = None self.last_exit = 0 self.user_feedback = user_feedback self.input = input_custom self.output = output_custom self.config_default = "" self.default_command = "" self.threads = [] self.curr_thread = None self.spin_val = -1 self.intermediate_sleep = intermediate_sleep self.final_sleep = final_sleep @property def cli(self): """ Makes the interface or refreshes it """ if self._cli is None or self.refresh_cli: self._cli = self.create_interface() self.refresh_cli = False return self._cli def handle_cd(self, cmd): """changes dir """ if len(cmd) != 2: print("Invalid syntax: cd path", file=self.output) return path = os.path.expandvars(os.path.expanduser(cmd[1])) try: os.chdir(path) except OSError as ex: print("cd: %s\n" % ex, file=self.output) def on_input_timeout(self, cli): """ brings up the metadata for the command if there is a valid command already typed """ document = cli.current_buffer.document text = document.text text = text.replace('az ', '') if self.default_command: text = self.default_command + ' ' + text param_info, example = self.generate_help_text(text) self.param_docs = u'{}'.format(param_info) self.example_docs = u'{}'.format(example) self._update_default_info() cli.buffers['description'].reset( initial_document=Document(self.description_docs, cursor_position=0)) cli.buffers['parameter'].reset( initial_document=Document(self.param_docs)) cli.buffers['examples'].reset( initial_document=Document(self.example_docs)) cli.buffers['default_values'].reset( initial_document=Document( u'{}'.format(self.config_default if self.config_default else 'No Default Values'))) self._update_toolbar() cli.request_redraw() def _update_toolbar(self): cli = self.cli _, cols = get_window_dim() cols = int(cols) empty_space = " " * cols delta = datetime.datetime.utcnow() - START_TIME if self.user_feedback and delta.seconds < DISPLAY_TIME: toolbar = [ ' Try out the \'feedback\' command', 'If refreshed disappear in: {}'.format(str(DISPLAY_TIME - delta.seconds))] elif self.command_table_thread.is_alive(): toolbar = [ ' Loading...', 'Hit [enter] to refresh' ] else: toolbar = self._toolbar_info() toolbar, empty_space = space_toolbar(toolbar, empty_space) cli.buffers['bottom_toolbar'].reset( initial_document=Document(u'{}{}{}'.format(NOTIFICATIONS, toolbar, empty_space))) def _toolbar_info(self): sub_name = "" try: sub_name = PROFILE.get_subscription()[_SUBSCRIPTION_NAME] except CLIError: pass curr_cloud = "Cloud: {}".format(get_active_cloud_name()) tool_val = 'Subscription: {}'.format(sub_name) if sub_name else curr_cloud settings_items = [ " [F1]Layout", "[F2]Defaults", "[F3]Keys", "[Ctrl+D]Quit", tool_val ] return settings_items def generate_help_text(self, text): """ generates the help text based on commands typed """ command = param_descrip = example = "" any_documentation = False is_command = True rows, _ = get_window_dim() rows = int(rows) if not self.completer: return param_descrip, example for word in text.split(): if word.startswith("-"): # any parameter is_command = False if is_command: command += str(word) + " " if self.completer and self.completer.is_completable(command.rstrip()): cmdstp = command.rstrip() any_documentation = True if word in self.completer.command_parameters[cmdstp] and self.completer.has_description( cmdstp + " " + word): param_descrip = word + ":\n" + \ self.completer.param_description.get(cmdstp + " " + word, '') self.description_docs = u'{}'.format( self.completer.command_description[cmdstp]) if cmdstp in self.completer.command_examples: string_example = "" for example in self.completer.command_examples[cmdstp]: for part in example: string_example += part example = space_examples( self.completer.command_examples[cmdstp], rows, get_section()) if not any_documentation: self.description_docs = u'' return param_descrip, example def _update_default_info(self): try: options = az_config.config_parser.options(DEFAULTS_SECTION) self.config_default = "" for opt in options: self.config_default += opt + ": " + az_config.get(DEFAULTS_SECTION, opt) + " " except configparser.NoSectionError: self.config_default = "" def create_application(self, full_layout=True): """ makes the application object and the buffers """ if full_layout: layout = create_layout(self.lexer, ExampleLexer, ToolbarLexer) else: layout = create_tutorial_layout(self.lexer) buffers = { DEFAULT_BUFFER: Buffer(is_multiline=True), 'description': Buffer(is_multiline=True, read_only=True), 'parameter': Buffer(is_multiline=True, read_only=True), 'examples': Buffer(is_multiline=True, read_only=True), 'bottom_toolbar': Buffer(is_multiline=True), 'example_line': Buffer(is_multiline=True), 'default_values': Buffer(), 'symbols': Buffer(), 'progress': Buffer(is_multiline=False) } writing_buffer = Buffer( history=self.history, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True, completer=self.completer, complete_while_typing=Always() ) return Application( mouse_support=False, style=self.styles, buffer=writing_buffer, on_input_timeout=self.on_input_timeout, key_bindings_registry=registry, layout=layout, buffers=buffers, ) def create_interface(self): """ instantiates the intereface """ return CommandLineInterface( application=self.create_application(), eventloop=create_eventloop()) def set_prompt(self, prompt_command="", position=0): """ writes the prompt line """ self.description_docs = u'{}'.format(prompt_command) self.cli.current_buffer.reset( initial_document=Document( self.description_docs, cursor_position=position)) self.cli.request_redraw() def set_scope(self, value): """ narrows the scopes the commands """ set_scope(value) if self.default_command: self.default_command += ' ' + value else: self.default_command += value return value def handle_example(self, text, continue_flag): """ parses for the tutorial """ cmd = text.partition(SELECT_SYMBOL['example'])[0].rstrip() num = text.partition(SELECT_SYMBOL['example'])[2].strip() example = "" try: num = int(num) - 1 except ValueError: print("An Integer should follow the colon", file=self.output) return "" if cmd in self.completer.command_examples: if num >= 0 and num < len(self.completer.command_examples[cmd]): example = self.completer.command_examples[cmd][num][1] example = example.replace('\n', '') else: print('Invalid example number', file=self.output) return '', True example = example.replace('az', '') starting_index = None counter = 0 example_no_fill = "" flag_fill = True for word in example.split(): if flag_fill: example_no_fill += word + " " if word.startswith('-'): example_no_fill += word + " " if not starting_index: starting_index = counter flag_fill = False counter += 1 return self.example_repl(example_no_fill, example, starting_index, continue_flag) def example_repl(self, text, example, start_index, continue_flag): """ REPL for interactive tutorials """ if start_index: start_index = start_index + 1 cmd = ' '.join(text.split()[:start_index]) example_cli = CommandLineInterface( application=self.create_application( full_layout=False), eventloop=create_eventloop()) example_cli.buffers['example_line'].reset( initial_document=Document(u'{}\n'.format( add_new_lines(example))) ) while start_index < len(text.split()): if self.default_command: cmd = cmd.replace(self.default_command + ' ', '') example_cli.buffers[DEFAULT_BUFFER].reset( initial_document=Document( u'{}'.format(cmd), cursor_position=len(cmd))) example_cli.request_redraw() answer = example_cli.run() if not answer: return "", True answer = answer.text if answer.strip('\n') == cmd.strip('\n'): continue else: if len(answer.split()) > 1: start_index += 1 cmd += " " + answer.split()[-1] + " " +\ u' '.join(text.split()[start_index:start_index + 1]) example_cli.exit() del example_cli else: cmd = text return cmd, continue_flag # pylint: disable=too-many-branches def _special_cases(self, cmd, outside): break_flag = False continue_flag = False args = parse_quotes(cmd) cmd_stripped = cmd.strip() if cmd_stripped and cmd.split(' ', 1)[0].lower() == 'az': telemetry.track_ssg('az', cmd) cmd = ' '.join(cmd.split()[1:]) if self.default_command: cmd = self.default_command + " " + cmd if cmd_stripped == "quit" or cmd_stripped == "exit": break_flag = True elif cmd_stripped == "clear-history": # clears the history, but only when you restart outside = True cmd = 'echo -n "" >' +\ os.path.join( SHELL_CONFIG_DIR(), SHELL_CONFIGURATION.get_history()) elif cmd_stripped == CLEAR_WORD: outside = True cmd = CLEAR_WORD if cmd_stripped: if cmd_stripped[0] == SELECT_SYMBOL['outside']: cmd = cmd_stripped[1:] outside = True if cmd.strip() and cmd.split()[0] == 'cd': self.handle_cd(parse_quotes(cmd)) continue_flag = True telemetry.track_ssg('outside', '') elif cmd_stripped[0] == SELECT_SYMBOL['exit_code']: meaning = "Success" if self.last_exit == 0 else "Failure" print(meaning + ": " + str(self.last_exit), file=self.output) continue_flag = True telemetry.track_ssg('exit code', '') elif SELECT_SYMBOL['query'] in cmd_stripped and self.last and self.last.result: continue_flag = self.handle_jmespath_query(args) telemetry.track_ssg('query', '') elif args[0] == '--version' or args[0] == '-v': try: continue_flag = True show_version_info_exit(self.output) except SystemExit: pass elif "|" in cmd or ">" in cmd: # anything I don't parse, send off outside = True cmd = "az " + cmd elif SELECT_SYMBOL['example'] in cmd: cmd, continue_flag = self.handle_example(cmd, continue_flag) telemetry.track_ssg('tutorial', cmd) elif len(cmd_stripped) > 2 and SELECT_SYMBOL['scope'] == cmd_stripped[0:2]: continue_flag, cmd = self.handle_scoping_input(continue_flag, cmd, cmd_stripped) return break_flag, continue_flag, outside, cmd def handle_jmespath_query(self, args): """ handles the jmespath query for injection or printing """ continue_flag = False query_symbol = SELECT_SYMBOL['query'] symbol_len = len(query_symbol) try: if len(args) == 1: # if arguments start with query_symbol, just print query result if args[0] == query_symbol: result = self.last.result elif args[0].startswith(query_symbol): result = jmespath.search(args[0][symbol_len:], self.last.result) print(json.dumps(result, sort_keys=True, indent=2), file=self.output) elif args[0].startswith(query_symbol): # print error message, user unsure of query shortcut usage print(("Usage Error: " + os.linesep + "1. Use {0} stand-alone to display previous result with optional filtering " "(Ex: {0}[jmespath query])" + os.linesep + "OR:" + os.linesep + "2. Use {0} to query the previous result for argument values " "(Ex: group show --name {0}[jmespath query])").format(query_symbol), file=self.output) else: # query, inject into cmd def jmespath_query(match): if match.group(0) == query_symbol: return str(self.last.result) query_result = jmespath.search(match.group(0)[symbol_len:], self.last.result) return str(query_result) def sub_result(arg): escaped_symbol = re.escape(query_symbol) # regex captures query symbol and all characters following it in the argument return json.dumps(re.sub(r'%s.*' % escaped_symbol, jmespath_query, arg)) cmd_base = ' '.join(map(sub_result, args)) self.cli_execute(cmd_base) continue_flag = True except (jmespath.exceptions.ParseError, CLIError) as e: print("Invalid Query Input: " + str(e), file=self.output) continue_flag = True return continue_flag def handle_scoping_input(self, continue_flag, cmd, text): """ handles what to do with a scoping gesture """ default_split = text.partition(SELECT_SYMBOL['scope'])[2].split() cmd = cmd.replace(SELECT_SYMBOL['scope'], '') continue_flag = True if not default_split: self.default_command = "" set_scope("", add=False) print('unscoping all', file=self.output) return continue_flag, cmd while default_split: if not text: value = '' else: value = default_split[0] if self.default_command: tree_val = self.default_command + " " + value else: tree_val = value if in_tree(self.completer.command_tree, tree_val.strip()): self.set_scope(value) print("defaulting: " + value, file=self.output) cmd = cmd.replace(SELECT_SYMBOL['scope'], '') telemetry.track_ssg('scope command', value) elif SELECT_SYMBOL['unscope'] == default_split[0] and \ len(self.default_command.split()) > 0: value = self.default_command.split()[-1] self.default_command = ' ' + ' '.join(self.default_command.split()[:-1]) if not self.default_command.strip(): self.default_command = self.default_command.strip() set_scope(self.default_command, add=False) print('unscoping: ' + value, file=self.output) elif SELECT_SYMBOL['unscope'] not in text: print("Scope must be a valid command", file=self.output) default_split = default_split[1:] return continue_flag, cmd def cli_execute(self, cmd): """ sends the command to the CLI to be executed """ try: args = parse_quotes(cmd) azlogging.configure_logging(args) if len(args) > 0 and args[0] == 'feedback': SHELL_CONFIGURATION.set_feedback('yes') self.user_feedback = False azure_folder = get_config_dir() if not os.path.exists(azure_folder): os.makedirs(azure_folder) ACCOUNT.load(os.path.join(azure_folder, 'azureProfile.json')) CONFIG.load(os.path.join(azure_folder, 'az.json')) SESSION.load(os.path.join(azure_folder, 'az.sess'), max_age=3600) if '--progress' in args: args.remove('--progress') execute_args = [args] thread = Thread(target=self.app.execute, args=execute_args) thread.daemon = True thread.start() self.threads.append(thread) self.curr_thread = thread progress_args = [self] thread = Thread(target=progress_view, args=progress_args) thread.daemon = True thread.start() self.threads.append(thread) result = None else: result = self.app.execute(args) self.last_exit = 0 if result and result.result is not None: from azure.cli.core._output import OutputProducer if self.output: self.output.write(result) self.output.flush() else: formatter = OutputProducer.get_formatter( self.app.configuration.output_format) OutputProducer(formatter=formatter).out(result) self.last = result except Exception as ex: # pylint: disable=broad-except self.last_exit = handle_exception(ex) except SystemExit as ex: self.last_exit = int(ex.code) def progress_patch(self, _=False): """ forces to use the Shell Progress """ from azure.cli.core.application import APPLICATION from azclishell.progress import ShellProgressView APPLICATION.progress_controller.init_progress(ShellProgressView()) return APPLICATION.progress_controller def run(self): """ starts the REPL """ telemetry.start() from azure.cli.core.application import APPLICATION APPLICATION.get_progress_controller = self.progress_patch # refresh the cache and completer self.command_table_thread = LoadCommandTableThread(restart_completer, self) self.command_table_thread.start() from azclishell.configuration import SHELL_HELP self.cli.buffers['symbols'].reset( initial_document=Document(u'{}'.format(SHELL_HELP))) while True: try: try: document = self.cli.run(reset_current_buffer=True) text = document.text if not text: # not input self.set_prompt() continue cmd = text outside = False except AttributeError: # when the user pressed Control D break else: b_flag, c_flag, outside, cmd = self._special_cases(cmd, outside) if not self.default_command: self.history.append(text) if b_flag: break if c_flag: self.set_prompt() continue self.set_prompt() if outside: subprocess.Popen(cmd, shell=True).communicate() else: self.cli_execute(cmd) # because I catch the sys exit, I have to push out cli_telemetry.conclude() except (KeyboardInterrupt, ValueError): # CTRL C self.set_prompt() continue print('Have a lovely day!!', file=self.output) telemetry.conclude()