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/VERSION +1 -1
- runem/blocking_print.py +10 -2
- runem/cli/initialise_options.py +26 -0
- runem/command_line.py +23 -1
- runem/config_parse.py +21 -7
- runem/job_execute.py +3 -2
- runem/log.py +23 -4
- runem/report.py +4 -1
- runem/run_command.py +62 -26
- runem/runem.py +46 -60
- runem/types/__init__.py +2 -1
- runem/utils.py +12 -0
- runem-0.6.0.dist-info/METADATA +161 -0
- runem-0.6.0.dist-info/RECORD +52 -0
- {runem-0.4.0.dist-info → runem-0.6.0.dist-info}/WHEEL +1 -1
- {runem-0.4.0.dist-info → runem-0.6.0.dist-info}/top_level.txt +1 -0
- scripts/test_hooks/__init__.py +0 -0
- scripts/test_hooks/json_validators.py +32 -0
- scripts/test_hooks/py.py +292 -0
- scripts/test_hooks/py.typed +0 -0
- scripts/test_hooks/runem_hooks.py +19 -0
- scripts/test_hooks/yarn.py +48 -0
- tests/cli/test_initialise_options.py +105 -0
- tests/data/help_output.3.10.txt +55 -0
- tests/data/help_output.3.11.txt +55 -0
- runem-0.4.0.dist-info/METADATA +0 -155
- runem-0.4.0.dist-info/RECORD +0 -42
- {runem-0.4.0.dist-info → runem-0.6.0.dist-info}/LICENSE +0 -0
- {runem-0.4.0.dist-info → runem-0.6.0.dist-info}/entry_points.txt +0 -0
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
|
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
|
-
|
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
|
-
|
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 =
|
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 =
|
134
|
+
report: str = (
|
135
|
+
f"[green]{phase}[/green]: {progress}({num_workers}): {running_jobs_list}"
|
136
|
+
)
|
155
137
|
if show_spinner:
|
156
|
-
|
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[
|
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[
|
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
|
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[
|
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[
|
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[
|
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[
|
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[
|
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
|
-
|
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
|
+
<!-- [](https://codecov.io/gh/lursight/runem) -->
|
46
|
+
[](https://github.com/lursight/runem/actions/workflows/main.yml)
|
47
|
+
[](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,,
|
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)
|