omdev 0.0.0.dev11__py3-none-any.whl → 0.0.0.dev13__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.

Potentially problematic release.


This version of omdev might be problematic. Click here for more details.

omdev/__about__.py CHANGED
@@ -13,17 +13,21 @@ class Project(ProjectBase):
13
13
 
14
14
  optional_dependencies = {
15
15
  'c': [
16
- 'pycparser >= 2.22',
17
- 'cffi >= 1.17',
18
- 'pcpp >= 1.30',
16
+ 'pycparser ~= 2.22',
17
+ 'cffi ~= 1.17',
18
+ 'pcpp ~= 1.30',
19
19
  ],
20
20
 
21
21
  'mypy': [
22
- 'mypy >= 1.11',
22
+ 'mypy ~= 1.11',
23
23
  ],
24
24
 
25
25
  'tokens': [
26
- 'tokenize_rt >= 6',
26
+ 'tokenize_rt ~= 6.0',
27
+ ],
28
+
29
+ 'wheel': [
30
+ 'wheel ~= 0.44',
27
31
  ],
28
32
  }
29
33
 
omdev/amalg/amalg.py CHANGED
@@ -363,13 +363,25 @@ def gen_amalg(
363
363
 
364
364
  ##
365
365
 
366
+ tyd: dict[str, list[Typing]] = {}
366
367
  tys = set()
367
368
  for sf in sfs:
368
369
  f = src_files[sf]
369
370
  for ty in f.typings:
370
371
  if ty.src not in tys:
371
- out.write(ty.src)
372
+ tyd.setdefault(f.path, []).append(ty)
372
373
  tys.add(ty.src)
374
+ for i, (sf, ftys) in enumerate(tyd.items()):
375
+ f = src_files[sf]
376
+ if i:
377
+ out.write('\n')
378
+ if f is not mf:
379
+ rp = os.path.relpath(f.path, mf.path)
380
+ else:
381
+ rp = os.path.basename(f.path)
382
+ out.write(f'# {rp}\n')
383
+ for ty in ftys:
384
+ out.write(ty.src)
373
385
  if tys:
374
386
  out.write('\n\n')
375
387
 
omdev/cmake.py CHANGED
@@ -1,5 +1,4 @@
1
1
  import abc
2
- import io
3
2
  import typing as ta
4
3
 
5
4
  from omlish import dataclasses as dc
@@ -80,7 +79,7 @@ class CmakeGen:
80
79
 
81
80
  def __init__(
82
81
  self,
83
- out: io.TextIOBase,
82
+ out: ta.TextIO,
84
83
  *,
85
84
  indent: int = 4,
86
85
  ) -> None:
omdev/exts/cmake.py CHANGED
@@ -9,6 +9,7 @@ TODO:
9
9
  - json? https://github.com/nlohmann/json
10
10
  - FindPackages? FetchContent? built_ext won't have that
11
11
  - move omml git / data retriever stuff into omdev, get just the one header file from git via sha?
12
+ - support local built pys
12
13
 
13
14
  ==
14
15
 
@@ -17,191 +18,324 @@ Done:
17
18
  - aight, generate a whole cmake subdir with symlinks to src files lol
18
19
 
19
20
  """ # noqa
21
+ import argparse
22
+ import dataclasses as dc
20
23
  import io
21
24
  import logging
22
25
  import os.path
23
26
  import shutil
24
27
  import sys
25
28
  import sysconfig
29
+ import typing as ta
26
30
 
27
31
  from omlish import check
32
+ from omlish import lang
28
33
  from omlish import logs
29
34
 
30
35
  from .. import cmake
36
+ from ..scripts import findmagic
31
37
 
32
38
 
33
39
  log = logging.getLogger(__name__)
34
40
 
35
41
 
36
- def _main() -> None:
37
- logs.configure_standard_logging('INFO')
42
+ ##
43
+
44
+
45
+ MAGIC = '@omdev-ext'
46
+ MAGIC_COMMENT = f'// {MAGIC}'
47
+
48
+ FILE_EXTENSIONS = ('c', 'cc', 'cpp')
49
+
50
+
51
+ def _sep_str_grps(*ls: ta.Sequence[str]) -> list[str]:
52
+ o = []
53
+ for i, l in enumerate(ls):
54
+ if not l:
55
+ continue
56
+ if i:
57
+ o.append('')
58
+ o.extend(check.not_isinstance(l, str))
59
+ return o
60
+
61
+
62
+ class CmakeProjectGen:
63
+ def __init__(
64
+ self,
65
+ exts: ta.Sequence[str],
66
+ prj_root: str | None = None,
67
+ ) -> None:
68
+ super().__init__()
69
+ self._exts = check.not_isinstance(exts, str)
70
+ self._prj_root = os.path.abspath(prj_root) if prj_root is not None else os.getcwd()
71
+
72
+ #
73
+
74
+ @property
75
+ def prj_root(self) -> str:
76
+ return self._prj_root
77
+
78
+ @lang.cached_function
79
+ def prj_name(self) -> str:
80
+ return os.path.basename(self.prj_root)
81
+
82
+ @lang.cached_function
83
+ def cmake_dir(self) -> str:
84
+ cmake_dir = os.path.join(self.prj_root, 'cmake')
85
+ if os.path.exists(cmake_dir):
86
+ for e in os.listdir(cmake_dir):
87
+ if e == '.idea':
88
+ continue
89
+ ep = os.path.join(cmake_dir, e)
90
+ if os.path.isfile(ep):
91
+ os.unlink(ep)
92
+ else:
93
+ shutil.rmtree(ep)
94
+ else:
95
+ os.mkdir(cmake_dir)
96
+ return cmake_dir
97
+
98
+ #
99
+
100
+ def write_git_ignore(self) -> None:
101
+ with open(os.path.join(self.cmake_dir(), '.gitignore'), 'w') as f:
102
+ f.write('\n'.join(sorted(['/cmake-*', '/build'])))
103
+
104
+ #
105
+
106
+ def write_idea_name(self) -> None:
107
+ idea_dir = os.path.join(self.cmake_dir(), '.idea')
108
+ if not os.path.isdir(idea_dir):
109
+ os.mkdir(idea_dir)
110
+ idea_name_file = os.path.join(idea_dir, '.name')
111
+ if not os.path.isfile(idea_name_file):
112
+ with open(idea_name_file, 'w') as f:
113
+ f.write(self.prj_name())
38
114
 
39
- prj_root = os.path.abspath(os.getcwd())
40
- if not os.path.isfile(os.path.join(prj_root, 'pyproject.toml')):
41
- raise Exception('Must be run in project root')
42
-
43
- cmake_dir = os.path.join(prj_root, 'cmake')
44
- if os.path.exists(cmake_dir):
45
- for e in os.listdir(cmake_dir):
46
- if e == '.idea':
47
- continue
48
- ep = os.path.join(cmake_dir, e)
49
- if os.path.isfile(ep):
50
- os.unlink(ep)
115
+ #
116
+
117
+ @dc.dataclass(frozen=True, kw_only=True)
118
+ class PyInfo:
119
+ venv_exe: str
120
+ venv_root: str
121
+ real_exe: str
122
+ root: str
123
+ suffix: str
124
+
125
+ @lang.cached_function
126
+ def py_info(self) -> PyInfo:
127
+ venv_exe = sys.executable
128
+ real_exe = os.path.realpath(venv_exe)
129
+ return self.PyInfo(
130
+ venv_exe=venv_exe,
131
+ venv_root=os.path.abspath(os.path.join(os.path.dirname(venv_exe), '..')),
132
+ real_exe=real_exe,
133
+ root=os.path.abspath(os.path.join(os.path.dirname(real_exe), '..')),
134
+ suffix='.'.join(map(str, sys.version_info[:2])),
135
+ )
136
+
137
+ #
138
+
139
+ @lang.cached_function
140
+ def ext_files(self) -> ta.Sequence[str]:
141
+ out = []
142
+ for e in self._exts:
143
+ e = os.path.abspath(e)
144
+ if os.path.isfile(e):
145
+ out.append(e)
146
+ elif os.path.isdir(e):
147
+ out.extend(
148
+ findmagic.find_magic(
149
+ [e],
150
+ [MAGIC_COMMENT],
151
+ FILE_EXTENSIONS,
152
+ ),
153
+ )
51
154
  else:
52
- shutil.rmtree(ep)
53
- else:
54
- os.mkdir(cmake_dir)
55
-
56
- with open(os.path.join(cmake_dir, '.gitignore'), 'w') as f:
57
- f.write('\n'.join(sorted(['/cmake-*', '/build'])))
58
-
59
- # idea_dir = os.path.join(cmake_dir, '.idea')
60
- # if not os.path.isdir(idea_dir):
61
- # os.mkdir(idea_dir)
62
- # idea_name_file = os.path.join(idea_dir, '.name')
63
- # if not os.path.isfile(idea_name_file):
64
- # with open(idea_name_file, 'w') as f:
65
- # f.write('omlish')
66
-
67
- venv_exe = sys.executable
68
- venv_root = os.path.abspath(os.path.join(os.path.dirname(venv_exe), '..'))
69
- real_exe = os.path.realpath(venv_exe)
70
- py_root = os.path.abspath(os.path.join(os.path.dirname(real_exe), '..'))
71
- py_sfx = '.'.join(map(str, sys.version_info[:2]))
72
-
73
- out = io.StringIO()
74
- gen = cmake.CmakeGen(out)
75
-
76
- prj_name = 'omlish'
77
- var_prefix = prj_name.upper()
78
-
79
- gen.write(gen.preamble)
80
- gen.write('')
81
-
82
- gen.write(f'project({prj_name})')
83
- gen.write('')
84
-
85
- def sep_grps(*ls):
86
- # itertools.interleave? or smth?
87
- o = []
88
- for i, l in enumerate(ls):
89
- if not l:
90
- continue
91
- if i:
92
- o.append('')
93
- o.extend(check.not_isinstance(l, str))
94
- return o
95
-
96
- gen.write_var(cmake.Var(
97
- f'{var_prefix}_INCLUDE_DIRECTORIES',
98
- sep_grps(
99
- [f'{venv_root}/include'],
100
- [f'{py_root}/include/python{py_sfx}'],
101
- [
102
- # $ENV{HOME}/src/python/cpython
103
- # $ENV{HOME}/src/python/cpython/include
104
- ],
105
- ),
106
- ))
107
-
108
- gen.write_var(cmake.Var(
109
- f'{var_prefix}_COMPILE_OPTIONS',
110
- sep_grps(
111
- [
112
- '-Wsign-compare',
113
- '-Wunreachable-code',
114
- '-DNDEBUG',
115
- '-g',
116
- '-fwrapv',
117
- '-O3',
118
- '-Wall',
119
- ],
120
- [
121
- '-g',
122
- '-c',
123
- ],
124
- ['-std=c++20'],
125
- ),
126
- ))
127
-
128
- gen.write_var(cmake.Var(
129
- f'{var_prefix}_LINK_DIRECTORIES',
130
- sep_grps(
131
- [f'{py_root}/lib'],
132
- # ['$ENV{HOME}/src/python/cpython'],
133
- ),
134
- ))
135
-
136
- gen.write_var(cmake.Var(
137
- f'{var_prefix}_LINK_LIBRARIES',
138
- sep_grps(
139
- *([[
140
- '-bundle',
141
- '"-undefined dynamic_lookup"',
142
- ]] if sys.platform == 'darwin' else []),
143
- ),
144
- ))
145
-
146
- for ext_src in [
147
- 'omdev/exts/_boilerplate.cc',
148
- 'x/dev/c/junk.cc',
149
- 'x/dev/c/_uuid.cc',
150
- 'x/dev/c/csv/_csvloader.cc',
151
- ]:
152
- ext_name = ext_src.rpartition('.')[0].replace('/', '__')
153
-
154
- log.info('Adding cmake c extension: %s -> %s', ext_src, ext_name)
155
-
156
- so_name = ''.join([
157
- os.path.basename(ext_src).split('.')[0],
158
- '.',
159
- sysconfig.get_config_var('SOABI'),
160
- sysconfig.get_config_var('SHLIB_SUFFIX'),
161
- ])
162
-
163
- sl = os.path.join(cmake_dir, ext_src)
164
- sal = os.path.abspath(sl)
165
- sd = os.path.dirname(sal)
166
- os.makedirs(sd, exist_ok=True)
167
- rp = os.path.relpath(os.path.abspath(ext_src), sd)
168
- os.symlink(rp, sal)
169
-
170
- gen.write_target(cmake.ModuleLibrary(
171
- ext_name,
172
- src_files=[
173
- sl,
174
- ],
175
- include_dirs=[
176
- f'${{{var_prefix}_INCLUDE_DIRECTORIES}}',
177
- ],
178
- compile_opts=[
179
- f'${{{var_prefix}_COMPILE_OPTIONS}}',
180
- ],
181
- link_dirs=[
182
- f'${{{var_prefix}_LINK_DIRECTORIES}}',
183
- ],
184
- link_libs=[
185
- f'${{{var_prefix}_LINK_LIBRARIES}}',
186
- ],
187
- extra_cmds=[
188
- cmake.Command(
189
- 'add_custom_command',
190
- ['TARGET', ext_name, 'POST_BUILD'],
155
+ raise KeyError(e)
156
+ return out
157
+
158
+ #
159
+
160
+ class _CmakeListsGen:
161
+ def __init__(
162
+ self,
163
+ p: 'CmakeProjectGen',
164
+ out: ta.TextIO,
165
+ ) -> None:
166
+ super().__init__()
167
+ self.p = p
168
+ self.g = cmake.CmakeGen(out)
169
+
170
+ @lang.cached_property
171
+ def var_prefix(self) -> str:
172
+ return self.p.prj_name().upper()
173
+
174
+ @lang.cached_property
175
+ def py(self) -> 'CmakeProjectGen.PyInfo':
176
+ return self.p.py_info()
177
+
178
+ def _add_ext(self, ext_src: str) -> None:
179
+ ext_name = ext_src.rpartition('.')[0].replace('/', '__')
180
+
181
+ log.info('Adding cmake c extension: %s -> %s', ext_src, ext_name)
182
+
183
+ so_name = ''.join([
184
+ os.path.basename(ext_src).split('.')[0],
185
+ '.',
186
+ sysconfig.get_config_var('SOABI'),
187
+ sysconfig.get_config_var('SHLIB_SUFFIX'),
188
+ ])
189
+
190
+ sl = os.path.join(self.p.cmake_dir(), ext_src)
191
+ sal = os.path.abspath(sl)
192
+ sd = os.path.dirname(sal)
193
+ os.makedirs(sd, exist_ok=True)
194
+ rp = os.path.relpath(os.path.abspath(ext_src), sd)
195
+ os.symlink(rp, sal)
196
+
197
+ ml = cmake.ModuleLibrary(
198
+ ext_name,
199
+ src_files=[
200
+ sl,
201
+ ],
202
+ include_dirs=[
203
+ f'${{{self.var_prefix}_INCLUDE_DIRECTORIES}}',
204
+ ],
205
+ compile_opts=[
206
+ f'${{{self.var_prefix}_COMPILE_OPTIONS}}',
207
+ ],
208
+ link_dirs=[
209
+ f'${{{self.var_prefix}_LINK_DIRECTORIES}}',
210
+ ],
211
+ link_libs=[
212
+ f'${{{self.var_prefix}_LINK_LIBRARIES}}',
213
+ ],
214
+ extra_cmds=[
215
+ cmake.Command(
216
+ 'add_custom_command',
217
+ ['TARGET', ext_name, 'POST_BUILD'],
218
+ [
219
+ ' '.join([
220
+ 'COMMAND ${CMAKE_COMMAND} -E ',
221
+ f'copy $<TARGET_FILE_NAME:{ext_name}> ../../{os.path.dirname(ext_src)}/{so_name}',
222
+ ]),
223
+ 'COMMAND_EXPAND_LISTS',
224
+ ],
225
+ ),
226
+ ],
227
+ )
228
+ self.g.write_target(ml)
229
+
230
+ def run(self) -> None:
231
+ self.g.write(self.g.preamble)
232
+ self.g.write('')
233
+
234
+ self.g.write(f'project({self.p.prj_name()})')
235
+ self.g.write('')
236
+
237
+ self.g.write_var(cmake.Var(
238
+ f'{self.var_prefix}_INCLUDE_DIRECTORIES',
239
+ _sep_str_grps(
240
+ [f'{self.py.venv_root}/include'],
241
+ [f'{self.py.root}/include/python{self.py.suffix}'],
242
+ [
243
+ # $ENV{HOME}/src/python/cpython
244
+ # $ENV{HOME}/src/python/cpython/include
245
+ ],
246
+ ),
247
+ ))
248
+
249
+ self.g.write_var(cmake.Var(
250
+ f'{self.var_prefix}_COMPILE_OPTIONS',
251
+ _sep_str_grps(
252
+ [
253
+ '-Wsign-compare',
254
+ '-Wunreachable-code',
255
+ '-DNDEBUG',
256
+ '-g',
257
+ '-fwrapv',
258
+ '-O3',
259
+ '-Wall',
260
+ ],
191
261
  [
192
- ' '.join([
193
- 'COMMAND ${CMAKE_COMMAND} -E ',
194
- f'copy $<TARGET_FILE_NAME:{ext_name}> ../../{os.path.dirname(ext_src)}/{so_name}',
195
- ]),
196
- 'COMMAND_EXPAND_LISTS',
262
+ '-g',
263
+ '-c',
197
264
  ],
265
+ ['-std=c++20'],
266
+ ),
267
+ ))
268
+
269
+ self.g.write_var(cmake.Var(
270
+ f'{self.var_prefix}_LINK_DIRECTORIES',
271
+ _sep_str_grps(
272
+ [f'{self.py.root}/lib'],
273
+ # ['$ENV{HOME}/src/python/cpython'],
274
+ ),
275
+ ))
276
+
277
+ self.g.write_var(cmake.Var(
278
+ f'{self.var_prefix}_LINK_LIBRARIES',
279
+ _sep_str_grps(
280
+ *([[
281
+ '-bundle',
282
+ '"-undefined dynamic_lookup"',
283
+ ]] if sys.platform == 'darwin' else []),
198
284
  ),
199
- ],
200
- ))
285
+ ))
286
+
287
+ for ext_src in self.p.ext_files():
288
+ self._add_ext(os.path.relpath(ext_src, self.p.prj_root))
289
+
290
+ #
291
+
292
+ def run(self) -> None:
293
+ if not os.path.isfile(os.path.join(self._prj_root, 'pyproject.toml')):
294
+ raise Exception('Must be run in project root')
295
+
296
+ self.ext_files()
297
+
298
+ log.info('Generating cmake project %s', self.prj_name())
201
299
 
202
- # print(out.getvalue())
203
- with open(os.path.join(cmake_dir, 'CMakeLists.txt'), 'w') as f:
204
- f.write(out.getvalue())
300
+ self.cmake_dir()
301
+ self.write_git_ignore()
302
+ self.write_idea_name()
303
+
304
+ out = io.StringIO()
305
+ clg = self._CmakeListsGen(self, out)
306
+ clg.run()
307
+
308
+ with open(os.path.join(self.cmake_dir(), 'CMakeLists.txt'), 'w') as f:
309
+ f.write(out.getvalue())
310
+
311
+
312
+ ##
313
+
314
+
315
+ def _gen_cmd(args) -> None:
316
+ if not args.exts:
317
+ raise Exception('must specify exts')
318
+
319
+ cpg = CmakeProjectGen(args.exts)
320
+ cpg.run()
321
+
322
+
323
+ def _main(argv=None) -> None:
324
+ logs.configure_standard_logging('INFO')
325
+
326
+ parser = argparse.ArgumentParser()
327
+
328
+ subparsers = parser.add_subparsers()
329
+
330
+ parser_gen = subparsers.add_parser('gen')
331
+ parser_gen.add_argument('exts', nargs='*')
332
+ parser_gen.set_defaults(func=_gen_cmd)
333
+
334
+ args = parser.parse_args(argv)
335
+ if not getattr(args, 'func', None):
336
+ parser.print_help()
337
+ else:
338
+ args.func(args)
205
339
 
206
340
 
207
341
  if __name__ == '__main__':
omdev/exts/scan.py CHANGED
@@ -12,7 +12,7 @@ log = logging.getLogger(__name__)
12
12
  SCAN_COMMENT = '// @omdev-ext'
13
13
 
14
14
 
15
- def _scan_one(
15
+ def scan_one(
16
16
  input_path: str,
17
17
  **kwargs: ta.Any,
18
18
  ) -> None:
@@ -42,7 +42,7 @@ def _scan_cmd(args) -> None:
42
42
  log.info('Scanning %s', i)
43
43
  for we_dirpath, we_dirnames, we_filenames in os.walk(i): # noqa
44
44
  for fname in we_filenames:
45
- _scan_one(
45
+ scan_one(
46
46
  os.path.abspath(os.path.join(we_dirpath, fname)),
47
47
  )
48
48
 
@@ -28,8 +28,11 @@ def find_magic(
28
28
  continue
29
29
 
30
30
  fp = os.path.join(dp, fn)
31
- with open(fp) as f:
32
- src = f.read()
31
+ try:
32
+ with open(fp) as f:
33
+ src = f.read()
34
+ except UnicodeDecodeError:
35
+ continue
33
36
 
34
37
  if not any(
35
38
  any(pat.fullmatch(l) for pat in pats)
omdev/scripts/interp.py CHANGED
@@ -29,13 +29,18 @@ import threading
29
29
  import typing as ta
30
30
 
31
31
 
32
+ # ../../versioning/versions.py
32
33
  VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
33
34
  VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
34
35
  _VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
35
36
  VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
36
37
  VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
37
38
  VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
39
+
40
+ # ../../../omlish/lite/check.py
38
41
  T = ta.TypeVar('T')
42
+
43
+ # ../../versioning/specifiers.py
39
44
  UnparsedVersion = ta.Union['Version', str]
40
45
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
41
46
  CallableVersionOperator = ta.Callable[['Version', str], bool]
@@ -55,16 +55,23 @@ import uuid
55
55
  import weakref # noqa
56
56
 
57
57
 
58
+ # ../../toml/parser.py
58
59
  TomlParseFloat = ta.Callable[[str], ta.Any]
59
60
  TomlKey = ta.Tuple[str, ...]
60
61
  TomlPos = int # ta.TypeAlias
62
+
63
+ # ../../versioning/versions.py
61
64
  VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
62
65
  VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
63
66
  _VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
64
67
  VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalType0]
65
68
  VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
66
69
  VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
70
+
71
+ # ../../../omlish/lite/check.py
67
72
  T = ta.TypeVar('T')
73
+
74
+ # ../../versioning/specifiers.py
68
75
  UnparsedVersion = ta.Union['Version', str]
69
76
  UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
70
77
  CallableVersionOperator = ta.Callable[['Version', str], bool]
@@ -0,0 +1,173 @@
1
+ """
2
+ TODO:
3
+ - omlish-lite, move to pyproject/
4
+ - vendor-lite wheel.wheelfile
5
+ """
6
+ # ruff: noqa: TCH003 UP006 UP007
7
+ # @omlish-lite
8
+ import argparse
9
+ import io
10
+ import os.path
11
+ import subprocess
12
+ import tarfile
13
+ import typing as ta
14
+ import zipfile
15
+
16
+ from omlish.lite.logs import configure_standard_logging
17
+ from omlish.lite.logs import log
18
+
19
+ from ..wheelfile import WheelFile
20
+
21
+
22
+ class RevisionAdder:
23
+ def __init__(
24
+ self,
25
+ revision: str,
26
+ output_suffix: ta.Optional[str] = None,
27
+ ) -> None:
28
+ super().__init__()
29
+ self._revision = revision
30
+ self._output_suffix = output_suffix
31
+
32
+ REVISION_ATTR = '__revision__'
33
+
34
+ def add_to_contents(self, dct: ta.Dict[str, bytes]) -> bool:
35
+ changed = False
36
+ for n in dct:
37
+ if not n.endswith('__about__.py'):
38
+ continue
39
+ src = dct[n].decode('utf-8')
40
+ lines = src.splitlines(keepends=True)
41
+ for i, l in enumerate(lines):
42
+ if l != f'{self.REVISION_ATTR} = None\n':
43
+ continue
44
+ lines[i] = f"{self.REVISION_ATTR} = '{self._revision}'\n"
45
+ changed = True
46
+ dct[n] = ''.join(lines).encode('utf-8')
47
+ return changed
48
+
49
+ def add_to_wheel(self, f: str) -> None:
50
+ if not f.endswith('.whl'):
51
+ raise Exception(f)
52
+ log.info('Scanning wheel %s', f)
53
+
54
+ zis: ta.Dict[str, zipfile.ZipInfo] = {}
55
+ dct: ta.Dict[str, bytes] = {}
56
+ with WheelFile(f) as wf:
57
+ for zi in wf.filelist:
58
+ if zi.filename == wf.record_path:
59
+ continue
60
+ zis[zi.filename] = zi
61
+ dct[zi.filename] = wf.read(zi.filename)
62
+
63
+ if self.add_to_contents(dct):
64
+ of = f[:-4] + (self._output_suffix or '') + '.whl'
65
+ log.info('Repacking wheel %s', of)
66
+ with WheelFile(of, 'w') as wf:
67
+ for n, d in dct.items():
68
+ log.info('Adding zipinfo %s', n)
69
+ wf.writestr(zis[n], d)
70
+
71
+ def add_to_tgz(self, f: str) -> None:
72
+ if not f.endswith('.tar.gz'):
73
+ raise Exception(f)
74
+ log.info('Scanning tgz %s', f)
75
+
76
+ tis: ta.Dict[str, tarfile.TarInfo] = {}
77
+ dct: ta.Dict[str, bytes] = {}
78
+ with tarfile.open(f, 'r:gz') as tf:
79
+ for ti in tf:
80
+ tis[ti.name] = ti
81
+ if ti.type == tarfile.REGTYPE:
82
+ with tf.extractfile(ti.name) as tif: # type: ignore
83
+ dct[ti.name] = tif.read()
84
+
85
+ if self.add_to_contents(dct):
86
+ of = f[:-7] + (self._output_suffix or '') + '.tar.gz'
87
+ log.info('Repacking tgz %s', of)
88
+ with tarfile.open(of, 'w:gz') as tf:
89
+ for n, ti in tis.items():
90
+ log.info('Adding tarinfo %s', n)
91
+ if n in dct:
92
+ data = dct[n]
93
+ ti.size = len(data)
94
+ fo = io.BytesIO(data)
95
+ else:
96
+ fo = None
97
+ tf.addfile(ti, fileobj=fo)
98
+
99
+ EXTS = ('.tar.gz', '.whl')
100
+
101
+ def add_to_file(self, f: str) -> None:
102
+ if f.endswith('.whl'):
103
+ self.add_to_wheel(f)
104
+
105
+ elif f.endswith('.tar.gz'):
106
+ self.add_to_tgz(f)
107
+
108
+ def add_to(self, tgt: str) -> None:
109
+ if os.path.isfile(tgt):
110
+ self.add_to_file(tgt)
111
+
112
+ elif os.path.isdir(tgt):
113
+ for dp, dns, fns in os.walk(tgt): # noqa
114
+ for f in fns:
115
+ if any(f.endswith(ext) for ext in self.EXTS):
116
+ self.add_to_file(os.path.join(dp, f))
117
+
118
+
119
+ #
120
+
121
+
122
+ def get_revision() -> str:
123
+ return subprocess.check_output([
124
+ 'git',
125
+ 'describe',
126
+ '--match=NeVeRmAtCh',
127
+ '--always',
128
+ '--abbrev=40',
129
+ '--dirty',
130
+ ]).decode().strip()
131
+
132
+
133
+ #
134
+
135
+
136
+ def _add_cmd(args) -> None:
137
+ if (revision := args.revision) is None:
138
+ revision = get_revision()
139
+ log.info('Using revision %s', revision)
140
+
141
+ if not args.targets:
142
+ raise Exception('must specify targets')
143
+
144
+ ra = RevisionAdder(
145
+ revision,
146
+ output_suffix=args.suffix,
147
+ )
148
+ for tgt in args.targets:
149
+ ra.add_to(tgt)
150
+
151
+
152
+ def _main(argv=None) -> None:
153
+ configure_standard_logging('INFO')
154
+
155
+ parser = argparse.ArgumentParser()
156
+
157
+ subparsers = parser.add_subparsers()
158
+
159
+ parser_add = subparsers.add_parser('add')
160
+ parser_add.add_argument('-r', '--revision')
161
+ parser_add.add_argument('-s', '--suffix')
162
+ parser_add.add_argument('targets', nargs='*')
163
+ parser_add.set_defaults(func=_add_cmd)
164
+
165
+ args = parser.parse_args(argv)
166
+ if not getattr(args, 'func', None):
167
+ parser.print_help()
168
+ else:
169
+ args.func(args)
170
+
171
+
172
+ if __name__ == '__main__':
173
+ _main()
omdev/wheelfile.py ADDED
@@ -0,0 +1,246 @@
1
+ # @omlish-lite
2
+ # ruff: noqa: UP006 UP007
3
+ # https://github.com/pypa/wheel/blob/7bb46d7727e6e89fe56b3c78297b3af2672bbbe2/src/wheel/wheelfile.py
4
+ # MIT License
5
+ #
6
+ # Copyright (c) 2012 Daniel Holth <dholth@fastmail.fm> and contributors
7
+ #
8
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
9
+ # documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
10
+ # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11
+ # persons to whom the Software is furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
14
+ # Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
17
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20
+ import base64
21
+ import csv
22
+ import hashlib
23
+ import io
24
+ import os.path
25
+ import re
26
+ import stat
27
+ import time
28
+ import typing as ta
29
+ import zipfile
30
+
31
+
32
+ class WheelError(Exception):
33
+ pass
34
+
35
+
36
+ # Non-greedy matching of an optional build number may be too clever (more invalid wheel filenames will match). Separate
37
+ # regex for .dist-info?
38
+ WHEEL_INFO_RE = re.compile(
39
+ r'^'
40
+ r'(?P<namever>(?P<name>[^\s-]+?)-(?P<ver>[^\s-]+?))'
41
+ r'(-(?P<build>\d[^\s-]*))?-'
42
+ r'(?P<pyver>[^\s-]+?)-'
43
+ r'(?P<abi>[^\s-]+?)-'
44
+ r'(?P<plat>\S+)'
45
+ r'\.whl$',
46
+ re.VERBOSE,
47
+ )
48
+
49
+
50
+ class WheelFile(zipfile.ZipFile):
51
+ """
52
+ A ZipFile derivative class that also reads SHA-256 hashes from .dist-info/RECORD and checks any read files against
53
+ those.
54
+ """
55
+
56
+ _default_algorithm = hashlib.sha256
57
+
58
+ def __init__(
59
+ self,
60
+ file: str,
61
+ mode: str = 'r', # ta.Literal["r", "w", "x", "a"]
62
+ compression: int = zipfile.ZIP_DEFLATED,
63
+ ) -> None:
64
+ basename = os.path.basename(file)
65
+ self.parsed_filename = WHEEL_INFO_RE.match(basename)
66
+ if not basename.endswith('.whl') or self.parsed_filename is None:
67
+ raise WheelError(f'Bad wheel filename {basename!r}')
68
+
69
+ super().__init__( # type: ignore
70
+ file,
71
+ mode,
72
+ compression=compression,
73
+ allowZip64=True,
74
+ )
75
+
76
+ self.dist_info_path = '{}.dist-info'.format(self.parsed_filename.group('namever'))
77
+ self.record_path = self.dist_info_path + '/RECORD'
78
+ self._file_hashes: ta.Dict[str, ta.Union[ta.Tuple[None, None], ta.Tuple[int, bytes]]] = {}
79
+ self._file_sizes: ta.Dict[str, int] = {}
80
+
81
+ if mode == 'r':
82
+ # Ignore RECORD and any embedded wheel signatures
83
+ self._file_hashes[self.record_path] = None, None
84
+ self._file_hashes[self.record_path + '.jws'] = None, None
85
+ self._file_hashes[self.record_path + '.p7s'] = None, None
86
+
87
+ # Fill in the expected hashes by reading them from RECORD
88
+ try:
89
+ record = self.open(self.record_path)
90
+ except KeyError:
91
+ raise WheelError(f'Missing {self.record_path} file') from None
92
+
93
+ with record:
94
+ for line in csv.reader(io.TextIOWrapper(record, newline='', encoding='utf-8')):
95
+ path, hash_sum, size = line
96
+ if not hash_sum:
97
+ continue
98
+
99
+ algorithm, hash_sum = hash_sum.split('=')
100
+ try:
101
+ hashlib.new(algorithm)
102
+ except ValueError:
103
+ raise WheelError(f'Unsupported hash algorithm: {algorithm}') from None
104
+
105
+ if algorithm.lower() in {'md5', 'sha1'}:
106
+ raise WheelError(f'Weak hash algorithm ({algorithm}) is not permitted by PEP 427')
107
+
108
+ self._file_hashes[path] = ( # type: ignore
109
+ algorithm,
110
+ self._urlsafe_b64decode(hash_sum.encode('ascii')),
111
+ )
112
+
113
+ @staticmethod
114
+ def _urlsafe_b64encode(data: bytes) -> bytes:
115
+ """urlsafe_b64encode without padding"""
116
+ return base64.urlsafe_b64encode(data).rstrip(b'=')
117
+
118
+ @staticmethod
119
+ def _urlsafe_b64decode(data: bytes) -> bytes:
120
+ """urlsafe_b64decode without padding"""
121
+ pad = b'=' * (4 - (len(data) & 3))
122
+ return base64.urlsafe_b64decode(data + pad)
123
+
124
+ def open( # type: ignore # noqa
125
+ self,
126
+ name_or_info: ta.Union[str, zipfile.ZipInfo],
127
+ mode: str = 'r', # ta.Literal["r", "w"]
128
+ pwd: ta.Optional[bytes] = None,
129
+ ) -> ta.IO[bytes]:
130
+ def _update_crc(newdata: bytes) -> None:
131
+ eof = ef._eof # type: ignore # noqa
132
+ update_crc_orig(newdata)
133
+ running_hash.update(newdata)
134
+ if eof and running_hash.digest() != expected_hash:
135
+ raise WheelError(f"Hash mismatch for file '{ef_name}'")
136
+
137
+ ef_name = name_or_info.filename if isinstance(name_or_info, zipfile.ZipInfo) else name_or_info
138
+ if (
139
+ mode == 'r'
140
+ and not ef_name.endswith('/')
141
+ and ef_name not in self._file_hashes
142
+ ):
143
+ raise WheelError(f"No hash found for file '{ef_name}'")
144
+
145
+ ef = super().open(name_or_info, mode, pwd) # noqa
146
+ if mode == 'r' and not ef_name.endswith('/'):
147
+ algorithm, expected_hash = self._file_hashes[ef_name]
148
+ if expected_hash is not None:
149
+ # Monkey patch the _update_crc method to also check for the hash from RECORD
150
+ running_hash = hashlib.new(algorithm) # type: ignore
151
+ update_crc_orig, ef._update_crc = ef._update_crc, _update_crc # type: ignore # noqa
152
+
153
+ return ef
154
+
155
+ def write_files(self, base_dir: str) -> None:
156
+ deferred: list[tuple[str, str]] = []
157
+ for root, dirnames, filenames in os.walk(base_dir):
158
+ # Sort the directory names so that `os.walk` will walk them in a defined order on the next iteration.
159
+ dirnames.sort()
160
+ for name in sorted(filenames):
161
+ path = os.path.normpath(os.path.join(root, name))
162
+ if os.path.isfile(path):
163
+ arcname = os.path.relpath(path, base_dir).replace(os.path.sep, '/')
164
+ if arcname == self.record_path:
165
+ pass
166
+ elif root.endswith('.dist-info'):
167
+ deferred.append((path, arcname))
168
+ else:
169
+ self.write(path, arcname)
170
+
171
+ deferred.sort()
172
+ for path, arcname in deferred:
173
+ self.write(path, arcname)
174
+
175
+ def write( # type: ignore # noqa
176
+ self,
177
+ filename: str,
178
+ arcname: ta.Optional[str] = None,
179
+ compress_type: ta.Optional[int] = None,
180
+ ) -> None:
181
+ with open(filename, 'rb') as f:
182
+ st = os.fstat(f.fileno())
183
+ data = f.read()
184
+
185
+ zinfo = zipfile.ZipInfo(
186
+ arcname or filename,
187
+ date_time=self._get_zipinfo_datetime(st.st_mtime),
188
+ )
189
+ zinfo.external_attr = (stat.S_IMODE(st.st_mode) | stat.S_IFMT(st.st_mode)) << 16
190
+ zinfo.compress_type = compress_type or self.compression
191
+ self.writestr(zinfo, data, compress_type)
192
+
193
+ _MINIMUM_TIMESTAMP = 315532800 # 1980-01-01 00:00:00 UTC
194
+
195
+ @classmethod
196
+ def _get_zipinfo_datetime(cls, timestamp: ta.Optional[float] = None) -> ta.Any:
197
+ # Some applications need reproducible .whl files, but they can't do this without forcing the timestamp of the
198
+ # individual ZipInfo objects. See issue #143.
199
+ timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timestamp or time.time()))
200
+ timestamp = max(timestamp, cls._MINIMUM_TIMESTAMP)
201
+ return time.gmtime(timestamp)[0:6]
202
+
203
+ def writestr( # type: ignore # noqa
204
+ self,
205
+ zinfo_or_arcname: ta.Union[str, zipfile.ZipInfo],
206
+ data: ta.Any, # SizedBuffer | str,
207
+ compress_type: ta.Optional[int] = None,
208
+ ) -> None:
209
+ if isinstance(zinfo_or_arcname, str):
210
+ zinfo_or_arcname = zipfile.ZipInfo(
211
+ zinfo_or_arcname,
212
+ date_time=self._get_zipinfo_datetime(),
213
+ )
214
+ zinfo_or_arcname.compress_type = self.compression
215
+ zinfo_or_arcname.external_attr = (0o664 | stat.S_IFREG) << 16
216
+
217
+ if isinstance(data, str):
218
+ data = data.encode('utf-8')
219
+
220
+ super().writestr(zinfo_or_arcname, data, compress_type)
221
+ fname = (
222
+ zinfo_or_arcname.filename
223
+ if isinstance(zinfo_or_arcname, zipfile.ZipInfo)
224
+ else zinfo_or_arcname
225
+ )
226
+ if fname != self.record_path:
227
+ hash_ = self._default_algorithm(data) # type: ignore
228
+ self._file_hashes[fname] = ( # type: ignore
229
+ hash_.name,
230
+ self._urlsafe_b64encode(hash_.digest()).decode('ascii'),
231
+ )
232
+ self._file_sizes[fname] = len(data)
233
+
234
+ def close(self) -> None:
235
+ # Write RECORD
236
+ if self.fp is not None and self.mode == 'w' and self._file_hashes:
237
+ data = io.StringIO()
238
+ writer = csv.writer(data, delimiter=',', quotechar='"', lineterminator='\n')
239
+ writer.writerows((
240
+ (fname, algorithm + '=' + hash_, self._file_sizes[fname]) # type: ignore
241
+ for fname, (algorithm, hash_) in self._file_hashes.items()
242
+ ))
243
+ writer.writerow((format(self.record_path), '', ''))
244
+ self.writestr(self.record_path, data.getvalue())
245
+
246
+ super().close()
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.1
2
+ Name: omdev
3
+ Version: 0.0.0.dev13
4
+ Summary: omdev
5
+ Author: wrmsr
6
+ License: BSD-3-Clause
7
+ Project-URL: source, https://github.com/wrmsr/omlish
8
+ Classifier: License :: OSI Approved :: BSD License
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Operating System :: POSIX
13
+ Requires-Python: ~=3.12
14
+ License-File: LICENSE
15
+ Requires-Dist: omlish ==0.0.0.dev13
16
+ Provides-Extra: all
17
+ Requires-Dist: pycparser ~=2.22 ; extra == 'all'
18
+ Requires-Dist: cffi ~=1.17 ; extra == 'all'
19
+ Requires-Dist: pcpp ~=1.30 ; extra == 'all'
20
+ Requires-Dist: mypy ~=1.11 ; extra == 'all'
21
+ Requires-Dist: tokenize-rt ~=6.0 ; extra == 'all'
22
+ Requires-Dist: wheel ~=0.44 ; extra == 'all'
23
+ Provides-Extra: c
24
+ Requires-Dist: pycparser ~=2.22 ; extra == 'c'
25
+ Requires-Dist: cffi ~=1.17 ; extra == 'c'
26
+ Requires-Dist: pcpp ~=1.30 ; extra == 'c'
27
+ Provides-Extra: mypy
28
+ Requires-Dist: mypy ~=1.11 ; extra == 'mypy'
29
+ Provides-Extra: tokens
30
+ Requires-Dist: tokenize-rt ~=6.0 ; extra == 'tokens'
31
+ Provides-Extra: wheel
32
+ Requires-Dist: wheel ~=0.44 ; extra == 'wheel'
33
+
@@ -1,16 +1,17 @@
1
- omdev/__about__.py,sha256=RXVZPsAF1ImmY7x_3cw2VrnoxdQ_bCa4unQHrnII14Y,722
1
+ omdev/__about__.py,sha256=VuY8DTDYJifMW_xeF21JIhFM-MwVp1F0vWUhzIqomJA,784
2
2
  omdev/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  omdev/classdot.py,sha256=urN5Pzd2ooAwnfkH0z-muQxdO90IMo-sX2WB-A37lVU,1533
4
- omdev/cmake.py,sha256=Sk9SL_mFCuYG0FYjG_w53wGt6mOmXfen5pZWZsqjSq4,4592
4
+ omdev/cmake.py,sha256=Diy2ry65806dQP125DAstD3w46z_wszMH7PwC2-6iik,4578
5
5
  omdev/tokens.py,sha256=GusxQ1Cd_eiScuR8XTTtc9QFhOgYviYGBZmFnn3Hj7s,756
6
+ omdev/wheelfile.py,sha256=yfupGcGkbFlmzGzKU64k_vmOKpaKnUlDWxeGn2KdekU,10005
6
7
  omdev/amalg/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
8
  omdev/amalg/__main__.py,sha256=OE1udULO1g4McUbeg1CoHbSm4hbQ2kcE3ffEGxlnPh4,69
8
- omdev/amalg/amalg.py,sha256=2k9oHxP5HDKopfWspABq2ZVjVsZpUZoejUnHeL7cE9M,11477
9
+ omdev/amalg/amalg.py,sha256=q1YysHehhJtHuf9znZ2cpPT5nFz1WQLXWVcIv99BVyI,11867
9
10
  omdev/exts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
11
  omdev/exts/build.py,sha256=zViF1wYx6z5ACyifgNjlCAVoPAMCKpTr_VoAvwtmvtY,1013
11
- omdev/exts/cmake.py,sha256=aUMk7irjo6Dr2BjpAyxmFE2V_a7jMCoa89kZmPx_f7w,5745
12
+ omdev/exts/cmake.py,sha256=D1RCmOAowxunEq8HAlIL_8514SBniDPlfynVdAzHS3c,9584
12
13
  omdev/exts/importhook.py,sha256=HgEM6UD52BoQi4dhSSk8buoqV6YRUvzQOOWzX6W1aic,2306
13
- omdev/exts/scan.py,sha256=rUfRFzKn8Tu_2B-en0bdkrjMlqb8qWK7SOCf7AF2-wk,1642
14
+ omdev/exts/scan.py,sha256=ivk_Zh_AnOu8jHeaFZnITwMSwaQqOcHxwOjzsAyL1Gc,1640
14
15
  omdev/exts/_distutils/__init__.py,sha256=c1zImtnPh3uY8BUTV4RLKtGKqTPx3c_pBbhA6tPtNsE,297
15
16
  omdev/exts/_distutils/build_ext.py,sha256=STHl9Rq2KeWJ3dQ8j8LwIQ-vFc4-3XsYWQ8Qc5_VByU,13833
16
17
  omdev/exts/_distutils/dir_util.py,sha256=xxfAIPHbjlh-aW9OX6UGDrXiXfB5biG4xEC5RA6oszM,2882
@@ -48,9 +49,9 @@ omdev/scripts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
48
49
  omdev/scripts/bracepy.py,sha256=CUg3nNKE8n34a5j77SUli8ivsOj5ks6I_5VIF8bl-zY,2562
49
50
  omdev/scripts/execrss.py,sha256=wBpdhWuQj_4EnEmvK_aLQHxaE-J52G9F6g0210fTNe8,307
50
51
  omdev/scripts/findimports.py,sha256=d8Xf2TcE7hHh_lXUx2V1APF4Jggu2uO2K0ex6bfmqyQ,2030
51
- omdev/scripts/findmagic.py,sha256=W7W07YR3zsA0hNej6-BPgQj2hWX1UydrbWnNMRdMA88,1893
52
- omdev/scripts/interp.py,sha256=z66pI9s1yVOD-HMCDdHzq6gRfezoex5MjQ7jnJIAIuU,62939
53
- omdev/scripts/pyproject.py,sha256=df2a4oKTWMohgSgiEKLshdABhkIapKH3arlbdJ71XeM,119248
52
+ omdev/scripts/findmagic.py,sha256=z_D9zzZJBuWPbR20NEh8i8NJBnCYWlttg2GTJpmDM4s,1994
53
+ omdev/scripts/interp.py,sha256=itY5pq1ahDjw8mYZcHec5qQRbAZezliPUyyZ1VD7zTg,63037
54
+ omdev/scripts/pyproject.py,sha256=NuWW0nu897jaydR-1f-Gz7pxl-zzTOjNwKiCK3uzjDY,119370
54
55
  omdev/scripts/traceimport.py,sha256=JmUgLEQLY7r3QNQ14Agarqh5qtMyRNenCb_OF5EIikY,13392
55
56
  omdev/toml/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
56
57
  omdev/toml/parser.py,sha256=84bn09uhYHwQGyfww6Rw6y1RxPAE_HDltODOSakcqDM,29186
@@ -58,12 +59,13 @@ omdev/toml/writer.py,sha256=StGYPvqgN__A2IxTI4rYeHMx8dLAFt8uogHG8dJdShs,2781
58
59
  omdev/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
60
  omdev/tools/dockertools.py,sha256=3844AhUst6kYo2xKNn-2Npi-f6r4rocxEOx0tHjE0dk,2063
60
61
  omdev/tools/gittools.py,sha256=zPy2D5WDs-CbwT86_T_hbaq5yCuss5e-ouUccXC6xlg,578
62
+ omdev/tools/revisions.py,sha256=LSW3IK5P4FFcZyD0f4hcObnf9cWv3_OoTqeP85UiX8E,4842
61
63
  omdev/tools/sqlrepl.py,sha256=v9uVQ4nvquSXcQVYIFq34ikumSILvKqzD6lUKLcncCE,5646
62
64
  omdev/versioning/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
63
65
  omdev/versioning/specifiers.py,sha256=6Odf9e6farwlPRsD_YqwTfYKG-BXn_dIcKtqfkhfodI,17432
64
66
  omdev/versioning/versions.py,sha256=ei2eopEsJq3zSMJmezK1nzZgikgCdxFtnF3f69nCRZQ,12246
65
- omdev-0.0.0.dev11.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
66
- omdev-0.0.0.dev11.dist-info/METADATA,sha256=LKBayZPwTJ2L-NtTD5tbPJhkobNBYsJ8g2MO_mC0NKE,1008
67
- omdev-0.0.0.dev11.dist-info/WHEEL,sha256=UvcQYKBHoFqaQd6LKyqHw9fxEolWLQnlzP0h_LgJAfI,91
68
- omdev-0.0.0.dev11.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
69
- omdev-0.0.0.dev11.dist-info/RECORD,,
67
+ omdev-0.0.0.dev13.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
68
+ omdev-0.0.0.dev13.dist-info/METADATA,sha256=EWbybLAXeBMkrn_XTfZlFQYsGO837utkj7Qgs1rXGXo,1126
69
+ omdev-0.0.0.dev13.dist-info/WHEEL,sha256=uCRv0ZEik_232NlR4YDw4Pv3Ajt5bKvMH13NUU7hFuI,91
70
+ omdev-0.0.0.dev13.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
71
+ omdev-0.0.0.dev13.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (74.0.0)
2
+ Generator: setuptools (74.1.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,30 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: omdev
3
- Version: 0.0.0.dev11
4
- Summary: omdev
5
- Author: wrmsr
6
- License: BSD-3-Clause
7
- Project-URL: source, https://github.com/wrmsr/omlish
8
- Classifier: License :: OSI Approved :: BSD License
9
- Classifier: Development Status :: 2 - Pre-Alpha
10
- Classifier: Intended Audience :: Developers
11
- Classifier: Operating System :: OS Independent
12
- Classifier: Operating System :: POSIX
13
- Requires-Python: >=3.12
14
- License-File: LICENSE
15
- Requires-Dist: omlish ==0.0.0.dev11
16
- Provides-Extra: all
17
- Requires-Dist: pycparser >=2.22 ; extra == 'all'
18
- Requires-Dist: cffi >=1.17 ; extra == 'all'
19
- Requires-Dist: pcpp >=1.30 ; extra == 'all'
20
- Requires-Dist: mypy >=1.11 ; extra == 'all'
21
- Requires-Dist: tokenize-rt >=6 ; extra == 'all'
22
- Provides-Extra: c
23
- Requires-Dist: pycparser >=2.22 ; extra == 'c'
24
- Requires-Dist: cffi >=1.17 ; extra == 'c'
25
- Requires-Dist: pcpp >=1.30 ; extra == 'c'
26
- Provides-Extra: mypy
27
- Requires-Dist: mypy >=1.11 ; extra == 'mypy'
28
- Provides-Extra: tokens
29
- Requires-Dist: tokenize-rt >=6 ; extra == 'tokens'
30
-