Commit 939be670c9e323d2967031b2ea4ba56c32c4d729

Authored by Miguel Barão
1 parent cad28d33
Exists in dev

update jquery. move links to include-libs.html

aprendizations/__init__.py
1 -# Copyright (C) 2019 Miguel Barão 1 +# Copyright © 2022 Miguel Barão
2 # 2 #
3 # THE MIT License 3 # THE MIT License
4 # 4 #
@@ -30,10 +30,10 @@ are progressively uncovered as the students progress. @@ -30,10 +30,10 @@ are progressively uncovered as the students progress.
30 ''' 30 '''
31 31
32 APP_NAME = 'aprendizations' 32 APP_NAME = 'aprendizations'
33 -APP_VERSION = '2022.08.dev1' 33 +APP_VERSION = '2022.12.dev1'
34 APP_DESCRIPTION = __doc__ 34 APP_DESCRIPTION = __doc__
35 35
36 __author__ = 'Miguel Barão' 36 __author__ = 'Miguel Barão'
37 -__copyright__ = 'Copyright 2021, Miguel Barão' 37 +__copyright__ = 'Copyright © 2022, Miguel Barão'
38 __license__ = 'MIT license' 38 __license__ = 'MIT license'
39 __version__ = APP_VERSION 39 __version__ = APP_VERSION
aprendizations/serve.py
@@ -11,6 +11,7 @@ import mimetypes @@ -11,6 +11,7 @@ import mimetypes
11 from os.path import join, dirname, expanduser 11 from os.path import join, dirname, expanduser
12 import signal 12 import signal
13 import sys 13 import sys
  14 +import re
14 from typing import List, Optional, Union 15 from typing import List, Optional, Union
15 import uuid 16 import uuid
16 17
@@ -21,7 +22,7 @@ import tornado.web @@ -21,7 +22,7 @@ import tornado.web
21 from tornado.escape import to_unicode 22 from tornado.escape import to_unicode
22 23
23 # this project 24 # this project
24 -from aprendizations.tools import md_to_html 25 +from aprendizations.renderer_markdown import md_to_html
25 from aprendizations.learnapp import LearnException 26 from aprendizations.learnapp import LearnException
26 from aprendizations import APP_NAME 27 from aprendizations import APP_NAME
27 28
@@ -82,6 +83,7 @@ class BaseHandler(tornado.web.RequestHandler): @@ -82,6 +83,7 @@ class BaseHandler(tornado.web.RequestHandler):
82 ''' 83 '''
83 Base handler common to all handlers. 84 Base handler common to all handlers.
84 ''' 85 '''
  86 +
85 @property 87 @property
86 def learn(self): 88 def learn(self):
87 '''easier access to learnapp''' 89 '''easier access to learnapp'''
@@ -105,6 +107,7 @@ class RankingsHandler(BaseHandler): @@ -105,6 +107,7 @@ class RankingsHandler(BaseHandler):
105 ''' 107 '''
106 Handles rankings page 108 Handles rankings page
107 ''' 109 '''
  110 +
108 @tornado.web.authenticated 111 @tornado.web.authenticated
109 def get(self) -> None: 112 def get(self) -> None:
110 ''' 113 '''
@@ -130,33 +133,30 @@ class LoginHandler(BaseHandler): @@ -130,33 +133,30 @@ class LoginHandler(BaseHandler):
130 ''' 133 '''
131 Handles /login 134 Handles /login
132 ''' 135 '''
  136 +
133 def get(self) -> None: 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 async def post(self): 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 passwd = self.get_body_argument('pw') 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,7 +167,7 @@ class LogoutHandler(BaseHandler):
167 @tornado.web.authenticated 167 @tornado.web.authenticated
168 def get(self) -> None: 168 def get(self) -> None:
169 ''' 169 '''
170 - clears cookies and removes user session 170 + clear cookies and user session
171 ''' 171 '''
172 self.clear_cookie('user') 172 self.clear_cookie('user')
173 self.clear_cookie('counter') 173 self.clear_cookie('counter')
@@ -182,14 +182,14 @@ class ChangePasswordHandler(BaseHandler): @@ -182,14 +182,14 @@ class ChangePasswordHandler(BaseHandler):
182 ''' 182 '''
183 Handles password change 183 Handles password change
184 ''' 184 '''
  185 +
