-- | Read a presentation from disk.
{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
module Patat.Presentation.Read
    ( readPresentation

      -- Exposed for testing mostly.
    , readMetaSettings
    ) where


--------------------------------------------------------------------------------
import           Control.Monad.Except           (ExceptT (..), runExceptT,
                                                 throwError)
import           Control.Monad.Trans            (liftIO)
import qualified Data.Aeson                     as A
import qualified Data.Aeson.KeyMap              as AKM
import           Data.Bifunctor                 (first)
import           Data.Maybe                     (fromMaybe)
import qualified Data.Text                      as T
import qualified Data.Text.Encoding             as T
import qualified Data.Text.IO                   as T
import qualified Data.Yaml                      as Yaml
import           Patat.Eval                     (eval)
import           Patat.Presentation.Fragment
import qualified Patat.Presentation.Instruction as Instruction
import           Patat.Presentation.Internal
import           Prelude
import           System.Directory               (doesFileExist,
                                                 getHomeDirectory)
import           System.FilePath                (splitFileName, takeExtension,
                                                 (</>))
import qualified Text.Pandoc.Error              as Pandoc
import qualified Text.Pandoc.Extended           as Pandoc


--------------------------------------------------------------------------------
readPresentation :: FilePath -> IO (Either String Presentation)
readPresentation :: String -> IO (Either String Presentation)
readPresentation String
filePath = forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT forall a b. (a -> b) -> a -> b
$ do
    -- We need to read the settings first.
    Text
src          <- forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ String -> IO Text
T.readFile String
filePath
    PresentationSettings
homeSettings <- forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT IO (Either String PresentationSettings)
readHomeSettings
    PresentationSettings
metaSettings <- forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ Text -> Either String PresentationSettings
readMetaSettings Text
src
    let settings :: PresentationSettings
settings = PresentationSettings
metaSettings forall a. Semigroup a => a -> a -> a
<> PresentationSettings
homeSettings forall a. Semigroup a => a -> a -> a
<> PresentationSettings
defaultPresentationSettings

    let pexts :: ExtensionList
pexts = forall a. a -> Maybe a -> a
fromMaybe ExtensionList
defaultExtensionList (PresentationSettings -> Maybe ExtensionList
psPandocExtensions PresentationSettings
settings)
    Text -> Either PandocError Pandoc
reader <- case ExtensionList
-> String -> Maybe (Text -> Either PandocError Pandoc)
readExtension ExtensionList
pexts String
ext of
        Maybe (Text -> Either PandocError Pandoc)
Nothing -> forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError forall a b. (a -> b) -> a -> b
$ String
"Unknown file extension: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show String
ext
        Just Text -> Either PandocError Pandoc
x  -> forall (m :: * -> *) a. Monad m => a -> m a
return Text -> Either PandocError Pandoc
x
    Pandoc
doc <- case Text -> Either PandocError Pandoc
reader Text
src of
        Left  PandocError
e -> forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError forall a b. (a -> b) -> a -> b
$ String
"Could not parse document: " forall a. [a] -> [a] -> [a]
++ forall a. Show a => a -> String
show PandocError
e
        Right Pandoc
x -> forall (m :: * -> *) a. Monad m => a -> m a
return Pandoc
x

    Presentation
pres <- forall e (m :: * -> *) a. m (Either e a) -> ExceptT e m a
ExceptT forall a b. (a -> b) -> a -> b
$ forall (f :: * -> *) a. Applicative f => a -> f a
pure forall a b. (a -> b) -> a -> b
$ String
-> PresentationSettings -> Pandoc -> Either String Presentation
pandocToPresentation String
filePath PresentationSettings
settings Pandoc
doc
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO forall a b. (a -> b) -> a -> b
$ Presentation -> IO Presentation
eval Presentation
pres
  where
    ext :: String
ext = String -> String
takeExtension String
filePath


--------------------------------------------------------------------------------
readExtension
    :: ExtensionList -> String
    -> Maybe (T.Text -> Either Pandoc.PandocError Pandoc.Pandoc)
readExtension :: ExtensionList
-> String -> Maybe (Text -> Either PandocError Pandoc)
readExtension (ExtensionList Extensions
extensions) String
fileExt = case String
fileExt of
    String
".markdown"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".md"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".mdown"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".mdtext"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".mdtxt"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".mdwn"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".mkd"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".mkdn"  -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".lhs" -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
lhsOpts
    String
""     -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readMarkdown ReaderOptions
readerOpts
    String
".org" -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ forall a. PandocPure a -> Either PandocError a
Pandoc.runPure forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (m :: * -> *) a.
(PandocMonad m, ToSources a) =>
ReaderOptions -> a -> m Pandoc
Pandoc.readOrg      ReaderOptions
readerOpts
    String
_      -> forall a. Maybe a
Nothing

  where
    readerOpts :: ReaderOptions
readerOpts = forall a. Default a => a
Pandoc.def
        { readerExtensions :: Extensions
Pandoc.readerExtensions =
            Extensions
extensions forall a. Semigroup a => a -> a -> a
<> Extensions
absolutelyRequiredExtensions
        }

    lhsOpts :: ReaderOptions
lhsOpts = ReaderOptions
readerOpts
        { readerExtensions :: Extensions
Pandoc.readerExtensions =
            ReaderOptions -> Extensions
Pandoc.readerExtensions ReaderOptions
readerOpts forall a. Semigroup a => a -> a -> a
<>
            [Extension] -> Extensions
Pandoc.extensionsFromList [Extension
Pandoc.Ext_literate_haskell]
        }

    absolutelyRequiredExtensions :: Extensions
absolutelyRequiredExtensions =
        [Extension] -> Extensions
Pandoc.extensionsFromList [Extension
Pandoc.Ext_yaml_metadata_block]


--------------------------------------------------------------------------------
pandocToPresentation
    :: FilePath -> PresentationSettings -> Pandoc.Pandoc
    -> Either String Presentation
pandocToPresentation :: String
-> PresentationSettings -> Pandoc -> Either String Presentation
pandocToPresentation String
pFilePath PresentationSettings
pSettings pandoc :: Pandoc
pandoc@(Pandoc.Pandoc Meta
meta [Block]
_) = do
    let !pTitle :: [Inline]
pTitle          = case Meta -> [Inline]
Pandoc.docTitle Meta
meta of
            []    -> [Text -> Inline
Pandoc.Str forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> b
snd forall a b. (a -> b) -> a -> b
$ String -> (String, String)
splitFileName String
pFilePath]
            [Inline]
title -> [Inline]
title
        !pSlides :: [Slide]
pSlides         = PresentationSettings -> Pandoc -> [Slide]
pandocToSlides PresentationSettings
pSettings Pandoc
pandoc
        !pBreadcrumbs :: [Breadcrumbs]
pBreadcrumbs    = [Slide] -> [Breadcrumbs]
collectBreadcrumbs [Slide]
pSlides
        !pActiveFragment :: (Int, Int)
pActiveFragment = (Int
0, Int
0)
        !pAuthor :: [Inline]
pAuthor         = forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat (Meta -> [[Inline]]
Pandoc.docAuthors Meta
meta)
    forall (m :: * -> *) a. Monad m => a -> m a
return Presentation {String
[Breadcrumbs]
[Inline]
[Slide]
(Int, Int)
PresentationSettings
pActiveFragment :: (Int, Int)
pBreadcrumbs :: [Breadcrumbs]
pSlides :: [Slide]
pSettings :: PresentationSettings
pAuthor :: [Inline]
pTitle :: [Inline]
pFilePath :: String
pAuthor :: [Inline]
pActiveFragment :: (Int, Int)
pBreadcrumbs :: [Breadcrumbs]
pSlides :: [Slide]
pTitle :: [Inline]
pSettings :: PresentationSettings
pFilePath :: String
..}


--------------------------------------------------------------------------------
-- | This re-parses the pandoc metadata block using the YAML library.  This
-- avoids the problems caused by pandoc involving rendering Markdown.  This
-- should only be used for settings though, not things like title / authors
-- since those /can/ contain markdown.
parseMetadataBlock :: T.Text -> Maybe (Either String A.Value)
parseMetadataBlock :: Text -> Maybe (Either String Value)
parseMetadataBlock Text
src = case Text -> [Text]
T.lines Text
src of
    (Text
"---" : [Text]
ls) -> case forall a. (a -> Bool) -> [a] -> ([a], [a])
break (forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Text
"---", Text
"..."]) [Text]
ls of
        ([Text]
_,     [])      -> forall a. Maybe a
Nothing
        ([Text]
block, (Text
_ : [Text]
_)) -> forall a. a -> Maybe a
Just forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ParseException -> String
Yaml.prettyPrintParseException forall b c a. (b -> c) -> (a -> b) -> a -> c
.
            forall a. FromJSON a => ByteString -> Either ParseException a
