runem 0.4.0__py3-none-any.whl → 0.6.0__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.
runem/runem.py CHANGED
@@ -19,6 +19,7 @@ We do:
19
19
  - time tests and tell you what used the most time, and how much time run-tests saved
20
20
  you
21
21
  """
22
+ import contextlib
22
23
  import multiprocessing
23
24
  import os
24
25
  import pathlib
@@ -30,14 +31,13 @@ from datetime import timedelta
30
31
  from itertools import repeat
31
32
  from multiprocessing.managers import DictProxy, ValueProxy
32
33
  from timeit import default_timer as timer
33
- from types import TracebackType
34
34
 
35
- from rich.console import Console, ConsoleOptions, ConsoleRenderable, RenderResult
36
35
  from rich.spinner import Spinner
36
+ from rich.status import Status
37
37
  from rich.text import Text
38
38
 
39
39
  from runem.blocking_print import RICH_CONSOLE
40
- from runem.command_line import parse_args
40
+ from runem.command_line import error_on_log_logic, parse_args
41
41
  from runem.config import load_project_config, load_user_configs
42
42
  from runem.config_metadata import ConfigMetadata
43
43
  from runem.config_parse import load_config_metadata
@@ -46,6 +46,7 @@ from runem.job_execute import job_execute
46
46
  from runem.job_filter import filter_jobs
47
47
  from runem.log import error, log, warn
48
48
  from runem.report import report_on_run
49
+ from runem.run_command import RunemJobError
49
50
  from runem.types.common import OrderedPhases, PhaseName
50
51
  from runem.types.filters import FilePathListLookup
51
52
  from runem.types.hooks import HookName
@@ -56,7 +57,7 @@ from runem.types.types_jobs import (
56
57
  JobRunMetadatasByPhase,
57
58
  JobTiming,
58
59
  )
59
- from runem.utils import printable_set
60
+ from runem.utils import printable_set_coloured
60
61
 
61
62
 
62
63
  def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
@@ -67,12 +68,19 @@ def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
67
68
 
68
69
  Return a ConfigMetadata object with all the required information.
69
70
  """
71
+
72
+ # Because we want to be able to show logging whilst parsing .runem.yml config, we
73
+ # need to check the state of the logging-verbosity switches here, manually, as well.
74
+ verbose = "--verbose" in argv
75
+ silent = ("--silent" in argv) or ("-s" in argv)
76
+ error_on_log_logic(verbose, silent)
77
+
70
78
  config: Config
71
79
  cfg_filepath: pathlib.Path
72
80
  config, cfg_filepath = load_project_config()
73
81
  user_configs: typing.List[typing.Tuple[Config, pathlib.Path]] = load_user_configs()
74
82
  config_metadata: ConfigMetadata = load_config_metadata(
75
- config, cfg_filepath, user_configs, verbose=("--verbose" in argv)
83
+ config, cfg_filepath, user_configs, silent, verbose=("--verbose" in argv)
76
84
  )
77
85
 
78
86
  # Now we parse the cli arguments extending them with information from the
@@ -85,38 +93,8 @@ def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
85
93
  return config_metadata
86
94
 
87
95
 
