Lyntin

tutorial 1: basic commands in Lyntin 4.0

documentation> tutorial 1: basic commands in Lyntin 4.0

last updated: Saturday, June 24th

Summary

This tutorial walks through the basics of building a module that adds commands to Lyntin. It's very basic and assumes you haven't done any module hacking in Lyntin before. It does assume you have a working knowledge of Python. If you don't, you should go to http://www.python.org/ and read through their tutorials and such.

Revisions

date description who
1/4/2003 wrote the document based on Lyntin 3.0 wbg
1/25/2003 minor updates to the end wbg
4/13/2003 moved it out of the LCR and into the documentation section wbg
10/27/2003 overhauled for 4.0 wbg

Step 1: defining the problem we're trying to solve

My typical mud session involves using an xterm and ssh'ing to a server in Texas and then launching Lyntin from there. As such I don't have many of the "features" in the tkui available (until I get around to building a cursesui). I have a tendency to idle a lot as I'm working in other windows. I find it's difficult to flip back to the xterm window and figure out what's going on--I need some kind of status section of the xterm which my actions can dump status information on. Like how long I've been idle for.

What I want to build is a module that will keep a dict of status name and value variables and display it in the xterm title bar.

Step 2: building the module skeleton

First thing I do is build a quickie module that I can start working with. I name it statusbar.py and I place it in my personal modules directory:

/home
  |-- /will
       |-- /tutorial
             |-- statusbar.py

I start Lyntin up with:

runlyntin.py -c tutorial.ini

My tutorial.ini config file looks like this:

[Lyntin]
datadir:   /home/willg/tutorial
moduledir: /home/willg/tutorial

If you're using Windows, you'll probably want to make a copy of runlyntin.pyw and edit one of them to have boot options like this:

bootoptions = {"ui": "tk",
               "datadir": "/path/to/tutorial/directory",
               "moduledir": ["/path/to/tutorial/directory"],
               "readfile": [],
               "snoopdefault": 1}

When Lyntin starts up, it loads all the modules in the directories specified by the moduledir property in the tutorial.ini file or bootoptions dict. In this case, it's the tutorial subdirectory which is where I'm going to put my statusbar module.

I open my favorite editor and type the following (or copy and paste it from previously existing modules):

from lyntin import exported
from lyntin.modules import modutils

# this will hold the command information for adding to
# Lyntin later on
commands_dict = {}

# this will hold the statusbar status name and value variables
# for display in the title bar
statusbar_dict = {}

def ststatus_cmd(ses, args, input):
   """
   This command allows you to set a new name/value pair in the
   xterm title bar.
   """
   exported.write_message("You typed #statusbar!")

commands_dict["setstatus"] = (setstatus_cmd, "name value= sortkey=")

def load():
   """ Initializes the module by binding all the commands."""
   modutils.load_commands(commands_dict)

def unload():
   """ Unbinds the commands (for when we reimport the module)."""
   modutils.unload_commands(commands_dict)

That's my basic module shell. Let's walk through the pieces.

The first 2 lines are:

from lyntin import exported
from lyntin.modules import modutils

The lyntin.exported module is Lyntin's API for interacting with Lyntin's internals. The lyntin.modules.modutils module has some quickie helper functions for building modules.

The next section:

# this will hold the command information for adding to
# Lyntin later on
commands_dict = {}

# this will hold the statusbar status name and value variables
# for display in the title bar
statusbar_dict = {}

def ststatus_cmd(ses, args, input):
   """
   This command allows you to set a new name/value pair in the
   xterm title bar.
   """
   exported.write_message("You typed #statusbar!")

creates some module variables and then defines the command we're going to build. Commands take three arguments:

  1. the session the user executed the command from (Session)
  2. arguments from the argparser (dict)
  3. the full input line the user typed (string)

The next line:

commands_dict["setstatus"] = (setstatus_cmd, "name value= sortkey=")

tosses an item of the name setstatus with value (setstatus_cmd, "name value= sortkey=") into the commands_dict. This creates a tuple of two items that are bound to the name of the command we're using. The tuple contains a reference to the function that will handle the command (setstatus_cmd) and a string that defines how to parse the input into a series of arguments that will be passed into args. In this case, we want three arguments: a name, a value, and a sortkey. The value and sortkey default to empty string--so they're optional.

Finally:

def load():
   """ Initializes the module by binding all the commands."""
   modutils.load_commands(commands_dict)

def unload():
   """ Unbinds the commands (for when we reimport the module)."""
   modutils.unload_commands(commands_dict)

which defines the load and unload functions. The load function which Lyntin calls when it imports the module. This function allows your module to bind commands and register with hooks and other initialization sorts of things. It also has an unload function. When you type #import statusbar when the module has already been loaded, Lyntin first calls unload on the module to allow it to gracefully unbind and unregister from Lyntin internals, then reloads the module, and finally calls load on the new instance of the module (assuming it imported without kicking up exceptions).

That's it for the skeleton. Now we launch Lyntin using the command line listed above and see if Lyntin has problems importing the new module.

launching Lyntin
image 1: launching Lyntin with the module

Oops! I spelled setstatus_cmd in the def line. I'll fix that in another terminal window and then use the #import command to reimport it.

statusbar reloaded
image 2: statusbar reloaded

Ahh--it loaded fine. I also did a #help and then #help setstatus to verify that the help text was there. When modules.modutils.add_commands is run on the commands_dict, it looks at the __doc__ on the command handling function and passes that in as help text (if it exists). When the command is bound Lyntin adds the syntax line in the help file according to the arguments line you passed in.

