"""Miscellaneous functions for the mudpy engine."""
-# Copyright (c) 2004-2016 Jeremy Stanley <fungi@yuggoth.org>. Permission
+# Copyright (c) 2004-2017 Jeremy Stanley <fungi@yuggoth.org>. Permission
# to use, copy, modify, and distribute this software is granted under
# terms provided in the LICENSE file distributed with this software.
"""An element of the universe."""
- def __init__(self, key, universe, filename=None, old_style=False):
+ def __init__(self, key, universe, origin=None, old_style=False):
"""Set up a new element."""
# TODO(fungi): This can be removed after the transition is complete
# parse out appropriate category and subkey names, add to list
if self.key.find(":") > 0:
+ # TODO(fungi) this can be removed once old_style Elements
+ # are no longer needed
self.category, self.subkey = self.key.split(":", 1)
+ elif self.key.find(".") > 0:
+ self.category, self.subkey = self.key.split(".", 1)[-2:]
else:
self.category = "other"
self.subkey = self.key
if self.category not in self.universe.categories:
- self.category = "other"
- self.subkey = self.key
-
- # get an appropriate filename for the origin
- if not filename:
- filename = self.universe.default_origins[self.category]
- if not os.path.isabs(filename):
- filename = os.path.abspath(filename)
+ self.universe.categories[self.category] = {}
- # add the file if it doesn't exist yet
- if filename not in self.universe.files:
- mudpy.data.DataFile(filename, self.universe)
+ # get an appropriate origin
+ if not origin:
+ self.universe.add_category(self.category)
+ origin = self.universe.files[
+ self.universe.origins[self.category]["fallback"]]
# record or reset a pointer to the origin file
- self.origin = self.universe.files[filename]
-
- # add a data section to the origin if necessary
- if self.key not in self.origin.data:
- self.origin.data[self.key] = {}
+ self.origin = self.universe.files[origin.source]
# add or replace this element in the universe
self.universe.contents[self.key] = self
def reload(self):
"""Create a new element and replace this one."""
- Element(self.key, self.universe, self.origin.filename,
- old_style=self.old_style)
+ Element(self.key, self.universe, self.origin, old_style=self.old_style)
del(self)
def destroy(self):
"""Remove an element from the universe and destroy it."""
- del(self.origin.data[self.key])
+ if self.old_style:
+ del self.origin.data[self.key]
+ else:
+ for facet in dict(self.facethash):
+ self.remove_facet(facet)
del self.universe.categories[self.category][self.subkey]
del self.universe.contents[self.key]
del self
def remove_facet(self, facet):
"""Remove a facet from the element."""
- if self.has_facet(facet):
- del(self.origin.data[self.key][facet])
- self.origin.modified = True
+ if self.old_style and self.has_facet(facet):
+ del self.origin.data[self.key][facet]
+ elif ".".join((self.key, facet)) in self.origin.data:
+ del self.origin.data[".".join((self.key, facet))]
+ if facet in self.facethash:
+ del self.facethash[facet]
+ self.origin.modified = True
def ancestry(self):
"""Return a list of the element's inheritance lineage."""
def set(self, facet, value):
"""Set values."""
+ if not self.origin.is_writeable() and not self.universe.loading:
+ # break if there is an attempt to update an element from a
+ # read-only file, unless the universe is in the midst of loading
+ # updated data from files
+ raise PermissionError("Altering elements in read-only files is "
+ "disallowed")
if facet in ["loglevel"]:
value = int(value)
+ elif facet in ["administrator"]:
+ value = bool(value)
if not self.has_facet(facet) or not self.get(facet) == value:
if self.old_style:
if self.key not in self.origin.data:
else:
node = ".".join((self.key, facet))
self.origin.data[node] = value
- self.facethash[node] = self.origin.data[node]
+ self.facethash[facet] = self.origin.data[node]
self.origin.modified = True
def append(self, facet, value):
def portals(self):
"""Map the portal directions for an area to neighbors."""
portals = {}
- if re.match("""^area:-?\d+,-?\d+,-?\d+$""", self.key):
+ if re.match(r"""^area:-?\d+,-?\d+,-?\d+$""", self.key):
coordinates = [(int(x))
for x in self.key.split(":")[1].split(",")]
offsets = dict(
"""Initialize the universe."""
self.categories = {}
self.contents = {}
- self.default_origins = {}
self.directions = set()
+ self.loading = False
self.loglines = []
- self.private_files = []
+ self.origins = {}
self.reload_flag = False
self.setup_loglines = []
self.startdir = os.getcwd()
def load(self):
"""Load universe data from persistent storage."""
+ # while loading, it's safe to update elements from read-only files
+ self.loading = True
+
# it's possible for this to enter before logging configuration is read
pending_loglines = []
del self.files[data_filename]
# start loading from the initial file
- mudpy.data.DataFile(self.filename, self)
+ mudpy.data.Data(self.filename, self)
+
+ # load default storage locations for categories
+ if hasattr(self, "contents") and "mudpy.filing" in self.contents:
+ self.origins.update(self.contents["mudpy.filing"].get(
+ "categories", {}))
+
+ # add some builtin categories we know we'll need
+ for category in ("account", "actor", "internal"):
+ self.add_category(category)
# make a list of inactive avatars
inactive_avatars = []
- for account in self.categories["account"].values():
+ for account in self.categories.get("account", {}).values():
for avatar in account.get("avatars"):
try:
inactive_avatars.append(self.contents[avatar])
except KeyError:
pending_loglines.append((
- "Missing avatar \"%s\", possible data corruption" %
+ 'Missing avatar "%s", possible data corruption' %
avatar, 6))
for user in self.userlist:
if user.avatar in inactive_avatars:
for element in self.contents.values():
element.update_location()
element.clean_contents()
+
+ # done loading, so disallow updating elements from read-only files
+ self.loading = False
+
return pending_loglines
def new(self):
"""Convenience method to get the elapsed time counter."""
return self.categories["internal"]["counters"].get("elapsed")
+ def add_category(self, category, fallback=None):
+ """Set up category tracking/metadata."""
+ if category not in self.origins:
+ self.origins[category] = {}
+ if not fallback:
+ fallback = mudpy.data.find_file(
+ ".".join((category, "yaml")), universe=self)
+ if "fallback" not in self.origins[category]:
+ self.origins[category]["fallback"] = fallback
+ flags = self.origins[category].get("flags", None)
+ if fallback not in self.files:
+ mudpy.data.Data(fallback, self, flags=flags)
+
class User:
def check_idle(self):
"""Warn or disconnect idle users as appropriate."""
idletime = universe.get_time() - self.last_input
- linkdead_dict = universe.categories["internal"]["time"].get(
- "linkdead"
- )
+ linkdead_dict = universe.contents[
+ "mudpy.timing.idle.disconnect"].facets()
if self.state in linkdead_dict:
linkdead_state = self.state
else:
log(logline, 2)
self.state = "disconnecting"
self.menu_seen = False
- idle_dict = universe.categories["internal"]["time"].get("idle")
+ idle_dict = universe.contents["mudpy.timing.idle.warn"].facets()
if self.state in idle_dict:
idle_state = self.state
else:
def authenticate(self):
"""Flag the user as authenticated and disconnect duplicates."""
if self.state is not "authenticated":
- log("User " + self.account.get("name") + " logged in.", 2)
self.authenticated = True
if ("mudpy.limit" in universe.contents and self.account.subkey in
universe.contents["mudpy.limit"].get("admins")):
- self.account.set("administrator", "True")
+ self.account.set("administrator", True)
+ log("Administrator %s authenticated." %
+ self.account.get("name"), 2)
+ else:
+ # log("User %s authenticated." % self.account.get("name"), 2)
+ log("User %s authenticated." % self.account.subkey, 2)
def show_menu(self):
"""Send the user their current menu."""
def new_avatar(self):
"""Instantiate a new, unconfigured avatar for this user."""
counter = 0
- while "avatar:" + self.account.get("name") + ":" + str(
- counter
- ) in universe.categories["actor"].keys():
+ while ("avatar_%s_%s" % (self.account.get("name"), counter)
+ in universe.categories.get("actor", {}).keys()):
counter += 1
self.avatar = Element(
- "actor:avatar:" + self.account.get("name") + ":" + str(
- counter
- ),
- universe, old_style=True
- )
- self.avatar.append("inherit", "archetype:avatar")
+ "actor.avatar_%s_%s" % (self.account.get("name"), counter),
+ universe)
+ self.avatar.append("inherit", "archetype.avatar")
self.account.append("avatars", self.avatar.key)
def delete_avatar(self, avatar):
try:
avatars.append(universe.contents[avatar].get("name"))
except KeyError:
- log("Missing avatar \"%s\", possible data corruption." %
+ log('Missing avatar "%s", possible data corruption.' %
avatar, 6)
return avatars
replacement = replacement[:-2]
else:
replacement = ""
- log("Couldn't read included " + incfile + " file.", 6)
+ log("Couldn't read included " + incfile + " file.", 7)
# if we get here, log and replace it with null
else:
user.pulse()
# add an element for counters if it doesn't exist
- if "counters" not in universe.categories["internal"]:
- universe.categories["internal"]["counters"] = Element(
- "internal:counters", universe, old_style=True
- )
+ if "counters" not in universe.categories.get("internal", {}):
+ Element("internal.counters", universe)
# update the log every now and then
if not universe.categories["internal"]["counters"].get("mark"):
log(str(len(universe.userlist)) + " connection(s)")
universe.categories["internal"]["counters"].set(
- "mark", universe.categories["internal"]["time"].get(
- "frequency_log"
- )
+ "mark", universe.contents["mudpy.timing"].get("status")
)
else:
universe.categories["internal"]["counters"].set(
if not universe.categories["internal"]["counters"].get("save"):
universe.save()
universe.categories["internal"]["counters"].set(
- "save", universe.categories["internal"]["time"].get(
- "frequency_save"
- )
+ "save", universe.contents["mudpy.timing"].get("save")
)
else:
universe.categories["internal"]["counters"].set(
)
# pause for a configurable amount of time (decimal seconds)
- time.sleep(universe.categories["internal"]
- ["time"].get("increment"))
+ time.sleep(universe.contents["mudpy.timing"].get("increment"))
# increase the elapsed increment counter
universe.categories["internal"]["counters"].set(
user.error = "bad_name"
# if that account exists, time to request a password
- elif name in universe.categories["account"]:
+ elif name in universe.categories.get("account", {}):
user.account = universe.categories["account"][name]
user.state = "checking_password"
# otherwise, this could be a brand new user
else:
- user.account = Element("account:" + name, universe, old_style=True)
+ user.account = Element("account.%s" % name, universe)
user.account.set("name", name)
log("New user: " + name, 2)
user.state = "checking_new_account_name"
log(
"User " +
actor.owner.account.get("name") + " reloaded the world.",
- 8
+ 6
)
# set a flag to reload
else:
output += " $(grn)"
output += item + "$(nrm) - " + description + "$(eol)"
- output += ("$(eol)Enter \"help COMMAND\" for help on a command "
- "named \"COMMAND\".")
+ output += ('$(eol)Enter "help COMMAND" for help on a command '
+ 'named "COMMAND".')
# send the accumulated output to the user
actor.send(output)
# if the message is wrapped in quotes, remove them and leave contents
# intact
- if parameters.startswith("\"") and parameters.endswith("\""):
+ if parameters.startswith('"') and parameters.endswith('"'):
message = parameters[1:-1]
literal = True
# otherwise, get rid of stray quote marks on the ends of the message
else:
- message = parameters.strip("\"'`")
+ message = parameters.strip('''"'`''')
literal = False
# the user entered a message
# tell the area
if message:
actor.echo_to_location(
- actor.get("name") + " " + action + "s, \"" + message + "\""
+ actor.get("name") + " " + action + 's, "' + message + '"'
)
- actor.send("You " + action + ", \"" + message + "\"")
+ actor.send("You " + action + ', "' + message + '"')
# there was no message
else:
message += "$(eol) $(grn)" + category + "$(nrm)"
elif arguments[0] == "files":
message = "These are the current files containing the universe:$(eol)"
- filenames = list(universe.files.keys())
- filenames.sort()
+ filenames = sorted(universe.files)
for filename in filenames:
if universe.files[filename].is_writeable():
status = "rw"
else:
status = "ro"
- message += ("$(eol) $(red)(" + status + ") $(grn)" + filename
- + "$(nrm)")
+ message += ("$(eol) $(red)(%s) $(grn)%s$(nrm)" %
+ (status, filename))
+ if universe.files[filename].flags:
+ message += (" $(yel)[%s]$(nrm)" %
+ ",".join(universe.files[filename].flags))
elif arguments[0] == "category":
if len(arguments) != 2:
message = "You must specify one category."
elif arguments[1] in universe.categories:
- message = ("These are the elements in the \"" + arguments[1]
- + "\" category:$(eol)")
+ message = ('These are the elements in the "' + arguments[1]
+ + '" category:$(eol)')
elements = [
(
universe.categories[arguments[1]][x].key
for element in elements:
message += "$(eol) $(grn)" + element + "$(nrm)"
else:
- message = "Category \"" + arguments[1] + "\" does not exist."
+ message = 'Category "' + arguments[1] + '" does not exist.'
elif arguments[0] == "file":
if len(arguments) != 2:
message = "You must specify one file."
elif arguments[1] in universe.files:
- message = ("These are the elements in the \"" + arguments[1]
- + "\" file:$(eol)")
- elements = universe.files[arguments[1]].data.keys()
- elements.sort()
+ message = ('These are the nodes in the "' + arguments[1]
+ + '" file:$(eol)')
+ elements = sorted(universe.files[arguments[1]].data)
for element in elements:
message += "$(eol) $(grn)" + element + "$(nrm)"
else:
- message = "Category \"" + arguments[1] + "\" does not exist."
+ message = 'File "%s" does not exist.' % arguments[1]
elif arguments[0] == "element":
if len(arguments) != 2:
message = "You must specify one element."
elif arguments[1].strip(".") in universe.contents:
element = universe.contents[arguments[1].strip(".")]
- message = ("These are the properties of the \"" + arguments[1]
- + "\" element (in \"" + element.origin.filename
- + "\"):$(eol)")
+ message = ('These are the properties of the "' + arguments[1]
+ + '" element (in "' + element.origin.source
+ + '"):$(eol)')
facets = element.facets()
for facet in sorted(facets):
if element.old_style:
message += ("$(eol) $(grn)%s: $(red)%s$(nrm)" %
(facet, str(facets[facet])))
else:
- message = "Element \"" + arguments[1] + "\" does not exist."
+ message = 'Element "' + arguments[1] + '" does not exist.'
elif arguments[0] == "result":
if len(arguments) < 2:
message = "You need to specify an expression."
"$(eol)$(bld)%s$(nrm)" % e)
elif arguments[0] == "log":
if len(arguments) == 4:
- if re.match("^\d+$", arguments[3]) and int(arguments[3]) >= 0:
+ if re.match(r"^\d+$", arguments[3]) and int(arguments[3]) >= 0:
stop = int(arguments[3])
else:
stop = -1
else:
stop = 0
if len(arguments) >= 3:
- if re.match("^\d+$", arguments[2]) and int(arguments[2]) > 0:
+ if re.match(r"^\d+$", arguments[2]) and int(arguments[2]) > 0:
start = int(arguments[2])
else:
start = -1
else:
start = 10
if len(arguments) >= 2:
- if (re.match("^\d+$", arguments[1])
+ if (re.match(r"^\d+$", arguments[1])
and 0 <= int(arguments[1]) <= 9):
level = int(arguments[1])
else:
message = ("When specified, level must be 0-9 (default 1), "
"start and stop must be >=1 (default 10 and 1).")
else:
- message = "I don't know what \"" + parameters + "\" is."
+ message = '''I don't know what "''' + parameters + '" is.'
actor.send(message)
if len(arguments) == 2:
element, filename = arguments
if element in universe.contents:
- message = "The \"" + element + "\" element already exists."
+ message = 'The "' + element + '" element already exists.'
else:
- message = ("You create \"" +
- element + "\" within the universe.")
+ message = ('You create "' +
+ element + '" within the universe.')
logline = actor.owner.account.get(
"name"
) + " created an element: " + element
logline += " in file " + filename
if filename not in universe.files:
message += (
- " Warning: \"" + filename + "\" is not yet "
+ ' Warning: "' + filename + '" is not yet '
"included in any other file and will not be read "
"on startup unless this is remedied.")
Element(element, universe, filename, old_style=True)
message = "You must specify an element to destroy."
else:
if parameters not in universe.contents:
- message = "The \"" + parameters + "\" element does not exist."
+ message = 'The "' + parameters + '" element does not exist.'
else:
universe.contents[parameters].destroy()
- message = ("You destroy \"" + parameters
- + "\" within the universe.")
+ message = ('You destroy "' + parameters
+ + '" within the universe.')
log(
actor.owner.account.get(
"name"
else:
arguments = parameters.split(" ", 2)
if len(arguments) == 1:
- message = ("What facet of element \"" + arguments[0]
- + "\" would you like to set?")
+ message = ('What facet of element "' + arguments[0]
+ + '" would you like to set?')
elif len(arguments) == 2:
- message = ("What value would you like to set for the \"" +
- arguments[1] + "\" facet of the \"" + arguments[0]
- + "\" element?")
+ message = ('What value would you like to set for the "' +
+ arguments[1] + '" facet of the "' + arguments[0]
+ + '" element?')
else:
element, facet, value = arguments
if element not in universe.contents:
- message = "The \"" + element + "\" element does not exist."
+ message = 'The "' + element + '" element does not exist.'
else:
- universe.contents[element].set(facet, value)
- message = ("You have successfully (re)set the \"" + facet
- + "\" facet of element \"" + element
- + "\". Try \"show element " +
- element + "\" for verification.")
+ try:
+ universe.contents[element].set(facet, value)
+ except PermissionError:
+ message = ('The "%s" element is kept in read-only file '
+ '"%s" and cannot be altered.' %
+ (element, universe.contents[
+ element].origin.source))
+ except ValueError:
+ message = ('Value "%s" of type "%s" cannot be coerced '
+ 'to the correct datatype for facet "%s".' %
+ (value, type(value), facet))
+ else:
+ message = ('You have successfully (re)set the "' + facet
+ + '" facet of element "' + element
+ + '". Try "show element ' +
+ element + '" for verification.')
actor.send(message)
else:
arguments = parameters.split(" ")
if len(arguments) == 1:
- message = ("What facet of element \"" + arguments[0]
- + "\" would you like to delete?")
+ message = ('What facet of element "' + arguments[0]
+ + '" would you like to delete?')
elif len(arguments) != 2:
message = "You may only specify an element and a facet."
else:
element, facet = arguments
if element not in universe.contents:
- message = "The \"" + element + "\" element does not exist."
+ message = 'The "' + element + '" element does not exist.'
elif facet not in universe.contents[element].facets():
- message = ("The \"" + element + "\" element has no \"" + facet
- + "\" facet.")
+ message = ('The "' + element + '" element has no "' + facet
+ + '" facet.')
else:
universe.contents[element].remove_facet(facet)
- message = ("You have successfully deleted the \"" + facet
- + "\" facet of element \"" + element
- + "\". Try \"show element " +
- element + "\" for verification.")
+ message = ('You have successfully deleted the "' + facet
+ + '" facet of element "' + element
+ + '". Try "show element ' +
+ element + '" for verification.')
actor.send(message)
# 90% of the time use a generic error
if random.randrange(10):
- message = "I'm not sure what \"" + input_data + "\" means..."
+ message = '''I'm not sure what "''' + input_data + '''" means...'''
# 10% of the time use the classic diku error
else: