Added prettytable (http://code.google.com/p/prettytable/) as a replacement for utils.format_table and updated almost all places where the old formatter was used. The code becomes much simpler and shorter with prettytable, there are some situations, such as the contrib/menusystem where the old format_table works well for dynamically creating any number of columns and rows on the fly.

This commit is contained in:
Griatch 2013-04-12 22:06:41 +02:00
parent 0fddf433dc
commit 8969017aaa
10 changed files with 1802 additions and 451 deletions

View file

@ -13,6 +13,7 @@ a ScriptableObject. It will handle access checks.
from ev import utils from ev import utils
from ev import default_cmds from ev import default_cmds
from src.utils import prettytable
#------------------------------------------------------------ #------------------------------------------------------------
# Evlang-related commands # Evlang-related commands
@ -94,17 +95,10 @@ class CmdCode(default_cmds.MuxCommand):
scripts.extend([(name, "--", "--") for name in evlang_locks if name not in evlang_scripts]) scripts.extend([(name, "--", "--") for name in evlang_locks if name not in evlang_scripts])
scripts = sorted(scripts, key=lambda p: p[0]) scripts = sorted(scripts, key=lambda p: p[0])
table = [["type"] + [tup[0] for tup in scripts], table = prettytable.PrettyTable(["{wtype", "{wcreator", "{wcode"])
["creator"] + [tup[1] for tup in scripts], for tup in scripts:
["code"] + [tup[2] for tup in scripts]] table.add_row([tup[0], tup[1], tup[2]])
ftable = utils.format_table(table, extra_space=5) string = "{wEvlang scripts on %s:{n\n%s" % (obj.key, table)
string = "{wEvlang scripts on %s:{n" % obj.key
for irow, row in enumerate(ftable):
if irow == 0:
string += "\n" + "".join("{w%s{n" % col for col in row)
else:
string += "\n" + "".join(col for col in row)
caller.msg(string) caller.msg(string)
return return
@ -131,6 +125,3 @@ class CmdCode(default_cmds.MuxCommand):
# debug mode # debug mode
caller.msg("{wDebug: running script (look out for errors below) ...{n\n" + "-"*68) caller.msg("{wDebug: running script (look out for errors below) ...{n\n" + "-"*68)
obj.ndb.evlang.run_by_name(codetype, caller, quiet=False) obj.ndb.evlang.run_by_name(codetype, caller, quiet=False)

View file

@ -10,7 +10,7 @@ from django.contrib.auth.models import User
from src.players.models import PlayerDB from src.players.models import PlayerDB
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
from src.server.models import ServerConfig from src.server.models import ServerConfig
from src.utils import utils from src.utils import utils, prettytable
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY] PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]
@ -106,29 +106,17 @@ IPREGEX = re.compile(r"[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}\.[0-9*]{1,3}")
def list_bans(banlist): def list_bans(banlist):
""" """
Helper function to display a list of active bans. Input argument Helper function to display a list of active bans. Input argument
is the banlist read into the two commands @ban and @undban below. is the banlist read into the two commands @ban and @unban below.
""" """
if not banlist: if not banlist:
return "No active bans were found." return "No active bans were found."
table = [["id"], ["name/ip"], ["date"], ["reason"]] table = prettytable.PrettyTable(["{wid", "{wname/ip", "{wdate", "{wreason"])
table[0].extend([str(i+1) for i in range(len(banlist))]) for inum, ban in enumerate(banlist):
for ban in banlist: table.add_row([str(inum+1),
if ban[0]: ban[0] and ban[0] or ban[1],
table[1].append(ban[0]) ban[3], ban[4]])
else: string = "{wActive bans:{n\n%s" % table
table[1].append(ban[1])
table[2].extend([ban[3] for ban in banlist])
table[3].extend([ban[4] for ban in banlist])
ftable = utils.format_table(table, 4)
string = "{wActive bans:{x"
for irow, row in enumerate(ftable):
if irow == 0:
srow = "\n" + "".join(row)
srow = "{w%s{n" % srow.rstrip()
else:
srow = "\n" + "{w%s{n" % row[0] + "".join(row[1:])
string += srow.rstrip()
return string return string
class CmdBan(MuxCommand): class CmdBan(MuxCommand):

View file

@ -39,6 +39,7 @@ class CharacterCmdSet(CmdSet):
self.add(system.CmdPy()) self.add(system.CmdPy())
self.add(system.CmdScripts()) self.add(system.CmdScripts())
self.add(system.CmdObjects()) self.add(system.CmdObjects())
self.add(system.CmdPlayers())
self.add(system.CmdService()) self.add(system.CmdService())
self.add(system.CmdAbout()) self.add(system.CmdAbout())
self.add(system.CmdTime()) self.add(system.CmdTime())

View file

