omdev 0.0.0.dev416__py3-none-any.whl → 0.0.0.dev500__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.

Files changed (211) hide show
  1. omdev/{.manifests.json → .omlish-manifests.json} +23 -47
  2. omdev/README.md +51 -0
  3. omdev/__about__.py +12 -8
  4. omdev/amalg/cli/main.py +1 -2
  5. omdev/amalg/gen/gen.py +49 -6
  6. omdev/amalg/gen/imports.py +1 -1
  7. omdev/amalg/gen/manifests.py +1 -1
  8. omdev/amalg/gen/resources.py +1 -1
  9. omdev/amalg/gen/srcfiles.py +26 -3
  10. omdev/amalg/gen/strip.py +1 -1
  11. omdev/amalg/gen/types.py +1 -1
  12. omdev/amalg/gen/typing.py +1 -1
  13. omdev/amalg/info.py +32 -0
  14. omdev/cache/compute/storage.py +3 -1
  15. omdev/cache/data/actions.py +1 -1
  16. omdev/cache/data/cache.py +2 -2
  17. omdev/cache/data/specs.py +1 -1
  18. omdev/cexts/_boilerplate.cc +2 -3
  19. omdev/cexts/_distutils/build_ext.py +5 -2
  20. omdev/cexts/_distutils/compilers/ccompiler.py +5 -2
  21. omdev/cexts/_distutils/compilers/options.py +3 -0
  22. omdev/cexts/_distutils/compilers/unixccompiler.py +6 -2
  23. omdev/cexts/_distutils/dir_util.py +6 -2
  24. omdev/cexts/_distutils/errors.py +3 -0
  25. omdev/cexts/_distutils/extension.py +3 -0
  26. omdev/cexts/_distutils/file_util.py +6 -2
  27. omdev/cexts/_distutils/modified.py +3 -0
  28. omdev/cexts/_distutils/spawn.py +6 -2
  29. omdev/cexts/_distutils/sysconfig.py +3 -0
  30. omdev/cexts/_distutils/util.py +6 -2
  31. omdev/cexts/_distutils/version.py +3 -0
  32. omdev/cexts/cmake.py +5 -3
  33. omdev/cexts/scan.py +1 -2
  34. omdev/ci/cache.py +7 -3
  35. omdev/ci/cli.py +6 -4
  36. omdev/ci/docker/buildcaching.py +3 -1
  37. omdev/ci/docker/cache.py +2 -1
  38. omdev/ci/docker/cacheserved/cache.py +4 -1
  39. omdev/ci/docker/cacheserved/manifests.py +2 -2
  40. omdev/ci/docker/dataserver.py +2 -2
  41. omdev/ci/docker/imagepulling.py +2 -1
  42. omdev/ci/docker/packing.py +1 -1
  43. omdev/ci/docker/repositories.py +2 -1
  44. omdev/ci/github/api/clients.py +8 -4
  45. omdev/ci/github/api/v1/client.py +4 -1
  46. omdev/ci/github/api/v2/api.py +2 -0
  47. omdev/ci/github/api/v2/azure.py +4 -1
  48. omdev/ci/github/api/v2/client.py +4 -1
  49. omdev/cli/clicli.py +37 -7
  50. omdev/clipboard/clipboard.py +1 -1
  51. omdev/cmake.py +2 -1
  52. omdev/cmdlog/cli.py +1 -2
  53. omdev/dataclasses/_dumping.py +1960 -0
  54. omdev/dataclasses/_template.py +22 -0
  55. omdev/dataclasses/cli.py +7 -2
  56. omdev/dataclasses/codegen.py +342 -62
  57. omdev/dataclasses/dumping.py +200 -0
  58. omdev/dataserver/handlers.py +3 -2
  59. omdev/dataserver/targets.py +2 -2
  60. omdev/imgur.py +2 -2
  61. omdev/interp/cli.py +1 -1
  62. omdev/interp/inspect.py +2 -1
  63. omdev/interp/providers/base.py +3 -2
  64. omdev/interp/providers/standalone.py +4 -1
  65. omdev/interp/providers/system.py +2 -2
  66. omdev/interp/pyenv/install.py +2 -1
  67. omdev/interp/pyenv/provider.py +2 -2
  68. omdev/interp/types.py +3 -2
  69. omdev/interp/uv/provider.py +40 -2
  70. omdev/interp/uv/uv.py +2 -2
  71. omdev/interp/venvs.py +3 -2
  72. omdev/irc/messages/base.py +50 -0
  73. omdev/irc/messages/formats.py +92 -0
  74. omdev/irc/messages/messages.py +775 -0
  75. omdev/irc/messages/parsing.py +99 -0
  76. omdev/irc/numerics/formats.py +97 -0
  77. omdev/irc/numerics/numerics.py +865 -0
  78. omdev/irc/numerics/types.py +59 -0
  79. omdev/irc/protocol/LICENSE +11 -0
  80. omdev/irc/protocol/__init__.py +61 -0
  81. omdev/irc/protocol/consts.py +6 -0
  82. omdev/irc/protocol/errors.py +30 -0
  83. omdev/irc/protocol/message.py +21 -0
  84. omdev/irc/protocol/nuh.py +55 -0
  85. omdev/irc/protocol/parsing.py +158 -0
  86. omdev/irc/protocol/rendering.py +153 -0
  87. omdev/irc/protocol/tags.py +102 -0
  88. omdev/irc/protocol/utils.py +30 -0
  89. omdev/manifests/_dumping.py +529 -136
  90. omdev/manifests/building.py +6 -3
  91. omdev/manifests/main.py +1 -1
  92. omdev/markdown/__init__.py +0 -0
  93. omdev/markdown/incparse.py +116 -0
  94. omdev/markdown/tokens.py +51 -0
  95. omdev/oci/data.py +2 -2
  96. omdev/oci/datarefs.py +2 -2
  97. omdev/oci/media.py +2 -2
  98. omdev/oci/repositories.py +3 -2
  99. omdev/packaging/marshal.py +9 -9
  100. omdev/packaging/requires.py +6 -6
  101. omdev/packaging/revisions.py +5 -2
  102. omdev/packaging/specifiers.py +41 -42
  103. omdev/packaging/versions.py +10 -10
  104. omdev/packaging/wheelfile.py +4 -2
  105. omdev/precheck/blanklines.py +66 -0
  106. omdev/precheck/caches.py +1 -1
  107. omdev/precheck/imports.py +14 -1
  108. omdev/precheck/lite.py +2 -2
  109. omdev/precheck/main.py +5 -5
  110. omdev/precheck/unicode.py +39 -15
  111. omdev/py/asts/__init__.py +0 -0
  112. omdev/py/asts/parents.py +28 -0
  113. omdev/py/asts/toplevel.py +123 -0
  114. omdev/py/asts/visitors.py +18 -0
  115. omdev/py/attrdocs.py +6 -7
  116. omdev/py/bracepy.py +12 -4
  117. omdev/py/docstrings/numpydoc.py +4 -4
  118. omdev/py/reprs.py +32 -0
  119. omdev/py/scripts/execstat.py +31 -26
  120. omdev/py/srcheaders.py +1 -1
  121. omdev/py/tokens/__init__.py +0 -0
  122. omdev/{tokens → py/tokens}/utils.py +2 -1
  123. omdev/py/tools/importscan.py +2 -2
  124. omdev/py/tools/mkrelimp.py +3 -4
  125. omdev/py/tools/pipdepup.py +686 -0
  126. omdev/pyproject/cli.py +1 -1
  127. omdev/pyproject/pkg.py +197 -48
  128. omdev/pyproject/reqs.py +36 -10
  129. omdev/pyproject/tools/__init__.py +0 -0
  130. omdev/pyproject/tools/aboutdeps.py +60 -0
  131. omdev/pyproject/venvs.py +12 -2
  132. omdev/rs/__init__.py +0 -0
  133. omdev/scripts/ci.py +9551 -6982
  134. omdev/scripts/interp.py +1323 -892
  135. omdev/scripts/lib/__init__.py +0 -0
  136. omdev/scripts/lib/inject.py +2086 -0
  137. omdev/scripts/lib/logs.py +2175 -0
  138. omdev/scripts/lib/marshal.py +1731 -0
  139. omdev/scripts/pyproject.py +4979 -1874
  140. omdev/tools/docker.py +19 -7
  141. omdev/tools/git/cli.py +56 -16
  142. omdev/tools/git/messages.py +2 -2
  143. omdev/tools/json/cli.py +6 -6
  144. omdev/tools/json/formats.py +2 -0
  145. omdev/tools/json/parsing.py +5 -5
  146. omdev/tools/json/processing.py +6 -3
  147. omdev/tools/json/rendering.py +2 -2
  148. omdev/tools/jsonview/cli.py +49 -65
  149. omdev/tools/jsonview/resources/jsonview.html.j2 +43 -0
  150. omdev/tools/pawk/README.md +195 -0
  151. omdev/tools/pawk/pawk.py +2 -2
  152. omdev/tools/pip.py +8 -0
  153. omdev/tui/__init__.py +0 -0
  154. omdev/tui/apps/__init__.py +0 -0
  155. omdev/tui/apps/edit/__init__.py +0 -0
  156. omdev/tui/apps/edit/main.py +167 -0
  157. omdev/tui/apps/irc/__init__.py +0 -0
  158. omdev/tui/apps/irc/__main__.py +4 -0
  159. omdev/tui/apps/irc/app.py +286 -0
  160. omdev/tui/apps/irc/client.py +187 -0
  161. omdev/tui/apps/irc/commands.py +175 -0
  162. omdev/tui/apps/irc/main.py +26 -0
  163. omdev/tui/apps/markdown/__init__.py +0 -0
  164. omdev/tui/apps/markdown/__main__.py +11 -0
  165. omdev/{ptk → tui/apps}/markdown/cli.py +5 -7
  166. omdev/tui/rich/__init__.py +46 -0
  167. omdev/tui/rich/console2.py +20 -0
  168. omdev/tui/rich/markdown2.py +186 -0
  169. omdev/tui/textual/__init__.py +265 -0
  170. omdev/tui/textual/app2.py +16 -0
  171. omdev/tui/textual/autocomplete/LICENSE +21 -0
  172. omdev/tui/textual/autocomplete/__init__.py +33 -0
  173. omdev/tui/textual/autocomplete/matching.py +226 -0
  174. omdev/tui/textual/autocomplete/paths.py +202 -0
  175. omdev/tui/textual/autocomplete/widget.py +612 -0
  176. omdev/tui/textual/debug/__init__.py +10 -0
  177. omdev/tui/textual/debug/dominfo.py +151 -0
  178. omdev/tui/textual/debug/screen.py +24 -0
  179. omdev/tui/textual/devtools.py +187 -0
  180. omdev/tui/textual/drivers2.py +55 -0
  181. omdev/tui/textual/logging2.py +20 -0
  182. omdev/tui/textual/types.py +45 -0
  183. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/METADATA +18 -12
  184. omdev-0.0.0.dev500.dist-info/RECORD +386 -0
  185. omdev/ptk/__init__.py +0 -103
  186. omdev/ptk/apps/ncdu.py +0 -167
  187. omdev/ptk/confirm.py +0 -60
  188. omdev/ptk/markdown/LICENSE +0 -22
  189. omdev/ptk/markdown/__init__.py +0 -10
  190. omdev/ptk/markdown/__main__.py +0 -11
  191. omdev/ptk/markdown/border.py +0 -94
  192. omdev/ptk/markdown/markdown.py +0 -390
  193. omdev/ptk/markdown/parser.py +0 -42
  194. omdev/ptk/markdown/styles.py +0 -29
  195. omdev/ptk/markdown/tags.py +0 -299
  196. omdev/ptk/markdown/utils.py +0 -366
  197. omdev/pyproject/cexts.py +0 -110
  198. omdev/tools/antlr/__main__.py +0 -11
  199. omdev/tools/antlr/cli.py +0 -62
  200. omdev/tools/antlr/consts.py +0 -7
  201. omdev/tools/antlr/gen.py +0 -188
  202. omdev-0.0.0.dev416.dist-info/RECORD +0 -332
  203. /omdev/{ptk/apps → irc}/__init__.py +0 -0
  204. /omdev/{tokens → irc/messages}/__init__.py +0 -0
  205. /omdev/{tools/antlr → irc/numerics}/__init__.py +0 -0
  206. /omdev/{tokens → py/tokens}/all.py +0 -0
  207. /omdev/{tokens → py/tokens}/tokenizert.py +0 -0
  208. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/WHEEL +0 -0
  209. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/entry_points.txt +0 -0
  210. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/licenses/LICENSE +0 -0
  211. {omdev-0.0.0.dev416.dist-info → omdev-0.0.0.dev500.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,66 @@
1
+ import dataclasses as dc
2
+ import os
3
+ import typing as ta
4
+
5
+ from omlish.text.filecache import TextFileCache
6
+
7
+ from .base import Precheck
8
+ from .base import PrecheckContext
9
+ from .caches import DirWalkCache
10
+ from .caches import HeadersCache
11
+
12
+
13
+ ##
14
+
15
+
16
+ class BlankLinesPrecheck(Precheck['BlankLinesPrecheck.Config']):
17
+ @dc.dataclass(frozen=True)
18
+ class Config(Precheck.Config):
19
+ DEFAULT_FILE_EXTENSIONS: ta.ClassVar[ta.AbstractSet[str]] = frozenset([
20
+ 'py',
21
+
22
+ 'c',
23
+ 'cc',
24
+ 'cu',
25
+ 'h',
26
+ 'hh',
27
+ ])
28
+
29
+ file_extensions: ta.AbstractSet[str] = DEFAULT_FILE_EXTENSIONS
30
+
31
+ def __init__(
32
+ self,
33
+ context: PrecheckContext,
34
+ config: Config = Config(),
35
+ *,
36
+ dir_walk_cache: DirWalkCache,
37
+ text_file_cache: TextFileCache,
38
+ headers_cache: HeadersCache,
39
+ ) -> None:
40
+ super().__init__(config)
41
+
42
+ self._context = context
43
+
44
+ self._dir_walk_cache = dir_walk_cache
45
+ self._text_file_cache = text_file_cache
46
+ self._headers_cache = headers_cache
47
+
48
+ async def _run_file(self, file: str) -> ta.AsyncGenerator[Precheck.Violation]:
49
+ src = self._text_file_cache.get_entry(file).text()
50
+
51
+ if src and not src.splitlines()[0]:
52
+ yield Precheck.Violation(self, f'source file {file} starts with blank line')
53
+
54
+ async def run(self) -> ta.AsyncGenerator[Precheck.Violation]:
55
+ files = [
56
+ os.path.join(e.root, f)
57
+ for src_root in self._context.src_roots
58
+ for e in self._dir_walk_cache.list_dir(src_root)
59
+ for f in e.files
60
+ if '.' in f
61
+ and any(f.endswith('.' + ext) for ext in self._config.file_extensions)
62
+ ]
63
+
64
+ for file in sorted(files):
65
+ async for v in self._run_file(file):
66
+ yield v
omdev/precheck/caches.py CHANGED
@@ -8,7 +8,7 @@ from omlish.text.filecache import TextFileCache
8
8
 
9
9
  from ..py.srcheaders import PyHeaderLine
10
10
  from ..py.srcheaders import get_py_header_lines
11
- from ..tokens import all as tks
11
+ from ..py.tokens import all as tks
12
12
 
13
13
 
14
14
  ##
omdev/precheck/imports.py CHANGED
@@ -3,6 +3,8 @@ import dataclasses as dc
3
3
  import os.path
4
4
  import typing as ta
5
5
 
6
+ from omlish.text.filecache import TextFileCache
7
+
6
8
  from .base import Precheck
7
9
  from .base import PrecheckContext
8
10
  from .caches import AstCache
@@ -26,6 +28,7 @@ class RootRelativeImportPrecheck(Precheck['RootRelativeImportPrecheck.Config']):
26
28
  dir_walk_cache: DirWalkCache,
27
29
  headers_cache: HeadersCache,
28
30
  ast_cache: AstCache,
31
+ text_file_cache: TextFileCache,
29
32
  ) -> None:
