Drop old-style Element support
[mudpy.git] / mudpy / misc.py
index 157096c..a596eb5 100644 (file)
@@ -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"):
@@ -162,15 +131,12 @@ class Element:
                                   "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):
@@ -305,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")
@@ -315,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:
@@ -351,11 +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()
@@ -404,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])
@@ -505,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:
 
@@ -664,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."""
@@ -915,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):
@@ -1375,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"):
@@ -1704,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"
@@ -2120,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."
@@ -2149,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":
@@ -2246,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."
@@ -2294,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".' %