omdev 0.0.0.dev22__py3-none-any.whl → 0.0.0.dev24__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/cexts/cmake.py CHANGED
@@ -302,7 +302,7 @@ class CmakeProjectGen:
302
302
  clg.run()
303
303
 
304
304
  with open(os.path.join(self.cmake_dir(), 'CMakeLists.txt'), 'w') as f:
305
- f.write(out.getvalue())
305
+ f.write(out.getvalue().strip() + '\n')
306
306
 
307
307
 
308
308
  ##
omdev/interp/pyenv.py CHANGED
@@ -92,6 +92,14 @@ class Pyenv:
92
92
  ret.append(l)
93
93
  return ret
94
94
 
95
+ def update(self) -> bool:
96
+ if (root := self.root()) is None:
97
+ return False
98
+ if not os.path.isdir(os.path.join(root, '.git')):
99
+ return False
100
+ subprocess_check_call('git', 'pull', cwd=root)
101
+ return True
102
+
95
103
 
96
104
  ##
97
105
 
@@ -292,6 +300,10 @@ class PyenvInterpProvider(InterpProvider):
292
300
 
293
301
  inspect: bool = False,
294
302
  inspector: InterpInspector = INTERP_INSPECTOR,
303
+
304
+ *,
305
+
306
+ try_update: bool = False,
295
307
  ) -> None:
296
308
  super().__init__()
297
309
 
@@ -300,6 +312,8 @@ class PyenvInterpProvider(InterpProvider):
300
312
  self._inspect = inspect
301
313
  self._inspector = inspector
302
314
 
315
+ self._try_update = try_update
316
+
303
317
  #
304
318
 
305
319
  @staticmethod
@@ -364,8 +378,9 @@ class PyenvInterpProvider(InterpProvider):
364
378
 
365
379
  #
366
380
 
367
- def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
381
+ def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
368
382
  lst = []
383
+
369
384
  for vs in self._pyenv.installable_versions():
370
385
  if (iv := self.guess_version(vs)) is None:
371
386
  continue
@@ -373,6 +388,16 @@ class PyenvInterpProvider(InterpProvider):
373
388
  raise Exception('Pyenv installable versions not expected to have debug suffix')
374
389
  for d in [False, True]:
375
390
  lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
391
+
392
+ return lst
393
+
394
+ def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
395
+ lst = self._get_installable_versions(spec)
396
+
397
+ if self._try_update and not any(v in spec for v in lst):
398
+ if self._pyenv.update():
399
+ lst = self._get_installable_versions(spec)
400
+
376
401
  return lst
377
402
 
378
403
  def install_version(self, version: InterpVersion) -> Interp:
omdev/interp/resolvers.py CHANGED
@@ -99,7 +99,7 @@ class InterpResolver:
99
99
 
100
100
  DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
101
101
  # pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
102
- PyenvInterpProvider(),
102
+ PyenvInterpProvider(try_update=True),
103
103
 
104
104
  RunningInterpProvider(),
105
105
 
omdev/pyproject/cli.py CHANGED
@@ -243,18 +243,30 @@ def _venv_cmd(args) -> None:
243
243
  f'--_docker_container={shlex.quote(sd)}',
244
244
  *map(shlex.quote, sys.argv[1:]),
245
245
  ])
246
+
247
+ docker_env = {
248
+ 'DOCKER_HOST_PLATFORM': os.environ.get('DOCKER_HOST_PLATFORM', sys.platform),
249
+ }
250
+ for e in args.docker_env or []:
251
+ if '=' in e:
252
+ k, _, v = e.split('=')
253
+ docker_env[k] = v
254
+ else:
255
+ docker_env[e] = os.environ.get(e, '')
256
+
246
257
  subprocess_check_call(
247
258
  'docker',
248
259
  'compose',
249
260
  '-f', 'docker/compose.yml',
250
261
  'exec',
251
262
  *itertools.chain.from_iterable(
252
- ('-e', f'{e}={os.environ.get(e, "")}' if '=' not in e else e)
253
- for e in (args.docker_env or [])
263
+ ('-e', f'{k}={v}')
264
+ for k, v in docker_env.items()
254
265
  ),
255
266
  '-it', sd,
256
267
  'bash', '--login', '-c', script,
257
268
  )
269
+
258
270
  return
259
271
 
260
272
  cmd = args.cmd
omdev/scripts/interp.py CHANGED
@@ -1234,13 +1234,134 @@ class StandardLogFormatter(logging.Formatter):
1234
1234
  ##
1235
1235
 
1236
1236
 
