Imported from archive.
authorJeremy Stanley <fungi@yuggoth.org>
Thu, 21 Jul 2005 20:19:02 +0000 (20:19 +0000)
committerJeremy Stanley <fungi@yuggoth.org>
Thu, 21 Jul 2005 20:19:02 +0000 (20:19 +0000)
* bin/muff, lib/muff: Replaced the embedded license copies with
pointers to the LICENSE file. Added lots of in-line code commentary
and docstrings. Refactored some blocks of code for improved
readability.

* bin/muff, etc/mudpy.conf: Moved to the top-level directory.

* doc/license: Renamed to LICENSE and moved it to the top-level
directory.

* lib/muff/muffcmds.py (command_null): Removed.
(command_say): Additional output cleanup.

* lib/muff/muffmain.py (main): More robust handling for variable
files.

* lib/muff/muffmisc.py (wrap_ansi_text): Minor word-wrapping fix.
(User.__init__): Added tracking of last login address.

13 files changed:
LICENSE [moved from doc/license with 93% similarity]
bin/muff [deleted file]
lib/muff/__init__.py
lib/muff/muffcmds.py
lib/muff/muffconf.py
lib/muff/muffmain.py
lib/muff/muffmenu.py
lib/muff/muffmisc.py
lib/muff/muffsock.py
lib/muff/muffuser.py
lib/muff/muffvars.py
muff [new file with mode: 0755]
muff.conf [moved from etc/muff.conf with 52% similarity]

similarity index 93%
rename from doc/license
rename to LICENSE
index 09499ea..da974d5 100644 (file)
+++ b/LICENSE
@@ -1,5 +1,4 @@
-Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>
-All rights reserved.
+Copyright (c) 2005 mudpy, 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
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
diff --git a/bin/muff b/bin/muff
deleted file mode 100755 (executable)
index 64aab9d..0000000
--- a/bin/muff
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/python
-"""Skeletal executable for the MUFF Engine"""
-
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-import ConfigParser
-import sys
-
-def get_main_loop():
-       """Find and return the main loop function"""
-
-       # figure out where to find our python modules
-       config_data = ConfigParser.SafeConfigParser()
-       config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
-       config_name = "muff.conf"
-       config_files = []
-       for each_dir in config_dirs:
-               config_files.append(each_dir + "/" + config_name)
-       config_data.read(config_files)
-       modules = config_data.get("general", "modules")
-
-       # add it to the load path
-       sys.path.append(modules)
-
-       # import the main loop function
-       from muff.muffmain import main
-       return main
-
-# load the main loop
-main = get_main_loop()
-
-# run the main loop
-main()
-
index 27df656..318e7a8 100644 (file)
@@ -1,31 +1,9 @@
 """Initialization for MUFF Modules"""
 
 """Initialization for MUFF Modules"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# these are all the modules included in the muff package; if you create
+# another, be sure to add it to this list
 __all__ = [ "muffcmds", "muffconf", "muffmain", "muffmenu", "muffmisc", "muffsock", "muffuser", "muffvars" ]
 
 __all__ = [ "muffcmds", "muffconf", "muffmain", "muffmenu", "muffmisc", "muffsock", "muffuser", "muffvars" ]
 
index a9df032..555526a 100644 (file)
 """Command objects for the MUFF Engine"""
 
 """Command objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# command data like descriptions, help text, limits, et cetera, are stored in
+# ini-style configuration files supported by the ConfigParser module
 import ConfigParser
 import ConfigParser
+
+# md5 hashing is used for verification of account passwords
 import md5
 import md5
+
+# the os module is used to we can get a directory listing and build lists of
+# multiple config files in one directory
 import os
 import os
+
+# the random module is useful for creating random conditional output
 import random
 import random
+
+# string.split is used extensively to tokenize user input (break up command
+# names and parameters)
 import string
 
 import string
 
+# bit of a hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
+# does the files:commands setting exist yet?
 try:
 try:
-       if muffconf.config_data.get("general", "command_path"):
-               pass
+       if muffconf.config_data.get("files", "commands"): pass
+
+# if not, reload the muffconf module
 except AttributeError:
        reload(muffconf)
 except AttributeError:
        reload(muffconf)
-command_path = muffconf.config_data.get("general", "command_path")
+
+# now we can safely nab the command path setting and build a list of data files
+command_path = muffconf.config_data.get("files", "commands")
 command_files = []
 for each_file in os.listdir(command_path):
        command_files.append(command_path + "/" + each_file)
 command_files = []
 for each_file in os.listdir(command_path):
        command_files.append(command_path + "/" + each_file)
+
+# read the command data files
 command_data = ConfigParser.SafeConfigParser()
 command_data.read(command_files)
 command_data = ConfigParser.SafeConfigParser()
 command_data.read(command_files)
+
+# this creates a list of commands mentioned in the data files
 command_list = command_data.sections()
 
 def handle_user_input(user, input):
 command_list = command_data.sections()
 
 def handle_user_input(user, input):
+       """The main handler, branches to a state-specific handler."""
+
+       # TODO: change this to use a dict
        if user.state == "active": handler_active(user, input)
        elif user.state == "entering account name": handler_entering_account_name(user, input)
        elif user.state == "checking password": handler_checking_password(user, input)
        elif user.state == "checking new account name": handler_checking_new_account_name(user, input)
        elif user.state == "entering new password": handler_entering_new_password(user, input)
        elif user.state == "verifying new password": handler_verifying_new_password(user, input)
        if user.state == "active": handler_active(user, input)
        elif user.state == "entering account name": handler_entering_account_name(user, input)
        elif user.state == "checking password": handler_checking_password(user, input)
        elif user.state == "checking new account name": handler_checking_new_account_name(user, input)
        elif user.state == "entering new password": handler_entering_new_password(user, input)
        elif user.state == "verifying new password": handler_verifying_new_password(user, input)
+
+       # if there's input with an unknown user state, something is wrong
        else: handler_fallthrough(user, input)
        else: handler_fallthrough(user, input)
