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
omdev/tools/docker.py CHANGED
@@ -13,12 +13,14 @@ import re
13
13
  import shutil
14
14
  import subprocess
15
15
  import sys
16
+ import threading
16
17
  import typing as ta
17
18
 
18
19
  from omlish import check
19
20
  from omlish import lang
20
21
  from omlish import marshal as msh
21
22
  from omlish.argparse import all as ap
23
+ from omlish.concurrent import all as conc
22
24
  from omlish.docker import all as dck
23
25
  from omlish.docker.ns1 import build_docker_ns1_run_cmd
24
26
  from omlish.formats import json
@@ -204,6 +206,7 @@ class Cli(ap.Cli):
204
206
 
205
207
  @ap.cmd(
206
208
  ap.arg('-f', '--file'),
209
+ ap.arg('-p', '--parallelism', type=int, default=4),
207
210
  )
208
211
  def compose_image_updates(self) -> None:
209
212
  if self.args.file:
@@ -215,23 +218,32 @@ class Cli(ap.Cli):
215
218
  yml_src = f.read()
216
219
 
217
220
  cfg_dct = yaml.safe_load(yml_src)
218
- for svc_name, svc_dct in cfg_dct.get('services', {}).items():
219
- if not (img := svc_dct.get('image')):
220
- continue
221
221
 
222
+ print_lock = threading.Lock()
223
+
224
+ def do_svc(svc_name: str, img: str) -> None: # noqa
222
225
  repo, _, base = img.partition(':')
223
226
  try:
224
227
  if (info := dck.get_hub_repo_info(repo)) is None:
225
- continue
228
+ return
226
229
 
227
230
  lt = dck.select_latest_tag(info.tags, base=base)
228
231
  if f'{repo}:{lt}' == img:
229
- continue
232
+ return
230
233
 
231
- print(f'{svc_name}: {lt}')
234
+ with print_lock:
235
+ print(f'{svc_name}: {lt}')
232
236
 
233
237
  except Exception as e: # noqa
234
- print(f'Error checking docker update for {svc_name}: {e!r}', file=sys.stderr)
238
+ with print_lock:
239
+ print(f'Error checking docker update for {svc_name}: {e!r}', file=sys.stderr)
240
+
241
+ with conc.new_executor(self.args.parallelism) as exe:
242
+ conc.wait_futures([
243
+ exe.submit(do_svc, svc_name, img)
244
+ for svc_name, svc_dct in cfg_dct.get('services', {}).items()
245
+ if (img := svc_dct.get('image'))
246
+ ])
235
247
 
236
248
  #
237
249
 
omdev/tools/git/cli.py CHANGED
@@ -21,7 +21,7 @@ TODO:
21
21
  fatal: Need to specify how to reconcile divergent branches.