1237
+ class ProxyLogFilterer(logging.Filterer):
1238
+ def __init__(self, underlying: logging.Filterer) -> None: # noqa
1239
+ self._underlying = underlying
1240
+
1241
+ @property
1242
+ def underlying(self) -> logging.Filterer:
1243
+ return self._underlying
1244
+
1245
+ @property
1246
+ def filters(self):
1247
+ return self._underlying.filters
1248
+
1249
+ @filters.setter
1250
+ def filters(self, filters):
1251
+ self._underlying.filters = filters
1252
+
1253
+ def addFilter(self, filter): # noqa
1254
+ self._underlying.addFilter(filter)
1255
+
1256
+ def removeFilter(self, filter): # noqa
1257
+ self._underlying.removeFilter(filter)
1258
+
1259
+ def filter(self, record):
1260
+ return self._underlying.filter(record)
1261
+
1262
+
1263
+ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
1264
+ def __init__(self, underlying: logging.Handler) -> None: # noqa
1265
+ ProxyLogFilterer.__init__(self, underlying)
1266
+
1267
+ _underlying: logging.Handler
1268
+
1269
+ @property
1270
+ def underlying(self) -> logging.Handler:
1271
+ return self._underlying
1272
+
1273
+ def get_name(self):
1274
+ return self._underlying.get_name()
1275
+
1276
+ def set_name(self, name):
1277
+ self._underlying.set_name(name)
1278
+
1279
+ @property
1280
+ def name(self):
1281
+ return self._underlying.name
1282
+
1283
+ @property
1284
+ def level(self):
1285
+ return self._underlying.level
1286
+
1287
+ @level.setter
1288
+ def level(self, level):
1289
+ self._underlying.level = level
1290
+
1291
+ @property
1292
+ def formatter(self):
1293
+ return self._underlying.formatter
1294
+
1295
+ @formatter.setter
1296
+ def formatter(self, formatter):
1297
+ self._underlying.formatter = formatter
1298
+
1299
+ def createLock(self):
1300
+ self._underlying.createLock()
1301
+
1302
+ def acquire(self):
1303
+ self._underlying.acquire()
1304
+
1305
+ def release(self):
1306
+ self._underlying.release()
1307
+
1308
+ def setLevel(self, level):
1309
+ self._underlying.setLevel(level)
1310
+
1311
+ def format(self, record):
1312
+ return self._underlying.format(record)
1313
+
1314
+ def emit(self, record):
1315
+ self._underlying.emit(record)
1316
+
1317
+ def handle(self, record):
1318
+ return self._underlying.handle(record)
1319
+
1320
+ def setFormatter(self, fmt):
1321
+ self._underlying.setFormatter(fmt)
1322
+
1323
+ def flush(self):
1324
+ self._underlying.flush()
1325
+
1326
+ def close(self):
1327
+ self._underlying.close()
1328
+
1329
+ def handleError(self, record):
1330
+ self._underlying.handleError(record)
1331
+
1332
+
1333
+ ##
1334
+
1335
+
1336
+ class StandardLogHandler(ProxyLogHandler):
1337
+ pass
1338
+
1339
+
1340
+ ##
1341
+
1342
+
1237
1343
  def configure_standard_logging(
1238
1344
  level: ta.Union[int, str] = logging.INFO,
1239
1345
  *,
1240
1346
  json: bool = False,
1241
- ) -> logging.Handler:
1347
+ target: ta.Optional[logging.Logger] = None,
1348
+ no_check: bool = False,
1349
+ ) -> ta.Optional[StandardLogHandler]:
1350
+ if target is None:
1351
+ target = logging.root
1352
+
1353
+ #
1354
+
1355
+ if not no_check:
1356
+ if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
1357
+ return None
1358
+
1359
+ #
1360
+
1242
1361
  handler = logging.StreamHandler()
1243
1362
 
1363
+ #
1364
+
1244
1365
  formatter: logging.Formatter
1245
1366
  if json:
1246
1367
  formatter = JsonLogFormatter()
