rbx.cp 0.5.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.
Files changed (164) hide show
  1. rbx/__init__.py +0 -0
  2. rbx/annotations.py +127 -0
  3. rbx/autoenum.py +333 -0
  4. rbx/box/__init__.py +0 -0
  5. rbx/box/builder.py +77 -0
  6. rbx/box/cd.py +37 -0
  7. rbx/box/checkers.py +134 -0
  8. rbx/box/code.py +185 -0
  9. rbx/box/compile.py +56 -0
  10. rbx/box/conftest.py +42 -0
  11. rbx/box/contest/__init__.py +0 -0
  12. rbx/box/contest/build_contest_statements.py +347 -0
  13. rbx/box/contest/contest_package.py +76 -0
  14. rbx/box/contest/contest_utils.py +20 -0
  15. rbx/box/contest/main.py +179 -0
  16. rbx/box/contest/schema.py +155 -0
  17. rbx/box/contest/statements.py +82 -0
  18. rbx/box/creation.py +72 -0
  19. rbx/box/download.py +64 -0
  20. rbx/box/environment.py +345 -0
  21. rbx/box/extensions.py +26 -0
  22. rbx/box/generators.py +478 -0
  23. rbx/box/generators_test.py +63 -0
  24. rbx/box/main.py +449 -0
  25. rbx/box/package.py +316 -0
  26. rbx/box/packaging/boca/extension.py +27 -0
  27. rbx/box/packaging/boca/packager.py +245 -0
  28. rbx/box/packaging/contest_main.py +82 -0
  29. rbx/box/packaging/main.py +68 -0
  30. rbx/box/packaging/packager.py +117 -0
  31. rbx/box/packaging/polygon/packager.py +320 -0
  32. rbx/box/packaging/polygon/test.py +81 -0
  33. rbx/box/packaging/polygon/xml_schema.py +106 -0
  34. rbx/box/presets/__init__.py +503 -0
  35. rbx/box/presets/fetch.py +70 -0
  36. rbx/box/presets/lock_schema.py +20 -0
  37. rbx/box/presets/schema.py +59 -0
  38. rbx/box/schema.py +394 -0
  39. rbx/box/solutions.py +792 -0
  40. rbx/box/solutions_test.py +41 -0
  41. rbx/box/statements/__init__.py +0 -0
  42. rbx/box/statements/build_statements.py +359 -0
  43. rbx/box/statements/builders.py +375 -0
  44. rbx/box/statements/joiners.py +113 -0
  45. rbx/box/statements/latex.py +47 -0
  46. rbx/box/statements/latex_jinja.py +214 -0
  47. rbx/box/statements/schema.py +138 -0
  48. rbx/box/stresses.py +292 -0
  49. rbx/box/stressing/__init__.py +0 -0
  50. rbx/box/stressing/finder_parser.py +359 -0
  51. rbx/box/stressing/generator_parser.py +258 -0
  52. rbx/box/testcases.py +54 -0
  53. rbx/box/ui/__init__.py +0 -0
  54. rbx/box/ui/captured_log.py +372 -0
  55. rbx/box/ui/css/app.tcss +48 -0
  56. rbx/box/ui/main.py +38 -0
  57. rbx/box/ui/run.py +209 -0
  58. rbx/box/validators.py +245 -0
  59. rbx/box/validators_test.py +15 -0
  60. rbx/checker.py +128 -0
  61. rbx/clone.py +197 -0
  62. rbx/config.py +271 -0
  63. rbx/conftest.py +38 -0
  64. rbx/console.py +27 -0
  65. rbx/create.py +37 -0
  66. rbx/edit.py +24 -0
  67. rbx/grading/__init__.py +0 -0
  68. rbx/grading/caching.py +356 -0
  69. rbx/grading/conftest.py +33 -0
  70. rbx/grading/judge/__init__.py +0 -0
  71. rbx/grading/judge/cacher.py +503 -0
  72. rbx/grading/judge/digester.py +35 -0
  73. rbx/grading/judge/sandbox.py +748 -0
  74. rbx/grading/judge/sandboxes/__init__.py +0 -0
  75. rbx/grading/judge/sandboxes/isolate.py +683 -0
  76. rbx/grading/judge/sandboxes/stupid_sandbox.py +310 -0
  77. rbx/grading/judge/sandboxes/timeit.py +217 -0
  78. rbx/grading/judge/storage.py +284 -0
  79. rbx/grading/judge/test.py +38 -0
  80. rbx/grading/judge/testiso.py +54 -0
  81. rbx/grading/steps.py +522 -0
  82. rbx/grading/steps_with_caching.py +59 -0
  83. rbx/grading/steps_with_caching_run_test.py +429 -0
  84. rbx/grading_utils.py +148 -0
  85. rbx/hydration.py +101 -0
  86. rbx/main.py +122 -0
  87. rbx/metadata.py +105 -0
  88. rbx/providers/__init__.py +43 -0
  89. rbx/providers/codeforces.py +73 -0
  90. rbx/providers/provider.py +26 -0
  91. rbx/resources/checkers/boilerplate.cpp +20 -0
  92. rbx/resources/default_config.json +48 -0
  93. rbx/resources/envs/default.rbx.yml +37 -0
  94. rbx/resources/envs/isolate.rbx.yml +37 -0
  95. rbx/resources/packagers/boca/checker.sh +43 -0
  96. rbx/resources/packagers/boca/compare +53 -0
  97. rbx/resources/packagers/boca/compile/c +172 -0
  98. rbx/resources/packagers/boca/compile/cc +173 -0
  99. rbx/resources/packagers/boca/compile/cpp +172 -0
  100. rbx/resources/packagers/boca/compile/java +194 -0
  101. rbx/resources/packagers/boca/compile/kt +155 -0
  102. rbx/resources/packagers/boca/compile/pas +172 -0
  103. rbx/resources/packagers/boca/compile/py2 +173 -0
  104. rbx/resources/packagers/boca/compile/py3 +173 -0
  105. rbx/resources/packagers/boca/run/c +128 -0
  106. rbx/resources/packagers/boca/run/cc +128 -0
  107. rbx/resources/packagers/boca/run/cpp +128 -0
  108. rbx/resources/packagers/boca/run/java +194 -0
  109. rbx/resources/packagers/boca/run/kt +159 -0
  110. rbx/resources/packagers/boca/run/py2 +166 -0
  111. rbx/resources/packagers/boca/run/py3 +166 -0
  112. rbx/resources/presets/default/contest/contest.rbx.yml +14 -0
  113. rbx/resources/presets/default/contest/statement/contest.rbx.tex +97 -0
  114. rbx/resources/presets/default/contest/statement/olymp.sty +250 -0
  115. rbx/resources/presets/default/contest/statement/template.rbx.tex +42 -0
  116. rbx/resources/presets/default/preset.rbx.yml +12 -0
  117. rbx/resources/presets/default/problem/.gitignore +6 -0
  118. rbx/resources/presets/default/problem/gen.cpp +9 -0
  119. rbx/resources/presets/default/problem/problem.rbx.yml +44 -0
  120. rbx/resources/presets/default/problem/random.py +3 -0
  121. rbx/resources/presets/default/problem/random.txt +2 -0
  122. rbx/resources/presets/default/problem/sols/main.cpp +9 -0
  123. rbx/resources/presets/default/problem/sols/slow.cpp +15 -0
  124. rbx/resources/presets/default/problem/sols/wa.cpp +9 -0
  125. rbx/resources/presets/default/problem/statement/olymp.sty +250 -0
  126. rbx/resources/presets/default/problem/statement/projecao.png +0 -0
  127. rbx/resources/presets/default/problem/statement/statement.rbx.tex +18 -0
  128. rbx/resources/presets/default/problem/statement/template.rbx.tex +89 -0
  129. rbx/resources/presets/default/problem/tests/samples/000.in +1 -0
  130. rbx/resources/presets/default/problem/tests/samples/001.in +1 -0
  131. rbx/resources/presets/default/problem/validator.cpp +16 -0
  132. rbx/resources/presets/default/problem/wcmp.cpp +34 -0
  133. rbx/resources/templates/template.cpp +19 -0
  134. rbx/run.py +45 -0
  135. rbx/schema.py +64 -0
  136. rbx/submit.py +61 -0
  137. rbx/submitors/__init__.py +18 -0
  138. rbx/submitors/codeforces.py +120 -0
  139. rbx/submitors/submitor.py +25 -0
  140. rbx/test.py +347 -0
  141. rbx/testcase.py +70 -0
  142. rbx/testcase_rendering.py +79 -0
  143. rbx/testdata/box1/gen1.cpp +7 -0
  144. rbx/testdata/box1/gen2.cpp +9 -0
  145. rbx/testdata/box1/genScript.py +2 -0
  146. rbx/testdata/box1/hard-tle.sol.cpp +26 -0
  147. rbx/testdata/box1/ole.cpp +17 -0
  148. rbx/testdata/box1/problem.rbx.yml +39 -0
  149. rbx/testdata/box1/re.sol.cpp +23 -0
  150. rbx/testdata/box1/sol.cpp +22 -0
  151. rbx/testdata/box1/tests/1.in +1 -0
  152. rbx/testdata/box1/tle-and-incorrect.sol.cpp +33 -0
  153. rbx/testdata/box1/tle.sol.cpp +35 -0
  154. rbx/testdata/box1/validator.cpp +11 -0
  155. rbx/testdata/box1/wa.sol.cpp +22 -0
  156. rbx/testdata/caching/executable.py +1 -0
  157. rbx/testdata/compatible +0 -0
  158. rbx/testing_utils.py +65 -0
  159. rbx/utils.py +162 -0
  160. rbx_cp-0.5.0.dist-info/LICENSE +201 -0
  161. rbx_cp-0.5.0.dist-info/METADATA +89 -0
  162. rbx_cp-0.5.0.dist-info/RECORD +164 -0
  163. rbx_cp-0.5.0.dist-info/WHEEL +4 -0
  164. rbx_cp-0.5.0.dist-info/entry_points.txt +4 -0
