Merge lp:~logan/ubuntu/trusty/prettytable/0.7.2-2ubuntu1 into lp:ubuntu/trusty/prettytable

Proposed by Logan Rosen
Status: Merged
Merged at revision: 9
Proposed branch: lp:~logan/ubuntu/trusty/prettytable/0.7.2-2ubuntu1
Merge into: lp:ubuntu/trusty/prettytable
Diff against target: 1962 lines (+914/-259)
12 files modified
CHANGELOG (+28/-12)
COPYING (+3/-1)
PKG-INFO (+3/-1)
README (+48/-12)
debian/changelog (+32/-1)
debian/control (+4/-4)
debian/copyright (+3/-3)
debian/rules (+13/-1)
prettytable.egg-info/PKG-INFO (+3/-1)
prettytable.py (+598/-195)
prettytable_test.py (+177/-28)
setup.py (+2/-0)
To merge this branch: bzr merge lp:~logan/ubuntu/trusty/prettytable/0.7.2-2ubuntu1
Reviewer Review Type Date Requested Status
Daniel Holbach (community) Approve
Review via email: mp+195698@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Daniel Holbach (dholbach) wrote :

Thanks. Uploaded.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CHANGELOG'
2--- CHANGELOG 2012-06-06 19:30:29 +0000
3+++ CHANGELOG 2013-11-19 02:16:58 +0000
4@@ -1,15 +1,31 @@
5-########## PrettyTable 0.6.1 - June 03, 2012 ##########
6-
7-* Unicode encoding scheme can now be set by user
8-* __str__ now uses user-specified encoding instead of ASCII. This
9- fixes a bug where tables with non-ASCII characters would print
10- correctly with "print x.get_string()" but not "print x" in 2.x
11- (thanks to Google Code user kevincobain2000 for reporting this
12- bug!)
13-* Fixed an incompatibility with 3.0 and 3.1 (html.escape was new
14- in 3.2)
15-
16-########## PrettyTable 0.6 - April 05, 2012 ##########
17+########## PrettyTable 0.7 - Feb 17, 2013 ###########
18+
19+* Improved Python 2 and 3 compatibility (2.4-3.2).
20+* Improved support for non-Latin characters. Table widths should
21+ now be calculated correctly for tables with e.g. Japanese text.
22+* Table contents can now be read in from a .csv file
23+* Table contents can now be read in from a DB-API compatible cursor
24+* Table contents can now be read in from a string containing a
25+ HTML table (thanks to Christoph Robbert for submitting this patch!)
26+* new valign attribute controls vertical alignment of text when
27+ some cells in a row have multiple lines of text and others don't.
28+ (thanks to Google Code user maartendb for submitting this patch!)
29+* hrules attribute can now be set to HEADER, which draws a rule only
30+ under the header row
31+* new vrules attribute controls drawing of vertical rules and can
32+ be set to FRAME, ALL or NONE
33+* new header_style attribute controls formatting of text in table
34+ headers and can be set to "cap", "title", "upper", "lower" or None
35+* Fixed a simple bug regarding validation of max_width (thanks to
36+ Anthony Toole for pointing out this bug and providing a patch).
37+* Fixed a simple bug regarding initialisation of int_format value
38+ for new tables (thanks to Ingo Schmiegel for pointing out this
39+ bug!)
40+* Fixed a bug regarding some constructor keywords, such as "border",
41+ being ignored (thanks to Google Code user antonio.s.messina for
42+ reporting this bug).
43+
44+########## PrettyTable 0.6 - May 5, 2012 ##########
45
46 * Code is now simultaneously compatible with Python 2 and 3
47 * Replaced all setter methods with managed attributes
48
49=== modified file 'COPYING'
50--- COPYING 2012-06-06 19:30:29 +0000
51+++ COPYING 2013-11-19 02:16:58 +0000
52@@ -1,8 +1,10 @@
53-# Copyright (c) 2009-2012 Luke Maurits <luke@maurits.id.au>
54+# Copyright (c) 2009-2013 Luke Maurits <luke@maurits.id.au>
55 # All rights reserved.
56 # With contributions from:
57 # * Chris Clark
58+# * Christoph Robbert
59 # * Klein Stephane
60+# * "maartendb"
61 #
62 # Redistribution and use in source and binary forms, with or without
63 # modification, are permitted provided that the following conditions are met:
64
65=== modified file 'PKG-INFO'
66--- PKG-INFO 2012-06-06 19:30:29 +0000
67+++ PKG-INFO 2013-11-19 02:16:58 +0000
68@@ -1,6 +1,6 @@
69 Metadata-Version: 1.0
70 Name: prettytable
71-Version: 0.6.1
72+Version: 0.7.2
73 Summary: A simple Python library for easily displaying tabular data in a visually appealing ASCII table format
74 Home-page: http://code.google.com/p/prettytable
75 Author: Luke Maurits
76@@ -9,6 +9,8 @@
77 Description: UNKNOWN
78 Platform: UNKNOWN
79 Classifier: Programming Language :: Python
80+Classifier: Programming Language :: Python :: 2.4
81+Classifier: Programming Language :: Python :: 2.5
82 Classifier: Programming Language :: Python :: 2.6
83 Classifier: Programming Language :: Python :: 2.7
84 Classifier: Programming Language :: Python :: 3
85
86=== modified file 'README'
87--- README 2012-05-06 11:06:13 +0000
88+++ README 2013-11-19 02:16:58 +0000
89@@ -1,4 +1,4 @@
90-TUTORIAL ON HOW TO USE THE PRETTYTABLE 0.6 API
91+TUTORIAL ON HOW TO USE THE PRETTYTABLE 0.6+ API
92
93 *** This tutorial is distributed with PrettyTable and is meant to serve
94 as a "quick start" guide for the lazy or impatient. It is not an
95@@ -13,15 +13,15 @@
96 from prettytable import PrettyTable
97 x = PrettyTable()
98
99-and you want to put some data into it. You have two (sane) options
100+and you want to put some data into it. You have a few options.
101
102 == Row by row ==
103
104-You can add data one row at a time. To do this you need to set the field names
105-first using the `set_field_names` method, and then add the rows one at a time
106+You can add data one row at a time. To do this you can set the field names
107+first using the `field_names` attribute, and then add the rows one at a time
108 using the `add_row` method:
109
110-x.set_field_names(["City name", "Area", "Population", "Annual Rainfall"])
111+x.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
112 x.add_row(["Adelaide",1295, 1158259, 600.5])
113 x.add_row(["Brisbane",5905, 1857594, 1146.4])
114 x.add_row(["Darwin", 112, 120900, 1714.7])
115@@ -54,6 +54,39 @@
116 this way are kind of confusing for other people to read, though, so don't do
117 this unless you have a good reason.
118
119+== Importing data from a CSV file ==
120+
121+If you have your table data in a comma separated values file (.csv), you can
122+read this data into a PrettyTable like this:
123+
124+from prettytable import from_csv
125+fp = open("myfile.csv", "r")
126+mytable = from_csv(fp)
127+fp.close()
128+
129+== Importing data from a HTML string ==
130+
131+If you have a string containing a HTML <table>, you can read this data into a
132+PrettyTable like this:
133+
134+from prettytable import from_html
135+mytable = from_html(html_string)
136+
137+== Importing data from a database cursor ==
138+
139+If you have your table data in a database which you can access using a library
140+which confirms to the Python DB-API (e.g. an SQLite database accessible using
141+the sqlite module), then you can build a PrettyTable using a cursor object,
142+like this:
143+
144+import sqlite3
145+from prettytable import from_db_cursor
146+
147+connection = sqlite3.connect("mydb.db")
148+cursor = connection.cursor()
149+cursor.execute("SELECT field1, field2, field3 FROM my_table")
150+mytable = from_db_cursor(cursor)
151+
152 == Getting data out ==
153
154 There are three ways to get data out of a PrettyTable, in increasing order of
155@@ -99,8 +132,9 @@
156
157 in Python 3.x.
158
159-The old x.printt() method from versions 0.5 and earlier has been removed. To
160-pass options changing the look of the table, use the get_string() method
161+The old x.printt() method from versions 0.5 and earlier has been removed.
162+
163+To pass options changing the look of the table, use the get_string() method
164 documented below:
165
166 print x.get_string()
167@@ -314,9 +348,11 @@
168 or not the first row of the table is a header showing the names of all the
169 fields.
170 * `hrules` - Controls printing of horizontal rules after rows. Allowed
171- values: FRAME, ALL, NONE - note that these are variables defined inside the
172- `prettytable` module so make sure you import them or use `prettytable.FRAME`
173- etc.
174+ values: FRAME, HEADER, ALL, NONE - note that these are variables defined
175+ inside the `prettytable` module so make sure you import them or use
176+ `prettytable.FRAME` etc.
177+ * `vrules` - Controls printing of vertical rules between columns. Allowed
178+ values: FRAME, ALL, NONE.
179 * `int_format` - A string which controls the way integer data is printed.
180 This works like: print "%<int_format>d" % data
181 * `float_format` - A string which controls the way floating point data is
182@@ -390,7 +426,7 @@
183 By default, PrettyTable outputs HTML for "vanilla" tables. The HTML code is
184 quite simple. It looks like this:
185
186-<table border="1">
187+<table>
188 <tr>
189 <th>City name</th>
190 <th>Area</th>
191@@ -437,7 +473,7 @@
192
193 will print:
194
195-<table border="1" name="my_table" class="red_table">
196+<table name="my_table" class="red_table">
197 <tr>
198 <th>City name</th>
199 <th>Area</th>
200
201=== modified file 'debian/changelog'
202--- debian/changelog 2012-11-05 04:00:38 +0000
203+++ debian/changelog 2013-11-19 02:16:58 +0000
204@@ -1,3 +1,34 @@
205+prettytable (0.7.2-2ubuntu1) trusty; urgency=low
206+
207+ * Merge from Debian unstable. Remaining changes:
208+ - Convert to dh_python2:
209+ + debian/control: Removed python-support from Build-Depends-Indep.
210+ + debian/rules: Added --with python2 to complete transition.
211+
212+ -- Logan Rosen <logan@ubuntu.com> Mon, 18 Nov 2013 21:11:55 -0500
213+
214+prettytable (0.7.2-2) unstable; urgency=low
215+
216+ [ Jakub Wilk ]
217+ * Use canonical URIs for Vcs-* fields.
218+
219+ [ Sandro Tosi ]
220+ * debian/control
221+ - bump Standards-Version to 3.9.4 (no changes needed)
222+
223+ -- Sandro Tosi <morph@debian.org> Sun, 29 Sep 2013 15:31:36 +0200
224+
225+prettytable (0.7.2-1) experimental; urgency=low
226+
227+ * New upstream release
228+ * debian/copyright
229+ - update upstream copyright information
230+ - extend packaging copyright years
231+ * debian/{control, rules}
232+ - install and use UTF-8 charset, needed by test suite
233+
234+ -- Sandro Tosi <morph@debian.org> Sun, 07 Apr 2013 23:23:17 +0200
235+
236 prettytable (0.6.1-1ubuntu1) raring; urgency=low
237
238 * Merge from Debian unstable. Remaining changes:
239@@ -5,7 +36,7 @@
240 + debian/control: Removed python-support from Build-Depends-Indep.
241 + debian/rules: Added --with python2 to complete transition.
242
243- -- Logan Rosen <logatronico@gmail.com> Sun, 04 Nov 2012 22:59:44 -0500
244+ -- Logan Rosen <logan@ubuntu.com> Sun, 04 Nov 2012 22:59:44 -0500
245
246 prettytable (0.6.1-1) unstable; urgency=low
247
248
249=== modified file 'debian/control'
250--- debian/control 2012-05-20 10:42:04 +0000
251+++ debian/control 2013-11-19 02:16:58 +0000
252@@ -4,11 +4,11 @@
253 Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
254 XSBC-Original-Maintainer: Debian Python Modules Team <python-modules-team@lists.alioth.debian.org>
255 Uploaders: Sandro Tosi <morph@debian.org>
256-Build-Depends: debhelper (>= 7.0.50~), python-all (>= 2.6.6-3~), python3-all (>= 3.1.3-3), python-setuptools, python3-setuptools
257-Standards-Version: 3.9.3
258+Build-Depends: debhelper (>= 7.0.50~), python-all (>= 2.6.6-3~), python3-all (>= 3.1.3-3), python-setuptools, python3-setuptools, locales
259+Standards-Version: 3.9.4
260 Homepage: http://code.google.com/p/prettytable/
261-Vcs-Svn: svn://svn.debian.org/python-modules/packages/prettytable/trunk/
262-Vcs-Browser: http://svn.debian.org/viewsvn/python-modules/packages/prettytable/trunk/
263+Vcs-Svn: svn://anonscm.debian.org/python-modules/packages/prettytable/trunk/
264+Vcs-Browser: http://anonscm.debian.org/viewvc/python-modules/packages/prettytable/trunk/
265
266 Package: python-prettytable
267 Architecture: all
268
269=== modified file 'debian/copyright'
270--- debian/copyright 2012-05-06 11:06:13 +0000
271+++ debian/copyright 2013-11-19 02:16:58 +0000
272@@ -2,12 +2,12 @@
273 Upstream-Name: prettytable
274 Source: http://code.google.com/p/prettytable/
275
276-Files: * debian/prettytable_test.py
277-Copyright: Copyright (c) 2009, Luke Maurits <luke@maurits.id.au>
278+Files: *
279+Copyright: Copyright (c) 2009-2013, Luke Maurits <luke@maurits.id.au>
280 License: BSD
281
282 Files: debian/*
283-Copyright: Copyright (C) 2009-2012 Sandro Tosi <morph@debian.org>
284+Copyright: Copyright (C) 2009-2013 Sandro Tosi <morph@debian.org>
285 License: BSD
286
287 License: BSD
288
289=== modified file 'debian/rules'
290--- debian/rules 2012-11-05 04:00:38 +0000
291+++ debian/rules 2013-11-19 02:16:58 +0000
292@@ -5,6 +5,12 @@
293 PY2VERS := $(shell pyversions -s)
294 PY3VERS := $(shell py3versions -s)
295
296+# For running tests, we need UTF-8 charset
297+# See #604706 for references about this method
298+LOCALE_PATH := debian/tmpdir/usr/lib/locale
299+LOCALE_NAME := en_US
300+LOCALE_CHARSET := UTF-8
301+
302 %:
303 dh $@ --with python2,python3
304
305@@ -31,13 +37,19 @@
306 $$python setup.py clean -a; \
307 done
308 find . -name \*.pyc -exec rm {} \;
309+ rm -rf $(LOCALE_PATH)
310 dh_clean
311
312 override_dh_auto_test:
313 ifeq ($(filter nocheck,$(DEB_BUILD_OPTIONS)),)
314+ # configure a local charset dir, for testing purposes
315+ mkdir -p $(LOCALE_PATH)
316+ localedef -i $(LOCALE_NAME) -c -f $(LOCALE_CHARSET) \
317+ -A /usr/share/locale/locale.alias --quiet \
318+ $(LOCALE_PATH)/$(LOCALE_NAME).$(LOCALE_CHARSET)
319 set -e ; \
320 for python in $(PY2VERS) $(PY3VERS); do \
321- PYTHONPATH=. $$python setup.py test ; \
322+ PYTHONPATH=. LOCPATH=$(LOCALE_PATH) LC_ALL=$(LOCALE_NAME).$(LOCALE_CHARSET) $$python setup.py test ; \
323 done
324 endif
325
326
327=== modified file 'prettytable.egg-info/PKG-INFO'
328--- prettytable.egg-info/PKG-INFO 2012-06-06 19:30:29 +0000
329+++ prettytable.egg-info/PKG-INFO 2013-11-19 02:16:58 +0000
330@@ -1,6 +1,6 @@
331 Metadata-Version: 1.0
332 Name: prettytable
333-Version: 0.6.1
334+Version: 0.7.2
335 Summary: A simple Python library for easily displaying tabular data in a visually appealing ASCII table format
336 Home-page: http://code.google.com/p/prettytable
337 Author: Luke Maurits
338@@ -9,6 +9,8 @@
339 Description: UNKNOWN
340 Platform: UNKNOWN
341 Classifier: Programming Language :: Python
342+Classifier: Programming Language :: Python :: 2.4
343+Classifier: Programming Language :: Python :: 2.5
344 Classifier: Programming Language :: Python :: 2.6
345 Classifier: Programming Language :: Python :: 2.7
346 Classifier: Programming Language :: Python :: 3
347
348=== modified file 'prettytable.py'
349--- prettytable.py 2012-06-06 19:30:29 +0000
350+++ prettytable.py 2013-11-19 02:16:58 +0000
351@@ -1,6 +1,6 @@
352 #!/usr/bin/env python
353 #
354-# Copyright (c) 2009, Luke Maurits <luke@maurits.id.au>
355+# Copyright (c) 2009-2013, Luke Maurits <luke@maurits.id.au>
356 # All rights reserved.
357 # With contributions from:
358 # * Chris Clark
359@@ -29,17 +29,31 @@
360 # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
361 # POSSIBILITY OF SUCH DAMAGE.
362
363-__version__ = "0.6.1"
364+__version__ = "0.7.2"
365
366-import sys
367 import copy
368+import csv
369 import random
370+import re
371+import sys
372 import textwrap
373+import itertools
374+import unicodedata
375
376 py3k = sys.version_info[0] >= 3
377 if py3k:
378 unicode = str
379 basestring = str
380+ itermap = map
381+ iterzip = zip
382+ uni_chr = chr
383+ from html.parser import HTMLParser
384+else:
385+ itermap = itertools.imap
386+ iterzip = itertools.izip
387+ uni_chr = unichr
388+ from HTMLParser import HTMLParser
389+
390 if py3k and sys.version_info[1] >= 2:
391 from html import escape
392 else:
393@@ -49,6 +63,7 @@
394 FRAME = 0
395 ALL = 1
396 NONE = 2
397+HEADER = 3
398
399 # Table styles
400 DEFAULT = 10
401@@ -56,43 +71,34 @@
402 PLAIN_COLUMNS = 12
403 RANDOM = 20
404
405+_re = re.compile("\033\[[0-9;]*m")
406+
407 def _get_size(text):
408- max_width = 0
409- max_height = 0
410- text = _unicode(text)
411- for line in text.split("\n"):
412- max_height += 1
413- if len(line) > max_width:
414- max_width = len(line)
415-
416- return (max_width, max_height)
417+ lines = text.split("\n")
418+ height = len(lines)
419+ width = max([_str_block_width(line) for line in lines])
420+ return (width, height)
421
422-def _unicode(value, encoding="UTF-8"):
423- if not isinstance(value, basestring):
424- value = str(value)
425- if not isinstance(value, unicode):
426- value = unicode(value, encoding, "replace")
427- return value
428-
429 class PrettyTable(object):
430
431- def __init__(self, field_names=None, encoding="UTF-8", **kwargs):
432+ def __init__(self, field_names=None, **kwargs):
433
434 """Return a new PrettyTable instance
435
436 Arguments:
437
438+ encoding - Unicode encoding scheme used to decode any encoded input
439 field_names - list or tuple of field names
440- encoding - Unicode encoding scheme to use
441 fields - list or tuple of field names to include in displays
442 start - index of first data row to include in output
443 end - index of last data row to include in output PLUS ONE (list slice style)
444- fields - names of fields (columns) to include
445 header - print a header showing field names (True or False)
446+ header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)
447 border - print a border around the table (True or False)
448- hrules - controls printing of horizontal rules after rows. Allowed values: FRAME, ALL, NONE
449- int_format - controls formatting of integer data
450- float_format - controls formatting of floating point data
451+ hrules - controls printing of horizontal rules after rows. Allowed values: FRAME, HEADER, ALL, NONE
452+ vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
453+ int_format - controls formatting of integer data
454+ float_format - controls formatting of floating point data
455 padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
456 left_padding_width - number of spaces on left hand side of column data
457 right_padding_width - number of spaces on right hand side of column data
458@@ -101,57 +107,101 @@
459 junction_char - single character string used to draw line junctions
460 sortby - name of field to sort rows by
461 sort_key - sorting key function, applied to data points before sorting
462+ valign - default valign for each row (None, "t", "m" or "b")
463 reversesort - True or False to sort in descending or ascending order"""
464
465- self.encoding = encoding
466+ self.encoding = kwargs.get("encoding", "UTF-8")
467
468 # Data
469 self._field_names = []
470 self._align = {}
471+ self._valign = {}
472 self._max_width = {}
473 self._rows = []
474 if field_names:
475 self.field_names = field_names
476 else:
477 self._widths = []
478- self._rows = []
479
480 # Options
481- self._options = "start end fields header border sortby reversesort sort_key attributes format hrules".split()
482+ self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split()
483 self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split())
484- self._options.extend("vertical_char horizontal_char junction_char".split())
485+ self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split())
486 for option in self._options:
487 if option in kwargs:
488 self._validate_option(option, kwargs[option])
489 else:
490 kwargs[option] = None
491
492-
493 self._start = kwargs["start"] or 0
494 self._end = kwargs["end"] or None
495 self._fields = kwargs["fields"] or None
496
497- self._header = kwargs["header"] or True
498- self._border = kwargs["border"] or True
499+ if kwargs["header"] in (True, False):
500+ self._header = kwargs["header"]
501+ else:
502+ self._header = True
503+ self._header_style = kwargs["header_style"] or None
504+ if kwargs["border"] in (True, False):
505+ self._border = kwargs["border"]
506+ else:
507+ self._border = True
508 self._hrules = kwargs["hrules"] or FRAME
509+ self._vrules = kwargs["vrules"] or ALL
510
511 self._sortby = kwargs["sortby"] or None
512- self._reversesort = kwargs["reversesort"] or False
513+ if kwargs["reversesort"] in (True, False):
514+ self._reversesort = kwargs["reversesort"]
515+ else:
516+ self._reversesort = False
517 self._sort_key = kwargs["sort_key"] or (lambda x: x)
518
519- self._int_format = kwargs["float_format"] or {}
520+ self._int_format = kwargs["int_format"] or {}
521 self._float_format = kwargs["float_format"] or {}
522 self._padding_width = kwargs["padding_width"] or 1
523 self._left_padding_width = kwargs["left_padding_width"] or None
524 self._right_padding_width = kwargs["right_padding_width"] or None
525
526- self._vertical_char = kwargs["vertical_char"] or "|"
527- self._horizontal_char = kwargs["horizontal_char"] or "-"
528- self._junction_char = kwargs["junction_char"] or "+"
529+ self._vertical_char = kwargs["vertical_char"] or self._unicode("|")
530+ self._horizontal_char = kwargs["horizontal_char"] or self._unicode("-")
531+ self._junction_char = kwargs["junction_char"] or self._unicode("+")
532
533+ if kwargs["print_empty"] in (True, False):
534+ self._print_empty = kwargs["print_empty"]
535+ else:
536+ self._print_empty = True
537 self._format = kwargs["format"] or False
538+ self._xhtml = kwargs["xhtml"] or False
539 self._attributes = kwargs["attributes"] or {}
540
541+ def _unicode(self, value):
542+ if not isinstance(value, basestring):
543+ value = str(value)
544+ if not isinstance(value, unicode):
545+ value = unicode(value, self.encoding, "strict")
546+ return value
547+
548+ def _justify(self, text, width, align):
549+ excess = width - _str_block_width(text)
550+ if align == "l":
551+ return text + excess * " "
552+ elif align == "r":
553+ return excess * " " + text
554+ else:
555+ if excess % 2:
556+ # Uneven padding
557+ # Put more space on right if text is of odd length...
558+ if _str_block_width(text) % 2:
559+ return (excess//2)*" " + text + (excess//2 + 1)*" "
560+ # and more space on left if text is of even length
561+ else:
562+ return (excess//2 + 1)*" " + text + (excess//2)*" "
563+ # Why distribute extra space this way? To match the behaviour of
564+ # the inbuilt str.center() method.
565+ else:
566+ # Equal padding on either side
567+ return (excess//2)*" " + text + (excess//2)*" "
568+
569 def __getattr__(self, name):
570
571 if name == "rowcount":
572@@ -168,20 +218,26 @@
573
574 def __getitem__(self, index):
575
576- newtable = copy.deepcopy(self)
577+ new = PrettyTable()
578+ new.field_names = self.field_names
579+ for attr in self._options:
580+ setattr(new, "_"+attr, getattr(self, "_"+attr))
581+ setattr(new, "_align", getattr(self, "_align"))
582 if isinstance(index, slice):
583- newtable._rows = self._rows[index]
584+ for row in self._rows[index]:
585+ new.add_row(row)
586 elif isinstance(index, int):
587- newtable._rows = [self._rows[index],]
588+ new.add_row(self._rows[index])
589 else:
590 raise Exception("Index %s is invalid, must be an integer or slice" % str(index))
591- return newtable
592+ return new
593
594- def __str__(self):
595- if py3k:
596- return self.get_string()
597- else:
598- return self.get_string().encode(self.encoding)
599+ if py3k:
600+ def __str__(self):
601+ return self.__unicode__()
602+ else:
603+ def __str__(self):
604+ return self.__unicode__().encode(self.encoding)
605
606 def __unicode__(self):
607 return self.get_string()
608@@ -198,7 +254,9 @@
609 # Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings
610
611 def _validate_option(self, option, val):
612- if option in ("start", "end", "padding_width", "left_padding_width", "right_padding_width", "format"):
613+ if option in ("field_names"):
614+ self._validate_field_names(val)
615+ elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"):
616 self._validate_nonnegative_int(option, val)
617 elif option in ("sortby"):
618 self._validate_field_name(option, val)
619@@ -206,10 +264,14 @@
620 self._validate_function(option, val)
621 elif option in ("hrules"):
622 self._validate_hrules(option, val)
623+ elif option in ("vrules"):
624+ self._validate_vrules(option, val)
625 elif option in ("fields"):
626 self._validate_all_field_names(option, val)
627- elif option in ("header", "border", "reversesort"):
628+ elif option in ("header", "border", "reversesort", "xhtml", "print_empty"):
629 self._validate_true_or_false(option, val)
630+ elif option in ("header_style"):
631+ self._validate_header_style(val)
632 elif option in ("int_format"):
633 self._validate_int_format(option, val)
634 elif option in ("float_format"):
635@@ -221,17 +283,47 @@
636 else:
637 raise Exception("Unrecognised option: %s!" % option)
638
639+ def _validate_field_names(self, val):
640+ # Check for appropriate length
641+ if self._field_names:
642+ try:
643+ assert len(val) == len(self._field_names)
644+ except AssertionError:
645+ raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names)))
646+ if self._rows:
647+ try:
648+ assert len(val) == len(self._rows[0])
649+ except AssertionError:
650+ raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0])))
651+ # Check for uniqueness
652+ try:
653+ assert len(val) == len(set(val))
654+ except AssertionError:
655+ raise Exception("Field names must be unique!")
656+
657+ def _validate_header_style(self, val):
658+ try:
659+ assert val in ("cap", "title", "upper", "lower", None)
660+ except AssertionError:
661+ raise Exception("Invalid header style, use cap, title, upper, lower or None!")
662+
663 def _validate_align(self, val):
664 try:
665 assert val in ["l","c","r"]
666 except AssertionError:
667 raise Exception("Alignment %s is invalid, use l, c or r!" % val)
668
669+ def _validate_valign(self, val):
670+ try:
671+ assert val in ["t","m","b",None]
672+ except AssertionError:
673+ raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
674+
675 def _validate_nonnegative_int(self, name, val):
676 try:
677 assert int(val) >= 0
678 except AssertionError:
679- raise Exception("Invalid value for %s: %s!" % (name, _unicode(val)))
680+ raise Exception("Invalid value for %s: %s!" % (name, self._unicode(val)))
681
682 def _validate_true_or_false(self, name, val):
683 try:
684@@ -269,13 +361,19 @@
685
686 def _validate_hrules(self, name, val):
687 try:
688+ assert val in (ALL, FRAME, HEADER, NONE)
689+ except AssertionError:
690+ raise Exception("Invalid value for %s! Must be ALL, FRAME, HEADER or NONE." % name)
691+
692+ def _validate_vrules(self, name, val):
693+ try:
694 assert val in (ALL, FRAME, NONE)
695 except AssertionError:
696- raise Exception("Invalid value for %s! Must be ALL, FRAME or NONE." % name)
697+ raise Exception("Invalid value for %s! Must be ALL, FRAME, or NONE." % name)
698
699 def _validate_field_name(self, name, val):
700 try:
701- assert val in self._field_names
702+ assert (val in self._field_names) or (val is None)
703 except AssertionError:
704 raise Exception("Invalid field name: %s!" % val)
705
706@@ -288,7 +386,7 @@
707
708 def _validate_single_char(self, name, val):
709 try:
710- assert len(_unicode(val)) == 1
711+ assert _str_block_width(val) == 1
712 except AssertionError:
713 raise Exception("Invalid value for %s! Must be a string of length 1." % name)
714
715@@ -310,6 +408,8 @@
716
717 fields - list or tuple of field names"""
718 def _set_field_names(self, val):
719+ val = [self._unicode(x) for x in val]
720+ self._validate_option("field_names", val)
721 if self._field_names:
722 old_names = self._field_names[:]
723 self._field_names = val
724@@ -317,10 +417,20 @@
725 for old_name, new_name in zip(old_names, val):
726 self._align[new_name] = self._align[old_name]
727 for old_name in old_names:
728- self._align.pop(old_name)
729+ if old_name not in self._align:
730+ self._align.pop(old_name)
731 else:
732 for field in self._field_names:
733 self._align[field] = "c"
734+ if self._valign and old_names:
735+ for old_name, new_name in zip(old_names, val):
736+ self._valign[new_name] = self._valign[old_name]
737+ for old_name in old_names:
738+ if old_name not in self._valign:
739+ self._valign.pop(old_name)
740+ else:
741+ for field in self._field_names:
742+ self._valign[field] = "t"
743 field_names = property(_get_field_names, _set_field_names)
744
745 def _get_align(self):
746@@ -331,14 +441,34 @@
747 self._align[field] = val
748 align = property(_get_align, _set_align)
749
750+ def _get_valign(self):
751+ return self._valign
752+ def _set_valign(self, val):
753+ self._validate_valign(val)
754+ for field in self._field_names:
755+ self._valign[field] = val
756+ valign = property(_get_valign, _set_valign)
757+
758 def _get_max_width(self):
759 return self._max_width
760 def _set_max_width(self, val):
761- self._validate_nonnegativeint(val)
762+ self._validate_option("max_width", val)
763 for field in self._field_names:
764 self._max_width[field] = val
765 max_width = property(_get_max_width, _set_max_width)
766
767+ def _get_fields(self):
768+ """List or tuple of field names to include in displays
769+
770+ Arguments:
771+
772+ fields - list or tuple of field names to include in displays"""
773+ return self._fields
774+ def _set_fields(self, val):
775+ self._validate_option("fields", val)
776+ self._fields = val
777+ fields = property(_get_fields, _set_fields)
778+
779 def _get_start(self):
780 """Start index of the range of rows to print
781
782@@ -412,6 +542,18 @@
783 self._header = val
784 header = property(_get_header, _set_header)
785
786+ def _get_header_style(self):
787+ """Controls stylisation applied to field names in header
788+
789+ Arguments:
790+
791+ header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)"""
792+ return self._header_style
793+ def _set_header_style(self, val):
794+ self._validate_header_style(val)
795+ self._header_style = val
796+ header_style = property(_get_header_style, _set_header_style)
797+
798 def _get_border(self):
799 """Controls printing of border around table
800
801@@ -429,13 +571,25 @@
802
803 Arguments:
804
805- hrules - horizontal rules style. Allowed values: FRAME, ALL, NONE"""
806+ hrules - horizontal rules style. Allowed values: FRAME, ALL, HEADER, NONE"""
807 return self._hrules
808 def _set_hrules(self, val):
809 self._validate_option("hrules", val)
810 self._hrules = val
811 hrules = property(_get_hrules, _set_hrules)
812
813+ def _get_vrules(self):
814+ """Controls printing of vertical rules between columns
815+
816+ Arguments:
817+
818+ vrules - vertical rules style. Allowed values: FRAME, ALL, NONE"""
819+ return self._vrules
820+ def _set_vrules(self, val):
821+ self._validate_option("vrules", val)
822+ self._vrules = val
823+ vrules = property(_get_vrules, _set_vrules)
824+
825 def _get_int_format(self):
826 """Controls formatting of integer data
827 Arguments:
828@@ -443,7 +597,7 @@
829 int_format - integer format string"""
830 return self._int_format
831 def _set_int_format(self, val):
832- self._validate_option("int_format", val)
833+# self._validate_option("int_format", val)
834 for field in self._field_names:
835 self._int_format[field] = val
836 int_format = property(_get_int_format, _set_int_format)
837@@ -455,7 +609,7 @@
838 float_format - floating point format string"""
839 return self._float_format
840 def _set_float_format(self, val):
841- self._validate_option("float_format", val)
842+# self._validate_option("float_format", val)
843 for field in self._field_names:
844 self._float_format[field] = val
845 float_format = property(_get_float_format, _set_float_format)
846@@ -504,6 +658,7 @@
847 vertical_char - single character string used to draw vertical lines"""
848 return self._vertical_char
849 def _set_vertical_char(self, val):
850+ val = self._unicode(val)
851 self._validate_option("vertical_char", val)
852 self._vertical_char = val
853 vertical_char = property(_get_vertical_char, _set_vertical_char)
854@@ -516,6 +671,7 @@
855 horizontal_char - single character string used to draw horizontal lines"""
856 return self._horizontal_char
857 def _set_horizontal_char(self, val):
858+ val = self._unicode(val)
859 self._validate_option("horizontal_char", val)
860 self._horizontal_char = val
861 horizontal_char = property(_get_horizontal_char, _set_horizontal_char)
862@@ -528,6 +684,7 @@
863 junction_char - single character string used to draw line junctions"""
864 return self._junction_char
865 def _set_junction_char(self, val):
866+ val = self._unicode(val)
867 self._validate_option("vertical_char", val)
868 self._junction_char = val
869 junction_char = property(_get_junction_char, _set_junction_char)
870@@ -544,6 +701,18 @@
871 self._format = val
872 format = property(_get_format, _set_format)
873
874+ def _get_print_empty(self):
875+ """Controls whether or not empty tables produce a header and frame or just an empty string
876+
877+ Arguments:
878+
879+ print_empty - True or False"""
880+ return self._print_empty
881+ def _set_print_empty(self, val):
882+ self._validate_option("print_empty", val)
883+ self._print_empty = val
884+ print_empty = property(_get_print_empty, _set_print_empty)
885+
886 def _get_attributes(self):
887 """A dictionary of HTML attribute name/value pairs to be included in the <table> tag when printing HTML
888
889@@ -552,7 +721,7 @@
890 attributes - dictionary of attributes"""
891 return self._attributes
892 def _set_attributes(self, val):
893- self.validate_option("attributes", val)
894+ self._validate_option("attributes", val)
895 self._attributes = val
896 attributes = property(_get_attributes, _set_attributes)
897
898@@ -593,6 +762,7 @@
899 self.header = True
900 self.border = True
901 self._hrules = FRAME
902+ self._vrules = ALL
903 self.padding_width = 1
904 self.left_padding_width = 1
905 self.right_padding_width = 1
906@@ -623,7 +793,8 @@
907 # Just for fun!
908 self.header = random.choice((True, False))
909 self.border = random.choice((True, False))
910- self._hrules = random.choice((ALL, FRAME, NONE))
911+ self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
912+ self._vrules = random.choice((ALL, FRAME, NONE))
913 self.left_padding_width = random.randint(0,5)
914 self.right_padding_width = random.randint(0,5)
915 self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
916@@ -645,6 +816,8 @@
917
918 if self._field_names and len(row) != len(self._field_names):
919 raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names)))
920+ if not self._field_names:
921+ self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))]
922 self._rows.append(list(row))
923
924 def del_row(self, row_index):
925@@ -659,7 +832,7 @@
926 raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
927 del self._rows[row_index]
928
929- def add_column(self, fieldname, column, align="c"):
930+ def add_column(self, fieldname, column, align="c", valign="t"):
931
932 """Add a column to the table.
933
934@@ -668,12 +841,15 @@
935 fieldname - name of the field to contain the new column of data
936 column - column of data, should be a list with as many elements as the
937 table has rows
938- align - desired alignment for this column - "l" for left, "c" for centre and "r" for right"""
939+ align - desired alignment for this column - "l" for left, "c" for centre and "r" for right
940+ valign - desired vertical alignment for new columns - "t" for top, "m" for middle and "b" for bottom"""
941
942 if len(self._rows) in (0, len(column)):
943 self._validate_align(align)
944+ self._validate_valign(valign)
945 self._field_names.append(fieldname)
946 self._align[fieldname] = align
947+ self._valign[fieldname] = valign
948 for i in range(0, len(column)):
949 if len(self._rows) < i+1:
950 self._rows.append([])
951@@ -708,10 +884,10 @@
952
953 def _format_value(self, field, value):
954 if isinstance(value, int) and field in self._int_format:
955- value = ("%%%sd" % self._int_format[field]) % value
956+ value = self._unicode(("%%%sd" % self._int_format[field]) % value)
957 elif isinstance(value, float) and field in self._float_format:
958- value = ("%%%sf" % self._float_format[field]) % value
959- return value
960+ value = self._unicode(("%%%sf" % self._float_format[field]) % value)
961+ return self._unicode(value)
962
963 def _compute_widths(self, rows, options):
964 if options["header"]:
965@@ -720,8 +896,11 @@
966 widths = len(self.field_names) * [0]
967 for row in rows:
968 for index, value in enumerate(row):
969- value = self._format_value(self.field_names[index], value)
970- widths[index] = max(widths[index], _get_size(_unicode(value))[0])
971+ fieldname = self.field_names[index]
972+ if fieldname in self.max_width:
973+ widths[index] = max(widths[index], min(_get_size(value)[0], self.max_width[fieldname]))
974+ else:
975+ widths[index] = max(widths[index], _get_size(value)[0])
976 self._widths = widths
977
978 def _get_padding_widths(self, options):
979@@ -743,7 +922,7 @@
980
981 options - dictionary of option settings."""
982
983- # Make a copy of only those rows in the slice range
984+ # Make a copy of only those rows in the slice range
985 rows = copy.deepcopy(self._rows[options["start"]:options["end"]])
986 # Sort if necessary
987 if options["sortby"]:
988@@ -755,7 +934,13 @@
989 # Undecorate
990 rows = [row[1:] for row in rows]
991 return rows
992-
993+
994+ def _format_row(self, row, options):
995+ return [self._format_value(field, value) for (field, value) in zip(self._field_names, row)]
996+
997+ def _format_rows(self, rows, options):
998+ return [self._format_row(row, options) for row in rows]
999+
1000 ##############################
1001 # PLAIN TEXT STRING METHODS #
1002 ##############################
1003@@ -771,9 +956,10 @@
1004 fields - names of fields (columns) to include
1005 header - print a header showing field names (True or False)
1006 border - print a border around the table (True or False)
1007- hrules - controls printing of horizontal rules after rows. Allowed values: FRAME, ALL, NONE
1008- int_format - controls formatting of integer data
1009- float_format - controls formatting of floating point data
1010+ hrules - controls printing of horizontal rules after rows. Allowed values: ALL, FRAME, HEADER, NONE
1011+ vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
1012+ int_format - controls formatting of integer data
1013+ float_format - controls formatting of floating point data
1014 padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
1015 left_padding_width - number of spaces on left hand side of column data
1016 right_padding_width - number of spaces on right hand side of column data
1017@@ -782,55 +968,67 @@
1018 junction_char - single character string used to draw line junctions
1019 sortby - name of field to sort rows by
1020 sort_key - sorting key function, applied to data points before sorting
1021- reversesort - True or False to sort in descending or ascending order"""
1022+ reversesort - True or False to sort in descending or ascending order
1023+ print empty - if True, stringify just the header for an empty table, if False return an empty string """
1024
1025 options = self._get_options(kwargs)
1026
1027- bits = []
1028+ lines = []
1029
1030 # Don't think too hard about an empty table
1031- if self.rowcount == 0:
1032+ # Is this the desired behaviour? Maybe we should still print the header?
1033+ if self.rowcount == 0 and (not options["print_empty"] or not options["border"]):
1034 return ""
1035
1036+ # Get the rows we need to print, taking into account slicing, sorting, etc.
1037 rows = self._get_rows(options)
1038- self._compute_widths(rows, options)
1039-
1040- # Build rows
1041- # (for now, this is done before building headers etc. because rowbits.append
1042- # contains width-adjusting voodoo which has to be done first. This is ugly
1043- # and Wrong and will change soon)
1044- rowbits = []
1045- for row in rows:
1046- rowbits.append(self._stringify_row(row, options))
1047-
1048+
1049+ # Turn all data in all rows into Unicode, formatted as desired
1050+ formatted_rows = self._format_rows(rows, options)
1051+
1052+ # Compute column widths
1053+ self._compute_widths(formatted_rows, options)
1054
1055 # Add header or top of border
1056+ self._hrule = self._stringify_hrule(options)
1057 if options["header"]:
1058- bits.append(self._stringify_header(options))
1059- elif options["border"] and options["hrules"] != NONE:
1060- bits.append(self._hrule)
1061+ lines.append(self._stringify_header(options))
1062+ elif options["border"] and options["hrules"] in (ALL, FRAME):
1063+ lines.append(self._hrule)
1064
1065 # Add rows
1066- bits.extend(rowbits)
1067+ for row in formatted_rows:
1068+ lines.append(self._stringify_row(row, options))
1069
1070 # Add bottom of border
1071- if options["border"] and not options["hrules"]:
1072- bits.append(self._hrule)
1073+ if options["border"] and options["hrules"] == FRAME:
1074+ lines.append(self._hrule)
1075
1076- string = "\n".join(bits)
1077- self._nonunicode = string
1078- return _unicode(string)
1079+ return self._unicode("\n").join(lines)
1080
1081 def _stringify_hrule(self, options):
1082
1083 if not options["border"]:
1084 return ""
1085 lpad, rpad = self._get_padding_widths(options)
1086- bits = [options["junction_char"]]
1087+ if options['vrules'] in (ALL, FRAME):
1088+ bits = [options["junction_char"]]
1089+ else:
1090+ bits = [options["horizontal_char"]]
1091+ # For tables with no data or fieldnames
1092+ if not self._field_names:
1093+ bits.append(options["junction_char"])
1094+ return "".join(bits)
1095 for field, width in zip(self._field_names, self._widths):
1096 if options["fields"] and field not in options["fields"]:
1097 continue
1098 bits.append((width+lpad+rpad)*options["horizontal_char"])
1099+ if options['vrules'] == ALL:
1100+ bits.append(options["junction_char"])
1101+ else:
1102+ bits.append(options["horizontal_char"])
1103+ if options["vrules"] == FRAME:
1104+ bits.pop()
1105 bits.append(options["junction_char"])
1106 return "".join(bits)
1107
1108@@ -839,54 +1037,62 @@
1109 bits = []
1110 lpad, rpad = self._get_padding_widths(options)
1111 if options["border"]:
1112- if options["hrules"] != NONE:
1113+ if options["hrules"] in (ALL, FRAME):
1114 bits.append(self._hrule)
1115 bits.append("\n")
1116+ if options["vrules"] in (ALL, FRAME):
1117+ bits.append(options["vertical_char"])
1118+ else:
1119+ bits.append(" ")
1120+ # For tables with no data or field names
1121+ if not self._field_names:
1122+ if options["vrules"] in (ALL, FRAME):
1123+ bits.append(options["vertical_char"])
1124+ else:
1125+ bits.append(" ")
1126+ for field, width, in zip(self._field_names, self._widths):
1127+ if options["fields"] and field not in options["fields"]:
1128+ continue
1129+ if self._header_style == "cap":
1130+ fieldname = field.capitalize()
1131+ elif self._header_style == "title":
1132+ fieldname = field.title()
1133+ elif self._header_style == "upper":
1134+ fieldname = field.upper()
1135+ elif self._header_style == "lower":
1136+ fieldname = field.lower()
1137+ else:
1138+ fieldname = field
1139+ bits.append(" " * lpad + self._justify(fieldname, width, self._align[field]) + " " * rpad)
1140+ if options["border"]:
1141+ if options["vrules"] == ALL:
1142+ bits.append(options["vertical_char"])
1143+ else:
1144+ bits.append(" ")
1145+ # If vrules is FRAME, then we just appended a space at the end
1146+ # of the last field, when we really want a vertical character
1147+ if options["border"] and options["vrules"] == FRAME:
1148+ bits.pop()
1149 bits.append(options["vertical_char"])
1150- for field, width, in zip(self._field_names, self._widths):
1151- if options["fields"] and field not in options["fields"]:
1152- continue
1153- if self._align[field] == "l":
1154- bits.append(" " * lpad + _unicode(field).ljust(width) + " " * rpad)
1155- elif self._align[field] == "r":
1156- bits.append(" " * lpad + _unicode(field).rjust(width) + " " * rpad)
1157- else:
1158- bits.append(" " * lpad + _unicode(field).center(width) + " " * rpad)
1159- if options["border"]:
1160- bits.append(options["vertical_char"])
1161 if options["border"] and options["hrules"] != NONE:
1162 bits.append("\n")
1163 bits.append(self._hrule)
1164 return "".join(bits)
1165
1166 def _stringify_row(self, row, options):
1167-
1168- for index, value in enumerate(row):
1169- row[index] = self._format_value(self.field_names[index], value)
1170-
1171+
1172 for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths):
1173 # Enforce max widths
1174- max_width = self._max_width.get(field, 0)
1175- lines = _unicode(value).split("\n")
1176+ lines = value.split("\n")
1177 new_lines = []
1178 for line in lines:
1179- if max_width and len(line) > max_width:
1180- line = textwrap.fill(line, max_width)
1181+ if _str_block_width(line) > width:
1182+ line = textwrap.fill(line, width)
1183 new_lines.append(line)
1184 lines = new_lines
1185 value = "\n".join(lines)
1186 row[index] = value
1187
1188- #old_widths = self._widths[:]
1189-
1190- for index, field in enumerate(self._field_names):
1191- namewidth = len(field)
1192- datawidth = min(self._widths[index], self._max_width.get(field, self._widths[index]))
1193- if options["header"]:
1194- self._widths[index] = max(namewidth, datawidth)
1195- else:
1196- self._widths[index] = datawidth
1197-
1198 row_height = 0
1199 for c in row:
1200 h = _get_size(c)[1]
1201@@ -898,31 +1104,43 @@
1202 for y in range(0, row_height):
1203 bits.append([])
1204 if options["border"]:
1205- bits[y].append(self.vertical_char)
1206+ if options["vrules"] in (ALL, FRAME):
1207+ bits[y].append(self.vertical_char)
1208+ else:
1209+ bits[y].append(" ")
1210
1211 for field, value, width, in zip(self._field_names, row, self._widths):
1212
1213- lines = _unicode(value).split("\n")
1214- if len(lines) < row_height:
1215- lines = lines + ([""] * (row_height-len(lines)))
1216+ valign = self._valign[field]
1217+ lines = value.split("\n")
1218+ dHeight = row_height - len(lines)
1219+ if dHeight:
1220+ if valign == "m":
1221+ lines = [""] * int(dHeight / 2) + lines + [""] * (dHeight - int(dHeight / 2))
1222+ elif valign == "b":
1223+ lines = [""] * dHeight + lines
1224+ else:
1225+ lines = lines + [""] * dHeight
1226
1227 y = 0
1228 for l in lines:
1229 if options["fields"] and field not in options["fields"]:
1230 continue
1231
1232- if self._align[field] == "l":
1233- bits[y].append(" " * lpad + _unicode(l).ljust(width) + " " * rpad)
1234- elif self._align[field] == "r":
1235- bits[y].append(" " * lpad + _unicode(l).rjust(width) + " " * rpad)
1236- else:
1237- bits[y].append(" " * lpad + _unicode(l).center(width) + " " * rpad)
1238+ bits[y].append(" " * lpad + self._justify(l, width, self._align[field]) + " " * rpad)
1239 if options["border"]:
1240- bits[y].append(self.vertical_char)
1241-
1242+ if options["vrules"] == ALL:
1243+ bits[y].append(self.vertical_char)
1244+ else:
1245+ bits[y].append(" ")
1246 y += 1
1247
1248- self._hrule = self._stringify_hrule(options)
1249+ # If vrules is FRAME, then we just appended a space at the end
1250+ # of the last field, when we really want a vertical character
1251+ for y in range(0, row_height):
1252+ if options["border"] and options["vrules"] == FRAME:
1253+ bits[y].pop()
1254+ bits[y].append(options["vertical_char"])
1255
1256 if options["border"] and options["hrules"]== ALL:
1257 bits[row_height-1].append("\n")
1258@@ -931,8 +1149,6 @@
1259 for y in range(0, row_height):
1260 bits[y] = "".join(bits[y])
1261
1262- #self._widths = old_widths
1263-
1264 return "\n".join(bits)
1265
1266 ##############################
1267@@ -950,15 +1166,17 @@
1268 fields - names of fields (columns) to include
1269 header - print a header showing field names (True or False)
1270 border - print a border around the table (True or False)
1271- hrules - controls printing of horizontal rules after rows. Allowed values: FRAME, ALL, NONE
1272- int_format - controls formatting of integer data
1273- float_format - controls formatting of floating point data
1274+ hrules - controls printing of horizontal rules after rows. Allowed values: ALL, FRAME, HEADER, NONE
1275+ vrules - controls printing of vertical rules between columns. Allowed values: FRAME, ALL, NONE
1276+ int_format - controls formatting of integer data
1277+ float_format - controls formatting of floating point data
1278 padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
1279 left_padding_width - number of spaces on left hand side of column data
1280 right_padding_width - number of spaces on right hand side of column data
1281 sortby - name of field to sort rows by
1282 sort_key - sorting key function, applied to data points before sorting
1283- attributes - dictionary of name/value pairs to include as HTML attributes in the <table> tag"""
1284+ attributes - dictionary of name/value pairs to include as HTML attributes in the <table> tag
1285+ xhtml - print <br/> tags if True, <br> tags if false"""
1286
1287 options = self._get_options(kwargs)
1288
1289@@ -967,97 +1185,282 @@
1290 else:
1291 string = self._get_simple_html_string(options)
1292
1293- self._nonunicode = string
1294- return _unicode(string)
1295+ return string
1296
1297 def _get_simple_html_string(self, options):
1298
1299- bits = []
1300- # Slow but works
1301- table_tag = '<table'
1302- if options["border"]:
1303- table_tag += ' border="1"'
1304+ lines = []
1305+ if options["xhtml"]:
1306+ linebreak = "<br/>"
1307+ else:
1308+ linebreak = "<br>"
1309+
1310+ open_tag = []
1311+ open_tag.append("<table")
1312 if options["attributes"]:
1313 for attr_name in options["attributes"]:
1314- table_tag += ' %s="%s"' % (attr_name, options["attributes"][attr_name])
1315- table_tag += '>'
1316- bits.append(table_tag)
1317+ open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
1318+ open_tag.append(">")
1319+ lines.append("".join(open_tag))
1320
1321 # Headers
1322 if options["header"]:
1323- bits.append(" <tr>")
1324+ lines.append(" <tr>")
1325 for field in self._field_names:
1326 if options["fields"] and field not in options["fields"]:
1327 continue
1328- bits.append(" <th>%s</th>" % escape(_unicode(field)).replace("\n", "<br />"))
1329- bits.append(" </tr>")
1330+ lines.append(" <th>%s</th>" % escape(field).replace("\n", linebreak))
1331+ lines.append(" </tr>")
1332
1333 # Data
1334 rows = self._get_rows(options)
1335- for row in rows:
1336- bits.append(" <tr>")
1337+ formatted_rows = self._format_rows(rows, options)
1338+ for row in formatted_rows:
1339+ lines.append(" <tr>")
1340 for field, datum in zip(self._field_names, row):
1341 if options["fields"] and field not in options["fields"]:
1342 continue
1343- bits.append(" <td>%s</td>" % escape(_unicode(datum)).replace("\n", "<br />"))
1344- bits.append(" </tr>")
1345-
1346- bits.append("</table>")
1347- string = "\n".join(bits)
1348-
1349- self._nonunicode = string
1350- return _unicode(string)
1351+ lines.append(" <td>%s</td>" % escape(datum).replace("\n", linebreak))
1352+ lines.append(" </tr>")
1353+
1354+ lines.append("</table>")
1355+
1356+ return self._unicode("\n").join(lines)
1357
1358 def _get_formatted_html_string(self, options):
1359
1360- bits = []
1361+ lines = []
1362 lpad, rpad = self._get_padding_widths(options)
1363- # Slow but works
1364- table_tag = '<table'
1365+ if options["xhtml"]:
1366+ linebreak = "<br/>"
1367+ else:
1368+ linebreak = "<br>"
1369+
1370+ open_tag = []
1371+ open_tag.append("<table")
1372 if options["border"]:
1373- table_tag += ' border="1"'
1374- if options["hrules"] == NONE:
1375- table_tag += ' frame="vsides" rules="cols"'
1376+ if options["hrules"] == ALL and options["vrules"] == ALL:
1377+ open_tag.append(" frame=\"box\" rules=\"all\"")
1378+ elif options["hrules"] == FRAME and options["vrules"] == FRAME:
1379+ open_tag.append(" frame=\"box\"")
1380+ elif options["hrules"] == FRAME and options["vrules"] == ALL:
1381+ open_tag.append(" frame=\"box\" rules=\"cols\"")
1382+ elif options["hrules"] == FRAME:
1383+ open_tag.append(" frame=\"hsides\"")
1384+ elif options["hrules"] == ALL:
1385+ open_tag.append(" frame=\"hsides\" rules=\"rows\"")
1386+ elif options["vrules"] == FRAME:
1387+ open_tag.append(" frame=\"vsides\"")
1388+ elif options["vrules"] == ALL:
1389+ open_tag.append(" frame=\"vsides\" rules=\"cols\"")
1390 if options["attributes"]:
1391 for attr_name in options["attributes"]:
1392- table_tag += ' %s="%s"' % (attr_name, options["attributes"][attr_name])
1393- table_tag += '>'
1394- bits.append(table_tag)
1395+ open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
1396+ open_tag.append(">")
1397+ lines.append("".join(open_tag))
1398+
1399 # Headers
1400 if options["header"]:
1401- bits.append(" <tr>")
1402+ lines.append(" <tr>")
1403 for field in self._field_names:
1404 if options["fields"] and field not in options["fields"]:
1405 continue
1406- bits.append(" <th style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</th>" % (lpad, rpad, escape(_unicode(field)).replace("\n", "<br />")))
1407- bits.append(" </tr>")
1408+ lines.append(" <th style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</th>" % (lpad, rpad, escape(field).replace("\n", linebreak)))
1409+ lines.append(" </tr>")
1410+
1411 # Data
1412 rows = self._get_rows(options)
1413- for row in self._rows:
1414- bits.append(" <tr>")
1415- for field, datum in zip(self._field_names, row):
1416+ formatted_rows = self._format_rows(rows, options)
1417+ aligns = []
1418+ valigns = []
1419+ for field in self._field_names:
1420+ aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]])
1421+ valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]])
1422+ for row in formatted_rows:
1423+ lines.append(" <tr>")
1424+ for field, datum, align, valign in zip(self._field_names, row, aligns, valigns):
1425 if options["fields"] and field not in options["fields"]:
1426 continue
1427- if self._align[field] == "l":
1428- bits.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: left\">%s</td>" % (lpad, rpad, escape(_unicode(datum)).replace("\n", "<br />")))
1429- elif self._align[field] == "r":
1430- bits.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: right\">%s</td>" % (lpad, rpad, escape(_unicode(datum)).replace("\n", "<br />")))
1431- else:
1432- bits.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</td>" % (lpad, rpad, escape(_unicode(datum)).replace("\n", "<br />")))
1433- bits.append(" </tr>")
1434- bits.append("</table>")
1435- string = "\n".join(bits)
1436-
1437- self._nonunicode = string
1438- return _unicode(string)
1439+ lines.append(" <td style=\"padding-left: %dem; padding-right: %dem; text-align: %s; vertical-align: %s\">%s</td>" % (lpad, rpad, align, valign, escape(datum).replace("\n", linebreak)))
1440+ lines.append(" </tr>")
1441+ lines.append("</table>")
1442+
1443+ return self._unicode("\n").join(lines)
1444+
1445+##############################
1446+# UNICODE WIDTH FUNCTIONS #
1447+##############################
1448+
1449+def _char_block_width(char):
1450+ # Basic Latin, which is probably the most common case
1451+ #if char in xrange(0x0021, 0x007e):
1452+ #if char >= 0x0021 and char <= 0x007e:
1453+ if 0x0021 <= char <= 0x007e:
1454+ return 1
1455+ # Chinese, Japanese, Korean (common)
1456+ if 0x4e00 <= char <= 0x9fff:
1457+ return 2
1458+ # Hangul
1459+ if 0xac00 <= char <= 0xd7af:
1460+ return 2
1461+ # Combining?
1462+ if unicodedata.combining(uni_chr(char)):
1463+ return 0
1464+ # Hiragana and Katakana
1465+ if 0x3040 <= char <= 0x309f or 0x30a0 <= char <= 0x30ff:
1466+ return 2
1467+ # Full-width Latin characters
1468+ if 0xff01 <= char <= 0xff60:
1469+ return 2
1470+ # CJK punctuation
1471+ if 0x3000 <= char <= 0x303e:
1472+ return 2
1473+ # Backspace and delete
1474+ if char in (0x0008, 0x007f):
1475+ return -1
1476+ # Other control characters
1477+ elif char in (0x0000, 0x001f):
1478+ return 0
1479+ # Take a guess
1480+ return 1
1481+
1482+def _str_block_width(val):
1483+
1484+ return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val))))
1485+
1486+##############################
1487+# TABLE FACTORIES #
1488+##############################
1489+
1490+def from_csv(fp, field_names = None, **kwargs):
1491+
1492+ dialect = csv.Sniffer().sniff(fp.read(1024))
1493+ fp.seek(0)
1494+ reader = csv.reader(fp, dialect)
1495+
1496+ table = PrettyTable(**kwargs)
1497+ if field_names:
1498+ table.field_names = field_names
1499+ else:
1500+ if py3k:
1501+ table.field_names = [x.strip() for x in next(reader)]
1502+ else:
1503+ table.field_names = [x.strip() for x in reader.next()]
1504+
1505+ for row in reader:
1506+ table.add_row([x.strip() for x in row])
1507+
1508+ return table
1509+
1510+def from_db_cursor(cursor, **kwargs):
1511+
1512+ if cursor.description:
1513+ table = PrettyTable(**kwargs)
1514+ table.field_names = [col[0] for col in cursor.description]
1515+ for row in cursor.fetchall():
1516+ table.add_row(row)
1517+ return table
1518+
1519+class TableHandler(HTMLParser):
1520+
1521+ def __init__(self, **kwargs):
1522+ HTMLParser.__init__(self)
1523+ self.kwargs = kwargs
1524+ self.tables = []
1525+ self.last_row = []
1526+ self.rows = []
1527+ self.max_row_width = 0
1528+ self.active = None
1529+ self.last_content = ""
1530+ self.is_last_row_header = False
1531+
1532+ def handle_starttag(self,tag, attrs):
1533+ self.active = tag
1534+ if tag == "th":
1535+ self.is_last_row_header = True
1536+
1537+ def handle_endtag(self,tag):
1538+ if tag in ["th", "td"]:
1539+ stripped_content = self.last_content.strip()
1540+ self.last_row.append(stripped_content)
1541+ if tag == "tr":
1542+ self.rows.append(
1543+ (self.last_row, self.is_last_row_header))
1544+ self.max_row_width = max(self.max_row_width, len(self.last_row))
1545+ self.last_row = []
1546+ self.is_last_row_header = False
1547+ if tag == "table":
1548+ table = self.generate_table(self.rows)
1549+ self.tables.append(table)
1550+ self.rows = []
1551+ self.last_content = " "
1552+ self.active = None
1553+
1554+
1555+ def handle_data(self, data):
1556+ self.last_content += data
1557+
1558+ def generate_table(self, rows):
1559+ """
1560+ Generates from a list of rows a PrettyTable object.
1561+ """
1562+ table = PrettyTable(**self.kwargs)
1563+ for row in self.rows:
1564+ if len(row[0]) < self.max_row_width:
1565+ appends = self.max_row_width - len(row[0])
1566+ for i in range(1,appends):
1567+ row[0].append("-")
1568+
1569+ if row[1] == True:
1570+ self.make_fields_unique(row[0])
1571+ table.field_names = row[0]
1572+ else:
1573+ table.add_row(row[0])
1574+ return table
1575+
1576+ def make_fields_unique(self, fields):
1577+ """
1578+ iterates over the row and make each field unique
1579+ """
1580+ for i in range(0, len(fields)):
1581+ for j in range(i+1, len(fields)):
1582+ if fields[i] == fields[j]:
1583+ fields[j] += "'"
1584+
1585+def from_html(html_code, **kwargs):
1586+ """
1587+ Generates a list of PrettyTables from a string of HTML code. Each <table> in
1588+ the HTML becomes one PrettyTable object.
1589+ """
1590+
1591+ parser = TableHandler(**kwargs)
1592+ parser.feed(html_code)
1593+ return parser.tables
1594+
1595+def from_html_one(html_code, **kwargs):
1596+ """
1597+ Generates a PrettyTables from a string of HTML code which contains only a
1598+ single <table>
1599+ """
1600+
1601+ tables = from_html(html_code, **kwargs)
1602+ try:
1603+ assert len(tables) == 1
1604+ except AssertionError:
1605+ raise Exception("More than one <table> in provided HTML code! Use from_html instead.")
1606+ return tables[0]
1607+
1608+##############################
1609+# MAIN (TEST FUNCTION) #
1610+##############################
1611
1612 def main():
1613
1614 x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
1615 x.sortby = "Population"
1616 x.reversesort = True
1617- x.int_format["Area"] = "04"
1618- x.float_format = "6.1"
1619+ x.int_format["Area"] = "04d"
1620+ x.float_format = "6.1f"
1621 x.align["City name"] = "l" # Left align city names
1622 x.add_row(["Adelaide", 1295, 1158259, 600.5])
1623 x.add_row(["Brisbane", 5905, 1857594, 1146.4])
1624
1625=== modified file 'prettytable_test.py'
1626--- prettytable_test.py 2012-06-06 19:30:29 +0000
1627+++ prettytable_test.py 2013-11-19 02:16:58 +0000
1628@@ -1,8 +1,20 @@
1629-import unittest
1630+# coding=UTF-8
1631+
1632+from prettytable import *
1633+
1634 import sys
1635-sys.path.append("../src/")
1636+py3k = sys.version_info[0] >= 3
1637+try:
1638+ import sqlite3
1639+ _have_sqlite = True
1640+except ImportError:
1641+ _have_sqlite = False
1642+if py3k:
1643+ import io as StringIO
1644+else:
1645+ import StringIO
1646 from math import pi, e, sqrt
1647-from prettytable import *
1648+import unittest
1649
1650 class BuildEquivelanceTest(unittest.TestCase):
1651
1652@@ -98,7 +110,7 @@
1653
1654 class OptionOverrideTests(CityDataTest):
1655
1656- """Make sure all options are properly overwritten by printt."""
1657+ """Make sure all options are properly overwritten by get_string."""
1658
1659 def testBorder(self):
1660 default = self.x.get_string()
1661@@ -121,6 +133,42 @@
1662 override = self.x.get_string(hrules=NONE)
1663 self.assertTrue(default != override)
1664
1665+class OptionAttributeTests(CityDataTest):
1666+
1667+ """Make sure all options which have an attribute interface work as they should.
1668+ Also make sure option settings are copied correctly when a table is cloned by
1669+ slicing."""
1670+
1671+ def testSetForAllColumns(self):
1672+ self.x.field_names = sorted(self.x.field_names)
1673+ self.x.align = "l"
1674+ self.x.max_width = 10
1675+ self.x.start = 2
1676+ self.x.end = 4
1677+ self.x.sortby = "Area"
1678+ self.x.reversesort = True
1679+ self.x.header = True
1680+ self.x.border = False
1681+ self.x.hrule = True
1682+ self.x.int_format = "4"
1683+ self.x.float_format = "2.2"
1684+ self.x.padding_width = 2
1685+ self.x.left_padding_width = 2
1686+ self.x.right_padding_width = 2
1687+ self.x.vertical_char = "!"
1688+ self.x.horizontal_char = "~"
1689+ self.x.junction_char = "*"
1690+ self.x.format = True
1691+ self.x.attributes = {"class" : "prettytable"}
1692+ assert self.x.get_string() == self.x[:].get_string()
1693+
1694+ def testSetForOneColumn(self):
1695+ self.x.align["Rainfall"] = "l"
1696+ self.x.max_width["Name"] = 10
1697+ self.x.int_format["Population"] = "4"
1698+ self.x.float_format["Area"] = "2.2"
1699+ assert self.x.get_string() == self.x[:].get_string()
1700+
1701 class BasicTests(CityDataTest):
1702
1703 """Some very basic tests."""
1704@@ -175,6 +223,25 @@
1705 BasicTests.setUp(self)
1706 self.x.hrules = ALL
1707
1708+class EmptyTableTests(CityDataTest):
1709+
1710+ """Make sure the print_empty option works"""
1711+
1712+ def setUp(self):
1713+ CityDataTest.setUp(self)
1714+ self.y = PrettyTable()
1715+ self.y.field_names = ["City name", "Area", "Population", "Annual Rainfall"]
1716+
1717+ def testPrintEmptyTrue(self):
1718+ assert self.y.get_string(print_empty=True) != ""
1719+ assert self.x.get_string(print_empty=True) != self.y.get_string(print_empty=True)
1720+
1721+ def testPrintEmptyFalse(self):
1722+ assert self.y.get_string(print_empty=False) == ""
1723+ assert self.y.get_string(print_empty=False) != self.x.get_string(print_empty=False)
1724+
1725+ def testInteractionWithBorder(self):
1726+ assert self.y.get_string(border=False, print_empty=True) == ""
1727 class PresetBasicTests(BasicTests):
1728
1729 """Run the basic tests after using set_style"""
1730@@ -188,6 +255,10 @@
1731 def setUp(self):
1732 CityDataTest.setUp(self)
1733
1734+ def testSliceAll(self):
1735+ y = self.x[:]
1736+ assert self.x.get_string() == y.get_string()
1737+
1738 def testSliceFirstTwoRows(self):
1739 y = self.x[0:2]
1740 string = y.get_string()
1741@@ -265,7 +336,7 @@
1742
1743 def setUp(self):
1744 BasicTests.setUp(self)
1745- self.x.float_format = "6.2"
1746+ self.x.float_format = "6.2f"
1747
1748 class FloatFormatTests(unittest.TestCase):
1749
1750@@ -276,12 +347,12 @@
1751 self.x.add_row(["sqrt(2)", sqrt(2)])
1752
1753 def testNoDecimals(self):
1754- self.x.float_format = ".0"
1755+ self.x.float_format = ".0f"
1756 self.x.caching = False
1757 assert "." not in self.x.get_string()
1758
1759 def testRoundTo5DP(self):
1760- self.x.float_format = ".5"
1761+ self.x.float_format = ".5f"
1762 string = self.x.get_string()
1763 assert "3.14159" in string
1764 assert "3.141592" not in string
1765@@ -292,7 +363,7 @@
1766 assert "1.414213" not in string
1767
1768 def testPadWith2Zeroes(self):
1769- self.x.float_format = "06.2"
1770+ self.x.float_format = "06.2f"
1771 string = self.x.get_string()
1772 assert "003.14" in string
1773 assert "002.72" in string
1774@@ -303,7 +374,7 @@
1775 t = PrettyTable(['Field 1', 'Field 2'])
1776 t.add_row(['value 1', 'value2\nsecond line'])
1777 t.add_row(['value 3', 'value4'])
1778- result = t.get_string(hrules=True)
1779+ result = t.get_string(hrules=ALL)
1780 assert result.strip() == """
1781 +---------+-------------+
1782 | Field 1 | Field 2 |
1783@@ -318,7 +389,7 @@
1784 t = PrettyTable(['Field 1', 'Field 2'])
1785 t.add_row(['value 1', 'value2\nsecond line'])
1786 t.add_row(['value 3\n\nother line', 'value4\n\n\nvalue5'])
1787- result = t.get_string(hrules=True)
1788+ result = t.get_string(hrules=ALL)
1789 assert result.strip() == """
1790 +------------+-------------+
1791 | Field 1 | Field 2 |
1792@@ -354,16 +425,16 @@
1793 t = PrettyTable(['Field 1', 'Field 2'])
1794 t.add_row(['value 1', 'value2\nsecond line'])
1795 t.add_row(['value 3', 'value4'])
1796- result = t.get_html_string(hrules=True)
1797+ result = t.get_html_string(hrules=ALL)
1798 assert result.strip() == """
1799-<table border="1">
1800+<table>
1801 <tr>
1802 <th>Field 1</th>
1803 <th>Field 2</th>
1804 </tr>
1805 <tr>
1806 <td>value 1</td>
1807- <td>value2<br />second line</td>
1808+ <td>value2<br>second line</td>
1809 </tr>
1810 <tr>
1811 <td>value 3</td>
1812@@ -373,6 +444,7 @@
1813 """.strip()
1814
1815 class HtmlOutputTests(unittest.TestCase):
1816+
1817 def testHtmlOutput(self):
1818 t = PrettyTable(['Field 1', 'Field 2', 'Field 3'])
1819 t.add_row(['value 1', 'value2', 'value3'])
1820@@ -380,7 +452,7 @@
1821 t.add_row(['value 7', 'value8', 'value9'])
1822 result = t.get_html_string()
1823 assert result.strip() == """
1824-<table border="1">
1825+<table>
1826 <tr>
1827 <th>Field 1</th>
1828 <th>Field 2</th>
1829@@ -411,29 +483,106 @@
1830 t.add_row(['value 7', 'value8', 'value9'])
1831 result = t.get_html_string(format=True)
1832 assert result.strip() == """
1833-<table border="1">
1834+<table frame="box" rules="cols">
1835 <tr>
1836 <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 1</th>
1837 <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 2</th>
1838 <th style="padding-left: 1em; padding-right: 1em; text-align: center">Field 3</th>
1839 </tr>
1840 <tr>
1841- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value 1</td>
1842- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value2</td>
1843- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value3</td>
1844- </tr>
1845- <tr>
1846- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value 4</td>
1847- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value5</td>
1848- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value6</td>
1849- </tr>
1850- <tr>
1851- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value 7</td>
1852- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value8</td>
1853- <td style="padding-left: 1em; padding-right: 1em; text-align: center">value9</td>
1854+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 1</td>
1855+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value2</td>
1856+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value3</td>
1857+ </tr>
1858+ <tr>
1859+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 4</td>
1860+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value5</td>
1861+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value6</td>
1862+ </tr>
1863+ <tr>
1864+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value 7</td>
1865+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value8</td>
1866+ <td style="padding-left: 1em; padding-right: 1em; text-align: center; vertical-align: top">value9</td>
1867 </tr>
1868 </table>
1869 """.strip()
1870
1871+class CsvConstructorTest(BasicTests):
1872+
1873+ def setUp(self):
1874+
1875+ csv_string = """City name, Area , Population , Annual Rainfall
1876+ Sydney, 2058 , 4336374 , 1214.8
1877+ Melbourne, 1566 , 3806092 , 646.9
1878+ Brisbane, 5905 , 1857594 , 1146.4
1879+ Perth, 5386 , 1554769 , 869.4
1880+ Adelaide, 1295 , 1158259 , 600.5
1881+ Hobart, 1357 , 205556 , 619.5
1882+ Darwin, 0112 , 120900 , 1714.7"""
1883+ csv_fp = StringIO.StringIO(csv_string)
1884+ self.x = from_csv(csv_fp)
1885+
1886+if _have_sqlite:
1887+ class DatabaseConstructorTest(BasicTests):
1888+
1889+ def setUp(self):
1890+ self.conn = sqlite3.connect(":memory:")
1891+ self.cur = self.conn.cursor()
1892+ self.cur.execute("CREATE TABLE cities (name TEXT, area INTEGER, population INTEGER, rainfall REAL)")
1893+ self.cur.execute("INSERT INTO cities VALUES (\"Adelaide\", 1295, 1158259, 600.5)")
1894+ self.cur.execute("INSERT INTO cities VALUES (\"Brisbane\", 5905, 1857594, 1146.4)")
1895+ self.cur.execute("INSERT INTO cities VALUES (\"Darwin\", 112, 120900, 1714.7)")
1896+ self.cur.execute("INSERT INTO cities VALUES (\"Hobart\", 1357, 205556, 619.5)")
1897+ self.cur.execute("INSERT INTO cities VALUES (\"Sydney\", 2058, 4336374, 1214.8)")
1898+ self.cur.execute("INSERT INTO cities VALUES (\"Melbourne\", 1566, 3806092, 646.9)")
1899+ self.cur.execute("INSERT INTO cities VALUES (\"Perth\", 5386, 1554769, 869.4)")
1900+ self.cur.execute("SELECT * FROM cities")
1901+ self.x = from_db_cursor(self.cur)
1902+
1903+ def testNonSelectCurosr(self):
1904+ self.cur.execute("INSERT INTO cities VALUES (\"Adelaide\", 1295, 1158259, 600.5)")
1905+ assert from_db_cursor(self.cur) is None
1906+
1907+class HtmlConstructorTest(CityDataTest):
1908+
1909+ def testHtmlAndBack(self):
1910+ html_string = self.x.get_html_string()
1911+ new_table = from_html(html_string)[0]
1912+ assert new_table.get_string() == self.x.get_string()
1913+
1914+ def testHtmlOneAndBack(self):
1915+ html_string = self.x.get_html_string()
1916+ new_table = from_html_one(html_string)
1917+ assert new_table.get_string() == self.x.get_string()
1918+
1919+ def testHtmlOneFailOnMany(self):
1920+ html_string = self.x.get_html_string()
1921+ html_string += self.x.get_html_string()
1922+ self.assertRaises(Exception, from_html_one, html_string)
1923+
1924+class PrintEnglishTest(CityDataTest):
1925+
1926+ def testPrint(self):
1927+ print()
1928+ print(self.x)
1929+
1930+class PrintJapanestTest(unittest.TestCase):
1931+
1932+ def setUp(self):
1933+
1934+ self.x = PrettyTable(["Kanji", "Hiragana", "English"])
1935+ self.x.add_row(["神戸", "こうべ", "Kobe"])
1936+ self.x.add_row(["京都", "きょうと", "Kyoto"])
1937+ self.x.add_row(["長崎", "ながさき", "Nagasaki"])
1938+ self.x.add_row(["名古屋", "なごや", "Nagoya"])
1939+ self.x.add_row(["大阪", "おおさか", "Osaka"])
1940+ self.x.add_row(["札幌", "さっぽろ", "Sapporo"])
1941+ self.x.add_row(["東京", "とうきょう", "Tokyo"])
1942+ self.x.add_row(["横浜", "よこはま", "Yokohama"])
1943+
1944+ def testPrint(self):
1945+ print()
1946+ print(self.x)
1947+
1948 if __name__ == "__main__":
1949 unittest.main()
1950
1951=== modified file 'setup.py'
1952--- setup.py 2012-06-06 19:30:29 +0000
1953+++ setup.py 2013-11-19 02:16:58 +0000
1954@@ -7,6 +7,8 @@
1955 version=version,
1956 classifiers=[
1957 'Programming Language :: Python',
1958+ 'Programming Language :: Python :: 2.4',
1959+ 'Programming Language :: Python :: 2.5',
1960 'Programming Language :: Python :: 2.6',
1961 'Programming Language :: Python :: 2.7',
1962 'Programming Language :: Python :: 3',

Subscribers

People subscribed via source and target branches

to all changes: