import logging
import sys
from urllib import parse
from xonsh.built_ins import XonshSession
from xonsh.environ import Env
from . import tags as t
from . import xonsh_data
from .file_writes import insert_into_xonshrc
[docs]
class Routes:
path: str
registry: "dict[str, type[Routes]]" = {}
navbar = False
nav_title: "str|None" = None
err_msgs: "list" = []
"""session storage for error messages"""
def __init__(
self,
url: "parse.ParseResult",
params: "dict[str, list[str]]",
xsh: "XonshSession",
):
self.url = url
self.params = params
self.env: Env = xsh.env
self.xsh = xsh
def __init_subclass__(cls, **kwargs):
cls.registry[cls.path] = cls
[docs]
def err(self, msg: str):
html = xonsh_data.rst_to_html(msg)
tree = t.etree.fromstring(html)
self.err_msgs.append(tree)
_external_links = [
("Docs", "https://xon.sh/dev/contents.html"),
("Xontribs", "https://github.com/topics/xontrib"),
("Code", "https://github.com/xonsh/xonsh"),
("Sponsor", "https://github.com/sponsors/xonsh"),
]
[docs]
def get_nav_links(self):
for page in self.registry.values():
klass = []
if page.path == self.url.path:
klass.append("active")
title = page.nav_title() if callable(page.nav_title) else page.nav_title
if title:
yield t.nav_item(*klass)[t.nav_link(href=page.path)[title],]
for title, url in self._external_links:
yield t.nav_item()[t.nav_link(href=url, target="_blank")[title],]
[docs]
def get_err_msgs(self):
if not self.err_msgs:
return
for msg in self.err_msgs:
yield t.alert("alert-danger")[msg]
self.err_msgs.clear()
[docs]
def get_sel_url(self, name):
params = parse.urlencode({"selected": name})
return self.path + "?" + params
[docs]
@staticmethod
def get_display(display):
try:
display = t.etree.fromstring(f"<div>{display}</div>")
except Exception as ex:
logging.error(f"Failed to parse color-display {ex!r}. {display!r}")
display = t.pre()[display]
return display
[docs]
def update_rc(self, **kwargs):
# todo: handle updates and deletion as well
insert_into_xonshrc(kwargs)
[docs]
class ColorsPage(Routes):
path = "/"
nav_title = "Colors"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.colors = dict(xonsh_data.render_colors())
self.var_name = "XONSH_COLOR_STYLE"
[docs]
def to_card(self, name: str, display: str):
return t.card()[
t.card_body()[
t.card_title()[
t.a("stretched-link", href=self.get_sel_url(name))[name]
],
self.get_display(display),
]
]
[docs]
def get_cols(self):
for name, display in self.colors.items():
yield t.col_sm()[self.to_card(name, display),]
def _get_selected_header(self):
selected = self.params.get("selected")
current = self.env.get(self.var_name)
if selected and selected != current:
name = selected[0]
# update env-variable
form = t.inline_form(method="post")[
t.btn_primary("ml-2", "p-1", type="submit", name=self.var_name)[
f"Update ${self.var_name}",
],
]
return (f"Selected: {name}", form), name
return (f"Current: {current}",), current
[docs]
def get_selected(self):
header, name = self._get_selected_header()
name = name if name in self.colors else "default"
display = self.colors[name]
card = t.card()[
t.card_header()[header],
t.card_body()[self.get_display(display)],
]
return t.row()[t.col()[card],]
[docs]
def get(self):
# banner
yield self.get_selected()
yield t.br()
yield t.br()
# rest
cols = list(self.get_cols())
yield t.row()[cols]
[docs]
def post(self, _):
selected = self.params.get("selected")
if selected:
self.env[self.var_name] = selected[0]
self.update_rc(color=selected[0])
[docs]
class PromptsPage(Routes):
path = "/prompts"
nav_title = "Prompts"
def __init__(self, **kwargs):
super().__init__(**kwargs)
prompts = xonsh_data.render_prompts(self.env)
self.current = next(prompts)
self.prompts = dict(prompts)
self.card_width = self.prompts.pop("__max_width__", 40)
self.var_name = "PROMPT"
# todo: support updating RIGHT_PROMPT, BOTTOM_TOOLBAR
[docs]
def to_card(self, name: str, display: str):
return t.card()[
t.card_body()[
t.card_title()[
t.a("stretched-link", href=self.get_sel_url(name))[name]
],
self.get_display(display),
]
]
def _get_selected_header(self):
ps_names = self.params.get("selected")
if ps_names:
name = ps_names[0]
if name in self.prompts:
return f"Selected: {name}", name
return "Current: ", None
[docs]
def get_selected(self):
header, cur_sel = self._get_selected_header()
if cur_sel is None:
prompt = self.current["value"]
display = self.current["display"]
else:
cont = self.prompts[cur_sel]
prompt: str = cont["value"]
display = cont["display"]
# update env-variable
txt_area = t.textarea(
"form-control",
name=self.var_name,
rows=str(len(prompt.splitlines())),
)[prompt]
card = t.card()[
t.card_header()[header],
t.card_body()[
t.card_title()["Edit"],
txt_area,
t.br(),
t.card_title()["Preview"],
t.p("text-muted")[
"It is not a live preview. `Set` to get the updated view."
],
self.get_display(display),
],
t.card_footer("py-1")[t.btn_primary("py-1", type="submit")["Set",],],
]
return t.row()[t.col()[t.form(method="post")[card]],]
[docs]
def get_cols(self):
style = f"min-width: {self.card_width}ch"
for name, prompt in self.prompts.items():
yield t.div("col-auto", style=style)[self.to_card(name, prompt["display"]),]
[docs]
def get(self):
# banner
yield self.get_selected()
yield t.br()
yield t.br()
# rest
cols = list(self.get_cols())
yield t.row()[cols]
[docs]
def post(self, data: dict[str, str]):
if prompt := data.get(self.var_name):
prompt = prompt.replace("\r", "")
self.env[self.var_name] = prompt
self.update_rc(prompt=prompt)
[docs]
class XontribsPage(Routes):
path = "/xontribs"
nav_title = "Xontribs"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.xontribs = dict(xonsh_data.render_xontribs())
[docs]
@staticmethod
def mod_name(name):
return f"xontrib.{name}"
[docs]
@staticmethod
def is_loaded(name):
return XontribsPage.mod_name(name) in sys.modules
[docs]
def xontrib_card(self, name, data):
from xonsh.xontribs import find_xontrib
title = t.a(href=data["url"])[name]
if find_xontrib(name):
act_label = "Add"
if self.is_loaded(name):
act_label = "Remove"
action = t.inline_form(method="post")[
t.btn_primary("ml-2", "p-1", type="submit", name=name)[act_label,],
]
else:
title = title("stretched-link") # add class
action = ""
return t.card()[
t.card_header()[title, action],
t.card_body()[self.get_display(data["display"]),],
]
[docs]
def get(self):
yield t.card()[
t.card_body()[
t.card_title()["Popular xontrib sources"],
t.card_body()[
t.li()[
t.a(href="https://github.com/topics/xontrib")[
"Xontribs on Github"
]
],
t.li()[
t.a(href="https://github.com/xonsh/awesome-xontribs")[
"Awesome xontribs"
]
],
t.li()[
t.a(
href="https://xon.sh/api/_autosummary/xontribs/xontrib.html"
)["Core xontribs"]
],
t.li()[
t.a(href="https://github.com/xonsh/xontrib-template")[
"Create a xontrib step by step from template"
]
],
],
]
]
yield t.br()
for name, data in self.xontribs.items():
yield t.row()[t.col()[self.xontrib_card(name, data),]]
yield t.br()
[docs]
def post(self, data: dict[str, str]):
if not data:
return
name = list(data)[0]
if self.is_loaded(name):
# todo: update rc file
del sys.modules[self.mod_name(name)]
else:
from xonsh.xontribs import xontribs_load
_, err, _ = xontribs_load([name])
if err:
self.err(err)
else:
self.update_rc(xontribs=[name])
[docs]
class EnvVariablesPage(Routes):
path = "/vars"
nav_title = "Variables"
[docs]
def get_rows(self):
for name in sorted(self.env.keys()):
if not self.env.is_configurable(name):
continue
value = self.env[name]
envvar = self.env.get_docs(name)
html = xonsh_data.rst_to_html(envvar.doc)
yield t.tr()[
t.td("text-right")[str(name)],
t.td()[
t.p()[repr(value)],
t.small()[self.get_display(html)],
],
]
[docs]
def get_table(self):
rows = list(self.get_rows())
yield t.tbl("table-striped")[
self.get_header(),
rows,
]
[docs]
def get(self):
yield t.div("table-responsive")[self.get_table()]
[docs]
class AliasesPage(Routes):
path = "/alias"
nav_title = "Aliases"
[docs]
def get_rows(self):
if not self.xsh.aliases:
return
for name in sorted(self.xsh.aliases.keys()):
alias = self.xsh.aliases[name]
if callable(alias):
continue
# todo:
# 1. do not edit default aliases as well
# 2. way to update string aliases
yield t.tr()[
t.td("text-right")[str(name)],
t.td()[t.p()[repr(alias)],],
]
[docs]
def get_table(self):
rows = list(self.get_rows())
yield t.tbl("table-sm", "table-striped")[
self.get_header(),
rows,
]
[docs]
def get(self):
yield t.card()[
t.card_body()[
t.card_title()["Docs"],
t.card_body()[
t.li()[t.a(href="https://xon.sh/tutorial.html")["Tutorial"]],
t.li()[
t.a(href="https://xon.sh/callable_aliases.html")[
"Callable Aliases"
]
],
],
]
]
yield t.br()
yield t.div("table-responsive")[self.get_table()]