@@ -1248,14 +1369,22 @@ def configure_standard_logging(
1248
1369
  formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
1249
1370
  handler.setFormatter(formatter)
1250
1371
 
1372
+ #
1373
+
1251
1374
  handler.addFilter(TidLogFilter())
1252
1375
 
1253
- logging.root.addHandler(handler)
1376
+ #
1377
+
1378
+ target.addHandler(handler)
1379
+
1380
+ #
1254
1381
 
1255
1382
  if level is not None:
1256
- logging.root.setLevel(level)
1383
+ target.setLevel(level)
1384
+
1385
+ #
1257
1386
 
1258
- return handler
1387
+ return StandardLogHandler(handler)
1259
1388
 
1260
1389
 
1261
1390
  ########################################
@@ -1705,6 +1834,14 @@ class Pyenv:
1705
1834
  ret.append(l)
1706
1835
  return ret
1707
1836
 
1837
+ def update(self) -> bool:
1838
+ if (root := self.root()) is None:
1839
+ return False
1840
+ if not os.path.isdir(os.path.join(root, '.git')):
1841
+ return False
1842
+ subprocess_check_call('git', 'pull', cwd=root)
1843
+ return True
1844
+
1708
1845
 
1709
1846
  ##
1710
1847
 
@@ -1905,6 +2042,10 @@ class PyenvInterpProvider(InterpProvider):
1905
2042
 
1906
2043
  inspect: bool = False,
1907
2044
  inspector: InterpInspector = INTERP_INSPECTOR,
2045
+
2046
+ *,
2047
+
2048
+ try_update: bool = False,
1908
2049
  ) -> None:
1909
2050
  super().__init__()
1910
2051
 
@@ -1913,6 +2054,8 @@ class PyenvInterpProvider(InterpProvider):
1913
2054
  self._inspect = inspect
1914
2055
  self._inspector = inspector
1915
2056
 
2057
+ self._try_update = try_update
2058
+
1916
2059
  #
1917
2060
 
1918
2061
  @staticmethod
@@ -1977,8 +2120,9 @@ class PyenvInterpProvider(InterpProvider):
1977
2120
 
1978
2121
  #
1979
2122
 
1980
- def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
2123
+ def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
1981
2124
  lst = []
2125
+
1982
2126
  for vs in self._pyenv.installable_versions():
1983
2127
  if (iv := self.guess_version(vs)) is None:
1984
2128
  continue
@@ -1986,6 +2130,16 @@ class PyenvInterpProvider(InterpProvider):
1986
2130
  raise Exception('Pyenv installable versions not expected to have debug suffix')
1987
2131
  for d in [False, True]:
1988
2132
  lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
2133
+
2134
+ return lst
2135
+
2136
+ def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
2137
+ lst = self._get_installable_versions(spec)
2138
+
2139
+ if self._try_update and not any(v in spec for v in lst):
2140
+ if self._pyenv.update():
2141
+ lst = self._get_installable_versions(spec)
2142
+
1989
2143
  return lst
1990
2144
 
1991
2145
  def install_version(self, version: InterpVersion) -> Interp:
@@ -2208,7 +2362,7 @@ class InterpResolver:
2208
2362
 
2209
2363
  DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
2210
2364
  # pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
2211
- PyenvInterpProvider(),
2365
+ PyenvInterpProvider(try_update=True),
2212
2366
 
2213
2367
  RunningInterpProvider(),
2214
2368
 
@@ -2580,13 +2580,134 @@ class StandardLogFormatter(logging.Formatter):
2580
2580
  ##
2581
2581
 
2582
2582
 
