From 3ae4e38e90348da0169cd946e9230aae65f1ba15 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 2 Feb 2016 21:09:35 +0000 Subject: [PATCH] Reordered functions. git-svn-id: http://www.winterrodeln.org/svn/wrpylib/trunk@2439 7aebc617-e5e2-0310-91dc-80fb5f6d2477 --- tests/test_wrvalidators.py | 218 ++++++---- wrpylib/wrvalidators.py | 801 ++++++++++++++++++------------------- 2 files changed, 530 insertions(+), 489 deletions(-) diff --git a/tests/test_wrvalidators.py b/tests/test_wrvalidators.py index 3b6a299..034b986 100644 --- a/tests/test_wrvalidators.py +++ b/tests/test_wrvalidators.py @@ -2,18 +2,105 @@ # -*- coding: iso-8859-15 -*- import collections import wrpylib.wrvalidators -import formencode import unittest from wrpylib.wrvalidators import * +# TODO: optional converter +# ------------------ + +# "no" converter +# -------------- + +class TestNoGermanConverter(unittest.TestCase): + def test_from_str(self): + self.assertEqual(no_german_from_str('abc'), (True, 'abc')) + self.assertEqual(no_german_from_str('Nein'), (False, None)) + with self.assertRaises(ValueError): + no_german_from_str('') + + def test_to_str(self): + self.assertEqual(no_german_to_str((True, 'abc')), 'abc') + self.assertEqual(no_german_to_str((False, None)), 'Nein') + + +# "optional"/"no" converter +# ------------------------- + +class TestOptNoGerman(unittest.TestCase): + def test_from_str(self): + self.assertEqual(opt_no_german_from_str('abc'), (True, 'abc')) + self.assertEqual(opt_no_german_from_str('Nein'), (False, None)) + self.assertEqual(opt_no_german_from_str(''), (None, None)) + + def test_to_str(self): + self.assertEqual(opt_no_german_to_str((True, 'abc')), 'abc') + self.assertEqual(opt_no_german_to_str((False, None)), 'Nein') + self.assertEqual(opt_no_german_to_str((None, None)), '') + + +# TODO: choice converter +# ---------------- + + +# TODO: dict converter +# -------------- + + +# TODO: enum/"list" converter +# --------------------- + + +# TODO: value/comment converter +# ----------------------- + + +# string converter +# ---------------- + +class TestStr(unittest.TestCase): + def test_from_str(self): + self.assertEqual('', str_from_str('')) + self.assertEqual('abc', str_from_str('abc')) + + def test_to_str(self): + self.assertEqual('', str_to_str('')) + self.assertEqual('abc', str_to_str('abc')) + + +class TestReqStr(unittest.TestCase): + def test_from_str(self): + self.assertEqual('abc', req_str_from_str('abc')) + self.assertEqual(' ', req_str_from_str(' ')) + with self.assertRaises(ValueError): + req_str_from_str('') + + +class TestOptStr(unittest.TestCase): + def test_from_str(self): + self.assertEqual('abc', opt_str_from_str('abc')) + self.assertEqual(' ', opt_str_from_str(' ')) + self.assertEqual(None, opt_str_from_str('')) + + def test_to_str(self): + self.assertEqual('abc', opt_str_to_str('abc')) + self.assertEqual(' ', opt_str_to_str(' ')) + self.assertEqual('', opt_str_to_str(None)) + + +# TODO: optional no or string converter +# ------------------------------- + + +# integer converter +# ----------------- class TestInt(unittest.TestCase): def test_from_str(self): - self.assertEqual(int_from_str('42'), 42) - self.assertEqual(int_from_str('+42'), 42) - self.assertEqual(int_from_str('-20'), -20) - self.assertEqual(int_from_str('0', min=0), 0) - self.assertEqual(int_from_str('10', max=10), 10) + self.assertEqual(42, int_from_str('42')) + self.assertEqual(42, int_from_str('+42')) + self.assertEqual(-20, int_from_str('-20')) + self.assertEqual(0, int_from_str('0', min=0)) + self.assertEqual(10, int_from_str('10', max=10)) with self.assertRaises(ValueError): int_from_str('abc') with self.assertRaises(ValueError): @@ -28,17 +115,17 @@ class TestInt(unittest.TestCase): int_from_str('0d') def test_to_str(self): - self.assertEqual(int_to_str(20), '20') - self.assertEqual(int_to_str(-20), '-20') - self.assertEqual(int_to_str(0), '0') + self.assertEqual('20', int_to_str(20)) + self.assertEqual('-20', int_to_str(-20)) + self.assertEqual('0', int_to_str(0)) class TestOptInt(unittest.TestCase): def test_from_str(self): - self.assertEqual(opt_int_from_str('42'), 42) - self.assertEqual(opt_int_from_str('+42'), 42) - self.assertEqual(opt_int_from_str('-20'), -20) - self.assertEqual(opt_int_from_str(''), None) + self.assertEqual(42, opt_int_from_str('42')) + self.assertEqual(42, opt_int_from_str('+42')) + self.assertEqual(-20, opt_int_from_str('-20')) + self.assertEqual(None, opt_int_from_str('')) with self.assertRaises(ValueError): opt_int_from_str('abc') with self.assertRaises(ValueError): @@ -47,10 +134,28 @@ class TestOptInt(unittest.TestCase): opt_int_from_str('0d') def test_to_str(self): - self.assertEqual(opt_int_to_str(20), '20') - self.assertEqual(opt_int_to_str(-20), '-20') - self.assertEqual(opt_int_to_str(0), '0') - self.assertEqual(opt_int_to_str(None), '') + self.assertEqual('20', opt_int_to_str(20)) + self.assertEqual('-20', opt_int_to_str(-20)) + self.assertEqual('0', opt_int_to_str(0)) + self.assertEqual('', opt_int_to_str(None)) + + +class TestOptUInt(unittest.TestCase): + def test_from_str(self): + self.assertEqual(42, opt_uint_from_str('42')) + self.assertEqual(0, opt_uint_from_str('0')) + self.assertEqual(None, opt_uint_from_str('')) + with self.assertRaises(ValueError): + opt_uint_from_str('-1') + + def test_to_str(self): + self.assertEqual('20', opt_uint_to_str(20)) + self.assertEqual('0', opt_uint_to_str(0)) + self.assertEqual('', opt_uint_to_str(None)) + + +# bool converter +# -------------- class TestEnumConverter(unittest.TestCase): @@ -173,30 +278,6 @@ class TestValueCommentConverter(unittest.TestCase): self.assertEqual(value_comment_to_str(('abc', ''), comment_optional=True), 'abc') -class TestNoGermanConverter(unittest.TestCase): - def test_from_str(self): - self.assertEqual(no_german_from_str('abc'), (True, 'abc')) - self.assertEqual(no_german_from_str('Nein'), (False, None)) - with self.assertRaises(ValueError): - no_german_from_str('') - - def test_to_str(self): - self.assertEqual(no_german_to_str((True, 'abc')), 'abc') - self.assertEqual(no_german_to_str((False, None)), 'Nein') - - -class TestOptNoGerman(unittest.TestCase): - def test_from_str(self): - self.assertEqual(opt_no_german_from_str('abc'), (True, 'abc')) - self.assertEqual(opt_no_german_from_str('Nein'), (False, None)) - self.assertEqual(opt_no_german_from_str(''), (None, None)) - - def test_to_str(self): - self.assertEqual(opt_no_german_to_str((True, 'abc')), 'abc') - self.assertEqual(opt_no_german_to_str((False, None)), 'Nein') - self.assertEqual(opt_no_german_to_str((None, None)), '') - - class TestLiftGermanValidator(unittest.TestCase): def test_from_str(self): self.assertEqual(lift_german_from_str(''), None) @@ -249,11 +330,11 @@ class TestOptStrOptCommentEnum(unittest.TestCase): opt_str_opt_comment_enum_from_str('Talstation (unten); ; Mittelstation') def test_to_str(self): - self.assertEqual(sledrental_to_str(None), '') - self.assertEqual(sledrental_to_str([]), 'Nein') - self.assertEqual(sledrental_to_str([('Talstation', None)]), 'Talstation') - self.assertEqual(sledrental_to_str([('Talstation', 'unten')]), 'Talstation (unten)') - self.assertEqual(sledrental_to_str([('Talstation', 'unten'), ('Mittelstation', None)]), 'Talstation (unten); Mittelstation') + self.assertEqual(opt_str_opt_comment_enum_to_str(None), '') + self.assertEqual(opt_str_opt_comment_enum_to_str([]), 'Nein') + self.assertEqual(opt_str_opt_comment_enum_to_str([('Talstation', None)]), 'Talstation') + self.assertEqual(opt_str_opt_comment_enum_to_str([('Talstation', 'unten')]), 'Talstation (unten)') + self.assertEqual(opt_str_opt_comment_enum_to_str([('Talstation', 'unten'), ('Mittelstation', None)]), 'Talstation (unten); Mittelstation') class TestNoOrStr(unittest.TestCase): @@ -357,20 +438,20 @@ class TestPhoneNumber(unittest.TestCase): class TestTelefonauskunft(unittest.TestCase): def test_from_str(self): - self.assertEqual(telefonauskunft_from_str(''), None) - self.assertEqual(telefonauskunft_from_str('Nein'), []) - self.assertEqual(telefonauskunft_from_str('+43-512-123456 (untertags)'), [('+43-512-123456', 'untertags')]) - self.assertEqual(telefonauskunft_from_str('+43-512-1234 (untertags); +43-664-123456 (Alm)'), [('+43-512-1234', 'untertags'), ('+43-664-123456', 'Alm')]) + self.assertEqual(opt_phone_comment_enum_from_str(''), None) + self.assertEqual(opt_phone_comment_enum_from_str('Nein'), []) + self.assertEqual(opt_phone_comment_enum_from_str('+43-512-123456 (untertags)'), [('+43-512-123456', 'untertags')]) + self.assertEqual(opt_phone_comment_enum_from_str('+43-512-1234 (untertags); +43-664-123456 (Alm)'), [('+43-512-1234', 'untertags'), ('+43-664-123456', 'Alm')]) with self.assertRaises(ValueError): - telefonauskunft_from_str('+43-512-123456+ (untertags)') + opt_phone_comment_enum_from_str('+43-512-123456+ (untertags)') with self.assertRaises(ValueError): - telefonauskunft_from_str('+43-512-123456') + opt_phone_comment_enum_from_str('+43-512-123456') def test_to_str(self): - self.assertEqual(telefonauskunft_to_str(None), '') - self.assertEqual(telefonauskunft_to_str([]), 'Nein') - self.assertEqual(telefonauskunft_to_str([('+43-512-123456', 'untertags')]), '+43-512-123456 (untertags)') - self.assertEqual(telefonauskunft_to_str([('+43-512-1234', 'untertags'), ('+43-664-123456', 'Alm')]), '+43-512-1234 (untertags); +43-664-123456 (Alm)') + self.assertEqual(opt_phone_comment_enum_to_str(None), '') + self.assertEqual(opt_phone_comment_enum_to_str([]), 'Nein') + self.assertEqual(opt_phone_comment_enum_to_str([('+43-512-123456', 'untertags')]), '+43-512-123456 (untertags)') + self.assertEqual(opt_phone_comment_enum_to_str([('+43-512-1234', 'untertags'), ('+43-664-123456', 'Alm')]), '+43-512-1234 (untertags); +43-664-123456 (Alm)') class TestEmail(unittest.TestCase): @@ -458,31 +539,31 @@ class TestBox(unittest.TestCase): def test_from_str(self): value = '{{MyTemplate|apple=2|banana=5}}' converter_dict = OrderedDict([('apple', opt_uint_converter), ('banana', opt_uint_converter)]) - result = box_from_str(value, 'MyTemplate', converter_dict) + result = wikibox_from_str(value, 'MyTemplate', converter_dict) self.assertEqual(result['apple'], 2) self.assertEqual(result['banana'], 5) value = '{{MyTemplate\n | apple = 2 \n| banana = 5 }}' - result = box_from_str(value, 'MyTemplate', converter_dict) + result = wikibox_from_str(value, 'MyTemplate', converter_dict) self.assertEqual(result['apple'], 2) self.assertEqual(result['banana'], 5) with self.assertRaises(ValueError): - box_from_str(value, 'myTemplate', converter_dict) + wikibox_from_str(value, 'myTemplate', converter_dict) with self.assertRaises(ValueError): value = '{{MyTemplate|apple=2|banana=five}}' - box_from_str(value, 'MyTemplate', converter_dict) + wikibox_from_str(value, 'MyTemplate', converter_dict) with self.assertRaises(ValueError): value = '{{MyTemplate|apple=2}}' - box_from_str(value, 'MyTemplate', converter_dict) + wikibox_from_str(value, 'MyTemplate', converter_dict) with self.assertRaises(ValueError): value = '{{MyTemplate|apple=2|banana=5|cherry=6}}' - box_from_str(value, 'MyTemplate', converter_dict) + wikibox_from_str(value, 'MyTemplate', converter_dict) def test_to_str(self): value = OrderedDict([('apple', 2), ('banana', 5)]) converter_dict = OrderedDict([('apple', opt_uint_converter), ('banana', opt_uint_converter)]) - result = box_to_str(value, 'MyTemplate', converter_dict) + result = wikibox_to_str(value, 'MyTemplate', converter_dict) self.assertEqual(result, '{{MyTemplate|apple=2|banana=5}}') @@ -581,16 +662,7 @@ class TestWrValidators(unittest.TestCase): assert v.from_python('T-Mobile (gut); A1') == 'T-Mobile (gut); A1' - def test_PhoneCommentListNeinLoopNone(self): - v = wrpylib.wrvalidators.PhoneCommentListNeinLoopNone(comments_are_optional=True) - assert v.to_python('') == None - assert v.to_python('Nein') == 'Nein' - assert v.to_python('+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456') == '+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' - assert v.from_python(None) == '' - assert v.from_python('Nein') == 'Nein' - assert v.from_python('+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456') == '+43-699-1234567 (nicht nach 20:00 Uhr); +43-512-123456' - - +class TestGasthausbox(unittest.TestCase): def test_GasthausboxDictValidator(self): v = wrpylib.wrvalidators.GasthausboxDictValidator() other = collections.OrderedDict([ diff --git a/wrpylib/wrvalidators.py b/wrpylib/wrvalidators.py index 8d158e5..d6fe67f 100644 --- a/wrpylib/wrvalidators.py +++ b/wrpylib/wrvalidators.py @@ -3,47 +3,94 @@ # $Id$ # $HeadURL$ """ -A converter is a Python variable (may be a class, class instance or anything else) that has the member -functions from_str and to_str. From string takes a string "from the outside", checks it and returns a Python variable -representing that value in Python. It reports error by raising ValueError. to_str does the opposite, however, it -can assume that the value it has to convert to a string is valid. If it gets an invalid value, the behavior is -undefined. +This module contains functions that convert winterrodeln specific strings (like geographic coordinates) from string +to appropriate python types and the other way round. +Functions that take strings to convert it to python types are called *_from_str(value, [...]) and are supposed to +validate the string. In case of errors, a ValueError (or a subclass thereof) is returned. +Functions that take python types and convert it to Winterrodeln strings are called *_to_str(value, [...]) and can +assume that the value they get is valid. If it is not, the behavior is undefined. +The namedtuple FromToConverter groups corresponding *_from_str and *_to_str converters. """ -import datetime import email.headerregistry import urllib.parse import re -import xml.dom.minidom as minidom -from xml.parsers.expat import ExpatError from collections import OrderedDict, namedtuple import mwparserfromhell -import formencode -import formencode.national + from wrpylib.mwmarkup import template_to_table -# Meta converter types and functions -# ---------------------------------- +# FromToConverter type +# -------------------- +# namedtuple that groups corresponding *_from_str and *_to_str functions. FromToConverter = namedtuple('FromToConverter', ['from_str', 'to_str']) -def opt_from_str(value, from_str, none=None): - return none if value == '' else from_str(value) +# optional converter +# ------------------ + +def opt_from_str(value, from_str, empty=None): + """Makes the converter `from_str` "optional" + by replacing the empty string with a predefined value (default: None).""" + return empty if value == '' else from_str(value) + + +def opt_to_str(value, to_str, empty=None): + return '' if value == empty else to_str(value) + + +# "no" converter +# -------------- + +def no_german_from_str(value, from_str, use_tuple=True, no_value=None): + """Makes it possible to have "Nein" as special value. If use_tuple is True, a tuple is returned. The first + entry of the tuple is False in case the value is "Nein", otherwiese the first value is True. The second value is + no_value in case of the value being "Nein", otherwise it is the result of from_str(value). + If use_tuple is False, no_value is returned in case the value is "Nein", otherwise the result of from_str(value).""" + if value == 'Nein': + return (False, no_value) if use_tuple else no_value + return (True, from_str(value)) if use_tuple else from_str(value) + + +def no_german_to_str(value, to_str, use_tuple=True, no_value=None): + if use_tuple: + if not value[0]: + return 'Nein' + return to_str(value[1]) + else: + if value == no_value: + return 'Nein' + return to_str(value) + + +# "optional"/"no" converter +# ------------------------- + +def opt_no_german_from_str(value, from_str, use_tuple=True, no_value=None, empty=(None, None)): + return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), empty) + +def opt_no_german_to_str(value, to_str, use_tuple=True, no_value=None, empty=(None, None)): + return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), empty) -def opt_to_str(value, to_str, none=None): - return '' if value == none else to_str(value) +# choice converter +# ---------------- def choice_from_str(value, choices): + """Returns the value if it is a member of the choices iterable.""" if value not in choices: raise ValueError('{} is an invalid value') return value +# dict converter +# -------------- + def dictkey_from_str(value, key_str_dict): + """Returns the key of an entry in the key_str_dict if the value of the entry corresponds to the given value.""" try: return dict(list(zip(key_str_dict.values(), key_str_dict.keys())))[value] except KeyError: @@ -57,11 +104,70 @@ def dictkey_to_str(value, key_str_dict): raise ValueError("Invalid value '{}'".format(value)) -# Basic type converter functions -# ------------------------------ +# enum/"list" converter +# --------------------- + +def enum_from_str(value, from_str, separator=';', min_len=0): + """Semicolon separated list of entries with the same "type".""" + values = value.split(separator) + if len(values) == 1 and values[0] == '': + values = [] + if len(values) < min_len: + raise ValueError('at least {} entry/entries have to be in the enumeration'.format(min_len)) + return list(map(from_str, map(str.strip, values))) + + +def enum_to_str(value, to_str, separator='; '): + return separator.join(map(to_str, value)) + + +# value/comment converter +# ----------------------- + +def value_comment_from_str(value, value_from_str, comment_from_str, comment_optional=False): + """Makes it possible to have a mandatory comment in parenthesis at the end of the string.""" + open_brackets = 0 + comment = '' + comment_end_pos = None + for i, char in enumerate(value[::-1]): + if char == ')': + open_brackets += 1 + if open_brackets == 1: + comment_end_pos = i + if len(value[-1-comment_end_pos:].rstrip()) > 1: + raise ValueError('invalid characters after comment') + elif char == '(': + open_brackets -= 1 + if open_brackets == 0: + comment = value[-i:-1-comment_end_pos] + value = value[:-i-1].rstrip() + break + else: + if open_brackets > 0: + raise ValueError('bracket mismatch') + if not comment_optional: + raise ValueError('mandatory comment not found') + return value_from_str(value), comment_from_str(comment) +def value_comment_to_str(value, value_to_str, comment_to_str, comment_optional=False): + left = value_to_str(value[0]) + comment = comment_to_str(value[1]) + if len(comment) > 0 or not comment_optional: + comment = '({})'.format(comment) + if len(left) == 0: + return comment + if len(comment) == 0: + return left + return '{} {}'.format(left, comment) + + +# string converter +# ---------------- + def str_from_str(value): + """Converter that takes any string and returns it as string without validation. + In other words, this function does nothing and just returns its argument.""" return value @@ -69,6 +175,12 @@ def str_to_str(value): return value +def req_str_from_str(value): + if value == '': + raise ValueError('missing required value') + return str_from_str(value) + + def opt_str_from_str(value): return opt_from_str(value, str_from_str) @@ -80,13 +192,31 @@ def opt_str_to_str(value): opt_str_converter = FromToConverter(opt_str_from_str, opt_str_to_str) -def req_str_from_str(value): - if value == '': - raise ValueError('missing required value') - return str_from_str(value) +# optional no or string converter +# ------------------------------- + +def opt_no_or_str_from_str(value): + """ + 'Nein' => (False, None); 'Nur Wochenende' => (True, 'Nur Wochenende'); 'Ja' => (True, 'Ja'); '' => (None, None)""" + return opt_no_german_from_str(value, req_str_from_str) + +def opt_no_or_str_to_str(value): + return opt_no_german_to_str(value, str_to_str) + + +opt_no_or_str_converter = FromToConverter(opt_no_or_str_from_str, opt_no_or_str_to_str) + + +# integer converter +# ----------------- def int_from_str(value, min=None, max=None): + """Converter that takes a string representation of an integer and returns the integer. + :param value: string representation of an integer + :param min: If not None, the integer has to be at least min + :param max: If not None, the integer has to be no more than max + """ value = int(value) if min is not None and value < min: raise ValueError('{} must be >= than {}'.format(value, min)) @@ -119,26 +249,8 @@ def opt_uint_to_str(value): opt_uint_converter = FromToConverter(opt_uint_from_str, opt_uint_to_str) - -# Complex types -# ------------- - -def enum_from_str(value, from_str=req_str_from_str, separator=';', min_len=0): - """Semicolon separated list of entries with the same "type".""" - values = value.split(separator) - if len(values) == 1 and values[0] == '': - values = [] - if len(values) < min_len: - raise ValueError('at least {} entry/entries have to be in the enumeration'.format(min_len)) - return list(map(from_str, map(str.strip, values))) - - -def enum_to_str(value, to_str=opt_str_to_str, separator='; '): - return separator.join(map(to_str, value)) - - -# Specific converter functions -# ---------------------------- +# bool converter +# -------------- BOOL_GERMAN = OrderedDict([(False, 'Nein'), (True, 'Ja')]) @@ -162,6 +274,9 @@ def opt_bool_german_to_str(value): opt_bool_german_converter = FromToConverter(opt_bool_german_from_str, opt_bool_german_to_str) +# tristate converter +# ------------------ + TRISTATE_GERMAN = OrderedDict([(0.0, 'Nein'), (0.5, 'Teilweise'), (1.0, 'Ja')]) @@ -184,498 +299,352 @@ def opt_tristate_german_to_str(value): opt_tristate_german_converter = FromToConverter(opt_tristate_german_from_str, opt_tristate_german_to_str) -LonLat = namedtuple('LonLat', ['lon', 'lat']) - - -lonlat_none = LonLat(None, None) +# tristate with comment converter +# ------------------------------- - -def lonlat_from_str(value): - """Converts a winterrodeln geo string like '47.076207 N 11.453553 E' (being ' N E' - to the LonLat(lon, lat) named tupel.""" - r = re.match('(\d+\.\d+) N (\d+\.\d+) E', value) - if r is None: raise ValueError("Coordinates '{}' have not a format like '47.076207 N 11.453553 E'".format(value)) - return LonLat(float(r.groups()[1]), float(r.groups()[0])) +def opt_tristate_german_comment_from_str(value): + """Ja, Nein or Vielleicht, optionally with comment in parenthesis.""" + return value_comment_from_str(value, opt_tristate_german_from_str, opt_str_from_str, True) -def lonlat_to_str(value): - return '{:.6f} N {:.6f} E'.format(value.lat, value.lon) +def opt_tristate_german_comment_to_str(value): + return value_comment_to_str(value, opt_tristate_german_to_str, opt_str_to_str, True) -def opt_lonlat_from_str(value): - return opt_from_str(value, lonlat_from_str, lonlat_none) +opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str, opt_tristate_german_comment_to_str) -def opt_lonlat_to_str(value): - return opt_to_str(value, lonlat_to_str, lonlat_none) +# url converter +# ------------- +def url_from_str(value): + result = urllib.parse.urlparse(value) + if result.scheme not in ['http', 'https']: + raise ValueError('scheme has to be http or https') + if not result.netloc: + raise ValueError('url does not contain netloc') + return value -opt_lonlat_converter = FromToConverter(opt_lonlat_from_str, opt_lonlat_to_str) +def url_to_str(value): + return value -class MultiGeo(formencode.FancyValidator): - "Formats multiple coordinates, even in multiple lines to [(latitude, longitude, elevation), ...] or [(latitude, longitude, None), ...] tuplets." - - # Valid for input_format - FORMAT_GUESS = 0 # guesses the input format; default for input_format - FORMAT_NONE = -1 # indicates missing formats - - # Valid for input_format and output_format - FORMAT_GEOCACHING = 1 # e.g. "N 47° 13.692 E 011° 25.535" - FORMAT_WINTERRODELN = 2 # e.g. "47.222134 N 11.467211 E" - FORMAT_GMAPPLUGIN = 3 # e.g. "47.232922, 11.452239" - FORMAT_GPX = 4 # e.g. "1090.57" - - input_format = FORMAT_GUESS - output_format = FORMAT_WINTERRODELN - last_input_format = FORMAT_NONE - - def __init__(self, input_format = FORMAT_GUESS, output_format = FORMAT_WINTERRODELN, **keywords): - self.input_format = input_format - self.output_format = output_format - formencode.FancyValidator.__init__(self, if_empty = (None, None, None), **keywords) - - def to_python(self, value, state=None): - self.assert_string(value, state) - input_format = self.input_format - if not input_format in [self.FORMAT_GUESS, self.FORMAT_GEOCACHING, self.FORMAT_WINTERRODELN, self.FORMAT_GMAPPLUGIN, self.FORMAT_GPX]: - raise formencode.Invalid("input_format %d is not recognized" % input_format, value, state) # Shouldn't it be an other type of runtime error? - lines = [line.strip() for line in value.split("\n") if len(line.strip()) > 0] - - result = [] - for line in lines: - if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GEOCACHING: - r = re.match('N ?(\d+)° ?(\d+\.\d+) +E ?(\d+)° ?(\d+\.\d+)', line) - if not r is None: - g = r.groups() - result.append((float(g[0]) + float(g[1])/60, float(g[2]) + float(g[3])/60, None)) - last_input_format = self.FORMAT_WINTERRODELN - continue - - if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_WINTERRODELN: - r = re.match('(\d+\.\d+) N (\d+\.\d+) E', line) - if not r is None: - result.append((float(r.groups()[0]), float(r.groups()[1]), None)) - last_input_format = self.FORMAT_WINTERRODELN - continue - - if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GMAPPLUGIN: - r = re.match('(\d+\.\d+), ?(\d+\.\d+)', line) - if not r is None: - result.append((float(r.groups()[0]), float(r.groups()[1]), None)) - last_input_format = self.FORMAT_GMAPPLUGIN - continue - - if input_format == self.FORMAT_GUESS or input_format == self.FORMAT_GPX: - try: - xml = minidom.parseString(line) - coord = xml.documentElement - lat = float(coord.getAttribute('lat')) - lon = float(coord.getAttribute('lon')) - try: ele = float(coord.childNodes[0].childNodes[0].nodeValue) - except (IndexError, ValueError): ele = None - result.append((lat, lon, ele)) - last_input_format = self.FORMAT_GPX - continue - except (ExpatError, IndexError, ValueError): pass - - raise formencode.Invalid("Coordinates '%s' have no known format" % line, value, state) - - return result - - def from_python(self, value, state=None): - output_format = self.output_format - result = [] - for latitude, longitude, height in value: - if output_format == self.FORMAT_GEOCACHING: - degree = latitude - result.append('N %02d° %02.3f E %03d° %02.3f' % (latitude, latitude % 1 * 60, longitude, longitude % 1 * 60)) - - elif output_format == self.FORMAT_WINTERRODELN: - result.append('%.6f N %.6f E' % (latitude, longitude)) - - elif output_format == self.FORMAT_GMAPPLUGIN: - result.append('%.6f, %.6f' % (latitude, longitude)) - - elif output_format == self.FORMAT_GPX: - if not height is None: result.append('%.2f' % (latitude, longitude, height)) - else: result.append('' % (latitude, longitude)) - - else: - raise formencode.Invalid("output_format %d is not recognized" % output_format, value, state) # Shouldn't it be an other type of runtime error? - - return "\n".join(result) +# webauskunft converter +# --------------------- +def webauskunft_from_str(value): + return opt_no_german_from_str(value, url_from_str) -DIFFICULTY_GERMAN = OrderedDict([(1, 'leicht'), (2, 'mittel'), (3, 'schwer')]) +def webauskunft_to_str(value): + return opt_no_german_to_str(value, url_to_str) -def difficulty_german_from_str(value): - return dictkey_from_str(value, DIFFICULTY_GERMAN) +webauskunft_converter = FromToConverter(webauskunft_from_str, webauskunft_to_str) -def difficulty_german_to_str(value): - return dictkey_to_str(value, DIFFICULTY_GERMAN) +# wikipage converter +# ------------------ -def opt_difficulty_german_from_str(value): - return opt_from_str(value, difficulty_german_from_str) +def wikipage_from_str(value): + """Validates wiki page name like '[[Birgitzer Alm]]'. + The page is not checked for existance. + An empty string is an error. + '[[Birgitzer Alm]]' => '[[Birgitzer Alm]]' + """ + if not value.startswith('[[') or not value.endswith(']]'): + raise ValueError('No valid wiki page name "{}"'.format(value)) + return value -def opt_difficulty_german_to_str(value): - return opt_to_str(value, difficulty_german_to_str) +def wikipage_to_str(value): + return value -opt_difficulty_german_converter = FromToConverter(opt_difficulty_german_from_str, opt_difficulty_german_to_str) +def opt_wikipage_enum_from_str(value): + """Validates a list of wiki pages like '[[Birgitzer Alm]]; [[Kemater Alm]]'. + '[[Birgitzer Alm]]; [[Kemater Alm]]' => ['[[Birgitzer Alm]]', '[[Kemater Alm]]'] + '[[Birgitzer Alm]]' => ['[[Birgitzer Alm]]'] + 'Nein' => [] + '' => None + """ + return opt_no_german_from_str(value, lambda val: enum_from_str(val, wikipage_from_str), False, [], None) -AVALANCHES_GERMAN = OrderedDict([(1, 'kaum'), (2, 'selten'), (3, 'gelegentlich'), (4, 'häufig')]) +def opt_wikipage_enum_to_str(value): + return opt_no_german_to_str(value, lambda val: enum_to_str(val, wikipage_to_str), False, [], None) -def avalanches_german_from_str(value): - return dictkey_from_str(value, AVALANCHES_GERMAN) +opt_wikipage_enum_converter = FromToConverter(opt_wikipage_enum_from_str, opt_wikipage_enum_to_str) -def avalanches_german_to_str(value): - return dictkey_to_str(value, AVALANCHES_GERMAN) +# email converter +# --------------- +def email_from_str(value): + """Takes an email address like 'office@example.com', checks it for correctness and returns it again as string.""" + try: + email.headerregistry.Address(addr_spec=value) + except email.errors.HeaderParseError as e: + raise ValueError('Invalid email address: {}'.format(value), e) + return value -def opt_avalanches_german_from_str(value): - return opt_from_str(value, avalanches_german_from_str) +def email_to_str(value): + return str(value) -def opt_avalanches_german_to_str(value): - return opt_to_str(value, avalanches_german_to_str) +def masked_email_from_str(value, mask='(at)', masked_only=False): + """Converts an email address that is possibly masked. Returns a tuple. The first parameter is the un-masked + email address as string, the second is a boolean telling whether the address was masked.""" + unmasked = value.replace(mask, '@') + was_masked = unmasked != value + if masked_only and not was_masked: + raise ValueError('E-Mail address not masked') + return email_from_str(unmasked), was_masked -opt_avalanches_german_converter = FromToConverter(opt_avalanches_german_from_str, opt_avalanches_german_to_str) +def masked_email_to_str(value, mask='(at)'): + """Value is a tuple. The first entry is the email address, the second one is a boolean telling whether the + email address should be masked.""" + email, do_masking = value + email = email_to_str(email) + if do_masking: + email = email.replace('@', mask) + return email -PUBLIC_TRANSPORT_GERMAN = OrderedDict([(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')]) +def emails_from_str(value): + return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)), False, [], None) -def public_transport_german_from_str(value): - return dictkey_from_str(value, PUBLIC_TRANSPORT_GERMAN) +def emails_to_str(value): + return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)), False, [], None) -def public_transport_german_to_str(value): - return dictkey_to_str(value, PUBLIC_TRANSPORT_GERMAN) +emails_converter = FromToConverter(emails_from_str, email_to_str) -def opt_public_transport_german_from_str(value): - return opt_from_str(value, public_transport_german_from_str) +# phone converter +# --------------- -def opt_public_transport_german_to_str(value): - return opt_to_str(value, public_transport_german_to_str) +def phone_number_from_str(value): + match = re.match(r'\+\d+(-\d+)*$', value) + if match is None: + raise ValueError('invalid format of phone number - use something like +43-699-1234567') + return value -opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str, opt_public_transport_german_to_str) +def phone_number_to_str(value): + return value -def value_comment_from_str(value, value_from_str=str_from_str, comment_from_str=str_from_str, comment_optional=False): - """Makes it possible to have a mandatory comment in parenthesis at the end of the string.""" - open_brackets = 0 - comment = '' - comment_end_pos = None - for i, char in enumerate(value[::-1]): - if char == ')': - open_brackets += 1 - if open_brackets == 1: - comment_end_pos = i - if len(value[-1-comment_end_pos:].rstrip()) > 1: - raise ValueError('invalid characters after comment') - elif char == '(': - open_brackets -= 1 - if open_brackets == 0: - comment = value[-i:-1-comment_end_pos] - value = value[:-i-1].rstrip() - break - else: - if open_brackets > 0: - raise ValueError('bracket mismatch') - if not comment_optional: - raise ValueError('mandatory comment not found') - return value_from_str(value), comment_from_str(comment) +def opt_phone_comment_enum_from_str(value, comment_optional=False): + return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, phone_number_from_str, req_str_from_str, comment_optional)), False, [], None) -def value_comment_to_str(value, value_to_str=str_to_str, comment_to_str=str_to_str, comment_optional=False): - left = value_to_str(value[0]) - comment = comment_to_str(value[1]) - if len(comment) > 0 or not comment_optional: - comment = '({})'.format(comment) - if len(left) == 0: - return comment - if len(comment) == 0: - return left - return '{} {}'.format(left, comment) +def opt_phone_comment_enum_to_str(value, comment_optional=False): + return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, phone_number_to_str, str_to_str, comment_optional)), False, [], None) -def opt_tristate_german_comment_from_str(value): - """Ja, Nein or Vielleicht, optionally with comment in parenthesis.""" - return value_comment_from_str(value, opt_tristate_german_from_str, opt_str_from_str, True) +opt_phone_comment_enum_converter = FromToConverter(opt_phone_comment_enum_from_str, opt_phone_comment_enum_to_str) -def opt_tristate_german_comment_to_str(value): - return value_comment_to_str(value, opt_tristate_german_to_str, opt_str_to_str, True) +opt_phone_comment_opt_enum_converter = FromToConverter(lambda value: opt_phone_comment_enum_from_str(value, True), lambda value: opt_phone_comment_enum_to_str(value, True)) -opt_tristate_german_comment_converter = FromToConverter(opt_tristate_german_comment_from_str, opt_tristate_german_comment_to_str) +# longitude/latitude converter +# ---------------------------- +LonLat = namedtuple('LonLat', ['lon', 'lat']) -def no_german_from_str(value, from_str=req_str_from_str, use_tuple=True, no_value=None): - if value == 'Nein': - return (False, no_value) if use_tuple else no_value - return (True, from_str(value)) if use_tuple else from_str(value) +lonlat_none = LonLat(None, None) -def no_german_to_str(value, to_str=str_to_str, use_tuple=True, no_value=None): - if use_tuple: - if not value[0]: - return 'Nein' - return to_str(value[1]) - else: - if value == no_value: - return 'Nein' - return to_str(value) +def lonlat_from_str(value): + """Converts a winterrodeln geo string like '47.076207 N 11.453553 E' (being ' N E' + to the LonLat(lon, lat) named tupel.""" + r = re.match('(\d+\.\d+) N (\d+\.\d+) E', value) + if r is None: raise ValueError("Coordinates '{}' have not a format like '47.076207 N 11.453553 E'".format(value)) + return LonLat(float(r.groups()[1]), float(r.groups()[0])) -def opt_no_german_from_str(value, from_str=str_from_str, use_tuple=True, no_value=None, none=(None, None)): - return opt_from_str(value, lambda v: no_german_from_str(v, from_str, use_tuple, no_value), none) +def lonlat_to_str(value): + return '{:.6f} N {:.6f} E'.format(value.lat, value.lon) -def opt_no_german_to_str(value, to_str=str_to_str, use_tuple=True, no_value=None, none=(None, None)): - return opt_to_str(value, lambda v: no_german_to_str(v, to_str, use_tuple, no_value), none) +def opt_lonlat_from_str(value): + return opt_from_str(value, lonlat_from_str, lonlat_none) -def night_light_from_str(value): - """'Beleuchtungsanlage' Tristate with optional comment: - '' <=> (None, None) - 'Ja' <=> (1.0, None) - 'Teilweise' <=> (0.5, None) - 'Nein' <=> (0.0, None) - 'Ja (aber schmal)' <=> (1.0, 'aber schmal') - 'Teilweise (oben)' <=> (0.5, 'oben') - 'Nein (aber breit)' <=> (0.0, 'aber breit') - """ - return +def opt_lonlat_to_str(value): + return opt_to_str(value, lonlat_to_str, lonlat_none) -def nightlightdays_from_str(value): - return value_comment_from_str(value, lambda val: opt_from_str(val, lambda v: int_from_str(v, min=0, max=7)), opt_str_from_str, comment_optional=True) +opt_lonlat_converter = FromToConverter(opt_lonlat_from_str, opt_lonlat_to_str) -def nightlightdays_to_str(value): - return value_comment_to_str(value, lambda val: opt_to_str(val, int_to_str), opt_str_to_str, comment_optional=True) +# difficulty converter +# -------------------- -nightlightdays_converter = FromToConverter(nightlightdays_from_str, nightlightdays_to_str) +DIFFICULTY_GERMAN = OrderedDict([(1, 'leicht'), (2, 'mittel'), (3, 'schwer')]) -CACHET_REGEXP = [r'(Tiroler Naturrodelbahn-Gütesiegel) ([12]\d{3}) (leicht|mittel|schwer)$'] +def difficulty_german_from_str(value): + return dictkey_from_str(value, DIFFICULTY_GERMAN) -def single_cachet_german_from_str(value): - for pattern in CACHET_REGEXP: - match = re.match(pattern, value) - if match: - return match.groups() - raise ValueError("'{}' is no valid cachet".format(value)) +def difficulty_german_to_str(value): + return dictkey_to_str(value, DIFFICULTY_GERMAN) -def single_cachet_german_to_str(value): - return ' '.join(value) +def opt_difficulty_german_from_str(value): + return opt_from_str(value, difficulty_german_from_str) -def cachet_german_from_str(value): - """Converts a "Gütesiegel": - '' => None - 'Nein' => [] - 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' => [('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]""" - return opt_no_german_from_str(value, lambda val: enum_from_str(val, single_cachet_german_from_str), False, [], None) +def opt_difficulty_german_to_str(value): + return opt_to_str(value, difficulty_german_to_str) - -def cachet_german_to_str(value): - return opt_no_german_to_str(value, lambda val: enum_to_str(val, single_cachet_german_to_str), False, [], None) +opt_difficulty_german_converter = FromToConverter(opt_difficulty_german_from_str, opt_difficulty_german_to_str) -cachet_german_converter = FromToConverter(cachet_german_from_str, cachet_german_to_str) +# avalanches converter +# -------------------- -def url_from_str(value): - result = urllib.parse.urlparse(value) - if result.scheme not in ['http', 'https']: - raise ValueError('scheme has to be http or https') - if not result.netloc: - raise ValueError('url does not contain netloc') - return value +AVALANCHES_GERMAN = OrderedDict([(1, 'kaum'), (2, 'selten'), (3, 'gelegentlich'), (4, 'häufig')]) -def url_to_str(value): - return value +def avalanches_german_from_str(value): + return dictkey_from_str(value, AVALANCHES_GERMAN) -def webauskunft_from_str(value): - return opt_no_german_from_str(value, url_from_str) +def avalanches_german_to_str(value): + return dictkey_to_str(value, AVALANCHES_GERMAN) -def webauskunft_to_str(value): - return opt_no_german_to_str(value, url_to_str) +def opt_avalanches_german_from_str(value): + return opt_from_str(value, avalanches_german_from_str) -webauskunft_converter = FromToConverter(webauskunft_from_str, webauskunft_to_str) +def opt_avalanches_german_to_str(value): + return opt_to_str(value, avalanches_german_to_str) -class Url(formencode.FancyValidator): - """Validates an URL. In contrast to fromencode.validators.URL, umlauts are allowed.""" - # formencode 1.2.5 to formencode 1.3.0a1 sometimes raise ValueError instead of Invalid exceptions - # https://github.com/formencode/formencode/pull/61 - urlv = formencode.validators.URL() +opt_avalanches_german_converter = FromToConverter(opt_avalanches_german_from_str, opt_avalanches_german_to_str) - def to_python(self, value, state=None): - self.assert_string(value, state) - v = value - v = v.replace('ä', 'a') - v = v.replace('ö', 'o') - v = v.replace('ü', 'u') - v = v.replace('ß', 'ss') - v = self.urlv.to_python(v, state) - return value - - def from_python(self, value, state=None): - return value +# lift converter +# -------------- -def phone_number_from_str(value): - match = re.match(r'\+\d+(-\d+)*$', value) - if match is None: - raise ValueError('invalid format of phone number - use something like +43-699-1234567') - return value +LIFT_GERMAN = ['Sessellift', 'Gondel', 'Linienbus', 'Taxi', 'Sonstige'] -def phone_number_to_str(value): - return value +def lift_german_from_str(value): + """Checks a lift_details property. It is a value comment property with the following + values allowed: + 'Sessellift' + 'Gondel' + 'Linienbus' + 'Taxi' + 'Sonstige' + Alternatively, the value u'Nein' is allowed. + An empty string maps to (None, None). + Examples: + '' <=> None + 'Nein' <=> [] + 'Sessellift <=> [('Sessellift', None)] + 'Gondel (nur bis zur Hälfte)' <=> [('Gondel', 'nur bis zur Hälfte')] + 'Sessellift; Taxi' <=> [('Sessellift', None), ('Taxi', None)] + 'Sessellift (Wochenende); Taxi (6 Euro)' <=> [('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')] + """ + return opt_no_german_from_str(value, lambda value_enum: enum_from_str(value_enum, lambda value_comment: value_comment_from_str(value_comment, lambda v: choice_from_str(v, LIFT_GERMAN), opt_str_from_str, comment_optional=True)), use_tuple=False, no_value=[], empty=None) -def telefonauskunft_from_str(value): - return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, phone_number_from_str, req_str_from_str, False)), False, [], None) +def lift_german_to_str(value): + return opt_no_german_to_str(value, lambda value_enum: enum_to_str(value_enum, lambda value_comment: value_comment_to_str(value_comment, str_to_str, opt_str_to_str, comment_optional=True)), use_tuple=False, no_value=[], empty=None) -def telefonauskunft_to_str(value): - return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, phone_number_to_str, str_to_str)), False, [], None) +lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str) -telefonauskunft_converter = FromToConverter(telefonauskunft_from_str, telefonauskunft_to_str) +# public transport converter +# -------------------------- -def email_from_str(value): - """Takes an email address like 'office@example.com', checks it for correctness and returns it again as string.""" - try: - email.headerregistry.Address(addr_spec=value) - except email.errors.HeaderParseError as e: - raise ValueError('Invalid email address: {}'.format(value), e) - return value +PUBLIC_TRANSPORT_GERMAN = OrderedDict([(1, 'Sehr gut'), (2, 'Gut'), (3, 'Mittelmäßig'), (4, 'Schlecht'), (5, 'Nein'), (6, 'Ja')]) -def email_to_str(value): - return str(value) +def public_transport_german_from_str(value): + return dictkey_from_str(value, PUBLIC_TRANSPORT_GERMAN) -def masked_email_from_str(value, mask='(at)', masked_only=False): - """Converts an email address that is possibly masked. Returns a tuple. The first parameter is the un-masked - email address as string, the second is a boolean telling whether the address was masked.""" - unmasked = value.replace(mask, '@') - was_masked = unmasked != value - if masked_only and not was_masked: - raise ValueError('E-Mail address not masked') - return email_from_str(unmasked), was_masked +def public_transport_german_to_str(value): + return dictkey_to_str(value, PUBLIC_TRANSPORT_GERMAN) -def masked_email_to_str(value, mask='(at)'): - """Value is a tuple. The first entry is the email address, the second one is a boolean telling whether the - email address should be masked.""" - email, do_masking = value - email = email_to_str(email) - if do_masking: - email = email.replace('@', mask) - return email +def opt_public_transport_german_from_str(value): + return opt_from_str(value, public_transport_german_from_str) -def emails_from_str(value): - return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, masked_email_from_str, opt_str_from_str, True)), False, [], None) +def opt_public_transport_german_to_str(value): + return opt_to_str(value, public_transport_german_to_str) -def emails_to_str(value): - return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, masked_email_to_str, opt_str_to_str, True)), False, [], None) +opt_public_transport_german_converter = FromToConverter(opt_public_transport_german_from_str, opt_public_transport_german_to_str) -emails_converter = FromToConverter(emails_from_str, email_to_str) +# cachet converter +# ---------------- +CACHET_REGEXP = [r'(Tiroler Naturrodelbahn-Gütesiegel) ([12]\d{3}) (leicht|mittel|schwer)$'] -def wikipage_from_str(value): - """Validates wiki page name like '[[Birgitzer Alm]]'. - The page is not checked for existance. - An empty string is an error. - '[[Birgitzer Alm]]' => '[[Birgitzer Alm]]' - """ - if not value.startswith('[[') or not value.endswith(']]'): - raise ValueError('No valid wiki page name "{}"'.format(value)) - return value +def single_cachet_german_from_str(value): + for pattern in CACHET_REGEXP: + match = re.match(pattern, value) + if match: + return match.groups() + raise ValueError("'{}' is no valid cachet".format(value)) -def wikipage_to_str(value): - return value +def single_cachet_german_to_str(value): + return ' '.join(value) -def opt_wikipage_enum_from_str(value): - """Validates a list of wiki pages like '[[Birgitzer Alm]]; [[Kemater Alm]]'. - '[[Birgitzer Alm]]; [[Kemater Alm]]' => ['[[Birgitzer Alm]]', '[[Kemater Alm]]'] - '[[Birgitzer Alm]]' => ['[[Birgitzer Alm]]'] - 'Nein' => [] - '' => None - """ - return opt_no_german_from_str(value, lambda val: enum_from_str(val, wikipage_from_str), False, [], None) +def cachet_german_from_str(value): + """Converts a "Gütesiegel": + '' => None + 'Nein' => [] + 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' => [('Tiroler Naturrodelbahn-Gütesiegel', '2009', 'mittel')]""" + return opt_no_german_from_str(value, lambda val: enum_from_str(val, single_cachet_german_from_str), False, [], None) -def opt_wikipage_enum_to_str(value): - return opt_no_german_to_str(value, lambda val: enum_to_str(val, wikipage_to_str), False, [], None) +def cachet_german_to_str(value): + return opt_no_german_to_str(value, lambda val: enum_to_str(val, single_cachet_german_to_str), False, [], None) -opt_wikipage_enum_converter = FromToConverter(opt_wikipage_enum_from_str, opt_wikipage_enum_to_str) +cachet_german_converter = FromToConverter(cachet_german_from_str, cachet_german_to_str) -LIFT_GERMAN = ['Sessellift', 'Gondel', 'Linienbus', 'Taxi', 'Sonstige'] +# night light days converter +# -------------------------- -def lift_german_from_str(value): - """Checks a lift_details property. It is a value comment property with the following - values allowed: - 'Sessellift' - 'Gondel' - 'Linienbus' - 'Taxi' - 'Sonstige' - Alternatively, the value u'Nein' is allowed. - An empty string maps to (None, None). +def nightlightdays_from_str(value): + return value_comment_from_str(value, lambda val: opt_from_str(val, lambda v: int_from_str(v, min=0, max=7)), opt_str_from_str, comment_optional=True) - Examples: - '' <=> None - 'Nein' <=> [] - 'Sessellift <=> [('Sessellift', None)] - 'Gondel (nur bis zur Hälfte)' <=> [('Gondel', 'nur bis zur Hälfte')] - 'Sessellift; Taxi' <=> [('Sessellift', None), ('Taxi', None)] - 'Sessellift (Wochenende); Taxi (6 Euro)' <=> [('Sessellift', 'Wochenende'), ('Taxi', '6 Euro')] - """ - return opt_no_german_from_str(value, lambda value_enum: enum_from_str(value_enum, lambda value_comment: value_comment_from_str(value_comment, lambda v: choice_from_str(v, LIFT_GERMAN), opt_str_from_str, comment_optional=True)), use_tuple=False, no_value=[], none=None) +def nightlightdays_to_str(value): + return value_comment_to_str(value, lambda val: opt_to_str(val, int_to_str), opt_str_to_str, comment_optional=True) -def lift_german_to_str(value): - return opt_no_german_to_str(value, lambda value_enum: enum_to_str(value_enum, lambda value_comment: value_comment_to_str(value_comment, str_to_str, opt_str_to_str, comment_optional=True)), use_tuple=False, no_value=[], none=None) +nightlightdays_converter = FromToConverter(nightlightdays_from_str, nightlightdays_to_str) -lift_german_converter = FromToConverter(lift_german_from_str, lift_german_to_str) +# string with optional comment enum/list converter +# ------------------------------------------------ def opt_str_opt_comment_enum_from_str(value): """The value can be an empty string, 'Nein' or a semicolon-separated list of strings with optional comments. @@ -685,30 +654,21 @@ def opt_str_opt_comment_enum_from_str(value): return opt_no_german_from_str(value, lambda val: enum_from_str(val, lambda v: value_comment_from_str(v, req_str_from_str, opt_str_from_str, True)), False, [], None) -def sledrental_to_str(value): +def opt_str_opt_comment_enum_to_str(value): return opt_no_german_to_str(value, lambda val: enum_to_str(val, lambda v: value_comment_to_str(v, str_to_str, opt_str_to_str, True)), False, [], None) -opt_str_opt_comment_enum_converter = FromToConverter(opt_str_opt_comment_enum_from_str, sledrental_to_str) +opt_str_opt_comment_enum_converter = FromToConverter(opt_str_opt_comment_enum_from_str, opt_str_opt_comment_enum_to_str) -def opt_no_or_str_from_str(value): - """Übernachtung. 'Nein' => (False, None); 'Nur Wochenende' => (True, 'Nur Wochenende'); 'Ja' => (True, 'Ja'); '' => (None, None)""" - return opt_no_german_from_str(value) - - -def opt_no_or_str_to_str(value): - return opt_no_german_to_str(value) - - -opt_no_or_str_converter = FromToConverter(opt_no_or_str_from_str, opt_no_or_str_to_str) - +# wikibox converter +# ----------------- class ValueErrorList(ValueError): pass -def box_from_template(template, name, converter_dict): +def wikibox_from_template(template, name, converter_dict): if template.name.strip() != name: raise ValueError('Box name has to be "{}"'.format(name)) result = OrderedDict() @@ -730,7 +690,7 @@ def box_from_template(template, name, converter_dict): return result -def box_to_template(value, name, converter_dict): +def wikibox_to_template(value, name, converter_dict): template = mwparserfromhell.nodes.template.Template(name) for key, converter in converter_dict.items(): template.add(key, converter.to_str(value[key])) @@ -747,15 +707,18 @@ def template_from_str(value, name): return template_list[0] -def box_from_str(value, name, converter_dict): +def wikibox_from_str(value, name, converter_dict): template = template_from_str(value, name) - return box_from_template(template, name, converter_dict) + return wikibox_from_template(template, name, converter_dict) -def box_to_str(value, name, converter_dict): - return str(box_to_template(value, name, converter_dict)) +def wikibox_to_str(value, name, converter_dict): + return str(wikibox_to_template(value, name, converter_dict)) +# Rodelbahnbox converter +# ---------------------- + RODELBAHNBOX_TEMPLATE_NAME = 'Rodelbahnbox' @@ -779,7 +742,7 @@ RODELBAHNBOX_DICT = OrderedDict([ ('Rodelverleih', opt_str_opt_comment_enum_converter), # 'Talstation Serlesbahnan' ('Gütesiegel', cachet_german_converter), # 'Tiroler Naturrodelbahn-Gütesiegel 2009 mittel' ('Webauskunft', webauskunft_converter), # 'http://www.nösslachhütte.at/page9.php' - ('Telefonauskunft', telefonauskunft_converter), # '+43-664-5487520 (Mitterer Alm)' + ('Telefonauskunft', opt_phone_comment_enum_converter), # '+43-664-5487520 (Mitterer Alm)' ('Bild', opt_str_converter), ('In Übersichtskarte', opt_bool_german_converter), ('Forumid', opt_uint_converter) @@ -787,15 +750,15 @@ RODELBAHNBOX_DICT = OrderedDict([ def rodelbahnbox_from_template(template): - return box_from_template(template, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) + return wikibox_from_template(template, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_to_template(value): - return box_to_template(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) + return wikibox_to_template(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_from_str(value): - return box_from_str(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) + return wikibox_from_str(value, RODELBAHNBOX_TEMPLATE_NAME, RODELBAHNBOX_DICT) def rodelbahnbox_to_str(value): @@ -804,6 +767,9 @@ def rodelbahnbox_to_str(value): return str(template) +# Gasthausbox converter +# --------------------- + GASTHAUSBOX_TEMPLATE_NAME = 'Gasthausbox' @@ -818,11 +784,14 @@ GASTHAUSBOX_DICT = OrderedDict([ ('Handyempfang', opt_str_opt_comment_enum_converter), ('Homepage', webauskunft_converter), ('E-Mail', emails_converter), - ('Telefon', None), # PhoneCommentListNeinLoopNone(comments_are_optional=True)), + ('Telefon', opt_phone_comment_opt_enum_converter), ('Bild', opt_str_converter), ('Rodelbahnen', opt_wikipage_enum_converter)]) +# Helper function to make page title pretty +# ----------------------------------------- + def sledrun_page_title_to_pretty_url(page_title): """Converts a page_title from the page_title column of wrsledruncache to name_url. name_url is not used by MediaWiki but by new applications like wrweb.""" -- 2.39.5