+
+       # since we got input, flag that the menu/prompt needs to be redisplayed
        user.menu_seen = False
 
 def handler_entering_account_name(user, input):
        user.menu_seen = False
 
 def handler_entering_account_name(user, input):
+       """Handle the login account name."""
+
+       # did the user enter anything?
        if input:
        if input:
+               
+               # keep only the first word and convert to lower-case
                user.proposed_name = string.split(input)[0].lower()
                user.proposed_name = string.split(input)[0].lower()
+
+               # try to get a password hash for the proposed name
                user.get_passhash()
                user.get_passhash()
+
+               # if we have a password hash, time to request a password
+               # TODO: make get_passhash() return pass/fail and test that
                if user.passhash:
                        user.state = "checking password"
                if user.passhash:
                        user.state = "checking password"
+
+               # otherwise, this could be a brand new user
                else:
                        user.name = user.proposed_name
                        user.proposed_name = None
                        user.load()
                        user.state = "checking new account name"
                else:
                        user.name = user.proposed_name
                        user.proposed_name = None
                        user.load()
                        user.state = "checking new account name"
+
+       # if the user entered nothing for a name, then buhbye
+       # TODO: make a disconnect state instead of calling command_quit()
        else:
                command_quit(user)
 
 def handler_checking_password(user, input):
        else:
                command_quit(user)
 
 def handler_checking_password(user, input):
+       """Handle the login account password."""
+
+       # does the hashed input equal the stored hash?
        if md5.new(user.proposed_name + input).hexdigest() == user.passhash:
        if md5.new(user.proposed_name + input).hexdigest() == user.passhash:
+
+               # if so, set the username and load from cold storage
                user.name = user.proposed_name
                user.name = user.proposed_name
-               user.proposed_name = None
+               del(user.proposed_name)
                user.load()
                user.load()
+
+               # now go active
+               # TODO: branch to character creation and selection menus
                user.state = "active"
                user.state = "active"
+
+       # if at first your hashes don't match, try, try again
        elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "incorrect"
        elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "incorrect"
+
+       # we've exceeded the maximum number of password failures, so disconnect
+       # TODO: make a disconnect state instead of calling command_quit()
        else:
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                command_quit(user)
 
 def handler_checking_new_account_name(user, input):
        else:
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                command_quit(user)
 
 def handler_checking_new_account_name(user, input):
+       """Handle input for the new user menu."""
+
+       # if there's input, take the first character and lowercase it
        if input:
                choice = input.lower()[0]
        if input:
                choice = input.lower()[0]
+
+       # if there's no input, use the default
        else:
                choice = muffmenu.get_default(user)
        else:
                choice = muffmenu.get_default(user)
+
+       # user selected to disconnect
+       # TODO: make a disconnect state instead of calling command_quit()
        if choice == "d":
                command_quit(user)
        if choice == "d":
                command_quit(user)
+
+       # go back to the login screen
        elif choice == "g":
                user.state = "entering account name"
        elif choice == "g":
                user.state = "entering account name"
+
+       # new user, so ask for a password
        elif choice == "n":
                user.state = "entering new password"
        elif choice == "n":
                user.state = "entering new password"
+
+       # user entered a non-existent option
        else:
                user.error = "default"
 
 def handler_entering_new_password(user, input):
        else:
                user.error = "default"
 
 def handler_entering_new_password(user, input):
+       """Handle a new password entry."""
+
+       # make sure the password is strong--at least one upper, one lower and
+       # one digit, seven or more characters in length
        if len(input) > 6 and len(filter(lambda x: x>="0" and x<="9", input)) and len(filter(lambda x: x>="A" and x<="Z", input)) and len(filter(lambda x: x>="a" and x<="z", input)):
        if len(input) > 6 and len(filter(lambda x: x>="0" and x<="9", input)) and len(filter(lambda x: x>="A" and x<="Z", input)) and len(filter(lambda x: x>="a" and x<="z", input)):
+
+               # hash and store it, then move on to verification
                user.passhash = md5.new(user.name + input).hexdigest()
                user.state = "verifying new password"
                user.passhash = md5.new(user.name + input).hexdigest()
                user.state = "verifying new password"
+
+       # the password was weak, try again if you haven't tried too many times
        elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "weak"
        elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "weak"
+
+       # too many tries, so adios
+       # TODO: make a disconnect state instead of calling command_quit()
        else:
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                command_quit(user)
 
 def handler_verifying_new_password(user, input):
        else:
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                command_quit(user)
 
 def handler_verifying_new_password(user, input):
+       """Handle the re-entered new password for verification."""
+
+       # hash the input and match it to storage
        if md5.new(user.name + input).hexdigest() == user.passhash:
        if md5.new(user.name + input).hexdigest() == user.passhash:
+
+               # the hashes matched, so go active
+               # TODO: branch to character creation and selection menus
                user.state = "active"
                user.state = "active"
+
+       # go back to entering the new password as long as you haven't tried
+       # too many times
        elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "differs"
                user.state = "entering new password"
        elif user.password_tries < muffconf.config_data.getint("general", "password_tries"):
                user.password_tries += 1
                user.error = "differs"
                user.state = "entering new password"
+
+       # otherwise, sayonara
+       # TODO: make a disconnect state instead of calling command_quit()
        else:
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                command_quit(user)
 
        else:
                user.send("$(eol)$(red)Too many failed password attempts...$(nrm)$(eol)")
                command_quit(user)
 
+# TODO: um, input is a reserved word. better replace it in all sources with
+# something else, like input_data
 def handler_active(user, input):
 def handler_active(user, input):
+       """Handle input for active users."""
+
+       # users reaching this stage should be considered authenticated
+       # TODO: this should actually happen before or in load() instead
        if not user.authenticated: user.authenticated = True
        if not user.authenticated: user.authenticated = True
+
+       # split out the command (first word) and parameters (everything else)
        try:
                inputlist = string.split(input, None, 1)
                command = inputlist[0]
        try:
                inputlist = string.split(input, None, 1)
                command = inputlist[0]
@@ -134,50 +211,90 @@ def handler_active(user, input):
                parameters = inputlist[1]
        except:
                parameters = ""
                parameters = inputlist[1]
        except:
                parameters = ""
