-
Notifications
You must be signed in to change notification settings - Fork 846
/
asciidoc_reader.py
121 lines (104 loc) · 4.43 KB
/
asciidoc_reader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# -*- coding: utf-8 -*-
"""
AsciiDoc Reader
===============
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 import signals
import os
import re
import shutil
import subprocess
import tempfile
import logging
logger = logging.getLogger(__name__)
def encoding():
"""Return encoding used to decode shell output in call function"""
if os.name == 'nt':
from ctypes import cdll
return 'cp' + str(cdll.kernel32.GetOEMCP())
return 'utf-8'
def call(cmd):
"""Calls a CLI command and returns the stdout as string."""
logger.debug('AsciiDocReader: Running: %s', cmd)
stdoutdata, stderrdata = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
if stderrdata:
logger.warning('AsciiDocReader: strderr: %s', stderrdata)
return stdoutdata.decode(encoding())
def default():
"""Attempt to find the default AsciiDoc utility."""
for cmd in ALLOWED_CMDS:
if shutil.which(cmd):
logger.debug('AsciiDocReader: Using cmd: %s', cmd)
return cmd
ALLOWED_CMDS = ["asciidoc", "asciidoctor"]
ENABLED = None != default()
class AsciiDocReader(BaseReader):
"""Reader for AsciiDoc files."""
enabled = ENABLED
file_extensions = ['asc', 'adoc', 'asciidoc']
default_options = ['--no-header-footer']
def read(self, source_path):
"""Parse content and metadata of AsciiDoc files."""
cmd = self._get_cmd()
content = ""
if cmd:
logger.debug('AsciiDocReader: Reading: %s', source_path)
optlist = self.settings.get('ASCIIDOC_OPTIONS', []) + self.default_options
options = " ".join(optlist)
# Beware! # Don't use tempfile.NamedTemporaryFile under Windows: https://bugs.python.org/issue14243
# Also, use mkstemp correctly (Linux and Windows): https://www.logilab.org/blogentry/17873
fd, temp_name = tempfile.mkstemp()
content = call("%s %s -o %s \"%s\"" % (cmd, options, temp_name, source_path))
with open(temp_name, encoding='utf-8') as f:
content = f.read()
os.close(fd)
os.unlink(temp_name)
metadata = self._read_metadata(source_path)
logger.debug('AsciiDocReader: Got content (showing first 50 chars): %s', (content[:50] + '...') if len(content) > 50 else content)
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, encoding='utf-8') as fi:
prev = ""
lines = iter(fi.readlines())
for line in lines:
# 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)
# Parse for other metadata.
regexp = re.compile(r"^:\w+:")
if regexp.search(line):
toks = line.split(":", 2)
key = toks[1].strip().lower()
val = toks[2].strip()
# Support for soft-wrapped attributes:
# https://docs.asciidoctor.org/asciidoc/latest/attributes/wrap-values/#soft-wrap-attribute-values
while val.endswith("\\"):
line = next(lines)
val = val[:-1] + line.strip()
metadata[key] = self.process_metadata(key, val)
prev = line
logger.debug('AsciiDocReader: Found metadata: %s', metadata)
return metadata
def add_reader(readers):
for ext in AsciiDocReader.file_extensions:
readers.reader_classes[ext] = AsciiDocReader
def register():
signals.readers_init.connect(add_reader)