rbx/box/main.py ADDED
@@ -0,0 +1,449 @@
1
+ # flake8: noqa
2
+ from gevent import monkey
3
+
4
+ monkey.patch_all()
5
+
6
+ import shlex
7
+ import sys
8
+ import typing
9
+
10
+ from rbx.box.schema import CodeItem, ExpectedOutcome
11
+
12
+
13
+ import pathlib
14
+ import shutil
15
+ from typing import Annotated, List, Optional
16
+
17
+ import rich
18
+ import rich.prompt
19
+ import typer
20
+ import questionary
21
+
22
+ from rbx import annotations, config, console, utils
23
+ from rbx.box import (
24
+ builder,
25
+ cd,
26
+ creation,
27
+ download,
28
+ environment,
29
+ generators,
30
+ package,
31
+ compile,
32
+ presets,
33
+ stresses,
34
+ )
35
+ from rbx.box.contest import main as contest
36
+ from rbx.box.environment import VerificationLevel, get_environment_path
37
+ from rbx.box.packaging import main as packaging
38
+ from rbx.box.solutions import (
39
+ get_matching_solutions,
40
+ print_run_report,
41
+ run_and_print_interactive_solutions,
42
+ run_solutions,
43
+ )
44
+ from rbx.box.statements import build_statements
45
+ from rbx.box.ui import main as ui_pkg
46
+
47
+ app = typer.Typer(no_args_is_help=True, cls=annotations.AliasGroup)
48
+ app.add_typer(
49
+ build_statements.app,
50
+ name='statements, st',
51
+ cls=annotations.AliasGroup,
52
+ help='Manage statements.',
53
+ )
54
+ app.add_typer(
55
+ download.app,
56
+ name='download',
57
+ cls=annotations.AliasGroup,
58
+ help='Download an asset from supported repositories.',
59
+ )
60
+ app.add_typer(
61
+ presets.app, name='presets', cls=annotations.AliasGroup, help='Manage presets.'
62
+ )
63
+ app.add_typer(
64
+ packaging.app,
65
+ name='package, pkg',
66
+ cls=annotations.AliasGroup,
67
+ help='Build problem packages.',
68
+ )
69
+ app.add_typer(
70
+ contest.app, name='contest', cls=annotations.AliasGroup, help='Contest management.'
71
+ )
72
+ app.add_typer(
73
+ compile.app, name='compile', cls=annotations.AliasGroup, help='Compile assets.'
74
+ )
75
+
76
+
77
+ @app.command('ui', hidden=True)
78
+ @package.within_problem
79
+ def ui():
80
+ ui_pkg.start()
81
+
82
+
83
+ @app.command('edit, e', help='Open problem.rbx.yml in your default editor.')
84
+ @package.within_problem
85
+ def edit():
86
+ console.console.print('Opening problem definition in editor...')
87
+ # Call this function just to raise exception in case we're no in
88
+ # a problem package.
89
+ package.find_problem()
90
+ config.open_editor(package.find_problem_yaml() or pathlib.Path())
91
+
92
+
93
+ @app.command('build, b', help='Build all tests for the problem.')
94
+ @package.within_problem
95
+ def build(verification: environment.VerificationParam):
96
+ builder.build(verification=verification)
97
+
98
+
99
+ @app.command('verify, v', help='Build and verify all the tests for the problem.')
100
+ @package.within_problem
101
+ def verify(verification: environment.VerificationParam):
102
+ if not builder.verify(verification=verification):
103
+ console.console.print('[error]Verification failed, check the report.[/error]')
104
+
105
+
106
+ @app.command('run, r', help='Build and run solution(s).')
107
+ @package.within_problem
108
+ def run(
109
+ verification: environment.VerificationParam,
110
+ solution: Annotated[
111
+ Optional[str],
112
+ typer.Argument(
113
+ help='Path to solution to run. If not specified, will run all solutions.'
114
+ ),
115
+ ] = None,
116
+ outcome: Optional[str] = typer.Option(
117
+ None,
118
+ '--outcome',
119
+ '-o',
120
+ help='Include only solutions whose expected outcomes intersect with this.',
121
+ ),
122
+ check: bool = typer.Option(
123
+ True,
124
+ '--nocheck',
125
+ flag_value=False,
126
+ help='Whether to not build outputs for tests and run checker.',
127
+ ),
128
+ detailed: bool = typer.Option(
129
+ False,
130
+ '--detailed',
131
+ '-d',
132
+ help='Whether to print a detailed view of the tests using tables.',
133
+ ),
134
+ ):
135
+ main_solution = package.get_main_solution()
136
+ if check and main_solution is None:
137
+ console.console.print(
138
+ '[warning]No main solution found, running without checkers.[/warning]'
139
+ )
140
+ check = False
141
+
142
+ builder.build(verification=verification, output=check)
143
+
144
+ with utils.StatusProgress('Running solutions...') as s:
145
+ tracked_solutions = None
146
+ if outcome is not None:
147
+ tracked_solutions = {
148
+ str(solution.path)
149
+ for solution in get_matching_solutions(ExpectedOutcome(outcome))
150
+ }
151
+ if solution:
152
+ tracked_solutions = {solution}
153
+ solution_result = run_solutions(
154
+ progress=s,
155
+ tracked_solutions=tracked_solutions,
156
+ check=check,
157
+ group_first=detailed,
158
+ verification=VerificationLevel(verification),
159
+ )
160
+
161
+ console.console.print()
162
+ console.console.rule('[status]Run report[/status]', style='status')
163
+ print_run_report(
164
+ solution_result,
165
+ console.console,
166
+ verification,
167
+ detailed=detailed,
168
+ )
169
+
170
+
171
+ @app.command(
172
+ 'irun, ir', help='Build and run solution(s) by passing testcases in the CLI.'
173
+ )
174
+ @package.within_problem
175
+ def irun(
176
+ verification: environment.VerificationParam,
177
+ solution: Annotated[
178
+ Optional[str],
179
+ typer.Argument(
180
+ help='Path to solution to run. If not specified, will run all solutions.'
181
+ ),
182
+ ] = None,
183
+ outcome: Optional[str] = typer.Option(
184
+ None,
185
+ '--outcome',
186
+ '-o',
187
+ help='Include only solutions whose expected outcomes intersect with this.',
188
+ ),
189
+ check: bool = typer.Option(
190
+ True,
191
+ '--nocheck',
192
+ flag_value=False,
193
+ help='Whether to not build outputs for tests and run checker.',
194
+ ),
195
+ generator: Optional[str] = typer.Option(
196
+ None,
197
+ '--generator',
198
+ '-g',
199
+ help='Generator call to use to generate a single test for execution.',
200
+ ),
201
+ print: bool = typer.Option(
202
+ False, '--print', '-p', help='Whether to print outputs to terminal.'
203
+ ),
204
+ ):
205
+ if not print:
206
+ console.console.print(
207
+ '[warning]Outputs will be written to files. If you wish to print them to the terminal, use the "-p" parameter.'
208
+ )
209
+ main_solution = package.get_main_solution()
210
+ if check and main_solution is None:
211
+ console.console.print(
212
+ '[warning]No main solution found, running without checkers.[/warning]'
213
+ )
214
+ check = False
215
+
216
+ tracked_solutions = None
217
+ if outcome is not None:
218
+ tracked_solutions = {
219
+ str(solution.path)
220
+ for solution in get_matching_solutions(ExpectedOutcome(outcome))
221
+ }
222
+ if solution:
223
+ tracked_solutions = {solution}
224
+ run_and_print_interactive_solutions(
225
+ tracked_solutions=tracked_solutions,
226
+ check=check,
227
+ verification=VerificationLevel(verification),
228
+ generator=generators.get_call_from_string(generator)
229
+ if generator is not None
230
+ else None,
231
+ print=print,
232
+ )
233
+
234
+
235
+ @app.command('create, c', help='Create a new problem package.')
236
+ @package.within_problem
237
+ def create(
238
+ name: str,
239
+ preset: Annotated[
240
+ Optional[str], typer.Option(help='Preset to use when creating the problem.')
241
+ ] = None,
242
+ ):
243
+ if preset is not None:
244
+ creation.create(name, preset=preset)
245
+ return
246
+ creation.create(name)
247
+
248
+
249
+ @app.command('stress', help='Run a stress test.')
250
+ @package.within_problem
251
+ def stress(
252
+ name: str,
253
+ generator_args: Annotated[
254
+ Optional[str],
255
+ typer.Option(
256
+ '--generator',
257
+ '-g',
258
+ help='Run generator [name] with these args.',
259
+ ),
260
+ ] = None,
261
+ finder: Annotated[
262
+ Optional[str],
263
+ typer.Option(
264
+ '--finder',
265
+ '-f',
266
+ help='Run a stress with this finder expression.',
267
+ ),
268
+ ] = None,
269
+ timeout: Annotated[
270
+ int, typer.Option(help='For how many seconds to run the stress test.')
271
+ ] = 10,
272
+ findings: Annotated[
273
+ int, typer.Option(help='How many breaking tests to look for.')
274
+ ] = 1,
275
+ verbose: bool = typer.Option(
276
+ False,
277
+ '-v',
278
+ '--verbose',
279
+ help='Whether to print verbose output for checkers and finders.',
280
+ ),
281
+ ):
282
+ if finder and not generator_args or generator_args and not finder:
283
+ console.console.print(
284
+ '[error]Options --generator/-g and --finder/-f should be specified together.'
285
+ )
286
+ raise typer.Exit(1)
287
+
288
+ with utils.StatusProgress('Running stress...') as s:
289
+ report = stresses.run_stress(
290
+ name,
291
+ timeout,
292
+ args=generator_args,
293
+ finder=finder,
294
+ findingsLimit=findings,
295
+ progress=s,
296
+ verbose=verbose,
297
+ )
298
+
299
+ stresses.print_stress_report(report)
300
+
301
+ if not report.findings:
302
+ return
303
+
304
+ # Add found tests.
305
+ res = rich.prompt.Confirm.ask(
306
+ 'Do you want to add the tests that were found to a test group?',
307
+ console=console.console,
308
+ )
309
+ if not res:
310
+ return
311
+ testgroup = None
312
+ while testgroup is None or testgroup:
313
+ groups_by_name = {
314
+ name: group
315
+ for name, group in package.get_test_groups_by_name().items()
316
+ if group.generatorScript is not None
317
+ and group.generatorScript.path.suffix == '.txt'
318
+ }
319
+
320
+ testgroup = questionary.select(
321
+ 'Choose the testgroup to add the tests to.\nOnly test groups that have a .txt generatorScript are shown below: ',
322
+ choices=list(groups_by_name) + ['(skip)'],
323
+ ).ask()
324
+
325
+ if testgroup not in groups_by_name:
326
+ break
327
+ try:
328
+ subgroup = groups_by_name[testgroup]
329
+ assert subgroup.generatorScript is not None
330
+ generator_script = pathlib.Path(subgroup.generatorScript.path)
331
+
332
+ finding_lines = []
333
+ for finding in report.findings:
334
+ line = finding.generator.name
335
+ if finding.generator.args is not None:
336
+ line = f'{line} {finding.generator.args}'
337
+ finding_lines.append(line)
338
+
339
+ with generator_script.open('a') as f:
340
+ stress_text = f'# Obtained by running `rbx {shlex.join(sys.argv[1:])}`'
341
+ finding_text = '\n'.join(finding_lines)
342
+ f.write(f'\n{stress_text}\n{finding_text}\n')
343
+
344
+ console.console.print(
345
+ f"Added [item]{len(report.findings)}[/item] tests to test group [item]{testgroup}[/item]'s generatorScript at [item]{subgroup.generatorScript.path}[/item]."
346
+ )
347
+ except typer.Exit:
348
+ continue
349
+ break
350
+
351
+
352
+ @app.command('environment, env', help='Set or show the current box environment.')
353
+ @package.within_problem
354
+ def environment_command(
355
+ env: Annotated[Optional[str], typer.Argument()] = None,
356
+ install_from: Annotated[
357
+ Optional[str],
358
+ typer.Option(
359
+ '--install',
360
+ '-i',
361
+ help='Whether to install this environment from the given file.',
362
+ ),
363
+ ] = None,
364
+ ):
365
+ if env is None:
366
+ cfg = config.get_config()
367
+ console.console.print(f'Current environment: [item]{cfg.boxEnvironment}[/item]')
368
+ return
369
+ if install_from is not None:
370
+ environment.install_environment(env, pathlib.Path(install_from))
371
+ if not get_environment_path(env).is_file():
372
+ console.console.print(
373
+ f'[error]Environment [item]{env}[/item] does not exist.[/error]'
374
+ )
375
+ raise typer.Exit(1)
376
+
377
+ cfg = config.get_config()
378
+ if env == cfg.boxEnvironment:
379
+ console.console.print(
380
+ f'Environment is already set to [item]{env}[/item].',
381
+ )
382
+ return
383
+ console.console.print(
384
+ f'Changing box environment from [item]{cfg.boxEnvironment}[/item] to [item]{env}[/item]...'
385
+ )
386
+ cfg.boxEnvironment = env
387
+ config.save_config(cfg)
388
+
389
+ # Also clear cache when changing environments.
390
+ clear()
391
+
392
+
393
+ @app.command(
394
+ 'activate',
395
+ help='Activate the environment of the current preset used by the package.',
396
+ )
397
+ @cd.within_closest_package
398
+ def activate():
399
+ preset_lock = presets.get_preset_lock()
400
+ if preset_lock is None:
401
+ console.console.print(
402
+ '[warning]No configured preset to be activated for this package.[/warning]'
403
+ )
404
+ raise typer.Exit(1)
405
+
406
+ preset = presets.get_installed_preset_or_null(preset_lock.preset_name)
407
+ if preset is None:
408
+ if preset_lock.uri is None:
409
+ console.console.print(
410
+ '[error]Preset is not installed. Install it manually, or specify a URI in [item].preset-lock.yml[/item].[/error]'
411
+ )
412
+ raise
413
+ presets.install(preset_lock.uri)
414
+
415
+ preset = presets.get_installed_preset(preset_lock.preset_name)
416
+ if preset.env is not None:
417
+ environment_command(preset.name)
418
+
419
+ console.console.print(f'[success]Preset [item]{preset.name}[/item] is activated.')
420
+
421
+
422
+ @app.command('languages', help='List the languages available in this environment')
423
+ @package.within_problem
424
+ def languages():
425
+ env = environment.get_environment()
426
+
427
+ console.console.print(
428
+ f'[success]There are [item]{len(env.languages)}[/item] language(s) available.'
429
+ )
430
+
431
+ for language in env.languages:
432
+ console.console.print(
433
+ f'[item]{language.name}[/item], aka [item]{language.readable_name or language.name}[/item]:'
434
+ )
435
+ console.console.print(language)
436
+ console.console.print()
437
+
438
+
439
+ @app.command('clear, clean', help='Clears cache and build directories.')
440
+ @package.within_problem
441
+ def clear():
442
+ console.console.print('Cleaning cache and build directories...')
443
+ shutil.rmtree('.box', ignore_errors=True)
444
+ shutil.rmtree('build', ignore_errors=True)
445
+
446
+
447
+ @app.callback()
448
+ def callback():
449
+ pass