2583
+ class ProxyLogFilterer(logging.Filterer):
2584
+ def __init__(self, underlying: logging.Filterer) -> None: # noqa
2585
+ self._underlying = underlying
2586
+
2587
+ @property
2588
+ def underlying(self) -> logging.Filterer:
2589
+ return self._underlying
2590
+
2591
+ @property
2592
+ def filters(self):
2593
+ return self._underlying.filters
2594
+
2595
+ @filters.setter
2596
+ def filters(self, filters):
2597
+ self._underlying.filters = filters
2598
+
2599
+ def addFilter(self, filter): # noqa
2600
+ self._underlying.addFilter(filter)
2601
+
2602
+ def removeFilter(self, filter): # noqa
2603
+ self._underlying.removeFilter(filter)
2604
+
2605
+ def filter(self, record):
2606
+ return self._underlying.filter(record)
2607
+
2608
+
2609
+ class ProxyLogHandler(ProxyLogFilterer, logging.Handler):
2610
+ def __init__(self, underlying: logging.Handler) -> None: # noqa
2611
+ ProxyLogFilterer.__init__(self, underlying)
2612
+
2613
+ _underlying: logging.Handler
2614
+
2615
+ @property
2616
+ def underlying(self) -> logging.Handler:
2617
+ return self._underlying
2618
+
2619
+ def get_name(self):
2620
+ return self._underlying.get_name()
2621
+
2622
+ def set_name(self, name):
2623
+ self._underlying.set_name(name)
2624
+
2625
+ @property
2626
+ def name(self):
2627
+ return self._underlying.name
2628
+
2629
+ @property
2630
+ def level(self):
2631
+ return self._underlying.level
2632
+
2633
+ @level.setter
2634
+ def level(self, level):
2635
+ self._underlying.level = level
2636
+
2637
+ @property
2638
+ def formatter(self):
2639
+ return self._underlying.formatter
2640
+
2641
+ @formatter.setter
2642
+ def formatter(self, formatter):
2643
+ self._underlying.formatter = formatter
2644
+
2645
+ def createLock(self):
2646
+ self._underlying.createLock()
2647
+
2648
+ def acquire(self):
2649
+ self._underlying.acquire()
2650
+
2651
+ def release(self):
2652
+ self._underlying.release()
2653
+
2654
+ def setLevel(self, level):
2655
+ self._underlying.setLevel(level)
2656
+
2657
+ def format(self, record):
2658
+ return self._underlying.format(record)
2659
+
2660
+ def emit(self, record):
2661
+ self._underlying.emit(record)
2662
+
2663
+ def handle(self, record):
2664
+ return self._underlying.handle(record)
2665
+
2666
+ def setFormatter(self, fmt):
2667
+ self._underlying.setFormatter(fmt)
2668
+
2669
+ def flush(self):
2670
+ self._underlying.flush()
2671
+
2672
+ def close(self):
2673
+ self._underlying.close()
2674
+
2675
+ def handleError(self, record):
2676
+ self._underlying.handleError(record)
2677
+
2678
+
2679
+ ##
2680
+
2681
+
2682
+ class StandardLogHandler(ProxyLogHandler):
2683
+ pass
2684
+
2685
+
2686
+ ##
2687
+
2688
+
2583
2689
  def configure_standard_logging(
2584
2690
  level: ta.Union[int, str] = logging.INFO,
2585
2691
  *,
2586
2692
  json: bool = False,
2587
- ) -> logging.Handler:
2693
+ target: ta.Optional[logging.Logger] = None,
2694
+ no_check: bool = False,
2695
+ ) -> ta.Optional[StandardLogHandler]:
2696
+ if target is None:
2697
+ target = logging.root
2698
+
2699
+ #
2700
+
2701
+ if not no_check:
2702
+ if any(isinstance(h, StandardLogHandler) for h in list(target.handlers)):
2703
+ return None
2704
+
2705
+ #
2706
+
2588
2707
  handler = logging.StreamHandler()
2589
2708
 
2709
+ #
2710
+
2590
2711
  formatter: logging.Formatter
2591
2712
  if json:
2592
2713
  formatter = JsonLogFormatter()
@@ -2594,14 +2715,22 @@ def configure_standard_logging(
2594
2715
  formatter = StandardLogFormatter(StandardLogFormatter.build_log_format(STANDARD_LOG_FORMAT_PARTS))
2595
2716
  handler.setFormatter(formatter)
2596
2717
 
2718
+ #
2719
+
2597
2720
  handler.addFilter(TidLogFilter())
2598
2721
 
2599
- logging.root.addHandler(handler)
2722
+ #
2723
+
2724
+ target.addHandler(handler)
2725
+
2726
+ #
2600
2727
 
2601
2728
  if level is not None:
2602
- logging.root.setLevel(level)
2729
+ target.setLevel(level)
2603
2730
 
2604
- return handler
2731
+ #
2732
+
2733
+ return StandardLogHandler(handler)
2605
2734
 
2606
2735
 
2607
2736
  ########################################
@@ -3973,6 +4102,14 @@ class Pyenv:
3973
4102
  ret.append(l)
3974
4103
  return ret
3975
4104
 
4105
+ def update(self) -> bool:
4106
+ if (root := self.root()) is None:
4107
+ return False
4108
+ if not os.path.isdir(os.path.join(root, '.git')):
4109
+ return False
4110
+ subprocess_check_call('git', 'pull', cwd=root)
4111
+ return True
4112
+
3976
4113
 
3977
4114
  ##
3978
4115
 
@@ -4173,6 +4310,10 @@ class PyenvInterpProvider(InterpProvider):
4173
4310
 
4174
4311
  inspect: bool = False,
4175
4312
  inspector: InterpInspector = INTERP_INSPECTOR,
4313
+
4314
+ *,
4315
+
4316
+ try_update: bool = False,
4176
4317
  ) -> None:
4177
4318
  super().__init__()
4178
4319
 
@@ -4181,6 +4322,8 @@ class PyenvInterpProvider(InterpProvider):
4181
4322
  self._inspect = inspect
