{-# LANGUAGE FlexibleContexts, MonoLocalBinds, TypeApplications, DataKinds #-}
{-# LANGUAGE MultiWayIf, OverloadedStrings #-}
module Aura.Core
(
Repository(..)
, liftEither, liftEitherM
, liftMaybe, liftMaybeM
, sudo, trueRoot
, foreignPackages, orphans, develPkgs
, isSatisfied, isInstalled
, checkDBLock
, removePkgs, partitionPkgs, packageBuildable
, notify, warn, scold, report
) where
import Aura.Colour
import Aura.Languages
import Aura.Pacman
import Aura.Pkgbuild.Editing (hotEdit)
import Aura.Settings
import Aura.Types
import Aura.Utils
import BasePrelude hiding ((<>))
import Control.Compactable (fmapEither)
import Control.Monad.Freer
import Control.Monad.Freer.Error
import Control.Monad.Freer.Reader
import Control.Monad.Trans.Maybe
import qualified Data.ByteString.Lazy.Char8 as BL
import Data.Generics.Product (field)
import qualified Data.List.NonEmpty as NEL
import Data.Semigroup
import qualified Data.Set as S
import Data.Set.NonEmpty (NonEmptySet)
import qualified Data.Set.NonEmpty as NES
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Text.Prettyprint.Doc
import Data.Text.Prettyprint.Doc.Render.Terminal
import Data.Versions (prettyV)
import Lens.Micro ((^.))
import Lens.Micro.Extras (view)
import System.Path.IO (doesFileExist)
newtype Repository = Repository { repoLookup :: Settings -> NonEmptySet PkgName -> IO (Maybe (S.Set PkgName, S.Set Package)) }
instance Semigroup Repository where
a <> b = Repository $ \ss ps -> runMaybeT $
MaybeT (repoLookup a ss ps) >>= \(bads, goods) -> case NES.fromSet bads of
Nothing -> pure (bads, goods)
Just bads' -> second (goods <>) <$> MaybeT (repoLookup b ss bads')
partitionPkgs :: NonEmpty (NonEmptySet Package) -> ([Prebuilt], [NonEmptySet Buildable])
partitionPkgs = bimap fold f . unzip . map g . toList
where g = fmapEither toEither . toList
f = mapMaybe (fmap NES.fromNonEmpty . NEL.nonEmpty)
toEither (FromAUR b) = Right b
toEither (FromRepo b) = Left b
packageBuildable :: Settings -> Buildable -> IO Package
packageBuildable ss b = FromAUR <$> hotEdit ss b
liftEither :: Member (Error a) r => Either a b -> Eff r b
liftEither = either throwError pure
liftEitherM :: (Member (Error a) r, Member m r) => m (Either a b) -> Eff r b
liftEitherM = send >=> liftEither
liftMaybe :: Member (Error a) r => a -> Maybe b -> Eff r b
liftMaybe a = maybe (throwError a) pure
liftMaybeM :: (Member (Error a) r, Member m r) => a -> m (Maybe b) -> Eff r b
liftMaybeM a m = send m >>= liftMaybe a
sudo :: (Member (Reader Settings) r, Member (Error Failure) r) => Eff r a -> Eff r a
sudo action = asks (hasRootPriv . envOf) >>= bool (throwError $ Failure mustBeRoot_1) action
trueRoot :: (Member (Reader Settings) r, Member (Error Failure) r) => Eff r a -> Eff r a
trueRoot action = ask >>= \ss ->
if not (isTrueRoot $ envOf ss) && buildUserOf (buildConfigOf ss) /= Just (User "root")
then action else throwError $ Failure trueRoot_3
foreignPackages :: IO (S.Set SimplePkg)
foreignPackages = S.fromList . mapMaybe (simplepkg' . strictText) . BL.lines <$> pacmanOutput ["-Qm"]
orphans :: IO (S.Set PkgName)
orphans = S.fromList . map (PkgName . strictText) . BL.lines <$> pacmanOutput ["-Qqdt"]
develPkgs :: IO (S.Set PkgName)
develPkgs = S.filter isDevelPkg . S.map (^. field @"name") <$> foreignPackages
where isDevelPkg (PkgName pkg) = any (`T.isSuffixOf` pkg) suffixes
suffixes = ["-git", "-hg", "-svn", "-darcs", "-cvs", "-bzr"]
isInstalled :: PkgName -> IO (Maybe PkgName)
isInstalled pkg = bool Nothing (Just pkg) <$> pacmanSuccess ["-Qq", T.unpack (pkg ^. field @"name")]
removePkgs :: (Member (Reader Settings) r, Member (Error Failure) r, Member IO r) => NonEmptySet PkgName -> Eff r ()
removePkgs pkgs = do
pacOpts <- asks commonConfigOf
liftEitherM . pacman $ ["-Rsu"] <> asFlag pkgs <> asFlag pacOpts
isSatisfied :: Dep -> IO Bool
isSatisfied (Dep n ver) = pacmanSuccess $ map T.unpack ["-T", (n ^. field @"name") <> asT ver]
where asT (LessThan v) = "<" <> prettyV v
asT (AtLeast v) = ">=" <> prettyV v
asT (MoreThan v) = ">" <> prettyV v
asT (MustBe v) = "=" <> prettyV v
asT Anything = ""
checkDBLock :: Settings -> IO ()
checkDBLock ss = do
locked <- doesFileExist lockFile
when locked $ (warn ss . checkDBLock_1 $ langOf ss) *> getLine *> checkDBLock ss
notify :: Settings -> Doc AnsiStyle -> IO ()
notify ss = putStrLnA ss . green
warn :: Settings -> Doc AnsiStyle -> IO ()
warn ss = putStrLnA ss . yellow
scold :: Settings -> Doc AnsiStyle -> IO ()
scold ss = putStrLnA ss . red
report :: (Member (Reader Settings) r, Member IO r) =>
(Doc AnsiStyle -> Doc AnsiStyle) -> (Language -> Doc AnsiStyle) -> NonEmpty PkgName -> Eff r ()
report c msg pkgs = do
ss <- ask
send . putStrLnA ss . c . msg $ langOf ss
send . T.putStrLn . dtot . colourCheck ss . vsep . map (cyan . pretty . view (field @"name")) $ toList pkgs