runem 0.5.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/log.py +23 -4
- runem/report.py +4 -1
- runem/run_command.py +62 -26
- runem/runem.py +37 -58
- runem/types/__init__.py +2 -1
- runem/utils.py +12 -0
- runem-0.6.0.dist-info/METADATA +161 -0
- {runem-0.5.0.dist-info → runem-0.6.0.dist-info}/RECORD +14 -14
- {runem-0.5.0.dist-info → runem-0.6.0.dist-info}/WHEEL +1 -1
- runem-0.5.0.dist-info/METADATA +0 -164
- {runem-0.5.0.dist-info → runem-0.6.0.dist-info}/LICENSE +0 -0
- {runem-0.5.0.dist-info → runem-0.6.0.dist-info}/entry_points.txt +0 -0
- {runem-0.5.0.dist-info → runem-0.6.0.dist-info}/top_level.txt +0 -0
runem/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
runem/blocking_print.py
CHANGED
@@ -13,7 +13,14 @@ def _reset_console() -> Console:
|
|
13
13
|
|
14
14
|
RICH_CONSOLE = Console(
|
15
15
|
log_path=False, # Do NOT print the source path.
|
16
|
-
markup
|
16
|
+
# We allow markup here, BUT stdout/stderr from other procs should have
|
17
|
+
# `escape()` called on them so they don't error here.
|
18
|
+
# This means 'rich' effects/colors can be judiciously applied:
|
19
|
+
# e.g. `[blink]Don't Panic![/blink]`.
|
20
|
+
markup=True,
|
21
|
+
# `highlight` is what colourises string and number in print() calls.
|
22
|
+
# We do not want this to be auto-magic.
|
23
|
+
highlight=False,
|
17
24
|
)
|
18
25
|
return RICH_CONSOLE
|
19
26
|
|
@@ -31,7 +38,8 @@ def _reset_console_for_tests() -> None:
|
|
31
38
|
RICH_CONSOLE = Console(
|
32
39
|
log_path=False, # Do NOT print the source path.
|
33
40
|
log_time=False, # Do not prefix with log time e.g. `[time] log message`.
|
34
|
-
markup=
|
41
|
+
markup=True, # Allow some markup e.g. `[blink]Don't Panic![/blink]`.
|
42
|
+
highlight=False,
|
35
43
|
width=999, # A very wide width.
|
36
44
|
)
|
37
45
|
|
runem/log.py
CHANGED
@@ -3,13 +3,32 @@ import typing
|
|
3
3
|
from runem.blocking_print import blocking_print
|
4
4
|
|
5
5
|
|
6
|
-
def log(
|
6
|
+
def log(
|
7
|
+
msg: str = "",
|
8
|
+
decorate: bool = True,
|
9
|
+
end: typing.Optional[str] = None,
|
10
|
+
) -> None:
|
7
11
|
"""Thin wrapper around 'print', change the 'msg' & handles system-errors.
|
8
12
|
|
9
13
|
One way we change it is to decorate the output with 'runem'
|
14
|
+
|
15
|
+
Parameters:
|
16
|
+
msg: str - the message to log out. Any `rich` markup will be escaped
|
17
|
+
and not applied.
|
18
|
+
decorate: str - whether to add runem-specific prefix text. We do this
|
19
|
+
to identify text that comes from the app vs text that
|
20
|
+
comes from hooks or other third-parties.
|
21
|
+
end: Optional[str] - same as the end option used by `print()` and
|
22
|
+
`rich`
|
23
|
+
Returns:
|
24
|
+
Nothing
|
10
25
|
"""
|
26
|
+
# Remove any markup as it will probably error, if unsanitised.
|
27
|
+
# msg = escape(msg)
|
28
|
+
|
11
29
|
if decorate:
|
12
|
-
|
30
|
+
# Make it clear that the message comes from `runem` internals.
|
31
|
+
msg = f"[light_slate_grey]runem[/light_slate_grey]: {msg}"
|
13
32
|
|
14
33
|
# print in a blocking manner, waiting for system resources to free up if a
|
15
34
|
# runem job is contending on stdout or similar.
|
@@ -17,8 +36,8 @@ def log(msg: str = "", decorate: bool = True, end: typing.Optional[str] = None)
|
|
17
36
|
|
18
37
|
|
19
38
|
def warn(msg: str) -> None:
|
20
|
-
log(f"WARNING: {msg}")
|
39
|
+
log(f"[yellow]WARNING[/yellow]: {msg}")
|
21
40
|
|
22
41
|
|
23
42
|
def error(msg: str) -> None:
|
24
|
-
log(f"ERROR: {msg}")
|
43
|
+
log(f"[red]ERROR[/red]: {msg}")
|
runem/report.py
CHANGED
@@ -211,7 +211,10 @@ def _print_reports_by_phase(
|
|
211
211
|
for job_report_url_info in report_urls:
|
212
212
|
if not job_report_url_info:
|
213
213
|
continue
|
214
|
-
log(
|
214
|
+
log(
|
215
|
+
f"report: [blue]{str(job_report_url_info[0])}[/blue]: "
|
216
|
+
f"{str(job_report_url_info[1])}"
|
217
|
+
)
|
215
218
|
|
216
219
|
|
217
220
|
def report_on_run(
|
runem/run_command.py
CHANGED
@@ -7,17 +7,33 @@ from subprocess import STDOUT as SUBPROCESS_STDOUT
|
|
7
7
|
from subprocess import Popen
|
8
8
|
from timeit import default_timer as timer
|
9
9
|
|
10
|
+
from rich.markup import escape
|
11
|
+
|
10
12
|
from runem.log import log
|
11
13
|
|
12
14
|
TERMINAL_WIDTH = 86
|
13
15
|
|
14
16
|
|
15
|
-
class
|
16
|
-
|
17
|
+
class RunemJobError(RuntimeError):
|
18
|
+
"""An exception type that stores the stdout/stderr.
|
19
|
+
|
20
|
+
Designed so that we do not print the full stdout via the exception stack, instead,
|
21
|
+
allows an opportunity to parse the markup in it.
|
22
|
+
"""
|
23
|
+
|
24
|
+
def __init__(self, friendly_message: str, stdout: str):
|
25
|
+
self.stdout = stdout
|
26
|
+
super().__init__(friendly_message)
|
17
27
|
|
18
28
|
|
19
|
-
class
|
20
|
-
|
29
|
+
class RunCommandBadExitCode(RunemJobError):
|
30
|
+
def __init__(self, stdout: str):
|
31
|
+
super().__init__(friendly_message="Bad exit-code", stdout=stdout)
|
32
|
+
|
33
|
+
|
34
|
+
class RunCommandUnhandledError(RunemJobError):
|
35
|
+
def __init__(self, stdout: str):
|
36
|
+
super().__init__(friendly_message="Unhandled job error", stdout=stdout)
|
21
37
|
|
22
38
|
|
23
39
|
# A function type for recording timing information.
|
@@ -27,25 +43,25 @@ RecordSubJobTimeType = typing.Callable[[str, timedelta], None]
|
|
27
43
|
def parse_stdout(stdout: str, prefix: str) -> str:
|
28
44
|
"""Prefixes each line of the output with a given label, except trailing new
|
29
45
|
lines."""
|
46
|
+
|
30
47
|
# Edge case: Return the prefix immediately for an empty string
|
31
48
|
if not stdout:
|
32
49
|
return prefix
|
33
50
|
|
34
|
-
#
|
35
|
-
|
51
|
+
# Stop errors in `rich` by parsing out anything that might look like
|
52
|
+
# rich-markup.
|
53
|
+
stdout = escape(stdout)
|
54
|
+
|
55
|
+
# Split stdout into lines
|
36
56
|
lines = stdout.split("\n")
|
37
57
|
|
38
58
|
# Apply prefix to all lines except the last if it's empty (due to a trailing newline)
|
39
|
-
modified_lines = [f"{prefix}{line}" for line in lines[:-1]] + (
|
40
|
-
[lines[-1]]
|
41
|
-
if lines[-1] == "" and ends_with_newline
|
42
|
-
else [f"{prefix}{lines[-1]}"]
|
59
|
+
modified_lines = [f"{prefix}{escape(line)}" for line in lines[:-1]] + (
|
60
|
+
[f"{prefix}{escape(lines[-1])}"]
|
43
61
|
)
|
44
62
|
|
45
63
|
# Join the lines back together, appropriately handling the final newline
|
46
|
-
modified_stdout = "\n".join(modified_lines)
|
47
|
-
# if ends_with_newline:
|
48
|
-
# modified_stdout += "\n"
|
64
|
+
modified_stdout: str = "\n".join(modified_lines)
|
49
65
|
|
50
66
|
return modified_stdout
|
51
67
|
|
@@ -69,24 +85,34 @@ def _log_command_execution(
|
|
69
85
|
label: str,
|
70
86
|
env_overrides: typing.Optional[typing.Dict[str, str]],
|
71
87
|
valid_exit_ids: typing.Optional[typing.Tuple[int, ...]],
|
88
|
+
decorate_logs: bool,
|
72
89
|
verbose: bool,
|
73
90
|
cwd: typing.Optional[pathlib.Path] = None,
|
74
91
|
) -> None:
|
75
92
|
"""Logs out useful debug information on '--verbose'."""
|
76
93
|
if verbose:
|
77
|
-
log(
|
94
|
+
log(
|
95
|
+
f"running: start: [blue]{label}[/blue]: [yellow]{cmd_string}[yellow]",
|
96
|
+
decorate=decorate_logs,
|
97
|
+
)
|
78
98
|
if valid_exit_ids is not None:
|
79
99
|
valid_exit_strs = ",".join(str(exit_code) for exit_code in valid_exit_ids)
|
80
|
-
log(
|
100
|
+
log(
|
101
|
+
f"\tallowed return ids are: [green]{valid_exit_strs}[/green]",
|
102
|
+
decorate=decorate_logs,
|
103
|
+
)
|
81
104
|
|
82
105
|
if env_overrides:
|
83
106
|
env_overrides_as_string = " ".join(
|
84
107
|
[f"{key}='{value}'" for key, value in env_overrides.items()]
|
85
108
|
)
|
86
|
-
log(
|
109
|
+
log(
|
110
|
+
f"ENV OVERRIDES: [yellow]{env_overrides_as_string} {cmd_string}[/yellow]",
|
111
|
+
decorate=decorate_logs,
|
112
|
+
)
|
87
113
|
|
88
114
|
if cwd:
|
89
|
-
log(f"cwd: {str(cwd)}")
|
115
|
+
log(f"cwd: {str(cwd)}", decorate=decorate_logs)
|
90
116
|
|
91
117
|
|
92
118
|
def run_command( # noqa: C901
|
@@ -98,6 +124,7 @@ def run_command( # noqa: C901
|
|
98
124
|
valid_exit_ids: typing.Optional[typing.Tuple[int, ...]] = None,
|
99
125
|
cwd: typing.Optional[pathlib.Path] = None,
|
100
126
|
record_sub_job_time: typing.Optional[RecordSubJobTimeType] = None,
|
127
|
+
decorate_logs: bool = True,
|
101
128
|
**kwargs: typing.Any,
|
102
129
|
) -> str:
|
103
130
|
"""Runs the given command, returning stdout or throwing on any error."""
|
@@ -115,6 +142,7 @@ def run_command( # noqa: C901
|
|
115
142
|
label,
|
116
143
|
env_overrides,
|
117
144
|
valid_exit_ids,
|
145
|
+
decorate_logs,
|
118
146
|
verbose,
|
119
147
|
cwd,
|
120
148
|
)
|
@@ -143,7 +171,12 @@ def run_command( # noqa: C901
|
|
143
171
|
stdout += line
|
144
172
|
if verbose:
|
145
173
|
# print each line of output, assuming that each has a newline
|
146
|
-
log(
|
174
|
+
log(
|
175
|
+
parse_stdout(
|
176
|
+
line, prefix=f"[green]| [/green][blue]{label}[/blue]: "
|
177
|
+
),
|
178
|
+
decorate=False,
|
179
|
+
)
|
147
180
|
|
148
181
|
# Wait for the subprocess to finish and get the exit code
|
149
182
|
process.wait()
|
@@ -154,15 +187,15 @@ def run_command( # noqa: C901
|
|
154
187
|
)
|
155
188
|
raise RunCommandBadExitCode(
|
156
189
|
(
|
157
|
-
f"non-zero exit {process.returncode} (allowed are "
|
158
|
-
f"{valid_exit_strs}) from {cmd_string}"
|
190
|
+
f"non-zero exit [red]{process.returncode}[/red] (allowed are "
|
191
|
+
f"[green]{valid_exit_strs}[/green]) from {cmd_string}"
|
159
192
|
)
|
160
193
|
)
|
161
194
|
except BaseException as err:
|
162
195
|
if ignore_fails:
|
163
196
|
return ""
|
164
197
|
parsed_stdout: str = (
|
165
|
-
parse_stdout(stdout, prefix=
|
198
|
+
parse_stdout(stdout, prefix="[red]| [/red]") if process else ""
|
166
199
|
)
|
167
200
|
env_overrides_as_string = ""
|
168
201
|
if env_overrides:
|
@@ -171,11 +204,11 @@ def run_command( # noqa: C901
|
|
171
204
|
)
|
172
205
|
env_overrides_as_string = f"{env_overrides_as_string} "
|
173
206
|
error_string = (
|
174
|
-
f"runem:
|
175
|
-
f"\n\t{env_overrides_as_string}{cmd_string}"
|
176
|
-
f"\
|
207
|
+
f"runem: [red bold]FATAL[/red bold]: command failed: [blue]{label}[/blue]"
|
208
|
+
f"\n\t[yellow]{env_overrides_as_string}{cmd_string}[/yellow]"
|
209
|
+
f"\n[red underline]| ERROR[/red underline]"
|
177
210
|
f"\n{str(parsed_stdout)}"
|
178
|
-
f"\
|
211
|
+
f"\n[red underline]| ERROR END[/red underline]"
|
179
212
|
)
|
180
213
|
|
181
214
|
if isinstance(err, RunCommandBadExitCode):
|
@@ -184,7 +217,10 @@ def run_command( # noqa: C901
|
|
184
217
|
raise RunCommandUnhandledError(error_string) from err
|
185
218
|
|
186
219
|
if verbose:
|
187
|
-
log(
|
220
|
+
log(
|
221
|
+
f"running: done: [blue]{label}[/blue]: [yellow]{cmd_string}[/yellow]",
|
222
|
+
decorate=decorate_logs,
|
223
|
+
)
|
188
224
|
|
189
225
|
if record_sub_job_time is not None:
|
190
226
|
# Capture how long this run took
|
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,10 +31,9 @@ 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
|
@@ -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:
|
@@ -92,38 +93,8 @@ def _determine_run_parameters(argv: typing.List[str]) -> ConfigMetadata:
|
|
92
93
|
return config_metadata
|
93
94
|
|
94
95
|
|
95
|
-
class DummySpinner(ConsoleRenderable): # pragma: no cover
|
96
|
-
"""A dummy spinner for when spinners are disabled."""
|
97
|
-
|
98
|
-
def __init__(self) -> None:
|
99
|
-
self.text = ""
|
100
|
-
|
101
|
-
def __rich__(self) -> Text:
|
102
|
-
"""Return a rich Text object for rendering."""
|
103
|
-
return Text(self.text)
|
104
|
-
|
105
|
-
def __rich_console__(
|
106
|
-
self, console: Console, options: ConsoleOptions
|
107
|
-
) -> RenderResult:
|
108
|
-
"""Yield an empty string or placeholder text."""
|
109
|
-
yield Text(self.text)
|
110
|
-
|
111
|
-
def __enter__(self) -> None:
|
112
|
-
"""Support for context manager."""
|
113
|
-
pass
|
114
|
-
|
115
|
-
def __exit__(
|
116
|
-
self,
|
117
|
-
exc_type: typing.Optional[typing.Type[BaseException]],
|
118
|
-
exc_value: typing.Optional[BaseException],
|
119
|
-
traceback: typing.Optional[TracebackType],
|
120
|
-
) -> None:
|
121
|
-
"""Support for context manager."""
|
122
|
-
pass
|
123
|
-
|
124
|
-
|
125
96
|
def _update_progress(
|
126
|
-
|
97
|
+
phase: str,
|
127
98
|
running_jobs: typing.Dict[str, str],
|
128
99
|
completed_jobs: typing.Dict[str, str],
|
129
100
|
all_jobs: Jobs,
|
@@ -140,30 +111,36 @@ def _update_progress(
|
|
140
111
|
is_running (ValueProxy[bool]): Flag indicating if jobs are still running.
|
141
112
|
num_workers (int): Indicates the number of workers performing the jobs.
|
142
113
|
"""
|
143
|
-
# Using the `rich` module to show a loading spinner on console
|
144
|
-
spinner: typing.Union[Spinner, DummySpinner]
|
145
|
-
if show_spinner:
|
146
|
-
spinner = Spinner("dots", text="Starting tasks...")
|
147
|
-
else:
|
148
|
-
spinner = DummySpinner()
|
149
114
|
|
150
115
|
last_running_jobs_set: typing.Set[str] = set()
|
151
116
|
|
152
|
-
|
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:
|
153
125
|
while is_running.value:
|
154
126
|
running_jobs_set: typing.Set[str] = set(running_jobs.values())
|
155
127
|
|
156
128
|
# Progress report
|
157
129
|
progress: str = f"{len(completed_jobs)}/{len(all_jobs)}"
|
158
|
-
running_jobs_list =
|
159
|
-
running_jobs_set
|
130
|
+
running_jobs_list = printable_set_coloured(
|
131
|
+
running_jobs_set,
|
132
|
+
"blue",
|
160
133
|
) # Reflect current running jobs accurately
|
161
|
-
report: str =
|
134
|
+
report: str = (
|
135
|
+
f"[green]{phase}[/green]: {progress}({num_workers}): {running_jobs_list}"
|
136
|
+
)
|
162
137
|
if show_spinner:
|
163
|
-
|
138
|
+
assert isinstance(spinner_ctx, Status)
|
139
|
+
spinner_ctx.update(Text.from_markup(report))
|
164
140
|
else:
|
165
141
|
if last_running_jobs_set != running_jobs_set:
|
166
142
|
RICH_CONSOLE.log(report)
|
143
|
+
last_running_jobs_set = running_jobs_set
|
167
144
|
|
168
145
|
# Sleep for reduced CPU usage
|
169
146
|
time.sleep(0.1)
|
@@ -176,7 +153,7 @@ def _process_jobs(
|
|
176
153
|
phase: PhaseName,
|
177
154
|
jobs: Jobs,
|
178
155
|
show_spinner: bool,
|
179
|
-
) -> typing.Optional[
|
156
|
+
) -> typing.Optional[RunemJobError]:
|
180
157
|
"""Execute each given job asynchronously.
|
181
158
|
|
182
159
|
This is where the major real-world time savings happen, and it could be
|
@@ -196,12 +173,12 @@ def _process_jobs(
|
|
196
173
|
num_concurrent_procs: int = min(max_num_concurrent_procs, len(jobs))
|
197
174
|
log(
|
198
175
|
(
|
199
|
-
f"Running '{phase}' with {num_concurrent_procs} workers (of "
|
176
|
+
f"Running '[green]{phase}[/green]' with {num_concurrent_procs} workers (of "
|
200
177
|
f"{max_num_concurrent_procs} max) processing {len(jobs)} jobs"
|
201
178
|
)
|
202
179
|
)
|
203
180
|
|
204
|
-
subprocess_error: typing.Optional[
|
181
|
+
subprocess_error: typing.Optional[RunemJobError] = None
|
205
182
|
|
206
183
|
with multiprocessing.Manager() as manager:
|
207
184
|
running_jobs: DictProxy[typing.Any, typing.Any] = manager.dict()
|
@@ -235,7 +212,7 @@ def _process_jobs(
|
|
235
212
|
repeat(file_lists),
|
236
213
|
),
|
237
214
|
)
|
238
|
-
except
|
215
|
+
except RunemJobError as err: # pylint: disable=broad-exception-caught
|
239
216
|
subprocess_error = err
|
240
217
|
finally:
|
241
218
|
# Signal the terminal_writer process to exit
|
@@ -251,7 +228,7 @@ def _process_jobs_by_phase(
|
|
251
228
|
filtered_jobs_by_phase: PhaseGroupedJobs,
|
252
229
|
in_out_job_run_metadatas: JobRunMetadatasByPhase,
|
253
230
|
show_spinner: bool,
|
254
|
-
) -> typing.Optional[
|
231
|
+
) -> typing.Optional[RunemJobError]:
|
255
232
|
"""Execute each job asynchronously, grouped by phase.
|
256
233
|
|
257
234
|
Whilst it is conceptually useful to group jobs by 'phase', Phases are
|
@@ -275,7 +252,7 @@ def _process_jobs_by_phase(
|
|
275
252
|
if config_metadata.args.verbose:
|
276
253
|
log(f"Running Phase {phase}")
|
277
254
|
|
278
|
-
failure_exception: typing.Optional[
|
255
|
+
failure_exception: typing.Optional[RunemJobError] = _process_jobs(
|
279
256
|
config_metadata,
|
280
257
|
file_lists,
|
281
258
|
in_out_job_run_metadatas,
|
@@ -293,7 +270,7 @@ def _process_jobs_by_phase(
|
|
293
270
|
|
294
271
|
|
295
272
|
MainReturnType = typing.Tuple[
|
296
|
-
ConfigMetadata, JobRunMetadatasByPhase, typing.Optional[
|
273
|
+
ConfigMetadata, JobRunMetadatasByPhase, typing.Optional[RunemJobError]
|
297
274
|
]
|
298
275
|
|
299
276
|
|
@@ -333,7 +310,7 @@ def _main(
|
|
333
310
|
|
334
311
|
start = timer()
|
335
312
|
|
336
|
-
failure_exception: typing.Optional[
|
313
|
+
failure_exception: typing.Optional[RunemJobError] = _process_jobs_by_phase(
|
337
314
|
config_metadata,
|
338
315
|
file_lists,
|
339
316
|
filtered_jobs_by_phase,
|
@@ -362,7 +339,7 @@ def timed_main(argv: typing.List[str]) -> None:
|
|
362
339
|
start = timer()
|
363
340
|
config_metadata: ConfigMetadata
|
364
341
|
job_run_metadatas: JobRunMetadatasByPhase
|
365
|
-
failure_exception: typing.Optional[
|
342
|
+
failure_exception: typing.Optional[RunemJobError]
|
366
343
|
config_metadata, job_run_metadatas, failure_exception = _main(argv)
|
367
344
|
phase_run_oder: OrderedPhases = config_metadata.phases
|
368
345
|
end = timer()
|
@@ -372,14 +349,15 @@ def timed_main(argv: typing.List[str]) -> None:
|
|
372
349
|
system_time_spent, wall_clock_time_saved = report_on_run(
|
373
350
|
phase_run_oder, job_run_metadatas, time_taken
|
374
351
|
)
|
375
|
-
message: str = "DONE: runem took"
|
352
|
+
message: str = "[green bold]DONE[/green bold]: runem took"
|
376
353
|
if failure_exception:
|
377
|
-
message = "FAILED: your jobs failed after"
|
354
|
+
message = "[red bold]FAILED[/red bold]: your jobs failed after"
|
378
355
|
log(
|
379
356
|
(
|
380
357
|
f"{message}: {time_taken.total_seconds()}s, "
|
381
|
-
f"saving you {wall_clock_time_saved.total_seconds()}s, "
|
382
|
-
|
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]"
|
383
361
|
)
|
384
362
|
)
|
385
363
|
|
@@ -392,6 +370,7 @@ def timed_main(argv: typing.List[str]) -> None:
|
|
392
370
|
if failure_exception is not None:
|
393
371
|
# we got a failure somewhere, now that we've reported the timings we
|
394
372
|
# re-raise.
|
373
|
+
error(failure_exception.stdout)
|
395
374
|
raise failure_exception
|
396
375
|
|
397
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
|
+
|
@@ -1,8 +1,8 @@
|
|
1
|
-
runem/VERSION,sha256=
|
1
|
+
runem/VERSION,sha256=l6XW5UCmEg0Jw53bZn4Ojiusf8wv_vgTuC4I_WA2W84,6
|
2
2
|
runem/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
runem/__main__.py,sha256=dsOiVZegpfK9JOs5n7UmbX5iwwbj7iFkEbLoVeEgAn4,136
|
4
4
|
runem/base.py,sha256=EZfR7FIlwEdU9Vfe47Wk2DOO8GQqpKxxLNKp6YHueZ4,316
|
5
|
-
runem/blocking_print.py,sha256=
|
5
|
+
runem/blocking_print.py,sha256=2nCvc10zXl1DRJldkruKthKjfmZKErcmxQzt3pjmN-c,2289
|
6
6
|
runem/cli.py,sha256=wEt_Jnumhl8SiOdKdSJzLkJpWv6n3_Odhi_HeIixr1k,134
|
7
7
|
runem/command_line.py,sha256=qkZFCCq9hUl6RO398SJzoigv8di5jGw2sdNwgTVBdd8,14474
|
8
8
|
runem/config.py,sha256=UiEU0Jyg5qjrNStvasWYjMOABQHhpZjbPiX3-sH_CMg,5969
|
@@ -17,15 +17,15 @@ runem/job_filter.py,sha256=7vgG4YWJ9gyGBFjV7QbSojG5ofYoszAmxXx9HnMLkHo,5384
|
|
17
17
|
runem/job_runner_simple_command.py,sha256=iP5an6yixW8o4C0ZBtu6csb-oVK3Q62ZZgtHBmxlXaU,2428
|
18
18
|
runem/job_wrapper.py,sha256=q5GtopZ5vhSJ581rwU4-lF9KnbL3ZYgOC8fqaCnXD_g,983
|
19
19
|
runem/job_wrapper_python.py,sha256=rx7J_N-JXs8GgMq7Sla7B9s_ZAfofKUhEnzgMcq_bts,4303
|
20
|
-
runem/log.py,sha256=
|
20
|
+
runem/log.py,sha256=6r6HIJyvp19P6PZNo93qdIxE0SpTAsY4ELrETBl1dC4,1363
|
21
21
|
runem/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
|
-
runem/report.py,sha256=
|
23
|
-
runem/run_command.py,sha256=
|
24
|
-
runem/runem.py,sha256=
|
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
25
|
runem/runem_version.py,sha256=MbETwZO2Tb1Y3hX_OYZjKepEMKA1cjNvr-7Cqhz6e3s,271
|
26
|
-
runem/utils.py,sha256=
|
26
|
+
runem/utils.py,sha256=MEYfox09rLvb6xmay_3rV1cWmdqMbhaAjOomYGNk15k,602
|
27
27
|
runem/cli/initialise_options.py,sha256=zx_EduWQk7yGBr3XUNrffHCSInPv05edFItHLnlo9dk,918
|
28
|
-
runem/types/__init__.py,sha256=
|
28
|
+
runem/types/__init__.py,sha256=0bWG7hE7VeqJ2oIu-xhrqQud8hcNp6WNbF3uMfT_n9g,314
|
29
29
|
runem/types/common.py,sha256=gPMSoJ3yRUYjHnoviRrpSg0gRwsGLFGWGpbTWkq4jX0,279
|
30
30
|
runem/types/errors.py,sha256=rbM5BA6UhY1X7Q0OZLUNsG7JXAjgNFTG5KQuqPNuZm8,103
|
31
31
|
runem/types/filters.py,sha256=8R5fyMssN0ISGBilJhEtbdHFl6OP7uI51WKkB5SH6EA,255
|
@@ -44,9 +44,9 @@ tests/data/help_output.3.10.txt,sha256=5TUpNITVL6pD5BpFAl-Orh3vkOpStveijZzvgJuI_
|
|
44
44
|
tests/data/help_output.3.11.txt,sha256=ycrF-xKgdQ8qrWzkkR-vbHe7NulUTsCsS0_Gda8xYDs,4162
|
45
45
|
tests/test_types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
46
46
|
tests/test_types/test_public_api.py,sha256=QHiwt7CetQur65JSbFRnOzQxhCJkX5MVLymHHVd_6yc,160
|
47
|
-
runem-0.
|
48
|
-
runem-0.
|
49
|
-
runem-0.
|
50
|
-
runem-0.
|
51
|
-
runem-0.
|
52
|
-
runem-0.
|
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,,
|
runem-0.5.0.dist-info/METADATA
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.1
|
2
|
-
Name: runem
|
3
|
-
Version: 0.5.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: Accelerate Your Development Workflow
|
50
|
-
**Boost Efficiency and Save Time**
|
51
|
-
Runem is a flexible, multi-process tool designed to speed up your everyday tasks by running them in parallel. Whether you're testing, linting, or deploying, runem helps you work smarter and faster.
|
52
|
-
|
53
|
-
## Why Choose Run'em?
|
54
|
-
- **Streamlined Task Management**: Configure tasks with ease using declarative .runem.yml files.
|
55
|
-
- **Multiprocess Execution**: Run multiple tasks simultaneously, minimizing wall-clock time.
|
56
|
-
- **Optimized for Monorepos**: Supports multiple projects and task types, with easy filtering and configuration.
|
57
|
-
- **Detailed Reporting**: Get insights into task execution time and efficiency gains.
|
58
|
-
|
59
|
-
## Contents
|
60
|
-
- [Run'em: Accelerate Your Development Workflow](#runem-accelerate-your-development-workflow)
|
61
|
-
- [Why Choose Run'em?](#why-choose-runem)
|
62
|
-
- [Contents](#contents)
|
63
|
-
- [Features At A Glance:](#features-at-a-glance)
|
64
|
-
- [Using Run'em](#using-runem)
|
65
|
-
- [Installation](#installation)
|
66
|
-
- [Quick-start](#quick-start)
|
67
|
-
- [Basic quick-start](#basic-quick-start)
|
68
|
-
- [A more complete quick-start](#a-more-complete-quick-start)
|
69
|
-
- [Basic Use](#basic-use)
|
70
|
-
- [Advanced Use](#advanced-use)
|
71
|
-
- [Advanced configuration options](#advanced-configuration-options)
|
72
|
-
- [Custom reports](#custom-reports)
|
73
|
-
- [Help and job discovery](#help-and-job-discovery)
|
74
|
-
- [Troubleshooting](#troubleshooting)
|
75
|
-
- [Contributing to and supporting runem](#contributing-to-and-supporting-runem)
|
76
|
-
- [Development](#development)
|
77
|
-
- [Sponsor](#sponsor)
|
78
|
-
- [About runem](#about-runem)
|
79
|
-
|
80
|
-
|
81
|
-
# Features At A Glance:
|
82
|
-
- **Tagging**: Easily run specific job groups (e.g., lint, test, python).
|
83
|
-
- **Phases**: Organize tasks by phase (e.g., edit, test, deploy).
|
84
|
-
- **Configurable Options**: Customize how jobs are executed using simple options.
|
85
|
-
- **Declarative**: Jobs are define using simple YAML in [.runem.yml](https://lursight.github.io/runem/docs/configuration.html) .
|
86
|
-
|
87
|
-
# Using Run'em
|
88
|
-
|
89
|
-
## Installation
|
90
|
-
|
91
|
-
```bash
|
92
|
-
pip install runem
|
93
|
-
```
|
94
|
-
|
95
|
-
## Quick-start
|
96
|
-
|
97
|
-
## Basic quick-start
|
98
|
-
Create the following `.runem.yml` file at the root of your project:
|
99
|
-
|
100
|
-
```yml
|
101
|
-
- job:
|
102
|
-
command: echo "hello world!"
|
103
|
-
```
|
104
|
-
|
105
|
-
Then anywhere in your project run `runem` to see how and when that task is run, and how long it took:
|
106
|
-
```bash
|
107
|
-
runem
|
108
|
-
```
|
109
|
-
|
110
|
-
To see the actual log output you will need to use `--verbose` as `runem` hides anything that isn't important. Only failures and reports are considered important.
|
111
|
-
```bash
|
112
|
-
# Or, to see "hello world!", use --verbose
|
113
|
-
runem --verbose # add --verbose to see the actual output
|
114
|
-
```
|
115
|
-
|
116
|
-
To see how you can control your job use `--help`:
|
117
|
-
```bash
|
118
|
-
runem --help
|
119
|
-
```
|
120
|
-
|
121
|
-
### A more complete quick-start
|
122
|
-
|
123
|
-
See [quick-start docs](https://lursight.github.io/runem/docs/quick_start.html) for more quick-start tips.
|
124
|
-
|
125
|
-
## Basic Use
|
126
|
-
|
127
|
-
See [docs on basic use and use-cases](https://lursight.github.io/runem/docs/basic_use.html) for a comprehensive introduction.
|
128
|
-
|
129
|
-
## Advanced Use
|
130
|
-
|
131
|
-
### Advanced configuration options
|
132
|
-
See [configuration docs](https://lursight.github.io/runem/docs/configuration.html) for advanced configuration and use.
|
133
|
-
|
134
|
-
### Custom reports
|
135
|
-
See [reporting docs](https://lursight.github.io/runem/docs/reports.html) for more information on how reporting works.
|
136
|
-
|
137
|
-
|
138
|
-
# Help and job discovery
|
139
|
-
|
140
|
-
`--help` is designed to help your team discover what jobs and tasks they can automated. Read more at
|
141
|
-
[help and discovery docs](https://lursight.github.io/runem/docs/help_and_job_discovery.html).
|
142
|
-
|
143
|
-
# Troubleshooting
|
144
|
-
|
145
|
-
See [troubleshooting and known issues docs](https://lursight.github.io/runem/docs/troubleshooting_known_issues.html).
|
146
|
-
|
147
|
-
---
|
148
|
-
# Contributing to and supporting runem
|
149
|
-
|
150
|
-
Awesome runem created by lursight
|
151
|
-
|
152
|
-
## Development
|
153
|
-
|
154
|
-
Read the [CONTRIBUTING.md](CONTRIBUTING.md) file.
|
155
|
-
|
156
|
-
## Sponsor
|
157
|
-
|
158
|
-
[❤️ Sponsor this project](https://github.com/sponsors/lursight/)
|
159
|
-
|
160
|
-
# About runem
|
161
|
-
The runem mission is to improve developer velocity at
|
162
|
-
[Lursight Ltd.](https://lursight.com), read more about the runem
|
163
|
-
[mission](https://lursight.github.io/runem/docs/mission.html).
|
164
|
-
|
File without changes
|
File without changes
|
File without changes
|