Skip to content
This repository

Add support for exporting to a text table, via the texttable module. #69

Closed
wants to merge 1 commit into from

6 participants

Marc Abramowitz Vincent Driessen Andrew Montalenti Phil Freo Guido U. Draheim Kenneth Reitz
Marc Abramowitz

Add support for exporting to a text table, via the texttable module.

Vincent Driessen
nvie commented May 31, 2012

+1 for pulling this :)

Andrew Montalenti

one more +1, we were just discussing this need on our team!

Vincent Driessen
nvie commented May 31, 2012

I'll even add special :sparkles::cake::sparkles: if this is pulled, @kennethreitz :)

Marc Abramowitz

@kennethreitz Let me know if this needs any tweaks. I might've just thought of a place where I could use tablib if it had this... :-)

Guido U. Draheim
guidod commented June 16, 2013

-1 ... tablib has the habit to package the dependencies but texttable is GPL-infected while openpyxl and xlwt are MIT/BSD just as tablib is. One could still add the support but only via dynamic loading (texttable is actually LGPL), it shouldn't be packaged.

Rendering a nice text result is not quite a sophisticated programming task to justify meddling in license issues.

Marc Abramowitz

@guidod: What are the implications of using/bundling an LGPL module, such as texttable? Tablib, being Python, already provides it's source code freely. Would it have to do anything else?

Texttable could be simply required and not bundled or if it's desired to bundle something, I could convert it to use PrettyTable (BSD-licensed) (http://code.google.com/p/prettytable/) instead.

@kennethreitz: What say you?

Guido U. Draheim

By the way, I have added markdown-support in my Tablib fork at https://bitbucket.org/guidod/easyxf/src - feel free to grab the code.

Kenneth Reitz
Owner

This project is in a bit of a crisis state — it's really useful, and I use regularly. However, I wrote it several years ago and haven't touched it since. In order to get the project into a stable state I'm closing all issues and pull requests to get a "fresh slate"

Don't take this as aggressive — it's just necessary for the project to make any progress any time soon (it's pretty clear the project is effectively unmaintained at the moment). Great things to come! Please watch the GitHub logs and feel free to re-open this discussion soon. I just need to really it into a good state first.

:sparkles::heart::sparkles:

Kenneth Reitz kennethreitz closed this January 08, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
178  NOTICE
@@ -456,4 +456,180 @@ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
456 456
 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
457 457
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
458 458
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
459  
-OF THE POSSIBILITY OF SUCH DAMAGE.
  459
+OF THE POSSIBILITY OF SUCH DAMAGE.
  460
+
  461
+
  462
+Texttable License
  463
+=================
  464
+
  465
+Home-page: http://foutaise.org/code/
  466
+Author: Gerome Fournier
  467
+Author-email: jef(at)foutaise.org
  468
+License: LGPL
  469
+
  470
+                   GNU LESSER GENERAL PUBLIC LICENSE
  471
+                       Version 3, 29 June 2007
  472
+
  473
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
  474
+ Everyone is permitted to copy and distribute verbatim copies
  475
+ of this license document, but changing it is not allowed.
  476
+
  477
+
  478
+  This version of the GNU Lesser General Public License incorporates
  479
+the terms and conditions of version 3 of the GNU General Public
  480
+License, supplemented by the additional permissions listed below.
  481
+
  482
+  0. Additional Definitions.
  483
+
  484
+  As used herein, "this License" refers to version 3 of the GNU Lesser
  485
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
  486
+General Public License.
  487
+
  488
+  "The Library" refers to a covered work governed by this License,
  489
+other than an Application or a Combined Work as defined below.
  490
+
  491
+  An "Application" is any work that makes use of an interface provided
  492
+by the Library, but which is not otherwise based on the Library.
  493
+Defining a subclass of a class defined by the Library is deemed a mode
  494
+of using an interface provided by the Library.
  495
+
  496
+  A "Combined Work" is a work produced by combining or linking an
  497
+Application with the Library.  The particular version of the Library
  498
+with which the Combined Work was made is also called the "Linked
  499
+Version".
  500
+
  501
+  The "Minimal Corresponding Source" for a Combined Work means the
  502
+Corresponding Source for the Combined Work, excluding any source code
  503
+for portions of the Combined Work that, considered in isolation, are
  504
+based on the Application, and not on the Linked Version.
  505
+
  506
+  The "Corresponding Application Code" for a Combined Work means the
  507
+object code and/or source code for the Application, including any data
  508
+and utility programs needed for reproducing the Combined Work from the
  509
+Application, but excluding the System Libraries of the Combined Work.
  510
+
  511
+  1. Exception to Section 3 of the GNU GPL.
  512
+
  513
+  You may convey a covered work under sections 3 and 4 of this License
  514
+without being bound by section 3 of the GNU GPL.
  515
+
  516
+  2. Conveying Modified Versions.
  517
+
  518
+  If you modify a copy of the Library, and, in your modifications, a
  519
+facility refers to a function or data to be supplied by an Application
  520
+that uses the facility (other than as an argument passed when the
  521
+facility is invoked), then you may convey a copy of the modified
  522
+version:
  523
+
  524
+   a) under this License, provided that you make a good faith effort to
  525
