1# -*- coding: utf-8 -*-
2"""Environment for the xonsh shell."""
3import os
4import re
5import sys
6import pprint
7import textwrap
8import locale
9import builtins
10import warnings
11import contextlib
12import collections
13import collections.abc as cabc
14
15from xonsh import __version__ as XONSH_VERSION
16from xonsh.lazyasd import LazyObject, lazyobject
17from xonsh.codecache import run_script_with_cache
18from xonsh.dirstack import _get_cwd
19from xonsh.events import events
20from xonsh.platform import (
21    BASH_COMPLETIONS_DEFAULT,
22    DEFAULT_ENCODING,
23    PATH_DEFAULT,
24    ON_WINDOWS,
25    ON_LINUX,
26    os_environ,
27)
28
29from xonsh.tools import (
30    always_true,
31    always_false,
32    ensure_string,
33    is_env_path,
34    str_to_env_path,
35    env_path_to_str,
36    is_bool,
37    to_bool,
38    bool_to_str,
39    is_history_tuple,
40    to_history_tuple,
41    history_tuple_to_str,
42    is_float,
43    is_string,
44    is_string_or_callable,
45    is_completions_display_value,
46    to_completions_display_value,
47    is_string_set,
48    csv_to_set,
49    set_to_csv,
50    is_int,
51    is_bool_seq,
52    to_bool_or_int,
53    bool_or_int_to_str,
54    csv_to_bool_seq,
55    bool_seq_to_csv,
56    DefaultNotGiven,
57    print_exception,
58    setup_win_unicode_console,
59    intensify_colors_on_win_setter,
60    is_dynamic_cwd_width,
61    to_dynamic_cwd_tuple,
62    dynamic_cwd_tuple_to_str,
63    is_logfile_opt,
64    to_logfile_opt,
65    logfile_opt_to_str,
66    executables_in,
67    is_nonstring_seq_of_strings,
68    pathsep_to_upper_seq,
69    seq_to_upper_pathsep,
70    print_color,
71    is_history_backend,
72    to_itself,
73    swap_values,
74    ptk2_color_depth_setter,
75)
76import xonsh.prompt.base as prompt
77
78
79events.doc(
80    "on_envvar_new",
81    """
82on_envvar_new(name: str, value: Any) -> None
83
84Fires after a new environment variable is created.
85Note: Setting envvars inside the handler might
86cause a recursion until the limit.
87""",
88)
89
90
91events.doc(
92    "on_envvar_change",
93    """
94on_envvar_change(name: str, oldvalue: Any, newvalue: Any) -> None
95
96Fires after an environment variable is changed.
97Note: Setting envvars inside the handler might
98cause a recursion until the limit.
99""",
100)
101
102
103@lazyobject
104def HELP_TEMPLATE():
105    return (
106        "{{INTENSE_RED}}{envvar}{{NO_COLOR}}:\n\n"
107        "{{INTENSE_YELLOW}}{docstr}{{NO_COLOR}}\n\n"
108        "default: {{CYAN}}{default}{{NO_COLOR}}\n"
109        "configurable: {{CYAN}}{configurable}{{NO_COLOR}}"
110    )
111
112
113@lazyobject
114def LOCALE_CATS():
115    lc = {
116        "LC_CTYPE": locale.LC_CTYPE,
117        "LC_COLLATE": locale.LC_COLLATE,
118        "LC_NUMERIC": locale.LC_NUMERIC,
119        "LC_MONETARY": locale.LC_MONETARY,
120        "LC_TIME": locale.LC_TIME,
121    }
122    if hasattr(locale, "LC_MESSAGES"):
123        lc["LC_MESSAGES"] = locale.LC_MESSAGES
124    return lc
125
126
127def locale_convert(key):
128    """Creates a converter for a locale key."""
129
130    def lc_converter(val):
131        try:
132            locale.setlocale(LOCALE_CATS[key], val)
133            val = locale.setlocale(LOCALE_CATS[key])
134        except (locale.Error, KeyError):
135            msg = "Failed to set locale {0!r} to {1!r}".format(key, val)
136            warnings.warn(msg, RuntimeWarning)
137        return val
138
139    return lc_converter
140
141
142def to_debug(x):
143    """Converts value using to_bool_or_int() and sets this value on as the
144    execer's debug level.
145    """
146    val = to_bool_or_int(x)
147    if hasattr(builtins, "__xonsh_execer__"):
148        builtins.__xonsh_execer__.debug_level = val
149    return val
150
151
152Ensurer = collections.namedtuple("Ensurer", ["validate", "convert", "detype"])
153Ensurer.__doc__ = """Named tuples whose elements are functions that
154represent environment variable validation, conversion, detyping.
155"""
156
157
158@lazyobject
159def DEFAULT_ENSURERS():
160    return {
161        "AUTO_CD": (is_bool, to_bool, bool_to_str),
162        "AUTO_PUSHD": (is_bool, to_bool, bool_to_str),
163        "AUTO_SUGGEST": (is_bool, to_bool, bool_to_str),
164        "AUTO_SUGGEST_IN_COMPLETIONS": (is_bool, to_bool, bool_to_str),
165        "BASH_COMPLETIONS": (is_env_path, str_to_env_path, env_path_to_str),
166        "CASE_SENSITIVE_COMPLETIONS": (is_bool, to_bool, bool_to_str),
167        re.compile("\w*DIRS$"): (is_env_path, str_to_env_path, env_path_to_str),
168        "COLOR_INPUT": (is_bool, to_bool, bool_to_str),
169        "COLOR_RESULTS": (is_bool, to_bool, bool_to_str),
170        "COMPLETIONS_BRACKETS": (is_bool, to_bool, bool_to_str),
171        "COMPLETIONS_CONFIRM": (is_bool, to_bool, bool_to_str),
172        "COMPLETIONS_DISPLAY": (
173            is_completions_display_value,
174            to_completions_display_value,
175            str,
176        ),
177        "COMPLETIONS_MENU_ROWS": (is_int, int, str),
178        "COMPLETION_QUERY_LIMIT": (is_int, int, str),
179        "DIRSTACK_SIZE": (is_int, int, str),
180        "DOTGLOB": (is_bool, to_bool, bool_to_str),
181        "DYNAMIC_CWD_WIDTH": (
182            is_dynamic_cwd_width,
183            to_dynamic_cwd_tuple,
184            dynamic_cwd_tuple_to_str,
185        ),
186        "DYNAMIC_CWD_ELISION_CHAR": (is_string, ensure_string, ensure_string),
187        "EXPAND_ENV_VARS": (is_bool, to_bool, bool_to_str),
188        "FORCE_POSIX_PATHS": (is_bool, to_bool, bool_to_str),
189        "FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE": (is_bool, to_bool, bool_to_str),
190        "FOREIGN_ALIASES_OVERRIDE": (is_bool, to_bool, bool_to_str),
191        "FUZZY_PATH_COMPLETION": (is_bool, to_bool, bool_to_str),
192        "GLOB_SORTED": (is_bool, to_bool, bool_to_str),
193        "HISTCONTROL": (is_string_set, csv_to_set, set_to_csv),
194        "IGNOREEOF": (is_bool, to_bool, bool_to_str),
195        "INTENSIFY_COLORS_ON_WIN": (
196            always_false,
197            intensify_colors_on_win_setter,
198            bool_to_str,
199        ),
200        "LANG": (is_string, ensure_string, ensure_string),
201        "LC_COLLATE": (always_false, locale_convert("LC_COLLATE"), ensure_string),
202        "LC_CTYPE": (always_false, locale_convert("LC_CTYPE"), ensure_string),
203        "LC_MESSAGES": (always_false, locale_convert("LC_MESSAGES"), ensure_string),
204        "LC_MONETARY": (always_false, locale_convert("LC_MONETARY"), ensure_string),
205        "LC_NUMERIC": (always_false, locale_convert("LC_NUMERIC"), ensure_string),
206        "LC_TIME": (always_false, locale_convert("LC_TIME"), ensure_string),
207        "LOADED_RC_FILES": (is_bool_seq, csv_to_bool_seq, bool_seq_to_csv),
208        "MOUSE_SUPPORT": (is_bool, to_bool, bool_to_str),
209        "MULTILINE_PROMPT": (is_string_or_callable, ensure_string, ensure_string),
210        re.compile("\w*PATH$"): (is_env_path, str_to_env_path, env_path_to_str),
211        "PATHEXT": (
212            is_nonstring_seq_of_strings,
213            pathsep_to_upper_seq,
214            seq_to_upper_pathsep,
215        ),
216        "PRETTY_PRINT_RESULTS": (is_bool, to_bool, bool_to_str),
217        "PROMPT": (is_string_or_callable, ensure_string, ensure_string),
218        "PROMPT_TOOLKIT_COLOR_DEPTH": (
219            always_false,
220            ptk2_color_depth_setter,
221            ensure_string,
222        ),
223        "PUSHD_MINUS": (is_bool, to_bool, bool_to_str),
224        "PUSHD_SILENT": (is_bool, to_bool, bool_to_str),
225        "RAISE_SUBPROC_ERROR": (is_bool, to_bool, bool_to_str),
226        "RIGHT_PROMPT": (is_string_or_callable, ensure_string, ensure_string),
227        "BOTTOM_TOOLBAR": (is_string_or_callable, ensure_string, ensure_string),
228        "SUBSEQUENCE_PATH_COMPLETION": (is_bool, to_bool, bool_to_str),
229        "SUGGEST_COMMANDS": (is_bool, to_bool, bool_to_str),
230        "SUGGEST_MAX_NUM": (is_int, int, str),
231        "SUGGEST_THRESHOLD": (is_int, int, str),
232        "SUPPRESS_BRANCH_TIMEOUT_MESSAGE": (is_bool, to_bool, bool_to_str),
233        "UPDATE_COMPLETIONS_ON_KEYPRESS": (is_bool, to_bool, bool_to_str),
234        "UPDATE_OS_ENVIRON": (is_bool, to_bool, bool_to_str),
235        "UPDATE_PROMPT_ON_KEYPRESS": (is_bool, to_bool, bool_to_str),
236        "VC_BRANCH_TIMEOUT": (is_float, float, str),
237        "VC_HG_SHOW_BRANCH": (is_bool, to_bool, bool_to_str),
238        "VI_MODE": (is_bool, to_bool, bool_to_str),
239        "VIRTUAL_ENV": (is_string, ensure_string, ensure_string),
240        "WIN_UNICODE_CONSOLE": (always_false, setup_win_unicode_console, bool_to_str),
241        "XONSHRC": (is_env_path, str_to_env_path, env_path_to_str),
242        "XONSH_APPEND_NEWLINE": (is_bool, to_bool, bool_to_str),
243        "XONSH_AUTOPAIR": (is_bool, to_bool, bool_to_str),
244        "XONSH_CACHE_SCRIPTS": (is_bool, to_bool, bool_to_str),
245        "XONSH_CACHE_EVERYTHING": (is_bool, to_bool, bool_to_str),
246        "XONSH_COLOR_STYLE": (is_string, ensure_string, ensure_string),
247        "XONSH_DEBUG": (always_false, to_debug, bool_or_int_to_str),
248        "XONSH_ENCODING": (is_string, ensure_string, ensure_string),
249        "XONSH_ENCODING_ERRORS": (is_string, ensure_string, ensure_string),
250        "XONSH_HISTORY_BACKEND": (is_history_backend, to_itself, ensure_string),
251        "XONSH_HISTORY_FILE": (is_string, ensure_string, ensure_string),
252        "XONSH_HISTORY_MATCH_ANYWHERE": (is_bool, to_bool, bool_to_str),
253        "XONSH_HISTORY_SIZE": (
254            is_history_tuple,
255            to_history_tuple,
256            history_tuple_to_str,
257        ),
258        "XONSH_LOGIN": (is_bool, to_bool, bool_to_str),
259        "XONSH_PROC_FREQUENCY": (is_float, float, str),
260        "XONSH_SHOW_TRACEBACK": (is_bool, to_bool, bool_to_str),
261        "XONSH_STDERR_PREFIX": (is_string, ensure_string, ensure_string),
262        "XONSH_STDERR_POSTFIX": (is_string, ensure_string, ensure_string),
263        "XONSH_STORE_STDOUT": (is_bool, to_bool, bool_to_str),
264        "XONSH_STORE_STDIN": (is_bool, to_bool, bool_to_str),
265        "XONSH_TRACEBACK_LOGFILE": (is_logfile_opt, to_logfile_opt, logfile_opt_to_str),
266        "XONSH_DATETIME_FORMAT": (is_string, ensure_string, ensure_string),
267    }
268
269
270#
271# Defaults
272#
273def default_value(f):
274    """Decorator for making callable default values."""
275    f._xonsh_callable_default = True
276    return f
277
278
279def is_callable_default(x):
280    """Checks if a value is a callable default."""
281    return callable(x) and getattr(x, "_xonsh_callable_default", False)
282
283
284DEFAULT_TITLE = "{current_job:{} | }{user}@{hostname}: {cwd} | xonsh"
285
286
287@default_value
288def xonsh_data_dir(env):
289    """Ensures and returns the $XONSH_DATA_DIR"""
290    xdd = os.path.expanduser(os.path.join(env.get("XDG_DATA_HOME"), "xonsh"))
291    os.makedirs(xdd, exist_ok=True)
292    return xdd
293
294
295@default_value
296def xonsh_config_dir(env):
297    """Ensures and returns the $XONSH_CONFIG_DIR"""
298    xcd = os.path.expanduser(os.path.join(env.get("XDG_CONFIG_HOME"), "xonsh"))
299    os.makedirs(xcd, exist_ok=True)
300    return xcd
301
302
303def xonshconfig(env):
304    """Ensures and returns the $XONSHCONFIG"""
305    xcd = env.get("XONSH_CONFIG_DIR")
306    xc = os.path.join(xcd, "config.json")
307    return xc
308
309
310@default_value
311def default_xonshrc(env):
312    """Creates a new instance of the default xonshrc tuple."""
313    xcdrc = os.path.join(xonsh_config_dir(env), "rc.xsh")
314    if ON_WINDOWS:
315        dxrc = (
316            os.path.join(os_environ["ALLUSERSPROFILE"], "xonsh", "xonshrc"),
317            xcdrc,
318            os.path.expanduser("~/.xonshrc"),
319        )
320    else:
321        dxrc = ("/etc/xonshrc", xcdrc, os.path.expanduser("~/.xonshrc"))
322    # Check if old config file exists and issue warning
323    old_config_filename = xonshconfig(env)
324    if os.path.isfile(old_config_filename):
325        print(
326            "WARNING! old style configuration ("
327            + old_config_filename
328            + ") is no longer supported. "
329            + "Please migrate to xonshrc."
330        )
331    return dxrc
332
333
334@default_value
335def xonsh_append_newline(env):
336    """Appends a newline if we are in interactive mode"""
337    return env.get("XONSH_INTERACTIVE", False)
338
339
340# Default values should generally be immutable, that way if a user wants
341# to set them they have to do a copy and write them to the environment.
342# try to keep this sorted.
343@lazyobject
344def DEFAULT_VALUES():
345    dv = {
346        "AUTO_CD": False,
347        "AUTO_PUSHD": False,
348        "AUTO_SUGGEST": True,
349        "AUTO_SUGGEST_IN_COMPLETIONS": False,
350        "BASH_COMPLETIONS": BASH_COMPLETIONS_DEFAULT,
351        "CASE_SENSITIVE_COMPLETIONS": ON_LINUX,
352        "CDPATH": (),
353        "COLOR_INPUT": True,
354        "COLOR_RESULTS": True,
355        "COMPLETIONS_BRACKETS": True,
356        "COMPLETIONS_CONFIRM": False,
357        "COMPLETIONS_DISPLAY": "multi",
358        "COMPLETIONS_MENU_ROWS": 5,
359        "COMPLETION_QUERY_LIMIT": 100,
360        "DIRSTACK_SIZE": 20,
361        "DOTGLOB": False,
362        "DYNAMIC_CWD_WIDTH": (float("inf"), "c"),
363        "DYNAMIC_CWD_ELISION_CHAR": "",
364        "EXPAND_ENV_VARS": True,
365        "FORCE_POSIX_PATHS": False,
366        "FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE": False,
367        "FOREIGN_ALIASES_OVERRIDE": False,
368        "PROMPT_FIELDS": dict(prompt.PROMPT_FIELDS),
369        "FUZZY_PATH_COMPLETION": True,
370        "GLOB_SORTED": True,
371        "HISTCONTROL": set(),
372        "IGNOREEOF": False,
373        "INDENT": "    ",
374        "INTENSIFY_COLORS_ON_WIN": True,
375        "LANG": "C.UTF-8",
376        "LC_CTYPE": locale.setlocale(locale.LC_CTYPE),
377        "LC_COLLATE": locale.setlocale(locale.LC_COLLATE),
378        "LC_TIME": locale.setlocale(locale.LC_TIME),
379        "LC_MONETARY": locale.setlocale(locale.LC_MONETARY),
380        "LC_NUMERIC": locale.setlocale(locale.LC_NUMERIC),
381        "LOADED_RC_FILES": (),
382        "MOUSE_SUPPORT": False,
383        "MULTILINE_PROMPT": ".",
384        "PATH": PATH_DEFAULT,
385        "PATHEXT": [".COM", ".EXE", ".BAT", ".CMD"] if ON_WINDOWS else [],
386        "PRETTY_PRINT_RESULTS": True,
387        "PROMPT": prompt.default_prompt(),
388        "PROMPT_TOOLKIT_COLOR_DEPTH": "",
389        "PUSHD_MINUS": False,
390        "PUSHD_SILENT": False,
391        "RAISE_SUBPROC_ERROR": False,
392        "RIGHT_PROMPT": "",
393        "BOTTOM_TOOLBAR": "",
394        "SHELL_TYPE": "best",
395        "SUBSEQUENCE_PATH_COMPLETION": True,
396        "SUPPRESS_BRANCH_TIMEOUT_MESSAGE": False,
397        "SUGGEST_COMMANDS": True,
398        "SUGGEST_MAX_NUM": 5,
399        "SUGGEST_THRESHOLD": 3,
400        "TITLE": DEFAULT_TITLE,
401        "UPDATE_COMPLETIONS_ON_KEYPRESS": False,
402        "UPDATE_OS_ENVIRON": False,
403        "UPDATE_PROMPT_ON_KEYPRESS": False,
404        "VC_BRANCH_TIMEOUT": 0.2 if ON_WINDOWS else 0.1,
405        "VC_HG_SHOW_BRANCH": True,
406        "VI_MODE": False,
407        "WIN_UNICODE_CONSOLE": True,
408        "XDG_CONFIG_HOME": os.path.expanduser(os.path.join("~", ".config")),
409        "XDG_DATA_HOME": os.path.expanduser(os.path.join("~", ".local", "share")),
410        "XONSHRC": default_xonshrc,
411        "XONSH_APPEND_NEWLINE": xonsh_append_newline,
412        "XONSH_AUTOPAIR": False,
413        "XONSH_CACHE_SCRIPTS": True,
414        "XONSH_CACHE_EVERYTHING": False,
415        "XONSH_COLOR_STYLE": "default",
416        "XONSH_CONFIG_DIR": xonsh_config_dir,
417        "XONSH_DATA_DIR": xonsh_data_dir,
418        "XONSH_DEBUG": 0,
419        "XONSH_ENCODING": DEFAULT_ENCODING,
420        "XONSH_ENCODING_ERRORS": "surrogateescape",
421        "XONSH_HISTORY_BACKEND": "json",
422        "XONSH_HISTORY_FILE": os.path.expanduser("~/.xonsh_history.json"),
423        "XONSH_HISTORY_MATCH_ANYWHERE": False,
424        "XONSH_HISTORY_SIZE": (8128, "commands"),
425        "XONSH_LOGIN": False,
426        "XONSH_PROC_FREQUENCY": 1e-4,
427        "XONSH_SHOW_TRACEBACK": False,
428        "XONSH_STDERR_PREFIX": "",
429        "XONSH_STDERR_POSTFIX": "",
430        "XONSH_STORE_STDIN": False,
431        "XONSH_STORE_STDOUT": False,
432        "XONSH_TRACEBACK_LOGFILE": None,
433        "XONSH_DATETIME_FORMAT": "%Y-%m-%d %H:%M",
434    }
435    if hasattr(locale, "LC_MESSAGES"):
436        dv["LC_MESSAGES"] = locale.setlocale(locale.LC_MESSAGES)
437    return dv
438
439
440VarDocs = collections.namedtuple(
441    "VarDocs", ["docstr", "configurable", "default", "store_as_str"]
442)
443VarDocs.__doc__ = """Named tuple for environment variable documentation
444
445Parameters
446----------
447docstr : str
448   The environment variable docstring.
449configurable : bool, optional
450    Flag for whether the environment variable is configurable or not.
451default : str, optional
452    Custom docstring for the default value for complex defaults.
453    Is this is DefaultNotGiven, then the default will be looked up
454    from DEFAULT_VALUES and converted to a str.
455store_as_str : bool, optional
456    Flag for whether the environment variable should be stored as a
457    string. This is used when persisting a variable that is not JSON
458    serializable to the config file. For example, sets, frozensets, and
459    potentially other non-trivial data types. default, False.
460"""
461# iterates from back
462VarDocs.__new__.__defaults__ = (True, DefaultNotGiven, False)
463
464
465# Please keep the following in alphabetic order - scopatz
466@lazyobject
467def DEFAULT_DOCS():
468    return {
469        "ANSICON": VarDocs(
470            "This is used on Windows to set the title, " "if available.",
471            configurable=False,
472        ),
473        "AUTO_CD": VarDocs(
474            "Flag to enable changing to a directory by entering the dirname or "
475            "full path only (without the cd command)."
476        ),
477        "AUTO_PUSHD": VarDocs(
478            "Flag for automatically pushing directories onto the directory stack."
479        ),
480        "AUTO_SUGGEST": VarDocs(
481            "Enable automatic command suggestions based on history, like in the fish "
482            "shell.\n\nPressing the right arrow key inserts the currently "
483            "displayed suggestion. Only usable with ``$SHELL_TYPE=prompt_toolkit.``"
484        ),
485        "AUTO_SUGGEST_IN_COMPLETIONS": VarDocs(
486            "Places the auto-suggest result as the first option in the completions. "
487            "This enables you to tab complete the auto-suggestion."
488        ),
489        "BASH_COMPLETIONS": VarDocs(
490            "This is a list (or tuple) of strings that specifies where the "
491            "``bash_completion`` script may be found. "
492            "The first valid path will be used. For better performance, "
493            "bash-completion v2.x is recommended since it lazy-loads individual "
494            "completion scripts. "
495            "For both bash-completion v1.x and v2.x, paths of individual completion "
496            "scripts (like ``.../completes/ssh``) do not need to be included here. "
497            "The default values are platform "
498            "dependent, but sane. To specify an alternate list, do so in the run "
499            "control file.",
500            default=(
501                "Normally this is:\n\n"
502                "    ``('/usr/share/bash-completion/bash_completion', )``\n\n"
503                "But, on Mac it is:\n\n"
504                "    ``('/usr/local/share/bash-completion/bash_completion', "
505                "'/usr/local/etc/bash_completion')``\n\n"
506                "Other OS-specific defaults may be added in the future."
507            ),
508        ),
509        "CASE_SENSITIVE_COMPLETIONS": VarDocs(
510            "Sets whether completions should be case sensitive or case " "insensitive.",
511            default="True on Linux, False otherwise.",
512        ),
513        "CDPATH": VarDocs(
514            "A list of paths to be used as roots for a cd, breaking compatibility "
515            "with Bash, xonsh always prefer an existing relative path."
516        ),
517        "COLOR_INPUT": VarDocs("Flag for syntax highlighting interactive input."),
518        "COLOR_RESULTS": VarDocs("Flag for syntax highlighting return values."),
519        "COMPLETIONS_BRACKETS": VarDocs(
520            "Flag to enable/disable inclusion of square brackets and parentheses "
521            "in Python attribute completions.",
522            default="True",
523        ),
524        "COMPLETIONS_DISPLAY": VarDocs(
525            "Configure if and how Python completions are displayed by the "
526            "``prompt_toolkit`` shell.\n\nThis option does not affect Bash "
527            "completions, auto-suggestions, etc.\n\nChanging it at runtime will "
528            "take immediate effect, so you can quickly disable and enable "
529            "completions during shell sessions.\n\n"
530            "- If ``$COMPLETIONS_DISPLAY`` is ``none`` or ``false``, do not display\n"
531            "  those completions.\n"
532            "- If ``$COMPLETIONS_DISPLAY`` is ``single``, display completions in a\n"
533            "  single column while typing.\n"
534            "- If ``$COMPLETIONS_DISPLAY`` is ``multi`` or ``true``, display completions\n"
535            "  in multiple columns while typing.\n\n"
536            "- If ``$COMPLETIONS_DISPLAY`` is ``readline``, display completions\n"
537            "  will emulate the behavior of readline.\n\n"
538            "These option values are not case- or type-sensitive, so e.g."
539            "writing ``$COMPLETIONS_DISPLAY = None`` "
540            "and ``$COMPLETIONS_DISPLAY = 'none'`` are equivalent. Only usable with "
541            "``$SHELL_TYPE=prompt_toolkit``"
542        ),
543        "COMPLETIONS_CONFIRM": VarDocs(
544            "While tab-completions menu is displayed, press <Enter> to confirm "
545            "completion instead of running command. This only affects the "
546            "prompt-toolkit shell."
547        ),
548        "COMPLETIONS_MENU_ROWS": VarDocs(
549            "Number of rows to reserve for tab-completions menu if "
550            "``$COMPLETIONS_DISPLAY`` is ``single`` or ``multi``. This only affects the "
551            "prompt-toolkit shell."
552        ),
553        "COMPLETION_QUERY_LIMIT": VarDocs(
554            "The number of completions to display before the user is asked "
555            "for confirmation."
556        ),
557        "DIRSTACK_SIZE": VarDocs("Maximum size of the directory stack."),
558        "DOTGLOB": VarDocs(
559            'Globbing files with "*" or "**" will also match '
560            "dotfiles, or those 'hidden' files whose names "
561            "begin with a literal '.'. Such files are filtered "
562            "out by default."
563        ),
564        "DYNAMIC_CWD_WIDTH": VarDocs(
565            "Maximum length in number of characters "
566            "or as a percentage for the ``cwd`` prompt variable. For example, "
567            '"20" is a twenty character width and "10%" is ten percent of the '
568            "number of columns available."
569        ),
570        "DYNAMIC_CWD_ELISION_CHAR": VarDocs(
571            "The string used to show a shortened directory in a shortened cwd, "
572            "e.g. ``'…'``."
573        ),
574        "EXPAND_ENV_VARS": VarDocs(
575            "Toggles whether environment variables are expanded inside of strings "
576            "in subprocess mode."
577        ),
578        "FORCE_POSIX_PATHS": VarDocs(
579            "Forces forward slashes (``/``) on Windows systems when using auto "
580            "completion if set to anything truthy.",
581            configurable=ON_WINDOWS,
582        ),
583        "FOREIGN_ALIASES_SUPPRESS_SKIP_MESSAGE": VarDocs(
584            "Whether or not foreign aliases should suppress the message "
585            "that informs the user when a foreign alias has been skipped "
586            "because it already exists in xonsh.",
587            configurable=True,
588        ),
589        "FOREIGN_ALIASES_OVERRIDE": VarDocs(
590            "Whether or not foreign aliases should override xonsh aliases "
591            "with the same name. Note that setting of this must happen in the "
592            "environment that xonsh was started from. "
593            "It cannot be set in the ``.xonshrc`` as loading of foreign aliases happens before"
594            "``.xonshrc`` is parsed",
595            configurable=True,
596        ),
597        "PROMPT_FIELDS": VarDocs(
598            "Dictionary containing variables to be used when formatting $PROMPT "
599            "and $TITLE. See 'Customizing the Prompt' "
600            "http://xon.sh/tutorial.html#customizing-the-prompt",
601            configurable=False,
602            default="``xonsh.prompt.PROMPT_FIELDS``",
603        ),
604        "FUZZY_PATH_COMPLETION": VarDocs(
605            "Toggles 'fuzzy' matching of paths for tab completion, which is only "
606            "used as a fallback if no other completions succeed but can be used "
607            "as a way to adjust for typographical errors. If ``True``, then, e.g.,"
608            " ``xonhs`` will match ``xonsh``."
609        ),
610        "GLOB_SORTED": VarDocs(
611            "Toggles whether globbing results are manually sorted. If ``False``, "
612            "the results are returned in arbitrary order."
613        ),
614        "HISTCONTROL": VarDocs(
615            "A set of strings (comma-separated list in string form) of options "
616            "that determine what commands are saved to the history list. By "
617            "default all commands are saved. The option ``ignoredups`` will not "
618            "save the command if it matches the previous command. The option "
619            "'ignoreerr' will cause any commands that fail (i.e. return non-zero "
620            "exit status) to not be added to the history list.",
621            store_as_str=True,
622        ),
623        "IGNOREEOF": VarDocs("Prevents Ctrl-D from exiting the shell."),
624        "INDENT": VarDocs("Indentation string for multiline input"),
625        "INTENSIFY_COLORS_ON_WIN": VarDocs(
626            "Enhance style colors for readability "
627            "when using the default terminal (``cmd.exe``) on Windows. Blue colors, "
628            "which are hard to read, are replaced with cyan. Other colors are "
629            "generally replaced by their bright counter parts.",
630            configurable=ON_WINDOWS,
631        ),
632        "LANG": VarDocs("Fallback locale setting for systems where it matters"),
633        "LOADED_RC_FILES": VarDocs(
634            "Whether or not any of the xonsh run control files were loaded at "
635            "startup. This is a sequence of bools in Python that is converted "
636            "to a CSV list in string form, ie ``[True, False]`` becomes "
637            "``'True,False'``.",
638            configurable=False,
639        ),
640        "MOUSE_SUPPORT": VarDocs(
641            "Enable mouse support in the ``prompt_toolkit`` shell. This allows "
642            "clicking for positioning the cursor or selecting a completion. In "
643            "some terminals however, this disables the ability to scroll back "
644            "through the history of the terminal. Only usable with "
645            "``$SHELL_TYPE=prompt_toolkit``"
646        ),
647        "MULTILINE_PROMPT": VarDocs(
648            "Prompt text for 2nd+ lines of input, may be str or function which "
649            "returns a str."
650        ),
651        "OLDPWD": VarDocs(
652            "Used to represent a previous present working directory.",
653            configurable=False,
654        ),
655        "PATH": VarDocs("List of strings representing where to look for executables."),
656        "PATHEXT": VarDocs(
657            "Sequence of extension strings (eg, ``.EXE``) for "
658            "filtering valid executables by. Each element must be "
659            "uppercase."
660        ),
661        "PRETTY_PRINT_RESULTS": VarDocs('Flag for "pretty printing" return values.'),
662        "PROMPT": VarDocs(
663            "The prompt text. May contain keyword arguments which are "
664            "auto-formatted, see 'Customizing the Prompt' at "
665            "http://xon.sh/tutorial.html#customizing-the-prompt. "
666            "This value is never inherited from parent processes.",
667            default="``xonsh.environ.DEFAULT_PROMPT``",
668        ),
669        "PROMPT_TOOLKIT_COLOR_DEPTH": VarDocs(
670            "The color depth used by prompt toolkit 2. Possible values are: "
671            "``DEPTH_1_BIT``, ``DEPTH_4_BIT``, ``DEPTH_8_BIT``, ``DEPTH_24_BIT`` "
672            "colors. Default is an empty string which means that prompt toolkit decide."
673        ),
674        "PUSHD_MINUS": VarDocs(
675            "Flag for directory pushing functionality. False is the normal " "behavior."
676        ),
677        "PUSHD_SILENT": VarDocs(
678            "Whether or not to suppress directory stack manipulation output."
679        ),
680        "RAISE_SUBPROC_ERROR": VarDocs(
681            "Whether or not to raise an error if a subprocess (captured or "
682            "uncaptured) returns a non-zero exit status, which indicates failure. "
683            "This is most useful in xonsh scripts or modules where failures "
684            "should cause an end to execution. This is less useful at a terminal. "
685            "The error that is raised is a ``subprocess.CalledProcessError``."
686        ),
687        "RIGHT_PROMPT": VarDocs(
688            "Template string for right-aligned text "
689            "at the prompt. This may be parametrized in the same way as "
690            "the ``$PROMPT`` variable. Currently, this is only available in the "
691            "prompt-toolkit shell."
692        ),
693        "BOTTOM_TOOLBAR": VarDocs(
694            "Template string for the bottom toolbar. "
695            "This may be parametrized in the same way as "
696            "the ``$PROMPT`` variable. Currently, this is only available in the "
697            "prompt-toolkit shell."
698        ),
699        "SHELL_TYPE": VarDocs(
700            "Which shell is used. Currently two base shell types are supported:\n\n"
701            "    - ``readline`` that is backed by Python's readline module\n"
702            "    - ``prompt_toolkit`` that uses external library of the same name\n"
703            "    - ``random`` selects a random shell from the above on startup\n"
704            "    - ``best`` selects the most feature-rich shell available on the\n"
705            "       user's system\n\n"
706            "To use the ``prompt_toolkit`` shell you need to have the "
707            "`prompt_toolkit <https://github.com/jonathanslenders/python-prompt-toolkit>`_"
708            " library installed. To specify which shell should be used, do so in "
709            "the run control file.",
710            default="``best``",
711        ),
712        "SUBSEQUENCE_PATH_COMPLETION": VarDocs(
713            "Toggles subsequence matching of paths for tab completion. "
714            "If ``True``, then, e.g., ``~/u/ro`` can match ``~/lou/carcolh``."
715        ),
716        "SUGGEST_COMMANDS": VarDocs(
717            "When a user types an invalid command, xonsh will try to offer "
718            "suggestions of similar valid commands if this is True."
719        ),
720        "SUGGEST_MAX_NUM": VarDocs(
721            "xonsh will show at most this many suggestions in response to an "
722            "invalid command. If negative, there is no limit to how many "
723            "suggestions are shown."
724        ),
725        "SUGGEST_THRESHOLD": VarDocs(
726            "An error threshold. If the Levenshtein distance between the entered "
727            "command and a valid command is less than this value, the valid "
728            'command will be offered as a suggestion.  Also used for "fuzzy" '
729            "tab completion of paths."
730        ),
731        "SUPPRESS_BRANCH_TIMEOUT_MESSAGE": VarDocs(
732            "Whether or not to suppress branch timeout warning messages."
733        ),
734        "TERM": VarDocs(
735            "TERM is sometimes set by the terminal emulator. This is used (when "
736            "valid) to determine whether or not to set the title. Users shouldn't "
737            "need to set this themselves. Note that this variable should be set as "
738            "early as possible in order to ensure it is effective. Here are a few "
739            "options:\n\n"
740            "* Set this from the program that launches xonsh. On POSIX systems, \n"
741            "  this can be performed by using env, e.g. \n"
742            "  ``/usr/bin/env TERM=xterm-color xonsh`` or similar.\n"
743            "* From the xonsh command line, namely ``xonsh -DTERM=xterm-color``.\n"
744            '* In the config file with ``{"env": {"TERM": "xterm-color"}}``.\n'
745            "* Lastly, in xonshrc with ``$TERM``\n\n"
746            "Ideally, your terminal emulator will set this correctly but that does "
747            "not always happen.",
748            configurable=False,
749        ),
750        "TITLE": VarDocs(
751            "The title text for the window in which xonsh is running. Formatted "
752            "in the same manner as ``$PROMPT``, see 'Customizing the Prompt' "
753            "http://xon.sh/tutorial.html#customizing-the-prompt.",
754            default="``xonsh.environ.DEFAULT_TITLE``",
755        ),
756        "UPDATE_COMPLETIONS_ON_KEYPRESS": VarDocs(
757            "Completions display is evaluated and presented whenever a key is "
758            "pressed. This avoids the need to press TAB, except to cycle through "
759            "the possibilities. This currently only affects the prompt-toolkit shell."
760        ),
761        "UPDATE_OS_ENVIRON": VarDocs(
762            "If True ``os_environ`` will always be updated "
763            "when the xonsh environment changes. The environment can be reset to "
764            "the default value by calling ``__xonsh_env__.undo_replace_env()``"
765        ),
766        "UPDATE_PROMPT_ON_KEYPRESS": VarDocs(
767            "Disables caching the prompt between commands, "
768            "so that it would be reevaluated on each keypress. "
769            "Disabled by default because of the incurred performance penalty."
770        ),
771        "VC_BRANCH_TIMEOUT": VarDocs(
772            "The timeout (in seconds) for version control "
773            "branch computations. This is a timeout per subprocess call, so the "
774            "total time to compute will be larger than this in many cases."
775        ),
776        "VC_HG_SHOW_BRANCH": VarDocs(
777            "Whether or not to show the Mercurial branch in the prompt."
778        ),
779        "VI_MODE": VarDocs(
780            "Flag to enable ``vi_mode`` in the ``prompt_toolkit`` shell."
781        ),
782        "VIRTUAL_ENV": VarDocs(
783            "Path to the currently active Python environment.", configurable=False
784        ),
785        "WIN_UNICODE_CONSOLE": VarDocs(
786            "Enables unicode support in windows terminals. Requires the external "
787            "library ``win_unicode_console``.",
788            configurable=ON_WINDOWS,
789        ),
790        "XDG_CONFIG_HOME": VarDocs(
791            "Open desktop standard configuration home dir. This is the same "
792            "default as used in the standard.",
793            configurable=False,
794            default="``~/.config``",
795        ),
796        "XDG_DATA_HOME": VarDocs(
797            "Open desktop standard data home dir. This is the same default as "
798            "used in the standard.",
799            default="``~/.local/share``",
800        ),
801        "XONSHRC": VarDocs(
802            "A list of the locations of run control files, if they exist.  User "
803            "defined run control file will supersede values set in system-wide "
804            "control file if there is a naming collision.",
805            default=(
806                "On Linux & Mac OSX: ``['/etc/xonshrc', '~/.config/xonsh/rc.xsh', '~/.xonshrc']``\n"
807                "\nOn Windows: "
808                "``['%ALLUSERSPROFILE%\\\\xonsh\\\\xonshrc', '~/.config/xonsh/rc.xsh', '~/.xonshrc']``"
809            ),
810        ),
811        "XONSH_APPEND_NEWLINE": VarDocs(
812            "Append new line when a partial line is preserved in output."
813        ),
814        "XONSH_AUTOPAIR": VarDocs(
815            "Whether Xonsh will auto-insert matching parentheses, brackets, and "
816            "quotes. Only available under the prompt-toolkit shell."
817        ),
818        "XONSH_CACHE_SCRIPTS": VarDocs(
819            "Controls whether the code for scripts run from xonsh will be cached"
820            " (``True``) or re-compiled each time (``False``)."
821        ),
822        "XONSH_CACHE_EVERYTHING": VarDocs(
823            "Controls whether all code (including code entered at the interactive"
824            " prompt) will be cached."
825        ),
826        "XONSH_COLOR_STYLE": VarDocs(
827            "Sets the color style for xonsh colors. This is a style name, not "
828            "a color map. Run ``xonfig styles`` to see the available styles."
829        ),
830        "XONSH_CONFIG_DIR": VarDocs(
831            "This is the location where xonsh configuration information is stored.",
832            configurable=False,
833            default="``$XDG_CONFIG_HOME/xonsh``",
834        ),
835        "XONSH_DEBUG": VarDocs(
836            "Sets the xonsh debugging level. This may be an integer or a boolean. "
837            "Setting this variable prior to stating xonsh to ``1`` or ``True`` "
838            "will suppress amalgamated imports. Setting it to ``2`` will get some "
839            "basic information like input transformation, command replacement. "
840            "With ``3`` or a higher number will make more debugging information "
841            "presented, like PLY parsing messages.",
842            configurable=False,
843        ),
844        "XONSH_DATA_DIR": VarDocs(
845            "This is the location where xonsh data files are stored, such as "
846            "history.",
847            default="``$XDG_DATA_HOME/xonsh``",
848        ),
849        "XONSH_ENCODING": VarDocs(
850            "This is the encoding that xonsh should use for subprocess operations.",
851            default="``sys.getdefaultencoding()``",
852        ),
853        "XONSH_ENCODING_ERRORS": VarDocs(
854            "The flag for how to handle encoding errors should they happen. "
855            "Any string flag that has been previously registered with Python "
856            "is allowed. See the 'Python codecs documentation' "
857            "(https://docs.python.org/3/library/codecs.html#error-handlers) "
858            "for more information and available options.",
859            default="``surrogateescape``",
860        ),
861        "XONSH_GITSTATUS_*": VarDocs(
862            "Symbols for gitstatus prompt. Default values are: \n\n"
863            "* ``XONSH_GITSTATUS_HASH``: ``:``\n"
864            "* ``XONSH_GITSTATUS_BRANCH``: ``{CYAN}``\n"
865            "* ``XONSH_GITSTATUS_OPERATION``: ``{CYAN}``\n"
866            "* ``XONSH_GITSTATUS_STAGED``: ``{RED}●``\n"
867            "* ``XONSH_GITSTATUS_CONFLICTS``: ``{RED}×``\n"
868            "* ``XONSH_GITSTATUS_CHANGED``: ``{BLUE}+``\n"
869            "* ``XONSH_GITSTATUS_UNTRACKED``: ``…``\n"
870            "* ``XONSH_GITSTATUS_STASHED``: ``⚑``\n"
871            "* ``XONSH_GITSTATUS_CLEAN``: ``{BOLD_GREEN}✓``\n"
872            "* ``XONSH_GITSTATUS_AHEAD``: ``↑·``\n"
873            "* ``XONSH_GITSTATUS_BEHIND``: ``↓·``\n"
874        ),
875        "XONSH_HISTORY_BACKEND": VarDocs(
876            "Set which history backend to use. Options are: 'json', "
877            "'sqlite', and 'dummy'. The default is 'json'. "
878            "``XONSH_HISTORY_BACKEND`` also accepts a class type that inherits "
879            "from ``xonsh.history.base.History``, or its instance."
880        ),
881        "XONSH_HISTORY_FILE": VarDocs(
882            "Location of history file (deprecated).",
883            configurable=False,
884            default="``~/.xonsh_history``",
885        ),
886        "XONSH_HISTORY_MATCH_ANYWHERE": VarDocs(
887            "When searching history from a partial string (by pressing up arrow), "
888            "match command history anywhere in a given line (not just the start)",
889            default="False",
890        ),
891        "XONSH_HISTORY_SIZE": VarDocs(
892            "Value and units tuple that sets the size of history after garbage "
893            "collection. Canonical units are:\n\n"
894            "- ``commands`` for the number of past commands executed,\n"
895            "- ``files`` for the number of history files to keep,\n"
896            "- ``s`` for the number of seconds in the past that are allowed, and\n"
897            "- ``b`` for the number of bytes that history may consume.\n\n"
898            "Common abbreviations, such as '6 months' or '1 GB' are also allowed.",
899            default="``(8128, 'commands')`` or ``'8128 commands'``",
900        ),
901        "XONSH_INTERACTIVE": VarDocs(
902            "``True`` if xonsh is running interactively, and ``False`` otherwise.",
903            configurable=False,
904        ),
905        "XONSH_LOGIN": VarDocs(
906            "``True`` if xonsh is running as a login shell, and ``False`` otherwise.",
907            configurable=False,
908        ),
909        "XONSH_PROC_FREQUENCY": VarDocs(
910            "The process frequency is the time that "
911            "xonsh process threads sleep for while running command pipelines. "
912            "The value has units of seconds [s]."
913        ),
914        "XONSH_SHOW_TRACEBACK": VarDocs(
915            "Controls if a traceback is shown if exceptions occur in the shell. "
916            "Set to ``True`` to always show traceback or ``False`` to always hide. "
917            "If undefined then the traceback is hidden but a notice is shown on how "
918            "to enable the full traceback."
919        ),
920        "XONSH_SOURCE": VarDocs(
921            "When running a xonsh script, this variable contains the absolute path "
922            "to the currently executing script's file.",
923            configurable=False,
924        ),
925        "XONSH_STDERR_PREFIX": VarDocs(
926            "A format string, using the same keys and colors as ``$PROMPT``, that "
927            "is prepended whenever stderr is displayed. This may be used in "
928            "conjunction with ``$XONSH_STDERR_POSTFIX`` to close out the block."
929            "For example, to have stderr appear on a red background, the "
930            'prefix & postfix pair would be "{BACKGROUND_RED}" & "{NO_COLOR}".'
931        ),
932        "XONSH_STDERR_POSTFIX": VarDocs(
933            "A format string, using the same keys and colors as ``$PROMPT``, that "
934            "is appended whenever stderr is displayed. This may be used in "
935            "conjunction with ``$XONSH_STDERR_PREFIX`` to start the block."
936            "For example, to have stderr appear on a red background, the "
937            'prefix & postfix pair would be "{BACKGROUND_RED}" & "{NO_COLOR}".'
938        ),
939        "XONSH_STORE_STDIN": VarDocs(
940            "Whether or not to store the stdin that is supplied to the "
941            "``!()`` and ``![]`` operators."
942        ),
943        "XONSH_STORE_STDOUT": VarDocs(
944            "Whether or not to store the ``stdout`` and ``stderr`` streams in the "
945            "history files."
946        ),
947        "XONSH_TRACEBACK_LOGFILE": VarDocs(
948            "Specifies a file to store the traceback log to, regardless of whether "
949            "``XONSH_SHOW_TRACEBACK`` has been set. Its value must be a writable file "
950            "or None / the empty string if traceback logging is not desired. "
951            "Logging to a file is not enabled by default."
952        ),
953        "XONSH_DATETIME_FORMAT": VarDocs(
954            "The format that is used for ``datetime.strptime()`` in various places"
955            "i.e the history timestamp option"
956        ),
957    }
958
959
960#
961# actual environment
962#
963
964
965class Env(cabc.MutableMapping):
966    """A xonsh environment, whose variables have limited typing
967    (unlike BASH). Most variables are, by default, strings (like BASH).
968    However, the following rules also apply based on variable-name:
969
970    * PATH: any variable whose name ends in PATH is a list of strings.
971    * XONSH_HISTORY_SIZE: this variable is an (int | float, str) tuple.
972    * LC_* (locale categories): locale category names get/set the Python
973      locale via locale.getlocale() and locale.setlocale() functions.
974
975    An Env instance may be converted to an untyped version suitable for
976    use in a subprocess.
977    """
978
979    _arg_regex = None
980
981    def __init__(self, *args, **kwargs):
982        """If no initial environment is given, os_environ is used."""
983        self._d = {}
984        # sentinel value for non existing envvars
985        self._no_value = object()
986        self._orig_env = None
987        self._ensurers = {k: Ensurer(*v) for k, v in DEFAULT_ENSURERS.items()}
988        self._defaults = DEFAULT_VALUES
989        self._docs = DEFAULT_DOCS
990        if len(args) == 0 and len(kwargs) == 0:
991            args = (os_environ,)
992        for key, val in dict(*args, **kwargs).items():
993            self[key] = val
994        if ON_WINDOWS:
995            path_key = next((k for k in self._d if k.upper() == "PATH"), None)
996            if path_key:
997                self["PATH"] = self._d.pop(path_key)
998        if "PATH" not in self._d:
999            # this is here so the PATH is accessible to subprocs and so that
1000            # it can be modified in-place in the xonshrc file
1001            self._d["PATH"] = list(PATH_DEFAULT)
1002        self._detyped = None
1003
1004    @staticmethod
1005    def detypeable(val):
1006        return not (callable(val) or isinstance(val, cabc.MutableMapping))
1007
1008    def detype(self):
1009        if self._detyped is not None:
1010            return self._detyped
1011        ctx = {}
1012        for key, val in self._d.items():
1013            if not self.detypeable(val):
1014                continue
1015            if not isinstance(key, str):
1016                key = str(key)
1017            ensurer = self.get_ensurer(key)
1018            val = ensurer.detype(val)
1019            ctx[key] = val
1020        self._detyped = ctx
1021        return ctx
1022
1023    def replace_env(self):
1024        """Replaces the contents of os_environ with a detyped version
1025        of the xonsh environment.
1026        """
1027        if self._orig_env is None:
1028            self._orig_env = dict(os_environ)
1029        os_environ.clear()
1030        os_environ.update(self.detype())
1031
1032    def undo_replace_env(self):
1033        """Replaces the contents of os_environ with a detyped version
1034        of the xonsh environment.
1035        """
1036        if self._orig_env is not None:
1037            os_environ.clear()
1038            os_environ.update(self._orig_env)
1039            self._orig_env = None
1040
1041    def get_ensurer(self, key, default=Ensurer(always_true, None, ensure_string)):
1042        """Gets an ensurer for the given key."""
1043        if key in self._ensurers:
1044            return self._ensurers[key]
1045        for k, ensurer in self._ensurers.items():
1046            if isinstance(k, str):
1047                continue
1048            if k.match(key) is not None:
1049                break
1050        else:
1051            ensurer = default
1052        self._ensurers[key] = ensurer
1053        return ensurer
1054
1055    def get_docs(self, key, default=VarDocs("<no documentation>")):
1056        """Gets the documentation for the environment variable."""
1057        vd = self._docs.get(key, None)
1058        if vd is None:
1059            return default
1060        if vd.default is DefaultNotGiven:
1061            dval = pprint.pformat(self._defaults.get(key, "<default not set>"))
1062            vd = vd._replace(default=dval)
1063            self._docs[key] = vd
1064        return vd
1065
1066    def help(self, key):
1067        """Get information about a specific environment variable."""
1068        vardocs = self.get_docs(key)
1069        width = min(79, os.get_terminal_size()[0])
1070        docstr = "\n".join(textwrap.wrap(vardocs.docstr, width=width))
1071        template = HELP_TEMPLATE.format(
1072            envvar=key,
1073            docstr=docstr,
1074            default=vardocs.default,
1075            configurable=vardocs.configurable,
1076        )
1077        print_color(template)
1078
1079    def is_manually_set(self, varname):
1080        """
1081        Checks if an environment variable has been manually set.
1082        """
1083        return varname in self._d
1084
1085    @contextlib.contextmanager
1086    def swap(self, other=None, **kwargs):
1087        """Provides a context manager for temporarily swapping out certain
1088        environment variables with other values. On exit from the context
1089        manager, the original values are restored.
1090        """
1091        old = {}
1092        # single positional argument should be a dict-like object
1093        if other is not None:
1094            for k, v in other.items():
1095                old[k] = self.get(k, NotImplemented)
1096                self[k] = v
1097        # kwargs could also have been sent in
1098        for k, v in kwargs.items():
1099            old[k] = self.get(k, NotImplemented)
1100            self[k] = v
1101
1102        exception = None
1103        try:
1104            yield self
1105        except Exception as e:
1106            exception = e
1107        finally:
1108            # restore the values
1109            for k, v in old.items():
1110                if v is NotImplemented:
1111                    del self[k]
1112                else:
1113                    self[k] = v
1114            if exception is not None:
1115                raise exception from None
1116
1117    #
1118    # Mutable mapping interface
1119    #
1120
1121    def __getitem__(self, key):
1122        # remove this block on next release
1123        if key == "FORMATTER_DICT":
1124            print(
1125                "PendingDeprecationWarning: FORMATTER_DICT is an alias of "
1126                "PROMPT_FIELDS and will be removed in the next release",
1127                file=sys.stderr,
1128            )
1129            return self["PROMPT_FIELDS"]
1130        if key is Ellipsis:
1131            return self
1132        elif key in self._d:
1133            val = self._d[key]
1134        elif key in self._defaults:
1135            val = self._defaults[key]
1136            if is_callable_default(val):
1137                val = val(self)
1138        else:
1139            e = "Unknown environment variable: ${}"
1140            raise KeyError(e.format(key))
1141        if isinstance(
1142            val, (cabc.MutableSet, cabc.MutableSequence, cabc.MutableMapping)
1143        ):
1144            self._detyped = None
1145        return val
1146
1147    def __setitem__(self, key, val):
1148        ensurer = self.get_ensurer(key)
1149        if not ensurer.validate(val):
1150            val = ensurer.convert(val)
1151        # existing envvars can have any value including None
1152        old_value = self._d[key] if key in self._d else self._no_value
1153        self._d[key] = val
1154        if self.detypeable(val):
1155            self._detyped = None
1156            if self.get("UPDATE_OS_ENVIRON"):
1157                if self._orig_env is None:
1158                    self.replace_env()
1159                else:
1160                    os_environ[key] = ensurer.detype(val)
1161        if old_value is self._no_value:
1162            events.on_envvar_new.fire(name=key, value=val)
1163        elif old_value != val:
1164            events.on_envvar_change.fire(name=key, oldvalue=old_value, newvalue=val)
1165
1166    def __delitem__(self, key):
1167        val = self._d.pop(key)
1168        if self.detypeable(val):
1169            self._detyped = None
1170            if self.get("UPDATE_OS_ENVIRON") and key in os_environ:
1171                del os_environ[key]
1172
1173    def get(self, key, default=None):
1174        """The environment will look up default values from its own defaults if a
1175        default is not given here.
1176        """
1177        try:
1178            return self[key]
1179        except KeyError:
1180            return default
1181
1182    def __iter__(self):
1183        yield from (set(self._d) | set(self._defaults))
1184
1185    def __contains__(self, item):
1186        return item in self._d or item in self._defaults
1187
1188    def __len__(self):
1189        return len(self._d)
1190
1191    def __str__(self):
1192        return str(self._d)
1193
1194    def __repr__(self):
1195        return "{0}.{1}(...)".format(
1196            self.__class__.__module__, self.__class__.__name__, self._d
1197        )
1198
1199    def _repr_pretty_(self, p, cycle):
1200        name = "{0}.{1}".format(self.__class__.__module__, self.__class__.__name__)
1201        with p.group(0, name + "(", ")"):
1202            if cycle:
1203                p.text("...")
1204            elif len(self):
1205                p.break_()
1206                p.pretty(dict(self))
1207
1208
1209def _yield_executables(directory, name):
1210    if ON_WINDOWS:
1211        base_name, ext = os.path.splitext(name.lower())
1212        for fname in executables_in(directory):
1213            fbase, fext = os.path.splitext(fname.lower())
1214            if base_name == fbase and (len(ext) == 0 or ext == fext):
1215                yield os.path.join(directory, fname)
1216    else:
1217        for x in executables_in(directory):
1218            if x == name:
1219                yield os.path.join(directory, name)
1220                return
1221
1222
1223def locate_binary(name):
1224    """Locates an executable on the file system."""
1225    return builtins.__xonsh_commands_cache__.locate_binary(name)
1226
1227
1228BASE_ENV = LazyObject(
1229    lambda: {
1230        "BASH_COMPLETIONS": list(DEFAULT_VALUES["BASH_COMPLETIONS"]),
1231        "PROMPT_FIELDS": dict(DEFAULT_VALUES["PROMPT_FIELDS"]),
1232        "XONSH_VERSION": XONSH_VERSION,
1233    },
1234    globals(),
1235    "BASE_ENV",
1236)
1237
1238
1239def xonshrc_context(rcfiles=None, execer=None, ctx=None, env=None, login=True):
1240    """Attempts to read in all xonshrc files and return the context."""
1241    loaded = env["LOADED_RC_FILES"] = []
1242    ctx = {} if ctx is None else ctx
1243    if rcfiles is None:
1244        return env
1245    env["XONSHRC"] = tuple(rcfiles)
1246    for rcfile in rcfiles:
1247        if not os.path.isfile(rcfile):
1248            loaded.append(False)
1249            continue
1250        _, ext = os.path.splitext(rcfile)
1251        status = xonsh_script_run_control(rcfile, ctx, env, execer=execer, login=login)
1252        loaded.append(status)
1253    return ctx
1254
1255
1256def windows_foreign_env_fixes(ctx):
1257    """Environment fixes for Windows. Operates in-place."""
1258    # remove these bash variables which only cause problems.
1259    for ev in ["HOME", "OLDPWD"]:
1260        if ev in ctx:
1261            del ctx[ev]
1262    # Override path-related bash variables; on Windows bash uses
1263    # /c/Windows/System32 syntax instead of C:\\Windows\\System32
1264    # which messes up these environment variables for xonsh.
1265    for ev in ["PATH", "TEMP", "TMP"]:
1266        if ev in os_environ:
1267            ctx[ev] = os_environ[ev]
1268        elif ev in ctx:
1269            del ctx[ev]
1270    ctx["PWD"] = _get_cwd() or ""
1271
1272
1273def foreign_env_fixes(ctx):
1274    """Environment fixes for all operating systems"""
1275    if "PROMPT" in ctx:
1276        del ctx["PROMPT"]
1277
1278
1279def xonsh_script_run_control(filename, ctx, env, execer=None, login=True):
1280    """Loads a xonsh file and applies it as a run control."""
1281    if execer is None:
1282        return False
1283    updates = {"__file__": filename, "__name__": os.path.abspath(filename)}
1284    try:
1285        with swap_values(ctx, updates):
1286            run_script_with_cache(filename, execer, ctx)
1287        loaded = True
1288    except SyntaxError as err:
1289        msg = "syntax error in xonsh run control file {0!r}: {1!s}"
1290        print_exception(msg.format(filename, err))
1291        loaded = False
1292    except Exception as err:
1293        msg = "error running xonsh run control file {0!r}: {1!s}"
1294        print_exception(msg.format(filename, err))
1295        loaded = False
1296    return loaded
1297
1298
1299def default_env(env=None):
1300    """Constructs a default xonsh environment."""
1301    # in order of increasing precedence
1302    ctx = dict(BASE_ENV)
1303    ctx.update(os_environ)
1304    ctx["PWD"] = _get_cwd() or ""
1305    # These can cause problems for programs (#2543)
1306    ctx.pop("LINES", None)
1307    ctx.pop("COLUMNS", None)
1308    # other shells' PROMPT definitions generally don't work in XONSH:
1309    try:
1310        del ctx["PROMPT"]
1311    except KeyError:
1312        pass
1313    # finalize env
1314    if env is not None:
1315        ctx.update(env)
1316    return ctx
1317
1318
1319def make_args_env(args=None):
1320    """Makes a dictionary containing the $ARGS and $ARG<N> environment
1321    variables. If the supplied ARGS is None, then sys.argv is used.
1322    """
1323    if args is None:
1324        args = sys.argv
1325    env = {"ARG" + str(i): arg for i, arg in enumerate(args)}
1326    env["ARGS"] = list(args)  # make a copy so we don't interfere with original variable
1327    return env
1328