-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathvalidate.py
115 lines (87 loc) · 2.93 KB
/
validate.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import concurrent.futures
import os
import shutil
import subprocess
import time
from concurrent.futures import Future
from dataclasses import dataclass
from rich.console import RenderableType, Console
from rich.live import Live
from rich.spinner import Spinner
from rich.table import Table
from src.mpyl.utilities.logging import try_parse_ansi
COMMANDS = [
"pipenv run format",
"pipenv run lint",
"pipenv run lint-test",
"pipenv run check-types",
"pipenv run check-types-test",
"pipenv run test",
]
def run_command(command: str):
try:
result = subprocess.run(
f"PIPENV_QUIET=1 {command}",
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
exit_code = result.returncode
stdout = result.stdout
stderr = result.stderr
return JobResult(command, stdout, stderr, exit_code)
except Exception as e:
return JobResult(command, "", f"Error running command {command}: {str(e)}", 1)
@dataclass
class Job:
command: str
future: Future
@dataclass
class JobResult:
command: str
stdout: str
stderr: str
exit_code: int
def trim(error_message: str) -> str:
lines = error_message.splitlines()
if len(lines) <= 10:
return "\n".join(lines).strip()
return "\n".join(error_message.splitlines()[-10:])
def to_row(job: Job) -> list[RenderableType]:
status = Spinner("clock")
output = ""
if job.future.done():
result: JobResult = job.future.result()
output = result.stdout if result.stdout else result.stderr
status = f":green_heart:" if result.exit_code == 0 else f":broken_heart:"
return [
status,
f"[italic]{job.command.replace('pipenv run ', '')}",
try_parse_ansi(trim(output).strip()),
]
def create_progress_table(jobs: list[Job]) -> Table:
table = Table(
*["Status", "Command", "Message"],
title="Validate sourcecode",
)
for job in jobs:
table.add_row(*to_row(job))
return table
def all_tasks_done(jobs_to_finish: list[Job]) -> bool:
return all([job.future.done() for job in jobs_to_finish])
def all_tasks_success(results: list[JobResult]) -> bool:
return all([job.exit_code == 0 for job in results])
def play_sound(success: bool):
if shutil.which("afplay") is None:
Console().bell()
return
sound = "Glass.aiff" if success else "Sosumi.aiff"
os.system(f"afplay /System/Library/Sounds/{sound}")
with concurrent.futures.ThreadPoolExecutor(max_workers=len(COMMANDS)) as executor:
jobs: list[Job] = [Job(cmd, executor.submit(run_command, cmd)) for cmd in COMMANDS]
with Live(create_progress_table(jobs), refresh_per_second=4) as live:
while not all_tasks_done(jobs):
time.sleep(0.2)
live.update(create_progress_table(jobs))
play_sound(all_tasks_success([job.future.result() for job in jobs]))