X-Git-Url: https://mudpy.org/gitweb?p=mudpy.git;a=blobdiff_plain;f=mudpy%2Fmisc.py;h=a596eb5076ca757eac7bbb8edb5e1dccd1241b01;hp=1d2fffffeb77c6c33178eb3fd406076741c9febd;hb=80783fa11b631d77ff47c85080be65c4bce3f7f6;hpb=ec67d85b3081695865850e07ce8fcacb842159f8 diff --git a/mudpy/misc.py b/mudpy/misc.py index 1d2ffff..a596eb5 100644 --- a/mudpy/misc.py +++ b/mudpy/misc.py @@ -1,6 +1,6 @@ """Miscellaneous functions for the mudpy engine.""" -# Copyright (c) 2004-2016 Jeremy Stanley . Permission +# Copyright (c) 2004-2017 Jeremy Stanley . Permission # to use, copy, modify, and distribute this software is granted under # terms provided in the LICENSE file distributed with this software. @@ -23,64 +23,40 @@ class Element: """An element of the universe.""" - def __init__(self, key, universe, filename=None, old_style=False): + def __init__(self, key, universe, origin=None): """Set up a new element.""" - # TODO(fungi): This can be removed after the transition is complete - self.old_style = old_style - # keep track of our key name self.key = key # keep track of what universe it's loading into self.universe = universe - # clone attributes if this is replacing another element - if self.old_style and self.key in self.universe.contents: - old_element = self.universe.contents[self.key] - for attribute in vars(old_element).keys(): - exec("self." + attribute + " = old_element." + attribute) - if self.owner: - self.owner.avatar = self - - # i guess this is a new element then - else: + # set of facet keys from the universe + self.facethash = dict() - # set of facet keys from the universe - self.facethash = dict() + # not owned by a user by default (used for avatars) + self.owner = None - # not owned by a user by default (used for avatars) - self.owner = None + # no contents in here by default + self.contents = {} - # no contents in here by default - self.contents = {} + if self.key.find(".") > 0: + self.category, self.subkey = self.key.split(".")[-2:] + else: + self.category = "other" + self.subkey = self.key + if self.category not in self.universe.categories: + self.universe.categories[self.category] = {} - # parse out appropriate category and subkey names, add to list - if self.key.find(":") > 0: - self.category, self.subkey = self.key.split(":", 1) - 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) - - # 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 @@ -88,26 +64,20 @@ class Element: 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) del(self) def destroy(self): """Remove an element from the universe and destroy it.""" - del(self.origin.data[self.key]) + 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 facets(self): """Return a list of non-inherited facets for this element.""" - if self.old_style: - try: - return self.origin.data[self.key].keys() - except (AttributeError, KeyError): - return [] - else: - return self.facethash + return self.facethash def has_facet(self, facet): """Return whether the non-inherited facet exists.""" @@ -115,9 +85,11 @@ class Element: 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 ".".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.""" @@ -139,10 +111,7 @@ class Element: if default is None: default = "" try: - if self.old_style: - return self.origin.data[self.key][facet] - else: - return self.origin.data[".".join((self.key, facet))] + return self.origin.data[".".join((self.key, facet))] except (KeyError, TypeError): pass if self.has_facet("inherit"): @@ -154,17 +123,20 @@ class Element: 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: - self.origin.data[self.key] = {} - self.origin.data[self.key][facet] = value - else: - node = ".".join((self.key, facet)) - self.origin.data[node] = value - self.facethash[facet] = self.origin.data[node] + node = ".".join((self.key, facet)) + self.origin.data[node] = value + self.facethash[facet] = self.origin.data[node] self.origin.modified = True def append(self, facet, value): @@ -299,9 +271,9 @@ class Element: def portals(self): """Map the portal directions for an area to neighbors.""" portals = {} - if re.match(r"""^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(",")] + for x in self.key.split(".")[-1].split(",")] offsets = dict( (x, self.universe.contents["mudpy.movement.%s" % x].get("vector") @@ -309,7 +281,7 @@ class Element: for portal in self.get("gridlinks"): adjacent = map(lambda c, o: c + o, coordinates, offsets[portal]) - neighbor = "area:" + ",".join( + neighbor = "area." + ",".join( [(str(x)) for x in adjacent] ) if neighbor in self.universe.contents: @@ -345,10 +317,10 @@ class Universe: """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() @@ -375,6 +347,9 @@ class Universe: 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 = [] @@ -394,11 +369,20 @@ class Universe: 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]) @@ -425,6 +409,10 @@ class Universe: 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): @@ -491,6 +479,19 @@ class Universe: """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: @@ -650,11 +651,15 @@ class User: 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.""" @@ -901,17 +906,13 @@ class User: 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): @@ -1361,10 +1362,8 @@ def on_pulse(): 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"): @@ -1690,13 +1689,13 @@ def handler_entering_account_name(user): 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" @@ -2106,15 +2105,17 @@ def command_show(actor, parameters): 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." @@ -2135,30 +2136,25 @@ def command_show(actor, parameters): 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] + message = ('These are the nodes in the "' + arguments[1] + '" file:$(eol)') - elements = universe.files[arguments[1]].data.keys() - elements.sort() + 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 + + '" 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, escape_macros(element.get(facet)))) - else: - message += ("$(eol) $(grn)%s: $(red)%s$(nrm)" % - (facet, str(facets[facet]))) + message += ("$(eol) $(grn)%s: $(red)%s$(nrm)" % + (facet, str(facets[facet]))) else: message = 'Element "' + arguments[1] + '" does not exist.' elif arguments[0] == "result": @@ -2232,7 +2228,7 @@ def command_create(actor, parameters): ' 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) + Element(element, universe, filename) log(logline, 6) elif len(arguments) > 2: message = "You can only specify an element and a filename." @@ -2280,6 +2276,11 @@ def command_set(actor, parameters): else: 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".' %