Skip to content

Commit b9ad3fd

Browse files
mzuennimpsijm
andauthored
Legacy export (#441)
* make legacy export an explicit command * [export] Add all the directories! * [export] Legacy: remove solution/ and problem_slide/ from export dir * [export] Make answer_validators/ not required Apparently, `bt validate` also doesn't require them? * prepend problem name * fix test * handle languages * use ngerman * add german to wsl * keep languages in sync * keep languages in sync * [export] Add comment explaining why name in problems.yaml can also be str * [doc] Improve singular/plural in explanation of `--languages` * [export][latex] Rename --languages flag to --lang * [test][export] Add assertions for which PDFs should be in the ZIPs * [test] TestContest.test_zip: also remove constituent zip files after test completes --------- Co-authored-by: Maarten Sijm <[email protected]>
1 parent 91513b4 commit b9ad3fd

File tree

16 files changed

+413
-252
lines changed

16 files changed

+413
-252
lines changed

.github/workflows/ci.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,6 @@ jobs:
5050
lmodern
5151
texlive-science
5252
latexmk
53+
texlive-lang-german
5354
- shell: wsl-bash {0}
5455
run: pytest

bin/config.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@
109109
"jobs": (os.cpu_count() or 1) // 2,
110110
"time": 600, # Used for `bt fuzz`
111111
"verbose": 0,
112-
"languages": None,
113112
}
114113

115114