4182
4323
  self._inspector = inspector
4183
4324
 
4325
+ self._try_update = try_update
4326
+
4184
4327
  #
4185
4328
 
4186
4329
  @staticmethod
@@ -4245,8 +4388,9 @@ class PyenvInterpProvider(InterpProvider):
4245
4388
 
4246
4389
  #
4247
4390
 
4248
- def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
4391
+ def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
4249
4392
  lst = []
4393
+
4250
4394
  for vs in self._pyenv.installable_versions():
4251
4395
  if (iv := self.guess_version(vs)) is None:
4252
4396
  continue
@@ -4254,6 +4398,16 @@ class PyenvInterpProvider(InterpProvider):
4254
4398
  raise Exception('Pyenv installable versions not expected to have debug suffix')
4255
4399
  for d in [False, True]:
4256
4400
  lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
4401
+
4402
+ return lst
4403
+
4404
+ def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
4405
+ lst = self._get_installable_versions(spec)
4406
+
4407
+ if self._try_update and not any(v in spec for v in lst):
4408
+ if self._pyenv.update():
4409
+ lst = self._get_installable_versions(spec)
4410
+
4257
4411
  return lst
4258
4412
 
4259
4413
  def install_version(self, version: InterpVersion) -> Interp:
@@ -4476,7 +4630,7 @@ class InterpResolver:
4476
4630
 
4477
4631
  DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
4478
4632
  # pyenv is preferred to system interpreters as it tends to have more support for things like tkinter
4479
- PyenvInterpProvider(),
4633
+ PyenvInterpProvider(try_update=True),
4480
4634
 
4481
4635
  RunningInterpProvider(),
4482
4636
 
@@ -4679,18 +4833,30 @@ def _venv_cmd(args) -> None:
4679
4833
  f'--_docker_container={shlex.quote(sd)}',
4680
4834
  *map(shlex.quote, sys.argv[1:]),
4681
4835
  ])
4836
+
4837
+ docker_env = {
4838
+ 'DOCKER_HOST_PLATFORM': os.environ.get('DOCKER_HOST_PLATFORM', sys.platform),
4839
+ }
4840
+ for e in args.docker_env or []:
4841
+ if '=' in e:
4842
+ k, _, v = e.split('=')
4843
+ docker_env[k] = v
4844
+ else:
4845
+ docker_env[e] = os.environ.get(e, '')
4846
+
4682
4847
  subprocess_check_call(
4683
4848
  'docker',
4684
4849
  'compose',
4685
4850
  '-f', 'docker/compose.yml',
4686
4851
  'exec',
4687
4852
  *itertools.chain.from_iterable(
4688
- ('-e', f'{e}={os.environ.get(e, "")}' if '=' not in e else e)
4689
- for e in (args.docker_env or [])
4853
+ ('-e', f'{k}={v}')
4854
+ for k, v in docker_env.items()
4690
4855
  ),
4691
4856
  '-it', sd,
4692
4857
  'bash', '--login', '-c', script,
4693
4858
  )
4859
+
4694
4860
  return
4695
4861
 
4696
4862
  cmd = args.cmd
