Commit c6da5826e844b004f99c1f91a9e557e633f125c1

Authored by Miguel Barão
1 parent bae2d15e
Exists in master and in 1 other branch dev

- using mistune to render markdown.

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. ![alt text](abc.jpg "Logo Title Text 1")
  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
... ... @@ -11,7 +11,7 @@
11 11 </h5>
12 12 <div class="card-body">
13 13 <div id="text">
14   - {{ md(q['text'], md) }}
  14 + {{ md(q['text'], q) }}
15 15 </div>
16 16  
17 17 {% block answer %}{% end %}
... ...
templates/review.html
... ... @@ -34,7 +34,7 @@
34 34 <div class="container">
35 35 <div class="jumbotron">
36 36 <h1 class="display-5">{{ t['title'] }}</h1>
37   - {{ t.get('duration', '') }}
  37 + {{ t.get('duration', chr(8734)) }}
38 38 <hr>
39 39  
40 40 <h3>
... ...
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=&#39;&#39;, 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 +# ])
... ...