+       del(inputlist)
+
+       # lowercase the command
        command = command.lower()
        command = command.lower()
-       if not command: command_null(user, command, parameters)
-       elif command in command_list: exec("command_" + command + "(user, command, parameters)")
-       else: command_error(user, command, parameters)
 
 
+       # the command matches a command word for which we have data
+       if command in command_list: exec("command_" + command + "(user, command, parameters)")
+
+       # no data matching the entered command word
+       elif command: command_error(user, command, parameters)
+
+# TODO: need a log function to handle conditions like this instead of print()
 def handler_fallthrough(user, input):
 def handler_fallthrough(user, input):
+       """Input received in an unknown user state."""
        if input:
                print("User \"" + user + "\" entered \"" + input + "\" while in unknown state \"" + user.state + "\".")
 
 def command_halt(user, command="", parameters=""):
        if input:
                print("User \"" + user + "\" entered \"" + input + "\" while in unknown state \"" + user.state + "\".")
 
 def command_halt(user, command="", parameters=""):
+       """Halt the world."""
+
+       # let everyone know
+       # TODO: optionally take input for the message
        muffmisc.broadcast(user.name + " halts the world.")
        muffmisc.broadcast(user.name + " halts the world.")
+
+       # save everyone
+       # TODO: probably want a misc function for this
        for each_user in muffvars.userlist:
                each_user.save()
        for each_user in muffvars.userlist:
                each_user.save()
+
+       # set a flag to terminate the world
        muffvars.terminate_world = True
 
 def command_reload(user, command="", parameters=""):
        muffvars.terminate_world = True
 
 def command_reload(user, command="", parameters=""):
-       user.send("Reloading all code modules.")
+       """Reload all code modules, configs and data."""
+
+       # let the user know
+       user.send("Reloading all code modules, configs and data.")
+
+       # set a flag to reload
        muffvars.reload_modules = True
 
 def command_quit(user, command="", parameters=""):
        muffvars.reload_modules = True
 
 def command_quit(user, command="", parameters=""):
+       """Quit the world."""
+
+       # save to cold storage
        user.save()
        user.save()
+
+       # close the connection
        user.connection.close()
        user.connection.close()
+
+       # remove from the list
        user.remove()
 
 def command_help(user, command="", parameters=""):
        user.remove()
 
 def command_help(user, command="", parameters=""):
+       """List available commands and provide help for commands."""
+
+       # did the user ask for help on a specific command word?
        if parameters:
        if parameters:
+
+               # is the command word one for which we have data?
                if parameters in command_list:
                if parameters in command_list:
-                       if command_data.has_section(parameters):
-                               try:
-                                       description = command_data.get(parameters, "description")
-                               except:
-                                       description = "(no short description provided)"
-                               output = "$(grn)" + parameters + "$(nrm) - " + command_data.get(parameters, "description") + "$(eol)$(eol)"
-                               try:
-                                       help_text = command_data.get(parameters, "help")
-                               except:
-                                       help_text = "No help is provided for this command."
-                               output += help_text
-                       else:
-                               output = "There is no information on that command."
+
+                       # add a description if provided
+                       try:
+                               description = command_data.get(parameters, "description")
+                       except:
+                               description = "(no short description provided)"
+                       output = "$(grn)" + parameters + "$(nrm) - " + command_data.get(parameters, "description") + "$(eol)$(eol)"
+
+                       # add the help text if provided
+                       try:
+                               help_text = command_data.get(parameters, "help")
+                       except:
+                               help_text = "No help is provided for this command."
+                       output += help_text
+
+               # no data for the requested command word
                else:
                        output = "That is not an available command."
                else:
                        output = "That is not an available command."
-                       
+
+       # no specific command word was indicated
        else:
        else:
+
+               # give a sorted list of commands with descriptions if provided
                output = "These are the commands available to you:$(eol)$(eol)"
                sorted_commands = command_list
                sorted_commands.sort()
                output = "These are the commands available to you:$(eol)$(eol)"
                sorted_commands = command_list
                sorted_commands.sort()
@@ -188,39 +305,71 @@ def command_help(user, command="", parameters=""):
                                description = "(no short description provided)"
                        output += "   $(grn)" + item + "$(nrm) - " + command_data.get(item, "description") + "$(eol)"
                output += "$(eol)Enter \"help COMMAND\" for help on a command named \"COMMAND\"."
                                description = "(no short description provided)"
                        output += "   $(grn)" + item + "$(nrm) - " + command_data.get(item, "description") + "$(eol)"
                output += "$(eol)Enter \"help COMMAND\" for help on a command named \"COMMAND\"."
+
+       # send the accumulated output to the user
        user.send(output)
 
 def command_say(user, command="", parameters=""):
        user.send(output)
 
 def command_say(user, command="", parameters=""):
-       message = parameters.strip("\"'`").capitalize()
+       """Speak to others in the same room."""
+
+       # the user entered a message
        if parameters:
        if parameters:
+
+               # get rid of quote marks on the ends of the message and
+               # capitalize the first letter
+               message = parameters.strip("\"'`").capitalize()
+
+               # exclaim because the message ended in an exclamation mark
+               # TODO: use the ends() function instead of an index throughout
                if message[-1] == "!":
                        action = "exclaim"
                if message[-1] == "!":
                        action = "exclaim"
+
+               # begin because the message ended in miscellaneous punctuation
                elif message[-1] in [ ",", "-", ":", ";" ]:
                        action = "begin"
                elif message[-1] in [ ",", "-", ":", ";" ]:
                        action = "begin"
+
+               # muse because the message ended in an ellipsis
                elif message[-3:] == "...":
                        action = "muse"
                elif message[-3:] == "...":
                        action = "muse"
+
+               # ask because the message ended in a question mark
                elif message[-1] == "?":
                        action = "ask"
                elif message[-1] == "?":
                        action = "ask"
+
+               # say because the message ended in a singular period
+               # TODO: entering one period results in a double-period--oops!
                else:
                        action = "say"
                        message += "."
                else:
                        action = "say"
                        message += "."
