We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
"Little Bobby Colors": $PROMPT evaluates {colors} *after* substitution from external input
## xonfig

<details>

```
+------------------+----------------------+
| xonsh            | 0.9.27               |
| Git SHA          | 71fe9014             |
| Commit Date      | Jan 29 08:58:58 2021 |
| Python           | 3.9.5                |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 3.0.19               |
| shell type       | prompt_toolkit       |
| pygments         | 2.9.0                |
| on posix         | True                 |
| on linux         | True                 |
| distro           | ubuntu               |
| on darwin        | False                |
| on windows       | False                |
| on cygwin        | False                |
| on msys2         | False                |
| is superuser     | False                |
| default encoding | utf-8                |
| xonsh encoding   | utf-8                |
| encoding errors  | surrogateescape      |
| on jupyter       | False                |
| jupyter kernel   | None                 |
| xontrib 1        | apt_tabcomplete      |
| xontrib 2        | direnv               |
| xontrib 3        | kitty                |
| xontrib 4        | prompt_ret_code      |
+------------------+----------------------+
```

</details>

## Expected Behavior
When a prompt includes shell-external names (like working directory, running job name, etc), any "meta" command directives should be escaped, lest unusual names trigger surprising (and perhaps even harmful) behavior.

## Current Behavior
```
$ xonsh --no-rc  # just to demo with the default prompt

... default xonsh message ...

egnor@ostrich ~ $ mkdir '{'  # slightly odd directory name
egnor@ostrich ~ $ cd '{'  # here we go!
{BOLD_GREEN}egnor@ostrich{BOLD_BLUE} ~/{{BOLD_INTENSE_YELLOW}{RESET} {BOLD_BLUE}
${RESET}
```

Suddenly the prompt is barfing, because the curly braces are no longer balanced, because the `{` directory name was substituted into the prompt. This is also fun:

```
egnor@ostrich ~ $ mkdir '{BACKGROUND_RED} ALERT'
egnor@ostrich ~ $ cd '{BACKGROUND_RED} ALERT'
egnor@ostrich ~/ ALERT $
```

...and "ALERT" gets a bright red background color in the prompt. As far as I know, nothing in curly braces will do anything particularly terrible (nothing takes any arguments) so I don't _think_ this is a security issue but it sure doesn't feel right.

## Steps to Reproduce
1. Have a prompt that shows the current directory (e.g. the default prompt)
2. Create a directory with `{` / `}` characters in it, perhaps even color tags like `{RED}`
3. Enter that directory

## VERY VERY HUMBLE editorializing 🙇
<sup>(Please take this commentary with a HUGE grain of salt :salt: because I am a super newcomer to xonsh and not a contributor (yet?), though I'd be happy to help improve this... I _love_ xonsh's overall structure and direction!)</sup>

<sup>This problem could be fixed by somehow "escaping" the output from `{cwd}` and the like, OR by doing color substitution before/while doing other expansions (rather than expanding `{cwd}` in one pass, then expanding colors like `{RED}` in a separate pass)...</sup>

<sup>BUT I don't love the little mini language used in `$PROMPT` and friends (`$RIGHT_PROMPT`, `$TITLE`, `$BOTTOM_TOOLBAR`, etc). It's conventional to have such a little mini language in shells but I think xonsh can do better _and_ be simpler. Fundamentally this is yet another little string interpolation mini template language, with the usual problems of escaping "text" vs "markup".</sup>

<sup>But since this is all Python we don't really need "markup" and its attendant escaping problems. $PROMPT could just be a function that returns the prompt to show. That function can then call whatever handy dandy utility functions it wants to (to get the cwd formatted various ways, hostname, git status, etc) and assemble it using ordinary Python string manipulation (f-strings or `string.Template` or just the `+` operator), no fuss, no muss, no weird little special curly-brackets-with-colons things to learn. Colors and similar text formatting could be supported with a `ColoredStr` class which could be constructed and concatenated (with other `ColoredStr` and/or regular `str`) and sliced much like `str`. Then everything would be clean and easy and Pythonic without curly braces flopping about.</sup>