30
33
  super().__init__(config)
31
34
 
@@ -34,6 +37,7 @@ class RootRelativeImportPrecheck(Precheck['RootRelativeImportPrecheck.Config']):
34
37
  self._dir_walk_cache = dir_walk_cache
35
38
  self._headers_cache = headers_cache
36
39
  self._ast_cache = ast_cache
40
+ self._text_file_cache = text_file_cache
37
41
 
38
42
  async def _run_py_file(self, py_file: str, src_root: str) -> ta.AsyncGenerator[Precheck.Violation]:
39
43
  if isinstance(header_lines := self._headers_cache.get_file_headers(py_file), Exception):
@@ -41,11 +45,20 @@ class RootRelativeImportPrecheck(Precheck['RootRelativeImportPrecheck.Config']):
41
45
  if any(hl.src.strip() == '# ruff: noqa' for hl in header_lines):
42
46
  return
43
47
 
48
+ py_file_lines = self._text_file_cache.get_entry(py_file).lines()
49
+
44
50
  py_file_ast = self._ast_cache.get_file_ast(py_file)
45
51
  if not isinstance(py_file_ast, ast.Module):
46
52
  return
47
53
 
48
- for cur_node in py_file_ast.body:
54
+ for cur_node in ast.walk(py_file_ast):
55
+ if not isinstance(cur_node, (ast.Import, ast.ImportFrom)):
56
+ continue
57
+
58
+ # FIXME: lame lol
59
+ if py_file_lines[cur_node.lineno - 1].strip().endswith('# noqa'):
60
+ continue
61
+
49
62
  if isinstance(cur_node, ast.Import):
