ghcup-0.2.6.2: lib/GHCup/Input/Parsers.hs
{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
module GHCup.Input.Parsers where
import GHCup.Command.List
import GHCup.Errors
import GHCup.Prelude
import GHCup.Prelude.Attoparsec as AP
import GHCup.Prelude.MegaParsec as MP
import GHCup.Query.DB
import GHCup.Query.Metadata
import GHCup.Types
import GHCup.Types.Optics
import GHCup.Input.Parsers.URI
import GHCup.Types.JSON
import Control.Applicative ( Alternative (..), (<|>) )
import Control.Monad ( forM, when )
#if !MIN_VERSION_base(4,13,0)
import Control.Monad.Fail ( MonadFail )
#endif
import Control.Monad.Reader
import Data.Aeson
import Data.Bifunctor
import Data.Char as C
import Data.Either
import Data.Functor
import Data.List ( sort )
import Data.Maybe
import Data.Text ( Text )
import Data.Time.Calendar ( Day )
import Data.Time.Format ( defaultTimeLocale, parseTimeM )
import Data.Variant.Excepts
import Data.Versions
import Data.Void
import Optics hiding ( set )
import Prelude hiding ( appendFile )
import Safe
import System.FilePath
import URI.ByteString hiding ( parseURI )
import qualified Data.Set as Set
import qualified Data.Attoparsec.ByteString as AP
import qualified Data.ByteString.UTF8 as UTF8
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LE
import qualified Text.Megaparsec as MP
#if !MIN_VERSION_aeson(2,0,0)
import qualified Data.HashMap.Strict as KM
#endif
-- $setup
-- >>> :set -XOverloadedStrings
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> :set -XQuasiQuotes
-- >>> import System.Directory
-- >>> import URI.ByteString
-- >>> import qualified Data.Text as T
-- >>> import GHCup.Prelude
-- >>> import GHCup.Download
-- >>> import GHCup.Version
-- >>> import GHCup.Errors
-- >>> import GHCup.Types
-- >>> import GHCup.Query.GHCupDirs
-- >>> import GHCup.Types.Optics
-- >>> import Data.Versions
-- >>> import Optics
-- >>> import GHCup.Prelude.Version.QQ
-- >>> import qualified Data.Text.Encoding as E
-- >>> import qualified Data.Map.Strict as M
-- >>> import Control.Monad.Reader
-- >>> import Data.Variant.Excepts
-- >>> import Text.PrettyPrint.HughesPJClass ( prettyShow )
-- >>> let lc = LoggerConfig { lcPrintDebug = False, consoleOutter = mempty, fileOutter = mempty, fancyColors = False }
-- >>> dirs' <- getAllDirs
-- >>> let installedVersions = [ ([pver|8.10.7|], "-debug+lol", Nothing), ([pver|8.10.4|], "", Nothing), ([pver|8.8.4|], "", Nothing), ([pver|8.8.3|], "", Nothing) ]
-- >>> let settings = defaultSettings { cache = True, metaCache = 0, noNetwork = True }
-- >>> let leanAppState = LeanAppState settings dirs' defaultKeyBindings lc
-- >>> cwd <- getCurrentDirectory
-- >>> (Right ref) <- pure $ GHCup.Utils.parseURI $ "file://" <> E.encodeUtf8 (T.pack cwd) <> "/data/metadata/" <> (urlBaseName . view pathL' $ ghcupURL)
-- >>> (Right ref') <- pure $ GHCup.Utils.parseURI $ "file://" <> E.encodeUtf8 (T.pack cwd) <> "/data/metadata/" <> (urlBaseName . view pathL' $ channelURL PrereleasesChannel)
-- >>> (VRight r) <- (fmap . fmap) _ghcupDownloads $ flip runReaderT leanAppState . runE @'[DigestError, GPGError, JSONError , DownloadFailed , FileDoesNotExistError, ContentLengthError] $ liftE (getBase ref) >>= liftE . decodeMetadata @GHCupInfo
-- >>> (VRight r') <- (fmap . fmap) _ghcupDownloads $ flip runReaderT leanAppState . runE @'[DigestError, GPGError, JSONError , DownloadFailed , FileDoesNotExistError, ContentLengthError] $ liftE (getBase ref') >>= liftE . decodeMetadata @GHCupInfo
-- >>> let rr = M.unionsWith (M.unionWith (\_ b2 -> b2)) [r, r']
-- >>> let go = flip runReaderT leanAppState . fmap (tVerToText . fst)
-------------
--[ Types ]--
-------------
-- a superset of ToolVersion
data SetToolVersion
= SetGHCVersion TargetVersionReq
| SetToolVersion VersionReq
| SetToolTag Tag
| SetToolDay Day
| SetRecommended
| SetNext
deriving (Eq, Show)
toSetToolVer :: Maybe ToolVersion -> SetToolVersion
toSetToolVer (Just (GHCVersion v')) = SetGHCVersion v'
toSetToolVer (Just (ToolVersion v')) = SetToolVersion v'
toSetToolVer (Just (ToolTag t')) = SetToolTag t'
toSetToolVer (Just (ToolDay d')) = SetToolDay d'
toSetToolVer Nothing = SetRecommended
platformParser :: String -> Either String PlatformRequest
platformParser s' = case MP.parse (platformP <* MP.eof) "" (T.pack s') of
Right r -> pure r
Left e -> Left $ errorBundlePretty e
where
archP :: MP.Parsec Void Text Architecture
archP = choice' ((\x -> MP.chunk (T.pack $ archToString x) $> x) <$> ([minBound..maxBound] :: [Architecture]))
platformP :: MP.Parsec Void Text PlatformRequest
platformP = choice'
[ (`PlatformRequest` FreeBSD)
<$> (archP <* MP.chunk "-")
<*> ( MP.chunk "portbld"
*> ( MP.try (Just <$> verP (MP.chunk "-freebsd" <* MP.eof))
<|> pure Nothing
)
<* MP.chunk "-freebsd"
)
, (`PlatformRequest` Darwin)
<$> (archP <* MP.chunk "-")
<*> ( MP.chunk "apple"
*> ( MP.try (Just <$> verP (MP.chunk "-darwin" <* MP.eof))
<|> pure Nothing
)
<* MP.chunk "-darwin"
)
, (\a d mv -> PlatformRequest a (Linux d) mv)
<$> (archP <* MP.chunk "-")
<*> distroP
<*> ((MP.try (Just <$> verP (MP.chunk "-linux" <* MP.eof)) <|> pure Nothing
)
<* MP.chunk "-linux"
)
, (\a -> PlatformRequest a Windows Nothing)
<$> ((archP <* MP.chunk "-")
<* (MP.chunk "unknown-mingw32" <|> MP.chunk "unknown-windows" <|> MP.chunk "windows"))
]
distroP :: MP.Parsec Void Text LinuxDistro
distroP = choice' ((\d -> MP.chunk (T.pack $ distroToString d) $> d) <$> allDistros)
uriParser :: String -> Either String URI
uriParser = first show . parseURI . UTF8.fromString
filePathParser :: FilePath -> Either String FilePath
filePathParser f =
if isValid f
then Right $ normalise f
else Left "Please enter a valid filepath."
absolutePathParser :: FilePath -> Either String FilePath
absolutePathParser f =
if isValid f && isAbsolute f
then Right $ normalise f
else Left "Please enter a valid absolute filepath."
isolateParser :: FilePath -> Either String FilePath
isolateParser f =
if isValid f && isAbsolute f
then Right $ normalise f
else Left "Please enter a valid filepath for isolate dir."
toolParser :: String -> Either String Tool
toolParser s' = case fmap toLower s' of
"ghcup" -> Left "'ghcup' is not a valid tool in this context"
s'' -> Right $ Tool s''
toolParserWithGHCup :: String -> Either String Tool
toolParserWithGHCup s' = case fmap toLower s' of
s'' -> Right $ Tool s''
installTargetParser :: String -> Either String [String]
installTargetParser = Right . words
toolAndVersionParser :: String -> Either String (Tool, ToolVersion)
toolAndVersionParser (T.pack -> s') = do
let (tool, toolVersion) = T.breakOn "," s'
when (toolVersion == "") $ Left "Missing tool version"
t <- toolParser (T.unpack tool)
v <- toolVersionTagEither (tail $ T.unpack toolVersion)
pure (t, v)
-- this accepts cross prefix
ghcVersionTagEither :: String -> Either String ToolVersion
ghcVersionTagEither s' =
second ToolDay (dayParser s') <|> second ToolTag (tagEither s') <|> second GHCVersion (ghcVersionEither' s')
-- this ignores cross prefix
toolVersionTagEither :: String -> Either String ToolVersion
toolVersionTagEither s' =
second ToolDay (dayParser s') <|> second ToolTag (tagEither s') <|> second ToolVersion (toolVersionEither' s')
tagEither :: String -> Either String Tag
tagEither s' = case ls of
"recommended" -> Right Recommended
"prerelease" -> Right Prerelease
"latest" -> Right Latest
"latest-prerelease" -> Right LatestPrerelease
"latest-nightly" -> Right LatestNightly
_ -> case s' of
('b':'a':'s':'e':'-':ver') -> case pvp (T.pack ver') of
Right x -> Right (Base x)
Left _ -> Left $ "Invalid PVP version for base " <> ver'
('G':'H':'C':':':xs) -> do
vers <- forM (splitOn "," xs)
$ \ver' -> either (\_ -> Left $ "Invalid PVP version for GHC " <> ver') pure (pvp . T.pack $ ver')
pure $ GHCCompat vers
('g':'h':'c':'-':ver') -> case pvp (T.pack ver') of
Right x -> Right (GHCCompat [x])
Left _ -> Left $ "Invalid PVP version for GHC " <> ver'
other -> Left $ "Unknown tag " <> other
where
ls = fmap toLower s'
ghcVersionEither :: String -> Either String TargetVersion
ghcVersionEither str' = do
v <- first (const "Not a valid version") . MP.parse ghcTargetVerP "" . T.pack $ str'
if safeVersion v
then pure v
else Left "Unsafe version, try something more vanilla like '1.2.3'"
toolVersionEither :: String -> Either String Version
toolVersionEither str' = do
v <- first (const "Not a valid version") . MP.parse (version' <* MP.eof) "" . T.pack $ str'
if safeVersion (mkTVer v)
then pure v
else Left "Unsafe version, try something more vanilla like '1.2.3'"
ghcVersionEither' :: String -> Either String TargetVersionReq
ghcVersionEither' str' = do
treq <- first (const "Not a valid version") . MP.parse ghcTargetVerRevP "" . T.pack $ str'
if safeVersion (_tvqTargetVer treq)
then pure treq
else Left "Unsafe version, try something more vanilla like '1.2.3'"
toolVersionEither' :: String -> Either String VersionReq
toolVersionEither' str' = do
vreq <- first (const "Not a valid version") . MP.parse (verRevP <* MP.eof) "" . T.pack $ str'
if safeVersion (mkTVer $ _vqVersion vreq)
then pure vreq
else Left "Unsafe version, try something more vanilla like '1.2.3'"
dayParser :: String -> Either String Day
dayParser s = maybe (Left $ "Could not parse \"" <> s <> "\". Expected format is: YYYY-MM-DD") Right
$ parseTimeM True defaultTimeLocale "%Y-%-m-%-d" s
revisionShowParser :: String -> Either String ShowRevisions
revisionShowParser s' | t == T.pack "updates" = Right ShowUpdates
| t == T.pack "all" = Right ShowAll
| t == T.pack "none" = Right ShowNone
| otherwise = Left ("Unknown criteria: " <> s')
where t = T.toLower (T.pack s')
nightlyShowParser :: String -> Either String ShowNightly
nightlyShowParser s' | t == T.pack "latest" = Right NShowLatest
| t == T.pack "all" = Right NShowAll
| t == T.pack "none" = Right NShowNone
| otherwise = Left ("Unknown criteria: " <> s')
where t = T.toLower (T.pack s')
criteriaParser :: String -> Either String ListCriteria
criteriaParser s' | t == T.pack "installed" = Right $ ListInstalled True
| t == T.pack "set" = Right $ ListSet True
| t == T.pack "available" = Right $ ListAvailable True
| t == T.pack "+installed" = Right $ ListInstalled True
| t == T.pack "+set" = Right $ ListSet True
| t == T.pack "+available" = Right $ ListAvailable True
| t == T.pack "-installed" = Right $ ListInstalled False
| t == T.pack "-set" = Right $ ListSet False
| t == T.pack "-available" = Right $ ListAvailable False
| t == T.pack "cross" = Right $ ListTarget False Nothing
| t == T.pack "-cross" = Right $ ListTarget True Nothing
| t == T.pack "+cross" = Right $ ListTarget False Nothing
| Just target <- T.stripPrefix "-target=" t = Right $ ListTarget False (Just target)
| Just target <- T.stripPrefix "+target=" t = Right $ ListTarget True (Just target)
| Just target <- T.stripPrefix "target=" t = Right $ ListTarget True (Just target)
| Just target <- T.stripPrefix "-target" t = Right $ ListTarget False (Just target)
| Just target <- T.stripPrefix "+target" t = Right $ ListTarget True (Just target)
| Just target <- T.stripPrefix "target" t = Right $ ListTarget True (Just target)
| Just tagText <- T.stripPrefix "tag=" t
, Right tag <- tagEither (T.unpack tagText) = Right $ ListTag True tag
| Just tagText <- T.stripPrefix "+tag=" t
, Right tag <- tagEither (T.unpack tagText) = Right $ ListTag True tag
| Just tagText <- T.stripPrefix "-tag=" t
, Right tag <- tagEither (T.unpack tagText) = Right $ ListTag False tag
| Just tagText <- T.stripPrefix "tag" t
, Right tag <- tagEither (T.unpack tagText) = Right $ ListTag True tag
| Just tagText <- T.stripPrefix "+tag" t
, Right tag <- tagEither (T.unpack tagText) = Right $ ListTag True tag
| Just tagText <- T.stripPrefix "-tag" t
, Right tag <- tagEither (T.unpack tagText) = Right $ ListTag False tag
| otherwise = Left ("Unknown criteria: " <> s')
where t = T.toLower (T.pack s')
keepOnParser :: String -> Either String KeepDirs
keepOnParser s' | t == T.pack "always" = Right Always
| t == T.pack "errors" = Right Errors
| t == T.pack "never" = Right Never
| otherwise = Left ("Unknown keep value: " <> s')
where t = T.toLower (T.pack s')
downloaderParser :: String -> Either String Downloader
downloaderParser s' | t == T.pack "curl" = Right Curl
| t == T.pack "wget" = Right Wget
#if defined(INTERNAL_DOWNLOADER)
| t == T.pack "internal" = Right Internal
#endif
| otherwise = Left ("Unknown downloader value: " <> s')
where t = T.toLower (T.pack s')
gpgParser :: String -> Either String GPGSetting
gpgParser s' | t == T.pack "strict" = Right GPGStrict
| t == T.pack "lax" = Right GPGLax
| t == T.pack "none" = Right GPGNone
| otherwise = Left ("Unknown gpg setting value: " <> s')
where t = T.toLower (T.pack s')
overWriteVersionParser :: String -> Either String [VersionPattern]
overWriteVersionParser = first (const "Not a valid version pattern") . MP.parse (MP.many versionPattern <* MP.eof) "" . T.pack
where
versionPattern :: MP.Parsec Void Text VersionPattern
versionPattern = do
str' <- T.unpack <$> MP.takeWhileP Nothing (/= '%')
if str' /= mempty
then pure (S str')
else fmap (const CabalVer) v_cabal
<|> fmap (const GitBranchName) b_name
<|> fmap (const GitHashShort) s_hash
<|> fmap (const GitHashLong) l_hash
<|> fmap (const GitDescribe) g_desc
<|> ((\a b -> S (a : T.unpack b)) <$> MP.satisfy (const True) <*> MP.takeWhileP Nothing (== '%')) -- invalid pattern, e.g. "%k"
where
v_cabal = MP.chunk "%v"
b_name = MP.chunk "%b"
s_hash = MP.chunk "%h"
l_hash = MP.chunk "%H"
g_desc = MP.chunk "%g"
-----------------
--[ Utilities ]--
-----------------
resolveVersion ::
( HasLog env
, MonadFail m
, MonadReader env m
, HasGHCupInfo env
, HasDirs env
, MonadIOish m
)
=> Maybe ToolVersion
-> GuessMode
-> Tool
-> Excepts
'[ TagNotFound
, DayNotFound
, NextVerNotFound
, NoToolVersionSet
, ParseError
] m TargetVersionReq
resolveVersion tv = resolveVersion' (toSetToolVer tv)
resolveVersion' ::
( HasLog env
, MonadReader env m
, HasGHCupInfo env
, HasDirs env
, MonadIOish m
)
=> SetToolVersion
-> GuessMode
-> Tool
-> Excepts
'[ TagNotFound
, DayNotFound
, NextVerNotFound
, NoToolVersionSet
, ParseError
] m TargetVersionReq
resolveVersion' SetRecommended _ tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing) $ getRecommended dls tool ?? TagNotFound Recommended tool
resolveVersion' (SetGHCVersion (TargetVersionReq v rev)) guessMode tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
tv' <- lift $ guessFullVersion dls v tool guessMode
pure (TargetVersionReq tv' rev)
resolveVersion' (SetToolVersion (toTargetVersionReq' -> treq)) guessMode tool = do
let TargetVersionReq v rev = treq
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
tv' <- lift $ guessFullVersion dls v tool guessMode
pure (TargetVersionReq tv' rev)
resolveVersion' (SetToolTag Latest) _ tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing) $ getLatest dls tool ?? TagNotFound Latest tool
resolveVersion' (SetToolDay day) _ tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
case getByReleaseDay dls tool day of
Left ad -> throwE $ DayNotFound day tool ad
Right (v, _) -> pure (TargetVersionReq v Nothing)
resolveVersion' (SetToolTag LatestPrerelease) _ tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing) $ getLatestPrerelease dls tool ?? TagNotFound LatestPrerelease tool
resolveVersion' (SetToolTag LatestNightly) _ tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing) $ getLatestNightly dls tool ?? TagNotFound LatestNightly tool
resolveVersion' (SetToolTag Recommended) _ tool = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing) $ getRecommended dls tool ?? TagNotFound Recommended tool
resolveVersion' (SetToolTag tag@(Base _)) _ t
| t == ghc = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing)
$ getLatestTagF
(\tags -> Prerelease `notElem` tags
&& LatestPrerelease `notElem` tags
&& tag `elem` tags
)
dls ghc Nothing ?? TagNotFound tag hls
resolveVersion' (SetToolTag (GHCCompat ghcs)) _ t
| t == hls = do
GHCupInfo { _ghcupDownloads = dls } <- lift getGHCupInfo
fmap (`TargetVersionReq` Nothing)
$ getLatestTagF
(\tags ->
if | Prerelease `elem` tags -> False
| LatestPrerelease `elem` tags -> False
| otherwise -> any
(\case
GHCCompat (Set.fromList -> ghcs') -> Set.fromList ghcs `Set.isSubsetOf` ghcs'
_ -> False
)
tags
)
dls hls Nothing ?? TagNotFound (GHCCompat ghcs) hls
resolveVersion' SetNext _ tool = do
set <- liftE $ getSetVersion tool Nothing !!? NoToolVersionSet tool Nothing -- TODO
vers <- lift $ getInstalledVersions tool Nothing
next <- (headMay
. tail
. dropWhile (/= set)
. cycle
. sort
$ vers) ?? NoToolVersionSet tool Nothing
let tv = mkTVer . _vrVersion $ next
pure (TargetVersionReq tv Nothing)
resolveVersion' (SetToolTag t') _ tool =
throwE $ TagNotFound t' tool
-- | Guess the full version from an input version, by possibly
-- examining the metadata and the installed versions.
--
-- >>> go $ guessFullVersion rr (mkTVer [vver|8|]) GHC GLax
-- "8.10.7"
-- >>> go $ guessFullVersion rr (mkTVer [vver|8.10|]) GHC GLax
-- "8.10.7"
-- >>> go $ guessFullVersion rr (mkTVer [vver|8.10.7|]) GHC GLax
-- "8.10.7"
-- >>> go $ guessFullVersion rr (mkTVer [vver|9.12.1|]) GHC GLax
-- "9.12.1"
-- >>> go $ guessFullVersion rr (mkTVer [vver|8|]) GHC GStrict
-- "8"
guessFullVersion :: ( HasLog env
, MonadReader env m
, HasDirs env
, MonadIOish m
)
=> GHCupDownloads
-> TargetVersion
-> Tool
-> GuessMode
-> m TargetVersion
guessFullVersion dls v tool guessMode = do
case pvp $ prettyVer (_tvVersion v) of -- need to be strict here
Left _ -> pure v
Right pvpIn
| (guessMode /= GStrict) && hasn't (_GHCupDownloads % ix tool % toolVersionsL % ix v) dls -> do
ghcs <- if guessMode == GLaxWithInstalled then getInstalledTools else pure []
if v `notElem` ghcs
then getLatestToolFor tool (_tvTarget v) pvpIn dls >>= \case
Just (pvp_, _vm, mt) -> do
v' <- pvpToVersion pvp_ ""
when (v' /= _tvVersion v) $ logWarn ("Assuming you meant version " <> prettyVer v')
pure $ TargetVersion mt v'
Nothing -> pure v
else pure v
_ -> pure v
where
getInstalledTools = fmap _tvrTargetVer <$> getInstalledVersions' tool
parseUrlSource :: String -> Either String [NewURLSource]
parseUrlSource s = (fromURLSource <$> parseUrlSource' s)
<|> ((:[]) <$> parseNewUrlSource s)
<|> parseNewUrlSources s
parseUrlSource' :: String -> Either String URLSource
parseUrlSource' "GHCupURL" = pure GHCupURL
parseUrlSource' "StackSetupURL" = pure StackSetupURL
parseUrlSource' s' = (eitherDecode . LE.encodeUtf8 . LT.pack $ s')
<|> (fmap (OwnSource . (:[]) . Right) . first show . parseURI . UTF8.fromString $ s')
parseNewUrlSource :: String -> Either String NewURLSource
parseNewUrlSource "GHCupURL" = pure NewGHCupURL
parseNewUrlSource "StackSetupURL" = pure NewStackSetupURL
parseNewUrlSource s' = (fmap NewChannelAlias . parseChannelAlias $ s')
<|> (eitherDecode . LE.encodeUtf8 . LT.pack $ s')
<|> (fmap NewURI . first show . parseURI . UTF8.fromString $ s')
parseNewUrlSources :: String -> Either String [NewURLSource]
parseNewUrlSources s = case AP.parseOnly
(AP.parseList' <* AP.skipSpaces <* AP.endOfInput)
(UTF8.fromString s) of
Right bs ->
forM bs $ \b -> AP.parseOnly (parse <* AP.skipSpaces <* AP.endOfInput) b
Left e -> Left e
where
parse :: AP.Parser NewURLSource
parse = (NewGHCupURL <$ AP.string "GHCupURL")
<|> (NewStackSetupURL <$ AP.string "StackSetupURL")
<|> AP.choice ((\x -> AP.string (UTF8.fromString . T.unpack . channelAliasText $ x) $> NewChannelAlias x) <$> ([minBound..maxBound] :: [ChannelAlias]))
<|> (NewURI <$> parseURIP)
parseChannelAlias :: String -> Either String ChannelAlias
parseChannelAlias s =
let aliases = map (\c -> (T.unpack (channelAliasText c), c)) [minBound..maxBound]
in case lookup s aliases of
Just c -> Right c
Nothing -> Left $ "Unexpected ChannelAlias: " <> s
#if MIN_VERSION_transformers(0,6,0)
instance Alternative (Either [a]) where
empty = Left []
Left _ <|> n = n
m <|> _ = m
#endif