Rename internal:process to .mudpy.process
[mudpy.git] / lib / mudpy / misc.py
index bcb50c2..6c720d5 100644 (file)
@@ -1,6 +1,6 @@
 """Miscellaneous functions for the mudpy engine."""
 
-# Copyright (c) 2004-2015 Jeremy Stanley <fungi@yuggoth.org>. Permission
+# Copyright (c) 2004-2016 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.
 
@@ -23,9 +23,12 @@ class Element:
 
     """An element of the universe."""
 
-    def __init__(self, key, universe, filename=None):
+    def __init__(self, key, universe, filename=None, old_style=False):
         """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
 
@@ -33,7 +36,7 @@ class Element:
         self.universe = universe
 
         # clone attributes if this is replacing another element
-        if self.key in self.universe.contents:
+        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)
@@ -43,6 +46,9 @@ class Element:
         # i guess this is a new element then
         else:
 
+            # set of facet keys from the universe
+            self.facethash = dict()
+
             # not owned by a user by default (used for avatars)
             self.owner = None
 
@@ -82,7 +88,8 @@ class Element:
 
     def reload(self):
         """Create a new element and replace this one."""
-        Element(self.key, self.universe, self.origin.filename)
+        Element(self.key, self.universe, self.origin.filename,
+                old_style=self.old_style)
         del(self)
 
     def destroy(self):
@@ -94,10 +101,13 @@ class Element:
 
     def facets(self):
         """Return a list of non-inherited facets for this element."""
-        try:
-            return self.origin.data[self.key].keys()
-        except (AttributeError, KeyError):
-            return []
+        if self.old_style:
+            try:
+                return self.origin.data[self.key].keys()
+            except (AttributeError, KeyError):
+                return []
+        else:
+            return self.facethash
 
     def has_facet(self, facet):
         """Return whether the non-inherited facet exists."""
@@ -129,7 +139,10 @@ class Element:
         if default is None:
             default = ""
         try:
-            return self.origin.data[self.key][facet]
+            if self.old_style:
+                return self.origin.data[self.key][facet]
+            else:
+                return self.origin.data[".".join((self.key, facet))]
         except (KeyError, TypeError):
             pass
         if self.has_facet("inherit"):
@@ -142,9 +155,14 @@ class Element:
     def set(self, facet, value):
         """Set values."""
         if not self.has_facet(facet) or not self.get(facet) == value:
-            if self.key not in self.origin.data:
-                self.origin.data[self.key] = {}
-            self.origin.data[self.key][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[node] = self.origin.data[node]
             self.origin.modified = True
 
     def append(self, facet, value):
@@ -234,48 +252,17 @@ class Element:
 
     def move_direction(self, direction):
         """Relocate the element in a specified direction."""
-        self.echo_to_location(
-            self.get(
-                "name"
-            ) + " exits " + self.universe.categories[
-                "internal"
-            ][
-                "directions"
-            ].get(
-                direction
-            )[
-                "exit"
-            ] + "."
-        )
-        self.send(
-            "You exit " + self.universe.categories[
-                "internal"
-            ][
-                "directions"
-            ].get(
-                direction
-            )[
-                "exit"
-            ] + ".",
-            add_prompt=False
-        )
+        motion = self.universe.contents["mudpy.movement.%s" % direction]
+        enter_term = motion.get("enter_term")
+        exit_term = motion.get("exit_term")
+        self.echo_to_location("%s exits %s." % (self.get("name"), exit_term))
+        self.send("You exit %s." % exit_term, add_prompt=False)
         self.go_to(
             self.universe.contents[
                 self.get("location")].link_neighbor(direction)
         )
-        self.echo_to_location(
-            self.get(
-                "name"
-            ) + " arrives from " + self.universe.categories[
-                "internal"
-            ][
-                "directions"
-            ].get(
-                direction
-            )[
-                "enter"
-            ] + "."
-        )
+        self.echo_to_location("%s arrives from %s." % (
+            self.get("name"), enter_term))
 
     def look_at(self, key):
         """Show an element to another element."""
@@ -313,14 +300,9 @@ class Element:
         if re.match("""^area:-?\d+,-?\d+,-?\d+$""", self.key):
             coordinates = [(int(x))
                            for x in self.key.split(":")[1].split(",")]
-            directions = self.universe.categories["internal"]["directions"]
-            offsets = dict(
-                [
-                    (
-                        x, directions.get(x)["vector"]
-                    ) for x in directions.facets()
-                ]
-            )
+            offsets = dict((x,
+                self.universe.contents["mudpy.movement.%s" % x].get("vector")
+                ) for x in self.universe.directions)
             for portal in self.get("gridlinks"):
                 adjacent = map(lambda c, o: c + o,
                                coordinates, offsets[portal])
@@ -361,6 +343,7 @@ class Universe:
         self.categories = {}
         self.contents = {}
         self.default_origins = {}
+        self.directions = set()
         self.loglines = []
         self.private_files = []
         self.reload_flag = False
@@ -459,8 +442,8 @@ class Universe:
         """Create and open the listening socket."""
 
         # need to know the local address and port number for the listener
-        host = self.categories["internal"]["network"].get("host")
-        port = self.categories["internal"]["network"].get("port")
+        host = self.contents["mudpy.network"].get("host")
+        port = self.contents["mudpy.network"].get("port")
 
         # if no host was specified, bind to all local addresses (preferring
         # ipv6)
@@ -667,13 +650,8 @@ class User:
         if self.state is not "authenticated":
             log("User " + self.account.get("name") + " logged in.", 2)
             self.authenticated = True
-            if self.account.subkey in universe.categories[
-               "internal"
-               ][
-                "limits"
-            ].get(
-                "default_admins"
-            ):
+            if self.account.subkey in universe.contents["mudpy.limit"].get(
+                    "admins"):
                 self.account.set("administrator", "True")
 
     def show_menu(self):
@@ -890,8 +868,7 @@ class User:
                 # log non-printable characters remaining
                 if mudpy.telnet.is_enabled(self, mudpy.telnet.TELOPT_BINARY,
                                            mudpy.telnet.HIM):
-                    asciiline = b"".join(
-                        filter(lambda x: b" " <= x <= b"~", line))
+                    asciiline = bytes([x for x in line if 32 <= x <= 126])
                     if line != asciiline:
                         logline = "Non-ASCII characters from "
                         if self.account and self.account.get("name"):
@@ -930,7 +907,7 @@ class User:
             "actor:avatar:" + self.account.get("name") + ":" + str(
                 counter
             ),
-            universe
+            universe, old_style=True
         )
         self.avatar.append("inherit", "archetype:avatar")
         self.account.append("avatars", self.avatar.key)
@@ -996,11 +973,9 @@ def log(message, level=0):
     """Log a message."""
 
     # a couple references we need
-    file_name = universe.categories["internal"]["logging"].get("file")
-    max_log_lines = universe.categories["internal"]["logging"].get(
-        "max_log_lines"
-    )
-    syslog_name = universe.categories["internal"]["logging"].get("syslog")
+    file_name = universe.contents["mudpy.log"].get("file")
+    max_log_lines = universe.contents["mudpy.log"].get("lines")
+    syslog_name = universe.contents["mudpy.log"].get("syslog")
     timestamp = time.asctime()[4:19]
 
     # turn the message into a list of nonempty lines
@@ -1017,7 +992,7 @@ def log(message, level=0):
         file_descriptor.close()
 
     # send the timestamp and line to standard output
-    if universe.categories["internal"]["logging"].get("stdout"):
+    if universe.contents["mudpy.log"].get("stdout"):
         for line in lines:
             print(timestamp + " " + line)
 
@@ -1342,9 +1317,12 @@ def replace_macros(user, text, is_input=False):
     return text
 
 
-def escape_macros(text):
+def escape_macros(value):
     """Escapes replacement macros in text."""
-    return text.replace("$(", "$_(")
+    if type(value) is str:
+        return value.replace("$(", "$_(")
+    else:
+        return value
 
 
 def first_word(text, separator=" "):
@@ -1377,7 +1355,7 @@ def on_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
+            "internal:counters", universe, old_style=True
         )
 
     # update the log every now and then
@@ -1715,7 +1693,7 @@ def handler_entering_account_name(user):
 
         # otherwise, this could be a brand new user
         else:
-            user.account = Element("account:" + name, universe)
+            user.account = Element("account:" + name, universe, old_style=True)
             user.account.set("name", name)
             log("New user: " + name, 2)
             user.state = "checking_new_account_name"
@@ -1740,13 +1718,8 @@ def handler_checking_password(user):
             user.state = "main_utility"
 
     # if at first your hashes don't match, try, try again
-    elif user.password_tries < universe.categories[
-        "internal"
-    ][
-        "limits"
-    ].get(
-        "password_tries"
-    ) - 1:
+    elif user.password_tries < universe.contents["mudpy.limit"].get(
+            "password_tries") - 1:
         user.password_tries += 1
         user.error = "incorrect"
 
@@ -1779,13 +1752,8 @@ def handler_entering_new_password(user):
         user.state = "verifying_new_password"
 
     # the password was weak, try again if you haven't tried too many times
-    elif user.password_tries < universe.categories[
-        "internal"
-    ][
-        "limits"
-    ].get(
-        "password_tries"
-    ) - 1:
+    elif user.password_tries < universe.contents["mudpy.limit"].get(
+            "password_tries") - 1:
         user.password_tries += 1
         user.error = "weak"
 
@@ -1814,13 +1782,8 @@ def handler_verifying_new_password(user):
 
     # go back to entering the new password as long as you haven't tried
     # too many times
-    elif user.password_tries < universe.categories[
-        "internal"
-    ][
-        "limits"
-    ].get(
-        "password_tries"
-    ) - 1:
+    elif user.password_tries < universe.contents["mudpy.limit"].get(
+            "password_tries") - 1:
         user.password_tries += 1
         user.error = "differs"
         user.state = "entering_new_password"
@@ -2034,11 +1997,11 @@ def command_say(actor, parameters):
     if message:
 
         # match the punctuation used, if any, to an action
-        actions = universe.categories["internal"]["language"].get(
+        actions = universe.contents["mudpy.linguistic"].get(
             "actions"
         )
         default_punctuation = (
-            universe.categories["internal"]["language"].get(
+            universe.contents["mudpy.linguistic"].get(
                 "default_punctuation"))
         action = ""
 
@@ -2063,7 +2026,7 @@ def command_say(actor, parameters):
             message = message[0].lower() + message[1:]
 
             # iterate over all words in message, replacing typos
-            typos = universe.categories["internal"]["language"].get(
+            typos = universe.contents["mudpy.linguistic"].get(
                 "typos"
             )
             words = message.split()
@@ -2163,16 +2126,19 @@ def command_show(actor, parameters):
     elif arguments[0] == "element":
         if len(arguments) != 2:
             message = "You must specify one element."
-        elif arguments[1] in universe.contents:
-            element = universe.contents[arguments[1]]
+        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)")
             facets = element.facets()
-            facets.sort()
-            for facet in facets:
-                message += ("$(eol)   $(grn)" + facet + ": $(red)"
-                            + escape_macros(element.get(facet)) + "$(nrm)")
+            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])))
         else:
             message = "Element \"" + arguments[1] + "\" does not exist."
     elif arguments[0] == "result":
@@ -2246,7 +2212,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)
+                Element(element, universe, filename, old_style=True)
                 log(logline, 6)
         elif len(arguments) > 2:
             message = "You can only specify an element and a filename."
@@ -2346,7 +2312,7 @@ def daemonize(universe):
     """Fork and disassociate from everything."""
 
     # only if this is what we're configured to do
-    if universe.contents["internal:process"].get("daemon"):
+    if universe.contents["mudpy.process"].get("daemon"):
 
         # log before we start forking around, so the terminal gets the message
         log("Disassociating from the controlling terminal.")
@@ -2383,7 +2349,7 @@ def create_pidfile(universe):
     """Write a file containing the current process ID."""
     pid = str(os.getpid())
     log("Process ID: " + pid)
-    file_name = universe.contents["internal:process"].get("pidfile")
+    file_name = universe.contents["mudpy.process"].get("pidfile")
     if file_name:
         if not os.path.isabs(file_name):
             file_name = os.path.join(universe.startdir, file_name)
@@ -2395,7 +2361,7 @@ def create_pidfile(universe):
 
 def remove_pidfile(universe):
     """Remove the file containing the current process ID."""
-    file_name = universe.contents["internal:process"].get("pidfile")
+    file_name = universe.contents["mudpy.process"].get("pidfile")
     if file_name:
         if not os.path.isabs(file_name):
             file_name = os.path.join(universe.startdir, file_name)