50
63
  imp_alias: ast.alias
51
64
  for imp_alias in cur_node.names:
omdev/precheck/lite.py CHANGED
@@ -2,13 +2,13 @@ import asyncio
2
2
  import dataclasses as dc
3
3
  import glob
4
4
  import inspect
5
- import logging
6
5
  import os.path
7
6
  import subprocess
8
7
  import textwrap
9
8
  import typing as ta
10
9
 
11
10
  from omlish import cached
11
+ from omlish.logs import all as logs
12
12
  from omlish.subprocesses.wrap import subprocess_maybe_shell_wrap_exec
13
13
 
14
14
  from .. import magic
@@ -16,7 +16,7 @@ from .base import Precheck
16
16
  from .base import PrecheckContext
17
17
 
18
18
 
19
- log = logging.getLogger(__name__)
19
+ log = logs.get_module_logger(globals())
20
20
 
21
21
 
22
22
  ##
omdev/precheck/main.py CHANGED
@@ -20,9 +20,7 @@ TODO:
20
20
  """
21
21
  import argparse
22
22
  import asyncio
23
- import logging
24
23
  import os.path
25
- import sys
26
24
  import typing as ta
27
25
 
28
26
  from omlish import inject as inj
@@ -30,6 +28,7 @@ from omlish.logs import all as logs
30
28
 
31
29
  from .base import Precheck
32
30
  from .base import PrecheckContext
31
+ from .blanklines import BlankLinesPrecheck
33
32
  from .caches import AstCache
34
33
  from .caches import DirWalkCache
35
34
  from .caches import HeadersCache
@@ -43,7 +42,7 @@ from .scripts import ScriptDepsPrecheck
43
42
  from .unicode import UnicodePrecheck
44
43
 
45
44
 
46
- log = logging.getLogger(__name__)
45
+ log = logs.get_module_logger(globals())
47
46
 
48
47
 
49
48
  ##
@@ -88,6 +87,7 @@ def _check_cmd(args) -> None:
88
87
  )
89
88
 
90
89
  pc_cfgs: list[Precheck.Config] = [
90
+ BlankLinesPrecheck.Config(),
91
91
  GitBlacklistPrecheck.Config(),
92
92
  LitePython8Precheck.Config(),
93
93
  ManifestsPrecheck.Config(),
@@ -125,7 +125,7 @@ def _check_cmd(args) -> None:
125
125
 
126
126
  if vs:
127
127
  print(f'{len(vs)} violations found')
128
- sys.exit(1)
128
+ raise SystemExit(1)
129
129
 
130
130
 
131
131
  ##
@@ -133,12 +133,12 @@ def _check_cmd(args) -> None:
133
133
 
134
134
  def _build_parser() -> argparse.ArgumentParser:
135
135
  parser = argparse.ArgumentParser()
136
+ parser.add_argument('-v', '--verbose', action='store_true')
136
137
 
137
138
  subparsers = parser.add_subparsers()
138
139
 
139
140
  parser_check = subparsers.add_parser('check')
140
141
  parser_check.add_argument('roots', nargs='+')
141
- parser_check.add_argument('-v', '--verbose', action='store_true')
142
142
  parser_check.set_defaults(func=_check_cmd)
143
143
 
144
144
  return parser
omdev/precheck/unicode.py CHANGED
@@ -29,6 +29,18 @@ class UnicodePrecheck(Precheck['UnicodePrecheck.Config']):
29
29
 
30
30
  permitted_categories: ta.AbstractSet[str] = DEFAULT_PERMITTED_CATEGORIES
31
31
 
32
+ DEFAULT_FILE_EXTENSIONS: ta.ClassVar[ta.AbstractSet[str]] = frozenset([
33
+ 'py',
34
+
35
+ 'c',
36
+ 'cc',
37
+ 'cu',
38
+ 'h',
39
+ 'hh',
40
+ ])
41
+
42
+ file_extensions: ta.AbstractSet[str] = DEFAULT_FILE_EXTENSIONS
43
+
32
44
  def __init__(
33
45
  self,
34
46
  context: PrecheckContext,
@@ -46,19 +58,15 @@ class UnicodePrecheck(Precheck['UnicodePrecheck.Config']):
46
58
  self._text_file_cache = text_file_cache
47
59
  self._headers_cache = headers_cache
48
60
 
49
- async def _run_py_file(self, py_file: str) -> ta.AsyncGenerator[Precheck.Violation]:
50
- if isinstance(header_lines := self._headers_cache.get_file_headers(py_file), Exception):
51
- return
52
- if any(hl.src.strip() == '# @omlish-precheck-allow-any-unicode' for hl in header_lines):
53
- return
54
-
55
- src = self._text_file_cache.get_entry(py_file).text()
61
+ async def _run_file(self, file: str, src: str | None = None) -> ta.AsyncGenerator[Precheck.Violation]:
62
+ if src is None:
63
+ src = self._text_file_cache.get_entry(file).text()
56
64
 
57
65
  illegal_chars = {
58
66
  ch
59
67
  for ch in src
60
- if ord(ch) > 255 and
61
- unicodedata.category(ch) not in self._config.permitted_categories
68
+ if ord(ch) > 255
69
+ and unicodedata.category(ch) not in self._config.permitted_categories
62
70
  }
63
71
 
64
72
  if illegal_chars:
@@ -66,16 +74,32 @@ class UnicodePrecheck(Precheck['UnicodePrecheck.Config']):
66
74
  f'({ch!r}, {unicodedata.category(ch)})'
67
75
  for ch in sorted(illegal_chars)
68
76
  ]
69
- yield Precheck.Violation(self, f'source file {py_file} has illegal unicode characters: {", ".join(sl)}')
77
+ yield Precheck.Violation(self, f'source file {file} has illegal unicode characters: {", ".join(sl)}')
78
+
79
+ async def _run_py_file(self, py_file: str) -> ta.AsyncGenerator[Precheck.Violation]:
80
+ if isinstance(header_lines := self._headers_cache.get_file_headers(py_file), Exception):
81
+ return
82
+ if any(hl.src.strip() == '# @omlish-precheck-allow-any-unicode' for hl in header_lines):
83
+ return
84
+
85
+ async for v in self._run_file(py_file):
86
+ yield v
70
87
 
71
88
  async def run(self) -> ta.AsyncGenerator[Precheck.Violation]:
72
- py_files = [
89
+ files = [
73
90
  os.path.join(e.root, f)
74
91
  for src_root in self._context.src_roots
75
92
  for e in self._dir_walk_cache.list_dir(src_root)
76
93
  for f in e.files
77
- if f.endswith('.py')
94
+ if '.' in f
95
+ and any(f.endswith('.' + ext) for ext in self._config.file_extensions)
78
96
  ]
79
- for py_file in sorted(py_files):
80
- async for v in self._run_py_file(py_file):
81
- yield v
97
+
98
+ for file in sorted(files):
99
+ if file.endswith('.py'):
100
+ async for v in self._run_py_file(file):
101
+ yield v
102
+
103
+ else:
104
+ async for v in self._run_file(file):
105
+ yield v
File without changes
@@ -0,0 +1,28 @@
1
+ import ast
2
+
3
+ from omlish import check
4
+
5
+
6
+ ##
7
+
8
+
9
+ class _ParentsNodeVisitor(ast.NodeVisitor):
10
+ def __init__(self) -> None:
11
+ super().__init__()
12
+
13
+ self.parents: dict[ast.AST, ast.AST | None] = {}
14
+
15
+ parent: ast.AST | None = None
16
+
17
+ def generic_visit(self, node: ast.AST) -> None:
18
+ check.not_in(node, self.parents)
19
+ prev_parent = self.parents[node] = self.parent
20
+ self.parent = node
21
+ super().generic_visit(node)
22
+ self.parent = prev_parent
23
+
24
+
25
+ def get_node_parents(node: ast.AST) -> dict[ast.AST, ast.AST | None]:
26
+ visitor = _ParentsNodeVisitor()
27
+ visitor.visit(node)
28
+ return visitor.parents
@@ -0,0 +1,123 @@
1
+ """
2
+ Quite, quite lame, but sufficient for immediate needs. Accurate static symbol resolution is in development but can't
3
+ block other work.
4
+ """
5
+ import ast
6
+ import dataclasses as dc
7
+ import typing as ta
8
+
9
+ from omlish import check
10
+
11
+
12
+ ##
13
+
14
+
15
+ @dc.dataclass(frozen=True, kw_only=True)
16
+ class TopLevelImport:
17
+ spec: str
18
+ name: str
19
+ node: ast.Import | ast.ImportFrom
20
+
21
+
22
+ @dc.dataclass(frozen=True, kw_only=True)
23
+ class TopLevelCall:
24
+ node: ast.Call
25
+ imp: TopLevelImport
26
+
27
+
28
+ @dc.dataclass(frozen=True, kw_only=True)
29
+ class TopLevelFindings:
30
+ imports: ta.Mapping[str, TopLevelImport]
31
+ calls: ta.Sequence[TopLevelCall]
32
+
33
+
34
+ ##
35
+
36
+
37
+ class _TopLevelModuleVisitor(ast.NodeVisitor):
38
+ def __init__(self, module_name: str) -> None:
39
+ super().__init__()
40
+
41
+ self.module_name = module_name
42
+ self.module_name_parts = module_name.split('.')
43
+
44
+ self.imports: dict[str, TopLevelImport] = {}
45
+ self.calls: list[TopLevelCall] = []
46
+
47
+ #
48
+
49
+ def visit_Import(self, node: ast.Import) -> None:
50
+ for alias in node.names:
51
+ name = alias.asname if alias.asname else alias.name
52
+ self.imports[name] = TopLevelImport(
53
+ spec=alias.name,
54
+ name=name,
55
+ node=node,
56
+ )
57
+
58
+ self.generic_visit(node)
59
+
60
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
61
+ if node.level:
62
+ check.state(node.level < len(self.module_name_parts))
63
+ module = '.'.join([
64
+ *self.module_name_parts[:-node.level],
65
+ *([node.module] if node.module else []),
66
+ ])
67
+ else:
68
+ module = check.not_none(node.module)
69
+
70
+ for alias in node.names:
71
+ name = alias.asname if alias.asname else alias.name
72
+ self.imports[name] = TopLevelImport(
73
+ spec='.'.join([module, alias.name]),
74
+ name=name,
75
+ node=node,
76
+ )
77
+
78
+ self.generic_visit(node)
79
+
80
+ #
81
+
82
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
83
+ pass
84
+
85
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
86
+ pass
87
+
88
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
89
+ pass
90
+
91
+ #
92
+
93
+ def handle_call(self, node: ast.Call) -> None:
94
+ if not (
95
+ isinstance(attr := node.func, ast.Attribute) and
96
+ isinstance(attr.ctx, ast.Load) and
97
+ isinstance(name := attr.value, ast.Name) and
98
+ isinstance(name.ctx, ast.Load)
99
+ ):
100
+ return
101
+
102
+ if (imp := self.imports.get(name.id)) is None:
103
+ return
104
+
105
+ self.calls.append(TopLevelCall(
106
+ node=node,
107
+ imp=imp,
108
+ ))
109
+
110
+ def visit_Call(self, node: ast.Call) -> None:
111
+ self.handle_call(node)
112
+
113
+
114
+ def analyze_module_top_level(
115
+ module: ast.Module,
116
+ module_name: str,
117
+ ) -> TopLevelFindings:
118
+ visitor = _TopLevelModuleVisitor(module_name)
119
+ visitor.visit(module)
120
+ return TopLevelFindings(
121
+ imports=visitor.imports,
122
+ calls=visitor.calls,
123
+ )
@@ -0,0 +1,18 @@
1
+ import ast
2
+
3
+ from omlish import check
4
+
5
+
6
+ ##
7
+
8
+
9
+ class StackNodeVisitor(ast.NodeVisitor):
10
+ def __init__(self) -> None:
11
+ super().__init__()
12
+
13
+ self.node_stack: list[ast.AST] = []
14
+
15
+ def generic_visit(self, node: ast.AST) -> None:
16
+ self.node_stack.append(node)
17
+ super().generic_visit(node)
18
+ check.is_(self.node_stack.pop(), node)
omdev/py/attrdocs.py CHANGED
@@ -12,7 +12,7 @@ import typing as ta
12
12
  from omlish import check
13
13
  from omlish import lang
14
14
 
15
- from ..tokens import all as tks
15
+ from .tokens import all as tks
16
16
 
17
17
 
18
18
  ##
@@ -24,12 +24,11 @@ class AttrDoc:
24
24
  trailing_comment: str | None = None
25
25
  preceding_comment: str | None = None
26
26
 
27
- __repr__ = lang.AttrRepr.of(
28
- 'docstring',
29
- 'trailing_comment',
30
- 'preceding_comment',
31
- value_filter=bool,
32
- )
27
+ __repr__ = lang.attr_ops(lambda o: (
28
+ o.docstring,
29
+ o.trailing_comment,
30
+ o.preceding_comment,
31
+ ), repr_filter=bool).repr
33
32
 
34
33
 
35
34
  _EMPTY_ATTR_DOC = AttrDoc()
omdev/py/bracepy.py CHANGED
@@ -101,18 +101,26 @@ _CLI_MODULE = {'!.cli.types.CliModule': {
101
101
  if __name__ == '__main__':
102
102
  def _main(argv=None) -> None:
103
103
  import argparse
104
+ import sys
104
105
 
105
106
  parser = argparse.ArgumentParser()
106
107
  parser.add_argument('-x', '--exec', action='store_true')
107
- parser.add_argument('cmd')
108
+ parser.add_argument('-i', '--indent', type=int)
109
+ parser.add_argument('code', nargs='?')
108
110
 
109
111
  args = parser.parse_args(argv)
110
112
 
111
- src = translate_brace_python(args.cmd)
113
+ if (code := args.code) is None:
114
+ code = sys.stdin.read()
115
+
116
+ out = translate_brace_python(
117
+ code,
118
+ **(dict(indent_width=args.indent) if args.indent is not None else {}),
119
+ )
112
120
 
113
121
  if args.exec:
114
- exec(src)
122
+ exec(out)
115
123
  else:
116
- print(src)
124
+ print(out)
117
125
 
118
126
  _main()
@@ -74,7 +74,7 @@ class Section:
74
74
  dashes = '-' * len(self.title)
75
75
  return rf'^({self.title})\s*?\n{dashes}\s*$'
76
76
 
77
- def parse(self, text: str) -> ta.Iterable[DocstringMeta]:
77
+ def parse(self, text: str) -> ta.Iterator[DocstringMeta]:
78
78
  """
