diff options
30 files changed, 311 insertions, 271 deletions
@@ -55,10 +55,6 @@ def build_tests(version=_pyversion[:3]): local('cp -r tests/* build/test.%s/tests' % version) local('cp run-tests.py build/test.%s/run-tests.py' % version) local('cp setup.cfg build/test.%s/setup.cfg' % version) - if version.startswith('3'): - # Do 2to3 conversion - local('2to3-%s -w -d build/test.%s/markdown' % (version, version)) - def generate_test(file): """ Generate a given test. """ diff --git a/markdown/__init__.py b/markdown/__init__.py index aceaf60..068e966 100644 --- a/markdown/__init__.py +++ b/markdown/__init__.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ Python Markdown =============== @@ -22,7 +23,7 @@ Limberg](http://achinghead.com/) and [Artem Yunusov](http://blog.splyer.com). Contact: markdown@freewisdom.org -Copyright 2007-2012 The Python Markdown Project (v. 1.7 and later) +Copyright 2007-2013 The Python Markdown Project (v. 1.7 and later) Copyright 200? Django Software Foundation (OrderedDict implementation) Copyright 2004, 2005, 2006 Yuri Takhteyev (v. 0.2-1.6b) Copyright 2004 Manfred Stienstra (the original version) @@ -30,26 +31,27 @@ Copyright 2004 Manfred Stienstra (the original version) License: BSD (see LICENSE for details). """ -from __version__ import version, version_info +from __future__ import absolute_import +from .__version__ import version, version_info import re import codecs import sys import logging -import util -from preprocessors import build_preprocessors -from blockprocessors import build_block_parser -from treeprocessors import build_treeprocessors -from inlinepatterns import build_inlinepatterns -from postprocessors import build_postprocessors -from extensions import Extension -from serializers import to_html_string, to_xhtml_string +from . import util +from .preprocessors import build_preprocessors +from .blockprocessors import build_block_parser +from .treeprocessors import build_treeprocessors +from .inlinepatterns import build_inlinepatterns +from .postprocessors import build_postprocessors +from .extensions import Extension +from .serializers import to_html_string, to_xhtml_string __all__ = ['Markdown', 'markdown', 'markdownFromFile'] logger = logging.getLogger('MARKDOWN') -class Markdown: +class Markdown(object): """Convert Markdown to HTML.""" doc_tag = "div" # Element used to wrap document - later removed @@ -108,7 +110,7 @@ class Markdown: pos = ['extensions', 'extension_configs', 'safe_mode', 'output_format'] c = 0 for arg in args: - if not kwargs.has_key(pos[c]): + if pos[c] not in kwargs: kwargs[pos[c]] = arg c += 1 if c == len(pos): @@ -120,7 +122,7 @@ class Markdown: setattr(self, option, kwargs.get(option, default)) self.safeMode = kwargs.get('safe_mode', False) - if self.safeMode and not kwargs.has_key('enable_attributes'): + if self.safeMode and 'enable_attributes' not in kwargs: # Disable attributes in safeMode when not explicitly set self.enable_attributes = False @@ -158,7 +160,7 @@ class Markdown: """ for ext in extensions: - if isinstance(ext, basestring): + if isinstance(ext, util.string_type): ext = self.build_extension(ext, configs.get(ext, [])) if isinstance(ext, Extension): ext.extendMarkdown(self, globals()) @@ -198,7 +200,7 @@ class Markdown: module_name_old_style = '_'.join(['mdx', ext_name]) try: # Old style (mdx_<extension>) module = __import__(module_name_old_style) - except ImportError, e: + except ImportError as e: message = "Failed loading extension '%s' from '%s' or '%s'" \ % (ext_name, module_name, module_name_old_style) e.args = (message,) + e.args[1:] @@ -208,7 +210,7 @@ class Markdown: # function called makeExtension() try: return module.makeExtension(configs.items()) - except AttributeError, e: + except AttributeError as e: message = e.args[0] message = "Failed to initiate extension " \ "'%s': %s" % (ext_name, message) @@ -238,7 +240,7 @@ class Markdown: self.output_format = format.lower() try: self.serializer = self.output_formats[self.output_format] - except KeyError, e: + except KeyError as e: valid_formats = self.output_formats.keys() valid_formats.sort() message = 'Invalid Output Format: "%s". Use one of %s.' \ @@ -272,11 +274,11 @@ class Markdown: # Fixup the source text if not source.strip(): - return u"" # a blank unicode string + return '' # a blank unicode string try: - source = unicode(source) - except UnicodeDecodeError, e: + source = util.text_type(source) + except UnicodeDecodeError as e: # Customise error message while maintaining original trackback e.reason += '. -- Note: Markdown only accepts unicode input!' raise @@ -341,7 +343,7 @@ class Markdown: # Read the source if input: - if isinstance(input, str): + if isinstance(input, util.string_type): input_file = codecs.open(input, mode="r", encoding=encoding) else: input_file = codecs.getreader(encoding)(input) @@ -349,7 +351,7 @@ class Markdown: input_file.close() else: text = sys.stdin.read() - if not isinstance(text, unicode): + if not isinstance(text, util.text_type): text = text.decode(encoding) text = text.lstrip('\ufeff') # remove the byte-order mark @@ -359,7 +361,7 @@ class Markdown: # Write to file or stdout if output: - if isinstance(output, str): + if isinstance(output, util.string_type): output_file = codecs.open(output, "w", encoding=encoding, errors="xmlcharrefreplace") @@ -428,7 +430,7 @@ def markdownFromFile(*args, **kwargs): pos = ['input', 'output', 'extensions', 'encoding'] c = 0 for arg in args: - if not kwargs.has_key(pos[c]): + if pos[c] not in kwargs: kwargs[pos[c]] = arg c += 1 if c == len(pos): diff --git a/markdown/blockparser.py b/markdown/blockparser.py index 9b59a97..4504a16 100644 --- a/markdown/blockparser.py +++ b/markdown/blockparser.py @@ -1,6 +1,7 @@ - -import util -import odict +from __future__ import unicode_literals +from __future__ import absolute_import +from . import util +from . import odict class State(list): """ Track the current and nested state of the parser. diff --git a/markdown/blockprocessors.py b/markdown/blockprocessors.py index 8b41a37..a681d6c 100644 --- a/markdown/blockprocessors.py +++ b/markdown/blockprocessors.py @@ -1,21 +1,21 @@ -""" -CORE MARKDOWN BLOCKPARSER -============================================================================= - -This parser handles basic parsing of Markdown blocks. It doesn't concern itself -with inline elements such as **bold** or *italics*, but rather just catches -blocks, lists, quotes, etc. - -The BlockParser is made up of a bunch of BlockProssors, each handling a -different type of block. Extensions may add/replace/remove BlockProcessors -as they need to alter how markdown blocks are parsed. - -""" - +from __future__ import unicode_literals +# CORE MARKDOWN BLOCKPARSER +# =========================================================================== +# +# This parser handles basic parsing of Markdown blocks. It doesn't concern itself +# with inline elements such as **bold** or *italics*, but rather just catches +# blocks, lists, quotes, etc. +# +# The BlockParser is made up of a bunch of BlockProssors, each handling a +# different type of block. Extensions may add/replace/remove BlockProcessors +# as they need to alter how markdown blocks are parsed. + +from __future__ import absolute_import +from __future__ import division import logging import re -import util -from blockparser import BlockParser +from . import util +from .blockparser import BlockParser logger = logging.getLogger('MARKDOWN') diff --git a/markdown/extensions/__init__.py b/markdown/extensions/__init__.py index 0222c91..960d8f9 100644 --- a/markdown/extensions/__init__.py +++ b/markdown/extensions/__init__.py @@ -1,9 +1,10 @@ +from __future__ import unicode_literals """ Extensions ----------------------------------------------------------------------------- """ -class Extension: +class Extension(object): """ Base class for extensions to subclass. """ def __init__(self, configs = {}): """Create an instance of an Extention. @@ -46,6 +47,6 @@ class Extension: * md_globals: Global variables in the markdown module namespace. """ - raise NotImplementedError, 'Extension "%s.%s" must define an "extendMarkdown"' \ - 'method.' % (self.__class__.__module__, self.__class__.__name__) + raise NotImplementedError('Extension "%s.%s" must define an "extendMarkdown"' \ + 'method.' % (self.__class__.__module__, self.__class__.__name__)) diff --git a/markdown/extensions/abbr.py b/markdown/extensions/abbr.py index 45663c0..76a08cd 100644 --- a/markdown/extensions/abbr.py +++ b/markdown/extensions/abbr.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals ''' Abbreviation Extension for Python-Markdown ========================================== @@ -23,14 +24,17 @@ Copyright 2007-2008 ''' +from __future__ import absolute_import +from . import Extension +from ..preprocessors import Preprocessor +from ..inlinepatterns import Pattern +from ..util import etree import re -import markdown -from markdown.util import etree # Global Vars ABBR_REF_RE = re.compile(r'[*]\[(?P<abbr>[^\]]*)\][ ]?:\s*(?P<title>.*)') -class AbbrExtension(markdown.Extension): +class AbbrExtension(Extension): """ Abbreviation Extension for Python-Markdown. """ def extendMarkdown(self, md, md_globals): @@ -38,7 +42,7 @@ class AbbrExtension(markdown.Extension): md.preprocessors.add('abbr', AbbrPreprocessor(md), '<reference') -class AbbrPreprocessor(markdown.preprocessors.Preprocessor): +class AbbrPreprocessor(Preprocessor): """ Abbreviation Preprocessor - parse text for abbr references. """ def run(self, lines): @@ -75,11 +79,11 @@ class AbbrPreprocessor(markdown.preprocessors.Preprocessor): return r'(?P<abbr>\b%s\b)' % (r''.join(chars)) -class AbbrPattern(markdown.inlinepatterns.Pattern): +class AbbrPattern(Pattern): """ Abbreviation inline pattern. """ def __init__(self, pattern, title): - markdown.inlinepatterns.Pattern.__init__(self, pattern) + super(AbbrPattern, self).__init__(pattern) self.title = title def handleMatch(self, m): @@ -90,7 +94,3 @@ class AbbrPattern(markdown.inlinepatterns.Pattern): def makeExtension(configs=None): return AbbrExtension(configs=configs) - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/markdown/extensions/admonition.py b/markdown/extensions/admonition.py index 122eb87..98dcb3a 100644 --- a/markdown/extensions/admonition.py +++ b/markdown/extensions/admonition.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python - +from __future__ import unicode_literals """ Admonition extension for Python-Markdown ======================================== @@ -42,12 +41,14 @@ By [Tiago Serafim](http://www.tiagoserafim.com/). """ +from __future__ import absolute_import +from . import Extension +from ..blockprocessors import BlockProcessor +from ..util import etree import re -import markdown -from markdown.util import etree -class AdmonitionExtension(markdown.Extension): +class AdmonitionExtension(Extension): """ Admonition extension for Python-Markdown. """ def extendMarkdown(self, md, md_globals): @@ -59,7 +60,7 @@ class AdmonitionExtension(markdown.Extension): '_begin') -class AdmonitionProcessor(markdown.blockprocessors.BlockProcessor): +class AdmonitionProcessor(BlockProcessor): CLASSNAME = 'admonition' CLASSNAME_TITLE = 'admonition-title' @@ -84,7 +85,7 @@ class AdmonitionProcessor(markdown.blockprocessors.BlockProcessor): if m: klass, title = self.get_class_and_title(m) div = etree.SubElement(parent, 'div') - div.set('class', u'%s %s' % (self.CLASSNAME, klass)) + div.set('class', '%s %s' % (self.CLASSNAME, klass)) if title: p = etree.SubElement(div, 'p') p.text = title diff --git a/markdown/extensions/attr_list.py b/markdown/extensions/attr_list.py index 3a79d85..d0d4873 100644 --- a/markdown/extensions/attr_list.py +++ b/markdown/extensions/attr_list.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ Attribute List Extension for Python-Markdown ============================================ @@ -18,9 +19,11 @@ Dependencies: """ -import markdown +from __future__ import absolute_import +from . import Extension +from ..treeprocessors import Treeprocessor +from ..util import isBlockLevel import re -from markdown.util import isBlockLevel try: Scanner = re.Scanner @@ -41,9 +44,9 @@ def _handle_key_value(s, t): def _handle_word(s, t): if t.startswith('.'): - return u'.', t[1:] + return '.', t[1:] if t.startswith('#'): - return u'id', t[1:] + return 'id', t[1:] return t, t _scanner = Scanner([ @@ -61,7 +64,7 @@ def get_attrs(str): def isheader(elem): return elem.tag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] -class AttrListTreeprocessor(markdown.treeprocessors.Treeprocessor): +class AttrListTreeprocessor(Treeprocessor): BASE_RE = r'\{\:?([^\}]*)\}' HEADER_RE = re.compile(r'[ ]*%s[ ]*$' % BASE_RE) @@ -128,7 +131,7 @@ class AttrListTreeprocessor(markdown.treeprocessors.Treeprocessor): return self.NAME_RE.sub('_', name) -class AttrListExtension(markdown.extensions.Extension): +class AttrListExtension(Extension): def extendMarkdown(self, md, md_globals): md.treeprocessors.add('attr_list', AttrListTreeprocessor(md), '>prettify') diff --git a/markdown/extensions/codehilite.py b/markdown/extensions/codehilite.py index fd8e2f4..89c0251 100644 --- a/markdown/extensions/codehilite.py +++ b/markdown/extensions/codehilite.py @@ -1,5 +1,4 @@ -#!/usr/bin/python - +from __future__ import unicode_literals """ CodeHilite Extension for Python-Markdown ======================================== @@ -20,7 +19,9 @@ Dependencies: """ -import markdown +from __future__ import absolute_import +from . import Extension +from ..treeprocessors import Treeprocessor import warnings try: from pygments import highlight @@ -31,7 +32,7 @@ except ImportError: pygments = False # ------------------ The Main CodeHilite Class ---------------------- -class CodeHilite: +class CodeHilite(object): """ Determine language of source code, and pass it into the pygments hilighter. @@ -167,7 +168,7 @@ class CodeHilite: # ------------------ The Markdown Extension ------------------------------- -class HiliteTreeprocessor(markdown.treeprocessors.Treeprocessor): +class HiliteTreeprocessor(Treeprocessor): """ Hilight source code in code blocks. """ def run(self, root): @@ -193,7 +194,7 @@ class HiliteTreeprocessor(markdown.treeprocessors.Treeprocessor): block.text = placeholder -class CodeHiliteExtension(markdown.Extension): +class CodeHiliteExtension(Extension): """ Add source code hilighting to markdown codeblocks. """ def __init__(self, configs): diff --git a/markdown/extensions/def_list.py b/markdown/extensions/def_list.py index 382445c..cd9dc9c 100644 --- a/markdown/extensions/def_list.py +++ b/markdown/extensions/def_list.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +from __future__ import unicode_literals """ Definition List Extension for Python-Markdown ============================================= @@ -19,12 +19,14 @@ Copyright 2008 - [Waylan Limberg](http://achinghead.com) """ +from __future__ import absolute_import +from . import Extension +from ..blockprocessors import BlockProcessor, ListIndentProcessor +from ..util import etree import re -import markdown -from markdown.util import etree -class DefListProcessor(markdown.blockprocessors.BlockProcessor): +class DefListProcessor(BlockProcessor): """ Process Definition Lists. """ RE = re.compile(r'(^|\n)[ ]{0,3}:[ ]{1,3}(.*?)(\n|$)') @@ -85,7 +87,7 @@ class DefListProcessor(markdown.blockprocessors.BlockProcessor): if theRest: blocks.insert(0, theRest) -class DefListIndentProcessor(markdown.blockprocessors.ListIndentProcessor): +class DefListIndentProcessor(ListIndentProcessor): """ Process indented children of definition list items. """ ITEM_TYPES = ['dd'] @@ -98,7 +100,7 @@ class DefListIndentProcessor(markdown.blockprocessors.ListIndentProcessor): -class DefListExtension(markdown.Extension): +class DefListExtension(Extension): """ Add definition lists to Markdown. """ def extendMarkdown(self, md, md_globals): diff --git a/markdown/extensions/extra.py b/markdown/extensions/extra.py index ba646f5..5b8876c 100644 --- a/markdown/extensions/extra.py +++ b/markdown/extensions/extra.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +from __future__ import unicode_literals """ Python-Markdown Extra Extension =============================== @@ -27,7 +27,8 @@ when you upgrade to any future version of Python-Markdown. """ -import markdown +from __future__ import absolute_import +from . import Extension extensions = ['smart_strong', 'fenced_code', @@ -39,7 +40,7 @@ extensions = ['smart_strong', ] -class ExtraExtension(markdown.Extension): +class ExtraExtension(Extension): """ Add various extensions to Markdown class.""" def extendMarkdown(self, md, md_globals): diff --git a/markdown/extensions/fenced_code.py b/markdown/extensions/fenced_code.py index 76d644f..91c59ee 100644 --- a/markdown/extensions/fenced_code.py +++ b/markdown/extensions/fenced_code.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python - +from __future__ import unicode_literals """ Fenced Code Extension for Python Markdown ========================================= @@ -75,9 +74,11 @@ Dependencies: """ +from __future__ import absolute_import +from . import Extension +from ..preprocessors import Preprocessor +from .codehilite import CodeHilite, CodeHiliteExtension import re -import markdown -from markdown.extensions.codehilite import CodeHilite, CodeHiliteExtension # Global vars FENCED_BLOCK_RE = re.compile( \ @@ -87,7 +88,7 @@ FENCED_BLOCK_RE = re.compile( \ CODE_WRAP = '<pre><code%s>%s</code></pre>' LANG_TAG = ' class="%s"' -class FencedCodeExtension(markdown.Extension): +class FencedCodeExtension(Extension): def extendMarkdown(self, md, md_globals): """ Add FencedBlockPreprocessor to the Markdown instance. """ @@ -98,10 +99,10 @@ class FencedCodeExtension(markdown.Extension): ">normalize_whitespace") -class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor): +class FencedBlockPreprocessor(Preprocessor): def __init__(self, md): - markdown.preprocessors.Preprocessor.__init__(self, md) + super(FencedBlockPreprocessor, self).__init__(md) self.checked_for_codehilite = False self.codehilite_conf = {} @@ -158,8 +159,3 @@ class FencedBlockPreprocessor(markdown.preprocessors.Preprocessor): def makeExtension(configs=None): return FencedCodeExtension(configs=configs) - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/markdown/extensions/footnotes.py b/markdown/extensions/footnotes.py index 0a0ddea..b7ebc35 100644 --- a/markdown/extensions/footnotes.py +++ b/markdown/extensions/footnotes.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ ========================= FOOTNOTES ================================= @@ -23,16 +24,22 @@ Example: """ +from __future__ import absolute_import +from . import Extension +from ..preprocessors import Preprocessor +from ..inlinepatterns import Pattern +from ..treeprocessors import Treeprocessor +from ..postprocessors import Postprocessor +from ..util import etree, text_type +from ..odict import OrderedDict import re -import markdown -from markdown.util import etree FN_BACKLINK_TEXT = "zz1337820767766393qq" NBSP_PLACEHOLDER = "qq3936677670287331zz" DEF_RE = re.compile(r'[ ]{0,3}\[\^([^\]]*)\]:\s*(.*)') TABBED_RE = re.compile(r'((\t)|( ))(.*)') -class FootnoteExtension(markdown.Extension): +class FootnoteExtension(Extension): """ Footnote Extension. """ def __init__ (self, configs): @@ -83,7 +90,7 @@ class FootnoteExtension(markdown.Extension): def reset(self): """ Clear the footnotes on reset, and prepare for a distinct document. """ - self.footnotes = markdown.odict.OrderedDict() + self.footnotes = OrderedDict() self.unique_prefix += 1 def findFootnotesPlaceholder(self, root): @@ -155,7 +162,7 @@ class FootnoteExtension(markdown.Extension): return div -class FootnotePreprocessor(markdown.preprocessors.Preprocessor): +class FootnotePreprocessor(Preprocessor): """ Find all footnote references and store for later use. """ def __init__ (self, footnotes): @@ -246,11 +253,11 @@ class FootnotePreprocessor(markdown.preprocessors.Preprocessor): return items, i -class FootnotePattern(markdown.inlinepatterns.Pattern): +class FootnotePattern(Pattern): """ InlinePattern for footnote markers in a document's body text. """ def __init__(self, pattern, footnotes): - markdown.inlinepatterns.Pattern.__init__(self, pattern) + super(FootnotePattern, self).__init__(pattern) self.footnotes = footnotes def handleMatch(self, m): @@ -263,13 +270,13 @@ class FootnotePattern(markdown.inlinepatterns.Pattern): if self.footnotes.md.output_format not in ['html5', 'xhtml5']: a.set('rel', 'footnote') # invalid in HTML5 a.set('class', 'footnote-ref') - a.text = unicode(self.footnotes.footnotes.index(id) + 1) + a.text = text_type(self.footnotes.footnotes.index(id) + 1) return sup else: return None -class FootnoteTreeprocessor(markdown.treeprocessors.Treeprocessor): +class FootnoteTreeprocessor(Treeprocessor): """ Build and append footnote div to end of document. """ def __init__ (self, footnotes): @@ -291,7 +298,7 @@ class FootnoteTreeprocessor(markdown.treeprocessors.Treeprocessor): else: root.append(footnotesDiv) -class FootnotePostprocessor(markdown.postprocessors.Postprocessor): +class FootnotePostprocessor(Postprocessor): """ Replace placeholders with html entities. """ def __init__(self, footnotes): self.footnotes = footnotes diff --git a/markdown/extensions/headerid.py b/markdown/extensions/headerid.py index cf3df17..33d7d90 100644 --- a/markdown/extensions/headerid.py +++ b/markdown/extensions/headerid.py @@ -1,5 +1,4 @@ -#!/usr/bin/python - +from __future__ import unicode_literals """ HeaderID Extension for Python-Markdown ====================================== @@ -76,7 +75,9 @@ Dependencies: """ -import markdown +from __future__ import absolute_import +from . import Extension +from ..treeprocessors import Treeprocessor import re import logging import unicodedata @@ -120,7 +121,7 @@ def itertext(elem): yield e.tail -class HeaderIdTreeprocessor(markdown.treeprocessors.Treeprocessor): +class HeaderIdTreeprocessor(Treeprocessor): """ Assign IDs to headers. """ IDs = set() @@ -135,7 +136,7 @@ class HeaderIdTreeprocessor(markdown.treeprocessors.Treeprocessor): if "id" in elem.attrib: id = elem.get('id') else: - id = slugify(u''.join(itertext(elem)), sep) + id = slugify(''.join(itertext(elem)), sep) elem.set('id', unique(id, self.IDs)) if start_level: level = int(elem.tag[-1]) + start_level @@ -149,9 +150,9 @@ class HeaderIdTreeprocessor(markdown.treeprocessors.Treeprocessor): level = int(self.config['level']) - 1 force = self._str2bool(self.config['forceid']) if hasattr(self.md, 'Meta'): - if self.md.Meta.has_key('header_level'): + if 'header_level' in self.md.Meta: level = int(self.md.Meta['header_level'][0]) - 1 - if self.md.Meta.has_key('header_forceid'): + if 'header_forceid' in self.md.Meta: force = self._str2bool(self.md.Meta['header_forceid'][0]) return level, force @@ -165,7 +166,7 @@ class HeaderIdTreeprocessor(markdown.treeprocessors.Treeprocessor): return default -class HeaderIdExtension (markdown.Extension): +class HeaderIdExtension(Extension): def __init__(self, configs): # set defaults self.config = { @@ -196,8 +197,3 @@ class HeaderIdExtension (markdown.Extension): def makeExtension(configs=None): return HeaderIdExtension(configs=configs) - -if __name__ == "__main__": - import doctest - doctest.testmod() - diff --git a/markdown/extensions/html_tidy.py b/markdown/extensions/html_tidy.py index 80272a3..e5f060c 100644 --- a/markdown/extensions/html_tidy.py +++ b/markdown/extensions/html_tidy.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python - +from __future__ import unicode_literals """ HTML Tidy Extension for Python-Markdown ======================================= @@ -28,13 +27,16 @@ Dependencies: """ -import markdown +from __future__ import absolute_import +from . import Extension +from ..postprocessors import Postprocessor +from ..util import text_type try: import tidy except ImportError: tidy = None -class TidyExtension(markdown.Extension): +class TidyExtension(Extension): def __init__(self, configs): # Set defaults to match typical markdown behavior. @@ -54,13 +56,13 @@ class TidyExtension(markdown.Extension): md.postprocessors['tidy'] = TidyProcessor(md) -class TidyProcessor(markdown.postprocessors.Postprocessor): +class TidyProcessor(Postprocessor): def run(self, text): # Pass text to Tidy. As Tidy does not accept unicode we need to encode # it and decode its return value. enc = self.markdown.tidy_options.get('char_encoding', 'utf8') - return unicode(tidy.parseString(text.encode(enc), + return text_type(tidy.parseString(text.encode(enc), **self.markdown.tidy_options), encoding=enc) diff --git a/markdown/extensions/meta.py b/markdown/extensions/meta.py index a3ee6fb..a2fbe80 100644 --- a/markdown/extensions/meta.py +++ b/markdown/extensions/meta.py @@ -1,5 +1,4 @@ -#!usr/bin/python - +from __future__ import unicode_literals """ Meta Data Extension for Python-Markdown ======================================= @@ -40,15 +39,17 @@ Contact: markdown@freewisdom.org License: BSD (see ../LICENSE.md for details) """ -import re -import markdown +from __future__ import absolute_import +from . import Extension +from ..preprocessors import Preprocessor +import re # Global Vars META_RE = re.compile(r'^[ ]{0,3}(?P<key>[A-Za-z0-9_-]+):\s*(?P<value>.*)') META_MORE_RE = re.compile(r'^[ ]{4,}(?P<value>.*)') -class MetaExtension (markdown.Extension): +class MetaExtension (Extension): """ Meta-Data extension for Python-Markdown. """ def extendMarkdown(self, md, md_globals): @@ -57,7 +58,7 @@ class MetaExtension (markdown.Extension): md.preprocessors.add("meta", MetaPreprocessor(md), "_begin") -class MetaPreprocessor(markdown.preprocessors.Preprocessor): +class MetaPreprocessor(Preprocessor): """ Get Meta-Data. """ def run(self, lines): @@ -90,7 +91,3 @@ class MetaPreprocessor(markdown.preprocessors.Preprocessor): def makeExtension(configs={}): return MetaExtension(configs=configs) - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/markdown/extensions/nl2br.py b/markdown/extensions/nl2br.py index 3967c75..5636214 100644 --- a/markdown/extensions/nl2br.py +++ b/markdown/extensions/nl2br.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ NL2BR Extension =============== @@ -20,17 +21,18 @@ Dependencies: """ -import markdown +from __future__ import absolute_import +from . import Extension +from ..inlinepatterns import SubstituteTagPattern BR_RE = r'\n' -class Nl2BrExtension(markdown.Extension): +class Nl2BrExtension(Extension): def extendMarkdown(self, md, md_globals): - br_tag = markdown.inlinepatterns.SubstituteTagPattern(BR_RE, 'br') + br_tag = SubstituteTagPattern(BR_RE, 'br') md.inlinePatterns.add('nl', br_tag, '_end') def makeExtension(configs=None): return Nl2BrExtension(configs) - diff --git a/markdown/extensions/sane_lists.py b/markdown/extensions/sane_lists.py index dce04ea..5620ccd 100644 --- a/markdown/extensions/sane_lists.py +++ b/markdown/extensions/sane_lists.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +from __future__ import unicode_literals """ Sane List Extension for Python-Markdown ======================================= @@ -19,23 +19,25 @@ Copyright 2011 - [Waylan Limberg](http://achinghead.com) """ +from __future__ import absolute_import +from . import Extension +from ..blockprocessors import OListProcessor, UListProcessor import re -import markdown -class SaneOListProcessor(markdown.blockprocessors.OListProcessor): +class SaneOListProcessor(OListProcessor): CHILD_RE = re.compile(r'^[ ]{0,3}((\d+\.))[ ]+(.*)') SIBLING_TAGS = ['ol'] -class SaneUListProcessor(markdown.blockprocessors.UListProcessor): +class SaneUListProcessor(UListProcessor): CHILD_RE = re.compile(r'^[ ]{0,3}(([*+-]))[ ]+(.*)') SIBLING_TAGS = ['ul'] -class SaneListExtension(markdown.Extension): +class SaneListExtension(Extension): """ Add sane lists to Markdown. """ def extendMarkdown(self, md, md_globals): diff --git a/markdown/extensions/smart_strong.py b/markdown/extensions/smart_strong.py index 7166989..7ce2d4d 100644 --- a/markdown/extensions/smart_strong.py +++ b/markdown/extensions/smart_strong.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals ''' Smart_Strong Extension for Python-Markdown ========================================== @@ -22,13 +23,14 @@ Copyright 2011 ''' -import markdown -from markdown.inlinepatterns import SimpleTagPattern +from __future__ import absolute_import +from . import Extension +from ..inlinepatterns import SimpleTagPattern SMART_STRONG_RE = r'(?<!\w)(_{2})(?!_)(.+?)(?<!_)\2(?!\w)' STRONG_RE = r'(\*{2})(.+?)\2' -class SmartEmphasisExtension(markdown.extensions.Extension): +class SmartEmphasisExtension(Extension): """ Add smart_emphasis extension to Markdown class.""" def extendMarkdown(self, md, md_globals): @@ -38,7 +40,3 @@ class SmartEmphasisExtension(markdown.extensions.Extension): def makeExtension(configs={}): return SmartEmphasisExtension(configs=dict(configs)) - -if __name__ == '__main__': - import doctest - doctest.testmod() diff --git a/markdown/extensions/tables.py b/markdown/extensions/tables.py index 1388cb5..3edaccf 100644 --- a/markdown/extensions/tables.py +++ b/markdown/extensions/tables.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +from __future__ import unicode_literals """ Tables Extension for Python-Markdown ==================================== @@ -14,11 +14,13 @@ A simple example: Copyright 2009 - [Waylan Limberg](http://achinghead.com) """ -import markdown -from markdown.util import etree +from __future__ import absolute_import +from . import Extension +from ..blockprocessors import BlockProcessor +from ..util import etree -class TableProcessor(markdown.blockprocessors.BlockProcessor): +class TableProcessor(BlockProcessor): """ Process Tables. """ def test(self, parent, block): @@ -84,7 +86,7 @@ class TableProcessor(markdown.blockprocessors.BlockProcessor): return row.split('|') -class TableExtension(markdown.Extension): +class TableExtension(Extension): """ Add tables to Markdown. """ def extendMarkdown(self, md, md_globals): diff --git a/markdown/extensions/toc.py b/markdown/extensions/toc.py index 1d6639e..f50f11b 100644 --- a/markdown/extensions/toc.py +++ b/markdown/extensions/toc.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ Table of Contents Extension for Python-Markdown * * * @@ -8,10 +9,12 @@ Dependencies: * [Markdown 2.1+](http://packages.python.org/Markdown/) """ -import markdown -from markdown.util import etree -from markdown.extensions.headerid import slugify, unique, itertext +from __future__ import absolute_import +from . import Extension +from ..treeprocessors import Treeprocessor +from ..util import etree +from .headerid import slugify, unique, itertext import re @@ -77,7 +80,7 @@ def order_toc_list(toc_list): return ordered_list -class TocTreeprocessor(markdown.treeprocessors.Treeprocessor): +class TocTreeprocessor(Treeprocessor): # Iterator wrapper to get parent and child all at once def iterparent(self, root): @@ -182,7 +185,7 @@ class TocTreeprocessor(markdown.treeprocessors.Treeprocessor): self.markdown.toc = toc -class TocExtension(markdown.Extension): +class TocExtension(Extension): TreeProcessorClass = TocTreeprocessor diff --git a/markdown/extensions/wikilinks.py b/markdown/extensions/wikilinks.py index 5384adb..de4d9aa 100644 --- a/markdown/extensions/wikilinks.py +++ b/markdown/extensions/wikilinks.py @@ -1,5 +1,4 @@ -#!/usr/bin/env python - +from __future__ import unicode_literals ''' WikiLinks Extension for Python-Markdown ====================================== @@ -78,7 +77,10 @@ Dependencies: * [Markdown 2.0+](http://packages.python.org/Markdown/) ''' -import markdown +from __future__ import absolute_import +from . import Extension +from ..inlinepatterns import Pattern +from ..util import etree import re def build_url(label, base, end): @@ -87,7 +89,7 @@ def build_url(label, base, end): return '%s%s%s'% (base, clean_label, end) -class WikiLinkExtension(markdown.Extension): +class WikiLinkExtension(Extension): def __init__(self, configs): # set extension defaults self.config = { @@ -111,9 +113,9 @@ class WikiLinkExtension(markdown.Extension): md.inlinePatterns.add('wikilink', wikilinkPattern, "<not_strong") -class WikiLinks(markdown.inlinepatterns.Pattern): +class WikiLinks(Pattern): def __init__(self, pattern, config): - markdown.inlinepatterns.Pattern.__init__(self, pattern) + super(WikiLinks, self).__init__(pattern) self.config = config def handleMatch(self, m): @@ -121,7 +123,7 @@ class WikiLinks(markdown.inlinepatterns.Pattern): base_url, end_url, html_class = self._getMeta() label = m.group(2).strip() url = self.config['build_url'](label, base_url, end_url) - a = markdown.util.etree.Element('a') + a = etree.Element('a') a.text = label a.set('href', url) if html_class: @@ -136,20 +138,14 @@ class WikiLinks(markdown.inlinepatterns.Pattern): end_url = self.config['end_url'] html_class = self.config['html_class'] if hasattr(self.md, 'Meta'): - if self.md.Meta.has_key('wiki_base_url'): + if 'wiki_base_url' in self.md.Meta: base_url = self.md.Meta['wiki_base_url'][0] - if self.md.Meta.has_key('wiki_end_url'): + if 'wiki_end_url' in self.md.Meta: end_url = self.md.Meta['wiki_end_url'][0] - if self.md.Meta.has_key('wiki_html_class'): + if 'wiki_html_class' in self.md.Meta: html_class = self.md.Meta['wiki_html_class'][0] return base_url, end_url, html_class def makeExtension(configs=None) : return WikiLinkExtension(configs=configs) - - -if __name__ == "__main__": - import doctest - doctest.testmod() - diff --git a/markdown/inlinepatterns.py b/markdown/inlinepatterns.py index f64aa58..e8ecaea 100644 --- a/markdown/inlinepatterns.py +++ b/markdown/inlinepatterns.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ INLINE PATTERNS ============================================================================= @@ -41,17 +42,18 @@ So, we apply the expressions in the following order: * finally we apply strong and emphasis """ -import util -import odict +from __future__ import absolute_import +from . import util +from . import odict import re -from urlparse import urlparse, urlunparse -# If you see an ImportError for htmlentitydefs after using 2to3 to convert for -# use by Python3, then you are probably using the buggy version from Python 3.0. -# We recomend using the tool from Python 3.1 even if you will be running the -# code on Python 3.0. The following line should be converted by the tool to: -# `from html import entities` and later calls to `htmlentitydefs` should be -# changed to call `entities`. Python 3.1's tool does this but 3.0's does not. -import htmlentitydefs +try: + from urllib.parse import urlparse, urlunparse +except ImportError: + from urlparse import urlparse, urlunparse +try: + from html import entities +except ImportError: + import htmlentitydefs as entities def build_inlinepatterns(md_instance, **kwargs): @@ -141,7 +143,7 @@ The pattern classes ----------------------------------------------------------------------------- """ -class Pattern: +class Pattern(object): """Base class that inline patterns subclass. """ def __init__(self, pattern, markdown_instance=None): @@ -191,7 +193,7 @@ class Pattern: def itertext(el): ' Reimplement Element.itertext for older python versions ' tag = el.tag - if not isinstance(tag, basestring) and tag is not None: + if not isinstance(tag, util.string_type) and tag is not None: return if el.text: yield el.text @@ -204,7 +206,7 @@ class Pattern: id = m.group(1) if id in stash: value = stash.get(id) - if isinstance(value, basestring): + if isinstance(value, util.string_type): return value else: # An etree Element - return text content only @@ -464,7 +466,7 @@ class AutomailPattern(Pattern): def codepoint2name(code): """Return entity definition by code, or the code if not defined.""" - entity = htmlentitydefs.codepoint2name.get(code) + entity = entities.codepoint2name.get(code) if entity: return "%s%s;" % (util.AMP_SUBSTITUTE, entity) else: diff --git a/markdown/odict.py b/markdown/odict.py index 02864bf..578cc76 100644 --- a/markdown/odict.py +++ b/markdown/odict.py @@ -1,3 +1,7 @@ +from __future__ import unicode_literals +from __future__ import absolute_import +from . import util + class OrderedDict(dict): """ A dictionary that keeps its keys in the order in which they're inserted. @@ -11,34 +15,44 @@ class OrderedDict(dict): return instance def __init__(self, data=None): - if data is None: - data = {} - super(OrderedDict, self).__init__(data) - if isinstance(data, dict): - self.keyOrder = data.keys() + if data is None or isinstance(data, dict): + data = data or [] + super(OrderedDict, self).__init__(data) + self.keyOrder = list(data) if data else [] else: - self.keyOrder = [] + super(OrderedDict, self).__init__() + super_set = super(OrderedDict, self).__setitem__ for key, value in data: - if key not in self.keyOrder: + # Take the ordering from first key + if key not in self: self.keyOrder.append(key) + # But override with last value in data (dict() does this) + super_set(key, value) def __deepcopy__(self, memo): - from copy import deepcopy - return self.__class__([(key, deepcopy(value, memo)) - for key, value in self.iteritems()]) + return self.__class__([(key, copy.deepcopy(value, memo)) + for key, value in self.items()]) + + def __copy__(self): + # The Python's default copy implementation will alter the state + # of self. The reason for this seems complex but is likely related to + # subclassing dict. + return self.copy() def __setitem__(self, key, value): - super(OrderedDict, self).__setitem__(key, value) - if key not in self.keyOrder: + if key not in self: self.keyOrder.append(key) + super(OrderedDict, self).__setitem__(key, value) def __delitem__(self, key): super(OrderedDict, self).__delitem__(key) self.keyOrder.remove(key) def __iter__(self): - for k in self.keyOrder: - yield k + return iter(self.keyOrder) + + def __reversed__(self): + return reversed(self.keyOrder) def pop(self, k, *args): result = super(OrderedDict, self).pop(k, *args) @@ -54,41 +68,51 @@ class OrderedDict(dict): self.keyOrder.remove(result[0]) return result - def items(self): - return zip(self.keyOrder, self.values()) + def _iteritems(self): + for key in self.keyOrder: + yield key, self[key] - def iteritems(self): + def _iterkeys(self): for key in self.keyOrder: - yield key, super(OrderedDict, self).__getitem__(key) + yield key - def keys(self): - return self.keyOrder[:] + def _itervalues(self): + for key in self.keyOrder: + yield self[key] - def iterkeys(self): - return iter(self.keyOrder) + if util.PY3: + items = _iteritems + keys = _iterkeys + values = _itervalues + else: + iteritems = _iteritems + iterkeys = _iterkeys + itervalues = _itervalues - def values(self): - return [super(OrderedDict, self).__getitem__(k) for k in self.keyOrder] + def items(self): + return [(k, self[k]) for k in self.keyOrder] - def itervalues(self): - for key in self.keyOrder: - yield super(OrderedDict, self).__getitem__(key) + def keys(self): + return self.keyOrder[:] + + def values(self): + return [self[k] for k in self.keyOrder] def update(self, dict_): - for k, v in dict_.items(): - self.__setitem__(k, v) + for k, v in six.iteritems(dict_): + self[k] = v def setdefault(self, key, default): - if key not in self.keyOrder: + if key not in self: self.keyOrder.append(key) return super(OrderedDict, self).setdefault(key, default) def value_for_index(self, index): - """Return the value of the item at the given zero-based index.""" + """Returns the value of the item at the given zero-based index.""" return self[self.keyOrder[index]] def insert(self, index, key, value): - """Insert the key, value pair before the item with the given index.""" + """Inserts the key, value pair before the item with the given index.""" if key in self.keyOrder: n = self.keyOrder.index(key) del self.keyOrder[n] @@ -98,18 +122,16 @@ class OrderedDict(dict): super(OrderedDict, self).__setitem__(key, value) def copy(self): - """Return a copy of this object.""" + """Returns a copy of this object.""" # This way of initializing the copy means it works for subclasses, too. - obj = self.__class__(self) - obj.keyOrder = self.keyOrder[:] - return obj + return self.__class__(self) def __repr__(self): """ - Replace the normal dict.__repr__ with a version that returns the keys - in their sorted order. + Replaces the normal dict.__repr__ with a version that returns the keys + in their Ordered order. """ - return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in self.items()]) + return '{%s}' % ', '.join(['%r: %r' % (k, v) for k, v in six.iteritems(self)]) def clear(self): super(OrderedDict, self).clear() @@ -159,7 +181,7 @@ class OrderedDict(dict): self.keyOrder.insert(i, key) else: self.keyOrder.append(key) - except Exception, e: + except Exception as e: # restore to prevent data loss and reraise self.keyOrder.insert(n, key) raise e diff --git a/markdown/postprocessors.py b/markdown/postprocessors.py index 071791a..36fa98d 100644 --- a/markdown/postprocessors.py +++ b/markdown/postprocessors.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ POST-PROCESSORS ============================================================================= @@ -8,9 +9,11 @@ processing. """ +from __future__ import absolute_import +from . import util +from . import odict import re -import util -import odict + def build_postprocessors(md_instance, **kwargs): """ Build the default postprocessors for Markdown. """ @@ -95,7 +98,7 @@ class UnescapePostprocessor(Postprocessor): RE = re.compile('%s(\d+)%s' % (util.STX, util.ETX)) def unescape(self, m): - return unichr(int(m.group(1))) + return util.int2str(int(m.group(1))) def run(self, text): return self.RE.sub(self.unescape, text) diff --git a/markdown/preprocessors.py b/markdown/preprocessors.py index 6238303..e61a6b8 100644 --- a/markdown/preprocessors.py +++ b/markdown/preprocessors.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals """ PRE-PROCESSORS ============================================================================= @@ -6,9 +7,10 @@ Preprocessors work on source text before we start doing anything too complicated. """ +from __future__ import absolute_import +from . import util +from . import odict import re -import util -import odict def build_preprocessors(md_instance, **kwargs): diff --git a/markdown/serializers.py b/markdown/serializers.py index 22a83d4..977d6e8 100644 --- a/markdown/serializers.py +++ b/markdown/serializers.py @@ -1,3 +1,4 @@ +from __future__ import unicode_literals # markdown/searializers.py # # Add x/html serialization to Elementree @@ -37,7 +38,8 @@ # -------------------------------------------------------------------- -import util +from __future__ import absolute_import +from . import util ElementTree = util.etree.ElementTree QName = util.etree.QName if hasattr(util.etree, 'test_comment'): @@ -251,7 +253,7 @@ def _namespaces(elem, default_namespace=None): tag = elem.tag if isinstance(tag, QName) and tag.text not in qnames: add_qname(tag.text) - elif isinstance(tag, basestring): + elif isinstance(tag, util.string_type): if tag not in qnames: add_qname(tag) elif tag is not None and tag is not Comment and tag is not PI: diff --git a/markdown/treeprocessors.py b/markdown/treeprocessors.py index b5eedbd..2df80f8 100644 --- a/markdown/treeprocessors.py +++ b/markdown/treeprocessors.py @@ -1,6 +1,8 @@ -import inlinepatterns -import util -import odict +from __future__ import unicode_literals +from __future__ import absolute_import +from . import util +from . import odict +from . import inlinepatterns def build_treeprocessors(md_instance, **kwargs): @@ -14,17 +16,11 @@ def build_treeprocessors(md_instance, **kwargs): def isString(s): """ Check if it's string """ if not isinstance(s, util.AtomicString): - return isinstance(s, basestring) + return isinstance(s, util.string_type) return False -class Processor: - def __init__(self, markdown_instance=None): - if markdown_instance: - self.markdown = markdown_instance - - -class Treeprocessor(Processor): +class Treeprocessor(util.Processor): """ Treeprocessors are run on the ElementTree object before serialization. diff --git a/markdown/util.py b/markdown/util.py index 34333f0..1036197 100644 --- a/markdown/util.py +++ b/markdown/util.py @@ -1,11 +1,24 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals import re +import sys """ -CONSTANTS +Python 3 Stuff ============================================================================= """ +PY3 = sys.version_info[0] == 3 + +if PY3: + string_type = str + text_type = str + int2str = chr +else: + string_type = basestring + text_type = unicode + int2str = unichr + """ Constants you might want to modify @@ -19,8 +32,8 @@ BLOCK_LEVEL_ELEMENTS = re.compile("^(p|div|h[1-6]|blockquote|pre|table|dl|ol|ul" "|figcaption|aside|article|canvas|output" "|progress|video)$", re.IGNORECASE) # Placeholders -STX = u'\u0002' # Use STX ("Start of text") for start-of-placeholder -ETX = u'\u0003' # Use ETX ("End of text") for end-of-placeholder +STX = '\u0002' # Use STX ("Start of text") for start-of-placeholder +ETX = '\u0003' # Use ETX ("End of text") for end-of-placeholder INLINE_PLACEHOLDER_PREFIX = STX+"klzzwxh:" INLINE_PLACEHOLDER = INLINE_PLACEHOLDER_PREFIX + "%s" + ETX INLINE_PLACEHOLDER_RE = re.compile(INLINE_PLACEHOLDER % r'([0-9]{4})') @@ -31,11 +44,11 @@ Constants you probably do not need to change ----------------------------------------------------------------------------- """ -RTL_BIDI_RANGES = ( (u'\u0590', u'\u07FF'), +RTL_BIDI_RANGES = ( ('\u0590', '\u07FF'), # Hebrew (0590-05FF), Arabic (0600-06FF), # Syriac (0700-074F), Arabic supplement (0750-077F), # Thaana (0780-07BF), Nko (07C0-07FF). - (u'\u2D30', u'\u2D7F'), # Tifinagh + ('\u2D30', '\u2D7F'), # Tifinagh ) # Extensions should use "markdown.util.etree" instead of "etree" (or do `from @@ -63,7 +76,7 @@ AUXILIARY GLOBAL FUNCTIONS def isBlockLevel(tag): """Check if the tag is a block level HTML tag.""" - if isinstance(tag, basestring): + if isinstance(tag, string_type): return BLOCK_LEVEL_ELEMENTS.match(tag) # Some ElementTree tags are not strings, so return False. return False @@ -73,18 +86,18 @@ MISC AUXILIARY CLASSES ============================================================================= """ -class AtomicString(unicode): +class AtomicString(text_type): """A string which should not be further processed.""" pass -class Processor: +class Processor(object): def __init__(self, markdown_instance=None): if markdown_instance: self.markdown = markdown_instance -class HtmlStash: +class HtmlStash(object): """ This class is used for stashing HTML objects that we extract in the beginning and replace with place-holders. @@ -10,14 +10,6 @@ from distutils.util import change_root, newer import codecs import imp -# Try to run 2to3 automaticaly when building in Python 3.x -try: - from distutils.command.build_py import build_py_2to3 as build_py -except ImportError: - if sys.version_info >= (3, 0): - raise ImportError("build_py_2to3 is required to build in Python 3.x.") - from distutils.command.build_py import build_py - def get_version(): " Get version & version_info without importing markdown.__init__ " path = os.path.join(os.path.dirname(__file__), 'markdown') @@ -235,7 +227,6 @@ setup( packages = ['markdown', 'markdown.extensions'], scripts = ['bin/%s' % SCRIPT_NAME], cmdclass = {'install_scripts': md_install_scripts, - 'build_py': build_py, 'build_docs': build_docs, 'build': md_build}, classifiers = ['Development Status :: %s' % DEVSTATUS, |