imm-2.1.0.0: src/main/Alternate.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module Alternate (AlternateException(..), extractAlternateLinks) where
-- {{{ Imports
import Data.Aeson
import qualified Data.ByteString.Char8 as Char8
import Imm.Link
import Imm.Logger as Logger
import Imm.Pretty
import Pipes
import Pipes.ByteString as Pipes (toHandle)
import System.IO (hClose)
import System.Process.Typed
import URI.ByteString.Extended
-- }}}
newtype AlternateException = FeedNotFound URI
deriving(Eq, Show)
instance Exception AlternateException where
displayException = show . pretty
instance Pretty AlternateException where
pretty (FeedNotFound uri) = "No feeds found at" <+> prettyURI uri
data AlternateLink = AlternateLink Text Text Text
deriving(Eq, Ord, Show)
instance Pretty AlternateLink where
pretty (AlternateLink title linkType uri) = "Alternate link" <+> braces (hsep parameters) where
parameters = [ "title" <> equals <> dquotes (pretty title)
, "type" <> equals <> dquotes (pretty linkType)
, "href" <> equals <> dquotes (pretty uri)
]
instance FromJSON AlternateLink where
parseJSON = withObject "AlternateLink" $ \v -> AlternateLink
<$> (v .: "title" <|> pure mempty)
<*> v .: "type"
<*> v .: "href"
asFeedURI :: MonadFail m => URI -> Text -> m URI
asFeedURI baseURI href = let bytes = encodeUtf8 @Text href in
case parseURI laxURIParserOptions bytes of
Right uri -> pure uri
_ -> case parseRelativeRef laxURIParserOptions bytes of
Right relativeRef -> pure $ mergeURIs baseURI relativeRef
Left e -> fail $ show e
mergeURIs :: URI -> URIRef Relative -> URI
mergeURIs uri relativeRef = URI scheme authority path query fragment where
scheme = uriScheme uri
authority = rrAuthority relativeRef <|> uriAuthority uri
path = mergePaths (uriPath uri) (rrPath relativeRef)
query = rrQuery relativeRef
fragment = rrFragment relativeRef
mergePaths :: ByteString -> ByteString -> ByteString
mergePaths basePath path = case Char8.head path of
'/' -> path
_ -> basePath <> "/" <> path
asLink :: MonadFail m => URI -> AlternateLink -> m Link
asLink baseURI (AlternateLink title type' uri) = Link (Just Alternate) title (parseMediaType type') . AnyURI <$> asFeedURI baseURI uri
extractAlternateLinks :: MonadIO m => MonadFail m => Logger.Handle IO -> URI -> Producer ByteString IO () -> m [Link]
extractAlternateLinks logger baseUri html = io $ withProcessWait pup $ \pupProcess -> do
log logger Debug $ pretty $ show @String pupProcess
runEffect $ html >-> toHandle (getStdin pupProcess)
hClose (getStdin pupProcess)
links <- getStdout pupProcess & atomically <&> decode <&> fromMaybe mempty
log logger Info $ "Found alternate links:" <+> pretty links
mapM (asLink baseUri) links
where pup = proc "pup" ["html head link[rel=\"alternate\"] json{}"]
& setStdin createPipe
& setStdout byteStringOutput
& setStderr nullStream