nakedmudv3.3/
nakedmudv3.3/lib/
nakedmudv3.3/lib/logs/
nakedmudv3.3/lib/misc/
nakedmudv3.3/lib/players/
nakedmudv3.3/lib/txt/
nakedmudv3.3/lib/world/
nakedmudv3.3/lib/world/examples/
nakedmudv3.3/lib/world/examples/mproto/
nakedmudv3.3/lib/world/examples/oproto/
nakedmudv3.3/lib/world/examples/reset/
nakedmudv3.3/lib/world/examples/rproto/
nakedmudv3.3/lib/world/examples/trigger/
nakedmudv3.3/lib/world/limbo/
nakedmudv3.3/lib/world/limbo/room/
nakedmudv3.3/lib/world/limbo/rproto/
nakedmudv3.3/src/alias/
nakedmudv3.3/src/char_vars/
nakedmudv3.3/src/editor/
nakedmudv3.3/src/example_module/
nakedmudv3.3/src/help/
nakedmudv3.3/src/set_val/
nakedmudv3.3/src/socials/
nakedmudv3.3/src/time/
//*****************************************************************************
//
// pyplugs.c
//
// Pyplugs are just various things that occur at the intersection between 
// Python and C, in NakedMud. They include the loading of python modules,
// a few commands for interacting with python modules, and miscellaneory (sp?)
// python stuff that doesn't neccessarily fall under "scripting".
//
//*****************************************************************************

#include <Python.h>
#include <structmember.h>
#include <compile.h>
#include <dirent.h> 
#include <node.h>

#include "../mud.h"
#include "../utils.h"
#include "../character.h"
#include "pyplugs.h"



//*****************************************************************************
// local functions, structures, and defines
//*****************************************************************************

// the directory where we keep python modules that we have extended ourself with
#define PYMOD_LIB       "../lib/pymodules"


//
// Similar to Py_CompileString (in the Python API), but a file is compiled
// instead. fname is the file that will be compiled. The code object will be
// stored in a file named fname + c (e.g. foo.py becomes foo.pyc)
PyObject *Py_CompileFile(char *fname) {
  FILE *fl = fopen(fname, "r");
  if(fl == NULL) return NULL;
  char cfname[strlen(fname) + 2];
  sprintf(cfname, "%sc", fname);

  struct _node *node = PyParser_SimpleParseFile(fl, fname, Py_file_input);
  if(node == NULL) return NULL;
  PyCodeObject *code = PyNode_Compile(node, cfname);
  PyNode_Free(node);
  fclose(fl);
  return (PyObject *)code;
}


//
// reads in one python module, and adds it to the game. Fname is the name of
// the file we are reading the module from, and mname is the name of the module
// we will be storing it as. This function will reload the module if it has
// already been loaded
bool PyModule_Reload(char *fname, char *mname) {
  PyObject *code = Py_CompileFile(fname);
  if(code == NULL) {
    char *tb = getPythonTraceback();
    log_string("Error in module file: %s\r\n"
	       "\r\nTraceback is:\r\n%s\r\n", fname, tb);
    free(tb);
    return FALSE;
  }
  // no errors occured... load the module into our package
  else {
    // we need to run an unload function if the module has been loaded before
    PyObject *old_mod = PyImport_ImportModule(mname);
    if(old_mod != NULL) {
      PyObject *dict = PyModule_GetDict(old_mod);
      
      // if the module has an __unload__ function, call it
      if(PyDict_GetItemString(dict, "__unload__") != NULL) {
	PyObject *tbList = PyObject_CallMethod(old_mod, "__unload__", "");

	// encountered an error with the unload function
	if(tbList == NULL) {
	  char *tb = getPythonTraceback();
	  log_string("Encountered error in %s.__unload__():\r\n"
		     "\r\nTraceback is:\r\n%s\r\n", mname, tb);
	  free(tb);
	}
	Py_XDECREF(tbList);
      }
      Py_XDECREF(old_mod);
    }

    PyObject *module = PyImport_ExecCodeModule(mname, code);
    if(module == NULL) {
      char *tb = getPythonTraceback();
      log_string("Error in module file: %s\r\n"
		 "\r\nTraceback is:\r\n%s\r\n", fname, tb);
      free(tb);
      return FALSE;
    }
    else
      Py_DECREF(module);
    Py_DECREF(code);
    return TRUE;
  }
}

//
// takes the name of a python module, and loads that module into the game
COMMAND(cmd_pyload) {
  if(!*arg)
    send_to_char(ch, "Which module or package would you like to load?\r\n");
  else {
    char fname[SMALL_BUFFER];
    sprintf(fname, "%s/%s.py", PYMOD_LIB, arg);
    // make sure the file exists
    if(!file_exists(fname))
      send_to_char(ch, "That module does not exist!\r\n");
    else if(PyModule_Reload(fname, arg))
      send_to_char(ch, "Module successfully loaded.\r\n");
  }
}

