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