omlish 0.0.0.dev193__py3-none-any.whl → 0.0.0.dev194__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omlish/__about__.py +2 -2
- omlish/configs/formats.py +247 -0
- omlish/configs/nginx.py +72 -0
- omlish/configs/processing/__init__.py +0 -0
- omlish/configs/{flattening.py → processing/flattening.py} +1 -1
- omlish/configs/processing/inheritance.py +58 -0
- omlish/configs/processing/matching.py +53 -0
- omlish/configs/processing/names.py +50 -0
- omlish/configs/processing/rewriting.py +141 -0
- omlish/configs/processing/strings.py +45 -0
- omlish/configs/types.py +9 -0
- omlish/formats/__init__.py +0 -1
- omlish/formats/toml/writer.py +9 -0
- omlish/lite/configs.py +38 -0
- omlish/text/glyphsplit.py +13 -0
- {omlish-0.0.0.dev193.dist-info → omlish-0.0.0.dev194.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev193.dist-info → omlish-0.0.0.dev194.dist-info}/RECORD +21 -12
- omlish/configs/strings.py +0 -96
- {omlish-0.0.0.dev193.dist-info → omlish-0.0.0.dev194.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev193.dist-info → omlish-0.0.0.dev194.dist-info}/WHEEL +0 -0
- {omlish-0.0.0.dev193.dist-info → omlish-0.0.0.dev194.dist-info}/entry_points.txt +0 -0
- {omlish-0.0.0.dev193.dist-info → omlish-0.0.0.dev194.dist-info}/top_level.txt +0 -0
omlish/__about__.py
CHANGED
@@ -0,0 +1,247 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
Notes:
|
5
|
+
- necessarily string-oriented
|
6
|
+
- single file, as this is intended to be amalg'd and thus all included anyway
|
7
|
+
|
8
|
+
TODO:
|
9
|
+
- ConfigDataMapper? to_map -> ConfigMap?
|
10
|
+
- nginx ?
|
11
|
+
- raw ?
|
12
|
+
"""
|
13
|
+
import abc
|
14
|
+
import collections.abc
|
15
|
+
import configparser
|
16
|
+
import dataclasses as dc
|
17
|
+
import json
|
18
|
+
import os.path
|
19
|
+
import typing as ta
|
20
|
+
|
21
|
+
from ..formats.ini.sections import IniSectionSettingsMap
|
22
|
+
from ..formats.ini.sections import extract_ini_sections
|
23
|
+
from ..formats.ini.sections import render_ini_sections
|
24
|
+
from ..formats.toml.parser import toml_loads
|
25
|
+
from ..formats.toml.writer import TomlWriter
|
26
|
+
from ..lite.check import check
|
27
|
+
from ..lite.json import json_dumps_pretty
|
28
|
+
from .types import ConfigMap
|
29
|
+
|
30
|
+
|
31
|
+
T = ta.TypeVar('T')
|
32
|
+
ConfigDataT = ta.TypeVar('ConfigDataT', bound='ConfigData')
|
33
|
+
|
34
|
+
|
35
|
+
##
|
36
|
+
|
37
|
+
|
38
|
+
@dc.dataclass(frozen=True)
|
39
|
+
class ConfigData(abc.ABC): # noqa
|
40
|
+
@abc.abstractmethod
|
41
|
+
def as_map(self) -> ConfigMap:
|
42
|
+
raise NotImplementedError
|
43
|
+
|
44
|
+
|
45
|
+
#
|
46
|
+
|
47
|
+
|
48
|
+
class ConfigLoader(abc.ABC, ta.Generic[ConfigDataT]):
|
49
|
+
@property
|
50
|
+
def file_exts(self) -> ta.Sequence[str]:
|
51
|
+
return ()
|
52
|
+
|
53
|
+
def match_file(self, n: str) -> bool:
|
54
|
+
return '.' in n and n.split('.')[-1] in check.not_isinstance(self.file_exts, str)
|
55
|
+
|
56
|
+
#
|
57
|
+
|
58
|
+
def load_file(self, p: str) -> ConfigDataT:
|
59
|
+
with open(p) as f:
|
60
|
+
return self.load_str(f.read())
|
61
|
+
|
62
|
+
@abc.abstractmethod
|
63
|
+
def load_str(self, s: str) -> ConfigDataT:
|
64
|
+
raise NotImplementedError
|
65
|
+
|
66
|
+
|
67
|
+
#
|
68
|
+
|
69
|
+
|
70
|
+
class ConfigRenderer(abc.ABC, ta.Generic[ConfigDataT]):
|
71
|
+
@property
|
72
|
+
@abc.abstractmethod
|
73
|
+
def data_cls(self) -> ta.Type[ConfigDataT]:
|
74
|
+
raise NotImplementedError
|
75
|
+
|
76
|
+
def match_data(self, d: ConfigDataT) -> bool:
|
77
|
+
return isinstance(d, self.data_cls)
|
78
|
+
|
79
|
+
#
|
80
|
+
|
81
|
+
@abc.abstractmethod
|
82
|
+
def render(self, d: ConfigDataT) -> str:
|
83
|
+
raise NotImplementedError
|
84
|
+
|
85
|
+
|
86
|
+
##
|
87
|
+
|
88
|
+
|
89
|
+
@dc.dataclass(frozen=True)
|
90
|
+
class ObjConfigData(ConfigData, abc.ABC):
|
91
|
+
obj: ta.Any
|
92
|
+
|
93
|
+
def as_map(self) -> ConfigMap:
|
94
|
+
return check.isinstance(self.obj, collections.abc.Mapping)
|
95
|
+
|
96
|
+
|
97
|
+
##
|
98
|
+
|
99
|
+
|
100
|
+
@dc.dataclass(frozen=True)
|
101
|
+
class JsonConfigData(ObjConfigData):
|
102
|
+
pass
|
103
|
+
|
104
|
+
|
105
|
+
class JsonConfigLoader(ConfigLoader[JsonConfigData]):
|
106
|
+
file_exts = ('json',)
|
107
|
+
|
108
|
+
def load_str(self, s: str) -> JsonConfigData:
|
109
|
+
return JsonConfigData(json.loads(s))
|
110
|
+
|
111
|
+
|
112
|
+
class JsonConfigRenderer(ConfigRenderer[JsonConfigData]):
|
113
|
+
data_cls = JsonConfigData
|
114
|
+
|
115
|
+
def render(self, d: JsonConfigData) -> str:
|
116
|
+
return json_dumps_pretty(d.obj)
|
117
|
+
|
118
|
+
|
119
|
+
##
|
120
|
+
|
121
|
+
|
122
|
+
@dc.dataclass(frozen=True)
|
123
|
+
class TomlConfigData(ObjConfigData):
|
124
|
+
pass
|
125
|
+
|
126
|
+
|
127
|
+
class TomlConfigLoader(ConfigLoader[TomlConfigData]):
|
128
|
+
file_exts = ('toml',)
|
129
|
+
|
130
|
+
def load_str(self, s: str) -> TomlConfigData:
|
131
|
+
return TomlConfigData(toml_loads(s))
|
132
|
+
|
133
|
+
|
134
|
+
class TomlConfigRenderer(ConfigRenderer[TomlConfigData]):
|
135
|
+
data_cls = TomlConfigData
|
136
|
+
|
137
|
+
def render(self, d: TomlConfigData) -> str:
|
138
|
+
return TomlWriter.write_str(d.obj)
|
139
|
+
|
140
|
+
|
141
|
+
##
|
142
|
+
|
143
|
+
|
144
|
+
@dc.dataclass(frozen=True)
|
145
|
+
class YamlConfigData(ObjConfigData):
|
146
|
+
pass
|
147
|
+
|
148
|
+
|
149
|
+
class YamlConfigLoader(ConfigLoader[YamlConfigData]):
|
150
|
+
file_exts = ('yaml', 'yml')
|
151
|
+
|
152
|
+
def load_str(self, s: str) -> YamlConfigData:
|
153
|
+
return YamlConfigData(__import__('yaml').safe_load(s))
|
154
|
+
|
155
|
+
|
156
|
+
class YamlConfigRenderer(ConfigRenderer[YamlConfigData]):
|
157
|
+
data_cls = YamlConfigData
|
158
|
+
|
159
|
+
def render(self, d: YamlConfigData) -> str:
|
160
|
+
return __import__('yaml').safe_dump(d.obj)
|
161
|
+
|
162
|
+
|
163
|
+
##
|
164
|
+
|
165
|
+
|
166
|
+
@dc.dataclass(frozen=True)
|
167
|
+
class IniConfigData(ConfigData):
|
168
|
+
sections: IniSectionSettingsMap
|
169
|
+
|
170
|
+
def as_map(self) -> ConfigMap:
|
171
|
+
return self.sections
|
172
|
+
|
173
|
+
|
174
|
+
class IniConfigLoader(ConfigLoader[IniConfigData]):
|
175
|
+
file_exts = ('ini',)
|
176
|
+
|
177
|
+
def load_str(self, s: str) -> IniConfigData:
|
178
|
+
cp = configparser.ConfigParser()
|
179
|
+
cp.read_string(s)
|
180
|
+
return IniConfigData(extract_ini_sections(cp))
|
181
|
+
|
182
|
+
|
183
|
+
class IniConfigRenderer(ConfigRenderer[IniConfigData]):
|
184
|
+
data_cls = IniConfigData
|
185
|
+
|
186
|
+
def render(self, d: IniConfigData) -> str:
|
187
|
+
return render_ini_sections(d.sections)
|
188
|
+
|
189
|
+
|
190
|
+
##
|
191
|
+
|
192
|
+
|
193
|
+
@dc.dataclass(frozen=True)
|
194
|
+
class SwitchedConfigFileLoader:
|
195
|
+
loaders: ta.Sequence[ConfigLoader]
|
196
|
+
default: ta.Optional[ConfigLoader] = None
|
197
|
+
|
198
|
+
def load_file(self, p: str) -> ConfigData:
|
199
|
+
n = os.path.basename(p)
|
200
|
+
|
201
|
+
for l in self.loaders:
|
202
|
+
if l.match_file(n):
|
203
|
+
return l.load_file(p)
|
204
|
+
|
205
|
+
if (d := self.default) is not None:
|
206
|
+
return d.load_file(p)
|
207
|
+
|
208
|
+
raise NameError(n)
|
209
|
+
|
210
|
+
|
211
|
+
DEFAULT_CONFIG_LOADERS: ta.Sequence[ConfigLoader] = [
|
212
|
+
JsonConfigLoader(),
|
213
|
+
TomlConfigLoader(),
|
214
|
+
YamlConfigLoader(),
|
215
|
+
IniConfigLoader(),
|
216
|
+
]
|
217
|
+
|
218
|
+
DEFAULT_CONFIG_LOADER: ConfigLoader = JsonConfigLoader()
|
219
|
+
|
220
|
+
DEFAULT_CONFIG_FILE_LOADER = SwitchedConfigFileLoader(
|
221
|
+
loaders=DEFAULT_CONFIG_LOADERS,
|
222
|
+
default=DEFAULT_CONFIG_LOADER,
|
223
|
+
)
|
224
|
+
|
225
|
+
|
226
|
+
##
|
227
|
+
|
228
|
+
|
229
|
+
@dc.dataclass(frozen=True)
|
230
|
+
class SwitchedConfigRenderer:
|
231
|
+
renderers: ta.Sequence[ConfigRenderer]
|
232
|
+
|
233
|
+
def render(self, d: ConfigData) -> str:
|
234
|
+
for r in self.renderers:
|
235
|
+
if r.match_data(d):
|
236
|
+
return r.render(d)
|
237
|
+
raise TypeError(d)
|
238
|
+
|
239
|
+
|
240
|
+
DEFAULT_CONFIG_RENDERERS: ta.Sequence[ConfigRenderer] = [
|
241
|
+
JsonConfigRenderer(),
|
242
|
+
TomlConfigRenderer(),
|
243
|
+
YamlConfigRenderer(),
|
244
|
+
IniConfigRenderer(),
|
245
|
+
]
|
246
|
+
|
247
|
+
DEFAULT_CONFIG_RENDERER = SwitchedConfigRenderer(DEFAULT_CONFIG_RENDERERS)
|
omlish/configs/nginx.py
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
See:
|
5
|
+
- https://nginx.org/en/docs/dev/development_guide.html
|
6
|
+
- https://nginx.org/en/docs/dev/development_guide.html#config_directives
|
7
|
+
- https://nginx.org/en/docs/example.html
|
8
|
+
"""
|
9
|
+
import collections.abc
|
10
|
+
import dataclasses as dc
|
11
|
+
import typing as ta
|
12
|
+
|
13
|
+
from ..lite.check import check
|
14
|
+
from ..text.indent import IndentWriter
|
15
|
+
|
16
|
+
|
17
|
+
@dc.dataclass()
|
18
|
+
class NginxConfigItems:
|
19
|
+
lst: ta.List['NginxConfigItem']
|
20
|
+
|
21
|
+
@classmethod
|
22
|
+
def of(cls, obj: ta.Any) -> 'NginxConfigItems':
|
23
|
+
if isinstance(obj, NginxConfigItems):
|
24
|
+
return obj
|
25
|
+
return cls([NginxConfigItem.of(e) for e in check.isinstance(obj, list)])
|
26
|
+
|
27
|
+
|
28
|
+
@dc.dataclass()
|
29
|
+
class NginxConfigItem:
|
30
|
+
name: str
|
31
|
+
args: ta.Optional[ta.List[str]] = None
|
32
|
+
block: ta.Optional[NginxConfigItems] = None
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
def of(cls, obj: ta.Any) -> 'NginxConfigItem':
|
36
|
+
if isinstance(obj, NginxConfigItem):
|
37
|
+
return obj
|
38
|
+
args = check.isinstance(check.not_isinstance(obj, str), collections.abc.Sequence)
|
39
|
+
name, args = check.isinstance(args[0], str), args[1:]
|
40
|
+
if args and not isinstance(args[-1], str):
|
41
|
+
block, args = NginxConfigItems.of(args[-1]), args[:-1]
|
42
|
+
else:
|
43
|
+
block = None
|
44
|
+
return NginxConfigItem(name, [check.isinstance(e, str) for e in args], block=block)
|
45
|
+
|
46
|
+
|
47
|
+
def render_nginx_config(wr: IndentWriter, obj: ta.Any) -> None:
|
48
|
+
if isinstance(obj, NginxConfigItem):
|
49
|
+
wr.write(obj.name)
|
50
|
+
for e in obj.args or ():
|
51
|
+
wr.write(' ')
|
52
|
+
wr.write(e)
|
53
|
+
if obj.block:
|
54
|
+
wr.write(' {\n')
|
55
|
+
with wr.indent():
|
56
|
+
render_nginx_config(wr, obj.block)
|
57
|
+
wr.write('}\n')
|
58
|
+
else:
|
59
|
+
wr.write(';\n')
|
60
|
+
|
61
|
+
elif isinstance(obj, NginxConfigItems):
|
62
|
+
for e2 in obj.lst:
|
63
|
+
render_nginx_config(wr, e2)
|
64
|
+
|
65
|
+
else:
|
66
|
+
raise TypeError(obj)
|
67
|
+
|
68
|
+
|
69
|
+
def render_nginx_config_str(obj: ta.Any) -> str:
|
70
|
+
iw = IndentWriter()
|
71
|
+
render_nginx_config(iw, obj)
|
72
|
+
return iw.getvalue()
|
File without changes
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- custom merging by key - replace, append, setdefault, equality enforcement, ...
|
6
|
+
- better handling of 'None' as signal to replace or not
|
7
|
+
- remove inherited
|
8
|
+
"""
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from ..types import ConfigMap
|
12
|
+
|
13
|
+
|
14
|
+
##
|
15
|
+
|
16
|
+
|
17
|
+
def build_config_inherited_values(
|
18
|
+
m: ta.Mapping[str, ConfigMap],
|
19
|
+
*,
|
20
|
+
inherits_key: str = 'inherits',
|
21
|
+
) -> ta.Dict[str, ConfigMap]:
|
22
|
+
done: ta.Dict[str, ConfigMap] = {}
|
23
|
+
|
24
|
+
def rec(mk: str) -> ta.Mapping[str, ConfigMap]:
|
25
|
+
try:
|
26
|
+
return done[mk]
|
27
|
+
except KeyError:
|
28
|
+
pass
|
29
|
+
|
30
|
+
cur = m[mk]
|
31
|
+
try:
|
32
|
+
inh_seq = cur[inherits_key]
|
33
|
+
except KeyError:
|
34
|
+
done[mk] = cur
|
35
|
+
return cur
|
36
|
+
|
37
|
+
new = dict(cur)
|
38
|
+
for inh in inh_seq:
|
39
|
+
inh_dct = rec(inh)
|
40
|
+
new.update({
|
41
|
+
k: v
|
42
|
+
for k, v in inh_dct.items()
|
43
|
+
if v is not None
|
44
|
+
and cur.get(k) is None
|
45
|
+
})
|
46
|
+
|
47
|
+
out = {
|
48
|
+
k: v
|
49
|
+
for k, v in new.items()
|
50
|
+
if k != inherits_key
|
51
|
+
}
|
52
|
+
|
53
|
+
done[mk] = out
|
54
|
+
return out
|
55
|
+
|
56
|
+
for mk in m:
|
57
|
+
rec(mk)
|
58
|
+
return done
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import dataclasses as dc
|
4
|
+
import functools
|
5
|
+
import typing as ta
|
6
|
+
|
7
|
+
from ...lite.check import check
|
8
|
+
from .rewriting import ConfigRewriter
|
9
|
+
from .rewriting import ConfigRewriterPath
|
10
|
+
|
11
|
+
|
12
|
+
T = ta.TypeVar('T')
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
|
17
|
+
|
18
|
+
class MatchingConfigRewriter(ConfigRewriter):
|
19
|
+
def __init__(
|
20
|
+
self,
|
21
|
+
fn: ta.Callable[[ta.Any], ta.Any],
|
22
|
+
paths: ta.Iterable[ConfigRewriterPath],
|
23
|
+
*,
|
24
|
+
recurse: bool = False,
|
25
|
+
) -> None:
|
26
|
+
super().__init__()
|
27
|
+
|
28
|
+
self._fn = fn
|
29
|
+
self._paths = frozenset(check.isinstance(p, tuple) for p in paths)
|
30
|
+
self._recurse = recurse
|
31
|
+
|
32
|
+
def rewrite(self, ctx: ConfigRewriter.Context[T]) -> T:
|
33
|
+
if ctx.path in self._paths:
|
34
|
+
no = self._fn(ctx.obj)
|
35
|
+
if not self._recurse:
|
36
|
+
return no
|
37
|
+
ctx = dc.replace(ctx, obj=no)
|
38
|
+
|
39
|
+
return super().rewrite(ctx)
|
40
|
+
|
41
|
+
|
42
|
+
def matched_config_rewrite(
|
43
|
+
fn: ta.Callable[[ta.Any], ta.Any],
|
44
|
+
obj: T,
|
45
|
+
*paths: ConfigRewriterPath,
|
46
|
+
recurse: bool = False,
|
47
|
+
**kwargs: ta.Any,
|
48
|
+
) -> T:
|
49
|
+
return MatchingConfigRewriter(
|
50
|
+
functools.partial(fn, **kwargs),
|
51
|
+
paths,
|
52
|
+
recurse=recurse,
|
53
|
+
)(obj)
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
usecase: supervisor process groups
|
5
|
+
"""
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from ...lite.check import check
|
9
|
+
from ..types import ConfigMap
|
10
|
+
|
11
|
+
|
12
|
+
##
|
13
|
+
|
14
|
+
|
15
|
+
def build_config_named_children(
|
16
|
+
o: ta.Union[
|
17
|
+
ta.Sequence[ConfigMap],
|
18
|
+
ta.Mapping[str, ConfigMap],
|
19
|
+
None,
|
20
|
+
],
|
21
|
+
*,
|
22
|
+
name_key: str = 'name',
|
23
|
+
) -> ta.Optional[ta.Sequence[ConfigMap]]:
|
24
|
+
if o is None:
|
25
|
+
return None
|
26
|
+
|
27
|
+
lst: ta.List[ConfigMap] = []
|
28
|
+
if isinstance(o, ta.Mapping):
|
29
|
+
for k, v in o.items():
|
30
|
+
check.isinstance(v, ta.Mapping)
|
31
|
+
if name_key in v:
|
32
|
+
n = v[name_key]
|
33
|
+
if k != n:
|
34
|
+
raise KeyError(f'Given names do not match: {n} != {k}')
|
35
|
+
lst.append(v)
|
36
|
+
else:
|
37
|
+
lst.append({name_key: k, **v})
|
38
|
+
|
39
|
+
else:
|
40
|
+
check.not_isinstance(o, str)
|
41
|
+
lst.extend(o)
|
42
|
+
|
43
|
+
seen = set()
|
44
|
+
for d in lst:
|
45
|
+
n = d['name']
|
46
|
+
if n in d:
|
47
|
+
raise KeyError(f'Duplicate name: {n}')
|
48
|
+
seen.add(n)
|
49
|
+
|
50
|
+
return lst
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
import abc
|
4
|
+
import collections.abc
|
5
|
+
import dataclasses as dc
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from ...lite.check import check
|
9
|
+
from ...lite.types import BUILTIN_SCALAR_ITERABLE_TYPES
|
10
|
+
|
11
|
+
|
12
|
+
T = ta.TypeVar('T')
|
13
|
+
StrT = ta.TypeVar('StrT', bound=str)
|
14
|
+
MappingT = ta.TypeVar('MappingT', bound=ta.Mapping)
|
15
|
+
IterableT = ta.TypeVar('IterableT', bound=ta.Iterable)
|
16
|
+
|
17
|
+
ConfigRewriterItem = ta.Union[int, str, None] # ta.TypeAlias
|
18
|
+
ConfigRewriterPath = ta.Tuple[ConfigRewriterItem, ...] # ta.TypeAlias
|
19
|
+
|
20
|
+
|
21
|
+
##
|
22
|
+
|
23
|
+
|
24
|
+
class RawConfigMetadata:
|
25
|
+
def __new__(cls, *args, **kwargs): # noqa
|
26
|
+
raise TypeError
|
27
|
+
|
28
|
+
|
29
|
+
class ConfigRewriter(abc.ABC): # noqa
|
30
|
+
@dc.dataclass(frozen=True)
|
31
|
+
class Context(ta.Generic[T]):
|
32
|
+
obj: T
|
33
|
+
|
34
|
+
parent: ta.Optional['ConfigRewriter.Context'] = None
|
35
|
+
item: ConfigRewriterItem = None
|
36
|
+
|
37
|
+
raw: bool = False
|
38
|
+
|
39
|
+
def __post_init__(self) -> None:
|
40
|
+
if self.parent is None:
|
41
|
+
check.none(self.item)
|
42
|
+
|
43
|
+
@property
|
44
|
+
def path(self) -> ConfigRewriterPath:
|
45
|
+
cur: ConfigRewriter.Context = self
|
46
|
+
lst: ta.List[ConfigRewriterItem] = []
|
47
|
+
while True:
|
48
|
+
if cur.parent is None:
|
49
|
+
break
|
50
|
+
lst.append(cur.item)
|
51
|
+
cur = cur.parent
|
52
|
+
return tuple(reversed(lst))
|
53
|
+
|
54
|
+
def make_child(
|
55
|
+
self,
|
56
|
+
obj: ta.Any,
|
57
|
+
item: ConfigRewriterItem,
|
58
|
+
**kwargs: ta.Any,
|
59
|
+
) -> 'ConfigRewriter.Context':
|
60
|
+
return dc.replace(
|
61
|
+
self,
|
62
|
+
obj=obj,
|
63
|
+
parent=self,
|
64
|
+
item=item,
|
65
|
+
**kwargs,
|
66
|
+
)
|
67
|
+
|
68
|
+
def rewrite_none(self, ctx: Context[None]) -> ta.Any:
|
69
|
+
return ctx.obj
|
70
|
+
|
71
|
+
def rewrite_dataclass(self, ctx: Context[T]) -> T:
|
72
|
+
kw = {}
|
73
|
+
for f in dc.fields(ctx.obj): # type: ignore
|
74
|
+
fv = getattr(ctx.obj, f.name)
|
75
|
+
nfv: ta.Any = self.rewrite(ctx.make_child(
|
76
|
+
fv,
|
77
|
+
f.name,
|
78
|
+
raw=bool(not f.metadata.get(RawConfigMetadata)),
|
79
|
+
))
|
80
|
+
if fv is not nfv:
|
81
|
+
kw[f.name] = nfv
|
82
|
+
if not kw:
|
83
|
+
return ctx.obj
|
84
|
+
return dc.replace(ctx.obj, **kw) # type: ignore
|
85
|
+
|
86
|
+
def rewrite_str(self, ctx: Context[StrT]) -> StrT:
|
87
|
+
return ctx.obj
|
88
|
+
|
89
|
+
def rewrite_builtin_scalar_iterable(self, ctx: Context[IterableT]) -> IterableT:
|
90
|
+
return ctx.obj
|
91
|
+
|
92
|
+
def rewrite_mapping(self, ctx: Context[MappingT]) -> MappingT:
|
93
|
+
nm = []
|
94
|
+
b = False
|
95
|
+
for mk, mv in ctx.obj.items():
|
96
|
+
nk: ta.Any = self.rewrite(ctx.make_child(mk, None))
|
97
|
+
nv: ta.Any = self.rewrite(ctx.make_child(mv, nk))
|
98
|
+
nm.append((nk, nv))
|
99
|
+
b |= nk is not mk or nv is not mv
|
100
|
+
if not b:
|
101
|
+
return ctx.obj
|
102
|
+
return type(ctx.obj)(nm) # type: ignore
|
103
|
+
|
104
|
+
def rewrite_iterable(self, ctx: Context[IterableT]) -> IterableT:
|
105
|
+
nl = []
|
106
|
+
b = False
|
107
|
+
for i, le in enumerate(ctx.obj):
|
108
|
+
ne: ta.Any = self.rewrite(ctx.make_child(le, i))
|
109
|
+
nl.append(ne)
|
110
|
+
b |= ne is not le
|
111
|
+
if not b:
|
112
|
+
return ctx.obj
|
113
|
+
return type(ctx.obj)(nl) # type: ignore
|
114
|
+
|
115
|
+
def rewrite_other(self, ctx: Context[T]) -> T:
|
116
|
+
return ctx.obj
|
117
|
+
|
118
|
+
def rewrite(self, ctx: Context[T]) -> T:
|
119
|
+
if ctx.obj is None:
|
120
|
+
return self.rewrite_none(ctx) # type: ignore
|
121
|
+
|
122
|
+
elif dc.is_dataclass(ctx.obj):
|
123
|
+
return self.rewrite_dataclass(ctx)
|
124
|
+
|
125
|
+
elif isinstance(ctx.obj, str):
|
126
|
+
return self.rewrite_str(ctx) # type: ignore
|
127
|
+
|
128
|
+
elif isinstance(ctx.obj, BUILTIN_SCALAR_ITERABLE_TYPES):
|
129
|
+
return self.rewrite_builtin_scalar_iterable(ctx) # type: ignore
|
130
|
+
|
131
|
+
elif isinstance(ctx.obj, collections.abc.Mapping):
|
132
|
+
return self.rewrite_mapping(ctx) # type: ignore
|
133
|
+
|
134
|
+
elif isinstance(ctx.obj, (collections.abc.Sequence, collections.abc.Set)):
|
135
|
+
return self.rewrite_iterable(ctx) # type: ignore
|
136
|
+
|
137
|
+
else:
|
138
|
+
return self.rewrite_other(ctx)
|
139
|
+
|
140
|
+
def __call__(self, v: T) -> T:
|
141
|
+
return self.rewrite(self.Context(obj=v))
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
# @omlish-lite
|
3
|
+
"""
|
4
|
+
TODO:
|
5
|
+
- env vars
|
6
|
+
- coalescing - {$FOO|$BAR|baz}
|
7
|
+
- minja?
|
8
|
+
"""
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from .rewriting import ConfigRewriter
|
12
|
+
|
13
|
+
|
14
|
+
T = ta.TypeVar('T')
|
15
|
+
StrT = ta.TypeVar('StrT', bound=str)
|
16
|
+
|
17
|
+
|
18
|
+
##
|
19
|
+
|
20
|
+
|
21
|
+
class StringConfigRewriter(ConfigRewriter):
|
22
|
+
def __init__(
|
23
|
+
self,
|
24
|
+
fn: ta.Optional[ta.Callable[[str], str]] = None,
|
25
|
+
) -> None:
|
26
|
+
super().__init__()
|
27
|
+
|
28
|
+
self._fn = fn
|
29
|
+
|
30
|
+
def rewrite_str(self, ctx: ConfigRewriter.Context[StrT]) -> StrT:
|
31
|
+
v = ctx.obj
|
32
|
+
if (fn := self._fn) is not None:
|
33
|
+
if not ctx.raw:
|
34
|
+
v = fn(v) # type: ignore
|
35
|
+
return type(v)(v)
|
36
|
+
|
37
|
+
|
38
|
+
##
|
39
|
+
|
40
|
+
|
41
|
+
def format_config_strings(v: T, rpl: ta.Mapping[str, str]) -> T:
|
42
|
+
def fn(v):
|
43
|
+
return v.format(**rpl)
|
44
|
+
|
45
|
+
return StringConfigRewriter(fn)(v)
|
omlish/configs/types.py
ADDED
omlish/formats/toml/writer.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# @omlish-lite
|
2
2
|
import dataclasses as dc
|
3
|
+
import io
|
3
4
|
import string
|
4
5
|
import typing as ta
|
5
6
|
|
@@ -113,3 +114,11 @@ class TomlWriter:
|
|
113
114
|
self.write_array(obj)
|
114
115
|
else:
|
115
116
|
raise TypeError(obj)
|
117
|
+
|
118
|
+
#
|
119
|
+
|
120
|
+
@classmethod
|
121
|
+
def write_str(cls, obj: ta.Any) -> str:
|
122
|
+
out = io.StringIO()
|
123
|
+
cls(out).write_value(obj)
|
124
|
+
return out.getvalue()
|
omlish/lite/configs.py
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import typing as ta
|
3
|
+
|
4
|
+
from ..configs.formats import DEFAULT_CONFIG_FILE_LOADER
|
5
|
+
from ..configs.types import ConfigMap
|
6
|
+
from .marshal import OBJ_MARSHALER_MANAGER
|
7
|
+
from .marshal import ObjMarshalerManager
|
8
|
+
|
9
|
+
|
10
|
+
T = ta.TypeVar('T')
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
def load_config_file_obj(
|
17
|
+
f: str,
|
18
|
+
cls: ta.Type[T],
|
19
|
+
*,
|
20
|
+
prepare: ta.Union[
|
21
|
+
ta.Callable[[ConfigMap], ConfigMap],
|
22
|
+
ta.Iterable[ta.Callable[[ConfigMap], ConfigMap]],
|
23
|
+
] = (),
|
24
|
+
msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
|
25
|
+
) -> T:
|
26
|
+
config_data = DEFAULT_CONFIG_FILE_LOADER.load_file(f)
|
27
|
+
|
28
|
+
config_dct = config_data.as_map()
|
29
|
+
|
30
|
+
if prepare is not None:
|
31
|
+
if isinstance(prepare, ta.Iterable):
|
32
|
+
pfs = list(prepare)
|
33
|
+
else:
|
34
|
+
pfs = [prepare]
|
35
|
+
for pf in pfs:
|
36
|
+
config_dct = pf(config_dct)
|
37
|
+
|
38
|
+
return msh.unmarshal_obj(config_dct, cls)
|
omlish/text/glyphsplit.py
CHANGED
@@ -97,3 +97,16 @@ glyph_split_parens = _PAREN_GLYPH_SPLITTER.split
|
|
97
97
|
glyph_split_braces = _BRACE_GLYPH_SPLITTER.split
|
98
98
|
glyph_split_brackets = _BRACKET_GLYPH_SPLITTER.split
|
99
99
|
glyph_split_angle_brackets = _ANGLE_BRACKET_GLYPH_SPLITTER.split
|
100
|
+
|
101
|
+
|
102
|
+
def glyph_split_interpolate(
|
103
|
+
split_fn: ta.Callable[[str], ta.Sequence[ta.Union[GlyphSplitMatch, str]]],
|
104
|
+
dct: ta.Mapping[str, str],
|
105
|
+
s: str,
|
106
|
+
) -> str:
|
107
|
+
if not s:
|
108
|
+
return s
|
109
|
+
sps = split_fn(s)
|
110
|
+
if len(sps) == 1 and isinstance(sps[0], str):
|
111
|
+
return sps[0]
|
112
|
+
return ''.join(dct[p.s] if isinstance(p, GlyphSplitMatch) else p for p in sps)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
omlish/.manifests.json,sha256=dyIpveH7Z8OnQp2pTn6NVv7LCDXVrozJWAzbk8PBavg,7950
|
2
|
-
omlish/__about__.py,sha256=
|
2
|
+
omlish/__about__.py,sha256=EGlq5fZGiyJg2x9ba9tOUhbLf6OjV9lr0mNkhwX1DnU,3409
|
3
3
|
omlish/__init__.py,sha256=SsyiITTuK0v74XpKV8dqNaCmjOlan1JZKrHQv5rWKPA,253
|
4
4
|
omlish/c3.py,sha256=ubu7lHwss5V4UznbejAI0qXhXahrU01MysuHOZI9C4U,8116
|
5
5
|
omlish/cached.py,sha256=UI-XTFBwA6YXWJJJeBn-WkwBkfzDjLBBaZf4nIJA9y0,510
|
@@ -151,8 +151,16 @@ omlish/concurrent/futures.py,sha256=J2s9wYURUskqRJiBbAR0PNEAp1pXbIMYldOVBTQduQY,
|
|
151
151
|
omlish/concurrent/threadlets.py,sha256=JfirbTDJgy9Ouokz_VmHeAAPS7cih8qMUJrN-owwXD4,2423
|
152
152
|
omlish/configs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
153
153
|
omlish/configs/classes.py,sha256=GLbB8xKjHjjoUQRCUQm3nEjM8z1qNTx9gPV7ODSt5dg,1317
|
154
|
-
omlish/configs/
|
155
|
-
omlish/configs/
|
154
|
+
omlish/configs/formats.py,sha256=RJw4Rzp7vlTd5YyAvpAoruQnk45v8dGPtPWwqH7aYyE,5301
|
155
|
+
omlish/configs/nginx.py,sha256=XuX9yyb0_MwkJ8esKiMS9gFkqHUPza_uCprhnWykNy8,2051
|
156
|
+
omlish/configs/types.py,sha256=t5_32MVRSKxbxor1hl1wRGKYm75F6Atisd_RYsN5ELU,103
|
157
|
+
omlish/configs/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
158
|
+
omlish/configs/processing/flattening.py,sha256=1duZH5zbrGuoYqaJco5LN5qx6e76QP5pHbIyItkQsFY,5021
|
159
|
+
omlish/configs/processing/inheritance.py,sha256=lFD8eWRE0cG0Z3-eCu7pMsP2skWhUNaoAtWi9fNfn8k,1218
|
160
|
+
omlish/configs/processing/matching.py,sha256=JMS9r58pMCBbpewOhPY5oPtBu3uD6z6YBfZq4jnLwBE,1236
|
161
|
+
omlish/configs/processing/names.py,sha256=weHmaTclzgM9lUn3aBtw-kwZ3mc2N-CZlFg3Kd_UsKo,1093
|
162
|
+
omlish/configs/processing/rewriting.py,sha256=v7PfHtuTn5v_5Y6Au7oMN2Z0nxAMy1iYyO5CXnTvZhs,4226
|
163
|
+
omlish/configs/processing/strings.py,sha256=qFS2oh6z02IaM_q4lTKLdufzkJqAJ6J-Qjrz5S-QJoM,826
|
156
164
|
omlish/dataclasses/__init__.py,sha256=AHo-tN5V_b_VYFUF7VFRmuHrjZBXS1WytRAj061MUTA,1423
|
157
165
|
omlish/dataclasses/utils.py,sha256=lcikCPiiX5Giu0Kb1hP18loZjmm_Z9D-XtJ-ZlHq9iM,3793
|
158
166
|
omlish/dataclasses/impl/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
|
@@ -209,7 +217,7 @@ omlish/docker/detect.py,sha256=Qrdbosm2wJkxKDuy8gaGmbQoxk4Wnp1HJjAEz58NA8Y,614
|
|
209
217
|
omlish/docker/hub.py,sha256=7LIuJGdA-N1Y1dmo50ynKM1KUTcnQM_5XbtPbdT_QLU,3940
|
210
218
|
omlish/docker/manifests.py,sha256=LR4FpOGNUT3bZQ-gTjB6r_-1C3YiG30QvevZjrsVUQM,7068
|
211
219
|
omlish/docker/timebomb.py,sha256=A_pgIDaXKsQwPiikrCTgIJl91gwYqkPGFY6j-Naq07Q,342
|
212
|
-
omlish/formats/__init__.py,sha256=
|
220
|
+
omlish/formats/__init__.py,sha256=T0AG1gFnqQ5JiHN0UPQjQ-7g5tnxMIG-mgOvMYExYAM,21
|
213
221
|
omlish/formats/cbor.py,sha256=o_Hbe4kthO9CeXK-FySrw0dHVlrdyTo2Y8PpGRDfZ3c,514
|
214
222
|
omlish/formats/cloudpickle.py,sha256=16si4yUp_pAdDWGECAWf6HLA2PwSANGGgDoMLGZUem8,579
|
215
223
|
omlish/formats/codecs.py,sha256=6AbKIz_gl_om36-7qsutybYzK8cJP3LzwC9YJlwCPOA,1590
|
@@ -248,7 +256,7 @@ omlish/formats/json/stream/render.py,sha256=NtmDsN92xZi5dkgSSuMeMXMAiJblmjz1arB4
|
|
248
256
|
omlish/formats/toml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
249
257
|
omlish/formats/toml/codec.py,sha256=5HFGWEPd9IFxPlRMRheX8FEDlRIzLe1moHEOj2_PFKU,342
|
250
258
|
omlish/formats/toml/parser.py,sha256=3QN3rqdJ_zlHfFtsDn-A9pl-BTwdVMUUpUV5-lGWCKc,29342
|
251
|
-
omlish/formats/toml/writer.py,sha256=
|
259
|
+
omlish/formats/toml/writer.py,sha256=HIp6XvriXaPTLqyLe-fkIiEf1Pyhsp0TcOg5rFBpO3g,3226
|
252
260
|
omlish/funcs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
253
261
|
omlish/funcs/genmachine.py,sha256=EY2k-IFNKMEmHo9CmGRptFvhEMZVOWzZhn0wdQGeaFM,2476
|
254
262
|
omlish/funcs/match.py,sha256=gMLZn1enNiFvQaWrQubY300M1BrmdKWzeePihBS7Ywc,6153
|
@@ -383,6 +391,7 @@ omlish/lifecycles/transitions.py,sha256=qQtFby-h4VzbvgaUqT2NnbNumlcOx9FVVADP9t83
|
|
383
391
|
omlish/lite/__init__.py,sha256=ISLhM4q0LR1XXTCaHdZOZxBRyIsoZqYm4u0bf1BPcVk,148
|
384
392
|
omlish/lite/cached.py,sha256=O7ozcoDNFm1Hg2wtpHEqYSp_i_nCLNOP6Ueq_Uk-7mU,1300
|
385
393
|
omlish/lite/check.py,sha256=0PD-GKtaDqDX6jU5KbzbMvH-vl6jH82xgYfplmfTQkg,12941
|
394
|
+
omlish/lite/configs.py,sha256=Ev_19sbII67pTWzInYjYqa9VyTiZBvyjhZqyG8TtufE,908
|
386
395
|
omlish/lite/contextmanagers.py,sha256=m9JO--p7L7mSl4cycXysH-1AO27weDKjP3DZG61cwwM,1683
|
387
396
|
omlish/lite/dataclasses.py,sha256=M6UD4VwGo0Ky7RNzKWbO0IOy7iBZVCIbTiC6EYbFnX8,1035
|
388
397
|
omlish/lite/inject.py,sha256=qBUftFeXMiRgANYbNS2e7TePMYyFAcuLgsJiLyMTW5o,28769
|
@@ -581,14 +590,14 @@ omlish/testing/pytest/plugins/utils.py,sha256=L5C622UXcA_AUKDcvyh5IMiRfqSGGz0Mcd
|
|
581
590
|
omlish/text/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
582
591
|
omlish/text/asdl.py,sha256=AS3irh-sag5pqyH3beJif78PjCbOaFso1NeKq-HXuTs,16867
|
583
592
|
omlish/text/delimit.py,sha256=ubPXcXQmtbOVrUsNh5gH1mDq5H-n1y2R4cPL5_DQf68,4928
|
584
|
-
omlish/text/glyphsplit.py,sha256=
|
593
|
+
omlish/text/glyphsplit.py,sha256=kqqjglRdxGo0czYZxOz9Vi8aBmVsCOq8h6lPwRA5xe0,3803
|
585
594
|
omlish/text/indent.py,sha256=XixaVnk9bvtBlH0n_cwN6yS5IyfrTWpe_alfu3_saLY,1341
|
586
595
|
omlish/text/minja.py,sha256=KAmZ2POcLcxwF4DPKxdWa16uWxXmVz1UnJXLSwt4oZo,5761
|
587
596
|
omlish/text/parts.py,sha256=7vPF1aTZdvLVYJ4EwBZVzRSy8XB3YqPd7JwEnNGGAOo,6495
|
588
597
|
omlish/text/random.py,sha256=jNWpqiaKjKyTdMXC-pWAsSC10AAP-cmRRPVhm59ZWLk,194
|
589
|
-
omlish-0.0.0.
|
590
|
-
omlish-0.0.0.
|
591
|
-
omlish-0.0.0.
|
592
|
-
omlish-0.0.0.
|
593
|
-
omlish-0.0.0.
|
594
|
-
omlish-0.0.0.
|
598
|
+
omlish-0.0.0.dev194.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
599
|
+
omlish-0.0.0.dev194.dist-info/METADATA,sha256=nhNsVPo6hNygJ8DxnRbJE-iqP-emALofRYUylUQkyJc,4264
|
600
|
+
omlish-0.0.0.dev194.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
601
|
+
omlish-0.0.0.dev194.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
|
602
|
+
omlish-0.0.0.dev194.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
|
603
|
+
omlish-0.0.0.dev194.dist-info/RECORD,,
|
omlish/configs/strings.py
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
TODO:
|
3
|
-
- reflecty generalized rewriter, obviously..
|
4
|
-
- env vars
|
5
|
-
- coalescing - {$FOO|$BAR|baz}
|
6
|
-
"""
|
7
|
-
import collections.abc
|
8
|
-
import typing as ta
|
9
|
-
|
10
|
-
from .. import dataclasses as dc
|
11
|
-
from .. import lang
|
12
|
-
from ..text import glyphsplit
|
13
|
-
|
14
|
-
|
15
|
-
T = ta.TypeVar('T')
|
16
|
-
|
17
|
-
|
18
|
-
class InterpolateStringsMetadata(lang.Marker):
|
19
|
-
pass
|
20
|
-
|
21
|
-
|
22
|
-
@dc.field_modifier
|
23
|
-
def secret_or_key_field(f: dc.Field) -> dc.Field:
|
24
|
-
return dc.update_field_metadata(f, {
|
25
|
-
InterpolateStringsMetadata: True,
|
26
|
-
})
|
27
|
-
|
28
|
-
|
29
|
-
@dc.field_modifier
|
30
|
-
def interpolate_field(f: dc.Field) -> dc.Field:
|
31
|
-
return dc.update_field_metadata(f, {InterpolateStringsMetadata: True})
|
32
|
-
|
33
|
-
|
34
|
-
class StringRewriter:
|
35
|
-
def __init__(self, fn: ta.Callable[[str], str]) -> None:
|
36
|
-
super().__init__()
|
37
|
-
self._fn = fn
|
38
|
-
|
39
|
-
def __call__(self, v: T, *, _soft: bool = False) -> T:
|
40
|
-
if v is None:
|
41
|
-
return None # type: ignore
|
42
|
-
|
43
|
-
if dc.is_dataclass(v):
|
44
|
-
kw = {}
|
45
|
-
for f in dc.fields(v):
|
46
|
-
fv = getattr(v, f.name)
|
47
|
-
nfv = self(fv, _soft=not f.metadata.get(InterpolateStringsMetadata))
|
48
|
-
if fv is not nfv:
|
49
|
-
kw[f.name] = nfv
|
50
|
-
if not kw:
|
51
|
-
return v # type: ignore
|
52
|
-
return dc.replace(v, **kw)
|
53
|
-
|
54
|
-
if isinstance(v, str):
|
55
|
-
if not _soft:
|
56
|
-
v = self._fn(v) # type: ignore
|
57
|
-
return v # type: ignore
|
58
|
-
|
59
|
-
if isinstance(v, lang.BUILTIN_SCALAR_ITERABLE_TYPES):
|
60
|
-
return v # type: ignore
|
61
|
-
|
62
|
-
if isinstance(v, collections.abc.Mapping):
|
63
|
-
nm = []
|
64
|
-
b = False
|
65
|
-
for mk, mv in v.items():
|
66
|
-
nk, nv = self(mk, _soft=_soft), self(mv, _soft=_soft)
|
67
|
-
nm.append((nk, nv))
|
68
|
-
b |= nk is not mk or nv is not mv
|
69
|
-
if not b:
|
70
|
-
return v # type: ignore
|
71
|
-
return v.__class__(nm) # type: ignore
|
72
|
-
|
73
|
-
if isinstance(v, (collections.abc.Sequence, collections.abc.Set)):
|
74
|
-
nl = []
|
75
|
-
b = False
|
76
|
-
for le in v:
|
77
|
-
ne = self(le, _soft=_soft)
|
78
|
-
nl.append(ne)
|
79
|
-
b |= ne is not le
|
80
|
-
if not b:
|
81
|
-
return v # type: ignore
|
82
|
-
return v.__class__(nl) # type: ignore
|
83
|
-
|
84
|
-
return v
|
85
|
-
|
86
|
-
|
87
|
-
def interpolate_strings(v: T, rpl: ta.Mapping[str, str]) -> T:
|
88
|
-
def fn(v):
|
89
|
-
if not v:
|
90
|
-
return v
|
91
|
-
sps = glyphsplit.glyph_split_braces(v)
|
92
|
-
if len(sps) == 1 and isinstance(sps[0], str):
|
93
|
-
return sps[0]
|
94
|
-
return ''.join(rpl[p.s] if isinstance(p, glyphsplit.GlyphSplitMatch) else p for p in sps)
|
95
|
-
|
96
|
-
return StringRewriter(fn)(v)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|