class TerminalInteractiveShell(InteractiveShell): space_for_menu = Integer( 6, help='Number of line at the bottom of the screen ' 'to reserve for the completion menu').tag(config=True) def _space_for_menu_changed(self, old, new): self._update_layout() pt_cli = None debugger_history = None _pt_app = None simple_prompt = Bool( _use_simple_prompt, help= """Use `raw_input` for the REPL, without completion, multiline input, and prompt colors. Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: IPython own testing machinery, and emacs inferior-shell integration through elpy. This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` environment variable is set, or the current terminal is not a tty. """).tag(config=True) @property def debugger_cls(self): return Pdb if self.simple_prompt else TerminalPdb confirm_exit = Bool( True, help=""" Set to confirm when you try to exit IPython with an EOF (Control-D in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', you can force a direct exit without any confirmation.""", ).tag(config=True) editing_mode = Unicode( 'emacs', help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", ).tag(config=True) mouse_support = Bool( False, help="Enable mouse support in the prompt").tag(config=True) highlighting_style = Union( [Unicode('legacy'), Type(klass=Style)], help="""The name or class of a Pygments style to use for syntax highlighting: \n %s""" % ', '.join(get_all_styles())).tag(config=True) @observe('highlighting_style') @observe('colors') def _highlighting_style_changed(self, change): self.refresh_style() def refresh_style(self): self._style = self._make_style_from_name_or_cls( self.highlighting_style) highlighting_style_overrides = Dict( help="Override highlighting format for specific tokens").tag( config=True) true_color = Bool( False, help=("Use 24bit colors instead of 256 colors in prompt highlighting. " "If your terminal supports true color, the following command " "should print 'TRUECOLOR' in orange: " "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"")).tag( config=True) editor = Unicode( get_default_editor(), help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." ).tag(config=True) prompts_class = Type( Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag( config=True) prompts = Instance(Prompts) @default('prompts') def _prompts_default(self): return self.prompts_class(self) @observe('prompts') def _(self, change): self._update_layout() @default('displayhook_class') def _displayhook_class_default(self): return RichPromptDisplayHook term_title = Bool( True, help="Automatically set the terminal title").tag(config=True) display_completions = Enum( ('column', 'multicolumn', 'readlinelike'), help= ("Options for displaying tab completions, 'column', 'multicolumn', and " "'readlinelike'. These options are for `prompt_toolkit`, see " "`prompt_toolkit` documentation for more information."), default_value='multicolumn').tag(config=True) highlight_matching_brackets = Bool( True, help="Highlight matching brackets .", ).tag(config=True) @observe('term_title') def init_term_title(self, change=None): # Enable or disable the terminal title. if self.term_title: toggle_set_term_title(True) set_term_title('IPython: ' + abbrev_cwd()) else: toggle_set_term_title(False) def init_display_formatter(self): super(TerminalInteractiveShell, self).init_display_formatter() # terminal only supports plain text self.display_formatter.active_types = ['text/plain'] def init_prompt_toolkit_cli(self): if self.simple_prompt: # Fall back to plain non-interactive output for tests. # This is very limited, and only accepts a single line. def prompt(): return cast_unicode_py2( input('In [%d]: ' % self.execution_count)) self.prompt_for_code = prompt return # Set up keyboard shortcuts kbmanager = KeyBindingManager.for_prompt() register_ipython_shortcuts(kbmanager.registry, self) # Pre-populate history from IPython's history database history = InMemoryHistory() last_cell = u"" for __, ___, cell in self.history_manager.get_tail( self.history_load_length, include_latest=True): # Ignore blank lines and consecutive duplicates cell = cell.rstrip() if cell and (cell != last_cell): history.append(cell) last_cell = cell self._style = self._make_style_from_name_or_cls( self.highlighting_style) style = DynamicStyle(lambda: self._style) editing_mode = getattr(EditingMode, self.editing_mode.upper()) self._pt_app = create_prompt_application( editing_mode=editing_mode, key_bindings_registry=kbmanager.registry, history=history, completer=IPythonPTCompleter(shell=self), enable_history_search=True, style=style, mouse_support=self.mouse_support, **self._layout_options()) self._eventloop = create_eventloop(self.inputhook) self.pt_cli = CommandLineInterface( self._pt_app, eventloop=self._eventloop, output=create_output(true_color=self.true_color)) def _make_style_from_name_or_cls(self, name_or_cls): """ Small wrapper that make an IPython compatible style from a style name We need that to add style for prompt ... etc. """ style_overrides = {} if name_or_cls == 'legacy': legacy = self.colors.lower() if legacy == 'linux': style_cls = get_style_by_name('monokai') style_overrides = _style_overrides_linux elif legacy == 'lightbg': style_overrides = _style_overrides_light_bg style_cls = get_style_by_name('pastie') elif legacy == 'neutral': # The default theme needs to be visible on both a dark background # and a light background, because we can't tell what the terminal # looks like. These tweaks to the default theme help with that. style_cls = get_style_by_name('default') style_overrides.update({ Token.Number: '#007700', Token.Operator: 'noinherit', Token.String: '#BB6622', Token.Name.Function: '#2080D0', Token.Name.Class: 'bold #2080D0', Token.Name.Namespace: 'bold #2080D0', Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#990000', Token.OutPromptNum: '#ff0000 bold', }) elif legacy == 'nocolor': style_cls = _NoStyle style_overrides = {} else: raise ValueError('Got unknown colors: ', legacy) else: if isinstance(name_or_cls, string_types): style_cls = get_style_by_name(name_or_cls) else: style_cls = name_or_cls style_overrides = { Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#990000', Token.OutPromptNum: '#ff0000 bold', } style_overrides.update(self.highlighting_style_overrides) style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls, style_dict=style_overrides) return style def _layout_options(self): """ Return the current layout option for the current Terminal InteractiveShell """ return { 'lexer': IPythonPTLexer(), 'reserve_space_for_menu': self.space_for_menu, 'get_prompt_tokens': self.prompts.in_prompt_tokens, 'get_continuation_tokens': self.prompts.continuation_prompt_tokens, 'multiline': True, 'display_completions_in_columns': (self.display_completions == 'multicolumn'), # Highlight matching brackets, but only when this setting is # enabled, and only when the DEFAULT_BUFFER has the focus. 'extra_input_processors': [ ConditionalProcessor( processor=HighlightMatchingBracketProcessor( chars='[](){}'), filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & Condition(lambda cli: self.highlight_matching_brackets)) ], } def _update_layout(self): """ Ask for a re computation of the application layout, if for example , some configuration options have changed. """ if self._pt_app: self._pt_app.layout = create_prompt_layout( **self._layout_options()) def prompt_for_code(self): document = self.pt_cli.run(pre_run=self.pre_prompt, reset_current_buffer=True) return document.text def enable_win_unicode_console(self): if sys.version_info >= (3, 6): # Since PEP 528, Python uses the unicode APIs for the Windows # console by default, so WUC shouldn't be needed. return import win_unicode_console if PY3: win_unicode_console.enable() else: # https://github.com/ipython/ipython/issues/9768 from win_unicode_console.streams import (TextStreamWrapper, stdout_text_transcoded, stderr_text_transcoded) class LenientStrStreamWrapper(TextStreamWrapper): def write(self, s): if isinstance(s, bytes): s = s.decode(self.encoding, 'replace') self.base.write(s) stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded) stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded) win_unicode_console.enable(stdout=stdout_text_str, stderr=stderr_text_str) def init_io(self): if sys.platform not in {'win32', 'cli'}: return self.enable_win_unicode_console() import colorama colorama.init() # For some reason we make these wrappers around stdout/stderr. # For now, we need to reset them so all output gets coloured. # https://github.com/ipython/ipython/issues/8669 # io.std* are deprecated, but don't show our own deprecation warnings # during initialization of the deprecated API. with warnings.catch_warnings(): warnings.simplefilter('ignore', DeprecationWarning) io.stdout = io.IOStream(sys.stdout) io.stderr = io.IOStream(sys.stderr) def init_magics(self): super(TerminalInteractiveShell, self).init_magics() self.register_magics(TerminalMagics) def init_alias(self): # The parent class defines aliases that can be safely used with any # frontend. super(TerminalInteractiveShell, self).init_alias() # Now define aliases that only make sense on the terminal, because they # need direct access to the console in a way that we can't emulate in # GUI or web frontend if os.name == 'posix': for cmd in ['clear', 'more', 'less', 'man']: self.alias_manager.soft_define_alias(cmd, cmd) def __init__(self, *args, **kwargs): super(TerminalInteractiveShell, self).__init__(*args, **kwargs) self.init_prompt_toolkit_cli() self.init_term_title() self.keep_running = True self.debugger_history = InMemoryHistory() def ask_exit(self): self.keep_running = False rl_next_input = None def pre_prompt(self): if self.rl_next_input: self.pt_cli.application.buffer.text = cast_unicode_py2( self.rl_next_input) self.rl_next_input = None def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): if display_banner is not DISPLAY_BANNER_DEPRECATED: warn( 'interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) self.keep_running = True while self.keep_running: print(self.separate_in, end='') try: code = self.prompt_for_code() except EOFError: if (not self.confirm_exit) \ or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): self.ask_exit() else: if code: self.run_cell(code, store_history=True) def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED): # An extra layer of protection in case someone mashing Ctrl-C breaks # out of our internal code. if display_banner is not DISPLAY_BANNER_DEPRECATED: warn( 'mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) while True: try: self.interact() break except KeyboardInterrupt: print("\nKeyboardInterrupt escaped interact()\n") _inputhook = None def inputhook(self, context): if self._inputhook is not None: self._inputhook(context) active_eventloop = None def enable_gui(self, gui=None): if gui: self.active_eventloop, self._inputhook =\ get_inputhook_name_and_func(gui) else: self.active_eventloop = self._inputhook = None # Run !system commands directly, not through pipes, so terminal programs # work correctly. system = InteractiveShell.system_raw def auto_rewrite_input(self, cmd): """Overridden from the parent class to use fancy rewriting prompt""" if not self.show_rewritten_input: return tokens = self.prompts.rewrite_prompt_tokens() if self.pt_cli: self.pt_cli.print_tokens(tokens) print(cmd) else: prompt = ''.join(s for t, s in tokens) print(prompt, cmd, sep='') _prompts_before = None def switch_doctest_mode(self, mode): """Switch prompts to classic for %doctest_mode""" if mode: self._prompts_before = self.prompts self.prompts = ClassicPrompts(self) elif self._prompts_before: self.prompts = self._prompts_before self._prompts_before = None self._update_layout()
class TerminalInteractiveShell(InteractiveShell): space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' 'to reserve for the completion menu' ).tag(config=True) def _space_for_menu_changed(self, old, new): self._update_layout() pt_cli = None debugger_history = None _pt_app = None simple_prompt = Bool(_use_simple_prompt, help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors. Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: IPython own testing machinery, and emacs inferior-shell integration through elpy. This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` environment variable is set, or the current terminal is not a tty. """ ).tag(config=True) @property def debugger_cls(self): return Pdb if self.simple_prompt else TerminalPdb confirm_exit = Bool(True, help=""" Set to confirm when you try to exit IPython with an EOF (Control-D in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', you can force a direct exit without any confirmation.""", ).tag(config=True) editing_mode = Unicode('emacs', help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", ).tag(config=True) mouse_support = Bool(False, help="Enable mouse support in the prompt" ).tag(config=True) highlighting_style = Union([Unicode('legacy'), Type(klass=Style)], help="""The name or class of a Pygments style to use for syntax highlighting: \n %s""" % ', '.join(get_all_styles()) ).tag(config=True) @observe('highlighting_style') @observe('colors') def _highlighting_style_changed(self, change): self.refresh_style() def refresh_style(self): self._style = self._make_style_from_name_or_cls(self.highlighting_style) highlighting_style_overrides = Dict( help="Override highlighting format for specific tokens" ).tag(config=True) true_color = Bool(False, help=("Use 24bit colors instead of 256 colors in prompt highlighting. " "If your terminal supports true color, the following command " "should print 'TRUECOLOR' in orange: " "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"") ).tag(config=True) editor = Unicode(get_default_editor(), help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." ).tag(config=True) prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) prompts = Instance(Prompts) @default('prompts') def _prompts_default(self): return self.prompts_class(self) @observe('prompts') def _(self, change): self._update_layout() @default('displayhook_class') def _displayhook_class_default(self): return RichPromptDisplayHook term_title = Bool(True, help="Automatically set the terminal title" ).tag(config=True) display_completions = Enum(('column', 'multicolumn','readlinelike'), help= ( "Options for displaying tab completions, 'column', 'multicolumn', and " "'readlinelike'. These options are for `prompt_toolkit`, see " "`prompt_toolkit` documentation for more information." ), default_value='multicolumn').tag(config=True) highlight_matching_brackets = Bool(True, help="Highlight matching brackets .", ).tag(config=True) @observe('term_title') def init_term_title(self, change=None): # Enable or disable the terminal title. if self.term_title: toggle_set_term_title(True) set_term_title('IPython: ' + abbrev_cwd()) else: toggle_set_term_title(False) def init_display_formatter(self): super(TerminalInteractiveShell, self).init_display_formatter() # terminal only supports plain text self.display_formatter.active_types = ['text/plain'] def init_prompt_toolkit_cli(self): if self.simple_prompt: # Fall back to plain non-interactive output for tests. # This is very limited, and only accepts a single line. def prompt(): return cast_unicode_py2(input('In [%d]: ' % self.execution_count)) self.prompt_for_code = prompt return # Set up keyboard shortcuts kbmanager = KeyBindingManager.for_prompt() register_ipython_shortcuts(kbmanager.registry, self) # Pre-populate history from IPython's history database history = InMemoryHistory() last_cell = u"" for __, ___, cell in self.history_manager.get_tail(self.history_load_length, include_latest=True): # Ignore blank lines and consecutive duplicates cell = cell.rstrip() if cell and (cell != last_cell): history.append(cell) last_cell = cell self._style = self._make_style_from_name_or_cls(self.highlighting_style) style = DynamicStyle(lambda: self._style) editing_mode = getattr(EditingMode, self.editing_mode.upper()) self._pt_app = create_prompt_application( editing_mode=editing_mode, key_bindings_registry=kbmanager.registry, history=history, completer=IPythonPTCompleter(shell=self), enable_history_search=True, style=style, mouse_support=self.mouse_support, **self._layout_options() ) self._eventloop = create_eventloop(self.inputhook) self.pt_cli = CommandLineInterface( self._pt_app, eventloop=self._eventloop, output=create_output(true_color=self.true_color)) def _make_style_from_name_or_cls(self, name_or_cls): """ Small wrapper that make an IPython compatible style from a style name We need that to add style for prompt ... etc. """ style_overrides = {} if name_or_cls == 'legacy': legacy = self.colors.lower() if legacy == 'linux': style_cls = get_style_by_name('monokai') style_overrides = _style_overrides_linux elif legacy == 'lightbg': style_overrides = _style_overrides_light_bg style_cls = get_style_by_name('pastie') elif legacy == 'neutral': # The default theme needs to be visible on both a dark background # and a light background, because we can't tell what the terminal # looks like. These tweaks to the default theme help with that. style_cls = get_style_by_name('default') style_overrides.update({ Token.Number: '#007700', Token.Operator: 'noinherit', Token.String: '#BB6622', Token.Name.Function: '#2080D0', Token.Name.Class: 'bold #2080D0', Token.Name.Namespace: 'bold #2080D0', Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#990000', Token.OutPromptNum: '#ff0000 bold', }) elif legacy =='nocolor': style_cls=_NoStyle style_overrides = {} else : raise ValueError('Got unknown colors: ', legacy) else : if isinstance(name_or_cls, string_types): style_cls = get_style_by_name(name_or_cls) else: style_cls = name_or_cls style_overrides = { Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#990000', Token.OutPromptNum: '#ff0000 bold', } style_overrides.update(self.highlighting_style_overrides) style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls, style_dict=style_overrides) return style def _layout_options(self): """ Return the current layout option for the current Terminal InteractiveShell """ return { 'lexer':IPythonPTLexer(), 'reserve_space_for_menu':self.space_for_menu, 'get_prompt_tokens':self.prompts.in_prompt_tokens, 'get_continuation_tokens':self.prompts.continuation_prompt_tokens, 'multiline':True, 'display_completions_in_columns': (self.display_completions == 'multicolumn'), # Highlight matching brackets, but only when this setting is # enabled, and only when the DEFAULT_BUFFER has the focus. 'extra_input_processors': [ConditionalProcessor( processor=HighlightMatchingBracketProcessor(chars='[](){}'), filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & Condition(lambda cli: self.highlight_matching_brackets))], } def _update_layout(self): """ Ask for a re computation of the application layout, if for example , some configuration options have changed. """ if self._pt_app: self._pt_app.layout = create_prompt_layout(**self._layout_options()) def prompt_for_code(self): document = self.pt_cli.run( pre_run=self.pre_prompt, reset_current_buffer=True) return document.text def enable_win_unicode_console(self): if sys.version_info >= (3, 6): # Since PEP 528, Python uses the unicode APIs for the Windows # console by default, so WUC shouldn't be needed. return import win_unicode_console if PY3: win_unicode_console.enable() else: # https://github.com/ipython/ipython/issues/9768 from win_unicode_console.streams import (TextStreamWrapper, stdout_text_transcoded, stderr_text_transcoded) class LenientStrStreamWrapper(TextStreamWrapper): def write(self, s): if isinstance(s, bytes): s = s.decode(self.encoding, 'replace') self.base.write(s) stdout_text_str = LenientStrStreamWrapper(stdout_text_transcoded) stderr_text_str = LenientStrStreamWrapper(stderr_text_transcoded) win_unicode_console.enable(stdout=stdout_text_str, stderr=stderr_text_str) def init_io(self): if sys.platform not in {'win32', 'cli'}: return self.enable_win_unicode_console() import colorama colorama.init() # For some reason we make these wrappers around stdout/stderr. # For now, we need to reset them so all output gets coloured. # https://github.com/ipython/ipython/issues/8669 # io.std* are deprecated, but don't show our own deprecation warnings # during initialization of the deprecated API. with warnings.catch_warnings(): warnings.simplefilter('ignore', DeprecationWarning) io.stdout = io.IOStream(sys.stdout) io.stderr = io.IOStream(sys.stderr) def init_magics(self): super(TerminalInteractiveShell, self).init_magics() self.register_magics(TerminalMagics) def init_alias(self): # The parent class defines aliases that can be safely used with any # frontend. super(TerminalInteractiveShell, self).init_alias() # Now define aliases that only make sense on the terminal, because they # need direct access to the console in a way that we can't emulate in # GUI or web frontend if os.name == 'posix': for cmd in ['clear', 'more', 'less', 'man']: self.alias_manager.soft_define_alias(cmd, cmd) def __init__(self, *args, **kwargs): super(TerminalInteractiveShell, self).__init__(*args, **kwargs) self.init_prompt_toolkit_cli() self.init_term_title() self.keep_running = True self.debugger_history = InMemoryHistory() def ask_exit(self): self.keep_running = False rl_next_input = None def pre_prompt(self): if self.rl_next_input: self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input) self.rl_next_input = None def interact(self, display_banner=DISPLAY_BANNER_DEPRECATED): if display_banner is not DISPLAY_BANNER_DEPRECATED: warn('interact `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) self.keep_running = True while self.keep_running: print(self.separate_in, end='') try: code = self.prompt_for_code() except EOFError: if (not self.confirm_exit) \ or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): self.ask_exit() else: if code: self.run_cell(code, store_history=True) def mainloop(self, display_banner=DISPLAY_BANNER_DEPRECATED): # An extra layer of protection in case someone mashing Ctrl-C breaks # out of our internal code. if display_banner is not DISPLAY_BANNER_DEPRECATED: warn('mainloop `display_banner` argument is deprecated since IPython 5.0. Call `show_banner()` if needed.', DeprecationWarning, stacklevel=2) while True: try: self.interact() break except KeyboardInterrupt: print("\nKeyboardInterrupt escaped interact()\n") _inputhook = None def inputhook(self, context): if self._inputhook is not None: self._inputhook(context) def enable_gui(self, gui=None): if gui: self._inputhook = get_inputhook_func(gui) else: self._inputhook = None # Run !system commands directly, not through pipes, so terminal programs # work correctly. system = InteractiveShell.system_raw def auto_rewrite_input(self, cmd): """Overridden from the parent class to use fancy rewriting prompt""" if not self.show_rewritten_input: return tokens = self.prompts.rewrite_prompt_tokens() if self.pt_cli: self.pt_cli.print_tokens(tokens) print(cmd) else: prompt = ''.join(s for t, s in tokens) print(prompt, cmd, sep='') _prompts_before = None def switch_doctest_mode(self, mode): """Switch prompts to classic for %doctest_mode""" if mode: self._prompts_before = self.prompts self.prompts = ClassicPrompts(self) elif self._prompts_before: self.prompts = self._prompts_before self._prompts_before = None self._update_layout()
class TerminalInteractiveShell(InteractiveShell): colors_force = True space_for_menu = Integer(6, help='Number of line at the bottom of the screen ' 'to reserve for the completion menu' ).tag(config=True) def _space_for_menu_changed(self, old, new): self._update_layout() pt_cli = None debugger_history = None simple_prompt = Bool(_use_simple_prompt, help="""Use `raw_input` for the REPL, without completion, multiline input, and prompt colors. Useful when controlling IPython as a subprocess, and piping STDIN/OUT/ERR. Known usage are: IPython own testing machinery, and emacs inferior-shell integration through elpy. This mode default to `True` if the `IPY_TEST_SIMPLE_PROMPT` environment variable is set, or the current terminal is not a tty. """ ).tag(config=True) @property def debugger_cls(self): return Pdb if self.simple_prompt else TerminalPdb autoedit_syntax = Bool(False, help="auto editing of files with syntax errors.", ).tag(config=True) confirm_exit = Bool(True, help=""" Set to confirm when you try to exit IPython with an EOF (Control-D in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit', you can force a direct exit without any confirmation.""", ).tag(config=True) editing_mode = Unicode('emacs', help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", ).tag(config=True) mouse_support = Bool(False, help="Enable mouse support in the prompt" ).tag(config=True) highlighting_style = Unicode('default', help="The name of a Pygments style to use for syntax highlighting: \n %s" % ', '.join(get_all_styles()) ).tag(config=True) @observe('highlighting_style') def _highlighting_style_changed(self, change): self._style = self._make_style_from_name(self.highlighting_style) highlighting_style_overrides = Dict( help="Override highlighting format for specific tokens" ).tag(config=True) editor = Unicode(get_default_editor(), help="Set the editor used by IPython (default to $EDITOR/vi/notepad)." ).tag(config=True) prompts_class = Type(Prompts, help='Class used to generate Prompt token for prompt_toolkit').tag(config=True) prompts = Instance(Prompts) @default('prompts') def _prompts_default(self): return self.prompts_class(self) @observe('prompts') def _(self, change): self._update_layout() @default('displayhook_class') def _displayhook_class_default(self): return RichPromptDisplayHook term_title = Bool(True, help="Automatically set the terminal title" ).tag(config=True) display_completions_in_columns = Bool(False, help="Display a multi column completion menu.", ).tag(config=True) highlight_matching_brackets = Bool(True, help="Highlight matching brackets .", ).tag(config=True) @observe('term_title') def init_term_title(self, change=None): # Enable or disable the terminal title. if self.term_title: toggle_set_term_title(True) set_term_title('IPython: ' + abbrev_cwd()) else: toggle_set_term_title(False) def init_prompt_toolkit_cli(self): self._app = None if self.simple_prompt: # Fall back to plain non-interactive output for tests. # This is very limited, and only accepts a single line. def prompt(): return cast_unicode_py2(input('In [%d]: ' % self.execution_count)) self.prompt_for_code = prompt return kbmanager = KeyBindingManager.for_prompt() insert_mode = ViInsertMode() | EmacsInsertMode() # Ctrl+J == Enter, seemingly @kbmanager.registry.add_binding(Keys.ControlJ, filter=(HasFocus(DEFAULT_BUFFER) & ~HasSelection() & insert_mode )) def _(event): b = event.current_buffer d = b.document if b.complete_state: cc = b.complete_state.current_completion if cc: b.apply_completion(cc) else: b.cancel_completion() return if not (d.on_last_line or d.cursor_position_row >= d.line_count - d.empty_line_count_at_the_end()): b.newline() return status, indent = self.input_splitter.check_complete(d.text) if (status != 'incomplete') and b.accept_action.is_returnable: b.accept_action.validate_and_handle(event.cli, b) else: b.insert_text('\n' + (' ' * (indent or 0))) @kbmanager.registry.add_binding(Keys.ControlP, filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER))) def _previous_history_or_previous_completion(event): """ Control-P in vi edit mode on readline is history next, unlike default prompt toolkit. If completer is open this still select previous completion. """ event.current_buffer.auto_up() @kbmanager.registry.add_binding(Keys.ControlN, filter=(ViInsertMode() & HasFocus(DEFAULT_BUFFER))) def _next_history_or_next_completion(event): """ Control-N in vi edit mode on readline is history previous, unlike default prompt toolkit. If completer is open this still select next completion. """ event.current_buffer.auto_down() @kbmanager.registry.add_binding(Keys.ControlG, filter=( HasFocus(DEFAULT_BUFFER) & HasCompletions() )) def _dismiss_completion(event): b = event.current_buffer if b.complete_state: b.cancel_completion() @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(DEFAULT_BUFFER)) def _reset_buffer(event): b = event.current_buffer if b.complete_state: b.cancel_completion() else: b.reset() @kbmanager.registry.add_binding(Keys.ControlC, filter=HasFocus(SEARCH_BUFFER)) def _reset_search_buffer(event): if event.current_buffer.document.text: event.current_buffer.reset() else: event.cli.push_focus(DEFAULT_BUFFER) supports_suspend = Condition(lambda cli: hasattr(signal, 'SIGTSTP')) @kbmanager.registry.add_binding(Keys.ControlZ, filter=supports_suspend) def _suspend_to_bg(event): event.cli.suspend_to_background() @Condition def cursor_in_leading_ws(cli): before = cli.application.buffer.document.current_line_before_cursor return (not before) or before.isspace() # Ctrl+I == Tab @kbmanager.registry.add_binding(Keys.ControlI, filter=(HasFocus(DEFAULT_BUFFER) & ~HasSelection() & insert_mode & cursor_in_leading_ws )) def _indent_buffer(event): event.current_buffer.insert_text(' ' * 4) # Pre-populate history from IPython's history database history = InMemoryHistory() last_cell = u"" for __, ___, cell in self.history_manager.get_tail(self.history_load_length, include_latest=True): # Ignore blank lines and consecutive duplicates cell = cell.rstrip() if cell and (cell != last_cell): history.append(cell) self._style = self._make_style_from_name(self.highlighting_style) style = DynamicStyle(lambda: self._style) editing_mode = getattr(EditingMode, self.editing_mode.upper()) self._app = create_prompt_application( editing_mode=editing_mode, key_bindings_registry=kbmanager.registry, history=history, completer=IPythonPTCompleter(self.Completer), enable_history_search=True, style=style, mouse_support=self.mouse_support, **self._layout_options() ) self._eventloop = create_eventloop(self.inputhook) self.pt_cli = CommandLineInterface(self._app, eventloop=self._eventloop) def _make_style_from_name(self, name): """ Small wrapper that make an IPython compatible style from a style name We need that to add style for prompt ... etc. """ style_cls = get_style_by_name(name) style_overrides = { Token.Prompt: '#009900', Token.PromptNum: '#00ff00 bold', Token.OutPrompt: '#990000', Token.OutPromptNum: '#ff0000 bold', } if name == 'default': style_cls = get_style_by_name('default') # The default theme needs to be visible on both a dark background # and a light background, because we can't tell what the terminal # looks like. These tweaks to the default theme help with that. style_overrides.update({ Token.Number: '#007700', Token.Operator: 'noinherit', Token.String: '#BB6622', Token.Name.Function: '#2080D0', Token.Name.Class: 'bold #2080D0', Token.Name.Namespace: 'bold #2080D0', }) style_overrides.update(self.highlighting_style_overrides) style = PygmentsStyle.from_defaults(pygments_style_cls=style_cls, style_dict=style_overrides) return style def _layout_options(self): """ Return the current layout option for the current Terminal InteractiveShell """ return { 'lexer':IPythonPTLexer(), 'reserve_space_for_menu':self.space_for_menu, 'get_prompt_tokens':self.prompts.in_prompt_tokens, 'get_continuation_tokens':self.prompts.continuation_prompt_tokens, 'multiline':True, 'display_completions_in_columns': self.display_completions_in_columns, # Highlight matching brackets, but only when this setting is # enabled, and only when the DEFAULT_BUFFER has the focus. 'extra_input_processors': [ConditionalProcessor( processor=HighlightMatchingBracketProcessor(chars='[](){}'), filter=HasFocus(DEFAULT_BUFFER) & ~IsDone() & Condition(lambda cli: self.highlight_matching_brackets))], } def _update_layout(self): """ Ask for a re computation of the application layout, if for example , some configuration options have changed. """ if getattr(self, '._app', None): self._app.layout = create_prompt_layout(**self._layout_options()) def prompt_for_code(self): document = self.pt_cli.run( pre_run=self.pre_prompt, reset_current_buffer=True) return document.text def init_io(self): if sys.platform not in {'win32', 'cli'}: return import colorama colorama.init() # For some reason we make these wrappers around stdout/stderr. # For now, we need to reset them so all output gets coloured. # https://github.com/ipython/ipython/issues/8669 from IPython.utils import io io.stdout = io.IOStream(sys.stdout) io.stderr = io.IOStream(sys.stderr) def init_magics(self): super(TerminalInteractiveShell, self).init_magics() self.register_magics(TerminalMagics) def init_alias(self): # The parent class defines aliases that can be safely used with any # frontend. super(TerminalInteractiveShell, self).init_alias() # Now define aliases that only make sense on the terminal, because they # need direct access to the console in a way that we can't emulate in # GUI or web frontend if os.name == 'posix': for cmd in ['clear', 'more', 'less', 'man']: self.alias_manager.soft_define_alias(cmd, cmd) def __init__(self, *args, **kwargs): super(TerminalInteractiveShell, self).__init__(*args, **kwargs) self.init_prompt_toolkit_cli() self.init_term_title() self.keep_running = True self.debugger_history = InMemoryHistory() def ask_exit(self): self.keep_running = False rl_next_input = None def pre_prompt(self): if self.rl_next_input: self.pt_cli.application.buffer.text = cast_unicode_py2(self.rl_next_input) self.rl_next_input = None def interact(self): while self.keep_running: print(self.separate_in, end='') try: code = self.prompt_for_code() except EOFError: if (not self.confirm_exit) \ or self.ask_yes_no('Do you really want to exit ([y]/n)?','y','n'): self.ask_exit() else: if code: self.run_cell(code, store_history=True) if self.autoedit_syntax and self.SyntaxTB.last_syntax_error: self.edit_syntax_error() def mainloop(self): # An extra layer of protection in case someone mashing Ctrl-C breaks # out of our internal code. while True: try: self.interact() break except KeyboardInterrupt: print("\nKeyboardInterrupt escaped interact()\n") if hasattr(self, '_eventloop'): self._eventloop.close() _inputhook = None def inputhook(self, context): if self._inputhook is not None: self._inputhook(context) def enable_gui(self, gui=None): if gui: self._inputhook = get_inputhook_func(gui) else: self._inputhook = None # Methods to support auto-editing of SyntaxErrors: def edit_syntax_error(self): """The bottom half of the syntax error handler called in the main loop. Loop until syntax error is fixed or user cancels. """ while self.SyntaxTB.last_syntax_error: # copy and clear last_syntax_error err = self.SyntaxTB.clear_err_state() if not self._should_recompile(err): return try: # may set last_syntax_error again if a SyntaxError is raised self.safe_execfile(err.filename, self.user_ns) except: self.showtraceback() else: try: with open(err.filename) as f: # This should be inside a display_trap block and I # think it is. sys.displayhook(f.read()) except: self.showtraceback() def _should_recompile(self, e): """Utility routine for edit_syntax_error""" if e.filename in ('<ipython console>', '<input>', '<string>', '<console>', '<BackgroundJob compilation>', None): return False try: if (self.autoedit_syntax and not self.ask_yes_no( 'Return to editor to correct syntax error? ' '[Y/n] ', 'y')): return False except EOFError: return False def int0(x): try: return int(x) except TypeError: return 0 # always pass integer line and offset values to editor hook try: self.hooks.fix_error_editor(e.filename, int0(e.lineno), int0(e.offset), e.msg) except TryNext: warn('Could not open editor') return False return True # Run !system commands directly, not through pipes, so terminal programs # work correctly. system = InteractiveShell.system_raw def auto_rewrite_input(self, cmd): """Overridden from the parent class to use fancy rewriting prompt""" if not self.show_rewritten_input: return tokens = self.prompts.rewrite_prompt_tokens() if self.pt_cli: self.pt_cli.print_tokens(tokens) print(cmd) else: prompt = ''.join(s for t, s in tokens) print(prompt, cmd, sep='') _prompts_before = None def switch_doctest_mode(self, mode): """Switch prompts to classic for %doctest_mode""" if mode: self._prompts_before = self.prompts self.prompts = ClassicPrompts(self) elif self._prompts_before: self.prompts = self._prompts_before self._prompts_before = None
class Cli: isElectionStarted = False primariesSelected = 0 electedPrimaries = set() name = 'plenum' properName = 'Plenum' fullName = 'Plenum protocol' NodeClass = Node ClientClass = Client # noinspection PyPep8 def __init__(self, looper, basedirpath, nodeReg, cliNodeReg, output=None, debug=False, logFileName=None): self.curClientPort = None logging.root.addHandler(CliHandler(self.out)) self.looper = looper self.basedirpath = basedirpath self.nodeReg = nodeReg self.cliNodeReg = cliNodeReg # Used to store created clients self.clients = {} # clientName -> Client # To store the created requests self.requests = {} # To store the nodes created self.nodes = {} self.externalClientKeys = {} # type: Dict[str,str] self.cliCmds = {'status', 'new'} self.nodeCmds = self.cliCmds | {'keyshare'} self.helpablesCommands = self.cliCmds | self.nodeCmds self.simpleCmds = {'status', 'exit', 'quit', 'license'} self.commands = {'list', 'help'} | self.simpleCmds self.cliActions = {'send', 'show'} self.commands.update(self.cliCmds) self.commands.update(self.nodeCmds) self.node_or_cli = ['node', 'client'] self.nodeNames = list(self.nodeReg.keys()) + ["all"] self.debug = debug self.plugins = {} ''' examples: status new node Alpha new node all new client Joe client Joe send <msg> client Joe show 1 ''' psep = re.escape(os.path.sep) self.utilGrams = [ "(\s* (?P<simple>{}) \s*) |".format(self.relist(self.simpleCmds)), "(\s* (?P<load>load) \s+ (?P<file_name>[.a-zA-z0-9{}]+) \s*) |".format(psep), "(\s* (?P<command>help) (\s+ (?P<helpable>[a-zA-Z0-9]+) )? (\s+ (?P<node_or_cli>{}) )?\s*) |".format(self.relist(self.node_or_cli)), "(\s* (?P<command>list) \s*)" ] self.nodeGrams = [ "(\s* (?P<node_command>{}) \s+ (?P<node_or_cli>nodes?) \s+ (?P<node_name>[a-zA-Z0-9]+)\s*) |".format(self.relist(self.nodeCmds)), "(\s* (?P<load_plugins>load\s+plugins\s+from) \s+ (?P<plugin_dir>[a-zA-Z0-9-:{}]+) \s*)".format(psep), ] self.clientGrams = [ "(\s* (?P<client_command>{}) \s+ (?P<node_or_cli>clients?) \s+ (?P<client_name>[a-zA-Z0-9]+) \s*) |".format(self.relist(self.cliCmds)), "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>send) \s+ (?P<msg>\{\s*.*\}) \s*) |", "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>show) \s+ (?P<req_id>[0-9]+) \s*) |", "(\s* (?P<add_key>add\s+key) \s+ (?P<verkey>[a-fA-F0-9]+) \s+ (?P<for_client>for\s+client) \s+ (?P<identifier>[a-zA-Z0-9]+) \s*)", ] self.lexers = { 'node_command': SimpleLexer(Token.Keyword), 'command': SimpleLexer(Token.Keyword), 'helpable': SimpleLexer(Token.Keyword), 'load_plugins': SimpleLexer(Token.Keyword), 'load': SimpleLexer(Token.Keyword), 'node_or_cli': SimpleLexer(Token.Keyword), 'arg1': SimpleLexer(Token.Name), 'node_name': SimpleLexer(Token.Name), 'more_nodes': SimpleLexer(Token.Name), 'simple': SimpleLexer(Token.Keyword), 'client_command': SimpleLexer(Token.Keyword), 'add_key': SimpleLexer(Token.Keyword), 'verkey': SimpleLexer(Token.Literal), 'for_client': SimpleLexer(Token.Keyword), 'identifier': SimpleLexer(Token.Name), } self.clientWC = WordCompleter([]) self.completers = { 'node_command': WordCompleter(self.nodeCmds), 'client_command': WordCompleter(self.cliCmds), 'client': WordCompleter(['client']), 'command': WordCompleter(self.commands), 'node_or_cli': WordCompleter(self.node_or_cli), 'node_name': WordCompleter(self.nodeNames), 'more_nodes': WordCompleter(self.nodeNames), 'helpable': WordCompleter(self.helpablesCommands), 'load_plugins': WordCompleter(['load plugins from']), 'client_name': self.clientWC, 'cli_action': WordCompleter(self.cliActions), 'simple': WordCompleter(self.simpleCmds), 'add_key': WordCompleter(['add key']), 'for_client': WordCompleter(['for client']), } self.initializeGrammar() self.initializeGrammarLexer() self.initializeGrammarCompleter() self.style = PygmentsStyle.from_defaults({ Token.Operator: '#33aa33 bold', Token.Number: '#aa3333 bold', Token.Name: '#ffff00 bold', Token.Heading: 'bold', Token.TrailingInput: 'bg:#662222 #ffffff', Token.BoldGreen: '#33aa33 bold', Token.BoldOrange: '#ff4f2f bold', Token.BoldBlue: '#095cab bold'}) self.functionMappings = self.createFunctionMappings() self.voidMsg = "<none>" # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop(looper.loop) pers_hist = FileHistory('.{}-cli-history'.format(self.name)) # Create interface. app = create_prompt_application('{}> '.format(self.name), lexer=self.grammarLexer, completer=self.grammarCompleter, style=self.style, history=pers_hist) if output: out = output else: if is_windows(): if is_conemu_ansi(): out = ConEmuOutput(sys.__stdout__) else: out = Win32Output(sys.__stdout__) else: out = CustomOutput.from_pty(sys.__stdout__, true_color=True) self.cli = CommandLineInterface( application=app, eventloop=eventloop, output=out) # Patch stdout in something that will always print *above* the prompt # when something is written to stdout. sys.stdout = self.cli.stdout_proxy() setupLogging(TRACE_LOG_LEVEL, Console.Wordage.mute, filename=logFileName) self.logger = getlogger("cli") self.print("\n{}-CLI (c) 2016 Evernym, Inc.".format(self.properName)) self.print("Node registry loaded.") self.print("None of these are created or running yet.") self.showNodeRegistry() self.print("Type 'help' for more information.") @staticmethod def relist(seq): return '(' + '|'.join(seq) + ')' def initializeGrammar(self): self.utilGrams[-1] += " |" self.nodeGrams[-1] += " |" self.grams = self.utilGrams + self.nodeGrams + self.clientGrams self.grammar = compile("".join(self.grams)) def initializeGrammarLexer(self): self.grammarLexer = GrammarLexer(self.grammar, lexers=self.lexers) def initializeGrammarCompleter(self): self.grammarCompleter = GrammarCompleter(self.grammar, self.completers) def createFunctionMappings(self): def newHelper(): self.print("""Is used to create a new node or a client. Usage: new <node/client> <nodeName/clientName>""") def statusHelper(): self.print("status command helper") def nodeHelper(): self.print("It is used to create a new node") def clientHelper(): self.print("It is used to create a new client") def statusNodeHelper(): self.print("It is used to check status of a created node") def statusClientHelper(): self.print("It is used to check status of a created client") def listHelper(): self.print("List all the commands, you can use in this CLI.") def exitHelper(): self.print("Exits the CLI") def licenseHelper(): self.print(""" Copyright 2016 Evernym, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """) def sendHelper(): self.print("""Used to send a message from a client to nodes" Usage: client <clientName> send <{Message}>""") def showHelper(): self.print("""Used to show status of request sent by the client" Usage: client <clientName> show <reqID>""") def defaultHelper(): self.printHelp() def pluginHelper(): self.print("""Used to load a plugin from a given directory Usage: load plugins from <dir>""") mappings = { 'new': newHelper, 'status': statusHelper, 'list': listHelper, 'newnode': nodeHelper, 'newclient': clientHelper, 'statusnode': statusNodeHelper, 'statusclient': statusClientHelper, 'license': licenseHelper, 'send': sendHelper, 'show': showHelper, 'exit': exitHelper, 'plugins': pluginHelper } return defaultdict(lambda: defaultHelper, **mappings) def print(self, msg, token=None, newline=True): if newline: msg += "\n" part = partial(self.cli.print_tokens, [(token, msg)]) if self.debug: part() else: self.cli.run_in_terminal(part) def printVoid(self): self.print(self.voidMsg) def out(self, record, extra_cli_value=None): """ Callback so that this cli can manage colors :param record: a log record served up from a custom handler :param extra_cli_value: the "cli" value in the extra dictionary :return: """ if extra_cli_value in ("IMPORTANT", "ANNOUNCE"): self.print(record.msg, Token.BoldGreen) # green elif extra_cli_value in ("WARNING",): self.print(record.msg, Token.BoldOrange) # orange elif extra_cli_value in ("STATUS",): self.print(record.msg, Token.BoldBlue) # blue elif extra_cli_value in ("PLAIN", "LOW_STATUS"): self.print(record.msg, Token) # white else: self.print(record.msg, Token) def printHelp(self): self.print("""{}-CLI, a simple command-line interface for a {} sandbox. Commands: help - Shows this help message help <command> - Shows the help message of <command> new - creates one or more new nodes or clients keyshare - manually starts key sharing of a node status - Shows general status of the sandbox status <node_name>|<client_name> - Shows specific status list - Shows the list of commands you can run license - Show the license exit - exit the command-line interface ('quit' also works)""". format(self.properName, self.fullName)) def printCmdHelper(self, command=None): self.functionMappings[command]() @staticmethod def joinTokens(tokens, separator=None, begin=None, end=None): if separator is None: separator = (Token, ', ') elif isinstance(separator, str): separator = (Token, separator) r = reduce(lambda x, y: x + [separator, y] if x else [y], tokens, []) if begin is not None: b = (Token, begin) if isinstance(begin, str) else begin r = [b] + r if end: if isinstance(end, str): r.append((Token, end)) return r def printTokens(self, tokens, separator=None, begin=None, end=None): x = self.joinTokens(tokens, separator, begin, end) self.cli.print_tokens(x, style=self.style) def printNames(self, names, newline=False): tokens = [(Token.Name, n) for n in names] self.printTokens(tokens) if newline: self.printTokens([(Token, "\n")]) def showValidNodes(self): self.printTokens([(Token, "Valid node names are: ")]) self.printNames(self.nodeReg.keys(), newline=True) def showNodeRegistry(self): t = [] for name in self.nodeReg: val = self.nodeReg[name] if len(val) == 3: ((ip, port), vk, pk) = val else: (ip, port) = val t.append((Token.Name, " " + name)) t.append((Token, ": {}:{}\n".format(ip, port))) self.cli.print_tokens(t, style=self.style) def loadFromFile(self, file: str) -> None: cfg = ConfigParser() cfg.read(file) self.nodeReg = Cli.loadNodeReg(cfg) self.cliNodeReg = Cli.loadCliNodeReg(cfg) @classmethod def loadNodeReg(cls, cfg: ConfigParser) -> OrderedDict: return cls._loadRegistry(cfg, 'node_reg') @classmethod def loadCliNodeReg(cls, cfg: ConfigParser) -> OrderedDict: try: return cls._loadRegistry(cfg, 'client_node_reg') except configparser.NoSectionError: return OrderedDict() @classmethod def _loadRegistry(cls, cfg: ConfigParser, reg: str): registry = OrderedDict() for n in cfg.items(reg): host, port = n[1].split() registry.update({n[0]: (host, int(port))}) return registry def getStatus(self): self.print('Nodes: ', newline=False) if not self.nodes: self.print("No nodes are running. Try typing 'new node <name>'.") else: self.printNames(self.nodes, newline=True) if not self.clients: clients = "No clients are running. Try typing 'new client <name>'." else: clients = ",".join(self.clients.keys()) self.print("Clients: " + clients) f = getMaxFailures(len(self.nodes)) self.print("f-value (number of possible faulty nodes): {}".format(f)) if f != 0 and len(self.nodes) >= 2 * f + 1: node = list(self.nodes.values())[0] mPrimary = node.replicas[node.instances.masterId].primaryName bPrimary = node.replicas[node.instances.backupIds[0]].primaryName self.print("Instances: {}".format(f + 1)) self.print(" Master (primary is on {})". format(Replica.getNodeName(mPrimary))) self.print(" Backup (primary is on {})". format(Replica.getNodeName(bPrimary))) else: self.print("Instances: " "Not enough nodes to create protocol instances") def keyshare(self, nodeName): node = self.nodes.get(nodeName, None) if node is not None: node = self.nodes[nodeName] node.startKeySharing() elif nodeName not in self.nodeReg: tokens = [(Token.Error, "Invalid node name '{}'.".format(nodeName))] self.printTokens(tokens) self.showValidNodes() return else: tokens = [(Token.Error, "Node '{}' not started.".format(nodeName))] self.printTokens(tokens) self.showStartedNodes() return def showStartedNodes(self): self.printTokens([(Token, "Started nodes are: ")]) startedNodes = self.nodes.keys() if startedNodes: self.printNames(self.nodes.keys(), newline=True) else: self.print("None", newline=True) def newNode(self, nodeName: str): opVerifiers = self.plugins['VERIFICATION'] if self.plugins else [] if nodeName in self.nodes: self.print("Node {} already exists.".format(nodeName)) return if nodeName == "all": names = set(self.nodeReg.keys()) - set(self.nodes.keys()) elif nodeName not in self.nodeReg: tokens = [ (Token.Error, "Invalid node name '{}'. ".format(nodeName))] self.printTokens(tokens) self.showValidNodes() return else: names = [nodeName] nodes = [] for name in names: node = self.NodeClass(name, self.nodeReg, basedirpath=self.basedirpath, opVerifiers=opVerifiers) self.nodes[name] = node self.looper.add(node) node.startKeySharing() for client in self.clients.values(): self.bootstrapClientKey(client, node) for identifier, verkey in self.externalClientKeys.items(): node.clientAuthNr.addClient(identifier, verkey) nodes.append(node) return nodes def ensureValidClientId(self, clientName): """ Ensures client id is not already used or is not starting with node names. :param clientName: :return: """ if clientName in self.clients: raise ValueError("Client {} already exists.".format(clientName)) if any([clientName.startswith(nm) for nm in self.nodeNames]): raise ValueError("Client name cannot start with node names, " "which are {}." .format(', '.join(self.nodeReg.keys()))) def statusClient(self, clientName): if clientName == "all": for nm in self.clients: self.statusClient(nm) return if clientName not in self.clients: self.print("client not found", Token.Error) else: self.print(" Name: " + clientName) client = self.clients[clientName] # type: Client self.printTokens([(Token.Heading, 'Status for client:'), (Token.Name, client.name)], separator=' ', end='\n') self.print(" age (seconds): {:.0f}".format( time.perf_counter() - client.created)) self.print(" status: {}".format(client.status.name)) self.print(" connected to: ", newline=False) if client._conns: self.printNames(client._conns, newline=True) else: self.printVoid() self.print(" Identifier: {}".format(client.defaultIdentifier)) self.print(" Verification key: {}".format(client.getSigner().verkey)) self.print(" Submissions: {}".format(client.lastReqId)) def statusNode(self, nodeName): if nodeName == "all": for nm in self.nodes: self.statusNode(nm) return if nodeName not in self.nodes: self.print("Node {} not found".format(nodeName), Token.Error) else: self.print("\n Name: " + nodeName) node = self.nodes[nodeName] # type: Node val = self.nodeReg.get(nodeName) if len(val) == 3: ((ip, port), vk, pk) = val else: ip, port = val nha = "{}:{}".format(ip, port) self.print(" Node listener: " + nha) val = self.cliNodeReg.get(nodeName + CLIENT_STACK_SUFFIX) if len(val) == 3: ((ip, port), vk, pk) = val else: ip, port = val cha = "{}:{}".format(ip, port) self.print(" Client listener: " + cha) self.print(" Status: {}".format(node.status.name)) self.print(' Connections: ', newline=False) connecteds = node.nodestack.connecteds() if connecteds: self.printNames(connecteds, newline=True) else: self.printVoid() notConnecteds = list({r for r in self.nodes.keys() if r not in connecteds and r != nodeName}) if notConnecteds: self.print(' Not connected: ', newline=False) self.printNames(notConnecteds, newline=True) self.print(" Replicas: {}".format(len(node.replicas)), newline=False) if node.hasPrimary: if node.primaryReplicaNo == 0: self.print(" (primary of Master)") else: self.print(" (primary of Backup)") else: self.print(" (no primary replicas)") self.print(" Up time (seconds): {:.0f}". format(time.perf_counter() - node.created)) self.print(" Clients: ", newline=False) clients = node.clientstack.connecteds() if clients: self.printNames(clients, newline=True) else: self.printVoid() def newClient(self, clientName, seed=None, identifier=None, signer=None): try: self.ensureValidClientId(clientName) client_addr = self.nextAvailableClientAddr() if not signer: seed = seed.encode("utf-8") if seed else None signer = SimpleSigner(identifier=identifier, seed=seed) \ if (seed or identifier) else None client = self.ClientClass(clientName, ha=client_addr, nodeReg=self.cliNodeReg, signer=signer, basedirpath=self.basedirpath) self.looper.add(client) for node in self.nodes.values(): self.bootstrapClientKey(client, node) self.clients[clientName] = client self.clientWC.words = list(self.clients.keys()) return client except ValueError as ve: self.print(ve.args[0], Token.Error) @staticmethod def bootstrapClientKey(client, node): idAndKey = client.getSigner().identifier, client.getSigner().verkey node.clientAuthNr.addClient(*idAndKey) def sendMsg(self, clientName, msg): client = self.clients.get(clientName, None) if client: request, = client.submit(msg) self.requests[str(request.reqId)] = request.reqId else: self.print("No such client. See: 'help new' for more details") def getReply(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: reply, status = client.getReply(requestID) self.print("Reply for the request: {}".format(reply)) self.print("Status: {}".format(status)) elif not client: self.print("No such client. See: 'help new' for more details") else: self.print("No such request. See: 'help new' for more details") def showDetails(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: client.showReplyDetails(requestID) else: self.print("No such client. See: 'help new' for more details") async def shell(self, *commands, interactive=True): """ Coroutine that runs command, including those from an interactive command line. :param commands: an iterable of commands to run first :param interactive: when True, this coroutine will process commands entered on the command line. :return: """ # First handle any commands passed in for command in commands: self.print("\nRunning command: '{}'...\n".format(command)) self.parse(command) # then handle commands from the prompt while interactive: try: result = await self.cli.run_async() self.parse(result.text if result else "") except (EOFError, KeyboardInterrupt, Exit): break self.print('Goodbye.') def _simpleAction(self, matchedVars): if matchedVars.get('simple'): cmd = matchedVars.get('simple') if cmd == 'status': self.getStatus() elif cmd == 'license': self.printCmdHelper('license') elif cmd in ['exit', 'quit']: raise Exit return True def _helpAction(self, matchedVars): if matchedVars.get('command') == 'help': helpable = matchedVars.get('helpable') node_or_cli = matchedVars.get('node_or_cli') if helpable: if node_or_cli: self.printCmdHelper(command="{}{}". format(helpable, node_or_cli)) else: self.printCmdHelper(command=helpable) else: self.printHelp() return True def _listAction(self, matchedVars): if matchedVars.get('command') == 'list': for cmd in self.commands: self.print(cmd) return True def _newNodeAction(self, matchedVars): if matchedVars.get('node_command') == 'new': self.createEntities('node_name', 'more_nodes', matchedVars, self.newNode) return True def _newClientAction(self, matchedVars): if matchedVars.get('client_command') == 'new': self.createEntities('client_name', 'more_clients', matchedVars, self.newClient) return True def _statusNodeAction(self, matchedVars): if matchedVars.get('node_command') == 'status': node = matchedVars.get('node_name') self.statusNode(node) return True def _statusClientAction(self, matchedVars): if matchedVars.get('client_command') == 'status': client = matchedVars.get('client_name') self.statusClient(client) return True def _keyShareAction(self, matchedVars): if matchedVars.get('node_command') == 'keyshare': name = matchedVars.get('node_name') self.keyshare(name) return True def _clientCommand(self, matchedVars): if matchedVars.get('client') == 'client': client_name = matchedVars.get('client_name') client_action = matchedVars.get('cli_action') if client_action == 'send': msg = matchedVars.get('msg') try: actualMsgRepr = ast.literal_eval(msg) except Exception as ex: self.print("error evaluating msg expression: {}". format(ex), Token.BoldOrange) return True self.sendMsg(client_name, actualMsgRepr) return True elif client_action == 'show': req_id = matchedVars.get('req_id') self.getReply(client_name, req_id) return True # else: # self.printCmdHelper("sendmsg") def _loadPluginDirAction(self, matchedVars): if matchedVars.get('load_plugins') == 'load plugins from': pluginsPath = matchedVars.get('plugin_dir') try: self.plugins = PluginLoader(pluginsPath).plugins except FileNotFoundError as ex: _, err = ex.args self.print(err, Token.BoldOrange) return True def _loadPluginAction(self, matchedVars): if matchedVars.get('load') == 'load': file = matchedVars.get("file_name") if os.path.exists(file): try: self.loadFromFile(file) self.print("Node registry loaded.") self.showNodeRegistry() except configparser.ParsingError: self.logger.warn("Could not parse file. " "Please ensure that the file " "has sections node_reg " "and client_node_reg.", extra={'cli': 'WARNING'}) else: self.logger.warn("File {} not found.".format(file), extra={"cli": "WARNING"}) return True def _addKeyAction(self, matchedVars): if matchedVars.get('add_key') == 'add key': verkey = matchedVars.get('verkey') # TODO make verkey case insensitive identifier = matchedVars.get('identifier') if identifier in self.externalClientKeys: self.print("identifier already added", Token.Error) return self.externalClientKeys[identifier] = verkey for n in self.nodes.values(): n.clientAuthNr.addClient(identifier, verkey) return True def getActionList(self): return [self._simpleAction, self._helpAction, self._listAction, self._newNodeAction, self._newClientAction, self._statusNodeAction, self._statusClientAction, self._keyShareAction, self._loadPluginDirAction, self._clientCommand, self._loadPluginAction, self._addKeyAction] def parse(self, cmdText): m = self.grammar.match(cmdText) if m: matchedVars = m.variables() self.logger.info("CLI command entered: {}".format(cmdText), extra={"cli": False}) for action in self.getActionList(): r = action(matchedVars) if r: break else: self.invalidCmd(cmdText) else: if cmdText != "": self.invalidCmd(cmdText) @staticmethod def createEntities(name: str, moreNames: str, matchedVars, initializer): entity = matchedVars.get(name) more = matchedVars.get(moreNames) more = more.split(',') if more is not None and len(more) > 0 else [] names = [n for n in [entity] + more if len(n) != 0] seed = matchedVars.get("seed") identifier = matchedVars.get("nym") if len(names) == 1 and (seed or identifier): initializer(names[0].strip(), seed=seed, identifier=identifier) else: for name in names: initializer(name.strip()) def invalidCmd(self, cmdText): self.print("Invalid command: '{}'\n".format(cmdText)) self.printCmdHelper(command=None) def nextAvailableClientAddr(self, curClientPort=8100): self.curClientPort = self.curClientPort or curClientPort self.curClientPort += 1 host = "127.0.0.1" try: checkPortAvailable((host,self.curClientPort)) return host, self.curClientPort except Exception as ex: tokens = [(Token.Error, "Cannot bind to port {}: {}, " "trying another port.".format( self.curClientPort, ex))] self.printTokens(tokens) return self.nextAvailableClientAddr(self.curClientPort)
class PtPdb(pdb.Pdb): def __init__(self): pdb.Pdb.__init__(self) # Cache for the grammar. self._grammar_cache = None # (current_pdb_commands, grammar) tuple. self.completer = None self.validator = None self.lexer = None self._source_code_window = Window( BufferControl( buffer_name='source_code', lexer=PygmentsLexer(PythonLexer), input_processors=[ HighlightSearchProcessor(preview_search=True), HighlightSelectionProcessor(), ], ), left_margins=[ SourceCodeMargin(self), NumberredMargin(), ], right_margins=[ScrollbarMargin()], scroll_offsets=ScrollOffsets(top=2, bottom=2), height=LayoutDimension(preferred=10)) # Callstack window. callstack = CallStack(weakref.ref(self)) self.callstack_focussed = False # When True, show cursor there, and allow navigation through it. self.callstack_selected_frame = 0 # Top frame. show_pdb_content_filter = ~IsDone() & Condition( lambda cli: not self.python_input.show_exit_confirmation) self.python_input = PythonInput( get_locals=lambda: self.curframe.f_locals, get_globals=lambda: self.curframe.f_globals, _completer=DynamicCompleter(lambda: self.completer), _validator=DynamicValidator(lambda: self.validator), _accept_action = self._create_accept_action(), _extra_buffers={'source_code': Buffer(read_only=True)}, _input_buffer_height=LayoutDimension(min=2, max=4), _lexer=PdbLexer(), _extra_buffer_processors=[ ConditionalProcessor( processor=CompletionHint(), filter=~IsDone()) ], _extra_layout_body=ConditionalContainer( HSplit([ VSplit([ HSplit([ SourceTitlebar(weakref.ref(self)), FloatContainer( content=self._source_code_window, floats=[ Float(right=0, bottom=0, content=BreakPointInfoToolbar(weakref.ref(self))) ]), ]), HSplit([ Window(width=LayoutDimension.exact(1), height=LayoutDimension.exact(1), content=FillControl('\u252c', token=Token.Toolbar.Title)), Window(width=LayoutDimension.exact(1), content=FillControl('\u2502', token=Token.Separator)), ]), HSplit([ StackTitlebar(weakref.ref(self)), Window(callstack, scroll_offsets=ScrollOffsets(top=2, bottom=2), right_margins=[ScrollbarMargin()], height=LayoutDimension(preferred=10)), ]), ]), ]), filter=show_pdb_content_filter), _extra_toolbars=[ ConditionalContainer( PdbShortcutsToolbar(weakref.ref(self)), show_pdb_content_filter) ], history_filename=os.path.expanduser('~/.ptpdb_history'), ) # Override prompt style. self.python_input.all_prompt_styles['pdb'] = PdbPromptStyle(self._get_current_pdb_commands()) self.python_input.prompt_style = 'pdb' # Override exit message. self.python_input.exit_message = 'Do you want to quit BDB? This raises BdbQuit.' # Set UI styles. self.python_input.ui_styles = { 'ptpdb': get_ui_style(), } self.python_input.use_ui_colorscheme('ptpdb') # Set autocompletion style. (Multi-column works nicer.) self.python_input.completion_visualisation = CompletionVisualisation.MULTI_COLUMN # Load additional key bindings. load_custom_pdb_key_bindings(self, self.python_input.key_bindings_registry) self.cli = CommandLineInterface( eventloop=create_eventloop(), application=self.python_input.create_application()) def _create_accept_action(self): """ Create an AcceptAction for the input buffer that replaces shortcuts like 's' with the full command ('step') before returning it. """ def handler(cli, buffer): # Get first part. parts = buffer.text.strip().split(None, 1) if len(parts) == 0: first, rest = '', '' elif len(parts) == 1: first, rest = parts[0], '' else: first, rest = parts # Replace text in buffer and return it. buffer.document = Document(shortcuts.get(first, first) + ' ' + rest) cli.set_return_value(buffer.document) return AcceptAction(handler) def cmdloop(self, intro=None): """ Copy/Paste of pdb.Pdb.cmdloop. But using our own CommandLineInterface for reading input instead. """ self.preloop() if intro is not None: self.intro = intro if self.intro: self.stdout.write(str(self.intro)+"\n") stop = None while not stop: if self.cmdqueue: line = self.cmdqueue.pop(0) else: if self.use_rawinput: line = self._get_input() line = self.precmd(line) stop = self.onecmd(line) stop = self.postcmd(stop, line) self.postloop() def _get_current_pdb_commands(self): return ( list(commands_with_help.keys()) + list(shortcuts.keys()) + list(self.aliases.keys())) def _create_grammar(self): """ Return the compiled grammar for this PDB shell. The grammar of PDB depends on the available list of PDB commands (which depends on the currently defined aliases.) Therefor we generate a new grammar when it changes, but cache it otherwise. (It's still expensive to compile.) """ pdb_commands = self._get_current_pdb_commands() if self._grammar_cache is None or self._grammar_cache[0] != pdb_commands: self._grammar_cache = [ pdb_commands, create_pdb_grammar(pdb_commands)] return self._grammar_cache[1] def _get_input(self): """ Read PDB input. Return input text. """ # Reset multiline/paste mode every time. self.python_input.paste_mode = False self.python_input.currently_multiline = False # Set source code document. self._show_source_code(self.curframe.f_code.co_filename) self.cli.buffers[DEFAULT_BUFFER].document = Document('') # Select the current frame of the stack. for i, (frame, lineno) in enumerate(self.stack): if frame is self.curframe: self.callstack_selected_frame = i break # Set up a new completer and validator for the new grammar. g = self._create_grammar() self.completer = GrammarCompleter(g, completers={ 'enabled_breakpoint': BreakPointListCompleter(only_enabled=True), 'disabled_breakpoint': BreakPointListCompleter(only_disabled=True), 'alias_name': AliasCompleter(self), 'python_code': PythonCompleter(lambda: self.curframe.f_globals, lambda: self.curframe.f_locals), 'breakpoint': BreakPointListCompleter(), 'pdb_command': PdbCommandsCompleter(self), 'python_file': PythonFileCompleter(), 'python_function': PythonFunctionCompleter(self), }) self.validator = GrammarValidator(g, { 'python_code': PythonValidator() }) # Make sure not to start in Vi navigation mode. self.python_input.key_bindings_manager.reset(self.cli) self.cli.buffers[DEFAULT_BUFFER].reset() def pre_run(): self._source_code_window.vertical_scroll = 100000 # source_code_doc.line_count try: return self.cli.run(reset_current_buffer=False, pre_run=pre_run).text except EOFError: # Turn Control-D key press into a 'quit' command. return 'quit' def _show_source_code(self, filename): """ Show the source code in the `source_code` buffer. """ source_code_doc = self._get_source_code_document(filename) self.cli.buffers['source_code']._set_text(source_code_doc.text + '\n') self.cli.buffers['source_code']._set_cursor_position(source_code_doc.cursor_position) def _get_source_code_document(self, filename): """ Return source code around current line as string. """ source_code = linecache.getlines(filename) if six.PY2: source_code = [l.decode('utf-8') for l in source_code] source_code = ''.join(source_code) document = Document(source_code) return Document(document.text, document.translate_row_col_to_index( row=self.curframe.f_lineno - 1, col=0)) # # Methods overriden from Pdb, in order to add highlighting. # def postcmd(self, stop, line): """ Override 'postcmd': (Insert whitespace.) """ print('') return pdb.Pdb.postcmd(self, stop, line) def preloop(self): print('') return pdb.Pdb.preloop(self) def do_interact(self, args): """ Interact: start interpreter. (Override the 'pdb' implementation. We call ptpython instead.) """ print('') ns = self.curframe.f_globals.copy() ns.update(self.curframe_locals) embed(globals=ns) def error(self, msg): """ Override default error handler from PDB. """ self.cli.print_tokens([ (Token.Pdb.Error, ' %s \n' % msg) ]) def print_stack_entry(self, frame_lineno, prompt_prefix=': '): """ Override `print_stack_entry` of Pdb, in order to add highlighting. """ frame, lineno = frame_lineno tokens = [] tokens.extend(format_stack_entry(self, frame, lineno)) tokens.append((Token, '\n')) self.cli.print_tokens(tokens) def do_list(self, arg): """ Override `Pdb.do_list`: Add highlighting. """ self.lastcmd = 'list' last = None if arg and arg != '.': try: if ',' in arg: first, last = arg.split(',') first = int(first.strip()) last = int(last.strip()) if last < first: # assume it's a count last = first + last else: first = int(arg.strip()) first = max(1, first - 5) except ValueError: self.error('Error in argument: %r' % arg) return elif self.lineno is None or arg == '.': first = max(1, self.curframe.f_lineno - 5) else: first = self.lineno + 1 if last is None: last = first + 10 filename = self.curframe.f_code.co_filename breaklist = self.get_file_breaks(filename) try: lines = linecache.getlines(filename, self.curframe.f_globals) self._print_lines_2(lines, first, last, breaklist, self.curframe) self.lineno = min(last, len(lines)) if len(lines) < last: self.message('[EOF]') except KeyboardInterrupt: pass do_l = do_list def _print_lines_2(self, lines, start, end, breaks=(), frame=None): """ Similar to `Pdb._print_lines`, except that this takes all the lines of the given file as input, it uses Pygments for the highlighting, it does slicing, and it prints everything in color. """ if frame: current_lineno = frame.f_lineno else: current_lineno = exc_lineno = -1 # Highlight everything. (Highlighting works much better from the # beginning of the file.) all_tokens = python_lexer.get_tokens(''.join(lines)) # Slice lines. lines = list(split_lines(all_tokens))[start-1:end] # Add left margin. (Numbers + 'B' or '->'.) def add_margin(lineno, tokens): is_break = lineno in breaks is_current_line = lineno == current_lineno return get_line_prefix_tokens(is_break, is_current_line) \ + [(Token.LineNumber, str(lineno).rjust(3) + ' ')] \ + tokens + [(Token, '\n')] lines = [add_margin(i + start, tokens) for i, tokens in enumerate(lines)] for l in lines: self.cli.print_tokens(l) def message(self, msg): """ Print message to stdout. This function is present in Pdb for Python3, but not in Python2. """ print(msg, file=self.stdout)
class PtPdb(pdb.Pdb): def __init__(self): pdb.Pdb.__init__(self) # Cache for the grammar. self._grammar_cache = None # (current_pdb_commands, grammar) tuple. self.completer = None self.validator = None self.lexer = None self._source_code_window = Window(BufferControl( buffer_name='source_code', lexer=PygmentsLexer(PythonLexer), input_processors=[ HighlightSearchProcessor(preview_search=True), HighlightSelectionProcessor(), ], ), left_margins=[ SourceCodeMargin(self), NumberredMargin(), ], right_margins=[ScrollbarMargin()], scroll_offsets=ScrollOffsets( top=2, bottom=2), height=LayoutDimension(preferred=10)) # Callstack window. callstack = CallStack(weakref.ref(self)) self.callstack_focussed = False # When True, show cursor there, and allow navigation through it. self.callstack_selected_frame = 0 # Top frame. show_pdb_content_filter = ~IsDone() & Condition( lambda cli: not self.python_input.show_exit_confirmation) self.python_input = PythonInput( get_locals=lambda: self.curframe.f_locals, get_globals=lambda: self.curframe.f_globals, _completer=DynamicCompleter(lambda: self.completer), _validator=DynamicValidator(lambda: self.validator), _accept_action=self._create_accept_action(), _extra_buffers={'source_code': Buffer(read_only=True)}, _input_buffer_height=LayoutDimension(min=2, max=4), _lexer=PdbLexer(), _extra_buffer_processors=[ ConditionalProcessor(processor=CompletionHint(), filter=~IsDone()) ], _extra_layout_body=ConditionalContainer( HSplit([ VSplit([ HSplit([ SourceTitlebar(weakref.ref(self)), FloatContainer( content=self._source_code_window, floats=[ Float(right=0, bottom=0, content=BreakPointInfoToolbar( weakref.ref(self))) ]), ]), HSplit([ Window(width=LayoutDimension.exact(1), height=LayoutDimension.exact(1), content=FillControl( '\u252c', token=Token.Toolbar.Title)), Window(width=LayoutDimension.exact(1), content=FillControl('\u2502', token=Token.Separator)), ]), HSplit([ StackTitlebar(weakref.ref(self)), Window(callstack, scroll_offsets=ScrollOffsets(top=2, bottom=2), right_margins=[ScrollbarMargin()], height=LayoutDimension(preferred=10)), ]), ]), ]), filter=show_pdb_content_filter), _extra_toolbars=[ ConditionalContainer(PdbShortcutsToolbar(weakref.ref(self)), show_pdb_content_filter) ], history_filename=os.path.expanduser('~/.ptpdb_history'), ) # Override prompt style. self.python_input.all_prompt_styles['pdb'] = PdbPromptStyle( self._get_current_pdb_commands()) self.python_input.prompt_style = 'pdb' # Override exit message. self.python_input.exit_message = 'Do you want to quit BDB? This raises BdbQuit.' # Set UI styles. self.python_input.ui_styles = { 'ptpdb': get_ui_style(), } self.python_input.use_ui_colorscheme('ptpdb') # Set autocompletion style. (Multi-column works nicer.) self.python_input.completion_visualisation = CompletionVisualisation.MULTI_COLUMN # Load additional key bindings. load_custom_pdb_key_bindings(self, self.python_input.key_bindings_registry) self.cli = CommandLineInterface( eventloop=create_eventloop(), application=self.python_input.create_application()) def _create_accept_action(self): """ Create an AcceptAction for the input buffer that replaces shortcuts like 's' with the full command ('step') before returning it. """ def handler(cli, buffer): # Get first part. parts = buffer.text.strip().split(None, 1) if len(parts) == 0: first, rest = '', '' elif len(parts) == 1: first, rest = parts[0], '' else: first, rest = parts # Replace text in buffer and return it. buffer.document = Document( shortcuts.get(first, first) + ' ' + rest) cli.set_return_value(buffer.document) return AcceptAction(handler) def cmdloop(self, intro=None): """ Copy/Paste of pdb.Pdb.cmdloop. But using our own CommandLineInterface for reading input instead. """ self.preloop() if intro is not None: self.intro = intro if self.intro: self.stdout.write(str(self.intro) + "\n") stop = None while not stop: if self.cmdqueue: line = self.cmdqueue.pop(0) else: if self.use_rawinput: line = self._get_input() line = self.precmd(line) stop = self.onecmd(line) stop = self.postcmd(stop, line) self.postloop() def _get_current_pdb_commands(self): return (list(commands_with_help.keys()) + list(shortcuts.keys()) + list(self.aliases.keys())) def _create_grammar(self): """ Return the compiled grammar for this PDB shell. The grammar of PDB depends on the available list of PDB commands (which depends on the currently defined aliases.) Therefor we generate a new grammar when it changes, but cache it otherwise. (It's still expensive to compile.) """ pdb_commands = self._get_current_pdb_commands() if self._grammar_cache is None or self._grammar_cache[ 0] != pdb_commands: self._grammar_cache = [ pdb_commands, create_pdb_grammar(pdb_commands) ] return self._grammar_cache[1] def _get_input(self): """ Read PDB input. Return input text. """ # Reset multiline/paste mode every time. self.python_input.paste_mode = False self.python_input.currently_multiline = False # Set source code document. self._show_source_code(self.curframe.f_code.co_filename) self.cli.buffers[DEFAULT_BUFFER].document = Document('') # Select the current frame of the stack. for i, (frame, lineno) in enumerate(self.stack): if frame is self.curframe: self.callstack_selected_frame = i break # Set up a new completer and validator for the new grammar. g = self._create_grammar() self.completer = GrammarCompleter( g, completers={ 'enabled_breakpoint': BreakPointListCompleter(only_enabled=True), 'disabled_breakpoint': BreakPointListCompleter(only_disabled=True), 'alias_name': AliasCompleter(self), 'python_code': PythonCompleter(lambda: self.curframe.f_globals, lambda: self.curframe.f_locals), 'breakpoint': BreakPointListCompleter(), 'pdb_command': PdbCommandsCompleter(self), 'python_file': PythonFileCompleter(), 'python_function': PythonFunctionCompleter(self), }) self.validator = GrammarValidator(g, {'python_code': PythonValidator()}) # Make sure not to start in Vi navigation mode. self.python_input.key_bindings_manager.reset(self.cli) self.cli.buffers[DEFAULT_BUFFER].reset() def pre_run(): self._source_code_window.vertical_scroll = 100000 # source_code_doc.line_count try: return self.cli.run(reset_current_buffer=False, pre_run=pre_run).text except EOFError: # Turn Control-D key press into a 'quit' command. return 'quit' def _show_source_code(self, filename): """ Show the source code in the `source_code` buffer. """ source_code_doc = self._get_source_code_document(filename) self.cli.buffers['source_code']._set_text(source_code_doc.text + '\n') self.cli.buffers['source_code']._set_cursor_position( source_code_doc.cursor_position) def _get_source_code_document(self, filename): """ Return source code around current line as string. """ source_code = linecache.getlines(filename) if six.PY2: source_code = [l.decode('utf-8') for l in source_code] source_code = ''.join(source_code) document = Document(source_code) return Document( document.text, document.translate_row_col_to_index(row=self.curframe.f_lineno - 1, col=0)) # # Methods overriden from Pdb, in order to add highlighting. # def postcmd(self, stop, line): """ Override 'postcmd': (Insert whitespace.) """ print('') return pdb.Pdb.postcmd(self, stop, line) def preloop(self): print('') return pdb.Pdb.preloop(self) def do_interact(self, args): """ Interact: start interpreter. (Override the 'pdb' implementation. We call ptpython instead.) """ print('') ns = self.curframe.f_globals.copy() ns.update(self.curframe_locals) embed(globals=ns) def error(self, msg): """ Override default error handler from PDB. """ self.cli.print_tokens([(Token.Pdb.Error, ' %s \n' % msg)]) def print_stack_entry(self, frame_lineno, prompt_prefix=': '): """ Override `print_stack_entry` of Pdb, in order to add highlighting. """ frame, lineno = frame_lineno tokens = [] tokens.extend(format_stack_entry(self, frame, lineno)) tokens.append((Token, '\n')) self.cli.print_tokens(tokens) def do_list(self, arg): """ Override `Pdb.do_list`: Add highlighting. """ self.lastcmd = 'list' last = None if arg and arg != '.': try: if ',' in arg: first, last = arg.split(',') first = int(first.strip()) last = int(last.strip()) if last < first: # assume it's a count last = first + last else: first = int(arg.strip()) first = max(1, first - 5) except ValueError: self.error('Error in argument: %r' % arg) return elif self.lineno is None or arg == '.': first = max(1, self.curframe.f_lineno - 5) else: first = self.lineno + 1 if last is None: last = first + 10 filename = self.curframe.f_code.co_filename breaklist = self.get_file_breaks(filename) try: lines = linecache.getlines(filename, self.curframe.f_globals) self._print_lines_2(lines, first, last, breaklist, self.curframe) self.lineno = min(last, len(lines)) if len(lines) < last: self.message('[EOF]') except KeyboardInterrupt: pass do_l = do_list def _print_lines_2(self, lines, start, end, breaks=(), frame=None): """ Similar to `Pdb._print_lines`, except that this takes all the lines of the given file as input, it uses Pygments for the highlighting, it does slicing, and it prints everything in color. """ if frame: current_lineno = frame.f_lineno else: current_lineno = exc_lineno = -1 # Highlight everything. (Highlighting works much better from the # beginning of the file.) all_tokens = python_lexer.get_tokens(''.join(lines)) # Slice lines. lines = list(split_lines(all_tokens))[start - 1:end] # Add left margin. (Numbers + 'B' or '->'.) def add_margin(lineno, tokens): is_break = lineno in breaks is_current_line = lineno == current_lineno return get_line_prefix_tokens(is_break, is_current_line) \ + [(Token.LineNumber, str(lineno).rjust(3) + ' ')] \ + tokens + [(Token, '\n')] lines = [ add_margin(i + start, tokens) for i, tokens in enumerate(lines) ] for l in lines: self.cli.print_tokens(l) def message(self, msg): """ Print message to stdout. This function is present in Pdb for Python3, but not in Python2. """ print(msg, file=self.stdout)
class Cli: isElectionStarted = False primariesSelected = 0 electedPrimaries = set() # noinspection PyPep8 def __init__(self, looper, tmpdir, nodeReg, cliNodeReg): self.curClientPort = None logging.root.addHandler(CliHandler(self.out)) self.looper = looper self.tmpdir = tmpdir self.nodeReg = nodeReg self.cliNodeReg = cliNodeReg # Used to store created clients self.clients = {} # clientId -> Client # To store the created requests self.requests = {} # To store the nodes created self.nodes = {} self.cliCmds = {'new', 'status', 'list'} self.nodeCmds = {'new', 'status', 'list', 'keyshare'} self.helpablesCommands = self.cliCmds | self.nodeCmds self.simpleCmds = {'status', 'help', 'exit', 'quit', 'license'} self.commands = {'list'} | self.simpleCmds self.cliActions = {'send', 'show'} self.commands.update(self.cliCmds) self.commands.update(self.nodeCmds) self.node_or_cli = ['node', 'client'] self.nodeNames = list(self.nodeReg.keys()) + ["all"] ''' examples: status new node Alpha new node all new client Joe client Joe send <msg> client Joe show 1 ''' def re(seq): return '(' + '|'.join(seq) + ')' grams = [ "(\s* (?P<simple>{}) \s*) |".format(re(self.simpleCmds)), "(\s* (?P<command>{}) \s*) |".format(re(self.commands)), "(\s* (?P<client_command>{}) \s+ (?P<node_or_cli>clients?) \s+ (?P<client_name>[a-zA-Z0-9]+) \s*) |".format(re(self.cliCmds)), "(\s* (?P<node_command>{}) \s+ (?P<node_or_cli>nodes?) \s+ (?P<node_name>[a-zA-Z0-9]+) \s*) |".format(re(self.nodeCmds)), "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>send) \s+ (?P<msg>\{\s*\".*\}) \s*) |", "(\s* (?P<client>client) \s+ (?P<client_name>[a-zA-Z0-9]+) \s+ (?P<cli_action>show) \s+ (?P<req_id>[0-9]+) \s*) " # "(\s* (?P<command>help) \s+ (?P<helpable>[a-zA-Z0-9]+) \s*) |", # "(\s* (?P<command>[a-z]+) \s+ (?P<arg1>[a-zA-Z0-9]+) \s*) |", # "(\s* (?P<command>[a-z]+) \s+ (?P<arg1>client) \s+ (?P<arg2>[a-zA-Z0-9]+) \s*) |", # "(\s* (?P<command>[a-z]+) \s+ (?P<arg1>[a-zA-Z0-9]+) \s+ (?P<arg2>\{\s*\".*\}) \s*) |", # "(\s* (?P<client_name>[a-z]+) \s+ (?P<cli_action>[a-zA-Z0-9]+) \s+ (?P<msg>\{\s*\".*\}) \s*) " ] self.grammar = compile("".join(grams)) lexer = GrammarLexer(self.grammar, lexers={ 'node_command': SimpleLexer(Token.Keyword), 'command': SimpleLexer(Token.Keyword), 'node_or_cli': SimpleLexer(Token.Keyword), 'arg2': SimpleLexer(Token.Name), 'node_name': SimpleLexer(Token.Name), 'simple': SimpleLexer(Token.Keyword), 'client_command': SimpleLexer(Token.Keyword), }) self.clientWC = WordCompleter([]) completer = GrammarCompleter(self.grammar, { 'node_command': WordCompleter(self.nodeCmds), 'client_command': WordCompleter(self.cliCmds), 'client': WordCompleter(['client']), 'command': WordCompleter(self.commands), 'node_or_cli': WordCompleter(self.node_or_cli), 'node_name': WordCompleter(self.nodeNames), 'helpable': WordCompleter(self.helpablesCommands), 'client_name': self.clientWC, 'cli_action': WordCompleter(self.cliActions), 'simple': WordCompleter(self.simpleCmds) }) self.style = PygmentsStyle.from_defaults({ Token.Operator: '#33aa33 bold', Token.Number: '#aa3333 bold', Token.Name: '#ffff00 bold', Token.Heading: 'bold', Token.TrailingInput: 'bg:#662222 #ffffff', Token.BoldGreen: '#33aa33 bold', Token.BoldOrange: '#ff4f2f bold', Token.BoldBlue: '#095cab bold'}) self.functionMappings = self.createFunctionMappings() # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop(looper.loop) pers_hist = FileHistory('.zeno-cli-history') # Create interface. app = create_prompt_application('zeno> ', lexer=lexer, completer=completer, style=self.style, history=pers_hist) self.cli = CommandLineInterface( application=app, eventloop=eventloop, output=CustomOutput.from_pty(sys.__stdout__, true_color=True)) # Patch stdout in something that will always print *above* the prompt # when something is written to stdout. sys.stdout = self.cli.stdout_proxy() setupLogging(TRACE_LOG_LEVEL, Console.Wordage.mute, filename="log/cli.log") self.logger = getlogger("cli") print("\nzeno-CLI (c) 2016 Evernym, Inc.") print("Node registry loaded.") print("None of these are created or running yet.") self.showNodeRegistry() print("Type 'help' for more information.") def createFunctionMappings(self): def newHelper(): print("""Is used to create a new node or a client. Usage: new <node/client> <nodeName/clientName>""") def statusHelper(): print("status command helper") def clientHelper(): print("Can be used to create a new client") def listHelper(): print("List all the commands, you can use in this CLI.") def exitHelper(): print("Exits the CLI") def licenseHelper(): print(""" Copyright 2016 Evernym, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """) def sendmsgHelper(): print("""Used to send a message from a client to nodes" Usage: sendmsg <clientName> <{Message}>""") def getreplyHelper(): print("""Used to send a message from a client to nodes" Usage: getreply <clientName> <reqID>""") def showdetailsHelper(): print("""Used to send a message from a client to nodes" Usage: showdetails <clientName> <reqID>""") def defaultHelper(): self.printHelp() mappings = { 'new': newHelper, 'status': statusHelper, 'list': listHelper, 'client': clientHelper, 'license': licenseHelper, 'sendmsg': sendmsgHelper, 'getreply': getreplyHelper, 'showdetails': showdetailsHelper, 'exit': exitHelper } return defaultdict(lambda: defaultHelper, **mappings) def print(self, msg, token=None, newline=True): if newline: msg += "\n" part = partial(self.cli.print_tokens, [(token, msg)]) self.cli.run_in_terminal(part) def out(self, record, extra_cli_value=None): """ Callback so that this cli can manage colors :param record: a log record served up from a custom handler :param extra_cli_value: the "cli" value in the extra dictionary :return: """ # self.trackElectionStarted(record) # self.trackElectionCompleted(record) # TODO come up with an encoding scheme if extra_cli_value in ("IMPORTANT", "ANNOUNCE"): self.print(record.msg, Token.BoldGreen) # green elif extra_cli_value in ("WARNING",): self.print(record.msg, Token.BoldOrange) # orange elif extra_cli_value in ("STATUS",): self.print(record.msg, Token.BoldBlue) # blue elif extra_cli_value in ("PLAIN", "LOW_STATUS"): self.print(record.msg, Token) # white else: self.print(record.msg, Token) @staticmethod def printHelp(): print("""zeno-CLI, a simple command-line interface for an zeno protocol sandbox. Commands: help - Shows this help message help <command> - Shows the help message of <command> new - creates a new node or client keyshare - manually starts key sharing of a node status - Shows general status of the sandbox status <node_name>|<client_name> - Shows specific status list - Shows the list of commands you can run license - Show the license exit - exit the command-line interface ('quit' also works)""") def printCmdHelper(self, command=None): self.functionMappings[command]() @staticmethod def joinTokens(tokens, separator=None, begin=None, end=None): if separator is None: separator = (Token, ', ') elif isinstance(separator, str): separator = (Token, separator) r = reduce(lambda x, y: x + [separator, y] if x else [y], tokens, []) if begin is not None: b = (Token, begin) if isinstance(begin, str) else begin r = [b] + r if end: if isinstance(end, str): r.append((Token, end)) return r def printTokens(self, tokens, separator=None, begin=None, end=None): x = self.joinTokens(tokens, separator, begin, end) self.cli.print_tokens(x, style=self.style) def printNames(self, names, newline=False): tokens = [(Token.Name, n) for n in names] self.printTokens(tokens) if newline: self.printTokens([(Token, "\n")]) def showValidNodes(self): self.printTokens([(Token, "Valid node names are: ")]) self.printNames(self.nodeReg.keys(), newline=True) def showNodeRegistry(self): t = [] for n, (ip, port) in self.nodeReg.items(): t.append((Token.Name, " " + n)) t.append((Token, ": {}:{}\n".format(ip, port))) self.cli.print_tokens(t, style=self.style) def getStatus(self): if len(self.nodes) > 1: print("The following nodes are up and running: ") elif len(self.nodes) == 1: print("The following node is up and running: ") else: print("No nodes are running. Try typing 'new node <name>'.") for node in self.nodes: print(node) if len(self.nodes) > 1: print("The following clients are up and running: ") elif len(self.nodes) == 1: print("The following client is up and running: ") else: print("No clients are running. Try typing 'new client <name>'.") for client in self.clients: print(client) def keyshare(self, nodeName): node = self.nodes[nodeName] node.startKeySharing() def newNode(self, nodeName): if nodeName in self.nodes: print("Node {} already exists.\n".format(nodeName)) return if nodeName == "all": names = set(self.nodeReg.keys()) - set(self.nodes.keys()) elif nodeName not in self.nodeReg: tokens = [(Token.Error, "Invalid node name '{}'. ".format(nodeName))] self.printTokens(tokens) self.showValidNodes() return else: names = [nodeName] for name in names: node = Node(name, self.nodeReg, basedirpath=self.tmpdir) self.nodes[name] = node self.looper.add(node) node.startKeySharing() for client in self.clients.values(): self.bootstrapClientKey(client, node) def ensureValidClientId(self, clientId): """ Ensures client id is not already used or is not starting with node names. :param clientId: :return: """ if clientId in self.clients: raise ValueError("Client {} already exists.\n".format(clientId)) if any([clientId.startswith(nm) for nm in self.nodeNames]): raise ValueError("Client name cannot start with node names, " "which are {}\n" .format(', '.join(self.nodeReg.keys()))) def statusClient(self, clientId): if clientId == "all": for nm in self.clients: self.statusClient(nm) return if clientId not in self.clients: self.print("client not found", Token.Error) else: client = self.clients[clientId] # type: Client self.printTokens([(Token.Heading, 'Status for client:'), (Token.Name, client.name)], separator=' ', end='\n') self.print(" age (seconds): {:.0f}".format(time.perf_counter() - client.created)) self.print(" connected to: ", newline=False) if client._conns: self.printNames(client._conns, newline=True) else: self.print("<none>") self.print(" identifier: {}".format(client.signer.identifier)) self.print(" verification key: {}".format(client. signer.verkey)) self.print(" submissions: {}".format(client.lastReqId)) def statusNode(self, nodeName): if nodeName == "all": for nm in self.nodes: self.statusNode(nm) return if nodeName not in self.nodes: self.print("node not found", Token.Error) else: node = self.nodes[nodeName] # type: Node self.printTokens([(Token.Heading, 'Status for node:'), (Token.Name, node.name)], separator=' ', end='\n') self.print(" status: {}".format(node.status.name)) self.print(" age (seconds): {:.0f}". format(time.perf_counter() - node.created)) self.print(" connected nodes: ", newline=False) if node._conns: self.printNames(node._conns, newline=True) else: self.print("<none>") self.print(" connected clients: ", newline=False) clis = node.clientstack.connecteds() if clis: self.printNames(clis, newline=True) else: self.print("<none>") self.print(" client verification keys: {}". format(node.clientAuthNr.clients)) def newClient(self, clientId): try: self.ensureValidClientId(clientId) client_addr = self.getNextAvailableAddr() client = Client(clientId, ha=client_addr, nodeReg=self.cliNodeReg, basedirpath=self.tmpdir) self.looper.add(client) for node in self.nodes.values(): self.bootstrapClientKey(client, node) self.clients[clientId] = client self.clientWC.words = list(self.clients.keys()) except ValueError as ve: self.print(ve.args[0], Token.Error) @staticmethod def bootstrapClientKey(client, node): idAndKey = client.signer.identifier, client.signer.verkey node.clientAuthNr.addClient(*idAndKey) def sendMsg(self, clientName, msg): client = self.clients.get(clientName, None) if client: request, = client.submit(msg) self.requests[str(request.reqId)] = request.reqId else: print("No such client. See: 'help new' for more details") def getReply(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: reply, status = client.getReply(requestID) print("Reply for the request: {}\n".format(reply)) print("Status: {}\n".format(status)) elif not client: print("No such client. See: 'help new' for more details") else: print("No such request. See: 'help new' for more details") def showDetails(self, clientName, reqId): client = self.clients.get(clientName, None) requestID = self.requests.get(reqId, None) if client and requestID: client.showReplyDetails(requestID) else: print("No such client. See: 'help new' for more details") async def shell(self, *commands, interactive=True): """ Coroutine that runs command, including those from an interactive command line. :param commands: an iterable of commands to run first :param interactive: when True, this coroutine will process commands entered on the command line. :return: """ # First handle any commands passed in for command in commands: print("\nRunning command: '{}'...\n".format(command)) self.parse(command) # then handle commands from the prompt while interactive: try: result = await self.cli.run_async() self.parse(result.text) except (EOFError, KeyboardInterrupt, Exit): return print('Goodbye.') def parse(self, cmdText): m = self.grammar.match(cmdText) if m: matchedVars = m.variables() self.logger.info("CLI command entered: {}".format(cmdText), extra={"cli": False}) # Check for helper commands if matchedVars.get('simple'): cmd = matchedVars.get('simple') if cmd == "help": self.printHelp() elif cmd == 'status': self.getStatus() elif cmd == 'license': self.printCmdHelper('license') elif cmd in ['exit', 'quit']: raise Exit elif matchedVars.get('command') == 'help': arg1 = matchedVars.get('arg1') if arg1: self.printCmdHelper(command=arg1) else: self.printHelp() elif matchedVars.get('command') == 'list': for cmd in self.commands: print(cmd) # Check for new commands elif matchedVars.get('node_command') == 'new': name = matchedVars.get('node_name') self.newNode(name) elif matchedVars.get('node_command') == 'status': node = matchedVars.get('node_name') self.statusNode(node) elif matchedVars.get('node_command') == 'keyshare': name = matchedVars.get('node_name') self.keyshare(name) elif matchedVars.get('client_command') == 'new': client = matchedVars.get('client_name') self.newClient(client) elif matchedVars.get('client_command') == 'status': client = matchedVars.get('client_name') self.statusClient(client) elif matchedVars.get('client') == 'client': client_name = matchedVars.get('client_name') client_action = matchedVars.get('cli_action') if client_action == 'send': msg = matchedVars.get('msg') self.sendMsg(client_name, msg) elif client_action == 'show': req_id = matchedVars.get('req_id') self.getReply(client_name, req_id) else: self.printCmdHelper("sendmsg") # check for the showdetails commmand elif matchedVars.get('command') == 'showdetails': arg1 = matchedVars.get('arg1') arg2 = matchedVars.get('arg2') if arg1 and arg2: self.showDetails(arg1, arg2) else: self.printCmdHelper("showstatus") # Fall back to the help saying, invalid command. else: self.invalidCmd(cmdText) else: if cmdText != "": self.invalidCmd(cmdText) def invalidCmd(self, cmdText): print("Invalid command: '{}'\n".format(cmdText)) self.printCmdHelper(command=None) def getNextAvailableAddr(self): self.curClientPort = self.curClientPort or 8100 self.curClientPort += 1 return "127.0.0.1", self.curClientPort