You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafodion.apache.org by li...@apache.org on 2017/01/16 04:56:40 UTC

[3/5] incubator-trafodion git commit: [TRAFODION-2431] python installer - add python lib dependencies

[TRAFODION-2431] python installer - add python lib dependencies


Project: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/commit/702b6ddd
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/tree/702b6ddd
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafodion/diff/702b6ddd

Branch: refs/heads/master
Commit: 702b6ddd25c11efff85f46e42ffbd6322119f514
Parents: 73446d8
Author: Eason <hf...@gmail.com>
Authored: Tue Jan 10 11:37:23 2017 +0800
Committer: Eason <hf...@gmail.com>
Committed: Wed Jan 11 21:20:23 2017 +0800

----------------------------------------------------------------------
 install/Makefile                                |    5 +-
 install/python-installer/prettytable.py         | 1475 +++++++++++++++
 .../scripts/httplib2/__init__.py                | 1714 ++++++++++++++++++
 .../scripts/httplib2/iri2uri.py                 |  132 ++
 .../python-installer/scripts/httplib2/socks.py  |  438 +++++
 licenses/Makefile                               |    7 +-
 licenses/lic-pyinstall-src                      |   63 +
 7 files changed, 3831 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-trafodion/blob/702b6ddd/install/Makefile
----------------------------------------------------------------------
diff --git a/install/Makefile b/install/Makefile
index c6f5da6..5dc7048 100644
--- a/install/Makefile
+++ b/install/Makefile
@@ -47,7 +47,10 @@ installer/NOTICE: ../NOTICE
 installer/DISCLAIMER: ../DISCLAIMER
 	cp -f $? $@
 
-python-installer/LICENSE: ../licenses/LICENSE-install
+../licenses/LICENSE-pyinstall:
+	cd $(@D) && $(MAKE) $(@F)
+
+python-installer/LICENSE: ../licenses/LICENSE-pyinstall
 	cp -f $? $@
 
 python-installer/NOTICE: ../NOTICE

