Merge lp:~logan/ubuntu/trusty/prettytable/0.7.2-2ubuntu1 into lp:ubuntu/trusty/prettytable
- Trusty (14.04)
- 0.7.2-2ubuntu1
- Merge into trusty
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
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', |
Thanks. Uploaded.