imm-2.1.2.0: src/main/Alternate.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UnicodeSyntax #-}
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