Commits

Colin Woodbury committed 41a81db Merge with conflicts

Merge branch 'abs'

Conflicts:
Aura/Flags.hs
Aura/Settings/Base.hs
Aura/Settings/Enable.hs
Aura/Settings/Test.hs
PKGBUILD
aura.cabal
aura.hs
changelog.txt

Comments (0)

Files changed (44)

+dist/

Aura/AUR.hs

--- Module for connecting to the AUR servers,
--- downloading PKGBUILDs and source tarballs, and handling them.
-
-{-
-
-Copyright 2012, 2013 Colin Woodbury <colingw@gmail.com>
-
-This file is part of Aura.
-
-Aura is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Aura is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Aura.  If not, see <http://www.gnu.org/licenses/>.
-
--}
-
-module Aura.AUR
-    ( aurInfoLookup
-    , aurSearchLookup
-    , trueVerViaPkgbuild
-    , downloadPkgbuild
-    , sourceTarball
-    , Pkgbuild
-    , PkgInfo(..) ) where
-
-import System.FilePath ((</>))
-import Control.Monad   (liftM)
-import Data.List       (intercalate)
-import Text.JSON
-
-import Aura.Monad.Aura
-import Aura.Languages
-import Aura.Utils (scoldAndFail)
-import Aura.Bash  (value,Namespace)
-
-import Internet
-
------------------------
--- AUR API URL CREATION
------------------------
-data RPCType = PkgSearch | MultiInfo | MSearch deriving (Eq)
-
-aurPkgUrl :: Int -> String
-aurPkgUrl n = "https://aur.archlinux.org/packages.php?ID=" ++ show n
-
-rpcBaseUrl :: String
-rpcBaseUrl = "https://aur.archlinux.org/rpc.php?"
-
--- Had to do a bit off a hack, since `urlEncodeVars` wasn't encoding
--- things in the necessary way.
-rpcUrl :: RPCType -> [String] -> String
-rpcUrl t ps = rpcBaseUrl ++ ps'
-    where ps' = intercalate "&" (t' : encodedPs t)
-          encodedPs MultiInfo = map (\p -> urlEncodeVars [("arg[]",p)]) ps
-          encodedPs _         = [urlEncodeVars [("arg",unwords ps)]]
-          t' = urlEncodeVars [rpcType t]
-
-rpcType :: RPCType -> (String,String)
-rpcType t = ("type",tname)
-    where tname = case t of
-                    PkgSearch -> "search"
-                    MultiInfo -> "multiinfo"
-                    MSearch   -> "msearch"
-
--------
--- JSON
--------
--- Extend this later as needed.
-data PkgInfo = PkgInfo { nameOf        :: String
-                       , latestVerOf   :: String
-                       , isOutOfDate   :: Bool
-                       , projectURLOf  :: String
-                       , aurURLOf      :: String
-                       , licenseOf     :: String
-                       , votesOf       :: Int
-                       , descriptionOf :: String } deriving (Eq,Show)
-
-aurSearchLookup :: [String] -> Aura [PkgInfo]
-aurSearchLookup regex = getAURPkgInfo regex PkgSearch
-
-aurInfoLookup :: [String] -> Aura [PkgInfo]
-aurInfoLookup pkgs = getAURPkgInfo pkgs MultiInfo
-
-getAURPkgInfo :: [String] -> RPCType -> Aura [PkgInfo]
-getAURPkgInfo [] _    = return []
-getAURPkgInfo items t = do
-  infoJSON <- liftIO . urlContents . rpcUrl t $ items
-  case resultToEither $ parseInfoJSON infoJSON of
-    Left _     -> scoldAndFail getAURPkgInfo_1
-    Right info -> return info
-
-parseInfoJSON :: String -> Result [PkgInfo]
-parseInfoJSON json = decode json >>= apiFailCheck >>= forgePkgInfo
-    where forgePkgInfo j = valFromObj "results" j >>= mapM pkgInfo
-
-apiFailCheck :: JSObject JSValue -> Result (JSObject JSValue)
-apiFailCheck json = do
-  isError <- (== "error") `liftM` valFromObj "type" json
-  if isError then Error "AUR API lookup failed." else Ok json
-
--- Upgrade to AUR 2.0 changed several return types to Ints,
--- but Text.JSON parses them as Rationals.
-pkgInfo :: JSObject JSValue -> Result PkgInfo
-pkgInfo pkgJSON = do
-  ur <- valFromObj "URL" pkgJSON
-  na <- valFromObj "Name" pkgJSON
-  ve <- valFromObj "Version" pkgJSON
-  li <- valFromObj "License" pkgJSON
-  vo <- fromJSRat `liftM` valFromObj "NumVotes" pkgJSON
-  de <- valFromObj "Description" pkgJSON
-  au <- (aurPkgUrl . fromJSRat) `liftM` valFromObj "ID" pkgJSON
-  ou <- ((/= 0) . fromJSRat) `liftM` valFromObj "OutOfDate" pkgJSON
-  return $ PkgInfo na ve ou ur au li vo de
-
-fromJSRat :: JSValue -> Int
-fromJSRat (JSRational _ r) = round (fromRational r :: Float)
-fromJSRat _                = error "JSValue given was not a JSRational!"
-
-------------
--- PKGBUILDS
-------------
-type Pkgbuild = String
-
-aurLink :: String
-aurLink = "https://aur.archlinux.org/packages/"
-
-pkgBaseUrl :: String -> String
-pkgBaseUrl pkg = aurLink </> take 2 pkg </> pkg
-
-pkgbuildUrl :: String -> String
-pkgbuildUrl pkg = pkgBaseUrl pkg </> "PKGBUILD"
-
-downloadPkgbuild :: String -> Aura Pkgbuild
-downloadPkgbuild = liftIO . urlContents . pkgbuildUrl
-
-trueVerViaPkgbuild :: Namespace -> String
-trueVerViaPkgbuild ns = pkgver ++ "-" ++ pkgrel
-    where pkgver = head $ value ns "pkgver"
-          pkgrel = head $ value ns "pkgrel"
-
-------------------
--- SOURCE TARBALLS
-------------------
-tarballUrl :: String -> String
-tarballUrl pkg = pkgBaseUrl pkg </> pkg ++ ".tar.gz"
-
-sourceTarball :: FilePath -> String -> IO FilePath
-sourceTarball path = saveUrlContents path . tarballUrl
     , Namespace ) where
 
 import Text.Parsec.Error (ParseError)
-import Control.Monad (liftM)
 import Data.Maybe    (fromMaybe)
 
 import Aura.Settings.Base
 
 namespace :: String -> String -> Aura Namespace
 namespace pn pb = do
-  carch <- ((: []) . NoQuote . carchOf) `liftM` ask
+  carch <- ((: []) . NoQuote . carchOf) `fmap` ask
   case namespace' carch pn pb of
     Left e   -> liftIO (print e) >> failure "PKGBUILD parse failed."
     Right ns -> return ns
 
 -}
 
+-- Agnostically builds packages. They can be either AUR or ABS.
+
 module Aura.Build
     ( installPkgFiles
     , buildPackages ) where
 
 import System.FilePath ((</>), takeFileName)
-import Control.Monad   (liftM, when)
+import Control.Monad   (when, void)
 
 import Aura.Pacman (pacman)
-import Aura.AUR    (sourceTarball)
 import Aura.Settings.Base
 import Aura.Colour.Text
 import Aura.Monad.Aura
 
 ---
 
+-- TODO should this be elsewhere
+srcPkgStore :: FilePath
+srcPkgStore = "/var/cache/aura/src"
+
 -- Expects files like: /var/cache/pacman/pkg/*.pkg.tar.xz
 installPkgFiles :: [String] -> [FilePath] -> Aura ()
 installPkgFiles pacOpts files = checkDBLock >> pacman (["-U"] ++ pacOpts ++ files)
 
 -- All building occurs within temp directories in the package cache.
-buildPackages :: [AURPkg] -> Aura [FilePath]
+buildPackages :: Buildable a => [a] -> Aura [FilePath]
 buildPackages []   = return []
 buildPackages pkgs = ask >>= \ss -> do
   let buildPath = buildPathOf ss
   wrap result
 
 -- Handles the building of Packages. Fails nicely.
--- Assumed: All pacman and AUR dependencies are already installed.
-build :: [FilePath] -> [AURPkg] -> Aura [FilePath]
+-- Assumed: All dependencies are already installed.
+build :: Buildable a => [FilePath] -> [a] -> Aura [FilePath]
 build built []       = return $ filter notNull built
 build built ps@(p:_) = do
-  notify (flip buildPackages_1 pn)
+  notify $ buildPackages_1 pn
   (paths,rest) <- catch (withTempDir pn (build' ps)) (buildFail built ps)
   build (paths ++ built) rest
       where pn = pkgNameOf p
         
 -- Perform the actual build.
-build' :: [AURPkg] -> Aura ([FilePath],[AURPkg])
+-- TODO: Clean this up.
+build' :: Buildable a => [a] -> Aura ([FilePath],[a])
 build' []     = failure "build' : You should never see this."
 build' (p:ps) = ask >>= \ss -> do
-  let makepkg'   = if toSuppress then makepkgQuiet else makepkgVerbose
-      toSuppress = suppressMakepkg ss
-      user       = getTrueUser $ environmentOf ss
+  let user       = getTrueUser $ environmentOf ss
+      makepkg'   = if suppressMakepkg ss then makepkgQuiet else makepkgVerbose
   curr <- liftIO pwd
-  getSourceCode (pkgNameOf p) user curr
+  getSourceCode p user curr
   overwritePkgbuild p
   pNames <- makepkg' user
   paths  <- moveToBuildPath pNames
+  when (keepSource ss) $ makepkgSource user True >>= void . moveToSourcePath
   liftIO $ cd curr
   return (paths,ps)
 
-getSourceCode :: String -> String -> FilePath -> Aura ()
-getSourceCode pkgName user currDir = liftIO $ do
+getSourceCode :: Buildable a => a -> String -> FilePath -> Aura ()
+getSourceCode pkg user currDir = liftIO $ do
   chown user currDir []
-  tarball   <- sourceTarball currDir pkgName
-  sourceDir <- decompress tarball
+  sourceDir <- source pkg currDir
   chown user sourceDir ["-R"]
   cd sourceDir
 
-overwritePkgbuild :: AURPkg -> Aura ()
-overwritePkgbuild p = (mayHotEdit `liftM` ask) >>= check
+overwritePkgbuild :: Buildable a => a -> Aura ()
+overwritePkgbuild p = (mayHotEdit `fmap` ask) >>= check
     where check True  = liftIO . writeFile "PKGBUILD" . pkgbuildOf $ p
           check False = return ()
 
 -- Inform the user that building failed. Ask them if they want to
 -- continue installing previous packages that built successfully.
-buildFail :: [FilePath] -> [AURPkg] -> String -> Aura ([FilePath],[AURPkg])
+buildFail :: Buildable a => [FilePath] -> [a] -> String -> Aura ([FilePath],[a])
 buildFail _ [] _ = failure "buildFail : You should never see this message."
 buildFail built (p:ps) errors = ask >>= \ss -> do
   let lang = langOf ss
-  scold (flip buildFail_1 (show p))
+  scold (buildFail_1 (show p))
   displayBuildErrors errors
   printList red cyan (buildFail_2 lang) (map pkgNameOf ps)
   printList yellow cyan (buildFail_3 lang) $ map takeFileName built
 moveToBuildPath :: [FilePath] -> Aura [FilePath]
 moveToBuildPath []     = return []
 moveToBuildPath (p:ps) = do
-  newName <- ((</> p) . buildPathOf) `liftM` ask
+  newName <- ((</> p) . buildPathOf) `fmap` ask
+  liftIO $ mv p newName
+  (newName :) `fmap` moveToBuildPath ps
+
+-- Moves a file to the aura src package cache and returns its location.
+moveToSourcePath :: [FilePath] -> Aura [FilePath]
+moveToSourcePath []     = return []
+moveToSourcePath (p:ps) = do
+  let newName = srcPkgStore </> p
   liftIO $ mv p newName
-  (newName :) `liftM` moveToBuildPath ps
+  (newName :) `fmap` moveToBuildPath ps
     , SimplePkg ) where
 
 import qualified Data.Map.Lazy as M
-import Control.Monad (liftM)
 
 import Aura.Monad.Aura
 import Aura.Utils (pkgFileNameAndVer)
 
 -- This takes the filepath of the package cache as an argument.
 rawCacheContents :: FilePath -> Aura [String]
-rawCacheContents c = filter dots `liftM` liftIO (ls c)
+rawCacheContents c = filter dots `fmap` liftIO (ls c)
     where dots p = p `notElem` [".",".."]
 
 cacheContents :: FilePath -> Aura Cache
-cacheContents c = cache `liftM` rawCacheContents c
+cacheContents c = cache `fmap` rawCacheContents c
 
 alterable :: Cache -> SimplePkg -> Bool
 alterable c p = M.member p c

Aura/Commands/A.hs

 -}
 
 module Aura.Commands.A
-    ( installPackages
+    ( install
     , upgradeAURPkgs
     , aurPkgInfo
     , aurSearch
     , displayPkgbuild ) where
 
 import Text.Regex.PCRE ((=~))
-import Control.Monad   (unless, liftM)
-import Data.List       ((\\), nub, nubBy, sort)
+
+import qualified Aura.Install as I
 
 import Aura.Pacman (pacman)
-import Aura.Pkgbuild.Records
-import Aura.Pkgbuild.Editing
+import Aura.Packages.Repository
 import Aura.Settings.Base
 import Aura.Dependencies
+import Aura.Packages.AUR
 import Aura.Colour.Text
 import Aura.Monad.Aura
 import Aura.Languages
-import Aura.Build
 import Aura.Utils
 import Aura.Core
-import Aura.AUR
 
 import Shell
 
 ---
 
-type PBHandler = [AURPkg] -> Aura [AURPkg]
-
--- | The user can handle PKGBUILDs in multiple ways.
--- `--hotedit` takes the highest priority.
-pbHandler :: Aura PBHandler
-pbHandler = ask >>= check
-    where check ss | mayHotEdit ss      = return hotEdit
-                   | useCustomizepkg ss = return customizepkg
-                   | otherwise          = return return
-
-installPackages :: [String] -> [String] -> Aura ()
-installPackages _ []         = return ()
-installPackages pacOpts pkgs = ask >>= \ss ->
-  if not $ delMakeDeps ss
-     then installPackages' pacOpts pkgs
-     else do  -- `-a` was used with `-A`.
-       orphansBefore <- getOrphans
-       installPackages' pacOpts pkgs
-       orphansAfter <- getOrphans
-       let makeDeps = orphansAfter \\ orphansBefore
-       unless (null makeDeps) $ notify removeMakeDepsAfter_1
-       removePkgs makeDeps pacOpts
-
-installPackages' :: [String] -> [String] -> Aura ()
-installPackages' pacOpts pkgs = ask >>= \ss -> do
-  let toInstall = pkgs \\ ignoredPkgsOf ss
-      ignored   = pkgs \\ toInstall
-  reportIgnoredPackages ignored
-  (_,aur,nons) <- knownBadPkgCheck toInstall >>= divideByPkgType ignoreRepos
-  reportNonPackages nons
-  handler <- pbHandler
-  aurPkgs <- mapM aurPkg aur >>= reportPkgbuildDiffs >>= handler
-  notify installPackages_5
-  (repoDeps,aurDeps) <- catch (getDepsToInstall aurPkgs) depCheckFailure
-  let repoPkgs    = nub repoDeps
-      pkgsAndOpts = pacOpts ++ repoPkgs
-  reportPkgsToInstall repoPkgs aurDeps aurPkgs
-  okay <- optionalPrompt installPackages_3
-  if not okay
-     then scoldAndFail installPackages_4
-     else do
-       unless (null repoPkgs) $ pacman (["-S","--asdeps"] ++ pkgsAndOpts)
-       storePkgbuilds $ aurPkgs ++ aurDeps
-       mapM_ (buildAndInstallDep handler pacOpts) aurDeps
-       buildPackages aurPkgs >>= installPkgFiles pacOpts
-
-knownBadPkgCheck :: [String] -> Aura [String]
-knownBadPkgCheck []     = return []
-knownBadPkgCheck (p:ps) = ask >>= \ss ->
-  case p `lookup` wontBuildOf ss of
-    Nothing -> (p :) `liftM` knownBadPkgCheck ps
-    Just r  -> do
-      scold $ knownBadPkgCheck_1 p
-      putStrLnA yellow r
-      okay <- optionalPrompt knownBadPkgCheck_2
-      if okay then (p :) `liftM` knownBadPkgCheck ps else knownBadPkgCheck ps
-
-depCheckFailure :: String -> Aura a
-depCheckFailure m = scold installPackages_1 >> failure m
-
-buildAndInstallDep :: PBHandler -> [String] -> AURPkg -> Aura ()
-buildAndInstallDep handler pacOpts pkg =
-  handler [pkg] >>= buildPackages >>=
-  installPkgFiles ("--asdeps" : pacOpts)
+-- For now.
+buildHandle :: [String] -> BuildHandle
+buildHandle pacOpts =
+    BH { pkgLabel = "AUR"
+       , initialPF = filterAURPkgs
+       , mainPF    = filterAURPkgs
+       , subPF     = filterRepoPkgs
+       , subBuild  = \ps -> pacman (["-S","--asdeps"] ++ pacOpts ++ map pkgNameOf ps) }
+
+install :: [String] -> [String] -> Aura ()
+install pacOpts pkgs = I.install b c (buildHandle pacOpts) pacOpts pkgs
+    where b = package  :: String -> Aura AURPkg
+          c = conflict :: Settings -> AURPkg -> Maybe ErrMsg
 
 upgradeAURPkgs :: [String] -> [String] -> Aura ()
 upgradeAURPkgs pacOpts pkgs = ask >>= \ss -> do
   let notIgnored p = splitName p `notElem` ignoredPkgsOf ss
   notify upgradeAURPkgs_1
-  foreignPkgs <- filter (\(n,_) -> notIgnored n) `liftM` getForeignPackages
+  foreignPkgs <- filter (\(n,_) -> notIgnored n) `fmap` getForeignPackages
   pkgInfo     <- aurInfoLookup $ map fst foreignPkgs
   let aurPkgs   = filter (\(n,_) -> n `elem` map nameOf pkgInfo) foreignPkgs
       toUpgrade = filter isntMostRecent $ zip pkgInfo (map snd aurPkgs)
-  devel <- develPkgCheck
-  notify upgradeAURPkgs_2
-  if null toUpgrade && null devel
-     then warn upgradeAURPkgs_3
-     else reportPkgsToUpgrade $ map prettify toUpgrade ++ devel
-  installPackages pacOpts $ map (nameOf . fst) toUpgrade ++ pkgs ++ devel
-    where prettify (p,v) = nameOf p ++ " : " ++ v ++ " => " ++ latestVerOf p
+  auraFirst <- auraCheck $ map (nameOf . fst) toUpgrade
+  if auraFirst
+     then auraUpgrade pacOpts
+     else do
+       devel <- develPkgCheck
+       notify upgradeAURPkgs_2
+       if null toUpgrade && null devel
+          then warn upgradeAURPkgs_3
+          else reportPkgsToUpgrade $ map prettify toUpgrade ++ devel
+       install pacOpts $ map (nameOf . fst) toUpgrade ++ pkgs ++ devel
+           where prettify (p,v) = nameOf p ++ " : " ++ v ++ " => " ++ latestVerOf p
+
+auraCheck :: [String] -> Aura Bool
+auraCheck toUpgrade = do
+  if "aura" `elem` toUpgrade
+     then optionalPrompt auraCheck_1
+     else return False
+
+auraUpgrade :: [String] -> Aura ()
+auraUpgrade pacOpts = install pacOpts ["aura"]
 
 develPkgCheck :: Aura [String]
 develPkgCheck = ask >>= \ss ->
 
 renderAurPkgInfo :: Settings -> PkgInfo -> String
 renderAurPkgInfo ss info = entrify ss fields entries
-    where fields  = map white . infoFields . langOf $ ss
+    where fields  = map bForeground . infoFields . langOf $ ss
           entries = [ magenta "aur"
-                    , white $ nameOf info
+                    , bForeground $ nameOf info
                     , latestVerOf info
                     , outOfDateMsg (isOutOfDate info) $ langOf ss
+                    , orphanedMsg (maintainerOf info) $ langOf ss
                     , cyan $ projectURLOf info
                     , aurURLOf info
                     , licenseOf info
 displayPkgDeps :: [String] -> Aura ()
 displayPkgDeps []   = return ()
 displayPkgDeps pkgs = do
-  info    <- aurInfoLookup pkgs
-  aurPkgs <- mapM (aurPkg . nameOf) info
-  allDeps <- mapM determineDeps aurPkgs
-  let (ps,as,_) = foldl groupPkgs ([],[],[]) allDeps
-  reportPkgsToInstall (n ps) (nubBy sameName as) []
-    where n = nub . map splitName
-          sameName a b = pkgNameOf a == pkgNameOf b
+  aurPkgs <- aurInfoLookup pkgs >>= mapM (package . nameOf)
+  allDeps <- mapM (depCheck $ buildHandle []) aurPkgs
+  let (subs,mains,_) = groupPkgs allDeps :: ([RepoPkg],[AURPkg],[String])
+  I.reportPkgsToInstall (buildHandle []) subs mains ([] :: [AURPkg])
 
 downloadTarballs :: [String] -> Aura ()
 downloadTarballs pkgs = do
 displayPkgbuild pkgs = filterAURPkgs pkgs >>= mapM_ dnload
       where dnload p = downloadPkgbuild p >>= liftIO . putStrLn
 
+isntMostRecent :: (PkgInfo,String) -> Bool
+isntMostRecent (info,v) = trueVer > currVer
+  where trueVer = comparableVer $ latestVerOf info
+        currVer = comparableVer v
+
 ------------
 -- REPORTING
 ------------
-reportPkgsToInstall :: [String] -> [AURPkg] -> [AURPkg] -> Aura ()
-reportPkgsToInstall pacPkgs aurDeps aurPkgs = do
-  lang <- langOf `liftM` ask
-  pl (reportPkgsToInstall_1 lang) (sort pacPkgs)
-  pl (reportPkgsToInstall_2 lang) (sort $ namesOf aurDeps)
-  pl (reportPkgsToInstall_3 lang) (sort $ namesOf aurPkgs)
-      where namesOf = map pkgNameOf
-            pl      = printList green cyan
-
-reportNonPackages :: [String] -> Aura ()
-reportNonPackages = badReport reportNonPackages_1
-
-reportIgnoredPackages :: [String] -> Aura ()
-reportIgnoredPackages pkgs = do
-  lang <- langOf `liftM` ask
-  printList yellow cyan (reportIgnoredPackages_1 lang) pkgs
-
-reportPkgbuildDiffs :: [AURPkg] -> Aura [AURPkg]
-reportPkgbuildDiffs [] = return []
-reportPkgbuildDiffs ps = ask >>= check
-    where check ss | not $ diffPkgbuilds ss = return ps
-                   | otherwise = mapM_ displayDiff ps >> return ps
-          displayDiff p = do
-            let name = pkgNameOf p
-            isStored <- hasPkgbuildStored name
-            if not isStored
-               then warn $ reportPkgbuildDiffs_1 name
-               else do
-                 let new = pkgbuildOf p
-                 old <- readPkgbuild name
-                 case comparePkgbuilds old new of
-                   "" -> notify $ reportPkgbuildDiffs_2 name
-                   d  -> do
-                      warn $ reportPkgbuildDiffs_3 name
-                      liftIO $ putStrLn $ d ++ "\n"
-
 reportPkgsToUpgrade :: [String] -> Aura ()
 reportPkgsToUpgrade pkgs = do
-  lang <- langOf `liftM` ask
+  lang <- langOf `fmap` ask
   printList green cyan (reportPkgsToUpgrade_1 lang) pkgs

Aura/Commands/C.hs

 import System.Posix.Files (fileExist)
 import System.FilePath    ((</>))
 import Text.Regex.PCRE    ((=~))
-import Control.Monad      (filterM, liftM, unless)
+import Control.Monad      (filterM, unless)
 import Data.List          ((\\), sort, groupBy)
 import Data.Char          (isDigit)
 
 downgradePackages pkgs = do
   reals <- filterM isInstalled pkgs
   reportBadDowngradePkgs (pkgs \\ reals)
-  cachePath <- cachePathOf `liftM` ask
+  cachePath <- cachePathOf `fmap` ask
   unless (null reals) $ do
     cache   <- cacheContents cachePath
     choices <- mapM (getDowngradeChoice cache) reals
 copyAndNotify :: FilePath -> [String] -> Int -> Aura ()
 copyAndNotify _ [] _       = return ()
 copyAndNotify dir (p:ps) n = do
-  cachePath <- cachePathOf `liftM` ask
+  cachePath <- cachePathOf `fmap` ask
   liftIO $ raiseCursorBy 1
   warn $ copyAndNotify_1 n
   liftIO $ cp (cachePath </> p) (dir </> p)

Aura/Commands/L.hs

     , logInfoOnPkg ) where
 
 import Text.Regex.PCRE ((=~))
-import Control.Monad   (liftM)
 import Data.List       ((\\))
 
 import Aura.Colour.Text (yellow)
 -- Very similar to `searchCache`. But is this worth generalizing?
 searchLogFile :: [String] -> Aura ()
 searchLogFile input = ask >>= \ss -> liftIO $ do
-  logFile <- lines `liftM` readFile (logFilePathOf ss)
+  logFile <- lines `fmap` readFile (logFilePathOf ss)
   mapM_ putStrLn $ searchLines (unwords input) logFile
 
 logInfoOnPkg :: [String] -> Aura ()

Aura/Commands/M.hs

+{- Handles all `-M` operations for building from the ABS.
+
+* On `-M` suboptions in general *
+ Note that `-M` interacts with your _local_ copy of the
+ Arch Build System Tree. `-i` `-p` or `-s` are thus _local_ searches
+ of whatever you have in your /var/abs/
+
+* On `-y` *
+ Using `-My` makes an `abs` shell call on all the packages in your
+ local tree. It does _not_ sync the entire ABS tree. For that, simply
+ use `sudo abs`.
+
+* On Building Packages *
+ Using just `-M` to build a package from the ABS tree will attempt
+ to build with the PKGBUILD from your local tree. If it doesn't
+ exist, a fresh copy will be synced with `abs` and then built.
+
+-}
+
+{-
+
+Copyright 2012, 2013 
+Colin Woodbury <colingw@gmail.com>
+Nicholas Clarke <nicholas.clarke@sanger.ac.uk>
+
+This file is part of Aura.
+
+Aura is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Aura is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Aura.  If not, see <http://www.gnu.org/licenses/>.
+
+-}
+
+module Aura.Commands.M
+    ( install
+    , absInfo
+    , absSearch
+    , absSync
+    , cleanABSTree
+    , displayPkgbuild
+    , displayPkgDeps ) where
+
+import System.Directory (removeDirectoryRecursive, createDirectory)
+import Text.Regex.PCRE  ((=~))
+
+import qualified Aura.Install as I
+
+import Aura.Pkgbuild.Base (trueVersion)
+import Aura.Pacman        (pacman)
+import Aura.Settings.Base
+import Aura.Dependencies
+import Aura.Packages.ABS
+import Aura.Packages.Repository
+import Aura.Colour.Text
+import Aura.Monad.Aura
+import Aura.Languages
+import Aura.Utils
+import Aura.Bash
+import Aura.Core
+
+import Utilities (whenM)
+
+---
+
+-- | All repo dependencies will be installed via pacman.
+defaultHandle :: [String] -> BuildHandle
+defaultHandle pacOpts =
+    BH { pkgLabel = "ABS"
+       , initialPF = filterABSPkgs
+       , mainPF    = \_ -> return []
+       , subPF     = filterRepoPkgs
+       , subBuild  = \ps -> pacman (["-S","--asdeps"] ++ pacOpts ++ map pkgNameOf ps) }
+
+-- | All repo dependencies will be built manually.
+manualHandle :: [String] -> BuildHandle
+manualHandle _ = BH { pkgLabel = "ABS"
+                    , initialPF = filterABSPkgs
+                    , mainPF    = filterABSPkgs
+                    , subPF     = \_  -> return []
+                    , subBuild  = \_  -> return () }
+
+-- | Install packages, managing dependencies.
+-- We force the types on some polymorphic functions here.
+install :: [String] -> [String] -> Aura ()
+install pacOpts pkgs = buildABSDeps `fmap` ask >>= \manual -> do
+  let handle = if manual then manualHandle else defaultHandle
+  I.install b c (handle pacOpts) pacOpts pkgs
+    where b = package  :: String -> Aura ABSPkg
+          c = conflict :: Settings -> RepoPkg -> Maybe ErrMsg
+
+-- | Get info about the specified package (-i)
+absInfo :: [String] -> Aura ()
+absInfo pkgs = packages pkgs >>= mapM_ displayAbsPkgInfo
+
+-- | Search ABS for any packages matching the given patterns (-s)
+absSearch :: [String] -> Aura ()
+absSearch pat = treeSearch pat' >>= mapM_ (liftIO . putStrLn . renderSearch pat')
+    where pat' = unwords pat
+
+cleanABSTree :: Aura ()
+cleanABSTree = whenM (optionalPrompt cleanABSTree_1) $ do
+  warn cleanABSTree_2
+  liftIO $ removeDirectoryRecursive absBasePath
+  liftIO $ createDirectory absBasePath
+
+displayPkgbuild :: [String] -> Aura ()
+displayPkgbuild pkgs =
+    (packages pkgs :: Aura [ABSPkg]) >>= mapM_ (liftIO . putStrLn . pkgbuildOf)
+
+displayPkgDeps :: [String] -> Aura ()
+displayPkgDeps []   = return ()
+displayPkgDeps pkgs = do
+  deps <- packages pkgs >>= mapM (depCheck $ defaultHandle [])
+  let (subs,mains,_) = groupPkgs deps :: ([RepoPkg],[ABSPkg],[String])
+  I.reportPkgsToInstall (defaultHandle []) subs mains ([] :: [ABSPkg])
+
+displayAbsPkgInfo :: ABSPkg -> Aura ()
+displayAbsPkgInfo pkg = ask >>= liftIO . putStrLn . renderPkgInfo pkg
+
+renderPkgInfo :: ABSPkg -> Settings -> String
+renderPkgInfo pkg ss = entrify ss fields entries
+  where ns      = namespaceOf pkg
+        fields  = map bForeground . absInfoFields . langOf $ ss
+        entries = [ magenta $ repoOf pkg
+                  , bForeground $ pkgNameOf pkg
+                  , trueVersion ns
+                  , unwords $ value ns "depends"
+                  , unwords $ value ns "makedepends"
+                  , concat  $ value ns "pkgdesc" ]
+
+renderSearch :: String -> ABSPkg -> String
+renderSearch pat pkg = repo ++ "/" ++ n ++ " " ++ v ++ " \n    " ++ d
+    where c cl cs = case cs =~ ("(?i)" ++ pat) of
+                      (b,m,a) -> cl b ++ bCyan m ++ cl a
+          repo = magenta $ repoOf pkg
+          ns   = namespaceOf pkg
+          n    = c bForeground $ pkgNameOf pkg
+          v    = green $ trueVersion ns
+          d    = head $ value ns "pkgdesc"

Aura/Conflicts.hs

+{-
+
+Copyright 2012, 2013 Colin Woodbury <colingw@gmail.com>
+
+This file is part of Aura.
+
+Aura is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Aura is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Aura.  If not, see <http://www.gnu.org/licenses/>.
+
+-}
+
+module Aura.Conflicts where
+
+import Text.Regex.PCRE ((=~))
+
+import Aura.Pkgbuild.Base (trueVersion)
+import Aura.Settings.Base
+import Aura.Languages
+import Aura.Utils
+import Aura.Core
+
+---
+
+-- Questions to be answered in conflict checks:
+-- 1. Is the package ignored in `pacman.conf`?
+-- 2. Is the version requested different from the one provided by
+--    the most recent version?
+
+-- | Common for all Packages that are Buildable.
+buildableConflicts :: Buildable b => Settings -> b -> Maybe ErrMsg
+buildableConflicts = realPkgConflicts (trueVersion . namespaceOf)
+
+-- | Must be called with a (f)unction that yields the version number
+-- of the most up-to-date form of the package.
+realPkgConflicts :: Package p => (p -> String) -> Settings -> p -> Maybe ErrMsg
+realPkgConflicts f ss pkg
+    | isIgnored (pkgNameOf pkg) toIgnore       = Just failMsg1
+    | isVersionConflict (versionOf pkg) curVer = Just failMsg2
+    | otherwise = Nothing
+    where curVer   = f pkg
+          name     = pkgNameOf pkg
+          reqVer   = show $ versionOf pkg
+          lang     = langOf ss
+          toIgnore = ignoredPkgsOf ss
+          failMsg1 = getRealPkgConflicts_2 name lang
+          failMsg2 = getRealPkgConflicts_1 name curVer reqVer lang
+
+-- Compares a (r)equested version number with a (c)urrent up-to-date one.
+-- The `MustBe` case uses regexes. A dependency demanding version 7.4
+-- SHOULD match as `okay` against version 7.4, 7.4.0.1, or even 7.4.0.1-2.
+isVersionConflict :: VersionDemand -> String -> Bool
+isVersionConflict Anything _     = False
+isVersionConflict (LessThan r) c = comparableVer c >= comparableVer r
+isVersionConflict (MoreThan r) c = comparableVer c <= comparableVer r
+isVersionConflict (MustBe   r) c = not $ c =~ ('^' : r)
+isVersionConflict (AtLeast  r) c | comparableVer c > comparableVer r = False
+                                 | isVersionConflict (MustBe r) c = True
+                                 | otherwise = False
+{-# LANGUAGE Rank2Types #-}  -- Not sure if this is correct.
+
 {-
 
 Copyright 2012, 2013 Colin Woodbury <colingw@gmail.com>
 
 import System.Directory (doesFileExist)
 import Text.Regex.PCRE  ((=~))
-import Control.Monad    (liftM,when)
-import Data.List        ((\\), nub, intercalate, isSuffixOf)
+import Control.Monad    (when)
+import Data.List        (isSuffixOf)
 
+import Aura.Bash (Namespace)
 import Aura.Settings.Base
 import Aura.Colour.Text
 import Aura.Monad.Aura
 import Aura.Languages
 import Aura.Pacman
 import Aura.Utils
-import Aura.Bash
-import Aura.AUR
 
 import Utilities
 import Shell
 --------
 -- TYPES
 --------
-type ErrMsg = String
-
------------
--- PACKAGES
------------
-class Package a where
-    pkgNameOf :: a -> String
-    versionOf :: a -> VersionDemand
+type ErrMsg    = String
+type Pkgbuild  = String
+type PkgFilter = [String] -> Aura [String]
 
 data VersionDemand = LessThan String
                    | AtLeast String
                    | Anything
                      deriving (Eq)
 
+-- These functions names could use changing.
+data BuildHandle = BH { pkgLabel  :: String
+                      , initialPF :: PkgFilter 
+                      , mainPF    :: PkgFilter
+                      , subPF     :: PkgFilter
+                      , subBuild  :: Package p => [p] -> Aura () }
+
+----------------
+-- Package Class
+----------------
+class (Show a, Eq a) => Package a where
+    pkgNameOf :: a -> String
+    versionOf :: a -> VersionDemand
+    conflict  :: Settings -> a -> Maybe ErrMsg
+    package   :: String -> Aura a
+
 instance Show VersionDemand where
     show (LessThan v) = '<' : v
     show (AtLeast v)  = ">=" ++ v
     show (MustBe  v)  = '=' : v
     show Anything     = ""
 
--- I would like to reduce the following three sets of instance declarations
--- to a single more polymorphic solution.
----------------
--- AUR Packages
----------------
-data AURPkg = AURPkg String VersionDemand Pkgbuild Namespace
-               
-instance Package AURPkg where
-    pkgNameOf (AURPkg n _ _ _) = n
-    versionOf (AURPkg _ v _ _) = v
-
-instance Show AURPkg where
-    show = pkgNameWithVersionDemand
-
-instance Eq AURPkg where
-    a == b = pkgNameWithVersionDemand a == pkgNameWithVersionDemand b
-
-pkgbuildOf :: AURPkg -> String
-pkgbuildOf (AURPkg _ _ p _) = p
-
-namespaceOf :: AURPkg -> Namespace
-namespaceOf (AURPkg _ _ _ ns) = ns
-
 ------------------
--- Pacman Packages
+-- Buildable Class
 ------------------
-data PacmanPkg = PacmanPkg String VersionDemand String
-
-instance Package PacmanPkg where
-    pkgNameOf (PacmanPkg n _ _) = n
-    versionOf (PacmanPkg _ v _) = v
-
-instance Show PacmanPkg where
-    show = pkgNameWithVersionDemand
-
-instance Eq PacmanPkg where
-    a == b = pkgNameWithVersionDemand a == pkgNameWithVersionDemand b
-
-pkgInfoOf :: PacmanPkg -> String
-pkgInfoOf (PacmanPkg _ _ i) = i
-
--------------------
--- Virtual Packages
--------------------
--- Virtual packages also contain a record of their providing package.
--- Providing packages are assumed to be Pacman (ABS) packages.
--- Are there any instances where this isn't the case?
-data VirtualPkg = VirtualPkg String VersionDemand (Maybe PacmanPkg)
-
-instance Package VirtualPkg where
-    pkgNameOf (VirtualPkg n _ _) = n
-    versionOf (VirtualPkg _ v _) = v
-
-instance Show VirtualPkg where
-    show = pkgNameWithVersionDemand
-
-providerPkgOf :: VirtualPkg -> Maybe PacmanPkg
-providerPkgOf (VirtualPkg _ _ p) = p
+class Package a => Buildable a where
+  pkgbuildOf  :: a -> Pkgbuild
+  namespaceOf :: a -> Namespace
+  -- | Fetch and extract the source code corresponding to the given package.
+  source :: a           -- ^ Package (currently AUR or ABS)
+         -> FilePath    -- ^ Directory in which to extract the package.
+         -> IO FilePath -- ^ Path to the extracted source.
+  rewrap :: a -> Namespace -> a  -- ^ Assign a new Namespace.
 
 ---------------------------------
 -- Functions common to `Package`s
                                | c == "="  = MustBe v
                                | otherwise = Anything
 
-pacmanPkg :: String -> Aura PacmanPkg
-pacmanPkg pkg = PacmanPkg name ver `liftM` pacmanOutput ["-Si",name]
-    where (name,ver) = parseNameAndVersionDemand pkg
-
-aurPkg :: String -> Aura AURPkg
-aurPkg pkg = do
-  pkgbuild  <- downloadPkgbuild name
-  AURPkg name ver pkgbuild `liftM` namespace name pkgbuild
-      where (name,ver) = parseNameAndVersionDemand pkg
-
-virtualPkg :: String -> Aura VirtualPkg
-virtualPkg pkg = VirtualPkg name ver `liftM` getProvider pkg
-    where (name,ver) = parseNameAndVersionDemand pkg
-          getProvider n = do
-            provider <- getProvidingPkg n
-            case provider of
-              Nothing -> return Nothing
-              Just p  -> Just `liftM` pacmanPkg p
-
--- Yields a virtual package's providing package if there is one.
-getProvidingPkg :: String -> Aura (Maybe String)
-getProvidingPkg virt = do
-  candidates <- getProvidingPkg' virt
-  let lined = lines candidates
-  if length lined /= 1
-     then return Nothing
-     else return . Just . head $ lined
-
--- Unsafe version.
--- Only use on virtual packages that have guaranteed providers.
--- Adding "$" to the pkg name (technically a regex) fixes a bug.
-getProvidingPkg' :: String -> Aura String
-getProvidingPkg' virt = do
-  let (name,_) = splitNameAndVer virt
-  nub `liftM` pacmanOutput ["-Ssq",name ++ "$"]
-
 -----------
 -- THE WORK
 -----------
 -- | Action won't be allowed unless user is root, or using sudo.
 sudo :: Aura () -> Aura ()
 sudo action = do
-  hasPerms <- (hasRootPriv . environmentOf) `liftM` ask
+  hasPerms <- (hasRootPriv . environmentOf) `fmap` ask
   if hasPerms then action else scoldAndFail mustBeRoot_1
 
 -- | Prompt if the user is the true Root. Building as it can be dangerous.
        okay <- optionalPrompt trueRoot_1
        if okay then action else notify trueRoot_2
 
+packages :: Package a => [String] -> Aura [a]
+packages = mapM package
+
 -- `-Qm` yields a list of sorted values.
 getForeignPackages :: Aura [(String,String)]
-getForeignPackages = (map fixName . lines) `liftM` pacmanOutput ["-Qm"]
+getForeignPackages = (map fixName . lines) `fmap` pacmanOutput ["-Qm"]
     where fixName = hardBreak (== ' ')
 
 getOrphans :: Aura [String]
-getOrphans = lines `liftM` pacmanOutput ["-Qqdt"]
+getOrphans = lines `fmap` pacmanOutput ["-Qqdt"]
 
 getDevelPkgs :: Aura [String]
-getDevelPkgs = (filter isDevelPkg . map fst) `liftM` getForeignPackages
+getDevelPkgs = (filter isDevelPkg . map fst) `fmap` getForeignPackages
 
 isDevelPkg :: String -> Bool
 isDevelPkg p = any (`isSuffixOf` p) suffixes
     where suffixes = ["-git","-hg","-svn","-darcs","-cvs","-bzr"]
 
-isntMostRecent :: (PkgInfo,String) -> Bool
-isntMostRecent (info,v) = trueVer > currVer
-  where trueVer = comparableVer $ latestVerOf info
-        currVer = comparableVer v
-
 isIgnored :: String -> [String] -> Bool
 isIgnored pkg toIgnore = pkg `elem` toIgnore
 
 isInstalled :: String -> Aura Bool
 isInstalled pkg = pacmanSuccess ["-Qq",pkg]
 
-type PkgFilter = [String] -> Aura [String]
-
-ignoreRepos :: PkgFilter
-ignoreRepos _ = return []
-
-filterAURPkgs :: PkgFilter
-filterAURPkgs pkgs = map nameOf `liftM` aurInfoLookup pkgs
-
-filterRepoPkgs :: PkgFilter
-filterRepoPkgs pkgs = do
-  repoPkgs <- lines `liftM` pacmanOutput ["-Ssq",pkgs']
-  return $ filter (`elem` repoPkgs) pkgs
-    where pkgs' = "^(" ++ prep pkgs ++ ")$"
-          prep  = specs . intercalate "|"
-          specs []     = []
-          specs (c:cs) | c `elem` "+" = ['[',c,']'] ++ specs cs
-                       | otherwise    = c : specs cs
-
 removePkgs :: [String] -> [String] -> Aura ()
 removePkgs [] _         = return ()
 removePkgs pkgs pacOpts = pacman  $ ["-Rsu"] ++ pkgs ++ pacOpts
 
-divideByPkgType :: PkgFilter -> [String] -> Aura ([String],[String],[String])
-divideByPkgType repoFilter pkgs = do
-  repoPkgNames <- repoFilter namesOnly
-  aurPkgNames  <- filterAURPkgs $ namesOnly \\ repoPkgNames
-  let aurPkgs  = filter (flip elem aurPkgNames . splitName) pkgs
-      repoPkgs = filter (flip elem repoPkgNames . splitName) pkgs
-      others   = (pkgs \\ aurPkgs) \\ repoPkgs
-  return (repoPkgs, aurPkgs, others)
-      where namesOnly = map splitName pkgs
-
 -- | Block further action until the database is free.
 checkDBLock :: Aura ()
 checkDBLock = do
 colouredMessage c msg = ask >>= putStrLnA c . msg . langOf
 
 renderColour :: Colouror -> (Language -> String) -> Aura String
-renderColour c msg = (c . msg . langOf) `liftM` ask
+renderColour c msg = (c . msg . langOf) `fmap` ask
 
 say :: (Language -> String) -> Aura ()
 say = colouredMessage noColour

Aura/Dependencies.hs

 
 -}
 
-module Aura.Dependencies where
+module Aura.Dependencies
+  ( ignoreRepos
+  , divideByPkgType
+  , depCheck
+  , depsToInstall ) where
 
-import Text.Regex.PCRE ((=~))
 import Control.Monad   (filterM)
-import Data.Maybe      (fromJust, isNothing)
-import Data.List       (nub)
+import Data.Maybe      (fromJust, mapMaybe)
+import Data.List       ((\\), nub)
 
-import Aura.Pacman (pacmanOutput)
-import Aura.AUR    (trueVerViaPkgbuild)
+import Aura.Pacman        (pacmanOutput)
+import Aura.Packages.Repository
+import Aura.Packages.Virtual
 import Aura.Settings.Base
 import Aura.Monad.Aura
 import Aura.Languages
 import Aura.Bash
 import Aura.Core
 
-import Utilities (notNull, tripleThrd)
+import Utilities (notNull)
 
 ---
 
--- Returns the deps to be installed, or fails nicely.
-getDepsToInstall :: [AURPkg] -> Aura ([String],[AURPkg])
-getDepsToInstall []   = ask >>= failure . getDepsToInstall_1 . langOf
-getDepsToInstall pkgs = ask >>= \ss -> do
-  allDeps <- mapM determineDeps pkgs
-  let (ps,as,vs) = foldl groupPkgs ([],[],[]) allDeps
-  necPacPkgs <- filterM mustInstall ps >>= mapM pacmanPkg
-  necAURPkgs <- filterM (mustInstall . show) as
-  necVirPkgs <- filterM mustInstall vs >>= mapM virtualPkg
-  let conflicts = getConflicts ss (necPacPkgs,necAURPkgs,necVirPkgs)
-  if notNull conflicts
-     then failure $ unlines conflicts
+{- NOTE
+
+`Main Packages` are either AUR or ABS packages.
+`Sub Packages`  are either ABS or Repository packages.
+
+These can (or should) freely overlap.
+
+-}
+
+-- `PkgFilter` is in the Aura Monad, so this function must be too.
+divideByPkgType ::
+    PkgFilter -> PkgFilter -> [String] -> Aura ([String],[String],[String])
+divideByPkgType subPF' mainPF' pkgs = do
+  subPkgNames  <- subPF' namesOnly
+  mainPkgNames <- mainPF' $ namesOnly \\ subPkgNames
+  let mains  = filter (flip elem mainPkgNames . splitName) pkgs
+      subs   = filter (flip elem subPkgNames  . splitName) pkgs
+      others = (pkgs \\ mains) \\ subs
+  return (subs, mains, others)
+      where namesOnly = map splitName pkgs
+
+-- | Returns all dependencies to be installed, or fails nicely.
+depsToInstall :: (Package p, Buildable b) => (Settings -> p -> Maybe ErrMsg) ->
+                 BuildHandle -> [b] -> Aura ([p],[b])
+depsToInstall _ _ [] = ask >>= failure . getDepsToInstall_1 . langOf
+depsToInstall subConflict bh pkgs = ask >>= \ss -> do
+  (subs,mains,virts) <- groupPkgs `fmap` mapM (depCheck bh) pkgs
+  necSubPkgs  <- filterM (mustInstall . show) subs
+  necMainPkgs <- filterM (mustInstall . show) mains
+  necVirPkgs  <- filterM mustInstall virts >>= packages
+  let flicts = conflicts subConflict ss (necSubPkgs,necMainPkgs,necVirPkgs)
+  if notNull flicts
+     then failure $ unlines flicts
      else do
-       let providers  = map (pkgNameOf . fromJust . providerPkgOf) necVirPkgs
-           pacmanPkgs = map pkgNameOf necPacPkgs
-       return (nub $ providers ++ pacmanPkgs, necAURPkgs)
+       let providers = map (pkgNameOf . fromJust . providerPkgOf) necVirPkgs
+       providers' <- packages $ (providers \\ map pkgNameOf necSubPkgs)
+       return (providers' ++ necSubPkgs, necMainPkgs)
 
--- Returns ([RepoPackages], [AURPackages], [VirtualPackages])
-determineDeps :: AURPkg -> Aura ([String],[AURPkg],[String])
-determineDeps pkg = do
+depCheck :: (Package p, Buildable b) => BuildHandle -> b -> Aura ([p],[b],[String])
+depCheck bh pkg = do
   let ns   = namespaceOf pkg
       deps = concatMap (value ns) ["depends","makedepends","checkdepends"]
-  (repoPkgNames,aurPkgNames,other) <- divideByPkgType filterRepoPkgs deps
-  aurPkgs       <- mapM aurPkg aurPkgNames
-  recursiveDeps <- mapM determineDeps aurPkgs
-  let (rs,as,os) = foldl groupPkgs (repoPkgNames,aurPkgs,other) recursiveDeps
-  return (nub rs, nub as, nub os)
-
--- If a package isn't installed, `pacman -T` will yield a single name.
+  (subNames,mainNames,other) <- divideByPkgType (subPF bh) (mainPF bh) deps
+  mainPkgs      <- packages mainNames
+  subPkgs       <- packages subNames
+  recursiveDeps <- mapM (depCheck bh) mainPkgs
+  let (ss,ms,os) = foldl groupPkgs' (subPkgs,mainPkgs,other) recursiveDeps
+  return (nub ss, nub ms, nub os)
+
+-- Moving to a libalpm backend will make this less hacked.
+-- | If a package isn't installed, `pacman -T` will yield a single name.
 -- Any other type of output means installation is not required. 
 mustInstall :: String -> Aura Bool
-mustInstall pkg = do
-  necessaryDeps <- pacmanOutput ["-T",pkg]
-  return $ length (words necessaryDeps) == 1
-
--- Questions to be answered in conflict checks:
--- 1. Is the package ignored in `pacman.conf`?
--- 2. Is the version requested different from the one provided by
---    the most recent version?
-getConflicts :: Settings -> ([PacmanPkg],[AURPkg],[VirtualPkg]) -> [ErrMsg]
-getConflicts settings (ps,as,vs) = rErr ++ aErr ++ vErr
-    where rErr     = extract $ map (getPacmanConflicts lang toIgnore) ps
-          aErr     = extract $ map (getAURConflicts lang toIgnore) as
-          vErr     = extract $ map (getVirtualConflicts lang toIgnore) vs
-          extract  = map fromJust . filter (/= Nothing)
-          lang     = langOf settings
-          toIgnore = ignoredPkgsOf settings
-
-getPacmanConflicts :: Language -> [String] -> PacmanPkg -> Maybe ErrMsg
-getPacmanConflicts = getRealPkgConflicts f
-    where f = getMostRecentVerNum . pkgInfoOf
-       
--- Takes `pacman -Si` output as input.
-getMostRecentVerNum :: String -> String
-getMostRecentVerNum info = tripleThrd match
-    where match     = thirdLine =~ ": " :: (String,String,String)
-          thirdLine = allLines !! 2  -- Version num is always the third line.
-          allLines  = lines info
-
-getAURConflicts :: Language -> [String] -> AURPkg -> Maybe ErrMsg
-getAURConflicts = getRealPkgConflicts f
-    where f = trueVerViaPkgbuild . namespaceOf
-
--- Must be called with a (f)unction that yields the version number
--- of the most up-to-date form of the package.
-getRealPkgConflicts :: Package a => (a -> String) -> Language -> [String] ->
-                       a -> Maybe ErrMsg
-getRealPkgConflicts f lang toIgnore pkg
-    | isIgnored (pkgNameOf pkg) toIgnore       = Just failMessage1
-    | isVersionConflict (versionOf pkg) curVer = Just failMessage2
-    | otherwise = Nothing    
-    where curVer       = f pkg
-          name         = pkgNameOf pkg
-          reqVer       = show $ versionOf pkg
-          failMessage1 = getRealPkgConflicts_2 name lang
-          failMessage2 = getRealPkgConflicts_1 name curVer reqVer lang
-
--- This can't be generalized as easily.
-getVirtualConflicts :: Language -> [String] -> VirtualPkg -> Maybe ErrMsg
-getVirtualConflicts lang toIgnore pkg
-    | isNothing (providerPkgOf pkg) = Just failMessage1
-    | isIgnored provider toIgnore   = Just failMessage2
-    | isVersionConflict (versionOf pkg) pVer = Just failMessage3
-    | otherwise = Nothing
-    where name         = pkgNameOf pkg
-          ver          = show $ versionOf pkg
-          provider     = pkgNameOf . fromJust . providerPkgOf $ pkg
-          pVer         = getProvidedVerNum pkg
-          failMessage1 = getVirtualConflicts_1 name lang
-          failMessage2 = getVirtualConflicts_2 name provider lang
-          failMessage3 = getVirtualConflicts_3 name ver provider pVer lang
-
-getProvidedVerNum :: VirtualPkg -> String
-getProvidedVerNum pkg = splitVer match
-    where match = info =~ ("[ ]" ++ pkgNameOf pkg ++ ">?=[0-9.]+")
-          info  = pkgInfoOf . fromJust . providerPkgOf $ pkg
-
--- Compares a (r)equested version number with a (c)urrent up-to-date one.
--- The `MustBe` case uses regexes. A dependency demanding version 7.4
--- SHOULD match as `okay` against version 7.4, 7.4.0.1, or even 7.4.0.1-2.
-isVersionConflict :: VersionDemand -> String -> Bool
-isVersionConflict Anything _     = False
-isVersionConflict (LessThan r) c = comparableVer c >= comparableVer r
-isVersionConflict (MoreThan r) c = comparableVer c <= comparableVer r
-isVersionConflict (MustBe r)   c = not $ c =~ ('^' : r)
-isVersionConflict (AtLeast r)  c | comparableVer c > comparableVer r = False
-                                 | isVersionConflict (MustBe r) c = True
-                                 | otherwise = False
+mustInstall pkg = ((==) 1 . length . words) `fmap` pacmanOutput ["-T",pkg]
+
+conflicts :: (Package p, Buildable b) => (Settings -> p -> Maybe ErrMsg) ->
+             Settings -> ([p],[b],[VirtualPkg]) -> [ErrMsg]
+conflicts subConflict ss (subs,mains,virts) = sErr ++ mErr ++ vErr
+    where sErr = mapMaybe (subConflict ss) subs
+          mErr = mapMaybe (conflict ss) mains
+          vErr = mapMaybe (conflict ss) virts
 
 -}
 
-module Aura.Flags where
+module Aura.Flags
+    ( parseLanguageFlag
+    , parseFlags 
+    , settingsFlags
+    , reconvertFlags
+    , dualFlagMap
+    , hijackedFlagMap
+    , suppressionStatus
+    , delMakeDepsStatus
+    , confirmationStatus
+    , hotEditStatus
+    , pbDiffStatus
+    , rebuildDevelStatus
+    , customizepkgStatus
+    , filterSettingsFlags
+    , ignoredAuraPkgs
+    , buildPath
+    , auraOperMsg
+    , noPowerPillStatus
+    , keepSourceStatus
+    , buildABSDepsStatus
+    , Flag(..) ) where
 
 import System.Console.GetOpt
 import Data.Maybe (fromMaybe)
 
 type FlagMap = [(Flag,String)]
 
-data Flag = AURInstall
+data Flag = ABSInstall
+          | AURInstall
           | SaveState
           | Cache
           | LogFile
           | DiffPkgbuilds
           | Devel
           | Customizepkg
+          | KeepSource
+          | BuildABSDeps
           | Debug
           | CacheBackup
           | Clean
     , Option ['B'] ["save"]      (NoArg SaveState)  (saveS lang)
     , Option ['C'] ["downgrade"] (NoArg Cache)      (downG lang)
     , Option ['L'] ["viewlog"]   (NoArg LogFile)    (viewL lang)
+    , Option ['M'] ["abssync"]   (NoArg ABSInstall) (absSy lang)
     , Option ['O'] ["orphans"]   (NoArg Orphans)    (orpha lang) ]
 
 auraOptions :: [OptDescr Flag]
               , ( ['u'], ["sysupgrade"],   Upgrade       )
               , ( ['w'], ["downloadonly"], Download      )
               , ( ['x'], ["unsuppress"],   Unsuppress    )
+              , ( [],    ["absdeps"],      BuildABSDeps  )
+              , ( [],    ["allsource"],    KeepSource    )
               , ( [],    ["auradebug"],    Debug         )
               , ( [],    ["custom"],       Customizepkg  )
               , ( [],    ["devel"],        Devel         )
 
 settingsFlags :: [Flag]
 settingsFlags = [ Unsuppress,NoConfirm,HotEdit,DiffPkgbuilds,Debug,Devel
-                , DelMDeps,Customizepkg,NoPowerPill ]
+                , DelMDeps,Customizepkg,NoPowerPill,KeepSource,BuildABSDeps ]
 
 filterSettingsFlags :: [Flag] -> [Flag]
 filterSettingsFlags []                 = []
 buildPath (BuildPath p : _) = p
 buildPath (_:fs) = buildPath fs
 
-suppressionStatus :: [Flag] -> Bool
-suppressionStatus = fishOutFlag [(Unsuppress,False)] True
-
-delMakeDepsStatus :: [Flag] -> Bool
-delMakeDepsStatus = fishOutFlag [(DelMDeps,True)] False
-
-confirmationStatus :: [Flag] -> Bool
+suppressionStatus  = fishOutFlag [(Unsuppress,False)] True
+delMakeDepsStatus  = fishOutFlag [(DelMDeps,True)] False
 confirmationStatus = fishOutFlag [(NoConfirm,False)] True
-
-hotEditStatus :: [Flag] -> Bool
-hotEditStatus = fishOutFlag [(HotEdit,True)] False
-
-pbDiffStatus :: [Flag] -> Bool
-pbDiffStatus = fishOutFlag [(DiffPkgbuilds,True)] False
-
-rebuildDevelStatus :: [Flag] -> Bool
+hotEditStatus      = fishOutFlag [(HotEdit,True)] False
+pbDiffStatus       = fishOutFlag [(DiffPkgbuilds,True)] False
 rebuildDevelStatus = fishOutFlag [(Devel,True)] False
-
-customizepkgStatus :: [Flag] -> Bool
 customizepkgStatus = fishOutFlag [(Customizepkg,True)] False
-
-noPowerPillStatus :: [Flag] -> Bool
-noPowerPillStatus = fishOutFlag [(NoPowerPill,True)] False
+noPowerPillStatus  = fishOutFlag [(NoPowerPill,True)] False
+keepSourceStatus   = fishOutFlag [(KeepSource,True)] False
+buildABSDepsStatus = fishOutFlag [(BuildABSDeps,True)] False
 
 parseLanguageFlag :: [String] -> (Maybe Language,[String])
 parseLanguageFlag args =
+{-
+
+Copyright 2012, 2013 Colin Woodbury <colingw@gmail.com>
+
+This file is part of Aura.
+
+Aura is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Aura is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Aura.  If not, see <http://www.gnu.org/licenses/>.
+
+-}
+
+-- Layer for agnostic package installation.
+-- Backend for `Aura.Commands.A` and `Aura.Commands.M`
+
+module Aura.Install
+    ( install
+    , reportPkgsToInstall ) where
+
+import Control.Monad (unless)
+import Data.List     (sort, (\\))
+
+import Aura.Pkgbuild.Records
+import Aura.Pkgbuild.Editing
+import Aura.Settings.Base
+import Aura.Dependencies
+import Aura.Colour.Text
+import Aura.Monad.Aura
+import Aura.Languages
+import Aura.Build
+import Aura.Utils
+import Aura.Core
+
+---
+
+-- | High level 'install' command. Handles installing
+-- dependencies.
+install, install' :: (Package p, Buildable b) =>
+                     (String -> Aura b)  -- ^ Constructor for Buildable data
+                  -> (Settings -> p -> Maybe ErrMsg)
+                  -> BuildHandle  -- ^ For filtering packages and building deps.
+                  -> [String]     -- ^ Pacman flags.
+                  -> [String]     -- ^ Package names.
+                  -> Aura ()
+install _ _ _ _ [] = return ()
+install custom subConflict bh pacOpts pkgs = ask >>= \ss ->
+  if not $ delMakeDeps ss
+     then install' custom subConflict bh pacOpts pkgs
+     else do  -- `-a` was used.
+       orphansBefore <- getOrphans
+       install' custom subConflict bh pacOpts pkgs
+       orphansAfter <- getOrphans
+       let makeDeps = orphansAfter \\ orphansBefore
+       unless (null makeDeps) $ notify removeMakeDepsAfter_1
+       removePkgs makeDeps pacOpts
+
+install' custom subConflict bh pacOpts pkgs = ask >>= \ss -> do
+  let toInstall = pkgs \\ ignoredPkgsOf ss
+      ignored   = pkgs \\ toInstall
+      mainPkgs  = initialPF bh
+  reportIgnoredPackages ignored
+  (_,okay,nons) <- badPkgCheck toInstall >>= divideByPkgType ignoreRepos mainPkgs
+  reportNonPackages nons
+  handler <- pbHandler
+  toBuild <- mapM custom okay >>= pkgbuildDiffs >>= handler
+  notify install_5
+  (subDeps,mainDeps) <- catch (depsToInstall subConflict bh toBuild) depCheckFailure
+  reportPkgsToInstall bh subDeps mainDeps toBuild
+  continue <- optionalPrompt install_3
+  if not continue
+     then scoldAndFail install_4
+     else do
+       unless (null subDeps) $ subBuild bh subDeps
+       storePkgbuilds $ toBuild ++ mainDeps
+       mapM_ (buildAndInstallDep pacOpts) mainDeps
+       buildPackages toBuild >>= installPkgFiles pacOpts
+
+-- | The user can handle PKGBUILDs in multiple ways.
+-- `--hotedit` takes the highest priority.
+pbHandler :: Buildable b => Aura ([b] -> Aura [b])
+pbHandler = ask >>= check
+    where check ss | mayHotEdit ss      = return hotEdit
+                   | useCustomizepkg ss = return customizepkg
+                   | otherwise          = return return
+
+badPkgCheck :: [String] -> Aura [String]
+badPkgCheck []     = return []
+badPkgCheck (p:ps) = ask >>= \ss ->
+  case p `lookup` wontBuildOf ss of
+    Nothing -> (p :) `fmap` badPkgCheck ps
+    Just r  -> do
+      scold $ badPkgCheck_1 p
+      putStrLnA yellow r
+      okay <- optionalPrompt badPkgCheck_2
+      if okay then (p :) `fmap` badPkgCheck ps else badPkgCheck ps
+
+depCheckFailure :: String -> Aura a
+depCheckFailure m = scold install_1 >> failure m
+
+buildAndInstallDep :: Buildable b => [String] -> b -> Aura ()
+buildAndInstallDep pacOpts pkg =
+  pbHandler >>= \h -> h [pkg] >>= buildPackages >>=
+  installPkgFiles ("--asdeps" : pacOpts)
+
+------------
+-- REPORTING
+------------
+reportPkgsToInstall :: (Package p1, Package p2, Package p3) =>
+                       BuildHandle -> [p1] -> [p2] -> [p3] -> Aura ()
+reportPkgsToInstall bh sd md mp = langOf `fmap` ask >>= \lang -> do
+  pl (reportPkgsToInstall_1    lang) (sort $ map pkgNameOf sd)
+  pl (reportPkgsToInstall_2 la lang) (sort $ map pkgNameOf md)
+  pl (reportPkgsToInstall_3 la lang) (sort $ map pkgNameOf mp)
+      where pl = printList green cyan
+            la = pkgLabel bh
+
+reportNonPackages :: [String] -> Aura ()
+reportNonPackages = badReport reportNonPackages_1
+
+reportIgnoredPackages :: [String] -> Aura ()
+reportIgnoredPackages pkgs = langOf `fmap` ask >>= \lang ->
+  printList yellow cyan (reportIgnoredPackages_1 lang) pkgs
+
+pkgbuildDiffs :: Buildable b => [b] -> Aura [b]
+pkgbuildDiffs [] = return []
+pkgbuildDiffs ps = ask >>= check
+    where check ss | not $ diffPkgbuilds ss = return ps
+                   | otherwise = mapM_ displayDiff ps >> return ps
+          displayDiff p = do
+            let name = pkgNameOf p
+            isStored <- hasPkgbuildStored name
+            if not isStored
+               then warn $ reportPkgbuildDiffs_1 name
+               else do
+                 let new = pkgbuildOf p
+                 old <- readPkgbuild name
+                 case comparePkgbuilds old new of
+                   "" -> notify $ reportPkgbuildDiffs_2 name
+                   d  -> do
+                      warn $ reportPkgbuildDiffs_3 name
+                      liftIO $ putStrLn $ d ++ "\n"

Aura/Languages.hs

 
 module Aura.Languages where
 
-import Aura.Colour.Text (cyan, green, red, blue, yellow)
+import Aura.Colour.Text (cyan, green, red, blue, yellow, magenta, bForeground)
 
 ---
 
 -----------------------
 -- Aura/Build functions
 -----------------------
-buildPackages_1 :: Language -> String -> String
-buildPackages_1 English    p = "Building " ++ bt p ++ "..."
-buildPackages_1 Japanese   p = bt p ++ "を作成中・・・"
-buildPackages_1 Polish     p = "Budowanie " ++ bt p ++ "..."
-buildPackages_1 Croatian   p = "Gradim " ++ bt p ++ "..."
-buildPackages_1 Swedish    p = "Bygger paket " ++ bt p ++ "..."
-buildPackages_1 German     p = "Baue Paket " ++ bt p ++ "..."
-buildPackages_1 Spanish    p = "Construyendo " ++ bt p ++ "..."
-buildPackages_1 Portuguese p = "Compilando " ++ bt p ++ "..."
-buildPackages_1 French     p = "Construction de " ++ bt p ++ "…"
-buildPackages_1 Russian    p = "Сборка " ++ bt p ++ "..."
-buildPackages_1 Italian    p = "Compilazione di " ++ bt p ++ "..."
-buildPackages_1 Serbian    p = "Градим " ++ bt p ++ "..."
-
-buildFail_1 :: Language -> String -> String
-buildFail_1 English    p = "Well, building " ++ bt p ++ " failed."
-buildFail_1 Japanese   p = bt p ++ "の作成は失敗したようだ。"
-buildFail_1 Polish     p = "Budowanie " ++ bt p ++ " zakończyło się niepowodzeniem."
-buildFail_1 Croatian   p = "Izgradnja " ++ bt p ++ " nije uspjela."
-buildFail_1 Swedish    p = "Det gick inte att bygga paketet " ++ bt p ++ "."
-buildFail_1 German     p = "Bauen von " ++ bt p ++ " ist fehlgeschlagen."
-buildFail_1 Spanish    p = "La construcción de " ++ bt p ++ " ha fallado."
-buildFail_1 Portuguese p = "Falha na compilação do pacote " ++ bt p ++ "."
-buildFail_1 French     p = "Bon, la construction de " ++ bt p ++ " a échouée."
-buildFail_1 Russian    p = "Что ж, сборка " ++ bt p ++ " не удалась."
-buildFail_1 Italian    p = "La compilazione di " ++ bt p ++ "è fallita."
-buildFail_1 Serbian    p = "Изградња пакета " ++ bt p ++ " није успела."
+buildPackages_1 :: String -> Language -> String
+buildPackages_1 p English    = "Building " ++ bt p ++ "..."
+buildPackages_1 p Japanese   = bt p ++ "を作成中・・・"
+buildPackages_1 p Polish     = "Budowanie " ++ bt p ++ "..."
+buildPackages_1 p Croatian   = "Gradim " ++ bt p ++ "..."
+buildPackages_1 p Swedish    = "Bygger paket " ++ bt p ++ "..."
+buildPackages_1 p German     = "Baue Paket " ++ bt p ++ "..."
+buildPackages_1 p Spanish    = "Construyendo " ++ bt p ++ "..."
+buildPackages_1 p Portuguese = "Compilando " ++ bt p ++ "..."
+buildPackages_1 p French     = "Construction de " ++ bt p ++ "…"
+buildPackages_1 p Russian    = "Сборка " ++ bt p ++ "..."
+buildPackages_1 p Italian    = "Compilazione di " ++ bt p ++ "..."
+buildPackages_1 p Serbian    = "Градим " ++ bt p ++ "..."
+
+buildFail_1 :: String -> Language -> String
+buildFail_1 p English    = "Well, building " ++ bt p ++ " failed."
+buildFail_1 p Japanese   = bt p ++ "の作成は失敗したようだ。"
+buildFail_1 p Polish     = "Budowanie " ++ bt p ++ " zakończyło się niepowodzeniem."
+buildFail_1 p Croatian   = "Izgradnja " ++ bt p ++ " nije uspjela."
+buildFail_1 p Swedish    = "Det gick inte att bygga paketet " ++ bt p ++ "."
+buildFail_1 p German     = "Bauen von " ++ bt p ++ " ist fehlgeschlagen."
+buildFail_1 p Spanish    = "La construcción de " ++ bt p ++ " ha fallado."
+buildFail_1 p Portuguese = "Falha na compilação do pacote " ++ bt p ++ "."
+buildFail_1 p French     = "Bon, la construction de " ++ bt p ++ " a échouée."
+buildFail_1 p Russian    = "Что ж, сборка " ++ bt p ++ " не удалась."
+buildFail_1 p Italian    = "La compilazione di " ++ bt p ++ "è fallita."
+buildFail_1 p Serbian    = "Изградња пакета " ++ bt p ++ " није успела."
 
 buildFail_2 :: Language -> String
 buildFail_2 English    = "Also, the following weren’t built:"
 ----------------------------
 -- Aura/Commands/A functions
 ----------------------------
-installPackages_1 :: Language -> String
-installPackages_1 English    = "Dependency checking failed for these reasons:"
-installPackages_1 Japanese   = "従属パッケージの確認は以下の理由で失敗した:"
-installPackages_1 Polish     = "Sprawdzanie zależności nie powiodło się z następujących powodów:"
-installPackages_1 Croatian   = "Provjera zavisnosti nije uspjela iz sljedećih razloga:"
-installPackages_1 Swedish    = "Beroende-kollen misslyckades pga följande skäl:"
-installPackages_1 German     = "Abhängigkeitsüberprüfung schlug Fehl aus folgenden Gründen:"
-installPackages_1 Spanish    = "La comprobación de dependencias falló por los siguientes motivos:"
-installPackages_1 Portuguese = "Não foi possível checar as dependências pelas seguintes razões:"
-installPackages_1 French     = "La vérification des dépendances a faillie pour les raisons suivantes :"
-installPackages_1 Russian    = "Проверка зависимостей не удалась из-за:"
-installPackages_1 Italian    = "Il controllo delle dipendenze è fallito per i seguenti motivi:"
-installPackages_1 Serbian    = "Провера зависности није успела из следећих разлога:"
-
-installPackages_2 :: Language -> String
-installPackages_2 English    = "No valid packages specified."
-installPackages_2 Japanese   = "適当なパッケージを入力してください。"
-installPackages_2 Polish     = "Nie podano prawidłowych pakietów."
-installPackages_2 Croatian   = "Nije specificiran nijedan ispravan paket."
-installPackages_2 Swedish    = "Inga giltiga paket valda."
-installPackages_2 German     = "Keine gültigen Pakete angegeben."
-installPackages_2 Spanish    = "No se ha especificado ningún paquete válido."
-installPackages_2 Portuguese = "Nenhum pacote válido foi especificado."
-installPackages_2 French     = "Aucun paquet valide spécifié."
-installPackages_2 Russian    = "Валидные пакеты не указаны."
-installPackages_2 Italian    = "Nessun pacchetto valido specificato."
-installPackages_2 Serbian    = "Ниједан исправан пакет није специфициран."
-
-installPackages_3 :: Language -> String
-installPackages_3 English    = "Continue?"
-installPackages_3 Japanese   = "続行?"
-installPackages_3 Polish     = "Kontynuować?"
-installPackages_3 Croatian   = "Nastavi?"
-installPackages_3 Swedish    = "Fortsätta?"
-installPackages_3 German     = "Fortsetzen?"
-installPackages_3 Spanish    = "¿Continuar?"
-installPackages_3 Portuguese = "Continuar?"
-installPackages_3 French     = "Continuer ?"
-installPackages_3 Russian    = "Продолжить?"
-installPackages_3 Italian    = "Continuare?"
-installPackages_3 Serbian    = "Наставити?"
-
-installPackages_4 :: Language -> String
-installPackages_4 English    = "Installation manually aborted."
-installPackages_4 Japanese   = "続行は意図的に阻止された。"
-installPackages_4 Polish     = "Instalacja została przerwana przez użytkownika."
-installPackages_4 Croatian   = "Instalacija prekinuta od strane korisnika."
-installPackages_4 Swedish    = "Installationen avbröts manuellt."
-installPackages_4 German     = "Installation durch Benutzer abgebrochen."
-installPackages_4 Spanish    = "Instalación abortada manualmente."
-installPackages_4 Portuguese = "Instalação manual abortada."
-installPackages_4 French     = "Installation manuelle annulée."
-installPackages_4 Russian    = "Пользователь прервал установку."
-installPackages_4 Italian    = "Installazione manuale interrotta."
-installPackages_4 Serbian    = "Инсталација је ручно прекинута."
-
-installPackages_5 :: Language -> String
-installPackages_5 English    = "Determining dependencies..."
-installPackages_5 Japanese   = "従属パッケージを確認中・・・"
-installPackages_5 Polish     = "Ustalanie zależności..."
-installPackages_5 Croatian   = "Određivanje zavisnosti..."
-installPackages_5 Swedish    = "Avgör beroenden..."
-installPackages_5 German     = "Bestimme Abhängigkeiten..."
-installPackages_5 Spanish    = "Determinando dependencias..."
-installPackages_5 Portuguese = "Determinando as dependências..."
-installPackages_5 French     = "Détermination des dépendances en cours…"
-installPackages_5 Russian    = "Определение зависимостей..."
-installPackages_5 Italian    = "Determinazione dipendenze..."
-installPackages_5 Serbian    = "Утврђивање зависности..."
+auraCheck_1 :: Language -> String
+auraCheck_1 Japanese = "Auraアップグレードあり。先にAuraだけを?"
+auraCheck_1 _        = "Aura update available. Update it first?"
+
+install_1 :: Language -> String
+install_1 English    = "Dependency checking failed for these reasons:"
+install_1 Japanese   = "従属パッケージの確認は以下の理由で失敗した:"
+install_1 Polish     = "Sprawdzanie zależności nie powiodło się z następujących powodów:"
+install_1 Croatian   = "Provjera zavisnosti nije uspjela iz sljedećih razloga:"
+install_1 Swedish    = "Beroende-kollen misslyckades pga följande skäl:"
+install_1 German     = "Abhängigkeitsüberprüfung schlug Fehl aus folgenden Gründen:"
+install_1 Spanish    = "La comprobación de dependencias falló por los siguientes motivos:"
+install_1 Portuguese = "Não foi possível checar as dependências pelas seguintes razões:"
+install_1 French     = "La vérification des dépendances a faillie pour les raisons suivantes :"
+install_1 Russian    = "Проверка зависимостей не удалась из-за:"
+install_1 Italian    = "Il controllo delle dipendenze è fallito per i seguenti motivi:"
+install_1 Serbian    = "Провера зависности није успела из следећих разлога:"
+
+install_2 :: Language -> String
+install_2 English    = "No valid packages specified."
+install_2 Japanese   = "適当なパッケージを入力してください。"
+install_2 Polish     = "Nie podano prawidłowych pakietów."
+install_2 Croatian   = "Nije specificiran nijedan ispravan paket."
+install_2 Swedish    = "Inga giltiga paket valda."
+install_2 German     = "Keine gültigen Pakete angegeben."
+install_2 Spanish    = "No se ha especificado ningún paquete válido."
+install_2 Portuguese = "Nenhum pacote válido foi especificado."
+install_2 French     = "Aucun paquet valide spécifié."
+install_2 Russian    = "Валидные пакеты не указаны."
+install_2 Italian    = "Nessun pacchetto valido specificato."
+install_2 Serbian    = "Ниједан исправан пакет није специфициран."
+
+install_3 :: Language -> String
+install_3 English    = "Continue?"
+install_3 Japanese   = "続行?"
+install_3 Polish     = "Kontynuować?"
+install_3 Croatian   = "Nastavi?"
+install_3 Swedish    = "Fortsätta?"
+install_3 German     = "Fortsetzen?"
+install_3 Spanish    = "¿Continuar?"
+install_3 Portuguese = "Continuar?"
+install_3 French     = "Continuer ?"
+install_3 Russian    = "Продолжить?"
+install_3 Italian    = "Continuare?"
+install_3 Serbian    = "Наставити?"
+
+install_4 :: Language -> String
+install_4 English    = "Installation manually aborted."
+install_4 Japanese   = "続行は意図的に阻止された。"
+install_4 Polish     = "Instalacja została przerwana przez użytkownika."
+install_4 Croatian   = "Instalacija prekinuta od strane korisnika."
+install_4 Swedish    = "Installationen avbröts manuellt."
+install_4 German     = "Installation durch Benutzer abgebrochen."
+install_4 Spanish    = "Instalación abortada manualmente."
+install_4 Portuguese = "Instalação manual abortada."
+install_4 French     = "Installation manuelle annulée."
+install_4 Russian    = "Пользователь прервал установку."
+install_4 Italian    = "Installazione manuale interrotta."
+install_4 Serbian    = "Инсталација је ручно прекинута."
+
+install_5 :: Language -> String
+install_5 English    = "Determining dependencies..."
+install_5 Japanese   = "従属パッケージを確認中・・・"
+install_5 Polish     = "Ustalanie zależności..."
+install_5 Croatian   = "Određivanje zavisnosti..."
+install_5 Swedish    = "Avgör beroenden..."
+install_5 German     = "Bestimme Abhängigkeiten..."
+install_5 Spanish    = "Determinando dependencias..."
+install_5 Portuguese = "Determinando as dependências..."
+install_5 French     = "Détermination des dépendances en cours…"
+install_5 Russian    = "Определение зависимостей..."
+install_5 Italian    = "Determinazione dipendenze..."
+install_5 Serbian    = "Утврђивање зависности..."
 
 -- NEEDS TRANSLATION
-knownBadPkgCheck_1 :: String -> Language -> String
-knownBadPkgCheck_1 p Japanese = bt p ++ "の作成は失敗すると知られている。理由:"
-knownBadPkgCheck_1 p Croatian = "Poznato je da se " ++ bt p ++ " neuspješno gradi. Razlog:"
-knownBadPkgCheck_1 p Serbian  = "Познато је да се " ++ bt p ++ " неуспешно гради. Разлог:"
-knownBadPkgCheck_1 p _        = bt p ++ " is known to fail at building. Reason:"
+badPkgCheck_1 :: String -> Language -> String
+badPkgCheck_1 p Japanese = bt p ++ "の作成は失敗すると知られている。理由:"
+badPkgCheck_1 p Croatian = "Poznato je da se " ++ bt p ++ " neuspješno gradi. Razlog:"
+badPkgCheck_1 p Serbian  = "Познато је да се " ++ bt p ++ " неуспешно гради. Разлог:"
+badPkgCheck_1 p _        = bt p ++ " is known to fail at building. Reason:"
 
 -- NEEDS TRANSLATION
-knownBadPkgCheck_2 :: Language -> String
-knownBadPkgCheck_2 Japanese = "それでもやってみる?"
-knownBadPkgCheck_2 Croatian = "Želite li ipak pokušati?"
-knownBadPkgCheck_2 Serbian  = "Желите ли ипак да пробате?"
-knownBadPkgCheck_2 _        = "Will you try anyway?"
+badPkgCheck_2 :: Language -> String
+badPkgCheck_2 Japanese = "それでもやってみる?"
+badPkgCheck_2 Croatian = "Želite li ipak pokušati?"
+badPkgCheck_2 Serbian  = "Желите ли ипак да пробате?"
+badPkgCheck_2 _        = "Will you try anyway?"
 
 -- NEEDS UPDATE TO REFLECT CHANGED ENGLISH
 reportNonPackages_1 :: Language -> String
 reportPkgsToInstall_1 Italian    = "Dipendenze nei repository:"
 reportPkgsToInstall_1 Serbian    = "Зависности из ризница:"
 
-reportPkgsToInstall_2 :: Language -> String
-reportPkgsToInstall_2 English    = "AUR dependencies:"
-reportPkgsToInstall_2 Japanese   = "AURの従属パッケージ:"
-reportPkgsToInstall_2 Polish     = "Zależności z AUR:"
-reportPkgsToInstall_2 Croatian   = "Zavisnosti iz AUR-a:"
-reportPkgsToInstall_2 Swedish    = "Beroenden ifrån AUR:"
-reportPkgsToInstall_2 German     = "Abhängigkeiten im AUR:"
-reportPkgsToInstall_2 Spanish    = "Dependencias en AUR:"
-reportPkgsToInstall_2 Portuguese = "Dependências no AUR:"
-reportPkgsToInstall_2 French     = "Dépendances AUR :"
-reportPkgsToInstall_2 Russian    = "Зависимости из AUR:"
-reportPkgsToInstall_2 Italian    = "Dipendenze in AUR:"
-reportPkgsToInstall_2 Serbian    = "Зависности из AUR-а:"
-
-reportPkgsToInstall_3 :: Language -> String
-reportPkgsToInstall_3 English    = "Main AUR packages:"
-reportPkgsToInstall_3 Japanese   = "主なAURパッケージ:"
-reportPkgsToInstall_3 Polish     = "Główne pakiety z AUR:"
-reportPkgsToInstall_3 Croatian   = "Glavni AUR paketi:"
-reportPkgsToInstall_3 Swedish    = "Huvudpaket ifrån AUR:"
-reportPkgsToInstall_3 German     = "Hauptpaket aus dem AUR:"
-reportPkgsToInstall_3 Spanish    = "Paquetes principales de AUR:"
-reportPkgsToInstall_3 Portuguese = "Pacotes principais do AUR:"
-reportPkgsToInstall_3 French     = "Principaux paquets AUR :"
-reportPkgsToInstall_3 Russian    = "Главные пакеты из AUR:"
-reportPkgsToInstall_3 Italian    = "Pacchetto principale di AUR"
-reportPkgsToInstall_3 Serbian    = "Главни пакети из AUR-а:"
+reportPkgsToInstall_2 :: String -> Language -> String
+reportPkgsToInstall_2 l English    = l ++ " dependencies:"
+reportPkgsToInstall_2 l Japanese   = l ++ "の従属パッケージ:"
+reportPkgsToInstall_2 l Polish     = "Zależności z " ++ l ++ ":"
+reportPkgsToInstall_2 l Croatian   = "Zavisnosti iz " ++ l ++ "-a:"
+reportPkgsToInstall_2 l Swedish    = "Beroenden ifrån " ++ l ++ ":"
+reportPkgsToInstall_2 l German     = "Abhängigkeiten im " ++ l ++ ":"
+reportPkgsToInstall_2 l Spanish    = "Dependencias en " ++ l ++ ":"
+reportPkgsToInstall_2 l Portuguese = "Dependências no " ++ l ++ ":"
+reportPkgsToInstall_2 l French     = "Dépendances " ++ l ++ " :"
+reportPkgsToInstall_2 l Russian    = "Зависимости из " ++ l ++ ":"
+reportPkgsToInstall_2 l Italian    = "Dipendenze in " ++ l ++ ":"
+reportPkgsToInstall_2 l Serbian    = "Зависности из " ++ l ++ "-а:"
+
+reportPkgsToInstall_3 :: String -> Language -> String
+reportPkgsToInstall_3 l English    = "Main " ++ l ++ " packages:"
+reportPkgsToInstall_3 l Japanese   = "主な" ++ l ++ "パッケージ:"
+reportPkgsToInstall_3 l Polish     = "Główne pakiety z " ++ l ++ ":"
+reportPkgsToInstall_3 l Croatian   = "Glavni " ++ l ++ " paketi:"
+reportPkgsToInstall_3 l Swedish    = "Huvudpaket ifrån " ++ l ++ ":"
+reportPkgsToInstall_3 l German     = "Hauptpaket aus dem " ++ l ++ ":"
+reportPkgsToInstall_3 l Spanish    = "Paquetes principales de " ++ l ++ ":"
+reportPkgsToInstall_3 l Portuguese = "Pacotes principais do " ++ l ++ ":"
+reportPkgsToInstall_3 l French     = "Principaux paquets " ++ l ++ " :"
+reportPkgsToInstall_3 l Russian    = "Главные пакеты из " ++ l ++ ":"
+reportPkgsToInstall_3 l Italian    = "Pacchetto principale di " ++ l ++ ":"
+reportPkgsToInstall_3 l Serbian    = "Главни пакети из " ++ l ++ "-а:"
 
 -- Needs translations.
 reportPkgbuildDiffs_1 :: String -> Language -> String
 reportNotInLog_1 Italian    = "Questo non apparirà nei file di log;"
 reportNotInLog_1 Serbian    = "Ови пакети се не спомињу у дневнику:"
 
+----------------------------
+-- Aura/Commands/M functions
+----------------------------
+-- NEEDS TRANSLATION
+cleanABSTree_1 :: Language -> String
+cleanABSTree_1 Japanese = "ABS Treeの中身を削除?"
+cleanABSTree_1 _        = "Delete the entire ABS Tree?"
+
+cleanABSTree_2 :: Language -> String
+cleanABSTree_2 Japanese = "ABS Treeの中身を削除中・・・"
+cleanABSTree_2 _        = "Clearing out ABS Tree..."
+
 ----------------------
 -- Aura/Flags functions
 ----------------------
 aurSy Italian    = green "Azioni riguardanti [A]UR.\n" ++ "Di default installa da AUR."
 aurSy Serbian    = green "Извршава радње везане за [A]UR.\n" ++ "Уобичајена радња инсталира из AUR-а."
 
+absSy :: Language -> String
+absSy _ = magenta "Perform actions involving the ABS tree.\n" ++ "Default action [M]anually builds from ABS."
+
 -- NEEDS TRANSLATION
 saveS :: Language -> String
 saveS Japanese = yellow "パッケージの設置状態に関する処理\n" ++ "デフォルトでインストール状態を保存する。"
 getAURPkgInfo_1 Serbian  = "Приступ AUR-у није успео. Проверите вашу везу."
 getAURPkgInfo_1 _        = "AUR API lookup failed. Please check your connection."
 
+-- `Maintainer` value NEEDS UPDATING!
 infoFields :: Language -> [String]
-infoFields English    = [ "Repository","Name","Version","AUR Status","Project URL","AUR URL","License", "Votes","Description" ]
-infoFields Japanese   = [ "リポジトリ","名前","バージョン","パッケージ状態","プロジェクト","パッケージページ","ライセンス","投票数","概要" ]
-infoFields Polish     = [ "Repository","Nazwa","Wersja","Status w AUR","URL Projektu","URL w AUR","Licencja","Głosy","Opis" ]
-infoFields Croatian   = [ "Repository","Ime","Verzija","AUR Stanje","URL Projekta","AUR URL","Licenca","Glasovi","Opis" ]
-infoFields Swedish    = [ "Repository","Namn","Version","AUR Status","Projekt URL","AUR URL","Licens","Röster","Beskrivning" ]
-infoFields German     = [ "Repository","Name","Version","AUR Status","Projekt URL","AUR URL","Lizenz","Stimmen","Beschreibung" ]
-infoFields Spanish    = [ "Repository","Nombre","Versión","Estado en AUR","URL del proyecto","URL en AUR","Licencia", "Votos","Descripción" ]
-infoFields Portuguese = [ "Repositório","Nome","Versão","Estado no AUR","URL do projeto","URL no AUR","Licença", "Votos","Descrição" ]
-infoFields French     = [ "Dépôt","Nom","Version","AUR Statut","URL du projet","URL AUR","License", "Votes","Description" ]
-infoFields Russian    = [ "Репозиторий","Название","Версия","Статус в AUR","URL проекта","URL в AUR","Лицензия", "Рейтинг","Описание" ]
-infoFields Italian    = [ "Repository","Nome","Versione","Stato in AUR","URL","URL AUR","Licenza","Voti","Descrizione" ]
-infoFields Serbian    = [ "Ризница","Име","Верзија","Статус у AUR-у","Страница пројекта","Страница у AUR-у","Лиценца","Гласови","Опис" ]
+infoFields English    = [ "Repository","Name","Version","AUR Status","Maintainer","Project URL","AUR URL","License", "Votes","Description" ]
+infoFields Japanese   = [ "リポジトリ","名前","バージョン","パッケージ状態","管理者","プロジェクト","パッケージページ","ライセンス","投票数","概要" ]
+infoFields Polish     = [ "Repository","Nazwa","Wersja","Status w AUR","Maintainer","URL Projektu","URL w AUR","Licencja","Głosy","Opis" ]
+infoFields Croatian   = [ "Repository","Ime","Verzija","AUR Stanje","Maintainer","URL Projekta","AUR URL","Licenca","Glasovi","Opis" ]
+infoFields Swedish    = [ "Repository","Namn","Version","AUR Status","Maintainer","Projekt URL","AUR URL","Licens","Röster","Beskrivning" ]
+infoFields German     = [ "Repository","Name","Version","AUR Status","Maintainer","Projekt URL","AUR URL","Lizenz","Stimmen","Beschreibung" ]
+infoFields Spanish    = [ "Repository","Nombre","Versión","Estado en AUR","Maintainer","URL del proyecto","URL en AUR","Licencia", "Votos","Descripción" ]
+infoFields Portuguese = [ "Repositório","Nome","Versão","Estado no AUR","Maintainer","URL do projeto","URL no AUR","Licença", "Votos","Descrição" ]
+infoFields French     = [ "Dépôt","Nom","Version","AUR Statut","Maintainer","URL du projet","URL AUR","License", "Votes","Description" ]
+infoFields Russian    = [ "Репозиторий","Название","Версия","Статус в AUR","Maintainer","URL проекта","URL в AUR","Лицензия", "Рейтинг","Описание" ]
+infoFields Italian    = [ "Repository","Nome","Versione","Stato in AUR","Maintainer","URL","URL AUR","Licenza","Voti","Descrizione" ]
+infoFields Serbian    = [ "Ризница","Име","Верзија","Статус у AUR-у","Maintainer","Страница пројекта","Страница у AUR-у","Лиценца","Гласови","Опис" ]
 
 outOfDateMsg :: Bool -> Language -> String
 outOfDateMsg True  English    = red "Out of Date!"
 outOfDateMsg True  Serbian    = red "Застарео!"
 outOfDateMsg False Serbian    = green "Ажуран"
 
+orphanedMsg :: Maybe String -> Language -> String
+orphanedMsg (Just m) _       = bForeground m
+orphanedMsg Nothing Japanese = red "いない"
+orphanedMsg Nothing _        = red "Orphaned!"
+
+-----------------------
+-- Aura/ABS functions
+-----------------------
+-- NEEDS TRANSLATION
+absSync_1 :: Language -> String
+absSync_1 Japanese = "ローカルABS Treeを同期?"
+absSync_1 _        = "Sync the local ABS Tree?"
+
+absSync_2 :: Language -> String
+absSync_2 Japanese = "ローカルABS Treeを同期中・・・"
+absSync_2 _        = "Syncing local ABS Tree..."
+
+singleSync_1 :: String -> Language -> String
+singleSync_1 p Japanese = bt p ++ "をABS Treeに同期・・・"
+singleSync_1 p _        = "Syncing " ++ bt p ++ " to the local ABS Tree..."
+
+absInfoFields :: Language -> [String]
+absInfoFields _ = [ "Repository","Name","Version","Depends On"
+                  , "Make Deps", "Description" ]
+
+pkgBuildKeyMissing :: Language -> String -> String
+pkgBuildKeyMissing _ key = "Unable to parse key " ++ key ++ " from PKGBUILD."
+
+missingDescription :: Language -> String
+missingDescription _ = "No description."
+
 -----------------------
 -- Aura/State functions
 -----------------------
 module Aura.MakePkg
     ( makepkgQuiet
     , makepkgVerbose
+    , makepkgSource
     , makepkgConfFile ) where
 
-import Control.Monad (liftM)
 import Text.Regex.PCRE ((=~))
+import Data.List (intercalate)
 
 import Aura.Monad.Aura
-import Aura.Shell (shellCmd, quietShellCmd', checkExitCode')
+import Aura.Shell (shellCmd, quietShellCmd, quietShellCmd', checkExitCode')
 
 import Shell (pwd, ls)
 
 makepkgConfFile :: FilePath
 makepkgConfFile = "/etc/makepkg.conf"
 
+-- | Make a source package.
+makepkgSource :: String -- ^ User
+              -> Bool -- ^ Include downloaded sources (--allsource)
+              -> Aura [FilePath]
+makepkgSource user allsource =
+  let allsourceOpt = if allsource then "--allsource" else "-S"
+      (cmd, opts) = determineRunStyle user [allsourceOpt]
+  in quietShellCmd cmd opts >> filter (=~ ".src.tar") `fmap` liftIO (pwd >>= ls)
+
 -- Builds a package with `makepkg`.
 -- Some packages create multiple .pkg.tar files. These are all returned.
 makepkgGen :: (String -> [String] -> Aura a) -> String -> Aura [FilePath]
 makepkgGen f user =
-    f cmd opts >> filter (=~ ".pkg.tar") `liftM` liftIO (pwd >>= ls)
-      where (cmd,opts) = determineRunStyle user
+    f cmd opts >> filter (=~ ".pkg.tar") `fmap` liftIO (pwd >>= ls)
+      where (cmd,opts) = determineRunStyle user []
 
-determineRunStyle :: String -> (String,[String])
-determineRunStyle "root" = ("makepkg",["--asroot"])
-determineRunStyle user   = ("su",[user,"-c","makepkg"])
+determineRunStyle :: String -> [String] -> (String,[String])
+determineRunStyle "root" opts = ("makepkg",["--asroot"] ++ opts)
+determineRunStyle user opts = ("su",[user,"-c","makepkg " ++ intercalate " " opts])
 
 makepkgQuiet :: String -> Aura [FilePath]
 makepkgQuiet user = makepkgGen quiet user

Aura/Monad/Aura.hs

 runAura : Unwraps an Aura action. Must be passed `Settings` as well.
 -}
 newtype Aura a = A { runA :: ErrorT AuraError (ReaderT Settings IO) a }
-    deriving (Monad, MonadError AuraError, MonadReader Settings, MonadIO)
+  deriving (Monad, MonadError AuraError, MonadReader Settings, MonadIO, Functor)
 
 -- This needs to be expanded.
 data AuraError = M String deriving (Eq,Show)
 
 instance Error AuraError where
-    noMsg  = M "No error message given."
+    noMsg  = strMsg "No error message given."
     strMsg = M
 
 runAura :: Aura a -> Settings -> IO (Either AuraError a)

Aura/Packages/ABS.hs

+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+
+-- Handles all ABS related functions.
+
+{-
+
+Copyright 2012, 2013
+Colin Woodbury <colingw@gmail.com>
+Nicholas Clarke <nicholas.clarke@sanger.ac.uk>
+
+This file is part of Aura.
+
+Aura is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Aura is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Aura.  If not, see <http://www.gnu.org/licenses/>.
+
+-}
+
+module Aura.Packages.ABS
+    ( ABSPkg
+    , absSync
+    , absTree
+    , absBasePath
+    , filterABSPkgs
+    , repoOf