diff --git a/example-config.yml b/example-config.yml index 530d01f..b32ec53 100644 --- a/example-config.yml +++ b/example-config.yml @@ -146,3 +146,7 @@ sageplot: d2: executable: d2 command_line_arguments: + +mermaid: + executable: mmdc + command_line_arguments: diff --git a/pandoc-plot.cabal b/pandoc-plot.cabal index 59eec3a..29a703a 100644 --- a/pandoc-plot.cabal +++ b/pandoc-plot.cabal @@ -85,6 +85,7 @@ library Text.Pandoc.Filter.Plot.Renderers.SageMath Text.Pandoc.Filter.Plot.Renderers.D2 Text.Pandoc.Filter.Plot.Renderers.Asymptote + Text.Pandoc.Filter.Plot.Renderers.Mermaid Text.Pandoc.Filter.Plot.Monad Text.Pandoc.Filter.Plot.Monad.Logging Text.Pandoc.Filter.Plot.Monad.Types diff --git a/src/Text/Pandoc/Filter/Plot/Configuration.hs b/src/Text/Pandoc/Filter/Plot/Configuration.hs index c399986..de146e0 100644 --- a/src/Text/Pandoc/Filter/Plot/Configuration.hs +++ b/src/Text/Pandoc/Filter/Plot/Configuration.hs @@ -66,6 +66,7 @@ defaultConfiguration = sagemathPreamble = mempty, d2Preamble = mempty, asyPreamble = mempty, + mermaidPreamble = mempty, -- Executables matplotlibExe = python, matlabExe = "matlab", @@ -82,6 +83,7 @@ defaultConfiguration = sagemathExe = "sage", d2Exe = "d2", asyExe = "asy", + mermaidExe = "mmdc", -- Command line arguments matplotlibCmdArgs = mempty, matlabCmdArgs = mempty, @@ -98,6 +100,7 @@ defaultConfiguration = sagemathCmdArgs = mempty, d2CmdArgs = mempty, asyCmdArgs = mempty, + mermaidCmdArgs = mempty, -- Extras matplotlibTightBBox = False, matplotlibTransparent = False @@ -159,7 +162,8 @@ data ConfigPrecursor = ConfigPrecursor _plantumlPrec :: !PlantUMLPrecursor, _sagemathPrec :: !SageMathPrecursor, _d2Prec :: !D2Precursor, - _asyPrec :: !AsyPrecursor + _asyPrec :: !AsyPrecursor, + _mermaidPrec :: !MermaidPrecursor } defaultConfigPrecursor :: ConfigPrecursor @@ -188,7 +192,8 @@ defaultConfigPrecursor = _plantumlPrec = PlantUMLPrecursor Nothing (plantumlExe defaultConfiguration) (plantumlCmdArgs defaultConfiguration), _sagemathPrec = SageMathPrecursor Nothing (sagemathExe defaultConfiguration) (sagemathCmdArgs defaultConfiguration), _d2Prec = D2Precursor Nothing (d2Exe defaultConfiguration) (d2CmdArgs defaultConfiguration), - _asyPrec = AsyPrecursor Nothing (asyExe defaultConfiguration) (asyCmdArgs defaultConfiguration) + _asyPrec = AsyPrecursor Nothing (asyExe defaultConfiguration) (asyCmdArgs defaultConfiguration), + _mermaidPrec = MermaidPrecursor Nothing (mermaidExe defaultConfiguration) (mermaidCmdArgs defaultConfiguration) } data LoggingPrecursor = LoggingPrecursor @@ -233,6 +238,8 @@ data D2Precursor = D2Precursor {_d2Preamble :: !(Maybe FilePath), _d2Exe :: !Fil data AsyPrecursor = AsyPrecursor {_asyPreamble :: !(Maybe FilePath), _asyExe :: !FilePath, _asyCmdArgs :: !Text} +data MermaidPrecursor = MermaidPrecursor {_mermaidPreamble :: !(Maybe FilePath), _mermaidExe :: !FilePath, _mermaidCmdArgs :: !Text} + instance FromJSON LoggingPrecursor where parseJSON (Object v) = LoggingPrecursor @@ -303,12 +310,16 @@ instance FromJSON SageMathPrecursor where instance FromJSON D2Precursor where parseJSON (Object v) = D2Precursor <$> v .:? asKey PreambleK <*> v .:? asKey ExecutableK .!= d2Exe defaultConfiguration <*> v .:? asKey CommandLineArgsK .!= d2CmdArgs defaultConfiguration - parseJSON _ = fail $ mconcat ["Could not parse ", show SageMath, " configuration."] + parseJSON _ = fail $ mconcat ["Could not parse ", show D2, " configuration."] instance FromJSON AsyPrecursor where parseJSON (Object v) = AsyPrecursor <$> v .:? asKey PreambleK <*> v .:? asKey ExecutableK .!= asyExe defaultConfiguration <*> v .:? asKey CommandLineArgsK .!= asyCmdArgs defaultConfiguration parseJSON _ = fail $ mconcat ["Could not parse ", show Asymptote, " configuration."] +instance FromJSON MermaidPrecursor where + parseJSON (Object v) = MermaidPrecursor <$> v .:? asKey PreambleK <*> v .:? asKey ExecutableK .!= mermaidExe defaultConfiguration <*> v .:? asKey CommandLineArgsK .!= mermaidCmdArgs defaultConfiguration + parseJSON _ = fail $ mconcat ["Could not parse ", show Mermaid, " configuration."] + toolkitAsKey :: Toolkit -> Key toolkitAsKey = fromString . unpack . cls @@ -340,6 +351,7 @@ instance FromJSON ConfigPrecursor where _sagemathPrec <- v .:? toolkitAsKey SageMath .!= _sagemathPrec defaultConfigPrecursor _d2Prec <- v .:? toolkitAsKey D2 .!= _d2Prec defaultConfigPrecursor _asyPrec <- v .:? toolkitAsKey Asymptote .!= _asyPrec defaultConfigPrecursor + _mermaidPrec <- v .:? toolkitAsKey Mermaid .!= _mermaidPrec defaultConfigPrecursor return $ ConfigPrecursor {..} parseJSON _ = fail "Could not parse configuration." @@ -376,6 +388,7 @@ renderConfig ConfigPrecursor {..} = do sagemathExe = _sagemathExe _sagemathPrec d2Exe = _d2Exe _d2Prec asyExe = _asyExe _asyPrec + mermaidExe = _mermaidExe _mermaidPrec matplotlibCmdArgs = _matplotlibCmdArgs _matplotlibPrec matlabCmdArgs = _matlabCmdArgs _matlabPrec @@ -392,6 +405,7 @@ renderConfig ConfigPrecursor {..} = do sagemathCmdArgs = _sagemathCmdArgs _sagemathPrec d2CmdArgs = _d2CmdArgs _d2Prec asyCmdArgs = _asyCmdArgs _asyPrec + mermaidCmdArgs = _mermaidCmdArgs _mermaidPrec matplotlibPreamble <- readPreamble (_matplotlibPreamble _matplotlibPrec) matlabPreamble <- readPreamble (_matlabPreamble _matlabPrec) @@ -408,6 +422,7 @@ renderConfig ConfigPrecursor {..} = do sagemathPreamble <- readPreamble (_sagemathPreamble _sagemathPrec) d2Preamble <- readPreamble (_d2Preamble _d2Prec) asyPreamble <- readPreamble (_asyPreamble _asyPrec) + mermaidPreamble <- readPreamble (_mermaidPreamble _mermaidPrec) return Configuration {..} where diff --git a/src/Text/Pandoc/Filter/Plot/Monad.hs b/src/Text/Pandoc/Filter/Plot/Monad.hs index ed5b607..53ce5d8 100644 --- a/src/Text/Pandoc/Filter/Plot/Monad.hs +++ b/src/Text/Pandoc/Filter/Plot/Monad.hs @@ -284,6 +284,7 @@ executable tk = exeSelector tk <&> exeFromPath exeSelector SageMath = asksConfig sagemathExe exeSelector D2 = asksConfig d2Exe exeSelector Asymptote = asksConfig asyExe + exeSelector Mermaid = asksConfig mermaidExe -- | The @Configuration@ type holds the default values to use -- when running pandoc-plot. These values can be overridden in code blocks. @@ -356,6 +357,8 @@ data Configuration = Configuration d2Preamble :: !Script, -- | The default preamble script for the Asymptote toolkit. asyPreamble :: !Script, + -- | The default preamble script for the Mermaid toolkit. + mermaidPreamble :: !Script, -- | The executable to use to generate figures using the matplotlib toolkit. matplotlibExe :: !FilePath, -- | The executable to use to generate figures using the MATLAB toolkit. @@ -386,6 +389,8 @@ data Configuration = Configuration d2Exe :: !FilePath, -- | The executable to use to generate figures using Asymptote asyExe :: !FilePath, + -- | The executable to use to generate figures using Mermaid + mermaidExe :: !FilePath, -- | Command-line arguments to pass to the Python interpreter for the Matplotlib toolkit matplotlibCmdArgs :: !Text, -- | Command-line arguments to pass to the interpreter for the MATLAB toolkit. @@ -416,6 +421,8 @@ data Configuration = Configuration d2CmdArgs :: !Text, -- | Command-line arguments to pass to the interpreter for the Asymptote toolkit. asyCmdArgs :: !Text, + -- | Command-line arguments to pass to the interpreter for the Mermaid toolkit. + mermaidCmdArgs :: !Text, -- | Whether or not to make Matplotlib figures tight by default. matplotlibTightBBox :: !Bool, -- | Whether or not to make Matplotlib figures transparent by default. diff --git a/src/Text/Pandoc/Filter/Plot/Monad/Types.hs b/src/Text/Pandoc/Filter/Plot/Monad/Types.hs index 003c827..ae051c9 100644 --- a/src/Text/Pandoc/Filter/Plot/Monad/Types.hs +++ b/src/Text/Pandoc/Filter/Plot/Monad/Types.hs @@ -63,6 +63,7 @@ data Toolkit | SageMath | D2 | Asymptote + | Mermaid deriving (Bounded, Eq, Enum, Generic, Ord) -- | This instance should only be used to display toolkit names @@ -82,6 +83,7 @@ instance Show Toolkit where show SageMath = "SageMath" show D2 = "D2" show Asymptote = "Asymptote" + show Mermaid = "Mermaid" -- | Class name which will trigger the filter cls :: Toolkit -> Text @@ -100,6 +102,7 @@ cls PlantUML = "plantuml" cls SageMath = "sageplot" cls D2 = "d2" cls Asymptote = "asy" +cls Mermaid = "mermaid" -- | Executable program, and sometimes the directory where it can be found. data Executable diff --git a/src/Text/Pandoc/Filter/Plot/Renderers.hs b/src/Text/Pandoc/Filter/Plot/Renderers.hs index 5fca78b..47f590f 100644 --- a/src/Text/Pandoc/Filter/Plot/Renderers.hs +++ b/src/Text/Pandoc/Filter/Plot/Renderers.hs @@ -99,6 +99,10 @@ import Text.Pandoc.Filter.Plot.Renderers.Asymptote ( asymptote, asymptoteSupportedSaveFormats, ) +import Text.Pandoc.Filter.Plot.Renderers.Mermaid + ( mermaid, + mermaidSupportedSaveFormats, + ) -- | Get the renderer associated with a toolkit. -- If the renderer has not been used before, -- initialize it and store where it is. It will be re-used. @@ -118,6 +122,7 @@ renderer PlantUML = plantuml renderer SageMath = sagemath renderer D2 = d2 renderer Asymptote = asymptote +renderer Mermaid = mermaid -- | Save formats supported by this renderer. supportedSaveFormats :: Toolkit -> [SaveFormat] @@ -136,6 +141,7 @@ supportedSaveFormats PlantUML = plantumlSupportedSaveFormats supportedSaveFormats SageMath = sagemathSupportedSaveFormats supportedSaveFormats D2 = d2SupportedSaveFormats supportedSaveFormats Asymptote = asymptoteSupportedSaveFormats +supportedSaveFormats Mermaid = mermaidSupportedSaveFormats -- | The function that maps from configuration to the preamble. preambleSelector :: Toolkit -> (Configuration -> Script) @@ -154,6 +160,7 @@ preambleSelector PlantUML = plantumlPreamble preambleSelector SageMath = sagemathPreamble preambleSelector D2 = d2Preamble preambleSelector Asymptote = asyPreamble +preambleSelector Mermaid = mermaidPreamble -- | Parse code block headers for extra attributes that are specific -- to this renderer. By default, no extra attributes are parsed. diff --git a/src/Text/Pandoc/Filter/Plot/Renderers/Mermaid.hs b/src/Text/Pandoc/Filter/Plot/Renderers/Mermaid.hs new file mode 100644 index 0000000..f111165 --- /dev/null +++ b/src/Text/Pandoc/Filter/Plot/Renderers/Mermaid.hs @@ -0,0 +1,51 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | +-- Module : $header$ +-- Copyright : (c) Sanchayan Maity, 2024 - present +-- License : GNU GPL, version 2 or above +-- Maintainer : sanchayan@sanchayanmaity.net +-- Stability : internal +-- Portability : portable +-- +-- Rendering Mermaid plots code blocks +module Text.Pandoc.Filter.Plot.Renderers.Mermaid + ( mermaid, + mermaidSupportedSaveFormats, + ) +where + +import Data.Char (toLower) +import Text.Pandoc.Filter.Plot.Renderers.Prelude + +mermaid :: PlotM Renderer +mermaid = do + cmdargs <- asksConfig mermaidCmdArgs + return + $ Renderer + { rendererToolkit = Mermaid, + rendererCapture = mermaidCapture, + rendererCommand = mermaidCommand cmdargs, + rendererAvailability = CommandSuccess $ \exe -> [st|#{pathToExe exe} -V|], + rendererSupportedSaveFormats = mermaidSupportedSaveFormats, + rendererChecks = mempty, + rendererLanguage = "mermaid", + rendererComment = mempty, + rendererScriptExtension = ".mermaid" + } + +mermaidSupportedSaveFormats :: [SaveFormat] +mermaidSupportedSaveFormats = [PDF, PNG, SVG] + +mermaidCommand :: Text -> OutputSpec -> Text +mermaidCommand cmdargs OutputSpec {..} = + let fmt = fmap toLower . show . saveFormat $ oFigureSpec + in [st|#{pathToExe oExecutable} #{cmdargs} -q -e #{fmt} -i "#{oScriptPath}" -o "#{oFigurePath}"|] + +-- Mermaid export is entirely based on command-line arguments +-- so there is no need to modify the script itself. +mermaidCapture :: FigureSpec -> FilePath -> Script +mermaidCapture FigureSpec {..} _ = script diff --git a/tests/Common.hs b/tests/Common.hs index 3a14e80..9952948 100644 --- a/tests/Common.hs +++ b/tests/Common.hs @@ -123,6 +123,7 @@ testFileInclusion tk = include SageMath = "tests/includes/sagemath.sage" include D2 = "tests/includes/d2-dd.d2" include Asymptote = "tests/includes/asymptote.asy" + include Mermaid = "tests/includes/mermaid.mermaid" ------------------------------------------------------------------------------- -- Tests that the files are saved in all the advertised formats @@ -423,6 +424,7 @@ trivialContent PlantUML = "@startuml\nAlice -> Bob: test\n@enduml" trivialContent SageMath = "G = plot(sin, 1, 10)" trivialContent D2 = "x -> y -> z" trivialContent Asymptote = "draw((0,0)--(1,0));" +trivialContent Mermaid = "graph LR\n\tA --> B" addCaption :: String -> Block -> Block addCaption caption (CodeBlock (id', cls, attrs) script) = diff --git a/tests/includes/mermaid.mermaid b/tests/includes/mermaid.mermaid new file mode 100644 index 0000000..e69de29