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