diff --git a/.flake8 b/.flake8
new file mode 100644
index 00000000..1cdb0993
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,22 @@
+[flake8]
+exclude =
+ # No need to traverse our git or venv directories
+ .git,
+ venv,
+
+ # Don't process our PyPi script
+ ./setup.py,
+
+ # There's no value in checking cache directories
+ __pycache__,
+
+ # This contains our documentation
+ docs,
+
+ # This contains tests that we don't want to check
+ kordac/tests,
+
+show-source = True
+statistics = True
+count = True
+max-line-length=119
diff --git a/.travis.yml b/.travis.yml
index bda1e2f6..d49463fe 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,10 +8,14 @@ python:
- "3.6-dev" # 3.6 development branch
# Install dependencies
-install: pip install -r requirements.txt
+install:
+ - pip install flake8
+ - pip install -r requirements.txt
# Runs test suite
-script: python -m kordac.tests.start_tests --travis
+script:
+ - flake8
+ - python -m kordac.tests.start_tests --travis
# Stop email notifications but post to organisation Slack channel
notifications:
diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst
index 4599d8ac..0a9f3702 100644
--- a/docs/source/contributing.rst
+++ b/docs/source/contributing.rst
@@ -102,11 +102,11 @@ The items of interest are:
- ``KordacExtension()`` - This is the main class of the project, and inherits the ``Extension`` class from Markdown. It loads all of the processor information, loads the template files and clears and populates the attributes to be returned by the ``KordacResult`` object.
-- ``Processors/`` - There is a different processor for each tag. A processor uses it's corresponding regex loaded from ``processor-info.json`` to find matches in the text, and uses the given arguments in the matched tag to populate and output it's html template.
+- ``Processors/`` - There is a different processor for each tag. A processor uses it's corresponding description loaded from ``processor-info.json`` to find matches in the text, and uses the given arguments in the matched tag to populate and output it's html template.
- ``html-templates/`` - The html templates (using the Jinja2 template engine) with variable arguments to be populated by processors.
-- ``processor-info.json`` - Every processor is listed in this file, and will at least contain a regex pattern to match it's corresponding tag. Most will also define required and optional parameters, these correspond to arguments in the tag's html template.
+- ``processor-info.json`` - Every processor is listed in this file, and will at least contain a class determining whether it is custom or generic, where custom processors will have a pattern to match it's corresponding tag. Most will also define required and optional parameters, these correspond to arguments in the tag's html template.
- ``tests/`` - explained in the Test Suite section further down the page.
@@ -117,7 +117,161 @@ It is important to note that Kordac is not just a Markdown Extension, it is a wr
Creating a New Processor
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-To create a new processor, a good place to start is the `Extension API`_ page of the Python Markdown docs, and you can also read the `source code`_ itself.
+There are two ways distinctly different ways to create a new processor. The simplist way is to make use of the provided generic processors and define the new processor in the ``processor-info.json`` file, while more complex processors reqiure additional source code. Complex processors should be considered when custom functionality is required that cannot be achieved with generic processors.
+
+In all cases new processors should:
+
+- Be thoroughly tested (see the section on :ref:`testing `)
+- Have clear and accurate documentation. See the docs on other processors for the preferred format. Your docs should include:
+
+ - An example of the tag in markdown
+ - Required parameters
+ - Optional parameters
+ - Examples
+ - Examples of overriding the html
+
+We recommend writing documentation and test cases before you even write the processor itself as this will give you a clear idea of how a processor in Kordac should behave.
+
+Generic Processors
+**************************************
+
+There are two types of generic processors:
+
+ - tags (``generic_tag``): which match ``{ }`` in the markdown text replacing with the given html-template.
+ - containers (``generic_container``): which are a pair of tags which capture the content between the tags for the html-template. A generic container's opening tag specifies the arguments, while the closing tag only has the ``end`` argument allowing for the content to contain generic containers.
+
+To create a new processor that uses the generic processors the processor must be added to the ``processor-info.json`` file and an associated html-template must be created.
+
+How to make a JSON Definition
+++++++++++++++++++++++++++++++++++++++
+
+The json description of a generic processor must contain the attributes:
+
+ - ``class``: Either ``generic_tag`` or ``generic_container`` for a generic processor.
+ - ``arguments``: An object describing arguments passed to the tag.
+ - ``template_parameters``: An object describing template parameters.
+ - (Optional) ``template_name``: A custom name for the html-template to use. Defaults to the processor name otherwise.
+
+The ``argument`` parameter is a dictionary (or object) containing argument name, argument-info pairs. Where the argument-info contains the attributes:
+
+ - ``required``: ``true`` if the argument must be set or ``false`` otherwise.
+ - (Optional) ``dependencies``: A list of argument-names that must also be set if this argument is used.
+
+These arguments are transformed for use in the html-template by the ``template_parameters`` attribute. This attribute is similar to the ``argument`` attribute by containing parameter name, parameter-info pairs. Where the parameter-info contains the attributes:
+
+ - ``argument``: The name of the argument to retrieve the value of to use/transform into the parameter value.
+ - (Optional) ``default``: The value the parameter defaults to if the argument is not given otherwise defaults to ``None``.
+ - (Optional) ``transform``: The name of the transform to modify the argument value by or defaults to null for no transformation. The avaliable transforms are detailed below.
+ - (Optional) ``transform_condition``: A function that takes the context after parameters are set but before transformation (The transformations are done in order they appear in the json document). If the function returns ``True`` then the transformation is applied.
+
+For a generic container type processor the ``argument`` of the parameter may be ``content`` which is the captured content between the start and end tags.
+
+The set of currently avaliable transformations for the ``transform`` attribute are:
+
+ - ``str.lower``: Converts the string into a lowercase version.
+ - ``str.upper``: Converts the string into an UPPERCASE version.
+ - ``relative_file_link``: Applies the relative-file-link html-template to the argument.
+
+Examples
+++++++++++++++++++++++++++++++++++++++
+
+A generic tag processor, is a simple single line tag that uses the given arguments as parameters to an html template. An example of a processor that uses the generic tag processor is the :ref:`button-link ` processor which is described in the json as:
+
+.. code-block:: none
+
+ "button-link": {
+ "class": "generic_tag",
+ "arguments": {
+ "link": {
+ "required": true,
+ "dependencies": []
+ },
+ "text": {
+ "required": true,
+ "dependencies": []
+ },
+ "file": {
+ "required": false,
+ "dependencies": []
+ }
+ },
+ "template_parameters": {
+ "file": {
+ "argument": "file",
+ "transform": "str.lower",
+ "default": "no"
+ },
+ "link": {
+ "argument": "link",
+ "transform": "relative_file_link",
+ "transform_condition": "lambda context: context['file'] == 'yes'"
+ },
+ "text": {
+ "argument": "text",
+ "transform": null
+ }
+ }
+ }
+
+And has the following html-template:
+
+.. literalinclude:: ../../kordac/html-templates/button-link.html
+ :language: css+jinja
+
+This enables the following markdown:
+
+.. literalinclude:: ../../kordac/tests/assets/button-link/doc_example_basic_usage.md
+ :language: none
+
+To generate the output:
+
+.. literalinclude:: ../../kordac/tests/assets/button-link/doc_example_basic_usage_expected.html
+ :language: html
+
+A generic container processor, a pair of matching tags where one opens the container and one closes the container. The start tag gives the arguments as parameters to an html template. The end tag is used to capture the content between the tags to be used as an additional parameter to the html template. An example of a processor that uses the generic container processor is the :ref:`boxed-text ` processor which is described in the json as:
+
+.. code-block:: none
+
+ "boxed-text": {
+ "class": "generic_container",
+ "arguments": {
+ "indented": {
+ "required": false,
+ "dependencies": []
+ }
+ },
+ "template_name": "boxed-text",
+ "template_parameters": {
+ "indented": {
+ "argument": "indented",
+ "transform": "str.lower"
+ },
+ "text": {
+ "argument": "content",
+ "transform": null
+ }
+ }
+ }
+
+And has the following html-template:
+
+.. literalinclude:: ../../kordac/html-templates/boxed-text.html
+ :language: css+jinja
+
+This enables the following markdown:
+
+.. literalinclude:: ../../kordac/tests/assets/boxed-text/doc_example_basic_usage.md
+ :language: none
+
+To generate the output:
+
+.. literalinclude:: ../../kordac/tests/assets/boxed-text/doc_example_basic_usage_expected.html
+ :language: html
+
+Custom Processors
+**************************************
+
+To create a custom processor, the ``class`` attribute of the processor in the ``processor-info.json`` file must be ``"custom"``. A good place to start when programming a new processor is the `Extension API`_ page of the Python Markdown docs, and you can also read the `source code`_ itself.
There are several different kinds of processors in Python Markdown, each serving a slightly different purpose. We recommend reading the API docs to determine which processor best suits your purpose. Kordac currently makes use of ``preprocessor``, ``blockprocessor``, ``inlinepattern``, ``treeprocessor`` and ``postprocessor``, but you are welcome to use another type of processor if it better suits the task.
@@ -132,18 +286,7 @@ The logic for each processor belongs in the ``processors/`` directory, and there
- The relevant list in ``extendMarkdown()`` in ``KordacExtension.py`` (see `OrderedDict in the Markdown API docs`_ for manipulating processor order)
- The processor's template should be added to ``html-templates`` using the Jinja2 template engine syntax for variable parameters
-The new processors should also:
-
-- Be thoroughly tested (see the section below)
-- Have clear and accurate documentation. See the docs on other processors for the preferred format. Your docs should include:
- - An example of the tag in markdown
- - Required parameters
- - Optional parameters
- - Examples
- - Examples of overriding the html
-
-We recommend writing documentation and test cases before you even write the processor itself as this will give you a clear idea of how a processor in Kordac should behave.
-
+.. _the-test-suite:
The Test Suite
=======================================
diff --git a/docs/source/processors/boxed-text.rst b/docs/source/processors/boxed-text.rst
index 69441f02..1730b207 100644
--- a/docs/source/processors/boxed-text.rst
+++ b/docs/source/processors/boxed-text.rst
@@ -1,3 +1,5 @@
+.. _boxed-text:
+
Boxed Text
#######################################
diff --git a/docs/source/processors/button-link.rst b/docs/source/processors/button-link.rst
index 797853c5..6365858b 100644
--- a/docs/source/processors/button-link.rst
+++ b/docs/source/processors/button-link.rst
@@ -1,3 +1,5 @@
+.. _button-link:
+
Button Link
#######################################
diff --git a/kordac/Kordac.py b/kordac/Kordac.py
index 18380335..efff8279 100644
--- a/kordac/Kordac.py
+++ b/kordac/Kordac.py
@@ -18,13 +18,14 @@
'scratch'
})
+
class Kordac(object):
- """A converter object for converting markdown
- with complex elements to HTML.
- """
+ '''A converter object for converting markdown with complex elements
+ to HTML.
+ '''
def __init__(self, processors=DEFAULT_PROCESSORS, html_templates={}, extensions=[]):
- """Creates a Kordac object.
+ '''Creates a Kordac object.
Args:
processors: A set of processor names given as strings for which
@@ -37,14 +38,14 @@ def __init__(self, processors=DEFAULT_PROCESSORS, html_templates={}, extensions=
eg: {'image': '
'}
extensions: A list of extra extensions to run on the
markdown package.
- """
+ '''
self.processors = set(processors)
self.html_templates = dict(html_templates)
self.extensions = list(extensions)
self.create_converter()
def create_converter(self):
- """Create the Kordac extension and converter for future use."""
+ '''Create the Kordac extension and converter for future use.'''
self.kordac_extension = KordacExtension(
processors=self.processors,
html_templates=self.html_templates,
@@ -54,7 +55,7 @@ def create_converter(self):
self.converter = markdown.Markdown(extensions=all_extensions)
def convert(self, text):
- """Return a KordacResult object after converting
+ '''Return a KordacResult object after converting
the given markdown string.
Args:
@@ -62,7 +63,7 @@ def convert(self, text):
Returns:
A KordacResult object.
- """
+ '''
self.kordac_extension.clear_saved_data()
html_string = self.converter.convert(text)
result = KordacResult(
@@ -75,61 +76,69 @@ def convert(self, text):
return result
def update_templates(self, html_templates):
- """Update the template dictionary with the given dictionary
+ '''Update the template dictionary with the given dictionary
of templates, while leaving all other HTML templates (including
any custom set templates) untouched. The updated dictionary
will be used for converting from this point onwards.
Args:
html_templates: A dictionary of HTML templates to override
- existing HTML templates for processors. Dictionary contains
- processor names given as a string as keys mapping HTML strings
- as values.
+ existing HTML templates for processors. Dictionary
+ contains processor names given as a string as keys
+ mapping HTML strings as values.
eg: {'image': '
'}
- """
+ '''
self.html_templates.update(html_templates)
self.create_converter()
def clear_templates(self):
- """Set the template dictionary to it's original values."""
+ '''Set the template dictionary to it's original values.
+ '''
self.html_templates = {}
self.create_converter()
@staticmethod
def processor_defaults():
- """Returns a copy of the default processor set.
+ '''Returns a copy of the default processor set.
Returns:
A set of default processor names as strings.
- """
+ '''
return set(DEFAULT_PROCESSORS)
def update_processors(self, processors=DEFAULT_PROCESSORS):
- """Update the processors used for conversion with the given set.
- The updated set will be used for converting from this point
- onwards. If parameter is empty, default processors will be used.
+ '''Update the processors used for conversion with the given
+ set. The updated set will be used for converting from this
+ point onwards. If parameter is empty, default processors will
+ be used.
Args:
- processors: A set of processor names given as strings for which
- their processors are enabled. If given, all other
+ processors: A set of processor names given as strings for
+ which their processors are enabled. If given, all other
processors are skipped.
- """
+ '''
self.processors = set(processors)
self.create_converter()
+
class KordacResult(object):
- """Object created by Kordac containing the result data
+ '''Object created by Kordac containing the result data
after a conversion by run.
- """
+ '''
def __init__(self, html_string, title, required_files, heading_tree, required_glossary_terms):
- """Create a KordacResult object.
+ '''Create a KordacResult object.
Args:
html_string: A string of HTML text.
title: The first heading encountered when converting.
- required_files: Dictionary of required file types to sets of paths.
- """
+ required_files: Dictionary of required file types to sets
+ of paths.
+ heading_tree: A tuple of HeadingNodes which represent the
+ heading structure of the document.
+ required_glossary_terms: A dictionary of glossary terms to
+ a list of tuples containing reference text and slugs.
+ '''
self.html_string = html_string
self.title = title
self.required_files = required_files
diff --git a/kordac/KordacExtension.py b/kordac/KordacExtension.py
index 86da06f9..f6f4b335 100644
--- a/kordac/KordacExtension.py
+++ b/kordac/KordacExtension.py
@@ -1,7 +1,6 @@
from markdown.extensions import Extension
import markdown.util as utils
-from kordac.processors.PanelBlockProcessor import PanelBlockProcessor
from kordac.processors.CommentPreprocessor import CommentPreprocessor
from kordac.processors.VideoBlockProcessor import VideoBlockProcessor
from kordac.processors.ImageBlockProcessor import ImageBlockProcessor
@@ -10,23 +9,21 @@
from kordac.processors.RemoveTitlePreprocessor import RemoveTitlePreprocessor
from kordac.processors.SaveTitlePreprocessor import SaveTitlePreprocessor
from kordac.processors.GlossaryLinkPattern import GlossaryLinkPattern
-from kordac.processors.ButtonLinkBlockProcessor import ButtonLinkBlockProcessor
-from kordac.processors.BoxedTextBlockProcessor import BoxedTextBlockProcessor
from kordac.processors.BeautifyPostprocessor import BeautifyPostprocessor
from kordac.processors.ConditionalProcessor import ConditionalProcessor
from kordac.processors.RemovePostprocessor import RemovePostprocessor
from kordac.processors.JinjaPostprocessor import JinjaPostprocessor
from kordac.processors.HeadingBlockProcessor import HeadingBlockProcessor
-from kordac.processors.FrameBlockProcessor import FrameBlockProcessor
-from kordac.processors.TableOfContentsBlockProcessor import TableOfContentsBlockProcessor
from kordac.processors.ScratchTreeprocessor import ScratchTreeprocessor
from kordac.processors.ScratchCompatibilityPreprocessor import ScratchCompatibilityPreprocessor
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
+from kordac.processors.GenericContainerBlockProcessor import GenericContainerBlockProcessor
from kordac.utils.UniqueSlugify import UniqueSlugify
from kordac.utils.HeadingNode import HeadingNode
from kordac.utils.overrides import is_block_level, BLOCK_LEVEL_ELEMENTS
-from collections import defaultdict
+from collections import defaultdict, OrderedDict
from os import listdir
import os.path
import re
@@ -34,8 +31,26 @@
from jinja2 import Environment, PackageLoader, select_autoescape
+
class KordacExtension(Extension):
+ '''The Kordac markdown extension which enables all the processors,
+ and extracts all the important information to expose externally to
+ the Kordac converter.
+ '''
+
def __init__(self, processors=[], html_templates={}, extensions=[], *args, **kwargs):
+ '''
+ Args:
+ processors: A set of processor names given as strings for which
+ their processors are enabled. If given, all other
+ processors are skipped.
+ html_templates: A dictionary of HTML templates to override
+ existing HTML templates for processors. Dictionary contains
+ processor names given as a string as keys mapping HTML strings
+ as values.
+ eg: {'image': '
'}
+ extensions: A list of extra extensions for compatibility.
+ '''
super().__init__(*args, **kwargs)
self.required_files = defaultdict(set)
self.title = None
@@ -55,50 +70,25 @@ def __init__(self, processors=[], html_templates={}, extensions=[], *args, **kwa
self.compatibility.append('fenced_code_block')
def extendMarkdown(self, md, md_globals):
- preprocessors = [
- ['comment', CommentPreprocessor(self, md), '_begin'],
- ['save-title', SaveTitlePreprocessor(self, md), '_end'],
- ['remove-title', RemoveTitlePreprocessor(self, md), '_end'],
- ]
- blockprocessors = [
- # Markdown overrides
- ['heading', HeadingBlockProcessor(self, md.parser), 'inline' if 'hilite' not in self.compatibility else '[^\\}]*)(?[^\\}]*)}?(?P.*?){glossary-link end\\}",
- "required_parameters": ["term"],
- "optional_parameter_dependencies": {
- "reference-text" :[]
+ "button-link": {
+ "class": "generic_tag",
+ "arguments": {
+ "link": {
+ "required": true,
+ "dependencies": []
+ },
+ "text": {
+ "required": true,
+ "dependencies": []
+ },
+ "file": {
+ "required": false,
+ "dependencies": []
+ }
+ },
+ "template_parameters": {
+ "file": {
+ "argument": "file",
+ "transform": "str.lower",
+ "default": "no"
+ },
+ "link": {
+ "argument": "link",
+ "transform": "relative_file_link",
+ "transform_condition": "lambda context: context['file'] == 'yes'"
+ },
+ "text": {
+ "argument": "text",
+ "transform": null
+ }
}
},
"comment": {
- "pattern" : "\\{comment [^\\}]+\\}"
+ "class": "custom",
+ "pattern": "(^|\\n) *\\{comment [^\\}]+\\} *(\\n|$)",
+ "arguments": {},
+ "template_parameters": {}
},
"conditional": {
- "pattern": "^\\{conditional (?P[^\\}]*(?[^\\}]*)\\}",
- "required_parameters": ["url"],
- "optional_parameter_dependencies": {}
- },
- "image": {
- "pattern": "(\\{image (?P[^\\}]*)\\})",
- "required_parameters": ["file-path"],
- "optional_parameter_dependencies": {
- "alt": [],
- "caption": [],
- "caption-link": ["caption"],
- "source": [],
- "alignment": [],
- "hover-text": []
+ "glossary-link": {
+ "class": "custom",
+ "pattern": "\\{glossary-link ?(?P[^\\}]*)\\}?(?P.*?)\\{glossary-link end\\}",
+ "arguments": {
+ "term": {
+ "required": true,
+ "dependencies": []
+ },
+ "reference-text": {
+ "required": false,
+ "dependencies": []
+ }
}
},
- "interactive": {
- "pattern": "^\\{interactive (?P[^\\}]*)\\}$",
- "required_parameters": ["name", "type"],
- "optional_parameter_dependencies": {
- "text": [],
- "parameters": [],
- "thumbnail": []
- }
+ "heading": {
+ "class": "custom",
+ "pattern": "(^|\\n)(?P#{1,6})(?!#+)\\s?(?P.*?)\\s?#*(\\n|$)"
},
- "title": {
- "pattern": "^#+ ?(.*)"
+ "iframe": {
+ "class": "generic_tag",
+ "arguments": {
+ "link": {
+ "required": true,
+ "dependencies": []
+ }
+ },
+ "template_name": "iframe",
+ "template_parameters": {
+ "link": {
+ "argument": "link",
+ "transform": null
+ }
+ }
},
- "button-link": {
- "pattern": "\\{button-link (?P[^\\}]*)\\}",
- "required_parameters": ["link", "text"],
- "optional_parameter_dependencies": {
- "file": []
+ "image": {
+ "class": "custom",
+ "pattern": "(^|\\n) *\\{image (?P[^\\}]*)\\} *(\\n|$)",
+ "arguments": {
+ "file-path": {
+ "required": true,
+ "dependencies": []
+ },
+ "alt": {
+ "required": false,
+ "dependencies": []
+ },
+ "caption": {
+ "required": false,
+ "dependencies": []
+ },
+ "caption-link": {
+ "required": false,
+ "dependencies": []
+ },
+ "source": {
+ "required": false,
+ "dependencies": []
+ },
+ "alignment": {
+ "required": false,
+ "dependencies": []
+ },
+ "hover-text": {
+ "required": false,
+ "dependencies": []
+ }
}
},
- "boxed-text": {
- "pattern_start": "\\{boxed-text ?(?P[^\\}]*)(?[^\\}]*)\\}",
- "required_parameters": ["link"],
- "optional_parameter_dependencies": {}
+ "panel": {
+ "class": "generic_container",
+ "arguments": {
+ "type": {
+ "required": true
+ },
+ "title": {
+ "required": true
+ },
+ "subtitle": {
+ "required": false
+ },
+ "expanded": {
+ "required": false
+ }
+ },
+ "template_parameters": {
+ "type": {
+ "argument": "type"
+ },
+ "title": {
+ "argument": "title"
+ },
+ "subtitle": {
+ "argument": "subtitle"
+ },
+ "expanded": {
+ "argument": "expanded"
+ },
+ "content": {
+ "argument": "content"
+ }
+ }
},
"table-of-contents": {
- "pattern": "(^|\\n)\\{table-of-contents\\}(\\n|$)"
- },
- "heading": {
- "pattern": "(^|\\n)(?P#{1,6})(?!#+)\\s?(?P.*?)\\s?#*(\\n|$)"
+ "class": "generic_tag",
+ "arguments": {},
+ "template_parameters": {}
},
"relative-link": {
+ "class": "custom",
"pattern": "\\[(?P[^\\]]+)\\]\\((?!(https?|ftps?|mailto|news):)(?P[^\\)]+)\\)"
},
"scratch": {
- "scratch-compatibility": {
- "pattern": "(?P^(?:~{3,}|`{3,}))[ ]*scratch?[ ]*(hl_lines=(?P\"|')(?P.*?)(?P=quot))?[ ]*}?[ ]*\n(?P.*?)(?<=\n)(?P=fence)[ ]*$"
- }
+ "class": "custom",
+ "scratch-compatibility": {
+ "pattern": "(?P^(?:~{3,}|`{3,}))[ ]*scratch?[ ]*(hl_lines=(?P\"|')(?P.*?)(?P=quot))?[ ]*}?[ ]*\n(?P.*?)(?<=\n)(?P=fence)[ ]*$"
+ }
+ },
+ "title": {
+ "class": "custom",
+ "pattern": "^#+ ?(.*)"
+ },
+ "video": {
+ "class": "custom",
+ "pattern": "(^|\\n) *\\{video (?P[^\\}]*)\\} *(\\n|$)",
+ "arguments": {
+ "url": {
+ "required": true
+ }
+ }
}
}
diff --git a/kordac/processors/BeautifyPostprocessor.py b/kordac/processors/BeautifyPostprocessor.py
index 76a88f47..6d5fda22 100644
--- a/kordac/processors/BeautifyPostprocessor.py
+++ b/kordac/processors/BeautifyPostprocessor.py
@@ -4,21 +4,42 @@
from bs4 import BeautifulSoup
import re
+
class BeautifyPostprocessor(Postprocessor):
+ ''' Converts the output document into a more asthetically
+ pleasing version with more consistent whitespace.
+ '''
def __init__(self, *args, **kwargs):
+ ''' Creates a new BeautifyPostprocessor.
+ '''
super().__init__(*args, **kwargs)
self.pre_pattern = re.compile(r'.*?
', re.DOTALL)
self.code_pattern = re.compile(r'(?P.*?)
', re.DOTALL)
self.html_stash = HtmlStash()
def run(self, text):
+ '''Converts the document into a more asthetically
+ pleasing version.
+ Args:
+ text: A string of the document to convert.
+ Returns:
+ A string of the converted document.
+ '''
text = self.store_non_pre_code_tags(text)
text = BeautifulSoup(text, 'html.parser').prettify(formatter="html")
text = self.restore_non_pre_code_tags(text)
return text
def store_non_pre_code_tags(self, text):
+ '''Stores code tags that are not in pre tags to preserve
+ whitespacing, replacing with placeholders.
+
+ Args:
+ text: A string of the document to process.
+ Returns:
+ The document with code blocks replaced with placeholders.
+ '''
prepass_text = text
pre_spans = []
@@ -44,6 +65,15 @@ def store_non_pre_code_tags(self, text):
return out_text
def restore_non_pre_code_tags(self, text):
+ '''Restores code tags that are not in pre tags by replacing
+ placeholders. This is to preserve whitespacing.
+
+ Args:
+ text: A string of the document to process.
+ Returns:
+ The document with placeholders replaced with stored
+ code blocks.
+ '''
replacements = OrderedDict()
for i in range(self.html_stash.html_counter):
html, safe = self.html_stash.rawHtmlBlocks[i]
diff --git a/kordac/processors/BoxedTextBlockProcessor.py b/kordac/processors/BoxedTextBlockProcessor.py
deleted file mode 100644
index 6fa31582..00000000
--- a/kordac/processors/BoxedTextBlockProcessor.py
+++ /dev/null
@@ -1,88 +0,0 @@
-from markdown.blockprocessors import BlockProcessor
-from kordac.processors.utils import blocks_to_string, parse_argument, etree, check_argument_requirements
-import kordac.processors.errors.TagNotMatchedError as TagNotMatchedError
-import re
-
-class BoxedTextBlockProcessor(BlockProcessor):
- def __init__(self, ext, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.processor = 'boxed-text'
- self.p_start = re.compile(ext.processor_info[self.processor]['pattern_start'])
- self.p_end = re.compile(ext.processor_info[self.processor]['pattern_end'])
- self.template = ext.jinja_templates[self.processor]
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
-
- def test(self, parent, block):
- return self.p_start.search(block) is not None or self.p_end.search(block) is not None
-
- def run(self, parent, blocks):
- block = blocks.pop(0)
-
- start_tag = self.p_start.search(block)
- end_tag = self.p_end.search(block)
-
- # Found an end tag without processing a start tag first
- if start_tag is None and end_tag is not None:
- raise TagNotMatchedError(self.processor, block, 'end tag found before start tag')
-
- check_argument_requirements(self.processor, start_tag.group('args'), self.required_parameters, self.optional_parameters)
-
- # Put left overs back on blocks, should be empty though
- blocks.insert(0, block[start_tag.end():])
-
- content_blocks = []
- the_rest = None
- inner_start_tags = 0
- inner_end_tags = 0
-
- # While there is still some work todo
- while len(blocks) > 0:
- block = blocks.pop(0)
-
- # Do we have either a start or end tag
- inner_tag = self.p_start.search(block)
- end_tag = self.p_end.search(block)
-
- # Keep track of how many inner boxed-text start tags we have seen
- if inner_tag:
- inner_start_tags += 1
-
- # If we have an end tag and all inner boxed-text tags have been closed - ~FIN
- if end_tag and inner_start_tags == inner_end_tags:
- content_blocks.append(block[:end_tag.start()])
- the_rest = block[end_tag.end():]
- break
- elif end_tag:
- inner_end_tags += 1
- end_tag = None
- content_blocks.append(block)
-
- if the_rest:
- blocks.insert(0, the_rest) # Keep anything off the end, should be empty though
-
- # Error if we reached the end without closing the start tag
- # or not all inner boxed-text tags were closed
- if end_tag is None or inner_start_tags != inner_end_tags:
- raise TagNotMatchedError(self.processor, block, 'no end tag found to close start tag')
-
- # Parse all the inner content of the boxed-text tags
- content_tree = etree.Element('content')
- self.parser.parseChunk(content_tree, blocks_to_string(content_blocks))
-
- # Convert parsed element tree back into html text for rendering
- content = ''
- for child in content_tree:
- content += etree.tostring(child, encoding="unicode", method="html") + '\n'
-
- # Collect all information for rendering the html template
- context = dict()
- context['indented'] = parse_argument('indented', start_tag.group('args'), 'no').lower() == 'yes'
- context['text'] = content
-
- # Render template and compile into an element
- html_string = self.template.render(context)
- node = etree.fromstring(html_string)
-
- # Update parent with the boxed-text element
- parent.append(node)
diff --git a/kordac/processors/ButtonLinkBlockProcessor.py b/kordac/processors/ButtonLinkBlockProcessor.py
deleted file mode 100644
index d1944e92..00000000
--- a/kordac/processors/ButtonLinkBlockProcessor.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from markdown.blockprocessors import BlockProcessor
-from kordac.processors.utils import parse_argument, check_argument_requirements
-import re
-from markdown.util import etree
-
-class ButtonLinkBlockProcessor(BlockProcessor):
- '''Searches blocks provided by markdown and turns button-link tags e.g. {button-link link="www.example.com" text="Lipsum" file="no"} and replaces them with the html template from the html-template directory.
- '''
- def __init__(self, ext, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.processor = 'button-link'
- self.template = ext.jinja_templates[self.processor]
- self.relative_file_template = ext.jinja_templates['relative-file-link']
- self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
-
- def test(self, parent, block):
- '''Return whether the provided document contains comments needing removal.
-
- Args:
- block: A string to test against.
-
- Returns:
- True if the document needs to be processed.
- '''
- return self.pattern.search(block) is not None
-
- def run(self, parent, blocks):
- ''' Inherited from BlockProcessor class from Markdown.
-
- Args:
- parent: Element (from ElementTree library) which this block resides within. The created html-template elements are placed within here.
- blocks: Blocks of text where the first block matched via the test method.
- '''
- block = blocks.pop(0)
- match = self.pattern.search(block)
-
- arguments = match.group('args')
- check_argument_requirements(self.processor, arguments, self.required_parameters, self.optional_parameters)
-
- context = dict()
- context['link'] = parse_argument('link', arguments)
- context['text'] = parse_argument('text', arguments)
- context['file'] = parse_argument('file', arguments, 'no').lower() == 'yes'
-
- if context['file']:
- context['link'] = self.relative_file_template.render({'file_path': context['link']})
-
- html_string = self.template.render(context)
- node = etree.fromstring(html_string)
- parent.append(node)
diff --git a/kordac/processors/CommentPreprocessor.py b/kordac/processors/CommentPreprocessor.py
index 5e1c4844..15846da1 100644
--- a/kordac/processors/CommentPreprocessor.py
+++ b/kordac/processors/CommentPreprocessor.py
@@ -1,8 +1,10 @@
from markdown.preprocessors import Preprocessor
import re
+
class CommentPreprocessor(Preprocessor):
- '''Searches a Document for comments e.g. {comment example text here} and removes them from the document.
+ ''' Searches a Document for comments (e.g. {comment example text here})
+ and removes them from the document.
'''
def __init__(self, ext, *args, **kwargs):
@@ -11,7 +13,8 @@ def __init__(self, ext, *args, **kwargs):
ext: An instance of the Markdown parser class.
'''
super().__init__(*args, **kwargs)
- self.pattern = re.compile(ext.processor_info['comment']['pattern'])
+ self.processor = 'comment'
+ self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
def test(self, lines):
'''Return whether the provided document contains comments needing removal.
@@ -25,11 +28,12 @@ def test(self, lines):
return self.pattern.search(lines) is not None
def run(self, lines):
- ''' Removes all instances of text that match the following example {comment example text here}. Inherited from Preprocessor class.
+ ''' Removes all instances of text that match the following
+ example {comment example text here}. Inherited from
+ Preprocessor class.
Args:
lines: A list of lines of the Markdown document to be converted.
-
Returns:
Markdown document with comments removed.
'''
diff --git a/kordac/processors/ConditionalProcessor.py b/kordac/processors/ConditionalProcessor.py
index ebc3a625..669edf9e 100644
--- a/kordac/processors/ConditionalProcessor.py
+++ b/kordac/processors/ConditionalProcessor.py
@@ -1,12 +1,14 @@
-from markdown.blockprocessors import BlockProcessor
-from kordac.processors.utils import blocks_to_string, parse_argument, parse_flag, etree, check_argument_requirements
+from kordac.processors.GenericContainerBlockProcessor import GenericContainerBlockProcessor
from kordac.processors.errors.TagNotMatchedError import TagNotMatchedError
+from kordac.processors.utils import etree, parse_arguments, parse_flag, blocks_to_string
from collections import OrderedDict
-import re
-class ConditionalProcessor(BlockProcessor):
- ''' Searches a Document for conditional tags e.g. {conditonal flag conditoon=""}
- The processor matches the following `elif` and `else` statements in the document and parses them via the provided html template.
+
+class ConditionalProcessor(GenericContainerBlockProcessor):
+ ''' Searches a Document for conditional tags e.g.
+ {conditonal flag condition=""} The processor matches
+ the following `elif` and `else` statements in the document and
+ parses them via the provided html template.
'''
def __init__(self, ext, *args, **kwargs):
@@ -14,38 +16,108 @@ def __init__(self, ext, *args, **kwargs):
Args:
ext: An instance of the KordacExtension.
'''
- super().__init__(*args, **kwargs)
- self.processor = 'conditional'
- self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
- self.p_end = re.compile(ext.processor_info[self.processor]['pattern_end'])
-
- self.template = ext.jinja_templates[self.processor]
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
+ super().__init__('conditional', ext, *args, **kwargs)
def test(self, parent, block):
- ''' Tests if the block if it contains any type of conditional types.
+ ''' Tests if the block if it contains any type of conditional
+ types.
Args:
parent: The parent element of the html tree.
blocks: The markdown blocks to until a new tag is found.
-
Returns:
Return true if any conditional tag is found.
'''
- return self.pattern.search(block) is not None or self.p_end.search(block) is not None
+ return (self.p_start.search(block) is not None
+ or self.p_end.search(block) is not None)
+
+ def run(self, parent, blocks):
+ ''' Replaces all conditionals with the given html template.
+ Allows for recursively defined if statements.
+
+ Args:
+ lines: A list of lines of the Markdown document to be
+ converted.
+ Returns:
+ Markdown document with comments removed.
+ Raises:
+ TagNotMatchedError: When a condition tags does not have
+ a matching start tag, or a start tag does not have
+ a matching end tag.
+ '''
+ block = blocks.pop(0)
+ context = dict()
+
+ start_tag = self.p_start.search(block)
+ end_tag = self.p_end.search(block)
+
+ if ((start_tag is None and end_tag is not None)
+ or (start_tag and end_tag and start_tag.end() > end_tag.start())):
+ raise TagNotMatchedError(self.processor, block, 'end tag found before start tag')
+
+ is_if = parse_flag('if', start_tag.group('args'))
+
+ # elif or else before an if conditional
+ if not is_if:
+ is_elif = parse_flag('elif', start_tag.group('args'))
+ is_else = parse_flag('else', start_tag.group('args'))
+ msg = '{} conditional found before if'.format('elif' if is_elif else 'else' if is_else else 'unrecognised')
+ raise TagNotMatchedError(self.processor, block, msg)
+
+ # Put left overs back on blocks, should be empty though
+ if block[:start_tag.start()].strip() != '':
+ self.parser.parseChunk(parent, block[:start_tag.start()])
+ if block[start_tag.end():].strip() != '':
+ blocks.insert(0, block[start_tag.end():])
+
+ # Process if statement
+ argument_values = parse_arguments(self.processor, start_tag.group('args'), self.arguments)
+ if_expression = argument_values['condition']
+ next_tag, block, content_blocks = self.get_content(blocks)
+ if_content = self.parse_blocks(content_blocks)
+
+ context['if_expression'] = if_expression
+ context['if_content'] = if_content
+
+ # Process elif statements
+ elifs = OrderedDict()
+ while next_tag is not None and parse_flag('elif', next_tag.group('args')):
+ argument_values = parse_arguments(self.processor, next_tag.group('args'), self.arguments)
+ elif_expression = argument_values['condition']
+ next_tag, block, content_blocks = self.get_content(blocks)
+ content = self.parse_blocks(content_blocks)
+ elifs[elif_expression] = content
+ context['elifs'] = elifs
+
+ # Process else statement
+ has_else = next_tag is not None and parse_flag('else', next_tag.group('args'))
+ else_content = ''
+ if has_else:
+ argument_values = parse_arguments(self.processor, next_tag.group('args'), self.arguments)
+ next_tag, block, content_blocks = self.get_content(blocks)
+ else_content = self.parse_blocks(content_blocks)
+ context['has_else'] = has_else
+ context['else_content'] = else_content
+
+ # Render template and compile into an element
+ html_string = self.template.render(context)
+ node = etree.fromstring(html_string)
+ parent.append(node)
def get_content(self, blocks):
- ''' Recursively parses blocks into an element tree, returning a string of the output.
+ ''' Recursively parses blocks into an element tree, returning
+ a string of the output.
Args:
blocks: The markdown blocks to until a new tag is found.
Returns:
- The next tag (regex match) the current block (string) and the content of the blocks (list of strings).
+ The next tag (regex match) the current block (string) and
+ the content of the blocks (list of strings).
Raises:
- TagNotMatchedError: When a sibling conditional is not closed.
+ TagNotMatchedError: When a sibling conditional is not
+ closed.
'''
next_tag = None
@@ -60,7 +132,7 @@ def get_content(self, blocks):
block = blocks.pop(0)
# Do we have either a start or end tag
- next_tag = self.pattern.search(block)
+ next_tag = self.p_start.search(block)
end_tag = self.p_end.search(block)
is_if = next_tag is not None and parse_flag('if', next_tag.group('args'))
@@ -76,25 +148,28 @@ def get_content(self, blocks):
inner_end_tags += 1
end_tag = None
elif is_elif or is_else:
- content_blocks.append(block[:next_tag.start()])
+ if block[:next_tag.start()].strip() != '':
+ content_blocks.append(block[:next_tag.start()])
the_rest = block[next_tag.end():]
break
elif end_tag is not None:
- content_blocks.append(block[:end_tag.start()])
+ if block[:end_tag.start()].strip() != '':
+ content_blocks.append(block[:end_tag.start()])
the_rest = block[end_tag.end():]
break
content_blocks.append(block)
- if the_rest:
- blocks.insert(0, the_rest) # Keep anything off the end, should be empty though
+ if the_rest.strip() != '':
+ blocks.insert(0, the_rest) # Keep anything off the end, should be empty though
if inner_if_tags != inner_end_tags:
raise TagNotMatchedError(self.processor, block, 'no end tag found to close start tag')
- return next_tag, block, content_blocks[:-1] if content_blocks[-1].strip() == '' else content_blocks
+ return next_tag, block, content_blocks
def parse_blocks(self, blocks):
- '''Recursively parses blocks into an element tree, returning a string of the output.
+ '''Recursively parses blocks into an element tree,
+ returning a string of the output.
Args:
blocks: The markdown blocks to process.
@@ -111,70 +186,3 @@ def parse_blocks(self, blocks):
for child in content_tree:
content += etree.tostring(child, encoding="unicode", method="html")
return content
-
- def run(self, parent, blocks):
- ''' Removes all instances of text that match the following example {comment example text here}. Inherited from Preprocessor class.
-
- Args:
- lines: A list of lines of the Markdown document to be converted.
-
- Returns:
- Markdown document with comments removed.
- '''
- block = blocks.pop(0)
- context = dict()
-
- start_tag = self.pattern.search(block)
- end_tag = self.p_end.search(block)
-
- # Found an end tag without processing a start tag first
- if start_tag is None and end_tag is not None:
- raise TagNotMatchedError(self.processor, block, 'end tag found before start tag')
-
- is_if = parse_flag('if', start_tag.group('args'))
-
- # elif or else before an if conditional
- if not is_if:
- is_elif = parse_flag('elif', start_tag.group('args'))
- is_else = parse_flag('else', start_tag.group('args'))
- msg = '{} conditional found before if'.format('elif' if is_elif else 'else' if is_else else 'unrecognised')
- raise TagNotMatchedError(self.processor, block, msg)
-
- # Put left overs back on blocks, should be empty though
- blocks.insert(0, block[start_tag.end():])
-
- # Process if statement
- check_argument_requirements(self.processor, start_tag.group('args'), self.required_parameters, self.optional_parameters)
- if_expression = parse_argument('condition', start_tag.group('args'))
- next_tag, block, content_blocks = self.get_content(blocks)
- if_content = self.parse_blocks(content_blocks)
-
- context['if_expression'] = if_expression
- context['if_content'] = if_content
-
- # Process elif statements
- elifs = OrderedDict()
- while next_tag is not None and parse_flag('elif', next_tag.group('args')):
- check_argument_requirements(self.processor, next_tag.group('args'), self.required_parameters, self.optional_parameters)
- elif_expression = parse_argument('condition', next_tag.group('args'))
- blocks.insert(0, block[next_tag.end():])
- next_tag, block, content_blocks = self.get_content(blocks)
- content = self.parse_blocks(content_blocks)
- elifs[elif_expression] = content
- context['elifs'] = elifs
-
- # Process else statement
- has_else = next_tag is not None and parse_flag('else', next_tag.group('args'))
- else_content = ''
- if has_else:
- check_argument_requirements(self.processor, next_tag.group('args'), self.required_parameters, self.optional_parameters)
- blocks.insert(0, block[next_tag.end():])
- next_tag, block, content_blocks = self.get_content(blocks)
- else_content = self.parse_blocks(content_blocks)
- context['has_else'] = has_else
- context['else_content'] = else_content
-
- # Render template and compile into an element
- html_string = self.template.render(context)
- node = etree.fromstring(html_string)
- parent.append(node)
diff --git a/kordac/processors/FrameBlockProcessor.py b/kordac/processors/FrameBlockProcessor.py
deleted file mode 100644
index a4f3acfa..00000000
--- a/kordac/processors/FrameBlockProcessor.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from markdown.blockprocessors import BlockProcessor
-from kordac.processors.utils import blocks_to_string, parse_argument, etree, check_argument_requirements
-import re
-
-class FrameBlockProcessor(BlockProcessor):
- def __init__(self, ext, *args, **kwargs):
- '''
- Args:
- ext: An instance of the Kordac Extension.
- '''
- super().__init__(*args, **kwargs)
- self.processor = 'iframe'
- self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
- self.template = ext.jinja_templates[self.processor]
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
-
- def test(self, parent, block):
- ''' Tests a block to see if the run method should be applied.
-
- Args:
- parent: The parent node of the element tree that children
- will reside in.
- block: The block to be tested.
-
- Returns:
- True if the block matches the pattern regex of a HeadingBlock.
- '''
- return self.pattern.search(block) is not None
-
- def run(self, parent, blocks):
- ''' Processes the block matching the heading and adding to the
- html tree and the kordac heading tree.
-
- Args:
- parent: The parent node of the element tree that children
- will reside in.
- blocks: A list of strings of the document, where the
- first block tests true.
- '''
- block = blocks.pop(0)
-
- match = self.pattern.search(block)
-
- check_argument_requirements(self.processor, match.group('args'), self.required_parameters, self.optional_parameters)
-
- context = dict()
- context['link'] = parse_argument('link', match.group('args'))
-
- html_string = self.template.render(context)
- node = etree.fromstring(html_string)
- parent.append(node)
diff --git a/kordac/processors/GenericContainerBlockProcessor.py b/kordac/processors/GenericContainerBlockProcessor.py
new file mode 100644
index 00000000..f3f0f1b5
--- /dev/null
+++ b/kordac/processors/GenericContainerBlockProcessor.py
@@ -0,0 +1,106 @@
+from markdown.blockprocessors import BlockProcessor
+from kordac.processors.errors.TagNotMatchedError import TagNotMatchedError
+from kordac.processors.utils import etree, parse_arguments, process_parameters, blocks_to_string
+import re
+
+
+class GenericContainerBlockProcessor(BlockProcessor):
+ def __init__(self, processor, ext, *args, **kwargs):
+ '''
+ Args:
+ ext: An instance of the Kordac Extension.
+ '''
+ super().__init__(*args, **kwargs)
+ self.processor = processor
+ self.p_start = re.compile(r'(^|\n) *\{{{0} ?(?P[^\}}]*)(? end_tag.start())):
+ raise TagNotMatchedError(self.processor, block, 'end tag found before start tag')
+
+ before = block[:start_tag.start()]
+ after = block[start_tag.end():]
+
+ if before.strip() != '':
+ self.parser.parseChunk(parent, before)
+ if after.strip() != '':
+ blocks.insert(0, after)
+
+ argument_values = parse_arguments(self.processor, start_tag.group('args'), self.arguments)
+
+ content_blocks = []
+ the_rest = ''
+ inner_start_tags = 0
+ inner_end_tags = 0
+
+ while len(blocks) > 0:
+ block = blocks.pop(0)
+ inner_tag = self.p_start.search(block)
+ end_tag = self.p_end.search(block)
+
+ if ((inner_tag and end_tag is None)
+ or (inner_tag and end_tag and inner_tag.start() < end_tag.end())):
+ inner_start_tags += 1
+
+ if end_tag and inner_start_tags == inner_end_tags:
+ content_blocks.append(block[:end_tag.start()])
+ the_rest = block[end_tag.end():]
+ break
+ elif end_tag:
+ inner_end_tags += 1
+ end_tag = None
+ content_blocks.append(block)
+
+ if the_rest.strip() != '':
+ blocks.insert(0, the_rest)
+
+ if end_tag is None or inner_start_tags != inner_end_tags:
+ raise TagNotMatchedError(self.processor, block, 'no end tag found to close start tag')
+
+ content_tree = etree.Element('content')
+ self.parser.parseChunk(content_tree, blocks_to_string(content_blocks))
+
+ content = ''
+ for child in content_tree:
+ content += etree.tostring(child, encoding="unicode", method="html") + '\n'
+
+ argument_values['content'] = content
+ context = self.process_parameters(self.processor, self.template_parameters, argument_values)
+
+ html_string = self.template.render(context)
+ node = etree.fromstring(html_string)
+ parent.append(node)
diff --git a/kordac/processors/GenericTagBlockProcessor.py b/kordac/processors/GenericTagBlockProcessor.py
new file mode 100644
index 00000000..1414be08
--- /dev/null
+++ b/kordac/processors/GenericTagBlockProcessor.py
@@ -0,0 +1,63 @@
+from markdown.blockprocessors import BlockProcessor
+from kordac.processors.utils import etree, parse_arguments, process_parameters
+import re
+
+
+class GenericTagBlockProcessor(BlockProcessor):
+ ''' A generic processor that matches '{ args}' and replaces
+ with the according html template.
+ '''
+ def __init__(self, processor, ext, *args, **kwargs):
+ '''
+ Args:
+ ext: An instance of the Kordac Extension.
+ '''
+ super().__init__(*args, **kwargs)
+ self.processor = processor
+ self.pattern = re.compile(r'(^|\n) *\{{{0} ?(?P[^\}}]*)\}} *(\n|$)'.format(self.processor))
+ self.arguments = ext.processor_info[self.processor]['arguments']
+ template_name = ext.processor_info.get('template_name', self.processor)
+ self.template = ext.jinja_templates[template_name]
+ self.template_parameters = ext.processor_info[self.processor].get('template_parameters', None)
+ self.process_parameters = lambda processor, parameters, argument_values: \
+ process_parameters(ext, processor, parameters, argument_values)
+
+ def test(self, parent, block):
+ ''' Tests a block to see if the run method should be applied.
+
+ Args:
+ parent: The parent node of the element tree that children
+ will reside in.
+ block: The block to be tested.
+
+ Returns:
+ True if there is a match within the block.
+ '''
+ return self.pattern.search(block) is not None
+
+ def run(self, parent, blocks):
+ ''' Generic run method for single match tags.
+
+ Args:
+ parent: The parent node of the element tree that children
+ will reside in.
+ blocks: A list of strings of the document, where the
+ first block tests true.
+ '''
+ block = blocks.pop(0)
+
+ match = self.pattern.search(block)
+ before = block[:match.start()]
+ after = block[match.end():]
+
+ if before.strip() != '':
+ self.parser.parseChunk(parent, before)
+ if after.strip() != '':
+ blocks.insert(0, after)
+
+ argument_values = parse_arguments(self.processor, match.group('args'), self.arguments)
+ context = self.process_parameters(self.processor, self.template_parameters, argument_values)
+
+ html_string = self.template.render(context)
+ node = etree.fromstring(html_string)
+ parent.append(node)
diff --git a/kordac/processors/GlossaryLinkPattern.py b/kordac/processors/GlossaryLinkPattern.py
index ead1cc35..d3677ee6 100644
--- a/kordac/processors/GlossaryLinkPattern.py
+++ b/kordac/processors/GlossaryLinkPattern.py
@@ -1,11 +1,10 @@
-from kordac.processors.utils import check_argument_requirements, parse_argument
-from markdown.util import etree
-import markdown.inlinepatterns
+from markdown.inlinepatterns import Pattern
+from kordac.processors.utils import etree, parse_arguments
import re
-class GlossaryLinkPattern(markdown.inlinepatterns.Pattern):
- """Return a glossary link element from the given match
+class GlossaryLinkPattern(Pattern):
+ '''Return a glossary link element from the given match
Matches:
{glossary-link term="super-serious-term"}Super Serious Term{glossary-link end}
@@ -15,32 +14,41 @@ class GlossaryLinkPattern(markdown.inlinepatterns.Pattern):
Super Serious Term
- """
+ '''
def __init__(self, ext, *args, **kwargs):
super().__init__(*args, **kwargs)
self.ext = ext
self.processor = 'glossary-link'
self.pattern = self.ext.processor_info['glossary-link']['pattern']
- self.compiled_re = re.compile('^(.*?){}(.*)$'.format(self.pattern), re.DOTALL | re.UNICODE) # TODO raw string prefix
- self.template = self.ext.jinja_templates[self.processor]
- self.required_parameters = self.ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = self.ext.processor_info[self.processor]['optional_parameter_dependencies']
+ self.compiled_re = re.compile(r'^(.*?){}(.*)$'.format(self.pattern), re.DOTALL | re.UNICODE)
+ self.arguments = ext.processor_info[self.processor]['arguments']
+ template_name = ext.processor_info.get('template_name', self.processor)
+ self.template = ext.jinja_templates[template_name]
+
self.ext_glossary_terms = ext.glossary_terms
self.unique_slugify = ext.custom_slugify
def handleMatch(self, match):
-
+ '''
+ Turns a match into a glossary-link and adds the slug and
+ identifier to the extension as part of the final result.
+ Args:
+ match: The string of text where the match was found.
+ Returns:
+ An element tree node to be appended to the html tree.
+ '''
text = match.group('text')
arguments = match.group('args')
- check_argument_requirements(self.processor, arguments, self.required_parameters, self.optional_parameters)
+ argument_values = parse_arguments(self.processor, arguments, self.arguments)
- term = parse_argument('term', arguments)
- reference = parse_argument('reference-text', arguments)
+ term = argument_values['term']
+ reference = argument_values.get('reference-text', None)
- context = dict()
- context['term'] = term
- context['text'] = text
+ context = {
+ 'term': term,
+ 'text': text
+ }
if reference is not None:
identifier = self.unique_slugify('glossary-' + term)
diff --git a/kordac/processors/HeadingBlockProcessor.py b/kordac/processors/HeadingBlockProcessor.py
index ecea8145..67c22dac 100644
--- a/kordac/processors/HeadingBlockProcessor.py
+++ b/kordac/processors/HeadingBlockProcessor.py
@@ -1,8 +1,9 @@
from markdown.blockprocessors import BlockProcessor
from markdown.util import etree
-from kordac.utils.HeadingNode import HeadingNode, DynamicHeadingNode
+from kordac.utils.HeadingNode import DynamicHeadingNode
import re
+
class HeadingBlockProcessor(BlockProcessor):
''' Searches a Document for markdown headings (e.g. # HeadingTitle)
these are then processed into html headings and generates level
@@ -12,10 +13,7 @@ class HeadingBlockProcessor(BlockProcessor):
def __init__(self, ext, *args, **kwargs):
'''
Args:
- ext: The parent node of the element tree that children will
- reside in.
- args: Arguments handed to the super class.
- kwargs: Arguments handed to the super class.
+ ext: The KordacExtension object.
'''
super().__init__(*args, **kwargs)
self.processor = 'heading'
@@ -54,7 +52,7 @@ def run(self, parent, blocks):
'''
block = blocks.pop(0)
match = self.pattern.search(block)
- assert match is not None # If this is true how did we test successfully
+ assert match is not None # If this is true how did we test successfully
before = block[:match.start()]
after = block[match.end():]
@@ -118,7 +116,8 @@ def add_to_heading_tree(self, heading, heading_slug, level):
root_node = root_node.parent
# Update the extension tree
- self.update_ext_tree(tuple(self.roots + [root_node.to_immutable(),]))
+ self.update_ext_tree(tuple(self.roots + [root_node.to_immutable(), ]))
+
class LevelGenerator:
''' Generates a level trail for example (1, 2, 3) which might be
diff --git a/kordac/processors/ImageBlockProcessor.py b/kordac/processors/ImageBlockProcessor.py
index 6461e518..5d5237ca 100644
--- a/kordac/processors/ImageBlockProcessor.py
+++ b/kordac/processors/ImageBlockProcessor.py
@@ -1,49 +1,76 @@
-from markdown.blockprocessors import BlockProcessor
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
+from kordac.processors.utils import etree, parse_arguments
import re
-from kordac.processors.utils import parse_argument, centre_html
-from markdown.util import etree
-from kordac.processors.utils import check_argument_requirements
-import jinja2
-# NTS needs to include alt tags
-class ImageBlockProcessor(BlockProcessor):
+
+class ImageBlockProcessor(GenericTagBlockProcessor):
+ ''' Searches a Document for image tags e.g. {image file-path=""}
+ adding any internal images to the kordac extension final result.
+ '''
def __init__(self, ext, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.processor = 'image'
+ '''
+ Args:
+ ext: The parent node of the element tree that children will
+ reside in.
+ '''
+ super().__init__('image', ext, *args, **kwargs)
self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
- self.template = ext.jinja_templates[self.processor]
self.relative_image_template = ext.jinja_templates['relative-file-link']
self.required = ext.required_files['images']
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
def test(self, parent, block):
+ ''' Tests a block to see if the run method should be applied.
+
+ Args:
+ parent: The parent node of the element tree that children
+ will reside in.
+ block: The block to be tested.
+ Returns:
+ True if the block matches the pattern regex of a HeadingBlock.
+ '''
return self.pattern.search(block) is not None
def run(self, parent, blocks):
+ ''' Processes the block matching the image pattern, adding
+ any internal images to the KordacExtension result.
+
+ Args:
+ parent: The parent node of the element tree that children
+ will reside in.
+ blocks: A list of strings of the document, where the
+ first block tests true.
+ '''
block = blocks.pop(0)
+
match = self.pattern.match(block)
+ before = block[:match.start()]
+ after = block[match.end():]
+
+ if before.strip() != '':
+ self.parser.parseChunk(parent, before)
+ if after.strip() != '':
+ blocks.insert(0, after)
arguments = match.group('args')
- check_argument_requirements(self.processor, arguments, self.required_parameters, self.optional_parameters)
+ argument_values = parse_arguments(self.processor, arguments, self.arguments)
# check if internal or external image
- file_path = parse_argument('file-path', arguments)
+ file_path = argument_values['file-path']
external_path_match = re.search(r'^http', file_path)
- if external_path_match is None: # internal image
+ if external_path_match is None: # internal image
self.required.add(file_path)
file_path = self.relative_image_template.render({'file_path': file_path})
context = dict()
context['file_path'] = file_path
- context['alt'] = parse_argument('alt', arguments)
- context['title'] = parse_argument('title', arguments)
- context['caption'] = parse_argument('caption', arguments)
- context['caption_link'] = parse_argument('caption-link', arguments)
- context['source_link'] = parse_argument('source', arguments)
- context['alignment'] = parse_argument('alignment', arguments)
- context['hover_text'] = parse_argument('hover-text', arguments)
+ context['alt'] = argument_values.get('alt', None)
+ context['title'] = argument_values.get('title', None)
+ context['caption'] = argument_values.get('caption', None)
+ context['caption_link'] = argument_values.get('caption-link', None)
+ context['source_link'] = argument_values.get('source', None)
+ context['alignment'] = argument_values.get('alignment', None)
+ context['hover_text'] = argument_values.get('hover-text', None)
html_string = self.template.render(context)
node = etree.fromstring(html_string)
diff --git a/kordac/processors/InteractiveBlockProcessor.py b/kordac/processors/InteractiveBlockProcessor.py
index 0e71019c..59c936bd 100644
--- a/kordac/processors/InteractiveBlockProcessor.py
+++ b/kordac/processors/InteractiveBlockProcessor.py
@@ -1,14 +1,10 @@
-from markdown.blockprocessors import BlockProcessor
-from markdown.postprocessors import Postprocessor
-from markdown.treeprocessors import Treeprocessor
-from kordac.processors.utils import parse_argument, check_argument_requirements
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
from kordac.processors.errors.InvalidParameterError import InvalidParameterError
-from markdown.util import etree
-
+from kordac.processors.utils import etree, parse_arguments
import re
-import os
-class InteractiveBlockProcessor(BlockProcessor):
+
+class InteractiveBlockProcessor(GenericTagBlockProcessor):
'''Searches a Document for interactive tags:
e.g. {interactive name='example' type='in-page'}
These are then replaced with the html template.
@@ -19,24 +15,18 @@ def __init__(self, ext, *args, **kwargs):
Args:
ext: An instance of the Kordac Extension.
'''
- super().__init__(*args, **kwargs)
- self.processor = 'interactive'
- self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
- self.template = ext.jinja_templates[self.processor]
+ super().__init__('interactive', ext, *args, **kwargs)
self.relative_file_template = ext.jinja_templates['relative-file-link']
self.scripts = ext.required_files["page_scripts"]
self.required = ext.required_files["interactives"]
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
def test(self, parent, block):
''' Tests a block to see if the run method should be applied.
Args:
parent: The parent node of the element tree that children
- will reside in.
+ will reside in.
block: The block to be tested.
-
Returns:
True if the block matches the pattern regex of a HeadingBlock.
'''
@@ -48,20 +38,28 @@ def run(self, parent, blocks):
Args:
parent: The parent node of the element tree that children
- will reside in.
+ will reside in.
blocks: A list of strings of the document, where the
- first block tests true.
+ first block tests true.
'''
block = blocks.pop(0)
+
match = self.pattern.match(block)
+ before = block[:match.start()]
+ after = block[match.end():]
+
+ if before.strip() != '':
+ self.parser.parseChunk(parent, before)
+ if after.strip() != '':
+ blocks.insert(0, after)
arguments = match.group('args')
- check_argument_requirements(self.processor, arguments, self.required_parameters, self.optional_parameters)
+ argument_values = parse_arguments(self.processor, arguments, self.arguments)
- name = parse_argument('name', arguments)
- interactive_type = parse_argument('type', arguments)
- text = parse_argument('text', arguments)
- parameters = parse_argument('parameters', arguments)
+ name = argument_values['name']
+ interactive_type = argument_values['type']
+ text = argument_values.get('text', None)
+ parameters = argument_values.get('parameters', None)
if name is not None and name is '':
raise InvalidParameterError(self.processor, "name", "Name parameter must not be an empty string.")
@@ -70,12 +68,12 @@ def run(self, parent, blocks):
self.scripts.add('interactive/{}/scripts.html'.format(name))
self.required.add(name)
- file_path = parse_argument('thumbnail', arguments)
+ file_path = argument_values.get('thumbnail', None)
if file_path is None:
file_path = "{}/thumbnail.png".format(name)
external_path_match = re.search(r'^http', file_path)
- if external_path_match is None: # internal image
+ if external_path_match is None: # internal image
self.required.add(file_path)
file_path = self.relative_file_template.render({'file_path': file_path})
diff --git a/kordac/processors/JinjaPostprocessor.py b/kordac/processors/JinjaPostprocessor.py
index 2c4853bb..28739eb4 100644
--- a/kordac/processors/JinjaPostprocessor.py
+++ b/kordac/processors/JinjaPostprocessor.py
@@ -2,11 +2,24 @@
from html import unescape
import re
+
class JinjaPostprocessor(Postprocessor):
+ ''' Checks all jinja blocks in the output and ensures that they
+ are not escaped like other html blocks.
+ '''
+
def __init__(self, *args, **kwargs):
+ ''' Creates a new JinjaPostprocessor.
+ '''
super().__init__(*args, **kwargs)
def run(self, text):
+ '''
+ Args:
+ text: A string of the document.
+ Returns:
+ The document text with all Jinja blocks unescaped.
+ '''
r = re.compile(r'{% ([^}])*}(?<= %})')
out = ''
diff --git a/kordac/processors/PanelBlockProcessor.py b/kordac/processors/PanelBlockProcessor.py
deleted file mode 100644
index f439c4e7..00000000
--- a/kordac/processors/PanelBlockProcessor.py
+++ /dev/null
@@ -1,97 +0,0 @@
-from markdown.blockprocessors import BlockProcessor
-from markdown.util import etree
-from kordac.processors.errors.TagNotMatchedError import TagNotMatchedError
-from kordac.processors.utils import blocks_to_string, parse_argument, check_argument_requirements
-import re
-
-
-class PanelBlockProcessor(BlockProcessor):
- def __init__(self, ext, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.processor = 'panel'
- self.p_start = re.compile(ext.processor_info[self.processor]['pattern_start'])
- self.p_end = re.compile(ext.processor_info[self.processor]['pattern_end'])
- self.template = ext.jinja_templates[self.processor]
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
-
- def test(self, parent, block):
- return self.p_start.search(block) is not None or self.p_end.search(block) is not None
-
- def run(self, parent, blocks):
- block = blocks.pop(0)
-
- # find start of match and place back in blocks list up to end of match
- start_tag = self.p_start.search(block)
- end_tag = self.p_end.search(block)
-
- if start_tag is None and end_tag is not None:
- raise TagNotMatchedError(self.processor, block, 'end tag found before start tag')
-
- check_argument_requirements(self.processor, start_tag.group('args'), self.required_parameters, self.optional_parameters)
-
- blocks.insert(0, block[start_tag.end():])
-
- # iterate over blocks until find {panel end} block
- content_blocks = []
- the_rest = None
- inner_start_tags = 0
- inner_end_tags = 0
-
- while len(blocks) > 0:
- block = blocks.pop(0)
-
- # Do we have either a start or end tag print("Here")
- inner_tag = self.p_start.search(block)
- end_tag = self.p_end.search(block)
-
- # Keep track of how many inner boxed-text start tags we have seen
- if inner_tag:
- inner_start_tags += 1
-
- # If we have an end tag and all inner boxed-text tags have been closed - ~FIN
- if end_tag and inner_start_tags == inner_end_tags:
- content_blocks.append(block[:end_tag.start()])
- the_rest = block[end_tag.end():]
- break
- elif end_tag:
- inner_end_tags += 1
- end_tag = None
- content_blocks.append(block)
-
- if the_rest:
- blocks.insert(0, the_rest) # Keep anything off the end, should be empty though
-
- # Error if we reached the end without closing the start tag
- # or not all inner boxed-text tags were closed
- if end_tag is None or inner_start_tags != inner_end_tags:
- raise TagNotMatchedError(self.processor, block, 'no end tag found to close start tag')
-
- # Parse all the inner content of the boxed-text tags
- content_tree = etree.Element('content')
- self.parser.parseChunk(content_tree, blocks_to_string(content_blocks))
-
- # Convert parsed element tree back into html text for rendering
- content = ''
- for child in content_tree:
- content += etree.tostring(child, encoding="unicode", method="html") + '\n'
-
- context = self.get_attributes(start_tag.group('args'))
- context['content'] = content
-
- # create panel node and add it to parent element
- html_string = self.template.render(context)
- node = etree.fromstring(html_string)
- parent.append(node)
-
- def get_attributes(self, args):
- panel_type = parse_argument('type', args)
- title = parse_argument('title', args)
- subtitle = parse_argument('subtitle', args)
- expanded = parse_argument('expanded', args, default='no')
- return {
- 'type': panel_type,
- 'title': title,
- 'subtitle': subtitle,
- 'expanded': expanded
- }
diff --git a/kordac/processors/RelativeLinkPattern.py b/kordac/processors/RelativeLinkPattern.py
index 5cc4f7f9..4d31bd74 100644
--- a/kordac/processors/RelativeLinkPattern.py
+++ b/kordac/processors/RelativeLinkPattern.py
@@ -1,10 +1,10 @@
+from markdown.inlinepatterns import Pattern
from markdown.util import etree
-import markdown.util as util
-import markdown.inlinepatterns
import re
-class RelativeLinkPattern(markdown.inlinepatterns.Pattern):
- """Return a link element from the given match.
+
+class RelativeLinkPattern(Pattern):
+ '''Return a link element from the given match.
Only matches:
- Markdown links using []() syntax.
@@ -15,16 +15,26 @@ class RelativeLinkPattern(markdown.inlinepatterns.Pattern):
- ftps:
- mailto:
- news:
- """
+ '''
def __init__(self, ext, *args, **kwargs):
+ '''
+ Args:
+ ext: An instance of the Markdown class.
+ '''
self.processor = 'relative-link'
self.pattern = ext.processor_info[self.processor]['pattern']
self.compiled_re = re.compile('^(.*?){}(.*)$'.format(self.pattern), re.DOTALL | re.UNICODE)
self.template = ext.jinja_templates[self.processor]
def handleMatch(self, match):
-
+ ''' Inherited from Pattern. Accepts a match and returns an
+ ElementTree element of a internal link.
+ Args:
+ match: The string of text where the match was found.
+ Returns:
+ An element tree node to be appended to the html tree.
+ '''
context = dict()
context['link_path'] = match.group('link_url')
context['text'] = match.group('link_text')
diff --git a/kordac/processors/RemovePostprocessor.py b/kordac/processors/RemovePostprocessor.py
index 960c8827..c696fd1c 100644
--- a/kordac/processors/RemovePostprocessor.py
+++ b/kordac/processors/RemovePostprocessor.py
@@ -1,8 +1,25 @@
from markdown.postprocessors import Postprocessor
+
class RemovePostprocessor(Postprocessor):
+ ''' Parses the output document and removes all remove html tags
+ (i.e. or ) keeping the body of said tags. This
+ allows for the returning of illegal html for further processing.
+ '''
+
def __init__(self, *args, **kwargs):
+ ''' Creates a new RemovePostprocessor.
+ '''
super().__init__(*args, **kwargs)
def run(self, text):
+ '''
+ Deletes removes tags from the text, without changing sibling
+ elements.
+ Args:
+ text: A string of the document.
+ Returns:
+ The document text with all remove tag removed.
+ '''
+ text = text.replace('\n', '').replace('\n', '')
return text.replace('', '').replace('', '')
diff --git a/kordac/processors/RemoveTitlePreprocessor.py b/kordac/processors/RemoveTitlePreprocessor.py
index d71f69ee..8f7ac9e0 100644
--- a/kordac/processors/RemoveTitlePreprocessor.py
+++ b/kordac/processors/RemoveTitlePreprocessor.py
@@ -1,18 +1,39 @@
from markdown.preprocessors import Preprocessor
import re
+
class RemoveTitlePreprocessor(Preprocessor):
+ '''Removes the first found title from the given document.
+ '''
def __init__(self, ext, *args, **kwargs):
+ '''
+ Args:
+ ext: An instance of the KordacExtension.
+ '''
super().__init__(*args, **kwargs)
self.ext = ext
self.pattern = re.compile(ext.processor_info['title']['pattern'])
def test(self, lines):
+ '''Tests the given document to check if the processor should be
+ run.
+
+ Args:
+ lines: A string of the document text.
+ Result:
+ True if a match is found.
+ '''
return self.pattern.search(lines) is not None
def run(self, lines):
- '''If the title is found on a line, remove the line.'''
+ '''If the title is found on a line, remove the line.
+
+ Args:
+ lines: A list of strings that form the document.
+ Returns:
+ The document with the first title removed.
+ '''
title_found = False
for i, line in enumerate(lines):
if not title_found and self.pattern.search(line) is not None:
diff --git a/kordac/processors/SaveTitlePreprocessor.py b/kordac/processors/SaveTitlePreprocessor.py
index 2462ed24..789e8db2 100644
--- a/kordac/processors/SaveTitlePreprocessor.py
+++ b/kordac/processors/SaveTitlePreprocessor.py
@@ -1,17 +1,41 @@
from markdown.preprocessors import Preprocessor
import re
+
class SaveTitlePreprocessor(Preprocessor):
+ ''' Saves the first title found in the document to
+ the KordacExtension as part of the final result.
+ '''
def __init__(self, ext, *args, **kwargs):
+ '''
+ Args:
+ ext: An instance of the KordacExtension.
+ '''
super().__init__(*args, **kwargs)
self.ext = ext
self.pattern = re.compile(ext.processor_info['title']['pattern'])
def test(self, lines):
+ ''' Tests the given document to check if the processor should be
+ run.
+
+ Args:
+ lines: A string of the document text.
+ Result:
+ True if a match is found.
+ '''
return self.pattern.search(lines) is not None
def run(self, lines):
+ ''' Finds the first title and saves it to the
+ KordacExtension for the final result.
+
+ Args:
+ lines: A list of strings that form the document.
+ Returns:
+ The original document.
+ '''
for line in lines:
match = self.pattern.search(line)
if match is not None:
diff --git a/kordac/processors/ScratchCompatibilityPreprocessor.py b/kordac/processors/ScratchCompatibilityPreprocessor.py
index a35b805c..e9f9dbe6 100644
--- a/kordac/processors/ScratchCompatibilityPreprocessor.py
+++ b/kordac/processors/ScratchCompatibilityPreprocessor.py
@@ -1,6 +1,7 @@
from markdown.preprocessors import Preprocessor
import re
+
class ScratchCompatibilityPreprocessor(Preprocessor):
'''Should only be active if using the scratch processor and the
extensions for fenced_code and codehilite. This preprocessor works
diff --git a/kordac/processors/ScratchTreeprocessor.py b/kordac/processors/ScratchTreeprocessor.py
index 0057ea90..51a89bbf 100644
--- a/kordac/processors/ScratchTreeprocessor.py
+++ b/kordac/processors/ScratchTreeprocessor.py
@@ -1,9 +1,8 @@
from markdown.treeprocessors import Treeprocessor
-from kordac.processors.utils import blocks_to_string, parse_argument, etree
-from kordac.processors.errors.TagNotMatchedError import TagNotMatchedError
+from kordac.processors.utils import etree
from collections import namedtuple
from hashlib import sha256
-import re
+
class ScratchImageMetaData(namedtuple('ScratchImageMetaData', 'hash, text')):
''' Represents data required to make a scratch image.
@@ -13,6 +12,7 @@ class ScratchImageMetaData(namedtuple('ScratchImageMetaData', 'hash, text')):
text: text of the scratch code
'''
+
class ScratchTreeprocessor(Treeprocessor):
''' Searches a Document for codeblocks with the scratch language.
These are then processed into the kordac result and hashed for
@@ -24,8 +24,6 @@ def __init__(self, ext, *args, **kwargs):
Args:
ext: The parent node of the element tree that children will
reside in.
- args: Arguments handed to the super class.
- kwargs: Arguments handed to the super class.
'''
super().__init__(*args, **kwargs)
self.processor = 'scratch'
@@ -67,20 +65,20 @@ def process_html(self, node):
'''
children = list(node)
if (len(children) == 1 and children[0].tag == 'code'
- and ((children[0].text.strip().startswith('scratch\n'))
- or ('class' in children[0].attrib.keys() and children[0].attrib['class'] == 'scratch'))):
- content = children[0].text.strip()
- if content.startswith('scratch\n'):
- content = content[len('scratch\n'):]
- content_hash = ScratchTreeprocessor.hash_content(content)
- self.update_required_images(content_hash, content)
- html_string = self.template.render({ 'hash': content_hash })
- new_node = etree.fromstring(html_string)
-
- node.tag = "remove"
- node.text = ""
- node.append(new_node)
- node.remove(children[0])
+ and ((children[0].text.strip().startswith('scratch\n'))
+ or ('class' in children[0].attrib.keys() and children[0].attrib['class'] == 'scratch'))):
+ content = children[0].text.strip()
+ if content.startswith('scratch\n'):
+ content = content[len('scratch\n'):]
+ content_hash = ScratchTreeprocessor.hash_content(content)
+ self.update_required_images(content_hash, content)
+ html_string = self.template.render({'hash': content_hash})
+ new_node = etree.fromstring(html_string)
+
+ node.tag = "remove"
+ node.text = ""
+ node.append(new_node)
+ node.remove(children[0])
@staticmethod
def hash_content(text):
diff --git a/kordac/processors/TableOfContentsBlockProcessor.py b/kordac/processors/TableOfContentsBlockProcessor.py
deleted file mode 100644
index deb859d7..00000000
--- a/kordac/processors/TableOfContentsBlockProcessor.py
+++ /dev/null
@@ -1,47 +0,0 @@
-from markdown.blockprocessors import BlockProcessor
-from markdown.util import etree
-import re
-
-class TableOfContentsBlockProcessor(BlockProcessor):
- def __init__(self, ext, *args, **kwargs):
- '''
- Args:
- ext: The parent node of the element tree that children will
- reside in.
- args: Arguments handed to the super class.
- kwargs: Arguments handed to the super class.
- '''
- super().__init__(*args, **kwargs)
- self.processor = 'table-of-contents'
- self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
- self.template = ext.jinja_templates[self.processor]
-
- def test(self, parent, block):
- ''' Tests a block to see if the run method should be applied.
-
- Args:
- parent: The parent node of the element tree that children
- will reside in.
- block: The block to be tested.
-
- Returns:
- True if the block matches the pattern regex of a HeadingBlock.
- '''
- return self.pattern.search(block) is not None
-
- def run(self, parent, blocks):
- ''' Processes the block matching the heading and adding to the
- html tree and the kordac heading tree.
-
- Args:
- parent: The parent node of the element tree that children
- will reside in.
- blocks: A list of strings of the document, where the
- first block tests true.
- '''
- block = blocks.pop(0)
- match = self.pattern.search(block)
-
- html_string = self.template.render(dict())
- node = etree.fromstring(html_string)
- parent.append(node)
diff --git a/kordac/processors/VideoBlockProcessor.py b/kordac/processors/VideoBlockProcessor.py
index 04727204..151e39a9 100644
--- a/kordac/processors/VideoBlockProcessor.py
+++ b/kordac/processors/VideoBlockProcessor.py
@@ -1,56 +1,61 @@
-from markdown.blockprocessors import BlockProcessor
-from markdown.util import etree
-from kordac.processors.errors.NoSourceLinkError import NoSourceLinkError
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
from kordac.processors.errors.NoVideoIdentifierError import NoVideoIdentifierError
from kordac.processors.errors.UnsupportedVideoPlayerError import UnsupportedVideoPlayerError
-from kordac.processors.utils import parse_argument, check_argument_requirements
+from kordac.processors.utils import etree, parse_arguments
import re
-class VideoBlockProcessor(BlockProcessor):
- '''Searches blocks of markdown text and turns video tags into embeded players
+class VideoBlockProcessor(GenericTagBlockProcessor):
+ ''' Searches blocks of markdown text and turns video tags into
+ embeded players.
'''
def __init__(self, ext, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.processor = 'video'
+ '''
+ Args:
+ ext: An instance of the Kordac Extension.
+ '''
+ super().__init__('video', ext, *args, **kwargs)
self.pattern = re.compile(ext.processor_info[self.processor]['pattern'])
self.youtube_template = ext.jinja_templates['video-youtube']
self.vimeo_template = ext.jinja_templates['video-vimeo']
- self.template = ext.jinja_templates[self.processor]
- self.required_parameters = ext.processor_info[self.processor]['required_parameters']
- self.optional_parameters = ext.processor_info[self.processor]['optional_parameter_dependencies']
+
def test(self, parent, block):
- '''Return whether block contains a video tag
+ ''' Return whether block contains a video tag.
Args:
parent: Element which this block is in.
block: A string of markdown text
-
Returns:
True if a video tag is found
'''
return self.pattern.search(block) is not None
def run(self, parent, blocks):
- '''Replaces all video tags {video url="example"} with embeded video link. Inherited from BlockProcessor class.
+ '''Replaces all video tags {video url="example"} with embeded
+ video link. Inherited from BlockProcessor class.
Args:
parent: Element which this block is in.
- block: A string of markdown text to be converted
-
- Returns:
- html string with embedded videos
+ block: A string of markdown text to be converted.
'''
block = blocks.pop(0)
+
match = self.pattern.search(block)
+ before = block[:match.start()]
+ after = block[match.end():]
+
+ if before.strip() != '':
+ self.parser.parseChunk(parent, before)
+ if after.strip() != '':
+ blocks.insert(0, after)
arguments = match.group('args')
- check_argument_requirements(self.processor, arguments, self.required_parameters, self.optional_parameters)
- url = parse_argument('url', arguments)
+ argument_values = parse_arguments(self.processor, arguments, self.arguments)
+ url = argument_values['url']
- (video_type, identifier) = self.extract_video_identifier(url, match)
+ (video_type, identifier) = self.extract_video_identifier(url)
if not video_type:
raise UnsupportedVideoPlayerError(block, url, 'unsupported video player')
@@ -72,17 +77,21 @@ def run(self, parent, blocks):
node = etree.fromstring(html_string)
parent.append(node)
+ def extract_video_identifier(self, video_url):
+ '''Extracts an identifier and service from a video url.
+ Args:
+ video_url: The input url.
+ Returns:
+ A tuple of the service and video identifier.
+ '''
- def extract_video_identifier(self, video_url, match):
- '''Returns the indentifier from a given URL'''
-
- if re.match('.*?youtu\.{0,1}be(.com){0,1}', video_url) is not None: # is a youtube url
+ if re.match('.*?youtu\.{0,1}be(.com){0,1}', video_url) is not None: # is a youtube url
video_url = re.sub(r'(.*?)(\?rel=0)', r'\g<1>', video_url)
if 'youtu.be' in video_url or 'youtube.com/embed' in video_url:
video_query = video_url.split('/')[-1]
elif 'youtube.com' in video_url:
start_pos = video_url.find('v=') + 2
- end_pos = video_url.find('&');
+ end_pos = video_url.find('&')
if end_pos == -1:
end_pos = len(video_url)
video_query = video_url[start_pos:end_pos]
diff --git a/kordac/processors/__init__.py b/kordac/processors/__init__.py
index 8b137891..9c0fa90a 100644
--- a/kordac/processors/__init__.py
+++ b/kordac/processors/__init__.py
@@ -1 +1 @@
-
+# flake8: noqa
diff --git a/kordac/processors/errors/ArgumentMissingError.py b/kordac/processors/errors/ArgumentMissingError.py
new file mode 100644
index 00000000..811f0086
--- /dev/null
+++ b/kordac/processors/errors/ArgumentMissingError.py
@@ -0,0 +1,18 @@
+from kordac.processors.errors.Error import Error
+
+
+class ArgumentMissingError(Error):
+ '''Exception raised when a custom markdown tag in not matched.
+
+ Attributes:
+ tag: tag which was not matched
+ block: block where tag was not matched
+ argument: the argument that was not found
+ message: explanation of why error was thrown
+ '''
+
+ def __init__(self, tag, argument, message):
+ super().__init__(message)
+ self.tag = tag
+ self.argument = argument
+ self.message = message
diff --git a/kordac/processors/errors/Error.py b/kordac/processors/errors/Error.py
index 5d502365..18c27f93 100644
--- a/kordac/processors/errors/Error.py
+++ b/kordac/processors/errors/Error.py
@@ -1,5 +1,5 @@
class Error(Exception):
- """Base class for Errors.
+ '''Base class for Errors.
(Exceptions from external sources such as inputs).
- """
+ '''
pass
diff --git a/kordac/processors/errors/InvalidParameterError.py b/kordac/processors/errors/InvalidParameterError.py
index cb5066fb..692d0b23 100644
--- a/kordac/processors/errors/InvalidParameterError.py
+++ b/kordac/processors/errors/InvalidParameterError.py
@@ -1,14 +1,15 @@
from kordac.processors.errors.Error import Error
+
class InvalidParameterError(Error):
- """Exception raised when an invalid parameter value is found.
+ '''Exception raised when an invalid parameter value is found.
Attributes:
- tag -- tag which was not matched
- block -- block where tag was not matched
- parameter -- the parameter that was not found
- message -- explanation of why error was thrown
- """
+ tag: tag which was not matched
+ block: block where tag was not matched
+ parameter: the parameter that was not found
+ message: explanation of why error was thrown
+ '''
def __init__(self, tag, parameter, message):
super().__init__(message)
diff --git a/kordac/processors/errors/NoSourceLinkError.py b/kordac/processors/errors/NoSourceLinkError.py
index 787dad1c..a74021d9 100644
--- a/kordac/processors/errors/NoSourceLinkError.py
+++ b/kordac/processors/errors/NoSourceLinkError.py
@@ -1,17 +1,17 @@
from kordac.processors.errors.Error import Error
+
class NoSourceLinkError(Error):
- """Exception raised when no source link is found for a video
+ '''Exception raised when no source link is found for a video
Attributes:
- block -- block where tag was not matched
- url -- original url
- message -- explanation of why error was thrown
- """
+ block: block where tag was not matched
+ url: original url
+ message: explanation of why error was thrown
+ '''
def __init__(self, block, url, message):
super().__init__(message)
self.block = block
self.url = url
self.message = message
-
diff --git a/kordac/processors/errors/NoVideoIdentifierError.py b/kordac/processors/errors/NoVideoIdentifierError.py
index b115b958..2b1d0fc0 100644
--- a/kordac/processors/errors/NoVideoIdentifierError.py
+++ b/kordac/processors/errors/NoVideoIdentifierError.py
@@ -1,17 +1,17 @@
from kordac.processors.errors.Error import Error
+
class NoVideoIdentifierError(Error):
- """Exception raised when no identifier is found for a video
+ '''Exception raised when no identifier is found for a video
Attributes:
- block -- block where tag was not matched
- url -- original url
- message -- explanation of why error was thrown
- """
+ block: block where tag was not matched
+ url: original url
+ message: explanation of why error was thrown
+ '''
def __init__(self, block, url, message):
super().__init__(message)
self.block = block
self.url = url
self.message = message
-
diff --git a/kordac/processors/errors/ParameterMissingError.py b/kordac/processors/errors/ParameterMissingError.py
deleted file mode 100644
index a79fcb6a..00000000
--- a/kordac/processors/errors/ParameterMissingError.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from kordac.processors.errors.Error import Error
-
-class ParameterMissingError(Error):
- """Exception raised when a custom markdown tag in not matched.
-
- Attributes:
- tag -- tag which was not matched
- block -- block where tag was not matched
- parameter -- the parameter that was not found
- message -- explanation of why error was thrown
- """
-
- def __init__(self, tag, parameter, message):
- super().__init__(message)
- self.tag = tag
- self.parameter = parameter
- self.message = message
diff --git a/kordac/processors/errors/TagNotMatchedError.py b/kordac/processors/errors/TagNotMatchedError.py
index 8d9b0029..86d5f8fd 100644
--- a/kordac/processors/errors/TagNotMatchedError.py
+++ b/kordac/processors/errors/TagNotMatchedError.py
@@ -1,13 +1,14 @@
from kordac.processors.errors.Error import Error
+
class TagNotMatchedError(Error):
- """Exception raised when a custom markdown tag in not matched.
+ '''Exception raised when a custom markdown tag in not matched.
Attributes:
- tag -- tag which was not matched
- block -- block where tag was not matched
- message -- explanation of why error was thrown
- """
+ tag: tag which was not matched
+ block: block where tag was not matched
+ message: explanation of why error was thrown
+ '''
def __init__(self, tag, block, message):
super().__init__(message)
diff --git a/kordac/processors/errors/UnsupportedVideoPlayerError.py b/kordac/processors/errors/UnsupportedVideoPlayerError.py
index 750cb4dd..dd3bc4b3 100644
--- a/kordac/processors/errors/UnsupportedVideoPlayerError.py
+++ b/kordac/processors/errors/UnsupportedVideoPlayerError.py
@@ -1,17 +1,17 @@
from kordac.processors.errors.Error import Error
+
class UnsupportedVideoPlayerError(Error):
- """Exception raised when video player is not recognised
+ '''Exception raised when video player is not recognised
Attributes:
- block -- block where tag was not matched
- url -- original url
- message -- explanation of why error was thrown
- """
+ block: block where tag was not matched
+ url: original url
+ message: explanation of why error was thrown
+ '''
def __init__(self, block, url, message):
super().__init__(message)
self.block = block
self.url = url
self.message = message
-
diff --git a/kordac/processors/errors/__init__.py b/kordac/processors/errors/__init__.py
index 8b137891..9c0fa90a 100644
--- a/kordac/processors/errors/__init__.py
+++ b/kordac/processors/errors/__init__.py
@@ -1 +1 @@
-
+# flake8: noqa
diff --git a/kordac/processors/utils.py b/kordac/processors/utils.py
index b49a0a12..1a725fab 100644
--- a/kordac/processors/utils.py
+++ b/kordac/processors/utils.py
@@ -1,10 +1,19 @@
import re
-from markdown.util import etree
-from kordac.processors.errors.ParameterMissingError import ParameterMissingError
+from markdown.util import etree # noqa: F401
+from collections import OrderedDict, defaultdict
+from kordac.processors.errors.ArgumentMissingError import ArgumentMissingError
+
def parse_argument(argument_key, arguments, default=None):
- """Search for the given argument in a string of all arguments
- Returns: Value of an argument as a string if found, otherwise None"""
+ ''' Search for the given argument in a string of all arguments
+
+ Args:
+ argument_key: The name of the argument.
+ arguments: A string of the argument inputs.
+ default: The default value if not found.
+ Returns:
+ Value of an argument as a string if found, otherwise None.
+ '''
result = re.search(r'(^|\s+){}="([^"]*("(?<=\\")[^"]*)*)"'.format(argument_key), arguments)
if result:
argument_value = result.group(2)
@@ -12,9 +21,18 @@ def parse_argument(argument_key, arguments, default=None):
argument_value = default
return argument_value
+
def parse_flag(argument_key, arguments, default=False):
- """Search for the given argument in a string of all arguments
- Returns: Value of an argument as a string if found, otherwise None"""
+ ''' Search for the given argument in a string of all arguments,
+ treating the argument as a flag only.
+
+ Args:
+ argument_key: The name of the argument.
+ arguments: A string of the argument inputs.
+ default: The default value if not found.
+ Returns:
+ Value of an argument as a string if found, otherwise None.
+ '''
result = re.search(r'(^|\s+){}($|\s)'.format(argument_key), arguments)
if result:
argument_value = True
@@ -22,28 +40,106 @@ def parse_flag(argument_key, arguments, default=False):
argument_value = default
return argument_value
-def check_argument_requirements(processor, arguments, required_parameters, optional_parameters):
+
+def parse_arguments(processor, inputs, arguments):
+ ''' Parses the arguments of a given input and ensures
+ they meet the defined requirements.
+
+ Args:
+ processor: The processor of the given arguments.
+ inputs: A string of the arguments from user input.
+ arguments: A dictionary of argument descriptions.
+ Returns:
+ A dictionary of arguments to values.
+ Raises:
+ ArgumentMissingError: If any required arguments are missing or
+ an argument an optional argument is dependent on is missing.
'''
- Raises an error if the arguments are missing any required parameters or a parameter an optional parameter is dependent on.
+ argument_values = defaultdict(None)
+ for argument, argument_info in arguments.items():
+ is_required = argument_info['required']
+ is_arg = parse_argument(argument, inputs, None) is not None
+ is_flag = parse_flag(argument, inputs)
+
+ if is_required and not (is_arg or is_flag):
+ raise ArgumentMissingError(processor, argument, "{} is a required argument.".format(argument))
+ elif not is_required and (is_arg or is_flag):
+ dependencies = argument_info.get('dependencies', [])
+ for other_argument in dependencies:
+ if not (parse_argument(other_argument, inputs, None) is not None
+ or parse_flag(other_argument, inputs) is not None):
+ message = "{} is a required argument because {} exists.".format(other_argument, argument)
+ raise ArgumentMissingError(processor, argument, message)
+
+ if is_flag:
+ argument_values[argument] = True
+ elif is_arg:
+ argument_values[argument] = parse_argument(argument, inputs, None)
+ return argument_values
+
+
+def process_parameters(ext, processor, parameters, argument_values):
'''
- if not all(parse_argument(parameter, arguments, None) is not None for parameter in required_parameters):
- parameter = next(parameter for parameter in required_parameters if parse_argument(parameter, arguments, None) is None)
- raise ParameterMissingError(processor, parameter, "{} is a required parameter.".format(parameter))
+ Processes a given set of arguments by the parameter definitions.
+
+ Args:
+ processor: The processor of the given arguments.
+ parameters: A dictionary of parameter definitions.
+ argument_values: A dictionary of argument to values.
+ Returns:
+ A dictionary of parameter to converted values.
+ '''
+ context = dict()
+ transformations = OrderedDict()
+ for parameter, parameter_info in parameters.items():
+ argument_name = parameter_info['argument']
+ parameter_default = parameter_info.get('default', None)
+ argument_value = argument_values.get(argument_name, parameter_default)
+
+ parameter_value = argument_value
+ if parameter_info.get('transform', None):
+ transform = find_transformation(ext, parameter_info['transform'])
+ if parameter_info.get('transform_condition', None):
+ transformations[parameter] = (eval(parameter_info['transform_condition']), transform)
+ else:
+ transformations[parameter] = (True, transform)
+
+ context[parameter] = parameter_value
+
+ for parameter, (condition, transform) in transformations.items():
+ if context[parameter] is not None:
+ if isinstance(condition, bool) and condition:
+ context[parameter] = transform(context[parameter])
+ if callable(condition) and condition(context):
+ context[parameter] = transform(context[parameter])
+ return context
+
+
+def find_transformation(ext, option):
+ '''
+ Returns a transformation for a given string.
+ TODO:
+ In future should be able to combine piped transformations
+ into a single function.
+
+ Args:
+ option: The desired transformations.
+ Returns:
+ A function of the transformation.
+ '''
+ return {
+ 'str.lower': lambda x: x.lower(),
+ 'str.upper': lambda x: x.upper(),
+ 'relative_file_link': lambda x: ext.jinja_templates['relative-file-link'].render({'file_path': x})
+ }.get(option, None)
- for option, dependencies in optional_parameters.items():
- is_arg = parse_argument(option, arguments, None) is not None
- is_flag = parse_flag(option, arguments)
- if (is_arg or is_flag) and not all(parse_argument(parameter, arguments, None) is not None for parameter in dependencies):
- parameter = next(parameter for parameter in dependencies if parse_argument(parameter, arguments, None) is None)
- raise ParameterMissingError(processor, parameter, "{} is a required parameter because {} exists.".format(parameter, option))
def blocks_to_string(blocks):
- """Returns a string after the blocks have been joined back together."""
- return '\n\n'.join(blocks).rstrip('\n')
+ ''' Returns a string after the blocks have been joined back together.
-def centre_html(node, width):
- """Wraps the given node with HTML to centre using the given number of columns"""
- offset_width = (12 - width) // 2
- root = etree.fromstring(CENTERED_HTML.format(width=width, offset_width=offset_width))
- root.find(".//div[@content]").append(node)
- return root
+ Args:
+ blocks: A list of strings of the document blocks.
+ Returns:
+ A string of the document.
+ '''
+ return '\n\n'.join(blocks).rstrip('\n')
diff --git a/kordac/tests/BoxedTextTest.py b/kordac/tests/BoxedTextTest.py
index 493d457c..923888d2 100644
--- a/kordac/tests/BoxedTextTest.py
+++ b/kordac/tests/BoxedTextTest.py
@@ -2,7 +2,7 @@
from unittest.mock import Mock
from kordac.KordacExtension import KordacExtension
-from kordac.processors.BoxedTextBlockProcessor import BoxedTextBlockProcessor
+from kordac.processors.GenericContainerBlockProcessor import GenericContainerBlockProcessor
from kordac.tests.ProcessorTest import ProcessorTest
@@ -16,12 +16,13 @@ def __init__(self, *args, **kwargs):
self.ext = Mock()
self.ext.processor_info = ProcessorTest.loadProcessorInfo(self)
self.ext.jinja_templates = {self.processor_name: ProcessorTest.loadJinjaTemplate(self, self.processor_name)}
+ self.block_processor = GenericContainerBlockProcessor(self.processor_name, self.ext, Mock())
def test_no_boxed_text(self):
test_string = self.read_test_file(self.processor_name, 'no_boxed_text.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([False, False, False, False], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([False, False, False, False], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'no_boxed_text_expected.html', strip=True)
@@ -31,7 +32,7 @@ def test_single_boxed_text(self):
test_string = self.read_test_file(self.processor_name, 'single_boxed_text.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, False, True], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'single_boxed_text_expected.html', strip=True)
@@ -41,7 +42,7 @@ def test_indented_boxed_text(self):
test_string = self.read_test_file(self.processor_name, 'indented_boxed_text.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'indented_boxed_text_expected.html', strip=True)
@@ -51,7 +52,7 @@ def test_multiple_boxed_text(self):
test_string = self.read_test_file(self.processor_name, 'multiple_boxed_text.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True, False, True, False, True, False], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True, False, True, False, True, False], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'multiple_boxed_text_expected.html', strip=True)
@@ -61,7 +62,7 @@ def test_recursive_boxed_text(self):
test_string = self.read_test_file(self.processor_name, 'recursive_boxed_text.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True, False, True, False, True], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True, False, True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'recursive_boxed_text_expected.html', strip=True)
@@ -75,7 +76,7 @@ def test_doc_example_basic(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, False, True], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage_expected.html', strip=True)
@@ -85,7 +86,7 @@ def test_doc_example_override_html(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_override_html.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [BoxedTextBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
html_template = self.read_test_file(self.processor_name, 'doc_example_override_html_template.html', strip=True)
kordac_extension = KordacExtension([self.processor_name], html_templates={self.processor_name: html_template})
diff --git a/kordac/tests/ButtonLinkTest.py b/kordac/tests/ButtonLinkTest.py
index 7d63eebe..70a943ca 100644
--- a/kordac/tests/ButtonLinkTest.py
+++ b/kordac/tests/ButtonLinkTest.py
@@ -2,7 +2,7 @@
from unittest.mock import Mock
from kordac.KordacExtension import KordacExtension
-from kordac.processors.ButtonLinkBlockProcessor import ButtonLinkBlockProcessor
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
from kordac.tests.ProcessorTest import ProcessorTest
@@ -16,12 +16,13 @@ def __init__(self, *args, **kwargs):
self.ext = Mock()
self.ext.processor_info = ProcessorTest.loadProcessorInfo(self)
self.ext.jinja_templates = {self.processor_name: ProcessorTest.loadJinjaTemplate(self, self.processor_name), 'relative-file-link': ProcessorTest.loadJinjaTemplate(self, 'relative-file-link')}
+ self.block_processor = GenericTagBlockProcessor(self.processor_name, self.ext, Mock())
def test_no_button(self):
test_string = self.read_test_file(self.processor_name, 'no_button.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([False] * 7, [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([False] * 7, [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'no_button_expected.html', strip=True)
self.assertEqual(expected_string, converted_test_string)
@@ -30,7 +31,7 @@ def test_contains_button(self):
test_string = self.read_test_file(self.processor_name, 'contains_button.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([False, True, False], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([False, True, False], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'contains_button_expected.html', strip=True)
@@ -40,7 +41,7 @@ def test_contains_missing_button(self):
test_string = self.read_test_file(self.processor_name, 'missing_end_brace.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([False, False, False], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([False, False, False], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'missing_end_brace_expected.html', strip=True)
@@ -50,7 +51,7 @@ def test_contains_multiple_buttons(self):
test_string = self.read_test_file(self.processor_name, 'contains_multiple_buttons.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([False, True, False, False, True, False, False, True, False, False , True], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([False, True, False, False, True, False, False, True, False, False , True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
@@ -61,7 +62,7 @@ def test_contains_file_link_button(self):
test_string = self.read_test_file(self.processor_name, 'contains_file_link_button.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([False, True, False, True], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([False, True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
@@ -76,7 +77,7 @@ def test_doc_example_basic(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage_expected.html', strip=True)
@@ -86,7 +87,7 @@ def test_doc_example_file(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_file_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_file_usage_expected.html', strip=True)
@@ -96,7 +97,7 @@ def test_doc_example_file(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_file_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_file_usage_expected.html', strip=True)
@@ -106,7 +107,7 @@ def test_doc_example_override_html(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_override_html.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [ButtonLinkBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
html_template = self.read_test_file(self.processor_name, 'doc_example_override_html_template.html', strip=True)
kordac_extension = KordacExtension([self.processor_name], html_templates={self.processor_name: html_template})
diff --git a/kordac/tests/CommentTest.py b/kordac/tests/CommentTest.py
index 6cb140e3..06492a78 100644
--- a/kordac/tests/CommentTest.py
+++ b/kordac/tests/CommentTest.py
@@ -58,7 +58,7 @@ def test_comment_contains_comment(self):
# We expect to match the first closing '}' to enforce simple comments
test_string = self.read_test_file(self.processor_name, 'comment_contains_comment.md')
- self.assertTrue(CommentPreprocessor(self.ext, self.md.parser).test(test_string), msg='"{}"'.format(test_string))
+ self.assertFalse(CommentPreprocessor(self.ext, self.md.parser).test(test_string), msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'comment_contains_comment_expected.html', strip=True)
diff --git a/kordac/tests/FrameTest.py b/kordac/tests/FrameTest.py
index 557eb56f..e891f4dd 100644
--- a/kordac/tests/FrameTest.py
+++ b/kordac/tests/FrameTest.py
@@ -2,27 +2,27 @@
from unittest.mock import Mock
from kordac.KordacExtension import KordacExtension
-from kordac.processors.FrameBlockProcessor import FrameBlockProcessor
-from kordac.processors.errors.ParameterMissingError import ParameterMissingError
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
+from kordac.processors.errors.ArgumentMissingError import ArgumentMissingError
from kordac.tests.ProcessorTest import ProcessorTest
class FrameTest(ProcessorTest):
- """
- """
+
def __init__(self, *args, **kwargs):
ProcessorTest.__init__(self, *args, **kwargs)
self.processor_name = 'iframe'
self.ext = Mock()
self.ext.processor_info = ProcessorTest.loadProcessorInfo(self)
self.ext.jinja_templates = {self.processor_name: ProcessorTest.loadJinjaTemplate(self, self.processor_name)}
+ self.block_processor = GenericTagBlockProcessor(self.processor_name, self.ext, Mock())
def test_example_no_link(self):
test_string = self.read_test_file(self.processor_name, 'example_no_link.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [FrameBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
- with self.assertRaises(ParameterMissingError):
+ with self.assertRaises(ArgumentMissingError):
markdown.markdown(test_string, extensions=[self.kordac_extension])
#~
@@ -33,7 +33,7 @@ def test_doc_example_basic(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [FrameBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage_expected.html', strip=True)
@@ -43,7 +43,7 @@ def test_doc_example_override_html(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_override_html.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [FrameBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
html_template = self.read_test_file(self.processor_name, 'doc_example_override_html_template.html', strip=True)
kordac_extension = KordacExtension([self.processor_name], html_templates={self.processor_name: html_template})
diff --git a/kordac/tests/PanelTest.py b/kordac/tests/PanelTest.py
index 7ebb3eaa..4cadc0ac 100644
--- a/kordac/tests/PanelTest.py
+++ b/kordac/tests/PanelTest.py
@@ -2,7 +2,7 @@
from unittest.mock import Mock
from kordac.KordacExtension import KordacExtension
-from kordac.processors.PanelBlockProcessor import PanelBlockProcessor
+from kordac.processors.GenericContainerBlockProcessor import GenericContainerBlockProcessor
from kordac.processors.errors.TagNotMatchedError import TagNotMatchedError
from kordac.tests.ProcessorTest import ProcessorTest
@@ -15,12 +15,13 @@ def __init__(self, *args, **kwargs):
self.ext = Mock()
self.ext.jinja_templates = {self.processor_name: ProcessorTest.loadJinjaTemplate(self, self.processor_name)}
self.ext.processor_info = ProcessorTest.loadProcessorInfo(self)
+ self.block_processor = GenericContainerBlockProcessor(self.processor_name, self.ext, Mock())
def test_parses_blank(self):
test_string = self.read_test_file(self.processor_name, 'parses_blank.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'parses_blank_expected.html', strip=True)
@@ -30,7 +31,7 @@ def test_parses_no_blank_lines_single_paragraph(self):
test_string = self.read_test_file(self.processor_name, 'parses_no_blank_lines_single_paragraph.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'parses_no_blank_lines_single_paragraph_expected.html', strip=True)
@@ -40,7 +41,7 @@ def test_parses_expanded_panel(self):
test_string = self.read_test_file(self.processor_name, 'parses_expanded_panel.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'parses_expanded_panel_expected.html', strip=True)
@@ -50,7 +51,7 @@ def test_parses_always_expanded_panel(self):
test_string = self.read_test_file(self.processor_name, 'parses_always_expanded_panel.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'parses_always_expanded_panel_expected.html', strip=True)
@@ -60,7 +61,7 @@ def test_parses_blank_lines_multiple_paragraphs(self):
test_string = self.read_test_file(self.processor_name, 'parses_blank_lines_multiple_paragraphs.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, False, False, False, False, False, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, False, False, False, False, False, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'parses_blank_lines_multiple_paragraphs_expected.html', strip=True)
@@ -70,7 +71,7 @@ def test_contains_multiple_panels(self):
test_string = self.read_test_file(self.processor_name, 'contains_multiple_panels.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, False, True, True, False, True, True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, False, True, True, False, True, True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'contains_multiple_panels_expected.html', strip=True)
@@ -80,7 +81,7 @@ def test_contains_inner_panel(self):
test_string = self.read_test_file(self.processor_name, 'contains_inner_panel.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True, False, True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True, False, True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'contains_inner_panel_expected.html', strip=True)
@@ -90,7 +91,7 @@ def test_missing_start_tag(self):
test_string = self.read_test_file(self.processor_name, 'missing_start_tag.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
self.assertRaises(TagNotMatchedError, lambda x: markdown.markdown(x, extensions=[self.kordac_extension]), test_string)
@@ -98,7 +99,7 @@ def test_missing_end_tag(self):
test_string = self.read_test_file(self.processor_name, 'missing_end_tag.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
self.assertRaises(TagNotMatchedError, lambda x: markdown.markdown(x, extensions=[self.kordac_extension]), test_string)
@@ -106,7 +107,7 @@ def test_missing_tag_inner(self):
test_string = self.read_test_file(self.processor_name, 'missing_tag_inner.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
self.assertRaises(TagNotMatchedError, lambda x: markdown.markdown(x, extensions=[self.kordac_extension]), test_string)
@@ -118,7 +119,7 @@ def test_doc_example_basic(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage_expected.html', strip=True)
@@ -128,7 +129,7 @@ def test_doc_example_override_html(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_override_html.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True, False, True], [PanelBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True, False, True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
html_template = self.read_test_file(self.processor_name, 'doc_example_override_html_template.html', strip=True)
kordac_extension = KordacExtension([self.processor_name], html_templates={self.processor_name: html_template})
diff --git a/kordac/tests/TableOfContentsTest.py b/kordac/tests/TableOfContentsTest.py
index fc345aa0..5d3dd5f4 100644
--- a/kordac/tests/TableOfContentsTest.py
+++ b/kordac/tests/TableOfContentsTest.py
@@ -2,7 +2,7 @@
from unittest.mock import Mock
from kordac.KordacExtension import KordacExtension
-from kordac.processors.TableOfContentsBlockProcessor import TableOfContentsBlockProcessor
+from kordac.processors.GenericTagBlockProcessor import GenericTagBlockProcessor
from kordac.tests.ProcessorTest import ProcessorTest
@@ -13,6 +13,7 @@ def __init__(self, *args, **kwargs):
self.ext = Mock()
self.ext.processor_info = ProcessorTest.loadProcessorInfo(self)
self.ext.jinja_templates = {self.processor_name: ProcessorTest.loadJinjaTemplate(self, self.processor_name)}
+ self.block_processor = GenericTagBlockProcessor(self.processor_name, self.ext, Mock())
#~
# Doc Tests
@@ -21,7 +22,7 @@ def test_doc_example_basic(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [TableOfContentsBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
converted_test_string = markdown.markdown(test_string, extensions=[self.kordac_extension])
expected_string = self.read_test_file(self.processor_name, 'doc_example_basic_usage_expected.html', strip=True)
@@ -31,7 +32,7 @@ def test_doc_example_override_html(self):
test_string = self.read_test_file(self.processor_name, 'doc_example_override_html.md')
blocks = self.to_blocks(test_string)
- self.assertListEqual([True], [TableOfContentsBlockProcessor(self.ext, self.md.parser).test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
+ self.assertListEqual([True], [self.block_processor.test(blocks, block) for block in blocks], msg='"{}"'.format(test_string))
html_template = self.read_test_file(self.processor_name, 'doc_example_override_html_template.html', strip=True)
kordac_extension = KordacExtension([self.processor_name], html_templates={self.processor_name: html_template})
diff --git a/kordac/tests/assets/comment/comment_contains_comment_expected.html b/kordac/tests/assets/comment/comment_contains_comment_expected.html
index 284fb04a..40995fc7 100644
--- a/kordac/tests/assets/comment/comment_contains_comment_expected.html
+++ b/kordac/tests/assets/comment/comment_contains_comment_expected.html
@@ -1,3 +1,3 @@
- }
+ {comment explain different views of algorithm (programming context) and Algorithm (that have interesting complexity) {comment explain different views of algorithm (programming context) and Algorithm (that have interesting complexity)}}
diff --git a/kordac/utils/HeadingNode.py b/kordac/utils/HeadingNode.py
index 8528ec51..53a655e7 100644
--- a/kordac/utils/HeadingNode.py
+++ b/kordac/utils/HeadingNode.py
@@ -1,5 +1,6 @@
from collections import namedtuple
+
class HeadingNode(namedtuple('HeadingNode', 'title, title_slug, level, children')):
''' Represents a heading in the heading tree.
@@ -10,6 +11,7 @@ class HeadingNode(namedtuple('HeadingNode', 'title, title_slug, level, children'
children: a tuple of HeadingNodes the level directly below the current node.
'''
+
class DynamicHeadingNode(object):
''' Represents a heading in the heading tree.
diff --git a/kordac/utils/UniqueSlugify.py b/kordac/utils/UniqueSlugify.py
index ec999100..dfb219a8 100644
--- a/kordac/utils/UniqueSlugify.py
+++ b/kordac/utils/UniqueSlugify.py
@@ -1,27 +1,30 @@
from slugify import slugify
from math import log10, floor
+
class UniqueSlugify(object):
''' Wrapper for the python-slugify library enforcing unique slugs
on each successive call.
'''
- def __init__(self, uids=set(), occurance_separator='-', entities=True, decimal=True, hexadecimal=True, max_length=0, word_boundary=False, separator='-', save_order=False, stopwords=()):
- '''
- Args:
- uids: A set of strings which are already taken as slugs.
- Others: Passed directly to slugify.
- '''
- self.uids = set(uids)
- self.occurance_separator = str(occurance_separator)
- self.entities = bool(entities)
- self.decimal = bool(decimal)
- self.hexadecimal = bool(hexadecimal)
- self.max_length = int(max_length)
- self.word_boundary = bool(word_boundary)
- self.separator = str(separator)
- self.save_order = bool(save_order)
- self.stopwords = tuple(stopwords)
+ def __init__(self, uids=set(), occurance_separator='-', entities=True,
+ decimal=True, hexadecimal=True, max_length=0, word_boundary=False,
+ separator='-', save_order=False, stopwords=()):
+ '''
+ Args:
+ uids: A set of strings which are already taken as slugs.
+ Others: Passed directly to slugify.
+ '''
+ self.uids = set(uids)
+ self.occurance_separator = str(occurance_separator)
+ self.entities = bool(entities)
+ self.decimal = bool(decimal)
+ self.hexadecimal = bool(hexadecimal)
+ self.max_length = int(max_length)
+ self.word_boundary = bool(word_boundary)
+ self.separator = str(separator)
+ self.save_order = bool(save_order)
+ self.stopwords = tuple(stopwords)
def __call__(self, text):
'''
@@ -31,14 +34,14 @@ def __call__(self, text):
A string which is a slug (as specified by slugify) that is unique.
'''
slug = slugify(text=text,
- entities=self.entities,
- decimal=self.decimal,
- hexadecimal=self.hexadecimal,
- max_length=self.max_length,
- word_boundary=self.word_boundary,
- separator=self.separator,
- save_order=self.save_order,
- stopwords=self.stopwords)
+ entities=self.entities,
+ decimal=self.decimal,
+ hexadecimal=self.hexadecimal,
+ max_length=self.max_length,
+ word_boundary=self.word_boundary,
+ separator=self.separator,
+ save_order=self.save_order,
+ stopwords=self.stopwords)
count = 1
new_slug = slug
while new_slug in self.uids:
@@ -74,4 +77,4 @@ def clear(self):
'''
Clears the known slugs used for uniqueness comparisons.
'''
- uids = set()
+ self.uids = set()
diff --git a/kordac/utils/overrides.py b/kordac/utils/overrides.py
index 5e1f6944..dd65c898 100644
--- a/kordac/utils/overrides.py
+++ b/kordac/utils/overrides.py
@@ -8,6 +8,7 @@
'pre', 'section', 'table', 'tfoot', 'ul', 'video', 'remove'
] # TODO: TO MAKE CONFIGURABLE
+
def is_block_level(html, block_level_elements):
'''
Checks if the root element (or first element) of the given
@@ -27,7 +28,7 @@ def is_block_level(html, block_level_elements):
if isinstance(tag, string_type):
elements = '|'.join(block_level_elements)
block_elements_re = re.compile("^({})$".format(elements),
- re.IGNORECASE)
+ re.IGNORECASE)
return block_elements_re.match(tag)
return False
return False
diff --git a/requirements.txt b/requirements.txt
index 0283ef9f..dc3c560a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,9 @@
# Required dependencies for Kordac (installed automatically in setup.py)
-markdown>=2.6.8
-bs4>=0.0.1
-Jinja2>=2.9.5
-python-slugify>=1.2.1
+markdown==2.6.8
+beautifulsoup4==4.5.3
+Jinja2==2.9.5
+python-slugify==1.2.1
# Required dependencies for building documentation
-sphinx>=1.5.2
-sphinx_rtd_theme>=0.1.9
+sphinx==1.5.2
+sphinx_rtd_theme==0.1.9