ui.cli: Fix ._set_ps1 when .question is None
[quizzer.git] / quizzer / ui / cli.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 cmd as _cmd
18 try:
19     import readline as _readline
20 except ImportError as _readline_import_error:
21     _readline = None
22
23 from . import UserInterface
24
25
26 class QuestionCommandLine (_cmd.Cmd):
27     _help = [
28         'Type help or ? to list commands.',
29         'Non-commands will be interpreted as answers.',
30         'Use a blank line to terminate multi-line answers.',
31         ]
32     intro = '\n'.join(['Welcome to the quizzer shell.'] + _help)
33     _prompt = 'quizzer? '
34
35     def __init__(self, ui):
36         super(QuestionCommandLine, self).__init__()
37         self.ui = ui
38
39     def preloop(self):
40         self.question = self.ui.get_question()
41         self._reset()
42
43     def _reset(self):
44         self.answers = []
45         self._set_ps1()
46
47     def _set_ps1(self):
48         "Pose a question and prompt"
49         if self.question:
50             self.prompt = '\n{}\n{}'.format(
51                 self.question.format_prompt(), self._prompt)
52         else:
53             self.prompt = self._prompt
54
55     def _set_ps2(self):
56         "Just prompt (without the question, e.g. for multi-line answers)"
57         self.prompt = self._prompt
58
59     def default(self, line):
60         self.answers.append(line)
61         if self.question.multiline:
62             self._set_ps2()
63         else:
64             self._answer()
65
66     def emptyline(self):
67         self._answer()
68
69     def _answer(self):
70         if self.question.multiline:
71             answer = self.answers
72         elif self.answers:
73             answer = self.answers[0]
74         else:
75             answer = ''
76         correct = self.ui.process_answer(question=self.question, answer=answer)
77         if correct:
78             print('correct\n')
79         else:
80             print('incorrect\n')
81         self.question = self.ui.get_question()
82         if not self.question:
83             return True  # out of questions
84         self._reset()
85
86     def do_answer(self, arg):
87         """Explicitly add a line to your answer
88
89         This is useful if the line you'd like to add starts with a
90         quizzer-shell command.  For example:
91
92           quizzer? answer help=5
93         """
94         return self.default(arg)
95
96     def do_quit(self, arg):
97         "Stop taking the quiz"
98         self._reset()
99         return True
100
101     def do_hint(self, arg):
102         "Show a hint for the current question"
103         self._reset()
104         print(self.question.format_help())
105
106     def do_help(self, arg):
107         'List available commands with "help" or detailed help with "help cmd"'
108         if not arg:
109             print('\n'.join(self._help))
110         super(QuestionCommandLine, self).do_help(arg)
111
112
113 class CommandLineInterface (UserInterface):
114     def run(self):
115         cmd = QuestionCommandLine(ui=self)
116         cmd.cmdloop()
117         print()
118
119     def display_results(self):
120         print('results:')
121         for question in self.quiz:
122             if question.id in self.answers:
123                 self.display_result(question=question)
124                 print()
125         self.display_totals()
126
127     def display_result(self, question):
128         answers = self.answers.get(question.id, [])
129         print('question:')
130         print('  {}'.format(question.format_prompt(newline='\n  ')))
131         la = len(answers)
132         lc = len([a for a in answers if a['correct']])
133         print('answers: {}/{} ({:.2f})'.format(lc, la, float(lc)/la))
134         for answer in answers:
135             if answer['correct']:
136                 correct = 'correct'
137             else:
138                 correct = 'incorrect'
139             print('  you answered: {}'.format(answer['answer']))
140             print('     which was: {}'.format(correct))
141
142     def display_totals(self):
143         answered = self.answers.get_answered(questions=self.quiz)
144         correctly_answered = self.answers.get_correctly_answered(
145             questions=self.quiz)
146         la = len(answered)
147         lc = len(correctly_answered)
148         print('answered {} of {} questions'.format(la, len(self.quiz)))
149         print(('of the answered questions, {} ({:.2f}) were answered correctly'
150                ).format(lc, float(lc)/la))