#!/usr/bin/python3

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#

from __future__ import unicode_literals
from __future__ import division
from __future__ import absolute_import
from __future__ import print_function

import os
from optparse import OptionGroup
import sys
import locale
import socket
import re
from datetime import datetime
from time import ctime, strftime, gmtime
import  qpid_dispatch_site
from qpid_dispatch.management.client import Url, Node, Entity
from qpid_dispatch_internal.management.qdrouter import QdSchema
from qpid_dispatch_internal.tools import Display, Header, Sorter, YN, Commas, TimeLong
from qpid_dispatch_internal.tools.command import connection_options, main, OptionParser, opts_ssl_domain, opts_sasl, \
        opts_url
from qpid_dispatch_internal.compat import UNICODE

def parse_args(argv):
    """ Set global variables for options, return arguments """

    usage = "%prog [options]"
    parser = OptionParser(usage=usage)

    parser.add_option_group(connection_options(parser))

    parser.add_option("-g", "--general", help="Show General Router Stats",  action="store_const", const="g",   dest="show")
    parser.add_option("-c", "--connections", help="Show Connections",       action="store_const", const="c",   dest="show")
    parser.add_option("-l", "--links", help="Show Router Links",            action="store_const", const="l",   dest="show")
    parser.add_option("-n", "--nodes", help="Show Router Nodes",            action="store_const", const="n",   dest="show")
    parser.add_option("-e", "--edge", help="Show edge connections",         action="store_const", const="e",   dest="show")
    parser.add_option("-a", "--address", help="Show Router Addresses",      action="store_const", const="a",   dest="show")
    parser.add_option("-m", "--memory", help="Show Router Memory Stats",    action="store_const", const="m",   dest="show")
    parser.add_option("--autolinks", help="Show Auto Links",                action="store_const", const="autolinks",  dest="show")
    parser.add_option("--linkroutes", help="Show Link Routes",              action="store_const", const="linkroutes", dest="show")

    parser.add_option("--all-routers", help="Show entities for all routers in network. Can also be used in combination with other options",  action="store_const", const="all_routers",  dest="all_routers")
    parser.add_option("--all-entities", help="Show all router entities. Can be combined with --all-routers option", action="store_const", const="all_entities", dest="all_entities")

    parser.add_option("-v", "--verbose", help="Show maximum detail",        action="store_true", dest="verbose")
    parser.add_option("--log", help="Show recent log entries", action="store_const", const="log", dest="show")

    # This limit can be used to limit the number of output rows and
    # can be used in conjunction with options
    # like -c, -l, -a, --autolinks, --linkroutes and --log.
    # By default, the limit is not set, which means the limit is unlimited.
    parser.add_option("--limit", help="Limit number of output rows", type="int", default=None)

    opts, args = parser.parse_args(args=argv)

    if opts.router and opts.all_routers:
        parser.error("--all-routers cannot be combined with single router option -r " + opts.router)

    if opts.all_entities and opts.show:
           parser.error("--all-entities cannot be combined with specific entity option -" + opts.show)

    if not opts.show and not opts.all_entities and opts.all_routers:
        opts.show = u'g'

    if not opts.all_routers and not opts.all_entities and not opts.show:
        opts.show = u'g'

    return opts, args

def get(obj, attr):
    if attr in obj.__dict__:
        return obj.__dict__[attr]
    return None


