asciidoc_reader.py 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  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. def call(cmd):
  14. """Calls a CLI command and returns the stdout as string."""
  15. return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()[0].decode('utf-8')
  16. def default():
  17. """Attempt to find the default AsciiDoc utility."""
  18. for cmd in ALLOWED_CMDS:
  19. if len(call(cmd + " --help")):
  20. return cmd
  21. ALLOWED_CMDS = ["asciidoc", "asciidoctor"]
  22. ENABLED = None != default()
  23. class AsciiDocReader(BaseReader):
  24. """Reader for AsciiDoc files."""
  25. enabled = ENABLED
  26. file_extensions = ['asc', 'adoc', 'asciidoc']
  27. default_options = ['--no-header-footer']
  28. def read(self, source_path):
  29. """Parse content and metadata of AsciiDoc files."""
  30. cmd = self._get_cmd()
  31. content = ""
  32. if cmd:
  33. optlist = self.settings.get('ASCIIDOC_OPTIONS', []) + self.default_options
  34. options = " ".join(optlist)
  35. content = call("%s %s -o - %s" % (cmd, options, source_path))
  36. metadata = self._read_metadata(source_path)
  37. return content, metadata
  38. def _get_cmd(self):
  39. """Returns the AsciiDoc utility command to use for rendering or None if
  40. one cannot be found."""
  41. if self.settings.get('ASCIIDOC_CMD') in ALLOWED_CMDS:
  42. return self.settings.get('ASCIIDOC_CMD')
  43. return default()
  44. def _read_metadata(self, source_path):
  45. """Parses the AsciiDoc file at the given `source_path` and returns found
  46. metadata."""
  47. metadata = {}
  48. with open(source_path) as fi:
  49. prev = ""
  50. for line in fi.readlines():
  51. # Parse for doc title.
  52. if 'title' not in metadata.keys():
  53. title = ""
  54. if line.startswith("= "):
  55. title = line[2:].strip()
  56. elif line.count("=") == len(prev.strip()):
  57. title = prev.strip()
  58. if title:
  59. metadata['title'] = self.process_metadata('title', title)
  60. # Parse for other metadata.
  61. regexp = re.compile(r"^:[A-z]+:\s*[A-z0-9]")
  62. if regexp.search(line):
  63. toks = line.split(":", 2)
  64. key = toks[1].strip().lower()
  65. val = toks[2].strip()
  66. metadata[key] = self.process_metadata(key, val)
  67. prev = line
  68. return metadata
  69. def add_reader(readers):
  70. for ext in AsciiDocReader.file_extensions:
  71. readers.reader_classes[ext] = AsciiDocReader
  72. def register():
  73. signals.readers_init.connect(add_reader)