+
+               # capitalize a list of words within the message
+               # TODO: move this list to the config
                capitalization = [ "i", "i'd", "i'll" ]
                for word in capitalization:
                        message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
                capitalization = [ "i", "i'd", "i'll" ]
                for word in capitalization:
                        message = message.replace(" " + word + " ", " " + word.capitalize() + " ")
+
+               # tell the room
+               # TODO: we won't be using broadcast once there are actual rooms
                muffmisc.broadcast(user.name + " " + action + "s, \"" + message + "\"")
                muffmisc.broadcast(user.name + " " + action + "s, \"" + message + "\"")
+
+       # there was no message
        else:
                user.send("What do you want to say?")
 
        else:
                user.send("What do you want to say?")
 
-def command_null(user, command="", parameters=""):
-       pass
-
 def command_error(user, command="", parameters=""):
 def command_error(user, command="", parameters=""):
+       """Generic error for an unrecognized command word."""
+
+       # 90% of the time use a generic error
        if random.random() > 0.1:
                message = "I'm not sure what \"" + command
                if parameters:
                        message += " " + parameters
                message += "\" means..."
        if random.random() > 0.1:
                message = "I'm not sure what \"" + command
                if parameters:
                        message += " " + parameters
                message += "\" means..."
+
+       # 10% of the time use the classic diku error
        else:
                message = "Arglebargle, glop-glyf!?!"
        else:
                message = "Arglebargle, glop-glyf!?!"
+
+       # send the error message
        user.send(message)
 
        user.send(message)
 
index 44c344c..67bdf1b 100644 (file)
@@ -1,45 +1,28 @@
 """Configuration objects for the MUFF Engine"""
 
 """Configuration objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# muff configuration files use the ini format supported by ConfigParser
 import ConfigParser
 import ConfigParser
-import string
 
 
+# hack to load all modules in teh muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
-config_data = ConfigParser.SafeConfigParser()
+# list of places to look for the config
 config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
 config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
+
+# name of the config file
 config_name = "muff.conf"
 config_name = "muff.conf"
+
+# build a list of possible config files
 config_files = []
 for each_dir in config_dirs:
        config_files.append(each_dir + "/" + config_name)
 config_files = []
 for each_dir in config_dirs:
        config_files.append(each_dir + "/" + config_name)
+
+# read the config
+config_data = ConfigParser.SafeConfigParser()
 config_data.read(config_files)
 
 config_data.read(config_files)
 
index 0b6707e..9e6c0b4 100644 (file)
 """Main loop for the MUFF Engine"""
 
 """Main loop for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# string.strip is used to clean up leading/trailing whitespace in user input
 import string
 import string
+
+# time.sleep is used in the loop to save cpu and provide crude pulse timing
 import time
 
 import time
 
+# hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 def main():
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 def main():
+       """The main loop."""
+
+       # loop indefinitely while the world is not flagged for termination
        while not muffvars.terminate_world:
        while not muffvars.terminate_world:
-               if not muffvars.newsocket:
-                       muffsock.initialize_server_socket()
+
+               # open the listening socket if it hasn't been already
+               if not muffvars.newsocket: muffsock.initialize_server_socket()
+
+               # pause for a configurable amount of time (decimal seconds)
                time.sleep(muffconf.config_data.getfloat("general", "increment"))
                time.sleep(muffconf.config_data.getfloat("general", "increment"))
+
+               # the world was flagged for a reload of all code/data
                if muffvars.reload_modules:
                if muffvars.reload_modules:
+
+                       # reload the muff package
                        reload(muff)
                        reload(muff)
+
+                       # reload all modules listed in the muff package
                        for module in muff.__all__:
                                exec("reload(muff." + module + ")")
                        for module in muff.__all__:
                                exec("reload(muff." + module + ")")
-                       muffvars.reload_modules = 0
+
+                       # reset the reload flag
+                       muffvars.reload_modules = False
+
+               # assign a user if a new connection is waiting
                user = muffsock.check_for_connection(muffvars.newsocket)
                user = muffsock.check_for_connection(muffvars.newsocket)
+
+               # there was a new connection
                if user:
                if user:
+
+                       # welcome to the user list
                        muffvars.userlist.append(user)
                        muffvars.userlist.append(user)
+
+                       # make a note of it
+                       # TODO: need to log this crap
                        print len(muffvars.userlist),"connection(s)"
                        print len(muffvars.userlist),"connection(s)"
+
+               # iterate over the connected users
                for each_user in muffvars.userlist:
                for each_user in muffvars.userlist:
+
+                       # show the user a menu as needed
                        each_user.show_menu()
                        each_user.show_menu()
-                       input_data = ""
+
+                       # check for some input
+                       # TODO: make a separate function for this
                        try:
                                input_data = each_user.connection.recv(1024)
                        except:
                        try:
                                input_data = each_user.connection.recv(1024)
                        except:
-                               pass
+                               input_data = ""
+                       # we got something
                        if input_data:
                        if input_data:
+
+                               # tack this on to any previous partial input
                                each_user.partial_input += input_data
                                each_user.partial_input += input_data
-                               if each_user.partial_input and each_user.partial_input[-1] == "\n":
+
+                               # the held input ends in a newline
+                               if each_user.partial_input[-1] == "\n":
+
+                                       # filter out non-printable characters
                                        each_user.partial_input = filter(lambda x: x>=' ' and x<='~', each_user.partial_input)
                                        each_user.partial_input = filter(lambda x: x>=' ' and x<='~', each_user.partial_input)
+
+                                       # strip off leading/trailing whitespace
                                        each_user.partial_input = string.strip(each_user.partial_input)
                                        each_user.partial_input = string.strip(each_user.partial_input)
+
+                                       # move it to the end of the input queue
                                        each_user.input_queue.append(each_user.partial_input)
                                        each_user.input_queue.append(each_user.partial_input)
+
+                                       # reset the held partial input
                                        each_user.partial_input = ""
                                        each_user.partial_input = ""
