Rename internal:process to .mudpy.process
[mudpy.git] / lib / mudpy / misc.py
index d67ff37..6c720d5 100644 (file)
@@ -1,19 +1,33 @@
-# -*- coding: utf-8 -*-
 """Miscellaneous functions for the mudpy engine."""
 
-# Copyright (c) 2004-2012 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.
 
+import codecs
+import os
+import random
+import re
+import signal
+import socket
+import sys
+import syslog
+import time
+import traceback
+import unicodedata
+
+import mudpy
+
 
 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."""
-        import mudpy.data
-        import os.path
+
+        # 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
@@ -22,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)
@@ -32,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
 
@@ -62,8 +79,8 @@ class Element:
         self.origin = self.universe.files[filename]
 
         # add a data section to the origin if necessary
-        if not self.origin.data.has_section(self.key):
-            self.origin.data.add_section(self.key)
+        if self.key not in self.origin.data:
+            self.origin.data[self.key] = {}
 
         # add or replace this element in the universe
         self.universe.contents[self.key] = self
@@ -71,22 +88,26 @@ class Element:
 
     def reload(self):
         """Create a new element and replace this one."""
-        new_element = 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):
         """Remove an element from the universe and destroy it."""
-        self.origin.data.remove_section(self.key)
+        del(self.origin.data[self.key])
         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.key in self.origin.data.sections():
-            return self.origin.data.options(self.key)
+        if self.old_style:
+            try:
+                return self.origin.data[self.key].keys()
+            except (AttributeError, KeyError):
+                return []
         else:
-            return []
+            return self.facethash
 
     def has_facet(self, facet):
         """Return whether the non-inherited facet exists."""
@@ -95,13 +116,15 @@ class Element:
     def remove_facet(self, facet):
         """Remove a facet from the element."""
         if self.has_facet(facet):
-            self.origin.data.remove_option(self.key, facet)
+            del(self.origin.data[self.key][facet])
             self.origin.modified = True
 
     def ancestry(self):
         """Return a list of the element's inheritance lineage."""
         if self.has_facet("inherit"):
-            ancestry = self.getlist("inherit")
+            ancestry = self.get("inherit")
+            if not ancestry:
+                ancestry = []
             for parent in ancestry[:]:
                 ancestors = self.universe.contents[parent].ancestry()
                 for ancestor in ancestors:
@@ -115,110 +138,43 @@ class Element:
         """Retrieve values."""
         if default is None:
             default = ""
-        if self.origin.data.has_option(self.key, facet):
-            raw_data = self.origin.data.get(self.key, facet)
-            if raw_data.startswith("u\"") or raw_data.startswith("u'"):
-                raw_data = raw_data[1:]
-            raw_data.strip("\"'")
-            return raw_data
-        elif self.has_facet("inherit"):
+        try:
+            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"):
             for ancestor in self.ancestry():
                 if self.universe.contents[ancestor].has_facet(facet):
                     return self.universe.contents[ancestor].get(facet)
         else:
             return default
 
-    def getboolean(self, facet, default=None):
-        """Retrieve values as boolean type."""
-        if default is None:
-            default = False
-        if self.origin.data.has_option(self.key, facet):
-            return self.origin.data.getboolean(self.key, facet)
-        elif self.has_facet("inherit"):
-            for ancestor in self.ancestry():
-                if self.universe.contents[ancestor].has_facet(facet):
-                    return self.universe.contents[ancestor].getboolean(facet)
-        else:
-            return default
-
-    def getint(self, facet, default=None):
-        """Return values as int/long type."""
-        if default is None:
-            default = 0
-        if self.origin.data.has_option(self.key, facet):
-            return self.origin.data.getint(self.key, facet)
-        elif self.has_facet("inherit"):
-            for ancestor in self.ancestry():
-                if self.universe.contents[ancestor].has_facet(facet):
-                    return self.universe.contents[ancestor].getint(facet)
-        else:
-            return default
-
-    def getfloat(self, facet, default=None):
-        """Return values as float type."""
-        if default is None:
-            default = 0.0
-        if self.origin.data.has_option(self.key, facet):
-            return self.origin.data.getfloat(self.key, facet)
-        elif self.has_facet("inherit"):
-            for ancestor in self.ancestry():
-                if self.universe.contents[ancestor].has_facet(facet):
-                    return self.universe.contents[ancestor].getfloat(facet)
-        else:
-            return default
-
-    def getlist(self, facet, default=None):
-        """Return values as list type."""
-        import mudpy.data
-        if default is None:
-            default = []
-        value = self.get(facet)
-        if value:
-            return mudpy.data.makelist(value)
-        else:
-            return default
-
-    def getdict(self, facet, default=None):
-        """Return values as dict type."""
-        import mudpy.data
-        if default is None:
-            default = {}
-        value = self.get(facet)
-        if value:
-            return mudpy.data.makedict(value)
-        else:
-            return default
-
     def set(self, facet, value):
         """Set values."""
         if not self.has_facet(facet) or not self.get(facet) == value:
-            if type(value) is long or repr(type(value)) == "<type 'unicode'>":
-                value = str(value)
-            elif not type(value) is str:
-                value = repr(value)
-            self.origin.data.set(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):
-        """Append value tp a list."""
-        if type(value) is long:
-            value = str(value)
-        elif not type(value) is str:
-            value = repr(value)
-        newlist = self.getlist(facet)
+        """Append value to a list."""
+        newlist = self.get(facet)
+        if not newlist:
+            newlist = []
+        if type(newlist) is not list:
+            newlist = list(newlist)
         newlist.append(value)
         self.set(facet, newlist)
 
-    def new_event(self, action, when=None):
-        """Create, attach and enqueue an event element."""
-
-        # if when isn't specified, that means now
-        if not when:
-            when = self.universe.get_time()
-
-        # events are elements themselves
-        event = Element("event:" + self.key + ":" + counter)
-
     def send(
         self,
         message,
@@ -251,11 +207,11 @@ class Element:
             result = False
 
         # avatars of administrators can run any command
-        elif self.owner and self.owner.account.getboolean("administrator"):
+        elif self.owner and self.owner.account.get("administrator"):
             result = True
 
         # everyone can run non-administrative commands
-        elif not command.getboolean("administrative"):
+        elif not command.get("administrative"):
             result = True
 
         # otherwise the command cannot be run by this actor
@@ -267,9 +223,9 @@ class Element:
 
     def update_location(self):
         """Make sure the location's contents contain this element."""
-        location = self.get("location")
-        if location in self.universe.contents:
-            self.universe.contents[location].contents[self.key] = self
+        area = self.get("location")
+        if area in self.universe.contents:
+            self.universe.contents[area].contents[self.key] = self
 
     def clean_contents(self):
         """Make sure the element's contents aren't bogus."""
@@ -277,15 +233,15 @@ class Element:
             if element.get("location") != self.key:
                 del self.contents[element.key]
 
-    def go_to(self, location):
-        """Relocate the element to a specific location."""
+    def go_to(self, area):
+        """Relocate the element to a specific area."""
         current = self.get("location")
         if current and self.key in self.universe.contents[current].contents:
             del universe.contents[current].contents[self.key]
-        if location in self.universe.contents:
-            self.set("location", location)
-        self.universe.contents[location].contents[self.key] = self
-        self.look_at(location)
+        if area in self.universe.contents:
+            self.set("location", area)
+        self.universe.contents[area].contents[self.key] = self
+        self.look_at(area)
 
     def go_home(self):
         """Relocate the element to its default location."""
@@ -296,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"
-            ].getdict(
-                direction
-            )[
-                "exit"
-            ] + "."
-        )
-        self.send(
-            "You exit " + self.universe.categories[
-                "internal"
-            ][
-                "directions"
-            ].getdict(
-                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"
-            ].getdict(
-                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."""
@@ -359,7 +284,7 @@ class Element:
             for element in self.universe.contents[
                 self.get("location")
             ].contents.values():
-                if element.getboolean("is_actor") and element is not self:
+                if element.get("is_actor") and element is not self:
                     message += "$(yel)" + element.get(
                         "name"
                     ) + " is here.$(nrm)$(eol)"
@@ -370,24 +295,18 @@ class Element:
             self.send(message)
 
     def portals(self):
-        """Map the portal directions for a room to neighbors."""
-        import re
+        """Map the portal directions for an area to neighbors."""
         portals = {}
-        if re.match("""^location:-?\d+,-?\d+,-?\d+$""", self.key):
+        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.getdict(x)["vector"]
-                    ) for x in directions.facets()
-                ]
-            )
-            for portal in self.getlist("gridlinks"):
+            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])
-                neighbor = "location:" + ",".join(
+                neighbor = "area:" + ",".join(
                     [(str(x)) for x in adjacent]
                 )
                 if neighbor in self.universe.contents:
@@ -421,30 +340,24 @@ class Universe:
 
     def __init__(self, filename="", load=False):
         """Initialize the universe."""
-        import os
-        import os.path
         self.categories = {}
         self.contents = {}
         self.default_origins = {}
+        self.directions = set()
         self.loglines = []
-        self.pending_events_long = {}
-        self.pending_events_short = {}
         self.private_files = []
         self.reload_flag = False
+        self.setup_loglines = []
         self.startdir = os.getcwd()
         self.terminate_flag = False
         self.userlist = []
         if not filename:
             possible_filenames = [
-                ".mudpyrc",
-                ".mudpy/mudpyrc",
-                ".mudpy/mudpy.conf",
-                "mudpy.conf",
-                "etc/mudpy.conf",
-                "/usr/local/mudpy/mudpy.conf",
-                "/usr/local/mudpy/etc/mudpy.conf",
-                "/etc/mudpy/mudpy.conf",
-                "/etc/mudpy.conf"
+                "etc/mudpy.yaml",
+                "/usr/local/mudpy/etc/mudpy.yaml",
+                "/usr/local/etc/mudpy.yaml",
+                "/etc/mudpy/mudpy.yaml",
+                "/etc/mudpy.yaml"
             ]
             for filename in possible_filenames:
                 if os.access(filename, os.R_OK):
@@ -453,11 +366,14 @@ class Universe:
             filename = os.path.join(self.startdir, filename)
         self.filename = filename
         if load:
-            self.load()
+            # make sure to preserve any accumulated log entries during load
+            self.setup_loglines += self.load()
 
     def load(self):
         """Load universe data from persistent storage."""
-        import mudpy.data
+
+        # it's possible for this to enter before logging configuration is read
+        pending_loglines = []
 
         # the files dict must exist and filename needs to be read-only
         if not hasattr(
@@ -470,7 +386,7 @@ class Universe:
 
             # clear out all read-only files
             if hasattr(self, "files"):
-                for data_filename in self.files.keys():
+                for data_filename in list(self.files.keys()):
                     if not self.files[data_filename].is_writeable():
                         del self.files[data_filename]
 
@@ -480,28 +396,33 @@ class Universe:
         # make a list of inactive avatars
         inactive_avatars = []
         for account in self.categories["account"].values():
-            inactive_avatars += [
-                (self.contents[x]) for x in account.getlist("avatars")
-            ]
+            for avatar in account.get("avatars"):
+                try:
+                    inactive_avatars.append(self.contents[avatar])
+                except KeyError:
+                    pending_loglines.append((
+                        "Missing avatar \"%s\", possible data corruption" %
+                        avatar, 6))
         for user in self.userlist:
             if user.avatar in inactive_avatars:
                 inactive_avatars.remove(user.avatar)
 
         # go through all elements to clear out inactive avatar locations
         for element in self.contents.values():
-            location = element.get("location")
-            if element in inactive_avatars and location:
-                if location in self.contents and element.key in self.contents[
-                   location
+            area = element.get("location")
+            if element in inactive_avatars and area:
+                if area in self.contents and element.key in self.contents[
+                   area
                    ].contents:
-                    del self.contents[location].contents[element.key]
-                element.set("default_location", location)
+                    del self.contents[area].contents[element.key]
+                element.set("default_location", area)
                 element.remove_facet("location")
 
         # another pass to straighten out all the element contents
         for element in self.contents.values():
             element.update_location()
             element.clean_contents()
+        return pending_loglines
 
     def new(self):
         """Create a new, empty Universe (the Big Bang)."""
@@ -519,11 +440,10 @@ class Universe:
 
     def initialize_server_socket(self):
         """Create and open the listening socket."""
-        import 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"].getint("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)
@@ -566,7 +486,7 @@ class Universe:
 
     def get_time(self):
         """Convenience method to get the elapsed time counter."""
-        return self.categories["internal"]["counters"].getint("elapsed")
+        return self.categories["internal"]["counters"].get("elapsed")
 
 
 class User:
@@ -575,7 +495,6 @@ class User:
 
     def __init__(self):
         """Default values for the in-memory user variables."""
-        import mudpy.telnet
         self.account = None
         self.address = ""
         self.authenticated = False
@@ -590,7 +509,7 @@ class User:
         self.menu_seen = False
         self.negotiation_pause = 0
         self.output_queue = []
-        self.partial_input = ""
+        self.partial_input = b""
         self.password_tries = 0
         self.state = "initial"
         self.telopts = {}
@@ -614,7 +533,7 @@ 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"].getdict(
+        linkdead_dict = universe.categories["internal"]["time"].get(
             "linkdead"
         )
         if self.state in linkdead_dict:
@@ -633,12 +552,12 @@ class User:
                 logline += self.account.get("name")
             else:
                 logline += "an unknown user"
-            logline += " after idling too long in the " + \
-                self.state + " state."
+            logline += (" after idling too long in the " + self.state
+                        + " state.")
             log(logline, 2)
             self.state = "disconnecting"
             self.menu_seen = False
-        idle_dict = universe.categories["internal"]["time"].getdict("idle")
+        idle_dict = universe.categories["internal"]["time"].get("idle")
         if self.state in idle_dict:
             idle_state = self.state
         else:
@@ -731,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"
-            ].getlist(
-                "default_admins"
-            ):
+            if self.account.subkey in universe.contents["mudpy.limit"].get(
+                    "admins"):
                 self.account.set("administrator", "True")
 
     def show_menu(self):
@@ -755,7 +669,6 @@ class User:
 
     def adjust_echoing(self):
         """Adjust echoing to match state menu requirements."""
-        import mudpy.telnet
         if mudpy.telnet.is_enabled(self, mudpy.telnet.TELOPT_ECHO,
                                    mudpy.telnet.US):
             if menu_echo_on(self.state):
@@ -781,7 +694,6 @@ class User:
         prepend_padding=True
     ):
         """Send arbitrary text to a connected user."""
-        import mudpy.telnet
 
         # unless raw mode is on, clean it up all nice and pretty
         if not raw:
@@ -800,16 +712,16 @@ class User:
             # with the optional eol string passed to this function
             # and the ansi escape to return to normal text
             if not just_prompt and prepend_padding:
-                if not self.output_queue \
-                   or not self.output_queue[-1].endswith("\r\n"):
+                if (not self.output_queue or not
+                        self.output_queue[-1].endswith(b"\r\n")):
                     output = "$(eol)" + output
                 elif not self.output_queue[-1].endswith(
-                    "\r\n\x1b[0m\r\n"
+                    b"\r\n\x1b[0m\r\n"
                 ) and not self.output_queue[-1].endswith(
-                    "\r\n\r\n"
+                    b"\r\n\r\n"
                 ):
                     output = "$(eol)" + output
-            output += eol + unichr(27) + "[0m"
+            output += eol + chr(27) + "[0m"
 
             # tack on a prompt if active
             if self.state == "active":
@@ -906,26 +818,23 @@ class User:
         if self.output_queue:
             try:
                 self.connection.send(self.output_queue[0])
-                del self.output_queue[0]
-            except:
+            except BrokenPipeError:
                 if self.account and self.account.get("name"):
                     account = self.account.get("name")
                 else:
                     account = "an unknown user"
-                log("Sending to %s raised an exception (broken pipe?)."
-                    % account, 7)
-                pass
+                self.state = "disconnecting"
+                log("Broken pipe sending to %s." % account, 7)
+            del self.output_queue[0]
 
     def enqueue_input(self):
         """Process and enqueue any new input."""
-        import mudpy.telnet
-        import unicodedata
 
         # check for some input
         try:
             raw_input = self.connection.recv(1024)
-        except:
-            raw_input = ""
+        except (BlockingIOError, OSError):
+            raw_input = b""
 
         # we got something
         if raw_input:
@@ -937,18 +846,18 @@ class User:
             mudpy.telnet.negotiate_telnet_options(self)
 
             # separate multiple input lines
-            new_input_lines = self.partial_input.split("\n")
+            new_input_lines = self.partial_input.split(b"\n")
 
             # if input doesn't end in a newline, replace the
             # held partial input with the last line of it
-            if not self.partial_input.endswith("\n"):
+            if not self.partial_input.endswith(b"\n"):
                 self.partial_input = new_input_lines.pop()
 
             # otherwise, chop off the extra null input and reset
             # the held partial input
             else:
                 new_input_lines.pop()
-                self.partial_input = ""
+                self.partial_input = b""
 
             # iterate over the remaining lines
             for line in new_input_lines:
@@ -959,8 +868,7 @@ class User:
                 # log non-printable characters remaining
                 if mudpy.telnet.is_enabled(self, mudpy.telnet.TELOPT_BINARY,
                                            mudpy.telnet.HIM):
-                    asciiline = "".join(
-                        filter(lambda x: " " <= x <= "~", 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"):
@@ -971,10 +879,22 @@ class User:
                         log(logline, 4)
                         line = asciiline
 
+                try:
+                    line = line.decode("utf-8")
+                except UnicodeDecodeError:
+                    logline = "Non-UTF-8 characters from "
+                    if self.account and self.account.get("name"):
+                        logline += self.account.get("name") + ": "
+                    else:
+                        logline += "unknown user: "
+                    logline += repr(line)
+                    log(logline, 4)
+                    return
+
+                line = unicodedata.normalize("NFKC", line)
+
                 # put on the end of the queue
-                self.input_queue.append(
-                    unicodedata.normalize("NFKC", line.decode("utf-8"))
-                )
+                self.input_queue.append(line)
 
     def new_avatar(self):
         """Instantiate a new, unconfigured avatar for this user."""
@@ -987,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)
@@ -997,14 +917,14 @@ class User:
         if self.avatar is universe.contents[avatar]:
             self.avatar = None
         universe.contents[avatar].destroy()
-        avatars = self.account.getlist("avatars")
+        avatars = self.account.get("avatars")
         avatars.remove(avatar)
         self.account.set("avatars", avatars)
 
     def activate_avatar_by_index(self, index):
         """Enter the world with a particular indexed avatar."""
         self.avatar = universe.contents[
-            self.account.getlist("avatars")[index]]
+            self.account.get("avatars")[index]]
         self.avatar.owner = self
         self.state = "active"
         self.avatar.go_home()
@@ -1027,17 +947,20 @@ class User:
 
     def destroy(self):
         """Destroy the user and associated avatars."""
-        for avatar in self.account.getlist("avatars"):
+        for avatar in self.account.get("avatars"):
             self.delete_avatar(avatar)
         self.account.destroy()
 
     def list_avatar_names(self):
         """List names of assigned avatars."""
-        return [
-            universe.contents[avatar].get(
-                "name"
-            ) for avatar in self.account.getlist("avatars")
-        ]
+        avatars = []
+        for avatar in self.account.get("avatars"):
+            try:
+                avatars.append(universe.contents[avatar].get("name"))
+            except KeyError:
+                log("Missing avatar \"%s\", possible data corruption." %
+                    avatar, 6)
+        return avatars
 
 
 def broadcast(message, add_prompt=True):
@@ -1048,23 +971,15 @@ def broadcast(message, add_prompt=True):
 
 def log(message, level=0):
     """Log a message."""
-    import codecs
-    import os.path
-    import syslog
-    import time
 
     # a couple references we need
-    file_name = universe.categories["internal"]["logging"].get("file")
-    max_log_lines = universe.categories["internal"]["logging"].getint(
-        "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 lines
-    lines = filter(
-        lambda x: x != "", [(x.rstrip()) for x in message.split("\n")]
-    )
+    # turn the message into a list of nonempty lines
+    lines = [x for x in [(x.rstrip()) for x in message.split("\n")] if x != ""]
 
     # send the timestamp and line to a file
     if file_name:
@@ -1077,7 +992,7 @@ def log(message, level=0):
         file_descriptor.close()
 
     # send the timestamp and line to standard output
-    if universe.categories["internal"]["logging"].getboolean("stdout"):
+    if universe.contents["mudpy.log"].get("stdout"):
         for line in lines:
             print(timestamp + " " + line)
 
@@ -1094,9 +1009,9 @@ def log(message, level=0):
 
     # display to connected administrators
     for user in universe.userlist:
-        if user.state == "active" and user.account.getboolean(
+        if user.state == "active" and user.account.get(
            "administrator"
-           ) and user.account.getint("loglevel") <= level:
+           ) and user.account.get("loglevel", 0) <= level:
             # iterate over every line in the message
             full_message = ""
             for line in lines:
@@ -1116,7 +1031,7 @@ def get_loglines(level, start, stop):
     """Return a specific range of loglines filtered by level."""
 
     # filter the log lines
-    loglines = filter(lambda x: x[0] >= level, universe.loglines)
+    loglines = [x for x in universe.loglines if x[0] >= level]
 
     # we need these in several places
     total_count = str(len(universe.loglines))
@@ -1165,7 +1080,6 @@ def get_loglines(level, start, stop):
 
 def glyph_columns(character):
     """Convenience function to return the column width of a glyph."""
-    import unicodedata
     if unicodedata.east_asian_width(character) in "FW":
         return 2
     else:
@@ -1174,7 +1088,6 @@ def glyph_columns(character):
 
 def wrap_ansi_text(text, width):
     """Wrap text with arbitrary width while ignoring ANSI colors."""
-    import unicodedata
 
     # the current position in the entire text string, including all
     # characters, printable or otherwise
@@ -1231,8 +1144,7 @@ def wrap_ansi_text(text, width):
                 last_whitespace = abs_pos
 
             # insert an eol in place of the space
-            text = text[:last_whitespace] + \
-                "\r\n" + text[last_whitespace + 1:]
+            text = text[:last_whitespace] + "\r\n" + text[last_whitespace + 1:]
 
             # increase the absolute position because an eol is two
             # characters but the space it replaced was only one
@@ -1261,7 +1173,6 @@ def wrap_ansi_text(text, width):
 
 def weighted_choice(data):
     """Takes a dict weighted by value and returns a random key."""
-    import random
 
     # this will hold our expanded list of keys from the data
     expanded = []
@@ -1277,7 +1188,6 @@ def weighted_choice(data):
 
 def random_name():
     """Returns a random character name."""
-    import random
 
     # the vowels and consonants needed to create romaji syllables
     vowels = [
@@ -1329,9 +1239,6 @@ def random_name():
 
 def replace_macros(user, text, is_input=False):
     """Replaces macros in text output."""
-    import codecs
-    import mudpy.data
-    import os.path
 
     # third person pronouns
     pronouns = {
@@ -1343,15 +1250,15 @@ def replace_macros(user, text, is_input=False):
     # a dict of replacement macros
     macros = {
         "eol": "\r\n",
-        "bld": unichr(27) + "[1m",
-        "nrm": unichr(27) + "[0m",
-        "blk": unichr(27) + "[30m",
-        "blu": unichr(27) + "[34m",
-        "cyn": unichr(27) + "[36m",
-        "grn": unichr(27) + "[32m",
-        "mgt": unichr(27) + "[35m",
-        "red": unichr(27) + "[31m",
-        "yel": unichr(27) + "[33m",
+        "bld": chr(27) + "[1m",
+        "nrm": chr(27) + "[0m",
+        "blk": chr(27) + "[30m",
+        "blu": chr(27) + "[34m",
+        "cyn": chr(27) + "[36m",
+        "grn": chr(27) + "[32m",
+        "mgt": chr(27) + "[35m",
+        "red": chr(27) + "[31m",
+        "yel": chr(27) + "[33m",
     }
 
     # add dynamic macros where possible
@@ -1410,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=" "):
@@ -1428,7 +1338,6 @@ def first_word(text, separator=" "):
 
 def on_pulse():
     """The things which should happen on each pulse, aside from reloads."""
-    import time
 
     # open the listening socket if it hasn't been already
     if not hasattr(universe, "listening_socket"):
@@ -1446,47 +1355,47 @@ 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
-    if not universe.categories["internal"]["counters"].getint("mark"):
+    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"].getint(
+            "mark", universe.categories["internal"]["time"].get(
                 "frequency_log"
             )
         )
     else:
         universe.categories["internal"]["counters"].set(
-            "mark", universe.categories["internal"]["counters"].getint(
+            "mark", universe.categories["internal"]["counters"].get(
                 "mark"
             ) - 1
         )
 
     # periodically save everything
-    if not universe.categories["internal"]["counters"].getint("save"):
+    if not universe.categories["internal"]["counters"].get("save"):
         universe.save()
         universe.categories["internal"]["counters"].set(
-            "save", universe.categories["internal"]["time"].getint(
+            "save", universe.categories["internal"]["time"].get(
                 "frequency_save"
             )
         )
     else:
         universe.categories["internal"]["counters"].set(
-            "save", universe.categories["internal"]["counters"].getint(
+            "save", universe.categories["internal"]["counters"].get(
                 "save"
             ) - 1
         )
 
     # pause for a configurable amount of time (decimal seconds)
     time.sleep(universe.categories["internal"]
-               ["time"].getfloat("increment"))
+               ["time"].get("increment"))
 
     # increase the elapsed increment counter
     universe.categories["internal"]["counters"].set(
-        "elapsed", universe.categories["internal"]["counters"].getint(
-            "elapsed"
+        "elapsed", universe.categories["internal"]["counters"].get(
+            "elapsed", 0
         ) + 1
     )
 
@@ -1503,12 +1412,11 @@ def reload_data():
 
 def check_for_connection(listening_socket):
     """Check for a waiting connection and return a new user object."""
-    import mudpy.telnet
 
     # try to accept a new connection
     try:
         connection, address = listening_socket.accept()
-    except:
+    except BlockingIOError:
         return None
 
     # note that we got one
@@ -1562,7 +1470,7 @@ def get_menu(state, error=None, choices=None):
 
 def menu_echo_on(state):
     """True if echo is on, false if it is off."""
-    return universe.categories["menu"][state].getboolean("echo", True)
+    return universe.categories["menu"][state].get("echo", True)
 
 
 def get_echo_message(state):
@@ -1720,7 +1628,6 @@ def get_choice_action(user, choice):
 
 def handle_user_input(user):
     """The main handler, branches to a state-specific handler."""
-    import mudpy.telnet
 
     # if the user's client echo is off, send a blank line for aesthetics
     if mudpy.telnet.is_enabled(user, mudpy.telnet.TELOPT_ECHO,
@@ -1786,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"
@@ -1798,7 +1705,6 @@ def handler_entering_account_name(user):
 
 def handler_checking_password(user):
     """Handle the login account password."""
-    import mudpy.password
 
     # get the next waiting line of input
     input_data = user.input_queue.pop(0)
@@ -1812,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"
-    ].getint(
-        "password_tries"
-    ) - 1:
+    elif user.password_tries < universe.contents["mudpy.limit"].get(
+            "password_tries") - 1:
         user.password_tries += 1
         user.error = "incorrect"
 
@@ -1832,7 +1733,6 @@ def handler_checking_password(user):
 
 def handler_entering_new_password(user):
     """Handle a new password entry."""
-    import mudpy.password
 
     # get the next waiting line of input
     input_data = user.input_queue.pop(0)
@@ -1852,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"
-    ].getint(
-        "password_tries"
-    ) - 1:
+    elif user.password_tries < universe.contents["mudpy.limit"].get(
+            "password_tries") - 1:
         user.password_tries += 1
         user.error = "weak"
 
@@ -1873,7 +1768,6 @@ def handler_entering_new_password(user):
 
 def handler_verifying_new_password(user):
     """Handle the re-entered new password for verification."""
-    import mudpy.password
 
     # get the next waiting line of input
     input_data = user.input_queue.pop(0)
@@ -1888,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"
-    ].getint(
-        "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"
@@ -2012,7 +1901,7 @@ def command_help(actor, parameters):
             description = command.get("description")
             if not description:
                 description = "(no short description provided)"
-            if command.getboolean("administrative"):
+            if command.get("administrative"):
                 output = "$(red)"
             else:
                 output = "$(grn)"
@@ -2025,7 +1914,7 @@ def command_help(actor, parameters):
             output += help_text
 
             # list related commands
-            see_also = command.getlist("see_also")
+            see_also = command.get("see_also")
             if see_also:
                 really_see_also = ""
                 for item in see_also:
@@ -2034,7 +1923,7 @@ def command_help(actor, parameters):
                         if actor.can_run(command):
                             if really_see_also:
                                 really_see_also += ", "
-                            if command.getboolean("administrative"):
+                            if command.get("administrative"):
                                 really_see_also += "$(red)"
                             else:
                                 really_see_also += "$(grn)"
@@ -2059,13 +1948,13 @@ def command_help(actor, parameters):
                 description = command.get("description")
                 if not description:
                     description = "(no short description provided)"
-                if command.getboolean("administrative"):
+                if command.get("administrative"):
                     output += "   $(red)"
                 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)
@@ -2088,8 +1977,7 @@ def command_look(actor, parameters):
 
 
 def command_say(actor, parameters):
-    """Speak to others in the same room."""
-    import unicodedata
+    """Speak to others in the same area."""
 
     # check for replacement macros and escape them
     parameters = escape_macros(parameters)
@@ -2109,14 +1997,16 @@ def command_say(actor, parameters):
     if message:
 
         # match the punctuation used, if any, to an action
-        actions = universe.categories["internal"]["language"].getdict(
+        actions = universe.contents["mudpy.linguistic"].get(
             "actions"
         )
         default_punctuation = (
-            universe.categories["internal"]["language"].get(
+            universe.contents["mudpy.linguistic"].get(
                 "default_punctuation"))
         action = ""
-        for mark in actions.keys():
+
+        # reverse sort punctuation options so the longest match wins
+        for mark in sorted(actions.keys(), reverse=True):
             if not literal and message.endswith(mark):
                 action = actions[mark]
                 break
@@ -2136,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"].getdict(
+            typos = universe.contents["mudpy.linguistic"].get(
                 "typos"
             )
             words = message.split()
@@ -2153,7 +2043,7 @@ def command_say(actor, parameters):
             # capitalize the first letter
             message = message[0].upper() + message[1:]
 
-    # tell the room
+    # tell the area
     if message:
         actor.echo_to_location(
             actor.get("name") + " " + action + "s, \"" + message + "\""
@@ -2180,7 +2070,6 @@ def command_chat(actor):
 
 def command_show(actor, parameters):
     """Show program data."""
-    import re
     message = ""
     arguments = parameters.split()
     if not parameters:
@@ -2204,14 +2093,14 @@ def command_show(actor, parameters):
                 status = "rw"
             else:
                 status = "ro"
-            message += "$(eol)   $(red)(" + status + ") $(grn)" + filename \
-                + "$(nrm)"
+            message += ("$(eol)   $(red)(" + status + ") $(grn)" + filename
+                        + "$(nrm)")
     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
@@ -2226,9 +2115,9 @@ 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] \
-                + "\" file:$(eol)"
-            elements = universe.files[arguments[1]].data.sections()
+            message = ("These are the elements in the \"" + arguments[1]
+                       + "\" file:$(eol)")
+            elements = universe.files[arguments[1]].data.keys()
             elements.sort()
             for element in elements:
                 message += "$(eol)   $(grn)" + element + "$(nrm)"
@@ -2237,17 +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]]
-            message = "These are the properties of the \"" + arguments[1] \
-                + \
-                "\" element (in \"" + \
-                element.origin.filename + "\"):$(eol)"
+        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":
@@ -2256,8 +2147,9 @@ def command_show(actor, parameters):
         else:
             try:
                 message = repr(eval(" ".join(arguments[1:])))
-            except:
-                message = "Your expression raised an exception!"
+            except Exception as e:
+                message = ("$(red)Your expression raised an exception...$(eol)"
+                           "$(eol)$(bld)%s$(nrm)" % e)
     elif arguments[0] == "log":
         if len(arguments) == 4:
             if re.match("^\d+$", arguments[3]) and int(arguments[3]) >= 0:
@@ -2279,15 +2171,15 @@ def command_show(actor, parameters):
                 level = int(arguments[1])
             else:
                 level = -1
-        elif 0 <= actor.owner.account.getint("loglevel") <= 9:
-            level = actor.owner.account.getint("loglevel")
+        elif 0 <= actor.owner.account.get("loglevel", 0) <= 9:
+            level = actor.owner.account.get("loglevel", 0)
         else:
             level = 1
         if level > -1 and start > -1 and stop > -1:
             message = get_loglines(level, start, stop)
         else:
-            message = "When specified, level must be 0-9 (default 1), " \
-                + "start and stop must be >=1 (default 10 and 1)."
+            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."
     actor.send(message)
@@ -2308,19 +2200,19 @@ def command_create(actor, parameters):
             if element in universe.contents:
                 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
                 if filename:
                     logline += " in file " + filename
                     if filename not in universe.files:
-                        message += " 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)
+                        message += (
+                            " 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)
                 log(logline, 6)
         elif len(arguments) > 2:
             message = "You can only specify an element and a filename."
@@ -2334,12 +2226,11 @@ def command_destroy(actor, parameters):
             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"
@@ -2356,22 +2247,22 @@ def command_set(actor, parameters):
     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."
             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."
+                message = ("You have successfully (re)set the \"" + facet
+                           + "\" facet of element \"" + element
+                           + "\". Try \"show element " +
+                           element + "\" for verification.")
     actor.send(message)
 
 
@@ -2382,8 +2273,8 @@ def command_delete(actor, parameters):
     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:
@@ -2391,20 +2282,19 @@ def command_delete(actor, parameters):
             if element not in universe.contents:
                 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)
 
 
 def command_error(actor, input_data):
     """Generic error for an unrecognized command word."""
-    import random
 
     # 90% of the time use a generic error
     if random.randrange(10):
@@ -2420,58 +2310,9 @@ def command_error(actor, input_data):
 
 def daemonize(universe):
     """Fork and disassociate from everything."""
-    import codecs
-    import ctypes
-    import ctypes.util
-    import os
-    import os.path
-    import sys
 
     # only if this is what we're configured to do
-    if universe.contents["internal:process"].getboolean("daemon"):
-
-        # if possible, we want to rename the process to the same as the script
-        # (these will need to be byte type during 2to3 migration)
-        new_argv = "\0".join(sys.argv) + "\0"
-        new_short_argv0 = os.path.basename(sys.argv[0]) + "\0"
-
-        # attempt the linux way first
-        try:
-            argv_array = ctypes.POINTER(ctypes.c_char_p)
-            ctypes.pythonapi.Py_GetArgcArgv.argtypes = (
-                ctypes.POINTER(ctypes.c_int),
-                ctypes.POINTER(argv_array)
-            )
-            argc = argv_array()
-            ctypes.pythonapi.Py_GetArgcArgv(
-                ctypes.c_int(0),
-                ctypes.pointer(argc)
-            )
-            old_argv0_size = len(argc.contents.value)
-            ctypes.memset(argc.contents, 0, len(new_argv) + old_argv0_size)
-            ctypes.memmove(argc.contents, new_argv, len(new_argv))
-            ctypes.CDLL(ctypes.util.find_library("c")).prctl(
-                15,
-                new_short_argv0,
-                0,
-                0,
-                0
-            )
-
-        except:
-
-            # since that failed, maybe it's bsd?
-            try:
-
-                # much simpler, since bsd has a libc function call for this
-                ctypes.CDLL(ctypes.util.find_library("c")).setproctitle(
-                    new_argv
-                )
-
-            except:
-
-                # that didn't work either, so just log that we couldn't
-                log("Failed to rename the interpreter process (cosmetic).")
+    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.")
@@ -2506,12 +2347,9 @@ def daemonize(universe):
 
 def create_pidfile(universe):
     """Write a file containing the current process ID."""
-    import codecs
-    import os
-    import os.path
     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)
@@ -2523,9 +2361,7 @@ def create_pidfile(universe):
 
 def remove_pidfile(universe):
     """Remove the file containing the current process ID."""
-    import os
-    import os.path
-    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)
@@ -2535,7 +2371,6 @@ def remove_pidfile(universe):
 
 def excepthook(excepttype, value, tracebackdata):
     """Handle uncaught exceptions."""
-    import traceback
 
     # assemble the list of errors into a single string
     message = "".join(
@@ -2545,19 +2380,13 @@ def excepthook(excepttype, value, tracebackdata):
     # try to log it, if possible
     try:
         log(message, 9)
-    except:
-        pass
-
-    # try to write it to stderr, if possible
-    try:
-        sys.stderr.write(message)
-    except:
-        pass
+    except Exception as e:
+        # try to write it to stderr, if possible
+        sys.stderr.write(message + "\nException while logging...\n%s" % e)
 
 
 def sighook(what, where):
     """Handle external signals."""
-    import signal
 
     # a generic message
     message = "Caught signal: "
@@ -2582,20 +2411,17 @@ def sighook(what, where):
 
 def override_excepthook():
     """Redefine sys.excepthook with our own."""
-    import sys
     sys.excepthook = excepthook
 
 
 def assign_sighook():
     """Assign a customized handler for some signals."""
-    import signal
     signal.signal(signal.SIGHUP, sighook)
     signal.signal(signal.SIGTERM, sighook)
 
 
 def setup():
     """This contains functions to be performed when starting the engine."""
-    import sys
 
     # see if a configuration file was specified
     if len(sys.argv) > 1:
@@ -2607,6 +2433,11 @@ def setup():
     global universe
     universe = Universe(conffile, True)
 
+    # report any loglines which accumulated during setup
+    for logline in universe.setup_loglines:
+        log(*logline)
+    universe.setup_loglines = []
+
     # log an initial message
     log("Started mudpy with command line: " + " ".join(sys.argv))
 
@@ -2627,8 +2458,7 @@ def setup():
 
 
 def finish():
-    """This contains functions to be performed when shutting down the
-        engine."""
+    """These are functions performed when shutting down the engine."""
 
     # the loop has terminated, so save persistent data
     universe.save()