1"""The xonsh configuration (xonfig) utility.""" 2import os 3import re 4import ast 5import json 6import shutil 7import random 8import pprint 9import textwrap 10import builtins 11import argparse 12import functools 13import itertools 14import contextlib 15import collections 16 17try: 18 import ply 19except ImportError: 20 from xonsh.ply import ply 21 22import xonsh.wizard as wiz 23from xonsh import __version__ as XONSH_VERSION 24from xonsh.prompt.base import is_template_string 25from xonsh.platform import ( 26 is_readline_available, 27 ptk_version, 28 PYTHON_VERSION_INFO, 29 pygments_version, 30 ON_POSIX, 31 ON_LINUX, 32 linux_distro, 33 ON_DARWIN, 34 ON_WINDOWS, 35 ON_CYGWIN, 36 DEFAULT_ENCODING, 37 ON_MSYS, 38 githash, 39) 40from xonsh.tools import ( 41 to_bool, 42 is_string, 43 print_exception, 44 is_superuser, 45 color_style_names, 46 print_color, 47 color_style, 48) 49from xonsh.foreign_shells import CANON_SHELL_NAMES 50from xonsh.xontribs import xontrib_metadata, find_xontrib 51from xonsh.lazyasd import lazyobject 52 53HR = "'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'`-.,_,.-*'" 54WIZARD_HEAD = """ 55 {{BOLD_WHITE}}Welcome to the xonsh configuration wizard!{{NO_COLOR}} 56 {{YELLOW}}------------------------------------------{{NO_COLOR}} 57This will present a guided tour through setting up the xonsh static 58config file. Xonsh will automatically ask you if you want to run this 59wizard if the configuration file does not exist. However, you can 60always rerun this wizard with the xonfig command: 61 62 $ xonfig wizard 63 64This wizard will load an existing configuration, if it is available. 65Also never fear when this wizard saves its results! It will create 66a backup of any existing configuration automatically. 67 68This wizard has two main phases: foreign shell setup and environment 69variable setup. Each phase may be skipped in its entirety. 70 71For the configuration to take effect, you will need to restart xonsh. 72 73{hr} 74""".format( 75 hr=HR 76) 77 78WIZARD_FS = """ 79{hr} 80 81 {{BOLD_WHITE}}Foreign Shell Setup{{NO_COLOR}} 82 {{YELLOW}}-------------------{{NO_COLOR}} 83The xonsh shell has the ability to interface with foreign shells such 84as Bash, zsh, or fish. 85 86For configuration, this means that xonsh can load the environment, 87aliases, and functions specified in the config files of these shells. 88Naturally, these shells must be available on the system to work. 89Being able to share configuration (and source) from foreign shells 90makes it easier to transition to and from xonsh. 91""".format( 92 hr=HR 93) 94 95WIZARD_ENV = """ 96{hr} 97 98 {{BOLD_WHITE}}Environment Variable Setup{{NO_COLOR}} 99 {{YELLOW}}--------------------------{{NO_COLOR}} 100The xonsh shell also allows you to setup environment variables from 101the static configuration file. Any variables set in this way are 102superseded by the definitions in the xonshrc or on the command line. 103Still, setting environment variables in this way can help define 104options that are global to the system or user. 105 106The following lists the environment variable name, its documentation, 107the default value, and the current value. The default and current 108values are presented as pretty repr strings of their Python types. 109 110{{BOLD_GREEN}}Note:{{NO_COLOR}} Simply hitting enter for any environment variable 111will accept the default value for that entry. 112""".format( 113 hr=HR 114) 115 116WIZARD_ENV_QUESTION = "Would you like to set env vars now, " + wiz.YN 117 118WIZARD_XONTRIB = """ 119{hr} 120 121 {{BOLD_WHITE}}Xontribs{{NO_COLOR}} 122 {{YELLOW}}--------{{NO_COLOR}} 123No shell is complete without extensions, and xonsh is no exception. Xonsh 124extensions are called {{BOLD_GREEN}}xontribs{{NO_COLOR}}, or xonsh contributions. 125Xontribs are dynamically loadable, either by importing them directly or by 126using the 'xontrib' command. However, you can also configure xonsh to load 127xontribs automatically on startup prior to loading the run control files. 128This allows the xontrib to be used immediately in your xonshrc files. 129 130The following describes all xontribs that have been registered with xonsh. 131These come from users, 3rd party developers, or xonsh itself! 132""".format( 133 hr=HR 134) 135 136WIZARD_XONTRIB_QUESTION = "Would you like to enable xontribs now, " + wiz.YN 137 138WIZARD_TAIL = """ 139Thanks for using the xonsh configuration wizard!""" 140 141 142_XONFIG_SOURCE_FOREIGN_SHELL_COMMAND = collections.defaultdict( 143 lambda: "source-foreign", bash="source-bash", cmd="source-cmd", zsh="source-zsh" 144) 145 146 147def _dump_xonfig_foreign_shell(path, value): 148 shell = value["shell"] 149 shell = CANON_SHELL_NAMES.get(shell, shell) 150 cmd = [_XONFIG_SOURCE_FOREIGN_SHELL_COMMAND.get(shell)] 151 interactive = value.get("interactive", None) 152 if interactive is not None: 153 cmd.extend(["--interactive", str(interactive)]) 154 login = value.get("login", None) 155 if login is not None: 156 cmd.extend(["--login", str(login)]) 157 envcmd = value.get("envcmd", None) 158 if envcmd is not None: 159 cmd.extend(["--envcmd", envcmd]) 160 aliascmd = value.get("aliasmd", None) 161 if aliascmd is not None: 162 cmd.extend(["--aliascmd", aliascmd]) 163 extra_args = value.get("extra_args", None) 164 if extra_args: 165 cmd.extend(["--extra-args", repr(" ".join(extra_args))]) 166 safe = value.get("safe", None) 167 if safe is not None: 168 cmd.extend(["--safe", str(safe)]) 169 prevcmd = value.get("prevcmd", "") 170 if prevcmd: 171 cmd.extend(["--prevcmd", repr(prevcmd)]) 172 postcmd = value.get("postcmd", "") 173 if postcmd: 174 cmd.extend(["--postcmd", repr(postcmd)]) 175 funcscmd = value.get("funcscmd", None) 176 if funcscmd: 177 cmd.extend(["--funcscmd", repr(funcscmd)]) 178 sourcer = value.get("sourcer", None) 179 if sourcer: 180 cmd.extend(["--sourcer", sourcer]) 181 if cmd[0] == "source-foreign": 182 cmd.append(shell) 183 cmd.append('"echo loading xonsh foreign shell"') 184 return " ".join(cmd) 185 186 187def _dump_xonfig_env(path, value): 188 name = os.path.basename(path.rstrip("/")) 189 ensurer = builtins.__xonsh_env__.get_ensurer(name) 190 dval = ensurer.detype(value) 191 return "${name} = {val!r}".format(name=name, val=dval) 192 193 194def _dump_xonfig_xontribs(path, value): 195 return "xontrib load {0}".format(" ".join(value)) 196 197 198@lazyobject 199def XONFIG_DUMP_RULES(): 200 return { 201 "/": None, 202 "/env/": None, 203 "/foreign_shells/*/": _dump_xonfig_foreign_shell, 204 "/env/*": _dump_xonfig_env, 205 "/env/*/[0-9]*": None, 206 "/xontribs/": _dump_xonfig_xontribs, 207 } 208 209 210def make_fs_wiz(): 211 """Makes the foreign shell part of the wizard.""" 212 cond = wiz.create_truefalse_cond(prompt="Add a new foreign shell, " + wiz.YN) 213 fs = wiz.While( 214 cond=cond, 215 body=[ 216 wiz.Input("shell name (e.g. bash): ", path="/foreign_shells/{idx}/shell"), 217 wiz.StoreNonEmpty( 218 "interactive shell [bool, default=True]: ", 219 converter=to_bool, 220 show_conversion=True, 221 path="/foreign_shells/{idx}/interactive", 222 ), 223 wiz.StoreNonEmpty( 224 "login shell [bool, default=False]: ", 225 converter=to_bool, 226 show_conversion=True, 227 path="/foreign_shells/{idx}/login", 228 ), 229 wiz.StoreNonEmpty( 230 "env command [str, default='env']: ", 231 path="/foreign_shells/{idx}/envcmd", 232 ), 233 wiz.StoreNonEmpty( 234 "alias command [str, default='alias']: ", 235 path="/foreign_shells/{idx}/aliascmd", 236 ), 237 wiz.StoreNonEmpty( 238 ("extra command line arguments [list of str, " "default=[]]: "), 239 converter=ast.literal_eval, 240 show_conversion=True, 241 path="/foreign_shells/{idx}/extra_args", 242 ), 243 wiz.StoreNonEmpty( 244 "safely handle exceptions [bool, default=True]: ", 245 converter=to_bool, 246 show_conversion=True, 247 path="/foreign_shells/{idx}/safe", 248 ), 249 wiz.StoreNonEmpty( 250 "pre-command [str, default='']: ", path="/foreign_shells/{idx}/prevcmd" 251 ), 252 wiz.StoreNonEmpty( 253 "post-command [str, default='']: ", path="/foreign_shells/{idx}/postcmd" 254 ), 255 wiz.StoreNonEmpty( 256 "foreign function command [str, default=None]: ", 257 path="/foreign_shells/{idx}/funcscmd", 258 ), 259 wiz.StoreNonEmpty( 260 "source command [str, default=None]: ", 261 path="/foreign_shells/{idx}/sourcer", 262 ), 263 wiz.Message(message="Foreign shell added.\n"), 264 ], 265 ) 266 return fs 267 268 269def _wrap_paragraphs(text, width=70, **kwargs): 270 """Wraps paragraphs instead.""" 271 pars = text.split("\n") 272 pars = ["\n".join(textwrap.wrap(p, width=width, **kwargs)) for p in pars] 273 s = "\n".join(pars) 274 return s 275 276 277ENVVAR_MESSAGE = """ 278{{BOLD_CYAN}}${name}{{NO_COLOR}} 279{docstr} 280{{RED}}default value:{{NO_COLOR}} {default} 281{{RED}}current value:{{NO_COLOR}} {current}""" 282 283ENVVAR_PROMPT = "{BOLD_GREEN}>>>{NO_COLOR} " 284 285 286def make_exit_message(): 287 """Creates a message for how to exit the wizard.""" 288 shell_type = builtins.__xonsh_shell__.shell_type 289 keyseq = "Ctrl-D" if shell_type == "readline" else "Ctrl-C" 290 msg = "To exit the wizard at any time, press {BOLD_UNDERLINE_CYAN}" 291 msg += keyseq + "{NO_COLOR}.\n" 292 m = wiz.Message(message=msg) 293 return m 294 295 296def make_envvar(name): 297 """Makes a StoreNonEmpty node for an environment variable.""" 298 env = builtins.__xonsh_env__ 299 vd = env.get_docs(name) 300 if not vd.configurable: 301 return 302 default = vd.default 303 if "\n" in default: 304 default = "\n" + _wrap_paragraphs(default, width=69) 305 curr = env.get(name) 306 if is_string(curr) and is_template_string(curr): 307 curr = curr.replace("{", "{{").replace("}", "}}") 308 curr = pprint.pformat(curr, width=69) 309 if "\n" in curr: 310 curr = "\n" + curr 311 msg = ENVVAR_MESSAGE.format( 312 name=name, 313 default=default, 314 current=curr, 315 docstr=_wrap_paragraphs(vd.docstr, width=69), 316 ) 317 mnode = wiz.Message(message=msg) 318 ens = env.get_ensurer(name) 319 path = "/env/" + name 320 pnode = wiz.StoreNonEmpty( 321 ENVVAR_PROMPT, 322 converter=ens.convert, 323 show_conversion=True, 324 path=path, 325 retry=True, 326 store_raw=vd.store_as_str, 327 ) 328 return mnode, pnode 329 330 331def _make_flat_wiz(kidfunc, *args): 332 kids = map(kidfunc, *args) 333 flatkids = [] 334 for k in kids: 335 if k is None: 336 continue 337 flatkids.extend(k) 338 wizard = wiz.Wizard(children=flatkids) 339 return wizard 340 341 342def make_env_wiz(): 343 """Makes an environment variable wizard.""" 344 w = _make_flat_wiz(make_envvar, sorted(builtins.__xonsh_env__._docs.keys())) 345 return w 346 347 348XONTRIB_PROMPT = "{BOLD_GREEN}Add this xontrib{NO_COLOR}, " + wiz.YN 349 350 351def _xontrib_path(visitor=None, node=None, val=None): 352 # need this to append only based on user-selected size 353 return ("xontribs", len(visitor.state.get("xontribs", ()))) 354 355 356def make_xontrib(xontrib, package): 357 """Makes a message and StoreNonEmpty node for a xontrib.""" 358 name = xontrib.get("name", "<unknown-xontrib-name>") 359 msg = "\n{BOLD_CYAN}" + name + "{NO_COLOR}\n" 360 if "url" in xontrib: 361 msg += "{RED}url:{NO_COLOR} " + xontrib["url"] + "\n" 362 if "package" in xontrib: 363 msg += "{RED}package:{NO_COLOR} " + xontrib["package"] + "\n" 364 if "url" in package: 365 if "url" in xontrib and package["url"] != xontrib["url"]: 366 msg += "{RED}package-url:{NO_COLOR} " + package["url"] + "\n" 367 if "license" in package: 368 msg += "{RED}license:{NO_COLOR} " + package["license"] + "\n" 369 msg += "{PURPLE}installed?{NO_COLOR} " 370 msg += ("no" if find_xontrib(name) is None else "yes") + "\n" 371 desc = xontrib.get("description", "") 372 if not isinstance(desc, str): 373 desc = "".join(desc) 374 msg += _wrap_paragraphs(desc, width=69) 375 if msg.endswith("\n"): 376 msg = msg[:-1] 377 mnode = wiz.Message(message=msg) 378 convert = lambda x: name if to_bool(x) else wiz.Unstorable 379 pnode = wiz.StoreNonEmpty(XONTRIB_PROMPT, converter=convert, path=_xontrib_path) 380 return mnode, pnode 381 382 383def make_xontribs_wiz(): 384 """Makes a xontrib wizard.""" 385 md = xontrib_metadata() 386 pkgs = [md["packages"].get(d.get("package", None), {}) for d in md["xontribs"]] 387 w = _make_flat_wiz(make_xontrib, md["xontribs"], pkgs) 388 return w 389 390 391def make_xonfig_wizard(default_file=None, confirm=False, no_wizard_file=None): 392 """Makes a configuration wizard for xonsh config file. 393 394 Parameters 395 ---------- 396 default_file : str, optional 397 Default filename to save and load to. User will still be prompted. 398 confirm : bool, optional 399 Confirm that the main part of the wizard should be run. 400 no_wizard_file : str, optional 401 Filename for that will flag to future runs that the wizard should not be 402 run again. If None (default), this defaults to default_file. 403 """ 404 w = wiz.Wizard( 405 children=[ 406 wiz.Message(message=WIZARD_HEAD), 407 make_exit_message(), 408 wiz.Message(message=WIZARD_FS), 409 make_fs_wiz(), 410 wiz.Message(message=WIZARD_ENV), 411 wiz.YesNo(question=WIZARD_ENV_QUESTION, yes=make_env_wiz(), no=wiz.Pass()), 412 wiz.Message(message=WIZARD_XONTRIB), 413 wiz.YesNo( 414 question=WIZARD_XONTRIB_QUESTION, yes=make_xontribs_wiz(), no=wiz.Pass() 415 ), 416 wiz.Message(message="\n" + HR + "\n"), 417 wiz.FileInserter( 418 prefix="# XONSH WIZARD START", 419 suffix="# XONSH WIZARD END", 420 dump_rules=XONFIG_DUMP_RULES, 421 default_file=default_file, 422 check=True, 423 ), 424 wiz.Message(message=WIZARD_TAIL), 425 ] 426 ) 427 if confirm: 428 q = ( 429 "Would you like to run the xonsh configuration wizard now?\n\n" 430 "1. Yes (You can abort at any time)\n" 431 "2. No, but ask me next time.\n" 432 "3. No, and don't ask me again.\n\n" 433 "1, 2, or 3 [default: 2]? " 434 ) 435 no_wizard_file = default_file if no_wizard_file is None else no_wizard_file 436 passer = wiz.Pass() 437 saver = wiz.SaveJSON( 438 check=False, ask_filename=False, default_file=no_wizard_file 439 ) 440 w = wiz.Question( 441 q, {1: w, 2: passer, 3: saver}, converter=lambda x: int(x) if x != "" else 2 442 ) 443 return w 444 445 446def _wizard(ns): 447 env = builtins.__xonsh_env__ 448 shell = builtins.__xonsh_shell__.shell 449 fname = env.get("XONSHRC")[-1] if ns.file is None else ns.file 450 no_wiz = os.path.join(env.get("XONSH_CONFIG_DIR"), "no-wizard") 451 w = make_xonfig_wizard( 452 default_file=fname, confirm=ns.confirm, no_wizard_file=no_wiz 453 ) 454 tempenv = {"PROMPT": "", "XONSH_STORE_STDOUT": False} 455 pv = wiz.PromptVisitor(w, store_in_history=False, multiline=False) 456 457 @contextlib.contextmanager 458 def force_hide(): 459 if env.get("XONSH_STORE_STDOUT") and hasattr(shell, "_force_hide"): 460 orig, shell._force_hide = shell._force_hide, False 461 yield 462 shell._force_hide = orig 463 else: 464 yield 465 466 with force_hide(), env.swap(tempenv): 467 try: 468 pv.visit() 469 except (KeyboardInterrupt, Exception): 470 print() 471 print_exception() 472 473 474def _xonfig_format_human(data): 475 wcol1 = wcol2 = 0 476 for key, val in data: 477 wcol1 = max(wcol1, len(key)) 478 wcol2 = max(wcol2, len(str(val))) 479 hr = "+" + ("-" * (wcol1 + 2)) + "+" + ("-" * (wcol2 + 2)) + "+\n" 480 row = "| {key!s:<{wcol1}} | {val!s:<{wcol2}} |\n" 481 s = hr 482 for key, val in data: 483 s += row.format(key=key, wcol1=wcol1, val=val, wcol2=wcol2) 484 s += hr 485 return s 486 487 488def _xonfig_format_json(data): 489 data = {k.replace(" ", "_"): v for k, v in data} 490 s = json.dumps(data, sort_keys=True, indent=1) + "\n" 491 return s 492 493 494def _info(ns): 495 env = builtins.__xonsh_env__ 496 try: 497 ply.__version__ = ply.__version__ 498 except AttributeError: 499 ply.__version__ = "3.8" 500 data = [("xonsh", XONSH_VERSION)] 501 hash_, date_ = githash() 502 if hash_: 503 data.append(("Git SHA", hash_)) 504 data.append(("Commit Date", date_)) 505 data.extend( 506 [ 507 ("Python", "{}.{}.{}".format(*PYTHON_VERSION_INFO)), 508 ("PLY", ply.__version__), 509 ("have readline", is_readline_available()), 510 ("prompt toolkit", ptk_version() or None), 511 ("shell type", env.get("SHELL_TYPE")), 512 ("pygments", pygments_version()), 513 ("on posix", bool(ON_POSIX)), 514 ("on linux", bool(ON_LINUX)), 515 ] 516 ) 517 if ON_LINUX: 518 data.append(("distro", linux_distro())) 519 data.extend( 520 [ 521 ("on darwin", ON_DARWIN), 522 ("on windows", ON_WINDOWS), 523 ("on cygwin", ON_CYGWIN), 524 ("on msys2", ON_MSYS), 525 ("is superuser", is_superuser()), 526 ("default encoding", DEFAULT_ENCODING), 527 ("xonsh encoding", env.get("XONSH_ENCODING")), 528 ("encoding errors", env.get("XONSH_ENCODING_ERRORS")), 529 ] 530 ) 531 formatter = _xonfig_format_json if ns.json else _xonfig_format_human 532 s = formatter(data) 533 return s 534 535 536def _styles(ns): 537 env = builtins.__xonsh_env__ 538 curr = env.get("XONSH_COLOR_STYLE") 539 styles = sorted(color_style_names()) 540 if ns.json: 541 s = json.dumps(styles, sort_keys=True, indent=1) 542 print(s) 543 return 544 lines = [] 545 for style in styles: 546 if style == curr: 547 lines.append("* {GREEN}" + style + "{NO_COLOR}") 548 else: 549 lines.append(" " + style) 550 s = "\n".join(lines) 551 print_color(s) 552 553 554def _str_colors(cmap, cols): 555 color_names = sorted(cmap.keys(), key=(lambda s: (len(s), s))) 556 grper = lambda s: min(cols // (len(s) + 1), 8) 557 lines = [] 558 for n, group in itertools.groupby(color_names, key=grper): 559 width = cols // n 560 line = "" 561 for i, name in enumerate(group): 562 buf = " " * (width - len(name)) 563 line += "{" + name + "}" + name + "{NO_COLOR}" + buf 564 if (i + 1) % n == 0: 565 lines.append(line) 566 line = "" 567 if len(line) != 0: 568 lines.append(line) 569 return "\n".join(lines) 570 571 572def _tok_colors(cmap, cols): 573 from xonsh.style_tools import Color 574 575 nc = Color.NO_COLOR 576 names_toks = {} 577 for t in cmap.keys(): 578 name = str(t) 579 if name.startswith("Token.Color."): 580 _, _, name = name.rpartition(".") 581 names_toks[name] = t 582 color_names = sorted(names_toks.keys(), key=(lambda s: (len(s), s))) 583 grper = lambda s: min(cols // (len(s) + 1), 8) 584 toks = [] 585 for n, group in itertools.groupby(color_names, key=grper): 586 width = cols // n 587 for i, name in enumerate(group): 588 toks.append((names_toks[name], name)) 589 buf = " " * (width - len(name)) 590 if (i + 1) % n == 0: 591 buf += "\n" 592 toks.append((nc, buf)) 593 if not toks[-1][1].endswith("\n"): 594 toks[-1] = (nc, toks[-1][1] + "\n") 595 return toks 596 597 598def _colors(args): 599 columns, _ = shutil.get_terminal_size() 600 columns -= int(ON_WINDOWS) 601 style_stash = builtins.__xonsh_env__["XONSH_COLOR_STYLE"] 602 603 if args.style is not None: 604 if args.style not in color_style_names(): 605 print("Invalid style: {}".format(args.style)) 606 return 607 builtins.__xonsh_env__["XONSH_COLOR_STYLE"] = args.style 608 609 color_map = color_style() 610 akey = next(iter(color_map)) 611 if isinstance(akey, str): 612 s = _str_colors(color_map, columns) 613 else: 614 s = _tok_colors(color_map, columns) 615 print_color(s) 616 builtins.__xonsh_env__["XONSH_COLOR_STYLE"] = style_stash 617 618 619def _tutorial(args): 620 import webbrowser 621 622 webbrowser.open("http://xon.sh/tutorial.html") 623 624 625@functools.lru_cache(1) 626def _xonfig_create_parser(): 627 p = argparse.ArgumentParser( 628 prog="xonfig", description="Manages xonsh configuration." 629 ) 630 subp = p.add_subparsers(title="action", dest="action") 631 info = subp.add_parser( 632 "info", help=("displays configuration information, " "default action") 633 ) 634 info.add_argument( 635 "--json", action="store_true", default=False, help="reports results as json" 636 ) 637 wiz = subp.add_parser("wizard", help="displays configuration information") 638 wiz.add_argument( 639 "--file", default=None, help="config file location, default=$XONSHRC" 640 ) 641 wiz.add_argument( 642 "--confirm", 643 action="store_true", 644 default=False, 645 help="confirm that the wizard should be run.", 646 ) 647 sty = subp.add_parser("styles", help="prints available xonsh color styles") 648 sty.add_argument( 649 "--json", action="store_true", default=False, help="reports results as json" 650 ) 651 colors = subp.add_parser("colors", help="preview color style") 652 colors.add_argument( 653 "style", nargs="?", default=None, help="style to preview, default: <current>" 654 ) 655 subp.add_parser("tutorial", help="Launch tutorial in browser.") 656 return p 657 658 659_XONFIG_MAIN_ACTIONS = { 660 "info": _info, 661 "wizard": _wizard, 662 "styles": _styles, 663 "colors": _colors, 664 "tutorial": _tutorial, 665} 666 667 668def xonfig_main(args=None): 669 """Main xonfig entry point.""" 670 if not args or ( 671 args[0] not in _XONFIG_MAIN_ACTIONS and args[0] not in {"-h", "--help"} 672 ): 673 args.insert(0, "info") 674 parser = _xonfig_create_parser() 675 ns = parser.parse_args(args) 676 if ns.action is None: # apply default action 677 ns = parser.parse_args(["info"] + args) 678 return _XONFIG_MAIN_ACTIONS[ns.action](ns) 679 680 681@lazyobject 682def STRIP_COLOR_RE(): 683 return re.compile("{.*?}") 684 685 686def _align_string(string, align="<", fill=" ", width=80): 687 """ Align and pad a color formatted string """ 688 linelen = len(STRIP_COLOR_RE.sub("", string)) 689 padlen = max(width - linelen, 0) 690 if align == "^": 691 return fill * (padlen // 2) + string + fill * (padlen // 2 + padlen % 2) 692 elif align == ">": 693 return fill * padlen + string 694 elif align == "<": 695 return string + fill * padlen 696 else: 697 return string 698 699 700@lazyobject 701def TAGLINES(): 702 return [ 703 "Exofrills in the shell", 704 "No frills in the shell", 705 "Become the Lord of the Files", 706 "Break out of your shell", 707 "The only shell that is also a shell", 708 "All that is and all that shell be", 709 "It cannot be that hard", 710 "Pass the xonsh, Piggy", 711 "Piggy glanced nervously into hell and cradled the xonsh", 712 "The xonsh is a symbol", 713 "It is pronounced conch", 714 "The shell, bourne again", 715 "Snailed it", 716 "Starfish loves you", 717 "Come snail away", 718 "This is Major Tom to Ground Xonshtrol", 719 "Sally sells csh and keeps xonsh to herself", 720 "Nice indeed. Everything's accounted for, except your old shell.", 721 "I wanna thank you for putting me back in my snail shell", 722 "Crustaceanly Yours", 723 "With great shell comes great reproducibility", 724 "None shell pass", 725 "You shell not pass!", 726 "The x-on shell", 727 "Ever wonder why there isn't a Taco Shell? Because it is a corny idea.", 728 "The carcolh will catch you!", 729 "People xonshtantly mispronounce these things", 730 "WHAT...is your favorite shell?", 731 "Conches for the xonsh god!", 732 "Python-powered, cross-platform, Unix-gazing shell", 733 "Tab completion in Alderaan places", 734 "This fix was trickier than expected", 735 "The unholy cross of Bash/Python", 736 ] 737 738 739# list of strings or tuples (string, align, fill) 740WELCOME_MSG = [ 741 "", 742 ("{{INTENSE_WHITE}}Welcome to the xonsh shell ({version}){{NO_COLOR}}", "^", " "), 743 "", 744 ("{{INTENSE_RED}}~{{NO_COLOR}} {tagline} {{INTENSE_RED}}~{{NO_COLOR}}", "^", " "), 745 "", 746 ("{{INTENSE_BLACK}}", "<", "-"), 747 "{{GREEN}}xonfig{{NO_COLOR}} tutorial {{INTENSE_WHITE}}-> Launch the tutorial in " 748 "the browser{{NO_COLOR}}", 749 "{{GREEN}}xonfig{{NO_COLOR}} wizard {{INTENSE_WHITE}}-> Run the configuration " 750 "wizard and claim your shell {{NO_COLOR}}", 751 "{{INTENSE_BLACK}}(Note: Run the Wizard or create a {{RED}}~/.xonshrc{{INTENSE_BLACK}} file " 752 "to suppress the welcome screen)", 753 "", 754] 755 756 757def print_welcome_screen(): 758 subst = dict(tagline=random.choice(list(TAGLINES)), version=XONSH_VERSION) 759 for elem in WELCOME_MSG: 760 if isinstance(elem, str): 761 elem = (elem, "", "") 762 line = elem[0].format(**subst) 763 termwidth = os.get_terminal_size().columns 764 line = _align_string(line, elem[1], elem[2], width=termwidth) 765 print_color(line) 766