Gave a more informative error message when reading non-UTF-8 batchfiles containing international symbols (issue97) as well as refactored the processors a bit further. Fixed some minor typographic details in some other commands.

This commit is contained in:
Griatch 2010-09-04 07:55:25 +00:00
parent 76edd254b0
commit 933e29afee
6 changed files with 115 additions and 73 deletions

View file

@ -29,8 +29,24 @@ from src.commands.cmdset import CmdSet
HEADER_WIDTH = 70
UTF8_ERROR = \
"""
{rDecode error in '%s'.{n
This file contains non-ascii character(s). This is common if you
wrote some input in a language that has more letters and special
symbols than English; such as accents or umlauts. This is usually
fine and fully supported! But for Evennia to know how to decode such
characters in a universal way, the batchfile must be saved with the
international 'UTF-8' encoding. This file is not.
Please re-save the batchfile with the UTF-8 encoding (refer to the
documentation of your text editor on how to do this, or switch to a
better featured one) and try again.
The (first) error was found with a character on line %s in the file.
"""
#global defines for storage
#------------------------------------------------------------
# Helper functions
@ -192,9 +208,15 @@ class CmdBatchCommands(MuxCommand):
return
python_path = self.args
#parse indata file
commands = BATCHCMD.parse_file(python_path)
#parse indata file
try:
commands = BATCHCMD.parse_file(python_path)
except UnicodeDecodeError, err:
lnum = err.linenum
caller.msg(UTF8_ERROR % (python_path, lnum))
return
if not commands:
string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)."
@ -271,7 +293,13 @@ class CmdBatchCode(MuxCommand):
python_path = self.args
#parse indata file
codes = BATCHCODE.parse_file(python_path)
try:
codes = BATCHCODE.parse_file(python_path)
except UnicodeDecodeError, err:
lnum = err.linenum
caller.msg(UTF8_ERROR % (python_path, lnum))
return
if not codes:
string = "'%s' not found.\nYou have to supply the python path "
string += "of the file relative to \nyour batch-file directory (%s)."

View file

@ -491,11 +491,11 @@ class CmdSay(MuxCommand):
speech = caller.location.at_say(caller, speech)
# Feedback for the object doing the talking.
caller.msg("You say, '%s'" % speech)
caller.msg('You say, "%s{n"' % speech)
# Build the string to emit to neighbors.
emit_string = "{c%s{n says, '%s'" % (caller.name,
speech)
emit_string = '{c%s{n says, "%s{n"' % (caller.name,
speech)
caller.location.msg_contents(emit_string,
exclude=caller)

View file

