From fdb3aa6c1302f2bbb7c4ee384998088799193470 Mon Sep 17 00:00:00 2001 From: emkael Date: Tue, 1 Sep 2015 22:56:09 +0200 Subject: * build tools and environment --- .gitignore | 1 - BUILD.md | 96 ++++++++++++++ README.md | 19 ++- dist/virtual_table.exe | Bin 0 -> 6033276 bytes src/.gitignore | 1 + src/icon.ico | Bin 0 -> 67118 bytes src/icon.xcf | Bin 0 -> 9812 bytes src/version | 39 ++++++ src/virtual_table.py | 330 +++++++++++++++++++++++++++++++++++++++++++++++++ virtual_table.py | 330 ------------------------------------------------- virtual_table.spec | 17 +++ 11 files changed, 500 insertions(+), 333 deletions(-) create mode 100644 BUILD.md create mode 100644 dist/virtual_table.exe create mode 100644 src/.gitignore create mode 100644 src/icon.ico create mode 100644 src/icon.xcf create mode 100644 src/version create mode 100644 src/virtual_table.py delete mode 100644 virtual_table.py create mode 100644 virtual_table.spec diff --git a/.gitignore b/.gitignore index 0d20b64..e69de29 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +0,0 @@ -*.pyc diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..cb5a972 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,96 @@ + +JFR Pary - wirtualne stoliki: Informacje dla programistów +========================================================= + +Struktura repozytorium kodu +--------------------------- + +Katalog [`src`](src) zawiera komponenty źródłowe programu: + +* [kod skryptu Pythona](src/virtual_table.py), który wykonuje całą robotę +* [ikonę programu](src/icon.ico) wraz ze [źródłami](src/icon.xcf) +* [metadane programu](src/version) dla PyInstallera + +Katalogi `dist` i `build` są domyślnie puste i są katalogami roboczymi +PyInstallera. + +W katalogu głównym znajdują się rozmaite README oraz skrypty budujące program. + +Od zera do bohatera - proces budowania programu +----------------------------------------------- + +Jedynym wymaganym do działania narzędzia elementem repozytorium jest źródłowy +skrypt [`virtual_table.py`](src/virtual_table.py). Cała reszta to tylko +fajerwerki i opakowanie w plik wykonywalny. + +Skrypt można uruchomić w dowolnym środowisku, w którym działa Python, a jego +wymagania wymienione są poniżej. `virtual_table.py` przyjmuje parametry +identycznie do wynikowego pliku wykonywalnego. + +--- + +Pliku źródłowego można użyć jako modułu, jeśli kogoś to kręci, importując go +do swojej aplikacji poprzez: +``` +from virtual_table import JFRVirtualTable +``` + +--- + +Skrypt można samodzielnie skompilować do pliku wykonywalnego, używając do tego +PyInstallera. Można to zrobić z pomocą dołączonego pliku [`virtual_table.spec`](virtual_table.spec): +``` +pyinstaller virtual_table.spec +``` +lub samodzielnie, podając odpowiednie parametry do PyInstallera: +``` +pyinstaller --onefile --version-file=src\version --icon=src\icon.ico src\virtual_table.py +``` +Zarówno metadane z pliku `src/version`, jak i ikona programu są w 100% opcjonalne. + +Wynik działania PyInstallera (pojedynczy plik wykonywalny) znajdzie się w katalogu `dist`. + +Wymagania systemowe +------------------- + +Skrypt [`virtual_table.py`](src/virtual_table.py): + +* python 2.x (testowane i tworzone w wersji 2.7.10) +* BeautifulSoup4 +* lxml (jako parser dla BS4) +* argparse + +Kompilacja do EXE: + +* [PyInstaller](http://pythonhosted.org/PyInstaller/) +* PyWin32 + +Kod żródłowy +------------ + +Kod źródłowy stara się, z grubsza: + +* zgadzać ze standardami [PEP8](https://www.python.org/dev/peps/pep-0008/) +* nie robić [głupich rzeczy](http://stackoverflow.com/a/1732454) +* nie psuć raz przekształconej strony przy próbie ponownego przekształcenia +* komentować rzeczy nieoczywiste + +Operacje na stronach JFR +------------------------ + +Ramowy algorytm działania programu: + +0. Jeśli na wejściu nie podano konkretnych numerów "wirtualnych" par, +skanowane są nagłówki plików historii (H-[PREFIKS]-[PARA].html). Puste +nazwiska przyjmowane są jako pary z wirtualnych stolików. +1. Z wyników ([PREFIKS]WYN.txt), pełnych wyników (W-[PREFIKS].html) +oraz zbiorówek (jeśli istnieją, [PREFIKS]zbior.html) usuwane są wiersze, +w których występują pary wirtualne. +2. Z listy historii (H-[PREFIKS]-lista.html) usuwane są linki do historii +par wirtualnych, a cała tabelka jest magicznie układana z powrotem w rządki. +3. Z protokołów usuwane są zapisy par wirtualnych, poza jednym, który dostaje +schludny nagłówek i przesuwany jest w dół protokołu. + +--- + +`It will end no other way.` diff --git a/README.md b/README.md index 3c9de7f..eda71ae 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,38 @@ Przykładowe efekty działania: Wymagania systemowe ------------------- +* system operacyjny MS Windows (testowane na Win7 i Win8.1) +* sterownik ODBC dla plików MS Access (zwykle obecny domyślnie z Windows, +weryfikowalny w Panelu Sterowania -> Narzędziach Administracyjnych -> +Żródła danych ODBC) + +LUB + * python 2.x (testowane i tworzone w wersji 2.7.10) * BeautifulSoup4 * lxml (jako parser dla BS4) * argparse +Kompilacja i praca z kodem narzędzia +------------------------------------ + +Patrz: [`BUILD.md`](BUILD.md) + Instalacja ---------- -Ściągnij zawartość tego repozytorium. +Ściągnij plik wykonywalny z katalogu [`dist`](dist) tego repozytorium. Już, gotowe. +Na nie-Windowsach wystarczy w analogiczny sposób ściągnąć skrypt źródłowy +Python: [`virtual_table.py`](src/virtual_table.py). + Użycie ------ ``` -python virtual_table.py [-t OPIS_STOLIKA] PLIK_TURNIEJU.html [NR_PARY NR_PARY ...] +virtual_table.exe [-t OPIS_STOLIKA] PLIK_TURNIEJU.html [NR_PARY NR_PARY ...] ``` Parametry wejściowe: diff --git a/dist/virtual_table.exe b/dist/virtual_table.exe new file mode 100644 index 0000000..071b4fd Binary files /dev/null and b/dist/virtual_table.exe differ diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/src/icon.ico b/src/icon.ico new file mode 100644 index 0000000..4554f6f Binary files /dev/null and b/src/icon.ico differ diff --git a/src/icon.xcf b/src/icon.xcf new file mode 100644 index 0000000..46d7416 Binary files /dev/null and b/src/icon.xcf differ diff --git a/src/version b/src/version new file mode 100644 index 0000000..d72eb36 --- /dev/null +++ b/src/version @@ -0,0 +1,39 @@ +# UTF-8 +VSVersionInfo( + ffi=FixedFileInfo( + filevers=(1, 0, 0, 0), + prodvers=(1, 0, 0, 0), + # Contains a bitmask that specifies the valid bits 'flags' + mask=0x3f, + # Contains a bitmask that specifies the Boolean attributes of the file. + flags=0x0, + # The operating system for which this file was designed. + # 0x4 - NT and there is no need to change it. + OS=0x4, + # The general type of file. + # 0x1 - the file is an application. + fileType=0x1, + # The function of the file. + # 0x0 - the function is not defined for this fileType + subtype=0x0, + # Creation date and time stamp. + date=(0, 0) + ), + kids=[ + StringFileInfo( + [ + StringTable( + u'040904b0', # 0x0409 = 1033 = English, 0x04b0 = 1200 = UTF-8 + [StringStruct(u'CompanyName', u'emkael.info'), + StringStruct(u'ProductName', u'virtual_table'), + StringStruct(u'ProductVersion', u'1, 0, 0, 0'), + StringStruct(u'InternalName', u'virtual_table'), + StringStruct(u'OriginalFilename', u'virtual_table.exe'), + StringStruct(u'FileVersion', u'1, 0, 0, 0'), + StringStruct(u'FileDescription', u'Virtual table display for JFR Pary result pages'), + StringStruct(u'LegalCopyright', u'© 2015 mkl (Michał Klichowicz)'), + StringStruct(u'LegalTrademarks', u''),]) + ]), + VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) + ] +) diff --git a/src/virtual_table.py b/src/virtual_table.py new file mode 100644 index 0000000..74a99b6 --- /dev/null +++ b/src/virtual_table.py @@ -0,0 +1,330 @@ +import sys +import glob +import re +import math +import copy +import warnings + +from os import path +from bs4 import BeautifulSoup as bs4 +from bs4.element import NavigableString + + +class JFRVirtualTable: + + def __parse_filepaths(self, prefix): + file_path = path.realpath(prefix) + tournament_path = path.dirname(file_path) + tournament_prefix = path.splitext(path.basename(file_path))[0] + + # RegEx matching traveller files for each board + traveller_files_match = re.compile( + re.escape(tournament_prefix) + '([0-9]{3})\.txt' + ) + + # converts {prefix}{anything}.{ext} filename to full path + def get_path(relative_path): + return path.join(tournament_path, relative_path) + + # filtering out traveller files from all TXT files + self.__traveller_files = [f for f + in glob.glob( + get_path(tournament_prefix + '*.txt')) + if re.search(traveller_files_match, f)] + + # RegEx for matching pair record files + records_files_match = re.compile( + 'H-' + tournament_prefix + '-([0-9]{1,3})\.html') + self.__pair_records_files = [ + f for f + in glob.glob(get_path('H-' + tournament_prefix + '*.html')) + if re.search(records_files_match, f) + ] + + # short rersult list, from side frame + self.__results_file = get_path(tournament_prefix + 'WYN.txt') + # full results page + self.__full_results_file = get_path('W-' + tournament_prefix + '.html') + # list of pair records links page + self.__pair_records_list_file = get_path( + 'H-' + tournament_prefix + '-lista.html') + # collected scores page + self.__collected_scores_file = get_path( + tournament_prefix + 'zbior.html') + + # auto-detect virtual pairs by their record file header + def __detect_virtual_pairs(self): + virtual_pairs = [] + # RegEx for matching pair number and names in pair record header + pair_header_match = re.compile('([0-9]{1,}): (.*) - (.*), .*') + for record_file_path in self.__pair_records_files: + with file(record_file_path) as record_file: + record = bs4(record_file, 'lxml') + # first with content matching + # pair header is what we're after + header = [con for con + in record.select('td.o1')[0].contents + if type(con) is NavigableString and re.match( + pair_header_match, con)] + if len(header): + header_match = re.match(pair_header_match, header[0]) + pair_number = int(header_match.group(1)) + names = filter(len, + [header_match.group(2).strip(), + header_match.group(3).strip()]) + # virtual pair does not have any names filled + if len(names) == 0: + virtual_pairs.append(pair_number) + if len(virtual_pairs) == 0: + warnings.warn('No virtual pairs detected') + return sorted(virtual_pairs) + + # wrapper for DOM manipulation + # wraps the inner function into BS4 invokation and file overwrite + def __fix_file(worker): + def file_wrapper(self, file_path, encoding='utf-8'): + with file(file_path, 'r+') as content_file: + content = bs4(content_file, 'lxml', from_encoding=encoding) + content = worker(self, content) + content_file.seek(0) + content_file.write( + content.prettify(encoding, formatter='html')) + content_file.truncate() + return file_wrapper + + # fix simple results list by removing virtual pair rows + @__fix_file + def __fix_results(self, content): + rows = content.select('tr') + for row in rows: + cells = row.select('td') + # 6 or more cells in a "proper" result row + # (may contain carry over or penalties) + if len(cells) >= 6: + try: + # third cell in the row is pair number + if int(cells[2].contents[0]) in self.__virtual_pairs: + row.extract() + except ValueError: + pass + return content.table + + # fix full results file by removing virtual pair rows + @__fix_file + def __fix_full_results(self, content): + rows = content.select('tr') + for row in rows: + # select rows by cells containing pair records links + cell_links = [link for link + in row.select('td a') + if link.has_attr('href') and + link['href'].startswith('H-') and + not link['href'].endswith('lista.html')] + # remove these containing links to virtual pairs + if len(cell_links): + if int(cell_links[0].contents[0]) in self.__virtual_pairs: + row.extract() + return content + + # fix the page with pair records links list + @__fix_file + def __fix_records_list(self, content): + # read the original column count + row_cell_count = int(content.table.select('tr td.o')[0]['colspan']) + rows = content.select('tr') + # gather rows which containted any links + link_rows = [] + # gather cells which should stay + link_cells = [] + for row in rows: + cells = row.select('td.u') + cells_found = False + for cell in cells: + # select cells by pair records links inside + cell_links = [link for link + in cell.select('a.pa') + if link.has_attr('href') and + link['href'].startswith('H-') and + not link['href'].endswith('lista.html')] + if len(cell_links): + # delete virtual pair cells + if int(cell_links[0].contents[0]) in self.__virtual_pairs: + cell.extract() + # store actual pair cells + else: + link_cells.append(cell) + cells_found = True + # gather processed rows + if cells_found: + link_rows.append(row) + # detach actual pair cells from the tree + cells = map(lambda cell: cell.extract(), link_cells) + for row in link_rows: + row.extract() + # first filler cell of each new row + first_cell = content.new_tag('td', **{'class': 'n'}) + first_cell.string = u'\xa0' + # arrange cells into rows, full rows first + while len(cells) >= row_cell_count: + new_row = content.new_tag('tr') + new_row.append(copy.copy(first_cell)) + for cell in cells[0:row_cell_count]: + new_row.append(cell) + content.table.append(new_row) + del cells[0:row_cell_count] + # last row may or may not be full + last_row = content.new_tag('tr') + last_row.append(copy.copy(first_cell)) + for cell in cells: + last_row.append(cell) + # if it wasn't full, fill it with a col-spanned last cell + if len(cells) < row_cell_count: + last_cell = content.new_tag('td', + colspan=row_cell_count-len(cells)) + last_cell.string = u'\xa0' + last_row.append(last_cell) + content.table.append(last_row) + return content + + # fix collected scores tables by removing virtual pair rows + @__fix_file + def __fix_collected(self, content): + rows = content.select('tr') + for row in rows: + cells = row.select('td') + # "proper" rows should have 7 cells + if len(cells) == 7: + # ignore cells without proper pair numbers + try: + if int(cells[1].contents[0]) in self.__virtual_pairs: + if int(cells[2].contents[0]) in self.__virtual_pairs: + row.extract() + except ValueError: + pass + # there are some clearly broken table cells, just throw them away + if len(cells) == 1 and cells[0]['colspan'] == '7': + if cells[0].contents[0] == ' ': + row.extract() + return content + + # fix board travellers, removing virtual tables and leaving one, annotated + @__fix_file + def __fix_traveller(self, content): + # this should only happen if the traveller wasn't already processed + # as it's the only operaton that may yield any results on second run + # and it might break stuff + if not len(content.select('tr.virtualTable')): + # looking for all the rows with more than 2 cells + rows = [row for row + in content.select('tr') + if len(row.select('td')) >= 3] + # only the first "virtual" row needs to be prefixed with a header + header_added = False + virtual_row = None + for row in rows: + cells = row.select('td') + # we're already added a header, meaning we're below the first + # virtual table, we need to move the row above it + # or remove it entirely + if header_added: + row_below = row.extract() + # only move it if it has meaningful information (10 cells) + if len(cells) >= 10: + virtual_row.insert_before(row_below) + # we're looking for a "proper" row, with at least 10 cells + if len(cells) >= 10: + # and with both pair numbers virtual + if int(cells[1].contents[0]) in self.__virtual_pairs: + if int(cells[2].contents[0]) in self.__virtual_pairs: + # if we're already processed the first one, + # just drop subsequent virtual tables + if header_added: + row.extract() + # it's the first virtual table + # prefix it with a header + else: + virtual_row = content.new_tag( + 'tr', + **{'class': 'virtualTable'}) + virtual_row.append( + content.new_tag('td', **{'class': 'n'})) + virtual_row_header = content.new_tag( + 'td', + colspan=10, **{'class': 'noc'}) + virtual_row_header.string = self.__header_text + virtual_row.append(virtual_row_header) + row.insert_before(virtual_row) + # clear pair numbers + for cell in cells[1:3]: + cell.contents = '' + header_added = True + return content.table + + __traveller_files = [] + __pair_records_files = [] + __results_file = None + __full_results_file = None + __pair_records_list_file = None + __collected_scores_file = None + # text for traveller header row + __header_text = '' + + def __init__(self, path_prefix, virtual_pairs=None, header_text=''): + self.__parse_filepaths(path_prefix) + if virtual_pairs is None or len(virtual_pairs) == 0: + virtual_pairs = self.__detect_virtual_pairs() + self.__virtual_pairs = virtual_pairs + self.__header_text = header_text + + def fix_results(self): + self.__fix_results(self.__results_file) + + def fix_full_results(self): + self.__fix_full_results(self.__full_results_file) + + def fix_collected_scores(self): + if path.isfile(self.__collected_scores_file): + self.__fix_collected(self.__collected_scores_file) + else: + warnings.warn('Collected scores file not found') + + def fix_records_list(self): + self.__fix_records_list(self.__pair_records_list_file) + + def fix_travellers(self): + for traveller_file in self.__traveller_files: + self.__fix_traveller(traveller_file, encoding='iso-8859-2') + +if __name__ == '__main__': + import argparse + + argument_parser = argparse.ArgumentParser( + description='Fix display for virtual tables in JFR Pary result pages') + + def file_path(filepath): + filepath = unicode(filepath, sys.getfilesystemencoding()) + if path.isfile(filepath): + return filepath + else: + argument_parser.error('File %s does not exist' % filepath) + + argument_parser.add_argument('path', metavar='PATH', + help='tournament path with JFR prefix', + type=file_path) + argument_parser.add_argument('-t', '--text', metavar='HEADER', + default='Wirtualny stolik:', + help='traveller header for virtual score') + argument_parser.add_argument('pairs', metavar='PAIR_NO', nargs='*', + type=int, help='virtual pair numbers') + + arguments = argument_parser.parse_args() + + table_parser = JFRVirtualTable( + path_prefix=arguments.path, + virtual_pairs=arguments.pairs, + header_text=arguments.text) + table_parser.fix_results() + table_parser.fix_full_results() + table_parser.fix_collected_scores() + table_parser.fix_records_list() + table_parser.fix_travellers() diff --git a/virtual_table.py b/virtual_table.py deleted file mode 100644 index 74a99b6..0000000 --- a/virtual_table.py +++ /dev/null @@ -1,330 +0,0 @@ -import sys -import glob -import re -import math -import copy -import warnings - -from os import path -from bs4 import BeautifulSoup as bs4 -from bs4.element import NavigableString - - -class JFRVirtualTable: - - def __parse_filepaths(self, prefix): - file_path = path.realpath(prefix) - tournament_path = path.dirname(file_path) - tournament_prefix = path.splitext(path.basename(file_path))[0] - - # RegEx matching traveller files for each board - traveller_files_match = re.compile( - re.escape(tournament_prefix) + '([0-9]{3})\.txt' - ) - - # converts {prefix}{anything}.{ext} filename to full path - def get_path(relative_path): - return path.join(tournament_path, relative_path) - - # filtering out traveller files from all TXT files - self.__traveller_files = [f for f - in glob.glob( - get_path(tournament_prefix + '*.txt')) - if re.search(traveller_files_match, f)] - - # RegEx for matching pair record files - records_files_match = re.compile( - 'H-' + tournament_prefix + '-([0-9]{1,3})\.html') - self.__pair_records_files = [ - f for f - in glob.glob(get_path('H-' + tournament_prefix + '*.html')) - if re.search(records_files_match, f) - ] - - # short rersult list, from side frame - self.__results_file = get_path(tournament_prefix + 'WYN.txt') - # full results page - self.__full_results_file = get_path('W-' + tournament_prefix + '.html') - # list of pair records links page - self.__pair_records_list_file = get_path( - 'H-' + tournament_prefix + '-lista.html') - # collected scores page - self.__collected_scores_file = get_path( - tournament_prefix + 'zbior.html') - - # auto-detect virtual pairs by their record file header - def __detect_virtual_pairs(self): - virtual_pairs = [] - # RegEx for matching pair number and names in pair record header - pair_header_match = re.compile('([0-9]{1,}): (.*) - (.*), .*') - for record_file_path in self.__pair_records_files: - with file(record_file_path) as record_file: - record = bs4(record_file, 'lxml') - # first with content matching - # pair header is what we're after - header = [con for con - in record.select('td.o1')[0].contents - if type(con) is NavigableString and re.match( - pair_header_match, con)] - if len(header): - header_match = re.match(pair_header_match, header[0]) - pair_number = int(header_match.group(1)) - names = filter(len, - [header_match.group(2).strip(), - header_match.group(3).strip()]) - # virtual pair does not have any names filled - if len(names) == 0: - virtual_pairs.append(pair_number) - if len(virtual_pairs) == 0: - warnings.warn('No virtual pairs detected') - return sorted(virtual_pairs) - - # wrapper for DOM manipulation - # wraps the inner function into BS4 invokation and file overwrite - def __fix_file(worker): - def file_wrapper(self, file_path, encoding='utf-8'): - with file(file_path, 'r+') as content_file: - content = bs4(content_file, 'lxml', from_encoding=encoding) - content = worker(self, content) - content_file.seek(0) - content_file.write( - content.prettify(encoding, formatter='html')) - content_file.truncate() - return file_wrapper - - # fix simple results list by removing virtual pair rows - @__fix_file - def __fix_results(self, content): - rows = content.select('tr') - for row in rows: - cells = row.select('td') - # 6 or more cells in a "proper" result row - # (may contain carry over or penalties) - if len(cells) >= 6: - try: - # third cell in the row is pair number - if int(cells[2].contents[0]) in self.__virtual_pairs: - row.extract() - except ValueError: - pass - return content.table - - # fix full results file by removing virtual pair rows - @__fix_file - def __fix_full_results(self, content): - rows = content.select('tr') - for row in rows: - # select rows by cells containing pair records links - cell_links = [link for link - in row.select('td a') - if link.has_attr('href') and - link['href'].startswith('H-') and - not link['href'].endswith('lista.html')] - # remove these containing links to virtual pairs - if len(cell_links): - if int(cell_links[0].contents[0]) in self.__virtual_pairs: - row.extract() - return content - - # fix the page with pair records links list - @__fix_file - def __fix_records_list(self, content): - # read the original column count - row_cell_count = int(content.table.select('tr td.o')[0]['colspan']) - rows = content.select('tr') - # gather rows which containted any links - link_rows = [] - # gather cells which should stay - link_cells = [] - for row in rows: - cells = row.select('td.u') - cells_found = False - for cell in cells: - # select cells by pair records links inside - cell_links = [link for link - in cell.select('a.pa') - if link.has_attr('href') and - link['href'].startswith('H-') and - not link['href'].endswith('lista.html')] - if len(cell_links): - # delete virtual pair cells - if int(cell_links[0].contents[0]) in self.__virtual_pairs: - cell.extract() - # store actual pair cells - else: - link_cells.append(cell) - cells_found = True - # gather processed rows - if cells_found: - link_rows.append(row) - # detach actual pair cells from the tree - cells = map(lambda cell: cell.extract(), link_cells) - for row in link_rows: - row.extract() - # first filler cell of each new row - first_cell = content.new_tag('td', **{'class': 'n'}) - first_cell.string = u'\xa0' - # arrange cells into rows, full rows first - while len(cells) >= row_cell_count: - new_row = content.new_tag('tr') - new_row.append(copy.copy(first_cell)) - for cell in cells[0:row_cell_count]: - new_row.append(cell) - content.table.append(new_row) - del cells[0:row_cell_count] - # last row may or may not be full - last_row = content.new_tag('tr') - last_row.append(copy.copy(first_cell)) - for cell in cells: - last_row.append(cell) - # if it wasn't full, fill it with a col-spanned last cell - if len(cells) < row_cell_count: - last_cell = content.new_tag('td', - colspan=row_cell_count-len(cells)) - last_cell.string = u'\xa0' - last_row.append(last_cell) - content.table.append(last_row) - return content - - # fix collected scores tables by removing virtual pair rows - @__fix_file - def __fix_collected(self, content): - rows = content.select('tr') - for row in rows: - cells = row.select('td') - # "proper" rows should have 7 cells - if len(cells) == 7: - # ignore cells without proper pair numbers - try: - if int(cells[1].contents[0]) in self.__virtual_pairs: - if int(cells[2].contents[0]) in self.__virtual_pairs: - row.extract() - except ValueError: - pass - # there are some clearly broken table cells, just throw them away - if len(cells) == 1 and cells[0]['colspan'] == '7': - if cells[0].contents[0] == ' ': - row.extract() - return content - - # fix board travellers, removing virtual tables and leaving one, annotated - @__fix_file - def __fix_traveller(self, content): - # this should only happen if the traveller wasn't already processed - # as it's the only operaton that may yield any results on second run - # and it might break stuff - if not len(content.select('tr.virtualTable')): - # looking for all the rows with more than 2 cells - rows = [row for row - in content.select('tr') - if len(row.select('td')) >= 3] - # only the first "virtual" row needs to be prefixed with a header - header_added = False - virtual_row = None - for row in rows: - cells = row.select('td') - # we're already added a header, meaning we're below the first - # virtual table, we need to move the row above it - # or remove it entirely - if header_added: - row_below = row.extract() - # only move it if it has meaningful information (10 cells) - if len(cells) >= 10: - virtual_row.insert_before(row_below) - # we're looking for a "proper" row, with at least 10 cells - if len(cells) >= 10: - # and with both pair numbers virtual - if int(cells[1].contents[0]) in self.__virtual_pairs: - if int(cells[2].contents[0]) in self.__virtual_pairs: - # if we're already processed the first one, - # just drop subsequent virtual tables - if header_added: - row.extract() - # it's the first virtual table - # prefix it with a header - else: - virtual_row = content.new_tag( - 'tr', - **{'class': 'virtualTable'}) - virtual_row.append( - content.new_tag('td', **{'class': 'n'})) - virtual_row_header = content.new_tag( - 'td', - colspan=10, **{'class': 'noc'}) - virtual_row_header.string = self.__header_text - virtual_row.append(virtual_row_header) - row.insert_before(virtual_row) - # clear pair numbers - for cell in cells[1:3]: - cell.contents = '' - header_added = True - return content.table - - __traveller_files = [] - __pair_records_files = [] - __results_file = None - __full_results_file = None - __pair_records_list_file = None - __collected_scores_file = None - # text for traveller header row - __header_text = '' - - def __init__(self, path_prefix, virtual_pairs=None, header_text=''): - self.__parse_filepaths(path_prefix) - if virtual_pairs is None or len(virtual_pairs) == 0: - virtual_pairs = self.__detect_virtual_pairs() - self.__virtual_pairs = virtual_pairs - self.__header_text = header_text - - def fix_results(self): - self.__fix_results(self.__results_file) - - def fix_full_results(self): - self.__fix_full_results(self.__full_results_file) - - def fix_collected_scores(self): - if path.isfile(self.__collected_scores_file): - self.__fix_collected(self.__collected_scores_file) - else: - warnings.warn('Collected scores file not found') - - def fix_records_list(self): - self.__fix_records_list(self.__pair_records_list_file) - - def fix_travellers(self): - for traveller_file in self.__traveller_files: - self.__fix_traveller(traveller_file, encoding='iso-8859-2') - -if __name__ == '__main__': - import argparse - - argument_parser = argparse.ArgumentParser( - description='Fix display for virtual tables in JFR Pary result pages') - - def file_path(filepath): - filepath = unicode(filepath, sys.getfilesystemencoding()) - if path.isfile(filepath): - return filepath - else: - argument_parser.error('File %s does not exist' % filepath) - - argument_parser.add_argument('path', metavar='PATH', - help='tournament path with JFR prefix', - type=file_path) - argument_parser.add_argument('-t', '--text', metavar='HEADER', - default='Wirtualny stolik:', - help='traveller header for virtual score') - argument_parser.add_argument('pairs', metavar='PAIR_NO', nargs='*', - type=int, help='virtual pair numbers') - - arguments = argument_parser.parse_args() - - table_parser = JFRVirtualTable( - path_prefix=arguments.path, - virtual_pairs=arguments.pairs, - header_text=arguments.text) - table_parser.fix_results() - table_parser.fix_full_results() - table_parser.fix_collected_scores() - table_parser.fix_records_list() - table_parser.fix_travellers() diff --git a/virtual_table.spec b/virtual_table.spec new file mode 100644 index 0000000..bca9843 --- /dev/null +++ b/virtual_table.spec @@ -0,0 +1,17 @@ +import os +a = Analysis(['src\\virtual_table.py'], + pathex=[os.path.abspath('.')], + hiddenimports=[], + hookspath=None, + runtime_hooks=None) +pyz = PYZ(a.pure) +exe = EXE(pyz, + a.scripts, + a.binaries, + a.zipfiles, + a.datas, + name='virtual_table.exe', + debug=False, + strip=None, + upx=True, + console=True , version='src\\version', icon='src\\icon.ico') -- cgit v1.2.3