rbx.cp 0.6.0__py3-none-any.whl → 0.6.1__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.
- rbx/box/cd.py +11 -1
- rbx/box/checkers.py +9 -1
- rbx/box/cli.py +49 -45
- rbx/box/code.py +9 -7
- rbx/box/contest/contest_package.py +4 -7
- rbx/box/contest/main.py +7 -58
- rbx/box/creation.py +3 -36
- rbx/box/environment.py +21 -9
- rbx/box/linting.py +26 -0
- rbx/box/package.py +3 -34
- rbx/box/presets/__init__.py +362 -281
- rbx/box/presets/lock_schema.py +1 -2
- rbx/box/presets/schema.py +13 -5
- rbx/box/retries.py +8 -0
- rbx/box/schema.py +15 -2
- rbx/box/solutions.py +71 -14
- rbx/box/stats.py +92 -0
- rbx/box/tasks.py +6 -3
- rbx/box/ui/utils/run_ui.py +1 -1
- rbx/grading/judge/sandbox.py +22 -10
- rbx/grading/steps.py +1 -0
- rbx/resources/presets/default/problem/problem.rbx.yml +11 -0
- rbx/resources/presets/default/problem/random.txt +3 -1
- rbx/resources/presets/default/problem/rbx.h +92 -0
- rbx/resources/presets/default/problem/statement/statement.rbx.tex +4 -7
- rbx/resources/presets/default/problem/validator.cpp +8 -8
- {rbx_cp-0.6.0.dist-info → rbx_cp-0.6.1.dist-info}/METADATA +22 -6
- {rbx_cp-0.6.0.dist-info → rbx_cp-0.6.1.dist-info}/RECORD +31 -29
- {rbx_cp-0.6.0.dist-info → rbx_cp-0.6.1.dist-info}/WHEEL +1 -1
- rbx/resources/presets/default/problem/statement/projecao.png +0 -0
- {rbx_cp-0.6.0.dist-info → rbx_cp-0.6.1.dist-info}/LICENSE +0 -0
- {rbx_cp-0.6.0.dist-info → rbx_cp-0.6.1.dist-info}/entry_points.txt +0 -0
rbx/box/presets/__init__.py
CHANGED
@@ -3,34 +3,28 @@ import shutil
|
|
3
3
|
import tempfile
|
4
4
|
from typing import Annotated, Iterable, List, Optional, Sequence, Union
|
5
5
|
|
6
|
-
import rich
|
7
|
-
import rich.prompt
|
8
6
|
import typer
|
9
|
-
from iso639.language import functools
|
10
7
|
|
11
8
|
from rbx import console, utils
|
12
|
-
from rbx.box import cd
|
13
|
-
from rbx.box.environment import get_environment_path
|
9
|
+
from rbx.box import cd
|
14
10
|
from rbx.box.presets.fetch import PresetFetchInfo, get_preset_fetch_info
|
15
11
|
from rbx.box.presets.lock_schema import LockedAsset, PresetLock
|
16
12
|
from rbx.box.presets.schema import Preset, TrackedAsset
|
17
13
|
from rbx.config import get_default_app_path
|
18
|
-
from rbx.grading.judge.digester import digest_cooperatively
|
14
|
+
from rbx.grading.judge.digester import digest_cooperatively
|
19
15
|
|
20
16
|
app = typer.Typer(no_args_is_help=True)
|
21
17
|
|
22
|
-
LOCAL = 'local'
|
23
18
|
|
24
|
-
|
25
|
-
def find_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
19
|
+
def _find_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
26
20
|
found = root / 'preset.rbx.yml'
|
27
21
|
if found.exists():
|
28
22
|
return found
|
29
23
|
return None
|
30
24
|
|
31
25
|
|
32
|
-
def
|
33
|
-
found =
|
26
|
+
def _get_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Preset:
|
27
|
+
found = _find_preset_yaml(root)
|
34
28
|
if not found:
|
35
29
|
console.console.print(
|
36
30
|
f'[error][item]preset.rbx.yml[/item] not found in [item]{root.absolute()}[/item][/error]'
|
@@ -39,7 +33,7 @@ def get_preset_yaml(root: pathlib.Path = pathlib.Path()) -> Preset:
|
|
39
33
|
return utils.model_from_yaml(Preset, found.read_text())
|
40
34
|
|
41
35
|
|
42
|
-
def
|
36
|
+
def _find_preset_lock(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Path]:
|
43
37
|
root = root.resolve()
|
44
38
|
problem_yaml_path = root / '.preset-lock.yml'
|
45
39
|
if not problem_yaml_path.is_file():
|
@@ -47,8 +41,8 @@ def find_preset_lock(root: pathlib.Path = pathlib.Path()) -> Optional[pathlib.Pa
|
|
47
41
|
return problem_yaml_path
|
48
42
|
|
49
43
|
|
50
|
-
def
|
51
|
-
found =
|
44
|
+
def _get_preset_lock(root: pathlib.Path = pathlib.Path()) -> Optional[PresetLock]:
|
45
|
+
found = _find_preset_lock(root)
|
52
46
|
if not found:
|
53
47
|
return None
|
54
48
|
return utils.model_from_yaml(PresetLock, found.read_text())
|
@@ -77,34 +71,22 @@ def _find_local_preset(root: pathlib.Path) -> Optional[pathlib.Path]:
|
|
77
71
|
return problem_yaml_path.parent
|
78
72
|
|
79
73
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
console.console.print('[error]Local preset was not found.[/error]')
|
87
|
-
raise typer.Exit(1)
|
88
|
-
return local_path
|
89
|
-
nested_preset_path = _find_nested_preset(root)
|
90
|
-
if nested_preset_path is not None:
|
91
|
-
nested_preset = utils.model_from_yaml(
|
92
|
-
Preset, (nested_preset_path / 'preset.rbx.yml').read_text()
|
93
|
-
)
|
94
|
-
if nested_preset.name == name:
|
95
|
-
return nested_preset_path
|
96
|
-
return utils.get_app_path() / 'presets' / name
|
97
|
-
|
74
|
+
def _is_installed_preset(root: pathlib.Path = pathlib.Path()) -> bool:
|
75
|
+
preset_path = _find_local_preset(root)
|
76
|
+
if preset_path is None:
|
77
|
+
return False
|
78
|
+
resolved_path = preset_path.resolve()
|
79
|
+
return resolved_path.name == '.local.rbx'
|
98
80
|
|
99
|
-
def _find_installed_presets() -> List[str]:
|
100
|
-
folder = utils.get_app_path() / 'presets'
|
101
81
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
82
|
+
def _is_active_preset_nested(root: pathlib.Path = pathlib.Path()) -> bool:
|
83
|
+
preset_path = _find_local_preset(root)
|
84
|
+
if preset_path is None:
|
85
|
+
return False
|
86
|
+
nested_preset_path = _find_nested_preset(root)
|
87
|
+
if nested_preset_path is None:
|
88
|
+
return False
|
89
|
+
return nested_preset_path == preset_path
|
108
90
|
|
109
91
|
|
110
92
|
def _is_contest(root: pathlib.Path = pathlib.Path()) -> bool:
|
@@ -121,24 +103,6 @@ def _check_is_valid_package(root: pathlib.Path = pathlib.Path()):
|
|
121
103
|
raise typer.Exit(1)
|
122
104
|
|
123
105
|
|
124
|
-
def _get_preset_package_path(
|
125
|
-
name: str, is_contest: bool, root: pathlib.Path = pathlib.Path()
|
126
|
-
) -> pathlib.Path:
|
127
|
-
preset_path = get_preset_installation_path(name, root)
|
128
|
-
preset = get_installed_preset(name, root)
|
129
|
-
|
130
|
-
if is_contest:
|
131
|
-
assert (
|
132
|
-
preset.contest is not None
|
133
|
-
), 'Preset does not have a contest package definition.'
|
134
|
-
return preset_path / preset.contest
|
135
|
-
|
136
|
-
assert (
|
137
|
-
preset.problem is not None
|
138
|
-
), 'Preset does not have a problem package definition,'
|
139
|
-
return preset_path / preset.problem
|
140
|
-
|
141
|
-
|
142
106
|
def _process_globbing(
|
143
107
|
assets: Iterable[TrackedAsset], preset_dir: pathlib.Path
|
144
108
|
) -> List[TrackedAsset]:
|
@@ -154,9 +118,12 @@ def _process_globbing(
|
|
154
118
|
return res
|
155
119
|
|
156
120
|
|
157
|
-
def _get_preset_tracked_assets(
|
158
|
-
|
159
|
-
|
121
|
+
def _get_preset_tracked_assets(
|
122
|
+
root: pathlib.Path, is_contest: bool
|
123
|
+
) -> List[TrackedAsset]:
|
124
|
+
preset = get_active_preset(root)
|
125
|
+
preset_path = _find_local_preset(root)
|
126
|
+
assert preset_path is not None
|
160
127
|
|
161
128
|
if is_contest:
|
162
129
|
assert (
|
@@ -222,7 +189,6 @@ def _find_modified_assets(
|
|
222
189
|
|
223
190
|
|
224
191
|
def _copy_updated_assets(
|
225
|
-
preset_name: str,
|
226
192
|
preset_lock: PresetLock,
|
227
193
|
is_contest: bool,
|
228
194
|
root: pathlib.Path = pathlib.Path(),
|
@@ -232,9 +198,11 @@ def _copy_updated_assets(
|
|
232
198
|
preset_lock.assets, current_package_snapshot
|
233
199
|
)
|
234
200
|
|
235
|
-
|
201
|
+
preset = get_active_preset(root)
|
202
|
+
preset_package_path = _get_active_preset_package_path(root, is_contest)
|
203
|
+
|
236
204
|
preset_tracked_assets = _get_preset_tracked_assets(
|
237
|
-
|
205
|
+
preset_package_path, is_contest=is_contest
|
238
206
|
)
|
239
207
|
current_preset_snapshot = _build_package_locked_assets(
|
240
208
|
preset_tracked_assets, preset_package_path
|
@@ -246,137 +214,151 @@ def _copy_updated_assets(
|
|
246
214
|
dst_path = root / asset.path
|
247
215
|
shutil.copyfile(str(src_path), str(dst_path))
|
248
216
|
console.console.print(
|
249
|
-
f'Updated [item]{asset.path}[/item] from preset [item]{
|
217
|
+
f'Updated [item]{asset.path}[/item] from preset [item]{preset.name}[/item].'
|
250
218
|
)
|
251
219
|
|
252
220
|
|
253
|
-
def
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
return False
|
259
|
-
yaml_path = rsrc_preset_path / 'preset.rbx.yml'
|
260
|
-
if not yaml_path.is_file():
|
261
|
-
return False
|
262
|
-
console.console.print(f'Installing preset [item]{name}[/item] from resources...')
|
263
|
-
_install(rsrc_preset_path, force=True)
|
264
|
-
return True
|
265
|
-
|
221
|
+
def get_active_preset_or_null(root: pathlib.Path = pathlib.Path()) -> Optional[Preset]:
|
222
|
+
local_preset = _find_local_preset(root)
|
223
|
+
if local_preset is not None:
|
224
|
+
return _get_preset_yaml(local_preset)
|
225
|
+
return None
|
266
226
|
|
267
|
-
@functools.cache
|
268
|
-
def _install_from_resources_just_once(name: str) -> bool:
|
269
|
-
# Send all output to the void since we don't wanna be verbose here.
|
270
|
-
with console.console.capture():
|
271
|
-
return _try_installing_from_resources(name)
|
272
227
|
|
228
|
+
def get_active_preset(root: pathlib.Path = pathlib.Path()) -> Preset:
|
229
|
+
preset = get_active_preset_or_null(root)
|
230
|
+
if preset is None:
|
231
|
+
console.console.print('[error]No preset is active.[/error]')
|
232
|
+
raise typer.Exit(1)
|
233
|
+
return preset
|
273
234
|
|
274
|
-
def get_installed_preset_or_null(
|
275
|
-
name: str, root: pathlib.Path = pathlib.Path()
|
276
|
-
) -> Optional[Preset]:
|
277
|
-
installation_path = get_preset_installation_path(name, root) / 'preset.rbx.yml'
|
278
|
-
if not installation_path.is_file():
|
279
|
-
if not _try_installing_from_resources(name):
|
280
|
-
return None
|
281
|
-
elif name == 'default':
|
282
|
-
_install_from_resources_just_once(name)
|
283
235
|
|
284
|
-
|
236
|
+
def get_active_preset_path(root: pathlib.Path = pathlib.Path()) -> pathlib.Path:
|
237
|
+
preset_path = _find_local_preset(root)
|
238
|
+
if preset_path is None:
|
239
|
+
console.console.print('[error]No preset is active.[/error]')
|
240
|
+
raise typer.Exit(1)
|
241
|
+
return preset_path
|
285
242
|
|
286
243
|
|
287
|
-
def
|
288
|
-
|
289
|
-
|
244
|
+
def get_preset_environment_path(
|
245
|
+
root: pathlib.Path = pathlib.Path(),
|
246
|
+
) -> Optional[pathlib.Path]:
|
247
|
+
preset = get_active_preset_or_null(root)
|
248
|
+
if preset is None or preset.env is None:
|
249
|
+
return None
|
250
|
+
preset_path = get_active_preset_path(root)
|
251
|
+
env_path = preset_path / preset.env
|
252
|
+
if not env_path.is_file():
|
290
253
|
console.console.print(
|
291
|
-
f'[error]Preset [item]{name}[/item]
|
254
|
+
f'[error]Preset [item]{preset.name}[/item] environment file [item]{preset.env}[/item] does not exist.[/error]'
|
292
255
|
)
|
293
256
|
raise typer.Exit(1)
|
294
|
-
return
|
257
|
+
return env_path
|
295
258
|
|
296
259
|
|
297
|
-
def
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
if
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
260
|
+
def _get_active_preset_package_path(
|
261
|
+
root: pathlib.Path = pathlib.Path(),
|
262
|
+
is_contest: bool = False,
|
263
|
+
) -> pathlib.Path:
|
264
|
+
preset = get_active_preset(root)
|
265
|
+
preset_path = _find_local_preset(root)
|
266
|
+
assert preset_path is not None
|
267
|
+
if is_contest:
|
268
|
+
assert (
|
269
|
+
preset.contest is not None
|
270
|
+
), 'Preset does not have a contest package definition.'
|
271
|
+
return preset_path / preset.contest
|
272
|
+
assert (
|
273
|
+
preset.problem is not None
|
274
|
+
), 'Preset does not have a problem package definition.'
|
275
|
+
return preset_path / preset.problem
|
276
|
+
|
277
|
+
|
278
|
+
def get_preset_fetch_info_with_fallback(
|
279
|
+
uri: Optional[str],
|
280
|
+
) -> Optional[PresetFetchInfo]:
|
281
|
+
if uri is None:
|
282
|
+
# Use active preset if any, otherwise use the default preset.
|
283
|
+
if get_active_preset_or_null() is not None:
|
284
|
+
return None
|
285
|
+
default_preset = get_preset_fetch_info('default')
|
286
|
+
if default_preset is None:
|
287
|
+
console.console.print(
|
288
|
+
'[error]Internal error: could not find [item]default[/item] preset.[/error]'
|
289
|
+
)
|
290
|
+
raise typer.Exit(1)
|
291
|
+
return default_preset
|
292
|
+
return get_preset_fetch_info(uri)
|
293
|
+
|
294
|
+
|
295
|
+
def _clean_copied_package_dir(dest: pathlib.Path):
|
296
|
+
for box_dir in dest.rglob('.box'):
|
297
|
+
shutil.rmtree(str(box_dir), ignore_errors=True)
|
298
|
+
for lock in dest.rglob('.preset-lock.yml'):
|
299
|
+
lock.unlink(missing_ok=True)
|
315
300
|
|
316
|
-
console.console.print(
|
317
|
-
f'[success]Overwriting the existing environment based on [item]{preset.env}[/item].'
|
318
|
-
)
|
319
|
-
env_path.parent.mkdir(parents=True, exist_ok=True)
|
320
|
-
shutil.copyfile(str(preset_env_path), env_path)
|
321
301
|
|
302
|
+
def _clean_copied_contest_dir(dest: pathlib.Path, delete_local_rbx: bool = True):
|
303
|
+
shutil.rmtree(str(dest / 'build'), ignore_errors=True)
|
304
|
+
if delete_local_rbx:
|
305
|
+
shutil.rmtree(str(dest / '.local.rbx'), ignore_errors=True)
|
306
|
+
_clean_copied_package_dir(dest)
|
322
307
|
|
323
|
-
def _install(root: pathlib.Path = pathlib.Path(), force: bool = False):
|
324
|
-
preset = get_preset_yaml(root)
|
325
308
|
|
326
|
-
|
327
|
-
|
309
|
+
def _clean_copied_problem_dir(dest: pathlib.Path):
|
310
|
+
shutil.rmtree(str(dest / 'build'), ignore_errors=True)
|
311
|
+
_clean_copied_package_dir(dest)
|
328
312
|
|
329
|
-
console.console.print(f'Installing preset [item]{preset.name}[/item]...')
|
330
|
-
installation_path = get_preset_installation_path(preset.name)
|
331
313
|
|
332
|
-
|
314
|
+
def _install_preset_from_dir(
|
315
|
+
src: pathlib.Path,
|
316
|
+
dest: pathlib.Path,
|
317
|
+
ensure_contest: bool = False,
|
318
|
+
ensure_problem: bool = False,
|
319
|
+
update: bool = False,
|
320
|
+
override_uri: Optional[str] = None,
|
321
|
+
):
|
322
|
+
preset = _get_preset_yaml(src)
|
323
|
+
|
324
|
+
if ensure_contest and preset.contest is None:
|
333
325
|
console.console.print(
|
334
|
-
'[error]
|
326
|
+
f'[error]Preset [item]{preset.name}[/item] does not have a contest package definition.[/error]'
|
335
327
|
)
|
336
328
|
raise typer.Exit(1)
|
337
|
-
|
338
|
-
if preset.env is not None:
|
329
|
+
if ensure_problem and preset.problem is None:
|
339
330
|
console.console.print(
|
340
|
-
f'[item]{preset.name}[/item]
|
331
|
+
f'[error]Preset [item]{preset.name}[/item] does not have a problem package definition.[/error]'
|
341
332
|
)
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
f'Environment [item]{preset.name}[/item] already exists. Overwrite?',
|
346
|
-
console=console.console,
|
347
|
-
)
|
348
|
-
if not res:
|
349
|
-
should_copy_env = False
|
350
|
-
|
351
|
-
if should_copy_env:
|
352
|
-
get_environment_path(preset.name).parent.mkdir(parents=True, exist_ok=True)
|
353
|
-
shutil.rmtree(get_environment_path(preset.name), ignore_errors=True)
|
354
|
-
shutil.copyfile(str(root / preset.env), get_environment_path(preset.name))
|
355
|
-
|
356
|
-
console.console.print(f'[item]{preset.name}[/item]: Copying preset folder...')
|
357
|
-
installation_path.parent.mkdir(parents=True, exist_ok=True)
|
358
|
-
if installation_path.exists():
|
359
|
-
res = force or rich.prompt.Confirm.ask(
|
360
|
-
f'Preset [item]{preset.name}[/item] is already installed. Overwrite?',
|
361
|
-
console=console.console,
|
362
|
-
)
|
363
|
-
if not res:
|
364
|
-
raise typer.Exit(1)
|
365
|
-
shutil.rmtree(str(installation_path), ignore_errors=True)
|
366
|
-
copy_tree_normalizing_gitdir(root, installation_path)
|
367
|
-
shutil.rmtree(str(installation_path / 'build'), ignore_errors=True)
|
368
|
-
shutil.rmtree(str(installation_path / '.box'), ignore_errors=True)
|
369
|
-
shutil.rmtree(str(installation_path / '.local.rbx'), ignore_errors=True)
|
333
|
+
raise typer.Exit(1)
|
334
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
335
|
+
copy_tree_normalizing_gitdir(src, dest, update=update)
|
370
336
|
|
337
|
+
# Override the uri of the preset.
|
338
|
+
if override_uri is not None:
|
339
|
+
preset.uri = override_uri
|
340
|
+
(dest / 'preset.rbx.yml').write_text(utils.model_to_yaml(preset))
|
371
341
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
342
|
+
# Clean up all cache and left over directories before copying
|
343
|
+
# to avoid conflicts.
|
344
|
+
shutil.rmtree(str(dest / 'build'), ignore_errors=True)
|
345
|
+
shutil.rmtree(str(dest / '.local.rbx'), ignore_errors=True)
|
346
|
+
|
347
|
+
if preset.contest is not None:
|
348
|
+
_clean_copied_contest_dir(dest / preset.contest)
|
349
|
+
if preset.problem is not None:
|
350
|
+
_clean_copied_problem_dir(dest / preset.problem)
|
377
351
|
|
352
|
+
_clean_copied_package_dir(dest)
|
378
353
|
|
379
|
-
|
354
|
+
|
355
|
+
def _install_preset_from_remote(
|
356
|
+
fetch_info: PresetFetchInfo,
|
357
|
+
dest: pathlib.Path,
|
358
|
+
ensure_contest: bool = False,
|
359
|
+
ensure_problem: bool = False,
|
360
|
+
update: bool = False,
|
361
|
+
):
|
380
362
|
import git
|
381
363
|
|
382
364
|
assert fetch_info.fetch_uri is not None
|
@@ -387,37 +369,176 @@ def install_from_remote(fetch_info: PresetFetchInfo, force: bool = False) -> str
|
|
387
369
|
git.Repo.clone_from(fetch_info.fetch_uri, d)
|
388
370
|
pd = pathlib.Path(d)
|
389
371
|
if fetch_info.inner_dir:
|
390
|
-
pd = pd / fetch_info.inner_dir
|
391
372
|
console.console.print(
|
392
373
|
f'Installing preset from [item]{fetch_info.inner_dir}[/item].'
|
393
374
|
)
|
394
|
-
|
395
|
-
|
375
|
+
pd = pd / fetch_info.inner_dir
|
376
|
+
_install_preset_from_dir(
|
377
|
+
pd,
|
378
|
+
dest,
|
379
|
+
ensure_contest,
|
380
|
+
ensure_problem,
|
381
|
+
override_uri=fetch_info.fetch_uri,
|
382
|
+
update=update,
|
383
|
+
)
|
396
384
|
|
397
|
-
|
398
|
-
|
399
|
-
|
385
|
+
|
386
|
+
def _install_preset_from_local_dir(
|
387
|
+
fetch_info: PresetFetchInfo,
|
388
|
+
dest: pathlib.Path,
|
389
|
+
ensure_contest: bool = False,
|
390
|
+
ensure_problem: bool = False,
|
391
|
+
update: bool = False,
|
392
|
+
):
|
393
|
+
pd = pathlib.Path(fetch_info.inner_dir)
|
394
|
+
preset = _get_preset_yaml(pd)
|
395
|
+
console.console.print(
|
396
|
+
f'Installing local preset [item]{preset.name}[/item] into [item]{dest}[/item]...'
|
397
|
+
)
|
398
|
+
_install_preset_from_dir(
|
399
|
+
pd,
|
400
|
+
dest,
|
401
|
+
ensure_contest,
|
402
|
+
ensure_problem,
|
403
|
+
override_uri=str(pd.resolve()),
|
404
|
+
update=update,
|
405
|
+
)
|
400
406
|
|
401
407
|
|
402
|
-
def
|
403
|
-
|
408
|
+
def _install_preset_from_resources(
|
409
|
+
fetch_info: PresetFetchInfo,
|
410
|
+
dest: pathlib.Path,
|
411
|
+
ensure_contest: bool = False,
|
412
|
+
ensure_problem: bool = False,
|
413
|
+
update: bool = False,
|
404
414
|
):
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
415
|
+
rsrc_preset_path = get_default_app_path() / 'presets' / fetch_info.name
|
416
|
+
if not rsrc_preset_path.exists():
|
417
|
+
return False
|
418
|
+
yaml_path = rsrc_preset_path / 'preset.rbx.yml'
|
419
|
+
if not yaml_path.is_file():
|
420
|
+
return False
|
421
|
+
console.console.print(
|
422
|
+
f'Installing preset [item]{fetch_info.name}[/item] from resources...'
|
423
|
+
)
|
424
|
+
_install_preset_from_dir(
|
425
|
+
rsrc_preset_path,
|
426
|
+
dest,
|
427
|
+
ensure_contest,
|
428
|
+
ensure_problem,
|
429
|
+
override_uri=str(rsrc_preset_path.resolve()),
|
430
|
+
update=update,
|
431
|
+
)
|
432
|
+
return True
|
433
|
+
|
434
|
+
|
435
|
+
def _install_preset_from_fetch_info(
|
436
|
+
fetch_info: PresetFetchInfo,
|
437
|
+
dest: pathlib.Path,
|
438
|
+
ensure_contest: bool = False,
|
439
|
+
ensure_problem: bool = False,
|
440
|
+
update: bool = False,
|
441
|
+
):
|
442
|
+
if fetch_info.is_remote():
|
443
|
+
_install_preset_from_remote(
|
444
|
+
fetch_info,
|
445
|
+
dest,
|
446
|
+
ensure_contest=ensure_contest,
|
447
|
+
ensure_problem=ensure_problem,
|
448
|
+
update=update,
|
449
|
+
)
|
450
|
+
return
|
451
|
+
if fetch_info.is_local_dir():
|
452
|
+
_install_preset_from_local_dir(
|
453
|
+
fetch_info,
|
454
|
+
dest,
|
455
|
+
ensure_contest=ensure_contest,
|
456
|
+
ensure_problem=ensure_problem,
|
457
|
+
update=update,
|
458
|
+
)
|
459
|
+
return
|
460
|
+
if _install_preset_from_resources(
|
461
|
+
fetch_info,
|
462
|
+
dest,
|
463
|
+
ensure_contest=ensure_contest,
|
464
|
+
ensure_problem=ensure_problem,
|
465
|
+
update=update,
|
466
|
+
):
|
467
|
+
return
|
468
|
+
console.console.print(
|
469
|
+
f'[error]Preset [item]{fetch_info.name}[/item] not found.[/error]'
|
470
|
+
)
|
471
|
+
raise typer.Exit(1)
|
472
|
+
|
473
|
+
|
474
|
+
def install_preset_at_package(fetch_info: PresetFetchInfo, dest_pkg: pathlib.Path):
|
475
|
+
_install_preset_from_fetch_info(fetch_info, dest_pkg / '.local.rbx')
|
476
|
+
|
477
|
+
|
478
|
+
def install_contest(
|
479
|
+
dest_pkg: pathlib.Path, fetch_info: Optional[PresetFetchInfo] = None
|
480
|
+
):
|
481
|
+
if fetch_info is not None:
|
482
|
+
_install_preset_from_fetch_info(
|
483
|
+
fetch_info,
|
484
|
+
dest_pkg / '.local.rbx',
|
485
|
+
ensure_contest=True,
|
486
|
+
)
|
487
|
+
preset = get_active_preset(dest_pkg)
|
488
|
+
preset_path = _find_local_preset(dest_pkg)
|
489
|
+
assert preset_path is not None
|
490
|
+
if preset.contest is None:
|
491
|
+
console.console.print(
|
492
|
+
f'[error]Preset [item]{preset.name}[/item] does not have a contest package definition.[/error]'
|
493
|
+
)
|
494
|
+
raise typer.Exit(1)
|
495
|
+
|
496
|
+
console.console.print(
|
497
|
+
f'Installing contest from [item]{preset_path / preset.contest}[/item] to [item]{dest_pkg}[/item]...'
|
498
|
+
)
|
499
|
+
shutil.copytree(
|
500
|
+
str(preset_path / preset.contest),
|
501
|
+
str(dest_pkg),
|
502
|
+
dirs_exist_ok=True,
|
503
|
+
)
|
504
|
+
_clean_copied_contest_dir(dest_pkg, delete_local_rbx=False)
|
505
|
+
|
506
|
+
|
507
|
+
def install_problem(
|
508
|
+
dest_pkg: pathlib.Path, fetch_info: Optional[PresetFetchInfo] = None
|
509
|
+
):
|
510
|
+
if fetch_info is not None:
|
511
|
+
_install_preset_from_fetch_info(
|
512
|
+
fetch_info,
|
513
|
+
dest_pkg / '.local.rbx',
|
514
|
+
ensure_problem=True,
|
515
|
+
)
|
516
|
+
preset = get_active_preset(dest_pkg)
|
517
|
+
preset_path = _find_local_preset(dest_pkg)
|
518
|
+
assert preset_path is not None
|
519
|
+
if preset.problem is None:
|
520
|
+
console.console.print(
|
521
|
+
f'[error]Preset [item]{preset.name}[/item] does not have a problem package definition.[/error]'
|
522
|
+
)
|
523
|
+
raise typer.Exit(1)
|
524
|
+
|
525
|
+
console.console.print(
|
526
|
+
f'Installing problem from [item]{preset_path / preset.problem}[/item] to [item]{dest_pkg}[/item]...'
|
527
|
+
)
|
528
|
+
shutil.copytree(
|
529
|
+
str(preset_path / preset.problem),
|
530
|
+
str(dest_pkg),
|
531
|
+
dirs_exist_ok=True,
|
532
|
+
)
|
533
|
+
_clean_copied_problem_dir(dest_pkg)
|
414
534
|
|
415
|
-
preset = get_installed_preset(preset_name, root)
|
416
535
|
|
417
|
-
|
536
|
+
def generate_lock(root: pathlib.Path = pathlib.Path()):
|
537
|
+
preset = get_active_preset(root)
|
538
|
+
|
539
|
+
tracked_assets = _get_preset_tracked_assets(root, is_contest=_is_contest(root))
|
418
540
|
preset_lock = PresetLock(
|
419
|
-
name=preset.name
|
420
|
-
uri=preset.uri,
|
541
|
+
name=preset.name,
|
421
542
|
assets=_build_package_locked_assets(tracked_assets, root),
|
422
543
|
)
|
423
544
|
|
@@ -428,7 +549,7 @@ def generate_lock(
|
|
428
549
|
|
429
550
|
|
430
551
|
def _sync(try_update: bool = False):
|
431
|
-
preset_lock =
|
552
|
+
preset_lock = _get_preset_lock()
|
432
553
|
if preset_lock is None:
|
433
554
|
console.console.print(
|
434
555
|
'[error]Package does not have a [item].preset.lock.yml[/item] file and thus cannot be synced.[/error]'
|
@@ -438,28 +559,22 @@ def _sync(try_update: bool = False):
|
|
438
559
|
)
|
439
560
|
raise typer.Exit(1)
|
440
561
|
|
441
|
-
|
442
|
-
|
443
|
-
if installed_preset is None:
|
444
|
-
if not try_update or preset_lock.uri is None:
|
445
|
-
console.console.print(
|
446
|
-
f'[error]Preset [item]{preset_lock.preset_name}[/item] is not installed. Install it before trying to update.'
|
447
|
-
)
|
448
|
-
raise typer.Exit(1)
|
449
|
-
install(preset_lock.uri)
|
450
|
-
elif should_update:
|
451
|
-
update(preset_lock.name)
|
562
|
+
if try_update:
|
563
|
+
update()
|
452
564
|
|
453
565
|
_copy_updated_assets(
|
454
|
-
preset_lock.preset_name,
|
455
566
|
preset_lock,
|
456
567
|
is_contest=_is_contest(),
|
457
568
|
)
|
458
|
-
generate_lock(
|
569
|
+
generate_lock()
|
570
|
+
|
459
571
|
|
572
|
+
def copy_tree_normalizing_gitdir(
|
573
|
+
src_path: pathlib.Path, dst_path: pathlib.Path, update: bool = False
|
574
|
+
):
|
575
|
+
from rbx.box import git_utils
|
460
576
|
|
461
|
-
|
462
|
-
shutil.copytree(str(src_path), str(dst_path))
|
577
|
+
shutil.copytree(str(src_path), str(dst_path), dirs_exist_ok=update)
|
463
578
|
if not (src_path / '.git').is_file():
|
464
579
|
return
|
465
580
|
|
@@ -522,79 +637,42 @@ def copy_local_preset(
|
|
522
637
|
)
|
523
638
|
|
524
639
|
|
525
|
-
@app.command(
|
526
|
-
|
527
|
-
)
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
):
|
534
|
-
|
535
|
-
|
640
|
+
@app.command('update', help='Update preset of current package')
|
641
|
+
def update():
|
642
|
+
preset = get_active_preset()
|
643
|
+
if _is_active_preset_nested():
|
644
|
+
console.console.print(
|
645
|
+
'[error]Your package is nested inside the active preset. Updating such a preset is not supported.[/error]'
|
646
|
+
)
|
647
|
+
return
|
648
|
+
if not _is_installed_preset():
|
649
|
+
console.console.print(
|
650
|
+
'[error]Your active preset is not installed in a [item].local.rbx[/item] directory. Updating such a preset is not supported.[/error]'
|
651
|
+
)
|
652
|
+
return
|
653
|
+
if preset.uri is None:
|
654
|
+
console.console.print(
|
655
|
+
f'[error]Preset [item]{preset.name}[/item] is not updateable because it does not have a remote URI.'
|
656
|
+
)
|
536
657
|
return
|
537
658
|
|
538
|
-
|
539
|
-
if fetch_info is None:
|
540
|
-
console.console.print(f'[error] Preset with URI {uri} not found.[/error]')
|
541
|
-
raise typer.Exit(1)
|
542
|
-
if not fetch_info.is_local_dir() and not fetch_info.is_remote():
|
543
|
-
console.console.print(f'[error]URI {uri} is invalid.[/error]')
|
544
|
-
raise typer.Exit(1)
|
545
|
-
if fetch_info.is_remote():
|
546
|
-
install_from_remote(fetch_info)
|
547
|
-
else:
|
548
|
-
install_from_local_dir(fetch_info)
|
659
|
+
import questionary
|
549
660
|
|
661
|
+
console.console.print(
|
662
|
+
f'Updating preset [item]{preset.name}[/item] from [item]{preset.uri}[/item]...'
|
663
|
+
)
|
664
|
+
if not questionary.confirm(
|
665
|
+
'Updating local preset from remote will remove all custom changes you made to the preset.',
|
666
|
+
default=False,
|
667
|
+
).ask():
|
668
|
+
return
|
550
669
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
)
|
557
|
-
if not name:
|
558
|
-
presets = _find_installed_presets()
|
559
|
-
else:
|
560
|
-
presets = [name]
|
561
|
-
|
562
|
-
for preset_name in presets:
|
563
|
-
if preset_name == LOCAL:
|
564
|
-
import questionary
|
565
|
-
|
566
|
-
if not questionary.confirm(
|
567
|
-
'Updating local preset will remove all custom changes you made to the preset.',
|
568
|
-
default=False,
|
569
|
-
).ask():
|
570
|
-
continue
|
571
|
-
|
572
|
-
preset = get_installed_preset_or_null(preset_name)
|
573
|
-
if preset is None:
|
574
|
-
console.console.print(
|
575
|
-
f'[error]Preset [item]{preset_name}[/item] is not installed.'
|
576
|
-
)
|
577
|
-
continue
|
578
|
-
if preset.uri is None:
|
579
|
-
console.console.print(
|
580
|
-
f'Skipping preset [item]{preset_name}[/item], not remote.'
|
581
|
-
)
|
582
|
-
continue
|
583
|
-
install_from_remote(preset.fetch_info, force=True)
|
584
|
-
|
585
|
-
if preset_name == LOCAL:
|
586
|
-
# Get global path to the preset.
|
587
|
-
preset_path = get_preset_installation_path(preset.name)
|
588
|
-
dest_path = '.local.rbx'
|
589
|
-
shutil.rmtree(dest_path, ignore_errors=True)
|
590
|
-
shutil.copytree(preset_path, dest_path)
|
591
|
-
console.console.print(
|
592
|
-
'[success]Local preset updated successfully.[/success]'
|
593
|
-
)
|
594
|
-
else:
|
595
|
-
console.console.print(
|
596
|
-
f'[success]Preset [item]{preset_name}[/item] updated successfully.[/success]'
|
597
|
-
)
|
670
|
+
preset_path = _find_local_preset(pathlib.Path.cwd())
|
671
|
+
assert preset_path is not None
|
672
|
+
_install_preset_from_fetch_info(preset.fetch_info, dest=preset_path, update=True)
|
673
|
+
console.console.print(
|
674
|
+
f'[success]Preset [item]{preset.name}[/item] updated successfully.[/success]'
|
675
|
+
)
|
598
676
|
|
599
677
|
|
600
678
|
@app.command(
|
@@ -620,16 +698,19 @@ def sync(
|
|
620
698
|
'lock', help='Generate a lock for this package, based on a existing preset.'
|
621
699
|
)
|
622
700
|
@cd.within_closest_package
|
623
|
-
def lock(
|
624
|
-
preset: Annotated[
|
625
|
-
Optional[str],
|
626
|
-
typer.Argument(
|
627
|
-
help='Preset to generate a lock for. If unset, will default to the one in the existing .preset-lock.yml.',
|
628
|
-
),
|
629
|
-
] = None,
|
630
|
-
):
|
701
|
+
def lock():
|
631
702
|
_check_is_valid_package()
|
632
|
-
generate_lock(
|
703
|
+
generate_lock()
|
704
|
+
|
705
|
+
|
706
|
+
@app.command('ls', help='List details about the active preset.')
|
707
|
+
@cd.within_closest_package
|
708
|
+
def ls():
|
709
|
+
preset = get_active_preset()
|
710
|
+
preset_path = _find_local_preset(pathlib.Path.cwd())
|
711
|
+
console.console.print(f'Preset: [item]{preset.name}[/item]')
|
712
|
+
console.console.print(f'Path: {preset_path}')
|
713
|
+
console.console.print(f'URI: {preset.uri}')
|
633
714
|
|
634
715
|
|
635
716
|
@app.callback()
|