X-Git-Url: https://mudpy.org/gitweb?p=mudpy.git;a=blobdiff_plain;f=lib%2Fmudpy%2Fmisc.py;fp=mudpy.py;h=033699c20ac8379e475e904565a70d21d65720a5;hp=6ec7c5b25f7cdbd4d104a39659c253c27d23f0b7;hb=8bf6b7a787510321b75e477079ebf70ac150d853;hpb=518f0aad04432b4bf0405520e659db3d6ba00a76 diff --git a/mudpy.py b/lib/mudpy/misc.py similarity index 90% rename from mudpy.py rename to lib/mudpy/misc.py index 6ec7c5b..033699c 100644 --- a/mudpy.py +++ b/lib/mudpy/misc.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -u"""Core objects for the mudpy engine.""" +u"""Miscellaneous functions for the mudpy engine.""" # Copyright (c) 2004-2009 Jeremy Stanley . Permission # to use, copy, modify, and distribute this software is granted under @@ -102,6 +102,9 @@ class Element: if default is None: default = u"" if self.origin.data.has_option(self.key, facet): raw_data = self.origin.data.get(self.key, facet) + if raw_data.startswith(u"u\"") or raw_data.startswith(u"u'"): + raw_data = raw_data[1:] + raw_data.strip(u"\"'") if type(raw_data) == str: return unicode(raw_data, "utf-8") else: return raw_data elif self.has_facet(u"inherit"): @@ -356,34 +359,58 @@ class DataFile: if os.access(self.filename, os.R_OK): self.data.read(self.filename) if not hasattr(self.universe, u"files"): self.universe.files = {} self.universe.files[self.filename] = self + includes = [] if self.data.has_option(u"__control__", u"include_files"): - includes = makelist(self.data.get(u"__control__", u"include_files")) - else: includes = [] + for included in makelist( + self.data.get(u"__control__", u"include_files") + ): + included = find_file( + included, + relative=self.filename, + universe=self.universe + ) + if included not in includes: includes.append(included) + if self.data.has_option(u"__control__", u"include_dirs"): + for included in [ os.path.join(x, u"__init__.mpy") for x in makelist( + self.data.get(u"__control__", u"include_dirs") + ) ]: + included = find_file( + included, + relative=self.filename, + universe=self.universe + ) + if included not in includes: includes.append(included) if self.data.has_option(u"__control__", u"default_files"): origins = makedict(self.data.get(u"__control__", u"default_files")) for key in origins.keys(): - if not os.path.isabs(origins[key]): - origins[key] = os.path.join( - os.path.dirname(self.filename), origins[key] - ) - if not origins[key] in includes: includes.append(origins[key]) + origins[key] = find_file( + origins[key], + relative=self.filename, + universe=self.universe + ) + if origins[key] not in includes: includes.append(origins[key]) self.universe.default_origins[key] = origins[key] - if not key in self.universe.categories: + if key not in self.universe.categories: self.universe.categories[key] = {} if self.data.has_option(u"__control__", u"private_files"): for item in makelist(self.data.get(u"__control__", u"private_files")): - if not item in includes: includes.append(item) - if not item in self.universe.private_files: - if not os.path.isabs(item): - item = os.path.join(os.path.dirname(self.filename), item) + item = find_file( + item, + relative=self.filename, + universe=self.universe + ) + if item not in includes: includes.append(item) + if item not in self.universe.private_files: self.universe.private_files.append(item) for section in self.data.sections(): if section != u"__control__": Element(section, self.universe, self.filename) for include_file in includes: if not os.path.isabs(include_file): - include_file = os.path.join( - os.path.dirname(self.filename), include_file + include_file = find_file( + include_file, + relative=self.filename, + universe=self.universe ) if include_file not in self.universe.files or not self.universe.files[ include_file @@ -567,8 +594,22 @@ class Universe: u"""Create and open the listening socket.""" import socket - # create a new ipv4 stream-type socket object - self.listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # need to know the local address and port number for the listener + host = self.categories[u"internal"][u"network"].get(u"host") + port = self.categories[u"internal"][u"network"].getint(u"port") + + # if no host was specified, bind to all local addresses (preferring ipv6) + if not host: + if socket.has_ipv6: host = u"::" + else: host = u"0.0.0.0" + + # figure out if this is ipv4 or v6 + family = socket.getaddrinfo(host, port)[0][0] + if family is socket.AF_INET6 and not socket.has_ipv6: + log(u"No support for IPv6 address %s (use IPv4 instead)." % host) + + # create a new stream-type socket object + self.listening_socket = socket.socket(family, 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 @@ -578,8 +619,6 @@ class Universe: ) # bind the socket to to our desired server ipa and port - host = self.categories[u"internal"][u"network"].get(u"host") - port = self.categories[u"internal"][u"network"].getint(u"port") self.listening_socket.bind((host, port)) # disable blocking so we can proceed whether or not we can @@ -590,7 +629,6 @@ class Universe: self.listening_socket.listen(1) # note that we're now ready for user connections - if not host: host = u"0.0.0.0" log( u"Listening for Telnet connections on: " + host + u":" + unicode(port) ) @@ -659,7 +697,7 @@ class User: logline += self.account.get(u"name") else: logline += u"an unknown user" - logline += u" after idling too long in a " + self.state + u" state." + logline += u" after idling too long in the " + self.state + u" state." log(logline, 2) self.state = u"disconnecting" self.menu_seen = False @@ -920,8 +958,8 @@ class User: account = self.account.get(u"name") else: account = u"an unknown user" log( - u"Sending to " + account \ - + u" raised an exception (broken pipe?)." + u"Sending to %s raised an exception (broken pipe?)." % account, + 7 ) pass @@ -1200,6 +1238,7 @@ class User: def makelist(value): u"""Turn string into list type.""" if value[0] + value[-1] == u"[]": return eval(value) + elif value[0] + value[-1] == u"\"\"": return [ value[1:-1] ] else: return [ value ] def makedict(value): @@ -1551,7 +1590,7 @@ def replace_macros(user, text, is_input=False): # this is how we handle local file inclusion (dangerous!) elif macro.startswith(u"inc:"): - incfile = os.path.join(universe.startdir, macro[4:]) + incfile = find_file(macro[4:], universe=universe) if os.path.exists(incfile): incfd = codecs.open(incfile, u"r", u"utf-8") replacement = u"" @@ -2210,15 +2249,21 @@ def command_say(actor, parameters): u"""Speak to others in the same room.""" import unicodedata - # check for replacement macros - if replace_macros(actor.owner, parameters, True) != parameters: - actor.send(u"You cannot speak $_(replacement macros).") + # check for replacement macros and escape them + parameters = escape_macros(parameters) - # the user entered a message - elif parameters: + # if the message is wrapped in quotes, remove them and leave contents intact + if parameters.startswith(u"\"") and parameters.endswith(u"\""): + message = parameters[1:-1] + literal = True - # get rid of quote marks on the ends of the message + # otherwise, get rid of stray quote marks on the ends of the message + else: message = parameters.strip(u"\"'`") + literal = False + + # the user entered a message + if message: # match the punctuation used, if any, to an action actions = universe.categories[u"internal"][u"language"].getdict( @@ -2229,36 +2274,44 @@ def command_say(actor, parameters): ) action = u"" for mark in actions.keys(): - if message.endswith(mark): + if not literal and message.endswith(mark): action = actions[mark] break # add punctuation if needed if not action: action = actions[default_punctuation] - if message and not unicodedata.category(message[-1]) == u"Po": + if message and not ( + literal or unicodedata.category(message[-1]) == u"Po" + ): message += default_punctuation - # decapitalize the first letter to improve matching - message = message[0].lower() + message[1:] - - # iterate over all words in message, replacing typos - typos = universe.categories[u"internal"][u"language"].getdict(u"typos") - words = message.split() - for index in range(len(words)): - word = words[index] - while unicodedata.category(word[0]) == u"Po": - word = word[1:] - while unicodedata.category(word[-1]) == u"Po": - word = word[:-1] - if word in typos.keys(): - words[index] = words[index].replace(word, typos[word]) - message = u" ".join(words) - - # capitalize the first letter - message = message[0].upper() + message[1:] - - # tell the room + # failsafe checks to avoid unwanted reformatting and null strings + if message and not literal: + + # decapitalize the first letter to improve matching + message = message[0].lower() + message[1:] + + # iterate over all words in message, replacing typos + typos = universe.categories[u"internal"][u"language"].getdict( + u"typos" + ) + words = message.split() + for index in range(len(words)): + word = words[index] + while unicodedata.category(word[0]) == u"Po": + word = word[1:] + while unicodedata.category(word[-1]) == u"Po": + word = word[:-1] + if word in typos.keys(): + words[index] = words[index].replace(word, typos[word]) + message = u" ".join(words) + + # capitalize the first letter + message = message[0].upper() + message[1:] + + # tell the room + if message: actor.echo_to_location( actor.get(u"name") + u" " + action + u"s, \"" + message + u"\"" ) @@ -2484,6 +2537,128 @@ def command_error(actor, input_data): # send the error message actor.send(message) +def find_file( + file_name=None, + root_path=None, + search_path=None, + default_dir=None, + relative=None, + universe=None +): + u"""Return an absolute file path based on configuration.""" + import os, os.path, sys + + # make sure to get rid of any surrounding quotes first thing + if file_name: file_name = file_name.strip(u"\"'") + + # this is all unnecessary if it's already absolute + if file_name and os.path.isabs(file_name): + return os.path.realpath(file_name) + + # when no file name is specified, look for .conf + elif not file_name: file_name = os.path.basename( sys.argv[0] ) + u".conf" + + # if a universe was provided, try to get some defaults from there + if universe: + + if hasattr( + universe, + u"contents" + ) and u"internal:storage" in universe.contents: + storage = universe.categories[u"internal"][u"storage"] + if not root_path: root_path = storage.get(u"root_path").strip("\"'") + if not search_path: search_path = storage.getlist(u"search_path") + if not default_dir: default_dir = storage.get(u"default_dir").strip("\"'") + + # if there's only one file loaded, try to work around a chicken 1: conffile = sys.argv[1] else: conffile = u"" + + # the big bang + global universe universe = Universe(conffile, True) -elif universe.reload_flag: - universe = universe.new() - reload_data() + # log an initial message + log(u"Started mudpy with command line: " + u" ".join(sys.argv)) + + # fork and disassociate + daemonize(universe) + + # override the default exception handler so we get logging first thing + override_excepthook() + + # set up custom signal handlers + assign_sighook() + + # make the pidfile + create_pidfile(universe) + + # pass the initialized universe back + return universe + +def finish(): + """This contains functions to be performed when shutting down the engine.""" + + # the loop has terminated, so save persistent data + universe.save() + + # log a final message + log(u"Shutting down now.") + + # get rid of the pidfile + remove_pidfile(universe)