packages feed

module-management-0.20.2: testdata/debian/Debian/Repo/Release.hs

-- | A release is a named collection of package indexes, e.g. sid.
{-# LANGUAGE FlexibleInstances, PackageImports, ScopedTypeVariables, StandaloneDeriving, TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Debian.Repo.Release
    ( Release(Release, releaseName, releaseAliases, releaseArchitectures, releaseComponents)
    , parseComponents
    , parseArchitectures
    , parseReleaseFile
    , getReleaseInfoRemote
    ) where

import Control.Applicative ((<$>), Applicative((<*>)))
import Control.Applicative.Error (Failing(Success, Failure))
import Control.Exception (ErrorCall(ErrorCall), Exception(toException), SomeException, try)
import Control.Monad.Trans (liftIO)
import Data.List (intercalate)
import Data.Maybe (catMaybes)
import Data.Set (Set, fromList)
import Data.Text (Text, unpack)
import qualified Data.Text as T (Text, unpack)
import qualified Data.Text.IO as T (readFile)
import Debian.Arch (Arch(..), parseArch)
import qualified Debian.Control.Text as T (Control'(Control), fieldValue, Paragraph, Paragraph', parseControl)
import Debian.Repo.Prelude.Verbosity (qPutStr)
import Debian.Release (parseReleaseName, parseSection', ReleaseName(..), releaseName', Section(..))
import Debian.URI (dirFromURI, fileFromURI, URI(uriPath), uriToString')
import Debian.UTF8 as Deb (decode)
import Prelude hiding (readFile)
import System.FilePath ((</>))
import Text.Regex (mkRegex, splitRegex)

-- |A file whose contents have been read into memory.
data File a = File { path :: Source, text :: Failing a }

data Source = LocalPath FilePath | RemotePath URI

readFile :: FilePath -> IO (File T.Text)
readFile x = File <$> return (LocalPath x) <*> (try (T.readFile x) >>= return . either (\ (e :: SomeException) -> Failure [show e]) Success)

instance Show Source where
    show (LocalPath p) = p
    show (RemotePath uri) = show uri

-- | A Debian Release is a named set of packages such as Jessie
-- (Debian version 7.0) or Precise (Ubuntu version 12.04.)  The
-- information here is obtained from the Release file in
-- dists/releasename.
data Release = Release { releaseName :: ReleaseName
                       , releaseAliases :: [ReleaseName]
                       , releaseArchitectures :: Set Arch
                       -- ^ e.g. amd64, i386, arm, etc, the set of architectures for this release - when a binary
                       -- package has architecture "all" or a source package has architecture "any" it means this.
                       , releaseComponents :: [Section] -- ^ Typically main, contrib, non-free
                       } deriving (Eq, Ord, Read, Show)

parseComponents :: Text -> [Section]
parseComponents compList =
    map parseSection' . splitRegex re . unpack  $ compList
    where
      re = mkRegex "[ ,]+"

parseReleaseFile :: FilePath -> ReleaseName -> [ReleaseName] -> IO Release
parseReleaseFile path' dist aliases =
    liftIO (readFile path') >>= return . parseRelease dist aliases

parseRelease :: ReleaseName -> [ReleaseName] -> File Text -> Release
parseRelease dist aliases file =
    case text file of
      Failure msgs -> error $ "Could not read " ++ show (path file) ++ ": " ++ show msgs
      Success t ->
          case T.parseControl (show (path file)) t of
            Left msg -> error $ "Failure parsing " ++ show (path file) ++ ": " ++ show msg
            Right (T.Control []) -> error $ "Empty release file: " ++ show (path file)
            Right (T.Control (info : _)) -> makeReleaseInfo (File {path = path file, text = Success info}) dist aliases

-- | Turn a parsed Release file into a Release
makeReleaseInfo :: File T.Paragraph -> ReleaseName -> [ReleaseName] -> Release
makeReleaseInfo file@(File {text = Failure msgs}) _dist _aliases =
    error $ "Failure reading " ++ show (path file) ++ ": " ++ show msgs
makeReleaseInfo file@(File {text = Success info}) dist aliases =
    case (T.fieldValue "Architectures" info, T.fieldValue "Components" info) of
      (Just archList, Just compList) ->
          Release { releaseName = dist
                  , releaseAliases = aliases
                  , releaseArchitectures = parseArchitectures archList
                  , releaseComponents = parseComponents compList }
      _ -> error $ "Missing Architectures or Components field in Release file " ++ show (path file)

parseArchitectures :: Text -> Set Arch
parseArchitectures archList =
    fromList . map parseArch . splitRegex re . unpack $ archList
    where
      re = mkRegex "[ ,]+"

-- |Get the list of releases of a remote repository.
getReleaseInfoRemote :: URI -> IO [Release]
getReleaseInfoRemote uri =
    qPutStr ("(verifying " ++ uriToString' uri ++ ".") >>
    dirFromURI distsURI >>=
    either (error . show) verify >>=
    return . catMaybes >>= 
    (\ result -> qPutStr ")\n" >> return result)
    where
      distsURI = uri {uriPath = uriPath uri </> "dists/"}
      verify names =
          do let dists = map parseReleaseName names
             (releaseFiles :: [File (T.Paragraph' Text)]) <- mapM getReleaseFile dists
             let releasePairs = zip3 (map getSuite releaseFiles) releaseFiles dists
             return $ map (uncurry3 getReleaseInfo) releasePairs
      releaseNameField releaseFile = case fmap T.unpack (T.fieldValue "Origin" releaseFile) of Just "Debian" -> "Codename"; _ -> "Suite"
      getReleaseInfo :: Maybe Text -> (File T.Paragraph) -> ReleaseName -> Maybe Release
      getReleaseInfo Nothing _ _ = Nothing
      getReleaseInfo (Just dist) _ relname | (parseReleaseName (T.unpack dist)) /= relname = Nothing
      getReleaseInfo (Just dist) info _ = Just $ makeReleaseInfo info (parseReleaseName (T.unpack dist)) []
      getSuite :: File (T.Paragraph' Text) -> Maybe Text
      getSuite (File {text = Success releaseFile}) = T.fieldValue (releaseNameField releaseFile) releaseFile
      getSuite (File {text = Failure msgs}) = fail (intercalate "\n" msgs)
      getReleaseFile :: ReleaseName -> IO (File (T.Paragraph' Text))
      getReleaseFile distName =
          do qPutStr "."
             release <- fileFromURI releaseURI
             let control = either Left (either (Left . toException . ErrorCall . show) Right . T.parseControl (show releaseURI) . Deb.decode) release
             case control of
               Right (T.Control [info :: T.Paragraph' Text]) -> return $ File {path = RemotePath releaseURI, text = Success info}
               _ -> error ("Failed to get release info from dist " ++ show (relName distName) ++ ", uri " ++ show releaseURI)
          where
            releaseURI = distURI {uriPath = uriPath distURI </> "Release"}
            distURI = distsURI {uriPath = uriPath distsURI </> releaseName' distName}
      uncurry3 :: (a -> b -> c -> d) -> (a, b, c) -> d
      uncurry3 f (a, b, c) =  f a b c