1 # Copyright (C) 2013 W. Trevor King <wking@tremily.us>
3 # This file is part of quizzer.
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
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.
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/>.
19 import readline as _readline
20 except ImportError as _readline_import_error:
24 from pygments.console import colorize as _colorize
25 except ImportError as e:
26 def _colorize(color_key=None, text=None):
30 from .. import error as _error
31 from .. import question as _question
32 from . import UserInterface as _UserInterface
35 class QuestionCommandLine (_cmd.Cmd):
37 'Type help or ? to list commands.',
38 'Non-commands will be interpreted as answers.',
39 'Use a blank line to terminate multi-line answers.',
41 intro = '\n'.join(['Welcome to the quizzer shell.'] + _help)
44 def __init__(self, ui):
45 super(QuestionCommandLine, self).__init__()
47 if self.ui.quiz.introduction:
48 self.intro = '\n\n'.join([self.intro, self.ui.quiz.introduction])
51 def get_question(self):
52 self.question = self.ui.get_question(user=self.ui.user)
56 return True # out of questions
64 self._tempdir.cleanup() # occasionally redundant, but that's ok
69 "Pose a question and prompt"
74 self.ui.colors['question'], self.question.format_prompt()),
77 _colorize(self.ui.colors['prompt'], line)
78 for line in self._extra_ps1_lines())
79 lines.append(_colorize(self.ui.colors['prompt'], self._prompt))
80 self.prompt = '\n'.join(lines)
82 self.prompt = _colorize(self.ui.colors['prompt'], self._prompt)
85 "Just prompt (without the question, e.g. for multi-line answers)"
86 self.prompt = _colorize(self.ui.colors['prompt'], self._prompt)
88 def _extra_ps1_lines(self):
89 if (isinstance(self.question, _question.ChoiceQuestion) and
90 self.question.display_choices):
91 for i,choice in enumerate(self.question.answer):
92 yield '{}) {}'.format(i, choice)
93 yield 'Answer with the index of your choice'
94 if self.question.accept_all:
96 if self.question.multiple_answers:
98 yield '{} fill in an alternative answer'.format(conj)
99 if self.question.multiple_answers:
100 self._separator = ','
101 yield ("Separate multiple answers with the '{}' character"
102 ).format(self._separator)
104 def _process_answer(self, answer):
105 "Back out any mappings suggested by _extra_ps1_lines()"
106 if (isinstance(self.question, _question.ChoiceQuestion) and
107 self.question.display_choices):
108 if self.question.multiple_answers:
110 for a in answer.split(self._separator):
113 answers.append(self.question.answer[i])
114 except (ValueError, IndexError):
120 return self.question.answer[i]
121 except (ValueError, IndexError):
125 def default(self, line):
126 self.answers.append(line)
127 if self.question.multiline:
130 return self._answer()
133 return self._answer()
136 if self.question.multiline:
137 answer = self.answers
139 answer = self.answers[0]
148 kwargs['tempdir'] = self._tempdir
149 answer = self._process_answer(answer=answer)
150 correct,details = self.ui.process_answer(
151 question=self.question, answer=answer, **kwargs)
153 print(_colorize(self.ui.colors['correct'], 'correct\n'))
155 print(_colorize(self.ui.colors['incorrect'], 'incorrect'))
158 self.ui.colors['incorrect'], '{}\n'.format(details)))
161 return self.get_question()
163 def do_answer(self, arg):
164 """Explicitly add a line to your answer
166 This is useful if the line you'd like to add starts with a
167 quizzer-shell command. For example:
169 quizzer? answer help=5
171 return self.default(arg)
173 def do_shell(self, arg):
174 """Run a shell command in the question temporary directory
176 For example, you can spawn an interactive session with:
180 If the question does not allow interactive sessions, this
183 if getattr(self.question, 'allow_interactive', False):
184 if not self._tempdir:
185 self._tempdir = self.question.setup_tempdir()
187 self._tempdir.invoke(
188 interpreter='/bin/sh', text=arg, stdout=None, stderr=None,
189 universal_newlines=False,
190 env=self.question.get_environment())
191 except (KeyboardInterrupt, _error.CommandError) as e:
192 if isinstance(e, KeyboardInterrupt):
193 LOG.warning('KeyboardInterrupt')
196 self._tempdir.cleanup()
199 def do_quit(self, arg):
200 "Stop taking the quiz"
204 def do_skip(self, arg):
205 "Skip the current question, and continue with the quiz"
206 self.ui.stack[self.ui.user].append(self.question)
207 return self.get_question()
209 def do_hint(self, arg):
210 "Show a hint for the current question"
212 print(self.question.format_help())
214 def do_copyright(self, arg):
215 "Print the quiz copyright notice"
216 if self.ui.quiz.copyright:
217 print('\n'.join(self.ui.quiz.copyright))
219 print(self.ui.quiz.copyright)
221 def do_help(self, arg):
222 'List available commands with "help" or detailed help with "help cmd"'
224 print('\n'.join(self._help))
225 super(QuestionCommandLine, self).do_help(arg)
228 class CommandLineInterface (_UserInterface):
229 colors = { # listed in pygments.console.light_colors
230 'question': 'turquoise',
239 if self.stack[self.user]:
240 cmd = QuestionCommandLine(ui=self)
243 self._display_results()
245 def _display_results(self):
246 print(_colorize(self.colors['result'], 'results:'))
247 answers = self.answers.get_answers(user=self.user)
248 for question in self.quiz:
249 if question.id in answers:
250 self._display_result(question=question)
252 self._display_totals()
254 def _display_result(self, question):
255 answers = self.answers.get_answers(user=self.user).get(question.id, [])
259 self.colors['question'],
260 question.format_prompt(newline='\n '))))
262 lc = len([a for a in answers if a['correct']])
264 print('answers: {}/{} ({:.2f})'.format(lc, la, float(lc)/la))
265 for answer in answers:
266 if answer['correct']:
269 correct = 'incorrect'
270 correct = _colorize(self.colors[correct], correct)
271 ans = answer['answer']
272 if question.multiline:
273 ans = '\n '.join(ans)
274 print(' you answered: {}'.format(ans))
275 print(' which was: {}'.format(correct))
277 def _display_totals(self):
278 answered = self.answers.get_answered(
279 questions=self.quiz, user=self.user)
280 correctly_answered = self.answers.get_correctly_answered(
281 questions=self.quiz, user=self.user)
283 lc = len(correctly_answered)
284 print('answered {} of {} questions'.format(la, len(self.quiz)))
286 print(('of the answered questions, '
287 '{} ({:.2f}) were answered correctly'
288 ).format(lc, float(lc)/la))