@ -205,7 +205,6 @@ class CmdSetObjAlias(MuxCommand):
obj.aliases = aliases
caller.msg("Aliases for '%s' are now set to %s." % (obj.name, aliases))
class CmdName(ObjManipCommand):
"""
cname - change the name and/or aliases of an object

View file

@ -48,9 +48,9 @@ know you want to!
# Now let's place the button where it belongs (let's say limbo #2 is
# the evil lair in our example)
@teleport #2
teleport #2
#... and drop it (remember, this comment ends input to @teleport, so don't
#forget it!) The very last command in the file need not be ended with #.
drop button
drop button

View file

@ -96,9 +96,9 @@ Code blocks are separated by python comments starting with special code words.
#HEADER - this denotes commands global to the entire file, such as
import statements and global variables. They will
automatically be made available for each block. Observe
automatically be pasted at the top of all code blocks. Observe
that changes to these variables made in one block is not
preserved between blocks!)
preserved between blocks!
#CODE [objname, objname, ...] - This designates a code block that will be executed like a
stand-alone piece of code together with any #HEADER
defined. <objname>s mark the (variable-)names of objects created in the code,
@ -138,16 +138,71 @@ script = create.create_script()
"""
import re
import codecs
from traceback import format_exc
from django.conf import settings
from django.core.management import setup_environ
from src.utils import logger
from src.utils import utils
#from src.commands.cmdset import CmdSet
#from src.scripts.scripts import Script
from game import settings as settings_module
from django.core.management import setup_environ
from traceback import format_exc
#------------------------------------------------------------
# Helper function
#------------------------------------------------------------
def read_batchfile(pythonpath, file_ending='.py', file_encoding='utf-8'):
"""
This reads the contents of a batch-file.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py.
file_ending specify which batchfile ending should be
assumed (.ev or .py).
"""
# open the file
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath, file_ending)
try:
# we read the file directly into unicode.
fobj = codecs.open(abspath, 'r', encoding=file_encoding)
except IOError:
# try again without the appended file ending
abspath2 = utils.pypath_to_realpath(pythonpath, None)
try:
fobj = codecs.open(abspath, 'r', encoding=file_encoding)
except IOError:
string = "Could not open batchfile '%s', nor '%s'."
logger.log_errmsg(string % (abspath2, abspath))
return None
# We have successfully found and opened the file. Now actually
# try to decode it using the given protocol.
try:
lines = fobj.readlines()
except UnicodeDecodeError:
# give the line of failure
fobj.seek(0)
try:
lnum = 0
for lnum, line in enumerate(fobj):
pass
except UnicodeDecodeError, err:
# lnum starts from 0, so we add +1 line,
# besides the faulty line is never read
# so we add another 1 (thus +2) to get
# the actual line number seen in an editor.
err.linenum = lnum + 2
raise err
fobj.close()
return lines
#------------------------------------------------------------
#
# Batch-command processor
@ -158,29 +213,7 @@ class BatchCommandProcessor(object):
"""
This class implements a batch-command processor.
"""
def read_file(self, pythonpath):
"""
This reads the contents of a batch-command file.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath, 'ev')
try:
fobj = open(abspath)
except IOError:
logger.log_errmsg("Could not open path '%s'." % abspath)
return None
lines = fobj.readlines()
fobj.close()
return lines
"""
def parse_file(self, pythonpath):
"""
This parses the lines of a batchfile according to the following
@ -212,7 +245,9 @@ class BatchCommandProcessor(object):
return "empty"
#read the indata, if possible.
lines = self.read_file(pythonpath)
lines = read_batchfile(pythonpath, file_ending='.ev')
#line = utils.to_unicode(line)
if not lines:
return None
@ -225,6 +260,7 @@ class BatchCommandProcessor(object):
#parse all command definitions into a list.
for line in lines:
typ = identify_line(line)
if typ == "commanddef":
curr_cmd += line
@ -258,28 +294,6 @@ class BatchCodeProcessor(object):
"""
def read_file(self, pythonpath):
"""
This reads the contents of batchfile.
Filename is considered to be the name of the batch file
relative the directory specified in settings.py
"""
if pythonpath and not (pythonpath.startswith('src.') or
pythonpath.startswith('game.')):
pythonpath = "%s.%s" % (settings.BASE_BATCHPROCESS_PATH,
pythonpath)
abspath = utils.pypath_to_realpath(pythonpath, 'py')
try:
fobj = open(abspath)
except IOError:
logger.log_errmsg("Could not open path '%s'." % abspath)
return None
lines = fobj.readlines()
fobj.close()
return lines
def parse_file(self, pythonpath):
"""
This parses the lines of a batchfile according to the following
@ -325,7 +339,7 @@ class BatchCodeProcessor(object):
# read indata
lines = self.read_file(pythonpath)
lines = read_batchfile(pythonpath, file_ending='.py')
if not lines:
return None

View file

@ -149,11 +149,11 @@ def get_evennia_version():
except IOError:
return "Unknown version"
def pypath_to_realpath(python_path, file_ending='py'):
def pypath_to_realpath(python_path, file_ending='.py'):
"""
Converts a path on dot python form (e.g. src.objects.models)
to a system path (src/objects/models.py). Calculates all
paths starting from the evennia main directory.
Converts a path on dot python form (e.g. 'src.objects.models') to
a system path (src/objects/models.py). Calculates all paths as
absoulte paths starting from the evennia main directory.
"""
pathsplit = python_path.strip().split('.')
if not pathsplit:
@ -161,14 +161,15 @@ def pypath_to_realpath(python_path, file_ending='py'):
path = settings.BASE_PATH
for directory in pathsplit:
path = os.path.join(path, directory)
return "%s.%s" % (path, file_ending)
if file_ending:
return "%s%s" % (path, file_ending)
return path
def dbref(dbref):
"""
Converts/checks if input is a valid dbref
Valid forms of dbref (database reference number)
are either a string '#N' or an integer N.
Output is the integer part.
Converts/checks if input is a valid dbref Valid forms of dbref
(database reference number) are either a string '#N' or
an integer N. Output is the integer part.
"""
if type(dbref) == str:
dbref = dbref.lstrip('#')