22
22
  """
23
23
  import dataclasses as dc
24
- import logging
24
+ import glob
25
25
  import os
26
26
  import shutil
27
27
  import tempfile
@@ -58,7 +58,7 @@ else:
58
58
  msh = lang.proxy_import('omlish.marshal')
59
59
 
60
60
 
61
- log = logging.getLogger(__name__)
61
+ log = logs.get_module_logger(globals())
62
62
 
63
63
 
64
64
  ##
@@ -90,7 +90,12 @@ def get_first_commit_of_day(rev: str) -> str | None:
90
90
  class Cli(ap.Cli):
91
91
  @dc.dataclass(frozen=True, kw_only=True)
92
92
  class Config:
93
- default_message_generator: str | None = None
93
+ @dc.dataclass(frozen=True, kw_only=True)
94
+ class MessageGenerator:
95
+ name: str
96
+ config: ta.Mapping[str, ta.Any] | None = None
97
+
98
+ message_generator: str | MessageGenerator | None = None
94
99
 
95
100
  _config_file_path_arg: ta.Optional[str] = ap.arg_('-c', '--config-file-path', nargs='?')
96
101
 
@@ -280,6 +285,35 @@ class Cli(ap.Cli):
280
285
 
281
286
  # Lazy helpers
282
287
 
288
+ def _make_git_message_generator(
289
+ self,
290
+ *,
291
+ name: str | None = None,
292
+ cwd: str | None = None,
293
+ ) -> GitMessageGenerator:
294
+ cls: type[GitMessageGenerator] = TimestampGitMessageGenerator
295
+ kw: dict[str, ta.Any] = {}
296
+
297
+ if name is None:
298
+ if cwd is None:
299
+ cwd = os.getcwd()
300
+
301
+ cfg = self.load_config(cwd).message_generator
302
+ if cfg is None:
303
+ pass
304
+ elif isinstance(cfg, str):
305
+ name = cfg
306
+ elif isinstance(cfg, Cli.Config.MessageGenerator):
307
+ name = cfg.name
308
+ kw.update(cfg.config or {})
309
+ else:
310
+ raise TypeError(cfg)
311
+
312
+ if name is not None:
313
+ cls = load_message_generator_manifests_map()[name].load_cls()
314
+
315
+ return cls(**kw)
316
+
283
317
  @ap.cmd(
284
318
  ap.arg('-g', '--message-generator', nargs='?'),
285
319
  ap.arg('dir', nargs='*'),
@@ -292,12 +326,10 @@ class Cli(ap.Cli):
292
326
  if not (st.has_staged or st.has_dirty):
293
327
  return
294
328
 
295
- mg_cls: type[GitMessageGenerator] = TimestampGitMessageGenerator
296
- if (mg_name := self.args.message_generator) is None:
297
- mg_name = self.load_config(cwd).default_message_generator
298
- if mg_name is not None:
299
- mg_cls = load_message_generator_manifests_map()[mg_name].load_cls()
300
- mg = mg_cls()
329
+ mg = self._make_git_message_generator(
330
+ name=self.args.message_generator,
331
+ cwd=cwd,
332
+ )
301
333
 
302
334
  mgr = mg.generate_commit_message(GitMessageGenerator.GenerateCommitMessageArgs(
303
335
  cwd=cwd,
@@ -316,6 +348,7 @@ class Cli(ap.Cli):
316
348
  ap.arg('-g', '--message-generator', nargs='?'),
317
349
  ap.arg('--dry-run', action='store_true'),
318
350
  ap.arg('-y', '--no-confirmation', action='store_true'),
351
+ ap.arg('-r', '--repository'),
319
352
  ap.arg('dir', nargs='*'),
320
353
  aliases=['acp'],
321
354
  )
@@ -337,12 +370,10 @@ class Cli(ap.Cli):
337
370
  msg = self.args.message
338
371
 
339
372
  else:
340
- mg_cls: type[GitMessageGenerator] = TimestampGitMessageGenerator
341
- if (mg_name := self.args.message_generator) is None:
342
- mg_name = self.load_config(cwd).default_message_generator
343
- if mg_name is not None:
344
- mg_cls = load_message_generator_manifests_map()[mg_name].load_cls()
345
- mg = mg_cls()
373
+ mg = self._make_git_message_generator(
374
+ name=self.args.message_generator,
375
+ cwd=cwd,
376
+ )
346
377
 
347
378
  mgr = mg.generate_commit_message(GitMessageGenerator.GenerateCommitMessageArgs(
348
379
  cwd=cwd,
@@ -355,7 +386,7 @@ class Cli(ap.Cli):
355
386
 
356
387
  check_call('git', 'commit', '-m', msg)
357
388
 
358
- check_call('git', 'push')
389
+ check_call('git', 'push', *([self.args.repository] if self.args.repository is not None else []))
359
390
 
360
391
  if not self.args.dir:
361
392
  run(None)
@@ -452,6 +483,7 @@ class Cli(ap.Cli):
452
483
  BUILTIN_COMMIT_MESSAGES: ta.Mapping[str, str] = {
453
484
  'tableflip': '(╯°□°)╯︵ ┻━┻',
454
485
  'tableunflip': '┬─┬ノ(º _ ºノ)',
486
+ 'shrug': r'¯\_(ツ)_/¯',
455
487
  }
456
488
 
457
489
  @ap.cmd(
@@ -501,10 +533,18 @@ class Cli(ap.Cli):
501
533
  repo_dir = os.path.join(tmp_dir, repo_dir_name)
502
534
  check.state(os.path.isdir(repo_dir))
503
535
 
536
+ #
537
+
504
538
  git_dir = os.path.join(repo_dir, '.git')
505
539
  check.state(os.path.isdir(git_dir))
506
540
  shutil.rmtree(git_dir)
507
541
 
542
+ for f in glob.glob(os.path.join(repo_dir, '**/.gitattributes'), recursive=True):
543
+ if os.path.isfile(f):
544
+ os.unlink(f)
545
+
546
+ #
547
+
508
548
  shutil.move(repo_dir, cwd)
509
549
 
510
550
  out_dir = repo_dir_name
@@ -16,7 +16,7 @@ from . import consts
16
16
  ##
17
17
 
18
18
 
19
- class GitMessageGenerator(abc.ABC):
19
+ class GitMessageGenerator(lang.Abstract):
20
20
  @dc.dataclass(frozen=True, kw_only=True)
21
21
  class GenerateCommitMessageArgs:
22
22
  cwd: str | None = None
@@ -44,7 +44,7 @@ class GitMessageGeneratorManifest(NameAliasesManifest, ModAttrManifest):
44
44
  return check.issubclass(self.resolve(), GitMessageGenerator)
45
45
 
46
46
 
47
- class StaticGitMessageGeneratorManifest(StaticModAttrManifest, GitMessageGeneratorManifest, abc.ABC):
47
+ class StaticGitMessageGeneratorManifest(StaticModAttrManifest, GitMessageGeneratorManifest, lang.Abstract):
48
48
  pass
49
49
 
50
50
 
omdev/tools/json/cli.py CHANGED
@@ -195,7 +195,7 @@ def _main() -> None:
195
195
  else:
196
196
  in_file = es.enter_context(open(args.file, 'rb'))
197
197
 
198
- def yield_input() -> ta.Generator[bytes]:
198
+ def yield_input() -> ta.Iterator[bytes]:
199
199
  fd = check.isinstance(in_file.fileno(), int)
200
200
 
201
201
  while True:
@@ -240,7 +240,7 @@ def _main() -> None:
240
240
  def flush_output(
241
241
  fn: ta.Callable[[T], ta.Iterable[U]],
242
242
  i: T,
243
- ) -> ta.Generator[U]:
243
+ ) -> ta.Iterator[U]:
244
244
  n = 0
245
245
  for o in fn(i):
246
246
  yield o
@@ -259,7 +259,7 @@ def _main() -> None:
259
259
  def append_newlines(
260
260
  fn: ta.Callable[[T], ta.Iterable[str]],
261
261
  i: T,
262
- ) -> ta.Generator[str]:
262
+ ) -> ta.Iterator[str]:
263
263
  yield from fn(i)
264
264
  yield '\n'
265
265
 
@@ -267,15 +267,15 @@ def _main() -> None:
267
267
  pipeline = fp.bind(append_newlines, pipeline) # Any -> [str]
268
268
  pipeline = fp.bind(lang.flatmap, pipeline) # [Any] -> [str]
269
269
  pipeline = fp.pipe(fp.bind(lang.flatmap, processor.process), pipeline) # [Any] -> [str]
270
- pipeline = fp.pipe(fp.bind(lang.flatmap, builder.build), pipeline) # [JsonStreamParserEvent] -> [str] # noqa
270
+ pipeline = fp.pipe(fp.bind(lang.flatmap, builder.build), pipeline) # [Event] -> [str] # noqa
271
271
  pipeline = fp.pipe(parser.parse, pipeline) # bytes -> [str]
272
272
 
273
273
  else:
274
274
  renderer = StreamRenderer(cfg.rendering)
275
275
  trailing_newline = True
276
276
 
277
- pipeline = renderer.render # JsonStreamParserEvent -> [str]
278
- pipeline = fp.bind(lang.flatmap, pipeline) # [JsonStreamParserEvent] -> [str]
277
+ pipeline = renderer.render # Event -> [str]
278
+ pipeline = fp.bind(lang.flatmap, pipeline) # [Event] -> [str]
279
279
  pipeline = fp.pipe(parser.parse, pipeline) # bytes -> [str]
280
280
 
281
281
  pipeline = fp.bind(flush_output, pipeline) # bytes -> [str]
@@ -2,6 +2,8 @@
2
2
  TODO:
3
3
  - options lol - csv header, newline, etc
4
4
  - edn
5
+ - jsonl
6
+ - jsonc (just comments)
5
7
  """
