Add groups to env vars (#4026)

* feat: add grouped-settings for env variables

fixes #4014

style: fix mypy errors

chore: update testing requirements versions

fix: update xonsh.tools import error

* chore: add news item

* fix: update Var.with_default handling env defaults

* fix: set env var.doc_default=DefaultNotGiven

there is a custom handler for it

* chore: update travis

speedup docs generation

* chore: add command to serve docs during development

* docs: add jinja2 helpers/renderers extension for sphinx

* docs: update envvars document

* docs: fix docs failing

* Update xonsh/environ.py

commit suggestion

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* Update xonsh/environ.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* Update xonsh/environ.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* Update xonsh/environ.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* Update xonsh/environ.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* Update xonsh/environ.py

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>

* refactor: update rst-extension

Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
This commit is contained in:
Noorhteen Raja NJ 2021-01-04 22:51:44 +05:30 committed by GitHub
parent 17f86d359e
commit bceaafae4d
Failed to generate hash of commit
14 changed files with 1304 additions and 1230 deletions

View file

@ -5,34 +5,13 @@ env:
matrix: matrix:
include: include:
- os: linux - os: linux
python: 3.6 python: 3.8
env: env:
- MINICONDA_OS="Linux"
- BUILD_DOCS=true - BUILD_DOCS=true
before_install:
- if [[ ! ("$TRAVIS_PYTHON_VERSION" == "nightly" || "$TRAVIS_PYTHON_VERSION" == "3.6-dev") && ! $BUILD_DOCS ]]; then
URL="https://repo.continuum.io/miniconda/Miniconda3-latest-${MINICONDA_OS}-x86_64.sh";
wget "${URL}" -O miniconda.sh;
bash miniconda.sh -b -p $HOME/miniconda;
export PATH="$HOME/miniconda/bin:$PATH";
hash -r;
conda config --set always_yes yes --set changeps1 no;
conda update -q conda;
conda info -a;
fi
install: install:
- if [[ $BUILD_DOCS = true ]]; then - pip install --upgrade -r requirements/docs.txt
pip install --upgrade -r requirements/docs.txt; - pip install .
pip install .;
else
pip install --upgrade -r requirements/tests.txt;
pip install .;
fi
before_script:
- rvm get head || true
script: script:
- set -ex - set -ex
@ -48,6 +27,4 @@ script:
make clean html; make clean html;
cd ..; cd ..;
doctr deploy --deploy-repo xonsh/xonsh-docs .; doctr deploy --deploy-repo xonsh/xonsh-docs .;
else
xonsh run-tests.xsh test;
fi fi

View file

@ -128,3 +128,7 @@ push-root:
git commit -am "Pushed root-level docs at $(date)" && \ git commit -am "Pushed root-level docs at $(date)" && \
git push git push
serve:
# run as `make serve`
# to not watch file changes, run as `make serve watch=no`
python serve_docs.py $(watch)

View file

@ -9,18 +9,29 @@
# serve to show the default. # serve to show the default.
import os import os
import sys import sys
from collections import OrderedDict
from pathlib import Path
# make current docs directory modules importable
sys.path.append(str(Path(__file__).parent.resolve()))
import builtins import builtins
import inspect import inspect
import importlib import importlib
import typing as tp
os.environ["XONSH_DEBUG"] = "1" os.environ["XONSH_DEBUG"] = "1"
from xonsh import __version__ as XONSH_VERSION from xonsh import __version__ as XONSH_VERSION
from xonsh.environ import DEFAULT_VARS, Env from xonsh.environ import Env, Var, Xettings
if tp.TYPE_CHECKING:
from xonsh.environ import VarKeyType
from xonsh.xontribs_meta import get_xontribs from xonsh.xontribs_meta import get_xontribs
from xonsh import main
from xonsh.commands_cache import CommandsCache from xonsh.commands_cache import CommandsCache
import rst_helpers
if not hasattr(builtins, "__xonsh__"): if not hasattr(builtins, "__xonsh__"):
from argparse import Namespace from argparse import Namespace
@ -57,6 +68,7 @@ extensions = [
"numpydoc", "numpydoc",
"cmdhelp", "cmdhelp",
"runthis.sphinxext", "runthis.sphinxext",
"jinja_rst_ext",
] ]
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
@ -96,7 +108,11 @@ release = XONSH_VERSION
# today_fmt = '%B %d, %Y' # today_fmt = '%B %d, %Y'
# List of documents that shouldn't be included in the build. # List of documents that shouldn't be included in the build.
exclude_patterns = ["api/blank.rst"] exclude_patterns = [
"api/blank.rst",
"_build",
"_static",
]
# List of directories, relative to source directory, that shouldn't be searched # List of directories, relative to source directory, that shouldn't be searched
# for source files. # for source files.
@ -275,48 +291,55 @@ runthis_server = "https://runthis.xonsh.org:80"
# #
def make_envvars(): class VarDoc(tp.NamedTuple):
env = Env() var: Var
vars = sorted(DEFAULT_VARS.keys(), key=lambda x: getattr(x, "pattern", x)) info: tp.Dict[str, tp.Any] # for using in template additional info
s = ".. list-table::\n" " :header-rows: 0\n\n"
table = []
ncol = 3 class EnvVarGroup(tp.NamedTuple):
row = " {0} - :ref:`${1} <{2}>`" vars: tp.Dict["VarKeyType", VarDoc] # sorted variables in the section
for i, varname in enumerate(vars): children: tp.Dict[Xettings, "EnvVarGroup"] # child sections
var = getattr(varname, "pattern", varname)
star = "*" if i % ncol == 0 else " "
table.append(row.format(star, var, var.lower())) def _gather_groups(cls, env: Env, _seen=None):
table.extend([" -"] * ((ncol - len(vars) % ncol) % ncol)) if _seen is None:
s += "\n".join(table) + "\n\n" _seen = set()
s += "Listing\n" "-------\n\n"
sec = ( env_vars = list(cls.get_settings())
".. _{low}:\n\n" env_vars.sort(key=lambda x: getattr(x[0], "pattern", x[0]))
"{title}\n" ordered_vars = OrderedDict() # within that section sort keys
"{under}\n" for key, var in env_vars:
"{docstr}\n\n" var = getattr(key, "pattern", key)
"**configurable:** {configurable}\n\n"
"**default:** {default}\n\n"
"**store_as_str:** {store_as_str}\n\n"
"-------\n\n"
)
for varname in vars:
var = getattr(varname, "pattern", varname)
title = "$" + var title = "$" + var
under = "." * len(title) vd = env.get_docs(key)
vd = env.get_docs(varname) info = dict(
s += sec.format(
low=var.lower(),
title=title, title=title,
under=under,
docstr=vd.doc, docstr=vd.doc,
configurable=vd.doc_configurable, configurable=vd.is_configurable,
default=vd.doc_default, default=vd.doc_default,
store_as_str=vd.doc_store_as_str, store_as_str=vd.can_store_as_str,
) )
s = s[:-9] ordered_vars[key] = VarDoc(var, info)
fname = os.path.join(os.path.dirname(__file__), "envvarsbody")
with open(fname, "w", encoding="utf-8") as f: vargrp = EnvVarGroup(ordered_vars, {})
f.write(s) for sub in cls.__subclasses__():
if sub not in _seen:
_seen.add(sub)
vargrp.children[sub] = _gather_groups(sub, env, _seen)
return vargrp
def make_envvars():
return _gather_groups(Xettings, env=Env())
jinja_contexts = {
# file-name envvars.rst
"envvars": {
"env_vars": make_envvars(),
"rst": rst_helpers,
},
}
def make_xontribs(): def make_xontribs():
@ -424,7 +447,6 @@ def make_events():
f.write(s) f.write(s)
make_envvars()
make_xontribs() make_xontribs()
make_events() make_events()
@ -436,5 +458,5 @@ builtins.__xonsh__.commands_cache = CommandsCache()
def setup(app): def setup(app):
from xonsh.pyghooks import XonshConsoleLexer from xonsh.pyghooks import XonshConsoleLexer
app.add_lexer("xonshcon", XonshConsoleLexer()) app.add_lexer("xonshcon", XonshConsoleLexer)
app.add_css_file("custom.css") app.add_css_file("custom.css")

