Commit c6da5826e844b004f99c1f91a9e557e633f125c1
1 parent
bae2d15e
Exists in
master
and in
1 other branch
- using mistune to render markdown.
Showing
9 changed files
with
71 additions
and
61 deletions
Show diff stats
BUGS.md
| 1 | 1 | ||
| 2 | # BUGS | 2 | # BUGS |
| 3 | 3 | ||
| 4 | +- fazer renderer para formulas com mathjax serverside (mathjax-node). | ||
| 5 | +- fazer renderer para imagens, com links /file?ref=xpto;name=zzz.jpg | ||
| 6 | +- fazer renderer para linguagem assembly mips? | ||
| 7 | +- converter markdown para mistune. | ||
| 4 | - qual a diferenca entre md_to_html e md_to_html_review, parece desnecessario haver dois. | 8 | - qual a diferenca entre md_to_html e md_to_html_review, parece desnecessario haver dois. |
| 5 | - servir imagens das perguntas | 9 | - servir imagens das perguntas |
| 6 | - hints nao funciona | 10 | - hints nao funciona |
demo/questions/questions-tutorial.yaml
| @@ -42,7 +42,8 @@ | @@ -42,7 +42,8 @@ | ||
| 42 | ref: tut-alert | 42 | ref: tut-alert |
| 43 | type: alert | 43 | type: alert |
| 44 | title: alert | 44 | title: alert |
| 45 | - text: Texto negativo (alerta). Não conta para avaliação. | 45 | + text: Texto negativo (alerta). Não conta para avaliação.  |
| 46 | + | ||
| 46 | # ---------------------------------------------------------------------------- | 47 | # ---------------------------------------------------------------------------- |
| 47 | - | 48 | - |
| 48 | ref: tut-radio | 49 | ref: tut-radio |
serve.py
| @@ -18,7 +18,7 @@ import tornado.httpserver | @@ -18,7 +18,7 @@ import tornado.httpserver | ||
| 18 | from tornado import template, gen | 18 | from tornado import template, gen |
| 19 | 19 | ||
| 20 | # project | 20 | # project |
| 21 | -from tools import load_yaml, md_to_html, md_to_html_review | 21 | +from tools import load_yaml, md_to_html #, md_to_html_review |
| 22 | from app import App, AppException | 22 | from app import App, AppException |
| 23 | 23 | ||
| 24 | 24 | ||
| @@ -98,7 +98,9 @@ class FileHandler(BaseHandler): | @@ -98,7 +98,9 @@ class FileHandler(BaseHandler): | ||
| 98 | def get(self): | 98 | def get(self): |
| 99 | uid = self.current_user | 99 | uid = self.current_user |
| 100 | qref = self.get_query_argument('ref') | 100 | qref = self.get_query_argument('ref') |
| 101 | - qfile = self.get_query_argument('filename') | 101 | + qfile = self.get_query_argument('file') |
| 102 | + print(f'FileHandler: ref={ref}, file={file}') | ||
| 103 | + | ||
| 102 | self.write(self.testapp.get_file(ref, filename)) | 104 | self.write(self.testapp.get_file(ref, filename)) |
| 103 | 105 | ||
| 104 | 106 | ||
| @@ -222,7 +224,7 @@ class ReviewHandler(BaseHandler): | @@ -222,7 +224,7 @@ class ReviewHandler(BaseHandler): | ||
| 222 | else: | 224 | else: |
| 223 | with f: | 225 | with f: |
| 224 | t = json.load(f) | 226 | t = json.load(f) |
| 225 | - self.render('review.html', t=t, md=md_to_html_review, templ=self._templates) | 227 | + self.render('review.html', t=t, md=md_to_html, templ=self._templates) |
| 226 | 228 | ||
| 227 | 229 | ||
| 228 | # --- FILE ------------------------------------------------------------- | 230 | # --- FILE ------------------------------------------------------------- |
templates/question-checkbox.html
| @@ -7,7 +7,7 @@ | @@ -7,7 +7,7 @@ | ||
| 7 | {% for n, opt in enumerate(q['options']) %} | 7 | {% for n, opt in enumerate(q['options']) %} |
| 8 | <a class="list-group-item"> | 8 | <a class="list-group-item"> |
| 9 | <input type="checkbox" id="{{i}}:{{n}}" name="{{i}}" value="{{n}}"> | 9 | <input type="checkbox" id="{{i}}:{{n}}" name="{{i}}" value="{{n}}"> |
| 10 | - <label for="{{n}}">{{ md(opt, q['ref'], q['files']) }}</label> | 10 | + <label for="{{n}}">{{ md(opt, q) }}</label> |
| 11 | </a> | 11 | </a> |
| 12 | {% end %} | 12 | {% end %} |
| 13 | </div> | 13 | </div> |
templates/question-radio.html
| @@ -7,7 +7,7 @@ | @@ -7,7 +7,7 @@ | ||
| 7 | {% for n, opt in enumerate(q['options']) %} | 7 | {% for n, opt in enumerate(q['options']) %} |
| 8 | <a class="list-group-item"> | 8 | <a class="list-group-item"> |
| 9 | <input type="radio" id="{{i}}:{{n}}" name="{{i}}" value="{{n}}"> | 9 | <input type="radio" id="{{i}}:{{n}}" name="{{i}}" value="{{n}}"> |
| 10 | - <label for="{{n}}">{{ md(opt, q['ref'], q['files']) }}</label> | 10 | + <label for="{{n}}">{{ md(opt, q) }}</label> |
| 11 | </a> | 11 | </a> |
| 12 | {% end %} | 12 | {% end %} |
| 13 | </div> | 13 | </div> |
templates/question.html
templates/review.html
| @@ -34,7 +34,7 @@ | @@ -34,7 +34,7 @@ | ||
| 34 | <div class="container"> | 34 | <div class="container"> |
| 35 | <div class="jumbotron"> | 35 | <div class="jumbotron"> |
| 36 | <h1 class="display-5">{{ t['title'] }}</h1> | 36 | <h1 class="display-5">{{ t['title'] }}</h1> |
| 37 | - {{ t.get('duration', '') }} | 37 | + {{ t.get('duration', chr(8734)) }} |
| 38 | <hr> | 38 | <hr> |
| 39 | 39 | ||
| 40 | <h3> | 40 | <h3> |
templates/test.html
| @@ -76,9 +76,6 @@ | @@ -76,9 +76,6 @@ | ||
| 76 | <div class="col-12"> | 76 | <div class="col-12"> |
| 77 | <button type="button" class="btn btn-success btn-lg btn-block" data-toggle="modal" data-target="#confirmar" id="form-button-submit">Submeter teste</button> | 77 | <button type="button" class="btn btn-success btn-lg btn-block" data-toggle="modal" data-target="#confirmar" id="form-button-submit">Submeter teste</button> |
| 78 | </div> | 78 | </div> |
| 79 | -<!-- <div class="col-2"> | ||
| 80 | - <button type="button" class="btn btn-danger btn-lg btn-block" data-toggle="modal" data-target="#sair" id="form-button-sair">Desisto</button> | ||
| 81 | - </div> --> | ||
| 82 | </div> | 79 | </div> |
| 83 | </form> | 80 | </form> |
| 84 | <hr> | 81 | <hr> |
| @@ -105,25 +102,6 @@ | @@ -105,25 +102,6 @@ | ||
| 105 | </div> | 102 | </div> |
| 106 | </div> | 103 | </div> |
| 107 | 104 | ||
| 108 | -<!-- Modal de confirmacao sair --> | ||
| 109 | -<div class="modal fade" id="sair" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> | ||
| 110 | - <div class="modal-dialog" role="document"> | ||
| 111 | - <div class="modal-content"> | ||
| 112 | - <div class="modal-header"> | ||
| 113 | - <h4 class="modal-title">Deseja desistir?</h4> | ||
| 114 | - </div> | ||
| 115 | - <div class="modal-body"> | ||
| 116 | - Se desistir, será registada a desistência da prova e terá uma classificação de 0 valores. | ||
| 117 | - </div> | ||
| 118 | - <div class="modal-footer"> | ||
| 119 | - <button type="button" class="btn btn-danger btn-lg" data-dismiss="modal">Não!</button> | ||
| 120 | - <a href="/giveup" class="btn btn-success btn-lg" role="button">Sim, desisto</a> | ||
| 121 | - </div> | ||
| 122 | - </div> | ||
| 123 | - </div> | ||
| 124 | -</div> | ||
| 125 | - | ||
| 126 | - | ||
| 127 | 105 | ||
| 128 | <!-- ===================================================================== --> | 106 | <!-- ===================================================================== --> |
| 129 | 107 |
tools.py
| @@ -4,11 +4,34 @@ import subprocess | @@ -4,11 +4,34 @@ import subprocess | ||
| 4 | import logging | 4 | import logging |
| 5 | 5 | ||
| 6 | import yaml | 6 | import yaml |
| 7 | -import markdown | 7 | +# import markdown |
| 8 | +import mistune | ||
| 9 | +from pygments import highlight | ||
| 10 | +from pygments.lexers import get_lexer_by_name | ||
| 11 | +from pygments.formatters import html | ||
| 8 | 12 | ||
| 9 | # setup logger for this module | 13 | # setup logger for this module |
| 10 | logger = logging.getLogger(__name__) | 14 | logger = logging.getLogger(__name__) |
| 11 | 15 | ||
| 16 | + | ||
| 17 | +# --------------------------------------------------------------------------- | ||
| 18 | +class HighlightRenderer(mistune.Renderer): | ||
| 19 | + def block_code(self, code, lang=None): | ||
| 20 | + if lang is None: | ||
| 21 | + return f'\n<pre><code>{mistune.escape(code)}</code></pre>\n' | ||
| 22 | + else: | ||
| 23 | + lexer = get_lexer_by_name(lang, stripall=True) | ||
| 24 | + formatter = html.HtmlFormatter() | ||
| 25 | + return highlight(code, lexer, formatter) | ||
| 26 | + | ||
| 27 | + def image(self, src, title, text): | ||
| 28 | + src = 'FIXME' # FIXME | ||
| 29 | + return super().image(src, title, text) | ||
| 30 | + | ||
| 31 | +renderer = HighlightRenderer(hard_wrap=True) | ||
| 32 | +markdown = mistune.Markdown(renderer=renderer) | ||
| 33 | + | ||
| 34 | + | ||
| 12 | # --------------------------------------------------------------------------- | 35 | # --------------------------------------------------------------------------- |
| 13 | # load data from yaml file | 36 | # load data from yaml file |
| 14 | # --------------------------------------------------------------------------- | 37 | # --------------------------------------------------------------------------- |
| @@ -16,7 +39,7 @@ def load_yaml(filename, default=None): | @@ -16,7 +39,7 @@ def load_yaml(filename, default=None): | ||
| 16 | try: | 39 | try: |
| 17 | f = open(path.expanduser(filename), 'r', encoding='utf-8') | 40 | f = open(path.expanduser(filename), 'r', encoding='utf-8') |
| 18 | except IOError: | 41 | except IOError: |
| 19 | - logger.error('Can\'t open file "{}"'.format(filename)) | 42 | + logger.error(f'Can\'t open file "{filename}"') |
| 20 | return default | 43 | return default |
| 21 | else: | 44 | else: |
| 22 | with f: | 45 | with f: |
| @@ -29,7 +52,6 @@ def load_yaml(filename, default=None): | @@ -29,7 +52,6 @@ def load_yaml(filename, default=None): | ||
| 29 | 52 | ||
| 30 | # --------------------------------------------------------------------------- | 53 | # --------------------------------------------------------------------------- |
| 31 | # Runs a script and returns its stdout parsed as yaml, or None on error. | 54 | # Runs a script and returns its stdout parsed as yaml, or None on error. |
| 32 | -# Note: requires python 3.5+ | ||
| 33 | # --------------------------------------------------------------------------- | 55 | # --------------------------------------------------------------------------- |
| 34 | def run_script(script, stdin='', timeout=5): | 56 | def run_script(script, stdin='', timeout=5): |
| 35 | script = path.expanduser(script) | 57 | script = path.expanduser(script) |
| @@ -42,45 +64,48 @@ def run_script(script, stdin='', timeout=5): | @@ -42,45 +64,48 @@ def run_script(script, stdin='', timeout=5): | ||
| 42 | timeout=timeout, | 64 | timeout=timeout, |
| 43 | ) | 65 | ) |
| 44 | except FileNotFoundError: | 66 | except FileNotFoundError: |
| 45 | - logger.error('Script not found: "{0}".'.format(script)) | 67 | + logger.error(f'Script "{script}" not found.') |
| 46 | except PermissionError: | 68 | except PermissionError: |
| 47 | - logger.error('Script "{0}" not executable (wrong permissions?).'.format(script)) | 69 | + logger.error(f'Script "{script}" not executable. Wrong permissions?') |
| 48 | except subprocess.TimeoutExpired: | 70 | except subprocess.TimeoutExpired: |
| 49 | - logger.error('Timeout {0}s exceeded while running script "{1}"'.format(timeout, script)) | 71 | + logger.error(f'Timeout {timeout}s exceeded while running "{script}".') |
| 50 | else: | 72 | else: |
| 51 | if p.returncode != 0: | 73 | if p.returncode != 0: |
| 52 | - logger.error('Script "{0}" returned error code {1}.'.format(script, p.returncode)) | 74 | + logger.error(f'Script "{script}" returned error code {p.returncode}.') |
| 53 | else: | 75 | else: |
| 54 | try: | 76 | try: |
| 55 | output = yaml.load(p.stdout) | 77 | output = yaml.load(p.stdout) |
| 56 | except: | 78 | except: |
| 57 | - logger.error('Error parsing yaml output of script "{0}"'.format(script)) | 79 | + logger.error('Error parsing yaml output of "{script}"') |
| 58 | else: | 80 | else: |
| 59 | return output | 81 | return output |
| 60 | 82 | ||
| 61 | # --------------------------------------------------------------------------- | 83 | # --------------------------------------------------------------------------- |
| 62 | -def md_to_html(text, ref=None, files={}): | ||
| 63 | - if ref is not None: | ||
| 64 | - # given q['ref'] and q['files'] replaces references to files by a | ||
| 65 | - # GET to /file?ref=???;name=??? | ||
| 66 | - for k in files: | ||
| 67 | - text = text.replace(k, '/file?ref={};name={}'.format(ref, k)) | ||
| 68 | - return markdown.markdown(text, extensions=[ | ||
| 69 | - 'markdown.extensions.tables', | ||
| 70 | - 'markdown.extensions.fenced_code', | ||
| 71 | - 'markdown.extensions.codehilite', | ||
| 72 | - 'markdown.extensions.def_list', | ||
| 73 | - 'markdown.extensions.sane_lists' | ||
| 74 | - ]) | 84 | +def md_to_html(text, q=None): |
| 85 | + return markdown(text) | ||
| 86 | + | ||
| 87 | +# def md_to_html(text, ref=None, files={}): | ||
| 88 | +# if ref is not None: | ||
| 89 | +# # given q['ref'] and q['files'] replaces references to files by a | ||
| 90 | +# # GET to /file?ref=???;name=??? | ||
| 91 | +# for k in files: | ||
| 92 | +# text = text.replace(k, '/file?ref={};name={}'.format(ref, k)) | ||
| 93 | +# return markdown.markdown(text, extensions=[ | ||
| 94 | +# 'markdown.extensions.tables', | ||
| 95 | +# 'markdown.extensions.fenced_code', | ||
| 96 | +# 'markdown.extensions.codehilite', | ||
| 97 | +# 'markdown.extensions.def_list', | ||
| 98 | +# 'markdown.extensions.sane_lists' | ||
| 99 | +# ]) | ||
| 75 | 100 | ||
| 76 | # --------------------------------------------------------------------------- | 101 | # --------------------------------------------------------------------------- |
| 77 | -def md_to_html_review(text, q): | ||
| 78 | - for k,f in q['files'].items(): | ||
| 79 | - text = text.replace(k, '/absfile?name={}'.format(q['files'][k])) | ||
| 80 | - return markdown.markdown(text, extensions=[ | ||
| 81 | - 'markdown.extensions.tables', | ||
| 82 | - 'markdown.extensions.fenced_code', | ||
| 83 | - 'markdown.extensions.codehilite', | ||
| 84 | - 'markdown.extensions.def_list', | ||
| 85 | - 'markdown.extensions.sane_lists' | ||
| 86 | - ]) | 102 | +# def md_to_html_review(text, q): |
| 103 | +# for k,f in q['files'].items(): | ||
| 104 | +# text = text.replace(k, '/absfile?name={}'.format(q['files'][k])) | ||
| 105 | +# return markdown.markdown(text, extensions=[ | ||
| 106 | +# 'markdown.extensions.tables', | ||
| 107 | +# 'markdown.extensions.fenced_code', | ||
| 108 | +# 'markdown.extensions.codehilite', | ||
| 109 | +# 'markdown.extensions.def_list', | ||
| 110 | +# 'markdown.extensions.sane_lists' | ||
| 111 | +# ]) |