@ -11,7 +11,7 @@ from django.conf import settings
from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChannelConnection from src.comms.models import Channel, Msg, PlayerChannelConnection, ExternalChannelConnection
from src.comms import irc, imc2, rss from src.comms import irc, imc2, rss
from src.comms.channelhandler import CHANNELHANDLER from src.comms.channelhandler import CHANNELHANDLER
from src.utils import create, utils from src.utils import create, utils, prettytable
from src.commands.default.muxcommand import MuxCommand, MuxPlayerCommand from src.commands.default.muxcommand import MuxCommand, MuxPlayerCommand
# limit symbol import for API # limit symbol import for API
@ -33,12 +33,12 @@ def find_channel(caller, channelname, silent=False, noaliases=False):
if channels: if channels:
return channels[0] return channels[0]
if not silent: if not silent:
self.msg("Channel '%s' not found." % channelname) caller.msg("Channel '%s' not found." % channelname)
return None return None
elif len(channels) > 1: elif len(channels) > 1:
matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels]) matches = ", ".join(["%s(%s)" % (chan.key, chan.id) for chan in channels])
if not silent: if not silent:
self.msg("Multiple channels match (be more specific): \n%s" % matches) caller.msg("Multiple channels match (be more specific): \n%s" % matches)
return None return None
return channels[0] return channels[0]
@ -238,6 +238,7 @@ class CmdChannels(MuxCommand):
Lists all channels available to you, wether you listen to them or not. Lists all channels available to you, wether you listen to them or not.
Use 'comlist" to only view your current channel subscriptions. Use 'comlist" to only view your current channel subscriptions.
Use addcom/delcom to join and leave channels
""" """
key = "@channels" key = "@channels"
aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"] aliases = ["@clist", "channels", "comlist", "chanlist", "channellist", "all channels"]
@ -257,46 +258,27 @@ class CmdChannels(MuxCommand):
# all channel we are already subscribed to # all channel we are already subscribed to
subs = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller)] subs = [conn.channel for conn in PlayerChannelConnection.objects.get_all_player_connections(caller)]
if self.cmdstring != "comlist": if self.cmdstring == "comlist":
# just display the subscribed channels with no extra info
string = "\nChannels available:" comtable = prettytable.PrettyTable(["{wchannel","{wmy aliases", "{wdescription"])
cols = [[" "], ["Channel"], ["Aliases"], ["Perms"], ["Description"]]
for chan in channels:
if chan in subs:
cols[0].append(">")
else:
cols[0].append(" ")
cols[1].append(chan.key)
cols[2].append(",".join(chan.aliases))
cols[3].append(str(chan.locks))
cols[4].append(chan.desc)
# put into table
for ir, row in enumerate(utils.format_table(cols)):
if ir == 0:
string += "\n{w" + "".join(row) + "{n"
else:
string += "\n" + "".join(row)
self.msg(string)
string = "\nChannel subscriptions:"
if not subs:
string += "(None)"
else:
nicks = [nick for nick in caller.nicks.get(nick_type="channel")]
cols = [[" "], ["Channel"], ["Aliases"], ["Description"]]
for chan in subs: for chan in subs:
cols[0].append(" ") clower = chan.key.lower()
cols[1].append(chan.key) nicks = [nick for nick in caller.nicks.get(nick_type="channel")]
cols[2].append(",".join([nick.db_nick for nick in nicks comtable.add_row(["%s%s" % (chan.key, chan.aliases and "(%s)" % ",".join(chan.aliases) or ""),
if nick.db_real.lower() == chan.key.lower()] + chan.aliases)) "%s".join(nick.db_nick for nick in nicks if nick.db_real.lower()==clower()),
cols[3].append(chan.desc) chan.desc])
# put into table caller.msg("\n{wChannel subscriptions{n (use {w@channels{n to list all, {waddcom{n/{wdelcom{n to sub/unsub):{n\n%s" % comtable)
for ir, row in enumerate(utils.format_table(cols)): else:
if ir == 0: # full listing (of channels caller is able to listen to)
string += "\n{w" + "".join(row) + "{n" comtable = prettytable.PrettyTable(["{wsub","{wchannel","{wmy aliases","{wlocks","{wdescription"])
else: for chan in channels:
string += "\n" + "".join(row) nicks = [nick for nick in caller.nicks.get(nick_type="channel")]
self.msg(string) comtable.add_row([chan in subs and "{gYes{n" or "{rNo{n",
"%s%s" % (chan.key, chan.aliases and "(%s)" % ",".join(chan.aliases) or ""),
"%s".join(nick.db_nick for nick in nicks if nick.db_real.lower()==clower()),
chan.locks,
chan.desc])
caller.msg("\n{wAvailable channels{n (use {wcomlist{n,{waddcom{n and {wdelcom{n to manage subscriptions):\n%s" % comtable)
class CmdCdestroy(MuxCommand): class CmdCdestroy(MuxCommand):
""" """
@ -774,17 +756,10 @@ class CmdIRC2Chan(MuxCommand):
# show all connections # show all connections
connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='irc_') connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='irc_')
if connections: if connections:
cols = [["Evennia channel"], ["IRC channel"]] table = prettytable.PrettyTable(["Evennia channel", "IRC channel"])
for conn in connections: for conn in connections:
cols[0].append(conn.channel.key) table.add_row([conn.channel.key, " ".join(conn.external_config.split('|'))])
cols[1].append(" ".join(conn.external_config.split('|'))) string = "{wIRC connections:{n\n%s" % table
ftable = utils.format_table(cols)
string = ""
for ir, row in enumerate(ftable):
if ir == 0:
string += "{w%s{n" % "".join(row)
else:
string += "\n" + "".join(row)
self.msg(string) self.msg(string)
else: else:
self.msg("No connections found.") self.msg("No connections found.")
@ -863,18 +838,10 @@ class CmdIMC2Chan(MuxCommand):
# show all connections # show all connections
connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='imc2_') connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='imc2_')
if connections: if connections:
cols = [["Evennia channel"], ["<->"], ["IMC channel"]] table = prettytable.PrettyTable(["Evennia channel", "IMC channel"])
for conn in connections: for conn in connections:
cols[0].append(conn.channel.key) table.add_row([conn.channel.key, conn.external_config])
cols[1].append("") string = "{wIMC connections:{n\n%s" % table
cols[2].append(conn.external_config)
ftable = utils.format_table(cols)
string = ""
for ir, row in enumerate(ftable):
if ir == 0:
string += "{w%s{n" % "".join(row)
else:
string += "\n" + "".join(row)
self.msg(string) self.msg(string)
else: else:
self.msg("No connections found.") self.msg("No connections found.")
@ -966,20 +933,11 @@ class CmdIMCInfo(MuxCommand):
string = "" string = ""
nmuds = 0 nmuds = 0
for network in networks: for network in networks:
string += "\n {GMuds registered on %s:{n" % network table = prettytable.PrettyTable(["Name", "Url", "Host", "Port"])
cols = [["Name"], ["Url"], ["Host"], ["Port"]]
for mud in (mud for mud in muds if mud.networkname == network): for mud in (mud for mud in muds if mud.networkname == network):
nmuds += 1 nmuds += 1
cols[0].append(mud.name) table.add_row([mud.name, mud.url, mud.host, mud.port])
cols[1].append(mud.url) string += "\n{wMuds registered on %s:{n\n%s" % (network, table)
cols[2].append(mud.host)
cols[3].append(mud.port)
ftable = utils.format_table(cols)
for ir, row in enumerate(ftable):
if ir == 0:
string += "\n{w" + "".join(row) + "{n"
else:
string += "\n" + "".join(row)
string += "\n %i Muds found." % nmuds string += "\n %i Muds found." % nmuds
self.msg(string) self.msg(string)
@ -999,24 +957,13 @@ class CmdIMCInfo(MuxCommand):
channels = IMC2_CHANLIST.get_channel_list() channels = IMC2_CHANLIST.get_channel_list()
string = "" string = ""
nchans = 0 nchans = 0
string += "\n {GChannels on %s:{n" % IMC2_CLIENT.factory.network table = prettytable.PrettyTable(["Full name", "Name", "Owner", "Perm", "Policy"])
cols = [["Full name"], ["Name"], ["Owner"], ["Perm"], ["Policy"]] for chan in channels:
for channel in channels:
nchans += 1 nchans += 1
cols[0].append(channel.name) table.add_row([chan.name, chan.localname, chan.owner, chan.level, chan.policy])
cols[1].append(channel.localname) string += "\n{wChannels on %s:{n\n%s" % (IMC2_CLIENT.factory.network, table)
cols[2].append(channel.owner) string += "\n%i Channels found." % nchans
cols[3].append(channel.level)
cols[4].append(channel.policy)
ftable = utils.format_table(cols)
for ir, row in enumerate(ftable):
if ir == 0:
string += "\n{w" + "".join(row) + "{n"
else:
string += "\n" + "".join(row)
string += "\n %i Channels found." % nchans
self.msg(string) self.msg(string)
else: else:
# no valid inputs # no valid inputs
string = "Usage: imcinfo|imcchanlist|imclist" string = "Usage: imcinfo|imcchanlist|imclist"
@ -1104,17 +1051,10 @@ class CmdRSS2Chan(MuxCommand):
# show all connections # show all connections
connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='rss_') connections = ExternalChannelConnection.objects.filter(db_external_key__startswith='rss_')
if connections: if connections:
cols = [["Evennia-channel"], ["RSS-url"]] table = prettytable.PrettyTable(["Evennia channel", "RSS url"])
for conn in connections: for conn in connections:
cols[0].append(conn.channel.key) table.add_row([conn.channel.key, conn.external_config.split('|')[0]])
cols[1].append(conn.external_config.split('|')[0]) string = "{wConnections to RSS:{n\n%s" % table
ftable = utils.format_table(cols)
string = ""
for ir, row in enumerate(ftable):
if ir == 0:
string += "{w%s{n" % "".join(row)
else:
string += "\n" + "".join(row)
self.msg(string) self.msg(string)
else: else:
self.msg("No connections found.") self.msg("No connections found.")