Now to add some functionality to our setstatus command.

Step 3: adding functionality

We will use the sys module, so we need to add an import line to the top of the file:

import sys

The argparser will parse the input the user typed in and associate the tokens with the arguments and stick them in the args dict. For some types it'll convert the input into a recognizable value (all boolean values get converted to 0 or 1) and it'll do type checking as well. It's pretty intense--and way outside the scope of this document.

Anyhow, so all our values are in that args dict. So we'll add the following code:

def ststatus_cmd(ses, args, input):
   """
   This command allows you to set a new name/value pair in the
   xterm title bar.
   """
   global statusbar_dict

   name = args["name"]
   value = args["value"]
   sortkey = args["sortkey"]

   if not sortkey:
      sortkey = name

   ui = str(exported.get_engine().getUI().__class__)

   if not value:
      if statusbar_dict.has_key(name):
         del statusbar_dict[name]
   else:
      statusbar_dict[name] = (value, sortkey)

   output = []
   for mem,(value,sortkey) in statusbar_dict.items():
      output.append( (sortkey, mem, mem + ": " + value + " "))

   output.sort()

   output = [ x[2] for x in output ]

   if ui.find("Textui") != -1:
      sys.stdout.write("\33]2;" + "".join(output) + "\07")
      sys.stdout.flush()
   else:
      exported.write_error("setstatus: textui support only.")

Let's break this down into chunks. The first chunk:

def ststatus_cmd(ses, args, input):
   """
   This command allows you to set a new name/value pair in the
   xterm title bar.
   """
   global statusbar_dict

   name = args["name"]
   value = args["value"]
   sortkey = args["sortkey"]

   if not sortkey:
      sortkey = name

   ui = str(exported.get_engine().getUI().__class__)

We pick up the values we're interested in from the args dict then assign a value to the sortkey if the user didn't specify one. The next line is a bit tricky... We're using the exported module to get the Lyntin Engine instance which has a link to the ui instance. The ui instance (like all objects in Python) has a __class__ member which tells you what the class name is. We use this to make sure the user isn't trying to use this in some other ui. Continuing on:

The next chunk:

   if not value:
      if statusbar_dict.has_key(name):
         del statusbar_dict[name]
   else:
      statusbar_dict[name] = (value, sortkey)

If there is no value, we're going to delete the name/value pair from the statusbar_dict altogether. Otherwise we add the new item or alter the existing item.

The next chunk:

   output = []
   for mem,(value,sortkey) in statusbar_dict.items():
      output.append( (sortkey, mem, mem + ": " + value + " "))

   output.sort()

   output = [ x[2] for x in output ]

Because they've altered one of the status bar items, we need to redisplay the new status bar in the xterm window. First we need to figure out what's in it. We go through the statusbar_dict and create a list of tuples with the sortkey first, and the thing to display second. Then we sort this list. Then we use a list comprehension to convert the list of tuples into a list of just the values to display.

And lastly:

   if ui.find("Textui") != -1:
      sys.stdout.write("\33]2;" + "".join(output) + "\07")
      sys.stdout.flush()
   else:
      exported.write_error("setstatus: textui and tkui support only.")

The textui writes text to stdout so we just pipe it to stdout. Though because Lyntin is multi-threaded this isn't an incredibly great idea since it could potentially cause issues. But that's a problem for another time.

Step 4: finishing it up

Now that you have a nice statusbar module, you might want to consider getting it added to the Lyntin code repository. Instructions for that are at http://lyntin.sourceforge.net/repository.php. At a minimum, you'll want to add a module doc-string explaining what the module does and what deficiencies it has as well as some module attributes such as who wrote it, when, and what version.

Step 5: viewing the complete module

"""
This module defines the command #setstatus which allows a user
to set the title bar using name/value pairs which are tracked
and sorted individually.  This has many uses amongst which
are display of hit points, experience points, and other statistics
in a static place.
"""
__author__ = "Will Guaraldi"
__version__ = "1.0"
__date__ = "January 4, 2003"

import sys
from lyntin import exported
from lyntin.modules import modutils

# this will hold the command information for adding to
# Lyntin later on
commands_dict = {}

# this will hold the statusbar status name and value variables
# for display in the title bar
statusbar_dict = {}

def setstatus_cmd(ses, args, input):
   """
   This command allows you to set a new name/value pair in the
   xterm title bar.
   """
   global statusbar_dict

   name = args["name"]
   value = args["value"]
   sortkey = args["sortkey"]

   if not sortkey:
      sortkey = name

   ui = str(exported.get_engine().getUI().__class__)

   if not value:
      if statusbar_dict.has_key(name):
         del statusbar_dict[name]
   else:
      statusbar_dict[name] = (value, sortkey)

   output = []
   for mem,(value,sortkey) in statusbar_dict.items():
      output.append( (sortkey, mem, mem + ": " + value + " "))

   output.sort()

   output = [ x[2] for x in output ]

   if ui.find("Textui") != -1:
      sys.stdout.write("\33]2;" + "".join(output) + "\07")
      sys.stdout.flush()
   else:
      exported.write_error("setstatus: textui support only.")

commands_dict["setstatus"] = (setstatus_cmd, "name value= sortkey=")

def load():
   """ Initializes the module by binding all the commands."""
   modutils.load_commands(commands_dict)

def unload():
   """ Unbinds the commands (for when we reimport the module)."""
   modutils.unload_commands(commands_dict)

Rock on!