+   ensure that, in the event an Application does not supply the
  526
+   function or data, the facility still operates, and performs
  527
+   whatever part of its purpose remains meaningful, or
  528
+
  529
+   b) under the GNU GPL, with none of the additional permissions of
  530
+   this License applicable to that copy.
  531
+
  532
+  3. Object Code Incorporating Material from Library Header Files.
  533
+
  534
+  The object code form of an Application may incorporate material from
  535
+a header file that is part of the Library.  You may convey such object
  536
+code under terms of your choice, provided that, if the incorporated
  537
+material is not limited to numerical parameters, data structure
  538
+layouts and accessors, or small macros, inline functions and templates
  539
+(ten or fewer lines in length), you do both of the following:
  540
+
  541
+   a) Give prominent notice with each copy of the object code that the
  542
+   Library is used in it and that the Library and its use are
  543
+   covered by this License.
  544
+
  545
+   b) Accompany the object code with a copy of the GNU GPL and this license
  546
+   document.
  547
+
  548
+  4. Combined Works.
  549
+
  550
+  You may convey a Combined Work under terms of your choice that,
  551
+taken together, effectively do not restrict modification of the
  552
+portions of the Library contained in the Combined Work and reverse
  553
+engineering for debugging such modifications, if you also do each of
  554
+the following:
  555
+
  556
+   a) Give prominent notice with each copy of the Combined Work that
  557
+   the Library is used in it and that the Library and its use are
  558
+   covered by this License.
  559
+
  560
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
  561
+   document.
  562
+
  563
+   c) For a Combined Work that displays copyright notices during
  564
+   execution, include the copyright notice for the Library among
  565
+   these notices, as well as a reference directing the user to the
  566
+   copies of the GNU GPL and this license document.
  567
+
  568
+   d) Do one of the following:
  569
+
  570
+       0) Convey the Minimal Corresponding Source under the terms of this
  571
+       License, and the Corresponding Application Code in a form
  572
+       suitable for, and under terms that permit, the user to
  573
+       recombine or relink the Application with a modified version of
  574
+       the Linked Version to produce a modified Combined Work, in the
  575
+       manner specified by section 6 of the GNU GPL for conveying
  576
+       Corresponding Source.
  577
+
  578
+       1) Use a suitable shared library mechanism for linking with the
  579
+       Library.  A suitable mechanism is one that (a) uses at run time
  580
+       a copy of the Library already present on the user's computer
  581
+       system, and (b) will operate properly with a modified version
  582
+       of the Library that is interface-compatible with the Linked
  583
+       Version.
  584
+
  585
+   e) Provide Installation Information, but only if you would otherwise
  586
+   be required to provide such information under section 6 of the
  587
+   GNU GPL, and only to the extent that such information is
  588
+   necessary to install and execute a modified version of the
  589
+   Combined Work produced by recombining or relinking the
  590
+   Application with a modified version of the Linked Version. (If
  591
+   you use option 4d0, the Installation Information must accompany
  592
+   the Minimal Corresponding Source and Corresponding Application
  593
+   Code. If you use option 4d1, you must provide the Installation
  594
+   Information in the manner specified by section 6 of the GNU GPL
  595
+   for conveying Corresponding Source.)
  596
+
  597
+  5. Combined Libraries.
  598
+
  599
+  You may place library facilities that are a work based on the
  600
+Library side by side in a single library together with other library
  601
+facilities that are not Applications and are not covered by this
  602
+License, and convey such a combined library under terms of your
  603
+choice, if you do both of the following:
  604
+
  605
+   a) Accompany the combined library with a copy of the same work based
  606
+   on the Library, uncombined with any other library facilities,
  607
+   conveyed under the terms of this License.
  608
