|
|
"""Utility for calling pandoc"""
|
|
|
#-----------------------------------------------------------------------------
|
|
|
# Copyright (c) 2013 the IPython Development Team.
|
|
|
#
|
|
|
# Distributed under the terms of the Modified BSD License.
|
|
|
#
|
|
|
# The full license is in the file COPYING.txt, distributed with this software.
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
# Imports
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
from __future__ import print_function
|
|
|
|
|
|
# Stdlib imports
|
|
|
import subprocess
|
|
|
import re
|
|
|
import warnings
|
|
|
from io import TextIOWrapper, BytesIO
|
|
|
|
|
|
# IPython imports
|
|
|
from IPython.utils.py3compat import cast_bytes
|
|
|
|
|
|
from .exceptions import ConversionException
|
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
|
# Preliminary checks.
|
|
|
# Not finding Pandoc is not always fatal so only a warning is issued at the
|
|
|
# module root level so that the import of this module is not fatal.
|
|
|
#----------------------------------------------------------------------------
|
|
|
|
|
|
class PandocMissing(ConversionException):
|
|
|
"""Exception raised when Pandoc is missing. """
|
|
|
def __init__(self, cmd, exc, *args, **kwargs):
|
|
|
super(PandocMissing, self).__init__( "The command '%s' returned an error: %s.\n" %(" ".join(cmd), exc) +
|
|
|
"Please check that pandoc is installed:\n" +
|
|
|
"http://johnmacfarlane.net/pandoc/installing.html" )
|
|
|
|
|
|
def pandoc_available(failmode="return", warn=False, alt=None):
|
|
|
"""Is pandoc available. Only tries to call Pandoc
|
|
|
and inform you that it succeeded or failed.
|
|
|
|
|
|
Parameters
|
|
|
----------
|
|
|
- failmode : string
|
|
|
either "return" or "raise". If "return" and pandoc
|
|
|
is not available, will return (False, e) where e is
|
|
|
the exception returned by subprocess.check_call.
|
|
|
- warn : bool
|
|
|
issue a user warning if pandoc is not available.
|
|
|
- alt: list of strings
|
|
|
command to print in the error (not used as actual call)
|
|
|
|
|
|
Return
|
|
|
------
|
|
|
out : (Bool, Exception)
|
|
|
On success will return (True, None). On failure and failmode=="return"
|
|
|
will return (False, OSError instance)
|
|
|
"""
|
|
|
|
|
|
cmd = ["pandoc", "-v"]
|
|
|
|
|
|
try:
|
|
|
out = subprocess.check_output(cmd, universal_newlines=True)
|
|
|
return True, None
|
|
|
except OSError as e:
|
|
|
if warn:
|
|
|
warnings.warn(
|
|
|
"Pandoc cannot be found (calling %s failed).\n" % " ".join(alt or cmd) +
|
|
|
"Please check that pandoc is installed:\n" +
|
|
|
"http://johnmacfarlane.net/pandoc/installing.html"
|
|
|
)
|
|
|
|
|
|
if failmode == "return":
|
|
|
return False, e
|
|
|
else:
|
|
|
raise PandocMissing(alt or cmd, e)
|
|
|
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
|
# Classes and functions
|
|
|
#-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
minimal_version = "1.12.1"
|
|
|
|
|
|
def pandoc(source, fmt, to, extra_args=None, encoding='utf-8'):
|
|
|
"""Convert an input string in format `from` to format `to` via pandoc.
|
|
|
|
|
|
This function will raise an error if pandoc is not installed.
|
|
|
Any error messages generated by pandoc are printed to stderr.
|
|
|
|
|
|
Parameters
|
|
|
----------
|
|
|
source : string
|
|
|
Input string, assumed to be valid format `from`.
|
|
|
fmt : string
|
|
|
The name of the input format (markdown, etc.)
|
|
|
to : string
|
|
|
The name of the output format (html, etc.)
|
|
|
|
|
|
Returns
|
|
|
-------
|
|
|
out : unicode
|
|
|
Output as returned by pandoc.
|
|
|
"""
|
|
|
cmd = ['pandoc', '-f', fmt, '-t', to]
|
|
|
if extra_args:
|
|
|
cmd.extend(extra_args)
|
|
|
|
|
|
# if pandoc is missing let the exception bubble us out of here
|
|
|
pandoc_available(failmode="raise", alt=cmd)
|
|
|
|
|
|
# we can safely continue
|
|
|
p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
|
|
|
out, _ = p.communicate(cast_bytes(source, encoding))
|
|
|
out = TextIOWrapper(BytesIO(out), encoding, 'replace').read()
|
|
|
return out.rstrip('\n')
|
|
|
|
|
|
|
|
|
def get_pandoc_version():
|
|
|
"""Gets the Pandoc version if Pandoc is installed."""
|
|
|
try:
|
|
|
return pandoc.version
|
|
|
except AttributeError:
|
|
|
out = pandoc("None", "None", "None", ["-v"])
|
|
|
pv_re = re.compile(r'(\d{0,3}\.\d{0,3}\.\d{0,3})')
|
|
|
pandoc.version = pv_re.search(out).group(0)
|
|
|
return pandoc.version
|
|
|
|
|
|
def check_pandoc_version():
|
|
|
"""Returns True if minimal pandoc version is met"""
|
|
|
return get_pandoc_version() >= minimal_version
|
|
|
|
|
|
if pandoc_available(warn=True)[0]:
|
|
|
if(not check_pandoc_version()):
|
|
|
warnings.warn( "You are using an old version of pandoc (%s)\n" % pandoc.version +
|
|
|
"Recommended version is %s.\nTry updating." % minimal_version +
|
|
|
"http://johnmacfarlane.net/pandoc/installing.html.\nContinuing with doubts...",
|
|
|
RuntimeWarning, stacklevel=2)
|
|
|
|