@@ -0,0 +1,187 @@
1
+ """
2
+ TODO:
3
+ - dump agg stats
4
+ - graphviz
5
+ """
6
+ import argparse
7
+ import dataclasses as dc
8
+ import inspect
9
+ import json
10
+ import os
11
+ import re
12
+ import shlex
13
+ import subprocess
14
+ import sys
15
+ import typing as ta
16
+
17
+ from omlish import concurrent as cu
18
+ from omlish import lang
19
+
20
+
21
+ ##
22
+
23
+
24
+ @dc.dataclass(frozen=True)
25
+ class Item:
26
+ spec: str
27
+
28
+ time: float
29
+ rss: int
30
+ imported: frozenset[str]
31
+
32
+
33
+ ##
34
+
35
+
36
+ def _payload(specs) -> None:
37
+ import resource
38
+ import sys
39
+ import time
40
+
41
+ def get_rss() -> int:
42
+ return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
43
+
44
+ start_modules = frozenset(sys.modules)
45
+ start_rss = get_rss()
46
+ start_time = time.time()
47
+
48
+ for spec in specs:
49
+ exec(f'import {spec}')
50
+
51
+ end_time = time.time()
52
+ end_rss = get_rss()
53
+ end_modules = frozenset(sys.modules)
54
+
55
+ import json
56
+
57
+ def json_dumps(obj):
58
+ return json.dumps(obj, indent=None, separators=(',', ':'))
59
+
60
+ print(json_dumps({
61
+ 'time': end_time - start_time,
62
+ 'rss': end_rss - start_rss,
63
+ 'imported': sorted(end_modules - start_modules),
64
+ }))
65
+
66
+
67
+ @lang.cached_function
68
+ def payload_src() -> str:
69
+ return inspect.getsource(_payload)
70
+
71
+
72
+ def run_one(
73
+ spec: str,
74
+ *,
75
+ shell_wrap: bool = True,
76
+ ) -> Item:
77
+ spec_payload_src = '\n\n'.join([
78
+ payload_src(),
79
+ f'_payload([{spec!r}])',
80
+ ])
81
+
82
+ args = [
83
+ sys.executable,
84
+ '-c',
85
+ spec_payload_src,
86
+ ]
87
+ if shell_wrap:
88
+ args = ['sh', '-c', ' '.join(map(shlex.quote, args))]
89
+
90
+ output = subprocess.check_output(args)
91
+
92
+ output_lines = output.decode().strip().splitlines()
93
+ if not output_lines:
94
+ raise Exception(f'no output: {spec}')
95
+ if len(output_lines) > 1:
96
+ print(f'warning: unexpected output: {spec}')
97
+
98
+ dct = json.loads(output_lines[-1])
99
+ return Item(
100
+ spec=spec,
101
+ **dct,
102
+ )
103
+
104
+
105
+ ##
106
+
107
+
108
+ def _find_specs(
109
+ *roots: str,
110
+ filters: ta.Iterable[str] | None = None,
111
+ ) -> ta.Sequence[str]:
112
+ filter_pats = [re.compile(f) for f in filters or []]
113
+
114
+ out: list[str] = []
115
+ stk: list[str] = list(reversed(roots))
116
+ while stk:
117
+ cur = stk.pop()
118
+ if os.sep in cur:
119
+ if os.path.isdir(cur):
120
+ stk.extend(reversed([
121
+ os.path.join(cur, c)
122
+ for c in os.listdir(cur)
123
+ ]))
124
+ continue
125
+
126
+ if not cur.endswith('.py'):
127
+ continue
128
+
129
+ spec = cur.rpartition('.')[0].replace(os.sep, '.')
130
+
131
+ else:
132
+ spec = cur
133
+
134
+ if any(p.fullmatch(spec) for p in filter_pats):
135
+ continue
136
+
137
+ out.append(spec)
138
+
139
+ return out
140
+
141
+
142
+ def run(
143
+ *roots: str,
144
+ filters: ta.Iterable[str] | None = None,
145
+ num_threads: int | None = 0,
146
+ **kwargs: ta.Any,
147
+ ) -> ta.Mapping[str, Item]:
148
+ specs = _find_specs(*roots, filters=filters)
149
+
150
+ out: dict[str, Item] = {}
151
+ with cu.new_executor(num_threads) as ex:
152
+ futs = [ex.submit(run_one, spec, **kwargs) for spec in specs]
153
+ for fut in futs:
154
+ item = fut.result()
155
+ out[item.spec] = item
156
+
157
+ return out
158
+
159
+
160
+ ##
161
+
162
+
163
+ def _main() -> None:
164
+ parser = argparse.ArgumentParser()
165
+ parser.add_argument('-j', '--jobs', type=int)
166
+ parser.add_argument('-f', '--filter', action='append')
167
+ parser.add_argument('-t', '--filter-tests', action='store_true')
168
+ parser.add_argument('root', nargs='+')
169
+ args = parser.parse_args()
170
+
171
+ filters = [*(args.filter or [])]
172
+ if args.filter_tests:
173
+ filters.extend([
174
+ r'.*\.conftest',
175
+ r'.*\.tests\..*',
176
+ ])
177
+
178
+ for item in run(
179
+ *args.root,
180
+ filters=filters,
181
+ num_threads=args.jobs,
182
+ ).values():
183
+ print(json.dumps(dc.asdict(item)))
184
+
185
+
186
+ if __name__ == '__main__':
187
+ _main()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omdev
3
- Version: 0.0.0.dev22
3
+ Version: 0.0.0.dev24
4
4
  Summary: omdev
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,7 +12,7 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: ~=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omlish ==0.0.0.dev22
15
+ Requires-Dist: omlish ==0.0.0.dev24
16
16
  Provides-Extra: all
17
17
  Requires-Dist: pycparser ~=2.22 ; extra == 'all'
18
18
  Requires-Dist: cffi ~=1.17 ; extra == 'all'