+
  609
+   b) Give prominent notice with the combined library that part of it
  610
+   is a work based on the Library, and explaining where to find the
  611
+   accompanying uncombined form of the same work.
  612
+
  613
+  6. Revised Versions of the GNU Lesser General Public License.
  614
+
  615
+  The Free Software Foundation may publish revised and/or new versions
  616
+of the GNU Lesser General Public License from time to time. Such new
  617
+versions will be similar in spirit to the present version, but may
  618
+differ in detail to address new problems or concerns.
  619
+
  620
+  Each version is given a distinguishing version number. If the
  621
+Library as you received it specifies that a certain numbered version
  622
+of the GNU Lesser General Public License "or any later version"
  623
+applies to it, you have the option of following the terms and
  624
+conditions either of that published version or of any later version
  625
+published by the Free Software Foundation. If the Library as you
  626
+received it does not specify a version number of the GNU Lesser
  627
+General Public License, you may choose any version of the GNU Lesser
  628
+General Public License ever published by the Free Software Foundation.
  629
+
  630
+  If the Library as you received it specifies that a proxy can decide
  631
+whether future versions of the GNU Lesser General Public License shall
  632
+apply, that proxy's public statement of acceptance of any version is
  633
+permanent authorization for you to choose that version for the
  634
+Library.
  635
+
4  tablib/core.py
@@ -518,6 +518,10 @@ def tsv():
518 518
         pass
519 519
 
520 520
     @property
  521
+    def texttable():
  522
+        pass
  523
+
  524
+    @property
521 525
     def yaml():
522 526
         """A YAML representation of the :class:`Dataset` object. If headers have been
523 527
         set, a YAML list of objects will be returned. If no headers have
3  tablib/formats/__init__.py
@@ -8,8 +8,9 @@
8 8
 from . import _xls as xls
9 9
 from . import _yaml as yaml
10 10
 from . import _tsv as tsv
  11
+from . import _texttable as texttable
11 12
 from . import _html as html
12 13
 from . import _xlsx as xlsx
13 14
 from . import _ods as ods
14 15
 
15  
-available = (json, xls, yaml, csv, tsv, html, xlsx, ods)
  16
+available = (json, xls, yaml, csv, tsv, texttable, html, xlsx, ods)
32  tablib/formats/_texttable.py
... ...
@@ -0,0 +1,32 @@
  1
+# -*- coding: utf-8 -*-
  2
+
  3
+""" Text Table Support.
  4
+"""
  5
+
  6
+title = 'texttable'
  7
+extentions = ('texttable',)
  8
+
  9
+DEFAULT_ENCODING = 'utf-8'
  10
+
  11
+import tablib.packages.texttable as texttable  # so clients can use constants like Texttable.HEADER
  12
+
  13
+def export_set(dataset):
  14
+    """Returns a texttable representation of Dataset."""
  15
+
  16
+    global deco
  17
+
  18
+    table = texttable.Texttable()
  19
+
  20
+    if 'deco' in globals():
  21
+        table.set_deco(deco)
  22
+
  23
+    first_row = True
  24
+
  25
+    for row in dataset._package(dicts=False):
  26
+        if first_row:
  27
+            table.header(row)
  28
+            first_row = False
  29
+        else:
  30
+            table.add_row(row)
  31
+
  32
+    return table.draw()
591  tablib/packages/texttable.py
... ...
@@ -0,0 +1,591 @@
  1
+#!/usr/bin/env python
  2
+#
  3
+# texttable - module for creating simple ASCII tables
  4
+# Copyright (C) 2003-2011 Gerome Fournier <jef(at)foutaise.org>
  5
+#
  6
+# This library is free software; you can redistribute it and/or
  7
+# modify it under the terms of the GNU Lesser General Public
  8
+# License as published by the Free Software Foundation; either
  9
+# version 2.1 of the License, or (at your option) any later version.
  10
+#
  11
+# This library is distributed in the hope that it will be useful,
  12
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
  13
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14
+# Lesser General Public License for more details.
  15
+#
  16
+# You should have received a copy of the GNU Lesser General Public
  17
+# License along with this library; if not, write to the Free Software
  18
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  19
+
  20
+"""module for creating simple ASCII tables
  21
+
  22
+
  23
+Example:
  24
+
  25
+    table = Texttable()
  26
+    table.set_cols_align(["l", "r", "c"])
  27
+    table.set_cols_valign(["t", "m", "b"])
  28
