asciidoc_reader.py 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. # -*- coding: utf-8 -*-
  2. """
  3. AsciiDoc Reader
  4. ===============
  5. This plugin allows you to use AsciiDoc to write your posts.
  6. File extension should be ``.asc``, ``.adoc``, or ``asciidoc``.
  7. """
  8. from pelican.readers import BaseReader
  9. from pelican import signals
  10. import os
  11. import re
  12. import subprocess
  13. import sys
  14. def call(cmd):
  15. """Calls a CLI command and returns the stdout as string."""
  16. return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0].decode('utf-8')
  17. def default():
  18. """Attempt to find the default AsciiDoc utility."""
  19. for cmd in ALLOWED_CMDS:
  20. if len(call(cmd + " --help")):
  21. return cmd
  22. def fix_unicode(val):
  23. if sys.version_info < (3,0):
  24. val = unicode(val.decode("utf-8"))
  25. else:
  26. # This fixes an issue with character substitutions, e.g. 'ñ' to 'ñ'.
  27. val = str.encode(val, "latin-1").decode("utf-8")
  28. return val
  29. ALLOWED_CMDS = ["asciidoc", "asciidoctor"]
  30. ENABLED = None != default()
  31. class AsciiDocReader(BaseReader):
  32. """Reader for AsciiDoc files."""
  33. enabled = ENABLED
  34. file_extensions = ['asc', 'adoc', 'asciidoc']
  35. default_options = ['--no-header-footer']
  36. def read(self, source_path):
  37. """Parse content and metadata of AsciiDoc files."""
  38. cmd = self._get_cmd()
  39. content = ""
  40. if cmd:
  41. optlist = self.settings.get('ASCIIDOC_OPTIONS', []) + self.default_options
  42. options = " ".join(optlist)
  43. content = call("%s %s -o - %s" % (cmd, options, source_path))
  44. metadata = self._read_metadata(source_path)
  45. return content, metadata
  46. def _get_cmd(self):
  47. """Returns the AsciiDoc utility command to use for rendering or None if
  48. one cannot be found."""
  49. if self.settings.get('ASCIIDOC_CMD') in ALLOWED_CMDS:
  50. return self.settings.get('ASCIIDOC_CMD')
  51. return default()
  52. def _read_metadata(self, source_path):
  53. """Parses the AsciiDoc file at the given `source_path` and returns found
  54. metadata."""
  55. metadata = {}
  56. with open(source_path) as fi:
  57. prev = ""
  58. for line in fi.readlines():
  59. # Parse for doc title.
  60. if 'title' not in metadata.keys():
  61. title = ""
  62. if line.startswith("= "):
  63. title = line[2:].strip()
  64. elif line.count("=") == len(prev.strip()):
  65. title = prev.strip()
  66. if title:
  67. metadata['title'] = self.process_metadata('title', fix_unicode(title))
  68. # Parse for other metadata.
  69. regexp = re.compile(r"^:[A-z]+:\s*[A-z0-9]")
  70. if regexp.search(line):
  71. toks = line.split(":", 2)
  72. key = toks[1].strip().lower()
  73. val = toks[2].strip()
  74. metadata[key] = self.process_metadata(key, fix_unicode(val))
  75. prev = line
  76. return metadata
  77. def add_reader(readers):
  78. for ext in AsciiDocReader.file_extensions:
  79. readers.reader_classes[ext] = AsciiDocReader
  80. def register():
  81. signals.readers_init.connect(add_reader)