79
79
  Parse ``DocstringMeta`` objects from the body of this section.
80
80
 
@@ -100,7 +100,7 @@ class _KVSection(Section, abc.ABC):
100
100
  def _parse_item(self, key: str, value: str) -> DocstringMeta:
101
101
  raise NotImplementedError
102
102
 
103
- def parse(self, text: str) -> ta.Iterable[DocstringMeta]:
103
+ def parse(self, text: str) -> ta.Iterator[DocstringMeta]:
104
104
  for match, next_match in _pairwise(KV_PAT.finditer(text)):
105
105
  start = match.end()
106
106
  end = next_match.start() if next_match is not None else None
@@ -227,7 +227,7 @@ class YieldsSection(ReturnsSection):
227
227
  class DeprecationSection(_SphinxSection):
228
228
  """Parser for numpydoc "deprecation warning" sections."""
229
229
 
230
- def parse(self, text: str) -> ta.Iterable[DocstringDeprecated]:
230
+ def parse(self, text: str) -> ta.Iterator[DocstringDeprecated]:
231
231
  version, desc, *_ = [*text.split(sep='\n', maxsplit=1), None, None]
232
232
 
233
233
  if desc is not None:
@@ -254,7 +254,7 @@ class ExamplesSection(Section):
254
254
  [ 6586976, 22740995]])
