#!/usr/bin/env python from gettext import gettext as _ from optparse import OptionParser import os import sys import random from time import time # Sibpath from Twisted def sibpath(path, sibling): """Return the path to a sibling of a file in the filesystem. This is useful in conjunction with the special __file__ attribute that Python provides for modules, so modules can load associated resource files. """ return os.path.join(os.path.dirname(os.path.abspath(path)), sibling) def semi_to_list(semi_sep): return [i.strip() for i in semi_sep.lower().split(u';')] class Question(object): def __init__(self, prompt, answers=[]): self.answered = False self.prompt = prompt self.answers = answers class Card(object): def __init__(self, question): self.question = question self.flipped = False class DataFile(dict): '''Data files are UTF-8 encoded(!) and each line looks like this: term (; term)* = definition (; definition)* ''' def __init__(self, file_, reverse=False): import codecs f = codecs.open(file_, encoding='utf-8') try: for line in f.readlines(): self._read_line(line, reverse) finally: f.close() def _add_pair(self, prompt, answers): if prompt in self: self[prompt].answers += answers else: self[prompt] = Question(prompt, answers) def _read_line(self, line, reverse): try: prompts, answers = line.split('=', 1) prompts = semi_to_list(prompts) answers = semi_to_list(answers) for p in prompts: if reverse: for a in answers: self._add_pair(a, [p]) else: self._add_pair(p, answers) except ValueError: print _(u'invalid line: ') + line.strip() class Quiz(object): def __init__(self, file_, reverse=False, strict=False): self._questions = DataFile(file_, reverse) self._strict = strict self.__choice = random.Random(time()).choice def next(self): return self.__choice(self._questions.values()) class TextQuiz(Quiz): def _ask(self, question): prompt = ('%s = ' % question.prompt).encode(sys.stdout.encoding) response = raw_input(prompt).strip().lower() if self._strict: response = set(semi_to_list(response)) answers = set(question.answers) return response == answers else: return response in question.answers def run(self): try: score = 0 while score < len(self._questions): question = self.next() if question.answered: continue if self._ask(question): print _(u'correct') question.answered = True score += 1 else: print _(u'incorrect: ') + u'; '.join(question.answers) except EOFError: pass class GtkQuiz(Quiz): def __init__(self, file_, font_size, reverse=False): Quiz.__init__(self, file_, reverse) xml = gtk.glade.XML(sibpath(__file__, 'quiz.glade')) xml.signal_autoconnect(self) self._aboutdialog = xml.get_widget('aboutdialog') text = xml.get_widget('text') text.modify_font(FontDescription('normal %s' % font_size)) self._display = text.set_text self._flip = xml.get_widget('flip') self._skip = xml.get_widget('skip') self._next = xml.get_widget('next') self._state = Card(self.next()) self._display(self._state.question.prompt) self._score = 0 def _game_over(self): self._display(_(u'You win!')) self._flip.set_sensitive(False) self._skip.set_sensitive(False) self._next.set_sensitive(False) def _new_card(self): if len(self._questions) > 1 and \ self._score + 1 < len(self._questions): while True: old = self._state.question new = self.next() if old != new and not new.answered: self._state = Card(new) break self._display(self._state.question.prompt) def on_next_clicked(self, p): self._state.question.answered = True self._new_card() self._score += 1 if self._score >= len(self._questions): self._game_over() def on_skip_clicked(self, p): self._new_card() def on_flip_clicked(self, p): if self._state.flipped: self._display(self._state.question.prompt) else: self._display('; '.join(self._state.question.answers)) self._state.flipped = not self._state.flipped def on_about_activate(self, p): self._aboutdialog.set_property('visible', True) def gtk_main_quit(*self): gtk.main_quit() def get_options(): parser = OptionParser(usage='%prog [OPTIONS] QUIZ', version='%prog $Rev: 683 $') parser.add_option('-r', '--reverse', action='store_true', \ dest='reverse', help=_(u'Reverse the deck.')) parser.add_option('-t', '--text-mode', action='store_true', \ dest='text', help=_(u'Force text mode.')) parser.add_option('-s', '--strict', action='store_true', \ dest='strict', help=_(u'Enable strict mode \ (require all possible definitions to be entered). Implies --text.')) parser.add_option('--font-size', type='string', default='24', \ dest='font_size', help=_('Font size to display \ quiz in.')) options, args = parser.parse_args() if options.strict: options.text = True if len(args) != 1: parser.error(_('Required argument QUIZ missing.')) return options, args if __name__ == '__main__': options, args = get_options() file_ = args[0] reverse = options.reverse text = options.text strict = options.strict run_text = TextQuiz(file_=file_, reverse=reverse, strict=strict).run if text: run_text() else: try: import pygtk import gtk, gtk.glade from pango import FontDescription quiz = GtkQuiz(file_=file_, font_size=options.font_size, reverse=reverse) gtk.main() except ImportError: run_text()