Yaml.decodeEither' forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> ByteString
T.encodeUtf8 forall b c a. (b -> c) -> (a -> b) -> a -> c
. [Text] -> Text
T.unlines forall a b. (a -> b) -> a -> b
$! [Text]
block
    [Text]
_            -> forall a. Maybe a
Nothing


--------------------------------------------------------------------------------
-- | Read settings from the metadata block in the Pandoc document.
readMetaSettings :: T.Text -> Either String PresentationSettings
readMetaSettings :: Text -> Either String PresentationSettings
readMetaSettings Text
src = case Text -> Maybe (Either String Value)
parseMetadataBlock Text
src of
    Maybe (Either String Value)
Nothing -> forall a b. b -> Either a b
Right forall a. Monoid a => a
mempty
    Just (Left String
err) -> forall a b. a -> Either a b
Left String
err
    Just (Right (A.Object Object
obj)) | Just Value
val <- forall v. Key -> KeyMap v -> Maybe v
AKM.lookup Key
"patat" Object
obj ->
       forall a. Result a -> Either String a
resultToEither forall a b. (a -> b) -> a -> b
$! forall a. FromJSON a => Value -> Result a
A.fromJSON Value
val
    Just (Right Value
_) -> forall a b. b -> Either a b
Right forall a. Monoid a => a
mempty
  where
    resultToEither :: A.Result a -> Either String a
    resultToEither :: forall a. Result a -> Either String a