@@ -13,7 +13,7 @@ omdev/amalg/__main__.py,sha256=OE1udULO1g4McUbeg1CoHbSm4hbQ2kcE3ffEGxlnPh4,69
13
13
  omdev/amalg/amalg.py,sha256=YEyH097MZop-f1qobZJW__srtyLFS3rI7M2MaRtshKg,13057
14
14
  omdev/cexts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  omdev/cexts/build.py,sha256=zViF1wYx6z5ACyifgNjlCAVoPAMCKpTr_VoAvwtmvtY,1013
16
- omdev/cexts/cmake.py,sha256=WrH5l3WnamyVEcMMjxkX87bNT7O7ZWX-docajc7XwAg,9726
16
+ omdev/cexts/cmake.py,sha256=WiGcxmsI9dGQ5rM5ByMtHdG_MBP1dhj3gkbwUWY9rm8,9741
17
17
  omdev/cexts/importhook.py,sha256=nljqEuPopuh10DPeSrIYKmkV4z-Wk5Q7WpUid1JEmkg,3530
18
18
  omdev/cexts/magic.py,sha256=LhC31I2GiCq3NRU5dpy_9do6IVjhdGu58uPPFffQx5Q,135
19
19
  omdev/cexts/scan.py,sha256=_U5DX9ksHP1845PdGxWh4Rf1a6x_sG1MH3uL_hwBnKY,1669
@@ -37,8 +37,8 @@ omdev/interp/__main__.py,sha256=gFhR9DikwDZk0LqgdR3qq_aXQHThUOPllDmHDOfnFAU,67
37
37
  omdev/interp/cli.py,sha256=8T3qLXTC2mni5FXDHkHN3mZG9_BnjkDMXYy6EYbAYR8,1679
38
38
  omdev/interp/inspect.py,sha256=SI4jQmWfXCnlceFTxlVRfTlEYYCjO_X12wuG5e74yto,2849
39
39
  omdev/interp/providers.py,sha256=PFEjozW0c33eqg8sno-GHMKbhVUzQF9jrAx-M0uQimk,1787
40
- omdev/interp/pyenv.py,sha256=oxwvX7fpM4VMVeqq09JHHweifPI5l4uFAa3V_V9z5Yg,11774
41
- omdev/interp/resolvers.py,sha256=e8H8WcVC0Du3nCWrghsbVLJuP3ABaiH_DexZhjZITpQ,3007
40
+ omdev/interp/pyenv.py,sha256=whNOnNnt01rRL6pe3Ew-o84_V84bBTG6mR-wro0Vh3Q,12454
41
+ omdev/interp/resolvers.py,sha256=tpzlmqGp1C4QKdA6TfcPmtmaygu7mb6WK2RPSbyNQ6s,3022
42
42
  omdev/interp/standalone.py,sha256=XcltiL7ypcfV89C82_3knQ3Kx7aW4wnnxf2056ZXC3A,7731
43
43
  omdev/interp/system.py,sha256=UFHfMR0CHCEnNx5fhrze8esAwigpRrJUA33ftq6nA0I,3514
44
44
  omdev/interp/types.py,sha256=2nM3MJaOU8O1XA8DrvwyOjBWSboBn16kgFIy5JVkDck,2440
@@ -50,29 +50,30 @@ omdev/precheck/precheck.py,sha256=2yTjNGvjPYf3QxUBbCbehBYYuB8gDR_dYSTrlNCs9qU,83
50
50
  omdev/pyproject/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
51
51
  omdev/pyproject/__main__.py,sha256=gFhR9DikwDZk0LqgdR3qq_aXQHThUOPllDmHDOfnFAU,67
52
52
  omdev/pyproject/cexts.py,sha256=x13piOOnNrYbA17qZLDVuR0p1sqhgEwpk4FtImX-klM,4281
53
- omdev/pyproject/cli.py,sha256=bj7BMsYjUagp266ms-maU11woWC2hP9eQoOsisCzrCY,10358
53
+ omdev/pyproject/cli.py,sha256=qBVsQDcNSCC3i78X9jFlPZ3ahDSY-0OD0UN1mbqLgYE,10649
54
54
  omdev/pyproject/configs.py,sha256=K9H5cGwVLgHi8wKwtYvlXHZ9ThtmnI4jo8JAb-t1-70,2859
55
55
  omdev/pyproject/pkg.py,sha256=1HcVUoTE_vdc5bAW3AUFcM-3BWI1ICoXEJlkTFIAmh4,9856
56
56
  omdev/pyproject/reqs.py,sha256=coq21cdWQIPs06-iuRnwc6F2Sf-IxpqoT6DEMhol2kA,2298
