_state_attributes = [
'id',
'prompt',
+ 'multimedia',
'answer',
'accept_all',
'multiline',
def __setstate__(self, state):
if 'id' not in state:
state['id'] = state.get('prompt', None)
- if 'dependencies' not in state:
- state['dependencies'] = []
if 'tags' not in state:
state['tags'] = set()
else:
for attr in ['accept_all', 'multiline']:
if attr not in state:
state[attr] = False
+ for attr in ['dependencies', 'multimedia']:
+ if attr not in state:
+ state[attr] = []
for attr in self._state_attributes:
if attr not in state:
state[attr] = None
import codecs as _codecs
import json as _json
+import os.path as _os_path
from . import __version__
from . import question as _question
raise NotImplementedError(
'multiple questions with one ID: {}'.format(matches))
+ def multimedia_path(self, multimedia):
+ if 'path' in multimedia:
+ basedir = _os_path.dirname(self.path)
+ path = multimedia['path']
+ if _os_path.sep != '/': # convert to native separators
+ path = path.replace('/', _os_path.sep)
+ return _os_path.join(basedir, multimedia['path'])
+ else:
+ raise NotImplementedError(question.multimedia)
+
def _upgrade_from_0_1(self, data):
data['version'] = __version__
return data
# quizzer. If not, see <http://www.gnu.org/licenses/>.
import cmd as _cmd
+import logging as _logging
+import os.path as _os_path
try:
import readline as _readline
except ImportError as _readline_import_error:
from .. import error as _error
from .. import question as _question
from . import UserInterface as _UserInterface
+from . import util as _util
+
+
+_LOG = _logging.getLogger(__name__)
class QuestionCommandLine (_cmd.Cmd):
if self.ui.quiz.introduction:
self.intro = '\n\n'.join([self.intro, self.ui.quiz.introduction])
self._tempdir = None
+ self._children = []
def get_question(self):
self.question = self.ui.get_question(user=self.ui.user)
def preloop(self):
self.get_question()
+ def postcmd(self, stop, line):
+ self._reap_children()
+ return stop
+
def _reset(self):
self.answers = []
if self._tempdir:
self.prompt = _colorize(self.ui.colors['prompt'], self._prompt)
def _extra_ps1_lines(self):
+ for multimedia in self.question.multimedia:
+ for line in self._format_multimedia(multimedia):
+ yield line # for Python 3.3, use PEP 380's `yield from ...`
if (isinstance(self.question, _question.ChoiceQuestion) and
self.question.display_choices):
- for i,choice in enumerate(self.question.answer):
- yield '{}) {}'.format(i, choice)
- yield 'Answer with the index of your choice'
- if self.question.accept_all:
- conj = 'or'
- if self.question.multiple_answers:
- conj = 'and/or'
- yield '{} fill in an alternative answer'.format(conj)
- if self.question.multiple_answers:
- self._separator = ','
- yield ("Separate multiple answers with the '{}' character"
- ).format(self._separator)
+ for line in self._format_choices(question=self.question):
+ yield line
+
+ def _format_multimedia(self, multimedia):
+ path = self.ui.quiz.multimedia_path(multimedia=multimedia)
+ content_type = multimedia['content-type']
+ try:
+ self._children.append(_util.mailcap_view(
+ path=path, content_type=content_type, background=True))
+ except NotImplementedError:
+ path = _os_path.abspath(path)
+ yield 'multimedia ({}): {}'.format(content_type, path)
+
+ def _reap_children(self):
+ reaped = []
+ for process in self._children:
+ _LOG.debug('poll child process {}'.format(process.pid))
+ if process.poll() is not None:
+ _LOG.debug('process {} returned {}'.format(
+ process.pid, process.returncode))
+ reaped.append(process)
+ for process in reaped:
+ self._children.remove(process)
+
+ def _format_choices(self, question):
+ for i,choice in enumerate(question.answer):
+ yield '{}) {}'.format(i, choice)
+ yield 'Answer with the index of your choice'
+ if question.accept_all:
+ conj = 'or'
+ if question.multiple_answers:
+ conj = 'and/or'
+ yield '{} fill in an alternative answer'.format(conj)
+ if question.multiple_answers:
+ self._separator = ','
+ yield ("Separate multiple answers with the '{}' character"
+ ).format(self._separator)
def _process_answer(self, answer):
"Back out any mappings suggested by _extra_ps1_lines()"
def do_copyright(self, arg):
"Print the quiz copyright notice"
- if self.ui.quiz.copyright:
+ if self.ui.quiz.copight:
print('\n'.join(self.ui.quiz.copyright))
else:
print(self.ui.quiz.copyright)
--- /dev/null
+# Copyright
+
+"""View files using mailcap-specified commands
+"""
+
+import logging as _logging
+import mailcap as _mailcap
+import mimetypes as _mimetypes
+import shlex as _shlex
+if not hasattr(_shlex, 'quote'): # Python < 3.3
+ import pipes as _pipes
+ _shlex.quote = _pipes.quote
+import subprocess as _subprocess
+
+
+_LOG = _logging.getLogger(__name__)
+_CAPS = _mailcap.getcaps()
+
+
+def mailcap_view(path, content_type=None, background=False):
+ if content_type is None:
+ content_type,encoding = _mimetypes.guess_type(path)
+ if content_type is None:
+ return 1
+ _LOG.debug('guessed {} for {}'.format(content_type, path))
+ match = _mailcap.findmatch(
+ _CAPS, content_type, filename=_shlex.quote(path))
+ if match[0] is None:
+ _LOG.warn('no mailcap viewer found for {}'.format(content_type))
+ raise NotImplementedError(content_type)
+ _LOG.debug('view {} with: {}'.format(path, match[0]))
+ process = _subprocess.Popen(match[0], shell=True)
+ if background:
+ return process
+ return process.wait()
# You should have received a copy of the GNU General Public License along with
# quizzer. If not, see <http://www.gnu.org/licenses/>.
+import hashlib as _hashlib
import logging as _logging
+import os.path as _os_path
import select as _select
import socket as _socket
import re as _re
('^question/', self._question),
('^answer/', self._answer),
('^results/', self._results),
+ ('^media/([^/]+)', self._media),
],
**kwargs)
self.ui = ui
self.user_regexp = _re.compile('^\w+$')
+ self._local_media = {}
def _index(self, environ, start_response):
lines = [
lc = len([a for a in answers if a['correct']])
lines = [
'<h2>Question</h2>',
- '<p>{}</p>'.format(question.format_prompt(newline='<br />')),
+ self._format_prompt(question=question),
'<p>Answers: {}/{} ({:.2f})</p>'.format(lc, la, float(lc)/la),
]
if answers:
' <input type="hidden" name="user" value="{}">'.format(user),
' <input type="hidden" name="question" value="{}">'.format(
question.id),
- ' <p>{}</p>'.format(
- question.format_prompt(newline='<br/>')),
+ self._format_prompt(question=question),
answer_element,
' <br />',
' <input type="submit" value="submit">',
' </head>',
' <body>',
' <h1>Answer</h1>',
- ' <p>{}</p>'.format(
- question.format_prompt(newline='<br/>')),
+ self._format_prompt(question=question),
' <pre>{}</pre>'.format(print_answer),
' <p>{}</p>'.format(correct_msg),
details or '',
environ, start_response, content=content, encoding='utf-8',
content_type='text/html')
+ def _format_prompt(self, question):
+ lines = ['<p>{}</p>'.format(question.format_prompt(newline='<br/>\n'))]
+ for multimedia in question.multimedia:
+ lines.append(self._format_multimedia(multimedia=multimedia))
+ return '\n'.join(lines)
+
+ def _format_multimedia(self, multimedia):
+ content_type = multimedia['content-type']
+ if 'path' in multimedia:
+ uid = _hashlib.sha1(
+ str(multimedia).encode('unicode-escape')
+ ).hexdigest()
+ path = self.ui.quiz.multimedia_path(multimedia)
+ self._local_media[uid] = {
+ 'content-type': content_type,
+ 'path': path,
+ }
+ url = '../media/{}'.format(uid)
+ else:
+ raise NotImplementedError(multimedia)
+ if content_type.startswith('image/'):
+ return '<p><img src="{}" /></p>'.format(url)
+ else:
+ raise NotImplementedError(content_type)
+
+ def _media(self, environ, start_response):
+ try:
+ uid, = environ['{}.url_args'.format(self.setting)]
+ except:
+ raise HandlerError(404, 'Not Found')
+ if uid not in self._local_media:
+ raise HandlerError(404, 'Not Found')
+ content_type = self._local_media[uid]['content-type']
+ with open(self._local_media[uid]['path'], 'rb') as f:
+ content = f.read()
+ return self.ok_response(
+ environ, start_response, content=content,
+ content_type=content_type)
+
class HTMLInterface (_UserInterface):
def run(self, host='', port=8000):