View file

@ -2,7 +2,7 @@
General Character commands usually availabe to all characters General Character commands usually availabe to all characters
""" """
from django.conf import settings from django.conf import settings
from src.utils import utils from src.utils import utils, prettytable
from src.objects.models import ObjectNick as Nick from src.objects.models import ObjectNick as Nick
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
@ -124,20 +124,13 @@ class CmdNick(MuxCommand):
caller = self.caller caller = self.caller
switches = self.switches switches = self.switches
nicks = Nick.objects.filter(db_obj=caller.dbobj).exclude(db_type="channel") nicks = Nick.objects.filter(db_obj=caller.dbobj).exclude(db_type="channel")
if 'list' in switches: if 'list' in switches:
string = "{wDefined Nicks:{n" table = prettytable.PrettyTable(["{wNickType", "{wNickname", "{wTranslates-to"])
cols = [["Type"],["Nickname"],["Translates-to"] ]
for nick in nicks: for nick in nicks:
cols[0].append(nick.db_type) table.add_row([nick.db_type, nick.db_nick, nick.db_real])
cols[1].append(nick.db_nick) string = "{wDefined Nicks:{n\n%s" % table
cols[2].append(nick.db_real)
for ir, row in enumerate(utils.format_table(cols)):
if ir == 0:
string += "\n{w" + "".join(row) + "{n"
else:
string += "\n" + "".join(row)
caller.msg(string) caller.msg(string)
return return
if 'clearall' in switches: if 'clearall' in switches:
@ -197,19 +190,12 @@ class CmdInventory(MuxCommand):
if not items: if not items:
string = "You are not carrying anything." string = "You are not carrying anything."
else: else:
# format item list into nice collumns table = prettytable.PrettyTable(["name", "desc"])
cols = [[],[]] table.header = False
table.border = False
for item in items: for item in items:
cols[0].append(item.name) table.add_row(["{C%s{n" % item.name, item.db.desc and item.db.desc or ""])
desc = item.db.desc string = "{wYou are carrying:\n%s" % table
if not desc:
desc = ""
cols[1].append(utils.crop(str(desc)))
# auto-format the columns to make them evenly wide
ftable = utils.format_table(cols)
string = "You are carrying:"
for row in ftable:
string += "\n " + "{C%s{n - %s" % (row[0], row[1])
self.caller.msg(string) self.caller.msg(string)
class CmdGet(MuxCommand): class CmdGet(MuxCommand):

View file

