{-#LANGUAGE NoImplicitPrelude #-} {-#LANGUAGE OverloadedStrings #-} {-#LANGUAGE LambdaCase #-} {-#LANGUAGE FlexibleInstances #-} {-#LANGUAGE MultiParamTypeClasses #-} {-#LANGUAGE ScopedTypeVariables #-} module Web.Sprinkles.PandocGVal where import Web.Sprinkles.Prelude hiding (asText, asList) import Text.Ginger as Ginger (GVal (..), ToGVal (..), dict, (~>)) import Text.Ginger.Html (unsafeRawHtml) import qualified Text.Ginger as Ginger import qualified Text.Ginger.Html as Ginger import qualified Text.Ginger.Run.VM as Ginger import Text.Pandoc import Text.Pandoc.Walk (query, walk) import Data.Default (def) import Data.Aeson (ToJSON (..)) import Data.Scientific (fromFloatDigits) writerOptions :: WriterOptions writerOptions = def gfnWithMediaRoot :: Monad m => Pandoc -> Ginger.Function (Ginger.Run p m h) gfnWithMediaRoot pandoc args = do defMediaroot <- fromMaybe def . Ginger.lookupKey "path" <$> Ginger.getVar "request" let extracted = Ginger.extractArgsDefL [ ("mediaroot", defMediaroot) ] args case extracted of Right [mediaroot] -> do let pandoc' = relativeUrlPrefix (unpack $ asText mediaroot) pandoc return $ toGVal pandoc' _ -> return def gfnWithAppRoot :: Monad m => Pandoc -> Ginger.Function (Ginger.Run p m h) gfnWithAppRoot pandoc args = do defApproot <- Ginger.getVar "approot" let extracted = Ginger.extractArgsDefL [ ("approot", defApproot) ] args case extracted of Right [approot] -> do let pandoc' = localUrlPrefix (unpack $ asText approot) pandoc return $ toGVal pandoc' _ -> return def prefixRelativeUrl :: String -> String -> String prefixRelativeUrl prefix url | "http://" `isPrefixOf` url = url | "https://" `isPrefixOf` url = url | "/" `isPrefixOf` url = url | ":" `isPrefixOf` url = url | otherwise = prefix ++ "/" ++ url prefixLocalUrl :: String -> String -> String prefixLocalUrl prefix url | "http://" `isPrefixOf` url = url | "https://" `isPrefixOf` url = url | ":" `isPrefixOf` url = url | "/" `isPrefixOf` url = prefix ++ url | otherwise = url modifyUrls :: (String -> String) -> Pandoc -> Pandoc modifyUrls f = walk goInline where goInline :: Inline -> Inline goInline (Image attrs inlines (url, title)) = Image attrs (map goInline inlines) (f url, title) goInline (Link attrs inlines (url, title)) = Link attrs (map goInline inlines) (f url, title) goInline x = x localUrlPrefix :: String -> Pandoc -> Pandoc localUrlPrefix prefix = modifyUrls (prefixLocalUrl prefix) relativeUrlPrefix :: String -> Pandoc -> Pandoc relativeUrlPrefix prefix = modifyUrls (prefixRelativeUrl prefix) instance Monad m => ToGVal (Ginger.Run p m h) Pandoc where toGVal pandoc@(Pandoc meta blocks) = def { asList = Just $ map toGVal blocks , asDictItems = Just [ ( "meta", toGVal meta ) , ( "body", toGVal blocks ) , ( "withAppRoot" , Ginger.fromFunction . gfnWithAppRoot $ pandoc ) , ( "withMediaRoot" , Ginger.fromFunction . gfnWithMediaRoot $ pandoc ) ] , asLookup = Just $ \case "meta" -> Just (toGVal meta) "body" -> Just (toGVal blocks) "withMediaRoot" -> Just . Ginger.fromFunction . gfnWithMediaRoot $ pandoc "withAppRoot" -> Just . Ginger.fromFunction . gfnWithAppRoot $ pandoc _ -> Nothing , asHtml = pandocToHtml pandoc , asText = unwords . fmap (asText . toGVal) $ blocks , asBoolean = True , asNumber = Nothing , asFunction = Nothing , Ginger.length = Just (Web.Sprinkles.Prelude.length blocks) , isNull = False } instance ToGVal m Meta where toGVal meta = let entries :: [(String, MetaValue)] entries = mapToList . unMeta $ meta in dict [ pack key ~> value | (key, value) <- entries ] instance ToGVal m MetaValue where toGVal (MetaMap m) = dict [ pack key ~> value | (key, value) <- mapToList m ] toGVal (MetaList values) = toGVal values toGVal (MetaBool b) = toGVal b toGVal (MetaString str) = toGVal str toGVal (MetaInlines inlines) = toGVal inlines toGVal (MetaBlocks blocks) = toGVal blocks instance ToGVal m Block where toGVal block = let pandoc = Pandoc nullMeta [block] listItems :: [GVal m] blockProps :: HashMap Text (GVal m) (blockProps, listItems) = blockChildren block baseProps = mapFromList ["children" ~> listItems] props = baseProps <> blockProps in def { asList = Just listItems , asDictItems = Just $ mapToList props , asLookup = Just $ \key -> lookup key props , asHtml = pandocToHtml pandoc , asText = unwords . fmap ((<> " ") . asText) $ listItems , asBoolean = True , asNumber = Nothing , asFunction = Nothing , Ginger.length = Just (Web.Sprinkles.Prelude.length listItems) , isNull = False } blockProperties :: forall m. Block -> HashMap Text (GVal m) blockProperties = fst . blockChildren blockItems :: forall m. Block -> [GVal m] blockItems = snd . blockChildren blockChildren :: forall m. Block -> (HashMap Text (GVal m), [GVal m]) blockChildren (Plain items) = ( mapFromList ["type" ~> ("plain" :: Text)] , fmap toGVal items ) blockChildren (Para items) = ( mapFromList ["type" ~> ("p" :: Text)] , fmap toGVal items ) blockChildren (CodeBlock (id, classes, attrs) items) = ( mapFromList [ "type" ~> ("code" :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , fmap toGVal items ) blockChildren (RawBlock (Format fmt) items) = ( mapFromList [ "type" ~> ("raw" :: Text) , "format" ~> fmt ] , fmap toGVal items ) blockChildren (BlockQuote items) = ( mapFromList ["type" ~> ("blockquote" :: Text)] , fmap toGVal items ) blockChildren (OrderedList _ items) = ( mapFromList [ "type" ~> ("ol" :: Text) , "items" ~> items ] , fmap toGVal items ) blockChildren (BulletList items) = ( mapFromList [ "type" ~> ("ul" :: Text) , "items" ~> items ] , fmap toGVal items ) blockChildren (LineBlock items) = ( mapFromList [ "type" ~> ("lines" :: Text) , "items" ~> items ] , fmap toGVal items ) blockChildren (DefinitionList pairs) = ( mapFromList [ "type" ~> ("dl" :: Text) , "items" ~> [ mapFromList [ "dt" ~> dt, "dd" ~> dd ] :: HashMap Text (GVal m) | (dt, dd) <- pairs ] ] , fmap toGVal pairs ) blockChildren (Header level (id, classes, attrs) items) = ( mapFromList [ "type" ~> ("h" <> tshow level :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , fmap toGVal items ) blockChildren HorizontalRule = ( mapFromList ["type" ~> ("hr" :: Text)] , [] ) blockChildren (Table caption alignments widths headers rows) = ( mapFromList [ "type" ~> ("table" :: Text) , "caption" ~> caption , "columns" ~> [ mapFromList [ "align" ~> alignment , "width" ~> fromFloatDigits width , "header" ~> header ] :: HashMap Text (GVal m) | (alignment, width, header) <- zip3 alignments widths headers ] , "rows" ~> rows ] , fmap toGVal rows :: [GVal m] ) blockChildren (Div (id, classes, attrs) items) = ( mapFromList [ "type" ~> ("div" :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , fmap toGVal items ) blockChildren Null = (mapFromList [], []) instance ToGVal m Alignment where toGVal AlignLeft = toGVal ("left" :: Text) toGVal AlignRight = toGVal ("right" :: Text) toGVal AlignCenter = toGVal ("center" :: Text) toGVal AlignDefault = def pandocToHtml :: Pandoc -> Ginger.Html pandocToHtml pandoc = case runPure $ writeHtml5String writerOptions pandoc of Left err -> unsafeRawHtml "" Right html -> unsafeRawHtml html instance ToGVal m Inline where toGVal inline = let pandoc = Pandoc nullMeta [Plain [inline]] listItems :: [GVal m] inlineProps :: HashMap Text (GVal m) (inlineProps, listItems) = inlineChildren inline baseProps = mapFromList ["children" ~> listItems] props = baseProps <> inlineProps in def { asList = Just listItems , asDictItems = Just $ mapToList props , asLookup = Just $ \key -> lookup key props , asHtml = pandocToHtml pandoc , asText = unwords . fmap asText $ listItems , asBoolean = True , asNumber = Nothing , asFunction = Nothing , Ginger.length = Just (Web.Sprinkles.Prelude.length listItems) , isNull = False } inlineChildren :: forall m. Inline -> (HashMap Text (GVal m), [GVal m]) inlineChildren (Str str) = ( mapFromList ["type" ~> ("str" :: Text)] , [toGVal . (pack :: String -> Text) $ str] :: [GVal m] ) inlineChildren (Emph items) = ( mapFromList ["type" ~> ("em" :: Text)] , fmap toGVal items ) inlineChildren (Strong items) = ( mapFromList ["type" ~> ("strong" :: Text)] , fmap toGVal items ) inlineChildren (Strikeout items) = ( mapFromList ["type" ~> ("strikeout" :: Text)] , fmap toGVal items ) inlineChildren (Superscript items) = ( mapFromList ["type" ~> ("superscript" :: Text)] , fmap toGVal items ) inlineChildren (Subscript items) = ( mapFromList ["type" ~> ("subscript" :: Text)] , fmap toGVal items ) inlineChildren (SmallCaps items) = ( mapFromList ["type" ~> ("smallCaps" :: Text)] , fmap toGVal items ) inlineChildren (Quoted quoteType items) = ( mapFromList ["type" ~> ("quoted" :: Text)] , fmap toGVal items ) inlineChildren (Cite citations items) = ( mapFromList [ "type" ~> ("cite" :: Text) , "citations" ~> citations ] , fmap toGVal items ) inlineChildren (Code (id, classes, attrs) code) = ( mapFromList [ "type" ~> ("code" :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , "attrs" ~> attrs , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , [toGVal (pack code :: Text)] ) inlineChildren Space = (mapFromList ["type" ~> ("space" :: Text)], [toGVal (" " :: Text)]) inlineChildren SoftBreak = (mapFromList ["type" ~> ("sbr" :: Text)], [toGVal (" " :: Text)]) inlineChildren LineBreak = (mapFromList ["type" ~> ("br" :: Text)], [toGVal (" " :: Text)]) inlineChildren (Math mathType src) = (mapFromList ["type" ~> ("math" :: Text)], [toGVal (pack src:: Text)]) inlineChildren (RawInline fmt src) = (mapFromList ["type" ~> ("rawInline" :: Text)], [toGVal (pack src:: Text)]) inlineChildren (Link (id, classes, attrs) items target) = ( mapFromList [ "type" ~> ("link" :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , fmap toGVal items ) inlineChildren (Image (id, classes, attrs) items target) = ( mapFromList [ "type" ~> ("image" :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , fmap toGVal items ) inlineChildren (Note items) = (mapFromList ["type" ~> ("note" :: Text)], fmap toGVal items) inlineChildren (Span (id, classes, attrs) items) = ( mapFromList [ "type" ~> ("span" :: Text) , "id" ~> (pack id :: Text) , "classes" ~> (fmap pack classes :: [Text]) , ("attrs", dict [ pack t ~> v | (t, v) <- attrs ]) ] , fmap toGVal items ) instance ToGVal m Citation where toGVal c = dict [ "id" ~> citationId c , "prefix" ~> citationPrefix c , "suffix" ~> citationSuffix c , "mode" ~> citationMode c , "noteNum" ~> citationNoteNum c , "hash" ~> citationHash c ] instance ToGVal m CitationMode where toGVal = toGVal . show