50
docs/envvars.jinja2 Normal file
View file

@ -0,0 +1,50 @@
{% set ns = namespace(root_index='') %}
{% macro list_table(vars) %}
{% for vardoc in rst.iterator_for_divmod(vars.values()) %}
{% if loop.index0 % 3 == 0 %}* {% else %} {% endif %}- {% if vardoc %}{{ rst.to_ref_string(vardoc.info.title) }}{% endif %}
{% endfor %}
{% endmacro %}
{#titular table#}
{% for cls, envgrp in env_vars.children.items() recursive %}
{% if not loop.depth0 %}
{% set ns.root_index=loop.index %}
{% endif %}
.. list-table:: {{ rst.to_ref_string(cls.get_group_title()) }}
:header-rows: 0
{{ list_table(envgrp.vars)|indent(4) }}
{% if envgrp.children %}{{ loop(envgrp.children.items()) }}{% endif %}
{% endfor %}
{#variables doc#}
{% for cls, envgrp in env_vars.children.items() recursive %}
.. _{{ rst.to_valid_id(cls.get_group_title()) }}:
{{ cls.get_group_title() }}
{{ rst.underline_title(cls.get_group_title(), loop.depth0) }}
{{ cls.get_group_description() }}
{% for key, vardoc in envgrp.vars.items() %}
.. _{{ rst.to_valid_id(vardoc.info.title) }}:
{{ vardoc.info.title }}
{{ rst.underline_title(vardoc.info.title) }}
{{ vardoc.info.docstr }}
**configurable:** {{ vardoc.info.configurable }}
**default:** {{ vardoc.info.default }}
**store_as_str:** {{ vardoc.info.store_as_str }}
-------
{% endfor %}
{% if envgrp.children %}{{ loop(envgrp.children.items()) }}{% endif %}
{% endfor %}

View file

@ -4,4 +4,4 @@ The following displays information about the environment variables that
affect xonsh performance in some way. It also lists their default values, if affect xonsh performance in some way. It also lists their default values, if
applicable. applicable.
.. include:: envvarsbody {{ content }}

46
docs/jinja_rst_ext.py Normal file
View file

@ -0,0 +1,46 @@
"""A sphinx extension to process jinja/rst template"""
# https://www.ericholscher.com/blog/2016/jul/25/integrating-jinja-rst-sphinx/
from pathlib import Path
import jinja2
cur_dir = Path(__file__).parent.resolve()
def rstjinja(app, docname, source):
"""
Render our pages as a jinja template for fancy templating goodness.
"""
# Make sure we're outputting HTML
if app.builder.format != "html":
return
ctx = app.config.jinja_contexts.get(docname)
if ctx is not None:
environment = jinja2.Environment(
trim_blocks=True,
lstrip_blocks=True,
)
src = source[0]
if "content" in src:
file_path = cur_dir / f"{docname}.jinja2"
if file_path.exists():
ctx["content"] = environment.from_string(file_path.read_text()).render(
**ctx
)
rendered = app.builder.templates.render_string(src, ctx)
source[0] = rendered
# for debugging purpose write output
# Path(cur_dir / "_build" / f"{docname}.rst.out").write_text(rendered)
def setup(app):
app.connect("source-read", rstjinja)
# rst files can define the context with their names to be pre-processed with jinja
app.add_config_value("jinja_contexts", {}, "env")

43
docs/rst_helpers.py Normal file
View file

@ -0,0 +1,43 @@
"""ReST-Jinja2 helpers"""
import re
def underline_title(title: str, level: int = -1):
symbols = "-.^"
if level < len(symbols):
under = symbols[level]
else:
under = symbols[-1]
return under * len(title)
def to_valid_id(name: str) -> str:
return re.sub(r"[^\w]", "_", name.lower()).strip("_")
def to_valid_name(name: str) -> str:
return name.replace("`", "")
def indent_depth(depth: int = None):
return " " * ((1 if depth else 0) * 4)
def to_ref_string(title: str, underline=False, depth=-1) -> str:
title = str(title)
ref = f":ref:`{to_valid_name(title)} <{to_valid_id(title)}>`"
if underline:
return "\n".join([ref, underline_title(ref, depth)])
return ref
def iterator_for_divmod(iterable, div: int = 3):
items = list(iterable)
size = len(items)
rem = size % div
complement = size + div - rem
for i in range(complement):
if i < size:
yield items[i]
else:
yield None

15
docs/serve_docs.py Normal file
View file

@ -0,0 +1,15 @@
from livereload import Server, shell
from pathlib import Path
import sys
cur_dir = Path(__file__).parent
server = Server()
if "no" not in sys.argv:
for ext in ("rst", "py", "jinja2"):
server.watch(
str(cur_dir / f"*.{ext}"),
shell("make html", cwd=str(cur_dir)),
)
server.serve(root=str(cur_dir / "_build" / "html"))

View file

@ -0,0 +1,23 @@
**Added:**
* <news item>
**Changed:**
* group environment variables into categories.
**Deprecated:**
* <news item>
**Removed:**
* <news item>
**Fixed:**
* <news item>
**Security:**
* <news item>

View file

@ -11,3 +11,4 @@ tornado
black black
pre-commit pre-commit
runthis-sphinxext runthis-sphinxext
livereload

View file

@ -1,5 +1,5 @@
py py
pytest>=5.4 pytest>=6
flake8 flake8
flake8-docstrings flake8-docstrings
pytest-cov pytest-cov
@ -7,7 +7,7 @@ pytest-timeout
prompt-toolkit>=3.0 prompt-toolkit>=3.0
pygments>=2.2 pygments>=2.2
codecov codecov
coverage coverage>=5.3.1
black==20.8b1 --pre black==20.8b1 --pre
pre-commit pre-commit
mypy==0.790 mypy==0.790

View file

@ -19,6 +19,7 @@ from xonsh.environ import (
make_args_env, make_args_env,
LsColors, LsColors,
default_value, default_value,
Var,
) )
from tools import skip_if_on_unix from tools import skip_if_on_unix
@ -431,12 +432,12 @@ def test_register_var_path():
env = Env() env = Env()
env.register("MY_PATH_VAR", type="path") env.register("MY_PATH_VAR", type="path")
path = '/tmp' path = "/tmp"
env["MY_PATH_VAR"] = path env["MY_PATH_VAR"] = path
assert env["MY_PATH_VAR"] == pathlib.Path(path) assert env["MY_PATH_VAR"] == pathlib.Path(path)
# Empty string is None to avoid uncontrolled converting empty string to Path('.') # Empty string is None to avoid uncontrolled converting empty string to Path('.')
path = '' path = ""
env["MY_PATH_VAR"] = path env["MY_PATH_VAR"] = path
assert env["MY_PATH_VAR"] == None assert env["MY_PATH_VAR"] == None
@ -518,8 +519,7 @@ def test_env_iterate_rawkeys():
def test_env_get_defaults(): def test_env_get_defaults():
"""Verify the rather complex rules for env.get("<envvar>",default) value when envvar is not defined. """Verify the rather complex rules for env.get("<envvar>",default) value when envvar is not defined."""
"""
env = Env(TEST1=0) env = Env(TEST1=0)
env.register("TEST_REG", default="abc") env.register("TEST_REG", default="abc")
@ -536,3 +536,16 @@ def test_env_get_defaults():
# var not defined, is registered, reg default is sentinel => value is *immediate* default # var not defined, is registered, reg default is sentinel => value is *immediate* default
assert env.get("TEST_REG_DNG", 22) == 22 assert env.get("TEST_REG_DNG", 22) == 22
assert "TEST_REG_DNG" not in env assert "TEST_REG_DNG" not in env
@pytest.mark.parametrize(
"val,validator",
[
("string", "is_string"),
(1, "is_int"),
(0.5, "is_float"),
],
)
def test_var_with_default_initer(val, validator):
var = Var.with_default(val)
assert var.validate.__name__ == validator

File diff suppressed because it is too large Load diff

View file

@ -301,7 +301,7 @@ def make_envvar(name):
"""Makes a StoreNonEmpty node for an environment variable.""" """Makes a StoreNonEmpty node for an environment variable."""
env = builtins.__xonsh__.env env = builtins.__xonsh__.env
vd = env.get_docs(name) vd = env.get_docs(name)
if not vd.doc_configurable: if not vd.is_configurable:
return return
default = vd.doc_default default = vd.doc_default
if "\n" in default: if "\n" in default:
@ -327,7 +327,7 @@ def make_envvar(name):
show_conversion=True, show_conversion=True,
path=path, path=path,
retry=True, retry=True,
store_raw=vd.doc_store_as_str, store_raw=vd.can_store_as_str,
) )
return mnode, pnode return mnode, pnode