diff options
| -rw-r--r-- | deal-converter.py | 5 | ||||
| -rw-r--r-- | dealconvert/formats/txt.py | 125 | ||||
| -rw-r--r-- | http/dealconvert.js | 1 | ||||
| -rw-r--r-- | http/index.html | 16 | ||||
| -rw-r--r-- | test/test.txt | 16 | ||||
| -rw-r--r-- | tests.py | 2 |
6 files changed, 162 insertions, 3 deletions
diff --git a/deal-converter.py b/deal-converter.py index de0d01a..7f611c7 100644 --- a/deal-converter.py +++ b/deal-converter.py @@ -6,11 +6,12 @@ from dealconvert import DealConverter parser = argparse.ArgumentParser( description='Universal converter for bridge deal formats', formatter_class=argparse.RawTextHelpFormatter, - epilog='Supported formats: BER BHG BRI CDS CSV DGE DLM DUP LIN PBN RZD.\n' + \ + epilog='Supported formats: BER BHG BRI CDS CSV DGE DLM DUP LIN PBN RZD TXT.\n' + \ 'Formats are auto-detected based on file extension.\n' + \ 'To display deals on STDOUT, provide "-" as an output file name.\n' + \ 'To print deals to HTML file, use *.html output file.\n' + \ - 'To print deals to PDF file, use *.pdf output file.\n\n' + \ + 'To print deals to PDF file, use *.pdf output file.\n' + \ + 'TXT format is a text file containing BBO HandViewer URLs, one board/URL per line.\n\n' + \ 'PDF output requires wkhtmltopdf in a patched-qt version.') parser.add_argument('--jfr', action='store_true', help='For PBN file, write only JFR DD fields') diff --git a/dealconvert/formats/txt.py b/dealconvert/formats/txt.py new file mode 100644 index 0000000..e29f37f --- /dev/null +++ b/dealconvert/formats/txt.py @@ -0,0 +1,125 @@ +from collections import OrderedDict +from urlparse import urlparse, parse_qsl +from urllib import urlencode +import re, warnings + +from . import DealFormat +from .. import dto + +class TXTFormat(DealFormat): + + _url_prefix = 'https://www.bridgebase.com/tools/handviewer.html' + _hand_format = re.compile(r'([shdc])([akqjt2-9]*)', re.I) + _hand_designations = { + 'n': dto.POSITION_NORTH, + 'e': dto.POSITION_EAST, + 's': dto.POSITION_SOUTH, + 'w': dto.POSITION_WEST + } + _suit_designations = { + 's': dto.SUIT_SPADES, + 'h': dto.SUIT_HEARTS, + 'd': dto.SUIT_DIAMONDS, + 'c': dto.SUIT_CLUBS + } + + @property + def suffix(self): + return '.txt' + + def parse_content(self, content): + deal_list = [] + deal_numbers = [] + renumber_deals = False + event = '' + lines = [line.strip() for line in content.readlines()] + for line in lines: + if line.startswith(self._url_prefix): + deal = dto.Deal() + url = urlparse(line) + params = OrderedDict(parse_qsl(url.query)) + for hand in self._hand_designations.keys(): + if hand not in params: + raise RuntimeError( + 'URL %s is missing "%s" hand' % ( + line, hand + ) + ) + suits = OrderedDict( + self._hand_format.findall(params[hand])) + for suit, cards in suits.iteritems(): + cards = cards.upper() + deal.hands[ + self._hand_designations[hand] + ][ + self._suit_designations[suit] + ] = list(cards) + if 'b' in params: + try: + deal.number = int(params['b']) + except: + warnings.warn( + 'URL %s contains no deal number. ALL deals will be auto-number, maintaining their vulnerability if defined' % (line) + ) + renumber_deals = True + else: + warnings.warn( + 'URL %s contains no deal number. ALL deals will be auto-number, maintaining their vulnerability if defined' % (line) + ) + renumber_deals = True + if deal.number: + if deal.number in deal_numbers: + warnings.warn( + 'Duplicate deal number %d. ALL deals will be auto-number, maintaining their vulnerability if defined' % (deal.number) + ) + renumber_deals = True + deal_numbers.append(deal.number) + if 'v' in params: + deal.vulnerable['NS'] = params['v'].lower() in ['b', 'n'] + deal.vulnerable['EW'] = params['v'].lower() in ['b', 'e'] + else: + deal.vulnerable = None + if 'd' in params: + deal.dealer = self._hand_designations.get( + params['d'], None) + deal.event = event + event = '' + deal_list.append(deal) + else: + event = line + for idx, deal in enumerate(deal_list): + if renumber_deals: + deal.number = idx+1 + if not deal.vulnerable: + deal.vulnerable = deal.get_vulnerability(deal.number) + if not deal.dealer: + deal.dealer = deal.get_dealer(deal.number) + return deal_list + + def output_content(self, out_file, dealset): + lines = [] + dealers = {} + for designation, hand in self._hand_designations.iteritems(): + dealers[hand] = designation + suits = {} + for deal in dealset: + if deal.event: + lines.append(deal.event) + params = { + 'b': deal.number, + 'd': dealers[deal.dealer], + 'v': ['0', 'n', 'e', 'b'][ + deal.vulnerable['NS'] + 2 * deal.vulnerable['EW'] + ], + 'a': 'pppp' + } + for hand, designation in dealers.iteritems(): + params[designation] = '' + for suit, idx in self._suit_designations.iteritems(): + params[designation] += suit + params[designation] += ''.join(deal.hands[hand][idx]).lower() + url = '%s?%s' % ( + self._url_prefix, urlencode(params)) + lines.append(url) + for line in lines: + out_file.write(line + '\r\n') diff --git a/http/dealconvert.js b/http/dealconvert.js index ab8e90b..7060324 100644 --- a/http/dealconvert.js +++ b/http/dealconvert.js @@ -1,5 +1,6 @@ $(document).ready(function() { $('a.faq-btn').popover(); + $('a.txt-faq-btn').popover(); $('input[name="output"]').change(function() { if ($('input[name="output"]:checked').length > 0) { $('#submit-btn').removeAttr('disabled'); diff --git a/http/index.html b/http/index.html index abb1e7e..dec2d46 100644 --- a/http/index.html +++ b/http/index.html @@ -64,6 +64,11 @@ <li>Prawdopodobnie konwerter zgłosi błąd sumy kontrolnej, można go ignorować, jeśli nie chce się używać pośredniego pliku <code>DLM</code>.</li> <li>Wynikowy plik <code>PBN</code> rozpoczyna się od rozdania nr 1 i jest uzupełniony rozdaniami z 13-kartowymi kolorami w każdej z rąk.</li> </ol> + </dd> + <dt>Czemu mój PBN nie ma zapisu licytacji z HandViewera BBO?</dt> + <dd> + Bo mój przypadek użycia zakładałby raczej konwersję w drugą stronę (z PBN do HandViewera), a ta wydała mi się zbyt upierdliwa jak na pierwsze podejście do tematu. + </dd> <dt>Czy to bezpieczne, tak wysyłać rozkłady do Internetu?</dt> <dd> Doskonałe pytanie!<br /> @@ -162,6 +167,17 @@ <label class="form-check-label" for="output-pdf">Wydruk (PDF)</label> </div> </div> + <div class="col-md-6 col-lg-4"> + </div> + <div class="col-md-6 col-lg-4"> + </div> + <div class="col-md-6 col-lg-4"> + <div class="form-check"> + <input class="form-check-input" type="checkbox" name="output" value="txt" id="output-txt" /> + <label class="form-check-label" for="output-txt">BBO HandViewer (TXT)</label> + <a href="#" class="txt-faq-btn" data-trigger="hover" data-content="Lista linków do rozdań w HandViewerze" data-placement="bottom">ⓘ</a> + </div> + </div> </div> </div> <div class="form-group collapse" id="submit-panel"> diff --git a/test/test.txt b/test/test.txt new file mode 100644 index 0000000..8421c7a --- /dev/null +++ b/test/test.txt @@ -0,0 +1,16 @@ +https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=1&e=hqt4sak87c95dak98&d=n&n=ha53s65cak762djt7&s=h982sjt9cq83dq532&w=hkj76sq432cjt4d64&v=0
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=2&e=ht5st942c9643dkt6&d=e&n=hj7saq63cakj7dq42&s=h4sj85cq82daj8753&w=hakq98632sk7ct5d9&v=n
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=3&e=ha7sk764caq9dq543&d=s&n=hkq9863sqt5ct8dt7&s=hj42saj92c72dkj96&w=ht5s83ckj6543da82&v=e
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=4&e=hak42sj6ct2dajt83&d=w&n=hj96sq942ck73dq94&s=h753st53cj64dk762&w=hqt8sak87caq985d5&v=b
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=5&e=hskq64caq8653dq73&d=n&n=hkt954sa2cjt74dk9&s=haq8732s53c2djt85&w=hj6sjt987ck9da642&v=n
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=6&e=hqjt6542st4c763d8&d=e&n=h83skj52c8daj5432&s=hak9saqcqjt542dqt&w=h7s98763cak9dk976&v=e
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=7&e=h7sk965cakq3dt654&d=s&n=hk95sjt32ct64daj2&s=haqj642s84c987d98&w=ht83saq7cj52dkq73&v=b
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=8&e=hat74s43ca642dqt7&d=w&n=hst865cqj75daj432&s=h9852sakj7c3dk965&w=hkqj63sq92ckt98d8&v=0
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=9&e=h632sk4cjt86dqj52&d=n&n=ht84sq93ck95dk987&s=haq5sajt76ca742dt&w=hkj97s852cq3da643&v=e
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=10&e=h74sqjtct3djt9643&d=e&n=hqj82s62cj985da87&s=hat953sk984cak6dk&w=hk6sa753cq742dq52&v=b
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=11&e=haj5s8cak94dj9762&d=s&n=hqt9432saqj6c62dt&s=h76skt972cqjt7dk5&w=hk8s543c853daq843&v=0
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=12&e=htsq876cj43dak543&d=w&n=hj73skt9532c6dqj2&s=hak952sa4c975dt97&w=hq864sjcakqt82d86&v=n
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=13&e=haq8skqj98c743d43&d=n&n=h7654s54cj652dak9&s=hj9sat2cakq8dt876&w=hkt32s763ct9dqj52&v=b
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=14&e=hk72s642c8753dq93&d=e&n=hq543sk93ctdaj852&s=hjt6saqcakq96dkt6&w=ha98sjt875cj42d74&v=0
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=15&e=ha75s7542cqjt92d4&d=s&n=hqt4saqjc743daq53&s=h3skt83c865dk9872&w=hkj9862s96cakdjt6&v=n
+https://www.bridgebase.com/tools/handviewer.html?a=pppp&b=16&e=hakj854sk95ck52d5&d=w&n=hq732sq3cjt643d87&s=ht96sjt4ca98da632&w=hsa8762cq7dkqjt94&v=e
@@ -5,7 +5,7 @@ from dealconvert import DealConverter import pytest -formats = ['ber', 'bhg', 'bri', 'rzd', 'cds', 'csv', 'dge', 'dlm', 'dup', 'lin', 'pbn'] +formats = ['ber', 'bhg', 'bri', 'rzd', 'cds', 'csv', 'dge', 'dlm', 'dup', 'lin', 'pbn', 'txt'] format_files = ['test.' + f for f in formats] + ['test-jfr.pbn'] test_data = [(d[0], d[1], d[1] == 'test-jfr.pbn') for d in itertools.product(format_files, format_files)] |