@ -20,7 +20,7 @@ import time
from django.conf import settings from django.conf import settings
from src.server.sessionhandler import SESSIONS from src.server.sessionhandler import SESSIONS
from src.commands.default.muxcommand import MuxPlayerCommand from src.commands.default.muxcommand import MuxPlayerCommand
from src.utils import utils, create, search from src.utils import utils, create, search, prettytable
from settings import MAX_NR_CHARACTERS, MULTISESSION_MODE from settings import MAX_NR_CHARACTERS, MULTISESSION_MODE
# limit symbol import for API # limit symbol import for API
@ -302,27 +302,18 @@ class CmdSessions(MuxPlayerCommand):
def func(self): def func(self):
"Implement function" "Implement function"
# make sure we work on the player, not on the character
player = self.caller player = self.caller
sessions = player.get_all_sessions() sessions = player.get_all_sessions()
table = [["sessid"], ['protocol'], ["host"], ["puppet/character"], ["location"]] table = prettytable.PrettyTable(["{wsessid", "{wprotocol", "{whost", "{wpuppet/character", "{wlocation"])
for sess in sorted(sessions, key=lambda x:x.sessid): for sess in sorted(sessions, key=lambda x:x.sessid):
sessid = sess.sessid sessid = sess.sessid
char = player.get_puppet(sessid) char = player.get_puppet(sessid)
table[0].append(str(sess.sessid)) table.add_row([str(sessid), str(sess.protocol_key),
table[1].append(str(sess.protocol_key)) type(sess.address)==tuple and sess.address[0] or sess.address,
table[2].append(type(sess.address)==tuple and sess.address[0] or sess.address) char and str(char) or "None",
table[3].append(char and str(char) or "None") char and str(char.location) or "N/A"])
table[4].append(char and str(char.location) or "N/A") string = "{wYour current session(s):{n\n%s" % table
ftable = utils.format_table(table, 5)
string = "{wYour current session(s):{n"
for ir, row in enumerate(ftable):
if ir == 0:
string += "\n" + "{w%s{n" % ("".join(row))
else:
string += "\n" + "".join(row)
self.msg(string) self.msg(string)
class CmdWho(MuxPlayerCommand): class CmdWho(MuxPlayerCommand):
@ -354,52 +345,36 @@ class CmdWho(MuxPlayerCommand):
else: else:
show_session_data = player.check_permstring("Immortals") or player.check_permstring("Wizards") show_session_data = player.check_permstring("Immortals") or player.check_permstring("Wizards")
if show_session_data:
table = [["Player Name"], ["On for"], ["Idle"], ["Room"], ["Cmds"], ["Host"]]
else:
table = [["Player Name"], ["On for"], ["Idle"]]
for session in session_list:
if not session.logged_in:
continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
plr_pobject = session.get_puppet()
if not plr_pobject:
plr_pobject = session.get_player()
show_session_data = False
table = [["Player Name"], ["On for"], ["Idle"]]
if show_session_data:
table[0].append(plr_pobject.name[:25])
table[1].append(utils.time_format(delta_conn, 0))
table[2].append(utils.time_format(delta_cmd, 1))
table[3].append(plr_pobject.location and plr_pobject.location.id or "None")
table[4].append(session.cmd_total)
table[5].append(session.address[0])
else:
table[0].append(plr_pobject.name[:25])
table[1].append(utils.time_format(delta_conn,0))
table[2].append(utils.time_format(delta_cmd,1))
stable = []
for row in table: # prettify values
stable.append([str(val).strip() for val in row])
ftable = utils.format_table(stable, 5)
string = ""
for ir, row in enumerate(ftable):
if ir == 0:
string += "\n" + "{w%s{n" % ("".join(row))
else:
string += "\n" + "".join(row)
nplayers = (SESSIONS.player_count()) nplayers = (SESSIONS.player_count())
if nplayers == 1: if show_session_data:
string += '\nOne player logged in.' table = prettytable.PrettyTable(["{wPlayer Name","{wOn for", "{wIdle", "{wRoom", "{wCmds", "{wHost"])
for session in session_list:
if not session.logged_in: continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
plr_pobject = session.get_puppet()
plr_pobject = plr_pobject or session.get_player()
table.add_row([utils.crop(plr_pobject.name, width=25),
utils.time_format(delta_conn, 0),
utils.time_format(delta_cmd, 1),
hasattr(plr_pobject, "location") and plr_pobject.location or "None",
session.cmd_total, type(session.address==tuple) and session.address[0] or session.address])
else: else:
string += '\n%d players logged in.' % nplayers table = prettytable.PrettyTable(["{wPlayer name", "{wOn for", "{Idle"])
for session in session_list:
if not session.logged_in: continue
delta_cmd = time.time() - session.cmd_last_visible
delta_conn = time.time() - session.conn_time
plr_pobject = session.get_puppet()
plr_pobject = plr_pobject or session.get_player()
table.add_row([utils.crop(plr_pobject.name, width=25),
utils.time.format(delta_conn, 0),
utils,time_format(delta_cmd, 1)])
string = "{wPlayers:\n%s\n%s logged in." % (table, nplayers==1 and "One player" or nplayer)
self.msg(string) self.msg(string)
class CmdEncoding(MuxPlayerCommand): class CmdEncoding(MuxPlayerCommand):
""" """
encoding - set a custom text encoding encoding - set a custom text encoding
@ -546,15 +521,29 @@ class CmdColorTest(MuxPlayerCommand):
locks = "cmd:all()" locks = "cmd:all()"
help_category = "General" help_category = "General"
def table_format(self, table):
"""
Helper method to format the ansi/xterm256 tables.
Takes a table of columns [[val,val,...],[val,val,...],...]
"""
if not table:
return [[]]
extra_space = 1
max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)])
return ftable
def func(self): def func(self):
"Show color tables" "Show color tables"
player = self.caller player = self.caller
if not self.args or not self.args in ("ansi", "xterm256"):
self.msg("Usage: @color ansi|xterm256")
return
if self.args == "ansi": if self.args.startswith("a"):
# show ansi 16-color table
from src.utils import ansi from src.utils import ansi
ap = ansi.ANSI_PARSER ap = ansi.ANSI_PARSER
# ansi colors # ansi colors
@ -570,7 +559,9 @@ class CmdColorTest(MuxPlayerCommand):
#print string #print string
self.msg(string) self.msg(string)
self.msg("({{X and %%cx are black-on-black\n %%r - return, %%t - tab, %%b - space)") self.msg("({{X and %%cx are black-on-black\n %%r - return, %%t - tab, %%b - space)")
elif self.args == "xterm256":
elif self.args.startswith("x"):
# show xterm256 table
table = [[],[],[],[],[],[],[],[],[],[],[],[]] table = [[],[],[],[],[],[],[],[],[],[],[],[]]
for ir in range(6): for ir in range(6):
for ig in range(6): for ig in range(6):
@ -581,11 +572,13 @@ class CmdColorTest(MuxPlayerCommand):
table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib, table[6+ir].append("%%cb%i%i%i%%c%i%i%i%s{n" % (ir,ig,ib,
5-ir,5-ig,5-ib, 5-ir,5-ig,5-ib,
"{{b%i%i%i" % (ir,ig,ib))) "{{b%i%i%i" % (ir,ig,ib)))
table = utils.format_table(table) table = self.table_format(table)
string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):" string = "Xterm256 colors (if not all hues show, your client might not report that it can handle xterm256):"
for row in table: for row in table:
string += "\n" + "".join(row) string += "\n" + "".join(row)
self.msg(string) self.msg(string)
self.msg("(e.g. %%c123 and %%cb123 also work)") self.msg("(e.g. %%c123 and %%cb123 also work)")
else:
# malformed input
self.msg("Usage: @color ansi|xterm256")