+
+                                       # pass the first item in the input
+                                       # queue to the main handler
                                        muffcmds.handle_user_input(each_user, each_user.input_queue[0])
                                        muffcmds.handle_user_input(each_user, each_user.input_queue[0])
+
+                                       # remove the first item from the queue
                                        each_user.input_queue.remove(each_user.input_queue[0])
                                        each_user.input_queue.remove(each_user.input_queue[0])
+
+       # the loop has terminated, so tear down all sockets
+       # TODO: move the save from command_halt() to here
        muffsock.destroy_all_sockets()
        muffsock.destroy_all_sockets()
+
+       # log a final message
+       # TODO: need a logging function for this kind of stuff
        print "Shutting down now."
 
        print "Shutting down now."
 
index 0a9989b..b84f783 100644 (file)
 """Menu objects for the MUFF Engine"""
 
 """Menu objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# muff uses menu data stored in ini-style files supported by ConfigParser
 import ConfigParser
 import ConfigParser
+
+# os.listdir is needed to get a file listing from a config directory
 import os
 import os
+
+# re.match is used to find menu options based on the choice_ prefix
 import re
 import re
-import string
 
 
+# hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
+# see if the menupath can be retrieved from muffconf
 try:
 try:
-       if muffconf.config_data.get("general", "menu_path"):
-               pass
+       if muffconf.config_data.get("files", "menus"): pass
+
+# otherwise, reload muffconf
 except AttributeError:
        reload(muffconf)
 
 except AttributeError:
        reload(muffconf)
 
+# build a list of files in the menus directory
 menu_files = []
 menu_files = []
-menu_path = muffconf.config_data.get("general", "menu_path")
+menu_path = muffconf.config_data.get("files", "menus")
 for each_file in os.listdir(menu_path):
        menu_files.append(menu_path + "/" + each_file)
 for each_file in os.listdir(menu_path):
        menu_files.append(menu_path + "/" + each_file)
+
+# read the menu files
 menu_data = ConfigParser.SafeConfigParser()
 menu_data.read(menu_files)
 
 def get_default(user):
 menu_data = ConfigParser.SafeConfigParser()
 menu_data.read(menu_files)
 
 def get_default(user):
+       """Return the default choice for a menu."""
        return menu_data.get(user.state, "default")
 
 def get_menu(user):
        return menu_data.get(user.state, "default")
 
 def get_menu(user):
+       """Show the correct menu text to a user."""
+
+       # the menu hasn't been shown yet since the user's last input
        if not user.menu_seen:
        if not user.menu_seen:
+
+               # echo is currently on for the user, so don't add an extra eol
                if user.echoing:
                        spacer = ""
                if user.echoing:
                        spacer = ""
+
+               # echo is currently off for the user, so add an extra eol
                else:
                        spacer = "$(eol)"
                else:
                        spacer = "$(eol)"
+
+               # if the user has echo on and the menu specifies it should be
+               # turned off, send: iac + will + echo + null
                try:
                        if menu_data.get(user.state, "echo") == "off" and user.echoing:
                                echo = chr(255) + chr(251) + chr(1) + chr(0)
                                user.echoing = False
                        else:
                                echo = ""
                try:
                        if menu_data.get(user.state, "echo") == "off" and user.echoing:
                                echo = chr(255) + chr(251) + chr(1) + chr(0)
                                user.echoing = False
                        else:
                                echo = ""
+
+               # if echo is not set to off in the menu and the user curently
+               # has echo off, send: iac + wont + echo + null
                except:
                        if not user.echoing:
                                echo = chr(255) + chr(252) + chr(1) + chr(0)
                                user.echoing = True
                        else:
                                echo = ""
                except:
                        if not user.echoing:
                                echo = chr(255) + chr(252) + chr(1) + chr(0)
                                user.echoing = True
                        else:
                                echo = ""
+
+               # and error condition was raised by the handler
                if user.error:
                if user.error:
+
+                       # try to get an error message matching the condition
+                       # and current state
                        try:
                                description = "$(red)" + menu_data.get(user.state, "error_" + user.error) + "$(nrm)$(eol)$(eol)"
                        try:
                                description = "$(red)" + menu_data.get(user.state, "error_" + user.error) + "$(nrm)$(eol)$(eol)"
+
+                       # otherwise, use a generic one
                        except:
                                description = "$(red)That is not a valid choice...$(nrm)$(eol)$(eol)"
                        except:
                                description = "$(red)That is not a valid choice...$(nrm)$(eol)$(eol)"
+
+                       # now clear the error condition
                        user.error = ""
                        user.error = ""
+
+               # there was no error condition raised by the handler
                else:
                else:
+
+                       # try to get a menu description for the current state
                        try:
                                description = menu_data.get(user.state, "description") + "$(eol)$(eol)"
                        try:
                                description = menu_data.get(user.state, "description") + "$(eol)$(eol)"
+
+                       # otherwise, leave it blank
                        except:
                                description = ""
                        except:
                                description = ""
+
+               # try to get menu choices for the current state
                try:
                try:
+
+                       # build a dict of choice:meaning
                        choices = {}
                        for option in menu_data.options(user.state):
                                if re.match("choice_", option):
                                        choices[option.split("_")[1]] = menu_data.get(user.state, option)
                        choices = {}
                        for option in menu_data.options(user.state):
                                if re.match("choice_", option):
                                        choices[option.split("_")[1]] = menu_data.get(user.state, option)
+
+                       # make a sorted list of choices
                        choice_keys = choices.keys()
                        choice_keys.sort()
                        choice_keys = choices.keys()
                        choice_keys.sort()
+
+                       # concatenate them all into a list for display
                        choicestring = ""
                        for choice in choice_keys:
                                choicestring += "   [$(red)" + choice + "$(nrm)]  " + choices[choice] + "$(eol)"
                        choicestring = ""
                        for choice in choice_keys:
                                choicestring += "   [$(red)" + choice + "$(nrm)]  " + choices[choice] + "$(eol)"
+
+                       # throw in an additional blank line after the choices,
+                       # if there were any
                        if choicestring:
                                choicestring += "$(eol)"
                        if choicestring:
                                choicestring += "$(eol)"
+
+               # there were no choices, so leave them blank
                except:
                        choicestring = ""
                except:
                        choicestring = ""
+
+               # try to get a prompt, if it was defined
                try:
                        prompt = menu_data.get(user.state, "prompt") + " "
                try:
                        prompt = menu_data.get(user.state, "prompt") + " "
+
+               # otherwise, leave it blank
                except:
                        prompt = ""
                except:
                        prompt = ""
+
+               # throw in the default choice, if it exists
                try:
                        default = "[$(red)" + menu_data.get(user.state, "default") + "$(nrm)] "
                try:
                        default = "[$(red)" + menu_data.get(user.state, "default") + "$(nrm)] "
+
+               # otherwise, leave it blank
                except:
                        default = ""
                except:
                        default = ""
+
+               # echo is on, so don't display a message about it
                if user.echoing:
                        echoing = ""
                if user.echoing:
                        echoing = ""
+
+               # echo is off, so let the user know
                else:
                        echoing = "(won't echo) "
                else:
                        echoing = "(won't echo) "
+
+               # assemble and send the various strings defined above
                user.send(echo + spacer + description + choicestring + prompt + default + echoing, "")
                user.send(echo + spacer + description + choicestring + prompt + default + echoing, "")
+
+               # flag that the menu has now been displayed
                user.menu_seen = True
 
                user.menu_seen = True
 
index 6bfbe62..ba90981 100644 (file)
@@ -1,63 +1,84 @@
 """Miscellaneous objects for the MUFF Engine"""
 
 """Miscellaneous objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-import string
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 def broadcast(output):
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 def broadcast(output):
+       """Send a message to all connected users."""
        for each_user in muffvars.userlist:
                each_user.send(output)
 
 def wrap_ansi_text(text, width):
        for each_user in muffvars.userlist:
                each_user.send(output)
 
 def wrap_ansi_text(text, width):