+    table.add_rows([ ["Name", "Age", "Nickname"], 
  29
+                     ["Mr\\nXavier\\nHuon", 32, "Xav'"],
  30
+                     ["Mr\\nBaptiste\\nClement", 1, "Baby"] ])
  31
+    print table.draw() + "\\n"
  32
+
  33
+    table = Texttable()
  34
+    table.set_deco(Texttable.HEADER)
  35
+    table.set_cols_dtype(['t',  # text 
  36
+                          'f',  # float (decimal)
  37
+                          'e',  # float (exponent)
  38
+                          'i',  # integer
  39
+                          'a']) # automatic
  40
+    table.set_cols_align(["l", "r", "r", "r", "l"])
  41
+    table.add_rows([["text",    "float", "exp", "int", "auto"],
  42
+                    ["abcd",    "67",    654,   89,    128.001],
  43
+                    ["efghijk", 67.5434, .654,  89.6,  12800000000000000000000.00023],
  44
+                    ["lmn",     5e-78,   5e-78, 89.4,  .000000000000128],
  45
+                    ["opqrstu", .023,    5e+78, 92.,   12800000000000000000000]])
  46
+    print table.draw()
  47
+
  48
+Result:
  49
+
  50
+    +----------+-----+----------+
  51
+    |   Name   | Age | Nickname |
  52
+    +==========+=====+==========+
  53
+    | Mr       |     |          |
  54
+    | Xavier   |  32 |          |
  55
+    | Huon     |     |   Xav'   |
  56
+    +----------+-----+----------+
  57
+    | Mr       |     |          |
  58
+    | Baptiste |   1 |          |
  59
+    | Clement  |     |   Baby   |
  60
+    +----------+-----+----------+
  61
+
  62
+    text   float       exp      int     auto
  63
+    ===========================================
  64
+    abcd   67.000   6.540e+02   89    128.001
  65
+    efgh   67.543   6.540e-01   90    1.280e+22
  66
+    ijkl   0.000    5.000e-78   89    0.000
  67
+    mnop   0.023    5.000e+78   92    1.280e+22
  68
+"""
  69
+
  70
+__all__ = ["Texttable", "ArraySizeError"]
  71
+
  72
+__author__ = 'Gerome Fournier <jef(at)foutaise.org>'
  73
+__license__ = 'GPL'
  74
+__version__ = '0.8.1'
  75
+__credits__ = """\
  76
+Jeff Kowalczyk:
  77
+    - textwrap improved import
  78
+    - comment concerning header output
  79
+
  80
+Anonymous:
  81
+    - add_rows method, for adding rows in one go
  82
+
  83
+Sergey Simonenko:
  84
+    - redefined len() function to deal with non-ASCII characters
  85
+
  86
+Roger Lew:
  87
+    - columns datatype specifications
  88
+
  89
+Brian Peterson:
  90
+    - better handling of unicode errors
  91
+"""
  92
+
  93
+import sys
  94
+import string
  95
+
  96
+try:
  97
+    if sys.version >= '2.3':
  98
+        import textwrap
  99
+    elif sys.version >= '2.2':
  100
+        from optparse import textwrap
  101
+    else:
  102
+        from optik import textwrap
  103
+except ImportError:
  104
+    sys.stderr.write("Can't import textwrap module!\n")
  105
+    raise
  106
+
  107
+try:
  108
+    True, False
  109
+except NameError:
  110
+    (True, False) = (1, 0)
  111
+
  112
+def len(iterable):
  113
+    """Redefining len here so it will be able to work with non-ASCII characters
  114
+    """
  115
+    if not isinstance(iterable, str):
  116
+        return iterable.__len__()
  117
+    
  118
+    try:
  119
+        return len(unicode(iterable, 'utf'))
  120
+    except:
  121
+        return iterable.__len__()
  122
+
  123
+class ArraySizeError(Exception):
  124
+    """Exception raised when specified rows don't fit the required size
  125
+    """
  126
+
  127
+    def __init__(self, msg):
  128
+        self.msg = msg
  129
+        Exception.__init__(self, msg, '')
  130
+
  131
+    def __str__(self):
  132
+        return self.msg
  133
+
  134
+class Texttable:
  135
+
  136
+    BORDER = 1
  137
+    HEADER = 1 << 1
  138
+    HLINES = 1 << 2
  139
+    VLINES = 1 << 3
  140
+
  141
+    def __init__(self, max_width=80):
  142
+        """Constructor
  143