255
255
  """
256
256
 
257
- def parse(self, text: str) -> ta.Iterable[DocstringMeta]:
257
+ def parse(self, text: str) -> ta.Iterator[DocstringMeta]:
258
258
  """
259
259
  Parse ``DocstringExample`` objects from the body of this section.
260
260
 
omdev/py/reprs.py ADDED
@@ -0,0 +1,32 @@
1
+ def textwrap_repr(text: str, width: int = 120, quote_char: str = '"') -> list[str]:
2
+ escaped_quote = '\\' + quote_char
3
+ content_width = width - 2
4
+
5
+ lines: list[str] = []
6
+ current_line: list[str] = []
7
+ current_length = 0
8
+
9
+ for char in text:
10
+ if char == quote_char:
11
+ safe_char = escaped_quote
12
+ elif char == '\\':
13
+ safe_char = '\\\\'
14
+ elif not char.isprintable():
15
+ safe_char = repr(char)[1:-1]
16
+ if quote_char in safe_char:
17
+ safe_char = safe_char.replace(quote_char, escaped_quote)
18
+ else:
19
+ safe_char = char
20
+
21
+ if current_length + len(safe_char) > content_width:
22
+ lines.append(f"{quote_char}{''.join(current_line)}{quote_char}")
23
+ current_line = []
24
+ current_length = 0
25
+
26
+ current_line.append(safe_char)
27
+ current_length += len(safe_char)
28
+
29
+ if current_line:
30
+ lines.append(f"{quote_char}{''.join(current_line)}{quote_char}")
31
+
32
+ return lines
@@ -17,13 +17,14 @@ import typing as ta
17
17
 
