diff --git a/markup/constants.py b/markup/constants.py index 88e22ff3aef835f8347ace3c3308f82cac77aefd..6084258ef204c51abdd48e062e84a6c4c561bd78 100644 --- a/markup/constants.py +++ b/markup/constants.py @@ -5,15 +5,17 @@ __license__ = "AGPL v3" # Dictionary for regex expressions to recognize reStructuredText headers. # This follows the Python conventions: order is #, *, =, -, ", ^ and # for the first two levels (# and *), over- and underlining are necessary, while -# only underlining is needed for the lower four levels. +# only underlining is needed for the lower four levels. In all cases we +# require the headline title to be at least one character long, and placed +# right above the lower headline marker. # The regex search should use the re.MULTILINE flag. ReST_HEADER_REGEX_DICT = { - '#': r'^(#{1,}\n).+\n\1', # this makes use of a regex backreference - '*': r'^(\*{1,}\n).+\n\1', # this makes use of a regex backreference - '=': r'^={1,}\n', - '-': r'^-{1,}\n', - '"': r'^"{1,}\n', - '^': r'^\^{1,}\n' + '#': r'^(#{1,}\n).{1,}\n\1', # this makes use of a regex backreference + '*': r'^(\*{1,}\n).{1,}\n\1', # this makes use of a regex backreference + '=': r'^.{1,}\n={1,}\n', # non-empty line followed by line of = + '-': r'^.{1,}\n-{1,}\n', # non-empty line followed by line of - + '"': r'^.{1,}\n"{1,}\n', # non-empty line followed by line of " + '^': r'^.{1,}\n\^{1,}\n' # non-empty line followed by line of ^ } # See list at http://docutils.sourceforge.net/0.4/docs/ref/rst/roles.html @@ -44,73 +46,6 @@ BLEACH_ALLOWED_TAGS = [ ] -MathSnippets = ( - { - 'title': 'Inline and online equations', - 'raw': -r"""Some say $e^{i\pi} + 1 = 0$ is the most beautiful equation there is. - -Simple multiplication: $a * b = c$ and $a < b$ and $c < d$. - -Ampersands: & and & are both ampersands. Lesser than: < is < -and $<$ is OK. What about AT&T? - -Do you know this famous Hamiltonian? -\[ -H = \sum_j {\boldsymbol S}_j \cdot {\boldsymbol S}_{j+1} -\] - -What about this one? -$$ -H = \int dx \left[ \partial_x \Psi^\dagger \partial_x \Psi -+ c \Psi^\dagger \Psi^\dagger \Psi \Psi \right] -$$ -""", - }, - { - 'title': 'Multiline equations', - 'raw': -r""" -<script>alert("Gotcha!");</script> - -$$ -</script><script>alert("Gotcha!");</script><script> -$$ - -Maxwell's equations: - -\[ -\begin{align*} -\nabla \cdot {\boldsymbol E} &= \frac{\rho}{\epsilon_0}, & -\nabla \times {\boldsymbol E} + \frac{\partial \boldsymbol B}{\partial t} &= 0, \\ -\nabla \cdot {\boldsymbol B} &= 0, & -\nabla \times {\boldsymbol B} - \frac{1}{c^2} \frac{\partial \boldsymbol E}{\partial t} -&= \mu_0 {\boldsymbol J} -\end{align*} -\] - -$$ -\begin{align*} -\nabla \cdot {\boldsymbol E} &= \frac{\rho}{\epsilon_0}, & -\nabla \times {\boldsymbol E} + \frac{\partial \boldsymbol B}{\partial t} &= 0, \\ -\nabla \cdot {\boldsymbol B} &= 0, & -\nabla \times {\boldsymbol B} - \frac{1}{c^2} \frac{\partial \boldsymbol E}{\partial t} -&= \mu_0 {\boldsymbol J} -\end{align*} -$$ - -$$ -\nabla \cdot {\boldsymbol E} = \frac{\rho}{\epsilon_0}, -\nabla \times {\boldsymbol E} + \frac{\partial \boldsymbol B}{\partial t} = 0, \\ -\nabla \cdot {\boldsymbol B} = 0, -\nabla \times {\boldsymbol B} - \frac{1}{c^2} \frac{\partial \boldsymbol E}{\partial t} -= \mu_0 {\boldsymbol J} -\label{eq:Maxwell} -$$ -""" - }, -) - PlainTextSnippets = { 'maths_inline_online': @@ -366,22 +301,224 @@ since beginning and end markers are not distinguishable): $$ H = \sum_j S^x_j S^x_{j+1} + S^y_j S^y_{j+1} + \Delta S^z_j S^z_{j+1} $$ + +Multiline equations can be obtained by using the ``\\`` carriage return as usual; +to align your equations, use the ``align`` environment. For example: + +\[ +\begin{align*} +\nabla \cdot {\boldsymbol E} &= \frac{\rho}{\epsilon_0}, & +\nabla \times {\boldsymbol E} + \frac{\partial \boldsymbol B}{\partial t} &= 0, \\ +\nabla \cdot {\boldsymbol B} &= 0, & +\nabla \times {\boldsymbol B} - \frac{1}{c^2} \frac{\partial \boldsymbol E}{\partial t} +&= \mu_0 {\boldsymbol J} +\end{align*} +\] """}, ) -ReStructuredTextSnippets = { - 'maths_inline_online': -r"""Inline maths -============ +ReStructuredTextSnippets = ( + { + 'id': 'paragraphs', + 'title': 'Paragraphs and line breaks', + 'raw': +"""Including an empty line between two blocks of text separates those into + +two different paragraphs. + +Typing text on consecutive lines separated +by linebreaks +will merge the lines into one +paragraph. + +As in Python, indentation is significant, so lines of a paragraph have to be +indented to the same level.""",}, + + { + 'id': 'headlines', + 'title': 'Headlines', + 'raw': +"""################## +Level 1 (html h1) +################## + +Topmost headline + +***************** +Level 2 (h2) +***************** + +two + +Level 3 (h3) +================== + +three + +Level 4 (h4) +------------------- + +four + +Level 5 (h5) +\"\"\"\"\"\"\"\"\"\"\"\"\"\"\" + +five + +Level 6 (h6) +^^^^^^^^^^^^^^^^^^^ + +six, lowest level available.""",}, + + { + 'id': 'emphasis', + 'title': 'Emphasis', + 'raw': +"""You can obtain italics with *asterisks*, +boldface using **double asterisks** and code samples using ``double backquotes``. +Note that these cannot be nested, and that there must not be a space at the +start or end of the contents. + +If you need to explicitly use these characters (namely \*), +you can escape them with a backslash.""",}, + + { + 'id': 'blockquotes', + 'title': 'Blockquotes', + 'raw': +"""It is often handy to use blockquotes. + + This is a blockquote with two paragraphs, obtained by simple + indentation from the surrounding text. For multiple lines, + each line should be indented the same. + + Here is the second paragraph, with lines indented + to the same level as the previous ones to preserve the + blockquote. + +To preserve line breaks, you can use line blocks: + +| Here is +| a small paragraph +| with linebreaks preserved. + +""",}, + + { + 'id': 'lists', + 'title': 'Lists', + 'raw': +"""reStructuredText supports unordered (bulleted) and ordered (numbered) lists. + +Unordered list items are marked with asterisk: + +* first item +* second item +* third item + +Ordered list items are marked by a number or # followed by a period: + +1. first item +2. second item +3. third item + + +Nested lists can be obtained by indentation: + +* First mainlist item + + * first sublist item + * second sublist item +* Second mainlist item + + +There are also *definition lists* obtained like this: + +term (up to a line of text) + Definition of the term, which must be indented + + and can even consist of multiple paragraphs + +next term + Description. +""",}, + + { + 'id': 'code', + 'title': 'Code', + 'raw': +"""An inline code span, to mention simple things like the +``print()`` function, is obtained by wrapping it with double backticks. + +A code block is obtained by the ``::`` marker followed by the indented code:: -For inline equations, you must use the :code:`math` role, for example :math:`E = mc^2`. + from django import forms -On-line maths -============= + class MarkupTextForm(forms.Form): + markup_text = forms.CharField() -For on-line display of equations, the :code:`math` directive must be used: + def get_processed_markup(self): + text = self.cleaned_data['markup_text'] + +which can then be followed by normal text.""",}, + + { + 'id': 'tables', + 'title': 'Tables', + 'raw': +""" +A grid table can be written by "painting" it directly: + ++------------------------+------------+----------+----------+ +| Header row, column 1 | Header 2 | Header 3 | Header 4 | +| (header rows optional) | | | | ++========================+============+==========+==========+ +| body row 1, column 1 | column 2 | column 3 | column 4 | ++------------------------+------------+----------+----------+ +| body row 2 | ... | ... | | ++------------------------+------------+----------+----------+ + +""",}, + + { + 'id': 'links', + 'title': 'Links', + 'raw': +"""Here is an example of an inline link to the `SciPost homepage <https://scipost.org/>`_. + +For example, one can also link to +a specific `Submission <https://scipost.org/submissions/1509.04230v5/>`_ +or a specific `Report <https://scipost.org/submissions/1509.04230v4/#report_2>`_. + +You can also use reference-style links when citing this `resource`_, the reference +will be resolved provided you define the link label somewhere +in your text. + +.. _resource: https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html""",}, + + { + 'id': 'mathematics', + 'title': 'Mathematics', + 'raw': +""" +For simple inline equations, use the :code:`math` role like this: :math:`E = mc^2`. + +For displayed maths, the :code:`math` directive must be used: .. math:: - H = \sum_j {\boldsymbol S}_j \cdot {\boldsymbol S}_{j+1}""" -} + H = \sum_j {\\boldsymbol S}_j \cdot {\\boldsymbol S}_{j+1} + +Multiline equations can be obtained by using the ``\\`` carriage return as usual; +to align your equations, use the ``align`` environment. For example: + +.. math:: + \\nabla \\cdot {\\boldsymbol E} = \\frac{\\rho}{\\epsilon_0}, + \\nabla \\times {\\boldsymbol E} + \\frac{\\partial \\boldsymbol B}{\\partial t} = 0, \\\\ + \\nabla \\cdot {\\boldsymbol B} = 0, + \\nabla \\times {\\boldsymbol B} - \\frac{1}{c^2} \\frac{\\partial \\boldsymbol E}{\\partial t} + = \\mu_0 {\\boldsymbol J} + + +"""}, +) diff --git a/markup/templates/markup/markdown_help.html b/markup/templates/markup/markdown_help.html index 5e0c4aea99e44a9b826bd65f714b501ddde1dffb..2f3e9bc637c62d307873770b74925a05a98a03a0 100644 --- a/markup/templates/markup/markdown_help.html +++ b/markup/templates/markup/markdown_help.html @@ -43,24 +43,4 @@ </div> </div> - {% with language='Markdown' %} - <div class="row"> - <div class="col-12"> - <h3 class="highlight">Math snippets</h3> - {{ math_snippets.length }} - {% for snippet in math_snippets %} - <h4>{{ snippet.title }}</h4> - <div class="row"> - <div class="col-lg-6"> - <pre>{{ snippet.raw }}</pre> - </div> - <div class="col-lg-6"> - {{ snippet.raw|automarkup:language }} - </div> - </div> - {% endfor %} - </div> - </div> - {% endwith %} - {% endblock content %} diff --git a/markup/templates/markup/plaintext_help.html b/markup/templates/markup/plaintext_help.html index 5a5a3acd0ca82678a9aeebc0acb25078512a1512..9282ee990a0c59491ccfd126f7e6831dbe2565a0 100644 --- a/markup/templates/markup/plaintext_help.html +++ b/markup/templates/markup/plaintext_help.html @@ -39,24 +39,4 @@ </div> </div> - {% with language='plain' %} - <div class="row"> - <div class="col-12"> - <h3 class="highlight">Math snippets</h3> - {{ math_snippets.length }} - {% for snippet in math_snippets %} - <h4>{{ snippet.title }}</h4> - <div class="row"> - <div class="col-lg-6"> - <pre>{{ snippet.raw }}</pre> - </div> - <div class="col-lg-6"> - {{ snippet.raw|automarkup:language }} - </div> - </div> - {% endfor %} - </div> - </div> - {% endwith %} - {% endblock content %} diff --git a/markup/templates/markup/restructuredtext_help.html b/markup/templates/markup/restructuredtext_help.html index e61967296f00942d0c956e7aadf0d5118af8c509..e6c4fcb272553bf050593b9a9aa4412f9bf7513d 100644 --- a/markup/templates/markup/restructuredtext_help.html +++ b/markup/templates/markup/restructuredtext_help.html @@ -16,43 +16,33 @@ <div class="col-12"> <h2 class="highlight">reStructuredText help</h2> - <h3 class="highlight" id="Mathematics">Mathematics</h3> - - <p> - For mathematics in reStructuredText, you must use the <code>math</code> - directive/role. - </p> - <div class="row"> - <div class="col-6"> - <h4><strong>If you write:</strong></h4> - <pre>{{ snippets.maths_inline_online }}</pre> - </div> - <div class="col-6"> - <h4><strong>You will get:</strong></h4> - {{ snippets.maths_inline_online|automarkup:'reStructuredText' }} + <p>You will find below a quick summary of reStructuredText basics, + as enabled here at SciPost.</p> + <p>You can find more details about reStructuredText's syntax + for example at <a href="https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html">this page</a>.</p> + + <h3>Quick links</h3> + <ul> + {% for snippet in snippets %} + <li><a href="#{{ snippet.id }}">{{ snippet.title }}</a></li> + {% endfor %} + </ul> + + {% for snippet in snippets %} + <h3 class="highlight" id="{{ snippet.id }}">{{ snippet.title }}</h3> + <div class="row"> + <div class="col-6"> + <h3><strong>If you write:</strong></h3> + <pre>{{ snippet.raw }}</pre> + </div> + <div class="col-6"> + <h3><strong>You will get:</strong></h3> + {{ snippet.raw|automarkup:'reStructuredText' }} + </div> </div> - </div> - </div> - </div> + {% endfor %} - {% with language='reStructuredText' %} - <div class="row"> - <div class="col-12"> - <h3 class="highlight">Math snippets</h3> - {{ math_snippets.length }} - {% for snippet in math_snippets %} - <h4>{{ snippet.title }}</h4> - <div class="row"> - <div class="col-lg-6"> - <pre>{{ snippet.raw }}</pre> - </div> - <div class="col-lg-6"> - {{ snippet.raw|automarkup:language }} - </div> - </div> - {% endfor %} - </div> </div> - {% endwith %} + </div> {% endblock content %} diff --git a/markup/utils.py b/markup/utils.py index 7b36be083175aa46f55056d9f17895b0b8db574b..3e04875101947c9cb966722af7f0b496872a9185 100644 --- a/markup/utils.py +++ b/markup/utils.py @@ -48,7 +48,7 @@ def match_md_header(text, level=None): raise TypeError('level must be an int') if level < 1 or level > 6: raise ValueError('level must be an integer from 1 to 6') - return re.search(r'^#{' + str(level) + ',}[ ].+$', text) + return re.search(r'^#{' + str(level) + ',}[ ].+$', text, re.MULTILINE) def match_md_blockquote(text): """Return first match of regex search for Markdown blockquote.""" @@ -88,10 +88,9 @@ def match_rst_directive(text, directive=None): return none if directive not in ReST_DIRECTIVES: raise ValueError('this directive is not listed in ReST directives') - print('regex = %s' % r'^\.\. ' + directive + '::(.+)*(\n(.+)*){1,3}') return re.search(r'^\.\. ' + directive + '::(.+)*(\n(.+)*){1,3}', text, re.MULTILINE) -def match_rst_header(text, symbol): +def match_rst_header(text, symbol=None): """ Return first match object of regex search for reStructuredText header. @@ -99,8 +98,17 @@ def match_rst_header(text, symbol): both over and underline (of equal length, so faulty ones are not matched), while the others (``=``, ``-``, ``"`` and ``^``) only have the underline. """ + if not symbol: + for newsymbol in ['#', '*', '=', '-', '"', '^']: + match = match_rst_header(text, newsymbol) + if match: + return match + return None if symbol not in ReST_HEADER_REGEX_DICT.keys(): raise ValueError('symbol is not a ReST header symbol') + print('Looking for %s in rst: %s' % ( + symbol, + re.search(ReST_HEADER_REGEX_DICT[symbol], text, re.MULTILINE))) return re.search(ReST_HEADER_REGEX_DICT[symbol], text, re.MULTILINE) @@ -161,6 +169,8 @@ def detect_markup_language(text): 'errors': None } + # Step 1: check maths + # Inline maths is of the form $ ... $ or \( ... \) inline_math = match_inline_math(text) @@ -170,9 +180,6 @@ def detect_markup_language(text): rst_math_role = match_rst_role(text, 'math') rst_math_directive = match_rst_directive(text, 'math') - md_header = match_md_header(text) - md_blockquote = match_md_blockquote(text) - if rst_math_role or rst_math_directive: # reStructuredText presumed; check for errors if inline_math: @@ -185,20 +192,39 @@ def detect_markup_language(text): 'You have mixed displayed maths ($$ ... $$ or \[ ... \]) with ' 'reStructuredText markup.\n\nPlease use one or the other, but not both!') return detector - elif md_header: - detector['errors'] = ( - 'You have mixed Markdown headers with reStructuredText math roles/directives.' - '\n\nPlease use one language only.') - elif md_blockquote: - detector['errors'] = ( - 'You have mixed Markdown blockquotes with reStructuredText math roles/directives.' - '\n\nPlease use one language only.') else: detector['language'] = 'reStructuredText' + return detector + + # no rst math from here onwards + + # Step 2: check headers and blockquotes + + md_header = match_md_header(text) + print('md_header: %s' % md_header) + md_blockquote = match_md_blockquote(text) + + rst_header = match_rst_header(text) + print('rst_header: %s' % rst_header) + + if md_header or md_blockquote: + if rst_math_role or rst_math_directive: + if md_header: + detector['errors'] = ( + 'You have mixed Markdown headers with reStructuredText math ' + 'roles/directives.\n\nPlease use one language only.') + elif md_blockquote: + detector['errors'] = ( + 'You have mixed Markdown blockquotes with reStructuredText math ' + 'roles/directives.\n\nPlease use one language only.') + detector['language'] = 'Markdown' elif md_header or md_blockquote: detector['language'] = 'Markdown' + elif rst_header: + detector['language'] = 'reStructuredText' + return detector @@ -321,6 +347,10 @@ def detect_markup_language_old(text): def apply_markdown_preserving_displayed_maths_bracket(text): + """ + Subsidiary function called by ``apply_markdown_preserving_displayed_maths``. + See explanations in docstring of that method. + """ part = text.partition(r'\[') part2 = part[2].partition(r'\]') return '%s%s%s%s%s' % ( diff --git a/markup/views.py b/markup/views.py index ea4fea6026df25dba6e5933eaec91711395957b0..b5e254e44f6b62d6f280e389d3853179470aa461 100644 --- a/markup/views.py +++ b/markup/views.py @@ -6,8 +6,7 @@ from django.contrib.auth.decorators import login_required from django.http import JsonResponse from django.shortcuts import render -from .constants import MathSnippets, PlainTextSnippets,\ - MarkdownSnippets, ReStructuredTextSnippets +from .constants import PlainTextSnippets, MarkdownSnippets, ReStructuredTextSnippets from .forms import MarkupTextForm @@ -39,7 +38,6 @@ def plaintext_help(request): Help page for plain text. """ context = { - 'math_snippets': MathSnippets, 'snippets': PlainTextSnippets, } return render(request, 'markup/plaintext_help.html', context) @@ -49,7 +47,6 @@ def markdown_help(request): Help page for Markdown. """ context = { - 'math_snippets': MathSnippets, 'snippets': MarkdownSnippets, } return render(request, 'markup/markdown_help.html', context) @@ -60,7 +57,6 @@ def restructuredtext_help(request): Help page for reStructuredText. """ context = { - 'math_snippets': MathSnippets, 'snippets': ReStructuredTextSnippets, } return render(request, 'markup/restructuredtext_help.html', context)