From c930b160c4783c1f5032a0eda5a3312eb2e24e9d Mon Sep 17 00:00:00 2001
From: gpotter2 <gabriel@potter.fr>
Date: Fri, 7 Apr 2017 23:13:22 +0200
Subject: [PATCH] Prettify routes

---
 scapy/route.py  | 13 +++------
 scapy/route6.py | 16 +++--------
 scapy/utils.py  | 72 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 79 insertions(+), 22 deletions(-)

diff --git a/scapy/route.py b/scapy/route.py
index 8a2495ed..0a88a232 100644
--- a/scapy/route.py
+++ b/scapy/route.py
@@ -9,7 +9,7 @@ Routing and handling of network interfaces.
 
 import socket
 from scapy.consts import LOOPBACK_NAME, getLoopbackInterface
-from scapy.utils import atol, ltoa, itom
+from scapy.utils import atol, ltoa, itom, pretty_routes
 from scapy.config import conf
 from scapy.error import Scapy_Exception, warning
 from scapy.arch import WINDOWS
@@ -41,15 +41,8 @@ class Route:
                       (iface.name if not isinstance(iface, basestring) else iface),
                       addr))
 
-        # Sort correctly
-        rtlst.sort(key=lambda x: x[0])
-        # Append tag
-        rtlst = [("Network", "Netmask", "Gateway", "Iface", "Output IP")] + rtlst
-        
-        colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst))
-        fmt = "  ".join(map(lambda x: "%%-%ds"%x, colwidth))
-        rt = "\n".join(map(lambda x: fmt % x, rtlst))
-        return rt
+        return pretty_routes(rtlst,
+                             [("Network", "Netmask", "Gateway", "Iface", "Output IP")])
 
     def make_route(self, host=None, net=None, gw=None, dev=None):
         from scapy.arch import get_if_addr
diff --git a/scapy/route6.py b/scapy/route6.py
index 7c88d6d5..f171b227 100644
--- a/scapy/route6.py
+++ b/scapy/route6.py
@@ -50,19 +50,11 @@ class Route6:
         rtlst = []
 
         for net,msk,gw,iface,cset in self.routes:
-            rtlst.append(('%s/%i'% (net,msk), gw, (iface if isinstance(iface, basestring) else iface.name), ", ".join(cset)))
-
-        # Sort correctly
-        rtlst.sort(key=lambda x: x[0])
-        # Append tag
-        rtlst = [('Destination', 'Next Hop', "iface", "src candidates")] + rtlst
-        
-        colwidth = [max([len(y) for y in x]) for x in zip(*rtlst)]
-        fmt = "  ".join(["%%-%ds"%x for x in colwidth])
-        rt = "\n".join([fmt % x for x in rtlst])
-
-        return rt
+            rtlst.append(('%s/%i'% (net,msk), gw, (iface if isinstance(iface, basestring) else iface.name), ", ".join(cset) if len(cset) > 0 else ""))
 
+        return pretty_routes(rtlst,
+                             [('Destination', 'Next Hop', "Iface", "Src candidates")],
+                             sortBy = 1)
 
     # Unlike Scapy's Route.make_route() function, we do not have 'host' and 'net'
     # parameters. We only have a 'dst' parameter that accepts 'prefix' and
diff --git a/scapy/utils.py b/scapy/utils.py
index e597c172..c221ad02 100644
--- a/scapy/utils.py
+++ b/scapy/utils.py
@@ -1232,6 +1232,78 @@ def hexedit(x):
     os.unlink(f)
     return x
 
+def get_terminal_width():
+    """Get terminal width if in a window"""
+    if WINDOWS:
+        from ctypes import windll, create_string_buffer
+        # http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/
+        h = windll.kernel32.GetStdHandle(-12)
+        csbi = create_string_buffer(22)
+        res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
+        if res:
+            import struct
+            (bufx, bufy, curx, cury, wattr,
+             left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
+            sizex = right - left + 1
+            #sizey = bottom - top + 1
+            return sizex
+        else:
+            return None
+    else:
+        sizex = 0
+        try:
+            import struct, fcntl, termios
+            s = struct.pack('HHHH', 0, 0, 0, 0)
+            x = fcntl.ioctl(1, termios.TIOCGWINSZ, s)
+            sizex = struct.unpack('HHHH', x)[1]
+        except IOError:
+            pass
+        if not sizex:
+            try:
+                sizex = int(os.environ['COLUMNS'])
+            except:
+                pass
+        if sizex:
+            return sizex
+        else:
+            return None
+
+def pretty_routes(rtlst, header, sortBy=0):
+    """Pretty route list, and add header"""
+    _l_header = len(header[0])
+    _space = "  "
+    # Sort correctly
+    rtlst.sort(key=lambda x: x[sortBy])
+    # Append tag
+    rtlst = header + rtlst
+    # Detect column's width
+    colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst))
+    # Make text fit in box (if exist)
+    # TODO: find a better and more precise way of doing this. That's currently working but very complicated
+    width = get_terminal_width()
+    if width:
+        if sum(colwidth) > width:
+            # Needs to be cropped
+            _med = (width // _l_header) - (1 if WINDOWS else 0) # Windows has a fat window border
+            # Crop biggest until size is correct
+            for i in range(1, len(colwidth)): # Should use while, but this is safer
+                if (sum(colwidth)+6) <= width:
+                    break
+                _max = max(colwidth)
+                colwidth = [_med if x == _max else x for x in colwidth]
+            def _crop(x, width):
+                _r = x[:width]
+                if _r != x:
+                    _r = x[:width-3]
+                    return _r + "..."
+                return _r
+            rtlst = [tuple([_crop(rtlst[j][i], colwidth[i]) for i in range(0, len(rtlst[j]))]) for j in range(0, len(rtlst))]
+            # Recalculate column's width
+            colwidth = map(lambda x: max(map(lambda y: len(y), x)), apply(zip, rtlst))
+    fmt = _space.join(map(lambda x: "%%-%ds"%x, colwidth))
+    rt = "\n".join(map(lambda x: fmt % x, rtlst))
+    return rt
+
 def __make_table(yfmtfunc, fmtfunc, endline, list, fxyz, sortx=None, sorty=None, seplinefunc=None):
     vx = {} 
     vy = {} 
-- 
GitLab