packages feed

ihaskell-rlangqq-0.2.0.1: IHaskell/Display/Rlangqq.hs

{-# OPTIONS_GHC -fno-warn-missing-fields #-}
{-# LANGUAGE TupleSections, TemplateHaskell #-}

module IHaskell.Display.Rlangqq (
    module RlangQQ,
    rDisp,
    rDisplayAll,
    rOutputParsed,
    rOutput,
    getPlotNames,
    getCaptions,
    ) where

import           RlangQQ
import           RlangQQ.ParseKnitted

import           System.Directory
import           System.FilePath
import           Data.Maybe
import           Data.List
import           Text.Read
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as Char
import qualified Data.ByteString.Base64 as Base64
import           IHaskell.Display
import           IHaskell.Display.Blaze () -- to confirm it's installed
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as H
import           Data.Char
import           Control.Monad
import           Data.Ord
import           Data.List.Split
import           Text.XFormat.Show hiding ((<>))
import           Control.Applicative
import           Control.Concurrent
import           Data.Monoid
import           Data.Typeable

import           Control.Concurrent.STM
import           Language.Haskell.TH.Quote

-- | same as 'RlangQQ.r', but displays plots at the end too
rDisp = QuasiQuoter { quoteExp = \s -> [|do
                                           result <- $(quoteExp r s)
                                           p <- rDisplayAll
                                           printDisplay p
                                           return result|] }

rOutput :: IO [Int]
rOutput = do
  fs <- mapMaybe (readMaybe <=< stripPrefix "raw" <=< stripSuffix ".md")
        <$> getDirectoryContents "Rtmp"
  fs' <- forM fs $ \f -> (,f) <$> getModificationTime (showf ("Rtmp/raw" % Int % ".md") f)
  return $ map snd $ sortBy (flip (comparing fst)) fs'

-- | like 'stripPrefix' except on the end
stripSuffix :: String -> String -> Maybe String
stripSuffix s x = fmap reverse $ stripPrefix (reverse s) $ reverse x

rOutputParsed :: IO [KnitInteraction]
rOutputParsed = do
  ns <- rOutput
  case ns of
    []  -> return []
    n:_ -> parseKnitted <$> readFile (showf ("Rtmp/raw" % Int % ".md") n)

getPlotNames :: IO [String]
getPlotNames = do
  interactions <- rOutputParsed
  return [p | KnitInteraction _ is <- interactions
            , KnitImage _ p <- is]

getCaptions :: IO [String]
getCaptions = do
  interactions <- rOutputParsed
  return
    [c | KnitInteraction _ is <- interactions
       , KnitImage c _ <- is
       , not (isBoringCaption c)]

-- | true when the caption name looks like one knitr will automatically generate
isBoringCaption :: String -> Bool
isBoringCaption s = maybe False (all isDigit) (stripPrefix "plot of chunk unnamed-chunk-" s)

rDisplayAll :: IO Display
rDisplayAll = do
  ns <- rOutputParsed
  imgs <- sequence [displayInteraction o | KnitInteraction _ os <- ns
                                         , o <- os]
  display (mconcat imgs)

displayInteraction :: KnitOutput -> IO Display
displayInteraction (KnitPrint c) = display (plain c)
displayInteraction (KnitWarning c) = display (plain c)
displayInteraction (KnitError c) = display (plain c)
displayInteraction (KnitAsIs c) = display (plain c)
displayInteraction (KnitImage cap img) = do
  let caption
        | isBoringCaption cap = mempty
        | otherwise = H.p (H.toMarkup cap)
  encoded <- Base64.encode <$> B.readFile img
  display $ H.img H.! H.src
                        (H.unsafeByteStringValue
                           -- assumes you use the default device which is png
                           (Char.pack "data:image/png;base64," <> encoded))
            <> caption