Commit 939be670c9e323d2967031b2ea4ba56c32c4d729
1 parent
cad28d33
Exists in
dev
update jquery. move links to include-libs.html
Showing
8 changed files
with
188 additions
and
197 deletions
Show diff stats
aprendizations/__init__.py
1 | -# Copyright (C) 2019 Miguel Barão | |
1 | +# Copyright © 2022 Miguel Barão | |
2 | 2 | # |
3 | 3 | # THE MIT License |
4 | 4 | # |
... | ... | @@ -30,10 +30,10 @@ are progressively uncovered as the students progress. |
30 | 30 | ''' |
31 | 31 | |
32 | 32 | APP_NAME = 'aprendizations' |
33 | -APP_VERSION = '2022.08.dev1' | |
33 | +APP_VERSION = '2022.12.dev1' | |
34 | 34 | APP_DESCRIPTION = __doc__ |
35 | 35 | |
36 | 36 | __author__ = 'Miguel Barão' |
37 | -__copyright__ = 'Copyright 2021, Miguel Barão' | |
37 | +__copyright__ = 'Copyright © 2022, Miguel Barão' | |
38 | 38 | __license__ = 'MIT license' |
39 | 39 | __version__ = APP_VERSION | ... | ... |
aprendizations/serve.py
... | ... | @@ -11,6 +11,7 @@ import mimetypes |
11 | 11 | from os.path import join, dirname, expanduser |
12 | 12 | import signal |
13 | 13 | import sys |
14 | +import re | |
14 | 15 | from typing import List, Optional, Union |
15 | 16 | import uuid |
16 | 17 | |
... | ... | @@ -21,7 +22,7 @@ import tornado.web |
21 | 22 | from tornado.escape import to_unicode |
22 | 23 | |
23 | 24 | # this project |
24 | -from aprendizations.tools import md_to_html | |
25 | +from aprendizations.renderer_markdown import md_to_html | |
25 | 26 | from aprendizations.learnapp import LearnException |
26 | 27 | from aprendizations import APP_NAME |
27 | 28 | |
... | ... | @@ -82,6 +83,7 @@ class BaseHandler(tornado.web.RequestHandler): |
82 | 83 | ''' |
83 | 84 | Base handler common to all handlers. |
84 | 85 | ''' |
86 | + | |
85 | 87 | @property |
86 | 88 | def learn(self): |
87 | 89 | '''easier access to learnapp''' |
... | ... | @@ -105,6 +107,7 @@ class RankingsHandler(BaseHandler): |
105 | 107 | ''' |
106 | 108 | Handles rankings page |
107 | 109 | ''' |
110 | + | |
108 | 111 | @tornado.web.authenticated |
109 | 112 | def get(self) -> None: |
110 | 113 | ''' |
... | ... | @@ -130,33 +133,30 @@ class LoginHandler(BaseHandler): |
130 | 133 | ''' |
131 | 134 | Handles /login |
132 | 135 | ''' |
136 | + | |
133 | 137 | def get(self) -> None: |
134 | 138 | ''' |
135 | - Renders login page | |
139 | + Render login page | |
136 | 140 | ''' |
137 | - self.render('login.html', | |
138 | - appname=APP_NAME, | |
139 | - error='') | |
141 | + self.render('login.html', appname=APP_NAME, error='') | |
140 | 142 | |
141 | 143 | async def post(self): |
142 | 144 | ''' |
143 | - Perform authentication and redirects to application if successful | |
145 | + Authenticate and redirect to application if successful | |
144 | 146 | ''' |
145 | - | |
146 | - userid = (self.get_body_argument('uid') or '').lstrip('l') | |
147 | + userid = self.get_body_argument('uid') or '' | |
147 | 148 | passwd = self.get_body_argument('pw') |
148 | - | |
149 | - login_ok = await self.learn.login(userid, passwd) | |
150 | - | |
151 | - if login_ok: | |
152 | - counter = str(self.learn.get_login_counter(userid)) | |
153 | - self.set_secure_cookie('aprendizations_user', userid) | |
154 | - self.set_secure_cookie('counter', counter) | |
155 | - self.redirect('/') | |
156 | - else: | |
157 | - self.render('login.html', | |
158 | - appname=APP_NAME, | |
159 | - error='Número ou senha incorrectos') | |
149 | + match = re.search(r'[0-9]+', userid) # extract number | |
150 | + if match is not None: | |
151 | + userid = match.group(0) # get string with number | |
152 | + if await self.learn.login(userid, passwd): | |
153 | + counter = str(self.learn.get_login_counter(userid)) | |
154 | + self.set_secure_cookie('aprendizations_user', userid) | |
155 | + self.set_secure_cookie('counter', counter) | |
156 | + self.redirect('/') | |
157 | + self.render('login.html', | |
158 | + appname=APP_NAME, | |
159 | + error='Número ou senha incorrectos') | |
160 | 160 | |
161 | 161 | |
162 | 162 | # ---------------------------------------------------------------------------- |
... | ... | @@ -167,7 +167,7 @@ class LogoutHandler(BaseHandler): |
167 | 167 | @tornado.web.authenticated |
168 | 168 | def get(self) -> None: |
169 | 169 | ''' |
170 | - clears cookies and removes user session | |
170 | + clear cookies and user session | |
171 | 171 | ''' |
172 | 172 | self.clear_cookie('user') |
173 | 173 | self.clear_cookie('counter') |
... | ... | @@ -182,14 +182,14 @@ class ChangePasswordHandler(BaseHandler): |
182 | 182 | ''' |
183 | 183 | Handles password change |
184 | 184 | ''' |
185 | + | |
185 | 186 | @tornado.web.authenticated |
186 | 187 | async def post(self) -> None: |
187 | 188 | ''' |
188 | - Tries to perform password change and then replies success/fail status | |
189 | + Try to change password and show success/fail status | |
189 | 190 | ''' |
190 | 191 | userid = self.current_user |
191 | 192 | passwd = self.get_body_arguments('new_password')[0] |
192 | - | |
193 | 193 | changed_ok = await self.learn.change_password(userid, passwd) |
194 | 194 | if changed_ok: |
195 | 195 | notification = self.render_string( |
... | ... | @@ -203,7 +203,6 @@ class ChangePasswordHandler(BaseHandler): |
203 | 203 | type='danger', |
204 | 204 | msg='A password não foi alterada!' |
205 | 205 | ) |
206 | - | |
207 | 206 | self.write({'msg': to_unicode(notification)}) |
208 | 207 | |
209 | 208 | |
... | ... | @@ -212,9 +211,10 @@ class RootHandler(BaseHandler): |
212 | 211 | ''' |
213 | 212 | Handles root / |
214 | 213 | ''' |
214 | + | |
215 | 215 | @tornado.web.authenticated |
216 | 216 | def get(self) -> None: |
217 | - '''Simply redirects to the main entrypoint''' | |
217 | + '''Redirect to main entrypoint''' | |
218 | 218 | self.redirect('/courses') |
219 | 219 | |
220 | 220 | |
... | ... | @@ -223,12 +223,13 @@ class CoursesHandler(BaseHandler): |
223 | 223 | ''' |
224 | 224 | Handles /courses |
225 | 225 | ''' |
226 | + | |
226 | 227 | def set_default_headers(self, *_) -> None: |
227 | 228 | self.set_header('Cache-Control', 'no-cache') |
228 | 229 | |
229 | 230 | @tornado.web.authenticated |
230 | 231 | def get(self) -> None: |
231 | - '''Renders list of available courses''' | |
232 | + '''Render available courses''' | |
232 | 233 | uid = self.current_user |
233 | 234 | self.render('courses.html', |
234 | 235 | appname=APP_NAME, |
... | ... | @@ -285,7 +286,7 @@ class TopicHandler(BaseHandler): |
285 | 286 | Starts a given topic |
286 | 287 | ''' |
287 | 288 | uid = self.current_user |
288 | - | |
289 | + logger.debug('[TopicHandler.get] %s', topic) | |
289 | 290 | try: |
290 | 291 | await self.learn.start_topic(uid, topic) # FIXME GET should not modify state... |
291 | 292 | except KeyError: |
... | ... | @@ -304,15 +305,18 @@ class FileHandler(BaseHandler): |
304 | 305 | ''' |
305 | 306 | Serves files from the /public subdir of the topics. |
306 | 307 | ''' |
308 | + | |
307 | 309 | @tornado.web.authenticated |
308 | 310 | async def get(self, filename) -> None: |
309 | 311 | ''' |
310 | - Serves files from /public subdirectories of a particular topic | |
312 | + Serve file from the /public subdirectory of a particular topic | |
311 | 313 | ''' |
312 | 314 | uid = self.current_user |
313 | 315 | public_dir = self.learn.get_current_public_dir(uid) |
314 | 316 | filepath = expanduser(join(public_dir, filename)) |
315 | 317 | |
318 | + logger.debug('uid=%s, public_dir=%s, filepath=%s', uid, public_dir, | |
319 | + filepath) | |
316 | 320 | try: |
317 | 321 | with open(filepath, 'rb') as file: |
318 | 322 | data = file.read() |
... | ... | @@ -350,10 +354,9 @@ class QuestionHandler(BaseHandler): |
350 | 354 | @tornado.web.authenticated |
351 | 355 | async def get(self) -> None: |
352 | 356 | ''' |
353 | - Gets question to render. | |
354 | - Shows an animated trophy if there are no more questions in the topic. | |
357 | + Get question to render or an animated trophy if no more questions. | |
355 | 358 | ''' |
356 | - logger.debug('[QuestionHandler]') | |
359 | + logger.debug('[QuestionHandler.get]') | |
357 | 360 | user = self.current_user |
358 | 361 | question = await self.learn.get_question(user) |
359 | 362 | |
... | ... | @@ -387,7 +390,7 @@ class QuestionHandler(BaseHandler): |
387 | 390 | @tornado.web.authenticated |
388 | 391 | async def post(self) -> None: |
389 | 392 | ''' |
390 | - Corrects answer and returns status: right, wrong, try_again | |
393 | + Correct answer and return status: right, wrong, try_again | |
391 | 394 | Does not move to next question. |
392 | 395 | ''' |
393 | 396 | user = self.current_user |
... | ... | @@ -422,18 +425,16 @@ class QuestionHandler(BaseHandler): |
422 | 425 | # --- check answer (nonblocking) and get corrected question and action |
423 | 426 | question = await self.learn.check_answer(user, ans) |
424 | 427 | |
425 | - # --- built response to return | |
428 | + # --- build response | |
426 | 429 | response = {'method': question['status'], 'params': {}} |
427 | 430 | |
428 | 431 | if question['status'] == 'right': # get next question in the topic |
429 | 432 | comments = self.render_string('comments-right.html', |
430 | 433 | comments=question['comments'], |
431 | 434 | md=md_to_html) |
432 | - | |
433 | 435 | solution = self.render_string('solution.html', |
434 | 436 | solution=question['solution'], |
435 | 437 | md=md_to_html) |
436 | - | |
437 | 438 | response['params'] = { |
438 | 439 | 'type': question['type'], |
439 | 440 | 'progress': self.learn.get_student_progress(user), |
... | ... | @@ -445,7 +446,6 @@ class QuestionHandler(BaseHandler): |
445 | 446 | comments = self.render_string('comments.html', |
446 | 447 | comments=question['comments'], |
447 | 448 | md=md_to_html) |
448 | - | |
449 | 449 | response['params'] = { |
450 | 450 | 'type': question['type'], |
451 | 451 | 'progress': self.learn.get_student_progress(user), |
... | ... | @@ -456,10 +456,8 @@ class QuestionHandler(BaseHandler): |
456 | 456 | comments = self.render_string('comments.html', |
457 | 457 | comments=question['comments'], |
458 | 458 | md=md_to_html) |
459 | - | |
460 | 459 | solution = self.render_string( |
461 | 460 | 'solution.html', solution=question['solution'], md=md_to_html) |
462 | - | |
463 | 461 | response['params'] = { |
464 | 462 | 'type': question['type'], |
465 | 463 | 'progress': self.learn.get_student_progress(user), |
... | ... | @@ -501,7 +499,7 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: |
501 | 499 | sys.exit(1) |
502 | 500 | logger.info('Web application started (tornado.web.Application)') |
503 | 501 | |
504 | - # --- create tornado webserver | |
502 | + # --- create tornado http server | |
505 | 503 | try: |
506 | 504 | httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl) |
507 | 505 | except ValueError: |
... | ... | @@ -515,9 +513,11 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: |
515 | 513 | logger.critical('Cannot bind port %d. Already in use?', port) |
516 | 514 | sys.exit(1) |
517 | 515 | logger.info('Webserver listening on %d... (Ctrl-C to stop)', port) |
516 | + | |
517 | + # --- set signal handler for Control-C | |
518 | 518 | signal.signal(signal.SIGINT, signal_handler) |
519 | 519 | |
520 | - # --- run webserver | |
520 | + # --- run tornado webserver | |
521 | 521 | try: |
522 | 522 | tornado.ioloop.IOLoop.current().start() # running... |
523 | 523 | except Exception: | ... | ... |
aprendizations/templates/include-libs.html
1 | 1 | <!-- jquery --> |
2 | -<script src="https://code.jquery.com/jquery-3.6.1.min.js" integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script> | |
2 | +<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script> | |
3 | 3 | |
4 | 4 | <!-- bootstrap --> |
5 | 5 | <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> |
... | ... | @@ -7,3 +7,14 @@ |
7 | 7 | |
8 | 8 | <!-- bootstrap icons --> |
9 | 9 | <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css"> |
10 | + | |
11 | +<!-- MathJax --> | |
12 | +<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> | |
13 | +<script async type="text/javascript" id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> | |
14 | + | |
15 | +<!-- codemirror --> | |
16 | +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.css" /> | |
17 | +<script defer src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.js"></script> | |
18 | + | |
19 | +<!-- animate --> | |
20 | +<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | ... | ... |
aprendizations/templates/question-checkbox.html
... | ... | @@ -3,13 +3,14 @@ |
3 | 3 | |
4 | 4 | {% block answer %} |
5 | 5 | <fieldset data-role="controlgroup"> |
6 | - <div class="list-group"> | |
7 | - {% for n,opt in enumerate(question['options']) %} | |
8 | - <label class="list-group-item list-group-item-action"> | |
9 | - <input type="checkbox" class="form-check-input" id="{{ n }}" accesskey="{{ n+1 }}" name="answer" value="{{ n }}"> | |
10 | - {{ md(opt, strip_p_tag=True) }} | |
11 | - </label> | |
12 | - {% end %} | |
6 | + <div class="list-group"> | |
7 | + {% for n,opt in enumerate(question['options']) %} | |
8 | + <li class="list-group-item"> | |
9 | + <input class="form-check-input" type="checkbox" id="{{ n }}" accesskey="{{ n+1 }}" name="answer" value="{{ n }}"> | |
10 | + <label class="form-check-label" for="{{ n }}"> | |
11 | + {{ md(opt).removeprefix('<p>').removesuffix('</p>') }} | |
12 | + </label> | |
13 | + {% end %} | |
13 | 14 | </div> |
14 | 15 | </fieldset> |
15 | 16 | <input type="hidden" name="qid" value="{{ question['qid'] }}"> | ... | ... |
aprendizations/templates/question-radio.html
... | ... | @@ -5,12 +5,12 @@ |
5 | 5 | <fieldset data-role="controlgroup"> |
6 | 6 | <div class="list-group"> |
7 | 7 | {% for n,opt in enumerate(question['options']) %} |
8 | - <label class="list-group-item list-group-item-action"> | |
9 | - <input type="radio" class="form-check-input" id="{{ n }}" accesskey="{{ n+1 }}" name="answer" value="{{ n }}"> | |
10 | - <label for="{{ n }}" class="custom-control-label"> | |
11 | - {{ md(opt, strip_p_tag=True) }} | |
8 | + <li class="list-group-item"> | |
9 | + <input class="form-check-input" type="radio" name="answer" value="{{ n }}" id="{{ n }}"> | |
10 | + <label class="form-check-label stretched-link" for="{{ n }}"> | |
11 | + {{ md(opt).removeprefix('<p>').removesuffix('</p>') }} | |
12 | 12 | </label> |
13 | - </label> | |
13 | + </li> | |
14 | 14 | {% end %} |
15 | 15 | </div> |
16 | 16 | </fieldset> | ... | ... |
aprendizations/templates/topic.html
... | ... | @@ -8,27 +8,6 @@ |
8 | 8 | |
9 | 9 | {% include include-libs.html %} |
10 | 10 | |
11 | - <!-- mathjax --> | |
12 | - <script> | |
13 | - MathJax = { | |
14 | - tex: { | |
15 | - inlineMath: [['$$$', '$$$'], ['\\(', '\\)']] | |
16 | - }, | |
17 | - svg: { | |
18 | - fontCache: 'global' | |
19 | - } | |
20 | - }; | |
21 | - </script> | |
22 | - <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> | |
23 | - <script async type="text/javascript" id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> | |
24 | - | |
25 | - <!-- codemirror --> | |
26 | - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.css" /> | |
27 | - <script defer src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.63.3/codemirror.min.js"></script> | |
28 | - | |
29 | - <!-- animate --> | |
30 | - <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" /> | |
31 | - | |
32 | 11 | <!-- local --> |
33 | 12 | <link rel="stylesheet" href="{{static_url('css/github.css')}}" /> |
34 | 13 | <link rel="stylesheet" href="{{static_url('css/topic.css')}}" /> |
... | ... | @@ -99,7 +78,7 @@ |
99 | 78 | <div id="solution_wrong"></div> |
100 | 79 | </div> |
101 | 80 | </div> |
102 | - <!-- button reponder / continuar --> | |
81 | + <!-- button responder / continuar --> | |
103 | 82 | <div class="d-grid gap-2"> |
104 | 83 | <button type="submit" class="btn btn-primary btn-lg btn-block my-5 shadow bg-gradient" id="submit" data-bs-toggle="button" href="#solution" style="display: none"></button> |
105 | 84 | </div> | ... | ... |
aprendizations/tools.py
... | ... | @@ -3,15 +3,15 @@ |
3 | 3 | import asyncio |
4 | 4 | import logging |
5 | 5 | from os import path |
6 | -import re | |
6 | +# import re | |
7 | 7 | import subprocess |
8 | 8 | from typing import Any, List |
9 | 9 | |
10 | 10 | # third party libraries |
11 | -import mistune | |
12 | -from pygments import highlight | |
13 | -from pygments.lexers import get_lexer_by_name | |
14 | -from pygments.formatters import HtmlFormatter | |
11 | +# import mistune | |
12 | +# from pygments import highlight | |
13 | +# from pygments.lexers import get_lexer_by_name | |
14 | +# from pygments.formatters import HtmlFormatter | |
15 | 15 | import yaml |
16 | 16 | |
17 | 17 | |
... | ... | @@ -28,119 +28,119 @@ logger = logging.getLogger(__name__) |
28 | 28 | # Block math: |
29 | 29 | # $$x$$ or \begin{equation}x\end{equation} |
30 | 30 | # ------------------------------------------------------------------------- |
31 | -class MathBlockGrammar(mistune.BlockGrammar): | |
32 | - block_math = re.compile(r'^\$\$(.*?)\$\$', re.DOTALL) | |
33 | - latex_environment = re.compile(r'^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}', | |
34 | - re.DOTALL) | |
35 | - | |
36 | - | |
37 | -class MathBlockLexer(mistune.BlockLexer): | |
38 | - default_rules = ['block_math', 'latex_environment'] \ | |
39 | - + mistune.BlockLexer.default_rules | |
40 | - | |
41 | - def __init__(self, rules=None, **kwargs): | |
42 | - if rules is None: | |
43 | - rules = MathBlockGrammar() | |
44 | - super().__init__(rules, **kwargs) | |
45 | - | |
46 | - def parse_block_math(self, m): | |
47 | - '''Parse a $$math$$ block''' | |
48 | - self.tokens.append({ | |
49 | - 'type': 'block_math', | |
50 | - 'text': m.group(1) | |
51 | - }) | |
52 | - | |
53 | - def parse_latex_environment(self, m): | |
54 | - r'''Parse an environment \begin{name}text\end{name}''' | |
55 | - self.tokens.append({ | |
56 | - 'type': 'latex_environment', | |
57 | - 'name': m.group(1), | |
58 | - 'text': m.group(2) | |
59 | - }) | |
60 | - | |
61 | - | |
62 | -# ------------------------------------------------------------------------- | |
63 | -# Inline math: $x$ | |
64 | -# ------------------------------------------------------------------------- | |
65 | -class MathInlineGrammar(mistune.InlineGrammar): | |
66 | - math = re.compile(r'^\$(.+?)\$', re.DOTALL) | |
67 | - block_math = re.compile(r'^\$\$(.+?)\$\$', re.DOTALL) | |
68 | - text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)') | |
69 | - | |
70 | - | |
71 | -class MathInlineLexer(mistune.InlineLexer): | |
72 | - default_rules = ['block_math', 'math'] + mistune.InlineLexer.default_rules | |
73 | - | |
74 | - def __init__(self, renderer, rules=None, **kwargs): | |
75 | - if rules is None: | |
76 | - rules = MathInlineGrammar() | |
77 | - super().__init__(renderer, rules, **kwargs) | |
78 | - | |
79 | - def output_math(self, m): | |
80 | - return self.renderer.inline_math(m.group(1)) | |
81 | - | |
82 | - def output_block_math(self, m): | |
83 | - return self.renderer.block_math(m.group(1)) | |
84 | - | |
85 | - | |
86 | -class MarkdownWithMath(mistune.Markdown): | |
87 | - def __init__(self, renderer, **kwargs): | |
88 | - if 'inline' not in kwargs: | |
89 | - kwargs['inline'] = MathInlineLexer | |
90 | - if 'block' not in kwargs: | |
91 | - kwargs['block'] = MathBlockLexer | |
92 | - super().__init__(renderer, **kwargs) | |
93 | - | |
94 | - def output_block_math(self): | |
95 | - return self.renderer.block_math(self.token['text']) | |
96 | - | |
97 | - def output_latex_environment(self): | |
98 | - return self.renderer.latex_environment(self.token['name'], | |
99 | - self.token['text']) | |
100 | - | |
101 | - | |
102 | -class HighlightRenderer(mistune.Renderer): | |
103 | - def block_code(self, code, lang='text'): | |
104 | - try: | |
105 | - lexer = get_lexer_by_name(lang, stripall=False) | |
106 | - except Exception: | |
107 | - lexer = get_lexer_by_name('text', stripall=False) | |
108 | - | |
109 | - formatter = HtmlFormatter() | |
110 | - return highlight(code, lexer, formatter) | |
111 | - | |
112 | - def table(self, header, body): | |
113 | - return ('<table class="table table-sm"><thead class="thead-light">' | |
114 | - f'{header}</thead><tbody>{body}</tbody></table>') | |
115 | - | |
116 | - def image(self, src, title, alt): | |
117 | - alt = mistune.escape(alt, quote=True) | |
118 | - title = mistune.escape(title or '', quote=True) | |
119 | - return (f'<img src="/file/{src}" alt="{alt}" title="{title}"' | |
120 | - f'class="img-fluid">') # class="img-fluid mx-auto d-block" | |
121 | - | |
122 | - # Pass math through unaltered - mathjax does the rendering in the browser | |
123 | - def block_math(self, text): | |
124 | - return fr'\[ {text} \]' | |
125 | - | |
126 | - def latex_environment(self, name, text): | |
127 | - return fr'\begin{{{name}}} {text} \end{{{name}}}' | |
128 | - | |
129 | - def inline_math(self, text): | |
130 | - return fr'\( {text} \)' | |
131 | - | |
132 | - | |
133 | -# hard_wrap=True to insert <br> on newline | |
134 | -markdown = MarkdownWithMath(HighlightRenderer(escape=True)) | |
135 | - | |
136 | - | |
137 | -def md_to_html(text: str, strip_p_tag: bool = False) -> str: | |
138 | - md: str = markdown(text) | |
139 | - if strip_p_tag and md.startswith('<p>') and md.endswith('</p>\n'): | |
140 | - return md[3:-5] | |
141 | - else: | |
142 | - return md | |
143 | - | |
31 | +# class MathBlockGrammar(mistune.BlockGrammar): | |
32 | +# block_math = re.compile(r'^\$\$(.*?)\$\$', re.DOTALL) | |
33 | +# latex_environment = re.compile(r'^\\begin\{([a-z]*\*?)\}(.*?)\\end\{\1\}', | |
34 | +# re.DOTALL) | |
35 | +# | |
36 | +# | |
37 | +# class MathBlockLexer(mistune.BlockLexer): | |
38 | +# default_rules = ['block_math', 'latex_environment'] \ | |
39 | +# + mistune.BlockLexer.default_rules | |
40 | +# | |
41 | +# def __init__(self, rules=None, **kwargs): | |
42 | +# if rules is None: | |
43 | +# rules = MathBlockGrammar() | |
44 | +# super().__init__(rules, **kwargs) | |
45 | +# | |
46 | +# def parse_block_math(self, m): | |
47 | +# '''Parse a $$math$$ block''' | |
48 | +# self.tokens.append({ | |
49 | +# 'type': 'block_math', | |
50 | +# 'text': m.group(1) | |
51 | +# }) | |
52 | +# | |
53 | +# def parse_latex_environment(self, m): | |
54 | +# r'''Parse an environment \begin{name}text\end{name}''' | |
55 | +# self.tokens.append({ | |
56 | +# 'type': 'latex_environment', | |
57 | +# 'name': m.group(1), | |
58 | +# 'text': m.group(2) | |
59 | +# }) | |
60 | +# | |
61 | +# | |
62 | +# # ------------------------------------------------------------------------- | |
63 | +# # Inline math: $x$ | |
64 | +# # ------------------------------------------------------------------------- | |
65 | +# class MathInlineGrammar(mistune.InlineGrammar): | |
66 | +# math = re.compile(r'^\$(.+?)\$', re.DOTALL) | |
67 | +# block_math = re.compile(r'^\$\$(.+?)\$\$', re.DOTALL) | |
68 | +# text = re.compile(r'^[\s\S]+?(?=[\\<!\[_*`~$]|https?://| {2,}\n|$)') | |
69 | +# | |
70 | +# | |
71 | +# class MathInlineLexer(mistune.InlineLexer): | |
72 | +# default_rules = ['block_math', 'math'] + mistune.InlineLexer.default_rules | |
73 | +# | |
74 | +# def __init__(self, renderer, rules=None, **kwargs): | |
75 | +# if rules is None: | |
76 | +# rules = MathInlineGrammar() | |
77 | +# super().__init__(renderer, rules, **kwargs) | |
78 | +# | |
79 | +# def output_math(self, m): | |
80 | +# return self.renderer.inline_math(m.group(1)) | |
81 | +# | |
82 | +# def output_block_math(self, m): | |
83 | +# return self.renderer.block_math(m.group(1)) | |
84 | +# | |
85 | +# | |
86 | +# class MarkdownWithMath(mistune.Markdown): | |
87 | +# def __init__(self, renderer, **kwargs): | |
88 | +# if 'inline' not in kwargs: | |
89 | +# kwargs['inline'] = MathInlineLexer | |
90 | +# if 'block' not in kwargs: | |
91 | +# kwargs['block'] = MathBlockLexer | |
92 | +# super().__init__(renderer, **kwargs) | |
93 | +# | |
94 | +# def output_block_math(self): | |
95 | +# return self.renderer.block_math(self.token['text']) | |
96 | +# | |
97 | +# def output_latex_environment(self): | |
98 | +# return self.renderer.latex_environment(self.token['name'], | |
99 | +# self.token['text']) | |
100 | +# | |
101 | +# | |
102 | +# class HighlightRenderer(mistune.Renderer): | |
103 | +# def block_code(self, code, lang='text'): | |
104 | +# try: | |
105 | +# lexer = get_lexer_by_name(lang, stripall=False) | |
106 | +# except Exception: | |
107 | +# lexer = get_lexer_by_name('text', stripall=False) | |
108 | +# | |
109 | +# formatter = HtmlFormatter() | |
110 | +# return highlight(code, lexer, formatter) | |
111 | +# | |
112 | +# def table(self, header, body): | |
113 | +# return ('<table class="table table-sm"><thead class="thead-light">' | |
114 | +# f'{header}</thead><tbody>{body}</tbody></table>') | |
115 | +# | |
116 | +# def image(self, src, title, alt): | |
117 | +# alt = mistune.escape(alt, quote=True) | |
118 | +# title = mistune.escape(title or '', quote=True) | |
119 | +# return (f'<img src="/file/{src}" alt="{alt}" title="{title}"' | |
120 | +# f'class="img-fluid">') # class="img-fluid mx-auto d-block" | |
121 | +# | |
122 | +# # Pass math through unaltered - mathjax does the rendering in the browser | |
123 | +# def block_math(self, text): | |
124 | +# return fr'\[ {text} \]' | |
125 | +# | |
126 | +# def latex_environment(self, name, text): | |
127 | +# return fr'\begin{{{name}}} {text} \end{{{name}}}' | |
128 | +# | |
129 | +# def inline_math(self, text): | |
130 | +# return fr'\( {text} \)' | |
131 | +# | |
132 | +# | |
133 | +# # hard_wrap=True to insert <br> on newline | |
134 | +# markdown = MarkdownWithMath(HighlightRenderer(escape=True)) | |
135 | +# | |
136 | +# | |
137 | +# def md_to_html(text: str, strip_p_tag: bool = False) -> str: | |
138 | +# md: str = markdown(text) | |
139 | +# if strip_p_tag and md.startswith('<p>') and md.endswith('</p>\n'): | |
140 | +# return md[3:-5] | |
141 | +# else: | |
142 | +# return md | |
143 | +# | |
144 | 144 | |
145 | 145 | # --------------------------------------------------------------------------- |
146 | 146 | # load data from yaml file | ... | ... |