185 @tornado.web.authenticated 186 @tornado.web.authenticated
186 async def post(self) -> None: 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 userid = self.current_user 191 userid = self.current_user
191 passwd = self.get_body_arguments('new_password')[0] 192 passwd = self.get_body_arguments('new_password')[0]
192 -  
193 changed_ok = await self.learn.change_password(userid, passwd) 193 changed_ok = await self.learn.change_password(userid, passwd)
194 if changed_ok: 194 if changed_ok:
195 notification = self.render_string( 195 notification = self.render_string(
@@ -203,7 +203,6 @@ class ChangePasswordHandler(BaseHandler): @@ -203,7 +203,6 @@ class ChangePasswordHandler(BaseHandler):
203 type='danger', 203 type='danger',
204 msg='A password não foi alterada!' 204 msg='A password não foi alterada!'
205 ) 205 )
206 -  
207 self.write({'msg': to_unicode(notification)}) 206 self.write({'msg': to_unicode(notification)})
208 207
209 208
@@ -212,9 +211,10 @@ class RootHandler(BaseHandler): @@ -212,9 +211,10 @@ class RootHandler(BaseHandler):
212 ''' 211 '''
213 Handles root / 212 Handles root /
214 ''' 213 '''
  214 +
215 @tornado.web.authenticated 215 @tornado.web.authenticated
216 def get(self) -> None: 216 def get(self) -> None:
217 - '''Simply redirects to the main entrypoint''' 217 + '''Redirect to main entrypoint'''
218 self.redirect('/courses') 218 self.redirect('/courses')
219 219
220 220
@@ -223,12 +223,13 @@ class CoursesHandler(BaseHandler): @@ -223,12 +223,13 @@ class CoursesHandler(BaseHandler):
223 ''' 223 '''
224 Handles /courses 224 Handles /courses
225 ''' 225 '''
  226 +