57
57
  omdev/scripts/__init__.py,sha256=MKCvUAEQwsIvwLixwtPlpBqmkMXLCnjjXyAXvVpDwVk,91
58
58
  omdev/scripts/bumpversion.py,sha256=Kn7fo73Hs8uJh3Hi3EIyLOlzLPWAC6dwuD_lZ3cIzuY,1064
59
59
  omdev/scripts/execrss.py,sha256=HzDNmwXOO8fMwIRXw9q8CUnVfLFCQASyU2tfY_y2Vf8,324
60
- omdev/scripts/interp.py,sha256=i0Mq1ryZOTPOchV4liGjUiWNv0xLkoRWWIRb5BUH36s,65122
61
- omdev/scripts/pyproject.py,sha256=MoILXhFFu5ItB1a9680TzuiwS-hosd2AwXy8rxEa_hk,144373
60
+ omdev/scripts/interp.py,sha256=gMFw-FbHvMO706wrLbOBY-kZlB3fNYJXMTwJlyVIQQc,68506
61
+ omdev/scripts/pyproject.py,sha256=X0Aw7LWf1dDrh6cIbI_u7rapKYYPymscKyePPrcjbxY,148048
62
62
  omdev/toml/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
63
63
  omdev/toml/parser.py,sha256=84bn09uhYHwQGyfww6Rw6y1RxPAE_HDltODOSakcqDM,29186
64
64
  omdev/toml/writer.py,sha256=dwz_Qw8z5Z_nmWpXqch63W6S_j6n256erb7AGFTVzB4,2872
65
65
  omdev/tools/__init__.py,sha256=iVJAOQ0viGTQOm0DLX4uZLro-9jOioYJGLg9s0kDx1A,78
66
66
  omdev/tools/dockertools.py,sha256=3844AhUst6kYo2xKNn-2Npi-f6r4rocxEOx0tHjE0dk,2063
67
67
  omdev/tools/gittools.py,sha256=zPy2D5WDs-CbwT86_T_hbaq5yCuss5e-ouUccXC6xlg,578
68
+ omdev/tools/importscan.py,sha256=XRLiasVSaTIp-jnO0-Nfhi0t6gnv_hVy5j2nVfEvuMI,3831
69
+ omdev/tools/importtrace.py,sha256=oDry9CwIv5h96wSaTVKJ0qQ5vMGxYE5oBtfF-GYNLJs,13430
68
70
  omdev/tools/rst.py,sha256=6dWk8QZHoGiLSuBw3TKsXZjjFK6wWBEtPi9krdCLKKg,977
69
71
  omdev/tools/sqlrepl.py,sha256=v9uVQ4nvquSXcQVYIFq34ikumSILvKqzD6lUKLcncCE,5646
70
- omdev/tools/traceimport.py,sha256=oDry9CwIv5h96wSaTVKJ0qQ5vMGxYE5oBtfF-GYNLJs,13430
71
72
  omdev/versioning/__init__.py,sha256=Y3l4WY4JRi2uLG6kgbGp93fuGfkxkKwZDvhsa0Rwgtk,15
72
73
  omdev/versioning/specifiers.py,sha256=6Odf9e6farwlPRsD_YqwTfYKG-BXn_dIcKtqfkhfodI,17432
73
74
  omdev/versioning/versions.py,sha256=ei2eopEsJq3zSMJmezK1nzZgikgCdxFtnF3f69nCRZQ,12246
74
- omdev-0.0.0.dev22.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
75
- omdev-0.0.0.dev22.dist-info/METADATA,sha256=e73D6b6kCLs6xhPkO203BOo7sy01b9t4qrw-ii4oW5U,1252
76
- omdev-0.0.0.dev22.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
77
- omdev-0.0.0.dev22.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
78
- omdev-0.0.0.dev22.dist-info/RECORD,,
75
+ omdev-0.0.0.dev24.dist-info/LICENSE,sha256=B_hVtavaA8zCYDW99DYdcpDLKz1n3BBRjZrcbv8uG8c,1451
76
+ omdev-0.0.0.dev24.dist-info/METADATA,sha256=aKMRukPrnmd7ZvW0-L-rvtXL4QpczmUX4yW3PegnvbA,1252
77
+ omdev-0.0.0.dev24.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
78
+ omdev-0.0.0.dev24.dist-info/top_level.txt,sha256=1nr7j30fEWgLYHW3lGR9pkdHkb7knv1U1ES1XRNVQ6k,6
79
+ omdev-0.0.0.dev24.dist-info/RECORD,,
File without changes