class BusManager(Node):

    schema = QdSchema()

    def __init__(self, opts):
        self.opts = opts
        super(BusManager, self).__init__(
            Node.connection(opts_url(opts), opts.router,
                            timeout=opts.timeout,
                            ssl_domain=opts_ssl_domain(opts),
                            sasl=opts_sasl(self.opts)))

    def query(self, entity_type, attribute_names=None, limit=None):
        if attribute_names:
            unames = []
            for a in attribute_names:
                unames.append(UNICODE(a))
            attribute_names = unames
        return super(BusManager, self).query(entity_type, attribute_names, count=limit).get_entities()

    def connAuth(self, conn):
        ##
        ## Summarize the authentication for a connection:
        ##   no-auth
        ##   anonymous-user
        ##   <user>(PLAIN)
        ##   <user>(kerberos)
        ##   <user>(x.509)
        ##
        if not conn.isAuthenticated:
            return "no-auth"
        sasl = conn.sasl
        if sasl == "GSSAPI":
            sasl = "Kerberos"
        elif sasl == "EXTERNAL":
            sasl = "x.509"
        elif sasl == "ANONYMOUS":
            return "anonymous-user"
        if not conn.user:
            return sasl
        return "%s(%s)" % (conn.user, sasl)

    def connSecurity(self, conn):
        ##
        ## Summarize the security of a connection:
        ##   no-security
        ##   SSLv3 (cipher)
        ##   TLS (cipher)
        ##   Kerberos
        ##
        if not conn.isEncrypted:
            return "no-security"
        if conn.sasl == "GSSAPI":
            return "Kerberos"
        return "%s(%s)" % (conn.sslProto, conn.sslCipher)

    def noTrailingSlash(self, text):
        if text == None:
            return ""
        if text[-1:] == '/':
            return text[:-1]
        return text

    def displayEdges(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("id"))
        heads.append(Header("host"))
        heads.append(Header("container"))
        heads.append(Header("role"))
        heads.append(Header("dir"))
        heads.append(Header("security"))
        heads.append(Header("authentication"))
        heads.append(Header("tenant"))
        
        rows = []
        objects = self.query('org.apache.qpid.dispatch.connection', limit=self.opts.limit)
        
        if not objects:
            print("No Edge Router Connections")
            return
        has_active = False
        
        first = objects[0]
        try:
            if first:
                active = first.active
                has_active = True
        except:
            pass
            
        if has_active:
            heads.append(Header("active"))
        
        for conn in objects:
            #print (conn)
            if conn.role == "edge":
               row = []
               row.append(conn.identity)
               row.append(conn.host)
               row.append(conn.container)
               row.append(conn.role)
               row.append(conn.dir)
               row.append(self.connSecurity(conn))
               row.append(self.connAuth(conn))
               row.append(self.noTrailingSlash(get(conn, 'tenant')))
               if has_active:
                   if conn.active:
                       row.append("yes")
                   else:
                       row.append("no")
               rows.append(row)
        
        if rows:
            title = "Connections"
        else:
            return
        
        dispRows = rows
        disp.formattedTable(title, heads, dispRows)            
        
    def displayConnections(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("id"))
        heads.append(Header("host"))
        heads.append(Header("container"))
        heads.append(Header("role"))
        heads.append(Header("dir"))
        heads.append(Header("security"))
        heads.append(Header("authentication"))
        heads.append(Header("tenant"))

        rows = []
        objects = self.query('org.apache.qpid.dispatch.connection', limit=self.opts.limit)

        for conn in objects:
            row = []
            row.append(conn.identity)
            row.append(conn.host)
            row.append(conn.container)
            row.append(conn.role)
            row.append(conn.dir)
            row.append(self.connSecurity(conn))
            row.append(self.connAuth(conn))
            row.append(self.noTrailingSlash(get(conn, 'tenant')))
            rows.append(row)
        title = "Connections"
        dispRows = rows
        disp.formattedTable(title, heads, dispRows)

    def _addr_summary(self, addr):
        cls   = self._addr_class(addr)
        phase = self._addr_phase(addr)
        text  = self._addr_text(addr)
        if cls == '-':
            return "-"
        if cls == 'M':
            if phase == '0':
                return text
            else:
                return "%s:%s" % (phase, text)
        return "%s:%s" % (cls, text)


    def _addr_class(self, addr):
        if not addr:
            return ""
        if addr[0] == 'M'  : return "mobile"
        if addr[0] == 'R'  : return "router"
        if addr[0] == 'A'  : return "area"
        if addr[0] == 'L'  : return "local"
        if addr[0] == 'T'  : return "topo"
        if addr[0] in 'CE' : return "link-in"
        if addr[0] in 'DF' : return "link-out"
        if addr[0] == 'H'  : return "edge"
        return "unknown: %s" % addr[0]

    def _addr_text(self, addr):
        if not addr:
            return ""
        if addr[0] == 'M':
            return addr[2:]
        else:
            return addr[1:]

    def _addr_phase(self, addr):
        if not addr:
            return ""
        if addr[0] == 'M':
            return addr[1]
        return ''

    def _identity_clean(self, identity, router_id=None):
        if router_id:
            return router_id
        if not identity:
            return "-"
        pos = identity.find('/')
        if pos >= 0:
            return identity[pos + 1:]
        return identity

    def _list_clean(self, inlist):
        outlist = []
        if not inlist:
            return outlist
        for i in inlist:
            outlist.append(str(i))
        return outlist

    def displayGeneral(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("attr"))
        heads.append(Header("value"))
        rows = []

        objects = self.query('org.apache.qpid.dispatch.router')

        router = objects[0]
        rows.append(('Version',       router.version))
        rows.append(('Mode',          router.mode))
        rows.append(('Router Id',     router.id))
        rows.append(('Area',          router.area))
        rows.append(('Link Routes',   router.linkRouteCount))
        rows.append(('Auto Links',    router.autoLinkCount))
        rows.append(('Links',         router.linkCount))
        rows.append(('Nodes',         router.nodeCount))
        rows.append(('Addresses',     router.addrCount))
        rows.append(('Connections',   router.connectionCount))

        # Overall delivery related counts.
        # These overall statistics were introduced in 1.1 version.
        # Wrap these in a try except so that newer versions of qdstat works with older version of router
        try:
            rows.append(('Presettled Count', router.presettledDeliveries))
            rows.append(('Dropped Presettled Count', router.droppedPresettledDeliveries))
            rows.append(('Accepted Count', router.acceptedDeliveries))
            rows.append(('Rejected Count', router.rejectedDeliveries))
            rows.append(('Released Count', router.releasedDeliveries))
            rows.append(('Modified Count', router.modifiedDeliveries))
            try:
                rows.append(('Deliveries Delayed > 1sec', router.deliveriesDelayed1Sec))
                rows.append(('Deliveries Delayed > 10sec', router.deliveriesDelayed10Sec))
                rows.append(('Deliveries to Fallback', router.deliveriesRedirectedToFallback))
            except:
                pass
            rows.append(('Ingress Count', router.deliveriesIngress))
            rows.append(('Egress Count', router.deliveriesEgress))
            rows.append(('Transit Count', router.deliveriesTransit))
            rows.append(('Deliveries from Route Container', router.deliveriesIngressRouteContainer))
            rows.append(('Deliveries to Route Container', router.deliveriesEgressRouteContainer))
        except:
            pass

        title = "Router Statistics"
        dispRows = rows
        disp.formattedTable(title, heads, dispRows)

    def displayRouterLinks(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("type"))
        heads.append(Header("dir"))
        heads.append(Header("conn id"))
        heads.append(Header("id"))
        heads.append(Header("peer"))
        heads.append(Header("class"))
        heads.append(Header("addr"))
        heads.append(Header("phs"))
        heads.append(Header("cap"))


        rows = []
        cols = ('linkType', 'linkDir', 'connectionId', 'identity', 'peer', 'owningAddr',
                'capacity', 'undeliveredCount', 'unsettledCount', 'deliveryCount',
                'presettledCount', 'droppedPresettledCount', 'acceptedCount', 'rejectedCount', 'releasedCount',
                'modifiedCount', 'deliveriesDelayed1Sec', 'deliveriesDelayed10Sec', 'adminStatus', 'operStatus',
                'linkName', 'priority', 'settleRate')

        objects = self.query('org.apache.qpid.dispatch.router.link', cols, limit=self.opts.limit)

        has_dropped_presettled_count = False
        has_priority = False
        has_delayed  = False

        if objects:
            first_row = objects[0]
            if first_row:
                if hasattr(first_row, 'droppedPresettledCount'):
                    has_dropped_presettled_count = True
                if hasattr(first_row, 'priority'):
                    has_priority = True
                if hasattr(first_row, 'deliveriesDelayed1Sec'):
                    has_delayed = True

        if has_priority:
            heads.append(Header("pri"))
        heads.append(Header("undel"))
        heads.append(Header("unsett"))
        heads.append(Header("deliv"))
        heads.append(Header("presett"))

        if has_dropped_presettled_count:
            heads.append(Header("psdrop"))

        heads.append(Header("acc"))
        heads.append(Header("rej"))
        heads.append(Header("rel"))
        heads.append(Header("mod"))
        if has_delayed:
            heads.append(Header("delay"))
            heads.append(Header("rate"))
        if self.opts.verbose:
            heads.append(Header("admin"))
            heads.append(Header("oper"))
            heads.append(Header("name"))

        for link in objects:
            row = []
            row.append(link.linkType)
            row.append(link.linkDir)
            row.append(link.connectionId)
            row.append(link.identity)
            row.append(link.peer)
            row.append(self._addr_class(link.owningAddr))
            row.append(self._addr_text(link.owningAddr))
            row.append(self._addr_phase(link.owningAddr))
            row.append(link.capacity)
            if has_priority:
                row.append(link.priority)
            row.append(link.undeliveredCount)
            row.append(link.unsettledCount)
            row.append(link.deliveryCount)
            row.append(link.presettledCount)
            if has_dropped_presettled_count:
                row.append(link.droppedPresettledCount)
            row.append(link.acceptedCount)
            row.append(link.rejectedCount)
            row.append(link.releasedCount)
            row.append(link.modifiedCount)
            if has_delayed:
                row.append(link.deliveriesDelayed1Sec + link.deliveriesDelayed10Sec)
                row.append(link.settleRate)
            if self.opts.verbose:
                row.append(link.adminStatus)
                row.append(link.operStatus)
                row.append(link.linkName)
            rows.append(row)
        title = "Router Links"
        dispRows = rows
        disp.formattedTable(title, heads, dispRows)

    def displayRouterNodes(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("router-id"))
        heads.append(Header("next-hop"))
        heads.append(Header("link"))
        if self.opts.verbose:
            heads.append(Header("ver"))
            heads.append(Header("cost"))
            heads.append(Header("neighbors"))
            heads.append(Header("valid-origins"))
        rows = []
        objects = self.query('org.apache.qpid.dispatch.router.node', limit=self.opts.limit)

        # Find the most recent topo change in this neighborhood.
        lastTopoChange = 0.0

        for node in objects:
            row = []
            if node.lastTopoChange:
                if float(node.lastTopoChange) > lastTopoChange:
                    lastTopoChange = float(node.lastTopoChange)
            row.append(node.id)
            if node.nextHop != None:
                row.append(node.nextHop)
                row.append('-')
            else:
                row.append('-')
                row.append(node.routerLink)

            if self.opts.verbose:
                row.append(get(node, 'protocolVersion'))
                row.append(get(node, 'cost'))
                row.append('%r' % self._list_clean(node.linkState))
                row.append('%r' % self._list_clean(node.validOrigins))
            rows.append(row)
        if len(rows) > 0:
            title = "Routers in the Network"
            # Use gmtime to make times comparable across large networks.
            if lastTopoChange > 1.0:
                topoLine = "\nLast Topology Change: " + strftime('%A %b %d %H:%M:%S %Y',gmtime(lastTopoChange)) + " GMT"
                title += topoLine
            sort = Sorter(heads, rows, 'router-id')
            dispRows = sort.getSorted()
            disp.formattedTable(title, heads, dispRows)
        else:
            print("Router is Standalone - No Router List")

    def displayAddresses(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("class"))
        heads.append(Header("addr"))
        heads.append(Header("phs"))
        heads.append(Header("distrib"))

        rows = []
        cols = ('distribution', 'inProcess', 'subscriberCount', 'remoteCount',
                'containerCount', 'deliveriesIngress', 'deliveriesEgress', 'deliveriesRedirectedToFallback',
                'deliveriesTransit', 'deliveriesToContainer', 'deliveriesFromContainer', 'name', 'priority')

        objects = self.query('org.apache.qpid.dispatch.router.address', cols, limit=self.opts.limit)

        has_priority = False

        if objects:
            first_row = objects[0]
            if first_row:
                if hasattr(first_row, 'priority'):
                    has_priority = True

        if has_priority:
            heads.append(Header("pri"))
        if self.opts.verbose:
            heads.append(Header("in-proc", Header.COMMAS))
        heads.append(Header("local", Header.COMMAS))
        heads.append(Header("remote", Header.COMMAS))
        heads.append(Header("in", Header.COMMAS))
        heads.append(Header("out", Header.COMMAS))
        heads.append(Header("thru", Header.COMMAS))
        heads.append(Header("fallback", Header.COMMAS))
        if self.opts.verbose:
            heads.append(Header("to-proc", Header.COMMAS))
            heads.append(Header("from-proc", Header.COMMAS))

        for addr in objects:
            row = []
            row.append(self._addr_class(addr.name))
            row.append(self._addr_text(addr.name))
            row.append(self._addr_phase(addr.name))
            row.append(addr.distribution)
            if has_priority:
                row.append(addr.priority if addr.priority >= 0 else "")
            if self.opts.verbose:
                row.append(addr.inProcess)
            row.append(addr.containerCount if addr.name[0] in 'CDEF' else addr.subscriberCount)
            row.append(addr.remoteCount)
            row.append(addr.deliveriesIngress)
            row.append(addr.deliveriesEgress)
            row.append(addr.deliveriesTransit)
            try:
                row.append(addr.deliveriesRedirectedToFallback)
            except:
                row.append("-")
            if self.opts.verbose:
                row.append(addr.deliveriesToContainer)
                row.append(addr.deliveriesFromContainer)
            rows.append(row)
        title = "Router Addresses"
        sorter = Sorter(heads, rows, 'addr', 0, True)
        dispRows = sorter.getSorted()
        disp.formattedTable(title, heads, dispRows)

    def displayAutolinks(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("addr"))
        heads.append(Header("dir"))
        heads.append(Header("phs"))
        heads.append(Header("extAddr"))
        heads.append(Header("link"))
        heads.append(Header("status"))
        heads.append(Header("lastErr"))
        rows = []
        cols = ('address', 'direction', 'phase', 'externalAddress', 'linkRef', 'operStatus', 'lastError')

        objects = self.query('org.apache.qpid.dispatch.router.config.autoLink', cols, limit=self.opts.limit)

        for al in objects:
            row = []
            row.append(al.address)
            row.append(al.direction)
            row.append(al.phase)
            row.append(al.externalAddress)
            row.append(al.linkRef)
            row.append(al.operStatus)
            row.append(al.lastError)
            rows.append(row)
        title = "AutoLinks"
        sorter = Sorter(heads, rows, 'addr', 0, True)
        dispRows = sorter.getSorted()
        disp.formattedTable(title, heads, dispRows)

    def displayLinkRoutes(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("address"))
        heads.append(Header("dir"))
        heads.append(Header("distrib"))
        heads.append(Header("status"))
        rows = []
        cols = ('prefix', 'direction', 'distribution', 'operStatus', 'pattern', 'addExternalPrefix', 'delExternalPrefix')

        link_routes = self.query('org.apache.qpid.dispatch.router.config.linkRoute', cols, limit=self.opts.limit)

        have_add_del_prefix = False
        for link_route in link_routes:
            row = []
            row.append(link_route.prefix if link_route.prefix else link_route.pattern)
            row.append(link_route.direction)
            row.append(link_route.distribution)
            row.append(link_route.operStatus)
            try:
                if link_route.addExternalPrefix or link_route.delExternalPrefix:
                    row.append(link_route.addExternalPrefix)
                    row.append(link_route.delExternalPrefix)
                    have_add_del_prefix = True
            except KeyError:
                pass # added post 1.1.0
            rows.append(row)
        title = "Link Routes"
        if have_add_del_prefix:
            heads.append(Header("add-ext-prefix"))
            heads.append(Header("del-ext-prefix"))
        sorter = Sorter(heads, rows, 'address', 0, True)
        dispRows = sorter.getSorted()
        disp.formattedTable(title, heads, dispRows)

    def displayMemory(self):
        disp = Display(prefix="  ")
        heads = []
        heads.append(Header("type"))
        heads.append(Header("size", Header.COMMAS))
        heads.append(Header("batch"))
        heads.append(Header("thread-max", Header.COMMAS))
        heads.append(Header("total", Header.COMMAS))
        heads.append(Header("in-threads", Header.COMMAS))
        heads.append(Header("rebal-in", Header.COMMAS))
        heads.append(Header("rebal-out", Header.COMMAS))
        rows = []
        cols = ('identity', 'typeSize', 'transferBatchSize', 'localFreeListMax',
                'totalAllocFromHeap', 'heldByThreads', 'batchesRebalancedToThreads',
                'batchesRebalancedToGlobal')

        objects = self.query('org.apache.qpid.dispatch.allocator', cols)

        for t in objects:
            row = []
            row.append(self._identity_clean(t.identity))
            row.append(t.typeSize)
            row.append(t.transferBatchSize)
            row.append(t.localFreeListMax)
            row.append(t.totalAllocFromHeap)
            row.append(t.heldByThreads)
            row.append(t.batchesRebalancedToThreads)
            row.append(t.batchesRebalancedToGlobal)
            rows.append(row)
        if not rows:
            # router built w/o memory pools:
            print("No memory statistics available")
            return
        title = "Types"
        sorter = Sorter(heads, rows, 'type', 0, True)
        dispRows = sorter.getSorted()
        disp.formattedTable(title, heads, dispRows)

    def displayLog(self):
        log = self.get_log(limit=self.opts.limit)
        for line in log:
            print("%s %s (%s) %s" % (ctime(line[5]), line[0], line[1], line[2]))

    def show_all(self):
        self.displayRouterLinks()
        self.displayAddresses()
        self.displayConnections()
        self.displayAutolinks()
        self.displayLinkRoutes()
        self.displayGeneral()
        self.displayMemory()

    def has_nodes(self):
        all_nodes = super(BusManager, self).get_mgmt_nodes()
        has_nodes = True
        if all_nodes:
            if len(all_nodes) < 2:
                has_nodes = False
        else:
           has_nodes = False

        return has_nodes, all_nodes

    def show_entity(self, main):
        if   main == 'l': self.displayRouterLinks()
        elif main == 'n': self.displayRouterNodes()
        elif main == 'a': self.displayAddresses()
        elif main == 'm': self.displayMemory()
        elif main == 'g': self.displayGeneral()
        elif main == 'e': self.displayEdges()
        elif main == 'c': self.displayConnections()
        elif main == 'autolinks': self.displayAutolinks()
        elif main == 'linkroutes': self.displayLinkRoutes()
        elif main == 'log': self.displayLog()

    def all_routers_entities(self):
        has_nodes, nodes = self.has_nodes()
        if has_nodes:
            for node in nodes:
                super(BusManager, self).set_client(node[6:])
                parts = node.split("/")
                print ("Router ", parts[3])
                self.show_all()
                print("")
        else:
            self.show_all()

    def displayMain(self, identitys, opts):
        main = opts.show
        if opts.all_routers and main:
            print (str(datetime.now()))
            has_nodes, nodes = self.has_nodes()
            if has_nodes:
                for node in nodes:
                    super(BusManager, self).set_client(node[6:])
                    parts = node.split("/")
                    print ("Router ", parts[3])
                    self.show_entity(main)
                    print("")
            else:
                self.show_entity(main)
        elif opts.all_routers and opts.all_entities:
            print (str(datetime.now()))
            self.all_routers_entities()
        elif opts.all_entities:
            print (str(datetime.now()))
            # Display all the relevant entities from the one router that
            # qdstat is connecting to.
            self.show_all()
        elif main:
            self.show_entity(main)

    def display(self, identitys):
        self.displayMain(identitys, self.opts)

def run(argv):
    opts, args = parse_args(argv)
    if args[1:]:
        raise Exception("Unexpected arguments: %s" % " ".join(args[1:]))
    bm = BusManager(opts)
    try:
        bm.display(args)
    finally:
        bm.close()

if __name__ == "__main__":
        sys.exit(main(run, sys.argv))