+
  144
+        - max_width is an integer, specifying the maximum width of the table
  145
+        - if set to 0, size is unlimited, therefore cells won't be wrapped
  146
+        """
  147
+
  148
+        if max_width <= 0:
  149
+            max_width = False
  150
+        self._max_width = max_width
  151
+        self._precision = 3
  152
+
  153
+        self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \
  154
+            Texttable.HEADER
  155
+        self.set_chars(['-', '|', '+', '='])
  156
+        self.reset()
  157
+
  158
+    def reset(self):
  159
+        """Reset the instance
  160
+
  161
+        - reset rows and header
  162
+        """
  163
+
  164
+        self._hline_string = None
  165
+        self._row_size = None
  166
+        self._header = []
  167
+        self._rows = []
  168
+
  169
+    def set_chars(self, array):
  170
+        """Set the characters used to draw lines between rows and columns
  171
+
  172
+        - the array should contain 4 fields:
  173
+
  174
+            [horizontal, vertical, corner, header]
  175
+
  176
+        - default is set to:
  177
+
  178
+            ['-', '|', '+', '=']
  179
+        """
  180
+
  181
+        if len(array) != 4:
  182
+            raise ArraySizeError, "array should contain 4 characters"
  183
+        array = [ x[:1] for x in [ str(s) for s in array ] ]
  184
+        (self._char_horiz, self._char_vert,
  185
+            self._char_corner, self._char_header) = array
  186
+
  187
+    def set_deco(self, deco):
  188
+        """Set the table decoration
  189
+
  190
+        - 'deco' can be a combinaison of:
  191
+
  192
+            Texttable.BORDER: Border around the table
  193
+            Texttable.HEADER: Horizontal line below the header
  194
+            Texttable.HLINES: Horizontal lines between rows
  195
+            Texttable.VLINES: Vertical lines between columns
  196
+
  197
+           All of them are enabled by default
  198
+
  199
+        - example:
  200
+
  201
+            Texttable.BORDER | Texttable.HEADER
  202
+        """
  203
+
  204
+        self._deco = deco
  205
+
  206
+    def set_cols_align(self, array):
  207
+        """Set the desired columns alignment
  208
+
  209
+        - the elements of the array should be either "l", "c" or "r":
  210
+
  211
+            * "l": column flushed left
  212
+            * "c": column centered
  213
+            * "r": column flushed right
  214
+        """
  215
+
  216
+        self._check_row_size(array)
  217
+        self._align = array
  218
+
  219
+    def set_cols_valign(self, array):
  220
+        """Set the desired columns vertical alignment
  221
+
  222
+        - the elements of the array should be either "t", "m" or "b":
  223
+
  224
+            * "t": column aligned on the top of the cell
  225
+            * "m": column aligned on the middle of the cell
  226
+            * "b": column aligned on the bottom of the cell
  227
+        """
  228
+
  229
+        self._check_row_size(array)
  230
+        self._valign = array
  231
+
  232
+    def set_cols_dtype(self, array):
  233
+        """Set the desired columns datatype for the cols.
  234
+
  235
+        - the elements of the array should be either "a", "t", "f", "e" or "i":
  236
+
  237
+            * "a": automatic (try to use the most appropriate datatype)
  238
+            * "t": treat as text
  239
+            * "f": treat as float in decimal format
  240
+            * "e": treat as float in exponential format
  241
+            * "i": treat as int
  242
+
  243
+        - by default, automatic datatyping is used for each column
  244
+        """
  245
+
  246
+        self._check_row_size(array)
  247
+        self._dtype = array
  248
+
  249
+    def set_cols_width(self, array):
  250
+        """Set the desired columns width
  251
+
  252
+        - the elements of the array should be integers, specifying the
  253
+          width of each column. For example:
  254
+
  255
+                [10, 20, 5]
  256
+        """
  257
+
  258
+        self._check_row_size(array)
  259
+        try:
  260
+            array = map(int, array)
  261
+            if reduce(min, array) <= 0:
  262
+                raise ValueError
  263
+        except ValueError:
  264
+            sys.stderr.write("Wrong argument in column width specification\n")
  265
+            raise
  266
+        self._width = array
  267
+
  268
+    def set_precision(self, width):
  269
+        """Set the desired precision for float/exponential formats
  270
+
  271
+        - width must be an integer >= 0
  272
+
  273
+        - default value is set to 3
  274
