Compare commits
No commits in common. "main" and "v0.9.0" have entirely different histories.
4
.flake8
4
.flake8
@ -1,4 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
per-file-ignores = __init__.py:F401
|
|
||||||
extend-exclude = build
|
|
||||||
extend-ignore = E501
|
|
8
Makefile
8
Makefile
@ -1,8 +0,0 @@
|
|||||||
all:
|
|
||||||
|
|
||||||
installer:
|
|
||||||
pynsist src/installer.cfg
|
|
||||||
|
|
||||||
@PHONY: black
|
|
||||||
black:
|
|
||||||
black src
|
|
25
Pipfile
25
Pipfile
@ -1,25 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
url = "https://pypi.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
name = "pypi"
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
beautifulsoup4 = "*"
|
|
||||||
coloredlogs = "*"
|
|
||||||
flask = "*"
|
|
||||||
tabulate = "*"
|
|
||||||
colorama = "*"
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
black = "*"
|
|
||||||
debugpy = "*"
|
|
||||||
flake8 = "*"
|
|
||||||
pytest = "*"
|
|
||||||
pytest-cov = "*"
|
|
||||||
pytest-mock = "*"
|
|
||||||
pydocstyle = "*"
|
|
||||||
pylint = "*"
|
|
||||||
pynsist = "*"
|
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.11"
|
|
715
Pipfile.lock
generated
715
Pipfile.lock
generated
@ -1,715 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"hash": {
|
|
||||||
"sha256": "7c711e876affdb7a45715a84641460c07f98a71fd83bb8eb6cbf4a4b13e9ab9a"
|
|
||||||
},
|
|
||||||
"pipfile-spec": 6,
|
|
||||||
"requires": {
|
|
||||||
"python_version": "3.11"
|
|
||||||
},
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"name": "pypi",
|
|
||||||
"url": "https://pypi.org/simple",
|
|
||||||
"verify_ssl": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"astroid": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819",
|
|
||||||
"sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.8.0'",
|
|
||||||
"version": "==3.1.0"
|
|
||||||
},
|
|
||||||
"attrs": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30",
|
|
||||||
"sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==23.2.0"
|
|
||||||
},
|
|
||||||
"beautifulsoup4": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051",
|
|
||||||
"sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.6.0'",
|
|
||||||
"version": "==4.12.3"
|
|
||||||
},
|
|
||||||
"black": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8",
|
|
||||||
"sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8",
|
|
||||||
"sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd",
|
|
||||||
"sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9",
|
|
||||||
"sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31",
|
|
||||||
"sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92",
|
|
||||||
"sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f",
|
|
||||||
"sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29",
|
|
||||||
"sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4",
|
|
||||||
"sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693",
|
|
||||||
"sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218",
|
|
||||||
"sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a",
|
|
||||||
"sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23",
|
|
||||||
"sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0",
|
|
||||||
"sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982",
|
|
||||||
"sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894",
|
|
||||||
"sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540",
|
|
||||||
"sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430",
|
|
||||||
"sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b",
|
|
||||||
"sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2",
|
|
||||||
"sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6",
|
|
||||||
"sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==24.2.0"
|
|
||||||
},
|
|
||||||
"blinker": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:c3f865d4d54db7abc53758a01601cf343fe55b84c1de4e3fa910e420b438d5b9",
|
|
||||||
"sha256:e6820ff6fa4e4d1d8e2747c2283749c3f547e4fee112b98555cdcdae32996182"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==1.7.0"
|
|
||||||
},
|
|
||||||
"certifi": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f",
|
|
||||||
"sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.6'",
|
|
||||||
"version": "==2024.2.2"
|
|
||||||
},
|
|
||||||
"charset-normalizer": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027",
|
|
||||||
"sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087",
|
|
||||||
"sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786",
|
|
||||||
"sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8",
|
|
||||||
"sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09",
|
|
||||||
"sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185",
|
|
||||||
"sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574",
|
|
||||||
"sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e",
|
|
||||||
"sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519",
|
|
||||||
"sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898",
|
|
||||||
"sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269",
|
|
||||||
"sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3",
|
|
||||||
"sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f",
|
|
||||||
"sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6",
|
|
||||||
"sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8",
|
|
||||||
"sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a",
|
|
||||||
"sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73",
|
|
||||||
"sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc",
|
|
||||||
"sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714",
|
|
||||||
"sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2",
|
|
||||||
"sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc",
|
|
||||||
"sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce",
|
|
||||||
"sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d",
|
|
||||||
"sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e",
|
|
||||||
"sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6",
|
|
||||||
"sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269",
|
|
||||||
"sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96",
|
|
||||||
"sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d",
|
|
||||||
"sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a",
|
|
||||||
"sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4",
|
|
||||||
"sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77",
|
|
||||||
"sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d",
|
|
||||||
"sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0",
|
|
||||||
"sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed",
|
|
||||||
"sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068",
|
|
||||||
"sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac",
|
|
||||||
"sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25",
|
|
||||||
"sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8",
|
|
||||||
"sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab",
|
|
||||||
"sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26",
|
|
||||||
"sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2",
|
|
||||||
"sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db",
|
|
||||||
"sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f",
|
|
||||||
"sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5",
|
|
||||||
"sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99",
|
|
||||||
"sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c",
|
|
||||||
"sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d",
|
|
||||||
"sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811",
|
|
||||||
"sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa",
|
|
||||||
"sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a",
|
|
||||||
"sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03",
|
|
||||||
"sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b",
|
|
||||||
"sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04",
|
|
||||||
"sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c",
|
|
||||||
"sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001",
|
|
||||||
"sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458",
|
|
||||||
"sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389",
|
|
||||||
"sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99",
|
|
||||||
"sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985",
|
|
||||||
"sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537",
|
|
||||||
"sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238",
|
|
||||||
"sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f",
|
|
||||||
"sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d",
|
|
||||||
"sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796",
|
|
||||||
"sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a",
|
|
||||||
"sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143",
|
|
||||||
"sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8",
|
|
||||||
"sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c",
|
|
||||||
"sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5",
|
|
||||||
"sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5",
|
|
||||||
"sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711",
|
|
||||||
"sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4",
|
|
||||||
"sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6",
|
|
||||||
"sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c",
|
|
||||||
"sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7",
|
|
||||||
"sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4",
|
|
||||||
"sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b",
|
|
||||||
"sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae",
|
|
||||||
"sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12",
|
|
||||||
"sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c",
|
|
||||||
"sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae",
|
|
||||||
"sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8",
|
|
||||||
"sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887",
|
|
||||||
"sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b",
|
|
||||||
"sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4",
|
|
||||||
"sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f",
|
|
||||||
"sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5",
|
|
||||||
"sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33",
|
|
||||||
"sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519",
|
|
||||||
"sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.7.0'",
|
|
||||||
"version": "==3.3.2"
|
|
||||||
},
|
|
||||||
"click": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
|
|
||||||
"sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==8.1.7"
|
|
||||||
},
|
|
||||||
"colorama": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44",
|
|
||||||
"sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'",
|
|
||||||
"version": "==0.4.6"
|
|
||||||
},
|
|
||||||
"coloredlogs": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934",
|
|
||||||
"sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
|
||||||
"version": "==15.0.1"
|
|
||||||
},
|
|
||||||
"coverage": {
|
|
||||||
"extras": [
|
|
||||||
"toml"
|
|
||||||
],
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa",
|
|
||||||
"sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003",
|
|
||||||
"sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f",
|
|
||||||
"sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c",
|
|
||||||
"sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e",
|
|
||||||
"sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0",
|
|
||||||
"sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9",
|
|
||||||
"sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52",
|
|
||||||
"sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e",
|
|
||||||
"sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454",
|
|
||||||
"sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0",
|
|
||||||
"sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079",
|
|
||||||
"sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352",
|
|
||||||
"sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f",
|
|
||||||
"sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30",
|
|
||||||
"sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe",
|
|
||||||
"sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113",
|
|
||||||
"sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765",
|
|
||||||
"sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc",
|
|
||||||
"sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e",
|
|
||||||
"sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501",
|
|
||||||
"sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7",
|
|
||||||
"sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2",
|
|
||||||
"sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f",
|
|
||||||
"sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4",
|
|
||||||
"sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524",
|
|
||||||
"sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c",
|
|
||||||
"sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51",
|
|
||||||
"sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840",
|
|
||||||
"sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6",
|
|
||||||
"sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee",
|
|
||||||
"sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e",
|
|
||||||
"sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45",
|
|
||||||
"sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba",
|
|
||||||
"sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d",
|
|
||||||
"sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3",
|
|
||||||
"sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10",
|
|
||||||
"sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e",
|
|
||||||
"sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb",
|
|
||||||
"sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9",
|
|
||||||
"sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a",
|
|
||||||
"sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47",
|
|
||||||
"sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1",
|
|
||||||
"sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3",
|
|
||||||
"sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914",
|
|
||||||
"sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328",
|
|
||||||
"sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6",
|
|
||||||
"sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d",
|
|
||||||
"sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0",
|
|
||||||
"sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94",
|
|
||||||
"sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc",
|
|
||||||
"sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==7.4.3"
|
|
||||||
},
|
|
||||||
"debugpy": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:016a9fcfc2c6b57f939673c874310d8581d51a0fe0858e7fac4e240c5eb743cb",
|
|
||||||
"sha256:0de56aba8249c28a300bdb0672a9b94785074eb82eb672db66c8144fff673146",
|
|
||||||
"sha256:1a9fe0829c2b854757b4fd0a338d93bc17249a3bf69ecf765c61d4c522bb92a8",
|
|
||||||
"sha256:28acbe2241222b87e255260c76741e1fbf04fdc3b6d094fcf57b6c6f75ce1242",
|
|
||||||
"sha256:3a79c6f62adef994b2dbe9fc2cc9cc3864a23575b6e387339ab739873bea53d0",
|
|
||||||
"sha256:3bda0f1e943d386cc7a0e71bfa59f4137909e2ed947fb3946c506e113000f741",
|
|
||||||
"sha256:3ebb70ba1a6524d19fa7bb122f44b74170c447d5746a503e36adc244a20ac539",
|
|
||||||
"sha256:58911e8521ca0c785ac7a0539f1e77e0ce2df753f786188f382229278b4cdf23",
|
|
||||||
"sha256:6df9aa9599eb05ca179fb0b810282255202a66835c6efb1d112d21ecb830ddd3",
|
|
||||||
"sha256:7a3afa222f6fd3d9dfecd52729bc2e12c93e22a7491405a0ecbf9e1d32d45b39",
|
|
||||||
"sha256:7eb7bd2b56ea3bedb009616d9e2f64aab8fc7000d481faec3cd26c98a964bcdd",
|
|
||||||
"sha256:92116039b5500633cc8d44ecc187abe2dfa9b90f7a82bbf81d079fcdd506bae9",
|
|
||||||
"sha256:a2e658a9630f27534e63922ebf655a6ab60c370f4d2fc5c02a5b19baf4410ace",
|
|
||||||
"sha256:bfb20cb57486c8e4793d41996652e5a6a885b4d9175dd369045dad59eaacea42",
|
|
||||||
"sha256:caad2846e21188797a1f17fc09c31b84c7c3c23baf2516fed5b40b378515bbf0",
|
|
||||||
"sha256:d915a18f0597ef685e88bb35e5d7ab968964b7befefe1aaea1eb5b2640b586c7",
|
|
||||||
"sha256:dda73bf69ea479c8577a0448f8c707691152e6c4de7f0c4dec5a4bc11dee516e",
|
|
||||||
"sha256:e38beb7992b5afd9d5244e96ad5fa9135e94993b0c551ceebf3fe1a5d9beb234",
|
|
||||||
"sha256:edcc9f58ec0fd121a25bc950d4578df47428d72e1a0d66c07403b04eb93bcf98",
|
|
||||||
"sha256:efd3fdd3f67a7e576dd869c184c5dd71d9aaa36ded271939da352880c012e703",
|
|
||||||
"sha256:f696d6be15be87aef621917585f9bb94b1dc9e8aced570db1b8a6fc14e8f9b42",
|
|
||||||
"sha256:fd97ed11a4c7f6d042d320ce03d83b20c3fb40da892f994bc041bbc415d7a099"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==1.8.1"
|
|
||||||
},
|
|
||||||
"dill": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca",
|
|
||||||
"sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==0.3.8"
|
|
||||||
},
|
|
||||||
"distlib": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784",
|
|
||||||
"sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.3.8"
|
|
||||||
},
|
|
||||||
"exceptiongroup": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14",
|
|
||||||
"sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==1.2.0"
|
|
||||||
},
|
|
||||||
"flake8": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132",
|
|
||||||
"sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.8.1'",
|
|
||||||
"version": "==7.0.0"
|
|
||||||
},
|
|
||||||
"flask": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3232e0e9c850d781933cf0207523d1ece087eb8d87b23777ae38456e2fbe7c6e",
|
|
||||||
"sha256:822c03f4b799204250a7ee84b1eddc40665395333973dfb9deebfe425fefcb7d"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==3.0.2"
|
|
||||||
},
|
|
||||||
"humanfriendly": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477",
|
|
||||||
"sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
|
|
||||||
"version": "==10.0"
|
|
||||||
},
|
|
||||||
"idna": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca",
|
|
||||||
"sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.5'",
|
|
||||||
"version": "==3.6"
|
|
||||||
},
|
|
||||||
"iniconfig": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
|
|
||||||
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.0.0"
|
|
||||||
},
|
|
||||||
"isort": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109",
|
|
||||||
"sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.8.0'",
|
|
||||||
"version": "==5.13.2"
|
|
||||||
},
|
|
||||||
"itsdangerous": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44",
|
|
||||||
"sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.1.2"
|
|
||||||
},
|
|
||||||
"jinja2": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa",
|
|
||||||
"sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==3.1.3"
|
|
||||||
},
|
|
||||||
"markupsafe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf",
|
|
||||||
"sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff",
|
|
||||||
"sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f",
|
|
||||||
"sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3",
|
|
||||||
"sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532",
|
|
||||||
"sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f",
|
|
||||||
"sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617",
|
|
||||||
"sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df",
|
|
||||||
"sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4",
|
|
||||||
"sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906",
|
|
||||||
"sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f",
|
|
||||||
"sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4",
|
|
||||||
"sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8",
|
|
||||||
"sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371",
|
|
||||||
"sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2",
|
|
||||||
"sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465",
|
|
||||||
"sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52",
|
|
||||||
"sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6",
|
|
||||||
"sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169",
|
|
||||||
"sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad",
|
|
||||||
"sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2",
|
|
||||||
"sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0",
|
|
||||||
"sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029",
|
|
||||||
"sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f",
|
|
||||||
"sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a",
|
|
||||||
"sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced",
|
|
||||||
"sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5",
|
|
||||||
"sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c",
|
|
||||||
"sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf",
|
|
||||||
"sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9",
|
|
||||||
"sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb",
|
|
||||||
"sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad",
|
|
||||||
"sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3",
|
|
||||||
"sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1",
|
|
||||||
"sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46",
|
|
||||||
"sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc",
|
|
||||||
"sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a",
|
|
||||||
"sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee",
|
|
||||||
"sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900",
|
|
||||||
"sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5",
|
|
||||||
"sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea",
|
|
||||||
"sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f",
|
|
||||||
"sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5",
|
|
||||||
"sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e",
|
|
||||||
"sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a",
|
|
||||||
"sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f",
|
|
||||||
"sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50",
|
|
||||||
"sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a",
|
|
||||||
"sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b",
|
|
||||||
"sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4",
|
|
||||||
"sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff",
|
|
||||||
"sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2",
|
|
||||||
"sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46",
|
|
||||||
"sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b",
|
|
||||||
"sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf",
|
|
||||||
"sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5",
|
|
||||||
"sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5",
|
|
||||||
"sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab",
|
|
||||||
"sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd",
|
|
||||||
"sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.1.5"
|
|
||||||
},
|
|
||||||
"mccabe": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
|
|
||||||
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.6'",
|
|
||||||
"version": "==0.7.0"
|
|
||||||
},
|
|
||||||
"mypy-extensions": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
|
|
||||||
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.5'",
|
|
||||||
"version": "==1.0.0"
|
|
||||||
},
|
|
||||||
"packaging": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5",
|
|
||||||
"sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==23.2"
|
|
||||||
},
|
|
||||||
"pathspec": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08",
|
|
||||||
"sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==0.12.1"
|
|
||||||
},
|
|
||||||
"platformdirs": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068",
|
|
||||||
"sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==4.2.0"
|
|
||||||
},
|
|
||||||
"pluggy": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981",
|
|
||||||
"sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==1.4.0"
|
|
||||||
},
|
|
||||||
"pycodestyle": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f",
|
|
||||||
"sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==2.11.1"
|
|
||||||
},
|
|
||||||
"pydocstyle": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:118762d452a49d6b05e194ef344a55822987a462831ade91ec5c06fd2169d019",
|
|
||||||
"sha256:7ce43f0c0ac87b07494eb9c0b462c0b73e6ff276807f204d6b53edc72b7e44e1"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.6'",
|
|
||||||
"version": "==6.3.0"
|
|
||||||
},
|
|
||||||
"pyflakes": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f",
|
|
||||||
"sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==3.2.0"
|
|
||||||
},
|
|
||||||
"pylint": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74",
|
|
||||||
"sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.8.0'",
|
|
||||||
"version": "==3.1.0"
|
|
||||||
},
|
|
||||||
"pynsist": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:465c4596cba5cc3698d4719ddc7afea31ce9dc8936a7b4d3feffec6e8adc0b5d",
|
|
||||||
"sha256:7d3e8343c10cdbfb262ab63201a62d38ed86f4f2d0cffc2677c9917793d800a6"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.6'",
|
|
||||||
"version": "==2.8"
|
|
||||||
},
|
|
||||||
"pyparsing": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:a1bac0ce561155ecc3ed78ca94d3c9378656ad4c94c1270de543f621420f94ad",
|
|
||||||
"sha256:f9db75911801ed778fe61bb643079ff86601aca99fcae6345aa67292038fb742"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_full_version >= '3.6.8'",
|
|
||||||
"version": "==3.1.2"
|
|
||||||
},
|
|
||||||
"pytest": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd",
|
|
||||||
"sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==8.0.2"
|
|
||||||
},
|
|
||||||
"pytest-cov": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6",
|
|
||||||
"sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==4.1.0"
|
|
||||||
},
|
|
||||||
"pytest-mock": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f",
|
|
||||||
"sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==3.12.0"
|
|
||||||
},
|
|
||||||
"requests": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
|
|
||||||
"sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.31.0"
|
|
||||||
},
|
|
||||||
"requests-download": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:92d895a6ca51ea51aa42bab864bddaee31b5601c7e7e1ade4c27b0eb6695d846",
|
|
||||||
"sha256:994d9d332befae6616f562769bab163f08d6404dc7e28fb7bfed4a0a43a754ad"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.1.2"
|
|
||||||
},
|
|
||||||
"snowballstemmer": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1",
|
|
||||||
"sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.2.0"
|
|
||||||
},
|
|
||||||
"soupsieve": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690",
|
|
||||||
"sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==2.5"
|
|
||||||
},
|
|
||||||
"tabulate": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c",
|
|
||||||
"sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==0.9.0"
|
|
||||||
},
|
|
||||||
"toml": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
|
|
||||||
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
|
|
||||||
"version": "==0.10.2"
|
|
||||||
},
|
|
||||||
"tomli": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
|
|
||||||
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==2.0.1"
|
|
||||||
},
|
|
||||||
"tomlkit": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b",
|
|
||||||
"sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.7'",
|
|
||||||
"version": "==0.12.4"
|
|
||||||
},
|
|
||||||
"urllib3": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d",
|
|
||||||
"sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==2.2.1"
|
|
||||||
},
|
|
||||||
"vulture": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:12d745f7710ffbf6aeb8279ba9068a24d4e52e8ed333b8b044035c9d6b823aba",
|
|
||||||
"sha256:f0fbb60bce6511aad87ee0736c502456737490a82d919a44e6d92262cb35f1c2"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==2.11"
|
|
||||||
},
|
|
||||||
"werkzeug": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:507e811ecea72b18a404947aded4b3390e1db8f826b494d76550ef45bb3b1dcc",
|
|
||||||
"sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"markers": "python_version >= '3.8'",
|
|
||||||
"version": "==3.0.1"
|
|
||||||
},
|
|
||||||
"yarg": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:4f9cebdc00fac946c9bf2783d634e538a71c7d280a4d806d45fd4dc0ef441492",
|
|
||||||
"sha256:55695bf4d1e3e7f756496c36a69ba32c40d18f821e38f61d028f6049e5e15911"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==0.1.9"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"develop": {}
|
|
||||||
}
|
|
@ -13,9 +13,6 @@
|
|||||||
"python.testing.pytestEnabled": false,
|
"python.testing.pytestEnabled": false,
|
||||||
"python.autoComplete.extraPaths": [
|
"python.autoComplete.extraPaths": [
|
||||||
"${workspaceFolder:code}/venv/lib"
|
"${workspaceFolder:code}/venv/lib"
|
||||||
],
|
]
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.renderWhitespace": "all",
|
|
||||||
"python.formatting.provider": "black",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
[too.black]
|
|
||||||
|
|
||||||
line-length = 120
|
|
@ -1,51 +1,29 @@
|
|||||||
astroid==3.1.0
|
attrs==22.1.0
|
||||||
attrs==23.2.0
|
beautifulsoup4==4.11.1
|
||||||
beautifulsoup4==4.12.3
|
certifi==2023.7.22
|
||||||
black==24.2.0
|
charset-normalizer==3.2.0
|
||||||
blinker==1.7.0
|
|
||||||
certifi==2024.2.2
|
|
||||||
charset-normalizer==3.3.2
|
|
||||||
click==8.1.7
|
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
coloredlogs==15.0.1
|
coloredlogs==15.0.1
|
||||||
coverage==7.4.3
|
coverage==6.5.0
|
||||||
debugpy==1.8.1
|
debugpy==1.6.7
|
||||||
dill==0.3.8
|
distlib==0.3.7
|
||||||
distlib==0.3.8
|
exceptiongroup==1.0.1
|
||||||
exceptiongroup==1.2.0
|
|
||||||
flake8==7.0.0
|
|
||||||
Flask==3.0.2
|
|
||||||
humanfriendly==10.0
|
humanfriendly==10.0
|
||||||
idna==3.6
|
idna==3.4
|
||||||
iniconfig==2.0.0
|
iniconfig==1.1.1
|
||||||
isort==5.13.2
|
Jinja2==3.1.2
|
||||||
itsdangerous==2.1.2
|
MarkupSafe==2.1.3
|
||||||
Jinja2==3.1.3
|
packaging==21.3
|
||||||
MarkupSafe==2.1.5
|
pluggy==1.0.0
|
||||||
mccabe==0.7.0
|
|
||||||
mypy-extensions==1.0.0
|
|
||||||
packaging==23.2
|
|
||||||
pathspec==0.12.1
|
|
||||||
platformdirs==4.2.0
|
|
||||||
pluggy==1.4.0
|
|
||||||
pycodestyle==2.11.1
|
|
||||||
pydocstyle==6.3.0
|
|
||||||
pyflakes==3.2.0
|
|
||||||
pylint==3.1.0
|
|
||||||
pynsist==2.8
|
pynsist==2.8
|
||||||
pyparsing==3.1.2
|
pyparsing==3.0.9
|
||||||
pytest==8.0.2
|
pytest==7.2.0
|
||||||
pytest-cov==4.1.0
|
pytest-cov==4.0.0
|
||||||
pytest-mock==3.12.0
|
pytest-mock==3.10.0
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
requests_download==0.1.2
|
requests_download==0.1.2
|
||||||
snowballstemmer==2.2.0
|
soupsieve==2.3.2.post1
|
||||||
soupsieve==2.5
|
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
toml==0.10.2
|
|
||||||
tomli==2.0.1
|
tomli==2.0.1
|
||||||
tomlkit==0.12.4
|
urllib3==2.0.5
|
||||||
urllib3==2.2.1
|
|
||||||
vulture==2.11
|
|
||||||
Werkzeug==3.0.1
|
|
||||||
yarg==0.1.9
|
yarg==0.1.9
|
||||||
|
@ -1,34 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [ $# -ne 1 ]
|
|
||||||
then
|
|
||||||
echo "Please give path of test cases as parameter."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rundir=$(realpath "$(dirname "$0")")
|
|
||||||
|
|
||||||
cd "$1"
|
|
||||||
|
|
||||||
for i in Turnier\ *
|
|
||||||
do
|
|
||||||
echo "Running on data in $i"
|
|
||||||
|
|
||||||
if [ ! -r "$i/result.table" ]
|
|
||||||
then
|
|
||||||
echo "No result file is found. Skipping."
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
tmp=$(mktemp)
|
|
||||||
"$rundir/solo_runner.sh" --no-flask -a "$i/HTML" > "$tmp"
|
|
||||||
|
|
||||||
if diff -u "$i/result.table" "$tmp" > /dev/null
|
|
||||||
then
|
|
||||||
rm "$tmp"
|
|
||||||
else
|
|
||||||
echo "Differences found in competition $i"
|
|
||||||
mv "$tmp" "$i/result2.table"
|
|
||||||
fi
|
|
||||||
|
|
||||||
done
|
|
@ -1,6 +1,6 @@
|
|||||||
[Application]
|
[Application]
|
||||||
name=Solo Auswertung
|
name=Solo Auswertung
|
||||||
version=2.1.1
|
version=0.9.0
|
||||||
# How to launch the app - this calls the 'main' function from the 'myapp' package:
|
# How to launch the app - this calls the 'main' function from the 'myapp' package:
|
||||||
entry_point=main:main
|
entry_point=main:main
|
||||||
# icon=myapp.ico
|
# icon=myapp.ico
|
||||||
@ -16,19 +16,24 @@ console=true
|
|||||||
[Include]
|
[Include]
|
||||||
# Packages from PyPI that your application requires, one per line
|
# Packages from PyPI that your application requires, one per line
|
||||||
# These must have wheels on PyPI:
|
# These must have wheels on PyPI:
|
||||||
pypi_wheels = beautifulsoup4==4.12.3
|
pypi_wheels = attrs==22.1.0
|
||||||
blinker==1.8.2
|
beautifulsoup4==4.11.1
|
||||||
click==8.1.7
|
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
coloredlogs==15.0.1
|
coloredlogs==15.0.1
|
||||||
flask==3.0.3
|
coverage==6.5.0
|
||||||
|
debugpy==1.6.7
|
||||||
|
exceptiongroup==1.0.1
|
||||||
humanfriendly==10.0
|
humanfriendly==10.0
|
||||||
itsdangerous==2.2.0
|
iniconfig==1.1.1
|
||||||
jinja2==3.1.4
|
packaging==21.3
|
||||||
markupsafe==2.1.5
|
pluggy==1.0.0
|
||||||
soupsieve==2.5
|
pyparsing==3.0.9
|
||||||
|
pytest==7.2.0
|
||||||
|
pytest-cov==4.0.0
|
||||||
|
pytest-mock==3.10.0
|
||||||
|
soupsieve==2.3.2.post1
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
werkzeug==3.0.3
|
tomli==2.0.1
|
||||||
|
|
||||||
packages = solo_turnier
|
packages = solo_turnier
|
||||||
|
|
||||||
|
27
src/main.py
27
src/main.py
@ -2,41 +2,24 @@ import solo_turnier
|
|||||||
import logging
|
import logging
|
||||||
import coloredlogs
|
import coloredlogs
|
||||||
|
|
||||||
|
|
||||||
def __initLogging():
|
def __initLogging():
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
logging.root.setLevel(logging.NOTSET)
|
logging.root.setLevel(logging.NOTSET)
|
||||||
logger = logging.getLogger("solo_turnier")
|
logger = logging.getLogger('solo_turnier')
|
||||||
|
|
||||||
coloredlogs.install(level=5, logger=logger)
|
coloredlogs.install(level=5, logger=logger)
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
l = __initLogging()
|
l = __initLogging()
|
||||||
cli = solo_turnier.cli.Cli(l)
|
cli = solo_turnier.cli.Cli(l)
|
||||||
|
|
||||||
batchWorker = solo_turnier.batch.BatchWorker(cli)
|
|
||||||
|
|
||||||
if cli.showGUI():
|
if cli.showGUI():
|
||||||
raise Exception("Not yet implemented")
|
raise Exception('Not yet implemented')
|
||||||
elif cli.startFlaskServer():
|
|
||||||
solo_turnier.flask.startFlask(
|
|
||||||
batchWorker,
|
|
||||||
debug=cli.getLogLevel() > 0,
|
|
||||||
port=cli.getPort(),
|
|
||||||
showOnlyFinalists=not cli.showAllParticipants(),
|
|
||||||
externalDebugger=cli.externalDebugger,
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
combinedData = batchWorker.run(
|
batchWorker = solo_turnier.batch.BatchWorker(cli)
|
||||||
removeFilteredParicipants=not cli.showAllParticipants()
|
batchWorker.run()
|
||||||
)
|
|
||||||
|
|
||||||
consoleOutputtter = solo_turnier.output.ConsoleOutputter()
|
if __name__ == '__main__':
|
||||||
consoleOutputtter.output(combinedData)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
main()
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
|
|
||||||
from . import competition_class
|
from . import competition_class
|
||||||
from . import group
|
from . import group
|
||||||
from . import types
|
from . import types
|
||||||
|
|
||||||
from . import cli
|
from . import cli
|
||||||
|
from . import reader
|
||||||
from . import participant
|
from . import participant
|
||||||
|
|
||||||
from . import html_locator
|
from . import html_locator
|
||||||
@ -11,6 +13,3 @@ from . import worker
|
|||||||
from . import output
|
from . import output
|
||||||
|
|
||||||
from . import batch
|
from . import batch
|
||||||
from . import flask
|
|
||||||
|
|
||||||
from . import workers
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import solo_turnier
|
import solo_turnier
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -5,33 +6,31 @@ import pprint
|
|||||||
|
|
||||||
import tabulate
|
import tabulate
|
||||||
|
|
||||||
|
|
||||||
class BatchWorker:
|
class BatchWorker:
|
||||||
def __init__(self, config: solo_turnier.cli.Cli):
|
def __init__(
|
||||||
self.l = logging.getLogger("solo_turnier.batch")
|
self,
|
||||||
|
config: solo_turnier.cli.Cli
|
||||||
|
):
|
||||||
|
self.l = logging.getLogger('solo_turnier.batch')
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
def run(self, removeFilteredParicipants=True) -> solo_turnier.types.Stage5:
|
def run(self):
|
||||||
self.l.debug(self.config.__dict__)
|
self.l.debug(self.config.__dict__)
|
||||||
|
|
||||||
locator = solo_turnier.html_locator.HtmlLocator()
|
locator = solo_turnier.html_locator.HtmlLocator()
|
||||||
self.l.info(
|
self.l.info('Checking for feasible preview HTML export files in "%s"', self.config.importHtmlPath())
|
||||||
'Checking for feasible HTML export files in "%s"',
|
htmlCandidatesPreview = locator.findPreviewRoundCandidates(self.config.importHtmlPath())
|
||||||
self.config.importHtmlPath(),
|
self.l.debug('Found HTML file candidates for preview rounds: %s', htmlCandidatesPreview)
|
||||||
)
|
|
||||||
|
|
||||||
htmlResultFiles = locator.findCandidates(self.config.importHtmlPath())
|
htmlResultFiles = locator.findCandidates(self.config.importHtmlPath())
|
||||||
self.l.debug(
|
self.l.debug('Using HTML result files for result extraction: %s', htmlResultFiles)
|
||||||
"Using HTML result files for result extraction: %s", htmlResultFiles
|
|
||||||
)
|
|
||||||
|
|
||||||
worker = solo_turnier.workers.Worker.Worker()
|
worker = solo_turnier.worker.Worker()
|
||||||
importedData = worker.collectAllData(htmlResultFiles)
|
importedData = worker.collectAllData(htmlCandidatesPreview, htmlResultFiles)
|
||||||
combinedData = worker.combineData(importedData)
|
combinedData = worker.combineData(importedData)
|
||||||
|
|
||||||
worker.filterOutFinalists(combinedData, removeFilteredParicipants)
|
if not self.config.showAllParticipants():
|
||||||
|
worker.filterOutFinalists(combinedData)
|
||||||
|
|
||||||
outputShaper = solo_turnier.workers.OutputShaper.OutputShaper()
|
consoleOutputtter = solo_turnier.output.ConsoleOutputter()
|
||||||
shapedData = outputShaper.shapeResults(combinedData)
|
consoleOutputtter.output(combinedData)
|
||||||
|
|
||||||
return shapedData
|
|
||||||
|
@ -1,60 +1,25 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import debugpy
|
||||||
|
|
||||||
class Cli:
|
class Cli:
|
||||||
def __init__(self, l: logging.Logger):
|
def __init__(self, l: logging.Logger):
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
# parser.add_argument('--gui', help='Show the GUI', action='store_true')
|
parser.add_argument('--gui', help='Show the GUI', action='store_true')
|
||||||
parser.add_argument(
|
|
||||||
"--no-flask",
|
|
||||||
action="store_false",
|
|
||||||
dest="flask",
|
|
||||||
help="Disable the internal flask web server",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--port", help="The port to listen for incoming requests", default="8082"
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument('html', help='The path from where to look for HTML export files', nargs=1, default=['.'])
|
||||||
"html",
|
parser.add_argument('-o', '--output', help='Set the output path of the script', nargs=1, default=[None])
|
||||||
help="The path from where to look for HTML export files",
|
parser.add_argument('--all-participants', '-a', action='store_true', help='Show all participants not only finalists')
|
||||||
nargs=1,
|
|
||||||
default=["."],
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-o",
|
|
||||||
"--output",
|
|
||||||
help="Set the output path of the script",
|
|
||||||
nargs=1,
|
|
||||||
default=[None],
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--all-participants",
|
|
||||||
"-a",
|
|
||||||
action="store_true",
|
|
||||||
help="Show all participants not only finalists",
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.add_argument(
|
parser.add_argument('-v', '--verbose', help='Increase verbosity', action='count', default=0)
|
||||||
"-v", "--verbose", help="Increase verbosity", action="count", default=0
|
parser.add_argument('-d', '--debug', action='store_true', help='Activate debugging during startup')
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-d",
|
|
||||||
"--debug",
|
|
||||||
action="store_true",
|
|
||||||
help="Activate debugging during startup",
|
|
||||||
)
|
|
||||||
self.__args = parser.parse_args()
|
self.__args = parser.parse_args()
|
||||||
|
|
||||||
if self.__args.debug:
|
if self.__args.debug:
|
||||||
import debugpy
|
|
||||||
|
|
||||||
debugpy.listen(5678)
|
debugpy.listen(5678)
|
||||||
debugpy.wait_for_client()
|
debugpy.wait_for_client()
|
||||||
|
|
||||||
self.externalDebugger = self.__args.debug
|
|
||||||
|
|
||||||
map = {
|
map = {
|
||||||
0: logging.ERROR,
|
0: logging.ERROR,
|
||||||
1: logging.WARN,
|
1: logging.WARN,
|
||||||
@ -66,15 +31,14 @@ class Cli:
|
|||||||
l.setLevel(logLevel)
|
l.setLevel(logLevel)
|
||||||
|
|
||||||
def showGUI(self):
|
def showGUI(self):
|
||||||
# return self.__args.gui
|
return self.__args.gui
|
||||||
return False
|
|
||||||
|
|
||||||
def startFlaskServer(self):
|
|
||||||
return self.__args.flask
|
|
||||||
|
|
||||||
def importHtmlPath(self):
|
def importHtmlPath(self):
|
||||||
return self.__args.html[0]
|
return self.__args.html[0]
|
||||||
|
|
||||||
|
def importCSVPath(self):
|
||||||
|
return self.__args.import_from[0]
|
||||||
|
|
||||||
def output(self):
|
def output(self):
|
||||||
return self.__args.output[0]
|
return self.__args.output[0]
|
||||||
|
|
||||||
@ -83,6 +47,3 @@ class Cli:
|
|||||||
|
|
||||||
def showAllParticipants(self):
|
def showAllParticipants(self):
|
||||||
return self.__args.all_participants
|
return self.__args.all_participants
|
||||||
|
|
||||||
def getPort(self):
|
|
||||||
return int(self.__args.port)
|
|
||||||
|
@ -1,72 +1,61 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class CompetitionClass:
|
class CompetitionClass:
|
||||||
def __init__(self, text: str):
|
def __init__(self, text: str):
|
||||||
self.name = text
|
self.name = text
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.name}"
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class CombinedCompetitionClass:
|
class CombinedCompetitionClass:
|
||||||
def __init__(
|
def __init__(self, clsA: CompetitionClass, clsB: CompetitionClass, clsC: CompetitionClass = None):
|
||||||
self,
|
|
||||||
clsA: CompetitionClass,
|
|
||||||
clsB: CompetitionClass,
|
|
||||||
clsC: CompetitionClass = None,
|
|
||||||
):
|
|
||||||
self.clsA = clsA
|
self.clsA = clsA
|
||||||
self.clsB = clsB
|
self.clsB = clsB
|
||||||
self.clsC = clsC
|
self.clsC = clsC
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.clsC is None:
|
if self.clsC is None:
|
||||||
return f"{self.clsA}/{self.clsB}"
|
return f'{self.clsA}/{self.clsB}'
|
||||||
else:
|
else:
|
||||||
return f"{self.clsA}/{self.clsB}/{self.clsC}"
|
return f'{self.clsA}/{self.clsB}/{self.clsC}'
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return type(self) == type(other) and self.__dict__ == other.__dict__
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(("combinedClass", self.clsA, self.clsB, self.clsC))
|
|
||||||
|
|
||||||
|
|
||||||
Class_t = CompetitionClass | CombinedCompetitionClass
|
Class_t = CompetitionClass | CombinedCompetitionClass
|
||||||
|
|
||||||
|
|
||||||
class NoEClassException(Exception):
|
|
||||||
def __init__(self, *args):
|
|
||||||
super(NoEClassException, self).__init__(*args)
|
|
||||||
|
|
||||||
|
|
||||||
class CompetitionClassParser:
|
class CompetitionClassParser:
|
||||||
E = CompetitionClass("E")
|
NEWC = CompetitionClass('Newc.')
|
||||||
|
BEG = CompetitionClass('Beg.')
|
||||||
|
ADV = CompetitionClass('Adv.')
|
||||||
|
|
||||||
|
PREVIEW = CompetitionClass('Sichtung')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.mapNames = {
|
self.mapNames = {
|
||||||
"E": self.E,
|
'Newc': self.NEWC,
|
||||||
|
'Newc.': self.NEWC,
|
||||||
|
'Newcomer': self.NEWC,
|
||||||
|
'Beg': self.BEG,
|
||||||
|
'Beg.': self.BEG,
|
||||||
|
'Beginner': self.BEG,
|
||||||
|
'Adv': self.ADV,
|
||||||
|
'Adv.': self.ADV,
|
||||||
|
'Advanced': self.ADV,
|
||||||
}
|
}
|
||||||
self.namesPreview = ["Sichtung"]
|
self.namesPreview = [
|
||||||
self.mapShortNames = self.mapNames
|
'Sichtung'
|
||||||
|
]
|
||||||
|
|
||||||
def parseClass(self, cls: str, allowPreview: bool = False) -> Class_t:
|
def parseClass(self, cls: str, allowPreview: bool = False) -> Class_t:
|
||||||
# match = re.compile("^(\\w+\\.?)/(\\w+\\.?)$").match(cls)
|
if allowPreview and cls in self.namesPreview:
|
||||||
# if match is not None:
|
return self.PREVIEW
|
||||||
# clsA = self.mapNames[match.group(1)]
|
|
||||||
# clsB = self.mapNames[match.group(2)]
|
|
||||||
# return CombinedCompetitionClass(clsA, clsB)
|
|
||||||
# else:
|
|
||||||
# return self.mapNames[cls]
|
|
||||||
|
|
||||||
if cls in self.mapNames:
|
match = re.compile('^(\\w+\\.?)/(\\w+\\.?)$').match(cls)
|
||||||
return self.mapNames[cls]
|
if match is not None:
|
||||||
|
clsA = self.mapNames[match.group(1)]
|
||||||
|
clsB = self.mapNames[match.group(2)]
|
||||||
|
return CombinedCompetitionClass(clsA, clsB)
|
||||||
else:
|
else:
|
||||||
raise NoEClassException(f'The class "{cls}" is not parsable.')
|
return self.mapNames[cls]
|
||||||
|
|
||||||
def parseAbbreviatedClass(self, cls: str) -> Class_t:
|
|
||||||
return self.mapShortNames[cls]
|
|
||||||
|
|
||||||
def isPureClass(self, cls: str, allowPreview: bool = False) -> bool:
|
def isPureClass(self, cls: str, allowPreview: bool = False) -> bool:
|
||||||
parsedClass = self.parseClass(cls, allowPreview)
|
parsedClass = self.parseClass(cls, allowPreview)
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import flask
|
|
||||||
import solo_turnier
|
|
||||||
import logging
|
|
||||||
|
|
||||||
_l = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def startFlask(
|
|
||||||
batchWorker: solo_turnier.batch.BatchWorker,
|
|
||||||
debug: bool = False,
|
|
||||||
port: int = 8082,
|
|
||||||
showOnlyFinalists: bool = True,
|
|
||||||
externalDebugger: bool = False,
|
|
||||||
):
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def index():
|
|
||||||
combinedData = batchWorker.run(False)
|
|
||||||
_l.debug("Show only finalists %s", showOnlyFinalists)
|
|
||||||
|
|
||||||
return flask.render_template(
|
|
||||||
"index.html", data=combinedData, onlyFinalists=showOnlyFinalists
|
|
||||||
)
|
|
||||||
|
|
||||||
@app.get("/custom.css")
|
|
||||||
def css():
|
|
||||||
ret = flask.render_template("custom.css", onlyFinalists=showOnlyFinalists)
|
|
||||||
return flask.Response(ret, mimetype="text/css")
|
|
||||||
|
|
||||||
useReloader = debug and not externalDebugger
|
|
||||||
app.run(host="0.0.0.0", port=port, debug=debug, use_reloader=useReloader)
|
|
@ -1,16 +1,11 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class Group:
|
class Group:
|
||||||
def __init__(self, text: str):
|
def __init__(self, text: str):
|
||||||
self.name = text
|
self.name = text
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.name}"
|
return self.name
|
||||||
|
|
||||||
def getContainedGroups(self):
|
|
||||||
return (self,)
|
|
||||||
|
|
||||||
|
|
||||||
class CombinedGroup:
|
class CombinedGroup:
|
||||||
def __init__(self, grpA: Group, grpB: Group):
|
def __init__(self, grpA: Group, grpB: Group):
|
||||||
@ -18,67 +13,61 @@ class CombinedGroup:
|
|||||||
self.clsB = grpB
|
self.clsB = grpB
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.clsA}/{self.clsB}"
|
return f'{self.clsA}/{self.clsB}'
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(("combinedGroup", self.clsA, self.clsB))
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return type(self) == type(other) and self.__hash__() == other.__hash__()
|
|
||||||
|
|
||||||
def getContainedGroups(self):
|
|
||||||
return (self.clsA, self.clsB)
|
|
||||||
|
|
||||||
|
|
||||||
Group_t = Group | CombinedGroup
|
Group_t = Group | CombinedGroup
|
||||||
|
|
||||||
|
|
||||||
class GroupParser:
|
class GroupParser:
|
||||||
KIN = Group("Kin.")
|
KIN = Group('Kin.')
|
||||||
JUN = Group("Jun.")
|
JUN = Group('Jun.')
|
||||||
JUG = Group("Jug.")
|
JUG = Group('Jug.')
|
||||||
HGR = Group("Hgr.")
|
HGR = Group('Hgr.')
|
||||||
MAS1 = Group("Mas. I")
|
MAS1 = Group('Mas. I')
|
||||||
MAS2 = Group("Mas. II")
|
MAS2 = Group('Mas. II')
|
||||||
MAS3 = Group("Mas. III")
|
MAS3 = Group('Mas. III')
|
||||||
MAS4 = Group("Mas. IV")
|
MAS4 = Group('Mas. IV')
|
||||||
MAS5 = Group("Mas. V")
|
MAS5 = Group('Mas. V')
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
self.mapNames = {
|
self.mapNames = {
|
||||||
"Kin": self.KIN,
|
'Kin': self.KIN,
|
||||||
"Kin.": self.KIN,
|
'Kin.': self.KIN,
|
||||||
"Kinder": self.KIN,
|
'Kinder': self.KIN,
|
||||||
"Jun": self.JUN,
|
|
||||||
"Jun.": self.JUN,
|
'Jun': self.JUN,
|
||||||
"Junioren": self.JUN,
|
'Jun.': self.JUN,
|
||||||
"Jug": self.JUG,
|
'Junioren': self.JUN,
|
||||||
"Jug.": self.JUG,
|
|
||||||
"Jugend": self.JUG,
|
'Jug': self.JUG,
|
||||||
"Hgr": self.HGR,
|
'Jug.': self.JUG,
|
||||||
"HGr": self.HGR,
|
'Jugend': self.JUG,
|
||||||
"Hgr.": self.HGR,
|
|
||||||
"HGr.": self.HGR,
|
'Hgr': self.HGR,
|
||||||
"Hauptgruppe": self.HGR,
|
'HGr': self.HGR,
|
||||||
"Mas. I": self.MAS1,
|
'Hgr.': self.HGR,
|
||||||
"Mas. II": self.MAS2,
|
'HGr.': self.HGR,
|
||||||
"Mas. III": self.MAS3,
|
'Hauptgruppe': self.HGR,
|
||||||
"Mas. IV": self.MAS4,
|
|
||||||
"Mas. V": self.MAS5,
|
'Mas. I': self.MAS1,
|
||||||
"Mas I": self.MAS1,
|
'Mas. II': self.MAS2,
|
||||||
"Mas II": self.MAS2,
|
'Mas. III': self.MAS3,
|
||||||
"Mas III": self.MAS3,
|
'Mas. IV': self.MAS4,
|
||||||
"Mas IV": self.MAS4,
|
'Mas. V': self.MAS5,
|
||||||
"Mas V": self.MAS5,
|
'Mas I': self.MAS1,
|
||||||
"Masters I": self.MAS1,
|
'Mas II': self.MAS2,
|
||||||
"Masters II": self.MAS2,
|
'Mas III': self.MAS3,
|
||||||
"Masters III": self.MAS3,
|
'Mas IV': self.MAS4,
|
||||||
"Masters IV": self.MAS4,
|
'Mas V': self.MAS5,
|
||||||
"Masters V": self.MAS5,
|
'Masters I': self.MAS1,
|
||||||
|
'Masters II': self.MAS2,
|
||||||
|
'Masters III': self.MAS3,
|
||||||
|
'Masters IV': self.MAS4,
|
||||||
|
'Masters V': self.MAS5,
|
||||||
}
|
}
|
||||||
|
|
||||||
def parseGroup(self, cls: str) -> Group_t:
|
def parseClass(self, cls: str) -> Group_t:
|
||||||
match = re.compile("^(\\w+\\.?)/(\\w+\\.?)$").match(cls)
|
match = re.compile('^(\\w+\\.?)/(\\w+\\.?)$').match(cls)
|
||||||
if match is not None:
|
if match is not None:
|
||||||
grpA = self.mapNames[match.group(1)]
|
grpA = self.mapNames[match.group(1)]
|
||||||
grpB = self.mapNames[match.group(2)]
|
grpB = self.mapNames[match.group(2)]
|
||||||
@ -86,9 +75,9 @@ class GroupParser:
|
|||||||
else:
|
else:
|
||||||
return self.mapNames[cls]
|
return self.mapNames[cls]
|
||||||
|
|
||||||
def isPureGroup(self, cls: str) -> bool:
|
def isPureClass(self, cls: str) -> bool:
|
||||||
parsedGroup = self.parseGroup(cls)
|
parsedClass = self.parseClass(cls)
|
||||||
return isinstance(parsedGroup, Group)
|
return isinstance(parsedClass, Group)
|
||||||
|
|
||||||
def getGroups(self) -> list[Group]:
|
def getGroups(self) -> list[Group]:
|
||||||
return[
|
return[
|
||||||
@ -100,10 +89,8 @@ class GroupParser:
|
|||||||
GroupParser.MAS2,
|
GroupParser.MAS2,
|
||||||
GroupParser.MAS3,
|
GroupParser.MAS3,
|
||||||
GroupParser.MAS4,
|
GroupParser.MAS4,
|
||||||
GroupParser.MAS5,
|
GroupParser.MAS5
|
||||||
]
|
]
|
||||||
|
|
||||||
def getGroupsAsSortedList(self, groups) -> list[Group]:
|
def getGroupsAsSortedList(self, groups) -> list[Group]:
|
||||||
mainGroups = [x for x in self.getGroups() if x in groups]
|
return [x for x in self.getGroups() if x in groups]
|
||||||
additionalGroups = set(groups).difference(mainGroups)
|
|
||||||
return mainGroups + list(additionalGroups)
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
|
||||||
class HtmlLocator:
|
class HtmlLocator:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.l = logging.getLogger("solo_turnier.html_locator")
|
self.l = logging.getLogger('solo_turnier.html_locator')
|
||||||
|
|
||||||
def __findRecursivelyCandidates(self, path: str, fileName: str):
|
def __findRecursivelyCandidates(self, path: str, fileName: str):
|
||||||
ret = []
|
ret = []
|
||||||
@ -22,12 +21,16 @@ class HtmlLocator:
|
|||||||
|
|
||||||
def __fingMatchingTabs(self, ergCandidate):
|
def __fingMatchingTabs(self, ergCandidate):
|
||||||
path = os.path.dirname(ergCandidate)
|
path = os.path.dirname(ergCandidate)
|
||||||
tabPath = os.path.join(path, "tabges.htm")
|
tabPath = os.path.join(path, 'tabges.htm')
|
||||||
if not os.path.exists(tabPath):
|
if not os.path.exists(tabPath):
|
||||||
tabPath = None
|
tabPath = None
|
||||||
return (ergCandidate, tabPath)
|
return (ergCandidate, tabPath)
|
||||||
|
|
||||||
def findCandidates(self, path: str):
|
def findCandidates(self, path: str):
|
||||||
candidatesErg = self.__findRecursivelyCandidates(path, "erg.htm")
|
candidatesErg = self.__findRecursivelyCandidates(path, 'erg.htm')
|
||||||
candidates = [self.__fingMatchingTabs(x) for x in candidatesErg]
|
candidates = [self.__fingMatchingTabs(x) for x in candidatesErg]
|
||||||
return candidates
|
return candidates
|
||||||
|
|
||||||
|
def findPreviewRoundCandidates(self, path: str):
|
||||||
|
candidates = self.__findRecursivelyCandidates(path, 'tabges.htm')
|
||||||
|
return candidates
|
||||||
|
@ -3,252 +3,204 @@ from bs4 import BeautifulSoup
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from .types import HtmlParticipant, HtmlResultTotalTable
|
from .types import HtmlPreviewParticipant, HtmlParticipant, HtmlResultTotalTable
|
||||||
from .types import HtmlResultImport
|
from .types import HtmlPreviewImport as HtmlImport, HtmlResultImport
|
||||||
from .group import GroupParser
|
from .group import GroupParser
|
||||||
from .competition_class import CompetitionClassParser
|
from .competition_class import CompetitionClassParser
|
||||||
import solo_turnier
|
|
||||||
|
|
||||||
|
|
||||||
class IncompleteRoundException(Exception):
|
|
||||||
def __init__(self, *args):
|
|
||||||
super(IncompleteRoundException, self).__init__(*args)
|
|
||||||
|
|
||||||
|
|
||||||
class CannotParseRowException(Exception):
|
|
||||||
def __init__(self, *args):
|
|
||||||
super(CannotParseRowException, self).__init__(*args)
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlParser:
|
class HtmlParser:
|
||||||
|
|
||||||
def __init__(self, text: str, fileName: str = None):
|
def __init__(self, text: str, fileName: str = None):
|
||||||
self.l = logging.getLogger("solo_turnier.html_parser")
|
self.l = logging.getLogger('solo_turnier.html_parser')
|
||||||
self.soup = BeautifulSoup(text, "html.parser")
|
self.soup = BeautifulSoup(text, 'html.parser')
|
||||||
self.fileName = fileName
|
self.fileName = fileName
|
||||||
self.groupParser = GroupParser()
|
self.groupParser = GroupParser()
|
||||||
self.classParser = CompetitionClassParser()
|
self.classParser = CompetitionClassParser()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.fileName is None:
|
if self.fileName is None:
|
||||||
return "HtmlParser(direct text)"
|
return 'HtmlParser(direct text)'
|
||||||
else:
|
else:
|
||||||
return f"HtmlParser({self.fileName})"
|
return f'HtmlParser({self.fileName})'
|
||||||
|
|
||||||
def getEventTitle(self):
|
def getEventTitle(self):
|
||||||
return self.soup.find("div", class_="eventhead").table.tr.td.contents[0]
|
return self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
|
||||||
|
|
||||||
def guessDataFromHtmlTitle(self, title = None):
|
def guessDataFromHtmlTitle(self, title = None):
|
||||||
if title is None:
|
if title is None:
|
||||||
title = self.getEventTitle()
|
title = self.getEventTitle()
|
||||||
|
|
||||||
match = re.compile('.*?OT, Solos (.*?)(?: ".*")?').fullmatch(title)
|
match = re.compile('.*?ETW, Solos (.*)').match(title)
|
||||||
if match is None:
|
if match is None:
|
||||||
self.l.debug(
|
raise Exception(f'Cannot parse title "{title}"')
|
||||||
'Parsing HTML page title "%s" as OT failed. Falling back to legacy ETW.',
|
|
||||||
title,
|
|
||||||
)
|
|
||||||
match = re.compile('.*?ETW, Solos (.*?)(?: ".*")?').fullmatch(title)
|
|
||||||
if match is None:
|
|
||||||
self.l.info(
|
|
||||||
'Cannot parse html title "%s". Is it a solo competition? Possible bug.',
|
|
||||||
title,
|
|
||||||
)
|
|
||||||
raise Exception(f'Cannot parse title "{title}".')
|
|
||||||
|
|
||||||
rest = match.group(1)
|
rest = match.group(1)
|
||||||
rawGroup, rawClass, dance = rest.split(" ", 2)
|
rawGroup, rawClass, dance = rest.split(' ', 2)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"dance": dance.strip(),
|
'dance': dance.strip(),
|
||||||
"class_": self.classParser.parseClass(rawClass, True),
|
'class_': str(self.classParser.parseClass(rawClass, True)),
|
||||||
"group": self.groupParser.parseGroup(rawGroup),
|
'group': str(self.groupParser.parseClass(rawGroup))
|
||||||
}
|
}
|
||||||
|
|
||||||
def parseResult(self) -> HtmlResultImport:
|
def parseResult(self):
|
||||||
participants = {}
|
participants = {}
|
||||||
|
|
||||||
nameRegex = re.compile("(.*) \\(([0-9]+)\\)")
|
def __parseRows(rows, finalist: bool):
|
||||||
|
def __parseRow(row):
|
||||||
|
tds = row.find_all('td')
|
||||||
|
|
||||||
def __parseNameAndId(string: str, tds) -> tuple[str, str]:
|
if len(tds) != 2:
|
||||||
match = nameRegex.fullmatch(string)
|
|
||||||
if match is None:
|
|
||||||
self.l.error("Could not match %s to regex search pattern", str(tds))
|
|
||||||
raise CannotParseRowException(
|
|
||||||
f"Could not match {tds} to regex search pattern for 'name (id)'"
|
|
||||||
)
|
|
||||||
name = match.group(1)
|
|
||||||
number = match.group(2)
|
|
||||||
return name, number
|
|
||||||
|
|
||||||
def __parseRows(rows, parsers):
|
|
||||||
def parseRow(row):
|
|
||||||
for parser in parsers:
|
|
||||||
try:
|
|
||||||
parser(row("td"))
|
|
||||||
return
|
return
|
||||||
except CannotParseRowException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# No parser was found if we get here.
|
regex = re.compile('(.*) \\(([0-9]+)\\)')
|
||||||
self.l.error("Cannot parse row in table.")
|
|
||||||
|
|
||||||
for row in rows:
|
|
||||||
parseRow(row)
|
|
||||||
|
|
||||||
def __ensureLength(tds, length):
|
|
||||||
if len(tds) != length:
|
|
||||||
raise CannotParseRowException(
|
|
||||||
"The row has %d entries but %d are expected." % (len(tds), length)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __parseFormationRowGeneric(tds, finalist):
|
|
||||||
__ensureLength(tds, 2)
|
|
||||||
|
|
||||||
place = tds[0].contents[0]
|
place = tds[0].contents[0]
|
||||||
name, number = __parseNameAndId(tds[1].contents[0], tds)
|
|
||||||
|
match = regex.fullmatch(tds[1].contents[0])
|
||||||
|
if match is None:
|
||||||
|
raise Exception(f'Could not match {tds} to regex search pattern')
|
||||||
|
name = match.group(1)
|
||||||
|
number = match.group(2)
|
||||||
|
|
||||||
participant = HtmlParticipant(name, number)
|
participant = HtmlParticipant(name, number)
|
||||||
participant.finalist = finalist
|
participant.finalist = finalist
|
||||||
participant.club = ""
|
|
||||||
participants[participant] = place
|
participants[participant] = place
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
__parseRow(row)
|
||||||
|
|
||||||
def __parseFirstTable(table):
|
def __parseFirstTable(table):
|
||||||
roundName = table.tr.td.contents[0]
|
roundName = table.tr.td.contents[0]
|
||||||
if roundName != "Endrunde":
|
if roundName != 'Endrunde':
|
||||||
self.l.warning("Found table with round name %s.", roundName)
|
raise Exception('Could not parse HTML file')
|
||||||
raise IncompleteRoundException("Could not parse HTML file")
|
|
||||||
|
|
||||||
def __parseFormationRow(tds):
|
__parseRows(table.find_all('tr')[2:], True)
|
||||||
__parseFormationRowGeneric(tds, True)
|
|
||||||
|
|
||||||
def __parsePairRow(tds):
|
|
||||||
__ensureLength(tds, 4)
|
|
||||||
|
|
||||||
place = tds[0].contents[0]
|
|
||||||
tdNameClub = tds[1]
|
|
||||||
tdClub = tdNameClub.i.extract()
|
|
||||||
name, number = __parseNameAndId(tdNameClub.contents[0], tds)
|
|
||||||
|
|
||||||
participant = HtmlParticipant(name, number)
|
|
||||||
participant.finalist = True
|
|
||||||
participant.club = tdClub.contents[0]
|
|
||||||
|
|
||||||
participants[participant] = place
|
|
||||||
|
|
||||||
__parseRows(
|
|
||||||
table.find_all("tr")[2:],
|
|
||||||
[
|
|
||||||
__parsePairRow,
|
|
||||||
__parseFormationRow,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
def __parseRemainingTables(tables):
|
def __parseRemainingTables(tables):
|
||||||
|
|
||||||
def __parseFormationRow(tds):
|
|
||||||
__parseFormationRowGeneric(tds, False)
|
|
||||||
|
|
||||||
def __parsePairRow(tds):
|
|
||||||
__ensureLength(tds, 3)
|
|
||||||
|
|
||||||
place = tds[0].contents[0]
|
|
||||||
name, number = __parseNameAndId(tds[1].contents[0], tds)
|
|
||||||
|
|
||||||
participant = HtmlParticipant(name, number)
|
|
||||||
participant.finalist = False
|
|
||||||
participant.club = tds[2].contents[0]
|
|
||||||
|
|
||||||
participants[participant] = place
|
|
||||||
|
|
||||||
def __parseSeparatorRow(tds):
|
|
||||||
__ensureLength(tds, 1)
|
|
||||||
if len(list(tds[0].stripped_strings)) == 0:
|
|
||||||
return
|
|
||||||
raise CannotParseRowException("No empty string")
|
|
||||||
|
|
||||||
regexZwischenRunde = re.compile("[1-9]\. Zwischenrunde")
|
|
||||||
|
|
||||||
def __parseRoundHeading(tds):
|
|
||||||
__ensureLength(tds, 1)
|
|
||||||
s = "".join(tds[0].stripped_strings)
|
|
||||||
if s.startswith("Vorrunde"):
|
|
||||||
return
|
|
||||||
if regexZwischenRunde.match(s) is not None:
|
|
||||||
return
|
|
||||||
raise CannotParseRowException("Kein Header einer Runde gefunden.")
|
|
||||||
|
|
||||||
def __parseAllSolosQualifiedFormation(tds):
|
|
||||||
__ensureLength(tds, 2)
|
|
||||||
if tds[1].contents[0].startswith("Alle Starter weiter genommen."):
|
|
||||||
return
|
|
||||||
raise CannotParseRowException(
|
|
||||||
'Not found the text "Alle Starter weiter genommen"'
|
|
||||||
)
|
|
||||||
|
|
||||||
def __parseAllSolosQualifiedPair(tds):
|
|
||||||
__ensureLength(tds, 3)
|
|
||||||
if tds[1].contents[0].startswith("Alle Mannschaften weiter genommen."):
|
|
||||||
return
|
|
||||||
raise CannotParseRowException(
|
|
||||||
'Not found the text "Alle Mannschaften weiter genommen"'
|
|
||||||
)
|
|
||||||
|
|
||||||
for table in tables:
|
for table in tables:
|
||||||
__parseRows(
|
__parseRows(table.find_all('tr'), False)
|
||||||
table.find_all("tr"),
|
|
||||||
[
|
|
||||||
__parseAllSolosQualifiedFormation,
|
|
||||||
__parseAllSolosQualifiedPair,
|
|
||||||
__parsePairRow,
|
|
||||||
__parseFormationRow,
|
|
||||||
__parseSeparatorRow,
|
|
||||||
__parseRoundHeading,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
tables = self.soup.find("div", class_="extract").find_all("table")
|
tables = self.soup.find('div', class_='extract').find_all('table')
|
||||||
|
|
||||||
try:
|
|
||||||
if len(tables) > 0:
|
if len(tables) > 0:
|
||||||
__parseFirstTable(tables[0])
|
__parseFirstTable(tables[0])
|
||||||
|
|
||||||
__parseRemainingTables(tables[1:])
|
__parseRemainingTables(tables[1:])
|
||||||
except IncompleteRoundException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
|
# title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
|
||||||
|
|
||||||
|
# ret = HtmlImport(title, participants)
|
||||||
ret = HtmlResultImport(participants)
|
ret = HtmlResultImport(participants)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def parsePreparationRound(self):
|
||||||
|
title = self.soup.find('div', class_='eventhead').table.tr.td.contents[0]
|
||||||
|
tableData = []
|
||||||
|
rowTitles = []
|
||||||
|
|
||||||
|
def __mapBr(td):
|
||||||
|
for br in td.find_all('br'):
|
||||||
|
br.replace_with('\n')
|
||||||
|
td.smooth()
|
||||||
|
return td
|
||||||
|
|
||||||
|
def __extractTitles(table):
|
||||||
|
for row in table.find_all('tr')[1:]:
|
||||||
|
rowTitles.append(__mapBr(row.td).string)
|
||||||
|
|
||||||
|
def __extractColumns(table):
|
||||||
|
content = []
|
||||||
|
|
||||||
|
def __extractContent(td):
|
||||||
|
for br in td.find_all('br'):
|
||||||
|
br.replace_with('\n')
|
||||||
|
|
||||||
|
span = td.span
|
||||||
|
if span is not None:
|
||||||
|
span = span.extract()
|
||||||
|
meta = span.string
|
||||||
|
else:
|
||||||
|
meta = None
|
||||||
|
|
||||||
|
td.smooth()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'text': td.string.replace('\xa0', ' ').strip(),
|
||||||
|
'meta': meta
|
||||||
|
}
|
||||||
|
|
||||||
|
def __extractRow(row):
|
||||||
|
entries = []
|
||||||
|
for entry in row.find_all('td')[1:]:
|
||||||
|
entries.append(__extractContent(entry))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
for row in table.find_all('tr')[1:]:
|
||||||
|
content.append(__extractRow(row))
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
def __mergeColumns(columns1, columns2):
|
||||||
|
return list(map(lambda x, y: x + y, columns1, columns2))
|
||||||
|
|
||||||
|
extract = self.soup.find('div', class_='extract')
|
||||||
|
tables = extract.find_all('table', class_='tab1')
|
||||||
|
|
||||||
|
__extractTitles(tables[0])
|
||||||
|
tableData = __extractColumns(tables[0])
|
||||||
|
|
||||||
|
for table in tables[1:]:
|
||||||
|
tableData = __mergeColumns(tableData, __extractColumns(table))
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'titles': rowTitles,
|
||||||
|
'table': tableData
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'title': title, 'data': data}
|
||||||
|
|
||||||
|
def cleanPreparationRoundImport(self, data):
|
||||||
|
def __cleanTable(table):
|
||||||
|
def __cleanText(s: str):
|
||||||
|
# print("cleaning string ", s)
|
||||||
|
return s.strip(' \n\xa0')
|
||||||
|
|
||||||
|
def __cleanEntry(entry):
|
||||||
|
entry['text'] = __cleanText(entry['text'])
|
||||||
|
if entry['meta'] is not None:
|
||||||
|
entry['meta'] = __cleanText(entry['meta'])
|
||||||
|
|
||||||
|
for row in table:
|
||||||
|
for entry in row:
|
||||||
|
# print(entry)
|
||||||
|
__cleanEntry(entry)
|
||||||
|
|
||||||
|
data['title'] = data['title'].strip()
|
||||||
|
__cleanTable(data['data']['table'])
|
||||||
|
|
||||||
def parseIndividualResult(self, competitionGroup, competitionClass, dance):
|
def parseIndividualResult(self, competitionGroup, competitionClass, dance):
|
||||||
|
|
||||||
participants = {}
|
participants = {}
|
||||||
|
|
||||||
rePlaceParser = re.compile("([0-9]+)(?:-([0-9]+))?")
|
|
||||||
|
|
||||||
groupParser = solo_turnier.group.GroupParser()
|
|
||||||
classParser = solo_turnier.competition_class.CompetitionClassParser()
|
|
||||||
|
|
||||||
def __parseTable(table):
|
def __parseTable(table):
|
||||||
rows = table.find_all("tr")
|
rows = table.find_all('tr')
|
||||||
|
|
||||||
def __getIds():
|
def __getIds():
|
||||||
row = rows[1]
|
row = rows[1]
|
||||||
entries = row("td")
|
entries = row('td')
|
||||||
entries = entries[1:]
|
entries = entries[1:]
|
||||||
entries = [x for x in entries if len(x.contents[0].strip()) > 0]
|
entries = [x for x in entries if len(x.contents[0].strip()) > 0]
|
||||||
return [x.contents[0].strip() for x in entries]
|
return [x.contents[0].strip() for x in entries]
|
||||||
|
|
||||||
ids = __getIds()
|
ids = __getIds()
|
||||||
numIds = len(ids)
|
numIds = len(ids)
|
||||||
self.l.log(5, "Found ids in dataset: %s", ids)
|
self.l.log(5, 'Found ids in dataset: %s', ids)
|
||||||
|
|
||||||
def findRowIndex(prefixStr):
|
def findRowIndex(prefixStr):
|
||||||
def isRowMatchingCriteria(row):
|
def isRowMatchingCriteria(row):
|
||||||
if row.td.contents[0].startswith(prefixStr):
|
if row.td.contents[0].startswith(prefixStr):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
l = list(map(isRowMatchingCriteria, rows))
|
l = list(map(isRowMatchingCriteria, rows))
|
||||||
|
|
||||||
if True not in l:
|
if True not in l:
|
||||||
@ -256,77 +208,47 @@ class HtmlParser:
|
|||||||
return l.index(True)
|
return l.index(True)
|
||||||
|
|
||||||
def getPlaces():
|
def getPlaces():
|
||||||
placeRowIdx = findRowIndex("Platz von")
|
placeRowIdx = findRowIndex('Platz von')
|
||||||
placeTags = rows[placeRowIdx]("td")[1 : (numIds + 1)]
|
placeTags = rows[placeRowIdx]('td')[1:(numIds+1)]
|
||||||
|
|
||||||
def getSinglePlaceStr(tag):
|
def getSinglePlaceStr(tag):
|
||||||
for br in tag("br"):
|
for br in tag('br'):
|
||||||
br.replace_with("-")
|
br.replace_with('-')
|
||||||
tag.smooth()
|
tag.smooth()
|
||||||
rawStr = tag.contents[0].strip()
|
rawStr = tag.contents[0].strip()
|
||||||
if rawStr.endswith("-"):
|
if rawStr.endswith('-'):
|
||||||
rawStr = rawStr[:-1]
|
rawStr = rawStr[:-1]
|
||||||
|
return rawStr
|
||||||
matcher = rePlaceParser.fullmatch(rawStr)
|
|
||||||
if matcher is None:
|
|
||||||
self.l.error(
|
|
||||||
"Could not parse place string '%s' to get fixture.", rawStr
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
place = int(matcher.group(1))
|
|
||||||
placeTo = matcher.group(2)
|
|
||||||
if placeTo is not None:
|
|
||||||
placeTo = int(placeTo)
|
|
||||||
|
|
||||||
return solo_turnier.types.Place(place, placeTo)
|
|
||||||
|
|
||||||
places = list(map(getSinglePlaceStr, placeTags))
|
places = list(map(getSinglePlaceStr, placeTags))
|
||||||
return places
|
return places
|
||||||
|
|
||||||
places = getPlaces()
|
places = getPlaces()
|
||||||
self.l.log(5, "Found places: %s", places)
|
self.l.log(5, 'Found places: %s', places)
|
||||||
|
|
||||||
def getClass():
|
def getClass():
|
||||||
classRow = findRowIndex("Startklasse")
|
classRow = findRowIndex('Startklasse')
|
||||||
if classRow is not None:
|
if classRow is not None:
|
||||||
classTags = rows[classRow]("td")[1 : (numIds + 1)]
|
classTags = rows[classRow]('td')[1:(numIds+1)]
|
||||||
return list(
|
return list(map(lambda x: x.contents[0], classTags))
|
||||||
map(
|
|
||||||
lambda x: classParser.parseAbbreviatedClass(x.contents[0]),
|
|
||||||
classTags,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
classes = getClass()
|
classes = getClass()
|
||||||
self.l.log(5, "Classes: %s", classes)
|
self.l.log(5, 'Classes: %s', classes)
|
||||||
|
|
||||||
def getGroups():
|
def getGroups():
|
||||||
groupRow = findRowIndex("Startgruppe")
|
groupRow = findRowIndex('Startgruppe')
|
||||||
if groupRow is not None:
|
if groupRow is not None:
|
||||||
groupTags = rows[groupRow]("td")[1 : (numIds + 1)]
|
classTags = rows[groupRow]('td')[1:(numIds+1)]
|
||||||
return list(
|
return list(map(lambda x: x.contents[0], classTags))
|
||||||
map(lambda x: groupParser.parseGroup(x.contents[0]), groupTags)
|
|
||||||
)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
groups = getGroups()
|
groups = getGroups()
|
||||||
self.l.log(5, "Groups: %s", groups)
|
self.l.log(5, 'Groups: %s', groups)
|
||||||
|
|
||||||
for idx, id in enumerate(ids):
|
for idx, id in enumerate(ids):
|
||||||
cls = classes[idx] if classes is not None else None
|
cls = classes[idx] if classes is not None else None
|
||||||
grp = groups[idx] if groups is not None else None
|
grp = groups[idx] if groups is not None else None
|
||||||
|
|
||||||
tup = solo_turnier.types.CompetitionTuple(
|
tup = (competitionGroup, competitionClass, dance, id)
|
||||||
competitionGroup, competitionClass, dance, int(id)
|
participants[tup] = (places[idx], cls, grp)
|
||||||
)
|
|
||||||
fixture = solo_turnier.types.HtmlSingleCompetitionFixture(
|
|
||||||
place=places[idx], class_=cls, group=grp
|
|
||||||
)
|
|
||||||
participants[tup] = fixture
|
|
||||||
|
|
||||||
tables = self.soup.find("div", class_="extract").find_all("table")
|
tables = self.soup.find('div', class_='extract').find_all('table')
|
||||||
for table in tables:
|
for table in tables:
|
||||||
__parseTable(table)
|
__parseTable(table)
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
import pprint
|
import pprint
|
||||||
@ -5,18 +6,17 @@ import pprint
|
|||||||
import solo_turnier
|
import solo_turnier
|
||||||
from solo_turnier import types
|
from solo_turnier import types
|
||||||
|
|
||||||
sections = ("Kin.", "Jun.", "Jug.", "Sonst")
|
sections = ('Kin.', 'Jun.', 'Jug.', 'Sonst')
|
||||||
sectionMap = {
|
sectionMap = {
|
||||||
"Kin.": "Kinder",
|
'Kin.': 'Kinder',
|
||||||
"Jun.": "Junioren",
|
'Jun.': 'Junioren',
|
||||||
"Jug.": "Jugend",
|
'Jug.': 'Jugend',
|
||||||
"Sonst": "Undefiniert",
|
'Sonst': 'Undefiniert'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class AbstractOutputter:
|
class AbstractOutputter:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.worker = solo_turnier.workers.DataWorker.DataWorker()
|
self.worker = solo_turnier.worker.DataWorker()
|
||||||
self.groups = []
|
self.groups = []
|
||||||
self.dances = []
|
self.dances = []
|
||||||
self.showIds = False
|
self.showIds = False
|
||||||
@ -24,26 +24,24 @@ class AbstractOutputter:
|
|||||||
def getRowData(self, person: solo_turnier.worker.ResultPerson, results):
|
def getRowData(self, person: solo_turnier.worker.ResultPerson, results):
|
||||||
mappedResults = self.worker.mapPersonResultsToDanceList(results, self.dances)
|
mappedResults = self.worker.mapPersonResultsToDanceList(results, self.dances)
|
||||||
if self.showIds:
|
if self.showIds:
|
||||||
name = f"{person.name} ({person.id})"
|
name = f'{person.name} ({person.id})'
|
||||||
else:
|
else:
|
||||||
name = person.name
|
name = person.name
|
||||||
|
|
||||||
ret = [name]
|
ret = [name]
|
||||||
for result in mappedResults:
|
for result in mappedResults:
|
||||||
if result is None:
|
if result is None:
|
||||||
ret.append("")
|
ret.append('')
|
||||||
elif result.finalist == False:
|
elif result.finalist == False:
|
||||||
ret.append("x")
|
ret.append('x')
|
||||||
elif result.place == result.placeTo:
|
elif result.place == result.placeTo:
|
||||||
ret.append(f"{result.place}. ({result.class_})")
|
ret.append(f'{result.place}. ({result.class_})')
|
||||||
else:
|
else:
|
||||||
ret.append(f"{result.place}.-{result.placeTo}. ({result.class_})")
|
ret.append(f'{result.place}.-{result.placeTo}. ({result.class_})')
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def getTabularData(self, data, section):
|
def getTabularData(self, data, section):
|
||||||
sortedPersons, self.showIds = self.worker.sortPersonsInGroup(
|
sortedPersons, self.showIds = self.worker.sortPersonsInGroup(self.groups[section])
|
||||||
self.groups[section]
|
|
||||||
)
|
|
||||||
|
|
||||||
tableData = []
|
tableData = []
|
||||||
for person in sortedPersons:
|
for person in sortedPersons:
|
||||||
@ -51,105 +49,65 @@ class AbstractOutputter:
|
|||||||
|
|
||||||
return tableData
|
return tableData
|
||||||
|
|
||||||
|
|
||||||
class ConsoleOutputter(AbstractOutputter):
|
class ConsoleOutputter(AbstractOutputter):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.l = logging.getLogger("solo_turnier.output.console")
|
self.l = logging.getLogger('solo_turnier.output.console')
|
||||||
|
|
||||||
def __outputSection(self, data, section):
|
def __outputSection(self, data, section):
|
||||||
tableData = self.getTabularData(data, section)
|
tableData = self.getTabularData(data, section)
|
||||||
tableData = [["Name"] + self.dances] + tableData
|
tableData = [['Name'] + self.dances] + tableData
|
||||||
|
|
||||||
print(f"Einzeltanzwettbewerb der {sectionMap[section]}")
|
print(f"Einzeltanzwettbewerb der {sectionMap[section]}")
|
||||||
print(tabulate(tableData, headers="firstrow", tablefmt="fancy_grid"))
|
print(tabulate(tableData, headers='firstrow', tablefmt='fancy_grid'))
|
||||||
print()
|
print()
|
||||||
|
|
||||||
def _reshapeRow(
|
def _outputGroup(self, group: solo_turnier.group.Group, groupResults: types.TotalGroupResult):
|
||||||
self,
|
|
||||||
results: list[solo_turnier.types.SingleParticipantResult],
|
|
||||||
dances: list[str],
|
|
||||||
) -> list[solo_turnier.types.SingleParticipantResult]:
|
|
||||||
ret = [None for x in dances]
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
if result.dance not in dances:
|
|
||||||
self.l.error(
|
|
||||||
"Result in unknown dance found in table. This is a bug. (%s)",
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
idx = dances.index(result.dance)
|
|
||||||
ret[idx] = result
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _outputGroup(
|
|
||||||
self,
|
|
||||||
group: solo_turnier.group.Group,
|
|
||||||
groupResults: solo_turnier.types.GroupTableData,
|
|
||||||
):
|
|
||||||
if group is not None:
|
|
||||||
print(f"Einzeltanzwettbewerb der Gruppe {group}")
|
print(f"Einzeltanzwettbewerb der Gruppe {group}")
|
||||||
else:
|
|
||||||
print("Einzeltanzwettbewerbe ohne eindeutige Gruppenzuordnung")
|
|
||||||
|
|
||||||
tableData = [["Tanz"] + groupResults.dances]
|
tableData = [['Tanz'] + groupResults.dances]
|
||||||
participants = list(groupResults.resultsInGroup.keys())
|
participants = list(groupResults.results.keys())
|
||||||
participants.sort(key=lambda x: (x.id, x.name))
|
participants.sort(key=lambda x: (x.id, x.name))
|
||||||
|
|
||||||
for participant in participants:
|
for participant in participants:
|
||||||
results = groupResults.resultsInGroup[participant]
|
results = groupResults.results[participant]
|
||||||
|
|
||||||
self.l.log(5, "Results of %s: %s", participant, results)
|
|
||||||
|
|
||||||
def mapResultColumn(result: types.SingleParticipantResult):
|
def mapResultColumn(result: types.SingleParticipantResult):
|
||||||
def getPlace(place, placeTo):
|
def getPlace(place, placeTo):
|
||||||
if placeTo is None:
|
if placeTo is None:
|
||||||
return f"{place}."
|
return f'{place}.'
|
||||||
else:
|
else:
|
||||||
return f"{place}.-{placeTo}."
|
return f'{place}.-{placeTo}.'
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
return ""
|
return ''
|
||||||
|
|
||||||
placeNative = str(result.nativePlace)
|
placeNative = getPlace(result.placeNative, result.placeNativeTo)
|
||||||
place = str(result.place)
|
place = getPlace(result.place, result.placeTo)
|
||||||
lineOne = f"{placeNative}"
|
lineOne = f'{placeNative} ({result.nativeClass})'
|
||||||
|
lineTwo = f'[{place} in {result.competitionClass}]'
|
||||||
lines = [lineOne]
|
|
||||||
|
|
||||||
groupCompetition = result.competitionGroup
|
|
||||||
if isinstance(groupCompetition, solo_turnier.group.CombinedGroup):
|
|
||||||
lineTwo = f"[{place} in {groupCompetition}]"
|
|
||||||
lines.append(lineTwo)
|
|
||||||
|
|
||||||
|
lines = [lineOne, lineTwo]
|
||||||
if not result.finalist:
|
if not result.finalist:
|
||||||
lines = ["kein/e Finalist/in"] + lines
|
lines = ['kein/e Finalist/in'] + lines
|
||||||
|
|
||||||
return "\n".join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
mappedResults = map(mapResultColumn, results)
|
mappedResults = map(mapResultColumn, results)
|
||||||
|
tableRow = [f'{participant.name} ({participant.id})'] + list(mappedResults)
|
||||||
participantName = f"{participant.name} ({participant.id})"
|
|
||||||
if participant.club is not None:
|
|
||||||
participantName = f"{participantName}, {participant.club}"
|
|
||||||
|
|
||||||
tableRow = [f"{participantName}"] + list(mappedResults)
|
|
||||||
tableData.append(tableRow)
|
tableData.append(tableRow)
|
||||||
|
|
||||||
self.l.log(5, "table data: %s", pprint.pformat(tableData))
|
self.l.log(5, 'table data: %s', pprint.pformat(tableData))
|
||||||
print(tabulate(tableData, headers="firstrow", tablefmt="fancy_grid"))
|
print(tabulate(tableData, headers='firstrow', tablefmt='fancy_grid'))
|
||||||
|
|
||||||
def output(self, data: types.Stage5):
|
|
||||||
for idx, group in enumerate(data.resultsPerGroup):
|
def output(self, data: types.State4):
|
||||||
|
for idx, group in enumerate(data.groups):
|
||||||
if idx > 0:
|
if idx > 0:
|
||||||
print()
|
print()
|
||||||
|
|
||||||
self.l.debug("Output for group %s", group)
|
self.l.debug('Output for group %s', group)
|
||||||
|
|
||||||
self._outputGroup(group, data.resultsPerGroup[group])
|
self._outputGroup(group, data.results[group])
|
||||||
# self.groups = self.worker.collectPersonsInGroups(data)
|
# self.groups = self.worker.collectPersonsInGroups(data)
|
||||||
# self.dances = self.worker.getAllDancesInCompetitions(data)
|
# self.dances = self.worker.getAllDancesInCompetitions(data)
|
||||||
|
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
|
|
||||||
class Person:
|
class Person:
|
||||||
def __init__(self, firstName: str, lastName: str, club: str, group: str):
|
def __init__(
|
||||||
|
self,
|
||||||
|
firstName: str,
|
||||||
|
lastName: str,
|
||||||
|
club: str,
|
||||||
|
group: str
|
||||||
|
):
|
||||||
self.firstName = firstName
|
self.firstName = firstName
|
||||||
self.lastName = lastName
|
self.lastName = lastName
|
||||||
self.club = club
|
self.club = club
|
||||||
@ -10,19 +17,18 @@ class Person:
|
|||||||
False
|
False
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self.firstName == o.firstName
|
self.firstName == o.firstName and
|
||||||
and self.lastName == o.lastName
|
self.lastName == o.lastName and
|
||||||
and self.club == o.club
|
self.club == o.club and
|
||||||
and self.group == o.group
|
self.group == o.group
|
||||||
)
|
)
|
||||||
|
|
||||||
def getTuple(self):
|
def getTuple(self):
|
||||||
return (self.firstName, self.lastName, self.club)
|
return (self.firstName, self.lastName, self.club)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.firstName} {self.lastName} ({self.club}, {self.group})"
|
return f'{self.firstName} {self.lastName} ({self.club}, {self.group})'
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return (
|
return self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__()
|
||||||
self.firstName.__hash__() + self.lastName.__hash__() + self.club.__hash__()
|
|
||||||
)
|
|
||||||
|
101
src/solo_turnier/reader.py
Normal file
101
src/solo_turnier/reader.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
|
||||||
|
import solo_turnier
|
||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
from pprint import pformat
|
||||||
|
from .types import CSVResultRow as ResultRow
|
||||||
|
|
||||||
|
class CSVResultReader:
|
||||||
|
def __init__(self, fileName: str):
|
||||||
|
self.fileName = fileName
|
||||||
|
self.l = logging.getLogger('solo_turnier.reader.CSVResultReader')
|
||||||
|
|
||||||
|
def readFile(self):
|
||||||
|
with open(self.fileName, 'r') as fp:
|
||||||
|
dialect = csv.Sniffer().sniff(fp.read(1024))
|
||||||
|
fp.seek(0)
|
||||||
|
|
||||||
|
csvReader = csv.reader(fp, dialect)
|
||||||
|
|
||||||
|
rows = []
|
||||||
|
for row in csvReader:
|
||||||
|
rows.append(row)
|
||||||
|
|
||||||
|
ret = {
|
||||||
|
'header': rows[0],
|
||||||
|
'data': rows[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
self.l.log(5, 'Imported results from allresults.csv file: %s', (ret))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def extractResult(self, entries = None) -> list[ResultRow]:
|
||||||
|
if entries is None:
|
||||||
|
entries = self.readFile()
|
||||||
|
|
||||||
|
groupParser = solo_turnier.group.GroupParser()
|
||||||
|
classParser = solo_turnier.competition_class.CompetitionClassParser()
|
||||||
|
|
||||||
|
def __processRow(row):
|
||||||
|
result = ResultRow(
|
||||||
|
competitionGroup=groupParser.parseClass(row[2]),
|
||||||
|
competitionClass=classParser.parseClass(row[3]),
|
||||||
|
dance=row[4],
|
||||||
|
id=row[5],
|
||||||
|
firstName=row[6], lastName=row[7],
|
||||||
|
club=row[10],
|
||||||
|
place=row[12], placeTo=row[13],
|
||||||
|
group=groupParser.parseClass(row[15]),
|
||||||
|
class_=classParser.parseClass(row[16])
|
||||||
|
)
|
||||||
|
self.l.log(5, 'Found row in CSV: %s', result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
ret = list(map(__processRow, entries['data']))
|
||||||
|
|
||||||
|
self.l.log(5, 'Extracted rows from CSV data: %s', ret)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class CSVExtractor:
|
||||||
|
def __init__(self):
|
||||||
|
self.l = logging.getLogger('solo_turnier.worker')
|
||||||
|
self.__groupMaps = {
|
||||||
|
'Kinder': 'Kin.',
|
||||||
|
'Junioren': 'Jun.',
|
||||||
|
'Jugend': 'Jug.'
|
||||||
|
}
|
||||||
|
self.__classMaps = {
|
||||||
|
'Newcomer': 'Newc.',
|
||||||
|
'Beginner': 'Beg.',
|
||||||
|
'Advanced': 'Adv.'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __mapGroup(self, group):
|
||||||
|
return self.__groupMaps.get(group, group)
|
||||||
|
|
||||||
|
def __mapClass(self, class_):
|
||||||
|
return self.__classMaps.get(class_, class_)
|
||||||
|
|
||||||
|
def mapCSVImport(self, imported) -> list[ResultRow]:
|
||||||
|
ret = []
|
||||||
|
|
||||||
|
def __processRow(row):
|
||||||
|
result = ResultRow(
|
||||||
|
competitionGroup=self.__mapGroup(row[2]),
|
||||||
|
competitionClass=self.__mapClass(row[3]),
|
||||||
|
dance=row[4],
|
||||||
|
id=row[5],
|
||||||
|
firstName=row[6], lastName=row[7],
|
||||||
|
club=row[10],
|
||||||
|
place=row[12], placeTo=row[13],
|
||||||
|
group=self.__mapGroup(row[15]), class_=self.__mapClass(row[16])
|
||||||
|
)
|
||||||
|
ret.append(result)
|
||||||
|
self.l.log(5, 'Found row in CSV: %s', result)
|
||||||
|
|
||||||
|
for row in imported['data']:
|
||||||
|
__processRow(row)
|
||||||
|
|
||||||
|
return ret
|
@ -1,51 +0,0 @@
|
|||||||
.tab-summary {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-summary tr:nth-of-type(even) {
|
|
||||||
background-color: cyan;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-summary td {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-summary td .competition-place {
|
|
||||||
font-size: smaller;
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-summary .no-finalist {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-summary .no-finalist-dance {
|
|
||||||
color: gray;
|
|
||||||
text-decoration-style: solid;
|
|
||||||
text-decoration-line: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
@page {
|
|
||||||
size: landscape;
|
|
||||||
}
|
|
||||||
@page portrait {
|
|
||||||
size: portrait;
|
|
||||||
}
|
|
||||||
/* body {
|
|
||||||
size: landscape;
|
|
||||||
page-orientation: rotate-right;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.section,
|
|
||||||
.section table tr,
|
|
||||||
.section table td {
|
|
||||||
page-break-inside: avoid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-summary .no-finalist {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
{% if onlyFinalists %}
|
|
||||||
.no-finalist {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
@ -1,71 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>Solo Turnier Auswertung</title>
|
|
||||||
{# <meta name="description" content="Webpage for xxxx"> #}
|
|
||||||
<!-- http://meyerweb.com/eric/tools/css/reset/ -->
|
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
||||||
<link rel="stylesheet" href="/custom.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{# <h1>Finalauswertung Solo-Turniere</h1> #}
|
|
||||||
{% for group in data.resultsPerGroup %}
|
|
||||||
{% block groupBlk scoped %}
|
|
||||||
<div class="section">
|
|
||||||
{% if group is none %}
|
|
||||||
<h1>Auswertung ohne eindeutige Gruppe</h1>
|
|
||||||
{% else %}
|
|
||||||
<h1>Auswertung Gruppe {{ group.name }}</h1>
|
|
||||||
{% endif %}
|
|
||||||
<table class="tab-summary">
|
|
||||||
<tr>
|
|
||||||
<th>Teilnehmer</th>
|
|
||||||
{% for dance in data.resultsPerGroup[group].dances %}
|
|
||||||
<th>{{ dance }}</th>
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% set activeGroup = data.resultsPerGroup[group].resultsInGroup %}
|
|
||||||
{% for participant, results in activeGroup|dictsort() %}
|
|
||||||
{% block participantGrp scoped %}
|
|
||||||
{% set rowCls = "" %}
|
|
||||||
{% if not participant.finalist %}
|
|
||||||
{% set rowCls = "no-finalist" %}
|
|
||||||
{% endif %}
|
|
||||||
{% if participant.finalist or not onlyFinalists %}
|
|
||||||
<tr class="{{ rowCls }}">
|
|
||||||
<td>
|
|
||||||
{{ participant.name }} ({{ participant.id }})
|
|
||||||
{% if participant.club is not none %}
|
|
||||||
, {{ participant.club}}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% for dance in data.resultsPerGroup[group].dances %}
|
|
||||||
{% block danceResult scoped %}
|
|
||||||
{% set res = activeGroup[participant][loop.index0] %}
|
|
||||||
<td>
|
|
||||||
{% if res is not none %}
|
|
||||||
{% if not participant.finalist %}
|
|
||||||
Kein/e Finalist/in <br />
|
|
||||||
{% endif %}
|
|
||||||
<span class="{% if not res.finalist %}no-finalist-dance{% endif %}">
|
|
||||||
{{ res.getNativePlace() }}
|
|
||||||
{% if res.isCombinedGroup() %}
|
|
||||||
<br />
|
|
||||||
({{ res.place }} {{ res.competitionGroup }})
|
|
||||||
{% endif %}
|
|
||||||
</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
{% endblock %}
|
|
||||||
{% endfor %}
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
{% endfor %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,23 +1,21 @@
|
|||||||
import solo_turnier.competition_class
|
import solo_turnier.competition_class
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(9))
|
@pytest.fixture(params=range(9))
|
||||||
def fix_pureClass(request):
|
def fix_pureClass(request):
|
||||||
cases = (
|
cases = (
|
||||||
("Newc", "Newc."),
|
('Newc', 'Newc.'),
|
||||||
("Newc.", "Newc."),
|
('Newc.', 'Newc.'),
|
||||||
("Newcomer", "Newc."),
|
('Newcomer', 'Newc.'),
|
||||||
("Beg", "Beg."),
|
('Beg', 'Beg.'),
|
||||||
("Beg.", "Beg."),
|
('Beg.', 'Beg.'),
|
||||||
("Beginner", "Beg."),
|
('Beginner', 'Beg.'),
|
||||||
("Adv", "Adv."),
|
('Adv', 'Adv.'),
|
||||||
("Adv.", "Adv."),
|
('Adv.', 'Adv.'),
|
||||||
("Advanced", "Adv."),
|
('Advanced', 'Adv.'),
|
||||||
)
|
)
|
||||||
return cases[request.param]
|
return cases[request.param]
|
||||||
|
|
||||||
|
|
||||||
def test_pureClassParsing(fix_pureClass):
|
def test_pureClassParsing(fix_pureClass):
|
||||||
className = fix_pureClass[0]
|
className = fix_pureClass[0]
|
||||||
expected = fix_pureClass[1]
|
expected = fix_pureClass[1]
|
||||||
@ -30,43 +28,39 @@ def test_pureClassParsing(fix_pureClass):
|
|||||||
|
|
||||||
assert parser.isPureClass(className)
|
assert parser.isPureClass(className)
|
||||||
|
|
||||||
|
|
||||||
def test_classParsingWithPreview():
|
def test_classParsingWithPreview():
|
||||||
parser = solo_turnier.competition_class.CompetitionClassParser()
|
parser = solo_turnier.competition_class.CompetitionClassParser()
|
||||||
ret = parser.parseClass("Sichtung", True)
|
ret = parser.parseClass('Sichtung', True)
|
||||||
|
|
||||||
assert isinstance(ret, solo_turnier.competition_class.CompetitionClass)
|
assert isinstance(ret, solo_turnier.competition_class.CompetitionClass)
|
||||||
assert str(ret) == "Sichtung"
|
assert str(ret) == 'Sichtung'
|
||||||
|
|
||||||
assert parser.isPureClass("Sichtung", True)
|
|
||||||
|
|
||||||
|
assert parser.isPureClass('Sichtung', True)
|
||||||
|
|
||||||
def test_classParsingInvalidPreview():
|
def test_classParsingInvalidPreview():
|
||||||
parser = solo_turnier.competition_class.CompetitionClassParser()
|
parser = solo_turnier.competition_class.CompetitionClassParser()
|
||||||
try:
|
try:
|
||||||
parser.parseClass("Sichtung")
|
parser.parseClass('Sichtung')
|
||||||
assert False
|
assert False
|
||||||
except:
|
except:
|
||||||
assert True
|
assert True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parser.isPureClass("Sichtung")
|
parser.isPureClass('Sichtung')
|
||||||
assert False
|
assert False
|
||||||
except:
|
except:
|
||||||
assert True
|
assert True
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(4))
|
@pytest.fixture(params=range(4))
|
||||||
def fix_combinedClass(request):
|
def fix_combinedClass(request):
|
||||||
cases = (
|
cases = (
|
||||||
("Newc/Beg", "Newc./Beg."),
|
('Newc/Beg', 'Newc./Beg.'),
|
||||||
("Newc./Beg", "Newc./Beg."),
|
('Newc./Beg', 'Newc./Beg.'),
|
||||||
("Beginner/Adv", "Beg./Adv."),
|
('Beginner/Adv', 'Beg./Adv.'),
|
||||||
("Beg/Adv", "Beg./Adv."),
|
('Beg/Adv', 'Beg./Adv.'),
|
||||||
)
|
)
|
||||||
return cases[request.param]
|
return cases[request.param]
|
||||||
|
|
||||||
|
|
||||||
def test_combinedClassParsing(fix_combinedClass):
|
def test_combinedClassParsing(fix_combinedClass):
|
||||||
className = fix_combinedClass[0]
|
className = fix_combinedClass[0]
|
||||||
expected = fix_combinedClass[1]
|
expected = fix_combinedClass[1]
|
||||||
|
@ -2,13 +2,12 @@ import solo_turnier.reader
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
def test_import():
|
def test_import():
|
||||||
fileName = os.path.join(os.path.dirname(__file__), "reader", "test.csv")
|
fileName = os.path.join(os.path.dirname(__file__), 'reader', 'test.csv')
|
||||||
reader = solo_turnier.reader.CSVResultReader(fileName)
|
reader = solo_turnier.reader.CSVResultReader(fileName)
|
||||||
ret = reader.readFile()
|
ret = reader.readFile()
|
||||||
|
|
||||||
with open(os.path.join(os.path.dirname(__file__), "reader", "expected.json")) as fp:
|
with open(os.path.join(os.path.dirname(__file__), 'reader', 'expected.json')) as fp:
|
||||||
expected = json.load(fp)
|
expected = json.load(fp)
|
||||||
|
|
||||||
assert ret == expected
|
assert ret == expected
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
import solo_turnier.group
|
import solo_turnier.group
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(9))
|
@pytest.fixture(params=range(9))
|
||||||
def fix_pureClass(request):
|
def fix_pureClass(request):
|
||||||
cases = (
|
cases = (
|
||||||
("Kin", "Kin."),
|
('Kin', 'Kin.'),
|
||||||
("Kin.", "Kin."),
|
('Kin.', 'Kin.'),
|
||||||
("Kinder", "Kin."),
|
('Kinder', 'Kin.'),
|
||||||
("Jun", "Jun."),
|
('Jun', 'Jun.'),
|
||||||
("Jun.", "Jun."),
|
('Jun.', 'Jun.'),
|
||||||
("Junioren", "Jun."),
|
('Junioren', 'Jun.'),
|
||||||
("Jug", "Jug."),
|
('Jug', 'Jug.'),
|
||||||
("Jug.", "Jug."),
|
('Jug.', 'Jug.'),
|
||||||
("Jugend", "Jug."),
|
('Jugend', 'Jug.'),
|
||||||
)
|
)
|
||||||
return cases[request.param]
|
return cases[request.param]
|
||||||
|
|
||||||
|
|
||||||
def test_pureClassParsing(fix_pureClass):
|
def test_pureClassParsing(fix_pureClass):
|
||||||
className = fix_pureClass[0]
|
className = fix_pureClass[0]
|
||||||
expected = fix_pureClass[1]
|
expected = fix_pureClass[1]
|
||||||
@ -30,18 +28,16 @@ def test_pureClassParsing(fix_pureClass):
|
|||||||
|
|
||||||
assert parser.isPureClass(className)
|
assert parser.isPureClass(className)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(4))
|
@pytest.fixture(params=range(4))
|
||||||
def fix_combinedClass(request):
|
def fix_combinedClass(request):
|
||||||
cases = (
|
cases = (
|
||||||
("Kin/Jun", "Kin./Jun."),
|
('Kin/Jun', 'Kin./Jun.'),
|
||||||
("Kin./Jun", "Kin./Jun."),
|
('Kin./Jun', 'Kin./Jun.'),
|
||||||
("Junioren/Jug", "Jun./Jug."),
|
('Junioren/Jug', 'Jun./Jug.'),
|
||||||
("Jun/Jug", "Jun./Jug."),
|
('Jun/Jug', 'Jun./Jug.'),
|
||||||
)
|
)
|
||||||
return cases[request.param]
|
return cases[request.param]
|
||||||
|
|
||||||
|
|
||||||
def test_combinedClassParsing(fix_combinedClass):
|
def test_combinedClassParsing(fix_combinedClass):
|
||||||
className = fix_combinedClass[0]
|
className = fix_combinedClass[0]
|
||||||
expected = fix_combinedClass[1]
|
expected = fix_combinedClass[1]
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import solo_turnier.html_locator
|
import solo_turnier.html_locator
|
||||||
|
|
||||||
|
|
||||||
def test_fetchLocationCandidates():
|
def test_fetchLocationCandidates():
|
||||||
folder = os.path.join(os.path.dirname(__file__), "html_locator", "export")
|
folder = os.path.join(os.path.dirname(__file__), 'html_locator', 'export')
|
||||||
relFolder = os.path.relpath(folder)
|
relFolder = os.path.relpath(folder)
|
||||||
|
|
||||||
locator = solo_turnier.html_locator.HtmlLocator()
|
locator = solo_turnier.html_locator.HtmlLocator()
|
||||||
candidates = locator.findCandidates(relFolder)
|
candidates = locator.findCandidates(relFolder)
|
||||||
|
|
||||||
expected = [
|
expected = [
|
||||||
"solo_turnier/tests/html_locator/export/2-bar/erg.htm",
|
'solo_turnier/tests/html_locator/export/2-bar/erg.htm',
|
||||||
"solo_turnier/tests/html_locator/export/3-baz/erg.htm",
|
'solo_turnier/tests/html_locator/export/3-baz/erg.htm',
|
||||||
"solo_turnier/tests/html_locator/export/3-baz/subfolder/4-baz/erg.htm",
|
'solo_turnier/tests/html_locator/export/3-baz/subfolder/4-baz/erg.htm'
|
||||||
]
|
]
|
||||||
assert set(candidates) == set(expected)
|
assert set(candidates) == set(expected)
|
||||||
|
@ -4,22 +4,20 @@ import json
|
|||||||
|
|
||||||
import solo_turnier.html_parser
|
import solo_turnier.html_parser
|
||||||
|
|
||||||
|
@pytest.fixture(scope='module', params=range(2))
|
||||||
@pytest.fixture(scope="module", params=range(2))
|
|
||||||
def dataProviderHtmlParser(request):
|
def dataProviderHtmlParser(request):
|
||||||
variant = str(request.param+1)
|
variant = str(request.param+1)
|
||||||
dir = os.path.join(os.path.dirname(__file__), "html_parser", "erg", variant)
|
dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'erg', variant)
|
||||||
htmlFile = os.path.join(dir, "erg.htm")
|
htmlFile = os.path.join(dir, 'erg.htm')
|
||||||
jsonFile = os.path.join(dir, "expected.json")
|
jsonFile = os.path.join(dir, 'expected.json')
|
||||||
|
|
||||||
with open(htmlFile, "r") as fp:
|
with open(htmlFile, 'r') as fp:
|
||||||
html = fp.read()
|
html = fp.read()
|
||||||
with open(jsonFile, "r") as fp:
|
with open(jsonFile, 'r') as fp:
|
||||||
jsonContent = json.load(fp)
|
jsonContent = json.load(fp)
|
||||||
|
|
||||||
return (html, jsonContent)
|
return (html, jsonContent)
|
||||||
|
|
||||||
|
|
||||||
def test_extractDataFromHtml(dataProviderHtmlParser):
|
def test_extractDataFromHtml(dataProviderHtmlParser):
|
||||||
htmlString = dataProviderHtmlParser[0]
|
htmlString = dataProviderHtmlParser[0]
|
||||||
expected = dataProviderHtmlParser[1]
|
expected = dataProviderHtmlParser[1]
|
||||||
@ -31,71 +29,67 @@ def test_extractDataFromHtml(dataProviderHtmlParser):
|
|||||||
for i in actualResult.participants:
|
for i in actualResult.participants:
|
||||||
participants[i] = actualResult.participants[i].__dict__
|
participants[i] = actualResult.participants[i].__dict__
|
||||||
|
|
||||||
assert actualResult.title == expected["title"]
|
assert actualResult.title == expected['title']
|
||||||
assert participants == expected["participants"]
|
assert participants == expected['participants']
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(6))
|
@pytest.fixture(params=range(6))
|
||||||
def fixture_guessDataFromTitle(request):
|
def fixture_guessDataFromTitle(request):
|
||||||
cases = {
|
cases = {
|
||||||
"09.07.2022 - ETW, Solos Jun. Beginner Jive": {
|
'09.07.2022 - ETW, Solos Jun. Beginner Jive': {
|
||||||
"class_": "Beg.",
|
'class_': 'Beg.',
|
||||||
"dance": "Jive",
|
'dance': 'Jive',
|
||||||
"group": "Jun.",
|
'group': 'Jun.'
|
||||||
},
|
},
|
||||||
"09.07.2022 - ETW, Solos Jun. Newc./Beg. Rumba": {
|
'09.07.2022 - ETW, Solos Jun. Newc./Beg. Rumba': {
|
||||||
"class_": "Newc./Beg.",
|
'class_': 'Newc./Beg.',
|
||||||
"dance": "Rumba",
|
'dance': 'Rumba',
|
||||||
"group": "Jun.",
|
'group': 'Jun.'
|
||||||
},
|
},
|
||||||
"09.07.2022 - ETW, Solos Kin./Jun. Beginner Cha Cha": {
|
'09.07.2022 - ETW, Solos Kin./Jun. Beginner Cha Cha': {
|
||||||
"class_": "Beg.",
|
'class_': 'Beg.',
|
||||||
"dance": "Cha Cha",
|
'dance': 'Cha Cha',
|
||||||
"group": "Kin./Jun.",
|
'group': 'Kin./Jun.'
|
||||||
},
|
},
|
||||||
"09.07.2022 - ETW, Solos Kin. Newcomer Samba": {
|
'09.07.2022 - ETW, Solos Kin. Newcomer Samba': {
|
||||||
"class_": "Newc.",
|
'class_': 'Newc.',
|
||||||
"dance": "Samba",
|
'dance': 'Samba',
|
||||||
"group": "Kin.",
|
'group': 'Kin.'
|
||||||
},
|
},
|
||||||
"09.07.2022 - ETW, Solos Jugend Beg./Adv. Wiener Walzer": {
|
'09.07.2022 - ETW, Solos Jugend Beg./Adv. Wiener Walzer': {
|
||||||
"class_": "Beg./Adv.",
|
'class_': 'Beg./Adv.',
|
||||||
"dance": "Wiener Walzer",
|
'dance': 'Wiener Walzer',
|
||||||
"group": "Jug.",
|
'group': 'Jug.'
|
||||||
},
|
},
|
||||||
"09.07.2022 - ETW, Solos Jugend Sichtung Wiener Walzer": {
|
'09.07.2022 - ETW, Solos Jugend Sichtung Wiener Walzer': {
|
||||||
"class_": "Sichtung",
|
'class_': 'Sichtung',
|
||||||
"dance": "Wiener Walzer",
|
'dance': 'Wiener Walzer',
|
||||||
"group": "Jug.",
|
'group': 'Jug.'
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
keys = list(cases.keys())
|
keys = list(cases.keys())
|
||||||
key = keys[request.param]
|
key = keys[request.param]
|
||||||
return (key, cases[key])
|
return (key, cases[key])
|
||||||
|
|
||||||
|
|
||||||
def test_guessDataFromTitle(fixture_guessDataFromTitle):
|
def test_guessDataFromTitle(fixture_guessDataFromTitle):
|
||||||
parser = solo_turnier.html_parser.HtmlParser("")
|
parser = solo_turnier.html_parser.HtmlParser('')
|
||||||
ret = parser.guessDataFromHtmlTitle(fixture_guessDataFromTitle[0])
|
ret = parser.guessDataFromHtmlTitle(fixture_guessDataFromTitle[0])
|
||||||
|
|
||||||
assert ret == fixture_guessDataFromTitle[1]
|
assert ret == fixture_guessDataFromTitle[1]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(1))
|
@pytest.fixture(params=range(1))
|
||||||
def fixture_parsePreparationResult(request):
|
def fixture_parsePreparationResult(request):
|
||||||
variant = str(request.param+1)
|
variant = str(request.param+1)
|
||||||
dir = os.path.join(os.path.dirname(__file__), "html_parser", "tabges", variant)
|
dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'tabges', variant)
|
||||||
htmlFile = os.path.join(dir, "tabges.htm")
|
htmlFile = os.path.join(dir, 'tabges.htm')
|
||||||
jsonFile = os.path.join(dir, "expected.json")
|
jsonFile = os.path.join(dir, 'expected.json')
|
||||||
|
|
||||||
with open(htmlFile, "r") as fp:
|
with open(htmlFile, 'r') as fp:
|
||||||
html = fp.read()
|
html = fp.read()
|
||||||
with open(jsonFile, "r") as fp:
|
with open(jsonFile, 'r') as fp:
|
||||||
jsonContent = json.load(fp)
|
jsonContent = json.load(fp)
|
||||||
|
|
||||||
return (html, jsonContent)
|
return (html, jsonContent)
|
||||||
|
|
||||||
|
|
||||||
def test_parsePreparationResult(fixture_parsePreparationResult):
|
def test_parsePreparationResult(fixture_parsePreparationResult):
|
||||||
html = fixture_parsePreparationResult[0]
|
html = fixture_parsePreparationResult[0]
|
||||||
jsonContent = fixture_parsePreparationResult[1]
|
jsonContent = fixture_parsePreparationResult[1]
|
||||||
@ -105,27 +99,25 @@ def test_parsePreparationResult(fixture_parsePreparationResult):
|
|||||||
|
|
||||||
assert ret == jsonContent
|
assert ret == jsonContent
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(1))
|
@pytest.fixture(params=range(1))
|
||||||
def fixture_cleanPreparationImport(request):
|
def fixture_cleanPreparationImport(request):
|
||||||
variant = str(request.param+1)
|
variant = str(request.param+1)
|
||||||
dir = os.path.join(os.path.dirname(__file__), "html_parser", "tabges", variant)
|
dir = os.path.join(os.path.dirname(__file__), 'html_parser', 'tabges', variant)
|
||||||
srcFile = os.path.join(dir, "expected.json")
|
srcFile = os.path.join(dir, 'expected.json')
|
||||||
expectedFile = os.path.join(dir, "cleaned.json")
|
expectedFile = os.path.join(dir, 'cleaned.json')
|
||||||
|
|
||||||
with open(srcFile, "r") as fp:
|
with open(srcFile, 'r') as fp:
|
||||||
source = json.load(fp)
|
source = json.load(fp)
|
||||||
with open(expectedFile, "r") as fp:
|
with open(expectedFile, 'r') as fp:
|
||||||
expected = json.load(fp)
|
expected = json.load(fp)
|
||||||
|
|
||||||
return (source, expected)
|
return (source, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_cleanPreparationImport(fixture_cleanPreparationImport):
|
def test_cleanPreparationImport(fixture_cleanPreparationImport):
|
||||||
src = fixture_cleanPreparationImport[0]
|
src = fixture_cleanPreparationImport[0]
|
||||||
expected = fixture_cleanPreparationImport[1]
|
expected = fixture_cleanPreparationImport[1]
|
||||||
|
|
||||||
parser = solo_turnier.html_parser.HtmlParser("")
|
parser = solo_turnier.html_parser.HtmlParser('')
|
||||||
parser.cleanPreparationRoundImport(src)
|
parser.cleanPreparationRoundImport(src)
|
||||||
|
|
||||||
assert src == expected
|
assert src == expected
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import solo_turnier.types as types
|
import solo_turnier.types as types
|
||||||
|
|
||||||
|
|
||||||
def test_HtmlPreviewParticipant_eq():
|
def test_HtmlPreviewParticipant_eq():
|
||||||
name = "Max Mustermann"
|
name = 'Max Mustermann'
|
||||||
id = 123
|
id = 123
|
||||||
group = "Kin"
|
group = 'Kin'
|
||||||
participant = types.HtmlPreviewParticipant(name, id, group)
|
participant = types.HtmlPreviewParticipant(name, id, group)
|
||||||
|
|
||||||
l = []
|
l = []
|
||||||
@ -14,6 +13,6 @@ def test_HtmlPreviewParticipant_eq():
|
|||||||
assert participant in l
|
assert participant in l
|
||||||
|
|
||||||
assert types.HtmlPreviewParticipant(name, id, group) in l
|
assert types.HtmlPreviewParticipant(name, id, group) in l
|
||||||
assert types.HtmlPreviewParticipant("Maxime Musterfrau", id, group) not in l
|
assert types.HtmlPreviewParticipant('Maxime Musterfrau', id, group) not in l
|
||||||
assert types.HtmlPreviewParticipant(name, 234, group) not in l
|
assert types.HtmlPreviewParticipant(name, 234, group) not in l
|
||||||
assert types.HtmlPreviewParticipant(name, id, "Jun") not in l
|
assert types.HtmlPreviewParticipant(name, id, 'Jun') not in l
|
||||||
|
@ -4,20 +4,17 @@ import json
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_mock
|
import pytest_mock
|
||||||
|
|
||||||
|
|
||||||
def __importJSONData(name):
|
def __importJSONData(name):
|
||||||
path = os.path.join(os.path.dirname(__file__), "worker", name)
|
path = os.path.join(os.path.dirname(__file__), 'worker', name)
|
||||||
with open(path, "r") as fp:
|
with open(path, 'r') as fp:
|
||||||
return json.load(fp)
|
return json.load(fp)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def fixture_csvExtractor():
|
def fixture_csvExtractor():
|
||||||
data = __importJSONData("csvImport.json")
|
data = __importJSONData('csvImport.json')
|
||||||
expected = __importJSONData("csvImportResult.json")
|
expected = __importJSONData('csvImportResult.json')
|
||||||
return (data, expected)
|
return (data, expected)
|
||||||
|
|
||||||
|
|
||||||
def test_csvExtractor(fixture_csvExtractor):
|
def test_csvExtractor(fixture_csvExtractor):
|
||||||
extractor = worker.CSVExtractor()
|
extractor = worker.CSVExtractor()
|
||||||
mapped = extractor.mapCSVImport(fixture_csvExtractor[0])
|
mapped = extractor.mapCSVImport(fixture_csvExtractor[0])
|
||||||
@ -26,276 +23,159 @@ def test_csvExtractor(fixture_csvExtractor):
|
|||||||
for i,elem in enumerate(fixture_csvExtractor[1]):
|
for i,elem in enumerate(fixture_csvExtractor[1]):
|
||||||
assert mapped[i].__dict__ == elem
|
assert mapped[i].__dict__ == elem
|
||||||
|
|
||||||
|
|
||||||
def test_extractPersonFromRow():
|
def test_extractPersonFromRow():
|
||||||
row = worker.ResultRow(
|
row = worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Beg./Adv.')
|
||||||
"Max",
|
|
||||||
"Mustermann",
|
|
||||||
"TSC Entenhausen",
|
|
||||||
"2",
|
|
||||||
"Kin",
|
|
||||||
"Adv.",
|
|
||||||
"Rumba",
|
|
||||||
"2",
|
|
||||||
"2",
|
|
||||||
"Kin.",
|
|
||||||
"Beg./Adv.",
|
|
||||||
)
|
|
||||||
person = worker.ResultPerson.extractFromResultRow(row)
|
person = worker.ResultPerson.extractFromResultRow(row)
|
||||||
expected = {
|
expected = {
|
||||||
"firstName": "Max",
|
'firstName': 'Max',
|
||||||
"lastName": "Mustermann",
|
'lastName': 'Mustermann',
|
||||||
"name": "Max Mustermann",
|
'name': 'Max Mustermann',
|
||||||
"club": "TSC Entenhausen",
|
'club': 'TSC Entenhausen',
|
||||||
"id": None,
|
'id': None,
|
||||||
"group": None,
|
'group': None
|
||||||
}
|
}
|
||||||
assert person.__dict__ == expected
|
assert person.__dict__ == expected
|
||||||
|
|
||||||
|
|
||||||
def test_extractCompetitionFromRow():
|
def test_extractCompetitionFromRow():
|
||||||
row = worker.ResultRow(
|
row = worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Beg./Adv.')
|
||||||
"Max",
|
|
||||||
"Mustermann",
|
|
||||||
"TSC Entenhausen",
|
|
||||||
"2",
|
|
||||||
"Kin",
|
|
||||||
"Adv.",
|
|
||||||
"Rumba",
|
|
||||||
"2",
|
|
||||||
"2",
|
|
||||||
"Kin.",
|
|
||||||
"Beg./Adv.",
|
|
||||||
)
|
|
||||||
competition = worker.CompetitionResult.extractFromResultRow(row)
|
competition = worker.CompetitionResult.extractFromResultRow(row)
|
||||||
expected = {
|
expected = {
|
||||||
"dance": "Rumba",
|
'dance': 'Rumba',
|
||||||
"class_": "Adv.",
|
'class_': 'Adv.',
|
||||||
"group": "Kin",
|
'group': 'Kin',
|
||||||
"place": "2",
|
'place': '2',
|
||||||
"placeTo": "2",
|
'placeTo': '2',
|
||||||
"id": 2,
|
'id': 2,
|
||||||
"finalist": None,
|
'finalist': None,
|
||||||
"competitionGroup": "Kin.",
|
'competitionGroup': 'Kin.',
|
||||||
"competitionClass": "Beg./Adv.",
|
'competitionClass': 'Beg./Adv.'
|
||||||
}
|
}
|
||||||
assert competition.__dict__ == expected
|
assert competition.__dict__ == expected
|
||||||
|
|
||||||
|
|
||||||
def test_combineRowsByPerson():
|
def test_combineRowsByPerson():
|
||||||
rows = [
|
rows = [
|
||||||
worker.ResultRow(
|
worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Cha Cha', '-', '-', 'Kin.', 'Adv.'),
|
||||||
"Max",
|
worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Adv.', 'Rumba', '2', '2', 'Kin.', 'Adv.'),
|
||||||
"Mustermann",
|
worker.ResultRow('Max', 'Mustermann', 'TSC Entenhausen', '2', 'Kin', 'Beg.', 'Jive', '1', '1', 'Kin.', 'Beg.'),
|
||||||
"TSC Entenhausen",
|
worker.ResultRow('Maxime', 'Musterfrau', '1. SC Entenhausen', '1', 'Kin', 'Adv.', 'Rumba', '1', '1', 'Kin.', 'Adv.')
|
||||||
"2",
|
|
||||||
"Kin",
|
|
||||||
"Adv.",
|
|
||||||
"Cha Cha",
|
|
||||||
"-",
|
|
||||||
"-",
|
|
||||||
"Kin.",
|
|
||||||
"Adv.",
|
|
||||||
),
|
|
||||||
worker.ResultRow(
|
|
||||||
"Max",
|
|
||||||
"Mustermann",
|
|
||||||
"TSC Entenhausen",
|
|
||||||
"2",
|
|
||||||
"Kin",
|
|
||||||
"Adv.",
|
|
||||||
"Rumba",
|
|
||||||
"2",
|
|
||||||
"2",
|
|
||||||
"Kin.",
|
|
||||||
"Adv.",
|
|
||||||
),
|
|
||||||
worker.ResultRow(
|
|
||||||
"Max",
|
|
||||||
"Mustermann",
|
|
||||||
"TSC Entenhausen",
|
|
||||||
"2",
|
|
||||||
"Kin",
|
|
||||||
"Beg.",
|
|
||||||
"Jive",
|
|
||||||
"1",
|
|
||||||
"1",
|
|
||||||
"Kin.",
|
|
||||||
"Beg.",
|
|
||||||
),
|
|
||||||
worker.ResultRow(
|
|
||||||
"Maxime",
|
|
||||||
"Musterfrau",
|
|
||||||
"1. SC Entenhausen",
|
|
||||||
"1",
|
|
||||||
"Kin",
|
|
||||||
"Adv.",
|
|
||||||
"Rumba",
|
|
||||||
"1",
|
|
||||||
"1",
|
|
||||||
"Kin.",
|
|
||||||
"Adv.",
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
result = dataWorker.combineRowsByPerson(rows)
|
result = dataWorker.combineRowsByPerson(rows)
|
||||||
expected = {
|
expected = {
|
||||||
worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen"): [
|
worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen'): [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', '2', 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin", "Adv.", "2", "2", "2", "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', '2', 'Kin.', 'Beg.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin", "Beg.", "1", "1", "2", "Kin.", "Beg."
|
|
||||||
),
|
|
||||||
],
|
|
||||||
worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen"): [
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Rumba", "Kin", "Adv.", "1", "1", "1", "Kin.", "Adv."
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
|
worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen'): [
|
||||||
|
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', '1', 'Kin.', 'Adv.')
|
||||||
|
]
|
||||||
}
|
}
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
def test_checkUniqueIds_True():
|
def test_checkUniqueIds_True():
|
||||||
person1 = worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen")
|
person1 = worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen')
|
||||||
person2 = worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen")
|
person2 = worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen')
|
||||||
data = {
|
data = {
|
||||||
person1: [
|
person1: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', 2, 'Kin.', 'Beg.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin", "Beg.", "1", "1", 2, "Kin.", "Beg."
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
person2: [
|
person2: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.')
|
||||||
"Rumba", "Kin", "Adv.", "1", "1", 1, "Kin.", "Adv."
|
]
|
||||||
)
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
assert dataWorker.checkUniqueIds(data) == True
|
assert dataWorker.checkUniqueIds(data) == True
|
||||||
assert person1.id == 2
|
assert person1.id == 2
|
||||||
assert person2.id == 1
|
assert person2.id == 1
|
||||||
|
|
||||||
|
|
||||||
def test_checkUniqueIds_False():
|
def test_checkUniqueIds_False():
|
||||||
person1 = worker.ResultPerson("Max", "Mustermann", "TSC Entenhausen")
|
person1 = worker.ResultPerson('Max', 'Mustermann', 'TSC Entenhausen')
|
||||||
person2 = worker.ResultPerson("Maxime", "Musterfrau", "1. SC Entenhausen")
|
person2 = worker.ResultPerson('Maxime', 'Musterfrau', '1. SC Entenhausen')
|
||||||
data = {
|
data = {
|
||||||
person1: [
|
person1: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Kin', 'Beg.', '1', '1', 3, 'Kin.', 'Beg.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin", "Beg.", "1", "1", 3, "Kin.", "Beg."
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
person2: [
|
person2: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.')
|
||||||
"Rumba", "Kin", "Adv.", "1", "1", 1, "Kin.", "Adv."
|
]
|
||||||
)
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
assert dataWorker.checkUniqueIds(data) == False
|
assert dataWorker.checkUniqueIds(data) == False
|
||||||
assert person1.id == None
|
assert person1.id == None
|
||||||
assert person2.id == 1
|
assert person2.id == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(5))
|
@pytest.fixture(params=range(5))
|
||||||
def fixture_consolidateGroups(request):
|
def fixture_consolidateGroups(request):
|
||||||
person1 = worker.ResultPerson("Max 1", "Mustermann", "TSC Entenhausen")
|
person1 = worker.ResultPerson('Max 1', 'Mustermann', 'TSC Entenhausen')
|
||||||
person2 = worker.ResultPerson("Max 2", "Mustermann", "TSC Entenhausen")
|
person2 = worker.ResultPerson('Max 2', 'Mustermann', 'TSC Entenhausen')
|
||||||
person3 = worker.ResultPerson("Max 3", "Mustermann", "TSC Entenhausen")
|
person3 = worker.ResultPerson('Max 3', 'Mustermann', 'TSC Entenhausen')
|
||||||
person4 = worker.ResultPerson("Max 4", "Mustermann", "TSC Entenhausen")
|
person4 = worker.ResultPerson('Max 4', 'Mustermann', 'TSC Entenhausen')
|
||||||
# persons = (person1, person2, person3, person4)
|
# persons = (person1, person2, person3, person4)
|
||||||
|
|
||||||
dict1 = {
|
dict1 = {
|
||||||
person1: [
|
person1: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '1', 3, 'Kin.', 'Beg.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin.", "Beg.", "1", "1", 3, "Kin.", "Beg."
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
dict2 = {
|
dict2 = {
|
||||||
person2: [
|
person2: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Kin./Jun.', 'Beg.', '1', '1', 3, 'Kin./Jun.', 'Beg.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin./Jun.", "Beg.", "1", "1", 3, "Kin./Jun.", "Beg."
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
dict3 = {
|
dict3 = {
|
||||||
person3: [
|
person3: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.')
|
||||||
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
dict4 = {
|
dict4 = {
|
||||||
person4: [
|
person4: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin./Jun.', 'Adv.', '2', '2', 2, 'Kin./Jun.', 'Adv.')
|
||||||
"Rumba", "Kin./Jun.", "Adv.", "2", "2", 2, "Kin./Jun.", "Adv."
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
dict5 = {
|
dict5 = {
|
||||||
person4: [
|
person4: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin./Jun.', 'Adv.', '2', '2', 2, 'Kin./Jun.', 'Adv.'),
|
||||||
"Rumba", "Kin./Jun.", "Adv.", "2", "2", 2, "Kin./Jun.", "Adv."
|
worker.CompetitionResult('Cha Cha', 'Jun./Jug.', 'Beg.', '3', '4', 2, 'Jun./Jug.', 'Beg.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Cha Cha", "Jun./Jug.", "Beg.", "3", "4", 2, "Jun./Jug.", "Beg."
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
cases = (
|
cases = (
|
||||||
(dict1|dict3, (True, False), {}),
|
(dict1|dict3, (True, False), {}),
|
||||||
(dict1|dict2|dict3, (True, True), {}),
|
(dict1|dict2|dict3, (True, True), {}),
|
||||||
(dict4, (False, False), {person4: "Kin./Jun."}),
|
(dict4, (False, False), {person4: 'Kin./Jun.'}),
|
||||||
(dict1 | dict2 | dict3 | dict4, (False, True), {person4: "Kin./Jun."}),
|
(dict1|dict2|dict3|dict4, (False, True), {person4: 'Kin./Jun.'}),
|
||||||
(dict5, (True, True), {person4: "Jun."}),
|
(dict5, (True, True), {person4: 'Jun.'}),
|
||||||
)
|
)
|
||||||
return cases[request.param]
|
return cases[request.param]
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(2))
|
@pytest.fixture(params=range(2))
|
||||||
def fixture_consolidateGroups_fail(request, fixture_consolidateGroups):
|
def fixture_consolidateGroups_fail(request, fixture_consolidateGroups):
|
||||||
person = worker.ResultPerson("Max 5", "Mustermann", "TSC Entenhausen")
|
person = worker.ResultPerson('Max 5', 'Mustermann', 'TSC Entenhausen')
|
||||||
|
|
||||||
dict1 = {
|
dict1 = {
|
||||||
person: [
|
person: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '1', '1', 3, 'Jun.', 'Adv.')
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Jun.", "Beg.", "1", "1", 3, "Jun.", "Adv."
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
dict2 = {
|
dict2 = {
|
||||||
person: [
|
person: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
"Rumba", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
worker.CompetitionResult('Jive', 'Hgr', 'Beg.', '1', '1', 3, 'Hgr', 'Adv.')
|
||||||
),
|
|
||||||
worker.CompetitionResult("Jive", "Hgr", "Beg.", "1", "1", 3, "Hgr", "Adv."),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
cases = (dict1 | fixture_consolidateGroups[0], dict2 | fixture_consolidateGroups[0])
|
cases = (
|
||||||
|
dict1 | fixture_consolidateGroups[0],
|
||||||
|
dict2 | fixture_consolidateGroups[0]
|
||||||
|
)
|
||||||
return cases[request.param]
|
return cases[request.param]
|
||||||
|
|
||||||
|
|
||||||
def test_consolidateGroups(fixture_consolidateGroups):
|
def test_consolidateGroups(fixture_consolidateGroups):
|
||||||
data = fixture_consolidateGroups[0]
|
data = fixture_consolidateGroups[0]
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
@ -303,8 +183,7 @@ def test_consolidateGroups(fixture_consolidateGroups):
|
|||||||
assert dataWorker.consolidateGroups(data) == fixture_consolidateGroups[1]
|
assert dataWorker.consolidateGroups(data) == fixture_consolidateGroups[1]
|
||||||
|
|
||||||
for person in data:
|
for person in data:
|
||||||
assert person.group == fixture_consolidateGroups[2].get(person, "Kin.")
|
assert person.group == fixture_consolidateGroups[2].get(person, 'Kin.')
|
||||||
|
|
||||||
|
|
||||||
def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
|
def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
|
||||||
data = fixture_consolidateGroups_fail
|
data = fixture_consolidateGroups_fail
|
||||||
@ -313,143 +192,105 @@ def test_consolidateGroups_failing(fixture_consolidateGroups_fail):
|
|||||||
with pytest.raises(Exception):
|
with pytest.raises(Exception):
|
||||||
dataWorker.consolidateGroups(data)
|
dataWorker.consolidateGroups(data)
|
||||||
|
|
||||||
|
|
||||||
def test_createHtmlLUT(mocker):
|
def test_createHtmlLUT(mocker):
|
||||||
mock = mocker.patch("solo_turnier.html_parser.HtmlParser.guessDataFromHtmlTitle")
|
mock = mocker.patch('solo_turnier.html_parser.HtmlParser.guessDataFromHtmlTitle')
|
||||||
mock.side_effect= [
|
mock.side_effect= [
|
||||||
{"group": "group1", "class_": "class1", "dance": "dance1"},
|
{'group': 'group1', 'class_': 'class1', 'dance': 'dance1'},
|
||||||
{"group": "group2", "class_": "class2", "dance": "dance2"},
|
{'group': 'group2', 'class_': 'class2', 'dance': 'dance2'},
|
||||||
{"group": "group3", "class_": "class3", "dance": "dance3"},
|
{'group': 'group3', 'class_': 'class3', 'dance': 'dance3'},
|
||||||
]
|
]
|
||||||
|
|
||||||
importMock1 = mocker.patch("solo_turnier.html_parser.HtmlImport")
|
importMock1 = mocker.patch('solo_turnier.html_parser.HtmlImport')
|
||||||
importMock2 = mocker.patch("solo_turnier.html_parser.HtmlImport")
|
importMock2 = mocker.patch('solo_turnier.html_parser.HtmlImport')
|
||||||
importMock3 = mocker.patch("solo_turnier.html_parser.HtmlImport")
|
importMock3 = mocker.patch('solo_turnier.html_parser.HtmlImport')
|
||||||
importMock1.title = "Fake title 1"
|
importMock1.title = 'Fake title 1'
|
||||||
importMock2.title = "Fake title 2"
|
importMock2.title = 'Fake title 2'
|
||||||
importMock3.title = "Fake title 3"
|
importMock3.title = 'Fake title 3'
|
||||||
|
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
structure = dataWorker._createHtmlLUT([importMock1, importMock2, importMock3])
|
structure = dataWorker._createHtmlLUT([importMock1, importMock2, importMock3])
|
||||||
|
|
||||||
expected = {
|
expected = {
|
||||||
("group1", "class1", "dance1"): importMock1,
|
('group1', 'class1', 'dance1'): importMock1,
|
||||||
("group2", "class2", "dance2"): importMock2,
|
('group2', 'class2', 'dance2'): importMock2,
|
||||||
("group3", "class3", "dance3"): importMock3,
|
('group3', 'class3', 'dance3'): importMock3,
|
||||||
}
|
}
|
||||||
assert expected == structure
|
assert expected == structure
|
||||||
|
|
||||||
|
|
||||||
def test_mergeHtmlData(mocker):
|
def test_mergeHtmlData(mocker):
|
||||||
person1 = worker.ResultPerson("Max 1", "Mustermann", "TSC Entenhausen")
|
person1 = worker.ResultPerson('Max 1', 'Mustermann', 'TSC Entenhausen')
|
||||||
person2 = worker.ResultPerson("Max 2", "Mustermann", "TSC Entenhausen")
|
person2 = worker.ResultPerson('Max 2', 'Mustermann', 'TSC Entenhausen')
|
||||||
person3 = worker.ResultPerson("Max 3", "Mustermann", "TSC Entenhausen")
|
person3 = worker.ResultPerson('Max 3', 'Mustermann', 'TSC Entenhausen')
|
||||||
person4 = worker.ResultPerson("Max 4", "Mustermann", "TSC Entenhausen")
|
person4 = worker.ResultPerson('Max 4', 'Mustermann', 'TSC Entenhausen')
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
person1: [
|
person1: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '1', '1', 1, 'Kin./Jun.', 'Beg.'),
|
||||||
"Rumba", "Kin.", "Beg.", "1", "1", 1, "Kin./Jun.", "Beg."
|
worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 1, 'Kin.', 'Adv.'),
|
||||||
),
|
worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '2', 1, 'Kin.', 'Beg.'),
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Beg.', '1', '1', 1, 'Kin.', 'Newc./Beg.'),
|
||||||
"Cha Cha", "Kin.", "Adv.", "1", "1", 1, "Kin.", "Adv."
|
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin.", "Beg.", "1", "2", 1, "Kin.", "Beg."
|
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Langs. Walzer", "Kin.", "Beg.", "1", "1", 1, "Kin.", "Newc./Beg."
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
person2: [
|
person2: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '2', '2', 2, 'Kin./Jun.', 'Beg.'),
|
||||||
"Rumba", "Kin.", "Beg.", "2", "2", 2, "Kin./Jun.", "Beg."
|
worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '2', '2', 2, 'Kin.', 'Adv.'),
|
||||||
),
|
worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '1', '2', 2, 'Kin.', 'Beg.'),
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc.', '1', '1', 2, 'Kin.', 'Newc./Beg.'),
|
||||||
"Cha Cha", "Kin.", "Adv.", "2", "2", 2, "Kin.", "Adv."
|
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Jive", "Kin.", "Beg.", "1", "2", 2, "Kin.", "Beg."
|
|
||||||
),
|
|
||||||
worker.CompetitionResult(
|
|
||||||
"Langs. Walzer", "Kin.", "Newc.", "1", "1", 2, "Kin.", "Newc./Beg."
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
person3: [
|
person3: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Jun.', 'Beg.', '1', '1', 3, 'Kin./Jun.', 'Beg.'),
|
||||||
"Rumba", "Jun.", "Beg.", "1", "1", 3, "Kin./Jun.", "Beg."
|
|
||||||
),
|
|
||||||
# worker.CompetitionResult('Cha Cha', 'Jun.', 'Adv.', '1', '1', 3, 'Kin.', 'Adv.'),
|
# worker.CompetitionResult('Cha Cha', 'Jun.', 'Adv.', '1', '1', 3, 'Kin.', 'Adv.'),
|
||||||
# worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '2', '2', 3, 'Kin.', 'Beg.'),
|
# worker.CompetitionResult('Jive', 'Jun.', 'Beg.', '2', '2', 3, 'Kin.', 'Beg.'),
|
||||||
# worker.CompetitionResult('Langs. Walzer', 'Jun.', 'Newc./Beg.', '1', '1', 3, 'Kin.', 'Beg.'),
|
# worker.CompetitionResult('Langs. Walzer', 'Jun.', 'Newc./Beg.', '1', '1', 3, 'Kin.', 'Beg.'),
|
||||||
],
|
],
|
||||||
person4: [
|
person4: [
|
||||||
worker.CompetitionResult(
|
worker.CompetitionResult('Rumba', 'Kin.', 'Beg.', '3', '3', 4, 'Kin./Jun.', 'Beg.'),
|
||||||
"Rumba", "Kin.", "Beg.", "3", "3", 4, "Kin./Jun.", "Beg."
|
|
||||||
),
|
|
||||||
# worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 4, 'Kin.', 'Adv.'),
|
# worker.CompetitionResult('Cha Cha', 'Kin.', 'Adv.', '1', '1', 4, 'Kin.', 'Adv.'),
|
||||||
# worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '2', '2', 4, 'Kin.', 'Beg.'),
|
# worker.CompetitionResult('Jive', 'Kin.', 'Beg.', '2', '2', 4, 'Kin.', 'Beg.'),
|
||||||
# worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc./Beg.', '1', '1', 4, 'Kin.', 'Beg.'),
|
# worker.CompetitionResult('Langs. Walzer', 'Kin.', 'Newc./Beg.', '1', '1', 4, 'Kin.', 'Beg.'),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlParticipant1Dance1 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
|
htmlParticipant1Dance1 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
|
||||||
htmlParticipant1Dance2 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
|
htmlParticipant1Dance2 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
|
||||||
htmlParticipant1Dance3 = html_parser.HtmlParticipant(
|
htmlParticipant1Dance3 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.-2.', True)
|
||||||
"Max 1 Mustermann", "1.-2.", True
|
htmlParticipant1Dance4 = html_parser.HtmlParticipant('Max 1 Mustermann', '1.', True)
|
||||||
)
|
|
||||||
htmlParticipant1Dance4 = html_parser.HtmlParticipant("Max 1 Mustermann", "1.", True)
|
|
||||||
|
|
||||||
htmlParticipant2Dance1 = html_parser.HtmlParticipant("Max 2 Mustermann", "2.", True)
|
htmlParticipant2Dance1 = html_parser.HtmlParticipant('Max 2 Mustermann', '2.', True)
|
||||||
htmlParticipant2Dance2 = html_parser.HtmlParticipant("Max 2 Mustermann", "2.", True)
|
htmlParticipant2Dance2 = html_parser.HtmlParticipant('Max 2 Mustermann', '2.', True)
|
||||||
htmlParticipant2Dance3 = html_parser.HtmlParticipant(
|
htmlParticipant2Dance3 = html_parser.HtmlParticipant('Max 2 Mustermann', '1.-2.', True)
|
||||||
"Max 2 Mustermann", "1.-2.", True
|
htmlParticipant2Dance4 = html_parser.HtmlParticipant('Max 2 Mustermann', '1.', True)
|
||||||
)
|
|
||||||
htmlParticipant2Dance4 = html_parser.HtmlParticipant("Max 2 Mustermann", "1.", True)
|
|
||||||
|
|
||||||
htmlParticipant3Dance1 = html_parser.HtmlParticipant("Max 3 Mustermann", "1.", True)
|
htmlParticipant3Dance1 = html_parser.HtmlParticipant('Max 3 Mustermann', '1.', True)
|
||||||
|
|
||||||
htmlParticipant4Dance1 = html_parser.HtmlParticipant(
|
htmlParticipant4Dance1 = html_parser.HtmlParticipant('Max 4 Mustermann', '3.', False)
|
||||||
"Max 4 Mustermann", "3.", False
|
|
||||||
)
|
|
||||||
|
|
||||||
htmlParticipantsDance1 = {
|
htmlParticipantsDance1 = {
|
||||||
"1": htmlParticipant1Dance1,
|
'1': htmlParticipant1Dance1,
|
||||||
"2": htmlParticipant2Dance1,
|
'2': htmlParticipant2Dance1,
|
||||||
"3": htmlParticipant3Dance1,
|
'3': htmlParticipant3Dance1,
|
||||||
"4": htmlParticipant4Dance1,
|
'4': htmlParticipant4Dance1
|
||||||
}
|
}
|
||||||
htmlParticipantsDance2 = {
|
htmlParticipantsDance2 = {
|
||||||
"1": htmlParticipant1Dance2,
|
'1': htmlParticipant1Dance2,
|
||||||
"2": htmlParticipant2Dance2,
|
'2': htmlParticipant2Dance2,
|
||||||
}
|
}
|
||||||
htmlParticipantsDance3 = {
|
htmlParticipantsDance3 = {
|
||||||
"1": htmlParticipant1Dance3,
|
'1': htmlParticipant1Dance3,
|
||||||
"2": htmlParticipant2Dance3,
|
'2': htmlParticipant2Dance3,
|
||||||
}
|
}
|
||||||
htmlParticipantsDance4 = {
|
htmlParticipantsDance4 = {
|
||||||
"1": htmlParticipant1Dance4,
|
'1': htmlParticipant1Dance4,
|
||||||
"2": htmlParticipant2Dance4,
|
'2': htmlParticipant2Dance4,
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlCompetition1 = html_parser.HtmlImport(
|
htmlCompetition1 = html_parser.HtmlImport('ETW, Solos Kin./Jun. Beginner Rumba', htmlParticipantsDance1)
|
||||||
"ETW, Solos Kin./Jun. Beginner Rumba", htmlParticipantsDance1
|
htmlCompetition2 = html_parser.HtmlImport('ETW, Solos Kin. Advanced Cha Cha', htmlParticipantsDance2)
|
||||||
)
|
htmlCompetition3 = html_parser.HtmlImport('ETW, Solos Kinder Beginner Jive', htmlParticipantsDance3)
|
||||||
htmlCompetition2 = html_parser.HtmlImport(
|
htmlCompetition4 = html_parser.HtmlImport('ETW, Solos Kin. Newc./Beg. Langs. Walzer', htmlParticipantsDance4)
|
||||||
"ETW, Solos Kin. Advanced Cha Cha", htmlParticipantsDance2
|
|
||||||
)
|
|
||||||
htmlCompetition3 = html_parser.HtmlImport(
|
|
||||||
"ETW, Solos Kinder Beginner Jive", htmlParticipantsDance3
|
|
||||||
)
|
|
||||||
htmlCompetition4 = html_parser.HtmlImport(
|
|
||||||
"ETW, Solos Kin. Newc./Beg. Langs. Walzer", htmlParticipantsDance4
|
|
||||||
)
|
|
||||||
|
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
|
|
||||||
dataWorker.mergeHtmlData(
|
dataWorker.mergeHtmlData(data, [htmlCompetition1, htmlCompetition2, htmlCompetition3, htmlCompetition4])
|
||||||
data, [htmlCompetition1, htmlCompetition2, htmlCompetition3, htmlCompetition4]
|
|
||||||
)
|
|
||||||
|
|
||||||
person1Finalist = [c.finalist for c in data[person1]]
|
person1Finalist = [c.finalist for c in data[person1]]
|
||||||
person2Finalist = [c.finalist for c in data[person2]]
|
person2Finalist = [c.finalist for c in data[person2]]
|
||||||
@ -470,54 +311,28 @@ def test_mergeHtmlData(mocker):
|
|||||||
|
|
||||||
assert finalists == expectedFinalists
|
assert finalists == expectedFinalists
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(params=range(4))
|
@pytest.fixture(params=range(4))
|
||||||
def fixture_getAllDancesInCompetition(request, mocker):
|
def fixture_getAllDancesInCompetition(request, mocker):
|
||||||
def mockCompetition(comp):
|
def mockCompetition(comp):
|
||||||
def mockUser():
|
def mockUser():
|
||||||
return mocker.patch("solo_turnier.worker.ResultPerson")
|
return mocker.patch('solo_turnier.worker.ResultPerson')
|
||||||
|
|
||||||
def mockDances(dances):
|
def mockDances(dances):
|
||||||
def mockDance(name):
|
def mockDance(name):
|
||||||
mock = mocker.patch("solo_turnier.worker.CompetitionResult")
|
mock = mocker.patch('solo_turnier.worker.CompetitionResult')
|
||||||
mock.dance = name
|
mock.dance = name
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
return [mockDance(d) for d in dances]
|
return [mockDance(d) for d in dances]
|
||||||
|
|
||||||
return {mockUser(): mockDances(dances) for dances in comp}
|
return {mockUser(): mockDances(dances) for dances in comp}
|
||||||
|
|
||||||
cases = (
|
cases = (
|
||||||
([["Samba"]], ["Samba"]),
|
([['Samba']], ['Samba']),
|
||||||
([["Samba", "Rumba"], ["Cha Cha"]], ["Samba", "Cha Cha", "Rumba"]),
|
([['Samba', 'Rumba'], ['Cha Cha']], ['Samba', 'Cha Cha', 'Rumba']),
|
||||||
(
|
([['Samba', 'Rumba'], ['Cha Cha', 'Tango', 'Langs. Walzer']], ['Samba', 'Cha Cha', 'Rumba', 'Langs. Walzer', 'Tango']),
|
||||||
[["Samba", "Rumba"], ["Cha Cha", "Tango", "Langs. Walzer"]],
|
([['Cha Cha', 'Rumba', 'Jive'], ['Quickstep', 'Tango', 'Wiener Walzer', 'Langs. Walzer'], ['Slowfox', 'Langs. Walzer', 'Paso Doble', 'Samba']], ['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive', 'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'])
|
||||||
["Samba", "Cha Cha", "Rumba", "Langs. Walzer", "Tango"],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
[
|
|
||||||
["Cha Cha", "Rumba", "Jive"],
|
|
||||||
["Quickstep", "Tango", "Wiener Walzer", "Langs. Walzer"],
|
|
||||||
["Slowfox", "Langs. Walzer", "Paso Doble", "Samba"],
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"Samba",
|
|
||||||
"Cha Cha",
|
|
||||||
"Rumba",
|
|
||||||
"Paso Doble",
|
|
||||||
"Jive",
|
|
||||||
"Langs. Walzer",
|
|
||||||
"Tango",
|
|
||||||
"Wiener Walzer",
|
|
||||||
"Slowfox",
|
|
||||||
"Quickstep",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
case = cases[request.param]
|
case = cases[request.param]
|
||||||
return (mockCompetition(case[0]), case[1])
|
return (mockCompetition(case[0]), case[1])
|
||||||
|
|
||||||
|
|
||||||
def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
|
def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
|
||||||
print(fixture_getAllDancesInCompetition)
|
print(fixture_getAllDancesInCompetition)
|
||||||
data = fixture_getAllDancesInCompetition[0]
|
data = fixture_getAllDancesInCompetition[0]
|
||||||
@ -525,76 +340,57 @@ def test_getAllDancesInCompetitions(fixture_getAllDancesInCompetition):
|
|||||||
ret = dataWorker.getAllDancesInCompetitions(data)
|
ret = dataWorker.getAllDancesInCompetitions(data)
|
||||||
assert ret == fixture_getAllDancesInCompetition[1]
|
assert ret == fixture_getAllDancesInCompetition[1]
|
||||||
|
|
||||||
|
|
||||||
def test_collectPersonsInGroups(mocker):
|
def test_collectPersonsInGroups(mocker):
|
||||||
def mockPerson(group):
|
def mockPerson(group):
|
||||||
mock = mocker.patch("solo_turnier.worker.ResultPerson")
|
mock = mocker.patch('solo_turnier.worker.ResultPerson')
|
||||||
mock.group = group
|
mock.group = group
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
persons = (
|
persons = (
|
||||||
mockPerson("Kin."),
|
mockPerson('Kin.'), mockPerson('Kin.'), mockPerson('Jun.'),
|
||||||
mockPerson("Kin."),
|
mockPerson('Kin.'), mockPerson(None), mockPerson('Jug.'),
|
||||||
mockPerson("Jun."),
|
mockPerson(None), mockPerson('Kin./Jun.'), mockPerson('Jun.')
|
||||||
mockPerson("Kin."),
|
|
||||||
mockPerson(None),
|
|
||||||
mockPerson("Jug."),
|
|
||||||
mockPerson(None),
|
|
||||||
mockPerson("Kin./Jun."),
|
|
||||||
mockPerson("Jun."),
|
|
||||||
)
|
)
|
||||||
data = {p: [] for p in persons}
|
data = {p: [] for p in persons}
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
groups = dataWorker.collectPersonsInGroups(data)
|
groups = dataWorker.collectPersonsInGroups(data)
|
||||||
|
|
||||||
assert groups["Kin."] == [persons[0], persons[1], persons[3]]
|
assert groups['Kin.'] == [persons[0], persons[1], persons[3]]
|
||||||
assert groups["Jun."] == [persons[2], persons[8]]
|
assert groups['Jun.'] == [persons[2], persons[8]]
|
||||||
assert groups["Jug."] == [persons[5]]
|
assert groups['Jug.'] == [persons[5]]
|
||||||
assert groups["Sonst"] == [persons[4], persons[6], persons[7]]
|
assert groups['Sonst'] == [persons[4], persons[6], persons[7]]
|
||||||
|
|
||||||
|
|
||||||
def test_sortPersons_withId(mocker):
|
def test_sortPersons_withId(mocker):
|
||||||
def mockPerson(id):
|
def mockPerson(id):
|
||||||
mock = mocker.patch("solo_turnier.worker.ResultPerson")
|
mock = mocker.patch('solo_turnier.worker.ResultPerson')
|
||||||
mock.id = id
|
mock.id = id
|
||||||
return mock
|
return mock
|
||||||
|
|
||||||
persons = [mockPerson(2), mockPerson(1), mockPerson(5), mockPerson(3)]
|
persons = [mockPerson(2), mockPerson(1), mockPerson(5), mockPerson(3)]
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
|
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
|
||||||
assert sorted == [persons[1], persons[0], persons[3], persons[2]]
|
assert sorted == [persons[1], persons[0], persons[3], persons[2]]
|
||||||
assert showIds == True
|
assert showIds == True
|
||||||
|
|
||||||
|
|
||||||
def test_sortPersons_withoutId(mocker):
|
def test_sortPersons_withoutId(mocker):
|
||||||
def mockPerson(name):
|
def mockPerson(name):
|
||||||
mock = mocker.patch("solo_turnier.worker.ResultPerson")
|
mock = mocker.patch('solo_turnier.worker.ResultPerson')
|
||||||
mock.id = 3
|
mock.id = 3
|
||||||
mock.name = name
|
mock.name = name
|
||||||
mock.club = "TSC Entenhausen"
|
mock.club = 'TSC Entenhausen'
|
||||||
return mock
|
return mock
|
||||||
|
persons = [mockPerson('Max'), mockPerson('Isabel'), mockPerson('Reimund'), mockPerson('Anna')]
|
||||||
persons = [
|
|
||||||
mockPerson("Max"),
|
|
||||||
mockPerson("Isabel"),
|
|
||||||
mockPerson("Reimund"),
|
|
||||||
mockPerson("Anna"),
|
|
||||||
]
|
|
||||||
persons[2].id = None
|
persons[2].id = None
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
|
sorted, showIds = dataWorker.sortPersonsInGroup(persons)
|
||||||
assert sorted == [persons[3], persons[1], persons[0], persons[2]]
|
assert sorted == [persons[3], persons[1], persons[0], persons[2]]
|
||||||
assert showIds == False
|
assert showIds == False
|
||||||
|
|
||||||
|
|
||||||
def test_mapPersonResultsToDanceList(mocker):
|
def test_mapPersonResultsToDanceList(mocker):
|
||||||
def mockResult(dance):
|
def mockResult(dance):
|
||||||
mock = mocker.patch("solo_turnier.worker.CompetitionResult")
|
mock = mocker.patch('solo_turnier.worker.CompetitionResult')
|
||||||
mock.dance = dance
|
mock.dance = dance
|
||||||
return mock
|
return mock
|
||||||
|
dances = ['Cha Cha', 'Rumba', 'Langs. Walzer', 'Quickstep']
|
||||||
dances = ["Cha Cha", "Rumba", "Langs. Walzer", "Quickstep"]
|
results = [mockResult('Rumba'), mockResult('Quickstep'), mockResult('Cha Cha')]
|
||||||
results = [mockResult("Rumba"), mockResult("Quickstep"), mockResult("Cha Cha")]
|
|
||||||
dataWorker = worker.DataWorker()
|
dataWorker = worker.DataWorker()
|
||||||
mappedResults = dataWorker.mapPersonResultsToDanceList(results, dances)
|
mappedResults = dataWorker.mapPersonResultsToDanceList(results, dances)
|
||||||
assert mappedResults == [results[2], results[0], None, results[1]]
|
assert mappedResults == [results[2], results[0], None, results[1]]
|
||||||
|
333
src/solo_turnier/types.py
Normal file
333
src/solo_turnier/types.py
Normal file
@ -0,0 +1,333 @@
|
|||||||
|
|
||||||
|
from . import group
|
||||||
|
from . import competition_class
|
||||||
|
|
||||||
|
class CSVResultRow:
|
||||||
|
def __init__(self, firstName, lastName, club, id, group, class_, dance, place, placeTo, competitionGroup, competitionClass):
|
||||||
|
self.firstName = firstName
|
||||||
|
self.lastName = lastName
|
||||||
|
self.name = f'{firstName} {lastName}'
|
||||||
|
self.club = club
|
||||||
|
self.id = id
|
||||||
|
self.group = group
|
||||||
|
self.class_ = class_
|
||||||
|
self.dance = dance
|
||||||
|
self.place = place
|
||||||
|
self.placeTo = placeTo
|
||||||
|
self.competitionGroup = competitionGroup
|
||||||
|
self.competitionClass = competitionClass
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'{self.name} ({self.id}, {self.club}) is in {self.group} {self.class_} and danced the {self.dance} in {self.competitionGroup} {self.competitionClass} getting place {self.place}-{self.placeTo}'
|
||||||
|
|
||||||
|
class HtmlPreviewParticipant:
|
||||||
|
def __init__(self, name, id, group_):
|
||||||
|
self.name = name
|
||||||
|
self.id = id
|
||||||
|
groupParser = group.GroupParser()
|
||||||
|
self.group = groupParser.parseClass(group_)
|
||||||
|
|
||||||
|
def __eq__(self, o):
|
||||||
|
if type(o) != HtmlPreviewParticipant:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return all(map(lambda x, y: x == y, (self.name, self.id, self.group), (o.name, o.id, o.group)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'{self.id} ({self.name}, {self.group})'
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.id, self.name, self.group))
|
||||||
|
|
||||||
|
class HtmlParticipant:
|
||||||
|
def __init__(self, name, id):
|
||||||
|
self.name = name
|
||||||
|
self.id = id
|
||||||
|
self.finalist = None
|
||||||
|
|
||||||
|
def __eq__(self, o):
|
||||||
|
if type(o) != HtmlPreviewParticipant:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return all(map(lambda x, y: x == y, (self.name, self.id, self.group), (o.name, o.id, o.group)))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'{self.id}: {self.name}'
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.id, self.name))
|
||||||
|
|
||||||
|
# class PreviewParticipationData:
|
||||||
|
# def __init__(self, dance: str, class_: competition_class.CompetitionClass):
|
||||||
|
# self.class_ = class_
|
||||||
|
# self.dance = dance
|
||||||
|
|
||||||
|
class HtmlPreviewImport:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
participants: dict[int, list[HtmlPreviewParticipant]],
|
||||||
|
results: dict[HtmlPreviewParticipant, dict[str, competition_class.CompetitionClass]]
|
||||||
|
):
|
||||||
|
self.participants = participants
|
||||||
|
self.results = results
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return (str(self.participants), str(self.results))
|
||||||
|
|
||||||
|
class HtmlResultImport:
|
||||||
|
def __init__(self, results: dict[HtmlParticipant, str]):
|
||||||
|
self.results = results
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.results)
|
||||||
|
|
||||||
|
class HtmlResultTotalTable:
|
||||||
|
def __init__(self, participants):
|
||||||
|
self.participants = participants
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self.participants)
|
||||||
|
|
||||||
|
class HtmlCompetitionResultRow:
|
||||||
|
def __init__(self, name, id, dance, group, class_, place, placeTo, finalist):
|
||||||
|
self.dance = dance
|
||||||
|
self.group = group
|
||||||
|
self.class_ = class_
|
||||||
|
self.place = place
|
||||||
|
self.placeTo = placeTo
|
||||||
|
self.id = int(id)
|
||||||
|
self.name = name
|
||||||
|
self.finalist = finalist
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.place == self.placeTo:
|
||||||
|
result = f'{self.place}.'
|
||||||
|
else:
|
||||||
|
result = f'{self.place}.-{self.placeTo}.'
|
||||||
|
|
||||||
|
if self.finalist == True:
|
||||||
|
finalist = '[F]'
|
||||||
|
else:
|
||||||
|
finalist = ''
|
||||||
|
return f'Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})'
|
||||||
|
|
||||||
|
def __eq__(self, o):
|
||||||
|
if not isinstance(o, CompetitionResult):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return (
|
||||||
|
self.dance == o.dance and
|
||||||
|
self.competitionClass == o.competitionClass and
|
||||||
|
self.competitionGroup == o.competitionGroup and
|
||||||
|
self.place == o.place and self.placeTo == o.placeTo and
|
||||||
|
self.id == o.id
|
||||||
|
)
|
||||||
|
|
||||||
|
class HtmlSingleCompetitionResult:
|
||||||
|
def __init__(self, name, place, placeTo, finalist):
|
||||||
|
self.name = name
|
||||||
|
self.place = place
|
||||||
|
self.placeTo = placeTo
|
||||||
|
self.finalist = finalist
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
if self.placeTo is None:
|
||||||
|
place = self.place
|
||||||
|
else:
|
||||||
|
place = f'{self.place}-{self.placeTo}'
|
||||||
|
|
||||||
|
if self.finalist:
|
||||||
|
return f'Res({self.name} [F], placed {place})'
|
||||||
|
else:
|
||||||
|
return f'Res({self.name}, placed {place})'
|
||||||
|
|
||||||
|
class HtmlCompetitionTotalResults:
|
||||||
|
def __init__(self):
|
||||||
|
self.results = {}
|
||||||
|
self.tabges = {}
|
||||||
|
|
||||||
|
def __getTuple(self, group: group.Group_t, class_: competition_class.Class_t, dance: str, id: int):
|
||||||
|
return (group, class_, dance, id)
|
||||||
|
|
||||||
|
def get(self, group: group.Group_t, class_: competition_class.Class_t, dance: str, id: int) -> list[HtmlSingleCompetitionResult]:
|
||||||
|
return self.results[self.__getTuple(group, class_, dance, id)]
|
||||||
|
|
||||||
|
def getById(self, id: int) -> dict[tuple[str, group.Group_t, competition_class.Class_t], HtmlSingleCompetitionResult]:
|
||||||
|
ret = {}
|
||||||
|
|
||||||
|
for k in self.results:
|
||||||
|
if int(k[3]) != id:
|
||||||
|
continue
|
||||||
|
# ret = ret + self.results[k]
|
||||||
|
# Dance, Group, Class
|
||||||
|
key = (k[2], k[0], k[1])
|
||||||
|
ret[key] = self.results[k]
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def add(self, group, class_, dance, id, result: HtmlSingleCompetitionResult):
|
||||||
|
tup = self.__getTuple(group, class_, dance, id)
|
||||||
|
l = self.results.get(tup, [])
|
||||||
|
l.append(result)
|
||||||
|
self.results[tup] = l
|
||||||
|
|
||||||
|
class SingleParticipantResult:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
competitionClass: competition_class.Class_t,
|
||||||
|
nativeClass: competition_class.CompetitionClass,
|
||||||
|
dance: str,
|
||||||
|
finalist: bool,
|
||||||
|
place: int,
|
||||||
|
placeTo: int|None
|
||||||
|
):
|
||||||
|
self.competitionClass = competitionClass
|
||||||
|
self.nativeClass = nativeClass
|
||||||
|
self.dance = dance
|
||||||
|
self.finalist = finalist
|
||||||
|
self.place = place
|
||||||
|
self.placeTo = placeTo
|
||||||
|
|
||||||
|
if placeTo == place:
|
||||||
|
self.placeTo = None
|
||||||
|
|
||||||
|
self.placeNative = None
|
||||||
|
self.placeNativeTo = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
asFinalist = ' as finalist' if self.finalist else ''
|
||||||
|
|
||||||
|
if self.placeTo is None:
|
||||||
|
return f'SR[{self.place} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]'
|
||||||
|
|
||||||
|
return f'SR[{self.place}-{self.placeTo} in {self.dance} {self.competitionClass} ({self.placeNative}-{self.placeNativeTo}, {self.nativeClass}){asFinalist}]'
|
||||||
|
|
||||||
|
class TotalGroupResult:
|
||||||
|
def __init__(self, dances: list[str], results: dict[HtmlPreviewParticipant, list[SingleParticipantResult]]):
|
||||||
|
self.dances = dances
|
||||||
|
self.results = results
|
||||||
|
def __repr__(self):
|
||||||
|
return f'TotalGroupResult({self.dances}, {self.results})'
|
||||||
|
|
||||||
|
class State4:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
resultPerGroup: dict[group.Group, TotalGroupResult]
|
||||||
|
):
|
||||||
|
parser = group.GroupParser()
|
||||||
|
self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys())
|
||||||
|
self.results = resultPerGroup
|
||||||
|
|
||||||
|
class State3:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
previewImport: HtmlPreviewImport,
|
||||||
|
htmlResults: HtmlCompetitionTotalResults
|
||||||
|
):
|
||||||
|
self.previewImport = previewImport
|
||||||
|
self.htmlResults = htmlResults
|
||||||
|
|
||||||
|
class Participant:
|
||||||
|
def __init__(self, firstName: str, lastName: str, club: str, group: group.Group, class_: competition_class.CompetitionClass):
|
||||||
|
self.firstName = firstName
|
||||||
|
self.lastName = lastName
|
||||||
|
self.club = club
|
||||||
|
self.group = group
|
||||||
|
self.class_ = class_
|
||||||
|
|
||||||
|
class ParticipantResult:
|
||||||
|
def __init__(
|
||||||
|
self, id: int, finalist: bool, cancelled: bool,
|
||||||
|
group: group.Group_t,
|
||||||
|
class_: competition_class.Class_t,
|
||||||
|
dance: str,
|
||||||
|
place, placeTo
|
||||||
|
):
|
||||||
|
self.id = id
|
||||||
|
self.finalist = finalist
|
||||||
|
self.cancelled = cancelled
|
||||||
|
self.group = group
|
||||||
|
self.class_ = class_
|
||||||
|
self.dance = dance
|
||||||
|
self.place = place
|
||||||
|
self.placeTo = placeTo
|
||||||
|
|
||||||
|
class Stage2:
|
||||||
|
def __init__(self, results: dict[Participant, list[ParticipantResult]]):
|
||||||
|
self.results = results
|
||||||
|
|
||||||
|
class TableCompetitionEntry:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
cancelled: bool,
|
||||||
|
finalist: bool,
|
||||||
|
class_: competition_class.Class_t,
|
||||||
|
place: int = -1,
|
||||||
|
placeTo: int = -1,
|
||||||
|
group: group.Group_t = None,
|
||||||
|
id: int = None
|
||||||
|
):
|
||||||
|
self.finalist = finalist
|
||||||
|
self.cancelled = cancelled
|
||||||
|
self.group = group
|
||||||
|
self.class_ = class_
|
||||||
|
self.place = place
|
||||||
|
self.placeTo = placeTo
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
def paramMerging(l):
|
||||||
|
return ', '.join(filter(lambda x: x is not None, l))
|
||||||
|
|
||||||
|
if self.cancelled:
|
||||||
|
params = paramMerging([self.group, self.class_, self.id])
|
||||||
|
if len(params) > 0:
|
||||||
|
return f'- ({params})'
|
||||||
|
else:
|
||||||
|
return '-'
|
||||||
|
elif not self.finalist:
|
||||||
|
params = paramMerging([self.group, self.class_, self.id])
|
||||||
|
if len(params) > 0:
|
||||||
|
return f'x ({params})'
|
||||||
|
else:
|
||||||
|
return 'x'
|
||||||
|
else:
|
||||||
|
if self.place == self.placeTo:
|
||||||
|
place = f'{self.place}.'
|
||||||
|
else:
|
||||||
|
place = f'{self.place}.-{self.placeTo}.'
|
||||||
|
params = paramMerging([self.group, self.class_, self.id])
|
||||||
|
return f'{place} ({params})'
|
||||||
|
|
||||||
|
class TableEntry:
|
||||||
|
def __init__(self, competitions: list[TableCompetitionEntry]):
|
||||||
|
self.competitions = competitions
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return ', '.join(self.competitions)
|
||||||
|
|
||||||
|
class TableRow:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
participant: Participant,
|
||||||
|
id: int,
|
||||||
|
entries: list[TableEntry]
|
||||||
|
):
|
||||||
|
self.participant = participant
|
||||||
|
self.id = id
|
||||||
|
self.entries = entries
|
||||||
|
|
||||||
|
def getRowList(self):
|
||||||
|
if self.id is not None:
|
||||||
|
first = f'{self.id}. {self.participant.firstName} {self.participant.lastName} ({self.participant.club})'
|
||||||
|
else:
|
||||||
|
first = f'{self.participant.firstName} {self.participant.lastName} ({self.participant.club})'
|
||||||
|
return [first] + map(str, self.entries)
|
||||||
|
|
||||||
|
class OutputTable:
|
||||||
|
def __init__(self, dances: list[str], rows: list[TableRow]):
|
||||||
|
self.dances = dances
|
||||||
|
self.rows = rows
|
||||||
|
|
||||||
|
class Stage1:
|
||||||
|
def __init__(self, tables: dict[group.Group, OutputTable]):
|
||||||
|
self.tables = tables
|
@ -1,18 +0,0 @@
|
|||||||
from .place import Place
|
|
||||||
|
|
||||||
from .person import Person
|
|
||||||
from .htmlPreviewParticipant import HtmlPreviewParticipant
|
|
||||||
from .htmlParticipant import HtmlParticipant
|
|
||||||
from .htmlResultImport import HtmlResultImport
|
|
||||||
from .htmlResultTotalTable import HtmlResultTotalTable
|
|
||||||
from .htmlCompetitionResultRow import HtmlCompetitionResultRow
|
|
||||||
from .competitionTuple import CompetitionTuple
|
|
||||||
from .htmlSingleCompetitionResult import HtmlSingleCompetitionResult
|
|
||||||
from .htmlSingleCompetitionFixture import HtmlSingleCompetitionFixture
|
|
||||||
from .htmlCompetitionTotalResults import HtmlCompetitionTotalResults
|
|
||||||
from .singleParticipantResult import SingleParticipantResult
|
|
||||||
from .totalGroupResult import TotalGroupResult
|
|
||||||
from .participant import Participant
|
|
||||||
from .tableData import *
|
|
||||||
|
|
||||||
from .stages import *
|
|
@ -1,27 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
|
|
||||||
|
|
||||||
class CompetitionTuple:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
group: solo_turnier.group.Group_t,
|
|
||||||
class_: solo_turnier.competition_class.Class_t,
|
|
||||||
dance: str,
|
|
||||||
id: int,
|
|
||||||
):
|
|
||||||
self.group = group
|
|
||||||
self.class_ = class_
|
|
||||||
self.dance = dance
|
|
||||||
self.id = id
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(("Tuple", self.group, self.class_, self.dance, self.id))
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"T({self.group},{self.class_},{self.dance},{self.id})"
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if type(other) != type(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return self.__hash__() == other.__hash__()
|
|
@ -1,35 +0,0 @@
|
|||||||
class HtmlCompetitionResultRow:
|
|
||||||
def __init__(self, name, id, dance, group, class_, place, placeTo, finalist):
|
|
||||||
self.dance = dance
|
|
||||||
self.group = group
|
|
||||||
self.class_ = class_
|
|
||||||
self.place = place
|
|
||||||
self.placeTo = placeTo
|
|
||||||
self.id = int(id)
|
|
||||||
self.name = name
|
|
||||||
self.finalist = finalist
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.place == self.placeTo:
|
|
||||||
result = f"{self.place}."
|
|
||||||
else:
|
|
||||||
result = f"{self.place}.-{self.placeTo}."
|
|
||||||
|
|
||||||
if self.finalist == True:
|
|
||||||
finalist = "[F]"
|
|
||||||
else:
|
|
||||||
finalist = ""
|
|
||||||
return f"Result[{self.id}]({self.group} {self.class_} {self.dance} as {result}{finalist})"
|
|
||||||
|
|
||||||
def __eq__(self, o):
|
|
||||||
if not isinstance(o, CompetitionResult):
|
|
||||||
return False
|
|
||||||
|
|
||||||
return (
|
|
||||||
self.dance == o.dance
|
|
||||||
and self.competitionClass == o.competitionClass
|
|
||||||
and self.competitionGroup == o.competitionGroup
|
|
||||||
and self.place == o.place
|
|
||||||
and self.placeTo == o.placeTo
|
|
||||||
and self.id == o.id
|
|
||||||
)
|
|
@ -1,49 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
from .htmlSingleCompetitionResult import HtmlSingleCompetitionResult
|
|
||||||
from .competitionTuple import CompetitionTuple
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlCompetitionTotalResults:
|
|
||||||
def __init__(self):
|
|
||||||
self.results = {}
|
|
||||||
self.fixups = {}
|
|
||||||
|
|
||||||
def __getTuple(
|
|
||||||
self,
|
|
||||||
group: solo_turnier.group.Group_t,
|
|
||||||
class_: solo_turnier.competition_class.Class_t,
|
|
||||||
dance: str,
|
|
||||||
id: int,
|
|
||||||
):
|
|
||||||
return CompetitionTuple(group, class_, dance, id)
|
|
||||||
|
|
||||||
def get(
|
|
||||||
self,
|
|
||||||
group: solo_turnier.group.Group_t,
|
|
||||||
class_: solo_turnier.competition_class.Class_t,
|
|
||||||
dance: str,
|
|
||||||
id: int,
|
|
||||||
) -> list[HtmlSingleCompetitionResult]:
|
|
||||||
return self.results[self.__getTuple(group, class_, dance, id)]
|
|
||||||
|
|
||||||
def getById(self, id: int) -> dict[
|
|
||||||
tuple[str, solo_turnier.group.Group_t, solo_turnier.competition_class.Class_t],
|
|
||||||
HtmlSingleCompetitionResult,
|
|
||||||
]:
|
|
||||||
ret = {}
|
|
||||||
|
|
||||||
for k in self.results:
|
|
||||||
if int(k.id) != id:
|
|
||||||
continue
|
|
||||||
# ret = ret + self.results[k]
|
|
||||||
# Dance, Group, Class
|
|
||||||
key = (k.dance, k.group, k.class_)
|
|
||||||
ret[key] = self.results[k]
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def add(self, group, class_, dance, id: int, result: HtmlSingleCompetitionResult):
|
|
||||||
tup = self.__getTuple(group, class_, dance, id)
|
|
||||||
l = self.results.get(tup, [])
|
|
||||||
l.append(result)
|
|
||||||
self.results[tup] = l
|
|
@ -1,27 +0,0 @@
|
|||||||
class HtmlParticipant:
|
|
||||||
def __init__(self, name, id):
|
|
||||||
self.name = name
|
|
||||||
self.id = id
|
|
||||||
self.finalist = None
|
|
||||||
self.club = None
|
|
||||||
|
|
||||||
def __eq__(self, o):
|
|
||||||
if type(o) != HtmlParticipant:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return all(
|
|
||||||
map(
|
|
||||||
lambda x, y: x == y,
|
|
||||||
(self.name, self.id, self.group),
|
|
||||||
(o.name, o.id, o.group),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"{self.id}: {self.name}"
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.id, self.name))
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self.id >= other.id
|
|
@ -1,28 +0,0 @@
|
|||||||
class HtmlPreviewParticipant:
|
|
||||||
def __init__(self, name, id, group_):
|
|
||||||
self.name = name
|
|
||||||
self.id = id
|
|
||||||
groupParser = group.GroupParser()
|
|
||||||
self.group = groupParser.parseGroup(group_)
|
|
||||||
self.finalist = None
|
|
||||||
|
|
||||||
def __eq__(self, o):
|
|
||||||
if type(o) != HtmlPreviewParticipant:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return all(
|
|
||||||
map(
|
|
||||||
lambda x, y: x == y,
|
|
||||||
(self.name, self.id, self.group),
|
|
||||||
(o.name, o.id, o.group),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"{self.id} ({self.name}, {self.group})"
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash((self.id, self.name, self.group))
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self.id >= other.id
|
|
@ -1,9 +0,0 @@
|
|||||||
from .htmlParticipant import HtmlParticipant
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlResultImport:
|
|
||||||
def __init__(self, results: dict[HtmlParticipant, str]):
|
|
||||||
self.results = results
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.results)
|
|
@ -1,6 +0,0 @@
|
|||||||
class HtmlResultTotalTable:
|
|
||||||
def __init__(self, participants):
|
|
||||||
self.participants = participants
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.participants)
|
|
@ -1,17 +0,0 @@
|
|||||||
from .place import Place
|
|
||||||
import solo_turnier
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlSingleCompetitionFixture:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
place: Place,
|
|
||||||
group: solo_turnier.group.Group,
|
|
||||||
class_: solo_turnier.competition_class.CompetitionClass,
|
|
||||||
):
|
|
||||||
self.place = place
|
|
||||||
self.group = group
|
|
||||||
self.class_ = class_
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"Fix({self.place},{self.group},{self.class_})"
|
|
@ -1,26 +0,0 @@
|
|||||||
from .place import Place
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlSingleCompetitionResult:
|
|
||||||
def __init__(self, name: str, place: Place, finalist: bool, club: str):
|
|
||||||
self.name = name
|
|
||||||
self.place = place
|
|
||||||
self.finalist = finalist
|
|
||||||
self.club = club
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
place = self.place
|
|
||||||
|
|
||||||
if self.finalist:
|
|
||||||
return f"Res({self.name} [F], placed {place})"
|
|
||||||
else:
|
|
||||||
return f"Res({self.name}, placed {place})"
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self.id > other.id
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self.id == other.id
|
|
||||||
|
|
||||||
def __hash__(self):
|
|
||||||
return hash(self.id)
|
|
@ -1,25 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
|
|
||||||
from .person import Person
|
|
||||||
|
|
||||||
|
|
||||||
class Participant(Person):
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
name: str,
|
|
||||||
id: int,
|
|
||||||
finalist: bool = None,
|
|
||||||
club: str = None,
|
|
||||||
):
|
|
||||||
super().__init__(name)
|
|
||||||
self.id = id
|
|
||||||
self.finalist = finalist
|
|
||||||
self.club = club
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.finalist == True:
|
|
||||||
return f"Part({self.id} {self.name},F)"
|
|
||||||
return f"Part({self.id} {self.name})"
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self.id > other.id
|
|
@ -1,3 +0,0 @@
|
|||||||
class Person:
|
|
||||||
def __init__(self, name: str):
|
|
||||||
self.name = name
|
|
@ -1,10 +0,0 @@
|
|||||||
class Place:
|
|
||||||
def __init__(self, place: int, placeTo: int | None = None):
|
|
||||||
self.place = place
|
|
||||||
self.placeTo = placeTo
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if self.placeTo is None:
|
|
||||||
return f"{self.place}."
|
|
||||||
|
|
||||||
return f"{self.place}.-{self.placeTo}."
|
|
@ -1,34 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
|
|
||||||
from .place import Place
|
|
||||||
|
|
||||||
|
|
||||||
class SingleParticipantResult:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
competitionClass: solo_turnier.competition_class.Class_t,
|
|
||||||
competitionGroup: solo_turnier.group.Group_t,
|
|
||||||
nativeClass: solo_turnier.competition_class.CompetitionClass,
|
|
||||||
dance: str,
|
|
||||||
finalist: bool,
|
|
||||||
place: Place,
|
|
||||||
nativePlace: Place = None,
|
|
||||||
):
|
|
||||||
self.competitionClass = competitionClass
|
|
||||||
self.competitionGroup = competitionGroup
|
|
||||||
self.nativeClass = nativeClass
|
|
||||||
self.dance = dance
|
|
||||||
self.finalist = finalist
|
|
||||||
self.place = place
|
|
||||||
self.nativePlace = nativePlace
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
asFinalist = " as finalist" if self.finalist else ""
|
|
||||||
|
|
||||||
return f"SR[{self.place} in {self.dance} {self.competitionClass} ({self.nativePlace} {self.nativeClass}){asFinalist}]"
|
|
||||||
|
|
||||||
def getNativePlace(self) -> str:
|
|
||||||
return str(self.nativePlace)
|
|
||||||
|
|
||||||
def isCombinedGroup(self) -> bool:
|
|
||||||
return isinstance(self.competitionGroup, solo_turnier.group.CombinedGroup)
|
|
@ -1,29 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
|
|
||||||
from .totalGroupResult import TotalGroupResult
|
|
||||||
from .htmlCompetitionTotalResults import HtmlCompetitionTotalResults
|
|
||||||
from .singleParticipantResult import SingleParticipantResult
|
|
||||||
from .participant import Participant
|
|
||||||
from .tableData import GroupTableData
|
|
||||||
|
|
||||||
NullableGroup = solo_turnier.group.Group | None
|
|
||||||
|
|
||||||
|
|
||||||
class Stage5:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
resultsPerGroup: dict[NullableGroup, GroupTableData],
|
|
||||||
):
|
|
||||||
self.resultsPerGroup = resultsPerGroup
|
|
||||||
|
|
||||||
|
|
||||||
class State4:
|
|
||||||
def __init__(self, resultPerGroup: dict[NullableGroup, TotalGroupResult]):
|
|
||||||
parser = solo_turnier.group.GroupParser()
|
|
||||||
self.groups = parser.getGroupsAsSortedList(resultPerGroup.keys())
|
|
||||||
self.results = resultPerGroup
|
|
||||||
|
|
||||||
|
|
||||||
class State3:
|
|
||||||
def __init__(self, htmlResults: HtmlCompetitionTotalResults):
|
|
||||||
self.htmlResults = htmlResults
|
|
@ -1,11 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
from .participant import Participant
|
|
||||||
from .singleParticipantResult import SingleParticipantResult
|
|
||||||
|
|
||||||
SortedResultList = dict[Participant, list[SingleParticipantResult | None]]
|
|
||||||
|
|
||||||
|
|
||||||
class GroupTableData:
|
|
||||||
def __init__(self, dances: list[str], results: SortedResultList):
|
|
||||||
self.dances = dances
|
|
||||||
self.resultsInGroup = results
|
|
@ -1,15 +0,0 @@
|
|||||||
from .singleParticipantResult import SingleParticipantResult
|
|
||||||
from .participant import Participant
|
|
||||||
|
|
||||||
|
|
||||||
class TotalGroupResult:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
dances: list[str],
|
|
||||||
results: dict[Participant, list[SingleParticipantResult]],
|
|
||||||
):
|
|
||||||
self.dances = dances
|
|
||||||
self.results = results
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"TotalGrR({self.dances}, {self.results})"
|
|
@ -1,3 +1,15 @@
|
|||||||
|
import logging
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import solo_turnier
|
||||||
|
from solo_turnier import html_parser
|
||||||
|
from .reader import ResultRow
|
||||||
|
from .types import HtmlCompetitionResultRow as CompetitionResult
|
||||||
|
from . import types
|
||||||
|
from . import competition_class
|
||||||
|
|
||||||
class HtmlPerson:
|
class HtmlPerson:
|
||||||
def __init__(self, name, id, group):
|
def __init__(self, name, id, group):
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -5,7 +17,7 @@ class HtmlPerson:
|
|||||||
self.group = group
|
self.group = group
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.name} ({self.id}, {self.group})"
|
return f'{self.name} ({self.id}, {self.group})'
|
||||||
|
|
||||||
def __eq__(self, o):
|
def __eq__(self, o):
|
||||||
if not isinstance(o, HtmlPerson):
|
if not isinstance(o, HtmlPerson):
|
||||||
@ -15,33 +27,651 @@ class HtmlPerson:
|
|||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return str(self).__hash__()
|
return str(self).__hash__()
|
||||||
|
|
||||||
|
|
||||||
class ResultPerson:
|
class ResultPerson:
|
||||||
def __init__(self, firstName, lastName, club, id = None, group = None):
|
def __init__(self, firstName, lastName, club, id = None, group = None):
|
||||||
self.firstName = firstName
|
self.firstName = firstName
|
||||||
self.lastName = lastName
|
self.lastName = lastName
|
||||||
self.name = f"{firstName} {lastName}"
|
self.name = f'{firstName} {lastName}'
|
||||||
self.club = club
|
self.club = club
|
||||||
self.id = id
|
self.id = id
|
||||||
self.group = group
|
self.group = group
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extractFromResultRow(row: ResultRow):
|
||||||
|
return ResultPerson(
|
||||||
|
firstName=row.firstName,
|
||||||
|
lastName=row.lastName,
|
||||||
|
club=row.club
|
||||||
|
)
|
||||||
|
|
||||||
def __eq__(self, o):
|
def __eq__(self, o):
|
||||||
if not isinstance(o, ResultPerson):
|
if not isinstance(o, ResultPerson):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return (
|
return (
|
||||||
self.firstName == o.firstName
|
self.firstName == o.firstName and
|
||||||
and self.lastName == o.lastName
|
self.lastName == o.lastName and
|
||||||
and self.club == o.club
|
self.club == o.club and
|
||||||
and self.id == o.id
|
self.id == o.id
|
||||||
)
|
)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if self.id is None:
|
if self.id is None:
|
||||||
return f"{self.name} ({self.club})"
|
return f'{self.name} ({self.club})'
|
||||||
else:
|
else:
|
||||||
return f"{self.name} ({self.club}) [{self.id}]"
|
return f'{self.name} ({self.club}) [{self.id}]'
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
text = str(self)
|
text = str(self)
|
||||||
return text.__hash__()
|
return text.__hash__()
|
||||||
|
|
||||||
|
|
||||||
|
class ImportNotParsableException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
ParserList_t = dict[str, html_parser.HtmlParser]
|
||||||
|
|
||||||
|
class PreviewWorker:
|
||||||
|
def __init__(self):
|
||||||
|
self.l = logging.getLogger('solo_turnier.worker.PreviewWorker')
|
||||||
|
self.participants = {}
|
||||||
|
self.previewResults = {}
|
||||||
|
|
||||||
|
def filterFilesPreview(self, files: list[str]) -> ParserList_t:
|
||||||
|
self.l.debug('Filtering the list of parsers by removing all non preview entries.')
|
||||||
|
ret = {}
|
||||||
|
for file in files:
|
||||||
|
with open(file, 'r') as fp:
|
||||||
|
text = fp.read()
|
||||||
|
|
||||||
|
parser = html_parser.HtmlParser(text, file)
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = parser.guessDataFromHtmlTitle()
|
||||||
|
except:
|
||||||
|
self.l.error(f'Unable to parse html file in {file}. Please check manually.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if data['class_'] == 'Sichtung':
|
||||||
|
self.l.debug(f"Found candidate in {file}. Adding to the list.")
|
||||||
|
ret[file] = parser
|
||||||
|
else:
|
||||||
|
self.l.debug(f'Rejecting file {file} as the name {data["class_"]} did not match.')
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __extractPersonsFromSinglePreview(self, parser: html_parser.HtmlParser):
|
||||||
|
imported = parser.parsePreparationRound()
|
||||||
|
parser.cleanPreparationRoundImport(imported)
|
||||||
|
data = imported['data']
|
||||||
|
|
||||||
|
headerData = parser.guessDataFromHtmlTitle()
|
||||||
|
dance = headerData['dance']
|
||||||
|
classParser = solo_turnier.competition_class.CompetitionClassParser()
|
||||||
|
|
||||||
|
def getRowIndexOfClass():
|
||||||
|
return data['titles'].index('Platz von\nPlatz bis')
|
||||||
|
|
||||||
|
self.l.log(5, data)
|
||||||
|
|
||||||
|
if data['titles'][0] != 'Wertungsrichter':
|
||||||
|
self.l.fatal('Cannot parse the parsed content of the preview file.')
|
||||||
|
raise ImportNotParsableException('Incompatible export file')
|
||||||
|
|
||||||
|
if data['titles'][-1] == 'Startgruppe':
|
||||||
|
self.l.debug('Combined competition found. Extracting group from table required.')
|
||||||
|
extractGroup = True
|
||||||
|
else:
|
||||||
|
self.l.debug('Using group from the title.')
|
||||||
|
group = parser.guessDataFromHtmlTitle(imported['title'])['group']
|
||||||
|
extractGroup = False
|
||||||
|
|
||||||
|
classRowIndex = getRowIndexOfClass()
|
||||||
|
|
||||||
|
for index, e in enumerate(data['table'][0]):
|
||||||
|
if e['text'] == '':
|
||||||
|
# Skip empty columns
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Extract data from column
|
||||||
|
name = e['meta']
|
||||||
|
id = int(e['text'])
|
||||||
|
if extractGroup:
|
||||||
|
group = data['table'][-1][index]['text']
|
||||||
|
|
||||||
|
# dance =
|
||||||
|
class_ = classParser.parseClass(data['table'][classRowIndex][index]['text'])
|
||||||
|
|
||||||
|
participant = types.HtmlPreviewParticipant(name, id, group)
|
||||||
|
|
||||||
|
l = self.participants.get(id, [])
|
||||||
|
self.l.log(5, 'Checking for existence of %s in %s: %s', participant, l, participant in l)
|
||||||
|
if participant not in l:
|
||||||
|
l.append(participant)
|
||||||
|
self.participants[id] = l
|
||||||
|
|
||||||
|
results = self.previewResults.get(participant, {})
|
||||||
|
results[dance] = class_
|
||||||
|
self.previewResults[participant] = results
|
||||||
|
|
||||||
|
def importAllData(self, parsers: ParserList_t) -> types.HtmlPreviewImport:
|
||||||
|
self.participants = {}
|
||||||
|
|
||||||
|
for file in parsers:
|
||||||
|
parser = parsers[file]
|
||||||
|
self.__extractPersonsFromSinglePreview(parser)
|
||||||
|
|
||||||
|
return types.HtmlPreviewImport(self.participants, self.previewResults)
|
||||||
|
|
||||||
|
class ResultExtractor:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.l = logging.getLogger('solo_turnier.worker.ResultExtractor')
|
||||||
|
self.rePlaceSingle = re.compile(' *([0-9]+) *')
|
||||||
|
self.rePlaceDouble = re.compile(' *([0-9]+) *- *([0-9]+) *')
|
||||||
|
|
||||||
|
def getAllParsers(self, files: list[tuple[str,str]]) -> ParserList_t:
|
||||||
|
ret = {}
|
||||||
|
classParser = competition_class.CompetitionClassParser()
|
||||||
|
|
||||||
|
for filePair in files:
|
||||||
|
with open(filePair[0], 'r') as fp:
|
||||||
|
text = fp.read()
|
||||||
|
with open(filePair[1], 'r') as fp:
|
||||||
|
textTab = fp.read()
|
||||||
|
parser = html_parser.HtmlParser(text, filePair[0])
|
||||||
|
parserTab = html_parser.HtmlParser(textTab, filePair[1])
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = parser.guessDataFromHtmlTitle()
|
||||||
|
except:
|
||||||
|
self.l.error('Cannot parse HTML file %s to check if it is a valid result. Check manually.', filePair[0])
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
guessedClass = classParser.parseClass(data['class_'])
|
||||||
|
except:
|
||||||
|
self.l.error('Issue parsing class of file %s. Check manually.', filePair[0])
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.l.debug('Fetched result data: %s, guessed class %s', data, guessedClass)
|
||||||
|
ret[filePair] = (parser, parserTab)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _extractPlace(self, placeStr: str):
|
||||||
|
s = placeStr.replace('.', '')
|
||||||
|
|
||||||
|
matches = self.rePlaceSingle.fullmatch(s)
|
||||||
|
if matches is not None:
|
||||||
|
return (int(matches.group(1)), None)
|
||||||
|
|
||||||
|
matches = self.rePlaceDouble.fullmatch(s)
|
||||||
|
if matches is not None:
|
||||||
|
return (int(matches.group(1)), int(matches.group(2)))
|
||||||
|
|
||||||
|
self.l.error('Could not parse place string "%s"', placeStr)
|
||||||
|
raise Exception('Place cannot be parsed')
|
||||||
|
|
||||||
|
def _analyzeSingleParser(self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults):
|
||||||
|
data = parser.guessDataFromHtmlTitle()
|
||||||
|
competitionClass = data['class_']
|
||||||
|
competitionGroup = data['group']
|
||||||
|
dance = data['dance']
|
||||||
|
|
||||||
|
result = parser.parseResult()
|
||||||
|
self.l.log(5, 'Raw data extracted: %s', result)
|
||||||
|
|
||||||
|
for person in result.results.keys():
|
||||||
|
placeStr = result.results[person]
|
||||||
|
place, placeTo = self._extractPlace(placeStr)
|
||||||
|
competitionResult = types.HtmlSingleCompetitionResult(person.name, place, placeTo, person.finalist)
|
||||||
|
results.add(competitionGroup, competitionClass, dance, person.id, competitionResult)
|
||||||
|
#
|
||||||
|
|
||||||
|
def _analyzeIndividualResults(self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults):
|
||||||
|
data = parser.guessDataFromHtmlTitle()
|
||||||
|
competitionClass = data['class_']
|
||||||
|
competitionGroup = data['group']
|
||||||
|
dance = data['dance']
|
||||||
|
|
||||||
|
result = parser.parseIndividualResult(competitionGroup, competitionClass, dance)
|
||||||
|
self.l.log(5, 'Found individual results: %s', result.participants)
|
||||||
|
results.tabges.update(result.participants)
|
||||||
|
|
||||||
|
def extractAllData(self, parsers: ParserList_t) -> types.HtmlCompetitionTotalResults:
|
||||||
|
ret = types.HtmlCompetitionTotalResults()
|
||||||
|
|
||||||
|
for fileNameTuple in parsers:
|
||||||
|
fileName = fileNameTuple[0]
|
||||||
|
self.l.debug('Extracting data from file %s', fileName)
|
||||||
|
self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
|
||||||
|
|
||||||
|
self.l.debug('Fetching individual result of combined competitions in %s', fileName)
|
||||||
|
self._analyzeIndividualResults(parsers[fileNameTuple][1], ret)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class DataWorker:
|
||||||
|
def __init__(self):
|
||||||
|
self.l = logging.getLogger('solo_turnier.worker.DataWorker')
|
||||||
|
|
||||||
|
def combineRowsByPerson(self, rows: list[ResultRow]) -> dict[ResultPerson, list[CompetitionResult]]:
|
||||||
|
ret = {}
|
||||||
|
for row in rows:
|
||||||
|
result = CompetitionResult.extractFromResultRow(row)
|
||||||
|
|
||||||
|
if result.place == '-' or result.placeTo == '-':
|
||||||
|
continue
|
||||||
|
|
||||||
|
person = ResultPerson.extractFromResultRow(row)
|
||||||
|
if person not in ret:
|
||||||
|
ret[person] = []
|
||||||
|
ret[person].append(result)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def checkUniqueIds(self, data: dict[ResultPerson, list[CompetitionResult]]) -> bool:
|
||||||
|
unique = True
|
||||||
|
for person in data:
|
||||||
|
ids = set([c.id for c in data[person]])
|
||||||
|
if len(ids) == 1:
|
||||||
|
person.id = list(ids)[0]
|
||||||
|
else:
|
||||||
|
unique = False
|
||||||
|
|
||||||
|
return unique
|
||||||
|
|
||||||
|
"""
|
||||||
|
Return a tuple
|
||||||
|
The first one is True, if all persons could be unambiguously identified a group
|
||||||
|
The second one is True if there was the need to override a group but it was possible to extract from other data
|
||||||
|
The second one can be seen as a warning
|
||||||
|
"""
|
||||||
|
def consolidateGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> tuple[bool, bool]:
|
||||||
|
ambiguous = False
|
||||||
|
warnChange = False
|
||||||
|
|
||||||
|
unambiguousGroups = set(['Kin.', 'Jun.', 'Jug.'])
|
||||||
|
combinations = set(['Kin./Jun.', 'Jun./Jug.'])
|
||||||
|
|
||||||
|
for person in data:
|
||||||
|
groupsRaw = set([c.group for c in data[person]])
|
||||||
|
|
||||||
|
unknown = groupsRaw.difference(unambiguousGroups).difference(combinations)
|
||||||
|
if len(unknown) > 0:
|
||||||
|
raise Exception(f'There were unknown groups found for {person}: {unknown}')
|
||||||
|
|
||||||
|
numUnambiguousGroups = len(groupsRaw.intersection(unambiguousGroups))
|
||||||
|
|
||||||
|
if numUnambiguousGroups == 0:
|
||||||
|
if len(groupsRaw) == 2:
|
||||||
|
warnChange = True
|
||||||
|
person.group = 'Jun.'
|
||||||
|
else:
|
||||||
|
ambiguous = True
|
||||||
|
if len(groupsRaw) == 1:
|
||||||
|
person.group = list(groupsRaw)[0]
|
||||||
|
|
||||||
|
elif numUnambiguousGroups == 1:
|
||||||
|
if len(groupsRaw.intersection(combinations)) > 0:
|
||||||
|
warnChange = True
|
||||||
|
|
||||||
|
person.group = list(groupsRaw.intersection(unambiguousGroups))[0]
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise Exception(f'{person} cannot have different groups.')
|
||||||
|
|
||||||
|
return (not ambiguous, warnChange)
|
||||||
|
|
||||||
|
def _createHtmlLUT(self, htmlImports: list[html_parser.HtmlImport]):
|
||||||
|
ret = {}
|
||||||
|
parser = html_parser.HtmlParser('')
|
||||||
|
for imp in htmlImports:
|
||||||
|
parsed = parser.guessDataFromHtmlTitle(imp.title)
|
||||||
|
key = (parsed['group'], parsed['class_'], parsed['dance'])
|
||||||
|
ret[key] = imp
|
||||||
|
self.l.debug('LUT[%s] = %s', key, imp)
|
||||||
|
self.l.debug('LUT completed')
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def mergeHtmlData(self, data:dict[ResultPerson, list[CompetitionResult]], htmlImports: list[html_parser.HtmlImport]):
|
||||||
|
lut = self._createHtmlLUT(htmlImports)
|
||||||
|
|
||||||
|
for person in data:
|
||||||
|
for competition in data[person]:
|
||||||
|
key = (competition.competitionGroup, competition.competitionClass, competition.dance)
|
||||||
|
htmlImport = lut[key]
|
||||||
|
participant = htmlImport.participants[str(competition.id)]
|
||||||
|
if participant.name != person.name:
|
||||||
|
self.l.error(f'Names for {person} and participant in HTML import ({participant}) do not match. Please check carefully.')
|
||||||
|
competition.finalist = participant.finalist
|
||||||
|
|
||||||
|
def getAllDancesInCompetitions(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[str]:
|
||||||
|
allDances = [
|
||||||
|
'Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive',
|
||||||
|
'Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep'
|
||||||
|
]
|
||||||
|
dancesPresent = {d: False for d in allDances}
|
||||||
|
|
||||||
|
for person in data:
|
||||||
|
for competition in data[person]:
|
||||||
|
dancesPresent[competition.dance] = True
|
||||||
|
|
||||||
|
return [d for d in allDances if dancesPresent[d]]
|
||||||
|
|
||||||
|
def collectPersonsInGroups(self, data:dict[ResultPerson, list[CompetitionResult]]) -> list[tuple[str, list[ResultPerson]]]:
|
||||||
|
groups = {
|
||||||
|
'Kin.': [p for p in data.keys() if p.group == 'Kin.'],
|
||||||
|
'Jun.': [p for p in data.keys() if p.group == 'Jun.'],
|
||||||
|
'Jug.': [p for p in data.keys() if p.group == 'Jug.'],
|
||||||
|
}
|
||||||
|
found = groups['Kin.'] + groups['Jun.'] + groups['Jug.']
|
||||||
|
groups['Sonst'] = [p for p in data.keys() if p not in found]
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
|
||||||
|
ids = [p.id for p in persons]
|
||||||
|
|
||||||
|
def decorateByName(p: ResultPerson):
|
||||||
|
return (f'{p.name} ({p.club})', p)
|
||||||
|
def decorateById(p: ResultPerson):
|
||||||
|
return (p.id, p)
|
||||||
|
|
||||||
|
if any([id == None for id in ids]):
|
||||||
|
# We need to sort by name
|
||||||
|
decorated = [decorateByName(p) for p in persons]
|
||||||
|
showIds = False
|
||||||
|
else:
|
||||||
|
decorated = [decorateById(p) for p in persons]
|
||||||
|
showIds = True
|
||||||
|
|
||||||
|
decorated.sort()
|
||||||
|
|
||||||
|
return ([d[1] for d in decorated], showIds)
|
||||||
|
|
||||||
|
def mapPersonResultsToDanceList(self, results: list[CompetitionResult], dances: list[str]) -> list[CompetitionResult|None]:
|
||||||
|
ret = []
|
||||||
|
for dance in dances:
|
||||||
|
competitions = [c for c in results if c.dance == dance]
|
||||||
|
if len(competitions) == 0:
|
||||||
|
ret.append(None)
|
||||||
|
elif len(competitions) > 1:
|
||||||
|
raise Exception(f'Multiple competitions with the same dance "{dance}" found.')
|
||||||
|
else:
|
||||||
|
ret.append(competitions[0])
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class Worker:
|
||||||
|
def __init__(self):
|
||||||
|
self.l = logging.getLogger('solo_turnier.worker.Worker')
|
||||||
|
self._allDances = (
|
||||||
|
['Samba', 'Cha Cha', 'Rumba', 'Paso Doble', 'Jive'] +
|
||||||
|
['Langs. Walzer', 'Tango', 'Wiener Walzer', 'Slowfox', 'Quickstep']
|
||||||
|
)
|
||||||
|
|
||||||
|
def collectAllData(
|
||||||
|
self,
|
||||||
|
htmlCandidatesPreview: list[str],
|
||||||
|
htmlResultsFileNames: list[str]
|
||||||
|
) -> types.State3:
|
||||||
|
|
||||||
|
previewWorker = PreviewWorker()
|
||||||
|
self.l.info('Filtering for pure preview rounds.')
|
||||||
|
parsers = previewWorker.filterFilesPreview(htmlCandidatesPreview)
|
||||||
|
self.l.debug('Remaining files: %s', list(parsers.keys()))
|
||||||
|
|
||||||
|
self.l.info('Extracting person data from the preview rounds.')
|
||||||
|
previewImport = previewWorker.importAllData(parsers)
|
||||||
|
self.l.debug('Total preview imported participants: %s', pformat(previewImport.participants))
|
||||||
|
self.l.log(5, 'Total preview results: %s', pformat(previewImport.results))
|
||||||
|
|
||||||
|
resultExtractor = ResultExtractor()
|
||||||
|
resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames)
|
||||||
|
htmlResults = resultExtractor.extractAllData(resultParsers)
|
||||||
|
self.l.info('Overall result data extracted: %s', pformat(htmlResults.results))
|
||||||
|
|
||||||
|
return types.State3(previewImport, htmlResults)
|
||||||
|
|
||||||
|
def combineData(self, importedData: types.State3):
|
||||||
|
self.l.info('Starting to build data sets.')
|
||||||
|
groups = self._extractGroups(importedData)
|
||||||
|
self.l.debug('Found groups in the dataset: %s', groups)
|
||||||
|
|
||||||
|
totalResult = {}
|
||||||
|
|
||||||
|
for group in groups:
|
||||||
|
self.l.debug('Collecting data for total result of group %s', group)
|
||||||
|
|
||||||
|
dances = self._extractDancesPerGroup(importedData, group)
|
||||||
|
self.l.log(5, 'Found dances in group %s: %s', group, dances)
|
||||||
|
|
||||||
|
participants = self._extractParticipantsPerGroup(importedData.previewImport, group)
|
||||||
|
self.l.log(5, 'Related participants %s', participants)
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
for participant in participants:
|
||||||
|
self.l.log(5, 'Collecting data for %s', participant)
|
||||||
|
resultsOfParticipant = self._getResultOfSingleParticipant(
|
||||||
|
participant, group, importedData.previewImport,
|
||||||
|
importedData.htmlResults, dances
|
||||||
|
)
|
||||||
|
self.l.log(5, 'Obtained result %s', resultsOfParticipant)
|
||||||
|
results[participant] = resultsOfParticipant
|
||||||
|
|
||||||
|
self.l.log(5, 'Result before native fixing: %s', (results))
|
||||||
|
# self._fixNativePlaces(dances, results)
|
||||||
|
self._fixNativePlacesFromTable(dances, results, importedData.htmlResults)
|
||||||
|
# self.l.log(5, 'Result after native fixing: %s', pformat(results))
|
||||||
|
self.l.log(5,'Data %s', results)
|
||||||
|
|
||||||
|
totalResult[group] = types.TotalGroupResult(dances, results)
|
||||||
|
|
||||||
|
self.l.log(5, 'Total result of all groups: %s', pformat(totalResult))
|
||||||
|
|
||||||
|
ret = types.State4(totalResult)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def _extractGroups(self, data: types.State3):
|
||||||
|
groupSet = set([])
|
||||||
|
for id in data.previewImport.participants:
|
||||||
|
participants = data.previewImport.participants[id]
|
||||||
|
for participant in participants:
|
||||||
|
groupSet.add(participant.group)
|
||||||
|
|
||||||
|
self.l.log(5, 'Set of active groups: %s', groupSet)
|
||||||
|
groupParser = solo_turnier.group.GroupParser()
|
||||||
|
groups = groupParser.getGroupsAsSortedList(groupSet)
|
||||||
|
return groups
|
||||||
|
|
||||||
|
def _extractDancesPerGroup(self, data: types.State3, group: solo_turnier.group.Group):
|
||||||
|
dances = set()
|
||||||
|
additionalDances = set()
|
||||||
|
for part in data.previewImport.results.keys():
|
||||||
|
allFoundDances = set(data.previewImport.results[part].keys())
|
||||||
|
dances.update(allFoundDances.intersection(self._allDances))
|
||||||
|
additionalDances.update(allFoundDances.difference(self._allDances))
|
||||||
|
|
||||||
|
if len(additionalDances) > 0:
|
||||||
|
self.l.warning('There were dances found, that are not registered. A bug? The dances were: %s', additionalDances)
|
||||||
|
|
||||||
|
dancesList = [x for x in self._allDances if x in dances]
|
||||||
|
additionalDancesList = list(additionalDances)
|
||||||
|
additionalDancesList.sort()
|
||||||
|
return dancesList + additionalDancesList
|
||||||
|
|
||||||
|
def _extractParticipantsPerGroup(
|
||||||
|
self,
|
||||||
|
previewData: types.HtmlPreviewImport,
|
||||||
|
group: solo_turnier.group.Group
|
||||||
|
) -> list[types.HtmlPreviewParticipant]:
|
||||||
|
ret = []
|
||||||
|
for id in previewData.participants:
|
||||||
|
participantList = previewData.participants[id]
|
||||||
|
for participant in participantList:
|
||||||
|
if participant.group == group:
|
||||||
|
ret.append(participant)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def _getResultOfSingleParticipant(
|
||||||
|
self,
|
||||||
|
participant: types.HtmlPreviewParticipant,
|
||||||
|
nominalGroup: solo_turnier.group.Group,
|
||||||
|
previewResults: types.HtmlPreviewImport,
|
||||||
|
totalResults: types.HtmlCompetitionTotalResults,
|
||||||
|
allDances: list[str]
|
||||||
|
) -> list[types.SingleParticipantResult|None]:
|
||||||
|
rawResults = totalResults.getById(participant.id)
|
||||||
|
self.l.log(5, 'Found result data (raw): %s', rawResults)
|
||||||
|
|
||||||
|
results = [None for x in allDances]
|
||||||
|
|
||||||
|
for danceIdx, dance in enumerate(allDances):
|
||||||
|
# self.l.log(5, '%s %s', dance, danceIdx)
|
||||||
|
def getResult() -> types.SingleParticipantResult|None:
|
||||||
|
for key in rawResults:
|
||||||
|
if key[0] != dance:
|
||||||
|
continue
|
||||||
|
rawResult = rawResults[key]
|
||||||
|
|
||||||
|
if len(rawResult) != 1:
|
||||||
|
raise Exception('Multiple results found with same key')
|
||||||
|
rawResult = rawResult[0]
|
||||||
|
|
||||||
|
nativeClass = previewResults.results[participant][dance]
|
||||||
|
|
||||||
|
# self.l.log(5, 'Result %s => %s', key, rawResult)
|
||||||
|
ret = types.SingleParticipantResult(
|
||||||
|
key[2], nativeClass, dance, rawResult.finalist,
|
||||||
|
rawResult.place, rawResult.placeTo
|
||||||
|
)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
return None
|
||||||
|
|
||||||
|
results[danceIdx] = getResult()
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def _fixNativePlacesFromTable(
|
||||||
|
self,
|
||||||
|
dances: list[str],
|
||||||
|
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
|
||||||
|
importedData: types.HtmlCompetitionTotalResults
|
||||||
|
):
|
||||||
|
rePlace = re.compile('([0-9]+)(?:-([0-9]+))?')
|
||||||
|
for participant in data.keys():
|
||||||
|
self.l.log(5, 'fixing participant %s', participant)
|
||||||
|
results = data[participant]
|
||||||
|
for result in results:
|
||||||
|
if result is None:
|
||||||
|
continue
|
||||||
|
self.l.log(5, 'Looking at result set %s', result)
|
||||||
|
|
||||||
|
def selectEntry(k):
|
||||||
|
return k[2] == result.dance and int(k[3]) == participant.id
|
||||||
|
|
||||||
|
keys = list(importedData.tabges.keys())
|
||||||
|
selected = list(map(selectEntry, keys))
|
||||||
|
selectedIndex = selected.index(True)
|
||||||
|
|
||||||
|
raw = importedData.tabges[keys[selectedIndex]]
|
||||||
|
self.l.log(5,'Raw %s', raw)
|
||||||
|
nativePlaceRaw = raw[0]
|
||||||
|
matcher = rePlace.fullmatch(nativePlaceRaw)
|
||||||
|
if matcher is None:
|
||||||
|
self.l.error('Cannot parse place string %s for participant %u (%s) in dance %s', nativePlaceRaw, participant.id, participant, result.dance)
|
||||||
|
continue
|
||||||
|
self.l.log(5, 'Found strings by regex: %s', matcher.groups())
|
||||||
|
result.placeNative = matcher.group(1)
|
||||||
|
result.placeNativeTo = matcher.group(2)
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
def _fixNativePlaces(
|
||||||
|
self,
|
||||||
|
dances: list[str],
|
||||||
|
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]]
|
||||||
|
):
|
||||||
|
classParser = solo_turnier.competition_class.CompetitionClassParser()
|
||||||
|
allClasses = classParser.getAllClasses()
|
||||||
|
allClasses.reverse()
|
||||||
|
|
||||||
|
for class_ in allClasses:
|
||||||
|
for danceIdx, dance in enumerate(dances):
|
||||||
|
self.l.log(5, 'Fixing native places for class %s in dance %s', class_, dance)
|
||||||
|
|
||||||
|
remainingParticipants = []
|
||||||
|
|
||||||
|
for participant in data.keys():
|
||||||
|
results = data[participant]
|
||||||
|
danceResult = results[danceIdx]
|
||||||
|
|
||||||
|
if danceResult is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# self.l.log(5, 'Result of dance: %s', danceResult)
|
||||||
|
|
||||||
|
if classParser.isABetterThanB(danceResult.nativeClass, class_):
|
||||||
|
# self.l.log(5, 'Skipping %s as the native class is higher', participant)
|
||||||
|
continue
|
||||||
|
|
||||||
|
remainingParticipants.append((danceResult.place, participant.id, participant))
|
||||||
|
|
||||||
|
remainingParticipants.sort()
|
||||||
|
# self.l.log(5, 'Remaining participants %s', remainingParticipants)
|
||||||
|
|
||||||
|
def getAllParticipantsWithSamePlace():
|
||||||
|
first = remainingParticipants.pop(0)
|
||||||
|
ret = [first]
|
||||||
|
while len(remainingParticipants) > 0 and remainingParticipants[0][0] == first[0]:
|
||||||
|
ret.append(remainingParticipants.pop(0))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def updateNativePlaces(samePlaced, placeStart):
|
||||||
|
nextPlace = placeStart + len(samePlaced)
|
||||||
|
if len(samePlaced) == 1:
|
||||||
|
placeTo = None
|
||||||
|
else:
|
||||||
|
placeTo = nextPlace - 1
|
||||||
|
|
||||||
|
for p in samePlaced:
|
||||||
|
data[p[2]][danceIdx].placeNative = placeStart
|
||||||
|
data[p[2]][danceIdx].placeNativeTo = placeTo
|
||||||
|
|
||||||
|
return nextPlace
|
||||||
|
|
||||||
|
places = list(map(lambda x: x[0], remainingParticipants))
|
||||||
|
place = 1
|
||||||
|
while len(remainingParticipants) > 0:
|
||||||
|
samePlaced = getAllParticipantsWithSamePlace()
|
||||||
|
place = updateNativePlaces(samePlaced, place)
|
||||||
|
|
||||||
|
# self.l.log(5, '(Partially) fixed places: %s', (data))
|
||||||
|
|
||||||
|
def filterOutFinalists(self, data: types.State4):
|
||||||
|
for group in data.results:
|
||||||
|
self.l.debug('Cleaning up group %s', group.name)
|
||||||
|
participants = data.results[group].results.keys()
|
||||||
|
droppedParticipants = []
|
||||||
|
|
||||||
|
for participant in participants:
|
||||||
|
self.l.debug('Checking %s', participant)
|
||||||
|
|
||||||
|
def isFinalistInDance(x: types.HtmlSingleCompetitionResult|None):
|
||||||
|
if x is None:
|
||||||
|
return False
|
||||||
|
return x.finalist
|
||||||
|
mapped = list(map(isFinalistInDance, data.results[group].results[participant]))
|
||||||
|
finalist = True in mapped
|
||||||
|
self.l.log(5,'Check for finalist (in dances %s): %s', mapped, finalist)
|
||||||
|
|
||||||
|
if not finalist:
|
||||||
|
self.l.warning('Dropping %s from the output as no finalist', participant)
|
||||||
|
droppedParticipants.append(participant)
|
||||||
|
|
||||||
|
for droppedParticipant in droppedParticipants:
|
||||||
|
data.results[group].results.pop(droppedParticipant)
|
||||||
|
pass
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
from ..worker import ResultPerson
|
|
||||||
from ..types import HtmlCompetitionResultRow as CompetitionResult
|
|
||||||
from solo_turnier import html_parser
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
class DataWorker:
|
|
||||||
def __init__(self):
|
|
||||||
self.l = logging.getLogger("solo_turnier.worker.DataWorker")
|
|
||||||
|
|
||||||
def sortPersonsInGroup(self, persons: list[ResultPerson]) -> list[ResultPerson]:
|
|
||||||
ids = [p.id for p in persons]
|
|
||||||
|
|
||||||
def decorateByName(p: ResultPerson):
|
|
||||||
return (f"{p.name} ({p.club})", p)
|
|
||||||
|
|
||||||
def decorateById(p: ResultPerson):
|
|
||||||
return (p.id, p)
|
|
||||||
|
|
||||||
if any([id == None for id in ids]):
|
|
||||||
# We need to sort by name
|
|
||||||
decorated = [decorateByName(p) for p in persons]
|
|
||||||
showIds = False
|
|
||||||
else:
|
|
||||||
decorated = [decorateById(p) for p in persons]
|
|
||||||
showIds = True
|
|
||||||
|
|
||||||
decorated.sort()
|
|
||||||
|
|
||||||
return ([d[1] for d in decorated], showIds)
|
|
||||||
|
|
||||||
def mapPersonResultsToDanceList(
|
|
||||||
self, results: list[CompetitionResult], dances: list[str]
|
|
||||||
) -> list[CompetitionResult | None]:
|
|
||||||
ret = []
|
|
||||||
for dance in dances:
|
|
||||||
competitions = [c for c in results if c.dance == dance]
|
|
||||||
if len(competitions) == 0:
|
|
||||||
ret.append(None)
|
|
||||||
elif len(competitions) > 1:
|
|
||||||
raise Exception(
|
|
||||||
f'Multiple competitions with the same dance "{dance}" found.'
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ret.append(competitions[0])
|
|
||||||
|
|
||||||
return ret
|
|
@ -1,58 +0,0 @@
|
|||||||
import solo_turnier
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
class OutputShaper:
|
|
||||||
def __init__(self):
|
|
||||||
self.l = logging.getLogger("solo_turnier.worker.OutputShaper")
|
|
||||||
|
|
||||||
def shapeResults(
|
|
||||||
self, results: solo_turnier.types.State4
|
|
||||||
) -> solo_turnier.types.Stage5:
|
|
||||||
ret = {}
|
|
||||||
for group in results.results:
|
|
||||||
ret[group] = self._handleGroup(results.results[group])
|
|
||||||
|
|
||||||
return solo_turnier.types.Stage5(ret)
|
|
||||||
|
|
||||||
def _handleGroup(
|
|
||||||
self,
|
|
||||||
totalGroupResult: solo_turnier.types.TotalGroupResult,
|
|
||||||
) -> solo_turnier.types.GroupTableData:
|
|
||||||
sortedResultList = {}
|
|
||||||
for participant in totalGroupResult.results:
|
|
||||||
sortedResultList[participant] = [None for x in totalGroupResult.dances]
|
|
||||||
|
|
||||||
for result in totalGroupResult.results[participant]:
|
|
||||||
if result.dance not in totalGroupResult.dances:
|
|
||||||
self.l.error(
|
|
||||||
"Result in unknown dance found in table. This is a bug. (%s)",
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
idx = totalGroupResult.dances.index(result.dance)
|
|
||||||
sortedResultList[participant][idx] = result
|
|
||||||
return solo_turnier.types.GroupTableData(
|
|
||||||
totalGroupResult.dances, sortedResultList
|
|
||||||
)
|
|
||||||
|
|
||||||
def _reshapeRow(
|
|
||||||
self,
|
|
||||||
results: list[solo_turnier.types.SingleParticipantResult],
|
|
||||||
dances: list[str],
|
|
||||||
) -> list[solo_turnier.types.SingleParticipantResult]:
|
|
||||||
ret = [None for x in dances]
|
|
||||||
|
|
||||||
for result in results:
|
|
||||||
if result.dance not in dances:
|
|
||||||
self.l.error(
|
|
||||||
"Result in unknown dance found in table. This is a bug. (%s)",
|
|
||||||
result,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
idx = dances.index(result.dance)
|
|
||||||
ret[idx] = result
|
|
||||||
|
|
||||||
return ret
|
|
@ -1,134 +0,0 @@
|
|||||||
from solo_turnier import html_parser
|
|
||||||
from .. import types
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
from .. import competition_class
|
|
||||||
|
|
||||||
ParserList_t = dict[str, html_parser.HtmlParser]
|
|
||||||
|
|
||||||
|
|
||||||
class ResultExtractor:
|
|
||||||
def __init__(self):
|
|
||||||
self.l = logging.getLogger(__name__)
|
|
||||||
self.rePlaceSingle = re.compile(" *([0-9]+) *")
|
|
||||||
self.rePlaceDouble = re.compile(" *([0-9]+) *- *([0-9]+) *")
|
|
||||||
|
|
||||||
def getAllParsers(self, files: list[tuple[str, str]]) -> ParserList_t:
|
|
||||||
ret = {}
|
|
||||||
classParser = competition_class.CompetitionClassParser()
|
|
||||||
|
|
||||||
for filePair in files:
|
|
||||||
with open(filePair[0], "r") as fp:
|
|
||||||
text = fp.read()
|
|
||||||
parser = html_parser.HtmlParser(text, filePair[0])
|
|
||||||
|
|
||||||
if filePair[1] is None:
|
|
||||||
parserTab = None
|
|
||||||
else:
|
|
||||||
with open(filePair[1], "r") as fp:
|
|
||||||
textTab = fp.read()
|
|
||||||
parserTab = html_parser.HtmlParser(textTab, filePair[1])
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = parser.guessDataFromHtmlTitle()
|
|
||||||
except competition_class.NoEClassException as ex:
|
|
||||||
self.l.info(
|
|
||||||
"The HTML file %s does not represent a solo E class. Skipping it. (%s)",
|
|
||||||
filePair[0],
|
|
||||||
ex,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
except:
|
|
||||||
self.l.warning(
|
|
||||||
"Cannot parse HTML file %s to check if it is a valid result. Check manually.",
|
|
||||||
filePair[0],
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
guessedClass = data["class_"]
|
|
||||||
|
|
||||||
self.l.debug(
|
|
||||||
"Fetched result data: %s, guessed class %s", data, guessedClass
|
|
||||||
)
|
|
||||||
ret[filePair] = (parser, parserTab)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _extractPlace(self, placeStr: str) -> types.Place:
|
|
||||||
s = placeStr.replace(".", "")
|
|
||||||
|
|
||||||
matches = self.rePlaceSingle.fullmatch(s)
|
|
||||||
if matches is not None:
|
|
||||||
return types.Place(int(matches.group(1)))
|
|
||||||
# return (int(matches.group(1)), None)
|
|
||||||
|
|
||||||
matches = self.rePlaceDouble.fullmatch(s)
|
|
||||||
if matches is not None:
|
|
||||||
return types.Place(int(matches.group(1)), int(matches.group(2)))
|
|
||||||
# return (int(matches.group(1)), int(matches.group(2)))
|
|
||||||
|
|
||||||
self.l.error('Could not parse place string "%s"', placeStr)
|
|
||||||
raise Exception("Place cannot be parsed")
|
|
||||||
|
|
||||||
def _analyzeSingleParser(
|
|
||||||
self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults
|
|
||||||
):
|
|
||||||
data = parser.guessDataFromHtmlTitle()
|
|
||||||
competitionClass = data["class_"]
|
|
||||||
competitionGroup = data["group"]
|
|
||||||
dance = data["dance"]
|
|
||||||
|
|
||||||
result = parser.parseResult()
|
|
||||||
self.l.log(5, "Raw data extracted: %s", result)
|
|
||||||
|
|
||||||
for person in result.results.keys():
|
|
||||||
placeStr = result.results[person]
|
|
||||||
place = self._extractPlace(placeStr)
|
|
||||||
competitionResult = types.HtmlSingleCompetitionResult(
|
|
||||||
person.name, place, person.finalist, person.club
|
|
||||||
)
|
|
||||||
results.add(
|
|
||||||
competitionGroup,
|
|
||||||
competitionClass,
|
|
||||||
dance,
|
|
||||||
int(person.id),
|
|
||||||
competitionResult,
|
|
||||||
)
|
|
||||||
#
|
|
||||||
|
|
||||||
def _analyzeResultFixups(
|
|
||||||
self, parser: html_parser.HtmlParser, results: types.HtmlCompetitionTotalResults
|
|
||||||
):
|
|
||||||
data = parser.guessDataFromHtmlTitle()
|
|
||||||
competitionClass = data["class_"]
|
|
||||||
competitionGroup = data["group"]
|
|
||||||
dance = data["dance"]
|
|
||||||
|
|
||||||
resultFixups = parser.parseIndividualResult(
|
|
||||||
competitionGroup, competitionClass, dance
|
|
||||||
)
|
|
||||||
self.l.log(5, "Found additional result fixups: %s", resultFixups.participants)
|
|
||||||
results.fixups.update(resultFixups.participants)
|
|
||||||
|
|
||||||
def extractAllData(
|
|
||||||
self, parsers: ParserList_t
|
|
||||||
) -> types.HtmlCompetitionTotalResults:
|
|
||||||
ret = types.HtmlCompetitionTotalResults()
|
|
||||||
|
|
||||||
for fileNameTuple in parsers:
|
|
||||||
fileName = fileNameTuple[0]
|
|
||||||
self.l.debug("Extracting data from file %s", fileName)
|
|
||||||
self._analyzeSingleParser(parsers[fileNameTuple][0], ret)
|
|
||||||
|
|
||||||
if parsers[fileNameTuple][1] is None:
|
|
||||||
self.l.info(
|
|
||||||
"Skipping extraction of individual result as class is not yet finished."
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.l.debug(
|
|
||||||
"Fetching individual result of combined competitions in %s",
|
|
||||||
fileName,
|
|
||||||
)
|
|
||||||
self._analyzeResultFixups(parsers[fileNameTuple][1], ret)
|
|
||||||
|
|
||||||
return ret
|
|
@ -1,547 +0,0 @@
|
|||||||
import logging
|
|
||||||
import solo_turnier
|
|
||||||
from .. import types
|
|
||||||
from .ResultExtractor import ResultExtractor
|
|
||||||
from pprint import pformat
|
|
||||||
import re
|
|
||||||
from .. import competition_class
|
|
||||||
|
|
||||||
|
|
||||||
class Worker:
|
|
||||||
def __init__(self):
|
|
||||||
self.l = logging.getLogger(__name__)
|
|
||||||
self._allDances = ["Samba", "Cha Cha", "Rumba", "Paso Doble", "Jive"] + [
|
|
||||||
"Langs. Walzer",
|
|
||||||
"Tango",
|
|
||||||
"Wiener Walzer",
|
|
||||||
"Slowfox",
|
|
||||||
"Quickstep",
|
|
||||||
]
|
|
||||||
self._groupParser = solo_turnier.group.GroupParser()
|
|
||||||
self._classParser = solo_turnier.competition_class.CompetitionClassParser()
|
|
||||||
|
|
||||||
def collectAllData(self, htmlResultsFileNames: list[str]) -> types.State3:
|
|
||||||
resultExtractor = ResultExtractor()
|
|
||||||
resultParsers = resultExtractor.getAllParsers(htmlResultsFileNames)
|
|
||||||
htmlResults = resultExtractor.extractAllData(resultParsers)
|
|
||||||
self.l.log(5, "Overall result data extracted: %s", pformat(htmlResults.results))
|
|
||||||
self.l.log(
|
|
||||||
5, "Overall result fixups extracted: %s", pformat(htmlResults.fixups)
|
|
||||||
)
|
|
||||||
|
|
||||||
return types.State3(htmlResults)
|
|
||||||
|
|
||||||
def combineData(self, importedData: types.State3):
|
|
||||||
self.l.info("Starting to build data sets.")
|
|
||||||
|
|
||||||
self.l.debug("Getting per participant groups")
|
|
||||||
groupMapping = self._getGroupMapping(importedData)
|
|
||||||
self.l.log(5, "ID-to-group mapping of the parsed data: %s", str(groupMapping))
|
|
||||||
|
|
||||||
groups = self._extractGroupsFromGroupMapping(groupMapping)
|
|
||||||
self.l.debug("Found groups in the dataset: %s", groups)
|
|
||||||
|
|
||||||
invertedGroupMapping = self._invertGroupMapping(groupMapping, groups)
|
|
||||||
self.l.log(5, "Inverted group mapping: %s", invertedGroupMapping)
|
|
||||||
|
|
||||||
idToParticipantMapping = self._invertIdMapping(importedData.htmlResults)
|
|
||||||
self.l.log(5, "Id to participant mapping: %s", idToParticipantMapping)
|
|
||||||
|
|
||||||
totalResult = {}
|
|
||||||
|
|
||||||
for group in invertedGroupMapping:
|
|
||||||
self.l.debug("Collecting data for group %s", group)
|
|
||||||
|
|
||||||
participants = invertedGroupMapping[group]
|
|
||||||
self.l.log(5, "Participants in group: %s", participants)
|
|
||||||
|
|
||||||
tuplesInCurrentGroup = []
|
|
||||||
|
|
||||||
for participantId in participants:
|
|
||||||
tuplesInCurrentGroup.extend(
|
|
||||||
self._filterResultKeys(importedData.htmlResults, id=participantId)
|
|
||||||
)
|
|
||||||
self.l.log(
|
|
||||||
5,
|
|
||||||
"Tuples of filtered in group %s: %s",
|
|
||||||
group,
|
|
||||||
list(tuplesInCurrentGroup),
|
|
||||||
)
|
|
||||||
|
|
||||||
dancesInGroup = self._extractAllDancesFromTuples(tuplesInCurrentGroup)
|
|
||||||
self.l.debug("Found dances in group %s: %s", group, dancesInGroup)
|
|
||||||
|
|
||||||
resultsInCurrentGroup = {}
|
|
||||||
|
|
||||||
for participantId in participants:
|
|
||||||
self.l.log(5, "Handling participant with ID %d", participantId)
|
|
||||||
# tuples = self._filterResultKeys(im)
|
|
||||||
participant = idToParticipantMapping[participantId]
|
|
||||||
self.l.log(5, "Participant in question: %s", participant)
|
|
||||||
|
|
||||||
participant.finalist = False
|
|
||||||
|
|
||||||
resultsInCurrentGroup[participant] = []
|
|
||||||
|
|
||||||
for tup in self._filterResultKeys(
|
|
||||||
importedData.htmlResults, id=participantId
|
|
||||||
):
|
|
||||||
singleHtmlResultList = importedData.htmlResults.results[tup]
|
|
||||||
if len(singleHtmlResultList) > 1:
|
|
||||||
self.l.warning(
|
|
||||||
"More than one result per tuple (%s) found.", tup
|
|
||||||
)
|
|
||||||
|
|
||||||
singleHtmlResult = singleHtmlResultList[0]
|
|
||||||
singleResult = solo_turnier.types.SingleParticipantResult(
|
|
||||||
competitionClass=tup.class_,
|
|
||||||
nativeClass=tup.class_,
|
|
||||||
competitionGroup=tup.group,
|
|
||||||
dance=tup.dance,
|
|
||||||
finalist=singleHtmlResult.finalist,
|
|
||||||
place=singleHtmlResult.place,
|
|
||||||
nativePlace=singleHtmlResult.place,
|
|
||||||
)
|
|
||||||
|
|
||||||
if tup in importedData.htmlResults.fixups:
|
|
||||||
fixup = importedData.htmlResults.fixups[tup]
|
|
||||||
self.l.log(
|
|
||||||
5, "Fixture found for %s, %s: %s", participant, tup, fixup
|
|
||||||
)
|
|
||||||
self._applyFixture(singleResult, fixup)
|
|
||||||
|
|
||||||
resultsInCurrentGroup[participant].append(singleResult)
|
|
||||||
|
|
||||||
if singleHtmlResult.finalist:
|
|
||||||
participant.finalist = True
|
|
||||||
|
|
||||||
###########################################################################################################################
|
|
||||||
|
|
||||||
totalGroupResult = types.TotalGroupResult(
|
|
||||||
dancesInGroup, resultsInCurrentGroup
|
|
||||||
)
|
|
||||||
self.l.log(5, "Total group result of group %s: %s", group, totalGroupResult)
|
|
||||||
totalResult[group] = totalGroupResult
|
|
||||||
|
|
||||||
ret = types.State4(totalResult)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
for group in groups:
|
|
||||||
self.l.debug("Collecting data for total result of group %s", group)
|
|
||||||
|
|
||||||
dances = self._extractDancesPerGroup(importedData, group)
|
|
||||||
self.l.log(5, "Found dances in group %s: %s", group, dances)
|
|
||||||
|
|
||||||
participants = self._extractParticipantsPerGroup(importedData, group)
|
|
||||||
self.l.log(5, "Related participants %s", participants)
|
|
||||||
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
for participant in participants:
|
|
||||||
self.l.log(5, "Collecting data for %s", participant)
|
|
||||||
resultsOfParticipant = self._getResultOfSingleParticipant(
|
|
||||||
participant,
|
|
||||||
group,
|
|
||||||
importedData.htmlResults,
|
|
||||||
dances,
|
|
||||||
)
|
|
||||||
self.l.log(5, "Obtained result %s", resultsOfParticipant)
|
|
||||||
results[participant] = resultsOfParticipant
|
|
||||||
|
|
||||||
self.l.log(5, "Result before native fixing: %s", pformat(results))
|
|
||||||
# self._fixNativePlaces(dances, results)
|
|
||||||
self._fixNativeDataFromTable(dances, results, importedData.htmlResults)
|
|
||||||
self.l.log(5, "Result after native fixing: %s", pformat(results))
|
|
||||||
# self.l.log(5,'Fixed data %s', results)
|
|
||||||
|
|
||||||
totalResult[group] = types.TotalGroupResult(dances, results)
|
|
||||||
|
|
||||||
self.l.log(5, "Total result of all groups: %s", pformat(totalResult))
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _extractGroups(self, data: types.State3):
|
|
||||||
groupSet = set([])
|
|
||||||
for tup in data.htmlResults.results.keys():
|
|
||||||
gr = self._groupParser.parseGroup(tup[0])
|
|
||||||
groupSet.update(gr.getContainedGroups())
|
|
||||||
|
|
||||||
self.l.log(5, "Set of active groups: %s", groupSet)
|
|
||||||
groups = self._groupParser.getGroupsAsSortedList(groupSet)
|
|
||||||
return groups
|
|
||||||
|
|
||||||
def _getGroupMapping(
|
|
||||||
self, importedData: types.State3
|
|
||||||
) -> dict[int, solo_turnier.group.Group | None]:
|
|
||||||
def _getBestGroupGuess(groups, id):
|
|
||||||
counts = {}
|
|
||||||
grNones = 0
|
|
||||||
for gr in set(groups):
|
|
||||||
length = len(list(filter(lambda x: x == gr, groups)))
|
|
||||||
if isinstance(gr, tuple) or gr is None:
|
|
||||||
grNones = grNones + length
|
|
||||||
else:
|
|
||||||
counts[gr] = length
|
|
||||||
counts[None] = grNones
|
|
||||||
candidates = list(counts.keys())
|
|
||||||
|
|
||||||
def ccomp(i1):
|
|
||||||
return counts[i1]
|
|
||||||
|
|
||||||
candidates.sort(key=ccomp, reverse=True)
|
|
||||||
|
|
||||||
if len(candidates) == 1:
|
|
||||||
self.l.warning("Unrequired group guessing started.")
|
|
||||||
return candidates[0]
|
|
||||||
if len(candidates) == 0:
|
|
||||||
self.l.error("Problem during the group guessing triggered.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
if counts[candidates[0]] > counts[candidates[1]]:
|
|
||||||
if candidates[0] is None:
|
|
||||||
self.l.error(
|
|
||||||
"Majority of guessed groups is ambiguous. Guessing failed for id %d. Falling back to second best guess %s.",
|
|
||||||
id,
|
|
||||||
candidates[1],
|
|
||||||
)
|
|
||||||
return candidates[1]
|
|
||||||
|
|
||||||
self.l.info("Using best fit %s for guessed group.", candidates[0])
|
|
||||||
return candidates[0]
|
|
||||||
|
|
||||||
self.l.warning("Group guessing failed.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
groupsPerId = {}
|
|
||||||
for tup in importedData.htmlResults.results:
|
|
||||||
competitionGroup = tup.group
|
|
||||||
fixture = importedData.htmlResults.fixups.get(
|
|
||||||
tup, solo_turnier.types.HtmlSingleCompetitionFixture(None, None, None)
|
|
||||||
)
|
|
||||||
id = tup.id
|
|
||||||
if fixture.group is not None:
|
|
||||||
group = fixture.group
|
|
||||||
else:
|
|
||||||
containedGroups = competitionGroup.getContainedGroups()
|
|
||||||
if len(containedGroups) > 1:
|
|
||||||
self.l.error(
|
|
||||||
"The group for participant %d is ambiguous in (%s %s %s).",
|
|
||||||
id,
|
|
||||||
tup.group,
|
|
||||||
tup.class_,
|
|
||||||
tup.dance,
|
|
||||||
)
|
|
||||||
group = containedGroups
|
|
||||||
else:
|
|
||||||
group = competitionGroup
|
|
||||||
|
|
||||||
knownGroups = groupsPerId.get(id, [])
|
|
||||||
if group is not None:
|
|
||||||
knownGroups.append(group)
|
|
||||||
groupsPerId[id] = knownGroups
|
|
||||||
|
|
||||||
ret = {}
|
|
||||||
for id in groupsPerId.keys():
|
|
||||||
groupCandidates = groupsPerId[id]
|
|
||||||
groupSet = set(groupCandidates)
|
|
||||||
|
|
||||||
if len(groupSet) == 1:
|
|
||||||
ret[id] = groupSet.pop()
|
|
||||||
elif len(groupSet) > 1:
|
|
||||||
self.l.warning(
|
|
||||||
"Multiple groups for id %d found: %s", id, groupsPerId[id]
|
|
||||||
)
|
|
||||||
ret[id] = _getBestGroupGuess(groupCandidates, id)
|
|
||||||
else:
|
|
||||||
self.l.warning("No group for id %d could be found.", id)
|
|
||||||
ret[id] = None
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _extractGroupsFromGroupMapping(self, mapping):
|
|
||||||
foundGroups = set()
|
|
||||||
for id in mapping:
|
|
||||||
foundGroups.add(mapping[id])
|
|
||||||
sortedGroup = self._groupParser.getGroupsAsSortedList(foundGroups)
|
|
||||||
missingGroups = foundGroups.difference(sortedGroup)
|
|
||||||
sortedGroup = sortedGroup + list(missingGroups)
|
|
||||||
return sortedGroup
|
|
||||||
|
|
||||||
def _invertGroupMapping(self, mapping, groups):
|
|
||||||
ret = {}
|
|
||||||
for group in groups:
|
|
||||||
ret[group] = []
|
|
||||||
for id in mapping:
|
|
||||||
ret[mapping[id]].append(id)
|
|
||||||
for key in ret:
|
|
||||||
ret[key].sort()
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _filterResultKeys(
|
|
||||||
self,
|
|
||||||
results: solo_turnier.types.HtmlCompetitionTotalResults,
|
|
||||||
group: solo_turnier.group.Group_t | None = None,
|
|
||||||
class_: solo_turnier.competition_class.Class_t | None = None,
|
|
||||||
dance: str | None = None,
|
|
||||||
id: int | None = None,
|
|
||||||
):
|
|
||||||
def checker(x: solo_turnier.types.CompetitionTuple) -> bool:
|
|
||||||
if group is not None and group != x.group:
|
|
||||||
return False
|
|
||||||
if class_ is not None and class_ != x.class_:
|
|
||||||
return False
|
|
||||||
if dance is not None and dance != x.dance:
|
|
||||||
return False
|
|
||||||
if id is not None and id != x.id:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
return filter(checker, results.results.keys())
|
|
||||||
|
|
||||||
def _extractAllDancesFromTuples(
|
|
||||||
self, tuples: list[solo_turnier.types.CompetitionTuple]
|
|
||||||
) -> list[str]:
|
|
||||||
danceSet = set()
|
|
||||||
danceSet.update(map(lambda x: x.dance, tuples))
|
|
||||||
|
|
||||||
# Check for unknown dances here
|
|
||||||
setDiff = danceSet.difference(self._allDances)
|
|
||||||
if len(setDiff) > 0:
|
|
||||||
self.l.warning(
|
|
||||||
"There are dances in the data set that are not known in the program. A bug?"
|
|
||||||
)
|
|
||||||
return [x for x in self._allDances if x in danceSet] + list(setDiff)
|
|
||||||
|
|
||||||
def _invertIdMapping(
|
|
||||||
self, htmlData: solo_turnier.types.HtmlCompetitionTotalResults
|
|
||||||
):
|
|
||||||
mapping = {}
|
|
||||||
for tup in htmlData.results:
|
|
||||||
id = tup.id
|
|
||||||
results = htmlData.results[tup]
|
|
||||||
if len(results) > 1:
|
|
||||||
self.l.error(
|
|
||||||
"Non-unique results for tuple %s were found. Most probably this is a bug. The results are %s.",
|
|
||||||
tup,
|
|
||||||
results,
|
|
||||||
)
|
|
||||||
elif len(results) == 0:
|
|
||||||
self.l.error("No results for tuple %s found.", tup)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if id not in mapping:
|
|
||||||
mapping[id] = solo_turnier.types.Participant(
|
|
||||||
name=results[0].name, id=id, club=results[0].club
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
if mapping[id].name != results[0].name or mapping[id].id != id:
|
|
||||||
self.l.error(
|
|
||||||
"Invalid id to participant mapping found. The name of id has changed. Tuple was %s (values %s), mapping was %s",
|
|
||||||
tup,
|
|
||||||
results,
|
|
||||||
mapping[id],
|
|
||||||
)
|
|
||||||
return mapping
|
|
||||||
|
|
||||||
def _filterResultsById(
|
|
||||||
self, data: solo_turnier.types.HtmlCompetitionTotalResults, ids: list[int]
|
|
||||||
):
|
|
||||||
ret = {}
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _applyFixture(
|
|
||||||
self,
|
|
||||||
singleResult: solo_turnier.types.SingleParticipantResult,
|
|
||||||
fixture: solo_turnier.types.HtmlSingleCompetitionFixture,
|
|
||||||
):
|
|
||||||
singleResult.nativePlace = fixture.place
|
|
||||||
|
|
||||||
if fixture.class_ is not None:
|
|
||||||
singleResult.nativeClass = fixture.class_
|
|
||||||
|
|
||||||
def _extractDancesPerGroup(
|
|
||||||
self, data: types.State3, group: solo_turnier.group.Group
|
|
||||||
):
|
|
||||||
dances = set()
|
|
||||||
additionalDances = set()
|
|
||||||
foundDances = set()
|
|
||||||
for tup in data.htmlResults.results.keys():
|
|
||||||
currentGroup = self._groupParser.parseGroup(tup[0])
|
|
||||||
if group not in currentGroup.getContainedGroups():
|
|
||||||
continue
|
|
||||||
foundDances.add(tup[2])
|
|
||||||
|
|
||||||
dances.update(foundDances.intersection(self._allDances))
|
|
||||||
additionalDances.update(foundDances.difference(self._allDances))
|
|
||||||
|
|
||||||
if len(additionalDances) > 0:
|
|
||||||
self.l.error(
|
|
||||||
"There were dances found, that are not registered. A bug? The dances were: %s",
|
|
||||||
additionalDances,
|
|
||||||
)
|
|
||||||
|
|
||||||
dancesList = [x for x in self._allDances if x in dances]
|
|
||||||
additionalDancesList = list(additionalDances)
|
|
||||||
additionalDancesList.sort()
|
|
||||||
return dancesList + additionalDancesList
|
|
||||||
|
|
||||||
def _extractParticipantsPerGroup(
|
|
||||||
self,
|
|
||||||
importedData: types.State3,
|
|
||||||
# previewData: types.HtmlPreviewImport,
|
|
||||||
group: solo_turnier.group.Group,
|
|
||||||
) -> list[types.HtmlPreviewParticipant]:
|
|
||||||
ret = []
|
|
||||||
|
|
||||||
# self.l.log(5, 'Table %s', pformat(importedData.htmlResults.tabges))
|
|
||||||
# self.l.log(5, 'Results %s', pformat(importedData.htmlResults.results))
|
|
||||||
|
|
||||||
for tup in importedData.htmlResults.results.keys():
|
|
||||||
currentGroup = self._groupParser.parseGroup(tup[0])
|
|
||||||
activeGroups = currentGroup.getContainedGroups()
|
|
||||||
if group not in activeGroups:
|
|
||||||
continue
|
|
||||||
|
|
||||||
fixture = importedData.htmlResults.tabges.get(tup, None)
|
|
||||||
if fixture is None:
|
|
||||||
self.l.error("A fixture for the tuple %s could not be read.", tup)
|
|
||||||
else:
|
|
||||||
if (
|
|
||||||
fixture[2] is not None
|
|
||||||
and self._groupParser.parseGroup(fixture[2]) != group
|
|
||||||
):
|
|
||||||
self.l.log(
|
|
||||||
5,
|
|
||||||
"Skipping id %s in group %s as in other group.",
|
|
||||||
tup[3],
|
|
||||||
group,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
part = importedData.htmlResults.results[tup][0]
|
|
||||||
part.id = int(tup[3])
|
|
||||||
ret.append(part)
|
|
||||||
|
|
||||||
self.l.log(5, "ret %s", ret)
|
|
||||||
# raise Exception('Test')
|
|
||||||
|
|
||||||
# for id in previewData.participants:
|
|
||||||
# participantList = previewData.participants[id]
|
|
||||||
# for participant in participantList:
|
|
||||||
# if participant.group == group:
|
|
||||||
# ret.append(participant)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _getResultOfSingleParticipant(
|
|
||||||
self,
|
|
||||||
participant: types.HtmlParticipant,
|
|
||||||
nominalGroup: solo_turnier.group.Group,
|
|
||||||
totalResults: types.HtmlCompetitionTotalResults,
|
|
||||||
allDances: list[str],
|
|
||||||
) -> list[types.SingleParticipantResult | None]:
|
|
||||||
rawResults = totalResults.getById(participant.id)
|
|
||||||
self.l.log(
|
|
||||||
5, "Found result data for id %i (raw): %s", participant.id, rawResults
|
|
||||||
)
|
|
||||||
|
|
||||||
results = [None for x in allDances]
|
|
||||||
|
|
||||||
for danceIdx, dance in enumerate(allDances):
|
|
||||||
# self.l.log(5, '%s %s', dance, danceIdx)
|
|
||||||
def getResult() -> types.SingleParticipantResult | None:
|
|
||||||
for key in rawResults:
|
|
||||||
if key[0] != dance:
|
|
||||||
continue
|
|
||||||
rawResult = rawResults[key]
|
|
||||||
|
|
||||||
if len(rawResult) != 1:
|
|
||||||
raise Exception("Multiple results found with same key")
|
|
||||||
rawResult = rawResult[0]
|
|
||||||
|
|
||||||
nativeClass = key[2]
|
|
||||||
# nativeClass = previewResults.results[participant][dance]
|
|
||||||
# nativeClass = key[2]
|
|
||||||
|
|
||||||
# self.l.log(5, 'Result %s => %s', key, rawResult)
|
|
||||||
ret = types.SingleParticipantResult(
|
|
||||||
key[2],
|
|
||||||
nativeClass,
|
|
||||||
dance,
|
|
||||||
rawResult.finalist,
|
|
||||||
rawResult.place,
|
|
||||||
rawResult.placeTo,
|
|
||||||
)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
return None
|
|
||||||
|
|
||||||
results[danceIdx] = getResult()
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def _fixNativeDataFromTable(
|
|
||||||
self,
|
|
||||||
dances: list[str],
|
|
||||||
data: dict[types.HtmlPreviewParticipant, list[types.SingleParticipantResult]],
|
|
||||||
importedData: types.HtmlCompetitionTotalResults,
|
|
||||||
):
|
|
||||||
rePlace = re.compile("([0-9]+)(?:-([0-9]+))?")
|
|
||||||
classParser = competition_class.CompetitionClassParser()
|
|
||||||
|
|
||||||
for participant in data.keys():
|
|
||||||
self.l.log(5, "fixing participant %s", participant)
|
|
||||||
results = data[participant]
|
|
||||||
for result in results:
|
|
||||||
if result is None:
|
|
||||||
continue
|
|
||||||
self.l.log(5, "Looking at result set %s", result)
|
|
||||||
|
|
||||||
def selectEntry(k):
|
|
||||||
return k[2] == result.dance and int(k[3]) == participant.id
|
|
||||||
|
|
||||||
keys = list(importedData.tabges.keys())
|
|
||||||
selected = list(map(selectEntry, keys))
|
|
||||||
try:
|
|
||||||
selectedIndex = selected.index(True)
|
|
||||||
except:
|
|
||||||
continue
|
|
||||||
|
|
||||||
raw = importedData.tabges[keys[selectedIndex]]
|
|
||||||
self.l.log(5, "Raw %s", raw)
|
|
||||||
nativePlaceRaw = raw[0]
|
|
||||||
matcher = rePlace.fullmatch(nativePlaceRaw)
|
|
||||||
if matcher is None:
|
|
||||||
self.l.error(
|
|
||||||
"Cannot parse place string %s for participant %u (%s) in dance %s",
|
|
||||||
nativePlaceRaw,
|
|
||||||
participant.id,
|
|
||||||
participant,
|
|
||||||
result.dance,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
self.l.log(5, "Found strings by regex: %s", matcher.groups())
|
|
||||||
result.placeNative = matcher.group(1)
|
|
||||||
result.placeNativeTo = matcher.group(2)
|
|
||||||
|
|
||||||
if raw[1] is not None:
|
|
||||||
result.nativeClass = classParser.parseAbbreviatedClass(raw[1])
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def filterOutFinalists(self, data: types.State4, filterOut: bool):
|
|
||||||
if filterOut:
|
|
||||||
for group in data.results:
|
|
||||||
groupName = "unknown" if group is None else group.name
|
|
||||||
self.l.debug("Cleaning up group %s", groupName)
|
|
||||||
participants = data.results[group].results.keys()
|
|
||||||
droppedParticipants = []
|
|
||||||
|
|
||||||
for participant in participants:
|
|
||||||
if participant.finalist == False:
|
|
||||||
self.l.info(
|
|
||||||
"Dropping %s from the output as no finalist", participant
|
|
||||||
)
|
|
||||||
droppedParticipants.append(participant)
|
|
||||||
|
|
||||||
for droppedParticipant in droppedParticipants:
|
|
||||||
data.results[group].results.pop(droppedParticipant)
|
|
@ -1,4 +0,0 @@
|
|||||||
from . import ResultExtractor
|
|
||||||
from . import DataWorker
|
|
||||||
from . import Worker
|
|
||||||
from . import OutputShaper
|
|
Loading…
Reference in New Issue
Block a user