[Spice-devel] [spice-space.org] Update asciidoc_reader to latest version
Christophe Fergeau
cfergeau at redhat.com
Mon May 14 15:17:35 UTC 2018
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. 'ñ' to 'ñ'.
+ 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')
- at unittest.skipUnless(asciidoc_enabled, "asciidoc isn't installed")
+ at 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
More information about the Spice-devel
mailing list