diff options
author | 2015-11-26 13:06:36 +0200 | |
---|---|---|
committer | 2015-11-26 13:06:36 +0200 | |
commit | 91f6c24f45cbb0cbf8568a9938059a1a934e6ae6 (patch) | |
tree | 0977d1129173d2b2be8e36c91aa5b7ec97b035a1 /scripts | |
parent | e7cb8b0f6c2fbe08d2086a7408040ac7d12aee5a (diff) |
Initial implementation of stats prompting
Diffstat (limited to 'scripts')
14 files changed, 1347 insertions, 44 deletions
diff --git a/scripts/automation/trex_control_plane/client/trex_async_client.py b/scripts/automation/trex_control_plane/client/trex_async_client.py index adb91d97..12c89c1a 100644 --- a/scripts/automation/trex_control_plane/client/trex_async_client.py +++ b/scripts/automation/trex_control_plane/client/trex_async_client.py @@ -21,13 +21,14 @@ from common.trex_stats import * from common.trex_streams import * # basic async stats class -class TrexAsyncStats(object): +class CTRexAsyncStats(object): def __init__ (self): self.ref_point = None self.current = {} self.last_update_ts = datetime.datetime.now() - def __format_num (self, size, suffix = ""): + @staticmethod + def format_num (size, suffix = ""): for unit in ['','K','M','G','T','P']: if abs(size) < 1000.0: @@ -47,7 +48,7 @@ class TrexAsyncStats(object): self.ref_point = self.current - def get (self, field, format = False, suffix = ""): + def get(self, field, format = False, suffix = ""): if not field in self.current: return "N/A" @@ -55,7 +56,7 @@ class TrexAsyncStats(object): if not format: return self.current[field] else: - return self.__format_num(self.current[field], suffix) + return self.format_num(self.current[field], suffix) def get_rel (self, field, format = False, suffix = ""): @@ -65,7 +66,7 @@ class TrexAsyncStats(object): if not format: return (self.current[field] - self.ref_point[field]) else: - return self.__format_num(self.current[field] - self.ref_point[field], suffix) + return self.format_num(self.current[field] - self.ref_point[field], suffix) # return true if new data has arrived in the past 2 seconds @@ -74,28 +75,28 @@ class TrexAsyncStats(object): return (delta_ms < 2000) # describes the general stats provided by TRex -class TrexAsyncStatsGeneral(TrexAsyncStats): +class CTRexAsyncStatsGeneral(CTRexAsyncStats): def __init__ (self): - super(TrexAsyncStatsGeneral, self).__init__() + super(CTRexAsyncStatsGeneral, self).__init__() # per port stats -class TrexAsyncStatsPort(TrexAsyncStats): +class CTRexAsyncStatsPort(CTRexAsyncStats): def __init__ (self): - super(TrexAsyncStatsPort, self).__init__() + super(CTRexAsyncStatsPort, self).__init__() def get_stream_stats (self, stream_id): return None # stats manager -class TrexAsyncStatsManager(): +class CTRexAsyncStatsManager(): def __init__ (self): - self.general_stats = TrexAsyncStatsGeneral() + self.general_stats = CTRexAsyncStatsGeneral() self.port_stats = {} - def get_general_stats (self): + def get_general_stats(self): return self.general_stats def get_port_stats (self, port_id): @@ -106,10 +107,10 @@ class TrexAsyncStatsManager(): return self.port_stats[str(port_id)] - def update (self, data): + def update(self, data): self.__handle_snapshot(data) - def __handle_snapshot (self, snapshot): + def __handle_snapshot(self, snapshot): general_stats = {} port_stats = {} @@ -140,7 +141,7 @@ class TrexAsyncStatsManager(): for port_id, data in port_stats.iteritems(): if not port_id in self.port_stats: - self.port_stats[port_id] = TrexAsyncStatsPort() + self.port_stats[port_id] = CTRexAsyncStatsPort() self.port_stats[port_id].update(data) @@ -157,22 +158,20 @@ class CTRexAsyncClient(): self.raw_snapshot = {} - self.stats = TrexAsyncStatsManager() + self.stats = CTRexAsyncStatsManager() self.tr = "tcp://{0}:{1}".format(self.server, self.port) print "\nConnecting To ZMQ Publisher At {0}".format(self.tr) self.active = True - self.t = threading.Thread(target = self.run) + self.t = threading.Thread(target= self.run) # kill this thread on exit and don't add it to the join list self.t.setDaemon(True) self.t.start() - - - def run (self): + def run(self): # Socket to talk to server self.context = zmq.Context() @@ -182,7 +181,7 @@ class CTRexAsyncClient(): self.socket.setsockopt(zmq.SUBSCRIBE, '') while self.active: - line = self.socket.recv_string(); + line = self.socket.recv_string() msg = json.loads(line) name = msg['name'] @@ -192,15 +191,13 @@ class CTRexAsyncClient(): self.__dispatch(name, type, data) - - def get_stats (self): + def get_stats(self): return self.stats def get_raw_snapshot (self): #return str(self.stats.global_stats.get('m_total_tx_bytes')) + " / " + str(self.stats.global_stats.get_rel('m_total_tx_bytes')) return self.raw_snapshot - # dispatch the message to the right place def __dispatch (self, name, type, data): # stats @@ -225,3 +222,6 @@ class CTRexAsyncClient(): self.active = False self.t.join() + +if __name__ == "__main__": + pass
\ No newline at end of file diff --git a/scripts/automation/trex_control_plane/client/trex_stateless_client.py b/scripts/automation/trex_control_plane/client/trex_stateless_client.py index 7bcbf2c7..4cb70483 100755 --- a/scripts/automation/trex_control_plane/client/trex_stateless_client.py +++ b/scripts/automation/trex_control_plane/client/trex_stateless_client.py @@ -10,11 +10,13 @@ except ImportError: from client_utils.jsonrpc_client import JsonRpcClient, BatchMessage from client_utils.packet_builder import CTRexPktBuilder import json -from common.trex_stats import * + from common.trex_streams import * from collections import namedtuple from common.text_opts import * -import parsing_opts +# import trex_stats +from common import trex_stats +from client_utils import parsing_opts, text_tables import time from trex_async_client import CTRexAsyncClient @@ -29,7 +31,7 @@ class RpcResponseStatus(namedtuple('RpcResponseStatus', ['success', 'id', 'msg'] stat="success" if self.success else "fail") # simple class to represent complex return value -class RC: +class RC(): def __init__ (self, rc = None, data = None): self.rc_list = [] @@ -74,7 +76,7 @@ class RC: def RC_OK(): return RC(True, "") -def RC_ERR (err): +def RC_ERR(err): return RC(False, err) @@ -86,7 +88,7 @@ class CStreamsDB(object): def __init__(self): self.stream_packs = {} - def load_yaml_file (self, filename): + def load_yaml_file(self, filename): stream_pack_name = filename if stream_pack_name in self.get_loaded_streams_names(): @@ -376,6 +378,7 @@ class CTRexStatelessClient(object): def __init__(self, username, server="localhost", sync_port = 5050, async_port = 4500, virtual=False): super(CTRexStatelessClient, self).__init__() self.user = username + self.system_info = None self.comm_link = CTRexStatelessClient.CCommLink(server, sync_port, virtual) self.verbose = False self.ports = [] @@ -388,6 +391,11 @@ class CTRexStatelessClient(object): self._async_client = CTRexAsyncClient(server, async_port, self) self.streams_db = CStreamsDB() + self.info_and_stats = trex_stats.CTRexInformationCenter({"server": server, + "sync_port": sync_port, + "async_port": async_port}, + self.ports, + self.get_stats_async()) self.connected = False @@ -444,13 +452,15 @@ class CTRexStatelessClient(object): return RC_ERR(data) self.server_version = data + self.info_and_stats.server_version = data # cache system info + # self.get_system_info(refresh=True) rc, data = self.transmit("get_system_info") if not rc: return RC_ERR(data) - self.system_info = data + self.info_and_stats.system_info = data # cache supported commands rc, data = self.transmit("get_supported_cmds") @@ -508,7 +518,7 @@ class CTRexStatelessClient(object): else: return port_ids - def get_stats_async (self): + def get_stats_async(self): return self._async_client.get_stats() def get_connection_port (self): @@ -548,6 +558,9 @@ class CTRexStatelessClient(object): return RC_OK() + def get_global_stats(self): + rc, info = self.transmit("get_global_stats") + return RC(rc, info) ########## port commands ############## @@ -787,7 +800,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") return self.cmd_pause(opts.ports) @@ -820,7 +833,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") return self.cmd_resume(opts.ports) @@ -861,6 +874,18 @@ class CTRexStatelessClient(object): return RC_OK() + def cmd_stats(self, port_id_list, stats_mask=set()): + print port_id_list + print stats_mask + stats_opts = trex_stats.ALL_STATS_OPTS.intersection(stats_mask) + print stats_opts + + stats_obj = {} + for stats_type in stats_opts: + stats_obj.update(self.info_and_stats.generate_single_statistic(stats_type)) + return stats_obj + pass + ############## High Level API With Parser ################ def cmd_start_line (self, line): '''Start selected traffic in specified ports on TRex\n''' @@ -877,10 +902,10 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") if opts.db: - stream_list = self.stream_db.get_stream_pack(opts.db) + stream_list = self.streams_db.get_stream_pack(opts.db) rc = RC(stream_list != None) rc.annotate("Load stream pack (from DB):") if rc.bad(): @@ -906,7 +931,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") return self.cmd_stop(opts.ports) @@ -915,6 +940,49 @@ class CTRexStatelessClient(object): return self.cmd_reset() + def cmd_stats_line (self, line): + '''Fetch statistics from TRex server by port\n''' + # define a parser + parser = parsing_opts.gen_parser(self, + "stats", + self.cmd_stats_line.__doc__, + parsing_opts.PORT_LIST_WITH_ALL, + parsing_opts.STATS_MASK) + + opts = parser.parse_args(line.split()) + + if opts is None: + return RC_ERR("bad command line parameters") + + print opts + print self.get_global_stats() + # determine stats mask + mask = self._get_mask_keys(**self._filter_namespace_args(opts, ['p', 'g', 'ps'])) + # get stats objects, as dictionary + stats = self.cmd_stats(opts.ports, mask) + # print stats to screen + for stat_type, stat_data in stats.iteritems(): + text_tables.print_table_with_header(stat_data.text_table, stat_type) + return + + # if opts.db: + # stream_list = self.streams_db.get_stream_pack(opts.db) + # rc = RC(stream_list != None) + # rc.annotate("Load stream pack (from DB):") + # if rc.bad(): + # return RC_ERR("Failed to load stream pack") + # + # else: + # # load streams from file + # stream_list = self.streams_db.load_yaml_file(opts.file[0]) + # rc = RC(stream_list != None) + # rc.annotate("Load stream pack (from file):") + # if stream_list == None: + # return RC_ERR("Failed to load stream pack") + # + # + # return self.cmd_start(opts.ports, stream_list, opts.mult, opts.force, opts.duration) + def cmd_exit_line (self, line): print format_text("Exiting\n", 'bold') # a way to exit @@ -931,7 +999,7 @@ class CTRexStatelessClient(object): opts = parser.parse_args(line.split()) if opts is None: - return RC_ERR("bad command line paramters") + return RC_ERR("bad command line parameters") delay_sec = opts.duration if (opts.duration > 0) else 1 @@ -991,6 +1059,20 @@ class CTRexStatelessClient(object): return True ################################# + # ------ private methods ------ # + @staticmethod + def _get_mask_keys(ok_values={True}, **kwargs): + masked_keys = set() + for key, val in kwargs.iteritems(): + if val in ok_values: + masked_keys.add(key) + return masked_keys + + @staticmethod + def _filter_namespace_args(namespace, ok_values): + return {k: v for k, v in namespace.__dict__.items() if k in ok_values} + + ################################# # ------ private classes ------ # class CCommLink(object): """describes the connectivity of the stateless client method""" diff --git a/scripts/automation/trex_control_plane/client_utils/external_packages.py b/scripts/automation/trex_control_plane/client_utils/external_packages.py index e2bb37a5..3c6eb449 100755 --- a/scripts/automation/trex_control_plane/client_utils/external_packages.py +++ b/scripts/automation/trex_control_plane/client_utils/external_packages.py @@ -9,7 +9,8 @@ PATH_TO_PYTHON_LIB = os.path.abspath(os.path.join(ROOT_PATH, os.pardir, os.pard CLIENT_UTILS_MODULES = ['zmq', 'dpkt-1.8.6', - 'PyYAML-3.01/lib' + 'PyYAML-3.01/lib', + 'texttable-0.8.4' ] def import_client_utils_modules(): diff --git a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py index b826f02f..dd208da4 100755 --- a/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py +++ b/scripts/automation/trex_control_plane/client_utils/jsonrpc_client.py @@ -174,7 +174,7 @@ class JsonRpcClient(object): def process_single_response (self, response_json): if (response_json.get("jsonrpc") != "2.0"): - return False, "Malfromed Response ({0})".format(str(response)) + return False, "Malformed Response ({0})".format(str(response_json)) # error reported by server if ("error" in response_json): @@ -185,7 +185,7 @@ class JsonRpcClient(object): # if no error there should be a result if ("result" not in response_json): - return False, "Malformed Response ({0})".format(str(response)) + return False, "Malformed Response ({0})".format(str(response_json)) return True, response_json["result"] diff --git a/scripts/automation/trex_control_plane/console/parsing_opts.py b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py index d5c21af0..c110983b 100755 --- a/scripts/automation/trex_control_plane/console/parsing_opts.py +++ b/scripts/automation/trex_control_plane/client_utils/parsing_opts.py @@ -19,6 +19,10 @@ SERVER_IP = 7 STREAM_FROM_PATH_OR_FILE = 8 DURATION = 9 FORCE = 10 +GLOBAL_STATS = 11 +PORT_STATS = 12 +PORT_STATUS = 13 +STATS_MASK = 14 # list of ArgumentGroup types MUTEX = 1 @@ -116,13 +120,29 @@ OPTIONS_DB = {MULTIPLIER: ArgumentPack(['-m', '--multiplier'], {'metavar': 'SERVER', 'help': "server IP"}), + GLOBAL_STATS: ArgumentPack(['-g'], + {'action': 'store_true', + 'help': "Fetch only global statistics"}), + + PORT_STATS: ArgumentPack(['-p'], + {'action': 'store_true', + 'help': "Fetch only port statistics"}), + + PORT_STATUS: ArgumentPack(['--ps'], + {'action': 'store_true', + 'help': "Fetch only port status data"}), + # advanced options PORT_LIST_WITH_ALL: ArgumentGroup(MUTEX, [PORT_LIST, ALL_PORTS], {'required': True}), STREAM_FROM_PATH_OR_FILE: ArgumentGroup(MUTEX, [FILE_PATH, FILE_FROM_DB], - {'required': True}) + {'required': True}), + STATS_MASK: ArgumentGroup(MUTEX, [GLOBAL_STATS, + PORT_STATS, + PORT_STATUS], + {}) } diff --git a/scripts/automation/trex_control_plane/client_utils/text_tables.py b/scripts/automation/trex_control_plane/client_utils/text_tables.py new file mode 100644 index 00000000..2debca38 --- /dev/null +++ b/scripts/automation/trex_control_plane/client_utils/text_tables.py @@ -0,0 +1,34 @@ + +import external_packages +from texttable import Texttable +from common.text_opts import format_text + +class TRexTextTable(Texttable): + + def __init__(self): + Texttable.__init__(self) + # set class attributes so that it'll be more like TRex standard output + self.set_chars(['-', '|', '-', '-']) + self.set_deco(Texttable.HEADER | Texttable.VLINES) + +class TRexTextInfo(Texttable): + + def __init__(self): + Texttable.__init__(self) + # set class attributes so that it'll be more like TRex standard output + self.set_chars(['-', ':', '-', '-']) + self.set_deco(Texttable.VLINES) + +def generate_trex_stats_table(): + pass + +def print_table_with_header(texttable_obj, header=""): + header = header.replace("_", " ").title() + print format_text(header, 'cyan', 'underline') + "\n" + print texttable_obj.draw() + "\n" + + pass + +if __name__ == "__main__": + pass + diff --git a/scripts/automation/trex_control_plane/common/trex_stats.py b/scripts/automation/trex_control_plane/common/trex_stats.py index b7e768c1..bf5ba2bb 100755 --- a/scripts/automation/trex_control_plane/common/trex_stats.py +++ b/scripts/automation/trex_control_plane/common/trex_stats.py @@ -1,6 +1,86 @@ #!/router/bin/python +from collections import namedtuple, OrderedDict +from client_utils import text_tables import copy +GLOBAL_STATS = 'g' +PORT_STATS = 'p' +PORT_STATUS = 'ps' +ALL_STATS_OPTS = {GLOBAL_STATS, PORT_STATS, PORT_STATUS} +ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table']) + + +class CTRexInformationCenter(object): + + def __init__(self, connection_info, ports_ref, async_stats_ref): + self.connection_info = connection_info + self.server_version = None + self.system_info = None + self._ports = ports_ref + self._async_stats = async_stats_ref + + # def __getitem__(self, item): + # stats_obj = getattr(self, item) + # if stats_obj: + # return stats_obj.get_stats() + # else: + # return None + + def generate_single_statistic(self, statistic_type): + if statistic_type == GLOBAL_STATS: + return self._generate_global_stats() + elif statistic_type == PORT_STATS: + # return generate_global_stats() + pass + elif statistic_type == PORT_STATUS: + pass + else: + # ignore by returning empty object + return {} + + def _generate_global_stats(self): + stats_obj = self._async_stats.get_general_stats() + return_stats_data = \ + OrderedDict([("connection", "{host}, Port {port}".format(host=self.connection_info.get("server"), + port=self.connection_info.get("sync_port"))), + ("version", self.server_version.get("version", "N/A")), + ("cpu_util", stats_obj.get("m_cpu_util")), + ("total_tx", stats_obj.get("m_tx_bps", format=True, suffix="b/sec")), + # {'m_tx_bps': stats_obj.get("m_tx_bps", format= True, suffix= "b/sec"), + # 'm_tx_pps': stats_obj.get("m_tx_pps", format= True, suffix= "pkt/sec"), + # 'm_total_tx_bytes':stats_obj.get_rel("m_total_tx_bytes", + # format= True, + # suffix = "B"), + # 'm_total_tx_pkts': stats_obj.get_rel("m_total_tx_pkts", + # format= True, + # suffix = "pkts")}, + ("total_rx", stats_obj.get("m_rx_bps", format=True, suffix="b/sec")), + # {'m_rx_bps': stats_obj.get("m_rx_bps", format= True, suffix= "b/sec"), + # 'm_rx_pps': stats_obj.get("m_rx_pps", format= True, suffix= "pkt/sec"), + # 'm_total_rx_bytes': stats_obj.get_rel("m_total_rx_bytes", + # format= True, + # suffix = "B"), + # 'm_total_rx_pkts': stats_obj.get_rel("m_total_rx_pkts", + # format= True, + # suffix = "pkts")}, + ("total_pps", stats_obj.format_num(stats_obj.get("m_tx_pps") + stats_obj.get("m_rx_pps"), + suffix="pkt/sec")), + ("total_streams", sum([len(port.streams) + for port in self._ports])), + ("active_ports", sum([port.is_active() + for port in self._ports])) + ] + ) + + # build table representation + stats_table = text_tables.TRexTextInfo() + stats_table.set_cols_align(["l", "l"]) + stats_table.add_rows([[k.replace("_", " ").title(), v] + for k, v in return_stats_data.iteritems()], + header=False) + + return {"global_statistics": ExportableStats(return_stats_data, stats_table)} + class CTRexStatsManager(object): diff --git a/scripts/automation/trex_control_plane/console/trex_console.py b/scripts/automation/trex_control_plane/console/trex_console.py index c03f2a82..7d4f3c27 100755 --- a/scripts/automation/trex_control_plane/console/trex_console.py +++ b/scripts/automation/trex_control_plane/console/trex_console.py @@ -33,8 +33,9 @@ from common.trex_streams import * from client.trex_stateless_client import CTRexStatelessClient from common.text_opts import * from client_utils.general_utils import user_input, get_current_user +from client_utils import parsing_opts import trex_status -import parsing_opts + __version__ = "1.1" @@ -283,6 +284,14 @@ class TRexConsole(TRexGeneralCmd): '''force stop all ports\n''' self.stateless_client.cmd_reset() + def do_stats(self, line): + '''Fetch statistics from TRex server by port\n''' + self.stateless_client.cmd_stats_line(line) + pass + + def help_stats(self): + self.do_stats("-h") + # tui def do_tui (self, line): diff --git a/scripts/automation/trex_control_plane/console/trex_status.py b/scripts/automation/trex_control_plane/console/trex_status.py index 869812a1..10ac75c9 100644 --- a/scripts/automation/trex_control_plane/console/trex_status.py +++ b/scripts/automation/trex_control_plane/console/trex_status.py @@ -385,7 +385,7 @@ class TrexStatusCommands(): # # # -class TrexStatus(): +class CTRexStatus(): def __init__ (self, stdscr, stateless_client): self.stdscr = stdscr @@ -506,7 +506,7 @@ def show_trex_status_internal (stdscr, stateless_client): global trex_status if trex_status == None: - trex_status = TrexStatus(stdscr, stateless_client) + trex_status = CTRexStatus(stdscr, stateless_client) trex_status.run() diff --git a/scripts/external_libs/texttable-0.8.4/LICENSE b/scripts/external_libs/texttable-0.8.4/LICENSE new file mode 100644 index 00000000..65c5ca88 --- /dev/null +++ b/scripts/external_libs/texttable-0.8.4/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/scripts/external_libs/texttable-0.8.4/PKG-INFO b/scripts/external_libs/texttable-0.8.4/PKG-INFO new file mode 100644 index 00000000..a3079ad9 --- /dev/null +++ b/scripts/external_libs/texttable-0.8.4/PKG-INFO @@ -0,0 +1,23 @@ +Metadata-Version: 1.0 +Name: texttable +Version: 0.8.4 +Summary: module for creating simple ASCII tables +Home-page: https://github.com/foutaise/texttable/ +Author: Gerome Fournier +Author-email: jef(at)foutaise.org +License: LGPL +Download-URL: https://github.com/foutaise/texttable/archive/v0.8.4.tar.gz +Description: texttable is a module to generate a formatted text table, using ASCII + characters. +Platform: any +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: Intended Audience :: Developers +Classifier: Intended Audience :: End Users/Desktop +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: POSIX +Classifier: Operating System :: MacOS +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Classifier: Topic :: Text Processing +Classifier: Topic :: Utilities diff --git a/scripts/external_libs/texttable-0.8.4/README.md b/scripts/external_libs/texttable-0.8.4/README.md new file mode 100644 index 00000000..0d9895e8 --- /dev/null +++ b/scripts/external_libs/texttable-0.8.4/README.md @@ -0,0 +1,223 @@ +# texttable + +Python module for creating simple ASCII tables + +## Availability + +This module is available on [PypI](https://pypi.python.org/pypi/texttable/0.8.4), and has been packaged for several Linux/Unix platforms +([Debian](https://packages.debian.org/search?&searchon=names&keywords=python-texttable+), +[FreeBSD](https://www.freebsd.org/cgi/ports.cgi?query=texttable&stype=all), Fedora, Suse...). + +## Documentation + +``` +NAME + texttable - module for creating simple ASCII tables + +FILE + /usr/lib/python2.3/site-packages/texttable.py + +DESCRIPTION + + Example: + + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([["Name", "Age", "Nickname"], + ["Mr\nXavier\nHuon", 32, "Xav'"], + ["Mr\nBaptiste\nClement", 1, "Baby"]]) + print table.draw() + "\n" + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print table.draw() + + Result: + + +----------+-----+----------+ + | Name | Age | Nickname | + +==========+=====+==========+ + | Mr | | | + | Xavier | 32 | | + | Huon | | Xav' | + +----------+-----+----------+ + | Mr | | | + | Baptiste | 1 | | + | Clement | | Baby | + +----------+-----+----------+ + + text float exp int auto + =========================================== + abcd 67.000 6.540e+02 89 128.001 + efgh 67.543 6.540e-01 90 1.280e+22 + ijkl 0.000 5.000e-78 89 0.000 + mnop 0.023 5.000e+78 92 1.280e+22 + +CLASSES + class Texttable + | Methods defined here: + | + | __init__(self, max_width=80) + | Constructor + | + | - max_width is an integer, specifying the maximum width of the table + | - if set to 0, size is unlimited, therefore cells won't be wrapped + | + | add_row(self, array) + | Add a row in the rows stack + | + | - cells can contain newlines and tabs + | + | add_rows(self, rows, header=True) + | Add several rows in the rows stack + | + | - The 'rows' argument can be either an iterator returning arrays, + | or a by-dimensional array + | - 'header' specifies if the first row should be used as the header + | of the table + | + | draw(self) + | Draw the table + | + | - the table is returned as a whole string + | + | header(self, array) + | Specify the header of the table + | + | reset(self) + | Reset the instance + | + | - reset rows and header + | + | set_chars(self, array) + | Set the characters used to draw lines between rows and columns + | + | - the array should contain 4 fields: + | + | [horizontal, vertical, corner, header] + | + | - default is set to: + | + | ['-', '|', '+', '='] + | + | set_cols_align(self, array) + | Set the desired columns alignment + | + | - the elements of the array should be either "l", "c" or "r": + | + | * "l": column flushed left + | * "c": column centered + | * "r": column flushed right + | + | set_cols_dtype(self, array) + | Set the desired columns datatype for the cols. + | + | - the elements of the array should be either "a", "t", "f", "e" or "i": + | + | * "a": automatic (try to use the most appropriate datatype) + | * "t": treat as text + | * "f": treat as float in decimal format + | * "e": treat as float in exponential format + | * "i": treat as int + | + | - by default, automatic datatyping is used for each column + | + | set_cols_valign(self, array) + | Set the desired columns vertical alignment + | + | - the elements of the array should be either "t", "m" or "b": + | + | * "t": column aligned on the top of the cell + | * "m": column aligned on the middle of the cell + | * "b": column aligned on the bottom of the cell + | + | set_cols_width(self, array) + | Set the desired columns width + | + | - the elements of the array should be integers, specifying the + | width of each column. For example: + | + | [10, 20, 5] + | + | set_deco(self, deco) + | Set the table decoration + | + | - 'deco' can be a combinaison of: + | + | Texttable.BORDER: Border around the table + | Texttable.HEADER: Horizontal line below the header + | Texttable.HLINES: Horizontal lines between rows + | Texttable.VLINES: Vertical lines between columns + | + | All of them are enabled by default + | + | - example: + | + | Texttable.BORDER | Texttable.HEADER + | + | set_precision(self, width) + | Set the desired precision for float/exponential formats + | + | - width must be an integer >= 0 + | + | - default value is set to 3 + | + | ---------------------------------------------------------------------- + | Data and other attributes defined here: + | + | BORDER = 1 + | + | HEADER = 2 + | + | HLINES = 4 + | + | VLINES = 8 + +DATA + __all__ = ['Texttable', 'ArraySizeError'] + __author__ = 'Gerome Fournier <jef(at)foutaise.org>' + __credits__ = 'Jeff Kowalczyk:\n - textwrap improved import\n ...:\... + __license__ = 'LGPL' + __version__ = '0.8.4' + +VERSION + 0.8.4 + +AUTHOR + Gerome Fournier <jef(at)foutaise.org> + +CREDITS + Jeff Kowalczyk: + - textwrap improved import + - comment concerning header output + + Anonymous: + - add_rows method, for adding rows in one go + + Sergey Simonenko: + - redefined len() function to deal with non-ASCII characters + + Roger Lew: + - columns datatype specifications + + Brian Peterson: + - better handling of unicode errors + + Frank Sachsenheim: + - add Python 2/3-compatibility + + Maximilian Hils: + - fix minor bug for Python 3 compatibility +``` diff --git a/scripts/external_libs/texttable-0.8.4/setup.py b/scripts/external_libs/texttable-0.8.4/setup.py new file mode 100644 index 00000000..eb8c9e3a --- /dev/null +++ b/scripts/external_libs/texttable-0.8.4/setup.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# +# texttable - module for creating simple ASCII tables +# Copyright (C) 2003-2015 Gerome Fournier <jef(at)foutaise.org> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +DESCRIPTION = "module for creating simple ASCII tables" + +LONG_DESCRIPTION = """\ +texttable is a module to generate a formatted text table, using ASCII +characters.""" + +import sys + +from distutils.core import setup +if sys.version < '2.2.3': + from distutils.dist import DistributionMetadata + DistributionMetadata.classifiers = None + DistributionMetadata.download_url = None + +setup( + name = "texttable", + version = "0.8.4", + author = "Gerome Fournier", + author_email = "jef(at)foutaise.org", + url = "https://github.com/foutaise/texttable/", + download_url = "https://github.com/foutaise/texttable/archive/v0.8.4.tar.gz", + license = "LGPL", + py_modules = ["texttable"], + description = DESCRIPTION, + long_description = LONG_DESCRIPTION, + platforms = "any", + classifiers = [ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: End Users/Desktop', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: MacOS', + 'Topic :: Software Development :: Libraries :: Python Modules', + 'Topic :: Text Processing', + 'Topic :: Utilities', + ] +) diff --git a/scripts/external_libs/texttable-0.8.4/texttable.py b/scripts/external_libs/texttable-0.8.4/texttable.py new file mode 100644 index 00000000..775a43e5 --- /dev/null +++ b/scripts/external_libs/texttable-0.8.4/texttable.py @@ -0,0 +1,607 @@ +#!/usr/bin/env python +# +# texttable - module for creating simple ASCII tables +# Copyright (C) 2003-2015 Gerome Fournier <jef(at)foutaise.org> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +"""module for creating simple ASCII tables + + +Example: + + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([["Name", "Age", "Nickname"], + ["Mr\\nXavier\\nHuon", 32, "Xav'"], + ["Mr\\nBaptiste\\nClement", 1, "Baby"]]) + print table.draw() + "\\n" + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print table.draw() + +Result: + + +----------+-----+----------+ + | Name | Age | Nickname | + +==========+=====+==========+ + | Mr | | | + | Xavier | 32 | | + | Huon | | Xav' | + +----------+-----+----------+ + | Mr | | | + | Baptiste | 1 | | + | Clement | | Baby | + +----------+-----+----------+ + + text float exp int auto + =========================================== + abcd 67.000 6.540e+02 89 128.001 + efgh 67.543 6.540e-01 90 1.280e+22 + ijkl 0.000 5.000e-78 89 0.000 + mnop 0.023 5.000e+78 92 1.280e+22 +""" + +__all__ = ["Texttable", "ArraySizeError"] + +__author__ = 'Gerome Fournier <jef(at)foutaise.org>' +__license__ = 'LGPL' +__version__ = '0.8.4' +__credits__ = """\ +Jeff Kowalczyk: + - textwrap improved import + - comment concerning header output + +Anonymous: + - add_rows method, for adding rows in one go + +Sergey Simonenko: + - redefined len() function to deal with non-ASCII characters + +Roger Lew: + - columns datatype specifications + +Brian Peterson: + - better handling of unicode errors + +Frank Sachsenheim: + - add Python 2/3-compatibility + +Maximilian Hils: + - fix minor bug for Python 3 compatibility +""" + +import sys +import string + +try: + if sys.version >= '2.3': + import textwrap + elif sys.version >= '2.2': + from optparse import textwrap + else: + from optik import textwrap +except ImportError: + sys.stderr.write("Can't import textwrap module!\n") + raise + +if sys.version >= '2.7': + from functools import reduce + +def len(iterable): + """Redefining len here so it will be able to work with non-ASCII characters + """ + if not isinstance(iterable, str): + return iterable.__len__() + + try: + if sys.version >= '3.0': + return len(str) + else: + return len(unicode(iterable, 'utf')) + except: + return iterable.__len__() + + +class ArraySizeError(Exception): + """Exception raised when specified rows don't fit the required size + """ + + def __init__(self, msg): + self.msg = msg + Exception.__init__(self, msg, '') + + def __str__(self): + return self.msg + + +class Texttable: + + BORDER = 1 + HEADER = 1 << 1 + HLINES = 1 << 2 + VLINES = 1 << 3 + + def __init__(self, max_width=80): + """Constructor + + - max_width is an integer, specifying the maximum width of the table + - if set to 0, size is unlimited, therefore cells won't be wrapped + """ + + if max_width <= 0: + max_width = False + self._max_width = max_width + self._precision = 3 + + self._deco = Texttable.VLINES | Texttable.HLINES | Texttable.BORDER | \ + Texttable.HEADER + self.set_chars(['-', '|', '+', '=']) + self.reset() + + def reset(self): + """Reset the instance + + - reset rows and header + """ + + self._hline_string = None + self._row_size = None + self._header = [] + self._rows = [] + + def set_chars(self, array): + """Set the characters used to draw lines between rows and columns + + - the array should contain 4 fields: + + [horizontal, vertical, corner, header] + + - default is set to: + + ['-', '|', '+', '='] + """ + + if len(array) != 4: + raise ArraySizeError("array should contain 4 characters") + array = [ x[:1] for x in [ str(s) for s in array ] ] + (self._char_horiz, self._char_vert, + self._char_corner, self._char_header) = array + + def set_deco(self, deco): + """Set the table decoration + + - 'deco' can be a combinaison of: + + Texttable.BORDER: Border around the table + Texttable.HEADER: Horizontal line below the header + Texttable.HLINES: Horizontal lines between rows + Texttable.VLINES: Vertical lines between columns + + All of them are enabled by default + + - example: + + Texttable.BORDER | Texttable.HEADER + """ + + self._deco = deco + + def set_cols_align(self, array): + """Set the desired columns alignment + + - the elements of the array should be either "l", "c" or "r": + + * "l": column flushed left + * "c": column centered + * "r": column flushed right + """ + + self._check_row_size(array) + self._align = array + + def set_cols_valign(self, array): + """Set the desired columns vertical alignment + + - the elements of the array should be either "t", "m" or "b": + + * "t": column aligned on the top of the cell + * "m": column aligned on the middle of the cell + * "b": column aligned on the bottom of the cell + """ + + self._check_row_size(array) + self._valign = array + + def set_cols_dtype(self, array): + """Set the desired columns datatype for the cols. + + - the elements of the array should be either "a", "t", "f", "e" or "i": + + * "a": automatic (try to use the most appropriate datatype) + * "t": treat as text + * "f": treat as float in decimal format + * "e": treat as float in exponential format + * "i": treat as int + + - by default, automatic datatyping is used for each column + """ + + self._check_row_size(array) + self._dtype = array + + def set_cols_width(self, array): + """Set the desired columns width + + - the elements of the array should be integers, specifying the + width of each column. For example: + + [10, 20, 5] + """ + + self._check_row_size(array) + try: + array = list(map(int, array)) + if reduce(min, array) <= 0: + raise ValueError + except ValueError: + sys.stderr.write("Wrong argument in column width specification\n") + raise + self._width = array + + def set_precision(self, width): + """Set the desired precision for float/exponential formats + + - width must be an integer >= 0 + + - default value is set to 3 + """ + + if not type(width) is int or width < 0: + raise ValueError('width must be an integer greater then 0') + self._precision = width + + def header(self, array): + """Specify the header of the table + """ + + self._check_row_size(array) + self._header = list(map(str, array)) + + def add_row(self, array): + """Add a row in the rows stack + + - cells can contain newlines and tabs + """ + + self._check_row_size(array) + + if not hasattr(self, "_dtype"): + self._dtype = ["a"] * self._row_size + + cells = [] + for i, x in enumerate(array): + cells.append(self._str(i, x)) + self._rows.append(cells) + + def add_rows(self, rows, header=True): + """Add several rows in the rows stack + + - The 'rows' argument can be either an iterator returning arrays, + or a by-dimensional array + - 'header' specifies if the first row should be used as the header + of the table + """ + + # nb: don't use 'iter' on by-dimensional arrays, to get a + # usable code for python 2.1 + if header: + if hasattr(rows, '__iter__') and hasattr(rows, 'next'): + self.header(rows.next()) + else: + self.header(rows[0]) + rows = rows[1:] + for row in rows: + self.add_row(row) + + def draw(self): + """Draw the table + + - the table is returned as a whole string + """ + + if not self._header and not self._rows: + return + self._compute_cols_width() + self._check_align() + out = "" + if self._has_border(): + out += self._hline() + if self._header: + out += self._draw_line(self._header, isheader=True) + if self._has_header(): + out += self._hline_header() + length = 0 + for row in self._rows: + length += 1 + out += self._draw_line(row) + if self._has_hlines() and length < len(self._rows): + out += self._hline() + if self._has_border(): + out += self._hline() + return out[:-1] + + def _str(self, i, x): + """Handles string formatting of cell data + + i - index of the cell datatype in self._dtype + x - cell data to format + """ + try: + f = float(x) + except: + return str(x) + + n = self._precision + dtype = self._dtype[i] + + if dtype == 'i': + return str(int(round(f))) + elif dtype == 'f': + return '%.*f' % (n, f) + elif dtype == 'e': + return '%.*e' % (n, f) + elif dtype == 't': + return str(x) + else: + if f - round(f) == 0: + if abs(f) > 1e8: + return '%.*e' % (n, f) + else: + return str(int(round(f))) + else: + if abs(f) > 1e8: + return '%.*e' % (n, f) + else: + return '%.*f' % (n, f) + + def _check_row_size(self, array): + """Check that the specified array fits the previous rows size + """ + + if not self._row_size: + self._row_size = len(array) + elif self._row_size != len(array): + raise ArraySizeError("array should contain %d elements" \ + % self._row_size) + + def _has_vlines(self): + """Return a boolean, if vlines are required or not + """ + + return self._deco & Texttable.VLINES > 0 + + def _has_hlines(self): + """Return a boolean, if hlines are required or not + """ + + return self._deco & Texttable.HLINES > 0 + + def _has_border(self): + """Return a boolean, if border is required or not + """ + + return self._deco & Texttable.BORDER > 0 + + def _has_header(self): + """Return a boolean, if header line is required or not + """ + + return self._deco & Texttable.HEADER > 0 + + def _hline_header(self): + """Print header's horizontal line + """ + + return self._build_hline(True) + + def _hline(self): + """Print an horizontal line + """ + + if not self._hline_string: + self._hline_string = self._build_hline() + return self._hline_string + + def _build_hline(self, is_header=False): + """Return a string used to separated rows or separate header from + rows + """ + horiz = self._char_horiz + if (is_header): + horiz = self._char_header + # compute cell separator + s = "%s%s%s" % (horiz, [horiz, self._char_corner][self._has_vlines()], + horiz) + # build the line + l = s.join([horiz * n for n in self._width]) + # add border if needed + if self._has_border(): + l = "%s%s%s%s%s\n" % (self._char_corner, horiz, l, horiz, + self._char_corner) + else: + l += "\n" + return l + + def _len_cell(self, cell): + """Return the width of the cell + + Special characters are taken into account to return the width of the + cell, such like newlines and tabs + """ + + cell_lines = cell.split('\n') + maxi = 0 + for line in cell_lines: + length = 0 + parts = line.split('\t') + for part, i in zip(parts, list(range(1, len(parts) + 1))): + length = length + len(part) + if i < len(parts): + length = (length//8 + 1) * 8 + maxi = max(maxi, length) + return maxi + + def _compute_cols_width(self): + """Return an array with the width of each column + + If a specific width has been specified, exit. If the total of the + columns width exceed the table desired width, another width will be + computed to fit, and cells will be wrapped. + """ + + if hasattr(self, "_width"): + return + maxi = [] + if self._header: + maxi = [ self._len_cell(x) for x in self._header ] + for row in self._rows: + for cell,i in zip(row, list(range(len(row)))): + try: + maxi[i] = max(maxi[i], self._len_cell(cell)) + except (TypeError, IndexError): + maxi.append(self._len_cell(cell)) + items = len(maxi) + length = reduce(lambda x, y: x+y, maxi) + if self._max_width and length + items * 3 + 1 > self._max_width: + maxi = [(self._max_width - items * 3 -1) // items \ + for n in range(items)] + self._width = maxi + + def _check_align(self): + """Check if alignment has been specified, set default one if not + """ + + if not hasattr(self, "_align"): + self._align = ["l"] * self._row_size + if not hasattr(self, "_valign"): + self._valign = ["t"] * self._row_size + + def _draw_line(self, line, isheader=False): + """Draw a line + + Loop over a single cell length, over all the cells + """ + + line = self._splitit(line, isheader) + space = " " + out = "" + for i in range(len(line[0])): + if self._has_border(): + out += "%s " % self._char_vert + length = 0 + for cell, width, align in zip(line, self._width, self._align): + length += 1 + cell_line = cell[i] + fill = width - len(cell_line) + if isheader: + align = "c" + if align == "r": + out += "%s " % (fill * space + cell_line) + elif align == "c": + out += "%s " % (int(fill/2) * space + cell_line \ + + int(fill/2 + fill%2) * space) + else: + out += "%s " % (cell_line + fill * space) + if length < len(line): + out += "%s " % [space, self._char_vert][self._has_vlines()] + out += "%s\n" % ['', self._char_vert][self._has_border()] + return out + + def _splitit(self, line, isheader): + """Split each element of line to fit the column width + + Each element is turned into a list, result of the wrapping of the + string to the desired width + """ + + line_wrapped = [] + for cell, width in zip(line, self._width): + array = [] + for c in cell.split('\n'): + try: + if sys.version >= '3.0': + c = str(c) + else: + c = unicode(c, 'utf') + except UnicodeDecodeError as strerror: + sys.stderr.write("UnicodeDecodeError exception for string '%s': %s\n" % (c, strerror)) + if sys.version >= '3.0': + c = str(c, 'utf', 'replace') + else: + c = unicode(c, 'utf', 'replace') + array.extend(textwrap.wrap(c, width)) + line_wrapped.append(array) + max_cell_lines = reduce(max, list(map(len, line_wrapped))) + for cell, valign in zip(line_wrapped, self._valign): + if isheader: + valign = "t" + if valign == "m": + missing = max_cell_lines - len(cell) + cell[:0] = [""] * int(missing / 2) + cell.extend([""] * int(missing / 2 + missing % 2)) + elif valign == "b": + cell[:0] = [""] * (max_cell_lines - len(cell)) + else: + cell.extend([""] * (max_cell_lines - len(cell))) + return line_wrapped + + +if __name__ == '__main__': + table = Texttable() + table.set_cols_align(["l", "r", "c"]) + table.set_cols_valign(["t", "m", "b"]) + table.add_rows([["Name", "Age", "Nickname"], + ["Mr\nXavier\nHuon", 32, "Xav'"], + ["Mr\nBaptiste\nClement", 1, "Baby"]]) + print(table.draw() + "\n") + + table = Texttable() + table.set_deco(Texttable.HEADER) + table.set_cols_dtype(['t', # text + 'f', # float (decimal) + 'e', # float (exponent) + 'i', # integer + 'a']) # automatic + table.set_cols_align(["l", "r", "r", "r", "l"]) + table.add_rows([["text", "float", "exp", "int", "auto"], + ["abcd", "67", 654, 89, 128.001], + ["efghijk", 67.5434, .654, 89.6, 12800000000000000000000.00023], + ["lmn", 5e-78, 5e-78, 89.4, .000000000000128], + ["opqrstu", .023, 5e+78, 92., 12800000000000000000000]]) + print(table.draw()) |