omdev 0.0.0.dev403__py3-none-any.whl → 0.0.0.dev405__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.
- omdev/.manifests.json +2 -2
- omdev/cli/main.py +11 -14
- omdev/precheck/manifests.py +1 -1
- omdev/py/scripts/execstat.py +31 -10
- omdev/scripts/ci.py +466 -461
- omdev/tools/git/messages.py +1 -5
- {omdev-0.0.0.dev403.dist-info → omdev-0.0.0.dev405.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev403.dist-info → omdev-0.0.0.dev405.dist-info}/RECORD +12 -12
- {omdev-0.0.0.dev403.dist-info → omdev-0.0.0.dev405.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev403.dist-info → omdev-0.0.0.dev405.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev403.dist-info → omdev-0.0.0.dev405.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev403.dist-info → omdev-0.0.0.dev405.dist-info}/top_level.txt +0 -0
omdev/.manifests.json
CHANGED
@@ -236,7 +236,7 @@
|
|
236
236
|
"module": ".py.scripts.execstat",
|
237
237
|
"attr": "_CLI_MODULE",
|
238
238
|
"file": "omdev/py/scripts/execstat.py",
|
239
|
-
"line":
|
239
|
+
"line": 89,
|
240
240
|
"value": {
|
241
241
|
"$.cli.types.CliModule": {
|
242
242
|
"cmd_name": "py/execstat",
|
@@ -404,7 +404,7 @@
|
|
404
404
|
"module": ".tools.git.messages",
|
405
405
|
"attr": "_TIMESTAMP_GIT_MESSAGE_GENERATOR_MANIFEST",
|
406
406
|
"file": "omdev/tools/git/messages.py",
|
407
|
-
"line":
|
407
|
+
"line": 81,
|
408
408
|
"value": {
|
409
409
|
"$.tools.git.messages.GitMessageGeneratorManifest": {
|
410
410
|
"mod_name": "omdev.tools.git.messages",
|
omdev/cli/main.py
CHANGED
@@ -6,7 +6,6 @@ TODO:
|
|
6
6
|
"""
|
7
7
|
import argparse
|
8
8
|
import dataclasses as dc
|
9
|
-
import os
|
10
9
|
import runpy
|
11
10
|
import sys
|
12
11
|
import typing as ta
|
@@ -184,26 +183,24 @@ def _build_arg_parser() -> argparse.ArgumentParser:
|
|
184
183
|
|
185
184
|
|
186
185
|
def _build_cmd_set(args: ta.Any) -> CliCmdSet:
|
187
|
-
|
188
|
-
**ManifestLoader.kwargs_from_entry_point(
|
189
|
-
globals(),
|
190
|
-
**GlobalManifestLoader.default_kwargs(),
|
191
|
-
),
|
192
|
-
)
|
186
|
+
ldr_cfg = GlobalManifestLoader.default_config()
|
193
187
|
|
194
|
-
|
188
|
+
ldr_cfg |= ManifestLoader.config_from_entry_point(globals())
|
189
|
+
|
190
|
+
if args.cli_pkg_root:
|
191
|
+
ldr_cfg |= ManifestLoader.Config(
|
192
|
+
package_scan_root_dirs=args.cli_pkg_root,
|
193
|
+
discover_packages=False,
|
194
|
+
)
|
195
195
|
|
196
|
-
|
197
|
-
specified_root_dirs=args.cli_pkg_root,
|
198
|
-
fallback_root_dir=os.getcwd(),
|
199
|
-
)
|
196
|
+
GlobalManifestLoader.initialize(ldr_cfg)
|
200
197
|
|
201
198
|
#
|
202
199
|
|
203
200
|
lst: list[CliCmd] = []
|
204
201
|
|
205
|
-
for
|
206
|
-
lst.append(check.isinstance(
|
202
|
+
for mv in GlobalManifestLoader.load_values_of(CliModule):
|
203
|
+
lst.append(check.isinstance(mv, CliModule))
|
207
204
|
|
208
205
|
lst.extend(_CLI_FUNCS)
|
209
206
|
|
omdev/precheck/manifests.py
CHANGED
@@ -23,6 +23,6 @@ class ManifestsPrecheck(Precheck['ManifestsPrecheck.Config']):
|
|
23
23
|
async def run(self) -> ta.AsyncGenerator[Precheck.Violation]:
|
24
24
|
for src_root in sorted(self._context.src_roots):
|
25
25
|
try:
|
26
|
-
GlobalManifestLoader.load(src_root)
|
26
|
+
GlobalManifestLoader.load(packages=[src_root])
|
27
27
|
except Exception as e: # noqa
|
28
28
|
yield Precheck.Violation(self, f'Error loading manifest for {src_root}: {e!r}')
|
omdev/py/scripts/execstat.py
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
|
+
# ruff: noqa: UP045
|
3
|
+
# @omlish-lite
|
2
4
|
# @omlish-script
|
3
5
|
import argparse
|
4
6
|
import inspect
|
5
7
|
import json
|
8
|
+
import os.path
|
6
9
|
import statistics
|
7
10
|
import subprocess
|
8
11
|
import sys
|
12
|
+
import tempfile
|
13
|
+
import typing as ta
|
9
14
|
|
10
15
|
|
11
16
|
##
|
@@ -14,7 +19,7 @@ import sys
|
|
14
19
|
def _run(
|
15
20
|
src: str,
|
16
21
|
*,
|
17
|
-
setup: str
|
22
|
+
setup: 'ta.Optional[str]' = None,
|
18
23
|
time: bool = False,
|
19
24
|
rss: bool = False,
|
20
25
|
modules: bool = False,
|
@@ -31,8 +36,8 @@ def _run(
|
|
31
36
|
if modules:
|
32
37
|
import sys # noqa
|
33
38
|
|
34
|
-
def get_modules() ->
|
35
|
-
return
|
39
|
+
def get_modules() -> 'ta.Sequence[str]':
|
40
|
+
return list(sys.modules)
|
36
41
|
|
37
42
|
#
|
38
43
|
|
@@ -49,7 +54,7 @@ def _run(
|
|
49
54
|
start_rss = get_rss() # noqa
|
50
55
|
|
51
56
|
if modules:
|
52
|
-
start_modules = get_modules() # noqa
|
57
|
+
start_modules = set(get_modules()) # noqa
|
53
58
|
|
54
59
|
if time:
|
55
60
|
start_time = get_time() # noqa
|
@@ -74,7 +79,7 @@ def _run(
|
|
74
79
|
return {
|
75
80
|
**({'time': (end_time - start_time)} if time else {}), # noqa
|
76
81
|
**({'rss': (end_rss - start_rss)} if rss else {}), # noqa
|
77
|
-
**({'modules':
|
82
|
+
**({'modules': [m for m in end_modules if m not in start_modules]} if modules else {}), # noqa
|
78
83
|
}
|
79
84
|
|
80
85
|
|
@@ -97,11 +102,14 @@ def _main() -> None:
|
|
97
102
|
parser.add_argument('-t', '--time', action='store_true')
|
98
103
|
parser.add_argument('-r', '--rss', action='store_true')
|
99
104
|
parser.add_argument('-m', '--modules', action='store_true')
|
105
|
+
parser.add_argument('-M', '--modules-ordered', action='store_true')
|
100
106
|
|
101
107
|
parser.add_argument('-n', '--num-runs', type=int, default=1)
|
102
108
|
|
103
109
|
parser.add_argument('-P', '--precision', type=int, default=3)
|
104
110
|
|
111
|
+
parser.add_argument('--out-dir')
|
112
|
+
|
105
113
|
parser.add_argument('-x', '--exe')
|
106
114
|
|
107
115
|
args = parser.parse_args()
|
@@ -116,8 +124,13 @@ def _main() -> None:
|
|
116
124
|
if (exe := args.exe) is None:
|
117
125
|
exe = sys.executable
|
118
126
|
|
127
|
+
if (out_dir := args.out_dir) is None:
|
128
|
+
out_dir = tempfile.mkdtemp()
|
129
|
+
|
119
130
|
results = []
|
120
131
|
for i in range(n):
|
132
|
+
out_file = os.path.join(out_dir, f'{str(i).zfill(len(str(n)))}.json')
|
133
|
+
|
121
134
|
run_kw = dict(
|
122
135
|
src=args.src,
|
123
136
|
setup=args.setup,
|
@@ -127,17 +140,21 @@ def _main() -> None:
|
|
127
140
|
if i == 0:
|
128
141
|
run_kw.update(
|
129
142
|
rss=bool(args.rss),
|
130
|
-
modules=bool(args.modules),
|
143
|
+
modules=bool(args.modules) or bool(args.modules),
|
131
144
|
)
|
132
145
|
|
133
146
|
payload = '\n'.join([
|
134
147
|
inspect.getsource(_run),
|
135
148
|
f'dct = _run(**{run_kw!r})',
|
136
|
-
'import json',
|
137
|
-
'
|
149
|
+
f'import json',
|
150
|
+
f'with open({out_file!r}, "w") as f:', # noqa
|
151
|
+
f' print(f.write(json.dumps(dct)))',
|
138
152
|
])
|
139
153
|
|
140
|
-
|
154
|
+
subprocess.check_call([exe, '-c', payload])
|
155
|
+
|
156
|
+
with open(out_file) as f:
|
157
|
+
result = json.load(f)
|
141
158
|
|
142
159
|
results.append(result)
|
143
160
|
|
@@ -170,7 +187,11 @@ def _main() -> None:
|
|
170
187
|
|
171
188
|
if args.modules:
|
172
189
|
out.update({
|
173
|
-
'modules': results[0]['modules'],
|
190
|
+
'modules': sorted(results[0]['modules']),
|
191
|
+
})
|
192
|
+
if args.modules_ordered:
|
193
|
+
out.update({
|
194
|
+
'modules_ordered': results[0]['modules'],
|
174
195
|
})
|
175
196
|
|
176
197
|
print(json.dumps(out, indent=2))
|
omdev/scripts/ci.py
CHANGED
@@ -99,6 +99,10 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
|
|
99
99
|
CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
|
100
100
|
CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
|
101
101
|
|
102
|
+
# ../../omlish/lite/contextmanagers.py
|
103
|
+
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
104
|
+
AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
105
|
+
|
102
106
|
# ../../omlish/lite/maybes.py
|
103
107
|
U = ta.TypeVar('U')
|
104
108
|
|
@@ -121,10 +125,6 @@ AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
|
121
125
|
# ../../omlish/http/parsing.py
|
122
126
|
HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
|
123
127
|
|
124
|
-
# ../../omlish/lite/contextmanagers.py
|
125
|
-
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
126
|
-
AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
127
|
-
|
128
128
|
# ../../omlish/lite/inject.py
|
129
129
|
InjectorKeyCls = ta.Union[type, ta.NewType]
|
130
130
|
InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
|
@@ -1026,6 +1026,201 @@ class Checks:
|
|
1026
1026
|
check = Checks()
|
1027
1027
|
|
1028
1028
|
|
1029
|
+
########################################
|
1030
|
+
# ../../../omlish/lite/contextmanagers.py
|
1031
|
+
|
1032
|
+
|
1033
|
+
##
|
1034
|
+
|
1035
|
+
|
1036
|
+
class ExitStacked:
|
1037
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
1038
|
+
super().__init_subclass__(**kwargs)
|
1039
|
+
|
1040
|
+
for a in ('__enter__', '__exit__'):
|
1041
|
+
for b in cls.__bases__:
|
1042
|
+
if b is ExitStacked:
|
1043
|
+
continue
|
1044
|
+
try:
|
1045
|
+
fn = getattr(b, a)
|
1046
|
+
except AttributeError:
|
1047
|
+
pass
|
1048
|
+
else:
|
1049
|
+
if fn is not getattr(ExitStacked, a):
|
1050
|
+
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
1051
|
+
|
1052
|
+
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
1053
|
+
|
1054
|
+
@contextlib.contextmanager
|
1055
|
+
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
1056
|
+
"""
|
1057
|
+
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
1058
|
+
things like wrapping __enter__ in a lock.
|
1059
|
+
"""
|
1060
|
+
|
1061
|
+
yield
|
1062
|
+
|
1063
|
+
@ta.final
|
1064
|
+
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
1065
|
+
"""
|
1066
|
+
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
1067
|
+
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
1068
|
+
"""
|
1069
|
+
|
1070
|
+
with self._exit_stacked_init_wrapper():
|
1071
|
+
if self._exit_stack is not None:
|
1072
|
+
raise RuntimeError
|
1073
|
+
es = self._exit_stack = contextlib.ExitStack()
|
1074
|
+
es.__enter__()
|
1075
|
+
try:
|
1076
|
+
self._enter_contexts()
|
1077
|
+
except Exception: # noqa
|
1078
|
+
es.__exit__(*sys.exc_info())
|
1079
|
+
raise
|
1080
|
+
return self
|
1081
|
+
|
1082
|
+
@ta.final
|
1083
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
1084
|
+
if (es := self._exit_stack) is None:
|
1085
|
+
return None
|
1086
|
+
try:
|
1087
|
+
self._exit_contexts()
|
1088
|
+
except Exception: # noqa
|
1089
|
+
es.__exit__(*sys.exc_info())
|
1090
|
+
raise
|
1091
|
+
return es.__exit__(exc_type, exc_val, exc_tb)
|
1092
|
+
|
1093
|
+
def _enter_contexts(self) -> None:
|
1094
|
+
pass
|
1095
|
+
|
1096
|
+
def _exit_contexts(self) -> None:
|
1097
|
+
pass
|
1098
|
+
|
1099
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
1100
|
+
if (es := self._exit_stack) is None:
|
1101
|
+
raise RuntimeError
|
1102
|
+
return es.enter_context(cm)
|
1103
|
+
|
1104
|
+
|
1105
|
+
class AsyncExitStacked:
|
1106
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
1107
|
+
super().__init_subclass__(**kwargs)
|
1108
|
+
|
1109
|
+
for a in ('__aenter__', '__aexit__'):
|
1110
|
+
for b in cls.__bases__:
|
1111
|
+
if b is AsyncExitStacked:
|
1112
|
+
continue
|
1113
|
+
try:
|
1114
|
+
fn = getattr(b, a)
|
1115
|
+
except AttributeError:
|
1116
|
+
pass
|
1117
|
+
else:
|
1118
|
+
if fn is not getattr(AsyncExitStacked, a):
|
1119
|
+
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
1120
|
+
|
1121
|
+
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
1122
|
+
|
1123
|
+
@contextlib.asynccontextmanager
|
1124
|
+
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
1125
|
+
yield
|
1126
|
+
|
1127
|
+
@ta.final
|
1128
|
+
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
1129
|
+
async with self._async_exit_stacked_init_wrapper():
|
1130
|
+
if self._exit_stack is not None:
|
1131
|
+
raise RuntimeError
|
1132
|
+
es = self._exit_stack = contextlib.AsyncExitStack()
|
1133
|
+
await es.__aenter__()
|
1134
|
+
try:
|
1135
|
+
await self._async_enter_contexts()
|
1136
|
+
except Exception: # noqa
|
1137
|
+
await es.__aexit__(*sys.exc_info())
|
1138
|
+
raise
|
1139
|
+
return self
|
1140
|
+
|
1141
|
+
@ta.final
|
1142
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
1143
|
+
if (es := self._exit_stack) is None:
|
1144
|
+
return None
|
1145
|
+
try:
|
1146
|
+
await self._async_exit_contexts()
|
1147
|
+
except Exception: # noqa
|
1148
|
+
await es.__aexit__(*sys.exc_info())
|
1149
|
+
raise
|
1150
|
+
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
1151
|
+
|
1152
|
+
async def _async_enter_contexts(self) -> None:
|
1153
|
+
pass
|
1154
|
+
|
1155
|
+
async def _async_exit_contexts(self) -> None:
|
1156
|
+
pass
|
1157
|
+
|
1158
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
1159
|
+
if (es := self._exit_stack) is None:
|
1160
|
+
raise RuntimeError
|
1161
|
+
return es.enter_context(cm)
|
1162
|
+
|
1163
|
+
async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
|
1164
|
+
if (es := self._exit_stack) is None:
|
1165
|
+
raise RuntimeError
|
1166
|
+
return await es.enter_async_context(cm)
|
1167
|
+
|
1168
|
+
|
1169
|
+
##
|
1170
|
+
|
1171
|
+
|
1172
|
+
@contextlib.contextmanager
|
1173
|
+
def defer(fn: ta.Callable, *args: ta.Any, **kwargs: ta.Any) -> ta.Generator[ta.Callable, None, None]:
|
1174
|
+
if args or kwargs:
|
1175
|
+
fn = functools.partial(fn, *args, **kwargs)
|
1176
|
+
try:
|
1177
|
+
yield fn
|
1178
|
+
finally:
|
1179
|
+
fn()
|
1180
|
+
|
1181
|
+
|
1182
|
+
@contextlib.asynccontextmanager
|
1183
|
+
async def adefer(fn: ta.Awaitable) -> ta.AsyncGenerator[ta.Awaitable, None]:
|
1184
|
+
try:
|
1185
|
+
yield fn
|
1186
|
+
finally:
|
1187
|
+
await fn
|
1188
|
+
|
1189
|
+
|
1190
|
+
##
|
1191
|
+
|
1192
|
+
|
1193
|
+
@contextlib.contextmanager
|
1194
|
+
def attr_setting(obj, attr, val, *, default=None): # noqa
|
1195
|
+
not_set = object()
|
1196
|
+
orig = getattr(obj, attr, not_set)
|
1197
|
+
try:
|
1198
|
+
setattr(obj, attr, val)
|
1199
|
+
if orig is not not_set:
|
1200
|
+
yield orig
|
1201
|
+
else:
|
1202
|
+
yield default
|
1203
|
+
finally:
|
1204
|
+
if orig is not_set:
|
1205
|
+
delattr(obj, attr)
|
1206
|
+
else:
|
1207
|
+
setattr(obj, attr, orig)
|
1208
|
+
|
1209
|
+
|
1210
|
+
##
|
1211
|
+
|
1212
|
+
|
1213
|
+
class aclosing(contextlib.AbstractAsyncContextManager): # noqa
|
1214
|
+
def __init__(self, thing):
|
1215
|
+
self.thing = thing
|
1216
|
+
|
1217
|
+
async def __aenter__(self):
|
1218
|
+
return self.thing
|
1219
|
+
|
1220
|
+
async def __aexit__(self, *exc_info):
|
1221
|
+
await self.thing.aclose()
|
1222
|
+
|
1223
|
+
|
1029
1224
|
########################################
|
1030
1225
|
# ../../../omlish/lite/dataclasses.py
|
1031
1226
|
|
@@ -2721,53 +2916,251 @@ class OciDataRefInfo:
|
|
2721
2916
|
|
2722
2917
|
|
2723
2918
|
########################################
|
2724
|
-
#
|
2725
|
-
"""
|
2726
|
-
FIXME:
|
2727
|
-
- exit_on_error lol
|
2728
|
-
|
2729
|
-
TODO:
|
2730
|
-
- default command
|
2731
|
-
- auto match all underscores to hyphens
|
2732
|
-
- pre-run, post-run hooks
|
2733
|
-
- exitstack?
|
2734
|
-
- suggestion - difflib.get_close_matches
|
2735
|
-
"""
|
2919
|
+
# ../../oci/pack/unpacking.py
|
2736
2920
|
|
2737
2921
|
|
2738
2922
|
##
|
2739
2923
|
|
2740
2924
|
|
2741
|
-
|
2742
|
-
|
2743
|
-
|
2744
|
-
|
2745
|
-
|
2746
|
-
|
2747
|
-
|
2748
|
-
if instance is None:
|
2749
|
-
return self
|
2750
|
-
return getattr(instance.args, self.dest) # type: ignore
|
2751
|
-
|
2925
|
+
class OciLayerUnpacker(ExitStacked):
|
2926
|
+
def __init__(
|
2927
|
+
self,
|
2928
|
+
input_files: ta.Sequence[ta.Union[str, tarfile.TarFile]],
|
2929
|
+
output_file_path: str,
|
2930
|
+
) -> None:
|
2931
|
+
super().__init__()
|
2752
2932
|
|
2753
|
-
|
2754
|
-
|
2933
|
+
self._input_files = list(input_files)
|
2934
|
+
self._output_file_path = output_file_path
|
2755
2935
|
|
2936
|
+
#
|
2756
2937
|
|
2757
|
-
|
2758
|
-
|
2938
|
+
@contextlib.contextmanager
|
2939
|
+
def _open_input_file(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Iterator[tarfile.TarFile]:
|
2940
|
+
if isinstance(input_file, tarfile.TarFile):
|
2941
|
+
yield input_file
|
2759
2942
|
|
2943
|
+
elif isinstance(input_file, str):
|
2944
|
+
with tarfile.open(input_file) as tar_file:
|
2945
|
+
yield tar_file
|
2760
2946
|
|
2761
|
-
|
2947
|
+
else:
|
2948
|
+
raise TypeError(input_file)
|
2762
2949
|
|
2950
|
+
#
|
2763
2951
|
|
2764
|
-
|
2765
|
-
|
2766
|
-
|
2767
|
-
fn: ArgparseCmdFn
|
2768
|
-
args: ta.Sequence[ArgparseArg] = () # noqa
|
2952
|
+
class _Entry(ta.NamedTuple):
|
2953
|
+
file: ta.Union[str, tarfile.TarFile]
|
2954
|
+
info: tarfile.TarInfo
|
2769
2955
|
|
2770
|
-
|
2956
|
+
def _build_input_file_sorted_entries(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Sequence[_Entry]:
|
2957
|
+
dct: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
2958
|
+
|
2959
|
+
with self._open_input_file(input_file) as input_tar_file:
|
2960
|
+
for info in input_tar_file.getmembers():
|
2961
|
+
check.not_in(info.name, dct)
|
2962
|
+
dct[info.name] = self._Entry(
|
2963
|
+
file=input_file,
|
2964
|
+
info=info,
|
2965
|
+
)
|
2966
|
+
|
2967
|
+
return sorted(dct.values(), key=lambda entry: entry.info.name)
|
2968
|
+
|
2969
|
+
@cached_nullary
|
2970
|
+
def _entries_by_name(self) -> ta.Mapping[str, _Entry]:
|
2971
|
+
root: dict = {}
|
2972
|
+
|
2973
|
+
def find_dir(dir_name: str) -> dict: # noqa
|
2974
|
+
if dir_name:
|
2975
|
+
dir_parts = dir_name.split('/')
|
2976
|
+
else:
|
2977
|
+
dir_parts = []
|
2978
|
+
|
2979
|
+
cur = root # noqa
|
2980
|
+
for dir_part in dir_parts:
|
2981
|
+
cur = cur[dir_part] # noqa
|
2982
|
+
|
2983
|
+
return check.isinstance(cur, dict)
|
2984
|
+
|
2985
|
+
#
|
2986
|
+
|
2987
|
+
for input_file in self._input_files:
|
2988
|
+
sorted_entries = self._build_input_file_sorted_entries(input_file)
|
2989
|
+
|
2990
|
+
wh_names = set()
|
2991
|
+
wh_opaques = set()
|
2992
|
+
|
2993
|
+
#
|
2994
|
+
|
2995
|
+
for entry in sorted_entries:
|
2996
|
+
info = entry.info
|
2997
|
+
name = check.non_empty_str(info.name)
|
2998
|
+
base_name = os.path.basename(name)
|
2999
|
+
dir_name = os.path.dirname(name)
|
3000
|
+
|
3001
|
+
if base_name == '.wh..wh..opq':
|
3002
|
+
wh_opaques.add(dir_name)
|
3003
|
+
continue
|
3004
|
+
|
3005
|
+
if base_name.startswith('.wh.'):
|
3006
|
+
wh_base_name = os.path.basename(base_name[4:])
|
3007
|
+
wh_name = os.path.join(dir_name, wh_base_name)
|
3008
|
+
wh_names.add(wh_name)
|
3009
|
+
continue
|
3010
|
+
|
3011
|
+
cur = find_dir(dir_name)
|
3012
|
+
|
3013
|
+
if info.type == tarfile.DIRTYPE:
|
3014
|
+
try:
|
3015
|
+
ex = cur[base_name]
|
3016
|
+
except KeyError:
|
3017
|
+
cur[base_name] = {'': entry}
|
3018
|
+
else:
|
3019
|
+
ex[''] = entry
|
3020
|
+
|
3021
|
+
else:
|
3022
|
+
cur[base_name] = entry
|
3023
|
+
|
3024
|
+
#
|
3025
|
+
|
3026
|
+
for wh_name in reversed(sorted(wh_names)): # noqa
|
3027
|
+
wh_dir_name = os.path.dirname(wh_name)
|
3028
|
+
wh_base_name = os.path.basename(wh_name)
|
3029
|
+
|
3030
|
+
cur = find_dir(wh_dir_name)
|
3031
|
+
rm = cur[wh_base_name]
|
3032
|
+
|
3033
|
+
if isinstance(rm, dict):
|
3034
|
+
# Whiteouts wipe out whole directory:
|
3035
|
+
# https://github.com/containerd/containerd/blob/59c8cf6ea5f4175ad512914dd5ce554942bf144f/pkg/archive/tar_test.go#L648
|
3036
|
+
# check.equal(set(rm), '')
|
3037
|
+
del cur[wh_base_name]
|
3038
|
+
|
3039
|
+
elif isinstance(rm, self._Entry):
|
3040
|
+
del cur[wh_base_name]
|
3041
|
+
|
3042
|
+
else:
|
3043
|
+
raise TypeError(rm)
|
3044
|
+
|
3045
|
+
if wh_opaques:
|
3046
|
+
raise NotImplementedError
|
3047
|
+
|
3048
|
+
#
|
3049
|
+
|
3050
|
+
out: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
3051
|
+
|
3052
|
+
def rec(cur): # noqa
|
3053
|
+
for _, child in sorted(cur.items(), key=lambda t: t[0]):
|
3054
|
+
if isinstance(child, dict):
|
3055
|
+
rec(child)
|
3056
|
+
|
3057
|
+
elif isinstance(child, self._Entry):
|
3058
|
+
check.not_in(child.info.name, out)
|
3059
|
+
out[child.info.name] = child
|
3060
|
+
|
3061
|
+
else:
|
3062
|
+
raise TypeError(child)
|
3063
|
+
|
3064
|
+
rec(root)
|
3065
|
+
|
3066
|
+
return out
|
3067
|
+
|
3068
|
+
#
|
3069
|
+
|
3070
|
+
@cached_nullary
|
3071
|
+
def _output_tar_file(self) -> tarfile.TarFile:
|
3072
|
+
return self._enter_context(tarfile.open(self._output_file_path, 'w'))
|
3073
|
+
|
3074
|
+
#
|
3075
|
+
|
3076
|
+
def _add_unpacked_entry(
|
3077
|
+
self,
|
3078
|
+
input_tar_file: tarfile.TarFile,
|
3079
|
+
info: tarfile.TarInfo,
|
3080
|
+
) -> None:
|
3081
|
+
base_name = os.path.basename(info.name)
|
3082
|
+
check.state(not base_name.startswith('.wh.'))
|
3083
|
+
|
3084
|
+
if info.type in tarfile.REGULAR_TYPES:
|
3085
|
+
with check.not_none(input_tar_file.extractfile(info)) as f:
|
3086
|
+
self._output_tar_file().addfile(info, f)
|
3087
|
+
|
3088
|
+
else:
|
3089
|
+
self._output_tar_file().addfile(info)
|
3090
|
+
|
3091
|
+
def _unpack_file(
|
3092
|
+
self,
|
3093
|
+
input_file: ta.Union[str, tarfile.TarFile],
|
3094
|
+
) -> None:
|
3095
|
+
entries_by_name = self._entries_by_name()
|
3096
|
+
|
3097
|
+
with self._open_input_file(input_file) as input_tar_file:
|
3098
|
+
info: tarfile.TarInfo
|
3099
|
+
for info in input_tar_file.getmembers():
|
3100
|
+
try:
|
3101
|
+
entry = entries_by_name[info.name]
|
3102
|
+
except KeyError:
|
3103
|
+
continue
|
3104
|
+
|
3105
|
+
if entry.file != input_file:
|
3106
|
+
continue
|
3107
|
+
|
3108
|
+
self._add_unpacked_entry(input_tar_file, info)
|
3109
|
+
|
3110
|
+
@cached_nullary
|
3111
|
+
def write(self) -> None:
|
3112
|
+
for input_file in self._input_files:
|
3113
|
+
self._unpack_file(input_file)
|
3114
|
+
|
3115
|
+
|
3116
|
+
########################################
|
3117
|
+
# ../../../omlish/argparse/cli.py
|
3118
|
+
"""
|
3119
|
+
FIXME:
|
3120
|
+
- exit_on_error lol
|
3121
|
+
|
3122
|
+
TODO:
|
3123
|
+
- default command
|
3124
|
+
- auto match all underscores to hyphens
|
3125
|
+
- pre-run, post-run hooks
|
3126
|
+
- exitstack?
|
3127
|
+
- suggestion - difflib.get_close_matches
|
3128
|
+
"""
|
3129
|
+
|
3130
|
+
|
3131
|
+
##
|
3132
|
+
|
3133
|
+
|
3134
|
+
@dc.dataclass(eq=False)
|
3135
|
+
class ArgparseArg:
|
3136
|
+
args: ta.Sequence[ta.Any]
|
3137
|
+
kwargs: ta.Mapping[str, ta.Any]
|
3138
|
+
dest: ta.Optional[str] = None
|
3139
|
+
|
3140
|
+
def __get__(self, instance, owner=None):
|
3141
|
+
if instance is None:
|
3142
|
+
return self
|
3143
|
+
return getattr(instance.args, self.dest) # type: ignore
|
3144
|
+
|
3145
|
+
|
3146
|
+
def argparse_arg(*args, **kwargs) -> ArgparseArg:
|
3147
|
+
return ArgparseArg(args, kwargs)
|
3148
|
+
|
3149
|
+
|
3150
|
+
def argparse_arg_(*args, **kwargs) -> ta.Any:
|
3151
|
+
return argparse_arg(*args, **kwargs)
|
3152
|
+
|
3153
|
+
|
3154
|
+
#
|
3155
|
+
|
3156
|
+
|
3157
|
+
@dc.dataclass(eq=False)
|
3158
|
+
class ArgparseCmd:
|
3159
|
+
name: str
|
3160
|
+
fn: ArgparseCmdFn
|
3161
|
+
args: ta.Sequence[ArgparseArg] = () # noqa
|
3162
|
+
|
3163
|
+
# _: dc.KW_ONLY
|
2771
3164
|
|
2772
3165
|
aliases: ta.Optional[ta.Sequence[str]] = None
|
2773
3166
|
parent: ta.Optional['ArgparseCmd'] = None
|
@@ -3447,239 +3840,49 @@ class HttpRequestParser:
|
|
3447
3840
|
return ParseHttpRequestError(
|
3448
3841
|
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
3449
3842
|
message=('Line too long', str(err)),
|
3450
|
-
**result_kwargs(),
|
3451
|
-
)
|
3452
|
-
|
3453
|
-
except http.client.HTTPException as err:
|
3454
|
-
return ParseHttpRequestError(
|
3455
|
-
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
3456
|
-
message=('Too many headers', str(err)),
|
3457
|
-
**result_kwargs(),
|
3458
|
-
)
|
3459
|
-
|
3460
|
-
# Check for connection directive
|
3461
|
-
|
3462
|
-
conn_type = headers.get('Connection', '')
|
3463
|
-
if conn_type.lower() == 'close':
|
3464
|
-
close_connection = True
|
3465
|
-
elif (
|
3466
|
-
conn_type.lower() == 'keep-alive' and
|
3467
|
-
version >= HttpProtocolVersions.HTTP_1_1
|
3468
|
-
):
|
3469
|
-
close_connection = False
|
3470
|
-
|
3471
|
-
# Check for expect directive
|
3472
|
-
|
3473
|
-
expect = headers.get('Expect', '')
|
3474
|
-
if (
|
3475
|
-
expect.lower() == '100-continue' and
|
3476
|
-
version >= HttpProtocolVersions.HTTP_1_1
|
3477
|
-
):
|
3478
|
-
expects_continue = True
|
3479
|
-
else:
|
3480
|
-
expects_continue = False
|
3481
|
-
|
3482
|
-
# Return
|
3483
|
-
|
3484
|
-
return ParsedHttpRequest(
|
3485
|
-
method=method,
|
3486
|
-
path=path,
|
3487
|
-
expects_continue=expects_continue,
|
3488
|
-
**result_kwargs(),
|
3489
|
-
)
|
3490
|
-
|
3491
|
-
def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
|
3492
|
-
return self._run_read_line_coro(self.coro_parse(), read_line)
|
3493
|
-
|
3494
|
-
|
3495
|
-
########################################
|
3496
|
-
# ../../../omlish/lite/contextmanagers.py
|
3497
|
-
|
3498
|
-
|
3499
|
-
##
|
3500
|
-
|
3501
|
-
|
3502
|
-
class ExitStacked:
|
3503
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3504
|
-
super().__init_subclass__(**kwargs)
|
3505
|
-
|
3506
|
-
for a in ('__enter__', '__exit__'):
|
3507
|
-
for b in cls.__bases__:
|
3508
|
-
if b is ExitStacked:
|
3509
|
-
continue
|
3510
|
-
try:
|
3511
|
-
fn = getattr(b, a)
|
3512
|
-
except AttributeError:
|
3513
|
-
pass
|
3514
|
-
else:
|
3515
|
-
if fn is not getattr(ExitStacked, a):
|
3516
|
-
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
3517
|
-
|
3518
|
-
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
3519
|
-
|
3520
|
-
@contextlib.contextmanager
|
3521
|
-
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
3522
|
-
"""
|
3523
|
-
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
3524
|
-
things like wrapping __enter__ in a lock.
|
3525
|
-
"""
|
3526
|
-
|
3527
|
-
yield
|
3528
|
-
|
3529
|
-
@ta.final
|
3530
|
-
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
3531
|
-
"""
|
3532
|
-
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
3533
|
-
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
3534
|
-
"""
|
3535
|
-
|
3536
|
-
with self._exit_stacked_init_wrapper():
|
3537
|
-
check.state(self._exit_stack is None)
|
3538
|
-
es = self._exit_stack = contextlib.ExitStack()
|
3539
|
-
es.__enter__()
|
3540
|
-
try:
|
3541
|
-
self._enter_contexts()
|
3542
|
-
except Exception: # noqa
|
3543
|
-
es.__exit__(*sys.exc_info())
|
3544
|
-
raise
|
3545
|
-
return self
|
3546
|
-
|
3547
|
-
@ta.final
|
3548
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
3549
|
-
if (es := self._exit_stack) is None:
|
3550
|
-
return None
|
3551
|
-
try:
|
3552
|
-
self._exit_contexts()
|
3553
|
-
except Exception: # noqa
|
3554
|
-
es.__exit__(*sys.exc_info())
|
3555
|
-
raise
|
3556
|
-
return es.__exit__(exc_type, exc_val, exc_tb)
|
3557
|
-
|
3558
|
-
def _enter_contexts(self) -> None:
|
3559
|
-
pass
|
3560
|
-
|
3561
|
-
def _exit_contexts(self) -> None:
|
3562
|
-
pass
|
3563
|
-
|
3564
|
-
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3565
|
-
es = check.not_none(self._exit_stack)
|
3566
|
-
return es.enter_context(cm)
|
3567
|
-
|
3568
|
-
|
3569
|
-
class AsyncExitStacked:
|
3570
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3571
|
-
super().__init_subclass__(**kwargs)
|
3572
|
-
|
3573
|
-
for a in ('__aenter__', '__aexit__'):
|
3574
|
-
for b in cls.__bases__:
|
3575
|
-
if b is AsyncExitStacked:
|
3576
|
-
continue
|
3577
|
-
try:
|
3578
|
-
fn = getattr(b, a)
|
3579
|
-
except AttributeError:
|
3580
|
-
pass
|
3581
|
-
else:
|
3582
|
-
if fn is not getattr(AsyncExitStacked, a):
|
3583
|
-
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
3584
|
-
|
3585
|
-
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
3586
|
-
|
3587
|
-
@contextlib.asynccontextmanager
|
3588
|
-
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
3589
|
-
yield
|
3590
|
-
|
3591
|
-
@ta.final
|
3592
|
-
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
3593
|
-
async with self._async_exit_stacked_init_wrapper():
|
3594
|
-
check.state(self._exit_stack is None)
|
3595
|
-
es = self._exit_stack = contextlib.AsyncExitStack()
|
3596
|
-
await es.__aenter__()
|
3597
|
-
try:
|
3598
|
-
await self._async_enter_contexts()
|
3599
|
-
except Exception: # noqa
|
3600
|
-
await es.__aexit__(*sys.exc_info())
|
3601
|
-
raise
|
3602
|
-
return self
|
3603
|
-
|
3604
|
-
@ta.final
|
3605
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
3606
|
-
if (es := self._exit_stack) is None:
|
3607
|
-
return None
|
3608
|
-
try:
|
3609
|
-
await self._async_exit_contexts()
|
3610
|
-
except Exception: # noqa
|
3611
|
-
await es.__aexit__(*sys.exc_info())
|
3612
|
-
raise
|
3613
|
-
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
3614
|
-
|
3615
|
-
async def _async_enter_contexts(self) -> None:
|
3616
|
-
pass
|
3617
|
-
|
3618
|
-
async def _async_exit_contexts(self) -> None:
|
3619
|
-
pass
|
3620
|
-
|
3621
|
-
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3622
|
-
es = check.not_none(self._exit_stack)
|
3623
|
-
return es.enter_context(cm)
|
3624
|
-
|
3625
|
-
async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
|
3626
|
-
es = check.not_none(self._exit_stack)
|
3627
|
-
return await es.enter_async_context(cm)
|
3628
|
-
|
3629
|
-
|
3630
|
-
##
|
3631
|
-
|
3632
|
-
|
3633
|
-
@contextlib.contextmanager
|
3634
|
-
def defer(fn: ta.Callable, *args: ta.Any, **kwargs: ta.Any) -> ta.Generator[ta.Callable, None, None]:
|
3635
|
-
if args or kwargs:
|
3636
|
-
fn = functools.partial(fn, *args, **kwargs)
|
3637
|
-
try:
|
3638
|
-
yield fn
|
3639
|
-
finally:
|
3640
|
-
fn()
|
3641
|
-
|
3642
|
-
|
3643
|
-
@contextlib.asynccontextmanager
|
3644
|
-
async def adefer(fn: ta.Awaitable) -> ta.AsyncGenerator[ta.Awaitable, None]:
|
3645
|
-
try:
|
3646
|
-
yield fn
|
3647
|
-
finally:
|
3648
|
-
await fn
|
3649
|
-
|
3650
|
-
|
3651
|
-
##
|
3843
|
+
**result_kwargs(),
|
3844
|
+
)
|
3652
3845
|
|
3846
|
+
except http.client.HTTPException as err:
|
3847
|
+
return ParseHttpRequestError(
|
3848
|
+
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
3849
|
+
message=('Too many headers', str(err)),
|
3850
|
+
**result_kwargs(),
|
3851
|
+
)
|
3653
3852
|
|
3654
|
-
|
3655
|
-
def attr_setting(obj, attr, val, *, default=None): # noqa
|
3656
|
-
not_set = object()
|
3657
|
-
orig = getattr(obj, attr, not_set)
|
3658
|
-
try:
|
3659
|
-
setattr(obj, attr, val)
|
3660
|
-
if orig is not not_set:
|
3661
|
-
yield orig
|
3662
|
-
else:
|
3663
|
-
yield default
|
3664
|
-
finally:
|
3665
|
-
if orig is not_set:
|
3666
|
-
delattr(obj, attr)
|
3667
|
-
else:
|
3668
|
-
setattr(obj, attr, orig)
|
3853
|
+
# Check for connection directive
|
3669
3854
|
|
3855
|
+
conn_type = headers.get('Connection', '')
|
3856
|
+
if conn_type.lower() == 'close':
|
3857
|
+
close_connection = True
|
3858
|
+
elif (
|
3859
|
+
conn_type.lower() == 'keep-alive' and
|
3860
|
+
version >= HttpProtocolVersions.HTTP_1_1
|
3861
|
+
):
|
3862
|
+
close_connection = False
|
3670
3863
|
|
3671
|
-
|
3864
|
+
# Check for expect directive
|
3672
3865
|
|
3866
|
+
expect = headers.get('Expect', '')
|
3867
|
+
if (
|
3868
|
+
expect.lower() == '100-continue' and
|
3869
|
+
version >= HttpProtocolVersions.HTTP_1_1
|
3870
|
+
):
|
3871
|
+
expects_continue = True
|
3872
|
+
else:
|
3873
|
+
expects_continue = False
|
3673
3874
|
|
3674
|
-
|
3675
|
-
def __init__(self, thing):
|
3676
|
-
self.thing = thing
|
3875
|
+
# Return
|
3677
3876
|
|
3678
|
-
|
3679
|
-
|
3877
|
+
return ParsedHttpRequest(
|
3878
|
+
method=method,
|
3879
|
+
path=path,
|
3880
|
+
expects_continue=expects_continue,
|
3881
|
+
**result_kwargs(),
|
3882
|
+
)
|
3680
3883
|
|
3681
|
-
|
3682
|
-
|
3884
|
+
def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
|
3885
|
+
return self._run_read_line_coro(self.coro_parse(), read_line)
|
3683
3886
|
|
3684
3887
|
|
3685
3888
|
########################################
|
@@ -7374,204 +7577,6 @@ def get_single_oci_image_manifest(image_index: OciImageIndex) -> OciImageManifes
|
|
7374
7577
|
return check.isinstance(child_index, OciImageManifest)
|
7375
7578
|
|
7376
7579
|
|
7377
|
-
########################################
|
7378
|
-
# ../../oci/pack/unpacking.py
|
7379
|
-
|
7380
|
-
|
7381
|
-
##
|
7382
|
-
|
7383
|
-
|
7384
|
-
class OciLayerUnpacker(ExitStacked):
|
7385
|
-
def __init__(
|
7386
|
-
self,
|
7387
|
-
input_files: ta.Sequence[ta.Union[str, tarfile.TarFile]],
|
7388
|
-
output_file_path: str,
|
7389
|
-
) -> None:
|
7390
|
-
super().__init__()
|
7391
|
-
|
7392
|
-
self._input_files = list(input_files)
|
7393
|
-
self._output_file_path = output_file_path
|
7394
|
-
|
7395
|
-
#
|
7396
|
-
|
7397
|
-
@contextlib.contextmanager
|
7398
|
-
def _open_input_file(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Iterator[tarfile.TarFile]:
|
7399
|
-
if isinstance(input_file, tarfile.TarFile):
|
7400
|
-
yield input_file
|
7401
|
-
|
7402
|
-
elif isinstance(input_file, str):
|
7403
|
-
with tarfile.open(input_file) as tar_file:
|
7404
|
-
yield tar_file
|
7405
|
-
|
7406
|
-
else:
|
7407
|
-
raise TypeError(input_file)
|
7408
|
-
|
7409
|
-
#
|
7410
|
-
|
7411
|
-
class _Entry(ta.NamedTuple):
|
7412
|
-
file: ta.Union[str, tarfile.TarFile]
|
7413
|
-
info: tarfile.TarInfo
|
7414
|
-
|
7415
|
-
def _build_input_file_sorted_entries(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Sequence[_Entry]:
|
7416
|
-
dct: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
7417
|
-
|
7418
|
-
with self._open_input_file(input_file) as input_tar_file:
|
7419
|
-
for info in input_tar_file.getmembers():
|
7420
|
-
check.not_in(info.name, dct)
|
7421
|
-
dct[info.name] = self._Entry(
|
7422
|
-
file=input_file,
|
7423
|
-
info=info,
|
7424
|
-
)
|
7425
|
-
|
7426
|
-
return sorted(dct.values(), key=lambda entry: entry.info.name)
|
7427
|
-
|
7428
|
-
@cached_nullary
|
7429
|
-
def _entries_by_name(self) -> ta.Mapping[str, _Entry]:
|
7430
|
-
root: dict = {}
|
7431
|
-
|
7432
|
-
def find_dir(dir_name: str) -> dict: # noqa
|
7433
|
-
if dir_name:
|
7434
|
-
dir_parts = dir_name.split('/')
|
7435
|
-
else:
|
7436
|
-
dir_parts = []
|
7437
|
-
|
7438
|
-
cur = root # noqa
|
7439
|
-
for dir_part in dir_parts:
|
7440
|
-
cur = cur[dir_part] # noqa
|
7441
|
-
|
7442
|
-
return check.isinstance(cur, dict)
|
7443
|
-
|
7444
|
-
#
|
7445
|
-
|
7446
|
-
for input_file in self._input_files:
|
7447
|
-
sorted_entries = self._build_input_file_sorted_entries(input_file)
|
7448
|
-
|
7449
|
-
wh_names = set()
|
7450
|
-
wh_opaques = set()
|
7451
|
-
|
7452
|
-
#
|
7453
|
-
|
7454
|
-
for entry in sorted_entries:
|
7455
|
-
info = entry.info
|
7456
|
-
name = check.non_empty_str(info.name)
|
7457
|
-
base_name = os.path.basename(name)
|
7458
|
-
dir_name = os.path.dirname(name)
|
7459
|
-
|
7460
|
-
if base_name == '.wh..wh..opq':
|
7461
|
-
wh_opaques.add(dir_name)
|
7462
|
-
continue
|
7463
|
-
|
7464
|
-
if base_name.startswith('.wh.'):
|
7465
|
-
wh_base_name = os.path.basename(base_name[4:])
|
7466
|
-
wh_name = os.path.join(dir_name, wh_base_name)
|
7467
|
-
wh_names.add(wh_name)
|
7468
|
-
continue
|
7469
|
-
|
7470
|
-
cur = find_dir(dir_name)
|
7471
|
-
|
7472
|
-
if info.type == tarfile.DIRTYPE:
|
7473
|
-
try:
|
7474
|
-
ex = cur[base_name]
|
7475
|
-
except KeyError:
|
7476
|
-
cur[base_name] = {'': entry}
|
7477
|
-
else:
|
7478
|
-
ex[''] = entry
|
7479
|
-
|
7480
|
-
else:
|
7481
|
-
cur[base_name] = entry
|
7482
|
-
|
7483
|
-
#
|
7484
|
-
|
7485
|
-
for wh_name in reversed(sorted(wh_names)): # noqa
|
7486
|
-
wh_dir_name = os.path.dirname(wh_name)
|
7487
|
-
wh_base_name = os.path.basename(wh_name)
|
7488
|
-
|
7489
|
-
cur = find_dir(wh_dir_name)
|
7490
|
-
rm = cur[wh_base_name]
|
7491
|
-
|
7492
|
-
if isinstance(rm, dict):
|
7493
|
-
# Whiteouts wipe out whole directory:
|
7494
|
-
# https://github.com/containerd/containerd/blob/59c8cf6ea5f4175ad512914dd5ce554942bf144f/pkg/archive/tar_test.go#L648
|
7495
|
-
# check.equal(set(rm), '')
|
7496
|
-
del cur[wh_base_name]
|
7497
|
-
|
7498
|
-
elif isinstance(rm, self._Entry):
|
7499
|
-
del cur[wh_base_name]
|
7500
|
-
|
7501
|
-
else:
|
7502
|
-
raise TypeError(rm)
|
7503
|
-
|
7504
|
-
if wh_opaques:
|
7505
|
-
raise NotImplementedError
|
7506
|
-
|
7507
|
-
#
|
7508
|
-
|
7509
|
-
out: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
7510
|
-
|
7511
|
-
def rec(cur): # noqa
|
7512
|
-
for _, child in sorted(cur.items(), key=lambda t: t[0]):
|
7513
|
-
if isinstance(child, dict):
|
7514
|
-
rec(child)
|
7515
|
-
|
7516
|
-
elif isinstance(child, self._Entry):
|
7517
|
-
check.not_in(child.info.name, out)
|
7518
|
-
out[child.info.name] = child
|
7519
|
-
|
7520
|
-
else:
|
7521
|
-
raise TypeError(child)
|
7522
|
-
|
7523
|
-
rec(root)
|
7524
|
-
|
7525
|
-
return out
|
7526
|
-
|
7527
|
-
#
|
7528
|
-
|
7529
|
-
@cached_nullary
|
7530
|
-
def _output_tar_file(self) -> tarfile.TarFile:
|
7531
|
-
return self._enter_context(tarfile.open(self._output_file_path, 'w'))
|
7532
|
-
|
7533
|
-
#
|
7534
|
-
|
7535
|
-
def _add_unpacked_entry(
|
7536
|
-
self,
|
7537
|
-
input_tar_file: tarfile.TarFile,
|
7538
|
-
info: tarfile.TarInfo,
|
7539
|
-
) -> None:
|
7540
|
-
base_name = os.path.basename(info.name)
|
7541
|
-
check.state(not base_name.startswith('.wh.'))
|
7542
|
-
|
7543
|
-
if info.type in tarfile.REGULAR_TYPES:
|
7544
|
-
with check.not_none(input_tar_file.extractfile(info)) as f:
|
7545
|
-
self._output_tar_file().addfile(info, f)
|
7546
|
-
|
7547
|
-
else:
|
7548
|
-
self._output_tar_file().addfile(info)
|
7549
|
-
|
7550
|
-
def _unpack_file(
|
7551
|
-
self,
|
7552
|
-
input_file: ta.Union[str, tarfile.TarFile],
|
7553
|
-
) -> None:
|
7554
|
-
entries_by_name = self._entries_by_name()
|
7555
|
-
|
7556
|
-
with self._open_input_file(input_file) as input_tar_file:
|
7557
|
-
info: tarfile.TarInfo
|
7558
|
-
for info in input_tar_file.getmembers():
|
7559
|
-
try:
|
7560
|
-
entry = entries_by_name[info.name]
|
7561
|
-
except KeyError:
|
7562
|
-
continue
|
7563
|
-
|
7564
|
-
if entry.file != input_file:
|
7565
|
-
continue
|
7566
|
-
|
7567
|
-
self._add_unpacked_entry(input_tar_file, info)
|
7568
|
-
|
7569
|
-
@cached_nullary
|
7570
|
-
def write(self) -> None:
|
7571
|
-
for input_file in self._input_files:
|
7572
|
-
self._unpack_file(input_file)
|
7573
|
-
|
7574
|
-
|
7575
7580
|
########################################
|
7576
7581
|
# ../../oci/repositories.py
|
7577
7582
|
|
omdev/tools/git/messages.py
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
import abc
|
2
|
-
import os
|
3
2
|
import typing as ta
|
4
3
|
|
5
4
|
from omlish import cached
|
@@ -54,10 +53,7 @@ class StaticGitMessageGeneratorManifest(StaticModAttrManifest, GitMessageGenerat
|
|
54
53
|
|
55
54
|
@cached.function
|
56
55
|
def load_message_generator_manifests() -> ta.Sequence[GitMessageGeneratorManifest]:
|
57
|
-
|
58
|
-
pkgs = ldr.scan_or_discover_packages(fallback_root_dir=os.getcwd())
|
59
|
-
mfs = ldr.load(*pkgs, only=[GitMessageGeneratorManifest])
|
60
|
-
return [mf.value() for mf in mfs]
|
56
|
+
return manifest_globals.GlobalManifestLoader.load_values_of(GitMessageGeneratorManifest)
|
61
57
|
|
62
58
|
|
63
59
|
@cached.function
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: omdev
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev405
|
4
4
|
Summary: omdev
|
5
5
|
Author: wrmsr
|
6
6
|
License-Expression: BSD-3-Clause
|
@@ -14,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Requires-Python: >=3.13
|
15
15
|
Description-Content-Type: text/markdown
|
16
16
|
License-File: LICENSE
|
17
|
-
Requires-Dist: omlish==0.0.0.
|
17
|
+
Requires-Dist: omlish==0.0.0.dev405
|
18
18
|
Provides-Extra: all
|
19
19
|
Requires-Dist: black~=25.1; extra == "all"
|
20
20
|
Requires-Dist: pycparser~=2.22; extra == "all"
|
@@ -1,4 +1,4 @@
|
|
1
|
-
omdev/.manifests.json,sha256=
|
1
|
+
omdev/.manifests.json,sha256=1muOc0K_eLWBw9EE5ei6tFo4OkLFp43sSNnz9AF5m_s,12453
|
2
2
|
omdev/__about__.py,sha256=fQNmzSa1MntcPSrzg_Vpo6JRU2RbXik2NqRz0oQCApE,1202
|
3
3
|
omdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
4
|
omdev/cmake.py,sha256=9rfSvFHPmKDj9ngvfDB2vK8O-xO_ZwUm7hMKLWA-yOw,4578
|
@@ -113,7 +113,7 @@ omdev/cli/__main__.py,sha256=mOJpgc07o0r5luQ1DlX4tk2PqZkgmbwPbdzJ3KmtjgQ,138
|
|
113
113
|
omdev/cli/_pathhack.py,sha256=UshIZX3oeXq0De-9X28gy2LgKMZDf_dzabdkUhZJdNA,2124
|
114
114
|
omdev/cli/clicli.py,sha256=TZnQYHyyh97B6N_pVYYduYgt03s8Yp5mrJ7wblXWSWY,6308
|
115
115
|
omdev/cli/install.py,sha256=oB34AOwu07sqEztW_z5mgorAFoP_Tw556XiTPj2WSM0,4904
|
116
|
-
omdev/cli/main.py,sha256=
|
116
|
+
omdev/cli/main.py,sha256=nuRpsHB7W-Z0SU_wgEIHPFaMY2sOxiNkY9rnEek99Rs,6905
|
117
117
|
omdev/cli/managers.py,sha256=BV98_n30Jj63OJrFgRoVZRfICxMLXEZKoEn4rMj9LV4,1160
|
118
118
|
omdev/cli/types.py,sha256=zRSq-SVr43zhXs8dTgkpD9kot9iOFJB8KzfEUnNE7_0,500
|
119
119
|
omdev/clipboard/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -218,7 +218,7 @@ omdev/precheck/git.py,sha256=O8rNQZ_vlHec0pOFbK6LOkbly5ZIUYT_HXRMqQX8GaI,774
|
|
218
218
|
omdev/precheck/imports.py,sha256=3loQxHMrpI0ce4-le77NCSxutLac_5vDW4UDX7KWWg8,2565
|
219
219
|
omdev/precheck/lite.py,sha256=qd6nXWEVut8aBSRD_NxnxXGRNa9ue8mu8ND8rGLisE4,4710
|
220
220
|
omdev/precheck/main.py,sha256=_1A5wiu9p2th1dn_17w1ZIFtMmCIOaTFpWyvK0jopEA,4374
|
221
|
-
omdev/precheck/manifests.py,sha256=
|
221
|
+
omdev/precheck/manifests.py,sha256=dxl7GSJHKjQrR6mbwvj6j92XDGHOpxxEEQ6smJkBEe4,810
|
222
222
|
omdev/precheck/scripts.py,sha256=6nb_lDgyX7u9kdF_BU6ubY01q_jGk96VH9q9gpOieng,1753
|
223
223
|
omdev/precheck/unicode.py,sha256=VUNDCrlfUas_U8ugV_q0eFUXuBgKjS8YdCFm0FXREXo,2583
|
224
224
|
omdev/ptk/__init__.py,sha256=4zhIfvhebFj4TJRRN1SQkcAe-7elizcfZLsD-fIlZsI,5198
|
@@ -252,7 +252,7 @@ omdev/py/docstrings/parser.py,sha256=umJEgQBkSXLwWIKZrBbFCfNrz847vpTNDqTTEwlvHpA
|
|
252
252
|
omdev/py/docstrings/rest.py,sha256=c2xPYg_W01W9eYY_KLKX69E4qu4jpkgUshi5K5EyVv8,5221
|
253
253
|
omdev/py/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
254
254
|
omdev/py/scripts/bumpversion.py,sha256=UDMtTd_wfkqsmrtHqTKDilWVzUBigzZAOCVND7t9jyw,1124
|
255
|
-
omdev/py/scripts/execstat.py,sha256=
|
255
|
+
omdev/py/scripts/execstat.py,sha256=a7KTmBW3IYgvcZR3u09_5LQcte6IgJaqzUN-LnaktIA,4577
|
256
256
|
omdev/py/scripts/importtrace.py,sha256=Plh2ixN8NxTekrNoac6l9wg9B4x1647sEOvQTSy_Hbk,14256
|
257
257
|
omdev/py/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
258
258
|
omdev/py/tools/importscan.py,sha256=4dCH0coX0OqNwesteKaTE8GxuSfLhgXYQlzNUXLiSNY,4640
|
@@ -270,7 +270,7 @@ omdev/pyproject/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
270
270
|
omdev/pyproject/resources/docker-dev.sh,sha256=DHkz5D18jok_oDolfg2mqrvGRWFoCe9GQo04dR1czcc,838
|
271
271
|
omdev/pyproject/resources/python.sh,sha256=rFaN4SiJ9hdLDXXsDTwugI6zsw6EPkgYMmtacZeTbvw,749
|
272
272
|
omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
|
273
|
-
omdev/scripts/ci.py,sha256=
|
273
|
+
omdev/scripts/ci.py,sha256=fqphFyV5xdBNC5QtxmGmCWL__na7c7E2ygWloRJBNZo,362813
|
274
274
|
omdev/scripts/interp.py,sha256=8FR5-a95K1bL7YHNBiX1XVDPYhqw_jTWz8mVNDts6pI,158710
|
275
275
|
omdev/scripts/pyproject.py,sha256=qgAW607OVKaeGma7XuxzyhuFyJLoKIcT9nZ52cl3KKU,270298
|
276
276
|
omdev/scripts/slowcat.py,sha256=lssv4yrgJHiWfOiHkUut2p8E8Tq32zB-ujXESQxFFHY,2728
|
@@ -303,7 +303,7 @@ omdev/tools/git/__main__.py,sha256=gI87SBUgTkKUcUM-RtZWnei-UUDDqzbr5aPztb-gvbE,1
|
|
303
303
|
omdev/tools/git/cli.py,sha256=I4AiCTz4OCzK6s8J2TJF1eYw9FH2JIK_CsdjQH_9UTE,16757
|
304
304
|
omdev/tools/git/cloning.py,sha256=CNGBBMoWaTBJW4SZTf1VvhDFSm0yg7qDfNwZun_PFbU,891
|
305
305
|
omdev/tools/git/consts.py,sha256=JuXivUNDkNhM4pe97icjRVAKM8cNRbrODquHINNKqOE,40
|
306
|
-
omdev/tools/git/messages.py,sha256=
|
306
|
+
omdev/tools/git/messages.py,sha256=a3E85r3uGbT0Puo6coBwmm7sj58k8_drYyxuxeH068U,2311
|
307
307
|
omdev/tools/json/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
308
308
|
omdev/tools/json/__main__.py,sha256=wqpkN_NsQyNwKW4qjVj8ADJ4_C98KhrFBtE-Z1UamfU,168
|
309
309
|
omdev/tools/json/cli.py,sha256=8aXX3ijU3lvPZamkIyUOz-vlBmyIdaX7dCQq5rN7adE,10193
|
@@ -321,9 +321,9 @@ omdev/tools/jsonview/resources/jsonview.js,sha256=faDvXDOXKvEvjOuIlz4D3F2ReQXb_b
|
|
321
321
|
omdev/tools/pawk/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
322
322
|
omdev/tools/pawk/__main__.py,sha256=VCqeRVnqT1RPEoIrqHFSu4PXVMg4YEgF4qCQm90-eRI,66
|
323
323
|
omdev/tools/pawk/pawk.py,sha256=ao5mdrpiSU4AZ8mBozoEaV3UVlmVTnRG9wD9XP70MZE,11429
|
324
|
-
omdev-0.0.0.
|
325
|
-
omdev-0.0.0.
|
326
|
-
omdev-0.0.0.
|
327
|
-
omdev-0.0.0.
|
328
|
-
omdev-0.0.0.
|
329
|
-
omdev-0.0.0.
|
324
|
+
omdev-0.0.0.dev405.dist-info/licenses/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
|
325
|
+
omdev-0.0.0.dev405.dist-info/METADATA,sha256=xCQhveQmUAKSQYqRtIyyyWfMmkpNxwaQkqhGVEy33L4,5094
|
326
|
+
omdev-0.0.0.dev405.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
327
|
+
omdev-0.0.0.dev405.dist-info/entry_points.txt,sha256=dHLXFmq5D9B8qUyhRtFqTGWGxlbx3t5ejedjrnXNYLU,33
|
328
|
+
omdev-0.0.0.dev405.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
|
329
|
+
omdev-0.0.0.dev405.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|