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