More included file content.
End of included content.
More included file content.
End of included content.
```
## ChangeLog
### Version 0.7.0
Modified to work with Python-Markdown 3.4. This makes the plugin
incompatible with versions < 3.0.
### Version 0.6.0
- Added ability ot offset headers in the included file so they fall under the header level in which the include occurs
- Add option to throw exception when can't find an include file (instead of printing a warning)
- Fixed stripping of last character in file, so only occurs if it is a new-line
- Some behind-the-scenes improvement to code and documentation
### Version 0.5.1
Bugfix for a syntax error.
### Version 0.5
Corrected some errors in documentation and merged in commits of
[diegobz](https://github.com/diegobz) to add support for encoding and tidy up
the source code.
### Version 0.4
Fixed problem related to passing configurations to the extension.
### Version 0.3
Added support for Python 3.
### Version 0.2
Changed the API to be less likely to conflict with other syntax.
### Version 0.1
Initial release.
markdown-include-0.8.1/markdown_include/ 0000775 0000000 0000000 00000000000 14370416701 0020313 5 ustar 00root root 0000000 0000000 markdown-include-0.8.1/markdown_include/__init__.py 0000664 0000000 0000000 00000000000 14370416701 0022412 0 ustar 00root root 0000000 0000000 markdown-include-0.8.1/markdown_include/include.py 0000664 0000000 0000000 00000020022 14370416701 0022304 0 ustar 00root root 0000000 0000000 # -*- coding: utf-8 -*-
#
# include.py
#
# Copyright 2015 Christopher MacMackin
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.
#
#
from __future__ import print_function
import re
import os.path
from codecs import open
from markdown.extensions import Extension
from markdown.preprocessors import Preprocessor
INC_SYNTAX = re.compile(r"{!\s*(.+?)\s*!((\blines\b)=([0-9 -]+))?\}")
HEADING_SYNTAX = re.compile("^#+")
class MarkdownInclude(Extension):
def __init__(self, configs={}):
self.config = {
"base_path": [
".",
"Default location from which to evaluate "
"relative paths for the include statement.",
],
"encoding": [
"utf-8",
"Encoding of the files used by the include " "statement.",
],
"inheritHeadingDepth": [
False,
"Increases headings on included "
"file by amount of previous heading (combines with "
"headingOffset option).",
],
"headingOffset": [
0,
"Increases heading depth by a specific "
"amount (and the inheritHeadingDepth option). Defaults to 0.",
],
"throwException": [
False,
"When true, if the extension is unable "
"to find an included file it will throw an "
"exception which the user can catch. If false "
"(default), a warning will be printed and "
"Markdown will continue parsing the file.",
],
}
for key, value in configs.items():
self.setConfig(key, value)
def extendMarkdown(self, md):
md.preprocessors.register(
IncludePreprocessor(md, self.getConfigs()), "include", 101
)
class IncludePreprocessor(Preprocessor):
"""
This provides an "include" function for Markdown, similar to that found in
LaTeX (also the C pre-processor and Fortran). The syntax is {!filename!},
which will be replaced by the contents of filename. Any such statements in
filename will also be replaced. This replacement is done prior to any other
Markdown processing. All file-names are evaluated relative to the location
from which Markdown is being called.
"""
def __init__(self, md, config):
super(IncludePreprocessor, self).__init__(md)
self.base_path = config["base_path"]
self.encoding = config["encoding"]
self.inheritHeadingDepth = config["inheritHeadingDepth"]
self.headingOffset = config["headingOffset"]
self.throwException = config["throwException"]
def run(self, lines):
done = False
bonusHeading = ""
while not done:
for loc, line in enumerate(lines):
m = INC_SYNTAX.search(line)
while m:
filename = m.group(1)
filename = os.path.expanduser(filename)
if not os.path.isabs(filename):
filename = os.path.normpath(
os.path.join(self.base_path, filename)
)
try:
with open(filename, "r", encoding=self.encoding) as r:
original_text = self.run(r.readlines())
except Exception as e:
if not self.throwException:
print(
"Warning: could not find file {}. Ignoring "
"include statement. Error: {}".format(filename, e)
)
lines[loc] = INC_SYNTAX.sub("", line)
break
else:
raise e
if m.group(2) is None:
text = original_text
else:
lines_str = m.group(4)
lines_blocks = lines_str.split()
wanted_lines = []
for block in lines_blocks:
if "-" in block:
start, end = block.strip().split("-")
current_start = int(start)
current_end = int(end)
if not len(original_text) >= current_end:
current_end = len(original_text)
print(
f"Warning: line range: {block} ending in "
f"line: {end} is larger than file: {filename} "
f"using end: {current_end}"
)
if not current_start <= current_end:
current_start = max(current_end - 1, 1)
print(
f"Warning: in line range: {block} "
f"the start line: {start} is not "
f"smaller than the end line: {current_end} "
f"using start: {current_start}"
)
wanted_lines.extend(
original_text[current_start - 1 : current_end]
)
else:
wanted_line = int(block.strip())
current_line = wanted_line
if current_line > len(original_text):
current_line = len(original_text)
print(
f"Warning: line: {wanted_line} is larger than "
f"file: {filename} using end: {current_line}"
)
wanted_lines.append(original_text[current_line - 1])
text = wanted_lines
if len(text) == 0:
text.append("")
for i in range(len(text)):
# Strip the newline, and optionally increase header depth
if HEADING_SYNTAX.search(text[i]):
if self.inheritHeadingDepth:
text[i] = bonusHeading + text[i]
if self.headingOffset:
text[i] = "#" * self.headingOffset + text[i]
text[i] = text[i].rstrip("\r\n")
text_to_insert = "\r\n".join(text)
line = line[: m.start()] + text_to_insert.strip() + line[m.end() :]
del lines[loc]
lines[loc:loc] = line.splitlines()
m = INC_SYNTAX.search(line)
else:
h = HEADING_SYNTAX.search(line)
if h:
headingDepth = len(h.group(0))
bonusHeading = "#" * headingDepth
else:
done = True
return lines
def makeExtension(*args, **kwargs):
return MarkdownInclude(kwargs)
markdown-include-0.8.1/pyproject.toml 0000664 0000000 0000000 00000002451 14370416701 0017704 0 ustar 00root root 0000000 0000000 [build-system]
requires = [
"setuptools >= 61.0.0",
"setuptools_scm[toml] >= 6.2",
]
build-backend = "setuptools.build_meta"
[project]
name = "markdown-include"
description = "A Python-Markdown extension which provides an 'include' function"
readme = "README.md"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Python",
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Topic :: Internet :: WWW/HTTP :: Site Management",
"Topic :: Software Development :: Documentation",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Text Processing :: Filters",
"Topic :: Text Processing :: Markup :: HTML",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
]
keywords = ["Markdown", "typesetting", "include", "plugin", "extension"]
license = {text = "GNU General Public License v3 (GPLv3)"}
authors = [{name = "Chris MacMackin", email = "cmacmackin@gmail.com"}]
urls = {project = "https://github.com/cmacmackin/markdown-include"}
dependencies = [
"markdown>=3.0",
]
dynamic = ["version"]
[project.optional-dependencies]
tests = [
"pytest",
]
[tool.setuptools]
packages = ["markdown_include"]
[tool.setuptools.dynamic]
version = { attr = "setuptools_scm.get_version" }
[tool.black]
markdown-include-0.8.1/setup.cfg 0000664 0000000 0000000 00000000051 14370416701 0016603 0 ustar 00root root 0000000 0000000 [metadata]
description-file = README.rst
markdown-include-0.8.1/setup.py 0000664 0000000 0000000 00000000045 14370416701 0016477 0 ustar 00root root 0000000 0000000 from setuptools import setup
setup()
markdown-include-0.8.1/tests/ 0000775 0000000 0000000 00000000000 14370416701 0016130 5 ustar 00root root 0000000 0000000 markdown-include-0.8.1/tests/resources/ 0000775 0000000 0000000 00000000000 14370416701 0020142 5 ustar 00root root 0000000 0000000 markdown-include-0.8.1/tests/resources/header.md 0000664 0000000 0000000 00000000167 14370416701 0021720 0 ustar 00root root 0000000 0000000 # This heading will be one level deeper from the previous heading
More included file content.
End of included content.
markdown-include-0.8.1/tests/resources/longer.md 0000664 0000000 0000000 00000000170 14370416701 0021750 0 ustar 00root root 0000000 0000000 This is line 1
This is line 2
This is line 3
This is line 4
This is line 5
This is line 6
This is line 7
This is line 8
markdown-include-0.8.1/tests/resources/simple.md 0000664 0000000 0000000 00000000032 14370416701 0021750 0 ustar 00root root 0000000 0000000 This is a simple template
markdown-include-0.8.1/tests/resources/simple_2.md 0000664 0000000 0000000 00000000040 14370416701 0022170 0 ustar 00root root 0000000 0000000 This is another simple template
markdown-include-0.8.1/tests/resources/table_inner.md 0000664 0000000 0000000 00000000150 14370416701 0022742 0 ustar 00root root 0000000 0000000 # Inner title
| Aaaaa | aaaaa | sdknjhdjklfhd |
| ----- | ----- | ------------- |
| aaa | aaa | aaaa |
markdown-include-0.8.1/tests/resources/template_inside.md 0000664 0000000 0000000 00000000063 14370416701 0023631 0 ustar 00root root 0000000 0000000 {!simple.md!}
This is a template with a template.
markdown-include-0.8.1/tests/test_include.py 0000664 0000000 0000000 00000012516 14370416701 0021171 0 ustar 00root root 0000000 0000000 from markdown_include.include import IncludePreprocessor, MarkdownInclude
import markdown
import pathlib
from textwrap import dedent
import pytest
RESOURCE_DIR = pathlib.Path(__file__).parent.absolute() / "resources"
@pytest.fixture(scope="module")
def markdown_include():
return MarkdownInclude(configs={"base_path": RESOURCE_DIR})
@pytest.fixture(scope="module")
def markdown_include_inherit_heading_depth():
return MarkdownInclude(
configs={"base_path": RESOURCE_DIR, "inheritHeadingDepth": True}
)
def test_single_include(markdown_include):
source = "{!simple.md!}"
html = markdown.markdown(source, extensions=[markdown_include])
assert html == "This is a simple template
"
def test_double_include(markdown_include):
source = "{!simple.md!} and {!simple_2.md!}"
html = markdown.markdown(source, extensions=[markdown_include])
assert (
html == "This is a simple template and This is another simple template
"
)
def test_headers(markdown_include):
source = (
"Source file\n"
"# Heading Level 1 of main file\n"
"{!header.md!}\n"
"## Heading Level 2 of main file\n"
"{!header.md!}"
)
html = markdown.markdown(source, extensions=[markdown_include])
assert html == dedent(
"""\
Source file
Heading Level 1 of main file
This heading will be one level deeper from the previous heading
More included file content.
End of included content.
Heading Level 2 of main file
This heading will be one level deeper from the previous heading
More included file content.
End of included content.
"""
)
def test_embedded_template(markdown_include):
source = "{!template_inside.md!}"
html = markdown.markdown(source, extensions=[markdown_include])
assert (
html
== "This is a simple template
\nThis is a template with a template.
"
)
def test_single_include_inherit_heading_depth(markdown_include_inherit_heading_depth):
source = "{!simple.md!}"
html = markdown.markdown(
source, extensions=[markdown_include_inherit_heading_depth]
)
assert html == "This is a simple template
"
def test_double_include_inherit_heading_depth(markdown_include_inherit_heading_depth):
source = "{!simple.md!} and {!simple_2.md!}"
html = markdown.markdown(
source, extensions=[markdown_include_inherit_heading_depth]
)
assert (
html == "This is a simple template and This is another simple template
"
)
def test_headers_inherit_heading_depth(markdown_include_inherit_heading_depth):
source = (
"Source file\n"
"# Heading Level 1 of main file\n"
"{!header.md!}\n"
"## Heading Level 2 of main file\n"
"{!header.md!}"
)
html = markdown.markdown(
source, extensions=[markdown_include_inherit_heading_depth]
)
assert html == dedent(
"""\
Source file
Heading Level 1 of main file
This heading will be one level deeper from the previous heading
More included file content.
End of included content.
Heading Level 2 of main file
This heading will be one level deeper from the previous heading
More included file content.
End of included content.
"""
)
def test_processor_lines():
processor = IncludePreprocessor(
None,
{
"base_path": RESOURCE_DIR,
"encoding": "utf-8",
"inheritHeadingDepth": False,
"headingOffset": 0,
"throwException": False,
},
)
source = [
"Source file",
"# Heading Level 1 of main file",
"{!header.md!}",
"## Heading Level 2 of main file",
"{!header.md!}",
]
result_lines = processor.run(source)
assert len(result_lines) == 9
def test_include_lines(markdown_include):
source = "{!longer.md!lines=1 3}"
html = markdown.markdown(source, extensions=[markdown_include])
assert html == dedent(
"""\
This is line 1
This is line 3
"""
)
def test_include_line_range(markdown_include):
source = "{!longer.md!lines=3-5}"
html = markdown.markdown(source, extensions=[markdown_include])
assert html == dedent(
"""\
This is line 3
This is line 4
This is line 5
"""
)
def test_include_lines_and_line_range(markdown_include):
source = "{!longer.md!lines=1 3-5 8}"
html = markdown.markdown(source, extensions=[markdown_include])
assert html == dedent(
"""\
This is line 1
This is line 3
This is line 4
This is line 5
This is line 8
"""
)
def test_include_lines_out_of_order(markdown_include):
source = "{!longer.md!lines=3 1}"
html = markdown.markdown(source, extensions=[markdown_include])
assert html == dedent(
"""\
This is line 3
This is line 1
"""
)
def test_nested_table(markdown_include_inherit_heading_depth):
source = "{!table_inner.md!}"
html = markdown.markdown(
source, extensions=[markdown_include_inherit_heading_depth, "tables"]
)
assert "