88
- class DummySpinner(ConsoleRenderable): # pragma: no cover
89
- """A dummy spinner for when spinners are disabled."""
90
-
91
- def __init__(self) -> None:
92
- self.text = ""
93
-
94
- def __rich__(self) -> Text:
95
- """Return a rich Text object for rendering."""
96
- return Text(self.text)
97
-
98
- def __rich_console__(
99
- self, console: Console, options: ConsoleOptions
100
- ) -> RenderResult:
101
- """Yield an empty string or placeholder text."""
102
- yield Text(self.text)
103
-
104
- def __enter__(self) -> None:
105
- """Support for context manager."""
106
- pass
107
-
108
- def __exit__(
109
- self,
110
- exc_type: typing.Optional[typing.Type[BaseException]],
111
- exc_value: typing.Optional[BaseException],
112
- traceback: typing.Optional[TracebackType],
113
- ) -> None:
114
- """Support for context manager."""
115
- pass
116
-
117
-
118
96
  def _update_progress(
119
- label: str,
97
+ phase: str,
120
98
  running_jobs: typing.Dict[str, str],
121
99
  completed_jobs: typing.Dict[str, str],
122
100
  all_jobs: Jobs,
@@ -133,30 +111,36 @@ def _update_progress(
133
111
  is_running (ValueProxy[bool]): Flag indicating if jobs are still running.
134
112
  num_workers (int): Indicates the number of workers performing the jobs.
135
113
  """
136
- # Using the `rich` module to show a loading spinner on console
137
- spinner: typing.Union[Spinner, DummySpinner]
138
- if show_spinner:
139
- spinner = Spinner("dots", text="Starting tasks...")
140
- else:
141
- spinner = DummySpinner()
142
114
 
143
115
  last_running_jobs_set: typing.Set[str] = set()
144
116
 
145
- with RICH_CONSOLE.status(spinner):
117
+ # Using the `rich` module to show a loading spinner on console
118
+ spinner_ctx: typing.Union[Status, typing.ContextManager[None]] = (
119
+ RICH_CONSOLE.status(Spinner("dots", text="Starting tasks..."))
120
+ if show_spinner
121
+ else contextlib.nullcontext()
122
+ )
123
+
124
+ with spinner_ctx:
146
125
  while is_running.value:
147
126
  running_jobs_set: typing.Set[str] = set(running_jobs.values())
148
127
 
149
128
  # Progress report
150
129
  progress: str = f"{len(completed_jobs)}/{len(all_jobs)}"
151
- running_jobs_list = printable_set(
152
- running_jobs_set
130
+ running_jobs_list = printable_set_coloured(
131
+ running_jobs_set,
132
+ "blue",
153
133
  ) # Reflect current running jobs accurately
154
- report: str = f"{label}: {progress}({num_workers}): {running_jobs_list}"
134
+ report: str = (
135
+ f"[green]{phase}[/green]: {progress}({num_workers}): {running_jobs_list}"
136
+ )
155
137
  if show_spinner:
156
- spinner.text = report
138
+ assert isinstance(spinner_ctx, Status)
139
+ spinner_ctx.update(Text.from_markup(report))
157
140
  else:
158
141
  if last_running_jobs_set != running_jobs_set:
159
142
  RICH_CONSOLE.log(report)
143
+ last_running_jobs_set = running_jobs_set
160
144
 
161
145
  # Sleep for reduced CPU usage
162
146
  time.sleep(0.1)
@@ -169,7 +153,7 @@ def _process_jobs(
169
153
  phase: PhaseName,
170
154
  jobs: Jobs,
171
155
  show_spinner: bool,
172
- ) -> typing.Optional[BaseException]:
156
+ ) -> typing.Optional[RunemJobError]:
173
157
  """Execute each given job asynchronously.
174
158
 
175
159
  This is where the major real-world time savings happen, and it could be
@@ -189,12 +173,12 @@ def _process_jobs(
189
173
  num_concurrent_procs: int = min(max_num_concurrent_procs, len(jobs))
190
174
  log(
191
175
  (
192
- f"Running '{phase}' with {num_concurrent_procs} workers (of "
176
+ f"Running '[green]{phase}[/green]' with {num_concurrent_procs} workers (of "
193
177
  f"{max_num_concurrent_procs} max) processing {len(jobs)} jobs"
194
178
  )
195
179
  )
196
180
 
197
- subprocess_error: typing.Optional[BaseException] = None
181
+ subprocess_error: typing.Optional[RunemJobError] = None
198
182
 
199
183
  with multiprocessing.Manager() as manager:
200
184
  running_jobs: DictProxy[typing.Any, typing.Any] = manager.dict()
@@ -228,7 +212,7 @@ def _process_jobs(
228
212
  repeat(file_lists),
229
213
  ),
230
214
  )
231
- except BaseException as err: # pylint: disable=broad-exception-caught
215
+ except RunemJobError as err: # pylint: disable=broad-exception-caught
232
216
  subprocess_error = err
233
217
  finally:
234
218
  # Signal the terminal_writer process to exit
@@ -244,7 +228,7 @@ def _process_jobs_by_phase(
244
228
  filtered_jobs_by_phase: PhaseGroupedJobs,
245
229
  in_out_job_run_metadatas: JobRunMetadatasByPhase,
246
230
  show_spinner: bool,
247
- ) -> typing.Optional[BaseException]:
231
+ ) -> typing.Optional[RunemJobError]:
248
232
  """Execute each job asynchronously, grouped by phase.
249
233
 
250
234
  Whilst it is conceptually useful to group jobs by 'phase', Phases are
@@ -268,7 +252,7 @@ def _process_jobs_by_phase(
268
252
  if config_metadata.args.verbose:
269
253
  log(f"Running Phase {phase}")
270
254
 
271
- failure_exception: typing.Optional[BaseException] = _process_jobs(
255
+ failure_exception: typing.Optional[RunemJobError] = _process_jobs(
272
256
  config_metadata,
273
257
  file_lists,
274
258
  in_out_job_run_metadatas,
@@ -286,7 +270,7 @@ def _process_jobs_by_phase(
286
270
 
287
271
 
288
272
  MainReturnType = typing.Tuple[
289
- ConfigMetadata, JobRunMetadatasByPhase, typing.Optional[BaseException]
273
+ ConfigMetadata, JobRunMetadatasByPhase, typing.Optional[RunemJobError]
290
274
  ]
291
275
 
292
276
 
@@ -326,7 +310,7 @@ def _main(
326
310
 
327
311
  start = timer()
328
312
 
329
- failure_exception: typing.Optional[BaseException] = _process_jobs_by_phase(
313
+ failure_exception: typing.Optional[RunemJobError] = _process_jobs_by_phase(
330
314
  config_metadata,
331
315
  file_lists,
332
316
  filtered_jobs_by_phase,
@@ -355,7 +339,7 @@ def timed_main(argv: typing.List[str]) -> None:
355
339
  start = timer()
356
340
  config_metadata: ConfigMetadata
357
341
  job_run_metadatas: JobRunMetadatasByPhase
358
- failure_exception: typing.Optional[BaseException]
342
+ failure_exception: typing.Optional[RunemJobError]
359
343
  config_metadata, job_run_metadatas, failure_exception = _main(argv)
360
344
  phase_run_oder: OrderedPhases = config_metadata.phases
361
345
  end = timer()
@@ -365,14 +349,15 @@ def timed_main(argv: typing.List[str]) -> None:
365
349
  system_time_spent, wall_clock_time_saved = report_on_run(
366
350
  phase_run_oder, job_run_metadatas, time_taken
367
351
  )
368
- message: str = "DONE: runem took"
352
+ message: str = "[green bold]DONE[/green bold]: runem took"
369
353
  if failure_exception:
370
- message = "FAILED: your jobs failed after"
354
+ message = "[red bold]FAILED[/red bold]: your jobs failed after"
371
355
  log(
372
356
  (
373
357
  f"{message}: {time_taken.total_seconds()}s, "
374
- f"saving you {wall_clock_time_saved.total_seconds()}s, "
375
- f"without runem you would have waited {system_time_spent.total_seconds()}s"
358
+ f"saving you [green]{wall_clock_time_saved.total_seconds()}s[/green], "
359
+ "without runem you would have waited "
360
+ f"[red]{system_time_spent.total_seconds()}s[/red]"
376
361
  )
377
362
  )
378
363
 
@@ -385,6 +370,7 @@ def timed_main(argv: typing.List[str]) -> None:
385
370
  if failure_exception is not None:
386
371
  # we got a failure somewhere, now that we've reported the timings we
387
372
  # re-raise.
373
+ error(failure_exception.stdout)
388
374
  raise failure_exception
389
375
 
390
376
 
runem/types/__init__.py CHANGED
@@ -1,11 +1,12 @@
1
1
  from runem.types.common import FilePathList, JobName
2
2
  from runem.types.options import Options
3
- from runem.types.types_jobs import HookKwargs, JobKwargs, JobReturnData
3
+ from runem.types.types_jobs import HookKwargs, JobKwargs, JobReturn, JobReturnData
4
4
 
5
5
  __all__ = [
6
6
  "FilePathList",
7
7
  "HookKwargs",
8
8
  "JobName",
9
+ "JobReturn",
9
10
  "JobReturnData",
10
11
  "Options",
11
12
  "JobKwargs",
runem/utils.py CHANGED
@@ -4,3 +4,15 @@ import typing
4
4
  def printable_set(some_set: typing.Set[typing.Any]) -> str:
5
5
  """Get a printable, deterministic string version of a set."""
6
6
  return ", ".join([f"'{set_item}'" for set_item in sorted(list(some_set))])
7
+
8
+
9
+ def printable_set_coloured(some_set: typing.Set[typing.Any], colour: str) -> str:
10
+ """`printable_set` but elements are surrounded with colour mark-up.
11
+
12
+ Parameters:
13
+ some_set: a set of anything
14
+ colour: a `rich` Console supported colour
15
+ """
16
+ return ", ".join(
17
+ [f"'[{colour}]{set_item}[/{colour}]'" for set_item in sorted(list(some_set))]
18
+ )
@@ -0,0 +1,161 @@
1
+ Metadata-Version: 2.2
2
+ Name: runem
3
+ Version: 0.6.0
4
+ Summary: Awesome runem created by lursight
5
+ Author: lursight
6
+ License: Specify your license here
7
+ Project-URL: Homepage, https://github.com/lursight/runem/
8
+ Keywords: example,runem
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Requires-Python: >=3.7
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: packaging>=22.0
18
+ Requires-Dist: PyYAML>=5.0.0
19
+ Requires-Dist: rich>10.0.0
20
+ Requires-Dist: typing_extensions>3.0.0
21
+ Provides-Extra: tests
22
+ Requires-Dist: black==24.10.0; extra == "tests"
23
+ Requires-Dist: coverage==7.5; extra == "tests"
24
+ Requires-Dist: docformatter==1.7.5; extra == "tests"
25
+ Requires-Dist: flake8-bugbear==24.2.6; extra == "tests"
26
+ Requires-Dist: flake8==7.0.0; extra == "tests"
27
+ Requires-Dist: gitchangelog==3.0.4; extra == "tests"
28
+ Requires-Dist: isort==5.13.2; extra == "tests"
29
+ Requires-Dist: mkdocs==1.5.3; extra == "tests"
30
+ Requires-Dist: mypy==1.9.0; extra == "tests"
31
+ Requires-Dist: pydocstyle==6.3.0; extra == "tests"
32
+ Requires-Dist: pylint==3.1.0; extra == "tests"
33
+ Requires-Dist: pylama==8.4.1; extra == "tests"
34
+ Requires-Dist: pytest-cov==6.0.0; extra == "tests"
35
+ Requires-Dist: pytest-profiling==1.7.0; extra == "tests"
36
+ Requires-Dist: pytest-xdist==3.6.1; extra == "tests"
37
+ Requires-Dist: pytest==8.3.3; extra == "tests"
38
+ Requires-Dist: setuptools; extra == "tests"
39
+ Requires-Dist: termplotlib==0.3.9; extra == "tests"
40
+ Requires-Dist: tox; extra == "tests"
41
+ Requires-Dist: types-PyYAML==6.0.12.20240311; extra == "tests"
42
+ Requires-Dist: requests-mock==1.11.0; extra == "tests"
43
+ Requires-Dist: types-setuptools; extra == "tests"
44
+
45
+ <!-- [![codecov](https://codecov.io/gh/lursight/runem/branch/main/graph/badge.svg?token=run-test_token_here)](https://codecov.io/gh/lursight/runem) -->
46
+ [![CI](https://github.com/lursight/runem/actions/workflows/main.yml/badge.svg)](https://github.com/lursight/runem/actions/workflows/main.yml)
47
+ [![DOCS](https://lursight.github.io/runem/docs/VIEW-DOCS-31c553.svg)](https://lursight.github.io/runem/)
48
+
49
+ # Run’em
50
+
51
+ **Your Blueprint of Commands. Your Engine of Parallel Execution.**
52
+ Run’em is your definitive blueprint of tasks and commands—instantly discoverable, effortlessly parallel, and elegantly extensible.
53
+
54
+ ## Core Strengths
55
+
56
+ **Blueprint** - discover tasks and onboard smoothly\
57
+ **Parallel** - get results quicker\
58
+ **Simple** - define task easily\
59
+ **Extensible** - add tasks quickly\
60
+ **Filters** - powerful task selection\
61
+ **Reports** - see metrics on tasks
62
+
63
+ ## Why Run’em?
64
+ - **Command Blueprint:** Instantly see and run all your tasks. No guesswork, no rummaging.
65
+ - **Effortless Parallelism:** Execute tasks side-by-side to obliterate downtime.
66
+ - **Simple YAML Declarations:** Define everything in one `.runem.yml`.
67
+ - **Extensible & Smart:** Adapt to monorepos, complex workflows, and evolving needs.
68
+ - **Discoverable by Design:** `runem --help` guides your team, new hires, or contributors to every defined command.
69
+
70
+ ## Contents
71
+ - [Run’em](#runem)
72
+ - [Core Strengths](#core-strengths)
73
+ - [Why Run’em?](#why-runem)
74
+ - [Contents](#contents)
75
+ - [Highlights](#highlights)
76
+ - [Quick Start](#quick-start)
77
+ - [Basic Use](#basic-use)
78
+ - [Advanced Use](#advanced-use)
79
+ - [Help & Discovery](#help--discovery)
80
+ - [Troubleshooting](#troubleshooting)
81
+ - [Contribute & Support](#contribute--support)
82
+ - [About Run’em](#about-runem)
83
+
84
+ # Highlights
85
+ ## Blueprint of Commands:
86
+ The blueprint (available via `--help`) gives you a manifest of all jobs and tasks in a
87
+ project. A single source of truth for all tasks.
88
+ ## Parallel Execution:
89
+ Maximise speed with automatic concurrency. Runem tries to run all tasks as quickly as
90
+ possible, looking at resources, with dependencies. It is not yet a full
91
+ dependency-execution graph, but by version 1.0.0 it will be.
92
+ ## Filtering:
93
+ Use powerful and flexible filtering. Select or excluded tasks by `tags`, `name` and
94
+ `phase`. Chose the task to be run based on your needs, right now.
95
+
96
+ You can also customise filtering by adding your own command `options`.
97
+
98
+ See `--tags`, `--not-tags`, `--jobs`, `--not-jobs`, `--phases` and `--not-phases`.
99
+ ## Powerful Insights:** Understand what ran, how fast, and what failed.
100
+ **Quiet by Default:** Focus on what matters, and reveal detail only when needed.
101
+
102
+ # Quick Start
103
+ **Install:**
104
+ ```bash
105
+ pip install runem
106
+ ```
107
+ **Define a task:**
108
+
109
+ ```yaml
110
+ `# .runem.yml
111
+ - job:
112
+ command: echo "hello world!"
113
+ ```
114
+
115
+ **Run:**
116
+
117
+ ```bash
118
+ runem
119
+ ```
120
+
121
+ Run multiple commands in parallel, see timing, and keep output minimal. Need detail?
122
+
123
+ ```bash
124
+ runem --verbose
125
+ ```
126
+
127
+ [Quick Start Docs](https://lursight.github.io/runem/docs/quick_start.html)
128
+
129
+ # Basic Use
130
+
131
+ Get comfortable with typical workflows:
132
+ [Basic Use Docs](https://lursight.github.io/runem/docs/basic_use.html)
133
+
134
+ # Advanced Use
135
+
136
+ Scale up with multi-phase configs, filtered execution, and custom reporting:
137
+ [Advanced Configuration](https://lursight.github.io/runem/docs/configuration.html)
138
+ [Custom Reporting](https://lursight.github.io/runem/docs/reports.html)
139
+
140
+ # Help & Discovery
141
+
142
+ `runem --help` is your radar—instantly mapping out every available task:
143
+ [Help & Job Discovery](https://lursight.github.io/runem/docs/help_and_job_discovery.html)
144
+
145
+ # Troubleshooting
146
+
147
+ Swift solutions to common issues:
148
+ [Troubleshooting & Known Issues](https://lursight.github.io/runem/docs/troubleshooting_known_issues.html)
149
+
150
+ ---
151
+
152
+ # Contribute & Support
153
+
154
+ Brought to you by [Lursight Ltd.](https://lursight.com) and an open community.
155
+ [CONTRIBUTING.md](CONTRIBUTING.md)
156
+ [❤️ Sponsor](https://github.com/sponsors/lursight/)
157
+
158
+ # About Run’em
159
+
160
+ Run’em exists to accelerate your team’s delivery and reduce complexity. Learn about our [Mission](https://lursight.github.io/runem/docs/mission.html).
161
+
@@ -0,0 +1,52 @@
1
+ runem/VERSION,sha256=l6XW5UCmEg0Jw53bZn4Ojiusf8wv_vgTuC4I_WA2W84,6
2
+ runem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ runem/__main__.py,sha256=dsOiVZegpfK9JOs5n7UmbX5iwwbj7iFkEbLoVeEgAn4,136
4
+ runem/base.py,sha256=EZfR7FIlwEdU9Vfe47Wk2DOO8GQqpKxxLNKp6YHueZ4,316
5
+ runem/blocking_print.py,sha256=2nCvc10zXl1DRJldkruKthKjfmZKErcmxQzt3pjmN-c,2289
6
+ runem/cli.py,sha256=wEt_Jnumhl8SiOdKdSJzLkJpWv6n3_Odhi_HeIixr1k,134
7
+ runem/command_line.py,sha256=qkZFCCq9hUl6RO398SJzoigv8di5jGw2sdNwgTVBdd8,14474
8
+ runem/config.py,sha256=UiEU0Jyg5qjrNStvasWYjMOABQHhpZjbPiX3-sH_CMg,5969
9
+ runem/config_metadata.py,sha256=krDomUcADsAeUQrxwNmOS58eeaNIlqmhWIKWv8mUH4A,3300
10
+ runem/config_parse.py,sha256=zXQ4rpj-igQufB5JtTsI1mOE_gBTdBcI2hI6HWU28gg,13830
11
+ runem/files.py,sha256=59boeFvUANYOS-PllIjeKIht6lNINZ43WxahDg90oAc,4392
12
+ runem/hook_manager.py,sha256=H0TL3HCqU2mgKm_-dgCD7TsK5T1bLT4g7x6kpytMPhU,4350
13
+ runem/informative_dict.py,sha256=U7p9z78UwOT4TAfng1iDXCEyeYz6C-XZlx9Z1pWNVrI,1548
14
+ runem/job.py,sha256=NOdRQnGePPyYdmIR_6JKVFzp9nbgNGetpE13bHEHaf4,3442
15
+ runem/job_execute.py,sha256=-76IJI0PDU_XdQiDxTKUfOHEno9pixxQb_zi58rFumo,4702
16
+ runem/job_filter.py,sha256=7vgG4YWJ9gyGBFjV7QbSojG5ofYoszAmxXx9HnMLkHo,5384
17
+ runem/job_runner_simple_command.py,sha256=iP5an6yixW8o4C0ZBtu6csb-oVK3Q62ZZgtHBmxlXaU,2428
18
+ runem/job_wrapper.py,sha256=q5GtopZ5vhSJ581rwU4-lF9KnbL3ZYgOC8fqaCnXD_g,983
19
+ runem/job_wrapper_python.py,sha256=rx7J_N-JXs8GgMq7Sla7B9s_ZAfofKUhEnzgMcq_bts,4303
20
+ runem/log.py,sha256=6r6HIJyvp19P6PZNo93qdIxE0SpTAsY4ELrETBl1dC4,1363
21
+ runem/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ runem/report.py,sha256=beye95AV9Lop_K7eOoLY40vKt0VGkIdg-ig-SmJ_5MY,9083
23
+ runem/run_command.py,sha256=R77jqAtrXPBkFtT7QXJfnQkivU4h01M8G0Q2sjWY6Gs,7691
24
+ runem/runem.py,sha256=6XZqf59_ZMyrBaK1FlaLzMXYNoDQsTUcbFqTbpZL1ik,13192
25
+ runem/runem_version.py,sha256=MbETwZO2Tb1Y3hX_OYZjKepEMKA1cjNvr-7Cqhz6e3s,271
26
+ runem/utils.py,sha256=MEYfox09rLvb6xmay_3rV1cWmdqMbhaAjOomYGNk15k,602
27
+ runem/cli/initialise_options.py,sha256=zx_EduWQk7yGBr3XUNrffHCSInPv05edFItHLnlo9dk,918
28
+ runem/types/__init__.py,sha256=0bWG7hE7VeqJ2oIu-xhrqQud8hcNp6WNbF3uMfT_n9g,314
29
+ runem/types/common.py,sha256=gPMSoJ3yRUYjHnoviRrpSg0gRwsGLFGWGpbTWkq4jX0,279
30
+ runem/types/errors.py,sha256=rbM5BA6UhY1X7Q0OZLUNsG7JXAjgNFTG5KQuqPNuZm8,103
31
+ runem/types/filters.py,sha256=8R5fyMssN0ISGBilJhEtbdHFl6OP7uI51WKkB5SH6EA,255
32
+ runem/types/hooks.py,sha256=lgrv5QAuHCEzr5dXDj4-azNcs63addY9zdrGWj5zv_s,292
33
+ runem/types/options.py,sha256=y8_hyWYvhalC9-kZbvoDtxm0trZgyyGcswQqfuQy_pM,265
34
+ runem/types/runem_config.py,sha256=qG_bghm5Nr-ZTbaZbf1v8Fx447V-hgEvvRy5NZ3t-Io,5141
35
+ runem/types/types_jobs.py,sha256=wqiiBmRIJDbGlKcfOqewHGKx350w0p4_7pysMm7xGmo,4906
36
+ scripts/test_hooks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
+ scripts/test_hooks/json_validators.py,sha256=N2FyWcpjWzfFGycXLo-ecNLJkxTFPPbqPfVBcJLBlb4,967
38
+ scripts/test_hooks/py.py,sha256=YUbwNny7NPmv2bY7k7YcbJ-jRcnNfjQajE9Hn1MLaBc,8821
39
+ scripts/test_hooks/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ scripts/test_hooks/runem_hooks.py,sha256=FJMuDBEOz3dr9gBW3WW6yKbUJs_LFXb3klpqSzCAZRk,628
41
+ scripts/test_hooks/yarn.py,sha256=1QsG1rKAclpZoqp86ntkuvzYaYN4UkEvO0JhO2Kf5C8,1082
42
+ tests/cli/test_initialise_options.py,sha256=Ogwfqz39r2o1waXMqCC22OJsgZoLF2stwGVO5AZUc4s,3148
43
+ tests/data/help_output.3.10.txt,sha256=5TUpNITVL6pD5BpFAl-Orh3vkOpStveijZzvgJuI_sA,4280
44
+ tests/data/help_output.3.11.txt,sha256=ycrF-xKgdQ8qrWzkkR-vbHe7NulUTsCsS0_Gda8xYDs,4162
45
+ tests/test_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
+ tests/test_types/test_public_api.py,sha256=QHiwt7CetQur65JSbFRnOzQxhCJkX5MVLymHHVd_6yc,160
47
+ runem-0.6.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
48
+ runem-0.6.0.dist-info/METADATA,sha256=00_gsiXfiHPCwcZumnmOU2KjaUTvR6kF9c9nr4p2YHc,5892
49
+ runem-0.6.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
50
+ runem-0.6.0.dist-info/entry_points.txt,sha256=nu0g_vBeuPihYtimbtlNusxWovylMppvJ8UxdJlJfvM,46
51
+ runem-0.6.0.dist-info/top_level.txt,sha256=NkdxkwLKNNhxItveR2KqNqTshTZ268m5D7SjJEmG4-Y,20
52
+ runem-0.6.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.6.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,2 +1,3 @@
1
1
  runem
2
+ scripts
2
3
  tests
File without changes
@@ -0,0 +1,32 @@
1
+ import pathlib
2
+
3
+ from typing_extensions import Unpack
4
+
5
+ from runem.run_command import run_command
6
+ from runem.types import FilePathList, JobKwargs
7
+
8
+
9
+ def _json_validate(
10
+ **kwargs: Unpack[JobKwargs],
11
+ ) -> None:
12
+ label = kwargs["label"]
13
+ json_files: FilePathList = kwargs["file_list"]
14
+ json_with_comments = (
15
+ "cspell.json",
16
+ "tsconfig.spec.json",
17
+ "launch.json",
18
+ "settings.json",
19
+ )
20
+ for json_file in json_files:
21
+ json_path = pathlib.Path(json_file)
22
+ if not json_path.exists():
23
+ raise RuntimeError(
24
+ f"could not find '{str(json_path)}, in {pathlib.Path('.').absolute()}"
25
+ )
26
+ if json_path.name in json_with_comments:
27
+ # until we use a validator that allows comments in json, skip these
28
+ continue
29
+
30
+ cmd = ["python", "-m", "json.tool", f"{json_file}"]
31
+ kwargs["label"] = f"{label} {json_file}"
32
+ run_command(cmd=cmd, **kwargs)