18
18
 
19
19
  def _run(
20
+ report: 'ta.Callable[[dict], None]',
20
21
  src: str,
21
22
  *,
22
23
  setup: 'ta.Optional[str]' = None,
23
24
  time: bool = False,
24
25
  rss: bool = False,
25
26
  modules: bool = False,
26
- ) -> dict:
27
+ ) -> None:
27
28
  if rss:
28
29
  import resource # noqa
29
30
 
@@ -61,26 +62,28 @@ def _run(
61
62
 
62
63
  #
63
64
 
64
- exec(code, globals(), ns)
65
+ try:
66
+ exec(code, globals(), ns)
65
67
 
66
- #
68
+ finally:
69
+ #
67
70
 
68
- if time:
69
- end_time = get_time()
71
+ if time:
72
+ end_time = get_time()
70
73
 
71
- if rss:
72
- end_rss = get_rss()
74
+ if rss:
75
+ end_rss = get_rss()
73
76
 
74
- if modules:
75
- end_modules = get_modules()
77
+ if modules:
78
+ end_modules = get_modules()
76
79
 
77
- #
80
+ #
78
81
 
79
- return {
80
- **({'time': (end_time - start_time)} if time else {}), # noqa
81
- **({'rss': (end_rss - start_rss)} if rss else {}), # noqa
82
- **({'modules': [m for m in end_modules if m not in start_modules]} if modules else {}), # noqa
83
- }
82
+ report({
83
+ **({'time': (end_time - start_time)} if time else {}), # noqa
84
+ **({'rss': (end_rss - start_rss)} if rss else {}), # noqa
85
+ **({'modules': [m for m in end_modules if m not in start_modules]} if modules else {}), # noqa
86
+ })
84
87
 
85
88
 
86
89
  #
@@ -145,13 +148,10 @@ def _main() -> None:
145
148
 
146
149
  payload = '\n'.join([
147
150
  inspect.getsource(_run),
148
- f'dct = _run(**{run_kw!r})',
149
- f'import json',
150
- f'with open({out_file!r}, "w") as f:', # noqa
151
- f' f.write(json.dumps(dct))',
151
+ f'with open({out_file!r}, "w") as f: _run(lambda dct: f.write(__import__("json").dumps(dct)), **{run_kw!r})', # noqa
152
152
  ])
153
153
 
154
- subprocess.check_call([exe, '-c', payload])
154
+ subprocess.call([exe, '-c', payload])
155
155
 
156
156
  with open(out_file) as f:
157
157
  result = json.load(f)
@@ -185,14 +185,19 @@ def _main() -> None:
185
185
  'rss_s': f'{rss:_}',
186
186
  })
187
187
 
188
- if args.modules:
188
+ if args.modules or args.modules_ordered:
189
+ mods = results[0]['modules']
189
190
  out.update({
190
- 'modules': sorted(results[0]['modules']),
191
- })
192
- if args.modules_ordered:
193
- out.update({
194
- 'modules_ordered': results[0]['modules'],
191
+ 'num_modules': len(mods),
195
192
  })
193
+ if args.modules:
194
+ out.update({
195
+ 'modules': sorted(mods),
196
+ })
197
+ if args.modules_ordered:
198
+ out.update({
199
+ 'modules_ordered': mods,
200
+ })
196
201
 
197
202
  print(json.dumps(out, indent=2))
198
203