2015-08-19 11:20:51 +02:00
# Licensed under GPLv3
# Written by Sebastian Lohff (seba@someserver.de)
# http://seba-geek.de/projects/seopardy/
2013-11-05 17:16:21 +01:00
import os
import yaml
2013-11-06 20:42:00 +01:00
2013-11-05 17:16:21 +01:00
class QuestionException ( Exception ) :
2013-11-06 20:42:00 +01:00
""" Exception to be thrown when there is something wrong with
the question file . """
2013-11-05 17:16:21 +01:00
pass
class Questions ( object ) :
2013-11-06 20:42:00 +01:00
""" Object holding all the questions """
2014-12-17 01:36:14 +01:00
QUESTION_TYPES = [ " Text " , " Image " , " Music " , " Code " , " Video " ]
QUESTION_KEYS = [ " Name " , " Question " , " Answer " , " Type " , " Double-Jeopardy " , " Audio " ]
2013-11-06 20:42:00 +01:00
2014-12-27 13:11:21 +01:00
def __init__ ( self , qfile , appendPath = False , verbose = False ) :
2013-11-05 17:16:21 +01:00
self . qfile = qfile
2014-12-27 13:11:21 +01:00
self . _questions = [ ]
2014-12-17 05:11:24 +01:00
self . _appendPath = appendPath
2014-12-27 13:11:21 +01:00
self . _verbose = verbose
self . _title = " Round n "
self . _read_questions ( self . qfile )
def get_title ( self ) :
return self . _title
2013-11-05 17:16:21 +01:00
def get_sections ( self ) :
return [ s [ " Section " ] for s in self . _questions ]
def get_questions ( self , section ) :
sec = filter ( lambda s : s [ " Section " ] == section , self . _questions )
if len ( sec ) < 1 :
raise ValueError ( " Section %s does not exist " % ( section , ) )
return sec [ 0 ] [ " Questions " ]
def get_question ( self , section , question ) :
if type ( question ) != int or question < 1 or question > 5 :
raise ValueError ( " question parameter needs to be an integer between 1 and 5 " )
return self . get_questions ( section ) [ question - 1 ]
2013-11-11 00:52:37 +01:00
def get_number_from_section ( self , section ) :
for i , s in enumerate ( self . _questions , 1 ) :
if s [ " Section " ] == section :
return i
raise ValueError ( " Section ' %s ' does not exist " % section )
2014-12-27 13:11:21 +01:00
def _get_yaml ( self , filename ) :
2013-11-05 17:16:21 +01:00
f = None
# open file
try :
2014-12-27 13:11:21 +01:00
f = open ( filename )
2013-11-05 17:16:21 +01:00
except OSError as e :
2014-12-27 13:11:21 +01:00
raise QuestionException ( " Could not read question file ' %s ' : %s " % ( filename , e ) )
2013-11-05 17:16:21 +01:00
# load yaml
ysrc = None
try :
2013-12-03 23:18:30 +01:00
ysrc = yaml . safe_load ( f . read ( ) )
2013-11-05 17:16:21 +01:00
except Exception as e :
2014-12-27 13:11:21 +01:00
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 )
2015-09-16 12:35:24 +02:00
for n , sec_data in enumerate ( ysrc [ " Sections " ] , 1 ) :
jeopardyOverride = None
if type ( sec_data ) not in ( unicode , str , dict ) :
self . _gen_error ( filename , " Section element %d is neither a string nor a dict (type %s found) " % ( n , type ( sec_data ) ) )
sec_filename = None
if type ( sec_data ) == dict :
if " File " not in sec_data :
self . _gen_error ( filename , " Section element %d is a dictionary, but has no key ' File ' pointing to a file to load " % ( n , ) )
sec_filename = sec_data [ " File " ]
if " Double-Jeopardies " in sec_data and sec_data [ " Double-Jeopardies " ] != None :
# TODO: load (override) double jeopardies for section
if type ( sec_data [ " Double-Jeopardies " ] ) != list :
self . _gen_error ( filename , " Section %d : Double-Jeopardies has to be a list or null (type %s found) " % ( n , type ( sec_data [ " Double-Jeopardies " ] ) ) )
# take care that jeopardyOverride is a list of 5 bools
jeopardyOverride = map ( bool , sec_data [ " Double-Jeopardies " ] )
jeopardyOverride = jeopardyOverride [ 0 : 5 ]
while len ( jeopardyOverride ) < 5 :
jeopardyOverride . append ( False )
else :
sec_filename = sec_data
2014-12-27 13:11:21 +01:00
fpath = os . path . join ( basedir , sec_filename )
2015-08-17 00:19:32 +02:00
sec_ysrc = None
try :
sec_ysrc = self . _get_yaml ( fpath )
except ( OSError , IOError ) as e :
raise QuestionException ( " Error reading question file %s : %s " % ( fpath , str ( e ) ) )
2015-09-16 12:35:24 +02:00
self . _read_sections ( sec_ysrc , fpath , jeopardyOverride )
2014-12-27 13:11:21 +01:00
2015-09-16 12:35:24 +02:00
def _read_sections ( self , ysrc , filename , jeopardyOverride = None ) :
2014-12-27 13:11:21 +01:00
basedir = os . path . dirname ( filename )
2013-11-05 17:16:21 +01:00
# now to check the integrity of the question file
if type ( ysrc ) is not list :
2014-12-27 13:11:21 +01:00
self . _gen_error ( filename , " The questionfile has to be a list of sections " )
2013-11-05 17:16:21 +01:00
for i , sec in enumerate ( ysrc , 1 ) :
if not " Section " in sec . keys ( ) or not " Questions " in sec . keys ( ) :
2014-12-27 13:11:21 +01:00
self . _gen_error ( filename , " Section %d needs to have the keys ' Section ' and ' Question ' (case-sensitive) " % i )
2013-11-05 17:16:21 +01:00
for j , q in enumerate ( sec [ " Questions " ] , 1 ) :
# check for keys we need in each question
if any ( [ x not in q . keys ( ) for x in [ " Question " , " Answer " , " Type " ] ] ) :
2014-12-27 13:11:21 +01:00
self . _gen_error ( filename , " Question %d from section %d ( %s ) is missing one of the keywords Question, Answer or Type " % ( j , i , sec [ " Section " ] ) )
2013-11-05 17:16:21 +01:00
2014-12-23 03:39:42 +01:00
# check wether the question is a string (else we'll get display errors)
2015-01-06 10:05:58 +01:00
if type ( q [ " Question " ] ) not in ( str , unicode ) :
self . _gen_error ( filename , " Question %d from section %d ( %s ) needs to have a string as question (type found was ' %s ' , maybe put the Question in \" \" ) " % ( j , i , sec [ " Section " ] , type ( q [ " Question " ] ) ) )
2014-12-23 03:39:42 +01:00
2013-11-05 17:16:21 +01:00
# check for keys we do not know
for key in q . keys ( ) :
if key not in self . QUESTION_KEYS :
2014-12-27 13:11:21 +01:00
self . _gen_error ( filename , " Qestion %d from section %d ( %s ) has invalid keyword ' %s ' " % ( j , i , sec [ " Section " ] , key ) )
2013-11-05 17:16:21 +01:00
2014-12-17 01:36:14 +01:00
# check Double-Jeopardy is a bool and is set to false if non-existant
2015-09-16 12:35:24 +02:00
2013-11-05 17:16:21 +01:00
if " Double-Jeopardy " not in q . keys ( ) :
q [ " Double-Jeopardy " ] = False
elif type ( q [ " Double-Jeopardy " ] ) != bool :
2014-12-27 13:11:21 +01:00
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 " ] ) )
2013-11-05 17:16:21 +01:00
2015-09-16 12:35:24 +02:00
# handle Double-Jeopardy override by round file
if jeopardyOverride :
2016-02-04 13:51:26 +01:00
q [ " Double-Jeopardy " ] = jeopardyOverride [ j - 1 ]
2015-09-16 12:35:24 +02:00
2014-12-17 01:36:14 +01:00
# check Audio is a bool and is set to false if non-existant
if " Audio " not in q . keys ( ) :
q [ " Audio " ] = False
elif type ( q [ " Audio " ] ) != bool :
2014-12-27 13:11:21 +01:00
self . _gen_error ( filename , " The Audio key from question %d from section %d ( %s ) must be either true or false " % ( j , i , sec [ " Section " ] ) )
2014-12-17 01:36:14 +01:00
2013-11-05 17:16:21 +01:00
# check for broken question types
if q [ " Type " ] not in self . QUESTION_TYPES :
2014-12-27 13:11:21 +01:00
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 ) ) )
2013-11-05 17:16:21 +01:00
# check if file for music/image questions exist
2014-12-17 01:36:14 +01:00
if q [ " Type " ] in ( " Music " , " Image " , " Video " ) :
2014-12-17 05:11:24 +01:00
if self . _appendPath :
2014-12-27 13:11:21 +01:00
q [ " Question " ] = os . path . join ( basedir , q [ " Question " ] )
2013-11-05 17:16:21 +01:00
if not os . path . isfile ( q [ " Question " ] ) :
2014-12-27 13:11:21 +01:00
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 ) )
2013-11-05 17:16:21 +01:00
# check if this section has enough questions
if j != 5 :
2014-12-27 13:11:21 +01:00
self . _gen_error ( filename , " Section %d ( %s ) needs to have exactly %d questions (has %d ) " % ( i , sec [ " Section " ] , 5 , j ) )
# done, save yaml src to _questions
self . _questions . extend ( ysrc )
2013-11-05 17:16:21 +01:00
# check for only having unique section names
2014-12-27 13:11:21 +01:00
sections = [ s [ " Section " ] for s in self . _questions ]
2013-11-05 17:16:21 +01:00
if len ( sections ) != len ( set ( sections ) ) :
2014-12-27 13:11:21 +01:00
map ( lambda _x : sections . remove ( _x ) , set ( sections ) )
self . _gen_error ( filename , " All section names must be unique (clashing: %s ) " % ( " , " . join ( sections ) , ) )
2013-11-05 17:16:21 +01:00
return True
if __name__ == ' __main__ ' :
q = Questions ( " questions/template.q " )
print ( q . get_sections ( ) )
print ( q . get_questions ( " A " ) )
print ( q . get_question ( " A " , 1 ) )