1783d9c9234c6401210ba821083ffa0ee6c7920a
[quizzer.git] / quizzer / question.py
1 QUESTION_CLASS = {}
2
3
4 def register_question(question_class):
5     QUESTION_CLASS[question_class.__name__] = question_class
6
7
8 class Question (object):
9     _state_attributes = [
10         'id',
11         'prompt',
12         'answer',
13         'help',
14         'dependencies',
15         ]
16
17     def __init__(self, **kwargs):
18         self.__setstate__(kwargs)
19
20     def __str__(self):
21         return '<{} id:{!r}>'.format(type(self).__name__, self.id)
22
23     def __repr__(self):
24         return '<{} id:{!r} at {:#x}>'.format(
25             type(self).__name__, self.id, id(self))
26
27     def __getstate__(self):
28         return {attr: getattr(self, attr)
29                 for attr in self._state_attributes} 
30
31     def __setstate__(self, state):
32         if 'id' not in state:
33             state['id'] = state.get('prompt', None)
34         if 'dependencies' not in state:
35             state['dependencies'] = []
36         for attr in self._state_attributes:
37             if attr not in state:
38                 state[attr] = None
39         self.__dict__.update(state)
40
41     def check(self, answer):
42         return answer == self.answer
43
44
45 class NormalizedStringQuestion (Question):
46     def normalize(self, string):
47         return string.strip().lower()
48
49     def check(self, answer):
50         return self.normalize(answer) == self.normalize(self.answer)
51
52
53 class ScriptQuestion (Question):
54     _state_attributes = Question._state_attributes + [
55         'interpreter',
56         'setup',
57         'teardown',
58         ]
59
60     def __setstate__(self, state):
61         if 'interpreter' not in state:
62             state['interpreter'] = 'sh'  # POSIX-compatible shell
63         for key in ['setup', 'teardown']:
64             if key not in state:
65                 state[key] = []
66         super(ScriptQuestion, self).__setstate__(state)
67
68     def check(self, answer):
69         script = '\n'.join(self.setup + [answer] + self.teardown)
70         raise ValueError(script)
71
72
73 for name,obj in list(locals().items()):
74     if name.startswith('_'):
75         continue
76     try:
77         subclass = issubclass(obj, Question)
78     except TypeError:  # obj is not a class
79         continue
80     if subclass:
81         register_question(obj)
82 del name, obj