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 | +# ]) | ... | ... |