<sup>(End humble editorializing!)</sup>

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `xonsh/prompt/cwd.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 """CWD related prompt formatter"""
3 
4 import os
5 import shutil
6 
7 import xonsh.tools as xt
8 import xonsh.platform as xp
9 from xonsh.built_ins import XSH
10 
11 
12 def _replace_home(x):
13     if xp.ON_WINDOWS:
14         home = XSH.env["HOMEDRIVE"] + XSH.env["HOMEPATH"][0]
15         if x.startswith(home):
16             x = x.replace(home, "~", 1)
17 
18         if XSH.env.get("FORCE_POSIX_PATHS"):
19             x = x.replace(os.sep, os.altsep)
20 
21         return x
22     else:
23         home = XSH.env["HOME"]
24         if x.startswith(home):
25             x = x.replace(home, "~", 1)
26         return x
27 
28 
29 def _replace_home_cwd():
30     return _replace_home(XSH.env["PWD"])
31 
32 
33 def _collapsed_pwd():
34     sep = xt.get_sep()
35     pwd = _replace_home_cwd().split(sep)
36     size = len(pwd)
37     leader = sep if size > 0 and len(pwd[0]) == 0 else ""
38     base = [
39         i[0] if ix != size - 1 and i[0] != "." else i[0:2] if ix != size - 1 else i
40         for ix, i in enumerate(pwd)
41         if len(i) > 0
42     ]
43     return leader + sep.join(base)
44 
45 
46 def _dynamically_collapsed_pwd():
47     """Return the compact current working directory.  It respects the
48     environment variable DYNAMIC_CWD_WIDTH.
49     """
50     original_path = _replace_home_cwd()
51     target_width, units = XSH.env["DYNAMIC_CWD_WIDTH"]
52     elision_char = XSH.env["DYNAMIC_CWD_ELISION_CHAR"]
53     if target_width == float("inf"):
54         return original_path
55     if units == "%":
56         cols, _ = shutil.get_terminal_size()
57         target_width = (cols * target_width) // 100
58     sep = xt.get_sep()
59     pwd = original_path.split(sep)
60     last = pwd.pop()
61     remaining_space = target_width - len(last)
62     # Reserve space for separators
63     remaining_space_for_text = remaining_space - len(pwd)
64     parts = []
65     for i in range(len(pwd)):
66         part = pwd[i]
67         part_len = int(
68             min(len(part), max(1, remaining_space_for_text // (len(pwd) - i)))
69         )
70         remaining_space_for_text -= part_len
71         if len(part) > part_len:
72             reduced_part = part[0 : part_len - len(elision_char)] + elision_char
73             parts.append(reduced_part)
74         else:
75             parts.append(part)
76     parts.append(last)
77     full = sep.join(parts)
78     truncature_char = elision_char if elision_char else "..."
79     # If even if displaying one letter per dir we are too long
80     if len(full) > target_width:
81         # We truncate the left most part
82         full = truncature_char + full[int(-target_width) + len(truncature_char) :]
83         # if there is not even a single separator we still
84         # want to display at least the beginning of the directory
85         if full.find(sep) == -1:
86             full = (truncature_char + sep + last)[
87                 0 : int(target_width) - len(truncature_char)
88             ] + truncature_char
89     return full
90
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/xonsh/prompt/cwd.py b/xonsh/prompt/cwd.py
--- a/xonsh/prompt/cwd.py
+++ b/xonsh/prompt/cwd.py
@@ -27,7 +27,8 @@
 
 
 def _replace_home_cwd():
-    return _replace_home(XSH.env["PWD"])
+    pwd = XSH.env["PWD"].replace("{", "{{").replace("}", "}}")
+    return _replace_home(pwd)
 
 
 def _collapsed_pwd():

{"golden_diff": "diff --git a/xonsh/prompt/cwd.py b/xonsh/prompt/cwd.py\n--- a/xonsh/prompt/cwd.py\n+++ b/xonsh/prompt/cwd.py\n@@ -27,7 +27,8 @@\n \n \n def _replace_home_cwd():\n-    return _replace_home(XSH.env[\"PWD\"])\n+    pwd = XSH.env[\"PWD\"].replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n+    return _replace_home(pwd)\n \n \n def _collapsed_pwd():\n", "issue": "\"Little Bobby Colors\": $PROMPT evaluates {colors} *after* substitution from external input\n## xonfig\r\n\r\n<details>\r\n\r\n```\r\n+------------------+----------------------+\r\n| xonsh            | 0.9.27               |\r\n| Git SHA          | 71fe9014             |\r\n| Commit Date      | Jan 29 08:58:58 2021 |\r\n| Python           | 3.9.5                |\r\n| PLY              | 3.11                 |\r\n| have readline    | True                 |\r\n| prompt toolkit   | 3.0.19               |\r\n| shell type       | prompt_toolkit       |\r\n| pygments         | 2.9.0                |\r\n| on posix         | True                 |\r\n| on linux         | True                 |\r\n| distro           | ubuntu               |\r\n| on darwin        | False                |\r\n| on windows       | False                |\r\n| on cygwin        | False                |\r\n| on msys2         | False                |\r\n| is superuser     | False                |\r\n| default encoding | utf-8                |\r\n| xonsh encoding   | utf-8                |\r\n| encoding errors  | surrogateescape      |\r\n| on jupyter       | False                |\r\n| jupyter kernel   | None                 |\r\n| xontrib 1        | apt_tabcomplete      |\r\n| xontrib 2        | direnv               |\r\n| xontrib 3        | kitty                |\r\n| xontrib 4        | prompt_ret_code      |\r\n+------------------+----------------------+\r\n```\r\n\r\n</details>\r\n\r\n## Expected Behavior\r\nWhen a prompt includes shell-external names (like working directory, running job name, etc), any \"meta\" command directives should be escaped, lest unusual names trigger surprising (and perhaps even harmful) behavior.\r\n\r\n## Current Behavior\r\n```\r\n$ xonsh --no-rc  # just to demo with the default prompt\r\n\r\n... default xonsh message ...\r\n\r\negnor@ostrich ~ $ mkdir '{'  # slightly odd directory name\r\negnor@ostrich ~ $ cd '{'  # here we go!\r\n{BOLD_GREEN}egnor@ostrich{BOLD_BLUE} ~/{{BOLD_INTENSE_YELLOW}{RESET} {BOLD_BLUE}\r\n${RESET}\r\n```\r\n\r\nSuddenly the prompt is barfing, because the curly braces are no longer balanced, because the `{` directory name was substituted into the prompt. This is also fun:\r\n\r\n```\r\negnor@ostrich ~ $ mkdir '{BACKGROUND_RED} ALERT'\r\negnor@ostrich ~ $ cd '{BACKGROUND_RED} ALERT'\r\negnor@ostrich ~/ ALERT $\r\n```\r\n\r\n...and \"ALERT\" gets a bright red background color in the prompt. As far as I know, nothing in curly braces will do anything particularly terrible (nothing takes any arguments) so I don't _think_ this is a security issue but it sure doesn't feel right.\r\n\r\n## Steps to Reproduce\r\n1. Have a prompt that shows the current directory (e.g. the default prompt)\r\n2. Create a directory with `{` / `}` characters in it, perhaps even color tags like `{RED}`\r\n3. Enter that directory\r\n\r\n## VERY VERY HUMBLE editorializing \ud83d\ude47\r\n<sup>(Please take this commentary with a HUGE grain of salt :salt: because I am a super newcomer to xonsh and not a contributor (yet?), though I'd be happy to help improve this... I _love_ xonsh's overall structure and direction!)</sup>\r\n\r\n<sup>This problem could be fixed by somehow \"escaping\" the output from `{cwd}` and the like, OR by doing color substitution before/while doing other expansions (rather than expanding `{cwd}` in one pass, then expanding colors like `{RED}` in a separate pass)...</sup>\r\n\r\n<sup>BUT I don't love the little mini language used in `$PROMPT` and friends (`$RIGHT_PROMPT`, `$TITLE`, `$BOTTOM_TOOLBAR`, etc). It's conventional to have such a little mini language in shells but I think xonsh can do better _and_ be simpler. Fundamentally this is yet another little string interpolation mini template language, with the usual problems of escaping \"text\" vs \"markup\".</sup>\r\n\r\n<sup>But since this is all Python we don't really need \"markup\" and its attendant escaping problems. $PROMPT could just be a function that returns the prompt to show. That function can then call whatever handy dandy utility functions it wants to (to get the cwd formatted various ways, hostname, git status, etc) and assemble it using ordinary Python string manipulation (f-strings or `string.Template` or just the `+` operator), no fuss, no muss, no weird little special curly-brackets-with-colons things to learn. Colors and similar text formatting could be supported with a `ColoredStr` class which could be constructed and concatenated (with other `ColoredStr` and/or regular `str`) and sliced much like `str`. Then everything would be clean and easy and Pythonic without curly braces flopping about.</sup>\r\n\r\n<sup>(End humble editorializing!)</sup>\r\n\r\n## For community\r\n\u2b07\ufe0f  **Please click the \ud83d\udc4d reaction instead of leaving a `+1` or \ud83d\udc4d  comment**\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"CWD related prompt formatter\"\"\"\n\nimport os\nimport shutil\n\nimport xonsh.tools as xt\nimport xonsh.platform as xp\nfrom xonsh.built_ins import XSH\n\n\ndef _replace_home(x):\n    if xp.ON_WINDOWS:\n        home = XSH.env[\"HOMEDRIVE\"] + XSH.env[\"HOMEPATH\"][0]\n        if x.startswith(home):\n            x = x.replace(home, \"~\", 1)\n\n        if XSH.env.get(\"FORCE_POSIX_PATHS\"):\n            x = x.replace(os.sep, os.altsep)\n\n        return x\n    else:\n        home = XSH.env[\"HOME\"]\n        if x.startswith(home):\n            x = x.replace(home, \"~\", 1)\n        return x\n\n\ndef _replace_home_cwd():\n    return _replace_home(XSH.env[\"PWD\"])\n\n\ndef _collapsed_pwd():\n    sep = xt.get_sep()\n    pwd = _replace_home_cwd().split(sep)\n    size = len(pwd)\n    leader = sep if size > 0 and len(pwd[0]) == 0 else \"\"\n    base = [\n        i[0] if ix != size - 1 and i[0] != \".\" else i[0:2] if ix != size - 1 else i\n        for ix, i in enumerate(pwd)\n        if len(i) > 0\n    ]\n    return leader + sep.join(base)\n\n\ndef _dynamically_collapsed_pwd():\n    \"\"\"Return the compact current working directory.  It respects the\n    environment variable DYNAMIC_CWD_WIDTH.\n    \"\"\"\n    original_path = _replace_home_cwd()\n    target_width, units = XSH.env[\"DYNAMIC_CWD_WIDTH\"]\n    elision_char = XSH.env[\"DYNAMIC_CWD_ELISION_CHAR\"]\n    if target_width == float(\"inf\"):\n        return original_path\n    if units == \"%\":\n        cols, _ = shutil.get_terminal_size()\n        target_width = (cols * target_width) // 100\n    sep = xt.get_sep()\n    pwd = original_path.split(sep)\n    last = pwd.pop()\n    remaining_space = target_width - len(last)\n    # Reserve space for separators\n    remaining_space_for_text = remaining_space - len(pwd)\n    parts = []\n    for i in range(len(pwd)):\n        part = pwd[i]\n        part_len = int(\n            min(len(part), max(1, remaining_space_for_text // (len(pwd) - i)))\n        )\n        remaining_space_for_text -= part_len\n        if len(part) > part_len:\n            reduced_part = part[0 : part_len - len(elision_char)] + elision_char\n            parts.append(reduced_part)\n        else:\n            parts.append(part)\n    parts.append(last)\n    full = sep.join(parts)\n    truncature_char = elision_char if elision_char else \"...\"\n    # If even if displaying one letter per dir we are too long\n    if len(full) > target_width:\n        # We truncate the left most part\n        full = truncature_char + full[int(-target_width) + len(truncature_char) :]\n        # if there is not even a single separator we still\n        # want to display at least the beginning of the directory\n        if full.find(sep) == -1:\n            full = (truncature_char + sep + last)[\n                0 : int(target_width) - len(truncature_char)\n            ] + truncature_char\n    return full\n", "path": "xonsh/prompt/cwd.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\"\"\"CWD related prompt formatter\"\"\"\n\nimport os\nimport shutil\n\nimport xonsh.tools as xt\nimport xonsh.platform as xp\nfrom xonsh.built_ins import XSH\n\n\ndef _replace_home(x):\n    if xp.ON_WINDOWS:\n        home = XSH.env[\"HOMEDRIVE\"] + XSH.env[\"HOMEPATH\"][0]\n        if x.startswith(home):\n            x = x.replace(home, \"~\", 1)\n\n        if XSH.env.get(\"FORCE_POSIX_PATHS\"):\n            x = x.replace(os.sep, os.altsep)\n\n        return x\n    else:\n        home = XSH.env[\"HOME\"]\n        if x.startswith(home):\n            x = x.replace(home, \"~\", 1)\n        return x\n\n\ndef _replace_home_cwd():\n    pwd = XSH.env[\"PWD\"].replace(\"{\", \"{{\").replace(\"}\", \"}}\")\n    return _replace_home(pwd)\n\n\ndef _collapsed_pwd():\n    sep = xt.get_sep()\n    pwd = _replace_home_cwd().split(sep)\n    size = len(pwd)\n    leader = sep if size > 0 and len(pwd[0]) == 0 else \"\"\n    base = [\n        i[0] if ix != size - 1 and i[0] != \".\" else i[0:2] if ix != size - 1 else i\n        for ix, i in enumerate(pwd)\n        if len(i) > 0\n    ]\n    return leader + sep.join(base)\n\n\ndef _dynamically_collapsed_pwd():\n    \"\"\"Return the compact current working directory.  It respects the\n    environment variable DYNAMIC_CWD_WIDTH.\n    \"\"\"\n    original_path = _replace_home_cwd()\n    target_width, units = XSH.env[\"DYNAMIC_CWD_WIDTH\"]\n    elision_char = XSH.env[\"DYNAMIC_CWD_ELISION_CHAR\"]\n    if target_width == float(\"inf\"):\n        return original_path\n    if units == \"%\":\n        cols, _ = shutil.get_terminal_size()\n        target_width = (cols * target_width) // 100\n    sep = xt.get_sep()\n    pwd = original_path.split(sep)\n    last = pwd.pop()\n    remaining_space = target_width - len(last)\n    # Reserve space for separators\n    remaining_space_for_text = remaining_space - len(pwd)\n    parts = []\n    for i in range(len(pwd)):\n        part = pwd[i]\n        part_len = int(\n            min(len(part), max(1, remaining_space_for_text // (len(pwd) - i)))\n        )\n        remaining_space_for_text -= part_len\n        if len(part) > part_len:\n            reduced_part = part[0 : part_len - len(elision_char)] + elision_char\n            parts.append(reduced_part)\n        else:\n            parts.append(part)\n    parts.append(last)\n    full = sep.join(parts)\n    truncature_char = elision_char if elision_char else \"...\"\n    # If even if displaying one letter per dir we are too long\n    if len(full) > target_width:\n        # We truncate the left most part\n        full = truncature_char + full[int(-target_width) + len(truncature_char) :]\n        # if there is not even a single separator we still\n        # want to display at least the beginning of the directory\n        if full.find(sep) == -1:\n            full = (truncature_char + sep + last)[\n                0 : int(target_width) - len(truncature_char)\n            ] + truncature_char\n    return full\n", "path": "xonsh/prompt/cwd.py"}]}