diff --git a/Dockerfile b/Dockerfile index e9787418..daa50dde 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,7 @@ RUN apt-get update && \ python3-minimal \ python3-pip \ python3-plastex \ + python3-markdown \ python3-yaml \ sudo \ texlive-fonts-recommended \ diff --git a/README.md b/README.md index 4a5a3f23..0422e70e 100644 --- a/README.md +++ b/README.md @@ -207,13 +207,13 @@ The dependencies needed to *build/install* problemtools can be installed with: And the dependencies needed to *run* problemtools can be installed with: - sudo apt install ghostscript libgmpxx4ldbl python-minimal python-pkg-resources python-plastex python-yaml texlive-fonts-recommended texlive-lang-cyrillic texlive-latex-extra texlive-plain-generic tidy + sudo apt install ghostscript libgmpxx4ldbl python3-minimal python-pkg-resources python3-plastex python3-markdown python3-yaml texlive-fonts-recommended texlive-lang-cyrillic texlive-latex-extra texlive-plain-generic tidy ### Fedora On Fedora, these dependencies can be installed with: - sudo dnf install boost-regex gcc gmp-devel gmp-c++ python3 python3-pyyaml texlive-latex texlive-collection-fontsrecommended texlive-fancyhdr texlive-subfigure texlive-wrapfig texlive-import texlive-ulem texlive-xifthen texlive-overpic texlive-pbox tidy ghostscript + sudo dnf install boost-regex gcc gmp-devel gmp-c++ python3 python3-pyyaml python3-markdown texlive-latex texlive-collection-fontsrecommended texlive-fancyhdr texlive-subfigure texlive-wrapfig texlive-import texlive-ulem texlive-xifthen texlive-overpic texlive-pbox tidy ghostscript Followed by: diff --git a/admin/docker/Dockerfile.minimal b/admin/docker/Dockerfile.minimal index d856240d..f5c35d5b 100644 --- a/admin/docker/Dockerfile.minimal +++ b/admin/docker/Dockerfile.minimal @@ -24,6 +24,7 @@ RUN apt update && \ python3-minimal \ python3-yaml \ python3-plastex \ + python3-markdown \ texlive-fonts-recommended \ texlive-lang-cyrillic \ texlive-latex-extra \ diff --git a/debian/control b/debian/control index 3e9de205..54378448 100644 --- a/debian/control +++ b/debian/control @@ -8,7 +8,7 @@ Homepage: https://github.com/Kattis/problemtools Package: kattis-problemtools Architecture: any -Depends: ${shlibs:Depends}, ${python3:Depends}, ${misc:Depends}, python3-plastex, python-pkg-resources, texlive-plain-generic, texlive-fonts-recommended, texlive-latex-extra, texlive-lang-cyrillic, tidy, ghostscript +Depends: ${shlibs:Depends}, ${python3:Depends}, ${misc:Depends}, python3-plastex, python3-markdown, python-pkg-resources, texlive-plain-generic, texlive-fonts-recommended, texlive-latex-extra, texlive-lang-cyrillic, tidy, ghostscript Recommends: gcc, g++ Description: Kattis Problem Tools These are tools to manage and verify problem packages in the diff --git a/examples/different/problem_statement/problem.sv.md b/examples/different/problem_statement/problem.sv.md new file mode 100644 index 00000000..7bfe0378 --- /dev/null +++ b/examples/different/problem_statement/problem.sv.md @@ -0,0 +1,9 @@ +Skriv ett program som beräknar differensen mellan icke-negativa heltal. + +## Indata +Indatan består av flera testfall (minst \(1\) och som mest \(40\)), ett per rad. +Varje testfall består av ett par av heltal mellan \(0\) och \(10^{15}\) (inklusive). +Indatan avslutas med vid indatans slut (end of file). + +## Utdata +För varje par av heltal i indatan, skriv ut en rad med absolutbeloppet av deras differens. diff --git a/examples/echo/data/sample/1.ans b/examples/echo/data/sample/1.ans new file mode 100644 index 00000000..617c6fc3 --- /dev/null +++ b/examples/echo/data/sample/1.ans @@ -0,0 +1,3 @@ +hello +am +echo diff --git a/examples/echo/data/sample/1.in b/examples/echo/data/sample/1.in new file mode 100644 index 00000000..d108b406 --- /dev/null +++ b/examples/echo/data/sample/1.in @@ -0,0 +1,6 @@ +5 +hello +i +am +an +echo diff --git a/examples/echo/data/sample/2.ans b/examples/echo/data/sample/2.ans new file mode 100644 index 00000000..d373a5af --- /dev/null +++ b/examples/echo/data/sample/2.ans @@ -0,0 +1,5 @@ +only +these +words +are +correct diff --git a/examples/echo/data/sample/2.in b/examples/echo/data/sample/2.in new file mode 100644 index 00000000..7779d285 --- /dev/null +++ b/examples/echo/data/sample/2.in @@ -0,0 +1,11 @@ +10 +only +if +these +oddindexed +words +appear +are +you +correct +output diff --git a/examples/echo/data/sample/testdata.yaml b/examples/echo/data/sample/testdata.yaml new file mode 100644 index 00000000..8034585a --- /dev/null +++ b/examples/echo/data/sample/testdata.yaml @@ -0,0 +1,5 @@ +on_reject: continue +range: 0 0 +accept_score: 0 +grader_flags: first_error +input_validator_flags: nFive=0 diff --git a/examples/echo/data/secret/subtask1/1.ans b/examples/echo/data/secret/subtask1/1.ans new file mode 100644 index 00000000..bf8d586a --- /dev/null +++ b/examples/echo/data/secret/subtask1/1.ans @@ -0,0 +1,3 @@ +a +c +e diff --git a/examples/echo/data/secret/subtask1/1.in b/examples/echo/data/secret/subtask1/1.in new file mode 100644 index 00000000..bc799ff8 --- /dev/null +++ b/examples/echo/data/secret/subtask1/1.in @@ -0,0 +1,6 @@ +5 +a +b +c +d +e diff --git a/examples/echo/data/secret/subtask1/2.ans b/examples/echo/data/secret/subtask1/2.ans new file mode 100644 index 00000000..d65cd02c --- /dev/null +++ b/examples/echo/data/secret/subtask1/2.ans @@ -0,0 +1,3 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa diff --git a/examples/echo/data/secret/subtask1/2.in b/examples/echo/data/secret/subtask1/2.in new file mode 100644 index 00000000..294867e4 --- /dev/null +++ b/examples/echo/data/secret/subtask1/2.in @@ -0,0 +1,6 @@ +5 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc +bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa diff --git a/examples/echo/data/secret/subtask1/3.ans b/examples/echo/data/secret/subtask1/3.ans new file mode 100644 index 00000000..e05c6300 --- /dev/null +++ b/examples/echo/data/secret/subtask1/3.ans @@ -0,0 +1,3 @@ +testdata +fun +write diff --git a/examples/echo/data/secret/subtask1/3.in b/examples/echo/data/secret/subtask1/3.in new file mode 100644 index 00000000..bb472c64 --- /dev/null +++ b/examples/echo/data/secret/subtask1/3.in @@ -0,0 +1,6 @@ +5 +testdata +is +fun +to +write diff --git a/examples/echo/data/secret/subtask1/testdata.yaml b/examples/echo/data/secret/subtask1/testdata.yaml new file mode 100644 index 00000000..9d5d1922 --- /dev/null +++ b/examples/echo/data/secret/subtask1/testdata.yaml @@ -0,0 +1,5 @@ +on_reject: break +accept_score: 1 +range: 0 1 +grader_flags: min +input_validator_flags: nFive=1 diff --git a/examples/echo/data/secret/subtask2/01.ans b/examples/echo/data/secret/subtask2/01.ans new file mode 100644 index 00000000..9ae9f03f --- /dev/null +++ b/examples/echo/data/secret/subtask2/01.ans @@ -0,0 +1 @@ +lfqxnsarqnsonfnrspgcdnvhcdacwtxqxpfqjbdhworqtcscpzxyaevsktuzjdbkhxlaqmc diff --git a/examples/echo/data/secret/subtask2/01.in b/examples/echo/data/secret/subtask2/01.in new file mode 100644 index 00000000..33733149 --- /dev/null +++ b/examples/echo/data/secret/subtask2/01.in @@ -0,0 +1,2 @@ +1 +lfqxnsarqnsonfnrspgcdnvhcdacwtxqxpfqjbdhworqtcscpzxyaevsktuzjdbkhxlaqmc diff --git a/examples/echo/data/secret/subtask2/02.ans b/examples/echo/data/secret/subtask2/02.ans new file mode 100644 index 00000000..0c734759 --- /dev/null +++ b/examples/echo/data/secret/subtask2/02.ans @@ -0,0 +1 @@ +sadswmlfyrzyuzurmawdpqjeorkjxrbigpdgurpbrpwvnwhpwzgsgwgrenbytkhyydepspqkmwekbcuyltbscfynzhjm diff --git a/examples/echo/data/secret/subtask2/02.in b/examples/echo/data/secret/subtask2/02.in new file mode 100644 index 00000000..732d96ad --- /dev/null +++ b/examples/echo/data/secret/subtask2/02.in @@ -0,0 +1,3 @@ +2 +sadswmlfyrzyuzurmawdpqjeorkjxrbigpdgurpbrpwvnwhpwzgsgwgrenbytkhyydepspqkmwekbcuyltbscfynzhjm +haieueifbuuuxpkcxojnepciypyttbnijwufdjopamhxv diff --git a/examples/echo/data/secret/subtask2/03.ans b/examples/echo/data/secret/subtask2/03.ans new file mode 100644 index 00000000..7cab9ade --- /dev/null +++ b/examples/echo/data/secret/subtask2/03.ans @@ -0,0 +1,2 @@ +ukne +hzilgamqhyahjclmuyjvejunnanjuxflviaeiyzpczfojvcodlujutzwbsyavodbuosqoatpagssnogkynjsm diff --git a/examples/echo/data/secret/subtask2/03.in b/examples/echo/data/secret/subtask2/03.in new file mode 100644 index 00000000..d46990b0 --- /dev/null +++ b/examples/echo/data/secret/subtask2/03.in @@ -0,0 +1,4 @@ +3 +ukne +zliqslrpwrnlnlnogfcnswqbnjbqxrlmtjebyllyolrrhzfspgumodkfnyomn +hzilgamqhyahjclmuyjvejunnanjuxflviaeiyzpczfojvcodlujutzwbsyavodbuosqoatpagssnogkynjsm diff --git a/examples/echo/data/secret/subtask2/04.ans b/examples/echo/data/secret/subtask2/04.ans new file mode 100644 index 00000000..c5188aab --- /dev/null +++ b/examples/echo/data/secret/subtask2/04.ans @@ -0,0 +1,2 @@ +vbrhnsjugbscwghs +wrqujghiseuvcrzomktwkljyrvsngudxtjzjankgphmcwewcklgfqrzvscpjjmigjpuid diff --git a/examples/echo/data/secret/subtask2/04.in b/examples/echo/data/secret/subtask2/04.in new file mode 100644 index 00000000..97c8f674 --- /dev/null +++ b/examples/echo/data/secret/subtask2/04.in @@ -0,0 +1,5 @@ +4 +vbrhnsjugbscwghs +dezpmjwllgbfirkbrgyznqdhfzyxggwkraopba +wrqujghiseuvcrzomktwkljyrvsngudxtjzjankgphmcwewcklgfqrzvscpjjmigjpuid +bglrjlbelhezdfciayabmsusdcrsnrzar diff --git a/examples/echo/data/secret/subtask2/05.ans b/examples/echo/data/secret/subtask2/05.ans new file mode 100644 index 00000000..7c03c2cd --- /dev/null +++ b/examples/echo/data/secret/subtask2/05.ans @@ -0,0 +1,3 @@ +irklktbawab +fclqvhrogqr +qqmzocleumdgvlakmuchrdyefveruoffmmqgqhlabsmvqgpkdmhqwzeh diff --git a/examples/echo/data/secret/subtask2/05.in b/examples/echo/data/secret/subtask2/05.in new file mode 100644 index 00000000..7a71eac6 --- /dev/null +++ b/examples/echo/data/secret/subtask2/05.in @@ -0,0 +1,6 @@ +5 +irklktbawab +jvzuldycrv +fclqvhrogqr +perenlqdlrjadjjshtcjjgtxisykhnvfzerpopetifrulhuxb +qqmzocleumdgvlakmuchrdyefveruoffmmqgqhlabsmvqgpkdmhqwzeh diff --git a/examples/echo/data/secret/subtask2/06.ans b/examples/echo/data/secret/subtask2/06.ans new file mode 100644 index 00000000..6ca720a6 --- /dev/null +++ b/examples/echo/data/secret/subtask2/06.ans @@ -0,0 +1,3 @@ +cpeylvrgilhhknuiqacizawqnhlwohatcfxcxrsrwwboplxzwffnmc +npsyxizmkmbhctrjvevjffwpivhfzjwbajeebbdwqparvsrmnypohqkyignenyfnolzaxrpcupof +shcsjdzoewvdaaubtpogvlfbvyzoxyqwonqvnfberoapbivdomlxngfpfcpwuknmzjmwthqacax diff --git a/examples/echo/data/secret/subtask2/06.in b/examples/echo/data/secret/subtask2/06.in new file mode 100644 index 00000000..186794cb --- /dev/null +++ b/examples/echo/data/secret/subtask2/06.in @@ -0,0 +1,7 @@ +6 +cpeylvrgilhhknuiqacizawqnhlwohatcfxcxrsrwwboplxzwffnmc +mvvfbckehiddcdgzahaqvcsygytwtkoticelntkfpeqihaxczolvk +npsyxizmkmbhctrjvevjffwpivhfzjwbajeebbdwqparvsrmnypohqkyignenyfnolzaxrpcupof +ttj +shcsjdzoewvdaaubtpogvlfbvyzoxyqwonqvnfberoapbivdomlxngfpfcpwuknmzjmwthqacax +auhkxxmljnnkzfcuxtqwdlosbrbplklwbidpnsvdaimaxxdnpuurfqylqybaxlybqbbknrcvfuvkwuvnqdak diff --git a/examples/echo/data/secret/subtask2/07.ans b/examples/echo/data/secret/subtask2/07.ans new file mode 100644 index 00000000..097d44c8 --- /dev/null +++ b/examples/echo/data/secret/subtask2/07.ans @@ -0,0 +1,4 @@ +vqbkewxjyflzwuzcprdvdmoquiqz +klseqgeziclrbkuflydmyxttlipwutnychfyuhcdbolikfmdqrxjbtlyfqazwaogqvbmgxnjcowzspliupjnk +oyibwjayadunwawrpjjtewsksrsvfpbqmfwkchisqh +vticcfq diff --git a/examples/echo/data/secret/subtask2/07.in b/examples/echo/data/secret/subtask2/07.in new file mode 100644 index 00000000..58490d91 --- /dev/null +++ b/examples/echo/data/secret/subtask2/07.in @@ -0,0 +1,8 @@ +7 +vqbkewxjyflzwuzcprdvdmoquiqz +nxiipmvrhjmmdpivdgchvtokirceudfoejawvdzrgiiizwuqotvhvttahmmbqvzkfwqvzklxuk +klseqgeziclrbkuflydmyxttlipwutnychfyuhcdbolikfmdqrxjbtlyfqazwaogqvbmgxnjcowzspliupjnk +gnmxpokvkotwddyqgnyihfwuvrfkhmlfaphmmyhjcgvuoocxxwmpgfozbhfwdnixlntspxxpujdbpwfl +oyibwjayadunwawrpjjtewsksrsvfpbqmfwkchisqh +tjhybleiurwulxvuxiaepnbfxthhguwkmjnhhjvsvuuqugqqlvhghzspigwhcwpj +vticcfq diff --git a/examples/echo/data/secret/subtask2/08.ans b/examples/echo/data/secret/subtask2/08.ans new file mode 100644 index 00000000..f8b7a88b --- /dev/null +++ b/examples/echo/data/secret/subtask2/08.ans @@ -0,0 +1,4 @@ +deqnzpbnsglenccnxbfftyozqkxkuxdsswoyxrpqzfrjw +ivlz +ufdykwdhtcuycogobloygqpdqvustnkhxlfqbxrwtencnamvplvjsoxefkkfysnjqtlsgvdmftmgxkzcvjonxftclnoeyi +hqgidntdapjijdbyffddxuxbgkbskljwtdbodbnaxfhvyqrqmhplu diff --git a/examples/echo/data/secret/subtask2/08.in b/examples/echo/data/secret/subtask2/08.in new file mode 100644 index 00000000..cc909aab --- /dev/null +++ b/examples/echo/data/secret/subtask2/08.in @@ -0,0 +1,9 @@ +8 +deqnzpbnsglenccnxbfftyozqkxkuxdsswoyxrpqzfrjw +lfekwxymhixfiywnkajsxhzhnsamfkpbnapdn +ivlz +vultxfpcljsnzhiuluotznaaxlnrqgowizkjpfknrpqjimvt +ufdykwdhtcuycogobloygqpdqvustnkhxlfqbxrwtencnamvplvjsoxefkkfysnjqtlsgvdmftmgxkzcvjonxftclnoeyi +gpbbrzgrxaffhkhszwqsnbhnfawfqsrrqnacdjspvdfbhguutebf +hqgidntdapjijdbyffddxuxbgkbskljwtdbodbnaxfhvyqrqmhplu +zzatbijlsrycjytezjqvnkycchtuugygrbouvonefwdiuyfd diff --git a/examples/echo/data/secret/subtask2/09.ans b/examples/echo/data/secret/subtask2/09.ans new file mode 100644 index 00000000..01e39aa8 --- /dev/null +++ b/examples/echo/data/secret/subtask2/09.ans @@ -0,0 +1,5 @@ +ywkmbqkkclwyvtwxdpcsehboqhoppqjvclxxvtcpsuodxczq +qcjncscqtrtlabzjcuksjtyhxzzkwhkiohusxuso +jqcbcdwsqoikrzvcitbzuqsluilgvdjjnjxjqfbjlnzslzukfcxgwgsgpuvvcvvaewkdcglrkx +pcgamvgaclmsgflrvvtubflyezombfkuowzixlqlpieljyxulvbmxdkaazudiltxaktbkfsjlbhpbkghdw +jabtuprvwrtxhoyqqjpoxwyeecsdfsdmf diff --git a/examples/echo/data/secret/subtask2/09.in b/examples/echo/data/secret/subtask2/09.in new file mode 100644 index 00000000..0cfed8a7 --- /dev/null +++ b/examples/echo/data/secret/subtask2/09.in @@ -0,0 +1,10 @@ +9 +ywkmbqkkclwyvtwxdpcsehboqhoppqjvclxxvtcpsuodxczq +wiswindhsujxyfospysxdhxcscwpcwsqwcmqfetxlyhtqwdbljggwexeogngnj +qcjncscqtrtlabzjcuksjtyhxzzkwhkiohusxuso +wdtcnphtacocstxq +jqcbcdwsqoikrzvcitbzuqsluilgvdjjnjxjqfbjlnzslzukfcxgwgsgpuvvcvvaewkdcglrkx +rvhxjnjbgneobaiokpgdcozeifokfzocj +pcgamvgaclmsgflrvvtubflyezombfkuowzixlqlpieljyxulvbmxdkaazudiltxaktbkfsjlbhpbkghdw +cghkfphphurvlfkggecrejynkimepdrfztbbuurhab +jabtuprvwrtxhoyqqjpoxwyeecsdfsdmf diff --git a/examples/echo/data/secret/subtask2/10.ans b/examples/echo/data/secret/subtask2/10.ans new file mode 100644 index 00000000..90877479 --- /dev/null +++ b/examples/echo/data/secret/subtask2/10.ans @@ -0,0 +1,5 @@ +xtrsypuaepzhccpetemqdhmcqxfodgdvratymblu +ahpeewdydhwnzroofgqosqpuogtksnxvgmcttqifkaesfynuqoyybgdlcdavgxpqzqpolmbfvbrsephlyetqzzssxr +shgumshytseusbxaltfupadvgsofzzynvyizkv +epzdnrhplcidomwd +duqrzupszqiepbdknabjyqiqwnzwnurnkzvyuekigteqmbviccczhzbyjtvdaoqxfwtqrypdfqjqhurpzpkgxwlvouprkhjcn diff --git a/examples/echo/data/secret/subtask2/10.in b/examples/echo/data/secret/subtask2/10.in new file mode 100644 index 00000000..eb17b61d --- /dev/null +++ b/examples/echo/data/secret/subtask2/10.in @@ -0,0 +1,11 @@ +10 +xtrsypuaepzhccpetemqdhmcqxfodgdvratymblu +ypthphgovonyeyvlhplxhoqokfsydnuthhalvigycjsvftsluubakxmsoiymxnuacfypkihbhbxrfqscaegd +ahpeewdydhwnzroofgqosqpuogtksnxvgmcttqifkaesfynuqoyybgdlcdavgxpqzqpolmbfvbrsephlyetqzzssxr +jwticnnpvgvundxanfypelksswwoteddqjgdkjmjalynmjkuimskqlzsbpxndhkcgrrywddahqcyxmpazmdscifxwcmkqbu +shgumshytseusbxaltfupadvgsofzzynvyizkv +rgidvfmdosdvmukttgrseffhmhwjjgrtmleoebfvulqbodxszbkhbrytandntcrspuv +epzdnrhplcidomwd +xpngzvutfhsdzxnbtyqxobg +duqrzupszqiepbdknabjyqiqwnzwnurnkzvyuekigteqmbviccczhzbyjtvdaoqxfwtqrypdfqjqhurpzpkgxwlvouprkhjcn +foxtfbqiywcbypfzrzaxbothhhevfghapr diff --git a/examples/echo/data/secret/subtask2/testdata.yaml b/examples/echo/data/secret/subtask2/testdata.yaml new file mode 100644 index 00000000..2343f355 --- /dev/null +++ b/examples/echo/data/secret/subtask2/testdata.yaml @@ -0,0 +1,5 @@ +on_reject: break +accept_score: 1 +range: 0 1 +grader_flags: min +input_validator_flags: nFive=0 diff --git a/examples/echo/data/secret/testdata.yaml b/examples/echo/data/secret/testdata.yaml new file mode 100644 index 00000000..e8135d50 --- /dev/null +++ b/examples/echo/data/secret/testdata.yaml @@ -0,0 +1,3 @@ +on_reject: continue +range: 0 2 +grader_flags: first_error accept_if_any_accepted diff --git a/examples/echo/data/testdata.yaml b/examples/echo/data/testdata.yaml new file mode 100644 index 00000000..6e832954 --- /dev/null +++ b/examples/echo/data/testdata.yaml @@ -0,0 +1,3 @@ +on_reject: continue +range: 0 2 +grader_flags: ignore_sample diff --git a/examples/echo/input_format_validators/validator/validator.cpp b/examples/echo/input_format_validators/validator/validator.cpp new file mode 100644 index 00000000..004f8de4 --- /dev/null +++ b/examples/echo/input_format_validators/validator/validator.cpp @@ -0,0 +1,15 @@ +#include "validator.h" + + +void run() { + const bool nFive = Arg("nFive", 0); + int N = Int(1, 10); Endl(); + assert(!nFive || N == 5); + for (int i = 0; i < N; i++) { + string s = Line(); + for (auto it : s) { + assert('a' <= it && it <= 'z'); + } + } + Eof(); +} diff --git a/examples/echo/input_format_validators/validator/validator.h b/examples/echo/input_format_validators/validator/validator.h new file mode 100644 index 00000000..f42bc2d7 --- /dev/null +++ b/examples/echo/input_format_validators/validator/validator.h @@ -0,0 +1,356 @@ +#ifdef NDEBUG +#error Asserts must be enabled! Do not set NDEBUG. +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +// Implemented by you! +void run(); + +// PUBLIC API +// (extend if you need to) + +[[noreturn]] +void die(const string& msg); +[[noreturn]] +void die_line(const string& msg); + +struct ArgType { + string _name, _x; + ArgType(const string& name, const string& x) : _name(name), _x(x) {} + operator string() const { return _x; } + operator long long() const; + operator bool() const; + operator int() const; +}; + +struct IntType { + long long _x; + IntType(long long x) : _x(x) {} + operator long long() const { return _x; } + operator int() const; + operator bool() const; +}; + +ArgType Arg(const string& name); + +ArgType Arg(const string& name, long long _default); + +string Arg(const string& name, const string& _default); + +template +void AssertUnique(const Vec& v); + +namespace IO { + IntType Int(long long lo, long long hi); + double Float(double lo, double hi, bool strict = true); + template + vector SpacedInts(long long count, T lo, T hi); + vector SpacedFloats(long long count, double lo, double hi); + void Char(char expected); + char Char(); + string Line(); + void Endl() { Char('\n'); } + void Space() { Char(' '); } + void Eof() { Char(-1); } +}; +using namespace IO; + +// INTERNALS + +bool _validator_initialized; +struct _validator { + map params; + set used_params; + + void construct(int argc, char** argv) { + _validator_initialized = true; + for (int i = 1; i < argc; i++) { + string s = argv[i]; + size_t ind = s.find('='); + if (ind == string::npos) continue; + auto before = s.substr(0, ind), after = s.substr(ind + 1); + if (params.count(before)) + die("Duplicate parameter " + before); + params[before] = after; + } + } + + void destroy() { + assert(_validator_initialized); + if (!params.empty()) { + string name = params.begin()->first; + die("Unused parameter " + name); + } + IO::Eof(); + _Exit(42); + } + + bool has_var(const string& name) { + if (!_validator_initialized) die("Must not read variables before main"); + return params.count(name) || used_params.count(name); + } + + string get_var(const string& name) { + if (!_validator_initialized) die("Must not read variables before main"); + if (used_params.count(name)) die("Must not read parameter " + name + " twice (either typo or slow)"); + if (!params.count(name)) die("No parameter " + name); + string res = params.at(name); + params.erase(name); + used_params.insert(name); + return res; + } +} _validator_inst; + +void die(const string& msg) { + cerr << msg << endl; + ofstream fout("/tmp/input_validator_msg", ios::app); + fout << msg << endl; + fout.close(); + _Exit(43); +} + +ArgType::operator long long() const { + string dummy; + { + long long num; + istringstream iss(_x); + iss >> num; + if (iss && !(iss >> dummy)) return num; + } + { + // We also allow scientific notation, for clarity + long double num; + istringstream iss(_x); + iss >> num; + if (iss && !(iss >> dummy)) return (long long)num; + } + die("Unable to parse value " + _x + " for parameter " + _name); +} + +ArgType::operator int() const { + long long val = (long long)*this; + if (val < INT_MIN || val > INT_MAX) + die("number " + to_string(val) + " is too large for an int for parameter " + _name); + return (int)val; +} + +ArgType::operator bool() const { + long long val = (long long)*this; + if (val < 0 || val > 1) + die("number " + to_string(val) + " is not boolean (0/1), for parameter " + _name); + return (bool)val; +} + +IntType::operator int() const { + long long val = (long long)*this; + if (val < INT_MIN || val > INT_MAX) + die_line("number " + to_string(val) + " is too large for an int"); + return (int)val; +} + +IntType::operator bool() const { + long long val = (long long)*this; + if (val < 0 || val > 1) + die_line("number " + to_string(val) + " is not boolean (0/1)"); + return (bool)val; +} + +ArgType Arg(const string& name) { + return {name, _validator_inst.get_var(name)}; +} + +ArgType Arg(const string& name, long long _default) { + if (!_validator_inst.has_var(name)) + return {name, to_string(_default)}; + ArgType ret = Arg(name); + (void)(long long)ret; + return ret; +} + +string Arg(const string& name, const string& _default) { + if (!_validator_inst.has_var(name)) + return _default; + return (string)Arg(name); +} + +static int _lineno = 1, _consumed_lineno = -1, _hit_char_error = 0; +char _peek1(); +void die_line(const string& msg) { + if (!_hit_char_error && _peek1() == -1) die(msg); + else if (_consumed_lineno == -1) die(msg + " (before reading any input)"); + else die(msg + " on line " + to_string(_consumed_lineno)); +} + +static char _buffer = -2; // -2 = none, -1 = eof, other = that char +char _peek1() { + if (_buffer != -2) return _buffer; + int val = getchar_unlocked(); + static_assert(EOF == -1, ""); + static_assert(CHAR_MIN == -128, ""); + if (val == -2 || val < CHAR_MIN || val >= CHAR_MAX) { + _hit_char_error = 1; + die_line("Unable to process byte " + to_string(val)); + } + _buffer = (char)val; + return _buffer; +} +void _use_peek(char ch) { + _buffer = -2; + if (ch == '\n') _lineno++; + else _consumed_lineno = _lineno; +} +char _read1() { + char ret = _peek1(); + _use_peek(ret); + return ret; +} +string _token() { + string ret; + for (;;) { + char ch = _peek1(); + if (ch == ' ' || ch == '\n' || ch == -1) { + break; + } + _use_peek(ch); + ret += ch; + } + return ret; +} +string _describe(char ch) { + assert(ch != -2); + if (ch == -1) return "EOF"; + if (ch == ' ') return "SPACE"; + if (ch == '\r') return "CARRIAGE RETURN"; + if (ch == '\n') return "NEWLINE"; + if (ch == '\t') return "TAB"; + if (ch == '\'') return "\"'\""; + return string("'") + ch + "'"; +} + +IntType IO::Int(long long lo, long long hi) { + string s = _token(); + if (s.empty()) die_line("Expected number, saw " + _describe(_peek1())); + try { + long long mul = 1; + int ind = 0; + if (s[0] == '-') { + mul = -1; + ind = 1; + } + if (ind == (int)s.size()) throw false; + char ch = s[ind++]; + if (ch < '0' || ch > '9') throw false; + if (ch == '0' && ind != (int)s.size()) throw false; + long long ret = ch - '0'; + while (ind < (int)s.size()) { + if (ret > LLONG_MAX / 10 - 20 || ret < LLONG_MIN / 10 + 20) + throw false; + ret *= 10; + ch = s[ind++]; + if (ch < '0' || ch > '9') throw false; + ret += ch - '0'; + } + ret *= mul; + if (ret < lo || ret > hi) die_line("Number " + s + " is out of range [" + to_string(lo) + ", " + to_string(hi) + "]"); + return {ret}; + } catch (bool) { + die_line("Unable to parse \"" + s + "\" as integer"); + } +} + +template +vector IO::SpacedInts(long long count, T lo, T hi) { + vector res; + res.reserve(count); + for (int i = 0; i < count; i++) { + if (i != 0) IO::Space(); + res.emplace_back((T)IO::Int(lo, hi)); + } + IO::Endl(); + return res; +} + +vector IO::SpacedFloats(long long count, double lo, double hi) { + vector res; + res.reserve(count); + for (int i = 0; i < count; i++) { + if (i != 0) IO::Space(); + res.emplace_back(IO::Float(lo, hi)); + } + IO::Endl(); + return res; +} + +double IO::Float(double lo, double hi, bool strict) { + string s = _token(); + if (s.empty()) die_line("Expected floating point number, saw " + _describe(_peek1())); + istringstream iss(s); + double res; + string dummy; + iss >> res; + if (!iss || iss >> dummy) die_line("Unable to parse " + s + " as a float"); + if (res < lo || res > hi) die_line("Floating-point number " + s + " is out of range [" + to_string(lo) + ", " + to_string(hi) + "]"); + if (res != res) die_line("Floating-point number " + s + " is NaN"); + if (strict) { + if (s.find('.') != string::npos && s.back() == '0' && s.substr(s.size() - 2) != ".0") + die_line("Number " + s + " has unnecessary trailing zeroes"); + if (s[0] == '0' && s.size() > 1 && s[1] == '0') + die_line("Number " + s + " has unnecessary leading zeroes"); + } + return res; +} + +char IO::Char() { + char ret = _read1(); + if (ret == -1) die_line("Expected character, saw EOF"); + return ret; +} + +void IO::Char(char expected) { + char ret = _peek1(); + if (ret != expected) die_line("Expected " + _describe(expected) + ", saw " + _describe(ret)); + _use_peek(ret); +} + +string IO::Line() { + string ret; + for (;;) { + char ch = IO::Char(); + if (ch == '\n') break; + ret += ch; + } + return ret; +} + +template +void AssertUnique(const Vec& v_) { + Vec v = v_; + auto beg = v.begin(), end = v.end(); + sort(beg, end); + int size = (int)(end - beg); + for (int i = 0; i < size - 1; i++) { + if (v[i] == v[i+1]) { + ostringstream oss; + oss << "Vector contains duplicate value " << v[i]; + die_line(oss.str()); + } + } +} + +int main(int argc, char** argv) { + _validator_inst.construct(argc, argv); + run(); + _validator_inst.destroy(); +} + diff --git a/examples/echo/problem.yaml b/examples/echo/problem.yaml new file mode 100644 index 00000000..f21f4b84 --- /dev/null +++ b/examples/echo/problem.yaml @@ -0,0 +1,6 @@ +source: Kattis +license: public domain +type: scoring +name: Echo +grading: + show_test_data_groups: true diff --git a/examples/echo/problem_statement/problem.en.md b/examples/echo/problem_statement/problem.en.md new file mode 100644 index 00000000..82f3974d --- /dev/null +++ b/examples/echo/problem_statement/problem.en.md @@ -0,0 +1,30 @@ +ECHO! Echo! Ech... + +You love shouting in caves to hear them echoed back to you. +Unfortunately, as a software engineer you are not lucky enough to get out that often to shout in caves. +Instead, you would like to implement a program that echoes words back at you. + +Every now and then, you want to be able to input a few words into the program, and have them echoed back to you. +However, as is well known, if you shout too quickly in a cave, the echo might cause interference with the new words you say. +More specifically, every other word you say will interfere with the echo of your previous word. +Thus only the first, third, fifth, and so on, words will actually produce an echo. + +Your task is to write a program that simulates this behaviour. + +## Input +The first line of the input contains an integer \(N\) (\(1 \le N \le 10\)). + +The next \(N\) lines each contains a word. +Each word is at most \(100\) letters long, and contains only letters `a-z`. + +## Output +Output the odd-indexed (i.e. first, third, fifth, and so on) words in the input. + +## Scoring +Your solution will be tested on a set of test groups, each worth a number of points. +To get the points for a test group you need to solve all test cases in the test group. + +| Group | Points | Constraints| +| ----- |--------| -----------| +| 1 | 1 | \(N\) is exactly \(5\) | +| 2 | 2 | No additional constraints | diff --git a/examples/echo/submissions/accepted/echo.cpp b/examples/echo/submissions/accepted/echo.cpp new file mode 100644 index 00000000..3ba03577 --- /dev/null +++ b/examples/echo/submissions/accepted/echo.cpp @@ -0,0 +1,13 @@ +#include + +using namespace std; + +int main() { + int N; + cin >> N; + for(int i = 0; i < N; i++) { + string s; + cin >> s; + if (i % 2 == 0) cout << s << endl; + } +} diff --git a/examples/echo/submissions/partially_accepted/sol.py b/examples/echo/submissions/partially_accepted/sol.py new file mode 100644 index 00000000..f7ffff2c --- /dev/null +++ b/examples/echo/submissions/partially_accepted/sol.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python3 +input() +A = input() +B = input() +C = input() +D = input() +E = input() +print(A) +print(C) +print(E) + diff --git a/examples/guess/problem.yaml b/examples/guess/problem.yaml index fcb51934..e1f92d6e 100644 --- a/examples/guess/problem.yaml +++ b/examples/guess/problem.yaml @@ -8,3 +8,5 @@ validation: custom interactive # happy. limits: time_safety_margin: 4 +name: + - sv: Gissa talet diff --git a/examples/guess/problem_statement/problem.sv.md b/examples/guess/problem_statement/problem.sv.md new file mode 100644 index 00000000..38478155 --- /dev/null +++ b/examples/guess/problem_statement/problem.sv.md @@ -0,0 +1,19 @@ +Jag tänker på ett tal mellan \(1\) och \(1000\). +Kan du gissa vilket talet är? +Givet en gissning berättar jag om du har rätt eller om gissningen är högre eller lägre än talet. +Jag ger dig bara \(10\) gissningar, så använd dom sparsamt! + +## Interaktion +Ditt program ska skriva ut gissningar om talet. +En gissning är en rad som enbart innehåller ett heltal mellan \(1\) och \(1000\). +Efter varje gissning måste du flusha standard out. + +Efter varje gissning kan du läs svaret på standard in. +Detta svar är ett av tre ord: + +- `lower` om talet jag tänker på är lägre än din gissning, +- `higher` om talet jag tänker på är högre än din gissning, eller +- `correct` om din gissning är korrekt. + +Efter att ha gissat rätt ska du avsluta ditt program. +Om du gissar fel \(10\) gånger får du inga fler chanser och ditt program kommer avbrytas. diff --git a/problemtools/md2html.py b/problemtools/md2html.py new file mode 100644 index 00000000..d61873e4 --- /dev/null +++ b/problemtools/md2html.py @@ -0,0 +1,146 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +import html +import re +import os.path +import string +import argparse +import logging +import subprocess + +import markdown +from markdown.inlinepatterns import InlineProcessor +from markdown.extensions import Extension +import xml.etree.ElementTree as etree + + +def _substitute_template(templatepath, templatefile, **params): + with open(os.path.join(templatepath, templatefile), "r", encoding="utf-8") as template_file: + html_template = template_file.read() % params + return html_template + + +def _escape(text): + return html.escape(text) + + +def get_markdown_statement(problem, language): + if language == '': + statement_path = os.path.join(problem, "problem_statement/problem.en.md".format(language)) + if os.path.isfile(statement_path): + return statement_path + return None + statement_path = os.path.join(problem, "problem_statement/problem.{}.md".format(language)) + if os.path.isfile(statement_path): + return statement_path + return None + + +def convert(problem, options=None): + problembase = os.path.splitext(os.path.basename(problem))[0] + destdir = string.Template(options.destdir).safe_substitute(problem=problembase) + destfile = string.Template(options.destfile).safe_substitute(problem=problembase) + imgbasedir = string.Template(options.imgbasedir).safe_substitute(problem=problembase) + + statement_path = get_markdown_statement(problem, options.language) + + with open(statement_path, "r", encoding="utf-8") as input_file: + text = input_file.read() + html = markdown.markdown(text, extensions=[InlineMathExtension(), "tables"]) + + templatepaths = [os.path.join(os.path.dirname(__file__), 'templates/markdown'), + os.path.join(os.path.dirname(__file__), '../templates/markdown'), + '/usr/lib/problemtools/templates/markdown'] + templatepath = next((p for p in templatepaths + if os.path.isdir(p) and os.path.isfile(os.path.join(p, "default-layout.html"))), + None) + if templatepath is None: + raise Exception('Could not find directory with markdown templates') + + html_template = _substitute_template(templatepath, "default-layout.html", + statement_html=html, + language=options.language, + title=options.title or "Problem Title", + problemid=problembase) + + + sample_path = os.path.join(problem, "data", "sample") + interactive_samples = [] + samples = [] + casenum = 1 + for sample in sorted(os.listdir(sample_path)): + if sample.endswith(".interaction"): + lines = [""" + + + + + +
ReadSample Interaction {}Write
""".format(casenum)] + with open(os.path.join(sample_path, sample), "r", encoding="utf-8") as infile: + sample_interaction = infile.readlines() + for interaction in sample_interaction: + data = interaction[1:] + if interaction[0] == '>': + lines.append("""
%s
""" % _escape(data)) + elif interaction[0] == '<': + lines.append("""
%s
""" % _escape(data)) + else: + print("Warning: Interaction had unknown prefix " + interaction[0]) + interactive_samples.append(''.join(lines)) + casenum += 1 + continue + if not sample.endswith(".in"): + continue + sample_name = sample[:-3] + outpath = os.path.join(sample_path, sample_name + ".ans") + if not os.path.isfile(outpath): + continue + with open(os.path.join(sample_path, sample), "r", encoding="utf-8") as infile: + sample_input = infile.read() + with open(outpath, "r", encoding="utf-8") as outfile: + sample_output = outfile.read() + + samples.append(r""" + + Sample Input %(case)d + Sample Output %(case)d + + +
%(input)s
+
%(output)s
+ """ % ({"case": casenum, "input": _escape(sample_input), "output": _escape(sample_output)})) + casenum += 1 + + if interactive_samples: + html_template = html_template + ''.join(interactive_samples) + if samples: + html_template = html_template + """ + + + %(samples)s + +
+ """ % {"samples": ''.join(samples)} + + with open(os.path.join(destdir, destfile), "w", encoding="utf-8", errors="xmlcharrefreplace") as output_file: + output_file.write(html_template) + + if options.css: + with open(os.path.join(destdir, "problem.css"), "w") as output_file: + with open(os.path.join(templatepath, "problem.css"), "r") as input_file: + output_file.write(input_file.read()) + + +class InlineMathProcessor(InlineProcessor): + def handleMatch(self, m, data): + el = etree.Element('span') + el.attrib['class'] = 'tex2jax_process' + el.text = "\\\(" + m.group(1) + "\\\)" + return el, m.start(0), m.end(0) + +class InlineMathExtension(Extension): + def extendMarkdown(self, md): + MATH_PATTERN = r'\\\((.*?)\\\)' # like \( 1 + 2 \) + md.inlinePatterns.register(InlineMathProcessor(MATH_PATTERN, md), 'inline-math', 200) + diff --git a/problemtools/problem2html.py b/problemtools/problem2html.py index 9f738070..5c374565 100644 --- a/problemtools/problem2html.py +++ b/problemtools/problem2html.py @@ -7,73 +7,29 @@ import logging import subprocess -import plasTeX.TeX -import plasTeX.Logging +from . import tex2html +from . import md2html -from .ProblemPlasTeX import ProblemRenderer -from .ProblemPlasTeX import ProblemsetMacros -from . import template + +SUPPORTED_EXTENSIONS = ("tex", "md") def convert(problem, options=None): problem = os.path.realpath(problem) - - problembase = os.path.splitext(os.path.basename(problem))[0] - destdir = string.Template(options.destdir).safe_substitute(problem=problembase) - destfile = string.Template(options.destfile).safe_substitute(problem=problembase) - imgbasedir = string.Template(options.imgbasedir).safe_substitute(problem=problembase) - - if options.quiet: - plasTeX.Logging.disableLogging() - else: - plasTeX.Logging.getLogger().setLevel(getattr(logging, options.loglevel.upper())) - plasTeX.Logging.getLogger('status').setLevel(getattr(logging, options.loglevel.upper())) - - texfile = problem - # Set up template if necessary - with template.Template(problem, language=options.language, title=options.title) as templ: - texfile = open(templ.get_file_name(), 'r') - - origcwd = os.getcwd() - - # Setup parser and renderer etc - - tex = plasTeX.TeX.TeX(myfile=texfile) - - ProblemsetMacros.init(tex) - - tex.ownerDocument.config['general']['copy-theme-extras'] = options.css - if not options.headers: - tex.ownerDocument.userdata['noheaders'] = True - tex.ownerDocument.config['files']['filename'] = destfile - tex.ownerDocument.config['images']['filenames'] = 'img-$num(4)' - tex.ownerDocument.config['images']['enabled'] = False - tex.ownerDocument.config['images']['imager'] = 'none' - tex.ownerDocument.config['images']['base-url'] = imgbasedir - - renderer = ProblemRenderer() - - if not options.quiet: - print('Parsing TeX source...') - doc = tex.parse() - texfile.close() - - # Go to destdir - if destdir: - if not os.path.isdir(destdir): - os.makedirs(destdir) - os.chdir(destdir) - + origcwd = os.getcwd() try: - if not options.quiet: - print('Rendering!') - renderer.render(doc) - - # Annoying: I have not figured out any way of stopping the plasTeX - # renderer from generating a .paux file - if os.path.isfile('.paux'): - os.remove('.paux') - + problembase = os.path.splitext(os.path.basename(problem))[0] + destdir = string.Template(options.destdir).safe_substitute(problem=problembase) + if destdir: + if not os.path.isdir(destdir): + os.makedirs(destdir) + + if md2html.get_markdown_statement(problem, options.language): + md2html.convert(problem, options) + else: + tex2html.convert(problem, options) + + destfile = os.path.join(destdir, string.Template(options.destfile).safe_substitute(problem=problembase)) if options.tidy: with open(os.devnull, 'w') as devnull: try: @@ -90,7 +46,6 @@ def convert(problem, options=None): finally: # restore cwd os.chdir(origcwd) - return True diff --git a/problemtools/problem2pdf.py b/problemtools/problem2pdf.py index 6bb503f0..5f2d8813 100644 --- a/problemtools/problem2pdf.py +++ b/problemtools/problem2pdf.py @@ -8,13 +8,16 @@ from . import template -def convert(problem, options=None): +def convert(problem, options=None, ignore_markdown=False): if options is None: options = ConvertOptions() problem = os.path.realpath(problem) problembase = os.path.splitext(os.path.basename(problem))[0] destfile = string.Template(options.destfile).safe_substitute(problem=problembase) + # We skip PDF check when verifying problems with markdown statements + if os.path.isfile(os.path.join(problem, "problem_statement", "problem.%s.md" % options.language)) and ignore_markdown: + return True texfile = problem # Set up template if necessary diff --git a/problemtools/templates/markdown/default-layout.html b/problemtools/templates/markdown/default-layout.html new file mode 100644 index 00000000..01be3db8 --- /dev/null +++ b/problemtools/templates/markdown/default-layout.html @@ -0,0 +1,35 @@ + + + + +%(title)s + + + + + + + + +
+

%(title)s

+

Problem ID: %(problemid)s

+
+
+ %(statement_html)s +
+ + + diff --git a/problemtools/templates/markdown/problem.css b/problemtools/templates/markdown/problem.css new file mode 100644 index 00000000..3ce6869d --- /dev/null +++ b/problemtools/templates/markdown/problem.css @@ -0,0 +1,90 @@ +.problemheader { + text-align: center; +} + +.problembody { + font-family: 'Times New Roman', Georgia, serif; + font-size: 1.1em; + text-align: justify; + padding-top: 1.5em; +} + +.problembody h2, .problembody h3, .problembody table.sample th { + font-family: Arial, Helvetica, sans-serif; +} + +div.minipage { + display: inline-block; +} + +div.illustration { + float: right; + padding-left: 20px; +} + +img.illustration { + width: 100%; +} + +div.figure { + display: block; + float: none; + margin-left: auto; + margin-right: auto; +} + +.illustration div.description { + font-size: 8pt; + text-align: right; +} + +.problembody p { + text-align: justify; +} + +td { + vertical-align:top; +} + +table, table td { + border: 0; +} + +table.tabular p { + margin: 0; +} + +table.sample { + width: 100%; +} + +table.sample th { + text-align: left; + width: 50%; +} + +table.sample td { + border: 1px solid black; +} + +div.sampleinteractionread { + border: 1px solid black; + width: 60%; + float: left; + margin: 3px 0px 3px 0px; +} + +.sampleinteractionread pre { + margin: 1px 5px 1px 5px; +} + +div.sampleinteractionwrite { + border: 1px solid black; + width: 60%; + float: right; + margin: 3px 0px 3px 0px; +} + +.sampleinteractionwrite pre { + margin: 1px 5px 1px 5px; +} diff --git a/problemtools/tex2html.py b/problemtools/tex2html.py new file mode 100644 index 00000000..11073b15 --- /dev/null +++ b/problemtools/tex2html.py @@ -0,0 +1,65 @@ +#! /usr/bin/env python3 +# -*- coding: utf-8 -*- +import re +import os.path +import string +import argparse +import logging +import subprocess + +import plasTeX.TeX +import plasTeX.Logging + +from .ProblemPlasTeX import ProblemRenderer +from .ProblemPlasTeX import ProblemsetMacros +from . import template + +def convert(problem, options=None): + problembase = os.path.splitext(os.path.basename(problem))[0] + destdir = string.Template(options.destdir).safe_substitute(problem=problembase) + destfile = string.Template(options.destfile).safe_substitute(problem=problembase) + imgbasedir = string.Template(options.imgbasedir).safe_substitute(problem=problembase) + + if options.quiet: + plasTeX.Logging.disableLogging() + else: + plasTeX.Logging.getLogger().setLevel(getattr(logging, options.loglevel.upper())) + plasTeX.Logging.getLogger('status').setLevel(getattr(logging, options.loglevel.upper())) + + texfile = problem + # Set up template if necessary + with template.Template(problem, language=options.language, title=options.title) as templ: + texfile = open(templ.get_file_name(), 'r') + + # Setup parser and renderer etc + tex = plasTeX.TeX.TeX(myfile=texfile) + + ProblemsetMacros.init(tex) + + tex.ownerDocument.config['general']['copy-theme-extras'] = options.css + if not options.headers: + tex.ownerDocument.userdata['noheaders'] = True + tex.ownerDocument.config['files']['filename'] = destfile + tex.ownerDocument.config['images']['filenames'] = 'img-$num(4)' + tex.ownerDocument.config['images']['enabled'] = False + tex.ownerDocument.config['images']['imager'] = 'none' + tex.ownerDocument.config['images']['base-url'] = imgbasedir + + renderer = ProblemRenderer() + + if not options.quiet: + print('Parsing TeX source...') + doc = tex.parse() + texfile.close() + + # Go to destdir + os.chdir(destdir) + + if not options.quiet: + print('Rendering!') + renderer.render(doc) + + # Annoying: I have not figured out any way of stopping the plasTeX + # renderer from generating a .paux file + if os.path.isfile('.paux'): + os.remove('.paux') diff --git a/problemtools/verifyproblem.py b/problemtools/verifyproblem.py index 195ba761..58411d0d 100644 --- a/problemtools/verifyproblem.py +++ b/problemtools/verifyproblem.py @@ -744,10 +744,14 @@ def __init__(self, problem): self._problem = problem self.languages = [] glob_path = os.path.join(problem.probdir, 'problem_statement', 'problem.') - if glob.glob(glob_path + 'tex'): - self.languages.append('') - for f in glob.glob(glob_path + '[a-z][a-z].tex'): - self.languages.append(re.search("problem.([a-z][a-z]).tex$", f).group(1)) + for extension in problem2html.SUPPORTED_EXTENSIONS: + if glob.glob(glob_path + extension): + self.languages.append('') + for f in glob.glob(glob_path + '[a-z][a-z].%s' % extension): + lang = re.search("problem.([a-z][a-z]).%s$" % extension, f).group(1) + if lang in self.languages: + self.error('Language %s has several statement formats' % lang) + self.languages.append(lang) def check(self, args): if self._check_res is not None: @@ -755,9 +759,9 @@ def check(self, args): self._check_res = True if not self.languages: - self.error('No problem statements found (expected problem.tex or problem.[a-z][a-z].tex in problem_statement directory)') + self.error('No problem statements found (expected problem.{tex,md} or problem.[a-z][a-z].{tex,md} in problem_statement directory)') if '' in self.languages and 'en' in self.languages: - self.error("Can't supply both problem.tex and problem.en.tex") + self.error("Can't supply both problem.{tex,md} and problem.en.{tex,md}") pdfopt = problem2pdf.ConvertOptions() pdfopt.nopdf = True pdfopt.quiet = True @@ -766,10 +770,10 @@ def check(self, args): htmlopt.quiet = True for lang in self.languages: - pdfopt.language = lang htmlopt.language = lang + pdfopt.language = lang try: - if not problem2pdf.convert(self._problem.probdir, pdfopt): + if not problem2pdf.convert(self._problem.probdir, pdfopt, ignore_markdown=True): langparam = '' if lang != '': langparam = '-l ' + lang @@ -791,19 +795,23 @@ def __str__(self): def get_config(self): ret = {} for lang in self.languages: - filename = ('problem.%s.tex' % lang) if lang != '' else 'problem.tex' - stmt = open(os.path.join(self._problem.probdir, 'problem_statement', filename)).read() - patterns = [(r'\\problemname{(.*)}', 'name'), - (r'^%%\s*plainproblemname:(.*)$', 'name') - ] - for tup in patterns: - pattern = tup[0] - dest = tup[1] - hit = re.search(pattern, stmt, re.MULTILINE) - if hit: - if not dest in ret: - ret[dest] = {} - ret[dest][lang] = hit.group(1).strip() + extless_filename = ('problem.%s.' % lang) if lang != '' else 'problem.' + for extension in problem2html.SUPPORTED_EXTENSIONS: + filename = extless_filename + extension + if not os.path.isfile(filename): + continue + stmt = open(os.path.join(self._problem.probdir, 'problem_statement', filename)).read() + patterns = [(r'\\problemname{(.*)}', 'name'), + (r'^%%\s*plainproblemname:(.*)$', 'name') + ] + for tup in patterns: + pattern = tup[0] + dest = tup[1] + hit = re.search(pattern, stmt, re.MULTILINE) + if hit: + if not dest in ret: + ret[dest] = {} + ret[dest][lang] = hit.group(1).strip() return ret