http://git-wip-us.apache.org/repos/asf/incubator-trafodion/blob/702b6ddd/install/python-installer/prettytable.py
----------------------------------------------------------------------
diff --git a/install/python-installer/prettytable.py b/install/python-installer/prettytable.py
new file mode 100644
index 0000000..8abb952
--- /dev/null
+++ b/install/python-installer/prettytable.py
@@ -0,0 +1,1475 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2009-2013, Luke Maurits <lu...@maurits.id.au>
+# All rights reserved.
+# With contributions from:
+#  * Chris Clark
+#  * Klein Stephane
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright notice,
+#   this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+#   this list of conditions and the following disclaimer in the documentation
+#   and/or other materials provided with the distribution.
+# * The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+__version__ = "0.7.2"
+
+import copy
+import csv
+import random
+import re
+import sys
+import textwrap
+import itertools
+import unicodedata
+
+py3k = sys.version_info[0] >= 3
+if py3k:
+    unicode = str
+    basestring = str
+    itermap = map
+    iterzip = zip
+    uni_chr = chr
+    from html.parser import HTMLParser
+else: 
+    itermap = itertools.imap
+    iterzip = itertools.izip
+    uni_chr = unichr
+    from HTMLParser import HTMLParser
+
+if py3k and sys.version_info[1] >= 2:
+    from html import escape
+else:
+    from cgi import escape
+
+# hrule styles
+FRAME = 0
+ALL   = 1
+NONE  = 2
+HEADER = 3
+
+# Table styles
+DEFAULT = 10
+MSWORD_FRIENDLY = 11
+PLAIN_COLUMNS = 12
+RANDOM = 20
+
+_re = re.compile("\033\[[0-9;]*m")
+
+def _get_size(text):
+    lines = text.split("\n")
+    height = len(lines)
+    width = max([_str_block_width(line) for line in lines])
+    return (width, height)
+        
+class PrettyTable(object):
+
+    def __init__(self, field_names=None, **kwargs):
+
+        """Return a new PrettyTable instance
+
+        Arguments:
+
+        encoding - Unicode encoding scheme used to decode any encoded input
+        field_names - list or tuple of field names
+        fields - list or tuple of field names to include in displays
+        start - index of first data row to include in output
+        end - index of last data row to include in output PLUS ONE (list slice style)
+        header - print a header showing field names (True or False)
+        header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)
+        border - print a border around the table (True or False)
+        hrules - controls printing of horizontal rules after rows.  Allowed values: FRAME, HEADER, ALL, NONE
+        vrules - controls printing of vertical rules between columns.  Allowed values: FRAME, ALL, NONE
+        int_format - controls formatting of integer data
+        float_format - controls formatting of floating point data
+        padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
+        left_padding_width - number of spaces on left hand side of column data
+        right_padding_width - number of spaces on right hand side of column data
+        vertical_char - single character string used to draw vertical lines
+        horizontal_char - single character string used to draw horizontal lines
+        junction_char - single character string used to draw line junctions
+        sortby - name of field to sort rows by
+        sort_key - sorting key function, applied to data points before sorting
+        valign - default valign for each row (None, "t", "m" or "b")
+        reversesort - True or False to sort in descending or ascending order"""
+
+        self.encoding = kwargs.get("encoding", "UTF-8")
+
+        # Data
+        self._field_names = []
+        self._align = {}
+        self._valign = {}
+        self._max_width = {}
+        self._rows = []
+        if field_names:
+            self.field_names = field_names
+        else:
+            self._widths = []
+
+        # Options
+        self._options = "start end fields header border sortby reversesort sort_key attributes format hrules vrules".split()
+        self._options.extend("int_format float_format padding_width left_padding_width right_padding_width".split())
+        self._options.extend("vertical_char horizontal_char junction_char header_style valign xhtml print_empty".split())
+        for option in self._options:
+            if option in kwargs:
+                self._validate_option(option, kwargs[option])
+            else:
+                kwargs[option] = None
+
+        self._start = kwargs["start"] or 0
+        self._end = kwargs["end"] or None
+        self._fields = kwargs["fields"] or None
+
+        if kwargs["header"] in (True, False):
+            self._header = kwargs["header"]
+        else:
+            self._header = True
+        self._header_style = kwargs["header_style"] or None
+        if kwargs["border"] in (True, False):
+            self._border = kwargs["border"]
+        else:
+            self._border = True
+        self._hrules = kwargs["hrules"] or FRAME
+        self._vrules = kwargs["vrules"] or ALL
+
+        self._sortby = kwargs["sortby"] or None
+        if kwargs["reversesort"] in (True, False):
+            self._reversesort = kwargs["reversesort"]
+        else:
+            self._reversesort = False
+        self._sort_key = kwargs["sort_key"] or (lambda x: x)
+
+        self._int_format = kwargs["int_format"] or {}
+        self._float_format = kwargs["float_format"] or {}
+        self._padding_width = kwargs["padding_width"] or 1
+        self._left_padding_width = kwargs["left_padding_width"] or None
+        self._right_padding_width = kwargs["right_padding_width"] or None
+
+        self._vertical_char = kwargs["vertical_char"] or self._unicode("|")
+        self._horizontal_char = kwargs["horizontal_char"] or self._unicode("-")
+        self._junction_char = kwargs["junction_char"] or self._unicode("+")
+        
+        if kwargs["print_empty"] in (True, False):
+            self._print_empty = kwargs["print_empty"]
+        else:
+            self._print_empty = True
+        self._format = kwargs["format"] or False
+        self._xhtml = kwargs["xhtml"] or False
+        self._attributes = kwargs["attributes"] or {}
+   
+    def _unicode(self, value):
+        if not isinstance(value, basestring):
+            value = str(value)
+        if not isinstance(value, unicode):
+            value = unicode(value, self.encoding, "strict")
+        return value
+
+    def _justify(self, text, width, align):
+        excess = width - _str_block_width(text)
+        if align == "l":
+            return text + excess * " "
+        elif align == "r":
+            return excess * " " + text
+        else:
+            if excess % 2:
+                # Uneven padding
+                # Put more space on right if text is of odd length...
+                if _str_block_width(text) % 2:
+                    return (excess//2)*" " + text + (excess//2 + 1)*" "
+                # and more space on left if text is of even length
+                else:
+                    return (excess//2 + 1)*" " + text + (excess//2)*" "
+                # Why distribute extra space this way?  To match the behaviour of
+                # the inbuilt str.center() method.
+            else:
+                # Equal padding on either side
+                return (excess//2)*" " + text + (excess//2)*" "
+
+    def __getattr__(self, name):
+
+        if name == "rowcount":
+            return len(self._rows)
+        elif name == "colcount":
+            if self._field_names:
+                return len(self._field_names)
+            elif self._rows:
+                return len(self._rows[0])
+            else:
+                return 0
+        else:
+            raise AttributeError(name)
+ 
+    def __getitem__(self, index):
+
+        new = PrettyTable()
+        new.field_names = self.field_names
+        for attr in self._options:
+            setattr(new, "_"+attr, getattr(self, "_"+attr))
+        setattr(new, "_align", getattr(self, "_align"))
+        if isinstance(index, slice):
+            for row in self._rows[index]:
+                new.add_row(row)
+        elif isinstance(index, int):
+            new.add_row(self._rows[index])
+        else:
+            raise Exception("Index %s is invalid, must be an integer or slice" % str(index))
+        return new
+
+    if py3k:
+        def __str__(self):
+           return self.__unicode__()
+    else:
+        def __str__(self):
+           return self.__unicode__().encode(self.encoding)
+
+    def __unicode__(self):
+        return self.get_string()
+
+    ##############################
+    # ATTRIBUTE VALIDATORS       #
+    ##############################
+
+    # The method _validate_option is all that should be used elsewhere in the code base to validate options.
+    # It will call the appropriate validation method for that option.  The individual validation methods should
+    # never need to be called directly (although nothing bad will happen if they *are*).
+    # Validation happens in TWO places.
+    # Firstly, in the property setters defined in the ATTRIBUTE MANAGMENT section.
+    # Secondly, in the _get_options method, where keyword arguments are mixed with persistent settings
+
+    def _validate_option(self, option, val):
+        if option in ("field_names"):
+            self._validate_field_names(val)
+        elif option in ("start", "end", "max_width", "padding_width", "left_padding_width", "right_padding_width", "format"):
+            self._validate_nonnegative_int(option, val)
+        elif option in ("sortby"):
+            self._validate_field_name(option, val)
+        elif option in ("sort_key"):
+            self._validate_function(option, val)
+        elif option in ("hrules"):
+            self._validate_hrules(option, val)
+        elif option in ("vrules"):
+            self._validate_vrules(option, val)
+        elif option in ("fields"):
+            self._validate_all_field_names(option, val)
+        elif option in ("header", "border", "reversesort", "xhtml", "print_empty"):
+            self._validate_true_or_false(option, val)
+        elif option in ("header_style"):
+            self._validate_header_style(val)
+        elif option in ("int_format"):
+            self._validate_int_format(option, val)
+        elif option in ("float_format"):
+            self._validate_float_format(option, val)
+        elif option in ("vertical_char", "horizontal_char", "junction_char"):
+            self._validate_single_char(option, val)
+        elif option in ("attributes"):
+            self._validate_attributes(option, val)
+        else:
+            raise Exception("Unrecognised option: %s!" % option)
+
+    def _validate_field_names(self, val):
+        # Check for appropriate length
+        if self._field_names:
+            try:
+               assert len(val) == len(self._field_names)
+            except AssertionError:
+               raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._field_names)))
+        if self._rows:
+            try:
+               assert len(val) == len(self._rows[0])
+            except AssertionError:
+               raise Exception("Field name list has incorrect number of values, (actual) %d!=%d (expected)" % (len(val), len(self._rows[0])))
+        # Check for uniqueness
+        try:
+            assert len(val) == len(set(val))
+        except AssertionError:
+            raise Exception("Field names must be unique!")
+
+    def _validate_header_style(self, val):
+        try:
+            assert val in ("cap", "title", "upper", "lower", None)
+        except AssertionError:
+            raise Exception("Invalid header style, use cap, title, upper, lower or None!")
+
+    def _validate_align(self, val):
+        try:
+            assert val in ["l","c","r"]
+        except AssertionError:
+            raise Exception("Alignment %s is invalid, use l, c or r!" % val)
+
+    def _validate_valign(self, val):
+        try:
+            assert val in ["t","m","b",None]
+        except AssertionError:
+            raise Exception("Alignment %s is invalid, use t, m, b or None!" % val)
+
+    def _validate_nonnegative_int(self, name, val):
+        try:
+            assert int(val) >= 0
+        except AssertionError:
+            raise Exception("Invalid value for %s: %s!" % (name, self._unicode(val)))
+
+    def _validate_true_or_false(self, name, val):
+        try:
+            assert val in (True, False)
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be True or False." % name)
+
+    def _validate_int_format(self, name, val):
+        if val == "":
+            return
+        try:
+            assert type(val) in (str, unicode)
+            assert val.isdigit()
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be an integer format string." % name)
+
+    def _validate_float_format(self, name, val):
+        if val == "":
+            return
+        try:
+            assert type(val) in (str, unicode)
+            assert "." in val
+            bits = val.split(".")
+            assert len(bits) <= 2
+            assert bits[0] == "" or bits[0].isdigit()
+            assert bits[1] == "" or bits[1].isdigit()
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be a float format string." % name)
+
+    def _validate_function(self, name, val):
+        try:
+            assert hasattr(val, "__call__")
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be a function." % name)
+
+    def _validate_hrules(self, name, val):
+        try:
+            assert val in (ALL, FRAME, HEADER, NONE)
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be ALL, FRAME, HEADER or NONE." % name)
+
+    def _validate_vrules(self, name, val):
+        try:
+            assert val in (ALL, FRAME, NONE)
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be ALL, FRAME, or NONE." % name)
+
+    def _validate_field_name(self, name, val):
+        try:
+            assert (val in self._field_names) or (val is None)
+        except AssertionError:
+            raise Exception("Invalid field name: %s!" % val)
+
+    def _validate_all_field_names(self, name, val):
+        try:
+            for x in val:
+                self._validate_field_name(name, x)
+        except AssertionError:
+            raise Exception("fields must be a sequence of field names!")
+
+    def _validate_single_char(self, name, val):
+        try:
+            assert _str_block_width(val) == 1
+        except AssertionError:
+            raise Exception("Invalid value for %s!  Must be a string of length 1." % name)
+
+    def _validate_attributes(self, name, val):
+        try:
+            assert isinstance(val, dict)
+        except AssertionError:
+            raise Exception("attributes must be a dictionary of name/value pairs!")
+
+    ##############################
+    # ATTRIBUTE MANAGEMENT       #
+    ##############################
+
+    def _get_field_names(self):
+        return self._field_names
+        """The names of the fields
+
+        Arguments:
+
+        fields - list or tuple of field names"""
+    def _set_field_names(self, val):
+        val = [self._unicode(x) for x in val]
+        self._validate_option("field_names", val)
+        if self._field_names:
+            old_names = self._field_names[:]
+        self._field_names = val
+        if self._align and old_names:
+            for old_name, new_name in zip(old_names, val):
+                self._align[new_name] = self._align[old_name]
+            for old_name in old_names:
+                if old_name not in self._align:
+                    self._align.pop(old_name)
+        else:
+            for field in self._field_names:
+                self._align[field] = "c"
+        if self._valign and old_names:
+            for old_name, new_name in zip(old_names, val):
+                self._valign[new_name] = self._valign[old_name]
+            for old_name in old_names:
+                if old_name not in self._valign:
+                    self._valign.pop(old_name)
+        else:
+            for field in self._field_names:
+                self._valign[field] = "t"
+    field_names = property(_get_field_names, _set_field_names)
+
+    def _get_align(self):
+        return self._align
+    def _set_align(self, val):
+        self._validate_align(val)
+        for field in self._field_names:
+            self._align[field] = val
+    align = property(_get_align, _set_align)
+
+    def _get_valign(self):
+        return self._valign
+    def _set_valign(self, val):
+        self._validate_valign(val)
+        for field in self._field_names:
+            self._valign[field] = val
+    valign = property(_get_valign, _set_valign)
+
+    def _get_max_width(self):
+        return self._max_width
+    def _set_max_width(self, val):
+        self._validate_option("max_width", val)
+        for field in self._field_names:
+            self._max_width[field] = val
+    max_width = property(_get_max_width, _set_max_width)
+    
+    def _get_fields(self):
+        """List or tuple of field names to include in displays
+
+        Arguments:
+
+        fields - list or tuple of field names to include in displays"""
+        return self._fields
+    def _set_fields(self, val):
+        self._validate_option("fields", val)
+        self._fields = val
+    fields = property(_get_fields, _set_fields)
+
+    def _get_start(self):
+        """Start index of the range of rows to print
+
+        Arguments:
+
+        start - index of first data row to include in output"""
+        return self._start
+
+    def _set_start(self, val):
+        self._validate_option("start", val)
+        self._start = val
+    start = property(_get_start, _set_start)
+
+    def _get_end(self):
+        """End index of the range of rows to print
+
+        Arguments:
+
+        end - index of last data row to include in output PLUS ONE (list slice style)"""
+        return self._end
+    def _set_end(self, val):
+        self._validate_option("end", val)
+        self._end = val
+    end = property(_get_end, _set_end)
+
+    def _get_sortby(self):
+        """Name of field by which to sort rows
+
+        Arguments:
+
+        sortby - field name to sort by"""
+        return self._sortby
+    def _set_sortby(self, val):
+        self._validate_option("sortby", val)
+        self._sortby = val
+    sortby = property(_get_sortby, _set_sortby)
+
+    def _get_reversesort(self):
+        """Controls direction of sorting (ascending vs descending)
+
+        Arguments:
+
+        reveresort - set to True to sort by descending order, or False to sort by ascending order"""
+        return self._reversesort
+    def _set_reversesort(self, val):
+        self._validate_option("reversesort", val)
+        self._reversesort = val
+    reversesort = property(_get_reversesort, _set_reversesort)
+
+    def _get_sort_key(self):
+        """Sorting key function, applied to data points before sorting
+
+        Arguments:
+
+        sort_key - a function which takes one argument and returns something to be sorted"""
+        return self._sort_key
+    def _set_sort_key(self, val):
+        self._validate_option("sort_key", val)
+        self._sort_key = val
+    sort_key = property(_get_sort_key, _set_sort_key)
+ 
+    def _get_header(self):
+        """Controls printing of table header with field names
+
+        Arguments:
+
+        header - print a header showing field names (True or False)"""
+        return self._header
+    def _set_header(self, val):
+        self._validate_option("header", val)
+        self._header = val
+    header = property(_get_header, _set_header)
+
+    def _get_header_style(self):
+        """Controls stylisation applied to field names in header
+
+        Arguments:
+
+        header_style - stylisation to apply to field names in header ("cap", "title", "upper", "lower" or None)"""
+        return self._header_style
+    def _set_header_style(self, val):
+        self._validate_header_style(val)
+        self._header_style = val
+    header_style = property(_get_header_style, _set_header_style)
+
+    def _get_border(self):
+        """Controls printing of border around table
+
+        Arguments:
+
+        border - print a border around the table (True or False)"""
+        return self._border
+    def _set_border(self, val):
+        self._validate_option("border", val)
+        self._border = val
+    border = property(_get_border, _set_border)
+
+    def _get_hrules(self):
+        """Controls printing of horizontal rules after rows
+
+        Arguments:
+
+        hrules - horizontal rules style.  Allowed values: FRAME, ALL, HEADER, NONE"""
+        return self._hrules
+    def _set_hrules(self, val):
+        self._validate_option("hrules", val)
+        self._hrules = val
+    hrules = property(_get_hrules, _set_hrules)
+
+    def _get_vrules(self):
+        """Controls printing of vertical rules between columns
+
+        Arguments:
+
+        vrules - vertical rules style.  Allowed values: FRAME, ALL, NONE"""
+        return self._vrules
+    def _set_vrules(self, val):
+        self._validate_option("vrules", val)
+        self._vrules = val
+    vrules = property(_get_vrules, _set_vrules)
+
+    def _get_int_format(self):
+        """Controls formatting of integer data
+        Arguments:
+
+        int_format - integer format string"""
+        return self._int_format
+    def _set_int_format(self, val):
+#        self._validate_option("int_format", val)
+        for field in self._field_names:
+            self._int_format[field] = val
+    int_format = property(_get_int_format, _set_int_format)
+
+    def _get_float_format(self):
+        """Controls formatting of floating point data
+        Arguments:
+
+        float_format - floating point format string"""
+        return self._float_format
+    def _set_float_format(self, val):
+#        self._validate_option("float_format", val)
+        for field in self._field_names:
+            self._float_format[field] = val
+    float_format = property(_get_float_format, _set_float_format)
+
+    def _get_padding_width(self):
+        """The number of empty spaces between a column's edge and its content
+
+        Arguments:
+
+        padding_width - number of spaces, must be a positive integer"""
+        return self._padding_width
+    def _set_padding_width(self, val):
+        self._validate_option("padding_width", val)
+        self._padding_width = val
+    padding_width = property(_get_padding_width, _set_padding_width)
+
+    def _get_left_padding_width(self):
+        """The number of empty spaces between a column's left edge and its content
+
+        Arguments:
+
+        left_padding - number of spaces, must be a positive integer"""
+        return self._left_padding_width
+    def _set_left_padding_width(self, val):
+        self._validate_option("left_padding_width", val)
+        self._left_padding_width = val
+    left_padding_width = property(_get_left_padding_width, _set_left_padding_width)
+
+    def _get_right_padding_width(self):
+        """The number of empty spaces between a column's right edge and its content
+
+        Arguments:
+
+        right_padding - number of spaces, must be a positive integer"""
+        return self._right_padding_width
+    def _set_right_padding_width(self, val):
+        self._validate_option("right_padding_width", val)
+        self._right_padding_width = val
+    right_padding_width = property(_get_right_padding_width, _set_right_padding_width)
+
+    def _get_vertical_char(self):
+        """The charcter used when printing table borders to draw vertical lines
+
+        Arguments:
+
+        vertical_char - single character string used to draw vertical lines"""
+        return self._vertical_char
+    def _set_vertical_char(self, val):
+        val = self._unicode(val)
+        self._validate_option("vertical_char", val)
+        self._vertical_char = val
+    vertical_char = property(_get_vertical_char, _set_vertical_char)
+
+    def _get_horizontal_char(self):
+        """The charcter used when printing table borders to draw horizontal lines
+
+        Arguments:
+
+        horizontal_char - single character string used to draw horizontal lines"""
+        return self._horizontal_char
+    def _set_horizontal_char(self, val):
+        val = self._unicode(val)
+        self._validate_option("horizontal_char", val)
+        self._horizontal_char = val
+    horizontal_char = property(_get_horizontal_char, _set_horizontal_char)
+
+    def _get_junction_char(self):
+        """The charcter used when printing table borders to draw line junctions
+
+        Arguments:
+
+        junction_char - single character string used to draw line junctions"""
+        return self._junction_char
+    def _set_junction_char(self, val):
+        val = self._unicode(val)
+        self._validate_option("vertical_char", val)
+        self._junction_char = val
+    junction_char = property(_get_junction_char, _set_junction_char)
+
+    def _get_format(self):
+        """Controls whether or not HTML tables are formatted to match styling options
+
+        Arguments:
+
+        format - True or False"""
+        return self._format
+    def _set_format(self, val):
+        self._validate_option("format", val)
+        self._format = val
+    format = property(_get_format, _set_format)
+
+    def _get_print_empty(self):
+        """Controls whether or not empty tables produce a header and frame or just an empty string
+
+        Arguments:
+
+        print_empty - True or False"""
+        return self._print_empty
+    def _set_print_empty(self, val):
+        self._validate_option("print_empty", val)
+        self._print_empty = val
+    print_empty = property(_get_print_empty, _set_print_empty)
+
+    def _get_attributes(self):
+        """A dictionary of HTML attribute name/value pairs to be included in the <table> tag when printing HTML
+
+        Arguments:
+
+        attributes - dictionary of attributes"""
+        return self._attributes
+    def _set_attributes(self, val):
+        self._validate_option("attributes", val)
+        self._attributes = val
+    attributes = property(_get_attributes, _set_attributes)
+
+    ##############################
+    # OPTION MIXER               #
+    ##############################
+
+    def _get_options(self, kwargs):
+
+        options = {}
+        for option in self._options:
+            if option in kwargs:
+                self._validate_option(option, kwargs[option])
+                options[option] = kwargs[option]
+            else:
+                options[option] = getattr(self, "_"+option)
+        return options
+
+    ##############################
+    # PRESET STYLE LOGIC         #
+    ##############################
+
+    def set_style(self, style):
+
+        if style == DEFAULT:
+            self._set_default_style()
+        elif style == MSWORD_FRIENDLY:
+            self._set_msword_style()
+        elif style == PLAIN_COLUMNS:
+            self._set_columns_style()
+        elif style == RANDOM:
+            self._set_random_style()
+        else:
+            raise Exception("Invalid pre-set style!")
+
+    def _set_default_style(self):
+
+        self.header = True
+        self.border = True
+        self._hrules = FRAME
+        self._vrules = ALL
+        self.padding_width = 1
+        self.left_padding_width = 1
+        self.right_padding_width = 1
+        self.vertical_char = "|"
+        self.horizontal_char = "-"
+        self.junction_char = "+"
+
+    def _set_msword_style(self):
+
+        self.header = True
+        self.border = True
+        self._hrules = NONE
+        self.padding_width = 1
+        self.left_padding_width = 1
+        self.right_padding_width = 1
+        self.vertical_char = "|"
+
+    def _set_columns_style(self):
+
+        self.header = True
+        self.border = False
+        self.padding_width = 1
+        self.left_padding_width = 0
+        self.right_padding_width = 8
+
+    def _set_random_style(self):
+
+        # Just for fun!
+        self.header = random.choice((True, False))
+        self.border = random.choice((True, False))
+        self._hrules = random.choice((ALL, FRAME, HEADER, NONE))
+        self._vrules = random.choice((ALL, FRAME, NONE))
+        self.left_padding_width = random.randint(0,5)
+        self.right_padding_width = random.randint(0,5)
+        self.vertical_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
+        self.horizontal_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
+        self.junction_char = random.choice("~!@#$%^&*()_+|-=\{}[];':\",./;<>?")
+
+    ##############################
+    # DATA INPUT METHODS         #
+    ##############################
+
+    def add_row(self, row):
+
+        """Add a row to the table
+
+        Arguments:
+
+        row - row of data, should be a list with as many elements as the table
+        has fields"""
+
+        if self._field_names and len(row) != len(self._field_names):
+            raise Exception("Row has incorrect number of values, (actual) %d!=%d (expected)" %(len(row),len(self._field_names)))
+        if not self._field_names:
+            self.field_names = [("Field %d" % (n+1)) for n in range(0,len(row))]
+        self._rows.append(list(row))
+
+    def del_row(self, row_index):
+
+        """Delete a row to the table
+
+        Arguments:
+
+        row_index - The index of the row you want to delete.  Indexing starts at 0."""
+
+        if row_index > len(self._rows)-1:
+            raise Exception("Cant delete row at index %d, table only has %d rows!" % (row_index, len(self._rows)))
+        del self._rows[row_index]
+
+    def add_column(self, fieldname, column, align="c", valign="t"):
+
+        """Add a column to the table.
+
+        Arguments:
+
+        fieldname - name of the field to contain the new column of data
+        column - column of data, should be a list with as many elements as the
+        table has rows
+        align - desired alignment for this column - "l" for left, "c" for centre and "r" for right
+        valign - desired vertical alignment for new columns - "t" for top, "m" for middle and "b" for bottom"""
+
+        if len(self._rows) in (0, len(column)):
+            self._validate_align(align)
+            self._validate_valign(valign)
+            self._field_names.append(fieldname)
+            self._align[fieldname] = align
+            self._valign[fieldname] = valign
+            for i in range(0, len(column)):
+                if len(self._rows) < i+1:
+                    self._rows.append([])
+                self._rows[i].append(column[i])
+        else:
+            raise Exception("Column length %d does not match number of rows %d!" % (len(column), len(self._rows)))
+
+    def clear_rows(self):
+
+        """Delete all rows from the table but keep the current field names"""
+
+        self._rows = []
+
+    def clear(self):
+
+        """Delete all rows and field names from the table, maintaining nothing but styling options"""
+
+        self._rows = []
+        self._field_names = []
+        self._widths = []
+
+    ##############################
+    # MISC PUBLIC METHODS        #
+    ##############################
+
+    def copy(self):
+        return copy.deepcopy(self)
+
+    ##############################
+    # MISC PRIVATE METHODS       #
+    ##############################
+
+    def _format_value(self, field, value):
+        if isinstance(value, int) and field in self._int_format:
+            value = self._unicode(("%%%sd" % self._int_format[field]) % value)
+        elif isinstance(value, float) and field in self._float_format:
+            value = self._unicode(("%%%sf" % self._float_format[field]) % value)
+        return self._unicode(value)
+
+    def _compute_widths(self, rows, options):
+        if options["header"]:
+            widths = [_get_size(field)[0] for field in self._field_names]
+        else:
+            widths = len(self.field_names) * [0]
+        for row in rows:
+            for index, value in enumerate(row):
+                fieldname = self.field_names[index]
+                if fieldname in self.max_width:
+                    widths[index] = max(widths[index], min(_get_size(value)[0], self.max_width[fieldname]))
+                else:
+                    widths[index] = max(widths[index], _get_size(value)[0])
+        self._widths = widths
+
+    def _get_padding_widths(self, options):
+
+        if options["left_padding_width"] is not None:
+            lpad = options["left_padding_width"]
+        else:
+            lpad = options["padding_width"]
+        if options["right_padding_width"] is not None:
+            rpad = options["right_padding_width"]
+        else:
+            rpad = options["padding_width"]
+        return lpad, rpad
+
+    def _get_rows(self, options):
+        """Return only those data rows that should be printed, based on slicing and sorting.
+
+        Arguments:
+
+        options - dictionary of option settings."""
+       
+        # Make a copy of only those rows in the slice range 
+        rows = copy.deepcopy(self._rows[options["start"]:options["end"]])
+        # Sort if necessary
+        if options["sortby"]:
+            sortindex = self._field_names.index(options["sortby"])
+            # Decorate
+            rows = [[row[sortindex]]+row for row in rows]
+            # Sort
+            rows.sort(reverse=options["reversesort"], key=options["sort_key"])
+            # Undecorate
+            rows = [row[1:] for row in rows]
+        return rows
+        
+    def _format_row(self, row, options):
+        return [self._format_value(field, value) for (field, value) in zip(self._field_names, row)]
+
+    def _format_rows(self, rows, options):
+        return [self._format_row(row, options) for row in rows]
+ 
+    ##############################
+    # PLAIN TEXT STRING METHODS  #
+    ##############################
+
+    def get_string(self, **kwargs):
+
+        """Return string representation of table in current state.
+
+        Arguments:
+
+        start - index of first data row to include in output
+        end - index of last data row to include in output PLUS ONE (list slice style)
+        fields - names of fields (columns) to include
+        header - print a header showing field names (True or False)
+        border - print a border around the table (True or False)
+        hrules - controls printing of horizontal rules after rows.  Allowed values: ALL, FRAME, HEADER, NONE
+        vrules - controls printing of vertical rules between columns.  Allowed values: FRAME, ALL, NONE
+        int_format - controls formatting of integer data
+        float_format - controls formatting of floating point data
+        padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
+        left_padding_width - number of spaces on left hand side of column data
+        right_padding_width - number of spaces on right hand side of column data
+        vertical_char - single character string used to draw vertical lines
+        horizontal_char - single character string used to draw horizontal lines
+        junction_char - single character string used to draw line junctions
+        sortby - name of field to sort rows by
+        sort_key - sorting key function, applied to data points before sorting
+        reversesort - True or False to sort in descending or ascending order
+        print empty - if True, stringify just the header for an empty table, if False return an empty string """
+
+        options = self._get_options(kwargs)
+
+        lines = []
+
+        # Don't think too hard about an empty table
+        # Is this the desired behaviour?  Maybe we should still print the header?
+        if self.rowcount == 0 and (not options["print_empty"] or not options["border"]):
+            return ""
+
+        # Get the rows we need to print, taking into account slicing, sorting, etc.
+        rows = self._get_rows(options)
+
+        # Turn all data in all rows into Unicode, formatted as desired
+        formatted_rows = self._format_rows(rows, options)
+
+        # Compute column widths
+        self._compute_widths(formatted_rows, options)
+
+        # Add header or top of border
+        self._hrule = self._stringify_hrule(options)
+        if options["header"]:
+            lines.append(self._stringify_header(options))
+        elif options["border"] and options["hrules"] in (ALL, FRAME):
+            lines.append(self._hrule)
+
+        # Add rows
+        for row in formatted_rows:
+            lines.append(self._stringify_row(row, options))
+
+        # Add bottom of border
+        if options["border"] and options["hrules"] == FRAME:
+            lines.append(self._hrule)
+        
+        return self._unicode("\n").join(lines)
+
+    def _stringify_hrule(self, options):
+
+        if not options["border"]:
+            return ""
+        lpad, rpad = self._get_padding_widths(options)
+        if options['vrules'] in (ALL, FRAME):
+            bits = [options["junction_char"]]
+        else:
+            bits = [options["horizontal_char"]]
+        # For tables with no data or fieldnames
+        if not self._field_names:
+                bits.append(options["junction_char"])
+                return "".join(bits)
+        for field, width in zip(self._field_names, self._widths):
+            if options["fields"] and field not in options["fields"]:
+                continue
+            bits.append((width+lpad+rpad)*options["horizontal_char"])
+            if options['vrules'] == ALL:
+                bits.append(options["junction_char"])
+            else:
+                bits.append(options["horizontal_char"])
+        if options["vrules"] == FRAME:
+            bits.pop()
+            bits.append(options["junction_char"])
+        return "".join(bits)
+
+    def _stringify_header(self, options):
+
+        bits = []
+        lpad, rpad = self._get_padding_widths(options)
+        if options["border"]:
+            if options["hrules"] in (ALL, FRAME):
+                bits.append(self._hrule)
+                bits.append("\n")
+            if options["vrules"] in (ALL, FRAME):
+                bits.append(options["vertical_char"])
+            else:
+                bits.append(" ")
+        # For tables with no data or field names
+        if not self._field_names:
+            if options["vrules"] in (ALL, FRAME):
+                bits.append(options["vertical_char"])
+            else:
+                bits.append(" ")
+        for field, width, in zip(self._field_names, self._widths):
+            if options["fields"] and field not in options["fields"]:
+                continue
+            if self._header_style == "cap":
+                fieldname = field.capitalize()
+            elif self._header_style == "title":
+                fieldname = field.title()
+            elif self._header_style == "upper":
+                fieldname = field.upper()
+            elif self._header_style == "lower":
+                fieldname = field.lower()
+            else:
+                fieldname = field
+            bits.append(" " * lpad + self._justify(fieldname, width, self._align[field]) + " " * rpad)
+            if options["border"]:
+                if options["vrules"] == ALL:
+                    bits.append(options["vertical_char"])
+                else:
+                    bits.append(" ")
+        # If vrules is FRAME, then we just appended a space at the end
+        # of the last field, when we really want a vertical character
+        if options["border"] and options["vrules"] == FRAME:
+            bits.pop()
+            bits.append(options["vertical_char"])
+        if options["border"] and options["hrules"] != NONE:
+            bits.append("\n")
+            bits.append(self._hrule)
+        return "".join(bits)
+
+    def _stringify_row(self, row, options):
+       
+        for index, field, value, width, in zip(range(0,len(row)), self._field_names, row, self._widths):
+            # Enforce max widths
+            lines = value.split("\n")
+            new_lines = []
+            for line in lines: 
+                if _str_block_width(line) > width:
+                    line = textwrap.fill(line, width)
+                new_lines.append(line)
+            lines = new_lines
+            value = "\n".join(lines)
+            row[index] = value
+
+        row_height = 0
+        for c in row:
+            h = _get_size(c)[1]
+            if h > row_height:
+                row_height = h
+
+        bits = []
+        lpad, rpad = self._get_padding_widths(options)
+        for y in range(0, row_height):
+            bits.append([])
+            if options["border"]:
+                if options["vrules"] in (ALL, FRAME):
+                    bits[y].append(self.vertical_char)
+                else:
+                    bits[y].append(" ")
+
+        for field, value, width, in zip(self._field_names, row, self._widths):
+
+            valign = self._valign[field]
+            lines = value.split("\n")
+            dHeight = row_height - len(lines)
+            if dHeight:
+                if valign == "m":
+                  lines = [""] * int(dHeight / 2) + lines + [""] * (dHeight - int(dHeight / 2))
+                elif valign == "b":
+                  lines = [""] * dHeight + lines
+                else:
+                  lines = lines + [""] * dHeight
+
+            y = 0
+            for l in lines:
+                if options["fields"] and field not in options["fields"]:
+                    continue
+
+                bits[y].append(" " * lpad + self._justify(l, width, self._align[field]) + " " * rpad)
+                if options["border"]:
+                    if options["vrules"] == ALL:
+                        bits[y].append(self.vertical_char)
+                    else:
+                        bits[y].append(" ")
+                y += 1
+
+        # If vrules is FRAME, then we just appended a space at the end
+        # of the last field, when we really want a vertical character
+        for y in range(0, row_height):
+            if options["border"] and options["vrules"] == FRAME:
+                bits[y].pop()
+                bits[y].append(options["vertical_char"])
+        
+        if options["border"] and options["hrules"]== ALL:
+            bits[row_height-1].append("\n")
+            bits[row_height-1].append(self._hrule)
+
+        for y in range(0, row_height):
+            bits[y] = "".join(bits[y])
+
+        return "\n".join(bits)
+
+    ##############################
+    # HTML STRING METHODS        #
+    ##############################
+
+    def get_html_string(self, **kwargs):
+
+        """Return string representation of HTML formatted version of table in current state.
+
+        Arguments:
+
+        start - index of first data row to include in output
+        end - index of last data row to include in output PLUS ONE (list slice style)
+        fields - names of fields (columns) to include
+        header - print a header showing field names (True or False)
+        border - print a border around the table (True or False)
+        hrules - controls printing of horizontal rules after rows.  Allowed values: ALL, FRAME, HEADER, NONE
+        vrules - controls printing of vertical rules between columns.  Allowed values: FRAME, ALL, NONE
+        int_format - controls formatting of integer data
+        float_format - controls formatting of floating point data
+        padding_width - number of spaces on either side of column data (only used if left and right paddings are None)
+        left_padding_width - number of spaces on left hand side of column data
+        right_padding_width - number of spaces on right hand side of column data
+        sortby - name of field to sort rows by
+        sort_key - sorting key function, applied to data points before sorting
+        attributes - dictionary of name/value pairs to include as HTML attributes in the <table> tag
+        xhtml - print <br/> tags if True, <br> tags if false"""
+
+        options = self._get_options(kwargs)
+
+        if options["format"]:
+            string = self._get_formatted_html_string(options)
+        else:
+            string = self._get_simple_html_string(options)
+
+        return string
+
+    def _get_simple_html_string(self, options):
+
+        lines = []
+        if options["xhtml"]:
+            linebreak = "<br/>"
+        else:
+            linebreak = "<br>"
+
+        open_tag = []
+        open_tag.append("<table")
+        if options["attributes"]:
+            for attr_name in options["attributes"]:
+                open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
+        open_tag.append(">")
+        lines.append("".join(open_tag))
+
+        # Headers
+        if options["header"]:
+            lines.append("    <tr>")
+            for field in self._field_names:
+                if options["fields"] and field not in options["fields"]:
+                    continue
+                lines.append("        <th>%s</th>" % escape(field).replace("\n", linebreak))
+            lines.append("    </tr>")
+
+        # Data
+        rows = self._get_rows(options)
+        formatted_rows = self._format_rows(rows, options)
+        for row in formatted_rows:
+            lines.append("    <tr>")
+            for field, datum in zip(self._field_names, row):
+                if options["fields"] and field not in options["fields"]:
+                    continue
+                lines.append("        <td>%s</td>" % escape(datum).replace("\n", linebreak))
+            lines.append("    </tr>")
+
+        lines.append("</table>")
+
+        return self._unicode("\n").join(lines)
+
+    def _get_formatted_html_string(self, options):
+
+        lines = []
+        lpad, rpad = self._get_padding_widths(options)
+        if options["xhtml"]:
+            linebreak = "<br/>"
+        else:
+            linebreak = "<br>"
+
+        open_tag = []
+        open_tag.append("<table")
+        if options["border"]:
+            if options["hrules"] == ALL and options["vrules"] == ALL:
+                open_tag.append(" frame=\"box\" rules=\"all\"")
+            elif options["hrules"] == FRAME and options["vrules"] == FRAME:
+                open_tag.append(" frame=\"box\"")
+            elif options["hrules"] == FRAME and options["vrules"] == ALL:
+                open_tag.append(" frame=\"box\" rules=\"cols\"")
+            elif options["hrules"] == FRAME:
+                open_tag.append(" frame=\"hsides\"")
+            elif options["hrules"] == ALL:
+                open_tag.append(" frame=\"hsides\" rules=\"rows\"")
+            elif options["vrules"] == FRAME:
+                open_tag.append(" frame=\"vsides\"")
+            elif options["vrules"] == ALL:
+                open_tag.append(" frame=\"vsides\" rules=\"cols\"")
+        if options["attributes"]:
+            for attr_name in options["attributes"]:
+                open_tag.append(" %s=\"%s\"" % (attr_name, options["attributes"][attr_name]))
+        open_tag.append(">")
+        lines.append("".join(open_tag))
+
+        # Headers
+        if options["header"]:
+            lines.append("    <tr>")
+            for field in self._field_names:
+                if options["fields"] and field not in options["fields"]:
+                    continue
+                lines.append("        <th style=\"padding-left: %dem; padding-right: %dem; text-align: center\">%s</th>" % (lpad, rpad, escape(field).replace("\n", linebreak)))
+            lines.append("    </tr>")
+
+        # Data
+        rows = self._get_rows(options)
+        formatted_rows = self._format_rows(rows, options)
+        aligns = []
+        valigns = []
+        for field in self._field_names:
+            aligns.append({ "l" : "left", "r" : "right", "c" : "center" }[self._align[field]])
+            valigns.append({"t" : "top", "m" : "middle", "b" : "bottom"}[self._valign[field]])
+        for row in formatted_rows:
+            lines.append("    <tr>")
+            for field, datum, align, valign in zip(self._field_names, row, aligns, valigns):
+                if options["fields"] and field not in options["fields"]:
+                    continue
+                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)))
+            lines.append("    </tr>")
+        lines.append("</table>")
+
+        return self._unicode("\n").join(lines)
+
+##############################
+# UNICODE WIDTH FUNCTIONS    #
+##############################
+
+def _char_block_width(char):
+    # Basic Latin, which is probably the most common case
+    #if char in xrange(0x0021, 0x007e):
+    #if char >= 0x0021 and char <= 0x007e:
+    if 0x0021 <= char <= 0x007e:
+        return 1
+    # Chinese, Japanese, Korean (common)
+    if 0x4e00 <= char <= 0x9fff:
+        return 2
+    # Hangul
+    if 0xac00 <= char <= 0xd7af:
+        return 2
+    # Combining?
+    if unicodedata.combining(uni_chr(char)):
+        return 0
+    # Hiragana and Katakana
+    if 0x3040 <= char <= 0x309f or 0x30a0 <= char <= 0x30ff:
+        return 2
+    # Full-width Latin characters
+    if 0xff01 <= char <= 0xff60:
+        return 2
+    # CJK punctuation
+    if 0x3000 <= char <= 0x303e:
+        return 2
+    # Backspace and delete
+    if char in (0x0008, 0x007f):
+        return -1
+    # Other control characters
+    elif char in (0x0000, 0x001f):
+        return 0
+    # Take a guess
+    return 1
+
+def _str_block_width(val):
+
+    return sum(itermap(_char_block_width, itermap(ord, _re.sub("", val))))
+
+##############################
+# TABLE FACTORIES            #
+##############################
+
+def from_csv(fp, field_names = None, **kwargs):
+
+    dialect = csv.Sniffer().sniff(fp.read(1024))
+    fp.seek(0)
+    reader = csv.reader(fp, dialect)
+
+    table = PrettyTable(**kwargs)
+    if field_names:
+        table.field_names = field_names
+    else:
+        if py3k:
+            table.field_names = [x.strip() for x in next(reader)]
+        else:
+            table.field_names = [x.strip() for x in reader.next()]
+
+    for row in reader:
+        table.add_row([x.strip() for x in row])
+
+    return table
+
+def from_db_cursor(cursor, **kwargs):
+
+    if cursor.description:
+        table = PrettyTable(**kwargs)
+        table.field_names = [col[0] for col in cursor.description]
+        for row in cursor.fetchall():
+            table.add_row(row)
+        return table
+
+class TableHandler(HTMLParser):
+
+    def __init__(self, **kwargs):
+        HTMLParser.__init__(self)
+        self.kwargs = kwargs
+        self.tables = []
+        self.last_row = []
+        self.rows = []
+        self.max_row_width = 0
+        self.active = None
+        self.last_content = ""
+        self.is_last_row_header = False
+
+    def handle_starttag(self,tag, attrs):
+        self.active = tag
+        if tag == "th":
+            self.is_last_row_header = True
+
+    def handle_endtag(self,tag):
+        if tag in ["th", "td"]:
+            stripped_content = self.last_content.strip()
+            self.last_row.append(stripped_content)
+        if tag == "tr":
+            self.rows.append(
+                (self.last_row, self.is_last_row_header))
+            self.max_row_width = max(self.max_row_width, len(self.last_row))
+            self.last_row = []
+            self.is_last_row_header = False
+        if tag == "table":
+            table = self.generate_table(self.rows)
+            self.tables.append(table)
+            self.rows = []
+        self.last_content = " "
+        self.active = None
+
+
+    def handle_data(self, data):
+        self.last_content += data
+
+    def generate_table(self, rows):
+        """
+        Generates from a list of rows a PrettyTable object.
+        """
+        table = PrettyTable(**self.kwargs)
+        for row in self.rows:
+            if len(row[0]) < self.max_row_width:
+                appends = self.max_row_width - len(row[0])
+                for i in range(1,appends):
+                    row[0].append("-")
+
+            if row[1] == True:
+                self.make_fields_unique(row[0])
+                table.field_names = row[0]
+            else:
+                table.add_row(row[0])
+        return table
+
+    def make_fields_unique(self, fields):
+        """
+        iterates over the row and make each field unique
+        """
+        for i in range(0, len(fields)):
+            for j in range(i+1, len(fields)):
+                if fields[i] == fields[j]:
+                    fields[j] += "'"
+
+def from_html(html_code, **kwargs):
+    """
+    Generates a list of PrettyTables from a string of HTML code. Each <table> in
+    the HTML becomes one PrettyTable object.
+    """
+
+    parser = TableHandler(**kwargs)
+    parser.feed(html_code)
+    return parser.tables
+
+def from_html_one(html_code, **kwargs):
+    """
+    Generates a PrettyTables from a string of HTML code which contains only a
+    single <table>
+    """
+
+    tables = from_html(html_code, **kwargs)
+    try:
+        assert len(tables) == 1
+    except AssertionError:
+        raise Exception("More than one <table> in provided HTML code!  Use from_html instead.")
+    return tables[0]
+
+##############################
+# MAIN (TEST FUNCTION)       #
+##############################
+
+def main():
+
+    x = PrettyTable(["City name", "Area", "Population", "Annual Rainfall"])
+    x.sortby = "Population"
+    x.reversesort = True
+    x.int_format["Area"] = "04d"
+    x.float_format = "6.1f"
+    x.align["City name"] = "l" # Left align city names
+    x.add_row(["Adelaide", 1295, 1158259, 600.5])
+    x.add_row(["Brisbane", 5905, 1857594, 1146.4])
+    x.add_row(["Darwin", 112, 120900, 1714.7])
+    x.add_row(["Hobart", 1357, 205556, 619.5])
+    x.add_row(["Sydney", 2058, 4336374, 1214.8])
+    x.add_row(["Melbourne", 1566, 3806092, 646.9])
+    x.add_row(["Perth", 5386, 1554769, 869.4])
+    print(x)
+    
+if __name__ == "__main__":
+    main()