├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── examples ├── codeblock_output.md ├── runnable_three_figures_dif_cells.md ├── runnable_three_figures_dif_cells_fig_num_specified.md ├── runnable_two_figures.md ├── table.md └── with-fignos.md ├── filter_pandoc_run_py ├── __init__.py └── filter_pandoc_run_py.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── note1.md ├── test.md ├── test_common_mark.md ├── test_filter_pandoc_run_py.py ├── test_matplotlib.py └── test_units.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | **/plt-images/ 3 | filter_pandoc_run_py_env/ 4 | testconv*.html 5 | testconv*.md 6 | **/*_converted.* 7 | **/test.json 8 | 9 | # Byte-compiled / optimized / DLL files 10 | __pycache__/ 11 | **/__pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | *.egg-info/ 33 | .installed.cfg 34 | *.egg 35 | MANIFEST 36 | 37 | # PyInstaller 38 | # Usually these files are written by a python script from a template 39 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 40 | *.manifest 41 | *.spec 42 | 43 | # Installer logs 44 | pip-log.txt 45 | pip-delete-this-directory.txt 46 | 47 | # Unit test / coverage reports 48 | htmlcov/ 49 | .tox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | .hypothesis/ 57 | 58 | # Translations 59 | *.mo 60 | *.pot 61 | 62 | # Django stuff: 63 | *.log 64 | .static_storage/ 65 | .media/ 66 | local_settings.py 67 | 68 | # Flask stuff: 69 | instance/ 70 | .webassets-cache 71 | 72 | # Scrapy stuff: 73 | .scrapy 74 | 75 | # Sphinx documentation 76 | docs/_build/ 77 | 78 | # PyBuilder 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # celery beat schedule file 88 | celerybeat-schedule 89 | 90 | # SageMath parsed files 91 | *.sage.py 92 | 93 | # Environments 94 | .env 95 | .venv 96 | env/ 97 | venv/ 98 | ENV/ 99 | env.bak/ 100 | venv.bak/ 101 | 102 | # Spyder project settings 103 | .spyderproject 104 | .spyproject 105 | 106 | # Rope project settings 107 | .ropeproject 108 | 109 | # mkdocs documentation 110 | /site 111 | 112 | # mypy 113 | .mypy_cache/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | # - "2.6" 4 | # - "2.7" 5 | # - "3.3" 6 | #- "3.4" 7 | - "3.5" 8 | - "3.5-dev" # 3.5 development branch 9 | - "3.6" 10 | - "3.6-dev" # 3.6 development branch 11 | #- "3.7-dev" # 3.7 development branch 12 | #- "nightly" 13 | # command to install dependencies 14 | #addons: 15 | # apt_packages: 16 | # - pandoc 17 | before_install: 18 | - wget https://github.com/jgm/pandoc/releases/download/2.1.1/pandoc-2.1.1-1-amd64.deb 19 | - sudo dpkg -i pandoc-2.1.1-1-amd64.deb 20 | install: 21 | - pip install -r requirements.txt 22 | - pip install coveralls 23 | # command to run tests 24 | script: 25 | # - pytest # or py.test for Python versions 3.5 and below 26 | - coverage run --source=filter_pandoc_run_py setup.py pytest 27 | after_success: 28 | - coveralls -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2017, caiofcm 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # filter_pandoc_run_py 2 | 3 | [![Build Status](https://img.shields.io/travis/caiofcm/filter_pandoc_run_py/master.svg)](https://travis-ci.org/caiofcm/filter_pandoc_run_py/branches) 4 | [![Coverage Status](https://coveralls.io/repos/github/caiofcm/filter_pandoc_run_py/badge.svg?branch=master)](https://coveralls.io/github/caiofcm/filter_pandoc_run_py?branch=master) 5 | [![PyPI version](https://img.shields.io/pypi/v/filter_pandoc_run_py.svg)](https://pypi.org/project/filter_pandoc_run_py/) 6 | [![PyPI format](https://img.shields.io/pypi/format/filter_pandoc_run_py.svg)](https://pypi.org/project/filter_pandoc_run_py/) 7 | [![License](https://img.shields.io/pypi/l/filter_pandoc_run_py.svg)](https://raw.githubusercontent.com/caiofcm/filter_pandoc_run_py/master/LICENSE) 8 | [![Python version](https://img.shields.io/pypi/pyversions/filter_pandoc_run_py.svg)](https://pypi.org/project/filter_pandoc_run_py/) 9 | [![Development Status](https://img.shields.io/pypi/status/filter_pandoc_run_py.svg)](https://pypi.org/project/filter_pandoc_run_py/) 10 | 11 | *filter_pandoc_run_py* is a [pandoc] filter for execute python codes written in `CodeBlocks` or inline `Code`. It receives the print statement output and place it to the markdown converted file. Also, it save any created pyplot figure to a folder and include it as an image. Code has to be **trusted** 12 | 13 | [pandoc]: http://pandoc.org/ 14 | 15 | 16 | 17 | ## Usage 18 | 19 | To apply the filter, use the following option with pandoc: 20 | 21 | pandoc INPUT_FILE -F filter_pandoc_run_py --to OUTPUT_FORMAT -o OUTPUT_FILE 22 | 23 | Example: 24 | 25 | pandoc ./tests/test.md -F filter_pandoc_run_py -t gfm -o test_converted.md 26 | 27 | - You can convert it to any pandoc supported format; 28 | - When converted to a markdown format it can change some part of the text to conform with the default style (e.g. changing setext-style headers to ATX headers). 29 | 30 | ## Installation 31 | 32 | *filter_pandoc_run_py* requires [python] (tested in version > 3.0) 33 | 34 | Install *filter_pandoc_run_py* as root using the bash command 35 | 36 | git clone URL 37 | cd dir 38 | pip install . 39 | 40 | Or get it from PYPI: 41 | 42 | pip install filter_pandoc_run_py 43 | 44 | 45 | ## How to Use It 46 | 47 | Create a regular markdown code but appending a class .run to it. 48 | 49 | ### For `CodeBlock` 50 | 51 | Output print statement as a BlockQuote or paragraph. You can hide the generation code. 52 | 53 | Syntax: `{.python .run format=[blockquote (default), text] hide_code=[False (default), True] }` 54 | 55 | The following syntax is also support for enabling standard IDE code highlight: 56 | 57 | ```python 58 | #filter: {.run format=[blockquote (default), text] hide_code=[False (default), True] } 59 | .... code .... 60 | ``` 61 | 62 | "Pretty print" enable: output of print statement is converted and is rendered 63 | 64 | ### For `Code` 65 | 66 | Output print statement as inline text. 67 | 68 | The syntax is: 69 | 70 | `print(code)`{.run} 71 | 72 | "Pretty print" enable: output of print statement is converted and is rendered 73 | 74 | ## Example 75 | 76 | From a markdown file such as: 77 | 78 | ```{.python .run} 79 | d = 1e3 80 | m = 2 * d 81 | print('The total mass is {:.2f} $m^3$'.format(m)) 82 | ``` 83 | 84 | `pandoc FILE --to markdown -F filter_pandoc_run_py -o OUTFILE.md` 85 | 86 | ```{.markdown} 87 | > Output: 88 | > 89 | > > The total mass is 2000.00 $m^3$ 90 | ``` 91 | 92 | Generating pyplot images embedded in markdown file: 93 | 94 | ```{.python .run caption="Figure Number One" label="my_fig"} 95 | import matplotlib 96 | matplotlib.use('AGG') 97 | from matplotlib import pyplot as plt 98 | plt.plot([1, 2], [3, 4], 'dr-') 99 | ``` 100 | 101 | ### More examples 102 | 103 | - Check files `./tests/test.md` and `./tests/test_common_mark.md` 104 | 105 | ## Getting Help 106 | 107 | If you have any difficulties with *filter_pandoc_run_py*, please feel welcome to [file an issue] on github so that we can help. 108 | 109 | [file an issue]: https://github.com/caiofcm/filter_pandoc_run_py/issues 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /examples/codeblock_output.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caiofcm/filter_pandoc_run_py/70d0a8167658a20caba9e29e0225c3b6543c8f8d/examples/codeblock_output.md -------------------------------------------------------------------------------- /examples/runnable_three_figures_dif_cells.md: -------------------------------------------------------------------------------- 1 | 2 | # Creates Three figures Matplotlib figure 3 | 4 | ```python 5 | #filter: {.run caption="Figure Number One" label="my_fig" hide_code=true title_as_caption=true figattr="#fig:1 width=9in"} 6 | import matplotlib 7 | matplotlib.use('AGG') 8 | from matplotlib import pyplot as plt 9 | plt.figure() 10 | plt.plot([1, 2], [-1, -4], 'or-') 11 | plt.title('This is figure caption from python') 12 | ``` 13 | 14 | ```python 15 | #filter: {.run caption="Second Figure" 16 | # caption2="Third Figure" 17 | # label="my_fig_2" 18 | # label2="my_fig_3" 19 | # hide_code=true 20 | # figattr="#fig:2 width=8in" 21 | # figattr2="#fig:3 width=3in tag='B.1'" 22 | # 23 | # } 24 | 25 | import numpy as np 26 | from mpl_toolkits.mplot3d import Axes3D 27 | from matplotlib import cm 28 | 29 | # First Figure 30 | plt.figure() 31 | plt.plot([1, 2], [3, 4], 'dg-') 32 | 33 | 34 | fig = plt.figure() 35 | ax = fig.gca(projection='3d') 36 | X = np.arange(-5, 5, 0.25) 37 | Y = np.arange(-5, 5, 0.25) 38 | X, Y = np.meshgrid(X, Y) 39 | Z = np.sin(np.sqrt(X**2 + Y**2)) 40 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 41 | fig.colorbar(surf, shrink=0.5, aspect=5) 42 | ``` 43 | 44 | -------------------------------------------------------------------------------- /examples/runnable_three_figures_dif_cells_fig_num_specified.md: -------------------------------------------------------------------------------- 1 | 2 | # Creates Three figures Matplotlib figure 3 | 4 | ```python 5 | #filter: {.run caption="Figure Number One" label="my_fig" hide_code=true title_as_caption=true} 6 | import matplotlib 7 | matplotlib.use('AGG') 8 | from matplotlib import pyplot as plt 9 | plt.figure(num=2) 10 | plt.plot([1, 2], [-1, -4], 'or-') 11 | plt.title('This is figure caption from python') 12 | ``` 13 | 14 | ```python 15 | #filter: {.run caption="Second Figure" caption2="Third Figure" label="my_fig_2" label2="my_fig_3" hide_code=true} 16 | # import matplotlib 17 | # matplotlib.use('AGG') 18 | # from matplotlib import pyplot as plt 19 | import numpy as np 20 | from mpl_toolkits.mplot3d import Axes3D 21 | from matplotlib import cm 22 | 23 | # First Figure 24 | plt.figure(num=3) 25 | plt.plot([1, 2], [3, 4], 'dg-') 26 | 27 | 28 | fig = plt.figure(num=4) 29 | ax = fig.gca(projection='3d') 30 | X = np.arange(-5, 5, 0.25) 31 | Y = np.arange(-5, 5, 0.25) 32 | X, Y = np.meshgrid(X, Y) 33 | Z = np.sin(np.sqrt(X**2 + Y**2)) 34 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 35 | fig.colorbar(surf, shrink=0.5, aspect=5) 36 | ``` 37 | 38 | -------------------------------------------------------------------------------- /examples/runnable_two_figures.md: -------------------------------------------------------------------------------- 1 | 2 | # Creates Two Matplotlib figure 3 | 4 | ```python 5 | #filter: {.run caption2="Number One" caption3="Other Figure" label2="my_fig_2" label3="my_fig_3"} 6 | import matplotlib 7 | matplotlib.use('AGG') 8 | from matplotlib import pyplot as plt 9 | from mpl_toolkits.mplot3d import Axes3D 10 | from matplotlib import cm 11 | import numpy as np 12 | 13 | # First Figure 14 | plt.plot([1, 2], [3, 4], 'dg-') 15 | 16 | # Second Figure 17 | fig = plt.figure() 18 | ax = fig.gca(projection='3d') 19 | X = np.arange(-5, 5, 0.25) 20 | Y = np.arange(-5, 5, 0.25) 21 | X, Y = np.meshgrid(X, Y) 22 | Z = np.sin(np.sqrt(X**2 + Y**2)) 23 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 24 | fig.colorbar(surf, shrink=0.5, aspect=5) 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /examples/table.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/caiofcm/filter_pandoc_run_py/70d0a8167658a20caba9e29e0225c3b6543c8f8d/examples/table.md -------------------------------------------------------------------------------- /examples/with-fignos.md: -------------------------------------------------------------------------------- 1 | --- 2 | fignos-cleveref: True 3 | fignos-plus-name: Fig. 4 | header-includes: \usepackage{caption} 5 | ... 6 | 7 | Reference to @fig:1. 8 | 9 | ![The number one.](plt-images/8f1a04c1b6419e08455f95cd1f28cfa7bddf2caa.png){#fig:1 width=5in} 10 | 11 | ![The number two.](plt-images/../../plt-images/8f1a04c1b6419e08455f95cd1f28cfa7bddf2caa.png){#fig:2 width=5in} 12 | 13 | *@fig:2 is given above. 14 | 15 | ![The number three.](plt-images/b59b4a1518715d260505263dcf0fe4bc375bdcae.png){#fig: width=1in} 16 | 17 | ```python 18 | #filter: {.run caption="Figure Number One" label="my_fig" hide_code=true title_as_caption=true} 19 | import matplotlib 20 | matplotlib.use('AGG') 21 | from matplotlib import pyplot as plt 22 | plt.figure() 23 | plt.plot([1, 2], [-1, -4], 'or-') 24 | plt.title('This is figure caption from python') 25 | ``` 26 | 27 | ```python 28 | #filter: {.run caption="Second Figure" caption2="Third Figure" label="my_fig_2" label2="my_fig_3" hide_code=true} 29 | # import matplotlib 30 | # matplotlib.use('AGG') 31 | # from matplotlib import pyplot as plt 32 | import numpy as np 33 | from mpl_toolkits.mplot3d import Axes3D 34 | from matplotlib import cm 35 | 36 | # First Figure 37 | plt.figure() 38 | plt.plot([1, 2], [3, 4], 'dg-') 39 | 40 | 41 | fig = plt.figure() 42 | ax = fig.gca(projection='3d') 43 | X = np.arange(-5, 5, 0.25) 44 | Y = np.arange(-5, 5, 0.25) 45 | X, Y = np.meshgrid(X, Y) 46 | Z = np.sin(np.sqrt(X**2 + Y**2)) 47 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 48 | fig.colorbar(surf, shrink=0.5, aspect=5) 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /filter_pandoc_run_py/__init__.py: -------------------------------------------------------------------------------- 1 | # from .PrintCollector import * 2 | from .filter_pandoc_run_py import * 3 | -------------------------------------------------------------------------------- /filter_pandoc_run_py/filter_pandoc_run_py.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Pandoc filter to process code blocks with class "python" into python output 5 | """ 6 | 7 | import os 8 | import sys 9 | from shutil import which 10 | from subprocess import Popen, PIPE 11 | import json 12 | import re 13 | try: 14 | from StringIO import StringIO 15 | except ImportError: 16 | from io import StringIO 17 | import contextlib 18 | 19 | from pandocfilters import * 20 | 21 | ############################################ 22 | ########################################### 23 | # 24 | # 25 | # Global Scope 26 | # 27 | # 28 | ########################################### 29 | ########################################### 30 | 31 | dir_path = os.path.dirname(os.path.realpath(__file__)) 32 | code_locals = {'fig_counter': 0, 'used_fig_num': []} 33 | 34 | 35 | ############################################ 36 | ########################################### 37 | # 38 | # 39 | # Aux Functions 40 | # 41 | # 42 | ########################################### 43 | ########################################### 44 | 45 | def run_pandoc(text='', args=['--from=markdown', '--to=json']): 46 | """ 47 | Low level function that calls Pandoc with (optionally) 48 | some input text and/or arguments 49 | Reference: http://scorreia.com/software/panflute/_modules/panflute/tools.html#run_pandoc 50 | """ 51 | pandoc_path = which('pandoc') 52 | if pandoc_path is None or not os.path.exists(pandoc_path): 53 | raise OSError("Path to pandoc executable does not exists") 54 | 55 | proc = Popen([pandoc_path] + args, stdin=PIPE, stdout=PIPE, stderr=PIPE) 56 | out, err = proc.communicate(input=text.encode('utf-8')) 57 | exitcode = proc.returncode 58 | if exitcode != 0: 59 | raise IOError(err) 60 | return out.decode('utf-8') 61 | 62 | ############################################ 63 | ########################################### 64 | # 65 | # 66 | # Start Functions Definitions 67 | # 68 | # 69 | ########################################### 70 | ########################################### 71 | 72 | def read_json(filename, mode = 'json'): 73 | with open(os.path.join(dir_path, filename), 'r') as fp: 74 | if mode == 'json': 75 | dt = json.load(fp) 76 | elif mode == 'string': 77 | dt = fp.read() 78 | return dt 79 | 80 | @contextlib.contextmanager 81 | def stdoutIO(stdout=None): 82 | ''' 83 | code = """ 84 | i = [0,1,2] 85 | for j in i : 86 | print(j) 87 | """ 88 | with stdoutIO() as s: 89 | exec(code) 90 | 91 | print("out:", s.getvalue()) 92 | assert s.getvalue() == '0\n1\n2\n' 93 | ''' 94 | old = sys.stdout 95 | if stdout is None: 96 | stdout = StringIO() 97 | sys.stdout = stdout 98 | yield stdout 99 | sys.stdout = old 100 | 101 | def run_code(source_code): 102 | try: 103 | # byte_code, errors = compile(source_code, '')[0:2] 104 | # exec(source_code, {}, code_locals) # {'__builtins__': safe_builtins} 105 | with stdoutIO() as s: 106 | exec(source_code, {}, code_locals) 107 | print_string = s.getvalue() 108 | except SyntaxError as e: 109 | raise e 110 | print_string = 'Code failed to Run' 111 | return print_string 112 | 113 | 114 | def from_txt_to_ast_pandoc_code(printed_var): 115 | # if breakLine: 116 | # printed_var = "\n\n".join(printed_var.split("\n")) 117 | txt_as_pandoc_obj_str = run_pandoc(printed_var) 118 | out = "\n".join(txt_as_pandoc_obj_str.splitlines()) # Replace \r\n with \n 119 | txt_as_pandoc_obj = json.loads(txt_as_pandoc_obj_str) 120 | metaData = txt_as_pandoc_obj['meta'] 121 | astCode = txt_as_pandoc_obj['blocks'] 122 | return metaData, astCode 123 | 124 | def adjust_print_output(printed_var, format_type = None): 125 | format_type = 'blockquote' if format_type is None else format_type 126 | if format_type == 'blockquote': 127 | printed_var = "\n\n".join(printed_var.split("\n")) 128 | txt_as_pandoc_obj = from_txt_to_ast_pandoc_code(printed_var) 129 | cod = [ BlockQuote([Para([Str('Output:')]), 130 | BlockQuote(txt_as_pandoc_obj[1])])] 131 | elif format_type == 'text': 132 | txt_as_pandoc_obj = from_txt_to_ast_pandoc_code(printed_var) 133 | cod = txt_as_pandoc_obj[1] 134 | return cod 135 | 136 | def get_key_in_keyval_list(keyvals, key, fallback_not_found): 137 | value = fallback_not_found 138 | for kw in keyvals: 139 | if key == kw[0]: 140 | value = kw[1] 141 | break 142 | return value 143 | 144 | def handle_inline_plot(code, classes, keyvals, format, ident): 145 | plt = code_locals['plt'] 146 | fignums = plt.get_fignums() 147 | kw = keyvals 148 | ast_ret_code = [] 149 | md_code = '' 150 | last_number_counter = len(code_locals['used_fig_num']) 151 | # ast_fig_kw = kw.copy() #addition keyvalus ? todo 152 | for num in fignums: #and num not in code_locals['used_fig_num']: 153 | if num in code_locals['used_fig_num']: 154 | continue 155 | unique_code = repr(plt.figure(num)) + str(num) 156 | fname = get_filename4code("plt", unique_code) 157 | num_mod = num - last_number_counter 158 | 159 | use_title = bool(get_key_in_keyval_list(kw, 'title_as_caption', False)) 160 | if not use_title: 161 | kw_cap = 'caption{}'.format(num_mod) if num_mod > 1 else 'caption' 162 | caption = get_key_in_keyval_list(kw, kw_cap, '') # fname.split('\\')[1] 163 | else: 164 | caption = plt.gca().title._text 165 | 166 | # kw_lbl = 'label{}'.format(num_mod) if num_mod > 1 else 'label' 167 | # label = get_key_in_keyval_list(kw, kw_lbl, 'label-{}'.format(num)) 168 | 169 | # kw_wdth = 'width{}'.format(num) if num > 1 else 'width' 170 | # width = get_key_in_keyval_list(kw, kw_wdth, '') 171 | 172 | # Parei aqui-> figures attribute 173 | kw_figattr = 'figattr{}'.format(num_mod) if num_mod > 1 else 'figattr' 174 | figattr_raw = get_key_in_keyval_list(kw, kw_figattr, '') 175 | if figattr_raw != '': 176 | figattr_id, figattr_kws = figattr_str_convertion(figattr_raw) 177 | else: 178 | figattr_id, figattr_kws = '', [] 179 | 180 | kw_ext = 'ext{}'.format(num) if num > 1 else 'ext' 181 | ext = get_key_in_keyval_list(kw, kw_ext, 'png') 182 | 183 | filePath = '{}.{}'.format(fname, ext) 184 | plt.savefig(filePath, format=ext) 185 | ast_ret_code += [Para([Image([figattr_id, [], figattr_kws], 186 | [Str(caption)], [filePath, "fig:"])])] 187 | code_locals['used_fig_num'].append(num) 188 | return ast_ret_code 189 | 190 | def figattr_str_convertion(s): 191 | fig_id = re.findall(r'#([\w\:]+)', s) 192 | if len(fig_id) == 0: 193 | raise ValueError('Error capturing figure ID.') 194 | elif len(fig_id) > 1: 195 | raise ValueError('Error capturing figure ID. Use single figure ID') 196 | kws = re.findall(r'(\w+)=([\w\'\.\:]+)', s) 197 | return fig_id[0], kws 198 | 199 | 200 | def workaround_classes_with_commonmark_syntax(code, classes, keyvals, value): 201 | ''' 202 | Configuration string as a pytho comment to get classes and key-vals 203 | Example of configuration: #filter: {.c1 .c2 key1=val1 key2="value 2" } 204 | ''' 205 | try: 206 | re.search(r'^\s*#\s*filter:\s+{', code).group() 207 | except AttributeError: 208 | return 209 | filter_configs = code[code.find("{") + 1:code.find("}")] 210 | found_class = re.findall(r'\.(\w+)', filter_configs) 211 | kw_pairs_simple = re.findall(r'(\w+)=(\w+)', filter_configs) 212 | # kw_pairs_cplx = re.findall(r'(\w+)=("[\w\-\s]+")', filter_configs) 213 | kw_pairs_cplx = re.findall(r'(\w+)=("[^"]+")', filter_configs) 214 | found_keyvals = kw_pairs_simple + kw_pairs_cplx 215 | classes += found_class 216 | keyvals += found_keyvals 217 | # Modiy Value to remove filter line: 218 | value[1] = code[code.find("}")+1:] 219 | return 220 | 221 | def run_py_code_block(key, value, format, meta): 222 | return_ast = [] 223 | if key == 'CodeBlock': 224 | [[ident, classes, keyvals], code] = value 225 | workaround_classes_with_commonmark_syntax(code, classes, keyvals, value) 226 | 227 | if "python" in classes and "run" in classes: 228 | # caption, typef, keyvals = get_caption(keyvals) 229 | 230 | printed_string = run_code(code) 231 | 232 | # show_inpt = list(filter(lambda v: v[0] == 'show_input', keyvals)) 233 | # if show_inpt is not None and show_inpt[0][1] != 'False': 234 | hide_code = get_value(keyvals, 'hide_code')[0] 235 | if hide_code is None or hide_code == 'False': 236 | ast_code = CodeBlock(value[0], value[1]) 237 | return_ast.append(ast_code) 238 | 239 | if printed_string: 240 | format_type = get_value(keyvals, 'format')[0] 241 | ast_print = adjust_print_output(printed_string, format_type) 242 | return_ast += ast_print 243 | 244 | if 'plt' in code_locals: 245 | fignums = code_locals['plt'].get_fignums() 246 | if len(fignums) > 0: 247 | ast_figure = handle_inline_plot(code, classes, keyvals, format, ident) 248 | # code_locals['plt'].close('all') 249 | return_ast += ast_figure 250 | # Continue from here: 251 | # https://github.com/jgm/pandocfilters/blob/master/examples/plantuml.py 252 | 253 | return return_ast 254 | elif key == 'Code': 255 | [[ident, classes, keyvals], code] = value 256 | if "run" in classes: 257 | printed_string = run_code(code) 258 | removed_last_line_break = printed_string[0:-1] 259 | 260 | if printed_string: 261 | metaAst, ast_print = from_txt_to_ast_pandoc_code(removed_last_line_break) 262 | # # ast_print[0]['t'] = 'Str' 263 | # # return_ast.append(ast_print) 264 | # return_ast.append([Para('Hello you')]) 265 | return ast_print[0]['c'] 266 | # return Str(removed_last_line_break) 267 | # return RawInline('html', 'Hello') 268 | # return return_ast 269 | # # return_ast += [Str(removed_last_line_break)] 270 | # # if printed_string: 271 | # return return_ast 272 | # return Str('Yes!') 273 | pass 274 | 275 | def main(): 276 | toJSONFilter(run_py_code_block) 277 | 278 | ############################################ 279 | ########################################### 280 | # 281 | # 282 | # Start 283 | # 284 | # 285 | ########################################### 286 | ########################################### 287 | 288 | if __name__ == "__main__": 289 | main() 290 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cycler==0.10.0 2 | ## !! Could not determine repository location 3 | filter-pandoc-run-py==0.1 4 | matplotlib==2.1.1 5 | numpy==1.13.3 6 | pandocfilters==1.4.2 7 | pyparsing==2.2.0 8 | python-dateutil==2.6.1 9 | pytz==2017.3 10 | six==1.11.0 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.rst -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup, find_packages 3 | 4 | try: 5 | import pypandoc 6 | long_description = pypandoc.convert('README.md', 'rst') 7 | except(IOError, ImportError): 8 | long_description = open('README.md').read() 9 | 10 | def read(fname): 11 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 12 | 13 | setup(name='filter_pandoc_run_py', 14 | version='0.6.2', 15 | description="Pandoc filter to run python code blocks", 16 | long_description=long_description, 17 | url='https://github.com/caiofcm/filter_pandoc_run_py', 18 | download_url='https://github.com/caiofcm/filter_pandoc_run_py/archive/0.1.tar.gz', 19 | author='Caio Marcellos', 20 | author_email='caiocuritiba@gmail.com', 21 | license='MIT', 22 | packages=find_packages(), 23 | install_requires=['pandocfilters', 'matplotlib'], 24 | keywords='pandoc filters markdown python notes', 25 | zip_safe=False, 26 | py_modules=["filter_pandoc_run_py.filter_pandoc_run_py"], 27 | entry_points={ 28 | 'console_scripts': [ 29 | 'filter_pandoc_run_py = filter_pandoc_run_py.filter_pandoc_run_py:main', 30 | ], 31 | }, 32 | extras_require={ 33 | 'dev': ['check-manifest'], 34 | 'test': ['coverage'], 35 | }, 36 | setup_requires=['pytest-runner'], 37 | tests_require=['pytest', 'coverage'], 38 | classifiers=[ 39 | "Development Status :: 3 - Alpha", 40 | "Topic :: Utilities", 41 | "License :: OSI Approved :: BSD License", # to be reviewed 42 | 'Programming Language :: Python :: 3.4', 43 | 'Programming Language :: Python :: 3.5', 44 | 'Programming Language :: Python :: 3.6', 45 | 46 | 'Environment :: Console', 47 | 'Intended Audience :: End Users/Desktop', 48 | 'Intended Audience :: Developers', 49 | 'Topic :: Software Development :: Build Tools', 50 | 'Topic :: Software Development :: Documentation', 51 | 'Topic :: Text Processing :: Filters', 52 | ] 53 | 54 | # Alternatively, if you want to distribute just a my_module.py, uncomment 55 | # this: 56 | ) 57 | -------------------------------------------------------------------------------- /tests/note1.md: -------------------------------------------------------------------------------- 1 | ## Evaluation of Dissolution Kinetics 2 | 3 | Consider the growth rate as 4 | 5 | $G(l) = k (C - C_s)$ 6 | 7 | At initial time: 8 | 9 | $G_i = k (-C_s)$ 10 | 11 | Considering $C_s$ as 0.323 12 | 13 | ```{.python .run} 14 | C_s = 0.323 15 | print('Growth initial is = {}'.format(1e-2*(-C_s))) 16 | ``` 17 | 18 | Because of that, we should take very care with this approach! 19 | 20 | ```{.python .run format="text"} 21 | print('This is a print with equation $E = m c^2$') 22 | ``` 23 | 24 | Caio Marcellos -------------------------------------------------------------------------------- /tests/test.md: -------------------------------------------------------------------------------- 1 | 2 | # Executing python codes in a markdown file 3 | 4 | 5 | ## Cases without printing 6 | 7 | ### Regular block code 8 | 9 | A regular markdown code definition 10 | 11 | Syntax: \{.python} 12 | 13 | ```{.python } 14 | e = 'foo' 15 | ``` 16 | 17 | ### A runnable block code 18 | 19 | For a runnable code append the class .run 20 | 21 | Syntax: \{.python .run} 22 | 23 | ```{.python .run} 24 | d = 1e3 25 | ``` 26 | 27 | ### A runnable inline code 28 | 29 | An inline code can also be run in python. 30 | 31 | Syntax: \`(code)\`\{.run\} 32 | 33 | Code to run is `foo = 1`{.run}. 34 | 35 | ## Cases with printing 36 | 37 | ### Printing to a BlockQuote 38 | 39 | Run a python code and show the print output. 40 | 41 | Syntax: \{.python .run format=[blockquote (default), text]\} 42 | 43 | ```{.python .run} 44 | m = 2 * d 45 | print('The total mass is {:.2f} $m^3$'.format(m)) 46 | ``` 47 | 48 | ### Printing to regular Paragraph 49 | 50 | ```{.python .run format=text} 51 | m = 2 * d 52 | print('The total mass is {:.2f} $m^3$'.format(m)) 53 | ``` 54 | 55 | ### A runnable code with print statement hiding the code block 56 | 57 | Run a python code and show the print output, but hiding the code. 58 | 59 | Syntax: \{.python .run format=[blockquote (default), text]\ hide_code=[False, True]} 60 | 61 | ```{.python .run hide_code=True} 62 | m = 2 * d 63 | print('Variable d is {}'.format(d)) 64 | ``` 65 | 66 | ### Printing a string from python having markdown syntax 67 | 68 | Syntax: \{.python .run format=[text]\} 69 | 70 | ```{.python .run format=text} 71 | a = '5.0 $\\frac{kg}{m^3}$' 72 | b = 1.0 73 | ## table can be obtained from a lib such as tabulate 74 | 75 | s = ''' 76 | A | B | 77 | ---- | ---- | 78 | {} | {} $\\frac{{kg}}{{m^3}}$ | 79 | '''.format(a, b) 80 | 81 | print(s) 82 | ``` 83 | 84 | ### A runnable inline code with print statement 85 | 86 | It will replace the inline code by the print function output 87 | 88 | Syntax: \`(print(code))\`\{.run\} 89 | 90 | Obs: It is returned the raw string (math mode will not work) 91 | 92 | Water density is `print(d)`{.run} $kg/m^3$ and the total mass was `print('{:.2f} $m^3$'.format(m))`{.run} 93 | 94 | ## Figures generation 95 | 96 | ### A runnable code with one figure generation 97 | 98 | It will run the code and save the created pyplot figure to a file, than it creates a Image text block to load it. 99 | 100 | Syntax: \{.python .run format=[blockquote (default), text]\ hide_code=[False, True] caption="" label="" width=""}} 101 | 102 | - Uses _pandoc-fignos_ filter notation for caption, label and width. 103 | - If more than one figure is created the configuration keyvals can be added, such as: caption2="" label2="" width2="" ext2="" ... 104 | - Figure is saved to folder `./plt-images` 105 | 106 | ```{.python .run caption="Figure Number One" label="my_fig"} 107 | import matplotlib 108 | matplotlib.use('AGG') 109 | from matplotlib import pyplot as plt 110 | plt.plot([1, 2], [3, 4], 'dr-') 111 | ``` 112 | 113 | ### A runnable code with two figures generation 114 | 115 | It will run the code and save the created pyplot figure to a file, than it creates a Image text block to load it. 116 | 117 | Syntax: \{.python .run format=[blockquote (default), text]\ hide_code=[False, True] caption="" label="" width=""}} 118 | 119 | - Uses _pandoc-fignos_ filter notation for caption, label and width. 120 | - If more than one figure is created the configuration keyvals can be added, such as: caption2="" label2="" width2="" ext2="" ... 121 | - Figure is saved to folder `./plt-images` 122 | 123 | ```{.python .run caption="Number One" caption2="Other Figure" label="my_fig" label2="my_fig2"} 124 | import matplotlib 125 | matplotlib.use('AGG') 126 | from matplotlib import pyplot as plt 127 | from mpl_toolkits.mplot3d import Axes3D 128 | from matplotlib import cm 129 | import numpy as np 130 | 131 | # First Figure 132 | plt.plot([1, 2], [3, 4], 'dr-') 133 | 134 | # Second Figure 135 | fig = plt.figure() 136 | ax = fig.gca(projection='3d') 137 | X = np.arange(-5, 5, 0.25) 138 | Y = np.arange(-5, 5, 0.25) 139 | X, Y = np.meshgrid(X, Y) 140 | Z = np.sin(np.sqrt(X**2 + Y**2)) 141 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 142 | fig.colorbar(surf, shrink=0.5, aspect=5) 143 | ``` 144 | 145 | ## Non filter related tests 146 | 147 | ### Math expression 148 | 149 | Oi math $\dfrac{1}{2} - a^2 = 2$ -------------------------------------------------------------------------------- /tests/test_common_mark.md: -------------------------------------------------------------------------------- 1 | 2 | # Executing python codes in a markdown file 3 | 4 | This file uses the CommonMark syntax such that IDEs (VSCode) can highlight properly the `CodeBlock` 5 | 6 | ## Regular block code 7 | 8 | A regular markdown code definition 9 | 10 | Syntax: \{.python} 11 | 12 | ```python 13 | e = 'foo' 14 | ``` 15 | 16 | ## A runnable block code 17 | 18 | For a runnable code append the class .run 19 | 20 | Syntax: \{.python .run} 21 | 22 | ```python 23 | #filter: {.run} 24 | d = 1e3 25 | ``` 26 | 27 | ## A runnable inline code 28 | 29 | An inline code can also be run in python. 30 | 31 | Syntax: \`(code)\`\{.run\} 32 | 33 | Water density is `foo = 1`{.run}. 34 | 35 | ## A runnable code with print statement 36 | 37 | ### Printing to a BlockQuote 38 | 39 | Run a python code and show the print output. 40 | 41 | Syntax: \{.python .run format=[blockquote (default), text]\} 42 | 43 | ```python 44 | # filter: { 45 | # .run 46 | # } 47 | m = 2 * d 48 | print('The total mass is {:.2f} $m^3$'.format(m)) 49 | ``` 50 | 51 | ### Printing to regular Paragraph 52 | 53 | ```python 54 | # filter: { .run format=text} 55 | m = 2 * d 56 | print('The total mass is {:.2f} $m^3$'.format(m)) 57 | ``` 58 | 59 | ## A runnable code with print statement hiding the code block 60 | 61 | Run a python code and show the print output, but hiding the code. 62 | 63 | Syntax: \{.python .run format=[blockquote (default), text]\ hide_code=[False, True]} 64 | 65 | ```python 66 | # filter: { .run hide_code=True} 67 | m = 2 * d 68 | print('Variable d is {}'.format(d)) 69 | ``` 70 | 71 | ## A runnable inline code with print statement 72 | 73 | It will replace the inline code by the print function output 74 | 75 | Syntax: \`(print(code))\`\{.run\} 76 | 77 | Obs: It is returned the raw string (math mode will not work) 78 | 79 | Water density is `print(d)`{.run} $kg/m^3$ and the total mass was `print('{:.2f} $m^3$'.format(m))`{.run} 80 | 81 | ## Figures generation 82 | 83 | ### A runnable code with one figure generation 84 | 85 | It will run the code and save the created pyplot figure to a file, than it creates a Image text block to load it. 86 | 87 | Syntax: \{.python .run format=[blockquote (default), text]\ hide_code=[False, True] caption="" label="" width=""}} 88 | 89 | - Uses _pandoc-fignos_ filter notation for caption, label and width. 90 | - If more than one figure is created the configuration keyvals can be added, such as: caption2="" label2="" width2="" ext2="" ... 91 | - Figure is saved to folder `./plt-images` 92 | 93 | ```python 94 | #filter: {.run 95 | # caption="Figure Number One" 96 | # label="my_fig" 97 | # } 98 | import matplotlib 99 | matplotlib.use('AGG') 100 | from matplotlib import pyplot as plt 101 | plt.plot([1, 2], [3, 4], 'dr-') 102 | ``` 103 | 104 | ### A runnable code with two figures generation 105 | 106 | It will run the code and save the created pyplot figure to a file, than it creates a Image text block to load it. 107 | 108 | Syntax: \{.python .run format=[blockquote (default), text]\ hide_code=[False, True] caption="" label="" width=""}} 109 | 110 | - Uses _pandoc-fignos_ filter notation for caption, label and width. 111 | - If more than one figure is created the configuration keyvals can be added, such as: caption2="" label2="" width2="" ext2="" ... 112 | - Figure is saved to folder `./plt-images` 113 | 114 | ```python 115 | #filter: {.run caption="Number One" caption2="Other Figure" label="my_fig" label2="my_fig2"} 116 | import matplotlib 117 | matplotlib.use('AGG') 118 | from matplotlib import pyplot as plt 119 | from mpl_toolkits.mplot3d import Axes3D 120 | from matplotlib import cm 121 | import numpy as np 122 | 123 | # First Figure 124 | plt.plot([1, 2], [3, 4], 'dr-') 125 | 126 | # Second Figure 127 | fig = plt.figure() 128 | ax = fig.gca(projection='3d') 129 | X = np.arange(-5, 5, 0.25) 130 | Y = np.arange(-5, 5, 0.25) 131 | X, Y = np.meshgrid(X, Y) 132 | Z = np.sin(np.sqrt(X**2 + Y**2)) 133 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 134 | fig.colorbar(surf, shrink=0.5, aspect=5) 135 | ``` 136 | 137 | End -------------------------------------------------------------------------------- /tests/test_filter_pandoc_run_py.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | # from filter_pandoc_run_py import * 4 | from filter_pandoc_run_py import * 5 | from subprocess import call 6 | 7 | dir_path = os.path.dirname(os.path.realpath(__file__)) 8 | 9 | """ 10 | 11 | Pandoc convert using the filter from markdown to html: 12 | pandoc ./test.md -F ../filter_pandoc_run_py/filter_pandoc_run_py.py --to html -s -o ./tests//test.html 13 | 14 | Pandoc convert using the filter from markdown to markdown: 15 | ! pandoc ./tests/test.md --to markdown -F ./filter_pandoc_run_py/filter_pandoc_run_py.py -o ./tests/test_conv.md 16 | 17 | Generate the intermediary json ast file: 18 | ! pandoc ./tests/test.md -t json -o ./tests/test.json 19 | """ 20 | 21 | #-------------------------------------------- 22 | # Low Level tests 23 | #-------------------------------------------- 24 | 25 | def test_run_pandoc_process(): 26 | 27 | text = 'I am a **mark** *down*' 28 | ret = run_pandoc(text) 29 | d = json.loads(ret) 30 | assert d['blocks'][0]['c'][6]['t'] == 'Strong' 31 | 32 | ret = run_pandoc(text, ['--from=markdown', '--to=html']) 33 | ret = ret.replace('\r', '') 34 | assert ret == '

I am a mark down

\n' 35 | 36 | text = 'Oi sou um **mark** *down* \n\n New Line!' 37 | ret = run_pandoc(text) 38 | d = json.loads(ret) 39 | assert d['blocks'][0]['c'][-1]['t'] == 'Emph' 40 | 41 | def test_json_ast_reader(): 42 | ''' 43 | Json generated as: pandoc test.md -t json -o test.json 44 | ''' 45 | call(['pandoc', os.path.join(dir_path, 'test.md'), '--to', 46 | 'json', '-o', os.path.join(dir_path, 'test.json')]) 47 | dt = read_json(os.path.join(dir_path, 'test.json')) 48 | assert isinstance(dt, (dict, list)) 49 | 50 | dt = read_json(os.path.join(dir_path, 'test.json'), 'string') 51 | assert isinstance(dt, (str)) 52 | 53 | def test_stdout_redirection(): 54 | code = """ 55 | i = [0,1,2] 56 | for j in i : 57 | print(j) 58 | """ 59 | with stdoutIO() as s: 60 | exec(code) 61 | 62 | print("out:", s.getvalue()) 63 | assert s.getvalue() == '0\n1\n2\n' 64 | pass 65 | 66 | #-------------------------------------------- 67 | # Testing code Samples run and Print 68 | #-------------------------------------------- 69 | 70 | def test_md_sample_regular_code(): 71 | MD_SAMPLE = ''' 72 | ```{.python } 73 | e = 'foo' 74 | ``` 75 | ''' 76 | ast_string = run_pandoc(MD_SAMPLE) 77 | processed = applyJSONFilters([run_py_code_block], ast_string) 78 | d = json.loads(processed) 79 | assert d['blocks'][0]['c'][1] == "e = 'foo'" 80 | 81 | 82 | def test_md_sample_runnable(): 83 | MD_SAMPLE = ''' 84 | ```{.python .run} 85 | d = 1e3 86 | ``` 87 | ''' 88 | ast_string = run_pandoc(MD_SAMPLE) 89 | processed = applyJSONFilters([run_py_code_block], ast_string) 90 | d = json.loads(processed) 91 | assert d['blocks'][0]['c'][1] == 'd = 1e3' 92 | 93 | def test_md_sample_run_inline(): 94 | MD_SAMPLE = ''' 95 | Water density is `foo = 1`{.run}. 96 | ''' 97 | ast_string = run_pandoc(MD_SAMPLE) 98 | processed = applyJSONFilters([run_py_code_block], ast_string) 99 | d = json.loads(processed) 100 | assert d['blocks'][0]['c'][6]['t'] == 'Code' 101 | 102 | def test_md_sample_print(): 103 | MD_SAMPLE = ''' 104 | ```{.python .run} 105 | print('A={}'.format(2.0)) 106 | ``` 107 | ''' 108 | ast_string = run_pandoc(MD_SAMPLE) 109 | processed = applyJSONFilters([run_py_code_block], ast_string) 110 | d = json.loads(processed) 111 | assert d['blocks'][1]['c'][1]['t'] == 'BlockQuote' 112 | 113 | def test_md_sample_print_text(): 114 | MD_SAMPLE = ''' 115 | # Normal code Here 116 | 117 | Parapgrah here 118 | 119 | ```{.python .run format=text} 120 | print('A={}'.format(2.0)) 121 | ``` 122 | ''' 123 | ast_string = run_pandoc(MD_SAMPLE) 124 | processed = applyJSONFilters([run_py_code_block], ast_string) 125 | d = json.loads(processed) 126 | assert d['blocks'][1]['t'] == 'Para' 127 | pass 128 | 129 | def test_md_sample_print_hiding(): 130 | MD_SAMPLE = ''' 131 | ```{.python .run hide_code=True} 132 | print('A={}'.format(2.0)) 133 | ``` 134 | ''' 135 | ast_string = run_pandoc(MD_SAMPLE) 136 | processed = applyJSONFilters([run_py_code_block], ast_string) 137 | d = json.loads(processed) 138 | assert d['blocks'][0]['c'][1]['t'] == 'BlockQuote' 139 | pass 140 | 141 | 142 | def test_md_sample_print_inline(): 143 | MD_SAMPLE = ''' 144 | `print('Hi')`{.run}. 145 | ''' 146 | ast_string = run_pandoc(MD_SAMPLE) 147 | processed = applyJSONFilters([run_py_code_block], ast_string) 148 | d = json.loads(processed) 149 | assert d['blocks'][0]['t'] == 'Para' 150 | pass 151 | 152 | def test_md_sample_print_inline_ast_transformed(): 153 | MD_SAMPLE = ''' 154 | I said: `print('Hello **word**!')`{.run}. 155 | ''' 156 | ast_string = run_pandoc(MD_SAMPLE) 157 | processed = applyJSONFilters([run_py_code_block], ast_string) 158 | d = json.loads(processed) 159 | assert d['blocks'][0]['t'] == 'Para' 160 | pass 161 | 162 | 163 | def test_md_sample_render_table(): 164 | MD_SAMPLE = ''' 165 | A | B | 166 | ---- | ---- | 167 | C | D | 168 | ''' 169 | ast_string = run_pandoc(MD_SAMPLE) 170 | processed = applyJSONFilters([run_py_code_block], ast_string) 171 | d = json.loads(processed) 172 | assert d['blocks'][0]['t'] == 'Table' 173 | pass 174 | 175 | #-------------------------------------------- 176 | # Testing Inline Image to document 177 | #-------------------------------------------- 178 | 179 | def test_inline_plot(): 180 | MD_SAMPLE = ''' 181 | ```{.python .run caption="cap1" label="lbl1"} 182 | import matplotlib 183 | matplotlib.use('AGG') 184 | from matplotlib import pyplot as plt 185 | plt.plot([1, 2], [3, 4], 'dr-') 186 | ``` 187 | ''' 188 | ast_string = run_pandoc(MD_SAMPLE) 189 | processed = applyJSONFilters([run_py_code_block], ast_string) 190 | d = json.loads(processed) 191 | assert d['blocks'][1]['c'][0]['t'] == 'Image' 192 | 193 | #-------------------------------------------- 194 | # Workaround for keeping vscode highlight in codeblock 195 | # Instead of the braces syntax, uses: 196 | # ```python 197 | # #.classes key=vals 198 | # 199 | # ``` 200 | #-------------------------------------------- 201 | 202 | def test_parser_filter_config_new_syntax(): 203 | S = '# filter: {.run caption="cap1" label="lbl1"}' 204 | classes = [] 205 | kv = [] 206 | value = [['', classes, kv], S] 207 | workaround_classes_with_commonmark_syntax(S, classes, kv, value) 208 | 209 | assert 'run' in classes 210 | assert 'caption' in kv[0][0] 211 | assert 'label' in kv[1][0] 212 | assert '"cap1"' in kv[0][1] 213 | assert '"lbl1"' in kv[1][1] 214 | 215 | def test_md_sample_runnable_new_syntax(): 216 | MD_SAMPLE = ''' 217 | ```python 218 | # filter: {.run } 219 | d = 1e3 220 | ``` 221 | ''' 222 | ast_string = run_pandoc(MD_SAMPLE) 223 | processed = applyJSONFilters([run_py_code_block], ast_string) 224 | d = json.loads(processed) 225 | assert '1e3' in d['blocks'][0]['c'][1] 226 | 227 | MD_SAMPLE = ''' 228 | ```python 229 | # filter: {.run key1=val1 .class2 key2="valor dois"} 230 | d = 1e3 231 | ``` 232 | ''' 233 | ast_string = run_pandoc(MD_SAMPLE) 234 | processed = applyJSONFilters([run_py_code_block], ast_string) 235 | d = json.loads(processed) 236 | assert '1e3' in d['blocks'][0]['c'][1] 237 | 238 | 239 | # def test_md_sample_runnable_new_syntax_mpl(): 240 | # MD_SAMPLE = ''' 241 | # ```python 242 | # # filter: {.run caption="cap1" label="lbl1"} 243 | # import matplotlib 244 | # matplotlib.use('AGG') 245 | # from matplotlib import pyplot as plt 246 | # plt.plot([1, 2], [3, 4], 'dr-') 247 | # ``` 248 | # ''' 249 | # ast_string = run_pandoc(MD_SAMPLE) 250 | # processed = applyJSONFilters([run_py_code_block], ast_string) 251 | # d = json.loads(processed) 252 | # print(d) 253 | # assert d['blocks'][1]['c'][0]['t'] == 'Image' 254 | # pass #This is not failing on win but is failing at travis ci 255 | 256 | def test_should_not_show_filter_comment_line_in_common_mark_mode(): 257 | MD_SAMPLE = ''' 258 | ```python 259 | # filter: {.run key1=val1 .class2 key2="valor dois"} 260 | d = 1e3 261 | ``` 262 | ''' 263 | ast_string = run_pandoc(MD_SAMPLE) 264 | processed = applyJSONFilters([run_py_code_block], ast_string) 265 | d = json.loads(processed) 266 | assert 'filter' not in d['blocks'][0]['c'][1] 267 | assert '1e3' in d['blocks'][0]['c'][1] 268 | 269 | #-------------------------------------------- 270 | # Testing Full Convertion 271 | #-------------------------------------------- 272 | 273 | def test_run_pandoc_like(): 274 | """ 275 | Requires test.json in the file directory. 276 | It is generated from test.md as: 277 | pandoc test.md --to json -o test.json 278 | """ 279 | call(['pandoc', os.path.join(dir_path, '../examples/runnable_three_figures_dif_cells.md'), '--to', 280 | 'json', '-o', os.path.join(dir_path, 'test.json')]) 281 | dt = read_json(os.path.join(dir_path, 'test.json'), 'string') 282 | applyJSONFilters([run_py_code_block], dt) 283 | 284 | ############################################ 285 | ########################################### 286 | # 287 | # 288 | # Regular Debugger Start 289 | # 290 | # 291 | ########################################### 292 | ########################################### 293 | def insider_Debugger(): 294 | # test_run_pandoc_like() 295 | # test_md_sample_print_text() 296 | test_md_sample_runnable_new_syntax() 297 | pass 298 | 299 | if __name__ == '__main__': 300 | insider_Debugger() 301 | pass 302 | 303 | 304 | # TODO 305 | # - All string returned passed through the ast pandoc conversion 306 | # 307 | # 308 | # 309 | # 310 | # 311 | # 312 | # 313 | -------------------------------------------------------------------------------- /tests/test_matplotlib.py: -------------------------------------------------------------------------------- 1 | import filter_pandoc_run_py 2 | import json 3 | from pandocfilters import applyJSONFilters 4 | 5 | def test_inline_plot_mpl_multiples(): 6 | MD_SAMPLE = ''' 7 | 8 | # Creates Three figures Matplotlib figure 9 | 10 | ```python 11 | #filter: {.run caption="Figure Number One" label="my_fig" hide_code=true title_as_caption=true figattr="#fig:1 width=5in"} 12 | import matplotlib 13 | matplotlib.use('AGG') 14 | from matplotlib import pyplot as plt 15 | plt.figure() 16 | plt.plot([1, 2], [-1, -4], 'or-') 17 | plt.title('This is figure caption from python') 18 | ``` 19 | 20 | ```{.python .run caption="Second Figure" caption2="Third Figure" label="my_fig_2" label2="my_fig_3" hide_code=true figattr="#fig:2 width=8in" figattr2="#fig:3 width=3in tag='B.1'"} 21 | # import matplotlib 22 | # matplotlib.use('AGG') 23 | # from matplotlib import pyplot as plt 24 | import numpy as np 25 | from mpl_toolkits.mplot3d import Axes3D 26 | from matplotlib import cm 27 | 28 | # First Figure 29 | plt.figure() 30 | plt.plot([1, 2], [3, 4], 'dg-') 31 | 32 | 33 | fig = plt.figure() 34 | ax = fig.gca(projection='3d') 35 | X = np.arange(-5, 5, 0.25) 36 | Y = np.arange(-5, 5, 0.25) 37 | X, Y = np.meshgrid(X, Y) 38 | Z = np.sin(np.sqrt(X**2 + Y**2)) 39 | surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm) 40 | fig.colorbar(surf, shrink=0.5, aspect=5) 41 | ``` 42 | 43 | 44 | ''' 45 | ast_string = filter_pandoc_run_py.run_pandoc(MD_SAMPLE) 46 | processed = applyJSONFilters( 47 | [filter_pandoc_run_py.run_py_code_block], ast_string 48 | ) 49 | d = json.loads(processed) 50 | assert d['blocks'][1]['c'][0]['t'] == 'Image' 51 | 52 | def insider_Debugger(): 53 | test_inline_plot_mpl_multiples() 54 | pass 55 | 56 | if __name__ == '__main__': 57 | insider_Debugger() 58 | pass 59 | 60 | 61 | #filter: {.run caption="Second Figure" caption2="Third Figure" label="my_fig_2" label2="my_fig_3" hide_code=true captions=['cap1', 'cap2']} 62 | -------------------------------------------------------------------------------- /tests/test_units.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import filter_pandoc_run_py 3 | 4 | def test_figattr_convertion(): 5 | s = '"#fig:1 width=5in"' 6 | 7 | fig_s, kw_list_tuple = filter_pandoc_run_py.figattr_str_convertion(s) 8 | assert fig_s == 'fig:1' 9 | assert isinstance(kw_list_tuple, list) 10 | assert isinstance(kw_list_tuple[0], tuple) 11 | assert kw_list_tuple[0][0] == 'width' 12 | assert kw_list_tuple[0][1] == '5in' --------------------------------------------------------------------------------