summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--deal-converter.py5
-rw-r--r--dealconvert/formats/txt.py125
-rw-r--r--http/dealconvert.js1
-rw-r--r--http/index.html16
-rw-r--r--test/test.txt16
-rw-r--r--tests.py2
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
diff --git a/tests.py b/tests.py
index ac9f260..cc7b71d 100644
--- a/tests.py
+++ b/tests.py
@@ -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)]