diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/.gitignore | 1 | ||||
-rw-r--r-- | src/bidding_data.py | 303 | ||||
-rw-r--r-- | src/icon.ico | bin | 0 -> 50862 bytes | |||
-rw-r--r-- | src/icon.xcf | bin | 0 -> 12001 bytes | |||
-rw-r--r-- | src/version | 39 |
5 files changed, 343 insertions, 0 deletions
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/bidding_data.py b/src/bidding_data.py new file mode 100644 index 0000000..ae789f1 --- /dev/null +++ b/src/bidding_data.py @@ -0,0 +1,303 @@ +import sys +import glob +import re +import pypyodbc + +from os import path +from bs4 import BeautifulSoup as bs4 + + +class JFRBidding: + + # alignment of the bidding table + __directions = ['W', 'N', 'E', 'S'] + + # converts BWS lineup data to + # {round}.{sector}_{table}.{pair numbers} structure + def __parse_lineup_data(self, sitting_data): + round_lineups = {} + for sitting in sitting_data[1:]: + round_no = sitting[2] + table_no = str(sitting[0]) + '_' + str(sitting[1]) + if round_no not in round_lineups: + round_lineups[round_no] = {} + round_lineups[round_no][table_no] = sorted([ + sitting[3], sitting[4]]) + return round_lineups + + # converts BWS bidding to the structure: + # {board}_{round}_{sector}_{table}.{sector}_{table}.{round} -> {bidding}[], + # including erased calls + def __parse_bidding_data(self, bidding_data): + bids = {} + for bid in bidding_data[1:]: + round_no = bid[3] + table_no = str(bid[1]) + '_' + str(bid[2]) + board_no = str(bid[4]) + '_' + str(round_no) + '_' + table_no + bid_counter = bid[5] + bid_erased = bid[10] + if board_no not in bids: + bids[board_no] = {} + if table_no not in bids[board_no]: + bids[board_no][table_no] = {} + if round_no not in bids[board_no][table_no]: + bids[board_no][table_no][round_no] = {} + if bid_erased == 1: + if bid_counter in bids[board_no][table_no][round_no]: + if bids[board_no][table_no][round_no][bid_counter][ + 'direction'] == bid[6]: + bids[board_no][table_no][round_no].pop( + bid_counter, None) + if len(bids[board_no][table_no][round_no]) == 0: + bids[board_no][table_no].pop(round_no, None) + else: + bids[board_no][table_no][round_no][bid_counter] = { + 'direction': bid[6], 'bid': bid[7]} + return bids + + # converts bidding data into HTML table + def __format_bidding(self, bidding): + bid_match = re.compile('(\d)([SHDCN])') + html_output = bs4('<table>', 'lxml') + header_row = html_output.new_tag('tr') + html_output.table.append(header_row) + for direction in self.__directions: + header_cell = html_output.new_tag('th') + header_cell.string = direction + header_row.append(header_cell) + for bid_round in bidding: + bidding_row = html_output.new_tag('tr') + html_output.table.append(bidding_row) + for bid in bid_round: + bid_cell = html_output.new_tag('td') + call_match = re.match(bid_match, bid) + if call_match: + bid_cell.append(call_match.group(1)) + bid_icon = html_output.new_tag( + 'img', src='images/' + call_match.group(2) + '.gif') + bid_cell.append(bid_icon) + else: + bid_cell.append(bid) + bidding_row.append(bid_cell) + return html_output.table.prettify() + + # returns file path for bidding HTML output + # {prefix}_bidding_{jfr_board_number}_{pair_numbers}.txt + def __get_bidding_file_output_path(self, + board_no, + round_no=None, + table_no=None, + pair_numbers=None): + return u'{0}_bidding_{1:03}_{2}.txt'.format( + self.__tournament_prefix, board_no, + '_'.join(map(str, + self.__round_lineups[round_no][table_no] + if pair_numbers is None # read numbers from lineup + else pair_numbers))) # or use provided numbers + + def __map_board_numbers(self): + self.__tournament_files = [ + f for f + in glob.glob(self.__tournament_prefix + '*.html') + if re.search(self.__tournament_files_match, f)] + for round_data in self.__lineup_data: + # 13th column has JFR number for the first board + if len(round_data) > 12: + jfr_number = round_data[12] + round_no = round_data[2] + sector_no = round_data[0] + table_no = round_data[1] + if jfr_number and round_no: + # 5th and 6th - actual board number + for board_number in range(int(round_data[5]), + int(round_data[6])+1): + board_string = '_'.join([ + str(board_number), + str(round_no), + str(sector_no), + str(table_no)]) + self.__board_number_mapping[ + board_string + ] = jfr_number + board_number - round_data[5] + # only include these board numbers from mapping + # which actually exist in JFR output + custom_files = [] + for b_number, jfr_number in self.__board_number_mapping.iteritems(): + board_files = [ + f for f + in self.__tournament_files + if f.endswith('{0:03}.html'.format(jfr_number))] + if len(board_files): + custom_files = custom_files + board_files + else: + self.__board_number_mapping[b_number] = None + self.__tournament_files = custom_files + + # sitting read from BWS + __round_lineups = {} + # bidding read from BWS + __bids = {} + + # full path + JFR prefix + __tournament_prefix = '' + # RegEx matching board HTML files + __tournament_files_match = None + # matched files, including board number mapping boundaries + __tournament_files = [] + + # BWS number -> JFR number mapping + __board_number_mapping = {} + + def __init__(self, bws_file, file_prefix): + with pypyodbc.win_connect_mdb(bws_file) as connection: + cursor = connection.cursor() + self.__lineup_data = cursor.execute( + 'SELECT * FROM RoundData').fetchall() + bid_data = cursor.execute('SELECT * FROM BiddingData').fetchall() + self.__round_lineups = self.__parse_lineup_data(self.__lineup_data) + self.__bids = self.__parse_bidding_data(bid_data) + self.__tournament_prefix = path.splitext( + path.realpath(file_prefix))[0] + self.__tournament_files_match = re.compile( + re.escape(self.__tournament_prefix) + '([0-9]{3})\.html') + self.__map_board_numbers() + + def write_bidding_tables(self): + for board_no, board_data in self.__bids.items(): + if board_no in self.__board_number_mapping: + for table_no, table_data in board_data.items(): + for round_no, round_data in table_data.items(): + if round_no in self.__round_lineups: + if table_no in self.__round_lineups[round_no]: + bidding = sorted(round_data) + dealer = round_data[bidding[0]]['direction'] + bidding_table = [[], [], [], []] + # compile bidding player-by-player + for bid_index in bidding: + bid = round_data[bid_index] + bidding_table[ + self.__directions.index( + bid['direction']) + ].append(bid['bid']) + last_bidder = bid['direction'] + # fill skipped calls for players before dealer + # in the first round of bidding + for pos in range( + 0, self.__directions.index(dealer)): + bidding_table[pos].insert(0, '') + # fill skipped calls for players after pass out + # (so that bidding table is a proper matrix) + for pos in range( + self.__directions.index(last_bidder), + len(self.__directions)): + bidding_table[pos].append('') + # transpose the bidding table + # aligning it row-by-row (bid round-by-round) + bidding_table = map(list, zip(*bidding_table)) + bidding_fpath = \ + self.__get_bidding_file_output_path( + self.__board_number_mapping[board_no], + round_no, table_no) + with file(bidding_fpath, 'w') as bidding_file: + bidding_file.write( + self.__format_bidding(bidding_table)) + + def write_bidding_scripts(self): + for tournament_file in self.__tournament_files: + with file(tournament_file, 'r+') as board_html: + board_content = bs4(board_html, 'lxml', from_encoding='utf-8') + header_scripts = board_content.select('head script') + # check for jQuery, append if necessary + jquery_scripts = [script for script in header_scripts + if script['src'] == 'javas/jquery.js'] + if not len(jquery_scripts): + jquery = board_content.new_tag( + 'script', src='javas/jquery.js', + type='text/javascript') + jquery_scripts.append(jquery) + board_content.head.append(jquery) + # check for bidding.js + bidding_scripts = [ + script for script in header_scripts + if script['src'] == 'javas/bidding.js'] + # and make sure bidding.js is appended after jQuery + for script in bidding_scripts: + script.extract() + bidding_script = board_content.new_tag( + 'script', src='javas/bidding.js', + type='text/javascript') + jquery_scripts[0].insert_after(bidding_script) + board_html.seek(0) + board_html.write(board_content.prettify( + 'utf-8', formatter='html')) + board_html.truncate() + + def write_bidding_links(self): + for tournament_file in self.__tournament_files: + file_number = re.match( + self.__tournament_files_match, + tournament_file).group(1) + board_text_path = path.splitext(tournament_file)[0] + '.txt' + with file(board_text_path, 'r+') as board_text: + board_text_content = bs4( + board_text, 'lxml', from_encoding='iso-8859-2') + for row in board_text_content.select('tr'): + cells = row.select('td') + # traveller table rows for specific score entries + # should have 11 cells + if len(cells) == 11: + try: + pair_numbers = sorted([ + int(cells[1].contents[0]), + int(cells[2].contents[0])]) + except ValueError: + continue + bidding_link = board_text_content.new_tag( + 'a', href='#', **{'class': 'biddingLink'}) + bidding_link.string = ' ' + bidding_link['data-bidding-link'] = path.basename( + self.__get_bidding_file_output_path( + int(file_number, 10), + pair_numbers=pair_numbers)) + # only append link if we've got bidding data + if path.isfile(path.join( + path.dirname(self.__tournament_prefix), + bidding_link['data-bidding-link'])): + # fourth cell is the contract + for link in cells[3].select('a.biddingLink'): + link.extract() + cells[3].append(bidding_link) + board_text.seek(0) + board_text.write(board_text_content.table.prettify( + 'iso-8859-2', formatter='html')) + board_text.truncate() + +if __name__ == '__main__': + import argparse + + argument_parser = argparse.ArgumentParser( + description='Display bidding data from BWS files on JFR Pary 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('bws_file', metavar='BWS_FILE', + help='path to BWS file', + type=file_path) + argument_parser.add_argument('path', metavar='PATH', + help='tournament path (to PREFIX.html)', + type=file_path) + + arguments = argument_parser.parse_args() + + bidding_parser = JFRBidding( + bws_file=arguments.bws_file, + file_prefix=arguments.path, + ) + bidding_parser.write_bidding_tables() + bidding_parser.write_bidding_scripts() + bidding_parser.write_bidding_links() diff --git a/src/icon.ico b/src/icon.ico Binary files differnew file mode 100644 index 0000000..ffc9601 --- /dev/null +++ b/src/icon.ico diff --git a/src/icon.xcf b/src/icon.xcf Binary files differnew file mode 100644 index 0000000..b625556 --- /dev/null +++ b/src/icon.xcf diff --git a/src/version b/src/version new file mode 100644 index 0000000..03aad11 --- /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'bidding_data'), + StringStruct(u'ProductVersion', u'1, 0, 0, 0'), + StringStruct(u'InternalName', u'bidding_data'), + StringStruct(u'OriginalFilename', u'bidding_data.exe'), + StringStruct(u'FileVersion', u'1, 0, 0, 0'), + StringStruct(u'FileDescription', u'Bidding data display for JFR Pary result pages'), + StringStruct(u'LegalCopyright', u'© 2015 mkl (Michał Klichowicz)'), + StringStruct(u'LegalTrademarks', u''),]) + ]), + VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) + ] +) |