-       relative_position = 0
+       """Wrap text with arbitrary width while ignoring ANSI colors."""
+
+       # the current position in the entire text string, including all
+       # characters, printable or otherwise
        absolute_position = 0
        absolute_position = 0
-       escape = 0
+
+       # the current text position relative to the begining of the line,
+       # ignoring color escape sequences
+       relative_position = 0
+
+       # whether the current character is part of a color escape sequence
+       escape = False
+
+       # iterate over each character from the begining of the text
        for each_character in text:
        for each_character in text:
+
+               # the current character is the escape character
                if each_character == chr(27):
                if each_character == chr(27):
-                       escape = 1
+                       escape = True
+
+               # the current character is within an escape sequence
                elif escape:
                elif escape:
+
+                       # the current character is m, which terminates the
+                       # current escape sequence
                        if each_character == "m":
                        if each_character == "m":
-                               escape = 0
+                               escape = False
+
+               # the current character is a newline, so reset the relative
+               # position (start a new line)
                elif each_character == "\n":
                        relative_position = 0
                elif each_character == "\n":
                        relative_position = 0
+
+               # the current character meets the requested maximum line width,
+               # so we need to backtrack and find a space at which to wrap
                elif relative_position == width:
                elif relative_position == width:
+
+                       # distance of the current character examined from the
+                       # relative position
                        wrap_offset = 0
                        wrap_offset = 0
+
+                       # count backwards until we find a space
                        while text[absolute_position - wrap_offset] != " ":
                                wrap_offset += 1
                        while text[absolute_position - wrap_offset] != " ":
                                wrap_offset += 1
+
+                       # insert an eol in place of the space
                        text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
                        text = text[:absolute_position - wrap_offset] + "\r\n" + text[absolute_position - wrap_offset + 1:]
+
+                       # increase the absolute position because an eol is two
+                       # characters but the space it replaced was only one
                        absolute_position += 1
                        absolute_position += 1
+
+                       # now we're at the begining of a new line, plus the
+                       # number of characters wrapped from the previous line
                        relative_position = wrap_offset
                        relative_position = wrap_offset
+
+               # as long as the character is not a carriage return and the
+               # other above conditions haven't been met, count it as a
+               # printable character
                elif each_character != "\r":
                        relative_position += 1
                elif each_character != "\r":
                        relative_position += 1
+
+               # increase the absolute position for every character
                absolute_position += 1
                absolute_position += 1
+
+       # return the newly-wrapped text
        return text
 
        return text
 
index 1ca604c..67010b1 100644 (file)
@@ -1,61 +1,79 @@
 """Socket objects for the MUFF Engine"""
 
 """Socket objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# need socket.socket for new connection objects and the server's listener
 import socket
 
 import socket
 
+# hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 def check_for_connection(newsocket):
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 def check_for_connection(newsocket):
+       """Check for a waiting connection and return a new user object."""
+
+       # try to accept a new connection
        try:
                connection, address = newsocket.accept()
        except:
                return None
        try:
                connection, address = newsocket.accept()
        except:
                return None
+
+       # note that we got one
+       # TODO: we should log this crap somewhere
        print "Connection from", address
        print "Connection from", address
+
+       # disable blocking so we can proceed whether or not we can send/receive
        connection.setblocking(0)
        connection.setblocking(0)
+
+       # create a new user object
        user = muffuser.User();
        user = muffuser.User();
+
+       # associate this connection with it
        user.connection = connection
        user.connection = connection
+
+       # set the user's ipa from the connection's ipa
        user.address = address[0]
        user.address = address[0]
+
+       # return the new user object
        return user
 
 def initialize_server_socket():
        return user
 
 def initialize_server_socket():
+       """Create and open the listening socket."""
+
+       # create a new ipv4 stream-type socket object
        newsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        newsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+       # set the socket options to allow existing open ones to be reused
+       # (fixes a bug where the server can't bind for a minute when restarting
+       # on linux systems)
        newsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        newsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+       # bind the socket to to our desired server ipa and port
        newsocket.bind((muffconf.config_data.get("network", "host"), muffconf.config_data.getint("network", "port")))
        newsocket.bind((muffconf.config_data.get("network", "host"), muffconf.config_data.getint("network", "port")))
+
+       # disable blocking so we can proceed whether or not we can send/receive
        newsocket.setblocking(0)
        newsocket.setblocking(0)
+
+       # start listening on the socket
        newsocket.listen(1)
        newsocket.listen(1)
+
+       # note that we're now ready for user connections
+       # TODO: we should log this crap somewhere
        print "Waiting for connection(s)..."
        print "Waiting for connection(s)..."
+
+       # store this in a globally-accessible place
        muffvars.newsocket = newsocket
 
 def destroy_all_sockets():
        muffvars.newsocket = newsocket
 
 def destroy_all_sockets():
+       """Go through all connected users and close their sockets."""
+
+       # note that we're closing all sockets
+       # TODO: we should log this crap somewhere
        print "Closing remaining connections..."
        print "Closing remaining connections..."
+
+       # iterate over each connected user and close their associated sockets
        for user in muffvars.userlist:
                user.connection.close()
 
        for user in muffvars.userlist:
                user.connection.close()
 
index 4ecdebf..6881869 100644 (file)
 """User objects for the MUFF Engine"""
 
 """User objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# user accounts are stored in ini-style files supported by ConfigParser
 import ConfigParser
 import ConfigParser