6
8
  import dataclasses as dc
7
9
  import enum
@@ -6,8 +6,8 @@ from omlish import check
6
6
  from omlish import lang
7
7
  from omlish.formats.json.stream.building import JsonValueBuilder
8
8
  from omlish.formats.json.stream.lexing import JsonStreamLexer
9
+ from omlish.formats.json.stream.parsing import Event
9
10
  from omlish.formats.json.stream.parsing import JsonStreamParser
10
- from omlish.formats.json.stream.parsing import JsonStreamParserEvent
11
11
  from omlish.io.buffers import DelimitingBuffer
12
12
 
13
13
  from .formats import Format
@@ -22,7 +22,7 @@ class EagerParser:
22
22
 
23
23
  self._fmt = fmt
24
24
 
25
- def parse(self, f: ta.TextIO) -> ta.Generator[ta.Any]:
25
+ def parse(self, f: ta.TextIO) -> ta.Iterator[ta.Any]:
26
26
  return self._fmt.load(f)
27
27
 
28
28
 
@@ -42,7 +42,7 @@ class DelimitingParser:
42
42
 
43
43
  self._db = DelimitingBuffer(delimiters)
44
44
 
45
- def parse(self, b: bytes) -> ta.Generator[ta.Any]:
45
+ def parse(self, b: bytes) -> ta.Iterator[ta.Any]:
46
46
  for chunk in self._db.feed(b):
