1"""Amalgamation of xonsh package, made up of the following modules, in order: 2 3* completer 4* contexts 5* lazyasd 6* lazyjson 7* platform 8* pretty 9* codecache 10* lazyimps 11* parser 12* tokenize 13* tools 14* ast 15* color_tools 16* commands_cache 17* diff_history 18* events 19* foreign_shells 20* jobs 21* jsonutils 22* lexer 23* openpy 24* xontribs 25* ansi_colors 26* dirstack 27* proc 28* shell 29* style_tools 30* timings 31* xonfig 32* base_shell 33* environ 34* inspectors 35* readline_shell 36* replay 37* tracer 38* aliases 39* built_ins 40* execer 41* imphooks 42* main 43 44""" 45 46from sys import modules as _modules 47from types import ModuleType as _ModuleType 48from importlib import import_module as _import_module 49 50 51class _LazyModule(_ModuleType): 52 53 def __init__(self, pkg, mod, asname=None): 54 '''Lazy module 'pkg.mod' in package 'pkg'.''' 55 self.__dct__ = { 56 'loaded': False, 57 'pkg': pkg, # pkg 58 'mod': mod, # pkg.mod 59 'asname': asname, # alias 60 } 61 62 @classmethod 63 def load(cls, pkg, mod, asname=None): 64 if mod in _modules: 65 key = pkg if asname is None else mod 66 return _modules[key] 67 else: 68 return cls(pkg, mod, asname) 69 70 def __getattribute__(self, name): 71 if name == '__dct__': 72 return super(_LazyModule, self).__getattribute__(name) 73 dct = self.__dct__ 74 mod = dct['mod'] 75 if dct['loaded']: 76 m = _modules[mod] 77 else: 78 m = _import_module(mod) 79 glbs = globals() 80 pkg = dct['pkg'] 81 asname = dct['asname'] 82 if asname is None: 83 glbs[pkg] = m = _modules[pkg] 84 else: 85 glbs[asname] = m 86 dct['loaded'] = True 87 return getattr(m, name) 88 89# 90# completer 91# 92# -*- coding: utf-8 -*- 93"""A (tab-)completer for xonsh.""" 94builtins = _LazyModule.load('builtins', 'builtins') 95cabc = _LazyModule.load('collections', 'collections.abc', 'cabc') 96class Completer(object): 97 """This provides a list of optional completions for the xonsh shell.""" 98 99 def complete(self, prefix, line, begidx, endidx, ctx=None): 100 """Complete the string, given a possible execution context. 101 102 Parameters 103 ---------- 104 prefix : str 105 The string to match 106 line : str 107 The line that prefix appears on. 108 begidx : int 109 The index in line that prefix starts on. 110 endidx : int 111 The index in line that prefix ends on. 112 ctx : Iterable of str (ie dict, set, etc), optional 113 Names in the current execution context. 114 115 Returns 116 ------- 117 rtn : list of str 118 Possible completions of prefix, sorted alphabetically. 119 lprefix : int 120 Length of the prefix to be replaced in the completion. 121 """ 122 ctx = ctx or {} 123 for func in builtins.__xonsh_completers__.values(): 124 try: 125 out = func(prefix, line, begidx, endidx, ctx) 126 except StopIteration: 127 return set(), len(prefix) 128 if isinstance(out, cabc.Sequence): 129 res, lprefix = out 130 else: 131 res = out 132 lprefix = len(prefix) 133 if res is not None and len(res) != 0: 134 135 def sortkey(s): 136 return s.lstrip(''''"''').lower() 137 138 return tuple(sorted(res, key=sortkey)), lprefix 139 return set(), lprefix 140 141# 142# contexts 143# 144"""Context management tools for xonsh.""" 145sys = _LazyModule.load('sys', 'sys') 146textwrap = _LazyModule.load('textwrap', 'textwrap') 147# amalgamated builtins 148from collections.abc import Mapping 149 150 151class Block(object): 152 """This is a context manager for obtaining a block of lines without actually 153 executing the block. The lines are accessible as the 'lines' attribute. 154 This must be used as a macro. 155 """ 156 157 __xonsh_block__ = str 158 159 def __init__(self): 160 """ 161 Attributes 162 ---------- 163 lines : list of str or None 164 Block lines as if split by str.splitlines(), if available. 165 glbs : Mapping or None 166 Global execution context, ie globals(). 167 locs : Mapping or None 168 Local execution context, ie locals(). 169 """ 170 self.lines = self.glbs = self.locs = None 171 172 def __enter__(self): 173 if not hasattr(self, "macro_block"): 174 raise XonshError(self.__class__.__name__ + " must be entered as a macro!") 175 self.lines = self.macro_block.splitlines() 176 self.glbs = self.macro_globals 177 if self.macro_locals is not self.macro_globals: 178 # leave locals as None when it is the same as globals 179 self.locs = self.macro_locals 180 return self 181 182 def __exit__(self, exc_type, exc_value, traceback): 183 pass 184 185 186class Functor(Block): 187 """This is a context manager that turns the block into a callable 188 object, bound to the execution context it was created in. 189 """ 190 191 def __init__(self, args=(), kwargs=None, rtn=""): 192 """ 193 Parameters 194 ---------- 195 args : Sequence of str, optional 196 A tuple of argument names for the functor. 197 kwargs : Mapping of str to values or list of item tuples, optional 198 Keyword argument names and values, if available. 199 rtn : str, optional 200 Name of object to return, if available. 201 202 Attributes 203 ---------- 204 func : function 205 The underlying function object. This defaults to none and is set 206 after the the block is exited. 207 """ 208 super().__init__() 209 self.func = None 210 self.args = args 211 if kwargs is None: 212 self.kwargs = [] 213 elif isinstance(kwargs, Mapping): 214 self.kwargs = sorted(kwargs.items()) 215 else: 216 self.kwargs = kwargs 217 self.rtn = rtn 218 219 def __enter__(self): 220 super().__enter__() 221 body = textwrap.indent(self.macro_block, " ") 222 uid = hash(body) + sys.maxsize # should always be a positive int 223 name = "__xonsh_functor_{uid}__".format(uid=uid) 224 # construct signature string 225 sig = rtn = "" 226 sig = ", ".join(self.args) 227 kwstr = ", ".join([k + "=None" for k, _ in self.kwargs]) 228 if len(kwstr) > 0: 229 sig = kwstr if len(sig) == 0 else sig + ", " + kwstr 230 # construct return string 231 rtn = str(self.rtn) 232 if len(rtn) > 0: 233 rtn = " return " + rtn + "\n" 234 # construct function string 235 fstr = "def {name}({sig}):\n{body}\n{rtn}" 236 fstr = fstr.format(name=name, sig=sig, body=body, rtn=rtn) 237 glbs = self.glbs 238 locs = self.locs 239 execer = builtins.__xonsh_execer__ 240 execer.exec(fstr, glbs=glbs, locs=locs) 241 if locs is not None and name in locs: 242 func = locs[name] 243 elif name in glbs: 244 func = glbs[name] 245 else: 246 raise ValueError("Functor block could not be found in context.") 247 if len(self.kwargs) > 0: 248 func.__defaults__ = tuple(v for _, v in self.kwargs) 249 self.func = func 250 return self 251 252 def __exit__(self, exc_type, exc_value, traceback): 253 pass 254 255 def __call__(self, *args, **kwargs): 256 """Dispatches to func.""" 257 if self.func is None: 258 msg = "{} block with 'None' func not callable" 259 raise AttributeError(msg.formst(self.__class__.__name__)) 260 return self.func(*args, **kwargs) 261 262# 263# lazyasd 264# 265"""Lazy and self destructive containers for speeding up module import.""" 266# Copyright 2015-2016, the xonsh developers. All rights reserved. 267os = _LazyModule.load('os', 'os') 268# amalgamated sys 269time = _LazyModule.load('time', 'time') 270types = _LazyModule.load('types', 'types') 271# amalgamated builtins 272threading = _LazyModule.load('threading', 'threading') 273importlib = _LazyModule.load('importlib', 'importlib') 274importlib = _LazyModule.load('importlib', 'importlib.util') 275# amalgamated collections.abc 276__version__ = "0.1.3" 277 278 279class LazyObject(object): 280 def __init__(self, load, ctx, name): 281 """Lazily loads an object via the load function the first time an 282 attribute is accessed. Once loaded it will replace itself in the 283 provided context (typically the globals of the call site) with the 284 given name. 285 286 For example, you can prevent the compilation of a regular expression 287 until it is actually used:: 288 289 DOT = LazyObject((lambda: re.compile('.')), globals(), 'DOT') 290 291 Parameters 292 ---------- 293 load : function with no arguments 294 A loader function that performs the actual object construction. 295 ctx : Mapping 296 Context to replace the LazyObject instance in 297 with the object returned by load(). 298 name : str 299 Name in the context to give the loaded object. This *should* 300 be the name on the LHS of the assignment. 301 """ 302 self._lasdo = {"loaded": False, "load": load, "ctx": ctx, "name": name} 303 304 def _lazy_obj(self): 305 d = self._lasdo 306 if d["loaded"]: 307 obj = d["obj"] 308 else: 309 obj = d["load"]() 310 d["ctx"][d["name"]] = d["obj"] = obj 311 d["loaded"] = True 312 return obj 313 314 def __getattribute__(self, name): 315 if name == "_lasdo" or name == "_lazy_obj": 316 return super().__getattribute__(name) 317 obj = self._lazy_obj() 318 return getattr(obj, name) 319 320 def __bool__(self): 321 obj = self._lazy_obj() 322 return bool(obj) 323 324 def __iter__(self): 325 obj = self._lazy_obj() 326 yield from obj 327 328 def __getitem__(self, item): 329 obj = self._lazy_obj() 330 return obj[item] 331 332 def __setitem__(self, key, value): 333 obj = self._lazy_obj() 334 obj[key] = value 335 336 def __delitem__(self, item): 337 obj = self._lazy_obj() 338 del obj[item] 339 340 def __call__(self, *args, **kwargs): 341 obj = self._lazy_obj() 342 return obj(*args, **kwargs) 343 344 def __lt__(self, other): 345 obj = self._lazy_obj() 346 return obj < other 347 348 def __le__(self, other): 349 obj = self._lazy_obj() 350 return obj <= other 351 352 def __eq__(self, other): 353 obj = self._lazy_obj() 354 return obj == other 355 356 def __ne__(self, other): 357 obj = self._lazy_obj() 358 return obj != other 359 360 def __gt__(self, other): 361 obj = self._lazy_obj() 362 return obj > other 363 364 def __ge__(self, other): 365 obj = self._lazy_obj() 366 return obj >= other 367 368 def __hash__(self): 369 obj = self._lazy_obj() 370 return hash(obj) 371 372 def __or__(self, other): 373 obj = self._lazy_obj() 374 return obj | other 375 376 def __str__(self): 377 return str(self._lazy_obj()) 378 379 def __repr__(self): 380 return repr(self._lazy_obj()) 381 382 383def lazyobject(f): 384 """Decorator for constructing lazy objects from a function.""" 385 return LazyObject(f, f.__globals__, f.__name__) 386 387 388class LazyDict(cabc.MutableMapping): 389 def __init__(self, loaders, ctx, name): 390 """Dictionary like object that lazily loads its values from an initial 391 dict of key-loader function pairs. Each key is loaded when its value 392 is first accessed. Once fully loaded, this object will replace itself 393 in the provided context (typically the globals of the call site) with 394 the given name. 395 396 For example, you can prevent the compilation of a bunch of regular 397 expressions until they are actually used:: 398 399 RES = LazyDict({ 400 'dot': lambda: re.compile('.'), 401 'all': lambda: re.compile('.*'), 402 'two': lambda: re.compile('..'), 403 }, globals(), 'RES') 404 405 Parameters 406 ---------- 407 loaders : Mapping of keys to functions with no arguments 408 A mapping of loader function that performs the actual value 409 construction upon access. 410 ctx : Mapping 411 Context to replace the LazyDict instance in 412 with the the fully loaded mapping. 413 name : str 414 Name in the context to give the loaded mapping. This *should* 415 be the name on the LHS of the assignment. 416 """ 417 self._loaders = loaders 418 self._ctx = ctx 419 self._name = name 420 self._d = type(loaders)() # make sure to return the same type 421 422 def _destruct(self): 423 if len(self._loaders) == 0: 424 self._ctx[self._name] = self._d 425 426 def __getitem__(self, key): 427 d = self._d 428 if key in d: 429 val = d[key] 430 else: 431 # pop will raise a key error for us 432 loader = self._loaders.pop(key) 433 d[key] = val = loader() 434 self._destruct() 435 return val 436 437 def __setitem__(self, key, value): 438 self._d[key] = value 439 if key in self._loaders: 440 del self._loaders[key] 441 self._destruct() 442 443 def __delitem__(self, key): 444 if key in self._d: 445 del self._d[key] 446 else: 447 del self._loaders[key] 448 self._destruct() 449 450 def __iter__(self): 451 yield from (set(self._d.keys()) | set(self._loaders.keys())) 452 453 def __len__(self): 454 return len(self._d) + len(self._loaders) 455 456 457def lazydict(f): 458 """Decorator for constructing lazy dicts from a function.""" 459 return LazyDict(f, f.__globals__, f.__name__) 460 461 462class LazyBool(object): 463 def __init__(self, load, ctx, name): 464 """Boolean like object that lazily computes it boolean value when it is 465 first asked. Once loaded, this result will replace itself 466 in the provided context (typically the globals of the call site) with 467 the given name. 468 469 For example, you can prevent the complex boolean until it is actually 470 used:: 471 472 ALIVE = LazyDict(lambda: not DEAD, globals(), 'ALIVE') 473 474 Parameters 475 ---------- 476 load : function with no arguments 477 A loader function that performs the actual boolean evaluation. 478 ctx : Mapping 479 Context to replace the LazyBool instance in 480 with the the fully loaded mapping. 481 name : str 482 Name in the context to give the loaded mapping. This *should* 483 be the name on the LHS of the assignment. 484 """ 485 self._load = load 486 self._ctx = ctx 487 self._name = name 488 self._result = None 489 490 def __bool__(self): 491 if self._result is None: 492 res = self._ctx[self._name] = self._result = self._load() 493 else: 494 res = self._result 495 return res 496 497 498def lazybool(f): 499 """Decorator for constructing lazy booleans from a function.""" 500 return LazyBool(f, f.__globals__, f.__name__) 501 502 503# 504# Background module loaders 505# 506 507 508class BackgroundModuleProxy(types.ModuleType): 509 """Proxy object for modules loaded in the background that block attribute 510 access until the module is loaded.. 511 """ 512 513 def __init__(self, modname): 514 self.__dct__ = {"loaded": False, "modname": modname} 515 516 def __getattribute__(self, name): 517 passthrough = frozenset({"__dct__", "__class__", "__spec__"}) 518 if name in passthrough: 519 return super().__getattribute__(name) 520 dct = self.__dct__ 521 modname = dct["modname"] 522 if dct["loaded"]: 523 mod = sys.modules[modname] 524 else: 525 delay_types = (BackgroundModuleProxy, type(None)) 526 while isinstance(sys.modules.get(modname, None), delay_types): 527 time.sleep(0.001) 528 mod = sys.modules[modname] 529 dct["loaded"] = True 530 # some modules may do construction after import, give them a second 531 stall = 0 532 while not hasattr(mod, name) and stall < 1000: 533 stall += 1 534 time.sleep(0.001) 535 return getattr(mod, name) 536 537 538class BackgroundModuleLoader(threading.Thread): 539 """Thread to load modules in the background.""" 540 541 def __init__(self, name, package, replacements, *args, **kwargs): 542 super().__init__(*args, **kwargs) 543 self.daemon = True 544 self.name = name 545 self.package = package 546 self.replacements = replacements 547 self.start() 548 549 def run(self): 550 # wait for other modules to stop being imported 551 # We assume that module loading is finished when sys.modules doesn't 552 # get longer in 5 consecutive 1ms waiting steps 553 counter = 0 554 last = -1 555 while counter < 5: 556 new = len(sys.modules) 557 if new == last: 558 counter += 1 559 else: 560 last = new 561 counter = 0 562 time.sleep(0.001) 563 # now import module properly 564 modname = importlib.util.resolve_name(self.name, self.package) 565 if isinstance(sys.modules[modname], BackgroundModuleProxy): 566 del sys.modules[modname] 567 mod = importlib.import_module(self.name, package=self.package) 568 for targname, varname in self.replacements.items(): 569 if targname in sys.modules: 570 targmod = sys.modules[targname] 571 setattr(targmod, varname, mod) 572 573 574def load_module_in_background( 575 name, package=None, debug="DEBUG", env=None, replacements=None 576): 577 """Entry point for loading modules in background thread. 578 579 Parameters 580 ---------- 581 name : str 582 Module name to load in background thread. 583 package : str or None, optional 584 Package name, has the same meaning as in importlib.import_module(). 585 debug : str, optional 586 Debugging symbol name to look up in the environment. 587 env : Mapping or None, optional 588 Environment this will default to __xonsh_env__, if available, and 589 os.environ otherwise. 590 replacements : Mapping or None, optional 591 Dictionary mapping fully qualified module names (eg foo.bar.baz) that 592 import the lazily loaded module, with the variable name in that 593 module. For example, suppose that foo.bar imports module a as b, 594 this dict is then {'foo.bar': 'b'}. 595 596 Returns 597 ------- 598 module : ModuleType 599 This is either the original module that is found in sys.modules or 600 a proxy module that will block until delay attribute access until the 601 module is fully loaded. 602 """ 603 modname = importlib.util.resolve_name(name, package) 604 if modname in sys.modules: 605 return sys.modules[modname] 606 if env is None: 607 env = getattr(builtins, "__xonsh_env__", os.environ) 608 if env.get(debug, None): 609 mod = importlib.import_module(name, package=package) 610 return mod 611 proxy = sys.modules[modname] = BackgroundModuleProxy(modname) 612 BackgroundModuleLoader(name, package, replacements or {}) 613 return proxy 614 615# 616# lazyjson 617# 618# -*- coding: utf-8 -*- 619"""Implements a lazy JSON file class that wraps around json data.""" 620io = _LazyModule.load('io', 'io') 621json = _LazyModule.load('json', 'json') 622weakref = _LazyModule.load('weakref', 'weakref') 623contextlib = _LazyModule.load('contextlib', 'contextlib') 624# amalgamated collections.abc 625def _to_json_with_size(obj, offset=0, sort_keys=False): 626 if isinstance(obj, str): 627 s = json.dumps(obj) 628 o = offset 629 n = size = len(s.encode()) # size in bytes 630 elif isinstance(obj, cabc.Mapping): 631 s = "{" 632 j = offset + 1 633 o = {} 634 size = {} 635 items = sorted(obj.items()) if sort_keys else obj.items() 636 for key, val in items: 637 s_k, o_k, n_k, size_k = _to_json_with_size( 638 key, offset=j, sort_keys=sort_keys 639 ) 640 s += s_k + ": " 641 j += n_k + 2 642 s_v, o_v, n_v, size_v = _to_json_with_size( 643 val, offset=j, sort_keys=sort_keys 644 ) 645 o[key] = o_v 646 size[key] = size_v 647 s += s_v + ", " 648 j += n_v + 2 649 if s.endswith(", "): 650 s = s[:-2] 651 s += "}\n" 652 n = len(s) 653 o["__total__"] = offset 654 size["__total__"] = n 655 elif isinstance(obj, cabc.Sequence): 656 s = "[" 657 j = offset + 1 658 o = [] 659 size = [] 660 for x in obj: 661 s_x, o_x, n_x, size_x = _to_json_with_size(x, offset=j, sort_keys=sort_keys) 662 o.append(o_x) 663 size.append(size_x) 664 s += s_x + ", " 665 j += n_x + 2 666 if s.endswith(", "): 667 s = s[:-2] 668 s += "]\n" 669 n = len(s) 670 o.append(offset) 671 size.append(n) 672 else: 673 s = json.dumps(obj, sort_keys=sort_keys) 674 o = offset 675 n = size = len(s) 676 return s, o, n, size 677 678 679def index(obj, sort_keys=False): 680 """Creates an index for a JSON file.""" 681 idx = {} 682 json_obj = _to_json_with_size(obj, sort_keys=sort_keys) 683 s, idx["offsets"], _, idx["sizes"] = json_obj 684 return s, idx 685 686 687JSON_FORMAT = """{{"locs": [{iloc:>10}, {ilen:>10}, {dloc:>10}, {dlen:>10}], 688 "index": {index}, 689 "data": {data} 690}} 691""" 692 693 694def dumps(obj, sort_keys=False): 695 """Dumps an object to JSON with an index.""" 696 data, idx = index(obj, sort_keys=sort_keys) 697 jdx = json.dumps(idx, sort_keys=sort_keys) 698 iloc = 69 699 ilen = len(jdx) 700 dloc = iloc + ilen + 11 701 dlen = len(data) 702 s = JSON_FORMAT.format( 703 index=jdx, data=data, iloc=iloc, ilen=ilen, dloc=dloc, dlen=dlen 704 ) 705 return s 706 707 708def ljdump(obj, fp, sort_keys=False): 709 """Dumps an object to JSON file.""" 710 s = dumps(obj, sort_keys=sort_keys) 711 fp.write(s) 712 713 714class LJNode(cabc.Mapping, cabc.Sequence): 715 """A proxy node for JSON nodes. Acts as both sequence and mapping.""" 716 717 def __init__(self, offsets, sizes, root): 718 """Parameters 719 ---------- 720 offsets : dict, list, or int 721 offsets of corresponding data structure, in bytes 722 sizes : dict, list, or int 723 sizes of corresponding data structure, in bytes 724 root : weakref.proxy of LazyJSON 725 weakref back to root node, which should be a LazyJSON object. 726 """ 727 self.offsets = offsets 728 self.sizes = sizes 729 self.root = root 730 self.is_mapping = isinstance(self.offsets, cabc.Mapping) 731 self.is_sequence = isinstance(self.offsets, cabc.Sequence) 732 733 def __len__(self): 734 # recall that for maps, the '__total__' key is added and for 735 # sequences the last element represents the total size/offset. 736 return len(self.sizes) - 1 737 738 def load(self): 739 """Returns the Python data structure represented by the node.""" 740 if self.is_mapping: 741 offset = self.offsets["__total__"] 742 size = self.sizes["__total__"] 743 elif self.is_sequence: 744 offset = self.offsets[-1] 745 size = self.sizes[-1] 746 elif isinstance(self.offsets, int): 747 offset = self.offsets 748 size = self.sizes 749 return self._load_or_node(offset, size) 750 751 def _load_or_node(self, offset, size): 752 if isinstance(offset, int): 753 with self.root._open(newline="\n") as f: 754 f.seek(self.root.dloc + offset) 755 s = f.read(size) 756 val = json.loads(s) 757 elif isinstance(offset, (cabc.Mapping, cabc.Sequence)): 758 val = LJNode(offset, size, self.root) 759 else: 760 raise TypeError("incorrect types for offset node") 761 return val 762 763 def _getitem_mapping(self, key): 764 if key == "__total__": 765 raise KeyError('"__total__" is a special LazyJSON key!') 766 offset = self.offsets[key] 767 size = self.sizes[key] 768 return self._load_or_node(offset, size) 769 770 def _getitem_sequence(self, key): 771 if isinstance(key, int): 772 rtn = self._load_or_node(self.offsets[key], self.sizes[key]) 773 elif isinstance(key, slice): 774 key = slice(*key.indices(len(self))) 775 rtn = list(map(self._load_or_node, self.offsets[key], self.sizes[key])) 776 else: 777 raise TypeError("only integer indexing available") 778 return rtn 779 780 def __getitem__(self, key): 781 if self.is_mapping: 782 rtn = self._getitem_mapping(key) 783 elif self.is_sequence: 784 rtn = self._getitem_sequence(key) 785 else: 786 raise NotImplementedError 787 return rtn 788 789 def __iter__(self): 790 if self.is_mapping: 791 keys = set(self.offsets.keys()) 792 keys.discard("__total__") 793 yield from iter(keys) 794 elif self.is_sequence: 795 i = 0 796 n = len(self) 797 while i < n: 798 yield self._load_or_node(self.offsets[i], self.sizes[i]) 799 i += 1 800 else: 801 raise NotImplementedError 802 803 804class LazyJSON(LJNode): 805 """Represents a lazy json file. Can be used like a normal Python 806 dict or list. 807 """ 808 809 def __init__(self, f, reopen=True): 810 """Parameters 811 ---------- 812 f : file handle or str 813 JSON file to open. 814 reopen : bool, optional 815 Whether new file handle should be opened for each load. 816 """ 817 self._f = f 818 self.reopen = reopen 819 if not reopen and isinstance(f, str): 820 self._f = open(f, "r", newline="\n") 821 self._load_index() 822 self.root = weakref.proxy(self) 823 self.is_mapping = isinstance(self.offsets, cabc.Mapping) 824 self.is_sequence = isinstance(self.offsets, cabc.Sequence) 825 826 def __del__(self): 827 self.close() 828 829 def close(self): 830 """Close the file handle, if appropriate.""" 831 if not self.reopen and isinstance(self._f, io.IOBase): 832 try: 833 self._f.close() 834 except OSError: 835 pass 836 837 @contextlib.contextmanager 838 def _open(self, *args, **kwargs): 839 if self.reopen and isinstance(self._f, str): 840 f = open(self._f, *args, **kwargs) 841 yield f 842 f.close() 843 else: 844 yield self._f 845 846 def _load_index(self): 847 """Loads the index from the start of the file.""" 848 with self._open(newline="\n") as f: 849 # read in the location data 850 f.seek(9) 851 locs = f.read(48) 852 locs = json.loads(locs) 853 self.iloc, self.ilen, self.dloc, self.dlen = locs 854 # read in the index 855 f.seek(self.iloc) 856 idx = f.read(self.ilen) 857 idx = json.loads(idx) 858 self.offsets = idx["offsets"] 859 self.sizes = idx["sizes"] 860 861 def __enter__(self): 862 return self 863 864 def __exit__(self, exc_type, exc_value, traceback): 865 self.close() 866 867# 868# platform 869# 870"""Module for platform-specific constants and implementations, as well as 871compatibility layers to make use of the 'best' implementation available 872on a platform. 873""" 874# amalgamated os 875# amalgamated sys 876ctypes = _LazyModule.load('ctypes', 'ctypes') 877signal = _LazyModule.load('signal', 'signal') 878pathlib = _LazyModule.load('pathlib', 'pathlib') 879# amalgamated builtins 880platform = _LazyModule.load('platform', 'platform') 881functools = _LazyModule.load('functools', 'functools') 882subprocess = _LazyModule.load('subprocess', 'subprocess') 883collections = _LazyModule.load('collections', 'collections') 884# amalgamated importlib.util 885# amalgamated xonsh.lazyasd 886FD_STDIN = 0 887FD_STDOUT = 1 888FD_STDERR = 2 889 890 891@lazyobject 892def distro(): 893 try: 894 import distro as d 895 except ImportError: 896 d = None 897 except Exception: 898 raise 899 return d 900 901 902# 903# OS 904# 905ON_DARWIN = LazyBool(lambda: platform.system() == "Darwin", globals(), "ON_DARWIN") 906"""``True`` if executed on a Darwin platform, else ``False``. """ 907ON_LINUX = LazyBool(lambda: platform.system() == "Linux", globals(), "ON_LINUX") 908"""``True`` if executed on a Linux platform, else ``False``. """ 909ON_WINDOWS = LazyBool(lambda: platform.system() == "Windows", globals(), "ON_WINDOWS") 910"""``True`` if executed on a native Windows platform, else ``False``. """ 911ON_CYGWIN = LazyBool(lambda: sys.platform == "cygwin", globals(), "ON_CYGWIN") 912"""``True`` if executed on a Cygwin Windows platform, else ``False``. """ 913ON_MSYS = LazyBool(lambda: sys.platform == "msys", globals(), "ON_MSYS") 914"""``True`` if executed on a MSYS Windows platform, else ``False``. """ 915ON_POSIX = LazyBool(lambda: (os.name == "posix"), globals(), "ON_POSIX") 916"""``True`` if executed on a POSIX-compliant platform, else ``False``. """ 917ON_FREEBSD = LazyBool( 918 lambda: (sys.platform.startswith("freebsd")), globals(), "ON_FREEBSD" 919) 920"""``True`` if on a FreeBSD operating system, else ``False``.""" 921ON_NETBSD = LazyBool( 922 lambda: (sys.platform.startswith("netbsd")), globals(), "ON_NETBSD" 923) 924"""``True`` if on a NetBSD operating system, else ``False``.""" 925 926 927@lazybool 928def ON_BSD(): 929 """``True`` if on a BSD operating system, else ``False``.""" 930 return bool(ON_FREEBSD) or bool(ON_NETBSD) 931 932 933@lazybool 934def ON_BEOS(): 935 """True if we are on BeOS or Haiku.""" 936 return sys.platform == "beos5" or sys.platform == "haiku1" 937 938 939# 940# Python & packages 941# 942 943PYTHON_VERSION_INFO = sys.version_info[:3] 944""" Version of Python interpreter as three-value tuple. """ 945 946 947@lazyobject 948def PYTHON_VERSION_INFO_BYTES(): 949 """The python version info tuple in a canonical bytes form.""" 950 return ".".join(map(str, sys.version_info)).encode() 951 952 953ON_ANACONDA = LazyBool( 954 lambda: any(s in sys.version for s in {"Anaconda", "Continuum", "conda-forge"}), 955 globals(), 956 "ON_ANACONDA", 957) 958""" ``True`` if executed in an Anaconda instance, else ``False``. """ 959CAN_RESIZE_WINDOW = LazyBool( 960 lambda: hasattr(signal, "SIGWINCH"), globals(), "CAN_RESIZE_WINDOW" 961) 962"""``True`` if we can resize terminal window, as provided by the presense of 963signal.SIGWINCH, else ``False``. 964""" 965 966 967@lazybool 968def HAS_PYGMENTS(): 969 """``True`` if `pygments` is available, else ``False``.""" 970 spec = importlib.util.find_spec("pygments") 971 return spec is not None 972 973 974@functools.lru_cache(1) 975def pygments_version(): 976 """pygments.__version__ version if available, else None.""" 977 if HAS_PYGMENTS: 978 import pygments 979 980 v = pygments.__version__ 981 else: 982 v = None 983 return v 984 985 986@functools.lru_cache(1) 987def pygments_version_info(): 988 """ Returns `pygments`'s version as tuple of integers. """ 989 if HAS_PYGMENTS: 990 return tuple(int(x) for x in pygments_version().strip("<>+-=.").split(".")) 991 else: 992 return None 993 994 995@functools.lru_cache(1) 996def has_prompt_toolkit(): 997 """Tests if the `prompt_toolkit` is available.""" 998 spec = importlib.util.find_spec("prompt_toolkit") 999 return spec is not None 1000 1001 1002@functools.lru_cache(1) 1003def ptk_version(): 1004 """Returns `prompt_toolkit.__version__` if available, else ``None``.""" 1005 if has_prompt_toolkit(): 1006 import prompt_toolkit 1007 1008 return getattr(prompt_toolkit, "__version__", "<0.57") 1009 else: 1010 return None 1011 1012 1013@functools.lru_cache(1) 1014def ptk_version_info(): 1015 """ Returns `prompt_toolkit`'s version as tuple of integers. """ 1016 if has_prompt_toolkit(): 1017 return tuple(int(x) for x in ptk_version().strip("<>+-=.").split(".")) 1018 else: 1019 return None 1020 1021 1022@functools.lru_cache(1) 1023def ptk_above_min_supported(): 1024 minimum_required_ptk_version = (1, 0) 1025 return ptk_version_info()[:2] >= minimum_required_ptk_version 1026 1027 1028@functools.lru_cache(1) 1029def ptk_shell_type(): 1030 """Returns the prompt_toolkit shell type based on the installed version.""" 1031 if ptk_version_info()[:2] < (2, 0): 1032 return "prompt_toolkit1" 1033 else: 1034 return "prompt_toolkit2" 1035 1036 1037@functools.lru_cache(1) 1038def win_ansi_support(): 1039 if ON_WINDOWS: 1040 try: 1041 from prompt_toolkit.utils import is_windows_vt100_supported, is_conemu_ansi 1042 except ImportError: 1043 return False 1044 return is_conemu_ansi() or is_windows_vt100_supported() 1045 else: 1046 return False 1047 1048 1049@functools.lru_cache(1) 1050def ptk_below_max_supported(): 1051 ptk_max_version_cutoff = (2, 0) 1052 return ptk_version_info()[:2] < ptk_max_version_cutoff 1053 1054 1055@functools.lru_cache(1) 1056def best_shell_type(): 1057 if ON_WINDOWS or has_prompt_toolkit(): 1058 return "prompt_toolkit" 1059 else: 1060 return "readline" 1061 1062 1063@functools.lru_cache(1) 1064def is_readline_available(): 1065 """Checks if readline is available to import.""" 1066 spec = importlib.util.find_spec("readline") 1067 return spec is not None 1068 1069 1070@lazyobject 1071def seps(): 1072 """String of all path separators.""" 1073 s = os.path.sep 1074 if os.path.altsep is not None: 1075 s += os.path.altsep 1076 return s 1077 1078 1079def pathsplit(p): 1080 """This is a safe version of os.path.split(), which does not work on input 1081 without a drive. 1082 """ 1083 n = len(p) 1084 while n and p[n - 1] not in seps: 1085 n -= 1 1086 pre = p[:n] 1087 pre = pre.rstrip(seps) or pre 1088 post = p[n:] 1089 return pre, post 1090 1091 1092def pathbasename(p): 1093 """This is a safe version of os.path.basename(), which does not work on 1094 input without a drive. This version does. 1095 """ 1096 return pathsplit(p)[-1] 1097 1098 1099@lazyobject 1100def expanduser(): 1101 """Dispatches to the correct platform-dependent expanduser() function.""" 1102 if ON_WINDOWS: 1103 return windows_expanduser 1104 else: 1105 return os.path.expanduser 1106 1107 1108def windows_expanduser(path): 1109 """A Windows-specific expanduser() function for xonsh. This is needed 1110 since os.path.expanduser() does not check on Windows if the user actually 1111 exists. This restricts expanding the '~' if it is not followed by a 1112 separator. That is only '~/' and '~\' are expanded. 1113 """ 1114 if not path.startswith("~"): 1115 return path 1116 elif len(path) < 2 or path[1] in seps: 1117 return os.path.expanduser(path) 1118 else: 1119 return path 1120 1121 1122# termios tc(get|set)attr indexes. 1123IFLAG = 0 1124OFLAG = 1 1125CFLAG = 2 1126LFLAG = 3 1127ISPEED = 4 1128OSPEED = 5 1129CC = 6 1130 1131 1132# 1133# Dev release info 1134# 1135 1136 1137@functools.lru_cache(1) 1138def githash(): 1139 """Returns a tuple contains two strings: the hash and the date.""" 1140 install_base = os.path.dirname(__file__) 1141 githash_file = "{}/dev.githash".format(install_base) 1142 if not os.path.exists(githash_file): 1143 return None, None 1144 sha = None 1145 date_ = None 1146 try: 1147 with open(githash_file) as f: 1148 sha, date_ = f.read().strip().split("|") 1149 except ValueError: 1150 pass 1151 return sha, date_ 1152 1153 1154# 1155# Encoding 1156# 1157 1158DEFAULT_ENCODING = sys.getdefaultencoding() 1159""" Default string encoding. """ 1160 1161 1162if PYTHON_VERSION_INFO < (3, 5, 0): 1163 1164 class DirEntry: 1165 def __init__(self, directory, name): 1166 self.__path__ = pathlib.Path(directory) / name 1167 self.name = name 1168 self.path = str(self.__path__) 1169 self.is_symlink = self.__path__.is_symlink 1170 1171 def inode(self): 1172 return os.stat(self.path, follow_symlinks=False).st_ino 1173 1174 def is_dir(self, *, follow_symlinks=True): 1175 if follow_symlinks: 1176 return self.__path__.is_dir() 1177 else: 1178 return not self.__path__.is_symlink() and self.__path__.is_dir() 1179 1180 def is_file(self, *, follow_symlinks=True): 1181 if follow_symlinks: 1182 return self.__path__.is_file() 1183 else: 1184 return not self.__path__.is_symlink() and self.__path__.is_file() 1185 1186 def stat(self, *, follow_symlinks=True): 1187 return os.stat(self.path, follow_symlinks=follow_symlinks) 1188 1189 def scandir(path): 1190 """ Compatibility layer for `os.scandir` from Python 3.5+. """ 1191 return (DirEntry(path, x) for x in os.listdir(path)) 1192 1193 1194else: 1195 scandir = os.scandir 1196 1197 1198# 1199# Linux distro 1200# 1201 1202 1203@functools.lru_cache(1) 1204def linux_distro(): 1205 """The id of the Linux distribution running on, possibly 'unknown'. 1206 None on non-Linux platforms. 1207 """ 1208 if ON_LINUX: 1209 if distro: 1210 ld = distro.id() 1211 elif PYTHON_VERSION_INFO < (3, 7, 0): 1212 ld = platform.linux_distribution()[0] or "unknown" 1213 elif "-ARCH-" in platform.platform(): 1214 ld = "arch" # that's the only one we need to know for now 1215 else: 1216 ld = "unknown" 1217 else: 1218 ld = None 1219 return ld 1220 1221 1222# 1223# Windows 1224# 1225 1226 1227@functools.lru_cache(1) 1228def git_for_windows_path(): 1229 """Returns the path to git for windows, if available and None otherwise.""" 1230 import winreg 1231 1232 try: 1233 key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, "SOFTWARE\\GitForWindows") 1234 gfwp, _ = winreg.QueryValueEx(key, "InstallPath") 1235 except FileNotFoundError: 1236 gfwp = None 1237 return gfwp 1238 1239 1240@functools.lru_cache(1) 1241def windows_bash_command(): 1242 """Determines the command for Bash on windows.""" 1243 # Check that bash is on path otherwise try the default directory 1244 # used by Git for windows 1245 wbc = "bash" 1246 cmd_cache = builtins.__xonsh_commands_cache__ 1247 bash_on_path = cmd_cache.lazy_locate_binary("bash", ignore_alias=True) 1248 if bash_on_path: 1249 try: 1250 out = subprocess.check_output( 1251 [bash_on_path, "--version"], 1252 stderr=subprocess.PIPE, 1253 universal_newlines=True, 1254 ) 1255 except subprocess.CalledProcessError: 1256 bash_works = False 1257 else: 1258 # Check if Bash is from the "Windows Subsystem for Linux" (WSL) 1259 # which can't be used by xonsh foreign-shell/completer 1260 bash_works = out and "pc-linux-gnu" not in out.splitlines()[0] 1261 1262 if bash_works: 1263 wbc = bash_on_path 1264 else: 1265 gfwp = git_for_windows_path() 1266 if gfwp: 1267 bashcmd = os.path.join(gfwp, "bin\\bash.exe") 1268 if os.path.isfile(bashcmd): 1269 wbc = bashcmd 1270 return wbc 1271 1272 1273# 1274# Environment variables defaults 1275# 1276 1277if ON_WINDOWS: 1278 1279 class OSEnvironCasePreserving(collections.MutableMapping): 1280 """ Case-preserving wrapper for os.environ on Windows. 1281 It uses nt.environ to get the correct cased keys on 1282 initialization. It also preserves the case of any variables 1283 add after initialization. 1284 """ 1285 1286 def __init__(self): 1287 import nt 1288 1289 self._upperkeys = dict((k.upper(), k) for k in nt.environ) 1290 1291 def _sync(self): 1292 """ Ensure that the case sensitive map of the keys are 1293 in sync with os.environ 1294 """ 1295 envkeys = set(os.environ.keys()) 1296 for key in envkeys.difference(self._upperkeys): 1297 self._upperkeys[key] = key.upper() 1298 for key in set(self._upperkeys).difference(envkeys): 1299 del self._upperkeys[key] 1300 1301 def __contains__(self, k): 1302 self._sync() 1303 return k.upper() in self._upperkeys 1304 1305 def __len__(self): 1306 self._sync() 1307 return len(self._upperkeys) 1308 1309 def __iter__(self): 1310 self._sync() 1311 return iter(self._upperkeys.values()) 1312 1313 def __getitem__(self, k): 1314 self._sync() 1315 return os.environ[k] 1316 1317 def __setitem__(self, k, v): 1318 self._sync() 1319 self._upperkeys[k.upper()] = k 1320 os.environ[k] = v 1321 1322 def __delitem__(self, k): 1323 self._sync() 1324 if k.upper() in self._upperkeys: 1325 del self._upperkeys[k.upper()] 1326 del os.environ[k] 1327 1328 def getkey_actual_case(self, k): 1329 self._sync() 1330 return self._upperkeys.get(k.upper()) 1331 1332 1333@lazyobject 1334def os_environ(): 1335 """This dispatches to the correct, case-sensitive version of os.environ. 1336 This is mainly a problem for Windows. See #2024 for more details. 1337 This can probably go away once support for Python v3.5 or v3.6 is 1338 dropped. 1339 """ 1340 if ON_WINDOWS: 1341 return OSEnvironCasePreserving() 1342 else: 1343 return os.environ 1344 1345 1346@functools.lru_cache(1) 1347def bash_command(): 1348 """Determines the command for Bash on the current platform.""" 1349 if ON_WINDOWS: 1350 bc = windows_bash_command() 1351 else: 1352 bc = "bash" 1353 return bc 1354 1355 1356@lazyobject 1357def BASH_COMPLETIONS_DEFAULT(): 1358 """A possibly empty tuple with default paths to Bash completions known for 1359 the current platform. 1360 """ 1361 if ON_LINUX or ON_CYGWIN or ON_MSYS: 1362 bcd = ("/usr/share/bash-completion/bash_completion",) 1363 elif ON_DARWIN: 1364 bcd = ( 1365 "/usr/local/share/bash-completion/bash_completion", # v2.x 1366 "/usr/local/etc/bash_completion", 1367 ) # v1.x 1368 elif ON_WINDOWS and git_for_windows_path(): 1369 bcd = ( 1370 os.path.join( 1371 git_for_windows_path(), "usr\\share\\bash-completion\\bash_completion" 1372 ), 1373 os.path.join( 1374 git_for_windows_path(), 1375 "mingw64\\share\\git\\completion\\" "git-completion.bash", 1376 ), 1377 ) 1378 else: 1379 bcd = () 1380 return bcd 1381 1382 1383@lazyobject 1384def PATH_DEFAULT(): 1385 if ON_LINUX or ON_CYGWIN or ON_MSYS: 1386 if linux_distro() == "arch": 1387 pd = ( 1388 "/usr/local/sbin", 1389 "/usr/local/bin", 1390 "/usr/bin", 1391 "/usr/bin/site_perl", 1392 "/usr/bin/vendor_perl", 1393 "/usr/bin/core_perl", 1394 ) 1395 else: 1396 pd = ( 1397 os.path.expanduser("~/bin"), 1398 "/usr/local/sbin", 1399 "/usr/local/bin", 1400 "/usr/sbin", 1401 "/usr/bin", 1402 "/sbin", 1403 "/bin", 1404 "/usr/games", 1405 "/usr/local/games", 1406 ) 1407 elif ON_DARWIN: 1408 pd = ("/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin") 1409 elif ON_WINDOWS: 1410 import winreg 1411 1412 key = winreg.OpenKey( 1413 winreg.HKEY_LOCAL_MACHINE, 1414 r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment", 1415 ) 1416 pd = tuple(winreg.QueryValueEx(key, "Path")[0].split(os.pathsep)) 1417 else: 1418 pd = () 1419 return pd 1420 1421 1422# 1423# libc 1424# 1425@lazyobject 1426def LIBC(): 1427 """The platform dependent libc implementation.""" 1428 global ctypes 1429 if ON_DARWIN: 1430 import ctypes.util 1431 1432 libc = ctypes.CDLL(ctypes.util.find_library("c")) 1433 elif ON_CYGWIN: 1434 libc = ctypes.CDLL("cygwin1.dll") 1435 elif ON_MSYS: 1436 libc = ctypes.CDLL("msys-2.0.dll") 1437 elif ON_BSD: 1438 try: 1439 libc = ctypes.CDLL("libc.so") 1440 except AttributeError: 1441 libc = None 1442 except OSError: 1443 # OS X; can't use ctypes.util.find_library because that creates 1444 # a new process on Linux, which is undesirable. 1445 try: 1446 libc = ctypes.CDLL("libc.dylib") 1447 except OSError: 1448 libc = None 1449 elif ON_POSIX: 1450 try: 1451 libc = ctypes.CDLL("libc.so") 1452 except AttributeError: 1453 libc = None 1454 except OSError: 1455 # Debian and derivatives do the wrong thing because /usr/lib/libc.so 1456 # is a GNU ld script rather than an ELF object. To get around this, we 1457 # have to be more specific. 1458 # We don't want to use ctypes.util.find_library because that creates a 1459 # new process on Linux. We also don't want to try too hard because at 1460 # this point we're already pretty sure this isn't Linux. 1461 try: 1462 libc = ctypes.CDLL("libc.so.6") 1463 except OSError: 1464 libc = None 1465 if not hasattr(libc, "sysinfo"): 1466 # Not Linux. 1467 libc = None 1468 elif ON_WINDOWS: 1469 if hasattr(ctypes, "windll") and hasattr(ctypes.windll, "kernel32"): 1470 libc = ctypes.windll.kernel32 1471 else: 1472 try: 1473 # Windows CE uses the cdecl calling convention. 1474 libc = ctypes.CDLL("coredll.lib") 1475 except (AttributeError, OSError): 1476 libc = None 1477 elif ON_BEOS: 1478 libc = ctypes.CDLL("libroot.so") 1479 else: 1480 libc = None 1481 return libc 1482 1483# 1484# pretty 1485# 1486# -*- coding: utf-8 -*- 1487""" 1488Python advanced pretty printer. This pretty printer is intended to 1489replace the old `pprint` python module which does not allow developers 1490to provide their own pretty print callbacks. 1491 1492This module is based on ruby's `prettyprint.rb` library by `Tanaka Akira`. 1493 1494The following implementations were forked from the IPython project: 1495* Copyright (c) 2008-2014, IPython Development Team 1496* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> 1497* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 1498* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 1499 1500Example Usage 1501------------- 1502 1503To directly print the representation of an object use `pprint`:: 1504 1505 from pretty import pretty_print 1506 pretty_pprint(complex_object) 1507 1508To get a string of the output use `pretty`:: 1509 1510 from pretty import pretty 1511 string = pretty(complex_object) 1512 1513 1514Extending 1515--------- 1516 1517The pretty library allows developers to add pretty printing rules for their 1518own objects. This process is straightforward. All you have to do is to 1519add a `_repr_pretty_` method to your object and call the methods on the 1520pretty printer passed:: 1521 1522 class MyObject(object): 1523 1524 def _repr_pretty_(self, p, cycle): 1525 ... 1526 1527Here is an example implementation of a `_repr_pretty_` method for a list 1528subclass:: 1529 1530 class MyList(list): 1531 1532 def _repr_pretty_(self, p, cycle): 1533 if cycle: 1534 p.text('MyList(...)') 1535 else: 1536 with p.group(8, 'MyList([', '])'): 1537 for idx, item in enumerate(self): 1538 if idx: 1539 p.text(',') 1540 p.breakable() 1541 p.pretty(item) 1542 1543The `cycle` parameter is `True` if pretty detected a cycle. You *have* to 1544react to that or the result is an infinite loop. `p.text()` just adds 1545non breaking text to the output, `p.breakable()` either adds a whitespace 1546or breaks here. If you pass it an argument it's used instead of the 1547default space. `p.pretty` prettyprints another object using the pretty print 1548method. 1549 1550The first parameter to the `group` function specifies the extra indentation 1551of the next line. In this example the next item will either be on the same 1552line (if the items are short enough) or aligned with the right edge of the 1553opening bracket of `MyList`. 1554 1555If you just want to indent something you can use the group function 1556without open / close parameters. You can also use this code:: 1557 1558 with p.indent(2): 1559 ... 1560 1561 1562:copyright: 2007 by Armin Ronacher. 1563 Portions (c) 2009 by Robert Kern. 1564:license: BSD License. 1565""" 1566# amalgamated io 1567re = _LazyModule.load('re', 're') 1568# amalgamated sys 1569# amalgamated types 1570datetime = _LazyModule.load('datetime', 'datetime') 1571# amalgamated contextlib 1572# amalgamated collections 1573# amalgamated xonsh.lazyasd 1574__all__ = [ 1575 "pretty", 1576 "pretty_print", 1577 "PrettyPrinter", 1578 "RepresentationPrinter", 1579 "for_type", 1580 "for_type_by_name", 1581] 1582 1583 1584MAX_SEQ_LENGTH = 1000 1585 1586 1587def _safe_getattr(obj, attr, default=None): 1588 """Safe version of getattr. 1589 1590 Same as getattr, but will return ``default`` on any Exception, 1591 rather than raising. 1592 """ 1593 try: 1594 return getattr(obj, attr, default) 1595 except Exception: 1596 return default 1597 1598 1599CUnicodeIO = io.StringIO 1600 1601 1602def pretty( 1603 obj, verbose=False, max_width=79, newline="\n", max_seq_length=MAX_SEQ_LENGTH 1604): 1605 """ 1606 Pretty print the object's representation. 1607 """ 1608 if hasattr(obj, "xonsh_display"): 1609 return obj.xonsh_display() 1610 1611 stream = CUnicodeIO() 1612 printer = RepresentationPrinter( 1613 stream, verbose, max_width, newline, max_seq_length=max_seq_length 1614 ) 1615 printer.pretty(obj) 1616 printer.flush() 1617 return stream.getvalue() 1618 1619 1620def pretty_print( 1621 obj, verbose=False, max_width=79, newline="\n", max_seq_length=MAX_SEQ_LENGTH 1622): 1623 """ 1624 Like pretty() but print to stdout. 1625 """ 1626 printer = RepresentationPrinter( 1627 sys.stdout, verbose, max_width, newline, max_seq_length=max_seq_length 1628 ) 1629 printer.pretty(obj) 1630 printer.flush() 1631 sys.stdout.write(newline) 1632 sys.stdout.flush() 1633 1634 1635class _PrettyPrinterBase(object): 1636 @contextlib.contextmanager 1637 def indent(self, indent): 1638 """with statement support for indenting/dedenting.""" 1639 self.indentation += indent 1640 try: 1641 yield 1642 finally: 1643 self.indentation -= indent 1644 1645 @contextlib.contextmanager 1646 def group(self, indent=0, open="", close=""): 1647 """like begin_group / end_group but for the with statement.""" 1648 self.begin_group(indent, open) 1649 try: 1650 yield 1651 finally: 1652 self.end_group(indent, close) 1653 1654 1655class PrettyPrinter(_PrettyPrinterBase): 1656 """ 1657 Baseclass for the `RepresentationPrinter` prettyprinter that is used to 1658 generate pretty reprs of objects. Contrary to the `RepresentationPrinter` 1659 this printer knows nothing about the default pprinters or the `_repr_pretty_` 1660 callback method. 1661 """ 1662 1663 def __init__( 1664 self, output, max_width=79, newline="\n", max_seq_length=MAX_SEQ_LENGTH 1665 ): 1666 self.output = output 1667 self.max_width = max_width 1668 self.newline = newline 1669 self.max_seq_length = max_seq_length 1670 self.output_width = 0 1671 self.buffer_width = 0 1672 self.buffer = collections.deque() 1673 1674 root_group = Group(0) 1675 self.group_stack = [root_group] 1676 self.group_queue = GroupQueue(root_group) 1677 self.indentation = 0 1678 1679 def _break_outer_groups(self): 1680 while self.max_width < self.output_width + self.buffer_width: 1681 group = self.group_queue.deq() 1682 if not group: 1683 return 1684 while group.breakables: 1685 x = self.buffer.popleft() 1686 self.output_width = x.output(self.output, self.output_width) 1687 self.buffer_width -= x.width 1688 while self.buffer and isinstance(self.buffer[0], Text): 1689 x = self.buffer.popleft() 1690 self.output_width = x.output(self.output, self.output_width) 1691 self.buffer_width -= x.width 1692 1693 def text(self, obj): 1694 """Add literal text to the output.""" 1695 width = len(obj) 1696 if self.buffer: 1697 text = self.buffer[-1] 1698 if not isinstance(text, Text): 1699 text = Text() 1700 self.buffer.append(text) 1701 text.add(obj, width) 1702 self.buffer_width += width 1703 self._break_outer_groups() 1704 else: 1705 self.output.write(obj) 1706 self.output_width += width 1707 1708 def breakable(self, sep=" "): 1709 """ 1710 Add a breakable separator to the output. This does not mean that it 1711 will automatically break here. If no breaking on this position takes 1712 place the `sep` is inserted which default to one space. 1713 """ 1714 width = len(sep) 1715 group = self.group_stack[-1] 1716 if group.want_break: 1717 self.flush() 1718 self.output.write(self.newline) 1719 self.output.write(" " * self.indentation) 1720 self.output_width = self.indentation 1721 self.buffer_width = 0 1722 else: 1723 self.buffer.append(Breakable(sep, width, self)) 1724 self.buffer_width += width 1725 self._break_outer_groups() 1726 1727 def break_(self): 1728 """ 1729 Explicitly insert a newline into the output, maintaining correct indentation. 1730 """ 1731 self.flush() 1732 self.output.write(self.newline) 1733 self.output.write(" " * self.indentation) 1734 self.output_width = self.indentation 1735 self.buffer_width = 0 1736 1737 def begin_group(self, indent=0, open=""): 1738 """ 1739 Begin a group. If you want support for python < 2.5 which doesn't has 1740 the with statement this is the preferred way: 1741 1742 p.begin_group(1, '{') 1743 ... 1744 p.end_group(1, '}') 1745 1746 The python 2.5 expression would be this: 1747 1748 with p.group(1, '{', '}'): 1749 ... 1750 1751 The first parameter specifies the indentation for the next line (usually 1752 the width of the opening text), the second the opening text. All 1753 parameters are optional. 1754 """ 1755 if open: 1756 self.text(open) 1757 group = Group(self.group_stack[-1].depth + 1) 1758 self.group_stack.append(group) 1759 self.group_queue.enq(group) 1760 self.indentation += indent 1761 1762 def _enumerate(self, seq): 1763 """like enumerate, but with an upper limit on the number of items""" 1764 for idx, x in enumerate(seq): 1765 if self.max_seq_length and idx >= self.max_seq_length: 1766 self.text(",") 1767 self.breakable() 1768 self.text("...") 1769 return 1770 yield idx, x 1771 1772 def end_group(self, dedent=0, close=""): 1773 """End a group. See `begin_group` for more details.""" 1774 self.indentation -= dedent 1775 group = self.group_stack.pop() 1776 if not group.breakables: 1777 self.group_queue.remove(group) 1778 if close: 1779 self.text(close) 1780 1781 def flush(self): 1782 """Flush data that is left in the buffer.""" 1783 for data in self.buffer: 1784 self.output_width += data.output(self.output, self.output_width) 1785 self.buffer.clear() 1786 self.buffer_width = 0 1787 1788 1789def _get_mro(obj_class): 1790 """ Get a reasonable method resolution order of a class and its superclasses 1791 for both old-style and new-style classes. 1792 """ 1793 if not hasattr(obj_class, "__mro__"): 1794 # Old-style class. Mix in object to make a fake new-style class. 1795 try: 1796 obj_class = type(obj_class.__name__, (obj_class, object), {}) 1797 except TypeError: 1798 # Old-style extension type that does not descend from object. 1799 # FIXME: try to construct a more thorough MRO. 1800 mro = [obj_class] 1801 else: 1802 mro = obj_class.__mro__[1:-1] 1803 else: 1804 mro = obj_class.__mro__ 1805 return mro 1806 1807 1808class RepresentationPrinter(PrettyPrinter): 1809 """ 1810 Special pretty printer that has a `pretty` method that calls the pretty 1811 printer for a python object. 1812 1813 This class stores processing data on `self` so you must *never* use 1814 this class in a threaded environment. Always lock it or reinstantiate 1815 it. 1816 1817 Instances also have a verbose flag callbacks can access to control their 1818 output. For example the default instance repr prints all attributes and 1819 methods that are not prefixed by an underscore if the printer is in 1820 verbose mode. 1821 """ 1822 1823 def __init__( 1824 self, 1825 output, 1826 verbose=False, 1827 max_width=79, 1828 newline="\n", 1829 singleton_pprinters=None, 1830 type_pprinters=None, 1831 deferred_pprinters=None, 1832 max_seq_length=MAX_SEQ_LENGTH, 1833 ): 1834 1835 PrettyPrinter.__init__( 1836 self, output, max_width, newline, max_seq_length=max_seq_length 1837 ) 1838 self.verbose = verbose 1839 self.stack = [] 1840 if singleton_pprinters is None: 1841 singleton_pprinters = _singleton_pprinters.copy() 1842 self.singleton_pprinters = singleton_pprinters 1843 if type_pprinters is None: 1844 type_pprinters = _type_pprinters.copy() 1845 self.type_pprinters = type_pprinters 1846 if deferred_pprinters is None: 1847 deferred_pprinters = _deferred_type_pprinters.copy() 1848 self.deferred_pprinters = deferred_pprinters 1849 1850 def pretty(self, obj): 1851 """Pretty print the given object.""" 1852 obj_id = id(obj) 1853 cycle = obj_id in self.stack 1854 self.stack.append(obj_id) 1855 self.begin_group() 1856 try: 1857 obj_class = _safe_getattr(obj, "__class__", None) or type(obj) 1858 # First try to find registered singleton printers for the type. 1859 try: 1860 printer = self.singleton_pprinters[obj_id] 1861 except (TypeError, KeyError): 1862 pass 1863 else: 1864 return printer(obj, self, cycle) 1865 # Next walk the mro and check for either: 1866 # 1) a registered printer 1867 # 2) a _repr_pretty_ method 1868 for cls in _get_mro(obj_class): 1869 if cls in self.type_pprinters: 1870 # printer registered in self.type_pprinters 1871 return self.type_pprinters[cls](obj, self, cycle) 1872 else: 1873 # deferred printer 1874 printer = self._in_deferred_types(cls) 1875 if printer is not None: 1876 return printer(obj, self, cycle) 1877 else: 1878 # Finally look for special method names. 1879 # Some objects automatically create any requested 1880 # attribute. Try to ignore most of them by checking for 1881 # callability. 1882 if "_repr_pretty_" in cls.__dict__: 1883 meth = cls._repr_pretty_ 1884 if callable(meth): 1885 return meth(obj, self, cycle) 1886 return _default_pprint(obj, self, cycle) 1887 finally: 1888 self.end_group() 1889 self.stack.pop() 1890 1891 def _in_deferred_types(self, cls): 1892 """ 1893 Check if the given class is specified in the deferred type registry. 1894 1895 Returns the printer from the registry if it exists, and None if the 1896 class is not in the registry. Successful matches will be moved to the 1897 regular type registry for future use. 1898 """ 1899 mod = _safe_getattr(cls, "__module__", None) 1900 name = _safe_getattr(cls, "__name__", None) 1901 key = (mod, name) 1902 printer = None 1903 if key in self.deferred_pprinters: 1904 # Move the printer over to the regular registry. 1905 printer = self.deferred_pprinters.pop(key) 1906 self.type_pprinters[cls] = printer 1907 return printer 1908 1909 1910class Printable(object): 1911 def output(self, stream, output_width): 1912 return output_width 1913 1914 1915class Text(Printable): 1916 def __init__(self): 1917 self.objs = [] 1918 self.width = 0 1919 1920 def output(self, stream, output_width): 1921 for obj in self.objs: 1922 stream.write(obj) 1923 return output_width + self.width 1924 1925 def add(self, obj, width): 1926 self.objs.append(obj) 1927 self.width += width 1928 1929 1930class Breakable(Printable): 1931 def __init__(self, seq, width, pretty): 1932 self.obj = seq 1933 self.width = width 1934 self.pretty = pretty 1935 self.indentation = pretty.indentation 1936 self.group = pretty.group_stack[-1] 1937 self.group.breakables.append(self) 1938 1939 def output(self, stream, output_width): 1940 self.group.breakables.popleft() 1941 if self.group.want_break: 1942 stream.write(self.pretty.newline) 1943 stream.write(" " * self.indentation) 1944 return self.indentation 1945 if not self.group.breakables: 1946 self.pretty.group_queue.remove(self.group) 1947 stream.write(self.obj) 1948 return output_width + self.width 1949 1950 1951class Group(Printable): 1952 def __init__(self, depth): 1953 self.depth = depth 1954 self.breakables = collections.deque() 1955 self.want_break = False 1956 1957 1958class GroupQueue(object): 1959 def __init__(self, *groups): 1960 self.queue = [] 1961 for group in groups: 1962 self.enq(group) 1963 1964 def enq(self, group): 1965 depth = group.depth 1966 while depth > len(self.queue) - 1: 1967 self.queue.append([]) 1968 self.queue[depth].append(group) 1969 1970 def deq(self): 1971 for stack in self.queue: 1972 for idx, group in enumerate(reversed(stack)): 1973 if group.breakables: 1974 del stack[idx] 1975 group.want_break = True 1976 return group 1977 for group in stack: 1978 group.want_break = True 1979 del stack[:] 1980 1981 def remove(self, group): 1982 try: 1983 self.queue[group.depth].remove(group) 1984 except ValueError: 1985 pass 1986 1987 1988@lazyobject 1989def _baseclass_reprs(): 1990 try: 1991 br = (object.__repr__, types.InstanceType.__repr__) 1992 except AttributeError: # Python 3 1993 br = (object.__repr__,) 1994 return br 1995 1996 1997def _default_pprint(obj, p, cycle): 1998 """ 1999 The default print function. Used if an object does not provide one and 2000 it's none of the builtin objects. 2001 """ 2002 klass = _safe_getattr(obj, "__class__", None) or type(obj) 2003 if _safe_getattr(klass, "__repr__", None) not in _baseclass_reprs: 2004 # A user-provided repr. Find newlines and replace them with p.break_() 2005 _repr_pprint(obj, p, cycle) 2006 return 2007 p.begin_group(1, "<") 2008 p.pretty(klass) 2009 p.text(" at 0x%x" % id(obj)) 2010 if cycle: 2011 p.text(" ...") 2012 elif p.verbose: 2013 first = True 2014 for key in dir(obj): 2015 if not key.startswith("_"): 2016 try: 2017 value = getattr(obj, key) 2018 except AttributeError: 2019 continue 2020 if isinstance(value, types.MethodType): 2021 continue 2022 if not first: 2023 p.text(",") 2024 p.breakable() 2025 p.text(key) 2026 p.text("=") 2027 step = len(key) + 1 2028 p.indentation += step 2029 p.pretty(value) 2030 p.indentation -= step 2031 first = False 2032 p.end_group(1, ">") 2033 2034 2035def _seq_pprinter_factory(start, end, basetype): 2036 """ 2037 Factory that returns a pprint function useful for sequences. Used by 2038 the default pprint for tuples, dicts, and lists. 2039 """ 2040 2041 def inner(obj, p, cycle): 2042 typ = type(obj) 2043 if ( 2044 basetype is not None 2045 and typ is not basetype 2046 and typ.__repr__ != basetype.__repr__ 2047 ): 2048 # If the subclass provides its own repr, use it instead. 2049 return p.text(typ.__repr__(obj)) 2050 2051 if cycle: 2052 return p.text(start + "..." + end) 2053 step = len(start) 2054 p.begin_group(step, start) 2055 for idx, x in p._enumerate(obj): 2056 if idx: 2057 p.text(",") 2058 p.breakable() 2059 p.pretty(x) 2060 if len(obj) == 1 and type(obj) is tuple: 2061 # Special case for 1-item tuples. 2062 p.text(",") 2063 p.end_group(step, end) 2064 2065 return inner 2066 2067 2068def _set_pprinter_factory(start, end, basetype): 2069 """ 2070 Factory that returns a pprint function useful for sets and frozensets. 2071 """ 2072 2073 def inner(obj, p, cycle): 2074 typ = type(obj) 2075 if ( 2076 basetype is not None 2077 and typ is not basetype 2078 and typ.__repr__ != basetype.__repr__ 2079 ): 2080 # If the subclass provides its own repr, use it instead. 2081 return p.text(typ.__repr__(obj)) 2082 2083 if cycle: 2084 return p.text(start + "..." + end) 2085 if len(obj) == 0: 2086 # Special case. 2087 p.text(basetype.__name__ + "()") 2088 else: 2089 step = len(start) 2090 p.begin_group(step, start) 2091 # Like dictionary keys, we will try to sort the items if there aren't too many 2092 items = obj 2093 if not (p.max_seq_length and len(obj) >= p.max_seq_length): 2094 try: 2095 items = sorted(obj) 2096 except Exception: 2097 # Sometimes the items don't sort. 2098 pass 2099 for idx, x in p._enumerate(items): 2100 if idx: 2101 p.text(",") 2102 p.breakable() 2103 p.pretty(x) 2104 p.end_group(step, end) 2105 2106 return inner 2107 2108 2109def _dict_pprinter_factory(start, end, basetype=None): 2110 """ 2111 Factory that returns a pprint function used by the default pprint of 2112 dicts and dict proxies. 2113 """ 2114 2115 def inner(obj, p, cycle): 2116 typ = type(obj) 2117 if ( 2118 basetype is not None 2119 and typ is not basetype 2120 and typ.__repr__ != basetype.__repr__ 2121 ): 2122 # If the subclass provides its own repr, use it instead. 2123 return p.text(typ.__repr__(obj)) 2124 2125 if cycle: 2126 return p.text("{...}") 2127 p.begin_group(1, start) 2128 keys = obj.keys() 2129 # if dict isn't large enough to be truncated, sort keys before displaying 2130 if not (p.max_seq_length and len(obj) >= p.max_seq_length): 2131 try: 2132 keys = sorted(keys) 2133 except Exception: 2134 # Sometimes the keys don't sort. 2135 pass 2136 for idx, key in p._enumerate(keys): 2137 if idx: 2138 p.text(",") 2139 p.breakable() 2140 p.pretty(key) 2141 p.text(": ") 2142 p.pretty(obj[key]) 2143 p.end_group(1, end) 2144 2145 return inner 2146 2147 2148def _super_pprint(obj, p, cycle): 2149 """The pprint for the super type.""" 2150 p.begin_group(8, "<super: ") 2151 p.pretty(obj.__thisclass__) 2152 p.text(",") 2153 p.breakable() 2154 p.pretty(obj.__self__) 2155 p.end_group(8, ">") 2156 2157 2158def _re_pattern_pprint(obj, p, cycle): 2159 """The pprint function for regular expression patterns.""" 2160 p.text("re.compile(") 2161 pattern = repr(obj.pattern) 2162 if pattern[:1] in "uU": 2163 pattern = pattern[1:] 2164 prefix = "ur" 2165 else: 2166 prefix = "r" 2167 pattern = prefix + pattern.replace("\\\\", "\\") 2168 p.text(pattern) 2169 if obj.flags: 2170 p.text(",") 2171 p.breakable() 2172 done_one = False 2173 for flag in ( 2174 "TEMPLATE", 2175 "IGNORECASE", 2176 "LOCALE", 2177 "MULTILINE", 2178 "DOTALL", 2179 "UNICODE", 2180 "VERBOSE", 2181 "DEBUG", 2182 ): 2183 if obj.flags & getattr(re, flag): 2184 if done_one: 2185 p.text("|") 2186 p.text("re." + flag) 2187 done_one = True 2188 p.text(")") 2189 2190 2191def _type_pprint(obj, p, cycle): 2192 """The pprint for classes and types.""" 2193 # Heap allocated types might not have the module attribute, 2194 # and others may set it to None. 2195 2196 # Checks for a __repr__ override in the metaclass 2197 if type(obj).__repr__ is not type.__repr__: 2198 _repr_pprint(obj, p, cycle) 2199 return 2200 2201 mod = _safe_getattr(obj, "__module__", None) 2202 try: 2203 name = obj.__qualname__ 2204 if not isinstance(name, str): 2205 # This can happen if the type implements __qualname__ as a property 2206 # or other descriptor in Python 2. 2207 raise Exception("Try __name__") 2208 except Exception: 2209 name = obj.__name__ 2210 if not isinstance(name, str): 2211 name = "<unknown type>" 2212 2213 if mod in (None, "__builtin__", "builtins", "exceptions"): 2214 p.text(name) 2215 else: 2216 p.text(mod + "." + name) 2217 2218 2219def _repr_pprint(obj, p, cycle): 2220 """A pprint that just redirects to the normal repr function.""" 2221 # Find newlines and replace them with p.break_() 2222 output = repr(obj) 2223 for idx, output_line in enumerate(output.splitlines()): 2224 if idx: 2225 p.break_() 2226 p.text(output_line) 2227 2228 2229def _function_pprint(obj, p, cycle): 2230 """Base pprint for all functions and builtin functions.""" 2231 name = _safe_getattr(obj, "__qualname__", obj.__name__) 2232 mod = obj.__module__ 2233 if mod and mod not in ("__builtin__", "builtins", "exceptions"): 2234 name = mod + "." + name 2235 p.text("<function %s>" % name) 2236 2237 2238def _exception_pprint(obj, p, cycle): 2239 """Base pprint for all exceptions.""" 2240 name = getattr(obj.__class__, "__qualname__", obj.__class__.__name__) 2241 if obj.__class__.__module__ not in ("exceptions", "builtins"): 2242 name = "%s.%s" % (obj.__class__.__module__, name) 2243 step = len(name) + 1 2244 p.begin_group(step, name + "(") 2245 for idx, arg in enumerate(getattr(obj, "args", ())): 2246 if idx: 2247 p.text(",") 2248 p.breakable() 2249 p.pretty(arg) 2250 p.end_group(step, ")") 2251 2252 2253@lazyobject 2254def _type_pprinters(): 2255 #: printers for builtin types 2256 tp = { 2257 int: _repr_pprint, 2258 float: _repr_pprint, 2259 str: _repr_pprint, 2260 tuple: _seq_pprinter_factory("(", ")", tuple), 2261 list: _seq_pprinter_factory("[", "]", list), 2262 dict: _dict_pprinter_factory("{", "}", dict), 2263 set: _set_pprinter_factory("{", "}", set), 2264 frozenset: _set_pprinter_factory("frozenset({", "})", frozenset), 2265 super: _super_pprint, 2266 type(re.compile("")): _re_pattern_pprint, 2267 type: _type_pprint, 2268 types.FunctionType: _function_pprint, 2269 types.BuiltinFunctionType: _function_pprint, 2270 types.MethodType: _repr_pprint, 2271 datetime.datetime: _repr_pprint, 2272 datetime.timedelta: _repr_pprint, 2273 } 2274 #: the exception base 2275 try: 2276 _exception_base = BaseException 2277 except NameError: 2278 _exception_base = Exception 2279 tp[_exception_base] = _exception_pprint 2280 try: 2281 tp[types.DictProxyType] = _dict_pprinter_factory("<dictproxy {", "}>") 2282 tp[types.ClassType] = _type_pprint 2283 tp[types.SliceType] = _repr_pprint 2284 except AttributeError: # Python 3 2285 tp[slice] = _repr_pprint 2286 try: 2287 tp[xrange] = _repr_pprint 2288 tp[long] = _repr_pprint 2289 tp[unicode] = _repr_pprint 2290 except NameError: 2291 tp[range] = _repr_pprint 2292 tp[bytes] = _repr_pprint 2293 return tp 2294 2295 2296#: printers for types specified by name 2297@lazyobject 2298def _deferred_type_pprinters(): 2299 dtp = {} 2300 for_type_by_name("collections", "defaultdict", _defaultdict_pprint, dtp=dtp) 2301 for_type_by_name("collections", "OrderedDict", _ordereddict_pprint, dtp=dtp) 2302 for_type_by_name("collections", "deque", _deque_pprint, dtp=dtp) 2303 for_type_by_name("collections", "Counter", _counter_pprint, dtp=dtp) 2304 return dtp 2305 2306 2307def for_type(typ, func): 2308 """ 2309 Add a pretty printer for a given type. 2310 """ 2311 oldfunc = _type_pprinters.get(typ, None) 2312 if func is not None: 2313 # To support easy restoration of old pprinters, we need to ignore Nones. 2314 _type_pprinters[typ] = func 2315 return oldfunc 2316 2317 2318def for_type_by_name(type_module, type_name, func, dtp=None): 2319 """ 2320 Add a pretty printer for a type specified by the module and name of a type 2321 rather than the type object itself. 2322 """ 2323 if dtp is None: 2324 dtp = _deferred_type_pprinters 2325 key = (type_module, type_name) 2326 oldfunc = dtp.get(key, None) 2327 if func is not None: 2328 # To support easy restoration of old pprinters, we need to ignore Nones. 2329 dtp[key] = func 2330 return oldfunc 2331 2332 2333#: printers for the default singletons 2334_singleton_pprinters = LazyObject( 2335 lambda: dict.fromkeys( 2336 map(id, [None, True, False, Ellipsis, NotImplemented]), _repr_pprint 2337 ), 2338 globals(), 2339 "_singleton_pprinters", 2340) 2341 2342 2343def _defaultdict_pprint(obj, p, cycle): 2344 name = obj.__class__.__name__ 2345 with p.group(len(name) + 1, name + "(", ")"): 2346 if cycle: 2347 p.text("...") 2348 else: 2349 p.pretty(obj.default_factory) 2350 p.text(",") 2351 p.breakable() 2352 p.pretty(dict(obj)) 2353 2354 2355def _ordereddict_pprint(obj, p, cycle): 2356 name = obj.__class__.__name__ 2357 with p.group(len(name) + 1, name + "(", ")"): 2358 if cycle: 2359 p.text("...") 2360 elif len(obj): 2361 p.pretty(list(obj.items())) 2362 2363 2364def _deque_pprint(obj, p, cycle): 2365 name = obj.__class__.__name__ 2366 with p.group(len(name) + 1, name + "(", ")"): 2367 if cycle: 2368 p.text("...") 2369 else: 2370 p.pretty(list(obj)) 2371 2372 2373def _counter_pprint(obj, p, cycle): 2374 name = obj.__class__.__name__ 2375 with p.group(len(name) + 1, name + "(", ")"): 2376 if cycle: 2377 p.text("...") 2378 elif len(obj): 2379 p.pretty(dict(obj)) 2380 2381# 2382# codecache 2383# 2384"""Tools for caching xonsh code.""" 2385# amalgamated os 2386# amalgamated sys 2387hashlib = _LazyModule.load('hashlib', 'hashlib') 2388marshal = _LazyModule.load('marshal', 'marshal') 2389# amalgamated builtins 2390from xonsh import __version__ as XONSH_VERSION 2391# amalgamated xonsh.lazyasd 2392# amalgamated xonsh.platform 2393def _splitpath(path, sofar=[]): 2394 folder, path = os.path.split(path) 2395 if path == "": 2396 return sofar[::-1] 2397 elif folder == "": 2398 return (sofar + [path])[::-1] 2399 else: 2400 return _splitpath(folder, sofar + [path]) 2401 2402 2403@lazyobject 2404def _CHARACTER_MAP(): 2405 cmap = {chr(o): "_%s" % chr(o + 32) for o in range(65, 91)} 2406 cmap.update({".": "_.", "_": "__"}) 2407 return cmap 2408 2409 2410def _cache_renamer(path, code=False): 2411 if not code: 2412 path = os.path.realpath(path) 2413 o = ["".join(_CHARACTER_MAP.get(i, i) for i in w) for w in _splitpath(path)] 2414 o[-1] = "{}.{}".format(o[-1], sys.implementation.cache_tag) 2415 return o 2416 2417 2418def _make_if_not_exists(dirname): 2419 if not os.path.isdir(dirname): 2420 os.makedirs(dirname) 2421 2422 2423def should_use_cache(execer, mode): 2424 """ 2425 Return ``True`` if caching has been enabled for this mode (through command 2426 line flags or environment variables) 2427 """ 2428 if mode == "exec": 2429 return (execer.scriptcache or execer.cacheall) and ( 2430 builtins.__xonsh_env__["XONSH_CACHE_SCRIPTS"] 2431 or builtins.__xonsh_env__["XONSH_CACHE_EVERYTHING"] 2432 ) 2433 else: 2434 return execer.cacheall or builtins.__xonsh_env__["XONSH_CACHE_EVERYTHING"] 2435 2436 2437def run_compiled_code(code, glb, loc, mode): 2438 """ 2439 Helper to run code in a given mode and context 2440 """ 2441 if code is None: 2442 return 2443 if mode in {"exec", "single"}: 2444 func = exec 2445 else: 2446 func = eval 2447 func(code, glb, loc) 2448 2449 2450def get_cache_filename(fname, code=True): 2451 """ 2452 Return the filename of the cache for the given filename. 2453 2454 Cache filenames are similar to those used by the Mercurial DVCS for its 2455 internal store. 2456 2457 The ``code`` switch should be true if we should use the code store rather 2458 than the script store. 2459 """ 2460 datadir = builtins.__xonsh_env__["XONSH_DATA_DIR"] 2461 cachedir = os.path.join( 2462 datadir, "xonsh_code_cache" if code else "xonsh_script_cache" 2463 ) 2464 cachefname = os.path.join(cachedir, *_cache_renamer(fname, code=code)) 2465 return cachefname 2466 2467 2468def update_cache(ccode, cache_file_name): 2469 """ 2470 Update the cache at ``cache_file_name`` to contain the compiled code 2471 represented by ``ccode``. 2472 """ 2473 if cache_file_name is not None: 2474 _make_if_not_exists(os.path.dirname(cache_file_name)) 2475 with open(cache_file_name, "wb") as cfile: 2476 cfile.write(XONSH_VERSION.encode() + b"\n") 2477 cfile.write(bytes(PYTHON_VERSION_INFO_BYTES) + b"\n") 2478 marshal.dump(ccode, cfile) 2479 2480 2481def _check_cache_versions(cfile): 2482 # version data should be < 1 kb 2483 ver = cfile.readline(1024).strip() 2484 if ver != XONSH_VERSION.encode(): 2485 return False 2486 ver = cfile.readline(1024).strip() 2487 return ver == PYTHON_VERSION_INFO_BYTES 2488 2489 2490def compile_code(filename, code, execer, glb, loc, mode): 2491 """ 2492 Wrapper for ``execer.compile`` to compile the given code 2493 """ 2494 try: 2495 if not code.endswith("\n"): 2496 code += "\n" 2497 old_filename = execer.filename 2498 execer.filename = filename 2499 ccode = execer.compile(code, glbs=glb, locs=loc, mode=mode, filename=filename) 2500 except Exception: 2501 raise 2502 finally: 2503 execer.filename = old_filename 2504 return ccode 2505 2506 2507def script_cache_check(filename, cachefname): 2508 """ 2509 Check whether the script cache for a particular file is valid. 2510 2511 Returns a tuple containing: a boolean representing whether the cached code 2512 should be used, and the cached code (or ``None`` if the cache should not be 2513 used). 2514 """ 2515 ccode = None 2516 run_cached = False 2517 if os.path.isfile(cachefname): 2518 if os.stat(cachefname).st_mtime >= os.stat(filename).st_mtime: 2519 with open(cachefname, "rb") as cfile: 2520 if not _check_cache_versions(cfile): 2521 return False, None 2522 ccode = marshal.load(cfile) 2523 run_cached = True 2524 return run_cached, ccode 2525 2526 2527def run_script_with_cache(filename, execer, glb=None, loc=None, mode="exec"): 2528 """ 2529 Run a script, using a cached version if it exists (and the source has not 2530 changed), and updating the cache as necessary. 2531 """ 2532 run_cached = False 2533 use_cache = should_use_cache(execer, mode) 2534 cachefname = get_cache_filename(filename, code=False) 2535 if use_cache: 2536 run_cached, ccode = script_cache_check(filename, cachefname) 2537 if not run_cached: 2538 with open(filename, "r") as f: 2539 code = f.read() 2540 ccode = compile_code(filename, code, execer, glb, loc, mode) 2541 update_cache(ccode, cachefname) 2542 run_compiled_code(ccode, glb, loc, mode) 2543 2544 2545def code_cache_name(code): 2546 """ 2547 Return an appropriate spoofed filename for the given code. 2548 """ 2549 if isinstance(code, str): 2550 _code = code.encode() 2551 else: 2552 _code = code 2553 return hashlib.md5(_code).hexdigest() 2554 2555 2556def code_cache_check(cachefname): 2557 """ 2558 Check whether the code cache for a particular piece of code is valid. 2559 2560 Returns a tuple containing: a boolean representing whether the cached code 2561 should be used, and the cached code (or ``None`` if the cache should not be 2562 used). 2563 """ 2564 ccode = None 2565 run_cached = False 2566 if os.path.isfile(cachefname): 2567 with open(cachefname, "rb") as cfile: 2568 if not _check_cache_versions(cfile): 2569 return False, None 2570 ccode = marshal.load(cfile) 2571 run_cached = True 2572 return run_cached, ccode 2573 2574 2575def run_code_with_cache(code, execer, glb=None, loc=None, mode="exec"): 2576 """ 2577 Run a piece of code, using a cached version if it exists, and updating the 2578 cache as necessary. 2579 """ 2580 use_cache = should_use_cache(execer, mode) 2581 filename = code_cache_name(code) 2582 cachefname = get_cache_filename(filename, code=True) 2583 run_cached = False 2584 if use_cache: 2585 run_cached, ccode = code_cache_check(cachefname) 2586 if not run_cached: 2587 ccode = compile_code(filename, code, execer, glb, loc, mode) 2588 update_cache(ccode, cachefname) 2589 run_compiled_code(ccode, glb, loc, mode) 2590 2591# 2592# lazyimps 2593# 2594"""Lazy imports that may apply across the xonsh package.""" 2595# amalgamated importlib 2596# amalgamated xonsh.platform 2597# amalgamated xonsh.lazyasd 2598pygments = LazyObject( 2599 lambda: importlib.import_module("pygments"), globals(), "pygments" 2600) 2601pyghooks = LazyObject( 2602 lambda: importlib.import_module("xonsh.pyghooks"), globals(), "pyghooks" 2603) 2604 2605 2606@lazyobject 2607def pty(): 2608 if ON_WINDOWS: 2609 return 2610 else: 2611 return importlib.import_module("pty") 2612 2613 2614@lazyobject 2615def termios(): 2616 if ON_WINDOWS: 2617 return 2618 else: 2619 return importlib.import_module("termios") 2620 2621 2622@lazyobject 2623def fcntl(): 2624 if ON_WINDOWS: 2625 return 2626 else: 2627 return importlib.import_module("fcntl") 2628 2629 2630@lazyobject 2631def tty(): 2632 if ON_WINDOWS: 2633 return 2634 else: 2635 return importlib.import_module("tty") 2636 2637 2638@lazyobject 2639def _winapi(): 2640 if ON_WINDOWS: 2641 import _winapi as m 2642 else: 2643 m = None 2644 return m 2645 2646 2647@lazyobject 2648def msvcrt(): 2649 if ON_WINDOWS: 2650 import msvcrt as m 2651 else: 2652 m = None 2653 return m 2654 2655 2656@lazyobject 2657def winutils(): 2658 if ON_WINDOWS: 2659 import xonsh.winutils as m 2660 else: 2661 m = None 2662 return m 2663 2664 2665@lazyobject 2666def macutils(): 2667 if ON_DARWIN: 2668 import xonsh.macutils as m 2669 else: 2670 m = None 2671 return m 2672 2673 2674@lazyobject 2675def terminal256(): 2676 return importlib.import_module("pygments.formatters.terminal256") 2677 2678# 2679# parser 2680# 2681# -*- coding: utf-8 -*- 2682"""Implements the xonsh parser.""" 2683# amalgamated xonsh.lazyasd 2684# amalgamated xonsh.platform 2685@lazyobject 2686def Parser(): 2687 if PYTHON_VERSION_INFO > (3, 6): 2688 from xonsh.parsers.v36 import Parser as p 2689 elif PYTHON_VERSION_INFO > (3, 5): 2690 from xonsh.parsers.v35 import Parser as p 2691 else: 2692 from xonsh.parsers.v34 import Parser as p 2693 return p 2694 2695# 2696# tokenize 2697# 2698"""Tokenization help for xonsh programs. 2699 2700This file is a modified version of tokenize.py form the Python 3.4 and 3.5 2701standard libraries (licensed under the Python Software Foundation License, 2702version 2), which provides tokenization help for Python programs. 2703 2704It is modified to properly tokenize xonsh code, including backtick regex 2705path and several xonsh-specific operators. 2706 2707A few pieces of this file are specific to the version of Python being used. 2708To find these pieces, search the PY35. 2709 2710Original file credits: 2711 __author__ = 'Ka-Ping Yee <ping@lfw.org>' 2712 __credits__ = ('GvR, ESR, Tim Peters, Thomas Wouters, Fred Drake, ' 2713 'Skip Montanaro, Raymond Hettinger, Trent Nelson, ' 2714 'Michael Foord') 2715""" 2716 2717# amalgamated re 2718# amalgamated io 2719# amalgamated sys 2720codecs = _LazyModule.load('codecs', 'codecs') 2721# amalgamated builtins 2722itertools = _LazyModule.load('itertools', 'itertools') 2723# amalgamated collections 2724token = _LazyModule.load('token', 'token') 2725from token import ( 2726 AMPER, 2727 AMPEREQUAL, 2728 AT, 2729 CIRCUMFLEX, 2730 CIRCUMFLEXEQUAL, 2731 COLON, 2732 COMMA, 2733 DEDENT, 2734 DOT, 2735 DOUBLESLASH, 2736 DOUBLESLASHEQUAL, 2737 DOUBLESTAR, 2738 DOUBLESTAREQUAL, 2739 ENDMARKER, 2740 EQEQUAL, 2741 EQUAL, 2742 ERRORTOKEN, 2743 GREATER, 2744 GREATEREQUAL, 2745 INDENT, 2746 LBRACE, 2747 LEFTSHIFT, 2748 LEFTSHIFTEQUAL, 2749 LESS, 2750 LESSEQUAL, 2751 LPAR, 2752 LSQB, 2753 MINEQUAL, 2754 MINUS, 2755 NAME, 2756 NEWLINE, 2757 NOTEQUAL, 2758 NUMBER, 2759 N_TOKENS, 2760 OP, 2761 PERCENT, 2762 PERCENTEQUAL, 2763 PLUS, 2764 PLUSEQUAL, 2765 RBRACE, 2766 RIGHTSHIFT, 2767 RIGHTSHIFTEQUAL, 2768 RPAR, 2769 RSQB, 2770 SEMI, 2771 SLASH, 2772 SLASHEQUAL, 2773 STAR, 2774 STAREQUAL, 2775 STRING, 2776 TILDE, 2777 VBAR, 2778 VBAREQUAL, 2779 tok_name, 2780) 2781 2782# amalgamated xonsh.lazyasd 2783# amalgamated xonsh.platform 2784cookie_re = LazyObject( 2785 lambda: re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)", re.ASCII), 2786 globals(), 2787 "cookie_re", 2788) 2789blank_re = LazyObject( 2790 lambda: re.compile(br"^[ \t\f]*(?:[#\r\n]|$)", re.ASCII), globals(), "blank_re" 2791) 2792 2793# 2794# token modifications 2795# 2796tok_name = tok_name.copy() 2797__all__ = token.__all__ + [ 2798 "COMMENT", 2799 "tokenize", 2800 "detect_encoding", 2801 "NL", 2802 "untokenize", 2803 "ENCODING", 2804 "TokenInfo", 2805 "TokenError", 2806 "SEARCHPATH", 2807 "ATDOLLAR", 2808 "ATEQUAL", 2809 "DOLLARNAME", 2810 "IOREDIRECT", 2811] 2812HAS_ASYNC = (3, 5, 0) <= PYTHON_VERSION_INFO < (3, 7, 0) 2813if HAS_ASYNC: 2814 ASYNC = token.ASYNC 2815 AWAIT = token.AWAIT 2816 ADDSPACE_TOKS = (NAME, NUMBER, ASYNC, AWAIT) 2817else: 2818 ADDSPACE_TOKS = (NAME, NUMBER) 2819del token # must clean up token 2820PY35 = (3, 5, 0) <= PYTHON_VERSION_INFO 2821AUGASSIGN_OPS = r"[+\-*/%&@|^=<>]=?" 2822if not PY35: 2823 AUGASSIGN_OPS = AUGASSIGN_OPS.replace("@", "") 2824 2825 2826COMMENT = N_TOKENS 2827tok_name[COMMENT] = "COMMENT" 2828NL = N_TOKENS + 1 2829tok_name[NL] = "NL" 2830ENCODING = N_TOKENS + 2 2831tok_name[ENCODING] = "ENCODING" 2832N_TOKENS += 3 2833SEARCHPATH = N_TOKENS 2834tok_name[N_TOKENS] = "SEARCHPATH" 2835N_TOKENS += 1 2836IOREDIRECT = N_TOKENS 2837tok_name[N_TOKENS] = "IOREDIRECT" 2838N_TOKENS += 1 2839DOLLARNAME = N_TOKENS 2840tok_name[N_TOKENS] = "DOLLARNAME" 2841N_TOKENS += 1 2842ATDOLLAR = N_TOKENS 2843tok_name[N_TOKENS] = "ATDOLLAR" 2844N_TOKENS += 1 2845ATEQUAL = N_TOKENS 2846tok_name[N_TOKENS] = "ATEQUAL" 2847N_TOKENS += 1 2848_xonsh_tokens = { 2849 "?": "QUESTION", 2850 "@=": "ATEQUAL", 2851 "@$": "ATDOLLAR", 2852 "||": "DOUBLEPIPE", 2853 "&&": "DOUBLEAMPER", 2854 "@(": "ATLPAREN", 2855 "!(": "BANGLPAREN", 2856 "![": "BANGLBRACKET", 2857 "$(": "DOLLARLPAREN", 2858 "$[": "DOLLARLBRACKET", 2859 "${": "DOLLARLBRACE", 2860 "??": "DOUBLEQUESTION", 2861 "@$(": "ATDOLLARLPAREN", 2862} 2863 2864additional_parenlevs = frozenset({"@(", "!(", "![", "$(", "$[", "${", "@$("}) 2865 2866_glbs = globals() 2867for v in _xonsh_tokens.values(): 2868 _glbs[v] = N_TOKENS 2869 tok_name[N_TOKENS] = v 2870 N_TOKENS += 1 2871 __all__.append(v) 2872del _glbs, v 2873 2874EXACT_TOKEN_TYPES = { 2875 "(": LPAR, 2876 ")": RPAR, 2877 "[": LSQB, 2878 "]": RSQB, 2879 ":": COLON, 2880 ",": COMMA, 2881 ";": SEMI, 2882 "+": PLUS, 2883 "-": MINUS, 2884 "*": STAR, 2885 "/": SLASH, 2886 "|": VBAR, 2887 "&": AMPER, 2888 "<": LESS, 2889 ">": GREATER, 2890 "=": EQUAL, 2891 ".": DOT, 2892 "%": PERCENT, 2893 "{": LBRACE, 2894 "}": RBRACE, 2895 "==": EQEQUAL, 2896 "!=": NOTEQUAL, 2897 "<=": LESSEQUAL, 2898 ">=": GREATEREQUAL, 2899 "~": TILDE, 2900 "^": CIRCUMFLEX, 2901 "<<": LEFTSHIFT, 2902 ">>": RIGHTSHIFT, 2903 "**": DOUBLESTAR, 2904 "+=": PLUSEQUAL, 2905 "-=": MINEQUAL, 2906 "*=": STAREQUAL, 2907 "/=": SLASHEQUAL, 2908 "%=": PERCENTEQUAL, 2909 "&=": AMPEREQUAL, 2910 "|=": VBAREQUAL, 2911 "^=": CIRCUMFLEXEQUAL, 2912 "<<=": LEFTSHIFTEQUAL, 2913 ">>=": RIGHTSHIFTEQUAL, 2914 "**=": DOUBLESTAREQUAL, 2915 "//": DOUBLESLASH, 2916 "//=": DOUBLESLASHEQUAL, 2917 "@": AT, 2918} 2919 2920EXACT_TOKEN_TYPES.update(_xonsh_tokens) 2921 2922 2923class TokenInfo(collections.namedtuple("TokenInfo", "type string start end line")): 2924 def __repr__(self): 2925 annotated_type = "%d (%s)" % (self.type, tok_name[self.type]) 2926 return ( 2927 "TokenInfo(type=%s, string=%r, start=%r, end=%r, line=%r)" 2928 % self._replace(type=annotated_type) 2929 ) 2930 2931 @property 2932 def exact_type(self): 2933 if self.type == OP and self.string in EXACT_TOKEN_TYPES: 2934 return EXACT_TOKEN_TYPES[self.string] 2935 else: 2936 return self.type 2937 2938 2939def group(*choices): 2940 return "(" + "|".join(choices) + ")" 2941 2942 2943def tokany(*choices): 2944 return group(*choices) + "*" 2945 2946 2947def maybe(*choices): 2948 return group(*choices) + "?" 2949 2950 2951# Note: we use unicode matching for names ("\w") but ascii matching for 2952# number literals. 2953Whitespace = r"[ \f\t]*" 2954Comment = r"#[^\r\n]*" 2955Ignore = Whitespace + tokany(r"\\\r?\n" + Whitespace) + maybe(Comment) 2956Name_RE = r"\$?\w+" 2957 2958Hexnumber = r"0[xX](?:_?[0-9a-fA-F])+" 2959Binnumber = r"0[bB](?:_?[01])+" 2960Octnumber = r"0[oO](?:_?[0-7])+" 2961Decnumber = r"(?:0(?:_?0)*|[1-9](?:_?[0-9])*)" 2962Intnumber = group(Hexnumber, Binnumber, Octnumber, Decnumber) 2963Exponent = r"[eE][-+]?[0-9](?:_?[0-9])*" 2964Pointfloat = group( 2965 r"[0-9](?:_?[0-9])*\.(?:[0-9](?:_?[0-9])*)?", r"\.[0-9](?:_?[0-9])*" 2966) + maybe(Exponent) 2967Expfloat = r"[0-9](?:_?[0-9])*" + Exponent 2968Floatnumber = group(Pointfloat, Expfloat) 2969Imagnumber = group(r"[0-9](?:_?[0-9])*[jJ]", Floatnumber + r"[jJ]") 2970Number = group(Imagnumber, Floatnumber, Intnumber) 2971 2972StringPrefix = r"(?:[bBp][rR]?|[rR][bBpfF]?|[uU]|[fF][rR]?)?" 2973 2974# Tail end of ' string. 2975Single = r"[^'\\]*(?:\\.[^'\\]*)*'" 2976# Tail end of " string. 2977Double = r'[^"\\]*(?:\\.[^"\\]*)*"' 2978# Tail end of ''' string. 2979Single3 = r"[^'\\]*(?:(?:\\.|'(?!''))[^'\\]*)*'''" 2980# Tail end of """ string. 2981Double3 = r'[^"\\]*(?:(?:\\.|"(?!""))[^"\\]*)*"""' 2982Triple = group(StringPrefix + "'''", StringPrefix + '"""') 2983# Single-line ' or " string. 2984String = group( 2985 StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*'", 2986 StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*"', 2987) 2988 2989# Xonsh-specific Syntax 2990SearchPath = r"((?:[rgp]+|@\w*)?)`([^\n`\\]*(?:\\.[^\n`\\]*)*)`" 2991 2992# Because of leftmost-then-longest match semantics, be sure to put the 2993# longest operators first (e.g., if = came before ==, == would get 2994# recognized as two instances of =). 2995_redir_names = ("out", "all", "err", "e", "2", "a", "&", "1", "o") 2996_redir_map = ( 2997 # stderr to stdout 2998 "err>out", 2999 "err>&1", 3000 "2>out", 3001 "err>o", 3002 "err>1", 3003 "e>out", 3004 "e>&1", 3005 "2>&1", 3006 "e>o", 3007 "2>o", 3008 "e>1", 3009 "2>1", 3010 # stdout to stderr 3011 "out>err", 3012 "out>&2", 3013 "1>err", 3014 "out>e", 3015 "out>2", 3016 "o>err", 3017 "o>&2", 3018 "1>&2", 3019 "o>e", 3020 "1>e", 3021 "o>2", 3022 "1>2", 3023) 3024IORedirect = group(group(*_redir_map), "{}>>?".format(group(*_redir_names))) 3025_redir_check = set(_redir_map) 3026_redir_check = {"{}>".format(i) for i in _redir_names}.union(_redir_check) 3027_redir_check = {"{}>>".format(i) for i in _redir_names}.union(_redir_check) 3028_redir_check = frozenset(_redir_check) 3029Operator = group( 3030 r"\*\*=?", 3031 r">>=?", 3032 r"<<=?", 3033 r"!=", 3034 r"//=?", 3035 r"->", 3036 r"@\$\(?", 3037 r"\|\|", 3038 "&&", 3039 r"@\(", 3040 r"!\(", 3041 r"!\[", 3042 r"\$\(", 3043 r"\$\[", 3044 "\${", 3045 r"\?\?", 3046 r"\?", 3047 AUGASSIGN_OPS, 3048 r"~", 3049) 3050 3051Bracket = "[][(){}]" 3052Special = group(r"\r?\n", r"\.\.\.", r"[:;.,@]") 3053Funny = group(Operator, Bracket, Special) 3054 3055PlainToken = group(IORedirect, Number, Funny, String, Name_RE, SearchPath) 3056 3057# First (or only) line of ' or " string. 3058ContStr = group( 3059 StringPrefix + r"'[^\n'\\]*(?:\\.[^\n'\\]*)*" + group("'", r"\\\r?\n"), 3060 StringPrefix + r'"[^\n"\\]*(?:\\.[^\n"\\]*)*' + group('"', r"\\\r?\n"), 3061) 3062PseudoExtras = group(r"\\\r?\n|\Z", Comment, Triple, SearchPath) 3063PseudoToken = Whitespace + group( 3064 PseudoExtras, IORedirect, Number, Funny, ContStr, Name_RE 3065) 3066 3067 3068def _compile(expr): 3069 return re.compile(expr, re.UNICODE) 3070 3071 3072endpats = { 3073 "'": Single, 3074 '"': Double, 3075 "'''": Single3, 3076 '"""': Double3, 3077 "r'''": Single3, 3078 'r"""': Double3, 3079 "b'''": Single3, 3080 'b"""': Double3, 3081 "f'''": Single3, 3082 'f"""': Double3, 3083 "R'''": Single3, 3084 'R"""': Double3, 3085 "B'''": Single3, 3086 'B"""': Double3, 3087 "F'''": Single3, 3088 'F"""': Double3, 3089 "br'''": Single3, 3090 'br"""': Double3, 3091 "fr'''": Single3, 3092 'fr"""': Double3, 3093 "bR'''": Single3, 3094 'bR"""': Double3, 3095 "Br'''": Single3, 3096 'Br"""': Double3, 3097 "BR'''": Single3, 3098 'BR"""': Double3, 3099 "rb'''": Single3, 3100 'rb"""': Double3, 3101 "rf'''": Single3, 3102 'rf"""': Double3, 3103 "Rb'''": Single3, 3104 'Rb"""': Double3, 3105 "Fr'''": Single3, 3106 'Fr"""': Double3, 3107 "rB'''": Single3, 3108 'rB"""': Double3, 3109 "rF'''": Single3, 3110 'rF"""': Double3, 3111 "RB'''": Single3, 3112 'RB"""': Double3, 3113 "RF'''": Single3, 3114 'RF"""': Double3, 3115 "u'''": Single3, 3116 'u"""': Double3, 3117 "U'''": Single3, 3118 'U"""': Double3, 3119 "p'''": Single3, 3120 'p"""': Double3, 3121 "pr'''": Single3, 3122 'pr"""': Double3, 3123 "pR'''": Single3, 3124 'pR"""': Double3, 3125 "rp'''": Single3, 3126 'rp"""': Double3, 3127 "Rp'''": Single3, 3128 'Rp"""': Double3, 3129 "r": None, 3130 "R": None, 3131 "b": None, 3132 "B": None, 3133 "u": None, 3134 "U": None, 3135 "p": None, 3136 "f": None, 3137 "F": None, 3138} 3139 3140triple_quoted = {} 3141for t in ( 3142 "'''", 3143 '"""', 3144 "r'''", 3145 'r"""', 3146 "R'''", 3147 'R"""', 3148 "b'''", 3149 'b"""', 3150 "B'''", 3151 'B"""', 3152 "f'''", 3153 'f"""', 3154 "F'''", 3155 'F"""', 3156 "br'''", 3157 'br"""', 3158 "Br'''", 3159 'Br"""', 3160 "bR'''", 3161 'bR"""', 3162 "BR'''", 3163 'BR"""', 3164 "rb'''", 3165 'rb"""', 3166 "rB'''", 3167 'rB"""', 3168 "Rb'''", 3169 'Rb"""', 3170 "RB'''", 3171 'RB"""', 3172 "fr'''", 3173 'fr"""', 3174 "Fr'''", 3175 'Fr"""', 3176 "fR'''", 3177 'fR"""', 3178 "FR'''", 3179 'FR"""', 3180 "rf'''", 3181 'rf"""', 3182 "rF'''", 3183 'rF"""', 3184 "Rf'''", 3185 'Rf"""', 3186 "RF'''", 3187 'RF"""', 3188 "u'''", 3189 'u"""', 3190 "U'''", 3191 'U"""', 3192 "p'''", 3193 'p""""', 3194 "pr'''", 3195 'pr""""', 3196 "pR'''", 3197 'pR""""', 3198 "rp'''", 3199 'rp""""', 3200 "Rp'''", 3201 'Rp""""', 3202): 3203 triple_quoted[t] = t 3204single_quoted = {} 3205for t in ( 3206 "'", 3207 '"', 3208 "r'", 3209 'r"', 3210 "R'", 3211 'R"', 3212 "b'", 3213 'b"', 3214 "B'", 3215 'B"', 3216 "f'", 3217 'f"', 3218 "F'", 3219 'F"', 3220 "br'", 3221 'br"', 3222 "Br'", 3223 'Br"', 3224 "bR'", 3225 'bR"', 3226 "BR'", 3227 'BR"', 3228 "rb'", 3229 'rb"', 3230 "rB'", 3231 'rB"', 3232 "Rb'", 3233 'Rb"', 3234 "RB'", 3235 'RB"', 3236 "fr'", 3237 'fr"', 3238 "Fr'", 3239 'Fr"', 3240 "fR'", 3241 'fR"', 3242 "FR'", 3243 'FR"', 3244 "rf'", 3245 'rf"', 3246 "rF'", 3247 'rF"', 3248 "Rf'", 3249 'Rf"', 3250 "RF'", 3251 'RF"', 3252 "u'", 3253 'u"', 3254 "U'", 3255 'U"', 3256 "p'", 3257 'p"', 3258 "pr'", 3259 'pr"', 3260 "pR'", 3261 'pR"', 3262 "rp'", 3263 'rp"', 3264 "Rp'", 3265 'Rp"', 3266): 3267 single_quoted[t] = t 3268 3269tabsize = 8 3270 3271 3272class TokenError(Exception): 3273 pass 3274 3275 3276class StopTokenizing(Exception): 3277 pass 3278 3279 3280class Untokenizer: 3281 def __init__(self): 3282 self.tokens = [] 3283 self.prev_row = 1 3284 self.prev_col = 0 3285 self.encoding = None 3286 3287 def add_whitespace(self, start): 3288 row, col = start 3289 if row < self.prev_row or row == self.prev_row and col < self.prev_col: 3290 raise ValueError( 3291 "start ({},{}) precedes previous end ({},{})".format( 3292 row, col, self.prev_row, self.prev_col 3293 ) 3294 ) 3295 row_offset = row - self.prev_row 3296 if row_offset: 3297 self.tokens.append("\\\n" * row_offset) 3298 self.prev_col = 0 3299 col_offset = col - self.prev_col 3300 if col_offset: 3301 self.tokens.append(" " * col_offset) 3302 3303 def untokenize(self, iterable): 3304 it = iter(iterable) 3305 indents = [] 3306 startline = False 3307 for t in it: 3308 if len(t) == 2: 3309 self.compat(t, it) 3310 break 3311 tok_type, token, start, end, line = t 3312 if tok_type == ENCODING: 3313 self.encoding = token 3314 continue 3315 if tok_type == ENDMARKER: 3316 break 3317 if tok_type == INDENT: 3318 indents.append(token) 3319 continue 3320 elif tok_type == DEDENT: 3321 indents.pop() 3322 self.prev_row, self.prev_col = end 3323 continue 3324 elif tok_type in (NEWLINE, NL): 3325 startline = True 3326 elif startline and indents: 3327 indent = indents[-1] 3328 if start[1] >= len(indent): 3329 self.tokens.append(indent) 3330 self.prev_col = len(indent) 3331 startline = False 3332 self.add_whitespace(start) 3333 self.tokens.append(token) 3334 self.prev_row, self.prev_col = end 3335 if tok_type in (NEWLINE, NL): 3336 self.prev_row += 1 3337 self.prev_col = 0 3338 return "".join(self.tokens) 3339 3340 def compat(self, token, iterable): 3341 indents = [] 3342 toks_append = self.tokens.append 3343 startline = token[0] in (NEWLINE, NL) 3344 prevstring = False 3345 3346 for tok in itertools.chain([token], iterable): 3347 toknum, tokval = tok[:2] 3348 if toknum == ENCODING: 3349 self.encoding = tokval 3350 continue 3351 3352 if toknum in ADDSPACE_TOKS: 3353 tokval += " " 3354 3355 # Insert a space between two consecutive strings 3356 if toknum == STRING: 3357 if prevstring: 3358 tokval = " " + tokval 3359 prevstring = True 3360 else: 3361 prevstring = False 3362 3363 if toknum == INDENT: 3364 indents.append(tokval) 3365 continue 3366 elif toknum == DEDENT: 3367 indents.pop() 3368 continue 3369 elif toknum in (NEWLINE, NL): 3370 startline = True 3371 elif startline and indents: 3372 toks_append(indents[-1]) 3373 startline = False 3374 toks_append(tokval) 3375 3376 3377def untokenize(iterable): 3378 """Transform tokens back into Python source code. 3379 It returns a bytes object, encoded using the ENCODING 3380 token, which is the first token sequence output by tokenize. 3381 3382 Each element returned by the iterable must be a token sequence 3383 with at least two elements, a token number and token value. If 3384 only two tokens are passed, the resulting output is poor. 3385 3386 Round-trip invariant for full input: 3387 Untokenized source will match input source exactly 3388 3389 Round-trip invariant for limited intput: 3390 # Output bytes will tokenize the back to the input 3391 t1 = [tok[:2] for tok in tokenize(f.readline)] 3392 newcode = untokenize(t1) 3393 readline = BytesIO(newcode).readline 3394 t2 = [tok[:2] for tok in tokenize(readline)] 3395 assert t1 == t2 3396 """ 3397 ut = Untokenizer() 3398 out = ut.untokenize(iterable) 3399 if ut.encoding is not None: 3400 out = out.encode(ut.encoding) 3401 return out 3402 3403 3404def _get_normal_name(orig_enc): 3405 """Imitates get_normal_name in tokenizer.c.""" 3406 # Only care about the first 12 characters. 3407 enc = orig_enc[:12].lower().replace("_", "-") 3408 if enc == "utf-8" or enc.startswith("utf-8-"): 3409 return "utf-8" 3410 if enc in ("latin-1", "iso-8859-1", "iso-latin-1") or enc.startswith( 3411 ("latin-1-", "iso-8859-1-", "iso-latin-1-") 3412 ): 3413 return "iso-8859-1" 3414 return orig_enc 3415 3416 3417def detect_encoding(readline): 3418 """ 3419 The detect_encoding() function is used to detect the encoding that should 3420 be used to decode a Python source file. It requires one argument, readline, 3421 in the same way as the tokenize() generator. 3422 3423 It will call readline a maximum of twice, and return the encoding used 3424 (as a string) and a list of any lines (left as bytes) it has read in. 3425 3426 It detects the encoding from the presence of a utf-8 bom or an encoding 3427 cookie as specified in pep-0263. If both a bom and a cookie are present, 3428 but disagree, a SyntaxError will be raised. If the encoding cookie is an 3429 invalid charset, raise a SyntaxError. Note that if a utf-8 bom is found, 3430 'utf-8-sig' is returned. 3431 3432 If no encoding is specified, then the default of 'utf-8' will be returned. 3433 """ 3434 try: 3435 filename = readline.__self__.name 3436 except AttributeError: 3437 filename = None 3438 bom_found = False 3439 encoding = None 3440 default = "utf-8" 3441 3442 def read_or_stop(): 3443 try: 3444 return readline() 3445 except StopIteration: 3446 return b"" 3447 3448 def find_cookie(line): 3449 try: 3450 # Decode as UTF-8. Either the line is an encoding declaration, 3451 # in which case it should be pure ASCII, or it must be UTF-8 3452 # per default encoding. 3453 line_string = line.decode("utf-8") 3454 except UnicodeDecodeError: 3455 msg = "invalid or missing encoding declaration" 3456 if filename is not None: 3457 msg = "{} for {!r}".format(msg, filename) 3458 raise SyntaxError(msg) 3459 3460 match = cookie_re.match(line_string) 3461 if not match: 3462 return None 3463 encoding = _get_normal_name(match.group(1)) 3464 try: 3465 codecs.lookup(encoding) 3466 except LookupError: 3467 # This behaviour mimics the Python interpreter 3468 if filename is None: 3469 msg = "unknown encoding: " + encoding 3470 else: 3471 msg = "unknown encoding for {!r}: {}".format(filename, encoding) 3472 raise SyntaxError(msg) 3473 3474 if bom_found: 3475 if encoding != "utf-8": 3476 # This behaviour mimics the Python interpreter 3477 if filename is None: 3478 msg = "encoding problem: utf-8" 3479 else: 3480 msg = "encoding problem for {!r}: utf-8".format(filename) 3481 raise SyntaxError(msg) 3482 encoding += "-sig" 3483 return encoding 3484 3485 first = read_or_stop() 3486 if first.startswith(codecs.BOM_UTF8): 3487 bom_found = True 3488 first = first[3:] 3489 default = "utf-8-sig" 3490 if not first: 3491 return default, [] 3492 3493 encoding = find_cookie(first) 3494 if encoding: 3495 return encoding, [first] 3496 if not blank_re.match(first): 3497 return default, [first] 3498 3499 second = read_or_stop() 3500 if not second: 3501 return default, [first] 3502 3503 encoding = find_cookie(second) 3504 if encoding: 3505 return encoding, [first, second] 3506 3507 return default, [first, second] 3508 3509 3510def tokopen(filename): 3511 """Open a file in read only mode using the encoding detected by 3512 detect_encoding(). 3513 """ 3514 buffer = builtins.open(filename, "rb") 3515 try: 3516 encoding, lines = detect_encoding(buffer.readline) 3517 buffer.seek(0) 3518 text = io.TextIOWrapper(buffer, encoding, line_buffering=True) 3519 text.mode = "r" 3520 return text 3521 except Exception: 3522 buffer.close() 3523 raise 3524 3525 3526def _tokenize(readline, encoding): 3527 lnum = parenlev = continued = 0 3528 numchars = "0123456789" 3529 contstr, needcont = "", 0 3530 contline = None 3531 indents = [0] 3532 3533 # 'stashed' and 'async_*' are used for async/await parsing 3534 stashed = None 3535 async_def = False 3536 async_def_indent = 0 3537 async_def_nl = False 3538 3539 if encoding is not None: 3540 if encoding == "utf-8-sig": 3541 # BOM will already have been stripped. 3542 encoding = "utf-8" 3543 yield TokenInfo(ENCODING, encoding, (0, 0), (0, 0), "") 3544 while True: # loop over lines in stream 3545 try: 3546 line = readline() 3547 except StopIteration: 3548 line = b"" 3549 3550 if encoding is not None: 3551 line = line.decode(encoding) 3552 lnum += 1 3553 pos, max = 0, len(line) 3554 3555 if contstr: # continued string 3556 if not line: 3557 raise TokenError("EOF in multi-line string", strstart) 3558 endmatch = endprog.match(line) 3559 if endmatch: 3560 pos = end = endmatch.end(0) 3561 yield TokenInfo( 3562 STRING, contstr + line[:end], strstart, (lnum, end), contline + line 3563 ) 3564 contstr, needcont = "", 0 3565 contline = None 3566 elif needcont and line[-2:] != "\\\n" and line[-3:] != "\\\r\n": 3567 yield TokenInfo( 3568 ERRORTOKEN, contstr + line, strstart, (lnum, len(line)), contline 3569 ) 3570 contstr = "" 3571 contline = None 3572 continue 3573 else: 3574 contstr = contstr + line 3575 contline = contline + line 3576 continue 3577 3578 elif parenlev == 0 and not continued: # new statement 3579 if not line: 3580 break 3581 column = 0 3582 while pos < max: # measure leading whitespace 3583 if line[pos] == " ": 3584 column += 1 3585 elif line[pos] == "\t": 3586 column = (column // tabsize + 1) * tabsize 3587 elif line[pos] == "\f": 3588 column = 0 3589 else: 3590 break 3591 pos += 1 3592 if pos == max: 3593 break 3594 3595 if line[pos] in "#\r\n": # skip comments or blank lines 3596 if line[pos] == "#": 3597 comment_token = line[pos:].rstrip("\r\n") 3598 nl_pos = pos + len(comment_token) 3599 yield TokenInfo( 3600 COMMENT, 3601 comment_token, 3602 (lnum, pos), 3603 (lnum, pos + len(comment_token)), 3604 line, 3605 ) 3606 yield TokenInfo( 3607 NL, line[nl_pos:], (lnum, nl_pos), (lnum, len(line)), line 3608 ) 3609 else: 3610 yield TokenInfo( 3611 (NL, COMMENT)[line[pos] == "#"], 3612 line[pos:], 3613 (lnum, pos), 3614 (lnum, len(line)), 3615 line, 3616 ) 3617 continue 3618 3619 if column > indents[-1]: # count indents or dedents 3620 indents.append(column) 3621 yield TokenInfo(INDENT, line[:pos], (lnum, 0), (lnum, pos), line) 3622 while column < indents[-1]: 3623 if column not in indents: 3624 raise IndentationError( 3625 "unindent does not match any outer indentation level", 3626 ("<tokenize>", lnum, pos, line), 3627 ) 3628 indents = indents[:-1] 3629 3630 if async_def and async_def_indent >= indents[-1]: 3631 async_def = False 3632 async_def_nl = False 3633 async_def_indent = 0 3634 3635 yield TokenInfo(DEDENT, "", (lnum, pos), (lnum, pos), line) 3636 3637 if async_def and async_def_nl and async_def_indent >= indents[-1]: 3638 async_def = False 3639 async_def_nl = False 3640 async_def_indent = 0 3641 3642 else: # continued statement 3643 if not line: 3644 raise TokenError("EOF in multi-line statement", (lnum, 0)) 3645 continued = 0 3646 3647 while pos < max: 3648 pseudomatch = _compile(PseudoToken).match(line, pos) 3649 if pseudomatch: # scan for tokens 3650 start, end = pseudomatch.span(1) 3651 spos, epos, pos = (lnum, start), (lnum, end), end 3652 if start == end: 3653 continue 3654 token, initial = line[start:end], line[start] 3655 3656 if token in _redir_check: 3657 yield TokenInfo(IOREDIRECT, token, spos, epos, line) 3658 elif initial in numchars or ( # ordinary number 3659 initial == "." and token != "." and token != "..." 3660 ): 3661 yield TokenInfo(NUMBER, token, spos, epos, line) 3662 elif initial in "\r\n": 3663 if stashed: 3664 yield stashed 3665 stashed = None 3666 if parenlev > 0: 3667 yield TokenInfo(NL, token, spos, epos, line) 3668 else: 3669 yield TokenInfo(NEWLINE, token, spos, epos, line) 3670 if async_def: 3671 async_def_nl = True 3672 3673 elif initial == "#": 3674 assert not token.endswith("\n") 3675 if stashed: 3676 yield stashed 3677 stashed = None 3678 yield TokenInfo(COMMENT, token, spos, epos, line) 3679 # Xonsh-specific Regex Globbing 3680 elif re.match(SearchPath, token): 3681 yield TokenInfo(SEARCHPATH, token, spos, epos, line) 3682 elif token in triple_quoted: 3683 endprog = _compile(endpats[token]) 3684 endmatch = endprog.match(line, pos) 3685 if endmatch: # all on one line 3686 pos = endmatch.end(0) 3687 token = line[start:pos] 3688 yield TokenInfo(STRING, token, spos, (lnum, pos), line) 3689 else: 3690 strstart = (lnum, start) # multiple lines 3691 contstr = line[start:] 3692 contline = line 3693 break 3694 elif ( 3695 initial in single_quoted 3696 or token[:2] in single_quoted 3697 or token[:3] in single_quoted 3698 ): 3699 if token[-1] == "\n": # continued string 3700 strstart = (lnum, start) 3701 endprog = _compile( 3702 endpats[initial] or endpats[token[1]] or endpats[token[2]] 3703 ) 3704 contstr, needcont = line[start:], 1 3705 contline = line 3706 break 3707 else: # ordinary string 3708 yield TokenInfo(STRING, token, spos, epos, line) 3709 elif token.startswith("$") and token[1:].isidentifier(): 3710 yield TokenInfo(DOLLARNAME, token, spos, epos, line) 3711 elif initial.isidentifier(): # ordinary name 3712 if token in ("async", "await"): 3713 if async_def: 3714 yield TokenInfo( 3715 ASYNC if token == "async" else AWAIT, 3716 token, 3717 spos, 3718 epos, 3719 line, 3720 ) 3721 continue 3722 3723 tok = TokenInfo(NAME, token, spos, epos, line) 3724 if token == "async" and not stashed: 3725 stashed = tok 3726 continue 3727 3728 if ( 3729 HAS_ASYNC 3730 and token == "def" 3731 and ( 3732 stashed 3733 and stashed.type == NAME 3734 and stashed.string == "async" 3735 ) 3736 ): 3737 async_def = True 3738 async_def_indent = indents[-1] 3739 3740 yield TokenInfo( 3741 ASYNC, 3742 stashed.string, 3743 stashed.start, 3744 stashed.end, 3745 stashed.line, 3746 ) 3747 stashed = None 3748 3749 if stashed: 3750 yield stashed 3751 stashed = None 3752 3753 yield tok 3754 elif token == "\\\n" or token == "\\\r\n": # continued stmt 3755 continued = 1 3756 yield TokenInfo(ERRORTOKEN, token, spos, epos, line) 3757 elif initial == "\\": # continued stmt 3758 # for cases like C:\\path\\to\\file 3759 continued = 1 3760 else: 3761 if initial in "([{": 3762 parenlev += 1 3763 elif initial in ")]}": 3764 parenlev -= 1 3765 elif token in additional_parenlevs: 3766 parenlev += 1 3767 if stashed: 3768 yield stashed 3769 stashed = None 3770 yield TokenInfo(OP, token, spos, epos, line) 3771 else: 3772 yield TokenInfo( 3773 ERRORTOKEN, line[pos], (lnum, pos), (lnum, pos + 1), line 3774 ) 3775 pos += 1 3776 3777 if stashed: 3778 yield stashed 3779 stashed = None 3780 3781 for indent in indents[1:]: # pop remaining indent levels 3782 yield TokenInfo(DEDENT, "", (lnum, 0), (lnum, 0), "") 3783 yield TokenInfo(ENDMARKER, "", (lnum, 0), (lnum, 0), "") 3784 3785 3786def tokenize(readline): 3787 """ 3788 The tokenize() generator requires one argument, readline, which 3789 must be a callable object which provides the same interface as the 3790 readline() method of built-in file objects. Each call to the function 3791 should return one line of input as bytes. Alternately, readline 3792 can be a callable function terminating with StopIteration: 3793 readline = open(myfile, 'rb').__next__ # Example of alternate readline 3794 3795 The generator produces 5-tuples with these members: the token type; the 3796 token string; a 2-tuple (srow, scol) of ints specifying the row and 3797 column where the token begins in the source; a 2-tuple (erow, ecol) of 3798 ints specifying the row and column where the token ends in the source; 3799 and the line on which the token was found. The line passed is the 3800 logical line; continuation lines are included. 3801 3802 The first token sequence will always be an ENCODING token 3803 which tells you which encoding was used to decode the bytes stream. 3804 """ 3805 encoding, consumed = detect_encoding(readline) 3806 rl_gen = iter(readline, b"") 3807 empty = itertools.repeat(b"") 3808 return _tokenize(itertools.chain(consumed, rl_gen, empty).__next__, encoding) 3809 3810 3811# An undocumented, backwards compatible, API for all the places in the standard 3812# library that expect to be able to use tokenize with strings 3813def generate_tokens(readline): 3814 return _tokenize(readline, None) 3815 3816 3817def tokenize_main(): 3818 import argparse 3819 3820 # Helper error handling routines 3821 def perror(message): 3822 print(message, file=sys.stderr) 3823 3824 def error(message, filename=None, location=None): 3825 if location: 3826 args = (filename,) + location + (message,) 3827 perror("%s:%d:%d: error: %s" % args) 3828 elif filename: 3829 perror("%s: error: %s" % (filename, message)) 3830 else: 3831 perror("error: %s" % message) 3832 sys.exit(1) 3833 3834 # Parse the arguments and options 3835 parser = argparse.ArgumentParser(prog="python -m tokenize") 3836 parser.add_argument( 3837 dest="filename", 3838 nargs="?", 3839 metavar="filename.py", 3840 help="the file to tokenize; defaults to stdin", 3841 ) 3842 parser.add_argument( 3843 "-e", 3844 "--exact", 3845 dest="exact", 3846 action="store_true", 3847 help="display token names using the exact type", 3848 ) 3849 args = parser.parse_args() 3850 3851 try: 3852 # Tokenize the input 3853 if args.filename: 3854 filename = args.filename 3855 with builtins.open(filename, "rb") as f: 3856 tokens = list(tokenize(f.readline)) 3857 else: 3858 filename = "<stdin>" 3859 tokens = _tokenize(sys.stdin.readline, None) 3860 3861 # Output the tokenization 3862 for token in tokens: 3863 token_type = token.type 3864 if args.exact: 3865 token_type = token.exact_type 3866 token_range = "%d,%d-%d,%d:" % (token.start + token.end) 3867 print("%-20s%-15s%-15r" % (token_range, tok_name[token_type], token.string)) 3868 except IndentationError as err: 3869 line, column = err.args[1][1:3] 3870 error(err.args[0], filename, (line, column)) 3871 except TokenError as err: 3872 line, column = err.args[1] 3873 error(err.args[0], filename, (line, column)) 3874 except SyntaxError as err: 3875 error(err, filename) 3876 except OSError as err: 3877 error(err) 3878 except KeyboardInterrupt: 3879 print("interrupted\n") 3880 except Exception as err: 3881 perror("unexpected error: %s" % err) 3882 raise 3883 3884# 3885# tools 3886# 3887# -*- coding: utf-8 -*- 3888"""Misc. xonsh tools. 3889 3890The following implementations were forked from the IPython project: 3891 3892* Copyright (c) 2008-2014, IPython Development Team 3893* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> 3894* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 3895* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 3896 3897Implementations: 3898 3899* decode() 3900* encode() 3901* cast_unicode() 3902* safe_hasattr() 3903* indent() 3904 3905""" 3906# amalgamated builtins 3907# amalgamated collections 3908# amalgamated collections.abc 3909# amalgamated contextlib 3910# amalgamated ctypes 3911# amalgamated datetime 3912from distutils.version import LooseVersion 3913# amalgamated functools 3914glob = _LazyModule.load('glob', 'glob') 3915# amalgamated itertools 3916# amalgamated os 3917# amalgamated pathlib 3918# amalgamated re 3919# amalgamated subprocess 3920# amalgamated sys 3921# amalgamated threading 3922traceback = _LazyModule.load('traceback', 'traceback') 3923warnings = _LazyModule.load('warnings', 'warnings') 3924operator = _LazyModule.load('operator', 'operator') 3925from xonsh import __version__ 3926# amalgamated xonsh.lazyasd 3927# amalgamated xonsh.platform 3928@functools.lru_cache(1) 3929def is_superuser(): 3930 if ON_WINDOWS: 3931 rtn = ctypes.windll.shell32.IsUserAnAdmin() != 0 3932 else: 3933 rtn = os.getuid() == 0 3934 return rtn 3935 3936 3937class XonshError(Exception): 3938 pass 3939 3940 3941class XonshCalledProcessError(XonshError, subprocess.CalledProcessError): 3942 """Raised when there's an error with a called process 3943 3944 Inherits from XonshError and subprocess.CalledProcessError, catching 3945 either will also catch this error. 3946 3947 Raised *after* iterating over stdout of a captured command, if the 3948 returncode of the command is nonzero. 3949 3950 Example: 3951 try: 3952 for line in !(ls): 3953 print(line) 3954 except subprocess.CalledProcessError as error: 3955 print("Error in process: {}.format(error.completed_command.pid)) 3956 3957 This also handles differences between Python3.4 and 3.5 where 3958 CalledProcessError is concerned. 3959 """ 3960 3961 def __init__( 3962 self, returncode, command, output=None, stderr=None, completed_command=None 3963 ): 3964 super().__init__(returncode, command, output) 3965 self.stderr = stderr 3966 self.completed_command = completed_command 3967 3968 3969def expand_path(s, expand_user=True): 3970 """Takes a string path and expands ~ to home if expand_user is set 3971 and environment vars if EXPAND_ENV_VARS is set.""" 3972 env = getattr(builtins, "__xonsh_env__", os_environ) 3973 if env.get("EXPAND_ENV_VARS", False): 3974 s = expandvars(s) 3975 if expand_user: 3976 # expand ~ according to Bash unquoted rules "Each variable assignment is 3977 # checked for unquoted tilde-prefixes immediately following a ':' or the 3978 # first '='". See the following for more details. 3979 # https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html 3980 pre, char, post = s.partition("=") 3981 if char: 3982 s = expanduser(pre) + char 3983 s += os.pathsep.join(map(expanduser, post.split(os.pathsep))) 3984 else: 3985 s = expanduser(s) 3986 return s 3987 3988 3989def _expandpath(path): 3990 """Performs environment variable / user expansion on a given path 3991 if EXPAND_ENV_VARS is set. 3992 """ 3993 env = getattr(builtins, "__xonsh_env__", os_environ) 3994 expand_user = env.get("EXPAND_ENV_VARS", False) 3995 return expand_path(path, expand_user=expand_user) 3996 3997 3998def decode_bytes(b): 3999 """Tries to decode the bytes using XONSH_ENCODING if available, 4000 otherwise using sys.getdefaultencoding(). 4001 """ 4002 env = getattr(builtins, "__xonsh_env__", os_environ) 4003 enc = env.get("XONSH_ENCODING") or DEFAULT_ENCODING 4004 err = env.get("XONSH_ENCODING_ERRORS") or "strict" 4005 return b.decode(encoding=enc, errors=err) 4006 4007 4008def findfirst(s, substrs): 4009 """Finds whichever of the given substrings occurs first in the given string 4010 and returns that substring, or returns None if no such strings occur. 4011 """ 4012 i = len(s) 4013 result = None 4014 for substr in substrs: 4015 pos = s.find(substr) 4016 if -1 < pos < i: 4017 i = pos 4018 result = substr 4019 return i, result 4020 4021 4022class EnvPath(collections.MutableSequence): 4023 """A class that implements an environment path, which is a list of 4024 strings. Provides a custom method that expands all paths if the 4025 relevant env variable has been set. 4026 """ 4027 4028 def __init__(self, args=None): 4029 if not args: 4030 self._l = [] 4031 else: 4032 if isinstance(args, str): 4033 self._l = args.split(os.pathsep) 4034 elif isinstance(args, pathlib.Path): 4035 self._l = [args] 4036 elif isinstance(args, bytes): 4037 # decode bytes to a string and then split based on 4038 # the default path separator 4039 self._l = decode_bytes(args).split(os.pathsep) 4040 elif isinstance(args, collections.Iterable): 4041 # put everything in a list -before- performing the type check 4042 # in order to be able to retrieve it later, for cases such as 4043 # when a generator expression was passed as an argument 4044 args = list(args) 4045 if not all(isinstance(i, (str, bytes, pathlib.Path)) for i in args): 4046 # make TypeError's message as informative as possible 4047 # when given an invalid initialization sequence 4048 raise TypeError( 4049 "EnvPath's initialization sequence should only " 4050 "contain str, bytes and pathlib.Path entries" 4051 ) 4052 self._l = args 4053 else: 4054 raise TypeError( 4055 "EnvPath cannot be initialized with items " 4056 "of type %s" % type(args) 4057 ) 4058 4059 def __getitem__(self, item): 4060 # handle slices separately 4061 if isinstance(item, slice): 4062 return [_expandpath(i) for i in self._l[item]] 4063 else: 4064 return _expandpath(self._l[item]) 4065 4066 def __setitem__(self, index, item): 4067 self._l.__setitem__(index, item) 4068 4069 def __len__(self): 4070 return len(self._l) 4071 4072 def __delitem__(self, key): 4073 self._l.__delitem__(key) 4074 4075 def insert(self, index, value): 4076 self._l.insert(index, value) 4077 4078 @property 4079 def paths(self): 4080 """ 4081 Returns the list of directories that this EnvPath contains. 4082 """ 4083 return list(self) 4084 4085 def __repr__(self): 4086 return repr(self._l) 4087 4088 def __eq__(self, other): 4089 if len(self) != len(other): 4090 return False 4091 return all(map(operator.eq, self, other)) 4092 4093 def _repr_pretty_(self, p, cycle): 4094 """ Pretty print path list """ 4095 if cycle: 4096 p.text("EnvPath(...)") 4097 else: 4098 with p.group(1, "EnvPath(\n[", "]\n)"): 4099 for idx, item in enumerate(self): 4100 if idx: 4101 p.text(",") 4102 p.breakable() 4103 p.pretty(item) 4104 4105 def __add__(self, other): 4106 if isinstance(other, EnvPath): 4107 other = other._l 4108 return EnvPath(self._l + other) 4109 4110 def __radd__(self, other): 4111 if isinstance(other, EnvPath): 4112 other = other._l 4113 return EnvPath(other + self._l) 4114 4115 def add(self, data, front=False, replace=False): 4116 """Add a value to this EnvPath, 4117 4118 path.add(data, front=bool, replace=bool) -> ensures that path contains data, with position determined by kwargs 4119 4120 Parameters 4121 ---------- 4122 data : string or bytes or pathlib.Path 4123 value to be added 4124 front : bool 4125 whether the value should be added to the front, will be 4126 ignored if the data already exists in this EnvPath and 4127 replace is False 4128 Default : False 4129 replace : bool 4130 If True, the value will be removed and added to the 4131 start or end(depending on the value of front) 4132 Default : False 4133 4134 Returns 4135 ------- 4136 None 4137 4138 """ 4139 if data not in self._l: 4140 self._l.insert(0 if front else len(self._l), data) 4141 elif replace: 4142 self._l.remove(data) 4143 self._l.insert(0 if front else len(self._l), data) 4144 4145 4146class DefaultNotGivenType(object): 4147 """Singleton for representing when no default value is given.""" 4148 4149 __inst = None 4150 4151 def __new__(cls): 4152 if DefaultNotGivenType.__inst is None: 4153 DefaultNotGivenType.__inst = object.__new__(cls) 4154 return DefaultNotGivenType.__inst 4155 4156 4157DefaultNotGiven = DefaultNotGivenType() 4158 4159BEG_TOK_SKIPS = LazyObject( 4160 lambda: frozenset(["WS", "INDENT", "NOT", "LPAREN"]), globals(), "BEG_TOK_SKIPS" 4161) 4162END_TOK_TYPES = LazyObject( 4163 lambda: frozenset(["SEMI", "AND", "OR", "RPAREN"]), globals(), "END_TOK_TYPES" 4164) 4165RE_END_TOKS = LazyObject( 4166 lambda: re.compile("(;|and|\&\&|or|\|\||\))"), globals(), "RE_END_TOKS" 4167) 4168LPARENS = LazyObject( 4169 lambda: frozenset( 4170 ["LPAREN", "AT_LPAREN", "BANG_LPAREN", "DOLLAR_LPAREN", "ATDOLLAR_LPAREN"] 4171 ), 4172 globals(), 4173 "LPARENS", 4174) 4175 4176 4177def _is_not_lparen_and_rparen(lparens, rtok): 4178 """Tests if an RPAREN token is matched with something other than a plain old 4179 LPAREN type. 4180 """ 4181 # note that any([]) is False, so this covers len(lparens) == 0 4182 return rtok.type == "RPAREN" and any(x != "LPAREN" for x in lparens) 4183 4184 4185def balanced_parens(line, mincol=0, maxcol=None, lexer=None): 4186 """Determines if parentheses are balanced in an expression.""" 4187 line = line[mincol:maxcol] 4188 if lexer is None: 4189 lexer = builtins.__xonsh_execer__.parser.lexer 4190 if "(" not in line and ")" not in line: 4191 return True 4192 cnt = 0 4193 lexer.input(line) 4194 for tok in lexer: 4195 if tok.type in LPARENS: 4196 cnt += 1 4197 elif tok.type == "RPAREN": 4198 cnt -= 1 4199 elif tok.type == "ERRORTOKEN" and ")" in tok.value: 4200 cnt -= 1 4201 return cnt == 0 4202 4203 4204def find_next_break(line, mincol=0, lexer=None): 4205 """Returns the column number of the next logical break in subproc mode. 4206 This function may be useful in finding the maxcol argument of 4207 subproc_toks(). 4208 """ 4209 if mincol >= 1: 4210 line = line[mincol:] 4211 if lexer is None: 4212 lexer = builtins.__xonsh_execer__.parser.lexer 4213 if RE_END_TOKS.search(line) is None: 4214 return None 4215 maxcol = None 4216 lparens = [] 4217 lexer.input(line) 4218 for tok in lexer: 4219 if tok.type in LPARENS: 4220 lparens.append(tok.type) 4221 elif tok.type in END_TOK_TYPES: 4222 if _is_not_lparen_and_rparen(lparens, tok): 4223 lparens.pop() 4224 else: 4225 maxcol = tok.lexpos + mincol + 1 4226 break 4227 elif tok.type == "ERRORTOKEN" and ")" in tok.value: 4228 maxcol = tok.lexpos + mincol + 1 4229 break 4230 elif tok.type == "BANG": 4231 maxcol = mincol + len(line) + 1 4232 break 4233 return maxcol 4234 4235 4236def _offset_from_prev_lines(line, last): 4237 lines = line.splitlines(keepends=True)[:last] 4238 return sum(map(len, lines)) 4239 4240 4241def subproc_toks( 4242 line, mincol=-1, maxcol=None, lexer=None, returnline=False, greedy=False 4243): 4244 """Encapsulates tokens in a source code line in a uncaptured 4245 subprocess ![] starting at a minimum column. If there are no tokens 4246 (ie in a comment line) this returns None. If greedy is True, it will encapsulate 4247 normal parentheses. Greedy is False by default. 4248 """ 4249 if lexer is None: 4250 lexer = builtins.__xonsh_execer__.parser.lexer 4251 if maxcol is None: 4252 maxcol = len(line) + 1 4253 lexer.reset() 4254 lexer.input(line) 4255 toks = [] 4256 lparens = [] 4257 saw_macro = False 4258 end_offset = 0 4259 for tok in lexer: 4260 pos = tok.lexpos 4261 if tok.type not in END_TOK_TYPES and pos >= maxcol: 4262 break 4263 if tok.type == "BANG": 4264 saw_macro = True 4265 if saw_macro and tok.type not in ("NEWLINE", "DEDENT"): 4266 toks.append(tok) 4267 continue 4268 if tok.type in LPARENS: 4269 lparens.append(tok.type) 4270 if greedy and len(lparens) > 0 and "LPAREN" in lparens: 4271 toks.append(tok) 4272 if tok.type == "RPAREN": 4273 lparens.pop() 4274 continue 4275 if len(toks) == 0 and tok.type in BEG_TOK_SKIPS: 4276 continue # handle indentation 4277 elif len(toks) > 0 and toks[-1].type in END_TOK_TYPES: 4278 if _is_not_lparen_and_rparen(lparens, toks[-1]): 4279 lparens.pop() # don't continue or break 4280 elif pos < maxcol and tok.type not in ("NEWLINE", "DEDENT", "WS"): 4281 if not greedy: 4282 toks.clear() 4283 if tok.type in BEG_TOK_SKIPS: 4284 continue 4285 else: 4286 break 4287 if pos < mincol: 4288 continue 4289 toks.append(tok) 4290 if tok.type == "WS" and tok.value == "\\": 4291 pass # line continuation 4292 elif tok.type == "NEWLINE": 4293 break 4294 elif tok.type == "DEDENT": 4295 # fake a newline when dedenting without a newline 4296 tok.type = "NEWLINE" 4297 tok.value = "\n" 4298 tok.lineno -= 1 4299 if len(toks) >= 2: 4300 prev_tok_end = toks[-2].lexpos + len(toks[-2].value) 4301 else: 4302 prev_tok_end = len(line) 4303 if "#" in line[prev_tok_end:]: 4304 tok.lexpos = prev_tok_end # prevents wrapping comments 4305 else: 4306 tok.lexpos = len(line) 4307 break 4308 elif check_bad_str_token(tok): 4309 return 4310 else: 4311 if len(toks) > 0 and toks[-1].type in END_TOK_TYPES: 4312 if _is_not_lparen_and_rparen(lparens, toks[-1]): 4313 pass 4314 elif greedy and toks[-1].type == "RPAREN": 4315 pass 4316 else: 4317 toks.pop() 4318 if len(toks) == 0: 4319 return # handle comment lines 4320 tok = toks[-1] 4321 pos = tok.lexpos 4322 if isinstance(tok.value, str): 4323 end_offset = len(tok.value.rstrip()) 4324 else: 4325 el = line[pos:].split("#")[0].rstrip() 4326 end_offset = len(el) 4327 if len(toks) == 0: 4328 return # handle comment lines 4329 elif saw_macro or greedy: 4330 end_offset = len(toks[-1].value.rstrip()) + 1 4331 if toks[0].lineno != toks[-1].lineno: 4332 # handle multiline cases 4333 end_offset += _offset_from_prev_lines(line, toks[-1].lineno) 4334 beg, end = toks[0].lexpos, (toks[-1].lexpos + end_offset) 4335 end = len(line[:end].rstrip()) 4336 rtn = "![" + line[beg:end] + "]" 4337 if returnline: 4338 rtn = line[:beg] + rtn + line[end:] 4339 return rtn 4340 4341 4342def check_bad_str_token(tok): 4343 """Checks if a token is a bad string.""" 4344 if tok.type == "ERRORTOKEN" and tok.value == "EOF in multi-line string": 4345 return True 4346 elif isinstance(tok.value, str) and not check_quotes(tok.value): 4347 return True 4348 else: 4349 return False 4350 4351 4352def check_quotes(s): 4353 """Checks a string to make sure that if it starts with quotes, it also 4354 ends with quotes. 4355 """ 4356 starts_as_str = RE_BEGIN_STRING.match(s) is not None 4357 ends_as_str = s.endswith('"') or s.endswith("'") 4358 if not starts_as_str and not ends_as_str: 4359 ok = True 4360 elif starts_as_str and not ends_as_str: 4361 ok = False 4362 elif not starts_as_str and ends_as_str: 4363 ok = False 4364 else: 4365 m = RE_COMPLETE_STRING.match(s) 4366 ok = m is not None 4367 return ok 4368 4369 4370def _have_open_triple_quotes(s): 4371 if s.count('"""') % 2 == 1: 4372 open_triple = '"""' 4373 elif s.count("'''") % 2 == 1: 4374 open_triple = "'''" 4375 else: 4376 open_triple = False 4377 return open_triple 4378 4379 4380def get_line_continuation(): 4381 """ The line continuation characters used in subproc mode. In interactive 4382 mode on Windows the backslash must be preceded by a space. This is because 4383 paths on Windows may end in a backslash. 4384 """ 4385 if ( 4386 ON_WINDOWS 4387 and hasattr(builtins, "__xonsh_env__") 4388 and builtins.__xonsh_env__.get("XONSH_INTERACTIVE", False) 4389 ): 4390 return " \\" 4391 else: 4392 return "\\" 4393 4394 4395def get_logical_line(lines, idx): 4396 """Returns a single logical line (i.e. one without line continuations) 4397 from a list of lines. This line should begin at index idx. This also 4398 returns the number of physical lines the logical line spans. The lines 4399 should not contain newlines 4400 """ 4401 n = 1 4402 nlines = len(lines) 4403 linecont = get_line_continuation() 4404 while idx > 0 and lines[idx - 1].endswith(linecont): 4405 idx -= 1 4406 start = idx 4407 line = lines[idx] 4408 open_triple = _have_open_triple_quotes(line) 4409 while (line.endswith(linecont) or open_triple) and idx < nlines - 1: 4410 n += 1 4411 idx += 1 4412 if line.endswith(linecont): 4413 line = line[:-1] + lines[idx] 4414 else: 4415 line = line + "\n" + lines[idx] 4416 open_triple = _have_open_triple_quotes(line) 4417 return line, n, start 4418 4419 4420def replace_logical_line(lines, logical, idx, n): 4421 """Replaces lines at idx that may end in line continuation with a logical 4422 line that spans n lines. 4423 """ 4424 linecont = get_line_continuation() 4425 if n == 1: 4426 lines[idx] = logical 4427 return 4428 space = " " 4429 for i in range(idx, idx + n - 1): 4430 a = len(lines[i]) 4431 b = logical.find(space, a - 1) 4432 if b < 0: 4433 # no space found 4434 lines[i] = logical 4435 logical = "" 4436 else: 4437 # found space to split on 4438 lines[i] = logical[:b] + linecont 4439 logical = logical[b:] 4440 lines[idx + n - 1] = logical 4441 4442 4443def is_balanced(expr, ltok, rtok): 4444 """Determines whether an expression has unbalanced opening and closing tokens.""" 4445 lcnt = expr.count(ltok) 4446 if lcnt == 0: 4447 return True 4448 rcnt = expr.count(rtok) 4449 if lcnt == rcnt: 4450 return True 4451 else: 4452 return False 4453 4454 4455def subexpr_from_unbalanced(expr, ltok, rtok): 4456 """Attempts to pull out a valid subexpression for unbalanced grouping, 4457 based on opening tokens, eg. '(', and closing tokens, eg. ')'. This 4458 does not do full tokenization, but should be good enough for tab 4459 completion. 4460 """ 4461 if is_balanced(expr, ltok, rtok): 4462 return expr 4463 subexpr = expr.rsplit(ltok, 1)[-1] 4464 subexpr = subexpr.rsplit(",", 1)[-1] 4465 subexpr = subexpr.rsplit(":", 1)[-1] 4466 return subexpr 4467 4468 4469def subexpr_before_unbalanced(expr, ltok, rtok): 4470 """Obtains the expression prior to last unbalanced left token.""" 4471 subexpr, _, post = expr.rpartition(ltok) 4472 nrtoks_in_post = post.count(rtok) 4473 while nrtoks_in_post != 0: 4474 for i in range(nrtoks_in_post): 4475 subexpr, _, post = subexpr.rpartition(ltok) 4476 nrtoks_in_post = post.count(rtok) 4477 _, _, subexpr = subexpr.rpartition(rtok) 4478 _, _, subexpr = subexpr.rpartition(ltok) 4479 return subexpr 4480 4481 4482def decode(s, encoding=None): 4483 encoding = encoding or DEFAULT_ENCODING 4484 return s.decode(encoding, "replace") 4485 4486 4487def encode(u, encoding=None): 4488 encoding = encoding or DEFAULT_ENCODING 4489 return u.encode(encoding, "replace") 4490 4491 4492def cast_unicode(s, encoding=None): 4493 if isinstance(s, bytes): 4494 return decode(s, encoding) 4495 return s 4496 4497 4498def safe_hasattr(obj, attr): 4499 """In recent versions of Python, hasattr() only catches AttributeError. 4500 This catches all errors. 4501 """ 4502 try: 4503 getattr(obj, attr) 4504 return True 4505 except Exception: # pylint:disable=bare-except 4506 return False 4507 4508 4509def indent(instr, nspaces=4, ntabs=0, flatten=False): 4510 """Indent a string a given number of spaces or tabstops. 4511 4512 indent(str,nspaces=4,ntabs=0) -> indent str by ntabs+nspaces. 4513 4514 Parameters 4515 ---------- 4516 instr : basestring 4517 The string to be indented. 4518 nspaces : int (default: 4) 4519 The number of spaces to be indented. 4520 ntabs : int (default: 0) 4521 The number of tabs to be indented. 4522 flatten : bool (default: False) 4523 Whether to scrub existing indentation. If True, all lines will be 4524 aligned to the same indentation. If False, existing indentation will 4525 be strictly increased. 4526 4527 Returns 4528 ------- 4529 outstr : string indented by ntabs and nspaces. 4530 4531 """ 4532 if instr is None: 4533 return 4534 ind = "\t" * ntabs + " " * nspaces 4535 if flatten: 4536 pat = re.compile(r"^\s*", re.MULTILINE) 4537 else: 4538 pat = re.compile(r"^", re.MULTILINE) 4539 outstr = re.sub(pat, ind, instr) 4540 if outstr.endswith(os.linesep + ind): 4541 return outstr[: -len(ind)] 4542 else: 4543 return outstr 4544 4545 4546def get_sep(): 4547 """ Returns the appropriate filepath separator char depending on OS and 4548 xonsh options set 4549 """ 4550 if ON_WINDOWS and builtins.__xonsh_env__.get("FORCE_POSIX_PATHS"): 4551 return os.altsep 4552 else: 4553 return os.sep 4554 4555 4556def fallback(cond, backup): 4557 """Decorator for returning the object if cond is true and a backup if cond 4558 is false. 4559 """ 4560 4561 def dec(obj): 4562 return obj if cond else backup 4563 4564 return dec 4565 4566 4567# The following redirect classes were taken directly from Python 3.5's source 4568# code (from the contextlib module). This can be removed when 3.5 is released, 4569# although redirect_stdout exists in 3.4, redirect_stderr does not. 4570# See the Python software license: https://docs.python.org/3/license.html 4571# Copyright (c) Python Software Foundation. All rights reserved. 4572class _RedirectStream: 4573 4574 _stream = None 4575 4576 def __init__(self, new_target): 4577 self._new_target = new_target 4578 # We use a list of old targets to make this CM re-entrant 4579 self._old_targets = [] 4580 4581 def __enter__(self): 4582 self._old_targets.append(getattr(sys, self._stream)) 4583 setattr(sys, self._stream, self._new_target) 4584 return self._new_target 4585 4586 def __exit__(self, exctype, excinst, exctb): 4587 setattr(sys, self._stream, self._old_targets.pop()) 4588 4589 4590class redirect_stdout(_RedirectStream): 4591 """Context manager for temporarily redirecting stdout to another file:: 4592 4593 # How to send help() to stderr 4594 with redirect_stdout(sys.stderr): 4595 help(dir) 4596 4597 # How to write help() to a file 4598 with open('help.txt', 'w') as f: 4599 with redirect_stdout(f): 4600 help(pow) 4601 4602 Mostly for backwards compatibility. 4603 """ 4604 4605 _stream = "stdout" 4606 4607 4608class redirect_stderr(_RedirectStream): 4609 """Context manager for temporarily redirecting stderr to another file.""" 4610 4611 _stream = "stderr" 4612 4613 4614def _yield_accessible_unix_file_names(path): 4615 """yield file names of executable files in path.""" 4616 if not os.path.exists(path): 4617 return 4618 for file_ in scandir(path): 4619 try: 4620 if file_.is_file() and os.access(file_.path, os.X_OK): 4621 yield file_.name 4622 except (FileNotFoundError, NotADirectoryError): 4623 # broken Symlink are neither dir not files 4624 pass 4625 4626 4627def _executables_in_posix(path): 4628 if not os.path.exists(path): 4629 return 4630 elif PYTHON_VERSION_INFO < (3, 5, 0): 4631 for fname in os.listdir(path): 4632 fpath = os.path.join(path, fname) 4633 if ( 4634 os.path.exists(fpath) 4635 and os.access(fpath, os.X_OK) 4636 and (not os.path.isdir(fpath)) 4637 ): 4638 yield fname 4639 else: 4640 yield from _yield_accessible_unix_file_names(path) 4641 4642 4643def _executables_in_windows(path): 4644 if not os.path.isdir(path): 4645 return 4646 extensions = builtins.__xonsh_env__["PATHEXT"] 4647 if PYTHON_VERSION_INFO < (3, 5, 0): 4648 for fname in os.listdir(path): 4649 fpath = os.path.join(path, fname) 4650 if os.path.exists(fpath) and not os.path.isdir(fpath): 4651 base_name, ext = os.path.splitext(fname) 4652 if ext.upper() in extensions: 4653 yield fname 4654 else: 4655 for x in scandir(path): 4656 if x.is_file(): 4657 fname = x.name 4658 else: 4659 continue 4660 base_name, ext = os.path.splitext(fname) 4661 if ext.upper() in extensions: 4662 yield fname 4663 4664 4665def executables_in(path): 4666 """Returns a generator of files in path that the user could execute. """ 4667 if ON_WINDOWS: 4668 func = _executables_in_windows 4669 else: 4670 func = _executables_in_posix 4671 try: 4672 yield from func(path) 4673 except PermissionError: 4674 return 4675 4676 4677def command_not_found(cmd): 4678 """Uses the debian/ubuntu command-not-found utility to suggest packages for a 4679 command that cannot currently be found. 4680 """ 4681 if not ON_LINUX: 4682 return "" 4683 elif not os.path.isfile("/usr/lib/command-not-found"): 4684 # utility is not on PATH 4685 return "" 4686 c = "/usr/lib/command-not-found {0}; exit 0" 4687 s = subprocess.check_output( 4688 c.format(cmd), universal_newlines=True, stderr=subprocess.STDOUT, shell=True 4689 ) 4690 s = "\n".join(s.rstrip().splitlines()).strip() 4691 return s 4692 4693 4694def suggest_commands(cmd, env, aliases): 4695 """Suggests alternative commands given an environment and aliases.""" 4696 if not env.get("SUGGEST_COMMANDS"): 4697 return "" 4698 thresh = env.get("SUGGEST_THRESHOLD") 4699 max_sugg = env.get("SUGGEST_MAX_NUM") 4700 if max_sugg < 0: 4701 max_sugg = float("inf") 4702 cmd = cmd.lower() 4703 suggested = {} 4704 4705 for alias in builtins.aliases: 4706 if alias not in suggested: 4707 if levenshtein(alias.lower(), cmd, thresh) < thresh: 4708 suggested[alias] = "Alias" 4709 4710 for path in filter(os.path.isdir, env.get("PATH")): 4711 for _file in executables_in(path): 4712 if ( 4713 _file not in suggested 4714 and levenshtein(_file.lower(), cmd, thresh) < thresh 4715 ): 4716 suggested[_file] = "Command ({0})".format(os.path.join(path, _file)) 4717 4718 suggested = collections.OrderedDict( 4719 sorted( 4720 suggested.items(), key=lambda x: suggestion_sort_helper(x[0].lower(), cmd) 4721 ) 4722 ) 4723 num = min(len(suggested), max_sugg) 4724 4725 if num == 0: 4726 rtn = command_not_found(cmd) 4727 else: 4728 oneof = "" if num == 1 else "one of " 4729 tips = "Did you mean {}the following?".format(oneof) 4730 items = list(suggested.popitem(False) for _ in range(num)) 4731 length = max(len(key) for key, _ in items) + 2 4732 alternatives = "\n".join( 4733 " {: <{}} {}".format(key + ":", length, val) for key, val in items 4734 ) 4735 rtn = "{}\n{}".format(tips, alternatives) 4736 c = command_not_found(cmd) 4737 rtn += ("\n\n" + c) if len(c) > 0 else "" 4738 return rtn 4739 4740 4741def print_exception(msg=None): 4742 """Print exceptions with/without traceback.""" 4743 env = getattr(builtins, "__xonsh_env__", None) 4744 # flags indicating whether the traceback options have been manually set 4745 if env is None: 4746 env = os_environ 4747 manually_set_trace = "XONSH_SHOW_TRACEBACK" in env 4748 manually_set_logfile = "XONSH_TRACEBACK_LOGFILE" in env 4749 else: 4750 manually_set_trace = env.is_manually_set("XONSH_SHOW_TRACEBACK") 4751 manually_set_logfile = env.is_manually_set("XONSH_TRACEBACK_LOGFILE") 4752 if (not manually_set_trace) and (not manually_set_logfile): 4753 # Notify about the traceback output possibility if neither of 4754 # the two options have been manually set 4755 sys.stderr.write( 4756 "xonsh: For full traceback set: " "$XONSH_SHOW_TRACEBACK = True\n" 4757 ) 4758 # get env option for traceback and convert it if necessary 4759 show_trace = env.get("XONSH_SHOW_TRACEBACK", False) 4760 if not is_bool(show_trace): 4761 show_trace = to_bool(show_trace) 4762 # if the trace option has been set, print all traceback info to stderr 4763 if show_trace: 4764 # notify user about XONSH_TRACEBACK_LOGFILE if it has 4765 # not been set manually 4766 if not manually_set_logfile: 4767 sys.stderr.write( 4768 "xonsh: To log full traceback to a file set: " 4769 "$XONSH_TRACEBACK_LOGFILE = <filename>\n" 4770 ) 4771 traceback.print_exc() 4772 # additionally, check if a file for traceback logging has been 4773 # specified and convert to a proper option if needed 4774 log_file = env.get("XONSH_TRACEBACK_LOGFILE", None) 4775 log_file = to_logfile_opt(log_file) 4776 if log_file: 4777 # if log_file <> '' or log_file <> None, append 4778 # traceback log there as well 4779 with open(os.path.abspath(log_file), "a") as f: 4780 traceback.print_exc(file=f) 4781 4782 if not show_trace: 4783 # if traceback output is disabled, print the exception's 4784 # error message on stderr. 4785 display_error_message() 4786 if msg: 4787 msg = msg if msg.endswith("\n") else msg + "\n" 4788 sys.stderr.write(msg) 4789 4790 4791def display_error_message(strip_xonsh_error_types=True): 4792 """ 4793 Prints the error message of the current exception on stderr. 4794 """ 4795 exc_type, exc_value, exc_traceback = sys.exc_info() 4796 exception_only = traceback.format_exception_only(exc_type, exc_value) 4797 if exc_type is XonshError and strip_xonsh_error_types: 4798 exception_only[0] = exception_only[0].partition(": ")[-1] 4799 sys.stderr.write("".join(exception_only)) 4800 4801 4802def is_writable_file(filepath): 4803 """ 4804 Checks if a filepath is valid for writing. 4805 """ 4806 filepath = expand_path(filepath) 4807 # convert to absolute path if needed 4808 if not os.path.isabs(filepath): 4809 filepath = os.path.abspath(filepath) 4810 # cannot write to directories 4811 if os.path.isdir(filepath): 4812 return False 4813 # if the file exists and is writable, we're fine 4814 if os.path.exists(filepath): 4815 return True if os.access(filepath, os.W_OK) else False 4816 # if the path doesn't exist, isolate its directory component 4817 # and ensure that directory is writable instead 4818 return os.access(os.path.dirname(filepath), os.W_OK) 4819 4820 4821# Modified from Public Domain code, by Magnus Lie Hetland 4822# from http://hetland.org/coding/python/levenshtein.py 4823def levenshtein(a, b, max_dist=float("inf")): 4824 """Calculates the Levenshtein distance between a and b.""" 4825 n, m = len(a), len(b) 4826 if abs(n - m) > max_dist: 4827 return float("inf") 4828 if n > m: 4829 # Make sure n <= m, to use O(min(n,m)) space 4830 a, b = b, a 4831 n, m = m, n 4832 current = range(n + 1) 4833 for i in range(1, m + 1): 4834 previous, current = current, [i] + [0] * n 4835 for j in range(1, n + 1): 4836 add, delete = previous[j] + 1, current[j - 1] + 1 4837 change = previous[j - 1] 4838 if a[j - 1] != b[i - 1]: 4839 change = change + 1 4840 current[j] = min(add, delete, change) 4841 return current[n] 4842 4843 4844def suggestion_sort_helper(x, y): 4845 """Returns a score (lower is better) for x based on how similar 4846 it is to y. Used to rank suggestions.""" 4847 x = x.lower() 4848 y = y.lower() 4849 lendiff = len(x) + len(y) 4850 inx = len([i for i in x if i not in y]) 4851 iny = len([i for i in y if i not in x]) 4852 return lendiff + inx + iny 4853 4854 4855def escape_windows_cmd_string(s): 4856 """Returns a string that is usable by the Windows cmd.exe. 4857 The escaping is based on details here and empirical testing: 4858 http://www.robvanderwoude.com/escapechars.php 4859 """ 4860 for c in '^()%!<>&|"': 4861 s = s.replace(c, "^" + c) 4862 return s 4863 4864 4865def argvquote(arg, force=False): 4866 """ Returns an argument quoted in such a way that that CommandLineToArgvW 4867 on Windows will return the argument string unchanged. 4868 This is the same thing Popen does when supplied with an list of arguments. 4869 Arguments in a command line should be separated by spaces; this 4870 function does not add these spaces. This implementation follows the 4871 suggestions outlined here: 4872 https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ 4873 """ 4874 if not force and len(arg) != 0 and not any([c in arg for c in ' \t\n\v"']): 4875 return arg 4876 else: 4877 n_backslashes = 0 4878 cmdline = '"' 4879 for c in arg: 4880 if c == "\\": 4881 # first count the number of current backslashes 4882 n_backslashes += 1 4883 continue 4884 if c == '"': 4885 # Escape all backslashes and the following double quotation mark 4886 cmdline += (n_backslashes * 2 + 1) * "\\" 4887 else: 4888 # backslashes are not special here 4889 cmdline += n_backslashes * "\\" 4890 n_backslashes = 0 4891 cmdline += c 4892 # Escape all backslashes, but let the terminating 4893 # double quotation mark we add below be interpreted 4894 # as a metacharacter 4895 cmdline += +n_backslashes * 2 * "\\" + '"' 4896 return cmdline 4897 4898 4899def on_main_thread(): 4900 """Checks if we are on the main thread or not.""" 4901 return threading.current_thread() is threading.main_thread() 4902 4903 4904_DEFAULT_SENTINEL = object() 4905 4906 4907@contextlib.contextmanager 4908def swap(namespace, name, value, default=_DEFAULT_SENTINEL): 4909 """Swaps a current variable name in a namespace for another value, and then 4910 replaces it when the context is exited. 4911 """ 4912 old = getattr(namespace, name, default) 4913 setattr(namespace, name, value) 4914 yield value 4915 if old is default: 4916 delattr(namespace, name) 4917 else: 4918 setattr(namespace, name, old) 4919 4920 4921@contextlib.contextmanager 4922def swap_values(d, updates, default=_DEFAULT_SENTINEL): 4923 """Updates a dictionary (or other mapping) with values from another mapping, 4924 and then restores the original mapping when the context is exited. 4925 """ 4926 old = {k: d.get(k, default) for k in updates} 4927 d.update(updates) 4928 yield 4929 for k, v in old.items(): 4930 if v is default and k in d: 4931 del d[k] 4932 else: 4933 d[k] = v 4934 4935 4936# 4937# Validators and converters 4938# 4939 4940 4941def is_int(x): 4942 """Tests if something is an integer""" 4943 return isinstance(x, int) 4944 4945 4946def is_float(x): 4947 """Tests if something is a float""" 4948 return isinstance(x, float) 4949 4950 4951def is_string(x): 4952 """Tests if something is a string""" 4953 return isinstance(x, str) 4954 4955 4956def is_slice(x): 4957 """Tests if something is a slice""" 4958 return isinstance(x, slice) 4959 4960 4961def is_callable(x): 4962 """Tests if something is callable""" 4963 return callable(x) 4964 4965 4966def is_string_or_callable(x): 4967 """Tests if something is a string or callable""" 4968 return is_string(x) or is_callable(x) 4969 4970 4971def is_class(x): 4972 """Tests if something is a class""" 4973 return isinstance(x, type) 4974 4975 4976def always_true(x): 4977 """Returns True""" 4978 return True 4979 4980 4981def always_false(x): 4982 """Returns False""" 4983 return False 4984 4985 4986def ensure_string(x): 4987 """Returns a string if x is not a string, and x if it already is.""" 4988 return str(x) 4989 4990 4991def is_env_path(x): 4992 """This tests if something is an environment path, ie a list of strings.""" 4993 return isinstance(x, EnvPath) 4994 4995 4996def str_to_env_path(x): 4997 """Converts a string to an environment path, ie a list of strings, 4998 splitting on the OS separator. 4999 """ 5000 # splitting will be done implicitly in EnvPath's __init__ 5001 return EnvPath(x) 5002 5003 5004def env_path_to_str(x): 5005 """Converts an environment path to a string by joining on the OS 5006 separator. 5007 """ 5008 return os.pathsep.join(x) 5009 5010 5011def is_bool(x): 5012 """Tests if something is a boolean.""" 5013 return isinstance(x, bool) 5014 5015 5016def is_logfile_opt(x): 5017 """ 5018 Checks if x is a valid $XONSH_TRACEBACK_LOGFILE option. Returns False 5019 if x is not a writable/creatable file or an empty string or None. 5020 """ 5021 if x is None: 5022 return True 5023 if not isinstance(x, str): 5024 return False 5025 else: 5026 return is_writable_file(x) or x == "" 5027 5028 5029def to_logfile_opt(x): 5030 """ 5031 Converts a $XONSH_TRACEBACK_LOGFILE option to either a str containing 5032 the filepath if it is a writable file or None if the filepath is not 5033 valid, informing the user on stderr about the invalid choice. 5034 """ 5035 if is_logfile_opt(x): 5036 return x 5037 else: 5038 # if option is not valid, return a proper 5039 # option and inform the user on stderr 5040 sys.stderr.write( 5041 "xonsh: $XONSH_TRACEBACK_LOGFILE must be a " 5042 "filepath pointing to a file that either exists " 5043 "and is writable or that can be created.\n" 5044 ) 5045 return None 5046 5047 5048def logfile_opt_to_str(x): 5049 """ 5050 Detypes a $XONSH_TRACEBACK_LOGFILE option. 5051 """ 5052 if x is None: 5053 # None should not be detyped to 'None', as 'None' constitutes 5054 # a perfectly valid filename and retyping it would introduce 5055 # ambiguity. Detype to the empty string instead. 5056 return "" 5057 return str(x) 5058 5059 5060_FALSES = LazyObject( 5061 lambda: frozenset(["", "0", "n", "f", "no", "none", "false"]), globals(), "_FALSES" 5062) 5063 5064 5065def to_bool(x): 5066 """"Converts to a boolean in a semantically meaningful way.""" 5067 if isinstance(x, bool): 5068 return x 5069 elif isinstance(x, str): 5070 return False if x.lower() in _FALSES else True 5071 else: 5072 return bool(x) 5073 5074 5075def to_itself(x): 5076 """No conversion, returns itself.""" 5077 return x 5078 5079 5080def bool_to_str(x): 5081 """Converts a bool to an empty string if False and the string '1' if 5082 True. 5083 """ 5084 return "1" if x else "" 5085 5086 5087_BREAKS = LazyObject( 5088 lambda: frozenset(["b", "break", "s", "skip", "q", "quit"]), globals(), "_BREAKS" 5089) 5090 5091 5092def to_bool_or_break(x): 5093 if isinstance(x, str) and x.lower() in _BREAKS: 5094 return "break" 5095 else: 5096 return to_bool(x) 5097 5098 5099def is_bool_or_int(x): 5100 """Returns whether a value is a boolean or integer.""" 5101 return is_bool(x) or is_int(x) 5102 5103 5104def to_bool_or_int(x): 5105 """Converts a value to a boolean or an integer.""" 5106 if isinstance(x, str): 5107 return int(x) if x.isdigit() else to_bool(x) 5108 elif is_int(x): # bools are ints too! 5109 return x 5110 else: 5111 return bool(x) 5112 5113 5114def bool_or_int_to_str(x): 5115 """Converts a boolean or integer to a string.""" 5116 return bool_to_str(x) if is_bool(x) else str(x) 5117 5118 5119@lazyobject 5120def SLICE_REG(): 5121 return re.compile( 5122 r"(?P<start>(?:-\d)?\d*):(?P<end>(?:-\d)?\d*):?(?P<step>(?:-\d)?\d*)" 5123 ) 5124 5125 5126def ensure_slice(x): 5127 """Try to convert an object into a slice, complain on failure""" 5128 if not x and x != 0: 5129 return slice(None) 5130 elif is_slice(x): 5131 return x 5132 try: 5133 x = int(x) 5134 if x != -1: 5135 s = slice(x, x + 1) 5136 else: 5137 s = slice(-1, None, None) 5138 except ValueError: 5139 x = x.strip("[]()") 5140 m = SLICE_REG.fullmatch(x) 5141 if m: 5142 groups = (int(i) if i else None for i in m.groups()) 5143 s = slice(*groups) 5144 else: 5145 raise ValueError("cannot convert {!r} to slice".format(x)) 5146 except TypeError: 5147 try: 5148 s = slice(*(int(i) for i in x)) 5149 except (TypeError, ValueError): 5150 raise ValueError("cannot convert {!r} to slice".format(x)) 5151 return s 5152 5153 5154def get_portions(it, slices): 5155 """Yield from portions of an iterable. 5156 5157 Parameters 5158 ---------- 5159 it: iterable 5160 slices: a slice or a list of slice objects 5161 """ 5162 if is_slice(slices): 5163 slices = [slices] 5164 if len(slices) == 1: 5165 s = slices[0] 5166 try: 5167 yield from itertools.islice(it, s.start, s.stop, s.step) 5168 return 5169 except ValueError: # islice failed 5170 pass 5171 it = list(it) 5172 for s in slices: 5173 yield from it[s] 5174 5175 5176def is_slice_as_str(x): 5177 """ 5178 Test if string x is a slice. If not a string return False. 5179 """ 5180 try: 5181 x = x.strip("[]()") 5182 m = SLICE_REG.fullmatch(x) 5183 if m: 5184 return True 5185 except AttributeError: 5186 pass 5187 return False 5188 5189 5190def is_int_as_str(x): 5191 """ 5192 Test if string x is an integer. If not a string return False. 5193 """ 5194 try: 5195 return x.isdecimal() 5196 except AttributeError: 5197 return False 5198 5199 5200def is_string_set(x): 5201 """Tests if something is a set of strings""" 5202 return isinstance(x, cabc.Set) and all(isinstance(a, str) for a in x) 5203 5204 5205def csv_to_set(x): 5206 """Convert a comma-separated list of strings to a set of strings.""" 5207 if not x: 5208 return set() 5209 else: 5210 return set(x.split(",")) 5211 5212 5213def set_to_csv(x): 5214 """Convert a set of strings to a comma-separated list of strings.""" 5215 return ",".join(x) 5216 5217 5218def pathsep_to_set(x): 5219 """Converts a os.pathsep separated string to a set of strings.""" 5220 if not x: 5221 return set() 5222 else: 5223 return set(x.split(os.pathsep)) 5224 5225 5226def set_to_pathsep(x, sort=False): 5227 """Converts a set to an os.pathsep separated string. The sort kwarg 5228 specifies whether to sort the set prior to str conversion. 5229 """ 5230 if sort: 5231 x = sorted(x) 5232 return os.pathsep.join(x) 5233 5234 5235def is_string_seq(x): 5236 """Tests if something is a sequence of strings""" 5237 return isinstance(x, cabc.Sequence) and all(isinstance(a, str) for a in x) 5238 5239 5240def is_nonstring_seq_of_strings(x): 5241 """Tests if something is a sequence of strings, where the top-level 5242 sequence is not a string itself. 5243 """ 5244 return ( 5245 isinstance(x, cabc.Sequence) 5246 and not isinstance(x, str) 5247 and all(isinstance(a, str) for a in x) 5248 ) 5249 5250 5251def pathsep_to_seq(x): 5252 """Converts a os.pathsep separated string to a sequence of strings.""" 5253 if not x: 5254 return [] 5255 else: 5256 return x.split(os.pathsep) 5257 5258 5259def seq_to_pathsep(x): 5260 """Converts a sequence to an os.pathsep separated string.""" 5261 return os.pathsep.join(x) 5262 5263 5264def pathsep_to_upper_seq(x): 5265 """Converts a os.pathsep separated string to a sequence of 5266 uppercase strings. 5267 """ 5268 if not x: 5269 return [] 5270 else: 5271 return x.upper().split(os.pathsep) 5272 5273 5274def seq_to_upper_pathsep(x): 5275 """Converts a sequence to an uppercase os.pathsep separated string.""" 5276 return os.pathsep.join(x).upper() 5277 5278 5279def is_bool_seq(x): 5280 """Tests if an object is a sequence of bools.""" 5281 return isinstance(x, cabc.Sequence) and all(isinstance(y, bool) for y in x) 5282 5283 5284def csv_to_bool_seq(x): 5285 """Takes a comma-separated string and converts it into a list of bools.""" 5286 return [to_bool(y) for y in csv_to_set(x)] 5287 5288 5289def bool_seq_to_csv(x): 5290 """Converts a sequence of bools to a comma-separated string.""" 5291 return ",".join(map(str, x)) 5292 5293 5294def ptk2_color_depth_setter(x): 5295 """ Setter function for $PROMPT_TOOLKIT_COLOR_DEPTH. Also 5296 updates os.environ so prompt toolkit can pickup the value. 5297 """ 5298 x = str(x) 5299 if x in { 5300 "DEPTH_1_BIT", 5301 "MONOCHROME", 5302 "DEPTH_4_BIT", 5303 "ANSI_COLORS_ONLY", 5304 "DEPTH_8_BIT", 5305 "DEFAULT", 5306 "DEPTH_24_BIT", 5307 "TRUE_COLOR", 5308 }: 5309 pass 5310 elif x in {"", None}: 5311 x = "" 5312 else: 5313 msg = '"{}" is not a valid value for $PROMPT_TOOLKIT_COLOR_DEPTH. '.format(x) 5314 warnings.warn(msg, RuntimeWarning) 5315 x = "" 5316 if x == "" and "PROMPT_TOOLKIT_COLOR_DEPTH" in os_environ: 5317 del os_environ["PROMPT_TOOLKIT_COLOR_DEPTH"] 5318 else: 5319 os_environ["PROMPT_TOOLKIT_COLOR_DEPTH"] = x 5320 return x 5321 5322 5323def is_completions_display_value(x): 5324 return x in {"none", "single", "multi"} 5325 5326 5327def to_completions_display_value(x): 5328 x = str(x).lower() 5329 if x in {"none", "false"}: 5330 x = "none" 5331 elif x in {"multi", "true"}: 5332 x = "multi" 5333 elif x in {"single", "readline"}: 5334 pass 5335 else: 5336 msg = '"{}" is not a valid value for $COMPLETIONS_DISPLAY. '.format(x) 5337 msg += 'Using "multi".' 5338 warnings.warn(msg, RuntimeWarning) 5339 x = "multi" 5340 return x 5341 5342 5343def setup_win_unicode_console(enable): 5344 """"Enables or disables unicode display on windows.""" 5345 try: 5346 import win_unicode_console 5347 except ImportError: 5348 win_unicode_console = False 5349 enable = to_bool(enable) 5350 if ON_WINDOWS and win_unicode_console: 5351 if enable: 5352 win_unicode_console.enable() 5353 else: 5354 win_unicode_console.disable() 5355 return enable 5356 5357 5358# history validation 5359 5360_min_to_sec = lambda x: 60.0 * float(x) 5361_hour_to_sec = lambda x: 60.0 * _min_to_sec(x) 5362_day_to_sec = lambda x: 24.0 * _hour_to_sec(x) 5363_month_to_sec = lambda x: 30.4375 * _day_to_sec(x) 5364_year_to_sec = lambda x: 365.25 * _day_to_sec(x) 5365_kb_to_b = lambda x: 1024 * int(x) 5366_mb_to_b = lambda x: 1024 * _kb_to_b(x) 5367_gb_to_b = lambda x: 1024 * _mb_to_b(x) 5368_tb_to_b = lambda x: 1024 * _tb_to_b(x) 5369 5370CANON_HISTORY_UNITS = LazyObject( 5371 lambda: frozenset(["commands", "files", "s", "b"]), globals(), "CANON_HISTORY_UNITS" 5372) 5373 5374HISTORY_UNITS = LazyObject( 5375 lambda: { 5376 "": ("commands", int), 5377 "c": ("commands", int), 5378 "cmd": ("commands", int), 5379 "cmds": ("commands", int), 5380 "command": ("commands", int), 5381 "commands": ("commands", int), 5382 "f": ("files", int), 5383 "files": ("files", int), 5384 "s": ("s", float), 5385 "sec": ("s", float), 5386 "second": ("s", float), 5387 "seconds": ("s", float), 5388 "m": ("s", _min_to_sec), 5389 "min": ("s", _min_to_sec), 5390 "mins": ("s", _min_to_sec), 5391 "h": ("s", _hour_to_sec), 5392 "hr": ("s", _hour_to_sec), 5393 "hour": ("s", _hour_to_sec), 5394 "hours": ("s", _hour_to_sec), 5395 "d": ("s", _day_to_sec), 5396 "day": ("s", _day_to_sec), 5397 "days": ("s", _day_to_sec), 5398 "mon": ("s", _month_to_sec), 5399 "month": ("s", _month_to_sec), 5400 "months": ("s", _month_to_sec), 5401 "y": ("s", _year_to_sec), 5402 "yr": ("s", _year_to_sec), 5403 "yrs": ("s", _year_to_sec), 5404 "year": ("s", _year_to_sec), 5405 "years": ("s", _year_to_sec), 5406 "b": ("b", int), 5407 "byte": ("b", int), 5408 "bytes": ("b", int), 5409 "kb": ("b", _kb_to_b), 5410 "kilobyte": ("b", _kb_to_b), 5411 "kilobytes": ("b", _kb_to_b), 5412 "mb": ("b", _mb_to_b), 5413 "meg": ("b", _mb_to_b), 5414 "megs": ("b", _mb_to_b), 5415 "megabyte": ("b", _mb_to_b), 5416 "megabytes": ("b", _mb_to_b), 5417 "gb": ("b", _gb_to_b), 5418 "gig": ("b", _gb_to_b), 5419 "gigs": ("b", _gb_to_b), 5420 "gigabyte": ("b", _gb_to_b), 5421 "gigabytes": ("b", _gb_to_b), 5422 "tb": ("b", _tb_to_b), 5423 "terabyte": ("b", _tb_to_b), 5424 "terabytes": ("b", _tb_to_b), 5425 }, 5426 globals(), 5427 "HISTORY_UNITS", 5428) 5429"""Maps lowercase unit names to canonical name and conversion utilities.""" 5430 5431 5432def is_history_tuple(x): 5433 """Tests if something is a proper history value, units tuple.""" 5434 if ( 5435 isinstance(x, cabc.Sequence) 5436 and len(x) == 2 5437 and isinstance(x[0], (int, float)) 5438 and x[1].lower() in CANON_HISTORY_UNITS 5439 ): 5440 return True 5441 return False 5442 5443 5444def is_history_backend(x): 5445 """Tests if something is a valid history backend.""" 5446 return is_string(x) or is_class(x) or isinstance(x, object) 5447 5448 5449def is_dynamic_cwd_width(x): 5450 """ Determine if the input is a valid input for the DYNAMIC_CWD_WIDTH 5451 environment variable. 5452 """ 5453 return ( 5454 isinstance(x, tuple) 5455 and len(x) == 2 5456 and isinstance(x[0], float) 5457 and x[1] in set("c%") 5458 ) 5459 5460 5461def to_dynamic_cwd_tuple(x): 5462 """Convert to a canonical cwd_width tuple.""" 5463 unit = "c" 5464 if isinstance(x, str): 5465 if x[-1] == "%": 5466 x = x[:-1] 5467 unit = "%" 5468 else: 5469 unit = "c" 5470 return (float(x), unit) 5471 else: 5472 return (float(x[0]), x[1]) 5473 5474 5475def dynamic_cwd_tuple_to_str(x): 5476 """Convert a canonical cwd_width tuple to a string.""" 5477 if x[1] == "%": 5478 return str(x[0]) + "%" 5479 else: 5480 return str(x[0]) 5481 5482 5483RE_HISTORY_TUPLE = LazyObject( 5484 lambda: re.compile("([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)\s*([A-Za-z]*)"), 5485 globals(), 5486 "RE_HISTORY_TUPLE", 5487) 5488 5489 5490def to_history_tuple(x): 5491 """Converts to a canonical history tuple.""" 5492 if not isinstance(x, (cabc.Sequence, float, int)): 5493 raise ValueError("history size must be given as a sequence or number") 5494 if isinstance(x, str): 5495 m = RE_HISTORY_TUPLE.match(x.strip().lower()) 5496 return to_history_tuple((m.group(1), m.group(3))) 5497 elif isinstance(x, (float, int)): 5498 return to_history_tuple((x, "commands")) 5499 units, converter = HISTORY_UNITS[x[1]] 5500 value = converter(x[0]) 5501 return (value, units) 5502 5503 5504def history_tuple_to_str(x): 5505 """Converts a valid history tuple to a canonical string.""" 5506 return "{0} {1}".format(*x) 5507 5508 5509def format_color(string, **kwargs): 5510 """Formats strings that may contain colors. This simply dispatches to the 5511 shell instances method of the same name. The results of this function should 5512 be directly usable by print_color(). 5513 """ 5514 return builtins.__xonsh_shell__.shell.format_color(string, **kwargs) 5515 5516 5517def print_color(string, **kwargs): 5518 """Prints a string that may contain colors. This dispatched to the shell 5519 method of the same name. Colors will be formatted if they have not already 5520 been. 5521 """ 5522 builtins.__xonsh_shell__.shell.print_color(string, **kwargs) 5523 5524 5525def color_style_names(): 5526 """Returns an iterable of all available style names.""" 5527 return builtins.__xonsh_shell__.shell.color_style_names() 5528 5529 5530def color_style(): 5531 """Returns the current color map.""" 5532 return builtins.__xonsh_shell__.shell.color_style() 5533 5534 5535def _token_attr_from_stylemap(stylemap): 5536 """yields tokens attr, and index from a stylemap """ 5537 import prompt_toolkit as ptk 5538 5539 if builtins.__xonsh_shell__.shell_type == "prompt_toolkit1": 5540 style = ptk.styles.style_from_dict(stylemap) 5541 for token in stylemap: 5542 yield token, style.token_to_attrs[token] 5543 else: 5544 style = ptk.styles.style_from_pygments_dict(stylemap) 5545 for token in stylemap: 5546 style_str = "class:{}".format( 5547 ptk.styles.pygments.pygments_token_to_classname(token) 5548 ) 5549 yield (token, style.get_attrs_for_style_str(style_str)) 5550 5551 5552def _get_color_lookup_table(): 5553 """Returns the prompt_toolkit win32 ColorLookupTable """ 5554 if builtins.__xonsh_shell__.shell_type == "prompt_toolkit1": 5555 from prompt_toolkit.terminal.win32_output import ColorLookupTable 5556 else: 5557 from prompt_toolkit.output.win32 import ColorLookupTable 5558 return ColorLookupTable() 5559 5560 5561def _get_color_indexes(style_map): 5562 """Generates the color and windows color index for a style """ 5563 table = _get_color_lookup_table() 5564 for token, attr in _token_attr_from_stylemap(style_map): 5565 if attr.color: 5566 index = table.lookup_fg_color(attr.color) 5567 try: 5568 rgb = ( 5569 int(attr.color[0:2], 16), 5570 int(attr.color[2:4], 16), 5571 int(attr.color[4:6], 16), 5572 ) 5573 except Exception: 5574 rgb = None 5575 yield token, index, rgb 5576 5577 5578# Map of new PTK2 color names to PTK1 variants 5579PTK_NEW_OLD_COLOR_MAP = LazyObject( 5580 lambda: { 5581 "black": "black", 5582 "red": "darkred", 5583 "green": "darkgreen", 5584 "yellow": "brown", 5585 "blue": "darkblue", 5586 "magenta": "purple", 5587 "cyan": "teal", 5588 "gray": "lightgray", 5589 "brightblack": "darkgray", 5590 "brightred": "red", 5591 "brightgreen": "green", 5592 "brightyellow": "yellow", 5593 "brightblue": "blue", 5594 "brightmagenta": "fuchsia", 5595 "brightcyan": "turquoise", 5596 "white": "white", 5597 }, 5598 globals(), 5599 "PTK_NEW_OLD_COLOR_MAP", 5600) 5601 5602# Map of new ansicolor names to old PTK1 names 5603ANSICOLOR_NAMES_MAP = LazyObject( 5604 lambda: {"ansi" + k: "#ansi" + v for k, v in PTK_NEW_OLD_COLOR_MAP.items()}, 5605 globals(), 5606 "ANSICOLOR_NAMES_MAP", 5607) 5608 5609 5610def _win10_color_map(): 5611 cmap = { 5612 "ansiblack": (12, 12, 12), 5613 "ansiblue": (0, 55, 218), 5614 "ansigreen": (19, 161, 14), 5615 "ansicyan": (58, 150, 221), 5616 "ansired": (197, 15, 31), 5617 "ansimagenta": (136, 23, 152), 5618 "ansiyellow": (193, 156, 0), 5619 "ansigray": (204, 204, 204), 5620 "ansibrightblack": (118, 118, 118), 5621 "ansibrightblue": (59, 120, 255), 5622 "ansibrightgreen": (22, 198, 12), 5623 "ansibrightcyan": (97, 214, 214), 5624 "ansibrightred": (231, 72, 86), 5625 "ansibrightmagenta": (180, 0, 158), 5626 "ansibrightyellow": (249, 241, 165), 5627 "ansiwhite": (242, 242, 242), 5628 } 5629 return { 5630 k: "#{0:02x}{1:02x}{2:02x}".format(r, g, b) for k, (r, g, b) in cmap.items() 5631 } 5632 5633 5634WIN10_COLOR_MAP = LazyObject(_win10_color_map, globals(), "WIN10_COLOR_MAP") 5635 5636 5637def _win_bold_color_map(): 5638 """ Map dark ansi colors to lighter version. """ 5639 return { 5640 "ansiblack": "ansibrightblack", 5641 "ansiblue": "ansibrightblue", 5642 "ansigreen": "ansibrightgreen", 5643 "ansicyan": "ansibrightcyan", 5644 "ansired": "ansibrightred", 5645 "ansimagenta": "ansibrightmagenta", 5646 "ansiyellow": "ansibrightyellow", 5647 "ansigray": "ansiwhite", 5648 } 5649 5650 5651WIN_BOLD_COLOR_MAP = LazyObject(_win_bold_color_map, globals(), "WIN_BOLD_COLOR_MAP") 5652 5653 5654def hardcode_colors_for_win10(style_map): 5655 """Replace all ansi colors with hardcoded colors to avoid unreadable defaults 5656 in conhost.exe 5657 """ 5658 modified_style = {} 5659 if not builtins.__xonsh_env__["PROMPT_TOOLKIT_COLOR_DEPTH"]: 5660 builtins.__xonsh_env__["PROMPT_TOOLKIT_COLOR_DEPTH"] = "DEPTH_24_BIT" 5661 # Replace all ansi colors with hardcoded colors to avoid unreadable defaults 5662 # in conhost.exe 5663 for token, style_str in style_map.items(): 5664 for ansicolor in WIN10_COLOR_MAP: 5665 if ansicolor in style_str: 5666 if "bold" in style_str and "nobold" not in style_str: 5667 # Win10 doesn't yet handle bold colors. Instead dark 5668 # colors are mapped to their lighter version. We simulate 5669 # the same here. 5670 style_str.replace("bold", "") 5671 hexcolor = WIN10_COLOR_MAP[ 5672 WIN_BOLD_COLOR_MAP.get(ansicolor, ansicolor) 5673 ] 5674 else: 5675 hexcolor = WIN10_COLOR_MAP[ansicolor] 5676 style_str = style_str.replace(ansicolor, hexcolor) 5677 modified_style[token] = style_str 5678 return modified_style 5679 5680 5681def ansicolors_to_ptk1_names(stylemap): 5682 """Converts ansicolor names in a stylemap to old PTK1 color names 5683 """ 5684 modified_stylemap = {} 5685 for token, style_str in stylemap.items(): 5686 for color, ptk1_color in ANSICOLOR_NAMES_MAP.items(): 5687 if "#" + color not in style_str: 5688 style_str = style_str.replace(color, ptk1_color) 5689 modified_stylemap[token] = style_str 5690 return modified_stylemap 5691 5692 5693def intensify_colors_for_cmd_exe(style_map): 5694 """Returns a modified style to where colors that maps to dark 5695 colors are replaced with brighter versions. 5696 """ 5697 modified_style = {} 5698 replace_colors = { 5699 1: "ansibrightcyan", # subst blue with bright cyan 5700 2: "ansibrightgreen", # subst green with bright green 5701 4: "ansibrightred", # subst red with bright red 5702 5: "ansibrightmagenta", # subst magenta with bright magenta 5703 6: "ansibrightyellow", # subst yellow with bright yellow 5704 9: "ansicyan", # subst intense blue with dark cyan (more readable) 5705 } 5706 if builtins.__xonsh_shell__.shell_type == "prompt_toolkit1": 5707 replace_colors = ansicolors_to_ptk1_names(replace_colors) 5708 for token, idx, _ in _get_color_indexes(style_map): 5709 if idx in replace_colors: 5710 modified_style[token] = replace_colors[idx] 5711 return modified_style 5712 5713 5714def intensify_colors_on_win_setter(enable): 5715 """Resets the style when setting the INTENSIFY_COLORS_ON_WIN 5716 environment variable. 5717 """ 5718 enable = to_bool(enable) 5719 if hasattr(builtins, "__xonsh_shell__"): 5720 builtins.__xonsh_shell__.shell.styler.trap.clear() 5721 if hasattr(builtins.__xonsh_shell__.shell.styler, "style_name"): 5722 delattr(builtins.__xonsh_shell__.shell.styler, "style_name") 5723 return enable 5724 5725 5726def format_std_prepost(template, env=None): 5727 """Formats a template prefix/postfix string for a standard buffer. 5728 Returns a string suitable for prepending or appending. 5729 """ 5730 if not template: 5731 return "" 5732 env = builtins.__xonsh_env__ if env is None else env 5733 shell = builtins.__xonsh_shell__.shell 5734 try: 5735 s = shell.prompt_formatter(template) 5736 except Exception: 5737 print_exception() 5738 # \001\002 is there to fool pygments into not returning an empty string 5739 # for potentially empty input. This happens when the template is just a 5740 # color code with no visible text. 5741 invis = "\001\002" 5742 s = shell.format_color(invis + s + invis, force_string=True) 5743 s = s.replace(invis, "") 5744 return s 5745 5746 5747_RE_STRING_START = "[bBprRuUf]*" 5748_RE_STRING_TRIPLE_DOUBLE = '"""' 5749_RE_STRING_TRIPLE_SINGLE = "'''" 5750_RE_STRING_DOUBLE = '"' 5751_RE_STRING_SINGLE = "'" 5752_STRINGS = ( 5753 _RE_STRING_TRIPLE_DOUBLE, 5754 _RE_STRING_TRIPLE_SINGLE, 5755 _RE_STRING_DOUBLE, 5756 _RE_STRING_SINGLE, 5757) 5758RE_BEGIN_STRING = LazyObject( 5759 lambda: re.compile("(" + _RE_STRING_START + "(" + "|".join(_STRINGS) + "))"), 5760 globals(), 5761 "RE_BEGIN_STRING", 5762) 5763"""Regular expression matching the start of a string, including quotes and 5764leading characters (r, b, or u)""" 5765 5766RE_STRING_START = LazyObject( 5767 lambda: re.compile(_RE_STRING_START), globals(), "RE_STRING_START" 5768) 5769"""Regular expression matching the characters before the quotes when starting a 5770string (r, b, or u, case insensitive)""" 5771 5772RE_STRING_CONT = LazyDict( 5773 { 5774 '"': lambda: re.compile(r'((\\(.|\n))|([^"\\]))*'), 5775 "'": lambda: re.compile(r"((\\(.|\n))|([^'\\]))*"), 5776 '"""': lambda: re.compile(r'((\\(.|\n))|([^"\\])|("(?!""))|\n)*'), 5777 "'''": lambda: re.compile(r"((\\(.|\n))|([^'\\])|('(?!''))|\n)*"), 5778 }, 5779 globals(), 5780 "RE_STRING_CONT", 5781) 5782"""Dictionary mapping starting quote sequences to regular expressions that 5783match the contents of a string beginning with those quotes (not including the 5784terminating quotes)""" 5785 5786 5787@lazyobject 5788def RE_COMPLETE_STRING(): 5789 ptrn = ( 5790 "^" 5791 + _RE_STRING_START 5792 + "(?P<quote>" 5793 + "|".join(_STRINGS) 5794 + ")" 5795 + ".*?(?P=quote)$" 5796 ) 5797 return re.compile(ptrn, re.DOTALL) 5798 5799 5800def check_for_partial_string(x): 5801 """Returns the starting index (inclusive), ending index (exclusive), and 5802 starting quote string of the most recent Python string found in the input. 5803 5804 check_for_partial_string(x) -> (startix, endix, quote) 5805 5806 Parameters 5807 ---------- 5808 x : str 5809 The string to be checked (representing a line of terminal input) 5810 5811 Returns 5812 ------- 5813 startix : int (or None) 5814 The index where the most recent Python string found started 5815 (inclusive), or None if no strings exist in the input 5816 5817 endix : int (or None) 5818 The index where the most recent Python string found ended (exclusive), 5819 or None if no strings exist in the input OR if the input ended in the 5820 middle of a Python string 5821 5822 quote : str (or None) 5823 A string containing the quote used to start the string (e.g., b", ", 5824 '''), or None if no string was found. 5825 """ 5826 string_indices = [] 5827 starting_quote = [] 5828 current_index = 0 5829 match = re.search(RE_BEGIN_STRING, x) 5830 while match is not None: 5831 # add the start in 5832 start = match.start() 5833 quote = match.group(0) 5834 lenquote = len(quote) 5835 current_index += start 5836 # store the starting index of the string, as well as the 5837 # characters in the starting quotes (e.g., ", ', """, r", etc) 5838 string_indices.append(current_index) 5839 starting_quote.append(quote) 5840 # determine the string that should terminate this string 5841 ender = re.sub(RE_STRING_START, "", quote) 5842 x = x[start + lenquote :] 5843 current_index += lenquote 5844 # figure out what is inside the string 5845 continuer = RE_STRING_CONT[ender] 5846 contents = re.match(continuer, x) 5847 inside = contents.group(0) 5848 leninside = len(inside) 5849 current_index += contents.start() + leninside + len(ender) 5850 # if we are not at the end of the input string, add the ending index of 5851 # the string to string_indices 5852 if contents.end() < len(x): 5853 string_indices.append(current_index) 5854 x = x[leninside + len(ender) :] 5855 # find the next match 5856 match = re.search(RE_BEGIN_STRING, x) 5857 numquotes = len(string_indices) 5858 if numquotes == 0: 5859 return (None, None, None) 5860 elif numquotes % 2: 5861 return (string_indices[-1], None, starting_quote[-1]) 5862 else: 5863 return (string_indices[-2], string_indices[-1], starting_quote[-1]) 5864 5865 5866# regular expressions for matching environment variables 5867# i.e $FOO, ${'FOO'} 5868@lazyobject 5869def POSIX_ENVVAR_REGEX(): 5870 pat = r"""\$({(?P<quote>['"])|)(?P<envvar>\w+)((?P=quote)}|(?:\1\b))""" 5871 return re.compile(pat) 5872 5873 5874def expandvars(path): 5875 """Expand shell variables of the forms $var, ${var} and %var%. 5876 Unknown variables are left unchanged.""" 5877 env = builtins.__xonsh_env__ 5878 if isinstance(path, bytes): 5879 path = path.decode( 5880 encoding=env.get("XONSH_ENCODING"), errors=env.get("XONSH_ENCODING_ERRORS") 5881 ) 5882 elif isinstance(path, pathlib.Path): 5883 # get the path's string representation 5884 path = str(path) 5885 if "$" in path: 5886 for match in POSIX_ENVVAR_REGEX.finditer(path): 5887 name = match.group("envvar") 5888 if name in env: 5889 ensurer = env.get_ensurer(name) 5890 value = ensurer.detype(env[name]) 5891 path = POSIX_ENVVAR_REGEX.sub(value, path, count=1) 5892 return path 5893 5894 5895# 5896# File handling tools 5897# 5898 5899 5900def backup_file(fname): 5901 """Moves an existing file to a new name that has the current time right 5902 before the extension. 5903 """ 5904 # lazy imports 5905 import shutil 5906 from datetime import datetime 5907 5908 base, ext = os.path.splitext(fname) 5909 timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M-%S-%f") 5910 newfname = "%s.%s%s" % (base, timestamp, ext) 5911 shutil.move(fname, newfname) 5912 5913 5914def normabspath(p): 5915 """Returns as normalized absolute path, namely, normcase(abspath(p))""" 5916 return os.path.normcase(os.path.abspath(p)) 5917 5918 5919def expanduser_abs_path(inp): 5920 """ Provides user expanded absolute path """ 5921 return os.path.abspath(expanduser(inp)) 5922 5923 5924WINDOWS_DRIVE_MATCHER = LazyObject( 5925 lambda: re.compile(r"^\w:"), globals(), "WINDOWS_DRIVE_MATCHER" 5926) 5927 5928 5929def expand_case_matching(s): 5930 """Expands a string to a case insensitive globable string.""" 5931 t = [] 5932 openers = {"[", "{"} 5933 closers = {"]", "}"} 5934 nesting = 0 5935 5936 drive_part = WINDOWS_DRIVE_MATCHER.match(s) if ON_WINDOWS else None 5937 5938 if drive_part: 5939 drive_part = drive_part.group(0) 5940 t.append(drive_part) 5941 s = s[len(drive_part) :] 5942 5943 for c in s: 5944 if c in openers: 5945 nesting += 1 5946 elif c in closers: 5947 nesting -= 1 5948 elif nesting > 0: 5949 pass 5950 elif c.isalpha(): 5951 folded = c.casefold() 5952 if len(folded) == 1: 5953 c = "[{0}{1}]".format(c.upper(), c.lower()) 5954 else: 5955 newc = ["[{0}{1}]?".format(f.upper(), f.lower()) for f in folded[:-1]] 5956 newc = "".join(newc) 5957 newc += "[{0}{1}{2}]".format(folded[-1].upper(), folded[-1].lower(), c) 5958 c = newc 5959 t.append(c) 5960 return "".join(t) 5961 5962 5963def globpath( 5964 s, ignore_case=False, return_empty=False, sort_result=None, include_dotfiles=None 5965): 5966 """Simple wrapper around glob that also expands home and env vars.""" 5967 o, s = _iglobpath( 5968 s, 5969 ignore_case=ignore_case, 5970 sort_result=sort_result, 5971 include_dotfiles=include_dotfiles, 5972 ) 5973 o = list(o) 5974 no_match = [] if return_empty else [s] 5975 return o if len(o) != 0 else no_match 5976 5977 5978def _dotglobstr(s): 5979 modified = False 5980 dotted_s = s 5981 if "/*" in dotted_s: 5982 dotted_s = dotted_s.replace("/*", "/.*") 5983 dotted_s = dotted_s.replace("/.**/.*", "/**/.*") 5984 modified = True 5985 if dotted_s.startswith("*") and not dotted_s.startswith("**"): 5986 dotted_s = "." + dotted_s 5987 modified = True 5988 return dotted_s, modified 5989 5990 5991def _iglobpath(s, ignore_case=False, sort_result=None, include_dotfiles=None): 5992 s = builtins.__xonsh_expand_path__(s) 5993 if sort_result is None: 5994 sort_result = builtins.__xonsh_env__.get("GLOB_SORTED") 5995 if include_dotfiles is None: 5996 include_dotfiles = builtins.__xonsh_env__.get("DOTGLOB") 5997 if ignore_case: 5998 s = expand_case_matching(s) 5999 if sys.version_info > (3, 5): 6000 if "**" in s and "**/*" not in s: 6001 s = s.replace("**", "**/*") 6002 if include_dotfiles: 6003 dotted_s, dotmodified = _dotglobstr(s) 6004 # `recursive` is only a 3.5+ kwarg. 6005 if sort_result: 6006 paths = glob.glob(s, recursive=True) 6007 if include_dotfiles and dotmodified: 6008 paths.extend(glob.iglob(dotted_s, recursive=True)) 6009 paths.sort() 6010 paths = iter(paths) 6011 else: 6012 paths = glob.iglob(s, recursive=True) 6013 if include_dotfiles and dotmodified: 6014 paths = itertools.chain(glob.iglob(dotted_s, recursive=True), paths) 6015 return paths, s 6016 else: 6017 if include_dotfiles: 6018 dotted_s, dotmodified = _dotglobstr(s) 6019 if sort_result: 6020 paths = glob.glob(s) 6021 if include_dotfiles and dotmodified: 6022 paths.extend(glob.iglob(dotted_s)) 6023 paths.sort() 6024 paths = iter(paths) 6025 else: 6026 paths = glob.iglob(s) 6027 if include_dotfiles and dotmodified: 6028 paths = itertools.chain(glob.iglob(dotted_s), paths) 6029 return paths, s 6030 6031 6032def iglobpath(s, ignore_case=False, sort_result=None, include_dotfiles=None): 6033 """Simple wrapper around iglob that also expands home and env vars.""" 6034 try: 6035 return _iglobpath( 6036 s, 6037 ignore_case=ignore_case, 6038 sort_result=sort_result, 6039 include_dotfiles=include_dotfiles, 6040 )[0] 6041 except IndexError: 6042 # something went wrong in the actual iglob() call 6043 return iter(()) 6044 6045 6046def ensure_timestamp(t, datetime_format=None): 6047 if isinstance(t, (int, float)): 6048 return t 6049 try: 6050 return float(t) 6051 except (ValueError, TypeError): 6052 pass 6053 if datetime_format is None: 6054 datetime_format = builtins.__xonsh_env__["XONSH_DATETIME_FORMAT"] 6055 if isinstance(t, datetime.datetime): 6056 t = t.timestamp() 6057 else: 6058 t = datetime.datetime.strptime(t, datetime_format).timestamp() 6059 return t 6060 6061 6062def format_datetime(dt): 6063 """Format datetime object to string base on $XONSH_DATETIME_FORMAT Env.""" 6064 format_ = builtins.__xonsh_env__["XONSH_DATETIME_FORMAT"] 6065 return dt.strftime(format_) 6066 6067 6068def columnize(elems, width=80, newline="\n"): 6069 """Takes an iterable of strings and returns a list of lines with the 6070 elements placed in columns. Each line will be at most *width* columns. 6071 The newline character will be appended to the end of each line. 6072 """ 6073 sizes = [len(e) + 1 for e in elems] 6074 total = sum(sizes) 6075 nelem = len(elems) 6076 if total - 1 <= width: 6077 ncols = len(sizes) 6078 nrows = 1 6079 columns = [sizes] 6080 last_longest_row = total 6081 enter_loop = False 6082 else: 6083 ncols = 1 6084 nrows = len(sizes) 6085 columns = [sizes] 6086 last_longest_row = max(sizes) 6087 enter_loop = True 6088 while enter_loop: 6089 longest_row = sum(map(max, columns)) 6090 if longest_row - 1 <= width: 6091 # we might be able to fit another column. 6092 ncols += 1 6093 nrows = nelem // ncols 6094 columns = [sizes[i * nrows : (i + 1) * nrows] for i in range(ncols)] 6095 last_longest_row = longest_row 6096 else: 6097 # we can't fit another column 6098 ncols -= 1 6099 nrows = nelem // ncols 6100 break 6101 pad = (width - last_longest_row + ncols) // ncols 6102 pad = pad if pad > 1 else 1 6103 data = [elems[i * nrows : (i + 1) * nrows] for i in range(ncols)] 6104 colwidths = [max(map(len, d)) + pad for d in data] 6105 colwidths[-1] -= pad 6106 row_t = "".join(["{{row[{i}]: <{{w[{i}]}}}}".format(i=i) for i in range(ncols)]) 6107 row_t += newline 6108 lines = [ 6109 row_t.format(row=row, w=colwidths) 6110 for row in itertools.zip_longest(*data, fillvalue="") 6111 ] 6112 return lines 6113 6114 6115def unthreadable(f): 6116 """Decorator that specifies that a callable alias should be run only 6117 on the main thread process. This is often needed for debuggers and 6118 profilers. 6119 """ 6120 f.__xonsh_threadable__ = False 6121 return f 6122 6123 6124def uncapturable(f): 6125 """Decorator that specifies that a callable alias should not be run with 6126 any capturing. This is often needed if the alias call interactive 6127 subprocess, like pagers and text editors. 6128 """ 6129 f.__xonsh_capturable__ = False 6130 return f 6131 6132 6133def carriage_return(): 6134 """Writes a carriage return to stdout, and nothing else.""" 6135 print("\r", flush=True, end="") 6136 6137 6138def deprecated(deprecated_in=None, removed_in=None): 6139 """Parametrized decorator that deprecates a function in a graceful manner. 6140 6141 Updates the decorated function's docstring to mention the version 6142 that deprecation occurred in and the version it will be removed 6143 in if both of these values are passed. 6144 6145 When removed_in is not a release equal to or less than the current 6146 release, call ``warnings.warn`` with details, while raising 6147 ``DeprecationWarning``. 6148 6149 When removed_in is a release equal to or less than the current release, 6150 raise an ``AssertionError``. 6151 6152 Parameters 6153 ---------- 6154 deprecated_in : str 6155 The version number that deprecated this function. 6156 removed_in : str 6157 The version number that this function will be removed in. 6158 """ 6159 message_suffix = _deprecated_message_suffix(deprecated_in, removed_in) 6160 if not message_suffix: 6161 message_suffix = "" 6162 6163 def decorated(func): 6164 warning_message = "{} has been deprecated".format(func.__name__) 6165 warning_message += message_suffix 6166 6167 @functools.wraps(func) 6168 def wrapped(*args, **kwargs): 6169 _deprecated_error_on_expiration(func.__name__, removed_in) 6170 func(*args, **kwargs) 6171 warnings.warn(warning_message, DeprecationWarning) 6172 6173 wrapped.__doc__ = ( 6174 "{}\n\n{}".format(wrapped.__doc__, warning_message) 6175 if wrapped.__doc__ 6176 else warning_message 6177 ) 6178 6179 return wrapped 6180 6181 return decorated 6182 6183 6184def _deprecated_message_suffix(deprecated_in, removed_in): 6185 if deprecated_in and removed_in: 6186 message_suffix = " in version {} and will be removed in version {}".format( 6187 deprecated_in, removed_in 6188 ) 6189 elif deprecated_in and not removed_in: 6190 message_suffix = " in version {}".format(deprecated_in) 6191 elif not deprecated_in and removed_in: 6192 message_suffix = " and will be removed in version {}".format(removed_in) 6193 else: 6194 message_suffix = None 6195 6196 return message_suffix 6197 6198 6199def _deprecated_error_on_expiration(name, removed_in): 6200 if not removed_in: 6201 return 6202 elif LooseVersion(__version__) >= LooseVersion(removed_in): 6203 raise AssertionError( 6204 "{} has passed its version {} expiry date!".format(name, removed_in) 6205 ) 6206 6207# 6208# ast 6209# 6210# -*- coding: utf-8 -*- 6211"""The xonsh abstract syntax tree node.""" 6212# These are imported into our module namespace for the benefit of parser.py. 6213# pylint: disable=unused-import 6214# amalgamated sys 6215from ast import ( 6216 Module, 6217 Num, 6218 Expr, 6219 Str, 6220 Bytes, 6221 UnaryOp, 6222 UAdd, 6223 USub, 6224 Invert, 6225 BinOp, 6226 Add, 6227 Sub, 6228 Mult, 6229 Div, 6230 FloorDiv, 6231 Mod, 6232 Pow, 6233 Compare, 6234 Lt, 6235 Gt, 6236 LtE, 6237 GtE, 6238 Eq, 6239 NotEq, 6240 In, 6241 NotIn, 6242 Is, 6243 IsNot, 6244 Not, 6245 BoolOp, 6246 Or, 6247 And, 6248 Subscript, 6249 Load, 6250 Slice, 6251 ExtSlice, 6252 List, 6253 Tuple, 6254 Set, 6255 Dict, 6256 AST, 6257 NameConstant, 6258 Name, 6259 GeneratorExp, 6260 Store, 6261 comprehension, 6262 ListComp, 6263 SetComp, 6264 DictComp, 6265 Assign, 6266 AugAssign, 6267 BitXor, 6268 BitAnd, 6269 BitOr, 6270 LShift, 6271 RShift, 6272 Assert, 6273 Delete, 6274 Del, 6275 Pass, 6276 Raise, 6277 Import, 6278 alias, 6279 ImportFrom, 6280 Continue, 6281 Break, 6282 Yield, 6283 YieldFrom, 6284 Return, 6285 IfExp, 6286 Lambda, 6287 arguments, 6288 arg, 6289 Call, 6290 keyword, 6291 Attribute, 6292 Global, 6293 Nonlocal, 6294 If, 6295 While, 6296 For, 6297 withitem, 6298 With, 6299 Try, 6300 ExceptHandler, 6301 FunctionDef, 6302 ClassDef, 6303 Starred, 6304 NodeTransformer, 6305 Interactive, 6306 Expression, 6307 Index, 6308 literal_eval, 6309 dump, 6310 walk, 6311 increment_lineno, 6312) 6313from ast import Ellipsis as EllipsisNode 6314 6315# pylint: enable=unused-import 6316# amalgamated textwrap 6317# amalgamated itertools 6318# amalgamated xonsh.tools 6319# amalgamated xonsh.platform 6320if PYTHON_VERSION_INFO >= (3, 5, 0): 6321 # pylint: disable=unused-import 6322 # pylint: disable=no-name-in-module 6323 from ast import MatMult, AsyncFunctionDef, AsyncWith, AsyncFor, Await 6324else: 6325 MatMult = AsyncFunctionDef = AsyncWith = AsyncFor = Await = None 6326 6327if PYTHON_VERSION_INFO >= (3, 6, 0): 6328 # pylint: disable=unused-import 6329 # pylint: disable=no-name-in-module 6330 from ast import JoinedStr, FormattedValue 6331else: 6332 JoinedStr = FormattedValue = None 6333 6334STATEMENTS = ( 6335 FunctionDef, 6336 ClassDef, 6337 Return, 6338 Delete, 6339 Assign, 6340 AugAssign, 6341 For, 6342 While, 6343 If, 6344 With, 6345 Raise, 6346 Try, 6347 Assert, 6348 Import, 6349 ImportFrom, 6350 Global, 6351 Nonlocal, 6352 Expr, 6353 Pass, 6354 Break, 6355 Continue, 6356) 6357 6358 6359def leftmostname(node): 6360 """Attempts to find the first name in the tree.""" 6361 if isinstance(node, Name): 6362 rtn = node.id 6363 elif isinstance(node, (BinOp, Compare)): 6364 rtn = leftmostname(node.left) 6365 elif isinstance(node, (Attribute, Subscript, Starred, Expr)): 6366 rtn = leftmostname(node.value) 6367 elif isinstance(node, Call): 6368 rtn = leftmostname(node.func) 6369 elif isinstance(node, UnaryOp): 6370 rtn = leftmostname(node.operand) 6371 elif isinstance(node, BoolOp): 6372 rtn = leftmostname(node.values[0]) 6373 elif isinstance(node, Assign): 6374 rtn = leftmostname(node.targets[0]) 6375 elif isinstance(node, (Str, Bytes, JoinedStr)): 6376 # handles case of "./my executable" 6377 rtn = leftmostname(node.s) 6378 elif isinstance(node, Tuple) and len(node.elts) > 0: 6379 # handles case of echo ,1,2,3 6380 rtn = leftmostname(node.elts[0]) 6381 else: 6382 rtn = None 6383 return rtn 6384 6385 6386def get_lineno(node, default=0): 6387 """Gets the lineno of a node or returns the default.""" 6388 return getattr(node, "lineno", default) 6389 6390 6391def min_line(node): 6392 """Computes the minimum lineno.""" 6393 node_line = get_lineno(node) 6394 return min(map(get_lineno, walk(node), itertools.repeat(node_line))) 6395 6396 6397def max_line(node): 6398 """Computes the maximum lineno.""" 6399 return max(map(get_lineno, walk(node))) 6400 6401 6402def get_col(node, default=-1): 6403 """Gets the col_offset of a node, or returns the default""" 6404 return getattr(node, "col_offset", default) 6405 6406 6407def min_col(node): 6408 """Computes the minimum col_offset.""" 6409 return min(map(get_col, walk(node), itertools.repeat(node.col_offset))) 6410 6411 6412def max_col(node): 6413 """Returns the maximum col_offset of the node and all sub-nodes.""" 6414 col = getattr(node, "max_col", None) 6415 if col is not None: 6416 return col 6417 highest = max(walk(node), key=get_col) 6418 col = highest.col_offset + node_len(highest) 6419 return col 6420 6421 6422def node_len(node): 6423 """The length of a node as a string""" 6424 val = 0 6425 for n in walk(node): 6426 if isinstance(n, Name): 6427 val += len(n.id) 6428 elif isinstance(n, Attribute): 6429 val += 1 + (len(n.attr) if isinstance(n.attr, str) else 0) 6430 # this may need to be added to for more nodes as more cases are found 6431 return val 6432 6433 6434def get_id(node, default=None): 6435 """Gets the id attribute of a node, or returns a default.""" 6436 return getattr(node, "id", default) 6437 6438 6439def gather_names(node): 6440 """Returns the set of all names present in the node's tree.""" 6441 rtn = set(map(get_id, walk(node))) 6442 rtn.discard(None) 6443 return rtn 6444 6445 6446def get_id_ctx(node): 6447 """Gets the id and attribute of a node, or returns a default.""" 6448 nid = getattr(node, "id", None) 6449 if nid is None: 6450 return (None, None) 6451 return (nid, node.ctx) 6452 6453 6454def gather_load_store_names(node): 6455 """Returns the names present in the node's tree in a set of load nodes and 6456 a set of store nodes. 6457 """ 6458 load = set() 6459 store = set() 6460 for nid, ctx in map(get_id_ctx, walk(node)): 6461 if nid is None: 6462 continue 6463 elif isinstance(ctx, Load): 6464 load.add(nid) 6465 else: 6466 store.add(nid) 6467 return (load, store) 6468 6469 6470def has_elts(x): 6471 """Tests if x is an AST node with elements.""" 6472 return isinstance(x, AST) and hasattr(x, "elts") 6473 6474 6475def xonsh_call(name, args, lineno=None, col=None): 6476 """Creates the AST node for calling a function of a given name.""" 6477 return Call( 6478 func=Name(id=name, ctx=Load(), lineno=lineno, col_offset=col), 6479 args=args, 6480 keywords=[], 6481 starargs=None, 6482 kwargs=None, 6483 lineno=lineno, 6484 col_offset=col, 6485 ) 6486 6487 6488def isdescendable(node): 6489 """Determines whether or not a node is worth visiting. Currently only 6490 UnaryOp and BoolOp nodes are visited. 6491 """ 6492 return isinstance(node, (UnaryOp, BoolOp)) 6493 6494 6495class CtxAwareTransformer(NodeTransformer): 6496 """Transforms a xonsh AST based to use subprocess calls when 6497 the first name in an expression statement is not known in the context. 6498 This assumes that the expression statement is instead parseable as 6499 a subprocess. 6500 """ 6501 6502 def __init__(self, parser): 6503 """Parameters 6504 ---------- 6505 parser : xonsh.Parser 6506 A parse instance to try to parse subprocess statements with. 6507 """ 6508 super(CtxAwareTransformer, self).__init__() 6509 self.parser = parser 6510 self.input = None 6511 self.contexts = [] 6512 self.lines = None 6513 self.mode = None 6514 self._nwith = 0 6515 self.filename = "<xonsh-code>" 6516 self.debug_level = 0 6517 6518 def ctxvisit(self, node, inp, ctx, mode="exec", filename=None, debug_level=0): 6519 """Transforms the node in a context-dependent way. 6520 6521 Parameters 6522 ---------- 6523 node : ast.AST 6524 A syntax tree to transform. 6525 input : str 6526 The input code in string format. 6527 ctx : dict 6528 The root context to use. 6529 filename : str, optional 6530 File we are to transform. 6531 debug_level : int, optional 6532 Debugging level to use in lexing and parsing. 6533 6534 Returns 6535 ------- 6536 node : ast.AST 6537 The transformed node. 6538 """ 6539 self.filename = self.filename if filename is None else filename 6540 self.debug_level = debug_level 6541 self.lines = inp.splitlines() 6542 self.contexts = [ctx, set()] 6543 self.mode = mode 6544 self._nwith = 0 6545 node = self.visit(node) 6546 del self.lines, self.contexts, self.mode 6547 self._nwith = 0 6548 return node 6549 6550 def ctxupdate(self, iterable): 6551 """Updated the most recent context.""" 6552 self.contexts[-1].update(iterable) 6553 6554 def ctxadd(self, value): 6555 """Adds a value the most recent context.""" 6556 self.contexts[-1].add(value) 6557 6558 def ctxremove(self, value): 6559 """Removes a value the most recent context.""" 6560 for ctx in reversed(self.contexts): 6561 if value in ctx: 6562 ctx.remove(value) 6563 break 6564 6565 def try_subproc_toks(self, node, strip_expr=False): 6566 """Tries to parse the line of the node as a subprocess.""" 6567 line, nlogical, idx = get_logical_line(self.lines, node.lineno - 1) 6568 if self.mode == "eval": 6569 mincol = len(line) - len(line.lstrip()) 6570 maxcol = None 6571 else: 6572 mincol = max(min_col(node) - 1, 0) 6573 maxcol = max_col(node) 6574 if mincol == maxcol: 6575 maxcol = find_next_break(line, mincol=mincol, lexer=self.parser.lexer) 6576 elif nlogical > 1: 6577 maxcol = None 6578 elif maxcol < len(line) and line[maxcol] == ";": 6579 pass 6580 else: 6581 maxcol += 1 6582 spline = subproc_toks( 6583 line, 6584 mincol=mincol, 6585 maxcol=maxcol, 6586 returnline=False, 6587 lexer=self.parser.lexer, 6588 ) 6589 if spline is None or len(spline) < len(line[mincol:maxcol]) + 2: 6590 # failed to get something consistent, try greedy wrap 6591 # The +2 comes from "![]" being length 3, minus 1 since maxcol 6592 # is one beyond the total length for slicing 6593 spline = subproc_toks( 6594 line, 6595 mincol=mincol, 6596 maxcol=maxcol, 6597 returnline=False, 6598 lexer=self.parser.lexer, 6599 greedy=True, 6600 ) 6601 if spline is None: 6602 return node 6603 try: 6604 newnode = self.parser.parse( 6605 spline, 6606 mode=self.mode, 6607 filename=self.filename, 6608 debug_level=(self.debug_level > 2), 6609 ) 6610 newnode = newnode.body 6611 if not isinstance(newnode, AST): 6612 # take the first (and only) Expr 6613 newnode = newnode[0] 6614 increment_lineno(newnode, n=node.lineno - 1) 6615 newnode.col_offset = node.col_offset 6616 if self.debug_level > 1: 6617 msg = "{0}:{1}:{2}{3} - {4}\n" "{0}:{1}:{2}{3} + {5}" 6618 mstr = "" if maxcol is None else ":" + str(maxcol) 6619 msg = msg.format(self.filename, node.lineno, mincol, mstr, line, spline) 6620 print(msg, file=sys.stderr) 6621 except SyntaxError: 6622 newnode = node 6623 if strip_expr and isinstance(newnode, Expr): 6624 newnode = newnode.value 6625 return newnode 6626 6627 def is_in_scope(self, node): 6628 """Determines whether or not the current node is in scope.""" 6629 names, store = gather_load_store_names(node) 6630 names -= store 6631 if not names: 6632 return True 6633 inscope = False 6634 for ctx in reversed(self.contexts): 6635 names -= ctx 6636 if not names: 6637 inscope = True 6638 break 6639 return inscope 6640 6641 # 6642 # Replacement visitors 6643 # 6644 6645 def visit_Expression(self, node): 6646 """Handle visiting an expression body.""" 6647 if isdescendable(node.body): 6648 node.body = self.visit(node.body) 6649 body = node.body 6650 inscope = self.is_in_scope(body) 6651 if not inscope: 6652 node.body = self.try_subproc_toks(body) 6653 return node 6654 6655 def visit_Expr(self, node): 6656 """Handle visiting an expression.""" 6657 if isdescendable(node.value): 6658 node.value = self.visit(node.value) # this allows diving into BoolOps 6659 if self.is_in_scope(node) or isinstance(node.value, Lambda): 6660 return node 6661 else: 6662 newnode = self.try_subproc_toks(node) 6663 if not isinstance(newnode, Expr): 6664 newnode = Expr( 6665 value=newnode, lineno=node.lineno, col_offset=node.col_offset 6666 ) 6667 if hasattr(node, "max_lineno"): 6668 newnode.max_lineno = node.max_lineno 6669 newnode.max_col = node.max_col 6670 return newnode 6671 6672 def visit_UnaryOp(self, node): 6673 """Handle visiting an unary operands, like not.""" 6674 if isdescendable(node.operand): 6675 node.operand = self.visit(node.operand) 6676 operand = node.operand 6677 inscope = self.is_in_scope(operand) 6678 if not inscope: 6679 node.operand = self.try_subproc_toks(operand, strip_expr=True) 6680 return node 6681 6682 def visit_BoolOp(self, node): 6683 """Handle visiting an boolean operands, like and/or.""" 6684 for i in range(len(node.values)): 6685 val = node.values[i] 6686 if isdescendable(val): 6687 val = node.values[i] = self.visit(val) 6688 inscope = self.is_in_scope(val) 6689 if not inscope: 6690 node.values[i] = self.try_subproc_toks(val, strip_expr=True) 6691 return node 6692 6693 # 6694 # Context aggregator visitors 6695 # 6696 6697 def visit_Assign(self, node): 6698 """Handle visiting an assignment statement.""" 6699 ups = set() 6700 for targ in node.targets: 6701 if isinstance(targ, (Tuple, List)): 6702 ups.update(leftmostname(elt) for elt in targ.elts) 6703 elif isinstance(targ, BinOp): 6704 newnode = self.try_subproc_toks(node) 6705 if newnode is node: 6706 ups.add(leftmostname(targ)) 6707 else: 6708 return newnode 6709 else: 6710 ups.add(leftmostname(targ)) 6711 self.ctxupdate(ups) 6712 return node 6713 6714 def visit_Import(self, node): 6715 """Handle visiting a import statement.""" 6716 for name in node.names: 6717 if name.asname is None: 6718 self.ctxadd(name.name) 6719 else: 6720 self.ctxadd(name.asname) 6721 return node 6722 6723 def visit_ImportFrom(self, node): 6724 """Handle visiting a "from ... import ..." statement.""" 6725 for name in node.names: 6726 if name.asname is None: 6727 self.ctxadd(name.name) 6728 else: 6729 self.ctxadd(name.asname) 6730 return node 6731 6732 def visit_With(self, node): 6733 """Handle visiting a with statement.""" 6734 for item in node.items: 6735 if item.optional_vars is not None: 6736 self.ctxupdate(gather_names(item.optional_vars)) 6737 self._nwith += 1 6738 self.generic_visit(node) 6739 self._nwith -= 1 6740 return node 6741 6742 def visit_For(self, node): 6743 """Handle visiting a for statement.""" 6744 targ = node.target 6745 self.ctxupdate(gather_names(targ)) 6746 self.generic_visit(node) 6747 return node 6748 6749 def visit_FunctionDef(self, node): 6750 """Handle visiting a function definition.""" 6751 self.ctxadd(node.name) 6752 self.contexts.append(set()) 6753 args = node.args 6754 argchain = [args.args, args.kwonlyargs] 6755 if args.vararg is not None: 6756 argchain.append((args.vararg,)) 6757 if args.kwarg is not None: 6758 argchain.append((args.kwarg,)) 6759 self.ctxupdate(a.arg for a in itertools.chain.from_iterable(argchain)) 6760 self.generic_visit(node) 6761 self.contexts.pop() 6762 return node 6763 6764 def visit_ClassDef(self, node): 6765 """Handle visiting a class definition.""" 6766 self.ctxadd(node.name) 6767 self.contexts.append(set()) 6768 self.generic_visit(node) 6769 self.contexts.pop() 6770 return node 6771 6772 def visit_Delete(self, node): 6773 """Handle visiting a del statement.""" 6774 for targ in node.targets: 6775 if isinstance(targ, Name): 6776 self.ctxremove(targ.id) 6777 self.generic_visit(node) 6778 return node 6779 6780 def visit_Try(self, node): 6781 """Handle visiting a try statement.""" 6782 for handler in node.handlers: 6783 if handler.name is not None: 6784 self.ctxadd(handler.name) 6785 self.generic_visit(node) 6786 return node 6787 6788 def visit_Global(self, node): 6789 """Handle visiting a global statement.""" 6790 self.contexts[1].update(node.names) # contexts[1] is the global ctx 6791 self.generic_visit(node) 6792 return node 6793 6794 6795def pdump(s, **kwargs): 6796 """performs a pretty dump of an AST node.""" 6797 if isinstance(s, AST): 6798 s = dump(s, **kwargs).replace(",", ",\n") 6799 openers = "([{" 6800 closers = ")]}" 6801 lens = len(s) + 1 6802 if lens == 1: 6803 return s 6804 i = min([s.find(o) % lens for o in openers]) 6805 if i == lens - 1: 6806 return s 6807 closer = closers[openers.find(s[i])] 6808 j = s.rfind(closer) 6809 if j == -1 or j <= i: 6810 return s[: i + 1] + "\n" + textwrap.indent(pdump(s[i + 1 :]), " ") 6811 pre = s[: i + 1] + "\n" 6812 mid = s[i + 1 : j] 6813 post = "\n" + s[j:] 6814 mid = textwrap.indent(pdump(mid), " ") 6815 if "(" in post or "[" in post or "{" in post: 6816 post = pdump(post) 6817 return pre + mid + post 6818 6819 6820def pprint_ast(s, *, sep=None, end=None, file=None, flush=False, **kwargs): 6821 """Performs a pretty print of the AST nodes.""" 6822 print(pdump(s, **kwargs), sep=sep, end=end, file=file, flush=flush) 6823 6824 6825# 6826# Private helpers 6827# 6828 6829 6830def _getblockattr(name, lineno, col): 6831 """calls getattr(name, '__xonsh_block__', False).""" 6832 return xonsh_call( 6833 "getattr", 6834 args=[ 6835 Name(id=name, ctx=Load(), lineno=lineno, col_offset=col), 6836 Str(s="__xonsh_block__", lineno=lineno, col_offset=col), 6837 NameConstant(value=False, lineno=lineno, col_offset=col), 6838 ], 6839 lineno=lineno, 6840 col=col, 6841 ) 6842 6843# 6844# color_tools 6845# 6846"""Tools for color handling in xonsh. 6847 6848This includes Convert values between RGB hex codes and xterm-256 6849color codes. Parts of this file were originally forked from Micah Elliott 6850http://MicahElliott.com Copyright (C) 2011 Micah Elliott. All rights reserved. 6851WTFPL http://sam.zoy.org/wtfpl/ 6852""" 6853# amalgamated re 6854math = _LazyModule.load('math', 'math') 6855# amalgamated xonsh.lazyasd 6856# amalgamated xonsh.tools 6857RE_BACKGROUND = LazyObject( 6858 lambda: re.compile("(BG#|BGHEX|BACKGROUND)"), globals(), "RE_BACKGROUND" 6859) 6860 6861 6862@lazyobject 6863def BASE_XONSH_COLORS(): 6864 return { 6865 "BLACK": (0, 0, 0), 6866 "RED": (170, 0, 0), 6867 "GREEN": (0, 170, 0), 6868 "YELLOW": (170, 85, 0), 6869 "BLUE": (0, 0, 170), 6870 "PURPLE": (170, 0, 170), 6871 "CYAN": (0, 170, 170), 6872 "WHITE": (170, 170, 170), 6873 "INTENSE_BLACK": (85, 85, 85), 6874 "INTENSE_RED": (255, 85, 85), 6875 "INTENSE_GREEN": (85, 255, 85), 6876 "INTENSE_YELLOW": (255, 255, 85), 6877 "INTENSE_BLUE": (85, 85, 255), 6878 "INTENSE_PURPLE": (255, 85, 255), 6879 "INTENSE_CYAN": (85, 255, 255), 6880 "INTENSE_WHITE": (255, 255, 255), 6881 } 6882 6883 6884@lazyobject 6885def CLUT(): 6886 """color look-up table""" 6887 return [ 6888 # 8-bit, RGB hex 6889 # Primary 3-bit (8 colors). Unique representation! 6890 ("00", "000000"), 6891 ("01", "800000"), 6892 ("02", "008000"), 6893 ("03", "808000"), 6894 ("04", "000080"), 6895 ("05", "800080"), 6896 ("06", "008080"), 6897 ("07", "c0c0c0"), 6898 # Equivalent "bright" versions of original 8 colors. 6899 ("08", "808080"), 6900 ("09", "ff0000"), 6901 ("10", "00ff00"), 6902 ("11", "ffff00"), 6903 ("12", "0000ff"), 6904 ("13", "ff00ff"), 6905 ("14", "00ffff"), 6906 ("15", "ffffff"), 6907 # Strictly ascending. 6908 ("16", "000000"), 6909 ("17", "00005f"), 6910 ("18", "000087"), 6911 ("19", "0000af"), 6912 ("20", "0000d7"), 6913 ("21", "0000ff"), 6914 ("22", "005f00"), 6915 ("23", "005f5f"), 6916 ("24", "005f87"), 6917 ("25", "005faf"), 6918 ("26", "005fd7"), 6919 ("27", "005fff"), 6920 ("28", "008700"), 6921 ("29", "00875f"), 6922 ("30", "008787"), 6923 ("31", "0087af"), 6924 ("32", "0087d7"), 6925 ("33", "0087ff"), 6926 ("34", "00af00"), 6927 ("35", "00af5f"), 6928 ("36", "00af87"), 6929 ("37", "00afaf"), 6930 ("38", "00afd7"), 6931 ("39", "00afff"), 6932 ("40", "00d700"), 6933 ("41", "00d75f"), 6934 ("42", "00d787"), 6935 ("43", "00d7af"), 6936 ("44", "00d7d7"), 6937 ("45", "00d7ff"), 6938 ("46", "00ff00"), 6939 ("47", "00ff5f"), 6940 ("48", "00ff87"), 6941 ("49", "00ffaf"), 6942 ("50", "00ffd7"), 6943 ("51", "00ffff"), 6944 ("52", "5f0000"), 6945 ("53", "5f005f"), 6946 ("54", "5f0087"), 6947 ("55", "5f00af"), 6948 ("56", "5f00d7"), 6949 ("57", "5f00ff"), 6950 ("58", "5f5f00"), 6951 ("59", "5f5f5f"), 6952 ("60", "5f5f87"), 6953 ("61", "5f5faf"), 6954 ("62", "5f5fd7"), 6955 ("63", "5f5fff"), 6956 ("64", "5f8700"), 6957 ("65", "5f875f"), 6958 ("66", "5f8787"), 6959 ("67", "5f87af"), 6960 ("68", "5f87d7"), 6961 ("69", "5f87ff"), 6962 ("70", "5faf00"), 6963 ("71", "5faf5f"), 6964 ("72", "5faf87"), 6965 ("73", "5fafaf"), 6966 ("74", "5fafd7"), 6967 ("75", "5fafff"), 6968 ("76", "5fd700"), 6969 ("77", "5fd75f"), 6970 ("78", "5fd787"), 6971 ("79", "5fd7af"), 6972 ("80", "5fd7d7"), 6973 ("81", "5fd7ff"), 6974 ("82", "5fff00"), 6975 ("83", "5fff5f"), 6976 ("84", "5fff87"), 6977 ("85", "5fffaf"), 6978 ("86", "5fffd7"), 6979 ("87", "5fffff"), 6980 ("88", "870000"), 6981 ("89", "87005f"), 6982 ("90", "870087"), 6983 ("91", "8700af"), 6984 ("92", "8700d7"), 6985 ("93", "8700ff"), 6986 ("94", "875f00"), 6987 ("95", "875f5f"), 6988 ("96", "875f87"), 6989 ("97", "875faf"), 6990 ("98", "875fd7"), 6991 ("99", "875fff"), 6992 ("100", "878700"), 6993 ("101", "87875f"), 6994 ("102", "878787"), 6995 ("103", "8787af"), 6996 ("104", "8787d7"), 6997 ("105", "8787ff"), 6998 ("106", "87af00"), 6999 ("107", "87af5f"), 7000 ("108", "87af87"), 7001 ("109", "87afaf"), 7002 ("110", "87afd7"), 7003 ("111", "87afff"), 7004 ("112", "87d700"), 7005 ("113", "87d75f"), 7006 ("114", "87d787"), 7007 ("115", "87d7af"), 7008 ("116", "87d7d7"), 7009 ("117", "87d7ff"), 7010 ("118", "87ff00"), 7011 ("119", "87ff5f"), 7012 ("120", "87ff87"), 7013 ("121", "87ffaf"), 7014 ("122", "87ffd7"), 7015 ("123", "87ffff"), 7016 ("124", "af0000"), 7017 ("125", "af005f"), 7018 ("126", "af0087"), 7019 ("127", "af00af"), 7020 ("128", "af00d7"), 7021 ("129", "af00ff"), 7022 ("130", "af5f00"), 7023 ("131", "af5f5f"), 7024 ("132", "af5f87"), 7025 ("133", "af5faf"), 7026 ("134", "af5fd7"), 7027 ("135", "af5fff"), 7028 ("136", "af8700"), 7029 ("137", "af875f"), 7030 ("138", "af8787"), 7031 ("139", "af87af"), 7032 ("140", "af87d7"), 7033 ("141", "af87ff"), 7034 ("142", "afaf00"), 7035 ("143", "afaf5f"), 7036 ("144", "afaf87"), 7037 ("145", "afafaf"), 7038 ("146", "afafd7"), 7039 ("147", "afafff"), 7040 ("148", "afd700"), 7041 ("149", "afd75f"), 7042 ("150", "afd787"), 7043 ("151", "afd7af"), 7044 ("152", "afd7d7"), 7045 ("153", "afd7ff"), 7046 ("154", "afff00"), 7047 ("155", "afff5f"), 7048 ("156", "afff87"), 7049 ("157", "afffaf"), 7050 ("158", "afffd7"), 7051 ("159", "afffff"), 7052 ("160", "d70000"), 7053 ("161", "d7005f"), 7054 ("162", "d70087"), 7055 ("163", "d700af"), 7056 ("164", "d700d7"), 7057 ("165", "d700ff"), 7058 ("166", "d75f00"), 7059 ("167", "d75f5f"), 7060 ("168", "d75f87"), 7061 ("169", "d75faf"), 7062 ("170", "d75fd7"), 7063 ("171", "d75fff"), 7064 ("172", "d78700"), 7065 ("173", "d7875f"), 7066 ("174", "d78787"), 7067 ("175", "d787af"), 7068 ("176", "d787d7"), 7069 ("177", "d787ff"), 7070 ("178", "d7af00"), 7071 ("179", "d7af5f"), 7072 ("180", "d7af87"), 7073 ("181", "d7afaf"), 7074 ("182", "d7afd7"), 7075 ("183", "d7afff"), 7076 ("184", "d7d700"), 7077 ("185", "d7d75f"), 7078 ("186", "d7d787"), 7079 ("187", "d7d7af"), 7080 ("188", "d7d7d7"), 7081 ("189", "d7d7ff"), 7082 ("190", "d7ff00"), 7083 ("191", "d7ff5f"), 7084 ("192", "d7ff87"), 7085 ("193", "d7ffaf"), 7086 ("194", "d7ffd7"), 7087 ("195", "d7ffff"), 7088 ("196", "ff0000"), 7089 ("197", "ff005f"), 7090 ("198", "ff0087"), 7091 ("199", "ff00af"), 7092 ("200", "ff00d7"), 7093 ("201", "ff00ff"), 7094 ("202", "ff5f00"), 7095 ("203", "ff5f5f"), 7096 ("204", "ff5f87"), 7097 ("205", "ff5faf"), 7098 ("206", "ff5fd7"), 7099 ("207", "ff5fff"), 7100 ("208", "ff8700"), 7101 ("209", "ff875f"), 7102 ("210", "ff8787"), 7103 ("211", "ff87af"), 7104 ("212", "ff87d7"), 7105 ("213", "ff87ff"), 7106 ("214", "ffaf00"), 7107 ("215", "ffaf5f"), 7108 ("216", "ffaf87"), 7109 ("217", "ffafaf"), 7110 ("218", "ffafd7"), 7111 ("219", "ffafff"), 7112 ("220", "ffd700"), 7113 ("221", "ffd75f"), 7114 ("222", "ffd787"), 7115 ("223", "ffd7af"), 7116 ("224", "ffd7d7"), 7117 ("225", "ffd7ff"), 7118 ("226", "ffff00"), 7119 ("227", "ffff5f"), 7120 ("228", "ffff87"), 7121 ("229", "ffffaf"), 7122 ("230", "ffffd7"), 7123 ("231", "ffffff"), 7124 # Gray-scale range. 7125 ("232", "080808"), 7126 ("233", "121212"), 7127 ("234", "1c1c1c"), 7128 ("235", "262626"), 7129 ("236", "303030"), 7130 ("237", "3a3a3a"), 7131 ("238", "444444"), 7132 ("239", "4e4e4e"), 7133 ("240", "585858"), 7134 ("241", "626262"), 7135 ("242", "6c6c6c"), 7136 ("243", "767676"), 7137 ("244", "808080"), 7138 ("245", "8a8a8a"), 7139 ("246", "949494"), 7140 ("247", "9e9e9e"), 7141 ("248", "a8a8a8"), 7142 ("249", "b2b2b2"), 7143 ("250", "bcbcbc"), 7144 ("251", "c6c6c6"), 7145 ("252", "d0d0d0"), 7146 ("253", "dadada"), 7147 ("254", "e4e4e4"), 7148 ("255", "eeeeee"), 7149 ] 7150 7151 7152def _str2hex(hexstr): 7153 return int(hexstr, 16) 7154 7155 7156def _strip_hash(rgb): 7157 # Strip leading `#` if exists. 7158 if rgb.startswith("#"): 7159 rgb = rgb.lstrip("#") 7160 return rgb 7161 7162 7163@lazyobject 7164def SHORT_TO_RGB(): 7165 return dict(CLUT) 7166 7167 7168@lazyobject 7169def RGB_TO_SHORT(): 7170 return {v: k for k, v in SHORT_TO_RGB.items()} 7171 7172 7173def short2rgb(short): 7174 return SHORT_TO_RGB[short] 7175 7176 7177def rgb_to_256(rgb): 7178 """Find the closest ANSI 256 approximation to the given RGB value. 7179 7180 >>> rgb2short('123456') 7181 ('23', '005f5f') 7182 >>> rgb2short('ffffff') 7183 ('231', 'ffffff') 7184 >>> rgb2short('0DADD6') # vimeo logo 7185 ('38', '00afd7') 7186 7187 Parameters 7188 ---------- 7189 rgb : Hex code representing an RGB value, eg, 'abcdef' 7190 7191 Returns 7192 ------- 7193 String between 0 and 255, compatible with xterm. 7194 """ 7195 rgb = rgb.lstrip("#") 7196 if len(rgb) == 0: 7197 return "0", "000000" 7198 incs = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) 7199 # Break 6-char RGB code into 3 integer vals. 7200 parts = rgb_to_ints(rgb) 7201 res = [] 7202 for part in parts: 7203 i = 0 7204 while i < len(incs) - 1: 7205 s, b = incs[i], incs[i + 1] # smaller, bigger 7206 if s <= part <= b: 7207 s1 = abs(s - part) 7208 b1 = abs(b - part) 7209 if s1 < b1: 7210 closest = s 7211 else: 7212 closest = b 7213 res.append(closest) 7214 break 7215 i += 1 7216 res = "".join([("%02.x" % i) for i in res]) 7217 equiv = RGB_TO_SHORT[res] 7218 return equiv, res 7219 7220 7221rgb2short = rgb_to_256 7222 7223 7224@lazyobject 7225def RE_RGB3(): 7226 return re.compile(r"(.)(.)(.)") 7227 7228 7229@lazyobject 7230def RE_RGB6(): 7231 return re.compile(r"(..)(..)(..)") 7232 7233 7234def rgb_to_ints(rgb): 7235 if len(rgb) == 6: 7236 return tuple([int(h, 16) for h in RE_RGB6.split(rgb)[1:4]]) 7237 else: 7238 return tuple([int(h * 2, 16) for h in RE_RGB3.split(rgb)[1:4]]) 7239 7240 7241def color_dist(x, y): 7242 return math.sqrt((x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2 + (x[2] - y[2]) ** 2) 7243 7244 7245def find_closest_color(x, palette): 7246 return min(sorted(palette.keys())[::-1], key=lambda k: color_dist(x, palette[k])) 7247 7248 7249def make_palette(strings): 7250 """Makes a color palette from a collection of strings.""" 7251 palette = {} 7252 for s in strings: 7253 while "#" in s: 7254 _, t = s.split("#", 1) 7255 t, _, s = t.partition(" ") 7256 palette[t] = rgb_to_ints(t) 7257 return palette 7258 7259 7260@deprecated(deprecated_in="0.5.10", removed_in="0.6.0") 7261def make_pallete(*args, **kwargs): 7262 make_palette(*args, **kwargs) 7263 7264# 7265# commands_cache 7266# 7267# -*- coding: utf-8 -*- 7268"""Module for caching command & alias names as well as for predicting whether 7269a command will be able to be run in the background. 7270 7271A background predictor is a function that accepts a single argument list 7272and returns whether or not the process can be run in the background (returns 7273True) or must be run the foreground (returns False). 7274""" 7275# amalgamated os 7276# amalgamated time 7277# amalgamated builtins 7278argparse = _LazyModule.load('argparse', 'argparse') 7279# amalgamated collections.abc 7280# amalgamated xonsh.platform 7281# amalgamated xonsh.tools 7282# amalgamated xonsh.lazyasd 7283class CommandsCache(cabc.Mapping): 7284 """A lazy cache representing the commands available on the file system. 7285 The keys are the command names and the values a tuple of (loc, has_alias) 7286 where loc is either a str pointing to the executable on the file system or 7287 None (if no executable exists) and has_alias is a boolean flag for whether 7288 the command has an alias. 7289 """ 7290 7291 def __init__(self): 7292 self._cmds_cache = {} 7293 self._path_checksum = None 7294 self._alias_checksum = None 7295 self._path_mtime = -1 7296 self.threadable_predictors = default_threadable_predictors() 7297 7298 def __contains__(self, key): 7299 _ = self.all_commands 7300 return self.lazyin(key) 7301 7302 def __iter__(self): 7303 for cmd, (path, is_alias) in self.all_commands.items(): 7304 if ON_WINDOWS and path is not None: 7305 # All command keys are stored in uppercase on Windows. 7306 # This ensures the original command name is returned. 7307 cmd = pathbasename(path) 7308 yield cmd 7309 7310 def __len__(self): 7311 return len(self.all_commands) 7312 7313 def __getitem__(self, key): 7314 _ = self.all_commands 7315 return self.lazyget(key) 7316 7317 def is_empty(self): 7318 """Returns whether the cache is populated or not.""" 7319 return len(self._cmds_cache) == 0 7320 7321 @staticmethod 7322 def get_possible_names(name): 7323 """Generates the possible `PATHEXT` extension variants of a given executable 7324 name on Windows as a list, conserving the ordering in `PATHEXT`. 7325 Returns a list as `name` being the only item in it on other platforms.""" 7326 if ON_WINDOWS: 7327 pathext = builtins.__xonsh_env__.get("PATHEXT", []) 7328 name = name.upper() 7329 return [name + ext for ext in ([""] + pathext)] 7330 else: 7331 return [name] 7332 7333 @staticmethod 7334 def remove_dups(p): 7335 ret = list() 7336 for e in p: 7337 if e not in ret: 7338 ret.append(e) 7339 return ret 7340 7341 @property 7342 def all_commands(self): 7343 paths = builtins.__xonsh_env__.get("PATH", []) 7344 paths = CommandsCache.remove_dups(paths) 7345 path_immut = tuple(x for x in paths if os.path.isdir(x)) 7346 # did PATH change? 7347 path_hash = hash(path_immut) 7348 cache_valid = path_hash == self._path_checksum 7349 self._path_checksum = path_hash 7350 # did aliases change? 7351 alss = getattr(builtins, "aliases", dict()) 7352 al_hash = hash(frozenset(alss)) 7353 cache_valid = cache_valid and al_hash == self._alias_checksum 7354 self._alias_checksum = al_hash 7355 # did the contents of any directory in PATH change? 7356 max_mtime = 0 7357 for path in path_immut: 7358 mtime = os.stat(path).st_mtime 7359 if mtime > max_mtime: 7360 max_mtime = mtime 7361 cache_valid = cache_valid and (max_mtime <= self._path_mtime) 7362 self._path_mtime = max_mtime 7363 if cache_valid: 7364 return self._cmds_cache 7365 allcmds = {} 7366 for path in reversed(path_immut): 7367 # iterate backwards so that entries at the front of PATH overwrite 7368 # entries at the back. 7369 for cmd in executables_in(path): 7370 key = cmd.upper() if ON_WINDOWS else cmd 7371 allcmds[key] = (os.path.join(path, cmd), alss.get(key, None)) 7372 for cmd in alss: 7373 if cmd not in allcmds: 7374 key = cmd.upper() if ON_WINDOWS else cmd 7375 allcmds[key] = (cmd, True) 7376 self._cmds_cache = allcmds 7377 return allcmds 7378 7379 def cached_name(self, name): 7380 """Returns the name that would appear in the cache, if it exists.""" 7381 if name is None: 7382 return None 7383 cached = pathbasename(name) 7384 if ON_WINDOWS: 7385 keys = self.get_possible_names(cached) 7386 cached = next((k for k in keys if k in self._cmds_cache), None) 7387 return cached 7388 7389 def lazyin(self, key): 7390 """Checks if the value is in the current cache without the potential to 7391 update the cache. It just says whether the value is known *now*. This 7392 may not reflect precisely what is on the $PATH. 7393 """ 7394 return self.cached_name(key) in self._cmds_cache 7395 7396 def lazyiter(self): 7397 """Returns an iterator over the current cache contents without the 7398 potential to update the cache. This may not reflect what is on the 7399 $PATH. 7400 """ 7401 return iter(self._cmds_cache) 7402 7403 def lazylen(self): 7404 """Returns the length of the current cache contents without the 7405 potential to update the cache. This may not reflect precisely 7406 what is on the $PATH. 7407 """ 7408 return len(self._cmds_cache) 7409 7410 def lazyget(self, key, default=None): 7411 """A lazy value getter.""" 7412 return self._cmds_cache.get(self.cached_name(key), default) 7413 7414 def locate_binary(self, name, ignore_alias=False): 7415 """Locates an executable on the file system using the cache. 7416 7417 Arguments 7418 --------- 7419 name : str 7420 name of binary to search for 7421 ignore_alias : bool, optional 7422 Force return of binary path even if alias of ``name`` exists 7423 (default ``False``) 7424 """ 7425 # make sure the cache is up to date by accessing the property 7426 _ = self.all_commands 7427 return self.lazy_locate_binary(name, ignore_alias) 7428 7429 def lazy_locate_binary(self, name, ignore_alias=False): 7430 """Locates an executable in the cache, without checking its validity. 7431 7432 Arguments 7433 --------- 7434 name : str 7435 name of binary to search for 7436 ignore_alias : bool, optional 7437 Force return of binary path even if alias of ``name`` exists 7438 (default ``False``) 7439 """ 7440 possibilities = self.get_possible_names(name) 7441 if ON_WINDOWS: 7442 # Windows users expect to be able to execute files in the same 7443 # directory without `./` 7444 local_bin = next((fn for fn in possibilities if os.path.isfile(fn)), None) 7445 if local_bin: 7446 return os.path.abspath(local_bin) 7447 cached = next((cmd for cmd in possibilities if cmd in self._cmds_cache), None) 7448 if cached: 7449 (path, alias) = self._cmds_cache[cached] 7450 ispure = path == pathbasename(path) 7451 if alias and ignore_alias and ispure: 7452 # pure alias, which we are ignoring 7453 return None 7454 else: 7455 return path 7456 elif os.path.isfile(name) and name != pathbasename(name): 7457 return name 7458 7459 def is_only_functional_alias(self, name): 7460 """Returns whether or not a command is only a functional alias, and has 7461 no underlying executable. For example, the "cd" command is only available 7462 as a functional alias. 7463 """ 7464 _ = self.all_commands 7465 return self.lazy_is_only_functional_alias(name) 7466 7467 def lazy_is_only_functional_alias(self, name): 7468 """Returns whether or not a command is only a functional alias, and has 7469 no underlying executable. For example, the "cd" command is only available 7470 as a functional alias. This search is performed lazily. 7471 """ 7472 val = self._cmds_cache.get(name, None) 7473 if val is None: 7474 return False 7475 return ( 7476 val == (name, True) and self.locate_binary(name, ignore_alias=True) is None 7477 ) 7478 7479 def predict_threadable(self, cmd): 7480 """Predicts whether a command list is able to be run on a background 7481 thread, rather than the main thread. 7482 """ 7483 name = self.cached_name(cmd[0]) 7484 predictors = self.threadable_predictors 7485 if ON_WINDOWS: 7486 # On all names (keys) are stored in upper case so instead 7487 # we get the original cmd or alias name 7488 path, _ = self.lazyget(name, (None, None)) 7489 if path is None: 7490 return True 7491 else: 7492 name = pathbasename(path) 7493 if name not in predictors: 7494 pre, ext = os.path.splitext(name) 7495 if pre in predictors: 7496 predictors[name] = predictors[pre] 7497 if name not in predictors: 7498 predictors[name] = self.default_predictor(name, cmd[0]) 7499 predictor = predictors[name] 7500 return predictor(cmd[1:]) 7501 7502 # 7503 # Background Predictors (as methods) 7504 # 7505 7506 def default_predictor(self, name, cmd0): 7507 if ON_POSIX: 7508 return self.default_predictor_readbin( 7509 name, cmd0, timeout=0.1, failure=predict_true 7510 ) 7511 else: 7512 return predict_true 7513 7514 def default_predictor_readbin(self, name, cmd0, timeout, failure): 7515 """Make a default predictor by 7516 analyzing the content of the binary. Should only works on POSIX. 7517 Return failure if the analysis fails. 7518 """ 7519 fname = cmd0 if os.path.isabs(cmd0) else None 7520 fname = cmd0 if fname is None and os.sep in cmd0 else fname 7521 fname = self.lazy_locate_binary(name) if fname is None else fname 7522 7523 if fname is None: 7524 return failure 7525 if not os.path.isfile(fname): 7526 return failure 7527 7528 try: 7529 fd = os.open(fname, os.O_RDONLY | os.O_NONBLOCK) 7530 except Exception: 7531 return failure # opening error 7532 7533 search_for = { 7534 (b"ncurses",): [False], 7535 (b"libgpm",): [False], 7536 (b"isatty", b"tcgetattr", b"tcsetattr"): [False, False, False], 7537 } 7538 tstart = time.time() 7539 block = b"" 7540 while time.time() < tstart + timeout: 7541 previous_block = block 7542 try: 7543 block = os.read(fd, 2048) 7544 except Exception: 7545 # should not occur, except e.g. if a file is deleted a a dir is 7546 # created with the same name between os.path.isfile and os.open 7547 os.close(fd) 7548 return failure 7549 if len(block) == 0: 7550 os.close(fd) 7551 return predict_true # no keys of search_for found 7552 analyzed_block = previous_block + block 7553 for k, v in search_for.items(): 7554 for i in range(len(k)): 7555 if v[i]: 7556 continue 7557 if k[i] in analyzed_block: 7558 v[i] = True 7559 if all(v): 7560 os.close(fd) 7561 return predict_false # use one key of search_for 7562 os.close(fd) 7563 return failure # timeout 7564 7565 7566# 7567# Background Predictors 7568# 7569 7570 7571def predict_true(args): 7572 """Always say the process is threadable.""" 7573 return True 7574 7575 7576def predict_false(args): 7577 """Never say the process is threadable.""" 7578 return False 7579 7580 7581@lazyobject 7582def SHELL_PREDICTOR_PARSER(): 7583 p = argparse.ArgumentParser("shell", add_help=False) 7584 p.add_argument("-c", nargs="?", default=None) 7585 p.add_argument("filename", nargs="?", default=None) 7586 return p 7587 7588 7589def predict_shell(args): 7590 """Predict the backgroundability of the normal shell interface, which 7591 comes down to whether it is being run in subproc mode. 7592 """ 7593 ns, _ = SHELL_PREDICTOR_PARSER.parse_known_args(args) 7594 if ns.c is None and ns.filename is None: 7595 pred = False 7596 else: 7597 pred = True 7598 return pred 7599 7600 7601@lazyobject 7602def HELP_VER_PREDICTOR_PARSER(): 7603 p = argparse.ArgumentParser("cmd", add_help=False) 7604 p.add_argument("-h", "--help", dest="help", action="store_true", default=None) 7605 p.add_argument( 7606 "-v", "-V", "--version", dest="version", action="store_true", default=None 7607 ) 7608 return p 7609 7610 7611def predict_help_ver(args): 7612 """Predict the backgroundability of commands that have help & version 7613 switches: -h, --help, -v, -V, --version. If either of these options is 7614 present, the command is assumed to print to stdout normally and is therefore 7615 threadable. Otherwise, the command is assumed to not be threadable. 7616 This is useful for commands, like top, that normally enter alternate mode 7617 but may not in certain circumstances. 7618 """ 7619 ns, _ = HELP_VER_PREDICTOR_PARSER.parse_known_args(args) 7620 pred = ns.help is not None or ns.version is not None 7621 return pred 7622 7623 7624@lazyobject 7625def HG_PREDICTOR_PARSER(): 7626 p = argparse.ArgumentParser("hg", add_help=False) 7627 p.add_argument("command") 7628 p.add_argument( 7629 "-i", "--interactive", action="store_true", default=False, dest="interactive" 7630 ) 7631 return p 7632 7633 7634def predict_hg(args): 7635 """Predict if mercurial is about to be run in interactive mode. 7636 If it is interactive, predict False. If it isn't, predict True. 7637 """ 7638 ns, _ = HG_PREDICTOR_PARSER.parse_known_args(args) 7639 return not ns.interactive 7640 7641 7642def default_threadable_predictors(): 7643 """Generates a new defaultdict for known threadable predictors. 7644 The default is to predict true. 7645 """ 7646 # alphabetical, for what it is worth. 7647 predictors = { 7648 "bash": predict_shell, 7649 "csh": predict_shell, 7650 "clear": predict_false, 7651 "cls": predict_false, 7652 "cmd": predict_shell, 7653 "curl": predict_true, 7654 "ex": predict_false, 7655 "emacsclient": predict_false, 7656 "fish": predict_shell, 7657 "gvim": predict_help_ver, 7658 "hg": predict_hg, 7659 "htop": predict_help_ver, 7660 "ipython": predict_shell, 7661 "ksh": predict_shell, 7662 "less": predict_help_ver, 7663 "man": predict_help_ver, 7664 "more": predict_help_ver, 7665 "mvim": predict_help_ver, 7666 "mutt": predict_help_ver, 7667 "nano": predict_help_ver, 7668 "nvim": predict_false, 7669 "ponysay": predict_help_ver, 7670 "psql": predict_false, 7671 "python": predict_shell, 7672 "python2": predict_shell, 7673 "python3": predict_shell, 7674 "repo": predict_help_ver, 7675 "ranger": predict_help_ver, 7676 "rview": predict_false, 7677 "rvim": predict_false, 7678 "scp": predict_false, 7679 "sh": predict_shell, 7680 "ssh": predict_false, 7681 "startx": predict_false, 7682 "sudo": predict_help_ver, 7683 "tcsh": predict_shell, 7684 "telnet": predict_false, 7685 "top": predict_help_ver, 7686 "vi": predict_false, 7687 "view": predict_false, 7688 "vim": predict_false, 7689 "vimpager": predict_help_ver, 7690 "weechat": predict_help_ver, 7691 "xclip": predict_help_ver, 7692 "xo": predict_help_ver, 7693 "xonsh": predict_shell, 7694 "xon.sh": predict_shell, 7695 "zsh": predict_shell, 7696 } 7697 return predictors 7698 7699# 7700# diff_history 7701# 7702# -*- coding: utf-8 -*- 7703"""Tools for diff'ing two xonsh history files in a meaningful fashion.""" 7704difflib = _LazyModule.load('difflib', 'difflib') 7705# amalgamated datetime 7706# amalgamated itertools 7707# amalgamated argparse 7708# amalgamated xonsh.lazyjson 7709# amalgamated xonsh.tools 7710NO_COLOR_S = "{NO_COLOR}" 7711RED_S = "{RED}" 7712GREEN_S = "{GREEN}" 7713BOLD_RED_S = "{BOLD_RED}" 7714BOLD_GREEN_S = "{BOLD_GREEN}" 7715 7716# intern some strings 7717REPLACE_S = "replace" 7718DELETE_S = "delete" 7719INSERT_S = "insert" 7720EQUAL_S = "equal" 7721 7722 7723def bold_str_diff(a, b, sm=None): 7724 if sm is None: 7725 sm = difflib.SequenceMatcher() 7726 aline = RED_S + "- " 7727 bline = GREEN_S + "+ " 7728 sm.set_seqs(a, b) 7729 for tag, i1, i2, j1, j2 in sm.get_opcodes(): 7730 if tag == REPLACE_S: 7731 aline += BOLD_RED_S + a[i1:i2] + RED_S 7732 bline += BOLD_GREEN_S + b[j1:j2] + GREEN_S 7733 elif tag == DELETE_S: 7734 aline += BOLD_RED_S + a[i1:i2] + RED_S 7735 elif tag == INSERT_S: 7736 bline += BOLD_GREEN_S + b[j1:j2] + GREEN_S 7737 elif tag == EQUAL_S: 7738 aline += a[i1:i2] 7739 bline += b[j1:j2] 7740 else: 7741 raise RuntimeError("tag not understood") 7742 return aline + NO_COLOR_S + "\n" + bline + NO_COLOR_S + "\n" 7743 7744 7745def redline(line): 7746 return "{red}- {line}{no_color}\n".format(red=RED_S, line=line, no_color=NO_COLOR_S) 7747 7748 7749def greenline(line): 7750 return "{green}+ {line}{no_color}\n".format( 7751 green=GREEN_S, line=line, no_color=NO_COLOR_S 7752 ) 7753 7754 7755def highlighted_ndiff(a, b): 7756 """Returns a highlighted string, with bold characters where different.""" 7757 s = "" 7758 sm = difflib.SequenceMatcher() 7759 sm.set_seqs(a, b) 7760 linesm = difflib.SequenceMatcher() 7761 for tag, i1, i2, j1, j2 in sm.get_opcodes(): 7762 if tag == REPLACE_S: 7763 for aline, bline in itertools.zip_longest(a[i1:i2], b[j1:j2]): 7764 if bline is None: 7765 s += redline(aline) 7766 elif aline is None: 7767 s += greenline(bline) 7768 else: 7769 s += bold_str_diff(aline, bline, sm=linesm) 7770 elif tag == DELETE_S: 7771 for aline in a[i1:i2]: 7772 s += redline(aline) 7773 elif tag == INSERT_S: 7774 for bline in b[j1:j2]: 7775 s += greenline(bline) 7776 elif tag == EQUAL_S: 7777 for aline in a[i1:i2]: 7778 s += " " + aline + "\n" 7779 else: 7780 raise RuntimeError("tag not understood") 7781 return s 7782 7783 7784class HistoryDiffer(object): 7785 """This class helps diff two xonsh history files.""" 7786 7787 def __init__(self, afile, bfile, reopen=False, verbose=False): 7788 """ 7789 Parameters 7790 ---------- 7791 afile : file handle or str 7792 The first file to diff 7793 bfile : file handle or str 7794 The second file to diff 7795 reopen : bool, optional 7796 Whether or not to reopen the file handles each time. The default here is 7797 opposite from the LazyJSON default because we know that we will be doing 7798 a lot of reading so it is best to keep the handles open. 7799 verbose : bool, optional 7800 Whether to print a verbose amount of information. 7801 """ 7802 self.a = LazyJSON(afile, reopen=reopen) 7803 self.b = LazyJSON(bfile, reopen=reopen) 7804 self.verbose = verbose 7805 self.sm = difflib.SequenceMatcher(autojunk=False) 7806 7807 def __del__(self): 7808 self.a.close() 7809 self.b.close() 7810 7811 def __str__(self): 7812 return self.format() 7813 7814 def _header_line(self, lj): 7815 s = lj._f.name if hasattr(lj._f, "name") else "" 7816 s += " (" + lj["sessionid"] + ")" 7817 s += " [locked]" if lj["locked"] else " [unlocked]" 7818 ts = lj["ts"].load() 7819 ts0 = datetime.datetime.fromtimestamp(ts[0]) 7820 s += " started: " + ts0.isoformat(" ") 7821 if ts[1] is not None: 7822 ts1 = datetime.datetime.fromtimestamp(ts[1]) 7823 s += " stopped: " + ts1.isoformat(" ") + " runtime: " + str(ts1 - ts0) 7824 return s 7825 7826 def header(self): 7827 """Computes a header string difference.""" 7828 s = "{red}--- {aline}{no_color}\n" "{green}+++ {bline}{no_color}" 7829 s = s.format( 7830 aline=self._header_line(self.a), 7831 bline=self._header_line(self.b), 7832 red=RED_S, 7833 green=GREEN_S, 7834 no_color=NO_COLOR_S, 7835 ) 7836 return s 7837 7838 def _env_both_diff(self, in_both, aenv, benv): 7839 sm = self.sm 7840 s = "" 7841 for key in sorted(in_both): 7842 aval = aenv[key] 7843 bval = benv[key] 7844 if aval == bval: 7845 continue 7846 s += "{0!r} is in both, but differs\n".format(key) 7847 s += bold_str_diff(aval, bval, sm=sm) + "\n" 7848 return s 7849 7850 def _env_in_one_diff(self, x, y, color, xid, xenv): 7851 only_x = sorted(x - y) 7852 if len(only_x) == 0: 7853 return "" 7854 if self.verbose: 7855 xstr = ",\n".join( 7856 [" {0!r}: {1!r}".format(key, xenv[key]) for key in only_x] 7857 ) 7858 xstr = "\n" + xstr 7859 else: 7860 xstr = ", ".join(["{0!r}".format(key) for key in only_x]) 7861 in_x = "These vars are only in {color}{xid}{no_color}: {{{xstr}}}\n\n" 7862 return in_x.format(xid=xid, color=color, no_color=NO_COLOR_S, xstr=xstr) 7863 7864 def envdiff(self): 7865 """Computes the difference between the environments.""" 7866 aenv = self.a["env"].load() 7867 benv = self.b["env"].load() 7868 akeys = frozenset(aenv) 7869 bkeys = frozenset(benv) 7870 in_both = akeys & bkeys 7871 if len(in_both) == len(akeys) == len(bkeys): 7872 keydiff = self._env_both_diff(in_both, aenv, benv) 7873 if len(keydiff) == 0: 7874 return "" 7875 in_a = in_b = "" 7876 else: 7877 keydiff = self._env_both_diff(in_both, aenv, benv) 7878 in_a = self._env_in_one_diff(akeys, bkeys, RED_S, self.a["sessionid"], aenv) 7879 in_b = self._env_in_one_diff( 7880 bkeys, akeys, GREEN_S, self.b["sessionid"], benv 7881 ) 7882 s = "Environment\n-----------\n" + in_a + keydiff + in_b 7883 return s 7884 7885 def _cmd_in_one_diff(self, inp, i, xlj, xid, color): 7886 s = "cmd #{i} only in {color}{xid}{no_color}:\n" 7887 s = s.format(i=i, color=color, xid=xid, no_color=NO_COLOR_S) 7888 lines = inp.splitlines() 7889 lt = "{color}{pre}{no_color} {line}\n" 7890 s += lt.format(color=color, no_color=NO_COLOR_S, line=lines[0], pre=">>>") 7891 for line in lines[1:]: 7892 s += lt.format(color=color, no_color=NO_COLOR_S, line=line, pre="...") 7893 if not self.verbose: 7894 return s + "\n" 7895 out = xlj["cmds"][0].get("out", "Note: no output stored") 7896 s += out.rstrip() + "\n\n" 7897 return s 7898 7899 def _cmd_out_and_rtn_diff(self, i, j): 7900 s = "" 7901 aout = self.a["cmds"][i].get("out", None) 7902 bout = self.b["cmds"][j].get("out", None) 7903 if aout is None and bout is None: 7904 # s += 'Note: neither output stored\n' 7905 pass 7906 elif bout is None: 7907 aid = self.a["sessionid"] 7908 s += "Note: only {red}{aid}{no_color} output stored\n".format( 7909 red=RED_S, aid=aid, no_color=NO_COLOR_S 7910 ) 7911 elif aout is None: 7912 bid = self.b["sessionid"] 7913 s += "Note: only {green}{bid}{no_color} output stored\n".format( 7914 green=GREEN_S, bid=bid, no_color=NO_COLOR_S 7915 ) 7916 elif aout != bout: 7917 s += "Outputs differ\n" 7918 s += highlighted_ndiff(aout.splitlines(), bout.splitlines()) 7919 else: 7920 pass 7921 artn = self.a["cmds"][i]["rtn"] 7922 brtn = self.b["cmds"][j]["rtn"] 7923 if artn != brtn: 7924 s += ( 7925 "Return vals {red}{artn}{no_color} & {green}{brtn}{no_color} differ\n" 7926 ).format( 7927 red=RED_S, green=GREEN_S, no_color=NO_COLOR_S, artn=artn, brtn=brtn 7928 ) 7929 return s 7930 7931 def _cmd_replace_diff(self, i, ainp, aid, j, binp, bid): 7932 s = ( 7933 "cmd #{i} in {red}{aid}{no_color} is replaced by \n" 7934 "cmd #{j} in {green}{bid}{no_color}:\n" 7935 ) 7936 s = s.format( 7937 i=i, aid=aid, j=j, bid=bid, red=RED_S, green=GREEN_S, no_color=NO_COLOR_S 7938 ) 7939 s += highlighted_ndiff(ainp.splitlines(), binp.splitlines()) 7940 if not self.verbose: 7941 return s + "\n" 7942 s += self._cmd_out_and_rtn_diff(i, j) 7943 return s + "\n" 7944 7945 def cmdsdiff(self): 7946 """Computes the difference of the commands themselves.""" 7947 aid = self.a["sessionid"] 7948 bid = self.b["sessionid"] 7949 ainps = [c["inp"] for c in self.a["cmds"]] 7950 binps = [c["inp"] for c in self.b["cmds"]] 7951 sm = self.sm 7952 sm.set_seqs(ainps, binps) 7953 s = "" 7954 for tag, i1, i2, j1, j2 in sm.get_opcodes(): 7955 if tag == REPLACE_S: 7956 zipper = itertools.zip_longest 7957 for i, ainp, j, binp in zipper( 7958 range(i1, i2), ainps[i1:i2], range(j1, j2), binps[j1:j2] 7959 ): 7960 if j is None: 7961 s += self._cmd_in_one_diff(ainp, i, self.a, aid, RED_S) 7962 elif i is None: 7963 s += self._cmd_in_one_diff(binp, j, self.b, bid, GREEN_S) 7964 else: 7965 self._cmd_replace_diff(i, ainp, aid, j, binp, bid) 7966 elif tag == DELETE_S: 7967 for i, inp in enumerate(ainps[i1:i2], i1): 7968 s += self._cmd_in_one_diff(inp, i, self.a, aid, RED_S) 7969 elif tag == INSERT_S: 7970 for j, inp in enumerate(binps[j1:j2], j1): 7971 s += self._cmd_in_one_diff(inp, j, self.b, bid, GREEN_S) 7972 elif tag == EQUAL_S: 7973 for i, j in zip(range(i1, i2), range(j1, j2)): 7974 odiff = self._cmd_out_and_rtn_diff(i, j) 7975 if len(odiff) > 0: 7976 h = ( 7977 "cmd #{i} in {red}{aid}{no_color} input is the same as \n" 7978 "cmd #{j} in {green}{bid}{no_color}, but output differs:\n" 7979 ) 7980 s += h.format( 7981 i=i, 7982 aid=aid, 7983 j=j, 7984 bid=bid, 7985 red=RED_S, 7986 green=GREEN_S, 7987 no_color=NO_COLOR_S, 7988 ) 7989 s += odiff + "\n" 7990 else: 7991 raise RuntimeError("tag not understood") 7992 if len(s) == 0: 7993 return s 7994 return "Commands\n--------\n" + s 7995 7996 def format(self): 7997 """Formats the difference between the two history files.""" 7998 s = self.header() 7999 ed = self.envdiff() 8000 if len(ed) > 0: 8001 s += "\n\n" + ed 8002 cd = self.cmdsdiff() 8003 if len(cd) > 0: 8004 s += "\n\n" + cd 8005 return s.rstrip() 8006 8007 8008_HD_PARSER = None 8009 8010 8011def dh_create_parser(p=None): 8012 global _HD_PARSER 8013 p_was_none = p is None 8014 if _HD_PARSER is not None and p_was_none: 8015 return _HD_PARSER 8016 if p_was_none: 8017 p = argparse.ArgumentParser( 8018 "diff-history", description="diffs two xonsh history files" 8019 ) 8020 p.add_argument( 8021 "--reopen", 8022 dest="reopen", 8023 default=False, 8024 action="store_true", 8025 help="make lazy file loading reopen files each time", 8026 ) 8027 p.add_argument( 8028 "-v", 8029 "--verbose", 8030 dest="verbose", 8031 default=False, 8032 action="store_true", 8033 help="whether to print even more information", 8034 ) 8035 p.add_argument("a", help="first file in diff") 8036 p.add_argument("b", help="second file in diff") 8037 if p_was_none: 8038 _HD_PARSER = p 8039 return p 8040 8041 8042def dh_main_action(ns, hist=None, stdout=None, stderr=None): 8043 hd = HistoryDiffer(ns.a, ns.b, reopen=ns.reopen, verbose=ns.verbose) 8044 print_color(hd.format(), file=stdout) 8045 8046# 8047# events 8048# 8049""" 8050Events for xonsh. 8051 8052In all likelihood, you want builtins.events 8053 8054The best way to "declare" an event is something like:: 8055 8056 events.doc('on_spam', "Comes with eggs") 8057""" 8058abc = _LazyModule.load('abc', 'abc') 8059# amalgamated builtins 8060collections = _LazyModule.load('collections', 'collections.abc') 8061inspect = _LazyModule.load('inspect', 'inspect') 8062# amalgamated xonsh.tools 8063def has_kwargs(func): 8064 return any( 8065 p.kind == p.VAR_KEYWORD for p in inspect.signature(func).parameters.values() 8066 ) 8067 8068 8069def debug_level(): 8070 if hasattr(builtins, "__xonsh_env__"): 8071 return builtins.__xonsh_env__.get("XONSH_DEBUG") 8072 # FIXME: Under py.test, return 1(?) 8073 else: 8074 return 0 # Optimize for speed, not guaranteed correctness 8075 8076 8077class AbstractEvent(collections.abc.MutableSet, abc.ABC): 8078 """ 8079 A given event that handlers can register against. 8080 8081 Acts as a ``MutableSet`` for registered handlers. 8082 8083 Note that ordering is never guaranteed. 8084 """ 8085 8086 @property 8087 def species(self): 8088 """ 8089 The species (basically, class) of the event 8090 """ 8091 return type(self).__bases__[ 8092 0 8093 ] # events.on_chdir -> <class on_chdir> -> <class Event> 8094 8095 def __call__(self, handler): 8096 """ 8097 Registers a handler. It's suggested to use this as a decorator. 8098 8099 A decorator method is added to the handler, validator(). If a validator 8100 function is added, it can filter if the handler will be considered. The 8101 validator takes the same arguments as the handler. If it returns False, 8102 the handler will not called or considered, as if it was not registered 8103 at all. 8104 8105 Parameters 8106 ---------- 8107 handler : callable 8108 The handler to register 8109 8110 Returns 8111 ------- 8112 rtn : callable 8113 The handler 8114 """ 8115 # Using Python's "private" munging to minimize hypothetical collisions 8116 handler.__validator = None 8117 if debug_level(): 8118 if not has_kwargs(handler): 8119 raise ValueError("Event handlers need a **kwargs for future proofing") 8120 self.add(handler) 8121 8122 def validator(vfunc): 8123 """ 8124 Adds a validator function to a handler to limit when it is considered. 8125 """ 8126 if debug_level(): 8127 if not has_kwargs(handler): 8128 raise ValueError( 8129 "Event validators need a **kwargs for future proofing" 8130 ) 8131 handler.__validator = vfunc 8132 8133 handler.validator = validator 8134 8135 return handler 8136 8137 def _filterhandlers(self, handlers, **kwargs): 8138 """ 8139 Helper method for implementing classes. Generates the handlers that pass validation. 8140 """ 8141 for handler in handlers: 8142 if handler.__validator is not None and not handler.__validator(**kwargs): 8143 continue 8144 yield handler 8145 8146 @abc.abstractmethod 8147 def fire(self, **kwargs): 8148 """ 8149 Fires an event, calling registered handlers with the given arguments. 8150 8151 Parameters 8152 ---------- 8153 **kwargs : 8154 Keyword arguments to pass to each handler 8155 """ 8156 8157 8158class Event(AbstractEvent): 8159 """ 8160 An event species for notify and scatter-gather events. 8161 """ 8162 8163 # Wish I could just pull from set... 8164 def __init__(self): 8165 self._handlers = set() 8166 8167 def __len__(self): 8168 return len(self._handlers) 8169 8170 def __contains__(self, item): 8171 return item in self._handlers 8172 8173 def __iter__(self): 8174 yield from self._handlers 8175 8176 def add(self, item): 8177 """ 8178 Add an element to a set. 8179 8180 This has no effect if the element is already present. 8181 """ 8182 self._handlers.add(item) 8183 8184 def discard(self, item): 8185 """ 8186 Remove an element from a set if it is a member. 8187 8188 If the element is not a member, do nothing. 8189 """ 8190 self._handlers.discard(item) 8191 8192 def fire(self, **kwargs): 8193 """ 8194 Fires an event, calling registered handlers with the given arguments. A non-unique iterable 8195 of the results is returned. 8196 8197 Each handler is called immediately. Exceptions are turned in to warnings. 8198 8199 Parameters 8200 ---------- 8201 **kwargs : 8202 Keyword arguments to pass to each handler 8203 8204 Returns 8205 ------- 8206 vals : iterable 8207 Return values of each handler. If multiple handlers return the same value, it will 8208 appear multiple times. 8209 """ 8210 vals = [] 8211 for handler in self._filterhandlers(self._handlers, **kwargs): 8212 try: 8213 rv = handler(**kwargs) 8214 except Exception: 8215 print_exception("Exception raised in event handler; ignored.") 8216 else: 8217 vals.append(rv) 8218 return vals 8219 8220 8221class LoadEvent(AbstractEvent): 8222 """ 8223 An event species where each handler is called exactly once, shortly after either the event is 8224 fired or the handler is registered (whichever is later). Additional firings are ignored. 8225 8226 Note: Does not support scatter/gather, due to never knowing when we have all the handlers. 8227 8228 Note: Maintains a strong reference to pargs/kwargs in case of the addition of future handlers. 8229 8230 Note: This is currently NOT thread safe. 8231 """ 8232 8233 def __init__(self): 8234 self._fired = set() 8235 self._unfired = set() 8236 self._hasfired = False 8237 8238 def __len__(self): 8239 return len(self._fired) + len(self._unfired) 8240 8241 def __contains__(self, item): 8242 return item in self._fired or item in self._unfired 8243 8244 def __iter__(self): 8245 yield from self._fired 8246 yield from self._unfired 8247 8248 def add(self, item): 8249 """ 8250 Add an element to a set. 8251 8252 This has no effect if the element is already present. 8253 """ 8254 if self._hasfired: 8255 self._call(item) 8256 self._fired.add(item) 8257 else: 8258 self._unfired.add(item) 8259 8260 def discard(self, item): 8261 """ 8262 Remove an element from a set if it is a member. 8263 8264 If the element is not a member, do nothing. 8265 """ 8266 self._fired.discard(item) 8267 self._unfired.discard(item) 8268 8269 def _call(self, handler): 8270 try: 8271 handler(**self._kwargs) 8272 except Exception: 8273 print_exception("Exception raised in event handler; ignored.") 8274 8275 def fire(self, **kwargs): 8276 if self._hasfired: 8277 return 8278 self._kwargs = kwargs 8279 while self._unfired: 8280 handler = self._unfired.pop() 8281 self._call(handler) 8282 self._hasfired = True 8283 return () # Entirely for API compatibility 8284 8285 8286class EventManager: 8287 """ 8288 Container for all events in a system. 8289 8290 Meant to be a singleton, but doesn't enforce that itself. 8291 8292 Each event is just an attribute. They're created dynamically on first use. 8293 """ 8294 8295 def doc(self, name, docstring): 8296 """ 8297 Applies a docstring to an event. 8298 8299 Parameters 8300 ---------- 8301 name : str 8302 The name of the event, eg "on_precommand" 8303 docstring : str 8304 The docstring to apply to the event 8305 """ 8306 type(getattr(self, name)).__doc__ = docstring 8307 8308 @staticmethod 8309 def _mkevent(name, species=Event, doc=None): 8310 # NOTE: Also used in `xonsh_events` test fixture 8311 # (A little bit of magic to enable docstrings to work right) 8312 return type( 8313 name, 8314 (species,), 8315 { 8316 "__doc__": doc, 8317 "__module__": "xonsh.events", 8318 "__qualname__": "events." + name, 8319 }, 8320 )() 8321 8322 def transmogrify(self, name, species): 8323 """ 8324 Converts an event from one species to another, preserving handlers and docstring. 8325 8326 Please note: Some species maintain specialized state. This is lost on transmogrification. 8327 8328 Parameters 8329 ---------- 8330 name : str 8331 The name of the event, eg "on_precommand" 8332 species : subclass of AbstractEvent 8333 The type to turn the event in to. 8334 """ 8335 if isinstance(species, str): 8336 species = globals()[species] 8337 8338 if not issubclass(species, AbstractEvent): 8339 raise ValueError("Invalid event class; must be a subclass of AbstractEvent") 8340 8341 oldevent = getattr(self, name) 8342 newevent = self._mkevent(name, species, type(oldevent).__doc__) 8343 setattr(self, name, newevent) 8344 8345 for handler in oldevent: 8346 newevent.add(handler) 8347 8348 def __getattr__(self, name): 8349 """Get an event, if it doesn't already exist.""" 8350 if name.startswith("_"): 8351 raise AttributeError 8352 # This is only called if the attribute doesn't exist, so create the Event... 8353 e = self._mkevent(name) 8354 # ... and save it. 8355 setattr(self, name, e) 8356 # Now it exists, and we won't be called again. 8357 return e 8358 8359 8360# Not lazy because: 8361# 1. Initialization of EventManager can't be much cheaper 8362# 2. It's expected to be used at load time, negating any benefits of using lazy object 8363events = EventManager() 8364 8365# 8366# foreign_shells 8367# 8368# -*- coding: utf-8 -*- 8369"""Tools to help interface with foreign shells, such as Bash.""" 8370# amalgamated os 8371# amalgamated re 8372# amalgamated json 8373shlex = _LazyModule.load('shlex', 'shlex') 8374# amalgamated sys 8375tempfile = _LazyModule.load('tempfile', 'tempfile') 8376# amalgamated builtins 8377# amalgamated subprocess 8378# amalgamated warnings 8379# amalgamated functools 8380# amalgamated collections.abc 8381# amalgamated xonsh.lazyasd 8382# amalgamated xonsh.tools 8383# amalgamated xonsh.platform 8384COMMAND = """{seterrprevcmd} 8385{prevcmd} 8386echo __XONSH_ENV_BEG__ 8387{envcmd} 8388echo __XONSH_ENV_END__ 8389echo __XONSH_ALIAS_BEG__ 8390{aliascmd} 8391echo __XONSH_ALIAS_END__ 8392echo __XONSH_FUNCS_BEG__ 8393{funcscmd} 8394echo __XONSH_FUNCS_END__ 8395{postcmd} 8396{seterrpostcmd}""" 8397 8398DEFAULT_BASH_FUNCSCMD = r"""# get function names from declare 8399declstr=$(declare -F) 8400read -r -a decls <<< $declstr 8401funcnames="" 8402for((n=0;n<${#decls[@]};n++)); do 8403 if (( $(($n % 3 )) == 2 )); then 8404 # get every 3rd entry 8405 funcnames="$funcnames ${decls[$n]}" 8406 fi 8407done 8408 8409# get functions locations: funcname lineno filename 8410shopt -s extdebug 8411namelocfilestr=$(declare -F $funcnames) 8412shopt -u extdebug 8413 8414# print just names and files as JSON object 8415read -r -a namelocfile <<< $namelocfilestr 8416sep=" " 8417namefile="{" 8418while IFS='' read -r line || [[ -n "$line" ]]; do 8419 name=${line%%"$sep"*} 8420 locfile=${line#*"$sep"} 8421 loc=${locfile%%"$sep"*} 8422 file=${locfile#*"$sep"} 8423 namefile="${namefile}\"${name}\":\"${file//\\/\\\\}\"," 8424done <<< "$namelocfilestr" 8425if [[ "{" == "${namefile}" ]]; then 8426 namefile="${namefile}}" 8427else 8428 namefile="${namefile%?}}" 8429fi 8430echo $namefile""" 8431 8432DEFAULT_ZSH_FUNCSCMD = """# get function names 8433autoload -U is-at-least # We'll need to version check zsh 8434namefile="{" 8435for name in ${(ok)functions}; do 8436 # force zsh to load the func in order to get the filename, 8437 # but use +X so that it isn't executed. 8438 autoload +X $name || continue 8439 loc=$(whence -v $name) 8440 loc=${(z)loc} 8441 if is-at-least 5.2; then 8442 file=${loc[-1]} 8443 else 8444 file=${loc[7,-1]} 8445 fi 8446 namefile="${namefile}\\"${name}\\":\\"${(Q)file:A}\\"," 8447done 8448if [[ "{" == "${namefile}" ]]; then 8449 namefile="${namefile}}" 8450else 8451 namefile="${namefile%?}}" 8452fi 8453echo ${namefile}""" 8454 8455 8456# mapping of shell name aliases to keys in other lookup dictionaries. 8457@lazyobject 8458def CANON_SHELL_NAMES(): 8459 return { 8460 "bash": "bash", 8461 "/bin/bash": "bash", 8462 "zsh": "zsh", 8463 "/bin/zsh": "zsh", 8464 "/usr/bin/zsh": "zsh", 8465 "cmd": "cmd", 8466 "cmd.exe": "cmd", 8467 } 8468 8469 8470@lazyobject 8471def DEFAULT_ENVCMDS(): 8472 return {"bash": "env", "zsh": "env", "cmd": "set"} 8473 8474 8475@lazyobject 8476def DEFAULT_ALIASCMDS(): 8477 return {"bash": "alias", "zsh": "alias -L", "cmd": ""} 8478 8479 8480@lazyobject 8481def DEFAULT_FUNCSCMDS(): 8482 return {"bash": DEFAULT_BASH_FUNCSCMD, "zsh": DEFAULT_ZSH_FUNCSCMD, "cmd": ""} 8483 8484 8485@lazyobject 8486def DEFAULT_SOURCERS(): 8487 return {"bash": "source", "zsh": "source", "cmd": "call"} 8488 8489 8490@lazyobject 8491def DEFAULT_TMPFILE_EXT(): 8492 return {"bash": ".sh", "zsh": ".zsh", "cmd": ".bat"} 8493 8494 8495@lazyobject 8496def DEFAULT_RUNCMD(): 8497 return {"bash": "-c", "zsh": "-c", "cmd": "/C"} 8498 8499 8500@lazyobject 8501def DEFAULT_SETERRPREVCMD(): 8502 return {"bash": "set -e", "zsh": "set -e", "cmd": "@echo off"} 8503 8504 8505@lazyobject 8506def DEFAULT_SETERRPOSTCMD(): 8507 return {"bash": "", "zsh": "", "cmd": "if errorlevel 1 exit 1"} 8508 8509 8510@functools.lru_cache() 8511def foreign_shell_data( 8512 shell, 8513 interactive=True, 8514 login=False, 8515 envcmd=None, 8516 aliascmd=None, 8517 extra_args=(), 8518 currenv=None, 8519 safe=True, 8520 prevcmd="", 8521 postcmd="", 8522 funcscmd=None, 8523 sourcer=None, 8524 use_tmpfile=False, 8525 tmpfile_ext=None, 8526 runcmd=None, 8527 seterrprevcmd=None, 8528 seterrpostcmd=None, 8529 show=False, 8530 dryrun=False, 8531): 8532 """Extracts data from a foreign (non-xonsh) shells. Currently this gets 8533 the environment, aliases, and functions but may be extended in the future. 8534 8535 Parameters 8536 ---------- 8537 shell : str 8538 The name of the shell, such as 'bash' or '/bin/sh'. 8539 interactive : bool, optional 8540 Whether the shell should be run in interactive mode. 8541 login : bool, optional 8542 Whether the shell should be a login shell. 8543 envcmd : str or None, optional 8544 The command to generate environment output with. 8545 aliascmd : str or None, optional 8546 The command to generate alias output with. 8547 extra_args : tuple of str, optional 8548 Additional command line options to pass into the shell. 8549 currenv : tuple of items or None, optional 8550 Manual override for the current environment. 8551 safe : bool, optional 8552 Flag for whether or not to safely handle exceptions and other errors. 8553 prevcmd : str, optional 8554 A command to run in the shell before anything else, useful for 8555 sourcing and other commands that may require environment recovery. 8556 postcmd : str, optional 8557 A command to run after everything else, useful for cleaning up any 8558 damage that the prevcmd may have caused. 8559 funcscmd : str or None, optional 8560 This is a command or script that can be used to determine the names 8561 and locations of any functions that are native to the foreign shell. 8562 This command should print *only* a JSON object that maps 8563 function names to the filenames where the functions are defined. 8564 If this is None, then a default script will attempted to be looked 8565 up based on the shell name. Callable wrappers for these functions 8566 will be returned in the aliases dictionary. 8567 sourcer : str or None, optional 8568 How to source a foreign shell file for purposes of calling functions 8569 in that shell. If this is None, a default value will attempt to be 8570 looked up based on the shell name. 8571 use_tmpfile : bool, optional 8572 This specifies if the commands are written to a tmp file or just 8573 parsed directly to the shell 8574 tmpfile_ext : str or None, optional 8575 If tmpfile is True this sets specifies the extension used. 8576 runcmd : str or None, optional 8577 Command line switches to use when running the script, such as 8578 -c for Bash and /C for cmd.exe. 8579 seterrprevcmd : str or None, optional 8580 Command that enables exit-on-error for the shell that is run at the 8581 start of the script. For example, this is "set -e" in Bash. To disable 8582 exit-on-error behavior, simply pass in an empty string. 8583 seterrpostcmd : str or None, optional 8584 Command that enables exit-on-error for the shell that is run at the end 8585 of the script. For example, this is "if errorlevel 1 exit 1" in 8586 cmd.exe. To disable exit-on-error behavior, simply pass in an 8587 empty string. 8588 show : bool, optional 8589 Whether or not to display the script that will be run. 8590 dryrun : bool, optional 8591 Whether or not to actually run and process the command. 8592 8593 8594 Returns 8595 ------- 8596 env : dict 8597 Dictionary of shell's environment. (None if the subproc command fails) 8598 aliases : dict 8599 Dictionary of shell's aliases, this includes foreign function 8600 wrappers.(None if the subproc command fails) 8601 """ 8602 cmd = [shell] 8603 cmd.extend(extra_args) # needs to come here for GNU long options 8604 if interactive: 8605 cmd.append("-i") 8606 if login: 8607 cmd.append("-l") 8608 shkey = CANON_SHELL_NAMES[shell] 8609 envcmd = DEFAULT_ENVCMDS.get(shkey, "env") if envcmd is None else envcmd 8610 aliascmd = DEFAULT_ALIASCMDS.get(shkey, "alias") if aliascmd is None else aliascmd 8611 funcscmd = DEFAULT_FUNCSCMDS.get(shkey, "echo {}") if funcscmd is None else funcscmd 8612 tmpfile_ext = ( 8613 DEFAULT_TMPFILE_EXT.get(shkey, "sh") if tmpfile_ext is None else tmpfile_ext 8614 ) 8615 runcmd = DEFAULT_RUNCMD.get(shkey, "-c") if runcmd is None else runcmd 8616 seterrprevcmd = ( 8617 DEFAULT_SETERRPREVCMD.get(shkey, "") if seterrprevcmd is None else seterrprevcmd 8618 ) 8619 seterrpostcmd = ( 8620 DEFAULT_SETERRPOSTCMD.get(shkey, "") if seterrpostcmd is None else seterrpostcmd 8621 ) 8622 command = COMMAND.format( 8623 envcmd=envcmd, 8624 aliascmd=aliascmd, 8625 prevcmd=prevcmd, 8626 postcmd=postcmd, 8627 funcscmd=funcscmd, 8628 seterrprevcmd=seterrprevcmd, 8629 seterrpostcmd=seterrpostcmd, 8630 ).strip() 8631 if show: 8632 print(command) 8633 if dryrun: 8634 return None, None 8635 cmd.append(runcmd) 8636 if not use_tmpfile: 8637 cmd.append(command) 8638 else: 8639 tmpfile = tempfile.NamedTemporaryFile(suffix=tmpfile_ext, delete=False) 8640 tmpfile.write(command.encode("utf8")) 8641 tmpfile.close() 8642 cmd.append(tmpfile.name) 8643 if currenv is None and hasattr(builtins, "__xonsh_env__"): 8644 currenv = builtins.__xonsh_env__.detype() 8645 elif currenv is not None: 8646 currenv = dict(currenv) 8647 try: 8648 s = subprocess.check_output( 8649 cmd, 8650 stderr=subprocess.PIPE, 8651 env=currenv, 8652 # start new session to avoid hangs 8653 # (doesn't work on Cygwin though) 8654 start_new_session=((not ON_CYGWIN) and (not ON_MSYS)), 8655 universal_newlines=True, 8656 ) 8657 except (subprocess.CalledProcessError, FileNotFoundError): 8658 if not safe: 8659 raise 8660 return None, None 8661 finally: 8662 if use_tmpfile: 8663 os.remove(tmpfile.name) 8664 env = parse_env(s) 8665 aliases = parse_aliases(s) 8666 funcs = parse_funcs(s, shell=shell, sourcer=sourcer, extra_args=extra_args) 8667 aliases.update(funcs) 8668 return env, aliases 8669 8670 8671@lazyobject 8672def ENV_RE(): 8673 return re.compile("__XONSH_ENV_BEG__\n(.*)" "__XONSH_ENV_END__", flags=re.DOTALL) 8674 8675 8676@lazyobject 8677def ENV_SPLIT_RE(): 8678 return re.compile("^([^=]+)=([^=]*|[^\n]*)$", flags=re.DOTALL | re.MULTILINE) 8679 8680 8681def parse_env(s): 8682 """Parses the environment portion of string into a dict.""" 8683 m = ENV_RE.search(s) 8684 if m is None: 8685 return {} 8686 g1 = m.group(1) 8687 g1 = g1[:-1] if g1.endswith("\n") else g1 8688 env = dict(ENV_SPLIT_RE.findall(g1)) 8689 return env 8690 8691 8692@lazyobject 8693def ALIAS_RE(): 8694 return re.compile( 8695 "__XONSH_ALIAS_BEG__\n(.*)" "__XONSH_ALIAS_END__", flags=re.DOTALL 8696 ) 8697 8698 8699def parse_aliases(s): 8700 """Parses the aliases portion of string into a dict.""" 8701 m = ALIAS_RE.search(s) 8702 if m is None: 8703 return {} 8704 g1 = m.group(1) 8705 items = [ 8706 line.split("=", 1) 8707 for line in g1.splitlines() 8708 if line.startswith("alias ") and "=" in line 8709 ] 8710 aliases = {} 8711 for key, value in items: 8712 try: 8713 key = key[6:] # lstrip 'alias ' 8714 # undo bash's weird quoting of single quotes (sh_single_quote) 8715 value = value.replace("'\\''", "'") 8716 # strip one single quote at the start and end of value 8717 if value[0] == "'" and value[-1] == "'": 8718 value = value[1:-1] 8719 value = shlex.split(value) 8720 except ValueError as exc: 8721 warnings.warn( 8722 'could not parse alias "{0}": {1!r}'.format(key, exc), RuntimeWarning 8723 ) 8724 continue 8725 aliases[key] = value 8726 return aliases 8727 8728 8729@lazyobject 8730def FUNCS_RE(): 8731 return re.compile( 8732 "__XONSH_FUNCS_BEG__\n(.+)\n" "__XONSH_FUNCS_END__", flags=re.DOTALL 8733 ) 8734 8735 8736def parse_funcs(s, shell, sourcer=None, extra_args=()): 8737 """Parses the funcs portion of a string into a dict of callable foreign 8738 function wrappers. 8739 """ 8740 m = FUNCS_RE.search(s) 8741 if m is None: 8742 return {} 8743 g1 = m.group(1) 8744 if ON_WINDOWS: 8745 g1 = g1.replace(os.sep, os.altsep) 8746 try: 8747 namefiles = json.loads(g1.strip()) 8748 except json.decoder.JSONDecodeError as exc: 8749 msg = ( 8750 "{0!r}\n\ncould not parse {1} functions:\n" 8751 " s = {2!r}\n" 8752 " g1 = {3!r}\n\n" 8753 "Note: you may be seeing this error if you use zsh with " 8754 "prezto. Prezto overwrites GNU coreutils functions (like echo) " 8755 "with its own zsh functions. Please try disabling prezto." 8756 ) 8757 warnings.warn(msg.format(exc, shell, s, g1), RuntimeWarning) 8758 return {} 8759 sourcer = DEFAULT_SOURCERS.get(shell, "source") if sourcer is None else sourcer 8760 funcs = {} 8761 for funcname, filename in namefiles.items(): 8762 if funcname.startswith("_") or not filename: 8763 continue # skip private functions and invalid files 8764 if not os.path.isabs(filename): 8765 filename = os.path.abspath(filename) 8766 wrapper = ForeignShellFunctionAlias( 8767 name=funcname, 8768 shell=shell, 8769 sourcer=sourcer, 8770 filename=filename, 8771 extra_args=extra_args, 8772 ) 8773 funcs[funcname] = wrapper 8774 return funcs 8775 8776 8777class ForeignShellFunctionAlias(object): 8778 """This class is responsible for calling foreign shell functions as if 8779 they were aliases. This does not currently support taking stdin. 8780 """ 8781 8782 INPUT = '{sourcer} "{filename}"\n' "{funcname} {args}\n" 8783 8784 def __init__(self, name, shell, filename, sourcer=None, extra_args=()): 8785 """ 8786 Parameters 8787 ---------- 8788 name : str 8789 function name 8790 shell : str 8791 Name or path to shell 8792 filename : str 8793 Where the function is defined, path to source. 8794 sourcer : str or None, optional 8795 Command to source foreign files with. 8796 extra_args : tuple of str, optional 8797 Additional command line options to pass into the shell. 8798 """ 8799 sourcer = DEFAULT_SOURCERS.get(shell, "source") if sourcer is None else sourcer 8800 self.name = name 8801 self.shell = shell 8802 self.filename = filename 8803 self.sourcer = sourcer 8804 self.extra_args = extra_args 8805 8806 def __eq__(self, other): 8807 if ( 8808 not hasattr(other, "name") 8809 or not hasattr(other, "shell") 8810 or not hasattr(other, "filename") 8811 or not hasattr(other, "sourcer") 8812 or not hasattr(other, "exta_args") 8813 ): 8814 return NotImplemented 8815 return ( 8816 (self.name == other.name) 8817 and (self.shell == other.shell) 8818 and (self.filename == other.filename) 8819 and (self.sourcer == other.sourcer) 8820 and (self.extra_args == other.extra_args) 8821 ) 8822 8823 def __call__(self, args, stdin=None): 8824 args, streaming = self._is_streaming(args) 8825 input = self.INPUT.format( 8826 sourcer=self.sourcer, 8827 filename=self.filename, 8828 funcname=self.name, 8829 args=" ".join(args), 8830 ) 8831 cmd = [self.shell] + list(self.extra_args) + ["-c", input] 8832 env = builtins.__xonsh_env__ 8833 denv = env.detype() 8834 if streaming: 8835 subprocess.check_call(cmd, env=denv) 8836 out = None 8837 else: 8838 out = subprocess.check_output(cmd, env=denv, stderr=subprocess.STDOUT) 8839 out = out.decode( 8840 encoding=env.get("XONSH_ENCODING"), 8841 errors=env.get("XONSH_ENCODING_ERRORS"), 8842 ) 8843 out = out.replace("\r\n", "\n") 8844 return out 8845 8846 def _is_streaming(self, args): 8847 """Test and modify args if --xonsh-stream is present.""" 8848 if "--xonsh-stream" not in args: 8849 return args, False 8850 args = list(args) 8851 args.remove("--xonsh-stream") 8852 return args, True 8853 8854 8855@lazyobject 8856def VALID_SHELL_PARAMS(): 8857 return frozenset( 8858 [ 8859 "shell", 8860 "interactive", 8861 "login", 8862 "envcmd", 8863 "aliascmd", 8864 "extra_args", 8865 "currenv", 8866 "safe", 8867 "prevcmd", 8868 "postcmd", 8869 "funcscmd", 8870 "sourcer", 8871 ] 8872 ) 8873 8874 8875def ensure_shell(shell): 8876 """Ensures that a mapping follows the shell specification.""" 8877 if not isinstance(shell, cabc.MutableMapping): 8878 shell = dict(shell) 8879 shell_keys = set(shell.keys()) 8880 if not (shell_keys <= VALID_SHELL_PARAMS): 8881 msg = "unknown shell keys: {0}" 8882 raise KeyError(msg.format(shell_keys - VALID_SHELL_PARAMS)) 8883 shell["shell"] = ensure_string(shell["shell"]).lower() 8884 if "interactive" in shell_keys: 8885 shell["interactive"] = to_bool(shell["interactive"]) 8886 if "login" in shell_keys: 8887 shell["login"] = to_bool(shell["login"]) 8888 if "envcmd" in shell_keys: 8889 shell["envcmd"] = ( 8890 None if shell["envcmd"] is None else ensure_string(shell["envcmd"]) 8891 ) 8892 if "aliascmd" in shell_keys: 8893 shell["aliascmd"] = ( 8894 None if shell["aliascmd"] is None else ensure_string(shell["aliascmd"]) 8895 ) 8896 if "extra_args" in shell_keys and not isinstance(shell["extra_args"], tuple): 8897 shell["extra_args"] = tuple(map(ensure_string, shell["extra_args"])) 8898 if "currenv" in shell_keys and not isinstance(shell["currenv"], tuple): 8899 ce = shell["currenv"] 8900 if isinstance(ce, cabc.Mapping): 8901 ce = tuple([(ensure_string(k), v) for k, v in ce.items()]) 8902 elif isinstance(ce, cabc.Sequence): 8903 ce = tuple([(ensure_string(k), v) for k, v in ce]) 8904 else: 8905 raise RuntimeError("unrecognized type for currenv") 8906 shell["currenv"] = ce 8907 if "safe" in shell_keys: 8908 shell["safe"] = to_bool(shell["safe"]) 8909 if "prevcmd" in shell_keys: 8910 shell["prevcmd"] = ensure_string(shell["prevcmd"]) 8911 if "postcmd" in shell_keys: 8912 shell["postcmd"] = ensure_string(shell["postcmd"]) 8913 if "funcscmd" in shell_keys: 8914 shell["funcscmd"] = ( 8915 None if shell["funcscmd"] is None else ensure_string(shell["funcscmd"]) 8916 ) 8917 if "sourcer" in shell_keys: 8918 shell["sourcer"] = ( 8919 None if shell["sourcer"] is None else ensure_string(shell["sourcer"]) 8920 ) 8921 if "seterrprevcmd" in shell_keys: 8922 shell["seterrprevcmd"] = ( 8923 None 8924 if shell["seterrprevcmd"] is None 8925 else ensure_string(shell["seterrprevcmd"]) 8926 ) 8927 if "seterrpostcmd" in shell_keys: 8928 shell["seterrpostcmd"] = ( 8929 None 8930 if shell["seterrpostcmd"] is None 8931 else ensure_string(shell["seterrpostcmd"]) 8932 ) 8933 return shell 8934 8935 8936def load_foreign_envs(shells): 8937 """Loads environments from foreign shells. 8938 8939 Parameters 8940 ---------- 8941 shells : sequence of dicts 8942 An iterable of dicts that can be passed into foreign_shell_data() as 8943 keyword arguments. 8944 8945 Returns 8946 ------- 8947 env : dict 8948 A dictionary of the merged environments. 8949 """ 8950 env = {} 8951 for shell in shells: 8952 shell = ensure_shell(shell) 8953 shenv, _ = foreign_shell_data(**shell) 8954 if shenv: 8955 env.update(shenv) 8956 return env 8957 8958 8959def load_foreign_aliases(shells): 8960 """Loads aliases from foreign shells. 8961 8962 Parameters 8963 ---------- 8964 shells : sequence of dicts 8965 An iterable of dicts that can be passed into foreign_shell_data() as 8966 keyword arguments. 8967 8968 Returns 8969 ------- 8970 aliases : dict 8971 A dictionary of the merged aliases. 8972 """ 8973 aliases = {} 8974 xonsh_aliases = builtins.aliases 8975 for shell in shells: 8976 shell = ensure_shell(shell) 8977 _, shaliases = foreign_shell_data(**shell) 8978 if not builtins.__xonsh_env__.get("FOREIGN_ALIASES_OVERRIDE"): 8979 shaliases = {} if shaliases is None else shaliases 8980 for alias in set(shaliases) & set(xonsh_aliases): 8981 del shaliases[alias] 8982 if builtins.__xonsh_env__.get("XONSH_DEBUG") > 1: 8983 print( 8984 "aliases: ignoring alias {!r} of shell {!r} " 8985 "which tries to override xonsh alias." 8986 "".format(alias, shell["shell"]), 8987 file=sys.stderr, 8988 ) 8989 aliases.update(shaliases) 8990 return aliases 8991 8992# 8993# jobs 8994# 8995# -*- coding: utf-8 -*- 8996"""Job control for the xonsh shell.""" 8997# amalgamated os 8998# amalgamated sys 8999# amalgamated time 9000# amalgamated ctypes 9001# amalgamated signal 9002# amalgamated builtins 9003# amalgamated subprocess 9004# amalgamated collections 9005# amalgamated xonsh.lazyasd 9006# amalgamated xonsh.platform 9007# amalgamated xonsh.tools 9008tasks = LazyObject(collections.deque, globals(), "tasks") 9009# Track time stamp of last exit command, so that two consecutive attempts to 9010# exit can kill all jobs and exit. 9011_last_exit_time = None 9012 9013 9014if ON_DARWIN: 9015 9016 def _send_signal(job, signal): 9017 # On OS X, os.killpg() may cause PermissionError when there are 9018 # any zombie processes in the process group. 9019 # See github issue #1012 for details 9020 for pid in job["pids"]: 9021 if pid is None: # the pid of an aliased proc is None 9022 continue 9023 try: 9024 os.kill(pid, signal) 9025 except ProcessLookupError: 9026 pass 9027 9028 9029elif ON_WINDOWS: 9030 pass 9031elif ON_CYGWIN or ON_MSYS: 9032 # Similar to what happened on OSX, more issues on Cygwin 9033 # (see Github issue #514). 9034 def _send_signal(job, signal): 9035 try: 9036 os.killpg(job["pgrp"], signal) 9037 except Exception: 9038 for pid in job["pids"]: 9039 try: 9040 os.kill(pid, signal) 9041 except Exception: 9042 pass 9043 9044 9045else: 9046 9047 def _send_signal(job, signal): 9048 pgrp = job["pgrp"] 9049 if pgrp is None: 9050 for pid in job["pids"]: 9051 try: 9052 os.kill(pid, signal) 9053 except Exception: 9054 pass 9055 else: 9056 os.killpg(job["pgrp"], signal) 9057 9058 9059if ON_WINDOWS: 9060 9061 def _continue(job): 9062 job["status"] = "running" 9063 9064 def _kill(job): 9065 subprocess.check_output(["taskkill", "/F", "/T", "/PID", str(job["obj"].pid)]) 9066 9067 def ignore_sigtstp(): 9068 pass 9069 9070 def give_terminal_to(pgid): 9071 pass 9072 9073 def wait_for_active_job(last_task=None, backgrounded=False): 9074 """ 9075 Wait for the active job to finish, to be killed by SIGINT, or to be 9076 suspended by ctrl-z. 9077 """ 9078 _clear_dead_jobs() 9079 active_task = get_next_task() 9080 # Return when there are no foreground active task 9081 if active_task is None: 9082 return last_task 9083 obj = active_task["obj"] 9084 _continue(active_task) 9085 while obj.returncode is None: 9086 try: 9087 obj.wait(0.01) 9088 except subprocess.TimeoutExpired: 9089 pass 9090 except KeyboardInterrupt: 9091 _kill(active_task) 9092 return wait_for_active_job(last_task=active_task) 9093 9094 9095else: 9096 9097 def _continue(job): 9098 _send_signal(job, signal.SIGCONT) 9099 9100 def _kill(job): 9101 _send_signal(job, signal.SIGKILL) 9102 9103 def ignore_sigtstp(): 9104 signal.signal(signal.SIGTSTP, signal.SIG_IGN) 9105 9106 _shell_pgrp = os.getpgrp() 9107 9108 _block_when_giving = LazyObject( 9109 lambda: (signal.SIGTTOU, signal.SIGTTIN, signal.SIGTSTP, signal.SIGCHLD), 9110 globals(), 9111 "_block_when_giving", 9112 ) 9113 9114 if ON_CYGWIN or ON_MSYS: 9115 # on cygwin, signal.pthread_sigmask does not exist in Python, even 9116 # though pthread_sigmask is defined in the kernel. thus, we use 9117 # ctypes to mimic the calls in the "normal" version below. 9118 LIBC.pthread_sigmask.restype = ctypes.c_int 9119 LIBC.pthread_sigmask.argtypes = [ 9120 ctypes.c_int, 9121 ctypes.POINTER(ctypes.c_ulong), 9122 ctypes.POINTER(ctypes.c_ulong), 9123 ] 9124 9125 def _pthread_sigmask(how, signals): 9126 mask = 0 9127 for sig in signals: 9128 mask |= 1 << sig 9129 oldmask = ctypes.c_ulong() 9130 mask = ctypes.c_ulong(mask) 9131 result = LIBC.pthread_sigmask( 9132 how, ctypes.byref(mask), ctypes.byref(oldmask) 9133 ) 9134 if result: 9135 raise OSError(result, "Sigmask error.") 9136 9137 return { 9138 sig 9139 for sig in getattr(signal, "Signals", range(0, 65)) 9140 if (oldmask.value >> sig) & 1 9141 } 9142 9143 else: 9144 _pthread_sigmask = signal.pthread_sigmask 9145 9146 # give_terminal_to is a simplified version of: 9147 # give_terminal_to from bash 4.3 source, jobs.c, line 4030 9148 # this will give the terminal to the process group pgid 9149 def give_terminal_to(pgid): 9150 if pgid is None: 9151 return False 9152 oldmask = _pthread_sigmask(signal.SIG_BLOCK, _block_when_giving) 9153 try: 9154 os.tcsetpgrp(FD_STDERR, pgid) 9155 return True 9156 except ProcessLookupError: 9157 # when the process finished before giving terminal to it, 9158 # see issue #2288 9159 return False 9160 except OSError as e: 9161 if e.errno == 22: # [Errno 22] Invalid argument 9162 # there are cases that all the processes of pgid have 9163 # finished, then we don't need to do anything here, see 9164 # issue #2220 9165 return False 9166 elif e.errno == 25: # [Errno 25] Inappropriate ioctl for device 9167 # There are also cases where we are not connected to a 9168 # real TTY, even though we may be run in interactive 9169 # mode. See issue #2267 for an example with emacs 9170 return False 9171 else: 9172 raise 9173 finally: 9174 _pthread_sigmask(signal.SIG_SETMASK, oldmask) 9175 9176 def wait_for_active_job(last_task=None, backgrounded=False): 9177 """ 9178 Wait for the active job to finish, to be killed by SIGINT, or to be 9179 suspended by ctrl-z. 9180 """ 9181 _clear_dead_jobs() 9182 active_task = get_next_task() 9183 # Return when there are no foreground active task 9184 if active_task is None: 9185 return last_task 9186 obj = active_task["obj"] 9187 backgrounded = False 9188 try: 9189 _, wcode = os.waitpid(obj.pid, os.WUNTRACED) 9190 except ChildProcessError: # No child processes 9191 return wait_for_active_job(last_task=active_task, backgrounded=backgrounded) 9192 if os.WIFSTOPPED(wcode): 9193 print("^Z") 9194 active_task["status"] = "stopped" 9195 backgrounded = True 9196 elif os.WIFSIGNALED(wcode): 9197 print() # get a newline because ^C will have been printed 9198 obj.signal = (os.WTERMSIG(wcode), os.WCOREDUMP(wcode)) 9199 obj.returncode = None 9200 else: 9201 obj.returncode = os.WEXITSTATUS(wcode) 9202 obj.signal = None 9203 return wait_for_active_job(last_task=active_task, backgrounded=backgrounded) 9204 9205 9206def get_next_task(): 9207 """ Get the next active task and put it on top of the queue""" 9208 selected_task = None 9209 for tid in tasks: 9210 task = get_task(tid) 9211 if not task["bg"] and task["status"] == "running": 9212 selected_task = tid 9213 break 9214 if selected_task is None: 9215 return 9216 tasks.remove(selected_task) 9217 tasks.appendleft(selected_task) 9218 return get_task(selected_task) 9219 9220 9221def get_task(tid): 9222 return builtins.__xonsh_all_jobs__[tid] 9223 9224 9225def _clear_dead_jobs(): 9226 to_remove = set() 9227 for tid in tasks: 9228 obj = get_task(tid)["obj"] 9229 if obj is None or obj.poll() is not None: 9230 to_remove.add(tid) 9231 for job in to_remove: 9232 tasks.remove(job) 9233 del builtins.__xonsh_all_jobs__[job] 9234 9235 9236def print_one_job(num, outfile=sys.stdout): 9237 """Print a line describing job number ``num``.""" 9238 try: 9239 job = builtins.__xonsh_all_jobs__[num] 9240 except KeyError: 9241 return 9242 pos = "+" if tasks[0] == num else "-" if tasks[1] == num else " " 9243 status = job["status"] 9244 cmd = [" ".join(i) if isinstance(i, list) else i for i in job["cmds"]] 9245 cmd = " ".join(cmd) 9246 pid = job["pids"][-1] 9247 bg = " &" if job["bg"] else "" 9248 print("[{}]{} {}: {}{} ({})".format(num, pos, status, cmd, bg, pid), file=outfile) 9249 9250 9251def get_next_job_number(): 9252 """Get the lowest available unique job number (for the next job created). 9253 """ 9254 _clear_dead_jobs() 9255 i = 1 9256 while i in builtins.__xonsh_all_jobs__: 9257 i += 1 9258 return i 9259 9260 9261def add_job(info): 9262 """Add a new job to the jobs dictionary.""" 9263 num = get_next_job_number() 9264 info["started"] = time.time() 9265 info["status"] = "running" 9266 tasks.appendleft(num) 9267 builtins.__xonsh_all_jobs__[num] = info 9268 if info["bg"] and builtins.__xonsh_env__.get("XONSH_INTERACTIVE"): 9269 print_one_job(num) 9270 9271 9272def clean_jobs(): 9273 """Clean up jobs for exiting shell 9274 9275 In non-interactive mode, kill all jobs. 9276 9277 In interactive mode, check for suspended or background jobs, print a 9278 warning if any exist, and return False. Otherwise, return True. 9279 """ 9280 jobs_clean = True 9281 if builtins.__xonsh_env__["XONSH_INTERACTIVE"]: 9282 _clear_dead_jobs() 9283 9284 if builtins.__xonsh_all_jobs__: 9285 global _last_exit_time 9286 hist = builtins.__xonsh_history__ 9287 if hist is not None and len(hist.tss) > 0: 9288 last_cmd_start = hist.tss[-1][0] 9289 else: 9290 last_cmd_start = None 9291 9292 if _last_exit_time and last_cmd_start and _last_exit_time > last_cmd_start: 9293 # Exit occurred after last command started, so it was called as 9294 # part of the last command and is now being called again 9295 # immediately. Kill jobs and exit without reminder about 9296 # unfinished jobs in this case. 9297 kill_all_jobs() 9298 else: 9299 if len(builtins.__xonsh_all_jobs__) > 1: 9300 msg = "there are unfinished jobs" 9301 else: 9302 msg = "there is an unfinished job" 9303 9304 if "prompt_toolkit" not in builtins.__xonsh_env__["SHELL_TYPE"]: 9305 # The Ctrl+D binding for prompt_toolkit already inserts a 9306 # newline 9307 print() 9308 print("xonsh: {}".format(msg), file=sys.stderr) 9309 print("-" * 5, file=sys.stderr) 9310 jobs([], stdout=sys.stderr) 9311 print("-" * 5, file=sys.stderr) 9312 print( 9313 'Type "exit" or press "ctrl-d" again to force quit.', 9314 file=sys.stderr, 9315 ) 9316 jobs_clean = False 9317 _last_exit_time = time.time() 9318 else: 9319 kill_all_jobs() 9320 9321 return jobs_clean 9322 9323 9324def kill_all_jobs(): 9325 """ 9326 Send SIGKILL to all child processes (called when exiting xonsh). 9327 """ 9328 _clear_dead_jobs() 9329 for job in builtins.__xonsh_all_jobs__.values(): 9330 _kill(job) 9331 9332 9333def jobs(args, stdin=None, stdout=sys.stdout, stderr=None): 9334 """ 9335 xonsh command: jobs 9336 9337 Display a list of all current jobs. 9338 """ 9339 _clear_dead_jobs() 9340 for j in tasks: 9341 print_one_job(j, outfile=stdout) 9342 return None, None 9343 9344 9345@unthreadable 9346def fg(args, stdin=None): 9347 """ 9348 xonsh command: fg 9349 9350 Bring the currently active job to the foreground, or, if a single number is 9351 given as an argument, bring that job to the foreground. Additionally, 9352 specify "+" for the most recent job and "-" for the second most recent job. 9353 """ 9354 _clear_dead_jobs() 9355 if len(tasks) == 0: 9356 return "", "Cannot bring nonexistent job to foreground.\n" 9357 9358 if len(args) == 0: 9359 tid = tasks[0] # take the last manipulated task by default 9360 elif len(args) == 1: 9361 try: 9362 if args[0] == "+": # take the last manipulated task 9363 tid = tasks[0] 9364 elif args[0] == "-": # take the second to last manipulated task 9365 tid = tasks[1] 9366 else: 9367 tid = int(args[0]) 9368 except (ValueError, IndexError): 9369 return "", "Invalid job: {}\n".format(args[0]) 9370 9371 if tid not in builtins.__xonsh_all_jobs__: 9372 return "", "Invalid job: {}\n".format(args[0]) 9373 else: 9374 return "", "fg expects 0 or 1 arguments, not {}\n".format(len(args)) 9375 9376 # Put this one on top of the queue 9377 tasks.remove(tid) 9378 tasks.appendleft(tid) 9379 9380 job = get_task(tid) 9381 job["bg"] = False 9382 job["status"] = "running" 9383 if builtins.__xonsh_env__.get("XONSH_INTERACTIVE"): 9384 print_one_job(tid) 9385 pipeline = job["pipeline"] 9386 pipeline.resume(job) 9387 9388 9389def bg(args, stdin=None): 9390 """xonsh command: bg 9391 9392 Resume execution of the currently active job in the background, or, if a 9393 single number is given as an argument, resume that job in the background. 9394 """ 9395 res = fg(args, stdin) 9396 if res is None: 9397 curtask = get_task(tasks[0]) 9398 curtask["bg"] = True 9399 _continue(curtask) 9400 else: 9401 return res 9402 9403# 9404# jsonutils 9405# 9406"""Custom tools for managing JSON serialization / deserialization of xonsh 9407objects. 9408""" 9409# amalgamated functools 9410# amalgamated xonsh.tools 9411@functools.singledispatch 9412def serialize_xonsh_json(val): 9413 """JSON serializer for xonsh custom data structures. This is only 9414 called when another normal JSON types are not found. 9415 """ 9416 return str(val) 9417 9418 9419@serialize_xonsh_json.register(EnvPath) 9420def _serialize_xonsh_json_env_path(val): 9421 return val.paths 9422 9423# 9424# lexer 9425# 9426# -*- coding: utf-8 -*- 9427"""Lexer for xonsh code. 9428 9429Written using a hybrid of ``tokenize`` and PLY. 9430""" 9431# amalgamated io 9432kwmod = _LazyModule.load('keyword', 'keyword', 'kwmod') 9433try: 9434 from ply.lex import LexToken 9435except ImportError: 9436 from xonsh.ply.ply.lex import LexToken 9437 9438# amalgamated xonsh.lazyasd 9439# amalgamated xonsh.platform 9440# amalgamated xonsh.tokenize 9441@lazyobject 9442def token_map(): 9443 """Mapping from ``tokenize`` tokens (or token types) to PLY token types. If 9444 a simple one-to-one mapping from ``tokenize`` to PLY exists, the lexer will 9445 look it up here and generate a single PLY token of the given type. 9446 Otherwise, it will fall back to handling that token using one of the 9447 handlers in``special_handlers``. 9448 """ 9449 tm = {} 9450 # operators 9451 _op_map = { 9452 # punctuation 9453 ",": "COMMA", 9454 ".": "PERIOD", 9455 ";": "SEMI", 9456 ":": "COLON", 9457 "...": "ELLIPSIS", 9458 # basic operators 9459 "+": "PLUS", 9460 "-": "MINUS", 9461 "*": "TIMES", 9462 "@": "AT", 9463 "/": "DIVIDE", 9464 "//": "DOUBLEDIV", 9465 "%": "MOD", 9466 "**": "POW", 9467 "|": "PIPE", 9468 "~": "TILDE", 9469 "^": "XOR", 9470 "<<": "LSHIFT", 9471 ">>": "RSHIFT", 9472 "<": "LT", 9473 "<=": "LE", 9474 ">": "GT", 9475 ">=": "GE", 9476 "==": "EQ", 9477 "!=": "NE", 9478 "->": "RARROW", 9479 # assignment operators 9480 "=": "EQUALS", 9481 "+=": "PLUSEQUAL", 9482 "-=": "MINUSEQUAL", 9483 "*=": "TIMESEQUAL", 9484 "@=": "ATEQUAL", 9485 "/=": "DIVEQUAL", 9486 "%=": "MODEQUAL", 9487 "**=": "POWEQUAL", 9488 "<<=": "LSHIFTEQUAL", 9489 ">>=": "RSHIFTEQUAL", 9490 "&=": "AMPERSANDEQUAL", 9491 "^=": "XOREQUAL", 9492 "|=": "PIPEEQUAL", 9493 "//=": "DOUBLEDIVEQUAL", 9494 # extra xonsh operators 9495 "?": "QUESTION", 9496 "??": "DOUBLE_QUESTION", 9497 "@$": "ATDOLLAR", 9498 "&": "AMPERSAND", 9499 } 9500 for (op, typ) in _op_map.items(): 9501 tm[(OP, op)] = typ 9502 tm[IOREDIRECT] = "IOREDIRECT" 9503 tm[STRING] = "STRING" 9504 tm[DOLLARNAME] = "DOLLAR_NAME" 9505 tm[NUMBER] = "NUMBER" 9506 tm[SEARCHPATH] = "SEARCHPATH" 9507 tm[NEWLINE] = "NEWLINE" 9508 tm[INDENT] = "INDENT" 9509 tm[DEDENT] = "DEDENT" 9510 if (3, 5, 0) <= PYTHON_VERSION_INFO < (3, 7, 0): 9511 from xonsh.tokenize import ASYNC, AWAIT 9512 9513 tm[ASYNC] = "ASYNC" 9514 tm[AWAIT] = "AWAIT" 9515 return tm 9516 9517 9518def handle_name(state, token): 9519 """Function for handling name tokens""" 9520 typ = "NAME" 9521 if state["pymode"][-1][0]: 9522 if token.string in kwmod.kwlist: 9523 typ = token.string.upper() 9524 state["last"] = token 9525 yield _new_token(typ, token.string, token.start) 9526 else: 9527 prev = state["last"] 9528 state["last"] = token 9529 has_whitespace = prev.end != token.start 9530 if token.string == "and" and has_whitespace: 9531 yield _new_token("AND", token.string, token.start) 9532 elif token.string == "or" and has_whitespace: 9533 yield _new_token("OR", token.string, token.start) 9534 else: 9535 yield _new_token("NAME", token.string, token.start) 9536 9537 9538def _end_delimiter(state, token): 9539 py = state["pymode"] 9540 s = token.string 9541 l, c = token.start 9542 if len(py) > 1: 9543 mode, orig, match, pos = py.pop() 9544 if s != match: 9545 e = '"{}" at {} ends "{}" at {} (expected "{}")' 9546 return e.format(s, (l, c), orig, pos, match) 9547 else: 9548 return 'Unmatched "{}" at line {}, column {}'.format(s, l, c) 9549 9550 9551def handle_rparen(state, token): 9552 """ 9553 Function for handling ``)`` 9554 """ 9555 e = _end_delimiter(state, token) 9556 if e is None: 9557 state["last"] = token 9558 yield _new_token("RPAREN", ")", token.start) 9559 else: 9560 yield _new_token("ERRORTOKEN", e, token.start) 9561 9562 9563def handle_rbrace(state, token): 9564 """Function for handling ``}``""" 9565 e = _end_delimiter(state, token) 9566 if e is None: 9567 state["last"] = token 9568 yield _new_token("RBRACE", "}", token.start) 9569 else: 9570 yield _new_token("ERRORTOKEN", e, token.start) 9571 9572 9573def handle_rbracket(state, token): 9574 """ 9575 Function for handling ``]`` 9576 """ 9577 e = _end_delimiter(state, token) 9578 if e is None: 9579 state["last"] = token 9580 yield _new_token("RBRACKET", "]", token.start) 9581 else: 9582 yield _new_token("ERRORTOKEN", e, token.start) 9583 9584 9585def handle_error_space(state, token): 9586 """ 9587 Function for handling special whitespace characters in subprocess mode 9588 """ 9589 if not state["pymode"][-1][0]: 9590 state["last"] = token 9591 yield _new_token("WS", token.string, token.start) 9592 else: 9593 yield from [] 9594 9595 9596def handle_error_linecont(state, token): 9597 """Function for handling special line continuations as whitespace 9598 characters in subprocess mode. 9599 """ 9600 if state["pymode"][-1][0]: 9601 return 9602 prev = state["last"] 9603 if prev.end != token.start: 9604 return # previous token is separated by whitespace 9605 state["last"] = token 9606 yield _new_token("WS", "\\", token.start) 9607 9608 9609def handle_error_token(state, token): 9610 """ 9611 Function for handling error tokens 9612 """ 9613 state["last"] = token 9614 if token.string == "!": 9615 typ = "BANG" 9616 elif not state["pymode"][-1][0]: 9617 typ = "NAME" 9618 else: 9619 typ = "ERRORTOKEN" 9620 yield _new_token(typ, token.string, token.start) 9621 9622 9623def handle_ignore(state, token): 9624 """Function for handling tokens that should be ignored""" 9625 yield from [] 9626 9627 9628def handle_double_amps(state, token): 9629 yield _new_token("AND", "and", token.start) 9630 9631 9632def handle_double_pipe(state, token): 9633 yield _new_token("OR", "or", token.start) 9634 9635 9636def handle_redirect(state, token): 9637 # The parser expects whitespace after a redirection in subproc mode. 9638 # If whitespace does not exist, we'll issue an empty whitespace 9639 # token before proceeding. 9640 state["last"] = token 9641 typ = token.type 9642 st = token.string 9643 key = (typ, st) if (typ, st) in token_map else typ 9644 yield _new_token(token_map[key], st, token.start) 9645 if state["pymode"][-1][0]: 9646 return 9647 # add a whitespace token after a redirection, if we need to 9648 next_tok = next(state["stream"]) 9649 if next_tok.start == token.end: 9650 yield _new_token("WS", "", token.end) 9651 yield from handle_token(state, next_tok) 9652 9653 9654def _make_matcher_handler(tok, typ, pymode, ender, handlers): 9655 matcher = ( 9656 ")" 9657 if tok.endswith("(") 9658 else "}" 9659 if tok.endswith("{") 9660 else "]" 9661 if tok.endswith("[") 9662 else None 9663 ) 9664 9665 def _inner_handler(state, token): 9666 state["pymode"].append((pymode, tok, matcher, token.start)) 9667 state["last"] = token 9668 yield _new_token(typ, tok, token.start) 9669 9670 handlers[(OP, tok)] = _inner_handler 9671 9672 9673@lazyobject 9674def special_handlers(): 9675 """Mapping from ``tokenize`` tokens (or token types) to the proper 9676 function for generating PLY tokens from them. In addition to 9677 yielding PLY tokens, these functions may manipulate the Lexer's state. 9678 """ 9679 sh = { 9680 NL: handle_ignore, 9681 COMMENT: handle_ignore, 9682 ENCODING: handle_ignore, 9683 ENDMARKER: handle_ignore, 9684 NAME: handle_name, 9685 ERRORTOKEN: handle_error_token, 9686 LESS: handle_redirect, 9687 GREATER: handle_redirect, 9688 RIGHTSHIFT: handle_redirect, 9689 IOREDIRECT: handle_redirect, 9690 (OP, "<"): handle_redirect, 9691 (OP, ">"): handle_redirect, 9692 (OP, ">>"): handle_redirect, 9693 (OP, ")"): handle_rparen, 9694 (OP, "}"): handle_rbrace, 9695 (OP, "]"): handle_rbracket, 9696 (OP, "&&"): handle_double_amps, 9697 (OP, "||"): handle_double_pipe, 9698 (ERRORTOKEN, " "): handle_error_space, 9699 (ERRORTOKEN, "\\\n"): handle_error_linecont, 9700 (ERRORTOKEN, "\\\r\n"): handle_error_linecont, 9701 } 9702 _make_matcher_handler("(", "LPAREN", True, ")", sh) 9703 _make_matcher_handler("[", "LBRACKET", True, "]", sh) 9704 _make_matcher_handler("{", "LBRACE", True, "}", sh) 9705 _make_matcher_handler("$(", "DOLLAR_LPAREN", False, ")", sh) 9706 _make_matcher_handler("$[", "DOLLAR_LBRACKET", False, "]", sh) 9707 _make_matcher_handler("${", "DOLLAR_LBRACE", True, "}", sh) 9708 _make_matcher_handler("!(", "BANG_LPAREN", False, ")", sh) 9709 _make_matcher_handler("![", "BANG_LBRACKET", False, "]", sh) 9710 _make_matcher_handler("@(", "AT_LPAREN", True, ")", sh) 9711 _make_matcher_handler("@$(", "ATDOLLAR_LPAREN", False, ")", sh) 9712 return sh 9713 9714 9715def handle_token(state, token): 9716 """ 9717 General-purpose token handler. Makes use of ``token_map`` or 9718 ``special_map`` to yield one or more PLY tokens from the given input. 9719 9720 Parameters 9721 ---------- 9722 9723 state : 9724 The current state of the lexer, including information about whether 9725 we are in Python mode or subprocess mode, which changes the lexer's 9726 behavior. Also includes the stream of tokens yet to be considered. 9727 token : 9728 The token (from ``tokenize``) currently under consideration 9729 """ 9730 typ = token.type 9731 st = token.string 9732 pymode = state["pymode"][-1][0] 9733 if not pymode: 9734 if state["last"] is not None and state["last"].end != token.start: 9735 cur = token.start 9736 old = state["last"].end 9737 if cur[0] == old[0] and cur[1] > old[1]: 9738 yield _new_token("WS", token.line[old[1] : cur[1]], old) 9739 if (typ, st) in special_handlers: 9740 yield from special_handlers[(typ, st)](state, token) 9741 elif (typ, st) in token_map: 9742 state["last"] = token 9743 yield _new_token(token_map[(typ, st)], st, token.start) 9744 elif typ in special_handlers: 9745 yield from special_handlers[typ](state, token) 9746 elif typ in token_map: 9747 state["last"] = token 9748 yield _new_token(token_map[typ], st, token.start) 9749 else: 9750 m = "Unexpected token: {0}".format(token) 9751 yield _new_token("ERRORTOKEN", m, token.start) 9752 9753 9754def get_tokens(s): 9755 """ 9756 Given a string containing xonsh code, generates a stream of relevant PLY 9757 tokens using ``handle_token``. 9758 """ 9759 state = { 9760 "indents": [0], 9761 "last": None, 9762 "pymode": [(True, "", "", (0, 0))], 9763 "stream": tokenize(io.BytesIO(s.encode("utf-8")).readline), 9764 } 9765 while True: 9766 try: 9767 token = next(state["stream"]) 9768 yield from handle_token(state, token) 9769 except StopIteration: 9770 if len(state["pymode"]) > 1: 9771 pm, o, m, p = state["pymode"][-1] 9772 l, c = p 9773 e = 'Unmatched "{}" at line {}, column {}' 9774 yield _new_token("ERRORTOKEN", e.format(o, l, c), (0, 0)) 9775 break 9776 except TokenError as e: 9777 # this is recoverable in single-line mode (from the shell) 9778 # (e.g., EOF while scanning string literal) 9779 yield _new_token("ERRORTOKEN", e.args[0], (0, 0)) 9780 break 9781 except IndentationError as e: 9782 # this is never recoverable 9783 yield _new_token("ERRORTOKEN", e, (0, 0)) 9784 break 9785 9786 9787# synthesize a new PLY token 9788def _new_token(type, value, pos): 9789 o = LexToken() 9790 o.type = type 9791 o.value = value 9792 o.lineno, o.lexpos = pos 9793 return o 9794 9795 9796class Lexer(object): 9797 """Implements a lexer for the xonsh language.""" 9798 9799 _tokens = None 9800 9801 def __init__(self): 9802 """ 9803 Attributes 9804 ---------- 9805 fname : str 9806 Filename 9807 last : token 9808 The last token seen. 9809 lineno : int 9810 The last line number seen. 9811 9812 """ 9813 self.fname = "" 9814 self.last = None 9815 self.beforelast = None 9816 9817 def build(self, **kwargs): 9818 """Part of the PLY lexer API.""" 9819 pass 9820 9821 def reset(self): 9822 pass 9823 9824 def input(self, s): 9825 """Calls the lexer on the string s.""" 9826 self.token_stream = get_tokens(s) 9827 9828 def token(self): 9829 """Retrieves the next token.""" 9830 self.beforelast = self.last 9831 self.last = next(self.token_stream, None) 9832 return self.last 9833 9834 def __iter__(self): 9835 t = self.token() 9836 while t is not None: 9837 yield t 9838 t = self.token() 9839 9840 def split(self, s): 9841 """Splits a string into a list of strings which are whitespace-separated 9842 tokens. 9843 """ 9844 vals = [] 9845 self.input(s) 9846 l = c = -1 9847 ws = "WS" 9848 nl = "\n" 9849 for t in self: 9850 if t.type == ws: 9851 continue 9852 elif l < t.lineno: 9853 vals.append(t.value) 9854 elif len(vals) > 0 and c == t.lexpos: 9855 vals[-1] = vals[-1] + t.value 9856 else: 9857 vals.append(t.value) 9858 nnl = t.value.count(nl) 9859 if nnl == 0: 9860 l = t.lineno 9861 c = t.lexpos + len(t.value) 9862 else: 9863 l = t.lineno + nnl 9864 c = len(t.value.rpartition(nl)[-1]) 9865 return vals 9866 9867 # 9868 # All the tokens recognized by the lexer 9869 # 9870 @property 9871 def tokens(self): 9872 if self._tokens is None: 9873 t = ( 9874 tuple(token_map.values()) 9875 + ( 9876 "NAME", # name tokens 9877 "BANG", # ! tokens 9878 "WS", # whitespace in subprocess mode 9879 "LPAREN", 9880 "RPAREN", # ( ) 9881 "LBRACKET", 9882 "RBRACKET", # [ ] 9883 "LBRACE", 9884 "RBRACE", # { } 9885 "AT_LPAREN", # @( 9886 "BANG_LPAREN", # !( 9887 "BANG_LBRACKET", # ![ 9888 "DOLLAR_LPAREN", # $( 9889 "DOLLAR_LBRACE", # ${ 9890 "DOLLAR_LBRACKET", # $[ 9891 "ATDOLLAR_LPAREN", # @$( 9892 "ERRORTOKEN", # whoops! 9893 ) 9894 + tuple(i.upper() for i in kwmod.kwlist) 9895 ) 9896 self._tokens = t 9897 return self._tokens 9898 9899# 9900# openpy 9901# 9902# -*- coding: utf-8 -*- 9903"""Tools to open ``*.py`` files as Unicode. 9904 9905Uses the encoding specified within the file, as per PEP 263. 9906 9907Much of the code is taken from the tokenize module in Python 3.2. 9908 9909This file was forked from the IPython project: 9910 9911* Copyright (c) 2008-2014, IPython Development Team 9912* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> 9913* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 9914* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 9915""" 9916# amalgamated io 9917# amalgamated re 9918# amalgamated xonsh.lazyasd 9919# amalgamated xonsh.tokenize 9920cookie_comment_re = LazyObject( 9921 lambda: re.compile(r"^\s*#.*coding[:=]\s*([-\w.]+)", re.UNICODE), 9922 globals(), 9923 "cookie_comment_re", 9924) 9925 9926 9927def source_to_unicode(txt, errors="replace", skip_encoding_cookie=True): 9928 """Converts a bytes string with python source code to unicode. 9929 9930 Unicode strings are passed through unchanged. Byte strings are checked 9931 for the python source file encoding cookie to determine encoding. 9932 txt can be either a bytes buffer or a string containing the source 9933 code. 9934 """ 9935 if isinstance(txt, str): 9936 return txt 9937 if isinstance(txt, bytes): 9938 buf = io.BytesIO(txt) 9939 else: 9940 buf = txt 9941 try: 9942 encoding, _ = detect_encoding(buf.readline) 9943 except SyntaxError: 9944 encoding = "ascii" 9945 buf.seek(0) 9946 text = io.TextIOWrapper(buf, encoding, errors=errors, line_buffering=True) 9947 text.mode = "r" 9948 if skip_encoding_cookie: 9949 return u"".join(strip_encoding_cookie(text)) 9950 else: 9951 return text.read() 9952 9953 9954def strip_encoding_cookie(filelike): 9955 """Generator to pull lines from a text-mode file, skipping the encoding 9956 cookie if it is found in the first two lines. 9957 """ 9958 it = iter(filelike) 9959 try: 9960 first = next(it) 9961 if not cookie_comment_re.match(first): 9962 yield first 9963 second = next(it) 9964 if not cookie_comment_re.match(second): 9965 yield second 9966 except StopIteration: 9967 return 9968 for line in it: 9969 yield line 9970 9971 9972def read_py_file(filename, skip_encoding_cookie=True): 9973 """Read a Python file, using the encoding declared inside the file. 9974 9975 Parameters 9976 ---------- 9977 filename : str 9978 The path to the file to read. 9979 skip_encoding_cookie : bool 9980 If True (the default), and the encoding declaration is found in the first 9981 two lines, that line will be excluded from the output - compiling a 9982 unicode string with an encoding declaration is a SyntaxError in Python 2. 9983 9984 Returns 9985 ------- 9986 A unicode string containing the contents of the file. 9987 """ 9988 with tokopen(filename) as f: # the open function defined in this module. 9989 if skip_encoding_cookie: 9990 return "".join(strip_encoding_cookie(f)) 9991 else: 9992 return f.read() 9993 9994 9995def read_py_url(url, errors="replace", skip_encoding_cookie=True): 9996 """Read a Python file from a URL, using the encoding declared inside the file. 9997 9998 Parameters 9999 ---------- 10000 url : str 10001 The URL from which to fetch the file. 10002 errors : str 10003 How to handle decoding errors in the file. Options are the same as for 10004 bytes.decode(), but here 'replace' is the default. 10005 skip_encoding_cookie : bool 10006 If True (the default), and the encoding declaration is found in the first 10007 two lines, that line will be excluded from the output - compiling a 10008 unicode string with an encoding declaration is a SyntaxError in Python 2. 10009 10010 Returns 10011 ------- 10012 A unicode string containing the contents of the file. 10013 """ 10014 # Deferred import for faster start 10015 try: 10016 from urllib.request import urlopen # Py 3 10017 except ImportError: 10018 from urllib import urlopen 10019 response = urlopen(url) 10020 buf = io.BytesIO(response.read()) 10021 return source_to_unicode(buf, errors, skip_encoding_cookie) 10022 10023 10024def _list_readline(x): 10025 """Given a list, returns a readline() function that returns the next element 10026 with each call. 10027 """ 10028 x = iter(x) 10029 10030 def readline(): 10031 return next(x) 10032 10033 return readline 10034 10035# 10036# xontribs 10037# 10038"""Tools for helping manage xontributions.""" 10039# amalgamated os 10040# amalgamated sys 10041# amalgamated json 10042# amalgamated builtins 10043# amalgamated argparse 10044# amalgamated functools 10045# amalgamated importlib 10046# amalgamated importlib.util 10047# amalgamated xonsh.tools 10048@functools.lru_cache(1) 10049def xontribs_json(): 10050 return os.path.join(os.path.dirname(__file__), "xontribs.json") 10051 10052 10053def find_xontrib(name): 10054 """Finds a xontribution from its name.""" 10055 if name.startswith("."): 10056 spec = importlib.util.find_spec(name, package="xontrib") 10057 else: 10058 spec = importlib.util.find_spec("." + name, package="xontrib") 10059 return spec or importlib.util.find_spec(name) 10060 10061 10062def xontrib_context(name): 10063 """Return a context dictionary for a xontrib of a given name.""" 10064 spec = find_xontrib(name) 10065 if spec is None: 10066 return None 10067 m = importlib.import_module(spec.name) 10068 pubnames = getattr(m, "__all__", None) 10069 if pubnames is not None: 10070 ctx = {k: getattr(m, k) for k in pubnames} 10071 else: 10072 ctx = {k: getattr(m, k) for k in dir(m) if not k.startswith("_")} 10073 return ctx 10074 10075 10076def prompt_xontrib_install(names): 10077 """Returns a formatted string with name of xontrib package to prompt user""" 10078 md = xontrib_metadata() 10079 packages = [] 10080 for name in names: 10081 for xontrib in md["xontribs"]: 10082 if xontrib["name"] == name: 10083 packages.append(xontrib["package"]) 10084 10085 print( 10086 "The following xontribs are enabled but not installed: \n" 10087 " {xontribs}\n" 10088 "To install them run \n" 10089 " xpip install {packages}".format( 10090 xontribs=" ".join(names), packages=" ".join(packages) 10091 ) 10092 ) 10093 10094 10095def update_context(name, ctx=None): 10096 """Updates a context in place from a xontrib. If ctx is not provided, 10097 then __xonsh_ctx__ is updated. 10098 """ 10099 if ctx is None: 10100 ctx = builtins.__xonsh_ctx__ 10101 if not hasattr(update_context, "bad_imports"): 10102 update_context.bad_imports = [] 10103 modctx = xontrib_context(name) 10104 if modctx is None: 10105 update_context.bad_imports.append(name) 10106 return ctx 10107 return ctx.update(modctx) 10108 10109 10110@functools.lru_cache() 10111def xontrib_metadata(): 10112 """Loads and returns the xontribs.json file.""" 10113 with open(xontribs_json(), "r") as f: 10114 md = json.load(f) 10115 return md 10116 10117 10118def xontribs_load(names, verbose=False): 10119 """Load xontribs from a list of names""" 10120 ctx = builtins.__xonsh_ctx__ 10121 for name in names: 10122 if verbose: 10123 print("loading xontrib {0!r}".format(name)) 10124 update_context(name, ctx=ctx) 10125 if update_context.bad_imports: 10126 prompt_xontrib_install(update_context.bad_imports) 10127 del update_context.bad_imports 10128 10129 10130def _load(ns): 10131 """load xontribs""" 10132 xontribs_load(ns.names, verbose=ns.verbose) 10133 10134 10135def _list(ns): 10136 """Lists xontribs.""" 10137 meta = xontrib_metadata() 10138 data = [] 10139 nname = 6 # ensures some buffer space. 10140 names = None if len(ns.names) == 0 else set(ns.names) 10141 for md in meta["xontribs"]: 10142 name = md["name"] 10143 if names is not None and md["name"] not in names: 10144 continue 10145 nname = max(nname, len(name)) 10146 spec = find_xontrib(name) 10147 if spec is None: 10148 installed = loaded = False 10149 else: 10150 installed = True 10151 loaded = spec.name in sys.modules 10152 d = {"name": name, "installed": installed, "loaded": loaded} 10153 data.append(d) 10154 if ns.json: 10155 jdata = {d.pop("name"): d for d in data} 10156 s = json.dumps(jdata) 10157 print(s) 10158 else: 10159 s = "" 10160 for d in data: 10161 name = d["name"] 10162 lname = len(name) 10163 s += "{PURPLE}" + name + "{NO_COLOR} " + " " * (nname - lname) 10164 if d["installed"]: 10165 s += "{GREEN}installed{NO_COLOR} " 10166 else: 10167 s += "{RED}not-installed{NO_COLOR} " 10168 if d["loaded"]: 10169 s += "{GREEN}loaded{NO_COLOR}" 10170 else: 10171 s += "{RED}not-loaded{NO_COLOR}" 10172 s += "\n" 10173 print_color(s[:-1]) 10174 10175 10176@functools.lru_cache() 10177def _create_xontrib_parser(): 10178 # parse command line args 10179 parser = argparse.ArgumentParser( 10180 prog="xontrib", description="Manages xonsh extensions" 10181 ) 10182 subp = parser.add_subparsers(title="action", dest="action") 10183 load = subp.add_parser("load", help="loads xontribs") 10184 load.add_argument( 10185 "-v", "--verbose", action="store_true", default=False, dest="verbose" 10186 ) 10187 load.add_argument("names", nargs="+", default=(), help="names of xontribs") 10188 lyst = subp.add_parser( 10189 "list", help=("list xontribs, whether they are " "installed, and loaded.") 10190 ) 10191 lyst.add_argument( 10192 "--json", action="store_true", default=False, help="reports results as json" 10193 ) 10194 lyst.add_argument("names", nargs="*", default=(), help="names of xontribs") 10195 return parser 10196 10197 10198_MAIN_XONTRIB_ACTIONS = {"load": _load, "list": _list} 10199 10200 10201@unthreadable 10202def xontribs_main(args=None, stdin=None): 10203 """Alias that loads xontribs""" 10204 if not args or ( 10205 args[0] not in _MAIN_XONTRIB_ACTIONS and args[0] not in {"-h", "--help"} 10206 ): 10207 args.insert(0, "load") 10208 parser = _create_xontrib_parser() 10209 ns = parser.parse_args(args) 10210 if ns.action is None: # apply default action 10211 ns = parser.parse_args(["load"] + args) 10212 return _MAIN_XONTRIB_ACTIONS[ns.action](ns) 10213 10214# 10215# ansi_colors 10216# 10217"""Tools for helping with ANSI color codes.""" 10218# amalgamated sys 10219string = _LazyModule.load('string', 'string') 10220# amalgamated warnings 10221# amalgamated builtins 10222# amalgamated xonsh.platform 10223# amalgamated xonsh.lazyasd 10224# amalgamated xonsh.color_tools 10225def ansi_partial_color_format(template, style="default", cmap=None, hide=False): 10226 """Formats a template string but only with respect to the colors. 10227 Another template string is returned, with the color values filled in. 10228 10229 Parameters 10230 ---------- 10231 template : str 10232 The template string, potentially with color names. 10233 style : str, optional 10234 Style name to look up color map from. 10235 cmap : dict, optional 10236 A color map to use, this will prevent the color map from being 10237 looked up via the style name. 10238 hide : bool, optional 10239 Whether to wrap the color codes in the \\001 and \\002 escape 10240 codes, so that the color codes are not counted against line 10241 length. 10242 10243 Returns 10244 ------- 10245 A template string with the color values filled in. 10246 """ 10247 try: 10248 return _ansi_partial_color_format_main( 10249 template, style=style, cmap=cmap, hide=hide 10250 ) 10251 except Exception: 10252 return template 10253 10254 10255def _ansi_partial_color_format_main(template, style="default", cmap=None, hide=False): 10256 if cmap is not None: 10257 pass 10258 elif style in ANSI_STYLES: 10259 cmap = ANSI_STYLES[style] 10260 else: 10261 try: # dynamically loading the style 10262 cmap = ansi_style_by_name(style) 10263 except Exception: 10264 msg = "Could not find color style {0!r}, using default." 10265 print(msg.format(style), file=sys.stderr) 10266 builtins.__xonsh_env__["XONSH_COLOR_STYLE"] = "default" 10267 cmap = ANSI_STYLES["default"] 10268 formatter = string.Formatter() 10269 esc = ("\001" if hide else "") + "\033[" 10270 m = "m" + ("\002" if hide else "") 10271 bopen = "{" 10272 bclose = "}" 10273 colon = ":" 10274 expl = "!" 10275 toks = [] 10276 for literal, field, spec, conv in formatter.parse(template): 10277 toks.append(literal) 10278 if field is None: 10279 pass 10280 elif field in cmap: 10281 toks.extend([esc, cmap[field], m]) 10282 elif "#" in field: 10283 field = field.lower() 10284 pre, _, post = field.partition("#") 10285 f_or_b = "38" if RE_BACKGROUND.search(pre) is None else "48" 10286 rgb, _, post = post.partition("_") 10287 c256, _ = rgb_to_256(rgb) 10288 color = f_or_b + ";5;" + c256 10289 mods = pre + "_" + post 10290 if "underline" in mods: 10291 color = "4;" + color 10292 if "bold" in mods: 10293 color = "1;" + color 10294 toks.extend([esc, color, m]) 10295 elif field is not None: 10296 toks.append(bopen) 10297 toks.append(field) 10298 if conv is not None and len(conv) > 0: 10299 toks.append(expl) 10300 toks.append(conv) 10301 if spec is not None and len(spec) > 0: 10302 toks.append(colon) 10303 toks.append(spec) 10304 toks.append(bclose) 10305 return "".join(toks) 10306 10307 10308def ansi_color_style_names(): 10309 """Returns an iterable of all ANSI color style names.""" 10310 return ANSI_STYLES.keys() 10311 10312 10313def ansi_color_style(style="default"): 10314 """Returns the current color map.""" 10315 if style in ANSI_STYLES: 10316 cmap = ANSI_STYLES[style] 10317 else: 10318 msg = "Could not find color style {0!r}, using default.".format(style) 10319 warnings.warn(msg, RuntimeWarning) 10320 cmap = ANSI_STYLES["default"] 10321 return cmap 10322 10323 10324def _ansi_expand_style(cmap): 10325 """Expands a style in order to more quickly make color map changes.""" 10326 for key, val in list(cmap.items()): 10327 if key == "NO_COLOR": 10328 continue 10329 elif len(val) == 0: 10330 cmap["BOLD_" + key] = "1" 10331 cmap["UNDERLINE_" + key] = "4" 10332 cmap["BOLD_UNDERLINE_" + key] = "1;4" 10333 cmap["BACKGROUND_" + key] = val 10334 else: 10335 cmap["BOLD_" + key] = "1;" + val 10336 cmap["UNDERLINE_" + key] = "4;" + val 10337 cmap["BOLD_UNDERLINE_" + key] = "1;4;" + val 10338 cmap["BACKGROUND_" + key] = val.replace("38", "48", 1) 10339 10340 10341def _bw_style(): 10342 style = { 10343 "BLACK": "", 10344 "BLUE": "", 10345 "CYAN": "", 10346 "GREEN": "", 10347 "INTENSE_BLACK": "", 10348 "INTENSE_BLUE": "", 10349 "INTENSE_CYAN": "", 10350 "INTENSE_GREEN": "", 10351 "INTENSE_PURPLE": "", 10352 "INTENSE_RED": "", 10353 "INTENSE_WHITE": "", 10354 "INTENSE_YELLOW": "", 10355 "NO_COLOR": "0", 10356 "PURPLE": "", 10357 "RED": "", 10358 "WHITE": "", 10359 "YELLOW": "", 10360 } 10361 _ansi_expand_style(style) 10362 return style 10363 10364 10365def _default_style(): 10366 style = { 10367 # Reset 10368 "NO_COLOR": "0", # Text Reset 10369 # Regular Colors 10370 "BLACK": "0;30", # BLACK 10371 "RED": "0;31", # RED 10372 "GREEN": "0;32", # GREEN 10373 "YELLOW": "0;33", # YELLOW 10374 "BLUE": "0;34", # BLUE 10375 "PURPLE": "0;35", # PURPLE 10376 "CYAN": "0;36", # CYAN 10377 "WHITE": "0;37", # WHITE 10378 # Bold 10379 "BOLD_BLACK": "1;30", # BLACK 10380 "BOLD_RED": "1;31", # RED 10381 "BOLD_GREEN": "1;32", # GREEN 10382 "BOLD_YELLOW": "1;33", # YELLOW 10383 "BOLD_BLUE": "1;34", # BLUE 10384 "BOLD_PURPLE": "1;35", # PURPLE 10385 "BOLD_CYAN": "1;36", # CYAN 10386 "BOLD_WHITE": "1;37", # WHITE 10387 # Underline 10388 "UNDERLINE_BLACK": "4;30", # BLACK 10389 "UNDERLINE_RED": "4;31", # RED 10390 "UNDERLINE_GREEN": "4;32", # GREEN 10391 "UNDERLINE_YELLOW": "4;33", # YELLOW 10392 "UNDERLINE_BLUE": "4;34", # BLUE 10393 "UNDERLINE_PURPLE": "4;35", # PURPLE 10394 "UNDERLINE_CYAN": "4;36", # CYAN 10395 "UNDERLINE_WHITE": "4;37", # WHITE 10396 # Bold, Underline 10397 "BOLD_UNDERLINE_BLACK": "1;4;30", # BLACK 10398 "BOLD_UNDERLINE_RED": "1;4;31", # RED 10399 "BOLD_UNDERLINE_GREEN": "1;4;32", # GREEN 10400 "BOLD_UNDERLINE_YELLOW": "1;4;33", # YELLOW 10401 "BOLD_UNDERLINE_BLUE": "1;4;34", # BLUE 10402 "BOLD_UNDERLINE_PURPLE": "1;4;35", # PURPLE 10403 "BOLD_UNDERLINE_CYAN": "1;4;36", # CYAN 10404 "BOLD_UNDERLINE_WHITE": "1;4;37", # WHITE 10405 # Background 10406 "BACKGROUND_BLACK": "40", # BLACK 10407 "BACKGROUND_RED": "41", # RED 10408 "BACKGROUND_GREEN": "42", # GREEN 10409 "BACKGROUND_YELLOW": "43", # YELLOW 10410 "BACKGROUND_BLUE": "44", # BLUE 10411 "BACKGROUND_PURPLE": "45", # PURPLE 10412 "BACKGROUND_CYAN": "46", # CYAN 10413 "BACKGROUND_WHITE": "47", # WHITE 10414 # High Intensity 10415 "INTENSE_BLACK": "0;90", # BLACK 10416 "INTENSE_RED": "0;91", # RED 10417 "INTENSE_GREEN": "0;92", # GREEN 10418 "INTENSE_YELLOW": "0;93", # YELLOW 10419 "INTENSE_BLUE": "0;94", # BLUE 10420 "INTENSE_PURPLE": "0;95", # PURPLE 10421 "INTENSE_CYAN": "0;96", # CYAN 10422 "INTENSE_WHITE": "0;97", # WHITE 10423 # Bold High Intensity 10424 "BOLD_INTENSE_BLACK": "1;90", # BLACK 10425 "BOLD_INTENSE_RED": "1;91", # RED 10426 "BOLD_INTENSE_GREEN": "1;92", # GREEN 10427 "BOLD_INTENSE_YELLOW": "1;93", # YELLOW 10428 "BOLD_INTENSE_BLUE": "1;94", # BLUE 10429 "BOLD_INTENSE_PURPLE": "1;95", # PURPLE 10430 "BOLD_INTENSE_CYAN": "1;96", # CYAN 10431 "BOLD_INTENSE_WHITE": "1;97", # WHITE 10432 # Underline High Intensity 10433 "UNDERLINE_INTENSE_BLACK": "4;90", # BLACK 10434 "UNDERLINE_INTENSE_RED": "4;91", # RED 10435 "UNDERLINE_INTENSE_GREEN": "4;92", # GREEN 10436 "UNDERLINE_INTENSE_YELLOW": "4;93", # YELLOW 10437 "UNDERLINE_INTENSE_BLUE": "4;94", # BLUE 10438 "UNDERLINE_INTENSE_PURPLE": "4;95", # PURPLE 10439 "UNDERLINE_INTENSE_CYAN": "4;96", # CYAN 10440 "UNDERLINE_INTENSE_WHITE": "4;97", # WHITE 10441 # Bold Underline High Intensity 10442 "BOLD_UNDERLINE_INTENSE_BLACK": "1;4;90", # BLACK 10443 "BOLD_UNDERLINE_INTENSE_RED": "1;4;91", # RED 10444 "BOLD_UNDERLINE_INTENSE_GREEN": "1;4;92", # GREEN 10445 "BOLD_UNDERLINE_INTENSE_YELLOW": "1;4;93", # YELLOW 10446 "BOLD_UNDERLINE_INTENSE_BLUE": "1;4;94", # BLUE 10447 "BOLD_UNDERLINE_INTENSE_PURPLE": "1;4;95", # PURPLE 10448 "BOLD_UNDERLINE_INTENSE_CYAN": "1;4;96", # CYAN 10449 "BOLD_UNDERLINE_INTENSE_WHITE": "1;4;97", # WHITE 10450 # High Intensity backgrounds 10451 "BACKGROUND_INTENSE_BLACK": "0;100", # BLACK 10452 "BACKGROUND_INTENSE_RED": "0;101", # RED 10453 "BACKGROUND_INTENSE_GREEN": "0;102", # GREEN 10454 "BACKGROUND_INTENSE_YELLOW": "0;103", # YELLOW 10455 "BACKGROUND_INTENSE_BLUE": "0;104", # BLUE 10456 "BACKGROUND_INTENSE_PURPLE": "0;105", # PURPLE 10457 "BACKGROUND_INTENSE_CYAN": "0;106", # CYAN 10458 "BACKGROUND_INTENSE_WHITE": "0;107", # WHITE 10459 } 10460 return style 10461 10462 10463def _monokai_style(): 10464 style = { 10465 "NO_COLOR": "0", 10466 "BLACK": "38;5;16", 10467 "BLUE": "38;5;63", 10468 "CYAN": "38;5;81", 10469 "GREEN": "38;5;40", 10470 "PURPLE": "38;5;89", 10471 "RED": "38;5;124", 10472 "WHITE": "38;5;188", 10473 "YELLOW": "38;5;184", 10474 "INTENSE_BLACK": "38;5;59", 10475 "INTENSE_BLUE": "38;5;20", 10476 "INTENSE_CYAN": "38;5;44", 10477 "INTENSE_GREEN": "38;5;148", 10478 "INTENSE_PURPLE": "38;5;141", 10479 "INTENSE_RED": "38;5;197", 10480 "INTENSE_WHITE": "38;5;15", 10481 "INTENSE_YELLOW": "38;5;186", 10482 } 10483 _ansi_expand_style(style) 10484 return style 10485 10486 10487#################################### 10488# Auto-generated below this line # 10489#################################### 10490 10491 10492def _algol_style(): 10493 style = { 10494 "BLACK": "38;5;59", 10495 "BLUE": "38;5;59", 10496 "CYAN": "38;5;59", 10497 "GREEN": "38;5;59", 10498 "INTENSE_BLACK": "38;5;59", 10499 "INTENSE_BLUE": "38;5;102", 10500 "INTENSE_CYAN": "38;5;102", 10501 "INTENSE_GREEN": "38;5;102", 10502 "INTENSE_PURPLE": "38;5;102", 10503 "INTENSE_RED": "38;5;09", 10504 "INTENSE_WHITE": "38;5;102", 10505 "INTENSE_YELLOW": "38;5;102", 10506 "NO_COLOR": "0", 10507 "PURPLE": "38;5;59", 10508 "RED": "38;5;09", 10509 "WHITE": "38;5;102", 10510 "YELLOW": "38;5;09", 10511 } 10512 _ansi_expand_style(style) 10513 return style 10514 10515 10516def _algol_nu_style(): 10517 style = { 10518 "BLACK": "38;5;59", 10519 "BLUE": "38;5;59", 10520 "CYAN": "38;5;59", 10521 "GREEN": "38;5;59", 10522 "INTENSE_BLACK": "38;5;59", 10523 "INTENSE_BLUE": "38;5;102", 10524 "INTENSE_CYAN": "38;5;102", 10525 "INTENSE_GREEN": "38;5;102", 10526 "INTENSE_PURPLE": "38;5;102", 10527 "INTENSE_RED": "38;5;09", 10528 "INTENSE_WHITE": "38;5;102", 10529 "INTENSE_YELLOW": "38;5;102", 10530 "NO_COLOR": "0", 10531 "PURPLE": "38;5;59", 10532 "RED": "38;5;09", 10533 "WHITE": "38;5;102", 10534 "YELLOW": "38;5;09", 10535 } 10536 _ansi_expand_style(style) 10537 return style 10538 10539 10540def _autumn_style(): 10541 style = { 10542 "BLACK": "38;5;18", 10543 "BLUE": "38;5;19", 10544 "CYAN": "38;5;37", 10545 "GREEN": "38;5;34", 10546 "INTENSE_BLACK": "38;5;59", 10547 "INTENSE_BLUE": "38;5;33", 10548 "INTENSE_CYAN": "38;5;33", 10549 "INTENSE_GREEN": "38;5;64", 10550 "INTENSE_PURPLE": "38;5;217", 10551 "INTENSE_RED": "38;5;130", 10552 "INTENSE_WHITE": "38;5;145", 10553 "INTENSE_YELLOW": "38;5;217", 10554 "NO_COLOR": "0", 10555 "PURPLE": "38;5;90", 10556 "RED": "38;5;124", 10557 "WHITE": "38;5;145", 10558 "YELLOW": "38;5;130", 10559 } 10560 _ansi_expand_style(style) 10561 return style 10562 10563 10564def _borland_style(): 10565 style = { 10566 "BLACK": "38;5;16", 10567 "BLUE": "38;5;18", 10568 "CYAN": "38;5;30", 10569 "GREEN": "38;5;28", 10570 "INTENSE_BLACK": "38;5;59", 10571 "INTENSE_BLUE": "38;5;21", 10572 "INTENSE_CYAN": "38;5;194", 10573 "INTENSE_GREEN": "38;5;102", 10574 "INTENSE_PURPLE": "38;5;188", 10575 "INTENSE_RED": "38;5;09", 10576 "INTENSE_WHITE": "38;5;224", 10577 "INTENSE_YELLOW": "38;5;188", 10578 "NO_COLOR": "0", 10579 "PURPLE": "38;5;90", 10580 "RED": "38;5;124", 10581 "WHITE": "38;5;145", 10582 "YELLOW": "38;5;124", 10583 } 10584 _ansi_expand_style(style) 10585 return style 10586 10587 10588def _colorful_style(): 10589 style = { 10590 "BLACK": "38;5;16", 10591 "BLUE": "38;5;20", 10592 "CYAN": "38;5;31", 10593 "GREEN": "38;5;34", 10594 "INTENSE_BLACK": "38;5;59", 10595 "INTENSE_BLUE": "38;5;61", 10596 "INTENSE_CYAN": "38;5;145", 10597 "INTENSE_GREEN": "38;5;102", 10598 "INTENSE_PURPLE": "38;5;217", 10599 "INTENSE_RED": "38;5;166", 10600 "INTENSE_WHITE": "38;5;15", 10601 "INTENSE_YELLOW": "38;5;217", 10602 "NO_COLOR": "0", 10603 "PURPLE": "38;5;90", 10604 "RED": "38;5;124", 10605 "WHITE": "38;5;145", 10606 "YELLOW": "38;5;130", 10607 } 10608 _ansi_expand_style(style) 10609 return style 10610 10611 10612def _emacs_style(): 10613 style = { 10614 "BLACK": "38;5;28", 10615 "BLUE": "38;5;18", 10616 "CYAN": "38;5;26", 10617 "GREEN": "38;5;34", 10618 "INTENSE_BLACK": "38;5;59", 10619 "INTENSE_BLUE": "38;5;26", 10620 "INTENSE_CYAN": "38;5;145", 10621 "INTENSE_GREEN": "38;5;34", 10622 "INTENSE_PURPLE": "38;5;129", 10623 "INTENSE_RED": "38;5;167", 10624 "INTENSE_WHITE": "38;5;145", 10625 "INTENSE_YELLOW": "38;5;145", 10626 "NO_COLOR": "0", 10627 "PURPLE": "38;5;90", 10628 "RED": "38;5;124", 10629 "WHITE": "38;5;145", 10630 "YELLOW": "38;5;130", 10631 } 10632 _ansi_expand_style(style) 10633 return style 10634 10635 10636def _friendly_style(): 10637 style = { 10638 "BLACK": "38;5;22", 10639 "BLUE": "38;5;18", 10640 "CYAN": "38;5;31", 10641 "GREEN": "38;5;34", 10642 "INTENSE_BLACK": "38;5;59", 10643 "INTENSE_BLUE": "38;5;74", 10644 "INTENSE_CYAN": "38;5;74", 10645 "INTENSE_GREEN": "38;5;71", 10646 "INTENSE_PURPLE": "38;5;134", 10647 "INTENSE_RED": "38;5;167", 10648 "INTENSE_WHITE": "38;5;15", 10649 "INTENSE_YELLOW": "38;5;145", 10650 "NO_COLOR": "0", 10651 "PURPLE": "38;5;90", 10652 "RED": "38;5;124", 10653 "WHITE": "38;5;145", 10654 "YELLOW": "38;5;166", 10655 } 10656 _ansi_expand_style(style) 10657 return style 10658 10659 10660def _fruity_style(): 10661 style = { 10662 "BLACK": "38;5;16", 10663 "BLUE": "38;5;32", 10664 "CYAN": "38;5;32", 10665 "GREEN": "38;5;28", 10666 "INTENSE_BLACK": "38;5;59", 10667 "INTENSE_BLUE": "38;5;33", 10668 "INTENSE_CYAN": "38;5;33", 10669 "INTENSE_GREEN": "38;5;102", 10670 "INTENSE_PURPLE": "38;5;198", 10671 "INTENSE_RED": "38;5;202", 10672 "INTENSE_WHITE": "38;5;15", 10673 "INTENSE_YELLOW": "38;5;187", 10674 "NO_COLOR": "0", 10675 "PURPLE": "38;5;198", 10676 "RED": "38;5;09", 10677 "WHITE": "38;5;187", 10678 "YELLOW": "38;5;202", 10679 } 10680 _ansi_expand_style(style) 10681 return style 10682 10683 10684def _igor_style(): 10685 style = { 10686 "BLACK": "38;5;34", 10687 "BLUE": "38;5;21", 10688 "CYAN": "38;5;30", 10689 "GREEN": "38;5;34", 10690 "INTENSE_BLACK": "38;5;30", 10691 "INTENSE_BLUE": "38;5;21", 10692 "INTENSE_CYAN": "38;5;30", 10693 "INTENSE_GREEN": "38;5;34", 10694 "INTENSE_PURPLE": "38;5;163", 10695 "INTENSE_RED": "38;5;166", 10696 "INTENSE_WHITE": "38;5;163", 10697 "INTENSE_YELLOW": "38;5;166", 10698 "NO_COLOR": "0", 10699 "PURPLE": "38;5;163", 10700 "RED": "38;5;166", 10701 "WHITE": "38;5;163", 10702 "YELLOW": "38;5;166", 10703 } 10704 _ansi_expand_style(style) 10705 return style 10706 10707 10708def _lovelace_style(): 10709 style = { 10710 "BLACK": "38;5;59", 10711 "BLUE": "38;5;25", 10712 "CYAN": "38;5;29", 10713 "GREEN": "38;5;65", 10714 "INTENSE_BLACK": "38;5;59", 10715 "INTENSE_BLUE": "38;5;25", 10716 "INTENSE_CYAN": "38;5;102", 10717 "INTENSE_GREEN": "38;5;29", 10718 "INTENSE_PURPLE": "38;5;133", 10719 "INTENSE_RED": "38;5;131", 10720 "INTENSE_WHITE": "38;5;102", 10721 "INTENSE_YELLOW": "38;5;136", 10722 "NO_COLOR": "0", 10723 "PURPLE": "38;5;133", 10724 "RED": "38;5;124", 10725 "WHITE": "38;5;102", 10726 "YELLOW": "38;5;130", 10727 } 10728 _ansi_expand_style(style) 10729 return style 10730 10731 10732def _manni_style(): 10733 style = { 10734 "BLACK": "38;5;16", 10735 "BLUE": "38;5;18", 10736 "CYAN": "38;5;30", 10737 "GREEN": "38;5;40", 10738 "INTENSE_BLACK": "38;5;59", 10739 "INTENSE_BLUE": "38;5;105", 10740 "INTENSE_CYAN": "38;5;45", 10741 "INTENSE_GREEN": "38;5;113", 10742 "INTENSE_PURPLE": "38;5;165", 10743 "INTENSE_RED": "38;5;202", 10744 "INTENSE_WHITE": "38;5;224", 10745 "INTENSE_YELLOW": "38;5;221", 10746 "NO_COLOR": "0", 10747 "PURPLE": "38;5;165", 10748 "RED": "38;5;124", 10749 "WHITE": "38;5;145", 10750 "YELLOW": "38;5;166", 10751 } 10752 _ansi_expand_style(style) 10753 return style 10754 10755 10756def _murphy_style(): 10757 style = { 10758 "BLACK": "38;5;16", 10759 "BLUE": "38;5;18", 10760 "CYAN": "38;5;31", 10761 "GREEN": "38;5;34", 10762 "INTENSE_BLACK": "38;5;59", 10763 "INTENSE_BLUE": "38;5;63", 10764 "INTENSE_CYAN": "38;5;86", 10765 "INTENSE_GREEN": "38;5;86", 10766 "INTENSE_PURPLE": "38;5;213", 10767 "INTENSE_RED": "38;5;209", 10768 "INTENSE_WHITE": "38;5;15", 10769 "INTENSE_YELLOW": "38;5;222", 10770 "NO_COLOR": "0", 10771 "PURPLE": "38;5;90", 10772 "RED": "38;5;124", 10773 "WHITE": "38;5;145", 10774 "YELLOW": "38;5;166", 10775 } 10776 _ansi_expand_style(style) 10777 return style 10778 10779 10780def _native_style(): 10781 style = { 10782 "BLACK": "38;5;52", 10783 "BLUE": "38;5;67", 10784 "CYAN": "38;5;31", 10785 "GREEN": "38;5;64", 10786 "INTENSE_BLACK": "38;5;59", 10787 "INTENSE_BLUE": "38;5;68", 10788 "INTENSE_CYAN": "38;5;87", 10789 "INTENSE_GREEN": "38;5;70", 10790 "INTENSE_PURPLE": "38;5;188", 10791 "INTENSE_RED": "38;5;160", 10792 "INTENSE_WHITE": "38;5;15", 10793 "INTENSE_YELLOW": "38;5;214", 10794 "NO_COLOR": "0", 10795 "PURPLE": "38;5;59", 10796 "RED": "38;5;124", 10797 "WHITE": "38;5;145", 10798 "YELLOW": "38;5;124", 10799 } 10800 _ansi_expand_style(style) 10801 return style 10802 10803 10804def _paraiso_dark_style(): 10805 style = { 10806 "BLACK": "38;5;95", 10807 "BLUE": "38;5;97", 10808 "CYAN": "38;5;39", 10809 "GREEN": "38;5;72", 10810 "INTENSE_BLACK": "38;5;95", 10811 "INTENSE_BLUE": "38;5;97", 10812 "INTENSE_CYAN": "38;5;79", 10813 "INTENSE_GREEN": "38;5;72", 10814 "INTENSE_PURPLE": "38;5;188", 10815 "INTENSE_RED": "38;5;203", 10816 "INTENSE_WHITE": "38;5;188", 10817 "INTENSE_YELLOW": "38;5;220", 10818 "NO_COLOR": "0", 10819 "PURPLE": "38;5;97", 10820 "RED": "38;5;203", 10821 "WHITE": "38;5;79", 10822 "YELLOW": "38;5;214", 10823 } 10824 _ansi_expand_style(style) 10825 return style 10826 10827 10828def _paraiso_light_style(): 10829 style = { 10830 "BLACK": "38;5;16", 10831 "BLUE": "38;5;16", 10832 "CYAN": "38;5;39", 10833 "GREEN": "38;5;72", 10834 "INTENSE_BLACK": "38;5;16", 10835 "INTENSE_BLUE": "38;5;97", 10836 "INTENSE_CYAN": "38;5;79", 10837 "INTENSE_GREEN": "38;5;72", 10838 "INTENSE_PURPLE": "38;5;97", 10839 "INTENSE_RED": "38;5;203", 10840 "INTENSE_WHITE": "38;5;79", 10841 "INTENSE_YELLOW": "38;5;220", 10842 "NO_COLOR": "0", 10843 "PURPLE": "38;5;97", 10844 "RED": "38;5;16", 10845 "WHITE": "38;5;102", 10846 "YELLOW": "38;5;214", 10847 } 10848 _ansi_expand_style(style) 10849 return style 10850 10851 10852def _pastie_style(): 10853 style = { 10854 "BLACK": "38;5;16", 10855 "BLUE": "38;5;20", 10856 "CYAN": "38;5;25", 10857 "GREEN": "38;5;28", 10858 "INTENSE_BLACK": "38;5;59", 10859 "INTENSE_BLUE": "38;5;61", 10860 "INTENSE_CYAN": "38;5;194", 10861 "INTENSE_GREEN": "38;5;34", 10862 "INTENSE_PURPLE": "38;5;188", 10863 "INTENSE_RED": "38;5;172", 10864 "INTENSE_WHITE": "38;5;15", 10865 "INTENSE_YELLOW": "38;5;188", 10866 "NO_COLOR": "0", 10867 "PURPLE": "38;5;125", 10868 "RED": "38;5;124", 10869 "WHITE": "38;5;145", 10870 "YELLOW": "38;5;130", 10871 } 10872 _ansi_expand_style(style) 10873 return style 10874 10875 10876def _perldoc_style(): 10877 style = { 10878 "BLACK": "38;5;18", 10879 "BLUE": "38;5;18", 10880 "CYAN": "38;5;31", 10881 "GREEN": "38;5;34", 10882 "INTENSE_BLACK": "38;5;59", 10883 "INTENSE_BLUE": "38;5;134", 10884 "INTENSE_CYAN": "38;5;145", 10885 "INTENSE_GREEN": "38;5;28", 10886 "INTENSE_PURPLE": "38;5;134", 10887 "INTENSE_RED": "38;5;167", 10888 "INTENSE_WHITE": "38;5;188", 10889 "INTENSE_YELLOW": "38;5;188", 10890 "NO_COLOR": "0", 10891 "PURPLE": "38;5;90", 10892 "RED": "38;5;124", 10893 "WHITE": "38;5;145", 10894 "YELLOW": "38;5;166", 10895 } 10896 _ansi_expand_style(style) 10897 return style 10898 10899 10900def _rrt_style(): 10901 style = { 10902 "BLACK": "38;5;09", 10903 "BLUE": "38;5;117", 10904 "CYAN": "38;5;117", 10905 "GREEN": "38;5;46", 10906 "INTENSE_BLACK": "38;5;117", 10907 "INTENSE_BLUE": "38;5;117", 10908 "INTENSE_CYAN": "38;5;122", 10909 "INTENSE_GREEN": "38;5;46", 10910 "INTENSE_PURPLE": "38;5;213", 10911 "INTENSE_RED": "38;5;09", 10912 "INTENSE_WHITE": "38;5;188", 10913 "INTENSE_YELLOW": "38;5;222", 10914 "NO_COLOR": "0", 10915 "PURPLE": "38;5;213", 10916 "RED": "38;5;09", 10917 "WHITE": "38;5;117", 10918 "YELLOW": "38;5;09", 10919 } 10920 _ansi_expand_style(style) 10921 return style 10922 10923 10924def _tango_style(): 10925 style = { 10926 "BLACK": "38;5;16", 10927 "BLUE": "38;5;20", 10928 "CYAN": "38;5;61", 10929 "GREEN": "38;5;34", 10930 "INTENSE_BLACK": "38;5;24", 10931 "INTENSE_BLUE": "38;5;62", 10932 "INTENSE_CYAN": "38;5;15", 10933 "INTENSE_GREEN": "38;5;64", 10934 "INTENSE_PURPLE": "38;5;15", 10935 "INTENSE_RED": "38;5;09", 10936 "INTENSE_WHITE": "38;5;15", 10937 "INTENSE_YELLOW": "38;5;178", 10938 "NO_COLOR": "0", 10939 "PURPLE": "38;5;90", 10940 "RED": "38;5;124", 10941 "WHITE": "38;5;15", 10942 "YELLOW": "38;5;94", 10943 } 10944 _ansi_expand_style(style) 10945 return style 10946 10947 10948def _trac_style(): 10949 style = { 10950 "BLACK": "38;5;16", 10951 "BLUE": "38;5;18", 10952 "CYAN": "38;5;30", 10953 "GREEN": "38;5;100", 10954 "INTENSE_BLACK": "38;5;59", 10955 "INTENSE_BLUE": "38;5;60", 10956 "INTENSE_CYAN": "38;5;194", 10957 "INTENSE_GREEN": "38;5;102", 10958 "INTENSE_PURPLE": "38;5;188", 10959 "INTENSE_RED": "38;5;137", 10960 "INTENSE_WHITE": "38;5;224", 10961 "INTENSE_YELLOW": "38;5;188", 10962 "NO_COLOR": "0", 10963 "PURPLE": "38;5;90", 10964 "RED": "38;5;124", 10965 "WHITE": "38;5;145", 10966 "YELLOW": "38;5;100", 10967 } 10968 _ansi_expand_style(style) 10969 return style 10970 10971 10972def _vim_style(): 10973 style = { 10974 "BLACK": "38;5;18", 10975 "BLUE": "38;5;18", 10976 "CYAN": "38;5;44", 10977 "GREEN": "38;5;40", 10978 "INTENSE_BLACK": "38;5;60", 10979 "INTENSE_BLUE": "38;5;68", 10980 "INTENSE_CYAN": "38;5;44", 10981 "INTENSE_GREEN": "38;5;40", 10982 "INTENSE_PURPLE": "38;5;164", 10983 "INTENSE_RED": "38;5;09", 10984 "INTENSE_WHITE": "38;5;188", 10985 "INTENSE_YELLOW": "38;5;184", 10986 "NO_COLOR": "0", 10987 "PURPLE": "38;5;164", 10988 "RED": "38;5;160", 10989 "WHITE": "38;5;188", 10990 "YELLOW": "38;5;160", 10991 } 10992 _ansi_expand_style(style) 10993 return style 10994 10995 10996def _vs_style(): 10997 style = { 10998 "BLACK": "38;5;28", 10999 "BLUE": "38;5;21", 11000 "CYAN": "38;5;31", 11001 "GREEN": "38;5;28", 11002 "INTENSE_BLACK": "38;5;31", 11003 "INTENSE_BLUE": "38;5;31", 11004 "INTENSE_CYAN": "38;5;31", 11005 "INTENSE_GREEN": "38;5;31", 11006 "INTENSE_PURPLE": "38;5;31", 11007 "INTENSE_RED": "38;5;09", 11008 "INTENSE_WHITE": "38;5;31", 11009 "INTENSE_YELLOW": "38;5;31", 11010 "NO_COLOR": "0", 11011 "PURPLE": "38;5;124", 11012 "RED": "38;5;124", 11013 "WHITE": "38;5;31", 11014 "YELLOW": "38;5;124", 11015 } 11016 _ansi_expand_style(style) 11017 return style 11018 11019 11020def _xcode_style(): 11021 style = { 11022 "BLACK": "38;5;16", 11023 "BLUE": "38;5;20", 11024 "CYAN": "38;5;60", 11025 "GREEN": "38;5;28", 11026 "INTENSE_BLACK": "38;5;60", 11027 "INTENSE_BLUE": "38;5;20", 11028 "INTENSE_CYAN": "38;5;60", 11029 "INTENSE_GREEN": "38;5;60", 11030 "INTENSE_PURPLE": "38;5;126", 11031 "INTENSE_RED": "38;5;160", 11032 "INTENSE_WHITE": "38;5;60", 11033 "INTENSE_YELLOW": "38;5;94", 11034 "NO_COLOR": "0", 11035 "PURPLE": "38;5;126", 11036 "RED": "38;5;160", 11037 "WHITE": "38;5;60", 11038 "YELLOW": "38;5;94", 11039 } 11040 _ansi_expand_style(style) 11041 return style 11042 11043 11044ANSI_STYLES = LazyDict( 11045 { 11046 "algol": _algol_style, 11047 "algol_nu": _algol_nu_style, 11048 "autumn": _autumn_style, 11049 "borland": _borland_style, 11050 "bw": _bw_style, 11051 "colorful": _colorful_style, 11052 "default": _default_style, 11053 "emacs": _emacs_style, 11054 "friendly": _friendly_style, 11055 "fruity": _fruity_style, 11056 "igor": _igor_style, 11057 "lovelace": _lovelace_style, 11058 "manni": _manni_style, 11059 "monokai": _monokai_style, 11060 "murphy": _murphy_style, 11061 "native": _native_style, 11062 "paraiso-dark": _paraiso_dark_style, 11063 "paraiso-light": _paraiso_light_style, 11064 "pastie": _pastie_style, 11065 "perldoc": _perldoc_style, 11066 "rrt": _rrt_style, 11067 "tango": _tango_style, 11068 "trac": _trac_style, 11069 "vim": _vim_style, 11070 "vs": _vs_style, 11071 "xcode": _xcode_style, 11072 }, 11073 globals(), 11074 "ANSI_STYLES", 11075) 11076 11077del ( 11078 _algol_style, 11079 _algol_nu_style, 11080 _autumn_style, 11081 _borland_style, 11082 _bw_style, 11083 _colorful_style, 11084 _default_style, 11085 _emacs_style, 11086 _friendly_style, 11087 _fruity_style, 11088 _igor_style, 11089 _lovelace_style, 11090 _manni_style, 11091 _monokai_style, 11092 _murphy_style, 11093 _native_style, 11094 _paraiso_dark_style, 11095 _paraiso_light_style, 11096 _pastie_style, 11097 _perldoc_style, 11098 _rrt_style, 11099 _tango_style, 11100 _trac_style, 11101 _vim_style, 11102 _vs_style, 11103 _xcode_style, 11104) 11105 11106 11107# 11108# Dynamically generated styles 11109# 11110def make_ansi_style(palette): 11111 """Makes an ANSI color style from a color palette""" 11112 style = {"NO_COLOR": "0"} 11113 for name, t in BASE_XONSH_COLORS.items(): 11114 closest = find_closest_color(t, palette) 11115 if len(closest) == 3: 11116 closest = "".join([a * 2 for a in closest]) 11117 short = rgb2short(closest)[0] 11118 style[name] = "38;5;" + short 11119 style["BOLD_" + name] = "1;38;5;" + short 11120 style["UNDERLINE_" + name] = "4;38;5;" + short 11121 style["BOLD_UNDERLINE_" + name] = "1;4;38;5;" + short 11122 style["BACKGROUND_" + name] = "48;5;" + short 11123 return style 11124 11125 11126def ansi_style_by_name(name): 11127 """Gets or makes an ANSI color style by name. If the styles does not 11128 exist, it will look for a style using the pygments name. 11129 """ 11130 if name in ANSI_STYLES: 11131 return ANSI_STYLES[name] 11132 elif not HAS_PYGMENTS: 11133 raise KeyError("could not find style {0!r}".format(name)) 11134 from xonsh.pygments_cache import get_style_by_name 11135 11136 pstyle = get_style_by_name(name) 11137 palette = make_palette(pstyle.styles.values()) 11138 astyle = make_ansi_style(palette) 11139 ANSI_STYLES[name] = astyle 11140 return astyle 11141 11142# 11143# dirstack 11144# 11145# -*- coding: utf-8 -*- 11146"""Directory stack and associated utilities for the xonsh shell.""" 11147# amalgamated os 11148# amalgamated glob 11149# amalgamated argparse 11150# amalgamated builtins 11151# amalgamated subprocess 11152# amalgamated xonsh.lazyasd 11153# amalgamated xonsh.tools 11154# amalgamated xonsh.events 11155# amalgamated xonsh.platform 11156DIRSTACK = [] 11157"""A list containing the currently remembered directories.""" 11158_unc_tempDrives = {} 11159""" drive: sharePath for temp drive letters we create for UNC mapping""" 11160 11161 11162def _unc_check_enabled() -> bool: 11163 """Check whether CMD.EXE is enforcing no-UNC-as-working-directory check. 11164 11165 Check can be disabled by setting {HKCU, HKLM}/SOFTWARE\Microsoft\Command Processor\DisableUNCCheck:REG_DWORD=1 11166 11167 Returns: 11168 True if `CMD.EXE` is enforcing the check (default Windows situation) 11169 False if check is explicitly disabled. 11170 """ 11171 if not ON_WINDOWS: 11172 return 11173 11174 import winreg 11175 11176 wval = None 11177 11178 try: 11179 key = winreg.OpenKey( 11180 winreg.HKEY_CURRENT_USER, r"software\microsoft\command processor" 11181 ) 11182 wval, wtype = winreg.QueryValueEx(key, "DisableUNCCheck") 11183 winreg.CloseKey(key) 11184 except OSError as e: 11185 pass 11186 11187 if wval is None: 11188 try: 11189 key2 = winreg.OpenKey( 11190 winreg.HKEY_LOCAL_MACHINE, r"software\microsoft\command processor" 11191 ) 11192 wval, wtype = winreg.QueryValueEx(key2, "DisableUNCCheck") 11193 winreg.CloseKey(key2) 11194 except OSError as e: # NOQA 11195 pass 11196 11197 return False if wval else True 11198 11199 11200def _is_unc_path(some_path) -> bool: 11201 """True if path starts with 2 backward (or forward, due to python path hacking) slashes.""" 11202 return ( 11203 len(some_path) > 1 11204 and some_path[0] == some_path[1] 11205 and some_path[0] in (os.sep, os.altsep) 11206 ) 11207 11208 11209def _unc_map_temp_drive(unc_path) -> str: 11210 11211 """Map a new temporary drive letter for each distinct share, 11212 unless `CMD.EXE` is not insisting on non-UNC working directory. 11213 11214 Emulating behavior of `CMD.EXE` `pushd`, create a new mapped drive (starting from Z: towards A:, skipping existing 11215 drive letters) for each new UNC path user selects. 11216 11217 Args: 11218 unc_path: the path specified by user. Assumed to be a UNC path of form \\<server>\share... 11219 11220 Returns: 11221 a replacement for `unc_path` to be used as the actual new working directory. 11222 Note that the drive letter may be a the same as one already mapped if the server and share portion of `unc_path` 11223 is the same as one still active on the stack. 11224 """ 11225 global _unc_tempDrives 11226 assert unc_path[1] in (os.sep, os.altsep), "unc_path is UNC form of path" 11227 11228 if not _unc_check_enabled(): 11229 return unc_path 11230 else: 11231 unc_share, rem_path = os.path.splitdrive(unc_path) 11232 unc_share = unc_share.casefold() 11233 for d in _unc_tempDrives: 11234 if _unc_tempDrives[d] == unc_share: 11235 return os.path.join(d, rem_path) 11236 11237 for dord in range(ord("z"), ord("a"), -1): 11238 d = chr(dord) + ":" 11239 if not os.path.isdir(d): # find unused drive letter starting from z: 11240 subprocess.check_output( 11241 ["NET", "USE", d, unc_share], universal_newlines=True 11242 ) 11243 _unc_tempDrives[d] = unc_share 11244 return os.path.join(d, rem_path) 11245 11246 11247def _unc_unmap_temp_drive(left_drive, cwd): 11248 """Unmap a temporary drive letter if it is no longer needed. 11249 Called after popping `DIRSTACK` and changing to new working directory, so we need stack *and* 11250 new current working directory to be sure drive letter no longer needed. 11251 11252 Args: 11253 left_drive: driveletter (and colon) of working directory we just left 11254 cwd: full path of new current working directory 11255""" 11256 11257 global _unc_tempDrives 11258 11259 if left_drive not in _unc_tempDrives: # if not one we've mapped, don't unmap it 11260 return 11261 11262 for p in DIRSTACK + [cwd]: # if still in use , don't unmap it. 11263 if p.casefold().startswith(left_drive): 11264 return 11265 11266 _unc_tempDrives.pop(left_drive) 11267 subprocess.check_output( 11268 ["NET", "USE", left_drive, "/delete"], universal_newlines=True 11269 ) 11270 11271 11272events.doc( 11273 "on_chdir", 11274 """ 11275on_chdir(olddir: str, newdir: str) -> None 11276 11277Fires when the current directory is changed for any reason. 11278""", 11279) 11280 11281 11282def _get_cwd(): 11283 try: 11284 return os.getcwd() 11285 except (OSError, FileNotFoundError): 11286 return None 11287 11288 11289def _change_working_directory(newdir, follow_symlinks=False): 11290 env = builtins.__xonsh_env__ 11291 old = env["PWD"] 11292 new = os.path.join(old, newdir) 11293 absnew = os.path.abspath(new) 11294 11295 if follow_symlinks: 11296 absnew = os.path.realpath(absnew) 11297 11298 try: 11299 os.chdir(absnew) 11300 except (OSError, FileNotFoundError): 11301 if new.endswith(get_sep()): 11302 new = new[:-1] 11303 if os.path.basename(new) == "..": 11304 env["PWD"] = new 11305 else: 11306 if old is not None: 11307 env["OLDPWD"] = old 11308 if new is not None: 11309 env["PWD"] = absnew 11310 11311 # Fire event if the path actually changed 11312 if old != env["PWD"]: 11313 events.on_chdir.fire(olddir=old, newdir=env["PWD"]) 11314 11315 11316def _try_cdpath(apath): 11317 # NOTE: this CDPATH implementation differs from the bash one. 11318 # In bash if a CDPATH is set, an unqualified local folder 11319 # is considered after all CDPATHs, example: 11320 # CDPATH=$HOME/src (with src/xonsh/ inside) 11321 # $ cd xonsh -> src/xonsh (with xonsh/xonsh) 11322 # a second $ cd xonsh has no effects, to move in the nested xonsh 11323 # in bash a full $ cd ./xonsh is needed. 11324 # In xonsh a relative folder is always preferred. 11325 env = builtins.__xonsh_env__ 11326 cdpaths = env.get("CDPATH") 11327 for cdp in cdpaths: 11328 globber = builtins.__xonsh_expand_path__(os.path.join(cdp, apath)) 11329 for cdpath_prefixed_path in glob.iglob(globber): 11330 return cdpath_prefixed_path 11331 return apath 11332 11333 11334def cd(args, stdin=None): 11335 """Changes the directory. 11336 11337 If no directory is specified (i.e. if `args` is None) then this 11338 changes to the current user's home directory. 11339 """ 11340 env = builtins.__xonsh_env__ 11341 oldpwd = env.get("OLDPWD", None) 11342 cwd = env["PWD"] 11343 11344 follow_symlinks = False 11345 if len(args) > 0 and args[0] == "-P": 11346 follow_symlinks = True 11347 del args[0] 11348 11349 if len(args) == 0: 11350 d = os.path.expanduser("~") 11351 elif len(args) == 1: 11352 d = os.path.expanduser(args[0]) 11353 if not os.path.isdir(d): 11354 if d == "-": 11355 if oldpwd is not None: 11356 d = oldpwd 11357 else: 11358 return "", "cd: no previous directory stored\n", 1 11359 elif d.startswith("-"): 11360 try: 11361 num = int(d[1:]) 11362 except ValueError: 11363 return "", "cd: Invalid destination: {0}\n".format(d), 1 11364 if num == 0: 11365 return None, None, 0 11366 elif num < 0: 11367 return "", "cd: Invalid destination: {0}\n".format(d), 1 11368 elif num > len(DIRSTACK): 11369 e = "cd: Too few elements in dirstack ({0} elements)\n" 11370 return "", e.format(len(DIRSTACK)), 1 11371 else: 11372 d = DIRSTACK[num - 1] 11373 else: 11374 d = _try_cdpath(d) 11375 else: 11376 return ( 11377 "", 11378 ( 11379 "cd takes 0 or 1 arguments, not {0}. An additional `-P` " 11380 "flag can be passed in first position to follow symlinks." 11381 "\n".format(len(args)) 11382 ), 11383 1, 11384 ) 11385 if not os.path.exists(d): 11386 return "", "cd: no such file or directory: {0}\n".format(d), 1 11387 if not os.path.isdir(d): 11388 return "", "cd: {0} is not a directory\n".format(d), 1 11389 if not os.access(d, os.X_OK): 11390 return "", "cd: permission denied: {0}\n".format(d), 1 11391 if ( 11392 ON_WINDOWS 11393 and _is_unc_path(d) 11394 and _unc_check_enabled() 11395 and (not env.get("AUTO_PUSHD")) 11396 ): 11397 return ( 11398 "", 11399 "cd: can't cd to UNC path on Windows, unless $AUTO_PUSHD set or reg entry " 11400 + r"HKCU\SOFTWARE\MICROSOFT\Command Processor\DisableUNCCheck:DWORD = 1" 11401 + "\n", 11402 1, 11403 ) 11404 11405 # now, push the directory onto the dirstack if AUTO_PUSHD is set 11406 if cwd is not None and env.get("AUTO_PUSHD"): 11407 pushd(["-n", "-q", cwd]) 11408 if ON_WINDOWS and _is_unc_path(d): 11409 d = _unc_map_temp_drive(d) 11410 _change_working_directory(d, follow_symlinks) 11411 return None, None, 0 11412 11413 11414@lazyobject 11415def pushd_parser(): 11416 parser = argparse.ArgumentParser(prog="pushd") 11417 parser.add_argument("dir", nargs="?") 11418 parser.add_argument( 11419 "-n", 11420 dest="cd", 11421 help="Suppresses the normal change of directory when" 11422 " adding directories to the stack, so that only the" 11423 " stack is manipulated.", 11424 action="store_false", 11425 ) 11426 parser.add_argument( 11427 "-q", 11428 dest="quiet", 11429 help="Do not call dirs, regardless of $PUSHD_SILENT", 11430 action="store_true", 11431 ) 11432 return parser 11433 11434 11435def pushd(args, stdin=None): 11436 """xonsh command: pushd 11437 11438 Adds a directory to the top of the directory stack, or rotates the stack, 11439 making the new top of the stack the current working directory. 11440 11441 On Windows, if the path is a UNC path (begins with `\\<server>\<share>`) and if the `DisableUNCCheck` registry 11442 value is not enabled, creates a temporary mapped drive letter and sets the working directory there, emulating 11443 behavior of `PUSHD` in `CMD.EXE` 11444 """ 11445 global DIRSTACK 11446 11447 try: 11448 args = pushd_parser.parse_args(args) 11449 except SystemExit: 11450 return None, None, 1 11451 11452 env = builtins.__xonsh_env__ 11453 11454 pwd = env["PWD"] 11455 11456 if env.get("PUSHD_MINUS", False): 11457 BACKWARD = "-" 11458 FORWARD = "+" 11459 else: 11460 BACKWARD = "+" 11461 FORWARD = "-" 11462 11463 if args.dir is None: 11464 try: 11465 new_pwd = DIRSTACK.pop(0) 11466 except IndexError: 11467 e = "pushd: Directory stack is empty\n" 11468 return None, e, 1 11469 elif os.path.isdir(args.dir): 11470 new_pwd = args.dir 11471 else: 11472 try: 11473 num = int(args.dir[1:]) 11474 except ValueError: 11475 e = "Invalid argument to pushd: {0}\n" 11476 return None, e.format(args.dir), 1 11477 11478 if num < 0: 11479 e = "Invalid argument to pushd: {0}\n" 11480 return None, e.format(args.dir), 1 11481 11482 if num > len(DIRSTACK): 11483 e = "Too few elements in dirstack ({0} elements)\n" 11484 return None, e.format(len(DIRSTACK)), 1 11485 elif args.dir.startswith(FORWARD): 11486 if num == len(DIRSTACK): 11487 new_pwd = None 11488 else: 11489 new_pwd = DIRSTACK.pop(len(DIRSTACK) - 1 - num) 11490 elif args.dir.startswith(BACKWARD): 11491 if num == 0: 11492 new_pwd = None 11493 else: 11494 new_pwd = DIRSTACK.pop(num - 1) 11495 else: 11496 e = "Invalid argument to pushd: {0}\n" 11497 return None, e.format(args.dir), 1 11498 if new_pwd is not None: 11499 if ON_WINDOWS and _is_unc_path(new_pwd): 11500 new_pwd = _unc_map_temp_drive(new_pwd) 11501 if args.cd: 11502 DIRSTACK.insert(0, os.path.expanduser(pwd)) 11503 _change_working_directory(new_pwd) 11504 else: 11505 DIRSTACK.insert(0, os.path.expanduser(new_pwd)) 11506 11507 maxsize = env.get("DIRSTACK_SIZE") 11508 if len(DIRSTACK) > maxsize: 11509 DIRSTACK = DIRSTACK[:maxsize] 11510 11511 if not args.quiet and not env.get("PUSHD_SILENT"): 11512 return dirs([], None) 11513 11514 return None, None, 0 11515 11516 11517@lazyobject 11518def popd_parser(): 11519 parser = argparse.ArgumentParser(prog="popd") 11520 parser.add_argument("dir", nargs="?") 11521 parser.add_argument( 11522 "-n", 11523 dest="cd", 11524 help="Suppresses the normal change of directory when" 11525 " adding directories to the stack, so that only the" 11526 " stack is manipulated.", 11527 action="store_false", 11528 ) 11529 parser.add_argument( 11530 "-q", 11531 dest="quiet", 11532 help="Do not call dirs, regardless of $PUSHD_SILENT", 11533 action="store_true", 11534 ) 11535 return parser 11536 11537 11538def popd(args, stdin=None): 11539 """ 11540 xonsh command: popd 11541 11542 Removes entries from the directory stack. 11543 """ 11544 global DIRSTACK 11545 11546 try: 11547 args = pushd_parser.parse_args(args) 11548 except SystemExit: 11549 return None, None, 1 11550 11551 env = builtins.__xonsh_env__ 11552 11553 if env.get("PUSHD_MINUS"): 11554 BACKWARD = "-" 11555 FORWARD = "+" 11556 else: 11557 BACKWARD = "-" 11558 FORWARD = "+" 11559 11560 if args.dir is None: 11561 try: 11562 new_pwd = DIRSTACK.pop(0) 11563 except IndexError: 11564 e = "popd: Directory stack is empty\n" 11565 return None, e, 1 11566 else: 11567 try: 11568 num = int(args.dir[1:]) 11569 except ValueError: 11570 e = "Invalid argument to popd: {0}\n" 11571 return None, e.format(args.dir), 1 11572 11573 if num < 0: 11574 e = "Invalid argument to popd: {0}\n" 11575 return None, e.format(args.dir), 1 11576 11577 if num > len(DIRSTACK): 11578 e = "Too few elements in dirstack ({0} elements)\n" 11579 return None, e.format(len(DIRSTACK)), 1 11580 elif args.dir.startswith(FORWARD): 11581 if num == len(DIRSTACK): 11582 new_pwd = DIRSTACK.pop(0) 11583 else: 11584 new_pwd = None 11585 DIRSTACK.pop(len(DIRSTACK) - 1 - num) 11586 elif args.dir.startswith(BACKWARD): 11587 if num == 0: 11588 new_pwd = DIRSTACK.pop(0) 11589 else: 11590 new_pwd = None 11591 DIRSTACK.pop(num - 1) 11592 else: 11593 e = "Invalid argument to popd: {0}\n" 11594 return None, e.format(args.dir), 1 11595 11596 if new_pwd is not None: 11597 e = None 11598 if args.cd: 11599 env = builtins.__xonsh_env__ 11600 pwd = env["PWD"] 11601 11602 _change_working_directory(new_pwd) 11603 11604 if ON_WINDOWS: 11605 drive, rem_path = os.path.splitdrive(pwd) 11606 _unc_unmap_temp_drive(drive.casefold(), new_pwd) 11607 11608 if not args.quiet and not env.get("PUSHD_SILENT"): 11609 return dirs([], None) 11610 11611 return None, None, 0 11612 11613 11614@lazyobject 11615def dirs_parser(): 11616 parser = argparse.ArgumentParser(prog="dirs") 11617 parser.add_argument("N", nargs="?") 11618 parser.add_argument( 11619 "-c", 11620 dest="clear", 11621 help="Clears the directory stack by deleting all of" " the entries.", 11622 action="store_true", 11623 ) 11624 parser.add_argument( 11625 "-p", 11626 dest="print_long", 11627 help="Print the directory stack with one entry per" " line.", 11628 action="store_true", 11629 ) 11630 parser.add_argument( 11631 "-v", 11632 dest="verbose", 11633 help="Print the directory stack with one entry per" 11634 " line, prefixing each entry with its index in the" 11635 " stack.", 11636 action="store_true", 11637 ) 11638 parser.add_argument( 11639 "-l", 11640 dest="long", 11641 help="Produces a longer listing; the default listing" 11642 " format uses a tilde to denote the home directory.", 11643 action="store_true", 11644 ) 11645 return parser 11646 11647 11648def dirs(args, stdin=None): 11649 """xonsh command: dirs 11650 11651 Displays the list of currently remembered directories. Can also be used 11652 to clear the directory stack. 11653 """ 11654 global DIRSTACK 11655 try: 11656 args = dirs_parser.parse_args(args) 11657 except SystemExit: 11658 return None, None 11659 11660 env = builtins.__xonsh_env__ 11661 dirstack = [os.path.expanduser(env["PWD"])] + DIRSTACK 11662 11663 if env.get("PUSHD_MINUS"): 11664 BACKWARD = "-" 11665 FORWARD = "+" 11666 else: 11667 BACKWARD = "-" 11668 FORWARD = "+" 11669 11670 if args.clear: 11671 DIRSTACK = [] 11672 return None, None, 0 11673 11674 if args.long: 11675 o = dirstack 11676 else: 11677 d = os.path.expanduser("~") 11678 o = [i.replace(d, "~") for i in dirstack] 11679 11680 if args.verbose: 11681 out = "" 11682 pad = len(str(len(o) - 1)) 11683 for (ix, e) in enumerate(o): 11684 blanks = " " * (pad - len(str(ix))) 11685 out += "\n{0}{1} {2}".format(blanks, ix, e) 11686 out = out[1:] 11687 elif args.print_long: 11688 out = "\n".join(o) 11689 else: 11690 out = " ".join(o) 11691 11692 N = args.N 11693 if N is not None: 11694 try: 11695 num = int(N[1:]) 11696 except ValueError: 11697 e = "Invalid argument to dirs: {0}\n" 11698 return None, e.format(N), 1 11699 11700 if num < 0: 11701 e = "Invalid argument to dirs: {0}\n" 11702 return None, e.format(len(o)), 1 11703 11704 if num >= len(o): 11705 e = "Too few elements in dirstack ({0} elements)\n" 11706 return None, e.format(len(o)), 1 11707 11708 if N.startswith(BACKWARD): 11709 idx = num 11710 elif N.startswith(FORWARD): 11711 idx = len(o) - 1 - num 11712 else: 11713 e = "Invalid argument to dirs: {0}\n" 11714 return None, e.format(N), 1 11715 11716 out = o[idx] 11717 11718 return out + "\n", None, 0 11719 11720# 11721# proc 11722# 11723# -*- coding: utf-8 -*- 11724"""Interface for running Python functions as subprocess-mode commands. 11725 11726Code for several helper methods in the `ProcProxy` class have been reproduced 11727without modification from `subprocess.py` in the Python 3.4.2 standard library. 11728The contents of `subprocess.py` (and, thus, the reproduced methods) are 11729Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se> and were 11730licensed to the Python Software foundation under a Contributor Agreement. 11731""" 11732# amalgamated io 11733# amalgamated os 11734# amalgamated re 11735# amalgamated sys 11736# amalgamated time 11737queue = _LazyModule.load('queue', 'queue') 11738array = _LazyModule.load('array', 'array') 11739# amalgamated ctypes 11740# amalgamated signal 11741# amalgamated inspect 11742# amalgamated builtins 11743# amalgamated functools 11744# amalgamated threading 11745# amalgamated subprocess 11746# amalgamated collections.abc 11747# amalgamated xonsh.platform 11748# amalgamated xonsh.tools 11749# amalgamated xonsh.lazyasd 11750# amalgamated xonsh.jobs 11751# amalgamated xonsh.lazyimps 11752# amalgamated xonsh.tools 11753foreground = unthreadable 11754 11755 11756@lazyobject 11757def STDOUT_CAPTURE_KINDS(): 11758 return frozenset(["stdout", "object"]) 11759 11760 11761# The following escape codes are xterm codes. 11762# See http://rtfm.etla.org/xterm/ctlseq.html for more. 11763MODE_NUMS = ("1049", "47", "1047") 11764START_ALTERNATE_MODE = LazyObject( 11765 lambda: frozenset("\x1b[?{0}h".format(i).encode() for i in MODE_NUMS), 11766 globals(), 11767 "START_ALTERNATE_MODE", 11768) 11769END_ALTERNATE_MODE = LazyObject( 11770 lambda: frozenset("\x1b[?{0}l".format(i).encode() for i in MODE_NUMS), 11771 globals(), 11772 "END_ALTERNATE_MODE", 11773) 11774ALTERNATE_MODE_FLAGS = LazyObject( 11775 lambda: tuple(START_ALTERNATE_MODE) + tuple(END_ALTERNATE_MODE), 11776 globals(), 11777 "ALTERNATE_MODE_FLAGS", 11778) 11779RE_HIDDEN_BYTES = LazyObject( 11780 lambda: re.compile(b"(\001.*?\002)"), globals(), "RE_HIDDEN" 11781) 11782 11783 11784@lazyobject 11785def RE_VT100_ESCAPE(): 11786 return re.compile(b"(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]") 11787 11788 11789@lazyobject 11790def RE_HIDE_ESCAPE(): 11791 return re.compile( 11792 b"(" + RE_HIDDEN_BYTES.pattern + b"|" + RE_VT100_ESCAPE.pattern + b")" 11793 ) 11794 11795 11796class QueueReader: 11797 """Provides a file-like interface to reading from a queue.""" 11798 11799 def __init__(self, fd, timeout=None): 11800 """ 11801 Parameters 11802 ---------- 11803 fd : int 11804 A file descriptor 11805 timeout : float or None, optional 11806 The queue reading timeout. 11807 """ 11808 self.fd = fd 11809 self.timeout = timeout 11810 self.closed = False 11811 self.queue = queue.Queue() 11812 self.thread = None 11813 11814 def close(self): 11815 """close the reader""" 11816 self.closed = True 11817 11818 def is_fully_read(self): 11819 """Returns whether or not the queue is fully read and the reader is 11820 closed. 11821 """ 11822 return ( 11823 self.closed 11824 and (self.thread is None or not self.thread.is_alive()) 11825 and self.queue.empty() 11826 ) 11827 11828 def read_queue(self): 11829 """Reads a single chunk from the queue. This is blocking if 11830 the timeout is None and non-blocking otherwise. 11831 """ 11832 try: 11833 return self.queue.get(block=True, timeout=self.timeout) 11834 except queue.Empty: 11835 return b"" 11836 11837 def read(self, size=-1): 11838 """Reads bytes from the file.""" 11839 i = 0 11840 buf = b"" 11841 while size < 0 or i != size: 11842 line = self.read_queue() 11843 if line: 11844 buf += line 11845 else: 11846 break 11847 i += len(line) 11848 return buf 11849 11850 def readline(self, size=-1): 11851 """Reads a line, or a partial line from the file descriptor.""" 11852 i = 0 11853 nl = b"\n" 11854 buf = b"" 11855 while size < 0 or i != size: 11856 line = self.read_queue() 11857 if line: 11858 buf += line 11859 if line.endswith(nl): 11860 break 11861 else: 11862 break 11863 i += len(line) 11864 return buf 11865 11866 def _read_all_lines(self): 11867 """This reads all remaining lines in a blocking fashion.""" 11868 lines = [] 11869 while not self.is_fully_read(): 11870 chunk = self.read_queue() 11871 lines.extend(chunk.splitlines(keepends=True)) 11872 return lines 11873 11874 def readlines(self, hint=-1): 11875 """Reads lines from the file descriptor. This is blocking for negative 11876 hints (i.e. read all the remaining lines) and non-blocking otherwise. 11877 """ 11878 if hint == -1: 11879 return self._read_all_lines() 11880 lines = [] 11881 while len(lines) != hint: 11882 chunk = self.read_queue() 11883 if not chunk: 11884 break 11885 lines.extend(chunk.splitlines(keepends=True)) 11886 return lines 11887 11888 def fileno(self): 11889 """Returns the file descriptor number.""" 11890 return self.fd 11891 11892 @staticmethod 11893 def readable(): 11894 """Returns true, because this object is always readable.""" 11895 return True 11896 11897 def iterqueue(self): 11898 """Iterates through all remaining chunks in a blocking fashion.""" 11899 while not self.is_fully_read(): 11900 chunk = self.read_queue() 11901 if not chunk: 11902 continue 11903 yield chunk 11904 11905 11906def populate_fd_queue(reader, fd, queue): 11907 """Reads 1 kb of data from a file descriptor into a queue. 11908 If this ends or fails, it flags the calling reader object as closed. 11909 """ 11910 while True: 11911 try: 11912 c = os.read(fd, 1024) 11913 except OSError: 11914 reader.closed = True 11915 break 11916 if c: 11917 queue.put(c) 11918 else: 11919 reader.closed = True 11920 break 11921 11922 11923class NonBlockingFDReader(QueueReader): 11924 """A class for reading characters from a file descriptor on a background 11925 thread. This has the advantages that the calling thread can close the 11926 file and that the reading does not block the calling thread. 11927 """ 11928 11929 def __init__(self, fd, timeout=None): 11930 """ 11931 Parameters 11932 ---------- 11933 fd : int 11934 A file descriptor 11935 timeout : float or None, optional 11936 The queue reading timeout. 11937 """ 11938 super().__init__(fd, timeout=timeout) 11939 # start reading from stream 11940 self.thread = threading.Thread( 11941 target=populate_fd_queue, args=(self, self.fd, self.queue) 11942 ) 11943 self.thread.daemon = True 11944 self.thread.start() 11945 11946 11947def populate_buffer(reader, fd, buffer, chunksize): 11948 """Reads bytes from the file descriptor and copies them into a buffer. 11949 11950 The reads happen in parallel using the pread() syscall; which is only 11951 available on POSIX systems. If the read fails for any reason, the reader is 11952 flagged as closed. 11953 """ 11954 offset = 0 11955 while True: 11956 try: 11957 buf = os.pread(fd, chunksize, offset) 11958 except OSError: 11959 reader.closed = True 11960 break 11961 if buf: 11962 buffer.write(buf) 11963 offset += len(buf) 11964 else: 11965 reader.closed = True 11966 break 11967 11968 11969class BufferedFDParallelReader: 11970 """Buffered, parallel background thread reader.""" 11971 11972 def __init__(self, fd, buffer=None, chunksize=1024): 11973 """ 11974 Parameters 11975 ---------- 11976 fd : int 11977 File descriptor from which to read. 11978 buffer : binary file-like or None, optional 11979 A buffer to write bytes into. If None, a new BytesIO object 11980 is created. 11981 chunksize : int, optional 11982 The max size of the parallel reads, default 1 kb. 11983 """ 11984 self.fd = fd 11985 self.buffer = io.BytesIO() if buffer is None else buffer 11986 self.chunksize = chunksize 11987 self.closed = False 11988 # start reading from stream 11989 self.thread = threading.Thread( 11990 target=populate_buffer, args=(self, fd, self.buffer, chunksize) 11991 ) 11992 self.thread.daemon = True 11993 11994 self.thread.start() 11995 11996 11997def _expand_console_buffer(cols, max_offset, expandsize, orig_posize, fd): 11998 # if we are getting close to the end of the console buffer, 11999 # expand it so that we can read from it successfully. 12000 if cols == 0: 12001 return orig_posize[-1], max_offset, orig_posize 12002 rows = ((max_offset + expandsize) // cols) + 1 12003 winutils.set_console_screen_buffer_size(cols, rows, fd=fd) 12004 orig_posize = orig_posize[:3] + (rows,) 12005 max_offset = (rows - 1) * cols 12006 return rows, max_offset, orig_posize 12007 12008 12009def populate_console(reader, fd, buffer, chunksize, queue, expandsize=None): 12010 """Reads bytes from the file descriptor and puts lines into the queue. 12011 The reads happened in parallel, 12012 using xonsh.winutils.read_console_output_character(), 12013 and is thus only available on windows. If the read fails for any reason, 12014 the reader is flagged as closed. 12015 """ 12016 # OK, so this function is super annoying because Windows stores its 12017 # buffers as a 2D regular, dense array -- without trailing newlines. 12018 # Meanwhile, we want to add *lines* to the queue. Also, as is typical 12019 # with parallel reads, the entire buffer that you ask for may not be 12020 # filled. Thus we have to deal with the full generality. 12021 # 1. reads may end in the middle of a line 12022 # 2. excess whitespace at the end of a line may not be real, unless 12023 # 3. you haven't read to the end of the line yet! 12024 # So there are alignment issues everywhere. Also, Windows will automatically 12025 # read past the current cursor position, even though there is presumably 12026 # nothing to see there. 12027 # 12028 # These chunked reads basically need to happen like this because, 12029 # a. The default buffer size is HUGE for the console (90k lines x 120 cols) 12030 # as so we can't just read in everything at the end and see what we 12031 # care about without a noticeable performance hit. 12032 # b. Even with this huge size, it is still possible to write more lines than 12033 # this, so we should scroll along with the console. 12034 # Unfortunately, because we do not have control over the terminal emulator, 12035 # It is not possible to compute how far back we should set the beginning 12036 # read position because we don't know how many characters have been popped 12037 # off the top of the buffer. If we did somehow know this number we could do 12038 # something like the following: 12039 # 12040 # new_offset = (y*cols) + x 12041 # if new_offset == max_offset: 12042 # new_offset -= scrolled_offset 12043 # x = new_offset%cols 12044 # y = new_offset//cols 12045 # continue 12046 # 12047 # So this method is imperfect and only works as long as the screen has 12048 # room to expand to. Thus the trick here is to expand the screen size 12049 # when we get close enough to the end of the screen. There remain some 12050 # async issues related to not being able to set the cursor position. 12051 # but they just affect the alignment / capture of the output of the 12052 # first command run after a screen resize. 12053 if expandsize is None: 12054 expandsize = 100 * chunksize 12055 x, y, cols, rows = posize = winutils.get_position_size(fd) 12056 pre_x = pre_y = -1 12057 orig_posize = posize 12058 offset = (cols * y) + x 12059 max_offset = (rows - 1) * cols 12060 # I believe that there is a bug in PTK that if we reset the 12061 # cursor position, the cursor on the next prompt is accidentally on 12062 # the next line. If this is fixed, uncomment the following line. 12063 # if max_offset < offset + expandsize: 12064 # rows, max_offset, orig_posize = _expand_console_buffer( 12065 # cols, max_offset, expandsize, 12066 # orig_posize, fd) 12067 # winutils.set_console_cursor_position(x, y, fd=fd) 12068 while True: 12069 posize = winutils.get_position_size(fd) 12070 offset = (cols * y) + x 12071 if ((posize[1], posize[0]) <= (y, x) and posize[2:] == (cols, rows)) or ( 12072 pre_x == x and pre_y == y 12073 ): 12074 # already at or ahead of the current cursor position. 12075 if reader.closed: 12076 break 12077 else: 12078 time.sleep(reader.timeout) 12079 continue 12080 elif max_offset <= offset + expandsize: 12081 ecb = _expand_console_buffer(cols, max_offset, expandsize, orig_posize, fd) 12082 rows, max_offset, orig_posize = ecb 12083 continue 12084 elif posize[2:] == (cols, rows): 12085 # cursor updated but screen size is the same. 12086 pass 12087 else: 12088 # screen size changed, which is offset preserving 12089 orig_posize = posize 12090 cols, rows = posize[2:] 12091 x = offset % cols 12092 y = offset // cols 12093 pre_x = pre_y = -1 12094 max_offset = (rows - 1) * cols 12095 continue 12096 try: 12097 buf = winutils.read_console_output_character( 12098 x=x, y=y, fd=fd, buf=buffer, bufsize=chunksize, raw=True 12099 ) 12100 except (OSError, IOError): 12101 reader.closed = True 12102 break 12103 # cursor position and offset 12104 if not reader.closed: 12105 buf = buf.rstrip() 12106 nread = len(buf) 12107 if nread == 0: 12108 time.sleep(reader.timeout) 12109 continue 12110 cur_x, cur_y = posize[0], posize[1] 12111 cur_offset = (cols * cur_y) + cur_x 12112 beg_offset = (cols * y) + x 12113 end_offset = beg_offset + nread 12114 if end_offset > cur_offset and cur_offset != max_offset: 12115 buf = buf[: cur_offset - end_offset] 12116 # convert to lines 12117 xshift = cols - x 12118 yshift = (nread // cols) + (1 if nread % cols > 0 else 0) 12119 lines = [buf[:xshift]] 12120 lines += [ 12121 buf[l * cols + xshift : (l + 1) * cols + xshift] for l in range(yshift) 12122 ] 12123 lines = [line for line in lines if line] 12124 if not lines: 12125 time.sleep(reader.timeout) 12126 continue 12127 # put lines in the queue 12128 nl = b"\n" 12129 for line in lines[:-1]: 12130 queue.put(line.rstrip() + nl) 12131 if len(lines[-1]) == xshift: 12132 queue.put(lines[-1].rstrip() + nl) 12133 else: 12134 queue.put(lines[-1]) 12135 # update x and y locations 12136 if (beg_offset + len(buf)) % cols == 0: 12137 new_offset = beg_offset + len(buf) 12138 else: 12139 new_offset = beg_offset + len(buf.rstrip()) 12140 pre_x = x 12141 pre_y = y 12142 x = new_offset % cols 12143 y = new_offset // cols 12144 time.sleep(reader.timeout) 12145 12146 12147class ConsoleParallelReader(QueueReader): 12148 """Parallel reader for consoles that runs in a background thread. 12149 This is only needed, available, and useful on Windows. 12150 """ 12151 12152 def __init__(self, fd, buffer=None, chunksize=1024, timeout=None): 12153 """ 12154 Parameters 12155 ---------- 12156 fd : int 12157 Standard buffer file descriptor, 0 for stdin, 1 for stdout (default), 12158 and 2 for stderr. 12159 buffer : ctypes.c_wchar_p, optional 12160 An existing buffer to (re-)use. 12161 chunksize : int, optional 12162 The max size of the parallel reads, default 1 kb. 12163 timeout : float, optional 12164 The queue reading timeout. 12165 """ 12166 timeout = timeout or builtins.__xonsh_env__.get("XONSH_PROC_FREQUENCY") 12167 super().__init__(fd, timeout=timeout) 12168 self._buffer = buffer # this cannot be public 12169 if buffer is None: 12170 self._buffer = ctypes.c_char_p(b" " * chunksize) 12171 self.chunksize = chunksize 12172 # start reading from stream 12173 self.thread = threading.Thread( 12174 target=populate_console, 12175 args=(self, fd, self._buffer, chunksize, self.queue), 12176 ) 12177 self.thread.daemon = True 12178 self.thread.start() 12179 12180 12181def safe_fdclose(handle, cache=None): 12182 """Closes a file handle in the safest way possible, and potentially 12183 storing the result. 12184 """ 12185 if cache is not None and cache.get(handle, False): 12186 return 12187 status = True 12188 if handle is None: 12189 pass 12190 elif isinstance(handle, int): 12191 if handle >= 3: 12192 # don't close stdin, stdout, stderr, -1 12193 try: 12194 os.close(handle) 12195 except OSError: 12196 status = False 12197 elif handle is sys.stdin or handle is sys.stdout or handle is sys.stderr: 12198 # don't close stdin, stdout, or stderr 12199 pass 12200 else: 12201 try: 12202 handle.close() 12203 except OSError: 12204 status = False 12205 if cache is not None: 12206 cache[handle] = status 12207 12208 12209def safe_flush(handle): 12210 """Attempts to safely flush a file handle, returns success bool.""" 12211 status = True 12212 try: 12213 handle.flush() 12214 except OSError: 12215 status = False 12216 return status 12217 12218 12219def still_writable(fd): 12220 """Determines whether a file descriptor is still writable by trying to 12221 write an empty string and seeing if it fails. 12222 """ 12223 try: 12224 os.write(fd, b"") 12225 status = True 12226 except OSError: 12227 status = False 12228 return status 12229 12230 12231class PopenThread(threading.Thread): 12232 """A thread for running and managing subprocess. This allows reading 12233 from the stdin, stdout, and stderr streams in a non-blocking fashion. 12234 12235 This takes the same arguments and keyword arguments as regular Popen. 12236 This requires that the captured_stdout and captured_stderr attributes 12237 to be set following instantiation. 12238 """ 12239 12240 def __init__(self, *args, stdin=None, stdout=None, stderr=None, **kwargs): 12241 super().__init__() 12242 self.lock = threading.RLock() 12243 env = builtins.__xonsh_env__ 12244 # stdin setup 12245 self.orig_stdin = stdin 12246 if stdin is None: 12247 self.stdin_fd = 0 12248 elif isinstance(stdin, int): 12249 self.stdin_fd = stdin 12250 else: 12251 self.stdin_fd = stdin.fileno() 12252 self.store_stdin = env.get("XONSH_STORE_STDIN") 12253 self.timeout = env.get("XONSH_PROC_FREQUENCY") 12254 self.in_alt_mode = False 12255 self.stdin_mode = None 12256 # stdout setup 12257 self.orig_stdout = stdout 12258 self.stdout_fd = 1 if stdout is None else stdout.fileno() 12259 self._set_pty_size() 12260 # stderr setup 12261 self.orig_stderr = stderr 12262 # Set some signal handles, if we can. Must come before process 12263 # is started to prevent deadlock on windows 12264 self.proc = None # has to be here for closure for handles 12265 self.old_int_handler = self.old_winch_handler = None 12266 self.old_tstp_handler = self.old_quit_handler = None 12267 if on_main_thread(): 12268 self.old_int_handler = signal.signal(signal.SIGINT, self._signal_int) 12269 if ON_POSIX: 12270 self.old_tstp_handler = signal.signal(signal.SIGTSTP, self._signal_tstp) 12271 self.old_quit_handler = signal.signal(signal.SIGQUIT, self._signal_quit) 12272 if CAN_RESIZE_WINDOW: 12273 self.old_winch_handler = signal.signal( 12274 signal.SIGWINCH, self._signal_winch 12275 ) 12276 # start up process 12277 if ON_WINDOWS and stdout is not None: 12278 os.set_inheritable(stdout.fileno(), False) 12279 12280 try: 12281 self.proc = proc = subprocess.Popen( 12282 *args, stdin=stdin, stdout=stdout, stderr=stderr, **kwargs 12283 ) 12284 except Exception: 12285 self._clean_up() 12286 raise 12287 12288 self.pid = proc.pid 12289 self.universal_newlines = uninew = proc.universal_newlines 12290 if uninew: 12291 self.encoding = enc = env.get("XONSH_ENCODING") 12292 self.encoding_errors = err = env.get("XONSH_ENCODING_ERRORS") 12293 self.stdin = io.BytesIO() # stdin is always bytes! 12294 self.stdout = io.TextIOWrapper(io.BytesIO(), encoding=enc, errors=err) 12295 self.stderr = io.TextIOWrapper(io.BytesIO(), encoding=enc, errors=err) 12296 else: 12297 self.encoding = self.encoding_errors = None 12298 self.stdin = io.BytesIO() 12299 self.stdout = io.BytesIO() 12300 self.stderr = io.BytesIO() 12301 self.suspended = False 12302 self.prevs_are_closed = False 12303 self.start() 12304 12305 def run(self): 12306 """Runs the subprocess by performing a parallel read on stdin if allowed, 12307 and copying bytes from captured_stdout to stdout and bytes from 12308 captured_stderr to stderr. 12309 """ 12310 proc = self.proc 12311 spec = self._wait_and_getattr("spec") 12312 # get stdin and apply parallel reader if needed. 12313 stdin = self.stdin 12314 if self.orig_stdin is None: 12315 origin = None 12316 elif ON_POSIX and self.store_stdin: 12317 origin = self.orig_stdin 12318 origfd = origin if isinstance(origin, int) else origin.fileno() 12319 origin = BufferedFDParallelReader(origfd, buffer=stdin) 12320 else: 12321 origin = None 12322 # get non-blocking stdout 12323 stdout = self.stdout.buffer if self.universal_newlines else self.stdout 12324 capout = spec.captured_stdout 12325 if capout is None: 12326 procout = None 12327 else: 12328 procout = NonBlockingFDReader(capout.fileno(), timeout=self.timeout) 12329 # get non-blocking stderr 12330 stderr = self.stderr.buffer if self.universal_newlines else self.stderr 12331 caperr = spec.captured_stderr 12332 if caperr is None: 12333 procerr = None 12334 else: 12335 procerr = NonBlockingFDReader(caperr.fileno(), timeout=self.timeout) 12336 # initial read from buffer 12337 self._read_write(procout, stdout, sys.__stdout__) 12338 self._read_write(procerr, stderr, sys.__stderr__) 12339 # loop over reads while process is running. 12340 i = j = cnt = 1 12341 while proc.poll() is None: 12342 # this is here for CPU performance reasons. 12343 if i + j == 0: 12344 cnt = min(cnt + 1, 1000) 12345 tout = self.timeout * cnt 12346 if procout is not None: 12347 procout.timeout = tout 12348 if procerr is not None: 12349 procerr.timeout = tout 12350 elif cnt == 1: 12351 pass 12352 else: 12353 cnt = 1 12354 if procout is not None: 12355 procout.timeout = self.timeout 12356 if procerr is not None: 12357 procerr.timeout = self.timeout 12358 # redirect some output! 12359 i = self._read_write(procout, stdout, sys.__stdout__) 12360 j = self._read_write(procerr, stderr, sys.__stderr__) 12361 if self.suspended: 12362 break 12363 if self.suspended: 12364 return 12365 # close files to send EOF to non-blocking reader. 12366 # capout & caperr seem to be needed only by Windows, while 12367 # orig_stdout & orig_stderr are need by posix and Windows. 12368 # Also, order seems to matter here, 12369 # with orig_* needed to be closed before cap* 12370 safe_fdclose(self.orig_stdout) 12371 safe_fdclose(self.orig_stderr) 12372 if ON_WINDOWS: 12373 safe_fdclose(capout) 12374 safe_fdclose(caperr) 12375 # read in the remaining data in a blocking fashion. 12376 while (procout is not None and not procout.is_fully_read()) or ( 12377 procerr is not None and not procerr.is_fully_read() 12378 ): 12379 self._read_write(procout, stdout, sys.__stdout__) 12380 self._read_write(procerr, stderr, sys.__stderr__) 12381 # kill the process if it is still alive. Happens when piping. 12382 if proc.poll() is None: 12383 proc.terminate() 12384 12385 def _wait_and_getattr(self, name): 12386 """make sure the instance has a certain attr, and return it.""" 12387 while not hasattr(self, name): 12388 time.sleep(1e-7) 12389 return getattr(self, name) 12390 12391 def _read_write(self, reader, writer, stdbuf): 12392 """Reads a chunk of bytes from a buffer and write into memory or back 12393 down to the standard buffer, as appropriate. Returns the number of 12394 successful reads. 12395 """ 12396 if reader is None: 12397 return 0 12398 i = -1 12399 for i, chunk in enumerate(iter(reader.read_queue, b"")): 12400 self._alt_mode_switch(chunk, writer, stdbuf) 12401 if i >= 0: 12402 writer.flush() 12403 stdbuf.flush() 12404 return i + 1 12405 12406 def _alt_mode_switch(self, chunk, membuf, stdbuf): 12407 """Enables recursively switching between normal capturing mode 12408 and 'alt' mode, which passes through values to the standard 12409 buffer. Pagers, text editors, curses applications, etc. use 12410 alternate mode. 12411 """ 12412 i, flag = findfirst(chunk, ALTERNATE_MODE_FLAGS) 12413 if flag is None: 12414 self._alt_mode_writer(chunk, membuf, stdbuf) 12415 else: 12416 # This code is executed when the child process switches the 12417 # terminal into or out of alternate mode. The line below assumes 12418 # that the user has opened vim, less, or similar, and writes writes 12419 # to stdin. 12420 j = i + len(flag) 12421 # write the first part of the chunk in the current mode. 12422 self._alt_mode_writer(chunk[:i], membuf, stdbuf) 12423 # switch modes 12424 # write the flag itself the current mode where alt mode is on 12425 # so that it is streamed to the terminal ASAP. 12426 # this is needed for terminal emulators to find the correct 12427 # positions before and after alt mode. 12428 alt_mode = flag in START_ALTERNATE_MODE 12429 if alt_mode: 12430 self.in_alt_mode = alt_mode 12431 self._alt_mode_writer(flag, membuf, stdbuf) 12432 self._enable_cbreak_stdin() 12433 else: 12434 self._alt_mode_writer(flag, membuf, stdbuf) 12435 self.in_alt_mode = alt_mode 12436 self._disable_cbreak_stdin() 12437 # recurse this function, but without the current flag. 12438 self._alt_mode_switch(chunk[j:], membuf, stdbuf) 12439 12440 def _alt_mode_writer(self, chunk, membuf, stdbuf): 12441 """Write bytes to the standard buffer if in alt mode or otherwise 12442 to the in-memory buffer. 12443 """ 12444 if not chunk: 12445 pass # don't write empty values 12446 elif self.in_alt_mode: 12447 stdbuf.buffer.write(chunk) 12448 else: 12449 with self.lock: 12450 p = membuf.tell() 12451 membuf.seek(0, io.SEEK_END) 12452 membuf.write(chunk) 12453 membuf.seek(p) 12454 12455 # 12456 # Window resize handlers 12457 # 12458 12459 def _signal_winch(self, signum, frame): 12460 """Signal handler for SIGWINCH - window size has changed.""" 12461 self.send_signal(signal.SIGWINCH) 12462 self._set_pty_size() 12463 12464 def _set_pty_size(self): 12465 """Sets the window size of the child pty based on the window size of 12466 our own controlling terminal. 12467 """ 12468 if ON_WINDOWS or not os.isatty(self.stdout_fd): 12469 return 12470 # Get the terminal size of the real terminal, set it on the 12471 # pseudoterminal. 12472 buf = array.array("h", [0, 0, 0, 0]) 12473 # 1 = stdout here 12474 try: 12475 fcntl.ioctl(1, termios.TIOCGWINSZ, buf, True) 12476 fcntl.ioctl(self.stdout_fd, termios.TIOCSWINSZ, buf) 12477 except OSError: 12478 pass 12479 12480 # 12481 # SIGINT handler 12482 # 12483 12484 def _signal_int(self, signum, frame): 12485 """Signal handler for SIGINT - Ctrl+C may have been pressed.""" 12486 self.send_signal(signum) 12487 if self.proc is not None and self.proc.poll() is not None: 12488 self._restore_sigint(frame=frame) 12489 if on_main_thread(): 12490 signal.pthread_kill(threading.get_ident(), signal.SIGINT) 12491 12492 def _restore_sigint(self, frame=None): 12493 old = self.old_int_handler 12494 if old is not None: 12495 if on_main_thread(): 12496 signal.signal(signal.SIGINT, old) 12497 self.old_int_handler = None 12498 if frame is not None: 12499 self._disable_cbreak_stdin() 12500 if old is not None and old is not self._signal_int: 12501 old(signal.SIGINT, frame) 12502 12503 # 12504 # SIGTSTP handler 12505 # 12506 12507 def _signal_tstp(self, signum, frame): 12508 """Signal handler for suspending SIGTSTP - Ctrl+Z may have been pressed. 12509 """ 12510 self.suspended = True 12511 self.send_signal(signum) 12512 self._restore_sigtstp(frame=frame) 12513 12514 def _restore_sigtstp(self, frame=None): 12515 old = self.old_tstp_handler 12516 if old is not None: 12517 if on_main_thread(): 12518 signal.signal(signal.SIGTSTP, old) 12519 self.old_tstp_handler = None 12520 if frame is not None: 12521 self._disable_cbreak_stdin() 12522 12523 # 12524 # SIGQUIT handler 12525 # 12526 12527 def _signal_quit(self, signum, frame): 12528 """Signal handler for quiting SIGQUIT - Ctrl+\ may have been pressed. 12529 """ 12530 self.send_signal(signum) 12531 self._restore_sigquit(frame=frame) 12532 12533 def _restore_sigquit(self, frame=None): 12534 old = self.old_quit_handler 12535 if old is not None: 12536 if on_main_thread(): 12537 signal.signal(signal.SIGQUIT, old) 12538 self.old_quit_handler = None 12539 if frame is not None: 12540 self._disable_cbreak_stdin() 12541 12542 # 12543 # cbreak mode handlers 12544 # 12545 12546 def _enable_cbreak_stdin(self): 12547 if not ON_POSIX: 12548 return 12549 try: 12550 self.stdin_mode = termios.tcgetattr(self.stdin_fd)[:] 12551 except termios.error: 12552 # this can happen for cases where another process is controlling 12553 # xonsh's tty device, such as in testing. 12554 self.stdin_mode = None 12555 return 12556 new = self.stdin_mode[:] 12557 new[LFLAG] &= ~(termios.ECHO | termios.ICANON) 12558 new[CC][termios.VMIN] = 1 12559 new[CC][termios.VTIME] = 0 12560 try: 12561 # termios.TCSAFLUSH may be less reliable than termios.TCSANOW 12562 termios.tcsetattr(self.stdin_fd, termios.TCSANOW, new) 12563 except termios.error: 12564 self._disable_cbreak_stdin() 12565 12566 def _disable_cbreak_stdin(self): 12567 if not ON_POSIX or self.stdin_mode is None: 12568 return 12569 new = self.stdin_mode[:] 12570 new[LFLAG] |= termios.ECHO | termios.ICANON 12571 new[CC][termios.VMIN] = 1 12572 new[CC][termios.VTIME] = 0 12573 try: 12574 termios.tcsetattr(self.stdin_fd, termios.TCSANOW, new) 12575 except termios.error: 12576 pass 12577 12578 # 12579 # Dispatch methods 12580 # 12581 12582 def poll(self): 12583 """Dispatches to Popen.returncode.""" 12584 return self.proc.returncode 12585 12586 def wait(self, timeout=None): 12587 """Dispatches to Popen.wait(), but also does process cleanup such as 12588 joining this thread and replacing the original window size signal 12589 handler. 12590 """ 12591 self._disable_cbreak_stdin() 12592 rtn = self.proc.wait(timeout=timeout) 12593 self.join() 12594 # need to replace the old signal handlers somewhere... 12595 if self.old_winch_handler is not None and on_main_thread(): 12596 signal.signal(signal.SIGWINCH, self.old_winch_handler) 12597 self.old_winch_handler = None 12598 self._clean_up() 12599 return rtn 12600 12601 def _clean_up(self): 12602 self._restore_sigint() 12603 self._restore_sigtstp() 12604 self._restore_sigquit() 12605 12606 @property 12607 def returncode(self): 12608 """Process return code.""" 12609 return self.proc.returncode 12610 12611 @returncode.setter 12612 def returncode(self, value): 12613 """Process return code.""" 12614 self.proc.returncode = value 12615 12616 @property 12617 def signal(self): 12618 """Process signal, or None.""" 12619 s = getattr(self.proc, "signal", None) 12620 if s is None: 12621 rtn = self.returncode 12622 if rtn is not None and rtn != 0: 12623 s = (-1 * rtn, rtn < 0 if ON_WINDOWS else os.WCOREDUMP(rtn)) 12624 return s 12625 12626 @signal.setter 12627 def signal(self, value): 12628 """Process signal, or None.""" 12629 self.proc.signal = value 12630 12631 def send_signal(self, signal): 12632 """Dispatches to Popen.send_signal().""" 12633 dt = 0.0 12634 while self.proc is None and dt < self.timeout: 12635 time.sleep(1e-7) 12636 dt += 1e-7 12637 if self.proc is None: 12638 return 12639 try: 12640 rtn = self.proc.send_signal(signal) 12641 except ProcessLookupError: 12642 # This can happen in the case of !(cmd) when the command has ended 12643 rtn = None 12644 return rtn 12645 12646 def terminate(self): 12647 """Dispatches to Popen.terminate().""" 12648 return self.proc.terminate() 12649 12650 def kill(self): 12651 """Dispatches to Popen.kill().""" 12652 return self.proc.kill() 12653 12654 12655class Handle(int): 12656 closed = False 12657 12658 def Close(self, CloseHandle=None): 12659 CloseHandle = CloseHandle or _winapi.CloseHandle 12660 if not self.closed: 12661 self.closed = True 12662 CloseHandle(self) 12663 12664 def Detach(self): 12665 if not self.closed: 12666 self.closed = True 12667 return int(self) 12668 raise ValueError("already closed") 12669 12670 def __repr__(self): 12671 return "Handle(%d)" % int(self) 12672 12673 __del__ = Close 12674 __str__ = __repr__ 12675 12676 12677class FileThreadDispatcher: 12678 """Dispatches to different file handles depending on the 12679 current thread. Useful if you want file operation to go to different 12680 places for different threads. 12681 """ 12682 12683 def __init__(self, default=None): 12684 """ 12685 Parameters 12686 ---------- 12687 default : file-like or None, optional 12688 The file handle to write to if a thread cannot be found in 12689 the registry. If None, a new in-memory instance. 12690 12691 Attributes 12692 ---------- 12693 registry : dict 12694 Maps thread idents to file handles. 12695 """ 12696 if default is None: 12697 default = io.TextIOWrapper(io.BytesIO()) 12698 self.default = default 12699 self.registry = {} 12700 12701 def register(self, handle): 12702 """Registers a file handle for the current thread. Returns self so 12703 that this method can be used in a with-statement. 12704 """ 12705 self.registry[threading.get_ident()] = handle 12706 return self 12707 12708 def deregister(self): 12709 """Removes the current thread from the registry.""" 12710 del self.registry[threading.get_ident()] 12711 12712 @property 12713 def available(self): 12714 """True if the thread is available in the registry.""" 12715 return threading.get_ident() in self.registry 12716 12717 @property 12718 def handle(self): 12719 """Gets the current handle for the thread.""" 12720 return self.registry.get(threading.get_ident(), self.default) 12721 12722 def __enter__(self): 12723 pass 12724 12725 def __exit__(self, ex_type, ex_value, ex_traceback): 12726 self.deregister() 12727 12728 # 12729 # io.TextIOBase interface 12730 # 12731 12732 @property 12733 def encoding(self): 12734 """Gets the encoding for this thread's handle.""" 12735 return self.handle.encoding 12736 12737 @property 12738 def errors(self): 12739 """Gets the errors for this thread's handle.""" 12740 return self.handle.errors 12741 12742 @property 12743 def newlines(self): 12744 """Gets the newlines for this thread's handle.""" 12745 return self.handle.newlines 12746 12747 @property 12748 def buffer(self): 12749 """Gets the buffer for this thread's handle.""" 12750 return self.handle.buffer 12751 12752 def detach(self): 12753 """Detaches the buffer for the current thread.""" 12754 return self.handle.detach() 12755 12756 def read(self, size=None): 12757 """Reads from the handle for the current thread.""" 12758 return self.handle.read(size) 12759 12760 def readline(self, size=-1): 12761 """Reads a line from the handle for the current thread.""" 12762 return self.handle.readline(size) 12763 12764 def readlines(self, hint=-1): 12765 """Reads lines from the handle for the current thread.""" 12766 return self.handle.readlines(hint) 12767 12768 def seek(self, offset, whence=io.SEEK_SET): 12769 """Seeks the current file.""" 12770 return self.handle.seek(offset, whence) 12771 12772 def tell(self): 12773 """Reports the current position in the handle for the current thread.""" 12774 return self.handle.tell() 12775 12776 def write(self, s): 12777 """Writes to this thread's handle. This also flushes, just to be 12778 extra sure the string was written. 12779 """ 12780 h = self.handle 12781 try: 12782 r = h.write(s) 12783 h.flush() 12784 except OSError: 12785 r = None 12786 return r 12787 12788 @property 12789 def line_buffering(self): 12790 """Gets if line buffering for this thread's handle enabled.""" 12791 return self.handle.line_buffering 12792 12793 # 12794 # io.IOBase interface 12795 # 12796 12797 def close(self): 12798 """Closes the current thread's handle.""" 12799 return self.handle.close() 12800 12801 @property 12802 def closed(self): 12803 """Is the thread's handle closed.""" 12804 return self.handle.closed 12805 12806 def fileno(self): 12807 """Returns the file descriptor for the current thread.""" 12808 return self.handle.fileno() 12809 12810 def flush(self): 12811 """Flushes the file descriptor for the current thread.""" 12812 return safe_flush(self.handle) 12813 12814 def isatty(self): 12815 """Returns if the file descriptor for the current thread is a tty.""" 12816 return self.handle.isatty() 12817 12818 def readable(self): 12819 """Returns if file descriptor for the current thread is readable.""" 12820 return self.handle.readable() 12821 12822 def seekable(self): 12823 """Returns if file descriptor for the current thread is seekable.""" 12824 return self.handle.seekable() 12825 12826 def truncate(self, size=None): 12827 """Truncates the file for for the current thread.""" 12828 return self.handle.truncate() 12829 12830 def writable(self, size=None): 12831 """Returns if file descriptor for the current thread is writable.""" 12832 return self.handle.writable(size) 12833 12834 def writelines(self): 12835 """Writes lines for the file descriptor for the current thread.""" 12836 return self.handle.writelines() 12837 12838 12839# These should NOT be lazy since they *need* to get the true stdout from the 12840# main thread. Also their creation time should be negligible. 12841STDOUT_DISPATCHER = FileThreadDispatcher(default=sys.stdout) 12842STDERR_DISPATCHER = FileThreadDispatcher(default=sys.stderr) 12843 12844 12845def parse_proxy_return(r, stdout, stderr): 12846 """Proxies may return a variety of outputs. This handles them generally. 12847 12848 Parameters 12849 ---------- 12850 r : tuple, str, int, or None 12851 Return from proxy function 12852 stdout : file-like 12853 Current stdout stream 12854 stdout : file-like 12855 Current stderr stream 12856 12857 Returns 12858 ------- 12859 cmd_result : int 12860 The return code of the proxy 12861 """ 12862 cmd_result = 0 12863 if isinstance(r, str): 12864 stdout.write(r) 12865 stdout.flush() 12866 elif isinstance(r, int): 12867 cmd_result = r 12868 elif isinstance(r, cabc.Sequence): 12869 rlen = len(r) 12870 if rlen > 0 and r[0] is not None: 12871 stdout.write(r[0]) 12872 stdout.flush() 12873 if rlen > 1 and r[1] is not None: 12874 stderr.write(r[1]) 12875 stderr.flush() 12876 if rlen > 2 and r[2] is not None: 12877 cmd_result = r[2] 12878 elif r is not None: 12879 # for the random object... 12880 stdout.write(str(r)) 12881 stdout.flush() 12882 return cmd_result 12883 12884 12885def proxy_zero(f, args, stdin, stdout, stderr, spec, stack): 12886 """Calls a proxy function which takes no parameters.""" 12887 return f() 12888 12889 12890def proxy_one(f, args, stdin, stdout, stderr, spec, stack): 12891 """Calls a proxy function which takes one parameter: args""" 12892 return f(args) 12893 12894 12895def proxy_two(f, args, stdin, stdout, stderr, spec, stack): 12896 """Calls a proxy function which takes two parameter: args and stdin.""" 12897 return f(args, stdin) 12898 12899 12900def proxy_three(f, args, stdin, stdout, stderr, spec, stack): 12901 """Calls a proxy function which takes three parameter: args, stdin, stdout. 12902 """ 12903 return f(args, stdin, stdout) 12904 12905 12906def proxy_four(f, args, stdin, stdout, stderr, spec, stack): 12907 """Calls a proxy function which takes four parameter: args, stdin, stdout, 12908 and stderr. 12909 """ 12910 return f(args, stdin, stdout, stderr) 12911 12912 12913def proxy_five(f, args, stdin, stdout, stderr, spec, stack): 12914 """Calls a proxy function which takes four parameter: args, stdin, stdout, 12915 stderr, and spec. 12916 """ 12917 return f(args, stdin, stdout, stderr, spec) 12918 12919 12920PROXIES = (proxy_zero, proxy_one, proxy_two, proxy_three, proxy_four, proxy_five) 12921PROXY_KWARG_NAMES = frozenset(["args", "stdin", "stdout", "stderr", "spec", "stack"]) 12922 12923 12924def partial_proxy(f): 12925 """Dispatches the appropriate proxy function based on the number of args.""" 12926 numargs = 0 12927 for name, param in inspect.signature(f).parameters.items(): 12928 if ( 12929 param.kind == param.POSITIONAL_ONLY 12930 or param.kind == param.POSITIONAL_OR_KEYWORD 12931 ): 12932 numargs += 1 12933 elif name in PROXY_KWARG_NAMES and param.kind == param.KEYWORD_ONLY: 12934 numargs += 1 12935 if numargs < 6: 12936 return functools.partial(PROXIES[numargs], f) 12937 elif numargs == 6: 12938 # don't need to partial. 12939 return f 12940 else: 12941 e = "Expected proxy with 6 or fewer arguments for {}, not {}" 12942 raise XonshError(e.format(", ".join(PROXY_KWARG_NAMES), numargs)) 12943 12944 12945class ProcProxyThread(threading.Thread): 12946 """ 12947 Class representing a function to be run as a subprocess-mode command. 12948 """ 12949 12950 def __init__( 12951 self, 12952 f, 12953 args, 12954 stdin=None, 12955 stdout=None, 12956 stderr=None, 12957 universal_newlines=False, 12958 env=None, 12959 ): 12960 """Parameters 12961 ---------- 12962 f : function 12963 The function to be executed. 12964 args : list 12965 A (possibly empty) list containing the arguments that were given on 12966 the command line 12967 stdin : file-like, optional 12968 A file-like object representing stdin (input can be read from 12969 here). If `stdin` is not provided or if it is explicitly set to 12970 `None`, then an instance of `io.StringIO` representing an empty 12971 file is used. 12972 stdout : file-like, optional 12973 A file-like object representing stdout (normal output can be 12974 written here). If `stdout` is not provided or if it is explicitly 12975 set to `None`, then `sys.stdout` is used. 12976 stderr : file-like, optional 12977 A file-like object representing stderr (error output can be 12978 written here). If `stderr` is not provided or if it is explicitly 12979 set to `None`, then `sys.stderr` is used. 12980 universal_newlines : bool, optional 12981 Whether or not to use universal newlines. 12982 env : Mapping, optional 12983 Environment mapping. 12984 """ 12985 self.orig_f = f 12986 self.f = partial_proxy(f) 12987 self.args = args 12988 self.pid = None 12989 self.returncode = None 12990 self._closed_handle_cache = {} 12991 12992 handles = self._get_handles(stdin, stdout, stderr) 12993 ( 12994 self.p2cread, 12995 self.p2cwrite, 12996 self.c2pread, 12997 self.c2pwrite, 12998 self.errread, 12999 self.errwrite, 13000 ) = handles 13001 13002 # default values 13003 self.stdin = stdin 13004 self.stdout = stdout 13005 self.stderr = stderr 13006 self.env = env or builtins.__xonsh_env__ 13007 self._interrupted = False 13008 13009 if ON_WINDOWS: 13010 if self.p2cwrite != -1: 13011 self.p2cwrite = msvcrt.open_osfhandle(self.p2cwrite.Detach(), 0) 13012 if self.c2pread != -1: 13013 self.c2pread = msvcrt.open_osfhandle(self.c2pread.Detach(), 0) 13014 if self.errread != -1: 13015 self.errread = msvcrt.open_osfhandle(self.errread.Detach(), 0) 13016 13017 if self.p2cwrite != -1: 13018 self.stdin = io.open(self.p2cwrite, "wb", -1) 13019 if universal_newlines: 13020 self.stdin = io.TextIOWrapper( 13021 self.stdin, write_through=True, line_buffering=False 13022 ) 13023 elif isinstance(stdin, int) and stdin != 0: 13024 self.stdin = io.open(stdin, "wb", -1) 13025 13026 if self.c2pread != -1: 13027 self.stdout = io.open(self.c2pread, "rb", -1) 13028 if universal_newlines: 13029 self.stdout = io.TextIOWrapper(self.stdout) 13030 13031 if self.errread != -1: 13032 self.stderr = io.open(self.errread, "rb", -1) 13033 if universal_newlines: 13034 self.stderr = io.TextIOWrapper(self.stderr) 13035 13036 # Set some signal handles, if we can. Must come before process 13037 # is started to prevent deadlock on windows 13038 self.old_int_handler = None 13039 if on_main_thread(): 13040 self.old_int_handler = signal.signal(signal.SIGINT, self._signal_int) 13041 # start up the proc 13042 super().__init__() 13043 self.start() 13044 13045 def __del__(self): 13046 self._restore_sigint() 13047 13048 def run(self): 13049 """Set up input/output streams and execute the child function in a new 13050 thread. This is part of the `threading.Thread` interface and should 13051 not be called directly. 13052 """ 13053 if self.f is None: 13054 return 13055 spec = self._wait_and_getattr("spec") 13056 last_in_pipeline = spec.last_in_pipeline 13057 if last_in_pipeline: 13058 capout = spec.captured_stdout # NOQA 13059 caperr = spec.captured_stderr # NOQA 13060 env = builtins.__xonsh_env__ 13061 enc = env.get("XONSH_ENCODING") 13062 err = env.get("XONSH_ENCODING_ERRORS") 13063 if ON_WINDOWS: 13064 if self.p2cread != -1: 13065 self.p2cread = msvcrt.open_osfhandle(self.p2cread.Detach(), 0) 13066 if self.c2pwrite != -1: 13067 self.c2pwrite = msvcrt.open_osfhandle(self.c2pwrite.Detach(), 0) 13068 if self.errwrite != -1: 13069 self.errwrite = msvcrt.open_osfhandle(self.errwrite.Detach(), 0) 13070 # get stdin 13071 if self.stdin is None: 13072 sp_stdin = None 13073 elif self.p2cread != -1: 13074 sp_stdin = io.TextIOWrapper( 13075 io.open(self.p2cread, "rb", -1), encoding=enc, errors=err 13076 ) 13077 else: 13078 sp_stdin = sys.stdin 13079 # stdout 13080 if self.c2pwrite != -1: 13081 sp_stdout = io.TextIOWrapper( 13082 io.open(self.c2pwrite, "wb", -1), encoding=enc, errors=err 13083 ) 13084 else: 13085 sp_stdout = sys.stdout 13086 # stderr 13087 if self.errwrite == self.c2pwrite: 13088 sp_stderr = sp_stdout 13089 elif self.errwrite != -1: 13090 sp_stderr = io.TextIOWrapper( 13091 io.open(self.errwrite, "wb", -1), encoding=enc, errors=err 13092 ) 13093 else: 13094 sp_stderr = sys.stderr 13095 # run the function itself 13096 try: 13097 with STDOUT_DISPATCHER.register(sp_stdout), STDERR_DISPATCHER.register( 13098 sp_stderr 13099 ), redirect_stdout(STDOUT_DISPATCHER), redirect_stderr(STDERR_DISPATCHER): 13100 r = self.f(self.args, sp_stdin, sp_stdout, sp_stderr, spec, spec.stack) 13101 except SystemExit as e: 13102 r = e.code if isinstance(e.code, int) else int(bool(e.code)) 13103 except OSError as e: 13104 status = still_writable(self.c2pwrite) and still_writable(self.errwrite) 13105 if status: 13106 # stdout and stderr are still writable, so error must 13107 # come from function itself. 13108 print_exception() 13109 r = 1 13110 else: 13111 # stdout and stderr are no longer writable, so error must 13112 # come from the fact that the next process in the pipeline 13113 # has closed the other side of the pipe. The function then 13114 # attempted to write to this side of the pipe anyway. This 13115 # is not truly an error and we should exit gracefully. 13116 r = 0 13117 except Exception: 13118 print_exception() 13119 r = 1 13120 safe_flush(sp_stdout) 13121 safe_flush(sp_stderr) 13122 self.returncode = parse_proxy_return(r, sp_stdout, sp_stderr) 13123 if not last_in_pipeline and not ON_WINDOWS: 13124 # mac requires us *not to* close the handles here while 13125 # windows requires us *to* close the handles here 13126 return 13127 # clean up 13128 # scopz: not sure why this is needed, but stdin cannot go here 13129 # and stdout & stderr must. 13130 handles = [self.stdout, self.stderr] 13131 for handle in handles: 13132 safe_fdclose(handle, cache=self._closed_handle_cache) 13133 13134 def _wait_and_getattr(self, name): 13135 """make sure the instance has a certain attr, and return it.""" 13136 while not hasattr(self, name): 13137 time.sleep(1e-7) 13138 return getattr(self, name) 13139 13140 def poll(self): 13141 """Check if the function has completed. 13142 13143 Returns 13144 ------- 13145 None if the function is still executing, and the returncode otherwise 13146 """ 13147 return self.returncode 13148 13149 def wait(self, timeout=None): 13150 """Waits for the process to finish and returns the return code.""" 13151 self.join() 13152 self._restore_sigint() 13153 return self.returncode 13154 13155 # 13156 # SIGINT handler 13157 # 13158 13159 def _signal_int(self, signum, frame): 13160 """Signal handler for SIGINT - Ctrl+C may have been pressed.""" 13161 # Check if we have already been interrupted. This should prevent 13162 # the possibility of infinite recursion. 13163 if self._interrupted: 13164 return 13165 self._interrupted = True 13166 # close file handles here to stop an processes piped to us. 13167 handles = ( 13168 self.p2cread, 13169 self.p2cwrite, 13170 self.c2pread, 13171 self.c2pwrite, 13172 self.errread, 13173 self.errwrite, 13174 ) 13175 for handle in handles: 13176 safe_fdclose(handle) 13177 if self.poll() is not None: 13178 self._restore_sigint(frame=frame) 13179 if on_main_thread(): 13180 signal.pthread_kill(threading.get_ident(), signal.SIGINT) 13181 13182 def _restore_sigint(self, frame=None): 13183 old = self.old_int_handler 13184 if old is not None: 13185 if on_main_thread(): 13186 signal.signal(signal.SIGINT, old) 13187 self.old_int_handler = None 13188 if frame is not None: 13189 if old is not None and old is not self._signal_int: 13190 old(signal.SIGINT, frame) 13191 if self._interrupted: 13192 self.returncode = 1 13193 13194 # The code below (_get_devnull, _get_handles, and _make_inheritable) comes 13195 # from subprocess.py in the Python 3.4.2 Standard Library 13196 def _get_devnull(self): 13197 if not hasattr(self, "_devnull"): 13198 self._devnull = os.open(os.devnull, os.O_RDWR) 13199 return self._devnull 13200 13201 if ON_WINDOWS: 13202 13203 def _make_inheritable(self, handle): 13204 """Return a duplicate of handle, which is inheritable""" 13205 h = _winapi.DuplicateHandle( 13206 _winapi.GetCurrentProcess(), 13207 handle, 13208 _winapi.GetCurrentProcess(), 13209 0, 13210 1, 13211 _winapi.DUPLICATE_SAME_ACCESS, 13212 ) 13213 return Handle(h) 13214 13215 def _get_handles(self, stdin, stdout, stderr): 13216 """Construct and return tuple with IO objects: 13217 p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite 13218 """ 13219 if stdin is None and stdout is None and stderr is None: 13220 return (-1, -1, -1, -1, -1, -1) 13221 13222 p2cread, p2cwrite = -1, -1 13223 c2pread, c2pwrite = -1, -1 13224 errread, errwrite = -1, -1 13225 13226 if stdin is None: 13227 p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) 13228 if p2cread is None: 13229 p2cread, _ = _winapi.CreatePipe(None, 0) 13230 p2cread = Handle(p2cread) 13231 _winapi.CloseHandle(_) 13232 elif stdin == subprocess.PIPE: 13233 p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) 13234 elif stdin == subprocess.DEVNULL: 13235 p2cread = msvcrt.get_osfhandle(self._get_devnull()) 13236 elif isinstance(stdin, int): 13237 p2cread = msvcrt.get_osfhandle(stdin) 13238 else: 13239 # Assuming file-like object 13240 p2cread = msvcrt.get_osfhandle(stdin.fileno()) 13241 p2cread = self._make_inheritable(p2cread) 13242 13243 if stdout is None: 13244 c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) 13245 if c2pwrite is None: 13246 _, c2pwrite = _winapi.CreatePipe(None, 0) 13247 c2pwrite = Handle(c2pwrite) 13248 _winapi.CloseHandle(_) 13249 elif stdout == subprocess.PIPE: 13250 c2pread, c2pwrite = _winapi.CreatePipe(None, 0) 13251 c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) 13252 elif stdout == subprocess.DEVNULL: 13253 c2pwrite = msvcrt.get_osfhandle(self._get_devnull()) 13254 elif isinstance(stdout, int): 13255 c2pwrite = msvcrt.get_osfhandle(stdout) 13256 else: 13257 # Assuming file-like object 13258 c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) 13259 c2pwrite = self._make_inheritable(c2pwrite) 13260 13261 if stderr is None: 13262 errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE) 13263 if errwrite is None: 13264 _, errwrite = _winapi.CreatePipe(None, 0) 13265 errwrite = Handle(errwrite) 13266 _winapi.CloseHandle(_) 13267 elif stderr == subprocess.PIPE: 13268 errread, errwrite = _winapi.CreatePipe(None, 0) 13269 errread, errwrite = Handle(errread), Handle(errwrite) 13270 elif stderr == subprocess.STDOUT: 13271 errwrite = c2pwrite 13272 elif stderr == subprocess.DEVNULL: 13273 errwrite = msvcrt.get_osfhandle(self._get_devnull()) 13274 elif isinstance(stderr, int): 13275 errwrite = msvcrt.get_osfhandle(stderr) 13276 else: 13277 # Assuming file-like object 13278 errwrite = msvcrt.get_osfhandle(stderr.fileno()) 13279 errwrite = self._make_inheritable(errwrite) 13280 13281 return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) 13282 13283 else: 13284 # POSIX versions 13285 def _get_handles(self, stdin, stdout, stderr): 13286 """Construct and return tuple with IO objects: 13287 p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite 13288 """ 13289 p2cread, p2cwrite = -1, -1 13290 c2pread, c2pwrite = -1, -1 13291 errread, errwrite = -1, -1 13292 13293 if stdin is None: 13294 pass 13295 elif stdin == subprocess.PIPE: 13296 p2cread, p2cwrite = os.pipe() 13297 elif stdin == subprocess.DEVNULL: 13298 p2cread = self._get_devnull() 13299 elif isinstance(stdin, int): 13300 p2cread = stdin 13301 else: 13302 # Assuming file-like object 13303 p2cread = stdin.fileno() 13304 13305 if stdout is None: 13306 pass 13307 elif stdout == subprocess.PIPE: 13308 c2pread, c2pwrite = os.pipe() 13309 elif stdout == subprocess.DEVNULL: 13310 c2pwrite = self._get_devnull() 13311 elif isinstance(stdout, int): 13312 c2pwrite = stdout 13313 else: 13314 # Assuming file-like object 13315 c2pwrite = stdout.fileno() 13316 13317 if stderr is None: 13318 pass 13319 elif stderr == subprocess.PIPE: 13320 errread, errwrite = os.pipe() 13321 elif stderr == subprocess.STDOUT: 13322 errwrite = c2pwrite 13323 elif stderr == subprocess.DEVNULL: 13324 errwrite = self._get_devnull() 13325 elif isinstance(stderr, int): 13326 errwrite = stderr 13327 else: 13328 # Assuming file-like object 13329 errwrite = stderr.fileno() 13330 13331 return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) 13332 13333 13334# 13335# Foreground Thread Process Proxies 13336# 13337 13338 13339class ProcProxy(object): 13340 """This is process proxy class that runs its alias functions on the 13341 same thread that it was called from, which is typically the main thread. 13342 This prevents the process from running on a background thread, but enables 13343 debugger and profiler tools (functions) be run on the same thread that they 13344 are attempting to debug. 13345 """ 13346 13347 def __init__( 13348 self, 13349 f, 13350 args, 13351 stdin=None, 13352 stdout=None, 13353 stderr=None, 13354 universal_newlines=False, 13355 env=None, 13356 ): 13357 self.orig_f = f 13358 self.f = partial_proxy(f) 13359 self.args = args 13360 self.pid = os.getpid() 13361 self.returncode = None 13362 self.stdin = stdin 13363 self.stdout = stdout 13364 self.stderr = stderr 13365 self.universal_newlines = universal_newlines 13366 self.env = env 13367 13368 def poll(self): 13369 """Check if the function has completed via the returncode or None. 13370 """ 13371 return self.returncode 13372 13373 def wait(self, timeout=None): 13374 """Runs the function and returns the result. Timeout argument only 13375 present for API compatibility. 13376 """ 13377 if self.f is None: 13378 return 0 13379 env = builtins.__xonsh_env__ 13380 enc = env.get("XONSH_ENCODING") 13381 err = env.get("XONSH_ENCODING_ERRORS") 13382 spec = self._wait_and_getattr("spec") 13383 # set file handles 13384 if self.stdin is None: 13385 stdin = None 13386 else: 13387 if isinstance(self.stdin, int): 13388 inbuf = io.open(self.stdin, "rb", -1) 13389 else: 13390 inbuf = self.stdin 13391 stdin = io.TextIOWrapper(inbuf, encoding=enc, errors=err) 13392 stdout = self._pick_buf(self.stdout, sys.stdout, enc, err) 13393 stderr = self._pick_buf(self.stderr, sys.stderr, enc, err) 13394 # run the actual function 13395 try: 13396 r = self.f(self.args, stdin, stdout, stderr, spec, spec.stack) 13397 except Exception: 13398 print_exception() 13399 r = 1 13400 self.returncode = parse_proxy_return(r, stdout, stderr) 13401 safe_flush(stdout) 13402 safe_flush(stderr) 13403 return self.returncode 13404 13405 @staticmethod 13406 def _pick_buf(handle, sysbuf, enc, err): 13407 if handle is None or handle is sysbuf: 13408 buf = sysbuf 13409 elif isinstance(handle, int): 13410 if handle < 3: 13411 buf = sysbuf 13412 else: 13413 buf = io.TextIOWrapper( 13414 io.open(handle, "wb", -1), encoding=enc, errors=err 13415 ) 13416 elif hasattr(handle, "encoding"): 13417 # must be a text stream, no need to wrap. 13418 buf = handle 13419 else: 13420 # must be a binary stream, should wrap it. 13421 buf = io.TextIOWrapper(handle, encoding=enc, errors=err) 13422 return buf 13423 13424 def _wait_and_getattr(self, name): 13425 """make sure the instance has a certain attr, and return it.""" 13426 while not hasattr(self, name): 13427 time.sleep(1e-7) 13428 return getattr(self, name) 13429 13430 13431@lazyobject 13432def SIGNAL_MESSAGES(): 13433 sm = { 13434 signal.SIGABRT: "Aborted", 13435 signal.SIGFPE: "Floating point exception", 13436 signal.SIGILL: "Illegal instructions", 13437 signal.SIGTERM: "Terminated", 13438 signal.SIGSEGV: "Segmentation fault", 13439 } 13440 if ON_POSIX: 13441 sm.update( 13442 {signal.SIGQUIT: "Quit", signal.SIGHUP: "Hangup", signal.SIGKILL: "Killed"} 13443 ) 13444 return sm 13445 13446 13447def safe_readlines(handle, hint=-1): 13448 """Attempts to read lines without throwing an error.""" 13449 try: 13450 lines = handle.readlines(hint) 13451 except OSError: 13452 lines = [] 13453 return lines 13454 13455 13456def safe_readable(handle): 13457 """Attempts to find if the handle is readable without throwing an error.""" 13458 try: 13459 status = handle.readable() 13460 except (OSError, ValueError): 13461 status = False 13462 return status 13463 13464 13465def update_fg_process_group(pipeline_group, background): 13466 if background: 13467 return False 13468 if not ON_POSIX: 13469 return False 13470 env = builtins.__xonsh_env__ 13471 if not env.get("XONSH_INTERACTIVE"): 13472 return False 13473 return give_terminal_to(pipeline_group) 13474 13475 13476class CommandPipeline: 13477 """Represents a subprocess-mode command pipeline.""" 13478 13479 attrnames = ( 13480 "stdin", 13481 "stdout", 13482 "stderr", 13483 "pid", 13484 "returncode", 13485 "args", 13486 "alias", 13487 "stdin_redirect", 13488 "stdout_redirect", 13489 "stderr_redirect", 13490 "timestamps", 13491 "executed_cmd", 13492 "input", 13493 "output", 13494 "errors", 13495 ) 13496 13497 nonblocking = (io.BytesIO, NonBlockingFDReader, ConsoleParallelReader) 13498 13499 def __init__(self, specs): 13500 """ 13501 Parameters 13502 ---------- 13503 specs : list of SubprocSpec 13504 Process specifications 13505 13506 Attributes 13507 ---------- 13508 spec : SubprocSpec 13509 The last specification in specs 13510 proc : Popen-like 13511 The process in procs 13512 ended : bool 13513 Boolean for if the command has stopped executing. 13514 input : str 13515 A string of the standard input. 13516 output : str 13517 A string of the standard output. 13518 errors : str 13519 A string of the standard error. 13520 lines : list of str 13521 The output lines 13522 starttime : floats or None 13523 Pipeline start timestamp. 13524 """ 13525 self.starttime = None 13526 self.ended = False 13527 self.procs = [] 13528 self.specs = specs 13529 self.spec = specs[-1] 13530 self.captured = specs[-1].captured 13531 self.input = self._output = self.errors = self.endtime = None 13532 self._closed_handle_cache = {} 13533 self.lines = [] 13534 self._stderr_prefix = self._stderr_postfix = None 13535 self.term_pgid = None 13536 13537 background = self.spec.background 13538 pipeline_group = None 13539 for spec in specs: 13540 if self.starttime is None: 13541 self.starttime = time.time() 13542 try: 13543 proc = spec.run(pipeline_group=pipeline_group) 13544 except Exception: 13545 print_exception() 13546 self._return_terminal() 13547 self.proc = None 13548 return 13549 if ( 13550 proc.pid 13551 and pipeline_group is None 13552 and not spec.is_proxy 13553 and self.captured != "object" 13554 ): 13555 pipeline_group = proc.pid 13556 if update_fg_process_group(pipeline_group, background): 13557 self.term_pgid = pipeline_group 13558 self.procs.append(proc) 13559 self.proc = self.procs[-1] 13560 13561 def __repr__(self): 13562 s = self.__class__.__name__ + "(" 13563 s += ", ".join(a + "=" + str(getattr(self, a)) for a in self.attrnames) 13564 s += ")" 13565 return s 13566 13567 def __bool__(self): 13568 return self.returncode == 0 13569 13570 def __len__(self): 13571 return len(self.procs) 13572 13573 def __iter__(self): 13574 """Iterates through stdout and returns the lines, converting to 13575 strings and universal newlines if needed. 13576 """ 13577 if self.ended: 13578 yield from iter(self.lines) 13579 else: 13580 yield from self.tee_stdout() 13581 13582 def iterraw(self): 13583 """Iterates through the last stdout, and returns the lines 13584 exactly as found. 13585 """ 13586 # get appropriate handles 13587 spec = self.spec 13588 proc = self.proc 13589 if proc is None: 13590 return 13591 timeout = builtins.__xonsh_env__.get("XONSH_PROC_FREQUENCY") 13592 # get the correct stdout 13593 stdout = proc.stdout 13594 if ( 13595 stdout is None or spec.stdout is None or not safe_readable(stdout) 13596 ) and spec.captured_stdout is not None: 13597 stdout = spec.captured_stdout 13598 if hasattr(stdout, "buffer"): 13599 stdout = stdout.buffer 13600 if stdout is not None and not isinstance(stdout, self.nonblocking): 13601 stdout = NonBlockingFDReader(stdout.fileno(), timeout=timeout) 13602 if ( 13603 not stdout 13604 or self.captured == "stdout" 13605 or not safe_readable(stdout) 13606 or not spec.threadable 13607 ): 13608 # we get here if the process is not threadable or the 13609 # class is the real Popen 13610 PrevProcCloser(pipeline=self) 13611 task = wait_for_active_job() 13612 if task is None or task["status"] != "stopped": 13613 proc.wait() 13614 self._endtime() 13615 if self.captured == "object": 13616 self.end(tee_output=False) 13617 elif self.captured == "hiddenobject" and stdout: 13618 b = stdout.read() 13619 lines = b.splitlines(keepends=True) 13620 yield from lines 13621 self.end(tee_output=False) 13622 elif self.captured == "stdout": 13623 b = stdout.read() 13624 s = self._decode_uninew(b, universal_newlines=True) 13625 self.lines = s.splitlines(keepends=True) 13626 return 13627 # get the correct stderr 13628 stderr = proc.stderr 13629 if ( 13630 stderr is None or spec.stderr is None or not safe_readable(stderr) 13631 ) and spec.captured_stderr is not None: 13632 stderr = spec.captured_stderr 13633 if hasattr(stderr, "buffer"): 13634 stderr = stderr.buffer 13635 if stderr is not None and not isinstance(stderr, self.nonblocking): 13636 stderr = NonBlockingFDReader(stderr.fileno(), timeout=timeout) 13637 # read from process while it is running 13638 check_prev_done = len(self.procs) == 1 13639 prev_end_time = None 13640 i = j = cnt = 1 13641 while proc.poll() is None: 13642 if getattr(proc, "suspended", False): 13643 return 13644 elif getattr(proc, "in_alt_mode", False): 13645 time.sleep(0.1) # probably not leaving any time soon 13646 continue 13647 elif not check_prev_done: 13648 # In the case of pipelines with more than one command 13649 # we should give the commands a little time 13650 # to start up fully. This is particularly true for 13651 # GNU Parallel, which has a long startup time. 13652 pass 13653 elif self._prev_procs_done(): 13654 self._close_prev_procs() 13655 proc.prevs_are_closed = True 13656 break 13657 stdout_lines = safe_readlines(stdout, 1024) 13658 i = len(stdout_lines) 13659 if i != 0: 13660 yield from stdout_lines 13661 stderr_lines = safe_readlines(stderr, 1024) 13662 j = len(stderr_lines) 13663 if j != 0: 13664 self.stream_stderr(stderr_lines) 13665 if not check_prev_done: 13666 # if we are piping... 13667 if stdout_lines or stderr_lines: 13668 # see if we have some output. 13669 check_prev_done = True 13670 elif prev_end_time is None: 13671 # or see if we already know that the next-to-last 13672 # proc in the pipeline has ended. 13673 if self._prev_procs_done(): 13674 # if it has, record the time 13675 prev_end_time = time.time() 13676 elif time.time() - prev_end_time >= 0.1: 13677 # if we still don't have any output, even though the 13678 # next-to-last proc has finished, wait a bit to make 13679 # sure we have fully started up, etc. 13680 check_prev_done = True 13681 # this is for CPU usage 13682 if i + j == 0: 13683 cnt = min(cnt + 1, 1000) 13684 else: 13685 cnt = 1 13686 time.sleep(timeout * cnt) 13687 # read from process now that it is over 13688 yield from safe_readlines(stdout) 13689 self.stream_stderr(safe_readlines(stderr)) 13690 proc.wait() 13691 self._endtime() 13692 yield from safe_readlines(stdout) 13693 self.stream_stderr(safe_readlines(stderr)) 13694 if self.captured == "object": 13695 self.end(tee_output=False) 13696 13697 def itercheck(self): 13698 """Iterates through the command lines and throws an error if the 13699 returncode is non-zero. 13700 """ 13701 yield from self 13702 if self.returncode: 13703 # I included self, as providing access to stderr and other details 13704 # useful when instance isn't assigned to a variable in the shell. 13705 raise XonshCalledProcessError( 13706 self.returncode, self.executed_cmd, self.stdout, self.stderr, self 13707 ) 13708 13709 def tee_stdout(self): 13710 """Writes the process stdout to the output variable, line-by-line, and 13711 yields each line. This may optionally accept lines (in bytes) to iterate 13712 over, in which case it does not call iterraw(). 13713 """ 13714 env = builtins.__xonsh_env__ 13715 enc = env.get("XONSH_ENCODING") 13716 err = env.get("XONSH_ENCODING_ERRORS") 13717 lines = self.lines 13718 stream = self.captured not in STDOUT_CAPTURE_KINDS 13719 if stream and not self.spec.stdout: 13720 stream = False 13721 stdout_has_buffer = hasattr(sys.stdout, "buffer") 13722 nl = b"\n" 13723 cr = b"\r" 13724 crnl = b"\r\n" 13725 for line in self.iterraw(): 13726 # write to stdout line ASAP, if needed 13727 if stream: 13728 if stdout_has_buffer: 13729 sys.stdout.buffer.write(line) 13730 else: 13731 sys.stdout.write(line.decode(encoding=enc, errors=err)) 13732 sys.stdout.flush() 13733 # do some munging of the line before we return it 13734 if line.endswith(crnl): 13735 line = line[:-2] + nl 13736 elif line.endswith(cr): 13737 line = line[:-1] + nl 13738 line = RE_HIDE_ESCAPE.sub(b"", line) 13739 line = line.decode(encoding=enc, errors=err) 13740 # tee it up! 13741 lines.append(line) 13742 yield line 13743 13744 def stream_stderr(self, lines): 13745 """Streams lines to sys.stderr and the errors attribute.""" 13746 if not lines: 13747 return 13748 env = builtins.__xonsh_env__ 13749 enc = env.get("XONSH_ENCODING") 13750 err = env.get("XONSH_ENCODING_ERRORS") 13751 b = b"".join(lines) 13752 if self.stderr_prefix: 13753 b = self.stderr_prefix + b 13754 if self.stderr_postfix: 13755 b += self.stderr_postfix 13756 stderr_has_buffer = hasattr(sys.stderr, "buffer") 13757 # write bytes to std stream 13758 if stderr_has_buffer: 13759 sys.stderr.buffer.write(b) 13760 else: 13761 sys.stderr.write(b.decode(encoding=enc, errors=err)) 13762 sys.stderr.flush() 13763 # do some munging of the line before we save it to the attr 13764 b = b.replace(b"\r\n", b"\n").replace(b"\r", b"\n") 13765 b = RE_HIDE_ESCAPE.sub(b"", b) 13766 env = builtins.__xonsh_env__ 13767 s = b.decode( 13768 encoding=env.get("XONSH_ENCODING"), errors=env.get("XONSH_ENCODING_ERRORS") 13769 ) 13770 # set the errors 13771 if self.errors is None: 13772 self.errors = s 13773 else: 13774 self.errors += s 13775 13776 def _decode_uninew(self, b, universal_newlines=None): 13777 """Decode bytes into a str and apply universal newlines as needed.""" 13778 if not b: 13779 return "" 13780 if isinstance(b, bytes): 13781 env = builtins.__xonsh_env__ 13782 s = b.decode( 13783 encoding=env.get("XONSH_ENCODING"), 13784 errors=env.get("XONSH_ENCODING_ERRORS"), 13785 ) 13786 else: 13787 s = b 13788 if universal_newlines or self.spec.universal_newlines: 13789 s = s.replace("\r\n", "\n").replace("\r", "\n") 13790 return s 13791 13792 # 13793 # Ending methods 13794 # 13795 13796 def end(self, tee_output=True): 13797 """ 13798 End the pipeline, return the controlling terminal if needed. 13799 13800 Main things done in self._end(). 13801 """ 13802 if self.ended: 13803 return 13804 self._end(tee_output=tee_output) 13805 self._return_terminal() 13806 13807 def _end(self, tee_output): 13808 """Waits for the command to complete and then runs any closing and 13809 cleanup procedures that need to be run. 13810 """ 13811 if tee_output: 13812 for _ in self.tee_stdout(): 13813 pass 13814 self._endtime() 13815 # since we are driven by getting output, input may not be available 13816 # until the command has completed. 13817 self._set_input() 13818 self._close_prev_procs() 13819 self._close_proc() 13820 self._check_signal() 13821 self._apply_to_history() 13822 self.ended = True 13823 self._raise_subproc_error() 13824 13825 def _return_terminal(self): 13826 if ON_WINDOWS or not ON_POSIX: 13827 return 13828 pgid = os.getpgid(0) 13829 if self.term_pgid is None or pgid == self.term_pgid: 13830 return 13831 if give_terminal_to(pgid): # if gave term succeed 13832 self.term_pgid = pgid 13833 if hasattr(builtins, "__xonsh_shell__"): 13834 # restoring sanity could probably be called whenever we return 13835 # control to the shell. But it only seems to matter after a 13836 # ^Z event. This *has* to be called after we give the terminal 13837 # back to the shell. 13838 builtins.__xonsh_shell__.shell.restore_tty_sanity() 13839 13840 def resume(self, job, tee_output=True): 13841 self.ended = False 13842 if give_terminal_to(job["pgrp"]): 13843 self.term_pgid = job["pgrp"] 13844 _continue(job) 13845 self.end(tee_output=tee_output) 13846 13847 def _endtime(self): 13848 """Sets the closing timestamp if it hasn't been already.""" 13849 if self.endtime is None: 13850 self.endtime = time.time() 13851 13852 def _safe_close(self, handle): 13853 safe_fdclose(handle, cache=self._closed_handle_cache) 13854 13855 def _prev_procs_done(self): 13856 """Boolean for if all previous processes have completed. If there 13857 is only a single process in the pipeline, this returns False. 13858 """ 13859 any_running = False 13860 for s, p in zip(self.specs[:-1], self.procs[:-1]): 13861 if p.poll() is None: 13862 any_running = True 13863 continue 13864 self._safe_close(s.stdin) 13865 self._safe_close(s.stdout) 13866 self._safe_close(s.stderr) 13867 if p is None: 13868 continue 13869 self._safe_close(p.stdin) 13870 self._safe_close(p.stdout) 13871 self._safe_close(p.stderr) 13872 return False if any_running else (len(self) > 1) 13873 13874 def _close_prev_procs(self): 13875 """Closes all but the last proc's stdout.""" 13876 for s, p in zip(self.specs[:-1], self.procs[:-1]): 13877 self._safe_close(s.stdin) 13878 self._safe_close(s.stdout) 13879 self._safe_close(s.stderr) 13880 if p is None: 13881 continue 13882 self._safe_close(p.stdin) 13883 self._safe_close(p.stdout) 13884 self._safe_close(p.stderr) 13885 13886 def _close_proc(self): 13887 """Closes last proc's stdout.""" 13888 s = self.spec 13889 p = self.proc 13890 self._safe_close(s.stdin) 13891 self._safe_close(s.stdout) 13892 self._safe_close(s.stderr) 13893 self._safe_close(s.captured_stdout) 13894 self._safe_close(s.captured_stderr) 13895 if p is None: 13896 return 13897 self._safe_close(p.stdin) 13898 self._safe_close(p.stdout) 13899 self._safe_close(p.stderr) 13900 13901 def _set_input(self): 13902 """Sets the input variable.""" 13903 if self.proc is None: 13904 return 13905 stdin = self.proc.stdin 13906 if ( 13907 stdin is None 13908 or isinstance(stdin, int) 13909 or stdin.closed 13910 or not stdin.seekable() 13911 or not safe_readable(stdin) 13912 ): 13913 input = b"" 13914 else: 13915 stdin.seek(0) 13916 input = stdin.read() 13917 self.input = self._decode_uninew(input) 13918 13919 def _check_signal(self): 13920 """Checks if a signal was received and issues a message.""" 13921 proc_signal = getattr(self.proc, "signal", None) 13922 if proc_signal is None: 13923 return 13924 sig, core = proc_signal 13925 sig_str = SIGNAL_MESSAGES.get(sig) 13926 if sig_str: 13927 if core: 13928 sig_str += " (core dumped)" 13929 print(sig_str, file=sys.stderr) 13930 if self.errors is not None: 13931 self.errors += sig_str + "\n" 13932 13933 def _apply_to_history(self): 13934 """Applies the results to the current history object.""" 13935 hist = builtins.__xonsh_history__ 13936 if hist is not None: 13937 hist.last_cmd_rtn = 1 if self.proc is None else self.proc.returncode 13938 13939 def _raise_subproc_error(self): 13940 """Raises a subprocess error, if we are supposed to.""" 13941 spec = self.spec 13942 rtn = self.returncode 13943 if ( 13944 not spec.is_proxy 13945 and rtn is not None 13946 and rtn > 0 13947 and builtins.__xonsh_env__.get("RAISE_SUBPROC_ERROR") 13948 ): 13949 try: 13950 raise subprocess.CalledProcessError(rtn, spec.cmd, output=self.output) 13951 finally: 13952 # this is need to get a working terminal in interactive mode 13953 self._return_terminal() 13954 13955 # 13956 # Properties 13957 # 13958 13959 @property 13960 def stdin(self): 13961 """Process stdin.""" 13962 return self.proc.stdin 13963 13964 @property 13965 def stdout(self): 13966 """Process stdout.""" 13967 return self.proc.stdout 13968 13969 @property 13970 def stderr(self): 13971 """Process stderr.""" 13972 return self.proc.stderr 13973 13974 @property 13975 def inp(self): 13976 """Creates normalized input string from args.""" 13977 return " ".join(self.args) 13978 13979 @property 13980 def output(self): 13981 """Non-blocking, lazy access to output""" 13982 if self.ended: 13983 if self._output is None: 13984 self._output = "".join(self.lines) 13985 return self._output 13986 else: 13987 return "".join(self.lines) 13988 13989 @property 13990 def out(self): 13991 """Output value as a str.""" 13992 self.end() 13993 return self.output 13994 13995 @property 13996 def err(self): 13997 """Error messages as a string.""" 13998 self.end() 13999 return self.errors 14000 14001 @property 14002 def pid(self): 14003 """Process identifier.""" 14004 return self.proc.pid 14005 14006 @property 14007 def returncode(self): 14008 """Process return code, waits until command is completed.""" 14009 self.end() 14010 if self.proc is None: 14011 return 1 14012 return self.proc.returncode 14013 14014 rtn = returncode 14015 14016 @property 14017 def args(self): 14018 """Arguments to the process.""" 14019 return self.spec.args 14020 14021 @property 14022 def rtn(self): 14023 """Alias to return code.""" 14024 return self.returncode 14025 14026 @property 14027 def alias(self): 14028 """Alias the process used.""" 14029 return self.spec.alias 14030 14031 @property 14032 def stdin_redirect(self): 14033 """Redirection used for stdin.""" 14034 stdin = self.spec.stdin 14035 name = getattr(stdin, "name", "<stdin>") 14036 mode = getattr(stdin, "mode", "r") 14037 return [name, mode] 14038 14039 @property 14040 def stdout_redirect(self): 14041 """Redirection used for stdout.""" 14042 stdout = self.spec.stdout 14043 name = getattr(stdout, "name", "<stdout>") 14044 mode = getattr(stdout, "mode", "a") 14045 return [name, mode] 14046 14047 @property 14048 def stderr_redirect(self): 14049 """Redirection used for stderr.""" 14050 stderr = self.spec.stderr 14051 name = getattr(stderr, "name", "<stderr>") 14052 mode = getattr(stderr, "mode", "r") 14053 return [name, mode] 14054 14055 @property 14056 def timestamps(self): 14057 """The start and end time stamps.""" 14058 return [self.starttime, self.endtime] 14059 14060 @property 14061 def executed_cmd(self): 14062 """The resolve and executed command.""" 14063 return self.spec.cmd 14064 14065 @property 14066 def stderr_prefix(self): 14067 """Prefix to print in front of stderr, as bytes.""" 14068 p = self._stderr_prefix 14069 if p is None: 14070 env = builtins.__xonsh_env__ 14071 t = env.get("XONSH_STDERR_PREFIX") 14072 s = format_std_prepost(t, env=env) 14073 p = s.encode( 14074 encoding=env.get("XONSH_ENCODING"), 14075 errors=env.get("XONSH_ENCODING_ERRORS"), 14076 ) 14077 self._stderr_prefix = p 14078 return p 14079 14080 @property 14081 def stderr_postfix(self): 14082 """Postfix to print after stderr, as bytes.""" 14083 p = self._stderr_postfix 14084 if p is None: 14085 env = builtins.__xonsh_env__ 14086 t = env.get("XONSH_STDERR_POSTFIX") 14087 s = format_std_prepost(t, env=env) 14088 p = s.encode( 14089 encoding=env.get("XONSH_ENCODING"), 14090 errors=env.get("XONSH_ENCODING_ERRORS"), 14091 ) 14092 self._stderr_postfix = p 14093 return p 14094 14095 14096class HiddenCommandPipeline(CommandPipeline): 14097 def __repr__(self): 14098 return "" 14099 14100 14101def pause_call_resume(p, f, *args, **kwargs): 14102 """For a process p, this will call a function f with the remaining args and 14103 and kwargs. If the process cannot accept signals, the function will be called. 14104 14105 Parameters 14106 ---------- 14107 p : Popen object or similar 14108 f : callable 14109 args : remaining arguments 14110 kwargs : keyword arguments 14111 """ 14112 can_send_signal = ( 14113 hasattr(p, "send_signal") and ON_POSIX and not ON_MSYS and not ON_CYGWIN 14114 ) 14115 if can_send_signal: 14116 p.send_signal(signal.SIGSTOP) 14117 try: 14118 f(*args, **kwargs) 14119 except Exception: 14120 pass 14121 if can_send_signal: 14122 p.send_signal(signal.SIGCONT) 14123 14124 14125class PrevProcCloser(threading.Thread): 14126 """Previous process closer thread for pipelines whose last command 14127 is itself unthreadable. This makes sure that the pipeline is 14128 driven forward and does not deadlock. 14129 """ 14130 14131 def __init__(self, pipeline): 14132 """ 14133 Parameters 14134 ---------- 14135 pipeline : CommandPipeline 14136 The pipeline whose prev procs we should close. 14137 """ 14138 self.pipeline = pipeline 14139 super().__init__() 14140 self.daemon = True 14141 self.start() 14142 14143 def run(self): 14144 """Runs the closing algorithm.""" 14145 pipeline = self.pipeline 14146 check_prev_done = len(pipeline.procs) == 1 14147 if check_prev_done: 14148 return 14149 proc = pipeline.proc 14150 prev_end_time = None 14151 timeout = builtins.__xonsh_env__.get("XONSH_PROC_FREQUENCY") 14152 sleeptime = min(timeout * 1000, 0.1) 14153 while proc.poll() is None: 14154 if not check_prev_done: 14155 # In the case of pipelines with more than one command 14156 # we should give the commands a little time 14157 # to start up fully. This is particularly true for 14158 # GNU Parallel, which has a long startup time. 14159 pass 14160 elif pipeline._prev_procs_done(): 14161 pipeline._close_prev_procs() 14162 proc.prevs_are_closed = True 14163 break 14164 if not check_prev_done: 14165 # if we are piping... 14166 if prev_end_time is None: 14167 # or see if we already know that the next-to-last 14168 # proc in the pipeline has ended. 14169 if pipeline._prev_procs_done(): 14170 # if it has, record the time 14171 prev_end_time = time.time() 14172 elif time.time() - prev_end_time >= 0.1: 14173 # if we still don't have any output, even though the 14174 # next-to-last proc has finished, wait a bit to make 14175 # sure we have fully started up, etc. 14176 check_prev_done = True 14177 # this is for CPU usage 14178 time.sleep(sleeptime) 14179 14180# 14181# shell 14182# 14183# -*- coding: utf-8 -*- 14184"""The xonsh shell""" 14185# amalgamated sys 14186random = _LazyModule.load('random', 'random') 14187# amalgamated time 14188# amalgamated difflib 14189# amalgamated builtins 14190# amalgamated warnings 14191# amalgamated xonsh.platform 14192# amalgamated xonsh.tools 14193# amalgamated xonsh.events 14194xhm = _LazyModule.load('xonsh', 'xonsh.history.main', 'xhm') 14195events.doc( 14196 "on_transform_command", 14197 """ 14198on_transform_command(cmd: str) -> str 14199 14200Fired to request xontribs to transform a command line. Return the transformed 14201command, or the same command if no transformation occurs. Only done for 14202interactive sessions. 14203 14204This may be fired multiple times per command, with other transformers input or 14205output, so design any handlers for this carefully. 14206""", 14207) 14208 14209events.doc( 14210 "on_precommand", 14211 """ 14212on_precommand(cmd: str) -> None 14213 14214Fires just before a command is executed. 14215""", 14216) 14217 14218events.doc( 14219 "on_postcommand", 14220 """ 14221on_postcommand(cmd: str, rtn: int, out: str or None, ts: list) -> None 14222 14223Fires just after a command is executed. The arguments are the same as history. 14224 14225Parameters: 14226 14227* ``cmd``: The command that was executed (after transformation) 14228* ``rtn``: The result of the command executed (``0`` for success) 14229* ``out``: If xonsh stores command output, this is the output 14230* ``ts``: Timestamps, in the order of ``[starting, ending]`` 14231""", 14232) 14233 14234events.doc( 14235 "on_pre_prompt", 14236 """ 14237on_first_prompt() -> None 14238 14239Fires just before the prompt is shown 14240""", 14241) 14242 14243events.doc( 14244 "on_post_prompt", 14245 """ 14246on_first_prompt() -> None 14247 14248Fires just after the prompt returns 14249""", 14250) 14251 14252 14253def transform_command(src, show_diff=True): 14254 """Returns the results of firing the precommand handles.""" 14255 i = 0 14256 limit = sys.getrecursionlimit() 14257 lst = "" 14258 raw = src 14259 while src != lst: 14260 lst = src 14261 srcs = events.on_transform_command.fire(cmd=src) 14262 for s in srcs: 14263 if s != lst: 14264 src = s 14265 break 14266 i += 1 14267 if i == limit: 14268 print_exception( 14269 "Modifications to source input took more than " 14270 "the recursion limit number of iterations to " 14271 "converge." 14272 ) 14273 debug_level = builtins.__xonsh_env__.get("XONSH_DEBUG") 14274 if show_diff and debug_level > 1 and src != raw: 14275 sys.stderr.writelines( 14276 difflib.unified_diff( 14277 raw.splitlines(keepends=True), 14278 src.splitlines(keepends=True), 14279 fromfile="before precommand event", 14280 tofile="after precommand event", 14281 ) 14282 ) 14283 return src 14284 14285 14286class Shell(object): 14287 """Main xonsh shell. 14288 14289 Initializes execution environment and decides if prompt_toolkit or 14290 readline version of shell should be used. 14291 """ 14292 14293 shell_type_aliases = { 14294 "b": "best", 14295 "best": "best", 14296 "ptk": "prompt_toolkit", 14297 "ptk1": "prompt_toolkit1", 14298 "ptk2": "prompt_toolkit2", 14299 "prompt-toolkit": "prompt_toolkit", 14300 "prompt_toolkit": "prompt_toolkit", 14301 "prompt-toolkit1": "prompt_toolkit1", 14302 "prompt-toolkit2": "prompt_toolkit2", 14303 "rand": "random", 14304 "random": "random", 14305 "rl": "readline", 14306 "readline": "readline", 14307 } 14308 14309 def __init__(self, execer, ctx=None, shell_type=None, **kwargs): 14310 """ 14311 Parameters 14312 ---------- 14313 execer : Execer 14314 An execer instance capable of running xonsh code. 14315 ctx : Mapping, optional 14316 The execution context for the shell (e.g. the globals namespace). 14317 If none, this is computed by loading the rc files. If not None, 14318 this no additional context is computed and this is used 14319 directly. 14320 shell_type : str, optional 14321 The shell type to start, such as 'readline', 'prompt_toolkit1', 14322 or 'random'. 14323 """ 14324 self.execer = execer 14325 self.ctx = {} if ctx is None else ctx 14326 env = builtins.__xonsh_env__ 14327 # build history backend before creating shell 14328 builtins.__xonsh_history__ = hist = xhm.construct_history( 14329 env=env.detype(), ts=[time.time(), None], locked=True 14330 ) 14331 14332 # pick a valid shell -- if no shell is specified by the user, 14333 # shell type is pulled from env 14334 if shell_type is None: 14335 shell_type = env.get("SHELL_TYPE") 14336 if shell_type == "none": 14337 # This bricks interactive xonsh 14338 # Can happen from the use of .xinitrc, .xsession, etc 14339 shell_type = "best" 14340 shell_type = self.shell_type_aliases.get(shell_type, shell_type) 14341 if shell_type == "best" or shell_type is None: 14342 shell_type = best_shell_type() 14343 elif shell_type == "random": 14344 shell_type = random.choice(("readline", "prompt_toolkit")) 14345 if shell_type == "prompt_toolkit": 14346 if not has_prompt_toolkit(): 14347 warnings.warn( 14348 "prompt_toolkit is not available, using " "readline instead." 14349 ) 14350 shell_type = "readline" 14351 elif not ptk_above_min_supported(): 14352 warnings.warn( 14353 "prompt-toolkit version < v1.0.0 is not " 14354 "supported. Please update prompt-toolkit. Using " 14355 "readline instead." 14356 ) 14357 shell_type = "readline" 14358 else: 14359 shell_type = ptk_shell_type() 14360 self.shell_type = env["SHELL_TYPE"] = shell_type 14361 # actually make the shell 14362 if shell_type == "none": 14363 from xonsh.base_shell import BaseShell as shell_class 14364 elif shell_type == "prompt_toolkit2": 14365 from xonsh.ptk2.shell import PromptToolkit2Shell as shell_class 14366 elif shell_type == "prompt_toolkit1": 14367 from xonsh.ptk.shell import PromptToolkitShell as shell_class 14368 elif shell_type == "readline": 14369 from xonsh.readline_shell import ReadlineShell as shell_class 14370 elif shell_type == "jupyter": 14371 from xonsh.jupyter_shell import JupyterShell as shell_class 14372 else: 14373 raise XonshError("{} is not recognized as a shell type".format(shell_type)) 14374 self.shell = shell_class(execer=self.execer, ctx=self.ctx, **kwargs) 14375 # allows history garbage collector to start running 14376 if hist.gc is not None: 14377 hist.gc.wait_for_shell = False 14378 14379 def __getattr__(self, attr): 14380 """Delegates calls to appropriate shell instance.""" 14381 return getattr(self.shell, attr) 14382 14383# 14384# style_tools 14385# 14386"""Xonsh color styling tools that simulate pygments, when it is unavailable.""" 14387# amalgamated builtins 14388# amalgamated string 14389from collections import defaultdict 14390 14391# amalgamated xonsh.platform 14392# amalgamated xonsh.lazyasd 14393# amalgamated xonsh.color_tools 14394class _TokenType(tuple): 14395 """ 14396 Forked from the pygments project 14397 https://bitbucket.org/birkenfeld/pygments-main 14398 Copyright (c) 2006-2017 by the respective authors, All rights reserved. 14399 See https://bitbucket.org/birkenfeld/pygments-main/raw/05818a4ef9891d9ac22c851f7b3ea4b4fce460ab/AUTHORS 14400 """ 14401 14402 parent = None 14403 14404 def split(self): 14405 buf = [] 14406 node = self 14407 while node is not None: 14408 buf.append(node) 14409 node = node.parent 14410 buf.reverse() 14411 return buf 14412 14413 def __init__(self, *args): 14414 # no need to call super.__init__ 14415 self.subtypes = set() 14416 14417 def __contains__(self, val): 14418 return self is val or (type(val) is self.__class__ and val[: len(self)] == self) 14419 14420 def __getattr__(self, val): 14421 if not val or not val[0].isupper(): 14422 return tuple.__getattribute__(self, val) 14423 new = _TokenType(self + (val,)) 14424 setattr(self, val, new) 14425 self.subtypes.add(new) 14426 new.parent = self 14427 return new 14428 14429 def __repr__(self): 14430 return "Token" + (self and "." or "") + ".".join(self) 14431 14432 def __copy__(self): 14433 # These instances are supposed to be singletons 14434 return self 14435 14436 def __deepcopy__(self, memo): 14437 # These instances are supposed to be singletons 14438 return self 14439 14440 14441Token = _TokenType() 14442Color = Token.Color 14443 14444 14445def partial_color_tokenize(template): 14446 """Tokenizes a template string containing colors. Will return a list 14447 of tuples mapping the token to the string which has that color. 14448 These sub-strings maybe templates themselves. 14449 """ 14450 if HAS_PYGMENTS and hasattr(builtins, "__xonsh_shell__"): 14451 styles = __xonsh_shell__.shell.styler.styles 14452 elif hasattr(builtins, "__xonsh_shell__"): 14453 styles = DEFAULT_STYLE_DICT 14454 else: 14455 styles = None 14456 color = Color.NO_COLOR 14457 try: 14458 toks, color = _partial_color_tokenize_main(template, styles) 14459 except Exception: 14460 toks = [(Color.NO_COLOR, template)] 14461 if styles is not None: 14462 styles[color] # ensure color is available 14463 return toks 14464 14465 14466def _partial_color_tokenize_main(template, styles): 14467 formatter = string.Formatter() 14468 bopen = "{" 14469 bclose = "}" 14470 colon = ":" 14471 expl = "!" 14472 color = Color.NO_COLOR 14473 fg = bg = None 14474 value = "" 14475 toks = [] 14476 for literal, field, spec, conv in formatter.parse(template): 14477 if field is None: 14478 value += literal 14479 elif field in KNOWN_COLORS or "#" in field: 14480 value += literal 14481 next_color, fg, bg = color_by_name(field, fg, bg) 14482 if next_color is not color: 14483 if len(value) > 0: 14484 toks.append((color, value)) 14485 if styles is not None: 14486 styles[color] # ensure color is available 14487 color = next_color 14488 value = "" 14489 elif field is not None: 14490 parts = [literal, bopen, field] 14491 if conv is not None and len(conv) > 0: 14492 parts.append(expl) 14493 parts.append(conv) 14494 if spec is not None and len(spec) > 0: 14495 parts.append(colon) 14496 parts.append(spec) 14497 parts.append(bclose) 14498 value += "".join(parts) 14499 else: 14500 value += literal 14501 toks.append((color, value)) 14502 return toks, color 14503 14504 14505def color_by_name(name, fg=None, bg=None): 14506 """Converts a color name to a color token, foreground name, 14507 and background name. Will take into consideration current foreground 14508 and background colors, if provided. 14509 14510 Parameters 14511 ---------- 14512 name : str 14513 Color name. 14514 fg : str, optional 14515 Foreground color name. 14516 bg : str, optional 14517 Background color name. 14518 14519 Returns 14520 ------- 14521 tok : Token 14522 Pygments Token.Color subclass 14523 fg : str or None 14524 New computed foreground color name. 14525 bg : str or None 14526 New computed background color name. 14527 """ 14528 name = name.upper() 14529 if name == "NO_COLOR": 14530 return Color.NO_COLOR, None, None 14531 m = RE_BACKGROUND.search(name) 14532 if m is None: # must be foreground color 14533 fg = norm_name(name) 14534 else: 14535 bg = norm_name(name) 14536 # assemble token 14537 if fg is None and bg is None: 14538 tokname = "NO_COLOR" 14539 elif fg is None: 14540 tokname = bg 14541 elif bg is None: 14542 tokname = fg 14543 else: 14544 tokname = fg + "__" + bg 14545 tok = getattr(Color, tokname) 14546 return tok, fg, bg 14547 14548 14549def norm_name(name): 14550 """Normalizes a color name.""" 14551 return name.replace("#", "HEX").replace("BGHEX", "BACKGROUND_HEX") 14552 14553 14554KNOWN_COLORS = LazyObject( 14555 lambda: frozenset( 14556 [ 14557 "BACKGROUND_BLACK", 14558 "BACKGROUND_BLUE", 14559 "BACKGROUND_CYAN", 14560 "BACKGROUND_GREEN", 14561 "BACKGROUND_INTENSE_BLACK", 14562 "BACKGROUND_INTENSE_BLUE", 14563 "BACKGROUND_INTENSE_CYAN", 14564 "BACKGROUND_INTENSE_GREEN", 14565 "BACKGROUND_INTENSE_PURPLE", 14566 "BACKGROUND_INTENSE_RED", 14567 "BACKGROUND_INTENSE_WHITE", 14568 "BACKGROUND_INTENSE_YELLOW", 14569 "BACKGROUND_PURPLE", 14570 "BACKGROUND_RED", 14571 "BACKGROUND_WHITE", 14572 "BACKGROUND_YELLOW", 14573 "BLACK", 14574 "BLUE", 14575 "BOLD_BLACK", 14576 "BOLD_BLUE", 14577 "BOLD_CYAN", 14578 "BOLD_GREEN", 14579 "BOLD_INTENSE_BLACK", 14580 "BOLD_INTENSE_BLUE", 14581 "BOLD_INTENSE_CYAN", 14582 "BOLD_INTENSE_GREEN", 14583 "BOLD_INTENSE_PURPLE", 14584 "BOLD_INTENSE_RED", 14585 "BOLD_INTENSE_WHITE", 14586 "BOLD_INTENSE_YELLOW", 14587 "BOLD_PURPLE", 14588 "BOLD_RED", 14589 "BOLD_UNDERLINE_BLACK", 14590 "BOLD_UNDERLINE_BLUE", 14591 "BOLD_UNDERLINE_CYAN", 14592 "BOLD_UNDERLINE_GREEN", 14593 "BOLD_UNDERLINE_INTENSE_BLACK", 14594 "BOLD_UNDERLINE_INTENSE_BLUE", 14595 "BOLD_UNDERLINE_INTENSE_CYAN", 14596 "BOLD_UNDERLINE_INTENSE_GREEN", 14597 "BOLD_UNDERLINE_INTENSE_PURPLE", 14598 "BOLD_UNDERLINE_INTENSE_RED", 14599 "BOLD_UNDERLINE_INTENSE_WHITE", 14600 "BOLD_UNDERLINE_INTENSE_YELLOW", 14601 "BOLD_UNDERLINE_PURPLE", 14602 "BOLD_UNDERLINE_RED", 14603 "BOLD_UNDERLINE_WHITE", 14604 "BOLD_UNDERLINE_YELLOW", 14605 "BOLD_WHITE", 14606 "BOLD_YELLOW", 14607 "CYAN", 14608 "GREEN", 14609 "INTENSE_BLACK", 14610 "INTENSE_BLUE", 14611 "INTENSE_CYAN", 14612 "INTENSE_GREEN", 14613 "INTENSE_PURPLE", 14614 "INTENSE_RED", 14615 "INTENSE_WHITE", 14616 "INTENSE_YELLOW", 14617 "NO_COLOR", 14618 "PURPLE", 14619 "RED", 14620 "UNDERLINE_BLACK", 14621 "UNDERLINE_BLUE", 14622 "UNDERLINE_CYAN", 14623 "UNDERLINE_GREEN", 14624 "UNDERLINE_INTENSE_BLACK", 14625 "UNDERLINE_INTENSE_BLUE", 14626 "UNDERLINE_INTENSE_CYAN", 14627 "UNDERLINE_INTENSE_GREEN", 14628 "UNDERLINE_INTENSE_PURPLE", 14629 "UNDERLINE_INTENSE_RED", 14630 "UNDERLINE_INTENSE_WHITE", 14631 "UNDERLINE_INTENSE_YELLOW", 14632 "UNDERLINE_PURPLE", 14633 "UNDERLINE_RED", 14634 "UNDERLINE_WHITE", 14635 "UNDERLINE_YELLOW", 14636 "WHITE", 14637 "YELLOW", 14638 ] 14639 ), 14640 globals(), 14641 "KNOWN_COLORS", 14642) 14643 14644DEFAULT_STYLE_DICT = LazyObject( 14645 lambda: defaultdict( 14646 lambda: "", 14647 { 14648 Token: "", 14649 Token.Aborted: "ansibrightblack", 14650 Token.AutoSuggestion: "ansibrightblack", 14651 Token.Color.BACKGROUND_BLACK: "bg:ansiblack", 14652 Token.Color.BACKGROUND_BLUE: "bg:ansiblue", 14653 Token.Color.BACKGROUND_CYAN: "bg:ansicyan", 14654 Token.Color.BACKGROUND_GREEN: "bg:ansigreen", 14655 Token.Color.BACKGROUND_INTENSE_BLACK: "bg:ansibrightblack", 14656 Token.Color.BACKGROUND_INTENSE_BLUE: "bg:ansibrightblue", 14657 Token.Color.BACKGROUND_INTENSE_CYAN: "bg:ansibrightcyan", 14658 Token.Color.BACKGROUND_INTENSE_GREEN: "bg:ansibrightgreen", 14659 Token.Color.BACKGROUND_INTENSE_PURPLE: "bg:ansibrightmagenta", 14660 Token.Color.BACKGROUND_INTENSE_RED: "bg:ansibrightred", 14661 Token.Color.BACKGROUND_INTENSE_WHITE: "bg:ansiwhite", 14662 Token.Color.BACKGROUND_INTENSE_YELLOW: "bg:ansibrightyellow", 14663 Token.Color.BACKGROUND_PURPLE: "bg:ansimagenta", 14664 Token.Color.BACKGROUND_RED: "bg:ansired", 14665 Token.Color.BACKGROUND_WHITE: "bg:ansigray", 14666 Token.Color.BACKGROUND_YELLOW: "bg:ansiyellow", 14667 Token.Color.BLACK: "ansiblack", 14668 Token.Color.BLUE: "ansiblue", 14669 Token.Color.BOLD_BLACK: "bold ansiblack", 14670 Token.Color.BOLD_BLUE: "bold ansiblue", 14671 Token.Color.BOLD_CYAN: "bold ansicyan", 14672 Token.Color.BOLD_GREEN: "bold ansigreen", 14673 Token.Color.BOLD_INTENSE_BLACK: "bold ansibrightblack", 14674 Token.Color.BOLD_INTENSE_BLUE: "bold ansibrightblue", 14675 Token.Color.BOLD_INTENSE_CYAN: "bold ansibrightcyan", 14676 Token.Color.BOLD_INTENSE_GREEN: "bold ansibrightgreen", 14677 Token.Color.BOLD_INTENSE_PURPLE: "bold ansibrightmagenta", 14678 Token.Color.BOLD_INTENSE_RED: "bold ansibrightred", 14679 Token.Color.BOLD_INTENSE_WHITE: "bold ansiwhite", 14680 Token.Color.BOLD_INTENSE_YELLOW: "bold ansibrightyellow", 14681 Token.Color.BOLD_PURPLE: "bold ansimagenta", 14682 Token.Color.BOLD_RED: "bold ansired", 14683 Token.Color.BOLD_UNDERLINE_BLACK: "bold underline ansiblack", 14684 Token.Color.BOLD_UNDERLINE_BLUE: "bold underline ansiblue", 14685 Token.Color.BOLD_UNDERLINE_CYAN: "bold underline ansicyan", 14686 Token.Color.BOLD_UNDERLINE_GREEN: "bold underline ansigreen", 14687 Token.Color.BOLD_UNDERLINE_INTENSE_BLACK: "bold underline ansibrightblack", 14688 Token.Color.BOLD_UNDERLINE_INTENSE_BLUE: "bold underline ansibrightblue", 14689 Token.Color.BOLD_UNDERLINE_INTENSE_CYAN: "bold underline ansibrightcyan", 14690 Token.Color.BOLD_UNDERLINE_INTENSE_GREEN: "bold underline ansibrightgreen", 14691 Token.Color.BOLD_UNDERLINE_INTENSE_PURPLE: "bold underline ansibrightmagenta", 14692 Token.Color.BOLD_UNDERLINE_INTENSE_RED: "bold underline ansibrightred", 14693 Token.Color.BOLD_UNDERLINE_INTENSE_WHITE: "bold underline ansiwhite", 14694 Token.Color.BOLD_UNDERLINE_INTENSE_YELLOW: "bold underline ansibrightyellow", 14695 Token.Color.BOLD_UNDERLINE_PURPLE: "bold underline ansimagenta", 14696 Token.Color.BOLD_UNDERLINE_RED: "bold underline ansired", 14697 Token.Color.BOLD_UNDERLINE_WHITE: "bold underline ansigray", 14698 Token.Color.BOLD_UNDERLINE_YELLOW: "bold underline ansiyellow", 14699 Token.Color.BOLD_WHITE: "bold ansigray", 14700 Token.Color.BOLD_YELLOW: "bold ansiyellow", 14701 Token.Color.CYAN: "ansicyan", 14702 Token.Color.GREEN: "ansigreen", 14703 Token.Color.INTENSE_BLACK: "ansibrightblack", 14704 Token.Color.INTENSE_BLUE: "ansibrightblue", 14705 Token.Color.INTENSE_CYAN: "ansibrightcyan", 14706 Token.Color.INTENSE_GREEN: "ansibrightgreen", 14707 Token.Color.INTENSE_PURPLE: "ansibrightmagenta", 14708 Token.Color.INTENSE_RED: "ansibrightred", 14709 Token.Color.INTENSE_WHITE: "ansiwhite", 14710 Token.Color.INTENSE_YELLOW: "ansibrightyellow", 14711 Token.Color.NO_COLOR: "noinherit", 14712 Token.Color.PURPLE: "ansimagenta", 14713 Token.Color.RED: "ansired", 14714 Token.Color.UNDERLINE_BLACK: "underline ansiblack", 14715 Token.Color.UNDERLINE_BLUE: "underline ansiblue", 14716 Token.Color.UNDERLINE_CYAN: "underline ansicyan", 14717 Token.Color.UNDERLINE_GREEN: "underline ansigreen", 14718 Token.Color.UNDERLINE_INTENSE_BLACK: "underline ansibrightblack", 14719 Token.Color.UNDERLINE_INTENSE_BLUE: "underline ansibrightblue", 14720 Token.Color.UNDERLINE_INTENSE_CYAN: "underline ansibrightcyan", 14721 Token.Color.UNDERLINE_INTENSE_GREEN: "underline ansibrightgreen", 14722 Token.Color.UNDERLINE_INTENSE_PURPLE: "underline ansibrightmagenta", 14723 Token.Color.UNDERLINE_INTENSE_RED: "underline ansibrightred", 14724 Token.Color.UNDERLINE_INTENSE_WHITE: "underline ansiwhite", 14725 Token.Color.UNDERLINE_INTENSE_YELLOW: "underline ansibrightyellow", 14726 Token.Color.UNDERLINE_PURPLE: "underline ansimagenta", 14727 Token.Color.UNDERLINE_RED: "underline ansired", 14728 Token.Color.UNDERLINE_WHITE: "underline ansigray", 14729 Token.Color.UNDERLINE_YELLOW: "underline ansiyellow", 14730 Token.Color.WHITE: "ansigray", 14731 Token.Color.YELLOW: "ansiyellow", 14732 Token.Comment: "underline ansicyan", 14733 Token.Comment.Hashbang: "", 14734 Token.Comment.Multiline: "", 14735 Token.Comment.Preproc: "underline ansiyellow", 14736 Token.Comment.PreprocFile: "", 14737 Token.Comment.Single: "", 14738 Token.Comment.Special: "", 14739 Token.Error: "ansibrightred", 14740 Token.Escape: "", 14741 Token.Generic: "", 14742 Token.Generic.Deleted: "ansired", 14743 Token.Generic.Emph: "underline", 14744 Token.Generic.Error: "bold ansibrightred", 14745 Token.Generic.Heading: "bold ansiblue", 14746 Token.Generic.Inserted: "ansibrightgreen", 14747 Token.Generic.Output: "ansiblue", 14748 Token.Generic.Prompt: "bold ansiblue", 14749 Token.Generic.Strong: "", 14750 Token.Generic.Subheading: "bold ansimagenta", 14751 Token.Generic.Traceback: "ansiblue", 14752 Token.Keyword: "bold ansigreen", 14753 Token.Keyword.Constant: "", 14754 Token.Keyword.Declaration: "", 14755 Token.Keyword.Namespace: "", 14756 Token.Keyword.Pseudo: "nobold", 14757 Token.Keyword.Reserved: "", 14758 Token.Keyword.Type: "nobold ansired", 14759 Token.Literal: "", 14760 Token.Literal.Date: "", 14761 Token.Literal.Number: "ansibrightblack", 14762 Token.Literal.Number.Bin: "", 14763 Token.Literal.Number.Float: "", 14764 Token.Literal.Number.Hex: "", 14765 Token.Literal.Number.Integer: "", 14766 Token.Literal.Number.Integer.Long: "", 14767 Token.Literal.Number.Oct: "", 14768 Token.Literal.String: "ansibrightred", 14769 Token.Literal.String.Affix: "", 14770 Token.Literal.String.Backtick: "", 14771 Token.Literal.String.Char: "", 14772 Token.Literal.String.Delimiter: "", 14773 Token.Literal.String.Doc: "underline", 14774 Token.Literal.String.Double: "", 14775 Token.Literal.String.Escape: "bold ansiyellow", 14776 Token.Literal.String.Heredoc: "", 14777 Token.Literal.String.Interpol: "bold ansimagenta", 14778 Token.Literal.String.Other: "ansigreen", 14779 Token.Literal.String.Regex: "ansimagenta", 14780 Token.Literal.String.Single: "", 14781 Token.Literal.String.Symbol: "ansiyellow", 14782 Token.Menu.Completions: "bg:ansigray ansiblack", 14783 Token.Menu.Completions.Completion: "", 14784 Token.Menu.Completions.Completion.Current: "bg:ansibrightblack ansiwhite", 14785 Token.Name: "", 14786 Token.Name.Attribute: "ansibrightyellow", 14787 Token.Name.Builtin: "ansigreen", 14788 Token.Name.Builtin.Pseudo: "", 14789 Token.Name.Class: "bold ansibrightblue", 14790 Token.Name.Constant: "ansired", 14791 Token.Name.Decorator: "ansibrightmagenta", 14792 Token.Name.Entity: "bold ansigray", 14793 Token.Name.Exception: "bold ansibrightred", 14794 Token.Name.Function: "ansibrightblue", 14795 Token.Name.Function.Magic: "", 14796 Token.Name.Label: "ansibrightyellow", 14797 Token.Name.Namespace: "bold ansibrightblue", 14798 Token.Name.Other: "", 14799 Token.Name.Property: "", 14800 Token.Name.Tag: "bold ansigreen", 14801 Token.Name.Variable: "ansiblue", 14802 Token.Name.Variable.Class: "", 14803 Token.Name.Variable.Global: "", 14804 Token.Name.Variable.Instance: "", 14805 Token.Name.Variable.Magic: "", 14806 Token.Operator: "ansibrightblack", 14807 Token.Operator.Word: "bold ansimagenta", 14808 Token.Other: "", 14809 Token.Punctuation: "", 14810 Token.Scrollbar: "bg:ansibrightblack", 14811 Token.Scrollbar.Arrow: "bg:ansiblack ansiwhite bold", 14812 Token.Scrollbar.Button: "bg:ansiblack", 14813 Token.Text: "", 14814 Token.Text.Whitespace: "ansigray", 14815 }, 14816 ), 14817 globals(), 14818 "DEFAULT_STYLE_DICT", 14819) 14820 14821# 14822# timings 14823# 14824# -*- coding: utf-8 -*- 14825"""Timing related functionality for the xonsh shell. 14826 14827The following time_it alias and Timer was forked from the IPython project: 14828* Copyright (c) 2008-2014, IPython Development Team 14829* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> 14830* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 14831* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 14832""" 14833# amalgamated os 14834gc = _LazyModule.load('gc', 'gc') 14835# amalgamated sys 14836# amalgamated math 14837# amalgamated time 14838timeit = _LazyModule.load('timeit', 'timeit') 14839# amalgamated builtins 14840# amalgamated itertools 14841# amalgamated xonsh.lazyasd 14842# amalgamated xonsh.events 14843# amalgamated xonsh.platform 14844@lazybool 14845def _HAVE_RESOURCE(): 14846 try: 14847 import resource as r 14848 14849 have = True 14850 except ImportError: 14851 # There is no distinction of user/system time under windows, so we 14852 # just use time.perf_counter() for everything... 14853 have = False 14854 return have 14855 14856 14857@lazyobject 14858def resource(): 14859 import resource as r 14860 14861 return r 14862 14863 14864@lazyobject 14865def clocku(): 14866 if _HAVE_RESOURCE: 14867 14868 def clocku(): 14869 """clocku() -> floating point number 14870 Return the *USER* CPU time in seconds since the start of the 14871 process.""" 14872 return resource.getrusage(resource.RUSAGE_SELF)[0] 14873 14874 else: 14875 clocku = time.perf_counter 14876 return clocku 14877 14878 14879@lazyobject 14880def clocks(): 14881 if _HAVE_RESOURCE: 14882 14883 def clocks(): 14884 """clocks() -> floating point number 14885 Return the *SYSTEM* CPU time in seconds since the start of the 14886 process.""" 14887 return resource.getrusage(resource.RUSAGE_SELF)[1] 14888 14889 else: 14890 clocks = time.perf_counter 14891 return clocks 14892 14893 14894@lazyobject 14895def clock(): 14896 if _HAVE_RESOURCE: 14897 14898 def clock(): 14899 """clock() -> floating point number 14900 Return the *TOTAL USER+SYSTEM* CPU time in seconds since the 14901 start of the process.""" 14902 u, s = resource.getrusage(resource.RUSAGE_SELF)[:2] 14903 return u + s 14904 14905 else: 14906 clock = time.perf_counter 14907 return clock 14908 14909 14910@lazyobject 14911def clock2(): 14912 if _HAVE_RESOURCE: 14913 14914 def clock2(): 14915 """clock2() -> (t_user,t_system) 14916 Similar to clock(), but return a tuple of user/system times.""" 14917 return resource.getrusage(resource.RUSAGE_SELF)[:2] 14918 14919 else: 14920 14921 def clock2(): 14922 """Under windows, system CPU time can't be measured. 14923 This just returns perf_counter() and zero.""" 14924 return time.perf_counter(), 0.0 14925 14926 return clock2 14927 14928 14929def format_time(timespan, precision=3): 14930 """Formats the timespan in a human readable form""" 14931 if timespan >= 60.0: 14932 # we have more than a minute, format that in a human readable form 14933 parts = [("d", 60 * 60 * 24), ("h", 60 * 60), ("min", 60), ("s", 1)] 14934 time = [] 14935 leftover = timespan 14936 for suffix, length in parts: 14937 value = int(leftover / length) 14938 if value > 0: 14939 leftover = leftover % length 14940 time.append("{0}{1}".format(str(value), suffix)) 14941 if leftover < 1: 14942 break 14943 return " ".join(time) 14944 # Unfortunately the unicode 'micro' symbol can cause problems in 14945 # certain terminals. 14946 # See bug: https://bugs.launchpad.net/ipython/+bug/348466 14947 # Try to prevent crashes by being more secure than it needs to 14948 # E.g. eclipse is able to print a mu, but has no sys.stdout.encoding set. 14949 units = ["s", "ms", "us", "ns"] # the save value 14950 if hasattr(sys.stdout, "encoding") and sys.stdout.encoding: 14951 try: 14952 "\xb5".encode(sys.stdout.encoding) 14953 units = ["s", "ms", "\xb5s", "ns"] 14954 except Exception: 14955 pass 14956 scaling = [1, 1e3, 1e6, 1e9] 14957 14958 if timespan > 0.0: 14959 order = min(-int(math.floor(math.log10(timespan)) // 3), 3) 14960 else: 14961 order = 3 14962 return "{1:.{0}g} {2}".format(precision, timespan * scaling[order], units[order]) 14963 14964 14965class Timer(timeit.Timer): 14966 """Timer class that explicitly uses self.inner 14967 which is an undocumented implementation detail of CPython, 14968 not shared by PyPy. 14969 """ 14970 14971 # Timer.timeit copied from CPython 3.4.2 14972 def timeit(self, number=timeit.default_number): 14973 """Time 'number' executions of the main statement. 14974 To be precise, this executes the setup statement once, and 14975 then returns the time it takes to execute the main statement 14976 a number of times, as a float measured in seconds. The 14977 argument is the number of times through the loop, defaulting 14978 to one million. The main statement, the setup statement and 14979 the timer function to be used are passed to the constructor. 14980 """ 14981 it = itertools.repeat(None, number) 14982 gcold = gc.isenabled() 14983 gc.disable() 14984 try: 14985 timing = self.inner(it, self.timer) 14986 finally: 14987 if gcold: 14988 gc.enable() 14989 return timing 14990 14991 14992INNER_TEMPLATE = """ 14993def inner(_it, _timer): 14994 #setup 14995 _t0 = _timer() 14996 for _i in _it: 14997 {stmt} 14998 _t1 = _timer() 14999 return _t1 - _t0 15000""" 15001 15002 15003def timeit_alias(args, stdin=None): 15004 """Runs timing study on arguments.""" 15005 # some real args 15006 number = 0 15007 quiet = False 15008 repeat = 3 15009 precision = 3 15010 # setup 15011 ctx = builtins.__xonsh_ctx__ 15012 timer = Timer(timer=clock) 15013 stmt = " ".join(args) 15014 innerstr = INNER_TEMPLATE.format(stmt=stmt) 15015 # Track compilation time so it can be reported if too long 15016 # Minimum time above which compilation time will be reported 15017 tc_min = 0.1 15018 t0 = clock() 15019 innercode = builtins.compilex( 15020 innerstr, filename="<xonsh-timeit>", mode="exec", glbs=ctx 15021 ) 15022 tc = clock() - t0 15023 # get inner func 15024 ns = {} 15025 builtins.execx(innercode, glbs=ctx, locs=ns, mode="exec") 15026 timer.inner = ns["inner"] 15027 # Check if there is a huge difference between the best and worst timings. 15028 worst_tuning = 0 15029 if number == 0: 15030 # determine number so that 0.2 <= total time < 2.0 15031 number = 1 15032 for _ in range(1, 10): 15033 time_number = timer.timeit(number) 15034 worst_tuning = max(worst_tuning, time_number / number) 15035 if time_number >= 0.2: 15036 break 15037 number *= 10 15038 all_runs = timer.repeat(repeat, number) 15039 best = min(all_runs) / number 15040 # print some debug info 15041 if not quiet: 15042 worst = max(all_runs) / number 15043 if worst_tuning: 15044 worst = max(worst, worst_tuning) 15045 # Check best timing is greater than zero to avoid a 15046 # ZeroDivisionError. 15047 # In cases where the slowest timing is less than 10 microseconds 15048 # we assume that it does not really matter if the fastest 15049 # timing is 4 times faster than the slowest timing or not. 15050 if worst > 4 * best and best > 0 and worst > 1e-5: 15051 print( 15052 ( 15053 "The slowest run took {0:0.2f} times longer than the " 15054 "fastest. This could mean that an intermediate result " 15055 "is being cached." 15056 ).format(worst / best) 15057 ) 15058 print( 15059 "{0} loops, best of {1}: {2} per loop".format( 15060 number, repeat, format_time(best, precision) 15061 ) 15062 ) 15063 if tc > tc_min: 15064 print("Compiler time: {0:.2f} s".format(tc)) 15065 return 15066 15067 15068_timings = {"start": clock()} 15069 15070 15071def setup_timings(): 15072 global _timings 15073 if "--timings" in sys.argv: 15074 events.doc( 15075 "on_timingprobe", 15076 """ 15077 on_timingprobe(name: str) -> None 15078 15079 Fired to insert some timings into the startuptime list 15080 """, 15081 ) 15082 15083 @events.on_timingprobe 15084 def timing_on_timingprobe(name, **kw): 15085 global _timings 15086 _timings[name] = clock() 15087 15088 @events.on_post_cmdloop 15089 def timing_on_post_cmdloop(**kw): 15090 global _timings 15091 _timings["on_post_cmdloop"] = clock() 15092 15093 @events.on_post_init 15094 def timing_on_post_init(**kw): 15095 global _timings 15096 _timings["on_post_init"] = clock() 15097 15098 @events.on_post_rc 15099 def timing_on_post_rc(**kw): 15100 global _timings 15101 _timings["on_post_rc"] = clock() 15102 15103 @events.on_postcommand 15104 def timing_on_postcommand(**kw): 15105 global _timings 15106 _timings["on_postcommand"] = clock() 15107 15108 @events.on_pre_cmdloop 15109 def timing_on_pre_cmdloop(**kw): 15110 global _timings 15111 _timings["on_pre_cmdloop"] = clock() 15112 15113 @events.on_pre_rc 15114 def timing_on_pre_rc(**kw): 15115 global _timings 15116 _timings["on_pre_rc"] = clock() 15117 15118 @events.on_precommand 15119 def timing_on_precommand(**kw): 15120 global _timings 15121 _timings["on_precommand"] = clock() 15122 15123 @events.on_ptk_create 15124 def timing_on_ptk_create(**kw): 15125 global _timings 15126 _timings["on_ptk_create"] = clock() 15127 15128 @events.on_chdir 15129 def timing_on_chdir(**kw): 15130 global _timings 15131 _timings["on_chdir"] = clock() 15132 15133 @events.on_post_prompt 15134 def timing_on_post_prompt(**kw): 15135 global _timings 15136 _timings = {"on_post_prompt": clock()} 15137 15138 @events.on_pre_prompt 15139 def timing_on_pre_prompt(**kw): 15140 global _timings 15141 _timings["on_pre_prompt"] = clock() 15142 times = list(_timings.items()) 15143 times = sorted(times, key=lambda x: x[1]) 15144 width = max(len(s) for s, _ in times) + 2 15145 header_format = "|{{:<{}}}|{{:^11}}|{{:^11}}|".format(width) 15146 entry_format = "|{{:<{}}}|{{:^11.3f}}|{{:^11.3f}}|".format(width) 15147 sepline = "|{}|{}|{}|".format("-" * width, "-" * 11, "-" * 11) 15148 # Print result table 15149 print(" Debug level: {}".format(os.getenv("XONSH_DEBUG", "Off"))) 15150 print(sepline) 15151 print(header_format.format("Event name", "Time (s)", "Delta (s)")) 15152 print(sepline) 15153 prevtime = tstart = times[0][1] 15154 for name, ts in times: 15155 print(entry_format.format(name, ts - tstart, ts - prevtime)) 15156 prevtime = ts 15157 print(sepline) 15158 15159# 15160# xonfig 15161# 15162"""The xonsh configuration (xonfig) utility.""" 15163# amalgamated os 15164# amalgamated re 15165ast = _LazyModule.load('ast', 'ast') 15166# amalgamated json 15167shutil = _LazyModule.load('shutil', 'shutil') 15168# amalgamated random 15169pprint = _LazyModule.load('pprint', 'pprint') 15170# amalgamated textwrap 15171# amalgamated builtins 15172# amalgamated argparse 15173# amalgamated functools 15174# amalgamated itertools 15175# amalgamated contextlib 15176# amalgamated collections 15177try: 15178 import ply 15179except ImportError: 15180 from xonsh.ply import ply 15181 15182wiz = _LazyModule.load('xonsh', 'xonsh.wizard', 'wiz') 15183from xonsh import __version__ as XONSH_VERSION 15184from xonsh.prompt.base import is_template_string 15185# amalgamated xonsh.platform 15186# amalgamated xonsh.tools 15187# amalgamated xonsh.foreign_shells 15188# amalgamated xonsh.xontribs 15189# amalgamated xonsh.lazyasd 15190HR = "'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'" 15191WIZARD_HEAD = """ 15192 {{BOLD_WHITE}}Welcome to the xonsh configuration wizard!{{NO_COLOR}} 15193 {{YELLOW}}------------------------------------------{{NO_COLOR}} 15194This will present a guided tour through setting up the xonsh static 15195config file. Xonsh will automatically ask you if you want to run this 15196wizard if the configuration file does not exist. However, you can 15197always rerun this wizard with the xonfig command: 15198 15199 $ xonfig wizard 15200 15201This wizard will load an existing configuration, if it is available. 15202Also never fear when this wizard saves its results! It will create 15203a backup of any existing configuration automatically. 15204 15205This wizard has two main phases: foreign shell setup and environment 15206variable setup. Each phase may be skipped in its entirety. 15207 15208For the configuration to take effect, you will need to restart xonsh. 15209 15210{hr} 15211""".format( 15212 hr=HR 15213) 15214 15215WIZARD_FS = """ 15216{hr} 15217 15218 {{BOLD_WHITE}}Foreign Shell Setup{{NO_COLOR}} 15219 {{YELLOW}}-------------------{{NO_COLOR}} 15220The xonsh shell has the ability to interface with foreign shells such 15221as Bash, zsh, or fish. 15222 15223For configuration, this means that xonsh can load the environment, 15224aliases, and functions specified in the config files of these shells. 15225Naturally, these shells must be available on the system to work. 15226Being able to share configuration (and source) from foreign shells 15227makes it easier to transition to and from xonsh. 15228""".format( 15229 hr=HR 15230) 15231 15232WIZARD_ENV = """ 15233{hr} 15234 15235 {{BOLD_WHITE}}Environment Variable Setup{{NO_COLOR}} 15236 {{YELLOW}}--------------------------{{NO_COLOR}} 15237The xonsh shell also allows you to setup environment variables from 15238the static configuration file. Any variables set in this way are 15239superseded by the definitions in the xonshrc or on the command line. 15240Still, setting environment variables in this way can help define 15241options that are global to the system or user. 15242 15243The following lists the environment variable name, its documentation, 15244the default value, and the current value. The default and current 15245values are presented as pretty repr strings of their Python types. 15246 15247{{BOLD_GREEN}}Note:{{NO_COLOR}} Simply hitting enter for any environment variable 15248will accept the default value for that entry. 15249""".format( 15250 hr=HR 15251) 15252 15253WIZARD_ENV_QUESTION = "Would you like to set env vars now, " + wiz.YN 15254 15255WIZARD_XONTRIB = """ 15256{hr} 15257 15258 {{BOLD_WHITE}}Xontribs{{NO_COLOR}} 15259 {{YELLOW}}--------{{NO_COLOR}} 15260No shell is complete without extensions, and xonsh is no exception. Xonsh 15261extensions are called {{BOLD_GREEN}}xontribs{{NO_COLOR}}, or xonsh contributions. 15262Xontribs are dynamically loadable, either by importing them directly or by 15263using the 'xontrib' command. However, you can also configure xonsh to load 15264xontribs automatically on startup prior to loading the run control files. 15265This allows the xontrib to be used immediately in your xonshrc files. 15266 15267The following describes all xontribs that have been registered with xonsh. 15268These come from users, 3rd party developers, or xonsh itself! 15269""".format( 15270 hr=HR 15271) 15272 15273WIZARD_XONTRIB_QUESTION = "Would you like to enable xontribs now, " + wiz.YN 15274 15275WIZARD_TAIL = """ 15276Thanks for using the xonsh configuration wizard!""" 15277 15278 15279_XONFIG_SOURCE_FOREIGN_SHELL_COMMAND = collections.defaultdict( 15280 lambda: "source-foreign", bash="source-bash", cmd="source-cmd", zsh="source-zsh" 15281) 15282 15283 15284def _dump_xonfig_foreign_shell(path, value): 15285 shell = value["shell"] 15286 shell = CANON_SHELL_NAMES.get(shell, shell) 15287 cmd = [_XONFIG_SOURCE_FOREIGN_SHELL_COMMAND.get(shell)] 15288 interactive = value.get("interactive", None) 15289 if interactive is not None: 15290 cmd.extend(["--interactive", str(interactive)]) 15291 login = value.get("login", None) 15292 if login is not None: 15293 cmd.extend(["--login", str(login)]) 15294 envcmd = value.get("envcmd", None) 15295 if envcmd is not None: 15296 cmd.extend(["--envcmd", envcmd]) 15297 aliascmd = value.get("aliasmd", None) 15298 if aliascmd is not None: 15299 cmd.extend(["--aliascmd", aliascmd]) 15300 extra_args = value.get("extra_args", None) 15301 if extra_args: 15302 cmd.extend(["--extra-args", repr(" ".join(extra_args))]) 15303 safe = value.get("safe", None) 15304 if safe is not None: 15305 cmd.extend(["--safe", str(safe)]) 15306 prevcmd = value.get("prevcmd", "") 15307 if prevcmd: 15308 cmd.extend(["--prevcmd", repr(prevcmd)]) 15309 postcmd = value.get("postcmd", "") 15310 if postcmd: 15311 cmd.extend(["--postcmd", repr(postcmd)]) 15312 funcscmd = value.get("funcscmd", None) 15313 if funcscmd: 15314 cmd.extend(["--funcscmd", repr(funcscmd)]) 15315 sourcer = value.get("sourcer", None) 15316 if sourcer: 15317 cmd.extend(["--sourcer", sourcer]) 15318 if cmd[0] == "source-foreign": 15319 cmd.append(shell) 15320 cmd.append('"echo loading xonsh foreign shell"') 15321 return " ".join(cmd) 15322 15323 15324def _dump_xonfig_env(path, value): 15325 name = os.path.basename(path.rstrip("/")) 15326 ensurer = builtins.__xonsh_env__.get_ensurer(name) 15327 dval = ensurer.detype(value) 15328 return "${name} = {val!r}".format(name=name, val=dval) 15329 15330 15331def _dump_xonfig_xontribs(path, value): 15332 return "xontrib load {0}".format(" ".join(value)) 15333 15334 15335@lazyobject 15336def XONFIG_DUMP_RULES(): 15337 return { 15338 "/": None, 15339 "/env/": None, 15340 "/foreign_shells/*/": _dump_xonfig_foreign_shell, 15341 "/env/*": _dump_xonfig_env, 15342 "/env/*/[0-9]*": None, 15343 "/xontribs/": _dump_xonfig_xontribs, 15344 } 15345 15346 15347def make_fs_wiz(): 15348 """Makes the foreign shell part of the wizard.""" 15349 cond = wiz.create_truefalse_cond(prompt="Add a new foreign shell, " + wiz.YN) 15350 fs = wiz.While( 15351 cond=cond, 15352 body=[ 15353 wiz.Input("shell name (e.g. bash): ", path="/foreign_shells/{idx}/shell"), 15354 wiz.StoreNonEmpty( 15355 "interactive shell [bool, default=True]: ", 15356 converter=to_bool, 15357 show_conversion=True, 15358 path="/foreign_shells/{idx}/interactive", 15359 ), 15360 wiz.StoreNonEmpty( 15361 "login shell [bool, default=False]: ", 15362 converter=to_bool, 15363 show_conversion=True, 15364 path="/foreign_shells/{idx}/login", 15365 ), 15366 wiz.StoreNonEmpty( 15367 "env command [str, default='env']: ", 15368 path="/foreign_shells/{idx}/envcmd", 15369 ), 15370 wiz.StoreNonEmpty( 15371 "alias command [str, default='alias']: ", 15372 path="/foreign_shells/{idx}/aliascmd", 15373 ), 15374 wiz.StoreNonEmpty( 15375 ("extra command line arguments [list of str, " "default=[]]: "), 15376 converter=ast.literal_eval, 15377 show_conversion=True, 15378 path="/foreign_shells/{idx}/extra_args", 15379 ), 15380 wiz.StoreNonEmpty( 15381 "safely handle exceptions [bool, default=True]: ", 15382 converter=to_bool, 15383 show_conversion=True, 15384 path="/foreign_shells/{idx}/safe", 15385 ), 15386 wiz.StoreNonEmpty( 15387 "pre-command [str, default='']: ", path="/foreign_shells/{idx}/prevcmd" 15388 ), 15389 wiz.StoreNonEmpty( 15390 "post-command [str, default='']: ", path="/foreign_shells/{idx}/postcmd" 15391 ), 15392 wiz.StoreNonEmpty( 15393 "foreign function command [str, default=None]: ", 15394 path="/foreign_shells/{idx}/funcscmd", 15395 ), 15396 wiz.StoreNonEmpty( 15397 "source command [str, default=None]: ", 15398 path="/foreign_shells/{idx}/sourcer", 15399 ), 15400 wiz.Message(message="Foreign shell added.\n"), 15401 ], 15402 ) 15403 return fs 15404 15405 15406def _wrap_paragraphs(text, width=70, **kwargs): 15407 """Wraps paragraphs instead.""" 15408 pars = text.split("\n") 15409 pars = ["\n".join(textwrap.wrap(p, width=width, **kwargs)) for p in pars] 15410 s = "\n".join(pars) 15411 return s 15412 15413 15414ENVVAR_MESSAGE = """ 15415{{BOLD_CYAN}}${name}{{NO_COLOR}} 15416{docstr} 15417{{RED}}default value:{{NO_COLOR}} {default} 15418{{RED}}current value:{{NO_COLOR}} {current}""" 15419 15420ENVVAR_PROMPT = "{BOLD_GREEN}>>>{NO_COLOR} " 15421 15422 15423def make_exit_message(): 15424 """Creates a message for how to exit the wizard.""" 15425 shell_type = builtins.__xonsh_shell__.shell_type 15426 keyseq = "Ctrl-D" if shell_type == "readline" else "Ctrl-C" 15427 msg = "To exit the wizard at any time, press {BOLD_UNDERLINE_CYAN}" 15428 msg += keyseq + "{NO_COLOR}.\n" 15429 m = wiz.Message(message=msg) 15430 return m 15431 15432 15433def make_envvar(name): 15434 """Makes a StoreNonEmpty node for an environment variable.""" 15435 env = builtins.__xonsh_env__ 15436 vd = env.get_docs(name) 15437 if not vd.configurable: 15438 return 15439 default = vd.default 15440 if "\n" in default: 15441 default = "\n" + _wrap_paragraphs(default, width=69) 15442 curr = env.get(name) 15443 if is_string(curr) and is_template_string(curr): 15444 curr = curr.replace("{", "{{").replace("}", "}}") 15445 curr = pprint.pformat(curr, width=69) 15446 if "\n" in curr: 15447 curr = "\n" + curr 15448 msg = ENVVAR_MESSAGE.format( 15449 name=name, 15450 default=default, 15451 current=curr, 15452 docstr=_wrap_paragraphs(vd.docstr, width=69), 15453 ) 15454 mnode = wiz.Message(message=msg) 15455 ens = env.get_ensurer(name) 15456 path = "/env/" + name 15457 pnode = wiz.StoreNonEmpty( 15458 ENVVAR_PROMPT, 15459 converter=ens.convert, 15460 show_conversion=True, 15461 path=path, 15462 retry=True, 15463 store_raw=vd.store_as_str, 15464 ) 15465 return mnode, pnode 15466 15467 15468def _make_flat_wiz(kidfunc, *args): 15469 kids = map(kidfunc, *args) 15470 flatkids = [] 15471 for k in kids: 15472 if k is None: 15473 continue 15474 flatkids.extend(k) 15475 wizard = wiz.Wizard(children=flatkids) 15476 return wizard 15477 15478 15479def make_env_wiz(): 15480 """Makes an environment variable wizard.""" 15481 w = _make_flat_wiz(make_envvar, sorted(builtins.__xonsh_env__._docs.keys())) 15482 return w 15483 15484 15485XONTRIB_PROMPT = "{BOLD_GREEN}Add this xontrib{NO_COLOR}, " + wiz.YN 15486 15487 15488def _xontrib_path(visitor=None, node=None, val=None): 15489 # need this to append only based on user-selected size 15490 return ("xontribs", len(visitor.state.get("xontribs", ()))) 15491 15492 15493def make_xontrib(xontrib, package): 15494 """Makes a message and StoreNonEmpty node for a xontrib.""" 15495 name = xontrib.get("name", "<unknown-xontrib-name>") 15496 msg = "\n{BOLD_CYAN}" + name + "{NO_COLOR}\n" 15497 if "url" in xontrib: 15498 msg += "{RED}url:{NO_COLOR} " + xontrib["url"] + "\n" 15499 if "package" in xontrib: 15500 msg += "{RED}package:{NO_COLOR} " + xontrib["package"] + "\n" 15501 if "url" in package: 15502 if "url" in xontrib and package["url"] != xontrib["url"]: 15503 msg += "{RED}package-url:{NO_COLOR} " + package["url"] + "\n" 15504 if "license" in package: 15505 msg += "{RED}license:{NO_COLOR} " + package["license"] + "\n" 15506 msg += "{PURPLE}installed?{NO_COLOR} " 15507 msg += ("no" if find_xontrib(name) is None else "yes") + "\n" 15508 desc = xontrib.get("description", "") 15509 if not isinstance(desc, str): 15510 desc = "".join(desc) 15511 msg += _wrap_paragraphs(desc, width=69) 15512 if msg.endswith("\n"): 15513 msg = msg[:-1] 15514 mnode = wiz.Message(message=msg) 15515 convert = lambda x: name if to_bool(x) else wiz.Unstorable 15516 pnode = wiz.StoreNonEmpty(XONTRIB_PROMPT, converter=convert, path=_xontrib_path) 15517 return mnode, pnode 15518 15519 15520def make_xontribs_wiz(): 15521 """Makes a xontrib wizard.""" 15522 md = xontrib_metadata() 15523 pkgs = [md["packages"].get(d.get("package", None), {}) for d in md["xontribs"]] 15524 w = _make_flat_wiz(make_xontrib, md["xontribs"], pkgs) 15525 return w 15526 15527 15528def make_xonfig_wizard(default_file=None, confirm=False, no_wizard_file=None): 15529 """Makes a configuration wizard for xonsh config file. 15530 15531 Parameters 15532 ---------- 15533 default_file : str, optional 15534 Default filename to save and load to. User will still be prompted. 15535 confirm : bool, optional 15536 Confirm that the main part of the wizard should be run. 15537 no_wizard_file : str, optional 15538 Filename for that will flag to future runs that the wizard should not be 15539 run again. If None (default), this defaults to default_file. 15540 """ 15541 w = wiz.Wizard( 15542 children=[ 15543 wiz.Message(message=WIZARD_HEAD), 15544 make_exit_message(), 15545 wiz.Message(message=WIZARD_FS), 15546 make_fs_wiz(), 15547 wiz.Message(message=WIZARD_ENV), 15548 wiz.YesNo(question=WIZARD_ENV_QUESTION, yes=make_env_wiz(), no=wiz.Pass()), 15549 wiz.Message(message=WIZARD_XONTRIB), 15550 wiz.YesNo( 15551 question=WIZARD_XONTRIB_QUESTION, yes=make_xontribs_wiz(), no=wiz.Pass() 15552 ), 15553 wiz.Message(message="\n" + HR + "\n"), 15554 wiz.FileInserter( 15555 prefix="# XONSH WIZARD START", 15556 suffix="# XONSH WIZARD END", 15557 dump_rules=XONFIG_DUMP_RULES, 15558 default_file=default_file, 15559 check=True, 15560 ), 15561 wiz.Message(message=WIZARD_TAIL), 15562 ] 15563 ) 15564 if confirm: 15565 q = ( 15566 "Would you like to run the xonsh configuration wizard now?\n\n" 15567 "1. Yes (You can abort at any time)\n" 15568 "2. No, but ask me next time.\n" 15569 "3. No, and don't ask me again.\n\n" 15570 "1, 2, or 3 [default: 2]? " 15571 ) 15572 no_wizard_file = default_file if no_wizard_file is None else no_wizard_file 15573 passer = wiz.Pass() 15574 saver = wiz.SaveJSON( 15575 check=False, ask_filename=False, default_file=no_wizard_file 15576 ) 15577 w = wiz.Question( 15578 q, {1: w, 2: passer, 3: saver}, converter=lambda x: int(x) if x != "" else 2 15579 ) 15580 return w 15581 15582 15583def _wizard(ns): 15584 env = builtins.__xonsh_env__ 15585 shell = builtins.__xonsh_shell__.shell 15586 fname = env.get("XONSHRC")[-1] if ns.file is None else ns.file 15587 no_wiz = os.path.join(env.get("XONSH_CONFIG_DIR"), "no-wizard") 15588 w = make_xonfig_wizard( 15589 default_file=fname, confirm=ns.confirm, no_wizard_file=no_wiz 15590 ) 15591 tempenv = {"PROMPT": "", "XONSH_STORE_STDOUT": False} 15592 pv = wiz.PromptVisitor(w, store_in_history=False, multiline=False) 15593 15594 @contextlib.contextmanager 15595 def force_hide(): 15596 if env.get("XONSH_STORE_STDOUT") and hasattr(shell, "_force_hide"): 15597 orig, shell._force_hide = shell._force_hide, False 15598 yield 15599 shell._force_hide = orig 15600 else: 15601 yield 15602 15603 with force_hide(), env.swap(tempenv): 15604 try: 15605 pv.visit() 15606 except (KeyboardInterrupt, Exception): 15607 print() 15608 print_exception() 15609 15610 15611def _xonfig_format_human(data): 15612 wcol1 = wcol2 = 0 15613 for key, val in data: 15614 wcol1 = max(wcol1, len(key)) 15615 wcol2 = max(wcol2, len(str(val))) 15616 hr = "+" + ("-" * (wcol1 + 2)) + "+" + ("-" * (wcol2 + 2)) + "+\n" 15617 row = "| {key!s:<{wcol1}} | {val!s:<{wcol2}} |\n" 15618 s = hr 15619 for key, val in data: 15620 s += row.format(key=key, wcol1=wcol1, val=val, wcol2=wcol2) 15621 s += hr 15622 return s 15623 15624 15625def _xonfig_format_json(data): 15626 data = {k.replace(" ", "_"): v for k, v in data} 15627 s = json.dumps(data, sort_keys=True, indent=1) + "\n" 15628 return s 15629 15630 15631def _info(ns): 15632 env = builtins.__xonsh_env__ 15633 try: 15634 ply.__version__ = ply.__version__ 15635 except AttributeError: 15636 ply.__version__ = "3.8" 15637 data = [("xonsh", XONSH_VERSION)] 15638 hash_, date_ = githash() 15639 if hash_: 15640 data.append(("Git SHA", hash_)) 15641 data.append(("Commit Date", date_)) 15642 data.extend( 15643 [ 15644 ("Python", "{}.{}.{}".format(*PYTHON_VERSION_INFO)), 15645 ("PLY", ply.__version__), 15646 ("have readline", is_readline_available()), 15647 ("prompt toolkit", ptk_version() or None), 15648 ("shell type", env.get("SHELL_TYPE")), 15649 ("pygments", pygments_version()), 15650 ("on posix", bool(ON_POSIX)), 15651 ("on linux", bool(ON_LINUX)), 15652 ] 15653 ) 15654 if ON_LINUX: 15655 data.append(("distro", linux_distro())) 15656 data.extend( 15657 [ 15658 ("on darwin", ON_DARWIN), 15659 ("on windows", ON_WINDOWS), 15660 ("on cygwin", ON_CYGWIN), 15661 ("on msys2", ON_MSYS), 15662 ("is superuser", is_superuser()), 15663 ("default encoding", DEFAULT_ENCODING), 15664 ("xonsh encoding", env.get("XONSH_ENCODING")), 15665 ("encoding errors", env.get("XONSH_ENCODING_ERRORS")), 15666 ] 15667 ) 15668 formatter = _xonfig_format_json if ns.json else _xonfig_format_human 15669 s = formatter(data) 15670 return s 15671 15672 15673def _styles(ns): 15674 env = builtins.__xonsh_env__ 15675 curr = env.get("XONSH_COLOR_STYLE") 15676 styles = sorted(color_style_names()) 15677 if ns.json: 15678 s = json.dumps(styles, sort_keys=True, indent=1) 15679 print(s) 15680 return 15681 lines = [] 15682 for style in styles: 15683 if style == curr: 15684 lines.append("* {GREEN}" + style + "{NO_COLOR}") 15685 else: 15686 lines.append(" " + style) 15687 s = "\n".join(lines) 15688 print_color(s) 15689 15690 15691def _str_colors(cmap, cols): 15692 color_names = sorted(cmap.keys(), key=(lambda s: (len(s), s))) 15693 grper = lambda s: min(cols // (len(s) + 1), 8) 15694 lines = [] 15695 for n, group in itertools.groupby(color_names, key=grper): 15696 width = cols // n 15697 line = "" 15698 for i, name in enumerate(group): 15699 buf = " " * (width - len(name)) 15700 line += "{" + name + "}" + name + "{NO_COLOR}" + buf 15701 if (i + 1) % n == 0: 15702 lines.append(line) 15703 line = "" 15704 if len(line) != 0: 15705 lines.append(line) 15706 return "\n".join(lines) 15707 15708 15709def _tok_colors(cmap, cols): 15710 from xonsh.style_tools import Color 15711 15712 nc = Color.NO_COLOR 15713 names_toks = {} 15714 for t in cmap.keys(): 15715 name = str(t) 15716 if name.startswith("Token.Color."): 15717 _, _, name = name.rpartition(".") 15718 names_toks[name] = t 15719 color_names = sorted(names_toks.keys(), key=(lambda s: (len(s), s))) 15720 grper = lambda s: min(cols // (len(s) + 1), 8) 15721 toks = [] 15722 for n, group in itertools.groupby(color_names, key=grper): 15723 width = cols // n 15724 for i, name in enumerate(group): 15725 toks.append((names_toks[name], name)) 15726 buf = " " * (width - len(name)) 15727 if (i + 1) % n == 0: 15728 buf += "\n" 15729 toks.append((nc, buf)) 15730 if not toks[-1][1].endswith("\n"): 15731 toks[-1] = (nc, toks[-1][1] + "\n") 15732 return toks 15733 15734 15735def _colors(args): 15736 columns, _ = shutil.get_terminal_size() 15737 columns -= int(ON_WINDOWS) 15738 style_stash = builtins.__xonsh_env__["XONSH_COLOR_STYLE"] 15739 15740 if args.style is not None: 15741 if args.style not in color_style_names(): 15742 print("Invalid style: {}".format(args.style)) 15743 return 15744 builtins.__xonsh_env__["XONSH_COLOR_STYLE"] = args.style 15745 15746 color_map = color_style() 15747 akey = next(iter(color_map)) 15748 if isinstance(akey, str): 15749 s = _str_colors(color_map, columns) 15750 else: 15751 s = _tok_colors(color_map, columns) 15752 print_color(s) 15753 builtins.__xonsh_env__["XONSH_COLOR_STYLE"] = style_stash 15754 15755 15756def _tutorial(args): 15757 import webbrowser 15758 15759 webbrowser.open("http://xon.sh/tutorial.html") 15760 15761 15762@functools.lru_cache(1) 15763def _xonfig_create_parser(): 15764 p = argparse.ArgumentParser( 15765 prog="xonfig", description="Manages xonsh configuration." 15766 ) 15767 subp = p.add_subparsers(title="action", dest="action") 15768 info = subp.add_parser( 15769 "info", help=("displays configuration information, " "default action") 15770 ) 15771 info.add_argument( 15772 "--json", action="store_true", default=False, help="reports results as json" 15773 ) 15774 wiz = subp.add_parser("wizard", help="displays configuration information") 15775 wiz.add_argument( 15776 "--file", default=None, help="config file location, default=$XONSHRC" 15777 ) 15778 wiz.add_argument( 15779 "--confirm", 15780 action="store_true", 15781 default=False, 15782 help="confirm that the wizard should be run.", 15783 ) 15784 sty = subp.add_parser("styles", help="prints available xonsh color styles") 15785 sty.add_argument( 15786 "--json", action="store_true", default=False, help="reports results as json" 15787 ) 15788 colors = subp.add_parser("colors", help="preview color style") 15789 colors.add_argument( 15790 "style", nargs="?", default=None, help="style to preview, default: <current>" 15791 ) 15792 subp.add_parser("tutorial", help="Launch tutorial in browser.") 15793 return p 15794 15795 15796_XONFIG_MAIN_ACTIONS = { 15797 "info": _info, 15798 "wizard": _wizard, 15799 "styles": _styles, 15800 "colors": _colors, 15801 "tutorial": _tutorial, 15802} 15803 15804 15805def xonfig_main(args=None): 15806 """Main xonfig entry point.""" 15807 if not args or ( 15808 args[0] not in _XONFIG_MAIN_ACTIONS and args[0] not in {"-h", "--help"} 15809 ): 15810 args.insert(0, "info") 15811 parser = _xonfig_create_parser() 15812 ns = parser.parse_args(args) 15813 if ns.action is None: # apply default action 15814 ns = parser.parse_args(["info"] + args) 15815 return _XONFIG_MAIN_ACTIONS[ns.action](ns) 15816 15817 15818@lazyobject 15819def STRIP_COLOR_RE(): 15820 return re.compile("{.*?}") 15821 15822 15823def _align_string(string, align="<", fill=" ", width=80): 15824 """ Align and pad a color formatted string """ 15825 linelen = len(STRIP_COLOR_RE.sub("", string)) 15826 padlen = max(width - linelen, 0) 15827 if align == "^": 15828 return fill * (padlen // 2) + string + fill * (padlen // 2 + padlen % 2) 15829 elif align == ">": 15830 return fill * padlen + string 15831 elif align == "<": 15832 return string + fill * padlen 15833 else: 15834 return string 15835 15836 15837@lazyobject 15838def TAGLINES(): 15839 return [ 15840 "Exofrills in the shell", 15841 "No frills in the shell", 15842 "Become the Lord of the Files", 15843 "Break out of your shell", 15844 "The only shell that is also a shell", 15845 "All that is and all that shell be", 15846 "It cannot be that hard", 15847 "Pass the xonsh, Piggy", 15848 "Piggy glanced nervously into hell and cradled the xonsh", 15849 "The xonsh is a symbol", 15850 "It is pronounced conch", 15851 "The shell, bourne again", 15852 "Snailed it", 15853 "Starfish loves you", 15854 "Come snail away", 15855 "This is Major Tom to Ground Xonshtrol", 15856 "Sally sells csh and keeps xonsh to herself", 15857 "Nice indeed. Everything's accounted for, except your old shell.", 15858 "I wanna thank you for putting me back in my snail shell", 15859 "Crustaceanly Yours", 15860 "With great shell comes great reproducibility", 15861 "None shell pass", 15862 "You shell not pass!", 15863 "The x-on shell", 15864 "Ever wonder why there isn't a Taco Shell? Because it is a corny idea.", 15865 "The carcolh will catch you!", 15866 "People xonshtantly mispronounce these things", 15867 "WHAT...is your favorite shell?", 15868 "Conches for the xonsh god!", 15869 "Python-powered, cross-platform, Unix-gazing shell", 15870 "Tab completion in Alderaan places", 15871 "This fix was trickier than expected", 15872 "The unholy cross of Bash/Python", 15873 ] 15874 15875 15876# list of strings or tuples (string, align, fill) 15877WELCOME_MSG = [ 15878 "", 15879 ("{{INTENSE_WHITE}}Welcome to the xonsh shell ({version}){{NO_COLOR}}", "^", " "), 15880 "", 15881 ("{{INTENSE_RED}}~{{NO_COLOR}} {tagline} {{INTENSE_RED}}~{{NO_COLOR}}", "^", " "), 15882 "", 15883 ("{{INTENSE_BLACK}}", "<", "-"), 15884 "{{GREEN}}xonfig{{NO_COLOR}} tutorial {{INTENSE_WHITE}}-> Launch the tutorial in " 15885 "the browser{{NO_COLOR}}", 15886 "{{GREEN}}xonfig{{NO_COLOR}} wizard {{INTENSE_WHITE}}-> Run the configuration " 15887 "wizard and claim your shell {{NO_COLOR}}", 15888 "{{INTENSE_BLACK}}(Note: Run the Wizard or create a {{RED}}~/.xonshrc{{INTENSE_BLACK}} file " 15889 "to suppress the welcome screen)", 15890 "", 15891] 15892 15893 15894def print_welcome_screen(): 15895 subst = dict(tagline=random.choice(list(TAGLINES)), version=XONSH_VERSION) 15896 for elem in WELCOME_MSG: 15897 if isinstance(elem, str): 15898 elem = (elem, "", "") 15899 line = elem[0].format(**subst) 15900 termwidth = os.get_terminal_size().columns 15901 line = _align_string(line, elem[1], elem[2], width=termwidth) 15902 print_color(line) 15903 15904# 15905# base_shell 15906# 15907# -*- coding: utf-8 -*- 15908"""The base class for xonsh shell""" 15909# amalgamated io 15910# amalgamated os 15911# amalgamated sys 15912# amalgamated time 15913# amalgamated builtins 15914# amalgamated xonsh.tools 15915# amalgamated xonsh.platform 15916# amalgamated xonsh.codecache 15917# amalgamated xonsh.completer 15918from xonsh.prompt.base import multiline_prompt, PromptFormatter 15919# amalgamated xonsh.events 15920# amalgamated xonsh.shell 15921# amalgamated xonsh.lazyimps 15922# amalgamated xonsh.ansi_colors 15923if ON_WINDOWS: 15924 import ctypes 15925 15926 kernel32 = ctypes.windll.kernel32 15927 kernel32.SetConsoleTitleW.argtypes = [ctypes.c_wchar_p] 15928 15929 15930class _TeeStdBuf(io.RawIOBase): 15931 """A dispatcher for bytes to two buffers, as std stream buffer and an 15932 in memory buffer. 15933 """ 15934 15935 def __init__( 15936 self, stdbuf, membuf, encoding=None, errors=None, prestd=b"", poststd=b"" 15937 ): 15938 """ 15939 Parameters 15940 ---------- 15941 stdbuf : BytesIO-like or StringIO-like 15942 The std stream buffer. 15943 membuf : BytesIO-like 15944 The in memory stream buffer. 15945 encoding : str or None, optional 15946 The encoding of the stream. Only used if stdbuf is a text stream, 15947 rather than a binary one. Defaults to $XONSH_ENCODING if None. 15948 errors : str or None, optional 15949 The error form for the encoding of the stream. Only used if stdbuf 15950 is a text stream, rather than a binary one. Deafults to 15951 $XONSH_ENCODING_ERRORS if None. 15952 prestd : bytes, optional 15953 The prefix to prepend to the standard buffer. 15954 poststd : bytes, optional 15955 The postfix to append to the standard buffer. 15956 """ 15957 self.stdbuf = stdbuf 15958 self.membuf = membuf 15959 env = builtins.__xonsh_env__ 15960 self.encoding = env.get("XONSH_ENCODING") if encoding is None else encoding 15961 self.errors = env.get("XONSH_ENCODING_ERRORS") if errors is None else errors 15962 self.prestd = prestd 15963 self.poststd = poststd 15964 self._std_is_binary = not hasattr(stdbuf, "encoding") 15965 15966 def fileno(self): 15967 """Returns the file descriptor of the std buffer.""" 15968 return self.stdbuf.fileno() 15969 15970 def seek(self, offset, whence=io.SEEK_SET): 15971 """Sets the location in both the stdbuf and the membuf.""" 15972 self.stdbuf.seek(offset, whence) 15973 self.membuf.seek(offset, whence) 15974 15975 def truncate(self, size=None): 15976 """Truncate both buffers.""" 15977 self.stdbuf.truncate(size) 15978 self.membuf.truncate(size) 15979 15980 def readinto(self, b): 15981 """Read bytes into buffer from both streams.""" 15982 if self._std_is_binary: 15983 self.stdbuf.readinto(b) 15984 return self.membuf.readinto(b) 15985 15986 def write(self, b): 15987 """Write bytes into both buffers.""" 15988 std_b = b 15989 if self.prestd: 15990 std_b = self.prestd + b 15991 if self.poststd: 15992 std_b += self.poststd 15993 # write to stdbuf 15994 if self._std_is_binary: 15995 self.stdbuf.write(std_b) 15996 else: 15997 self.stdbuf.write(std_b.decode(encoding=self.encoding, errors=self.errors)) 15998 return self.membuf.write(b) 15999 16000 16001class _TeeStd(io.TextIOBase): 16002 """Tees a std stream into an in-memory container and the original stream.""" 16003 16004 def __init__(self, name, mem, prestd="", poststd=""): 16005 """ 16006 Parameters 16007 ---------- 16008 name : str 16009 The name of the buffer in the sys module, e.g. 'stdout'. 16010 mem : io.TextIOBase-like 16011 The in-memory text-based representation. 16012 prestd : str, optional 16013 The prefix to prepend to the standard stream. 16014 poststd : str, optional 16015 The postfix to append to the standard stream. 16016 """ 16017 self._name = name 16018 self.std = std = getattr(sys, name) 16019 self.mem = mem 16020 self.prestd = prestd 16021 self.poststd = poststd 16022 preb = prestd.encode(encoding=mem.encoding, errors=mem.errors) 16023 postb = poststd.encode(encoding=mem.encoding, errors=mem.errors) 16024 if hasattr(std, "buffer"): 16025 buffer = _TeeStdBuf(std.buffer, mem.buffer, prestd=preb, poststd=postb) 16026 else: 16027 # TextIO does not have buffer as part of the API, so std streams 16028 # may not either. 16029 buffer = _TeeStdBuf( 16030 std, 16031 mem.buffer, 16032 encoding=mem.encoding, 16033 errors=mem.errors, 16034 prestd=preb, 16035 poststd=postb, 16036 ) 16037 self.buffer = buffer 16038 setattr(sys, name, self) 16039 16040 @property 16041 def encoding(self): 16042 """The encoding of the in-memory buffer.""" 16043 return self.mem.encoding 16044 16045 @property 16046 def errors(self): 16047 """The errors of the in-memory buffer.""" 16048 return self.mem.errors 16049 16050 @property 16051 def newlines(self): 16052 """The newlines of the in-memory buffer.""" 16053 return self.mem.newlines 16054 16055 def _replace_std(self): 16056 std = self.std 16057 if std is None: 16058 return 16059 setattr(sys, self._name, std) 16060 self.std = self._name = None 16061 16062 def __del__(self): 16063 self._replace_std() 16064 16065 def close(self): 16066 """Restores the original std stream.""" 16067 self._replace_std() 16068 16069 def write(self, s): 16070 """Writes data to the original std stream and the in-memory object.""" 16071 self.mem.write(s) 16072 if self.std is None: 16073 return 16074 std_s = s 16075 if self.prestd: 16076 std_s = self.prestd + std_s 16077 if self.poststd: 16078 std_s += self.poststd 16079 self.std.write(std_s) 16080 16081 def flush(self): 16082 """Flushes both the original stdout and the buffer.""" 16083 self.std.flush() 16084 self.mem.flush() 16085 16086 def fileno(self): 16087 """Tunnel fileno() calls to the std stream.""" 16088 return self.std.fileno() 16089 16090 def seek(self, offset, whence=io.SEEK_SET): 16091 """Seek to a location in both streams.""" 16092 self.std.seek(offset, whence) 16093 self.mem.seek(offset, whence) 16094 16095 def truncate(self, size=None): 16096 """Seek to a location in both streams.""" 16097 self.std.truncate(size) 16098 self.mem.truncate(size) 16099 16100 def detach(self): 16101 """This operation is not supported.""" 16102 raise io.UnsupportedOperation 16103 16104 def read(self, size=None): 16105 """Read from the in-memory stream and seek to a new location in the 16106 std stream. 16107 """ 16108 s = self.mem.read(size) 16109 loc = self.std.tell() 16110 self.std.seek(loc + len(s)) 16111 return s 16112 16113 def readline(self, size=-1): 16114 """Read a line from the in-memory stream and seek to a new location 16115 in the std stream. 16116 """ 16117 s = self.mem.readline(size) 16118 loc = self.std.tell() 16119 self.std.seek(loc + len(s)) 16120 return s 16121 16122 16123class Tee: 16124 """Class that merges tee'd stdout and stderr into a single stream. 16125 16126 This represents what a user would actually see on the command line. 16127 This class has the same interface as io.TextIOWrapper, except that 16128 the buffer is optional. 16129 """ 16130 16131 # pylint is a stupid about counting public methods when using inheritance. 16132 # pylint: disable=too-few-public-methods 16133 16134 def __init__( 16135 self, 16136 buffer=None, 16137 encoding=None, 16138 errors=None, 16139 newline=None, 16140 line_buffering=False, 16141 write_through=False, 16142 ): 16143 self.buffer = io.BytesIO() if buffer is None else buffer 16144 self.memory = io.TextIOWrapper( 16145 self.buffer, 16146 encoding=encoding, 16147 errors=errors, 16148 newline=newline, 16149 line_buffering=line_buffering, 16150 write_through=write_through, 16151 ) 16152 self.stdout = _TeeStd("stdout", self.memory) 16153 env = builtins.__xonsh_env__ 16154 prestderr = format_std_prepost(env.get("XONSH_STDERR_PREFIX")) 16155 poststderr = format_std_prepost(env.get("XONSH_STDERR_POSTFIX")) 16156 self.stderr = _TeeStd( 16157 "stderr", self.memory, prestd=prestderr, poststd=poststderr 16158 ) 16159 16160 @property 16161 def line_buffering(self): 16162 return self.memory.line_buffering 16163 16164 def __del__(self): 16165 del self.stdout, self.stderr 16166 self.stdout = self.stderr = None 16167 16168 def close(self): 16169 """Closes the buffer as well as the stdout and stderr tees.""" 16170 self.stdout.close() 16171 self.stderr.close() 16172 self.memory.close() 16173 16174 def getvalue(self): 16175 """Gets the current contents of the in-memory buffer.""" 16176 m = self.memory 16177 loc = m.tell() 16178 m.seek(0) 16179 s = m.read() 16180 m.seek(loc) 16181 return s 16182 16183 16184class BaseShell(object): 16185 """The xonsh shell.""" 16186 16187 def __init__(self, execer, ctx, **kwargs): 16188 super().__init__() 16189 self.execer = execer 16190 self.ctx = ctx 16191 self.completer = Completer() if kwargs.get("completer", True) else None 16192 self.buffer = [] 16193 self.need_more_lines = False 16194 self.mlprompt = None 16195 self._styler = DefaultNotGiven 16196 self.prompt_formatter = PromptFormatter() 16197 self.accumulated_inputs = "" 16198 16199 @property 16200 def styler(self): 16201 if self._styler is DefaultNotGiven: 16202 if HAS_PYGMENTS: 16203 from xonsh.pyghooks import XonshStyle 16204 16205 env = builtins.__xonsh_env__ 16206 self._styler = XonshStyle(env.get("XONSH_COLOR_STYLE")) 16207 else: 16208 self._styler = None 16209 return self._styler 16210 16211 @styler.setter 16212 def styler(self, value): 16213 self._styler = value 16214 16215 @styler.deleter 16216 def styler(self): 16217 self._styler = DefaultNotGiven 16218 16219 def emptyline(self): 16220 """Called when an empty line has been entered.""" 16221 self.need_more_lines = False 16222 self.default("") 16223 16224 def singleline(self, **kwargs): 16225 """Reads a single line of input from the shell.""" 16226 msg = "{0} has not implemented singleline()." 16227 raise RuntimeError(msg.format(self.__class__.__name__)) 16228 16229 def precmd(self, line): 16230 """Called just before execution of line.""" 16231 return line if self.need_more_lines else line.lstrip() 16232 16233 def default(self, line): 16234 """Implements code execution.""" 16235 line = line if line.endswith("\n") else line + "\n" 16236 src, code = self.push(line) 16237 if code is None: 16238 return 16239 16240 events.on_precommand.fire(cmd=src) 16241 16242 env = builtins.__xonsh_env__ 16243 hist = builtins.__xonsh_history__ # pylint: disable=no-member 16244 ts1 = None 16245 enc = env.get("XONSH_ENCODING") 16246 err = env.get("XONSH_ENCODING_ERRORS") 16247 tee = Tee(encoding=enc, errors=err) 16248 try: 16249 ts0 = time.time() 16250 run_compiled_code(code, self.ctx, None, "single") 16251 ts1 = time.time() 16252 if hist is not None and hist.last_cmd_rtn is None: 16253 hist.last_cmd_rtn = 0 # returncode for success 16254 except XonshError as e: 16255 print(e.args[0], file=sys.stderr) 16256 if hist is not None and hist.last_cmd_rtn is None: 16257 hist.last_cmd_rtn = 1 # return code for failure 16258 except Exception: # pylint: disable=broad-except 16259 print_exception() 16260 if hist is not None and hist.last_cmd_rtn is None: 16261 hist.last_cmd_rtn = 1 # return code for failure 16262 finally: 16263 ts1 = ts1 or time.time() 16264 tee_out = tee.getvalue() 16265 self._append_history(inp=src, ts=[ts0, ts1], tee_out=tee_out) 16266 self.accumulated_inputs += src 16267 if ( 16268 tee_out 16269 and env.get("XONSH_APPEND_NEWLINE") 16270 and not tee_out.endswith(os.linesep) 16271 ): 16272 print(os.linesep, end="") 16273 tee.close() 16274 self._fix_cwd() 16275 if builtins.__xonsh_exit__: # pylint: disable=no-member 16276 return True 16277 16278 def _append_history(self, tee_out=None, **info): 16279 """Append information about the command to the history. 16280 16281 This also handles on_postcommand because this is the place where all the 16282 information is available. 16283 """ 16284 hist = builtins.__xonsh_history__ # pylint: disable=no-member 16285 info["rtn"] = hist.last_cmd_rtn if hist is not None else None 16286 tee_out = tee_out or None 16287 last_out = hist.last_cmd_out if hist is not None else None 16288 if last_out is None and tee_out is None: 16289 pass 16290 elif last_out is None and tee_out is not None: 16291 info["out"] = tee_out 16292 elif last_out is not None and tee_out is None: 16293 info["out"] = last_out 16294 else: 16295 info["out"] = tee_out + "\n" + last_out 16296 events.on_postcommand.fire( 16297 cmd=info["inp"], rtn=info["rtn"], out=info.get("out", None), ts=info["ts"] 16298 ) 16299 if hist is not None: 16300 hist.append(info) 16301 hist.last_cmd_rtn = hist.last_cmd_out = None 16302 16303 def _fix_cwd(self): 16304 """Check if the cwd changed out from under us.""" 16305 env = builtins.__xonsh_env__ 16306 try: 16307 cwd = os.getcwd() 16308 except (FileNotFoundError, OSError): 16309 cwd = None 16310 if cwd is None: 16311 # directory has been deleted out from under us, most likely 16312 pwd = env.get("PWD", None) 16313 if pwd is None: 16314 # we have no idea where we are 16315 env["PWD"] = "<invalid directory>" 16316 elif os.path.isdir(pwd): 16317 # unclear why os.getcwd() failed. do nothing. 16318 pass 16319 else: 16320 # OK PWD is really gone. 16321 msg = "{UNDERLINE_INTENSE_WHITE}{BACKGROUND_INTENSE_BLACK}" 16322 msg += "xonsh: working directory does not exist: " + pwd 16323 msg += "{NO_COLOR}" 16324 self.print_color(msg, file=sys.stderr) 16325 elif "PWD" not in env: 16326 # $PWD is missing from env, recreate it 16327 env["PWD"] = cwd 16328 elif os.path.realpath(cwd) != os.path.realpath(env["PWD"]): 16329 # The working directory has changed without updating $PWD, fix this 16330 old = env["PWD"] 16331 env["PWD"] = cwd 16332 env["OLDPWD"] = old 16333 events.on_chdir.fire(olddir=old, newdir=cwd) 16334 16335 def push(self, line): 16336 """Pushes a line onto the buffer and compiles the code in a way that 16337 enables multiline input. 16338 """ 16339 self.buffer.append(line) 16340 if self.need_more_lines: 16341 return None, None 16342 src = "".join(self.buffer) 16343 src = transform_command(src) 16344 return self.compile(src) 16345 16346 def compile(self, src): 16347 """Compiles source code and returns the (possibly modified) source and 16348 a valid code object. 16349 """ 16350 _cache = should_use_cache(self.execer, "single") 16351 if _cache: 16352 codefname = code_cache_name(src) 16353 cachefname = get_cache_filename(codefname, code=True) 16354 usecache, code = code_cache_check(cachefname) 16355 if usecache: 16356 self.reset_buffer() 16357 return src, code 16358 lincont = get_line_continuation() 16359 if src.endswith(lincont + "\n"): 16360 self.need_more_lines = True 16361 return src, None 16362 try: 16363 code = self.execer.compile(src, mode="single", glbs=self.ctx, locs=None) 16364 if _cache: 16365 update_cache(code, cachefname) 16366 self.reset_buffer() 16367 except SyntaxError: 16368 partial_string_info = check_for_partial_string(src) 16369 in_partial_string = ( 16370 partial_string_info[0] is not None and partial_string_info[1] is None 16371 ) 16372 if (src == "\n" or src.endswith("\n\n")) and not in_partial_string: 16373 self.reset_buffer() 16374 print_exception() 16375 return src, None 16376 self.need_more_lines = True 16377 code = None 16378 except Exception: # pylint: disable=broad-except 16379 self.reset_buffer() 16380 print_exception() 16381 code = None 16382 return src, code 16383 16384 def reset_buffer(self): 16385 """Resets the line buffer.""" 16386 self.buffer.clear() 16387 self.need_more_lines = False 16388 self.mlprompt = None 16389 16390 def settitle(self): 16391 """Sets terminal title.""" 16392 env = builtins.__xonsh_env__ # pylint: disable=no-member 16393 term = env.get("TERM", None) 16394 # Shells running in emacs sets TERM to "dumb" or "eterm-color". 16395 # Do not set title for these to avoid garbled prompt. 16396 if (term is None and not ON_WINDOWS) or term in [ 16397 "dumb", 16398 "eterm-color", 16399 "linux", 16400 ]: 16401 return 16402 t = env.get("TITLE") 16403 if t is None: 16404 return 16405 t = self.prompt_formatter(t) 16406 if ON_WINDOWS and "ANSICON" not in env: 16407 kernel32.SetConsoleTitleW(t) 16408 else: 16409 with open(1, "wb", closefd=False) as f: 16410 # prevent xonsh from answering interactive questions 16411 # on the next command by writing the title 16412 f.write("\x1b]0;{0}\x07".format(t).encode()) 16413 f.flush() 16414 16415 @property 16416 def prompt(self): 16417 """Obtains the current prompt string.""" 16418 if self.need_more_lines: 16419 if self.mlprompt is None: 16420 try: 16421 self.mlprompt = multiline_prompt() 16422 except Exception: # pylint: disable=broad-except 16423 print_exception() 16424 self.mlprompt = "<multiline prompt error> " 16425 return self.mlprompt 16426 env = builtins.__xonsh_env__ # pylint: disable=no-member 16427 p = env.get("PROMPT") 16428 try: 16429 p = self.prompt_formatter(p) 16430 except Exception: # pylint: disable=broad-except 16431 print_exception() 16432 self.settitle() 16433 return p 16434 16435 def format_color(self, string, hide=False, force_string=False, **kwargs): 16436 """Formats the colors in a string. ``BaseShell``'s default implementation 16437 of this method uses colors based on ANSI color codes. 16438 """ 16439 style = builtins.__xonsh_env__.get("XONSH_COLOR_STYLE") 16440 return ansi_partial_color_format(string, hide=hide, style=style) 16441 16442 def print_color(self, string, hide=False, **kwargs): 16443 """Prints a string in color. This base implementation's colors are based 16444 on ANSI color codes if a string was given as input. If a list of token 16445 pairs is given, it will color based on pygments, if available. If 16446 pygments is not available, it will print a colorless string. 16447 """ 16448 if isinstance(string, str): 16449 s = self.format_color(string, hide=hide) 16450 elif HAS_PYGMENTS: 16451 # assume this is a list of (Token, str) tuples and format it 16452 env = builtins.__xonsh_env__ 16453 self.styler.style_name = env.get("XONSH_COLOR_STYLE") 16454 style_proxy = pyghooks.xonsh_style_proxy(self.styler) 16455 formatter = pyghooks.XonshTerminal256Formatter(style=style_proxy) 16456 s = pygments.format(string, formatter).rstrip() 16457 else: 16458 # assume this is a list of (Token, str) tuples and remove color 16459 s = "".join([x for _, x in string]) 16460 print(s, **kwargs) 16461 16462 def color_style_names(self): 16463 """Returns an iterable of all available style names.""" 16464 return () 16465 16466 def color_style(self): 16467 """Returns the current color map.""" 16468 return {} 16469 16470 def restore_tty_sanity(self): 16471 """An interface for resetting the TTY stdin mode. This is highly 16472 dependent on the shell backend. Also it is mostly optional since 16473 it only affects ^Z backgrounding behaviour. 16474 """ 16475 pass 16476 16477# 16478# environ 16479# 16480# -*- coding: utf-8 -*- 16481"""Environment for the xonsh shell.""" 16482# amalgamated os 16483# amalgamated re 16484# amalgamated sys 16485# amalgamated pprint 16486# amalgamated textwrap 16487locale = _LazyModule.load('locale', 'locale') 16488# amalgamated builtins 16489# amalgamated warnings 16490# amalgamated contextlib 16491# amalgamated collections 16492# amalgamated collections.abc 16493from xonsh import __version__ as XONSH_VERSION 16494# amalgamated xonsh.lazyasd 16495# amalgamated xonsh.codecache 16496# amalgamated xonsh.dirstack 16497# amalgamated xonsh.events 16498# amalgamated xonsh.platform 16499# amalgamated xonsh.tools 16500prompt = _LazyModule.load('xonsh', 'xonsh.prompt.base', 'prompt') 16501events.doc( 16502 "on_envvar_new", 16503 """ 16504on_envvar_new(name: str, value: Any) -> None 16505 16506Fires after a new environment variable is created. 16507Note: Setting envvars inside the handler might 16508cause a recursion until the limit. 16509""", 16510) 16511 16512 16513events.doc( 16514 "on_envvar_change", 16515 """ 16516on_envvar_change(name: str, oldvalue: Any, newvalue: Any) -> None 16517 16518Fires after an environment variable is changed. 16519Note: Setting envvars inside the handler might 16520cause a recursion until the limit. 16521""", 16522) 16523 16524 16525@lazyobject 16526def HELP_TEMPLATE(): 16527 return ( 16528 "{{INTENSE_RED}}{envvar}{{NO_COLOR}}:\n\n" 16529 "{{INTENSE_YELLOW}}{docstr}{{NO_COLOR}}\n\n" 16530 "default: {{CYAN}}{default}{{NO_COLOR}}\n" 16531 "configurable: {{CYAN}}{configurable}{{NO_COLOR}}" 16532 ) 16533 16534 16535@lazyobject 16536def LOCALE_CATS(): 16537 lc = { 16538 "LC_CTYPE": locale.LC_CTYPE, 16539 "LC_COLLATE": locale.LC_COLLATE, 16540 "LC_NUMERIC": locale.LC_NUMERIC, 16541 "LC_MONETARY": locale.LC_MONETARY, 16542 "LC_TIME": locale.LC_TIME, 16543 } 16544 if hasattr(locale, "LC_MESSAGES"): 16545 lc["LC_MESSAGES"] = locale.LC_MESSAGES 16546 return lc 16547 16548 16549def locale_convert(key): 16550 """Creates a converter for a locale key.""" 16551 16552 def lc_converter(val): 16553 try: 16554 locale.setlocale(LOCALE_CATS[key], val) 16555 val = locale.setlocale(LOCALE_CATS[key]) 16556 except (locale.Error, KeyError): 16557 msg = "Failed to set locale {0!r} to {1!r}".format(key, val) 16558 warnings.warn(msg, RuntimeWarning) 16559 return val 16560 16561 return lc_converter 16562 16563 16564def to_debug(x): 16565 """Converts value using to_bool_or_int() and sets this value on as the 16566 execer's debug level. 16567 """ 16568 val = to_bool_or_int(x) 16569 if hasattr(builtins, "__xonsh_execer__"): 16570 builtins.__xonsh_execer__.debug_level = val 16571 return val 16572 16573 16574Ensurer = collections.namedtuple("Ensurer", ["validate", "convert", "detype"]) 16575Ensurer.__doc__ = """Named tuples whose elements are functions that 16576represent environment variable validation, conversion, detyping. 16577""" 16578 16579 16580@lazyobject 16581def DEFAULT_ENSURERS(): 16582 return { 16583 "AUTO_CD": (is_bool, to_bool, bool_to_str), 16584 "AUTO_PUSHD": (is_bool, to_bool, bool_to_str), 16585 "AUTO_SUGGEST": (is_bool, to_bool, bool_to_str), 16586 "AUTO_SUGGEST_IN_COMPLETIONS": (is_bool, to_bool, bool_to_str), 16587 "BASH_COMPLETIONS": (is_env_path, str_to_env_path, env_path_to_str), 16588 "CASE_SENSITIVE_COMPLETIONS": (is_bool, to_bool, bool_to_str), 16589 re.compile("\w*DIRS$"): (is_env_path, str_to_env_path, env_path_to_str), 16590 "COLOR_INPUT": (is_bool, to_bool, bool_to_str), 16591 "COLOR_RESULTS": (is_bool, to_bool, bool_to_str), 16592 "COMPLETIONS_BRACKETS": (is_bool, to_bool, bool_to_str), 16593 "COMPLETIONS_CONFIRM": (is_bool, to_bool, bool_to_str), 16594 "COMPLETIONS_DISPLAY": ( 16595 is_completions_display_value, 16596 to_completions_display_value, 16597 str, 16598 ), 16599 "COMPLETIONS_MENU_ROWS": (is_int, int, str), 16600 "COMPLETION_QUERY_LIMIT": (is_int, int, str), 16601 "DIRSTACK_SIZE": (is_int, int, str), 16602 "DOTGLOB": (is_bool, to_bool, bool_to_str), 16603 "DYNAMIC_CWD_WIDTH": ( 16604 is_dynamic_cwd_width, 16605 to_dynamic_cwd_tuple, 16606 dynamic_cwd_tuple_to_str, 16607 ), 16608 "DYNAMIC_CWD_ELISION_CHAR": (is_string, ensure_string, ensure_string), 16609 "EXPAND_ENV_VARS": (is_bool, to_bool, bool_to_str), 16610 "FORCE_POSIX_PATHS": (is_bool, to_bool, bool_to_str), 16611 "FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE": (is_bool, to_bool, bool_to_str), 16612 "FOREIGN_ALIASES_OVERRIDE": (is_bool, to_bool, bool_to_str), 16613 "FUZZY_PATH_COMPLETION": (is_bool, to_bool, bool_to_str), 16614 "GLOB_SORTED": (is_bool, to_bool, bool_to_str), 16615 "HISTCONTROL": (is_string_set, csv_to_set, set_to_csv), 16616 "IGNOREEOF": (is_bool, to_bool, bool_to_str), 16617 "INTENSIFY_COLORS_ON_WIN": ( 16618 always_false, 16619 intensify_colors_on_win_setter, 16620 bool_to_str, 16621 ), 16622 "LANG": (is_string, ensure_string, ensure_string), 16623 "LC_COLLATE": (always_false, locale_convert("LC_COLLATE"), ensure_string), 16624 "LC_CTYPE": (always_false, locale_convert("LC_CTYPE"), ensure_string), 16625 "LC_MESSAGES": (always_false, locale_convert("LC_MESSAGES"), ensure_string), 16626 "LC_MONETARY": (always_false, locale_convert("LC_MONETARY"), ensure_string), 16627 "LC_NUMERIC": (always_false, locale_convert("LC_NUMERIC"), ensure_string), 16628 "LC_TIME": (always_false, locale_convert("LC_TIME"), ensure_string), 16629 "LOADED_RC_FILES": (is_bool_seq, csv_to_bool_seq, bool_seq_to_csv), 16630 "MOUSE_SUPPORT": (is_bool, to_bool, bool_to_str), 16631 "MULTILINE_PROMPT": (is_string_or_callable, ensure_string, ensure_string), 16632 re.compile("\w*PATH$"): (is_env_path, str_to_env_path, env_path_to_str), 16633 "PATHEXT": ( 16634 is_nonstring_seq_of_strings, 16635 pathsep_to_upper_seq, 16636 seq_to_upper_pathsep, 16637 ), 16638 "PRETTY_PRINT_RESULTS": (is_bool, to_bool, bool_to_str), 16639 "PROMPT": (is_string_or_callable, ensure_string, ensure_string), 16640 "PROMPT_TOOLKIT_COLOR_DEPTH": ( 16641 always_false, 16642 ptk2_color_depth_setter, 16643 ensure_string, 16644 ), 16645 "PUSHD_MINUS": (is_bool, to_bool, bool_to_str), 16646 "PUSHD_SILENT": (is_bool, to_bool, bool_to_str), 16647 "RAISE_SUBPROC_ERROR": (is_bool, to_bool, bool_to_str), 16648 "RIGHT_PROMPT": (is_string_or_callable, ensure_string, ensure_string), 16649 "BOTTOM_TOOLBAR": (is_string_or_callable, ensure_string, ensure_string), 16650 "SUBSEQUENCE_PATH_COMPLETION": (is_bool, to_bool, bool_to_str), 16651 "SUGGEST_COMMANDS": (is_bool, to_bool, bool_to_str), 16652 "SUGGEST_MAX_NUM": (is_int, int, str), 16653 "SUGGEST_THRESHOLD": (is_int, int, str), 16654 "SUPPRESS_BRANCH_TIMEOUT_MESSAGE": (is_bool, to_bool, bool_to_str), 16655 "UPDATE_COMPLETIONS_ON_KEYPRESS": (is_bool, to_bool, bool_to_str), 16656 "UPDATE_OS_ENVIRON": (is_bool, to_bool, bool_to_str), 16657 "UPDATE_PROMPT_ON_KEYPRESS": (is_bool, to_bool, bool_to_str), 16658 "VC_BRANCH_TIMEOUT": (is_float, float, str), 16659 "VC_HG_SHOW_BRANCH": (is_bool, to_bool, bool_to_str), 16660 "VI_MODE": (is_bool, to_bool, bool_to_str), 16661 "VIRTUAL_ENV": (is_string, ensure_string, ensure_string), 16662 "WIN_UNICODE_CONSOLE": (always_false, setup_win_unicode_console, bool_to_str), 16663 "XONSHRC": (is_env_path, str_to_env_path, env_path_to_str), 16664 "XONSH_APPEND_NEWLINE": (is_bool, to_bool, bool_to_str), 16665 "XONSH_AUTOPAIR": (is_bool, to_bool, bool_to_str), 16666 "XONSH_CACHE_SCRIPTS": (is_bool, to_bool, bool_to_str), 16667 "XONSH_CACHE_EVERYTHING": (is_bool, to_bool, bool_to_str), 16668 "XONSH_COLOR_STYLE": (is_string, ensure_string, ensure_string), 16669 "XONSH_DEBUG": (always_false, to_debug, bool_or_int_to_str), 16670 "XONSH_ENCODING": (is_string, ensure_string, ensure_string), 16671 "XONSH_ENCODING_ERRORS": (is_string, ensure_string, ensure_string), 16672 "XONSH_HISTORY_BACKEND": (is_history_backend, to_itself, ensure_string), 16673 "XONSH_HISTORY_FILE": (is_string, ensure_string, ensure_string), 16674 "XONSH_HISTORY_MATCH_ANYWHERE": (is_bool, to_bool, bool_to_str), 16675 "XONSH_HISTORY_SIZE": ( 16676 is_history_tuple, 16677 to_history_tuple, 16678 history_tuple_to_str, 16679 ), 16680 "XONSH_LOGIN": (is_bool, to_bool, bool_to_str), 16681 "XONSH_PROC_FREQUENCY": (is_float, float, str), 16682 "XONSH_SHOW_TRACEBACK": (is_bool, to_bool, bool_to_str), 16683 "XONSH_STDERR_PREFIX": (is_string, ensure_string, ensure_string), 16684 "XONSH_STDERR_POSTFIX": (is_string, ensure_string, ensure_string), 16685 "XONSH_STORE_STDOUT": (is_bool, to_bool, bool_to_str), 16686 "XONSH_STORE_STDIN": (is_bool, to_bool, bool_to_str), 16687 "XONSH_TRACEBACK_LOGFILE": (is_logfile_opt, to_logfile_opt, logfile_opt_to_str), 16688 "XONSH_DATETIME_FORMAT": (is_string, ensure_string, ensure_string), 16689 } 16690 16691 16692# 16693# Defaults 16694# 16695def default_value(f): 16696 """Decorator for making callable default values.""" 16697 f._xonsh_callable_default = True 16698 return f 16699 16700 16701def is_callable_default(x): 16702 """Checks if a value is a callable default.""" 16703 return callable(x) and getattr(x, "_xonsh_callable_default", False) 16704 16705 16706DEFAULT_TITLE = "{current_job:{} | }{user}@{hostname}: {cwd} | xonsh" 16707 16708 16709@default_value 16710def xonsh_data_dir(env): 16711 """Ensures and returns the $XONSH_DATA_DIR""" 16712 xdd = os.path.expanduser(os.path.join(env.get("XDG_DATA_HOME"), "xonsh")) 16713 os.makedirs(xdd, exist_ok=True) 16714 return xdd 16715 16716 16717@default_value 16718def xonsh_config_dir(env): 16719 """Ensures and returns the $XONSH_CONFIG_DIR""" 16720 xcd = os.path.expanduser(os.path.join(env.get("XDG_CONFIG_HOME"), "xonsh")) 16721 os.makedirs(xcd, exist_ok=True) 16722 return xcd 16723 16724 16725def xonshconfig(env): 16726 """Ensures and returns the $XONSHCONFIG""" 16727 xcd = env.get("XONSH_CONFIG_DIR") 16728 xc = os.path.join(xcd, "config.json") 16729 return xc 16730 16731 16732@default_value 16733def default_xonshrc(env): 16734 """Creates a new instance of the default xonshrc tuple.""" 16735 xcdrc = os.path.join(xonsh_config_dir(env), "rc.xsh") 16736 if ON_WINDOWS: 16737 dxrc = ( 16738 os.path.join(os_environ["ALLUSERSPROFILE"], "xonsh", "xonshrc"), 16739 xcdrc, 16740 os.path.expanduser("~/.xonshrc"), 16741 ) 16742 else: 16743 dxrc = ("/etc/xonshrc", xcdrc, os.path.expanduser("~/.xonshrc")) 16744 # Check if old config file exists and issue warning 16745 old_config_filename = xonshconfig(env) 16746 if os.path.isfile(old_config_filename): 16747 print( 16748 "WARNING! old style configuration (" 16749 + old_config_filename 16750 + ") is no longer supported. " 16751 + "Please migrate to xonshrc." 16752 ) 16753 return dxrc 16754 16755 16756@default_value 16757def xonsh_append_newline(env): 16758 """Appends a newline if we are in interactive mode""" 16759 return env.get("XONSH_INTERACTIVE", False) 16760 16761 16762# Default values should generally be immutable, that way if a user wants 16763# to set them they have to do a copy and write them to the environment. 16764# try to keep this sorted. 16765@lazyobject 16766def DEFAULT_VALUES(): 16767 dv = { 16768 "AUTO_CD": False, 16769 "AUTO_PUSHD": False, 16770 "AUTO_SUGGEST": True, 16771 "AUTO_SUGGEST_IN_COMPLETIONS": False, 16772 "BASH_COMPLETIONS": BASH_COMPLETIONS_DEFAULT, 16773 "CASE_SENSITIVE_COMPLETIONS": ON_LINUX, 16774 "CDPATH": (), 16775 "COLOR_INPUT": True, 16776 "COLOR_RESULTS": True, 16777 "COMPLETIONS_BRACKETS": True, 16778 "COMPLETIONS_CONFIRM": False, 16779 "COMPLETIONS_DISPLAY": "multi", 16780 "COMPLETIONS_MENU_ROWS": 5, 16781 "COMPLETION_QUERY_LIMIT": 100, 16782 "DIRSTACK_SIZE": 20, 16783 "DOTGLOB": False, 16784 "DYNAMIC_CWD_WIDTH": (float("inf"), "c"), 16785 "DYNAMIC_CWD_ELISION_CHAR": "", 16786 "EXPAND_ENV_VARS": True, 16787 "FORCE_POSIX_PATHS": False, 16788 "FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE": False, 16789 "FOREIGN_ALIASES_OVERRIDE": False, 16790 "PROMPT_FIELDS": dict(prompt.PROMPT_FIELDS), 16791 "FUZZY_PATH_COMPLETION": True, 16792 "GLOB_SORTED": True, 16793 "HISTCONTROL": set(), 16794 "IGNOREEOF": False, 16795 "INDENT": " ", 16796 "INTENSIFY_COLORS_ON_WIN": True, 16797 "LANG": "C.UTF-8", 16798 "LC_CTYPE": locale.setlocale(locale.LC_CTYPE), 16799 "LC_COLLATE": locale.setlocale(locale.LC_COLLATE), 16800 "LC_TIME": locale.setlocale(locale.LC_TIME), 16801 "LC_MONETARY": locale.setlocale(locale.LC_MONETARY), 16802 "LC_NUMERIC": locale.setlocale(locale.LC_NUMERIC), 16803 "LOADED_RC_FILES": (), 16804 "MOUSE_SUPPORT": False, 16805 "MULTILINE_PROMPT": ".", 16806 "PATH": PATH_DEFAULT, 16807 "PATHEXT": [".COM", ".EXE", ".BAT", ".CMD"] if ON_WINDOWS else [], 16808 "PRETTY_PRINT_RESULTS": True, 16809 "PROMPT": prompt.default_prompt(), 16810 "PROMPT_TOOLKIT_COLOR_DEPTH": "", 16811 "PUSHD_MINUS": False, 16812 "PUSHD_SILENT": False, 16813 "RAISE_SUBPROC_ERROR": False, 16814 "RIGHT_PROMPT": "", 16815 "BOTTOM_TOOLBAR": "", 16816 "SHELL_TYPE": "best", 16817 "SUBSEQUENCE_PATH_COMPLETION": True, 16818 "SUPPRESS_BRANCH_TIMEOUT_MESSAGE": False, 16819 "SUGGEST_COMMANDS": True, 16820 "SUGGEST_MAX_NUM": 5, 16821 "SUGGEST_THRESHOLD": 3, 16822 "TITLE": DEFAULT_TITLE, 16823 "UPDATE_COMPLETIONS_ON_KEYPRESS": False, 16824 "UPDATE_OS_ENVIRON": False, 16825 "UPDATE_PROMPT_ON_KEYPRESS": False, 16826 "VC_BRANCH_TIMEOUT": 0.2 if ON_WINDOWS else 0.1, 16827 "VC_HG_SHOW_BRANCH": True, 16828 "VI_MODE": False, 16829 "WIN_UNICODE_CONSOLE": True, 16830 "XDG_CONFIG_HOME": os.path.expanduser(os.path.join("~", ".config")), 16831 "XDG_DATA_HOME": os.path.expanduser(os.path.join("~", ".local", "share")), 16832 "XONSHRC": default_xonshrc, 16833 "XONSH_APPEND_NEWLINE": xonsh_append_newline, 16834 "XONSH_AUTOPAIR": False, 16835 "XONSH_CACHE_SCRIPTS": True, 16836 "XONSH_CACHE_EVERYTHING": False, 16837 "XONSH_COLOR_STYLE": "default", 16838 "XONSH_CONFIG_DIR": xonsh_config_dir, 16839 "XONSH_DATA_DIR": xonsh_data_dir, 16840 "XONSH_DEBUG": 0, 16841 "XONSH_ENCODING": DEFAULT_ENCODING, 16842 "XONSH_ENCODING_ERRORS": "surrogateescape", 16843 "XONSH_HISTORY_BACKEND": "json", 16844 "XONSH_HISTORY_FILE": os.path.expanduser("~/.xonsh_history.json"), 16845 "XONSH_HISTORY_MATCH_ANYWHERE": False, 16846 "XONSH_HISTORY_SIZE": (8128, "commands"), 16847 "XONSH_LOGIN": False, 16848 "XONSH_PROC_FREQUENCY": 1e-4, 16849 "XONSH_SHOW_TRACEBACK": False, 16850 "XONSH_STDERR_PREFIX": "", 16851 "XONSH_STDERR_POSTFIX": "", 16852 "XONSH_STORE_STDIN": False, 16853 "XONSH_STORE_STDOUT": False, 16854 "XONSH_TRACEBACK_LOGFILE": None, 16855 "XONSH_DATETIME_FORMAT": "%Y-%m-%d %H:%M", 16856 } 16857 if hasattr(locale, "LC_MESSAGES"): 16858 dv["LC_MESSAGES"] = locale.setlocale(locale.LC_MESSAGES) 16859 return dv 16860 16861 16862VarDocs = collections.namedtuple( 16863 "VarDocs", ["docstr", "configurable", "default", "store_as_str"] 16864) 16865VarDocs.__doc__ = """Named tuple for environment variable documentation 16866 16867Parameters 16868---------- 16869docstr : str 16870 The environment variable docstring. 16871configurable : bool, optional 16872 Flag for whether the environment variable is configurable or not. 16873default : str, optional 16874 Custom docstring for the default value for complex defaults. 16875 Is this is DefaultNotGiven, then the default will be looked up 16876 from DEFAULT_VALUES and converted to a str. 16877store_as_str : bool, optional 16878 Flag for whether the environment variable should be stored as a 16879 string. This is used when persisting a variable that is not JSON 16880 serializable to the config file. For example, sets, frozensets, and 16881 potentially other non-trivial data types. default, False. 16882""" 16883# iterates from back 16884VarDocs.__new__.__defaults__ = (True, DefaultNotGiven, False) 16885 16886 16887# Please keep the following in alphabetic order - scopatz 16888@lazyobject 16889def DEFAULT_DOCS(): 16890 return { 16891 "ANSICON": VarDocs( 16892 "This is used on Windows to set the title, " "if available.", 16893 configurable=False, 16894 ), 16895 "AUTO_CD": VarDocs( 16896 "Flag to enable changing to a directory by entering the dirname or " 16897 "full path only (without the cd command)." 16898 ), 16899 "AUTO_PUSHD": VarDocs( 16900 "Flag for automatically pushing directories onto the directory stack." 16901 ), 16902 "AUTO_SUGGEST": VarDocs( 16903 "Enable automatic command suggestions based on history, like in the fish " 16904 "shell.\n\nPressing the right arrow key inserts the currently " 16905 "displayed suggestion. Only usable with ``$SHELL_TYPE=prompt_toolkit.``" 16906 ), 16907 "AUTO_SUGGEST_IN_COMPLETIONS": VarDocs( 16908 "Places the auto-suggest result as the first option in the completions. " 16909 "This enables you to tab complete the auto-suggestion." 16910 ), 16911 "BASH_COMPLETIONS": VarDocs( 16912 "This is a list (or tuple) of strings that specifies where the " 16913 "``bash_completion`` script may be found. " 16914 "The first valid path will be used. For better performance, " 16915 "bash-completion v2.x is recommended since it lazy-loads individual " 16916 "completion scripts. " 16917 "For both bash-completion v1.x and v2.x, paths of individual completion " 16918 "scripts (like ``.../completes/ssh``) do not need to be included here. " 16919 "The default values are platform " 16920 "dependent, but sane. To specify an alternate list, do so in the run " 16921 "control file.", 16922 default=( 16923 "Normally this is:\n\n" 16924 " ``('/usr/share/bash-completion/bash_completion', )``\n\n" 16925 "But, on Mac it is:\n\n" 16926 " ``('/usr/local/share/bash-completion/bash_completion', " 16927 "'/usr/local/etc/bash_completion')``\n\n" 16928 "Other OS-specific defaults may be added in the future." 16929 ), 16930 ), 16931 "CASE_SENSITIVE_COMPLETIONS": VarDocs( 16932 "Sets whether completions should be case sensitive or case " "insensitive.", 16933 default="True on Linux, False otherwise.", 16934 ), 16935 "CDPATH": VarDocs( 16936 "A list of paths to be used as roots for a cd, breaking compatibility " 16937 "with Bash, xonsh always prefer an existing relative path." 16938 ), 16939 "COLOR_INPUT": VarDocs("Flag for syntax highlighting interactive input."), 16940 "COLOR_RESULTS": VarDocs("Flag for syntax highlighting return values."), 16941 "COMPLETIONS_BRACKETS": VarDocs( 16942 "Flag to enable/disable inclusion of square brackets and parentheses " 16943 "in Python attribute completions.", 16944 default="True", 16945 ), 16946 "COMPLETIONS_DISPLAY": VarDocs( 16947 "Configure if and how Python completions are displayed by the " 16948 "``prompt_toolkit`` shell.\n\nThis option does not affect Bash " 16949 "completions, auto-suggestions, etc.\n\nChanging it at runtime will " 16950 "take immediate effect, so you can quickly disable and enable " 16951 "completions during shell sessions.\n\n" 16952 "- If ``$COMPLETIONS_DISPLAY`` is ``none`` or ``false``, do not display\n" 16953 " those completions.\n" 16954 "- If ``$COMPLETIONS_DISPLAY`` is ``single``, display completions in a\n" 16955 " single column while typing.\n" 16956 "- If ``$COMPLETIONS_DISPLAY`` is ``multi`` or ``true``, display completions\n" 16957 " in multiple columns while typing.\n\n" 16958 "- If ``$COMPLETIONS_DISPLAY`` is ``readline``, display completions\n" 16959 " will emulate the behavior of readline.\n\n" 16960 "These option values are not case- or type-sensitive, so e.g." 16961 "writing ``$COMPLETIONS_DISPLAY = None`` " 16962 "and ``$COMPLETIONS_DISPLAY = 'none'`` are equivalent. Only usable with " 16963 "``$SHELL_TYPE=prompt_toolkit``" 16964 ), 16965 "COMPLETIONS_CONFIRM": VarDocs( 16966 "While tab-completions menu is displayed, press <Enter> to confirm " 16967 "completion instead of running command. This only affects the " 16968 "prompt-toolkit shell." 16969 ), 16970 "COMPLETIONS_MENU_ROWS": VarDocs( 16971 "Number of rows to reserve for tab-completions menu if " 16972 "``$COMPLETIONS_DISPLAY`` is ``single`` or ``multi``. This only affects the " 16973 "prompt-toolkit shell." 16974 ), 16975 "COMPLETION_QUERY_LIMIT": VarDocs( 16976 "The number of completions to display before the user is asked " 16977 "for confirmation." 16978 ), 16979 "DIRSTACK_SIZE": VarDocs("Maximum size of the directory stack."), 16980 "DOTGLOB": VarDocs( 16981 'Globbing files with "*" or "**" will also match ' 16982 "dotfiles, or those 'hidden' files whose names " 16983 "begin with a literal '.'. Such files are filtered " 16984 "out by default." 16985 ), 16986 "DYNAMIC_CWD_WIDTH": VarDocs( 16987 "Maximum length in number of characters " 16988 "or as a percentage for the ``cwd`` prompt variable. For example, " 16989 '"20" is a twenty character width and "10%" is ten percent of the ' 16990 "number of columns available." 16991 ), 16992 "DYNAMIC_CWD_ELISION_CHAR": VarDocs( 16993 "The string used to show a shortened directory in a shortened cwd, " 16994 "e.g. ``'…'``." 16995 ), 16996 "EXPAND_ENV_VARS": VarDocs( 16997 "Toggles whether environment variables are expanded inside of strings " 16998 "in subprocess mode." 16999 ), 17000 "FORCE_POSIX_PATHS": VarDocs( 17001 "Forces forward slashes (``/``) on Windows systems when using auto " 17002 "completion if set to anything truthy.", 17003 configurable=ON_WINDOWS, 17004 ), 17005 "FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE": VarDocs( 17006 "Whether or not foreign aliases should suppress the message " 17007 "that informs the user when a foreign alias has been skipped " 17008 "because it already exists in xonsh.", 17009 configurable=True, 17010 ), 17011 "FOREIGN_ALIASES_OVERRIDE": VarDocs( 17012 "Whether or not foreign aliases should override xonsh aliases " 17013 "with the same name. Note that setting of this must happen in the " 17014 "environment that xonsh was started from. " 17015 "It cannot be set in the ``.xonshrc`` as loading of foreign aliases happens before" 17016 "``.xonshrc`` is parsed", 17017 configurable=True, 17018 ), 17019 "PROMPT_FIELDS": VarDocs( 17020 "Dictionary containing variables to be used when formatting $PROMPT " 17021 "and $TITLE. See 'Customizing the Prompt' " 17022 "http://xon.sh/tutorial.html#customizing-the-prompt", 17023 configurable=False, 17024 default="``xonsh.prompt.PROMPT_FIELDS``", 17025 ), 17026 "FUZZY_PATH_COMPLETION": VarDocs( 17027 "Toggles 'fuzzy' matching of paths for tab completion, which is only " 17028 "used as a fallback if no other completions succeed but can be used " 17029 "as a way to adjust for typographical errors. If ``True``, then, e.g.," 17030 " ``xonhs`` will match ``xonsh``." 17031 ), 17032 "GLOB_SORTED": VarDocs( 17033 "Toggles whether globbing results are manually sorted. If ``False``, " 17034 "the results are returned in arbitrary order." 17035 ), 17036 "HISTCONTROL": VarDocs( 17037 "A set of strings (comma-separated list in string form) of options " 17038 "that determine what commands are saved to the history list. By " 17039 "default all commands are saved. The option ``ignoredups`` will not " 17040 "save the command if it matches the previous command. The option " 17041 "'ignoreerr' will cause any commands that fail (i.e. return non-zero " 17042 "exit status) to not be added to the history list.", 17043 store_as_str=True, 17044 ), 17045 "IGNOREEOF": VarDocs("Prevents Ctrl-D from exiting the shell."), 17046 "INDENT": VarDocs("Indentation string for multiline input"), 17047 "INTENSIFY_COLORS_ON_WIN": VarDocs( 17048 "Enhance style colors for readability " 17049 "when using the default terminal (``cmd.exe``) on Windows. Blue colors, " 17050 "which are hard to read, are replaced with cyan. Other colors are " 17051 "generally replaced by their bright counter parts.", 17052 configurable=ON_WINDOWS, 17053 ), 17054 "LANG": VarDocs("Fallback locale setting for systems where it matters"), 17055 "LOADED_RC_FILES": VarDocs( 17056 "Whether or not any of the xonsh run control files were loaded at " 17057 "startup. This is a sequence of bools in Python that is converted " 17058 "to a CSV list in string form, ie ``[True, False]`` becomes " 17059 "``'True,False'``.", 17060 configurable=False, 17061 ), 17062 "MOUSE_SUPPORT": VarDocs( 17063 "Enable mouse support in the ``prompt_toolkit`` shell. This allows " 17064 "clicking for positioning the cursor or selecting a completion. In " 17065 "some terminals however, this disables the ability to scroll back " 17066 "through the history of the terminal. Only usable with " 17067 "``$SHELL_TYPE=prompt_toolkit``" 17068 ), 17069 "MULTILINE_PROMPT": VarDocs( 17070 "Prompt text for 2nd+ lines of input, may be str or function which " 17071 "returns a str." 17072 ), 17073 "OLDPWD": VarDocs( 17074 "Used to represent a previous present working directory.", 17075 configurable=False, 17076 ), 17077 "PATH": VarDocs("List of strings representing where to look for executables."), 17078 "PATHEXT": VarDocs( 17079 "Sequence of extension strings (eg, ``.EXE``) for " 17080 "filtering valid executables by. Each element must be " 17081 "uppercase." 17082 ), 17083 "PRETTY_PRINT_RESULTS": VarDocs('Flag for "pretty printing" return values.'), 17084 "PROMPT": VarDocs( 17085 "The prompt text. May contain keyword arguments which are " 17086 "auto-formatted, see 'Customizing the Prompt' at " 17087 "http://xon.sh/tutorial.html#customizing-the-prompt. " 17088 "This value is never inherited from parent processes.", 17089 default="``xonsh.environ.DEFAULT_PROMPT``", 17090 ), 17091 "PROMPT_TOOLKIT_COLOR_DEPTH": VarDocs( 17092 "The color depth used by prompt toolkit 2. Possible values are: " 17093 "``DEPTH_1_BIT``, ``DEPTH_4_BIT``, ``DEPTH_8_BIT``, ``DEPTH_24_BIT`` " 17094 "colors. Default is an empty string which means that prompt toolkit decide." 17095 ), 17096 "PUSHD_MINUS": VarDocs( 17097 "Flag for directory pushing functionality. False is the normal " "behavior." 17098 ), 17099 "PUSHD_SILENT": VarDocs( 17100 "Whether or not to suppress directory stack manipulation output." 17101 ), 17102 "RAISE_SUBPROC_ERROR": VarDocs( 17103 "Whether or not to raise an error if a subprocess (captured or " 17104 "uncaptured) returns a non-zero exit status, which indicates failure. " 17105 "This is most useful in xonsh scripts or modules where failures " 17106 "should cause an end to execution. This is less useful at a terminal. " 17107 "The error that is raised is a ``subprocess.CalledProcessError``." 17108 ), 17109 "RIGHT_PROMPT": VarDocs( 17110 "Template string for right-aligned text " 17111 "at the prompt. This may be parametrized in the same way as " 17112 "the ``$PROMPT`` variable. Currently, this is only available in the " 17113 "prompt-toolkit shell." 17114 ), 17115 "BOTTOM_TOOLBAR": VarDocs( 17116 "Template string for the bottom toolbar. " 17117 "This may be parametrized in the same way as " 17118 "the ``$PROMPT`` variable. Currently, this is only available in the " 17119 "prompt-toolkit shell." 17120 ), 17121 "SHELL_TYPE": VarDocs( 17122 "Which shell is used. Currently two base shell types are supported:\n\n" 17123 " - ``readline`` that is backed by Python's readline module\n" 17124 " - ``prompt_toolkit`` that uses external library of the same name\n" 17125 " - ``random`` selects a random shell from the above on startup\n" 17126 " - ``best`` selects the most feature-rich shell available on the\n" 17127 " user's system\n\n" 17128 "To use the ``prompt_toolkit`` shell you need to have the " 17129 "`prompt_toolkit <https://github.com/jonathanslenders/python-prompt-toolkit>`_" 17130 " library installed. To specify which shell should be used, do so in " 17131 "the run control file.", 17132 default="``best``", 17133 ), 17134 "SUBSEQUENCE_PATH_COMPLETION": VarDocs( 17135 "Toggles subsequence matching of paths for tab completion. " 17136 "If ``True``, then, e.g., ``~/u/ro`` can match ``~/lou/carcolh``." 17137 ), 17138 "SUGGEST_COMMANDS": VarDocs( 17139 "When a user types an invalid command, xonsh will try to offer " 17140 "suggestions of similar valid commands if this is True." 17141 ), 17142 "SUGGEST_MAX_NUM": VarDocs( 17143 "xonsh will show at most this many suggestions in response to an " 17144 "invalid command. If negative, there is no limit to how many " 17145 "suggestions are shown." 17146 ), 17147 "SUGGEST_THRESHOLD": VarDocs( 17148 "An error threshold. If the Levenshtein distance between the entered " 17149 "command and a valid command is less than this value, the valid " 17150 'command will be offered as a suggestion. Also used for "fuzzy" ' 17151 "tab completion of paths." 17152 ), 17153 "SUPPRESS_BRANCH_TIMEOUT_MESSAGE": VarDocs( 17154 "Whether or not to suppress branch timeout warning messages." 17155 ), 17156 "TERM": VarDocs( 17157 "TERM is sometimes set by the terminal emulator. This is used (when " 17158 "valid) to determine whether or not to set the title. Users shouldn't " 17159 "need to set this themselves. Note that this variable should be set as " 17160 "early as possible in order to ensure it is effective. Here are a few " 17161 "options:\n\n" 17162 "* Set this from the program that launches xonsh. On POSIX systems, \n" 17163 " this can be performed by using env, e.g. \n" 17164 " ``/usr/bin/env TERM=xterm-color xonsh`` or similar.\n" 17165 "* From the xonsh command line, namely ``xonsh -DTERM=xterm-color``.\n" 17166 '* In the config file with ``{"env": {"TERM": "xterm-color"}}``.\n' 17167 "* Lastly, in xonshrc with ``$TERM``\n\n" 17168 "Ideally, your terminal emulator will set this correctly but that does " 17169 "not always happen.", 17170 configurable=False, 17171 ), 17172 "TITLE": VarDocs( 17173 "The title text for the window in which xonsh is running. Formatted " 17174 "in the same manner as ``$PROMPT``, see 'Customizing the Prompt' " 17175 "http://xon.sh/tutorial.html#customizing-the-prompt.", 17176 default="``xonsh.environ.DEFAULT_TITLE``", 17177 ), 17178 "UPDATE_COMPLETIONS_ON_KEYPRESS": VarDocs( 17179 "Completions display is evaluated and presented whenever a key is " 17180 "pressed. This avoids the need to press TAB, except to cycle through " 17181 "the possibilities. This currently only affects the prompt-toolkit shell." 17182 ), 17183 "UPDATE_OS_ENVIRON": VarDocs( 17184 "If True ``os_environ`` will always be updated " 17185 "when the xonsh environment changes. The environment can be reset to " 17186 "the default value by calling ``__xonsh_env__.undo_replace_env()``" 17187 ), 17188 "UPDATE_PROMPT_ON_KEYPRESS": VarDocs( 17189 "Disables caching the prompt between commands, " 17190 "so that it would be reevaluated on each keypress. " 17191 "Disabled by default because of the incurred performance penalty." 17192 ), 17193 "VC_BRANCH_TIMEOUT": VarDocs( 17194 "The timeout (in seconds) for version control " 17195 "branch computations. This is a timeout per subprocess call, so the " 17196 "total time to compute will be larger than this in many cases." 17197 ), 17198 "VC_HG_SHOW_BRANCH": VarDocs( 17199 "Whether or not to show the Mercurial branch in the prompt." 17200 ), 17201 "VI_MODE": VarDocs( 17202 "Flag to enable ``vi_mode`` in the ``prompt_toolkit`` shell." 17203 ), 17204 "VIRTUAL_ENV": VarDocs( 17205 "Path to the currently active Python environment.", configurable=False 17206 ), 17207 "WIN_UNICODE_CONSOLE": VarDocs( 17208 "Enables unicode support in windows terminals. Requires the external " 17209 "library ``win_unicode_console``.", 17210 configurable=ON_WINDOWS, 17211 ), 17212 "XDG_CONFIG_HOME": VarDocs( 17213 "Open desktop standard configuration home dir. This is the same " 17214 "default as used in the standard.", 17215 configurable=False, 17216 default="``~/.config``", 17217 ), 17218 "XDG_DATA_HOME": VarDocs( 17219 "Open desktop standard data home dir. This is the same default as " 17220 "used in the standard.", 17221 default="``~/.local/share``", 17222 ), 17223 "XONSHRC": VarDocs( 17224 "A list of the locations of run control files, if they exist. User " 17225 "defined run control file will supersede values set in system-wide " 17226 "control file if there is a naming collision.", 17227 default=( 17228 "On Linux & Mac OSX: ``['/etc/xonshrc', '~/.config/xonsh/rc.xsh', '~/.xonshrc']``\n" 17229 "\nOn Windows: " 17230 "``['%ALLUSERSPROFILE%\\\\xonsh\\\\xonshrc', '~/.config/xonsh/rc.xsh', '~/.xonshrc']``" 17231 ), 17232 ), 17233 "XONSH_APPEND_NEWLINE": VarDocs( 17234 "Append new line when a partial line is preserved in output." 17235 ), 17236 "XONSH_AUTOPAIR": VarDocs( 17237 "Whether Xonsh will auto-insert matching parentheses, brackets, and " 17238 "quotes. Only available under the prompt-toolkit shell." 17239 ), 17240 "XONSH_CACHE_SCRIPTS": VarDocs( 17241 "Controls whether the code for scripts run from xonsh will be cached" 17242 " (``True``) or re-compiled each time (``False``)." 17243 ), 17244 "XONSH_CACHE_EVERYTHING": VarDocs( 17245 "Controls whether all code (including code entered at the interactive" 17246 " prompt) will be cached." 17247 ), 17248 "XONSH_COLOR_STYLE": VarDocs( 17249 "Sets the color style for xonsh colors. This is a style name, not " 17250 "a color map. Run ``xonfig styles`` to see the available styles." 17251 ), 17252 "XONSH_CONFIG_DIR": VarDocs( 17253 "This is the location where xonsh configuration information is stored.", 17254 configurable=False, 17255 default="``$XDG_CONFIG_HOME/xonsh``", 17256 ), 17257 "XONSH_DEBUG": VarDocs( 17258 "Sets the xonsh debugging level. This may be an integer or a boolean. " 17259 "Setting this variable prior to stating xonsh to ``1`` or ``True`` " 17260 "will suppress amalgamated imports. Setting it to ``2`` will get some " 17261 "basic information like input transformation, command replacement. " 17262 "With ``3`` or a higher number will make more debugging information " 17263 "presented, like PLY parsing messages.", 17264 configurable=False, 17265 ), 17266 "XONSH_DATA_DIR": VarDocs( 17267 "This is the location where xonsh data files are stored, such as " 17268 "history.", 17269 default="``$XDG_DATA_HOME/xonsh``", 17270 ), 17271 "XONSH_ENCODING": VarDocs( 17272 "This is the encoding that xonsh should use for subprocess operations.", 17273 default="``sys.getdefaultencoding()``", 17274 ), 17275 "XONSH_ENCODING_ERRORS": VarDocs( 17276 "The flag for how to handle encoding errors should they happen. " 17277 "Any string flag that has been previously registered with Python " 17278 "is allowed. See the 'Python codecs documentation' " 17279 "(https://docs.python.org/3/library/codecs.html#error-handlers) " 17280 "for more information and available options.", 17281 default="``surrogateescape``", 17282 ), 17283 "XONSH_GITSTATUS_*": VarDocs( 17284 "Symbols for gitstatus prompt. Default values are: \n\n" 17285 "* ``XONSH_GITSTATUS_HASH``: ``:``\n" 17286 "* ``XONSH_GITSTATUS_BRANCH``: ``{CYAN}``\n" 17287 "* ``XONSH_GITSTATUS_OPERATION``: ``{CYAN}``\n" 17288 "* ``XONSH_GITSTATUS_STAGED``: ``{RED}●``\n" 17289 "* ``XONSH_GITSTATUS_CONFLICTS``: ``{RED}×``\n" 17290 "* ``XONSH_GITSTATUS_CHANGED``: ``{BLUE}+``\n" 17291 "* ``XONSH_GITSTATUS_UNTRACKED``: ``…``\n" 17292 "* ``XONSH_GITSTATUS_STASHED``: ``⚑``\n" 17293 "* ``XONSH_GITSTATUS_CLEAN``: ``{BOLD_GREEN}✓``\n" 17294 "* ``XONSH_GITSTATUS_AHEAD``: ``↑·``\n" 17295 "* ``XONSH_GITSTATUS_BEHIND``: ``↓·``\n" 17296 ), 17297 "XONSH_HISTORY_BACKEND": VarDocs( 17298 "Set which history backend to use. Options are: 'json', " 17299 "'sqlite', and 'dummy'. The default is 'json'. " 17300 "``XONSH_HISTORY_BACKEND`` also accepts a class type that inherits " 17301 "from ``xonsh.history.base.History``, or its instance." 17302 ), 17303 "XONSH_HISTORY_FILE": VarDocs( 17304 "Location of history file (deprecated).", 17305 configurable=False, 17306 default="``~/.xonsh_history``", 17307 ), 17308 "XONSH_HISTORY_MATCH_ANYWHERE": VarDocs( 17309 "When searching history from a partial string (by pressing up arrow), " 17310 "match command history anywhere in a given line (not just the start)", 17311 default="False", 17312 ), 17313 "XONSH_HISTORY_SIZE": VarDocs( 17314 "Value and units tuple that sets the size of history after garbage " 17315 "collection. Canonical units are:\n\n" 17316 "- ``commands`` for the number of past commands executed,\n" 17317 "- ``files`` for the number of history files to keep,\n" 17318 "- ``s`` for the number of seconds in the past that are allowed, and\n" 17319 "- ``b`` for the number of bytes that history may consume.\n\n" 17320 "Common abbreviations, such as '6 months' or '1 GB' are also allowed.", 17321 default="``(8128, 'commands')`` or ``'8128 commands'``", 17322 ), 17323 "XONSH_INTERACTIVE": VarDocs( 17324 "``True`` if xonsh is running interactively, and ``False`` otherwise.", 17325 configurable=False, 17326 ), 17327 "XONSH_LOGIN": VarDocs( 17328 "``True`` if xonsh is running as a login shell, and ``False`` otherwise.", 17329 configurable=False, 17330 ), 17331 "XONSH_PROC_FREQUENCY": VarDocs( 17332 "The process frequency is the time that " 17333 "xonsh process threads sleep for while running command pipelines. " 17334 "The value has units of seconds [s]." 17335 ), 17336 "XONSH_SHOW_TRACEBACK": VarDocs( 17337 "Controls if a traceback is shown if exceptions occur in the shell. " 17338 "Set to ``True`` to always show traceback or ``False`` to always hide. " 17339 "If undefined then the traceback is hidden but a notice is shown on how " 17340 "to enable the full traceback." 17341 ), 17342 "XONSH_SOURCE": VarDocs( 17343 "When running a xonsh script, this variable contains the absolute path " 17344 "to the currently executing script's file.", 17345 configurable=False, 17346 ), 17347 "XONSH_STDERR_PREFIX": VarDocs( 17348 "A format string, using the same keys and colors as ``$PROMPT``, that " 17349 "is prepended whenever stderr is displayed. This may be used in " 17350 "conjunction with ``$XONSH_STDERR_POSTFIX`` to close out the block." 17351 "For example, to have stderr appear on a red background, the " 17352 'prefix & postfix pair would be "{BACKGROUND_RED}" & "{NO_COLOR}".' 17353 ), 17354 "XONSH_STDERR_POSTFIX": VarDocs( 17355 "A format string, using the same keys and colors as ``$PROMPT``, that " 17356 "is appended whenever stderr is displayed. This may be used in " 17357 "conjunction with ``$XONSH_STDERR_PREFIX`` to start the block." 17358 "For example, to have stderr appear on a red background, the " 17359 'prefix & postfix pair would be "{BACKGROUND_RED}" & "{NO_COLOR}".' 17360 ), 17361 "XONSH_STORE_STDIN": VarDocs( 17362 "Whether or not to store the stdin that is supplied to the " 17363 "``!()`` and ``![]`` operators." 17364 ), 17365 "XONSH_STORE_STDOUT": VarDocs( 17366 "Whether or not to store the ``stdout`` and ``stderr`` streams in the " 17367 "history files." 17368 ), 17369 "XONSH_TRACEBACK_LOGFILE": VarDocs( 17370 "Specifies a file to store the traceback log to, regardless of whether " 17371 "``XONSH_SHOW_TRACEBACK`` has been set. Its value must be a writable file " 17372 "or None / the empty string if traceback logging is not desired. " 17373 "Logging to a file is not enabled by default." 17374 ), 17375 "XONSH_DATETIME_FORMAT": VarDocs( 17376 "The format that is used for ``datetime.strptime()`` in various places" 17377 "i.e the history timestamp option" 17378 ), 17379 } 17380 17381 17382# 17383# actual environment 17384# 17385 17386 17387class Env(cabc.MutableMapping): 17388 """A xonsh environment, whose variables have limited typing 17389 (unlike BASH). Most variables are, by default, strings (like BASH). 17390 However, the following rules also apply based on variable-name: 17391 17392 * PATH: any variable whose name ends in PATH is a list of strings. 17393 * XONSH_HISTORY_SIZE: this variable is an (int | float, str) tuple. 17394 * LC_* (locale categories): locale category names get/set the Python 17395 locale via locale.getlocale() and locale.setlocale() functions. 17396 17397 An Env instance may be converted to an untyped version suitable for 17398 use in a subprocess. 17399 """ 17400 17401 _arg_regex = None 17402 17403 def __init__(self, *args, **kwargs): 17404 """If no initial environment is given, os_environ is used.""" 17405 self._d = {} 17406 # sentinel value for non existing envvars 17407 self._no_value = object() 17408 self._orig_env = None 17409 self._ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()} 17410 self._defaults = DEFAULT_VALUES 17411 self._docs = DEFAULT_DOCS 17412 if len(args) == 0 and len(kwargs) == 0: 17413 args = (os_environ,) 17414 for key, val in dict(*args, **kwargs).items(): 17415 self[key] = val 17416 if ON_WINDOWS: 17417 path_key = next((k for k in self._d if k.upper() == "PATH"), None) 17418 if path_key: 17419 self["PATH"] = self._d.pop(path_key) 17420 if "PATH" not in self._d: 17421 # this is here so the PATH is accessible to subprocs and so that 17422 # it can be modified in-place in the xonshrc file 17423 self._d["PATH"] = list(PATH_DEFAULT) 17424 self._detyped = None 17425 17426 @staticmethod 17427 def detypeable(val): 17428 return not (callable(val) or isinstance(val, cabc.MutableMapping)) 17429 17430 def detype(self): 17431 if self._detyped is not None: 17432 return self._detyped 17433 ctx = {} 17434 for key, val in self._d.items(): 17435 if not self.detypeable(val): 17436 continue 17437 if not isinstance(key, str): 17438 key = str(key) 17439 ensurer = self.get_ensurer(key) 17440 val = ensurer.detype(val) 17441 ctx[key] = val 17442 self._detyped = ctx 17443 return ctx 17444 17445 def replace_env(self): 17446 """Replaces the contents of os_environ with a detyped version 17447 of the xonsh environment. 17448 """ 17449 if self._orig_env is None: 17450 self._orig_env = dict(os_environ) 17451 os_environ.clear() 17452 os_environ.update(self.detype()) 17453 17454 def undo_replace_env(self): 17455 """Replaces the contents of os_environ with a detyped version 17456 of the xonsh environment. 17457 """ 17458 if self._orig_env is not None: 17459 os_environ.clear() 17460 os_environ.update(self._orig_env) 17461 self._orig_env = None 17462 17463 def get_ensurer(self, key, default=Ensurer(always_true, None, ensure_string)): 17464 """Gets an ensurer for the given key.""" 17465 if key in self._ensurers: 17466 return self._ensurers[key] 17467 for k, ensurer in self._ensurers.items(): 17468 if isinstance(k, str): 17469 continue 17470 if k.match(key) is not None: 17471 break 17472 else: 17473 ensurer = default 17474 self._ensurers[key] = ensurer 17475 return ensurer 17476 17477 def get_docs(self, key, default=VarDocs("<no documentation>")): 17478 """Gets the documentation for the environment variable.""" 17479 vd = self._docs.get(key, None) 17480 if vd is None: 17481 return default 17482 if vd.default is DefaultNotGiven: 17483 dval = pprint.pformat(self._defaults.get(key, "<default not set>")) 17484 vd = vd._replace(default=dval) 17485 self._docs[key] = vd 17486 return vd 17487 17488 def help(self, key): 17489 """Get information about a specific environment variable.""" 17490 vardocs = self.get_docs(key) 17491 width = min(79, os.get_terminal_size()[0]) 17492 docstr = "\n".join(textwrap.wrap(vardocs.docstr, width=width)) 17493 template = HELP_TEMPLATE.format( 17494 envvar=key, 17495 docstr=docstr, 17496 default=vardocs.default, 17497 configurable=vardocs.configurable, 17498 ) 17499 print_color(template) 17500 17501 def is_manually_set(self, varname): 17502 """ 17503 Checks if an environment variable has been manually set. 17504 """ 17505 return varname in self._d 17506 17507 @contextlib.contextmanager 17508 def swap(self, other=None, **kwargs): 17509 """Provides a context manager for temporarily swapping out certain 17510 environment variables with other values. On exit from the context 17511 manager, the original values are restored. 17512 """ 17513 old = {} 17514 # single positional argument should be a dict-like object 17515 if other is not None: 17516 for k, v in other.items(): 17517 old[k] = self.get(k, NotImplemented) 17518 self[k] = v 17519 # kwargs could also have been sent in 17520 for k, v in kwargs.items(): 17521 old[k] = self.get(k, NotImplemented) 17522 self[k] = v 17523 17524 exception = None 17525 try: 17526 yield self 17527 except Exception as e: 17528 exception = e 17529 finally: 17530 # restore the values 17531 for k, v in old.items(): 17532 if v is NotImplemented: 17533 del self[k] 17534 else: 17535 self[k] = v 17536 if exception is not None: 17537 raise exception from None 17538 17539 # 17540 # Mutable mapping interface 17541 # 17542 17543 def __getitem__(self, key): 17544 # remove this block on next release 17545 if key == "FORMATTER_DICT": 17546 print( 17547 "PendingDeprecationWarning: FORMATTER_DICT is an alias of " 17548 "PROMPT_FIELDS and will be removed in the next release", 17549 file=sys.stderr, 17550 ) 17551 return self["PROMPT_FIELDS"] 17552 if key is Ellipsis: 17553 return self 17554 elif key in self._d: 17555 val = self._d[key] 17556 elif key in self._defaults: 17557 val = self._defaults[key] 17558 if is_callable_default(val): 17559 val = val(self) 17560 else: 17561 e = "Unknown environment variable: ${}" 17562 raise KeyError(e.format(key)) 17563 if isinstance( 17564 val, (cabc.MutableSet, cabc.MutableSequence, cabc.MutableMapping) 17565 ): 17566 self._detyped = None 17567 return val 17568 17569 def __setitem__(self, key, val): 17570 ensurer = self.get_ensurer(key) 17571 if not ensurer.validate(val): 17572 val = ensurer.convert(val) 17573 # existing envvars can have any value including None 17574 old_value = self._d[key] if key in self._d else self._no_value 17575 self._d[key] = val 17576 if self.detypeable(val): 17577 self._detyped = None 17578 if self.get("UPDATE_OS_ENVIRON"): 17579 if self._orig_env is None: 17580 self.replace_env() 17581 else: 17582 os_environ[key] = ensurer.detype(val) 17583 if old_value is self._no_value: 17584 events.on_envvar_new.fire(name=key, value=val) 17585 elif old_value != val: 17586 events.on_envvar_change.fire(name=key, oldvalue=old_value, newvalue=val) 17587 17588 def __delitem__(self, key): 17589 val = self._d.pop(key) 17590 if self.detypeable(val): 17591 self._detyped = None 17592 if self.get("UPDATE_OS_ENVIRON") and key in os_environ: 17593 del os_environ[key] 17594 17595 def get(self, key, default=None): 17596 """The environment will look up default values from its own defaults if a 17597 default is not given here. 17598 """ 17599 try: 17600 return self[key] 17601 except KeyError: 17602 return default 17603 17604 def __iter__(self): 17605 yield from (set(self._d) | set(self._defaults)) 17606 17607 def __contains__(self, item): 17608 return item in self._d or item in self._defaults 17609 17610 def __len__(self): 17611 return len(self._d) 17612 17613 def __str__(self): 17614 return str(self._d) 17615 17616 def __repr__(self): 17617 return "{0}.{1}(...)".format( 17618 self.__class__.__module__, self.__class__.__name__, self._d 17619 ) 17620 17621 def _repr_pretty_(self, p, cycle): 17622 name = "{0}.{1}".format(self.__class__.__module__, self.__class__.__name__) 17623 with p.group(0, name + "(", ")"): 17624 if cycle: 17625 p.text("...") 17626 elif len(self): 17627 p.break_() 17628 p.pretty(dict(self)) 17629 17630 17631def _yield_executables(directory, name): 17632 if ON_WINDOWS: 17633 base_name, ext = os.path.splitext(name.lower()) 17634 for fname in executables_in(directory): 17635 fbase, fext = os.path.splitext(fname.lower()) 17636 if base_name == fbase and (len(ext) == 0 or ext == fext): 17637 yield os.path.join(directory, fname) 17638 else: 17639 for x in executables_in(directory): 17640 if x == name: 17641 yield os.path.join(directory, name) 17642 return 17643 17644 17645def locate_binary(name): 17646 """Locates an executable on the file system.""" 17647 return builtins.__xonsh_commands_cache__.locate_binary(name) 17648 17649 17650BASE_ENV = LazyObject( 17651 lambda: { 17652 "BASH_COMPLETIONS": list(DEFAULT_VALUES["BASH_COMPLETIONS"]), 17653 "PROMPT_FIELDS": dict(DEFAULT_VALUES["PROMPT_FIELDS"]), 17654 "XONSH_VERSION": XONSH_VERSION, 17655 }, 17656 globals(), 17657 "BASE_ENV", 17658) 17659 17660 17661def xonshrc_context(rcfiles=None, execer=None, ctx=None, env=None, login=True): 17662 """Attempts to read in all xonshrc files and return the context.""" 17663 loaded = env["LOADED_RC_FILES"] = [] 17664 ctx = {} if ctx is None else ctx 17665 if rcfiles is None: 17666 return env 17667 env["XONSHRC"] = tuple(rcfiles) 17668 for rcfile in rcfiles: 17669 if not os.path.isfile(rcfile): 17670 loaded.append(False) 17671 continue 17672 _, ext = os.path.splitext(rcfile) 17673 status = xonsh_script_run_control(rcfile, ctx, env, execer=execer, login=login) 17674 loaded.append(status) 17675 return ctx 17676 17677 17678def windows_foreign_env_fixes(ctx): 17679 """Environment fixes for Windows. Operates in-place.""" 17680 # remove these bash variables which only cause problems. 17681 for ev in ["HOME", "OLDPWD"]: 17682 if ev in ctx: 17683 del ctx[ev] 17684 # Override path-related bash variables; on Windows bash uses 17685 # /c/Windows/System32 syntax instead of C:\\Windows\\System32 17686 # which messes up these environment variables for xonsh. 17687 for ev in ["PATH", "TEMP", "TMP"]: 17688 if ev in os_environ: 17689 ctx[ev] = os_environ[ev] 17690 elif ev in ctx: 17691 del ctx[ev] 17692 ctx["PWD"] = _get_cwd() or "" 17693 17694 17695def foreign_env_fixes(ctx): 17696 """Environment fixes for all operating systems""" 17697 if "PROMPT" in ctx: 17698 del ctx["PROMPT"] 17699 17700 17701def xonsh_script_run_control(filename, ctx, env, execer=None, login=True): 17702 """Loads a xonsh file and applies it as a run control.""" 17703 if execer is None: 17704 return False 17705 updates = {"__file__": filename, "__name__": os.path.abspath(filename)} 17706 try: 17707 with swap_values(ctx, updates): 17708 run_script_with_cache(filename, execer, ctx) 17709 loaded = True 17710 except SyntaxError as err: 17711 msg = "syntax error in xonsh run control file {0!r}: {1!s}" 17712 print_exception(msg.format(filename, err)) 17713 loaded = False 17714 except Exception as err: 17715 msg = "error running xonsh run control file {0!r}: {1!s}" 17716 print_exception(msg.format(filename, err)) 17717 loaded = False 17718 return loaded 17719 17720 17721def default_env(env=None): 17722 """Constructs a default xonsh environment.""" 17723 # in order of increasing precedence 17724 ctx = dict(BASE_ENV) 17725 ctx.update(os_environ) 17726 ctx["PWD"] = _get_cwd() or "" 17727 # These can cause problems for programs (#2543) 17728 ctx.pop("LINES", None) 17729 ctx.pop("COLUMNS", None) 17730 # other shells' PROMPT definitions generally don't work in XONSH: 17731 try: 17732 del ctx["PROMPT"] 17733 except KeyError: 17734 pass 17735 # finalize env 17736 if env is not None: 17737 ctx.update(env) 17738 return ctx 17739 17740 17741def make_args_env(args=None): 17742 """Makes a dictionary containing the $ARGS and $ARG<N> environment 17743 variables. If the supplied ARGS is None, then sys.argv is used. 17744 """ 17745 if args is None: 17746 args = sys.argv 17747 env = {"ARG" + str(i): arg for i, arg in enumerate(args)} 17748 env["ARGS"] = list(args) # make a copy so we don't interfere with original variable 17749 return env 17750 17751# 17752# inspectors 17753# 17754# -*- coding: utf-8 -*- 17755"""Tools for inspecting Python objects. 17756 17757This file was forked from the IPython project: 17758 17759* Copyright (c) 2008-2014, IPython Development Team 17760* Copyright (C) 2001-2007 Fernando Perez <fperez@colorado.edu> 17761* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 17762* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 17763""" 17764# amalgamated os 17765# amalgamated io 17766# amalgamated sys 17767# amalgamated types 17768# amalgamated inspect 17769# amalgamated itertools 17770linecache = _LazyModule.load('linecache', 'linecache') 17771# amalgamated collections 17772# amalgamated xonsh.lazyasd 17773# amalgamated xonsh.tokenize 17774# amalgamated xonsh.openpy 17775# amalgamated xonsh.tools 17776# amalgamated xonsh.platform 17777# amalgamated xonsh.lazyimps 17778# amalgamated xonsh.style_tools 17779_func_call_docstring = LazyObject( 17780 lambda: types.FunctionType.__call__.__doc__, globals(), "_func_call_docstring" 17781) 17782_object_init_docstring = LazyObject( 17783 lambda: object.__init__.__doc__, globals(), "_object_init_docstring" 17784) 17785_builtin_type_docstrings = LazyObject( 17786 lambda: { 17787 t.__doc__ for t in (types.ModuleType, types.MethodType, types.FunctionType) 17788 }, 17789 globals(), 17790 "_builtin_type_docstrings", 17791) 17792 17793_builtin_func_type = LazyObject(lambda: type(all), globals(), "_builtin_func_type") 17794# Bound methods have the same type as builtin functions 17795_builtin_meth_type = LazyObject( 17796 lambda: type(str.upper), globals(), "_builtin_meth_type" 17797) 17798 17799info_fields = LazyObject( 17800 lambda: [ 17801 "type_name", 17802 "base_class", 17803 "string_form", 17804 "namespace", 17805 "length", 17806 "file", 17807 "definition", 17808 "docstring", 17809 "source", 17810 "init_definition", 17811 "class_docstring", 17812 "init_docstring", 17813 "call_def", 17814 "call_docstring", 17815 # These won't be printed but will be used to determine how to 17816 # format the object 17817 "ismagic", 17818 "isalias", 17819 "isclass", 17820 "argspec", 17821 "found", 17822 "name", 17823 ], 17824 globals(), 17825 "info_fields", 17826) 17827 17828 17829def object_info(**kw): 17830 """Make an object info dict with all fields present.""" 17831 infodict = dict(itertools.zip_longest(info_fields, [None])) 17832 infodict.update(kw) 17833 return infodict 17834 17835 17836def get_encoding(obj): 17837 """Get encoding for python source file defining obj 17838 17839 Returns None if obj is not defined in a sourcefile. 17840 """ 17841 ofile = find_file(obj) 17842 # run contents of file through pager starting at line where the object 17843 # is defined, as long as the file isn't binary and is actually on the 17844 # filesystem. 17845 if ofile is None: 17846 return None 17847 elif ofile.endswith((".so", ".dll", ".pyd")): 17848 return None 17849 elif not os.path.isfile(ofile): 17850 return None 17851 else: 17852 # Print only text files, not extension binaries. Note that 17853 # getsourcelines returns lineno with 1-offset and page() uses 17854 # 0-offset, so we must adjust. 17855 with io.open(ofile, "rb") as buf: # Tweaked to use io.open for Python 2 17856 encoding, _ = detect_encoding(buf.readline) 17857 return encoding 17858 17859 17860def getdoc(obj): 17861 """Stable wrapper around inspect.getdoc. 17862 17863 This can't crash because of attribute problems. 17864 17865 It also attempts to call a getdoc() method on the given object. This 17866 allows objects which provide their docstrings via non-standard mechanisms 17867 (like Pyro proxies) to still be inspected by ipython's ? system.""" 17868 # Allow objects to offer customized documentation via a getdoc method: 17869 try: 17870 ds = obj.getdoc() 17871 except Exception: # pylint:disable=broad-except 17872 pass 17873 else: 17874 # if we get extra info, we add it to the normal docstring. 17875 if isinstance(ds, str): 17876 return inspect.cleandoc(ds) 17877 17878 try: 17879 docstr = inspect.getdoc(obj) 17880 encoding = get_encoding(obj) 17881 return cast_unicode(docstr, encoding=encoding) 17882 except Exception: # pylint:disable=broad-except 17883 # Harden against an inspect failure, which can occur with 17884 # SWIG-wrapped extensions. 17885 raise 17886 17887 17888def getsource(obj, is_binary=False): 17889 """Wrapper around inspect.getsource. 17890 17891 This can be modified by other projects to provide customized source 17892 extraction. 17893 17894 Inputs: 17895 17896 - obj: an object whose source code we will attempt to extract. 17897 17898 Optional inputs: 17899 17900 - is_binary: whether the object is known to come from a binary source. 17901 This implementation will skip returning any output for binary objects, 17902 but custom extractors may know how to meaningfully process them.""" 17903 17904 if is_binary: 17905 return None 17906 else: 17907 # get source if obj was decorated with @decorator 17908 if hasattr(obj, "__wrapped__"): 17909 obj = obj.__wrapped__ 17910 try: 17911 src = inspect.getsource(obj) 17912 except TypeError: 17913 if hasattr(obj, "__class__"): 17914 src = inspect.getsource(obj.__class__) 17915 encoding = get_encoding(obj) 17916 return cast_unicode(src, encoding=encoding) 17917 17918 17919def is_simple_callable(obj): 17920 """True if obj is a function ()""" 17921 return ( 17922 inspect.isfunction(obj) 17923 or inspect.ismethod(obj) 17924 or isinstance(obj, _builtin_func_type) 17925 or isinstance(obj, _builtin_meth_type) 17926 ) 17927 17928 17929def getargspec(obj): 17930 """Wrapper around :func:`inspect.getfullargspec` on Python 3, and 17931 :func:inspect.getargspec` on Python 2. 17932 17933 In addition to functions and methods, this can also handle objects with a 17934 ``__call__`` attribute. 17935 """ 17936 if safe_hasattr(obj, "__call__") and not is_simple_callable(obj): 17937 obj = obj.__call__ 17938 17939 return inspect.getfullargspec(obj) 17940 17941 17942def format_argspec(argspec): 17943 """Format argspect, convenience wrapper around inspect's. 17944 17945 This takes a dict instead of ordered arguments and calls 17946 inspect.format_argspec with the arguments in the necessary order. 17947 """ 17948 return inspect.formatargspec( 17949 argspec["args"], argspec["varargs"], argspec["varkw"], argspec["defaults"] 17950 ) 17951 17952 17953def call_tip(oinfo, format_call=True): 17954 """Extract call tip data from an oinfo dict. 17955 17956 Parameters 17957 ---------- 17958 oinfo : dict 17959 17960 format_call : bool, optional 17961 If True, the call line is formatted and returned as a string. If not, a 17962 tuple of (name, argspec) is returned. 17963 17964 Returns 17965 ------- 17966 call_info : None, str or (str, dict) tuple. 17967 When format_call is True, the whole call information is formatted as a 17968 single string. Otherwise, the object's name and its argspec dict are 17969 returned. If no call information is available, None is returned. 17970 17971 docstring : str or None 17972 The most relevant docstring for calling purposes is returned, if 17973 available. The priority is: call docstring for callable instances, then 17974 constructor docstring for classes, then main object's docstring otherwise 17975 (regular functions). 17976 """ 17977 # Get call definition 17978 argspec = oinfo.get("argspec") 17979 if argspec is None: 17980 call_line = None 17981 else: 17982 # Callable objects will have 'self' as their first argument, prune 17983 # it out if it's there for clarity (since users do *not* pass an 17984 # extra first argument explicitly). 17985 try: 17986 has_self = argspec["args"][0] == "self" 17987 except (KeyError, IndexError): 17988 pass 17989 else: 17990 if has_self: 17991 argspec["args"] = argspec["args"][1:] 17992 17993 call_line = oinfo["name"] + format_argspec(argspec) 17994 17995 # Now get docstring. 17996 # The priority is: call docstring, constructor docstring, main one. 17997 doc = oinfo.get("call_docstring") 17998 if doc is None: 17999 doc = oinfo.get("init_docstring") 18000 if doc is None: 18001 doc = oinfo.get("docstring", "") 18002 18003 return call_line, doc 18004 18005 18006def find_file(obj): 18007 """Find the absolute path to the file where an object was defined. 18008 18009 This is essentially a robust wrapper around `inspect.getabsfile`. 18010 18011 Returns None if no file can be found. 18012 18013 Parameters 18014 ---------- 18015 obj : any Python object 18016 18017 Returns 18018 ------- 18019 fname : str 18020 The absolute path to the file where the object was defined. 18021 """ 18022 # get source if obj was decorated with @decorator 18023 if safe_hasattr(obj, "__wrapped__"): 18024 obj = obj.__wrapped__ 18025 18026 fname = None 18027 try: 18028 fname = inspect.getabsfile(obj) 18029 except TypeError: 18030 # For an instance, the file that matters is where its class was 18031 # declared. 18032 if hasattr(obj, "__class__"): 18033 try: 18034 fname = inspect.getabsfile(obj.__class__) 18035 except TypeError: 18036 # Can happen for builtins 18037 pass 18038 except: # pylint:disable=bare-except 18039 pass 18040 return cast_unicode(fname) 18041 18042 18043def find_source_lines(obj): 18044 """Find the line number in a file where an object was defined. 18045 18046 This is essentially a robust wrapper around `inspect.getsourcelines`. 18047 18048 Returns None if no file can be found. 18049 18050 Parameters 18051 ---------- 18052 obj : any Python object 18053 18054 Returns 18055 ------- 18056 lineno : int 18057 The line number where the object definition starts. 18058 """ 18059 # get source if obj was decorated with @decorator 18060 if safe_hasattr(obj, "__wrapped__"): 18061 obj = obj.__wrapped__ 18062 18063 try: 18064 try: 18065 lineno = inspect.getsourcelines(obj)[1] 18066 except TypeError: 18067 # For instances, try the class object like getsource() does 18068 if hasattr(obj, "__class__"): 18069 lineno = inspect.getsourcelines(obj.__class__)[1] 18070 else: 18071 lineno = None 18072 except: # pylint:disable=bare-except 18073 return None 18074 18075 return lineno 18076 18077 18078if PYTHON_VERSION_INFO < (3, 5, 0): 18079 FrameInfo = collections.namedtuple( 18080 "FrameInfo", 18081 ["frame", "filename", "lineno", "function", "code_context", "index"], 18082 ) 18083 18084 def getouterframes(frame, context=1): 18085 """Wrapper for getouterframes so that it acts like the Python v3.5 version.""" 18086 return [FrameInfo(*f) for f in inspect.getouterframes(frame, context=context)] 18087 18088 18089else: 18090 getouterframes = inspect.getouterframes 18091 18092 18093class Inspector(object): 18094 """Inspects objects.""" 18095 18096 def __init__(self, str_detail_level=0): 18097 self.str_detail_level = str_detail_level 18098 18099 def _getdef(self, obj, oname=""): 18100 """Return the call signature for any callable object. 18101 18102 If any exception is generated, None is returned instead and the 18103 exception is suppressed. 18104 """ 18105 try: 18106 hdef = oname + inspect.formatargspec(*getargspec(obj)) 18107 return cast_unicode(hdef) 18108 except: # pylint:disable=bare-except 18109 return None 18110 18111 def noinfo(self, msg, oname): 18112 """Generic message when no information is found.""" 18113 print("No %s found" % msg, end=" ") 18114 if oname: 18115 print("for %s" % oname) 18116 else: 18117 print() 18118 18119 def pdef(self, obj, oname=""): 18120 """Print the call signature for any callable object. 18121 18122 If the object is a class, print the constructor information. 18123 """ 18124 18125 if not callable(obj): 18126 print("Object is not callable.") 18127 return 18128 18129 header = "" 18130 18131 if inspect.isclass(obj): 18132 header = self.__head("Class constructor information:\n") 18133 obj = obj.__init__ 18134 18135 output = self._getdef(obj, oname) 18136 if output is None: 18137 self.noinfo("definition header", oname) 18138 else: 18139 print(header, output, end=" ", file=sys.stdout) 18140 18141 def pdoc(self, obj, oname=""): 18142 """Print the docstring for any object. 18143 18144 Optional 18145 18146 -formatter: a function to run the docstring through for specially 18147 formatted docstrings. 18148 """ 18149 18150 head = self.__head # For convenience 18151 lines = [] 18152 ds = getdoc(obj) 18153 if ds: 18154 lines.append(head("Class docstring:")) 18155 lines.append(indent(ds)) 18156 if inspect.isclass(obj) and hasattr(obj, "__init__"): 18157 init_ds = getdoc(obj.__init__) 18158 if init_ds is not None: 18159 lines.append(head("Init docstring:")) 18160 lines.append(indent(init_ds)) 18161 elif hasattr(obj, "__call__"): 18162 call_ds = getdoc(obj.__call__) 18163 if call_ds: 18164 lines.append(head("Call docstring:")) 18165 lines.append(indent(call_ds)) 18166 18167 if not lines: 18168 self.noinfo("documentation", oname) 18169 else: 18170 print("\n".join(lines)) 18171 18172 def psource(self, obj, oname=""): 18173 """Print the source code for an object.""" 18174 # Flush the source cache because inspect can return out-of-date source 18175 linecache.checkcache() 18176 try: 18177 src = getsource(obj) 18178 except: # pylint:disable=bare-except 18179 self.noinfo("source", oname) 18180 else: 18181 print(src) 18182 18183 def pfile(self, obj, oname=""): 18184 """Show the whole file where an object was defined.""" 18185 lineno = find_source_lines(obj) 18186 if lineno is None: 18187 self.noinfo("file", oname) 18188 return 18189 18190 ofile = find_file(obj) 18191 # run contents of file through pager starting at line where the object 18192 # is defined, as long as the file isn't binary and is actually on the 18193 # filesystem. 18194 if ofile.endswith((".so", ".dll", ".pyd")): 18195 print("File %r is binary, not printing." % ofile) 18196 elif not os.path.isfile(ofile): 18197 print("File %r does not exist, not printing." % ofile) 18198 else: 18199 # Print only text files, not extension binaries. Note that 18200 # getsourcelines returns lineno with 1-offset and page() uses 18201 # 0-offset, so we must adjust. 18202 o = read_py_file(ofile, skip_encoding_cookie=False) 18203 print(o, lineno - 1) 18204 18205 def _format_fields_str(self, fields, title_width=0): 18206 """Formats a list of fields for display using color strings. 18207 18208 Parameters 18209 ---------- 18210 fields : list 18211 A list of 2-tuples: (field_title, field_content) 18212 title_width : int 18213 How many characters to pad titles to. Default to longest title. 18214 """ 18215 out = [] 18216 if title_width == 0: 18217 title_width = max(len(title) + 2 for title, _ in fields) 18218 for title, content in fields: 18219 title_len = len(title) 18220 title = "{BOLD_RED}" + title + ":{NO_COLOR}" 18221 if len(content.splitlines()) > 1: 18222 title += "\n" 18223 else: 18224 title += " ".ljust(title_width - title_len) 18225 out.append(cast_unicode(title) + cast_unicode(content)) 18226 return format_color("\n".join(out) + "\n") 18227 18228 def _format_fields_tokens(self, fields, title_width=0): 18229 """Formats a list of fields for display using color tokens from 18230 pygments. 18231 18232 Parameters 18233 ---------- 18234 fields : list 18235 A list of 2-tuples: (field_title, field_content) 18236 title_width : int 18237 How many characters to pad titles to. Default to longest title. 18238 """ 18239 out = [] 18240 if title_width == 0: 18241 title_width = max(len(title) + 2 for title, _ in fields) 18242 for title, content in fields: 18243 title_len = len(title) 18244 title = "{BOLD_RED}" + title + ":{NO_COLOR}" 18245 if not isinstance(content, str) or len(content.splitlines()) > 1: 18246 title += "\n" 18247 else: 18248 title += " ".ljust(title_width - title_len) 18249 out += partial_color_tokenize(title) 18250 if isinstance(content, str): 18251 out[-1] = (out[-1][0], out[-1][1] + content + "\n") 18252 else: 18253 out += content 18254 out[-1] = (out[-1][0], out[-1][1] + "\n") 18255 out[-1] = (out[-1][0], out[-1][1] + "\n") 18256 return out 18257 18258 def _format_fields(self, fields, title_width=0): 18259 """Formats a list of fields for display using color tokens from 18260 pygments. 18261 18262 Parameters 18263 ---------- 18264 fields : list 18265 A list of 2-tuples: (field_title, field_content) 18266 title_width : int 18267 How many characters to pad titles to. Default to longest title. 18268 """ 18269 if HAS_PYGMENTS: 18270 rtn = self._format_fields_tokens(fields, title_width=title_width) 18271 else: 18272 rtn = self._format_fields_str(fields, title_width=title_width) 18273 return rtn 18274 18275 # The fields to be displayed by pinfo: (fancy_name, key_in_info_dict) 18276 pinfo_fields1 = [("Type", "type_name")] 18277 18278 pinfo_fields2 = [("String form", "string_form")] 18279 18280 pinfo_fields3 = [ 18281 ("Length", "length"), 18282 ("File", "file"), 18283 ("Definition", "definition"), 18284 ] 18285 18286 pinfo_fields_obj = [ 18287 ("Class docstring", "class_docstring"), 18288 ("Init docstring", "init_docstring"), 18289 ("Call def", "call_def"), 18290 ("Call docstring", "call_docstring"), 18291 ] 18292 18293 def pinfo(self, obj, oname="", info=None, detail_level=0): 18294 """Show detailed information about an object. 18295 18296 Parameters 18297 ---------- 18298 obj : object 18299 oname : str, optional 18300 name of the variable pointing to the object. 18301 info : dict, optional 18302 a structure with some information fields which may have been 18303 precomputed already. 18304 detail_level : int, optional 18305 if set to 1, more information is given. 18306 """ 18307 info = self.info(obj, oname=oname, info=info, detail_level=detail_level) 18308 displayfields = [] 18309 18310 def add_fields(fields): 18311 for title, key in fields: 18312 field = info[key] 18313 if field is not None: 18314 displayfields.append((title, field.rstrip())) 18315 18316 add_fields(self.pinfo_fields1) 18317 add_fields(self.pinfo_fields2) 18318 18319 # Namespace 18320 if info["namespace"] is not None and info["namespace"] != "Interactive": 18321 displayfields.append(("Namespace", info["namespace"].rstrip())) 18322 18323 add_fields(self.pinfo_fields3) 18324 if info["isclass"] and info["init_definition"]: 18325 displayfields.append(("Init definition", info["init_definition"].rstrip())) 18326 18327 # Source or docstring, depending on detail level and whether 18328 # source found. 18329 if detail_level > 0 and info["source"] is not None: 18330 displayfields.append(("Source", cast_unicode(info["source"]))) 18331 elif info["docstring"] is not None: 18332 displayfields.append(("Docstring", info["docstring"])) 18333 18334 # Constructor info for classes 18335 if info["isclass"]: 18336 if info["init_docstring"] is not None: 18337 displayfields.append(("Init docstring", info["init_docstring"])) 18338 18339 # Info for objects: 18340 else: 18341 add_fields(self.pinfo_fields_obj) 18342 18343 # Finally send to printer/pager: 18344 if displayfields: 18345 print_color(self._format_fields(displayfields)) 18346 18347 def info(self, obj, oname="", info=None, detail_level=0): 18348 """Compute a dict with detailed information about an object. 18349 18350 Optional arguments: 18351 18352 - oname: name of the variable pointing to the object. 18353 18354 - info: a structure with some information fields which may have been 18355 precomputed already. 18356 18357 - detail_level: if set to 1, more information is given. 18358 """ 18359 obj_type = type(obj) 18360 if info is None: 18361 ismagic = 0 18362 isalias = 0 18363 ospace = "" 18364 else: 18365 ismagic = info.ismagic 18366 isalias = info.isalias 18367 ospace = info.namespace 18368 # Get docstring, special-casing aliases: 18369 if isalias: 18370 if not callable(obj): 18371 if len(obj) >= 2 and isinstance(obj[1], str): 18372 ds = "Alias to the system command:\n {0}".format(obj[1]) 18373 else: # pylint:disable=bare-except 18374 ds = "Alias: " + str(obj) 18375 else: 18376 ds = "Alias to " + str(obj) 18377 if obj.__doc__: 18378 ds += "\nDocstring:\n" + obj.__doc__ 18379 else: 18380 ds = getdoc(obj) 18381 if ds is None: 18382 ds = "<no docstring>" 18383 18384 # store output in a dict, we initialize it here and fill it as we go 18385 out = dict(name=oname, found=True, isalias=isalias, ismagic=ismagic) 18386 18387 string_max = 200 # max size of strings to show (snipped if longer) 18388 shalf = int((string_max - 5) / 2) 18389 18390 if ismagic: 18391 obj_type_name = "Magic function" 18392 elif isalias: 18393 obj_type_name = "System alias" 18394 else: 18395 obj_type_name = obj_type.__name__ 18396 out["type_name"] = obj_type_name 18397 18398 try: 18399 bclass = obj.__class__ 18400 out["base_class"] = str(bclass) 18401 except: # pylint:disable=bare-except 18402 pass 18403 18404 # String form, but snip if too long in ? form (full in ??) 18405 if detail_level >= self.str_detail_level: 18406 try: 18407 ostr = str(obj) 18408 str_head = "string_form" 18409 if not detail_level and len(ostr) > string_max: 18410 ostr = ostr[:shalf] + " <...> " + ostr[-shalf:] 18411 ostr = ("\n" + " " * len(str_head.expandtabs())).join( 18412 q.strip() for q in ostr.split("\n") 18413 ) 18414 out[str_head] = ostr 18415 except: # pylint:disable=bare-except 18416 pass 18417 18418 if ospace: 18419 out["namespace"] = ospace 18420 18421 # Length (for strings and lists) 18422 try: 18423 out["length"] = str(len(obj)) 18424 except: # pylint:disable=bare-except 18425 pass 18426 18427 # Filename where object was defined 18428 binary_file = False 18429 fname = find_file(obj) 18430 if fname is None: 18431 # if anything goes wrong, we don't want to show source, so it's as 18432 # if the file was binary 18433 binary_file = True 18434 else: 18435 if fname.endswith((".so", ".dll", ".pyd")): 18436 binary_file = True 18437 elif fname.endswith("<string>"): 18438 fname = "Dynamically generated function. " "No source code available." 18439 out["file"] = fname 18440 18441 # Docstrings only in detail 0 mode, since source contains them (we 18442 # avoid repetitions). If source fails, we add them back, see below. 18443 if ds and detail_level == 0: 18444 out["docstring"] = ds 18445 18446 # Original source code for any callable 18447 if detail_level: 18448 # Flush the source cache because inspect can return out-of-date 18449 # source 18450 linecache.checkcache() 18451 source = None 18452 try: 18453 try: 18454 source = getsource(obj, binary_file) 18455 except TypeError: 18456 if hasattr(obj, "__class__"): 18457 source = getsource(obj.__class__, binary_file) 18458 if source is not None: 18459 source = source.rstrip() 18460 if HAS_PYGMENTS: 18461 lexer = pyghooks.XonshLexer() 18462 source = list(pygments.lex(source, lexer=lexer)) 18463 out["source"] = source 18464 except Exception: # pylint:disable=broad-except 18465 pass 18466 18467 if ds and source is None: 18468 out["docstring"] = ds 18469 18470 # Constructor docstring for classes 18471 if inspect.isclass(obj): 18472 out["isclass"] = True 18473 # reconstruct the function definition and print it: 18474 try: 18475 obj_init = obj.__init__ 18476 except AttributeError: 18477 init_def = init_ds = None 18478 else: 18479 init_def = self._getdef(obj_init, oname) 18480 init_ds = getdoc(obj_init) 18481 # Skip Python's auto-generated docstrings 18482 if init_ds == _object_init_docstring: 18483 init_ds = None 18484 18485 if init_def or init_ds: 18486 if init_def: 18487 out["init_definition"] = init_def 18488 if init_ds: 18489 out["init_docstring"] = init_ds 18490 18491 # and class docstring for instances: 18492 else: 18493 # reconstruct the function definition and print it: 18494 defln = self._getdef(obj, oname) 18495 if defln: 18496 out["definition"] = defln 18497 18498 # First, check whether the instance docstring is identical to the 18499 # class one, and print it separately if they don't coincide. In 18500 # most cases they will, but it's nice to print all the info for 18501 # objects which use instance-customized docstrings. 18502 if ds: 18503 try: 18504 cls = getattr(obj, "__class__") 18505 except: # pylint:disable=bare-except 18506 class_ds = None 18507 else: 18508 class_ds = getdoc(cls) 18509 # Skip Python's auto-generated docstrings 18510 if class_ds in _builtin_type_docstrings: 18511 class_ds = None 18512 if class_ds and ds != class_ds: 18513 out["class_docstring"] = class_ds 18514 18515 # Next, try to show constructor docstrings 18516 try: 18517 init_ds = getdoc(obj.__init__) 18518 # Skip Python's auto-generated docstrings 18519 if init_ds == _object_init_docstring: 18520 init_ds = None 18521 except AttributeError: 18522 init_ds = None 18523 if init_ds: 18524 out["init_docstring"] = init_ds 18525 18526 # Call form docstring for callable instances 18527 if safe_hasattr(obj, "__call__") and not is_simple_callable(obj): 18528 call_def = self._getdef(obj.__call__, oname) 18529 if call_def: 18530 call_def = call_def 18531 # it may never be the case that call def and definition 18532 # differ, but don't include the same signature twice 18533 if call_def != out.get("definition"): 18534 out["call_def"] = call_def 18535 call_ds = getdoc(obj.__call__) 18536 # Skip Python's auto-generated docstrings 18537 if call_ds == _func_call_docstring: 18538 call_ds = None 18539 if call_ds: 18540 out["call_docstring"] = call_ds 18541 18542 # Compute the object's argspec as a callable. The key is to decide 18543 # whether to pull it from the object itself, from its __init__ or 18544 # from its __call__ method. 18545 18546 if inspect.isclass(obj): 18547 # Old-style classes need not have an __init__ 18548 callable_obj = getattr(obj, "__init__", None) 18549 elif callable(obj): 18550 callable_obj = obj 18551 else: 18552 callable_obj = None 18553 18554 if callable_obj: 18555 try: 18556 argspec = getargspec(callable_obj) 18557 except (TypeError, AttributeError): 18558 # For extensions/builtins we can't retrieve the argspec 18559 pass 18560 else: 18561 # named tuples' _asdict() method returns an OrderedDict, but we 18562 # we want a normal 18563 out["argspec"] = argspec_dict = dict(argspec._asdict()) 18564 # We called this varkw before argspec became a named tuple. 18565 # With getfullargspec it's also called varkw. 18566 if "varkw" not in argspec_dict: 18567 argspec_dict["varkw"] = argspec_dict.pop("keywords") 18568 18569 return object_info(**out) 18570 18571# 18572# readline_shell 18573# 18574# -*- coding: utf-8 -*- 18575"""The readline based xonsh shell. 18576 18577Portions of this code related to initializing the readline library 18578are included from the IPython project. The IPython project is: 18579 18580* Copyright (c) 2008-2014, IPython Development Team 18581* Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu> 18582* Copyright (c) 2001, Janko Hauser <jhauser@zscout.de> 18583* Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu> 18584 18585""" 18586# amalgamated os 18587# amalgamated sys 18588cmd = _LazyModule.load('cmd', 'cmd') 18589select = _LazyModule.load('select', 'select') 18590# amalgamated shutil 18591# amalgamated builtins 18592# amalgamated importlib 18593# amalgamated threading 18594# amalgamated subprocess 18595# amalgamated collections 18596# amalgamated xonsh.lazyasd 18597# amalgamated xonsh.base_shell 18598# amalgamated xonsh.ansi_colors 18599# amalgamated from xonsh.prompt.base import multiline_prompt 18600# amalgamated xonsh.tools 18601# amalgamated xonsh.platform 18602# amalgamated xonsh.lazyimps 18603# amalgamated xonsh.events 18604readline = None 18605RL_COMPLETION_SUPPRESS_APPEND = RL_LIB = RL_STATE = None 18606RL_COMPLETION_QUERY_ITEMS = None 18607RL_CAN_RESIZE = False 18608RL_DONE = None 18609RL_VARIABLE_VALUE = None 18610_RL_STATE_DONE = 0x1000000 18611_RL_STATE_ISEARCH = 0x0000080 18612 18613_RL_PREV_CASE_SENSITIVE_COMPLETIONS = "to-be-set" 18614 18615 18616def setup_readline(): 18617 """Sets up the readline module and completion suppression, if available.""" 18618 global RL_COMPLETION_SUPPRESS_APPEND, RL_LIB, RL_CAN_RESIZE, RL_STATE, readline, RL_COMPLETION_QUERY_ITEMS 18619 if RL_COMPLETION_SUPPRESS_APPEND is not None: 18620 return 18621 for _rlmod_name in ("gnureadline", "readline"): 18622 try: 18623 readline = importlib.import_module(_rlmod_name) 18624 sys.modules["readline"] = readline 18625 except ImportError: 18626 pass 18627 else: 18628 break 18629 18630 if readline is None: 18631 print( 18632 """Skipping setup. Because no `readline` implementation available. 18633 Please install a backend (`readline`, `prompt-toolkit`, etc) to use 18634 `xonsh` interactively. 18635 See https://github.com/xonsh/xonsh/issues/1170""" 18636 ) 18637 return 18638 18639 import ctypes 18640 import ctypes.util 18641 18642 uses_libedit = readline.__doc__ and "libedit" in readline.__doc__ 18643 readline.set_completer_delims(" \t\n") 18644 # Cygwin seems to hang indefinitely when querying the readline lib 18645 if (not ON_CYGWIN) and (not ON_MSYS) and (not readline.__file__.endswith(".py")): 18646 RL_LIB = lib = ctypes.cdll.LoadLibrary(readline.__file__) 18647 try: 18648 RL_COMPLETION_SUPPRESS_APPEND = ctypes.c_int.in_dll( 18649 lib, "rl_completion_suppress_append" 18650 ) 18651 except ValueError: 18652 # not all versions of readline have this symbol, ie Macs sometimes 18653 RL_COMPLETION_SUPPRESS_APPEND = None 18654 try: 18655 RL_COMPLETION_QUERY_ITEMS = ctypes.c_int.in_dll( 18656 lib, "rl_completion_query_items" 18657 ) 18658 except ValueError: 18659 # not all versions of readline have this symbol, ie Macs sometimes 18660 RL_COMPLETION_QUERY_ITEMS = None 18661 try: 18662 RL_STATE = ctypes.c_int.in_dll(lib, "rl_readline_state") 18663 except Exception: 18664 pass 18665 RL_CAN_RESIZE = hasattr(lib, "rl_reset_screen_size") 18666 env = builtins.__xonsh_env__ 18667 # reads in history 18668 readline.set_history_length(-1) 18669 ReadlineHistoryAdder() 18670 # sets up IPython-like history matching with up and down 18671 readline.parse_and_bind('"\e[B": history-search-forward') 18672 readline.parse_and_bind('"\e[A": history-search-backward') 18673 # Setup Shift-Tab to indent 18674 readline.parse_and_bind('"\e[Z": "{0}"'.format(env.get("INDENT"))) 18675 18676 # handle tab completion differences found in libedit readline compatibility 18677 # as discussed at http://stackoverflow.com/a/7116997 18678 if uses_libedit and ON_DARWIN: 18679 readline.parse_and_bind("bind ^I rl_complete") 18680 print( 18681 "\n".join( 18682 [ 18683 "", 18684 "*" * 78, 18685 "libedit detected - readline will not be well behaved, including but not limited to:", 18686 " * crashes on tab completion", 18687 " * incorrect history navigation", 18688 " * corrupting long-lines", 18689 " * failure to wrap or indent lines properly", 18690 "", 18691 "It is highly recommended that you install gnureadline, which is installable with:", 18692 " xpip install gnureadline", 18693 "*" * 78, 18694 ] 18695 ), 18696 file=sys.stderr, 18697 ) 18698 else: 18699 readline.parse_and_bind("tab: complete") 18700 # try to load custom user settings 18701 inputrc_name = os_environ.get("INPUTRC") 18702 if inputrc_name is None: 18703 if uses_libedit: 18704 inputrc_name = ".editrc" 18705 else: 18706 inputrc_name = ".inputrc" 18707 inputrc_name = os.path.join(os.path.expanduser("~"), inputrc_name) 18708 if (not ON_WINDOWS) and (not os.path.isfile(inputrc_name)): 18709 inputrc_name = "/etc/inputrc" 18710 if ON_WINDOWS: 18711 winutils.enable_virtual_terminal_processing() 18712 if os.path.isfile(inputrc_name): 18713 try: 18714 readline.read_init_file(inputrc_name) 18715 except Exception: 18716 # this seems to fail with libedit 18717 print_exception("xonsh: could not load readline default init file.") 18718 # properly reset input typed before the first prompt 18719 readline.set_startup_hook(carriage_return) 18720 18721 18722def teardown_readline(): 18723 """Tears down up the readline module, if available.""" 18724 try: 18725 import readline 18726 except (ImportError, TypeError): 18727 return 18728 18729 18730def _rebind_case_sensitive_completions(): 18731 # handle case sensitive, see Github issue #1342 for details 18732 global _RL_PREV_CASE_SENSITIVE_COMPLETIONS 18733 env = builtins.__xonsh_env__ 18734 case_sensitive = env.get("CASE_SENSITIVE_COMPLETIONS") 18735 if case_sensitive is _RL_PREV_CASE_SENSITIVE_COMPLETIONS: 18736 return 18737 if case_sensitive: 18738 readline.parse_and_bind("set completion-ignore-case off") 18739 else: 18740 readline.parse_and_bind("set completion-ignore-case on") 18741 _RL_PREV_CASE_SENSITIVE_COMPLETIONS = case_sensitive 18742 18743 18744def fix_readline_state_after_ctrl_c(): 18745 """ 18746 Fix to allow Ctrl-C to exit reverse-i-search. 18747 18748 Based on code from: 18749 http://bugs.python.org/file39467/raw_input__workaround_demo.py 18750 """ 18751 if ON_WINDOWS: 18752 # hack to make pyreadline mimic the desired behavior 18753 try: 18754 _q = readline.rl.mode.process_keyevent_queue 18755 if len(_q) > 1: 18756 _q.pop() 18757 except Exception: 18758 pass 18759 if RL_STATE is None: 18760 return 18761 if RL_STATE.value & _RL_STATE_ISEARCH: 18762 RL_STATE.value &= ~_RL_STATE_ISEARCH 18763 if not RL_STATE.value & _RL_STATE_DONE: 18764 RL_STATE.value |= _RL_STATE_DONE 18765 18766 18767def rl_completion_suppress_append(val=1): 18768 """Sets the rl_completion_suppress_append variable, if possible. 18769 A value of 1 (default) means to suppress, a value of 0 means to enable. 18770 """ 18771 if RL_COMPLETION_SUPPRESS_APPEND is None: 18772 return 18773 RL_COMPLETION_SUPPRESS_APPEND.value = val 18774 18775 18776def rl_completion_query_items(val=None): 18777 """Sets the rl_completion_query_items variable, if possible. 18778 A None value will set this to $COMPLETION_QUERY_LIMIT, otherwise any integer 18779 is accepted. 18780 """ 18781 if RL_COMPLETION_QUERY_ITEMS is None: 18782 return 18783 if val is None: 18784 val = builtins.__xonsh_env__.get("COMPLETION_QUERY_LIMIT") 18785 RL_COMPLETION_QUERY_ITEMS.value = val 18786 18787 18788def rl_variable_dumper(readable=True): 18789 """Dumps the currently set readline variables. If readable is True, then this 18790 output may be used in an inputrc file. 18791 """ 18792 RL_LIB.rl_variable_dumper(int(readable)) 18793 18794 18795def rl_variable_value(variable): 18796 """Returns the currently set value for a readline configuration variable.""" 18797 global RL_VARIABLE_VALUE 18798 if RL_VARIABLE_VALUE is None: 18799 import ctypes 18800 18801 RL_VARIABLE_VALUE = RL_LIB.rl_variable_value 18802 RL_VARIABLE_VALUE.restype = ctypes.c_char_p 18803 env = builtins.__xonsh_env__ 18804 enc, errors = env.get("XONSH_ENCODING"), env.get("XONSH_ENCODING_ERRORS") 18805 if isinstance(variable, str): 18806 variable = variable.encode(encoding=enc, errors=errors) 18807 rtn = RL_VARIABLE_VALUE(variable) 18808 return rtn.decode(encoding=enc, errors=errors) 18809 18810 18811@lazyobject 18812def rl_on_new_line(): 18813 """Grabs one of a few possible redisplay functions in readline.""" 18814 names = ["rl_on_new_line", "rl_forced_update_display", "rl_redisplay"] 18815 for name in names: 18816 func = getattr(RL_LIB, name, None) 18817 if func is not None: 18818 break 18819 else: 18820 18821 def print_for_newline(): 18822 print() 18823 18824 func = print_for_newline 18825 return func 18826 18827 18828def _insert_text_func(s, readline): 18829 """Creates a function to insert text via readline.""" 18830 18831 def inserter(): 18832 readline.insert_text(s) 18833 readline.redisplay() 18834 18835 return inserter 18836 18837 18838DEDENT_TOKENS = LazyObject( 18839 lambda: frozenset(["raise", "return", "pass", "break", "continue"]), 18840 globals(), 18841 "DEDENT_TOKENS", 18842) 18843 18844 18845class ReadlineShell(BaseShell, cmd.Cmd): 18846 """The readline based xonsh shell.""" 18847 18848 def __init__(self, completekey="tab", stdin=None, stdout=None, **kwargs): 18849 super().__init__(completekey=completekey, stdin=stdin, stdout=stdout, **kwargs) 18850 setup_readline() 18851 self._current_indent = "" 18852 self._current_prompt = "" 18853 self._force_hide = None 18854 self._complete_only_last_table = { 18855 # Truth table for completions, keys are: 18856 # (prefix_begs_quote, prefix_ends_quote, i_ends_quote, 18857 # last_starts_with_prefix, i_has_space) 18858 (True, True, True, True, True): True, 18859 (True, True, True, True, False): True, 18860 (True, True, True, False, True): False, 18861 (True, True, True, False, False): True, 18862 (True, True, False, True, True): False, 18863 (True, True, False, True, False): False, 18864 (True, True, False, False, True): False, 18865 (True, True, False, False, False): False, 18866 (True, False, True, True, True): True, 18867 (True, False, True, True, False): False, 18868 (True, False, True, False, True): False, 18869 (True, False, True, False, False): True, 18870 (True, False, False, True, True): False, 18871 (True, False, False, True, False): False, 18872 (True, False, False, False, True): False, 18873 (True, False, False, False, False): False, 18874 (False, True, True, True, True): True, 18875 (False, True, True, True, False): True, 18876 (False, True, True, False, True): True, 18877 (False, True, True, False, False): True, 18878 (False, True, False, True, True): False, 18879 (False, True, False, True, False): False, 18880 (False, True, False, False, True): False, 18881 (False, True, False, False, False): False, 18882 (False, False, True, True, True): False, 18883 (False, False, True, True, False): False, 18884 (False, False, True, False, True): False, 18885 (False, False, True, False, False): True, 18886 (False, False, False, True, True): True, 18887 (False, False, False, True, False): False, 18888 (False, False, False, False, True): False, 18889 (False, False, False, False, False): False, 18890 } 18891 self.cmdqueue = collections.deque() 18892 18893 def __del__(self): 18894 teardown_readline() 18895 18896 def singleline(self, store_in_history=True, **kwargs): 18897 """Reads a single line of input. The store_in_history kwarg 18898 flags whether the input should be stored in readline's in-memory 18899 history. 18900 """ 18901 if not store_in_history: # store current position to remove it later 18902 try: 18903 import readline 18904 except ImportError: 18905 store_in_history = True 18906 pos = readline.get_current_history_length() - 1 18907 events.on_pre_prompt.fire() 18908 rtn = input(self.prompt) 18909 events.on_post_prompt.fire() 18910 if not store_in_history and pos >= 0: 18911 readline.remove_history_item(pos) 18912 return rtn 18913 18914 def parseline(self, line): 18915 """Overridden to no-op.""" 18916 return "", line, line 18917 18918 def _querycompletions(self, completions, loc): 18919 """Returns whether or not we should show completions. 0 means that prefixes 18920 should not be shown, 1 means that there is a common prefix among all completions 18921 and they should be shown, while 2 means that there is no common prefix but 18922 we are under the query limit and they should be shown. 18923 """ 18924 if os.path.commonprefix([c[loc:] for c in completions]): 18925 return 1 18926 elif len(completions) <= builtins.__xonsh_env__.get("COMPLETION_QUERY_LIMIT"): 18927 return 2 18928 msg = "\nDisplay all {} possibilities? ".format(len(completions)) 18929 msg += "({GREEN}y{NO_COLOR} or {RED}n{NO_COLOR})" 18930 self.print_color(msg, end="", flush=True, file=sys.stderr) 18931 yn = "x" 18932 while yn not in "yn": 18933 yn = sys.stdin.read(1) 18934 show_completions = to_bool(yn) 18935 print() 18936 if not show_completions: 18937 rl_on_new_line() 18938 return 0 18939 w, h = shutil.get_terminal_size() 18940 lines = columnize(completions, width=w) 18941 more_msg = self.format_color( 18942 "{YELLOW}==={NO_COLOR} more or " 18943 "{PURPLE}({NO_COLOR}q{PURPLE}){NO_COLOR}uit " 18944 "{YELLOW}==={NO_COLOR}" 18945 ) 18946 while len(lines) > h - 1: 18947 print("".join(lines[: h - 1]), end="", flush=True, file=sys.stderr) 18948 lines = lines[h - 1 :] 18949 print(more_msg, end="", flush=True, file=sys.stderr) 18950 q = sys.stdin.read(1).lower() 18951 print(flush=True, file=sys.stderr) 18952 if q == "q": 18953 rl_on_new_line() 18954 return 0 18955 print("".join(lines), end="", flush=True, file=sys.stderr) 18956 rl_on_new_line() 18957 return 0 18958 18959 def completedefault(self, prefix, line, begidx, endidx): 18960 """Implements tab-completion for text.""" 18961 if self.completer is None: 18962 return [] 18963 rl_completion_suppress_append() # this needs to be called each time 18964 _rebind_case_sensitive_completions() 18965 rl_completion_query_items(val=999999999) 18966 completions, l = self.completer.complete( 18967 prefix, line, begidx, endidx, ctx=self.ctx 18968 ) 18969 chopped = prefix[:-l] 18970 if chopped: 18971 rtn_completions = [chopped + i for i in completions] 18972 else: 18973 rtn_completions = completions 18974 rtn = [] 18975 prefix_begs_quote = prefix.startswith("'") or prefix.startswith('"') 18976 prefix_ends_quote = prefix.endswith("'") or prefix.endswith('"') 18977 for i in rtn_completions: 18978 i_ends_quote = i.endswith("'") or i.endswith('"') 18979 last = i.rsplit(" ", 1)[-1] 18980 last_starts_prefix = last.startswith(prefix) 18981 i_has_space = " " in i 18982 key = ( 18983 prefix_begs_quote, 18984 prefix_ends_quote, 18985 i_ends_quote, 18986 last_starts_prefix, 18987 i_has_space, 18988 ) 18989 rtn.append(last if self._complete_only_last_table[key] else i) 18990 # return based on show completions 18991 show_completions = self._querycompletions(completions, endidx - begidx) 18992 if show_completions == 0: 18993 return [] 18994 elif show_completions == 1: 18995 return rtn 18996 elif show_completions == 2: 18997 return completions 18998 else: 18999 raise ValueError("query completions flag not understood.") 19000 19001 # tab complete on first index too 19002 completenames = completedefault 19003 19004 def _load_remaining_input_into_queue(self): 19005 buf = b"" 19006 while True: 19007 r, w, x = select.select([self.stdin], [], [], 1e-6) 19008 if len(r) == 0: 19009 break 19010 buf += os.read(self.stdin.fileno(), 1024) 19011 if len(buf) > 0: 19012 buf = buf.decode().replace("\r\n", "\n").replace("\r", "\n") 19013 self.cmdqueue.extend(buf.splitlines(keepends=True)) 19014 19015 def postcmd(self, stop, line): 19016 """Called just before execution of line. For readline, this handles the 19017 automatic indentation of code blocks. 19018 """ 19019 try: 19020 import readline 19021 except ImportError: 19022 return stop 19023 if self.need_more_lines: 19024 if len(line.strip()) == 0: 19025 readline.set_pre_input_hook(None) 19026 self._current_indent = "" 19027 elif line.rstrip()[-1] == ":": 19028 ind = line[: len(line) - len(line.lstrip())] 19029 ind += builtins.__xonsh_env__.get("INDENT") 19030 readline.set_pre_input_hook(_insert_text_func(ind, readline)) 19031 self._current_indent = ind 19032 elif line.split(maxsplit=1)[0] in DEDENT_TOKENS: 19033 env = builtins.__xonsh_env__ 19034 ind = self._current_indent[: -len(env.get("INDENT"))] 19035 readline.set_pre_input_hook(_insert_text_func(ind, readline)) 19036 self._current_indent = ind 19037 else: 19038 ind = line[: len(line) - len(line.lstrip())] 19039 if ind != self._current_indent: 19040 insert_func = _insert_text_func(ind, readline) 19041 readline.set_pre_input_hook(insert_func) 19042 self._current_indent = ind 19043 else: 19044 readline.set_pre_input_hook(None) 19045 return stop 19046 19047 def _cmdloop(self, intro=None): 19048 """Repeatedly issue a prompt, accept input, parse an initial prefix 19049 off the received input, and dispatch to action methods, passing them 19050 the remainder of the line as argument. 19051 19052 This was forked from Lib/cmd.py from the Python standard library v3.4.3, 19053 (C) Python Software Foundation, 2015. 19054 """ 19055 self.preloop() 19056 if self.use_rawinput and self.completekey: 19057 try: 19058 import readline 19059 19060 self.old_completer = readline.get_completer() 19061 readline.set_completer(self.complete) 19062 readline.parse_and_bind(self.completekey + ": complete") 19063 have_readline = True 19064 except ImportError: 19065 have_readline = False 19066 try: 19067 if intro is not None: 19068 self.intro = intro 19069 if self.intro: 19070 self.stdout.write(str(self.intro) + "\n") 19071 stop = None 19072 while not stop: 19073 line = None 19074 exec_now = False 19075 if len(self.cmdqueue) > 0: 19076 line = self.cmdqueue.popleft() 19077 exec_now = line.endswith("\n") 19078 if self.use_rawinput and not exec_now: 19079 inserter = ( 19080 None if line is None else _insert_text_func(line, readline) 19081 ) 19082 if inserter is not None: 19083 readline.set_pre_input_hook(inserter) 19084 try: 19085 line = self.singleline() 19086 except EOFError: 19087 if builtins.__xonsh_env__.get("IGNOREEOF"): 19088 self.stdout.write('Use "exit" to leave the shell.' "\n") 19089 line = "" 19090 else: 19091 line = "EOF" 19092 if inserter is not None: 19093 readline.set_pre_input_hook(None) 19094 else: 19095 self.print_color(self.prompt, file=self.stdout) 19096 if line is not None: 19097 os.write(self.stdin.fileno(), line.encode()) 19098 if not exec_now: 19099 line = self.stdin.readline() 19100 if len(line) == 0: 19101 line = "EOF" 19102 else: 19103 line = line.rstrip("\r\n") 19104 if have_readline and line != "EOF": 19105 readline.add_history(line) 19106 if not ON_WINDOWS: 19107 # select() is not fully functional on windows 19108 self._load_remaining_input_into_queue() 19109 line = self.precmd(line) 19110 stop = self.onecmd(line) 19111 stop = self.postcmd(stop, line) 19112 if ON_WINDOWS: 19113 winutils.enable_virtual_terminal_processing() 19114 self.postloop() 19115 finally: 19116 if self.use_rawinput and self.completekey: 19117 try: 19118 import readline 19119 19120 readline.set_completer(self.old_completer) 19121 except ImportError: 19122 pass 19123 19124 def cmdloop(self, intro=None): 19125 while not builtins.__xonsh_exit__: 19126 try: 19127 self._cmdloop(intro=intro) 19128 except (KeyboardInterrupt, SystemExit): 19129 print() # Gives a newline 19130 fix_readline_state_after_ctrl_c() 19131 self.reset_buffer() 19132 intro = None 19133 19134 @property 19135 def prompt(self): 19136 """Obtains the current prompt string.""" 19137 global RL_LIB, RL_CAN_RESIZE 19138 if RL_CAN_RESIZE: 19139 # This is needed to support some system where line-wrapping doesn't 19140 # work. This is a bug in upstream Python, or possibly readline. 19141 RL_LIB.rl_reset_screen_size() 19142 if self.need_more_lines: 19143 if self.mlprompt is None: 19144 try: 19145 self.mlprompt = multiline_prompt(curr=self._current_prompt) 19146 except Exception: # pylint: disable=broad-except 19147 print_exception() 19148 self.mlprompt = "<multiline prompt error> " 19149 return self.mlprompt 19150 env = builtins.__xonsh_env__ # pylint: disable=no-member 19151 p = env.get("PROMPT") 19152 try: 19153 p = self.prompt_formatter(p) 19154 except Exception: # pylint: disable=broad-except 19155 print_exception() 19156 hide = True if self._force_hide is None else self._force_hide 19157 p = ansi_partial_color_format(p, style=env.get("XONSH_COLOR_STYLE"), hide=hide) 19158 self._current_prompt = p 19159 self.settitle() 19160 return p 19161 19162 def format_color(self, string, hide=False, force_string=False, **kwargs): 19163 """Readline implementation of color formatting. This uses ANSI color 19164 codes. 19165 """ 19166 hide = hide if self._force_hide is None else self._force_hide 19167 style = builtins.__xonsh_env__.get("XONSH_COLOR_STYLE") 19168 return ansi_partial_color_format(string, hide=hide, style=style) 19169 19170 def print_color(self, string, hide=False, **kwargs): 19171 if isinstance(string, str): 19172 s = self.format_color(string, hide=hide) 19173 else: 19174 # assume this is a list of (Token, str) tuples and format it 19175 env = builtins.__xonsh_env__ 19176 self.styler.style_name = env.get("XONSH_COLOR_STYLE") 19177 style_proxy = pyghooks.xonsh_style_proxy(self.styler) 19178 formatter = pyghooks.XonshTerminal256Formatter(style=style_proxy) 19179 s = pygments.format(string, formatter).rstrip() 19180 print(s, **kwargs) 19181 19182 def color_style_names(self): 19183 """Returns an iterable of all available style names.""" 19184 return ansi_color_style_names() 19185 19186 def color_style(self): 19187 """Returns the current color map.""" 19188 style = style = builtins.__xonsh_env__.get("XONSH_COLOR_STYLE") 19189 return ansi_color_style(style=style) 19190 19191 def restore_tty_sanity(self): 19192 """An interface for resetting the TTY stdin mode. This is highly 19193 dependent on the shell backend. Also it is mostly optional since 19194 it only affects ^Z backgrounding behaviour. 19195 """ 19196 if not ON_POSIX: 19197 return 19198 stty, _ = builtins.__xonsh_commands_cache__.lazyget("stty", None) 19199 if stty is None: 19200 return 19201 # If available, we should just call the stty utility. This call should 19202 # not throw even if stty fails. It should also be noted that subprocess 19203 # calls, like the following, seem to be ineffective: 19204 # subprocess.call([stty, 'sane'], shell=True) 19205 # My guess is that this is because Popen does some crazy redirecting 19206 # under the covers. This effectively hides the true TTY stdin handle 19207 # from stty. To get around this we have to use the lower level 19208 # os.system() function. 19209 os.system(stty + " sane") 19210 19211 19212class ReadlineHistoryAdder(threading.Thread): 19213 def __init__(self, wait_for_gc=True, *args, **kwargs): 19214 """Thread responsible for adding inputs from history to the 19215 current readline instance. May wait for the history garbage 19216 collector to finish. 19217 """ 19218 super(ReadlineHistoryAdder, self).__init__(*args, **kwargs) 19219 self.daemon = True 19220 self.wait_for_gc = wait_for_gc 19221 self.start() 19222 19223 def run(self): 19224 try: 19225 import readline 19226 except ImportError: 19227 return 19228 hist = builtins.__xonsh_history__ 19229 if hist is None: 19230 return 19231 i = 1 19232 for h in hist.all_items(): 19233 line = h["inp"].rstrip() 19234 if i == 1: 19235 pass 19236 elif line == readline.get_history_item(i - 1): 19237 continue 19238 readline.add_history(line) 19239 if RL_LIB is not None: 19240 RL_LIB.history_set_pos(i) 19241 i += 1 19242 19243# 19244# replay 19245# 19246# -*- coding: utf-8 -*- 19247"""Tools to replay xonsh history files.""" 19248# amalgamated json 19249# amalgamated time 19250# amalgamated builtins 19251# amalgamated collections.abc 19252# amalgamated xonsh.tools 19253# amalgamated xonsh.lazyjson 19254# amalgamated xonsh.environ 19255# amalgamated xonsh.history.main 19256DEFAULT_MERGE_ENVS = ("replay", "native") 19257 19258 19259class Replayer(object): 19260 """Replays a xonsh history file.""" 19261 19262 def __init__(self, f, reopen=True): 19263 """ 19264 Parameters 19265 ---------- 19266 f : file handle or str 19267 Path to xonsh history file. 19268 reopen : bool, optional 19269 Whether new file handle should be opened for each load, passed directly into 19270 LazyJSON class. 19271 """ 19272 self._lj = LazyJSON(f, reopen=reopen) 19273 19274 def __del__(self): 19275 self._lj.close() 19276 19277 def replay(self, merge_envs=DEFAULT_MERGE_ENVS, target=None): 19278 """Replays the history specified, returns the history object where the code 19279 was executed. 19280 19281 Parameters 19282 ---------- 19283 merge_env : tuple of str or Mappings, optional 19284 Describes how to merge the environments, in order of increasing precedence. 19285 Available strings are 'replay' and 'native'. The 'replay' env comes from the 19286 history file that we are replaying. The 'native' env comes from what this 19287 instance of xonsh was started up with. Instead of a string, a dict or other 19288 mapping may be passed in as well. Defaults to ('replay', 'native'). 19289 target : str, optional 19290 Path to new history file. 19291 """ 19292 shell = builtins.__xonsh_shell__ 19293 re_env = self._lj["env"].load() 19294 new_env = self._merge_envs(merge_envs, re_env) 19295 new_hist = xhm.construct_history( 19296 env=new_env.detype(), 19297 locked=True, 19298 ts=[time.time(), None], 19299 gc=False, 19300 filename=target, 19301 ) 19302 with swap(builtins, "__xonsh_env__", new_env), swap( 19303 builtins, "__xonsh_history__", new_hist 19304 ): 19305 for cmd in self._lj["cmds"]: 19306 inp = cmd["inp"] 19307 shell.default(inp) 19308 if builtins.__xonsh_exit__: # prevent premature exit 19309 builtins.__xonsh_exit__ = False 19310 new_hist.flush(at_exit=True) 19311 return new_hist 19312 19313 def _merge_envs(self, merge_envs, re_env): 19314 new_env = {} 19315 for e in merge_envs: 19316 if e == "replay": 19317 new_env.update(re_env) 19318 elif e == "native": 19319 new_env.update(builtins.__xonsh_env__) 19320 elif isinstance(e, cabc.Mapping): 19321 new_env.update(e) 19322 else: 19323 raise TypeError("Type of env not understood: {0!r}".format(e)) 19324 new_env = Env(**new_env) 19325 return new_env 19326 19327 19328_REPLAY_PARSER = None 19329 19330 19331def replay_create_parser(p=None): 19332 global _REPLAY_PARSER 19333 p_was_none = p is None 19334 if _REPLAY_PARSER is not None and p_was_none: 19335 return _REPLAY_PARSER 19336 if p_was_none: 19337 from argparse import ArgumentParser 19338 19339 p = ArgumentParser("replay", description="replays a xonsh history file") 19340 p.add_argument( 19341 "--merge-envs", 19342 dest="merge_envs", 19343 default=DEFAULT_MERGE_ENVS, 19344 nargs="+", 19345 help="Describes how to merge the environments, in order of " 19346 "increasing precedence. Available strings are 'replay' and " 19347 "'native'. The 'replay' env comes from the history file that we " 19348 "are replaying. The 'native' env comes from what this instance " 19349 "of xonsh was started up with. One or more of these options may " 19350 "be passed in. Defaults to '--merge-envs replay native'.", 19351 ) 19352 p.add_argument( 19353 "--json", 19354 dest="json", 19355 default=False, 19356 action="store_true", 19357 help="print history info in JSON format", 19358 ) 19359 p.add_argument( 19360 "-o", "--target", dest="target", default=None, help="path to new history file" 19361 ) 19362 p.add_argument("path", help="path to replay history file") 19363 if p_was_none: 19364 _REPLAY_PARSER = p 19365 return p 19366 19367 19368def replay_main_action(h, ns, stdout=None, stderr=None): 19369 replayer = Replayer(ns.path) 19370 hist = replayer.replay(merge_envs=ns.merge_envs, target=ns.target) 19371 print("----------------------------------------------------------------") 19372 print("Just replayed history, new history has the following information") 19373 print("----------------------------------------------------------------") 19374 data = hist.info() 19375 if ns.json: 19376 s = json.dumps(data) 19377 print(s, file=stdout) 19378 else: 19379 lines = ["{0}: {1}".format(k, v) for k, v in data.items()] 19380 print("\n".join(lines), file=stdout) 19381 19382 19383def replay_main(args, stdin=None): 19384 """Acts as main function for replaying a xonsh history file.""" 19385 parser = replay_create_parser() 19386 ns = parser.parse_args(args) 19387 replay_main_action(ns) 19388 19389# 19390# tracer 19391# 19392"""Implements a xonsh tracer.""" 19393# amalgamated os 19394# amalgamated re 19395# amalgamated sys 19396# amalgamated inspect 19397# amalgamated argparse 19398# amalgamated linecache 19399# amalgamated importlib 19400# amalgamated functools 19401# amalgamated xonsh.lazyasd 19402# amalgamated xonsh.platform 19403# amalgamated xonsh.tools 19404# amalgamated xonsh.inspectors 19405# amalgamated xonsh.lazyimps 19406# amalgamated xonsh.proc 19407prompt = _LazyModule.load('xonsh', 'xonsh.prompt.cwd', 'prompt') 19408terminal = LazyObject( 19409 lambda: importlib.import_module("pygments.formatters.terminal"), 19410 globals(), 19411 "terminal", 19412) 19413 19414 19415class TracerType(object): 19416 """Represents a xonsh tracer object, which keeps track of all tracing 19417 state. This is a singleton. 19418 """ 19419 19420 _inst = None 19421 valid_events = frozenset(["line", "call"]) 19422 19423 def __new__(cls, *args, **kwargs): 19424 if cls._inst is None: 19425 cls._inst = super(TracerType, cls).__new__(cls, *args, **kwargs) 19426 return cls._inst 19427 19428 def __init__(self): 19429 self.prev_tracer = DefaultNotGiven 19430 self.files = set() 19431 self.usecolor = True 19432 self.lexer = pyghooks.XonshLexer() 19433 self.formatter = terminal.TerminalFormatter() 19434 self._last = ("", -1) # filename, lineno tuple 19435 19436 def __del__(self): 19437 for f in set(self.files): 19438 self.stop(f) 19439 19440 def color_output(self, usecolor): 19441 """Specify whether or not the tracer output should be colored.""" 19442 # we have to use a function to set usecolor because of the way that 19443 # lazyasd works. Namely, it cannot dispatch setattr to the target 19444 # object without being unable to access its own __dict__. This makes 19445 # setting an attr look like getting a function. 19446 self.usecolor = usecolor 19447 19448 def start(self, filename): 19449 """Starts tracing a file.""" 19450 files = self.files 19451 if len(files) == 0: 19452 self.prev_tracer = sys.gettrace() 19453 files.add(normabspath(filename)) 19454 sys.settrace(self.trace) 19455 curr = inspect.currentframe() 19456 for frame, fname, *_ in getouterframes(curr, context=0): 19457 if normabspath(fname) in files: 19458 frame.f_trace = self.trace 19459 19460 def stop(self, filename): 19461 """Stops tracing a file.""" 19462 filename = normabspath(filename) 19463 self.files.discard(filename) 19464 if len(self.files) == 0: 19465 sys.settrace(self.prev_tracer) 19466 curr = inspect.currentframe() 19467 for frame, fname, *_ in getouterframes(curr, context=0): 19468 if normabspath(fname) == filename: 19469 frame.f_trace = self.prev_tracer 19470 self.prev_tracer = DefaultNotGiven 19471 19472 def trace(self, frame, event, arg): 19473 """Implements a line tracing function.""" 19474 if event not in self.valid_events: 19475 return self.trace 19476 fname = find_file(frame) 19477 if fname in self.files: 19478 lineno = frame.f_lineno 19479 curr = (fname, lineno) 19480 if curr != self._last: 19481 line = linecache.getline(fname, lineno).rstrip() 19482 s = tracer_format_line( 19483 fname, 19484 lineno, 19485 line, 19486 color=self.usecolor, 19487 lexer=self.lexer, 19488 formatter=self.formatter, 19489 ) 19490 print_color(s) 19491 self._last = curr 19492 return self.trace 19493 19494 19495tracer = LazyObject(TracerType, globals(), "tracer") 19496 19497COLORLESS_LINE = "{fname}:{lineno}:{line}" 19498COLOR_LINE = "{{PURPLE}}{fname}{{BLUE}}:" "{{GREEN}}{lineno}{{BLUE}}:" "{{NO_COLOR}}" 19499 19500 19501def tracer_format_line(fname, lineno, line, color=True, lexer=None, formatter=None): 19502 """Formats a trace line suitable for printing.""" 19503 fname = min(fname, prompt._replace_home(fname), os.path.relpath(fname), key=len) 19504 if not color: 19505 return COLORLESS_LINE.format(fname=fname, lineno=lineno, line=line) 19506 cline = COLOR_LINE.format(fname=fname, lineno=lineno) 19507 if not HAS_PYGMENTS: 19508 return cline + line 19509 # OK, so we have pygments 19510 tokens = pyghooks.partial_color_tokenize(cline) 19511 lexer = lexer or pyghooks.XonshLexer() 19512 tokens += pygments.lex(line, lexer=lexer) 19513 if tokens[-1][1] == "\n": 19514 del tokens[-1] 19515 elif tokens[-1][1].endswith("\n"): 19516 tokens[-1] = (tokens[-1][0], tokens[-1][1].rstrip()) 19517 return tokens 19518 19519 19520# 19521# Command line interface 19522# 19523 19524 19525def _find_caller(args): 19526 """Somewhat hacky method of finding the __file__ based on the line executed.""" 19527 re_line = re.compile(r"[^;\s|&<>]+\s+" + r"\s+".join(args)) 19528 curr = inspect.currentframe() 19529 for _, fname, lineno, _, lines, _ in getouterframes(curr, context=1)[3:]: 19530 if lines is not None and re_line.search(lines[0]) is not None: 19531 return fname 19532 elif ( 19533 lineno == 1 and re_line.search(linecache.getline(fname, lineno)) is not None 19534 ): 19535 # There is a bug in CPython such that getouterframes(curr, context=1) 19536 # will actually return the 2nd line in the code_context field, even though 19537 # line number is itself correct. We manually fix that in this branch. 19538 return fname 19539 else: 19540 msg = ( 19541 "xonsh: warning: __file__ name could not be found. You may be " 19542 "trying to trace interactively. Please pass in the file names " 19543 "you want to trace explicitly." 19544 ) 19545 print(msg, file=sys.stderr) 19546 19547 19548def _on(ns, args): 19549 """Turns on tracing for files.""" 19550 for f in ns.files: 19551 if f == "__file__": 19552 f = _find_caller(args) 19553 if f is None: 19554 continue 19555 tracer.start(f) 19556 19557 19558def _off(ns, args): 19559 """Turns off tracing for files.""" 19560 for f in ns.files: 19561 if f == "__file__": 19562 f = _find_caller(args) 19563 if f is None: 19564 continue 19565 tracer.stop(f) 19566 19567 19568def _color(ns, args): 19569 """Manages color action for tracer CLI.""" 19570 tracer.color_output(ns.toggle) 19571 19572 19573@functools.lru_cache(1) 19574def _tracer_create_parser(): 19575 """Creates tracer argument parser""" 19576 p = argparse.ArgumentParser( 19577 prog="trace", description="tool for tracing xonsh code as it runs." 19578 ) 19579 subp = p.add_subparsers(title="action", dest="action") 19580 onp = subp.add_parser( 19581 "on", aliases=["start", "add"], help="begins tracing selected files." 19582 ) 19583 onp.add_argument( 19584 "files", 19585 nargs="*", 19586 default=["__file__"], 19587 help=( 19588 'file paths to watch, use "__file__" (default) to select ' 19589 "the current file." 19590 ), 19591 ) 19592 off = subp.add_parser( 19593 "off", aliases=["stop", "del", "rm"], help="removes selected files fom tracing." 19594 ) 19595 off.add_argument( 19596 "files", 19597 nargs="*", 19598 default=["__file__"], 19599 help=( 19600 'file paths to stop watching, use "__file__" (default) to ' 19601 "select the current file." 19602 ), 19603 ) 19604 col = subp.add_parser("color", help="output color management for tracer.") 19605 col.add_argument( 19606 "toggle", type=to_bool, help="true/false, y/n, etc. to toggle color usage." 19607 ) 19608 return p 19609 19610 19611_TRACER_MAIN_ACTIONS = { 19612 "on": _on, 19613 "add": _on, 19614 "start": _on, 19615 "rm": _off, 19616 "off": _off, 19617 "del": _off, 19618 "stop": _off, 19619 "color": _color, 19620} 19621 19622 19623def tracermain(args=None, stdin=None, stdout=None, stderr=None, spec=None): 19624 """Main function for tracer command-line interface.""" 19625 parser = _tracer_create_parser() 19626 ns = parser.parse_args(args) 19627 usecolor = (spec.captured not in STDOUT_CAPTURE_KINDS) and sys.stdout.isatty() 19628 tracer.color_output(usecolor) 19629 return _TRACER_MAIN_ACTIONS[ns.action](ns, args) 19630 19631# 19632# aliases 19633# 19634# -*- coding: utf-8 -*- 19635"""Aliases for the xonsh shell.""" 19636# amalgamated os 19637# amalgamated sys 19638# amalgamated shlex 19639# amalgamated inspect 19640# amalgamated argparse 19641# amalgamated builtins 19642# amalgamated collections.abc 19643# amalgamated xonsh.lazyasd 19644# amalgamated xonsh.dirstack 19645# amalgamated xonsh.environ 19646# amalgamated xonsh.foreign_shells 19647# amalgamated xonsh.jobs 19648# amalgamated xonsh.platform 19649# amalgamated xonsh.tools 19650# amalgamated xonsh.replay 19651# amalgamated xonsh.timings 19652# amalgamated xonsh.tools 19653# amalgamated xonsh.xontribs 19654xca = _LazyModule.load('xonsh', 'xonsh.completers._aliases', 'xca') 19655# amalgamated xonsh.history.main 19656xxw = _LazyModule.load('xonsh', 'xonsh.xoreutils.which', 'xxw') 19657class Aliases(cabc.MutableMapping): 19658 """Represents a location to hold and look up aliases.""" 19659 19660 def __init__(self, *args, **kwargs): 19661 self._raw = {} 19662 self.update(*args, **kwargs) 19663 19664 def get(self, key, default=None): 19665 """Returns the (possibly modified) value. If the key is not present, 19666 then `default` is returned. 19667 If the value is callable, it is returned without modification. If it 19668 is an iterable of strings it will be evaluated recursively to expand 19669 other aliases, resulting in a new list or a "partially applied" 19670 callable. 19671 """ 19672 val = self._raw.get(key) 19673 if val is None: 19674 return default 19675 elif isinstance(val, cabc.Iterable) or callable(val): 19676 return self.eval_alias(val, seen_tokens={key}) 19677 else: 19678 msg = "alias of {!r} has an inappropriate type: {!r}" 19679 raise TypeError(msg.format(key, val)) 19680 19681 def eval_alias(self, value, seen_tokens=frozenset(), acc_args=()): 19682 """ 19683 "Evaluates" the alias `value`, by recursively looking up the leftmost 19684 token and "expanding" if it's also an alias. 19685 19686 A value like ["cmd", "arg"] might transform like this: 19687 > ["cmd", "arg"] -> ["ls", "-al", "arg"] -> callable() 19688 where `cmd=ls -al` and `ls` is an alias with its value being a 19689 callable. The resulting callable will be "partially applied" with 19690 ["-al", "arg"]. 19691 """ 19692 # Beware of mutability: default values for keyword args are evaluated 19693 # only once. 19694 if callable(value): 19695 if acc_args: # Partial application 19696 19697 def _alias(args, stdin=None): 19698 args = list(acc_args) + args 19699 return value(args, stdin=stdin) 19700 19701 return _alias 19702 else: 19703 return value 19704 else: 19705 expand_path = builtins.__xonsh_expand_path__ 19706 token, *rest = map(expand_path, value) 19707 if token in seen_tokens or token not in self._raw: 19708 # ^ Making sure things like `egrep=egrep --color=auto` works, 19709 # and that `l` evals to `ls --color=auto -CF` if `l=ls -CF` 19710 # and `ls=ls --color=auto` 19711 rtn = [token] 19712 rtn.extend(rest) 19713 rtn.extend(acc_args) 19714 return rtn 19715 else: 19716 seen_tokens = seen_tokens | {token} 19717 acc_args = rest + list(acc_args) 19718 return self.eval_alias(self._raw[token], seen_tokens, acc_args) 19719 19720 def expand_alias(self, line): 19721 """Expands any aliases present in line if alias does not point to a 19722 builtin function and if alias is only a single command. 19723 """ 19724 word = line.split(" ", 1)[0] 19725 if word in builtins.aliases and isinstance(self.get(word), cabc.Sequence): 19726 word_idx = line.find(word) 19727 expansion = " ".join(self.get(word)) 19728 line = line[:word_idx] + expansion + line[word_idx + len(word) :] 19729 return line 19730 19731 # 19732 # Mutable mapping interface 19733 # 19734 19735 def __getitem__(self, key): 19736 return self._raw[key] 19737 19738 def __setitem__(self, key, val): 19739 if isinstance(val, str): 19740 self._raw[key] = shlex.split(val) 19741 else: 19742 self._raw[key] = val 19743 19744 def __delitem__(self, key): 19745 del self._raw[key] 19746 19747 def update(self, *args, **kwargs): 19748 for key, val in dict(*args, **kwargs).items(): 19749 self[key] = val 19750 19751 def __iter__(self): 19752 yield from self._raw 19753 19754 def __len__(self): 19755 return len(self._raw) 19756 19757 def __str__(self): 19758 return str(self._raw) 19759 19760 def __repr__(self): 19761 return "{0}.{1}({2})".format( 19762 self.__class__.__module__, self.__class__.__name__, self._raw 19763 ) 19764 19765 def _repr_pretty_(self, p, cycle): 19766 name = "{0}.{1}".format(self.__class__.__module__, self.__class__.__name__) 19767 with p.group(0, name + "(", ")"): 19768 if cycle: 19769 p.text("...") 19770 elif len(self): 19771 p.break_() 19772 p.pretty(dict(self)) 19773 19774 19775def xonsh_exit(args, stdin=None): 19776 """Sends signal to exit shell.""" 19777 if not clean_jobs(): 19778 # Do not exit if jobs not cleaned up 19779 return None, None 19780 builtins.__xonsh_exit__ = True 19781 print() # gimme a newline 19782 return None, None 19783 19784 19785def xonsh_reset(args, stdin=None): 19786 """ Clears __xonsh_ctx__""" 19787 builtins.__xonsh_ctx__.clear() 19788 19789 19790@lazyobject 19791def _SOURCE_FOREIGN_PARSER(): 19792 desc = "Sources a file written in a foreign shell language." 19793 parser = argparse.ArgumentParser("source-foreign", description=desc) 19794 parser.add_argument("shell", help="Name or path to the foreign shell") 19795 parser.add_argument( 19796 "files_or_code", 19797 nargs="+", 19798 help="file paths to source or code in the target " "language.", 19799 ) 19800 parser.add_argument( 19801 "-i", 19802 "--interactive", 19803 type=to_bool, 19804 default=True, 19805 help="whether the sourced shell should be interactive", 19806 dest="interactive", 19807 ) 19808 parser.add_argument( 19809 "-l", 19810 "--login", 19811 type=to_bool, 19812 default=False, 19813 help="whether the sourced shell should be login", 19814 dest="login", 19815 ) 19816 parser.add_argument( 19817 "--envcmd", default=None, dest="envcmd", help="command to print environment" 19818 ) 19819 parser.add_argument( 19820 "--aliascmd", default=None, dest="aliascmd", help="command to print aliases" 19821 ) 19822 parser.add_argument( 19823 "--extra-args", 19824 default=(), 19825 dest="extra_args", 19826 type=(lambda s: tuple(s.split())), 19827 help="extra arguments needed to run the shell", 19828 ) 19829 parser.add_argument( 19830 "-s", 19831 "--safe", 19832 type=to_bool, 19833 default=True, 19834 help="whether the source shell should be run safely, " 19835 "and not raise any errors, even if they occur.", 19836 dest="safe", 19837 ) 19838 parser.add_argument( 19839 "-p", 19840 "--prevcmd", 19841 default=None, 19842 dest="prevcmd", 19843 help="command(s) to run before any other commands, " 19844 "replaces traditional source.", 19845 ) 19846 parser.add_argument( 19847 "--postcmd", 19848 default="", 19849 dest="postcmd", 19850 help="command(s) to run after all other commands", 19851 ) 19852 parser.add_argument( 19853 "--funcscmd", 19854 default=None, 19855 dest="funcscmd", 19856 help="code to find locations of all native functions " "in the shell language.", 19857 ) 19858 parser.add_argument( 19859 "--sourcer", 19860 default=None, 19861 dest="sourcer", 19862 help="the source command in the target shell " "language, default: source.", 19863 ) 19864 parser.add_argument( 19865 "--use-tmpfile", 19866 type=to_bool, 19867 default=False, 19868 help="whether the commands for source shell should be " 19869 "written to a temporary file.", 19870 dest="use_tmpfile", 19871 ) 19872 parser.add_argument( 19873 "--seterrprevcmd", 19874 default=None, 19875 dest="seterrprevcmd", 19876 help="command(s) to set exit-on-error before any" "other commands.", 19877 ) 19878 parser.add_argument( 19879 "--seterrpostcmd", 19880 default=None, 19881 dest="seterrpostcmd", 19882 help="command(s) to set exit-on-error after all" "other commands.", 19883 ) 19884 parser.add_argument( 19885 "--overwrite-aliases", 19886 default=False, 19887 action="store_true", 19888 dest="overwrite_aliases", 19889 help="flag for whether or not sourced aliases should " 19890 "replace the current xonsh aliases.", 19891 ) 19892 parser.add_argument( 19893 "--suppress-skip-message", 19894 default=None, 19895 action="store_true", 19896 dest="suppress_skip_message", 19897 help="flag for whether or not skip messages should be suppressed.", 19898 ) 19899 parser.add_argument( 19900 "--show", 19901 default=False, 19902 action="store_true", 19903 dest="show", 19904 help="Will show the script output.", 19905 ) 19906 parser.add_argument( 19907 "-d", 19908 "--dry-run", 19909 default=False, 19910 action="store_true", 19911 dest="dryrun", 19912 help="Will not actually source the file.", 19913 ) 19914 return parser 19915 19916 19917def source_foreign(args, stdin=None, stdout=None, stderr=None): 19918 """Sources a file written in a foreign shell language.""" 19919 env = builtins.__xonsh_env__ 19920 ns = _SOURCE_FOREIGN_PARSER.parse_args(args) 19921 ns.suppress_skip_message = ( 19922 env.get("FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE") 19923 if ns.suppress_skip_message is None 19924 else ns.suppress_skip_message 19925 ) 19926 if ns.prevcmd is not None: 19927 pass # don't change prevcmd if given explicitly 19928 elif os.path.isfile(ns.files_or_code[0]): 19929 # we have filename to source 19930 ns.prevcmd = '{} "{}"'.format(ns.sourcer, '" "'.join(ns.files_or_code)) 19931 elif ns.prevcmd is None: 19932 ns.prevcmd = " ".join(ns.files_or_code) # code to run, no files 19933 foreign_shell_data.cache_clear() # make sure that we don't get prev src 19934 fsenv, fsaliases = foreign_shell_data( 19935 shell=ns.shell, 19936 login=ns.login, 19937 interactive=ns.interactive, 19938 envcmd=ns.envcmd, 19939 aliascmd=ns.aliascmd, 19940 extra_args=ns.extra_args, 19941 safe=ns.safe, 19942 prevcmd=ns.prevcmd, 19943 postcmd=ns.postcmd, 19944 funcscmd=ns.funcscmd, 19945 sourcer=ns.sourcer, 19946 use_tmpfile=ns.use_tmpfile, 19947 seterrprevcmd=ns.seterrprevcmd, 19948 seterrpostcmd=ns.seterrpostcmd, 19949 show=ns.show, 19950 dryrun=ns.dryrun, 19951 ) 19952 if fsenv is None: 19953 if ns.dryrun: 19954 return 19955 else: 19956 msg = "xonsh: error: Source failed: {0!r}\n".format(ns.prevcmd) 19957 msg += "xonsh: error: Possible reasons: File not found or syntax error\n" 19958 return (None, msg, 1) 19959 # apply results 19960 denv = env.detype() 19961 for k, v in fsenv.items(): 19962 if k in denv and v == denv[k]: 19963 continue # no change from original 19964 env[k] = v 19965 # Remove any env-vars that were unset by the script. 19966 for k in denv: 19967 if k not in fsenv: 19968 env.pop(k, None) 19969 # Update aliases 19970 baliases = builtins.aliases 19971 for k, v in fsaliases.items(): 19972 if k in baliases and v == baliases[k]: 19973 continue # no change from original 19974 elif ns.overwrite_aliases or k not in baliases: 19975 baliases[k] = v 19976 elif ns.suppress_skip_message: 19977 pass 19978 else: 19979 msg = ( 19980 "Skipping application of {0!r} alias from {1!r} " 19981 "since it shares a name with an existing xonsh alias. " 19982 'Use "--overwrite-alias" option to apply it anyway.' 19983 'You may prevent this message with "--suppress-skip-message" or ' 19984 '"$FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE = True".' 19985 ) 19986 print(msg.format(k, ns.shell), file=stderr) 19987 19988 19989def source_alias(args, stdin=None): 19990 """Executes the contents of the provided files in the current context. 19991 If sourced file isn't found in cwd, search for file along $PATH to source 19992 instead. 19993 """ 19994 env = builtins.__xonsh_env__ 19995 encoding = env.get("XONSH_ENCODING") 19996 errors = env.get("XONSH_ENCODING_ERRORS") 19997 for i, fname in enumerate(args): 19998 fpath = fname 19999 if not os.path.isfile(fpath): 20000 fpath = locate_binary(fname) 20001 if fpath is None: 20002 if env.get("XONSH_DEBUG"): 20003 print("source: {}: No such file".format(fname), file=sys.stderr) 20004 if i == 0: 20005 raise RuntimeError( 20006 "must source at least one file, " + fname + "does not exist." 20007 ) 20008 break 20009 _, fext = os.path.splitext(fpath) 20010 if fext and fext != ".xsh" and fext != ".py": 20011 raise RuntimeError( 20012 "attempting to source non-xonsh file! If you are " 20013 "trying to source a file in another language, " 20014 "then please use the appropriate source command. " 20015 "For example, source-bash script.sh" 20016 ) 20017 with open(fpath, "r", encoding=encoding, errors=errors) as fp: 20018 src = fp.read() 20019 if not src.endswith("\n"): 20020 src += "\n" 20021 ctx = builtins.__xonsh_ctx__ 20022 updates = {"__file__": fpath, "__name__": os.path.abspath(fpath)} 20023 with env.swap(**make_args_env(args[i + 1 :])), swap_values(ctx, updates): 20024 try: 20025 builtins.execx(src, "exec", ctx, filename=fpath) 20026 except Exception: 20027 print_color( 20028 "{RED}You may be attempting to source non-xonsh file! " 20029 "{NO_COLOR}If you are trying to source a file in " 20030 "another language, then please use the appropriate " 20031 "source command. For example, {GREEN}source-bash " 20032 "script.sh{NO_COLOR}", 20033 file=sys.stderr, 20034 ) 20035 raise 20036 20037 20038def source_cmd(args, stdin=None): 20039 """Simple cmd.exe-specific wrapper around source-foreign.""" 20040 args = list(args) 20041 fpath = locate_binary(args[0]) 20042 args[0] = fpath if fpath else args[0] 20043 if not os.path.isfile(args[0]): 20044 return (None, "xonsh: error: File not found: {}\n".format(args[0]), 1) 20045 prevcmd = "call " 20046 prevcmd += " ".join([argvquote(arg, force=True) for arg in args]) 20047 prevcmd = escape_windows_cmd_string(prevcmd) 20048 args.append("--prevcmd={}".format(prevcmd)) 20049 args.insert(0, "cmd") 20050 args.append("--interactive=0") 20051 args.append("--sourcer=call") 20052 args.append("--envcmd=set") 20053 args.append("--seterrpostcmd=if errorlevel 1 exit 1") 20054 args.append("--use-tmpfile=1") 20055 with builtins.__xonsh_env__.swap(PROMPT="$P$G"): 20056 return source_foreign(args, stdin=stdin) 20057 20058 20059def xexec(args, stdin=None): 20060 """exec [-h|--help] command [args...] 20061 20062 exec (also aliased as xexec) uses the os.execvpe() function to 20063 replace the xonsh process with the specified program. This provides 20064 the functionality of the bash 'exec' builtin:: 20065 20066 >>> exec bash -l -i 20067 bash $ 20068 20069 The '-h' and '--help' options print this message and exit. 20070 20071 Notes 20072 ----- 20073 This command **is not** the same as the Python builtin function 20074 exec(). That function is for running Python code. This command, 20075 which shares the same name as the sh-lang statement, is for launching 20076 a command directly in the same process. In the event of a name conflict, 20077 please use the xexec command directly or dive into subprocess mode 20078 explicitly with ![exec command]. For more details, please see 20079 http://xon.sh/faq.html#exec. 20080 """ 20081 if len(args) == 0: 20082 return (None, "xonsh: exec: no args specified\n", 1) 20083 elif args[0] == "-h" or args[0] == "--help": 20084 return inspect.getdoc(xexec) 20085 else: 20086 denv = builtins.__xonsh_env__.detype() 20087 try: 20088 os.execvpe(args[0], args, denv) 20089 except FileNotFoundError as e: 20090 return ( 20091 None, 20092 "xonsh: exec: file not found: {}: {}" "\n".format(e.args[1], args[0]), 20093 1, 20094 ) 20095 20096 20097class AWitchAWitch(argparse.Action): 20098 SUPPRESS = "==SUPPRESS==" 20099 20100 def __init__( 20101 self, option_strings, version=None, dest=SUPPRESS, default=SUPPRESS, **kwargs 20102 ): 20103 super().__init__( 20104 option_strings=option_strings, dest=dest, default=default, nargs=0, **kwargs 20105 ) 20106 20107 def __call__(self, parser, namespace, values, option_string=None): 20108 import webbrowser 20109 20110 webbrowser.open("https://github.com/xonsh/xonsh/commit/f49b400") 20111 parser.exit() 20112 20113 20114def xonfig(args, stdin=None): 20115 """Runs the xonsh configuration utility.""" 20116 from xonsh.xonfig import xonfig_main # lazy import 20117 20118 return xonfig_main(args) 20119 20120 20121@unthreadable 20122def trace(args, stdin=None, stdout=None, stderr=None, spec=None): 20123 """Runs the xonsh tracer utility.""" 20124 from xonsh.tracer import tracermain # lazy import 20125 20126 try: 20127 return tracermain(args, stdin=stdin, stdout=stdout, stderr=stderr, spec=spec) 20128 except SystemExit: 20129 pass 20130 20131 20132def showcmd(args, stdin=None): 20133 """usage: showcmd [-h|--help|cmd args] 20134 20135 Displays the command and arguments as a list of strings that xonsh would 20136 run in subprocess mode. This is useful for determining how xonsh evaluates 20137 your commands and arguments prior to running these commands. 20138 20139 optional arguments: 20140 -h, --help show this help message and exit 20141 20142 example: 20143 >>> showcmd echo $USER can't hear "the sea" 20144 ['echo', 'I', "can't", 'hear', 'the sea'] 20145 """ 20146 if len(args) == 0 or (len(args) == 1 and args[0] in {"-h", "--help"}): 20147 print(showcmd.__doc__.rstrip().replace("\n ", "\n")) 20148 else: 20149 sys.displayhook(args) 20150 20151 20152def detect_xpip_alias(): 20153 """ 20154 Determines the correct invocation to get xonsh's pip 20155 """ 20156 if not getattr(sys, "executable", None): 20157 return lambda args, stdin=None: ( 20158 "", 20159 "Sorry, unable to run pip on your system (missing sys.executable)", 20160 1, 20161 ) 20162 20163 basecmd = [sys.executable, "-m", "pip"] 20164 try: 20165 if ON_WINDOWS: 20166 # XXX: Does windows have an installation mode that requires UAC? 20167 return basecmd 20168 elif not os.access(os.path.dirname(sys.executable), os.W_OK): 20169 return ["sudo"] + basecmd 20170 else: 20171 return basecmd 20172 except Exception: 20173 # Something freaky happened, return something that'll probably work 20174 return basecmd 20175 20176 20177def make_default_aliases(): 20178 """Creates a new default aliases dictionary.""" 20179 default_aliases = { 20180 "cd": cd, 20181 "pushd": pushd, 20182 "popd": popd, 20183 "dirs": dirs, 20184 "jobs": jobs, 20185 "fg": fg, 20186 "bg": bg, 20187 "EOF": xonsh_exit, 20188 "exit": xonsh_exit, 20189 "quit": xonsh_exit, 20190 "exec": xexec, 20191 "xexec": xexec, 20192 "source": source_alias, 20193 "source-zsh": ["source-foreign", "zsh", "--sourcer=source"], 20194 "source-bash": ["source-foreign", "bash", "--sourcer=source"], 20195 "source-cmd": source_cmd, 20196 "source-foreign": source_foreign, 20197 "history": xhm.history_main, 20198 "replay": replay_main, 20199 "trace": trace, 20200 "timeit": timeit_alias, 20201 "xonfig": xonfig, 20202 "scp-resume": ["rsync", "--partial", "-h", "--progress", "--rsh=ssh"], 20203 "showcmd": showcmd, 20204 "ipynb": ["jupyter", "notebook", "--no-browser"], 20205 "which": xxw.which, 20206 "xontrib": xontribs_main, 20207 "completer": xca.completer_alias, 20208 "xpip": detect_xpip_alias(), 20209 "xonsh-reset": xonsh_reset, 20210 } 20211 if ON_WINDOWS: 20212 # Borrow builtin commands from cmd.exe. 20213 windows_cmd_aliases = { 20214 "cls", 20215 "copy", 20216 "del", 20217 "dir", 20218 "echo", 20219 "erase", 20220 "md", 20221 "mkdir", 20222 "mklink", 20223 "move", 20224 "rd", 20225 "ren", 20226 "rename", 20227 "rmdir", 20228 "time", 20229 "type", 20230 "vol", 20231 } 20232 for alias in windows_cmd_aliases: 20233 default_aliases[alias] = ["cmd", "/c", alias] 20234 default_aliases["call"] = ["source-cmd"] 20235 default_aliases["source-bat"] = ["source-cmd"] 20236 default_aliases["clear"] = "cls" 20237 if ON_ANACONDA: 20238 # Add aliases specific to the Anaconda python distribution. 20239 default_aliases["activate"] = ["source-cmd", "activate.bat"] 20240 default_aliases["deactivate"] = ["source-cmd", "deactivate.bat"] 20241 if not locate_binary("sudo"): 20242 import xonsh.winutils as winutils 20243 20244 def sudo(args): 20245 if len(args) < 1: 20246 print( 20247 "You need to provide an executable to run as " "Administrator." 20248 ) 20249 return 20250 cmd = args[0] 20251 if locate_binary(cmd): 20252 return winutils.sudo(cmd, args[1:]) 20253 elif cmd.lower() in windows_cmd_aliases: 20254 args = ["/D", "/C", "CD", _get_cwd(), "&&"] + args 20255 return winutils.sudo("cmd", args) 20256 else: 20257 msg = 'Cannot find the path for executable "{0}".' 20258 print(msg.format(cmd)) 20259 20260 default_aliases["sudo"] = sudo 20261 elif ON_DARWIN: 20262 default_aliases["ls"] = ["ls", "-G"] 20263 elif ON_FREEBSD: 20264 default_aliases["grep"] = ["grep", "--color=auto"] 20265 default_aliases["egrep"] = ["egrep", "--color=auto"] 20266 default_aliases["fgrep"] = ["fgrep", "--color=auto"] 20267 default_aliases["ls"] = ["ls", "-G"] 20268 elif ON_NETBSD: 20269 default_aliases["grep"] = ["grep", "--color=auto"] 20270 default_aliases["egrep"] = ["egrep", "--color=auto"] 20271 default_aliases["fgrep"] = ["fgrep", "--color=auto"] 20272 else: 20273 default_aliases["grep"] = ["grep", "--color=auto"] 20274 default_aliases["egrep"] = ["egrep", "--color=auto"] 20275 default_aliases["fgrep"] = ["fgrep", "--color=auto"] 20276 default_aliases["ls"] = ["ls", "--color=auto", "-v"] 20277 return default_aliases 20278 20279# 20280# built_ins 20281# 20282# -*- coding: utf-8 -*- 20283"""The xonsh built-ins. 20284 20285Note that this module is named 'built_ins' so as not to be confused with the 20286special Python builtins module. 20287""" 20288# amalgamated io 20289# amalgamated os 20290# amalgamated re 20291# amalgamated sys 20292# amalgamated types 20293# amalgamated shlex 20294# amalgamated signal 20295atexit = _LazyModule.load('atexit', 'atexit') 20296# amalgamated pathlib 20297# amalgamated inspect 20298# amalgamated builtins 20299# amalgamated itertools 20300# amalgamated subprocess 20301# amalgamated contextlib 20302# amalgamated collections.abc 20303# amalgamated xonsh.ast 20304# amalgamated xonsh.lazyasd 20305# amalgamated xonsh.inspectors 20306# amalgamated xonsh.aliases 20307# amalgamated xonsh.environ 20308# amalgamated xonsh.jobs 20309# amalgamated xonsh.platform 20310# amalgamated xonsh.proc 20311# amalgamated xonsh.tools 20312# amalgamated xonsh.lazyimps 20313# amalgamated xonsh.commands_cache 20314# amalgamated xonsh.events 20315xonsh = _LazyModule.load('xonsh', 'xonsh.completers.init') 20316BUILTINS_LOADED = False 20317INSPECTOR = LazyObject(Inspector, globals(), "INSPECTOR") 20318 20319 20320@lazyobject 20321def AT_EXIT_SIGNALS(): 20322 sigs = ( 20323 signal.SIGABRT, 20324 signal.SIGFPE, 20325 signal.SIGILL, 20326 signal.SIGSEGV, 20327 signal.SIGTERM, 20328 ) 20329 if ON_POSIX: 20330 sigs += (signal.SIGTSTP, signal.SIGQUIT, signal.SIGHUP) 20331 return sigs 20332 20333 20334def resetting_signal_handle(sig, f): 20335 """Sets a new signal handle that will automatically restore the old value 20336 once the new handle is finished. 20337 """ 20338 oldh = signal.getsignal(sig) 20339 20340 def newh(s=None, frame=None): 20341 f(s, frame) 20342 signal.signal(sig, oldh) 20343 if sig != 0: 20344 sys.exit(sig) 20345 20346 signal.signal(sig, newh) 20347 20348 20349def helper(x, name=""): 20350 """Prints help about, and then returns that variable.""" 20351 INSPECTOR.pinfo(x, oname=name, detail_level=0) 20352 return x 20353 20354 20355def superhelper(x, name=""): 20356 """Prints help about, and then returns that variable.""" 20357 INSPECTOR.pinfo(x, oname=name, detail_level=1) 20358 return x 20359 20360 20361def reglob(path, parts=None, i=None): 20362 """Regular expression-based globbing.""" 20363 if parts is None: 20364 path = os.path.normpath(path) 20365 drive, tail = os.path.splitdrive(path) 20366 parts = tail.split(os.sep) 20367 d = os.sep if os.path.isabs(path) else "." 20368 d = os.path.join(drive, d) 20369 return reglob(d, parts, i=0) 20370 base = subdir = path 20371 if i == 0: 20372 if not os.path.isabs(base): 20373 base = "" 20374 elif len(parts) > 1: 20375 i += 1 20376 regex = os.path.join(base, parts[i]) 20377 if ON_WINDOWS: 20378 # currently unable to access regex backslash sequences 20379 # on Windows due to paths using \. 20380 regex = regex.replace("\\", "\\\\") 20381 regex = re.compile(regex) 20382 files = os.listdir(subdir) 20383 files.sort() 20384 paths = [] 20385 i1 = i + 1 20386 if i1 == len(parts): 20387 for f in files: 20388 p = os.path.join(base, f) 20389 if regex.fullmatch(p) is not None: 20390 paths.append(p) 20391 else: 20392 for f in files: 20393 p = os.path.join(base, f) 20394 if regex.fullmatch(p) is None or not os.path.isdir(p): 20395 continue 20396 paths += reglob(p, parts=parts, i=i1) 20397 return paths 20398 20399 20400def path_literal(s): 20401 s = expand_path(s) 20402 return pathlib.Path(s) 20403 20404 20405def regexsearch(s): 20406 s = expand_path(s) 20407 return reglob(s) 20408 20409 20410def globsearch(s): 20411 csc = builtins.__xonsh_env__.get("CASE_SENSITIVE_COMPLETIONS") 20412 glob_sorted = builtins.__xonsh_env__.get("GLOB_SORTED") 20413 dotglob = builtins.__xonsh_env__.get("DOTGLOB") 20414 return globpath( 20415 s, 20416 ignore_case=(not csc), 20417 return_empty=True, 20418 sort_result=glob_sorted, 20419 include_dotfiles=dotglob, 20420 ) 20421 20422 20423def pathsearch(func, s, pymode=False, pathobj=False): 20424 """ 20425 Takes a string and returns a list of file paths that match (regex, glob, 20426 or arbitrary search function). If pathobj=True, the return is a list of 20427 pathlib.Path objects instead of strings. 20428 """ 20429 if not callable(func) or len(inspect.signature(func).parameters) != 1: 20430 error = "%r is not a known path search function" 20431 raise XonshError(error % func) 20432 o = func(s) 20433 if pathobj and pymode: 20434 o = list(map(pathlib.Path, o)) 20435 no_match = [] if pymode else [s] 20436 return o if len(o) != 0 else no_match 20437 20438 20439RE_SHEBANG = LazyObject(lambda: re.compile(r"#![ \t]*(.+?)$"), globals(), "RE_SHEBANG") 20440 20441 20442def _is_binary(fname, limit=80): 20443 with open(fname, "rb") as f: 20444 for i in range(limit): 20445 char = f.read(1) 20446 if char == b"\0": 20447 return True 20448 if char == b"\n": 20449 return False 20450 if char == b"": 20451 return False 20452 return False 20453 20454 20455def _un_shebang(x): 20456 if x == "/usr/bin/env": 20457 return [] 20458 elif any(x.startswith(i) for i in ["/usr/bin", "/usr/local/bin", "/bin"]): 20459 x = os.path.basename(x) 20460 elif x.endswith("python") or x.endswith("python.exe"): 20461 x = "python" 20462 if x == "xonsh": 20463 return ["python", "-m", "xonsh.main"] 20464 return [x] 20465 20466 20467def get_script_subproc_command(fname, args): 20468 """Given the name of a script outside the path, returns a list representing 20469 an appropriate subprocess command to execute the script. Raises 20470 PermissionError if the script is not executable. 20471 """ 20472 # make sure file is executable 20473 if not os.access(fname, os.X_OK): 20474 raise PermissionError 20475 if ON_POSIX and not os.access(fname, os.R_OK): 20476 # on some systems, some important programs (e.g. sudo) will have 20477 # execute permissions but not read/write permissions. This enables 20478 # things with the SUID set to be run. Needs to come before _is_binary() 20479 # is called, because that function tries to read the file. 20480 return [fname] + args 20481 elif _is_binary(fname): 20482 # if the file is a binary, we should call it directly 20483 return [fname] + args 20484 if ON_WINDOWS: 20485 # Windows can execute various filetypes directly 20486 # as given in PATHEXT 20487 _, ext = os.path.splitext(fname) 20488 if ext.upper() in builtins.__xonsh_env__.get("PATHEXT"): 20489 return [fname] + args 20490 # find interpreter 20491 with open(fname, "rb") as f: 20492 first_line = f.readline().decode().strip() 20493 m = RE_SHEBANG.match(first_line) 20494 # xonsh is the default interpreter 20495 if m is None: 20496 interp = ["xonsh"] 20497 else: 20498 interp = m.group(1).strip() 20499 if len(interp) > 0: 20500 interp = shlex.split(interp) 20501 else: 20502 interp = ["xonsh"] 20503 if ON_WINDOWS: 20504 o = [] 20505 for i in interp: 20506 o.extend(_un_shebang(i)) 20507 interp = o 20508 return interp + [fname] + args 20509 20510 20511@lazyobject 20512def _REDIR_REGEX(): 20513 name = "(o(?:ut)?|e(?:rr)?|a(?:ll)?|&?\d?)" 20514 return re.compile("{r}(>?>|<){r}$".format(r=name)) 20515 20516 20517_MODES = LazyObject(lambda: {">>": "a", ">": "w", "<": "r"}, globals(), "_MODES") 20518_WRITE_MODES = LazyObject(lambda: frozenset({"w", "a"}), globals(), "_WRITE_MODES") 20519_REDIR_ALL = LazyObject(lambda: frozenset({"&", "a", "all"}), globals(), "_REDIR_ALL") 20520_REDIR_ERR = LazyObject(lambda: frozenset({"2", "e", "err"}), globals(), "_REDIR_ERR") 20521_REDIR_OUT = LazyObject( 20522 lambda: frozenset({"", "1", "o", "out"}), globals(), "_REDIR_OUT" 20523) 20524_E2O_MAP = LazyObject( 20525 lambda: frozenset( 20526 {"{}>{}".format(e, o) for e in _REDIR_ERR for o in _REDIR_OUT if o != ""} 20527 ), 20528 globals(), 20529 "_E2O_MAP", 20530) 20531_O2E_MAP = LazyObject( 20532 lambda: frozenset( 20533 {"{}>{}".format(o, e) for e in _REDIR_ERR for o in _REDIR_OUT if o != ""} 20534 ), 20535 globals(), 20536 "_O2E_MAP", 20537) 20538 20539 20540def _is_redirect(x): 20541 return isinstance(x, str) and _REDIR_REGEX.match(x) 20542 20543 20544def safe_open(fname, mode, buffering=-1): 20545 """Safely attempts to open a file in for xonsh subprocs.""" 20546 # file descriptors 20547 try: 20548 return io.open(fname, mode, buffering=buffering) 20549 except PermissionError: 20550 raise XonshError("xonsh: {0}: permission denied".format(fname)) 20551 except FileNotFoundError: 20552 raise XonshError("xonsh: {0}: no such file or directory".format(fname)) 20553 except Exception: 20554 raise XonshError("xonsh: {0}: unable to open file".format(fname)) 20555 20556 20557def safe_close(x): 20558 """Safely attempts to close an object.""" 20559 if not isinstance(x, io.IOBase): 20560 return 20561 if x.closed: 20562 return 20563 try: 20564 x.close() 20565 except Exception: 20566 pass 20567 20568 20569def _parse_redirects(r, loc=None): 20570 """returns origin, mode, destination tuple""" 20571 orig, mode, dest = _REDIR_REGEX.match(r).groups() 20572 # redirect to fd 20573 if dest.startswith("&"): 20574 try: 20575 dest = int(dest[1:]) 20576 if loc is None: 20577 loc, dest = dest, "" # NOQA 20578 else: 20579 e = "Unrecognized redirection command: {}".format(r) 20580 raise XonshError(e) 20581 except (ValueError, XonshError): 20582 raise 20583 except Exception: 20584 pass 20585 mode = _MODES.get(mode, None) 20586 if mode == "r" and (len(orig) > 0 or len(dest) > 0): 20587 raise XonshError("Unrecognized redirection command: {}".format(r)) 20588 elif mode in _WRITE_MODES and len(dest) > 0: 20589 raise XonshError("Unrecognized redirection command: {}".format(r)) 20590 return orig, mode, dest 20591 20592 20593def _redirect_streams(r, loc=None): 20594 """Returns stdin, stdout, stderr tuple of redirections.""" 20595 stdin = stdout = stderr = None 20596 no_ampersand = r.replace("&", "") 20597 # special case of redirecting stderr to stdout 20598 if no_ampersand in _E2O_MAP: 20599 stderr = subprocess.STDOUT 20600 return stdin, stdout, stderr 20601 elif no_ampersand in _O2E_MAP: 20602 stdout = 2 # using 2 as a flag, rather than using a file object 20603 return stdin, stdout, stderr 20604 # get streams 20605 orig, mode, dest = _parse_redirects(r) 20606 if mode == "r": 20607 stdin = safe_open(loc, mode) 20608 elif mode in _WRITE_MODES: 20609 if orig in _REDIR_ALL: 20610 stdout = stderr = safe_open(loc, mode) 20611 elif orig in _REDIR_OUT: 20612 stdout = safe_open(loc, mode) 20613 elif orig in _REDIR_ERR: 20614 stderr = safe_open(loc, mode) 20615 else: 20616 raise XonshError("Unrecognized redirection command: {}".format(r)) 20617 else: 20618 raise XonshError("Unrecognized redirection command: {}".format(r)) 20619 return stdin, stdout, stderr 20620 20621 20622def default_signal_pauser(n, f): 20623 """Pauses a signal, as needed.""" 20624 signal.pause() 20625 20626 20627def no_pg_xonsh_preexec_fn(): 20628 """Default subprocess preexec function for when there is no existing 20629 pipeline group. 20630 """ 20631 os.setpgrp() 20632 signal.signal(signal.SIGTSTP, default_signal_pauser) 20633 20634 20635class SubprocSpec: 20636 """A container for specifying how a subprocess command should be 20637 executed. 20638 """ 20639 20640 kwnames = ("stdin", "stdout", "stderr", "universal_newlines") 20641 20642 def __init__( 20643 self, 20644 cmd, 20645 *, 20646 cls=subprocess.Popen, 20647 stdin=None, 20648 stdout=None, 20649 stderr=None, 20650 universal_newlines=False, 20651 captured=False 20652 ): 20653 """ 20654 Parameters 20655 ---------- 20656 cmd : list of str 20657 Command to be run. 20658 cls : Popen-like 20659 Class to run the subprocess with. 20660 stdin : file-like 20661 Popen file descriptor or flag for stdin. 20662 stdout : file-like 20663 Popen file descriptor or flag for stdout. 20664 stderr : file-like 20665 Popen file descriptor or flag for stderr. 20666 universal_newlines : bool 20667 Whether or not to use universal newlines. 20668 captured : bool or str, optional 20669 The flag for if the subprocess is captured, may be one of: 20670 False for $[], 'stdout' for $(), 'hiddenobject' for ![], or 20671 'object' for !(). 20672 20673 Attributes 20674 ---------- 20675 args : list of str 20676 Arguments as originally supplied. 20677 alias : list of str, callable, or None 20678 The alias that was resolved for this command, if any. 20679 binary_loc : str or None 20680 Path to binary to execute. 20681 is_proxy : bool 20682 Whether or not the subprocess is or should be run as a proxy. 20683 background : bool 20684 Whether or not the subprocess should be started in the background. 20685 threadable : bool 20686 Whether or not the subprocess is able to be run in a background 20687 thread, rather than the main thread. 20688 last_in_pipeline : bool 20689 Whether the subprocess is the last in the execution pipeline. 20690 captured_stdout : file-like 20691 Handle to captured stdin 20692 captured_stderr : file-like 20693 Handle to captured stderr 20694 stack : list of FrameInfo namedtuples or None 20695 The stack of the call-site of alias, if the alias requires it. 20696 None otherwise. 20697 """ 20698 self._stdin = self._stdout = self._stderr = None 20699 # args 20700 self.cmd = list(cmd) 20701 self.cls = cls 20702 self.stdin = stdin 20703 self.stdout = stdout 20704 self.stderr = stderr 20705 self.universal_newlines = universal_newlines 20706 self.captured = captured 20707 # pure attrs 20708 self.args = list(cmd) 20709 self.alias = None 20710 self.binary_loc = None 20711 self.is_proxy = False 20712 self.background = False 20713 self.threadable = True 20714 self.last_in_pipeline = False 20715 self.captured_stdout = None 20716 self.captured_stderr = None 20717 self.stack = None 20718 20719 def __str__(self): 20720 s = self.__class__.__name__ + "(" + str(self.cmd) + ", " 20721 s += self.cls.__name__ + ", " 20722 kws = [n + "=" + str(getattr(self, n)) for n in self.kwnames] 20723 s += ", ".join(kws) + ")" 20724 return s 20725 20726 def __repr__(self): 20727 s = self.__class__.__name__ + "(" + repr(self.cmd) + ", " 20728 s += self.cls.__name__ + ", " 20729 kws = [n + "=" + repr(getattr(self, n)) for n in self.kwnames] 20730 s += ", ".join(kws) + ")" 20731 return s 20732 20733 # 20734 # Properties 20735 # 20736 20737 @property 20738 def stdin(self): 20739 return self._stdin 20740 20741 @stdin.setter 20742 def stdin(self, value): 20743 if self._stdin is None: 20744 self._stdin = value 20745 elif value is None: 20746 pass 20747 else: 20748 safe_close(value) 20749 msg = "Multiple inputs for stdin for {0!r}" 20750 msg = msg.format(" ".join(self.args)) 20751 raise XonshError(msg) 20752 20753 @property 20754 def stdout(self): 20755 return self._stdout 20756 20757 @stdout.setter 20758 def stdout(self, value): 20759 if self._stdout is None: 20760 self._stdout = value 20761 elif value is None: 20762 pass 20763 else: 20764 safe_close(value) 20765 msg = "Multiple redirections for stdout for {0!r}" 20766 msg = msg.format(" ".join(self.args)) 20767 raise XonshError(msg) 20768 20769 @property 20770 def stderr(self): 20771 return self._stderr 20772 20773 @stderr.setter 20774 def stderr(self, value): 20775 if self._stderr is None: 20776 self._stderr = value 20777 elif value is None: 20778 pass 20779 else: 20780 safe_close(value) 20781 msg = "Multiple redirections for stderr for {0!r}" 20782 msg = msg.format(" ".join(self.args)) 20783 raise XonshError(msg) 20784 20785 # 20786 # Execution methods 20787 # 20788 20789 def run(self, *, pipeline_group=None): 20790 """Launches the subprocess and returns the object.""" 20791 kwargs = {n: getattr(self, n) for n in self.kwnames} 20792 self.prep_env(kwargs) 20793 self.prep_preexec_fn(kwargs, pipeline_group=pipeline_group) 20794 if callable(self.alias): 20795 if "preexec_fn" in kwargs: 20796 kwargs.pop("preexec_fn") 20797 p = self.cls(self.alias, self.cmd, **kwargs) 20798 else: 20799 self._fix_null_cmd_bytes() 20800 p = self._run_binary(kwargs) 20801 p.spec = self 20802 p.last_in_pipeline = self.last_in_pipeline 20803 p.captured_stdout = self.captured_stdout 20804 p.captured_stderr = self.captured_stderr 20805 return p 20806 20807 def _run_binary(self, kwargs): 20808 try: 20809 bufsize = 1 20810 p = self.cls(self.cmd, bufsize=bufsize, **kwargs) 20811 except PermissionError: 20812 e = "xonsh: subprocess mode: permission denied: {0}" 20813 raise XonshError(e.format(self.cmd[0])) 20814 except FileNotFoundError: 20815 cmd0 = self.cmd[0] 20816 e = "xonsh: subprocess mode: command not found: {0}".format(cmd0) 20817 env = builtins.__xonsh_env__ 20818 sug = suggest_commands(cmd0, env, builtins.aliases) 20819 if len(sug.strip()) > 0: 20820 e += "\n" + suggest_commands(cmd0, env, builtins.aliases) 20821 raise XonshError(e) 20822 return p 20823 20824 def prep_env(self, kwargs): 20825 """Prepares the environment to use in the subprocess.""" 20826 denv = builtins.__xonsh_env__.detype() 20827 if ON_WINDOWS: 20828 # Over write prompt variable as xonsh's $PROMPT does 20829 # not make much sense for other subprocs 20830 denv["PROMPT"] = "$P$G" 20831 kwargs["env"] = denv 20832 20833 def prep_preexec_fn(self, kwargs, pipeline_group=None): 20834 """Prepares the 'preexec_fn' keyword argument""" 20835 if not ON_POSIX: 20836 return 20837 if not builtins.__xonsh_env__.get("XONSH_INTERACTIVE"): 20838 return 20839 if pipeline_group is None: 20840 xonsh_preexec_fn = no_pg_xonsh_preexec_fn 20841 else: 20842 20843 def xonsh_preexec_fn(): 20844 """Preexec function bound to a pipeline group.""" 20845 os.setpgid(0, pipeline_group) 20846 signal.signal(signal.SIGTSTP, default_signal_pauser) 20847 20848 kwargs["preexec_fn"] = xonsh_preexec_fn 20849 20850 def _fix_null_cmd_bytes(self): 20851 # Popen does not accept null bytes in its input commands. 20852 # That doesn't stop some subprocesses from using them. Here we 20853 # escape them just in case. 20854 cmd = self.cmd 20855 for i in range(len(cmd)): 20856 cmd[i] = cmd[i].replace("\0", "\\0") 20857 20858 # 20859 # Building methods 20860 # 20861 20862 @classmethod 20863 def build(kls, cmd, *, cls=subprocess.Popen, **kwargs): 20864 """Creates an instance of the subprocess command, with any 20865 modifications and adjustments based on the actual cmd that 20866 was received. 20867 """ 20868 # modifications that do not alter cmds may come before creating instance 20869 spec = kls(cmd, cls=cls, **kwargs) 20870 # modifications that alter cmds must come after creating instance 20871 # perform initial redirects 20872 spec.redirect_leading() 20873 spec.redirect_trailing() 20874 # apply aliases 20875 spec.resolve_alias() 20876 spec.resolve_binary_loc() 20877 spec.resolve_auto_cd() 20878 spec.resolve_executable_commands() 20879 spec.resolve_alias_cls() 20880 spec.resolve_stack() 20881 return spec 20882 20883 def redirect_leading(self): 20884 """Manage leading redirects such as with '< input.txt COMMAND'. """ 20885 while len(self.cmd) >= 3 and self.cmd[0] == "<": 20886 self.stdin = safe_open(self.cmd[1], "r") 20887 self.cmd = self.cmd[2:] 20888 20889 def redirect_trailing(self): 20890 """Manages trailing redirects.""" 20891 while True: 20892 cmd = self.cmd 20893 if len(cmd) >= 3 and _is_redirect(cmd[-2]): 20894 streams = _redirect_streams(cmd[-2], cmd[-1]) 20895 self.stdin, self.stdout, self.stderr = streams 20896 self.cmd = cmd[:-2] 20897 elif len(cmd) >= 2 and _is_redirect(cmd[-1]): 20898 streams = _redirect_streams(cmd[-1]) 20899 self.stdin, self.stdout, self.stderr = streams 20900 self.cmd = cmd[:-1] 20901 else: 20902 break 20903 20904 def resolve_alias(self): 20905 """Sets alias in command, if applicable.""" 20906 cmd0 = self.cmd[0] 20907 if callable(cmd0): 20908 alias = cmd0 20909 else: 20910 alias = builtins.aliases.get(cmd0, None) 20911 self.alias = alias 20912 20913 def resolve_binary_loc(self): 20914 """Sets the binary location""" 20915 alias = self.alias 20916 if alias is None: 20917 binary_loc = locate_binary(self.cmd[0]) 20918 elif callable(alias): 20919 binary_loc = None 20920 else: 20921 binary_loc = locate_binary(alias[0]) 20922 self.binary_loc = binary_loc 20923 20924 def resolve_auto_cd(self): 20925 """Implements AUTO_CD functionality.""" 20926 if not ( 20927 self.alias is None 20928 and self.binary_loc is None 20929 and len(self.cmd) == 1 20930 and builtins.__xonsh_env__.get("AUTO_CD") 20931 and os.path.isdir(self.cmd[0]) 20932 ): 20933 return 20934 self.cmd.insert(0, "cd") 20935 self.alias = builtins.aliases.get("cd", None) 20936 20937 def resolve_executable_commands(self): 20938 """Resolve command executables, if applicable.""" 20939 alias = self.alias 20940 if alias is None: 20941 pass 20942 elif callable(alias): 20943 self.cmd.pop(0) 20944 return 20945 else: 20946 self.cmd = alias + self.cmd[1:] 20947 # resolve any redirects the aliases may have applied 20948 self.redirect_leading() 20949 self.redirect_trailing() 20950 if self.binary_loc is None: 20951 return 20952 try: 20953 self.cmd = get_script_subproc_command(self.binary_loc, self.cmd[1:]) 20954 except PermissionError: 20955 e = "xonsh: subprocess mode: permission denied: {0}" 20956 raise XonshError(e.format(self.cmd[0])) 20957 20958 def resolve_alias_cls(self): 20959 """Determine which proxy class to run an alias with.""" 20960 alias = self.alias 20961 if not callable(alias): 20962 return 20963 self.is_proxy = True 20964 thable = getattr(alias, "__xonsh_threadable__", True) 20965 cls = ProcProxyThread if thable else ProcProxy 20966 self.cls = cls 20967 self.threadable = thable 20968 # also check capturability, while we are here 20969 cpable = getattr(alias, "__xonsh_capturable__", self.captured) 20970 self.captured = cpable 20971 20972 def resolve_stack(self): 20973 """Computes the stack for a callable alias's call-site, if needed.""" 20974 if not callable(self.alias): 20975 return 20976 # check that we actual need the stack 20977 sig = inspect.signature(self.alias) 20978 if len(sig.parameters) <= 5 and "stack" not in sig.parameters: 20979 return 20980 # compute the stack, and filter out these build methods 20981 # run_subproc() is the 4th command in the stack 20982 # we want to filter out one up, e.g. subproc_captured_hiddenobject() 20983 # after that the stack from the call site starts. 20984 stack = inspect.stack(context=0) 20985 assert stack[3][3] == "run_subproc", "xonsh stack has changed!" 20986 del stack[:5] 20987 self.stack = stack 20988 20989 20990def _safe_pipe_properties(fd, use_tty=False): 20991 """Makes sure that a pipe file descriptor properties are sane.""" 20992 if not use_tty: 20993 return 20994 # due to some weird, long standing issue in Python, PTYs come out 20995 # replacing newline \n with \r\n. This causes issues for raw unix 20996 # protocols, like git and ssh, which expect unix line endings. 20997 # see https://mail.python.org/pipermail/python-list/2013-June/650460.html 20998 # for more details and the following solution. 20999 props = termios.tcgetattr(fd) 21000 props[1] = props[1] & (~termios.ONLCR) | termios.ONLRET 21001 termios.tcsetattr(fd, termios.TCSANOW, props) 21002 21003 21004def _update_last_spec(last): 21005 captured = last.captured 21006 last.last_in_pipeline = True 21007 if not captured: 21008 return 21009 callable_alias = callable(last.alias) 21010 if callable_alias: 21011 pass 21012 else: 21013 cmds_cache = builtins.__xonsh_commands_cache__ 21014 thable = cmds_cache.predict_threadable( 21015 last.args 21016 ) and cmds_cache.predict_threadable(last.cmd) 21017 if captured and thable: 21018 last.cls = PopenThread 21019 elif not thable: 21020 # foreground processes should use Popen 21021 last.threadable = False 21022 if captured == "object" or captured == "hiddenobject": 21023 # CommandPipeline objects should not pipe stdout, stderr 21024 return 21025 # cannot used PTY pipes for aliases, for some dark reason, 21026 # and must use normal pipes instead. 21027 use_tty = ON_POSIX and not callable_alias 21028 # Do not set standard in! Popen is not a fan of redirections here 21029 # set standard out 21030 if last.stdout is not None: 21031 last.universal_newlines = True 21032 elif captured in STDOUT_CAPTURE_KINDS: 21033 last.universal_newlines = False 21034 r, w = os.pipe() 21035 last.stdout = safe_open(w, "wb") 21036 last.captured_stdout = safe_open(r, "rb") 21037 elif builtins.__xonsh_stdout_uncaptured__ is not None: 21038 last.universal_newlines = True 21039 last.stdout = builtins.__xonsh_stdout_uncaptured__ 21040 last.captured_stdout = last.stdout 21041 elif ON_WINDOWS and not callable_alias: 21042 last.universal_newlines = True 21043 last.stdout = None # must truly stream on windows 21044 last.captured_stdout = ConsoleParallelReader(1) 21045 else: 21046 last.universal_newlines = True 21047 r, w = pty.openpty() if use_tty else os.pipe() 21048 _safe_pipe_properties(w, use_tty=use_tty) 21049 last.stdout = safe_open(w, "w") 21050 _safe_pipe_properties(r, use_tty=use_tty) 21051 last.captured_stdout = safe_open(r, "r") 21052 # set standard error 21053 if last.stderr is not None: 21054 pass 21055 elif captured == "object": 21056 r, w = os.pipe() 21057 last.stderr = safe_open(w, "w") 21058 last.captured_stderr = safe_open(r, "r") 21059 elif builtins.__xonsh_stderr_uncaptured__ is not None: 21060 last.stderr = builtins.__xonsh_stderr_uncaptured__ 21061 last.captured_stderr = last.stderr 21062 elif ON_WINDOWS and not callable_alias: 21063 last.universal_newlines = True 21064 last.stderr = None # must truly stream on windows 21065 else: 21066 r, w = pty.openpty() if use_tty else os.pipe() 21067 _safe_pipe_properties(w, use_tty=use_tty) 21068 last.stderr = safe_open(w, "w") 21069 _safe_pipe_properties(r, use_tty=use_tty) 21070 last.captured_stderr = safe_open(r, "r") 21071 # redirect stdout to stderr, if we should 21072 if isinstance(last.stdout, int) and last.stdout == 2: 21073 # need to use private interface to avoid duplication. 21074 last._stdout = last.stderr 21075 # redirect stderr to stdout, if we should 21076 if callable_alias and last.stderr == subprocess.STDOUT: 21077 last._stderr = last.stdout 21078 last.captured_stderr = last.captured_stdout 21079 21080 21081def cmds_to_specs(cmds, captured=False): 21082 """Converts a list of cmds to a list of SubprocSpec objects that are 21083 ready to be executed. 21084 """ 21085 # first build the subprocs independently and separate from the redirects 21086 specs = [] 21087 redirects = [] 21088 for cmd in cmds: 21089 if isinstance(cmd, str): 21090 redirects.append(cmd) 21091 else: 21092 if cmd[-1] == "&": 21093 cmd = cmd[:-1] 21094 redirects.append("&") 21095 spec = SubprocSpec.build(cmd, captured=captured) 21096 specs.append(spec) 21097 # now modify the subprocs based on the redirects. 21098 for i, redirect in enumerate(redirects): 21099 if redirect == "|": 21100 # these should remain integer file descriptors, and not Python 21101 # file objects since they connect processes. 21102 r, w = os.pipe() 21103 specs[i].stdout = w 21104 specs[i + 1].stdin = r 21105 elif redirect == "&" and i == len(redirects) - 1: 21106 specs[-1].background = True 21107 else: 21108 raise XonshError("unrecognized redirect {0!r}".format(redirect)) 21109 # Apply boundary conditions 21110 _update_last_spec(specs[-1]) 21111 return specs 21112 21113 21114def _should_set_title(captured=False): 21115 env = builtins.__xonsh_env__ 21116 return ( 21117 env.get("XONSH_INTERACTIVE") 21118 and not env.get("XONSH_STORE_STDOUT") 21119 and captured not in STDOUT_CAPTURE_KINDS 21120 and hasattr(builtins, "__xonsh_shell__") 21121 ) 21122 21123 21124def run_subproc(cmds, captured=False): 21125 """Runs a subprocess, in its many forms. This takes a list of 'commands,' 21126 which may be a list of command line arguments or a string, representing 21127 a special connecting character. For example:: 21128 21129 $ ls | grep wakka 21130 21131 is represented by the following cmds:: 21132 21133 [['ls'], '|', ['grep', 'wakka']] 21134 21135 Lastly, the captured argument affects only the last real command. 21136 """ 21137 specs = cmds_to_specs(cmds, captured=captured) 21138 captured = specs[-1].captured 21139 if captured == "hiddenobject": 21140 command = HiddenCommandPipeline(specs) 21141 else: 21142 command = CommandPipeline(specs) 21143 proc = command.proc 21144 background = command.spec.background 21145 if not all(x.is_proxy for x in specs): 21146 add_job( 21147 { 21148 "cmds": cmds, 21149 "pids": [i.pid for i in command.procs], 21150 "obj": proc, 21151 "bg": background, 21152 "pipeline": command, 21153 "pgrp": command.term_pgid, 21154 } 21155 ) 21156 if _should_set_title(captured=captured): 21157 # set title here to get currently executing command 21158 pause_call_resume(proc, builtins.__xonsh_shell__.settitle) 21159 # create command or return if backgrounding. 21160 if background: 21161 return 21162 # now figure out what we should return. 21163 if captured == "stdout": 21164 command.end() 21165 return command.output 21166 elif captured == "object": 21167 return command 21168 elif captured == "hiddenobject": 21169 command.end() 21170 return command 21171 else: 21172 command.end() 21173 return 21174 21175 21176def subproc_captured_stdout(*cmds): 21177 """Runs a subprocess, capturing the output. Returns the stdout 21178 that was produced as a str. 21179 """ 21180 return run_subproc(cmds, captured="stdout") 21181 21182 21183def subproc_captured_inject(*cmds): 21184 """Runs a subprocess, capturing the output. Returns a list of 21185 whitespace-separated strings of the stdout that was produced. 21186 The string is split using xonsh's lexer, rather than Python's str.split() 21187 or shlex.split(). 21188 """ 21189 s = run_subproc(cmds, captured="stdout") 21190 toks = builtins.__xonsh_execer__.parser.lexer.split(s.strip()) 21191 return toks 21192 21193 21194def subproc_captured_object(*cmds): 21195 """ 21196 Runs a subprocess, capturing the output. Returns an instance of 21197 CommandPipeline representing the completed command. 21198 """ 21199 return run_subproc(cmds, captured="object") 21200 21201 21202def subproc_captured_hiddenobject(*cmds): 21203 """Runs a subprocess, capturing the output. Returns an instance of 21204 HiddenCommandPipeline representing the completed command. 21205 """ 21206 return run_subproc(cmds, captured="hiddenobject") 21207 21208 21209def subproc_uncaptured(*cmds): 21210 """Runs a subprocess, without capturing the output. Returns the stdout 21211 that was produced as a str. 21212 """ 21213 return run_subproc(cmds, captured=False) 21214 21215 21216def ensure_list_of_strs(x): 21217 """Ensures that x is a list of strings.""" 21218 if isinstance(x, str): 21219 rtn = [x] 21220 elif isinstance(x, cabc.Sequence): 21221 rtn = [i if isinstance(i, str) else str(i) for i in x] 21222 else: 21223 rtn = [str(x)] 21224 return rtn 21225 21226 21227def list_of_strs_or_callables(x): 21228 """Ensures that x is a list of strings or functions""" 21229 if isinstance(x, str) or callable(x): 21230 rtn = [x] 21231 elif isinstance(x, cabc.Iterable): 21232 rtn = [i if isinstance(i, str) or callable(i) else str(i) for i in x] 21233 else: 21234 rtn = [str(x)] 21235 return rtn 21236 21237 21238def list_of_list_of_strs_outer_product(x): 21239 """Takes an outer product of a list of strings""" 21240 lolos = map(ensure_list_of_strs, x) 21241 rtn = [] 21242 for los in itertools.product(*lolos): 21243 s = "".join(los) 21244 if "*" in s: 21245 rtn.extend(builtins.__xonsh_glob__(s)) 21246 else: 21247 rtn.append(builtins.__xonsh_expand_path__(s)) 21248 return rtn 21249 21250 21251@lazyobject 21252def MACRO_FLAG_KINDS(): 21253 return { 21254 "s": str, 21255 "str": str, 21256 "string": str, 21257 "a": AST, 21258 "ast": AST, 21259 "c": types.CodeType, 21260 "code": types.CodeType, 21261 "compile": types.CodeType, 21262 "v": eval, 21263 "eval": eval, 21264 "x": exec, 21265 "exec": exec, 21266 "t": type, 21267 "type": type, 21268 } 21269 21270 21271def _convert_kind_flag(x): 21272 """Puts a kind flag (string) a canonical form.""" 21273 x = x.lower() 21274 kind = MACRO_FLAG_KINDS.get(x, None) 21275 if kind is None: 21276 raise TypeError("{0!r} not a recognized macro type.".format(x)) 21277 return kind 21278 21279 21280def convert_macro_arg(raw_arg, kind, glbs, locs, *, name="<arg>", macroname="<macro>"): 21281 """Converts a string macro argument based on the requested kind. 21282 21283 Parameters 21284 ---------- 21285 raw_arg : str 21286 The str representation of the macro argument. 21287 kind : object 21288 A flag or type representing how to convert the argument. 21289 glbs : Mapping 21290 The globals from the call site. 21291 locs : Mapping or None 21292 The locals from the call site. 21293 name : str, optional 21294 The macro argument name. 21295 macroname : str, optional 21296 The name of the macro itself. 21297 21298 Returns 21299 ------- 21300 The converted argument. 21301 """ 21302 # munge kind and mode to start 21303 mode = None 21304 if isinstance(kind, cabc.Sequence) and not isinstance(kind, str): 21305 # have (kind, mode) tuple 21306 kind, mode = kind 21307 if isinstance(kind, str): 21308 kind = _convert_kind_flag(kind) 21309 if kind is str or kind is None: 21310 return raw_arg # short circuit since there is nothing else to do 21311 # select from kind and convert 21312 execer = builtins.__xonsh_execer__ 21313 filename = macroname + "(" + name + ")" 21314 if kind is AST: 21315 ctx = set(dir(builtins)) | set(glbs.keys()) 21316 if locs is not None: 21317 ctx |= set(locs.keys()) 21318 mode = mode or "eval" 21319 arg = execer.parse(raw_arg, ctx, mode=mode, filename=filename) 21320 elif kind is types.CodeType or kind is compile: # NOQA 21321 mode = mode or "eval" 21322 arg = execer.compile( 21323 raw_arg, mode=mode, glbs=glbs, locs=locs, filename=filename 21324 ) 21325 elif kind is eval: 21326 arg = execer.eval(raw_arg, glbs=glbs, locs=locs, filename=filename) 21327 elif kind is exec: 21328 mode = mode or "exec" 21329 if not raw_arg.endswith("\n"): 21330 raw_arg += "\n" 21331 arg = execer.exec(raw_arg, mode=mode, glbs=glbs, locs=locs, filename=filename) 21332 elif kind is type: 21333 arg = type(execer.eval(raw_arg, glbs=glbs, locs=locs, filename=filename)) 21334 else: 21335 msg = "kind={0!r} and mode={1!r} was not recognized for macro " "argument {2!r}" 21336 raise TypeError(msg.format(kind, mode, name)) 21337 return arg 21338 21339 21340@contextlib.contextmanager 21341def in_macro_call(f, glbs, locs): 21342 """Attaches macro globals and locals temporarily to function as a 21343 context manager. 21344 21345 Parameters 21346 ---------- 21347 f : callable object 21348 The function that is called as ``f(*args)``. 21349 glbs : Mapping 21350 The globals from the call site. 21351 locs : Mapping or None 21352 The locals from the call site. 21353 """ 21354 prev_glbs = getattr(f, "macro_globals", None) 21355 prev_locs = getattr(f, "macro_locals", None) 21356 f.macro_globals = glbs 21357 f.macro_locals = locs 21358 yield 21359 if prev_glbs is None: 21360 del f.macro_globals 21361 else: 21362 f.macro_globals = prev_glbs 21363 if prev_locs is None: 21364 del f.macro_locals 21365 else: 21366 f.macro_locals = prev_locs 21367 21368 21369def call_macro(f, raw_args, glbs, locs): 21370 """Calls a function as a macro, returning its result. 21371 21372 Parameters 21373 ---------- 21374 f : callable object 21375 The function that is called as ``f(*args)``. 21376 raw_args : tuple of str 21377 The str representation of arguments of that were passed into the 21378 macro. These strings will be parsed, compiled, evaled, or left as 21379 a string depending on the annotations of f. 21380 glbs : Mapping 21381 The globals from the call site. 21382 locs : Mapping or None 21383 The locals from the call site. 21384 """ 21385 sig = inspect.signature(f) 21386 empty = inspect.Parameter.empty 21387 macroname = f.__name__ 21388 i = 0 21389 args = [] 21390 for (key, param), raw_arg in zip(sig.parameters.items(), raw_args): 21391 i += 1 21392 if raw_arg == "*": 21393 break 21394 kind = param.annotation 21395 if kind is empty or kind is None: 21396 kind = str 21397 arg = convert_macro_arg( 21398 raw_arg, kind, glbs, locs, name=key, macroname=macroname 21399 ) 21400 args.append(arg) 21401 reg_args, kwargs = _eval_regular_args(raw_args[i:], glbs, locs) 21402 args += reg_args 21403 with in_macro_call(f, glbs, locs): 21404 rtn = f(*args, **kwargs) 21405 return rtn 21406 21407 21408@lazyobject 21409def KWARG_RE(): 21410 return re.compile("([A-Za-z_]\w*=|\*\*)") 21411 21412 21413def _starts_as_arg(s): 21414 """Tests if a string starts as a non-kwarg string would.""" 21415 return KWARG_RE.match(s) is None 21416 21417 21418def _eval_regular_args(raw_args, glbs, locs): 21419 if not raw_args: 21420 return [], {} 21421 arglist = list(itertools.takewhile(_starts_as_arg, raw_args)) 21422 kwarglist = raw_args[len(arglist) :] 21423 execer = builtins.__xonsh_execer__ 21424 if not arglist: 21425 args = arglist 21426 kwargstr = "dict({})".format(", ".join(kwarglist)) 21427 kwargs = execer.eval(kwargstr, glbs=glbs, locs=locs) 21428 elif not kwarglist: 21429 argstr = "({},)".format(", ".join(arglist)) 21430 args = execer.eval(argstr, glbs=glbs, locs=locs) 21431 kwargs = {} 21432 else: 21433 argstr = "({},)".format(", ".join(arglist)) 21434 kwargstr = "dict({})".format(", ".join(kwarglist)) 21435 both = "({}, {})".format(argstr, kwargstr) 21436 args, kwargs = execer.eval(both, glbs=glbs, locs=locs) 21437 return args, kwargs 21438 21439 21440def enter_macro(obj, raw_block, glbs, locs): 21441 """Prepares to enter a context manager macro by attaching the contents 21442 of the macro block, globals, and locals to the object. These modifications 21443 are made in-place and the original object is returned. 21444 21445 21446 Parameters 21447 ---------- 21448 obj : context manager 21449 The object that is about to be entered via a with-statement. 21450 raw_block : str 21451 The str of the block that is the context body. 21452 This string will be parsed, compiled, evaled, or left as 21453 a string depending on the return annotation of obj.__enter__. 21454 glbs : Mapping 21455 The globals from the context site. 21456 locs : Mapping or None 21457 The locals from the context site. 21458 21459 Returns 21460 ------- 21461 obj : context manager 21462 The same context manager but with the new macro information applied. 21463 """ 21464 # recurse down sequences 21465 if isinstance(obj, cabc.Sequence): 21466 for x in obj: 21467 enter_macro(x, raw_block, glbs, locs) 21468 return obj 21469 # convert block as needed 21470 kind = getattr(obj, "__xonsh_block__", str) 21471 macroname = getattr(obj, "__name__", "<context>") 21472 block = convert_macro_arg( 21473 raw_block, kind, glbs, locs, name="<with!>", macroname=macroname 21474 ) 21475 # attach attrs 21476 obj.macro_globals = glbs 21477 obj.macro_locals = locs 21478 obj.macro_block = block 21479 return obj 21480 21481 21482def load_builtins(execer=None, ctx=None): 21483 """Loads the xonsh builtins into the Python builtins. Sets the 21484 BUILTINS_LOADED variable to True. 21485 """ 21486 global BUILTINS_LOADED 21487 # private built-ins 21488 builtins.__xonsh_config__ = {} 21489 builtins.__xonsh_env__ = Env(default_env()) 21490 builtins.__xonsh_help__ = helper 21491 builtins.__xonsh_superhelp__ = superhelper 21492 builtins.__xonsh_pathsearch__ = pathsearch 21493 builtins.__xonsh_globsearch__ = globsearch 21494 builtins.__xonsh_regexsearch__ = regexsearch 21495 builtins.__xonsh_glob__ = globpath 21496 builtins.__xonsh_expand_path__ = expand_path 21497 builtins.__xonsh_exit__ = False 21498 builtins.__xonsh_stdout_uncaptured__ = None 21499 builtins.__xonsh_stderr_uncaptured__ = None 21500 if hasattr(builtins, "exit"): 21501 builtins.__xonsh_pyexit__ = builtins.exit 21502 del builtins.exit 21503 if hasattr(builtins, "quit"): 21504 builtins.__xonsh_pyquit__ = builtins.quit 21505 del builtins.quit 21506 builtins.__xonsh_subproc_captured_stdout__ = subproc_captured_stdout 21507 builtins.__xonsh_subproc_captured_inject__ = subproc_captured_inject 21508 builtins.__xonsh_subproc_captured_object__ = subproc_captured_object 21509 builtins.__xonsh_subproc_captured_hiddenobject__ = subproc_captured_hiddenobject 21510 builtins.__xonsh_subproc_uncaptured__ = subproc_uncaptured 21511 builtins.__xonsh_execer__ = execer 21512 builtins.__xonsh_commands_cache__ = CommandsCache() 21513 builtins.__xonsh_all_jobs__ = {} 21514 builtins.__xonsh_ensure_list_of_strs__ = ensure_list_of_strs 21515 builtins.__xonsh_list_of_strs_or_callables__ = list_of_strs_or_callables 21516 builtins.__xonsh_list_of_list_of_strs_outer_product__ = ( 21517 list_of_list_of_strs_outer_product 21518 ) 21519 builtins.__xonsh_completers__ = xonsh.completers.init.default_completers() 21520 builtins.__xonsh_call_macro__ = call_macro 21521 builtins.__xonsh_enter_macro__ = enter_macro 21522 builtins.__xonsh_path_literal__ = path_literal 21523 # public built-ins 21524 builtins.XonshError = XonshError 21525 builtins.XonshCalledProcessError = XonshCalledProcessError 21526 builtins.evalx = None if execer is None else execer.eval 21527 builtins.execx = None if execer is None else execer.exec 21528 builtins.compilex = None if execer is None else execer.compile 21529 builtins.events = events 21530 21531 # sneak the path search functions into the aliases 21532 # Need this inline/lazy import here since we use locate_binary that 21533 # relies on __xonsh_env__ in default aliases 21534 builtins.default_aliases = builtins.aliases = Aliases(make_default_aliases()) 21535 builtins.__xonsh_history__ = None 21536 atexit.register(_lastflush) 21537 for sig in AT_EXIT_SIGNALS: 21538 resetting_signal_handle(sig, _lastflush) 21539 BUILTINS_LOADED = True 21540 21541 21542def _lastflush(s=None, f=None): 21543 if hasattr(builtins, "__xonsh_history__"): 21544 if builtins.__xonsh_history__ is not None: 21545 builtins.__xonsh_history__.flush(at_exit=True) 21546 21547 21548def unload_builtins(): 21549 """Removes the xonsh builtins from the Python builtins, if the 21550 BUILTINS_LOADED is True, sets BUILTINS_LOADED to False, and returns. 21551 """ 21552 global BUILTINS_LOADED 21553 env = getattr(builtins, "__xonsh_env__", None) 21554 if isinstance(env, Env): 21555 env.undo_replace_env() 21556 if hasattr(builtins, "__xonsh_pyexit__"): 21557 builtins.exit = builtins.__xonsh_pyexit__ 21558 if hasattr(builtins, "__xonsh_pyquit__"): 21559 builtins.quit = builtins.__xonsh_pyquit__ 21560 if not BUILTINS_LOADED: 21561 return 21562 names = [ 21563 "__xonsh_config__", 21564 "__xonsh_env__", 21565 "__xonsh_ctx__", 21566 "__xonsh_help__", 21567 "__xonsh_superhelp__", 21568 "__xonsh_pathsearch__", 21569 "__xonsh_globsearch__", 21570 "__xonsh_regexsearch__", 21571 "__xonsh_glob__", 21572 "__xonsh_expand_path__", 21573 "__xonsh_exit__", 21574 "__xonsh_stdout_uncaptured__", 21575 "__xonsh_stderr_uncaptured__", 21576 "__xonsh_pyexit__", 21577 "__xonsh_pyquit__", 21578 "__xonsh_subproc_captured_stdout__", 21579 "__xonsh_subproc_captured_inject__", 21580 "__xonsh_subproc_captured_object__", 21581 "__xonsh_subproc_captured_hiddenobject__", 21582 "__xonsh_subproc_uncaptured__", 21583 "__xonsh_execer__", 21584 "__xonsh_commands_cache__", 21585 "__xonsh_completers__", 21586 "__xonsh_call_macro__", 21587 "__xonsh_enter_macro__", 21588 "__xonsh_path_literal__", 21589 "XonshError", 21590 "XonshCalledProcessError", 21591 "evalx", 21592 "execx", 21593 "compilex", 21594 "default_aliases", 21595 "__xonsh_all_jobs__", 21596 "__xonsh_ensure_list_of_strs__", 21597 "__xonsh_list_of_strs_or_callables__", 21598 "__xonsh_list_of_list_of_strs_outer_product__", 21599 "__xonsh_history__", 21600 ] 21601 for name in names: 21602 if hasattr(builtins, name): 21603 delattr(builtins, name) 21604 BUILTINS_LOADED = False 21605 21606 21607@contextlib.contextmanager 21608def xonsh_builtins(execer=None): 21609 """A context manager for using the xonsh builtins only in a limited 21610 scope. Likely useful in testing. 21611 """ 21612 load_builtins(execer=execer) 21613 yield 21614 unload_builtins() 21615 21616# 21617# execer 21618# 21619# -*- coding: utf-8 -*- 21620"""Implements the xonsh executer.""" 21621# amalgamated sys 21622# amalgamated types 21623# amalgamated inspect 21624# amalgamated builtins 21625# amalgamated collections.abc 21626# amalgamated xonsh.ast 21627# amalgamated xonsh.parser 21628# amalgamated xonsh.tools 21629# amalgamated xonsh.built_ins 21630class Execer(object): 21631 """Executes xonsh code in a context.""" 21632 21633 def __init__( 21634 self, 21635 filename="<xonsh-code>", 21636 debug_level=0, 21637 parser_args=None, 21638 unload=True, 21639 xonsh_ctx=None, 21640 scriptcache=True, 21641 cacheall=False, 21642 ): 21643 """Parameters 21644 ---------- 21645 filename : str, optional 21646 File we are to execute. 21647 debug_level : int, optional 21648 Debugging level to use in lexing and parsing. 21649 parser_args : dict, optional 21650 Arguments to pass down to the parser. 21651 unload : bool, optional 21652 Whether or not to unload xonsh builtins upon deletion. 21653 xonsh_ctx : dict or None, optional 21654 Xonsh xontext to load as builtins.__xonsh_ctx__ 21655 scriptcache : bool, optional 21656 Whether or not to use a precompiled bytecode cache when execing 21657 code, default: True. 21658 cacheall : bool, optional 21659 Whether or not to cache all xonsh code, and not just files. If this 21660 is set to true, it will cache command line input too, default: False. 21661 """ 21662 parser_args = parser_args or {} 21663 self.parser = Parser(**parser_args) 21664 self.filename = filename 21665 self.debug_level = debug_level 21666 self.unload = unload 21667 self.scriptcache = scriptcache 21668 self.cacheall = cacheall 21669 self.ctxtransformer = CtxAwareTransformer(self.parser) 21670 load_builtins(execer=self, ctx=xonsh_ctx) 21671 21672 def __del__(self): 21673 if self.unload: 21674 unload_builtins() 21675 21676 def parse(self, input, ctx, mode="exec", filename=None, transform=True): 21677 """Parses xonsh code in a context-aware fashion. For context-free 21678 parsing, please use the Parser class directly or pass in 21679 transform=False. 21680 """ 21681 if filename is None: 21682 filename = self.filename 21683 if not transform: 21684 return self.parser.parse( 21685 input, filename=filename, mode=mode, debug_level=(self.debug_level > 2) 21686 ) 21687 21688 # Parsing actually happens in a couple of phases. The first is a 21689 # shortcut for a context-free parser. Normally, all subprocess 21690 # lines should be wrapped in $(), to indicate that they are a 21691 # subproc. But that would be super annoying. Unfortunately, Python 21692 # mode - after indentation - is whitespace agnostic while, using 21693 # the Python token, subproc mode is whitespace aware. That is to say, 21694 # in Python mode "ls -l", "ls-l", and "ls - l" all parse to the 21695 # same AST because whitespace doesn't matter to the minus binary op. 21696 # However, these phases all have very different meaning in subproc 21697 # mode. The 'right' way to deal with this is to make the entire 21698 # grammar whitespace aware, and then ignore all of the whitespace 21699 # tokens for all of the Python rules. The lazy way implemented here 21700 # is to parse a line a second time with a $() wrapper if it fails 21701 # the first time. This is a context-free phase. 21702 tree, input = self._parse_ctx_free(input, mode=mode, filename=filename) 21703 if tree is None: 21704 return None 21705 21706 # Now we need to perform context-aware AST transformation. This is 21707 # because the "ls -l" is valid Python. The only way that we know 21708 # it is not actually Python is by checking to see if the first token 21709 # (ls) is part of the execution context. If it isn't, then we will 21710 # assume that this line is supposed to be a subprocess line, assuming 21711 # it also is valid as a subprocess line. 21712 if ctx is None: 21713 ctx = set() 21714 elif isinstance(ctx, cabc.Mapping): 21715 ctx = set(ctx.keys()) 21716 tree = self.ctxtransformer.ctxvisit( 21717 tree, input, ctx, mode=mode, debug_level=self.debug_level 21718 ) 21719 return tree 21720 21721 def compile( 21722 self, 21723 input, 21724 mode="exec", 21725 glbs=None, 21726 locs=None, 21727 stacklevel=2, 21728 filename=None, 21729 transform=True, 21730 ): 21731 """Compiles xonsh code into a Python code object, which may then 21732 be execed or evaled. 21733 """ 21734 if filename is None: 21735 filename = self.filename 21736 if glbs is None or locs is None: 21737 frame = inspect.stack()[stacklevel][0] 21738 glbs = frame.f_globals if glbs is None else glbs 21739 locs = frame.f_locals if locs is None else locs 21740 ctx = set(dir(builtins)) | set(glbs.keys()) | set(locs.keys()) 21741 tree = self.parse(input, ctx, mode=mode, filename=filename, transform=transform) 21742 if tree is None: 21743 return None # handles comment only input 21744 code = compile(tree, filename, mode) 21745 return code 21746 21747 def eval( 21748 self, input, glbs=None, locs=None, stacklevel=2, filename=None, transform=True 21749 ): 21750 """Evaluates (and returns) xonsh code.""" 21751 if isinstance(input, types.CodeType): 21752 code = input 21753 else: 21754 if filename is None: 21755 filename = self.filename 21756 code = self.compile( 21757 input=input, 21758 glbs=glbs, 21759 locs=locs, 21760 mode="eval", 21761 stacklevel=stacklevel, 21762 filename=filename, 21763 transform=transform, 21764 ) 21765 if code is None: 21766 return None # handles comment only input 21767 return eval(code, glbs, locs) 21768 21769 def exec( 21770 self, 21771 input, 21772 mode="exec", 21773 glbs=None, 21774 locs=None, 21775 stacklevel=2, 21776 filename=None, 21777 transform=True, 21778 ): 21779 """Execute xonsh code.""" 21780 if isinstance(input, types.CodeType): 21781 code = input 21782 else: 21783 if filename is None: 21784 filename = self.filename 21785 code = self.compile( 21786 input=input, 21787 glbs=glbs, 21788 locs=locs, 21789 mode=mode, 21790 stacklevel=stacklevel, 21791 filename=filename, 21792 transform=transform, 21793 ) 21794 if code is None: 21795 return None # handles comment only input 21796 return exec(code, glbs, locs) 21797 21798 def _print_debug_wrapping( 21799 self, line, sbpline, last_error_line, last_error_col, maxcol=None 21800 ): 21801 """print some debugging info if asked for.""" 21802 if self.debug_level > 1: 21803 msg = "{0}:{1}:{2}{3} - {4}\n" "{0}:{1}:{2}{3} + {5}" 21804 mstr = "" if maxcol is None else ":" + str(maxcol) 21805 msg = msg.format( 21806 self.filename, last_error_line, last_error_col, mstr, line, sbpline 21807 ) 21808 print(msg, file=sys.stderr) 21809 21810 def _parse_ctx_free(self, input, mode="exec", filename=None, logical_input=False): 21811 last_error_line = last_error_col = -1 21812 parsed = False 21813 original_error = None 21814 greedy = False 21815 if filename is None: 21816 filename = self.filename 21817 while not parsed: 21818 try: 21819 tree = self.parser.parse( 21820 input, 21821 filename=filename, 21822 mode=mode, 21823 debug_level=(self.debug_level > 2), 21824 ) 21825 parsed = True 21826 except IndentationError as e: 21827 if original_error is None: 21828 raise e 21829 else: 21830 raise original_error 21831 except SyntaxError as e: 21832 if original_error is None: 21833 original_error = e 21834 if (e.loc is None) or ( 21835 last_error_line == e.loc.lineno 21836 and last_error_col in (e.loc.column + 1, e.loc.column) 21837 ): 21838 raise original_error from None 21839 elif last_error_line != e.loc.lineno: 21840 original_error = e 21841 last_error_col = e.loc.column 21842 last_error_line = e.loc.lineno 21843 idx = last_error_line - 1 21844 lines = input.splitlines() 21845 line, nlogical, idx = get_logical_line(lines, idx) 21846 if nlogical > 1 and not logical_input: 21847 _, sbpline = self._parse_ctx_free( 21848 line, mode=mode, filename=filename, logical_input=True 21849 ) 21850 self._print_debug_wrapping( 21851 line, sbpline, last_error_line, last_error_col, maxcol=None 21852 ) 21853 replace_logical_line(lines, sbpline, idx, nlogical) 21854 last_error_col += 3 21855 input = "\n".join(lines) 21856 continue 21857 if input.endswith("\n"): 21858 lines.append("") 21859 if len(line.strip()) == 0: 21860 # whitespace only lines are not valid syntax in Python's 21861 # interactive mode='single', who knew?! Just ignore them. 21862 # this might cause actual syntax errors to have bad line 21863 # numbers reported, but should only affect interactive mode 21864 del lines[idx] 21865 last_error_line = last_error_col = -1 21866 input = "\n".join(lines) 21867 continue 21868 21869 if last_error_line > 1 and lines[idx - 1].rstrip()[-1:] == ":": 21870 # catch non-indented blocks and raise error. 21871 prev_indent = len(lines[idx - 1]) - len(lines[idx - 1].lstrip()) 21872 curr_indent = len(lines[idx]) - len(lines[idx].lstrip()) 21873 if prev_indent == curr_indent: 21874 raise original_error 21875 lexer = self.parser.lexer 21876 maxcol = ( 21877 None 21878 if greedy 21879 else find_next_break(line, mincol=last_error_col, lexer=lexer) 21880 ) 21881 if not greedy and maxcol in (e.loc.column + 1, e.loc.column): 21882 # go greedy the first time if the syntax error was because 21883 # we hit an end token out of place. This usually indicates 21884 # a subshell or maybe a macro. 21885 if not balanced_parens(line, maxcol=maxcol): 21886 greedy = True 21887 maxcol = None 21888 sbpline = subproc_toks( 21889 line, returnline=True, greedy=greedy, maxcol=maxcol, lexer=lexer 21890 ) 21891 if sbpline is None: 21892 # subprocess line had no valid tokens, 21893 if len(line.partition("#")[0].strip()) == 0: 21894 # likely because it only contained a comment. 21895 del lines[idx] 21896 last_error_line = last_error_col = -1 21897 input = "\n".join(lines) 21898 continue 21899 elif not greedy: 21900 greedy = True 21901 continue 21902 else: 21903 # or for some other syntax error 21904 raise original_error 21905 elif sbpline[last_error_col:].startswith( 21906 "![![" 21907 ) or sbpline.lstrip().startswith("![!["): 21908 # if we have already wrapped this in subproc tokens 21909 # and it still doesn't work, adding more won't help 21910 # anything 21911 if not greedy: 21912 greedy = True 21913 continue 21914 else: 21915 raise original_error 21916 # replace the line 21917 self._print_debug_wrapping( 21918 line, sbpline, last_error_line, last_error_col, maxcol=maxcol 21919 ) 21920 replace_logical_line(lines, sbpline, idx, nlogical) 21921 last_error_col += 3 21922 input = "\n".join(lines) 21923 return tree, input 21924 21925# 21926# imphooks 21927# 21928# -*- coding: utf-8 -*- 21929"""Import hooks for importing xonsh source files. 21930 21931This module registers the hooks it defines when it is imported. 21932""" 21933# amalgamated os 21934# amalgamated re 21935# amalgamated sys 21936# amalgamated types 21937# amalgamated builtins 21938# amalgamated contextlib 21939# amalgamated importlib 21940from importlib.machinery import ModuleSpec 21941from importlib.abc import MetaPathFinder, SourceLoader, Loader 21942 21943# amalgamated xonsh.events 21944# amalgamated xonsh.execer 21945# amalgamated xonsh.platform 21946# amalgamated xonsh.lazyasd 21947@lazyobject 21948def ENCODING_LINE(): 21949 # this regex comes from PEP 263 21950 # https://www.python.org/dev/peps/pep-0263/#defining-the-encoding 21951 return re.compile(b"^[ tv]*#.*?coding[:=][ t]*([-_.a-zA-Z0-9]+)") 21952 21953 21954def find_source_encoding(src): 21955 """Finds the source encoding given bytes representing a file. If 21956 no encoding is found, UTF-8 will be returned as per the docs 21957 https://docs.python.org/3/howto/unicode.html#unicode-literals-in-python-source-code 21958 """ 21959 utf8 = "UTF-8" 21960 first, _, rest = src.partition(b"\n") 21961 m = ENCODING_LINE.match(first) 21962 if m is not None: 21963 return m.group(1).decode(utf8) 21964 second, _, _ = rest.partition(b"\n") 21965 m = ENCODING_LINE.match(second) 21966 if m is not None: 21967 return m.group(1).decode(utf8) 21968 return utf8 21969 21970 21971class XonshImportHook(MetaPathFinder, SourceLoader): 21972 """Implements the import hook for xonsh source files.""" 21973 21974 def __init__(self, *args, **kwargs): 21975 super(XonshImportHook, self).__init__(*args, **kwargs) 21976 self._filenames = {} 21977 self._execer = None 21978 21979 @property 21980 def execer(self): 21981 if hasattr(builtins, "__xonsh_execer__"): 21982 execer = builtins.__xonsh_execer__ 21983 if self._execer is not None: 21984 self._execer = None 21985 elif self._execer is None: 21986 self._execer = execer = Execer(unload=False) 21987 else: 21988 execer = self._execer 21989 return execer 21990 21991 # 21992 # MetaPathFinder methods 21993 # 21994 def find_spec(self, fullname, path, target=None): 21995 """Finds the spec for a xonsh module if it exists.""" 21996 dot = "." 21997 spec = None 21998 path = sys.path if path is None else path 21999 if dot not in fullname and dot not in path: 22000 path = [dot] + path 22001 name = fullname.rsplit(dot, 1)[-1] 22002 fname = name + ".xsh" 22003 for p in path: 22004 if not isinstance(p, str): 22005 continue 22006 if not os.path.isdir(p) or not os.access(p, os.R_OK): 22007 continue 22008 if fname not in (x.name for x in scandir(p)): 22009 continue 22010 spec = ModuleSpec(fullname, self) 22011 self._filenames[fullname] = os.path.join(p, fname) 22012 break 22013 return spec 22014 22015 # 22016 # SourceLoader methods 22017 # 22018 def create_module(self, spec): 22019 """Create a xonsh module with the appropriate attributes.""" 22020 mod = types.ModuleType(spec.name) 22021 mod.__file__ = self.get_filename(spec.name) 22022 mod.__loader__ = self 22023 mod.__package__ = spec.parent or "" 22024 return mod 22025 22026 def get_filename(self, fullname): 22027 """Returns the filename for a module's fullname.""" 22028 return self._filenames[fullname] 22029 22030 def get_data(self, path): 22031 """Gets the bytes for a path.""" 22032 raise NotImplementedError 22033 22034 def get_code(self, fullname): 22035 """Gets the code object for a xonsh file.""" 22036 filename = self.get_filename(fullname) 22037 if filename is None: 22038 msg = "xonsh file {0!r} could not be found".format(fullname) 22039 raise ImportError(msg) 22040 with open(filename, "rb") as f: 22041 src = f.read() 22042 enc = find_source_encoding(src) 22043 src = src.decode(encoding=enc) 22044 src = src if src.endswith("\n") else src + "\n" 22045 execer = self.execer 22046 execer.filename = filename 22047 ctx = {} # dummy for modules 22048 code = execer.compile(src, glbs=ctx, locs=ctx) 22049 return code 22050 22051 22052# 22053# Import events 22054# 22055events.doc( 22056 "on_import_pre_find_spec", 22057 """ 22058on_import_pre_find_spec(fullname: str, path: str, target: module or None) -> None 22059 22060Fires before any import find_spec() calls have been executed. The parameters 22061here are the same as importlib.abc.MetaPathFinder.find_spec(). Namely, 22062 22063:``fullname``: The full name of the module to import. 22064:``path``: None if a top-level import, otherwise the ``__path__`` of the parent 22065 package. 22066:``target``: Target module used to make a better guess about the package spec. 22067""", 22068) 22069 22070events.doc( 22071 "on_import_post_find_spec", 22072 """ 22073on_import_post_find_spec(spec, fullname, path, target) -> None 22074 22075Fires after all import find_spec() calls have been executed. The parameters 22076here the spec and the arguments importlib.abc.MetaPathFinder.find_spec(). Namely, 22077 22078:``spec``: A ModuleSpec object if the spec was found, or None if it was not. 22079:``fullname``: The full name of the module to import. 22080:``path``: None if a top-level import, otherwise the ``__path__`` of the parent 22081 package. 22082:``target``: Target module used to make a better guess about the package spec. 22083""", 22084) 22085 22086events.doc( 22087 "on_import_pre_create_module", 22088 """ 22089on_import_pre_create_module(spec: ModuleSpec) -> None 22090 22091Fires right before a module is created by its loader. The only parameter 22092is the spec object. See importlib for more details. 22093""", 22094) 22095 22096events.doc( 22097 "on_import_post_create_module", 22098 """ 22099on_import_post_create_module(module: Module, spec: ModuleSpec) -> None 22100 22101Fires after a module is created by its loader but before the loader returns it. 22102The parameters here are the module object itself and the spec object. 22103See importlib for more details. 22104""", 22105) 22106 22107events.doc( 22108 "on_import_pre_exec_module", 22109 """ 22110on_import_pre_exec_module(module: Module) -> None 22111 22112Fires right before a module is executed by its loader. The only parameter 22113is the module itself. See importlib for more details. 22114""", 22115) 22116 22117events.doc( 22118 "on_import_post_exec_module", 22119 """ 22120on_import_post_create_module(module: Module) -> None 22121 22122Fires after a module is executed by its loader but before the loader returns it. 22123The only parameter is the module itself. See importlib for more details. 22124""", 22125) 22126 22127 22128def _should_dispatch_xonsh_import_event_loader(): 22129 """Figures out if we should dispatch to a load event""" 22130 return ( 22131 len(events.on_import_pre_create_module) > 0 22132 or len(events.on_import_post_create_module) > 0 22133 or len(events.on_import_pre_exec_module) > 0 22134 or len(events.on_import_post_exec_module) > 0 22135 ) 22136 22137 22138class XonshImportEventHook(MetaPathFinder): 22139 """Implements the import hook for firing xonsh events on import.""" 22140 22141 def __init__(self, *args, **kwargs): 22142 super().__init__(*args, **kwargs) 22143 self._fullname_stack = [] 22144 22145 @contextlib.contextmanager 22146 def append_stack(self, fullname): 22147 """A context manager for appending and then removing a name from the 22148 fullname stack. 22149 """ 22150 self._fullname_stack.append(fullname) 22151 yield 22152 del self._fullname_stack[-1] 22153 22154 # 22155 # MetaPathFinder methods 22156 # 22157 def find_spec(self, fullname, path, target=None): 22158 """Finds the spec for a xonsh module if it exists.""" 22159 if fullname in reversed(self._fullname_stack): 22160 # don't execute if we are already in the stack. 22161 return None 22162 npre = len(events.on_import_pre_find_spec) 22163 npost = len(events.on_import_post_find_spec) 22164 dispatch_load = _should_dispatch_xonsh_import_event_loader() 22165 if npre > 0: 22166 events.on_import_pre_find_spec.fire( 22167 fullname=fullname, path=path, target=target 22168 ) 22169 elif npost == 0 and not dispatch_load: 22170 # no events to fire, proceed normally and prevent recursion 22171 return None 22172 # now find the spec 22173 with self.append_stack(fullname): 22174 spec = importlib.util.find_spec(fullname) 22175 # fire post event 22176 if npost > 0: 22177 events.on_import_post_find_spec.fire( 22178 spec=spec, fullname=fullname, path=path, target=target 22179 ) 22180 if dispatch_load and spec is not None and hasattr(spec.loader, "create_module"): 22181 spec.loader = XonshImportEventLoader(spec.loader) 22182 return spec 22183 22184 22185class XonshImportEventLoader(Loader): 22186 """A class that dispatches loader calls to another loader and fires relevant 22187 xonsh events. 22188 """ 22189 22190 def __init__(self, loader): 22191 self.loader = loader 22192 22193 # 22194 # Loader methods 22195 # 22196 def create_module(self, spec): 22197 """Creates and returns the module object.""" 22198 events.on_import_pre_create_module.fire(spec=spec) 22199 mod = self.loader.create_module(spec) 22200 events.on_import_post_create_module.fire(module=mod, spec=spec) 22201 return mod 22202 22203 def exec_module(self, module): 22204 """Executes the module in its own namespace.""" 22205 events.on_import_pre_exec_module.fire(module=module) 22206 rtn = self.loader.exec_module(module) 22207 events.on_import_post_exec_module.fire(module=module) 22208 return rtn 22209 22210 def load_module(self, fullname): 22211 """Legacy module loading, provided for backwards compatibility.""" 22212 return self.loader.load_module(fullname) 22213 22214 def module_repr(self, module): 22215 """Legacy module repr, provided for backwards compatibility.""" 22216 return self.loader.module_repr(module) 22217 22218 22219def install_import_hooks(): 22220 """ 22221 Install Xonsh import hooks in ``sys.meta_path`` in order for ``.xsh`` files 22222 to be importable and import events to be fired. 22223 22224 Can safely be called many times, will be no-op if xonsh import hooks are 22225 already present. 22226 """ 22227 found_imp = found_event = False 22228 for hook in sys.meta_path: 22229 if isinstance(hook, XonshImportHook): 22230 found_imp = True 22231 elif isinstance(hook, XonshImportEventHook): 22232 found_event = True 22233 if not found_imp: 22234 sys.meta_path.append(XonshImportHook()) 22235 if not found_event: 22236 sys.meta_path.insert(0, XonshImportEventHook()) 22237 22238 22239# alias to deprecated name 22240install_hook = install_import_hooks 22241 22242# 22243# main 22244# 22245# -*- coding: utf-8 -*- 22246"""The main xonsh script.""" 22247# amalgamated os 22248# amalgamated sys 22249enum = _LazyModule.load('enum', 'enum') 22250# amalgamated argparse 22251# amalgamated builtins 22252# amalgamated contextlib 22253# amalgamated signal 22254# amalgamated traceback 22255from xonsh import __version__ 22256# amalgamated xonsh.timings 22257# amalgamated xonsh.lazyasd 22258# amalgamated xonsh.shell 22259# amalgamated xonsh.pretty 22260# amalgamated xonsh.execer 22261# amalgamated xonsh.proc 22262# amalgamated xonsh.jobs 22263# amalgamated xonsh.tools 22264# amalgamated xonsh.platform 22265# amalgamated xonsh.codecache 22266# amalgamated xonsh.xonfig 22267# amalgamated xonsh.lazyimps 22268# amalgamated xonsh.imphooks 22269# amalgamated xonsh.events 22270# amalgamated xonsh.environ 22271# amalgamated xonsh.xontribs 22272events.transmogrify("on_post_init", "LoadEvent") 22273events.doc( 22274 "on_post_init", 22275 """ 22276on_post_init() -> None 22277 22278Fired after all initialization is finished and we're ready to do work. 22279 22280NOTE: This is fired before the wizard is automatically started. 22281""", 22282) 22283 22284events.transmogrify("on_exit", "LoadEvent") 22285events.doc( 22286 "on_exit", 22287 """ 22288on_exit() -> None 22289 22290Fired after all commands have been executed, before tear-down occurs. 22291 22292NOTE: All the caveats of the ``atexit`` module also apply to this event. 22293""", 22294) 22295 22296 22297events.transmogrify("on_pre_cmdloop", "LoadEvent") 22298events.doc( 22299 "on_pre_cmdloop", 22300 """ 22301on_pre_cmdloop() -> None 22302 22303Fired just before the command loop is started, if it is. 22304""", 22305) 22306 22307events.transmogrify("on_post_cmdloop", "LoadEvent") 22308events.doc( 22309 "on_post_cmdloop", 22310 """ 22311on_post_cmdloop() -> None 22312 22313Fired just after the command loop finishes, if it is. 22314 22315NOTE: All the caveats of the ``atexit`` module also apply to this event. 22316""", 22317) 22318 22319events.transmogrify("on_pre_rc", "LoadEvent") 22320events.doc( 22321 "on_pre_rc", 22322 """ 22323on_pre_rc() -> None 22324 22325Fired just before rc files are loaded, if they are. 22326""", 22327) 22328 22329events.transmogrify("on_post_rc", "LoadEvent") 22330events.doc( 22331 "on_post_rc", 22332 """ 22333on_post_rc() -> None 22334 22335Fired just after rc files are loaded, if they are. 22336""", 22337) 22338 22339 22340def get_setproctitle(): 22341 """Proxy function for loading process title""" 22342 try: 22343 from setproctitle import setproctitle as spt 22344 except ImportError: 22345 return 22346 return spt 22347 22348 22349def path_argument(s): 22350 """Return a path only if the path is actually legal 22351 22352 This is very similar to argparse.FileType, except that it doesn't return 22353 an open file handle, but rather simply validates the path.""" 22354 22355 s = os.path.abspath(os.path.expanduser(s)) 22356 if not os.path.isfile(s): 22357 msg = "{0!r} must be a valid path to a file".format(s) 22358 raise argparse.ArgumentTypeError(msg) 22359 return s 22360 22361 22362@lazyobject 22363def parser(): 22364 p = argparse.ArgumentParser(description="xonsh", add_help=False) 22365 p.add_argument( 22366 "-h", 22367 "--help", 22368 dest="help", 22369 action="store_true", 22370 default=False, 22371 help="show help and exit", 22372 ) 22373 p.add_argument( 22374 "-V", 22375 "--version", 22376 dest="version", 22377 action="store_true", 22378 default=False, 22379 help="show version information and exit", 22380 ) 22381 p.add_argument( 22382 "-c", 22383 help="Run a single command and exit", 22384 dest="command", 22385 required=False, 22386 default=None, 22387 ) 22388 p.add_argument( 22389 "-i", 22390 "--interactive", 22391 help="force running in interactive mode", 22392 dest="force_interactive", 22393 action="store_true", 22394 default=False, 22395 ) 22396 p.add_argument( 22397 "-l", 22398 "--login", 22399 help="run as a login shell", 22400 dest="login", 22401 action="store_true", 22402 default=False, 22403 ) 22404 p.add_argument( 22405 "--config-path", 22406 help="DEPRECATED: static configuration files may now be used " 22407 "in the XONSHRC file list, see the --rc option.", 22408 dest="config_path", 22409 default=None, 22410 type=path_argument, 22411 ) 22412 p.add_argument( 22413 "--rc", 22414 help="The xonshrc files to load, these may be either xonsh " 22415 "files or JSON-based static configuration files.", 22416 dest="rc", 22417 nargs="+", 22418 type=path_argument, 22419 default=None, 22420 ) 22421 p.add_argument( 22422 "--no-rc", 22423 help="Do not load the .xonshrc files", 22424 dest="norc", 22425 action="store_true", 22426 default=False, 22427 ) 22428 p.add_argument( 22429 "--no-script-cache", 22430 help="Do not cache scripts as they are run", 22431 dest="scriptcache", 22432 action="store_false", 22433 default=True, 22434 ) 22435 p.add_argument( 22436 "--cache-everything", 22437 help="Use a cache, even for interactive commands", 22438 dest="cacheall", 22439 action="store_true", 22440 default=False, 22441 ) 22442 p.add_argument( 22443 "-D", 22444 dest="defines", 22445 help="define an environment variable, in the form of " 22446 "-DNAME=VAL. May be used many times.", 22447 metavar="ITEM", 22448 action="append", 22449 default=None, 22450 ) 22451 p.add_argument( 22452 "--shell-type", 22453 help="What kind of shell should be used. " 22454 "Possible options: readline, prompt_toolkit, random. " 22455 "Warning! If set this overrides $SHELL_TYPE variable.", 22456 dest="shell_type", 22457 choices=tuple(Shell.shell_type_aliases.keys()), 22458 default=None, 22459 ) 22460 p.add_argument( 22461 "--timings", 22462 help="Prints timing information before the prompt is shown. " 22463 "This is useful while tracking down performance issues " 22464 "and investigating startup times.", 22465 dest="timings", 22466 action="store_true", 22467 default=None, 22468 ) 22469 p.add_argument( 22470 "file", 22471 metavar="script-file", 22472 help="If present, execute the script in script-file" " and exit", 22473 nargs="?", 22474 default=None, 22475 ) 22476 p.add_argument( 22477 "args", 22478 metavar="args", 22479 help="Additional arguments to the script specified " "by script-file", 22480 nargs=argparse.REMAINDER, 22481 default=[], 22482 ) 22483 return p 22484 22485 22486def _pprint_displayhook(value): 22487 if value is None: 22488 return 22489 builtins._ = None # Set '_' to None to avoid recursion 22490 if isinstance(value, HiddenCommandPipeline): 22491 builtins._ = value 22492 return 22493 env = builtins.__xonsh_env__ 22494 if env.get("PRETTY_PRINT_RESULTS"): 22495 printed_val = pretty(value) 22496 else: 22497 printed_val = repr(value) 22498 if HAS_PYGMENTS and env.get("COLOR_RESULTS"): 22499 tokens = list(pygments.lex(printed_val, lexer=pyghooks.XonshLexer())) 22500 print_color(tokens) 22501 else: 22502 print(printed_val) # black & white case 22503 builtins._ = value 22504 22505 22506class XonshMode(enum.Enum): 22507 single_command = 0 22508 script_from_file = 1 22509 script_from_stdin = 2 22510 interactive = 3 22511 22512 22513def start_services(shell_kwargs, args): 22514 """Starts up the essential services in the proper order. 22515 This returns the environment instance as a convenience. 22516 """ 22517 install_import_hooks() 22518 # create execer, which loads builtins 22519 ctx = shell_kwargs.get("ctx", {}) 22520 debug = to_bool_or_int(os.getenv("XONSH_DEBUG", "0")) 22521 events.on_timingprobe.fire(name="pre_execer_init") 22522 execer = Execer( 22523 xonsh_ctx=ctx, 22524 debug_level=debug, 22525 scriptcache=shell_kwargs.get("scriptcache", True), 22526 cacheall=shell_kwargs.get("cacheall", False), 22527 ) 22528 events.on_timingprobe.fire(name="post_execer_init") 22529 # load rc files 22530 login = shell_kwargs.get("login", True) 22531 env = builtins.__xonsh_env__ 22532 rc = shell_kwargs.get("rc", None) 22533 rc = env.get("XONSHRC") if rc is None else rc 22534 if args.mode != XonshMode.interactive and not args.force_interactive: 22535 # Don't load xonshrc if not interactive shell 22536 rc = None 22537 events.on_pre_rc.fire() 22538 xonshrc_context(rcfiles=rc, execer=execer, ctx=ctx, env=env, login=login) 22539 events.on_post_rc.fire() 22540 # create shell 22541 builtins.__xonsh_shell__ = Shell(execer=execer, **shell_kwargs) 22542 ctx["__name__"] = "__main__" 22543 return env 22544 22545 22546def premain(argv=None): 22547 """Setup for main xonsh entry point. Returns parsed arguments.""" 22548 if argv is None: 22549 argv = sys.argv[1:] 22550 setup_timings() 22551 setproctitle = get_setproctitle() 22552 if setproctitle is not None: 22553 setproctitle(" ".join(["xonsh"] + argv)) 22554 builtins.__xonsh_ctx__ = {} 22555 args = parser.parse_args(argv) 22556 if args.help: 22557 parser.print_help() 22558 parser.exit() 22559 if args.version: 22560 version = "/".join(("xonsh", __version__)) 22561 print(version) 22562 parser.exit() 22563 shell_kwargs = { 22564 "shell_type": args.shell_type, 22565 "completer": False, 22566 "login": False, 22567 "scriptcache": args.scriptcache, 22568 "cacheall": args.cacheall, 22569 "ctx": builtins.__xonsh_ctx__, 22570 } 22571 if args.login: 22572 shell_kwargs["login"] = True 22573 if args.norc: 22574 shell_kwargs["rc"] = () 22575 elif args.rc: 22576 shell_kwargs["rc"] = args.rc 22577 setattr(sys, "displayhook", _pprint_displayhook) 22578 if args.command is not None: 22579 args.mode = XonshMode.single_command 22580 shell_kwargs["shell_type"] = "none" 22581 elif args.file is not None: 22582 args.mode = XonshMode.script_from_file 22583 shell_kwargs["shell_type"] = "none" 22584 elif not sys.stdin.isatty() and not args.force_interactive: 22585 args.mode = XonshMode.script_from_stdin 22586 shell_kwargs["shell_type"] = "none" 22587 else: 22588 args.mode = XonshMode.interactive 22589 shell_kwargs["completer"] = True 22590 shell_kwargs["login"] = True 22591 env = start_services(shell_kwargs, args) 22592 env["XONSH_LOGIN"] = shell_kwargs["login"] 22593 if args.defines is not None: 22594 env.update([x.split("=", 1) for x in args.defines]) 22595 env["XONSH_INTERACTIVE"] = args.force_interactive or ( 22596 args.mode == XonshMode.interactive 22597 ) 22598 if ON_WINDOWS: 22599 setup_win_unicode_console(env.get("WIN_UNICODE_CONSOLE", True)) 22600 return args 22601 22602 22603def _failback_to_other_shells(args, err): 22604 # only failback for interactive shell; if we cannot tell, treat it 22605 # as an interactive one for safe. 22606 if hasattr(args, "mode") and args.mode != XonshMode.interactive: 22607 raise err 22608 foreign_shell = None 22609 shells_file = "/etc/shells" 22610 if not os.path.exists(shells_file): 22611 # right now, it will always break here on Windows 22612 raise err 22613 excluded_list = ["xonsh", "screen"] 22614 with open(shells_file) as f: 22615 for line in f: 22616 line = line.strip() 22617 if not line or line.startswith("#"): 22618 continue 22619 if "/" not in line: 22620 continue 22621 _, shell = line.rsplit("/", 1) 22622 if shell in excluded_list: 22623 continue 22624 if not os.path.exists(line): 22625 continue 22626 foreign_shell = line 22627 break 22628 if foreign_shell: 22629 traceback.print_exc() 22630 print("Xonsh encountered an issue during launch", file=sys.stderr) 22631 print("Failback to {}".format(foreign_shell), file=sys.stderr) 22632 os.execlp(foreign_shell, foreign_shell) 22633 else: 22634 raise err 22635 22636 22637def main(argv=None): 22638 args = None 22639 try: 22640 args = premain(argv) 22641 return main_xonsh(args) 22642 except Exception as err: 22643 _failback_to_other_shells(args, err) 22644 22645 22646def main_xonsh(args): 22647 """Main entry point for xonsh cli.""" 22648 if not ON_WINDOWS: 22649 22650 def func_sig_ttin_ttou(n, f): 22651 pass 22652 22653 signal.signal(signal.SIGTTIN, func_sig_ttin_ttou) 22654 signal.signal(signal.SIGTTOU, func_sig_ttin_ttou) 22655 22656 events.on_post_init.fire() 22657 env = builtins.__xonsh_env__ 22658 shell = builtins.__xonsh_shell__ 22659 try: 22660 if args.mode == XonshMode.interactive: 22661 # enter the shell 22662 env["XONSH_INTERACTIVE"] = True 22663 ignore_sigtstp() 22664 if env["XONSH_INTERACTIVE"] and not any( 22665 os.path.isfile(i) for i in env["XONSHRC"] 22666 ): 22667 print_welcome_screen() 22668 events.on_pre_cmdloop.fire() 22669 try: 22670 shell.shell.cmdloop() 22671 finally: 22672 events.on_post_cmdloop.fire() 22673 elif args.mode == XonshMode.single_command: 22674 # run a single command and exit 22675 run_code_with_cache(args.command.lstrip(), shell.execer, mode="single") 22676 elif args.mode == XonshMode.script_from_file: 22677 # run a script contained in a file 22678 path = os.path.abspath(os.path.expanduser(args.file)) 22679 if os.path.isfile(path): 22680 sys.argv = [args.file] + args.args 22681 env.update(make_args_env()) # $ARGS is not sys.argv 22682 env["XONSH_SOURCE"] = path 22683 shell.ctx.update({"__file__": args.file, "__name__": "__main__"}) 22684 run_script_with_cache( 22685 args.file, shell.execer, glb=shell.ctx, loc=None, mode="exec" 22686 ) 22687 else: 22688 print("xonsh: {0}: No such file or directory.".format(args.file)) 22689 elif args.mode == XonshMode.script_from_stdin: 22690 # run a script given on stdin 22691 code = sys.stdin.read() 22692 run_code_with_cache( 22693 code, shell.execer, glb=shell.ctx, loc=None, mode="exec" 22694 ) 22695 finally: 22696 events.on_exit.fire() 22697 postmain(args) 22698 22699 22700def postmain(args=None): 22701 """Teardown for main xonsh entry point, accepts parsed arguments.""" 22702 if ON_WINDOWS: 22703 setup_win_unicode_console(enable=False) 22704 if hasattr(builtins, "__xonsh_shell__"): 22705 del builtins.__xonsh_shell__ 22706 22707 22708@contextlib.contextmanager 22709def main_context(argv=None): 22710 """Generator that runs pre- and post-main() functions. This has two iterations. 22711 The first yields the shell. The second returns None but cleans 22712 up the shell. 22713 """ 22714 args = premain(argv) 22715 yield builtins.__xonsh_shell__ 22716 postmain(args) 22717 22718 22719def setup( 22720 ctx=None, 22721 shell_type="none", 22722 env=(("RAISE_SUBPROC_ERROR", True),), 22723 aliases=(), 22724 xontribs=(), 22725 threadable_predictors=(), 22726): 22727 """Starts up a new xonsh shell. Calling this in function in another 22728 packages __init__.py will allow xonsh to be fully used in the 22729 package in headless or headed mode. This function is primarily indended to 22730 make starting up xonsh for 3rd party packages easier. 22731 22732 Parameters 22733 ---------- 22734 ctx : dict-like or None, optional 22735 The xonsh context to start with. If None, an empty dictionary 22736 is provided. 22737 shell_type : str, optional 22738 The type of shell to start. By default this is 'none', indicating 22739 we should start in headless mode. 22740 env : dict-like, optional 22741 Environment to update the current environment with after the shell 22742 has been initialized. 22743 aliases : dict-like, optional 22744 Aliases to add after the shell has been initialized. 22745 xontribs : iterable of str, optional 22746 Xontrib names to load. 22747 threadable_predictors : dict-like, optional 22748 Threadable predictors to start up with. These overide the defaults. 22749 """ 22750 ctx = {} if ctx is None else ctx 22751 # setup xonsh ctx and execer 22752 builtins.__xonsh_ctx__ = ctx 22753 builtins.__xonsh_execer__ = Execer(xonsh_ctx=ctx) 22754 builtins.__xonsh_shell__ = Shell( 22755 builtins.__xonsh_execer__, ctx=ctx, shell_type=shell_type 22756 ) 22757 builtins.__xonsh_env__.update(env) 22758 install_import_hooks() 22759 builtins.aliases.update(aliases) 22760 if xontribs: 22761 xontribs_load(xontribs) 22762 tp = builtins.__xonsh_commands_cache__.threadable_predictors 22763 tp.update(threadable_predictors) 22764 22765