View file

@ -17,7 +17,7 @@ from src.server.sessionhandler import SESSIONS
from src.scripts.models import ScriptDB from src.scripts.models import ScriptDB
from src.objects.models import ObjectDB from src.objects.models import ObjectDB
from src.players.models import PlayerDB from src.players.models import PlayerDB
from src.utils import logger, utils, gametime, create, is_pypy from src.utils import logger, utils, gametime, create, is_pypy, prettytable
from src.commands.default.muxcommand import MuxCommand from src.commands.default.muxcommand import MuxCommand
# delayed imports # delayed imports
@ -210,47 +210,20 @@ def format_script_list(scripts):
if not scripts: if not scripts:
return "<No scripts>" return "<No scripts>"
table = [["id"], ["obj"], ["key"], ["intval"], ["next"], ["rept"], ["db"], ["typeclass"], ["desc"]] table = prettytable.PrettyTable(["{wid","{wobj","{wkey","{wintval","{wnext","{wrept","{wdb"," {wtypeclass","{wdesc"],align='r')
table.align = 'r'
for script in scripts: for script in scripts:
nextrep = script.time_until_next_repeat()
table[0].append(script.id) table.add_row([script.id,
if not hasattr(script, 'obj') or not script.obj: (not hasattr(script, 'obj') or not script.obj) and "<Global>" or script.obj.key,
table[1].append("<Global>") script.key,
else: (not hasattr(script, 'interval') or script.interval < 0) and "--" or "%ss" % script.interval,
table[1].append(script.obj.key) not nextrep and "--" or "%ss" % nextrep,
table[2].append(script.key) (not hasattr(script, 'repeats') or not script.repeats) and "--" or "%i" % script.repeats,
if not hasattr(script, 'interval') or script.interval < 0: script.persistent and "*" or "-",
table[3].append("--") script.typeclass_path.rsplit('.', 1)[-1],
else: script.desc])
table[3].append("%ss" % script.interval) return "%s" % table
next = script.time_until_next_repeat()
if not next:
table[4].append("--")
else:
table[4].append("%ss" % next)
if not hasattr(script, 'repeats') or not script.repeats:
table[5].append("--")
else:
table[5].append("%s" % script.repeats)
if script.persistent:
table[6].append("*")
else:
table[6].append("-")
typeclass_path = script.typeclass_path.rsplit('.', 1)
table[7].append("%s" % typeclass_path[-1])
table[8].append(script.desc)
ftable = utils.format_table(table)
string = ""
for irow, row in enumerate(ftable):
if irow == 0:
srow = "\n" + "".join(row)
srow = "{w%s{n" % srow.rstrip()
else:
srow = "\n" + "{w%s{n" % row[0] + "".join(row[1:])
string += srow.rstrip()
return string.strip()
class CmdScripts(MuxCommand): class CmdScripts(MuxCommand):
@ -352,7 +325,7 @@ class CmdScripts(MuxCommand):
class CmdObjects(MuxCommand): class CmdObjects(MuxCommand):
""" """
Give a summary of object types in database @objects - Give a summary of object types in database
Usage: Usage:
@objects [<nr>] @objects [<nr>]
@ -376,54 +349,81 @@ class CmdObjects(MuxCommand):
else: else:
nlim = 10 nlim = 10
string = "\n{wDatabase totals:{n"
nplayers = PlayerDB.objects.count()
nobjs = ObjectDB.objects.count() nobjs = ObjectDB.objects.count()
base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS base_char_typeclass = settings.BASE_CHARACTER_TYPECLASS
nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count() nchars = ObjectDB.objects.filter(db_typeclass_path=base_char_typeclass).count()
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=base_char_typeclass).count() nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=base_char_typeclass).count()
nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count() nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count()
nother = nobjs - nchars - nrooms - nexits
string += "\n{wPlayers:{n %i" % nplayers # total object sum table
string += "\n{wObjects:{n %i" % nobjs totaltable = prettytable.PrettyTable(["{wtype","{wcomment","{wcount", "{w%%"])
string += "\n{w Characters (BASE_CHARACTER_TYPECLASS):{n %i" % nchars totaltable.align = 'l'
string += "\n{w Rooms (location==None):{n %i" % nrooms totaltable.add_row(["Characters", "(BASE_CHARACTER_TYPECLASS)", nchars, "%.2f" % ((float(nchars)/nobjs)*100)])
string += "\n{w Exits (destination!=None):{n %i" % nexits totaltable.add_row(["Rooms", "(location=None)", nrooms, "%.2f" % ((float(nrooms)/nobjs)*100)])
string += "\n{w Other:{n %i\n" % (nobjs - nchars - nrooms - nexits) totaltable.add_row(["Exits", "(destination!=None)", nexits, "%.2f" % ((float(nexits)/nobjs)*100)])
totaltable.add_row(["Other", "", nother, "%.2f" % ((float(nother)/nobjs)*100)])
# typeclass table
typetable = prettytable.PrettyTable(["{wtypeclass","{wcount", "{w%%"])
typetable.align = 'l'
dbtotals = ObjectDB.objects.object_totals() dbtotals = ObjectDB.objects.object_totals()
table = [["Count"], ["Typeclass"]]
for path, count in dbtotals.items(): for path, count in dbtotals.items():
table[0].append(count) typetable.add_row([path, count, "%.2f" % ((float(count)/nobjs)*100)])
table[1].append(path)
ftable = utils.format_table(table, 3)
for irow, row in enumerate(ftable):
srow = "\n" + "".join(row)
srow = srow.rstrip()
if irow == 0:
srow = "{w%s{n" % srow
string += srow
string += "\n\n{wLast %s Objects created:{n" % min(nobjs, nlim) # last N table
objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):] objs = ObjectDB.objects.all().order_by("db_date_created")[max(0, nobjs - nlim):]
latesttable = prettytable.PrettyTable(["{wcreated","{wdbref","{wname","{wtypeclass"])
latesttable.align = 'l'
for obj in objs:
latesttable.add_row([utils.datetime_format(obj.date_created), obj.dbref, obj.key, obj.typeclass.path])
table = [["Created"], ["dbref"], ["name"], ["typeclass"]] string = "\n{wObject subtype totals (out of %i Objects):{n\n%s" % (nobjs, totaltable)
for i, obj in enumerate(objs): string += "\n{wObject typeclass distribution:{n\n%s" % typetable
table[0].append(utils.datetime_format(obj.date_created)) string += "\n{wLast %s Objects created:{n\n%s" % (min(nobjs, nlim), latesttable)
table[1].append(obj.dbref)
table[2].append(obj.key)
table[3].append(str(obj.typeclass.path))
ftable = utils.format_table(table, 5)
for irow, row in enumerate(ftable):
srow = "\n" + "".join(row)
srow = srow.rstrip()
if irow == 0:
srow = "{w%s{n" % srow
string += srow
caller.msg(string) caller.msg(string)
class CmdPlayers(MuxCommand):
"""
@players - give a summary of all registed Players
Usage:
@players [nr]
Lists statistics about the Players registered with the game.
It will list the <nr> amount of latest registered players
If not given, <nr> defaults to 10.
"""
key = "@players"
aliases = ["@listplayers"]
locks = "cmd:perm(listplayers) or perm(Admins)"
def func(self):
"List the players"
caller = self.caller
if self.args and self.args.is_digit():
nlim = int(self.args)
else:
nlim = 10
nplayers = PlayerDB.objects.count()
# typeclass table
dbtotals = PlayerDB.objects.object_totals()
typetable = prettytable.PrettyTable(["{wtypeclass", "{wcount", "{w%%"])
typetable.align = 'l'
for path, count in dbtotals.items():
typetable.add_row([path, count, "%.2f" % ((float(count)/nplayers)*100)])
# last N table
plyrs = PlayerDB.objects.all().order_by("db_date_created")[max(0, nplayers - nlim):]
latesttable = prettytable.PrettyTable(["{wcreated", "{wdbref","{wname","{wtypeclass"])
latesttable.align = 'l'
for ply in plyrs:
latesttable.add_row([utils.datetime_format(ply.date_created), ply.dbref, ply.key, ply.typeclass.path])
string = "\n{wPlayer typeclass distribution:{n\n%s" % typetable
string += "\n{wLast %s Players created:{n\n%s" % (min(nplayers, nlim), latesttable)
caller.msg(string)
class CmdService(MuxCommand): class CmdService(MuxCommand):
""" """
@ -468,18 +468,11 @@ class CmdService(MuxCommand):
if not switches or switches[0] == "list": if not switches or switches[0] == "list":
# Just display the list of installed services and their # Just display the list of installed services and their
# status, then exit. # status, then exit.
string = "-" * 78 table = prettytable.PrettyTable(["{wService{n (use @services/start|stop|delete)", "{wstatus"])
string += "\n{wServices{n (use @services/start|stop|delete):" table.align = 'l'
for service in service_collection.services: for service in service_collection.services:
if service.running: table.add_row([service.name, service.running and "{gRunning" or "{rNot Running"])
status = 'Running' caller.msg(str(table))
string += '\n * {g%s{n (%s)' % (service.name, status)
else:
status = 'Inactive'
string += '\n {R%s{n (%s)' % (service.name, status)
string += "\n" + "-" * 78
caller.msg(string)
return return
# Get the service to start / stop # Get the service to start / stop
@ -581,7 +574,7 @@ class CmdTime(MuxCommand):
Usage: Usage:
@time @time
Server local time. Server time statistics.
""" """
key = "@time" key = "@time"
aliases = "@uptime" aliases = "@uptime"
@ -589,30 +582,14 @@ class CmdTime(MuxCommand):
help_category = "System" help_category = "System"
def func(self): def func(self):
"Show times." "Show server time data in a table."
table = prettytable.PrettyTable(["{wserver time statistic","{wtime"])
table = [["Current server uptime:", table.align = 'l'
"Total server running time:", table.add_row(["Current server uptime", utils.time_format(time.time() - SESSIONS.server.start_time, 3)])
"Total in-game time (realtime x %g):" % (gametime.TIMEFACTOR), table.add_row(["Total server running time", utils.time_format(gametime.runtime(format=False), 2)])
"Server time stamp:" table.add_row(["Total in-game time (realtime x %g" % (gametime.TIMEFACTOR), utils.time_format(gametime.gametime(format=False), 2)])
], table.add_row(["Server time stamp", datetime.datetime.now()])
[utils.time_format(time.time() - SESSIONS.server.start_time, 3), self.caller.msg(str(table))
utils.time_format(gametime.runtime(format=False), 2),
utils.time_format(gametime.gametime(format=False), 2),
datetime.datetime.now()
]]
if utils.host_os_is('posix'):
loadavg = os.getloadavg()
table[0].append("Server load (per minute):")
table[1].append("%g" % (loadavg[0]))
stable = []
for col in table:
stable.append([str(val).strip() for val in col])
ftable = utils.format_table(stable, 5)
string = ""
for row in ftable:
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
self.caller.msg(string)
class CmdServerLoad(MuxCommand): class CmdServerLoad(MuxCommand):
""" """
@ -656,92 +633,60 @@ class CmdServerLoad(MuxCommand):
if not utils.host_os_is('posix'): if not utils.host_os_is('posix'):
string = "Process listings are only available under Linux/Unix." string = "Process listings are only available under Linux/Unix."
else: caller.msg(string)
global _resource, _idmapper return
if not _resource:
import resource as _resource
if not _idmapper:
from src.utils.idmapper import base as _idmapper
import resource global _resource, _idmapper
loadavg = os.getloadavg() if not _resource:
psize = _resource.getpagesize() import resource as _resource
pid = os.getpid() if not _idmapper:
rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1024.0 from src.utils.idmapper import base as _idmapper
vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1024.0
rusage = resource.getrusage(resource.RUSAGE_SELF) import resource
table = [["Server load (1 min):", loadavg = os.getloadavg()
"Process ID:", psize = _resource.getpagesize()
"Bytes per page:", pid = os.getpid()
"CPU time used:", rmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "rss")).read()) / 1024.0 # resident memory
"Resident memory:", vmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "vsz")).read()) / 1024.0 # virtual memory
"Virtual memory:", pmem = float(os.popen('ps -p %d -o %s | tail -1' % (pid, "%mem")).read()) # percent of resident memory to total
"Page faults:", rusage = resource.getrusage(resource.RUSAGE_SELF)
"Disk I/O:",
"Network I/O:",
"Context switching:"
],
["%g" % loadavg[0],
"%10d" % pid,
"%10d " % psize,
"%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime),
#"%10d shared" % rusage.ru_ixrss,
#"%10d pages" % rusage.ru_maxrss,
"%10.2f MB" % rmem,
"%10.2f MB" % vmem,
"%10d hard" % rusage.ru_majflt,
"%10d reads" % rusage.ru_inblock,
"%10d in" % rusage.ru_msgrcv,
"%10d vol" % rusage.ru_nvcsw
],
["", "", "",
"(user: %gs)" % rusage.ru_stime,
"", #"%10d private" % rusage.ru_idrss,
"", #"%10d bytes" % (rusage.ru_maxrss * psize),
"%10d soft" % rusage.ru_minflt,
"%10d writes" % rusage.ru_oublock,
"%10d out" % rusage.ru_msgsnd,
"%10d forced" % rusage.ru_nivcsw
],
["", "", "", "",
"", #"%10d stack" % rusage.ru_isrss,
"",
"%10d swapouts" % rusage.ru_nswap,
"", "",
"%10d sigs" % rusage.ru_nsignals
]
]
stable = []
for col in table:
stable.append([str(val).strip() for val in col])
ftable = utils.format_table(stable, 5)
string = ""
for row in ftable:
string += "\n " + "{w%s{n" % row[0] + "".join(row[1:])
if not is_pypy: # load table
# Cache size measurements are not available on PyPy because it lacks sys.getsizeof loadtable = prettytable.PrettyTable(["property", "statistic"])
loadtable.align = 'l'
loadtable.add_row(["Server load (1 min)","%g" % loadavg[0]])
loadtable.add_row(["Process ID","%g" % pid]),
loadtable.add_row(["Bytes per page","%g " % psize])
loadtable.add_row(["CPU time used (total)", "%s (%gs)" % (utils.time_format(rusage.ru_utime), rusage.ru_utime)])
loadtable.add_row(["CPU time used (user)", "%s (%gs)" % (utils.time_format(rusage.ru_stime), rusage.ru_stime)])
loadtable.add_row(["Memory usage","%g MB (%g%%)" % (rmem, pmem)])
loadtable.add_row(["Virtual address space\n {x(resident+swap+caching){n", "%g MB" % vmem])
loadtable.add_row(["Page faults","%g hard, %g soft, %g swapouts" % (rusage.ru_majflt, rusage.ru_minflt, rusage.ru_nswap)])
loadtable.add_row(["Disk I/O", "%g reads, %g writes" % (rusage.ru_inblock, rusage.ru_oublock)])
loadtable.add_row(["Network I/O", "%g in, %g out" % (rusage.ru_msgrcv, rusage.ru_msgsnd)])
loadtable.add_row(["Context switching", "%g vol, %g forced, %g signals" % (rusage.ru_nvcsw, rusage.ru_nivcsw, rusage.ru_nsignals)])
# object cache size string = "{wServer CPU and Memory load:{n\n%s" % loadtable
cachedict = _idmapper.cache_size()
totcache = cachedict["_total"] if not is_pypy:
string += "\n{w Database entity (idmapper) cache usage:{n %5.2f MB (%i items)" % (totcache[1], totcache[0]) # Cache size measurements are not available on PyPy because it lacks sys.getsizeof
sorted_cache = sorted([(key, tup[0], tup[1]) for key, tup in cachedict.items() if key !="_total" and tup[0] > 0],
key=lambda tup: tup[2], reverse=True) # object cache size
table = [[tup[0] for tup in sorted_cache], cachedict = _idmapper.cache_size()
["%5.2f MB" % tup[2] for tup in sorted_cache], totcache = cachedict["_total"]
["%i item(s)" % tup[1] for tup in sorted_cache]] sorted_cache = sorted([(key, tup[0], tup[1]) for key, tup in cachedict.items() if key !="_total" and tup[0] > 0],
ftable = utils.format_table(table, 5) key=lambda tup: tup[2], reverse=True)
for row in ftable: memtable = prettytable.PrettyTable(["entity name", "number", "cache (MB)", "idmapper %%"])
string += "\n " + row[0] + row[1] + row[2] memtable.align = 'l'
# get sizes of other caches for tup in sorted_cache:
attr_cache_info, field_cache_info, prop_cache_info = get_cache_sizes() memtable.add_row([tup[0], "%i" % tup [1], "%5.2f" % tup[2], "%.2f" % (float(tup[2]/totcache[1])*100)])
#size = sum([sum([getsizeof(obj) for obj in dic.values()]) for dic in _attribute_cache.values()])/1024.0
#count = sum([len(dic) for dic in _attribute_cache.values()]) # get sizes of other caches
string += "\n{w On-entity Attribute cache usage:{n %5.2f MB (%i attrs)" % (attr_cache_info[1], attr_cache_info[0]) attr_cache_info, field_cache_info, prop_cache_info = get_cache_sizes()
string += "\n{w On-entity Field cache usage:{n %5.2f MB (%i fields)" % (field_cache_info[1], field_cache_info[0]) string += "\n{w Entity idmapper cache usage:{n %5.2f MB (%i items)\n%s" % (totcache[1], totcache[0], memtable)
string += "\n{w On-entity Property cache usage:{n %5.2f MB (%i props)" % (prop_cache_info[1], prop_cache_info[0]) string += "\n{w On-entity Attribute cache usage:{n %5.2f MB (%i attrs)" % (attr_cache_info[1], attr_cache_info[0])
string += "\n{w On-entity Field cache usage:{n %5.2f MB (%i fields)" % (field_cache_info[1], field_cache_info[0])
string += "\n{w On-entity Property cache usage:{n %5.2f MB (%i props)" % (prop_cache_info[1], prop_cache_info[0])
caller.msg(string) caller.msg(string)

