Imported from archive.
authorJeremy Stanley <fungi@yuggoth.org>
Mon, 10 Jul 2006 03:30:46 +0000 (03:30 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Mon, 10 Jul 2006 03:30:46 +0000 (03:30 +0000)
* LICENSE, mudpy, mudpy.py: Altered the copyright statements to
correctly mention all years instead of only the most recent.

* command (command:chat, command:say), mydpy.py (User.send)
(command_chat, first_word, handler_active): Added a chat command
which toggles the avatar in and out of a mode where all further
lines of input are passed as parameters to the say command.

* command (command:quit): Minor clarifications to several command
help texts.

* command (command:say), mudpy.conf (internal:language), mudpy.py
(command_say): Moved the capitalize_words list facet to a more
flexible typos dict, and condensed punctuation_* facets into an
actions dict facet.

* mudpy.py (User.deactivate_avatar): Fixed a bug where avatars
without a location could trigger an exception.
(command_help): Added a see_also list facet for menu elements, which
allow help output to suggest other related commands.
(create_pidfile, remove_pidfile): Minor adjustments to assure file
path canonicalization.
(get_loglines): Aesthetic tweaks to the show log output.
(on_pulse): Create internal:counters if it doesn't exist, to avoid
throwing an exception.

LICENSE
command
mudpy
mudpy.conf
mudpy.py

diff --git a/LICENSE b/LICENSE
index 901a49d..c1ce24f 100644 (file)
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,5 @@
-Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
diff --git a/command b/command
index 409cbcf..c5499c9 100644 (file)
--- a/command
+++ b/command
@@ -1,6 +1,12 @@
 [__control__]
 read_only = yes
 
+[command:chat]
+action = command_chat(actor)
+description = Enter and leave chat mode.
+help = The chat command toggles chat mode. When in chat mode, all input is passed as a parameter to the say command, unless prepended by an exclamation mark (!). For example, to leave chat mode, use:$(eol)$(eol)   !chat
+see_also = say
+
 [command:create]
 action = command_create(actor, parameters)
 administrative = yes
@@ -43,7 +49,7 @@ help = You move in a direction by entering:$(eol)$(eol)   move north
 [command:quit]
 action = command_quit(actor)
 description = Leave Example.
-help = This will save your account and disconnect your client connection.
+help = This will deactivate your avatar and return you to the main menu.
 
 [command:reload]
 action = command_reload(actor)
@@ -54,7 +60,8 @@ help = This will reload all python modules and read-only data files.
 [command:say]
 action = command_say(actor, parameters)
 description = State something out loud.
-help = This allows you to speak to other characters within the same room. If you end your sentence with specific punctuation, the aparent speech action (ask, exclaim, et cetera) will be adapted accordingly. It will also add punctuation and capitalize your message where needed.
+help = This allows you to speak to other characters within the same room. If you end your sentence with punctuation, the message displayed will incorporate an appropriate action (ask, exclaim, et cetera). It will also correct common typographical errors, add punctuation and capitalize your sentence as needed (assuming you speak one sentence per line). For example:$(eol)$(eol)   > say youre sure i went teh wrong way?$(eol)   You ask, "You're sure I went the wrong way?"
+see_also = chat
 
 [command:set]
 action = command_set(actor, parameters)
diff --git a/mudpy b/mudpy
index 7a5d5b2..519f07b 100755 (executable)
--- a/mudpy
+++ b/mudpy
@@ -1,8 +1,9 @@
 #!/usr/bin/python
 """Skeletal executable for the mudpy engine."""
 
-# Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
-# Licensed per terms in the LICENSE file distributed with this software.
+# Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+# reserved. Licensed per terms in the LICENSE file distributed with this
+# software.
 
 # core objects for the mudpy engine
 import mudpy
index 0ef7482..8fa0876 100644 (file)
@@ -5,13 +5,9 @@ private_files = account
 read_only = yes
 
 [internal:language]
-capitalize_words = [ "i", "i'd", "i'll", "i'm" ]
+actions = { "?": "ask", ",": "begin", "-": "begin", ":": "begin", ";": "begin", "!": "exclaim", "...": "muse", ".": "say" }
 default_punctuation = .
-punctuation_ask = ?
-punctuation_begin = [ ",", "-", ":", ";" ]
-punctuation_exclaim = !
-punctuation_muse = ...
-punctuation_say = .
+typos = { "i": "I", "i'd": "I'd", "i'll": "I'll", "i'm": "I'm", "teh": "the", "theyre": "they're", "youre": "you're" }
 
 [internal:limits]
 #default_admins = admin
@@ -22,7 +18,7 @@ password_tries = 3
 [internal:logging]
 #file = mudpy.log
 max_log_lines = 1000
-#stdout = yes
+stdout = yes
 #syslog = mudpy
 
 [internal:network]
index 9f5cd1d..06563f7 100644 (file)
--- a/mudpy.py
+++ b/mudpy.py
@@ -1,7 +1,8 @@
 """Core objects for the mudpy engine."""
 
-# Copyright (c) 2006 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
-# Licensed per terms in the LICENSE file distributed with this software.
+# Copyright (c) 2005, 2006 Jeremy Stanley <fungi@yuggoth.org>. All rights
+# reserved. Licensed per terms in the LICENSE file distributed with this
+# software.
 
 # import some things we need
 from ConfigParser import RawConfigParser
@@ -13,6 +14,7 @@ from re import match
 from signal import SIGHUP, SIGTERM, signal
 from socket import AF_INET, SO_REUSEADDR, SOCK_STREAM, SOL_SOCKET, socket
 from stat import S_IMODE, ST_MODE
+from string import digits, letters, punctuation, uppercase
 from sys import argv, stderr
 from syslog import LOG_PID, LOG_INFO, LOG_DAEMON, closelog, openlog, syslog
 from telnetlib import DO, DONT, ECHO, EOR, GA, IAC, LINEMODE, SB, SE, SGA, WILL, WONT
@@ -449,6 +451,7 @@ class Universe:
                        element.clean_contents()
 
        def new(self):
+               """Create a new, empty Universe (the Big Bang)."""
                new_universe = Universe()
                for attribute in vars(self).keys():
                        exec("new_universe." + attribute + " = self." + attribute)
@@ -646,7 +649,10 @@ class User:
                        # tack on a prompt if active
                        if self.state == "active":
                                if not just_prompt: output += "$(eol)"
-                               if add_prompt: output += "> "
+                               if add_prompt:
+                                       output += "> "
+                                       mode = self.avatar.get("mode")
+                                       if mode: output += "(" + mode + ") "
 
                        # find and replace macros in the output
                        output = replace_macros(self, output)
@@ -856,10 +862,11 @@ class User:
                """Have the active avatar leave the world."""
                if self.avatar:
                        current = self.avatar.get("location")
-                       self.avatar.set("default_location", current)
-                       self.avatar.echo_to_location("You suddenly wonder where " + self.avatar.get("name") + " went.")
-                       del universe.contents[current].contents[self.avatar.key]
-                       self.avatar.remove_facet("location")
+                       if current:
+                               self.avatar.set("default_location", current)
+                               self.avatar.echo_to_location("You suddenly wonder where " + self.avatar.get("name") + " went.")
+                               del universe.contents[current].contents[self.avatar.key]
+                               self.avatar.remove_facet("location")
                        self.avatar.owner = None
                        self.avatar = None
 
@@ -892,8 +899,6 @@ def log(message, level=0):
 
        # a couple references we need
        file_name = universe.categories["internal"]["logging"].get("file")
-       if not isabs(file_name):
-               file_name = path_join(universe.startdir, file_name)
        max_log_lines = universe.categories["internal"]["logging"].getint("max_log_lines")
        syslog_name = universe.categories["internal"]["logging"].get("syslog")
        timestamp = asctime()[4:19]
@@ -903,6 +908,8 @@ def log(message, level=0):
 
        # send the timestamp and line to a file
        if file_name:
+               if not isabs(file_name):
+                       file_name = path_join(universe.startdir, file_name)
                file_descriptor = file(file_name, "a")
                for line in lines: file_descriptor.write(timestamp + " " + line + "\n")
                file_descriptor.flush()
@@ -957,8 +964,8 @@ def get_loglines(level, start, stop):
                message = "There are " + str(total_count)
                message += " log lines in memory and " + str(filtered_count)
                message += " at or above level " + str(level) + "."
-               message += " The lines from " + str(stop) + " to " + str(start)
-               message += " are:$(eol)$(eol)"
+               message += " The matching lines from " + str(stop) + " to "
+               message += str(start) + " are:$(eol)$(eol)"
 
                # add the text from the selected lines
                if stop > 1: range_lines = loglines[-start:-(stop-1)]
@@ -1144,6 +1151,13 @@ def escape_macros(text):
        """Escapes replacement macros in text."""
        return text.replace("$(", "$_(")
 
+def first_word(text, separator=" "):
+       """Returns a tuple of the first word and the rest."""
+       if text:
+               if text.find(separator) > 0: return text.split(separator, 1)
+               else: return text, ""
+       else: return "", ""
+
 def on_pulse():
        """The things which should happen on each pulse, aside from reloads."""
 
@@ -1158,6 +1172,10 @@ def on_pulse():
        # iterate over the connected users
        for user in universe.userlist: user.pulse()
 
+       # add an element for counters if it doesn't exist
+       if not "counters" in universe.categories["internal"]:
+               universe.categories["internal"]["counters"] = Element("internal:counters", universe)
+
        # update the log every now and then
        if not universe.categories["internal"]["counters"].getint("mark"):
                log(str(len(universe.userlist)) + " connection(s)")
@@ -1523,12 +1541,16 @@ def handler_active(user):
        # is there input?
        if input_data:
 
-               # split out the command (first word) and parameters (everything else)
-               if input_data.find(" ") > 0:
-                       command_name, parameters = input_data.split(" ", 1)
+               # split out the command and parameters
+               actor = user.avatar
+               mode = actor.get("mode")
+               if mode and input_data.startswith("!"):
+                       command_name, parameters = first_word(input_data[1:])
+               elif mode == "chat":
+                       command_name = "say"
+                       parameters = input_data
                else:
-                       command_name = input_data
-                       parameters = ""
+                       command_name, parameters = first_word(input_data)
 
                # lowercase the command
                command_name = command_name.lower()
@@ -1539,7 +1561,6 @@ def handler_active(user):
                else: command = None
 
                # if it's allowed, do it
-               actor = user.avatar
                if actor.can_run(command): exec(command.get("action"))
 
                # otherwise, give an error
@@ -1608,6 +1629,24 @@ def command_help(actor, parameters):
                                help_text = "No help is provided for this command."
                        output += help_text
 
+                       # list related commands
+                       see_also = command.getlist("see_also")
+                       if see_also:
+                               really_see_also = ""
+                               for item in see_also:
+                                       if item in universe.categories["command"]:
+                                               command = universe.categories["command"][item]
+                                               if actor.can_run(command):
+                                                       if really_see_also:
+                                                               really_see_also += ", "
+                                                       if command.getboolean("administrative"):
+                                                               really_see_also += "$(red)"
+                                                       else:
+                                                               really_see_also += "$(grn)"
+                                                       really_see_also += item + "$(nrm)"
+                               if really_see_also:
+                                       output += "$(eol)$(eol)See also: " + really_see_also
+
                # no data for the requested command word
                else:
                        output = "That is not an available command."
@@ -1654,35 +1693,40 @@ def command_say(actor, parameters):
        # the user entered a message
        elif parameters:
 
-               # get rid of quote marks on the ends of the message and
-               # capitalize the first letter
+               # get rid of quote marks on the ends of the message
                message = parameters.strip("\"'`")
-               message = message[0].capitalize() + message[1:]
-
-               # a dictionary of punctuation:action pairs
-               actions = {}
-               for facet in universe.categories["internal"]["language"].facets():
-                       if facet.startswith("punctuation_"):
-                               action = facet.split("_")[1]
-                               for mark in universe.categories["internal"]["language"].getlist(facet):
-                                               actions[mark] = action
 
                # match the punctuation used, if any, to an action
+               actions = universe.categories["internal"]["language"].getdict("actions")
                default_punctuation = universe.categories["internal"]["language"].get("default_punctuation")
-               action = actions[default_punctuation]
+               action = ""
                for mark in actions.keys():
-                       if message.endswith(mark) and mark != default_punctuation:
+                       if message.endswith(mark):
                                action = actions[mark]
                                break
 
-               # if the action is default and there is no mark, add one
-               if action == actions[default_punctuation] and not message.endswith(default_punctuation):
-                       message += default_punctuation
+               # add punctuation if needed
+               if not action:
+                       action = actions[default_punctuation]
+                       if message and not message[-1] in punctuation:
+                               message += default_punctuation
+
+               # decapitalize the first letter to improve matching
+               if message and message[0] in uppercase:
+                       message = message[0].lower() + message[1:]
+
+               # iterate over all words in message, replacing typos
+               typos = universe.categories["internal"]["language"].getdict("typos")
+               words = message.split()
+               for index in range(len(words)):
+                       word = words[index]
+                       bare_word = word.strip(punctuation)
+                       if bare_word in typos.keys():
+                               words[index] = word.replace(bare_word, typos[bare_word])
+               message = " ".join(words)
 
-               # capitalize a list of words within the message
-               capitalize_words = universe.categories["internal"]["language"].getlist("capitalize_words")
-               for word in capitalize_words:
-                       message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
+               # capitalize the first letter
+               message = message[0].upper() + message[1:]
 
                # tell the room
                actor.echo_to_location(actor.get("name") + " " + action + "s, \"" + message + "\"")
@@ -1692,6 +1736,17 @@ def command_say(actor, parameters):
        else:
                actor.send("What do you want to say?")
 
+def command_chat(actor):
+       """Toggle chat mode."""
+       mode = actor.get("mode")
+       if not mode:
+               actor.set("mode", "chat")
+               actor.send("Entering chat mode (use $(grn)!chat$(nrm) to exit).")
+       elif mode == "chat":
+               actor.remove_facet("mode")
+               actor.send("Exiting chat mode.")
+       else: actor.send("Sorry, but you're already busy with something else!")
+
 def command_show(actor, parameters):
        """Show program data."""
        message = ""
@@ -1870,9 +1925,9 @@ def create_pidfile(universe):
        pid = str(getpid())
        log("Process ID: " + pid)
        file_name = universe.contents["internal:process"].get("pidfile")
-       if not isabs(file_name):
-               file_name = path_join(universe.startdir, file_name)
        if file_name:
+               if not isabs(file_name):
+                       file_name = path_join(universe.startdir, file_name)
                file_descriptor = file(file_name, 'w')
                file_descriptor.write(pid + "\n")
                file_descriptor.flush()
@@ -1881,9 +1936,10 @@ def create_pidfile(universe):
 def remove_pidfile(universe):
        """Remove the file containing the current process ID."""
        file_name = universe.contents["internal:process"].get("pidfile")
-       if not isabs(file_name):
-               file_name = path_join(universe.startdir, file_name)
-       if file_name and access(file_name, W_OK): remove(file_name)
+       if file_name:
+               if not isabs(file_name):
+                       file_name = path_join(universe.startdir, file_name)
+               if access(file_name, W_OK): remove(file_name)
 
 def excepthook(excepttype, value, traceback):
        """Handle uncaught exceptions."""