# $Id: qa_helpers.py 081917d30609 2010-03-05 mtnyogi $
# coding=utf-8
#
# Copyright 2008 Bruce Frederiksen
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import types
import re
import sys
class regexp(object):
r'''
>>> m = regexp(ur'(hi)\s*there', u'the msg', u'the prompt')
>>> m
<regexp u'the msg'[the prompt]/(hi)\s*there/>
>>> m.msg
u'the msg'
>>> m.prompt
u'the prompt'
>>> m.match(u'hithere')
u'hi'
'''
def __init__(self, regexp, msg=None, prompt=None):
self.re = re.compile(regexp, re.UNICODE | re.VERBOSE)
self.pattern = regexp
self.msg = msg
self.prompt = prompt
def __repr__(self):
if self.msg:
if self.prompt:
return '<regexp %r[%s]/%s/>' % \
(self.msg, str(self.prompt), self.pattern)
return '<regexp %r/%s/>' % (self.msg, self.pattern)
if self.prompt:
return '<regexp [%s]/%s/>' % (self.prompt, self.pattern)
return '<regexp /%s/>' % self.pattern
def __getstate__(self):
return self.pattern, self.msg, self.prompt
def __setstate__(self, args):
self.pattern, self.msg, self.prompt = args
self.re = re.compile(self.pattern, re.UNICODE | re.VERBOSE)
def match(self, str):
m = self.re.match(str)
if m and m.end() == len(str):
if m.lastindex > 1: return m.groups()
if m.lastindex == 1: return m.group(1)
return str
class qmap(object):
r'''
>>> m = qmap(u'y', True)
>>> m
<qmap True = u'y'>
>>> m.test
u'y'
>>> m.value
True
'''
def __init__(self, test, value):
self.test = test
self.value = value
def __repr__(self):
return "<qmap %s = %s>" % (repr(self.value), repr(self.test))
if sys.version_info[0] < 3:
def urepr(x):
r'''
>>> urepr(44)
u'44'
>>> tuple(urepr('hi\n'))
(u"'", u'h', u'i', u'\\', u'n', u"'")
'''
if isinstance(x, types.StringTypes):
return repr(x.encode('utf-8')).decode('utf-8')
return unicode(x)
else:
urepr = repr
def to_int(str):
r'''
>>> to_int(u' -34')
-34
>>> to_int(u' +43')
43
>>> to_int(u'43x')
Traceback (most recent call last):
...
ValueError: illegal integer: '43x'
'''
try:
return int(str)
except ValueError:
raise ValueError(u"illegal integer: %s" % urepr(str))
def to_float(str):
r'''
>>> str(to_float(u' -34.444'))
'-34.444'
>>> str(to_float(u' +43'))
'43.0'
>>> to_float(u'43.3.3')
Traceback (most recent call last):
...
ValueError: illegal floating point number: '43.3.3'
'''
try:
return float(str)
except ValueError:
raise ValueError(u"illegal floating point number: %s" % urepr(str))
def to_number(str):
r'''
>>> str(to_number(u' -34.444'))
'-34.444'
>>> to_number(u' +43')
43
>>> to_number(u'43.3.3')
Traceback (most recent call last):
...
ValueError: illegal number: '43.3.3'
'''
try:
return to_int(str)
except ValueError:
try:
return to_float(str)
except ValueError:
raise ValueError(u"illegal number: %s" % urepr(str))
def to_tuple(str, conv_fn=None, test=None, separator=','):
r'''
>>> to_tuple(u'1, 2,3', to_int)
(1, 2, 3)
>>> to_tuple(u'1, 2.5, -7e3', to_number)
(1, 2.5, -7000.0)
>>> to_tuple(u'43', to_number)
(43,)
>>> to_tuple(u'1,43.3.3', to_number)
Traceback (most recent call last):
...
ValueError: illegal number: '43.3.3'
'''
def conv_element(elem):
elem = elem.strip()
if conv_fn: elem = conv_fn(elem)
if test: elem = match(elem, test)
return elem
return tuple(conv_element(elem) for elem in str.split(separator))
def msg_for(test, type):
r'''
>>> msg_for(None, int)
>>> msg_for(regexp(u'', u'the msg'), int)
u'the msg'
>>> msg_for(qmap(44, True), int)
u'44'
>>> msg_for(slice(3, 55), int)
u'between 3 and 55'
>>> msg_for(slice(None, 55), int)
u'<= 55'
>>> msg_for(slice(3, None), int)
u'>= 3'
>>> msg_for(slice(None, None), int)
u''
>>> msg_for(slice(3, 55), str)
u'between 3 and 55 characters'
>>> msg_for(slice(None, 55), str)
u'<= 55 characters'
>>> msg_for(slice(3, None), str)
u'>= 3 characters'
>>> msg_for(slice(None, None), str)
u''
>>> msg_for((slice(3, 5), True), str)
u'between 3 and 5 characters or True'
>>> msg_for(True, str)
u'True'
'''
if test is None: return None
if isinstance(test, regexp): return test.msg
if isinstance(test, qmap): return msg_for(test.test, type)
if isinstance(test, slice):
if test.start is None:
if test.stop is not None: ans = u"<= %d" % test.stop
else: ans = u""
elif test.stop is None:
ans = u">= %d" % test.start
else:
ans = u"between %d and %d" % (test.start, test.stop)
if (type == str or type == unicode) and ans: ans += u' characters'
return ans
if isinstance(test, (tuple, list)):
return u' or '.join(filter(None, (msg_for(test_i, type)
for test_i in test)))
return urepr(test)
def match_prompt(test, type, format, default=u''):
r'''
>>> match_prompt(None, int, u' [%s] ')
u''
>>> match_prompt(regexp(u'', u'', u'the prompt'), int, u' [%s] ')
u' [the prompt] '
>>> match_prompt(qmap(44, True), int, u' [%s] ')
u' [44] '
>>> match_prompt(slice(3, 55), int, u' [%s] ')
u' [3-55] '
>>> match_prompt(slice(None, 55), int, u' [%s] ')
u' [max 55] '
>>> match_prompt(slice(3, None), int, u' [%s] ')
u' [min 3] '
>>> match_prompt(slice(None, None), int, u' [%s] ', u'foo')
u'foo'
>>> match_prompt(slice(3, 55), str, u' [%s] ')
u' [len: 3-55] '
>>> match_prompt(slice(None, 55), str, u' [%s] ')
u' [len <= 55] '
>>> match_prompt(slice(3, None), str, u' [%s] ')
u' [len >= 3] '
>>> match_prompt(slice(None, None), str, u' [%s] ')
u''
>>> match_prompt((slice(3, 5), True), str, u' [%s] ')
u' [len: 3-5 or True] '
>>> match_prompt(True, str, u' [%s] ')
u' [True] '
'''
def prompt_body(test, type):
if test is None: return None
if isinstance(test, regexp): return test.prompt
if isinstance(test, qmap): return prompt_body(test.test, type)
if isinstance(test, slice):
if test.start is None:
if test.stop is not None:
if issubclass(type, types.StringTypes):
return u"len <= %d" % test.stop
else:
return u"max %d" % test.stop
else: return u""
elif test.stop is None:
if issubclass(type, types.StringTypes):
return u"len >= %d" % test.start
else:
return u"min %d" % test.start
else:
if issubclass(type, types.StringTypes):
return u"len: %d-%d" % (test.start, test.stop)
else:
return u"%d-%d" % (test.start, test.stop)
if isinstance(test, (tuple, list)):
return u' or '.join(filter(None, (prompt_body(test_i, type)
for test_i in test)))
return urepr(test)
body = prompt_body(test, type)
if body: return format % body
return default
def match(ans, test):
r'''
>>> match(u'foobar', None)
u'foobar'
>>> match(u'hithere', regexp(ur'(hi)\s*there', u'hi there'))
u'hi'
>>> match(u'hi mom', regexp(ur'(hi)\s*there', u'hi there'))
Traceback (most recent call last):
...
ValueError: hi there
>>> match(u'y', qmap(u'y', True))
True
>>> match(2, qmap(slice(3, 5), True))
Traceback (most recent call last):
...
ValueError: between 3 and 5
>>> match(3, slice(3,5))
3
>>> match(2, slice(3,5))
Traceback (most recent call last):
...
ValueError: between 3 and 5
>>> match(2, (slice(3,5), slice(5,10), 2))
2
>>> match(2, 2)
2
'''
if test is None: return ans
if isinstance(test, regexp):
ans = test.match(ans)
if ans is not None: return ans
if isinstance(test, qmap):
match(ans, test.test) # raises ValueError if it doesn't match
return test.value
elif isinstance(test, slice):
if isinstance(ans, types.StringTypes): value = len(ans)
else: value = ans
if (test.start is None or value >= test.start) and \
(test.stop is None or value <= test.stop):
return ans
elif isinstance(test, (tuple, list)):
for test_i in test:
try:
return match(ans, test_i)
except ValueError:
pass
elif test == ans: return ans
raise ValueError(msg_for(test, type(ans)))
|