+
+# test for existence of the account dir with os.listdir and os.mkdir to make it
+import os
+
+# string.replace is used to perform substitutions for color codes and the like
 import string
 
 import string
 
+# hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 class User:
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 class User:
+       """This is a connected user."""
+
        def __init__(self):
        def __init__(self):
+               """Default values for the in-memory user variables."""
+
+               # the account name
                self.name = ""
                self.name = ""
+
+               # the password hash
                self.passhash = ""
                self.passhash = ""
+
+               # the current client ip address
                self.address = ""
                self.address = ""
+
+               # the previous client ip address
+               self.last_address = ""
+
+               # the current socket connection object
                self.connection = None
                self.connection = None
+
+               # a flag to denote whether the user is authenticated
                self.authenticated = False
                self.authenticated = False
+
+               # number of times password entry has failed during this session
                self.password_tries = 1
                self.password_tries = 1
+
+               # the current state of the user
                self.state = "entering account name"
                self.state = "entering account name"
+
+               # flag to indicate whether a menu has been displayed
                self.menu_seen = False
                self.menu_seen = False
+
+               # current error condition, if any
                self.error = ""
                self.error = ""
+
+               # fifo-style queue for lines of user input
                self.input_queue = []
                self.input_queue = []
+
+               # fifo-style queue for blocks of user output
                self.output_queue = []
                self.output_queue = []
+
+               # holding pen for unterminated user input
                self.partial_input = ""
                self.partial_input = ""
+
+               # flag to indicate the current echo status of the client
                self.echoing = True
                self.echoing = True
+
+               # an object containing persistent account data
                self.record = ConfigParser.SafeConfigParser()
                self.record = ConfigParser.SafeConfigParser()
+
        def load(self):
        def load(self):
-               filename = muffconf.config_data.get("general", "account_path") + "/" + self.name
+               """Retrieve account data from cold storage."""
+
+               # what the filename for the user account should be
+               filename = muffconf.config_data.get("files", "accounts") + "/" + self.name
+
+               # try to load the password hash and last connection ipa
                try:
                        self.record.read(filename)
                        self.passhash = self.record.get("account", "passhash")
                        self.last_address = self.record.get("account", "last_address", self.address)
                try:
                        self.record.read(filename)
                        self.passhash = self.record.get("account", "passhash")
                        self.last_address = self.record.get("account", "last_address", self.address)
+
+               # if we can't, that's okay too
                except:
                        pass
                except:
                        pass
+
        def get_passhash(self):
        def get_passhash(self):
-               filename = muffconf.config_data.get("general", "account_path") + "/" + self.proposed_name
+               """Retrieve the user's account password hash from storage."""
+
+               # what the filename for the user account could be
+               filename = muffconf.config_data.get("files", "accounts") + "/" + self.proposed_name
+
+               # create a temporary account record object
                temporary_record = ConfigParser.SafeConfigParser()
                temporary_record = ConfigParser.SafeConfigParser()
+
+               # try to load the indicated account and get a password hash
                try:
                        temporary_record.read(filename)
                        self.passhash = temporary_record.get("account", "passhash")
                try:
                        temporary_record.read(filename)
                        self.passhash = temporary_record.get("account", "passhash")
+
+               # otherwise, the password hash is empty
                except:
                        self.passhash = ""
                except:
                        self.passhash = ""
+
        def save(self):
        def save(self):
+               """Record account data to cold storage."""
+
+               # the user account must be authenticated to save
                if self.authenticated:
                if self.authenticated:
-                       filename = muffconf.config_data.get("general", "account_path") + "/" + self.name.lower()
+
+                       # create an account section if it doesn't exist
                        if not self.record.has_section("account"):
                                self.record.add_section("account")
                        if not self.record.has_section("account"):
                                self.record.add_section("account")
+
+                       # write some in-memory data to the record
                        self.record.set("account", "name", self.name)
                        self.record.set("account", "passhash", self.passhash)
                        self.record.set("account", "last_address", self.address)
                        self.record.set("account", "name", self.name)
                        self.record.set("account", "passhash", self.passhash)
                        self.record.set("account", "last_address", self.address)
+
+                       # the account files live here
+                       account_path = muffconf.config_data.get("files", "accounts")
+                       # the filename to which we'll write
+                       filename = account_path + "/" + self.name.lower()
+
+                       # if the directory doesn't exist, create it
+                       # TODO: create account_path with 0700 perms
+                       try:
+                               if os.listdir(account_path): pass
+                       except:
+                               os.mkdir(account_path, )
+
+                       # open the user account file for writing
+                       # TODO: create filename with 0600 perms
                        record_file = file(filename, "w")
                        record_file = file(filename, "w")
+
+                       # dump the account data to it
                        self.record.write(record_file)
                        self.record.write(record_file)
