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 | 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 | 8 | - qual a diferenca entre md_to_html e md_to_html_review, parece desnecessario haver dois. |
| 5 | 9 | - servir imagens das perguntas |
| 6 | 10 | - hints nao funciona | ... | ... |
demo/questions/questions-tutorial.yaml
| ... | ... | @@ -42,7 +42,8 @@ |
| 42 | 42 | ref: tut-alert |
| 43 | 43 | type: alert |
| 44 | 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 | 49 | ref: tut-radio | ... | ... |
serve.py
| ... | ... | @@ -18,7 +18,7 @@ import tornado.httpserver |
| 18 | 18 | from tornado import template, gen |
| 19 | 19 | |
| 20 | 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 | 22 | from app import App, AppException |
| 23 | 23 | |
| 24 | 24 | |
| ... | ... | @@ -98,7 +98,9 @@ class FileHandler(BaseHandler): |
| 98 | 98 | def get(self): |
| 99 | 99 | uid = self.current_user |
| 100 | 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 | 104 | self.write(self.testapp.get_file(ref, filename)) |
| 103 | 105 | |
| 104 | 106 | |
| ... | ... | @@ -222,7 +224,7 @@ class ReviewHandler(BaseHandler): |
| 222 | 224 | else: |
| 223 | 225 | with f: |
| 224 | 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 | 230 | # --- FILE ------------------------------------------------------------- | ... | ... |
templates/question-checkbox.html
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | {% for n, opt in enumerate(q['options']) %} |
| 8 | 8 | <a class="list-group-item"> |
| 9 | 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 | 11 | </a> |
| 12 | 12 | {% end %} |
| 13 | 13 | </div> | ... | ... |
templates/question-radio.html
| ... | ... | @@ -7,7 +7,7 @@ |
| 7 | 7 | {% for n, opt in enumerate(q['options']) %} |
| 8 | 8 | <a class="list-group-item"> |
| 9 | 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 | 11 | </a> |
| 12 | 12 | {% end %} |
| 13 | 13 | </div> | ... | ... |
templates/question.html
templates/review.html
templates/test.html
| ... | ... | @@ -76,9 +76,6 @@ |
| 76 | 76 | <div class="col-12"> |
| 77 | 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 | 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 | 79 | </div> |
| 83 | 80 | </form> |
| 84 | 81 | <hr> |
| ... | ... | @@ -105,25 +102,6 @@ |
| 105 | 102 | </div> |
| 106 | 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 | 4 | import logging |
| 5 | 5 | |
| 6 | 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 | 13 | # setup logger for this module |
| 10 | 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 | 36 | # load data from yaml file |
| 14 | 37 | # --------------------------------------------------------------------------- |
| ... | ... | @@ -16,7 +39,7 @@ def load_yaml(filename, default=None): |
| 16 | 39 | try: |
| 17 | 40 | f = open(path.expanduser(filename), 'r', encoding='utf-8') |
| 18 | 41 | except IOError: |
| 19 | - logger.error('Can\'t open file "{}"'.format(filename)) | |
| 42 | + logger.error(f'Can\'t open file "{filename}"') | |
| 20 | 43 | return default |
| 21 | 44 | else: |
| 22 | 45 | with f: |
| ... | ... | @@ -29,7 +52,6 @@ def load_yaml(filename, default=None): |
| 29 | 52 | |
| 30 | 53 | # --------------------------------------------------------------------------- |
| 31 | 54 | # Runs a script and returns its stdout parsed as yaml, or None on error. |
| 32 | -# Note: requires python 3.5+ | |
| 33 | 55 | # --------------------------------------------------------------------------- |
| 34 | 56 | def run_script(script, stdin='', timeout=5): |
| 35 | 57 | script = path.expanduser(script) |
| ... | ... | @@ -42,45 +64,48 @@ def run_script(script, stdin='', timeout=5): |
| 42 | 64 | timeout=timeout, |
| 43 | 65 | ) |
| 44 | 66 | except FileNotFoundError: |
| 45 | - logger.error('Script not found: "{0}".'.format(script)) | |
| 67 | + logger.error(f'Script "{script}" not found.') | |
| 46 | 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 | 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 | 72 | else: |
| 51 | 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 | 75 | else: |
| 54 | 76 | try: |
| 55 | 77 | output = yaml.load(p.stdout) |
| 56 | 78 | except: |
| 57 | - logger.error('Error parsing yaml output of script "{0}"'.format(script)) | |
| 79 | + logger.error('Error parsing yaml output of "{script}"') | |
| 58 | 80 | else: |
| 59 | 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 | +# ]) | ... | ... |