New question file format support
This commit is contained in:
parent
c771f24ff6
commit
1eef6f4d61
2
game.py
2
game.py
|
@ -42,7 +42,7 @@ class SeopardyGame(QtGui.QWidget):
|
||||||
|
|
||||||
layout = QtGui.QVBoxLayout()
|
layout = QtGui.QVBoxLayout()
|
||||||
headerLayout = QtGui.QHBoxLayout()
|
headerLayout = QtGui.QHBoxLayout()
|
||||||
self.header = QtGui.QLabel("")
|
self.header = QtGui.QLabel(self.questions.get_title())
|
||||||
headerLayout.addWidget(self.header)
|
headerLayout.addWidget(self.header)
|
||||||
headerLayout.setSizeConstraint(QtGui.QLayout.SetMinimumSize)
|
headerLayout.setSizeConstraint(QtGui.QLayout.SetMinimumSize)
|
||||||
layout.addWidget(self.header, alignment=QtCore.Qt.AlignCenter)
|
layout.addWidget(self.header, alignment=QtCore.Qt.AlignCenter)
|
||||||
|
|
114
question.py
114
question.py
|
@ -13,12 +13,17 @@ class Questions(object):
|
||||||
QUESTION_TYPES = ["Text", "Image", "Music", "Code", "Video"]
|
QUESTION_TYPES = ["Text", "Image", "Music", "Code", "Video"]
|
||||||
QUESTION_KEYS = ["Name", "Question", "Answer", "Type", "Double-Jeopardy", "Audio"]
|
QUESTION_KEYS = ["Name", "Question", "Answer", "Type", "Double-Jeopardy", "Audio"]
|
||||||
|
|
||||||
def __init__(self, qfile, appendPath=False):
|
def __init__(self, qfile, appendPath=False, verbose=False):
|
||||||
self.qfile = qfile
|
self.qfile = qfile
|
||||||
self._questions = None
|
self._questions = []
|
||||||
self._appendPath = appendPath
|
self._appendPath = appendPath
|
||||||
self._basedir = os.path.dirname(qfile)
|
self._verbose = verbose
|
||||||
self._read_questions()
|
self._title = "Round n"
|
||||||
|
|
||||||
|
self._read_questions(self.qfile)
|
||||||
|
|
||||||
|
def get_title(self):
|
||||||
|
return self._title
|
||||||
|
|
||||||
def get_sections(self):
|
def get_sections(self):
|
||||||
return [s["Section"] for s in self._questions]
|
return [s["Section"] for s in self._questions]
|
||||||
|
@ -40,80 +45,137 @@ class Questions(object):
|
||||||
return i
|
return i
|
||||||
raise ValueError("Section '%s' does not exist" % section)
|
raise ValueError("Section '%s' does not exist" % section)
|
||||||
|
|
||||||
def _read_questions(self):
|
def _get_yaml(self, filename):
|
||||||
f = None
|
f = None
|
||||||
|
|
||||||
# open file
|
# open file
|
||||||
try:
|
try:
|
||||||
f = open(self.qfile)
|
f = open(filename)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise QuestionException("Could not read question file: %s" % e)
|
raise QuestionException("Could not read question file '%s': %s" % (filename, e))
|
||||||
|
|
||||||
# load yaml
|
# load yaml
|
||||||
ysrc = None
|
ysrc = None
|
||||||
try:
|
try:
|
||||||
ysrc = yaml.safe_load(f.read())
|
ysrc = yaml.safe_load(f.read())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise QuestionException("Error parsing question file %s: %s" % (self.qfile, e))
|
raise QuestionException("Error parsing YAML in question file '%s': %s" % (filename, e))
|
||||||
|
|
||||||
|
return ysrc
|
||||||
|
|
||||||
|
def _gen_error(self, filename, error):
|
||||||
|
raise QuestionException("Error parsing question file '%s': %s" % (filename, error))
|
||||||
|
|
||||||
|
def _read_questions(self, filename):
|
||||||
|
ysrc = self._get_yaml(filename)
|
||||||
|
|
||||||
|
if type(ysrc) == list:
|
||||||
|
# "old style" question file, list of sections
|
||||||
|
# just read sections and return happily ever after
|
||||||
|
self._read_sections(ysrc, filename)
|
||||||
|
|
||||||
|
return True
|
||||||
|
elif type(ysrc) == dict:
|
||||||
|
# "new style" quetion file, dict of Name and Sections
|
||||||
|
self._read_newstyle_questions(ysrc, filename)
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
self._gen_error(filename, "Contents of YAML is neither a dict nor a list (format error), see seopardy guide for question file format documentation.")
|
||||||
|
|
||||||
|
def _read_newstyle_questions(self, ysrc, filename):
|
||||||
|
if type(ysrc) != dict:
|
||||||
|
self._gen_error(filename, "Error parsing question file '%s': Is not a dict (and this sould(tm) have been safeguarded by an earlier if)")
|
||||||
|
|
||||||
|
if "Name" not in ysrc.keys():
|
||||||
|
self._gen_error(filename, "Missing 'Name' key.")
|
||||||
|
|
||||||
|
if "Sections" not in ysrc.keys():
|
||||||
|
self._gen_error(filename, "Missing 'Sections' key.")
|
||||||
|
|
||||||
|
extra_keys = filter(lambda _x: _x not in ["Name", "Sections"], ysrc.keys())
|
||||||
|
if extra_keys:
|
||||||
|
self._gen_error(filename, "Unsupported keys found: %s (only 'Name' and 'Sections' are supported. Is your case correct?)" % (", ".join(extra_keys),))
|
||||||
|
|
||||||
|
if type(ysrc["Sections"]) != list:
|
||||||
|
self._gen_error(filename, "The 'Sections' part needs to be a list of filenames, not a '%s'" % (type(ysrc["Sections"]),))
|
||||||
|
|
||||||
|
self._title = ysrc["Name"]
|
||||||
|
|
||||||
|
# read sections
|
||||||
|
basedir = os.path.dirname(filename)
|
||||||
|
for n, sec_filename in enumerate(ysrc["Sections"], 1):
|
||||||
|
if type(sec_filename) != str:
|
||||||
|
self._gen_error(filename, "Section element %d is not a string (type %s found)" % (n, type(sec_filename)))
|
||||||
|
|
||||||
|
fpath = os.path.join(basedir, sec_filename)
|
||||||
|
sec_ysrc = self._get_yaml(fpath)
|
||||||
|
self._read_sections(sec_ysrc, fpath)
|
||||||
|
|
||||||
|
def _read_sections(self, ysrc, filename):
|
||||||
|
basedir = os.path.dirname(filename)
|
||||||
|
|
||||||
# now to check the integrity of the question file
|
# now to check the integrity of the question file
|
||||||
if type(ysrc) is not list:
|
if type(ysrc) is not list:
|
||||||
raise QuestionException("The questionfile has to be a list of question")
|
self._gen_error(filename, "The questionfile has to be a list of sections")
|
||||||
|
|
||||||
for i, sec in enumerate(ysrc, 1):
|
for i, sec in enumerate(ysrc, 1):
|
||||||
if not "Section" in sec.keys() or not "Questions" in sec.keys():
|
if not "Section" in sec.keys() or not "Questions" in sec.keys():
|
||||||
raise QuestionException("Section %d needs to have the keys 'Section' and 'Question' (case-sensitive)" % i)
|
self._gen_error(filename, "Section %d needs to have the keys 'Section' and 'Question' (case-sensitive)" % i)
|
||||||
|
|
||||||
for j, q in enumerate(sec["Questions"], 1):
|
for j, q in enumerate(sec["Questions"], 1):
|
||||||
# check for keys we need in each question
|
# check for keys we need in each question
|
||||||
if any([x not in q.keys() for x in ["Question", "Answer", "Type"]]):
|
if any([x not in q.keys() for x in ["Question", "Answer", "Type"]]):
|
||||||
raise QuestionException("Question %d from section %d (%s) is missing one of the keywords Question, Answer or Type" % (j, i, sec["Section"]))
|
self._gen_error(filename, "Question %d from section %d (%s) is missing one of the keywords Question, Answer or Type" % (j, i, sec["Section"]))
|
||||||
|
|
||||||
# check wether the question is a string (else we'll get display errors)
|
# check wether the question is a string (else we'll get display errors)
|
||||||
if type(q["Question"]) != str:
|
if type(q["Question"]) != str:
|
||||||
raise QuestionException("Question %d from section %d (%s) needs to have a string as question (put the Question in \"\")" % (j, i, sec["Section"]))
|
self._gen_error(filename, "Question %d from section %d (%s) needs to have a string as question (put the Question in \"\")" % (j, i, sec["Section"]))
|
||||||
print(type(q["Question"]))
|
|
||||||
|
|
||||||
|
|
||||||
# check for keys we do not know
|
# check for keys we do not know
|
||||||
for key in q.keys():
|
for key in q.keys():
|
||||||
if key not in self.QUESTION_KEYS:
|
if key not in self.QUESTION_KEYS:
|
||||||
raise QuestionException("Qestion %d from section %d (%s) has invalid keyword '%s'" % (j, i, sec["Section"], key))
|
self._gen_error(filename, "Qestion %d from section %d (%s) has invalid keyword '%s'" % (j, i, sec["Section"], key))
|
||||||
|
|
||||||
# check Double-Jeopardy is a bool and is set to false if non-existant
|
# check Double-Jeopardy is a bool and is set to false if non-existant
|
||||||
if "Double-Jeopardy" not in q.keys():
|
if "Double-Jeopardy" not in q.keys():
|
||||||
q["Double-Jeopardy"] = False
|
q["Double-Jeopardy"] = False
|
||||||
elif type(q["Double-Jeopardy"]) != bool:
|
elif type(q["Double-Jeopardy"]) != bool:
|
||||||
raise QuestionException("The Double-Jeopardy key from question %d from section %d (%s) must be either true or false" % (j, i, sec["Section"]))
|
self._gen_error(filename, "The Double-Jeopardy key from question %d from section %d (%s) must be either true or false" % (j, i, sec["Section"]))
|
||||||
|
|
||||||
# check Audio is a bool and is set to false if non-existant
|
# check Audio is a bool and is set to false if non-existant
|
||||||
if "Audio" not in q.keys():
|
if "Audio" not in q.keys():
|
||||||
q["Audio"] = False
|
q["Audio"] = False
|
||||||
elif type(q["Audio"]) != bool:
|
elif type(q["Audio"]) != bool:
|
||||||
raise QuestionException("The Audio key from question %d from section %d (%s) must be either true or false" % (j, i, sec["Section"]))
|
self._gen_error(filename, "The Audio key from question %d from section %d (%s) must be either true or false" % (j, i, sec["Section"]))
|
||||||
|
|
||||||
# check for broken question types
|
# check for broken question types
|
||||||
if q["Type"] not in self.QUESTION_TYPES:
|
if q["Type"] not in self.QUESTION_TYPES:
|
||||||
raise QuestionException("Question %d from Section %d (%s) has an invalid type '%s' (valid types are %s)" % (j, i, sec["Section"], q["Type"], ", ".join(self.QUESTION_TYPES)))
|
self._gen_error(filename, "Question %d from Section %d (%s) has an invalid type '%s' (valid types are %s)" % (j, i, sec["Section"], q["Type"], ", ".join(self.QUESTION_TYPES)))
|
||||||
|
|
||||||
# check if file for music/image questions exist
|
# check if file for music/image questions exist
|
||||||
if q["Type"] in ("Music", "Image", "Video"):
|
if q["Type"] in ("Music", "Image", "Video"):
|
||||||
if self._appendPath:
|
if self._appendPath:
|
||||||
q["Question"] = os.path.join(self._basedir, q["Question"])
|
q["Question"] = os.path.join(basedir, q["Question"])
|
||||||
if not os.path.isfile(q["Question"]):
|
if not os.path.isfile(q["Question"]):
|
||||||
raise QuestionException("File for question %d, section %d (%s) not found" % (j, i, sec["Section"]))
|
fname_error = ""
|
||||||
|
if self._verbose:
|
||||||
|
fname_error = " (path: %s)" % (q["Question"],)
|
||||||
|
self._gen_error(filename, "File for question %d, section %d (%s) not found%s" % (j, i, sec["Section"], fname_error))
|
||||||
|
|
||||||
# check if this section has enough questions
|
# check if this section has enough questions
|
||||||
if j != 5:
|
if j != 5:
|
||||||
raise QuestionException("Section %d (%s) needs to have exactly %d questions (has %d)" % (i, sec["Section"], 5, j))
|
self._gen_error(filename, "Section %d (%s) needs to have exactly %d questions (has %d)" % (i, sec["Section"], 5, j))
|
||||||
|
|
||||||
# check for only having unique section names
|
|
||||||
sections = [s["Section"] for s in ysrc]
|
|
||||||
if len(sections) != len(set(sections)):
|
|
||||||
raise QuestionException("All section names must be unique")
|
|
||||||
|
|
||||||
# done, save yaml src to _questions
|
# done, save yaml src to _questions
|
||||||
self._questions = ysrc
|
self._questions.extend(ysrc)
|
||||||
|
|
||||||
|
# check for only having unique section names
|
||||||
|
sections = [s["Section"] for s in self._questions]
|
||||||
|
if len(sections) != len(set(sections)):
|
||||||
|
map(lambda _x: sections.remove(_x), set(sections))
|
||||||
|
self._gen_error(filename, "All section names must be unique (clashing: %s)" % (", ".join(sections),))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ def _parser():
|
||||||
parser.add_argument("--gamestate", action="store", default=None, help="Gamestate to load to recover a crashed game")
|
parser.add_argument("--gamestate", action="store", default=None, help="Gamestate to load to recover a crashed game")
|
||||||
parser.add_argument("--conf", action="store", default="seopardy.conf", help="Path to config file")
|
parser.add_argument("--conf", action="store", default="seopardy.conf", help="Path to config file")
|
||||||
parser.add_argument("--no-cwd", action="store_true", default=False, help="Do not change working directory to question folder")
|
parser.add_argument("--no-cwd", action="store_true", default=False, help="Do not change working directory to question folder")
|
||||||
|
parser.add_argument("-v", "--verbose", action="store_true", default=False, help="Be more verbose, print filenames of missing files")
|
||||||
parser.add_argument("questions", action="store", help="Path to questionfile")
|
parser.add_argument("questions", action="store", help="Path to questionfile")
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
@ -28,7 +29,7 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
questions = None
|
questions = None
|
||||||
try:
|
try:
|
||||||
questions = Questions(args.questions, appendPath=not args.no_cwd)
|
questions = Questions(args.questions, appendPath=not args.no_cwd, verbose=args.verbose)
|
||||||
except QuestionException as e:
|
except QuestionException as e:
|
||||||
print(str(e), file=sys.stderr)
|
print(str(e), file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
Loading…
Reference in New Issue