Merge branch 'license'
[quizzer.git] / quizzer / quiz.py
1 # Copyright (C) 2013 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of quizzer.
4 #
5 # quizzer is free software: you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # quizzer is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # quizzer.  If not, see <http://www.gnu.org/licenses/>.
16
17 import codecs as _codecs
18 import json as _json
19
20 from . import __version__
21 from . import question as _question
22
23
24 class Quiz (list):
25     def __init__(self, questions=None, path=None, encoding=None,
26                  copyright=None, introduction=None):
27         if questions is None:
28             questions = []
29         super(Quiz, self).__init__(questions)
30         self.path = path
31         self.encoding = encoding
32         self.copyright = copyright
33         self.introduction = introduction
34
35     def _open(self, mode='r', path=None, encoding=None):
36         if path:
37             self.path = path
38         if encoding:
39             self.encoding = encoding
40         return _codecs.open(self.path, mode, self.encoding)
41
42     def load(self, **kwargs):
43         with self._open(mode='r', **kwargs) as f:
44             data = _json.load(f)
45         version = data.get('version', None)
46         if version != __version__:
47             raise NotImplementedError('upgrade from {} to {}'.format(
48                     version, __version__))
49         self.copyright = data.get('copyright', None)
50         self.introduction = data.get('introduction', None)
51         for state in data['questions']:
52             question_class_name = state.pop('class', 'Question')
53             question_class = _question.QUESTION_CLASS[question_class_name]
54             q = question_class()
55             q.__setstate__(state)
56             self.append(q)
57
58     def save(self, **kwargs):
59         questions = []
60         for question in self:
61             state = question.__getstate__()
62             state['class'] = type(question).__name__
63         data = {
64             'version': __version__,
65             'copyright': self.copyright,
66             'introduction': self.introduction,
67             'questions': questions,
68             }
69         with self._open(mode='w', **kwargs) as f:
70             _json.dump(
71                 data, f, indent=2, separators=(',', ': '), sort_keys=True)
72             f.write('\n')
73
74     def leaf_questions(self):
75         "Questions that are not dependencies of other question"
76         dependents = set()
77         for question in self:
78             dependents.update(question.dependencies)
79         return [q for q in self if q.id not in dependents]
80
81     def get(self, id=None):
82         matches = [q for q in self if q.id == id]
83         if len(matches) == 1:
84             return matches[0]
85         elif len(matches) == 0:
86             raise KeyError(id)
87         raise NotImplementedError(
88             'multiple questions with one ID: {}'.format(matches))