182 lines
5.9 KiB
Python
182 lines
5.9 KiB
Python
|
# -*- coding: utf-8 -*-
|
||
|
"""
|
||
|
AsciiDoctor
|
||
|
===========
|
||
|
|
||
|
This plugin allows you to use AsciiDoctor to write your posts.
|
||
|
File extension should be ``.asc``, ``.adoc``, or ``asciidoc``.
|
||
|
|
||
|
This plugin is based on
|
||
|
https://github.com/getpelican/pelican-plugins/tree/master/asciidoc_reader
|
||
|
and
|
||
|
https://github.com/marienfressinaud/Pelidoc
|
||
|
|
||
|
"""
|
||
|
|
||
|
from pelican.readers import BaseReader
|
||
|
from pelican.generators import Generator
|
||
|
from pelican import signals
|
||
|
import os
|
||
|
import re
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import logging
|
||
|
from subprocess import check_call, check_output
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
pattern = re.compile("Asciidoctor.*asciidoctor.org")
|
||
|
|
||
|
class AsciiDoctorReader(BaseReader):
|
||
|
""" Reader for AsciiDoc files. """
|
||
|
|
||
|
file_extensions = ['asc', 'adoc', 'asciidoc']
|
||
|
|
||
|
def read(self, source_path):
|
||
|
"""Parse content and metadata of AsciiDoc files."""
|
||
|
cmd = self.settings.get('ASCIIDOCTOR_CMD', 'asciidoctor')
|
||
|
cmd_version = check_output(['asciidoctor', '--version']).decode('utf-8')
|
||
|
if not pattern.match(cmd_version):
|
||
|
logger.error("AsciiDoctorReader: {} is not asciidoctor executable".format(cmd))
|
||
|
return
|
||
|
|
||
|
content = ""
|
||
|
if cmd:
|
||
|
content = check_output(
|
||
|
[cmd] + self.settings.get('ASCIIDOCTOR_EXTRA_OPTIONS', []) +
|
||
|
['--doctype=article'] + ['--no-header-footer'] +
|
||
|
['--out-file', '-', source_path]
|
||
|
).decode('utf-8')
|
||
|
metadata = self._read_metadata(source_path)
|
||
|
return content, metadata
|
||
|
|
||
|
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', title)
|
||
|
|
||
|
regexp = re.compile(r"^:[\-A-z]+:\s*")
|
||
|
if regexp.search(line):
|
||
|
toks = line.split(":", 2)
|
||
|
key = toks[1].strip().lower()
|
||
|
val = toks[2].strip()
|
||
|
metadata[key] = self.process_metadata(key, val)
|
||
|
prev = line
|
||
|
if (not 'summary' in metadata) or (metadata['summary'] is None):
|
||
|
metadata['summary'] = ''
|
||
|
return metadata
|
||
|
|
||
|
|
||
|
class AsciiDoctorPdfGenerator(Generator):
|
||
|
"""The AsciiDoctorPdf generator.
|
||
|
"""
|
||
|
|
||
|
def guess_format(self, content):
|
||
|
"""Return the format used by a given content.
|
||
|
"""
|
||
|
|
||
|
formats = {
|
||
|
'.adoc': 'asciidoc',
|
||
|
'.asciidoc': 'asciidoc',
|
||
|
'.asc': 'asciidoc',
|
||
|
}
|
||
|
|
||
|
file_name, file_extension = os.path.splitext(content.source_path)
|
||
|
|
||
|
return formats[file_extension]
|
||
|
|
||
|
def check_output_dir(self, dir_):
|
||
|
"""Check and create if needed the given directory."""
|
||
|
if not os.path.isdir(dir_):
|
||
|
try:
|
||
|
os.mkdir(dir_)
|
||
|
except OSError:
|
||
|
return False
|
||
|
|
||
|
return True
|
||
|
|
||
|
def generate_files(self, content):
|
||
|
"""Generates the list of files for a given content.
|
||
|
|
||
|
:param content: the content to generate.
|
||
|
:type content: pelican.contents.Content
|
||
|
|
||
|
"""
|
||
|
|
||
|
cmd = self.settings.get('ASCIIDOCTOR_CMD', 'asciidoctor')
|
||
|
cmd_version = check_output(['asciidoctor', '--version']).decode('utf-8')
|
||
|
if not pattern.match(cmd_version):
|
||
|
logger.error("AsciiDoctorPdfGenerator: {} is not asciidoctor executable".format(cmd))
|
||
|
return
|
||
|
|
||
|
if self.settings.get('PDF_PROCESSOR', False) == False:
|
||
|
return
|
||
|
|
||
|
try:
|
||
|
from_format = self.guess_format(content)
|
||
|
except KeyError:
|
||
|
return
|
||
|
|
||
|
list_outputs = self.settings.get('ASCIIDOCTOR_PDF_OUTPUT_DIR', 'pdf')
|
||
|
output_dir = os.path.join(self.output_path, list_outputs)
|
||
|
|
||
|
if not self.check_output_dir(output_dir):
|
||
|
logger.error("Couldn't create the PDF output "
|
||
|
"folder in {dir}".format(dir=output_dir))
|
||
|
return
|
||
|
|
||
|
filename = "{id_file}.pdf".format(id_file=content.slug)
|
||
|
filepath = os.path.join(output_dir, filename)
|
||
|
|
||
|
check_call([cmd] + self.settings.get('ASCIIDOCTOR_EXTRA_OPTIONS', []) +
|
||
|
['--require', 'asciidoctor-pdf', '--backend', 'pdf'] +
|
||
|
['--doctype=article'] +
|
||
|
['--out-file', filepath, content.source_path])
|
||
|
|
||
|
logger.info("[ok] writing {filepath}".format(filepath=filepath))
|
||
|
|
||
|
def generate_output(self, writer=None):
|
||
|
"""Generate files for each articles and pages.
|
||
|
|
||
|
If ASCIIDOCTOR_EXPORT_ARTICLES is False, articles are not generated.
|
||
|
If ASCIIDOCTOR_EXPORT_PAGES is False, pages are not generated.
|
||
|
|
||
|
We don't use the writer passed as argument since we write our own
|
||
|
files ((c) PDF plugin :)).
|
||
|
|
||
|
"""
|
||
|
contents_to_export = []
|
||
|
if self.settings.get('ASCIIDOCTOR_EXPORT_ARTICLES', True):
|
||
|
contents_to_export += self.context['articles']
|
||
|
|
||
|
if self.settings.get('ASCIIDOCTOR_EXPORT_PAGES', True):
|
||
|
contents_to_export += self.context['pages']
|
||
|
|
||
|
for content_to_export in contents_to_export:
|
||
|
self.generate_files(content_to_export)
|
||
|
|
||
|
|
||
|
def add_reader(readers):
|
||
|
for ext in AsciiDoctorReader.file_extensions:
|
||
|
readers.reader_classes[ext] = AsciiDoctorReader
|
||
|
|
||
|
|
||
|
def get_generator(generator):
|
||
|
return AsciiDoctorPdfGenerator
|
||
|
|
||
|
|
||
|
def register():
|
||
|
signals.readers_init.connect(add_reader)
|
||
|
signals.get_generators.connect(get_generator)
|