Eliminado venv y www del repositorio, agrege un requirements igual
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
venv/
|
||||
www/
|
||||
|
||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
jinja2
|
||||
markdown
|
||||
pygments
|
||||
@@ -19,7 +19,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', (event) => {
|
||||
window.addEventListener('load', (event) => {
|
||||
makeImagesClickeable();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<#
|
||||
.Synopsis
|
||||
Activate a Python virtual environment for the current Powershell session.
|
||||
Activate a Python virtual environment for the current PowerShell session.
|
||||
|
||||
.Description
|
||||
Pushes the python executable for a virtual environment to the front of the
|
||||
@@ -37,6 +37,15 @@ Activates the Python virtual environment that contains the Activate.ps1 script,
|
||||
and prefixes the current prompt with the specified string (surrounded in
|
||||
parentheses) while the virtual environment is active.
|
||||
|
||||
.Notes
|
||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
||||
execution policy for the user. You can do this by issuing the following PowerShell
|
||||
command:
|
||||
|
||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
|
||||
For more information on Execution Policies:
|
||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
||||
|
||||
#>
|
||||
Param(
|
||||
@@ -137,7 +146,7 @@ function Get-PyVenvConfig(
|
||||
$val = $keyval[1]
|
||||
|
||||
# Remove extraneous quotations around a string value.
|
||||
if ("'""".Contains($val.Substring(0,1))) {
|
||||
if ("'""".Contains($val.Substring(0, 1))) {
|
||||
$val = $val.Substring(1, $val.Length - 2)
|
||||
}
|
||||
|
||||
@@ -165,7 +174,8 @@ Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
||||
# VenvExecDir if specified on the command line.
|
||||
if ($VenvDir) {
|
||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||
Write-Verbose "VenvDir=$VenvDir"
|
||||
@@ -179,7 +189,8 @@ $pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
||||
# just use the name of the virtual environment folder.
|
||||
if ($Prompt) {
|
||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||
|
||||
@@ -37,7 +37,7 @@ deactivate () {
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
VIRTUAL_ENV="/home/ryuuji/src/generator/venv"
|
||||
VIRTUAL_ENV="/home/ryuuji/src/danielcortes.xyz/venv"
|
||||
export VIRTUAL_ENV
|
||||
|
||||
_OLD_VIRTUAL_PATH="$PATH"
|
||||
@@ -59,7 +59,7 @@ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
||||
else
|
||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
# see https://aspen.io/
|
||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||
else
|
||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||
|
||||
@@ -8,7 +8,7 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
|
||||
# Unset irrelevant variables.
|
||||
deactivate nondestructive
|
||||
|
||||
setenv VIRTUAL_ENV "/home/ryuuji/src/generator/venv"
|
||||
setenv VIRTUAL_ENV "/home/ryuuji/src/danielcortes.xyz/venv"
|
||||
|
||||
set _OLD_VIRTUAL_PATH="$PATH"
|
||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||
@@ -22,7 +22,7 @@ if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
||||
else
|
||||
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
# see https://aspen.io/
|
||||
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||
else
|
||||
set env_name = `basename "$VIRTUAL_ENV"`
|
||||
|
||||
@@ -29,7 +29,7 @@ end
|
||||
# unset irrelevant variables
|
||||
deactivate nondestructive
|
||||
|
||||
set -gx VIRTUAL_ENV "/home/ryuuji/src/generator/venv"
|
||||
set -gx VIRTUAL_ENV "/home/ryuuji/src/danielcortes.xyz/venv"
|
||||
|
||||
set -gx _OLD_VIRTUAL_PATH $PATH
|
||||
set -gx PATH "$VIRTUAL_ENV/bin" $PATH
|
||||
@@ -59,7 +59,7 @@ if test -z "$VIRTUAL_ENV_DISABLE_PROMPT"
|
||||
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||
if test $_checkbase = "__"
|
||||
# special case for Aspen magic directories
|
||||
# see http://www.zetadev.com/software/aspen/
|
||||
# see https://aspen.io/
|
||||
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
||||
else
|
||||
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/home/ryuuji/src/generator/venv/bin/python
|
||||
#!/home/ryuuji/src/danielcortes.xyz/venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools.command.easy_install import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/home/ryuuji/src/generator/venv/bin/python
|
||||
#!/home/ryuuji/src/danielcortes.xyz/venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from setuptools.command.easy_install import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/home/ryuuji/src/generator/venv/bin/python
|
||||
#!/home/ryuuji/src/danielcortes.xyz/venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from markdown.__main__ import run
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(run())
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/home/ryuuji/src/generator/venv/bin/python
|
||||
#!/home/ryuuji/src/danielcortes.xyz/venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/home/ryuuji/src/generator/venv/bin/python
|
||||
#!/home/ryuuji/src/danielcortes.xyz/venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#!/home/ryuuji/src/generator/venv/bin/python
|
||||
#!/home/ryuuji/src/danielcortes.xyz/venv/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import sys
|
||||
|
||||
from pip._internal import main
|
||||
|
||||
from pip._internal.cli.main import main
|
||||
if __name__ == '__main__':
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0])
|
||||
sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
|
||||
sys.exit(main())
|
||||
|
||||
@@ -2,6 +2,7 @@ Jinja2-2.11.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuw
|
||||
Jinja2-2.11.2.dist-info/LICENSE.rst,sha256=O0nc7kEF6ze6wQ-vG-JgQI_oXSUrjp3y4JefweCUQ3s,1475
|
||||
Jinja2-2.11.2.dist-info/METADATA,sha256=5ZHRZoIRAMHsJPnqhlJ622_dRPsYePYJ-9EH4-Ry7yI,3535
|
||||
Jinja2-2.11.2.dist-info/RECORD,,
|
||||
Jinja2-2.11.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
||||
Jinja2-2.11.2.dist-info/WHEEL,sha256=kGT74LWyRUZrL4VgLh6_g12IeVl_9u9ZVhadrgXZUEY,110
|
||||
Jinja2-2.11.2.dist-info/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61
|
||||
Jinja2-2.11.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pip
|
||||
@@ -1,29 +0,0 @@
|
||||
Copyright 2007, 2008 The Python Markdown Project (v. 1.7 and later)
|
||||
Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b)
|
||||
Copyright 2004 Manfred Stienstra (the original version)
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the Python Markdown Project nor the
|
||||
names of its contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE PYTHON MARKDOWN PROJECT ''AS IS'' AND ANY
|
||||
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL ANY CONTRIBUTORS TO THE PYTHON MARKDOWN PROJECT
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,57 +0,0 @@
|
||||
Metadata-Version: 2.1
|
||||
Name: Markdown
|
||||
Version: 3.2.1
|
||||
Summary: Python implementation of Markdown.
|
||||
Home-page: https://Python-Markdown.github.io/
|
||||
Author: Manfred Stienstra, Yuri takhteyev and Waylan limberg
|
||||
Author-email: waylan.limberg@icloud.com
|
||||
Maintainer: Waylan Limberg
|
||||
Maintainer-email: waylan.limberg@icloud.com
|
||||
License: BSD License
|
||||
Download-URL: http://pypi.python.org/packages/source/M/Markdown/Markdown-3.2.1-py2.py3-none-any.whl
|
||||
Platform: UNKNOWN
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Programming Language :: Python :: 3
|
||||
Classifier: Programming Language :: Python :: 3.5
|
||||
Classifier: Programming Language :: Python :: 3.6
|
||||
Classifier: Programming Language :: Python :: 3.7
|
||||
Classifier: Programming Language :: Python :: 3.8
|
||||
Classifier: Programming Language :: Python :: 3 :: Only
|
||||
Classifier: Programming Language :: Python :: Implementation :: CPython
|
||||
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
||||
Classifier: Topic :: Communications :: Email :: Filters
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content :: CGI Tools/Libraries
|
||||
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
||||
Classifier: Topic :: Software Development :: Documentation
|
||||
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
||||
Classifier: Topic :: Text Processing :: Filters
|
||||
Classifier: Topic :: Text Processing :: Markup :: HTML
|
||||
Requires-Python: >=3.5
|
||||
Requires-Dist: setuptools (>=36)
|
||||
Provides-Extra: testing
|
||||
Requires-Dist: coverage ; extra == 'testing'
|
||||
Requires-Dist: pyyaml ; extra == 'testing'
|
||||
|
||||
|
||||
This is a Python implementation of John Gruber's Markdown_.
|
||||
It is almost completely compliant with the reference implementation,
|
||||
though there are a few known issues. See Features_ for information
|
||||
on what exactly is supported and what is not. Additional features are
|
||||
supported by the `Available Extensions`_.
|
||||
|
||||
.. _Markdown: https://daringfireball.net/projects/markdown/
|
||||
.. _Features: https://Python-Markdown.github.io#features
|
||||
.. _`Available Extensions`: https://Python-Markdown.github.io/extensions/
|
||||
|
||||
Support
|
||||
=======
|
||||
|
||||
You may report bugs, ask for help, and discuss various other issues on
|
||||
the `bug tracker`_.
|
||||
|
||||
.. _`bug tracker`: https://github.com/Python-Markdown/markdown/issues
|
||||
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
../../../bin/markdown_py,sha256=CN-Ji-5IbiekxHQIaCGuLnffDcAJe1KbGNP2M75sqGM,243
|
||||
Markdown-3.2.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
|
||||
Markdown-3.2.1.dist-info/LICENSE.md,sha256=bxGTy2NHGOZcOlN9biXr1hSCDsDvaTz8EiSBEmONZNo,1645
|
||||
Markdown-3.2.1.dist-info/METADATA,sha256=PK6UzXb9yL09qZJH7SCqZd6-mj8keovCmxQLt89NlEQ,2383
|
||||
Markdown-3.2.1.dist-info/RECORD,,
|
||||
Markdown-3.2.1.dist-info/WHEEL,sha256=h_aVn5OB2IERUjMbi2pucmR_zzWJtk303YXvhh60NJ8,110
|
||||
Markdown-3.2.1.dist-info/entry_points.txt,sha256=j4jiKg-iwZGImvi8OzotZePWoFbJJ4GrfzDqH03u3SQ,1103
|
||||
Markdown-3.2.1.dist-info/top_level.txt,sha256=IAxs8x618RXoH1uCqeLLxXsDefJvE_mIibr_M4sOlyk,9
|
||||
markdown/__init__.py,sha256=002-LuHviYzROW2rg_gBGai81nMouUNO9UFj5nSsTSk,2065
|
||||
markdown/__main__.py,sha256=MpVK3zlwQ-4AzDzZmIScPB90PpunMGVgS5KBmJuHYTw,5802
|
||||
markdown/__meta__.py,sha256=xhmwLb0Eb6kfiapdM21pCb80lyVEl8hxv8Re_X6wsI0,1837
|
||||
markdown/__pycache__/__init__.cpython-38.pyc,,
|
||||
markdown/__pycache__/__main__.cpython-38.pyc,,
|
||||
markdown/__pycache__/__meta__.cpython-38.pyc,,
|
||||
markdown/__pycache__/blockparser.cpython-38.pyc,,
|
||||
markdown/__pycache__/blockprocessors.cpython-38.pyc,,
|
||||
markdown/__pycache__/core.cpython-38.pyc,,
|
||||
markdown/__pycache__/inlinepatterns.cpython-38.pyc,,
|
||||
markdown/__pycache__/pep562.cpython-38.pyc,,
|
||||
markdown/__pycache__/postprocessors.cpython-38.pyc,,
|
||||
markdown/__pycache__/preprocessors.cpython-38.pyc,,
|
||||
markdown/__pycache__/serializers.cpython-38.pyc,,
|
||||
markdown/__pycache__/test_tools.cpython-38.pyc,,
|
||||
markdown/__pycache__/treeprocessors.cpython-38.pyc,,
|
||||
markdown/__pycache__/util.cpython-38.pyc,,
|
||||
markdown/blockparser.py,sha256=JpBhOokOoBUGCXolftOc5m1hPcR2y9s9hVd9WSuhHzo,4285
|
||||
markdown/blockprocessors.py,sha256=l4gmkAN9b2L340EX0gm24EyWS7UzBviPqX6wYrcgEco,23736
|
||||
markdown/core.py,sha256=JLR5hIMwWSeIHRQhTzAymB3QUD3gHCdITFvmuuCpIcA,15360
|
||||
markdown/extensions/__init__.py,sha256=6kUSgoqDT4gGUVsqf7F9oQD_jA0RJCbX5EK3JVo8iQE,3517
|
||||
markdown/extensions/__pycache__/__init__.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/abbr.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/admonition.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/attr_list.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/codehilite.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/def_list.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/extra.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/fenced_code.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/footnotes.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/legacy_attrs.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/legacy_em.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/md_in_html.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/meta.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/nl2br.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/sane_lists.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/smarty.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/tables.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/toc.cpython-38.pyc,,
|
||||
markdown/extensions/__pycache__/wikilinks.cpython-38.pyc,,
|
||||
markdown/extensions/abbr.py,sha256=pqp2HnOR2giT-iYKyqtsp2_eUOWBR0j_hUfjvUV5c88,2916
|
||||
markdown/extensions/admonition.py,sha256=HWHHjuYZPAPOg5X8hbpDuSbw8gB6k0odw8GuTT1v_N4,3124
|
||||
markdown/extensions/attr_list.py,sha256=m9a1H-S33rV2twtlFYuoxSiCAf22ndU5tziSzNF2dNg,6003
|
||||
markdown/extensions/codehilite.py,sha256=rVZVOIjp2KEIZsnz90mX6E2_xnwVPQZpVVQVJMuMVU0,9834
|
||||
markdown/extensions/def_list.py,sha256=iqRXAEl2XnyF415afCxihAgOmEUOK1hIuBPIK1k7Tzo,3521
|
||||
markdown/extensions/extra.py,sha256=udRN8OvSWcq3UwkPygvsFl1RlCVtCJ-ARVg2IwVH6VY,1831
|
||||
markdown/extensions/fenced_code.py,sha256=dww9rDu2kQtkoTpjn9BBgeGCTNdE1bMPJ2wgR6695iM,3897
|
||||
markdown/extensions/footnotes.py,sha256=a9sb8RoKqFU8p8ZhpTObrn_Uek0hbyPFVGYpRaEDXaw,15339
|
||||
markdown/extensions/legacy_attrs.py,sha256=2EaVQkxQoNnP8_lMPvGRBdNda8L4weUQroiyEuVdS-w,2547
|
||||
markdown/extensions/legacy_em.py,sha256=9ZMGCTrFh01eiOpnFjS0jVkqgYXiTzCGn-eNvYcvObg,1579
|
||||
markdown/extensions/md_in_html.py,sha256=ohSiGcgR5yBqusuTs0opbTO_5fq442fqPK-klFd_qaM,4040
|
||||
markdown/extensions/meta.py,sha256=EUfkzM7l7UpH__Or9K3pl8ldVddwndlCZWA3d712RAE,2331
|
||||
markdown/extensions/nl2br.py,sha256=wAqTNOuf2L1NzlEvEqoID70n9y-aiYaGLkuyQk3CD0w,783
|
||||
markdown/extensions/sane_lists.py,sha256=ZQmCf-247KBexVG0fc62nDvokGkV6W1uavYbieNKSG4,1505
|
||||
markdown/extensions/smarty.py,sha256=0padzkVCNACainKw-Xj1S5UfT0125VCTfNejmrCZItA,10238
|
||||
markdown/extensions/tables.py,sha256=bicFx_wqhnEx6Y_8MJqA56rh71pt5fOe94oiWbvcobY,7685
|
||||
markdown/extensions/toc.py,sha256=E-d3R4etcM_R2sQyTpKkejRv2NHrHPCvaXK9hUqfK58,13224
|
||||
markdown/extensions/wikilinks.py,sha256=GkgT9BY7b1-qW--dIwFAhC9V20RoeF13b7CFdw_V21Q,2812
|
||||
markdown/inlinepatterns.py,sha256=EnYq9aU_Hi1gu5e8dcbUxUu0mRz-pHFV79uGQCYbD5I,29378
|
||||
markdown/pep562.py,sha256=5UkqT7sb-cQufgbOl_jF-RYUVVHS7VThzlMzR9vrd3I,8917
|
||||
markdown/postprocessors.py,sha256=25g6qqpJ4kuiq4RBrGz8RA6GMb7ArUi1AN2VDVnR35U,3738
|
||||
markdown/preprocessors.py,sha256=dsmMVPP2afKAZ0s59_mFidM_mCiNfgdBJ9aVDWu_viE,15323
|
||||
markdown/serializers.py,sha256=_wQl-iJrPSUEQ4Q1owWYqN9qceVh6TOlAOH_i44BKAQ,6540
|
||||
markdown/test_tools.py,sha256=zFHFzmtzjfMRroyyli3LY4SP8yLfLf4S7SsU3z7Z1SQ,6823
|
||||
markdown/treeprocessors.py,sha256=NBaYc9TEGP7TBaN6YRROIqE5Lj-AMoAqp0jN-coGW3Q,15401
|
||||
markdown/util.py,sha256=0ySktJgYplEV7g6TOOs8fatAS4Fi-6F7iv4D9Vw3g0c,15201
|
||||
@@ -1,6 +0,0 @@
|
||||
Wheel-Version: 1.0
|
||||
Generator: bdist_wheel (0.33.4)
|
||||
Root-Is-Purelib: true
|
||||
Tag: py2-none-any
|
||||
Tag: py3-none-any
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[console_scripts]
|
||||
markdown_py = markdown.__main__:run
|
||||
|
||||
[markdown.extensions]
|
||||
abbr = markdown.extensions.abbr:AbbrExtension
|
||||
admonition = markdown.extensions.admonition:AdmonitionExtension
|
||||
attr_list = markdown.extensions.attr_list:AttrListExtension
|
||||
codehilite = markdown.extensions.codehilite:CodeHiliteExtension
|
||||
def_list = markdown.extensions.def_list:DefListExtension
|
||||
extra = markdown.extensions.extra:ExtraExtension
|
||||
fenced_code = markdown.extensions.fenced_code:FencedCodeExtension
|
||||
footnotes = markdown.extensions.footnotes:FootnoteExtension
|
||||
legacy_attrs = markdown.extensions.legacy_attrs:LegacyAttrExtension
|
||||
legacy_em = markdown.extensions.legacy_em:LegacyEmExtension
|
||||
md_in_html = markdown.extensions.md_in_html:MarkdownInHtmlExtension
|
||||
meta = markdown.extensions.meta:MetaExtension
|
||||
nl2br = markdown.extensions.nl2br:Nl2BrExtension
|
||||
sane_lists = markdown.extensions.sane_lists:SaneListExtension
|
||||
smarty = markdown.extensions.smarty:SmartyExtension
|
||||
tables = markdown.extensions.tables:TableExtension
|
||||
toc = markdown.extensions.toc:TocExtension
|
||||
wikilinks = markdown.extensions.wikilinks:WikiLinkExtension
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
markdown
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -19,11 +19,6 @@ Copyright 2004 Manfred Stienstra (the original version)
|
||||
License: BSD (see LICENSE.md for details).
|
||||
"""
|
||||
|
||||
try:
|
||||
import packaging.version
|
||||
except ImportError:
|
||||
from pkg_resources.extern import packaging
|
||||
|
||||
# __version_info__ format:
|
||||
# (major, minor, patch, dev/alpha/beta/rc/final, #)
|
||||
# (1, 1, 2, 'dev', 0) => "1.1.2.dev0"
|
||||
@@ -31,25 +26,24 @@ except ImportError:
|
||||
# (1, 2, 0, 'beta', 2) => "1.2b2"
|
||||
# (1, 2, 0, 'rc', 4) => "1.2rc4"
|
||||
# (1, 2, 0, 'final', 0) => "1.2"
|
||||
__version_info__ = (3, 2, 1, 'final', 0)
|
||||
__version_info__ = (3, 3, 3, 'final', 0)
|
||||
|
||||
|
||||
def _get_version(): # pragma: no cover
|
||||
def _get_version(version_info):
|
||||
" Returns a PEP 440-compliant version number from version_info. "
|
||||
assert len(__version_info__) == 5
|
||||
assert __version_info__[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
|
||||
assert len(version_info) == 5
|
||||
assert version_info[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
|
||||
|
||||
parts = 2 if __version_info__[2] == 0 else 3
|
||||
v = '.'.join(map(str, __version_info__[:parts]))
|
||||
parts = 2 if version_info[2] == 0 else 3
|
||||
v = '.'.join(map(str, version_info[:parts]))
|
||||
|
||||
if __version_info__[3] == 'dev':
|
||||
v += '.dev' + str(__version_info__[4])
|
||||
elif __version_info__[3] != 'final':
|
||||
if version_info[3] == 'dev':
|
||||
v += '.dev' + str(version_info[4])
|
||||
elif version_info[3] != 'final':
|
||||
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
|
||||
v += mapping[__version_info__[3]] + str(__version_info__[4])
|
||||
v += mapping[version_info[3]] + str(version_info[4])
|
||||
|
||||
# Ensure version is valid and normalized
|
||||
return str(packaging.version.Version(v))
|
||||
return v
|
||||
|
||||
|
||||
__version__ = _get_version()
|
||||
__version__ = _get_version(__version_info__)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -51,6 +51,7 @@ def build_block_parser(md, **kwargs):
|
||||
parser.blockprocessors.register(OListProcessor(parser), 'olist', 40)
|
||||
parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
|
||||
parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
|
||||
parser.blockprocessors.register(ReferenceProcessor(parser), 'reference', 15)
|
||||
parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10)
|
||||
return parser
|
||||
|
||||
@@ -276,7 +277,7 @@ class BlockQuoteProcessor(BlockProcessor):
|
||||
RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')
|
||||
|
||||
def test(self, parent, block):
|
||||
return bool(self.RE.search(block))
|
||||
return bool(self.RE.search(block)) and not util.nearing_recursion_limit()
|
||||
|
||||
def run(self, parent, blocks):
|
||||
block = blocks.pop(0)
|
||||
@@ -495,16 +496,15 @@ class SetextHeaderProcessor(BlockProcessor):
|
||||
class HRProcessor(BlockProcessor):
|
||||
""" Process Horizontal Rules. """
|
||||
|
||||
RE = r'^[ ]{0,3}((-+[ ]{0,2}){3,}|(_+[ ]{0,2}){3,}|(\*+[ ]{0,2}){3,})[ ]*'
|
||||
# Python's re module doesn't officially support atomic grouping. However you can fake it.
|
||||
# See https://stackoverflow.com/a/13577411/866026
|
||||
RE = r'^[ ]{0,3}(?=(?P<atomicgroup>(-+[ ]{0,2}){3,}|(_+[ ]{0,2}){3,}|(\*+[ ]{0,2}){3,}))(?P=atomicgroup)[ ]*$'
|
||||
# Detect hr on any line of a block.
|
||||
SEARCH_RE = re.compile(RE, re.MULTILINE)
|
||||
|
||||
def test(self, parent, block):
|
||||
m = self.SEARCH_RE.search(block)
|
||||
# No atomic grouping in python so we simulate it here for performance.
|
||||
# The regex only matches what would be in the atomic group - the HR.
|
||||
# Then check if we are at end of block or if next char is a newline.
|
||||
if m and (m.end() == len(block) or block[m.end()] == '\n'):
|
||||
if m:
|
||||
# Save match object on class instance so we can use it later.
|
||||
self.match = m
|
||||
return True
|
||||
@@ -554,6 +554,35 @@ class EmptyBlockProcessor(BlockProcessor):
|
||||
)
|
||||
|
||||
|
||||
class ReferenceProcessor(BlockProcessor):
|
||||
""" Process link references. """
|
||||
RE = re.compile(
|
||||
r'^[ ]{0,3}\[([^\]]*)\]:[ ]*\n?[ ]*([^\s]+)[ ]*\n?[ ]*((["\'])(.*)\4|\((.*)\))?[ ]*$', re.MULTILINE
|
||||
)
|
||||
|
||||
def test(self, parent, block):
|
||||
return True
|
||||
|
||||
def run(self, parent, blocks):
|
||||
block = blocks.pop(0)
|
||||
m = self.RE.search(block)
|
||||
if m:
|
||||
id = m.group(1).strip().lower()
|
||||
link = m.group(2).lstrip('<').rstrip('>')
|
||||
title = m.group(5) or m.group(6)
|
||||
self.parser.md.references[id] = (link, title)
|
||||
if block[m.end():].strip():
|
||||
# Add any content after match back to blocks as separate block
|
||||
blocks.insert(0, block[m.end():].lstrip('\n'))
|
||||
if block[:m.start()].strip():
|
||||
# Add any content before match back to blocks as separate block
|
||||
blocks.insert(0, block[:m.start()].rstrip('\n'))
|
||||
return True
|
||||
# No match. Restore block.
|
||||
blocks.insert(0, block)
|
||||
return False
|
||||
|
||||
|
||||
class ParagraphProcessor(BlockProcessor):
|
||||
""" Process Paragraph blocks. """
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ import codecs
|
||||
import sys
|
||||
import logging
|
||||
import importlib
|
||||
import pkg_resources
|
||||
from . import util
|
||||
from .preprocessors import build_preprocessors
|
||||
from .blockprocessors import build_block_parser
|
||||
@@ -78,11 +77,12 @@ class Markdown:
|
||||
# See https://w3c.github.io/html/grouping-content.html#the-p-element
|
||||
'address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl',
|
||||
'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
|
||||
'h4', 'h5', 'h6', 'header', 'hr', 'main', 'menu', 'nav', 'ol', 'p', 'pre',
|
||||
'section', 'table', 'ul',
|
||||
'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'main', 'menu', 'nav', 'ol',
|
||||
'p', 'pre', 'section', 'table', 'ul',
|
||||
# Other elements which Markdown should not be mucking up the contents of.
|
||||
'canvas', 'dd', 'dt', 'group', 'iframe', 'li', 'math', 'noscript', 'output',
|
||||
'progress', 'script', 'style', 'tbody', 'td', 'th', 'thead', 'tr', 'video'
|
||||
'canvas', 'colgroup', 'dd', 'body', 'dt', 'group', 'iframe', 'li', 'legend',
|
||||
'math', 'map', 'noscript', 'output', 'object', 'option', 'progress', 'script',
|
||||
'style', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'video'
|
||||
]
|
||||
|
||||
self.registeredExtensions = []
|
||||
@@ -141,9 +141,8 @@ class Markdown:
|
||||
Build extension from a string name, then return an instance.
|
||||
|
||||
First attempt to load an entry point. The string name must be registered as an entry point in the
|
||||
`markdown.extensions` group which points to a subclass of the `markdown.extensions.Extension` class. If
|
||||
multiple distributions have registered the same name, the first one found by `pkg_resources.iter_entry_points`
|
||||
is returned.
|
||||
`markdown.extensions` group which points to a subclass of the `markdown.extensions.Extension` class.
|
||||
If multiple distributions have registered the same name, the first one found is returned.
|
||||
|
||||
If no entry point is found, assume dot notation (`path.to.module:ClassName`). Load the specified class and
|
||||
return an instance. If no class is specified, import the module and call a `makeExtension` function and return
|
||||
@@ -151,7 +150,7 @@ class Markdown:
|
||||
"""
|
||||
configs = dict(configs)
|
||||
|
||||
entry_points = [ep for ep in pkg_resources.iter_entry_points('markdown.extensions', ext_name)]
|
||||
entry_points = [ep for ep in util.INSTALLED_EXTENSIONS if ep.name == ext_name]
|
||||
if entry_points:
|
||||
ext = entry_points[0].load()
|
||||
return ext(**configs)
|
||||
@@ -278,14 +277,14 @@ class Markdown:
|
||||
'<%s>' % self.doc_tag) + len(self.doc_tag) + 2
|
||||
end = output.rindex('</%s>' % self.doc_tag)
|
||||
output = output[start:end].strip()
|
||||
except ValueError: # pragma: no cover
|
||||
except ValueError as e: # pragma: no cover
|
||||
if output.strip().endswith('<%s />' % self.doc_tag):
|
||||
# We have an empty document
|
||||
output = ''
|
||||
else:
|
||||
# We have a serious problem
|
||||
raise ValueError('Markdown failed to strip top-level '
|
||||
'tags. Document=%r' % output.strip())
|
||||
'tags. Document=%r' % output.strip()) from e
|
||||
|
||||
# Run the text post-processors
|
||||
for pp in self.postprocessors:
|
||||
|
||||
@@ -75,7 +75,8 @@ class Extension:
|
||||
md = args[0]
|
||||
try:
|
||||
self.extendMarkdown(md)
|
||||
except TypeError:
|
||||
except TypeError as e:
|
||||
if "missing 1 required positional argument" in str(e):
|
||||
# Must be a 2.x extension. Pass in a dumby md_globals.
|
||||
self.extendMarkdown(md, {})
|
||||
warnings.warn(
|
||||
@@ -84,6 +85,8 @@ class Extension:
|
||||
category=DeprecationWarning,
|
||||
stacklevel=2
|
||||
)
|
||||
else:
|
||||
raise
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
"""
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -17,48 +17,53 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||
'''
|
||||
|
||||
from . import Extension
|
||||
from ..preprocessors import Preprocessor
|
||||
from ..blockprocessors import BlockProcessor
|
||||
from ..inlinepatterns import InlineProcessor
|
||||
from ..util import AtomicString
|
||||
import re
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
# Global Vars
|
||||
ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)')
|
||||
|
||||
|
||||
class AbbrExtension(Extension):
|
||||
""" Abbreviation Extension for Python-Markdown. """
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
""" Insert AbbrPreprocessor before ReferencePreprocessor. """
|
||||
md.preprocessors.register(AbbrPreprocessor(md), 'abbr', 12)
|
||||
md.parser.blockprocessors.register(AbbrPreprocessor(md.parser), 'abbr', 16)
|
||||
|
||||
|
||||
class AbbrPreprocessor(Preprocessor):
|
||||
class AbbrPreprocessor(BlockProcessor):
|
||||
""" Abbreviation Preprocessor - parse text for abbr references. """
|
||||
|
||||
def run(self, lines):
|
||||
RE = re.compile(r'^[*]\[(?P<abbr>[^\]]*)\][ ]?:[ ]*\n?[ ]*(?P<title>.*)$', re.MULTILINE)
|
||||
|
||||
def test(self, parent, block):
|
||||
return True
|
||||
|
||||
def run(self, parent, blocks):
|
||||
'''
|
||||
Find and remove all Abbreviation references from the text.
|
||||
Each reference is set as a new AbbrPattern in the markdown instance.
|
||||
|
||||
'''
|
||||
new_text = []
|
||||
for line in lines:
|
||||
m = ABBR_REF_RE.match(line)
|
||||
block = blocks.pop(0)
|
||||
m = self.RE.search(block)
|
||||
if m:
|
||||
abbr = m.group('abbr').strip()
|
||||
title = m.group('title').strip()
|
||||
self.md.inlinePatterns.register(
|
||||
self.parser.md.inlinePatterns.register(
|
||||
AbbrInlineProcessor(self._generate_pattern(abbr), title), 'abbr-%s' % abbr, 2
|
||||
)
|
||||
# Preserve the line to prevent raw HTML indexing issue.
|
||||
# https://github.com/Python-Markdown/markdown/issues/584
|
||||
new_text.append('')
|
||||
else:
|
||||
new_text.append(line)
|
||||
return new_text
|
||||
if block[m.end():].strip():
|
||||
# Add any content after match back to blocks as separate block
|
||||
blocks.insert(0, block[m.end():].lstrip('\n'))
|
||||
if block[:m.start()].strip():
|
||||
# Add any content before match back to blocks as separate block
|
||||
blocks.insert(0, block[:m.start()].rstrip('\n'))
|
||||
return True
|
||||
# No match. Restore block.
|
||||
blocks.insert(0, block)
|
||||
return False
|
||||
|
||||
def _generate_pattern(self, text):
|
||||
'''
|
||||
|
||||
@@ -40,19 +40,82 @@ class AdmonitionProcessor(BlockProcessor):
|
||||
RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)')
|
||||
RE_SPACES = re.compile(' +')
|
||||
|
||||
def test(self, parent, block):
|
||||
def __init__(self, parser):
|
||||
"""Initialization."""
|
||||
|
||||
super().__init__(parser)
|
||||
|
||||
self.current_sibling = None
|
||||
self.content_indention = 0
|
||||
|
||||
def get_sibling(self, parent, block):
|
||||
"""Get sibling admontion.
|
||||
|
||||
Retrieve the appropriate siblimg element. This can get trickly when
|
||||
dealing with lists.
|
||||
|
||||
"""
|
||||
|
||||
# We already acquired the block via test
|
||||
if self.current_sibling is not None:
|
||||
sibling = self.current_sibling
|
||||
block = block[self.content_indent:]
|
||||
self.current_sibling = None
|
||||
self.content_indent = 0
|
||||
return sibling, block
|
||||
|
||||
sibling = self.lastChild(parent)
|
||||
return self.RE.search(block) or \
|
||||
(block.startswith(' ' * self.tab_length) and sibling is not None and
|
||||
sibling.get('class', '').find(self.CLASSNAME) != -1)
|
||||
|
||||
if sibling is None or sibling.get('class', '').find(self.CLASSNAME) == -1:
|
||||
sibling = None
|
||||
else:
|
||||
# If the last child is a list and the content is idented sufficient
|
||||
# to be under it, then the content's is sibling is in the list.
|
||||
last_child = self.lastChild(sibling)
|
||||
indent = 0
|
||||
while last_child:
|
||||
if (
|
||||
sibling and block.startswith(' ' * self.tab_length * 2) and
|
||||
last_child and last_child.tag in ('ul', 'ol', 'dl')
|
||||
):
|
||||
|
||||
# The expectation is that we'll find an <li> or <dt>.
|
||||
# We should get it's last child as well.
|
||||
sibling = self.lastChild(last_child)
|
||||
last_child = self.lastChild(sibling) if sibling else None
|
||||
|
||||
# Context has been lost at this point, so we must adjust the
|
||||
# text's identation level so it will be evaluated correctly
|
||||
# under the list.
|
||||
block = block[self.tab_length:]
|
||||
indent += self.tab_length
|
||||
else:
|
||||
last_child = None
|
||||
|
||||
if not block.startswith(' ' * self.tab_length):
|
||||
sibling = None
|
||||
|
||||
if sibling is not None:
|
||||
self.current_sibling = sibling
|
||||
self.content_indent = indent
|
||||
|
||||
return sibling, block
|
||||
|
||||
def test(self, parent, block):
|
||||
|
||||
if self.RE.search(block):
|
||||
return True
|
||||
else:
|
||||
return self.get_sibling(parent, block)[0] is not None
|
||||
|
||||
def run(self, parent, blocks):
|
||||
sibling = self.lastChild(parent)
|
||||
block = blocks.pop(0)
|
||||
m = self.RE.search(block)
|
||||
|
||||
if m:
|
||||
block = block[m.end():] # removes the first line
|
||||
else:
|
||||
sibling, block = self.get_sibling(parent, block)
|
||||
|
||||
block, theRest = self.detab(block)
|
||||
|
||||
@@ -65,6 +128,13 @@ class AdmonitionProcessor(BlockProcessor):
|
||||
p.text = title
|
||||
p.set('class', self.CLASSNAME_TITLE)
|
||||
else:
|
||||
# Sibling is a list item, but we need to wrap it's content should be wrapped in <p>
|
||||
if sibling.tag in ('li', 'dd') and sibling.text:
|
||||
text = sibling.text
|
||||
sibling.text = ''
|
||||
p = etree.SubElement(sibling, 'p')
|
||||
p.text = text
|
||||
|
||||
div = sibling
|
||||
|
||||
self.parser.parseChunk(div, block)
|
||||
|
||||
@@ -64,10 +64,10 @@ def isheader(elem):
|
||||
|
||||
class AttrListTreeprocessor(Treeprocessor):
|
||||
|
||||
BASE_RE = r'\{\:?([^\}\n]*)\}'
|
||||
HEADER_RE = re.compile(r'[ ]+%s[ ]*$' % BASE_RE)
|
||||
BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE)
|
||||
INLINE_RE = re.compile(r'^%s' % BASE_RE)
|
||||
BASE_RE = r'\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}'
|
||||
HEADER_RE = re.compile(r'[ ]+{}[ ]*$'.format(BASE_RE))
|
||||
BLOCK_RE = re.compile(r'\n[ ]*{}[ ]*$'.format(BASE_RE))
|
||||
INLINE_RE = re.compile(r'^{}'.format(BASE_RE))
|
||||
NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff'
|
||||
r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d'
|
||||
r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff'
|
||||
@@ -79,8 +79,8 @@ class AttrListTreeprocessor(Treeprocessor):
|
||||
if self.md.is_block_level(elem.tag):
|
||||
# Block level: check for attrs on last line of text
|
||||
RE = self.BLOCK_RE
|
||||
if isheader(elem) or elem.tag == 'dt':
|
||||
# header or def-term: check for attrs at end of line
|
||||
if isheader(elem) or elem.tag in ['dt', 'td', 'th']:
|
||||
# header, def-term, or table cell: check for attrs at end of element
|
||||
RE = self.HEADER_RE
|
||||
if len(elem) and elem.tag == 'li':
|
||||
# special case list items. children may include a ul or ol.
|
||||
@@ -120,8 +120,6 @@ class AttrListTreeprocessor(Treeprocessor):
|
||||
elif elem.text:
|
||||
# no children. Get from text.
|
||||
m = RE.search(elem.text)
|
||||
if not m and elem.tag == 'td':
|
||||
m = re.search(self.BASE_RE, elem.text)
|
||||
if m:
|
||||
self.assign_attrs(elem, m.group(1))
|
||||
elem.text = elem.text[:m.start()]
|
||||
@@ -161,6 +159,7 @@ class AttrListTreeprocessor(Treeprocessor):
|
||||
class AttrListExtension(Extension):
|
||||
def extendMarkdown(self, md):
|
||||
md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8)
|
||||
md.registerExtension(self)
|
||||
|
||||
|
||||
def makeExtension(**kwargs): # pragma: no cover
|
||||
|
||||
@@ -17,13 +17,14 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||
|
||||
from . import Extension
|
||||
from ..treeprocessors import Treeprocessor
|
||||
from ..util import parseBoolValue
|
||||
|
||||
try:
|
||||
try: # pragma: no cover
|
||||
from pygments import highlight
|
||||
from pygments.lexers import get_lexer_by_name, guess_lexer
|
||||
from pygments.formatters import get_formatter_by_name
|
||||
pygments = True
|
||||
except ImportError:
|
||||
except ImportError: # pragma: no cover
|
||||
pygments = False
|
||||
|
||||
|
||||
@@ -38,52 +39,78 @@ def parse_hl_lines(expr):
|
||||
|
||||
try:
|
||||
return list(map(int, expr.split()))
|
||||
except ValueError:
|
||||
except ValueError: # pragma: no cover
|
||||
return []
|
||||
|
||||
|
||||
# ------------------ The Main CodeHilite Class ----------------------
|
||||
class CodeHilite:
|
||||
"""
|
||||
Determine language of source code, and pass it into pygments hilighter.
|
||||
Determine language of source code, and pass it on to the Pygments highlighter.
|
||||
|
||||
Basic Usage:
|
||||
>>> code = CodeHilite(src = 'some text')
|
||||
>>> html = code.hilite()
|
||||
Usage:
|
||||
code = CodeHilite(src=some_code, lang='python')
|
||||
html = code.hilite()
|
||||
|
||||
Arguments:
|
||||
* src: Source string or any object with a .readline attribute.
|
||||
|
||||
* linenums: (Boolean) Set line numbering to 'on' (True),
|
||||
'off' (False) or 'auto'(None). Set to 'auto' by default.
|
||||
* lang: String name of Pygments lexer to use for highlighting. Default: `None`.
|
||||
|
||||
* guess_lang: (Boolean) Turn language auto-detection
|
||||
'on' or 'off' (on by default).
|
||||
* guess_lang: Auto-detect which lexer to use. Ignored if `lang` is set to a valid
|
||||
value. Default: `True`.
|
||||
|
||||
* css_class: Set class name of wrapper div ('codehilite' by default).
|
||||
* use_pygments: Pass code to pygments for code highlighting. If `False`, the code is
|
||||
instead wrapped for highlighting by a JavaScript library. Default: `True`.
|
||||
|
||||
* hl_lines: (List of integers) Lines to emphasize, 1-indexed.
|
||||
* linenums: An alias to Pygments `linenos` formatter option. Default: `None`.
|
||||
|
||||
Low Level Usage:
|
||||
>>> code = CodeHilite()
|
||||
>>> code.src = 'some text' # String or anything with a .readline attr.
|
||||
>>> code.linenos = True # Turns line numbering on or of.
|
||||
>>> html = code.hilite()
|
||||
* css_class: An alias to Pygments `cssclass` formatter option. Default: 'codehilite'.
|
||||
|
||||
* lang_prefix: Prefix prepended to the language when `use_pygments` is `False`.
|
||||
Default: "language-".
|
||||
|
||||
Other Options:
|
||||
Any other options are accepted and passed on to the lexer and formatter. Therefore,
|
||||
valid options include any options which are accepted by the `html` formatter or
|
||||
whichever lexer the code's language uses. Note that most lexers do not have any
|
||||
options. However, a few have very useful options, such as PHP's `startinline` option.
|
||||
Any invalid options are ignored without error.
|
||||
|
||||
Formatter options: https://pygments.org/docs/formatters/#HtmlFormatter
|
||||
Lexer Options: https://pygments.org/docs/lexers/
|
||||
|
||||
Advanced Usage:
|
||||
code = CodeHilite(
|
||||
src = some_code,
|
||||
lang = 'php',
|
||||
startinline = True, # Lexer option. Snippet does not start with `<?php`.
|
||||
linenostart = 42, # Formatter option. Snippet starts on line 42.
|
||||
hl_lines = [45, 49, 50], # Formatter option. Highlight lines 45, 49, and 50.
|
||||
linenos = 'inline' # Formatter option. Avoid alignment problems.
|
||||
)
|
||||
html = code.hilite()
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, src=None, linenums=None, guess_lang=True,
|
||||
css_class="codehilite", lang=None, style='default',
|
||||
noclasses=False, tab_length=4, hl_lines=None, use_pygments=True):
|
||||
def __init__(self, src, **options):
|
||||
self.src = src
|
||||
self.lang = lang
|
||||
self.linenums = linenums
|
||||
self.guess_lang = guess_lang
|
||||
self.css_class = css_class
|
||||
self.style = style
|
||||
self.noclasses = noclasses
|
||||
self.tab_length = tab_length
|
||||
self.hl_lines = hl_lines or []
|
||||
self.use_pygments = use_pygments
|
||||
self.lang = options.pop('lang', None)
|
||||
self.guess_lang = options.pop('guess_lang', True)
|
||||
self.use_pygments = options.pop('use_pygments', True)
|
||||
self.lang_prefix = options.pop('lang_prefix', 'language-')
|
||||
|
||||
if 'linenos' not in options:
|
||||
options['linenos'] = options.pop('linenums', None)
|
||||
if 'cssclass' not in options:
|
||||
options['cssclass'] = options.pop('css_class', 'codehilite')
|
||||
if 'wrapcode' not in options:
|
||||
# Override pygments default
|
||||
options['wrapcode'] = True
|
||||
# Disallow use of `full` option
|
||||
options['full'] = False
|
||||
|
||||
self.options = options
|
||||
|
||||
def hilite(self):
|
||||
"""
|
||||
@@ -103,22 +130,16 @@ class CodeHilite:
|
||||
|
||||
if pygments and self.use_pygments:
|
||||
try:
|
||||
lexer = get_lexer_by_name(self.lang)
|
||||
lexer = get_lexer_by_name(self.lang, **self.options)
|
||||
except ValueError:
|
||||
try:
|
||||
if self.guess_lang:
|
||||
lexer = guess_lexer(self.src)
|
||||
lexer = guess_lexer(self.src, **self.options)
|
||||
else:
|
||||
lexer = get_lexer_by_name('text')
|
||||
except ValueError:
|
||||
lexer = get_lexer_by_name('text')
|
||||
formatter = get_formatter_by_name('html',
|
||||
linenos=self.linenums,
|
||||
cssclass=self.css_class,
|
||||
style=self.style,
|
||||
noclasses=self.noclasses,
|
||||
hl_lines=self.hl_lines,
|
||||
wrapcode=True)
|
||||
lexer = get_lexer_by_name('text', **self.options)
|
||||
except ValueError: # pragma: no cover
|
||||
lexer = get_lexer_by_name('text', **self.options)
|
||||
formatter = get_formatter_by_name('html', **self.options)
|
||||
return highlight(self.src, lexer, formatter)
|
||||
else:
|
||||
# just escape and build markup usable by JS highlighting libs
|
||||
@@ -128,27 +149,30 @@ class CodeHilite:
|
||||
txt = txt.replace('"', '"')
|
||||
classes = []
|
||||
if self.lang:
|
||||
classes.append('language-%s' % self.lang)
|
||||
if self.linenums:
|
||||
classes.append('{}{}'.format(self.lang_prefix, self.lang))
|
||||
if self.options['linenos']:
|
||||
classes.append('linenums')
|
||||
class_str = ''
|
||||
if classes:
|
||||
class_str = ' class="%s"' % ' '.join(classes)
|
||||
return '<pre class="%s"><code%s>%s</code></pre>\n' % \
|
||||
(self.css_class, class_str, txt)
|
||||
class_str = ' class="{}"'.format(' '.join(classes))
|
||||
return '<pre class="{}"><code{}>{}\n</code></pre>\n'.format(
|
||||
self.options['cssclass'],
|
||||
class_str,
|
||||
txt
|
||||
)
|
||||
|
||||
def _parseHeader(self):
|
||||
"""
|
||||
Determines language of a code block from shebang line and whether said
|
||||
line should be removed or left in place. If the sheband line contains a
|
||||
path (even a single /) then it is assumed to be a real shebang line and
|
||||
left alone. However, if no path is given (e.i.: #!python or :::python)
|
||||
then it is assumed to be a mock shebang for language identifitation of
|
||||
a code fragment and removed from the code block prior to processing for
|
||||
code highlighting. When a mock shebang (e.i: #!python) is found, line
|
||||
numbering is turned on. When colons are found in place of a shebang
|
||||
(e.i.: :::python), line numbering is left in the current state - off
|
||||
by default.
|
||||
Determines language of a code block from shebang line and whether the
|
||||
said line should be removed or left in place. If the sheband line
|
||||
contains a path (even a single /) then it is assumed to be a real
|
||||
shebang line and left alone. However, if no path is given
|
||||
(e.i.: #!python or :::python) then it is assumed to be a mock shebang
|
||||
for language identification of a code fragment and removed from the
|
||||
code block prior to processing for code highlighting. When a mock
|
||||
shebang (e.i: #!python) is found, line numbering is turned on. When
|
||||
colons are found in place of a shebang (e.i.: :::python), line
|
||||
numbering is left in the current state - off by default.
|
||||
|
||||
Also parses optional list of highlight lines, like:
|
||||
|
||||
@@ -176,16 +200,16 @@ class CodeHilite:
|
||||
# we have a match
|
||||
try:
|
||||
self.lang = m.group('lang').lower()
|
||||
except IndexError:
|
||||
except IndexError: # pragma: no cover
|
||||
self.lang = None
|
||||
if m.group('path'):
|
||||
# path exists - restore first line
|
||||
lines.insert(0, fl)
|
||||
if self.linenums is None and m.group('shebang'):
|
||||
if self.options['linenos'] is None and m.group('shebang'):
|
||||
# Overridable and Shebang exists - use line numbers
|
||||
self.linenums = True
|
||||
self.options['linenos'] = True
|
||||
|
||||
self.hl_lines = parse_hl_lines(m.group('hl_lines'))
|
||||
self.options['hl_lines'] = parse_hl_lines(m.group('hl_lines'))
|
||||
else:
|
||||
# No match
|
||||
lines.insert(0, fl)
|
||||
@@ -201,9 +225,11 @@ class HiliteTreeprocessor(Treeprocessor):
|
||||
|
||||
def code_unescape(self, text):
|
||||
"""Unescape code."""
|
||||
text = text.replace("&", "&")
|
||||
text = text.replace("<", "<")
|
||||
text = text.replace(">", ">")
|
||||
# Escaped '&' should be replaced at the end to avoid
|
||||
# conflicting with < and >.
|
||||
text = text.replace("&", "&")
|
||||
return text
|
||||
|
||||
def run(self, root):
|
||||
@@ -213,13 +239,9 @@ class HiliteTreeprocessor(Treeprocessor):
|
||||
if len(block) == 1 and block[0].tag == 'code':
|
||||
code = CodeHilite(
|
||||
self.code_unescape(block[0].text),
|
||||
linenums=self.config['linenums'],
|
||||
guess_lang=self.config['guess_lang'],
|
||||
css_class=self.config['css_class'],
|
||||
style=self.config['pygments_style'],
|
||||
noclasses=self.config['noclasses'],
|
||||
tab_length=self.md.tab_length,
|
||||
use_pygments=self.config['use_pygments']
|
||||
style=self.config.pop('pygments_style', 'default'),
|
||||
**self.config
|
||||
)
|
||||
placeholder = self.md.htmlStash.store(code.hilite())
|
||||
# Clear codeblock in etree instance
|
||||
@@ -237,7 +259,7 @@ class CodeHiliteExtension(Extension):
|
||||
# define default configs
|
||||
self.config = {
|
||||
'linenums': [None,
|
||||
"Use lines numbers. True=yes, False=no, None=auto"],
|
||||
"Use lines numbers. True|table|inline=yes, False=no, None=auto"],
|
||||
'guess_lang': [True,
|
||||
"Automatic language detection - Default: True"],
|
||||
'css_class': ["codehilite",
|
||||
@@ -252,10 +274,25 @@ class CodeHiliteExtension(Extension):
|
||||
'use_pygments': [True,
|
||||
'Use Pygments to Highlight code blocks. '
|
||||
'Disable if using a JavaScript library. '
|
||||
'Default: True']
|
||||
'Default: True'],
|
||||
'lang_prefix': [
|
||||
'language-',
|
||||
'Prefix prepended to the language when use_pygments is false. Default: "language-"'
|
||||
]
|
||||
}
|
||||
|
||||
super().__init__(**kwargs)
|
||||
for key, value in kwargs.items():
|
||||
if key in self.config:
|
||||
self.setConfig(key, value)
|
||||
else:
|
||||
# manually set unknown keywords.
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
# Attempt to parse str as a bool value
|
||||
value = parseBoolValue(value, preserve_none=True)
|
||||
except ValueError:
|
||||
pass # Assume it's not a bool value. Use as-is.
|
||||
self.config[key] = [value, '']
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
""" Add HilitePostprocessor to Markdown instance. """
|
||||
|
||||
@@ -34,8 +34,8 @@ class DefListProcessor(BlockProcessor):
|
||||
|
||||
raw_block = blocks.pop(0)
|
||||
m = self.RE.search(raw_block)
|
||||
terms = [l.strip() for l in
|
||||
raw_block[:m.start()].split('\n') if l.strip()]
|
||||
terms = [term.strip() for term in
|
||||
raw_block[:m.start()].split('\n') if term.strip()]
|
||||
block = raw_block[m.end():]
|
||||
no_indent = self.NO_INDENT_RE.match(block)
|
||||
if no_indent:
|
||||
@@ -87,11 +87,13 @@ class DefListProcessor(BlockProcessor):
|
||||
class DefListIndentProcessor(ListIndentProcessor):
|
||||
""" Process indented children of definition list items. """
|
||||
|
||||
ITEM_TYPES = ['dd']
|
||||
LIST_TYPES = ['dl']
|
||||
# Defintion lists need to be aware of all list types
|
||||
ITEM_TYPES = ['dd', 'li']
|
||||
LIST_TYPES = ['dl', 'ol', 'ul']
|
||||
|
||||
def create_item(self, parent, block):
|
||||
""" Create a new dd and parse the block with it as the parent. """
|
||||
""" Create a new dd or li (depending on parent) and parse the block with it as the parent. """
|
||||
|
||||
dd = etree.SubElement(parent, 'dd')
|
||||
self.parser.parseBlocks(dd, [block])
|
||||
|
||||
|
||||
@@ -15,78 +15,130 @@ All changes Copyright 2008-2014 The Python Markdown Project
|
||||
License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||
"""
|
||||
|
||||
|
||||
from textwrap import dedent
|
||||
from . import Extension
|
||||
from ..preprocessors import Preprocessor
|
||||
from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
|
||||
from .attr_list import get_attrs, AttrListExtension
|
||||
from ..util import parseBoolValue
|
||||
import re
|
||||
|
||||
|
||||
class FencedCodeExtension(Extension):
|
||||
def __init__(self, **kwargs):
|
||||
self.config = {
|
||||
'lang_prefix': ['language-', 'Prefix prepended to the language. Default: "language-"']
|
||||
}
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def extendMarkdown(self, md):
|
||||
""" Add FencedBlockPreprocessor to the Markdown instance. """
|
||||
md.registerExtension(self)
|
||||
|
||||
md.preprocessors.register(FencedBlockPreprocessor(md), 'fenced_code_block', 25)
|
||||
md.preprocessors.register(FencedBlockPreprocessor(md, self.getConfigs()), 'fenced_code_block', 25)
|
||||
|
||||
|
||||
class FencedBlockPreprocessor(Preprocessor):
|
||||
FENCED_BLOCK_RE = re.compile(r'''
|
||||
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # Opening ``` or ~~~
|
||||
(\{?\.?(?P<lang>[\w#.+-]*))?[ ]* # Optional {, and lang
|
||||
# Optional highlight lines, single- or double-quote-delimited
|
||||
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?[ ]*
|
||||
}?[ ]*\n # Optional closing }
|
||||
(?P<code>.*?)(?<=\n)
|
||||
(?P=fence)[ ]*$''', re.MULTILINE | re.DOTALL | re.VERBOSE)
|
||||
CODE_WRAP = '<pre><code%s>%s</code></pre>'
|
||||
LANG_TAG = ' class="%s"'
|
||||
FENCED_BLOCK_RE = re.compile(
|
||||
dedent(r'''
|
||||
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # opening fence
|
||||
((\{(?P<attrs>[^\}\n]*)\})?| # (optional {attrs} or
|
||||
(\.?(?P<lang>[\w#.+-]*))?[ ]* # optional (.)lang
|
||||
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?) # optional hl_lines)
|
||||
[ ]*\n # newline (end of opening fence)
|
||||
(?P<code>.*?)(?<=\n) # the code block
|
||||
(?P=fence)[ ]*$ # closing fence
|
||||
'''),
|
||||
re.MULTILINE | re.DOTALL | re.VERBOSE
|
||||
)
|
||||
|
||||
def __init__(self, md):
|
||||
def __init__(self, md, config):
|
||||
super().__init__(md)
|
||||
|
||||
self.checked_for_codehilite = False
|
||||
self.config = config
|
||||
self.checked_for_deps = False
|
||||
self.codehilite_conf = {}
|
||||
self.use_attr_list = False
|
||||
# List of options to convert to bool values
|
||||
self.bool_options = [
|
||||
'linenums',
|
||||
'guess_lang',
|
||||
'noclasses',
|
||||
'use_pygments'
|
||||
]
|
||||
|
||||
def run(self, lines):
|
||||
""" Match and store Fenced Code Blocks in the HtmlStash. """
|
||||
|
||||
# Check for code hilite extension
|
||||
if not self.checked_for_codehilite:
|
||||
# Check for dependent extensions
|
||||
if not self.checked_for_deps:
|
||||
for ext in self.md.registeredExtensions:
|
||||
if isinstance(ext, CodeHiliteExtension):
|
||||
self.codehilite_conf = ext.config
|
||||
break
|
||||
self.codehilite_conf = ext.getConfigs()
|
||||
if isinstance(ext, AttrListExtension):
|
||||
self.use_attr_list = True
|
||||
|
||||
self.checked_for_codehilite = True
|
||||
self.checked_for_deps = True
|
||||
|
||||
text = "\n".join(lines)
|
||||
while 1:
|
||||
m = self.FENCED_BLOCK_RE.search(text)
|
||||
if m:
|
||||
lang = ''
|
||||
lang, id, classes, config = None, '', [], {}
|
||||
if m.group('attrs'):
|
||||
id, classes, config = self.handle_attrs(get_attrs(m.group('attrs')))
|
||||
if len(classes):
|
||||
lang = classes.pop(0)
|
||||
else:
|
||||
if m.group('lang'):
|
||||
lang = self.LANG_TAG % m.group('lang')
|
||||
lang = m.group('lang')
|
||||
if m.group('hl_lines'):
|
||||
# Support hl_lines outside of attrs for backward-compatibility
|
||||
config['hl_lines'] = parse_hl_lines(m.group('hl_lines'))
|
||||
|
||||
# If config is not empty, then the codehighlite extension
|
||||
# is enabled, so we call it to highlight the code
|
||||
if self.codehilite_conf:
|
||||
if self.codehilite_conf and self.codehilite_conf['use_pygments'] and config.get('use_pygments', True):
|
||||
local_config = self.codehilite_conf.copy()
|
||||
local_config.update(config)
|
||||
# Combine classes with cssclass. Ensure cssclass is at end
|
||||
# as pygments appends a suffix under certain circumstances.
|
||||
# Ignore ID as Pygments does not offer an option to set it.
|
||||
if classes:
|
||||
local_config['css_class'] = '{} {}'.format(
|
||||
' '.join(classes),
|
||||
local_config['css_class']
|
||||
)
|
||||
highliter = CodeHilite(
|
||||
m.group('code'),
|
||||
linenums=self.codehilite_conf['linenums'][0],
|
||||
guess_lang=self.codehilite_conf['guess_lang'][0],
|
||||
css_class=self.codehilite_conf['css_class'][0],
|
||||
style=self.codehilite_conf['pygments_style'][0],
|
||||
use_pygments=self.codehilite_conf['use_pygments'][0],
|
||||
lang=(m.group('lang') or None),
|
||||
noclasses=self.codehilite_conf['noclasses'][0],
|
||||
hl_lines=parse_hl_lines(m.group('hl_lines'))
|
||||
lang=lang,
|
||||
style=local_config.pop('pygments_style', 'default'),
|
||||
**local_config
|
||||
)
|
||||
|
||||
code = highliter.hilite()
|
||||
else:
|
||||
code = self.CODE_WRAP % (lang,
|
||||
self._escape(m.group('code')))
|
||||
id_attr = lang_attr = class_attr = kv_pairs = ''
|
||||
if lang:
|
||||
lang_attr = ' class="{}{}"'.format(self.config.get('lang_prefix', 'language-'), lang)
|
||||
if classes:
|
||||
class_attr = ' class="{}"'.format(' '.join(classes))
|
||||
if id:
|
||||
id_attr = ' id="{}"'.format(id)
|
||||
if self.use_attr_list and config and not config.get('use_pygments', False):
|
||||
# Only assign key/value pairs to code element if attr_list ext is enabled, key/value pairs
|
||||
# were defined on the code block, and the `use_pygments` key was not set to True. The
|
||||
# `use_pygments` key could be either set to False or not defined. It is omitted from output.
|
||||
kv_pairs = ' ' + ' '.join(
|
||||
'{k}="{v}"'.format(k=k, v=v) for k, v in config.items() if k != 'use_pygments'
|
||||
)
|
||||
code = '<pre{id}{cls}><code{lang}{kv}>{code}</code></pre>'.format(
|
||||
id=id_attr,
|
||||
cls=class_attr,
|
||||
lang=lang_attr,
|
||||
kv=kv_pairs,
|
||||
code=self._escape(m.group('code'))
|
||||
)
|
||||
|
||||
placeholder = self.md.htmlStash.store(code)
|
||||
text = '{}\n{}\n{}'.format(text[:m.start()],
|
||||
@@ -96,6 +148,24 @@ class FencedBlockPreprocessor(Preprocessor):
|
||||
break
|
||||
return text.split("\n")
|
||||
|
||||
def handle_attrs(self, attrs):
|
||||
""" Return tuple: (id, [list, of, classes], {configs}) """
|
||||
id = ''
|
||||
classes = []
|
||||
configs = {}
|
||||
for k, v in attrs:
|
||||
if k == 'id':
|
||||
id = v
|
||||
elif k == '.':
|
||||
classes.append(v)
|
||||
elif k == 'hl_lines':
|
||||
configs[k] = parse_hl_lines(v)
|
||||
elif k in self.bool_options:
|
||||
configs[k] = parseBoolValue(v, fail_on_errors=False, preserve_none=True)
|
||||
else:
|
||||
configs[k] = v
|
||||
return id, classes, configs
|
||||
|
||||
def _escape(self, txt):
|
||||
""" basic html escaping """
|
||||
txt = txt.replace('&', '&')
|
||||
|
||||
@@ -14,7 +14,7 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||
"""
|
||||
|
||||
from . import Extension
|
||||
from ..preprocessors import Preprocessor
|
||||
from ..blockprocessors import BlockProcessor
|
||||
from ..inlinepatterns import InlineProcessor
|
||||
from ..treeprocessors import Treeprocessor
|
||||
from ..postprocessors import Postprocessor
|
||||
@@ -26,8 +26,6 @@ import xml.etree.ElementTree as etree
|
||||
|
||||
FN_BACKLINK_TEXT = util.STX + "zz1337820767766393qq" + util.ETX
|
||||
NBSP_PLACEHOLDER = util.STX + "qq3936677670287331zz" + util.ETX
|
||||
DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)')
|
||||
TABBED_RE = re.compile(r'((\t)|( ))(.*)')
|
||||
RE_REF_ID = re.compile(r'(fnref)(\d+)')
|
||||
|
||||
|
||||
@@ -72,8 +70,8 @@ class FootnoteExtension(Extension):
|
||||
md.registerExtension(self)
|
||||
self.parser = md.parser
|
||||
self.md = md
|
||||
# Insert a preprocessor before ReferencePreprocessor
|
||||
md.preprocessors.register(FootnotePreprocessor(self), 'footnote', 15)
|
||||
# Insert a blockprocessor before ReferencePreprocessor
|
||||
md.parser.blockprocessors.register(FootnoteBlockProcessor(self), 'footnote', 17)
|
||||
|
||||
# Insert an inline pattern before ImageReferencePattern
|
||||
FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
|
||||
@@ -202,106 +200,92 @@ class FootnoteExtension(Extension):
|
||||
return div
|
||||
|
||||
|
||||
class FootnotePreprocessor(Preprocessor):
|
||||
class FootnoteBlockProcessor(BlockProcessor):
|
||||
""" Find all footnote references and store for later use. """
|
||||
|
||||
RE = re.compile(r'^[ ]{0,3}\[\^([^\]]*)\]:[ ]*(.*)$', re.MULTILINE)
|
||||
|
||||
def __init__(self, footnotes):
|
||||
super().__init__(footnotes.parser)
|
||||
self.footnotes = footnotes
|
||||
|
||||
def run(self, lines):
|
||||
"""
|
||||
Loop through lines and find, set, and remove footnote definitions.
|
||||
def test(self, parent, block):
|
||||
return True
|
||||
|
||||
Keywords:
|
||||
|
||||
* lines: A list of lines of text
|
||||
|
||||
Return: A list of lines of text with footnote definitions removed.
|
||||
|
||||
"""
|
||||
newlines = []
|
||||
i = 0
|
||||
while True:
|
||||
m = DEF_RE.match(lines[i])
|
||||
def run(self, parent, blocks):
|
||||
""" Find, set, and remove footnote definitions. """
|
||||
block = blocks.pop(0)
|
||||
m = self.RE.search(block)
|
||||
if m:
|
||||
fn, _i = self.detectTabbed(lines[i+1:])
|
||||
fn.insert(0, m.group(2))
|
||||
i += _i-1 # skip past footnote
|
||||
footnote = "\n".join(fn)
|
||||
self.footnotes.setFootnote(m.group(1), footnote.rstrip())
|
||||
# Preserve a line for each block to prevent raw HTML indexing issue.
|
||||
# https://github.com/Python-Markdown/markdown/issues/584
|
||||
num_blocks = (len(footnote.split('\n\n')) * 2)
|
||||
newlines.extend([''] * (num_blocks))
|
||||
else:
|
||||
newlines.append(lines[i])
|
||||
if len(lines) > i+1:
|
||||
i += 1
|
||||
else:
|
||||
break
|
||||
return newlines
|
||||
id = m.group(1)
|
||||
fn_blocks = [m.group(2)]
|
||||
|
||||
def detectTabbed(self, lines):
|
||||
# Handle rest of block
|
||||
therest = block[m.end():].lstrip('\n')
|
||||
m2 = self.RE.search(therest)
|
||||
if m2:
|
||||
# Another footnote exists in the rest of this block.
|
||||
# Any content before match is continuation of this footnote, which may be lazily indented.
|
||||
before = therest[:m2.start()].rstrip('\n')
|
||||
fn_blocks[0] = '\n'.join([fn_blocks[0], self.detab(before)]).lstrip('\n')
|
||||
# Add back to blocks everything from begining of match forward for next iteration.
|
||||
blocks.insert(0, therest[m2.start():])
|
||||
else:
|
||||
# All remaining lines of block are continuation of this footnote, which may be lazily indented.
|
||||
fn_blocks[0] = '\n'.join([fn_blocks[0], self.detab(therest)]).strip('\n')
|
||||
|
||||
# Check for child elements in remaining blocks.
|
||||
fn_blocks.extend(self.detectTabbed(blocks))
|
||||
|
||||
footnote = "\n\n".join(fn_blocks)
|
||||
self.footnotes.setFootnote(id, footnote.rstrip())
|
||||
|
||||
if block[:m.start()].strip():
|
||||
# Add any content before match back to blocks as separate block
|
||||
blocks.insert(0, block[:m.start()].rstrip('\n'))
|
||||
return True
|
||||
# No match. Restore block.
|
||||
blocks.insert(0, block)
|
||||
return False
|
||||
|
||||
def detectTabbed(self, blocks):
|
||||
""" Find indented text and remove indent before further proccesing.
|
||||
|
||||
Keyword arguments:
|
||||
|
||||
* lines: an array of strings
|
||||
|
||||
Returns: a list of post processed items and the index of last line.
|
||||
|
||||
Returns: a list of blocks with indentation removed.
|
||||
"""
|
||||
items = []
|
||||
blank_line = False # have we encountered a blank line yet?
|
||||
i = 0 # to keep track of where we are
|
||||
|
||||
def detab(line):
|
||||
match = TABBED_RE.match(line)
|
||||
if match:
|
||||
return match.group(4)
|
||||
|
||||
for line in lines:
|
||||
if line.strip(): # Non-blank line
|
||||
detabbed_line = detab(line)
|
||||
if detabbed_line:
|
||||
items.append(detabbed_line)
|
||||
i += 1
|
||||
continue
|
||||
elif not blank_line and not DEF_RE.match(line):
|
||||
# not tabbed but still part of first par.
|
||||
items.append(line)
|
||||
i += 1
|
||||
continue
|
||||
else:
|
||||
return items, i+1
|
||||
|
||||
else: # Blank line: _maybe_ we are done.
|
||||
blank_line = True
|
||||
i += 1 # advance
|
||||
|
||||
# Find the next non-blank line
|
||||
for j in range(i, len(lines)):
|
||||
if lines[j].strip():
|
||||
next_line = lines[j]
|
||||
fn_blocks = []
|
||||
while blocks:
|
||||
if blocks[0].startswith(' '*4):
|
||||
block = blocks.pop(0)
|
||||
# Check for new footnotes within this block and split at new footnote.
|
||||
m = self.RE.search(block)
|
||||
if m:
|
||||
# Another footnote exists in this block.
|
||||
# Any content before match is continuation of this footnote, which may be lazily indented.
|
||||
before = block[:m.start()].rstrip('\n')
|
||||
fn_blocks.append(self.detab(before))
|
||||
# Add back to blocks everything from begining of match forward for next iteration.
|
||||
blocks.insert(0, block[m.start():])
|
||||
# End of this footnote.
|
||||
break
|
||||
else:
|
||||
# Include extreaneous padding to prevent raw HTML
|
||||
# parsing issue: https://github.com/Python-Markdown/markdown/issues/584
|
||||
items.append("")
|
||||
i += 1
|
||||
# Entire block is part of this footnote.
|
||||
fn_blocks.append(self.detab(block))
|
||||
else:
|
||||
break # There is no more text; we are done.
|
||||
# End of this footnote.
|
||||
break
|
||||
return fn_blocks
|
||||
|
||||
# Check if the next non-blank line is tabbed
|
||||
if detab(next_line): # Yes, more work to do.
|
||||
items.append("")
|
||||
continue
|
||||
else:
|
||||
break # No, we are done.
|
||||
else:
|
||||
i += 1
|
||||
def detab(self, block):
|
||||
""" Remove one level of indent from a block.
|
||||
|
||||
return items, i
|
||||
Preserve lazily indented blocks by only removing indent from indented lines.
|
||||
"""
|
||||
lines = block.split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
if line.startswith(' '*4):
|
||||
lines[i] = line[4:]
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class FootnoteInlineProcessor(InlineProcessor):
|
||||
@@ -347,8 +331,8 @@ class FootnotePostTreeprocessor(Treeprocessor):
|
||||
self.offset += 1
|
||||
# Add all the new duplicate links.
|
||||
el = list(li)[-1]
|
||||
for l in links:
|
||||
el.append(l)
|
||||
for link in links:
|
||||
el.append(link)
|
||||
break
|
||||
|
||||
def get_num_duplicates(self, li):
|
||||
|
||||
@@ -21,7 +21,7 @@ EMPHASIS_RE = r'(_)([^_]+)\1'
|
||||
STRONG_RE = r'(_{2})(.+?)\1'
|
||||
|
||||
# __strong_em___
|
||||
STRONG_EM_RE = r'(_)\1(?!\1)(.+?)\1(?!\1)(.+?)\1{3}'
|
||||
STRONG_EM_RE = r'(_)\1(?!\1)([^_]+?)\1(?!\1)(.+?)\1{3}'
|
||||
|
||||
|
||||
class LegacyUnderscoreProcessor(UnderscoreProcessor):
|
||||
|
||||
@@ -16,68 +16,313 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||
|
||||
from . import Extension
|
||||
from ..blockprocessors import BlockProcessor
|
||||
from ..preprocessors import Preprocessor
|
||||
from ..postprocessors import RawHtmlPostprocessor
|
||||
from .. import util
|
||||
import re
|
||||
from ..htmlparser import HTMLExtractor
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
|
||||
class MarkdownInHtmlProcessor(BlockProcessor):
|
||||
"""Process Markdown Inside HTML Blocks."""
|
||||
def test(self, parent, block):
|
||||
return block == util.TAG_PLACEHOLDER % \
|
||||
str(self.parser.blockprocessors.tag_counter + 1)
|
||||
class HTMLExtractorExtra(HTMLExtractor):
|
||||
"""
|
||||
Override HTMLExtractor and create etree Elements for any elements which should have content parsed as Markdown.
|
||||
"""
|
||||
|
||||
def _process_nests(self, element, block):
|
||||
"""Process the element's child elements in self.run."""
|
||||
# Build list of indexes of each nest within the parent element.
|
||||
nest_index = [] # a list of tuples: (left index, right index)
|
||||
i = self.parser.blockprocessors.tag_counter + 1
|
||||
while len(self._tag_data) > i and self._tag_data[i]['left_index']:
|
||||
left_child_index = self._tag_data[i]['left_index']
|
||||
right_child_index = self._tag_data[i]['right_index']
|
||||
nest_index.append((left_child_index - 1, right_child_index))
|
||||
i += 1
|
||||
def __init__(self, md, *args, **kwargs):
|
||||
# All block-level tags.
|
||||
self.block_level_tags = set(md.block_level_elements.copy())
|
||||
# Block-level tags in which the content only gets span level parsing
|
||||
self.span_tags = set(
|
||||
['address', 'dd', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'legend', 'li', 'p', 'td', 'th']
|
||||
)
|
||||
# Block-level tags which never get their content parsed.
|
||||
self.raw_tags = set(['canvas', 'math', 'option', 'pre', 'script', 'style', 'textarea'])
|
||||
# Block-level tags in which the content gets parsed as blocks
|
||||
super().__init__(md, *args, **kwargs)
|
||||
|
||||
# Create each nest subelement.
|
||||
for i, (left_index, right_index) in enumerate(nest_index[:-1]):
|
||||
self.run(element, block[left_index:right_index],
|
||||
block[right_index:nest_index[i + 1][0]], True)
|
||||
self.run(element, block[nest_index[-1][0]:nest_index[-1][1]], # last
|
||||
block[nest_index[-1][1]:], True) # nest
|
||||
self.block_tags = set(self.block_level_tags) - (self.span_tags | self.raw_tags | self.empty_tags)
|
||||
self.span_and_blocks_tags = self.block_tags | self.span_tags
|
||||
|
||||
def run(self, parent, blocks, tail=None, nest=False):
|
||||
self._tag_data = self.parser.md.htmlStash.tag_data
|
||||
def reset(self):
|
||||
"""Reset this instance. Loses all unprocessed data."""
|
||||
self.mdstack = [] # When markdown=1, stack contains a list of tags
|
||||
self.treebuilder = etree.TreeBuilder()
|
||||
self.mdstate = [] # one of 'block', 'span', 'off', or None
|
||||
super().reset()
|
||||
|
||||
self.parser.blockprocessors.tag_counter += 1
|
||||
tag = self._tag_data[self.parser.blockprocessors.tag_counter]
|
||||
def close(self):
|
||||
"""Handle any buffered data."""
|
||||
super().close()
|
||||
# Handle any unclosed tags.
|
||||
if self.mdstack:
|
||||
# Close the outermost parent. handle_endtag will close all unclosed children.
|
||||
self.handle_endtag(self.mdstack[0])
|
||||
|
||||
# Create Element
|
||||
markdown_value = tag['attrs'].pop('markdown')
|
||||
element = etree.SubElement(parent, tag['tag'], tag['attrs'])
|
||||
def get_element(self):
|
||||
""" Return element from treebuilder and reset treebuilder for later use. """
|
||||
element = self.treebuilder.close()
|
||||
self.treebuilder = etree.TreeBuilder()
|
||||
return element
|
||||
|
||||
# Slice Off Block
|
||||
if nest:
|
||||
self.parser.parseBlocks(parent, tail) # Process Tail
|
||||
block = blocks[1:]
|
||||
else: # includes nests since a third level of nesting isn't supported
|
||||
block = blocks[tag['left_index'] + 1: tag['right_index']]
|
||||
del blocks[:tag['right_index']]
|
||||
def get_state(self, tag, attrs):
|
||||
""" Return state from tag and `markdown` attr. One of 'block', 'span', or 'off'. """
|
||||
md_attr = attrs.get('markdown', '0')
|
||||
if md_attr == 'markdown':
|
||||
# `<tag markdown>` is the same as `<tag markdown='1'>`.
|
||||
md_attr = '1'
|
||||
parent_state = self.mdstate[-1] if self.mdstate else None
|
||||
if parent_state == 'off' or (parent_state == 'span' and md_attr != '0'):
|
||||
# Only use the parent state if it is more restrictive than the markdown attribute.
|
||||
md_attr = parent_state
|
||||
if ((md_attr == '1' and tag in self.block_tags) or
|
||||
(md_attr == 'block' and tag in self.span_and_blocks_tags)):
|
||||
return 'block'
|
||||
elif ((md_attr == '1' and tag in self.span_tags) or
|
||||
(md_attr == 'span' and tag in self.span_and_blocks_tags)):
|
||||
return 'span'
|
||||
elif tag in self.block_level_tags:
|
||||
return 'off'
|
||||
else: # pragma: no cover
|
||||
return None
|
||||
|
||||
# Process Text
|
||||
if (self.parser.blockprocessors.contain_span_tags.match( # Span Mode
|
||||
tag['tag']) and markdown_value != 'block') or \
|
||||
markdown_value == 'span':
|
||||
element.text = '\n'.join(block)
|
||||
else: # Block Mode
|
||||
i = self.parser.blockprocessors.tag_counter + 1
|
||||
if len(self._tag_data) > i and self._tag_data[i]['left_index']:
|
||||
first_subelement_index = self._tag_data[i]['left_index'] - 1
|
||||
self.parser.parseBlocks(
|
||||
element, block[:first_subelement_index])
|
||||
if not nest:
|
||||
block = self._process_nests(element, block)
|
||||
def at_line_start(self):
|
||||
"""At line start."""
|
||||
|
||||
value = super().at_line_start()
|
||||
if not value and self.cleandoc and self.cleandoc[-1].endswith('\n'):
|
||||
value = True
|
||||
return value
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
# Handle tags that should always be empty and do not specify a closing tag
|
||||
if tag in self.empty_tags:
|
||||
attrs = {key: value if value is not None else key for key, value in attrs}
|
||||
if "markdown" in attrs:
|
||||
attrs.pop('markdown')
|
||||
element = etree.Element(tag, attrs)
|
||||
data = etree.tostring(element, encoding='unicode', method='html')
|
||||
else:
|
||||
self.parser.parseBlocks(element, block)
|
||||
data = self.get_starttag_text()
|
||||
self.handle_empty_tag(data, True)
|
||||
return
|
||||
|
||||
if tag in self.block_level_tags:
|
||||
# Valueless attr (ex: `<tag checked>`) results in `[('checked', None)]`.
|
||||
# Convert to `{'checked': 'checked'}`.
|
||||
attrs = {key: value if value is not None else key for key, value in attrs}
|
||||
state = self.get_state(tag, attrs)
|
||||
|
||||
if self.inraw or (state in [None, 'off'] and not self.mdstack) or not self.at_line_start():
|
||||
# fall back to default behavior
|
||||
attrs.pop('markdown', None)
|
||||
super().handle_starttag(tag, attrs)
|
||||
else:
|
||||
if 'p' in self.mdstack and tag in self.block_level_tags:
|
||||
# Close unclosed 'p' tag
|
||||
self.handle_endtag('p')
|
||||
self.mdstate.append(state)
|
||||
self.mdstack.append(tag)
|
||||
attrs['markdown'] = state
|
||||
self.treebuilder.start(tag, attrs)
|
||||
else:
|
||||
# Span level tag
|
||||
if self.inraw:
|
||||
super().handle_starttag(tag, attrs)
|
||||
else:
|
||||
text = self.get_starttag_text()
|
||||
if self.mdstate and self.mdstate[-1] == "off":
|
||||
self.handle_data(self.md.htmlStash.store(text))
|
||||
else:
|
||||
self.handle_data(text)
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in self.block_level_tags:
|
||||
if self.inraw:
|
||||
super().handle_endtag(tag)
|
||||
elif tag in self.mdstack:
|
||||
# Close element and any unclosed children
|
||||
while self.mdstack:
|
||||
item = self.mdstack.pop()
|
||||
self.mdstate.pop()
|
||||
self.treebuilder.end(item)
|
||||
if item == tag:
|
||||
break
|
||||
if not self.mdstack:
|
||||
# Last item in stack is closed. Stash it
|
||||
element = self.get_element()
|
||||
# Get last entry to see if it ends in newlines
|
||||
# If it is an element, assume there is no newlines
|
||||
item = self.cleandoc[-1] if self.cleandoc else ''
|
||||
# If we only have one newline before block element, add another
|
||||
if not item.endswith('\n\n') and item.endswith('\n'):
|
||||
self.cleandoc.append('\n')
|
||||
self.cleandoc.append(self.md.htmlStash.store(element))
|
||||
self.cleandoc.append('\n\n')
|
||||
self.state = []
|
||||
else:
|
||||
# Treat orphan closing tag as a span level tag.
|
||||
text = self.get_endtag_text(tag)
|
||||
if self.mdstate and self.mdstate[-1] == "off":
|
||||
self.handle_data(self.md.htmlStash.store(text))
|
||||
else:
|
||||
self.handle_data(text)
|
||||
else:
|
||||
# Span level tag
|
||||
if self.inraw:
|
||||
super().handle_endtag(tag)
|
||||
else:
|
||||
text = self.get_endtag_text(tag)
|
||||
if self.mdstate and self.mdstate[-1] == "off":
|
||||
self.handle_data(self.md.htmlStash.store(text))
|
||||
else:
|
||||
self.handle_data(text)
|
||||
|
||||
def handle_startendtag(self, tag, attrs):
|
||||
if tag in self.empty_tags:
|
||||
attrs = {key: value if value is not None else key for key, value in attrs}
|
||||
if "markdown" in attrs:
|
||||
attrs.pop('markdown')
|
||||
element = etree.Element(tag, attrs)
|
||||
data = etree.tostring(element, encoding='unicode', method='html')
|
||||
else:
|
||||
data = self.get_starttag_text()
|
||||
else:
|
||||
data = self.get_starttag_text()
|
||||
self.handle_empty_tag(data, is_block=self.md.is_block_level(tag))
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.inraw or not self.mdstack:
|
||||
super().handle_data(data)
|
||||
else:
|
||||
self.treebuilder.data(data)
|
||||
|
||||
def handle_empty_tag(self, data, is_block):
|
||||
if self.inraw or not self.mdstack:
|
||||
super().handle_empty_tag(data, is_block)
|
||||
else:
|
||||
if self.at_line_start() and is_block:
|
||||
self.handle_data('\n' + self.md.htmlStash.store(data) + '\n\n')
|
||||
else:
|
||||
if self.mdstate and self.mdstate[-1] == "off":
|
||||
self.handle_data(self.md.htmlStash.store(data))
|
||||
else:
|
||||
self.handle_data(data)
|
||||
|
||||
|
||||
class HtmlBlockPreprocessor(Preprocessor):
|
||||
"""Remove html blocks from the text and store them for later retrieval."""
|
||||
|
||||
def run(self, lines):
|
||||
source = '\n'.join(lines)
|
||||
parser = HTMLExtractorExtra(self.md)
|
||||
parser.feed(source)
|
||||
parser.close()
|
||||
return ''.join(parser.cleandoc).split('\n')
|
||||
|
||||
|
||||
class MarkdownInHtmlProcessor(BlockProcessor):
|
||||
"""Process Markdown Inside HTML Blocks which have been stored in the HtmlStash."""
|
||||
|
||||
def test(self, parent, block):
|
||||
# ALways return True. `run` will return `False` it not a valid match.
|
||||
return True
|
||||
|
||||
def parse_element_content(self, element):
|
||||
"""
|
||||
Resursively parse the text content of an etree Element as Markdown.
|
||||
|
||||
Any block level elements generated from the Markdown will be inserted as children of the element in place
|
||||
of the text content. All `markdown` attributes are removed. For any elements in which Markdown parsing has
|
||||
been dissabled, the text content of it and its chidlren are wrapped in an `AtomicString`.
|
||||
"""
|
||||
|
||||
md_attr = element.attrib.pop('markdown', 'off')
|
||||
|
||||
if md_attr == 'block':
|
||||
# Parse content as block level
|
||||
# The order in which the different parts are parsed (text, children, tails) is important here as the
|
||||
# order of elements needs to be preserved. We can't be inserting items at a later point in the current
|
||||
# iteration as we don't want to do raw processing on elements created from parsing Markdown text (for
|
||||
# example). Therefore, the order of operations is children, tails, text.
|
||||
|
||||
# Recursively parse existing children from raw HTML
|
||||
for child in list(element):
|
||||
self.parse_element_content(child)
|
||||
|
||||
# Parse Markdown text in tail of children. Do this seperate to avoid raw HTML parsing.
|
||||
# Save the position of each item to be inserted later in reverse.
|
||||
tails = []
|
||||
for pos, child in enumerate(element):
|
||||
if child.tail:
|
||||
block = child.tail.rstrip('\n')
|
||||
child.tail = ''
|
||||
# Use a dummy placeholder element.
|
||||
dummy = etree.Element('div')
|
||||
self.parser.parseBlocks(dummy, block.split('\n\n'))
|
||||
children = list(dummy)
|
||||
children.reverse()
|
||||
tails.append((pos + 1, children))
|
||||
|
||||
# Insert the elements created from the tails in reverse.
|
||||
tails.reverse()
|
||||
for pos, tail in tails:
|
||||
for item in tail:
|
||||
element.insert(pos, item)
|
||||
|
||||
# Parse Markdown text content. Do this last to avoid raw HTML parsing.
|
||||
if element.text:
|
||||
block = element.text.rstrip('\n')
|
||||
element.text = ''
|
||||
# Use a dummy placeholder element as the content needs to get inserted before existing children.
|
||||
dummy = etree.Element('div')
|
||||
self.parser.parseBlocks(dummy, block.split('\n\n'))
|
||||
children = list(dummy)
|
||||
children.reverse()
|
||||
for child in children:
|
||||
element.insert(0, child)
|
||||
|
||||
elif md_attr == 'span':
|
||||
# Span level parsing will be handled by inlineprocessors.
|
||||
# Walk children here to remove any `markdown` attributes.
|
||||
for child in list(element):
|
||||
self.parse_element_content(child)
|
||||
|
||||
else:
|
||||
# Disable inline parsing for everything else
|
||||
if element.text is None:
|
||||
element.text = ''
|
||||
element.text = util.AtomicString(element.text)
|
||||
for child in list(element):
|
||||
self.parse_element_content(child)
|
||||
if child.tail:
|
||||
child.tail = util.AtomicString(child.tail)
|
||||
|
||||
def run(self, parent, blocks):
|
||||
m = util.HTML_PLACEHOLDER_RE.match(blocks[0])
|
||||
if m:
|
||||
index = int(m.group(1))
|
||||
element = self.parser.md.htmlStash.rawHtmlBlocks[index]
|
||||
if isinstance(element, etree.Element):
|
||||
# We have a matched element. Process it.
|
||||
blocks.pop(0)
|
||||
self.parse_element_content(element)
|
||||
parent.append(element)
|
||||
# Cleanup stash. Replace element with empty string to avoid confusing postprocessor.
|
||||
self.parser.md.htmlStash.rawHtmlBlocks.pop(index)
|
||||
self.parser.md.htmlStash.rawHtmlBlocks.insert(index, '')
|
||||
# Comfirm the match to the blockparser.
|
||||
return True
|
||||
# No match found.
|
||||
return False
|
||||
|
||||
|
||||
class MarkdownInHTMLPostprocessor(RawHtmlPostprocessor):
|
||||
def stash_to_string(self, text):
|
||||
""" Override default to handle any etree elements still in the stash. """
|
||||
if isinstance(text, etree.Element):
|
||||
return self.md.serializer(text)
|
||||
else:
|
||||
return str(text)
|
||||
|
||||
|
||||
class MarkdownInHtmlExtension(Extension):
|
||||
@@ -86,14 +331,14 @@ class MarkdownInHtmlExtension(Extension):
|
||||
def extendMarkdown(self, md):
|
||||
""" Register extension instances. """
|
||||
|
||||
# Turn on processing of markdown text within raw html
|
||||
md.preprocessors['html_block'].markdown_in_raw = True
|
||||
# Replace raw HTML preprocessor
|
||||
md.preprocessors.register(HtmlBlockPreprocessor(md), 'html_block', 20)
|
||||
# Add blockprocessor which handles the placeholders for etree elements
|
||||
md.parser.blockprocessors.register(
|
||||
MarkdownInHtmlProcessor(md.parser), 'markdown_block', 105
|
||||
)
|
||||
md.parser.blockprocessors.tag_counter = -1
|
||||
md.parser.blockprocessors.contain_span_tags = re.compile(
|
||||
r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
|
||||
# Replace raw HTML postprocessor
|
||||
md.postprocessors.register(MarkdownInHTMLPostprocessor(md), 'raw_html', 30)
|
||||
|
||||
|
||||
def makeExtension(**kwargs): # pragma: no cover
|
||||
|
||||
@@ -15,18 +15,24 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||
|
||||
from . import Extension
|
||||
from ..treeprocessors import Treeprocessor
|
||||
from ..util import code_escape, parseBoolValue, AMP_SUBSTITUTE, HTML_PLACEHOLDER_RE
|
||||
from ..util import code_escape, parseBoolValue, AMP_SUBSTITUTE, HTML_PLACEHOLDER_RE, AtomicString
|
||||
from ..postprocessors import UnescapePostprocessor
|
||||
import re
|
||||
import html
|
||||
import unicodedata
|
||||
import xml.etree.ElementTree as etree
|
||||
|
||||
|
||||
def slugify(value, separator):
|
||||
def slugify(value, separator, encoding='ascii'):
|
||||
""" Slugify a string, to make it URL friendly. """
|
||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
||||
value = re.sub(r'[^\w\s-]', '', value.decode('ascii')).strip().lower()
|
||||
return re.sub(r'[%s\s]+' % separator, separator, value)
|
||||
value = unicodedata.normalize('NFKD', value).encode(encoding, 'ignore')
|
||||
value = re.sub(r'[^\w\s-]', '', value.decode(encoding)).strip().lower()
|
||||
return re.sub(r'[{}\s]+'.format(separator), separator, value)
|
||||
|
||||
|
||||
def slugify_unicode(value, separator):
|
||||
""" Slugify a string, to make it URL friendly while preserving Unicode characters. """
|
||||
return slugify(value, separator, 'utf-8')
|
||||
|
||||
|
||||
IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$')
|
||||
@@ -44,6 +50,18 @@ def unique(id, ids):
|
||||
return id
|
||||
|
||||
|
||||
def get_name(el):
|
||||
"""Get title name."""
|
||||
|
||||
text = []
|
||||
for c in el.itertext():
|
||||
if isinstance(c, AtomicString):
|
||||
text.append(html.unescape(c))
|
||||
else:
|
||||
text.append(c)
|
||||
return ''.join(text).strip()
|
||||
|
||||
|
||||
def stashedHTML2text(text, md, strip_entities=True):
|
||||
""" Extract raw HTML from stash, reduce to plain text and swap with placeholder. """
|
||||
def _html_sub(m):
|
||||
@@ -253,7 +271,7 @@ class TocTreeprocessor(Treeprocessor):
|
||||
self.set_level(el)
|
||||
if int(el.tag[-1]) < self.toc_top or int(el.tag[-1]) > self.toc_bottom:
|
||||
continue
|
||||
text = ''.join(el.itertext()).strip()
|
||||
text = get_name(el)
|
||||
|
||||
# Do not override pre-existing ids
|
||||
if "id" not in el.attrib:
|
||||
|
||||
@@ -84,6 +84,9 @@ def build_inlinepatterns(md, **kwargs):
|
||||
inlinePatterns.register(
|
||||
ShortReferenceInlineProcessor(REFERENCE_RE, md), 'short_reference', 130
|
||||
)
|
||||
inlinePatterns.register(
|
||||
ShortImageReferenceInlineProcessor(IMAGE_REFERENCE_RE, md), 'short_image_ref', 125
|
||||
)
|
||||
inlinePatterns.register(AutolinkInlineProcessor(AUTOLINK_RE, md), 'autolink', 120)
|
||||
inlinePatterns.register(AutomailInlineProcessor(AUTOMAIL_RE, md), 'automail', 110)
|
||||
inlinePatterns.register(SubstituteTagInlineProcessor(LINE_BREAK_RE, 'br'), 'linebreak', 100)
|
||||
@@ -135,8 +138,8 @@ STRONG_EM_RE = r'(\*)\1{2}(.+?)\1{2}(.*?)\1'
|
||||
# ___strong__em_
|
||||
STRONG_EM2_RE = r'(_)\1{2}(.+?)\1{2}(.*?)\1'
|
||||
|
||||
# __strong_em___
|
||||
STRONG_EM3_RE = r'(\*)\1(?!\1)(.+?)\1(?!\1)(.+?)\1{3}'
|
||||
# **strong*em***
|
||||
STRONG_EM3_RE = r'(\*)\1(?!\1)([^*]+?)\1(?!\1)(.+?)\1{3}'
|
||||
|
||||
# [text](url) or [text](<url>) or [text](url "title")
|
||||
LINK_RE = NOIMG + r'\['
|
||||
@@ -844,6 +847,14 @@ class ImageReferenceInlineProcessor(ReferenceInlineProcessor):
|
||||
return el
|
||||
|
||||
|
||||
class ShortImageReferenceInlineProcessor(ImageReferenceInlineProcessor):
|
||||
""" Short form of inage reference: ![ref]. """
|
||||
def evalId(self, data, index, text):
|
||||
"""Evaluate the id from of [ref] """
|
||||
|
||||
return text.lower(), index, True
|
||||
|
||||
|
||||
class AutolinkInlineProcessor(InlineProcessor):
|
||||
""" Return a link Element given an autolink (`<http://example/com>`). """
|
||||
def handleMatch(self, m, data):
|
||||
|
||||
@@ -69,11 +69,10 @@ class RawHtmlPostprocessor(Postprocessor):
|
||||
""" Iterate over html stash and restore html. """
|
||||
replacements = OrderedDict()
|
||||
for i in range(self.md.htmlStash.html_counter):
|
||||
html = self.md.htmlStash.rawHtmlBlocks[i]
|
||||
html = self.stash_to_string(self.md.htmlStash.rawHtmlBlocks[i])
|
||||
if self.isblocklevel(html):
|
||||
replacements["<p>%s</p>" %
|
||||
(self.md.htmlStash.get_placeholder(i))] = \
|
||||
html + "\n"
|
||||
replacements["<p>{}</p>".format(
|
||||
self.md.htmlStash.get_placeholder(i))] = html
|
||||
replacements[self.md.htmlStash.get_placeholder(i)] = html
|
||||
|
||||
if replacements:
|
||||
@@ -96,6 +95,10 @@ class RawHtmlPostprocessor(Postprocessor):
|
||||
return self.md.is_block_level(m.group(1))
|
||||
return False
|
||||
|
||||
def stash_to_string(self, text):
|
||||
""" Convert a stashed object to a string. """
|
||||
return str(text)
|
||||
|
||||
|
||||
class AndSubstitutePostprocessor(Postprocessor):
|
||||
""" Restore valid entities """
|
||||
|
||||
@@ -26,6 +26,7 @@ complicated.
|
||||
"""
|
||||
|
||||
from . import util
|
||||
from .htmlparser import HTMLExtractor
|
||||
import re
|
||||
|
||||
|
||||
@@ -34,7 +35,6 @@ def build_preprocessors(md, **kwargs):
|
||||
preprocessors = util.Registry()
|
||||
preprocessors.register(NormalizeWhitespace(md), 'normalize_whitespace', 30)
|
||||
preprocessors.register(HtmlBlockPreprocessor(md), 'html_block', 20)
|
||||
preprocessors.register(ReferencePreprocessor(md), 'reference', 10)
|
||||
return preprocessors
|
||||
|
||||
|
||||
@@ -74,297 +74,9 @@ class NormalizeWhitespace(Preprocessor):
|
||||
class HtmlBlockPreprocessor(Preprocessor):
|
||||
"""Remove html blocks from the text and store them for later retrieval."""
|
||||
|
||||
right_tag_patterns = ["</%s>", "%s>"]
|
||||
attrs_pattern = r"""
|
||||
\s+(?P<attr>[^>"'/= ]+)=(?P<q>['"])(?P<value>.*?)(?P=q) # attr="value"
|
||||
| # OR
|
||||
\s+(?P<attr1>[^>"'/= ]+)=(?P<value1>[^> ]+) # attr=value
|
||||
| # OR
|
||||
\s+(?P<attr2>[^>"'/= ]+) # attr
|
||||
"""
|
||||
left_tag_pattern = r'^\<(?P<tag>[^> ]+)(?P<attrs>(%s)*)\s*\/?\>?' % \
|
||||
attrs_pattern
|
||||
attrs_re = re.compile(attrs_pattern, re.VERBOSE)
|
||||
left_tag_re = re.compile(left_tag_pattern, re.VERBOSE)
|
||||
markdown_in_raw = False
|
||||
|
||||
def _get_left_tag(self, block):
|
||||
m = self.left_tag_re.match(block)
|
||||
if m:
|
||||
tag = m.group('tag')
|
||||
raw_attrs = m.group('attrs')
|
||||
attrs = {}
|
||||
if raw_attrs:
|
||||
for ma in self.attrs_re.finditer(raw_attrs):
|
||||
if ma.group('attr'):
|
||||
if ma.group('value'):
|
||||
attrs[ma.group('attr').strip()] = ma.group('value')
|
||||
else:
|
||||
attrs[ma.group('attr').strip()] = ""
|
||||
elif ma.group('attr1'):
|
||||
if ma.group('value1'):
|
||||
attrs[ma.group('attr1').strip()] = ma.group(
|
||||
'value1'
|
||||
)
|
||||
else:
|
||||
attrs[ma.group('attr1').strip()] = ""
|
||||
elif ma.group('attr2'):
|
||||
attrs[ma.group('attr2').strip()] = ""
|
||||
return tag, len(m.group(0)), attrs
|
||||
else:
|
||||
tag = block[1:].split(">", 1)[0].lower()
|
||||
return tag, len(tag)+2, {}
|
||||
|
||||
def _recursive_tagfind(self, ltag, rtag, start_index, block):
|
||||
while 1:
|
||||
i = block.find(rtag, start_index)
|
||||
if i == -1:
|
||||
return -1
|
||||
j = block.find(ltag, start_index)
|
||||
# if no ltag, or rtag found before another ltag, return index
|
||||
if (j > i or j == -1):
|
||||
return i + len(rtag)
|
||||
# another ltag found before rtag, use end of ltag as starting
|
||||
# point and search again
|
||||
j = block.find('>', j)
|
||||
start_index = self._recursive_tagfind(ltag, rtag, j + 1, block)
|
||||
if start_index == -1:
|
||||
# HTML potentially malformed- ltag has no corresponding
|
||||
# rtag
|
||||
return -1
|
||||
|
||||
def _get_right_tag(self, left_tag, left_index, block):
|
||||
for p in self.right_tag_patterns:
|
||||
tag = p % left_tag
|
||||
i = self._recursive_tagfind(
|
||||
"<%s" % left_tag, tag, left_index, block
|
||||
)
|
||||
if i > 2:
|
||||
return tag.lstrip("<").rstrip(">"), i
|
||||
return block.rstrip()[-left_index:-1].lower(), len(block)
|
||||
|
||||
def _equal_tags(self, left_tag, right_tag):
|
||||
if left_tag[0] in ['?', '@', '%']: # handle PHP, etc.
|
||||
return True
|
||||
if ("/" + left_tag) == right_tag:
|
||||
return True
|
||||
if (right_tag == "--" and left_tag == "--"):
|
||||
return True
|
||||
elif left_tag == right_tag[1:] and right_tag[0] == "/":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _is_oneliner(self, tag):
|
||||
return (tag in ['hr', 'hr/'])
|
||||
|
||||
def _stringindex_to_listindex(self, stringindex, items):
|
||||
"""
|
||||
Same effect as concatenating the strings in items,
|
||||
finding the character to which stringindex refers in that string,
|
||||
and returning the index of the item in which that character resides.
|
||||
"""
|
||||
items.append('dummy')
|
||||
i, count = 0, 0
|
||||
while count <= stringindex:
|
||||
count += len(items[i])
|
||||
i += 1
|
||||
return i - 1
|
||||
|
||||
def _nested_markdown_in_html(self, items):
|
||||
"""Find and process html child elements of the given element block."""
|
||||
for i, item in enumerate(items):
|
||||
if self.left_tag_re.match(item):
|
||||
left_tag, left_index, attrs = \
|
||||
self._get_left_tag(''.join(items[i:]))
|
||||
right_tag, data_index = self._get_right_tag(
|
||||
left_tag, left_index, ''.join(items[i:]))
|
||||
right_listindex = \
|
||||
self._stringindex_to_listindex(data_index, items[i:]) + i
|
||||
if 'markdown' in attrs.keys():
|
||||
items[i] = items[i][left_index:] # remove opening tag
|
||||
placeholder = self.md.htmlStash.store_tag(
|
||||
left_tag, attrs, i + 1, right_listindex + 1)
|
||||
items.insert(i, placeholder)
|
||||
if len(items) - right_listindex <= 1: # last nest, no tail
|
||||
right_listindex -= 1
|
||||
items[right_listindex] = items[right_listindex][
|
||||
:-len(right_tag) - 2] # remove closing tag
|
||||
else: # raw html
|
||||
if len(items) - right_listindex <= 1: # last element
|
||||
right_listindex -= 1
|
||||
if right_listindex <= i:
|
||||
right_listindex = i + 1
|
||||
placeholder = self.md.htmlStash.store('\n\n'.join(
|
||||
items[i:right_listindex]))
|
||||
del items[i:right_listindex]
|
||||
items.insert(i, placeholder)
|
||||
return items
|
||||
|
||||
def run(self, lines):
|
||||
text = "\n".join(lines)
|
||||
new_blocks = []
|
||||
text = text.rsplit("\n\n")
|
||||
items = []
|
||||
left_tag = ''
|
||||
right_tag = ''
|
||||
in_tag = False # flag
|
||||
|
||||
while text:
|
||||
block = text[0]
|
||||
if block.startswith("\n"):
|
||||
block = block[1:]
|
||||
text = text[1:]
|
||||
|
||||
if block.startswith("\n"):
|
||||
block = block[1:]
|
||||
|
||||
if not in_tag:
|
||||
if block.startswith("<") and len(block.strip()) > 1:
|
||||
|
||||
if block[1:4] == "!--":
|
||||
# is a comment block
|
||||
left_tag, left_index, attrs = "--", 2, {}
|
||||
else:
|
||||
left_tag, left_index, attrs = self._get_left_tag(block)
|
||||
right_tag, data_index = self._get_right_tag(left_tag,
|
||||
left_index,
|
||||
block)
|
||||
# keep checking conditions below and maybe just append
|
||||
|
||||
if data_index < len(block) and (self.md.is_block_level(left_tag) or left_tag == '--'):
|
||||
text.insert(0, block[data_index:])
|
||||
block = block[:data_index]
|
||||
|
||||
if not (self.md.is_block_level(left_tag) or block[1] in ["!", "?", "@", "%"]):
|
||||
new_blocks.append(block)
|
||||
continue
|
||||
|
||||
if self._is_oneliner(left_tag):
|
||||
new_blocks.append(block.strip())
|
||||
continue
|
||||
|
||||
if block.rstrip().endswith(">") \
|
||||
and self._equal_tags(left_tag, right_tag):
|
||||
if self.markdown_in_raw and 'markdown' in attrs.keys():
|
||||
block = block[left_index:-len(right_tag) - 2]
|
||||
new_blocks.append(self.md.htmlStash.
|
||||
store_tag(left_tag, attrs, 0, 2))
|
||||
new_blocks.extend([block])
|
||||
else:
|
||||
new_blocks.append(
|
||||
self.md.htmlStash.store(block.strip()))
|
||||
continue
|
||||
else:
|
||||
# if is block level tag and is not complete
|
||||
if (not self._equal_tags(left_tag, right_tag)) and \
|
||||
(self.md.is_block_level(left_tag) or left_tag == "--"):
|
||||
items.append(block.strip())
|
||||
in_tag = True
|
||||
else:
|
||||
new_blocks.append(
|
||||
self.md.htmlStash.store(block.strip())
|
||||
)
|
||||
continue
|
||||
|
||||
else:
|
||||
new_blocks.append(block)
|
||||
|
||||
else:
|
||||
items.append(block)
|
||||
|
||||
# Need to evaluate all items so we can calculate relative to the left index.
|
||||
right_tag, data_index = self._get_right_tag(left_tag, left_index, ''.join(items))
|
||||
# Adjust data_index: relative to items -> relative to last block
|
||||
prev_block_length = 0
|
||||
for item in items[:-1]:
|
||||
prev_block_length += len(item)
|
||||
data_index -= prev_block_length
|
||||
|
||||
if self._equal_tags(left_tag, right_tag):
|
||||
# if find closing tag
|
||||
|
||||
if data_index < len(block):
|
||||
# we have more text after right_tag
|
||||
items[-1] = block[:data_index]
|
||||
text.insert(0, block[data_index:])
|
||||
|
||||
in_tag = False
|
||||
if self.markdown_in_raw and 'markdown' in attrs.keys():
|
||||
items[0] = items[0][left_index:]
|
||||
items[-1] = items[-1][:-len(right_tag) - 2]
|
||||
if items[len(items) - 1]: # not a newline/empty string
|
||||
right_index = len(items) + 3
|
||||
else:
|
||||
right_index = len(items) + 2
|
||||
new_blocks.append(self.md.htmlStash.store_tag(
|
||||
left_tag, attrs, 0, right_index))
|
||||
placeholderslen = len(self.md.htmlStash.tag_data)
|
||||
new_blocks.extend(
|
||||
self._nested_markdown_in_html(items))
|
||||
nests = len(self.md.htmlStash.tag_data) - \
|
||||
placeholderslen
|
||||
self.md.htmlStash.tag_data[-1 - nests][
|
||||
'right_index'] += nests - 2
|
||||
else:
|
||||
new_blocks.append(
|
||||
self.md.htmlStash.store('\n\n'.join(items)))
|
||||
items = []
|
||||
|
||||
if items:
|
||||
if self.markdown_in_raw and 'markdown' in attrs.keys():
|
||||
items[0] = items[0][left_index:]
|
||||
items[-1] = items[-1][:-len(right_tag) - 2]
|
||||
if items[len(items) - 1]: # not a newline/empty string
|
||||
right_index = len(items) + 3
|
||||
else:
|
||||
right_index = len(items) + 2
|
||||
new_blocks.append(
|
||||
self.md.htmlStash.store_tag(
|
||||
left_tag, attrs, 0, right_index))
|
||||
placeholderslen = len(self.md.htmlStash.tag_data)
|
||||
new_blocks.extend(self._nested_markdown_in_html(items))
|
||||
nests = len(self.md.htmlStash.tag_data) - placeholderslen
|
||||
self.md.htmlStash.tag_data[-1 - nests][
|
||||
'right_index'] += nests - 2
|
||||
else:
|
||||
new_blocks.append(
|
||||
self.md.htmlStash.store('\n\n'.join(items)))
|
||||
new_blocks.append('\n')
|
||||
|
||||
new_text = "\n\n".join(new_blocks)
|
||||
return new_text.split("\n")
|
||||
|
||||
|
||||
class ReferencePreprocessor(Preprocessor):
|
||||
""" Remove reference definitions from text and store for later use. """
|
||||
|
||||
TITLE = r'[ ]*(\"(.*)\"|\'(.*)\'|\((.*)\))[ ]*'
|
||||
RE = re.compile(
|
||||
r'^[ ]{0,3}\[([^\]]*)\]:\s*([^ ]*)[ ]*(%s)?$' % TITLE, re.DOTALL
|
||||
)
|
||||
TITLE_RE = re.compile(r'^%s$' % TITLE)
|
||||
|
||||
def run(self, lines):
|
||||
new_text = []
|
||||
while lines:
|
||||
line = lines.pop(0)
|
||||
m = self.RE.match(line)
|
||||
if m:
|
||||
id = m.group(1).strip().lower()
|
||||
link = m.group(2).lstrip('<').rstrip('>')
|
||||
t = m.group(5) or m.group(6) or m.group(7)
|
||||
if not t:
|
||||
# Check next line for title
|
||||
tm = self.TITLE_RE.match(lines[0])
|
||||
if tm:
|
||||
lines.pop(0)
|
||||
t = tm.group(2) or tm.group(3) or tm.group(4)
|
||||
self.md.references[id] = (link, t)
|
||||
# Preserve the line to prevent raw HTML indexing issue.
|
||||
# https://github.com/Python-Markdown/markdown/issues/584
|
||||
new_text.append('')
|
||||
else:
|
||||
new_text.append(line)
|
||||
|
||||
return new_text # + "\n"
|
||||
source = '\n'.join(lines)
|
||||
parser = HTMLExtractor(self.md)
|
||||
parser.feed(source)
|
||||
parser.close()
|
||||
return ''.join(parser.cleandoc).split('\n')
|
||||
|
||||
@@ -20,9 +20,10 @@ License: BSD (see LICENSE.md for details).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
import textwrap
|
||||
from . import markdown
|
||||
from . import markdown, util
|
||||
|
||||
try:
|
||||
import tidylib
|
||||
@@ -73,6 +74,32 @@ class TestCase(unittest.TestCase):
|
||||
return textwrap.dedent(text).strip()
|
||||
|
||||
|
||||
class recursionlimit:
|
||||
"""
|
||||
A context manager which temporarily modifies the Python recursion limit.
|
||||
|
||||
The testing framework, coverage, etc. may add an arbitrary number of levels to the depth. To maintain consistency
|
||||
in the tests, the current stack depth is determined when called, then added to the provided limit.
|
||||
|
||||
Example usage:
|
||||
|
||||
with recursionlimit(20):
|
||||
# test code here
|
||||
|
||||
See https://stackoverflow.com/a/50120316/866026
|
||||
"""
|
||||
|
||||
def __init__(self, limit):
|
||||
self.limit = util._get_stack_depth() + limit
|
||||
self.old_limit = sys.getrecursionlimit()
|
||||
|
||||
def __enter__(self):
|
||||
sys.setrecursionlimit(self.limit)
|
||||
|
||||
def __exit__(self, type, value, tb):
|
||||
sys.setrecursionlimit(self.old_limit)
|
||||
|
||||
|
||||
#########################
|
||||
# Legacy Test Framework #
|
||||
#########################
|
||||
@@ -113,8 +140,11 @@ class LegacyTestMeta(type):
|
||||
expected = f.read().replace("\r\n", "\n")
|
||||
output = markdown(input, **kwargs)
|
||||
if tidylib and normalize:
|
||||
try:
|
||||
expected = _normalize_whitespace(expected)
|
||||
output = _normalize_whitespace(output)
|
||||
except OSError:
|
||||
self.skipTest("Tidylib's c library not available.")
|
||||
elif normalize:
|
||||
self.skipTest('Tidylib not available.')
|
||||
self.assertMultiLineEqual(output, expected)
|
||||
@@ -167,7 +197,7 @@ class LegacyTestCase(unittest.TestCase, metaclass=LegacyTestMeta):
|
||||
arguments for all test files in the directory.
|
||||
|
||||
In addition, properties can be defined for each individual set of test files within
|
||||
the directory. The property should be given the name of the file wihtout the file
|
||||
the directory. The property should be given the name of the file without the file
|
||||
extension. Any spaces and dashes in the filename should be replaced with
|
||||
underscores. The value of the property should be a `Kwargs` instance which
|
||||
contains the keyword arguments that should be passed to `Markdown` for that
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user