resultToEither (A.Success a
x) = forall a b. b -> Either a b
Right a
x
    resultToEither (A.Error   String
e) = forall a b. a -> Either a b
Left forall a b. (a -> b) -> a -> b
$!
        String
"Error parsing patat settings from metadata: " forall a. [a] -> [a] -> [a]
++ String
e


--------------------------------------------------------------------------------
-- | Read settings from "$HOME/.patat.yaml".
readHomeSettings :: IO (Either String PresentationSettings)
readHomeSettings :: IO (Either String PresentationSettings)
readHomeSettings = do
    String
home <- IO String
getHomeDirectory
    let path :: String
path = String
home String -> String -> String
</> String
".patat.yaml"
    Bool
exists <- String -> IO Bool
doesFileExist String
path
    if Bool -> Bool
not Bool
exists
        then forall (m :: * -> *) a. Monad m => a -> m a
return (forall a b. b -> Either a b
Right forall a. Monoid a => a
mempty)
        else do
            Either ParseException PresentationSettings
errOrPs <- forall a. FromJSON a => String -> IO (Either ParseException a)
Yaml.decodeFileEither String
path
            forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! case Either ParseException PresentationSettings
errOrPs of
                Left  ParseException
err -> forall a b. a -> Either a b
Left (forall a. Show a => a -> String
show ParseException
err)
                Right PresentationSettings
ps  -> forall a b. b -> Either a b
Right PresentationSettings
ps


