├── .gitignore ├── .travis.yml ├── ChangeLog.md ├── LICENSE ├── README-pre-process.md ├── README.md ├── Setup.hs ├── app └── Main.hs ├── package.yaml ├── src └── CodeBlockExecutor.hs ├── stack.yaml ├── test.md └── test └── Spec.hs /.gitignore: -------------------------------------------------------------------------------- 1 | .stack-work/ 2 | pandoc-markdown-ghci-filter.cabal 3 | stack.yaml.lock 4 | *~ 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # This is the simple Travis configuration, which is intended for use 2 | # on applications which do not require cross-platform and 3 | # multiple-GHC-version support. For more information and other 4 | # options, see: 5 | # 6 | # https://docs.haskellstack.org/en/stable/travis_ci/ 7 | # 8 | # Copy these contents into the root directory of your Github project in a file 9 | # named .travis.yml 10 | 11 | # Choose a build environment 12 | dist: xenial 13 | 14 | # Do not choose a language; we provide our own build tools. 15 | language: generic 16 | 17 | # Caching so the next build will be fast too. 18 | cache: 19 | directories: 20 | - $HOME/.stack 21 | 22 | # Ensure necessary system libraries are present 23 | addons: 24 | apt: 25 | packages: 26 | - libgmp-dev 27 | 28 | before_install: 29 | # Download and unpack the stack executable 30 | - mkdir -p ~/.local/bin 31 | - export PATH=$HOME/.local/bin:$PATH 32 | - travis_retry curl -L https://get.haskellstack.org/stable/linux-x86_64.tar.gz | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack' 33 | 34 | install: 35 | # Build dependencies 36 | - stack --no-terminal --install-ghc test --only-dependencies 37 | 38 | script: 39 | # Build the package, its tests, and its docs and run the tests 40 | - stack --no-terminal test --haddock --no-haddock-deps 41 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Changelog for learn-aeson 2 | 3 | ## Unreleased changes 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Guru Devanla 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README-pre-process.md: -------------------------------------------------------------------------------- 1 | # pandoc-markdown-ghci-filter 2 | 3 | A `Pandoc` filter that identifies code blocks(`Haskell`) in Pandoc supported formats, executes the code in GHCI and embeds the results in the returned output by updating the AST provided by `Pandoc`.Note, this library is tested with pandoc 2.7.3 and may not be compatible with older versions. 4 | 5 | # Quick Overview 6 | 7 | Often a markdown(or any `pandoc` supported document) for any `Haskell` related documentation or a technical blog post involves `code` blocks. The `code` block could include `definitions` and also a demonstration of output with an `interactive` prompt. For, example, take this `code` block: 8 | 9 | ``` {.haskell code-filter=Off} 10 | -- README.md 11 | 12 | -- definition 13 | increment:: Integer -> Integer 14 | increment x = x + 1 15 | 16 | -- interactive prompt to demonstrate the working of definitions so far 17 | 18 | >> increment 41 19 | ``` 20 | 21 | It would be nice if this `code` block was automatically evaluated and `output` of `increment 41` is automatically recorded below `>> increment 41`, as follows: 22 | 23 | ``` haskell 24 | -- README.md 25 | 26 | -- definition 27 | increment:: Integer -> Integer 28 | increment x = x + 1 29 | 30 | -- interactive prompt to demonstrate the working of definitions so far 31 | 32 | >> increment 41 33 | ``` 34 | 35 | Notice, that the `42` is automatically populated by this filter while transforming the original document. 36 | 37 | To transform the document, we need to run the document through the `pandoc` filter, as follows: 38 | 39 | ``` shell 40 | 41 | -- set up pandoc_filter to the executable of this program (see Installation) 42 | 43 | pandoc -s -t json README.md | pandoc_filter | pandoc -f json -t markdown 44 | 45 | ``` 46 | 47 | To read more about how `filter` work, visit the [this](https://pandoc.org/filters.html) page. 48 | 49 | # Installation 50 | 51 | ## Requirements 52 | 53 | - [Stack](https://docs.haskellstack.org/en/stable/README/) 54 | 55 | 56 | ### From Source 57 | ``` shell 58 | 59 | git clone https://github.com/gdevanla/pandoc-markdown-ghci-filter.git 60 | cd pandoc-markdown-ghci-filter 61 | 62 | stack build 63 | 64 | ``` 65 | 66 | ### From Stackage/Hackage 67 | 68 | ``` shell 69 | stack build pandoc-markdown-ghci-filter # executable only available to local stack environment 70 | 71 | or 72 | 73 | stack install pandoc-markdown-ghci-filter # if you want to across all stack environments 74 | 75 | ``` 76 | 77 | # Running the filter 78 | 79 | ``` shell 80 | pandoc -s -t json test.md | pandoc-markdown-ghci-filter-exe | pandoc -f json -t markdown 81 | ``` 82 | 83 | # Usage Notes/Caveats 84 | 85 | 1. All interactive statements (prefixed with `>>`) need to be preceded by `\n` to let the filter respect original new line spacing. If this is not followed, `\n` may be truncated. 86 | 2. The program internally wraps all commands inside the GHCi multi-line construct `:{..:}`. Therefore, the code segments should not have multi-line constructs as part of code blocks. 87 | 3. If you want the filter to ignore a certain `code` block, you can turn-off the filter by setting the `code` block attribute as follows 88 | 89 | 90 | ``` markdown 91 | 92 | {.haskell code_filter="Off"} 93 | 94 | -- do not run this code through GHCi 95 | 96 | >> putStrLn "This line will not be expanded by the filter" 97 | ``` 98 | 99 | Note, the default value is "On" 100 | 101 | # Limitations/Open Issues 102 | 103 | 1. Attaching different formattting properties to `output`. 104 | 2. As explained in `Usage Notes`, all `interactive` statements should be preceded by an empty line, for the filter to maintain the `\n` characters as given by the input. 105 | 106 | # More examples 107 | 108 | ## Sample Markdown Before Transformation 109 | 110 | Sample markdown as fed into filter through `pandoc`. 111 | 112 | ## Example 1 113 | 114 | ``` {.haskell code-filter=Off} 115 | import Data.Text 116 | 117 | >> putStrLn "This string should show up in the output" 118 | 119 | ``` 120 | ## Example 2 121 | 122 | ``` {.haskell code-filter=Off} 123 | addOne:: Integer -> Integer 124 | addOne x = x + 1 125 | 126 | >> addOne 13 127 | 128 | multBy2:: Integer -> Integer 129 | multBy2 x = x * 2 130 | 131 | >> (addOne 10) + (multBy2 20) 132 | ``` 133 | 134 | ## Example 3 135 | 136 | Any errors that occur while executing statements in the `code` block are also rendered. 137 | 138 | ``` {.haskell code-filter=Off} 139 | 140 | wrongFuncDefinition:: Integer -> Integer 141 | wrongFuncDefintion = x + 1 142 | 143 | >> functionNotInScope 10 144 | ``` 145 | 146 | ## Example 4 147 | 148 | Expand type definitions 149 | 150 | ``` {.haskell code-filter=Off} 151 | 152 | testDefinition :: Integer -> Integer -> Integer 153 | testDefinition x y = x + y 154 | 155 | >>:t testDefinition 156 | 157 | >>:t testDefinition 10 158 | 159 | >>:t testDefinition 10 20 160 | 161 | ``` 162 | 163 | 164 | 165 | ## Markdown after transformation 166 | 167 | ## Example 1 168 | 169 | ``` {.haskell code-filter=On} 170 | import Data.Text 171 | 172 | >> putStrLn "This string should show up in the output" 173 | 174 | ``` 175 | ## Example 2 176 | 177 | ``` {.haskell code-filter=On} 178 | addOne:: Integer -> Integer 179 | addOne x = x + 1 180 | 181 | >> addOne 13 182 | 183 | multBy2:: Integer -> Integer 184 | multBy2 x = x * 2 185 | 186 | >> (addOne 10) + (multBy2 20) 187 | ``` 188 | 189 | ## Example 3 190 | 191 | Any errors that occur while executing statements in the `code` block are also rendered. 192 | 193 | ``` {.haskell code-filter=On} 194 | 195 | wrongFuncDefinition:: Integer -> Integer 196 | wrongFuncDefintion = x + 1 197 | 198 | >> functionNotInScope 10 199 | ``` 200 | 201 | ## Example 4 202 | 203 | Expand type definitions 204 | 205 | ``` {.haskell code-filter=On} 206 | 207 | testDefinition :: Integer -> Integer -> Integer 208 | testDefinition x y = x + y 209 | 210 | >>:t testDefinition 211 | 212 | >>:t testDefinition 10 213 | 214 | >>:t testDefinition 10 20 215 | 216 | ``` 217 | 218 | 219 | *Fun Fact:* This document was generated using this same tool it describes. This [README-pre-process.md](https://github.com/gdevanla/pandoc-markdown-ghci-filter/blob/master/README-pre-process.md) was used to generate this document. Here is the command that was used: 220 | 221 | ``` shell 222 | pandoc -s -t json README-pre-process.md | stack runhaskell app/Main.hs | pandoc -f json -t markdown > README.md 223 | ``` 224 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pandoc-markdown-ghci-filter 2 | =========================== 3 | 4 | A `Pandoc` filter that identifies code blocks(`Haskell`) in Pandoc 5 | supported formats, executes the code in GHCI and embeds the results in 6 | the returned output by updating the AST provided by `Pandoc`.Note, this 7 | library is tested with pandoc 2.7.3 and may not be compatible with older 8 | versions. 9 | 10 | Quick Overview 11 | ============== 12 | 13 | Often a markdown(or any `pandoc` supported document) for any `Haskell` 14 | related documentation or a technical blog post involves `code` blocks. 15 | The `code` block could include `definitions` and also a demonstration of 16 | output with an `interactive` prompt. For, example, take this `code` 17 | block: 18 | 19 | ``` {.haskell code-filter="Off"} 20 | -- README.md 21 | 22 | -- definition 23 | increment:: Integer -> Integer 24 | increment x = x + 1 25 | 26 | -- interactive prompt to demonstrate the working of definitions so far 27 | 28 | >> increment 41 29 | ``` 30 | 31 | It would be nice if this `code` block was automatically evaluated and 32 | `output` of `increment 41` is automatically recorded below 33 | `>> increment 41`, as follows: 34 | 35 | ``` {.haskell} 36 | -- README.md 37 | 38 | -- definition 39 | increment:: Integer -> Integer 40 | increment x = x + 1 41 | 42 | -- interactive prompt to demonstrate the working of definitions so far 43 | 44 | >> increment 41 45 | 42 46 | 47 | ``` 48 | 49 | Notice, that the `42` is automatically populated by this filter while 50 | transforming the original document. 51 | 52 | To transform the document, we need to run the document through the 53 | `pandoc` filter, as follows: 54 | 55 | ``` {.shell} 56 | 57 | -- set up pandoc_filter to the executable of this program (see Installation) 58 | 59 | pandoc -s -t json README.md | pandoc_filter | pandoc -f json -t markdown 60 | ``` 61 | 62 | To read more about how `filter` work, visit the 63 | [this](https://pandoc.org/filters.html) page. 64 | 65 | Installation 66 | ============ 67 | 68 | Requirements 69 | ------------ 70 | 71 | - [Stack](https://docs.haskellstack.org/en/stable/README/) 72 | 73 | ### From Source 74 | 75 | ``` {.shell} 76 | 77 | git clone https://github.com/gdevanla/pandoc-markdown-ghci-filter.git 78 | cd pandoc-markdown-ghci-filter 79 | 80 | stack build 81 | ``` 82 | 83 | ### From Stackage/Hackage 84 | 85 | ``` {.shell} 86 | stack build pandoc-markdown-ghci-filter # executable only available to local stack environment 87 | 88 | or 89 | 90 | stack install pandoc-markdown-ghci-filter # if you want to across all stack environments 91 | ``` 92 | 93 | Running the filter 94 | ================== 95 | 96 | ``` {.shell} 97 | pandoc -s -t json test.md | pandoc-markdown-ghci-filter-exe | pandoc -f json -t markdown 98 | ``` 99 | 100 | Usage Notes/Caveats 101 | =================== 102 | 103 | 1. All interactive statements (prefixed with `>>`) need to be preceded 104 | by `\n` to let the filter respect original new line spacing. If this 105 | is not followed, `\n` may be truncated. 106 | 2. The program internally wraps all commands inside the GHCi multi-line 107 | construct `:{..:}`. Therefore, the code segments should not have 108 | multi-line constructs as part of code blocks. 109 | 3. If you want the filter to ignore a certain `code` block, you can 110 | turn-off the filter by setting the `code` block attribute as follows 111 | 112 | ``` {.markdown} 113 | 114 | {.haskell code_filter="Off"} 115 | 116 | -- do not run this code through GHCi 117 | 118 | >> putStrLn "This line will not be expanded by the filter" 119 | ``` 120 | 121 | Note, the default value is "On" 122 | 123 | Limitations/Open Issues 124 | ======================= 125 | 126 | 1. Attaching different formattting properties to `output`. 127 | 2. As explained in `Usage Notes`, all `interactive` statements should 128 | be preceded by an empty line, for the filter to maintain the `\n` 129 | characters as given by the input. 130 | 131 | More examples 132 | ============= 133 | 134 | Sample Markdown Before Transformation 135 | ------------------------------------- 136 | 137 | Sample markdown as fed into filter through `pandoc`. 138 | 139 | Example 1 140 | --------- 141 | 142 | ``` {.haskell code-filter="Off"} 143 | import Data.Text 144 | 145 | >> putStrLn "This string should show up in the output" 146 | ``` 147 | 148 | Example 2 149 | --------- 150 | 151 | ``` {.haskell code-filter="Off"} 152 | addOne:: Integer -> Integer 153 | addOne x = x + 1 154 | 155 | >> addOne 13 156 | 157 | multBy2:: Integer -> Integer 158 | multBy2 x = x * 2 159 | 160 | >> (addOne 10) + (multBy2 20) 161 | ``` 162 | 163 | Example 3 164 | --------- 165 | 166 | Any errors that occur while executing statements in the `code` block are 167 | also rendered. 168 | 169 | ``` {.haskell code-filter="Off"} 170 | 171 | wrongFuncDefinition:: Integer -> Integer 172 | wrongFuncDefintion = x + 1 173 | 174 | >> functionNotInScope 10 175 | ``` 176 | 177 | Example 4 178 | --------- 179 | 180 | Expand type definitions 181 | 182 | ``` {.haskell code-filter="Off"} 183 | 184 | testDefinition :: Integer -> Integer -> Integer 185 | testDefinition x y = x + y 186 | 187 | >>:t testDefinition 188 | 189 | >>:t testDefinition 10 190 | 191 | >>:t testDefinition 10 20 192 | ``` 193 | 194 | Markdown after transformation 195 | ----------------------------- 196 | 197 | Example 1 198 | --------- 199 | 200 | ``` {.haskell code-filter="On"} 201 | import Data.Text 202 | 203 | >> putStrLn "This string should show up in the output" 204 | This string should show up in the output 205 | 206 | ``` 207 | 208 | Example 2 209 | --------- 210 | 211 | ``` {.haskell code-filter="On"} 212 | addOne:: Integer -> Integer 213 | addOne x = x + 1 214 | 215 | >> addOne 13 216 | 14 217 | 218 | multBy2:: Integer -> Integer 219 | multBy2 x = x * 2 220 | 221 | >> (addOne 10) + (multBy2 20) 222 | 51 223 | 224 | ``` 225 | 226 | Example 3 227 | --------- 228 | 229 | Any errors that occur while executing statements in the `code` block are 230 | also rendered. 231 | 232 | ``` {.haskell code-filter="On"} 233 | 234 | wrongFuncDefinition:: Integer -> Integer 235 | wrongFuncDefintion = x + 1 236 | 237 | :16:1: error: 238 | The type signature for ‘wrongFuncDefinition’ 239 | lacks an accompanying binding 240 | 241 | >> functionNotInScope 10 242 | :29:2: error: 243 | Variable not in scope: functionNotInScope :: Integer -> t 244 | 245 | ``` 246 | 247 | Example 4 248 | --------- 249 | 250 | Expand type definitions 251 | 252 | ``` {.haskell code-filter="On"} 253 | 254 | testDefinition :: Integer -> Integer -> Integer 255 | testDefinition x y = x + y 256 | 257 | >>:t testDefinition 258 | testDefinition :: Integer -> Integer -> Integer 259 | 260 | >>:t testDefinition 10 261 | testDefinition 10 :: Integer -> Integer 262 | 263 | >>:t testDefinition 10 20 264 | testDefinition 10 20 :: Integer 265 | 266 | ``` 267 | 268 | *Fun Fact:* This document was generated using this same tool it 269 | describes. This 270 | [README-pre-process.md](https://github.com/gdevanla/pandoc-markdown-ghci-filter/blob/master/README-pre-process.md) 271 | was used to generate this document. Here is the command that was used: 272 | 273 | ``` {.shell} 274 | pandoc -s -t json README-pre-process.md | stack runhaskell app/Main.hs | pandoc -f json -t markdown > README.md 275 | ``` 276 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | module Main where 2 | 3 | import Text.Pandoc.JSON 4 | import CodeBlockExecutor 5 | 6 | -- test c@(CodeBlock (identifier, classes, key_value) str) = do 7 | -- putStrLn $ show key_value 8 | -- putStrLn $ show classes 9 | -- return c 10 | -- test b = return b 11 | 12 | -- main :: IO () 13 | -- main = toJSONFilter test -- applyFilterToBlock 14 | 15 | main :: IO () 16 | main = toJSONFilter applyFilterToBlock 17 | -------------------------------------------------------------------------------- /package.yaml: -------------------------------------------------------------------------------- 1 | name: pandoc-markdown-ghci-filter 2 | version: 0.1.0.0 3 | github: "gdevanla/pandoc-markdown-ghci-filter" 4 | license: MIT 5 | author: "Guru Devanla" 6 | maintainer: "gdrvnl@gmail.com" 7 | copyright: "2019 Guru Devanla" 8 | 9 | extra-source-files: 10 | - README.md 11 | - ChangeLog.md 12 | 13 | # Metadata used when publishing your package 14 | synopsis: Pandoc-filter to evaluate `code` section in markdown and auto-embed output. 15 | category: program, text, documentation, filter,mit 16 | 17 | # To avoid duplicated efforts in documentation and dealing with the 18 | # complications of embedding Haddock markup inside cabal files, it is 19 | # common to point users to the README.md file. 20 | description: Please see the README on GitHub at 21 | 22 | dependencies: 23 | - base >= 4.7 && < 5 24 | - text 25 | - pandoc-types 26 | - ghcid 27 | - containers 28 | 29 | default-extensions: 30 | - DeriveGeneric 31 | - OverloadedStrings 32 | 33 | library: 34 | source-dirs: src 35 | 36 | executables: 37 | pandoc-markdown-ghci-filter-exe: 38 | main: Main.hs 39 | source-dirs: app 40 | ghc-options: 41 | - -threaded 42 | - -rtsopts 43 | - -with-rtsopts=-N 44 | - -Wall 45 | - -Wcompat 46 | - -Wincomplete-record-updates 47 | - -Wincomplete-uni-patterns 48 | - -Wredundant-constraints 49 | dependencies: 50 | - pandoc-markdown-ghci-filter 51 | 52 | tests: 53 | pandoc-markdown-ghci-filter-test: 54 | main: Spec.hs 55 | source-dirs: test 56 | ghc-options: 57 | - -threaded 58 | - -rtsopts 59 | - -with-rtsopts=-N 60 | dependencies: 61 | - pandoc-markdown-ghci-filter 62 | - tasty-hunit 63 | - QuickCheck 64 | - tasty 65 | - tasty-quickcheck 66 | -------------------------------------------------------------------------------- /src/CodeBlockExecutor.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | -- | Module that communicates with GHCid, splits the commands in the code block, 4 | -- gather the results and emits the trasnformed JSON based Pandoc AST. 5 | 6 | 7 | module CodeBlockExecutor 8 | ( 9 | applyFilterToBlock, 10 | runCodeBlock, 11 | processResults 12 | ) where 13 | 14 | 15 | --import GHC.Generics 16 | import Text.Pandoc.Definition 17 | import Language.Haskell.Ghcid 18 | import Control.Applicative 19 | import Control.Exception 20 | import Data.String 21 | import Data.List as L 22 | import Data.Map.Strict as M 23 | import Data.Maybe 24 | 25 | import qualified Data.Text as T 26 | 27 | removeAll:: T.Text -> T.Text -> T.Text 28 | removeAll pat str = if (T.replace pat "" str) == str then str 29 | else removeAll pat (T.replace pat "" str) 30 | 31 | -- | Determines if the command encountered is an `interactive` command. 32 | -- This is one of the conditions that determines whether output needs to be captured. 33 | isInteractive :: T.Text -> Bool 34 | isInteractive cmd = T.isPrefixOf ">>" cmd 35 | 36 | -- | This function takes care of placing back the newline characters 37 | -- that may been stripped out before sending the commands to GHCid 38 | updateSuffixForInteractiveCmd :: Data.String.IsString p => T.Text -> p 39 | updateSuffixForInteractiveCmd cmd = if isInteractive cmd then 40 | if T.last cmd == '\n' then "" else "\n" 41 | else "\n\n" 42 | 43 | -- | Intercalate the commands and respective results. Typically, only errors 44 | -- encountered while running definitions, and outut of interactive commands ( 45 | -- i.e commands prefixed with `>>`) are captured. All empty strins are dropped. 46 | intercalateCmdAndResults :: T.Text -> T.Text -> T.Text 47 | intercalateCmdAndResults cmd result = 48 | T.concat [cmd, updateSuffixForInteractiveCmd cmd, result, trailResult result] where 49 | trailResult r = if r /= "" then "\n" else "" 50 | 51 | -- | Post-processing function that interleaves command and results 52 | processResults :: [T.Text] -- ^ List of commands that were executed 53 | -> [T.Text] -- ^ List of results for the executed commands 54 | -> String -- ^ New string that will form the bodyof the modified Code Block. 55 | processResults cmds results = 56 | let cmd_result = getZipList $ intercalateCmdAndResults <$> ZipList cmds <*> ZipList results 57 | in 58 | (T.unpack . T.concat) $ cmd_result 59 | 60 | -- | Apply the filter block only if the language attribute 61 | -- is set to `haskell` and `code-filter` property is *not* set to "Off" 62 | -- 63 | -- Example: 64 | -- ``` {.haskell code-filter="Off"} ``` will turn off any kind of transformation. 65 | -- 66 | -- By default the filer is "On" 67 | applyFilterToBlock:: Block -- ^ The 'Block' yielded by toJSONFilter in "Text.Pandoc.JSON" 68 | -> IO Block -- ^ The newly minted 'Block' 69 | applyFilterToBlock c@(CodeBlock (_, classes, key_values) _) = let 70 | attrs = M.fromList key_values 71 | haskell_in_class = L.find (== "haskell") classes 72 | code_filter_flag = maybe "On" id (M.lookup ("code-filter") attrs) 73 | in 74 | if code_filter_flag == "On" && isJust haskell_in_class then runCodeBlock c 75 | else (return c) 76 | applyFilterToBlock b = return b 77 | 78 | -- | Run the commands in the 'Block' in one single GHCid session. 79 | runCodeBlock:: Block -- ^ 'Block' to execute. Only 'CodeBlock' is executed. 80 | -> IO Block -- ^ transformed code block 81 | runCodeBlock (CodeBlock attr str) = bracket startGhciProcess' stopGhci runCommands 82 | where 83 | startGhciProcess' = do 84 | (ghci_handle, _) <- startGhci "stack ghci" (Just ".") (\_ _ -> return ()) 85 | return ghci_handle 86 | runCommands g = do 87 | let cmds = L.filter (\s -> s /= "") $ T.splitOn "\n\n" $ T.pack str 88 | results <- mapM (runCmd g) cmds 89 | let results''' = processResults cmds results 90 | return (CodeBlock attr results''') 91 | runCodeBlock b = return b 92 | 93 | -- | Executes a command in GHCIid. 94 | runCmd :: Ghci -- ^ Handle to the GHCi process through the GHCid interface 95 | -> T.Text -- ^ Statement to execute 96 | -> IO T.Text -- ^ Result of the executed statement 97 | runCmd g cmd = do 98 | let executeStatement = exec g 99 | cmd_ = if T.isPrefixOf (T.pack "import") cmd then T.concat ["\n", cmd, "\n"] 100 | else T.concat [":{\n", T.replace ">>" "" cmd, "\n:}\n"] 101 | result <- executeStatement . T.unpack $ cmd_ 102 | -- we send this PROBE here since GHCi has its own mind on how it prefixes output based on its native needs. By sending the probe we can guess what is the latest prompt and then discard it while processing thye output. 103 | probe <- exec g ":{\nshow (\"PANDOC_FILTER_PROBE_PROMPT_INTERNAL\"::String)\n:}\n" 104 | let current_prompt = preparePrompt probe 105 | where 106 | preparePrompt probe' = 107 | let prompt = T.replace " \"\\\"PANDOC_FILTER_PROBE_PROMPT_INTERNAL\\\"\"\n" "" (T.pack . unlines $ probe') 108 | in 109 | T.concat [T.takeWhile (/='|') prompt, "|"] 110 | --putStrLn $ show . unlines $ probe 111 | --putStrLn $ show current_prompt 112 | result' = T.stripStart $ removeAll current_prompt (T.pack . unlines $ result) 113 | --putStrLn $ show result' 114 | return $ result' 115 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | # This file was automatically generated by 'stack init' 2 | # 3 | # Some commonly used options have been documented as comments in this file. 4 | # For advanced use and comprehensive documentation of the format, please see: 5 | # https://docs.haskellstack.org/en/stable/yaml_configuration/ 6 | 7 | # Resolver to choose a 'specific' stackage snapshot or a compiler version. 8 | # A snapshot resolver dictates the compiler version and the set of packages 9 | # to be used for project dependencies. For example: 10 | # 11 | # resolver: lts-3.5 12 | # resolver: nightly-2015-09-21 13 | # resolver: ghc-7.10.2 14 | # 15 | # The location of a snapshot can be provided as a file or url. Stack assumes 16 | # a snapshot provided as a file might change, whereas a url resource does not. 17 | # 18 | # resolver: ./custom-snapshot.yaml 19 | # resolver: https://example.com/snapshots/2018-01-01.yaml 20 | resolver: lts-13.26 21 | 22 | # User packages to be built. 23 | # Various formats can be used as shown in the example below. 24 | # 25 | # packages: 26 | # - some-directory 27 | # - https://example.com/foo/bar/baz-0.0.2.tar.gz 28 | # subdirs: 29 | # - auto-update 30 | # - wai 31 | packages: 32 | - . 33 | # Dependency packages to be pulled from upstream that are not in the resolver. 34 | # These entries can reference officially published versions as well as 35 | # forks / in-progress versions pinned to a git hash. For example: 36 | # 37 | # extra-deps: 38 | # - acme-missiles-0.3 39 | # - git: https://github.com/commercialhaskell/stack.git 40 | # commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a 41 | # 42 | # extra-deps: [] 43 | 44 | # Override default flag values for local packages and extra-deps 45 | # flags: {} 46 | 47 | # Extra package databases containing global packages 48 | # extra-package-dbs: [] 49 | 50 | # Control whether we use the GHC we find on the path 51 | # system-ghc: true 52 | # 53 | # Require a specific version of stack, using version ranges 54 | # require-stack-version: -any # Default 55 | # require-stack-version: ">=2.1" 56 | # 57 | # Override the architecture used by stack, especially useful on Windows 58 | # arch: i386 59 | # arch: x86_64 60 | # 61 | # Extra directories used by stack for building 62 | # extra-include-dirs: [/path/to/dir] 63 | # extra-lib-dirs: [/path/to/dir] 64 | # 65 | # Allow a newer minor version of GHC than the snapshot specifies 66 | # compiler-check: newer-minor 67 | 68 | allow-newer: true 69 | -------------------------------------------------------------------------------- /test.md: -------------------------------------------------------------------------------- 1 | ## Sample Markdown Before Transformation 2 | 3 | Sample markdown as fed into filter through `pandoc`. 4 | 5 | ## Example 1 6 | 7 | ``` {.haskell code-filter=Off} 8 | import Data.Text 9 | 10 | >> putStrLn "This string should show up in the output" 11 | 12 | ``` 13 | ## Example 2 14 | 15 | ``` {.haskell code-filter=Off} 16 | addOne:: Integer -> Integer 17 | addOne x = x + 1 18 | 19 | >> addOne 13 20 | 21 | multBy2:: Integer -> Integer 22 | multBy = x * 2 23 | 24 | >> (addOne 10) + (multBy2 20) 25 | ``` 26 | 27 | ## Markdown after transformation 28 | 29 | ``` {.haskell code-filter=On} 30 | import Data.Text 31 | 32 | >> putStrLn "This string should show up in the output" 33 | 34 | ``` 35 | ## Example 3 36 | 37 | ``` {.haskell code-filter=On} 38 | addOne:: Integer -> Integer 39 | addOne x = x + 1 40 | 41 | >> addOne 13 42 | 43 | multBy2:: Integer -> Integer 44 | multBy = x * 2 45 | 46 | >> (addOne 10) + (multBy2 20) 47 | ``` 48 | 49 | 50 | ``` 51 | ## Example 3 52 | 53 | ``` {.haskell code-filter=On} 54 | import Data.Text as T 55 | import Control.Monad 56 | 57 | addOne:: Integer -> Integer 58 | addOne x = x + 1 59 | 60 | >> addOne 13 61 | 62 | multBy2:: Integer -> Integer 63 | multBy = x * 2 64 | 65 | >> (addOne 10) + (multBy2 20) 66 | ``` 67 | 68 | ## Example 4 69 | 70 | ``` {.haskell reset-session=True} 71 | addOne:: Integer -> Integer 72 | addOne x = x + 1 73 | 74 | >> addOne 13 75 | 76 | multBy2:: Integer -> Integer 77 | multBy = x * 2 78 | 79 | >> (addOne 10) + (multBy2 20) 80 | ``` 81 | -------------------------------------------------------------------------------- /test/Spec.hs: -------------------------------------------------------------------------------- 1 | {-# OPTIONS_GHC -fno-warn-type-defaults #-} 2 | import Test.Tasty.HUnit 3 | import Test.Tasty 4 | 5 | import Data.Text as T 6 | import CodeBlockExecutor 7 | import Control.Applicative 8 | 9 | 10 | input1 :: [Text] 11 | input1 = [">> putStrLn \"This string should show up in the output\""] 12 | ghci_result1 :: [Text] 13 | ghci_result1 = ["This string should show up in the output"] 14 | combined_result1 :: String 15 | combined_result1 = T.unpack . T.unlines $ 16 | [ 17 | ">> putStrLn \"This string should show up in the output\"", 18 | "This string should show up in the output" 19 | ] 20 | 21 | input2 :: [Text] 22 | input2 = fmap T.pack [ 23 | "testFunc:: Integer -> Integer\ntestFunc x = x + 1", 24 | ">> testFunc 13\n", 25 | "anotherFunc:: Integer -> Integer\nanotherFunc x = x * 2" 26 | ] 27 | 28 | ghci_result2 :: [Text] 29 | ghci_result2 = fmap T.pack ["", "14", ""] 30 | combined_result2 :: String 31 | combined_result2 = T.unpack . T.unlines $ 32 | ["testFunc:: Integer -> Integer", 33 | "testFunc x = x + 1", 34 | "", 35 | ">> testFunc 13", 36 | "14", 37 | "anotherFunc:: Integer -> Integer", 38 | "anotherFunc x = x * 2", 39 | ""] 40 | 41 | fixtures :: [([Text], [Text], String)] 42 | fixtures = [ 43 | (input1, ghci_result1, combined_result1), 44 | (input2, ghci_result2, combined_result2)] 45 | 46 | generateTests :: [TestTree] 47 | generateTests = getZipList $ testProcessResults <$> ZipList fixtures <*> ZipList [1..] 48 | 49 | testProcessResults :: Show a => ([Text], [Text], String) -> a -> TestTree 50 | testProcessResults (input, ghci_results, expected) i = testCase ("test_processResults" ++ (show i)) $ do 51 | let actual = processResults input ghci_results 52 | assertEqual "processResult" expected actual 53 | 54 | -- test_processResults = testCase "test_processResults" ( 55 | -- assertEqual "input1" combined_result (processResults [input1] [ghci_result])) 56 | 57 | -- test_processResults2 = testCase "test_processResults" ( 58 | -- assertEqual "input1" combined_result (processResults [input1] [ghci_result])) 59 | 60 | tests:: TestTree 61 | tests = testGroup "Tests" generateTests 62 | 63 | main :: IO () 64 | main = defaultMain tests 65 | 66 | --runTestTT $ TestList [TestLabel "test1" test_processResults] 67 | --------------------------------------------------------------------------------