47
47
  s = check.isinstance(chunk, bytes).decode('utf-8')
48
48
  v = self._fmt.load(io.StringIO(s))
@@ -58,7 +58,7 @@ class StreamBuilder(lang.ExitStacked):
58
58
  def _enter_contexts(self) -> None:
59
59
  self._builder = self._enter_context(JsonValueBuilder())
60
60
 
61
- def build(self, e: JsonStreamParserEvent) -> ta.Generator[ta.Any]:
61
+ def build(self, e: Event) -> ta.Iterator[ta.Any]:
62
62
  yield from check.not_none(self._builder)(e)
63
63
 
64
64
 
@@ -72,7 +72,7 @@ class StreamParser(lang.ExitStacked):
72
72
  self._lex = self._enter_context(JsonStreamLexer())
73
73
  self._parse = self._enter_context(JsonStreamParser())
74
74
 
75
- def parse(self, b: bytes) -> ta.Generator[JsonStreamParserEvent]:
75
+ def parse(self, b: bytes) -> ta.Iterator[Event]:
76
76
  for s in self._decoder.decode(b, not b):
77
77
  for c in s:
78
78
  for t in self._lex(c):
@@ -53,11 +53,14 @@ class Processor:
53
53
 
54
54
  def _marshal(self, v: ta.Any) -> ta.Any:
55
55
  return msh.MarshalContext(
56
- msh.global_registry(),
57
- factory=self._marshaler_factory(),
56
+ configs=msh.global_config_registry(),
57
+ marshal_factory_context=msh.MarshalFactoryContext(
58
+ configs=msh.global_config_registry(),
59
+ marshaler_factory=self._marshaler_factory(),
60
+ ),
58
61
  ).marshal(v)
59
62
 
60
- def process(self, v: ta.Any) -> ta.Iterable[ta.Any]:
63
+ def process(self, v: ta.Any) -> ta.Iterator[ta.Any]:
61
64
  if self._jmespath_expr is not None:
62
65
  v = self._jmespath_expr.search(v)
63
66
 
@@ -4,7 +4,7 @@ import typing as ta
4
4
 
5
5
  from omlish import lang
6
6
  from omlish.formats.json.rendering import JsonRenderer
7
- from omlish.formats.json.stream.parsing import JsonStreamParserEvent
7
+ from omlish.formats.json.stream.parsing import Event
8
8
  from omlish.formats.json.stream.rendering import StreamJsonRenderer
