import re from const_commands import * _imports = ["commandlib_user", "commandlib_admin", "lib_commandparser"] _parents = [] _version = 1 lastError = None #A note on command prefixes: # No prefix - Used for IC commands. # @command - Used for OOC commands. # $command - Used for building commands. # *command - Used for GM commands. # %command - Used for administrative commands. #Parse type definitions. #Grabs all text following a command. def Parse_rawtext(avatar, userText, args): return (True, "", userText) #Grabs all text following a command, as long as its length is greater than 0. def Parse_sometext(avatar, userText, args): if len(userText) > 0: return (True, "", userText) else: return (False, "Must include text after command.", None) numRe = re.compile("^[0-9]+") #Grabs a single base10 integer. Returns in integer form. def Parse_num(avatar, userText, args): matchObj = numRe.match(userText) if matchObj != None: return (True, userText[matchObj.end():], int(userText[:matchObj.end()])) else: return (False, "\"%s\" is not a number." % userText, None) #Grabs a valid object type. #Returns the objtype string. def Parse_objtype(avatar, userText, args): splitList = userText.split(" ", 1) if len(splitList) > 1: remainder = userText[len(splitList[0]):] else: remainder = "" objType = splitList[0] if mudWorld.objIndex.has_key(objType): return (True, remainder, objType) else: return (False, "\"%s\" is not a valid object type." % objType, None) parseTypes = { "raw_text":Parse_rawtext, "some_text":Parse_sometext, "num":Parse_num, "object_type":Parse_objtype } welcome = "\r\n\r\nWelcome! Type @commands for a list of commands.\r\n" #[priority, word or prefix or regexp, textMatch, commandMode, caseSensitive, commandFunc, permissions] def EmptyParser(avatar, message): pass def TakeControl(avatar): avatar.usePrompt = True avatar.commandModules = [commandlib_user, commandlib_admin] avatar.Send(welcome) avatar.handler = (lib_commandparser, "CommandHandler") def HasPermissions(account, permissions): for permission in permissions.keys(): if not(account.permissions.has_key(permission)): return False elif account.permissions[permission] < permissions[permission]: return False return True def FixCase(message, caseSensitive): if caseSensitive is False: return message.lower() else: return message def CommandHandler(avatar, message): if message == "": return None commands = GenerateCommands(avatar) #List of commands in format (command, command trigger, text of command). #For RegExp's, command trigger is actually the match object. matchCommands = [] #We have to do three different kinds of check: # Prefix: commands such as 'Hi fellows! # RegExp: complex commands that need their prefix parsed as a re. Note, the handler assumes these are # pre-compiled. # Word: command such as say Hi fellows! #This will find exact matches. Usually the user is going to be typing the exact command, and we want that to take #precedence. #First, prefixes. matchCommands.extend([ (command, message[:len(command[1])], message[len(command[1]):]) for command in commands if ((command[3] == COMMANDMODE_PREFIX) and (FixCase(message, command[4]).startswith(FixCase(command[1], command[4]))) and (HasPermissions(avatar, command[6]))) ]) #Next, regexp's. These are slow, but there shouldn't be many of them, so we can just do a for loop. reCommands = [command for command in commands if ((command[3] == COMMANDMODE_REGEXP) and (HasPermissions(avatar, command[6])))] for reCommand in reCommands: matchObj = reCommand[1].match(message) if not(matchObj is None): matchCommands.append((reCommand, matchObj, message[matchObj.end():])) #Last, normal commands. messageSplit = message.split(" ", 1) commandTrig = messageSplit[0] #If there's no space, add some empty arguments. if len(messageSplit) == 1: commandArgs = "" else: commandArgs = messageSplit[1] matchCommands.extend([ (command, commandTrig, commandArgs) for command in commands if ((command[3] == COMMANDMODE_WORD) and (FixCase(commandTrig, command[4]) == FixCase(command[1], command[4])) and (HasPermissions(avatar, command[6]))) ]) if len(matchCommands) > 0: matchCommands.sort() matchCommands.reverse() if HandleCommands(avatar, matchCommands): return None #If we're at this point, it means we didn't get an exact match. #We can only do inexact matches with Word type commands. matchCommands = [(command, commandTrig, commandArgs) for command in commands if ((command[3] == COMMANDMODE_WORD) and (FixCase(command[1], command[4]).startswith(FixCase(commandTrig, command[4]))) and (HasPermissions(avatar, command[6]))) ] if len(matchCommands) > 0: matchCommands.sort() matchCommands.reverse() if HandleCommands(avatar, matchCommands): return None #If we're at this point, it means no command handled things. if lastError is None: avatar.Send("I don't understand what you typed.\r\n") else: avatar.Send("Parse error: %s\r\n" % lastError) globals()["lastError"] = None #Attempts to parse the commands. def HandleCommands(avatar, commandTuples): for commandTuple in commandTuples: parseResult = MatchCommandText(avatar, commandTuple[2], commandTuple[0][2]) if parseResult[0]: execResult = commandTuple[0][5](avatar, commandTuple[1], parseResult[1]) if execResult: return True return False #Recursively attempts to match command text. def MatchCommandText(avatar, userText, commandText): #All parse structures are encapsulated in two % signs. #Two % signs back to back escapes a single % sign. #Parse structures can except arguments in <> brackets; these arguments are passed as a string to the #parse function. #For example, the matchText string "%bunnynumber%:die %deathmethod<horrible>%" would probably match: # 1:die cancer # 3:die ebola # 14:die pokemon #Depending on how the %bunnynumber% and %deathmethod% parse structures handle things. MODE_ANCHOR, MODE_PARSE = range(2) curMode = MODE_ANCHOR matchDataList = [] #First, we split everything along % lines. commandSplit = commandText.split("%") #Now, we alternate using anchors and parse structures over the commandSplit. for subText in commandSplit: if curMode is MODE_ANCHOR: if userText.startswith(subText): userText = userText[len(subText):] curMode = MODE_PARSE continue else: #Couldn't find the anchor. return (False, None) else: #First, check and see if this is an escaped percentage sign. if subText == "": #Since it is, we parse it as an anchor. if userText.startswith("%"): userText = userText[1:] continue #It's not, so lets check the parse table for it. #We have to separate out an arguments string(if any). parseSplit = subText.split("<", 1) parseName = parseSplit[0] if len(parseSplit) > 1: #Remove ending >. If its not there, this'll behave screwy, so double check your argument thingies. argString = parseSplit[1][:-1] else: argString = "" #Find if the parse function is defined. if not(parseTypes.has_key(parseName)): mudWorld.loggers["mud"].error("Invalid parse type [%s]." % parseName) return (False, None) #Now, LETS PARSE! (success, userText, matchData) = parseTypes[parseName](avatar, userText, argString) if not(success): if globals()["lastError"] == None: globals()["lastError"] = userText return (False, None) matchDataList.append(matchData) curMode = MODE_ANCHOR #Return False if all the data hasn't been usen. if len(userText.strip()): if globals()["lastError"] == None: globals()["lastError"] = "Invalid arguments following command." return (False, None) return (True, matchDataList) def GenerateCommands(avatar): commands = [] commands.extend(avatar.ExportCommands()) return commands