# -*- 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 os.path 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) if ((not os.path.isfile(filepath)) or (os.path.getmtime(filepath) < os.path.getmtime(content.source_path))): 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)