def runtimer(self): """ This timer thread sleeps for a second, then calls everything in the queue with the current tick. """ from time import time self._current_tick = 0 wakeup_time = current_time = time() import threading ev = threading.Event() exported.hook_register("shutdown_hook", lambda *_: ev.set()) while not ev.isSet(): try: tout = wakeup_time - current_time if tout > 0: ev.wait(timeout = tout) current_time = time() elif tout < -10: # we are late too much; drop 10 ticks wakeup_time += 10 continue event.SpamEvent(hookname="timer_hook", argmap={"tick": self._current_tick} ).enqueue() self._current_tick += 1 wakeup_time += 1 except KeyboardInterrupt: return except SystemExit: return except: exported.write_traceback("ticker: ticker hiccupped.")
def runtimer(self): """ This timer thread sleeps for a second, then calls everything in the queue with the current tick. Note: This will almost always be slightly behind and will get worse as there are more things that get executed each tick. """ import time, event self._current_tick = 0 while not self._shutdownflag: try: time.sleep(1) event.SpamEvent(hookname="timer_hook", argmap={ "tick": self._current_tick }).enqueue() self._current_tick += 1 except KeyboardInterrupt: return except SystemExit: return except: exported.write_traceback("ticker: ticker hiccupped.")
def runtimer(self): """ This timer thread sleeps for a second, then calls everything in the queue with the current tick. Note: This will almost always be slightly behind and will get worse as there are more things that get executed each tick. """ import time, event self._current_tick = 0 while not self._shutdownflag: try: time.sleep(1) event.SpamEvent(hookname="timer_hook", argmap={"tick": self._current_tick} ).enqueue() self._current_tick += 1 except KeyboardInterrupt: return except SystemExit: return except: exported.write_traceback("ticker: ticker hiccupped.")
def checkActions(self, text): """ Checks to see if text triggered any actions. Any resulting actions will get added as an InputEvent to the queue. @param text: the data coming from the mud to check for triggers @type text: string """ # FIXME - make sure this works even when lines are broken up. actionlist = self._actionlist if not actionlist: actionlist = [x for x in list(self._actions.values()) if x[6] not in self._disabled] actionlist.sort(key = lambda x:x[3]) self._actionlist = actionlist colorline = utils.filter_cm(text) nocolorline = ansi.filter_ansi(colorline) # go through all the lines in the data and see if we have # any matches for (action, actioncompiled, response, color, priority, onetime, tag) in actionlist: if color: match = actioncompiled.search(colorline) line = colorline else: match = actioncompiled.search(nocolorline) line = nocolorline if match: # for every match we figure out what the expanded response # is and add it as an InputEvent in the queue. the reason # we do a series of separate events rather than one big # event with ; separators is due to possible issues with # braces and such in malformed responses. # get variables from the action actionvars = get_ordered_vars(action) # fill in values for all the variables in the match varvals = {} for i in range(len(actionvars)): varvals[actionvars[i]] = match.group(i+1) # add special variables varvals['a'] = line.replace(';', '_') # fill in response variables from those that # matched on the trigger response = utils.expand_vars(response, varvals) # event.InputEvent(response, internal=1, ses=self._ses).enqueue() try: exported.lyntin_command(response, internal=1, session=self._ses) except: exported.write_traceback() if onetime and action in self._actions: del self._actions[action] self._actionlist = None # invalidate the list
def unload_cmd(ses, args, input): """ Unloads a module from Lyntin by calling the module's "unload" function and then removing references to it in the Python environment. examples: #unload wbgscheduler #unload modules.alias category: commands """ mod = args["modulename"] if sys.modules.has_key(mod): _module = sys.modules[mod] if _module.__dict__.has_key("lyntin_import"): if _module.__dict__.has_key("unload"): try: _module.unload() except: exported.write_traceback("unload: module %s didn't unload properly." % mod) else: exported.write_error("unload: module %s doesn't have an unload function." % mod) del sys.modules[mod] exported.write_message("unload: module %s unloaded." % mod) config.lyntinmodules.remove(mod) return else: exported.write_error("unload: module %s cannot be unloaded." % mod) return exported.write_error("unload: module %s is not loaded." % mod)
def load_modules(): """ Magically dynamically loads all the modules in the modules package. This is truly a semi-magic function. """ # handle modules.* _module_list = [] for loader, name, isPkg in pkgutil.iter_modules(lyntin.modules.__path__): _module_list.append(name) _module_list.sort() for mem in _module_list: # we skip over all files that start with a _ # this allows hackers to be working on a module and not have # it die every time. mem2 = get_module_name(mem) if mem2.startswith("_"): continue try: name = "lyntin.modules." + mem2 _module = __import__(name) _module = sys.modules[name] if _module.__dict__.has_key("load"): _module.load() _module.__dict__["lyntin_import"] = 1 config.lyntinmodules.append(name) except: exported.write_traceback("Module '%s' refuses to load." % name) # handle modules found in the moduledir moduledirlist = config.options["moduledir"] if moduledirlist: for moduledir in moduledirlist: # grab the contents of the moduledir directory _module_list = glob.glob( os.path.join( moduledir, "*.py")) # toss the moduledir in the sys.path sys.path.append(moduledir) # and toss all the contents of the directory in our _module_list for mem in _module_list: mem2 = get_module_name(mem) if mem2.startswith("_"): continue try: _module = __import__(mem2) if _module.__dict__.has_key("load"): _module.load() test_for_conflicts(mem, _module) _module.__dict__["lyntin_import"] = 1 config.lyntinmodules.append(mem2) except: exported.write_traceback("Module '%s' refuses to load." % mem)
def clear_cmd(ses, words, input): """ This command clears a session of all session data (except the actual connection). This covers gags, subs, actions, aliases... category: commands """ try: ses.clear() exported.write_message("clear: session %s cleared." % ses.getName(), ses) except Exception, e: exported.write_traceback("clear: error in clearing session %s" % ses)
def timeUpdate(self, args): """ This gets called by the timer_hook in the engine every second. It goes through and executes all the events for this Lyntin tick as well as events who are supposed to execute at this seconds since the epoch or before. It also handles tossing events back in the schedule if they need repeating. """ tick = args["tick"] events = [] if self._events.has_key(tick): for mem in self._events[tick]: events.append(mem) del self._events[tick] self._current_tick = tick # we want to execute for this second and any previous seconds # that have been missed. sec = int(time.time()) keys = self._tevents.keys() keys = [mem for mem in keys if mem < sec] for key in keys: for mem in self._tevents[key]: events.append(mem) del self._tevents[key] # go through and execute all the events we've found for mem in events: if not mem._quiet: exported.write_message("Executing %r." % mem) if not callable(mem._cmd): exported.lyntin_command(mem._cmd, internal=1, session=mem._ses) else: try: mem._cmd(*mem._args, **mem._xargs) except: exported.write_traceback( "exception kicked up while trying to execute event.") # handles repeating events if mem._repeat == 1: self.addEvent(tick + mem._offset, mem, id=mem._id)
def timeUpdate(self, args): """ This gets called by the timer_hook in the engine every second. It goes through and executes all the events for this Lyntin tick as well as events who are supposed to execute at this seconds since the epoch or before. It also handles tossing events back in the schedule if they need repeating. """ tick = args["tick"] events = [] if self._events.has_key(tick): for mem in self._events[tick]: events.append(mem) del self._events[tick] self._current_tick = tick # we want to execute for this second and any previous seconds # that have been missed. sec = int(time.time()) keys = self._tevents.keys() keys = [mem for mem in keys if mem < sec] for key in keys: for mem in self._tevents[key]: events.append(mem) del self._tevents[key] # go through and execute all the events we've found for mem in events: if not mem._quiet: exported.write_message("Executing %r." % mem) if not callable(mem._cmd): exported.lyntin_command(mem._cmd, internal=1, session=mem._ses) else: try: mem._cmd(*mem._args, **mem._xargs) except: exported.write_traceback("exception kicked up while trying to execute event.") # handles repeating events if mem._repeat == 1: self.addEvent(tick + mem._offset, mem, id=mem._id)
def write_cmd(ses, args, input): """ Writes all aliases, actions, gags, etc to the file specified. You can then use the #read command to read this file in and restore your session settings. The quiet argument lets you specify whether you want command data to be written to the file so that when you read it back in with #read, the commands are executed quietly. If you don't specify a directory, it will be written to your datadir. Note: Windows users should either use two \\'s or use / to separate directory names. category: commands """ filename = args["file"] quiet = args["quiet"] f = None c = exported.myengine.getConfigManager().get("commandchar") if os.sep not in filename: filename = config.options["datadir"] + filename data = exported.get_write_data(ses, quiet) if data: try: f = open(filename, "w") to_encode = c + (u"\n" + c).join(data) f.write(to_encode.encode("utf-8")) f.close() exported.write_message( "write: file %s has been written for session %s." % (filename, ses.getName()), ses) except Exception: exported.write_traceback( "write: error writing to file %s." % filename, ses) try: f.close() except: pass return exported.write_message("write: no data to write.")
def write_cmd(ses, args, input): """ Writes all aliases, actions, gags, etc to the file specified. You can then use the #read command to read this file in and restore your session settings. The quiet argument lets you specify whether you want command data to be written to the file so that when you read it back in with #read, the commands are executed quietly. If you don't specify a directory, it will be written to your datadir. Note: Windows users should either use two \\'s or use / to separate directory names. category: commands """ filename = args["file"] quiet = args["quiet"] f = None c = exported.myengine.getConfigManager().get("commandchar") if os.sep not in filename: filename = config.options["datadir"] + filename data = exported.get_write_data(ses, quiet) if data: try: f = open(filename, "w") f.write(c + ("\n" + c).join(data)) f.close() exported.write_message("write: file %s has been written for session %s." % (filename, ses.getName()), ses) except Exception: exported.write_traceback("write: error writing to file %s." % filename, ses) try: f.close() except: pass return exported.write_message("write: no data to write.")
def runengine(self): """ This gets kicked off in a thread and just keep going through events until it detects a shutdown. """ while not self._shutdownflag: try: # blocks on the event queue e = self._event_queue.get() e.execute() except KeyboardInterrupt: return except SystemExit: return except: self.tallyError() exported.write_traceback("engine: unhandled error in engine.") self._num_events_processed += 1
def run(self): """ This is the poll loop for user input.""" try: while not self.shutdownflag: if os.name == 'posix': if self._rline == 1: data = self._posix_readline_input() else: data = self._posix_input() else: data = self._non_posix_input() if data != None: self.handleinput(data) # FIXME - this is just plain icky. the issue is that # we need to know we're ending _before_ we block for # the next input. otherwise Lyntin will shut down except # for this thread which will hang around blocking until # the user hits the enter key. # # any good ideas for dealing with this are more than welcome. if data.find("#end") == 0: break except select.error as e: (errno, name) = e if errno == 4: exported.write_message("system exit: select.error.") event.ShutdownEvent().enqueue() return except SystemExit: exported.write_message("system exit: you'll be back...") event.ShutdownEvent().enqueue() except: exported.write_traceback() event.ShutdownEvent().enqueue()
def log(self, input): """ Logs text to a file instance self._logfile and optionally filters ansi according to self._strip_ansi. @param input: the string to log to the logfile for this session @type input: string """ if self._logfile == None: return try: if self._strip_ansi == 1: input = ansi.filter_ansi(input) text = utils.filter_cm(input) text = text.replace("\n", os.linesep) self._logfile.write(text) self._logfile.flush() except: self._logfile = None exported.write_traceback("Logfile cannot be written to.", self._session)
def unload_cmd(ses, args, input): """ Unloads a module from Lyntin by calling the module's "unload" function and then removing references to it in the Python environment. examples: #unload wbgscheduler #unload modules.alias category: commands """ mod = args["modulename"] if mod in sys.modules: _module = sys.modules[mod] if "lyntin_import" in _module.__dict__: if "unload" in _module.__dict__: try: _module.unload() except: exported.write_traceback( "unload: module %s didn't unload properly." % mod) else: exported.write_error( "unload: module %s doesn't have an unload function." % mod) del sys.modules[mod] exported.write_message("unload: module %s unloaded." % mod) config.lyntinmodules.remove(mod) return else: exported.write_error("unload: module %s cannot be unloaded." % mod) return exported.write_error("unload: module %s is not loaded." % mod)
def python_cmd(ses, words, input): """ #@ allows you to execute arbitrary Python code inside of Lyntin. It will first look for a module named "lyntinuser" and execute the code inside that module's __dict__ environment. If no such module exists, it will execute the code inside modules.advanced . At present it can only handle one-line Python statements. examples: #@ print "hello" #@ print "\\n".join(exported.get_commands()) category: commands """ global execdictglobals, execdictlocals # NOTE: if we ever get to handling multiple-lines, we'll need # to change this function completely. try: if execdictlocals == None: execdictlocals = {} execdictlocals["session"] = ses execdictlocals["exported"] = exported my_usermodule = _get_user_module() if my_usermodule: dictglobals = my_usermodule.__dict__ else: if execdictglobals == None: execdictglobals = {} exported.write_error( "No lyntinuser module loaded--executing with no context.") dictglobals = execdictglobals source = input[1:].lstrip() compiled = compile_command(source) # # XXX for one-liners only: # if not compiled: compiled = compile_command(source + "\n") old_stdout = sys.stdout old_stderr = sys.stderr old_stdin = sys.stdin sys_stdout = io.StringIO() sys_stderr = io.StringIO() sys_stdin = io.StringIO() try: sys.stdout = sys_stdout sys.stderr = sys_stderr sys.stdin = sys_stdin exec(compiled, dictglobals, execdictlocals) finally: sys.stdout = old_stdout sys.stderr = old_stderr sys.stdin = old_stdin error = sys_stderr.getvalue() if error: exported.write_error(error) text = sys_stdout.getvalue() if text.endswith("\n"): text = text[:-1] if text: exported.write_message(text) except (OverflowError, SyntaxError, ValueError, NameError): import traceback exported.write_error("".join( traceback.format_exception_only(*(sys.exc_info()[:2])))) except: exported.write_traceback("@: error in raw python stuff.") exported.tally_error()
def load_cmd(ses, args, input): """ Loads/reloads a module. When reloading, it looks for an "unload" function and executes it prior to reloading the module. After reloading/loading, it looks for a "load" function and executes it. Lyntin modules located in the modules package are safe to reload in-game. Lyntin core modules (engine, helpmanager, event...) are NOT safe to import in-game. examples: #load modules.action #load exportuser #load will look for the module on the sys.path. So if your module is not on the sys.path, you should first add the directory using #@: #@ import sys #@ sys.path.append("/directory/where/my/module/exists") Directories specified by the moduledir command-line argument are added to the sys.path upon Lyntin startup. category: commands """ mod = args["modulename"] reload = args["reload"] # if this module has previously been loaded, we try to reload it. if mod in sys.modules: _module = sys.modules[mod] _oldmodule = _module try: if "lyntin_import" in _module.__dict__: # if we're told not to reload it, we toss up a message and then # do nothing if not reload: exported.write_message( "load: module %s has already been loaded." % mod) return # if we loaded it via a lyntin_import mechanism and it has an # unload method, then we try calling that if "unload" in _module.__dict__: try: _module.unload() except: exported.write_traceback( "load: module %s didn't unload properly." % mod) del sys.modules[mod] exported.write_message("load: reloading %s." % mod) except: exported.write_traceback("load: had problems unloading %s." % mod) return else: _oldmodule = None # here's where we import the module try: _module = __import__(mod) _module = sys.modules[mod] if (_oldmodule and "reload" in _oldmodule.__dict__): try: _oldmodule.reload() except: exported.write_traceback( "load: had problems calling reload on %s." % mod) if ("load" in _module.__dict__): _module.load() _module.__dict__["lyntin_import"] = 1 exported.write_message("load successful.") if mod not in config.lyntinmodules: config.lyntinmodules.append(mod) except: exported.write_traceback("load: had problems with %s." % mod)
def run(self): """ While the connection hasn't been shut down, we spin through this loop retrieving data from the mud, """ from lyntin import exported try: data = '' while not self._shutdownflag: newdata = self._pollForData() if newdata: newdata = self._filterIncomingData(newdata) if newdata == "": continue last_index = 0 alldata = (data+newdata).replace("\r","") # incrementally walk through each line in the data, # adjusting last_index to the end of the previous match for (m) in self._line_regex.finditer(alldata): oneline = alldata[last_index:m.end()] last_index = m.end() self.handleData(oneline) # keep the remainder (empty if alldata ended with a delimiter) data = alldata[last_index:] elif newdata == '': # if we got back an empty string, then something's amiss # and we should dump them. if data: self.handleData(data) if self._shutdownflag == 0 and self._session: self._session.shutdown(()) break elif not self._good_prompts and data: # Now we have rest of the input which is neither delimited prompt # nor complete line, and we yet did not see this server # delimiting it's prompts with telnet GA or EOR option. # We'll handle these data because the socket read was timed out. self.handleData(data) data = '' except SystemExit: if self._session: self._session.shutdown(()) except: exported.write_traceback("socket exception") if self._session: self._session.shutdown(()) # if we hit this point, we want to shut down the socket try: self._sock.shutdown(2) except: pass try: self._sock.close() except: pass self._sock = None self._session = None # sometimes the mud will hose up with echo off--we want to kick it # on again. self._config.change("mudecho", "on") # output message so the user knows what happened. event.OutputEvent(message.Message("Lost connection to: %s\n" % self._host)).enqueue()
if data.find("#end") == 0: break except select.error, e: (errno, name) = e if errno == 4: exported.write_message("system exit: select.error.") event.ShutdownEvent().enqueue() return except SystemExit: exported.write_message("system exit: you'll be back...") event.ShutdownEvent().enqueue() except: exported.write_traceback() event.ShutdownEvent().enqueue() def write(self, args): """ Handles writing information from the mud and/or Lyntin to the user. """ msg = args["message"] if type(msg) == types.StringType: msg = message.Message(msg, message.LTDATA) line = msg.data ses = msg.session
break except select.error, e: (errno,name) = e if errno == 4: exported.write_message("system exit: select.error.") event.ShutdownEvent().enqueue() return except SystemExit: exported.write_message("system exit: you'll be back...") event.ShutdownEvent().enqueue() except: exported.write_traceback() event.ShutdownEvent().enqueue() def write(self, args): """ Handles writing information from the mud and/or Lyntin to the user. """ msg = args["message"] if type(msg) == types.StringType: msg = message.Message(msg, message.LTDATA) line = msg.data ses = msg.session
def python_cmd(ses, words, input): """ #@ allows you to execute arbitrary Python code inside of Lyntin. It will first look for a module named "lyntinuser" and execute the code inside that module's __dict__ environment. If no such module exists, it will execute the code inside modules.advanced . At present it can only handle one-line Python statements. examples: #@ print "hello" #@ print "\\n".join(exported.get_commands()) category: commands """ global execdictglobals, execdictlocals # NOTE: if we ever get to handling multiple-lines, we'll need # to change this function completely. try: if execdictlocals == None: execdictlocals = {} execdictlocals["session"] = ses execdictlocals["exported"] = exported my_usermodule = _get_user_module() if my_usermodule: dictglobals = my_usermodule.__dict__ else: if execdictglobals == None: execdictglobals = {} exported.write_error("No lyntinuser module loaded--executing with no context.") dictglobals = execdictglobals source = input[1:].lstrip() compiled = compile_command(source) # # XXX for one-liners only: # if not compiled: compiled = compile_command(source+"\n") old_stdout = sys.stdout old_stderr = sys.stderr old_stdin = sys.stdin sys_stdout = StringIO.StringIO() sys_stderr = StringIO.StringIO() sys_stdin = StringIO.StringIO() try: sys.stdout = sys_stdout sys.stderr = sys_stderr sys.stdin = sys_stdin exec compiled in dictglobals, execdictlocals finally: sys.stdout = old_stdout sys.stderr = old_stderr sys.stdin = old_stdin error = sys_stderr.getvalue() if error: exported.write_error(error) text = sys_stdout.getvalue() if text.endswith("\n"): text = text[:-1] if text: exported.write_message(text) except (OverflowError, SyntaxError, ValueError, NameError): import traceback exported.write_error("".join(traceback.format_exception_only( *(sys.exc_info()[:2]) ))) except: exported.write_traceback("@: error in raw python stuff.") exported.tally_error()
def main(defaultoptions={}): """ This parses the command line arguments and makes sure they're all valid, instantiates a ui, does some setup, spins off an engine thread, and goes into the ui's mainloop. @param defaultoptions: the boot options to use. we update the config.options dict with these options--this is the easiest way to override the ui, moduledir, datadir, et al from a Lyntin run script. @type defaultoptions: dict """ try: import sys, os, traceback, ConfigParser from lyntin import config, event, utils, exported from lyntin.ui import base import locale locale.setlocale(locale.LC_ALL, '') config.options.update(defaultoptions) # read through options and arguments optlist = utils.parse_args(sys.argv[1:]) for mem in optlist: if mem[0] == '--help': print constants.HELPTEXT sys.exit(0) elif mem[0] == '--version': print constants.VERSION sys.exit(0) elif mem[0] in ["--configuration", "-c"]: # ini files OVERRIDE the default options # they can provide multiple ini files, but each new # ini file will OVERRIDE the contents of the previous ini file # where the two files intersect. parser = ConfigParser.ConfigParser() parser.read([mem[1]]) newoptions = {} for s in parser.sections(): for o in parser.options(s): c = parser.get(s, o).split(",") if newoptions.has_key(o): newoptions[o] += c else: newoptions[o] = c config.options.update(newoptions) else: opt = mem[0] while opt.startswith("-"): opt = opt[1:] if len(opt) > 0: if config.options.has_key(opt): if type(config.options[opt]) is list: config.options[opt].append(mem[1]) else: config.options[opt] = mem[1] else: config.options[opt] = [mem[1]] for mem in ["datadir", "ui", "commandchar"]: if config.options.has_key(mem) and type(config.options[mem]) is list: config.options[mem] = config.options[mem][0] # if they haven't set the datadir via the command line, then # we go see if they have a HOME in their environment variables.... if not config.options["datadir"]: if os.environ.has_key("HOME"): config.options["datadir"] = os.environ["HOME"] config.options["datadir"] = utils.fixdir(config.options["datadir"]) def on_shutdown(): """ This gets called by the Python interpreter atexit. The reason we do shutdown stuff here is we're more likely to catch things here than we are to let everything cycle through the ShutdownEvent. This should probably get fixed up at some point in the future. """ if not hasattr(sys, 'frozen'): sys.stderr.write("goodbye.\n") #exported.hook_spam("shutdown_hook", {}) import atexit atexit.register(on_shutdown) # instantiate the engine Engine.instance = Engine() exported.myengine = Engine.instance Engine.instance._setupConfiguration() # instantiate the ui uiinstance = None try: uiname = str(config.options['ui']) modulename = uiname + "ui" uiinstance = base.get_ui(modulename) if not uiinstance: raise ValueError("No ui instance.") except Exception, e: print "Cannot start '%s': %s" % (uiname, e) traceback.print_exc() sys.exit(0) Engine.instance.setUI(uiinstance) # do some more silly initialization stuff # adds the .lyntinrc file to the readfile list if it exists. if config.options["datadir"]: lyntinrcfile = config.options["datadir"] + ".lyntinrc" if os.path.exists(lyntinrcfile): # we want the .lyntinrc file read in first, so then other # files can overwrite the contents therein config.options['readfile'].insert(0, lyntinrcfile) # import modules listed in modulesinit exported.write_message("Loading Lyntin modules.") try: import lyntin.modules lyntin.modules.load_modules() except: exported.write_traceback("Modules did not load correctly.") sys.exit(1) commandchar = Engine.instance._managers["config"].get("commandchar") # handle command files for mem in config.options['readfile']: exported.write_message("Reading in file " + mem) # we have to escape windows os separators because \ has a specific # meaning in the argparser mem = mem.replace("\\", "\\\\") exported.lyntin_command("%sread %s" % (commandchar, mem), internal=1) # spam the startup hook exported.hook_spam("startup_hook", {}) # we're done initialization! exported.write_message(constants.STARTUPTEXT) Engine.instance.writePrompt() engine_thread = Engine.instance.startthread("engine", Engine.instance.runengine) timer_thread = Engine.instance.startthread("timer", Engine.instance.runtimer) try: Engine.instance._ui.runui() finally: if not hasattr(sys, 'frozen'): sys.stderr.write("Shutting down...") event.ShutdownEvent().enqueue() engine_thread.join(10) timer_thread.join(10)
def main(defaultoptions={}): """ This parses the command line arguments and makes sure they're all valid, instantiates a ui, does some setup, spins off an engine thread, and goes into the ui's mainloop. @param defaultoptions: the boot options to use. we update the config.options dict with these options--this is the easiest way to override the ui, moduledir, datadir, et al from a Lyntin run script. @type defaultoptions: dict """ try: import sys, os, traceback, ConfigParser from lyntin import config, event, utils, exported from lyntin.ui import base import locale locale.setlocale(locale.LC_ALL, '') config.options.update(defaultoptions) # read through options and arguments optlist = utils.parse_args(sys.argv[1:]) for mem in optlist: if mem[0] == '--help': print constants.HELPTEXT sys.exit(0) elif mem[0] == '--version': print constants.VERSION sys.exit(0) elif mem[0] in ["--configuration", "-c"]: # ini files OVERRIDE the default options # they can provide multiple ini files, but each new # ini file will OVERRIDE the contents of the previous ini file # where the two files intersect. parser = ConfigParser.ConfigParser() parser.read([mem[1]]) newoptions = {} for s in parser.sections(): for o in parser.options(s): c = parser.get(s, o).split(",") if newoptions.has_key(o): newoptions[o] += c else: newoptions[o] = c config.options.update(newoptions) else: opt = mem[0] while opt.startswith("-"): opt = opt[1:] if len(opt) > 0: if config.options.has_key(opt): if type(config.options[opt]) is list: config.options[opt].append(mem[1]) else: config.options[opt] = mem[1] else: config.options[opt] = [mem[1]] for mem in ["datadir", "ui", "commandchar"]: if config.options.has_key(mem) and type( config.options[mem]) is list: config.options[mem] = config.options[mem][0] # if they haven't set the datadir via the command line, then # we go see if they have a HOME in their environment variables.... if not config.options["datadir"]: if os.environ.has_key("HOME"): config.options["datadir"] = os.environ["HOME"] config.options["datadir"] = utils.fixdir(config.options["datadir"]) def on_shutdown(): """ This gets called by the Python interpreter atexit. The reason we do shutdown stuff here is we're more likely to catch things here than we are to let everything cycle through the ShutdownEvent. This should probably get fixed up at some point in the future. """ sys.stderr.write("goodbye.\n") #exported.hook_spam("shutdown_hook", {}) import atexit atexit.register(on_shutdown) # instantiate the engine Engine.instance = Engine() exported.myengine = Engine.instance Engine.instance._setupConfiguration() # instantiate the ui uiinstance = None try: uiname = str(config.options['ui']) modulename = uiname + "ui" uiinstance = base.get_ui(modulename) if not uiinstance: raise ValueError("No ui instance.") except Exception, e: print "Cannot start '%s': %s" % (uiname, e) traceback.print_exc() sys.exit(0) Engine.instance.setUI(uiinstance) # do some more silly initialization stuff # adds the .lyntinrc file to the readfile list if it exists. if config.options["datadir"]: lyntinrcfile = config.options["datadir"] + ".lyntinrc" if os.path.exists(lyntinrcfile): # we want the .lyntinrc file read in first, so then other # files can overwrite the contents therein config.options['readfile'].insert(0, lyntinrcfile) # import modules listed in modulesinit exported.write_message("Loading Lyntin modules.") try: import modules.__init__ modules.__init__.load_modules() except: exported.write_traceback("Modules did not load correctly.") sys.exit(1) # spam the startup hook exported.hook_spam("startup_hook", {}) commandchar = Engine.instance._managers["config"].get("commandchar") # handle command files for mem in config.options['readfile']: exported.write_message("Reading in file " + mem) # we have to escape windows os separators because \ has a specific # meaning in the argparser mem = mem.replace("\\", "\\\\") exported.lyntin_command("%sread %s" % (commandchar, mem), internal=1) # we're done initialization! exported.write_message(constants.STARTUPTEXT) Engine.instance.writePrompt() engine_thread = Engine.instance.startthread("engine", Engine.instance.runengine) timer_thread = Engine.instance.startthread("timer", Engine.instance.runtimer) try: Engine.instance._ui.runui() finally: sys.stderr.write("Shutting down...") event.ShutdownEvent().enqueue() engine_thread.join(10) timer_thread.join(10)
def checkActions(self, text): """ Checks to see if text triggered any actions. Any resulting actions will get added as an InputEvent to the queue. @param text: the data coming from the mud to check for triggers @type text: string """ # FIXME - make sure this works even when lines are broken up. actionlist = self._actionlist if not actionlist: actionlist = filter(lambda x: not self._disabled.has_key(x[6]), self._actions.values()) actionlist.sort(key=lambda i: i[3]) self._actionlist = actionlist colorline = utils.filter_cm(text) nocolorline = ansi.filter_ansi(colorline) # go through all the lines in the data and see if we have # any matches for (action, actioncompiled, response, color, priority, onetime, tag) in actionlist: if color: match = actioncompiled.search(colorline) line = colorline else: match = actioncompiled.search(nocolorline) line = nocolorline if match: # for every match we figure out what the expanded response # is and add it as an InputEvent in the queue. the reason # we do a series of separate events rather than one big # event with ; separators is due to possible issues with # braces and such in malformed responses. # fill in values for all the variables in the match varvals = {} if match.lastindex is not None: if action.startswith('r['): for i in xrange(match.lastindex): varvals[str(i+1)] = match.group(i+1) else: # get variables from the action actionvars = get_ordered_vars(action) for i in xrange(len(actionvars)): varvals[actionvars[i]] = match.group(i+1) # add special variables varvals['a'] = line.replace(';', '_') # fill in response variables from those that # matched on the trigger response = utils.expand_vars(response, varvals) # event.InputEvent(response, internal=1, ses=self._ses).enqueue() try: exported.lyntin_command(response, internal=1, session=self._ses) except: exported.write_traceback() if onetime and self._actions.has_key(action): del self._actions[action] self._actionlist = None # invalidate the list
def action_cmd(ses, args, input): """ With no trigger, no action and no tag, prints all actions. With no trigger and no action, prints all actions with given tag. With a trigger and no action, prints actions that match the trigger statement. With a trigger and an action, creates an action. When data from the mud matches the trigger clause, the response will be executed. Trigger clauses can use anchors (^ and $) to anchor the text to the beginning and end of the line respectively. Triggers can also contain Lyntin pattern-variables which start with a % sign and have digits: %0, %1, %10... When Lyntin sees a pattern-variable in an action trigger, it tries to match any pattern against it, and saves any match it finds so you can use it in the response. See below for examples. Note: As a note, actions are matched via regular expressions. %1 gets translated to (.+?) and %_1 gets translated to (\S+?). The special variable "%a" means "the whole matched line". We handle regular expressions with a special r[ ... ] syntax. If you put an "i" or "I" after the ], then we'll ignorecase as well. The onetime argument can be set to true to have the action remove itself automatically after it is triggered. examples: #action {^You are hungry} {get bread bag;eat bread} #action {%0 gives you %5} {say thanks for the %5, %0!} #action {r[^%_1 tells\\s+you %2$]} {say %1 just told me %2} #action {r[sven dealt .+? to %1$]i} {say i just killed %1!} see also: unaction, enable, disable, atags category: commands """ trigger = args["trigger"] action = args["action"] color = args["color"] priority = args["priority"] onetime = args["onetime"] quiet = args["quiet"] tag = args["tag"] am = exported.get_manager("action") ad = am.getActionData(ses) # they typed '#action'--print out all the current actions if not action: data = ad.getInfo(trigger, tag) if not data: data = ["action: no actions defined."] message = "actions" if tag: message += " with tag={%s}" % tag data += ad.getDisabledInfo(tag) exported.write_message(message + "\n" + "\n".join(data), ses) return try: ad.addAction(trigger, action, color, priority, onetime, tag) if not quiet: exported.write_message("action: {%s} {%s} color={%d} priority={%d} tag={%s} added." % (trigger, action, color, priority, str(tag)), ses) except: exported.write_traceback("action: exception thrown.", ses)
def load_cmd(ses, args, input): """ Loads/reloads a module. When reloading, it looks for an "unload" function and executes it prior to reloading the module. After reloading/loading, it looks for a "load" function and executes it. Lyntin modules located in the modules package are safe to reload in-game. Lyntin core modules (engine, helpmanager, event...) are NOT safe to import in-game. examples: #load modules.action #load exportuser #load will look for the module on the sys.path. So if your module is not on the sys.path, you should first add the directory using #@: #@ import sys #@ sys.path.append("/directory/where/my/module/exists") Directories specified by the moduledir command-line argument are added to the sys.path upon Lyntin startup. category: commands """ mod = args["modulename"] reload = args["reload"] # if this module has previously been loaded, we try to reload it. if sys.modules.has_key(mod): _module = sys.modules[mod] _oldmodule = _module try: if _module.__dict__.has_key("lyntin_import"): # if we're told not to reload it, we toss up a message and then # do nothing if not reload: exported.write_message("load: module %s has already been loaded." % mod) return # if we loaded it via a lyntin_import mechanism and it has an # unload method, then we try calling that if _module.__dict__.has_key("unload"): try: _module.unload() except: exported.write_traceback("load: module %s didn't unload properly." % mod) del sys.modules[mod] exported.write_message("load: reloading %s." % mod) except: exported.write_traceback("load: had problems unloading %s." % mod) return else: _oldmodule = None # here's where we import the module try: _module = __import__( mod ) _module = sys.modules[mod] if (_oldmodule and _oldmodule.__dict__.has_key("reload")): try: _oldmodule.reload() except: exported.write_traceback("load: had problems calling reload on %s." % mod) if (_module.__dict__.has_key("load")): _module.load() _module.__dict__["lyntin_import"] = 1 exported.write_message("load successful.") if mod not in config.lyntinmodules: config.lyntinmodules.append(mod) except: exported.write_traceback("load: had problems with %s." % mod)
def session_cmd(ses, args, input): """ This command creates a connection to a specific mud. When you create a session, that session becomes the active Lyntin session. To create a session to 3k.org named "3k": #session 3k www.3k.org 5000 To create a session and initialize it with commands from a specific file: #session 3k www.3k.org 5000 /home/david/3k/3k.lyntin Then to create another session to another mud: #session eto gytje.pvv.unit.no 4000 Then if 3k was your active session, you could do things on the eto session by prepending your command with "#eto ": #eto say hello or switch to the eto session by typing just "#eto". category: commands """ name = args["sessionname"] host = args["host"] port = args["port"] filename = args["filename"] if not name and not host and (not port or port == -1): data = "Sessions available:\n" for mem in exported.get_active_sessions(): data += " %s: %r\n" % (mem.getName(), mem._socket) exported.write_message(data[:-1]) return if not name or not host or port == -1: exported.write_error("syntax: #session <sesname> <host> <port>") return if name.isdigit(): exported.write_error("session: session name cannot be all numbers.") return e = exported.myengine ses = e.getSession(name) if ses != None: preexistingsession = 1 else: preexistingsession = 0 if preexistingsession == 1 and ses.isConnected(): exported.write_error("session: session of that name already exists.") return try: # create and register a session for this connection.... if ses == None: ses = e.createSession(name) sock = net.SocketCommunicator(e, ses, host, port) ses.setSocketCommunicator(sock) ses._host = host ses._port = port e.changeSession(name) # connect to the mud... # this might take a while--we block here until this is done. sock.connect(host, port, name) # start the network thread e.startthread("network", sock.run) except: exported.write_traceback("session: had problems creating the session.") ses.setSocketCommunicator(None) if preexistingsession == 0: try: e.unregisterSession(ses) except: pass try: e.closeSession(name) except: pass # populate the session using the specified file if filename: read_cmd(ses, args, '')
def load_modules(): """ Magically dynamically loads all the modules in the modules package. This is truly a semi-magic function. """ # handle modules.* index = __file__.rfind(os.sep) if index == -1: path = "." + os.sep else: path = __file__[:index] _module_list = glob.glob(os.path.join(path, "*.py")) _module_list.sort() for mem in _module_list: # we skip over all files that start with a _ # this allows hackers to be working on a module and not have # it die every time. mem2 = get_module_name(mem) if mem2.startswith("_"): continue try: name = "lyntin.modules." + mem2 _module = __import__(name) _module = sys.modules[name] if "load" in _module.__dict__: _module.load() _module.__dict__["lyntin_import"] = 1 config.lyntinmodules.append(name) except: exported.write_traceback("Module '%s' refuses to load." % name) # handle modules found in the moduledir moduledirlist = config.options["moduledir"] if moduledirlist: for moduledir in moduledirlist: # grab the contents of the moduledir directory _module_list = glob.glob(os.path.join(moduledir, "*.py")) # toss the moduledir in the sys.path sys.path.append(moduledir) # and toss all the contents of the directory in our _module_list for mem in _module_list: mem2 = get_module_name(mem) if mem2.startswith("_"): continue try: _module = __import__(mem2) if "load" in _module.__dict__: _module.load() test_for_conflicts(mem, _module) _module.__dict__["lyntin_import"] = 1 config.lyntinmodules.append(mem2) except: exported.write_traceback("Module '%s' refuses to load." % mem)
def action_cmd(ses, args, input): """ With no trigger, no action and no tag, prints all actions. With no trigger and no action, prints all actions with given tag. With a trigger and no action, prints actions that match the trigger statement. With a trigger and an action, creates an action. When data from the mud matches the trigger clause, the response will be executed. Trigger clauses can use anchors (^ and $) to anchor the text to the beginning and end of the line respectively. Triggers can also contain Lyntin pattern-variables which start with a % sign and have digits: %0, %1, %10... When Lyntin sees a pattern-variable in an action trigger, it tries to match any pattern against it, and saves any match it finds so you can use it in the response. See below for examples. Note: As a note, actions are matched via regular expressions. %1 gets translated to (.+?) and %_1 gets translated to (\S+?). The special variable "%a" means "the whole matched line". We handle regular expressions with a special r[ ... ] syntax. If you put an "i" or "I" after the ], then we'll ignorecase as well. The onetime argument can be set to true to have the action remove itself automatically after it is triggered. examples: #action {^You are hungry} {get bread bag;eat bread} #action {%0 gives you %5} {say thanks for the %5, %0!} #action {r[^%_1 tells\\s+you %2$]} {say %1 just told me %2} #action {r[sven dealt .+? to %1$]i} {say i just killed %1!} see also: unaction, enable, disable, atags category: commands """ trigger = args["trigger"] action = args["action"] color = args["color"] priority = args["priority"] onetime = args["onetime"] quiet = args["quiet"] tag = args["tag"] am = exported.get_manager("action") ad = am.getActionData(ses) # they typed '#action'--print out all the current actions if not action: data = ad.getInfo(trigger, tag) if not data: data = ["action: no actions defined."] message = "actions" if tag: message += " with tag={%s}" % tag data += ad.getDisabledInfo(tag) exported.write_message(message + "\n" + "\n".join(data), ses) return try: ad.addAction(trigger, action, color, priority, onetime, tag) if not quiet: exported.write_message( "action: {%s} {%s} color={%d} priority={%d} tag={%s} added." % (trigger, action, color, priority, str(tag)), ses) except: exported.write_traceback("action: exception thrown.", ses)