1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
|
'''
testcode2.util
--------------
Utility functions.
:copyright: (c) 2012 James Spencer.
:license: modified BSD; see LICENSE for more details.
'''
import os.path
import re
import sys
import testcode2.compatibility as compat
import testcode2.exceptions as exceptions
def testcode_filename(stem, file_id, inp, args):
'''Construct filename in testcode format.'''
filename = '%s.%s' % (stem, file_id)
if inp:
filename = '%s.inp=%s' % (filename, inp)
if args:
filename = '%s.args=%s' % (filename, args)
filename = filename.replace(' ','_')
filename = filename.replace('/', '_')
return filename
def testcode_file_id(filename, stem):
'''Extract the file_id from a filename in the testcode format.'''
filename = os.path.basename(filename)
file_id = filename.replace('%s.' % (stem), '')
file_id = re.sub(r'\.inp=.*', '', file_id)
file_id = re.sub(r'\.args=.*', '', file_id)
return file_id
def try_floatify(val):
'''Convert val to a float if possible.'''
try:
return float(val)
except ValueError:
return val
def extract_tagged_data(data_tag, filename):
'''Extract data from lines marked by the data_tag in filename.'''
if not os.path.exists(filename):
err = 'Cannot extract data: file %s does not exist.' % (filename)
raise exceptions.AnalysisError(err)
data_file = open(filename)
# Data tag is the first non-space character in the line.
# e.g. extract data from lines:
# data_tag Energy: 1.256743 a.u.
data_tag_regex = re.compile('^ *%s' % (re.escape(data_tag)))
data = {}
for line in data_file.readlines():
if data_tag_regex.match(line):
# This is a line containing info to be tested.
words = line.split()
key = []
# name of data is string after the data_tag and preceeding the
# (numerical) data. only use the first number in the line, with
# the key taken from all proceeding information.
for word in words[1:]:
val = try_floatify(word)
if val != word:
break
else:
key.append(word)
if key[-1] in ("=",':'):
key.pop()
key = '_'.join(key)
if key[-1] in ("=",':'):
key = key[:-1]
if not key:
key = 'data'
if key in data:
data[key].append(val)
else:
data[key] = [val]
# We shouldn't change the data from this point: convert entries to tuples.
for (key, val) in list(data.items()):
data[key] = tuple(val)
return data
def dict_table_string(table_string):
'''Read a data table from a string into a dictionary.
The first row and any subsequent rows containing no numbers are assumed to form
headers of a subtable, and so form the keys for the subsequent subtable.
Values, where possible, are converted to floats.
e.g. a b c a -> {'a':(1,4,7,8), 'b':(2,5), 'c':(3,6)}
1 2 3 7
4 5 6 8
and
a b c -> {'a':(1,4,7), 'b':(2,5,8), 'c':(3,6), 'd':(9), 'e':(6)}
1 2 3
4 5 6
a b d e
7 8 9 6
'''
data = [i.split() for i in table_string.splitlines()]
# Convert to numbers where appropriate
data = [[try_floatify(val) for val in dline] for dline in data]
data_dict = {}
head = []
for dline in data:
# Test if all items are strings; if so start a new subtable.
# We actually test if all items are not floats, as python 3 can return
# a bytes variable from subprocess whereas (e.g.) python 2.4 returns a
# str. Testing for this is problematic as the bytes type does not
# exist in python 2.4. Fortunately we have converted all items to
# floats if possible, so can just test for the inverse condition...
if compat.compat_all(type(val) is not float for val in dline):
# header of new subtable
head = dline
for val in head:
if val not in data_dict:
data_dict[val] = []
else:
if len(dline) > len(head):
err = 'Table missing column heading(s):\n%s' % (table_string)
raise exceptions.AnalysisError(err)
for (ind, val) in enumerate(dline):
# Add data to appropriate key.
# Note that this handles the case where the same column heading
# occurs multiple times in the same subtable and does not
# overwrite the previous column with the same heading.
data_dict[head[ind]].append(val)
# We shouldn't change the data from this point: convert entries to tuples.
for (key, val) in list(data_dict.items()):
data_dict[key] = tuple(val)
return data_dict
def wrap_list_strings(word_list, width):
'''Create a list of strings of a given width from a list of words.
This is, to some extent, a version of textwrap.wrap but without the 'feature'
of removing additional whitespace.'''
wrapped_strings = []
clen = 0
cstring = []
for string in word_list:
if clen + len(string) + len(cstring) <= width:
cstring.append(string)
clen += len(string)
else:
wrapped_strings.append(' '.join(cstring))
cstring = [string]
clen = len(string)
if cstring:
wrapped_strings.append(' '.join(cstring))
return wrapped_strings
def pretty_print_table(labels, dicts):
'''Print data in dictionaries of identical size in a tabular format.'''
# Loop through all elements in order to calculate the field width.
# Create header line as we go.
fmt = dict(_tc_label='%%-%is' % (max(len(str(label)) for label in labels)))
header = []
for key in sorted(dicts[0].keys()):
fmt[key] = len(str(key))
nitems = 1
if type(dicts[0][key]) is tuple or type(dicts[0][key]) is list:
nitems = len(dicts[0][key])
for dval in dicts:
for item in dval[key]:
fmt[key] = max(fmt[key], len(str(item)))
else:
fmt[key] = max(len(str(dval[key])) for dval in dicts)
fmt[key] = max(fmt[key], len(str(key)))
# Finished processing all data items with this key.
# Covert from field width into a format statement.
fmt[key] = '%%-%is' % (fmt[key])
for item in range(nitems):
header.append(fmt[key] % (key))
# Wrap header line and insert key/label at the start of each line.
key = fmt['_tc_label'] % ('')
header = wrap_list_strings(header, 70)
header = ['%s %s' % (key, line_part) for line_part in header]
# Printing without a new line is different in python 2 and python 3, so for
# ease we construct the formatting for the line and then print it.
lines = [ header ]
for (ind, label) in enumerate(labels):
line = [fmt['_tc_label'] % (label)]
line = []
for key in sorted(dicts[ind].keys()):
if type(dicts[ind][key]) is tuple or type(dicts[ind][key]) is list:
for item in range(len(dicts[ind][key])):
line.append(fmt[key] % (dicts[ind][key][item]))
else:
line.append(fmt[key] % (dicts[ind][key]))
# Wrap line and insert key/label at the start of each line.
key = fmt['_tc_label'] % (label)
line = wrap_list_strings(line, 70)
line = ['%s %s' % (key, line_part) for line_part in line]
lines.extend([line])
# Now actually form table. Due to line wrapping we might actually form
# several subtables. As each line has the same number of items (or
# should!), this is quite simple.
table = []
for ind in range(len(lines[0])):
table.append('\n'.join([line[ind] for line in lines]))
table = '\n'.join(table)
return (table or
'No data for %s.' % ('; '.join(label.strip() for label in labels)))
def info_line(path, input_file, args, rundir):
'''Produce a (terse) string describing a test.'''
if rundir:
path = compat.relpath(path, rundir)
info_line = path
if input_file:
info_line += ' - %s' % (input_file)
if args:
info_line += ' (arg(s): %s)' % (args)
info_line += ': '
return info_line
|