import os
import yaml
class QuestionException ( Exception ) :
""" Exception to be thrown when there is something wrong with
the question file . """
pass
class Questions ( object ) :
""" Object holding all the questions """
QUESTION_TYPES = [ " Text " , " Image " , " Music " , " Code " , " Video " ]
QUESTION_KEYS = [ " Name " , " Question " , " Answer " , " Type " , " Double-Jeopardy " , " Audio " ]
def __init__ ( self , qfile , appendPath = False , verbose = False ) :
self . qfile = qfile
self . _questions = [ ]
self . _appendPath = appendPath
self . _verbose = verbose
self . _title = " Round n "
self . _read_questions ( self . qfile )
def get_title ( self ) :
return self . _title
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 ]
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 )
def _get_yaml ( self , filename ) :
f = None
# open file
try :
f = open ( filename )
except OSError as e :
raise QuestionException ( " Could not read question file ' %s ' : %s " % ( filename , e ) )
# load yaml
ysrc = None
try :
ysrc = yaml . safe_load ( f . read ( ) )
except Exception as 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 ) not in ( unicode , 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 = None
try :
sec_ysrc = self . _get_yaml ( fpath )
except ( OSError , IOError ) as e :
raise QuestionException ( " Error reading question file %s : %s " % ( fpath , str ( e ) ) )
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
if type ( ysrc ) is not list :
self . _gen_error ( filename , " The questionfile has to be a list of sections " )
for i , sec in enumerate ( ysrc , 1 ) :
if not " Section " in sec . keys ( ) or not " Questions " in sec . keys ( ) :
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 ) :
# check for keys we need in each question
if any ( [ x not in q . keys ( ) for x in [ " Question " , " Answer " , " Type " ] ] ) :
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)
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 " ] ) ) )
# check for keys we do not know
for key in q . keys ( ) :
if key not in self . QUESTION_KEYS :
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
if " Double-Jeopardy " not in q . keys ( ) :
q [ " Double-Jeopardy " ] = False
elif type ( q [ " Double-Jeopardy " ] ) != bool :
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
if " Audio " not in q . keys ( ) :
q [ " Audio " ] = False
elif type ( q [ " Audio " ] ) != bool :
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
if q [ " Type " ] not in 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
if q [ " Type " ] in ( " Music " , " Image " , " Video " ) :
if self . _appendPath :
q [ " Question " ] = os . path . join ( basedir , q [ " Question " ] )
if not os . path . isfile ( q [ " Question " ] ) :
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
if j != 5 :
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 )
# 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
if __name__ == ' __main__ ' :
q = Questions ( " questions/template.q " )
print ( q . get_sections ( ) )
print ( q . get_questions ( " A " ) )
print ( q . get_question ( " A " , 1 ) )