pandoc-plot 0.5.0.0 → 0.6.0.0
raw patch · 23 files changed
+961/−311 lines, 23 filesdep ~pandocdep ~pandoc-typesPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: pandoc, pandoc-types
API changes (from Hackage documentation)
+ Text.Pandoc.Filter.Plot: [graphvizExe] :: Configuration -> !FilePath
+ Text.Pandoc.Filter.Plot: [graphvizPreamble] :: Configuration -> !Script
+ Text.Pandoc.Filter.Plot: [plotlyRExe] :: Configuration -> !FilePath
+ Text.Pandoc.Filter.Plot: [plotlyRPreamble] :: Configuration -> !Script
+ Text.Pandoc.Filter.Plot.Internal: Graphviz :: Toolkit
+ Text.Pandoc.Filter.Plot.Internal: OutputSpec :: Configuration -> FigureSpec -> FilePath -> FilePath -> OutputSpec
+ Text.Pandoc.Filter.Plot.Internal: PlotlyR :: Toolkit
+ Text.Pandoc.Filter.Plot.Internal: [graphvizExe] :: Configuration -> !FilePath
+ Text.Pandoc.Filter.Plot.Internal: [graphvizPreamble] :: Configuration -> !Script
+ Text.Pandoc.Filter.Plot.Internal: [oConfiguration] :: OutputSpec -> Configuration
+ Text.Pandoc.Filter.Plot.Internal: [oFigurePath] :: OutputSpec -> FilePath
+ Text.Pandoc.Filter.Plot.Internal: [oFigureSpec] :: OutputSpec -> FigureSpec
+ Text.Pandoc.Filter.Plot.Internal: [oScriptPath] :: OutputSpec -> FilePath
+ Text.Pandoc.Filter.Plot.Internal: [plotlyRExe] :: Configuration -> !FilePath
+ Text.Pandoc.Filter.Plot.Internal: [plotlyRPreamble] :: Configuration -> !Script
+ Text.Pandoc.Filter.Plot.Internal: configurationPathMeta :: Pandoc -> Maybe FilePath
+ Text.Pandoc.Filter.Plot.Internal: data OutputSpec
+ Text.Pandoc.Filter.Plot.Internal: executable :: Toolkit -> Configuration -> IO FilePath
+ Text.Pandoc.Filter.Plot.Internal: figureContentHash :: FigureSpec -> Int
- Text.Pandoc.Filter.Plot: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
+ Text.Pandoc.Filter.Plot: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
- Text.Pandoc.Filter.Plot.Internal: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
+ Text.Pandoc.Filter.Plot.Internal: Configuration :: !FilePath -> !Bool -> !Int -> !SaveFormat -> !Format -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !Script -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !FilePath -> !Bool -> !Bool -> Configuration
- Text.Pandoc.Filter.Plot.Internal: command :: Toolkit -> Configuration -> FigureSpec -> FilePath -> Text
+ Text.Pandoc.Filter.Plot.Internal: command :: Toolkit -> OutputSpec -> IO Text
Files
- CHANGELOG.md +30/−0
- README.md +408/−168
- example-config.yml +10/−0
- executable/Main.hs +114/−52
- pandoc-plot.cabal +7/−3
- src/Text/Pandoc/Filter/Plot.hs +1/−1
- src/Text/Pandoc/Filter/Plot/Configuration.hs +54/−4
- src/Text/Pandoc/Filter/Plot/Renderers.hs +24/−2
- src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs +9/−4
- src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs +7/−3
- src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs +47/−0
- src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs +7/−3
- src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs +7/−7
- src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs +7/−3
- src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs +7/−3
- src/Text/Pandoc/Filter/Plot/Renderers/Plotly.hs +0/−43
- src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs +47/−0
- src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs +49/−0
- src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs +35/−1
- src/Text/Pandoc/Filter/Plot/Scripting.hs +16/−7
- src/Text/Pandoc/Filter/Plot/Types.hs +54/−5
- tests/Common.hs +4/−0
- tests/Main.hs +17/−2
CHANGELOG.md view
@@ -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 ---------------
README.md view
@@ -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 -[](http://hackage.haskell.org/package/pandoc-plot) [](http://stackage.org/nightly/package/pandoc-plot) [](https://ci.appveyor.com/project/LaurentRDC/pandoc-plot) [](https://dev.azure.com/laurentdecotret/pandoc-plot/_build/latest?definitionId=5&branchName=master)  [](https://anaconda.org/conda-forge/pandoc-plot) +[](http://hackage.haskell.org/package/pandoc-plot) [](http://stackage.org/nightly/package/pandoc-plot) [](https://ci.appveyor.com/project/LaurentRDC/pandoc-plot) [](https://dev.azure.com/laurentdecotret/pandoc-plot/_build/latest?definitionId=5&branchName=master) [](https://www.gnu.org/licenses/gpl.html) [](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.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/); +`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/-t` flag: +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 +``` 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 @@ 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 @@ \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 @@ ### 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.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 -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: +`pandoc-plot` is a command line executable with a few functions. You can +take a look at the help using the `-h`/`--help` flag: -```yaml +``` 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. + # 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 +--- +``` -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. +or on the command line, using pandoc’s `-M/--metadata` flag: +``` 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 - -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). - -#### Usage with Hakyll - -In case you want to use the filter with your own Hakyll setup, you can use a transform function that works on entire documents: +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. -```haskell -import Text.Pandoc.Filter.Plot (plotTransform) +Here is the full help text for the `write-example-config` command: -import Data.Default (def) -- From data-default package, for default configuration -import Hakyll +``` bash +Usage: pandoc-plot.exe write-example-config [--path FILE] + Write example configuration to a file and exit. --- 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 @@ ## 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**.
example-config.yml view
@@ -50,6 +50,11 @@ # 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 @@ gnuplot: # preamble: gnuplot.gp executable: gnuplot + +# The possible parameters for the graphviz toolkit +graphviz: + # preamble: graphviz.dot + executable: dot
executable/Main.hs view
@@ -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 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 @@ 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 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 @@ 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 @@ , 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 -config :: IO Configuration -config = do +-- | 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 + + +-- | 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 @@ 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 + availToolkitInfo = toolkitInfo True + unavailToolkitInfo = toolkitInfo False -clean :: FilePath -> IO () -clean fp = do - conf <- config +-- | 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 =
pandoc-plot.cabal view
@@ -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 @@ 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 @@ other-modules: Common build-depends: base >= 4.11 && < 5 + , containers , directory , data-default-class >= 0.1.2 , filepath
src/Text/Pandoc/Filter/Plot.hs view
@@ -49,7 +49,7 @@ * @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:
src/Text/Pandoc/Filter/Plot/Configuration.hs view
@@ -15,24 +15,25 @@ 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 = 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 @@ , _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 @@ , _matplotlibPrec = def , _matlabPrec = def , _plotlyPythonPrec = def + , _plotlyRPrec = def , _mathematicaPrec = def , _octavePrec = def , _ggplot2Prec = def , _gnuplotPrec = def + , _graphvizPrec = def } @@ -91,10 +123,12 @@ } 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 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 @@ 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,10 +177,14 @@ 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 parseJSON (Object v) = do @@ -154,10 +198,12 @@ _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 @@ 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
src/Text/Pandoc/Filter/Plot/Renderers.hs view
@@ -21,9 +21,11 @@ , parseExtraAttrs , command , capture + , executable , toolkitAvailable , availableToolkits , unavailableToolkits + , OutputSpec(..) ) where import Control.Concurrent.Async (mapConcurrently) @@ -37,9 +39,12 @@ 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 @@ 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 @@ -- | 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.
src/Text/Pandoc/Filter/Plot/Renderers/GGPlot2.hs view
@@ -10,7 +10,7 @@ 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 @@ 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
src/Text/Pandoc/Filter/Plot/Renderers/GNUPlot.hs view
@@ -26,12 +26,16 @@ 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
+ src/Text/Pandoc/Filter/Plot/Renderers/Graphviz.hs view
@@ -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
src/Text/Pandoc/Filter/Plot/Renderers/Mathematica.hs view
@@ -26,12 +26,16 @@ 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
src/Text/Pandoc/Filter/Plot/Renderers/Matlab.hs view
@@ -20,6 +20,8 @@ , matlabAvailable ) where +import System.Directory (exeExtension) + import Text.Pandoc.Filter.Plot.Renderers.Prelude @@ -27,8 +29,10 @@ 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 @@ -- 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
src/Text/Pandoc/Filter/Plot/Renderers/Matplotlib.hs view
@@ -36,8 +36,10 @@ 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 @@ 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,
src/Text/Pandoc/Filter/Plot/Renderers/Octave.hs view
@@ -27,12 +27,16 @@ 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
− src/Text/Pandoc/Filter/Plot/Renderers/Plotly.hs
@@ -1,43 +0,0 @@-{-# 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 code blocks --} - -module Text.Pandoc.Filter.Plot.Renderers.Plotly ( - plotlyPythonSupportedSaveFormats - , plotlyPythonCommand - , plotlyPythonCapture - , plotlyPythonAvailable -) where - -import Text.Pandoc.Filter.Plot.Renderers.Prelude - - -plotlyPythonSupportedSaveFormats :: [SaveFormat] -plotlyPythonSupportedSaveFormats = [PNG, JPG, WEBP, PDF, SVG, EPS] - - -plotlyPythonCommand :: Configuration -> FigureSpec -> FilePath -> Text -plotlyPythonCommand Configuration{..} _ fp = [st|#{plotlyPythonExe} "#{fp}"|] - - -plotlyPythonAvailable :: Configuration -> IO Bool -plotlyPythonAvailable Configuration{..} = commandSuccess [st|#{plotlyPythonExe} -c "import plotly.graph_objects"|] - - -plotlyPythonCapture :: FigureSpec -> FilePath -> Script -plotlyPythonCapture _ fname = [st| -import plotly.graph_objects as go -__current_plotly_figure = next(obj for obj in globals().values() if type(obj) == go.Figure) -__current_plotly_figure.write_image(r"#{fname}") -|]
+ src/Text/Pandoc/Filter/Plot/Renderers/PlotlyPython.hs view
@@ -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 Plotly-python code blocks +-} + +module Text.Pandoc.Filter.Plot.Renderers.PlotlyPython ( + plotlyPythonSupportedSaveFormats + , plotlyPythonCommand + , plotlyPythonCapture + , plotlyPythonAvailable +) where + +import Text.Pandoc.Filter.Plot.Renderers.Prelude + + +plotlyPythonSupportedSaveFormats :: [SaveFormat] +plotlyPythonSupportedSaveFormats = [PNG, JPG, WEBP, PDF, SVG, EPS] + + +plotlyPythonCommand :: OutputSpec -> IO Text +plotlyPythonCommand OutputSpec{..} = do + exe <- executable PlotlyPython oConfiguration + return [st|#{exe} "#{oScriptPath}"|] + + +plotlyPythonAvailable :: Configuration -> IO Bool +plotlyPythonAvailable conf = do + exe <- executable PlotlyPython conf + commandSuccess [st|#{exe} -c "import plotly.graph_objects"|] + + +plotlyPythonCapture :: FigureSpec -> FilePath -> Script +plotlyPythonCapture _ fname = [st| +import plotly.graph_objects as go +__current_plotly_figure = next(obj for obj in globals().values() if type(obj) == go.Figure) +__current_plotly_figure.write_image(r"#{fname}") +|]
+ src/Text/Pandoc/Filter/Plot/Renderers/PlotlyR.hs view
@@ -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}") +|]
src/Text/Pandoc/Filter/Plot/Renderers/Prelude.hs view
@@ -19,10 +19,14 @@ , 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 @@ -- | Checks that an executable is available on path, at all. existsOnPath :: FilePath -> IO Bool -existsOnPath fp = Sh.which (Sh.fromString fp) >>= fmap isJust . return+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 + }
src/Text/Pandoc/Filter/Plot/Scripting.hs view
@@ -52,6 +52,7 @@ | 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 @@ 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 @@ 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 @@ -- 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 @@ -- | 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
src/Text/Pandoc/Filter/Plot/Types.hs view
@@ -1,5 +1,6 @@ {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} {-| @@ -23,6 +24,7 @@ , InclusionKey(..) , FigureSpec(..) , SaveFormat(..) + , figureContentHash , cls , extension , toolkits @@ -35,7 +37,7 @@ 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 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 @@ -- | 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 @@ , 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 @@ , 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 @@ type Script = Text + -- | Result of checking scripts for problems data CheckResult = CheckPassed @@ -162,6 +202,7 @@ instance Monoid CheckResult where mempty = CheckPassed + -- | Description of any possible inclusion key, both in documents -- and in configuration files. data InclusionKey @@ -211,10 +252,19 @@ , 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 + } -instance Hashable FigureSpec -- From 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) + + -- | Generated figure file format supported by pandoc-plot. -- Note that not all formats are supported by all toolkits. data SaveFormat @@ -228,8 +278,6 @@ | 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 @@ -255,6 +303,7 @@ instance ToJSON SaveFormat where toJSON = toJSON . extension + -- | Save format file extension extension :: SaveFormat -> String
tests/Common.hs view
@@ -69,6 +69,7 @@ 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 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 @@ 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 @@ -332,6 +335,7 @@ then removePathForcibly dir else return () createDirectory dir + tshow :: Show a => a -> Text tshow = pack . show
tests/Main.hs view
@@ -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 @@ "Configuration tests" [ testEmptyConfiguration , testExampleConfiguration + , testConfigurationPathMeta ] , testGroup "Parsing tests" @@ -59,11 +61,11 @@ 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 @@ -78,6 +80,19 @@ 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 =