@@ -120,7 +119,7 @@
120119
grep -Ev '^(h|jobs|time|verbose)$' | sed "s/^/'/;s/$/',/" | tr '\n' ' ' | sed 's/^/ARGS_LIST: Final[Sequence[str]] = [/;s/, $/]\n/'
121120
"""
122121
# fmt: off
123-
ARGS_LIST: Final[Sequence[str]] = ['1', 'add', 'all', 'answer', 'api', 'author', 'check_deterministic', 'clean', 'colors', 'contest', 'contest_id', 'contestname', 'cp', 'default_solution', 'depth', 'directory', 'error', 'force', 'force_build', 'generic', 'input', 'interaction', 'interactive', 'invalid', 'kattis', 'language', 'memory', 'more', 'move_to', 'no_bar', 'no_generate', 'no_solution', 'no_solutions', 'no_testcase_sanity_checks', 'no_time_limit', 'no_validators', 'no_visualizer', 'open', 'order', 'order_from_ccs', 'overview', 'password', 'post_freeze', 'problem', 'problemname', 'remove', 'reorder', 'samples', 'sanitizer', 'skel', 'skip', 'sort', 'submissions', 'table', 'testcases', 'time_limit', 'timeout', 'token', 'tree', 'type', 'username', 'valid_output', 'watch', 'web', 'write']
122+
ARGS_LIST: Final[Sequence[str]] = ['1', 'add', 'all', 'answer', 'api', 'author', 'check_deterministic', 'clean', 'colors', 'contest', 'contest_id', 'contestname', 'cp', 'default_solution', 'depth', 'directory', 'error', 'force', 'force_build', 'generic', 'input', 'interaction', 'interactive', 'invalid', 'kattis', 'lang', 'legacy', 'memory', 'more', 'move_to', 'no_bar', 'no_generate', 'no_solution', 'no_solutions', 'no_testcase_sanity_checks', 'no_time_limit', 'no_validators', 'no_visualizer', 'open', 'order', 'order_from_ccs', 'overview', 'password', 'post_freeze', 'problem', 'problemname', 'remove', 'reorder', 'samples', 'sanitizer', 'skel', 'skip', 'sort', 'submissions', 'table', 'testcases', 'time_limit', 'timeout', 'token', 'tree', 'type', 'username', 'valid_output', 'watch', 'web', 'write']
124123
# fmt: on
125124

126125

bin/export.py

+217-171
Large diffs are not rendered by default.

bin/latex.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -396,21 +396,21 @@ def build_problem_pdf(problem: "Problem", language: str, build_type=PdfType.PROB
396396

397397
def build_problem_pdfs(problem: "Problem", build_type=PdfType.PROBLEM, web=False):
398398
"""Build PDFs for various languages. If list of languages is specified,
399-
(either via config files or --language arguments), build those. Otherwise
399+
(either via config files or --lang arguments), build those. Otherwise
400400
build all languages for which there is a statement latex source.
401401
"""
402-
if config.args.languages is not None:
403-
for lang in config.args.languages:
402+
if config.args.lang is not None:
403+
for lang in config.args.lang:
404404
if lang not in problem.statement_languages:
405405
message(
406406
f"No statement source for language {lang}",
407407
problem.name,
408408
color_type=MessageType.FATAL,
409409
)
410-
languages = config.args.languages
410+
languages = config.args.lang
411411
else:
412412
languages = problem.statement_languages
413-
# For solutions or problem slides, filter for `<build_type>.<language>.tex` files that exist.
413+
# For solutions or problem slides, filter for `<build_type>.<lang>.tex` files that exist.
414414
if build_type != PdfType.PROBLEM:
415415
filtered_languages = []
416416
for lang in languages:
@@ -424,7 +424,7 @@ def build_problem_pdfs(problem: "Problem", build_type=PdfType.PROBLEM, web=False
424424
)
425425
languages = filtered_languages
426426
if config.args.watch and len(languages) > 1:
427-
fatal("--watch does not work with multiple languages. Please use --language")
427+
fatal("--watch does not work with multiple languages. Please use --lang")
428428
return all([build_problem_pdf(problem, lang, build_type, web) for lang in languages])
429429

430430

@@ -551,8 +551,8 @@ def build_contest_pdfs(contest, problems, tmpdir, lang=None, build_type=PdfType.
551551
message(
552552
"No statement language present in every problem.", contest, color_type=MessageType.FATAL
553553
)
554-
if config.args.languages is not None:
555-
languages = config.args.languages
554+
if config.args.lang is not None:
555+
languages = config.args.lang
556556
for lang in set(languages) - statement_languages:
557557
message(
558558
f"Unable to build all statements for language {lang}",
@@ -563,7 +563,7 @@ def build_contest_pdfs(contest, problems, tmpdir, lang=None, build_type=PdfType.
563563
languages = statement_languages
564564
if config.args.watch and len(languages) > 1:
565565
message(
566-
"--watch does not work with multiple languages. Please use --language",
566+
"--watch does not work with multiple languages. Please use --lang",
567567
contest,
568568
color_type=MessageType.FATAL,
569569
)

bin/skel.py

+1-7
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
# Local imports
66
import config
77
import latex
8-
from export import force_single_language
98
from problem import Problem
109
from util import *
1110
import contest
@@ -140,7 +139,7 @@ def new_problem():
140139
if config.args.problem:
141140
fatal("--problem does not work for new_problem.")
142141

143-
statement_languages = config.args.languages if config.args.languages else ["en"]
142+
statement_languages = config.args.lang if config.args.lang else ["en"]
144143
main_language = "en" if "en" in statement_languages else statement_languages[0]
145144

146145
problemname = {
@@ -288,11 +287,6 @@ def rename_problem(problem):
288287
data["name"] = newname
289288
write_yaml(data, problem_yaml)
290289

291-
# DOMjudge does not yet support multilingual problems.yaml files.
292-
statement_language = force_single_language([problem])
293-
if isinstance(newname, dict):
294-
newname = newname[statement_language]
295-
296290
problems_yaml = Path("problems.yaml")
297291
if problems_yaml.is_file():
298292
data = read_yaml(problems_yaml) or []

bin/tools.py

+72-45
Original file line numberDiff line numberDiff line change
@@ -343,9 +343,7 @@ def build_parser():
343343
action="store_true",
344344
help="Copy the output pdf instead of symlinking it.",
345345
)
346-
global_parser.add_argument(
347-
"--language", dest="languages", action="append", help="Set language."
348-
)
346+
global_parser.add_argument("--lang", nargs="+", help="Languages to include.")
349347

350348
subparsers = parser.add_subparsers(
351349
title="actions", dest="action", parser_class=SuppressingParser
@@ -808,12 +806,22 @@ def build_parser():
808806
action="store_true",
809807
help="Make a zip more following the kattis problemarchive.com format.",
810808
)
809+
zipparser.add_argument(
810+
"--legacy",
811+
action="store_true",
812+
help="Make a zip more following the legacy format.",
813+
)
811814
zipparser.add_argument("--no-solutions", action="store_true", help="Do not compile solutions")
812815

813816
# Build a zip with all samples.
814-
subparsers.add_parser(
817+
samplezipparser = subparsers.add_parser(
815818
"samplezip", parents=[global_parser], help="Create zip file of all samples."
816819
)
820+
samplezipparser.add_argument(
821+
"--legacy",
822+
action="store_true",
823+
help="Make a zip more following the legacy format.",
824+
)
817825

818826
subparsers.add_parser(
819827
"gitlabci",
@@ -831,6 +839,11 @@ def build_parser():
831839
action="store",
832840
help="Contest ID to use when writing to the API. Defaults to value of contest_id in contest.yaml.",
833841
)
842+
exportparser.add_argument(
843+
"--legacy",
844+
action="store_true",
845+
help="Make export more following the legacy format.",
846+
)
834847

835848
updateproblemsyamlparser = subparsers.add_parser(
836849
"update_problems_yaml",
@@ -846,6 +859,11 @@ def build_parser():
846859
action="store_true",
847860
help="Sort the problems by id.",
848861
)
862+
updateproblemsyamlparser.add_argument(
863+
"--legacy",
864+
action="store_true",
865+
help="Make problems.yaml more following the legacy format.",
866+
)
849867

850868
# Print the corresponding temporary directory.
851869
tmpparser = subparsers.add_parser(
@@ -1009,8 +1027,8 @@ def run_parsed_arguments(args):
10091027
sampleout = Path("samples.zip")
10101028
if level == "problem":
10111029
sampleout = problems[0].path / sampleout
1012-
statement_language = export.force_single_language(problems)
1013-
export.build_samples_zip(problems, sampleout, statement_language)
1030+
languages = export.select_languages(problems)
1031+
export.build_samples_zip(problems, sampleout, languages)
10141032
return
10151033

10161034
if action == "rename_problem":
@@ -1142,10 +1160,16 @@ def run_parsed_arguments(args):
11421160
config.args = old_args
11431161

11441162
if not config.args.kattis:
1145-
# Make sure that all problems use the same language for the PDFs
1146-
export.force_single_language(problems)
1147-
11481163
success &= latex.build_problem_pdfs(problem)
1164+
if not config.args.no_solutions:
1165+
success &= latex.build_problem_pdfs(
1166+
problem, build_type=latex.PdfType.SOLUTION
1167+
)
1168+
1169+
if problem.path.glob(str(latex.PdfType.PROBLEM_SLIDE.path("*"))):
1170+
success &= latex.build_problem_pdfs(
1171+
problem, build_type=latex.PdfType.PROBLEM_SLIDE
1172+
)
11491173

11501174
if not config.args.force:
11511175
success &= problem.validate_data(validate.Mode.INPUT, constraints={})
@@ -1159,10 +1183,8 @@ def run_parsed_arguments(args):
11591183
print(file=sys.stderr)
11601184

11611185
if action in ["export"]:
1162-
# Add contest PDF for only one language to DOMjudge
1163-
statement_language = export.force_single_language(problems)
1164-
1165-
export.export_contest_and_problems(problems, statement_language)
1186+
languages = export.select_languages(problems)
1187+
export.export_contest_and_problems(problems, languages)
11661188

11671189
if level == "problemset":
11681190
print(f"{Style.BRIGHT}CONTEST {contest}{Style.RESET_ALL}", file=sys.stderr)
@@ -1190,48 +1212,53 @@ def run_parsed_arguments(args):
11901212
)
11911213

11921214
if action in ["zip"]:
1193-
statement_language = None
1215+
languages = []
11941216
if not config.args.kattis:
1195-
# Add contest/solutions PDF for only one language to the zip file
1196-
statement_language = export.force_single_language(problems)
1217+
languages = export.select_languages(problems)
11971218

1198-
success &= latex.build_contest_pdfs(contest, problems, tmpdir, statement_language)
1199-
success &= latex.build_contest_pdfs(
1200-
contest, problems, tmpdir, statement_language, web=True
1201-
)
1202-
if not config.args.no_solutions:
1203-
success &= latex.build_contest_pdf(
1204-
contest,
1205-
problems,
1206-
tmpdir,
1207-
statement_language,
1208-
build_type=latex.PdfType.SOLUTION,
1209-
)
1210-
success &= latex.build_contest_pdf(
1211-
contest,
1212-
problems,
1213-
tmpdir,
1214-
statement_language,
1215-
build_type=latex.PdfType.SOLUTION,
1216-
web=True,
1217-
)
12181219
# Only build the problem slides if at least one problem has the TeX for it
12191220
slideglob = latex.PdfType.PROBLEM_SLIDE.path("*")
1220-
if any(problem.path.glob(str(slideglob)) for problem in problems):
1221-
success &= latex.build_contest_pdf(
1222-
contest,
1223-
problems,
1224-
tmpdir,
1225-
statement_language,
1226-
build_type=latex.PdfType.PROBLEM_SLIDE,
1221+
build_problem_slides = any(
1222+
problem.path.glob(str(slideglob)) for problem in problems
1223+
)
1224+
1225+
for language in languages:
1226+
success &= latex.build_contest_pdfs(contest, problems, tmpdir, language)
1227+
success &= latex.build_contest_pdfs(
1228+
contest, problems, tmpdir, language, web=True
12271229
)
1228-
else:
1230+
if not config.args.no_solutions:
1231+
success &= latex.build_contest_pdf(
1232+
contest,
1233+
problems,
1234+
tmpdir,
1235+
language,
1236+
build_type=latex.PdfType.SOLUTION,
1237+
)
1238+
success &= latex.build_contest_pdf(
1239+
contest,
1240+
problems,
1241+
tmpdir,
1242+
language,
1243+
build_type=latex.PdfType.SOLUTION,
1244+
web=True,
1245+
)
1246+
if build_problem_slides:
1247+
success &= latex.build_contest_pdf(
1248+
contest,
1249+
problems,
1250+
tmpdir,
1251+
language,
1252+
build_type=latex.PdfType.PROBLEM_SLIDE,
1253+
)
1254+
1255+
if not build_problem_slides:
12291256
log(f"No problem has {slideglob.name}, skipping problem slides")
12301257

12311258
outfile = contest + ".zip"
12321259
if config.args.kattis:
12331260
outfile = contest + "-kattis.zip"
1234-
export.build_contest_zip(problems, problem_zips, outfile, statement_language)
1261+
export.build_contest_zip(problems, problem_zips, outfile, languages)
12351262

12361263
if action in ["update_problems_yaml"]:
12371264
export.update_problems_yaml(

bin/upgrade.py

-1
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,6 @@ def _upgrade(problem_path: Path, bar: ProgressBar) -> None:
388388
upgrade_statement(problem_path, bar)
389389
upgrade_format_validators(problem_path, bar)
390390
upgrade_output_validators(problem_path, bar)
391-
# update .in.statement?
392391
upgrade_problem_yaml(problem_path, bar)
393392

394393
bar.done()

doc/commands.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ The flags below work for any subcommand:
5555
- `--no-bar`: Disable showing progress bars. This is useful when running in non-interactive contexts (such as CI jobs) or on platforms/terminals that don't handle the progress bars well.
5656
- `--error`/`-e`: show full output of failing commands using `--error`. The default is to show a short snippet only.
5757
- `--force-build`: Force rebuilding binaries instead of reusing cached version.
58-
- `--language <LANG>`: select a single language to use. `<LANG>` should be a language code like `en` or `nl`.
58+
- `--lang`: select languages to use for LaTeX commands. The languages should be specified by language codes like `en` or `nl`.
5959

6060
# Problem development
6161

doc/multiple_languages.md

+9-9
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,18 @@ Here, `LANG` is a two-letter language code, see
1515

1616
It is expected that the languages keys in the metadata and statement files agree.
1717

18-
The default language for BAPCtools is English, but multiple languages can be specified at various points of the tool, typically using the `--language` flag or configuration files.
18+
The default language for BAPCtools is English, but multiple languages can be specified at various points of the tool, typically using the `--lang` flag or configuration files.
1919

2020
## Creating a contest
2121

2222
In short,
2323

24-
1. configure `languages` in `.bapctools.yaml`.
24+
1. configure `lang` in `.bapctools.yaml`.
2525
2. add a skeleton for `problem.LANG.tex` in `skel/problem/statement`.
2626

27-
### Configure `language`
27+
### Configure `lang`
2828

29-
To create a contest supporting French, Dutch, and Luxembourgish, set the configurartion key `languages` to the list `['nl', 'fr', 'lt']`.
29+
To create a contest supporting French, Dutch, and Luxembourgish, set the configurartion key `lang` to the list `['nl', 'fr', 'lt']`.
3030
Configuration keys can be set in many ways, see **Personal configuration file** in the BAPCtools documentation, but an easy way is to create a new contest:
3131

3232
```sh
@@ -36,7 +36,7 @@ bt new_contest
3636
and then create or extend the file `<contestdirectory>/.bapctools.yaml` with
3737

3838
```yaml
39-
languages:
39+
lang:
4040
- nl
4141
- fr
4242
- lt
@@ -82,13 +82,13 @@ To create a problem,
8282
bt new_problem
8383
```
8484

85-
will look for the `languages` configuration (for instance, at contest level) and use that by default.
85+
will look for the `lang` configuration (for instance, at contest level) and use that by default.
8686
Thus, if the contest is set up as above, you need to do nothing extra.
8787

8888
With arguments, or outside of a contest directory,
8989

9090
```sh
91-
bt new_problem --language en --language fr
91+
bt new_problem --lang en fr
9292
```
9393

9494
creates a problem with two languages, English and French.
@@ -108,7 +108,7 @@ creates PDFs for every problem language statement `problem.xy.tex`.
108108
With arguments,
109109

110110
```sh
111-
bt pdf --language en --language fr
111+
bt pdf --lang en fr
112112
```
113113

114114
produces PDFs for English and French.
@@ -117,7 +117,7 @@ The resulting PDFs are named `<problemdirectory>/problem.xy.pdf`.
117117

118118
## Solution PDF
119119

120-
Similarly, `bt solutions [--language en --language fr]` creates
120+
Similarly, `bt solutions [--lang en fr]` creates
121121
`<problemdirectory>/solution.xy.pdf` for the given languages, defaulting to
122122
all available `solution.xy.tex` files.
123123

latex/lang/de.tex

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
\newcommand{\langbabel}{german}
1+
\newcommand{\langbabel}{ngerman}
22

33
% bapc.cls
44
\newcommand{\langblank}{Diese Seite wurde absichtlich leer gelassen.}

test/problems/identity/problem.yaml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
problem_format_version: 2023-07-draft
22
type: pass-fail
3-
name: Identity
3+
name:
4+
en: Identity
5+
de: Identität
46
credits:
57
authors: Ragnar Groot Koerkamp
68
uuid: a7d29d67-9b0b-4fd4-ae56-ab2cad5919ab

0 commit comments

Comments
 (0)