226 def set_default_headers(self, *_) -> None: 227 def set_default_headers(self, *_) -> None:
227 self.set_header('Cache-Control', 'no-cache') 228 self.set_header('Cache-Control', 'no-cache')
228 229
229 @tornado.web.authenticated 230 @tornado.web.authenticated
230 def get(self) -> None: 231 def get(self) -> None:
231 - '''Renders list of available courses''' 232 + '''Render available courses'''
232 uid = self.current_user 233 uid = self.current_user
233 self.render('courses.html', 234 self.render('courses.html',
234 appname=APP_NAME, 235 appname=APP_NAME,
@@ -285,7 +286,7 @@ class TopicHandler(BaseHandler): @@ -285,7 +286,7 @@ class TopicHandler(BaseHandler):
285 Starts a given topic 286 Starts a given topic
286 ''' 287 '''
287 uid = self.current_user 288 uid = self.current_user
288 - 289 + logger.debug('[TopicHandler.get] %s', topic)
289 try: 290 try:
290 await self.learn.start_topic(uid, topic) # FIXME GET should not modify state... 291 await self.learn.start_topic(uid, topic) # FIXME GET should not modify state...
291 except KeyError: 292 except KeyError:
@@ -304,15 +305,18 @@ class FileHandler(BaseHandler): @@ -304,15 +305,18 @@ class FileHandler(BaseHandler):
304 ''' 305 '''
305 Serves files from the /public subdir of the topics. 306 Serves files from the /public subdir of the topics.
306 ''' 307 '''
  308 +
307 @tornado.web.authenticated 309 @tornado.web.authenticated
308 async def get(self, filename) -> None: 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 uid = self.current_user 314 uid = self.current_user
313 public_dir = self.learn.get_current_public_dir(uid) 315 public_dir = self.learn.get_current_public_dir(uid)
314 filepath = expanduser(join(public_dir, filename)) 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 try: 320 try:
317 with open(filepath, 'rb') as file: 321 with open(filepath, 'rb') as file:
318 data = file.read() 322 data = file.read()
@@ -350,10 +354,9 @@ class QuestionHandler(BaseHandler): @@ -350,10 +354,9 @@ class QuestionHandler(BaseHandler):
350 @tornado.web.authenticated 354 @tornado.web.authenticated
351 async def get(self) -> None: 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 user = self.current_user 360 user = self.current_user
358 question = await self.learn.get_question(user) 361 question = await self.learn.get_question(user)
359 362
@@ -387,7 +390,7 @@ class QuestionHandler(BaseHandler): @@ -387,7 +390,7 @@ class QuestionHandler(BaseHandler):
387 @tornado.web.authenticated 390 @tornado.web.authenticated
388 async def post(self) -> None: 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 Does not move to next question. 394 Does not move to next question.
392 ''' 395 '''
393 user = self.current_user 396 user = self.current_user
@@ -422,18 +425,16 @@ class QuestionHandler(BaseHandler): @@ -422,18 +425,16 @@ class QuestionHandler(BaseHandler):
422 # --- check answer (nonblocking) and get corrected question and action 425 # --- check answer (nonblocking) and get corrected question and action
423 question = await self.learn.check_answer(user, ans) 426 question = await self.learn.check_answer(user, ans)
424 427
425 - # --- built response to return 428 + # --- build response
426 response = {'method': question['status'], 'params': {}} 429 response = {'method': question['status'], 'params': {}}
427 430
428 if question['status'] == 'right': # get next question in the topic 431 if question['status'] == 'right': # get next question in the topic
429 comments = self.render_string('comments-right.html', 432 comments = self.render_string('comments-right.html',
430 comments=question['comments'], 433 comments=question['comments'],
431 md=md_to_html) 434 md=md_to_html)
432 -  
433 solution = self.render_string('solution.html', 435 solution = self.render_string('solution.html',
434 solution=question['solution'], 436 solution=question['solution'],
435 md=md_to_html) 437 md=md_to_html)
436 -  
437 response['params'] = { 438 response['params'] = {
438 'type': question['type'], 439 'type': question['type'],
439 'progress': self.learn.get_student_progress(user), 440 'progress': self.learn.get_student_progress(user),
@@ -445,7 +446,6 @@ class QuestionHandler(BaseHandler): @@ -445,7 +446,6 @@ class QuestionHandler(BaseHandler):
445 comments = self.render_string('comments.html', 446 comments = self.render_string('comments.html',
446 comments=question['comments'], 447 comments=question['comments'],
447 md=md_to_html) 448 md=md_to_html)
448 -  
449 response['params'] = { 449 response['params'] = {
450 'type': question['type'], 450 'type': question['type'],
451 'progress': self.learn.get_student_progress(user), 451 'progress': self.learn.get_student_progress(user),
@@ -456,10 +456,8 @@ class QuestionHandler(BaseHandler): @@ -456,10 +456,8 @@ class QuestionHandler(BaseHandler):
456 comments = self.render_string('comments.html', 456 comments = self.render_string('comments.html',
457 comments=question['comments'], 457 comments=question['comments'],
458 md=md_to_html) 458 md=md_to_html)
459 -  
460 solution = self.render_string( 459 solution = self.render_string(
461 'solution.html', solution=question['solution'], md=md_to_html) 460 'solution.html', solution=question['solution'], md=md_to_html)
462 -  
463 response['params'] = { 461 response['params'] = {
464 'type': question['type'], 462 'type': question['type'],
465 'progress': self.learn.get_student_progress(user), 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,7 +499,7 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None:
501 sys.exit(1) 499 sys.exit(1)
502 logger.info('Web application started (tornado.web.Application)') 500 logger.info('Web application started (tornado.web.Application)')
503 501
504 - # --- create tornado webserver 502 + # --- create tornado http server
505 try: 503 try:
506 httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl) 504 httpserver = tornado.httpserver.HTTPServer(webapp, ssl_options=ssl)
507 except ValueError: 505 except ValueError:
@@ -515,9 +513,11 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None: @@ -515,9 +513,11 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None:
515 logger.critical('Cannot bind port %d. Already in use?', port) 513 logger.critical('Cannot bind port %d. Already in use?', port)
516 sys.exit(1) 514 sys.exit(1)
517 logger.info('Webserver listening on %d... (Ctrl-C to stop)', port) 515 logger.info('Webserver listening on %d... (Ctrl-C to stop)', port)
  516 +
  517 + # --- set signal handler for Control-C
518 signal.signal(signal.SIGINT, signal_handler) 518 signal.signal(signal.SIGINT, signal_handler)
519 519
520 - # --- run webserver 520 + # --- run tornado webserver
521 try: 521 try:
522 tornado.ioloop.IOLoop.current().start() # running... 522 tornado.ioloop.IOLoop.current().start() # running...
523 except Exception: 523 except Exception:
aprendizations/templates/include-libs.html
1 <!-- jquery --> 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 <!-- bootstrap --> 4 <!-- bootstrap -->
5 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> 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,3 +7,14 @@
7 7
8 <!-- bootstrap icons --> 8 <!-- bootstrap icons -->
9 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.2/font/bootstrap-icons.css"> 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,13 +3,14 @@
3 3
4 {% block answer %} 4 {% block answer %}
5 <fieldset data-role="controlgroup"> 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 </div> 14 </div>
14 </fieldset> 15 </fieldset>
15 <input type="hidden" name="qid" value="{{ question['qid'] }}"> 16 <input type="hidden" name="qid" value="{{ question['qid'] }}">
aprendizations/templates/question-radio.html
@@ -5,12 +5,12 @@ @@ -5,12 +5,12 @@
5 <fieldset data-role="controlgroup"> 5 <fieldset data-role="controlgroup">
6 <div class="list-group"> 6 <div class="list-group">
7 {% for n,opt in enumerate(question['options']) %} 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 </label> 12 </label>
13 - </label> 13 + </li>
14 {% end %} 14 {% end %}
15 </div> 15 </div>
16 </fieldset> 16 </fieldset>
aprendizations/templates/topic.html
@@ -8,27 +8,6 @@ @@ -8,27 +8,6 @@
8 8
9 {% include include-libs.html %} 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 <!-- local --> 11 <!-- local -->
33 <link rel="stylesheet" href="{{static_url('css/github.css')}}" /> 12 <link rel="stylesheet" href="{{static_url('css/github.css')}}" />
34 <link rel="stylesheet" href="{{static_url('css/topic.css')}}" /> 13 <link rel="stylesheet" href="{{static_url('css/topic.css')}}" />
@@ -99,7 +78,7 @@ @@ -99,7 +78,7 @@
99 <div id="solution_wrong"></div> 78 <div id="solution_wrong"></div>
100 </div> 79 </div>
101 </div> 80 </div>
102 - <!-- button reponder / continuar --> 81 + <!-- button responder / continuar -->
103 <div class="d-grid gap-2"> 82 <div class="d-grid gap-2">
104 <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> 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 </div> 84 </div>
aprendizations/tools.py
@@ -3,15 +3,15 @@ @@ -3,15 +3,15 @@
3 import asyncio 3 import asyncio
4 import logging 4 import logging
5 from os import path 5 from os import path
6 -import re 6 +# import re
7 import subprocess 7 import subprocess
8 from typing import Any, List 8 from typing import Any, List
9 9
10 # third party libraries 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 import yaml 15 import yaml
16 16
17 17
@@ -28,119 +28,119 @@ logger = logging.getLogger(__name__) @@ -28,119 +28,119 @@ logger = logging.getLogger(__name__)
28 # Block math: 28 # Block math:
29 # $$x$$ or \begin{equation}x\end{equation} 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 # load data from yaml file 146 # load data from yaml file
@@ -21,7 +21,7 @@ setup( @@ -21,7 +21,7 @@ setup(
21 python_requires='>=3.9.*', 21 python_requires='>=3.9.*',
22 install_requires=[ 22 install_requires=[
23 'tornado>=6.0', 23 'tornado>=6.0',
24 - 'mistune<2', 24 + 'mistune>=3',
25 'pyyaml>=5.1', 25 'pyyaml>=5.1',
26 'pygments', 26 'pygments',
27 'sqlalchemy>=1.4', 27 'sqlalchemy>=1.4',