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();
|
makeImagesClickeable();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<#
|
<#
|
||||||
.Synopsis
|
.Synopsis
|
||||||
Activate a Python virtual environment for the current Powershell session.
|
Activate a Python virtual environment for the current PowerShell session.
|
||||||
|
|
||||||
.Description
|
.Description
|
||||||
Pushes the python executable for a virtual environment to the front of the
|
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
|
and prefixes the current prompt with the specified string (surrounded in
|
||||||
parentheses) while the virtual environment is active.
|
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(
|
Param(
|
||||||
@@ -137,7 +146,7 @@ function Get-PyVenvConfig(
|
|||||||
$val = $keyval[1]
|
$val = $keyval[1]
|
||||||
|
|
||||||
# Remove extraneous quotations around a string value.
|
# 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)
|
$val = $val.Substring(1, $val.Length - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,7 +174,8 @@ Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
|||||||
# VenvExecDir if specified on the command line.
|
# VenvExecDir if specified on the command line.
|
||||||
if ($VenvDir) {
|
if ($VenvDir) {
|
||||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
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."
|
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
||||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
||||||
Write-Verbose "VenvDir=$VenvDir"
|
Write-Verbose "VenvDir=$VenvDir"
|
||||||
@@ -179,7 +189,8 @@ $pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
|||||||
# just use the name of the virtual environment folder.
|
# just use the name of the virtual environment folder.
|
||||||
if ($Prompt) {
|
if ($Prompt) {
|
||||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
||||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
||||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ deactivate () {
|
|||||||
# unset irrelevant variables
|
# unset irrelevant variables
|
||||||
deactivate nondestructive
|
deactivate nondestructive
|
||||||
|
|
||||||
VIRTUAL_ENV="/home/ryuuji/src/generator/venv"
|
VIRTUAL_ENV="/home/ryuuji/src/danielcortes.xyz/venv"
|
||||||
export VIRTUAL_ENV
|
export VIRTUAL_ENV
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH"
|
_OLD_VIRTUAL_PATH="$PATH"
|
||||||
@@ -59,7 +59,7 @@ if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
|||||||
else
|
else
|
||||||
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
if [ "`basename \"$VIRTUAL_ENV\"`" = "__" ] ; then
|
||||||
# special case for Aspen magic directories
|
# special case for Aspen magic directories
|
||||||
# see http://www.zetadev.com/software/aspen/
|
# see https://aspen.io/
|
||||||
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
PS1="[`basename \`dirname \"$VIRTUAL_ENV\"\``] $PS1"
|
||||||
else
|
else
|
||||||
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
PS1="(`basename \"$VIRTUAL_ENV\"`)$PS1"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PA
|
|||||||
# Unset irrelevant variables.
|
# Unset irrelevant variables.
|
||||||
deactivate nondestructive
|
deactivate nondestructive
|
||||||
|
|
||||||
setenv VIRTUAL_ENV "/home/ryuuji/src/generator/venv"
|
setenv VIRTUAL_ENV "/home/ryuuji/src/danielcortes.xyz/venv"
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH="$PATH"
|
set _OLD_VIRTUAL_PATH="$PATH"
|
||||||
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
setenv PATH "$VIRTUAL_ENV/bin:$PATH"
|
||||||
@@ -22,7 +22,7 @@ if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then
|
|||||||
else
|
else
|
||||||
if (`basename "VIRTUAL_ENV"` == "__") then
|
if (`basename "VIRTUAL_ENV"` == "__") then
|
||||||
# special case for Aspen magic directories
|
# special case for Aspen magic directories
|
||||||
# see http://www.zetadev.com/software/aspen/
|
# see https://aspen.io/
|
||||||
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
set env_name = `basename \`dirname "$VIRTUAL_ENV"\``
|
||||||
else
|
else
|
||||||
set env_name = `basename "$VIRTUAL_ENV"`
|
set env_name = `basename "$VIRTUAL_ENV"`
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ end
|
|||||||
# unset irrelevant variables
|
# unset irrelevant variables
|
||||||
deactivate nondestructive
|
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 _OLD_VIRTUAL_PATH $PATH
|
||||||
set -gx PATH "$VIRTUAL_ENV/bin" $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")
|
set -l _checkbase (basename "$VIRTUAL_ENV")
|
||||||
if test $_checkbase = "__"
|
if test $_checkbase = "__"
|
||||||
# special case for Aspen magic directories
|
# 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)
|
printf "%s[%s]%s " (set_color -b blue white) (basename (dirname "$VIRTUAL_ENV")) (set_color normal)
|
||||||
else
|
else
|
||||||
printf "%s(%s)%s" (set_color -b blue white) (basename "$VIRTUAL_ENV") (set_color normal)
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from setuptools.command.easy_install import main
|
from setuptools.command.easy_install import main
|
||||||
|
|
||||||
if __name__ == '__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())
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from setuptools.command.easy_install import main
|
from setuptools.command.easy_install import main
|
||||||
|
|
||||||
if __name__ == '__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())
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from markdown.__main__ import run
|
from markdown.__main__ import run
|
||||||
|
|
||||||
if __name__ == '__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(run())
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
from pip._internal import main
|
|
||||||
|
|
||||||
if __name__ == '__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())
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
from pip._internal import main
|
|
||||||
|
|
||||||
if __name__ == '__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())
|
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 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
from pip._internal.cli.main import main
|
||||||
from pip._internal import main
|
|
||||||
|
|
||||||
if __name__ == '__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())
|
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/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/METADATA,sha256=5ZHRZoIRAMHsJPnqhlJ622_dRPsYePYJ-9EH4-Ry7yI,3535
|
||||||
Jinja2-2.11.2.dist-info/RECORD,,
|
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/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/entry_points.txt,sha256=Qy_DkVo6Xj_zzOtmErrATe8lHZhOqdjpt3e4JJAGyi8,61
|
||||||
Jinja2-2.11.2.dist-info/top_level.txt,sha256=PkeVWtLb3-CqjWi1fO29OCbj55EhX_chhKrCdrVe_zs,7
|
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).
|
License: BSD (see LICENSE.md for details).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
|
||||||
import packaging.version
|
|
||||||
except ImportError:
|
|
||||||
from pkg_resources.extern import packaging
|
|
||||||
|
|
||||||
# __version_info__ format:
|
# __version_info__ format:
|
||||||
# (major, minor, patch, dev/alpha/beta/rc/final, #)
|
# (major, minor, patch, dev/alpha/beta/rc/final, #)
|
||||||
# (1, 1, 2, 'dev', 0) => "1.1.2.dev0"
|
# (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, 'beta', 2) => "1.2b2"
|
||||||
# (1, 2, 0, 'rc', 4) => "1.2rc4"
|
# (1, 2, 0, 'rc', 4) => "1.2rc4"
|
||||||
# (1, 2, 0, 'final', 0) => "1.2"
|
# (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. "
|
" Returns a PEP 440-compliant version number from version_info. "
|
||||||
assert len(__version_info__) == 5
|
assert len(version_info) == 5
|
||||||
assert __version_info__[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
|
assert version_info[3] in ('dev', 'alpha', 'beta', 'rc', 'final')
|
||||||
|
|
||||||
parts = 2 if __version_info__[2] == 0 else 3
|
parts = 2 if version_info[2] == 0 else 3
|
||||||
v = '.'.join(map(str, __version_info__[:parts]))
|
v = '.'.join(map(str, version_info[:parts]))
|
||||||
|
|
||||||
if __version_info__[3] == 'dev':
|
if version_info[3] == 'dev':
|
||||||
v += '.dev' + str(__version_info__[4])
|
v += '.dev' + str(version_info[4])
|
||||||
elif __version_info__[3] != 'final':
|
elif version_info[3] != 'final':
|
||||||
mapping = {'alpha': 'a', 'beta': 'b', 'rc': 'rc'}
|
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 v
|
||||||
return str(packaging.version.Version(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(OListProcessor(parser), 'olist', 40)
|
||||||
parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
|
parser.blockprocessors.register(UListProcessor(parser), 'ulist', 30)
|
||||||
parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
|
parser.blockprocessors.register(BlockQuoteProcessor(parser), 'quote', 20)
|
||||||
|
parser.blockprocessors.register(ReferenceProcessor(parser), 'reference', 15)
|
||||||
parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10)
|
parser.blockprocessors.register(ParagraphProcessor(parser), 'paragraph', 10)
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@@ -276,7 +277,7 @@ class BlockQuoteProcessor(BlockProcessor):
|
|||||||
RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')
|
RE = re.compile(r'(^|\n)[ ]{0,3}>[ ]?(.*)')
|
||||||
|
|
||||||
def test(self, parent, block):
|
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):
|
def run(self, parent, blocks):
|
||||||
block = blocks.pop(0)
|
block = blocks.pop(0)
|
||||||
@@ -495,16 +496,15 @@ class SetextHeaderProcessor(BlockProcessor):
|
|||||||
class HRProcessor(BlockProcessor):
|
class HRProcessor(BlockProcessor):
|
||||||
""" Process Horizontal Rules. """
|
""" 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.
|
# Detect hr on any line of a block.
|
||||||
SEARCH_RE = re.compile(RE, re.MULTILINE)
|
SEARCH_RE = re.compile(RE, re.MULTILINE)
|
||||||
|
|
||||||
def test(self, parent, block):
|
def test(self, parent, block):
|
||||||
m = self.SEARCH_RE.search(block)
|
m = self.SEARCH_RE.search(block)
|
||||||
# No atomic grouping in python so we simulate it here for performance.
|
if m:
|
||||||
# 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'):
|
|
||||||
# Save match object on class instance so we can use it later.
|
# Save match object on class instance so we can use it later.
|
||||||
self.match = m
|
self.match = m
|
||||||
return True
|
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):
|
class ParagraphProcessor(BlockProcessor):
|
||||||
""" Process Paragraph blocks. """
|
""" Process Paragraph blocks. """
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import codecs
|
|||||||
import sys
|
import sys
|
||||||
import logging
|
import logging
|
||||||
import importlib
|
import importlib
|
||||||
import pkg_resources
|
|
||||||
from . import util
|
from . import util
|
||||||
from .preprocessors import build_preprocessors
|
from .preprocessors import build_preprocessors
|
||||||
from .blockprocessors import build_block_parser
|
from .blockprocessors import build_block_parser
|
||||||
@@ -78,11 +77,12 @@ class Markdown:
|
|||||||
# See https://w3c.github.io/html/grouping-content.html#the-p-element
|
# See https://w3c.github.io/html/grouping-content.html#the-p-element
|
||||||
'address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl',
|
'address', 'article', 'aside', 'blockquote', 'details', 'div', 'dl',
|
||||||
'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
|
'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3',
|
||||||
'h4', 'h5', 'h6', 'header', 'hr', 'main', 'menu', 'nav', 'ol', 'p', 'pre',
|
'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'main', 'menu', 'nav', 'ol',
|
||||||
'section', 'table', 'ul',
|
'p', 'pre', 'section', 'table', 'ul',
|
||||||
# Other elements which Markdown should not be mucking up the contents of.
|
# Other elements which Markdown should not be mucking up the contents of.
|
||||||
'canvas', 'dd', 'dt', 'group', 'iframe', 'li', 'math', 'noscript', 'output',
|
'canvas', 'colgroup', 'dd', 'body', 'dt', 'group', 'iframe', 'li', 'legend',
|
||||||
'progress', 'script', 'style', 'tbody', 'td', 'th', 'thead', 'tr', 'video'
|
'math', 'map', 'noscript', 'output', 'object', 'option', 'progress', 'script',
|
||||||
|
'style', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'tr', 'video'
|
||||||
]
|
]
|
||||||
|
|
||||||
self.registeredExtensions = []
|
self.registeredExtensions = []
|
||||||
@@ -141,9 +141,8 @@ class Markdown:
|
|||||||
Build extension from a string name, then return an instance.
|
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
|
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
|
`markdown.extensions` group which points to a subclass of the `markdown.extensions.Extension` class.
|
||||||
multiple distributions have registered the same name, the first one found by `pkg_resources.iter_entry_points`
|
If multiple distributions have registered the same name, the first one found is returned.
|
||||||
is returned.
|
|
||||||
|
|
||||||
If no entry point is found, assume dot notation (`path.to.module:ClassName`). Load the specified class and
|
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
|
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)
|
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:
|
if entry_points:
|
||||||
ext = entry_points[0].load()
|
ext = entry_points[0].load()
|
||||||
return ext(**configs)
|
return ext(**configs)
|
||||||
@@ -278,14 +277,14 @@ class Markdown:
|
|||||||
'<%s>' % self.doc_tag) + len(self.doc_tag) + 2
|
'<%s>' % self.doc_tag) + len(self.doc_tag) + 2
|
||||||
end = output.rindex('</%s>' % self.doc_tag)
|
end = output.rindex('</%s>' % self.doc_tag)
|
||||||
output = output[start:end].strip()
|
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):
|
if output.strip().endswith('<%s />' % self.doc_tag):
|
||||||
# We have an empty document
|
# We have an empty document
|
||||||
output = ''
|
output = ''
|
||||||
else:
|
else:
|
||||||
# We have a serious problem
|
# We have a serious problem
|
||||||
raise ValueError('Markdown failed to strip top-level '
|
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
|
# Run the text post-processors
|
||||||
for pp in self.postprocessors:
|
for pp in self.postprocessors:
|
||||||
|
|||||||
@@ -75,15 +75,18 @@ class Extension:
|
|||||||
md = args[0]
|
md = args[0]
|
||||||
try:
|
try:
|
||||||
self.extendMarkdown(md)
|
self.extendMarkdown(md)
|
||||||
except TypeError:
|
except TypeError as e:
|
||||||
# Must be a 2.x extension. Pass in a dumby md_globals.
|
if "missing 1 required positional argument" in str(e):
|
||||||
self.extendMarkdown(md, {})
|
# Must be a 2.x extension. Pass in a dumby md_globals.
|
||||||
warnings.warn(
|
self.extendMarkdown(md, {})
|
||||||
"The 'md_globals' parameter of '{}.{}.extendMarkdown' is "
|
warnings.warn(
|
||||||
"deprecated.".format(self.__class__.__module__, self.__class__.__name__),
|
"The 'md_globals' parameter of '{}.{}.extendMarkdown' is "
|
||||||
category=DeprecationWarning,
|
"deprecated.".format(self.__class__.__module__, self.__class__.__name__),
|
||||||
stacklevel=2
|
category=DeprecationWarning,
|
||||||
)
|
stacklevel=2
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
def extendMarkdown(self, md):
|
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 . import Extension
|
||||||
from ..preprocessors import Preprocessor
|
from ..blockprocessors import BlockProcessor
|
||||||
from ..inlinepatterns import InlineProcessor
|
from ..inlinepatterns import InlineProcessor
|
||||||
from ..util import AtomicString
|
from ..util import AtomicString
|
||||||
import re
|
import re
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
# Global Vars
|
|
||||||
ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)')
|
|
||||||
|
|
||||||
|
|
||||||
class AbbrExtension(Extension):
|
class AbbrExtension(Extension):
|
||||||
""" Abbreviation Extension for Python-Markdown. """
|
""" Abbreviation Extension for Python-Markdown. """
|
||||||
|
|
||||||
def extendMarkdown(self, md):
|
def extendMarkdown(self, md):
|
||||||
""" Insert AbbrPreprocessor before ReferencePreprocessor. """
|
""" 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. """
|
""" 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.
|
Find and remove all Abbreviation references from the text.
|
||||||
Each reference is set as a new AbbrPattern in the markdown instance.
|
Each reference is set as a new AbbrPattern in the markdown instance.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
new_text = []
|
block = blocks.pop(0)
|
||||||
for line in lines:
|
m = self.RE.search(block)
|
||||||
m = ABBR_REF_RE.match(line)
|
if m:
|
||||||
if m:
|
abbr = m.group('abbr').strip()
|
||||||
abbr = m.group('abbr').strip()
|
title = m.group('title').strip()
|
||||||
title = m.group('title').strip()
|
self.parser.md.inlinePatterns.register(
|
||||||
self.md.inlinePatterns.register(
|
AbbrInlineProcessor(self._generate_pattern(abbr), title), 'abbr-%s' % abbr, 2
|
||||||
AbbrInlineProcessor(self._generate_pattern(abbr), title), 'abbr-%s' % abbr, 2
|
)
|
||||||
)
|
if block[m.end():].strip():
|
||||||
# Preserve the line to prevent raw HTML indexing issue.
|
# Add any content after match back to blocks as separate block
|
||||||
# https://github.com/Python-Markdown/markdown/issues/584
|
blocks.insert(0, block[m.end():].lstrip('\n'))
|
||||||
new_text.append('')
|
if block[:m.start()].strip():
|
||||||
else:
|
# Add any content before match back to blocks as separate block
|
||||||
new_text.append(line)
|
blocks.insert(0, block[:m.start()].rstrip('\n'))
|
||||||
return new_text
|
return True
|
||||||
|
# No match. Restore block.
|
||||||
|
blocks.insert(0, block)
|
||||||
|
return False
|
||||||
|
|
||||||
def _generate_pattern(self, text):
|
def _generate_pattern(self, text):
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -40,19 +40,82 @@ class AdmonitionProcessor(BlockProcessor):
|
|||||||
RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)')
|
RE = re.compile(r'(?:^|\n)!!! ?([\w\-]+(?: +[\w\-]+)*)(?: +"(.*?)")? *(?:\n|$)')
|
||||||
RE_SPACES = re.compile(' +')
|
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)
|
sibling = self.lastChild(parent)
|
||||||
return self.RE.search(block) or \
|
|
||||||
(block.startswith(' ' * self.tab_length) and sibling is not None and
|
if sibling is None or sibling.get('class', '').find(self.CLASSNAME) == -1:
|
||||||
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):
|
def run(self, parent, blocks):
|
||||||
sibling = self.lastChild(parent)
|
|
||||||
block = blocks.pop(0)
|
block = blocks.pop(0)
|
||||||
m = self.RE.search(block)
|
m = self.RE.search(block)
|
||||||
|
|
||||||
if m:
|
if m:
|
||||||
block = block[m.end():] # removes the first line
|
block = block[m.end():] # removes the first line
|
||||||
|
else:
|
||||||
|
sibling, block = self.get_sibling(parent, block)
|
||||||
|
|
||||||
block, theRest = self.detab(block)
|
block, theRest = self.detab(block)
|
||||||
|
|
||||||
@@ -65,6 +128,13 @@ class AdmonitionProcessor(BlockProcessor):
|
|||||||
p.text = title
|
p.text = title
|
||||||
p.set('class', self.CLASSNAME_TITLE)
|
p.set('class', self.CLASSNAME_TITLE)
|
||||||
else:
|
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
|
div = sibling
|
||||||
|
|
||||||
self.parser.parseChunk(div, block)
|
self.parser.parseChunk(div, block)
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ def isheader(elem):
|
|||||||
|
|
||||||
class AttrListTreeprocessor(Treeprocessor):
|
class AttrListTreeprocessor(Treeprocessor):
|
||||||
|
|
||||||
BASE_RE = r'\{\:?([^\}\n]*)\}'
|
BASE_RE = r'\{\:?[ ]*([^\}\n ][^\}\n]*)[ ]*\}'
|
||||||
HEADER_RE = re.compile(r'[ ]+%s[ ]*$' % BASE_RE)
|
HEADER_RE = re.compile(r'[ ]+{}[ ]*$'.format(BASE_RE))
|
||||||
BLOCK_RE = re.compile(r'\n[ ]*%s[ ]*$' % BASE_RE)
|
BLOCK_RE = re.compile(r'\n[ ]*{}[ ]*$'.format(BASE_RE))
|
||||||
INLINE_RE = re.compile(r'^%s' % 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'
|
NAME_RE = re.compile(r'[^A-Z_a-z\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u02ff'
|
||||||
r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d'
|
r'\u0370-\u037d\u037f-\u1fff\u200c-\u200d'
|
||||||
r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff'
|
r'\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff'
|
||||||
@@ -79,8 +79,8 @@ class AttrListTreeprocessor(Treeprocessor):
|
|||||||
if self.md.is_block_level(elem.tag):
|
if self.md.is_block_level(elem.tag):
|
||||||
# Block level: check for attrs on last line of text
|
# Block level: check for attrs on last line of text
|
||||||
RE = self.BLOCK_RE
|
RE = self.BLOCK_RE
|
||||||
if isheader(elem) or elem.tag == 'dt':
|
if isheader(elem) or elem.tag in ['dt', 'td', 'th']:
|
||||||
# header or def-term: check for attrs at end of line
|
# header, def-term, or table cell: check for attrs at end of element
|
||||||
RE = self.HEADER_RE
|
RE = self.HEADER_RE
|
||||||
if len(elem) and elem.tag == 'li':
|
if len(elem) and elem.tag == 'li':
|
||||||
# special case list items. children may include a ul or ol.
|
# special case list items. children may include a ul or ol.
|
||||||
@@ -120,8 +120,6 @@ class AttrListTreeprocessor(Treeprocessor):
|
|||||||
elif elem.text:
|
elif elem.text:
|
||||||
# no children. Get from text.
|
# no children. Get from text.
|
||||||
m = RE.search(elem.text)
|
m = RE.search(elem.text)
|
||||||
if not m and elem.tag == 'td':
|
|
||||||
m = re.search(self.BASE_RE, elem.text)
|
|
||||||
if m:
|
if m:
|
||||||
self.assign_attrs(elem, m.group(1))
|
self.assign_attrs(elem, m.group(1))
|
||||||
elem.text = elem.text[:m.start()]
|
elem.text = elem.text[:m.start()]
|
||||||
@@ -161,6 +159,7 @@ class AttrListTreeprocessor(Treeprocessor):
|
|||||||
class AttrListExtension(Extension):
|
class AttrListExtension(Extension):
|
||||||
def extendMarkdown(self, md):
|
def extendMarkdown(self, md):
|
||||||
md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8)
|
md.treeprocessors.register(AttrListTreeprocessor(md), 'attr_list', 8)
|
||||||
|
md.registerExtension(self)
|
||||||
|
|
||||||
|
|
||||||
def makeExtension(**kwargs): # pragma: no cover
|
def makeExtension(**kwargs): # pragma: no cover
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
|||||||
|
|
||||||
from . import Extension
|
from . import Extension
|
||||||
from ..treeprocessors import Treeprocessor
|
from ..treeprocessors import Treeprocessor
|
||||||
|
from ..util import parseBoolValue
|
||||||
|
|
||||||
try:
|
try: # pragma: no cover
|
||||||
from pygments import highlight
|
from pygments import highlight
|
||||||
from pygments.lexers import get_lexer_by_name, guess_lexer
|
from pygments.lexers import get_lexer_by_name, guess_lexer
|
||||||
from pygments.formatters import get_formatter_by_name
|
from pygments.formatters import get_formatter_by_name
|
||||||
pygments = True
|
pygments = True
|
||||||
except ImportError:
|
except ImportError: # pragma: no cover
|
||||||
pygments = False
|
pygments = False
|
||||||
|
|
||||||
|
|
||||||
@@ -38,52 +39,78 @@ def parse_hl_lines(expr):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
return list(map(int, expr.split()))
|
return list(map(int, expr.split()))
|
||||||
except ValueError:
|
except ValueError: # pragma: no cover
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
# ------------------ The Main CodeHilite Class ----------------------
|
# ------------------ The Main CodeHilite Class ----------------------
|
||||||
class CodeHilite:
|
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:
|
Usage:
|
||||||
>>> code = CodeHilite(src = 'some text')
|
code = CodeHilite(src=some_code, lang='python')
|
||||||
>>> html = code.hilite()
|
html = code.hilite()
|
||||||
|
|
||||||
|
Arguments:
|
||||||
* src: Source string or any object with a .readline attribute.
|
* src: Source string or any object with a .readline attribute.
|
||||||
|
|
||||||
* linenums: (Boolean) Set line numbering to 'on' (True),
|
* lang: String name of Pygments lexer to use for highlighting. Default: `None`.
|
||||||
'off' (False) or 'auto'(None). Set to 'auto' by default.
|
|
||||||
|
|
||||||
* guess_lang: (Boolean) Turn language auto-detection
|
* guess_lang: Auto-detect which lexer to use. Ignored if `lang` is set to a valid
|
||||||
'on' or 'off' (on by default).
|
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:
|
* css_class: An alias to Pygments `cssclass` formatter option. Default: 'codehilite'.
|
||||||
>>> code = CodeHilite()
|
|
||||||
>>> code.src = 'some text' # String or anything with a .readline attr.
|
* lang_prefix: Prefix prepended to the language when `use_pygments` is `False`.
|
||||||
>>> code.linenos = True # Turns line numbering on or of.
|
Default: "language-".
|
||||||
>>> html = code.hilite()
|
|
||||||
|
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,
|
def __init__(self, src, **options):
|
||||||
css_class="codehilite", lang=None, style='default',
|
|
||||||
noclasses=False, tab_length=4, hl_lines=None, use_pygments=True):
|
|
||||||
self.src = src
|
self.src = src
|
||||||
self.lang = lang
|
self.lang = options.pop('lang', None)
|
||||||
self.linenums = linenums
|
self.guess_lang = options.pop('guess_lang', True)
|
||||||
self.guess_lang = guess_lang
|
self.use_pygments = options.pop('use_pygments', True)
|
||||||
self.css_class = css_class
|
self.lang_prefix = options.pop('lang_prefix', 'language-')
|
||||||
self.style = style
|
|
||||||
self.noclasses = noclasses
|
if 'linenos' not in options:
|
||||||
self.tab_length = tab_length
|
options['linenos'] = options.pop('linenums', None)
|
||||||
self.hl_lines = hl_lines or []
|
if 'cssclass' not in options:
|
||||||
self.use_pygments = use_pygments
|
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):
|
def hilite(self):
|
||||||
"""
|
"""
|
||||||
@@ -103,22 +130,16 @@ class CodeHilite:
|
|||||||
|
|
||||||
if pygments and self.use_pygments:
|
if pygments and self.use_pygments:
|
||||||
try:
|
try:
|
||||||
lexer = get_lexer_by_name(self.lang)
|
lexer = get_lexer_by_name(self.lang, **self.options)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
try:
|
try:
|
||||||
if self.guess_lang:
|
if self.guess_lang:
|
||||||
lexer = guess_lexer(self.src)
|
lexer = guess_lexer(self.src, **self.options)
|
||||||
else:
|
else:
|
||||||
lexer = get_lexer_by_name('text')
|
lexer = get_lexer_by_name('text', **self.options)
|
||||||
except ValueError:
|
except ValueError: # pragma: no cover
|
||||||
lexer = get_lexer_by_name('text')
|
lexer = get_lexer_by_name('text', **self.options)
|
||||||
formatter = get_formatter_by_name('html',
|
formatter = get_formatter_by_name('html', **self.options)
|
||||||
linenos=self.linenums,
|
|
||||||
cssclass=self.css_class,
|
|
||||||
style=self.style,
|
|
||||||
noclasses=self.noclasses,
|
|
||||||
hl_lines=self.hl_lines,
|
|
||||||
wrapcode=True)
|
|
||||||
return highlight(self.src, lexer, formatter)
|
return highlight(self.src, lexer, formatter)
|
||||||
else:
|
else:
|
||||||
# just escape and build markup usable by JS highlighting libs
|
# just escape and build markup usable by JS highlighting libs
|
||||||
@@ -128,27 +149,30 @@ class CodeHilite:
|
|||||||
txt = txt.replace('"', '"')
|
txt = txt.replace('"', '"')
|
||||||
classes = []
|
classes = []
|
||||||
if self.lang:
|
if self.lang:
|
||||||
classes.append('language-%s' % self.lang)
|
classes.append('{}{}'.format(self.lang_prefix, self.lang))
|
||||||
if self.linenums:
|
if self.options['linenos']:
|
||||||
classes.append('linenums')
|
classes.append('linenums')
|
||||||
class_str = ''
|
class_str = ''
|
||||||
if classes:
|
if classes:
|
||||||
class_str = ' class="%s"' % ' '.join(classes)
|
class_str = ' class="{}"'.format(' '.join(classes))
|
||||||
return '<pre class="%s"><code%s>%s</code></pre>\n' % \
|
return '<pre class="{}"><code{}>{}\n</code></pre>\n'.format(
|
||||||
(self.css_class, class_str, txt)
|
self.options['cssclass'],
|
||||||
|
class_str,
|
||||||
|
txt
|
||||||
|
)
|
||||||
|
|
||||||
def _parseHeader(self):
|
def _parseHeader(self):
|
||||||
"""
|
"""
|
||||||
Determines language of a code block from shebang line and whether said
|
Determines language of a code block from shebang line and whether the
|
||||||
line should be removed or left in place. If the sheband line contains a
|
said line should be removed or left in place. If the sheband line
|
||||||
path (even a single /) then it is assumed to be a real shebang line and
|
contains a path (even a single /) then it is assumed to be a real
|
||||||
left alone. However, if no path is given (e.i.: #!python or :::python)
|
shebang line and left alone. However, if no path is given
|
||||||
then it is assumed to be a mock shebang for language identifitation of
|
(e.i.: #!python or :::python) then it is assumed to be a mock shebang
|
||||||
a code fragment and removed from the code block prior to processing for
|
for language identification of a code fragment and removed from the
|
||||||
code highlighting. When a mock shebang (e.i: #!python) is found, line
|
code block prior to processing for code highlighting. When a mock
|
||||||
numbering is turned on. When colons are found in place of a shebang
|
shebang (e.i: #!python) is found, line numbering is turned on. When
|
||||||
(e.i.: :::python), line numbering is left in the current state - off
|
colons are found in place of a shebang (e.i.: :::python), line
|
||||||
by default.
|
numbering is left in the current state - off by default.
|
||||||
|
|
||||||
Also parses optional list of highlight lines, like:
|
Also parses optional list of highlight lines, like:
|
||||||
|
|
||||||
@@ -176,16 +200,16 @@ class CodeHilite:
|
|||||||
# we have a match
|
# we have a match
|
||||||
try:
|
try:
|
||||||
self.lang = m.group('lang').lower()
|
self.lang = m.group('lang').lower()
|
||||||
except IndexError:
|
except IndexError: # pragma: no cover
|
||||||
self.lang = None
|
self.lang = None
|
||||||
if m.group('path'):
|
if m.group('path'):
|
||||||
# path exists - restore first line
|
# path exists - restore first line
|
||||||
lines.insert(0, fl)
|
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
|
# 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:
|
else:
|
||||||
# No match
|
# No match
|
||||||
lines.insert(0, fl)
|
lines.insert(0, fl)
|
||||||
@@ -201,9 +225,11 @@ class HiliteTreeprocessor(Treeprocessor):
|
|||||||
|
|
||||||
def code_unescape(self, text):
|
def code_unescape(self, text):
|
||||||
"""Unescape code."""
|
"""Unescape code."""
|
||||||
text = text.replace("&", "&")
|
|
||||||
text = text.replace("<", "<")
|
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
|
return text
|
||||||
|
|
||||||
def run(self, root):
|
def run(self, root):
|
||||||
@@ -213,13 +239,9 @@ class HiliteTreeprocessor(Treeprocessor):
|
|||||||
if len(block) == 1 and block[0].tag == 'code':
|
if len(block) == 1 and block[0].tag == 'code':
|
||||||
code = CodeHilite(
|
code = CodeHilite(
|
||||||
self.code_unescape(block[0].text),
|
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,
|
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())
|
placeholder = self.md.htmlStash.store(code.hilite())
|
||||||
# Clear codeblock in etree instance
|
# Clear codeblock in etree instance
|
||||||
@@ -237,7 +259,7 @@ class CodeHiliteExtension(Extension):
|
|||||||
# define default configs
|
# define default configs
|
||||||
self.config = {
|
self.config = {
|
||||||
'linenums': [None,
|
'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,
|
'guess_lang': [True,
|
||||||
"Automatic language detection - Default: True"],
|
"Automatic language detection - Default: True"],
|
||||||
'css_class': ["codehilite",
|
'css_class': ["codehilite",
|
||||||
@@ -252,10 +274,25 @@ class CodeHiliteExtension(Extension):
|
|||||||
'use_pygments': [True,
|
'use_pygments': [True,
|
||||||
'Use Pygments to Highlight code blocks. '
|
'Use Pygments to Highlight code blocks. '
|
||||||
'Disable if using a JavaScript library. '
|
'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):
|
def extendMarkdown(self, md):
|
||||||
""" Add HilitePostprocessor to Markdown instance. """
|
""" Add HilitePostprocessor to Markdown instance. """
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class DefListProcessor(BlockProcessor):
|
|||||||
|
|
||||||
raw_block = blocks.pop(0)
|
raw_block = blocks.pop(0)
|
||||||
m = self.RE.search(raw_block)
|
m = self.RE.search(raw_block)
|
||||||
terms = [l.strip() for l in
|
terms = [term.strip() for term in
|
||||||
raw_block[:m.start()].split('\n') if l.strip()]
|
raw_block[:m.start()].split('\n') if term.strip()]
|
||||||
block = raw_block[m.end():]
|
block = raw_block[m.end():]
|
||||||
no_indent = self.NO_INDENT_RE.match(block)
|
no_indent = self.NO_INDENT_RE.match(block)
|
||||||
if no_indent:
|
if no_indent:
|
||||||
@@ -87,11 +87,13 @@ class DefListProcessor(BlockProcessor):
|
|||||||
class DefListIndentProcessor(ListIndentProcessor):
|
class DefListIndentProcessor(ListIndentProcessor):
|
||||||
""" Process indented children of definition list items. """
|
""" Process indented children of definition list items. """
|
||||||
|
|
||||||
ITEM_TYPES = ['dd']
|
# Defintion lists need to be aware of all list types
|
||||||
LIST_TYPES = ['dl']
|
ITEM_TYPES = ['dd', 'li']
|
||||||
|
LIST_TYPES = ['dl', 'ol', 'ul']
|
||||||
|
|
||||||
def create_item(self, parent, block):
|
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')
|
dd = etree.SubElement(parent, 'dd')
|
||||||
self.parser.parseBlocks(dd, [block])
|
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)
|
License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
from textwrap import dedent
|
||||||
from . import Extension
|
from . import Extension
|
||||||
from ..preprocessors import Preprocessor
|
from ..preprocessors import Preprocessor
|
||||||
from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
|
from .codehilite import CodeHilite, CodeHiliteExtension, parse_hl_lines
|
||||||
|
from .attr_list import get_attrs, AttrListExtension
|
||||||
|
from ..util import parseBoolValue
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
class FencedCodeExtension(Extension):
|
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):
|
def extendMarkdown(self, md):
|
||||||
""" Add FencedBlockPreprocessor to the Markdown instance. """
|
""" Add FencedBlockPreprocessor to the Markdown instance. """
|
||||||
md.registerExtension(self)
|
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):
|
class FencedBlockPreprocessor(Preprocessor):
|
||||||
FENCED_BLOCK_RE = re.compile(r'''
|
FENCED_BLOCK_RE = re.compile(
|
||||||
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # Opening ``` or ~~~
|
dedent(r'''
|
||||||
(\{?\.?(?P<lang>[\w#.+-]*))?[ ]* # Optional {, and lang
|
(?P<fence>^(?:~{3,}|`{3,}))[ ]* # opening fence
|
||||||
# Optional highlight lines, single- or double-quote-delimited
|
((\{(?P<attrs>[^\}\n]*)\})?| # (optional {attrs} or
|
||||||
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?[ ]*
|
(\.?(?P<lang>[\w#.+-]*))?[ ]* # optional (.)lang
|
||||||
}?[ ]*\n # Optional closing }
|
(hl_lines=(?P<quot>"|')(?P<hl_lines>.*?)(?P=quot))?) # optional hl_lines)
|
||||||
(?P<code>.*?)(?<=\n)
|
[ ]*\n # newline (end of opening fence)
|
||||||
(?P=fence)[ ]*$''', re.MULTILINE | re.DOTALL | re.VERBOSE)
|
(?P<code>.*?)(?<=\n) # the code block
|
||||||
CODE_WRAP = '<pre><code%s>%s</code></pre>'
|
(?P=fence)[ ]*$ # closing fence
|
||||||
LANG_TAG = ' class="%s"'
|
'''),
|
||||||
|
re.MULTILINE | re.DOTALL | re.VERBOSE
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, md):
|
def __init__(self, md, config):
|
||||||
super().__init__(md)
|
super().__init__(md)
|
||||||
|
self.config = config
|
||||||
self.checked_for_codehilite = False
|
self.checked_for_deps = False
|
||||||
self.codehilite_conf = {}
|
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):
|
def run(self, lines):
|
||||||
""" Match and store Fenced Code Blocks in the HtmlStash. """
|
""" Match and store Fenced Code Blocks in the HtmlStash. """
|
||||||
|
|
||||||
# Check for code hilite extension
|
# Check for dependent extensions
|
||||||
if not self.checked_for_codehilite:
|
if not self.checked_for_deps:
|
||||||
for ext in self.md.registeredExtensions:
|
for ext in self.md.registeredExtensions:
|
||||||
if isinstance(ext, CodeHiliteExtension):
|
if isinstance(ext, CodeHiliteExtension):
|
||||||
self.codehilite_conf = ext.config
|
self.codehilite_conf = ext.getConfigs()
|
||||||
break
|
if isinstance(ext, AttrListExtension):
|
||||||
|
self.use_attr_list = True
|
||||||
|
|
||||||
self.checked_for_codehilite = True
|
self.checked_for_deps = True
|
||||||
|
|
||||||
text = "\n".join(lines)
|
text = "\n".join(lines)
|
||||||
while 1:
|
while 1:
|
||||||
m = self.FENCED_BLOCK_RE.search(text)
|
m = self.FENCED_BLOCK_RE.search(text)
|
||||||
if m:
|
if m:
|
||||||
lang = ''
|
lang, id, classes, config = None, '', [], {}
|
||||||
if m.group('lang'):
|
if m.group('attrs'):
|
||||||
lang = self.LANG_TAG % m.group('lang')
|
id, classes, config = self.handle_attrs(get_attrs(m.group('attrs')))
|
||||||
|
if len(classes):
|
||||||
|
lang = classes.pop(0)
|
||||||
|
else:
|
||||||
|
if 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
|
# If config is not empty, then the codehighlite extension
|
||||||
# is enabled, so we call it to highlight the code
|
# 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(
|
highliter = CodeHilite(
|
||||||
m.group('code'),
|
m.group('code'),
|
||||||
linenums=self.codehilite_conf['linenums'][0],
|
lang=lang,
|
||||||
guess_lang=self.codehilite_conf['guess_lang'][0],
|
style=local_config.pop('pygments_style', 'default'),
|
||||||
css_class=self.codehilite_conf['css_class'][0],
|
**local_config
|
||||||
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'))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
code = highliter.hilite()
|
code = highliter.hilite()
|
||||||
else:
|
else:
|
||||||
code = self.CODE_WRAP % (lang,
|
id_attr = lang_attr = class_attr = kv_pairs = ''
|
||||||
self._escape(m.group('code')))
|
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)
|
placeholder = self.md.htmlStash.store(code)
|
||||||
text = '{}\n{}\n{}'.format(text[:m.start()],
|
text = '{}\n{}\n{}'.format(text[:m.start()],
|
||||||
@@ -96,6 +148,24 @@ class FencedBlockPreprocessor(Preprocessor):
|
|||||||
break
|
break
|
||||||
return text.split("\n")
|
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):
|
def _escape(self, txt):
|
||||||
""" basic html escaping """
|
""" basic html escaping """
|
||||||
txt = txt.replace('&', '&')
|
txt = txt.replace('&', '&')
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from . import Extension
|
from . import Extension
|
||||||
from ..preprocessors import Preprocessor
|
from ..blockprocessors import BlockProcessor
|
||||||
from ..inlinepatterns import InlineProcessor
|
from ..inlinepatterns import InlineProcessor
|
||||||
from ..treeprocessors import Treeprocessor
|
from ..treeprocessors import Treeprocessor
|
||||||
from ..postprocessors import Postprocessor
|
from ..postprocessors import Postprocessor
|
||||||
@@ -26,8 +26,6 @@ import xml.etree.ElementTree as etree
|
|||||||
|
|
||||||
FN_BACKLINK_TEXT = util.STX + "zz1337820767766393qq" + util.ETX
|
FN_BACKLINK_TEXT = util.STX + "zz1337820767766393qq" + util.ETX
|
||||||
NBSP_PLACEHOLDER = util.STX + "qq3936677670287331zz" + 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+)')
|
RE_REF_ID = re.compile(r'(fnref)(\d+)')
|
||||||
|
|
||||||
|
|
||||||
@@ -72,8 +70,8 @@ class FootnoteExtension(Extension):
|
|||||||
md.registerExtension(self)
|
md.registerExtension(self)
|
||||||
self.parser = md.parser
|
self.parser = md.parser
|
||||||
self.md = md
|
self.md = md
|
||||||
# Insert a preprocessor before ReferencePreprocessor
|
# Insert a blockprocessor before ReferencePreprocessor
|
||||||
md.preprocessors.register(FootnotePreprocessor(self), 'footnote', 15)
|
md.parser.blockprocessors.register(FootnoteBlockProcessor(self), 'footnote', 17)
|
||||||
|
|
||||||
# Insert an inline pattern before ImageReferencePattern
|
# Insert an inline pattern before ImageReferencePattern
|
||||||
FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
|
FOOTNOTE_RE = r'\[\^([^\]]*)\]' # blah blah [^1] blah
|
||||||
@@ -202,106 +200,92 @@ class FootnoteExtension(Extension):
|
|||||||
return div
|
return div
|
||||||
|
|
||||||
|
|
||||||
class FootnotePreprocessor(Preprocessor):
|
class FootnoteBlockProcessor(BlockProcessor):
|
||||||
""" Find all footnote references and store for later use. """
|
""" Find all footnote references and store for later use. """
|
||||||
|
|
||||||
|
RE = re.compile(r'^[ ]{0,3}\[\^([^\]]*)\]:[ ]*(.*)$', re.MULTILINE)
|
||||||
|
|
||||||
def __init__(self, footnotes):
|
def __init__(self, footnotes):
|
||||||
|
super().__init__(footnotes.parser)
|
||||||
self.footnotes = footnotes
|
self.footnotes = footnotes
|
||||||
|
|
||||||
def run(self, lines):
|
def test(self, parent, block):
|
||||||
"""
|
return True
|
||||||
Loop through lines and find, set, and remove footnote definitions.
|
|
||||||
|
|
||||||
Keywords:
|
def run(self, parent, blocks):
|
||||||
|
""" Find, set, and remove footnote definitions. """
|
||||||
|
block = blocks.pop(0)
|
||||||
|
m = self.RE.search(block)
|
||||||
|
if m:
|
||||||
|
id = m.group(1)
|
||||||
|
fn_blocks = [m.group(2)]
|
||||||
|
|
||||||
* lines: A list of lines of text
|
# Handle rest of block
|
||||||
|
therest = block[m.end():].lstrip('\n')
|
||||||
Return: A list of lines of text with footnote definitions removed.
|
m2 = self.RE.search(therest)
|
||||||
|
if m2:
|
||||||
"""
|
# Another footnote exists in the rest of this block.
|
||||||
newlines = []
|
# Any content before match is continuation of this footnote, which may be lazily indented.
|
||||||
i = 0
|
before = therest[:m2.start()].rstrip('\n')
|
||||||
while True:
|
fn_blocks[0] = '\n'.join([fn_blocks[0], self.detab(before)]).lstrip('\n')
|
||||||
m = DEF_RE.match(lines[i])
|
# Add back to blocks everything from begining of match forward for next iteration.
|
||||||
if m:
|
blocks.insert(0, therest[m2.start():])
|
||||||
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:
|
else:
|
||||||
newlines.append(lines[i])
|
# All remaining lines of block are continuation of this footnote, which may be lazily indented.
|
||||||
if len(lines) > i+1:
|
fn_blocks[0] = '\n'.join([fn_blocks[0], self.detab(therest)]).strip('\n')
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return newlines
|
|
||||||
|
|
||||||
def detectTabbed(self, lines):
|
# 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.
|
""" Find indented text and remove indent before further proccesing.
|
||||||
|
|
||||||
Keyword arguments:
|
Returns: a list of blocks with indentation removed.
|
||||||
|
|
||||||
* lines: an array of strings
|
|
||||||
|
|
||||||
Returns: a list of post processed items and the index of last line.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
items = []
|
fn_blocks = []
|
||||||
blank_line = False # have we encountered a blank line yet?
|
while blocks:
|
||||||
i = 0 # to keep track of where we are
|
if blocks[0].startswith(' '*4):
|
||||||
|
block = blocks.pop(0)
|
||||||
def detab(line):
|
# Check for new footnotes within this block and split at new footnote.
|
||||||
match = TABBED_RE.match(line)
|
m = self.RE.search(block)
|
||||||
if match:
|
if m:
|
||||||
return match.group(4)
|
# Another footnote exists in this block.
|
||||||
|
# Any content before match is continuation of this footnote, which may be lazily indented.
|
||||||
for line in lines:
|
before = block[:m.start()].rstrip('\n')
|
||||||
if line.strip(): # Non-blank line
|
fn_blocks.append(self.detab(before))
|
||||||
detabbed_line = detab(line)
|
# Add back to blocks everything from begining of match forward for next iteration.
|
||||||
if detabbed_line:
|
blocks.insert(0, block[m.start():])
|
||||||
items.append(detabbed_line)
|
# End of this footnote.
|
||||||
i += 1
|
break
|
||||||
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:
|
else:
|
||||||
return items, i+1
|
# Entire block is part of this footnote.
|
||||||
|
fn_blocks.append(self.detab(block))
|
||||||
|
else:
|
||||||
|
# End of this footnote.
|
||||||
|
break
|
||||||
|
return fn_blocks
|
||||||
|
|
||||||
else: # Blank line: _maybe_ we are done.
|
def detab(self, block):
|
||||||
blank_line = True
|
""" Remove one level of indent from a block.
|
||||||
i += 1 # advance
|
|
||||||
|
|
||||||
# Find the next non-blank line
|
Preserve lazily indented blocks by only removing indent from indented lines.
|
||||||
for j in range(i, len(lines)):
|
"""
|
||||||
if lines[j].strip():
|
lines = block.split('\n')
|
||||||
next_line = lines[j]
|
for i, line in enumerate(lines):
|
||||||
break
|
if line.startswith(' '*4):
|
||||||
else:
|
lines[i] = line[4:]
|
||||||
# Include extreaneous padding to prevent raw HTML
|
return '\n'.join(lines)
|
||||||
# parsing issue: https://github.com/Python-Markdown/markdown/issues/584
|
|
||||||
items.append("")
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
break # There is no more text; we are done.
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
return items, i
|
|
||||||
|
|
||||||
|
|
||||||
class FootnoteInlineProcessor(InlineProcessor):
|
class FootnoteInlineProcessor(InlineProcessor):
|
||||||
@@ -347,8 +331,8 @@ class FootnotePostTreeprocessor(Treeprocessor):
|
|||||||
self.offset += 1
|
self.offset += 1
|
||||||
# Add all the new duplicate links.
|
# Add all the new duplicate links.
|
||||||
el = list(li)[-1]
|
el = list(li)[-1]
|
||||||
for l in links:
|
for link in links:
|
||||||
el.append(l)
|
el.append(link)
|
||||||
break
|
break
|
||||||
|
|
||||||
def get_num_duplicates(self, li):
|
def get_num_duplicates(self, li):
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ EMPHASIS_RE = r'(_)([^_]+)\1'
|
|||||||
STRONG_RE = r'(_{2})(.+?)\1'
|
STRONG_RE = r'(_{2})(.+?)\1'
|
||||||
|
|
||||||
# __strong_em___
|
# __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):
|
class LegacyUnderscoreProcessor(UnderscoreProcessor):
|
||||||
|
|||||||
@@ -16,68 +16,313 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
|||||||
|
|
||||||
from . import Extension
|
from . import Extension
|
||||||
from ..blockprocessors import BlockProcessor
|
from ..blockprocessors import BlockProcessor
|
||||||
|
from ..preprocessors import Preprocessor
|
||||||
|
from ..postprocessors import RawHtmlPostprocessor
|
||||||
from .. import util
|
from .. import util
|
||||||
import re
|
from ..htmlparser import HTMLExtractor
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
|
||||||
class MarkdownInHtmlProcessor(BlockProcessor):
|
class HTMLExtractorExtra(HTMLExtractor):
|
||||||
"""Process Markdown Inside HTML Blocks."""
|
"""
|
||||||
def test(self, parent, block):
|
Override HTMLExtractor and create etree Elements for any elements which should have content parsed as Markdown.
|
||||||
return block == util.TAG_PLACEHOLDER % \
|
"""
|
||||||
str(self.parser.blockprocessors.tag_counter + 1)
|
|
||||||
|
|
||||||
def _process_nests(self, element, block):
|
def __init__(self, md, *args, **kwargs):
|
||||||
"""Process the element's child elements in self.run."""
|
# All block-level tags.
|
||||||
# Build list of indexes of each nest within the parent element.
|
self.block_level_tags = set(md.block_level_elements.copy())
|
||||||
nest_index = [] # a list of tuples: (left index, right index)
|
# Block-level tags in which the content only gets span level parsing
|
||||||
i = self.parser.blockprocessors.tag_counter + 1
|
self.span_tags = set(
|
||||||
while len(self._tag_data) > i and self._tag_data[i]['left_index']:
|
['address', 'dd', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'legend', 'li', 'p', 'td', 'th']
|
||||||
left_child_index = self._tag_data[i]['left_index']
|
)
|
||||||
right_child_index = self._tag_data[i]['right_index']
|
# Block-level tags which never get their content parsed.
|
||||||
nest_index.append((left_child_index - 1, right_child_index))
|
self.raw_tags = set(['canvas', 'math', 'option', 'pre', 'script', 'style', 'textarea'])
|
||||||
i += 1
|
# Block-level tags in which the content gets parsed as blocks
|
||||||
|
super().__init__(md, *args, **kwargs)
|
||||||
|
|
||||||
# Create each nest subelement.
|
self.block_tags = set(self.block_level_tags) - (self.span_tags | self.raw_tags | self.empty_tags)
|
||||||
for i, (left_index, right_index) in enumerate(nest_index[:-1]):
|
self.span_and_blocks_tags = self.block_tags | self.span_tags
|
||||||
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
|
|
||||||
|
|
||||||
def run(self, parent, blocks, tail=None, nest=False):
|
def reset(self):
|
||||||
self._tag_data = self.parser.md.htmlStash.tag_data
|
"""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
|
def close(self):
|
||||||
tag = self._tag_data[self.parser.blockprocessors.tag_counter]
|
"""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
|
def get_element(self):
|
||||||
markdown_value = tag['attrs'].pop('markdown')
|
""" Return element from treebuilder and reset treebuilder for later use. """
|
||||||
element = etree.SubElement(parent, tag['tag'], tag['attrs'])
|
element = self.treebuilder.close()
|
||||||
|
self.treebuilder = etree.TreeBuilder()
|
||||||
|
return element
|
||||||
|
|
||||||
# Slice Off Block
|
def get_state(self, tag, attrs):
|
||||||
if nest:
|
""" Return state from tag and `markdown` attr. One of 'block', 'span', or 'off'. """
|
||||||
self.parser.parseBlocks(parent, tail) # Process Tail
|
md_attr = attrs.get('markdown', '0')
|
||||||
block = blocks[1:]
|
if md_attr == 'markdown':
|
||||||
else: # includes nests since a third level of nesting isn't supported
|
# `<tag markdown>` is the same as `<tag markdown='1'>`.
|
||||||
block = blocks[tag['left_index'] + 1: tag['right_index']]
|
md_attr = '1'
|
||||||
del blocks[:tag['right_index']]
|
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
|
def at_line_start(self):
|
||||||
if (self.parser.blockprocessors.contain_span_tags.match( # Span Mode
|
"""At line start."""
|
||||||
tag['tag']) and markdown_value != 'block') or \
|
|
||||||
markdown_value == 'span':
|
value = super().at_line_start()
|
||||||
element.text = '\n'.join(block)
|
if not value and self.cleandoc and self.cleandoc[-1].endswith('\n'):
|
||||||
else: # Block Mode
|
value = True
|
||||||
i = self.parser.blockprocessors.tag_counter + 1
|
return value
|
||||||
if len(self._tag_data) > i and self._tag_data[i]['left_index']:
|
|
||||||
first_subelement_index = self._tag_data[i]['left_index'] - 1
|
def handle_starttag(self, tag, attrs):
|
||||||
self.parser.parseBlocks(
|
# Handle tags that should always be empty and do not specify a closing tag
|
||||||
element, block[:first_subelement_index])
|
if tag in self.empty_tags:
|
||||||
if not nest:
|
attrs = {key: value if value is not None else key for key, value in attrs}
|
||||||
block = self._process_nests(element, block)
|
if "markdown" in attrs:
|
||||||
|
attrs.pop('markdown')
|
||||||
|
element = etree.Element(tag, attrs)
|
||||||
|
data = etree.tostring(element, encoding='unicode', method='html')
|
||||||
else:
|
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):
|
class MarkdownInHtmlExtension(Extension):
|
||||||
@@ -86,14 +331,14 @@ class MarkdownInHtmlExtension(Extension):
|
|||||||
def extendMarkdown(self, md):
|
def extendMarkdown(self, md):
|
||||||
""" Register extension instances. """
|
""" Register extension instances. """
|
||||||
|
|
||||||
# Turn on processing of markdown text within raw html
|
# Replace raw HTML preprocessor
|
||||||
md.preprocessors['html_block'].markdown_in_raw = True
|
md.preprocessors.register(HtmlBlockPreprocessor(md), 'html_block', 20)
|
||||||
|
# Add blockprocessor which handles the placeholders for etree elements
|
||||||
md.parser.blockprocessors.register(
|
md.parser.blockprocessors.register(
|
||||||
MarkdownInHtmlProcessor(md.parser), 'markdown_block', 105
|
MarkdownInHtmlProcessor(md.parser), 'markdown_block', 105
|
||||||
)
|
)
|
||||||
md.parser.blockprocessors.tag_counter = -1
|
# Replace raw HTML postprocessor
|
||||||
md.parser.blockprocessors.contain_span_tags = re.compile(
|
md.postprocessors.register(MarkdownInHTMLPostprocessor(md), 'raw_html', 30)
|
||||||
r'^(p|h[1-6]|li|dd|dt|td|th|legend|address)$', re.IGNORECASE)
|
|
||||||
|
|
||||||
|
|
||||||
def makeExtension(**kwargs): # pragma: no cover
|
def makeExtension(**kwargs): # pragma: no cover
|
||||||
|
|||||||
@@ -15,18 +15,24 @@ License: [BSD](https://opensource.org/licenses/bsd-license.php)
|
|||||||
|
|
||||||
from . import Extension
|
from . import Extension
|
||||||
from ..treeprocessors import Treeprocessor
|
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
|
from ..postprocessors import UnescapePostprocessor
|
||||||
import re
|
import re
|
||||||
|
import html
|
||||||
import unicodedata
|
import unicodedata
|
||||||
import xml.etree.ElementTree as etree
|
import xml.etree.ElementTree as etree
|
||||||
|
|
||||||
|
|
||||||
def slugify(value, separator):
|
def slugify(value, separator, encoding='ascii'):
|
||||||
""" Slugify a string, to make it URL friendly. """
|
""" Slugify a string, to make it URL friendly. """
|
||||||
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
|
value = unicodedata.normalize('NFKD', value).encode(encoding, 'ignore')
|
||||||
value = re.sub(r'[^\w\s-]', '', value.decode('ascii')).strip().lower()
|
value = re.sub(r'[^\w\s-]', '', value.decode(encoding)).strip().lower()
|
||||||
return re.sub(r'[%s\s]+' % separator, separator, value)
|
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]+)$')
|
IDCOUNT_RE = re.compile(r'^(.*)_([0-9]+)$')
|
||||||
@@ -44,6 +50,18 @@ def unique(id, ids):
|
|||||||
return id
|
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):
|
def stashedHTML2text(text, md, strip_entities=True):
|
||||||
""" Extract raw HTML from stash, reduce to plain text and swap with placeholder. """
|
""" Extract raw HTML from stash, reduce to plain text and swap with placeholder. """
|
||||||
def _html_sub(m):
|
def _html_sub(m):
|
||||||
@@ -253,7 +271,7 @@ class TocTreeprocessor(Treeprocessor):
|
|||||||
self.set_level(el)
|
self.set_level(el)
|
||||||
if int(el.tag[-1]) < self.toc_top or int(el.tag[-1]) > self.toc_bottom:
|
if int(el.tag[-1]) < self.toc_top or int(el.tag[-1]) > self.toc_bottom:
|
||||||
continue
|
continue
|
||||||
text = ''.join(el.itertext()).strip()
|
text = get_name(el)
|
||||||
|
|
||||||
# Do not override pre-existing ids
|
# Do not override pre-existing ids
|
||||||
if "id" not in el.attrib:
|
if "id" not in el.attrib:
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ def build_inlinepatterns(md, **kwargs):
|
|||||||
inlinePatterns.register(
|
inlinePatterns.register(
|
||||||
ShortReferenceInlineProcessor(REFERENCE_RE, md), 'short_reference', 130
|
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(AutolinkInlineProcessor(AUTOLINK_RE, md), 'autolink', 120)
|
||||||
inlinePatterns.register(AutomailInlineProcessor(AUTOMAIL_RE, md), 'automail', 110)
|
inlinePatterns.register(AutomailInlineProcessor(AUTOMAIL_RE, md), 'automail', 110)
|
||||||
inlinePatterns.register(SubstituteTagInlineProcessor(LINE_BREAK_RE, 'br'), 'linebreak', 100)
|
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__em_
|
||||||
STRONG_EM2_RE = r'(_)\1{2}(.+?)\1{2}(.*?)\1'
|
STRONG_EM2_RE = r'(_)\1{2}(.+?)\1{2}(.*?)\1'
|
||||||
|
|
||||||
# __strong_em___
|
# **strong*em***
|
||||||
STRONG_EM3_RE = r'(\*)\1(?!\1)(.+?)\1(?!\1)(.+?)\1{3}'
|
STRONG_EM3_RE = r'(\*)\1(?!\1)([^*]+?)\1(?!\1)(.+?)\1{3}'
|
||||||
|
|
||||||
# [text](url) or [text](<url>) or [text](url "title")
|
# [text](url) or [text](<url>) or [text](url "title")
|
||||||
LINK_RE = NOIMG + r'\['
|
LINK_RE = NOIMG + r'\['
|
||||||
@@ -844,6 +847,14 @@ class ImageReferenceInlineProcessor(ReferenceInlineProcessor):
|
|||||||
return el
|
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):
|
class AutolinkInlineProcessor(InlineProcessor):
|
||||||
""" Return a link Element given an autolink (`<http://example/com>`). """
|
""" Return a link Element given an autolink (`<http://example/com>`). """
|
||||||
def handleMatch(self, m, data):
|
def handleMatch(self, m, data):
|
||||||
|
|||||||
@@ -69,11 +69,10 @@ class RawHtmlPostprocessor(Postprocessor):
|
|||||||
""" Iterate over html stash and restore html. """
|
""" Iterate over html stash and restore html. """
|
||||||
replacements = OrderedDict()
|
replacements = OrderedDict()
|
||||||
for i in range(self.md.htmlStash.html_counter):
|
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):
|
if self.isblocklevel(html):
|
||||||
replacements["<p>%s</p>" %
|
replacements["<p>{}</p>".format(
|
||||||
(self.md.htmlStash.get_placeholder(i))] = \
|
self.md.htmlStash.get_placeholder(i))] = html
|
||||||
html + "\n"
|
|
||||||
replacements[self.md.htmlStash.get_placeholder(i)] = html
|
replacements[self.md.htmlStash.get_placeholder(i)] = html
|
||||||
|
|
||||||
if replacements:
|
if replacements:
|
||||||
@@ -96,6 +95,10 @@ class RawHtmlPostprocessor(Postprocessor):
|
|||||||
return self.md.is_block_level(m.group(1))
|
return self.md.is_block_level(m.group(1))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def stash_to_string(self, text):
|
||||||
|
""" Convert a stashed object to a string. """
|
||||||
|
return str(text)
|
||||||
|
|
||||||
|
|
||||||
class AndSubstitutePostprocessor(Postprocessor):
|
class AndSubstitutePostprocessor(Postprocessor):
|
||||||
""" Restore valid entities """
|
""" Restore valid entities """
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ complicated.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from . import util
|
from . import util
|
||||||
|
from .htmlparser import HTMLExtractor
|
||||||
import re
|
import re
|
||||||
|
|
||||||
|
|
||||||
@@ -34,7 +35,6 @@ def build_preprocessors(md, **kwargs):
|
|||||||
preprocessors = util.Registry()
|
preprocessors = util.Registry()
|
||||||
preprocessors.register(NormalizeWhitespace(md), 'normalize_whitespace', 30)
|
preprocessors.register(NormalizeWhitespace(md), 'normalize_whitespace', 30)
|
||||||
preprocessors.register(HtmlBlockPreprocessor(md), 'html_block', 20)
|
preprocessors.register(HtmlBlockPreprocessor(md), 'html_block', 20)
|
||||||
preprocessors.register(ReferencePreprocessor(md), 'reference', 10)
|
|
||||||
return preprocessors
|
return preprocessors
|
||||||
|
|
||||||
|
|
||||||
@@ -74,297 +74,9 @@ class NormalizeWhitespace(Preprocessor):
|
|||||||
class HtmlBlockPreprocessor(Preprocessor):
|
class HtmlBlockPreprocessor(Preprocessor):
|
||||||
"""Remove html blocks from the text and store them for later retrieval."""
|
"""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):
|
def run(self, lines):
|
||||||
text = "\n".join(lines)
|
source = '\n'.join(lines)
|
||||||
new_blocks = []
|
parser = HTMLExtractor(self.md)
|
||||||
text = text.rsplit("\n\n")
|
parser.feed(source)
|
||||||
items = []
|
parser.close()
|
||||||
left_tag = ''
|
return ''.join(parser.cleandoc).split('\n')
|
||||||
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"
|
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ License: BSD (see LICENSE.md for details).
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
import textwrap
|
import textwrap
|
||||||
from . import markdown
|
from . import markdown, util
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import tidylib
|
import tidylib
|
||||||
@@ -73,6 +74,32 @@ class TestCase(unittest.TestCase):
|
|||||||
return textwrap.dedent(text).strip()
|
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 #
|
# Legacy Test Framework #
|
||||||
#########################
|
#########################
|
||||||
@@ -113,8 +140,11 @@ class LegacyTestMeta(type):
|
|||||||
expected = f.read().replace("\r\n", "\n")
|
expected = f.read().replace("\r\n", "\n")
|
||||||
output = markdown(input, **kwargs)
|
output = markdown(input, **kwargs)
|
||||||
if tidylib and normalize:
|
if tidylib and normalize:
|
||||||
expected = _normalize_whitespace(expected)
|
try:
|
||||||
output = _normalize_whitespace(output)
|
expected = _normalize_whitespace(expected)
|
||||||
|
output = _normalize_whitespace(output)
|
||||||
|
except OSError:
|
||||||
|
self.skipTest("Tidylib's c library not available.")
|
||||||
elif normalize:
|
elif normalize:
|
||||||
self.skipTest('Tidylib not available.')
|
self.skipTest('Tidylib not available.')
|
||||||
self.assertMultiLineEqual(output, expected)
|
self.assertMultiLineEqual(output, expected)
|
||||||
@@ -167,7 +197,7 @@ class LegacyTestCase(unittest.TestCase, metaclass=LegacyTestMeta):
|
|||||||
arguments for all test files in the directory.
|
arguments for all test files in the directory.
|
||||||
|
|
||||||
In addition, properties can be defined for each individual set of test files within
|
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
|
extension. Any spaces and dashes in the filename should be replaced with
|
||||||
underscores. The value of the property should be a `Kwargs` instance which
|
underscores. The value of the property should be a `Kwargs` instance which
|
||||||
contains the keyword arguments that should be passed to `Markdown` for that
|
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