maps/python/dialog/dialog_check.py

173 lines
7.3 KiB
Python

# -*- coding: utf-8 -*-
# dialog_check.py
# This script is *not* intended to be used by the crossfire plugin, it is
# designed to verify the correctness of a dialog script -independantly of the crossfire server.
# Typically you will want to run this script against a single file, the one that you specify in
# the event_say, but if you want to check all of the .msg files in the map distribution, then
# you can run something like:
# for i in $(grep -h name.*msg ../../ -r | cut -d " " -f 2 | sort | uniq); do echo $i && python dialog_check.py "../../"$i; done
# from maps/python/dialog
import cjson
import sys
import os
import re
# There is an upper limit on the maximum message length the server can cope with, exceeding it throws out a warning.
MAX_MSG_LENGTH = 2048
def checkactionfile(filename, condition):
args = condition[1:]
checkstatus = 0
if not os.path.isfile(filename):
print "Error: No script to support action: ", condition[0], "Expected: ", filename
return False
else:
actf = open(filename,"r")
argnum = 0
for actline in actf.readlines():
if checkstatus == 0:
if actline.find("## DIALOGCHECK") != -1:
checkstatus = 1
elif checkstatus == 1:
if actline.find("## ENDDIALOGCHECK") != -1:
checkstatus = 2
elif actline.find("## MINARGS") != -1:
num = actline.split()[2]
if not num.isdigit():
print "ERROR: Action definition for script ", filename, " MINARGS not defined"
return False
else:
if len(args)<int(num):
print "ERROR: Insufficiant options passed to script ", filename, "expected ", int(num), " recieved ", len(args)
return False
elif actline.find("## MAXARGS") != -1:
num = actline.split()[2]
if not num.isdigit():
print "ERROR: Action definition for script ", filename, " MAXARGS not defined"
return False
elif int(num) == 0:
# zero means there is no upper limit to the number of arguments
pass
else:
if len(args)>int(num):
print "ERROR: Too many options passed to script ", filename, "expected ", int(num), " recieved ", len(args)
return False
elif actline.find("##") != 1:
# This is a regexp for one of the arguments
if argnum < len(args):
argmatch = re.compile(actline.split()[1])
if not argmatch.match(args[argnum]):
print "ERROR: Argument ", argnum+2, "of rule: ", condition, " doesn't match regexp ", actline.split()[1]
return False
argnum+=1
if checkstatus != 2:
print "Warning: No dialogcheck block for file ", filename, " Unable to check condition ", condition
return True
return True
def checkdialoguefile(msgfile, location):
rulenumber = 0
errors = 0
warnings = 0
extrafiles =[]
params = {}
try:
f = open(msgfile,"rb")
except:
print "ERROR: Can't open file, ", msgfile
errors +=1
else:
try:
params = cjson.decode(f.read())
except:
print "ERROR: Failed to parse file, ", msgfile, "not a valid json file"
errors +=1
f.close()
if "location" in params:
if not location == '':
print "Warning: Location defined multiple times in included files"
warnings+=1
location = params["location"]
if location =='':
print "Warning: no location was specified"
warnings +=1
rulenumber =0
for jsonRule in params["rules"]:
rulenumber +=1
include = 0
msg=0
post=0
match=0
pre =0
for action in jsonRule:
if action == "pre":
pre+=1
for condition in jsonRule["pre"]:
action = condition[0]
path = os.path.join("pre/", action + ".py")
if not checkactionfile(path, condition):
print "ERROR: verification of action file ", path, " failed for rule ", rulenumber, " condition ", condition
errors+=1
elif action == "msg":
for line in jsonRule["msg"]:
if len(line) > MAX_MSG_LENGTH:
# We won't print out the entire line, because it's very
# very long, but we'll print the first 70 characters in order to help identify it
print "WARNING: A Dialog Line for rule", rulenumber, "is too long. (", len(line), "characters, maximum is", MAX_MSG_LENGTH, ") \nLine begins:", line[:70]
warnings+=1
msg+=1
elif action == "post":
post+=1
for condition in jsonRule["post"]:
action = condition[0]
path = os.path.join("post/", action + ".py")
if not checkactionfile(path, condition):
print "ERROR: verification of action file ", path, " failed for rule ", rulenumber, " condition ", condition
errors +=1
elif action == "match":
match+=1
elif action == "replies":
pass
elif action == "comment":
pass
elif action == "include":
include+=1
for condition in jsonRule["include"]:
if condition[0] == "/":
inclname = os.path.join("../..", condition[1:])
else:
inclname = os.path.join(os.path.dirname(msgfile), condition)
extrafiles.append(inclname)
else:
print "Warning: Ignoring unknown rule:", action
warnings+=1
if (include == 1 and msg+post+match == 0) or (msg == 1 and post == 1 and match ==1 and pre == 1):
pass
else:
print "ERROR: Rule created with an invalid combination of actions, actions are: ", jsonRule.keys()
errors +=1
newfiles =0
newrules =0
newwarnings=0
newerrors=0
if len(extrafiles) > 0:
for extrapath in extrafiles:
newfiles, newrules, newwarnings, newerrors = checkdialoguefile(extrapath, location)
print "checked ", newrules, "rules from file", extrapath, "Found ", newerrors, " errors and ", newwarnings,"warnings"
warnings +=newwarnings
rulenumber+=newrules
errors+=newerrors
extrafiles = []
return (1+newfiles, rulenumber, warnings, errors)
if len(sys.argv) < 2:
print "usage: python dialog_check.py path/to/dialogfile.msg"
exit()
for arg in sys.argv[1:]:
newfiles, rulecount, newwarnings, newerrors = checkdialoguefile(arg, '')
print "checked ", rulecount, "rules from file", arg, "Found ", newerrors, " errors and ", newwarnings,"warnings"