1# -*- coding: utf-8 -*- 2"""The xonsh shell""" 3import sys 4import random 5import time 6import difflib 7import builtins 8import warnings 9 10from xonsh.platform import ( 11 best_shell_type, 12 has_prompt_toolkit, 13 ptk_above_min_supported, 14 ptk_shell_type, 15) 16from xonsh.tools import XonshError, print_exception 17from xonsh.events import events 18import xonsh.history.main as xhm 19 20 21events.doc( 22 "on_transform_command", 23 """ 24on_transform_command(cmd: str) -> str 25 26Fired to request xontribs to transform a command line. Return the transformed 27command, or the same command if no transformation occurs. Only done for 28interactive sessions. 29 30This may be fired multiple times per command, with other transformers input or 31output, so design any handlers for this carefully. 32""", 33) 34 35events.doc( 36 "on_precommand", 37 """ 38on_precommand(cmd: str) -> None 39 40Fires just before a command is executed. 41""", 42) 43 44events.doc( 45 "on_postcommand", 46 """ 47on_postcommand(cmd: str, rtn: int, out: str or None, ts: list) -> None 48 49Fires just after a command is executed. The arguments are the same as history. 50 51Parameters: 52 53* ``cmd``: The command that was executed (after transformation) 54* ``rtn``: The result of the command executed (``0`` for success) 55* ``out``: If xonsh stores command output, this is the output 56* ``ts``: Timestamps, in the order of ``[starting, ending]`` 57""", 58) 59 60events.doc( 61 "on_pre_prompt", 62 """ 63on_first_prompt() -> None 64 65Fires just before the prompt is shown 66""", 67) 68 69events.doc( 70 "on_post_prompt", 71 """ 72on_first_prompt() -> None 73 74Fires just after the prompt returns 75""", 76) 77 78 79def transform_command(src, show_diff=True): 80 """Returns the results of firing the precommand handles.""" 81 i = 0 82 limit = sys.getrecursionlimit() 83 lst = "" 84 raw = src 85 while src != lst: 86 lst = src 87 srcs = events.on_transform_command.fire(cmd=src) 88 for s in srcs: 89 if s != lst: 90 src = s 91 break 92 i += 1 93 if i == limit: 94 print_exception( 95 "Modifications to source input took more than " 96 "the recursion limit number of iterations to " 97 "converge." 98 ) 99 debug_level = builtins.__xonsh_env__.get("XONSH_DEBUG") 100 if show_diff and debug_level > 1 and src != raw: 101 sys.stderr.writelines( 102 difflib.unified_diff( 103 raw.splitlines(keepends=True), 104 src.splitlines(keepends=True), 105 fromfile="before precommand event", 106 tofile="after precommand event", 107 ) 108 ) 109 return src 110 111 112class Shell(object): 113 """Main xonsh shell. 114 115 Initializes execution environment and decides if prompt_toolkit or 116 readline version of shell should be used. 117 """ 118 119 shell_type_aliases = { 120 "b": "best", 121 "best": "best", 122 "ptk": "prompt_toolkit", 123 "ptk1": "prompt_toolkit1", 124 "ptk2": "prompt_toolkit2", 125 "prompt-toolkit": "prompt_toolkit", 126 "prompt_toolkit": "prompt_toolkit", 127 "prompt-toolkit1": "prompt_toolkit1", 128 "prompt-toolkit2": "prompt_toolkit2", 129 "rand": "random", 130 "random": "random", 131 "rl": "readline", 132 "readline": "readline", 133 } 134 135 def __init__(self, execer, ctx=None, shell_type=None, **kwargs): 136 """ 137 Parameters 138 ---------- 139 execer : Execer 140 An execer instance capable of running xonsh code. 141 ctx : Mapping, optional 142 The execution context for the shell (e.g. the globals namespace). 143 If none, this is computed by loading the rc files. If not None, 144 this no additional context is computed and this is used 145 directly. 146 shell_type : str, optional 147 The shell type to start, such as 'readline', 'prompt_toolkit1', 148 or 'random'. 149 """ 150 self.execer = execer 151 self.ctx = {} if ctx is None else ctx 152 env = builtins.__xonsh_env__ 153 # build history backend before creating shell 154 builtins.__xonsh_history__ = hist = xhm.construct_history( 155 env=env.detype(), ts=[time.time(), None], locked=True 156 ) 157 158 # pick a valid shell -- if no shell is specified by the user, 159 # shell type is pulled from env 160 if shell_type is None: 161 shell_type = env.get("SHELL_TYPE") 162 if shell_type == "none": 163 # This bricks interactive xonsh 164 # Can happen from the use of .xinitrc, .xsession, etc 165 shell_type = "best" 166 shell_type = self.shell_type_aliases.get(shell_type, shell_type) 167 if shell_type == "best" or shell_type is None: 168 shell_type = best_shell_type() 169 elif shell_type == "random": 170 shell_type = random.choice(("readline", "prompt_toolkit")) 171 if shell_type == "prompt_toolkit": 172 if not has_prompt_toolkit(): 173 warnings.warn( 174 "prompt_toolkit is not available, using " "readline instead." 175 ) 176 shell_type = "readline" 177 elif not ptk_above_min_supported(): 178 warnings.warn( 179 "prompt-toolkit version < v1.0.0 is not " 180 "supported. Please update prompt-toolkit. Using " 181 "readline instead." 182 ) 183 shell_type = "readline" 184 else: 185 shell_type = ptk_shell_type() 186 self.shell_type = env["SHELL_TYPE"] = shell_type 187 # actually make the shell 188 if shell_type == "none": 189 from xonsh.base_shell import BaseShell as shell_class 190 elif shell_type == "prompt_toolkit2": 191 from xonsh.ptk2.shell import PromptToolkit2Shell as shell_class 192 elif shell_type == "prompt_toolkit1": 193 from xonsh.ptk.shell import PromptToolkitShell as shell_class 194 elif shell_type == "readline": 195 from xonsh.readline_shell import ReadlineShell as shell_class 196 elif shell_type == "jupyter": 197 from xonsh.jupyter_shell import JupyterShell as shell_class 198 else: 199 raise XonshError("{} is not recognized as a shell type".format(shell_type)) 200 self.shell = shell_class(execer=self.execer, ctx=self.ctx, **kwargs) 201 # allows history garbage collector to start running 202 if hist.gc is not None: 203 hist.gc.wait_for_shell = False 204 205 def __getattr__(self, attr): 206 """Delegates calls to appropriate shell instance.""" 207 return getattr(self.shell, attr) 208