View file

@ -25,7 +25,7 @@ _DA = object.__delattr__
# to *in-game* safety (if you can edit typeclasses you have # to *in-game* safety (if you can edit typeclasses you have
# full access anyway), so no protection against changing # full access anyway), so no protection against changing
# e.g. 'locks' or 'permissions' should go here. # e.g. 'locks' or 'permissions' should go here.
PROTECTED = ('id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass', 'db_player', 'player', PROTECTED = ('id', 'dbobj', 'db', 'ndb', 'objects', 'typeclass', 'db_player',
'attr', 'save', 'delete', 'db_model_name','attribute_class', 'attr', 'save', 'delete', 'db_model_name','attribute_class',
'typeclass_paths') 'typeclass_paths')

1503
src/utils/prettytable.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -434,39 +434,6 @@ def inherits_from(obj, parent):
parent_path = "%s.%s" % (parent.__class__.__module__, parent.__class__.__name__) parent_path = "%s.%s" % (parent.__class__.__module__, parent.__class__.__name__)
return any(1 for obj_path in obj_paths if obj_path == parent_path) return any(1 for obj_path in obj_paths if obj_path == parent_path)
def format_table(table, extra_space=1):
"""
Takes a table of collumns: [[val,val,val,...], [val,val,val,...], ...]
where each val will be placed on a separate row in the column. All
collumns must have the same number of rows (some positions may be
empty though).
The function formats the columns to be as wide as the widest member
of each column.
extra_space defines how much extra padding should minimum be left between
collumns.
print the resulting list e.g. with
for ir, row in enumarate(ftable):
if ir == 0:
# make first row white
string += "\n{w" + ""join(row) + "{n"
else:
string += "\n" + "".join(row)
print string
"""
if not table:
return [[]]
max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)])
return ftable
def server_services(): def server_services():
""" """
@ -989,3 +956,40 @@ def string_partial_matching(alternatives, inp, ret_index=True):
return matches[max(matches)] return matches[max(matches)]
return [] return []
def format_table(table, extra_space=1):
"""
Note: src.utils.prettytable is more powerful than this, but this
function can be useful when the number of columns and rows are
unknown and must be calculated on the fly.
Takes a table of collumns: [[val,val,val,...], [val,val,val,...], ...]
where each val will be placed on a separate row in the column. All
collumns must have the same number of rows (some positions may be
empty though).
The function formats the columns to be as wide as the widest member
of each column.
extra_space defines how much extra padding should minimum be left between
collumns.
print the resulting list e.g. with
for ir, row in enumarate(ftable):
if ir == 0:
# make first row white
string += "\n{w" + ""join(row) + "{n"
else:
string += "\n" + "".join(row)
print string
"""
if not table:
return [[]]
max_widths = [max([len(str(val)) for val in col]) for col in table]
ftable = []
for irow in range(len(table[0])):
ftable.append([str(col[irow]).ljust(max_widths[icol]) + " " * extra_space
for icol, col in enumerate(table)])
return ftable