--------------------------------------------------------------------------------
pandocToSlides :: PresentationSettings -> Pandoc.Pandoc -> [Slide]
pandocToSlides :: PresentationSettings -> Pandoc -> [Slide]
pandocToSlides PresentationSettings
settings Pandoc
pandoc =
    let slideLevel :: Int
slideLevel   = forall a. a -> Maybe a -> a
fromMaybe (Pandoc -> Int
detectSlideLevel Pandoc
pandoc) (PresentationSettings -> Maybe Int
psSlideLevel PresentationSettings
settings)
        unfragmented :: [Slide]
unfragmented = Int -> Pandoc -> [Slide]
splitSlides Int
slideLevel Pandoc
pandoc
        fragmented :: [Slide]
fragmented   =
            [ case Slide
slide of
                TitleSlide   Int
_ [Inline]
_        -> Slide
slide
                ContentSlide Instructions Block
instrs0 -> Instructions Block -> Slide
ContentSlide forall a b. (a -> b) -> a -> b
$
                    FragmentSettings -> Instructions Block -> Instructions Block
fragmentInstructions FragmentSettings
fragmentSettings Instructions Block
instrs0
            | Slide
slide <- [Slide]
unfragmented
            ] in
    [Slide]
fragmented
  where
    fragmentSettings :: FragmentSettings
fragmentSettings = FragmentSettings
        { fsIncrementalLists :: Bool
fsIncrementalLists = forall a. a -> Maybe a -> a
fromMaybe Bool
False (PresentationSettings -> Maybe Bool
psIncrementalLists PresentationSettings
settings)
        }


--------------------------------------------------------------------------------
-- | Find level of header that starts slides.  This is defined as the least
-- header that occurs before a non-header in the blocks.
detectSlideLevel :: Pandoc.Pandoc -> Int
detectSlideLevel :: Pandoc -> Int
detectSlideLevel (Pandoc.Pandoc Meta
_meta [Block]
blocks0) =
    Int -> [Block] -> Int
go Int
6 [Block]
blocks0
  where
    go :: Int -> [Block] -> Int
go Int
level (Pandoc.Header Int
n Attr
_ [Inline]
_ : Block
x : [Block]
xs)
        | Int
n forall a. Ord a => a -> a -> Bool
< Int
level Bool -> Bool -> Bool
&& Block -> Bool
nonHeader Block
x = Int -> [Block] -> Int
go Int
n [Block]
xs
        | Bool
otherwise                = Int -> [Block] -> Int
go Int
level (Block
xforall a. a -> [a] -> [a]
:[Block]
xs)
    go Int
level (Block
_ : [Block]
xs)              = Int -> [Block] -> Int
go Int
level [Block]
xs
    go Int
level []                    = Int
level

    nonHeader :: Block -> Bool
nonHeader (Pandoc.Header Int
_ Attr
_ [Inline]
_) = Bool
False
    nonHeader Block
_                     = Bool
True


--------------------------------------------------------------------------------
-- | Split a pandoc document into slides.  If the document contains horizonal
-- rules, we use those as slide delimiters.  If there are no horizontal rules,
-- we split using headers, determined by the slide level (see
-- 'detectSlideLevel').
splitSlides :: Int -> Pandoc.Pandoc -> [Slide]
splitSlides :: Int -> Pandoc -> [Slide]
splitSlides Int
slideLevel (Pandoc.Pandoc Meta
_meta [Block]
blocks0)
    | forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (forall a. Eq a => a -> a -> Bool
== Block
Pandoc.HorizontalRule) [Block]
blocks0 = [Block] -> [Slide]
splitAtRules   [Block]
blocks0
    | Bool
otherwise                              = [Block] -> [Block] -> [Slide]
splitAtHeaders [] [Block]
blocks0
  where
    mkContentSlide :: [Pandoc.Block] -> [Slide]
    mkContentSlide :: [Block] -> [Slide]
mkContentSlide [] = []  -- Never create empty slides
    mkContentSlide [Block]
