It comes from https://github.com/getpelican/pelican-plugins, commit 8d96866a4ec and adds python 3 support. --- plugins/asciidoc_reader/README.rst | 41 ++- plugins/asciidoc_reader/asciidoc_reader.py | 108 +++++--- plugins/asciidoc_reader/asciidocapi.py | 257 ------------------ .../asciidoc_reader/test_asciidoc_reader.py | 31 ++- .../test_data/article_with_asc_extension.asc | 3 +- .../test_data/article_with_asc_options.asc | 6 +- 6 files changed, 120 insertions(+), 326 deletions(-) delete mode 100644 plugins/asciidoc_reader/asciidocapi.py diff --git a/plugins/asciidoc_reader/README.rst b/plugins/asciidoc_reader/README.rst index 3b28c4f..a14bd2d 100644 --- a/plugins/asciidoc_reader/README.rst +++ b/plugins/asciidoc_reader/README.rst @@ -1,18 +1,19 @@ AsciiDoc Reader ############### -This plugin allows you to use `AsciiDoc <http://www.methods.co.nz/asciidoc/>`_ -to write your posts. File extension should be ``.asc``, ``.adoc``, +This plugin allows you to use `AsciiDoc <http://www.methods.co.nz/asciidoc/>`_ +to write your posts. File extension should be ``.asc``, ``.adoc``, or ``.asciidoc``. Dependency ---------- -If you want to use AsciiDoc you need to install it from `source -<http://www.methods.co.nz/asciidoc/INSTALL.html>`_ or use your operating -system's package manager. +There are two command line utilities commonly used to render AsciiDoc: +``asciidoc`` and ``asciidoctor``. One of the two will need to be installed and +on the PATH. -**Note**: AsciiDoc does not work with Python 3, so you should be using Python 2. +**Note**: The ``asciidoctor`` utility is recommended since the original +``asciidoc`` is no longer maintained. Settings -------- @@ -20,9 +21,29 @@ Settings ======================================== ======================================================= Setting name (followed by default value) What does it do? ======================================== ======================================================= +``ASCIIDOC_CMD = asciidoc`` Selects which utility to use for rendering. Will + autodetect utility if not provided. ``ASCIIDOC_OPTIONS = []`` A list of options to pass to AsciiDoc. See the `manpage <http://www.methods.co.nz/asciidoc/manpage.html>`_. -``ASCIIDOC_BACKEND = 'html5'`` Backend format for output. See the `documentation - <http://www.methods.co.nz/asciidoc/userguide.html#X5>`_ - for possible values. -======================================== ======================================================= \ No newline at end of file +======================================== ======================================================= + +Example file header +------------------- + +Following the `example <https://github.com/getpelican/pelican/blob/master/docs/content.rst#file-metadata>`_ in the main pelican documentation: + +.. code-block:: none + + = My super title + + :date: 2010-10-03 10:20 + :modified: 2010-10-04 18:40 + :tags: thats, awesome + :category: yeah + :slug: my-super-post + :authors: Alexis Metaireau, Conan Doyle + :summary: Short version for index and feeds + + == title level 2 + + and so on... diff --git a/plugins/asciidoc_reader/asciidoc_reader.py b/plugins/asciidoc_reader/asciidoc_reader.py index 043403b..afcfb5a 100644 --- a/plugins/asciidoc_reader/asciidoc_reader.py +++ b/plugins/asciidoc_reader/asciidoc_reader.py @@ -3,61 +3,91 @@ AsciiDoc Reader =============== -This plugin allows you to use AsciiDoc to write your posts. +This plugin allows you to use AsciiDoc to write your posts. File extension should be ``.asc``, ``.adoc``, or ``asciidoc``. """ from pelican.readers import BaseReader -from pelican.utils import pelican_open from pelican import signals -import six +import os +import re +import subprocess +import sys -try: - # asciidocapi won't import on Py3 - from .asciidocapi import AsciiDocAPI, AsciiDocError - # AsciiDocAPI class checks for asciidoc.py - AsciiDocAPI() -except: - asciidoc_enabled = False -else: - asciidoc_enabled = True +def call(cmd): + """Calls a CLI command and returns the stdout as string.""" + return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0].decode('utf-8') +def default(): + """Attempt to find the default AsciiDoc utility.""" + for cmd in ALLOWED_CMDS: + if len(call(cmd + " --help")): + return cmd +def fix_unicode(val): + if sys.version_info < (3,0): + val = unicode(val.decode("utf-8")) + else: + # This fixes an issue with character substitutions, e.g. '�o 'ñ'. + val = str.encode(val, "latin-1").decode("utf-8") + return val + +ALLOWED_CMDS = ["asciidoc", "asciidoctor"] + +ENABLED = None != default() class AsciiDocReader(BaseReader): - """Reader for AsciiDoc files""" + """Reader for AsciiDoc files.""" - enabled = asciidoc_enabled + enabled = ENABLED file_extensions = ['asc', 'adoc', 'asciidoc'] - default_options = ["--no-header-footer", "-a newline=\\n"] - default_backend = 'html5' + default_options = ['--no-header-footer'] def read(self, source_path): - """Parse content and metadata of asciidoc files""" - from cStringIO import StringIO - with pelican_open(source_path) as source: - text = StringIO(source.encode('utf8')) - content = StringIO() - ad = AsciiDocAPI() - - options = self.settings.get('ASCIIDOC_OPTIONS', []) - options = self.default_options + options - print options - for o in options: - ad.options(*o.split()) - - backend = self.settings.get('ASCIIDOC_BACKEND', self.default_backend) - ad.execute(text, content, backend=backend) - content = content.getvalue().decode('utf8') - - metadata = {} - for name, value in ad.asciidoc.document.attributes.items(): - name = name.lower() - metadata[name] = self.process_metadata(name, six.text_type(value)) - if 'doctitle' in metadata: - metadata['title'] = metadata['doctitle'] + """Parse content and metadata of AsciiDoc files.""" + cmd = self._get_cmd() + content = "" + if cmd: + optlist = self.settings.get('ASCIIDOC_OPTIONS', []) + self.default_options + options = " ".join(optlist) + content = call("%s %s -o - %s" % (cmd, options, source_path)) + metadata = self._read_metadata(source_path) return content, metadata + def _get_cmd(self): + """Returns the AsciiDoc utility command to use for rendering or None if + one cannot be found.""" + if self.settings.get('ASCIIDOC_CMD') in ALLOWED_CMDS: + return self.settings.get('ASCIIDOC_CMD') + return default() + + def _read_metadata(self, source_path): + """Parses the AsciiDoc file at the given `source_path` and returns found + metadata.""" + metadata = {} + with open(source_path) as fi: + prev = "" + for line in fi.readlines(): + # Parse for doc title. + if 'title' not in metadata.keys(): + title = "" + if line.startswith("= "): + title = line[2:].strip() + elif line.count("=") == len(prev.strip()): + title = prev.strip() + if title: + metadata['title'] = self.process_metadata('title', fix_unicode(title)) + + # Parse for other metadata. + regexp = re.compile(r"^:[A-z]+:\s*[A-z0-9]") + if regexp.search(line): + toks = line.split(":", 2) + key = toks[1].strip().lower() + val = toks[2].strip() + metadata[key] = self.process_metadata(key, fix_unicode(val)) + prev = line + return metadata + def add_reader(readers): for ext in AsciiDocReader.file_extensions: readers.reader_classes[ext] = AsciiDocReader diff --git a/plugins/asciidoc_reader/asciidocapi.py b/plugins/asciidoc_reader/asciidocapi.py deleted file mode 100644 index dcdf262..0000000 --- a/plugins/asciidoc_reader/asciidocapi.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/env python -""" -asciidocapi - AsciiDoc API wrapper class. - -The AsciiDocAPI class provides an API for executing asciidoc. Minimal example -compiles `mydoc.txt` to `mydoc.html`: - - import asciidocapi - asciidoc = asciidocapi.AsciiDocAPI() - asciidoc.execute('mydoc.txt') - -- Full documentation in asciidocapi.txt. -- See the doctests below for more examples. - -Doctests: - -1. Check execution: - - >>> import StringIO - >>> infile = StringIO.StringIO('Hello *{author}*') - >>> outfile = StringIO.StringIO() - >>> asciidoc = AsciiDocAPI() - >>> asciidoc.options('--no-header-footer') - >>> asciidoc.attributes['author'] = 'Joe Bloggs' - >>> asciidoc.execute(infile, outfile, backend='html4') - >>> print outfile.getvalue() - <p>Hello <strong>Joe Bloggs</strong></p> - - >>> asciidoc.attributes['author'] = 'Bill Smith' - >>> infile = StringIO.StringIO('Hello _{author}_') - >>> outfile = StringIO.StringIO() - >>> asciidoc.execute(infile, outfile, backend='docbook') - >>> print outfile.getvalue() - <simpara>Hello <emphasis>Bill Smith</emphasis></simpara> - -2. Check error handling: - - >>> import StringIO - >>> asciidoc = AsciiDocAPI() - >>> infile = StringIO.StringIO('---------') - >>> outfile = StringIO.StringIO() - >>> asciidoc.execute(infile, outfile) - Traceback (most recent call last): - File "<stdin>", line 1, in <module> - File "asciidocapi.py", line 189, in execute - raise AsciiDocError(self.messages[-1]) - AsciiDocError: ERROR: <stdin>: line 1: [blockdef-listing] missing closing delimiter - - -Copyright (C) 2009 Stuart Rackham. Free use of this software is granted -under the terms of the GNU General Public License (GPL). - -""" - -import sys,os,re,imp - -API_VERSION = '0.1.2' -MIN_ASCIIDOC_VERSION = '8.4.1' # Minimum acceptable AsciiDoc version. - - -def find_in_path(fname, path=None): - """ - Find file fname in paths. Return None if not found. - """ - if path is None: - path = os.environ.get('PATH', '') - for dir in path.split(os.pathsep): - fpath = os.path.join(dir, fname) - if os.path.isfile(fpath): - return fpath - else: - return None - - -class AsciiDocError(Exception): - pass - - -class Options(object): - """ - Stores asciidoc(1) command options. - """ - def __init__(self, values=[]): - self.values = values[:] - def __call__(self, name, value=None): - """Shortcut for append method.""" - self.append(name, value) - def append(self, name, value=None): - if type(value) in (int,float): - value = str(value) - self.values.append((name,value)) - - -class Version(object): - """ - Parse and compare AsciiDoc version numbers. Instance attributes: - - string: String version number '<major>.<minor>[.<micro>][suffix]'. - major: Integer major version number. - minor: Integer minor version number. - micro: Integer micro version number. - suffix: Suffix (begins with non-numeric character) is ignored when - comparing. - - Doctest examples: - - >>> Version('8.2.5') < Version('8.3 beta 1') - True - >>> Version('8.3.0') == Version('8.3. beta 1') - True - >>> Version('8.2.0') < Version('8.20') - True - >>> Version('8.20').major - 8 - >>> Version('8.20').minor - 20 - >>> Version('8.20').micro - 0 - >>> Version('8.20').suffix - '' - >>> Version('8.20 beta 1').suffix - 'beta 1' - - """ - def __init__(self, version): - self.string = version - reo = re.match(r'^(\d+)\.(\d+)(\.(\d+))?\s*(.*?)\s*$', self.string) - if not reo: - raise ValueError('invalid version number: %s' % self.string) - groups = reo.groups() - self.major = int(groups[0]) - self.minor = int(groups[1]) - self.micro = int(groups[3] or '0') - self.suffix = groups[4] or '' - def __cmp__(self, other): - result = cmp(self.major, other.major) - if result == 0: - result = cmp(self.minor, other.minor) - if result == 0: - result = cmp(self.micro, other.micro) - return result - - -class AsciiDocAPI(object): - """ - AsciiDoc API class. - """ - def __init__(self, asciidoc_py=None): - """ - Locate and import asciidoc.py. - Initialize instance attributes. - """ - self.options = Options() - self.attributes = {} - self.messages = [] - # Search for the asciidoc command file. - # Try ASCIIDOC_PY environment variable first. - cmd = os.environ.get('ASCIIDOC_PY') - if cmd: - if not os.path.isfile(cmd): - raise AsciiDocError('missing ASCIIDOC_PY file: %s' % cmd) - elif asciidoc_py: - # Next try path specified by caller. - cmd = asciidoc_py - if not os.path.isfile(cmd): - raise AsciiDocError('missing file: %s' % cmd) - else: - # Try shell search paths. - for fname in ['asciidoc.py','asciidoc.pyc','asciidoc']: - cmd = find_in_path(fname) - if cmd: break - else: - # Finally try current working directory. - for cmd in ['asciidoc.py','asciidoc.pyc','asciidoc']: - if os.path.isfile(cmd): break - else: - raise AsciiDocError('failed to locate asciidoc') - self.cmd = os.path.realpath(cmd) - self.__import_asciidoc() - - def __import_asciidoc(self, reload=False): - ''' - Import asciidoc module (script or compiled .pyc). - See - http://groups.google.com/group/asciidoc/browse_frm/thread/66e7b59d12cd2f91 - for an explanation of why a seemingly straight-forward job turned out - quite complicated. - ''' - if os.path.splitext(self.cmd)[1] in ['.py','.pyc']: - sys.path.insert(0, os.path.dirname(self.cmd)) - try: - try: - if reload: - import __builtin__ # Because reload() is shadowed. - __builtin__.reload(self.asciidoc) - else: - import asciidoc - self.asciidoc = asciidoc - except ImportError: - raise AsciiDocError('failed to import ' + self.cmd) - finally: - del sys.path[0] - else: - # The import statement can only handle .py or .pyc files, have to - # use imp.load_source() for scripts with other names. - try: - imp.load_source('asciidoc', self.cmd) - import asciidoc - self.asciidoc = asciidoc - except ImportError: - raise AsciiDocError('failed to import ' + self.cmd) - if Version(self.asciidoc.VERSION) < Version(MIN_ASCIIDOC_VERSION): - raise AsciiDocError( - 'asciidocapi %s requires asciidoc %s or better' - % (API_VERSION, MIN_ASCIIDOC_VERSION)) - - def execute(self, infile, outfile=None, backend=None): - """ - Compile infile to outfile using backend format. - infile can outfile can be file path strings or file like objects. - """ - self.messages = [] - opts = Options(self.options.values) - if outfile is not None: - opts('--out-file', outfile) - if backend is not None: - opts('--backend', backend) - for k,v in self.attributes.items(): - if v == '' or k[-1] in '!@': - s = k - elif v is None: # A None value undefines the attribute. - s = k + '!' - else: - s = '%s=%s' % (k,v) - opts('--attribute', s) - args = [infile] - # The AsciiDoc command was designed to process source text then - # exit, there are globals and statics in asciidoc.py that have - # to be reinitialized before each run -- hence the reload. - self.__import_asciidoc(reload=True) - try: - try: - self.asciidoc.execute(self.cmd, opts.values, args) - finally: - self.messages = self.asciidoc.messages[:] - except SystemExit, e: - if e.code: - raise AsciiDocError(self.messages[-1]) - - -if __name__ == "__main__": - """ - Run module doctests. - """ - import doctest - options = doctest.NORMALIZE_WHITESPACE + doctest.ELLIPSIS - doctest.testmod(optionflags=options) diff --git a/plugins/asciidoc_reader/test_asciidoc_reader.py b/plugins/asciidoc_reader/test_asciidoc_reader.py index 4174206..46a4dac 100644 --- a/plugins/asciidoc_reader/test_asciidoc_reader.py +++ b/plugins/asciidoc_reader/test_asciidoc_reader.py @@ -7,12 +7,12 @@ import os from pelican.readers import Readers from pelican.tests.support import unittest, get_settings -from .asciidoc_reader import asciidoc_enabled +from .asciidoc_reader import ENABLED CUR_DIR = os.path.dirname(__file__) CONTENT_PATH = os.path.join(CUR_DIR, 'test_data') -@unittest.skipUnless(asciidoc_enabled, "asciidoc isn't installed") +@unittest.skipUnless(ENABLED, "asciidoc isn't installed") class AsciiDocReaderTest(unittest.TestCase): def read_file(self, path, **kwargs): # Isolate from future API changes to readers.read_file @@ -23,15 +23,17 @@ class AsciiDocReaderTest(unittest.TestCase): # Ensure the asc extension is being processed by the correct reader page = self.read_file( path='article_with_asc_extension.asc') - expected = ('<div class="sect1">\n' + expected = ('<div class="sect1">' '<h2 id="_used_for_pelican_test">' - 'Used for pelican test</h2>\n' - '<div class="sectionbody">\n' + 'Used for pelican test</h2>' + '<div class="sectionbody">' '<div class="paragraph">' '<p>The quick brown fox jumped over ' 'the lazy dog’s back.</p>' - '</div>\n</div>\n</div>\n') - self.assertEqual(page.content, expected) + '</div></div></div>') + actual = "".join(page.content.splitlines()) + expected = "".join(expected.splitlines()) + self.assertEqual(actual, expected) expected = { 'category': 'Blog', 'author': 'Author O. Article', @@ -39,7 +41,6 @@ class AsciiDocReaderTest(unittest.TestCase): 'date': datetime.datetime(2011, 9, 15, 9, 5), 'tags': ['Linux', 'Python', 'Pelican'], } - for key, value in expected.items(): self.assertEqual(value, page.metadata[key], ( 'Metadata attribute \'%s\' does not match expected value.\n' @@ -50,17 +51,19 @@ class AsciiDocReaderTest(unittest.TestCase): # test to ensure the ASCIIDOC_OPTIONS is being used page = self.read_file(path='article_with_asc_options.asc', ASCIIDOC_OPTIONS=["-a revision=1.0.42"]) - expected = ('<div class="sect1">\n' + expected = ('<div class="sect1">' '<h2 id="_used_for_pelican_test">' - 'Used for pelican test</h2>\n' - '<div class="sectionbody">\n' + 'Used for pelican test</h2>' + '<div class="sectionbody">' '<div class="paragraph">' - '<p>version 1.0.42</p></div>\n' + '<p>version 1.0.42</p></div>' '<div class="paragraph">' '<p>The quick brown fox jumped over ' 'the lazy dog’s back.</p>' - '</div>\n</div>\n</div>\n') - self.assertEqual(page.content, expected) + '</div></div></div>') + actual = "".join(page.content.splitlines()) + expected = "".join(expected.splitlines()) + self.assertEqual(actual, expected) if __name__ == '__main__': diff --git a/plugins/asciidoc_reader/test_data/article_with_asc_extension.asc b/plugins/asciidoc_reader/test_data/article_with_asc_extension.asc index 9ce2166..3204d8f 100644 --- a/plugins/asciidoc_reader/test_data/article_with_asc_extension.asc +++ b/plugins/asciidoc_reader/test_data/article_with_asc_extension.asc @@ -6,7 +6,6 @@ Test AsciiDoc File Header :Category: Blog :Tags: Linux, Python, Pelican -Used for pelican test ---------------------- +== Used for pelican test The quick brown fox jumped over the lazy dog's back. diff --git a/plugins/asciidoc_reader/test_data/article_with_asc_options.asc b/plugins/asciidoc_reader/test_data/article_with_asc_options.asc index bafb3a4..620abba 100644 --- a/plugins/asciidoc_reader/test_data/article_with_asc_options.asc +++ b/plugins/asciidoc_reader/test_data/article_with_asc_options.asc @@ -1,8 +1,6 @@ -Test AsciiDoc File Header -========================= += Test AsciiDoc File Header -Used for pelican test ---------------------- +== Used for pelican test version {revision} -- 2.17.0
_______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel