summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurentRDC <>2020-06-07 13:13:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2020-06-07 13:13:00 (GMT)
commit53b46fbfdffba7f8725f156903ecfa7890ed62c0 (patch)
tree5ddf4f1f5848843c2bbfb65d7d547975a98463cf
parent0df2b4572ff2d70d508553019af73b36dd2cb9fb (diff)
version 0.6.0.00.6.0.0
-rw-r--r--CHANGELOG.md30
-rw-r--r--README.md582
-rw-r--r--example-config.yml10
-rw-r--r--executable/Main.hs172
-rw-r--r--pandoc-plot.cabal10
-rw-r--r--src/Text/Pandoc/Filter/Plot.hs2
-rw-r--r--src/Text/Pandoc/Filter/Plot/Configuration.hs58
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers.hs26
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs13
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs47
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs14
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs10
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs (renamed from src/Text/Pandoc/Filter/Plot/Renderers/Plotly.hs)14
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs49
-rw-r--r--src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs36
-rw-r--r--src/Text/Pandoc/Filter/Plot/Scripting.hs23
-rw-r--r--src/Text/Pandoc/Filter/Plot/Types.hs59
-rw-r--r--tests/Common.hs4
-rw-r--r--tests/Main.hs19
22 files changed, 929 insertions, 279 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c22758..77a6c8c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,36 @@
pandoc-plot uses [Semantic Versioning](http://semver.org/spec/v2.0.0.html)
+Release 0.6.0.0
+--------------
+
+New toolkits:
+* Added support for the Plotly/R plotting library.
+* Added support for Graphviz.
+
+Other changes:
+* The determination of which figures to re-render or not has been improved. For example, changing the caption will not trigger a re-render of a figure anymore.
+* `pandoc-plot` will look for executables more thoroughly.
+* `pandoc-plot toolkits` will now show the exact executable that is being used, if possible.
+* Added a check when running the filter that the Pandoc version is at least 2.8. This is easier to understand that the default Pandoc warning on API incompatibility.
+* Added the ability to write the example configuration to an arbitrary file using `pandoc-plot write-example-config`.
+* Added the possibility to specify the configuration file via metadata. For example, in Markdown:
+
+ ```markdown
+ ---
+ title: My document
+ author: John Doe
+ plot-configuration: /path/to/file.yml
+ ---
+ ```
+
+or on the command line:
+
+```bash
+pandoc --filter pandoc-plot -M plot-configuration=/path/to/file.yml ...
+```
+* Added the ability to specify configuration file to the `pandoc-plot clean` and `pandoc-plot toolkits` commands.
+
Release 0.5.0.0
---------------
diff --git a/README.md b/README.md
index e22c8dd..32bcca3 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,47 @@
+<!--
+The file README.md is automatically generated by the tools/mkreadme script
+Do not edit manually
+-->
+
# pandoc-plot
## A Pandoc filter to generate figures from code blocks in documents
-[![Hackage version](https://img.shields.io/hackage/v/pandoc-plot.svg)](http://hackage.haskell.org/package/pandoc-plot) [![Stackage version (nightly)](http://stackage.org/package/pandoc-plot/badge/nightly)](http://stackage.org/nightly/package/pandoc-plot) [![Build status](https://ci.appveyor.com/api/projects/status/mmgiuk52j356e6jp?svg=true)](https://ci.appveyor.com/project/LaurentRDC/pandoc-plot) [![Build Status](https://dev.azure.com/laurentdecotret/pandoc-plot/_apis/build/status/LaurentRDC.pandoc-plot?branchName=master)](https://dev.azure.com/laurentdecotret/pandoc-plot/_build/latest?definitionId=5&branchName=master) ![GitHub](https://img.shields.io/github/license/LaurentRDC/pandoc-plot) [![Conda Version](https://img.shields.io/conda/vn/conda-forge/pandoc-plot.svg)](https://anaconda.org/conda-forge/pandoc-plot)
+[![Hackage version](https://img.shields.io/hackage/v/pandoc-plot.svg)](http://hackage.haskell.org/package/pandoc-plot) [![Stackage version (nightly)](http://stackage.org/package/pandoc-plot/badge/nightly)](http://stackage.org/nightly/package/pandoc-plot) [![Build status](https://ci.appveyor.com/api/projects/status/mmgiuk52j356e6jp?svg=true)](https://ci.appveyor.com/project/LaurentRDC/pandoc-plot) [![Build Status](https://dev.azure.com/laurentdecotret/pandoc-plot/_apis/build/status/LaurentRDC.pandoc-plot?branchName=master)](https://dev.azure.com/laurentdecotret/pandoc-plot/_build/latest?definitionId=5&branchName=master) [![license](https://img.shields.io/badge/license-GPLv2+-lightgray.svg)](https://www.gnu.org/licenses/gpl.html) [![Conda Version](https://img.shields.io/conda/vn/conda-forge/pandoc-plot.svg)](https://anaconda.org/conda-forge/pandoc-plot)
`pandoc-plot` turns code blocks present in your documents (Markdown, LaTeX, etc.) into embedded figures, using your plotting toolkit of choice, including Matplotlib, ggplot2, MATLAB, Mathematica, and more.
-## Table of content
-
-* [Overview](#overview)
-* [Supported toolkits](#supported-toolkits)
-* [Features](#features)
- * [Captions](#captions)
- * [Link to source code](#link-to-source-code)
- * [Preamble scripts](#preamble-scripts)
- * [Performance](#performance)
- * [Compatibility with pandoc-crossref](#compatibility-with-pandoc-crossref)
-* [Configuration](#configuration)
- * [Toolkit-specific options](#toolkit-specific-options)
-* [Detailed usage](#detailed-usage)
- * [As a filter](#as-a-filter)
- * [Cleaning output](#cleaning-output)
- * [Configuration template](#configuration-template)
- * [As a Haskell library](#as-a-haskell-library)
- * [Usage with Hakyll](#usage-with-hakyll)
-* [Installation](#installation)
-* [Warning](#warning)
+ - [Overview](#overview)
+ - [Supported toolkits](#supported-toolkits)
+ - [Features](#features)
+ - [Captions](#captions)
+ - [Link to source code](#link-to-source-code)
+ - [Preamble scripts](#preamble-scripts)
+ - [Performance](#performance)
+ - [Compatibility with
+ pandoc-crossref](#compatibility-with-pandoc-crossref)
+ - [Detailed usage](#detailed-usage)
+ - [As a filter](#as-a-filter)
+ - [Parameters and options](#parameters-and-options)
+ - [Configuration](#configuration)
+ - [Other commands](#other-commands)
+ - [Installation](#installation)
+ - [Binaries and Installers](#binaries-and-installers)
+ - [conda](#conda)
+ - [From Hackage/Stackage](#from-hackagestackage)
+ - [From source](#from-source)
+ - [Warning](#warning)
## Overview
-This program is a [Pandoc](https://pandoc.org/) filter. It can therefore be used in the middle of conversion from input format to output format, replacing code blocks with figures.
+This program is a [Pandoc](https://pandoc.org/) filter. It can therefore
+be used in the middle of conversion from input format to output format,
+replacing code blocks with figures.
-The filter recognizes code blocks with classes that match plotting toolkits. For example, using the `matplotlib` toolkit:
+The filter recognizes code blocks with classes that match plotting
+toolkits. For example, using the `matplotlib` toolkit:
-~~~markdown
+```` markdown
# My document
This is a paragraph.
@@ -45,46 +53,60 @@ plt.figure()
plt.plot([0,1,2,3,4], [1,2,3,4,5])
plt.title('This is an example figure')
```
-~~~
+````
-Putting the above in `input.md`, we can then generate the plot and embed it in an HTML page:
+Putting the above in `input.md`, we can then generate the plot and embed
+it in an HTML page:
-```bash
+``` bash
pandoc --filter pandoc-plot input.md --output output.html
```
-*Note that pandoc-plot only works with pandoc >= 2.8 because of some breaking changes in pandoc's API.*
+*Note that pandoc-plot only works with pandoc \>= 2.8 because of some
+breaking changes in pandoc’s API.*
## Supported toolkits
-`pandoc-plot` currently supports the following plotting toolkits (**installed separately**):
-
-* `matplotlib`: plots using the [matplotlib](https://matplotlib.org/) Python library;
-* `plotly_python` : plots using the [plotly](https://plot.ly/python/) Python library;
-* `matlabplot`: plots using [MATLAB](https://www.mathworks.com/);
-* `mathplot` : plots using [Mathematica](https://www.wolfram.com/mathematica/);
-* `octaveplot`: plots using [GNU Octave](https://www.gnu.org/software/octave/);
-* `ggplot2`: plots using [ggplot2](https://ggplot2.tidyverse.org/);
-* `gnuplot`: plots using [gnuplot](http://www.gnuplot.info/);
-
-
-To know which toolkits are useable on *your machine* (and which ones are not available), you can check with the `--toolkits/-t` flag:
-
-```bash
-pandoc-plot --toolkits
+`pandoc-plot` currently supports the following plotting toolkits
+(**installed separately**):
+
+ - `matplotlib`: plots using the [matplotlib](https://matplotlib.org/)
+ Python library;
+ - `plotly_python` : plots using the
+ [plotly](https://plotly.com/python/) Python library;
+ - `plotly_r`: plots using the [plotly](https://plotly.com/r/) R
+ library
+ - `matlabplot`: plots using [MATLAB](https://www.mathworks.com/);
+ - `mathplot` : plots using
+ [Mathematica](https://www.wolfram.com/mathematica/);
+ - `octaveplot`: plots using [GNU
+ Octave](https://www.gnu.org/software/octave/);
+ - `ggplot2`: plots using [ggplot2](https://ggplot2.tidyverse.org/);
+ - `gnuplot`: plots using [gnuplot](http://www.gnuplot.info/);
+ - `graphviz`: graphs using the [Graphviz](http://graphviz.org/)
+
+To know which toolkits are useable on *your machine* (and which ones are
+not available), you can check with the `toolkits` command:
+
+``` bash
+pandoc-plot toolkits
```
-**Wish your plotting toolkit of choice was available? Please [raise an issue](https://github.com/LaurentRDC/pandoc-plot/issues)!**
+The `toolkits` command is described in its own section below.
+
+**Wish your plotting toolkit of choice was available? Please [raise an
+issue](https://github.com/LaurentRDC/pandoc-plot/issues)\!**
## Features
### Captions
-You can also specify a caption for your image. This is done using the optional `caption` parameter.
+You can also specify a caption for your image. This is done using the
+optional `caption` parameter.
-__Markdown__:
+**Markdown**:
-~~~markdown
+```` markdown
```{.matlabplot caption="This is a simple figure with a **Markdown** caption"}
x = 0: .1 : 2*pi;
y1 = cos(x);
@@ -93,11 +115,11 @@ y2 = sin(x);
figure
plot(x, y1, 'b', x, y2, 'r-.', 'LineWidth', 2)
```
-~~~
+````
-__LaTex__:
+**LaTex**:
-```latex
+``` latex
\begin{minted}[caption=This is a simple figure with a caption]{matlabplot}
x = 0: .1 : 2*pi;
y1 = cos(x);
@@ -108,25 +130,30 @@ plot(x, y1, 'b', x, y2, 'r-.', 'LineWidth', 2)
\end{minted}
```
-Caption formatting unfortunately cannot be determined automatically. To specify a caption format (e.g. "markdown", "LaTeX", etc.), see [Configuration](#configuration).
+Caption formatting unfortunately cannot be determined automatically. To
+specify a caption format (e.g. “markdown”, “LaTeX”, etc.), see
+[Configuration](#configuration).
### Link to source code
-In case of an output format that supports links (e.g. HTML), the embedded image generated by `pandoc-plot` can show a link to the source code which was used to generate the file. Therefore, other people can see what code was used to create your figures.
+In case of an output format that supports links (e.g. HTML), the
+embedded image generated by `pandoc-plot` can show a link to the source
+code which was used to generate the file. Therefore, other people can
+see what code was used to create your figures.
You can turn this on via the `source=true` key:
-__Markdown__:
+**Markdown**:
-~~~markdown
+```` markdown
```{.mathplot source=true}
...
```
-~~~
+````
-__LaTex__:
+**LaTex**:
-```latex
+``` latex
\begin{minted}[source=true]{mathplot}
...
\end{minted}
@@ -136,26 +163,30 @@ or via a [configuration file](#Configuration).
### Preamble scripts
-If you find yourself always repeating some steps, inclusion of scripts is possible using the `preamble` parameter. For example, if you want all Matplotlib plots to have the [`ggplot`](https://matplotlib.org/tutorials/introductory/customizing.html#sphx-glr-tutorials-introductory-customizing-py) style, you can write a very short preamble `style.py` like so:
+If you find yourself always repeating some steps, inclusion of scripts
+is possible using the `preamble` parameter. For example, if you want all
+Matplotlib plots to have the
+[`ggplot`](https://matplotlib.org/tutorials/introductory/customizing.html#sphx-glr-tutorials-introductory-customizing-py)
+style, you can write a very short preamble `style.py` like so:
-```python
+``` python
import matplotlib.pyplot as plt
plt.style.use('ggplot')
```
and include it in your document as follows:
-~~~markdown
+```` markdown
```{.matplotlib preamble=style.py}
plt.figure()
plt.plot([0,1,2,3,4], [1,2,3,4,5])
plt.title('This is an example figure')
```
-~~~
+````
Which is equivalent to writing the following markdown:
-~~~markdown
+```` markdown
```{.matplotlib}
import matplotlib.pyplot as plt
plt.style.use('ggplot')
@@ -164,256 +195,464 @@ plt.figure()
plt.plot([0,1,2,3,4], [1,2,3,4,5])
plt.title('This is an example figure')
```
-~~~
+````
The equivalent LaTeX usage is as follows:
-```latex
+``` latex
\begin{minted}[include=style.py]{matplotlib}
\end{minted}
```
-This `preamble` parameter is perfect for longer documents with many plots. Simply define the style you want in a separate script! You can also import packages this way, or define functions you often use.
+This `preamble` parameter is perfect for longer documents with many
+plots. Simply define the style you want in a separate script\! You can
+also import packages this way, or define functions you often use.
### Performance
-`pandoc-plot` minimizes work, only generating figures if it absolutely must, i.e. if the content has changed. `pandoc-plot` will save the hash of the source code used to generate a figure in its filename. Before generating a figure, `pandoc-plot` will check it this figure already exists based on the hash of its source! This also means that there is no way to directly name figures.
+`pandoc-plot` minimizes work, only generating figures if it absolutely
+must, i.e. if the content has changed. `pandoc-plot` will save the hash
+of the source code used to generate a figure in its filename. Before
+generating a figure, `pandoc-plot` will check it this figure already
+exists based on the hash of its source\! This also means that there is
+no way to directly name figures.
-Moreover, starting with version 0.5.0.0, `pandoc-plot` takes advantage of multicore CPUs, rendering figures **in parallel**.
+Moreover, starting with version 0.5.0.0, `pandoc-plot` takes advantage
+of multicore CPUs, rendering figures **in parallel**.
-Therefore, you can confidently run the filter on very large documents containing hundreds of figures, like a book or a thesis.
+Therefore, you can confidently run the filter on very large documents
+containing hundreds of figures, like a book or a thesis.
### Compatibility with pandoc-crossref
-[`pandoc-crossref`](https://github.com/lierdakil/pandoc-crossref) is a pandoc filter that makes it effortless to cross-reference objects in Markdown documents.
+[`pandoc-crossref`](https://github.com/lierdakil/pandoc-crossref) is a
+pandoc filter that makes it effortless to cross-reference objects in
+Markdown documents.
-You can use `pandoc-crossref` in conjunction with `pandoc-plot` for the ultimate figure-making pipeline. You can combine both in a figure like so:
+You can use `pandoc-crossref` in conjunction with `pandoc-plot` for the
+ultimate figure-making pipeline. You can combine both in a figure like
+so:
-~~~markdown
+```` markdown
```{#fig:myexample .plotly_python caption="This is a caption"}
# Insert figure script here
```
As you can see in @fig:myexample, ...
-~~~
+````
-If the above source is located in file `myfile.md`, you can render the figure and references by applying `pandoc-plot` **first**, and then `pandoc-crossref`. For example:
+If the above source is located in file `myfile.md`, you can render the
+figure and references by applying `pandoc-plot` **first**, and then
+`pandoc-crossref`. For example:
-```bash
+``` bash
pandoc --filter pandoc-plot --filter pandoc-crossref -i myfile.md -o myfile.html
```
-## Configuration
+## Detailed usage
+
+`pandoc-plot` is a command line executable with a few functions. You can
+take a look at the help using the `-h`/`--help` flag:
-To avoid repetition, `pandoc-plot` can be configured using simple YAML files. `pandoc-plot` will look for a `.pandoc-plot.yml` file in the current working directory. Here are **all** the possible parameters:
+``` bash
+pandoc-plot - generate figures directly in documents using your plotting toolkit
+of choice.
+
+Usage: pandoc-plot.exe ([-v|--version] | [--full-version] | [-m|--manual])
+ [COMMAND] [AST]
+ This pandoc filter generates plots from code blocks using a multitude of
+ possible renderers. This allows to keep documentation and figures in perfect
+ synchronicity.
+
+Available options:
+ -v,--version Show version number and exit.
+ --full-version Show full version information and exit.
+ -m,--manual Open the manual page in the default web browser and
+ exit.
+ -h,--help Show this help text
+
+Available commands:
+ toolkits Show information on toolkits and exit.
+ clean Clean output directories where figures from FILE
+ might be stored. WARNING: All files in those
+ directories will be deleted.
+ write-example-config Write example configuration to a file and exit.
+
+More information can be found via the manual (pandoc-plot --manual) or the repository README, located at
+ https://github.com/LaurentRDC/pandoc-plot
+
+```
+
+### As a filter
+
+The most common use for `pandoc-plot` is as a pandoc filter, in which
+case it should be called without arguments. For example:
+
+``` bash
+pandoc --filter pandoc-plot -i input.md -o output.html
+```
+
+If `pandoc-plot` fails to render a code block into a figure, the
+filtering will not stop. Your code blocks will stay unchanged.
+
+You can chain other filters with it (e.g.,
+[`pandoc-crossref`](https://github.com/lierdakil/pandoc-crossref)) like
+so:
+
+``` bash
+pandoc --filter pandoc-plot --filter pandoc-crossref -i input.md -o output.html
+```
+
+### Parameters and options
+
+`pandoc-plot` looks for code blocks with a specific class, depending on
+the toolkit you want to use. `pandoc-plot` will run the code and capture
+the figure output. There can only be **one** figure per code block.
+
+There are parameters that affect the figure that will be included in
+your document. Here are all the possible general parameters:
+
+```` markdown
+ ```{.cls directory=(path) caption=(text) format=(PNG|PDF|SVG|JPG|EPS|GIF|TIF|WEBP) source=(true|false) preamble=(path) dpi=(integer) executable=(path) caption_format=(text)}
+ # script content
+ ```
+````
+
+ - `cls` must be one of the following: `matplotlib`, `matlabplot`,
+ `plotly_python`, `plotly_r`, `mathplot`, `octaveplot`, `ggplot2`,
+ `gnuplot`
+
+All following parameters are optional, with their default values
+controlled by the [configuration](#configuration)
+
+ - `directory` is a path to the directory where the figure and source
+ code will be saved. You cannot control the file name. This path is
+ either absolute, or relative from the working directory where you
+ call `pandoc-plot`.
+ - `caption` is the caption text. The format of the caption is
+ specified in the `caption_format` parameter, described below.
+ - `format` is the desired filetype for the resulting figure. Possible
+ values for `format` are \[`PNG`, `PDF`, `SVG`, `JPG`, `EPS`, `GIF`,
+ `TIF`, `WEBP`\]. Not all toolkits support all formats. See
+ `pandoc-plot toolkits` for toolkit-specific information regarding
+ save formats.
+ - `source` is a boolean toggle that determines whether the source code
+ should be linked in the caption or not. Possible values are
+ \[`true`, `True`, `false`, `False`\].
+ - `preamble` is a path to a script that will be included as a preamble
+ to the content of the code block. This path is either absolute, or
+ relative from the working directory where you call `pandoc-plot`.
+ - `dpi` is the pixel density of the figure in dots-per-inch. Possible
+ values are positive integers. Not all toolkits respect this.
+ - `executable` is a path to the executable to use
+ (e.g. `C:\\python3.exe`) or the name of the executable
+ (e.g. `python3`).
+ - `caption_format` is the text format of the caption. Possible values
+ are exactly the same as `pandoc`’s format specification, usually
+ `FORMAT+EXTENSION-EXTENSION`. For example, captions in Markdown with
+ raw LaTeX would be parsed correctly provided that
+ `caption_format=markdown+raw_tex`. See Pandoc’s guide on [Specifying
+ formats](https://pandoc.org/MANUAL.html#specifying-formats).
+
+#### Code highlighting
+
+If your editor supports code highlighting in code blocks, you can also
+include the programming language. In Markdown:
+
+```` markdown
+ ```{.language .cls (options)}
+ # script content
+ ```
+````
+
+For example, for Matplotlib plots:
+
+```` markdown
+ ```{.python .matplotlib}
+ # script content
+ ```
+````
+
+or for GGPlot2 figures:
+
+```` markdown
+ ```{.r .ggplot2}
+ # script content
+ ```
+````
+
+This way, you benefit from code highlighting *and* `pandoc-plot`.
+
+### Configuration
+
+To avoid repetition, `pandoc-plot` can be configured using simple YAML
+files. Here are **all** the possible parameters:
+
+``` yaml
+
+# This is an example configuration. Everything in this file is optional.
+# Please refer to the documentation to know about the parameters herein.
+#
+# The `executable` parameter for all toolkits can be either the
+# executable name (if it is present on the PATH), or
+# the full path to the executable.
+# E.g.:
+# executable: python3
+# executable: "C:\Python37\Scripts\python.exe"
+#
+# Note that this file should be re-named to ".pandoc-plot.yml" before pandoc-plot
+# notices it.
-```yaml
# The following parameters affect all toolkits
+# Directory where to save the plots. The path can be relative to this file, or absolute.
directory: plots/
+
+# Whether or not to include a link to the source script in the caption.
+# Particularly useful for HTML output.
source: false
+
+# Default density of figures in dots per inches (DPI).
+# This can be changed in the document specifically as well.
dpi: 80
+
+# Default format in which to save the figures. This can be specified individually as well.
format: PNG
+
+# Text format for the captions. Unfortunately, there is no way to detect this automatically.
+# You can use the same notation as Pandoc's --from parameter, specified here:
+# https://pandoc.org/MANUAL.html#option--from
+# Example: markdown, rst+raw_tex
caption_format: markdown+tex_math_dollars
# The possible parameters for the Matplotlib toolkit
matplotlib:
- preamble: matplotlib.py
+ # preamble: matplotlib.py
tight_bbox: false
transparent: false
executable: python
# The possible parameters for the MATLAB toolkit
matlabplot:
- preamble: matlab.m
+ # preamble: matlab.m
executable: matlab
# The possible parameters for the Plotly/Python toolkit
plotly_python:
- preamble: plotly-python.py
+ # preamble: plotly-python.py
executable: python
+# The possible parameters for the Plotly/R toolkit
+plotly_r:
+ # preamble: plotly-r.r
+ executable: Rscript
+
# The possible parameters for the Mathematica toolkit
mathplot:
- preamble: mathematica.m
+ # preamble: mathematica.m
executable: math
# The possible parameters for the GNU Octave toolkit
octaveplot:
- preamble: octave.m
+ # preamble: octave.m
executable: octave
# The possible parameters for the ggplot2 toolkit
ggplot2:
- preamble: ggplot2.r
+ # preamble: ggplot2.r
executable: Rscript
# The possible parameters for the gnuplot toolkit
gnuplot:
- preamble: gnuplot.gp
+ # preamble: gnuplot.gp
executable: gnuplot
+# The possible parameters for the graphviz toolkit
+graphviz:
+ # preamble: graphviz.dot
+ executable: dot
```
-A file like the above sets the **default** values; you can still override them in documents directly.
+A file like the above sets the **default** values; you can still
+override them in documents directly.
-Using `pandoc-plot write-example-config` will write the default configuration to a file which you can then customize.
+The easiest way to specify configuration for `pandoc-plot` is to place a
+`.pandoc-plot.yml` file in the current working directory. You can also
+specify a configuration file in document metadata, under the
+`plot-configuration` key. For example, in Markdown:
-### Executables
+``` markdown
+---
+title: My document
+author: John Doe
+plot-configuration: /path/to/file.yml
+---
+```
+
+or on the command line, using pandoc’s `-M/--metadata` flag:
-The `executable` parameter for all toolkits can be either the executable name (if it is present on the PATH), or the full path to the executable.
+``` bash
+pandoc --filter pandoc-plot -M plot-configuration=/path/to/file.yml ...
+```
+
+The hierarchy of configuration files is as follows:
+
+1. A configuration file specified in the metadata under the
+ `plot-configuration` key;
+2. Otherwise, a file in the current working directory named
+ `.pandoc-plot.yml`;
+3. Finally, the default configuration is used.
+
+#### Executables
+
+The `executable` parameter for all toolkits can be either the executable
+name (if it is present on the PATH), or the full path to the executable.
Examples:
-```yaml
+``` yaml
matplotlib:
executable: python3
```
-```yaml
+``` yaml
matlabplot:
executable: "C:\Program Files\Matlab\R2019b\bin\matlab.exe"
```
-### Toolkit-specific options
+#### Toolkit-specific options
-#### Matplotlib
+##### Matplotlib
-* `tight_bbox` is a boolean that determines whether to use `bbox_inches="tight"` or not when saving Matplotlib figures. For example, `tight_bbox: true`. See [here](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html) for details.
-* `transparent` is a boolean that determines whether to make Matplotlib figure background transparent or not. This is useful, for example, for displaying a plot on top of a colored background on a web page. High-resolution figures are not affected. For example, `transparent: true`.
+ - `tight_bbox` is a boolean that determines whether to use
+ `bbox_inches="tight"` or not when saving Matplotlib figures. For
+ example, `tight_bbox: true`. See
+ [here](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.savefig.html)
+ for details.
+ - `transparent` is a boolean that determines whether to make
+ Matplotlib figure background transparent or not. This is useful, for
+ example, for displaying a plot on top of a colored background on a
+ web page. High-resolution figures are not affected. For example,
+ `transparent: true`.
-## Detailed usage
+### Other commands
-`pandoc-plot` is a command line executable with a few functions. You can take a look at the help using the `-h`/`--help` flag:
+#### Finding installed toolkits
-```bash
-$ pandoc-plot --help
-pandoc-plot - generate figures directly in documents using your plotting toolkit
-of choice.
+You can determine which toolkits are available on your current machine
+using the `pandoc-plot toolkits` command. Here is the full help text:
-Usage: pandoc-plot.exe ([-v|--version] | [--full-version] | [-m|--manual] |
- [-t|--toolkits]) [COMMAND] [AST]
- This pandoc filter generates plots from code blocks using a multitude of
- possible renderers. This allows to keep documentation and figures in perfect
- synchronicity.
+``` bash
+Usage: pandoc-plot.exe toolkits [--config PATH]
+ Show information on toolkits and exit.
Available options:
- -v,--version Show version number and exit.
- --full-version Show full version information and exit.
- -m,--manual Open the manual page in the default web browser and
- exit.
- -t,--toolkits Show information on toolkits and exit. Executables
- from the configuration file will be used, if a
- '.pandoc-plot.yml' file is in the current directory.
+ --config PATH Path to optional configuration file.
-h,--help Show this help text
-
-Available commands:
- clean Clean output directories where figures from FILE
- might be stored. WARNING: All files in those
- directories will be deleted.
- write-example-config Write example configuration to a file.
-
-More information can be found via the manual (pandoc-plot --manual) or the repository README, located at
- https://github.com/LaurentRDC/pandoc-plot
```
-### As a filter
+#### Cleaning output
-The most common use for `pandoc-plot` is as a pandoc filter, in which case it should be called without arguments. For example:
+Figures produced by `pandoc-plot` can be placed in a few different
+locations. You can set a default location in the
+[Configuration](#configuration), but you can also re-direct specific
+figures in other directories if you use the `directory=...` argument in
+code blocks. These figures will build up over time. You can use the
+`clean` command to scan documents and delete the associated
+`pandoc-plot` output files. For example, to delete the figures generated
+from the `input.md` file:
-```bash
-pandoc --filter pandoc-plot -i input.md -o output.html
+``` bash
+pandoc-plot clean input.md
```
-If `pandoc-plot` fails to render a code block into a figure, the filtering will not stop. Your code blocks will stay unchanged.
-
-You can chain other filters with it (e.g., [`pandoc-crossref`](https://github.com/lierdakil/pandoc-crossref)) like so:
-
-```bash
-pandoc --filter pandoc-plot --filter pandoc-crossref -i input.md -o output.html
-```
+This sill remove all directories where a figure *could* have been
+placed. **WARNING**: all files will be removed.
-### Cleaning output
+Here is the full help text for the `clean` command:
-Figures produced by `pandoc-plot` can be placed in a few different locations. You can set a default location in the [Configuration](#configuration), but you can also re-direct specific figures in other directories if you use the `directory=...` argument in code blocks. These figures will build up over time. You can use the `clean` command to scan documents and delete the associated `pandoc-plot` output files. For example, to delete the figures generated from the `input.md` file:
+``` bash
+Usage: pandoc-plot.exe clean [--config PATH] FILE
+ Clean output directories where figures from FILE might be stored. WARNING: All
+ files in those directories will be deleted.
-```bash
-pandoc-plot clean input.md
+Available options:
+ --config PATH Path to optional configuration file.
+ -h,--help Show this help text
```
-This sill remove all directories where a figure *could* have been placed. **WARNING**: all files will be removed.
-
-### Configuration template
+#### Configuration template
-Because `pandoc-plot` supports a few toolkits, there are a lot of configuration options. Don't start from scratch! The `write-example-config` command will create a file for you, which you can then modify:
+Because `pandoc-plot` supports a few toolkits, there are a lot of
+configuration options. Don’t start from scratch\! The
+`write-example-config` command will create a file for you, which you can
+then modify:
-```bash
+``` bash
pandoc-plot write-example-config
```
-You will need to re-name the file to `.pandoc-ploy.yml` to be able to use it, so don't worry about overwriting your own configuration.
-
-### As a Haskell library
+You will need to re-name the file to `.pandoc-ploy.yml` to be able to
+use it, so don’t worry about overwriting your own configuration.
-To include the functionality of `pandoc-plot` in a Haskell package, you can use the `makePlot` function (for single blocks) or `plotTransform` function (for entire documents). [Take a look at the documentation on Hackage](https://hackage.haskell.org/package/pandoc-plot).
+Here is the full help text for the `write-example-config` command:
-#### Usage with Hakyll
+``` bash
+Usage: pandoc-plot.exe write-example-config [--path FILE]
+ Write example configuration to a file and exit.
-In case you want to use the filter with your own Hakyll setup, you can use a transform function that works on entire documents:
-
-```haskell
-import Text.Pandoc.Filter.Plot (plotTransform)
-
-import Data.Default (def) -- From data-default package, for default configuration
-import Hakyll
-
--- Unsafe compiler is required because of the interaction
--- in IO (i.e. running an external script).
-makePlotPandocCompiler :: Compiler (Item String)
-makePlotPandocCompiler =
- pandocCompilerWithTransformM
- defaultHakyllReaderOptions
- defaultHakyllWriterOptions
- (unsafeCompiler . plotTransform def) -- default configuration
+Available options:
+ --path FILE Target location of the configuration file. Default is
+ ".example-pandoc-plot.yml"
+ -h,--help Show this help text
```
## Installation
### Binaries and Installers
-Windows, Linux, and Mac OS binaries are available on the [GitHub release page](https://github.com/LaurentRDC/pandoc-plot/releases). There are also Windows installers.
+Windows, Linux, and Mac OS binaries are available on the [GitHub release
+page](https://github.com/LaurentRDC/pandoc-plot/releases). There are
+also Windows installers.
### conda
-Like `pandoc`, `pandoc-plot` is available as a package installable with [`conda`](https://docs.conda.io/en/latest/). [Click here to see the package page](https://anaconda.org/conda-forge/pandoc-plot).
+Like `pandoc`, `pandoc-plot` is available as a package installable with
+[`conda`](https://docs.conda.io/en/latest/). [Click here to see the
+package page](https://anaconda.org/conda-forge/pandoc-plot).
To install in the current environment:
-```sh
+``` sh
conda install -c conda-forge pandoc-plot
```
### From Hackage/Stackage
-`pandoc-plot` is available on [Hackage](http://hackage.haskell.org/package/pandoc-plot) and [Stackage](https://www.stackage.org/nightly/package/pandoc-plot). Using the [`cabal-install`](https://www.haskell.org/cabal/) tool:
+`pandoc-plot` is available on
+[Hackage](http://hackage.haskell.org/package/pandoc-plot) and
+[Stackage](https://www.stackage.org/nightly/package/pandoc-plot). Using
+the [`cabal-install`](https://www.haskell.org/cabal/) tool:
-```bash
+``` bash
cabal update
cabal install pandoc-plot
```
or
-```bash
+``` bash
stack update
stack install pandoc-plot
```
### From source
-Building from source can be done using [`stack`](https://docs.haskellstack.org/en/stable/README/) or [`cabal`](https://www.haskell.org/cabal/):
+Building from source can be done using
+[`stack`](https://docs.haskellstack.org/en/stable/README/) or
+[`cabal`](https://www.haskell.org/cabal/):
-```bash
+``` bash
git clone https://github.com/LaurentRDC/pandoc-plot
cd pandoc-plot
stack install # Alternatively, `cabal install`
@@ -421,4 +660,5 @@ stack install # Alternatively, `cabal install`
## Warning
-Do not run this filter on unknown documents. There is nothing in `pandoc-plot` that can stop a script from performing **evil actions**.
+Do not run this filter on unknown documents. There is nothing in
+`pandoc-plot` that can stop a script from performing **evil actions**.
diff --git a/example-config.yml b/example-config.yml
index 688730f..c23b175 100644
--- a/example-config.yml
+++ b/example-config.yml
@@ -50,6 +50,11 @@ plotly_python:
# preamble: plotly-python.py
executable: python
+# The possible parameters for the Plotly/R toolkit
+plotly_r:
+ # preamble: plotly-r.r
+ executable: Rscript
+
# The possible parameters for the Mathematica toolkit
mathplot:
# preamble: mathematica.m
@@ -69,3 +74,8 @@ ggplot2:
gnuplot:
# preamble: gnuplot.gp
executable: gnuplot
+
+# The possible parameters for the graphviz toolkit
+graphviz:
+ # preamble: graphviz.dot
+ executable: dot \ No newline at end of file
diff --git a/executable/Main.hs b/executable/Main.hs
index 30a22e7..10fdec4 100644
--- a/executable/Main.hs
+++ b/executable/Main.hs
@@ -6,11 +6,12 @@
module Main where
import Control.Applicative ((<|>))
-import Control.Monad (join, forM_)
+import Control.Monad (join, forM_, when, msum)
import Data.List (intersperse, (\\))
import Data.Monoid ((<>))
import Data.Text (unpack)
+import Data.Version (parseVersion, showVersion)
import GitHash as Git
@@ -18,6 +19,8 @@ import Options.Applicative
import qualified Options.Applicative.Help.Pretty as P
import System.Directory (doesFileExist)
+import System.Environment (lookupEnv)
+import System.IO (hPutStrLn, stderr)
import System.IO.Temp (writeSystemTempFile)
import Text.Pandoc.Filter.Plot (availableToolkits,
@@ -27,12 +30,16 @@ import Text.Pandoc.Filter.Plot (availableToolkits,
Configuration(..))
import Text.Pandoc.Filter.Plot.Internal (cls, supportedSaveFormats,
toolkits, readDoc,
- cleanOutputDirs, )
+ cleanOutputDirs,
+ configurationPathMeta,
+ executable)
import Text.Pandoc (pandocVersion)
import Text.Pandoc.Definition (pandocTypesVersion)
import Text.Pandoc.JSON (toJSONFilter)
+import Text.ParserCombinators.ReadP (readP_to_S)
+
import Web.Browser (openBrowser)
import qualified Data.Version as V
@@ -41,22 +48,16 @@ import Paths_pandoc_plot (version)
import ManPage (embedManualHtml)
import ExampleConfig (embedExampleConfig)
--- It is understood that Opts Nothing Nothing should be used for filtering
-data Opts = Opts
- { optCommand :: Maybe Command
- , optFlag :: Maybe Flag
- }
-
--- The difference between commands and flags is that commands perform actions,
--- while flags only display information.
+-- The difference between commands and flags is that commands require knowledge of
+-- the configuration, while flags only display static information.
-data Command = Clean FilePath
- | WriteConfig
+data Command = Clean (Maybe FilePath) FilePath
+ | WriteConfig FilePath
+ | Toolkits (Maybe FilePath)
data Flag = Version
| FullVersion
| Manual
- | Toolkits
deriving (Eq)
@@ -81,16 +82,16 @@ main = join $ execParser opts
return $ go flag_ command_ input
go :: Maybe Flag -> Maybe Command -> Maybe String -> IO ()
- go (Just Version) _ _ = putStrLn (V.showVersion version)
- go (Just FullVersion) _ _ = showFullVersion
- go (Just Manual) _ _ = showManPage
- go (Just Toolkits) _ _ = showAvailableToolkits
- go _ (Just (Clean fp)) _ = clean fp
- go _ (Just WriteConfig) _ = writeFile ".example-pandoc-plot.yml" $(embedExampleConfig)
- go Nothing Nothing _ = toJSONFilterWithConfig
+ go (Just Version) _ _ = putStrLn (V.showVersion version)
+ go (Just FullVersion) _ _ = showFullVersion
+ go (Just Manual) _ _ = showManPage
+ go _ (Just (Toolkits mfp)) _ = showAvailableToolkits mfp
+ go _ (Just (Clean mfp fp)) _ = clean mfp fp
+ go _ (Just (WriteConfig fp)) _ = writeFile fp $(embedExampleConfig)
+ go Nothing Nothing _ = toJSONFilterWithConfig
flagParser :: Parser (Maybe Flag)
-flagParser = versionP <|> fullVersionP <|> manualP <|> toolkitsP
+flagParser = versionP <|> fullVersionP <|> manualP
where
versionP = flag Nothing (Just Version) (mconcat
[ long "version"
@@ -109,41 +110,85 @@ flagParser = versionP <|> fullVersionP <|> manualP <|> toolkitsP
, help "Open the manual page in the default web browser and exit."
])
- toolkitsP = flag Nothing (Just Toolkits) (mconcat
- [ long "toolkits"
- , short 't'
- , help "Show information on toolkits and exit. Executables from the configuration \
- \file will be used, if a '.pandoc-plot.yml' file is in the current directory."
- ])
-
commandParser :: Parser (Maybe Command)
-commandParser = optional $ subparser (
- command "clean" (
+commandParser = optional $ subparser $ mconcat
+ [ command "toolkits" (
+ info (toolkitsP <**> helper) (progDesc "Show information on toolkits and exit.")
+ )
+ , command "clean" (
info (cleanP <**> helper) (
progDesc "Clean output directories where figures from FILE might be stored.\
\ WARNING: All files in those directories will be deleted."
)
)
- <> command "write-example-config" (
- info (writeConfigP <**> helper) (progDesc "Write example configuration to a file.")
- )
+ , command "write-example-config" (
+ info (writeConfigP <**> helper) (progDesc "Write example configuration to a file and exit.")
)
+ ]
where
- cleanP = Clean <$> strArgument (metavar "FILE")
- writeConfigP = pure WriteConfig
-
-
+ configP = optional $ strOption (mconcat [long "config", metavar "PATH", help "Path to optional configuration file."])
+ toolkitsP = Toolkits <$> configP
+ cleanP = Clean <$> configP <*> strArgument (metavar "FILE")
+ writeConfigP = WriteConfig <$>
+ strOption (
+ mconcat [ long "path"
+ , metavar "FILE"
+ , value ".example-pandoc-plot.yml"
+ , help "Target location of the configuration file. Default is \".example-pandoc-plot.yml\""
+ ]
+ )
+
+-- | Determine configuration and run filter.
+--
+-- Priority for configuration:
+--
+-- (1) Loaded from filepath stored in document metadata, under the key @plot-configuration@;
+--
+-- (2) Loaded from file @.pandoc-plot.yml@ in current work directory;
+--
+-- (3) Default configuration
+--
toJSONFilterWithConfig :: IO ()
toJSONFilterWithConfig = do
- c <- config
- toJSONFilter (plotTransform c)
+ upToDatePandoc <- checkRuntimePandocVersion
+ when upToDatePandoc $ toJSONFilter $ \doc -> do
+ c <- maybe localConfig configuration (configurationPathMeta doc)
+ plotTransform c doc
+
+
+-- | Check that the runtime version of Pandoc is at least 2.8. The return value
+-- indicates whether the Pandoc version is new enough or not.
+checkRuntimePandocVersion :: IO Bool
+checkRuntimePandocVersion = do
+ let minimumPandocVersion = V.Version [2,8,0,0] []
+ -- Pandoc runs filters in an environment with two variables:
+ -- PANDOV_VERSION and PANDOC_READER_OPTS
+ -- We can use the former to ensure that people are not using pandoc < 2.8
+ pandocV <- lookupEnv "PANDOC_VERSION"
+ case pandocV >>= readVersion of
+ Nothing -> return True
+ Just v -> if (v < minimumPandocVersion)
+ then do
+ hPutStrLn stderr $ mconcat
+ [ "ERROR (pandoc-plot) The pandoc-plot filter only "
+ , "supports Pandoc 2.8 and newer. "
+ , "but you are using Pandoc "
+ , showVersion v
+ ]
+ return False
+ else return True
+ where
+ readVersion = fmap fst . lastMaybe . readP_to_S parseVersion
+ lastMaybe xs = if length xs > 1 then Just (last xs) else Nothing
-config :: IO Configuration
-config = do
+-- | Load configuration from local file @.pandoc-plot.yml@.
+-- If the file does not exist, the default configuration will be used.
+localConfig :: IO Configuration
+localConfig = do
configExists <- doesFileExist ".pandoc-plot.yml"
if configExists
- then configuration ".pandoc-plot.yml"
+ then configuration ".pandoc-plot.yml"
else return defaultConfiguration
@@ -162,32 +207,49 @@ showFullVersion = do
gitrev = either (const "unknown") Git.giHash ($$tGitInfoCwdTry)
-showAvailableToolkits :: IO ()
-showAvailableToolkits = do
- c <- config
+showAvailableToolkits :: Maybe FilePath -> IO ()
+showAvailableToolkits mfp = do
+ c <- case mfp of
+ Nothing -> localConfig
+ Just fp -> configuration fp
+
putStrLn "\nAVAILABLE TOOLKITS\n"
available <- availableToolkits c
- return available >>= mapM_ toolkitInfo
+ return available >>= mapM_ (availToolkitInfo c)
putStrLn "\nUNAVAILABLE TOOLKITS\n"
-- We don't use unavailableToolkits because this would force
-- more IO actions
let unavailable = toolkits \\ available
- return unavailable >>= mapM_ toolkitInfo
+ return unavailable >>= mapM_ (unavailToolkitInfo c)
where
- toolkitInfo tk = do
+ toolkitInfo avail conf tk = do
+ exe <- executable tk conf
putStrLn $ "Toolkit: " <> show tk
+ when avail $ putStrLn $ " Executable: " <> exe
putStrLn $ " Code block trigger: " <> (unpack . cls $ tk)
putStrLn $ " Supported save formats: " <> (mconcat . intersperse ", " . fmap show $ supportedSaveFormats tk)
putStrLn mempty
-
-
-clean :: FilePath -> IO ()
-clean fp = do
- conf <- config
+ availToolkitInfo = toolkitInfo True
+ unavailToolkitInfo = toolkitInfo False
+
+
+-- | Clean output directories associated with a file
+--
+-- Priority for configuration are the same as @toJSONFilterWithConfig@.
+clean :: Maybe FilePath -- Use configuration file?
+ -> FilePath -- Document to clean
+ -> IO ()
+clean mfp fp = do
+ doc <- readDoc fp
+ -- Note the priority for configuration:
+ -- (1) path of argument --config (2) document metadata (3) local .pandoc-plot.yml (4) default config
+ conf <- maybe localConfig configuration $ firstJusts [configurationPathMeta doc, mfp]
putStrLn $ "Cleaning output directories for " <> fp
- cleanedDirs <- readDoc fp >>= cleanOutputDirs conf
+ cleanedDirs <- cleanOutputDirs conf doc
forM_ cleanedDirs $ \d -> putStrLn $ "Removed directory " <> d
-
+ where
+ firstJusts :: [Maybe a] -> Maybe a
+ firstJusts = msum
showManPage :: IO ()
showManPage =
diff --git a/pandoc-plot.cabal b/pandoc-plot.cabal
index 5c2a4ab..b57e303 100644
--- a/pandoc-plot.cabal
+++ b/pandoc-plot.cabal
@@ -1,9 +1,10 @@
name: pandoc-plot
-version: 0.5.0.0
+version: 0.6.0.0
cabal-version: >= 1.12
synopsis: A Pandoc filter to include figures generated from code blocks using your plotting toolkit of choice.
description: A Pandoc filter to include figures generated from code blocks. Keep the document and code in the same location. Output is captured and included as a figure.
-category: Documentation
+category: Text
+copyright: (c) 2019-2020 Laurent P. René de Cotret
homepage: https://github.com/LaurentRDC/pandoc-plot#readme
bug-reports: https://github.com/LaurentRDC/pandoc-plot/issues
author: Laurent P. René de Cotret
@@ -36,12 +37,14 @@ library
Text.Pandoc.Filter.Plot.Renderers
Text.Pandoc.Filter.Plot.Renderers.Prelude
Text.Pandoc.Filter.Plot.Renderers.Matplotlib
- Text.Pandoc.Filter.Plot.Renderers.Plotly
+ Text.Pandoc.Filter.Plot.Renderers.PlotlyPython
+ Text.Pandoc.Filter.Plot.Renderers.PlotlyR
Text.Pandoc.Filter.Plot.Renderers.Matlab
Text.Pandoc.Filter.Plot.Renderers.Mathematica
Text.Pandoc.Filter.Plot.Renderers.Octave
Text.Pandoc.Filter.Plot.Renderers.GGPlot2
Text.Pandoc.Filter.Plot.Renderers.GNUPlot
+ Text.Pandoc.Filter.Plot.Renderers.Graphviz
hs-source-dirs:
src
ghc-options:
@@ -99,6 +102,7 @@ test-suite tests
other-modules:
Common
build-depends: base >= 4.11 && < 5
+ , containers
, directory
, data-default-class >= 0.1.2
, filepath
diff --git a/src/Text/Pandoc/Filter/Plot.hs b/src/Text/Pandoc/Filter/Plot.hs
index 06bb31e..194a2dc 100644
--- a/src/Text/Pandoc/Filter/Plot.hs
+++ b/src/Text/Pandoc/Filter/Plot.hs
@@ -49,7 +49,7 @@ Here are the possible attributes what pandoc-plot understands for ALL toolkits:
* @preamble=...@: Path to a file to include before the code block. Ideal to avoid repetition over many figures.
Default values for the above attributes are stored in the @Configuration@ datatype. These can be specified in a
-YAML file which must be named ".pandoc-plot.yml".
+YAML file.
Here is an example code block which will render a figure using gnuplot, in Markdown:
diff --git a/src/Text/Pandoc/Filter/Plot/Configuration.hs b/src/Text/Pandoc/Filter/Plot/Configuration.hs
index a3828cf..75741bf 100644
--- a/src/Text/Pandoc/Filter/Plot/Configuration.hs
+++ b/src/Text/Pandoc/Filter/Plot/Configuration.hs
@@ -15,24 +15,25 @@ Reading configuration from file
module Text.Pandoc.Filter.Plot.Configuration (
configuration
+ , configurationPathMeta
, defaultConfiguration
) where
import Data.Default.Class (Default, def)
import Data.Maybe (fromMaybe)
-import Data.Text (Text, pack)
+import Data.Text (Text, pack, unpack)
import qualified Data.Text.IO as TIO
import Data.Yaml
import Data.Yaml.Config (ignoreEnv, loadYamlSettings)
-import Text.Pandoc.Definition (Format(..))
+import Text.Pandoc.Definition (Format(..), Pandoc(..), MetaValue(..), lookupMeta)
import Text.Pandoc.Filter.Plot.Types
-- | Read configuration from a YAML file. The
-- keys are exactly the same as for code blocks.
--
--- If a key is either not present, its value will be set
+-- If a key is not present, its value will be set
-- to the default value. Parsing errors result in thrown exceptions.
configuration :: FilePath -> IO Configuration
configuration fp = (loadYamlSettings [fp] [] ignoreEnv) >>= renderConfig
@@ -45,6 +46,33 @@ defaultConfiguration :: Configuration
defaultConfiguration = def
+-- | Extact path to configuration from the metadata in a Pandoc document.
+-- The path to the configuration file should be under the @plot-configuration@ key.
+-- In case there is no such metadata, return the default configuration.
+--
+-- For example, at the top of a markdown file:
+--
+-- @
+-- ---
+-- title: My document
+-- author: John Doe
+-- plot-configuration: /path/to/file.yml
+-- ---
+-- @
+--
+-- The same can be specified via the command line using Pandoc's @-M@ flag:
+--
+-- > pandoc --filter pandoc-plot -M plot-configuration="path/to/file.yml" ...
+--
+-- @since 0.6.0.0
+configurationPathMeta :: Pandoc -> Maybe FilePath
+configurationPathMeta (Pandoc meta _) =
+ lookupMeta "plot-configuration" meta >>= getPath
+ where
+ getPath (MetaString s) = Just (unpack s)
+ getPath _ = Nothing
+
+
-- We define a precursor type because preambles are best specified as file paths,
-- but we want to read those files before building a full
-- @Configuration@ value.
@@ -58,10 +86,12 @@ data ConfigPrecursor = ConfigPrecursor
, _matplotlibPrec :: !MatplotlibPrecursor
, _matlabPrec :: !MatlabPrecursor
, _plotlyPythonPrec :: !PlotlyPythonPrecursor
+ , _plotlyRPrec :: !PlotlyRPrecursor
, _mathematicaPrec :: !MathematicaPrecursor
, _octavePrec :: !OctavePrecursor
, _ggplot2Prec :: !GGPlot2Precursor
, _gnuplotPrec :: !GNUPlotPrecursor
+ , _graphvizPrec :: !GraphvizPrecursor
}
instance Default ConfigPrecursor where
@@ -75,10 +105,12 @@ instance Default ConfigPrecursor where
, _matplotlibPrec = def
, _matlabPrec = def
, _plotlyPythonPrec = def
+ , _plotlyRPrec = def
, _mathematicaPrec = def
, _octavePrec = def
, _ggplot2Prec = def
, _gnuplotPrec = def
+ , _graphvizPrec = def
}
@@ -91,10 +123,12 @@ data MatplotlibPrecursor = MatplotlibPrecursor
}
data MatlabPrecursor = MatlabPrecursor {_matlabPreamble :: !(Maybe FilePath), _matlabExe :: !FilePath}
data PlotlyPythonPrecursor = PlotlyPythonPrecursor {_plotlyPythonPreamble :: !(Maybe FilePath), _plotlyPythonExe :: !FilePath}
+data PlotlyRPrecursor = PlotlyRPrecursor {_plotlyRPreamble :: !(Maybe FilePath), _plotlyRExe :: !FilePath}
data MathematicaPrecursor = MathematicaPrecursor {_mathematicaPreamble :: !(Maybe FilePath), _mathematicaExe :: !FilePath}
data OctavePrecursor = OctavePrecursor {_octavePreamble :: !(Maybe FilePath), _octaveExe :: !FilePath}
data GGPlot2Precursor = GGPlot2Precursor {_ggplot2Preamble :: !(Maybe FilePath), _ggplot2Exe :: !FilePath}
data GNUPlotPrecursor = GNUPlotPrecursor {_gnuplotPreamble :: !(Maybe FilePath), _gnuplotExe :: !FilePath}
+data GraphvizPrecursor = GraphvizPrecursor {_graphvizPreamble :: !(Maybe FilePath), _graphvizExe :: !FilePath}
instance Default MatplotlibPrecursor where
@@ -102,10 +136,12 @@ instance Default MatplotlibPrecursor where
instance Default MatlabPrecursor where def = MatlabPrecursor Nothing (matlabExe def)
instance Default PlotlyPythonPrecursor where def = PlotlyPythonPrecursor Nothing (plotlyPythonExe def)
+instance Default PlotlyRPrecursor where def = PlotlyRPrecursor Nothing (plotlyRExe def)
instance Default MathematicaPrecursor where def = MathematicaPrecursor Nothing (mathematicaExe def)
instance Default OctavePrecursor where def = OctavePrecursor Nothing (octaveExe def)
instance Default GGPlot2Precursor where def = GGPlot2Precursor Nothing (ggplot2Exe def)
instance Default GNUPlotPrecursor where def = GNUPlotPrecursor Nothing (gnuplotExe def)
+instance Default GraphvizPrecursor where def = GraphvizPrecursor Nothing (graphvizExe def)
instance FromJSON MatplotlibPrecursor where
parseJSON (Object v) =
@@ -124,6 +160,10 @@ instance FromJSON PlotlyPythonPrecursor where
parseJSON (Object v) = PlotlyPythonPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyPythonExe def)
parseJSON _ = fail $ mconcat ["Could not parse ", show PlotlyPython, " configuration."]
+instance FromJSON PlotlyRPrecursor where
+ parseJSON (Object v) = PlotlyRPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (plotlyRExe def)
+ parseJSON _ = fail $ mconcat ["Could not parse ", show PlotlyR, " configuration."]
+
instance FromJSON MathematicaPrecursor where
parseJSON (Object v) = MathematicaPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (mathematicaExe def)
parseJSON _ = fail $ mconcat ["Could not parse ", show Mathematica, " configuration."]
@@ -137,9 +177,13 @@ instance FromJSON GGPlot2Precursor where
parseJSON _ = fail $ mconcat ["Could not parse ", show GGPlot2, " configuration."]
instance FromJSON GNUPlotPrecursor where
- parseJSON (Object v) = GNUPlotPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (ggplot2Exe def)
+ parseJSON (Object v) = GNUPlotPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (gnuplotExe def)
parseJSON _ = fail $ mconcat ["Could not parse ", show GNUPlot, " configuration."]
+instance FromJSON GraphvizPrecursor where
+ parseJSON (Object v) = GraphvizPrecursor <$> v .:? (tshow PreambleK) <*> v .:? (tshow ExecutableK) .!= (graphvizExe def)
+ parseJSON _ = fail $ mconcat ["Could not parse ", show Graphviz, " configuration."]
+
instance FromJSON ConfigPrecursor where
parseJSON (Null) = return def -- In case of empty file
@@ -154,10 +198,12 @@ instance FromJSON ConfigPrecursor where
_matplotlibPrec <- v .:? (cls Matplotlib) .!= def
_matlabPrec <- v .:? (cls Matlab) .!= def
_plotlyPythonPrec <- v .:? (cls PlotlyPython) .!= def
+ _plotlyRPrec <- v .:? (cls PlotlyR) .!= def
_mathematicaPrec <- v .:? (cls Mathematica) .!= def
_octavePrec <- v .:? (cls Octave) .!= def
_ggplot2Prec <- v .:? (cls GGPlot2) .!= def
_gnuplotPrec <- v .:? (cls GNUPlot) .!= def
+ _graphvizPrec <- v .:? (cls Graphviz) .!= def
return $ ConfigPrecursor{..}
parseJSON _ = fail "Could not parse configuration."
@@ -177,18 +223,22 @@ renderConfig ConfigPrecursor{..} = do
matplotlibExe = _matplotlibExe _matplotlibPrec
matlabExe = _matlabExe _matlabPrec
plotlyPythonExe = _plotlyPythonExe _plotlyPythonPrec
+ plotlyRExe = _plotlyRExe _plotlyRPrec
mathematicaExe = _mathematicaExe _mathematicaPrec
octaveExe = _octaveExe _octavePrec
ggplot2Exe = _ggplot2Exe _ggplot2Prec
gnuplotExe = _gnuplotExe _gnuplotPrec
+ graphvizExe = _graphvizExe _graphvizPrec
matplotlibPreamble <- readPreamble (_matplotlibPreamble _matplotlibPrec)
matlabPreamble <- readPreamble (_matlabPreamble _matlabPrec)
plotlyPythonPreamble <- readPreamble (_plotlyPythonPreamble _plotlyPythonPrec)
+ plotlyRPreamble <- readPreamble (_plotlyRPreamble _plotlyRPrec)
mathematicaPreamble <- readPreamble (_mathematicaPreamble _mathematicaPrec)
octavePreamble <- readPreamble (_octavePreamble _octavePrec)
ggplot2Preamble <- readPreamble (_ggplot2Preamble _ggplot2Prec)
gnuplotPreamble <- readPreamble (_gnuplotPreamble _gnuplotPrec)
+ graphvizPreamble <- readPreamble (_graphvizPreamble _graphvizPrec)
return Configuration{..}
where
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers.hs b/src/Text/Pandoc/Filter/Plot/Renderers.hs
index 69a2bb5..528343a 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers.hs
@@ -21,9 +21,11 @@ module Text.Pandoc.Filter.Plot.Renderers (
, parseExtraAttrs
, command
, capture
+ , executable
, toolkitAvailable
, availableToolkits
, unavailableToolkits
+ , OutputSpec(..)
) where
import Control.Concurrent.Async (mapConcurrently)
@@ -37,9 +39,12 @@ import Text.Pandoc.Filter.Plot.Renderers.Mathematica
import Text.Pandoc.Filter.Plot.Renderers.Matlab
import Text.Pandoc.Filter.Plot.Renderers.Matplotlib
import Text.Pandoc.Filter.Plot.Renderers.Octave
-import Text.Pandoc.Filter.Plot.Renderers.Plotly
+import Text.Pandoc.Filter.Plot.Renderers.PlotlyPython
+import Text.Pandoc.Filter.Plot.Renderers.PlotlyR
import Text.Pandoc.Filter.Plot.Renderers.GGPlot2
import Text.Pandoc.Filter.Plot.Renderers.GNUPlot
+import Text.Pandoc.Filter.Plot.Renderers.Graphviz
+import Text.Pandoc.Filter.Plot.Renderers.Prelude (executable, OutputSpec(..))
import Text.Pandoc.Filter.Plot.Types
@@ -48,44 +53,52 @@ import Text.Pandoc.Filter.Plot.Types
scriptExtension :: Toolkit -> String
scriptExtension Matplotlib = ".py"
scriptExtension PlotlyPython = ".py"
+scriptExtension PlotlyR = ".r"
scriptExtension Matlab = ".m"
scriptExtension Mathematica = ".m"
scriptExtension Octave = ".m"
scriptExtension GGPlot2 = ".r"
scriptExtension GNUPlot = ".gp"
+scriptExtension Graphviz = ".dot"
-- Make a string into a comment
comment :: Toolkit -> (Text -> Text)
comment Matplotlib = mappend "# "
comment PlotlyPython = mappend "# "
+comment PlotlyR = mappend "# "
comment Matlab = mappend "% "
comment Mathematica = \t -> mconcat ["(*", t, "*)"]
comment Octave = mappend "% "
comment GGPlot2 = mappend "# "
comment GNUPlot = mappend "# "
+comment Graphviz = mappend "// "
-- | The function that maps from configuration to the preamble.
preambleSelector :: Toolkit -> (Configuration -> Script)
preambleSelector Matplotlib = matplotlibPreamble
preambleSelector PlotlyPython = plotlyPythonPreamble
+preambleSelector PlotlyR = plotlyRPreamble
preambleSelector Matlab = matlabPreamble
preambleSelector Mathematica = mathematicaPreamble
preambleSelector Octave = octavePreamble
preambleSelector GGPlot2 = ggplot2Preamble
preambleSelector GNUPlot = gnuplotPreamble
+preambleSelector Graphviz = graphvizPreamble
-- | Save formats supported by this renderer.
supportedSaveFormats :: Toolkit -> [SaveFormat]
supportedSaveFormats Matplotlib = matplotlibSupportedSaveFormats
supportedSaveFormats PlotlyPython = plotlyPythonSupportedSaveFormats
+supportedSaveFormats PlotlyR = plotlyRSupportedSaveFormats
supportedSaveFormats Matlab = matlabSupportedSaveFormats
supportedSaveFormats Mathematica = mathematicaSupportedSaveFormats
supportedSaveFormats Octave = octaveSupportedSaveFormats
supportedSaveFormats GGPlot2 = ggplot2SupportedSaveFormats
supportedSaveFormats GNUPlot = gnuplotSupportedSaveFormats
+supportedSaveFormats Graphviz = graphvizSupportedSaveFormats
-- Checks to perform before running a script. If ANY check fails,
@@ -104,36 +117,45 @@ parseExtraAttrs _ = return mempty
-- | Generate the appropriate command-line command to generate a figure.
-command :: Toolkit -> (Configuration -> FigureSpec -> FilePath -> Text)
+-- The executable will need to be found first, hence the IO monad.
+command :: Toolkit
+ -> OutputSpec
+ -> IO Text
command Matplotlib = matplotlibCommand
command PlotlyPython = plotlyPythonCommand
+command PlotlyR = plotlyRCommand
command Matlab = matlabCommand
command Mathematica = mathematicaCommand
command Octave = octaveCommand
command GGPlot2 = ggplot2Command
command GNUPlot = gnuplotCommand
+command Graphviz = graphvizCommand
-- | Script fragment required to capture a figure.
capture :: Toolkit -> (FigureSpec -> FilePath -> Script)
capture Matplotlib = matplotlibCapture
capture PlotlyPython = plotlyPythonCapture
+capture PlotlyR = plotlyRCapture
capture Matlab = matlabCapture
capture Mathematica = mathematicaCapture
capture Octave = octaveCapture
capture GGPlot2 = ggplot2Capture
capture GNUPlot = gnuplotCapture
+capture Graphviz = graphvizCapture
-- | Check if a toolkit is available, based on the current configuration
toolkitAvailable :: Toolkit -> Configuration -> IO Bool
toolkitAvailable Matplotlib = matplotlibAvailable
toolkitAvailable PlotlyPython = plotlyPythonAvailable
+toolkitAvailable PlotlyR = plotlyRAvailable
toolkitAvailable Matlab = matlabAvailable
toolkitAvailable Mathematica = mathematicaAvailable
toolkitAvailable Octave = octaveAvailable
toolkitAvailable GGPlot2 = ggplot2Available
toolkitAvailable GNUPlot = gnuplotAvailable
+toolkitAvailable Graphviz = graphvizAvailable
-- | List of toolkits available on this machine.
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs b/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs
index 9ff3caa..8e833f6 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs
@@ -10,7 +10,7 @@ Maintainer : laurent.decotret@outlook.com
Stability : internal
Portability : portable
-Rendering Mathematica plots code blocks
+Rendering GGPlot2 plots code blocks
-}
module Text.Pandoc.Filter.Plot.Renderers.GGPlot2 (
@@ -22,16 +22,21 @@ module Text.Pandoc.Filter.Plot.Renderers.GGPlot2 (
import Text.Pandoc.Filter.Plot.Renderers.Prelude
+
ggplot2SupportedSaveFormats :: [SaveFormat]
ggplot2SupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, TIF]
-ggplot2Command :: Configuration -> FigureSpec -> FilePath -> Text
-ggplot2Command Configuration{..} _ fp = [st|#{ggplot2Exe} "#{fp}"|]
+ggplot2Command :: OutputSpec -> IO Text
+ggplot2Command OutputSpec{..} = do
+ exe <- executable GGPlot2 oConfiguration
+ return [st|#{exe} "#{oScriptPath}"|]
ggplot2Available :: Configuration -> IO Bool
-ggplot2Available Configuration{..} = commandSuccess [st|#{ggplot2Exe} -e 'library("ggplot2")'|]
+ggplot2Available conf = do
+ exe <- executable GGPlot2 conf
+ commandSuccess [st|#{exe} -e 'library("ggplot2")'|]
ggplot2Capture :: FigureSpec -> FilePath -> Script
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs b/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs
index e488419..bdcfbeb 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs
@@ -26,12 +26,16 @@ gnuplotSupportedSaveFormats :: [SaveFormat]
gnuplotSupportedSaveFormats = [PNG, SVG, EPS, GIF, JPG, PDF]
-gnuplotCommand :: Configuration -> FigureSpec -> FilePath -> Text
-gnuplotCommand Configuration{..} _ fp = [st|#{gnuplotExe} -c "#{fp}"|]
+gnuplotCommand :: OutputSpec -> IO Text
+gnuplotCommand OutputSpec{..} = do
+ exe <- executable GNUPlot oConfiguration
+ return [st|#{exe} -c "#{oScriptPath}"|]
gnuplotAvailable :: Configuration -> IO Bool
-gnuplotAvailable Configuration{..} = commandSuccess [st|#{gnuplotExe} -h|]
+gnuplotAvailable conf = do
+ exe <- executable GNUPlot conf
+ commandSuccess [st|#{exe} -h|]
gnuplotCapture :: FigureSpec -> FilePath -> Script
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs
new file mode 100644
index 0000000..6bce0b4
--- /dev/null
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs
@@ -0,0 +1,47 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-|
+Module : $header$
+Copyright : (c) Laurent P René de Cotret, 2020
+License : GNU GPL, version 2 or above
+Maintainer : laurent.decotret@outlook.com
+Stability : internal
+Portability : portable
+
+Rendering Graphviz plots code blocks
+-}
+
+module Text.Pandoc.Filter.Plot.Renderers.Graphviz (
+ graphvizSupportedSaveFormats
+ , graphvizCommand
+ , graphvizCapture
+ , graphvizAvailable
+) where
+
+import Data.Char
+import Text.Pandoc.Filter.Plot.Renderers.Prelude
+
+
+graphvizSupportedSaveFormats :: [SaveFormat]
+graphvizSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, WEBP, GIF]
+
+
+graphvizCommand :: OutputSpec -> IO Text
+graphvizCommand OutputSpec{..} = do
+ exe <- executable Graphviz oConfiguration
+ let fmt = fmap toLower . show . saveFormat $ oFigureSpec
+ return [st|#{exe} -T#{fmt} -o "#{oFigurePath}" "#{oScriptPath}"|]
+
+
+graphvizAvailable :: Configuration -> IO Bool
+graphvizAvailable conf = do
+ exe <- executable Graphviz conf
+ commandSuccess [st|#{exe} -?|]
+
+
+-- Graphviz export is entirely based on command-line arguments
+-- so there is no need to modify the script itself.
+graphvizCapture :: FigureSpec -> FilePath -> Script
+graphvizCapture _ _ = mempty
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs
index a3c83f1..4eec412 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs
@@ -26,12 +26,16 @@ mathematicaSupportedSaveFormats :: [SaveFormat]
mathematicaSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
-mathematicaCommand :: Configuration -> FigureSpec -> FilePath -> Text
-mathematicaCommand Configuration{..} _ fp = [st|#{mathematicaExe} -script "#{fp}"|]
+mathematicaCommand :: OutputSpec -> IO Text
+mathematicaCommand OutputSpec{..} = do
+ exe <- executable Mathematica oConfiguration
+ return [st|#{exe} -script "#{oScriptPath}"|]
mathematicaAvailable :: Configuration -> IO Bool
-mathematicaAvailable Configuration{..} = commandSuccess [st|#{mathematicaExe} -h|] -- TODO: test this
+mathematicaAvailable conf = do
+ exe <- executable Mathematica conf
+ commandSuccess [st|#{exe} -h|] -- TODO: test this
mathematicaCapture :: FigureSpec -> FilePath -> Script
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs
index f518855..52e524e 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs
@@ -20,6 +20,8 @@ module Text.Pandoc.Filter.Plot.Renderers.Matlab (
, matlabAvailable
) where
+import System.Directory (exeExtension)
+
import Text.Pandoc.Filter.Plot.Renderers.Prelude
@@ -27,8 +29,10 @@ matlabSupportedSaveFormats :: [SaveFormat]
matlabSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
-matlabCommand :: Configuration -> FigureSpec -> FilePath -> Text
-matlabCommand Configuration{..} _ fp = [st|#{matlabExe} -batch "run('#{fp}')"|]
+matlabCommand :: OutputSpec -> IO Text
+matlabCommand OutputSpec{..} = do
+ exe <- executable Matlab oConfiguration
+ return [st|#{exe} -batch "run('#{oScriptPath}')"|]
-- On Windows at least, "matlab -help" actually returns -1, even though the
@@ -36,11 +40,7 @@ matlabCommand Configuration{..} _ fp = [st|#{matlabExe} -batch "run('#{fp}')"|]
-- Therefore, we cannot rely on this behavior to know if matlab is present,
-- like other toolkits.
matlabAvailable :: Configuration -> IO Bool
-matlabAvailable Configuration{..} = existsOnPath (matlabExe <> ext)
- where
- -- The @which@ function from Turtle only works on
- -- windows if the executable extension is included.
- ext = if isWindows then ".exe" else mempty
+matlabAvailable Configuration{..} = existsOnPath (matlabExe <> exeExtension)
matlabCapture :: FigureSpec -> FilePath -> Script
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs
index 5d6dc16..80fa6bb 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs
@@ -36,8 +36,10 @@ matplotlibSupportedSaveFormats :: [SaveFormat]
matplotlibSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
-matplotlibCommand :: Configuration -> FigureSpec -> FilePath -> Text
-matplotlibCommand Configuration{..} _ fp = [st|#{matplotlibExe} "#{fp}"|]
+matplotlibCommand :: OutputSpec -> IO Text
+matplotlibCommand OutputSpec{..} = do
+ exe <- executable Matplotlib oConfiguration
+ return [st|#{exe} "#{oScriptPath}"|]
matplotlibCapture :: FigureSpec -> FilePath -> Script
@@ -57,7 +59,9 @@ matplotlibExtraAttrs kv = M.filterWithKey (\k _ -> k `elem` ["tight_bbox", "tran
matplotlibAvailable :: Configuration -> IO Bool
-matplotlibAvailable Configuration{..} = commandSuccess [st|#{matplotlibExe} -c "import matplotlib"|]
+matplotlibAvailable conf = do
+ exe <- executable Matplotlib conf
+ commandSuccess [st|#{exe} -c "import matplotlib"|]
-- | Check if `matplotlib.pyplot.show()` calls are present in the script,
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs
index dcc9f34..77bf576 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs
@@ -27,12 +27,16 @@ octaveSupportedSaveFormats :: [SaveFormat]
octaveSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS, GIF, TIF]
-octaveCommand :: Configuration -> FigureSpec -> FilePath -> Text
-octaveCommand Configuration{..} _ fp = [st|#{octaveExe} --no-gui --no-window-system "#{fp}"|]
+octaveCommand :: OutputSpec -> IO Text
+octaveCommand OutputSpec{..} = do
+ exe <- executable Octave oConfiguration
+ return [st|#{exe} --no-gui --no-window-system "#{oScriptPath}"|]
octaveAvailable :: Configuration -> IO Bool
-octaveAvailable Configuration{..} = commandSuccess [st|#{octaveExe} -h|]
+octaveAvailable conf = do
+ exe <- executable Octave conf
+ commandSuccess [st|#{exe} -h|]
octaveCapture :: FigureSpec -> FilePath -> Script
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Plotly.hs b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs
index de4e920..7a4820c 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Plotly.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs
@@ -10,10 +10,10 @@ Maintainer : laurent.decotret@outlook.com
Stability : internal
Portability : portable
-Rendering Plotly code blocks
+Rendering Plotly-python code blocks
-}
-module Text.Pandoc.Filter.Plot.Renderers.Plotly (
+module Text.Pandoc.Filter.Plot.Renderers.PlotlyPython (
plotlyPythonSupportedSaveFormats
, plotlyPythonCommand
, plotlyPythonCapture
@@ -27,12 +27,16 @@ plotlyPythonSupportedSaveFormats :: [SaveFormat]
plotlyPythonSupportedSaveFormats = [PNG, JPG, WEBP, PDF, SVG, EPS]
-plotlyPythonCommand :: Configuration -> FigureSpec -> FilePath -> Text
-plotlyPythonCommand Configuration{..} _ fp = [st|#{plotlyPythonExe} "#{fp}"|]
+plotlyPythonCommand :: OutputSpec -> IO Text
+plotlyPythonCommand OutputSpec{..} = do
+ exe <- executable PlotlyPython oConfiguration
+ return [st|#{exe} "#{oScriptPath}"|]
plotlyPythonAvailable :: Configuration -> IO Bool
-plotlyPythonAvailable Configuration{..} = commandSuccess [st|#{plotlyPythonExe} -c "import plotly.graph_objects"|]
+plotlyPythonAvailable conf = do
+ exe <- executable PlotlyPython conf
+ commandSuccess [st|#{exe} -c "import plotly.graph_objects"|]
plotlyPythonCapture :: FigureSpec -> FilePath -> Script
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs
new file mode 100644
index 0000000..bb1782c
--- /dev/null
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs
@@ -0,0 +1,49 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-|
+Module : $header$
+Copyright : (c) Laurent P René de Cotret, 2020
+License : GNU GPL, version 2 or above
+Maintainer : laurent.decotret@outlook.com
+Stability : internal
+Portability : portable
+
+Rendering Plotly/R plots code blocks
+-}
+
+module Text.Pandoc.Filter.Plot.Renderers.PlotlyR (
+ plotlyRSupportedSaveFormats
+ , plotlyRCommand
+ , plotlyRCapture
+ , plotlyRAvailable
+) where
+
+import Text.Pandoc.Filter.Plot.Renderers.Prelude
+
+
+plotlyRSupportedSaveFormats :: [SaveFormat]
+plotlyRSupportedSaveFormats = [PNG, PDF, SVG, JPG, EPS]
+
+
+plotlyRCommand :: OutputSpec -> IO Text
+plotlyRCommand OutputSpec{..} = do
+ exe <- executable PlotlyR oConfiguration
+ return [st|#{exe} "#{oScriptPath}"|]
+
+
+plotlyRAvailable :: Configuration -> IO Bool
+plotlyRAvailable conf = do
+ exe <- executable GGPlot2 conf
+ commandSuccess [st|#{exe} -e 'library("plotly")'|]
+
+
+-- Based on the following documentation:
+-- https://plotly.com/r/static-image-export/
+plotlyRCapture :: FigureSpec -> FilePath -> Script
+plotlyRCapture FigureSpec{..} fname = [st|
+library(plotly) # just in case
+if (!require("processx")) install.packages("processx")
+orca(last_plot(), file = "#{fname}")
+|]
diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs
index b0faeb1..3eb5443 100644
--- a/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs
+++ b/src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs
@@ -19,10 +19,14 @@ module Text.Pandoc.Filter.Plot.Renderers.Prelude (
, unpack
, commandSuccess
, existsOnPath
+ , executable
+ , OutputSpec(..)
) where
import Data.Maybe (isJust)
import Data.Text (Text, unpack)
+
+import System.Directory (findExecutable)
import System.Exit (ExitCode(..))
import System.Process.Typed (runProcess, shell,
setStdout, setStderr,
@@ -47,4 +51,34 @@ commandSuccess s = do
-- | Checks that an executable is available on path, at all.
existsOnPath :: FilePath -> IO Bool
-existsOnPath fp = Sh.which (Sh.fromString fp) >>= fmap isJust . return \ No newline at end of file
+existsOnPath fp = Sh.which (Sh.fromString fp) >>= fmap isJust . return
+
+
+-- | Try to find the executable and normalise its path.
+-- If it cannot be found, it is left unchanged - just in case.
+tryToFindExe :: String -> IO FilePath
+tryToFindExe fp = findExecutable fp >>= maybe (return fp) return
+
+
+-- | Path to the executable of a toolkit. If the executable can
+-- be found, then it will be the full path to it.
+executable :: Toolkit -> Configuration -> IO FilePath
+executable Matplotlib = tryToFindExe . matplotlibExe
+executable PlotlyPython = tryToFindExe . plotlyPythonExe
+executable PlotlyR = tryToFindExe . plotlyRExe
+executable Matlab = tryToFindExe . matlabExe
+executable Mathematica = tryToFindExe . mathematicaExe
+executable Octave = tryToFindExe . octaveExe
+executable GGPlot2 = tryToFindExe . ggplot2Exe
+executable GNUPlot = tryToFindExe . gnuplotExe
+executable Graphviz = tryToFindExe . graphvizExe
+
+
+-- | Internal description of all information
+-- needed to output a figure.
+data OutputSpec = OutputSpec
+ { oConfiguration :: Configuration -- ^ Pandoc-plot configuration
+ , oFigureSpec :: FigureSpec -- ^ Figure spec
+ , oScriptPath :: FilePath -- ^ Path to the script to render
+ , oFigurePath :: FilePath -- ^ Figure output path
+ }
diff --git a/src/Text/Pandoc/Filter/Plot/Scripting.hs b/src/Text/Pandoc/Filter/Plot/Scripting.hs
index 676fb3a..aa8ec7e 100644
--- a/src/Text/Pandoc/Filter/Plot/Scripting.hs
+++ b/src/Text/Pandoc/Filter/Plot/Scripting.hs
@@ -52,6 +52,7 @@ data ScriptResult
| ScriptFailure String Int -- Command and exit code
| ToolkitNotInstalled Toolkit -- Script failed because toolkit is not installed
+
-- Run script as described by the spec, only if necessary
runScriptIfNecessary :: FigureSpec -> PlotM ScriptResult
runScriptIfNecessary spec = do
@@ -80,8 +81,6 @@ runTempScript spec@FigureSpec{..} = do
case checkResult of
CheckFailed msg -> return $ ScriptChecksFailed msg
CheckPassed -> do
- -- We involve the script hash as a temporary filename
- -- so that there is never any collision
scriptPath <- tempScriptPath spec
let captureFragment = (capture tk) spec (figurePath spec)
-- Note: for gnuplot, the capture string must be placed
@@ -91,7 +90,12 @@ runTempScript spec@FigureSpec{..} = do
then mconcat [captureFragment, "\n", script]
else mconcat [script, "\n", captureFragment]
liftIO $ T.writeFile scriptPath scriptWithCapture
- let command_ = T.unpack $ command tk conf spec scriptPath
+ let outputSpec = OutputSpec { oConfiguration = conf
+ , oFigureSpec = spec
+ , oScriptPath = scriptPath
+ , oFigurePath = figurePath spec
+ }
+ command_ <- T.unpack <$> (liftIO $ command tk outputSpec)
ec <- liftIO
$ runProcess
@@ -134,11 +138,13 @@ toImage fmt spec = head . toList $ para $ imageWith attrs' (T.pack target') "fig
-- is important.
tempScriptPath :: FigureSpec -> PlotM FilePath
tempScriptPath FigureSpec{..} = do
- tk <- asks toolkit
+ ext <- scriptExtension <$> asks toolkit
-- Note that matlab will refuse to process files that don't start with
-- a letter... so we append the renderer name
- let ext = scriptExtension tk
- hashedPath = "pandocplot" <> (show . abs . hash $ script) <> ext
+ -- Note that this hash is only so that we are running scripts from unique
+ -- file names; it does NOT determine whether this figure should
+ -- be rendered or not.
+ let hashedPath = "pandocplot" <> (show . abs . hash $ script) <> ext
liftIO $ (</> hashedPath) <$> getCanonicalTemporaryDirectory
@@ -148,8 +154,11 @@ sourceCodePath = normalise . flip replaceExtension ".txt" . figurePath
-- | Determine the path a figure should have.
+-- The path for this file is unique to the content of the figure,
+-- so that @figurePath@ can be used to determine whether a figure should
+-- be rendered again or not.
figurePath :: FigureSpec -> FilePath
figurePath spec = normalise $ directory spec </> stem spec
where
- stem = flip addExtension ext . show . hash
+ stem = flip addExtension ext . show . figureContentHash
ext = extension . saveFormat $ spec
diff --git a/src/Text/Pandoc/Filter/Plot/Types.hs b/src/Text/Pandoc/Filter/Plot/Types.hs
index fb2b787..c48cf92 100644
--- a/src/Text/Pandoc/Filter/Plot/Types.hs
+++ b/src/Text/Pandoc/Filter/Plot/Types.hs
@@ -1,5 +1,6 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
{-|
@@ -23,6 +24,7 @@ module Text.Pandoc.Filter.Plot.Types (
, InclusionKey(..)
, FigureSpec(..)
, SaveFormat(..)
+ , figureContentHash
, cls
, extension
, toolkits
@@ -35,7 +37,7 @@ import Control.Monad.Reader
import Data.Char (toLower)
import Data.Default.Class (Default, def)
-import Data.Hashable (Hashable (..))
+import Data.Hashable (hash)
import Data.List (intersperse)
import Data.Semigroup (Semigroup (..))
import Data.String (IsString (..))
@@ -47,43 +49,54 @@ import System.Info (os)
import Text.Pandoc.Definition (Attr, Format(..))
+
toolkits :: [Toolkit]
toolkits = enumFromTo minBound maxBound
+
-- | Enumeration of supported toolkits
data Toolkit
= Matplotlib
| Matlab
| PlotlyPython
+ | PlotlyR
| Mathematica
| Octave
| GGPlot2
| GNUPlot
+ | Graphviz
deriving (Bounded, Eq, Enum, Generic)
+
-- | This instance should only be used to display toolkit names
instance Show Toolkit where
show Matplotlib = "Python/Matplotlib"
show Matlab = "MATLAB"
show PlotlyPython = "Python/Plotly"
+ show PlotlyR = "R/Plotly"
show Mathematica = "Mathematica"
show Octave = "GNU Octave"
show GGPlot2 = "ggplot2"
show GNUPlot = "gnuplot"
+ show Graphviz = "graphviz"
+
-- | Class name which will trigger the filter
cls :: Toolkit -> Text
cls Matplotlib = "matplotlib"
cls Matlab = "matlabplot"
cls PlotlyPython = "plotly_python"
+cls PlotlyR = "plotly_r"
cls Mathematica = "mathplot"
cls Octave = "octaveplot"
cls GGPlot2 = "ggplot2"
cls GNUPlot = "gnuplot"
+cls Graphviz = "graphviz"
type PlotM a = ReaderT PlotEnv IO a
+
data PlotEnv
= PlotEnv { toolkit :: !Toolkit
, config :: !Configuration
@@ -91,6 +104,24 @@ data PlotEnv
-- | The @Configuration@ type holds the default values to use
-- when running pandoc-plot. These values can be overridden in code blocks.
+--
+-- You can create an instance of the @Configuration@ type from file using the @configuration@ function.
+--
+-- You can store the path to a configuration file in metadata under the key @plot-configuration@. For example, in Markdown:
+--
+-- @
+-- ---
+-- title: My document
+-- author: John Doe
+-- plot-configuration: /path/to/file.yml
+-- ---
+-- @
+--
+-- The same can be specified via the command line using Pandoc's @-M@ flag:
+--
+-- > pandoc --filter pandoc-plot -M plot-configuration="path/to/file.yml" ...
+--
+-- In this case, use @configurationPathMeta@ to extact the path from @Pandoc@ documents.
data Configuration = Configuration
{ defaultDirectory :: !FilePath -- ^ The default directory where figures will be saved.
, defaultWithSource :: !Bool -- ^ The default behavior of whether or not to include links to source code and high-res
@@ -100,19 +131,23 @@ data Configuration = Configuration
, matplotlibPreamble :: !Script -- ^ The default preamble script for the matplotlib toolkit.
, plotlyPythonPreamble :: !Script -- ^ The default preamble script for the Plotly/Python toolkit.
+ , plotlyRPreamble :: !Script -- ^ The default preamble script for the Plotly/R toolkit.
, matlabPreamble :: !Script -- ^ The default preamble script for the MATLAB toolkit.
, mathematicaPreamble :: !Script -- ^ The default preamble script for the Mathematica toolkit.
, octavePreamble :: !Script -- ^ The default preamble script for the GNU Octave toolkit.
, ggplot2Preamble :: !Script -- ^ The default preamble script for the GGPlot2 toolkit.
, gnuplotPreamble :: !Script -- ^ The default preamble script for the gnuplot toolkit.
+ , graphvizPreamble :: !Script -- ^ The default preamble script for the Graphviz toolkit.
, matplotlibExe :: !FilePath -- ^ The executable to use to generate figures using the matplotlib toolkit.
, matlabExe :: !FilePath -- ^ The executable to use to generate figures using the MATLAB toolkit.
, plotlyPythonExe :: !FilePath -- ^ The executable to use to generate figures using the Plotly/Python toolkit.
+ , plotlyRExe :: !FilePath -- ^ The executable to use to generate figures using the Plotly/R toolkit.
, mathematicaExe :: !FilePath -- ^ The executable to use to generate figures using the Mathematica toolkit.
, octaveExe :: !FilePath -- ^ The executable to use to generate figures using the GNU Octave toolkit.
, ggplot2Exe :: !FilePath -- ^ The executable to use to generate figures using the GGPlot2 toolkit.
, gnuplotExe :: !FilePath -- ^ The executable to use to generate figures using the gnuplot toolkit.
+ , graphvizExe :: !FilePath -- ^ The executable to use to generate figures using the Graphviz toolkit.
, matplotlibTightBBox :: !Bool -- ^ Whether or not to make Matplotlib figures tight by default.
, matplotlibTransparent :: !Bool -- ^ Whether or not to make Matplotlib figures transparent by default.
@@ -128,19 +163,23 @@ instance Default Configuration where
, matplotlibPreamble = mempty
, plotlyPythonPreamble= mempty
+ , plotlyRPreamble = mempty
, matlabPreamble = mempty
, mathematicaPreamble = mempty
, octavePreamble = mempty
, ggplot2Preamble = mempty
, gnuplotPreamble = mempty
+ , graphvizPreamble = mempty
, matplotlibExe = if isWindows then "python" else "python3"
, matlabExe = "matlab"
, plotlyPythonExe = if isWindows then "python" else "python3"
+ , plotlyRExe = "Rscript"
, mathematicaExe = "math"
, octaveExe = "octave"
, ggplot2Exe = "Rscript"
, gnuplotExe = "gnuplot"
+ , graphvizExe = "dot"
, matplotlibTightBBox = False
, matplotlibTransparent = False
@@ -148,6 +187,7 @@ instance Default Configuration where
type Script = Text
+
-- | Result of checking scripts for problems
data CheckResult
= CheckPassed
@@ -162,6 +202,7 @@ instance Semigroup CheckResult where
instance Monoid CheckResult where
mempty = CheckPassed
+
-- | Description of any possible inclusion key, both in documents
-- and in configuration files.
data InclusionKey
@@ -211,9 +252,18 @@ data FigureSpec = FigureSpec
, dpi :: !Int -- ^ Dots-per-inch of figure.
, extraAttrs :: ![(Text, Text)] -- ^ Renderer-specific extra attributes.
, blockAttrs :: !Attr -- ^ Attributes not related to @pandoc-plot@ will be propagated.
- } deriving Generic
+ }
+
+
+-- | Hash of the content of a @FigureSpec@. Note that unlike usual hashes,
+-- two @FigureSpec@ with the same @figureContentHash@ does not mean that they are equal!
+--
+-- Not all parts of a FigureSpec are related to running code.
+-- For example, changing the caption should not require running the figure again.
+figureContentHash :: FigureSpec -> Int
+figureContentHash FigureSpec{..} =
+ hash (script, fromEnum saveFormat, directory, dpi, extraAttrs)
-instance Hashable FigureSpec -- From Generic
-- | Generated figure file format supported by pandoc-plot.
-- Note that not all formats are supported by all toolkits.
@@ -228,8 +278,6 @@ data SaveFormat
| WEBP
deriving (Bounded, Enum, Eq, Show, Generic)
-instance Hashable SaveFormat -- From Generic
-
instance IsString SaveFormat where
-- | An error is thrown if the save format cannot be parsed. That's OK
-- since pandoc-plot is a command-line tool and isn't expected to run
@@ -256,6 +304,7 @@ instance FromJSON SaveFormat -- TODO: test this parsing
instance ToJSON SaveFormat where
toJSON = toJSON . extension
+
-- | Save format file extension
extension :: SaveFormat -> String
extension fmt = mconcat [".", fmap toLower . show $ fmt]
diff --git a/tests/Common.hs b/tests/Common.hs
index 68e2055..0e54b4e 100644
--- a/tests/Common.hs
+++ b/tests/Common.hs
@@ -69,6 +69,7 @@ testFileInclusion tk =
include Octave = "tests/includes/octave.m"
include GGPlot2 = "tests/includes/ggplot2.r"
include GNUPlot = "tests/includes/gnuplot.gp"
+ include Graphviz = "tests/includes/graphviz.dot"
-------------------------------------------------------------------------------
-- Test that the files are saved in the appropriate format
@@ -261,6 +262,7 @@ trivialContent Mathematica = "\n"
trivialContent Octave = "figure('visible', 'off')\nplot (-10:0.1:10);"
trivialContent GGPlot2 = "library(ggplot2)\nggplot()\n"
trivialContent GNUPlot = "plot sin(x)"
+trivialContent Graphviz = "digraph {A -> B [label=\"test\"];}"
addCaption :: String -> Block -> Block
@@ -324,6 +326,7 @@ assertIsInfix xs ys = unless (xs `isInfixOf` ys) (assertFailure msg)
where
msg = mconcat ["Expected ", show xs, " to be an infix of ", show ys]
+
-- Ensure a directory is empty but exists.
ensureDirectoryExistsAndEmpty :: FilePath -> IO ()
ensureDirectoryExistsAndEmpty dir = do
@@ -333,5 +336,6 @@ ensureDirectoryExistsAndEmpty dir = do
else return ()
createDirectory dir
+
tshow :: Show a => a -> Text
tshow = pack . show
diff --git a/tests/Main.hs b/tests/Main.hs
index 72b3aab..e592e0c 100644
--- a/tests/Main.hs
+++ b/tests/Main.hs
@@ -4,6 +4,7 @@
import Control.Monad (forM_)
import Data.Default.Class (Default, def)
+import qualified Data.Map.Strict as Map
import Data.Text (Text, unpack)
import Test.Tasty
@@ -28,6 +29,7 @@ main = do
"Configuration tests"
[ testEmptyConfiguration
, testExampleConfiguration
+ , testConfigurationPathMeta
]
, testGroup
"Parsing tests"
@@ -59,11 +61,11 @@ testEmptyConfiguration =
testCase "empty configuration is correctly parsed to default values" $ do
let config = def
- parsedConfig <- configuration "tests/fixtures/.pandoc-plot.yml"
+ parsedConfig <- configuration "tests/fixtures/.empty-config.yml"
assertEqual "" config parsedConfig
--- The exampel configuration is build by hand (to add comments)
+-- The example configuration is build by hand (to add comments)
-- and it is embedded into the executable. Therefore, we must make sure it
-- is correctly parsed (and is therefore valid.)
testExampleConfiguration :: TestTree
@@ -79,6 +81,19 @@ testExampleConfiguration =
parsedConfig <- configuration "example-config.yml"
assertEqual "" config parsedConfig
+
+-- Test that the path to configuration in metadata is found correctly
+testConfigurationPathMeta :: TestTree
+testConfigurationPathMeta =
+ testCase "Configuration path stored in metadata is correctly parsed" $ do
+ let configPath = "tests/fixtures/.config-meta.yml"
+ meta = B.Meta $ Map.fromList [("plot-configuration", B.MetaString configPath)]
+
+ parsedConfig <- maybe (return defaultConfiguration) configuration $ configurationPathMeta (B.Pandoc meta mempty)
+ expected <- configuration (unpack configPath)
+ assertEqual "" expected parsedConfig
+
+
testCaptionReader :: TestTree
testCaptionReader =
testCase "caption is parsed in the same way as input document format" $ do