+        """
  275
+
  276
+        if not type(width) is int or width < 0:
  277
+            raise ValueError('width must be an integer greater then 0')
  278
+        self._precision = width
  279
+
  280
+    def header(self, array):
  281
+        """Specify the header of the table
  282
+        """
  283
+
  284
+        self._check_row_size(array)
  285
+        self._header = map(str, array)
  286
+
  287
+    def add_row(self, array):
  288
+        """Add a row in the rows stack
  289
+
  290
+        - cells can contain newlines and tabs
  291
+        """
  292
+
  293
+        self._check_row_size(array)
  294
+
  295
+        if not hasattr(self, "_dtype"):
  296
+            self._dtype = ["a"] * self._row_size
  297
+            
  298
+        cells = []
  299
+        for i,x in enumerate(array):
  300
+            cells.append(self._str(i,x))
  301
+        self._rows.append(cells)
  302
+
  303
+    def add_rows(self, rows, header=True):
  304
+        """Add several rows in the rows stack
  305
+
  306
+        - The 'rows' argument can be either an iterator returning arrays,
  307
+          or a by-dimensional array
  308
+        - 'header' specifies if the first row should be used as the header
  309
+          of the table
  310
+        """
  311
+
  312
+        # nb: don't use 'iter' on by-dimensional arrays, to get a 
  313
+        #     usable code for python 2.1
  314
+        if header:
  315
+            if hasattr(rows, '__iter__') and hasattr(rows, 'next'):
  316
+                self.header(rows.next())
  317
+            else:
  318
+                self.header(rows[0])
  319
+                rows = rows[1:]
  320
+        for row in rows:
  321
+            self.add_row(row)
  322
+
  323
+    def draw(self):
  324
+        """Draw the table
  325
+
  326
+        - the table is returned as a whole string
  327
+        """
  328
+
  329
+        if not self._header and not self._rows:
  330
+            return
  331
+        self._compute_cols_width()
  332
+        self._check_align()
  333
+        out = ""
  334
+        if self._has_border():
  335
+            out += self._hline()
  336
+        if self._header:
  337
+            out += self._draw_line(self._header, isheader=True)
  338
+            if self._has_header():
  339
+                out += self._hline_header()
  340
+        length = 0
  341
+        for row in self._rows:
  342
+            length += 1
  343
+            out += self._draw_line(row)
  344
+            if self._has_hlines() and length < len(self._rows):
  345
+                out += self._hline()
  346
+        if self._has_border():
  347
+            out += self._hline()
  348
+        return out[:-1]
  349
+
  350
+    def _str(self, i, x):
  351
+        """Handles string formatting of cell data
  352
+
  353
+            i - index of the cell datatype in self._dtype 
  354
+            x - cell data to format
  355
+        """
  356
+        try:
  357
+            f = float(x)
  358
+        except:
  359
+            return str(x)
  360
+
  361
+        n = self._precision
  362
+        dtype = self._dtype[i]
  363
+
  364
+        if dtype == 'i':
  365
+            return str(int(round(f)))
  366
+        elif dtype == 'f':
  367
+            return '%.*f' % (n, f)
  368
+        elif dtype == 'e':
  369
+            return '%.*e' % (n, f)
  370
+        elif dtype == 't':
  371
+            return str(x)
  372
+        else:
  373
+            if f - round(f) == 0:
  374
+                if abs(f) > 1e8:
  375
+                    return '%.*e' % (n, f)
  376
+                else:
  377
+                    return str(int(round(f)))
  378
+            else:
  379
+                if abs(f) > 1e8:
  380
+                    return '%.*e' % (n, f)
  381
+                else:
  382
+                    return '%.*f' % (n, f)
  383
+
  384
+    def _check_row_size(self, array):
  385
+        """Check that the specified array fits the previous rows size
  386
+        """
  387
+
  388
+        if not self._row_size:
  389
+            self._row_size = len(array)
  390
+        elif self._row_size != len(array):
  391
+            raise ArraySizeError, "array should contain %d elements" \
  392
+                % self._row_size
  393
+
  394
+    def _has_vlines(self):
  395
+        """Return a boolean, if vlines are required or not
  396
+        """
  397
+
  398
+        return self._deco & Texttable.VLINES > 0
  399
+
  400
+    def _has_hlines(self):
  401
+        """Return a boolean, if hlines are required or not
  402
+        """
  403
+
  404
+        return self._deco & Texttable.HLINES > 0
  405
+
  406
+    def _has_border(self):
  407
+        """Return a boolean, if border is required or not
  408