+
+                       # close the user account file
                        record_file.close()
                        record_file.close()
+
        def show_menu(self):
        def show_menu(self):
+               """Send the user their current menu."""
                self.send(muffmenu.get_menu(self))
                self.send(muffmenu.get_menu(self))
+
        def remove(self):
        def remove(self):
+               """Remove a user from the list of connected users."""
                muffvars.userlist.remove(self)
                muffvars.userlist.remove(self)
+
        def send(self, output, eol="$(eol)"):
        def send(self, output, eol="$(eol)"):
+               """Send arbitrary text to a connected user."""
+
+               # only when there is actual output
                if output:
                if output:
+
+                       # start with a newline, append the message, then end
+                       # with the optional eol string passed to this function
                        output = "$(eol)" + output + eol
                        output = "$(eol)" + output + eol
+
+                       # replace eol markers with a crlf
+                       # TODO: search for markers and replace from a dict
                        output = string.replace(output, "$(eol)", "\r\n")
                        output = string.replace(output, "$(eol)", "\r\n")
-                       output = string.replace(output, "$(div)", "\r\n\r\n")
+
+                       # replace display markers with ansi escapse sequences
                        output = string.replace(output, "$(bld)", chr(27)+"[1m")
                        output = string.replace(output, "$(nrm)", chr(27)+"[0m")
                        output = string.replace(output, "$(blk)", chr(27)+"[30m")
                        output = string.replace(output, "$(grn)", chr(27)+"[32m")
                        output = string.replace(output, "$(red)", chr(27)+"[31m")
                        output = string.replace(output, "$(bld)", chr(27)+"[1m")
                        output = string.replace(output, "$(nrm)", chr(27)+"[0m")
                        output = string.replace(output, "$(blk)", chr(27)+"[30m")
                        output = string.replace(output, "$(grn)", chr(27)+"[32m")
                        output = string.replace(output, "$(red)", chr(27)+"[31m")
+
+                       # the user's account name
                        output = string.replace(output, "$(account)", self.name)
                        output = string.replace(output, "$(account)", self.name)
+
+                       # wrap the text at 80 characters
+                       # TODO: prompt user for preferred wrap width
                        output = muffmisc.wrap_ansi_text(output, 80)
                        output = muffmisc.wrap_ansi_text(output, 80)
+
+                       # drop the formatted output into the output queue
                        self.output_queue.append(output)
                        self.output_queue.append(output)
+
+                       # try to send the last item in the queue, remove it and
+                       # flag that menu display is not needed
                        try:
                                self.connection.send(self.output_queue[0])
                                self.output_queue.remove(self.output_queue[0])
                                self.menu_seen = False
                        try:
                                self.connection.send(self.output_queue[0])
                                self.output_queue.remove(self.output_queue[0])
                                self.menu_seen = False
+
+                       # but if we can't, that's okay too
                        except:
                                pass
 
                        except:
                                pass
 
index b7879cc..cededce 100644 (file)
@@ -1,46 +1,28 @@
 """Global variable objects for the MUFF Engine"""
 
 """Global variable objects for the MUFF Engine"""
 
-# Copyright (c) 2005 mudpy, 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:
-#
-#    - Redistributions of source code must retain the above copyright
-#      notice, this list of conditions and the following disclaimer.
-#    - Redistributions in binary form must reproduce the above
-#      copyright notice, this list of conditions and the following
-#      disclaimer in the documentation and/or other materials provided
-#      with the distribution.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
-# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
-# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
-# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
-# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
 
 
+# hack to load all modules in the muff package
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
 import muff
 for module in muff.__all__:
        exec("import " + module)
 
+# if there is no userlist, create an empty one
 try:
        if userlist: pass
 except NameError:
        userlist = []
 
 try:
        if userlist: pass
 except NameError:
        userlist = []
 
+# if there is no listening socket, create an empty one
 try:
        if newsocket: pass
 except NameError:
        newsocket = None
 
 try:
        if newsocket: pass
 except NameError:
        newsocket = None
 
+# flag to raise when the world should be shut down
 terminate_world = False
 terminate_world = False
+
+# flag to raise when all code modules, config and data should be reloaded
 reload_modules = False
 
 reload_modules = False
 
diff --git a/muff b/muff
new file mode 100755 (executable)
index 0000000..57874d4
--- /dev/null
+++ b/muff
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+"""Skeletal executable for the MUFF Engine"""
+
+# Copyright (c) 2005 mudpy, Jeremy Stanley <fungi@yuggoth.org>, all rights reserved.
+# Licensed per terms in the LICENSE file distributed with this software.
+
+# muff uses the ini-style configs supported by the ConfigParser module
+import ConfigParser
+
+# need the sys module to alter the import path appropriately
+import sys
+
+def get_main_loop():
+       """Find and return the main loop function"""
+
+       # figure out where to find our main configuration file
+       config_data = ConfigParser.SafeConfigParser()
+       config_dirs = [".", "./etc", "/usr/local/muff", "/usr/local/muff/etc", "/etc/muff", "/etc" ]
+       config_name = "muff.conf"
+       config_files = []
+       for each_dir in config_dirs:
+               config_files.append(each_dir + "/" + config_name)
+
+       # load the config file, get the module path and add it to sys.path
+       config_data.read(config_files)
+       module_path = config_data.get("files", "modules")
+       sys.path.append(module_path)
+
+       # import the main loop function
+       from muff.muffmain import main
+       return main
+
+# load the main loop and run it
+main = get_main_loop()
+main()
+
similarity index 52%
rename from etc/muff.conf
rename to muff.conf
index ca879e7..da57ea8 100644 (file)
+++ b/muff.conf
@@ -1,9 +1,11 @@
+[files]
+accounts = ./lib/accounts
+commands = ./lib/commands
+menus = ./lib/menus
+modules = ./lib
+
 [general]
 [general]
-account_path = ./lib/accounts
-command_path = ./lib/commands
 increment = 0.1
 increment = 0.1
-menu_path = ./lib/menus
-modules = ./lib
 password_tries = 3
 
 [network]
 password_tries = 3
 
 [network]