commit 9e0019a728113f8502d04eeca8bf2682219086b6 Author: Andrey Astafyev Date: Sun Jun 2 13:17:13 2019 +0300 Init diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..57d84de --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ +from .asciidoctor import * diff --git a/asciidoctor.py b/asciidoctor.py new file mode 100644 index 0000000..5d3df32 --- /dev/null +++ b/asciidoctor.py @@ -0,0 +1,181 @@ +# -*- 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)