pandoc.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. """Utility for calling pandoc"""
  2. # Copyright (c) IPython Development Team.
  3. # Distributed under the terms of the Modified BSD License.
  4. from __future__ import print_function, absolute_import
  5. import subprocess
  6. import warnings
  7. import re
  8. from io import TextIOWrapper, BytesIO
  9. from nbconvert.utils.version import check_version
  10. from ipython_genutils.py3compat import cast_bytes, which
  11. from .exceptions import ConversionException
  12. _minimal_version = "1.12.1"
  13. _maximal_version = "3.0.0"
  14. def pandoc(source, fmt, to, extra_args=None, encoding='utf-8'):
  15. """Convert an input string using pandoc.
  16. Pandoc converts an input string `from` a format `to` a target format.
  17. Parameters
  18. ----------
  19. source : string
  20. Input string, assumed to be valid format `from`.
  21. fmt : string
  22. The name of the input format (markdown, etc.)
  23. to : string
  24. The name of the output format (html, etc.)
  25. Returns
  26. -------
  27. out : unicode
  28. Output as returned by pandoc.
  29. Raises
  30. ------
  31. PandocMissing
  32. If pandoc is not installed.
  33. Any error messages generated by pandoc are printed to stderr.
  34. """
  35. cmd = ['pandoc', '-f', fmt, '-t', to]
  36. if extra_args:
  37. cmd.extend(extra_args)
  38. # this will raise an exception that will pop us out of here
  39. check_pandoc_version()
  40. # we can safely continue
  41. p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  42. out, _ = p.communicate(cast_bytes(source, encoding))
  43. out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
  44. return out.rstrip('\n')
  45. def get_pandoc_version():
  46. """Gets the Pandoc version if Pandoc is installed.
  47. If the minimal version is not met, it will probe Pandoc for its version, cache it and return that value.
  48. If the minimal version is met, it will return the cached version and stop probing Pandoc
  49. (unless :func:`clean_cache()` is called).
  50. Raises
  51. ------
  52. PandocMissing
  53. If pandoc is unavailable.
  54. """
  55. global __version
  56. if __version is None:
  57. if not which('pandoc'):
  58. raise PandocMissing()
  59. out = subprocess.check_output(['pandoc', '-v'])
  60. out_lines = out.splitlines()
  61. version_pattern = re.compile(r"^\d+(\.\d+){1,}$")
  62. for tok in out_lines[0].decode('ascii', 'replace').split():
  63. if version_pattern.match(tok):
  64. __version = tok
  65. break
  66. return __version
  67. def check_pandoc_version():
  68. """Returns True if pandoc's version meets at least minimal version.
  69. Raises
  70. ------
  71. PandocMissing
  72. If pandoc is unavailable.
  73. """
  74. if check_pandoc_version._cached is not None:
  75. return check_pandoc_version._cached
  76. v = get_pandoc_version()
  77. if v is None:
  78. warnings.warn("Sorry, we cannot determine the version of pandoc.\n"
  79. "Please consider reporting this issue and include the"
  80. "output of pandoc --version.\nContinuing...",
  81. RuntimeWarning, stacklevel=2)
  82. return False
  83. ok = check_version(v, _minimal_version, max_v=_maximal_version)
  84. check_pandoc_version._cached = ok
  85. if not ok:
  86. warnings.warn( "You are using an unsupported version of pandoc (%s).\n" % v +
  87. "Your version must be at least (%s) " % _minimal_version +
  88. "but less than (%s).\n" % _maximal_version +
  89. "Refer to http://pandoc.org/installing.html.\nContinuing with doubts...",
  90. RuntimeWarning, stacklevel=2)
  91. return ok
  92. check_pandoc_version._cached = None
  93. #-----------------------------------------------------------------------------
  94. # Exception handling
  95. #-----------------------------------------------------------------------------
  96. class PandocMissing(ConversionException):
  97. """Exception raised when Pandoc is missing."""
  98. def __init__(self, *args, **kwargs):
  99. super(PandocMissing, self).__init__( "Pandoc wasn't found.\n" +
  100. "Please check that pandoc is installed:\n" +
  101. "http://pandoc.org/installing.html" )
  102. #-----------------------------------------------------------------------------
  103. # Internal state management
  104. #-----------------------------------------------------------------------------
  105. def clean_cache():
  106. global __version
  107. __version = None
  108. __version = None