9
9
  from omlish.formats.json5.rendering import Json5Renderer
10
10
  from omlish.term import codes as tc
@@ -101,5 +101,5 @@ class StreamRenderer(Renderer):
101
101
  **self._kw,
102
102
  )
103
103
 
104
- def render(self, e: JsonStreamParserEvent) -> ta.Generator[str]:
104
+ def render(self, e: Event) -> ta.Iterator[str]:
105
105
  return self._renderer.render((e,))
@@ -3,7 +3,7 @@ TODO:
3
3
  - read from stdin
4
4
  - evaluate jmespath on server using extended engine
5
5
  - integrate with json tool
6
- - use omlish server and templates
6
+ - use omlish server
7
7
  - vendor deps, serve local
8
8
  - update to https://github.com/josdejong/svelte-jsoneditor
9
9
  """
@@ -14,62 +14,33 @@ import os
14
14
  import socketserver
15
15
  import sys
16
16
  import threading
17
+ import typing as ta
17
18
  import webbrowser
18
19
 
20
+ from omlish import check
19
21
  from omlish import lang
22
+ from omlish.sockets.ports import get_available_port
23
+ from omlish.text import minja
20
24
 
21
25
 
22
26
  ##
23
27
 
24
28
 
25
- HTML_TEMPLATE = """
26
- <!DOCTYPE html>
27
- <html lang="en">
28
-
29
- <head>
30
- <meta charset="UTF-8">
31
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
32
- <title>JSON Viewer</title>
33
- <link
34
- href="https://cdn.jsdelivr.net/npm/jsoneditor/dist/jsoneditor.min.css"
35
- rel="stylesheet"
36
- type="text/css"
37
- >
38
- <style>
39
- {css_src}
40
- </style>
41
- </head>
42
-
43
- <body>
44
- <div class="input-area">
45
- <label for="jmespath-input">JMESPath:</label>
46
- <input
47
- type="text"
48
- id="jmespath-input"
49
- list="jmespath-history"
50
- placeholder="Enter JMESPath expression..."
51
- autocomplete="off"
52
- >
53
- <datalist id="jmespath-history"></datalist>
54
- <span id="error-message"></span>
55
- </div>
56
- <div id="jsoneditor"></div>
57
-
58
- <script src="https://cdn.jsdelivr.net/npm/jsoneditor@10.2.0/dist/jsoneditor.min.js"></script>
59
- <script src="https://cdn.jsdelivr.net/npm/jmespath@0.16.0/jmespath.min.js"></script>
60
- <script>
61
- const originalJsonData = {json_data};
62
- </script>
63
- <script>
64
- {js_src}
65
- </script>
66
- </body>
67
-
68
- </html>
69
- """
29
+ @lang.cached_function
30
+ def html_template() -> minja.MinjaTemplate:
31
+ src = lang.get_relative_resources('resources', globals=globals())['jsonview.html.j2'].read_text()
32
+ return minja.compile_minja_template(src, ['ctx'])
33
+
70
34
 
35
+ def view_json(
36
+ filepath: str,
37
+ port: int | None,
38
+ *,
39
+ mode: ta.Literal['jsonl', 'json5', 'json', None] = None,
40
+ ) -> None:
41
+ if filepath == '-':
42
+ filepath = '/dev/stdin'
71
43
 
72
- def view_json(filepath: str, port: int) -> None:
73
44
  if not os.path.exists(filepath):
74
45
  print(f"Error: File not found at '{filepath}'", file=sys.stderr)
75
46
  return
@@ -81,13 +52,21 @@ def view_json(filepath: str, port: int) -> None:
81
52
  print(f'Error: Invalid JSON file. {e}', file=sys.stderr)
82
53
  return
83
54
 
84
- if filepath.endswith('.jsonl'):
55
+ if mode is None:
56
+ if filepath.endswith('.jsonl'):
57
+ mode = 'json'
58
+ elif filepath.endswith('.json5'):
59
+ mode = 'json5'
60
+
61
+ if mode == 'jsonl':
85
62
  json_content = [json.loads(sl) for l in raw_content.splitlines() if (sl := l.strip())]
86
- elif filepath.endswith('.json5'):
63
+ elif mode == 'json5':
87
64
  from omlish.formats import json5
88
65
  json_content = json5.loads(raw_content)
89
- else:
66
+ elif mode in ('json', None):
90
67
  json_content = json.loads(raw_content)
68
+ else:
69
+ raise ValueError(mode)
91
70
 
92
71
  # Use compact dumps for embedding in JS, it's more efficient
93
72
  json_string = json.dumps(json_content)
@@ -102,15 +81,18 @@ def view_json(filepath: str, port: int) -> None:
102
81
  self.send_response(200)
103
82
  self.send_header('Content-type', 'text/html')
104
83
  self.end_headers()
105
- html_content = HTML_TEMPLATE.format(
106
- css_src=css_src,
107
- js_src=js_src,
84
+ html_content = html_template()(ctx=dict(
85
+ css_src=css_src.strip(),
86
+ js_src=js_src.strip(),
108
87
  json_data=json_string,
109
- )
88
+ ))
110
89
  self.wfile.write(html_content.encode('utf-8'))
111
90
  else:
112
91
  super().do_GET()
113
92
 
93
+ if port is None:
94
+ port = get_available_port()
95
+
114
96
  handler_cls = JsonViewerHttpRequestHandler
115
97
  with socketserver.TCPServer(('127.0.0.1', port), handler_cls) as httpd:
116
98
  url = f'http://127.0.0.1:{port}'
@@ -132,19 +114,21 @@ def _main() -> None:
132
114
  description='Launch a web-based JSON viewer with JMESPath transformations.',
133
115
  formatter_class=argparse.RawTextHelpFormatter,
134
116
  )
135
- parser.add_argument(
136
- 'filepath',
137
- help='The path to the JSON file you want to view.',
138
- )
139
- parser.add_argument(
140
- '-p', '--port',
141
- type=int,
142
- default=(default_port := 8999),
143
- help=f'The port to run the web server on. Defaults to {default_port}.',
144
- )
117
+
118
+ parser.add_argument('filepath')
119
+ parser.add_argument('-p', '--port', type=int)
120
+ parser.add_argument('-l', '--lines', action='store_true')
121
+ parser.add_argument('-5', '--five', action='store_true')
122
+
145
123
  args = parser.parse_args()
146
124
 
147
- view_json(args.filepath, args.port)
125
+ check.state(not (args.lines and args.five))
126
+
127
+ view_json(
128
+ args.filepath,
129
+ args.port,
130
+ mode='jsonl' if args.lines else 'json5' if args.five else None,
131
+ )
148
132
 
149
133
 
150
134
  if __name__ == '__main__':
@@ -0,0 +1,43 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>JSON Viewer</title>
8
+ <link
9
+ href="https://cdn.jsdelivr.net/npm/jsoneditor/dist/jsoneditor.min.css"
10
+ rel="stylesheet"
11
+ type="text/css"
12
+ >
13
+ <style>
14
+ {{ ctx['css_src'] }}
15
+ </style>
16
+ </head>
17
+
18
+ <body>
19
+ <div class="input-area">
20
+ <label for="jmespath-input">JMESPath:</label>
21
+ <input
22
+ type="text"
23
+ id="jmespath-input"
24
+ list="jmespath-history"
25
+ placeholder="Enter JMESPath expression..."
26
+ autocomplete="off"
27
+ >
28
+ <datalist id="jmespath-history"></datalist>
29
+ <span id="error-message"></span>
30
+ </div>
31
+ <div id="jsoneditor"></div>
32
+
33
+ <script src="https://cdn.jsdelivr.net/npm/jsoneditor@10.2.0/dist/jsoneditor.min.js"></script>
34
+ <script src="https://cdn.jsdelivr.net/npm/jmespath@0.16.0/jmespath.min.js"></script>
35
+ <script>
36
+ const originalJsonData = {{ ctx['json_data'] }};
37
+ </script>
38
+ <script>
39
+ {{ ctx['js_src'] }}
40
+ </script>
41
+ </body>
42
+
43
+ </html>