summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Klein <danklein10@gmail.com>2015-11-26 13:06:36 +0200
committerDan Klein <danklein10@gmail.com>2015-11-26 13:06:36 +0200
commit91f6c24f45cbb0cbf8568a9938059a1a934e6ae6 (patch)
tree0977d1129173d2b2be8e36c91aa5b7ec97b035a1
parente7cb8b0f6c2fbe08d2086a7408040ac7d12aee5a (diff)
Initial implementation of stats prompting
-rw-r--r--scripts/automation/trex_control_plane/client/trex_async_client.py48
-rwxr-xr-xscripts/automation/trex_control_plane/client/trex_stateless_client.py108
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/external_packages.py3
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/jsonrpc_client.py4
-rwxr-xr-xscripts/automation/trex_control_plane/client_utils/parsing_opts.py (renamed from scripts/automation/trex_control_plane/console/parsing_opts.py)22
-rw-r--r--scripts/automation/trex_control_plane/client_utils/text_tables.py34
-rwxr-xr-xscripts/automation/trex_control_plane/common/trex_stats.py80
-rwxr-xr-xscripts/automation/trex_control_plane/console/trex_console.py11
-rw-r--r--scripts/automation/trex_control_plane/console/trex_status.py4
-rw-r--r--scripts/external_libs/texttable-0.8.4/LICENSE165
-rw-r--r--scripts/external_libs/texttable-0.8.4/PKG-INFO23
-rw-r--r--scripts/external_libs/texttable-0.8.4/README.md223
-rw-r--r--scripts/external_libs/texttable-0.8.4/setup.py59
-rw-r--r--scripts/external_libs/texttable-0.8.4/texttable.py607
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())