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 CHANGED
@@ -1,5 +1,5 @@
1
- __version__ = '0.0.0.dev193'
2
- __revision__ = 'abb15c34774729bc05f56f8b60e9ee7fdc3b3526'
1
+ __version__ = '0.0.0.dev194'
2
+ __revision__ = '39cfe872573628af34684eb1ec41a0ccb127ad0f'
3
3
 
4
4
 
5
5
  #
@@ -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)
@@ -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
@@ -3,7 +3,7 @@
3
3
  import abc
4
4
  import typing as ta
5
5
 
6
- from ..lite.check import check
6
+ from ...lite.check import check
7
7
 
8
8
 
9
9
  K = ta.TypeVar('K')
@@ -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)
@@ -0,0 +1,9 @@
1
+ # ruff: noqa: UP006 UP007
2
+ # @omlish-lite
3
+ import typing as ta
4
+
5
+
6
+ ConfigMap = ta.Mapping[str, ta.Any]
7
+
8
+
9
+ #
@@ -1,5 +1,4 @@
1
1
  """
2
2
  TODO:
3
3
  - csv
4
- - ini
5
4
  """
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev193
3
+ Version: 0.0.0.dev194
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -1,5 +1,5 @@
1
1
  omlish/.manifests.json,sha256=dyIpveH7Z8OnQp2pTn6NVv7LCDXVrozJWAzbk8PBavg,7950
2
- omlish/__about__.py,sha256=8ZHxg4DUtn_y7pls_IM3xNDk_hcPGBh1tnkB36CBP5U,3409
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/flattening.py,sha256=t29dgCTcwrgvOEuxIxLf7FgZhOpH2Z72xIRHglXbxUI,5020
155
- omlish/configs/strings.py,sha256=Ho92yy3Ex_-FoUTBuUcRF_tJ-iE9kFFa5oOqnGtCRZg,2662
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=KC-AiWm95O6kXVW4cRUnq8WnybKnE6lR9sf_i2DuV4o,28
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=KyT1PnAQqx-uVcb0vTPzt-A9N-WfDiiaokg4kUeEXHk,3055
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=vN6TgnMONCTsHL8_lDaOfFmd5j_JVxOV_nRre4SENF8,3420
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.dev193.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
590
- omlish-0.0.0.dev193.dist-info/METADATA,sha256=CurOKNHEhxcUbUQtXrRsaxGfunrIIFaJO-ZJiGOmAg4,4264
591
- omlish-0.0.0.dev193.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
592
- omlish-0.0.0.dev193.dist-info/entry_points.txt,sha256=Lt84WvRZJskWCAS7xnQGZIeVWksprtUHj0llrvVmod8,35
593
- omlish-0.0.0.dev193.dist-info/top_level.txt,sha256=pePsKdLu7DvtUiecdYXJ78iO80uDNmBlqe-8hOzOmfs,7
594
- omlish-0.0.0.dev193.dist-info/RECORD,,
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)