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