#----------------------------------------------------------------------
# poo.py JJS 05/20/99
#
# This is the main module for POO (Pythonic MOO).
# See: http://www.strout.net/python/poo/
#----------------------------------------------------------------------
import string
import pickle
import sys
import os
from types import *
from tostr import *
from time import time
import copy
import marksub
import msg
from pooparse import HandleCommand, CmdDef
gObjlist = {} # global dictionary of POO objects
gHighID = 0 # highest ID used
gProps = {} # maps an object to a property dictionary
gCmdDefs = {} # maps a verb to a sequence of matching commands
gContents = {} # maps an object to a list of contents
gCmdLine = '' # copy of command typed by the user
gSafeGlobals = {} # "safe" globals for user's environment
gLocals = {} # maps user object to a dict of local vars
gUpdateList = [] # list of objects which want updates
gOwner = None # object which owns current function (etc.)
gUser = None # object who invoked the current command
gCallers = [None] # sort of a traceback list of calling objects
gUnpickling = 0 # set to 1 when unpickling
gLastSave = time() # time database was last saved
gFilePath = os.path.join(os.curdir,'poofiles') # path for user-accessible files
gTreasury = 100000 # credits for use by wizards
flushNoCR = 0 # set to 1 if should flush() after tell_NoCR
maxSaveTime = 1800 # maximum time between saves (seconds)
kObjValue = 90 # standard "value" of objects, in credits
kCreationTax = 10 # extra cost of creating an object -- goes to treasury
kRecycleTax = 40 # tax levied when recycling an object
kInbufLimit = 20 # maximum number of lines in "hear" buffer
#----------------------------------------------------------------------
# FUNCTION: copyList
#----------------------------------------------------------------------
def copyList( fromwhat, intowhat ):
"""FUNCTION copyList(fromwhat, intowhat):
This function copies one list into another, WITHOUT creating
a new list instance. This if you have A=B, and copy from C into B,
A will also be changed.
"""
# LATER: check for list/tuple type; if not, coerce
intowhat[:] = list(fromwhat) # (thanks, Aaron!)
#----------------------------------------------------------------------
# CLASS: PooPickler
#----------------------------------------------------------------------
class PooPickler(pickle.Pickler):
"""CLASS PooPickler:
This class extends the normal Pickler object such that datatypes
which can't by pickled are set to None, rather than crashing
the server.
"""
# RADICALLY CHANGED on 4/20/98 to work with Python 1.5,
# as per recommendation by our fearless leader (Guido)
def save(self, object):
try:
pickle.Pickler.save(self, object)
except pickle.PicklingError:
pickle.Pickler.save(self, None)
#----------------------------------------------------------------------
# CLASS: Func
#----------------------------------------------------------------------
class Func:
"""CLASS Func:
This class implements a POO function. Objects of this class walk
like functions, but they are pickleable and do permission checks.
"""
#------------------------------------------------------------------
# Func constructor
#------------------------------------------------------------------
def __init__(self,name='f',owner=None):
"""Func constructor."""
if gUnpickling: return
name = string.replace(name,' ','')
attributes = {
'owner':owner, # function owner
'name':name, # function delaration (name and args)
'x':1, # if 0, can only be called as command
'code':None, # compiled code
'source':[], # source (list of strings)
'presource':'', # extra code before 'source'
'desc':"", # brief description
'f':None, # function defined by code
'boundTo':None, # POO object to be considered 'self'
'definedOn':None } # POO object where this Func was found
for k in attributes.keys():
self.__dict__[k] = attributes[k]
#------------------------------------------------------------------
# Func call method
#------------------------------------------------------------------
def __call__(self, *args):
"""Func call method."""
global gOwner, gUser, gCallers
if not self.x:
raise "PermError", self.name + " may only be called as a command"
prevOwner = gOwner
if self.owner:
gOwner = self.owner
if self.source and not self.code:
self.compile()
outval = None
# prepare to attach "super" and other changables
bdict = gSafeGlobals['__builtins__'].__dict__
prevInfo = (bdict['super'],bdict['caller'])
# update the stack of callers
gCallers.append(self.boundTo)
try:
if self.code:
# attach "super" and other automatic globals
bdict['super'] = self.doSuper
bdict['caller'] = gCallers[-2]
bdict['user'] = gUser
bdict['permHolder'] = gOwner
# call the function created and stored in compile():
if self.boundTo:
# is this the most efficient way to do this?
outval = apply(self.f, tuple( [self.boundTo]+list(args) ))
else:
outval = apply(self.f,args)
finally:
gCallers = gCallers[:-1]
gOwner = prevOwner
bdict['super'],bdict['caller'] = prevInfo
gOwner = prevOwner
return outval
#------------------------------------------------------------------
# Func METHOD: compile
#------------------------------------------------------------------
def compile(self):
"""Func METHOD: compile(self):
compile self.source, store executable byte code."""
if not self.source:
self.__dict__['code'] = None
return
pos = string.find(self.name,'(')
paramdef = self.name[pos+1:-1]
fullsource = 'def f(' + paramdef + '):\n ' \
+ self.__dict__['presource'] + '\n ' \
+ string.join(self.source,'\n ') + '\n'
if gUnpickling:
try: self.__dict__['code'] = compile(fullsource,'<'+self.name[:pos]+'>','exec')
except: return
else:
self.__dict__['code'] = compile(fullsource,'<'+self.name[:pos]+'>','exec')
# create a "dummy" function:
locals = {'__builtins__':None} # (is this necessary?)
exec(self.code) in gSafeGlobals,locals
# copy (a reference to) the function for future use
self.__dict__['f'] = locals['f']
#------------------------------------------------------------------
# Func METHOD: list
#------------------------------------------------------------------
def list(self, fromLine=1, toLine=99999):
"""Func METHOD: list(self, fromLine=1, toLine=99999):
list the source code [within the specified range]."""
for line in self.source[fromLine-1:toLine]:
print line
#------------------------------------------------------------------
# Func METHOD: arg
#------------------------------------------------------------------
def arg(self,num=0):
"""Func METHOD: arg(self, num=0):
find the specified argument name amoung self.name."""
args = self.name[string.find(self.name,'(') +1 : \
string.rfind(self.name,')') ]
try: return string.split(args,',')[num]
except: return ''
#------------------------------------------------------------------
# Func conversion to string
#------------------------------------------------------------------
def __str__(self):
"""Func conversion to string."""
if self.boundTo: out = '' # str(self.boundTo) + '.'
else: out = "unbound "
if self.desc:
out = out + self.name + " -- " + self.desc
else:
out = out + self.name
return out
#------------------------------------------------------------------
# Func attribute setter
#------------------------------------------------------------------
def __setattr__(self,propname,value):
"""Func attribute setter."""
# owner can't be changed directly
if gOwner and propname == 'owner':
raise "PermError", "Can't directly change func owner"
# other attributes can be changed only by owner
if gOwner and not gOwner.wizard and gOwner != self.__dict__['owner']:
raise "PermError", tostr(gOwner) \
+ " can't modify " + propname + " of " + self.__str__()
self.__dict__[propname] = value
if propname == 'source': # when source changes,
self.code = None # force a recompile
#------------------------------------------------------------------
# Func pickling support
#------------------------------------------------------------------
def __getstate__(self):
"""Func pickling support."""
temp = {}
for key in self.__dict__.keys():
if key not in ['code','f']:
temp[key] = self.__dict__[key]
return temp
#------------------------------------------------------------------
# Func unpickling support
#------------------------------------------------------------------
def __setstate__(self,value):
"""Func unpickling support."""
for key in value.keys():
self.__dict__[key] = value[key]
self.compile()
#------------------------------------------------------------------
# Func METHOD: unbind
#------------------------------------------------------------------
def unbind(self):
"""Func METHOD: unbind(self):
set self.boundTo = None."""
self.__dict__['boundTo'] = None
return self
#------------------------------------------------------------------
# Func METHOD: doSuper
#------------------------------------------------------------------
def doSuper(self,*args):
"""Func METHOD: doSuper(self,*args):
Find the Func this overrides, and apply args to it.
This is usually invoked via the 'super' variable."""
func = self.findSuper()
func.__dict__['boundTo'] = self.boundTo
return apply(func,args)
#------------------------------------------------------------------
# Func METHOD: findSuper
#------------------------------------------------------------------
def findSuper(self):
"""Func METHOD: findSuper(self):
This method returns the Func which is apparently overridden
by this one -- that is, it has the same name, but is defined
further back in the ancestor chain. Return it unbound.
"""
if not self.boundTo or type(self.boundTo)!=InstanceType:
raise "UseError", "Func.findSuper() must be called on bound Func"
for p in self.definedOn.parents:
super = self.findSameName( p )
if super: return super
return None
#------------------------------------------------------------------
# Func METHOD: findSelf
#------------------------------------------------------------------
def findSameName(self,obj):
"""Func METHOD: findSameName(self,obj):
Given an object reference, find a property (on it or its
ancestors) which refers to a Func with the same name
as this Func.
"""
matches = filter(lambda x,y=self.name: \
type(x.val) == InstanceType and \
x.val.__class__ == Func and x.val.name == y, gProps[obj].values())
if matches: return matches[0].val
# not found here -- check ancestors
for p in obj.parents:
found = self.findSameName(p)
if found: return found
# still not found -- return None
return None
#----------------------------------------------------------------------
# CLASS: Prop
#----------------------------------------------------------------------
class Prop:
"""CLASS Prop:
This class implements a POO property. These are used instead of
normal Python attributes to give us full access control.
"""
#------------------------------------------------------------------
# Prop constructor
#------------------------------------------------------------------
def __init__(self,owner=None,val=None):
"""Prop constructor."""
if gUnpickling: return
attributes = {
'owner':owner,
'val':val,
'r':1, # readable by others
'w':0, # writable by others
'c':1 } # ownership changes when inherited
for k in attributes.keys():
self.__dict__[k] = attributes[k]
if type(val) == InstanceType and val.__class__ == Func:
val.__dict__['owner'] = owner
#------------------------------------------------------------------
# Prop METHOD: permstr
#------------------------------------------------------------------
def permstr(self):
"""Prop METHOD: permstr(self):
return a string listing permissions as some combo of r,w,c,x."""
perm = ''
if self.r: perm = 'r'
if self.w: perm = perm + 'w'
if self.c: perm = perm + 'c'
if type(self.val) == InstanceType and self.val.__class__ == Func \
and self.val.x: perm = perm + 'x'
return perm
#------------------------------------------------------------------
# Prop conversion to string
#------------------------------------------------------------------
def __str__(self):
"""Prop conversion to string."""
return "Prop(owner="+str(self.owner)+", perm="+self.permstr()+")"
#------------------------------------------------------------------
# Prop attribute setter
#------------------------------------------------------------------
def __setattr__(self,propname,value):
"""Prop attribute setter."""
# property flags can only be changed by the owner or a wizard
if gOwner and gOwner!=self.owner and not gOwner.wizard and \
(propname=='r' or propname=='w' or propname=='c'):
raise "PermError", "Can't change property permissions"
if propname=='x':
raise "UseError", "'x' flag should be set on Func, not Prop"
# ownership can only be changed by a wizard
if gOwner and not gOwner.wizard and propname=='owner':
raise "PermError", "Can't change property owner"
self.__dict__[propname] = value
# make sure owner of a function and its property are in sync
# (unless we're unpickling, in which case it takes care of itself)
if gUnpickling: return
if propname == 'val' and type(value) == InstanceType \
and value.__class__ == Func:
value.__dict__['owner'] = self.owner
elif propname == 'owner' and type(self.val) == InstanceType \
and self.val.__class__ == Func:
self.val.__dict__['owner'] = self.owner
#----------------------------------------------------------------------
# CLASS: Outfix
#----------------------------------------------------------------------
class Outfix:
"""CLASS Outfix:
This class is used to wrap an output stream so that output text
is formatted to the user's preferences."""
def __init__(self, out=sys.stdout, user=None):
self.out = out
self.user = user
def write(self,s):
try:
outlines = marksub.marksub(s, \
self.user.marksub,
self.user.linelen)
except:
outlines = [s]
try:
sfx = self.user.linesuffix
if type(sfx) != StringType:
sfx = '\n'
self.user.linesuffix = sfx
except:
sfx = '\n'
self.out.write(string.join(outlines, sfx))
def flush(self): self.out.flush()
def close(self): self.out.close()
#----------------------------------------------------------------------
# CLASS: Obj
#----------------------------------------------------------------------
class Obj:
"""CLASS: Obj
This is the base class of any POO object -- rooms, users, widgets,
etc. It implements a 'multiple superinheritance' (see POO docs!).
"""
#------------------------------------------------------------------
# Obj constructor
#------------------------------------------------------------------
def __init__(self,id=0,*parents):
"""Obj constructor."""
global gObjlist, gHighID, gProps
if not gProps.has_key(self): gProps[self] = {}
gContents[self] = []
if gUnpickling: return
if gOwner and not gOwner.wizard:
raise "Hacker", "Hacking is grounds for termination of your account."
if id and gObjlist.has_key(id):
print "Error: object",id,"already exists."
id = 0
if id:
gObjlist[id] = self
if gHighID < id: gHighID = id
attributes = {
'__outstream': None, # output stream
'id': id, # unique identifier
'__isuser': 0, # flag indicating a User object
'_inbuf': [], # input buffer
'__value': kObjValue, # creation value
'__credits': 0, # transferrable credits
'__commands': {}, # commands defined on this object
'parents': parents, # parent objects
'name': '', # name of the object
'aliases': (), # name aliases
'owner': self, # object which controls this one
'location': None, # object that contains this one
'wantsUpdates': 0, # 1 if wants calls to update()
'wizard': 0, # 1 if has divine powers
'programmer': 0, # 1 if can use programmer commands
'r': 1, # 1 if publicly readable
'w': 0, # 1 if publicly writable
'f': 0 } # 1 if can have children ("fertile")
for k in attributes.keys():
self.__dict__[k] = attributes[k]
#------------------------------------------------------------------
# Obj conversion to string
#------------------------------------------------------------------
def __str__(self):
"""Obj conversion to string."""
return "#" + str(self.__dict__['id'])
#------------------------------------------------------------------
# Obj METHOD: tell
#------------------------------------------------------------------
def tell(self, *what):
"""Obj METHOD: tell(self, *what):
put the given string or list of strings in our auditory buffer.
Terminate with a carriage return.
"""
bigstr = string.join(map(str,what),' ') + "\n"
if self.__dict__['__outstream']:
try:
self.__dict__['__outstream'].write(bigstr)
self.__dict__['__outstream'].flush()
return
except:
# user appears to be disconnected; log them out
self.Logout()
# place in the input buffer (within INBUFLIMIT)
if not callable(self.hear): return
try:
buf = self.__dict__['_inbuf']
except:
buf = []
self.__dict__['_inbuf'] = buf
if len(buf) >= kInbufLimit: return
buf.append(bigstr)
self.CheckUpdate()
#------------------------------------------------------------------
# Obj METHOD: tell_noCR
#------------------------------------------------------------------
def tell_noCR(self, *what):
"""Obj METHOD: tell_noCR(self, *what):
put the given string or list of strings in our auditory buffer.
Do not terminate with a carriage return.
"""
bigstr = string.join(map(str,what),' ')
if self.__dict__['__outstream']:
# try:
self.__dict__['__outstream'].write(bigstr)
if flushNoCR:
self.__dict__['__outstream'].flush()
return
# except:
# user appears to be disconnected; log them out
self.Logout()
# place in the input buffer (within INBUFLIMIT)
if not callable(self.hear): return
try:
buf = self.__dict__['_inbuf']
except:
buf = []
self.__dict__['_inbuf'] = buf
if len(buf) >= kInbufLimit: return
buf.append(bigstr)
self.CheckUpdate()
#------------------------------------------------------------------
# Obj METHOD: getprop
#------------------------------------------------------------------
def getprop(self,propname):
"""Obj METHOD: getprop(self,propname):
return the Prop defined on this object or an ancestor with
the specified name.
"""
d = gProps[self]
if propname in d.keys():
prop = d[propname]
# check read permissions
# if no owner, assume it's (safe) POO code
if not gOwner or \
gOwner.__dict__['wizard'] or \
prop.owner == gOwner or \
prop.r:
# we've found the property on self (noninherited)
# return it as-is (make sure funcs are bound)
if type(prop.val) == InstanceType and \
prop.val.__class__ == Func:
prop.val.__dict__['boundTo'] = self
prop.val.__dict__['definedOn'] = self
return prop
raise "PermError", "Can't read property "+propname
for p in self.parents:
prop = p.getprop(propname)
if prop != None:
# we've found it as an inherited value
# if it's a function, return a COPY bound to this
if type(prop.val) == InstanceType and \
prop.val.__class__ == Func:
# do a quick copy of the Func, changing boundTo
func2 = Func(prop.val.name,prop.val.owner)
for i in prop.val.__dict__.keys():
func2.__dict__[i] = prop.val.__dict__[i]
func2.__dict__['boundTo'] = self
func2.__dict__['definedOn'] = prop.val.definedOn
# do a quick copy of the prop, pointing to Func
prop2 = Prop(prop.owner, func2)
prop2.__dict__['r'] = prop.__dict__['r']
prop2.__dict__['w'] = prop.__dict__['w']
prop2.__dict__['c'] = prop.__dict__['c']
return prop2
return prop
return None
#------------------------------------------------------------------
# Obj attribute accessor
#------------------------------------------------------------------
def __getattr__(self,propname):
"""Obj attribute accessor."""
# note: don't perform inheritance on Python props
if propname[:2] == '__':
try: return self.__dict__[propname]
except: raise AttributeError, propname
prop = self.getprop(propname)
if prop: return prop.val
else: return None
#------------------------------------------------------------------
# Obj METHOD: canAddOrDeleteProp
#------------------------------------------------------------------
def canAddOrDeleteProp(self, propname):
"""Obj METHOD: canAddOrDeleteProp(self, propname):
return 1 if gOwner can add a property to this object
"""
if not propname: return 0
if (propname[0] < 'a' or propname[0] > 'z') and \
(propname[0] < 'A' or propname[0] > 'Z'): return 0
if ' ' in propname or '.' in propname: return 0
return not gOwner \
or gOwner==self.owner \
or gOwner.wizard \
or self.w
#------------------------------------------------------------------
# Obj METHOD: canWriteProp
#------------------------------------------------------------------
def canWriteProp(self, propname, useCflag=1):
"""Obj METHOD: canWriteProp(self, propname, useCflag=1):
return 1 if gOwner can change the given property.
"""
# if no owner, assume it's (safe) POO code
if not gOwner: return 1
# python attributes can't safely be changed by anyone
if len(propname) > 1 and propname[:2] == "__":
return 0
# location hierarchy can only be changed by move()
if propname=='location':
return gOwner==move
# allow wizards to do anything
if gOwner.wizard: return 1
# check for wizard-only properties
if propname=='owner' or \
propname=='programmer' or \
propname=='wizard' or \
propname[0] == '_': return 0
# built-in properties are only writable by the object owner
if propname in self.__dict__.keys(): return gOwner == self.owner
# check owner & permission bit on the property itself
if gProps[self].has_key(propname):
prop = gProps[self][propname]
owner = prop.owner
else: # not defined here...
prop = self.getprop(propname) # inherited?
if not prop: return self.canAddOrDeleteProp(propname)
# if inherited, check flag "c"
if prop.c and useCflag: owner = self.owner
else: owner = prop.owner
if owner != gOwner and not prop.w: return 0
# if none of the above restrictions apply, must be OK
return 1
#------------------------------------------------------------------
# Obj METHOD: setParents
#------------------------------------------------------------------
def setParents(self,parents):
"""Obj METHOD: setParents(self,parents):
change parents of this object to the given object or list.
"""
if not self.canWriteProp('parents'):
raise "PermError", "Can't modify built-in parents"
if type(parents) == ListType:
parents = tuple(parents)
elif type(parents) == InstanceType:
parents = (parents,)
elif type(parents) != TupleType:
raise "ParamError", "parents must be an object, list, or tuple"
for p in parents:
if type(p) != InstanceType or \
(p.__class__ != Obj and p.__class__ != User and
p.__class__ != Directory):
raise "ParamError", `p` + " is not a POO object"
# is proposed parent fertile?
if not p.f:
raise "ParamError", tostr(p) + " is infertile"
# is proposed parent already one of our descendants?
if self in p.ancestors():
raise "ParamError", tostr(p) + \
" cannot be both ancestor and descendant"
self.__dict__['parents'] = parents
#------------------------------------------------------------------
# Obj METHOD: ancestors
#------------------------------------------------------------------
def ancestors(self):
"""Obj METHOD: ancestors(self):
return list of all this object's ancestors.
"""
lst = []
for p in self.__dict__['parents']:
lst = lst + [p] + p.ancestors()
return lst
#------------------------------------------------------------------
# Obj attribute setter
#------------------------------------------------------------------
def __setattr__(self,propname,value):
"""Obj attribute setter."""
# if value is a Func, don't allow the assignment
# unless caller is a wiz or the Func owner
if type(value) == InstanceType and value.__class__ == Func:
if gOwner and gOwner != value.owner and not gOwner.wizard:
raise "PermError", "Can't store reference to " \
+ str(value) + " owned by " + str(value.owner)
# coerce types where appropriate
if propname == "aliases":
if type(value) == ListType:
value = tuple(value)
elif type(value) != TupleType:
value = (value,)
# if it's a built-in attribute, just check permission
if self.__dict__.has_key(propname):
if self.canWriteProp(propname):
if propname == 'parents':
self.setParents(value)
else: self.__dict__[propname] = value
if propname=='wantsUpdates': self.CheckUpdate()
else: raise "PermError", "Can't modify built-in "+propname
return
# if it's an existing local property, change val
if gProps[self].has_key(propname):
if self.canWriteProp(propname):
gProps[self][propname].val = value
if propname=='update': self.CheckUpdate()
else: raise "PermError", "Can't modify "+propname
return
# if it's an inherited property, add it anew here
prop = self.getprop(propname)
if prop:
# implement "c" flag: when overriding a prop with c==1
# the owner of the object owns the overridden property
if prop.c: owner = self.owner
else: owner = prop.owner
if gOwner != owner and not prop.w and not gOwner.wizard:
raise "PermError", "Can't modify "+propname
np = Prop(owner,value)
for k in ['r','w','c']:
np.__dict__[k] = prop.__dict__[k]
gProps[self][propname] = np
if propname=='update': self.CheckUpdate()
return
# check to see if this would actually add a new property
if not gProps[self].has_key(propname):
if not self.canAddOrDeleteProp(propname):
raise "PermError", str(gOwner) + " can't add property (" \
+ propname + ") to "+str(self)
else:
gProps[self][propname] = Prop(gOwner,value)
if propname=='update': self.CheckUpdate()
return
#------------------------------------------------------------------
# Obj attribute deletion
#------------------------------------------------------------------
def __delattr__(self,propname):
"""Obj attribute deletor."""
if self.__dict__.has_key(propname):
raise "PermError", "Can't delete built-in "+propname
elif self.canAddOrDeleteProp(propname):
del gProps[self][propname]
if propname=='update': self.CheckUpdate()
else: raise "PermError", "Can't delete property "+propname
#------------------------------------------------------------------
# Obj pickling support
#------------------------------------------------------------------
def __getstate__(self):
"""Obj pickling support."""
temp = {}
for key in self.__dict__.keys():
if key not in ['__outstream']:
temp[key] = self.__dict__[key]
return temp
#------------------------------------------------------------------
# Obj unpickling support
#------------------------------------------------------------------
def __setstate__(self,value):
"""Obj unpickling support."""
for key in value.keys():
self.__dict__[key] = value[key]
self.__dict__['__outstream'] = None
#------------------------------------------------------------------
# Obj METHOD: DoUpdate
#------------------------------------------------------------------
def DoUpdate(self):
"""Obj METHOD: DoUpdate(self):
update this object by calling self.hear or self.update
as appropriate.
"""
global gOwner
prevOwner = gOwner
buf = self._inbuf
if buf:
nextline = buf[0]
del buf[0]
try: self.hear(nextline)
except: pass
if not buf: self.CheckUpdate()
elif self.wantsUpdates:
gOwner = self
try: self.update()
except:
self.owner.tell("Error calling " + str(self) + ".update():")
self.owner.handleErr(sys.exc_type, sys.exc_value, sys.exc_traceback)
if (time()%10 < 1):
self.__dict__['wantsUpdates'] = 0
self.owner.tell(str(self) + ".wantsUpdates has been set to 0")
self.CheckUpdate()
else:
self.CheckUpdate()
gOwner = prevOwner
#------------------------------------------------------------------
# Obj METHOD: CheckUpdate
#------------------------------------------------------------------
def CheckUpdate(self):
"""Obj METHOD: CheckUpdate(self):
check whether this object should receive updates or not,
and update gUpdateList accordingly.
"""
wantit = (callable(self.update) and self.wantsUpdates) \
or (callable(self.hear) and len(self._inbuf))
if wantit and self not in gUpdateList:
gUpdateList.append(self)
# self.owner.tell(self.name + " (#" + str(self.id) + \
# ") now receives updates.")
elif not wantit and self in gUpdateList:
gUpdateList.remove(self)
# self.owner.tell(self.name + " (#" + str(self.id) + \
# ") no longer receives updates.")
#------------------------------------------------------------------
# Obj METHOD: handleErr
#------------------------------------------------------------------
def handleErr(self,e_type, e_val, e_traceback):
"""Obj METHOD: handleErr(self,e_type, e_val, e_traceback):
crawl through the traceback and present only the most relevant line.
"""
# find the last traceback:
tb = e_traceback
# print tb.tb_frame.f_code.co_filename, tb.tb_lineno
found = None
if tb.tb_frame.f_code.co_filename[0] == '<':
found = tb
while tb.tb_next:
tb = tb.tb_next
if tb.tb_frame.f_code.co_filename[0] == '<':
found = tb
# print tb.tb_frame.f_code.co_filename, tb.tb_lineno
if found: tb = found
lineno = tb.tb_lineno
codename = tb.tb_frame.f_code.co_filename
if codename == '<string>':
self.tell( e_type,'in command:', e_val)
elif string.find(codename, ".py") > 0 and e_type == "SyntaxError":
self.tell( "Compilation error: ", e_val )
else:
self.tell( e_type,'in line',lineno-2, 'of ',codename, ':', e_val)
return lineno
#------------------------------------------------------------------
# Obj METHOD: do_cmd
#------------------------------------------------------------------
def do_cmd(self,cmd):
"""Obj METHOD: do_cmd(self,cmd):
attempt to execute the given POO command.
"""
global gCmdLine, gOwner, gUser
# check permissions
if gOwner and not gOwner.wizard and self.owner != gOwner:
raise "PermError", "Must be wizard or owner to do_cmd()"
gCmdLine = cmd
gSafeGlobals['gCmdLine'] = gCmdLine
if not cmd: return
# change output and user characteristics
prevInfo = (gUser, sys.stdout, gOwner)
gUser, sys.stdout, gOwner = self, self.__dict__['__outstream'], self
# if the cmd starts with ; or %, execute Python code
if cmd[0] == '%' or cmd[0] == ';':
try: locals = gLocals[self]
except: locals = {'self':self,'this':self,'player':self,
'caller':self, 'me':self, 'user':self, '__builtins__':None}
try:
if not self.programmer:
raise "PermError", "requires programmer privs"
exec(cmd[1:]) in gSafeGlobals, locals
except:
self.handleErr(sys.exc_type, sys.exc_value, sys.exc_traceback)
else:
# otherwise, attempt to execute the named command
try:
sys.stdout = self.__dict__['__outstream']
HandleCommand(self,cmd)
except:
self.handleErr(sys.exc_type, sys.exc_value, sys.exc_traceback)
if sys.stdout: sys.stdout.flush()
# restore output and user characteristics
gUser, sys.stdout, gOwner = prevInfo
#------------------------------------------------------------------
# Obj METHOD: proplist
#------------------------------------------------------------------
def proplist(self):
"""Obj METHOD: proplist(self)
return a list of all property names.
"""
# you must be able to read the object to get a proplist
if gOwner and gOwner != self.owner and not gOwner.wizard and not self.r:
raise "PermError", "Can't list properties of this object"
props = filter(lambda x:x[0]!='_' and x!='password', \
self.__dict__.keys() + gProps[self].keys())
props.sort()
return props
#------------------------------------------------------------------
# Obj METHOD: isa
#------------------------------------------------------------------
def isa(self,what):
"""Obj METHOD: isa(self,what):
return 1 if what is an ancestor of self.
"""
if type(what) != types.InstanceType:
what = gProps[gObjlist[0]][what].val
if what==self or what in self.parents: return 1
for p in self.parents:
if p.isa(what): return 1
return 0
#------------------------------------------------------------------
# Obj METHOD: contents
#------------------------------------------------------------------
def contents(self):
"""Obj METHOD: contents(self):
return list of objects contained by self.
"""
if gOwner and gOwner != self.owner and not gOwner.wizard and not self.r:
raise "PermError", "Can't list contents of this object"
aCopy = []
aCopy[:] = gContents[self]
return aCopy
#----------------------------------------------------------------------
# Obj METHOD: getCmdDef
#----------------------------------------------------------------------
def getCmdDef( self, verb, inherit=1 ):
"""Obj METHOD: getCmdDef(self, verb, inherit=1):
This method returns a (possibly empty) tuple of commands which
begin with the given verb from this object's command dictionary.
If no verb is specified, return all defined commands.
"""
# first look for command defs on this object (overriding parents)
out = ()
if gCmdDefs.has_key(self):
defs = gCmdDefs[self]
if not verb:
for i in defs.values():
out = out + i
elif defs.has_key(verb):
out = defs[verb]
# then, add commands from parents
if not inherit: return out
for p in self.parents:
out = out + p.getCmdDef(verb,inherit)
return out
#----------------------------------------------------------------------
# Obj METHOD: setCmdDef
#----------------------------------------------------------------------
def setCmdDef( self, verb, pattern, funcdef ):
"""Obj METHOD: setCmdDef( self, verb, pattern, funcdef ):
This function sets a command definition in the global dictionary.
If the given function call is None or an empty string, the command
will be removed. If the given pattern matches an existing one
exactly, the existing one will be replaced. This function requires
wiz privs.
"""
global gOwner, gCmdDefs
if gOwner and not gOwner.wizard and gOwner != self.owner :
raise "PermError", "Must be owner or wizard to call setCmdDef()"
if funcdef:
newcmd = CmdDef(gOwner, pattern, funcdef)
else:
newcmd = None
# make sure we have an entry for this object in gCmdDefs
if not gCmdDefs.has_key(self):
gCmdDefs[self] = {}
defs = gCmdDefs[self]
# get list of commands on this object for this verb
try:
cmds = list(defs[verb])
except:
if newcmd: defs[verb] = (newcmd,)
return
for i in range(0,len(cmds)):
if cmds[i].pat == pattern:
# command exists already; replace it
if newcmd: cmds[i] = newcmd
else: del cmds[i]
if cmds: defs[verb] = tuple(cmds)
else: del defs[verb]
if not gCmdDefs[self]: del gCmdDefs[self]
return
if newcmd: cmds.append(newcmd)
else: return
defs[verb] = tuple(cmds)
#----------------------------------------------------------------------
# CLASS: User
#----------------------------------------------------------------------
class User(Obj):
"""CLASS User:
This subclass of Obj defines the class of Users in the game. They
have all the properties of other POO objects, but also have
facilities for being connected via the network.
"""
#------------------------------------------------------------------
# User constructor
#------------------------------------------------------------------
def __init__(self,id=0,*parents):
"""User constructor."""
global gObjlist, gHighID, gProps
if not gProps.has_key(self): gProps[self] = {}
gContents[self] = []
if gUnpickling: return
Obj.__init__(self,id)
attributes = {
'__outstream': None, # output stream
'id': id, # uniquet identifier
'_editObj': None, # object whose property we're editing
'_editProp': '', # name of property we're editing
'_editBuf': [], # edit buffer
'_editState': None, # edit state (temp storage)
'_prompt': '<t>poo>', # user prompt
'_oldprompt': '', # previous prompt
'__isuser': 1 } # yes, we're a user
for k in attributes.keys():
self.__dict__[k] = attributes[k]
self.__dict__['parents'] = parents
#------------------------------------------------------------------
# User METHOD: connected
#------------------------------------------------------------------
def connected(self):
"""User METHOD: connected(self):
return 1 if this User is currently logged in.
"""
return (self.__dict__['__outstream'] != None)
#------------------------------------------------------------------
# User METHOD: Login
#------------------------------------------------------------------
def Login(self, stream):
"""User METHOD: Login(self, stream):
log into the POO server, and connect output to the given stream.
"""
global gOwner, gLocals
if gOwner:
raise "UseError", "Never call Login() directly!"
gLocals[self] = {'self':self, 'this':self, 'player':self,
'caller':self, 'me':self, 'user':self, '__builtins__':None }
self.__dict__['__outstream'] = stream
self.loginTime = time()
self.CheckUpdate()
prevOwner = gOwner
try:
sys.stdout = stream
gOwner = self
gObjlist[0].login(self)
except: pass
gOwner = prevOwner
self.tell_noCR(self._prompt)
#------------------------------------------------------------------
# User METHOD: Logout
#------------------------------------------------------------------
def Logout(self):
"""User METHOD: Logout(self):
disconnect from the POO server."""
# beware of the case where the user is already logged out
# (perhaps because her connection hung -- see Object.tell_noCR)
if self not in gLocals.keys(): return
global gOwner
if gOwner and not gOwner.wizard and self.owner != gOwner:
raise "PermError", "Must be wizard or owner to Logout()"
prevOwner = gOwner
sys.stdout = self.__dict__['__outstream']
self.__dict__['__outstream'] = None
try:
gOwner = self
gObjlist[0].logout(self)
except: pass
gOwner = prevOwner
self.CheckUpdate()
del gLocals[self]
#------------------------------------------------------------------
# User METHOD: handleMsg
#------------------------------------------------------------------
def handleMsg(self,msg):
"""User METHOD: handleMsg(self,msg):
handle some input from the real-life user.
We assume msg is a single line.
"""
if gOwner and gOwner != self:
raise "UseError", "Never call handleMsg() directly!"
#lines = string.split(msg,'\n')
#lines = map(lambda x:string.rstrip(x), lines)
#self._inbuf = self._inbuf + lines
self._inbuf = self._inbuf + [msg]
self.activeTime = time()
#------------------------------------------------------------------
# User METHOD: CheckUpdate
#------------------------------------------------------------------
def CheckUpdate(self):
"""User METHOD: CheckUpdate(self):
overrides Obj.CheckUpdate; a User should be updated if it
is connected, and not otherwise.
"""
if self.__dict__['__outstream'] and self not in gUpdateList:
gUpdateList.append(self)
elif not self.__dict__['__outstream'] and self in gUpdateList:
gUpdateList.remove(self)
#------------------------------------------------------------------
# User METHOD: DoUpdate
#------------------------------------------------------------------
def DoUpdate(self):
"""User METHOD: DoUpdate(self):
overrides Obj.DoUpdate; this method gets a command from the
inbuf and attempts to execute it.
"""
# if there's a command in the inbuf, do it
if self._inbuf:
cmd = self._inbuf[0]
self._inbuf = self._inbuf[1:]
if self._editObj != None: self.doEdit( cmd )
else: self.do_cmd( cmd )
self.tell_noCR(self._prompt)
# if user wants update() calls, do it
if self.wantsUpdates and callable(self.update):
try: self.update()
except: pass
# otherwise, do nothing (but show prompt)
#------------------------------------------------------------------
# User METHOD: startEdit
#------------------------------------------------------------------
def startEdit(self,object,propname,postEdit=None):
"""User METHOD: startEdit(self,object,propname,postEdit=None):
invoke the editor on the named object property.
If present, postEdit should be a function which will receive
the object reference, property name, and a "saved" parameter
which will be 0 if user aborted, 1 if user saved changes.
"""
global gOwner
if gOwner and not gOwner.wizard and self.owner != gOwner:
raise "PermError", "Must be wizard or owner to startEdit()"
# check permissions...
# but don't apply C flag, since we're modifying it in place
if not object.canWriteProp(propname,0):
raise "PermError", "Can't modify property"
self.__dict__['_editBuf'] = []
prop = getattr(object,propname)
if prop == None:
raise "NotFoundError", "No property named " + propname
if type(prop) == InstanceType and prop.__class__ == Func:
self.__dict__['_editObj'] = prop
self.__dict__['_editProp'] = "source"
copyList(prop.source, self._editBuf)
elif type(prop) == ListType or type(prop) == TupleType:
self.__dict__['_editObj'] = object
self.__dict__['_editProp'] = propname
copyList(prop, self._editBuf)
else:
raise "TypeError", "Can't edit "+ str(type(prop.val)) +" objects"
self.__dict__['_postEditFunc'] = postEdit
# set editor prompt
self.__dict__['_oldprompt'] = self._prompt
self.__dict__['_prompt'] = 'edit>'
# now, invoke the built-in editor, or the user's preferred one
prevOwner = gOwner
gOwner = self
try:
self.__dict__['_editState'] = \
self.editor( self._editBuf, "", None )
except:
try:
self.__dict__['_editState'] = \
self.standardEdit( self._editBuf, "", None )
except: pass
gOwner = prevOwner
#------------------------------------------------------------------
# User METHOD: doEdit
#------------------------------------------------------------------
def doEdit(self, msg):
if gOwner and gOwner != self:
raise "UseError", "doEdit() must only be called on oneself"
# invoke the built-in editor, or the user's preferred one
try:
state = self.editor( self._editBuf, msg, self._editState )
except:
state = self.standardEdit( self._editBuf, msg, self._editState )
# if it was an exit or abort command, make sure we really do!
if msg == '.x' and state != 'DONE': state = 'DONE'
if msg == '.q' and state != 'ABORT': state = 'ABORT'
if state == 'DONE': self.endEdit(1)
elif state == 'ABORT': self.endEdit(0)
else: self.__dict__['_editState'] = state
#------------------------------------------------------------------
# User METHOD: endEdit
#------------------------------------------------------------------
def endEdit(self, save):
if gOwner and gOwner != self:
raise "UseError", "endEdit() must only be called on oneself"
self.__dict__['_prompt'] = self._oldprompt
if not self._editObj:
raise "Error", "endEdit() called while not editing!"
if save:
setattr( self._editObj, self._editProp, tuple(self._editBuf) )
if self._postEditFunc:
try:
self._postEditFunc( self, self._editObj, self._editProp, save )
except:
self.tell( "Error calling post-edit function " \
+ str(self._postEditFunc) )
self.__dict__['_editObj'] = None
self.__dict__['_editProp'] = ''
self.__dict__['_editBuf'] = []
self.__dict__['_postEditFunc'] = None
#------------------------------------------------------------------
# User METHOD: enterFunc
#------------------------------------------------------------------
def enterFunc(self,object,funcname):
"""User METHOD: enterFunc(self,object,funcname):
start the editor on the given function, creating it if it
does not already exist.
"""
if gOwner and gOwner != self:
raise "UseError", "enterFunc() must only be called on oneself"
if gOwner and not gOwner.programmer:
raise "PermError", "enterFunc requires programmer privs"
lpos = string.find(funcname,'(')
if lpos < 0: args = '(self)' # by default, make it a method
else:
args = string.strip(funcname[lpos:])
if not args or args == ')':
raise "DeclarationError", \
"methods must have at least one parameter (self)"
funcname = funcname[:lpos]
setattr( object, funcname, Func(funcname+args, gOwner) )
self.tell('Enter function code below:')
self.startEdit( object, funcname )
#------------------------------------------------------------------
# User METHOD: standardEdit
#------------------------------------------------------------------
def standardEdit( self, buf, entry, state ):
"""User METHOD: standardEdit( self, buf, entry, state ):
This method implements the simple, standard POO editor.
"""
if gOwner and gOwner != self:
raise "UseError", "standardEdit() must only be called on oneself"
if not state:
# new editing session
self.tell("[POO Editor -- enter .? for help]")
return 'OK'
if entry=='.?': # ? - help
self.tell("Editing Commands:")
self.tell(" .l List")
self.tell(" .d Delete Last Line")
self.tell(" .da Delete All Lines")
self.tell(" .x Save & Exit ('x' is optional)")
self.tell(" .q Quit (Abort)")
elif entry=='.l': # l - list
self.tell("---- Current Buffer ----")
for line in buf:
self.tell(line)
self.tell("------------------------")
elif entry=='.d': # d - delete last line
if not buf:
self.tell("Buffer is empty.")
return
del buf[-1]
self.tell("Line deleted.")
elif entry=='.da': # da - delete all
buf[:] = []
self.tell("All lines deleted.")
elif entry=='.' or entry == '.x': # x - save & exit
self.tell("Saved.")
return 'DONE'
elif entry=='.q': # q - quit (abort)
self.tell("Aborted.")
return 'ABORT'
else: # not a known command? Just append it to the buffer
buf.append(entry)
return 'OK'
#----------------------------------------------------------------------
# CLASS: Directory
#----------------------------------------------------------------------
class Directory(Obj):
"""CLASS: Directory
This subclass of Obj defines the class of Directories in the game.
These are POO objects which are only used for keeping references to
other objects. They have the special characteristic that contents
are treated like properties; that is, if Directory "foo" contains
an object called "bar", it can be referred to as "foo.bar".
Directories are severely restricted, so that they may be freely
used within a quota system (i.e., they won't take up much memory).
"""
#------------------------------------------------------------------
# Directory constructor
#------------------------------------------------------------------
def __init__(self,id=0,*parents):
"""Directory constructor."""
global gObjlist, gHighID, gProps
if not gProps.has_key(self): gProps[self] = {}
if not gContents.has_key(self): gContents[self] = []
if gUnpickling: return
for p in parents:
if gOwner and not gOwner.wizard and p != getObj('$dir'):
raise "PermError", "Directories must be derived only from $dir"
if not parents:
raise "PermError", "Directories must be derived only from $dir"
Obj.__init__(self,id)
self.__dict__['parents'] = parents
#------------------------------------------------------------------
# Directory attribute accessor
#------------------------------------------------------------------
def __getattr__(self,propname):
"""Directory attribute gettor.
Treats contents as if they were properties."""
# try usual methods first...
val = Obj.__getattr__(self,propname)
if val: return val
# if those fail, look in our contents
matches = filter( \
lambda x,y=propname:x.__dict__['name']==y,
gContents[self])
if matches: return matches[0]
return None
#------------------------------------------------------------------
# Directory METHOD: canAddOrDeleteProp
#------------------------------------------------------------------
def canAddOrDeleteProp(self, propname):
"""Directory METHOD: canAddOrDeleteProp(self, propname):
overrides Obj.canAddorDeleteProp;
only wizards can add properties to a Directory.
"""
if gOwner and not gOwner.wizard: return 0
return Obj.canAddOrDeleteProp(self, propname)
#------------------------------------------------------------------
# Directory METHOD: canWriteProp
#------------------------------------------------------------------
def canWriteProp(self, propname, useCflag=1):
"""Directory METHOD: canWriteprop(self, propname, useCflag=1):
overrides the Obj method; prevents nonwizards from changing
a directory's parents.
"""
# non-wizards can't change a directory's parents
if propname == "parents" and gOwner and not gOwner.wizard:
return 0
return Obj.canWriteProp( self, propname, useCflag )
#------------------------------------------------------------------
# Directory METHOD: proplist
#------------------------------------------------------------------
def proplist(self):
"""Directory METHOD: proplist(self):
return a list of property and contents names.
"""
# first, get normal props
props = Obj.proplist(self)
# then, add contents
props = props + map(lambda x:x.name, gContents[self])
props.sort()
return props
#----------------------------------------------------------------------
# FUNCTION: users
#----------------------------------------------------------------------
def users():
"""FUNCTION: users():
This function returns a list of all connected Users.
"""
global gObjlist
return filter( lambda x:x.__isuser and x.__outstream,
gObjlist.values() )
#----------------------------------------------------------------------
# FUNCTION: globalkeys
#----------------------------------------------------------------------
def globalkeys():
"""FUNCTION: globalkeys():
This function returns a list of global variable names, like
doing globals().keys().
"""
return gSafeGlobals.keys()
#----------------------------------------------------------------------
# FUNCTION: getObj
#----------------------------------------------------------------------
def getObj(ref):
"""FUNCTION: getObj(ref):
This function returns a POO object by number, #num, or $refname.
"""
global gObjlist
if type(ref) == IntType: return gObjlist[ref]
if type(ref) == StringType:
ref = string.join(string.split(ref,'@'),'at_')
periodpos = string.find(ref,'.')
if periodpos > 0:
parts = string.split(ref,'.')
obj = getObj(parts[0])
for p in parts[1:]:
obj = getattr(obj,p)
return obj
if ref[0] == '#':
return gObjlist[ string.atoi(ref[1:]) ]
if ref[0] == '$':
return gProps[gObjlist[0]][ref[1:]].val
if ref == "ALL":
return gObjlist.values()
raise "ParamError", "Invalid parameter (" + str(ref) + ") for getObj"
#----------------------------------------------------------------------
# FUNCTION: treasury
#----------------------------------------------------------------------
def treasury():
"""FUNCTION: treasury():
This function returns the current value of the wizard treasury.
(Wizards only.)
"""
global gTreasury, gOwner
if gOwner and not gOwner.wizard:
raise "PermError", "Must be wizard to view treasury"
return gTreasury
#----------------------------------------------------------------------
# FUNCTION: transfer
#----------------------------------------------------------------------
def transfer(src, dest, amount):
"""FUNCTION: transfer(src, dest, amount):
This function transfers credits from the source to the dest object.
If source is None, credits come from the treasury; in this case,
both gOwner and the dest object must be wizards.
"""
global gTreasury, gOwner
if gOwner and not gOwner.wizard:
raise "PermError", "Must be wizard to transfer funds"
if src:
if src.__dict__['__credits'] < amount:
raise "CreditError", str(amount) + " credits requested, " \
+ str(src.__dict__['__credits']) + " available"
src.__dict__['__credits'] = src.__dict__['__credits'] - amount
dest.__dict__['__credits'] = dest.__dict__['__credits'] + amount
else:
if not dest.wizard:
raise "PermError", "Treasury funds may be transferred only to wizards"
if gTreasury < amount:
raise "CreditError", str(amount) + " credits requested, " \
+ str(gTreasury) + " available"
gTreasury = gTreasury - amount
dest.__dict__['__credits'] = dest.__dict__['__credits'] + amount
#----------------------------------------------------------------------
# FUNCTION: move
#----------------------------------------------------------------------
def move(what,where):
"""FUNCTION: move(what,where):
This function moves an object inside another one, if various checks
are successfully negotiated.
"""
global gOwner
# mover must be a wizard or owner of the object
if gOwner and not gOwner.wizard: ### and gOwner!=what.owner:
raise "PermError", "Must be wizard to move()"
# if where is an object, it must return 1 from accept()
if where:
if callable(where.accept): OK = where.accept(what)
else: OK = where.accept
if not OK: raise "DeniedError", where.name + \
" refuses to accept " + what.name
# disallow any loops in the containment hierarchy
# this occurs if where is contained by what
loc = where
while loc:
if loc == what:
raise "ContainerLoopError", \
"Can't move "+what.name+" into "+where.name
loc = loc.location
# actually perform the move
prevOwner = gOwner; gOwner = move
oldwhere = what.location
# ...remove from previous location's contents list
if what.location:
gContents[what.location].remove(what)
# ...update location, and add to location's contents
what.location = where
if where:
gContents[where].append(what)
gOwner = prevOwner
# call notification routines
if oldwhere and callable(oldwhere.exitfunc): oldwhere.exitfunc(what)
if where and callable(where.enterfunc): where.enterfunc(what)
#----------------------------------------------------------------------
# FUNCTION: create
#----------------------------------------------------------------------
def create(creator,parent,name=""):
"""FUNCTION: create(creator, parent, name=''):
This function creates a new object and returns a reference to it.
"""
global gHighID, gTreasury, gOwner
# check parent object type, fertility, and creator funds
if type(parent) != InstanceType or \
(parent.__class__ != Obj and parent.__class__ != User and
parent.__class__ != Directory):
raise "ParamError", "Parent must be a POO object."
if not parent.f:
raise "ParamError", "Parent object must be fertile (flag f)."
if gOwner and not gOwner.wizard:
raise "PermError", "Object creation requires wizard privs."
cost = kObjValue + kCreationTax
if creator.__dict__['__credits'] < cost:
raise "CreditError", str(cost) + " credits needed for object creation."
# create a new object of the appropriate class
oldclass = parent.__class__
newobj = oldclass(gHighID+1, parent)
# set owner and name
newobj.__dict__['owner'] = creator
if name: newobj.name = name
else: newobj.name = "new" + parent.name
# move credits around
newobj.__dict__['__value'] = kObjValue
gTreasury = gTreasury + kCreationTax
creator.__dict__['__credits'] = creator.__dict__['__credits'] - cost
# place the object in the creator's inventory
move(newobj,creator)
return newobj
#----------------------------------------------------------------------
# FUNCTION: destroy
#----------------------------------------------------------------------
def destroy(object):
"""FUNCTION: destroy(object):
The object is destroyed, and all references to it are removed.
If it has any credits, they are given to its owner if possible, and
otherwise to the treasury. The object must not have any children.
This is a very time-consuming operation, and should be used rarely.
"""
global gObjlist, gOwner, gTreasury, gUpdateList
# check permissions
if gOwner and not gOwner.wizard and gOwner != object.owner:
raise "PermError", "Caller (" + str(gOwner) + \
") must be owner or wizard to destroy()"
# refuse to make orphans
children = filter(lambda x,a=object:a in x.parents,gObjlist.values())
if children:
raise "OrphanError", "Object has children: "+tostr(children)
# refuse to strand contents
if object.contents():
raise "ContentsError", "Object has contents: "+tostr(object.contents())
# adjust credits
gTreasury = gTreasury + kRecycleTax
if (object.__dict__['__value'] > kRecycleTax):
gained = object.__dict__['__value'] - kRecycleTax
else:
gained = 0
try:
recipient = object.__dict__['owner']
recipient.__dict__['__credits'] = recipient.__dict__['__credits'] + gained
except:
gTreasury = gTreasury + gained
# move into nothingness
move(object,None)
# destroy all properties, and its contents list
del gProps[object]
del gContents[object]
# find all references to the object, anywhere, and remove them
for ob in gObjlist.values():
for k in ob.__dict__.keys():
if ob.__dict__[k]==object: ob.__dict__[k] = None
if gProps.has_key(ob):
for k in gProps[ob].keys():
try:
if gProps[ob][k] == object or \
gProps[ob][k].val==object:
del gProps[ob][k]
except: pass
# remove from gObjlist
for k in gObjlist.keys():
if gObjlist[k] == object: del gObjlist[k]
# remove from gUpdateList
for i in range(0,len(gUpdateList)):
if gUpdateList[i] == object: del gUpdateList[i]
#----------------------------------------------------------------------
# FUNCTION: show
#----------------------------------------------------------------------
def show( message, parties, broadcast=1 ):
"""FUNCTION: show( message, parties, broadcast=1):
message: string containing key tags
e.g., "%1D %1:(gets) %2i."
parties: dictionary mapping keys to objects
e.g., {1:caller, 2:dobj}
broadcast: also broadcast a message to the container
of the object specified by this key
This function displays an effect involving several parties. Each
of the parties gets a customized message, and the location of
the party specified by the broadcast key gets a general message
sent to its broadcast function.
"""
# make lists out of the dictionary
keys = parties.keys()
values = parties.values() # can we assume this is the same order?!?
# get various customized outputs
outputs = msg.Msg().sub_parties(parties,message,values)
# tell each object (if it's really an object)
for i in range(0,len(values)):
if type(values[i]) == InstanceType:
values[i].tell(outputs[0][i])
# and finally, broadcast the general message if possible
if not broadcast: return
try: parties[broadcast].location.broadcast(outputs[1], values)
except: pass
#----------------------------------------------------------------------
# FUNCTION: POOopen
#----------------------------------------------------------------------
def POOopen( filename, mode='r' ):
"""FUNCTION: POOopen( filename, mode='r' ):
This function opens a file under the POO directory for reading.
Filenames with slashes (either direction) or colons are disallowed.
"""
# check privs
if gOwner and not gOwner.wizard:
raise "PermError", "open() requires wizard privs"
# check for dangerous characters
if '/' in filename or '\\' in filename or ':' in filename:
raise "ParamError", "filenames cannot contain /, \\, or :"
# append it to the POO file directory, and return the file ref
fullpath = os.path.join( gFilePath, filename )
return open( fullpath, mode )
#----------------------------------------------------------------------
# FUNCTION: gSave
#----------------------------------------------------------------------
def gSave(filename='poo.dat'):
"""FUNCTION: gSave(filename='poo.dat'):
This function saves the current database to a file (via pickle).
"""
global gObjlist, gProps, gContents, gCmdDefs, gTreasury, gLastSave
# try saving the previous data file
backupname = filename + ".bak"
try: os.unlink(backupname)
except: pass
try: os.rename(filename, backupname)
except: pass
file = open(filename, 'w')
PooPickler(file).dump((gObjlist,gProps,gContents,gCmdDefs,gTreasury))
file.close()
gLastSave = time()
#----------------------------------------------------------------------
# FUNCTION: gLoad
#----------------------------------------------------------------------
def gLoad(filename='poo.dat'):
"""FUNCTION: gLoad(filename='poo.dat'):
This function loads the database from a file (via pickle).
"""
global gObjlist, gProps, gContents, gCmdDefs, gSafeGlobals, \
gHighID, gUnpickling, gUpdateList, gTreasury
# unpickle the file
gUnpickling = 1
file = open(filename, 'r')
# (gObjlist,gProps,gContents,gTreasury) = pickle.load(file)
(gObjlist,gProps,gContents,gCmdDefs,gTreasury) = pickle.load(file)
file.close()
gUnpickling = 0
# build the index
kys = filter(lambda x:type(x)==types.IntType, gObjlist.keys())
kys.sort()
gHighID = kys[-1]
# figure out which objects need updates
gUpdateList = filter(lambda x:callable(x.update), gObjlist.values())
# set up any automatic globals which refer to the object set
if getObj(0).pub: gSafeGlobals['pub'] = getObj(0).pub
# apply corrections, if any
# (currently none)
#----------------------------------------------------------------------
# FUNCTION: gUpdate
#----------------------------------------------------------------------
def gUpdate():
"""FUNCTION: gUpdate():
This function updates all the objects in the database which need
updating. It should be called periodically by the main program
to make the engine crank. It also periodically saves the database.
"""
# update all objects in the update list
for ob in gUpdateList:
ob.DoUpdate()
# consider saving the database
t = time() - gLastSave
if t > maxSaveTime: # later: do more sophisticated checks here!
gSave()
#----------------------------------------------------------------------
# FUNCTION: newDatabase
#----------------------------------------------------------------------
def newDatabase():
"""FUNCTION: newDatabase():
This function creates a new database from scratch ...
used for bootstrapping the initial database.
"""
global gObjlist, gHighID
print "Creating new database..."
gObjlist = {}
gObjlist[0] = Obj(0)
gObjlist[0].name = "root"
print str(gObjlist[0]), ':', gObjlist[0].name
gObjlist[1] = Obj(1)
gObjlist[1].name = "object"
gObjlist[1].f = 1
print str(gObjlist[1]), ':', gObjlist[1].name
gObjlist[2] = User(2,gObjlist[1])
gObjlist[2].name = "Implementor"
gObjlist[2].wizard = 1
gObjlist[2].programmer = 1
print str(gObjlist[2]), ':', gObjlist[2].name
gObjlist[3] = Obj(3,gObjlist[1])
gObjlist[3].name = "place"
gObjlist[3].accept = 1
print str(gObjlist[3]), ':', gObjlist[3].name
move(gObjlist[2],gObjlist[3])
gObjlist[4] = Directory(4,gObjlist[1])
gObjlist[4].name = "directory"
print str(gObjlist[4]), ':', gObjlist[4].name
gHighID = 4
#----------------------------------------------------------------------
# FUNCTION: makeSafeGlobals
#----------------------------------------------------------------------
def makeSafeGlobals():
"""FUNCTION: makeSafeGlobals():
This function returns a set of global variables that will be safe
for POO coders to use.
"""
import md5
import imp
from time import time,ctime
from qsplit import *
from whrandom import randint
import poohelp
poohelp.hLoad()
safebuiltins = imp.new_module('safebuiltins')
exceptions = ['__import__','open','reload','unload','__name__',
'execfile', 'globals']
for name in __builtins__.keys():
if name not in exceptions:
safebuiltins.__dict__[name] = __builtins__[name]
safebuiltins.__dict__['super'] = None
safebuiltins.__dict__['caller'] = None
d = { 'getObj':getObj,
'gCmdLine':gCmdLine,
'__builtins__':safebuiltins,
'create':create,
'ctime':ctime,
'destroy':destroy,
'Directory':Directory,
'Func':Func,
'help':poohelp.help,
'globalkeys':globalkeys,
'marksub':marksub,
'md5':md5,
'move':move,
'msg':msg,
'Obj':Obj,
'open':POOopen,
'Prop':Prop,
'randint':randint,
'show':show,
'string':string,
'split':qsplit,
'time':time,
'tostr':tostr,
'treasury':treasury,
'transfer':transfer,
'User':User,
'users':users }
for item in d.values():
if type(item) == ModuleType and hasattr(item,'__builtins__'):
item.__builtins__ = safebuiltins
g = globals()
for k in g.keys():
if k[-4:] == "Type": d[k] = g[k]
return d
#----------------------------------------------------------------------
# FUNCTION: initialize
#----------------------------------------------------------------------
def initialize(newDB=0):
"""FUNCTION: initialize(newDB=0):
This function sets up POO globals variables, loads files, etc.
"""
global gSafeGlobals
gSafeGlobals = makeSafeGlobals()
if newDB: newDatabase()
else: gLoad()
#----------------------------------------------------------------------
# ----- end of poo.py -----
#----------------------------------------------------------------------