forked from sio2project/oioioi
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patheasy_toolbox.py
More file actions
executable file
·235 lines (192 loc) · 6.92 KB
/
easy_toolbox.py
File metadata and controls
executable file
·235 lines (192 loc) · 6.92 KB
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python3
# pip requirements:
# python ^3.6
# inquirer (only for GUI)
#
# system:
# docker
# docker compose (V2)
# This script was created in order to help the users
# execute commands faster. The main idea was to omit
# copy-pasting commands from GitHub. This script
# is prepared and should be upgraded or/and extended
# for any future needs.
import os
import sys
from shlex import quote
if sys.platform == "win32":
# for Windows, a different syntax command_prefix is needed
command_prefix = "set OIOIOI_UID=1000 &&"
else:
command_prefix = "OIOIOI_UID=$(id -u)"
BASE_DOCKER_COMMAND = f"{command_prefix} docker compose -f docker-compose-dev.yml"
RAW_COMMANDS = [
(
"build",
"Build OIOIOI container from source.",
"build",
"This may result in the loss of local modifications inside containers.",
),
("up", "Run all SIO2 containers", "up -d"),
("down", "Stop and remove all SIO2 containers", "down"),
(
"wipe",
"Stop all SIO2 containers and DESTROY all data",
"down -v",
"This will stop all SIO2 containers and DESTROY all data.",
),
(
"run",
"Run django server and webpack",
'{exec} web conc -n js,py -c yellow,green -k "npm --prefix ../oioioi run -s watch" "python3 manage.py runserver 0.0.0.0:8000"',
),
("stop", "Stop all SIO2 containers", "stop"),
("bash", "Open command prompt on web container.", "{exec} web bash"),
("exec", "Run a command in the web container.", "{exec} web {extra_args}"),
("bash-db", "Open command prompt on database container.", "{exec} db bash"),
# This one CLEARS the database. Use wisely.
(
"flush-db",
"Clear database.",
"{exec} web python manage.py flush --noinput",
"This will CLEAR the database.",
),
("add-superuser", "Create admin_admin.", "{exec} web python manage.py loaddata ../oioioi/oioioi_cypress/cypress/fixtures/admin_admin.json"),
("test", "Run unit tests.", "{exec} web ../oioioi/test.sh {extra_args}"),
("test-slow", "Run unit tests. (--runslow)", "{exec} web ../oioioi/test.sh --runslow {extra_args}"),
(
"test-coverage",
"Run coverage tests.",
"{exec} 'web' ../oioioi/test.sh oioioi/problems --cov-report term --cov-report xml:coverage.xml --cov=oioioi {extra_args}",
),
("cypress-apply-settings", "Apply settings for CyPress.", '{exec} web bash -c "echo >> settings.py && echo CAPTCHA_TEST_MODE=True >> settings.py"'),
("npm", "Run npm command.", "{exec} web npm --prefix ../oioioi {extra_args}"),
("eslint", "Run javascript linter.", "{exec} web npm --prefix ../oioioi run lint"),
(
"ruff",
"Run Ruff, a linter and formatter. You can add `--fix` to automatically fix some errors.",
"{exec} -w /sio2/oioioi web bash -c 'ruff check . {extra_args} ; echo ; ruff format .'",
),
]
longest_command_arg = max([len(command[0]) for command in RAW_COMMANDS])
class Help(Exception):
pass
class Warning:
def __init__(self, _msg: str):
self.message = _msg
@classmethod
def default_warning(cls, _cmd: str):
message = f"You are going to execute command [{_cmd}] marked as `dangerous`."
return cls(message)
def __str__(self) -> str:
return f"{self.message}\nAre you sure? [y/N]"
class Option:
def __init__(self, _arg, _help, _command, _warning_msg=None, extra_args=None):
self.arg = _arg
self.extra_args = extra_args
self.help = _help
self.command = _command
if _warning_msg is None:
self.warning = None
else:
self.warning = Warning(_warning_msg)
# If we use exec we should add -T for GitHub actions (disable tty).
def gen_full_command(self, disable=False):
return self.command.format(
exec="exec -T" if disable else "exec",
extra_args=self.extra_args or "",
)
def long_str(self) -> str:
return f"Option({self.arg}, Description='{self.help}', Command='{self.command}')"
def __str__(self) -> str:
spaces = longest_command_arg - len(self.arg)
return f"[{self.arg}] {' ' * spaces} {self.help}"
# command names are unique
assert len(RAW_COMMANDS) == len({x[0] for x in RAW_COMMANDS})
COMMANDS = {x[0]: Option(*x) for x in RAW_COMMANDS}
NO_INPUT = False
def get_action_from_args() -> Option | None:
# not flags
arguments = []
for arg in sys.argv[1:]:
if arg in ["--help", "-h"]:
raise Help
elif arg in ["--no-input", "-i"]:
global NO_INPUT
NO_INPUT = True
else:
arguments.append(arg)
if len(arguments) < 1:
return None
if arguments[0] not in COMMANDS:
raise Exception("No argument was found!")
opt = COMMANDS[arguments[0]]
if len(arguments) > 1:
if r"{extra_args}" in opt.command:
opt.extra_args = " ".join(map(quote, arguments[1:]))
else:
raise Exception("Too many arguments!")
return opt
def get_action_from_gui() -> Option:
import inquirer
questions = [
inquirer.List(
"action",
message="Select OIOIOI action",
choices=COMMANDS,
),
]
answers = inquirer.prompt(questions)
return COMMANDS[answers["action"]]
def run_command(command) -> None:
print("Running command", command)
if not NO_INPUT:
width = os.get_terminal_size().columns
print("=" * width)
if sys.platform == "win32":
# for Windows, a different syntax for exiting is needed
exit_code = os.system(command)
sys.exit(exit_code)
else:
sys.exit(os.WEXITSTATUS(os.system(command)))
def warn_user(warning: Warning) -> bool:
print(warning)
while True:
choice = input().lower()
if not choice or "no".startswith(choice):
return False
elif "yes".startswith(choice):
return True
else:
print("Please answer [yes] or [no].")
def run() -> None:
action = get_action_from_args() or get_action_from_gui()
command = action.gen_full_command(disable=NO_INPUT)
if action.warning is not None and not NO_INPUT:
if not warn_user(action.warning):
print("Aborting.")
return
run_command(f"{BASE_DOCKER_COMMAND} {command}")
def print_help() -> None:
print(
"OIOIOI helper toolbox",
"",
"This script allows to control OIOIOI with Docker commands.",
f"Commands are always being run with '{BASE_DOCKER_COMMAND}' prefix.",
"Available commands are: ",
"",
*COMMANDS.values(),
"",
"Example `build`:",
f"{sys.argv[0]} build",
sep="\n",
)
def main() -> None:
try:
run()
except Help:
print_help()
except Exception as e:
print(f"An error occurred during execution: {e}", file=sys.stderr)
if __name__ == "__main__":
main()