Update copyright dates for files changed this year
[mudpy.git] / mudpy / misc.py
index 3cb201c..af5427a 100644 (file)
@@ -1,6 +1,6 @@
 """Miscellaneous functions for the mudpy engine."""
 
-# Copyright (c) 2004-2016 Jeremy Stanley <fungi@yuggoth.org>. Permission
+# Copyright (c) 2004-2017 Jeremy Stanley <fungi@yuggoth.org>. Permission
 # to use, copy, modify, and distribute this software is granted under
 # terms provided in the LICENSE file distributed with this software.
 
@@ -154,6 +154,8 @@ class Element:
 
     def set(self, facet, value):
         """Set values."""
+        if facet in ["loglevel"]:
+            value = int(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:
@@ -162,7 +164,7 @@ class Element:
             else:
                 node = ".".join((self.key, facet))
                 self.origin.data[node] = value
-                self.facethash[node] = self.origin.data[node]
+                self.facethash[facet] = self.origin.data[node]
             self.origin.modified = True
 
     def append(self, facet, value):
@@ -297,7 +299,7 @@ class Element:
     def portals(self):
         """Map the portal directions for an area to neighbors."""
         portals = {}
-        if re.match("""^area:-?\d+,-?\d+,-?\d+$""", self.key):
+        if re.match(r"""^area:-?\d+,-?\d+,-?\d+$""", self.key):
             coordinates = [(int(x))
                            for x in self.key.split(":")[1].split(",")]
             offsets = dict(
@@ -402,7 +404,7 @@ class Universe:
                     inactive_avatars.append(self.contents[avatar])
                 except KeyError:
                     pending_loglines.append((
-                        "Missing avatar \"%s\", possible data corruption" %
+                        'Missing avatar "%s", possible data corruption' %
                         avatar, 6))
         for user in self.userlist:
             if user.avatar in inactive_avatars:
@@ -534,9 +536,8 @@ class User:
     def check_idle(self):
         """Warn or disconnect idle users as appropriate."""
         idletime = universe.get_time() - self.last_input
-        linkdead_dict = universe.categories["internal"]["time"].get(
-            "linkdead"
-        )
+        linkdead_dict = universe.contents[
+            "mudpy.timing.idle.disconnect"].facets()
         if self.state in linkdead_dict:
             linkdead_state = self.state
         else:
@@ -558,7 +559,7 @@ class User:
             log(logline, 2)
             self.state = "disconnecting"
             self.menu_seen = False
-        idle_dict = universe.categories["internal"]["time"].get("idle")
+        idle_dict = universe.contents["mudpy.timing.idle.warn"].facets()
         if self.state in idle_dict:
             idle_state = self.state
         else:
@@ -651,8 +652,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.contents["mudpy.limit"].get(
-                    "admins"):
+            if ("mudpy.limit" in universe.contents and self.account.subkey in
+                    universe.contents["mudpy.limit"].get("admins")):
                 self.account.set("administrator", "True")
 
     def show_menu(self):
@@ -959,7 +960,7 @@ class User:
             try:
                 avatars.append(universe.contents[avatar].get("name"))
             except KeyError:
-                log("Missing avatar \"%s\", possible data corruption." %
+                log('Missing avatar "%s", possible data corruption.' %
                     avatar, 6)
         return avatars
 
@@ -974,9 +975,14 @@ def log(message, level=0):
     """Log a message."""
 
     # a couple references we need
-    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")
+    if "mudpy.log" in universe.contents:
+        file_name = universe.contents["mudpy.log"].get("file", "")
+        max_log_lines = universe.contents["mudpy.log"].get("lines", 0)
+        syslog_name = universe.contents["mudpy.log"].get("syslog", "")
+    else:
+        file_name = ""
+        max_log_lines = 0
+        syslog_name = ""
     timestamp = time.asctime()[4:19]
 
     # turn the message into a list of nonempty lines
@@ -993,7 +999,8 @@ def log(message, level=0):
         file_descriptor.close()
 
     # send the timestamp and line to standard output
-    if universe.contents["mudpy.log"].get("stdout"):
+    if ("mudpy.log" in universe.contents and
+            universe.contents["mudpy.log"].get("stdout")):
         for line in lines:
             print(timestamp + " " + line)
 
@@ -1300,7 +1307,7 @@ def replace_macros(user, text, is_input=False):
                 replacement = replacement[:-2]
             else:
                 replacement = ""
-                log("Couldn't read included " + incfile + " file.", 6)
+                log("Couldn't read included " + incfile + " file.", 7)
 
         # if we get here, log and replace it with null
         else:
@@ -1363,9 +1370,7 @@ def on_pulse():
     if not universe.categories["internal"]["counters"].get("mark"):
         log(str(len(universe.userlist)) + " connection(s)")
         universe.categories["internal"]["counters"].set(
-            "mark", universe.categories["internal"]["time"].get(
-                "frequency_log"
-            )
+            "mark", universe.contents["mudpy.timing"].get("status")
         )
     else:
         universe.categories["internal"]["counters"].set(
@@ -1378,9 +1383,7 @@ def on_pulse():
     if not universe.categories["internal"]["counters"].get("save"):
         universe.save()
         universe.categories["internal"]["counters"].set(
-            "save", universe.categories["internal"]["time"].get(
-                "frequency_save"
-            )
+            "save", universe.contents["mudpy.timing"].get("save")
         )
     else:
         universe.categories["internal"]["counters"].set(
@@ -1390,8 +1393,7 @@ def on_pulse():
         )
 
     # pause for a configurable amount of time (decimal seconds)
-    time.sleep(universe.categories["internal"]
-               ["time"].get("increment"))
+    time.sleep(universe.contents["mudpy.timing"].get("increment"))
 
     # increase the elapsed increment counter
     universe.categories["internal"]["counters"].set(
@@ -1710,6 +1712,12 @@ def handler_checking_password(user):
     # get the next waiting line of input
     input_data = user.input_queue.pop(0)
 
+    if "mudpy.limit" in universe.contents:
+        max_password_tries = universe.contents["mudpy.limit"].get(
+            "password_tries", 3)
+    else:
+        max_password_tries = 3
+
     # does the hashed input equal the stored hash?
     if mudpy.password.verify(input_data, user.account.get("passhash")):
 
@@ -1719,8 +1727,7 @@ 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.contents["mudpy.limit"].get(
-            "password_tries") - 1:
+    elif user.password_tries < max_password_tries - 1:
         user.password_tries += 1
         user.error = "incorrect"
 
@@ -1738,6 +1745,12 @@ def handler_entering_new_password(user):
     # get the next waiting line of input
     input_data = user.input_queue.pop(0)
 
+    if "mudpy.limit" in universe.contents:
+        max_password_tries = universe.contents["mudpy.limit"].get(
+            "password_tries", 3)
+    else:
+        max_password_tries = 3
+
     # make sure the password is strong--at least one upper, one lower and
     # one digit, seven or more characters in length
     if len(input_data) > 6 and len(
@@ -1753,8 +1766,7 @@ 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.contents["mudpy.limit"].get(
-            "password_tries") - 1:
+    elif user.password_tries < max_password_tries - 1:
         user.password_tries += 1
         user.error = "weak"
 
@@ -1773,6 +1785,12 @@ def handler_verifying_new_password(user):
     # get the next waiting line of input
     input_data = user.input_queue.pop(0)
 
+    if "mudpy.limit" in universe.contents:
+        max_password_tries = universe.contents["mudpy.limit"].get(
+            "password_tries", 3)
+    else:
+        max_password_tries = 3
+
     # hash the input and match it to storage
     if mudpy.password.verify(input_data, user.account.get("passhash")):
         user.authenticate()
@@ -1783,8 +1801,7 @@ 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.contents["mudpy.limit"].get(
-            "password_tries") - 1:
+    elif user.password_tries < max_password_tries - 1:
         user.password_tries += 1
         user.error = "differs"
         user.state = "entering_new_password"
@@ -1869,7 +1886,7 @@ def command_reload(actor):
         log(
             "User " +
             actor.owner.account.get("name") + " reloaded the world.",
-            8
+            6
         )
 
         # set a flag to reload
@@ -1954,8 +1971,8 @@ def command_help(actor, parameters):
                 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)
@@ -1985,25 +2002,26 @@ def command_say(actor, parameters):
 
     # if the message is wrapped in quotes, remove them and leave contents
     # intact
-    if parameters.startswith("\"") and parameters.endswith("\""):
+    if parameters.startswith('"') and parameters.endswith('"'):
         message = parameters[1:-1]
         literal = True
 
     # otherwise, get rid of stray quote marks on the ends of the message
     else:
-        message = parameters.strip("\"'`")
+        message = parameters.strip('''"'`''')
         literal = False
 
     # the user entered a message
     if message:
 
         # match the punctuation used, if any, to an action
-        actions = universe.contents["mudpy.linguistic"].get(
-            "actions"
-        )
-        default_punctuation = (
-            universe.contents["mudpy.linguistic"].get(
-                "default_punctuation"))
+        if "mudpy.linguistic" in universe.contents:
+            actions = universe.contents["mudpy.linguistic"].get("actions", {})
+            default_punctuation = (universe.contents["mudpy.linguistic"].get(
+                "default_punctuation", "."))
+        else:
+            actions = {}
+            default_punctuation = "."
         action = ""
 
         # reverse sort punctuation options so the longest match wins
@@ -2027,9 +2045,10 @@ def command_say(actor, parameters):
             message = message[0].lower() + message[1:]
 
             # iterate over all words in message, replacing typos
-            typos = universe.contents["mudpy.linguistic"].get(
-                "typos"
-            )
+            if "mudpy.linguistic" in universe.contents:
+                typos = universe.contents["mudpy.linguistic"].get("typos", {})
+            else:
+                typos = {}
             words = message.split()
             for index in range(len(words)):
                 word = words[index]
@@ -2047,9 +2066,9 @@ def command_say(actor, parameters):
     # tell the area
     if message:
         actor.echo_to_location(
-            actor.get("name") + " " + action + "s, \"" + message + "\""
+            actor.get("name") + " " + action + 's, "' + message + '"'
         )
-        actor.send("You " + action + ", \"" + message + "\"")
+        actor.send("You " + action + ', "' + message + '"')
 
     # there was no message
     else:
@@ -2100,8 +2119,8 @@ def command_show(actor, parameters):
         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
@@ -2111,27 +2130,27 @@ def command_show(actor, parameters):
             for element in elements:
                 message += "$(eol)   $(grn)" + element + "$(nrm)"
         else:
-            message = "Category \"" + arguments[1] + "\" does not exist."
+            message = 'Category "' + arguments[1] + '" does not exist.'
     elif arguments[0] == "file":
         if len(arguments) != 2:
             message = "You must specify one file."
         elif arguments[1] in universe.files:
-            message = ("These are the elements in the \"" + arguments[1]
-                       + "\" file:$(eol)")
+            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)"
         else:
-            message = "Category \"" + arguments[1] + "\" does not exist."
+            message = 'Category "' + arguments[1] + '" does not exist.'
     elif arguments[0] == "element":
         if len(arguments) != 2:
             message = "You must specify one element."
         elif arguments[1].strip(".") in universe.contents:
             element = universe.contents[arguments[1].strip(".")]
-            message = ("These are the properties of the \"" + arguments[1]
-                       + "\" element (in \"" + element.origin.filename
-                       + "\"):$(eol)")
+            message = ('These are the properties of the "' + arguments[1]
+                       + '" element (in "' + element.origin.filename
+                       + '"):$(eol)')
             facets = element.facets()
             for facet in sorted(facets):
                 if element.old_style:
@@ -2141,7 +2160,7 @@ def command_show(actor, parameters):
                     message += ("$(eol)   $(grn)%s: $(red)%s$(nrm)" %
                                 (facet, str(facets[facet])))
         else:
-            message = "Element \"" + arguments[1] + "\" does not exist."
+            message = 'Element "' + arguments[1] + '" does not exist.'
     elif arguments[0] == "result":
         if len(arguments) < 2:
             message = "You need to specify an expression."
@@ -2153,21 +2172,21 @@ def command_show(actor, parameters):
                            "$(eol)$(bld)%s$(nrm)" % e)
     elif arguments[0] == "log":
         if len(arguments) == 4:
-            if re.match("^\d+$", arguments[3]) and int(arguments[3]) >= 0:
+            if re.match(r"^\d+$", arguments[3]) and int(arguments[3]) >= 0:
                 stop = int(arguments[3])
             else:
                 stop = -1
         else:
             stop = 0
         if len(arguments) >= 3:
-            if re.match("^\d+$", arguments[2]) and int(arguments[2]) > 0:
+            if re.match(r"^\d+$", arguments[2]) and int(arguments[2]) > 0:
                 start = int(arguments[2])
             else:
                 start = -1
         else:
             start = 10
         if len(arguments) >= 2:
-            if (re.match("^\d+$", arguments[1])
+            if (re.match(r"^\d+$", arguments[1])
                     and 0 <= int(arguments[1]) <= 9):
                 level = int(arguments[1])
             else:
@@ -2182,7 +2201,7 @@ def command_show(actor, parameters):
             message = ("When specified, level must be 0-9 (default 1), "
                        "start and stop must be >=1 (default 10 and 1).")
     else:
-        message = "I don't know what \"" + parameters + "\" is."
+        message = '''I don't know what "''' + parameters + '" is.'
     actor.send(message)
 
 
@@ -2199,10 +2218,10 @@ def command_create(actor, parameters):
         if len(arguments) == 2:
             element, filename = arguments
             if element in universe.contents:
-                message = "The \"" + element + "\" element already exists."
+                message = 'The "' + element + '" element already exists.'
             else:
-                message = ("You create \"" +
-                           element + "\" within the universe.")
+                message = ('You create "' +
+                           element + '" within the universe.')
                 logline = actor.owner.account.get(
                     "name"
                 ) + " created an element: " + element
@@ -2210,7 +2229,7 @@ def command_create(actor, parameters):
                     logline += " in file " + filename
                     if filename not in universe.files:
                         message += (
-                            " Warning: \"" + filename + "\" is not yet "
+                            ' Warning: "' + filename + '" is not yet '
                             "included in any other file and will not be read "
                             "on startup unless this is remedied.")
                 Element(element, universe, filename, old_style=True)
@@ -2227,11 +2246,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"
@@ -2248,22 +2267,28 @@ 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."
+                message = 'The "' + element + '" element does not exist.'
             else:
-                universe.contents[element].set(facet, value)
-                message = ("You have successfully (re)set the \"" + facet
-                           + "\" facet of element \"" + element
-                           + "\". Try \"show element " +
-                           element + "\" for verification.")
+                try:
+                    universe.contents[element].set(facet, value)
+                except ValueError:
+                    message = ('Value "%s" of type "%s" cannot be coerced '
+                               'to the correct datatype for facet "%s".' %
+                               (value, type(value), facet))
+                else:
+                    message = ('You have successfully (re)set the "' + facet
+                               + '" facet of element "' + element
+                               + '". Try "show element ' +
+                               element + '" for verification.')
     actor.send(message)
 
 
@@ -2274,23 +2299,23 @@ 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:
             element, facet = arguments
             if element not in universe.contents:
-                message = "The \"" + element + "\" element does not exist."
+                message = 'The "' + element + '" element does not exist.'
             elif facet not in universe.contents[element].facets():
-                message = ("The \"" + element + "\" element has no \"" + facet
-                           + "\" facet.")
+                message = ('The "' + element + '" element has no "' + facet
+                           + '" facet.')
             else:
                 universe.contents[element].remove_facet(facet)
-                message = ("You have successfully deleted the \"" + facet
-                           + "\" facet of element \"" + element
-                           + "\". Try \"show element " +
-                           element + "\" for verification.")
+                message = ('You have successfully deleted the "' + facet
+                           + '" facet of element "' + element
+                           + '". Try "show element ' +
+                           element + '" for verification.')
     actor.send(message)
 
 
@@ -2299,7 +2324,7 @@ def command_error(actor, input_data):
 
     # 90% of the time use a generic error
     if random.randrange(10):
-        message = "I'm not sure what \"" + input_data + "\" means..."
+        message = '''I'm not sure what "''' + input_data + '''" means...'''
 
     # 10% of the time use the classic diku error
     else:
@@ -2313,7 +2338,8 @@ def daemonize(universe):
     """Fork and disassociate from everything."""
 
     # only if this is what we're configured to do
-    if universe.contents["mudpy.process"].get("daemon"):
+    if "mudpy.process" in universe.contents and universe.contents[
+            "mudpy.process"].get("daemon"):
 
         # log before we start forking around, so the terminal gets the message
         log("Disassociating from the controlling terminal.")
@@ -2350,7 +2376,10 @@ def create_pidfile(universe):
     """Write a file containing the current process ID."""
     pid = str(os.getpid())
     log("Process ID: " + pid)
-    file_name = universe.contents["mudpy.process"].get("pidfile")
+    if "mudpy.process" in universe.contents:
+        file_name = universe.contents["mudpy.process"].get("pidfile", "")
+    else:
+        file_name = ""
     if file_name:
         if not os.path.isabs(file_name):
             file_name = os.path.join(universe.startdir, file_name)
@@ -2362,7 +2391,10 @@ def create_pidfile(universe):
 
 def remove_pidfile(universe):
     """Remove the file containing the current process ID."""
-    file_name = universe.contents["mudpy.process"].get("pidfile")
+    if "mudpy.process" in universe.contents:
+        file_name = universe.contents["mudpy.process"].get("pidfile", "")
+    else:
+        file_name = ""
     if file_name:
         if not os.path.isabs(file_name):
             file_name = os.path.join(universe.startdir, file_name)