+        """
  409
+
  410
+        return self._deco & Texttable.BORDER > 0
  411
+
  412
+    def _has_header(self):
  413
+        """Return a boolean, if header line is required or not
  414
+        """
  415
+
  416
+        return self._deco & Texttable.HEADER > 0
  417
+
  418
+    def _hline_header(self):
  419
+        """Print header's horizontal line
  420
+        """
  421
+
  422
+        return self._build_hline(True)
  423
+
  424
+    def _hline(self):
  425
+        """Print an horizontal line
  426
+        """
  427
+
  428
+        if not self._hline_string:
  429
+            self._hline_string = self._build_hline()
  430
+        return self._hline_string
  431
+
  432
+    def _build_hline(self, is_header=False):
  433
+        """Return a string used to separated rows or separate header from
  434
+        rows
  435
+        """
  436
+        horiz = self._char_horiz
  437
+        if (is_header):
  438
+            horiz = self._char_header
  439
+        # compute cell separator
  440
+        s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()],
  441
+            horiz)
  442
+        # build the line
  443
+        l = string.join([horiz * n for n in self._width], s)
  444
+        # add border if needed
  445
+        if self._has_border():
  446
+            l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz,
  447
+                self._char_corner)
  448
+        else:
  449
+            l += "\n"
  450
+        return l
  451
+
  452
+    def _len_cell(self, cell):
  453
+        """Return the width of the cell
  454
+
  455
+        Special characters are taken into account to return the width of the
  456
+        cell, such like newlines and tabs
  457
+        """
  458
+
  459
+        cell_lines = cell.split('\n')
  460
+        maxi = 0
  461
+        for line in cell_lines:
  462
+            length = 0
  463
+            parts = line.split('\t')
  464
+            for part, i in zip(parts, range(1, len(parts) + 1)):
  465
+                length = length + len(part)
  466
+                if i < len(parts):
  467
+                    length = (length/8 + 1) * 8
  468
+            maxi = max(maxi, length)
  469
+        return maxi
  470
+
  471
+    def _compute_cols_width(self):
  472
+        """Return an array with the width of each column
  473
+
  474
+        If a specific width has been specified, exit. If the total of the
  475
+        columns width exceed the table desired width, another width will be
  476
+        computed to fit, and cells will be wrapped.
  477
+        """
  478
+
  479
+        if hasattr(self, "_width"):
  480
+            return
  481
+        maxi = []
  482
+        if self._header:
  483
+            maxi = [ self._len_cell(x) for x in self._header ]
  484
+        for row in self._rows:
  485
+            for cell,i in zip(row, range(len(row))):
  486
+                try:
  487
+                    maxi[i] = max(maxi[i], self._len_cell(cell))
  488
+                except (TypeError, IndexError):
  489
+                    maxi.append(self._len_cell(cell))
  490
+        items = len(maxi)
  491
+        length = reduce(lambda x,y: x+y, maxi)
  492
+        if self._max_width and length + items * 3 + 1 > self._max_width:
  493
+            maxi = [(self._max_width - items * 3 -1) / items \
  494
+                for n in range(items)]
  495
+        self._width = maxi
  496
+
  497
+    def _check_align(self):
  498
+        """Check if alignment has been specified, set default one if not
  499
+        """
  500
+
  501
+        if not hasattr(self, "_align"):
  502
+            self._align = ["l"] * self._row_size
  503
+        if not hasattr(self, "_valign"):
  504
+            self._valign = ["t"] * self._row_size
  505
+
  506
+    def _draw_line(self, line, isheader=False):
  507
+        """Draw a line
  508
+
  509
+        Loop over a single cell length, over all the cells
  510
+        """
  511
+
  512
+        line = self._splitit(line, isheader)
  513
+        space = " "
  514
+        out  = ""
  515
+        for i in range(len(line[0])):
  516
+            if self._has_border():
  517
+                out += "%s " % self._char_vert
  518
+            length = 0
  519
+            for cell, width, align in zip(line, self._width, self._align):
  520
+                length += 1
  521
+                cell_line = cell[i]
  522
+                fill = width - len(cell_line)
  523
+                if isheader:
  524
+                    align = "c"
  525
+                if align == "r":
  526
+                    out += "%s " % (fill * space + cell_line)
  527
+                elif align == "c":
  528
+                    out += "%s " % (fill/2 * space + cell_line \
  529
+                            + (fill/2 + fill%2) * space)
  530
+                else:
  531
+                    out += "%s " % (cell_line + fill * space)
  532
+                if length < len(line):
  533
+                    out += "%s " % [space, self._char_vert][self._has_vlines()]
  534
+            out += "%s\n" % ['', self._char_vert][self._has_border()]
  535
+        return out
  536
+
  537
+    def _splitit(self, line, isheader):
  538
+        """Split each element of line to fit the column width
  539
