/
utils.py
131 lines (107 loc) · 3.77 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""
Utilities for managing configuration.
"""
from __future__ import annotations
import os
import re
import sys
import typing as T
import unicodedata
from pathlib import Path
import toml
from jinja2 import Environment, FileSystemLoader, Template
from mapyde import cards, data, likelihoods, scripts, templates
from mapyde.typing import ImmutableConfig, MutableConfig
# importlib.resources.as_file wasn't added until Python 3.9
# c.f. https://docs.python.org/3.9/library/importlib.html#importlib.resources.as_file
if sys.version_info >= (3, 9):
from importlib import resources
else:
import importlib_resources as resources
def merge(
left: MutableConfig, right: ImmutableConfig, path: T.Optional[list[str]] = None
) -> ImmutableConfig:
"""
merges right dictionary into left dictionary
"""
if path is None:
path = []
for key in right:
if key in left:
if isinstance(left[key], dict) and isinstance(right[key], dict):
merge(left[key], right[key], path + [str(key)])
else:
left[key] = right[key]
else:
left[key] = right[key]
return left
def render_string(blob: str, variables: T.Optional[ImmutableConfig] = None) -> str:
"""
Render a string using various variables set by the mapyde package.
"""
variables = variables or {}
tpl = Template(blob)
return tpl.render(
PWD=os.getenv("PWD"),
USER=os.getenv("USER"),
MAPYDE_DATA=data,
MAPYDE_CARDS=cards,
MAPYDE_LIKELIHOODS=likelihoods,
MAPYDE_SCRIPTS=scripts,
MAPYDE_TEMPLATES=templates,
**variables,
)
def env_override(value: T.Any, key: str) -> T.Any:
"""
Helper function for jinja2 to override environment variables
"""
return os.getenv(key, value)
def load_config(filename: str, cwd: str = ".") -> T.Any:
"""
Helper function to load a local toml configuration by filename
"""
env = Environment(loader=FileSystemLoader(cwd))
env.filters["env_override"] = env_override
tpl = env.get_template(filename)
assert tpl.filename
return toml.load(open(tpl.filename, encoding="utf-8"))
def build_config(user: MutableConfig) -> T.Any:
"""
Function to build a configuration from a user-provided toml configuration on top of the base/template one.
"""
template_path = Path(
render_string(
user["base"].get("template", "{{MAPYDE_TEMPLATES}}/defaults.toml")
)
)
with resources.as_file(template_path) as template:
if not template.exists():
raise OSError(f"{template_path} does not exist.")
defaults = load_config(template.name, str(template.parent))
variables = merge(defaults, user)
config = toml.loads(render_string(toml.dumps(variables), variables))
return config
def output_path(config: ImmutableConfig) -> Path:
"""
Return the output path from the config.
"""
return Path(config["base"]["path"]).joinpath(config["base"]["output"]).resolve()
def slugify(value: str, allow_unicode: bool = False) -> str:
"""
Taken from https://github.com/django/django/blob/master/django/utils/text.py
Convert to ASCII if 'allow_unicode' is False. Convert spaces or repeated
dashes to single dashes. Remove characters that aren't alphanumerics,
underscores, or hyphens. Convert to lowercase. Also strip leading and
trailing whitespace, dashes, and underscores.
"""
value = str(value)
if allow_unicode:
value = unicodedata.normalize("NFKC", value)
else:
value = (
unicodedata.normalize("NFKD", value)
.encode("ascii", "ignore")
.decode("ascii")
)
value = re.sub(r"[^\w\s-]", "", value.lower())
return re.sub(r"[-\s]+", "-", value).strip("-_")