bs =
        [Instructions Block -> Slide
ContentSlide forall a b. (a -> b) -> a -> b
$ forall a. [Instruction a] -> Instructions a
Instruction.fromList [forall a. [a] -> Instruction a
Instruction.Append [Block]
bs]]

    splitAtRules :: [Block] -> [Slide]
splitAtRules [Block]
blocks = case forall a. (a -> Bool) -> [a] -> ([a], [a])
break (forall a. Eq a => a -> a -> Bool
== Block
Pandoc.HorizontalRule) [Block]
blocks of
        ([Block]
xs, [])           -> [Block] -> [Slide]
mkContentSlide [Block]
xs
        ([Block]
xs, (Block
_rule : [Block]
ys)) -> [Block] -> [Slide]
mkContentSlide [Block]
xs forall a. [a] -> [a] -> [a]
++ [Block] -> [Slide]
splitAtRules [Block]
ys

    splitAtHeaders :: [Block] -> [Block] -> [Slide]
splitAtHeaders [Block]
acc [] =
        [Block] -> [Slide]
mkContentSlide (forall a. [a] -> [a]
reverse [Block]
acc)
    splitAtHeaders [Block]
acc (b :: Block
b@(Pandoc.Header Int
i Attr
_ [Inline]
txt) : [Block]
bs)
        | Int
i forall a. Ord a => a -> a -> Bool
> Int
slideLevel  = [Block] -> [Block] -> [Slide]
splitAtHeaders (Block
b forall a. a -> [a] -> [a]
: [Block]
acc) [Block]
bs
        | Int
i forall a. Eq a => a -> a -> Bool
== Int
slideLevel =
            [Block] -> [Slide]
mkContentSlide (forall a. [a] -> [a]
reverse [Block]
acc) forall a. [a] -> [a] -> [a]
++ [Block] -> [Block] -> [Slide]
splitAtHeaders [Block
b] [Block]
bs
        | Bool
otherwise       =
            [Block] -> [Slide]
mkContentSlide (forall a. [a] -> [a]
reverse [Block]
acc) forall a. [a] -> [a] -> [a]
++ [Int -> [Inline] -> Slide
TitleSlide Int
i [Inline]
txt] forall a. [a] -> [a] -> [a]
++
            [Block] -> [Block] -> [Slide]
splitAtHeaders [] [Block]
bs
    splitAtHeaders [Block]
acc (Block
b : [Block]
bs) =
        [Block] -> [Block] -> [Slide]
splitAtHeaders (Block
b forall a. a -> [a] -> [a]
: [Block]
acc) [Block]
bs

collectBreadcrumbs :: [Slide] -> [Breadcrumbs]
collectBreadcrumbs :: [Slide] -> [Breadcrumbs]
collectBreadcrumbs = Breadcrumbs -> [Slide] -> [Breadcrumbs]
go []
  where
    go :: Breadcrumbs -> [Slide] -> [Breadcrumbs]
go Breadcrumbs
breadcrumbs = \case
        [] -> []
        ContentSlide Instructions Block
_ : [Slide]
slides -> Breadcrumbs
breadcrumbs forall a. a -> [a] -> [a]
: Breadcrumbs -> [Slide] -> [Breadcrumbs]
go Breadcrumbs
breadcrumbs [Slide]
slides
        TitleSlide Int
lvl [Inline]
inlines : [Slide]
slides ->
            let parent :: Breadcrumbs
parent = forall a. (a -> Bool) -> [a] -> [a]
filter ((forall a. Ord a => a -> a -> Bool
< Int
lvl) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a, b) -> a
fst) Breadcrumbs
breadcrumbs in
            Breadcrumbs
parent forall a. a -> [a] -> [a]
: Breadcrumbs -> [Slide] -> [Breadcrumbs]
go (Breadcrumbs
parent forall a. [a] -> [a] -> [a]
++ [(Int
lvl, [Inline]
inlines)]) [Slide]
slides