+
  540
+        Each element is turned into a list, result of the wrapping of the
  541
+        string to the desired width
  542
+        """
  543
+
  544
+        line_wrapped = []
  545
+        for cell, width in zip(line, self._width):
  546
+            array = []
  547
+            for c in cell.split('\n'):
  548
+                try:
  549
+                    c = unicode(c, 'utf')
  550
+                except UnicodeDecodeError, strerror:
  551
+                    sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (c, strerror))
  552
+                    c = unicode(c, 'utf', 'replace')
  553
+                array.extend(textwrap.wrap(c, width))
  554
+            line_wrapped.append(array)
  555
+        max_cell_lines = reduce(max, map(len, line_wrapped))
  556
+        for cell, valign in zip(line_wrapped, self._valign):
  557
+            if isheader:
  558
+                valign = "t"
  559
+            if valign == "m":
  560
+                missing = max_cell_lines - len(cell)
  561
+                cell[:0] = [""] * (missing / 2)
  562
+                cell.extend([""] * (missing / 2 + missing % 2))
  563
+            elif valign == "b":
  564
+                cell[:0] = [""] * (max_cell_lines - len(cell))
  565
+            else:
  566
+                cell.extend([""] * (max_cell_lines - len(cell)))
  567
+        return line_wrapped
  568
+
  569
+if __name__ == '__main__':
  570
+    table = Texttable()
  571
+    table.set_cols_align(["l", "r", "c"])
  572
+    table.set_cols_valign(["t", "m", "b"])
  573
+    table.add_rows([ ["Name", "Age", "Nickname"], 
  574
+                     ["Mr\nXavier\nHuon", 32, "Xav'"],
  575
+                     ["Mr\nBaptiste\nClement", 1, "Baby"] ])
  576
+    print table.draw() + "\n"
  577
+
  578
+    table = Texttable()
  579
+    table.set_deco(Texttable.HEADER)
  580
+    table.set_cols_dtype(['t',  # text 
  581
+                          'f',  # float (decimal)
  582
+                          'e',  # float (exponent)
  583
+                          'i',  # integer
  584
+                          'a']) # automatic
  585
+    table.set_cols_align(["l", "r", "r", "r", "l"])
  586
+    table.add_rows([["text",    "float", "exp", "int", "auto"],
  587
+                    ["abcd",    "67",    654,   89,    128.001],
  588
+                    ["efghijk", 67.5434, .654,  89.6,  12800000000000000000000.00023],
  589
+                    ["lmn",     5e-78,   5e-78, 89.4,  .000000000000128],
  590
+                    ["opqrstu", .023,    5e+78, 92.,   12800000000000000000000]])
  591
+    print table.draw()
28  test_tablib.py
@@ -264,6 +264,34 @@ def test_tsv_export(self):
264 264
         self.assertEqual(tsv, self.founders.tsv)
265 265
 
266 266
 
  267
+    def test_texttable_export(self):
  268
+        # print("\n"+self.founders.texttable)
  269
+
  270
+        self.assertEqual("""
  271
++------------+------------+-----+
  272
+| first_name | last_name  | gpa |
  273
++============+============+=====+
  274
+| John       | Adams      | 90  |
  275
++------------+------------+-----+
  276
+| George     | Washington | 67  |
  277
++------------+------------+-----+
  278
+| Thomas     | Jefferson  | 50  |
  279
++------------+------------+-----+
  280
+""".strip(), self.founders.texttable)
  281
+
  282
+        tablib.formats._texttable.deco = tablib.formats._texttable.texttable.Texttable.HEADER
  283
+
  284
+        # print("\n"+self.founders.texttable)
  285
+
  286
+        self.assertEqual("""
  287
+first_name   last_name    gpa 
  288
+=============================
  289
+John         Adams        90  
  290
+George       Washington   67  
  291
+Thomas       Jefferson    50  
  292
+""".strip(), self.founders.texttable.strip())
  293
+
  294
+
267 295
     def test_html_export(self):
268 296
         """HTML export"""
269 297
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.