//
// NakedMud allows you to extend the codebase in Python. These extensions must
// take the form of Python modules, and must be stored in the PYMOD_LIB
// directory. These modules might add new commands to the mud, or provide new
// functions that scripts or other extension modules can use. The game datatypes
// (i.e. chars, rooms, objects, etc...) cannot be extended by Python, but just
// about any other feature of the mud can be.
void init_py_modules() {
  // build a list of all the files in this directory
  char mname[SMALL_BUFFER]; // module name
  char fname[SMALL_BUFFER]; // the name of the file
  DIR *dir = opendir(PYMOD_LIB);
  struct dirent *entry;

  // add our PYMOD_LIB directory to the sys path, 
  // so the modules can access each other
  PyObject *sys  = PyImport_ImportModule("sys");
  PyObject *path = (sys ? PyDict_GetItemString(PyModule_GetDict(sys), "path") :
		    NULL);
  if(path != NULL)
    PyList_Append(path, Py_BuildValue("s", PYMOD_LIB));
  else
    log_string("ERROR: Unable to add %s to python sys.path", PYMOD_LIB);

  // go through each of our python modules, and add them to the pymod package
  for(entry = readdir(dir); entry; entry = readdir(dir)) {
    // two cases: it's a package, or it's a module file. Packages will
    // be directories, and modules will be .py files. Check for both cases,
    // and then ignore all of the rest:
    int nlen = strlen(entry->d_name);
    sprintf(fname, "%s/%s", PYMOD_LIB, entry->d_name);

    // skip ourself and our parent
    if(!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
      continue;
    // python file == module
    else if(nlen >= 4 && !strcasecmp(".py", entry->d_name + nlen-3)) {
      sprintf(mname, "%s", entry->d_name);
      mname[strlen(mname)-3] = '\0';
    }
    // directory == package
    else if(dir_exists(fname))
      sprintf(mname, "%s", entry->d_name);
    // nothing we can use
    else
      continue;

    // Load the module if it hasn't been loaded yet
    PyObject *mod = PyImport_ImportModule(mname);
    if(mod != NULL)
      log_string("loading python module, %s", mname);
    // oops... something went wrong. Let's get the traceback
    else {
      // we now abandon bootup if we fail to load a Python module, because it
      // can have potentially dangerous affects on the loading of other modules
      // Thanks to Thirsteh for pointing this out.
      BUFFER *buf = newBuffer(1);
      char *tb = getPythonTraceback();
      bprintf(buf, "Error loading module, %s:\r\n%s\r\n"
	           "Bootup aborted. MUD shutting down.", mname, tb);
      log_string(bufferString(buf));
      printf("%s", bufferString(buf));
      
      deleteBuffer(buf);
      free(tb);
      closedir(dir);
      exit(1);
    }
  }
  closedir(dir);
}



//*****************************************************************************
// implementation of pyplugs.h
//*****************************************************************************
void init_pyplugs(void) {
  init_py_modules();
  add_cmd("pyload", NULL, cmd_pyload, POS_UNCONSCIOUS, POS_FLYING,
	  "admin", FALSE, FALSE);
}



//
// Python makes it overly complicated (IMO) to get traceback information when
// running in C. I spent some time hunting around, and came across a lovely
// site that provided me with this function for getting a traceback of an error.
// The site that provided this piece of code to me is located here:
//   http://stompstompstomp.com/weblog/technical/2004-03-29
char* getPythonTraceback(void) {
    // Python equivilant:
    // import traceback, sys
    // return "".join(traceback.format_exception(sys.exc_type, 
    //    sys.exc_value, sys.exc_traceback))

    PyObject *type, *value, *traceback;
    PyObject *tracebackModule;
    char *chrRetval;

    PyErr_Fetch(&type, &value, &traceback);

    tracebackModule = PyImport_ImportModule("traceback");
    if (tracebackModule != NULL) {
        PyObject *tbList, *emptyString, *strRetval;

        tbList = PyObject_CallMethod(
            tracebackModule, 
            "format_exception", 
            "OOO",
            type,
            value == NULL ? Py_None : value,
            traceback == NULL ? Py_None : traceback);

        emptyString = PyString_FromString("");
        strRetval = PyObject_CallMethod(emptyString, "join", "O", tbList);

        chrRetval = strdup((strRetval ? PyString_AsString(strRetval) :
			    "unknown error"));

        Py_XDECREF(tbList);
        Py_XDECREF(emptyString);
        Py_XDECREF(strRetval);
        Py_XDECREF(tracebackModule);
    }
    else
      chrRetval = strdup("Unable to import traceback module.");

    Py_XDECREF(type);
    Py_XDECREF(value);
    Py_XDECREF(traceback);

    return chrRetval;
}

PyGetSetDef *makePyGetSetters(LIST *getsetters) {
  PyGetSetDef *getsets = calloc(listSize(getsetters)+1,sizeof(PyGetSetDef));
  LIST_ITERATOR  *gs_i = newListIterator(getsetters);
  PyGetSetDef      *gs = NULL;
  int i                = 0;
  ITERATE_LIST(gs, gs_i) {
    getsets[i] = *gs;
    i++;
  } deleteListIterator(gs_i);
  return getsets;
}

PyMethodDef *makePyMethods(LIST *methods) {
  PyMethodDef  *meth = calloc(listSize(methods)+1, sizeof(PyMethodDef));
  LIST_ITERATOR *m_i = newListIterator(methods);
  PyMethodDef     *m = NULL;
  int i              = 0;
  ITERATE_LIST(m, m_i) {
    meth[i] = *m;
    i++;
  } deleteListIterator(m_i);
  return meth;
}

void makePyType(PyTypeObject *type, LIST *getsetters, LIST *methods) {
  // build up the array of getsetters for this object
  if(getsetters != NULL)
    type->tp_getset = makePyGetSetters(getsetters);
  
  // build up the array of methods for this object
  if(methods != NULL)
    type->tp_methods = makePyMethods(methods);
}