debian 3.93.5 → 3.95
raw patch · 83 files changed
+5051/−4857 lines, 83 filesdep +Cabaldep +QuickCheckdep +hostnamedep ~network
Dependencies added: Cabal, QuickCheck, hostname, lens, syb, th-lift, th-orphans
Dependency ranges changed: network
Files
- Debian/Apt/Dependencies.hs +0/−285
- Debian/Apt/Index.hs +0/−332
- Debian/Apt/Methods.hs +0/−486
- Debian/Apt/Package.hs +0/−54
- Debian/Arch.hs +0/−54
- Debian/Changes.hs +0/−309
- Debian/Control.hs +0/−76
- Debian/Control/Builder.hs +0/−161
- Debian/Control/ByteString.hs +0/−289
- Debian/Control/Common.hs +0/−186
- Debian/Control/Policy.hs +0/−180
- Debian/Control/String.hs +0/−110
- Debian/Control/Text.hs +0/−155
- Debian/Control/TextLazy.hs +0/−155
- Debian/Deb.hs +0/−24
- Debian/Extra/Files.hs +0/−31
- Debian/GenBuildDeps.hs +0/−330
- Debian/Loc.hs +0/−23
- Debian/Pretty.hs +0/−46
- Debian/Relation.hs +0/−25
- Debian/Relation/ByteString.hs +0/−30
- Debian/Relation/Common.hs +0/−132
- Debian/Relation/String.hs +0/−139
- Debian/Relation/Text.hs +0/−30
- Debian/Release.hs +0/−60
- Debian/Report.hs +0/−87
- Debian/Sources.hs +0/−323
- Debian/Time.hs +0/−27
- Debian/URI.hs +0/−99
- Debian/UTF8.hs +0/−29
- Debian/Util/FakeChanges.hs +0/−256
- Debian/Version.hs +0/−16
- Debian/Version/ByteString.hs +0/−18
- Debian/Version/Common.hs +0/−185
- Debian/Version/Internal.hs +0/−36
- Debian/Version/String.hs +0/−25
- Debian/Version/Text.hs +0/−18
- Test/Changes.hs +17/−16
- Test/Control.hs +7/−6
- changelog +21/−0
- debian.cabal +20/−9
- debian/changelog +21/−0
- src/Debian/Apt/Dependencies.hs +285/−0
- src/Debian/Apt/Index.hs +337/−0
- src/Debian/Apt/Methods.hs +486/−0
- src/Debian/Apt/Package.hs +54/−0
- src/Debian/Arch.hs +54/−0
- src/Debian/Changes.hs +309/−0
- src/Debian/Codename.hs +27/−0
- src/Debian/Control.hs +76/−0
- src/Debian/Control/Builder.hs +162/−0
- src/Debian/Control/ByteString.hs +289/−0
- src/Debian/Control/Common.hs +186/−0
- src/Debian/Control/Policy.hs +180/−0
- src/Debian/Control/String.hs +110/−0
- src/Debian/Control/Text.hs +155/−0
- src/Debian/Control/TextLazy.hs +155/−0
- src/Debian/Deb.hs +24/−0
- src/Debian/Extra/Files.hs +31/−0
- src/Debian/GenBuildDeps.hs +330/−0
- src/Debian/Loc.hs +23/−0
- src/Debian/Pretty.hs +48/−0
- src/Debian/Relation.hs +25/−0
- src/Debian/Relation/ByteString.hs +30/−0
- src/Debian/Relation/Common.hs +132/−0
- src/Debian/Relation/String.hs +139/−0
- src/Debian/Relation/Text.hs +30/−0
- src/Debian/Release.hs +48/−0
- src/Debian/Report.hs +88/−0
- src/Debian/Sources.hs +325/−0
- src/Debian/TH.hs +30/−0
- src/Debian/Time.hs +27/−0
- src/Debian/URI.hs +155/−0
- src/Debian/UTF8.hs +29/−0
- src/Debian/Util/FakeChanges.hs +256/−0
- src/Debian/VendorURI.hs +27/−0
- src/Debian/Version.hs +16/−0
- src/Debian/Version/ByteString.hs +18/−0
- src/Debian/Version/Common.hs +185/−0
- src/Debian/Version/Internal.hs +36/−0
- src/Debian/Version/String.hs +25/−0
- src/Debian/Version/Text.hs +18/−0
- utils/Report.hs +5/−5
− Debian/Apt/Dependencies.hs
@@ -1,285 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}-{-# OPTIONS -fno-warn-missing-signatures #-}-module Debian.Apt.Dependencies-{-- ( solve- , State- , binaryDepends- , search- , bj'- , bt- , CSP(..)- ) -} where---- test gutsyPackages "libc6" (\csp -> bt csp)--import Control.Arrow (second)-import qualified Data.ByteString.Char8 as C-import Data.List as List (find, union)-import Data.Tree (Tree(rootLabel, Node))-import Debian.Apt.Package (PackageNameMap, packageNameMap, lookupPackageByRel)-import Debian.Control.ByteString (ControlFunctions(stripWS, lookupP, parseControlFromFile),- Field'(Field, Comment), Control'(Control), Paragraph, Control)-import Debian.Relation (BinPkgName(..))-import Debian.Relation.ByteString (ParseRelations(..), Relation(..), OrRelation, AndRelation, Relations, checkVersionReq)-import Debian.Version (DebianVersion, parseDebianVersion, prettyDebianVersion)-import Debian.Version.ByteString ()-import Text.PrettyPrint (render)---- * Basic CSP Types and Functions--data Status- = Remaining AndRelation- | MissingDep Relation- | Complete- deriving (Eq)--type State a = (Status, [a])--complete :: State a -> Bool-complete (Complete, _) = True-complete _ = False--data CSP a- = CSP { pnm :: PackageNameMap a- , relations :: Relations- , depFunction :: (a -> Relations)- , conflicts :: a -> Relations- , packageVersion :: a -> (BinPkgName, DebianVersion)- }---- * Test CSP---- |TODO addProvides -- see DQL.Exec-controlCSP :: Control -> Relations -> (Paragraph -> Relations) -> CSP Paragraph-controlCSP (Control paragraphs) rels depF' =- CSP { pnm = packageNameMap getName paragraphs- , relations = rels- , depFunction = depF'- , conflicts = conflicts'- , packageVersion = packageVersionParagraph- }- where- getName :: Paragraph -> BinPkgName- getName p = case lookupP "Package" p of- Nothing -> error "Missing Package field"- Just (Field (_,n)) -> BinPkgName (C.unpack (stripWS n))- Just (Comment _) -> error "controlCSP"- conflicts' :: Paragraph -> Relations- conflicts' p =- case lookupP "Conflicts" p of- Nothing -> []- Just (Field (_, c)) -> either (error . show) id (parseRelations c)- Just (Comment _) -> error "controlCSP"--testCSP :: FilePath -> (Paragraph -> Relations) -> String -> (CSP Paragraph -> IO a) -> IO a-testCSP controlFile depf relationStr cspf =- do c' <- parseControlFromFile controlFile- case c' of- Left e -> error (show e)- Right control@(Control _) ->- case parseRelations relationStr of- Left e -> error (show e)- Right r ->- cspf (controlCSP control r depf)--depF :: Paragraph -> Relations-depF p =- let preDepends =- case lookupP "Pre-Depends" p of- Nothing -> []- Just (Field (_,pd)) ->- either (error . show) id (parseRelations pd)- Just (Comment _) -> error "depF"- depends =- case lookupP "Depends" p of- Nothing -> []- Just (Field (_,pd)) ->- either (error . show) id (parseRelations pd)- Just (Comment _) -> error "depF"- in- preDepends ++ depends--sidPackages = "/var/lib/apt/lists/ftp.debian.org_debian_dists_unstable_main_binary-i386_Packages"-gutsyPackages = "/var/lib/apt/lists/mirror.anl.gov_pub_ubuntu_dists_gutsy_main_binary-i386_Packages"--test controlFP rel labeler =- testCSP controlFP depF rel (mapM_ (\ (_,p) -> mapM_ (print . second (render . prettyDebianVersion) . packageVersionParagraph) p ) . take 1 . search labeler)---- TODO: add better errors-packageVersionParagraph :: Paragraph -> (BinPkgName, DebianVersion)-packageVersionParagraph p =- case lookupP "Package" p of- Nothing -> error $ "Paragraph missing Package field"- (Just (Field (_, name))) ->- case lookupP "Version" p of- Nothing -> error $ "Paragraph missing Version field"- (Just (Field (_, str))) ->- case parseDebianVersion str of- Right ver -> (BinPkgName (C.unpack (stripWS name)), ver)- Left e -> error $ "packageVersionParagraph: " ++ show e- (Just (Comment _)) -> error "packageVersionParagraph"- (Just (Comment _)) -> error "packageVersionParagraph"----conflict :: CSP p -> p -> p -> Bool-conflict csp p1 p2 =- let (name1, version1) = (packageVersion csp) p1- (name2, version2) = (packageVersion csp) p2- in- if name1 == name2- then version1 /= version2- else- any (conflict' (name1, version1)) (concat $ (conflicts csp) p2) ||- any (conflict' (name2, version2)) (concat $ (conflicts csp) p1)---- |JAS: deal with 'Provides' (can a package provide more than one package?)-conflict' :: (BinPkgName, DebianVersion) -> Relation -> Bool-conflict' (pName, pVersion) (Rel pkgName mVersionReq _) =- (pName == pkgName) && (checkVersionReq mVersionReq (Just pVersion))------ * Tree Helper Functions--mkTree :: a -> [Tree a] -> Tree a-mkTree = Node--label :: Tree a -> a-label = rootLabel--initTree :: (a -> [a]) -> a -> Tree a-initTree f a = Node a (map (initTree f) (f a))--mapTree :: (a -> b) -> Tree a -> Tree b-mapTree = fmap--foldTree :: (a -> [b] -> b) -> Tree a -> b-foldTree f (Node a ts) = f a (map (foldTree f) ts)--zipTreesWith :: (a -> b -> c) -> Tree a -> Tree b -> Tree c-zipTreesWith f (Node a ts) (Node b us) =- Node (f a b) (zipWith (zipTreesWith f) ts us)--prune :: (a -> Bool) -> Tree a -> Tree a-prune p = foldTree f- where f a ts = Node a (filter (not . p . label) ts)--leaves :: Tree a -> [a]-leaves = foldTree f- where f leaf [] = [leaf]- f _ ts = concat ts--inhTree :: (b -> a -> b) -> b -> Tree a -> Tree b-inhTree f b (Node a ts) = Node b' (map (inhTree f b') ts)- where b' = f b a--distrTree :: (a -> [b]) -> b -> Tree a -> Tree b-distrTree f b (Node a ts) = Node b (zipWith (distrTree f) (f a) ts)---- * mkSearchTree---- TODO: might want to leave markers about what relation we are satisfying?-mkSearchTree :: forall a. CSP a -> Tree (State a)-mkSearchTree csp =- Node (Remaining (relations csp),[]) (andRelation ([],[]) (relations csp))- where- andRelation :: ([a],AndRelation) -> AndRelation -> [Tree (State a)]- andRelation (candidates,[]) [] = [Node (Complete, candidates) []]- andRelation (candidates,remaining) [] = andRelation (candidates, []) remaining- andRelation (candidates, remaining) (x:xs) =- orRelation (candidates, xs ++ remaining) x- orRelation :: ([a],AndRelation) -> OrRelation -> [Tree (State a)]- orRelation acc x =- concat (fmap (relation acc) x)- relation :: ([a],AndRelation) -> Relation -> [Tree (State a)]- relation acc@(candidates,_) rel =- let packages = lookupPackageByRel (pnm csp) (packageVersion csp) rel in- case packages of- [] -> [Node (MissingDep rel, candidates) []]- _ -> map (package acc) packages- package :: ([a],AndRelation) -> a -> Tree (State a)- package (candidates, remaining) p =- if ((packageVersion csp) p) `elem` (map (packageVersion csp) candidates)- then if null remaining- then Node (Complete, candidates) []- else Node (Remaining remaining, candidates) (andRelation (candidates, []) remaining)- else Node (Remaining remaining, (p : candidates)) (andRelation ((p : candidates), remaining) ((depFunction csp) p))----- |earliestInconsistency does what it sounds like--- the 'reverse as' is because the vars are order high to low, but we--- want to find the lowest numbered (aka, eariest) inconsistency ??----earliestInconsistency :: CSP a -> State a -> Maybe ((BinPkgName, DebianVersion), (BinPkgName, DebianVersion))-earliestInconsistency _ (_,[]) = Nothing-earliestInconsistency _ (_,[_p]) = Nothing-earliestInconsistency csp (_,(p:ps)) =- case find ((conflict csp) p) (reverse ps) of- Nothing -> Nothing- (Just conflictingPackage) -> Just ((packageVersion csp) p, (packageVersion csp) conflictingPackage)---- * Conflict Set---- | conflicting packages and relations that require non-existant packages-type ConflictSet = ([(BinPkgName, DebianVersion)],[Relation])--isConflict :: ConflictSet -> Bool-isConflict ([],[]) = False-isConflict _ = True--solutions :: Tree (State a, ConflictSet) -> [State a]-solutions = filter complete . map fst . leaves . prune (isConflict . snd)--type Labeler a = CSP a -> Tree (State a) -> Tree (State a, ConflictSet)--search :: Labeler a -> CSP a -> [State a]-search labeler csp = (solutions . (labeler csp) . mkSearchTree) csp---- * Backtracking Labeler--bt :: Labeler a-bt csp = mapTree f- where- f s@(status,_) =- case status of- (MissingDep rel) -> (s, ([], [rel]))- _ ->- (s,- case (earliestInconsistency csp) s of- Nothing -> ([],[])- Just (a,b) -> ([a,b], []))---- * BackJumping Solver--{-|bj - backjumping labeler--If the node already has a conflict set, then leave it alone.--Otherwise, the conflictset for the node is the combination of the-conflict sets of its direct children.--}-bj :: CSP p -> Tree (State p, ConflictSet) -> Tree (State p, ConflictSet)-bj csp = foldTree f- where f (s, cs) ts- | isConflict cs = mkTree (s, cs) ts--- | isConflict cs' = mkTree (s, cs') [] -- prevent space leak- | otherwise = mkTree (s, cs') ts- where cs' =- let set = combine csp (map label ts) [] in- set `seq` set -- prevent space leak--unionCS :: [ConflictSet] -> ConflictSet-unionCS css = foldr (\(c1, m1) (c2, m2) -> ((c1 `union` c2), (m1 `union` m2))) ([],[]) css--combine :: CSP p -> [(State p, ConflictSet)] -> [ConflictSet] -> ConflictSet-combine _ [] acc = unionCS acc-combine csp ((s,cs@(c,m)):ns) acc- | (not (lastvar `elem` c)) && null m = cs- | null c && null m = ([],[]) -- is this case ever used?- | otherwise = combine csp ns ((c, m):acc)- where lastvar =- let (_,(p:_)) = s in (packageVersion csp) p--
− Debian/Apt/Index.hs
@@ -1,332 +0,0 @@-{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, OverloadedStrings, ScopedTypeVariables #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}-module Debian.Apt.Index- ( update- , Fetcher- , CheckSums(..)- , Compression(..)- , FileTuple- , Size- , controlFromIndex- , controlFromIndex'- , findContentsFiles- , findIndexes- , indexesInRelease- , tupleFromFilePath- ) where--import Control.Monad-import qualified Codec.Compression.GZip as GZip-import qualified Codec.Compression.BZip as BZip-import qualified Data.ByteString.Char8 as B-import qualified Data.ByteString.Lazy.Char8 as L-import qualified Data.Digest.Pure.MD5 as MD5-import qualified Data.Digest.Pure.SHA as SHA-import Data.Either (partitionEithers)-import Data.Function-import Data.List as List (null, intercalate, sortBy, isSuffixOf, isPrefixOf)-import qualified Data.Map as M-import Data.Monoid ((<>))-import Data.Text as Text (Text, unpack, concat, lines, null, words)-import Data.Time-import Debian.Apt.Methods-import Debian.Control (formatControl)-import Debian.Control.ByteString-import Debian.Control.Common-import Debian.Control.Text (decodeControl)-import Debian.Release-import Debian.Sources-import Debian.URI-import System.Directory-import System.FilePath ((</>))-import System.Posix.Files-import System.FilePath (takeBaseName)---import qualified System.Unix.Misc as Misc-import Text.ParserCombinators.Parsec.Error-import Text.PrettyPrint (render)-import Text.PrettyPrint.HughesPJClass (pPrint)-import Text.Read (readMaybe)-#if !MIN_VERSION_base(4,8,0)-import Control.Applicative (pure, (<$>), (<*>))-#endif---- |Package indexes on the server are uncompressed or compressed with--- gzip or bzip2. We do not know what will exist on the server until we--- actually look. This type is used to mark the compression status of--- what was actually found.-data Compression- = BZ2 | GZ | Uncompressed- deriving (Read, Show, Eq, Ord, Enum, Bounded)--data CheckSums- = CheckSums { md5sum :: Maybe String- , sha1 :: Maybe String- , sha256 :: Maybe String- }- deriving (Read, Show, Eq)---- |function-type for a function that downloads a file--- The timestamp is optional. If the local file is as new or newer--- than the remote copy, the download may be skipped.------ A good choice might be a partially parameterized call to--- 'Debian.Apt.Methods.fetch'-type Fetcher =- URI -> -- remote URI- FilePath -> -- local file name- Maybe UTCTime -> -- optional time stamp for local file- IO Bool -- True on success, False on failure---- |update - similar to apt-get update---- downloads the index files associated with a sources.list. The--- downloaded index files will have the same basenames that apt-get uses--- in \/var\/lib\/apt\/lists. You can almost use this function instead of--- calling apt-get update. However there are a few key differences:--- 1. apt-get update also updates the binary cache files--- 2. apt-get update uses the partial directory and lock file in\ /var\/lib\/apt\/lists--- 3. apt-get update downloads the Release and Release.gpg files-update :: Fetcher -- ^ function that will do actually downloading- -> FilePath -- ^ download indexes to the directory (must already exist)- -> String -- ^ binary architecture- -> [DebSource] -- ^ sources.list- -> IO [Maybe (FilePath, Compression)] -- ^ (basename of index file, compression status)-update fetcher basePath arch sourcesList =- mapM (uncurry $ fetchIndex fetcher) (map (\(uri, fp, _) -> (uri, (basePath </> fp))) (concatMap (indexURIs arch) sourcesList))---- | download possibly compressed files--- NOTE: index uri must not include the .bz2 or .gz extension-fetchIndex :: Fetcher -- ^ function that will do the actual fetch- -> URI -- ^ remote URI of package index, without .bz2 or .gz extension- -> FilePath -- ^ name to save downloaded file as, without .bz2 or .gz extension- -> IO (Maybe (FilePath, Compression)) -- ^ (downloaded file name + extension, compression status)-fetchIndex fetcher uri localPath =- do let localPath' = localPath ++ ".bz2"- --lm <- getLastModified localPath'- res <- fetcher (uri { uriPath = (uriPath uri) ++ ".bz2" }) localPath' Nothing- if res- then return $ Just (localPath', BZ2)- else do let localPath' = localPath ++ ".gz"- lm <- getLastModified localPath'- res <- fetcher (uri { uriPath = (uriPath uri) ++ ".gz" }) localPath' lm- if res- then return $ Just (localPath', GZ)- else do lm <- getLastModified localPath- res <- fetcher (uri { uriPath = (uriPath uri) }) localPath lm- if res- then return (Just (localPath, Uncompressed))- else return Nothing---- |examine a DebSource line, and calculate for each section:--- - the URI to the uncompressed index file--- - the basename that apt-get would name the downloaded index--- FIXME: ExactPath dist will fail with error at runtime :(-indexURIs :: String -- ^ which binary architecture- -> DebSource -- ^ line from sources.list- -> [(URI, FilePath, DebSource)] -- ^ (remote uri, local name, deb source for just this section)-indexURIs arch debSource =- map (\ section -> let (uri, fp) = calcPath (sourceType debSource) arch baseURI release section- in (uri,fp, debSource { sourceDist = (Right (release, [section])) }) ) sections- where- baseURI = sourceUri debSource- (release, sections) =- either (error $ "indexURIs: support not implemented for exact path: " ++ render (pPrint debSource)) id (sourceDist debSource)---- |return a tuple for the section--- - the URI to the uncompressed index file--- - the basename that apt-get uses for the downloaded index--- FIXME: support for Release and Release.gpg-calcPath :: SourceType -- ^ do we want Packages or Sources- -> String -- ^ The binary architecture to use for Packages- -> URI -- ^ base URI as it appears in sources.list- -> ReleaseName -- ^ the release (e.g., unstable, testing, stable, sid, etc)- -> Section -- ^ the section (main, contrib, non-free, etc)- -> (URI, [Char]) -- ^ (uri to index file, basename for the downloaded file)-calcPath srcType arch baseURI release section =- let indexPath = case srcType of- DebSrc -> "source/Sources"- Deb -> "binary-" ++ arch </> "Packages"- path = (uriPath baseURI) </> "dists" </> (releaseName' release) </> sectionName' section </> indexPath- in (baseURI { uriPath = path }, addPrefix . escapePath $ path)- where- addPrefix s = prefix scheme user' pass' reg port ++ {- "_" ++ -} s- prefix "http:" (Just user) Nothing (Just host) port = user ++ host ++ port- prefix "http:" _ _ (Just host) port = host ++ port- prefix "ftp:" _ _ (Just host) _ = host- prefix "file:" Nothing Nothing Nothing "" = ""- prefix "ssh:" (Just user) Nothing (Just host) port = user ++ host ++ port- prefix "ssh:" _ _ (Just host) port = host ++ port- prefix _ _ _ _ _ = error ("calcPath: unsupported uri: " ++ uriToString' baseURI)- user' = maybeOfString user- pass' = maybeOfString pass- (user, pass) = break (== ':') userpass- userpass = maybe "" uriUserInfo auth- reg = maybeOfString $ maybe "" uriRegName auth- port = maybe "" uriPort auth- scheme = uriScheme baseURI- auth = uriAuthority baseURI- --path = uriPath baseURI-- escapePath :: String -> String- escapePath s = intercalate "_" $ wordsBy (== '/') s-- maybeOfString :: String -> Maybe String- maybeOfString "" = Nothing- maybeOfString s = Just s-- wordsBy :: Eq a => (a -> Bool) -> [a] -> [[a]]- wordsBy p s =- case (break p s) of- (s, []) -> [s]- (h, t) -> h : wordsBy p (drop 1 t)---- |Parse a possibly compressed index file.-controlFromIndex :: Compression -> FilePath -> L.ByteString -> Either ParseError (Control' Text)-controlFromIndex GZ path s = either Left (Right . decodeControl) . parseControl path . B.concat . L.toChunks . GZip.decompress $ s-controlFromIndex BZ2 path s = either Left (Right . decodeControl) . parseControl path . B.concat . L.toChunks . BZip.decompress $ s-controlFromIndex Uncompressed path s = either Left (Right . decodeControl) . parseControl path . B.concat . L.toChunks $ s---- |parse an index possibly compressed file-controlFromIndex' :: Compression -> FilePath -> IO (Either ParseError (Control' Text))-controlFromIndex' compression path = L.readFile path >>= return . controlFromIndex compression path--type Size = Integer-type FileTuple = (CheckSums, Size, FilePath)---- |A release file contains a list of indexes (Packages\/Sources). Each--- Package or Source index may appear multiple times because it may be--- compressed several different ways. This function will return an--- assoc list where the key is the name of the uncompressed package--- index name and the value is the list of (file, compression) which--- decompress to the key.-groupIndexes :: [FileTuple] -> [(FilePath, [(FileTuple, Compression)])]-groupIndexes indexFiles =- M.toList $ M.fromListWith combine $ map makeKV indexFiles- where- makeKV fileTuple@(_,_,fp) =- let (name, compressionMethod) = uncompressedName fp- in- (name, [(fileTuple, compressionMethod)])- combine = (\x y -> sortBy (compare `on` snd) (x ++ y))-{-- with t@(_,_,fp) m =- let (un, compression) =- in- M.insertWith--}--{--groupIndexes' :: String ->[FileTuple] -> [(FilePath, [(FileTuple, Compression)])]-groupIndexes' iType indexFiles =- M.toList (foldr (insertType iType) M.empty indexFiles)- where- insertType iType t@(_,_,fp) m =- case uncompressedName' iType fp of- Nothing -> m- (Just (un, compression)) ->- M.insertWith (\x y -> sortBy (compare `on` snd) (x ++ y)) un [(t, compression)] m--}---- |The release file contains the checksums for the uncompressed--- package indexes, even if the uncompressed package indexes are not--- stored on the server. This function returns the list of files that--- actually exist.-filterExists :: FilePath -> (FilePath, [(FileTuple, Compression)]) -> IO (FilePath, [(FileTuple, Compression)])-filterExists distDir (fp, alternatives) =- do e <- filterM ( \((_,_,fp),_) -> fileExist (distDir </> fp)) alternatives- -- when (null e) (error $ "None of these files exist: " ++ show alternatives)- return (fp, e)--findIndexes :: FilePath -> String -> [FileTuple] -> IO [(FileTuple, Compression)]-findIndexes distDir iType controlFiles =- let indexes = groupIndexes controlFiles- in- do indexes' <- mapM (filterExists distDir) (filter (isType iType) indexes)- return $ map (head . snd) (filter (not . List.null . snd) indexes')- where- isType iType (fp, _) = iType `isSuffixOf` fp--{--findIndexes' :: FilePath -> String -> [FileTuple] -> IO [(FileTuple, Compression)]-findIndexes' distDir iType controlFiles =- let m = groupIndexes' iType controlFiles- in- do m' <- mapM (filterExists distDir) m- return $ map (head . snd) (filter (not . null . snd) m')--}-- -- insertType :: String -> (CheckSums, Integer, FilePath) -> M.Map FilePath ((CheckSums, Integer, FilePath), Compression) -> M.Map FilePath ((CheckSums, Integer, FilePath), Compression)--{--uncompressedName' :: String -> FilePath -> Maybe (FilePath, Compression)-uncompressedName' iType fp- | isSuffixOf iType fp = Just (fp, Uncompressed)- | isSuffixOf (iType ++".gz") fp = Just (reverse . (drop 3) . reverse $ fp, GZ)- | isSuffixOf (iType ++".bz2") fp = Just (reverse . (drop 4) . reverse $ fp, BZ2)- | otherwise = Nothing--}--uncompressedName :: FilePath -> (FilePath, Compression)-uncompressedName fp- | isSuffixOf ".gz" fp = (reverse . (drop 3) . reverse $ fp, GZ)- | isSuffixOf ".bz2" fp = (reverse . (drop 4) . reverse $ fp, BZ2)- | otherwise = (fp, Uncompressed)--indexesInRelease :: (FilePath -> Bool)- -> Control' Text -- ^ A release file- -> [(CheckSums, Integer, FilePath)] -- ^-indexesInRelease filterp (Control [p]) =- -- In a release file we should find one or more of the fields- -- "SHA256", "SHA1", or "MD5Sum", each containing a list of triples- either error (filter (\(_,_,fp) -> filterp fp)) $- msum [either Left (makeTuples makeSHA256) (maybe (Left "No SHA256 Field") makeTriples $ fieldValue "SHA256" p),- either Left (makeTuples makeSHA1) (maybe (Left "No SHA1 Field") makeTriples $ fieldValue "SHA1" p),- either Left (makeTuples makeMD5) (maybe (Left "No MD5Sum Field") makeTriples $ msum [fieldValue "MD5Sum" p,- fieldValue "Md5Sum" p,- fieldValue "MD5sum" p])]- where- makeSHA256 s = CheckSums {md5sum = Nothing, sha1 = Nothing, sha256 = Just s}- makeSHA1 s = CheckSums {md5sum = Nothing, sha1 = Just s, sha256 = Nothing}- makeMD5 s = CheckSums {md5sum = Just s, sha1 = Nothing, sha256 = Nothing}-- makeTuples :: (String -> CheckSums) -> [(Text, Text, Text)] -> Either String [(CheckSums, Integer, FilePath)]- makeTuples mk triples =- case partitionEithers (fmap (makeTuple mk) triples) of- ([], tuples) -> Right tuples- (s : _, _) -> Left s-- makeTuple :: (String -> CheckSums) -> (Text, Text, Text) -> Either String (CheckSums, Integer, FilePath)- makeTuple mk (sum, size, fp) =- (,,) <$> pure (mk (Text.unpack sum))- <*> maybe (Left ("Invalid size field: " ++ show size)) Right (readMaybe (Text.unpack size))- <*> pure (Text.unpack fp)-- makeTriples :: Text -> Either String [(Text, Text, Text)]- makeTriples t = case partitionEithers (map makeTriple (Text.lines t)) of- ([], xs) -> Right xs- (s : _, _) -> Left s-- makeTriple :: Text -> Either String (Text, Text, Text)- makeTriple t = case Text.words t of- [a, b, c] -> Right (a, b, c)- _ -> Left ("Invalid checksum line: " ++ show t)-indexesInRelease _ x = error $ "Invalid release file: " <> Text.unpack (Text.concat (formatControl x))---- |make a FileTuple for a file found on the local disk--- returns 'Nothing' if the file does not exist.-tupleFromFilePath :: FilePath -> FilePath -> IO (Maybe FileTuple)-tupleFromFilePath basePath fp =- do e <- fileExist (basePath </> fp)- if not e- then return Nothing- else do size <- getFileStatus (basePath </> fp) >>= return . fromIntegral . fileSize- md5 <- L.readFile (basePath </> fp) >>= return . show . MD5.md5- sha1 <- L.readFile (basePath </> fp) >>= return . show . SHA.sha1- sha256 <- L.readFile (basePath </> fp) >>= return . show . SHA.sha256- return $ Just (CheckSums { md5sum = Just md5, sha1 = Just sha1, sha256 = Just sha256 }, size, fp)---- |find the Contents-* files. These are not listed in the Release file-findContentsFiles :: (FilePath -> Bool) -> FilePath -> IO [FilePath]-findContentsFiles filterP distDir =- do files <- getDirectoryContents distDir- return $ filter filterP $ filter (isPrefixOf "Contents-" . takeBaseName) files
− Debian/Apt/Methods.hs
@@ -1,486 +0,0 @@-{-# LANGUAGE PackageImports #-}-{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-name-shadowing #-}--- |an interface for using the methods in /var/lib/apt/methods-module Debian.Apt.Methods- ( withMethodPath- , withMethodURI- , whichMethodPath- , openMethod- , closeMethod- , recvStatus- , sendCommand- , getLastModified- , simpleFetch- , fetch- , FetchCallbacks(..)- , emptyFetchCallbacks- , cliFetchCallbacks- , Command(..)- , Status(..)- , Message, Site, User, Password, Media, Drive, Header, ConfigItem- )- where--import Debian.Time-import Debian.URI--import Control.Exception-import "mtl" Control.Monad.Error-import Data.Maybe-import Data.Time-import System.Directory-import System.Exit-import System.IO-import System.Posix.Files-import System.Process--type MethodHandle = (Handle, Handle, Handle, ProcessHandle)--capabilities, logMsg, status, uriStart, uriDone, uriFailure, generalFailure, authorizationRequired, mediaFailure, uriAcquire, configuration, authorizationCredentials, mediaChanged :: String-capabilities = "100"-logMsg = "101"-status = "102"-uriStart = "200"-uriDone = "201"-uriFailure = "400"-generalFailure = "401"-authorizationRequired = "402"-mediaFailure = "403"-uriAcquire = "600"-configuration = "601"-authorizationCredentials = "602"-mediaChanged = "603"--type Message = String-type Site = String-type User = String-type Password = String-type Media = String-type Drive = String--data Status- = Capabilities { version :: String, singleInstance :: Bool, preScan :: Bool, pipeline :: Bool, sendConfig :: Bool- , needsCleanup :: Bool, localOnly :: Bool }- | LogMsg Message- | Status URI Message- | URIStart { uri :: URI, size :: Maybe Integer, lastModified :: Maybe UTCTime, resumePoint :: Maybe Integer }- | URIDone { uri :: URI, size :: Maybe Integer, lastModified :: Maybe UTCTime, resumePoint :: Maybe Integer- , filename :: Maybe FilePath, hashes :: Hashes, imsHit :: Bool }- | URIFailure { uri :: URI, message :: Message }- | GeneralFailure Message- | AuthorizationRequired Site- | MediaFailure Media Drive- deriving (Show, Eq)--data Hashes- = Hashes { md5 :: Maybe String- , sha1 :: Maybe String- , sha256 :: Maybe String- }- deriving (Show, Eq)--emptyHashes = Hashes Nothing Nothing Nothing--data Command- = URIAcquire URI FilePath (Maybe UTCTime)- | Configuration [ConfigItem]- | AuthorizationCredentials Site User Password- | MediaChanged Media (Maybe Bool) -- I don't really understand the Fail field, I am assuming it is 'Fail: true'- deriving (Show, Eq)--type Header = (String, String)-type ConfigItem = (String, String)--withMethodURI :: URI -> (MethodHandle -> IO a) -> IO a-withMethodURI uri f =- do mp <- liftM fromJust (whichMethodPath uri)- withMethodPath mp f---- |withMethod - run |methodPath| bracketed with--- openMethod\/closeMethod. |f| gets the open handle.-withMethodPath :: FilePath -> (MethodHandle -> IO a) -> IO a-withMethodPath methodPath f =- bracket (openMethod methodPath) closeMethod $ f---- |whichMethodBinary - find the method executable associated with a URI--- throws an exception on failure-whichMethodPath :: URI -> IO (Maybe FilePath)-whichMethodPath uri =- let scheme = init (uriScheme uri)- path = "/usr/lib/apt/methods/" ++ scheme- in- doesFileExist path >>= return . bool Nothing (Just path)--{--The flow of messages starts with the method sending out a-100 Capabilities and APT sending out a 601 Configuration.--The flow is largely unsynchronized, but our function may have to-respond to things like authorization requests. Perhaps we do a-recvContents and then mapM_ over that ? Not all incoming messages-require a response, so...---}--parseStatus :: [String] -> Status-parseStatus [] = error "parseStatus"-parseStatus (code' : headers') =- parseStatus' (take 3 code') (map parseHeader headers')- where- parseStatus' code headers- | code == capabilities =- foldr updateCapability defaultCapabilities headers- where- updateCapability (a,v) c- | a == "Version" = c { version = v }- | a == "Single-Instance" = c { singleInstance = parseTrueFalse v }- | a == "Pre-Scan" = c { preScan = parseTrueFalse v }- | a == "Pipeline" = c { pipeline = parseTrueFalse v }- | a == "Send-Config" = c { sendConfig = parseTrueFalse v }- | a == "Needs-Cleanup" = c { needsCleanup = parseTrueFalse v }- | a == "Local-Only" = c { localOnly = parseTrueFalse v }- | otherwise = error $ "unknown capability: " ++ show (a,v)- defaultCapabilities =- Capabilities { version = ""- , singleInstance = False- , preScan = False- , pipeline = False- , sendConfig = False- , needsCleanup = False- , localOnly = False- }- parseStatus' code headers- | code == logMsg =- case headers of- [("Message", msg)] -> LogMsg msg- _ -> error "parseStatus'"- | code == status =- Status (fromJust $ parseURI $ fromJust $ lookup "URI" headers) (fromJust $ lookup "Message" headers)- | code == uriStart =- foldr updateUriStart (URIStart undefined Nothing Nothing Nothing) headers- where- updateUriStart (a,v) u- | a == "URI" = u { uri = fromJust $ parseURI v }- | a == "Size" = u { size = Just (read v) }- | a == "Last-Modified" = u { lastModified = parseTimeRFC822 v } -- if the date is unparseable, we silently truncate. Is that bad ?- | a == "Resume-Point" = u { resumePoint = Just (read v) }- updateUriStart _ _ = error "updateUriStart"- parseStatus' code headers- | code == uriDone =- foldr updateUriDone (URIDone undefined Nothing Nothing Nothing Nothing emptyHashes False) headers- where- updateUriDone (a,v) u- | a == "URI" = u { uri = fromJust $ parseURI v }- | a == "Size" = u { size = Just (read v) }- | a == "Last-Modified" = u { lastModified = parseTimeRFC822 v } -- if the date is unparseable, we silently truncate. Is that bad ?- | a == "Filename" = u { filename = Just v }- | a == "MD5Sum-Hash" = u { hashes = (hashes u) { md5 = Just v } }- | a == "MD5-Hash" = u { hashes = (hashes u) { md5 = Just v } }- | a == "SHA1-Hash" = u { hashes = (hashes u) { sha1 = Just v } }- | a == "SHA256-Hash" = u { hashes = (hashes u) { sha256 = Just v } }- | a == "Resume-Point" = u { resumePoint = Just (read v) }- | a == "IMS-Hit" && v == "true" = u { imsHit = True }- | otherwise = error $ "updateUriDone: unknown header: " ++ show (a,v)- parseStatus' code headers- | code == uriFailure =- URIFailure (fromJust $ parseURI $ fromJust $ lookup "URI" headers) (fromJust $ lookup "Message" headers)- | code == generalFailure =- GeneralFailure (fromJust $ lookup "Message" headers)- | code == authorizationRequired =- AuthorizationRequired (fromJust $ lookup "Site" headers)- | code == mediaFailure =- MediaFailure (fromJust $ lookup "Media" headers) (fromJust $ lookup "Drive" headers)- parseStatus' _ _ = error "parseStatus'"--formatCommand :: Command -> [String]-formatCommand (URIAcquire uri filepath mLastModified) =- [ uriAcquire ++ " URI Acquire"- , "URI: " ++ uriToString' uri -- will this get credentials correct ? Or do we always pass those in seperately- , "FileName: " ++ filepath- ] ++ maybe [] (\lm -> ["Last-Modified: " ++ formatTimeRFC822 lm ]) mLastModified-formatCommand (Configuration configItems) =- (configuration ++ " Configuration") : (map formatConfigItem configItems)- where- formatConfigItem (a,v) = concat ["Config-Item: ", a, "=", v]-formatCommand (AuthorizationCredentials site user passwd) =- (authorizationCredentials ++ " Authorization Credentials") :- [ "Site: " ++ site- , "User: " ++ user- , "Password: " ++ passwd- ]-formatCommand (MediaChanged media mFail) =- [ mediaChanged ++ " Media Changed"- , "Media: " ++ media- ] ++ maybe [] (\b -> ["Fail: " ++ case b of True -> "true" ; False -> "false"]) mFail---parseTrueFalse :: String -> Bool-parseTrueFalse "true" = True-parseTrueFalse "false" = False-parseTrueFalse s = error $ "Invalid boolean string: " ++ s---recvStatus :: MethodHandle -> IO Status-recvStatus mh = liftM parseStatus $ recv mh--sendCommand :: MethodHandle -> Command -> IO ()-sendCommand mh cmd = sendMethod mh (formatCommand cmd)---parseHeader :: String -> Header-parseHeader str =- let (a, r) = span (/= ':') str- v = dropWhile (flip elem ": \t") r- in- (a, v)--openMethod :: FilePath -> IO MethodHandle-openMethod methodBinary =- do- -- hPutStrLn stderr ("openMethod " ++ methodBinary)- runInteractiveCommand methodBinary- -- runInteractiveProcess methodBinary [] Nothing Nothing--sendMethod :: MethodHandle -> [String] -> IO ()-sendMethod (pIn, _pOut, _, _) strings =- do- -- hPutStrLn stderr "send:"- mapM_ put strings- hPutStrLn pIn ""- hFlush pIn- where- put line =- do- -- hPutStrLn stderr (" " ++ line)- hPutStrLn pIn line--closeMethod :: MethodHandle -> IO ExitCode-closeMethod (pIn, pOut, pErr, handle) =- do- -- hPutStrLn stderr "closeMethod"- hClose pIn- hClose pOut- hClose pErr- waitForProcess handle--recv :: MethodHandle -> IO [String]-recv (_pIn, pOut, _pErr, _pHandle) =- do- -- hPutStrLn stderr "recv:"- readTillEmptyLine pOut- where- readTillEmptyLine pOut =- do- line <- hGetLine pOut- case line of- "" -> return []- line ->- do- -- hPutStrLn stderr (" " ++ line)- tail <- readTillEmptyLine pOut- return $ line : tail-{--The flow of messages starts with the method sending out a-<em>100 Capabilities</> and APT sending out a <em>601 Configuration</>.--The flow is largely unsynchronized, but our function may have to-respond to things like authorization requests. Perhaps we do a-recvContents and then mapM_ over that ? Not all incoming messages-require a response.--We probably also need to track state, for example, if we are-pipelining multiple downloads and want to show seperate progress bars-for each download.--If someone wants to use fetch, they will need to provide methods to:-- 1. prompt for and provide authentication- 2. show progress- 3. show media change dialog- 4. Show log messages- 5. Show failures- 6. Send Configuration--pipeline vs non-pipeline mode.-what if different methods are being used ?--when pipelining, we probably don't want to have too many pipelines to-the same server. Perhaps there can be a limit, and for non-pipelinable-methods, we set the limit to 1.--Each method can run in a seperate thread, since methods do not-interact with each other. In fact, each unique method+uri can be a-seperate thread. We can use a MVar to track the global max download-count. Perhaps we also want a per host throttle, since it is the host-connect that is likely to max out, not the access method.--Plan:--partition fetches by (host,method).-fork off threads for each (host, method).-Use MVar to throttle per host, and total connections--We don't know if a method supports pipelining until we connect atleast-once. So if we have a non-pipelined method, we might want to start-multiple streams. On the other hand, for something like a CDROM, that-will just cause the system to thrash.--cdrom, file, etc, don't have a host, so that is not a unique key then.-Pipelining on local methods is tricky, because it is hard to tell if-the local methods point to the same device or not.--Even though we have multiple threads, the interactor can view the-incoming Stream as a single Stream because all the events are tagged-with the URI (i think). But, sending commands involves a fancy-router. We could include a reference to corresponding command for each-stream.--For now, let's serialize the transfers, but allow pipeling for methods-that really allow pipelining.---}--data FetchCallbacks- = FetchCallbacks { logCB :: Message -> IO ()- , statusCB :: URI -> Message -> IO ()- , uriStartCB :: URI -> Maybe Integer -> Maybe UTCTime -> Maybe Integer -> IO ()- , uriDoneCB :: URI -> Maybe Integer -> Maybe UTCTime -> Maybe Integer -> Maybe FilePath -> Hashes -> Bool -> IO ()- , uriFailureCB :: URI -> Message -> IO ()- , generalFailureCB :: Message -> IO ()- , authorizationRequiredCB :: Site -> IO (Maybe (User, Password))- , mediaFailureCB :: Media -> Drive -> IO ()- , debugCB :: String -> IO ()- }--simpleFetch :: [ConfigItem] -> URI -> FilePath -> Maybe UTCTime -> IO Bool-simpleFetch = fetch cliFetchCallbacks---- |fetch a single item, show console output--- see also: getLastModified-fetch :: FetchCallbacks -> [ConfigItem] -> URI -> FilePath -> Maybe UTCTime -> IO Bool-fetch cb configItems uri fp lastModified =- do withMethodURI uri $ \mh ->- do s <- recvStatus mh- debugCB cb ("<- " ++ show s)- sendCommand' mh (URIAcquire uri fp lastModified)- loop mh- where- sendCommand' mh c =- do mapM_ (debugCB cb . ("-> " ++)) (formatCommand c)- sendCommand mh c- loop mh =- do r <- recvStatus mh- case r of- Capabilities {} ->- do unless (null configItems) (sendCommand' mh (Configuration configItems))- loop mh- LogMsg m ->- do logCB cb m- loop mh- Status uri m ->- do statusCB cb uri m- loop mh- URIStart uri size lastModified resumePoint ->- uriStartCB cb uri size lastModified resumePoint >> loop mh- URIDone uri size lastModified resumePoint filename hashes imsHit ->- uriDoneCB cb uri size lastModified resumePoint filename hashes imsHit >> return True- URIFailure uri message ->- uriFailureCB cb uri message >> return False- GeneralFailure m -> generalFailureCB cb m >> return False- AuthorizationRequired site ->- do mCredentials <- authorizationRequiredCB cb site- case mCredentials of- Nothing -> return False -- FIXME: do we need a force close option for closeMethod ?- Just (user, passwd) ->- do sendCommand' mh (AuthorizationCredentials site user passwd)- loop mh- MediaFailure media drive ->- do mediaFailureCB cb media drive- return False---- |set of callbacks which do nothing.--- suitable for non-interactive usage. In the case authorization is--- required, no credentials will be supplied and the download should--- abort.-emptyFetchCallbacks =- FetchCallbacks { logCB = \ _m -> return ()- , statusCB = \ _uri _m -> return ()- , uriStartCB = \ _uri _size _lastModified _resumePoint -> return ()- , uriDoneCB = \ _uri _size _lastModified _resumePoint _filename _hashes _imsHit -> return ()- , uriFailureCB = \ _uri _message -> return ()- , generalFailureCB = \ _m -> return ()- , authorizationRequiredCB = \ _site -> return Nothing- , mediaFailureCB = \ _media _drive -> return ()- , debugCB = \ _m -> return ()- }--cliFetchCallbacks =- emptyFetchCallbacks { statusCB = \uri m -> putStrLn $ uriToString' uri ++ " : " ++ m- , uriStartCB = \ uri _size lastModified _resumePoint -> putStrLn $ uriToString' uri ++ " started. " ++ show lastModified- , uriDoneCB = \uri _size _lastModified _resumePoint _filename _hashes imsHit -> putStrLn $ uriToString' uri ++ (if imsHit then " cached." else " downloaded.")- , uriFailureCB = \uri message -> hPutStrLn stderr $ "URI Failure: " ++ uriToString' uri ++ " : " ++ message- , generalFailureCB = \message -> hPutStrLn stderr $ "General Failure: " ++ message- , authorizationRequiredCB = \site ->- do putStrLn $ "Authorization Required for " ++ site- putStrLn "Username: " >> hFlush stdout- user <- getLine- putStrLn "Password: " >> hFlush stdout- passwd <- getLine -- TODO: write a getPasswd function which does not echo input- return (Just (user, passwd))- , mediaFailureCB = \media drive -> hPutStrLn stderr $ "Media Failure: media=" ++ media ++" drive="++ drive- , debugCB = \m -> print m- }--{-- FetchCallbacks { logCB = \m -> hPutStrLn stderr m- , statusCB = \uri m -> putStrLn (show uri ++" : "++ m)- , uriStartCB = \uri- }--defaultAuthenticate site =- do putStrLn $ "Authorization Required for " ++ site- putStrLn "Username: " >> hFlush stdout- user <- getLine- putStrLn "Password: " >> hFlush stdout- passwd <- getLine -- TODO: write a getPasswd function which does not echo input- return (user, passwd)--}--{-- let itemsByHost = groupOn (regName . fst) items- in- do totalQSem <- newQSem 16 -- max number of streams allowed for- forkIO- where- regName = fmap uriRegName . uriAuthority- withQSem :: QSem -> IO a -> IO a- withQSem qSem f = bracket (waitQSem qSem) (const $ signalQSem qSem) (const f)--uris = map (fromJust . parseURI) [ "http://n-heptane.com/whee"- , "file:/one/two/three"- , "ssh://jeremy:aoeu@n-heptane.com"- , "cdrom:/one"- ]--}---- * Misc Helper Functions--bool :: a -> a -> Bool -> a-bool f _ False = f-bool _ t True = t---getLastModified :: FilePath -> IO (Maybe UTCTime)-getLastModified fp =- do e <- doesFileExist fp- if e- then getFileStatus fp >>= return . Just . epochTimeToUTCTime . modificationTime- else return Nothing--{--groupOn :: (Ord b) => (a -> b) -> [a] -> [[a]]-groupOn f = groupBy ((==) `on` f) . sortBy (compare `on` f)--on :: (a -> a -> b) -> (c -> a) -> c -> c -> b-on f g x y = f (g x) (g y)--}
− Debian/Apt/Package.hs
@@ -1,54 +0,0 @@-{-# LANGUAGE ScopedTypeVariables #-}--- |Functions for dealing with source and binary packages in an abstract-way-module Debian.Apt.Package where---- Standard GHC Modules--import qualified Data.Map as Map---- Local Modules--import Debian.Version-import Debian.Relation--type PackageNameMap a = Map.Map BinPkgName [a]---- |'packageNameMap' creates a map from a package name to all the versions of that package--- NOTE: Provides are not included in the map--- NOTE: the sort order is random -- this is perhaps a bug--- see also: 'addProvides'-packageNameMap :: (a -> BinPkgName) -> [a] -> PackageNameMap a-packageNameMap getName packages = foldl (\m p -> Map.insertWith (++) (getName p) [p] m) Map.empty packages---- |'addProvides' finds packages that Provide other packages and adds--- them to the PackageNameMap. They will be adde to the end of the--- list, so that real packages have 'higher priority' than virtual--- packages.--- NOTE: Does not check for duplication or multiple use-addProvides :: (p -> [BinPkgName]) -> [p] -> PackageNameMap p -> PackageNameMap p-addProvides providesf ps pnm =- let provides = findProvides providesf ps in- foldl (\m (packageName, package) -> Map.insertWith (flip (++)) packageName [package] m) pnm provides---- |'findProvides'-findProvides :: forall p. (p -> [BinPkgName]) -> [p] -> [(BinPkgName, p)]-findProvides providesf packages = foldl addProvides' [] packages- where addProvides' :: [(BinPkgName, p)] -> p -> [(BinPkgName, p)]- addProvides' providesList package =- foldl (\pl pkgName -> (pkgName, package): pl) providesList (providesf package)---- |'lookupPackageByRel' returns all the packages that satisfy the specified relation--- TODO: Add architecture check-lookupPackageByRel :: PackageNameMap a -> (a -> (BinPkgName, DebianVersion)) -> Relation -> [a]-lookupPackageByRel pm packageVersionF (Rel pkgName mVerReq _mArch) =- case Map.lookup pkgName pm of- Nothing -> []- Just packages -> filter filterVer packages- where filterVer p =- case mVerReq of- Nothing -> True- Just _verReq ->- let (pName, pVersion) = packageVersionF p- in if pName /= pkgName- then False -- package is a virtual package, hence we can not do a version req- else checkVersionReq mVerReq (Just pVersion)
− Debian/Arch.hs
@@ -1,54 +0,0 @@-{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}-module Debian.Arch- ( Arch(..)- , ArchOS(..)- , ArchCPU(..)- , prettyArch- , parseArch- ) where--import Data.Data (Data)-import Data.Monoid ((<>))-import Data.Typeable (Typeable)-import Text.PrettyPrint (Doc, text)--data ArchOS = ArchOS String | ArchOSAny deriving (Eq, Ord, Read, Show, Data, Typeable)--prettyOS :: ArchOS -> Doc-prettyOS (ArchOS s) = text s-prettyOS ArchOSAny = text "any"--parseOS :: String -> ArchOS-parseOS "any" = ArchOSAny-parseOS s = ArchOS s--data ArchCPU = ArchCPU String | ArchCPUAny deriving (Eq, Ord, Read, Show, Data, Typeable)--prettyCPU :: ArchCPU -> Doc-prettyCPU (ArchCPU s) = text s-prettyCPU ArchCPUAny = text "any"--parseCPU :: String -> ArchCPU-parseCPU "any" = ArchCPUAny-parseCPU s = ArchCPU s--data Arch- = Source- | All- | Binary ArchOS ArchCPU- deriving (Eq, Ord, Read, Show, Data, Typeable)--prettyArch :: Arch -> Doc-prettyArch Source = text "source"-prettyArch All = text "all"-prettyArch (Binary (ArchOS "linux") cpu) = prettyCPU cpu-prettyArch (Binary os cpu) = prettyOS os <> text "-" <> prettyCPU cpu--parseArch :: String -> Arch-parseArch s =- case span (/= '-') s of- ("source", "") -> Source- ("all", "") -> All- (cpu, "") -> Binary (ArchOS "linux") (parseCPU cpu)- (os, '-' : cpu) -> Binary (parseOS os) (parseCPU cpu)- _ -> error "parseArch: internal error"
− Debian/Changes.hs
@@ -1,309 +0,0 @@-{-# LANGUAGE FlexibleInstances #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-missing-signatures #-}--- |Changelog and changes file support.-module Debian.Changes- ( ChangesFile(..)- , ChangedFileSpec(..)- , changesFileName- , ChangeLog(..)- , ChangeLogEntry(..)- , parseChangeLog- , parseEntries -- was parseLog- , parseEntry- , parseChanges- ) where--import Data.Either (partitionEithers)-import Data.List (intercalate, intersperse)-import Data.Monoid ((<>))-import Data.Text (Text, pack, unpack, strip)-import Debian.Arch (Arch, prettyArch)-import qualified Debian.Control.String as S-import Debian.Pretty (PP(..))-import Debian.Release-import Debian.URI()-import Debian.Version-import System.Posix.Types-import Text.Regex.TDFA hiding (empty)-import Text.PrettyPrint (Doc, text, hcat, render)-import Text.PrettyPrint.HughesPJClass (Pretty(pPrint))---- |A file generated by dpkg-buildpackage describing the result of a--- package build-data ChangesFile =- Changes { changeDir :: FilePath -- ^ The full pathname of the directory holding the .changes file.- , changePackage :: String -- ^ The package name parsed from the .changes file name- , changeVersion :: DebianVersion -- ^ The version number parsed from the .changes file name- , changeRelease :: ReleaseName -- ^ The Distribution field of the .changes file- , changeArch :: Arch -- ^ The architecture parsed from the .changes file name- , changeInfo :: S.Paragraph' Text -- ^ The contents of the .changes file- , changeEntry :: ChangeLogEntry -- ^ The value of the Changes field of the .changes file- , changeFiles :: [ChangedFileSpec] -- ^ The parsed value of the Files attribute- } deriving (Eq, Read, Show)---- |An entry in the list of files generated by the build.-data ChangedFileSpec =- ChangedFileSpec { changedFileMD5sum :: String- , changedFileSHA1sum :: String- , changedFileSHA256sum :: String- , changedFileSize :: FileOffset- , changedFileSection :: SubSection- , changedFilePriority :: String- , changedFileName :: FilePath- } deriving (Eq, Read, Show)---- |A changelog is a series of ChangeLogEntries-data ChangeLogEntry =- Entry { logPackage :: String -- FIXME: Should be a SrcPkgName- , logVersion :: DebianVersion- , logDists :: [ReleaseName]- , logUrgency :: String- , logComments :: String- , logWho :: String- , logDate :: String- }- | WhiteSpace String -- ^ The parser here never returns this- deriving (Eq, Read, Show)--newtype ChangeLog = ChangeLog [ChangeLogEntry] deriving (Eq, Read, Show)--{--instance Show ChangesFile where- show = changesFileName--}--changesFileName :: ChangesFile -> String-changesFileName = render . pPrint . PP--instance Pretty (PP ChangesFile) where- pPrint (PP changes) = text (changePackage changes ++ "_") <> prettyDebianVersion (changeVersion changes) <> text "_" <> prettyArch (changeArch changes) <> text ".changes"--instance Pretty (PP ChangedFileSpec) where- pPrint (PP file) =- text (changedFileMD5sum file <> " " <>- show (changedFileSize file) <> " " <>- sectionName (changedFileSection file) <> " " <>- changedFilePriority file <> " " <>- changedFileName file)--instance Pretty (PP ChangeLogEntry) where- pPrint (PP (Entry package ver dists urgency details who date)) =- hcat [ text package <> text " (" <> prettyDebianVersion ver <> text (") " <> intercalate " " (map releaseName' dists) ++ "; urgency=" ++ urgency)- , text "\n\n"- , text " " <> text (strip' details)- , text "\n\n"- , text (" -- " <> who <> " " <> date)- , text "\n" ]- pPrint (PP (WhiteSpace _)) = error "instance Pretty ChangeLogEntry"--instance Pretty (PP [ChangeLogEntry]) where- pPrint = hcat . intersperse (text "\n") . map (pPrint . PP) . unPP--strip' = unpack . strip . pack--instance Pretty (PP ChangeLog) where- pPrint (PP (ChangeLog xs)) = hcat (intersperse (text "\n") (map (pPrint . PP) xs))---- |Show just the top line of a changelog entry (for debugging output.)-_showHeader :: ChangeLogEntry -> Doc-_showHeader (Entry package ver dists urgency _ _ _) =- text (package <> " (") <> prettyDebianVersion ver <> text (") " <> intercalate " " (map releaseName' dists) <> "; urgency=" <> urgency <> "...")-_showHeader (WhiteSpace _) = error "_showHeader"--{--format is a series of entries like this:-- package (version) distribution(s); urgency=urgency- [optional blank line(s), stripped]- * change details- more change details- [blank line(s), included in output of dpkg-parsechangelog]- * even more change details- [optional blank line(s), stripped]- -- maintainer name <email address>[two spaces] date--package and version are the source package name and version number.--distribution(s) lists the distributions where this version should be-installed when it is uploaded - it is copied to the Distribution field-in the .changes file. See Distribution, Section 5.6.14.--urgency is the value for the Urgency field in the .changes file for-the upload (see Urgency, Section 5.6.17). It is not possible to-specify an urgency containing commas; commas are used to separate-keyword=value settings in the dpkg changelog format (though there is-currently only one useful keyword, urgency).--The change details may in fact be any series of lines starting with at-least two spaces, but conventionally each change starts with an-asterisk and a separating space and continuation lines are indented so-as to bring them in line with the start of the text above. Blank lines-may be used here to separate groups of changes, if desired.--If this upload resolves bugs recorded in the Bug Tracking System-(BTS), they may be automatically closed on the inclusion of this-package into the Debian archive by including the string: closes:-Bug#nnnnn in the change details.[16] This information is conveyed via-the Closes field in the .changes file (see Closes, Section 5.6.22).--The maintainer name and email address used in the changelog should be-the details of the person uploading this version. They are not-necessarily those of the usual package maintainer. The information-here will be copied to the Changed-By field in the .changes file (see-Changed-By, Section 5.6.4), and then later used to send an-acknowledgement when the upload has been installed.--The date must be in RFC822 format[17]; it must include the time zone-specified numerically, with the time zone name or abbreviation-optionally present as a comment in parentheses.--The first "title" line with the package name must start at the left-hand margin. The "trailer" line with the maintainer and date details-must be preceded by exactly one space. The maintainer details and the-date must be separated by exactly two spaces.--The entire changelog must be encoded in UTF-8.--}---- | Parse the entries of a debian changelog and verify they are all--- valid.-parseChangeLog :: String -> Either [[String]] ChangeLog-parseChangeLog s =- case partitionEithers (parseEntries s) of- ([], xs) -> Right (ChangeLog xs)- (ss, _) -> Left ss---- |Parse a Debian Changelog and return a lazy list of entries-parseEntries :: String -> [Either [String] ChangeLogEntry]-parseEntries "" = []-parseEntries text =- case parseEntry text of- Left messages -> [Left messages]- Right (entry, text') -> Right entry : parseEntries text'---- |Parse a single changelog entry, returning the entry and the remaining text.-parseEntry :: String -> Either [String] (ChangeLogEntry, String)-parseEntry text =- case text =~ entryRE :: MatchResult String of- x | mrSubList x == [] -> Left ["Parse error in " ++ show text]- MR {mrAfter = after, mrSubList = [_, name, ver, dists, urgency, _, details, _, _, who, _, date, _]} ->- Right (Entry name- (parseDebianVersion' ver)- (map parseReleaseName . words $ dists)- urgency- (" " ++ unpack (strip (pack details)) ++ "\n")- (take (length who - 2) who)- date,- after)- MR {mrBefore = _before, mrMatch = _matched, mrAfter = after, mrSubList = matches} ->- Left ["Internal error\n after=" ++ show after ++ "\n " ++ show (length matches) ++ " matches: " ++ show matches]--entryRE = bol ++ blankLines ++ headerRE ++ changeDetails ++ signature ++ blankLines-changeDetails = "((\n| \n| -\n|([^ ]| [^--]| -[^--])[^\n]*\n)*)"-signature = " -- ([ ]*([^ ]+ )* )([^\n]*)\n"---- |Parse the changelog information that shows up in the .changes--- file, i.e. a changelog entry with no signature.-parseChanges :: Text -> Maybe ChangeLogEntry-parseChanges text =- case unpack text =~ changesRE :: MatchResult String of- MR {mrSubList = []} -> Nothing- MR {mrSubList = [_, name, ver, dists, urgency, _, details]} ->- Just $ Entry name- (parseDebianVersion' ver)- (map parseReleaseName . words $ dists)- urgency- details- "" ""- MR {mrSubList = x} -> error $ "Unexpected match: " ++ show x- where- changesRE = bol ++ blankLines ++ optWhite ++ headerRE ++ "(.*)$"--headerRE =- package ++ ver ++ dists ++ urgency- where- package = "([^ \t(]*)" ++ optWhite- ver = "\\(([^)]*)\\)" ++ optWhite- dists = "([^;]*);" ++ optWhite- urgency = "urgency=([^\n]*)\n" ++ blankLines--blankLines = blankLine ++ "*"-blankLine = "(" ++ optWhite ++ "\n)"-optWhite = "[ \t]*"-bol = "^"---- This can be used for tests-_s1 = unlines- ["haskell-regex-compat (0.92-3+seereason1~jaunty4) jaunty-seereason; urgency=low",- "",- " [ Joachim Breitner ]",- " * Adjust priority according to override file",- " * Depend on hscolour (Closes: #550769)",- "",- " [ Marco Túlio Gontijo e Silva ]",- " * debian/control: Use more sintetic name for Vcs-Darcs.",- " * Built from sid apt pool",- " * Build dependency changes:",- " cpphs: 1.9-1+seereason1~jaunty5 -> 1.9-1+seereason1~jaunty6",- " ghc6: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1",- " ghc6-doc: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1",- " ghc6-prof: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1",- " haddock: 2.4.2-3+seereason3~jaunty1 -> 6.12.1-0+seereason1~jaunty1",- " haskell-devscripts: 0.6.18-21+seereason1~jaunty1 -> 0.6.18-23+seereason1~jaunty1",- " haskell-regex-base-doc: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1",- " haskell-regex-posix-doc: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2",- " libghc6-regex-base-dev: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1",- " libghc6-regex-base-prof: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1",- " libghc6-regex-posix-dev: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2",- " libghc6-regex-posix-prof: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2",- "",- " -- SeeReason Autobuilder <autobuilder@seereason.org> Fri, 25 Dec 2009 01:55:37 -0800",- "",- "haskell-regex-compat (0.92-3) unstable; urgency=low",- "",- " [ Joachim Breitner ]",- " * Adjust priority according to override file",- " * Depend on hscolour (Closes: #550769)",- "",- " [ Marco Túlio Gontijo e Silva ]",- " * debian/control: Use more sintetic name for Vcs-Darcs.",- "",- " -- Joachim Breitner <nomeata@debian.org> Mon, 20 Jul 2009 13:05:35 +0200",- "",- "haskell-regex-compat (0.92-2) unstable; urgency=low",- "",- " * Adopt package for the Debian Haskell Group",- " * Fix \"FTBFS with new dpkg-dev\" by adding comma to debian/control",- " (Closes: #536473)",- "",- " -- Joachim Breitner <nomeata@debian.org> Mon, 20 Jul 2009 12:05:40 +0200",- "",- "haskell-regex-compat (0.92-1.1) unstable; urgency=low",- "",- " * Rebuild for GHC 6.10.",- " * NMU with permission of the author.",- "",- " -- John Goerzen <jgoerzen@complete.org> Mon, 16 Mar 2009 10:12:04 -0500",- "",- "haskell-regex-compat (0.92-1) unstable; urgency=low",- "",- " * New upstream release",- " * debian/control:",- " - Bump Standards-Version. No changes needed.",- "",- " -- Arjan Oosting <arjan@debian.org> Sun, 18 Jan 2009 00:05:02 +0100",- "",- "haskell-regex-compat (0.91-1) unstable; urgency=low",- "",- " * Take over package from Ian, as I already maintain haskell-regex-base,",- " and move Ian to the Uploaders field.",- " * Packaging complete redone (based on my haskell-regex-base package).",- "",- " -- Arjan Oosting <arjan@debian.org> Sat, 19 Jan 2008 16:48:39 +0100",- "",- "haskell-regex-compat (0.71.0.1-1) unstable; urgency=low",- " ",- " * Initial release (used to be part of ghc6).",- " * Using \"Generic Haskell cabal library packaging files v9\".",- " ",- " -- Ian Lynagh (wibble) <igloo@debian.org> Wed, 21 Nov 2007 01:26:57 +0000"]
− Debian/Control.hs
@@ -1,76 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--- |A module for working with Debian control files <http://www.debian.org/doc/debian-policy/ch-controlfields.html>-module Debian.Control- ( -- * Types- Control'(..)- , Paragraph'(..)- , Field'(..)- , Control- , Paragraph- , Field- , ControlParser- , ControlFunctions(..)- -- * Control File Parser- , pControl- -- * Helper Functions- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- , packParagraph- , packField- , formatControl- , formatParagraph- , formatField- -- * Policy classes and functions- , P.HasDebianControl(..)- , P.ControlFileError(..)- , P.parseDebianControlFromFile- , P.validateDebianControl- , P.unsafeDebianControl- , P.debianSourceParagraph- , P.debianBinaryParagraphs- , P.debianPackageParagraphs- , P.debianPackageNames- , P.debianSourcePackageName- , P.debianBinaryPackageNames- , P.debianRelations- , P.debianBuildDeps- , P.debianBuildDepsIndep- ) where----import Control.Monad---import Data.List---import Text.ParserCombinators.Parsec---import System.IO-import Debian.Control.Common-import Debian.Control.String-import Data.List-import Data.Text as T (Text, pack, concat)-import qualified Debian.Control.Builder ()-import qualified Debian.Control.Text as T-import qualified Debian.Control.TextLazy as TL-import qualified Debian.Control.ByteString as B ()-import qualified Debian.Control.Policy as P-import qualified Debian.Control.String as S--packParagraph :: S.Paragraph -> T.Paragraph-packParagraph (S.Paragraph s) = T.Paragraph (map packField s)--packField :: Field' String -> Field' Text-packField (S.Field (name, value)) = T.Field (T.pack name, T.pack value)-packField (S.Comment s) = T.Comment (T.pack s)--formatControl :: Control' Text -> [Text]-formatControl (T.Control paragraphs) = intersperse (T.pack "\n") . map formatParagraph $ paragraphs--formatParagraph :: Paragraph' Text -> Text-formatParagraph (T.Paragraph fields) = T.concat . map formatField $ fields--formatField :: Field' Text -> Text-formatField (T.Field (name, value)) = T.concat [name, T.pack ":", value, T.pack "\n"]-formatField (T.Comment s) = s
− Debian/Control/Builder.hs
@@ -1,161 +0,0 @@-{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}-module Debian.Control.Builder- ( -- * Types- Control'(..)- , Paragraph'(..)- , Field'(..)- , Control- , Paragraph- , Field- -- , ControlParser- , ControlFunctions(..)- -- * Control File Parser- -- , pControl- -- * Helper Functions- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- , decodeControl- , decodeParagraph- , decodeField- ) where--import qualified Data.ByteString.Char8 as B-import Data.Char (toLower, chr)-import Data.List (find)-import qualified Data.ListLike as LL-import Data.ListLike.Text.Builder ()-import qualified Data.Text as T (pack, unpack, map, reverse)-import Data.Text.Lazy (toStrict)-import Data.Text.Lazy.Builder (Builder, fromLazyText, fromText, toLazyText)-import Data.Text.Encoding (decodeUtf8With, encodeUtf8)---import Data.Text.IO as T (readFile)-import qualified Debian.Control.ByteString as B---import Text.Parsec.Error (ParseError)---import Text.Parsec.Text (Parser)---import Text.Parsec.Prim (runP)-import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, asString),- Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),- mergeControls, fieldValue, removeField, prependFields, appendFields,- renameField, modifyField, raiseFields, protectFieldText')---- | @parseFromFile p filePath@ runs a string parser @p@ on the--- input read from @filePath@ using 'Prelude.readFile'. Returns either a 'ParseError'--- ('Left') or a value of type @a@ ('Right').------ > main = do{ result <- parseFromFile numbers "digits.txt"--- > ; case result of--- > Left err -> print err--- > Right xs -> print (sum xs)--- > }-{--parseFromFile :: Parser a -> String -> IO (Either ParseError a)-parseFromFile p fname- = do input <- T.readFile fname `E.catch` (\ (_ :: E.SomeException) -> B.readFile fname >>= return . decode)- return (runP p () fname input)--}--type Field = Field' Builder-type Control = Control' Builder-type Paragraph = Paragraph' Builder--decodeControl :: B.Control -> Control-decodeControl (B.Control paragraphs) = Control (map decodeParagraph paragraphs)--decodeParagraph :: B.Paragraph -> Paragraph-decodeParagraph (B.Paragraph s) = B.Paragraph (map decodeField s)--decodeField :: Field' B.ByteString -> Field' Builder-decodeField (B.Field (name, value)) = Field (decode name, decode value)-decodeField (B.Comment s) = Comment (decode s)--decode :: B.ByteString -> Builder-decode = fromText . decodeUtf8With (\ _ w -> fmap (chr . fromIntegral) w)---- * ControlFunctions--instance ControlFunctions Builder where- parseControlFromFile filepath =- -- The ByteString parser is far more efficient than the Text- -- parser. By calling decodeControl we tell the compiler to- -- use it instead.- parseControlFromFile filepath >>= return . either Left (Right . decodeControl)- parseControlFromHandle sourceName handle =- parseControlFromHandle sourceName handle >>= return . either Left (Right . decodeControl)- parseControl sourceName c =- -- Warning: This is very slow, it does a utf8 round trip- either Left (Right . decodeControl) (parseControl sourceName (encodeUtf8 (toStrict (toLazyText c))))- lookupP fieldName (Paragraph paragraph) =- find (hasFieldName (map toLower fieldName)) paragraph- where hasFieldName :: String -> Field' Builder -> Bool- hasFieldName name (Field (fieldName',_)) = name == LL.map toLower (LL.toString fieldName')- hasFieldName _ _ = False- stripWS = dropAround (`elem` (" \t" :: String))- -- T.strip would also strip newlines- protectFieldText = protectFieldText'- asString = LL.toString--dropAround p = LL.dropWhile p . LL.dropWhileEnd p---- * Control File Parser-{---- type ControlParser = GenParser T.Text-type ControlParser a = Parsec T.Text () a---- |A parser for debian control file. This parser handles control files--- that end without a newline as well as ones that have several blank--- lines at the end. It is very liberal and does not attempt validate--- the fields in any way. All trailing, leading, and folded whitespace--- is preserved in the field values. See 'stripWS'.-pControl :: ControlParser Control-pControl =- do many $ char '\n'- sepEndBy pParagraph pBlanks >>= return . Control--pParagraph :: ControlParser Paragraph-pParagraph = many1 (pComment <|> pField) >>= return . Paragraph---- |We are liberal in that we allow *any* field to have folded white--- space, even though the specific restricts that to a few fields.-pField :: ControlParser Field-pField =- do c1 <- noneOf "#\n"- fieldName <- many1 $ noneOf ":\n"- char ':'- fieldValue <- many fcharfws- (char '\n' >> return ()) <|> eof- return $ Field (T.cons c1 (T.pack fieldName), T.pack fieldValue)--pComment :: ControlParser Field-pComment =- do char '#'- text <- many (satisfy (not . ((==) '\n')))- char '\n'- return $ Comment (T.pack ("#" <> text <> "\n"))--fcharfws :: ControlParser Char-fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')--fchar :: ControlParser Char-fchar = satisfy (/='\n')--_fws :: ControlParser T.Text-_fws =- try $ do char '\n'- ws <- many1 (char ' ')- c <- many1 (satisfy (not . ((==) '\n')))- return $ T.cons '\n' (T.pack ws <> T.pack c)---- |We go with the assumption that 'blank lines' mean lines that--- consist of entirely of zero or more whitespace characters.-pBlanks :: ControlParser T.Text-pBlanks =- do s <- many1 (oneOf " \n")- return . T.pack $ s--}
− Debian/Control/ByteString.hs
@@ -1,289 +0,0 @@-{-# LANGUAGE CPP, FlexibleContexts, MultiParamTypeClasses, PackageImports, ScopedTypeVariables #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}-module Debian.Control.ByteString- ( Control'(..)- , Paragraph'(..)- , Field'(..)- , Control- , Paragraph- , Field- , ControlFunctions(..)- -- * Helper Functions- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- ) where---- Standard GHC modules--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative (Applicative(..))-#endif-import Control.Applicative (Alternative(..))-import qualified Control.Exception as E-import "mtl" Control.Monad.State--import Data.Char(toLower, isSpace, chr, ord)-import Data.Word (Word8)-import Data.List-import qualified Data.ListLike as LL-import qualified Data.ListLike.String as LL--import Text.ParserCombinators.Parsec.Error-import Text.ParserCombinators.Parsec.Pos---- Third Party Modules--import qualified Data.ByteString.Char8 as C--import Debian.Control.Common hiding (protectFieldText')---- Local Modules---- import ByteStreamParser---- * Types-{--newtype Control = Control [Paragraph]-newtype Paragraph = Paragraph [Field]-newtype Field = Field (C.ByteString, C.ByteString)--}--type Control = Control' C.ByteString-type Paragraph = Paragraph' C.ByteString-type Field = Field' C.ByteString--- * Control Parser--type ControlParser a = Parser C.ByteString a--pKey :: ControlParser C.ByteString-pKey = notEmpty $ pTakeWhile (\c -> (c /= ':') && (c /= '\n'))--pValue :: ControlParser C.ByteString-pValue = Parser $ \bs ->- let newlines = C.elemIndices '\n' bs- rest = dropWhile continuedAfter newlines ++ [C.length bs]- continuedAfter i = bs `safeIndex` (i+1) `elem` map Just " \t#"- (text, bs') = C.splitAt (head rest) bs- in Ok (text, bs')--pField :: ControlParser Field-pField =- do k <- pKey- _ <- pChar ':'- v <- pValue--- pChar '\n'- (pChar '\n' >> return ()) <|> pEOF- return (Field (k,v))--pComment :: ControlParser Field-pComment = Parser $ \bs ->- let newlines = C.elemIndices '\n' bs- linestarts = 0 : map (+1) newlines- rest = dropWhile commentAt linestarts ++ [C.length bs]- commentAt i = bs `safeIndex` i == Just '#'- (text, bs') = C.splitAt (head rest) bs- in if C.null text- then Empty- else Ok (Comment text, bs')--pParagraph :: ControlParser Paragraph-pParagraph =- do f <- pMany1 (pComment <|> pField)- pSkipMany (pChar '\n')- return (Paragraph f)--pControl :: ControlParser Control-pControl =- do pSkipMany (pChar '\n')- c <- pMany pParagraph- return (Control c)----- parseControlFromFile :: FilePath -> IO (Either String Control)--instance ControlFunctions C.ByteString where- parseControlFromFile fp =- do c <- C.readFile fp- case parse pControl c of- Nothing -> return (Left (newErrorMessage (Message ("Failed to parse " ++ fp)) (newPos fp 0 0)))- (Just (cntl,_)) -> return (Right cntl)- parseControlFromHandle sourceName handle =- E.try (C.hGetContents handle) >>=- either (\ (e :: E.SomeException) -> error ("parseControlFromHandle ByteString: Failure parsing " ++ sourceName ++ ": " ++ show e)) (return . parseControl sourceName)- parseControl sourceName c =- do case parse pControl c of- Nothing -> Left (newErrorMessage (Message ("Failed to parse " ++ sourceName)) (newPos sourceName 0 0))- Just (cntl,_) -> Right cntl- lookupP fieldName (Paragraph fields) =- let pFieldName = C.pack (map toLower fieldName) in- find (\ (Field (fieldName',_)) -> C.map toLower fieldName' == pFieldName) fields- -- NOTE: probably inefficient- stripWS = C.reverse . strip . C.reverse . strip- where strip = C.dropWhile (flip elem " \t")- protectFieldText = protectFieldText'- asString = C.unpack--protectFieldText' :: (LL.StringLike a, LL.ListLike a Word8) => ControlFunctions a => a -> a-protectFieldText' s =- case LL.lines s of- [] -> LL.empty- (l : ls) -> dropWhileEnd (isSpace . chr . fromIntegral) $ LL.unlines $ l : map protect ls- where- dropWhileEnd :: (LL.StringLike a, LL.ListLike a Word8) => (Word8 -> Bool) -> a -> a- dropWhileEnd func = LL.reverse . LL.dropWhile func . LL.reverse -- foldr (\x xs -> if func x && LL.null xs then LL.empty else LL.cons x xs) empty- protect :: (LL.StringLike a, LL.ListLike a Word8) => a -> a- protect l = maybe LL.empty (\ c -> if isHorizSpace c then l else LL.cons (ord' ' ' :: Word8) l) (LL.find (const True :: Word8 -> Bool) l)- -- isSpace' = isSpace . chr'- isHorizSpace c = elem c (map ord' " \t")- ord' = fromIntegral . ord- -- chr' = chr . fromIntegral--{--main =- do [fp] <- getArgs- C.readFile fp >>= \c -> maybe (putStrLn "failed.") (print . length . fst) (parse pControl c)--}--- * Helper Functions--safeIndex :: C.ByteString -> Int -> Maybe Char-bs `safeIndex` i = if i < C.length bs then Just (bs `C.index` i) else Nothing---- * Parser--data Result a- = Ok a- | Fail- | Empty- deriving Show---- m2r :: Maybe a -> Result a--- m2r (Just a) = Ok a--- m2r Nothing = Empty--r2m :: Result a -> Maybe a-r2m (Ok a) = Just a-r2m _ = Nothing--newtype Parser state a = Parser { unParser :: (state -> Result (a, state)) }--instance Functor (Parser state) where- fmap f m =- Parser $ \ state ->- let r = (unParser m) state in- case r of- Ok (a,state') -> Ok (f a,state')- Empty -> Empty- Fail -> Fail--instance Applicative (Parser state) where- pure = return- (<*>) = ap--instance Alternative (Parser state) where- empty =- Parser $ \state ->- (unParser mzero) state- (<|>) = mplus--instance Monad (Parser state) where- return a = Parser (\s -> Ok (a,s))- m >>= f =- Parser $ \state ->- let r = (unParser m) state in- case r of- Ok (a,state') ->- case unParser (f a) $ state' of- Empty -> Fail- o -> o- Empty -> Empty- Fail -> Fail--instance MonadPlus (Parser state) where- mzero = Parser (const Empty)- mplus (Parser p1) (Parser p2) =- Parser (\s -> case p1 s of- Empty -> p2 s- o -> o- )---- Parser (\s -> maybe (p2 s) (Just) (p1 s))---_pSucceed :: a -> Parser state a-_pSucceed = return--_pFail :: Parser state a-_pFail = Parser (const Empty)---satisfy :: (Char -> Bool) -> Parser C.ByteString Char-satisfy f =- Parser $ \bs ->- if C.null bs- then Empty- else let (s,ss) = (C.head bs, C.tail bs) in- if (f s)- then Ok (s,ss)- else Empty--pChar :: Char -> Parser C.ByteString Char-pChar c = satisfy ((==) c)---_try :: Parser state a -> Parser state a-_try (Parser p) =- Parser $ \bs -> case (p bs) of- Fail -> Empty- o -> o--pEOF :: Parser C.ByteString ()-pEOF =- Parser $ \bs -> if C.null bs then Ok ((),bs) else Empty--pTakeWhile :: (Char -> Bool) -> Parser C.ByteString C.ByteString-pTakeWhile f =- Parser $ \bs -> Ok (C.span f bs)--_pSkipWhile :: (Char -> Bool) -> Parser C.ByteString ()-_pSkipWhile p =- Parser $ \bs -> Ok ((), C.dropWhile p bs)--pMany :: Parser st a -> Parser st [a]-pMany p- = scan id- where- scan f = do x <- p- scan (\tail -> f (x:tail))- <|> return (f [])--notEmpty :: Parser st C.ByteString -> Parser st C.ByteString-notEmpty (Parser p) =- Parser $ \s -> case p s of- o@(Ok (a, _s)) ->- if C.null a- then Empty- else o- x -> x--pMany1 :: Parser st a -> Parser st [a]-pMany1 p =- do x <- p- xs <- pMany p- return (x:xs)--pSkipMany :: Parser st a -> Parser st ()-pSkipMany p = scan- where- scan = (p >> scan) <|> return ()--_pSkipMany1 :: Parser st a -> Parser st ()-_pSkipMany1 p = p >> pSkipMany p--parse :: Parser state a -> state -> Maybe (a, state)-parse p s = r2m ((unParser p) s)
− Debian/Control/Common.hs
@@ -1,186 +0,0 @@-{-# LANGUAGE FlexibleContexts, FlexibleInstances, OverloadedStrings, ScopedTypeVariables, UndecidableInstances #-}-module Debian.Control.Common- ( -- * Types- Control'(..)- , Paragraph'(..)- , Field'(..)- , ControlFunctions(..)- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- , parseControlFromCmd- , md5sumField- , protectFieldText'- )- where--import Control.Monad (msum)-import Data.Char (isSpace)-import Data.List as List (dropWhileEnd, partition, intersperse)-import Data.ListLike as LL (ListLike, cons, dropWhileEnd, empty, find, null, singleton)-import Data.ListLike.String as LL (StringLike, lines, unlines)-import Data.Monoid ((<>))-import Debian.Pretty (PP(..))-import System.Exit (ExitCode(ExitSuccess, ExitFailure))-import System.IO (Handle)-import System.Process (runInteractiveCommand, waitForProcess)-import Text.ParserCombinators.Parsec (ParseError)-import Text.PrettyPrint (Doc, text, hcat)-import Text.PrettyPrint.HughesPJClass (Pretty(pPrint))--newtype Control' a- = Control { unControl :: [Paragraph' a] } deriving (Eq, Ord, Read, Show)--newtype Paragraph' a- = Paragraph [Field' a]- deriving (Eq, Ord, Read, Show)---- |NOTE: we do not strip the leading or trailing whitespace in the--- name or value-data Field' a- = Field (a, a)- | Comment a -- ^ Lines beginning with #- deriving (Eq, Ord, Read, Show)--class ControlFunctions a where- -- |'parseControlFromFile' @filepath@ is a simple wrapper function- -- that parses @filepath@ using 'pControl'- parseControlFromFile :: FilePath -> IO (Either ParseError (Control' a))- -- |'parseControlFromHandle' @sourceName@ @handle@ - @sourceName@ is only used for error reporting- parseControlFromHandle :: String -> Handle -> IO (Either ParseError (Control' a))- -- |'parseControlFromString' @sourceName@ @text@ - @sourceName@ is only used for error reporting- parseControl :: String -> a -> (Either ParseError (Control' a))- -- | 'lookupP' @fieldName paragraph@ looks up a 'Field' in a 'Paragraph'.- -- @N.B.@ trailing and leading whitespace is /not/ stripped.- lookupP :: String -> (Paragraph' a) -> Maybe (Field' a)- -- |Strip the trailing and leading space and tab characters from a- -- string. Folded whitespace is /not/ unfolded. This should probably- -- be moved to someplace more general purpose.- stripWS :: a -> a- -- |Protect field value text so the parser doesn't split it into- -- multiple fields or paragraphs. This must modify all field text- -- to enforce two conditions: (1) All lines other than the initial- -- one must begin with a space or a tab, and (2) the trailing- -- white space must not contain newlines. This is called before- -- pretty printing to prevent the parser from misinterpreting- -- field text as multiple fields or paragraphs.- protectFieldText :: a -> a- asString :: a -> String---- | This can usually be used as the implementation of protectFieldText-protectFieldText' :: forall a. (StringLike a, ListLike a Char) => ControlFunctions a => a -> a-protectFieldText' s =- let trimmedLines :: [a]- trimmedLines = map (LL.dropWhileEnd isSpace :: a -> a) $ (LL.lines s :: [a])- strippedLines :: [a]- strippedLines = List.dropWhileEnd LL.null trimmedLines in- -- Split the text into lines, drop trailing whitespace from each- -- line, and drop trailing blank lines.- case strippedLines of- [] -> empty- (l : ls) ->- let -- The first line is indented one space- l' = {-LL.cons ' '-} l- -- Null lines are replaced by a single '.' If any line- -- is unindented, all will get an additional space of- -- indentation.- ls' = case all indented ls of- True -> map (\ x -> if LL.null x then (LL.cons ' ' $ singleton '.') else x) ls- False -> map (LL.cons ' ') $ map (\ x -> if LL.null x then (singleton '.') else x) ls in- LL.dropWhileEnd isSpace (LL.unlines (l' : ls'))- where- indented l = maybe True isSpace (LL.find (const True) l)---- | This may have bad performance issues (dsf: Whoever wrote this--- comment should have explained why.)-instance (ControlFunctions a, Pretty (PP a)) => Pretty (Control' a) where- pPrint = ppControl-instance (ControlFunctions a, Pretty (PP a)) => Pretty (Paragraph' a) where- pPrint = ppParagraph--instance (ControlFunctions a, Pretty (PP a)) => Pretty (Field' a) where- pPrint = ppField--ppControl :: (ControlFunctions a, Pretty (PP a)) => Control' a -> Doc-ppControl (Control paragraph) =- hcat (intersperse (text "\n") (map ppParagraph paragraph))--ppParagraph :: (ControlFunctions a, Pretty (PP a)) => Paragraph' a -> Doc-ppParagraph (Paragraph fields) =- hcat (map (\ x -> ppField x <> text "\n") fields)--ppField :: (ControlFunctions a, Pretty (PP a)) => Field' a -> Doc-ppField (Field (n,v)) = pPrint (PP n) <> text ":" <> pPrint (PP (protectFieldText v))-ppField (Comment c) = pPrint (PP c)--mergeControls :: [Control' a] -> Control' a-mergeControls controls =- Control (concatMap unControl controls)--fieldValue :: (ControlFunctions a) => String -> Paragraph' a -> Maybe a-fieldValue fieldName paragraph =- case lookupP fieldName paragraph of- Just (Field (_, val)) -> Just $ stripWS val- _ -> Nothing--removeField :: (Eq a) => a -> Paragraph' a -> Paragraph' a-removeField toRemove (Paragraph fields) =- Paragraph (filter remove fields)- where- remove (Field (name,_)) = name == toRemove- remove (Comment _) = False--prependFields :: [Field' a] -> Paragraph' a -> Paragraph' a-prependFields newfields (Paragraph fields) = Paragraph (newfields ++ fields)--appendFields :: [Field' a] -> Paragraph' a -> Paragraph' a-appendFields newfields (Paragraph fields) = Paragraph (fields ++ newfields)--renameField :: (Eq a) => a -> a -> Paragraph' a -> Paragraph' a-renameField oldname newname (Paragraph fields) =- Paragraph (map rename fields)- where- rename (Field (name, value)) | name == oldname = Field (newname, value)- rename field = field--modifyField :: (Eq a) => a -> (a -> a) -> Paragraph' a -> Paragraph' a-modifyField name f (Paragraph fields) =- Paragraph (map modify fields)- where- modify (Field (name', value)) | name' == name = Field (name, f value)- modify field = field---- | Move selected fields to the beginning of a paragraph.-raiseFields :: (Eq a) => (a -> Bool) -> Paragraph' a -> Paragraph' a-raiseFields f (Paragraph fields) =- let (a, b) = partition f' fields in Paragraph (a ++ b)- where f' (Field (name, _)) = f name- f' (Comment _) = False---- | Run a command and parse its output as a control file.-parseControlFromCmd :: ControlFunctions a => String -> IO (Either String (Control' a))-parseControlFromCmd cmd =- do- (_, outh, _, handle) <- runInteractiveCommand cmd- result <- parseControlFromHandle cmd outh- either (return . Left . show) (finish handle) result- where- finish handle control =- do- exitCode <- waitForProcess handle- case exitCode of- ExitSuccess -> return $ Right control- ExitFailure n -> return $ Left ("Failure: " ++ cmd ++ " -> " ++ show n)---- |look up the md5sum file in a paragraph--- Tries several different variations:--- MD5Sum:--- Md5Sum:--- MD5sum:-md5sumField :: (ControlFunctions a) => Paragraph' a -> Maybe a-md5sumField p = msum [fieldValue "MD5Sum" p, fieldValue "Md5Sum" p, fieldValue "MD5sum" p]
− Debian/Control/Policy.hs
@@ -1,180 +0,0 @@-{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, RecordWildCards, ScopedTypeVariables, TemplateHaskell #-}-{-# OPTIONS_GHC -Wall #-}--- | Access to things that Debian policy says should be in a valid--- control file. The pure functions will not throw ControlFileError--- if they are operating on a DebianControl value returned by--- validateDebianControl. However, they might if they are created--- using unsafeDebianControl.-module Debian.Control.Policy- ( -- * Validated debian control file type- DebianControl(unDebianControl)- , validateDebianControl- , unsafeDebianControl- , parseDebianControlFromFile- , parseDebianControl- , ControlFileError(..)- -- * Class of things that contain one DebianControl value- , HasDebianControl(debianControl)- -- * Pure functions that operate on validated control files- , debianSourceParagraph- , debianBinaryParagraphs- , debianPackageParagraphs- , debianPackageNames- , debianSourcePackageName- , debianBinaryPackageNames- , debianRelations- , debianBuildDeps- , debianBuildDepsIndep- ) where--import Control.Exception (Exception, throw)-import Control.Monad.Catch (MonadCatch, try)-import Data.List (intercalate)-import Data.Text (Text)-import Data.Typeable (Typeable)-import Data.ListLike (toList)-import Debian.Control.Common (Control'(..), Paragraph'(..), Field'(..), fieldValue, ControlFunctions(parseControlFromFile, parseControl))-import Debian.Control.Text ()-import Debian.Loc (__LOC__)-import Debian.Pretty (prettyShow)-import Debian.Relation (SrcPkgName(..), BinPkgName(..), Relations, parseRelations)-import Debian.Relation.Text ()-import Language.Haskell.TH (Loc(..))-import Prelude hiding (ioError)--- import qualified Debug.ShowPlease as Please-import Text.Parsec.Error (ParseError)---- | Opaque (constructor not exported) type to hold a validated Debian--- Control File-data DebianControl = DebianControl {unDebianControl :: Control' Text}--instance Show DebianControl where- show c = "(parseDebianControl \"\" " ++ show (prettyShow (unDebianControl c)) ++ ")"---- | Validate and return a control file in an opaque wrapper. May--- throw a ControlFileError. Currently we only verify that it has a--- Source field in the first paragraph and one or more subsequent--- paragraphs each with a Package field, and no syntax errors in the--- build dependencies (though they may be absent.)-validateDebianControl :: MonadCatch m => Control' Text -> m (Either ControlFileError DebianControl)-validateDebianControl ctl =- try (do _ <- return $ debianPackageNames (DebianControl ctl)- _ <- return $ debianBuildDeps (DebianControl ctl)- _ <- return $ debianBuildDepsIndep (DebianControl ctl)- return ()) >>=- return . either Left (\ _ -> Right $ DebianControl ctl)--unsafeDebianControl :: Control' Text -> DebianControl-unsafeDebianControl = DebianControl--parseDebianControl :: MonadCatch m => String -> Text -> m (Either ControlFileError DebianControl)-parseDebianControl sourceName s = either (return . Left . ParseControlError [$__LOC__]) validateDebianControl (parseControl sourceName s)--parseDebianControlFromFile :: FilePath -> IO (Either ControlFileError DebianControl)-parseDebianControlFromFile controlPath =- try (parseControlFromFile controlPath) >>=- either (return . Left . IOError [$__LOC__])- (either (return . Left . ParseControlError [$__LOC__]) validateDebianControl)---- | Class of things that contain a validated Debian control file.-class Show a => HasDebianControl a where- debianControl :: a -> DebianControl--instance HasDebianControl DebianControl where- debianControl = id--class HasControl a where- control :: a -> Control' Text--instance HasControl (Control' Text) where- control = id--instance HasControl DebianControl where- control = unDebianControl---- | Errors that control files might throw, with source file name and--- line number generated by template haskell.-data ControlFileError- = NoParagraphs {locs :: [Loc]}- | NoBinaryParagraphs {locs :: [Loc], ctl :: String}- | MissingField {locs :: [Loc], field :: String}- | ParseRelationsError {locs :: [Loc], parseError :: ParseError}- | ParseControlError {locs :: [Loc], parseError :: ParseError}- | IOError {locs :: [Loc], ioError :: IOError}- deriving Typeable--instance Show ControlFileError where- show (NoParagraphs {..}) = intercalate ", " (map showLoc locs) ++ ": NoParagraphs"- show (NoBinaryParagraphs {..}) = intercalate ", " (map showLoc locs) ++ ": NoBinaryParagraphs"- show (MissingField {..}) = intercalate ", " (map showLoc locs) ++ ": MissingField " ++ show field- show (ParseRelationsError {..}) = intercalate ", " (map showLoc locs) ++ ": ParseRelationsError " ++ show parseError- show (ParseControlError {..}) = intercalate ", " (map showLoc locs) ++ ": ParseControlError " ++ show parseError- show (IOError {..}) = intercalate ", " (map showLoc locs) ++ ": IOError " ++ show ioError--showLoc :: Loc -> String-showLoc x = show (loc_filename x) ++ "(line " ++ show (fst (loc_start x)) ++ ", column " ++ show (snd (loc_start x)) ++ ")"---- instance Please.Show ControlFileError where--- show (IOError e) = "(IOError " ++ Please.show e ++ ")"--- show (ParseRelationsError e) = "(ParseRelationsError " ++ Please.show e ++ ")"--- show (ParseControlError e) = "(ParseControlError " ++ Please.show e ++ ")"--- show x = show x--instance Exception ControlFileError--instance Eq ControlFileError where- _ == _ = False--debianPackageParagraphs :: HasDebianControl a => a -> (Paragraph' Text, [Paragraph' Text])-debianPackageParagraphs ctl =- case removeCommentParagraphs ctl of- DebianControl (Control [_]) -> throw $ NoBinaryParagraphs [$__LOC__] (show ctl)- DebianControl (Control []) -> throw $ NoParagraphs [$__LOC__]- DebianControl (Control (sourceParagraph : binParagraphs)) -> (sourceParagraph, binParagraphs)---- | Comment paragraphs are rare, but they happen.-removeCommentParagraphs :: HasDebianControl a => a -> DebianControl-removeCommentParagraphs c =- DebianControl (Control (filter (not . isCommentParagraph) (unControl (unDebianControl (debianControl c)))))- where- isCommentParagraph (Paragraph fields) = all isCommentField fields- isCommentField (Comment _) = True- isCommentField _ = False--debianSourceParagraph :: HasDebianControl a => a -> Paragraph' Text-debianSourceParagraph = fst . debianPackageParagraphs--debianBinaryParagraphs :: HasDebianControl a => a -> [Paragraph' Text]-debianBinaryParagraphs = snd . debianPackageParagraphs--debianPackageNames :: HasDebianControl a => a -> (SrcPkgName, [BinPkgName])-debianPackageNames c =- let (srcParagraph, binParagraphs) = debianPackageParagraphs c in- (mapFieldValue (SrcPkgName . toList) "Source" srcParagraph, map (mapFieldValue (BinPkgName . toList) "Package") binParagraphs)--debianSourcePackageName :: HasDebianControl a => a -> SrcPkgName-debianSourcePackageName = fst . debianPackageNames--debianBinaryPackageNames :: HasDebianControl a => a -> [BinPkgName]-debianBinaryPackageNames = snd . debianPackageNames--debianBuildDepsIndep :: HasDebianControl a => a -> Maybe Relations-debianBuildDepsIndep ctl = either throw id $ debianRelations "Build-Depends-Indep" (debianControl ctl)--debianBuildDeps :: HasDebianControl a => a -> Maybe Relations-debianBuildDeps ctl = either throw id $ debianRelations "Build-Depends" (debianControl ctl)---- | Version of fieldValue that may throw a ControlFileError. We only--- use this internally on fields that we already validated.-fieldValue' :: ControlFunctions text => String -> Paragraph' text -> text-fieldValue' fieldName paragraph = maybe (throw $ MissingField [$__LOC__] fieldName) id $ fieldValue fieldName paragraph---- | This could access fields we haven't validated, so--- it can return an error. Additionally, the field might--- be absent, in which case it returns Nothing.-debianRelations :: HasDebianControl a => String -> a -> Either ControlFileError (Maybe Relations)-debianRelations fieldName ctl = maybe (Right Nothing) (either (Left . ParseRelationsError [$__LOC__]) (Right . Just) . parseRelations) $ fieldValue fieldName (debianSourceParagraph ctl)---- | Apply a function to the text from a named field in a control file paragraph.-mapFieldValue :: (Text -> a) -> String -> Paragraph' Text -> a-mapFieldValue f fieldName paragraph = f $ fieldValue' fieldName paragraph
− Debian/Control/String.hs
@@ -1,110 +0,0 @@-{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}-module Debian.Control.String- ( -- * Types- Control'(..)- , Paragraph'(..)- , Field'(..)- , Control- , Paragraph- , Field- , ControlParser- , ControlFunctions(..)- -- * Control File Parser- , pControl- -- * Helper Functions- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- ) where--import qualified Control.Exception as E-import Data.Char (toLower)-import Data.List (find)-import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, protectFieldText, asString),- Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),- mergeControls, fieldValue, removeField, prependFields, appendFields,- renameField, modifyField, raiseFields, protectFieldText')-import System.IO (hGetContents)-import Text.ParserCombinators.Parsec (CharParser, parse, parseFromFile, sepEndBy, satisfy, oneOf, string, lookAhead, try, many, many1, (<|>), noneOf, char, eof)--type Field = Field' String-type Control = Control' String-type Paragraph = Paragraph' String---- * ControlFunctions--instance ControlFunctions String where- parseControlFromFile filepath =- parseFromFile pControl filepath- parseControlFromHandle sourceName handle =- E.try (hGetContents handle) >>=- either (\ (e :: E.SomeException) -> error ("parseControlFromHandle String: Failure parsing " ++ sourceName ++ ": " ++ show e)) (return . parseControl sourceName)- parseControl sourceName c =- parse pControl sourceName c- lookupP fieldName (Paragraph paragraph) =- find (hasFieldName (map toLower fieldName)) paragraph- where hasFieldName name (Field (fieldName',_)) = name == map toLower fieldName'- hasFieldName _ _ = False- stripWS = reverse . strip . reverse . strip- where strip = dropWhile (flip elem " \t")- protectFieldText = protectFieldText'- asString = id---- * Control File Parser--type ControlParser a = CharParser () a---- |A parser for debian control file. This parser handles control files--- that end without a newline as well as ones that have several blank--- lines at the end. It is very liberal and does not attempt validate--- the fields in any way. All trailing, leading, and folded whitespace--- is preserved in the field values. See 'stripWS'.-pControl :: ControlParser Control-pControl =- do many $ char '\n'- sepEndBy pParagraph pBlanks >>= return . Control--pParagraph :: ControlParser Paragraph-pParagraph = many1 (pComment <|> pField) >>= return . Paragraph---- |We are liberal in that we allow *any* field to have folded white--- space, even though the specific restricts that to a few fields.-pField :: ControlParser Field-pField =- do c1 <- noneOf "#\n"- fieldName <- many1 $ noneOf ":\n"- char ':'- fieldValue <- many fcharfws- (char '\n' >> return ()) <|> eof- return $ Field (c1 : fieldName, fieldValue)--pComment :: ControlParser Field-pComment =- do char '#'- text <- many (satisfy (not . ((==) '\n')))- char '\n'- return $ Comment ("#" ++ text ++ "\n")--fcharfws :: ControlParser Char-fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')--fchar :: ControlParser Char-fchar = satisfy (/='\n')--_fws :: ControlParser String-_fws =- try $ do char '\n'- ws <- many1 (char ' ')- c <- many1 (satisfy (not . ((==) '\n')))- return $ '\n' : (ws ++ c)---- |We go with the assumption that 'blank lines' mean lines that--- consist of entirely of zero or more whitespace characters.-pBlanks :: ControlParser String-pBlanks = many1 (oneOf " \n")
− Debian/Control/Text.hs
@@ -1,155 +0,0 @@-{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}-module Debian.Control.Text- ( -- * Types- Control'(..)- , Paragraph'(..)- , Field'(..)- , Control- , Paragraph- , Field- -- , ControlParser- , ControlFunctions(..)- -- * Control File Parser- -- , pControl- -- * Helper Functions- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- , decodeControl- , decodeParagraph- , decodeField- ) where--import qualified Data.ByteString.Char8 as B-import Data.Char (toLower, chr)-import Data.List (find)-import qualified Data.Text as T (Text, pack, unpack, map, dropAround, reverse)-import Data.Text.Encoding (decodeUtf8With, encodeUtf8)---import Data.Text.IO as T (readFile)-import qualified Debian.Control.ByteString as B---import Text.Parsec.Error (ParseError)---import Text.Parsec.Text (Parser)---import Text.Parsec.Prim (runP)-import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, asString),- Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),- mergeControls, fieldValue, removeField, prependFields, appendFields,- renameField, modifyField, raiseFields, protectFieldText')---- | @parseFromFile p filePath@ runs a string parser @p@ on the--- input read from @filePath@ using 'Prelude.readFile'. Returns either a 'ParseError'--- ('Left') or a value of type @a@ ('Right').------ > main = do{ result <- parseFromFile numbers "digits.txt"--- > ; case result of--- > Left err -> print err--- > Right xs -> print (sum xs)--- > }-{--parseFromFile :: Parser a -> String -> IO (Either ParseError a)-parseFromFile p fname- = do input <- T.readFile fname `E.catch` (\ (_ :: E.SomeException) -> B.readFile fname >>= return . decode)- return (runP p () fname input)--}--type Field = Field' T.Text-type Control = Control' T.Text-type Paragraph = Paragraph' T.Text--decodeControl :: B.Control -> Control-decodeControl (B.Control paragraphs) = Control (map decodeParagraph paragraphs)--decodeParagraph :: B.Paragraph -> Paragraph-decodeParagraph (B.Paragraph s) = B.Paragraph (map decodeField s)--decodeField :: Field' B.ByteString -> Field' T.Text-decodeField (B.Field (name, value)) = Field (decode name, decode value)-decodeField (B.Comment s) = Comment (decode s)--decode :: B.ByteString -> T.Text-decode = decodeUtf8With (\ _ w -> fmap (chr . fromIntegral) w)---- * ControlFunctions--instance ControlFunctions T.Text where- parseControlFromFile filepath =- -- The ByteString parser is far more efficient than the Text- -- parser. By calling decodeControl we tell the compiler to- -- use it instead.- parseControlFromFile filepath >>= return . either Left (Right . decodeControl)- parseControlFromHandle sourceName handle =- parseControlFromHandle sourceName handle >>= return . either Left (Right . decodeControl)- parseControl sourceName c =- -- Warning: This is very slow, it does a utf8 round trip- either Left (Right . decodeControl) (parseControl sourceName (encodeUtf8 c))- lookupP fieldName (Paragraph paragraph) =- find (hasFieldName (map toLower fieldName)) paragraph- where hasFieldName :: String -> Field' T.Text -> Bool- hasFieldName name (Field (fieldName',_)) = T.pack name == T.map toLower fieldName'- hasFieldName _ _ = False- stripWS = T.dropAround (`elem` (" \t" :: String))- -- T.strip would also strip newlines- protectFieldText = protectFieldText'- asString = T.unpack---- * Control File Parser-{---- type ControlParser = GenParser T.Text-type ControlParser a = Parsec T.Text () a---- |A parser for debian control file. This parser handles control files--- that end without a newline as well as ones that have several blank--- lines at the end. It is very liberal and does not attempt validate--- the fields in any way. All trailing, leading, and folded whitespace--- is preserved in the field values. See 'stripWS'.-pControl :: ControlParser Control-pControl =- do many $ char '\n'- sepEndBy pParagraph pBlanks >>= return . Control--pParagraph :: ControlParser Paragraph-pParagraph = many1 (pComment <|> pField) >>= return . Paragraph---- |We are liberal in that we allow *any* field to have folded white--- space, even though the specific restricts that to a few fields.-pField :: ControlParser Field-pField =- do c1 <- noneOf "#\n"- fieldName <- many1 $ noneOf ":\n"- char ':'- fieldValue <- many fcharfws- (char '\n' >> return ()) <|> eof- return $ Field (T.cons c1 (T.pack fieldName), T.pack fieldValue)--pComment :: ControlParser Field-pComment =- do char '#'- text <- many (satisfy (not . ((==) '\n')))- char '\n'- return $ Comment (T.pack ("#" <> text <> "\n"))--fcharfws :: ControlParser Char-fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')--fchar :: ControlParser Char-fchar = satisfy (/='\n')--_fws :: ControlParser T.Text-_fws =- try $ do char '\n'- ws <- many1 (char ' ')- c <- many1 (satisfy (not . ((==) '\n')))- return $ T.cons '\n' (T.pack ws <> T.pack c)---- |We go with the assumption that 'blank lines' mean lines that--- consist of entirely of zero or more whitespace characters.-pBlanks :: ControlParser T.Text-pBlanks =- do s <- many1 (oneOf " \n")- return . T.pack $ s--}
− Debian/Control/TextLazy.hs
@@ -1,155 +0,0 @@-{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, TypeSynonymInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}-module Debian.Control.TextLazy- ( -- * Types- Control'(..)- , Paragraph'(..)- , Field'(..)- , Control- , Paragraph- , Field- -- , ControlParser- , ControlFunctions(..)- -- * Control File Parser- -- , pControl- -- * Helper Functions- , mergeControls- , fieldValue- , removeField- , prependFields- , appendFields- , renameField- , modifyField- , raiseFields- , decodeControl- , decodeParagraph- , decodeField- ) where--import qualified Data.ByteString.Char8 as B-import Data.Char (toLower, chr)-import Data.List (find)-import qualified Data.Text.Lazy as T (Text, pack, unpack, map, dropAround, reverse, fromStrict, toStrict)-import Data.Text.Encoding (decodeUtf8With, encodeUtf8)---import Data.Text.IO as T (readFile)-import qualified Debian.Control.ByteString as B---import Text.Parsec.Error (ParseError)---import Text.Parsec.Text (Parser)---import Text.Parsec.Prim (runP)-import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, asString),- Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),- mergeControls, fieldValue, removeField, prependFields, appendFields,- renameField, modifyField, raiseFields, protectFieldText')---- | @parseFromFile p filePath@ runs a string parser @p@ on the--- input read from @filePath@ using 'Prelude.readFile'. Returns either a 'ParseError'--- ('Left') or a value of type @a@ ('Right').------ > main = do{ result <- parseFromFile numbers "digits.txt"--- > ; case result of--- > Left err -> print err--- > Right xs -> print (sum xs)--- > }-{--parseFromFile :: Parser a -> String -> IO (Either ParseError a)-parseFromFile p fname- = do input <- T.readFile fname `E.catch` (\ (_ :: E.SomeException) -> B.readFile fname >>= return . decode)- return (runP p () fname input)--}--type Field = Field' T.Text-type Control = Control' T.Text-type Paragraph = Paragraph' T.Text--decodeControl :: B.Control -> Control-decodeControl (B.Control paragraphs) = Control (map decodeParagraph paragraphs)--decodeParagraph :: B.Paragraph -> Paragraph-decodeParagraph (B.Paragraph s) = B.Paragraph (map decodeField s)--decodeField :: Field' B.ByteString -> Field' T.Text-decodeField (B.Field (name, value)) = Field (decode name, decode value)-decodeField (B.Comment s) = Comment (decode s)--decode :: B.ByteString -> T.Text-decode = T.fromStrict . decodeUtf8With (\ _ w -> fmap (chr . fromIntegral) w)---- * ControlFunctions--instance ControlFunctions T.Text where- parseControlFromFile filepath =- -- The ByteString parser is far more efficient than the Text- -- parser. By calling decodeControl we tell the compiler to- -- use it instead.- parseControlFromFile filepath >>= return . either Left (Right . decodeControl)- parseControlFromHandle sourceName handle =- parseControlFromHandle sourceName handle >>= return . either Left (Right . decodeControl)- parseControl sourceName c =- -- Warning: This is very slow, it does a utf8 round trip- either Left (Right . decodeControl) (parseControl sourceName (encodeUtf8 (T.toStrict c)))- lookupP fieldName (Paragraph paragraph) =- find (hasFieldName (map toLower fieldName)) paragraph- where hasFieldName :: String -> Field' T.Text -> Bool- hasFieldName name (Field (fieldName',_)) = T.pack name == T.map toLower fieldName'- hasFieldName _ _ = False- stripWS = T.dropAround (`elem` (" \t" :: String))- -- T.strip would also strip newlines- protectFieldText = protectFieldText'- asString = T.unpack---- * Control File Parser-{---- type ControlParser = GenParser T.Text-type ControlParser a = Parsec T.Text () a---- |A parser for debian control file. This parser handles control files--- that end without a newline as well as ones that have several blank--- lines at the end. It is very liberal and does not attempt validate--- the fields in any way. All trailing, leading, and folded whitespace--- is preserved in the field values. See 'stripWS'.-pControl :: ControlParser Control-pControl =- do many $ char '\n'- sepEndBy pParagraph pBlanks >>= return . Control--pParagraph :: ControlParser Paragraph-pParagraph = many1 (pComment <|> pField) >>= return . Paragraph---- |We are liberal in that we allow *any* field to have folded white--- space, even though the specific restricts that to a few fields.-pField :: ControlParser Field-pField =- do c1 <- noneOf "#\n"- fieldName <- many1 $ noneOf ":\n"- char ':'- fieldValue <- many fcharfws- (char '\n' >> return ()) <|> eof- return $ Field (T.cons c1 (T.pack fieldName), T.pack fieldValue)--pComment :: ControlParser Field-pComment =- do char '#'- text <- many (satisfy (not . ((==) '\n')))- char '\n'- return $ Comment (T.pack ("#" <> text <> "\n"))--fcharfws :: ControlParser Char-fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')--fchar :: ControlParser Char-fchar = satisfy (/='\n')--_fws :: ControlParser T.Text-_fws =- try $ do char '\n'- ws <- many1 (char ' ')- c <- many1 (satisfy (not . ((==) '\n')))- return $ T.cons '\n' (T.pack ws <> T.pack c)---- |We go with the assumption that 'blank lines' mean lines that--- consist of entirely of zero or more whitespace characters.-pBlanks :: ControlParser T.Text-pBlanks =- do s <- many1 (oneOf " \n")- return . T.pack $ s--}
− Debian/Deb.hs
@@ -1,24 +0,0 @@-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}-module Debian.Deb where--import Control.Monad--import Debian.Control.Common-import System.Directory (canonicalizePath)-import System.Exit (ExitCode(..))-import System.Process (readProcessWithExitCode)-import System.Unix.Directory (withTemporaryDirectory, withWorkingDirectory)--fields :: (ControlFunctions a) => FilePath -> IO (Control' a)-fields debFP =- withTemporaryDirectory ("fields.XXXXXX") $ \tmpdir ->- do debFP <- canonicalizePath debFP- withWorkingDirectory tmpdir $- do (res, out, err) <- readProcessWithExitCode "ar" ["x",debFP,"control.tar.gz"] ""- when (res /= ExitSuccess) (error $ "Dpkg.fields: " ++ show out ++ "\n" ++ show err ++ "\n" ++ show res)- (res, out, err) <- readProcessWithExitCode "tar" ["xzf", "control.tar.gz", "./control"] ""- when (res /= ExitSuccess) (error $ "Dpkg.fields: " ++ show out ++ "\n" ++ show err ++ "\n" ++ show res)- c <- parseControlFromFile "control"- case c of- Left e -> error (show e)- (Right c) -> return c -- I don't think we need seq because parsec will force everything from the file
− Debian/Extra/Files.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE PackageImports #-}--- |Domain independent functions used by the haskell-debian package.-module Debian.Extra.Files- ( withTemporaryFile- ) where--import "mtl" Control.Monad.Trans (MonadIO, liftIO)-import System.Directory (getTemporaryDirectory, removeFile)-import System.IO (hPutStr, hClose, openBinaryTempFile)--withTemporaryFile :: MonadIO m- => (FilePath -> m a) -- ^ The function we want to pass a FilePath to- -> String -- ^ The text that the file should contain- -> m a -- ^ The function's return value-withTemporaryFile f text =- do path <- liftIO writeTemporaryFile- result <- f path- liftIO $ removeFile path- return result- where- writeTemporaryFile =- do dir <- getTemporaryDirectory- (path, h) <- openBinaryTempFile dir "wtf.tmp"- hPutStr h text- hClose h- return path---- Example: write the path of the temporary file and its contents into /tmp/result:--- test =--- withTemporaryFile f "Some text\n"--- where f path = readFile path >>= return . (("Contents of " ++ path ++ ":\n") ++) >>= writeFile "/tmp/result"
− Debian/GenBuildDeps.hs
@@ -1,330 +0,0 @@-{-# LANGUAGE CPP, FlexibleContexts, OverloadedStrings, RecordWildCards, ScopedTypeVariables, TemplateHaskell #-}-{-# LANGUAGE NoDisambiguateRecordFields, NoRecordWildCards #-}--- |Figure out the dependency relation between debianized source--- directories. The code to actually solve these dependency relations--- for a particular set of binary packages is in Debian.Repo.Dependency.-module Debian.GenBuildDeps- ( DepInfo(..)- , sourceName'- , relations'- , binaryNames'- -- * Preparing dependency info- , buildDependencies- , RelaxInfo- , relaxDeps- -- * Using dependency info- , BuildableInfo(..)- , ReadyTarget(..)- , buildable- , compareSource- -- * Obsolete?- , orderSource- , genDeps- , failPackage- , getSourceOrder- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif-import Control.Exception (throw)-import Control.Monad (filterM, foldM)-import Control.Monad.State (evalState, get, modify, State)-import Data.Graph (Graph, Edge, Vertex, buildG, topSort, reachable, transposeG, edges, scc)-import Data.List as List (elemIndex, find, map, nub, partition, tails)-import Data.Map as Map (empty, findWithDefault, fromList, insert, Map, lookup)-import Data.Maybe-import Data.Set as Set (fromList, intersection, null, Set)-import Data.Tree as Tree (Tree(Node, rootLabel, subForest))-import Debian.Control (parseControlFromFile)-import Debian.Control.Policy (HasDebianControl, DebianControl, ControlFileError(..), validateDebianControl, debianSourcePackageName, debianBinaryPackageNames, debianBuildDeps, debianBuildDepsIndep)-import Debian.Loc (__LOC__)-import Debian.Relation-import Debian.Relation.Text ()--- import Debug.Trace (trace)-import System.Directory (getDirectoryContents, doesFileExist)---- | This type describes the build dependencies of a source package.-data DepInfo = DepInfo {- sourceName :: SrcPkgName -- ^ source package name- , relations :: Relations -- ^ dependency relations- , binaryNames :: [BinPkgName] -- ^ binary dependency names (is this a function of relations?)- , depSet :: Set.Set BinPkgName -- ^ Set containing all binary package names mentioned in relations- , binSet :: Set.Set BinPkgName -- ^ Set containing binaryNames- } deriving Show--instance Eq DepInfo where- a == b = (sourceName a == sourceName b) &&- Set.fromList (map Set.fromList (relations a)) == Set.fromList (map Set.fromList (relations b)) &&- Set.fromList (binaryNames a) == Set.fromList (binaryNames b)---- |Return the dependency info for a source package with the given dependency relaxation.--- |According to debian policy, only the first paragraph in debian\/control can be a source package--- <http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-sourcecontrolfiles>-buildDependencies :: HasDebianControl control => control -> DepInfo-buildDependencies control = do- let rels = concat [fromMaybe [] (debianBuildDeps control),- fromMaybe [] (debianBuildDepsIndep control)]- bins = debianBinaryPackageNames control- DepInfo { sourceName = debianSourcePackageName control- , relations = rels- , binaryNames = bins- , depSet = Set.fromList (List.map (\(Rel x _ _) -> x) (concat rels))- , binSet = Set.fromList bins }---- | source package name-sourceName' :: HasDebianControl control => control -> SrcPkgName-sourceName' control = debianSourcePackageName control---- | dependency relations-relations' :: HasDebianControl control => control -> Relations-relations' control = concat [fromMaybe [] (debianBuildDeps control),- fromMaybe [] (debianBuildDepsIndep control)]---- | binary dependency names (is this a function of relations?)-binaryNames' :: HasDebianControl control => control -> [BinPkgName]-binaryNames' control = debianBinaryPackageNames control---- |Specifies build dependencies that should be ignored during the build--- decision. If the pair is (BINARY, Nothing) it means the binary package--- BINARY should always be ignored when deciding whether to build. If the--- pair is (BINARY, Just SOURCE) it means that binary package BINARY should--- be ignored when deiciding whether to build package SOURCE.-newtype OldRelaxInfo = RelaxInfo [(BinPkgName, Maybe SrcPkgName)] deriving Show---- | Given a source package name and a binary package name, return--- False if the binary package should be ignored hwen deciding whether--- to build the source package. This is used to prevent build--- dependency cycles from triggering unnecessary rebuilds. (This is a--- replacement for the RelaxInfo type, which we temporarily rename--- OldRelaxInfo.)-type RelaxInfo = SrcPkgName -> BinPkgName -> Bool---- |Remove any dependencies that are designated \"relaxed\" by relaxInfo.-relaxDeps :: RelaxInfo -> [DepInfo] -> [DepInfo]-relaxDeps relaxInfo deps =- List.map relaxDep deps- where- relaxDep :: DepInfo -> DepInfo- relaxDep info = info {relations = filteredDependencies}- where- -- Discard any dependencies not on the filtered package name list. If- -- this results in an empty list in an or-dep the entire dependency can- -- be discarded.- filteredDependencies :: Relations- filteredDependencies = filter (/= []) (List.map (filter keepDep) (relations info))- keepDep :: Relation -> Bool- keepDep (Rel name _ _) = not (relaxInfo (sourceName info) name)--data ReadyTarget a- = ReadyTarget { ready :: a- -- ^ Some target whose build dependencies are all satisfied- , waiting :: [a]- -- ^ The targets that are waiting for the ready target- , other :: [a]- -- ^ The rest of the targets that need to be built- }--data BuildableInfo a- = BuildableInfo- { readyTargets :: [ReadyTarget a]- , allBlocked :: [a] }- | CycleInfo- { depPairs :: [(a, a)] }---- | Given an ordering function representing the dependencies on a--- list of packages, return a ReadyTarget triple: One ready package,--- the packages that depend on the ready package directly or--- indirectly, and all the other packages.-buildable :: forall a. (a -> DepInfo) -> [a] -> BuildableInfo a-buildable relax packages =- -- Find all packages which can't reach any other packages in the- -- graph of the "has build dependency" relation on the- -- yet-to-be-built packages- case partition (\ x -> reachable hasDep x == [x]) verts of- -- None of the packages are buildable, return information- -- about how to break this build dependency cycle.- ([], _) -> CycleInfo {depPairs = List.map ofEdge $ head $ (allCycles hasDep)}- -- We have some buildable packages, return them along with- -- the list of packages each one directly blocks- (allReady, blocked) ->- BuildableInfo { readyTargets = List.map (makeReady blocked allReady) allReady- , allBlocked = List.map ofVertex blocked }- where- makeReady :: [Vertex] -> [Vertex] -> Vertex -> ReadyTarget a- makeReady blocked ready thisReady =- let otherReady = filter (/= thisReady) ready- (directlyBlocked, otherBlocked) = partition (\ x -> elem x (reachable isDep thisReady)) blocked in- ReadyTarget { ready = ofVertex thisReady- , waiting = List.map ofVertex directlyBlocked- , other = List.map ofVertex (otherReady ++ otherBlocked) }- --allDeps x = (ofVertex x, List.map ofVertex (filter (/= x) (reachable hasDep x)))- isDep :: Graph- isDep = transposeG hasDep- hasDep :: Graph- hasDep = buildG (0, length packages - 1) hasDepEdges-- hasDepEdges :: [(Int, Int)]- hasDepEdges =-#if 0- nub (foldr f [] (tails vertPairs))- where f :: [(Int, DepInfo)] -> [(Int, Int)] -> [(Int, Int)]- f [] es = es- f (x : xs) es = catMaybes (List.map (toEdge x) xs) ++ es- toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> Maybe Edge- toEdge (xv, xa) (yv, ya) =- case compareSource xa ya of- EQ -> Nothing- LT -> Just (yv, xv)- GT -> Just (xv, yv)-#else- nub (evalState (foldM f [] (tails vertPairs)) Map.empty)- where f :: [(Int, Int)] -> [(Int, DepInfo)] -> State (Map.Map (Int, Int) Ordering) [(Int, Int)]- f es [] = return es- f es (x : xs) = mapM (toEdge x) xs >>= \es' -> return (catMaybes es' ++ es)- toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> State (Map.Map (Int, Int) Ordering) (Maybe Edge)- toEdge (xv, xa) (yv, ya) = do- mp <- get- r <- case Map.lookup (xv, yv) mp of- Just r' -> return r'- Nothing -> do- let r' = compareSource xa ya- -- trace ("compareSource " ++ show (unSrcPkgName $ sourceName xa) ++ " " ++ show (unSrcPkgName $ sourceName ya) ++ " -> " ++ show r') (return ())- modify (Map.insert (xv, yv) r')- return r'- case r of- EQ -> return Nothing- LT -> return $ Just (yv, xv)- GT -> return $ Just (xv, yv)-#endif- ofEdge :: Edge -> (a, a)- ofEdge (a, b) = (ofVertex a, ofVertex b)- ofVertex :: Int -> a- ofVertex n = fromJust (Map.findWithDefault Nothing n (Map.fromList (zip [0..] (map Just packages))))- verts :: [Int]- verts = map fst vertPairs- vertPairs :: [(Int, DepInfo)]- vertPairs = zip [0..] $ map relax packages---- | Find a cycle in a graph that involves-allCycles :: Graph -> [[Edge]]-allCycles g =- -- Every cycle is confined to an SCC (strongly connected component).- -- Every node in an SCC is part of some cycle.- concatMap sccCycles (scc g)- where- -- Find all the cycles in an SCC- sccCycles :: Tree Vertex -> [[Edge]]- sccCycles t = mapMaybe addBackEdge (treePaths t)-- addBackEdge :: [Vertex] -> Maybe [Edge]- addBackEdge path@(root : _) =- let back = (last path, root) in- if elem back (edges g) then Just (pathEdges (path ++ [root])) else Nothing---- | All the paths from root to a leaf-treePaths :: Tree a -> [[a]]-treePaths (Node {rootLabel = r, subForest = []}) = [[r]]-treePaths (Node {rootLabel = r, subForest = ts}) = map (r :) (concatMap treePaths ts)--pathEdges :: [a] -> [(a, a)]-pathEdges (v1 : v2 : vs) = (v1, v2) : pathEdges (v2 : vs)-pathEdges _ = []---- | Remove any packages which can't be built given that a package has failed.-failPackage :: Eq a => (a -> a -> Ordering) -> a -> [a] -> ([a], [a])-failPackage cmp failed packages =- let graph = buildGraph cmp packages in- let root = elemIndex failed packages in- let victims = maybe [] (map (fromJust . vertex) . reachable graph) root in- partition (\ x -> not . elem x $ victims) packages- where- vertex n = Map.findWithDefault Nothing n vertexMap- vertexMap = Map.fromList (zip [0..] (map Just packages))---- | Given a list of packages, sort them according to their apparant--- build dependencies so that the first element doesn't depend on any--- of the other packages.-orderSource :: (a -> a -> Ordering) -> [a] -> [a]-orderSource cmp packages =- map (fromJust . vertex) (topSort graph)- where- graph = buildGraph cmp packages- vertex n = Map.findWithDefault Nothing n vertexMap- vertexMap = Map.fromList (zip [0..] (map Just packages))---- | Build a graph with the list of packages as its nodes and the--- build dependencies as its edges.-buildGraph :: (a -> a -> Ordering) -> [a] -> Graph-buildGraph cmp packages =- let es = someEdges (zip packages [0..]) in- buildG (0, length packages - 1) es- where- someEdges [] = []- someEdges (a : etc) = aEdges a etc ++ someEdges etc- aEdges (ap, an) etc =- concat (map (\ (bp, bn) ->- case cmp ap bp of- LT -> [(an, bn)]- GT -> [(bn, an)]- EQ -> []) etc)---- |This is a nice start. It ignores circular build depends and takes--- a pretty simplistic approach to 'or' build depends. However, I--- think this should work pretty nicely in practice.-compareSource :: DepInfo -> DepInfo -> Ordering-compareSource p1 p2-#if 0- | any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p2))) (concat (relations p1)) = GT- | any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p1))) (concat (relations p2)) = LT- | otherwise = EQ- where- checkPackageNameReq :: Relation -> BinPkgName -> Bool- checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName-#else- | not (Set.null (Set.intersection (depSet p1) (binSet p2))) = GT- | not (Set.null (Set.intersection (depSet p2) (binSet p1))) = LT- | otherwise = EQ-#endif--compareSource' :: HasDebianControl control => control -> control -> Ordering-compareSource' control1 control2- | any (\rel -> isJust (find (checkPackageNameReq rel) bins2)) (concat depends1) = GT- | any (\rel -> isJust (find (checkPackageNameReq rel) bins1)) (concat depends2) = LT- | otherwise = EQ- where- bins1 = binaryNames' control1- bins2 = binaryNames' control2- depends1 = relations' control1- depends2 = relations' control2- checkPackageNameReq :: Relation -> BinPkgName -> Bool- checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName---- |Return the dependency info for a list of control files.-genDeps :: [FilePath] -> IO [DebianControl]-genDeps controlFiles = do- orderSource compareSource' <$> mapM genDep' controlFiles- where- -- Parse the control file and extract the build dependencies- genDep' controlPath = parseControlFromFile controlPath >>=- either (\ x -> throw (ParseRelationsError [$__LOC__] x))- (\ x -> validateDebianControl x {- `mapExn` (pushLoc $__LOC__) -} >>= either throw return)---- pushLoc :: Loc -> ControlFileError -> ControlFileError--- pushLoc loc e = e {locs = loc : locs e}---- |One example of how to tie the below functions together. In this--- case 'fp' is the path to a directory that contains a bunch of--- checked out source packages. The code will automatically look for--- debian\/control. It returns a list with the packages in the--- order they should be built.-getSourceOrder :: FilePath -> IO [SrcPkgName]-getSourceOrder fp =- findControlFiles fp >>= genDeps >>= return . map sourceName'- where- -- Return a list of the files that look like debian\/control.- findControlFiles :: FilePath -> IO [FilePath]- findControlFiles root =- getDirectoryContents root >>=- mapM (\ x -> return $ root ++ "/" ++ x ++ "/debian/control") >>=- filterM doesFileExist
− Debian/Loc.hs
@@ -1,23 +0,0 @@-{-# LANGUAGE ScopedTypeVariables, StandaloneDeriving, TemplateHaskell #-}-module Debian.Loc- ( __LOC__- , mapExn- ) where--import Control.Applicative ((<$>), (<*>), pure)-import Control.Exception (Exception, throw)-import Control.Monad.Catch (MonadCatch, catch)-import Language.Haskell.TH--__LOC__ :: Q Exp-__LOC__ = location >>= \ x ->- recConE 'Loc [ (,) <$> (pure 'loc_filename) <*> litE (stringL (loc_filename x))- , (,) <$> (pure 'loc_package) <*> litE (stringL (loc_package x))- , (,) <$> (pure 'loc_module) <*> litE (stringL (loc_module x))- , (,) <$> (pure 'loc_start) <*> [|($(litE (integerL (fromIntegral (fst (loc_start x))))),- $(litE (integerL (fromIntegral (snd (loc_start x)))))) :: (Int, Int)|]- , (,) <$> (pure 'loc_end) <*> [|($(litE (integerL (fromIntegral (fst (loc_end x))))),- $(litE (integerL (fromIntegral (snd (loc_end x)))))) :: (Int, Int)|] ]--mapExn :: forall e m a. (MonadCatch m, Exception e) => m a -> (e -> e) -> m a-mapExn task f = task `catch` (\ (e :: e) -> throw (f e))
− Debian/Pretty.hs
@@ -1,46 +0,0 @@--- | A constructor we can wrap around values to avoid any built in--- Pretty instance - for example, instance Pretty [a].------ * display is now prettyShow--- * display' is now prettyText--- * ppDisplay is now ppShow--- * ppDisplay' is now ppText-{-# LANGUAGE DeriveFunctor, FlexibleContexts, FlexibleInstances, OverloadedStrings, TypeSynonymInstances #-}-module Debian.Pretty- ( PP(PP, unPP)- , prettyShow- , prettyText- , ppPrint- , ppShow- , ppText- ) where--import Data.Text (Text, unpack, pack)-import Text.PrettyPrint.HughesPJClass (Doc, Pretty(pPrint), text, empty, prettyShow)---- | This type is wrapped around values before we pretty print them so--- we can write our own Pretty instances for common types without--- polluting the name space of clients of this package with instances--- they don't want.-newtype PP a = PP {unPP :: a} deriving (Functor)--instance Pretty (PP Text) where- pPrint = text . unpack . unPP--instance Pretty (PP String) where- pPrint = text . unPP--instance Pretty (PP a) => Pretty (PP (Maybe a)) where- pPrint = maybe empty ppPrint . unPP--prettyText :: Pretty a => a -> Text-prettyText = pack . prettyShow--ppPrint :: Pretty (PP a) => a -> Doc-ppPrint = pPrint . PP--ppShow :: Pretty (PP a) => a -> String-ppShow = prettyShow . PP--ppText :: Pretty (PP a) => a -> Text-ppText = pack . prettyShow . PP
− Debian/Relation.hs
@@ -1,25 +0,0 @@--- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>-module Debian.Relation- ( -- * Types- PkgName(..)- , SrcPkgName(..)- , BinPkgName(..)- , Relations- , AndRelation- , OrRelation- , Relation(..)- , ArchitectureReq(..)- , Arch(..)- , ArchOS(..)- , ArchCPU(..)- , VersionReq(..)- -- * Helper Functions- , checkVersionReq- -- * Relation Parser- , RelParser- , ParseRelations(..)- ) where--import Debian.Arch (Arch(..), ArchOS(..), ArchCPU(..))-import Debian.Relation.Common (SrcPkgName(..), BinPkgName(..), PkgName(pkgNameFromString))-import Debian.Relation.String
− Debian/Relation/ByteString.hs
@@ -1,30 +0,0 @@-{-# OPTIONS -fno-warn-orphans #-}--- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>-module Debian.Relation.ByteString- ( -- * Types- AndRelation- , OrRelation- , Relations- , Relation(..)- , ArchitectureReq(..)- , VersionReq(..)- -- * Helper Functions- , checkVersionReq- -- * Relation Parser- , RelParser- , ParseRelations(..)- ) where--import qualified Data.ByteString.Char8 as C---- Local Modules----import Debian.Relation.Common-import Debian.Relation.String---import Debian.Version---- * ParseRelations---- For now we just wrap the string version-instance ParseRelations C.ByteString where- parseRelations byteStr = parseRelations (C.unpack byteStr)
− Debian/Relation/Common.hs
@@ -1,132 +0,0 @@-{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, FlexibleInstances, OverloadedStrings, TypeSynonymInstances #-}-module Debian.Relation.Common where---- Standard GHC Modules--import Data.Data (Data)-import Data.List as List (map, intersperse)-import Data.Monoid (mconcat, (<>))-import Data.Function-import Data.Set as Set (Set, toList)-import Data.Typeable (Typeable)-import Debian.Arch (Arch, prettyArch)-import Debian.Pretty (PP(..))-import Prelude hiding (map)-import Text.ParserCombinators.Parsec-import Text.PrettyPrint (Doc, text, empty)-import Text.PrettyPrint.HughesPJClass (Pretty(pPrint))---- Local Modules--import Debian.Version---- Datatype for relations--type Relations = AndRelation-type AndRelation = [OrRelation]-type OrRelation = [Relation]--data Relation = Rel BinPkgName (Maybe VersionReq) (Maybe ArchitectureReq) deriving (Eq, Read, Show)--newtype SrcPkgName = SrcPkgName {unSrcPkgName :: String} deriving (Read, Show, Eq, Ord, Data, Typeable)-newtype BinPkgName = BinPkgName {unBinPkgName :: String} deriving (Read, Show, Eq, Ord, Data, Typeable)--class Pretty (PP a) => PkgName a where- pkgNameFromString :: String -> a--instance PkgName BinPkgName where- pkgNameFromString = BinPkgName--instance PkgName SrcPkgName where- pkgNameFromString = SrcPkgName--class ParseRelations a where- -- |'parseRelations' parse a debian relation (i.e. the value of a- -- Depends field). Return a parsec error or a value of type- -- 'Relations'- parseRelations :: a -> Either ParseError Relations---- | This needs to be indented for use in a control file: intercalate "\n " . lines . show-prettyRelations :: [[Relation]] -> Doc-prettyRelations xss = mconcat . intersperse (text "\n, ") . List.map prettyOrRelation $ xss--prettyOrRelation :: [Relation] -> Doc-prettyOrRelation xs = mconcat . intersperse (text " | ") . List.map prettyRelation $ xs--prettyRelation :: Relation -> Doc-prettyRelation (Rel name ver arch) =- pPrint (PP name) <> maybe empty prettyVersionReq ver <> maybe empty prettyArchitectureReq arch--instance Ord Relation where- compare (Rel pkgName1 mVerReq1 _mArch1) (Rel pkgName2 mVerReq2 _mArch2) =- case compare pkgName1 pkgName2 of- LT -> LT- GT -> GT- EQ -> compare mVerReq1 mVerReq2--data ArchitectureReq- = ArchOnly (Set Arch)- | ArchExcept (Set Arch)- deriving (Eq, Ord, Read, Show)--prettyArchitectureReq :: ArchitectureReq -> Doc-prettyArchitectureReq (ArchOnly arch) = text " [" <> mconcat (List.map prettyArch (toList arch)) <> text "]"-prettyArchitectureReq (ArchExcept arch) = text " [" <> mconcat (List.map ((text "!") <>) (List.map prettyArch (toList arch))) <> text "]"--data VersionReq- = SLT DebianVersion- | LTE DebianVersion- | EEQ DebianVersion- | GRE DebianVersion- | SGR DebianVersion- deriving (Eq, Read, Show)--prettyVersionReq :: VersionReq -> Doc-prettyVersionReq (SLT v) = text " (<< " <> prettyDebianVersion v <> text ")"-prettyVersionReq (LTE v) = text " (<= " <> prettyDebianVersion v <> text ")"-prettyVersionReq (EEQ v) = text " (= " <> prettyDebianVersion v <> text ")"-prettyVersionReq (GRE v) = text " (>= " <> prettyDebianVersion v <> text ")"-prettyVersionReq (SGR v) = text " (>> " <> prettyDebianVersion v <> text ")"---- |The sort order is based on version number first, then on the kind of--- relation, sorting in the order <<, <= , ==, >= , >>-instance Ord VersionReq where- compare = compare `on` extr- where extr (SLT v) = (v,0 :: Int)- extr (LTE v) = (v,1 :: Int)- extr (EEQ v) = (v,2 :: Int)- extr (GRE v) = (v,3 :: Int)- extr (SGR v) = (v,4 :: Int)---- |Check if a version number satisfies a version requirement.-checkVersionReq :: Maybe VersionReq -> Maybe DebianVersion -> Bool-checkVersionReq Nothing _ = True-checkVersionReq _ Nothing = False-checkVersionReq (Just (SLT v1)) (Just v2) = v2 < v1-checkVersionReq (Just (LTE v1)) (Just v2) = v2 <= v1-checkVersionReq (Just (EEQ v1)) (Just v2) = v2 == v1-checkVersionReq (Just (GRE v1)) (Just v2) = v2 >= v1-checkVersionReq (Just (SGR v1)) (Just v2) = v2 > v1--instance Pretty (PP BinPkgName) where- pPrint = text . unBinPkgName . unPP--instance Pretty (PP SrcPkgName) where- pPrint = text . unSrcPkgName . unPP---- | Wrap `PP` around type synonyms that might overlap with the--- `Pretty [a]` instance.-instance Pretty (PP Relations) where- pPrint = prettyRelations . unPP--instance Pretty (PP OrRelation) where- pPrint = prettyOrRelation . unPP--instance Pretty (PP Relation) where- pPrint = prettyRelation . unPP--instance Pretty (PP VersionReq) where- pPrint = prettyVersionReq . unPP--instance Pretty (PP ArchitectureReq) where- pPrint = prettyArchitectureReq . unPP
− Debian/Relation/String.hs
@@ -1,139 +0,0 @@-{-# LANGUAGE FlexibleInstances, PackageImports, TypeSynonymInstances #-}-{-# OPTIONS -fno-warn-unused-do-bind -fno-warn-orphans #-}--- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>-module Debian.Relation.String- ( -- * Types- AndRelation- , OrRelation- , Relations- , Relation(..)- , ArchitectureReq(..)- , VersionReq(..)- -- * Helper Functions- , checkVersionReq- -- * Relation Parser- , RelParser- , ParseRelations(..)- , pRelations- ) where---- Standard GHC Modules--import "mtl" Control.Monad.Identity (Identity)-import Data.Set (fromList)-import Text.ParserCombinators.Parsec-import Text.Parsec.Prim (ParsecT)---- Local Modules--import Debian.Arch (Arch, parseArch)-import Debian.Relation.Common-import Debian.Version---- * ParseRelations--instance ParseRelations String where- parseRelations str =- let str' = scrub str in- case parse pRelations str' str' of- Right relations -> Right (filter (/= []) relations)- x -> x- where- scrub = unlines . filter (not . comment) . lines- comment s = case dropWhile (`elem` " \t") s of- ('#' : _) -> True- _ -> False---- * Relation Parser--type RelParser a = CharParser () a---- "Correct" dependency lists are separated by commas, but sometimes they--- are omitted and it is possible to parse relations without them.-pRelations :: RelParser Relations-pRelations = do -- rel <- sepBy pOrRelation (char ',')- rel <- many pOrRelation- eof- return rel--pOrRelation :: RelParser OrRelation-pOrRelation = do skipMany (char ',' <|> whiteChar)- rel <- sepBy1 pRelation (char '|')- skipMany (char ',' <|> whiteChar)- return rel--whiteChar :: ParsecT String u Identity Char-whiteChar = oneOf [' ','\t','\n']--pRelation :: RelParser Relation-pRelation =- do skipMany whiteChar- pkgName <- many1 (noneOf [' ',',','|','\t','\n','('])- skipMany whiteChar- mVerReq <- pMaybeVerReq- skipMany whiteChar- mArch <- pMaybeArch- return $ Rel (BinPkgName pkgName) mVerReq mArch--pMaybeVerReq :: RelParser (Maybe VersionReq)-pMaybeVerReq =- do char '('- skipMany whiteChar- op <- pVerReq- skipMany whiteChar- ver <- many1 (noneOf [' ',')','\t','\n'])- skipMany whiteChar- char ')'- return $ Just (op (parseDebianVersion' ver))- <|>- do return $ Nothing--pVerReq :: ParsecT [Char] u Identity (DebianVersion -> VersionReq)-pVerReq =- do char '<'- (do char '<' <|> char ' ' <|> char '\t'- return $ SLT- <|>- do char '='- return $ LTE)- <|>- do string "="- return $ EEQ- <|>- do char '>'- (do char '='- return $ GRE- <|>- do char '>' <|> char ' ' <|> char '\t'- return $ SGR)--pMaybeArch :: RelParser (Maybe ArchitectureReq)-pMaybeArch =- do char '['- (do archs <- pArchExcept- char ']'- skipMany whiteChar- return (Just (ArchExcept (fromList . map parseArchExcept $ archs)))- <|>- do archs <- pArchOnly- char ']'- skipMany whiteChar- return (Just (ArchOnly (fromList . map parseArch $ archs)))- )- <|>- return Nothing---- Some packages (e.g. coreutils) have architecture specs like [!i386--- !hppa], even though this doesn't really make sense: once you have--- one !, anything else you include must also be (implicitly) a !.-pArchExcept :: RelParser [String]-pArchExcept = sepBy (char '!' >> many1 (noneOf [']',' '])) (skipMany1 whiteChar)--pArchOnly :: RelParser [String]-pArchOnly = sepBy (many1 (noneOf [']',' '])) (skipMany1 whiteChar)---- | Ignore the ! if it is present, we already know this list has at--- least one, and the rest are implicit.-parseArchExcept :: String -> Arch-parseArchExcept ('!' : s) = parseArch s-parseArchExcept s = parseArch s
− Debian/Relation/Text.hs
@@ -1,30 +0,0 @@-{-# OPTIONS -fno-warn-orphans #-}--- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>-module Debian.Relation.Text- ( -- * Types- AndRelation- , OrRelation- , Relations- , Relation(..)- , ArchitectureReq(..)- , VersionReq(..)- -- * Helper Functions- , checkVersionReq- -- * Relation Parser- , RelParser- , ParseRelations(..)- ) where--import qualified Data.Text as T---- Local Modules----import Debian.Relation.Common-import Debian.Relation.String---import Debian.Version---- * ParseRelations---- For now we just wrap the string version-instance ParseRelations T.Text where- parseRelations text = parseRelations (T.unpack text)
− Debian/Release.hs
@@ -1,60 +0,0 @@-{-# LANGUAGE DeriveDataTypeable #-}-module Debian.Release- ( ReleaseName(..)- , parseReleaseName- , releaseName'- , Section(..)- , SubSection(..)- , sectionName- , sectionName'- , sectionNameOfSubSection- , parseSection- , parseSection'- ) where--import Data.Data (Data)-import Data.Typeable (Typeable)-import Network.URI (unEscapeString, escapeURIString, isAllowedInURI)---- |A distribution (aka release) name. This type is expected to refer--- to a subdirectory of the dists directory which is at the top level--- of a repository.-data ReleaseName = ReleaseName { relName :: String } deriving (Eq, Ord, Read, Show, Data, Typeable)--parseReleaseName :: String -> ReleaseName-parseReleaseName name = ReleaseName {relName = unEscapeString name}--releaseName' :: ReleaseName -> String-releaseName' (ReleaseName {relName = s}) = escapeURIString isAllowedInURI s---- |A section of a repository such as main, contrib, non-free,--- restricted. The indexes for a section are located below the--- distribution directory.-newtype Section = Section String deriving (Read, Show, Eq, Ord)---- |A package's subsection is only evident in its control information,--- packages from different subsections all reside in the same index.-data SubSection = SubSection { section :: Section, subSectionName :: String } deriving (Read, Show, Eq, Ord)--sectionName :: SubSection -> String-sectionName (SubSection (Section "main") y) = y-sectionName (SubSection x y) = sectionName' x ++ "/" ++ y--sectionName' :: Section -> String-sectionName' (Section s) = escapeURIString isAllowedInURI s--sectionNameOfSubSection :: SubSection -> String-sectionNameOfSubSection = sectionName' . section---- |Parse the value that appears in the @Section@ field of a .changes file.--- (Does this need to be unesacped?)-parseSection :: String -> SubSection-parseSection s =- case span (/= '/') s of- (x, "") -> SubSection (Section "main") x- ("main", y) -> SubSection (Section "main") y- (x, y) -> SubSection (Section x) (tail y)--parseSection' :: String -> Section-parseSection' name =- Section (unEscapeString name)
− Debian/Report.hs
@@ -1,87 +0,0 @@-module Debian.Report where--import Data.Maybe-import qualified Data.Map as M-import Data.Text as Text (Text, unpack)-import Debian.Apt.Index (Fetcher, Compression(..), update, controlFromIndex')-import Debian.Control.Text-import Debian.Sources-import Debian.Version-import Text.XML.HaXml (CFilter, mkElem, cdata)-import Text.XML.HaXml.Posn-import Text.PrettyPrint (render)---- * General Package Map Builders---- |create a map of (package name, extracted field) from a list of index files------ NOTE: we could merge all the files into a single control and then--- run packageMap over that. We currently do it one control file at a--- time to avoid having all the control files loaded in memory at--- once. However, I am not sure that property is actually occuring--- anyway. So, this should be revisited.-makePackageMap :: (Paragraph -> a) -> (a -> a -> a) -> [(FilePath, Compression)] -> IO (M.Map Text a)-makePackageMap _ _ [] = return M.empty-makePackageMap extractValue resolveConflict ((path, compression):is) =- do r <- controlFromIndex' compression path- case r of- (Left e) -> error (show e)- (Right c) ->- do let pm = packageMap extractValue resolveConflict c- pms <- makePackageMap extractValue resolveConflict is- return $ M.unionWith resolveConflict pm pms---- |create a map of (package name, max version) from a single control file-packageMap :: (Paragraph -> a) -> (a -> a -> a) -> Control' Text -> M.Map Text a-packageMap extractValue resolveConflict control =- M.fromListWith resolveConflict (map packageTuple (unControl control))- where- packageTuple paragraph = (fromJust $ fieldValue "Package" paragraph, extractValue paragraph)---- |extract the version number from a control paragraph-extractVersion :: Paragraph -> Maybe DebianVersion-extractVersion paragraph = fmap (parseDebianVersion' . unpack) $ fieldValue "Version" paragraph---- * Trump Report---- |compare two sources.list and find all the packages in the second that trump packages in the first--- see also: |trumpedMap|-trumped :: Fetcher -- ^ function for downloading package indexes- -> FilePath -- ^ cache directory to store index files in (must already exist)- -> String -- ^ binary architecture- -> [DebSource] -- ^ sources.list a- -> [DebSource] -- ^ sources.list b- -> IO (M.Map Text (DebianVersion, DebianVersion)) -- ^ a map of trumped package names to (version a, version b)-trumped fetcher cacheDir arch sourcesA sourcesB =- do indexesA <- update fetcher cacheDir arch (filter isDebSrc sourcesA)- pmA <- makePackageMap (fromJust . extractVersion) max (map fromJust indexesA)- indexesB <- update fetcher cacheDir arch (filter isDebSrc sourcesB)- pmB <- makePackageMap (fromJust . extractVersion) max (map fromJust indexesB)- return (trumpedMap pmA pmB)- where- isDebSrc ds = sourceType ds == DebSrc---- |calculate all the trumped packages-trumpedMap :: M.Map Text DebianVersion -- ^ package map a- -> M.Map Text DebianVersion -- ^ package map b- -> M.Map Text (DebianVersion, DebianVersion) -- ^ trumped packages (version a, version b)-trumpedMap pmA pmB =- M.foldrWithKey (checkTrumped pmB) M.empty pmA- where- checkTrumped pm package aVersion trumpedPM =- case M.lookup package pm of- (Just bVersion)- | bVersion > aVersion -> M.insert package (aVersion, bVersion) trumpedPM- _ -> trumpedPM---- |create <trumped /> XML element and children from a trumped Map-trumpedXML :: M.Map Text (DebianVersion, DebianVersion) -> CFilter Posn-trumpedXML trumpedMap' =- mkElem "trumped" (map mkTrumpedPackage (M.toAscList trumpedMap' ))- where- mkTrumpedPackage (package, (oldVersion, newVersion)) =- mkElem "trumpedPackage"- [ mkElem "package" [ cdata (unpack package) ]- , mkElem "oldVersion" [ cdata (render . prettyDebianVersion $ oldVersion) ]- , mkElem "newVersion" [ cdata (render . prettyDebianVersion $ newVersion) ]- ]
− Debian/Sources.hs
@@ -1,323 +0,0 @@-{-# LANGUAGE FlexibleInstances, OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-name-shadowing #-}-module Debian.Sources- {- ( SourceType(..)- , SourceOption(..)- , SourceOp(..)- , DebSource(..)- , parseSourceLine- , parseSourceLine'- , parseSourcesList- ) -} where--import Data.Maybe (fromJust)-import Data.Monoid ((<>))-import Data.Text (Text)-import Debian.Pretty (PP(..))-import Debian.Release-import Network.URI (URI, uriToString, parseURI, unEscapeString, escapeURIString, isAllowedInURI)-import Test.HUnit-import Text.ParserCombinators.Parsec-import Text.PrettyPrint (hcat, punctuate, render, text)-import Text.PrettyPrint.HughesPJClass (Pretty(pPrint))--data SourceType- = Deb | DebSrc- deriving (Eq, Ord, Show)---- arch--- lang--- target--- pdiffs--- by-hash--- allow-insecure=no--- allow-weak=no--- allow-downgrade-to-insecure=no--- trusted=no--- signed-by--- check-valid-until--- valid-until-min--- valid-until-max-data SourceOption- = SourceOption String SourceOp [String]- deriving (Eq, Ord, Show)--data SourceOp = OpSet | OpAdd | OpDel deriving (Eq, Ord, Show)--instance Pretty SourceOp where- pPrint OpSet = text "="- pPrint OpAdd = text "+="- pPrint OpDel = text "-="--data DebSource- = DebSource- { sourceType :: SourceType- , sourceOptions :: [SourceOption]- , sourceUri :: URI- , sourceDist :: Either String (ReleaseName, [Section])- } deriving (Eq, Ord, Show)--instance Pretty SourceType where- pPrint Deb = text "deb"- pPrint DebSrc = text "deb-src"--instance Pretty SourceOption where- pPrint (SourceOption k op vs) = text k <> pPrint op <> hcat (punctuate (text ",") (map text vs))--instance Pretty DebSource where- pPrint (DebSource thetype theoptions theuri thedist) =- hcat (punctuate (text " ")- ([pPrint thetype] ++- (case theoptions of- [] -> []- _ -> [text "[" <> hcat (punctuate (text ", ") (map pPrint theoptions)) <> text "]"]) ++- [text (uriToString id theuri "")] ++- case thedist of- Left exactPath -> [text (escapeURIString isAllowedInURI exactPath)]- Right (dist, sections) ->- map text (releaseName' dist : map sectionName' sections)))--instance Pretty (PP [DebSource]) where- pPrint = hcat . map (\ x -> pPrint x <> text "\n") . unPP---- |This is a name given to a combination of parts of one or more--- releases that can be specified by a sources.list file.-type SliceName = ReleaseName--- data SliceName = SliceName { sliceName :: String } deriving (Eq, Ord, Show)--{---deb uri distribution [component1] [componenent2] [...]--The URI for the deb type must specify the base of the Debian-distribution, from which APT will find the information it needs.--distribution can specify an exact path, in which case the components-must be omitted and distribution must end with a slash (/).--If distribution does not specify an exact path, at least one component-must be present.--Distribution may also contain a variable, $(ARCH), which expands to-the Debian architecture (i386, m68k, powerpc, ...) used on the-system.--The rest of the line can be marked as a comment by using a #.--Additional Notes:-- + Lines can begin with leading white space.-- + If the dist ends with slash (/), then it must be an absolute path- and it is an error to specify components after it.---}---- |quoteWords - similar to words, but with special handling of--- double-quotes and brackets.------ The handling double quotes and [] is supposed to match:--- apt-0.6.44.2\/apt-pkg\/contrib\/strutl.cc:ParseQuoteWord()------ The behaviour can be defined as:------ Break the string into space seperated words ignoring spaces that--- appear between \"\" or []. Strip trailing and leading white space--- around words. Strip out double quotes, but leave the square--- brackets intact.-quoteWords :: String -> [String]-quoteWords [] = []-quoteWords s = quoteWords' (dropWhile (==' ') s)- where- quoteWords' :: String -> [String]- quoteWords' [] = []- quoteWords' str =- case break (flip elem (" [\"" :: String)) str of- ([],[]) -> []- (w, []) -> [w]- (w, (' ':rest)) -> w : (quoteWords' (dropWhile (==' ') rest))- (w, ('"':rest)) ->- case break (== '"') rest of- (w',('"':rest)) ->- case quoteWords' rest of- [] -> [w ++ w']- (w'':ws) -> ((w ++ w' ++ w''): ws)- (_w',[]) -> error ("quoteWords: missing \" in the string: " ++ s)- _ -> error ("the impossible happened in SourcesList.quoteWords")- (w, ('[':rest)) ->- case break (== ']') rest of- (w',(']':rest)) ->- case quoteWords' rest of- [] -> [w ++ "[" ++ w' ++ "]"]- (w'':ws) -> ((w ++ "[" ++ w' ++ "]" ++ w''): ws)- (_w',[]) -> error ("quoteWords: missing ] in the string: " ++ s)- _ -> error ("the impossible happened in SourcesList.quoteWords")- _ -> error ("the impossible happened in SourcesList.quoteWords")--stripLine :: String -> String-stripLine = takeWhile (/= '#') . dropWhile (== ' ')--sourceLines :: String -> [String]-sourceLines = filter (not . null) . map stripLine . lines---- |parseSourceLine -- parses a source line--- the argument must be a non-empty, valid source line with comments stripped--- see: 'sourceLines'-parseSourceLine :: String -> DebSource-parseSourceLine str = either error id (parseSourceLine' str)-{-- case quoteWords str of- (theTypeStr : theUriStr : theDistStr : sectionStrs) ->- let sections = map parseSection' sectionStrs- theType = case unEscapeString theTypeStr of- "deb" -> Deb- "deb-src" -> DebSrc- o -> error ("parseSourceLine: invalid type " ++ o ++ " in line:\n" ++ str)- theUri = case parseURI theUriStr of- Nothing -> error ("parseSourceLine: invalid uri " ++ theUriStr ++ " in the line:\n" ++ str)- Just u -> u- theDist = unEscapeString theDistStr- in- case last theDist of- '/' -> if null sections- then DebSource { sourceType = theType, sourceOptions = [], sourceUri = theUri, sourceDist = Left theDist }- else error ("parseSourceLine: Dist is an exact path, so sections are not allowed on the line:\n" ++ str)- _ -> if null sections- then error ("parseSourceLine: Dist is not an exact path, so at least one section is required on the line:\n" ++ str)- else DebSource { sourceType = theType, sourceOptions = [], sourceUri = theUri, sourceDist = Right (parseReleaseName theDist, sections) }- _ -> error ("parseSourceLine: invalid line in sources.list:\n" ++ str)--}--parseOptions :: String -> Either ParseError [SourceOption]-parseOptions s = parse pOptions s s--pOptions :: CharParser () [SourceOption]-pOptions = do _ <- char '['- skipMany (oneOf [' ','\t'])- opts <- sepBy1 pOption (char ',')- skipMany (oneOf [' ','\t'])- _ <- char ']'- return opts--pOption :: CharParser () SourceOption-pOption = do skipMany (oneOf [' ','\t'])- key <- many1 (noneOf ['+','-','=',' ','\t'])- skipMany (oneOf [' ','\t'])- op <- pOp- skipMany (oneOf [' ','\t'])- values <- sepBy1 (many1 (noneOf [',',']',' ','\t'])) (char ',')- skipMany (oneOf [' ','\t'])- return $ SourceOption key op values--pOp :: CharParser () SourceOp-pOp = do (char '+' >> char '=' >> return OpAdd)- <|>- (char '-' >> char '=' >> return OpDel)- <|>- (char '=' >> return OpSet)--parseSourceLine' :: String -> Either String DebSource-parseSourceLine' str =- case quoteWords str of- theTypeStr : theOptionStr@('[' : _) : theURIStr : theDistStr : sectionStrs ->- either- (Left . show)- (\opts -> go theTypeStr opts theURIStr theDistStr sectionStrs)- (parseOptions theOptionStr)- theTypeStr : theURIStr : theDistStr : sectionStrs ->- go theTypeStr [] theURIStr theDistStr sectionStrs- _ -> Left ("parseSourceLine: invalid line in sources.list:\n" ++ str)- where- go theTypeStr theOptions theURIStr theDistStr sectionStrs =- let sections = map parseSection' sectionStrs- theType = case unEscapeString theTypeStr of- "deb" -> Right Deb- "deb-src" -> Right DebSrc- s -> Left ("parseSourceLine: invalid type " ++ s ++ " in line:\n" ++ str)- theURI = case parseURI theURIStr of- Nothing -> Left ("parseSourceLine: invalid uri " ++ theURIStr ++ " in the line:\n" ++ str)- Just u -> Right u- theDist = unEscapeString theDistStr- in- case (last theDist, theType, theURI) of- ('/', Right typ, Right uri) -> if null sections- then Right $ DebSource { sourceType = typ, sourceOptions = theOptions, sourceUri = uri, sourceDist = Left theDist }- else Left ("parseSourceLine: Dist is an exact path, so sections are not allowed on the line:\n" ++ str)- (_, Right typ, Right uri) -> if null sections- then Left ("parseSourceLine: Dist is not an exact path, so at least one section is required on the line:\n" ++ str)- else Right $ DebSource { sourceType = typ, sourceOptions = theOptions, sourceUri = uri, sourceDist = Right ((parseReleaseName theDist), sections) }- (_, Left msg, _) -> Left msg- (_, _, Left msg) -> Left msg--parseSourcesList :: String -> [DebSource]-parseSourcesList = map parseSourceLine . sourceLines---- * Unit Tests---- TODO: add test cases that test for unterminated double-quote or bracket-testQuoteWords :: Test-testQuoteWords =- test [ assertEqual "Space seperate words, no quoting" ["hello", "world","!"] (quoteWords " hello world ! ")- , assertEqual "Space seperate words, double quotes" ["hello world","!"] (quoteWords " hel\"lo world\" ! ")- , assertEqual "Space seperate words, square brackets" ["hel[lo worl]d","!"] (quoteWords " hel[lo worl]d ! ")- , assertEqual "Space seperate words, square-bracket at end" ["hel[lo world]"] (quoteWords " hel[lo world]")- , assertEqual "Space seperate words, double quote at end" ["hello world"] (quoteWords " hel\"lo world\"")- , assertEqual "Space seperate words, square-bracket at beginning" ["[hello wo]rld","!"] (quoteWords "[hello wo]rld !")- , assertEqual "Space seperate words, double quote at beginning" ["hello world","!"] (quoteWords "\"hello wor\"ld !")- ]--testSourcesList :: Test-testSourcesList =- test [ assertEqual "parse and pretty sources.list" validSourcesListExpected (render . pPrint . PP . parseSourcesList $ validSourcesListStr) ]--testSourcesList2 :: Test-testSourcesList2 =- test [ assertEqual "pretty sources.list" validSourcesListExpected (render . pPrint . PP $ validSourcesList) ]--validSourcesListStr :: String-validSourcesListStr =- unlines $ [ " # A comment only line "- , " deb ftp://ftp.debian.org/debian unstable main contrib non-free # typical deb line"- , " deb-src ftp://ftp.debian.org/debian unstable main contrib non-free # typical deb-src line"- , ""- , "# comment line"- , "deb http://pkg-kde.alioth.debian.org/kde-3.5.0/ ./ # exact path"- , "deb [trusted=yes] http://ftp.debian.org/whee \"space dist\" main"- , "deb [trusted=yes] http://ftp.debian.org/whee dist space%20section"- ]--validSourcesList :: [DebSource]-validSourcesList =- [DebSource {sourceType = Deb, sourceOptions = [], sourceUri = fromJust (parseURI "ftp://ftp.debian.org/debian"), sourceDist = Right (ReleaseName {relName = "unstable"},[Section "main",Section "contrib",Section "non-free"])},- DebSource {sourceType = DebSrc, sourceOptions = [], sourceUri = fromJust (parseURI "ftp://ftp.debian.org/debian"), sourceDist = Right (ReleaseName {relName = "unstable"},[Section "main",Section "contrib",Section "non-free"])},- DebSource {sourceType = Deb, sourceOptions = [], sourceUri = fromJust (parseURI "http://pkg-kde.alioth.debian.org/kde-3.5.0/"), sourceDist = Left "./"},- DebSource {sourceType = Deb, sourceOptions = [SourceOption "trusted" OpSet ["yes"]], sourceUri = fromJust (parseURI "http://ftp.debian.org/whee"), sourceDist = Right (ReleaseName {relName = "space dist"},[Section "main"])},- DebSource {sourceType = Deb, sourceOptions = [SourceOption "trusted" OpSet ["yes"]], sourceUri = fromJust (parseURI "http://ftp.debian.org/whee"), sourceDist = Right (ReleaseName {relName = "dist"},[Section "space section"])}]--validSourcesListExpected :: String-validSourcesListExpected =- unlines $ [ "deb ftp://ftp.debian.org/debian unstable main contrib non-free"- , "deb-src ftp://ftp.debian.org/debian unstable main contrib non-free"- , "deb http://pkg-kde.alioth.debian.org/kde-3.5.0/ ./"- , "deb [trusted=yes] http://ftp.debian.org/whee space%20dist main"- , "deb [trusted=yes] http://ftp.debian.org/whee dist space%20section"- ]-_invalidSourcesListStr1 :: Text-_invalidSourcesListStr1 = "deb http://pkg-kde.alioth.debian.org/kde-3.5.0/ ./ main contrib non-free # exact path with sections"--testSourcesListParse :: Test-testSourcesListParse =- test [ assertEqual "" gutsy (concat . map (<> "\n") . map (render . pPrint) . parseSourcesList $ gutsy) ]- where- gutsy = concat ["deb http://us.archive.ubuntu.com/ubuntu/ gutsy main restricted universe multiverse\n",- "deb-src http://us.archive.ubuntu.com/ubuntu/ gutsy main restricted universe multiverse\n",- "deb http://us.archive.ubuntu.com/ubuntu/ gutsy-updates main restricted universe multiverse\n",- "deb-src http://us.archive.ubuntu.com/ubuntu/ gutsy-updates main restricted universe multiverse\n",- "deb http://us.archive.ubuntu.com/ubuntu/ gutsy-backports main restricted universe multiverse\n",- "deb-src http://us.archive.ubuntu.com/ubuntu/ gutsy-backports main restricted universe multiverse\n",- "deb http://security.ubuntu.com/ubuntu/ gutsy-security main restricted universe multiverse\n",- "deb-src http://security.ubuntu.com/ubuntu/ gutsy-security main restricted universe multiverse\n"]--sourcesListTests :: Test-sourcesListTests =- TestList [ testQuoteWords, testSourcesList, testSourcesList2, testSourcesListParse ]
− Debian/Time.hs
@@ -1,27 +0,0 @@-{-# LANGUAGE CPP #-}-module Debian.Time where--import Data.Time-#if !MIN_VERSION_time(1,5,0)-import System.Locale (defaultTimeLocale)-#endif-import Data.Time.Clock.POSIX-import System.Posix.Types---- * Time Helper Functions--rfc822DateFormat' :: String-rfc822DateFormat' = "%a, %d %b %Y %T %z"--epochTimeToUTCTime :: EpochTime -> UTCTime-epochTimeToUTCTime = posixSecondsToUTCTime . fromIntegral . fromEnum--formatTimeRFC822 :: (FormatTime t) => t -> String-formatTimeRFC822 = formatTime defaultTimeLocale rfc822DateFormat'--parseTimeRFC822 :: (ParseTime t) => String -> Maybe t-parseTimeRFC822 = parseTime defaultTimeLocale rfc822DateFormat'--getCurrentLocalRFC822Time :: IO String-getCurrentLocalRFC822Time = getCurrentTime >>= utcToLocalZonedTime >>= return . formatTime defaultTimeLocale rfc822DateFormat'-
− Debian/URI.hs
@@ -1,99 +0,0 @@-{-# LANGUAGE CPP, PackageImports #-}-{-# OPTIONS -Wall -fno-warn-orphans #-}--module Debian.URI- ( module Network.URI- , URI'- , toURI'- , fromURI'- , readURI'- , uriToString'- , fileFromURI- , fileFromURIStrict- , dirFromURI- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif-import Control.Exception (SomeException, throw, try)-import Data.ByteString.Lazy.UTF8 as L-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Maybe (catMaybes, fromJust)-import Data.Text as T (unpack)-import Data.Text.Encoding (decodeUtf8)-import Network.URI (URI(..), URIAuth(..), parseURI, uriToString)-import System.Directory (getDirectoryContents)-import System.Exit (ExitCode(..))--- import System.Process.ByteString (readProcessWithExitCode)-import System.Process (CreateProcess, proc)-import System.Process.Common (showCreateProcessForUser)-import System.Process.ByteString.Lazy (readCreateProcessWithExitCode)-import Text.Regex (mkRegex, matchRegex)---- | A wrapper around a String containing a known parsable URI. Not--- absolutely safe, because you could say read "URI' \"bogus string\""--- :: URI'. But enough to save me from myself.-newtype URI' = URI' String deriving (Read, Show, Eq, Ord)--readURI' :: String -> Maybe URI'-readURI' s = maybe Nothing (const (Just (URI' s))) (parseURI s)--fromURI' :: URI' -> URI-fromURI' (URI' s) = fromJust (parseURI s)---- | Using the bogus Show instance of URI here. If it ever gets fixed--- this will stop working. Worth noting that show will obscure any--- password info embedded in the URI, so that's nice.-toURI' :: URI -> URI'-toURI' = URI' . show--uriToString' :: URI -> String-uriToString' uri = uriToString id uri ""--fileFromURI :: URI -> IO (Either SomeException L.ByteString)-fileFromURI uri = fileFromURIStrict uri--fileFromURIStrict :: URI -> IO (Either SomeException L.ByteString)-fileFromURIStrict uri = try $- case (uriScheme uri, uriAuthority uri) of- ("file:", Nothing) -> L.readFile (uriPath uri)- -- ("ssh:", Just auth) -> cmdOutputStrict ("ssh " ++ uriUserInfo auth ++ uriRegName auth ++ uriPort auth ++ " cat " ++ show (uriPath uri))- ("ssh:", Just auth) ->- run (proc "ssh" [uriUserInfo auth ++ uriRegName auth ++ uriPort auth, "cat", uriPath uri])- _ ->- run (proc "curl" ["-s", "-g", uriToString' uri])---- | Parse the text returned when a directory is listed by a web--- server. This is currently only known to work with Apache.--- NOTE: there is a second copy of this function in--- Extra:Extra.Net. Please update both locations if you make changes.-webServerDirectoryContents :: L.ByteString -> [String]-webServerDirectoryContents text =- catMaybes . map (second . matchRegex re) . Prelude.lines . L.toString $ text- where- re = mkRegex "( <A HREF|<a href)=\"([^/][^\"]*)/\""- second (Just [_, b]) = Just b- second _ = Nothing---dirFromURI :: URI -> IO (Either SomeException [String])-dirFromURI uri = try $- case (uriScheme uri, uriAuthority uri) of- ("file:", Nothing) -> getDirectoryContents (uriPath uri)- ("ssh:", Just auth) ->- (Prelude.lines . L.toString) <$>- run (proc "ssh" [uriUserInfo auth ++ uriRegName auth ++ uriPort auth, "ls", "-1", uriPath uri])- _ ->- webServerDirectoryContents <$>- run (proc "curl" ["-s", "-g", uriToString' uri])--run :: CreateProcess -> IO L.ByteString-run cp = do- (code, out, err) <- readCreateProcessWithExitCode cp L.empty- case code of- ExitSuccess -> return out- ExitFailure _ -> throw (userError (show code ++ "\n" ++- " command: " ++ showCreateProcessForUser cp ++ "\n" ++- " stderr: " ++ unpack (decodeUtf8 (L.toStrict err)) ++ "\n" ++- " stdout: " ++ unpack (decodeUtf8 (L.toStrict out))))
− Debian/UTF8.hs
@@ -1,29 +0,0 @@-{-# LANGUAGE CPP #-}--- | There are old index files that have funky characters like 'ø'--- that are not properly UTF8 encoded. As far as I can tell, these--- files are otherwise plain ascii, so just naivelyinsert the--- character into the output stream.-module Debian.UTF8- ( decode- , readFile- ) where--#if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>))-#endif-import qualified Data.ByteString.Char8 as B (concat)-import qualified Data.ByteString.Lazy.Char8 as L (ByteString, readFile, toChunks)-import Data.Char (chr)-import Data.Text as T-import Data.Text.Encoding (decodeUtf8With)-import Data.Word (Word8)-import Prelude hiding (readFile)--decode :: L.ByteString -> T.Text-decode b = decodeUtf8With e (B.concat (L.toChunks b))- where- e :: String -> Maybe Word8 -> Maybe Char- e _description w = fmap (chr . fromIntegral) w--readFile :: FilePath -> IO T.Text-readFile path = decode <$> L.readFile path
− Debian/Util/FakeChanges.hs
@@ -1,256 +0,0 @@-{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-}-module Debian.Util.FakeChanges (fakeChanges) where----import Control.Arrow-import Control.Exception-import Control.Monad hiding (mapM)-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Data (Data, Typeable)-import Data.Digest.Pure.SHA as SHA-import Data.Foldable (concat, all, foldr)-import Data.List as List (intercalate, nub, partition, isSuffixOf)-import Data.Maybe-import Debian.Pretty (prettyShow)-import Data.Traversable-import Debian.Control-import qualified Debian.Deb as Deb-import Debian.Time-import Network.BSD (getHostName)-import Prelude hiding (concat, foldr, all, mapM, sum)-import System.Environment-import System.FilePath-import System.Posix.Files-import Text.Regex.TDFA--data Error- = NoDebs- | TooManyDscs [FilePath]- | TooManyTars [FilePath]- | TooManyDiffs [FilePath]- | UnknownFiles [FilePath]- | MalformedDebFilename [FilePath]- | VersionMismatch [Maybe String]- deriving (Read, Show, Eq, Typeable, Data)--data Files- = Files { dsc :: Maybe (FilePath, Paragraph)- , debs :: [(FilePath, Paragraph)]- , tar :: Maybe FilePath- , diff :: Maybe FilePath- }--fakeChanges :: [FilePath] -> IO (FilePath, String)-fakeChanges fps =- do files <- loadFiles fps- let version = getVersion files- source = getSource files- maintainer = getMaintainer files- arches = getArches files- binArch = getBinArch files- dist = "unstable"- urgency = "low"- (invalid, binaries) = unzipEithers $ map (debNameSplit . fst) (debs files)- when (not . null $ invalid) (error $ "Some .deb names are invalid: " ++ show invalid)- uploader <- getUploader- date <- getCurrentLocalRFC822Time- fileLines <- mapM mkFileLine fps- let changes = Control $ return . Paragraph $ map Field- [ ("Format"," 1.7")- , ("Date", ' ' : date)- , ("Source", ' ' : source)- , ("Binary", ' ' : (intercalate " " $ map (\(n,_,_) -> n) binaries))- , ("Architecture", ' ' : intercalate " " arches)- , ("Version", ' ' : version)- , ("Distribution", ' ' : dist)- , ("Urgency", ' ' : urgency)- , ("Maintainer", ' ' : maintainer)- , ("Changed-By", ' ' : uploader)- , ("Description", "\n Simulated description")- , ("Changes", "\n" ++ unlines (map (' ':) [ source ++ " (" ++ version ++") " ++ dist ++ "; urgency=" ++ urgency- , "."- , " * Simulated changes"- ]- ))- , ("Files", "\n" ++ unlines fileLines)- ]- return $ (concat [ source, "_", version, "_", binArch, ".changes"], prettyShow changes)--- let (invalid, binaries) = unzipEithers $ map debNameSplit debs-{-- when (not . null $ invalid) (throwDyn [MalformedDebFilename invalid])- version <- getVersion dsc debs- putStrLn version- source <- getSource dsc debs- putStrLn source--}--- TODO: seems like this could be more aggressive about ensure the--- versions make sense. Except with packages like libc, the versions--- don't make sense. Maybe we want a flag that disables version check--- ?-getVersion :: Files -> String-getVersion files- | isNothing (dsc files) =- let versions = map (fieldValue "Version" . snd) (debs files)- in- if (all isJust versions) && (length (nub versions) == 1)- then fromJust (head versions)- else error (show [VersionMismatch (nub versions)])- | otherwise =- case fieldValue "Version" (snd . fromJust $ dsc files) of- (Just v) -> v- Nothing -> error $ "show (dsc files)" ++ " does not have a Version field :("---getSource :: Files -> String-getSource files =- let dscSource =- case (dsc files) of- Nothing -> []- (Just (fp, p)) ->- case fieldValue "Source" p of- (Just v) -> [v]- Nothing -> error $ fp ++ " does not have a Source field :("- debSources = map debSource (debs files)- srcs = nub (dscSource ++ debSources)- in- if (singleton srcs)- then (head srcs)- else error $ "Could not determine source."- where- debSource (deb,p) =- case (fieldValue "Source" p) of- (Just v) -> v- Nothing ->- case fieldValue "Package" p of- (Just v) -> v- Nothing -> error $ "Could not find Source or Package field in " ++ deb----getMaintainer :: Files -> String-getMaintainer files- | isJust (dsc files) =- let (fp, p) = fromJust (dsc files)- in- case fieldValue "Maintainer" p of- Nothing -> error $ fp ++ " is missing the Maintainer field."- (Just v) -> v- | otherwise =- let maintainers = catMaybes $ map (fieldValue "Maintainer" . snd) (debs files)- maintainer = nub maintainers- in- if singleton maintainer- then head maintainer- else error $ "Could not uniquely determine the maintainer: " ++ show maintainer--getArches :: Files -> [String]-getArches files =- let debArchs = map (fieldValue "Architecture" . snd) (debs files)- tarArch = fmap (const "source") (tar files)- diffArch = fmap (const "source") (diff files)- in- nub $ catMaybes (tarArch : diffArch : debArchs)---getBinArch :: Files -> String-getBinArch files =- let binArch = nub $ mapMaybe (fieldValue "Architecture" . snd) (debs files)- in- if singleton binArch- then head binArch- else case (filter (/= "all") binArch) of- [b] -> b- _ -> error $ "Could not uniquely determine binary architecture: " ++ show binArch--mkFileLine :: FilePath -> IO String-mkFileLine fp- | ".deb" `isSuffixOf` fp =- do sum <- L.readFile fp >>= return . show . sha256- size <- liftM fileSize $ getFileStatus fp- (Control (p:_)) <- Deb.fields fp- return $ concat [ " ", sum, " ", show size, " ", fromMaybe "unknown" (fieldValue "Section" p), " "- , fromMaybe "optional" (fieldValue "Priority" p), " ", (takeBaseName fp)- ]- | otherwise =- do sum <- L.readFile fp >>= return . show . sha256- size <- liftM fileSize $ getFileStatus fp- return $ concat [ " ", sum, " ", show size, " ", "unknown", " "- , "optional"," ", (takeBaseName fp)- ]---- more implementations can be found at:--- http://www.google.com/codesearch?hl=en&lr=&q=%22%5BEither+a+b%5D+-%3E+%28%5Ba%5D%2C%5Bb%5D%29%22&btnG=Search-unzipEithers :: [Either a b] -> ([a],[b])-unzipEithers = foldr unzipEither ([],[])- where- unzipEither (Left l) ~(ls, rs) = (l:ls, rs)- unzipEither (Right r) ~(ls, rs) = (ls, r:rs)---- move to different library-debNameSplit :: String -> Either FilePath (String, String, String)-debNameSplit fp =- case (takeFileName fp) =~ "^(.*)_(.*)_(.*).deb$" of- [[_, name, version, arch]] -> Right (name, version, arch)- _ -> Left fp---loadFiles :: [FilePath] -> IO Files-loadFiles files =- let (dscs', files'') = partition (isSuffixOf ".dsc") files'- (debs', files') = partition (isSuffixOf ".deb") files- (tars', files''') = partition (isSuffixOf ".tar.gz") files''- (diffs', rest) = partition (isSuffixOf ".diff.gz") files'''- errors = concat [ if (length debs' < 1) then [NoDebs] else []- , if (length dscs' > 1) then [TooManyDscs dscs'] else []- , if (length tars' > 1) then [TooManyTars tars'] else []- , if (length diffs' > 1) then [TooManyDiffs diffs'] else []- , if (length rest > 0) then [UnknownFiles rest] else []- ]- in- do when (not . null $ errors) (error $ show errors)- dsc' <- mapM loadDsc (listToMaybe dscs')- debs'' <- mapM loadDeb debs'- return $ Files { dsc = dsc', debs = debs'', tar = listToMaybe tars', diff = listToMaybe diffs' }- -- if (not . null $ errors) then throwDyn errors else return (debs, listToMaybe dscs, listToMaybe tars, listToMaybe diffs)- where- loadDsc :: FilePath -> IO (FilePath, Paragraph)- loadDsc dsc' =- do res <- parseControlFromFile dsc'- case res of- (Left e) -> error $ "Error parsing " ++ dsc' ++ "\n" ++ show e- (Right (Control [p])) -> return (dsc', p)- (Right c) -> error $ dsc' ++ " did not have exactly one paragraph: " ++ prettyShow c- loadDeb :: FilePath -> IO (FilePath, Paragraph)- loadDeb deb =- do res <- Deb.fields deb- case res of- (Control [p]) -> return (deb, p)- _ -> error $ deb ++ " did not have exactly one paragraph: " ++ prettyShow res---getUploader :: IO String-getUploader =- do debFullName <-- do dfn <- try (getEnv "DEBFULLNAME")- case dfn of- (Right n) -> return n- (Left (_ :: SomeException)) ->- do dfn' <-try (getEnv "USER")- case dfn' of- (Right n) -> return n- (Left (_ :: SomeException)) -> error $ "Could not determine user name, neither DEBFULLNAME nor USER enviroment variables were set."- emailAddr <-- do eml <- try (getEnv "DEBEMAIL")- case eml of- (Right e) -> return e- (Left (_ :: SomeException)) ->- do eml' <- try (getEnv "EMAIL")- case eml' of- (Right e) -> return e- (Left (_ :: SomeException)) -> getHostName -- FIXME: this is not a FQDN- return $ debFullName ++ " <" ++ emailAddr ++ ">"---- * Utils--singleton :: [a] -> Bool-singleton [_] = True-singleton _ = False
− Debian/Version.hs
@@ -1,16 +0,0 @@--- |A module for parsing, comparing, and (eventually) modifying debian version--- numbers. <http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version>-module Debian.Version- (DebianVersion -- |Exported abstract because the internal representation is likely to change- , prettyDebianVersion- , parseDebianVersion- , parseDebianVersion'- , epoch- , version- , revision- , buildDebianVersion- , evr- ) where--import Debian.Version.Common-import Debian.Version.String ()
− Debian/Version/ByteString.hs
@@ -1,18 +0,0 @@-{-# OPTIONS -fno-warn-orphans #-}-module Debian.Version.ByteString- ( ParseDebianVersion(..)- ) where--import Text.ParserCombinators.Parsec--import qualified Data.ByteString.Char8 as C--import Debian.Version.Common-import Debian.Version.Internal--instance ParseDebianVersion C.ByteString where- parseDebianVersion byteStr =- let str = C.unpack byteStr in- case parse parseDV str str of- Left e -> Left e- Right dv -> Right (DebianVersion str dv)
− Debian/Version/Common.hs
@@ -1,185 +0,0 @@--- |A module for parsing, comparing, and (eventually) modifying debian version--- numbers. <http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version>-{-# LANGUAGE FlexibleInstances #-}-{-# OPTIONS -fno-warn-orphans -fno-warn-unused-do-bind #-}-module Debian.Version.Common- ( DebianVersion -- |Exported abstract because the internal representation is likely to change- , prettyDebianVersion- , ParseDebianVersion(..)- , parseDebianVersion'- , evr -- DebianVersion -> (Maybe Int, String, Maybe String)- , epoch- , version- , revision- , buildDebianVersion- , parseDV- ) where--import Data.Char (ord, isDigit, isAlpha)-import Debian.Pretty (PP(..))-import Debian.Version.Internal-import Text.ParserCombinators.Parsec-import Text.Regex-import Text.PrettyPrint (Doc, render)-import Text.PrettyPrint.HughesPJClass (Pretty(pPrint), text)--prettyDebianVersion :: DebianVersion -> Doc-prettyDebianVersion (DebianVersion s _) = text s--instance Pretty (PP DebianVersion) where- pPrint = prettyDebianVersion . unPP--instance Eq DebianVersion where- (DebianVersion _ v1) == (DebianVersion _ v2) = v1 == v2--instance Ord DebianVersion where- compare (DebianVersion _ v1) (DebianVersion _ v2) = compare v1 v2--instance Show DebianVersion where- show v = "(Debian.Version.parseDebianVersion (" ++ show (render (prettyDebianVersion v)) ++ " :: String))"---- make ~ less than everything, and everything else higher that letters-order :: Char -> Int-order c- | isDigit c = 0- | isAlpha c = ord c- | c == '~' = -1- | otherwise = (ord c) + 256---- |We have to do this wackiness because ~ is less than the empty string-compareNonNumeric :: [Char] -> [Char] -> Ordering-compareNonNumeric "" "" = EQ-compareNonNumeric "" ('~':_cs) = GT-compareNonNumeric ('~':_cs) "" = LT-compareNonNumeric "" _ = LT-compareNonNumeric _ "" = GT-compareNonNumeric (c1:cs1) (c2:cs2) =- if (order c1) == (order c2)- then compareNonNumeric cs1 cs2- else compare (order c1) (order c2)--instance Eq NonNumeric where- (NonNumeric s1 n1) == (NonNumeric s2 n2) =- case compareNonNumeric s1 s2 of- EQ -> n1 == n2- _o -> False--instance Ord NonNumeric where- compare (NonNumeric s1 n1) (NonNumeric s2 n2) =- case compareNonNumeric s1 s2 of- EQ -> compare n1 n2- o -> o--instance Eq Numeric where- (Numeric n1 mnn1) == (Numeric n2 mnn2) =- case compare n1 n2 of- EQ -> case compareMaybeNonNumeric mnn1 mnn2 of- EQ -> True- _ -> False- _ -> False--compareMaybeNonNumeric :: Maybe NonNumeric -> Maybe NonNumeric -> Ordering-compareMaybeNonNumeric mnn1 mnn2 =- case (mnn1, mnn2) of- (Nothing, Nothing) -> EQ- (Just (NonNumeric nn _), Nothing) -> compareNonNumeric nn ""- (Nothing, Just (NonNumeric nn _)) -> compareNonNumeric "" nn- (Just nn1, Just nn2) -> compare nn1 nn2--instance Ord Numeric where- compare (Numeric n1 mnn1) (Numeric n2 mnn2) =- case compare n1 n2 of- EQ -> compareMaybeNonNumeric mnn1 mnn2- o -> o---- * Parser--class ParseDebianVersion a where- parseDebianVersion :: a-> Either ParseError DebianVersion--- |Convert a string to a debian version number. May throw an--- exception if the string is unparsable -- but I am not sure if that--- can currently happen. Are there any invalid version strings?--- Perhaps ones with underscore, or something?--parseDebianVersion' :: ParseDebianVersion string => string -> DebianVersion-parseDebianVersion' str = either (\e -> error (show e)) id (parseDebianVersion str)--{--showNN :: NonNumeric -> String-showNN (NonNumeric s n) = s ++ showN n--showN :: Found Numeric -> String-showN (Found (Numeric n nn)) = show n ++ maybe "" showNN nn-showN (Simulated _) = ""--}--parseDV :: CharParser () (Found Int, NonNumeric, Found NonNumeric)-parseDV =- do skipMany $ oneOf " \t"- e <- parseEpoch- upstreamVersion <- parseNonNumeric True True- debianRevision <- option (Simulated (NonNumeric "" (Simulated (Numeric 0 Nothing)))) (char '-' >> parseNonNumeric True False >>= return . Found)- return (e, upstreamVersion, debianRevision)--parseEpoch :: CharParser () (Found Int)-parseEpoch =- option (Simulated 0) (try (many1 digit >>= \d -> char ':' >> return (Found (read d))))---parseNonNumeric :: Bool -> Bool -> CharParser () NonNumeric-parseNonNumeric zeroOk upstream =- do nn <- (if zeroOk then many else many1) ((noneOf "-0123456789") <|> (if upstream then upstreamDash else pzero))- n <- parseNumeric upstream- return $ NonNumeric nn n- where- upstreamDash :: CharParser () Char- upstreamDash = try $ do char '-'- lookAhead $ (many (noneOf "- \n\t") >> char '-')- return '-'--parseNumeric :: Bool -> CharParser () (Found Numeric)-parseNumeric upstream =- do n <- many1 (satisfy isDigit)- nn <- option Nothing (parseNonNumeric False upstream >>= return . Just)- return $ Found (Numeric (read n) nn)- <|>- return (Simulated (Numeric 0 Nothing))--{--compareTest :: String -> String -> Ordering-compareTest str1 str2 =- let v1 = either (error . show) id $ parse parseDV str1 str1- v2 = either (error . show) id $ parse parseDV str2 str2- in- compare v1 v2--}---- |Split a DebianVersion into its three components: epoch, version,--- revision. It is not safe to use the parsed version number for--- this because you will lose information, such as leading zeros.-evr :: DebianVersion -> (Maybe Int, String, Maybe String)-evr (DebianVersion s _) =- let re = mkRegex "^(([0-9]+):)?(([^-]*)|((.*)-([^-]*)))$" in- -- ( ) ( ( ))- -- ( e ) ( v ) (v2) ( r )- case matchRegex re s of- Just ["", _, _, v, "", _, _] -> (Nothing, v, Nothing)- Just ["", _, _, _, _, v, r] -> (Nothing, v, Just r)- Just [_, e, _, v, "", _, _] -> (Just (read e), v, Nothing)- Just [_, e, _, _, _, v, r] -> (Just (read e), v, Just r)- -- I really don't think this can happen.- _ -> error ("Invalid Debian Version String: " ++ s)--epoch :: DebianVersion -> Maybe Int-epoch v = case evr v of (x, _, _) -> x-version :: DebianVersion -> String-version v = case evr v of (_, x, _) -> x-revision :: DebianVersion -> Maybe String-revision v = case evr v of (_, _, x) -> x---- Build a Debian version number from epoch, version, revision-buildDebianVersion :: Maybe Int -> String -> Maybe String -> DebianVersion-buildDebianVersion e v r =- either (error . show) (DebianVersion str) $ parse parseDV str str- where- str = (maybe "" (\ n -> show n ++ ":") e ++ v ++ maybe "" (\ s -> "-" ++ s) r)
− Debian/Version/Internal.hs
@@ -1,36 +0,0 @@-{-# LANGUAGE DeriveDataTypeable, FlexibleContexts #-}-module Debian.Version.Internal- ( DebianVersion(..)- , Numeric(..)- , NonNumeric(..)- , Found(..)- ) where--import Data.Data (Data)-import Data.Typeable (Typeable)---- Currently we store the original version string in the data-type so--- that we can faithfully reproduce it quickly. Currently we do not--- have any way to modify a version number -- so this works fine. May--- have to change later.-data DebianVersion- = DebianVersion String (Found Int, NonNumeric, Found NonNumeric) deriving (Data, Typeable)--data NonNumeric- = NonNumeric String (Found Numeric)- deriving (Show, Data, Typeable)--data Numeric- = Numeric Int (Maybe NonNumeric)- deriving (Show, Data, Typeable)--data Found a- = Found { unFound :: a }- | Simulated { unFound :: a }- deriving (Show, Data, Typeable)--instance (Eq a) => Eq (Found a) where- f1 == f2 = (unFound f1) == (unFound f2)--instance (Ord a) => Ord (Found a) where- compare f1 f2 = compare (unFound f1) (unFound f2)
− Debian/Version/String.hs
@@ -1,25 +0,0 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}-{-# OPTIONS -fno-warn-orphans #-}-module Debian.Version.String- ( ParseDebianVersion(..)- ) where--import Text.ParserCombinators.Parsec--import Data.List (stripPrefix)-import Debian.Version.Common-import Debian.Version.Internal--instance ParseDebianVersion String where- parseDebianVersion str =- case parse parseDV str str of- Left e -> Left e- Right dv -> Right (DebianVersion str dv)- -instance Read DebianVersion where- readsPrec _ s =- case stripPrefix "Debian.Version.parseDebianVersion " s of- Just s' -> case reads s' :: [(String, String)] of- []-> []- (v, s'') : _ -> [(parseDebianVersion' v, s'')]- Nothing -> []
− Debian/Version/Text.hs
@@ -1,18 +0,0 @@-{-# OPTIONS -fno-warn-orphans #-}-module Debian.Version.Text- ( ParseDebianVersion(..)- ) where--import Text.ParserCombinators.Parsec--import qualified Data.Text as T--import Debian.Version.Common-import Debian.Version.Internal--instance ParseDebianVersion T.Text where- parseDebianVersion text =- let str = T.unpack text in- case parse parseDV str str of- Left e -> Left e- Right dv -> Right (DebianVersion str dv)
Test/Changes.hs view
@@ -1,14 +1,15 @@-{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE StandaloneDeriving, TemplateHaskell #-} {-# OPTIONS -fno-warn-missing-signatures -fno-warn-orphans #-} module Changes where import Debian.Changes+import Debian.Codename (Codename, parseCodename) import Debian.Pretty (PP(..))-import Debian.Release (ReleaseName(ReleaseName, relName)) import Debian.Version (parseDebianVersion, parseDebianVersion')+import Debian.TH (here)+import Distribution.Pretty (pretty) import Test.HUnit import Text.PrettyPrint (render)-import Text.PrettyPrint.HughesPJClass (pPrint) s3 = unlines ["name (version) dist; urgency=urgency",@@ -157,17 +158,17 @@ " -- Marco Túlio Gontijo e Silva <marcot@holoscopio.com> Wed, 11 Mar 2009 18:58:06 -0300", ""] -test5 = TestCase (assertEqual "haskell-regex-compat changelog 1" s1 (render . pPrint . PP . either (const (error "parse")) id . parseChangeLog $ s1))+test5 = TestCase (assertEqual "haskell-regex-compat changelog 1" s1 (render . pretty . PP . either (const (error "parse")) id . parseChangeLog $ s1)) test3 = TestCase (assertEqual "haskell-regex-compat changelog 2" expected (parseEntries s3))- where expected = [Right (Entry {logPackage = "name", logVersion = parseDebianVersion' "version", logDists = [ReleaseName {relName = "dist"}], logUrgency = "urgency", logComments = " * details\n", logWho = "David Fox <dsf@seereason.com>", logDate = "Wed, 21 Nov 2007 01:26:57 +0000"})]+ where expected = [Right (Entry {logPackage = "name", logVersion = parseDebianVersion' "version", logDists = [parseCodename "dist"], logUrgency = "urgency", logComments = " * details\n", logWho = "David Fox <dsf@seereason.com>", logDate = "Wed, 21 Nov 2007 01:26:57 +0000"})] test4 = TestCase (assertEqual "haskell-regex-compat changelog 3" expected (parseEntries s4)) where expected = [Right (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-3+seereason1~jaunty4",- logDists = [ReleaseName {relName = "jaunty-seereason"}],+ logDists = [parseCodename "jaunty-seereason"], logUrgency = "low", logComments = " [ Joachim Breitner ]\n * Adjust priority according to override file\n * Depend on hscolour (Closes: #550769)\n\n [ Marco T\250lio Gontijo e Silva ]\n", logWho = "David Fox <dsf@seereason.com>",@@ -175,33 +176,33 @@ test1 = TestCase (assertEqual "haskell-regex-compat changelog 4" expected (either (const (error "parse")) id (parseChangeLog s1)))- where expected = ChangeLog [(Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-3+seereason1~jaunty4", logDists = [ReleaseName {relName = "jaunty-seereason"}], logUrgency = "low", logComments = " [ Joachim Breitner ]\n * Adjust priority according to override file\n * Depend on hscolour (Closes: #550769)\n\n [ Marco T\250lio Gontijo e Silva ]\n * debian/control: Use more sintetic name for Vcs-Darcs.\n * Built from sid apt pool\n * Build dependency changes:\n cpphs: 1.9-1+seereason1~jaunty5 -> 1.9-1+seereason1~jaunty6\n ghc6: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n ghc6-doc: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n ghc6-prof: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n haddock: 2.4.2-3+seereason3~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n haskell-devscripts: 0.6.18-21+seereason1~jaunty1 -> 0.6.18-23+seereason1~jaunty1\n haskell-regex-base-doc: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1\n haskell-regex-posix-doc: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2\n libghc6-regex-base-dev: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1\n libghc6-regex-base-prof: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1\n libghc6-regex-posix-dev: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2\n libghc6-regex-posix-prof: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2\n", logWho = "SeeReason Autobuilder <autobuilder@seereason.org>", logDate = "Fri, 25 Dec 2009 01:55:37 -0800"}),- (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-3", logDists = [ReleaseName {relName = "unstable"}], logUrgency = "low", logComments = " [ Joachim Breitner ]\n * Adjust priority according to override file\n * Depend on hscolour (Closes: #550769)\n\n [ Marco T\250lio Gontijo e Silva ]\n * debian/control: Use more sintetic name for Vcs-Darcs.\n", logWho = "Joachim Breitner <nomeata@debian.org>", logDate = "Mon, 20 Jul 2009 13:05:35 +0200"}),- (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-2", logDists = [ReleaseName {relName = "unstable"}], logUrgency = "low", logComments = " * Adopt package for the Debian Haskell Group\n * Fix \"FTBFS with new dpkg-dev\" by adding comma to debian/control\n (Closes: #536473)\n", logWho = "Joachim Breitner <nomeata@debian.org>", logDate = "Mon, 20 Jul 2009 12:05:40 +0200"}),- (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-1.1", logDists = [ReleaseName {relName = "unstable"}], logUrgency = "low", logComments = " * Rebuild for GHC 6.10.\n * NMU with permission of the author.\n", logWho = "John Goerzen <jgoerzen@complete.org>", logDate = "Mon, 16 Mar 2009 10:12:04 -0500"}),- (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-1", logDists = [ReleaseName {relName = "unstable"}], logUrgency = "low", logComments = " * New upstream release\n * debian/control:\n - Bump Standards-Version. No changes needed.\n", logWho = "Arjan Oosting <arjan@debian.org>", logDate = "Sun, 18 Jan 2009 00:05:02 +0100"}),- (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.91-1", logDists = [ReleaseName {relName = "unstable"}], logUrgency = "low", logComments = " * Take over package from Ian, as I already maintain haskell-regex-base,\n and move Ian to the Uploaders field.\n * Packaging complete redone (based on my haskell-regex-base package).\n", logWho = "Arjan Oosting <arjan@debian.org>", logDate = "Sat, 19 Jan 2008 16:48:39 +0100"}),- (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.71.0.1-1", logDists = [ReleaseName {relName = "unstable"}], logUrgency = "low", logComments = " * Initial release (used to be part of ghc6).\n * Using \"Generic Haskell cabal library packaging files v9\".\n", logWho = "Ian Lynagh (wibble) <igloo@debian.org>", logDate = "Wed, 21 Nov 2007 01:26:57 +0000"})]+ where expected = ChangeLog [(Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-3+seereason1~jaunty4", logDists = [parseCodename "jaunty-seereason"], logUrgency = "low", logComments = " [ Joachim Breitner ]\n * Adjust priority according to override file\n * Depend on hscolour (Closes: #550769)\n\n [ Marco T\250lio Gontijo e Silva ]\n * debian/control: Use more sintetic name for Vcs-Darcs.\n * Built from sid apt pool\n * Build dependency changes:\n cpphs: 1.9-1+seereason1~jaunty5 -> 1.9-1+seereason1~jaunty6\n ghc6: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n ghc6-doc: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n ghc6-prof: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n haddock: 2.4.2-3+seereason3~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n haskell-devscripts: 0.6.18-21+seereason1~jaunty1 -> 0.6.18-23+seereason1~jaunty1\n haskell-regex-base-doc: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1\n haskell-regex-posix-doc: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2\n libghc6-regex-base-dev: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1\n libghc6-regex-base-prof: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1\n libghc6-regex-posix-dev: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2\n libghc6-regex-posix-prof: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2\n", logWho = "SeeReason Autobuilder <autobuilder@seereason.org>", logDate = "Fri, 25 Dec 2009 01:55:37 -0800"}),+ (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-3", logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " [ Joachim Breitner ]\n * Adjust priority according to override file\n * Depend on hscolour (Closes: #550769)\n\n [ Marco T\250lio Gontijo e Silva ]\n * debian/control: Use more sintetic name for Vcs-Darcs.\n", logWho = "Joachim Breitner <nomeata@debian.org>", logDate = "Mon, 20 Jul 2009 13:05:35 +0200"}),+ (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-2", logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * Adopt package for the Debian Haskell Group\n * Fix \"FTBFS with new dpkg-dev\" by adding comma to debian/control\n (Closes: #536473)\n", logWho = "Joachim Breitner <nomeata@debian.org>", logDate = "Mon, 20 Jul 2009 12:05:40 +0200"}),+ (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-1.1", logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * Rebuild for GHC 6.10.\n * NMU with permission of the author.\n", logWho = "John Goerzen <jgoerzen@complete.org>", logDate = "Mon, 16 Mar 2009 10:12:04 -0500"}),+ (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.92-1", logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * New upstream release\n * debian/control:\n - Bump Standards-Version. No changes needed.\n", logWho = "Arjan Oosting <arjan@debian.org>", logDate = "Sun, 18 Jan 2009 00:05:02 +0100"}),+ (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.91-1", logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * Take over package from Ian, as I already maintain haskell-regex-base,\n and move Ian to the Uploaders field.\n * Packaging complete redone (based on my haskell-regex-base package).\n", logWho = "Arjan Oosting <arjan@debian.org>", logDate = "Sat, 19 Jan 2008 16:48:39 +0100"}),+ (Entry {logPackage = "haskell-regex-compat", logVersion = parseDebianVersion' "0.71.0.1-1", logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * Initial release (used to be part of ghc6).\n * Using \"Generic Haskell cabal library packaging files v9\".\n", logWho = "Ian Lynagh (wibble) <igloo@debian.org>", logDate = "Wed, 21 Nov 2007 01:26:57 +0000"})] test2 = TestCase (assertEqual "haskell-regex-compat changelog" expected (parseEntries s2)) where expected = [Right (Entry {logPackage = "haskell-haskeline", logVersion = parseDebianVersion' "0.6.1.6-1+seereason1~jaunty6",- logDists = [ReleaseName {relName = "jaunty-seereason"}],+ logDists = [parseCodename "jaunty-seereason"], logUrgency = "low", logComments = " * New upstream version.\n * Remove extensible-exceptions patch, since ghc6 now ships it.\n * debian/control:\n - Use versioned Build-Depends.\n - Use unversioned Recommends for ghc6-doc in libghc6-terminfo-doc.\n - Use haskell Section.\n - Use new Standards-Version: 3.8.1.\n - Use DM-Upload-Allowed: yes.\n - Use haskell:Recommends and haskell:Suggests.\n - Don't use shlibs:Depends for -prof.\n - Split dependencies in more than one line.\n * Built from sid apt pool\n * Build dependency changes:\n ghc6: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n ghc6-doc: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n ghc6-prof: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n haddock: 2.4.2-3+seereason3~jaunty1 -> 6.12.1-0+seereason1~jaunty1\n haskell-devscripts: 0.6.18-21+seereason1~jaunty1 -> 0.6.18-23+seereason1~jaunty1\n libghc6-mtl-dev: 1.1.0.2-7+seereason3~jaunty7 -> 1.1.0.2-7+seereason3~jaunty8\n libghc6-mtl-doc: 1.1.0.2-7+seereason3~jaunty7 -> 1.1.0.2-7+seereason3~jaunty8\n libghc6-mtl-prof: 1.1.0.2-7+seereason3~jaunty7 -> 1.1.0.2-7+seereason3~jaunty8\n libghc6-terminfo-dev: 0.3.0.2-2+seereason1~jaunty5 -> 0.3.0.2-2+seereason1~jaunty6\n libghc6-terminfo-doc: 0.3.0.2-2+seereason1~jaunty5 -> 0.3.0.2-2+seereason1~jaunty6\n libghc6-terminfo-prof: 0.3.0.2-2+seereason1~jaunty5 -> 0.3.0.2-2+seereason1~jaunty6\n libghc6-utf8-string-dev: 0.3.5-1+seereason3~jaunty7 -> 0.3.5-1++1+seereason1~jaunty1\n libghc6-utf8-string-doc: 0.3.5-1+seereason3~jaunty7 -> 0.3.5-1++1+seereason1~jaunty1\n libghc6-utf8-string-prof: 0.3.5-1+seereason3~jaunty7 -> 0.3.5-1++1+seereason1~jaunty1\n", logWho = "SeeReason Autobuilder <autobuilder@seereason.org>", logDate = "Fri, 25 Dec 2009 13:48:18 -0800"}), Right (Entry {logPackage = "haskell-haskeline", logVersion = parseDebianVersion' "0.6.1.6-1",- logDists = [ReleaseName {relName = "unstable"}],+ logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * New upstream version.\n * Remove extensible-exceptions patch, since ghc6 now ships it.\n * debian/control:\n - Use versioned Build-Depends.\n - Use unversioned Recommends for ghc6-doc in libghc6-terminfo-doc.\n - Use haskell Section.\n - Use new Standards-Version: 3.8.1.\n - Use DM-Upload-Allowed: yes.\n - Use haskell:Recommends and haskell:Suggests.\n - Don't use shlibs:Depends for -prof.\n - Split dependencies in more than one line.\n", logWho = "Marco T\250lio Gontijo e Silva <marcot@holoscopio.com>", logDate = "Tue, 02 Jun 2009 10:18:27 -0300"}), Right (Entry {logPackage = "haskell-haskeline", logVersion = parseDebianVersion' "0.6.1.3-1",- logDists = [ReleaseName {relName = "unstable"}],+ logDists = [parseCodename "unstable"], logUrgency = "low", logComments = " * Initial Debian package. (Closes: #496961)\n", logWho = "Marco T\250lio Gontijo e Silva <marcot@holoscopio.com>",
Test/Control.hs view
@@ -12,9 +12,10 @@ import Debian.Pretty (prettyShow) import Debian.Relation import Debian.Version (parseDebianVersion, parseDebianVersion')+import Distribution.Pretty (pretty) import Paths_debian (version) import Text.Parsec.Error (ParseError)-import Text.PrettyPrint.HughesPJClass (Doc, text, pPrint)+import Text.PrettyPrint.HughesPJClass (Doc, text) import Text.Regex.TDFA ((=~), MatchResult(..)) #if !MIN_VERSION_pretty(1,1,2)@@ -38,14 +39,14 @@ -- inter-paragraph newlines, or missing terminating newlines, would be -- good. controlTests =- [ TestCase (assertEqual "pretty1" (pPrint control) (either (error "parser failed") pPrint (parseControl "debian/control" sample)))- , TestCase (assertEqual "pretty2" (text sample) (pPrint control))- , TestCase (assertEqual "pretty3" (text (head paragraphs <> "\n")) (pPrint (head (unControl control))))+ [ TestCase (assertEqual "pretty1" (pretty control) (either (error "parser failed") pretty (parseControl "debian/control" sample)))+ , TestCase (assertEqual "pretty2" (text sample) (pretty control))+ , TestCase (assertEqual "pretty3" (text (head paragraphs <> "\n")) (pretty (head (unControl control)))) -- The Pretty class instances are distinct implementations from -- those in Debian.Control.PrettyPrint. Not sure why, there is a -- terse note about performance concerns.- , TestCase (assertEqual "pretty4" (text sample) (pPrint control))- , TestCase (assertEqual "pretty5" (text (head paragraphs <> "\n")) (pPrint (head (unControl control))))+ , TestCase (assertEqual "pretty4" (text sample) (pretty control))+ , TestCase (assertEqual "pretty5" (text (head paragraphs <> "\n")) (pretty (head (unControl control)))) , TestCase (validateDebianControl control >>= \ vc -> assertEqual "policy1" (Right (unsafeDebianControl control)) vc) -- validate control file , TestCase (validateDebianControl control >>= \ vc -> assertEqual "policy2" (Right (Just builddeps)) (either Left (debianRelations "Build-Depends") vc)) -- parse build deps , TestCase (validateDebianControl control >>= \ vc -> assertEqual "policy3" (Right Nothing) (either Left (debianRelations "Foo") vc)) -- absent field
changelog view
@@ -1,3 +1,24 @@+haskell-debian (3.95) unstable; urgency=medium++ * Clean up error handling and monad transformers.+ * Move source code into src subdirectory to avoid build problems on case+ insensitive HFS partitions.+ * Move Debian.Except module to Extra.Except in sr-extra package.++ -- David Fox <dsf@seereason.com> Tue, 12 Feb 2019 16:38:30 -0800++haskell-debian (3.94) unstable; urgency=medium++ * Additions to Debian.URI.++ -- David Fox <dsf@seereason.com> Sat, 02 Feb 2019 08:18:12 -0800++haskell-debian (3.93.6) unstable; urgency=medium++ * Throw a UserError when dirFromURI gets 404 Not Found++ -- David Fox <dsf@seereason.com> Fri, 25 Jan 2019 12:12:56 -0800+ haskell-debian (3.93.5) unstable; urgency=medium * Restore import of <$> for older versions of base.
debian.cabal view
@@ -1,5 +1,5 @@ Name: debian-Version: 3.93.5+Version: 3.95 License: BSD3 License-File: debian/copyright Author: David Fox <dsf@seereason.com>, Jeremy Shaw <jeremy@seereason.com>, Clifford Beshers <beshers@seereason.com>@@ -26,18 +26,21 @@ Manual: True Library- Hs-Source-Dirs: .+ Hs-Source-Dirs: src Build-Depends: base >= 4 && < 5, bytestring, bzlib,+ Cabal, containers, directory, either, exceptions, filepath, HaXml >= 1.20,+ hostname, HUnit,+ lens, ListLike >= 4.3.5, mtl, old-locale,@@ -45,18 +48,22 @@ process, process-extras >= 0.2.0, pureMD5,+ QuickCheck, regex-compat, regex-tdfa, SHA,+ syb, template-haskell, text,+ th-lift,+ th-orphans, time, unix, Unixutils >= 1.52.4, utf8-string, zlib if flag(network-uri)- Build-Depends: network >= 2.6, network-uri >= 2.6+ Build-Depends: network-uri >= 2.6 else Build-Depends: network >= 2.4 && < 2.6 if flag(pretty-new)@@ -64,7 +71,7 @@ else Build-Depends: pretty, prettyclass - ghc-options: -Wall -O2+ ghc-options: -Wall Exposed-modules: Debian.Apt.Dependencies, Debian.Apt.Index,@@ -72,6 +79,7 @@ Debian.Apt.Package, Debian.Arch, Debian.Changes,+ Debian.Codename, Debian.Control, Debian.Control.Common, Debian.Control.Builder@@ -98,10 +106,12 @@ Debian.Version.String, Debian.Version.Text, Debian.Report,+ Debian.TH, Debian.Time, Debian.URI, Debian.UTF8,- Debian.Util.FakeChanges+ Debian.Util.FakeChanges,+ Debian.VendorURI other-modules: Debian.Version.Internal @@ -109,14 +119,14 @@ Hs-Source-Dirs: utils Main-is: FakeChanges.hs Build-Depends: base, debian, directory, filepath- ghc-options: -threaded -W -O2+ ghc-options: -threaded -W Extensions: ExistentialQuantification CPP Executable debian-report Hs-Source-Dirs: utils Main-is: Report.hs Build-Depends: base, debian, HaXml, unix- ghc-options: -threaded -W -O2+ ghc-options: -threaded -W C-Sources: cbits/gwinsz.c Include-Dirs: cbits Install-Includes: gwinsz.h@@ -127,14 +137,15 @@ Hs-Source-Dirs: utils Main-is: AptGetBuildDeps.hs Build-Depends: base, debian, process- ghc-options: -threaded -W -O2+ ghc-options: -threaded -W Extensions: ExistentialQuantification CPP Test-Suite debian-tests Type: exitcode-stdio-1.0 Hs-Source-Dirs: Test Main-Is: Main.hs- Build-Depends: base, debian, HUnit, parsec, regex-tdfa, text+ Build-Depends: base, Cabal, debian, HUnit, parsec, regex-tdfa, text+ Other-Modules: Changes Control Dependencies Paths_debian Versions if flag(pretty-new) Build-Depends: pretty >= 1.1.2 else
debian/changelog view
@@ -1,3 +1,24 @@+haskell-debian (3.95) unstable; urgency=medium++ * Clean up error handling and monad transformers.+ * Move source code into src subdirectory to avoid build problems on case+ insensitive HFS partitions.+ * Move Debian.Except module to Extra.Except in sr-extra package.++ -- David Fox <dsf@seereason.com> Tue, 12 Feb 2019 16:38:30 -0800++haskell-debian (3.94) unstable; urgency=medium++ * Additions to Debian.URI.++ -- David Fox <dsf@seereason.com> Sat, 02 Feb 2019 08:18:12 -0800++haskell-debian (3.93.6) unstable; urgency=medium++ * Throw a UserError when dirFromURI gets 404 Not Found++ -- David Fox <dsf@seereason.com> Fri, 25 Jan 2019 12:12:56 -0800+ haskell-debian (3.93.5) unstable; urgency=medium * Restore import of <$> for older versions of base.
+ src/Debian/Apt/Dependencies.hs view
@@ -0,0 +1,285 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# OPTIONS -fno-warn-missing-signatures #-}+module Debian.Apt.Dependencies+{-+ ( solve+ , State+ , binaryDepends+ , search+ , bj'+ , bt+ , CSP(..)+ ) -} where++-- test gutsyPackages "libc6" (\csp -> bt csp)++import Control.Arrow (second)+import qualified Data.ByteString.Char8 as C+import Data.List as List (find, union)+import Data.Tree (Tree(rootLabel, Node))+import Debian.Apt.Package (PackageNameMap, packageNameMap, lookupPackageByRel)+import Debian.Control.ByteString (ControlFunctions(stripWS, lookupP, parseControlFromFile),+ Field'(Field, Comment), Control'(Control), Paragraph, Control)+import Debian.Relation (BinPkgName(..))+import Debian.Relation.ByteString (ParseRelations(..), Relation(..), OrRelation, AndRelation, Relations, checkVersionReq)+import Debian.Version (DebianVersion, parseDebianVersion, prettyDebianVersion)+import Debian.Version.ByteString ()+import Text.PrettyPrint (render)++-- * Basic CSP Types and Functions++data Status+ = Remaining AndRelation+ | MissingDep Relation+ | Complete+ deriving (Eq)++type State a = (Status, [a])++complete :: State a -> Bool+complete (Complete, _) = True+complete _ = False++data CSP a+ = CSP { pnm :: PackageNameMap a+ , relations :: Relations+ , depFunction :: (a -> Relations)+ , conflicts :: a -> Relations+ , packageVersion :: a -> (BinPkgName, DebianVersion)+ }++-- * Test CSP++-- |TODO addProvides -- see DQL.Exec+controlCSP :: Control -> Relations -> (Paragraph -> Relations) -> CSP Paragraph+controlCSP (Control paragraphs) rels depF' =+ CSP { pnm = packageNameMap getName paragraphs+ , relations = rels+ , depFunction = depF'+ , conflicts = conflicts'+ , packageVersion = packageVersionParagraph+ }+ where+ getName :: Paragraph -> BinPkgName+ getName p = case lookupP "Package" p of+ Nothing -> error "Missing Package field"+ Just (Field (_,n)) -> BinPkgName (C.unpack (stripWS n))+ Just (Comment _) -> error "controlCSP"+ conflicts' :: Paragraph -> Relations+ conflicts' p =+ case lookupP "Conflicts" p of+ Nothing -> []+ Just (Field (_, c)) -> either (error . show) id (parseRelations c)+ Just (Comment _) -> error "controlCSP"++testCSP :: FilePath -> (Paragraph -> Relations) -> String -> (CSP Paragraph -> IO a) -> IO a+testCSP controlFile depf relationStr cspf =+ do c' <- parseControlFromFile controlFile+ case c' of+ Left e -> error (show e)+ Right control@(Control _) ->+ case parseRelations relationStr of+ Left e -> error (show e)+ Right r ->+ cspf (controlCSP control r depf)++depF :: Paragraph -> Relations+depF p =+ let preDepends =+ case lookupP "Pre-Depends" p of+ Nothing -> []+ Just (Field (_,pd)) ->+ either (error . show) id (parseRelations pd)+ Just (Comment _) -> error "depF"+ depends =+ case lookupP "Depends" p of+ Nothing -> []+ Just (Field (_,pd)) ->+ either (error . show) id (parseRelations pd)+ Just (Comment _) -> error "depF"+ in+ preDepends ++ depends++sidPackages = "/var/lib/apt/lists/ftp.debian.org_debian_dists_unstable_main_binary-i386_Packages"+gutsyPackages = "/var/lib/apt/lists/mirror.anl.gov_pub_ubuntu_dists_gutsy_main_binary-i386_Packages"++test controlFP rel labeler =+ testCSP controlFP depF rel (mapM_ (\ (_,p) -> mapM_ (print . second (render . prettyDebianVersion) . packageVersionParagraph) p ) . take 1 . search labeler)++-- TODO: add better errors+packageVersionParagraph :: Paragraph -> (BinPkgName, DebianVersion)+packageVersionParagraph p =+ case lookupP "Package" p of+ Nothing -> error $ "Paragraph missing Package field"+ (Just (Field (_, name))) ->+ case lookupP "Version" p of+ Nothing -> error $ "Paragraph missing Version field"+ (Just (Field (_, str))) ->+ case parseDebianVersion str of+ Right ver -> (BinPkgName (C.unpack (stripWS name)), ver)+ Left e -> error $ "packageVersionParagraph: " ++ show e+ (Just (Comment _)) -> error "packageVersionParagraph"+ (Just (Comment _)) -> error "packageVersionParagraph"++++conflict :: CSP p -> p -> p -> Bool+conflict csp p1 p2 =+ let (name1, version1) = (packageVersion csp) p1+ (name2, version2) = (packageVersion csp) p2+ in+ if name1 == name2+ then version1 /= version2+ else+ any (conflict' (name1, version1)) (concat $ (conflicts csp) p2) ||+ any (conflict' (name2, version2)) (concat $ (conflicts csp) p1)++-- |JAS: deal with 'Provides' (can a package provide more than one package?)+conflict' :: (BinPkgName, DebianVersion) -> Relation -> Bool+conflict' (pName, pVersion) (Rel pkgName mVersionReq _) =+ (pName == pkgName) && (checkVersionReq mVersionReq (Just pVersion))++++-- * Tree Helper Functions++mkTree :: a -> [Tree a] -> Tree a+mkTree = Node++label :: Tree a -> a+label = rootLabel++initTree :: (a -> [a]) -> a -> Tree a+initTree f a = Node a (map (initTree f) (f a))++mapTree :: (a -> b) -> Tree a -> Tree b+mapTree = fmap++foldTree :: (a -> [b] -> b) -> Tree a -> b+foldTree f (Node a ts) = f a (map (foldTree f) ts)++zipTreesWith :: (a -> b -> c) -> Tree a -> Tree b -> Tree c+zipTreesWith f (Node a ts) (Node b us) =+ Node (f a b) (zipWith (zipTreesWith f) ts us)++prune :: (a -> Bool) -> Tree a -> Tree a+prune p = foldTree f+ where f a ts = Node a (filter (not . p . label) ts)++leaves :: Tree a -> [a]+leaves = foldTree f+ where f leaf [] = [leaf]+ f _ ts = concat ts++inhTree :: (b -> a -> b) -> b -> Tree a -> Tree b+inhTree f b (Node a ts) = Node b' (map (inhTree f b') ts)+ where b' = f b a++distrTree :: (a -> [b]) -> b -> Tree a -> Tree b+distrTree f b (Node a ts) = Node b (zipWith (distrTree f) (f a) ts)++-- * mkSearchTree++-- TODO: might want to leave markers about what relation we are satisfying?+mkSearchTree :: forall a. CSP a -> Tree (State a)+mkSearchTree csp =+ Node (Remaining (relations csp),[]) (andRelation ([],[]) (relations csp))+ where+ andRelation :: ([a],AndRelation) -> AndRelation -> [Tree (State a)]+ andRelation (candidates,[]) [] = [Node (Complete, candidates) []]+ andRelation (candidates,remaining) [] = andRelation (candidates, []) remaining+ andRelation (candidates, remaining) (x:xs) =+ orRelation (candidates, xs ++ remaining) x+ orRelation :: ([a],AndRelation) -> OrRelation -> [Tree (State a)]+ orRelation acc x =+ concat (fmap (relation acc) x)+ relation :: ([a],AndRelation) -> Relation -> [Tree (State a)]+ relation acc@(candidates,_) rel =+ let packages = lookupPackageByRel (pnm csp) (packageVersion csp) rel in+ case packages of+ [] -> [Node (MissingDep rel, candidates) []]+ _ -> map (package acc) packages+ package :: ([a],AndRelation) -> a -> Tree (State a)+ package (candidates, remaining) p =+ if ((packageVersion csp) p) `elem` (map (packageVersion csp) candidates)+ then if null remaining+ then Node (Complete, candidates) []+ else Node (Remaining remaining, candidates) (andRelation (candidates, []) remaining)+ else Node (Remaining remaining, (p : candidates)) (andRelation ((p : candidates), remaining) ((depFunction csp) p))+++-- |earliestInconsistency does what it sounds like+-- the 'reverse as' is because the vars are order high to low, but we+-- want to find the lowest numbered (aka, eariest) inconsistency ??+--+earliestInconsistency :: CSP a -> State a -> Maybe ((BinPkgName, DebianVersion), (BinPkgName, DebianVersion))+earliestInconsistency _ (_,[]) = Nothing+earliestInconsistency _ (_,[_p]) = Nothing+earliestInconsistency csp (_,(p:ps)) =+ case find ((conflict csp) p) (reverse ps) of+ Nothing -> Nothing+ (Just conflictingPackage) -> Just ((packageVersion csp) p, (packageVersion csp) conflictingPackage)++-- * Conflict Set++-- | conflicting packages and relations that require non-existant packages+type ConflictSet = ([(BinPkgName, DebianVersion)],[Relation])++isConflict :: ConflictSet -> Bool+isConflict ([],[]) = False+isConflict _ = True++solutions :: Tree (State a, ConflictSet) -> [State a]+solutions = filter complete . map fst . leaves . prune (isConflict . snd)++type Labeler a = CSP a -> Tree (State a) -> Tree (State a, ConflictSet)++search :: Labeler a -> CSP a -> [State a]+search labeler csp = (solutions . (labeler csp) . mkSearchTree) csp++-- * Backtracking Labeler++bt :: Labeler a+bt csp = mapTree f+ where+ f s@(status,_) =+ case status of+ (MissingDep rel) -> (s, ([], [rel]))+ _ ->+ (s,+ case (earliestInconsistency csp) s of+ Nothing -> ([],[])+ Just (a,b) -> ([a,b], []))++-- * BackJumping Solver++{-|bj - backjumping labeler++If the node already has a conflict set, then leave it alone.++Otherwise, the conflictset for the node is the combination of the+conflict sets of its direct children.+-}+bj :: CSP p -> Tree (State p, ConflictSet) -> Tree (State p, ConflictSet)+bj csp = foldTree f+ where f (s, cs) ts+ | isConflict cs = mkTree (s, cs) ts+-- | isConflict cs' = mkTree (s, cs') [] -- prevent space leak+ | otherwise = mkTree (s, cs') ts+ where cs' =+ let set = combine csp (map label ts) [] in+ set `seq` set -- prevent space leak++unionCS :: [ConflictSet] -> ConflictSet+unionCS css = foldr (\(c1, m1) (c2, m2) -> ((c1 `union` c2), (m1 `union` m2))) ([],[]) css++combine :: CSP p -> [(State p, ConflictSet)] -> [ConflictSet] -> ConflictSet+combine _ [] acc = unionCS acc+combine csp ((s,cs@(c,m)):ns) acc+ | (not (lastvar `elem` c)) && null m = cs+ | null c && null m = ([],[]) -- is this case ever used?+ | otherwise = combine csp ns ((c, m):acc)+ where lastvar =+ let (_,(p:_)) = s in (packageVersion csp) p++
+ src/Debian/Apt/Index.hs view
@@ -0,0 +1,337 @@+{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, OverloadedStrings, ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}+module Debian.Apt.Index+ ( update+ , Fetcher+ , CheckSums(..)+ , Compression(..)+ , FileTuple+ , Size+ , controlFromIndex+ , controlFromIndex'+ , findContentsFiles+ , findIndexes+ , indexesInRelease+ , tupleFromFilePath+ ) where++import qualified Codec.Compression.GZip as GZip+import qualified Codec.Compression.BZip as BZip+import Control.Lens (over, to, view)+import Control.Monad+import qualified Data.ByteString.Char8 as B+import qualified Data.ByteString.Lazy.Char8 as L+import qualified Data.Digest.Pure.MD5 as MD5+import qualified Data.Digest.Pure.SHA as SHA+import Data.Either (partitionEithers)+import Data.Function+import Data.List as List (null, intercalate, sortBy, isSuffixOf, isPrefixOf)+import qualified Data.Map as M+import Data.Monoid ((<>))+import Data.Text as Text (Text, unpack, concat, lines, words)+import Data.Time+import Debian.Apt.Methods+import Debian.Codename (Codename, codename)+import Debian.Control (formatControl)+import Debian.Control.ByteString+--import Debian.Control.Common+import Debian.Control.Text (decodeControl)+import Debian.Release+import Debian.Sources+import Debian.URI (uriPathLens, uriToString')+import Debian.VendorURI (VendorURI, vendorURI)+import Network.URI+import System.Directory+import System.FilePath ((</>))+import System.Posix.Files+import System.FilePath (takeBaseName)+--import qualified System.Unix.Misc as Misc+import Text.ParserCombinators.Parsec.Error+import Text.PrettyPrint (render)+import Distribution.Pretty (pretty)+import Text.Read (readMaybe)+#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (pure, (<$>), (<*>))+#endif++-- |Package indexes on the server are uncompressed or compressed with+-- gzip or bzip2. We do not know what will exist on the server until we+-- actually look. This type is used to mark the compression status of+-- what was actually found.+data Compression+ = BZ2 | GZ | Uncompressed+ deriving (Read, Show, Eq, Ord, Enum, Bounded)++data CheckSums+ = CheckSums { md5sum :: Maybe String+ , sha1 :: Maybe String+ , sha256 :: Maybe String+ }+ deriving (Read, Show, Eq)++-- |function-type for a function that downloads a file+-- The timestamp is optional. If the local file is as new or newer+-- than the remote copy, the download may be skipped.+--+-- A good choice might be a partially parameterized call to+-- 'Debian.Apt.Methods.fetch'+type Fetcher =+ URI -> -- remote URI+ FilePath -> -- local file name+ Maybe UTCTime -> -- optional time stamp for local file+ IO Bool -- True on success, False on failure++-- |update - similar to apt-get update++-- downloads the index files associated with a sources.list. The+-- downloaded index files will have the same basenames that apt-get uses+-- in \/var\/lib\/apt\/lists. You can almost use this function instead of+-- calling apt-get update. However there are a few key differences:+-- 1. apt-get update also updates the binary cache files+-- 2. apt-get update uses the partial directory and lock file in\ /var\/lib\/apt\/lists+-- 3. apt-get update downloads the Release and Release.gpg files+update :: Fetcher -- ^ function that will do actually downloading+ -> FilePath -- ^ download indexes to the directory (must already exist)+ -> String -- ^ binary architecture+ -> [DebSource] -- ^ sources.list+ -> IO [Maybe (FilePath, Compression)] -- ^ (basename of index file, compression status)+update fetcher basePath arch sourcesList =+ mapM (uncurry $ fetchIndex fetcher) (map (\(uri, fp, _) -> (uri, (basePath </> fp))) (concatMap (indexURIs arch) sourcesList))++-- | download possibly compressed files+-- NOTE: index uri must not include the .bz2 or .gz extension+fetchIndex :: Fetcher -- ^ function that will do the actual fetch+ -> URI -- ^ remote URI of package index, without .bz2 or .gz extension+ -> FilePath -- ^ name to save downloaded file as, without .bz2 or .gz extension+ -> IO (Maybe (FilePath, Compression)) -- ^ (downloaded file name + extension, compression status)+fetchIndex fetcher uri localPath =+ do let localPath' = localPath ++ ".bz2"+ --lm <- getLastModified localPath'+ res <- fetcher (uri { uriPath = (uriPath uri) ++ ".bz2" }) localPath' Nothing+ if res+ then return $ Just (localPath', BZ2)+ else do let localPath' = localPath ++ ".gz"+ lm <- getLastModified localPath'+ res <- fetcher (uri { uriPath = (uriPath uri) ++ ".gz" }) localPath' lm+ if res+ then return $ Just (localPath', GZ)+ else do lm <- getLastModified localPath+ res <- fetcher (uri { uriPath = (uriPath uri) }) localPath lm+ if res+ then return (Just (localPath, Uncompressed))+ else return Nothing++-- |examine a DebSource line, and calculate for each section:+-- - the URI to the uncompressed index file+-- - the basename that apt-get would name the downloaded index+-- FIXME: ExactPath dist will fail with error at runtime :(+indexURIs :: String -- ^ which binary architecture+ -> DebSource -- ^ line from sources.list+ -> [(URI, FilePath, DebSource)] -- ^ (remote uri, local name, deb source for just this section)+indexURIs arch debSource =+ map (\ section -> let (uri, fp) = calcPath (view sourceType debSource) arch baseURI release section+ in (uri,fp, debSource { _sourceDist = (Right (release, [section])) }) ) sections+ where+ baseURI = view sourceUri debSource+ (release, sections) =+ either (error $ "indexURIs: support not implemented for exact path: " ++ render (pretty debSource)) id (view sourceDist debSource)++-- |return a tuple for the section+-- - the URI to the uncompressed index file+-- - the basename that apt-get uses for the downloaded index+-- FIXME: support for Release and Release.gpg+calcPath :: SourceType -- ^ do we want Packages or Sources+ -> String -- ^ The binary architecture to use for Packages+ -> VendorURI -- ^ base URI as it appears in sources.list+ -> Codename -- ^ the release (e.g., unstable, testing, stable, sid, etc)+ -> Section -- ^ the section (main, contrib, non-free, etc)+ -> (URI, [Char]) -- ^ (uri to index file, basename for the downloaded file)+calcPath srcType arch baseURI release section =+ let indexPath = case srcType of+ DebSrc -> "source/Sources"+ Deb -> "binary-" ++ arch </> "Packages"+ uri' = over uriPathLens (\path -> path </> "dists" </> codename release </> sectionName' section </> indexPath) (view vendorURI baseURI)+ path = view uriPathLens uri'+ in (uri', addPrefix (escapePath path))+ where+ addPrefix s = prefix scheme user' pass' reg port ++ {- "_" ++ -} s+ prefix "http:" (Just user) Nothing (Just host) port = user ++ host ++ port+ prefix "http:" _ _ (Just host) port = host ++ port+ prefix "ftp:" _ _ (Just host) _ = host+ prefix "file:" Nothing Nothing Nothing "" = ""+ prefix "ssh:" (Just user) Nothing (Just host) port = user ++ host ++ port+ prefix "ssh:" _ _ (Just host) port = host ++ port+ prefix _ _ _ _ _ = error ("calcPath: unsupported uri: " ++ view (vendorURI . to uriToString') baseURI)+ user' = maybeOfString user+ pass' = maybeOfString pass+ (user, pass) = break (== ':') userpass+ userpass = maybe "" uriUserInfo auth+ reg = maybeOfString $ maybe "" uriRegName auth+ port = maybe "" uriPort auth+ scheme = view (vendorURI . to uriScheme) baseURI+ auth = view (vendorURI . to uriAuthority) baseURI+ --path = uriPath baseURI++ escapePath :: String -> String+ escapePath s = intercalate "_" $ wordsBy (== '/') s++ maybeOfString :: String -> Maybe String+ maybeOfString "" = Nothing+ maybeOfString s = Just s++ wordsBy :: Eq a => (a -> Bool) -> [a] -> [[a]]+ wordsBy p s =+ case (break p s) of+ (s, []) -> [s]+ (h, t) -> h : wordsBy p (drop 1 t)++-- |Parse a possibly compressed index file.+controlFromIndex :: Compression -> FilePath -> L.ByteString -> Either ParseError (Control' Text)+controlFromIndex GZ path s = either Left (Right . decodeControl) . parseControl path . B.concat . L.toChunks . GZip.decompress $ s+controlFromIndex BZ2 path s = either Left (Right . decodeControl) . parseControl path . B.concat . L.toChunks . BZip.decompress $ s+controlFromIndex Uncompressed path s = either Left (Right . decodeControl) . parseControl path . B.concat . L.toChunks $ s++-- |parse an index possibly compressed file+controlFromIndex' :: Compression -> FilePath -> IO (Either ParseError (Control' Text))+controlFromIndex' compression path = L.readFile path >>= return . controlFromIndex compression path++type Size = Integer+type FileTuple = (CheckSums, Size, FilePath)++-- |A release file contains a list of indexes (Packages\/Sources). Each+-- Package or Source index may appear multiple times because it may be+-- compressed several different ways. This function will return an+-- assoc list where the key is the name of the uncompressed package+-- index name and the value is the list of (file, compression) which+-- decompress to the key.+groupIndexes :: [FileTuple] -> [(FilePath, [(FileTuple, Compression)])]+groupIndexes indexFiles =+ M.toList $ M.fromListWith combine $ map makeKV indexFiles+ where+ makeKV fileTuple@(_,_,fp) =+ let (name, compressionMethod) = uncompressedName fp+ in+ (name, [(fileTuple, compressionMethod)])+ combine = (\x y -> sortBy (compare `on` snd) (x ++ y))+{-+ with t@(_,_,fp) m =+ let (un, compression) =+ in+ M.insertWith+-}++{-+groupIndexes' :: String ->[FileTuple] -> [(FilePath, [(FileTuple, Compression)])]+groupIndexes' iType indexFiles =+ M.toList (foldr (insertType iType) M.empty indexFiles)+ where+ insertType iType t@(_,_,fp) m =+ case uncompressedName' iType fp of+ Nothing -> m+ (Just (un, compression)) ->+ M.insertWith (\x y -> sortBy (compare `on` snd) (x ++ y)) un [(t, compression)] m+-}++-- |The release file contains the checksums for the uncompressed+-- package indexes, even if the uncompressed package indexes are not+-- stored on the server. This function returns the list of files that+-- actually exist.+filterExists :: FilePath -> (FilePath, [(FileTuple, Compression)]) -> IO (FilePath, [(FileTuple, Compression)])+filterExists distDir (fp, alternatives) =+ do e <- filterM ( \((_,_,fp),_) -> fileExist (distDir </> fp)) alternatives+ -- when (null e) (error $ "None of these files exist: " ++ show alternatives)+ return (fp, e)++findIndexes :: FilePath -> String -> [FileTuple] -> IO [(FileTuple, Compression)]+findIndexes distDir iType controlFiles =+ let indexes = groupIndexes controlFiles+ in+ do indexes' <- mapM (filterExists distDir) (filter (isType iType) indexes)+ return $ map (head . snd) (filter (not . List.null . snd) indexes')+ where+ isType iType (fp, _) = iType `isSuffixOf` fp++{-+findIndexes' :: FilePath -> String -> [FileTuple] -> IO [(FileTuple, Compression)]+findIndexes' distDir iType controlFiles =+ let m = groupIndexes' iType controlFiles+ in+ do m' <- mapM (filterExists distDir) m+ return $ map (head . snd) (filter (not . null . snd) m')+-}++ -- insertType :: String -> (CheckSums, Integer, FilePath) -> M.Map FilePath ((CheckSums, Integer, FilePath), Compression) -> M.Map FilePath ((CheckSums, Integer, FilePath), Compression)++{-+uncompressedName' :: String -> FilePath -> Maybe (FilePath, Compression)+uncompressedName' iType fp+ | isSuffixOf iType fp = Just (fp, Uncompressed)+ | isSuffixOf (iType ++".gz") fp = Just (reverse . (drop 3) . reverse $ fp, GZ)+ | isSuffixOf (iType ++".bz2") fp = Just (reverse . (drop 4) . reverse $ fp, BZ2)+ | otherwise = Nothing+-}++uncompressedName :: FilePath -> (FilePath, Compression)+uncompressedName fp+ | isSuffixOf ".gz" fp = (reverse . (drop 3) . reverse $ fp, GZ)+ | isSuffixOf ".bz2" fp = (reverse . (drop 4) . reverse $ fp, BZ2)+ | otherwise = (fp, Uncompressed)++indexesInRelease :: (FilePath -> Bool)+ -> Control' Text -- ^ A release file+ -> [(CheckSums, Integer, FilePath)] -- ^+indexesInRelease filterp (Control [p]) =+ -- In a release file we should find one or more of the fields+ -- "SHA256", "SHA1", or "MD5Sum", each containing a list of triples+ either error (filter (\(_,_,fp) -> filterp fp)) $+ msum [either Left (makeTuples makeSHA256) (maybe (Left "No SHA256 Field") makeTriples $ fieldValue "SHA256" p),+ either Left (makeTuples makeSHA1) (maybe (Left "No SHA1 Field") makeTriples $ fieldValue "SHA1" p),+ either Left (makeTuples makeMD5) (maybe (Left "No MD5Sum Field") makeTriples $ msum [fieldValue "MD5Sum" p,+ fieldValue "Md5Sum" p,+ fieldValue "MD5sum" p])]+ where+ makeSHA256 s = CheckSums {md5sum = Nothing, sha1 = Nothing, sha256 = Just s}+ makeSHA1 s = CheckSums {md5sum = Nothing, sha1 = Just s, sha256 = Nothing}+ makeMD5 s = CheckSums {md5sum = Just s, sha1 = Nothing, sha256 = Nothing}++ makeTuples :: (String -> CheckSums) -> [(Text, Text, Text)] -> Either String [(CheckSums, Integer, FilePath)]+ makeTuples mk triples =+ case partitionEithers (fmap (makeTuple mk) triples) of+ ([], tuples) -> Right tuples+ (s : _, _) -> Left s++ makeTuple :: (String -> CheckSums) -> (Text, Text, Text) -> Either String (CheckSums, Integer, FilePath)+ makeTuple mk (sum, size, fp) =+ (,,) <$> pure (mk (Text.unpack sum))+ <*> maybe (Left ("Invalid size field: " ++ show size)) Right (readMaybe (Text.unpack size))+ <*> pure (Text.unpack fp)++ makeTriples :: Text -> Either String [(Text, Text, Text)]+ makeTriples t = case partitionEithers (map makeTriple (Text.lines t)) of+ ([], xs) -> Right xs+ (s : _, _) -> Left s++ makeTriple :: Text -> Either String (Text, Text, Text)+ makeTriple t = case Text.words t of+ [a, b, c] -> Right (a, b, c)+ _ -> Left ("Invalid checksum line: " ++ show t)+indexesInRelease _ x = error $ "Invalid release file: " <> Text.unpack (Text.concat (formatControl x))++-- |make a FileTuple for a file found on the local disk+-- returns 'Nothing' if the file does not exist.+tupleFromFilePath :: FilePath -> FilePath -> IO (Maybe FileTuple)+tupleFromFilePath basePath fp =+ do e <- fileExist (basePath </> fp)+ if not e+ then return Nothing+ else do size <- getFileStatus (basePath </> fp) >>= return . fromIntegral . fileSize+ md5 <- L.readFile (basePath </> fp) >>= return . show . MD5.md5+ sha1 <- L.readFile (basePath </> fp) >>= return . show . SHA.sha1+ sha256 <- L.readFile (basePath </> fp) >>= return . show . SHA.sha256+ return $ Just (CheckSums { md5sum = Just md5, sha1 = Just sha1, sha256 = Just sha256 }, size, fp)++-- |find the Contents-* files. These are not listed in the Release file+findContentsFiles :: (FilePath -> Bool) -> FilePath -> IO [FilePath]+findContentsFiles filterP distDir =+ do files <- getDirectoryContents distDir+ return $ filter filterP $ filter (isPrefixOf "Contents-" . takeBaseName) files
+ src/Debian/Apt/Methods.hs view
@@ -0,0 +1,486 @@+{-# LANGUAGE PackageImports #-}+{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-name-shadowing #-}+-- |an interface for using the methods in /var/lib/apt/methods+module Debian.Apt.Methods+ ( withMethodPath+ , withMethodURI+ , whichMethodPath+ , openMethod+ , closeMethod+ , recvStatus+ , sendCommand+ , getLastModified+ , simpleFetch+ , fetch+ , FetchCallbacks(..)+ , emptyFetchCallbacks+ , cliFetchCallbacks+ , Command(..)+ , Status(..)+ , Message, Site, User, Password, Media, Drive, Header, ConfigItem+ )+ where++import Debian.Time+import Debian.URI (URI(..), parseURI, uriToString')++import Control.Exception+import Control.Monad.Except+import Data.Maybe+import Data.Time+import System.Directory+import System.Exit+import System.IO+import System.Posix.Files+import System.Process++type MethodHandle = (Handle, Handle, Handle, ProcessHandle)++capabilities, logMsg, status, uriStart, uriDone, uriFailure, generalFailure, authorizationRequired, mediaFailure, uriAcquire, configuration, authorizationCredentials, mediaChanged :: String+capabilities = "100"+logMsg = "101"+status = "102"+uriStart = "200"+uriDone = "201"+uriFailure = "400"+generalFailure = "401"+authorizationRequired = "402"+mediaFailure = "403"+uriAcquire = "600"+configuration = "601"+authorizationCredentials = "602"+mediaChanged = "603"++type Message = String+type Site = String+type User = String+type Password = String+type Media = String+type Drive = String++data Status+ = Capabilities { version :: String, singleInstance :: Bool, preScan :: Bool, pipeline :: Bool, sendConfig :: Bool+ , needsCleanup :: Bool, localOnly :: Bool }+ | LogMsg Message+ | Status URI Message+ | URIStart { uri :: URI, size :: Maybe Integer, lastModified :: Maybe UTCTime, resumePoint :: Maybe Integer }+ | URIDone { uri :: URI, size :: Maybe Integer, lastModified :: Maybe UTCTime, resumePoint :: Maybe Integer+ , filename :: Maybe FilePath, hashes :: Hashes, imsHit :: Bool }+ | URIFailure { uri :: URI, message :: Message }+ | GeneralFailure Message+ | AuthorizationRequired Site+ | MediaFailure Media Drive+ deriving (Show, Eq)++data Hashes+ = Hashes { md5 :: Maybe String+ , sha1 :: Maybe String+ , sha256 :: Maybe String+ }+ deriving (Show, Eq)++emptyHashes = Hashes Nothing Nothing Nothing++data Command+ = URIAcquire URI FilePath (Maybe UTCTime)+ | Configuration [ConfigItem]+ | AuthorizationCredentials Site User Password+ | MediaChanged Media (Maybe Bool) -- I don't really understand the Fail field, I am assuming it is 'Fail: true'+ deriving (Show, Eq)++type Header = (String, String)+type ConfigItem = (String, String)++withMethodURI :: URI -> (MethodHandle -> IO a) -> IO a+withMethodURI uri f =+ do mp <- liftM fromJust (whichMethodPath uri)+ withMethodPath mp f++-- |withMethod - run |methodPath| bracketed with+-- openMethod\/closeMethod. |f| gets the open handle.+withMethodPath :: FilePath -> (MethodHandle -> IO a) -> IO a+withMethodPath methodPath f =+ bracket (openMethod methodPath) closeMethod $ f++-- |whichMethodBinary - find the method executable associated with a URI+-- throws an exception on failure+whichMethodPath :: URI -> IO (Maybe FilePath)+whichMethodPath uri =+ let scheme = init (uriScheme uri)+ path = "/usr/lib/apt/methods/" ++ scheme+ in+ doesFileExist path >>= return . bool Nothing (Just path)++{-+The flow of messages starts with the method sending out a+100 Capabilities and APT sending out a 601 Configuration.++The flow is largely unsynchronized, but our function may have to+respond to things like authorization requests. Perhaps we do a+recvContents and then mapM_ over that ? Not all incoming messages+require a response, so...++-}++parseStatus :: [String] -> Status+parseStatus [] = error "parseStatus"+parseStatus (code' : headers') =+ parseStatus' (take 3 code') (map parseHeader headers')+ where+ parseStatus' code headers+ | code == capabilities =+ foldr updateCapability defaultCapabilities headers+ where+ updateCapability (a,v) c+ | a == "Version" = c { version = v }+ | a == "Single-Instance" = c { singleInstance = parseTrueFalse v }+ | a == "Pre-Scan" = c { preScan = parseTrueFalse v }+ | a == "Pipeline" = c { pipeline = parseTrueFalse v }+ | a == "Send-Config" = c { sendConfig = parseTrueFalse v }+ | a == "Needs-Cleanup" = c { needsCleanup = parseTrueFalse v }+ | a == "Local-Only" = c { localOnly = parseTrueFalse v }+ | otherwise = error $ "unknown capability: " ++ show (a,v)+ defaultCapabilities =+ Capabilities { version = ""+ , singleInstance = False+ , preScan = False+ , pipeline = False+ , sendConfig = False+ , needsCleanup = False+ , localOnly = False+ }+ parseStatus' code headers+ | code == logMsg =+ case headers of+ [("Message", msg)] -> LogMsg msg+ _ -> error "parseStatus'"+ | code == status =+ Status (fromJust $ parseURI $ fromJust $ lookup "URI" headers) (fromJust $ lookup "Message" headers)+ | code == uriStart =+ foldr updateUriStart (URIStart undefined Nothing Nothing Nothing) headers+ where+ updateUriStart (a,v) u+ | a == "URI" = u { uri = fromJust $ parseURI v }+ | a == "Size" = u { size = Just (read v) }+ | a == "Last-Modified" = u { lastModified = parseTimeRFC822 v } -- if the date is unparseable, we silently truncate. Is that bad ?+ | a == "Resume-Point" = u { resumePoint = Just (read v) }+ updateUriStart _ _ = error "updateUriStart"+ parseStatus' code headers+ | code == uriDone =+ foldr updateUriDone (URIDone undefined Nothing Nothing Nothing Nothing emptyHashes False) headers+ where+ updateUriDone (a,v) u+ | a == "URI" = u { uri = fromJust $ parseURI v }+ | a == "Size" = u { size = Just (read v) }+ | a == "Last-Modified" = u { lastModified = parseTimeRFC822 v } -- if the date is unparseable, we silently truncate. Is that bad ?+ | a == "Filename" = u { filename = Just v }+ | a == "MD5Sum-Hash" = u { hashes = (hashes u) { md5 = Just v } }+ | a == "MD5-Hash" = u { hashes = (hashes u) { md5 = Just v } }+ | a == "SHA1-Hash" = u { hashes = (hashes u) { sha1 = Just v } }+ | a == "SHA256-Hash" = u { hashes = (hashes u) { sha256 = Just v } }+ | a == "Resume-Point" = u { resumePoint = Just (read v) }+ | a == "IMS-Hit" && v == "true" = u { imsHit = True }+ | otherwise = error $ "updateUriDone: unknown header: " ++ show (a,v)+ parseStatus' code headers+ | code == uriFailure =+ URIFailure (fromJust $ parseURI $ fromJust $ lookup "URI" headers) (fromJust $ lookup "Message" headers)+ | code == generalFailure =+ GeneralFailure (fromJust $ lookup "Message" headers)+ | code == authorizationRequired =+ AuthorizationRequired (fromJust $ lookup "Site" headers)+ | code == mediaFailure =+ MediaFailure (fromJust $ lookup "Media" headers) (fromJust $ lookup "Drive" headers)+ parseStatus' _ _ = error "parseStatus'"++formatCommand :: Command -> [String]+formatCommand (URIAcquire uri filepath mLastModified) =+ [ uriAcquire ++ " URI Acquire"+ , "URI: " ++ uriToString' uri -- will this get credentials correct ? Or do we always pass those in seperately+ , "FileName: " ++ filepath+ ] ++ maybe [] (\lm -> ["Last-Modified: " ++ formatTimeRFC822 lm ]) mLastModified+formatCommand (Configuration configItems) =+ (configuration ++ " Configuration") : (map formatConfigItem configItems)+ where+ formatConfigItem (a,v) = concat ["Config-Item: ", a, "=", v]+formatCommand (AuthorizationCredentials site user passwd) =+ (authorizationCredentials ++ " Authorization Credentials") :+ [ "Site: " ++ site+ , "User: " ++ user+ , "Password: " ++ passwd+ ]+formatCommand (MediaChanged media mFail) =+ [ mediaChanged ++ " Media Changed"+ , "Media: " ++ media+ ] ++ maybe [] (\b -> ["Fail: " ++ case b of True -> "true" ; False -> "false"]) mFail+++parseTrueFalse :: String -> Bool+parseTrueFalse "true" = True+parseTrueFalse "false" = False+parseTrueFalse s = error $ "Invalid boolean string: " ++ s+++recvStatus :: MethodHandle -> IO Status+recvStatus mh = liftM parseStatus $ recv mh++sendCommand :: MethodHandle -> Command -> IO ()+sendCommand mh cmd = sendMethod mh (formatCommand cmd)+++parseHeader :: String -> Header+parseHeader str =+ let (a, r) = span (/= ':') str+ v = dropWhile (flip elem ": \t") r+ in+ (a, v)++openMethod :: FilePath -> IO MethodHandle+openMethod methodBinary =+ do+ -- hPutStrLn stderr ("openMethod " ++ methodBinary)+ runInteractiveCommand methodBinary+ -- runInteractiveProcess methodBinary [] Nothing Nothing++sendMethod :: MethodHandle -> [String] -> IO ()+sendMethod (pIn, _pOut, _, _) strings =+ do+ -- hPutStrLn stderr "send:"+ mapM_ put strings+ hPutStrLn pIn ""+ hFlush pIn+ where+ put line =+ do+ -- hPutStrLn stderr (" " ++ line)+ hPutStrLn pIn line++closeMethod :: MethodHandle -> IO ExitCode+closeMethod (pIn, pOut, pErr, handle) =+ do+ -- hPutStrLn stderr "closeMethod"+ hClose pIn+ hClose pOut+ hClose pErr+ waitForProcess handle++recv :: MethodHandle -> IO [String]+recv (_pIn, pOut, _pErr, _pHandle) =+ do+ -- hPutStrLn stderr "recv:"+ readTillEmptyLine pOut+ where+ readTillEmptyLine pOut =+ do+ line <- hGetLine pOut+ case line of+ "" -> return []+ line ->+ do+ -- hPutStrLn stderr (" " ++ line)+ tail <- readTillEmptyLine pOut+ return $ line : tail+{-+The flow of messages starts with the method sending out a+<em>100 Capabilities</> and APT sending out a <em>601 Configuration</>.++The flow is largely unsynchronized, but our function may have to+respond to things like authorization requests. Perhaps we do a+recvContents and then mapM_ over that ? Not all incoming messages+require a response.++We probably also need to track state, for example, if we are+pipelining multiple downloads and want to show seperate progress bars+for each download.++If someone wants to use fetch, they will need to provide methods to:++ 1. prompt for and provide authentication+ 2. show progress+ 3. show media change dialog+ 4. Show log messages+ 5. Show failures+ 6. Send Configuration++pipeline vs non-pipeline mode.+what if different methods are being used ?++when pipelining, we probably don't want to have too many pipelines to+the same server. Perhaps there can be a limit, and for non-pipelinable+methods, we set the limit to 1.++Each method can run in a seperate thread, since methods do not+interact with each other. In fact, each unique method+uri can be a+seperate thread. We can use a MVar to track the global max download+count. Perhaps we also want a per host throttle, since it is the host+connect that is likely to max out, not the access method.++Plan:++partition fetches by (host,method).+fork off threads for each (host, method).+Use MVar to throttle per host, and total connections++We don't know if a method supports pipelining until we connect atleast+once. So if we have a non-pipelined method, we might want to start+multiple streams. On the other hand, for something like a CDROM, that+will just cause the system to thrash.++cdrom, file, etc, don't have a host, so that is not a unique key then.+Pipelining on local methods is tricky, because it is hard to tell if+the local methods point to the same device or not.++Even though we have multiple threads, the interactor can view the+incoming Stream as a single Stream because all the events are tagged+with the URI (i think). But, sending commands involves a fancy+router. We could include a reference to corresponding command for each+stream.++For now, let's serialize the transfers, but allow pipeling for methods+that really allow pipelining.++-}++data FetchCallbacks+ = FetchCallbacks { logCB :: Message -> IO ()+ , statusCB :: URI -> Message -> IO ()+ , uriStartCB :: URI -> Maybe Integer -> Maybe UTCTime -> Maybe Integer -> IO ()+ , uriDoneCB :: URI -> Maybe Integer -> Maybe UTCTime -> Maybe Integer -> Maybe FilePath -> Hashes -> Bool -> IO ()+ , uriFailureCB :: URI -> Message -> IO ()+ , generalFailureCB :: Message -> IO ()+ , authorizationRequiredCB :: Site -> IO (Maybe (User, Password))+ , mediaFailureCB :: Media -> Drive -> IO ()+ , debugCB :: String -> IO ()+ }++simpleFetch :: [ConfigItem] -> URI -> FilePath -> Maybe UTCTime -> IO Bool+simpleFetch = fetch cliFetchCallbacks++-- |fetch a single item, show console output+-- see also: getLastModified+fetch :: FetchCallbacks -> [ConfigItem] -> URI -> FilePath -> Maybe UTCTime -> IO Bool+fetch cb configItems uri fp lastModified =+ do withMethodURI uri $ \mh ->+ do s <- recvStatus mh+ debugCB cb ("<- " ++ show s)+ sendCommand' mh (URIAcquire uri fp lastModified)+ loop mh+ where+ sendCommand' mh c =+ do mapM_ (debugCB cb . ("-> " ++)) (formatCommand c)+ sendCommand mh c+ loop mh =+ do r <- recvStatus mh+ case r of+ Capabilities {} ->+ do unless (null configItems) (sendCommand' mh (Configuration configItems))+ loop mh+ LogMsg m ->+ do logCB cb m+ loop mh+ Status uri m ->+ do statusCB cb uri m+ loop mh+ URIStart uri size lastModified resumePoint ->+ uriStartCB cb uri size lastModified resumePoint >> loop mh+ URIDone uri size lastModified resumePoint filename hashes imsHit ->+ uriDoneCB cb uri size lastModified resumePoint filename hashes imsHit >> return True+ URIFailure uri message ->+ uriFailureCB cb uri message >> return False+ GeneralFailure m -> generalFailureCB cb m >> return False+ AuthorizationRequired site ->+ do mCredentials <- authorizationRequiredCB cb site+ case mCredentials of+ Nothing -> return False -- FIXME: do we need a force close option for closeMethod ?+ Just (user, passwd) ->+ do sendCommand' mh (AuthorizationCredentials site user passwd)+ loop mh+ MediaFailure media drive ->+ do mediaFailureCB cb media drive+ return False++-- |set of callbacks which do nothing.+-- suitable for non-interactive usage. In the case authorization is+-- required, no credentials will be supplied and the download should+-- abort.+emptyFetchCallbacks =+ FetchCallbacks { logCB = \ _m -> return ()+ , statusCB = \ _uri _m -> return ()+ , uriStartCB = \ _uri _size _lastModified _resumePoint -> return ()+ , uriDoneCB = \ _uri _size _lastModified _resumePoint _filename _hashes _imsHit -> return ()+ , uriFailureCB = \ _uri _message -> return ()+ , generalFailureCB = \ _m -> return ()+ , authorizationRequiredCB = \ _site -> return Nothing+ , mediaFailureCB = \ _media _drive -> return ()+ , debugCB = \ _m -> return ()+ }++cliFetchCallbacks =+ emptyFetchCallbacks { statusCB = \uri m -> putStrLn $ uriToString' uri ++ " : " ++ m+ , uriStartCB = \ uri _size lastModified _resumePoint -> putStrLn $ uriToString' uri ++ " started. " ++ show lastModified+ , uriDoneCB = \uri _size _lastModified _resumePoint _filename _hashes imsHit -> putStrLn $ uriToString' uri ++ (if imsHit then " cached." else " downloaded.")+ , uriFailureCB = \uri message -> hPutStrLn stderr $ "URI Failure: " ++ uriToString' uri ++ " : " ++ message+ , generalFailureCB = \message -> hPutStrLn stderr $ "General Failure: " ++ message+ , authorizationRequiredCB = \site ->+ do putStrLn $ "Authorization Required for " ++ site+ putStrLn "Username: " >> hFlush stdout+ user <- getLine+ putStrLn "Password: " >> hFlush stdout+ passwd <- getLine -- TODO: write a getPasswd function which does not echo input+ return (Just (user, passwd))+ , mediaFailureCB = \media drive -> hPutStrLn stderr $ "Media Failure: media=" ++ media ++" drive="++ drive+ , debugCB = \m -> print m+ }++{-+ FetchCallbacks { logCB = \m -> hPutStrLn stderr m+ , statusCB = \uri m -> putStrLn (show uri ++" : "++ m)+ , uriStartCB = \uri+ }++defaultAuthenticate site =+ do putStrLn $ "Authorization Required for " ++ site+ putStrLn "Username: " >> hFlush stdout+ user <- getLine+ putStrLn "Password: " >> hFlush stdout+ passwd <- getLine -- TODO: write a getPasswd function which does not echo input+ return (user, passwd)+-}++{-+ let itemsByHost = groupOn (regName . fst) items+ in+ do totalQSem <- newQSem 16 -- max number of streams allowed for+ forkIO+ where+ regName = fmap uriRegName . uriAuthority+ withQSem :: QSem -> IO a -> IO a+ withQSem qSem f = bracket (waitQSem qSem) (const $ signalQSem qSem) (const f)++uris = map (fromJust . parseURI) [ "http://n-heptane.com/whee"+ , "file:/one/two/three"+ , "ssh://jeremy:aoeu@n-heptane.com"+ , "cdrom:/one"+ ]+-}++-- * Misc Helper Functions++bool :: a -> a -> Bool -> a+bool f _ False = f+bool _ t True = t+++getLastModified :: FilePath -> IO (Maybe UTCTime)+getLastModified fp =+ do e <- doesFileExist fp+ if e+ then getFileStatus fp >>= return . Just . epochTimeToUTCTime . modificationTime+ else return Nothing++{-+groupOn :: (Ord b) => (a -> b) -> [a] -> [[a]]+groupOn f = groupBy ((==) `on` f) . sortBy (compare `on` f)++on :: (a -> a -> b) -> (c -> a) -> c -> c -> b+on f g x y = f (g x) (g y)+-}
+ src/Debian/Apt/Package.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- |Functions for dealing with source and binary packages in an abstract-way+module Debian.Apt.Package where++-- Standard GHC Modules++import qualified Data.Map as Map++-- Local Modules++import Debian.Version+import Debian.Relation++type PackageNameMap a = Map.Map BinPkgName [a]++-- |'packageNameMap' creates a map from a package name to all the versions of that package+-- NOTE: Provides are not included in the map+-- NOTE: the sort order is random -- this is perhaps a bug+-- see also: 'addProvides'+packageNameMap :: (a -> BinPkgName) -> [a] -> PackageNameMap a+packageNameMap getName packages = foldl (\m p -> Map.insertWith (++) (getName p) [p] m) Map.empty packages++-- |'addProvides' finds packages that Provide other packages and adds+-- them to the PackageNameMap. They will be adde to the end of the+-- list, so that real packages have 'higher priority' than virtual+-- packages.+-- NOTE: Does not check for duplication or multiple use+addProvides :: (p -> [BinPkgName]) -> [p] -> PackageNameMap p -> PackageNameMap p+addProvides providesf ps pnm =+ let provides = findProvides providesf ps in+ foldl (\m (packageName, package) -> Map.insertWith (flip (++)) packageName [package] m) pnm provides++-- |'findProvides'+findProvides :: forall p. (p -> [BinPkgName]) -> [p] -> [(BinPkgName, p)]+findProvides providesf packages = foldl addProvides' [] packages+ where addProvides' :: [(BinPkgName, p)] -> p -> [(BinPkgName, p)]+ addProvides' providesList package =+ foldl (\pl pkgName -> (pkgName, package): pl) providesList (providesf package)++-- |'lookupPackageByRel' returns all the packages that satisfy the specified relation+-- TODO: Add architecture check+lookupPackageByRel :: PackageNameMap a -> (a -> (BinPkgName, DebianVersion)) -> Relation -> [a]+lookupPackageByRel pm packageVersionF (Rel pkgName mVerReq _mArch) =+ case Map.lookup pkgName pm of+ Nothing -> []+ Just packages -> filter filterVer packages+ where filterVer p =+ case mVerReq of+ Nothing -> True+ Just _verReq ->+ let (pName, pVersion) = packageVersionF p+ in if pName /= pkgName+ then False -- package is a virtual package, hence we can not do a version req+ else checkVersionReq mVerReq (Just pVersion)
+ src/Debian/Arch.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}+module Debian.Arch+ ( Arch(..)+ , ArchOS(..)+ , ArchCPU(..)+ , prettyArch+ , parseArch+ ) where++import Data.Data (Data)+import Data.Monoid ((<>))+import Data.Typeable (Typeable)+import Text.PrettyPrint (Doc, text)++data ArchOS = ArchOS String | ArchOSAny deriving (Eq, Ord, Read, Show, Data, Typeable)++prettyOS :: ArchOS -> Doc+prettyOS (ArchOS s) = text s+prettyOS ArchOSAny = text "any"++parseOS :: String -> ArchOS+parseOS "any" = ArchOSAny+parseOS s = ArchOS s++data ArchCPU = ArchCPU String | ArchCPUAny deriving (Eq, Ord, Read, Show, Data, Typeable)++prettyCPU :: ArchCPU -> Doc+prettyCPU (ArchCPU s) = text s+prettyCPU ArchCPUAny = text "any"++parseCPU :: String -> ArchCPU+parseCPU "any" = ArchCPUAny+parseCPU s = ArchCPU s++data Arch+ = Source+ | All+ | Binary ArchOS ArchCPU+ deriving (Eq, Ord, Read, Show, Data, Typeable)++prettyArch :: Arch -> Doc+prettyArch Source = text "source"+prettyArch All = text "all"+prettyArch (Binary (ArchOS "linux") cpu) = prettyCPU cpu+prettyArch (Binary os cpu) = prettyOS os <> text "-" <> prettyCPU cpu++parseArch :: String -> Arch+parseArch s =+ case span (/= '-') s of+ ("source", "") -> Source+ ("all", "") -> All+ (cpu, "") -> Binary (ArchOS "linux") (parseCPU cpu)+ (os, '-' : cpu) -> Binary (parseOS os) (parseCPU cpu)+ _ -> error "parseArch: internal error"
+ src/Debian/Changes.hs view
@@ -0,0 +1,309 @@+{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-missing-signatures #-}+-- |Changelog and changes file support.+module Debian.Changes+ ( ChangesFile(..)+ , ChangedFileSpec(..)+ , changesFileName+ , ChangeLog(..)+ , ChangeLogEntry(..)+ , parseChangeLog+ , parseEntries -- was parseLog+ , parseEntry+ , parseChanges+ ) where++import Data.Either (partitionEithers)+import Data.List (intercalate, intersperse)+import Data.Monoid ((<>))+import Data.Text (Text, pack, unpack, strip)+import Debian.Arch (Arch, prettyArch)+import Debian.Codename (Codename, codename, parseCodename)+import qualified Debian.Control.String as S+import Debian.Pretty (PP(..))+import Debian.Release+import Debian.Version+import System.Posix.Types+import Text.Regex.TDFA hiding (empty)+import Text.PrettyPrint (Doc, text, hcat, render)+import Distribution.Pretty (Pretty(pretty))++-- |A file generated by dpkg-buildpackage describing the result of a+-- package build+data ChangesFile =+ Changes { changeDir :: FilePath -- ^ The full pathname of the directory holding the .changes file.+ , changePackage :: String -- ^ The package name parsed from the .changes file name+ , changeVersion :: DebianVersion -- ^ The version number parsed from the .changes file name+ , changeRelease :: Codename -- ^ The Distribution field of the .changes file+ , changeArch :: Arch -- ^ The architecture parsed from the .changes file name+ , changeInfo :: S.Paragraph' Text -- ^ The contents of the .changes file+ , changeEntry :: ChangeLogEntry -- ^ The value of the Changes field of the .changes file+ , changeFiles :: [ChangedFileSpec] -- ^ The parsed value of the Files attribute+ } deriving (Eq, Read, Show)++-- |An entry in the list of files generated by the build.+data ChangedFileSpec =+ ChangedFileSpec { changedFileMD5sum :: String+ , changedFileSHA1sum :: String+ , changedFileSHA256sum :: String+ , changedFileSize :: FileOffset+ , changedFileSection :: SubSection+ , changedFilePriority :: String+ , changedFileName :: FilePath+ } deriving (Eq, Read, Show)++-- |A changelog is a series of ChangeLogEntries+data ChangeLogEntry =+ Entry { logPackage :: String -- FIXME: Should be a SrcPkgName+ , logVersion :: DebianVersion+ , logDists :: [Codename]+ , logUrgency :: String+ , logComments :: String+ , logWho :: String+ , logDate :: String+ }+ | WhiteSpace String -- ^ The parser here never returns this+ deriving (Eq, Read, Show)++newtype ChangeLog = ChangeLog [ChangeLogEntry] deriving (Eq, Read, Show)++{-+instance Show ChangesFile where+ show = changesFileName+-}++changesFileName :: ChangesFile -> String+changesFileName = render . pretty . PP++instance Pretty (PP ChangesFile) where+ pretty (PP changes) = text (changePackage changes ++ "_") <> prettyDebianVersion (changeVersion changes) <> text "_" <> prettyArch (changeArch changes) <> text ".changes"++instance Pretty (PP ChangedFileSpec) where+ pretty (PP file) =+ text (changedFileMD5sum file <> " " <>+ show (changedFileSize file) <> " " <>+ sectionName (changedFileSection file) <> " " <>+ changedFilePriority file <> " " <>+ changedFileName file)++instance Pretty (PP ChangeLogEntry) where+ pretty (PP (Entry package ver dists urgency details who date)) =+ hcat [ text package <> text " (" <> prettyDebianVersion ver <> text (") " <> intercalate " " (map codename dists) ++ "; urgency=" ++ urgency)+ , text "\n\n"+ , text " " <> text (strip' details)+ , text "\n\n"+ , text (" -- " <> who <> " " <> date)+ , text "\n" ]+ pretty (PP (WhiteSpace _)) = error "instance Pretty ChangeLogEntry"++instance Pretty (PP [ChangeLogEntry]) where+ pretty = hcat . intersperse (text "\n") . map (pretty . PP) . unPP++strip' = unpack . strip . pack++instance Pretty (PP ChangeLog) where+ pretty (PP (ChangeLog xs)) = hcat (intersperse (text "\n") (map (pretty . PP) xs))++-- |Show just the top line of a changelog entry (for debugging output.)+_showHeader :: ChangeLogEntry -> Doc+_showHeader (Entry package ver dists urgency _ _ _) =+ text (package <> " (") <> prettyDebianVersion ver <> text (") " <> intercalate " " (map codename dists) <> "; urgency=" <> urgency <> "...")+_showHeader (WhiteSpace _) = error "_showHeader"++{-+format is a series of entries like this:++ package (version) distribution(s); urgency=urgency+ [optional blank line(s), stripped]+ * change details+ more change details+ [blank line(s), included in output of dpkg-parsechangelog]+ * even more change details+ [optional blank line(s), stripped]+ -- maintainer name <email address>[two spaces] date++package and version are the source package name and version number.++distribution(s) lists the distributions where this version should be+installed when it is uploaded - it is copied to the Distribution field+in the .changes file. See Distribution, Section 5.6.14.++urgency is the value for the Urgency field in the .changes file for+the upload (see Urgency, Section 5.6.17). It is not possible to+specify an urgency containing commas; commas are used to separate+keyword=value settings in the dpkg changelog format (though there is+currently only one useful keyword, urgency).++The change details may in fact be any series of lines starting with at+least two spaces, but conventionally each change starts with an+asterisk and a separating space and continuation lines are indented so+as to bring them in line with the start of the text above. Blank lines+may be used here to separate groups of changes, if desired.++If this upload resolves bugs recorded in the Bug Tracking System+(BTS), they may be automatically closed on the inclusion of this+package into the Debian archive by including the string: closes:+Bug#nnnnn in the change details.[16] This information is conveyed via+the Closes field in the .changes file (see Closes, Section 5.6.22).++The maintainer name and email address used in the changelog should be+the details of the person uploading this version. They are not+necessarily those of the usual package maintainer. The information+here will be copied to the Changed-By field in the .changes file (see+Changed-By, Section 5.6.4), and then later used to send an+acknowledgement when the upload has been installed.++The date must be in RFC822 format[17]; it must include the time zone+specified numerically, with the time zone name or abbreviation+optionally present as a comment in parentheses.++The first "title" line with the package name must start at the left+hand margin. The "trailer" line with the maintainer and date details+must be preceded by exactly one space. The maintainer details and the+date must be separated by exactly two spaces.++The entire changelog must be encoded in UTF-8.+-}++-- | Parse the entries of a debian changelog and verify they are all+-- valid.+parseChangeLog :: String -> Either [[String]] ChangeLog+parseChangeLog s =+ case partitionEithers (parseEntries s) of+ ([], xs) -> Right (ChangeLog xs)+ (ss, _) -> Left ss++-- |Parse a Debian Changelog and return a lazy list of entries+parseEntries :: String -> [Either [String] ChangeLogEntry]+parseEntries "" = []+parseEntries text =+ case parseEntry text of+ Left messages -> [Left messages]+ Right (entry, text') -> Right entry : parseEntries text'++-- |Parse a single changelog entry, returning the entry and the remaining text.+parseEntry :: String -> Either [String] (ChangeLogEntry, String)+parseEntry text =+ case text =~ entryRE :: MatchResult String of+ x | mrSubList x == [] -> Left ["Parse error in " ++ show text]+ MR {mrAfter = after, mrSubList = [_, name, ver, dists, urgency, _, details, _, _, who, _, date, _]} ->+ Right (Entry name+ (parseDebianVersion' ver)+ (map parseCodename . words $ dists)+ urgency+ (" " ++ unpack (strip (pack details)) ++ "\n")+ (take (length who - 2) who)+ date,+ after)+ MR {mrBefore = _before, mrMatch = _matched, mrAfter = after, mrSubList = matches} ->+ Left ["Internal error\n after=" ++ show after ++ "\n " ++ show (length matches) ++ " matches: " ++ show matches]++entryRE = bol ++ blankLines ++ headerRE ++ changeDetails ++ signature ++ blankLines+changeDetails = "((\n| \n| -\n|([^ ]| [^--]| -[^--])[^\n]*\n)*)"+signature = " -- ([ ]*([^ ]+ )* )([^\n]*)\n"++-- |Parse the changelog information that shows up in the .changes+-- file, i.e. a changelog entry with no signature.+parseChanges :: Text -> Maybe ChangeLogEntry+parseChanges text =+ case unpack text =~ changesRE :: MatchResult String of+ MR {mrSubList = []} -> Nothing+ MR {mrSubList = [_, name, ver, dists, urgency, _, details]} ->+ Just $ Entry name+ (parseDebianVersion' ver)+ (map parseCodename . words $ dists)+ urgency+ details+ "" ""+ MR {mrSubList = x} -> error $ "Unexpected match: " ++ show x+ where+ changesRE = bol ++ blankLines ++ optWhite ++ headerRE ++ "(.*)$"++headerRE =+ package ++ ver ++ dists ++ urgency+ where+ package = "([^ \t(]*)" ++ optWhite+ ver = "\\(([^)]*)\\)" ++ optWhite+ dists = "([^;]*);" ++ optWhite+ urgency = "urgency=([^\n]*)\n" ++ blankLines++blankLines = blankLine ++ "*"+blankLine = "(" ++ optWhite ++ "\n)"+optWhite = "[ \t]*"+bol = "^"++-- This can be used for tests+_s1 = unlines+ ["haskell-regex-compat (0.92-3+seereason1~jaunty4) jaunty-seereason; urgency=low",+ "",+ " [ Joachim Breitner ]",+ " * Adjust priority according to override file",+ " * Depend on hscolour (Closes: #550769)",+ "",+ " [ Marco Túlio Gontijo e Silva ]",+ " * debian/control: Use more sintetic name for Vcs-Darcs.",+ " * Built from sid apt pool",+ " * Build dependency changes:",+ " cpphs: 1.9-1+seereason1~jaunty5 -> 1.9-1+seereason1~jaunty6",+ " ghc6: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1",+ " ghc6-doc: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1",+ " ghc6-prof: 6.10.4-1+seereason5~jaunty1 -> 6.12.1-0+seereason1~jaunty1",+ " haddock: 2.4.2-3+seereason3~jaunty1 -> 6.12.1-0+seereason1~jaunty1",+ " haskell-devscripts: 0.6.18-21+seereason1~jaunty1 -> 0.6.18-23+seereason1~jaunty1",+ " haskell-regex-base-doc: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1",+ " haskell-regex-posix-doc: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2",+ " libghc6-regex-base-dev: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1",+ " libghc6-regex-base-prof: 0.93.1-5+seereason1~jaunty1 -> 0.93.1-5++1+seereason1~jaunty1",+ " libghc6-regex-posix-dev: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2",+ " libghc6-regex-posix-prof: 0.93.2-4+seereason1~jaunty1 -> 0.93.2-4+seereason1~jaunty2",+ "",+ " -- SeeReason Autobuilder <autobuilder@seereason.org> Fri, 25 Dec 2009 01:55:37 -0800",+ "",+ "haskell-regex-compat (0.92-3) unstable; urgency=low",+ "",+ " [ Joachim Breitner ]",+ " * Adjust priority according to override file",+ " * Depend on hscolour (Closes: #550769)",+ "",+ " [ Marco Túlio Gontijo e Silva ]",+ " * debian/control: Use more sintetic name for Vcs-Darcs.",+ "",+ " -- Joachim Breitner <nomeata@debian.org> Mon, 20 Jul 2009 13:05:35 +0200",+ "",+ "haskell-regex-compat (0.92-2) unstable; urgency=low",+ "",+ " * Adopt package for the Debian Haskell Group",+ " * Fix \"FTBFS with new dpkg-dev\" by adding comma to debian/control",+ " (Closes: #536473)",+ "",+ " -- Joachim Breitner <nomeata@debian.org> Mon, 20 Jul 2009 12:05:40 +0200",+ "",+ "haskell-regex-compat (0.92-1.1) unstable; urgency=low",+ "",+ " * Rebuild for GHC 6.10.",+ " * NMU with permission of the author.",+ "",+ " -- John Goerzen <jgoerzen@complete.org> Mon, 16 Mar 2009 10:12:04 -0500",+ "",+ "haskell-regex-compat (0.92-1) unstable; urgency=low",+ "",+ " * New upstream release",+ " * debian/control:",+ " - Bump Standards-Version. No changes needed.",+ "",+ " -- Arjan Oosting <arjan@debian.org> Sun, 18 Jan 2009 00:05:02 +0100",+ "",+ "haskell-regex-compat (0.91-1) unstable; urgency=low",+ "",+ " * Take over package from Ian, as I already maintain haskell-regex-base,",+ " and move Ian to the Uploaders field.",+ " * Packaging complete redone (based on my haskell-regex-base package).",+ "",+ " -- Arjan Oosting <arjan@debian.org> Sat, 19 Jan 2008 16:48:39 +0100",+ "",+ "haskell-regex-compat (0.71.0.1-1) unstable; urgency=low",+ " ",+ " * Initial release (used to be part of ghc6).",+ " * Using \"Generic Haskell cabal library packaging files v9\".",+ " ",+ " -- Ian Lynagh (wibble) <igloo@debian.org> Wed, 21 Nov 2007 01:26:57 +0000"]
+ src/Debian/Codename.hs view
@@ -0,0 +1,27 @@+-- | https://wiki.debian.org/DebianRepository/Format#Codename++{-# LANGUAGE DeriveDataTypeable, TemplateHaskell #-}++module Debian.Codename+ ( Codename(..)+ , codename+ , parseCodename+ ) where++import Data.Data (Data, Typeable)+import Debian.TH ({-instance Pretty Loc-})+import Network.URI (unEscapeString, escapeURIString, isAllowedInURI)+--import Text.PrettyPrint.HughesPJClass as PP (Pretty(pPrint), text)+import Text.PrettyPrint (text)+import Distribution.Pretty++data Codename = Codename String deriving (Eq, Ord, Read, Show, Data, Typeable)++parseCodename :: String -> Codename+parseCodename = Codename . unEscapeString++codename :: Codename -> String+codename (Codename s) = escapeURIString isAllowedInURI s++instance Pretty Codename where+ pretty (Codename s) = text s
+ src/Debian/Control.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}+-- |A module for working with Debian control files <http://www.debian.org/doc/debian-policy/ch-controlfields.html>+module Debian.Control+ ( -- * Types+ Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , Control+ , Paragraph+ , Field+ , ControlParser+ , ControlFunctions(..)+ -- * Control File Parser+ , pControl+ -- * Helper Functions+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ , packParagraph+ , packField+ , formatControl+ , formatParagraph+ , formatField+ -- * Policy classes and functions+ , P.HasDebianControl(..)+ , P.ControlFileError(..)+ , P.parseDebianControlFromFile+ , P.validateDebianControl+ , P.unsafeDebianControl+ , P.debianSourceParagraph+ , P.debianBinaryParagraphs+ , P.debianPackageParagraphs+ , P.debianPackageNames+ , P.debianSourcePackageName+ , P.debianBinaryPackageNames+ , P.debianRelations+ , P.debianBuildDeps+ , P.debianBuildDepsIndep+ ) where++--import Control.Monad+--import Data.List+--import Text.ParserCombinators.Parsec+--import System.IO+import Debian.Control.Common+import Debian.Control.String+import Data.List+import Data.Text as T (Text, pack, concat)+import qualified Debian.Control.Builder ()+import qualified Debian.Control.Text as T+--import qualified Debian.Control.TextLazy as TL+import qualified Debian.Control.ByteString as B ()+import qualified Debian.Control.Policy as P+import qualified Debian.Control.String as S++packParagraph :: S.Paragraph -> T.Paragraph+packParagraph (S.Paragraph s) = T.Paragraph (map packField s)++packField :: Field' String -> Field' Text+packField (S.Field (name, value)) = T.Field (T.pack name, T.pack value)+packField (S.Comment s) = T.Comment (T.pack s)++formatControl :: Control' Text -> [Text]+formatControl (T.Control paragraphs) = intersperse (T.pack "\n") . map formatParagraph $ paragraphs++formatParagraph :: Paragraph' Text -> Text+formatParagraph (T.Paragraph fields) = T.concat . map formatField $ fields++formatField :: Field' Text -> Text+formatField (T.Field (name, value)) = T.concat [name, T.pack ":", value, T.pack "\n"]+formatField (T.Comment s) = s
+ src/Debian/Control/Builder.hs view
@@ -0,0 +1,162 @@+{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}+module Debian.Control.Builder+ ( -- * Types+ Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , Control+ , Paragraph+ , Field+ -- , ControlParser+ , ControlFunctions(..)+ -- * Control File Parser+ -- , pControl+ -- * Helper Functions+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ , decodeControl+ , decodeParagraph+ , decodeField+ ) where++import qualified Data.ByteString.Char8 as B+import Data.Char (toLower, chr)+import Data.List (find)+import qualified Data.ListLike as LL+import Data.ListLike.Text.Builder ()+--import qualified Data.Text as T (pack, unpack, map, reverse)+import Data.Text.Lazy (toStrict)+import Data.Text.Lazy.Builder (Builder, {-fromLazyText,-} fromText, toLazyText)+import Data.Text.Encoding (decodeUtf8With, encodeUtf8)+--import Data.Text.IO as T (readFile)+import qualified Debian.Control.ByteString as B+--import Text.Parsec.Error (ParseError)+--import Text.Parsec.Text (Parser)+--import Text.Parsec.Prim (runP)+import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, asString),+ Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),+ mergeControls, fieldValue, removeField, prependFields, appendFields,+ renameField, modifyField, raiseFields, protectFieldText')++-- | @parseFromFile p filePath@ runs a string parser @p@ on the+-- input read from @filePath@ using 'Prelude.readFile'. Returns either a 'ParseError'+-- ('Left') or a value of type @a@ ('Right').+--+-- > main = do{ result <- parseFromFile numbers "digits.txt"+-- > ; case result of+-- > Left err -> print err+-- > Right xs -> print (sum xs)+-- > }+{-+parseFromFile :: Parser a -> String -> IO (Either ParseError a)+parseFromFile p fname+ = do input <- T.readFile fname `E.catch` (\ (_ :: E.SomeException) -> B.readFile fname >>= return . decode)+ return (runP p () fname input)+-}++type Field = Field' Builder+type Control = Control' Builder+type Paragraph = Paragraph' Builder++decodeControl :: B.Control -> Control+decodeControl (B.Control paragraphs) = Control (map decodeParagraph paragraphs)++decodeParagraph :: B.Paragraph -> Paragraph+decodeParagraph (B.Paragraph s) = B.Paragraph (map decodeField s)++decodeField :: Field' B.ByteString -> Field' Builder+decodeField (B.Field (name, value)) = Field (decode name, decode value)+decodeField (B.Comment s) = Comment (decode s)++decode :: B.ByteString -> Builder+decode = fromText . decodeUtf8With (\ _ w -> fmap (chr . fromIntegral) w)++-- * ControlFunctions++instance ControlFunctions Builder where+ parseControlFromFile filepath =+ -- The ByteString parser is far more efficient than the Text+ -- parser. By calling decodeControl we tell the compiler to+ -- use it instead.+ parseControlFromFile filepath >>= return . either Left (Right . decodeControl)+ parseControlFromHandle sourceName handle =+ parseControlFromHandle sourceName handle >>= return . either Left (Right . decodeControl)+ parseControl sourceName c =+ -- Warning: This is very slow, it does a utf8 round trip+ either Left (Right . decodeControl) (parseControl sourceName (encodeUtf8 (toStrict (toLazyText c))))+ lookupP fieldName (Paragraph paragraph) =+ find (hasFieldName (map toLower fieldName)) paragraph+ where hasFieldName :: String -> Field' Builder -> Bool+ hasFieldName name (Field (fieldName',_)) = name == LL.map toLower (LL.toString fieldName')+ hasFieldName _ _ = False+ stripWS = dropAround (`elem` (" \t" :: String))+ -- T.strip would also strip newlines+ protectFieldText = protectFieldText'+ asString = LL.toString++dropAround :: LL.ListLike c item => (item -> Bool) -> c -> c+dropAround p = LL.dropWhile p . LL.dropWhileEnd p++-- * Control File Parser+{-+-- type ControlParser = GenParser T.Text+type ControlParser a = Parsec T.Text () a++-- |A parser for debian control file. This parser handles control files+-- that end without a newline as well as ones that have several blank+-- lines at the end. It is very liberal and does not attempt validate+-- the fields in any way. All trailing, leading, and folded whitespace+-- is preserved in the field values. See 'stripWS'.+pControl :: ControlParser Control+pControl =+ do many $ char '\n'+ sepEndBy pParagraph pBlanks >>= return . Control++pParagraph :: ControlParser Paragraph+pParagraph = many1 (pComment <|> pField) >>= return . Paragraph++-- |We are liberal in that we allow *any* field to have folded white+-- space, even though the specific restricts that to a few fields.+pField :: ControlParser Field+pField =+ do c1 <- noneOf "#\n"+ fieldName <- many1 $ noneOf ":\n"+ char ':'+ fieldValue <- many fcharfws+ (char '\n' >> return ()) <|> eof+ return $ Field (T.cons c1 (T.pack fieldName), T.pack fieldValue)++pComment :: ControlParser Field+pComment =+ do char '#'+ text <- many (satisfy (not . ((==) '\n')))+ char '\n'+ return $ Comment (T.pack ("#" <> text <> "\n"))++fcharfws :: ControlParser Char+fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')++fchar :: ControlParser Char+fchar = satisfy (/='\n')++_fws :: ControlParser T.Text+_fws =+ try $ do char '\n'+ ws <- many1 (char ' ')+ c <- many1 (satisfy (not . ((==) '\n')))+ return $ T.cons '\n' (T.pack ws <> T.pack c)++-- |We go with the assumption that 'blank lines' mean lines that+-- consist of entirely of zero or more whitespace characters.+pBlanks :: ControlParser T.Text+pBlanks =+ do s <- many1 (oneOf " \n")+ return . T.pack $ s+-}
+ src/Debian/Control/ByteString.hs view
@@ -0,0 +1,289 @@+{-# LANGUAGE CPP, FlexibleContexts, MultiParamTypeClasses, PackageImports, ScopedTypeVariables #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing -fno-warn-orphans #-}+module Debian.Control.ByteString+ ( Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , Control+ , Paragraph+ , Field+ , ControlFunctions(..)+ -- * Helper Functions+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ ) where++-- Standard GHC modules++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative (Applicative(..))+#endif+import Control.Applicative (Alternative(..))+import qualified Control.Exception as E+import "mtl" Control.Monad.State++import Data.Char(toLower, isSpace, chr, ord)+import Data.Word (Word8)+import Data.List+import qualified Data.ListLike as LL+import qualified Data.ListLike.String as LL++import Text.ParserCombinators.Parsec.Error+import Text.ParserCombinators.Parsec.Pos++-- Third Party Modules++import qualified Data.ByteString.Char8 as C++import Debian.Control.Common hiding (protectFieldText')++-- Local Modules++-- import ByteStreamParser++-- * Types+{-+newtype Control = Control [Paragraph]+newtype Paragraph = Paragraph [Field]+newtype Field = Field (C.ByteString, C.ByteString)+-}++type Control = Control' C.ByteString+type Paragraph = Paragraph' C.ByteString+type Field = Field' C.ByteString+-- * Control Parser++type ControlParser a = Parser C.ByteString a++pKey :: ControlParser C.ByteString+pKey = notEmpty $ pTakeWhile (\c -> (c /= ':') && (c /= '\n'))++pValue :: ControlParser C.ByteString+pValue = Parser $ \bs ->+ let newlines = C.elemIndices '\n' bs+ rest = dropWhile continuedAfter newlines ++ [C.length bs]+ continuedAfter i = bs `safeIndex` (i+1) `elem` map Just " \t#"+ (text, bs') = C.splitAt (head rest) bs+ in Ok (text, bs')++pField :: ControlParser Field+pField =+ do k <- pKey+ _ <- pChar ':'+ v <- pValue+-- pChar '\n'+ (pChar '\n' >> return ()) <|> pEOF+ return (Field (k,v))++pComment :: ControlParser Field+pComment = Parser $ \bs ->+ let newlines = C.elemIndices '\n' bs+ linestarts = 0 : map (+1) newlines+ rest = dropWhile commentAt linestarts ++ [C.length bs]+ commentAt i = bs `safeIndex` i == Just '#'+ (text, bs') = C.splitAt (head rest) bs+ in if C.null text+ then Empty+ else Ok (Comment text, bs')++pParagraph :: ControlParser Paragraph+pParagraph =+ do f <- pMany1 (pComment <|> pField)+ pSkipMany (pChar '\n')+ return (Paragraph f)++pControl :: ControlParser Control+pControl =+ do pSkipMany (pChar '\n')+ c <- pMany pParagraph+ return (Control c)+++-- parseControlFromFile :: FilePath -> IO (Either String Control)++instance ControlFunctions C.ByteString where+ parseControlFromFile fp =+ do c <- C.readFile fp+ case parse pControl c of+ Nothing -> return (Left (newErrorMessage (Message ("Failed to parse " ++ fp)) (newPos fp 0 0)))+ (Just (cntl,_)) -> return (Right cntl)+ parseControlFromHandle sourceName handle =+ E.try (C.hGetContents handle) >>=+ either (\ (e :: E.SomeException) -> error ("parseControlFromHandle ByteString: Failure parsing " ++ sourceName ++ ": " ++ show e)) (return . parseControl sourceName)+ parseControl sourceName c =+ do case parse pControl c of+ Nothing -> Left (newErrorMessage (Message ("Failed to parse " ++ sourceName)) (newPos sourceName 0 0))+ Just (cntl,_) -> Right cntl+ lookupP fieldName (Paragraph fields) =+ let pFieldName = C.pack (map toLower fieldName) in+ find (\ (Field (fieldName',_)) -> C.map toLower fieldName' == pFieldName) fields+ -- NOTE: probably inefficient+ stripWS = C.reverse . strip . C.reverse . strip+ where strip = C.dropWhile (flip elem [' ', '\t'])+ protectFieldText = protectFieldText'+ asString = C.unpack++protectFieldText' :: (LL.StringLike a, LL.ListLike a Word8) => ControlFunctions a => a -> a+protectFieldText' s =+ case LL.lines s of+ [] -> LL.empty+ (l : ls) -> dropWhileEnd (isSpace . chr . fromIntegral) $ LL.unlines $ l : map protect ls+ where+ dropWhileEnd :: (LL.StringLike a, LL.ListLike a Word8) => (Word8 -> Bool) -> a -> a+ dropWhileEnd func = LL.reverse . LL.dropWhile func . LL.reverse -- foldr (\x xs -> if func x && LL.null xs then LL.empty else LL.cons x xs) empty+ protect :: (LL.StringLike a, LL.ListLike a Word8) => a -> a+ protect l = maybe LL.empty (\ c -> if isHorizSpace c then l else LL.cons (ord' ' ' :: Word8) l) (LL.find (const True :: Word8 -> Bool) l)+ -- isSpace' = isSpace . chr'+ isHorizSpace c = elem c (map ord' " \t")+ ord' = fromIntegral . ord+ -- chr' = chr . fromIntegral++{-+main =+ do [fp] <- getArgs+ C.readFile fp >>= \c -> maybe (putStrLn "failed.") (print . length . fst) (parse pControl c)+-}+-- * Helper Functions++safeIndex :: C.ByteString -> Int -> Maybe Char+bs `safeIndex` i = if i < C.length bs then Just (bs `C.index` i) else Nothing++-- * Parser++data Result a+ = Ok a+ | Fail+ | Empty+ deriving Show++-- m2r :: Maybe a -> Result a+-- m2r (Just a) = Ok a+-- m2r Nothing = Empty++r2m :: Result a -> Maybe a+r2m (Ok a) = Just a+r2m _ = Nothing++newtype Parser state a = Parser { unParser :: (state -> Result (a, state)) }++instance Functor (Parser state) where+ fmap f m =+ Parser $ \ state ->+ let r = (unParser m) state in+ case r of+ Ok (a,state') -> Ok (f a,state')+ Empty -> Empty+ Fail -> Fail++instance Applicative (Parser state) where+ pure = return+ (<*>) = ap++instance Alternative (Parser state) where+ empty =+ Parser $ \state ->+ (unParser mzero) state+ (<|>) = mplus++instance Monad (Parser state) where+ return a = Parser (\s -> Ok (a,s))+ m >>= f =+ Parser $ \state ->+ let r = (unParser m) state in+ case r of+ Ok (a,state') ->+ case unParser (f a) $ state' of+ Empty -> Fail+ o -> o+ Empty -> Empty+ Fail -> Fail++instance MonadPlus (Parser state) where+ mzero = Parser (const Empty)+ mplus (Parser p1) (Parser p2) =+ Parser (\s -> case p1 s of+ Empty -> p2 s+ o -> o+ )++-- Parser (\s -> maybe (p2 s) (Just) (p1 s))+++_pSucceed :: a -> Parser state a+_pSucceed = return++_pFail :: Parser state a+_pFail = Parser (const Empty)+++satisfy :: (Char -> Bool) -> Parser C.ByteString Char+satisfy f =+ Parser $ \bs ->+ if C.null bs+ then Empty+ else let (s,ss) = (C.head bs, C.tail bs) in+ if (f s)+ then Ok (s,ss)+ else Empty++pChar :: Char -> Parser C.ByteString Char+pChar c = satisfy ((==) c)+++_try :: Parser state a -> Parser state a+_try (Parser p) =+ Parser $ \bs -> case (p bs) of+ Fail -> Empty+ o -> o++pEOF :: Parser C.ByteString ()+pEOF =+ Parser $ \bs -> if C.null bs then Ok ((),bs) else Empty++pTakeWhile :: (Char -> Bool) -> Parser C.ByteString C.ByteString+pTakeWhile f =+ Parser $ \bs -> Ok (C.span f bs)++_pSkipWhile :: (Char -> Bool) -> Parser C.ByteString ()+_pSkipWhile p =+ Parser $ \bs -> Ok ((), C.dropWhile p bs)++pMany :: Parser st a -> Parser st [a]+pMany p+ = scan id+ where+ scan f = do x <- p+ scan (\tail -> f (x:tail))+ <|> return (f [])++notEmpty :: Parser st C.ByteString -> Parser st C.ByteString+notEmpty (Parser p) =+ Parser $ \s -> case p s of+ o@(Ok (a, _s)) ->+ if C.null a+ then Empty+ else o+ x -> x++pMany1 :: Parser st a -> Parser st [a]+pMany1 p =+ do x <- p+ xs <- pMany p+ return (x:xs)++pSkipMany :: Parser st a -> Parser st ()+pSkipMany p = scan+ where+ scan = (p >> scan) <|> return ()++_pSkipMany1 :: Parser st a -> Parser st ()+_pSkipMany1 p = p >> pSkipMany p++parse :: Parser state a -> state -> Maybe (a, state)+parse p s = r2m ((unParser p) s)
+ src/Debian/Control/Common.hs view
@@ -0,0 +1,186 @@+{-# LANGUAGE FlexibleContexts, FlexibleInstances, OverloadedStrings, ScopedTypeVariables, UndecidableInstances #-}+module Debian.Control.Common+ ( -- * Types+ Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , ControlFunctions(..)+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ , parseControlFromCmd+ , md5sumField+ , protectFieldText'+ )+ where++import Control.Monad (msum)+import Data.Char (isSpace)+import Data.List as List (dropWhileEnd, partition, intersperse)+import Data.ListLike as LL (ListLike, cons, dropWhileEnd, empty, find, null, singleton)+import Data.ListLike.String as LL (StringLike, lines, unlines)+import Data.Monoid ((<>))+import Debian.Pretty (PP(..))+import System.Exit (ExitCode(ExitSuccess, ExitFailure))+import System.IO (Handle)+import System.Process (runInteractiveCommand, waitForProcess)+import Text.ParserCombinators.Parsec (ParseError)+import Text.PrettyPrint (Doc, text, hcat)+import Distribution.Pretty (Pretty(pretty))++newtype Control' a+ = Control { unControl :: [Paragraph' a] } deriving (Eq, Ord, Read, Show)++newtype Paragraph' a+ = Paragraph [Field' a]+ deriving (Eq, Ord, Read, Show)++-- |NOTE: we do not strip the leading or trailing whitespace in the+-- name or value+data Field' a+ = Field (a, a)+ | Comment a -- ^ Lines beginning with #+ deriving (Eq, Ord, Read, Show)++class ControlFunctions a where+ -- |'parseControlFromFile' @filepath@ is a simple wrapper function+ -- that parses @filepath@ using 'pControl'+ parseControlFromFile :: FilePath -> IO (Either ParseError (Control' a))+ -- |'parseControlFromHandle' @sourceName@ @handle@ - @sourceName@ is only used for error reporting+ parseControlFromHandle :: String -> Handle -> IO (Either ParseError (Control' a))+ -- |'parseControlFromString' @sourceName@ @text@ - @sourceName@ is only used for error reporting+ parseControl :: String -> a -> (Either ParseError (Control' a))+ -- | 'lookupP' @fieldName paragraph@ looks up a 'Field' in a 'Paragraph'.+ -- @N.B.@ trailing and leading whitespace is /not/ stripped.+ lookupP :: String -> (Paragraph' a) -> Maybe (Field' a)+ -- |Strip the trailing and leading space and tab characters from a+ -- string. Folded whitespace is /not/ unfolded. This should probably+ -- be moved to someplace more general purpose.+ stripWS :: a -> a+ -- |Protect field value text so the parser doesn't split it into+ -- multiple fields or paragraphs. This must modify all field text+ -- to enforce two conditions: (1) All lines other than the initial+ -- one must begin with a space or a tab, and (2) the trailing+ -- white space must not contain newlines. This is called before+ -- pretty printing to prevent the parser from misinterpreting+ -- field text as multiple fields or paragraphs.+ protectFieldText :: a -> a+ asString :: a -> String++-- | This can usually be used as the implementation of protectFieldText+protectFieldText' :: forall a. (StringLike a, ListLike a Char) => ControlFunctions a => a -> a+protectFieldText' s =+ let trimmedLines :: [a]+ trimmedLines = map (LL.dropWhileEnd isSpace :: a -> a) $ (LL.lines s :: [a])+ strippedLines :: [a]+ strippedLines = List.dropWhileEnd LL.null trimmedLines in+ -- Split the text into lines, drop trailing whitespace from each+ -- line, and drop trailing blank lines.+ case strippedLines of+ [] -> empty+ (l : ls) ->+ let -- The first line is indented one space+ l' = {-LL.cons ' '-} l+ -- Null lines are replaced by a single '.' If any line+ -- is unindented, all will get an additional space of+ -- indentation.+ ls' = case all indented ls of+ True -> map (\ x -> if LL.null x then (LL.cons ' ' $ singleton '.') else x) ls+ False -> map (LL.cons ' ') $ map (\ x -> if LL.null x then (singleton '.') else x) ls in+ LL.dropWhileEnd isSpace (LL.unlines (l' : ls'))+ where+ indented l = maybe True isSpace (LL.find (const True) l)++-- | This may have bad performance issues (dsf: Whoever wrote this+-- comment should have explained why.)+instance (ControlFunctions a, Pretty (PP a)) => Pretty (Control' a) where+ pretty = ppControl+instance (ControlFunctions a, Pretty (PP a)) => Pretty (Paragraph' a) where+ pretty = ppParagraph++instance (ControlFunctions a, Pretty (PP a)) => Pretty (Field' a) where+ pretty = ppField++ppControl :: (ControlFunctions a, Pretty (PP a)) => Control' a -> Doc+ppControl (Control paragraph) =+ hcat (intersperse (text "\n") (map ppParagraph paragraph))++ppParagraph :: (ControlFunctions a, Pretty (PP a)) => Paragraph' a -> Doc+ppParagraph (Paragraph fields) =+ hcat (map (\ x -> ppField x <> text "\n") fields)++ppField :: (ControlFunctions a, Pretty (PP a)) => Field' a -> Doc+ppField (Field (n,v)) = pretty (PP n) <> text ":" <> pretty (PP (protectFieldText v))+ppField (Comment c) = pretty (PP c)++mergeControls :: [Control' a] -> Control' a+mergeControls controls =+ Control (concatMap unControl controls)++fieldValue :: (ControlFunctions a) => String -> Paragraph' a -> Maybe a+fieldValue fieldName paragraph =+ case lookupP fieldName paragraph of+ Just (Field (_, val)) -> Just $ stripWS val+ _ -> Nothing++removeField :: (Eq a) => a -> Paragraph' a -> Paragraph' a+removeField toRemove (Paragraph fields) =+ Paragraph (filter remove fields)+ where+ remove (Field (name,_)) = name == toRemove+ remove (Comment _) = False++prependFields :: [Field' a] -> Paragraph' a -> Paragraph' a+prependFields newfields (Paragraph fields) = Paragraph (newfields ++ fields)++appendFields :: [Field' a] -> Paragraph' a -> Paragraph' a+appendFields newfields (Paragraph fields) = Paragraph (fields ++ newfields)++renameField :: (Eq a) => a -> a -> Paragraph' a -> Paragraph' a+renameField oldname newname (Paragraph fields) =+ Paragraph (map rename fields)+ where+ rename (Field (name, value)) | name == oldname = Field (newname, value)+ rename field = field++modifyField :: (Eq a) => a -> (a -> a) -> Paragraph' a -> Paragraph' a+modifyField name f (Paragraph fields) =+ Paragraph (map modify fields)+ where+ modify (Field (name', value)) | name' == name = Field (name, f value)+ modify field = field++-- | Move selected fields to the beginning of a paragraph.+raiseFields :: (Eq a) => (a -> Bool) -> Paragraph' a -> Paragraph' a+raiseFields f (Paragraph fields) =+ let (a, b) = partition f' fields in Paragraph (a ++ b)+ where f' (Field (name, _)) = f name+ f' (Comment _) = False++-- | Run a command and parse its output as a control file.+parseControlFromCmd :: ControlFunctions a => String -> IO (Either String (Control' a))+parseControlFromCmd cmd =+ do+ (_, outh, _, handle) <- runInteractiveCommand cmd+ result <- parseControlFromHandle cmd outh+ either (return . Left . show) (finish handle) result+ where+ finish handle control =+ do+ exitCode <- waitForProcess handle+ case exitCode of+ ExitSuccess -> return $ Right control+ ExitFailure n -> return $ Left ("Failure: " ++ cmd ++ " -> " ++ show n)++-- |look up the md5sum file in a paragraph+-- Tries several different variations:+-- MD5Sum:+-- Md5Sum:+-- MD5sum:+md5sumField :: (ControlFunctions a) => Paragraph' a -> Maybe a+md5sumField p = msum [fieldValue "MD5Sum" p, fieldValue "Md5Sum" p, fieldValue "MD5sum" p]
+ src/Debian/Control/Policy.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE CPP, DeriveDataTypeable, FlexibleContexts, FlexibleInstances, FunctionalDependencies, MultiParamTypeClasses, RecordWildCards, ScopedTypeVariables, TemplateHaskell #-}+{-# OPTIONS_GHC -Wall #-}+-- | Access to things that Debian policy says should be in a valid+-- control file. The pure functions will not throw ControlFileError+-- if they are operating on a DebianControl value returned by+-- validateDebianControl. However, they might if they are created+-- using unsafeDebianControl.+module Debian.Control.Policy+ ( -- * Validated debian control file type+ DebianControl(unDebianControl)+ , validateDebianControl+ , unsafeDebianControl+ , parseDebianControlFromFile+ , parseDebianControl+ , ControlFileError(..)+ -- * Class of things that contain one DebianControl value+ , HasDebianControl(debianControl)+ -- * Pure functions that operate on validated control files+ , debianSourceParagraph+ , debianBinaryParagraphs+ , debianPackageParagraphs+ , debianPackageNames+ , debianSourcePackageName+ , debianBinaryPackageNames+ , debianRelations+ , debianBuildDeps+ , debianBuildDepsIndep+ ) where++import Control.Exception (Exception, throw)+import Control.Monad.Catch (MonadCatch, try)+import Data.List (intercalate)+import Data.Text (Text)+import Data.Typeable (Typeable)+import Data.ListLike (toList)+import Debian.Control.Common (Control'(..), Paragraph'(..), Field'(..), fieldValue, ControlFunctions(parseControlFromFile, parseControl))+import Debian.Control.Text ()+import Debian.Loc (__LOC__)+import Debian.Pretty (prettyShow)+import Debian.Relation (SrcPkgName(..), BinPkgName(..), Relations, parseRelations)+import Debian.Relation.Text ()+import Language.Haskell.TH (Loc(..))+import Prelude hiding (ioError)+-- import qualified Debug.ShowPlease as Please+import Text.Parsec.Error (ParseError)++-- | Opaque (constructor not exported) type to hold a validated Debian+-- Control File+data DebianControl = DebianControl {unDebianControl :: Control' Text}++instance Show DebianControl where+ show c = "(parseDebianControl \"\" " ++ show (prettyShow (unDebianControl c)) ++ ")"++-- | Validate and return a control file in an opaque wrapper. May+-- throw a ControlFileError. Currently we only verify that it has a+-- Source field in the first paragraph and one or more subsequent+-- paragraphs each with a Package field, and no syntax errors in the+-- build dependencies (though they may be absent.)+validateDebianControl :: MonadCatch m => Control' Text -> m (Either ControlFileError DebianControl)+validateDebianControl ctl =+ try (do _ <- return $ debianPackageNames (DebianControl ctl)+ _ <- return $ debianBuildDeps (DebianControl ctl)+ _ <- return $ debianBuildDepsIndep (DebianControl ctl)+ return ()) >>=+ return . either Left (\ _ -> Right $ DebianControl ctl)++unsafeDebianControl :: Control' Text -> DebianControl+unsafeDebianControl = DebianControl++parseDebianControl :: MonadCatch m => String -> Text -> m (Either ControlFileError DebianControl)+parseDebianControl sourceName s = either (return . Left . ParseControlError [$__LOC__]) validateDebianControl (parseControl sourceName s)++parseDebianControlFromFile :: FilePath -> IO (Either ControlFileError DebianControl)+parseDebianControlFromFile controlPath =+ try (parseControlFromFile controlPath) >>=+ either (return . Left . IOError [$__LOC__])+ (either (return . Left . ParseControlError [$__LOC__]) validateDebianControl)++-- | Class of things that contain a validated Debian control file.+class Show a => HasDebianControl a where+ debianControl :: a -> DebianControl++instance HasDebianControl DebianControl where+ debianControl = id++class HasControl a where+ control :: a -> Control' Text++instance HasControl (Control' Text) where+ control = id++instance HasControl DebianControl where+ control = unDebianControl++-- | Errors that control files might throw, with source file name and+-- line number generated by template haskell.+data ControlFileError+ = NoParagraphs {locs :: [Loc]}+ | NoBinaryParagraphs {locs :: [Loc], ctl :: String}+ | MissingField {locs :: [Loc], field :: String}+ | ParseRelationsError {locs :: [Loc], parseError :: ParseError}+ | ParseControlError {locs :: [Loc], parseError :: ParseError}+ | IOError {locs :: [Loc], ioError :: IOError}+ deriving Typeable++instance Show ControlFileError where+ show (NoParagraphs {..}) = intercalate ", " (map showLoc locs) ++ ": NoParagraphs"+ show (NoBinaryParagraphs {..}) = intercalate ", " (map showLoc locs) ++ ": NoBinaryParagraphs"+ show (MissingField {..}) = intercalate ", " (map showLoc locs) ++ ": MissingField " ++ show field+ show (ParseRelationsError {..}) = intercalate ", " (map showLoc locs) ++ ": ParseRelationsError " ++ show parseError+ show (ParseControlError {..}) = intercalate ", " (map showLoc locs) ++ ": ParseControlError " ++ show parseError+ show (IOError {..}) = intercalate ", " (map showLoc locs) ++ ": IOError " ++ show ioError++showLoc :: Loc -> String+showLoc x = show (loc_filename x) ++ "(line " ++ show (fst (loc_start x)) ++ ", column " ++ show (snd (loc_start x)) ++ ")"++-- instance Please.Show ControlFileError where+-- show (IOError e) = "(IOError " ++ Please.show e ++ ")"+-- show (ParseRelationsError e) = "(ParseRelationsError " ++ Please.show e ++ ")"+-- show (ParseControlError e) = "(ParseControlError " ++ Please.show e ++ ")"+-- show x = show x++instance Exception ControlFileError++instance Eq ControlFileError where+ _ == _ = False++debianPackageParagraphs :: HasDebianControl a => a -> (Paragraph' Text, [Paragraph' Text])+debianPackageParagraphs ctl =+ case removeCommentParagraphs ctl of+ DebianControl (Control [_]) -> throw $ NoBinaryParagraphs [$__LOC__] (show ctl)+ DebianControl (Control []) -> throw $ NoParagraphs [$__LOC__]+ DebianControl (Control (sourceParagraph : binParagraphs)) -> (sourceParagraph, binParagraphs)++-- | Comment paragraphs are rare, but they happen.+removeCommentParagraphs :: HasDebianControl a => a -> DebianControl+removeCommentParagraphs c =+ DebianControl (Control (filter (not . isCommentParagraph) (unControl (unDebianControl (debianControl c)))))+ where+ isCommentParagraph (Paragraph fields) = all isCommentField fields+ isCommentField (Comment _) = True+ isCommentField _ = False++debianSourceParagraph :: HasDebianControl a => a -> Paragraph' Text+debianSourceParagraph = fst . debianPackageParagraphs++debianBinaryParagraphs :: HasDebianControl a => a -> [Paragraph' Text]+debianBinaryParagraphs = snd . debianPackageParagraphs++debianPackageNames :: HasDebianControl a => a -> (SrcPkgName, [BinPkgName])+debianPackageNames c =+ let (srcParagraph, binParagraphs) = debianPackageParagraphs c in+ (mapFieldValue (SrcPkgName . toList) "Source" srcParagraph, map (mapFieldValue (BinPkgName . toList) "Package") binParagraphs)++debianSourcePackageName :: HasDebianControl a => a -> SrcPkgName+debianSourcePackageName = fst . debianPackageNames++debianBinaryPackageNames :: HasDebianControl a => a -> [BinPkgName]+debianBinaryPackageNames = snd . debianPackageNames++debianBuildDepsIndep :: HasDebianControl a => a -> Maybe Relations+debianBuildDepsIndep ctl = either throw id $ debianRelations "Build-Depends-Indep" (debianControl ctl)++debianBuildDeps :: HasDebianControl a => a -> Maybe Relations+debianBuildDeps ctl = either throw id $ debianRelations "Build-Depends" (debianControl ctl)++-- | Version of fieldValue that may throw a ControlFileError. We only+-- use this internally on fields that we already validated.+fieldValue' :: ControlFunctions text => String -> Paragraph' text -> text+fieldValue' fieldName paragraph = maybe (throw $ MissingField [$__LOC__] fieldName) id $ fieldValue fieldName paragraph++-- | This could access fields we haven't validated, so+-- it can return an error. Additionally, the field might+-- be absent, in which case it returns Nothing.+debianRelations :: HasDebianControl a => String -> a -> Either ControlFileError (Maybe Relations)+debianRelations fieldName ctl = maybe (Right Nothing) (either (Left . ParseRelationsError [$__LOC__]) (Right . Just) . parseRelations) $ fieldValue fieldName (debianSourceParagraph ctl)++-- | Apply a function to the text from a named field in a control file paragraph.+mapFieldValue :: (Text -> a) -> String -> Paragraph' Text -> a+mapFieldValue f fieldName paragraph = f $ fieldValue' fieldName paragraph
+ src/Debian/Control/String.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE FlexibleInstances, ScopedTypeVariables, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}+module Debian.Control.String+ ( -- * Types+ Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , Control+ , Paragraph+ , Field+ , ControlParser+ , ControlFunctions(..)+ -- * Control File Parser+ , pControl+ -- * Helper Functions+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ ) where++import qualified Control.Exception as E+import Data.Char (toLower)+import Data.List (find)+import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, protectFieldText, asString),+ Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),+ mergeControls, fieldValue, removeField, prependFields, appendFields,+ renameField, modifyField, raiseFields, protectFieldText')+import System.IO (hGetContents)+import Text.ParserCombinators.Parsec (CharParser, parse, parseFromFile, sepEndBy, satisfy, oneOf, string, lookAhead, try, many, many1, (<|>), noneOf, char, eof)++type Field = Field' String+type Control = Control' String+type Paragraph = Paragraph' String++-- * ControlFunctions++instance ControlFunctions String where+ parseControlFromFile filepath =+ parseFromFile pControl filepath+ parseControlFromHandle sourceName handle =+ E.try (hGetContents handle) >>=+ either (\ (e :: E.SomeException) -> error ("parseControlFromHandle String: Failure parsing " ++ sourceName ++ ": " ++ show e)) (return . parseControl sourceName)+ parseControl sourceName c =+ parse pControl sourceName c+ lookupP fieldName (Paragraph paragraph) =+ find (hasFieldName (map toLower fieldName)) paragraph+ where hasFieldName name (Field (fieldName',_)) = name == map toLower fieldName'+ hasFieldName _ _ = False+ stripWS = reverse . strip . reverse . strip+ where strip = dropWhile (flip elem (" \t" :: [Char]))+ protectFieldText = protectFieldText'+ asString = id++-- * Control File Parser++type ControlParser a = CharParser () a++-- |A parser for debian control file. This parser handles control files+-- that end without a newline as well as ones that have several blank+-- lines at the end. It is very liberal and does not attempt validate+-- the fields in any way. All trailing, leading, and folded whitespace+-- is preserved in the field values. See 'stripWS'.+pControl :: ControlParser Control+pControl =+ do many $ char '\n'+ sepEndBy pParagraph pBlanks >>= return . Control++pParagraph :: ControlParser Paragraph+pParagraph = many1 (pComment <|> pField) >>= return . Paragraph++-- |We are liberal in that we allow *any* field to have folded white+-- space, even though the specific restricts that to a few fields.+pField :: ControlParser Field+pField =+ do c1 <- noneOf "#\n"+ fieldName <- many1 $ noneOf ":\n"+ char ':'+ fieldValue <- many fcharfws+ (char '\n' >> return ()) <|> eof+ return $ Field (c1 : fieldName, fieldValue)++pComment :: ControlParser Field+pComment =+ do char '#'+ text <- many (satisfy (not . ((==) '\n')))+ char '\n'+ return $ Comment ("#" ++ text ++ "\n")++fcharfws :: ControlParser Char+fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')++fchar :: ControlParser Char+fchar = satisfy (/='\n')++_fws :: ControlParser String+_fws =+ try $ do char '\n'+ ws <- many1 (char ' ')+ c <- many1 (satisfy (not . ((==) '\n')))+ return $ '\n' : (ws ++ c)++-- |We go with the assumption that 'blank lines' mean lines that+-- consist of entirely of zero or more whitespace characters.+pBlanks :: ControlParser String+pBlanks = many1 (oneOf " \n")
+ src/Debian/Control/Text.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}+module Debian.Control.Text+ ( -- * Types+ Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , Control+ , Paragraph+ , Field+ -- , ControlParser+ , ControlFunctions(..)+ -- * Control File Parser+ -- , pControl+ -- * Helper Functions+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ , decodeControl+ , decodeParagraph+ , decodeField+ ) where++import qualified Data.ByteString.Char8 as B+import Data.Char (toLower, chr)+import Data.List (find)+import qualified Data.Text as T (Text, pack, unpack, map, dropAround)+import Data.Text.Encoding (decodeUtf8With, encodeUtf8)+--import Data.Text.IO as T (readFile)+import qualified Debian.Control.ByteString as B+--import Text.Parsec.Error (ParseError)+--import Text.Parsec.Text (Parser)+--import Text.Parsec.Prim (runP)+import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, asString),+ Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),+ mergeControls, fieldValue, removeField, prependFields, appendFields,+ renameField, modifyField, raiseFields, protectFieldText')++-- | @parseFromFile p filePath@ runs a string parser @p@ on the+-- input read from @filePath@ using 'Prelude.readFile'. Returns either a 'ParseError'+-- ('Left') or a value of type @a@ ('Right').+--+-- > main = do{ result <- parseFromFile numbers "digits.txt"+-- > ; case result of+-- > Left err -> print err+-- > Right xs -> print (sum xs)+-- > }+{-+parseFromFile :: Parser a -> String -> IO (Either ParseError a)+parseFromFile p fname+ = do input <- T.readFile fname `E.catch` (\ (_ :: E.SomeException) -> B.readFile fname >>= return . decode)+ return (runP p () fname input)+-}++type Field = Field' T.Text+type Control = Control' T.Text+type Paragraph = Paragraph' T.Text++decodeControl :: B.Control -> Control+decodeControl (B.Control paragraphs) = Control (map decodeParagraph paragraphs)++decodeParagraph :: B.Paragraph -> Paragraph+decodeParagraph (B.Paragraph s) = B.Paragraph (map decodeField s)++decodeField :: Field' B.ByteString -> Field' T.Text+decodeField (B.Field (name, value)) = Field (decode name, decode value)+decodeField (B.Comment s) = Comment (decode s)++decode :: B.ByteString -> T.Text+decode = decodeUtf8With (\ _ w -> fmap (chr . fromIntegral) w)++-- * ControlFunctions++instance ControlFunctions T.Text where+ parseControlFromFile filepath =+ -- The ByteString parser is far more efficient than the Text+ -- parser. By calling decodeControl we tell the compiler to+ -- use it instead.+ parseControlFromFile filepath >>= return . either Left (Right . decodeControl)+ parseControlFromHandle sourceName handle =+ parseControlFromHandle sourceName handle >>= return . either Left (Right . decodeControl)+ parseControl sourceName c =+ -- Warning: This is very slow, it does a utf8 round trip+ either Left (Right . decodeControl) (parseControl sourceName (encodeUtf8 c))+ lookupP fieldName (Paragraph paragraph) =+ find (hasFieldName (map toLower fieldName)) paragraph+ where hasFieldName :: String -> Field' T.Text -> Bool+ hasFieldName name (Field (fieldName',_)) = T.pack name == T.map toLower fieldName'+ hasFieldName _ _ = False+ stripWS = T.dropAround (`elem` (" \t" :: String))+ -- T.strip would also strip newlines+ protectFieldText = protectFieldText'+ asString = T.unpack++-- * Control File Parser+{-+-- type ControlParser = GenParser T.Text+type ControlParser a = Parsec T.Text () a++-- |A parser for debian control file. This parser handles control files+-- that end without a newline as well as ones that have several blank+-- lines at the end. It is very liberal and does not attempt validate+-- the fields in any way. All trailing, leading, and folded whitespace+-- is preserved in the field values. See 'stripWS'.+pControl :: ControlParser Control+pControl =+ do many $ char '\n'+ sepEndBy pParagraph pBlanks >>= return . Control++pParagraph :: ControlParser Paragraph+pParagraph = many1 (pComment <|> pField) >>= return . Paragraph++-- |We are liberal in that we allow *any* field to have folded white+-- space, even though the specific restricts that to a few fields.+pField :: ControlParser Field+pField =+ do c1 <- noneOf "#\n"+ fieldName <- many1 $ noneOf ":\n"+ char ':'+ fieldValue <- many fcharfws+ (char '\n' >> return ()) <|> eof+ return $ Field (T.cons c1 (T.pack fieldName), T.pack fieldValue)++pComment :: ControlParser Field+pComment =+ do char '#'+ text <- many (satisfy (not . ((==) '\n')))+ char '\n'+ return $ Comment (T.pack ("#" <> text <> "\n"))++fcharfws :: ControlParser Char+fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')++fchar :: ControlParser Char+fchar = satisfy (/='\n')++_fws :: ControlParser T.Text+_fws =+ try $ do char '\n'+ ws <- many1 (char ' ')+ c <- many1 (satisfy (not . ((==) '\n')))+ return $ T.cons '\n' (T.pack ws <> T.pack c)++-- |We go with the assumption that 'blank lines' mean lines that+-- consist of entirely of zero or more whitespace characters.+pBlanks :: ControlParser T.Text+pBlanks =+ do s <- many1 (oneOf " \n")+ return . T.pack $ s+-}
+ src/Debian/Control/TextLazy.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE FlexibleInstances, OverloadedStrings, ScopedTypeVariables, TypeSynonymInstances #-}+{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-name-shadowing -fno-warn-unused-do-bind #-}+module Debian.Control.TextLazy+ ( -- * Types+ Control'(..)+ , Paragraph'(..)+ , Field'(..)+ , Control+ , Paragraph+ , Field+ -- , ControlParser+ , ControlFunctions(..)+ -- * Control File Parser+ -- , pControl+ -- * Helper Functions+ , mergeControls+ , fieldValue+ , removeField+ , prependFields+ , appendFields+ , renameField+ , modifyField+ , raiseFields+ , decodeControl+ , decodeParagraph+ , decodeField+ ) where++import qualified Data.ByteString.Char8 as B+import Data.Char (toLower, chr)+import Data.List (find)+import qualified Data.Text.Lazy as T (Text, pack, unpack, map, dropAround, {-reverse,-} fromStrict, toStrict)+import Data.Text.Encoding (decodeUtf8With, encodeUtf8)+--import Data.Text.IO as T (readFile)+import qualified Debian.Control.ByteString as B+--import Text.Parsec.Error (ParseError)+--import Text.Parsec.Text (Parser)+--import Text.Parsec.Prim (runP)+import Debian.Control.Common (ControlFunctions(parseControlFromFile, parseControlFromHandle, parseControl, lookupP, stripWS, asString),+ Control'(Control), Paragraph'(Paragraph), Field'(Field, Comment),+ mergeControls, fieldValue, removeField, prependFields, appendFields,+ renameField, modifyField, raiseFields, protectFieldText')++-- | @parseFromFile p filePath@ runs a string parser @p@ on the+-- input read from @filePath@ using 'Prelude.readFile'. Returns either a 'ParseError'+-- ('Left') or a value of type @a@ ('Right').+--+-- > main = do{ result <- parseFromFile numbers "digits.txt"+-- > ; case result of+-- > Left err -> print err+-- > Right xs -> print (sum xs)+-- > }+{-+parseFromFile :: Parser a -> String -> IO (Either ParseError a)+parseFromFile p fname+ = do input <- T.readFile fname `E.catch` (\ (_ :: E.SomeException) -> B.readFile fname >>= return . decode)+ return (runP p () fname input)+-}++type Field = Field' T.Text+type Control = Control' T.Text+type Paragraph = Paragraph' T.Text++decodeControl :: B.Control -> Control+decodeControl (B.Control paragraphs) = Control (map decodeParagraph paragraphs)++decodeParagraph :: B.Paragraph -> Paragraph+decodeParagraph (B.Paragraph s) = B.Paragraph (map decodeField s)++decodeField :: Field' B.ByteString -> Field' T.Text+decodeField (B.Field (name, value)) = Field (decode name, decode value)+decodeField (B.Comment s) = Comment (decode s)++decode :: B.ByteString -> T.Text+decode = T.fromStrict . decodeUtf8With (\ _ w -> fmap (chr . fromIntegral) w)++-- * ControlFunctions++instance ControlFunctions T.Text where+ parseControlFromFile filepath =+ -- The ByteString parser is far more efficient than the Text+ -- parser. By calling decodeControl we tell the compiler to+ -- use it instead.+ parseControlFromFile filepath >>= return . either Left (Right . decodeControl)+ parseControlFromHandle sourceName handle =+ parseControlFromHandle sourceName handle >>= return . either Left (Right . decodeControl)+ parseControl sourceName c =+ -- Warning: This is very slow, it does a utf8 round trip+ either Left (Right . decodeControl) (parseControl sourceName (encodeUtf8 (T.toStrict c)))+ lookupP fieldName (Paragraph paragraph) =+ find (hasFieldName (map toLower fieldName)) paragraph+ where hasFieldName :: String -> Field' T.Text -> Bool+ hasFieldName name (Field (fieldName',_)) = T.pack name == T.map toLower fieldName'+ hasFieldName _ _ = False+ stripWS = T.dropAround (`elem` (" \t" :: String))+ -- T.strip would also strip newlines+ protectFieldText = protectFieldText'+ asString = T.unpack++-- * Control File Parser+{-+-- type ControlParser = GenParser T.Text+type ControlParser a = Parsec T.Text () a++-- |A parser for debian control file. This parser handles control files+-- that end without a newline as well as ones that have several blank+-- lines at the end. It is very liberal and does not attempt validate+-- the fields in any way. All trailing, leading, and folded whitespace+-- is preserved in the field values. See 'stripWS'.+pControl :: ControlParser Control+pControl =+ do many $ char '\n'+ sepEndBy pParagraph pBlanks >>= return . Control++pParagraph :: ControlParser Paragraph+pParagraph = many1 (pComment <|> pField) >>= return . Paragraph++-- |We are liberal in that we allow *any* field to have folded white+-- space, even though the specific restricts that to a few fields.+pField :: ControlParser Field+pField =+ do c1 <- noneOf "#\n"+ fieldName <- many1 $ noneOf ":\n"+ char ':'+ fieldValue <- many fcharfws+ (char '\n' >> return ()) <|> eof+ return $ Field (T.cons c1 (T.pack fieldName), T.pack fieldValue)++pComment :: ControlParser Field+pComment =+ do char '#'+ text <- many (satisfy (not . ((==) '\n')))+ char '\n'+ return $ Comment (T.pack ("#" <> text <> "\n"))++fcharfws :: ControlParser Char+fcharfws = fchar <|> (try $ lookAhead (string "\n ") >> char '\n') <|> (try $ lookAhead (string "\n\t") >> char '\n') <|> (try $ lookAhead (string "\n#") >> char '\n')++fchar :: ControlParser Char+fchar = satisfy (/='\n')++_fws :: ControlParser T.Text+_fws =+ try $ do char '\n'+ ws <- many1 (char ' ')+ c <- many1 (satisfy (not . ((==) '\n')))+ return $ T.cons '\n' (T.pack ws <> T.pack c)++-- |We go with the assumption that 'blank lines' mean lines that+-- consist of entirely of zero or more whitespace characters.+pBlanks :: ControlParser T.Text+pBlanks =+ do s <- many1 (oneOf " \n")+ return . T.pack $ s+-}
+ src/Debian/Deb.hs view
@@ -0,0 +1,24 @@+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+module Debian.Deb where++import Control.Monad++import Debian.Control.Common+import System.Directory (canonicalizePath)+import System.Exit (ExitCode(..))+import System.Process (readProcessWithExitCode)+import System.Unix.Directory (withTemporaryDirectory, withWorkingDirectory)++fields :: (ControlFunctions a) => FilePath -> IO (Control' a)+fields debFP =+ withTemporaryDirectory ("fields.XXXXXX") $ \tmpdir ->+ do debFP <- canonicalizePath debFP+ withWorkingDirectory tmpdir $+ do (res, out, err) <- readProcessWithExitCode "ar" ["x",debFP,"control.tar.gz"] ""+ when (res /= ExitSuccess) (error $ "Dpkg.fields: " ++ show out ++ "\n" ++ show err ++ "\n" ++ show res)+ (res, out, err) <- readProcessWithExitCode "tar" ["xzf", "control.tar.gz", "./control"] ""+ when (res /= ExitSuccess) (error $ "Dpkg.fields: " ++ show out ++ "\n" ++ show err ++ "\n" ++ show res)+ c <- parseControlFromFile "control"+ case c of+ Left e -> error (show e)+ (Right c) -> return c -- I don't think we need seq because parsec will force everything from the file
+ src/Debian/Extra/Files.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE PackageImports #-}+-- |Domain independent functions used by the haskell-debian package.+module Debian.Extra.Files+ ( withTemporaryFile+ ) where++import "mtl" Control.Monad.Trans (MonadIO, liftIO)+import System.Directory (getTemporaryDirectory, removeFile)+import System.IO (hPutStr, hClose, openBinaryTempFile)++withTemporaryFile :: MonadIO m+ => (FilePath -> m a) -- ^ The function we want to pass a FilePath to+ -> String -- ^ The text that the file should contain+ -> m a -- ^ The function's return value+withTemporaryFile f text =+ do path <- liftIO writeTemporaryFile+ result <- f path+ liftIO $ removeFile path+ return result+ where+ writeTemporaryFile =+ do dir <- getTemporaryDirectory+ (path, h) <- openBinaryTempFile dir "wtf.tmp"+ hPutStr h text+ hClose h+ return path++-- Example: write the path of the temporary file and its contents into /tmp/result:+-- test =+-- withTemporaryFile f "Some text\n"+-- where f path = readFile path >>= return . (("Contents of " ++ path ++ ":\n") ++) >>= writeFile "/tmp/result"
+ src/Debian/GenBuildDeps.hs view
@@ -0,0 +1,330 @@+{-# LANGUAGE CPP, FlexibleContexts, OverloadedStrings, RecordWildCards, ScopedTypeVariables, TemplateHaskell #-}+{-# LANGUAGE NoDisambiguateRecordFields, NoRecordWildCards #-}+-- |Figure out the dependency relation between debianized source+-- directories. The code to actually solve these dependency relations+-- for a particular set of binary packages is in Debian.Repo.Dependency.+module Debian.GenBuildDeps+ ( DepInfo(..)+ , sourceName'+ , relations'+ , binaryNames'+ -- * Preparing dependency info+ , buildDependencies+ , RelaxInfo+ , relaxDeps+ -- * Using dependency info+ , BuildableInfo(..)+ , ReadyTarget(..)+ , buildable+ , compareSource+ -- * Obsolete?+ , orderSource+ , genDeps+ , failPackage+ , getSourceOrder+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif+import Control.Exception (throw)+import Control.Monad (filterM, foldM)+import Control.Monad.State (evalState, get, modify, State)+import Data.Graph (Graph, Edge, Vertex, buildG, topSort, reachable, transposeG, edges, scc)+import Data.List as List (elemIndex, find, map, nub, partition, tails)+import Data.Map as Map (empty, findWithDefault, fromList, insert, Map, lookup)+import Data.Maybe+import Data.Set as Set (fromList, intersection, null, Set)+import Data.Tree as Tree (Tree(Node, rootLabel, subForest))+import Debian.Control (parseControlFromFile)+import Debian.Control.Policy (HasDebianControl, DebianControl, ControlFileError(..), validateDebianControl, debianSourcePackageName, debianBinaryPackageNames, debianBuildDeps, debianBuildDepsIndep)+import Debian.Loc (__LOC__)+import Debian.Relation+import Debian.Relation.Text ()+-- import Debug.Trace (trace)+import System.Directory (getDirectoryContents, doesFileExist)++-- | This type describes the build dependencies of a source package.+data DepInfo = DepInfo {+ sourceName :: SrcPkgName -- ^ source package name+ , relations :: Relations -- ^ dependency relations+ , binaryNames :: [BinPkgName] -- ^ binary dependency names (is this a function of relations?)+ , depSet :: Set.Set BinPkgName -- ^ Set containing all binary package names mentioned in relations+ , binSet :: Set.Set BinPkgName -- ^ Set containing binaryNames+ } deriving Show++instance Eq DepInfo where+ a == b = (sourceName a == sourceName b) &&+ Set.fromList (map Set.fromList (relations a)) == Set.fromList (map Set.fromList (relations b)) &&+ Set.fromList (binaryNames a) == Set.fromList (binaryNames b)++-- |Return the dependency info for a source package with the given dependency relaxation.+-- |According to debian policy, only the first paragraph in debian\/control can be a source package+-- <http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-sourcecontrolfiles>+buildDependencies :: HasDebianControl control => control -> DepInfo+buildDependencies control = do+ let rels = concat [fromMaybe [] (debianBuildDeps control),+ fromMaybe [] (debianBuildDepsIndep control)]+ bins = debianBinaryPackageNames control+ DepInfo { sourceName = debianSourcePackageName control+ , relations = rels+ , binaryNames = bins+ , depSet = Set.fromList (List.map (\(Rel x _ _) -> x) (concat rels))+ , binSet = Set.fromList bins }++-- | source package name+sourceName' :: HasDebianControl control => control -> SrcPkgName+sourceName' control = debianSourcePackageName control++-- | dependency relations+relations' :: HasDebianControl control => control -> Relations+relations' control = concat [fromMaybe [] (debianBuildDeps control),+ fromMaybe [] (debianBuildDepsIndep control)]++-- | binary dependency names (is this a function of relations?)+binaryNames' :: HasDebianControl control => control -> [BinPkgName]+binaryNames' control = debianBinaryPackageNames control++-- |Specifies build dependencies that should be ignored during the build+-- decision. If the pair is (BINARY, Nothing) it means the binary package+-- BINARY should always be ignored when deciding whether to build. If the+-- pair is (BINARY, Just SOURCE) it means that binary package BINARY should+-- be ignored when deiciding whether to build package SOURCE.+newtype OldRelaxInfo = RelaxInfo [(BinPkgName, Maybe SrcPkgName)] deriving Show++-- | Given a source package name and a binary package name, return+-- False if the binary package should be ignored hwen deciding whether+-- to build the source package. This is used to prevent build+-- dependency cycles from triggering unnecessary rebuilds. (This is a+-- replacement for the RelaxInfo type, which we temporarily rename+-- OldRelaxInfo.)+type RelaxInfo = SrcPkgName -> BinPkgName -> Bool++-- |Remove any dependencies that are designated \"relaxed\" by relaxInfo.+relaxDeps :: RelaxInfo -> [DepInfo] -> [DepInfo]+relaxDeps relaxInfo deps =+ List.map relaxDep deps+ where+ relaxDep :: DepInfo -> DepInfo+ relaxDep info = info {relations = filteredDependencies}+ where+ -- Discard any dependencies not on the filtered package name list. If+ -- this results in an empty list in an or-dep the entire dependency can+ -- be discarded.+ filteredDependencies :: Relations+ filteredDependencies = filter (/= []) (List.map (filter keepDep) (relations info))+ keepDep :: Relation -> Bool+ keepDep (Rel name _ _) = not (relaxInfo (sourceName info) name)++data ReadyTarget a+ = ReadyTarget { ready :: a+ -- ^ Some target whose build dependencies are all satisfied+ , waiting :: [a]+ -- ^ The targets that are waiting for the ready target+ , other :: [a]+ -- ^ The rest of the targets that need to be built+ }++data BuildableInfo a+ = BuildableInfo+ { readyTargets :: [ReadyTarget a]+ , allBlocked :: [a] }+ | CycleInfo+ { depPairs :: [(a, a)] }++-- | Given an ordering function representing the dependencies on a+-- list of packages, return a ReadyTarget triple: One ready package,+-- the packages that depend on the ready package directly or+-- indirectly, and all the other packages.+buildable :: forall a. (a -> DepInfo) -> [a] -> BuildableInfo a+buildable relax packages =+ -- Find all packages which can't reach any other packages in the+ -- graph of the "has build dependency" relation on the+ -- yet-to-be-built packages+ case partition (\ x -> reachable hasDep x == [x]) verts of+ -- None of the packages are buildable, return information+ -- about how to break this build dependency cycle.+ ([], _) -> CycleInfo {depPairs = List.map ofEdge $ head $ (allCycles hasDep)}+ -- We have some buildable packages, return them along with+ -- the list of packages each one directly blocks+ (allReady, blocked) ->+ BuildableInfo { readyTargets = List.map (makeReady blocked allReady) allReady+ , allBlocked = List.map ofVertex blocked }+ where+ makeReady :: [Vertex] -> [Vertex] -> Vertex -> ReadyTarget a+ makeReady blocked ready thisReady =+ let otherReady = filter (/= thisReady) ready+ (directlyBlocked, otherBlocked) = partition (\ x -> elem x (reachable isDep thisReady)) blocked in+ ReadyTarget { ready = ofVertex thisReady+ , waiting = List.map ofVertex directlyBlocked+ , other = List.map ofVertex (otherReady ++ otherBlocked) }+ --allDeps x = (ofVertex x, List.map ofVertex (filter (/= x) (reachable hasDep x)))+ isDep :: Graph+ isDep = transposeG hasDep+ hasDep :: Graph+ hasDep = buildG (0, length packages - 1) hasDepEdges++ hasDepEdges :: [(Int, Int)]+ hasDepEdges =+#if 0+ nub (foldr f [] (tails vertPairs))+ where f :: [(Int, DepInfo)] -> [(Int, Int)] -> [(Int, Int)]+ f [] es = es+ f (x : xs) es = catMaybes (List.map (toEdge x) xs) ++ es+ toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> Maybe Edge+ toEdge (xv, xa) (yv, ya) =+ case compareSource xa ya of+ EQ -> Nothing+ LT -> Just (yv, xv)+ GT -> Just (xv, yv)+#else+ nub (evalState (foldM f [] (tails vertPairs)) Map.empty)+ where f :: [(Int, Int)] -> [(Int, DepInfo)] -> State (Map.Map (Int, Int) Ordering) [(Int, Int)]+ f es [] = return es+ f es (x : xs) = mapM (toEdge x) xs >>= \es' -> return (catMaybes es' ++ es)+ toEdge :: (Int, DepInfo) -> (Int, DepInfo) -> State (Map.Map (Int, Int) Ordering) (Maybe Edge)+ toEdge (xv, xa) (yv, ya) = do+ mp <- get+ r <- case Map.lookup (xv, yv) mp of+ Just r' -> return r'+ Nothing -> do+ let r' = compareSource xa ya+ -- trace ("compareSource " ++ show (unSrcPkgName $ sourceName xa) ++ " " ++ show (unSrcPkgName $ sourceName ya) ++ " -> " ++ show r') (return ())+ modify (Map.insert (xv, yv) r')+ return r'+ case r of+ EQ -> return Nothing+ LT -> return $ Just (yv, xv)+ GT -> return $ Just (xv, yv)+#endif+ ofEdge :: Edge -> (a, a)+ ofEdge (a, b) = (ofVertex a, ofVertex b)+ ofVertex :: Int -> a+ ofVertex n = fromJust (Map.findWithDefault Nothing n (Map.fromList (zip [0..] (map Just packages))))+ verts :: [Int]+ verts = map fst vertPairs+ vertPairs :: [(Int, DepInfo)]+ vertPairs = zip [0..] $ map relax packages++-- | Find a cycle in a graph that involves+allCycles :: Graph -> [[Edge]]+allCycles g =+ -- Every cycle is confined to an SCC (strongly connected component).+ -- Every node in an SCC is part of some cycle.+ concatMap sccCycles (scc g)+ where+ -- Find all the cycles in an SCC+ sccCycles :: Tree Vertex -> [[Edge]]+ sccCycles t = mapMaybe addBackEdge (treePaths t)++ addBackEdge :: [Vertex] -> Maybe [Edge]+ addBackEdge path@(root : _) =+ let back = (last path, root) in+ if elem back (edges g) then Just (pathEdges (path ++ [root])) else Nothing++-- | All the paths from root to a leaf+treePaths :: Tree a -> [[a]]+treePaths (Node {rootLabel = r, subForest = []}) = [[r]]+treePaths (Node {rootLabel = r, subForest = ts}) = map (r :) (concatMap treePaths ts)++pathEdges :: [a] -> [(a, a)]+pathEdges (v1 : v2 : vs) = (v1, v2) : pathEdges (v2 : vs)+pathEdges _ = []++-- | Remove any packages which can't be built given that a package has failed.+failPackage :: Eq a => (a -> a -> Ordering) -> a -> [a] -> ([a], [a])+failPackage cmp failed packages =+ let graph = buildGraph cmp packages in+ let root = elemIndex failed packages in+ let victims = maybe [] (map (fromJust . vertex) . reachable graph) root in+ partition (\ x -> not . elem x $ victims) packages+ where+ vertex n = Map.findWithDefault Nothing n vertexMap+ vertexMap = Map.fromList (zip [0..] (map Just packages))++-- | Given a list of packages, sort them according to their apparant+-- build dependencies so that the first element doesn't depend on any+-- of the other packages.+orderSource :: (a -> a -> Ordering) -> [a] -> [a]+orderSource cmp packages =+ map (fromJust . vertex) (topSort graph)+ where+ graph = buildGraph cmp packages+ vertex n = Map.findWithDefault Nothing n vertexMap+ vertexMap = Map.fromList (zip [0..] (map Just packages))++-- | Build a graph with the list of packages as its nodes and the+-- build dependencies as its edges.+buildGraph :: (a -> a -> Ordering) -> [a] -> Graph+buildGraph cmp packages =+ let es = someEdges (zip packages [0..]) in+ buildG (0, length packages - 1) es+ where+ someEdges [] = []+ someEdges (a : etc) = aEdges a etc ++ someEdges etc+ aEdges (ap, an) etc =+ concat (map (\ (bp, bn) ->+ case cmp ap bp of+ LT -> [(an, bn)]+ GT -> [(bn, an)]+ EQ -> []) etc)++-- |This is a nice start. It ignores circular build depends and takes+-- a pretty simplistic approach to 'or' build depends. However, I+-- think this should work pretty nicely in practice.+compareSource :: DepInfo -> DepInfo -> Ordering+compareSource p1 p2+#if 0+ | any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p2))) (concat (relations p1)) = GT+ | any (\rel -> isJust (find (checkPackageNameReq rel) (binaryNames p1))) (concat (relations p2)) = LT+ | otherwise = EQ+ where+ checkPackageNameReq :: Relation -> BinPkgName -> Bool+ checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName+#else+ | not (Set.null (Set.intersection (depSet p1) (binSet p2))) = GT+ | not (Set.null (Set.intersection (depSet p2) (binSet p1))) = LT+ | otherwise = EQ+#endif++compareSource' :: HasDebianControl control => control -> control -> Ordering+compareSource' control1 control2+ | any (\rel -> isJust (find (checkPackageNameReq rel) bins2)) (concat depends1) = GT+ | any (\rel -> isJust (find (checkPackageNameReq rel) bins1)) (concat depends2) = LT+ | otherwise = EQ+ where+ bins1 = binaryNames' control1+ bins2 = binaryNames' control2+ depends1 = relations' control1+ depends2 = relations' control2+ checkPackageNameReq :: Relation -> BinPkgName -> Bool+ checkPackageNameReq (Rel rPkgName _ _) bPkgName = rPkgName == bPkgName++-- |Return the dependency info for a list of control files.+genDeps :: [FilePath] -> IO [DebianControl]+genDeps controlFiles = do+ orderSource compareSource' <$> mapM genDep' controlFiles+ where+ -- Parse the control file and extract the build dependencies+ genDep' controlPath = parseControlFromFile controlPath >>=+ either (\ x -> throw (ParseRelationsError [$__LOC__] x))+ (\ x -> validateDebianControl x {- `mapExn` (pushLoc $__LOC__) -} >>= either throw return)++-- pushLoc :: Loc -> ControlFileError -> ControlFileError+-- pushLoc loc e = e {locs = loc : locs e}++-- |One example of how to tie the below functions together. In this+-- case 'fp' is the path to a directory that contains a bunch of+-- checked out source packages. The code will automatically look for+-- debian\/control. It returns a list with the packages in the+-- order they should be built.+getSourceOrder :: FilePath -> IO [SrcPkgName]+getSourceOrder fp =+ findControlFiles fp >>= genDeps >>= return . map sourceName'+ where+ -- Return a list of the files that look like debian\/control.+ findControlFiles :: FilePath -> IO [FilePath]+ findControlFiles root =+ getDirectoryContents root >>=+ mapM (\ x -> return $ root ++ "/" ++ x ++ "/debian/control") >>=+ filterM doesFileExist
+ src/Debian/Loc.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE ScopedTypeVariables, StandaloneDeriving, TemplateHaskell #-}+module Debian.Loc+ ( __LOC__+ , mapExn+ ) where++import Control.Applicative ((<$>), (<*>), pure)+import Control.Exception (Exception, throw)+import Control.Monad.Catch (MonadCatch, catch)+import Language.Haskell.TH++__LOC__ :: Q Exp+__LOC__ = location >>= \ x ->+ recConE 'Loc [ (,) <$> (pure 'loc_filename) <*> litE (stringL (loc_filename x))+ , (,) <$> (pure 'loc_package) <*> litE (stringL (loc_package x))+ , (,) <$> (pure 'loc_module) <*> litE (stringL (loc_module x))+ , (,) <$> (pure 'loc_start) <*> [|($(litE (integerL (fromIntegral (fst (loc_start x))))),+ $(litE (integerL (fromIntegral (snd (loc_start x)))))) :: (Int, Int)|]+ , (,) <$> (pure 'loc_end) <*> [|($(litE (integerL (fromIntegral (fst (loc_end x))))),+ $(litE (integerL (fromIntegral (snd (loc_end x)))))) :: (Int, Int)|] ]++mapExn :: forall e m a. (MonadCatch m, Exception e) => m a -> (e -> e) -> m a+mapExn task f = task `catch` (\ (e :: e) -> throw (f e))
+ src/Debian/Pretty.hs view
@@ -0,0 +1,48 @@+-- | A constructor we can wrap around values to avoid any built in+-- Pretty instance - for example, instance Pretty [a].+--+-- * display is now prettyShow+-- * display' is now prettyText+-- * ppDisplay is now ppShow+-- * ppDisplay' is now ppText+{-# LANGUAGE DeriveFunctor, FlexibleContexts, FlexibleInstances, OverloadedStrings, TypeSynonymInstances #-}+module Debian.Pretty+ ( PP(PP, unPP)+ , prettyText+ , ppPrint+ , ppShow+ , ppText+ -- * Re-export+ , prettyShow+ ) where++import Data.Text (Text, unpack, pack)+import Text.PrettyPrint.HughesPJClass (Doc, text, empty)+import Distribution.Pretty (Pretty(pretty), prettyShow)++-- | This type is wrapped around values before we pretty print them so+-- we can write our own Pretty instances for common types without+-- polluting the name space of clients of this package with instances+-- they don't want.+newtype PP a = PP {unPP :: a} deriving (Functor)++instance Pretty (PP Text) where+ pretty = text . unpack . unPP++instance Pretty (PP String) where+ pretty = text . unPP++instance Pretty (PP a) => Pretty (PP (Maybe a)) where+ pretty = maybe empty ppPrint . unPP++prettyText :: Pretty a => a -> Text+prettyText = pack . prettyShow++ppPrint :: Pretty (PP a) => a -> Doc+ppPrint = pretty . PP++ppShow :: Pretty (PP a) => a -> String+ppShow = prettyShow . PP++ppText :: Pretty (PP a) => a -> Text+ppText = pack . prettyShow . PP
+ src/Debian/Relation.hs view
@@ -0,0 +1,25 @@+-- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>+module Debian.Relation+ ( -- * Types+ PkgName(..)+ , SrcPkgName(..)+ , BinPkgName(..)+ , Relations+ , AndRelation+ , OrRelation+ , Relation(..)+ , ArchitectureReq(..)+ , Arch(..)+ , ArchOS(..)+ , ArchCPU(..)+ , VersionReq(..)+ -- * Helper Functions+ , checkVersionReq+ -- * Relation Parser+ , RelParser+ , ParseRelations(..)+ ) where++import Debian.Arch (Arch(..), ArchOS(..), ArchCPU(..))+import Debian.Relation.Common (SrcPkgName(..), BinPkgName(..), PkgName(pkgNameFromString))+import Debian.Relation.String
+ src/Debian/Relation/ByteString.hs view
@@ -0,0 +1,30 @@+{-# OPTIONS -fno-warn-orphans #-}+-- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>+module Debian.Relation.ByteString+ ( -- * Types+ AndRelation+ , OrRelation+ , Relations+ , Relation(..)+ , ArchitectureReq(..)+ , VersionReq(..)+ -- * Helper Functions+ , checkVersionReq+ -- * Relation Parser+ , RelParser+ , ParseRelations(..)+ ) where++import qualified Data.ByteString.Char8 as C++-- Local Modules++--import Debian.Relation.Common+import Debian.Relation.String+--import Debian.Version++-- * ParseRelations++-- For now we just wrap the string version+instance ParseRelations C.ByteString where+ parseRelations byteStr = parseRelations (C.unpack byteStr)
+ src/Debian/Relation/Common.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE DeriveDataTypeable, FlexibleContexts, FlexibleInstances, OverloadedStrings, TypeSynonymInstances #-}+module Debian.Relation.Common where++-- Standard GHC Modules++import Data.Data (Data)+import Data.List as List (map, intersperse)+import Data.Monoid (mconcat, (<>))+import Data.Function+import Data.Set as Set (Set, toList)+import Data.Typeable (Typeable)+import Debian.Arch (Arch, prettyArch)+import Debian.Pretty (PP(..))+import Prelude hiding (map)+import Text.ParserCombinators.Parsec+import Text.PrettyPrint (Doc, text, empty)+import Distribution.Pretty (Pretty(pretty))++-- Local Modules++import Debian.Version++-- Datatype for relations++type Relations = AndRelation+type AndRelation = [OrRelation]+type OrRelation = [Relation]++data Relation = Rel BinPkgName (Maybe VersionReq) (Maybe ArchitectureReq) deriving (Eq, Read, Show)++newtype SrcPkgName = SrcPkgName {unSrcPkgName :: String} deriving (Read, Show, Eq, Ord, Data, Typeable)+newtype BinPkgName = BinPkgName {unBinPkgName :: String} deriving (Read, Show, Eq, Ord, Data, Typeable)++class Pretty (PP a) => PkgName a where+ pkgNameFromString :: String -> a++instance PkgName BinPkgName where+ pkgNameFromString = BinPkgName++instance PkgName SrcPkgName where+ pkgNameFromString = SrcPkgName++class ParseRelations a where+ -- |'parseRelations' parse a debian relation (i.e. the value of a+ -- Depends field). Return a parsec error or a value of type+ -- 'Relations'+ parseRelations :: a -> Either ParseError Relations++-- | This needs to be indented for use in a control file: intercalate "\n " . lines . show+prettyRelations :: [[Relation]] -> Doc+prettyRelations xss = mconcat . intersperse (text "\n, ") . List.map prettyOrRelation $ xss++prettyOrRelation :: [Relation] -> Doc+prettyOrRelation xs = mconcat . intersperse (text " | ") . List.map prettyRelation $ xs++prettyRelation :: Relation -> Doc+prettyRelation (Rel name ver arch) =+ pretty (PP name) <> maybe empty prettyVersionReq ver <> maybe empty prettyArchitectureReq arch++instance Ord Relation where+ compare (Rel pkgName1 mVerReq1 _mArch1) (Rel pkgName2 mVerReq2 _mArch2) =+ case compare pkgName1 pkgName2 of+ LT -> LT+ GT -> GT+ EQ -> compare mVerReq1 mVerReq2++data ArchitectureReq+ = ArchOnly (Set Arch)+ | ArchExcept (Set Arch)+ deriving (Eq, Ord, Read, Show)++prettyArchitectureReq :: ArchitectureReq -> Doc+prettyArchitectureReq (ArchOnly arch) = text " [" <> mconcat (List.map prettyArch (toList arch)) <> text "]"+prettyArchitectureReq (ArchExcept arch) = text " [" <> mconcat (List.map ((text "!") <>) (List.map prettyArch (toList arch))) <> text "]"++data VersionReq+ = SLT DebianVersion+ | LTE DebianVersion+ | EEQ DebianVersion+ | GRE DebianVersion+ | SGR DebianVersion+ deriving (Eq, Read, Show)++prettyVersionReq :: VersionReq -> Doc+prettyVersionReq (SLT v) = text " (<< " <> prettyDebianVersion v <> text ")"+prettyVersionReq (LTE v) = text " (<= " <> prettyDebianVersion v <> text ")"+prettyVersionReq (EEQ v) = text " (= " <> prettyDebianVersion v <> text ")"+prettyVersionReq (GRE v) = text " (>= " <> prettyDebianVersion v <> text ")"+prettyVersionReq (SGR v) = text " (>> " <> prettyDebianVersion v <> text ")"++-- |The sort order is based on version number first, then on the kind of+-- relation, sorting in the order <<, <= , ==, >= , >>+instance Ord VersionReq where+ compare = compare `on` extr+ where extr (SLT v) = (v,0 :: Int)+ extr (LTE v) = (v,1 :: Int)+ extr (EEQ v) = (v,2 :: Int)+ extr (GRE v) = (v,3 :: Int)+ extr (SGR v) = (v,4 :: Int)++-- |Check if a version number satisfies a version requirement.+checkVersionReq :: Maybe VersionReq -> Maybe DebianVersion -> Bool+checkVersionReq Nothing _ = True+checkVersionReq _ Nothing = False+checkVersionReq (Just (SLT v1)) (Just v2) = v2 < v1+checkVersionReq (Just (LTE v1)) (Just v2) = v2 <= v1+checkVersionReq (Just (EEQ v1)) (Just v2) = v2 == v1+checkVersionReq (Just (GRE v1)) (Just v2) = v2 >= v1+checkVersionReq (Just (SGR v1)) (Just v2) = v2 > v1++instance Pretty (PP BinPkgName) where+ pretty = text . unBinPkgName . unPP++instance Pretty (PP SrcPkgName) where+ pretty = text . unSrcPkgName . unPP++-- | Wrap `PP` around type synonyms that might overlap with the+-- `Pretty [a]` instance.+instance Pretty (PP Relations) where+ pretty = prettyRelations . unPP++instance Pretty (PP OrRelation) where+ pretty = prettyOrRelation . unPP++instance Pretty (PP Relation) where+ pretty = prettyRelation . unPP++instance Pretty (PP VersionReq) where+ pretty = prettyVersionReq . unPP++instance Pretty (PP ArchitectureReq) where+ pretty = prettyArchitectureReq . unPP
+ src/Debian/Relation/String.hs view
@@ -0,0 +1,139 @@+{-# LANGUAGE FlexibleInstances, PackageImports, TypeSynonymInstances #-}+{-# OPTIONS -fno-warn-unused-do-bind -fno-warn-orphans #-}+-- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>+module Debian.Relation.String+ ( -- * Types+ AndRelation+ , OrRelation+ , Relations+ , Relation(..)+ , ArchitectureReq(..)+ , VersionReq(..)+ -- * Helper Functions+ , checkVersionReq+ -- * Relation Parser+ , RelParser+ , ParseRelations(..)+ , pRelations+ ) where++-- Standard GHC Modules++import "mtl" Control.Monad.Identity (Identity)+import Data.Set (fromList)+import Text.ParserCombinators.Parsec+import Text.Parsec.Prim (ParsecT)++-- Local Modules++import Debian.Arch (Arch, parseArch)+import Debian.Relation.Common+import Debian.Version++-- * ParseRelations++instance ParseRelations String where+ parseRelations str =+ let str' = scrub str in+ case parse pRelations str' str' of+ Right relations -> Right (filter (/= []) relations)+ x -> x+ where+ scrub = unlines . filter (not . comment) . lines+ comment s = case dropWhile (`elem` [' ', '\t']) s of+ ('#' : _) -> True+ _ -> False++-- * Relation Parser++type RelParser a = CharParser () a++-- "Correct" dependency lists are separated by commas, but sometimes they+-- are omitted and it is possible to parse relations without them.+pRelations :: RelParser Relations+pRelations = do -- rel <- sepBy pOrRelation (char ',')+ rel <- many pOrRelation+ eof+ return rel++pOrRelation :: RelParser OrRelation+pOrRelation = do skipMany (char ',' <|> whiteChar)+ rel <- sepBy1 pRelation (char '|')+ skipMany (char ',' <|> whiteChar)+ return rel++whiteChar :: ParsecT String u Identity Char+whiteChar = oneOf [' ','\t','\n']++pRelation :: RelParser Relation+pRelation =+ do skipMany whiteChar+ pkgName <- many1 (noneOf [' ',',','|','\t','\n','('])+ skipMany whiteChar+ mVerReq <- pMaybeVerReq+ skipMany whiteChar+ mArch <- pMaybeArch+ return $ Rel (BinPkgName pkgName) mVerReq mArch++pMaybeVerReq :: RelParser (Maybe VersionReq)+pMaybeVerReq =+ do char '('+ skipMany whiteChar+ op <- pVerReq+ skipMany whiteChar+ ver <- many1 (noneOf [' ',')','\t','\n'])+ skipMany whiteChar+ char ')'+ return $ Just (op (parseDebianVersion' ver))+ <|>+ do return $ Nothing++pVerReq :: ParsecT [Char] u Identity (DebianVersion -> VersionReq)+pVerReq =+ do char '<'+ (do char '<' <|> char ' ' <|> char '\t'+ return $ SLT+ <|>+ do char '='+ return $ LTE)+ <|>+ do string "="+ return $ EEQ+ <|>+ do char '>'+ (do char '='+ return $ GRE+ <|>+ do char '>' <|> char ' ' <|> char '\t'+ return $ SGR)++pMaybeArch :: RelParser (Maybe ArchitectureReq)+pMaybeArch =+ do char '['+ (do archs <- pArchExcept+ char ']'+ skipMany whiteChar+ return (Just (ArchExcept (fromList . map parseArchExcept $ archs)))+ <|>+ do archs <- pArchOnly+ char ']'+ skipMany whiteChar+ return (Just (ArchOnly (fromList . map parseArch $ archs)))+ )+ <|>+ return Nothing++-- Some packages (e.g. coreutils) have architecture specs like [!i386+-- !hppa], even though this doesn't really make sense: once you have+-- one !, anything else you include must also be (implicitly) a !.+pArchExcept :: RelParser [String]+pArchExcept = sepBy (char '!' >> many1 (noneOf [']',' '])) (skipMany1 whiteChar)++pArchOnly :: RelParser [String]+pArchOnly = sepBy (many1 (noneOf [']',' '])) (skipMany1 whiteChar)++-- | Ignore the ! if it is present, we already know this list has at+-- least one, and the rest are implicit.+parseArchExcept :: String -> Arch+parseArchExcept ('!' : s) = parseArch s+parseArchExcept s = parseArch s
+ src/Debian/Relation/Text.hs view
@@ -0,0 +1,30 @@+{-# OPTIONS -fno-warn-orphans #-}+-- |A module for working with debian relationships <http://www.debian.org/doc/debian-policy/ch-relationships.html>+module Debian.Relation.Text+ ( -- * Types+ AndRelation+ , OrRelation+ , Relations+ , Relation(..)+ , ArchitectureReq(..)+ , VersionReq(..)+ -- * Helper Functions+ , checkVersionReq+ -- * Relation Parser+ , RelParser+ , ParseRelations(..)+ ) where++import qualified Data.Text as T++-- Local Modules++--import Debian.Relation.Common+import Debian.Relation.String+--import Debian.Version++-- * ParseRelations++-- For now we just wrap the string version+instance ParseRelations T.Text where+ parseRelations text = parseRelations (T.unpack text)
+ src/Debian/Release.hs view
@@ -0,0 +1,48 @@+-- | This module name is spurious - "Release" is not an official term+-- in the debian documentation.++{-# LANGUAGE DeriveDataTypeable #-}++module Debian.Release+ ( Section(..)+ , SubSection(..)+ , sectionName+ , sectionName'+ , sectionNameOfSubSection+ , parseSection+ , parseSection'+ ) where++import Network.URI (unEscapeString, escapeURIString, isAllowedInURI)++-- |A section of a repository such as main, contrib, non-free,+-- restricted. The indexes for a section are located below the+-- distribution directory.+newtype Section = Section String deriving (Read, Show, Eq, Ord)++-- |A package's subsection is only evident in its control information,+-- packages from different subsections all reside in the same index.+data SubSection = SubSection { section :: Section, subSectionName :: String } deriving (Read, Show, Eq, Ord)++sectionName :: SubSection -> String+sectionName (SubSection (Section "main") y) = y+sectionName (SubSection x y) = sectionName' x ++ "/" ++ y++sectionName' :: Section -> String+sectionName' (Section s) = escapeURIString isAllowedInURI s++sectionNameOfSubSection :: SubSection -> String+sectionNameOfSubSection = sectionName' . section++-- |Parse the value that appears in the @Section@ field of a .changes file.+-- (Does this need to be unesacped?)+parseSection :: String -> SubSection+parseSection s =+ case span (/= '/') s of+ (x, "") -> SubSection (Section "main") x+ ("main", y) -> SubSection (Section "main") y+ (x, y) -> SubSection (Section x) (tail y)++parseSection' :: String -> Section+parseSection' name =+ Section (unEscapeString name)
+ src/Debian/Report.hs view
@@ -0,0 +1,88 @@+module Debian.Report where++import Data.Maybe+import qualified Data.Map as M+import Data.Text as Text (Text, unpack)+import Debian.Apt.Index (Fetcher, Compression(..), update, controlFromIndex')+import Debian.Control.Text+import Debian.Sources+import Debian.Version+import Text.XML.HaXml (CFilter, mkElem, cdata)+import Text.XML.HaXml.Posn+import Text.PrettyPrint (render)++-- * General Package Map Builders++-- |create a map of (package name, extracted field) from a list of index files+--+-- NOTE: we could merge all the files into a single control and then+-- run packageMap over that. We currently do it one control file at a+-- time to avoid having all the control files loaded in memory at+-- once. However, I am not sure that property is actually occuring+-- anyway. So, this should be revisited.+makePackageMap :: (Paragraph -> a) -> (a -> a -> a) -> [(FilePath, Compression)] -> IO (M.Map Text a)+makePackageMap _ _ [] = return M.empty+makePackageMap extractValue resolveConflict ((path, compression):is) =+ do r <- controlFromIndex' compression path+ case r of+ (Left e) -> error (show e)+ (Right c) ->+ do let pm = packageMap extractValue resolveConflict c+ pms <- makePackageMap extractValue resolveConflict is+ return $ M.unionWith resolveConflict pm pms++-- |create a map of (package name, max version) from a single control file+packageMap :: (Paragraph -> a) -> (a -> a -> a) -> Control' Text -> M.Map Text a+packageMap extractValue resolveConflict control =+ M.fromListWith resolveConflict (map packageTuple (unControl control))+ where+ packageTuple paragraph = (fromJust $ fieldValue "Package" paragraph, extractValue paragraph)++-- |extract the version number from a control paragraph+extractVersion :: Paragraph -> Maybe DebianVersion+extractVersion paragraph = fmap (parseDebianVersion' . unpack) $ fieldValue "Version" paragraph++-- * Trump Report++-- |compare two sources.list and find all the packages in the second that trump packages in the first+-- see also: |trumpedMap|+trumped :: Fetcher -- ^ function for downloading package indexes+ -> FilePath -- ^ cache directory to store index files in (must already exist)+ -> String -- ^ binary architecture+ -> [DebSource] -- ^ sources.list a+ -> [DebSource] -- ^ sources.list b+ -> IO (M.Map Text (DebianVersion, DebianVersion)) -- ^ a map of trumped package names to (version a, version b)+trumped fetcher cacheDir arch sourcesA sourcesB =+ do indexesA <- update fetcher cacheDir arch (filter isDebSrc sourcesA)+ pmA <- makePackageMap (fromJust . extractVersion) max (map fromJust indexesA)+ indexesB <- update fetcher cacheDir arch (filter isDebSrc sourcesB)+ pmB <- makePackageMap (fromJust . extractVersion) max (map fromJust indexesB)+ return (trumpedMap pmA pmB)+ where+ isDebSrc :: DebSource -> Bool+ isDebSrc ds = _sourceType ds == DebSrc++-- |calculate all the trumped packages+trumpedMap :: M.Map Text DebianVersion -- ^ package map a+ -> M.Map Text DebianVersion -- ^ package map b+ -> M.Map Text (DebianVersion, DebianVersion) -- ^ trumped packages (version a, version b)+trumpedMap pmA pmB =+ M.foldrWithKey (checkTrumped pmB) M.empty pmA+ where+ checkTrumped pm package aVersion trumpedPM =+ case M.lookup package pm of+ (Just bVersion)+ | bVersion > aVersion -> M.insert package (aVersion, bVersion) trumpedPM+ _ -> trumpedPM++-- |create <trumped /> XML element and children from a trumped Map+trumpedXML :: M.Map Text (DebianVersion, DebianVersion) -> CFilter Posn+trumpedXML trumpedMap' =+ mkElem "trumped" (map mkTrumpedPackage (M.toAscList trumpedMap' ))+ where+ mkTrumpedPackage (package, (oldVersion, newVersion)) =+ mkElem "trumpedPackage"+ [ mkElem "package" [ cdata (unpack package) ]+ , mkElem "oldVersion" [ cdata (render . prettyDebianVersion $ oldVersion) ]+ , mkElem "newVersion" [ cdata (render . prettyDebianVersion $ newVersion) ]+ ]
+ src/Debian/Sources.hs view
@@ -0,0 +1,325 @@+{-# LANGUAGE FlexibleInstances, OverloadedStrings, TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-name-shadowing #-}+module Debian.Sources+ {- ( SourceType(..)+ , SourceOption(..)+ , SourceOp(..)+ , DebSource(..)+ , parseSourceLine+ , parseSourceLine'+ , parseSourcesList+ ) -} where++import Control.Lens (makeLenses, review, view)+import Data.Maybe (fromJust)+import Data.Monoid ((<>))+import Data.Text (Text)+import Debian.Codename (Codename, codename, parseCodename)+import Debian.Pretty (PP(..))+import Debian.Release+import Debian.TH (here, Loc)+import Debian.VendorURI (parseVendorURI, VendorURI, vendorURI)+import Network.URI (parseURI, unEscapeString, escapeURIString, isAllowedInURI)+import Test.HUnit+import Text.ParserCombinators.Parsec+import Text.PrettyPrint (hcat, punctuate, render, text)+import Distribution.Pretty (Pretty(pretty), prettyShow)++data SourceType+ = Deb | DebSrc+ deriving (Eq, Ord, Show)++-- arch+-- lang+-- target+-- pdiffs+-- by-hash+-- allow-insecure=no+-- allow-weak=no+-- allow-downgrade-to-insecure=no+-- trusted=no+-- signed-by+-- check-valid-until+-- valid-until-min+-- valid-until-max+data SourceOption+ = SourceOption String SourceOp [String]+ deriving (Eq, Ord, Show)++data SourceOp = OpSet | OpAdd | OpDel deriving (Eq, Ord, Show)++instance Pretty SourceOp where+ pretty OpSet = text "="+ pretty OpAdd = text "+="+ pretty OpDel = text "-="++data DebSource+ = DebSource+ { _sourceType :: SourceType+ , _sourceOptions :: [SourceOption]+ , _sourceUri :: VendorURI+ , _sourceDist :: Either String (Codename, [Section])+ } deriving (Eq, Ord, Show)++instance Pretty SourceType where+ pretty Deb = text "deb"+ pretty DebSrc = text "deb-src"++instance Pretty SourceOption where+ pretty (SourceOption k op vs) = text k <> pretty op <> hcat (punctuate (text ",") (map text vs))++instance Pretty DebSource where+ pretty (DebSource thetype theoptions theuri thedist) =+ hcat (punctuate (text " ")+ ([pretty thetype] +++ (case theoptions of+ [] -> []+ _ -> [text "[" <> hcat (punctuate (text ", ") (map pretty theoptions)) <> text "]"]) +++ [text (show (view vendorURI theuri))] +++ case thedist of+ Left exactPath -> [text (escapeURIString isAllowedInURI exactPath)]+ Right (dist, sections) ->+ map text (codename dist : map sectionName' sections)))++instance Pretty (PP [DebSource]) where+ pretty = hcat . map (\ x -> pretty x <> text "\n") . unPP++{-++deb uri distribution [component1] [componenent2] [...]++The URI for the deb type must specify the base of the Debian+distribution, from which APT will find the information it needs.++distribution can specify an exact path, in which case the components+must be omitted and distribution must end with a slash (/).++If distribution does not specify an exact path, at least one component+must be present.++Distribution may also contain a variable, $(ARCH), which expands to+the Debian architecture (i386, m68k, powerpc, ...) used on the+system.++The rest of the line can be marked as a comment by using a #.++Additional Notes:++ + Lines can begin with leading white space.++ + If the dist ends with slash (/), then it must be an absolute path+ and it is an error to specify components after it.++-}++-- |quoteWords - similar to words, but with special handling of+-- double-quotes and brackets.+--+-- The handling double quotes and [] is supposed to match:+-- apt-0.6.44.2\/apt-pkg\/contrib\/strutl.cc:ParseQuoteWord()+--+-- The behaviour can be defined as:+--+-- Break the string into space seperated words ignoring spaces that+-- appear between \"\" or []. Strip trailing and leading white space+-- around words. Strip out double quotes, but leave the square+-- brackets intact.+quoteWords :: String -> [String]+quoteWords [] = []+quoteWords s = quoteWords' (dropWhile (==' ') s)+ where+ quoteWords' :: String -> [String]+ quoteWords' [] = []+ quoteWords' str =+ case break (flip elem (" [\"" :: String)) str of+ ([],[]) -> []+ (w, []) -> [w]+ (w, (' ':rest)) -> w : (quoteWords' (dropWhile (==' ') rest))+ (w, ('"':rest)) ->+ case break (== '"') rest of+ (w',('"':rest)) ->+ case quoteWords' rest of+ [] -> [w ++ w']+ (w'':ws) -> ((w ++ w' ++ w''): ws)+ (_w',[]) -> error ("quoteWords: missing \" in the string: " ++ s)+ _ -> error ("the impossible happened in SourcesList.quoteWords")+ (w, ('[':rest)) ->+ case break (== ']') rest of+ (w',(']':rest)) ->+ case quoteWords' rest of+ [] -> [w ++ "[" ++ w' ++ "]"]+ (w'':ws) -> ((w ++ "[" ++ w' ++ "]" ++ w''): ws)+ (_w',[]) -> error ("quoteWords: missing ] in the string: " ++ s)+ _ -> error ("the impossible happened in SourcesList.quoteWords")+ _ -> error ("the impossible happened in SourcesList.quoteWords")++stripLine :: String -> String+stripLine = takeWhile (/= '#') . dropWhile (== ' ')++sourceLines :: String -> [String]+sourceLines = filter (not . null) . map stripLine . lines++-- |parseSourceLine -- parses a source line+-- the argument must be a non-empty, valid source line with comments stripped+-- see: 'sourceLines'+parseSourceLine :: [Loc] -> String -> DebSource+parseSourceLine locs str = either error id (parseSourceLine' locs str)+{-+ case quoteWords str of+ (theTypeStr : theUriStr : theDistStr : sectionStrs) ->+ let sections = map parseSection' sectionStrs+ theType = case unEscapeString theTypeStr of+ "deb" -> Deb+ "deb-src" -> DebSrc+ o -> error ("parseSourceLine: invalid type " ++ o ++ " in line:\n" ++ str)+ theUri = case parseURI theUriStr of+ Nothing -> error ("parseSourceLine: invalid uri " ++ theUriStr ++ " in the line:\n" ++ str)+ Just u -> u+ theDist = unEscapeString theDistStr+ in+ case last theDist of+ '/' -> if null sections+ then DebSource { sourceType = theType, sourceOptions = [], sourceUri = theUri, sourceDist = Left theDist }+ else error ("parseSourceLine: Dist is an exact path, so sections are not allowed on the line:\n" ++ str)+ _ -> if null sections+ then error ("parseSourceLine: Dist is not an exact path, so at least one section is required on the line:\n" ++ str)+ else DebSource { sourceType = theType, sourceOptions = [], sourceUri = theUri, sourceDist = Right (parseReleaseName theDist, sections) }+ _ -> error ("parseSourceLine: invalid line in sources.list:\n" ++ str)+-}++parseOptions :: String -> Either ParseError [SourceOption]+parseOptions s = parse pOptions s s++pOptions :: CharParser () [SourceOption]+pOptions = do _ <- char '['+ skipMany (oneOf [' ','\t'])+ opts <- sepBy1 pOption (char ',')+ skipMany (oneOf [' ','\t'])+ _ <- char ']'+ return opts++pOption :: CharParser () SourceOption+pOption = do skipMany (oneOf [' ','\t'])+ key <- many1 (noneOf ['+','-','=',' ','\t'])+ skipMany (oneOf [' ','\t'])+ op <- pOp+ skipMany (oneOf [' ','\t'])+ values <- sepBy1 (many1 (noneOf [',',']',' ','\t'])) (char ',')+ skipMany (oneOf [' ','\t'])+ return $ SourceOption key op values++pOp :: CharParser () SourceOp+pOp = do (char '+' >> char '=' >> return OpAdd)+ <|>+ (char '-' >> char '=' >> return OpDel)+ <|>+ (char '=' >> return OpSet)++parseSourceLine' :: [Loc] -> String -> Either String DebSource+parseSourceLine' locs str =+ case quoteWords str of+ theTypeStr : theOptionStr@('[' : _) : theURIStr : theDistStr : sectionStrs ->+ either+ (Left . show)+ (\opts -> go theTypeStr opts theURIStr theDistStr sectionStrs)+ (parseOptions theOptionStr)+ theTypeStr : theURIStr : theDistStr : sectionStrs ->+ go theTypeStr [] theURIStr theDistStr sectionStrs+ _ -> Left ("parseSourceLine: invalid line in sources.list:\n" ++ str)+ where+ go :: String -> [SourceOption] -> String -> String -> [String] -> Either String DebSource+ go theTypeStr theOptions theURIStr theDistStr sectionStrs =+ let sections = map parseSection' sectionStrs+ theType = case unEscapeString theTypeStr of+ "deb" -> Right Deb+ "deb-src" -> Right DebSrc+ s -> Left ("parseSourceLine" ++ prettyShow ($here : locs) ++ ": invalid type " ++ s ++ " in line:\n" ++ str ++ " str=" ++ show str)+ theURI = case parseVendorURI ($here : locs) theURIStr of+ Nothing -> Left ("parseSourceLine' " ++ prettyShow ($here : locs) ++ ": invalid uri " ++ theURIStr ++ " str=" ++ show str)+ Just u -> Right u+ theDist = unEscapeString theDistStr+ in+ case (last theDist, theType, theURI) of+ ('/', Right typ, Right uri) -> if null sections+ then Right $ DebSource { _sourceType = typ, _sourceOptions = theOptions, _sourceUri = uri, _sourceDist = Left theDist }+ else Left ("parseSourceLine: Dist is an exact path, so sections are not allowed on the line:\n" ++ str)+ (_, Right typ, Right uri) -> if null sections+ then Left ("parseSourceLine: Dist is not an exact path, so at least one section is required on the line:\n" ++ str)+ else Right $ DebSource { _sourceType = typ, _sourceOptions = theOptions, _sourceUri = uri, _sourceDist = Right ((parseCodename theDist), sections) }+ (_, Left msg, _) -> Left msg+ (_, _, Left msg) -> Left msg++parseSourcesList :: [Loc] -> String -> [DebSource]+parseSourcesList locs = map (parseSourceLine locs) . sourceLines++-- * Unit Tests++-- TODO: add test cases that test for unterminated double-quote or bracket+testQuoteWords :: Test+testQuoteWords =+ test [ assertEqual "Space seperate words, no quoting" ["hello", "world","!"] (quoteWords " hello world ! ")+ , assertEqual "Space seperate words, double quotes" ["hello world","!"] (quoteWords " hel\"lo world\" ! ")+ , assertEqual "Space seperate words, square brackets" ["hel[lo worl]d","!"] (quoteWords " hel[lo worl]d ! ")+ , assertEqual "Space seperate words, square-bracket at end" ["hel[lo world]"] (quoteWords " hel[lo world]")+ , assertEqual "Space seperate words, double quote at end" ["hello world"] (quoteWords " hel\"lo world\"")+ , assertEqual "Space seperate words, square-bracket at beginning" ["[hello wo]rld","!"] (quoteWords "[hello wo]rld !")+ , assertEqual "Space seperate words, double quote at beginning" ["hello world","!"] (quoteWords "\"hello wor\"ld !")+ ]++testSourcesList :: Test+testSourcesList =+ test [ assertEqual "parse and pretty sources.list" validSourcesListExpected (render . pretty . PP . parseSourcesList [$here] $ validSourcesListStr) ]++testSourcesList2 :: Test+testSourcesList2 =+ test [ assertEqual "pretty sources.list" validSourcesListExpected (render . pretty . PP $ validSourcesList) ]++validSourcesListStr :: String+validSourcesListStr =+ unlines $ [ " # A comment only line "+ , " deb ftp://ftp.debian.org/debian unstable main contrib non-free # typical deb line"+ , " deb-src ftp://ftp.debian.org/debian unstable main contrib non-free # typical deb-src line"+ , ""+ , "# comment line"+ , "deb http://pkg-kde.alioth.debian.org/kde-3.5.0/ ./ # exact path"+ , "deb [trusted=yes] http://ftp.debian.org/whee \"space dist\" main"+ , "deb [trusted=yes] http://ftp.debian.org/whee dist space%20section"+ ]++validSourcesList :: [DebSource]+validSourcesList =+ [DebSource {_sourceType = Deb, _sourceOptions = [], _sourceUri = (review vendorURI . fromJust) (parseURI "ftp://ftp.debian.org/debian"), _sourceDist = Right (parseCodename "unstable",[Section "main",Section "contrib",Section "non-free"])},+ DebSource {_sourceType = DebSrc, _sourceOptions = [], _sourceUri = (review vendorURI . fromJust) (parseURI "ftp://ftp.debian.org/debian"), _sourceDist = Right (parseCodename "unstable",[Section "main",Section "contrib",Section "non-free"])},+ DebSource {_sourceType = Deb, _sourceOptions = [], _sourceUri = (review vendorURI . fromJust) (parseURI "http://pkg-kde.alioth.debian.org/kde-3.5.0/"), _sourceDist = Left "./"},+ DebSource {_sourceType = Deb, _sourceOptions = [SourceOption "trusted" OpSet ["yes"]], _sourceUri = (review vendorURI . fromJust) (parseURI "http://ftp.debian.org/whee"), _sourceDist = Right (parseCodename "space dist",[Section "main"])},+ DebSource {_sourceType = Deb, _sourceOptions = [SourceOption "trusted" OpSet ["yes"]], _sourceUri = (review vendorURI . fromJust) (parseURI "http://ftp.debian.org/whee"), _sourceDist = Right (parseCodename "dist",[Section "space section"])}]++validSourcesListExpected :: String+validSourcesListExpected =+ unlines $ [ "deb ftp://ftp.debian.org/debian unstable main contrib non-free"+ , "deb-src ftp://ftp.debian.org/debian unstable main contrib non-free"+ , "deb http://pkg-kde.alioth.debian.org/kde-3.5.0/ ./"+ , "deb [trusted=yes] http://ftp.debian.org/whee space%20dist main"+ , "deb [trusted=yes] http://ftp.debian.org/whee dist space%20section"+ ]+_invalidSourcesListStr1 :: Text+_invalidSourcesListStr1 = "deb http://pkg-kde.alioth.debian.org/kde-3.5.0/ ./ main contrib non-free # exact path with sections"++testSourcesListParse :: Test+testSourcesListParse =+ test [ assertEqual "" gutsy (concat . map (<> "\n") . map (render . pretty) . parseSourcesList [$here] $ gutsy) ]+ where+ gutsy = concat ["deb http://us.archive.ubuntu.com/ubuntu/ gutsy main restricted universe multiverse\n",+ "deb-src http://us.archive.ubuntu.com/ubuntu/ gutsy main restricted universe multiverse\n",+ "deb http://us.archive.ubuntu.com/ubuntu/ gutsy-updates main restricted universe multiverse\n",+ "deb-src http://us.archive.ubuntu.com/ubuntu/ gutsy-updates main restricted universe multiverse\n",+ "deb http://us.archive.ubuntu.com/ubuntu/ gutsy-backports main restricted universe multiverse\n",+ "deb-src http://us.archive.ubuntu.com/ubuntu/ gutsy-backports main restricted universe multiverse\n",+ "deb http://security.ubuntu.com/ubuntu/ gutsy-security main restricted universe multiverse\n",+ "deb-src http://security.ubuntu.com/ubuntu/ gutsy-security main restricted universe multiverse\n"]++sourcesListTests :: Test+sourcesListTests =+ TestList [ testQuoteWords, testSourcesList, testSourcesList2, testSourcesListParse ]++$(makeLenses ''DebSource)
+ src/Debian/TH.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE CPP, FlexibleInstances, TemplateHaskell #-}+{-# OPTIONS -Wall #-}++module Debian.TH+ ( here+ , Loc+ ) where++import Data.List (intersperse)+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import Distribution.Pretty (Pretty(..))+import Language.Haskell.TH (ExpQ, Loc(..), location)+import Language.Haskell.TH.Instances ()+import Language.Haskell.TH.Lift (lift)+--import Text.PrettyPrint (Doc, text)+import Text.PrettyPrint.HughesPJClass (Doc, hcat, text)++here :: ExpQ+here = lift =<< location++instance Pretty Loc where+ pretty = prettyLoc++prettyLoc :: Loc -> Doc+prettyLoc (Loc _filename _package modul (line, col) _) = text (modul <> ":" ++ show line ++ ":" ++ show col)++instance Pretty [Loc] where+ pretty locs = text "[" <> hcat (intersperse (text " → ") (fmap prettyLoc locs)) <> text "]"
+ src/Debian/Time.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE CPP #-}+module Debian.Time where++import Data.Time+#if !MIN_VERSION_time(1,5,0)+import System.Locale (defaultTimeLocale)+#endif+import Data.Time.Clock.POSIX+import System.Posix.Types++-- * Time Helper Functions++rfc822DateFormat' :: String+rfc822DateFormat' = "%a, %d %b %Y %T %z"++epochTimeToUTCTime :: EpochTime -> UTCTime+epochTimeToUTCTime = posixSecondsToUTCTime . fromIntegral . fromEnum++formatTimeRFC822 :: (FormatTime t) => t -> String+formatTimeRFC822 = formatTime defaultTimeLocale rfc822DateFormat'++parseTimeRFC822 :: (ParseTime t) => String -> Maybe t+parseTimeRFC822 = parseTimeM True defaultTimeLocale rfc822DateFormat'++getCurrentLocalRFC822Time :: IO String+getCurrentLocalRFC822Time = getCurrentTime >>= utcToLocalZonedTime >>= return . formatTime defaultTimeLocale rfc822DateFormat'+
+ src/Debian/URI.hs view
@@ -0,0 +1,155 @@+{-# LANGUAGE CPP, DeriveDataTypeable, OverloadedStrings, PackageImports, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# OPTIONS -Wall -fno-warn-orphans #-}++module Debian.URI+ ( module Network.URI++#if 0+ , _NodeElement -- :: Prism' Node Element+ , _NodeContent -- :: Prism' Node Text+ , eltAttrsLens -- :: Lens' Element (HashMap AttrName AttrValue)+ , eltChildrenLens -- :: Lens' Element [Node]+ , eltNameLens -- :: Lens' Element Text+#endif++ , URIError(..)+ , uriSchemeLens+ , uriAuthorityLens+ , uriPathLens+ , uriQueryLens+ , uriFragmentLens+ -- * String known to parsable by parseURIReference. Mainly+ -- useful because it has a Read instance.+ , URI'(..)+ , fromURI'+ , toURI'+ , readURI'++ -- Show URI as a Haskell expression+ , showURI+ -- Monadic URI parsers+ , parseURIReference'+ , parseURI'+ , parseAbsoluteURI'+ , parseRelativeReference'+ , parseURIUnsafe+ -- URI appending+ , appendURI+ , appendURIs+ , parentURI+ , uriToString'+ -- * Lift IO operations into a MonadError instance+ , HasParseError(fromParseError)+ , HasURIError(fromURIError)+ -- * QuickCheck properties+ , prop_print_parse+ , prop_append_singleton+ ) where++import Control.Lens (makeLensesFor)+import Control.Monad.Except (MonadError, throwError)+import Data.Foldable (foldrM)+import Data.Maybe (fromJust, fromMaybe)+#if !MIN_VERSION_base(4,11,0)+import Data.Monoid ((<>))+#endif+import Network.URI (nullURI, parseURIReference, parseURI, parseAbsoluteURI, parseRelativeReference, URI(..), URIAuth(..), uriToString)+import System.FilePath ((</>), dropTrailingPathSeparator, takeDirectory)+import Test.QuickCheck (Arbitrary)+import Text.Parsec (ParseError)++$(makeLensesFor [("uriScheme", "uriSchemeLens"),+ ("uriAuthority", "uriAuthorityLens"),+ ("uriPath", "uriPathLens"),+ ("uriQuery", "uriQueryLens"),+ ("uriFragment", "uriFragmentLens")] ''URI)++showURI :: URI -> String+showURI (URI {..}) =+ "URI {uriScheme = " <> show uriScheme <>+ ", uriAuthority = " <> show uriAuthority <>+ ", uriPath = " <> show uriPath <>+ ", uriQuery = " <> show uriQuery <>+ ", uriFragment = " <> show uriFragment <> "}"++-- | parseURI with MonadError+parseURI' :: (HasURIError e, MonadError e m) => String -> m URI+parseURI' s = maybe (throwError $ fromURIError $ URIParseError "parseURI" s) return (parseURI s)+parseURIReference' :: (HasURIError e, MonadError e m) => String -> m URI+parseURIReference' s = maybe (throwError $ fromURIError $ URIParseError "parseURIReference" s) return (parseURIReference s)+parseAbsoluteURI' :: (HasURIError e, MonadError e m) => String -> m URI+parseAbsoluteURI' s = maybe (throwError $ fromURIError $ URIParseError "parseAbsoluteURI" s) return (parseAbsoluteURI s)+parseRelativeReference' :: (HasURIError e, MonadError e m) => String -> m URI+parseRelativeReference' s = maybe (throwError $ fromURIError $ URIParseError "parseRelativeReference" s) return (parseRelativeReference s)++parseURIUnsafe :: String -> URI+parseURIUnsafe s = fromMaybe (error ("parseURIUnsafe " ++ show s)) $ parseURIReference s++--parseAbsoluteURI :: String -> Maybe URI+--parseRelativeReference :: String -> Maybe URI+--parseURI :: String -> Maybe URI+--parseURIReference :: String -> Maybe URI++data URIError =+ URIParseError String String+ | URIAppendError URI URI+ deriving (Eq, Ord, Show)++-- | Conservative appending of absolute and relative URIs. There may+-- be other cases that can be implemented, lets see if they turn up.+appendURI :: MonadError URIError m => URI -> URI -> m URI+ -- Append the two paths+appendURI (URI scheme auth path1 "" "") (URI "" Nothing path2 query fragment) = return $ URI scheme auth (path1 </> path2) query fragment+ -- Use query from RHS+appendURI a b = throwError (URIAppendError a b)++-- | Append a list of URI+-- @@+-- λ> appendURIs (parseURI "http://host.com") (parseURIRelative "/bar")+appendURIs :: (Foldable t, MonadError URIError m) => t URI -> m URI+appendURIs uris = foldrM appendURI nullURI uris++parentURI :: URI -> URI+parentURI uri = uri {uriPath = takeDirectory (dropTrailingPathSeparator (uriPath uri))}++-- properties+-- appendURIs [x] == x++prop_append_singleton :: URI -> Bool+prop_append_singleton uri = appendURIs [uri] == Right uri++prop_print_parse :: URI -> Bool+prop_print_parse uri = parseURIReference (show uri) == Just uri++-- | A wrapper around a String containing a known parsable URI. Not+-- absolutely safe, because you could say read "URI' \"bogus string\""+-- :: URI'. But enough to save me from myself.+newtype URI' = URI' String deriving (Read, Show, Eq, Ord)++readURI' :: String -> Maybe URI'+readURI' s = maybe Nothing (const (Just (URI' s))) (parseURIReference s)++fromURI' :: URI' -> URI+fromURI' (URI' s) = fromJust (parseURI s) -- this should provably parse++-- | Using the bogus Show instance of URI here. If it ever gets fixed+-- this will stop working. Worth noting that show will obscure any+-- password info embedded in the URI, so that's nice.+toURI' :: URI -> URI'+toURI' = URI' . show++uriToString' :: URI -> String+uriToString' uri = uriToString id uri ""++instance Arbitrary URI where+ -- Replace with import from network-arbitrary package++class HasParseError e where fromParseError :: ParseError -> e+instance HasParseError ParseError where fromParseError = id++class HasURIError e where fromURIError :: URIError -> e+instance HasURIError URIError where fromURIError = id++instance Ord ParseError where+ compare a b = compare (show a) (show b)
+ src/Debian/UTF8.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE CPP #-}+-- | There are old index files that have funky characters like 'ø'+-- that are not properly UTF8 encoded. As far as I can tell, these+-- files are otherwise plain ascii, so just naivelyinsert the+-- character into the output stream.+module Debian.UTF8+ ( decode+ , readFile+ ) where++#if !MIN_VERSION_base(4,8,0)+import Control.Applicative ((<$>))+#endif+import qualified Data.ByteString.Char8 as B (concat)+import qualified Data.ByteString.Lazy.Char8 as L (ByteString, readFile, toChunks)+import Data.Char (chr)+import Data.Text as T+import Data.Text.Encoding (decodeUtf8With)+import Data.Word (Word8)+import Prelude hiding (readFile)++decode :: L.ByteString -> T.Text+decode b = decodeUtf8With e (B.concat (L.toChunks b))+ where+ e :: String -> Maybe Word8 -> Maybe Char+ e _description w = fmap (chr . fromIntegral) w++readFile :: FilePath -> IO T.Text+readFile path = decode <$> L.readFile path
+ src/Debian/Util/FakeChanges.hs view
@@ -0,0 +1,256 @@+{-# LANGUAGE DeriveDataTypeable, ScopedTypeVariables #-}+module Debian.Util.FakeChanges (fakeChanges) where++--import Control.Arrow+import Control.Exception+import Control.Monad hiding (mapM)+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Data (Data, Typeable)+import Data.Digest.Pure.SHA as SHA+import Data.Foldable (concat, all, foldr)+import Data.List as List (intercalate, nub, partition, isSuffixOf)+import Data.Maybe+import Debian.Pretty (prettyShow)+import Data.Traversable+import Debian.Control+import qualified Debian.Deb as Deb+import Debian.Time+import Network.HostName (getHostName)+import Prelude hiding (concat, foldr, all, mapM, sum)+import System.Environment+import System.FilePath+import System.Posix.Files+import Text.Regex.TDFA++data Error+ = NoDebs+ | TooManyDscs [FilePath]+ | TooManyTars [FilePath]+ | TooManyDiffs [FilePath]+ | UnknownFiles [FilePath]+ | MalformedDebFilename [FilePath]+ | VersionMismatch [Maybe String]+ deriving (Read, Show, Eq, Typeable, Data)++data Files+ = Files { dsc :: Maybe (FilePath, Paragraph)+ , debs :: [(FilePath, Paragraph)]+ , tar :: Maybe FilePath+ , diff :: Maybe FilePath+ }++fakeChanges :: [FilePath] -> IO (FilePath, String)+fakeChanges fps =+ do files <- loadFiles fps+ let version = getVersion files+ source = getSource files+ maintainer = getMaintainer files+ arches = getArches files+ binArch = getBinArch files+ dist = "unstable"+ urgency = "low"+ (invalid, binaries) = unzipEithers $ map (debNameSplit . fst) (debs files)+ when (not . null $ invalid) (error $ "Some .deb names are invalid: " ++ show invalid)+ uploader <- getUploader+ date <- getCurrentLocalRFC822Time+ fileLines <- mapM mkFileLine fps+ let changes = Control $ return . Paragraph $ map Field+ [ ("Format"," 1.7")+ , ("Date", ' ' : date)+ , ("Source", ' ' : source)+ , ("Binary", ' ' : (intercalate " " $ map (\(n,_,_) -> n) binaries))+ , ("Architecture", ' ' : intercalate " " arches)+ , ("Version", ' ' : version)+ , ("Distribution", ' ' : dist)+ , ("Urgency", ' ' : urgency)+ , ("Maintainer", ' ' : maintainer)+ , ("Changed-By", ' ' : uploader)+ , ("Description", "\n Simulated description")+ , ("Changes", "\n" ++ unlines (map (' ':) [ source ++ " (" ++ version ++") " ++ dist ++ "; urgency=" ++ urgency+ , "."+ , " * Simulated changes"+ ]+ ))+ , ("Files", "\n" ++ unlines fileLines)+ ]+ return $ (concat [ source, "_", version, "_", binArch, ".changes"], prettyShow changes)+-- let (invalid, binaries) = unzipEithers $ map debNameSplit debs+{-+ when (not . null $ invalid) (throwDyn [MalformedDebFilename invalid])+ version <- getVersion dsc debs+ putStrLn version+ source <- getSource dsc debs+ putStrLn source+-}+-- TODO: seems like this could be more aggressive about ensure the+-- versions make sense. Except with packages like libc, the versions+-- don't make sense. Maybe we want a flag that disables version check+-- ?+getVersion :: Files -> String+getVersion files+ | isNothing (dsc files) =+ let versions = map (fieldValue "Version" . snd) (debs files)+ in+ if (all isJust versions) && (length (nub versions) == 1)+ then fromJust (head versions)+ else error (show [VersionMismatch (nub versions)])+ | otherwise =+ case fieldValue "Version" (snd . fromJust $ dsc files) of+ (Just v) -> v+ Nothing -> error $ "show (dsc files)" ++ " does not have a Version field :("+++getSource :: Files -> String+getSource files =+ let dscSource =+ case (dsc files) of+ Nothing -> []+ (Just (fp, p)) ->+ case fieldValue "Source" p of+ (Just v) -> [v]+ Nothing -> error $ fp ++ " does not have a Source field :("+ debSources = map debSource (debs files)+ srcs = nub (dscSource ++ debSources)+ in+ if (singleton srcs)+ then (head srcs)+ else error $ "Could not determine source."+ where+ debSource (deb,p) =+ case (fieldValue "Source" p) of+ (Just v) -> v+ Nothing ->+ case fieldValue "Package" p of+ (Just v) -> v+ Nothing -> error $ "Could not find Source or Package field in " ++ deb++++getMaintainer :: Files -> String+getMaintainer files+ | isJust (dsc files) =+ let (fp, p) = fromJust (dsc files)+ in+ case fieldValue "Maintainer" p of+ Nothing -> error $ fp ++ " is missing the Maintainer field."+ (Just v) -> v+ | otherwise =+ let maintainers = catMaybes $ map (fieldValue "Maintainer" . snd) (debs files)+ maintainer = nub maintainers+ in+ if singleton maintainer+ then head maintainer+ else error $ "Could not uniquely determine the maintainer: " ++ show maintainer++getArches :: Files -> [String]+getArches files =+ let debArchs = map (fieldValue "Architecture" . snd) (debs files)+ tarArch = fmap (const "source") (tar files)+ diffArch = fmap (const "source") (diff files)+ in+ nub $ catMaybes (tarArch : diffArch : debArchs)+++getBinArch :: Files -> String+getBinArch files =+ let binArch = nub $ mapMaybe (fieldValue "Architecture" . snd) (debs files)+ in+ if singleton binArch+ then head binArch+ else case (filter (/= "all") binArch) of+ [b] -> b+ _ -> error $ "Could not uniquely determine binary architecture: " ++ show binArch++mkFileLine :: FilePath -> IO String+mkFileLine fp+ | ".deb" `isSuffixOf` fp =+ do sum <- L.readFile fp >>= return . show . sha256+ size <- liftM fileSize $ getFileStatus fp+ (Control (p:_)) <- Deb.fields fp+ return $ concat [ " ", sum, " ", show size, " ", fromMaybe "unknown" (fieldValue "Section" p), " "+ , fromMaybe "optional" (fieldValue "Priority" p), " ", (takeBaseName fp)+ ]+ | otherwise =+ do sum <- L.readFile fp >>= return . show . sha256+ size <- liftM fileSize $ getFileStatus fp+ return $ concat [ " ", sum, " ", show size, " ", "unknown", " "+ , "optional"," ", (takeBaseName fp)+ ]++-- more implementations can be found at:+-- http://www.google.com/codesearch?hl=en&lr=&q=%22%5BEither+a+b%5D+-%3E+%28%5Ba%5D%2C%5Bb%5D%29%22&btnG=Search+unzipEithers :: [Either a b] -> ([a],[b])+unzipEithers = foldr unzipEither ([],[])+ where+ unzipEither (Left l) ~(ls, rs) = (l:ls, rs)+ unzipEither (Right r) ~(ls, rs) = (ls, r:rs)++-- move to different library+debNameSplit :: String -> Either FilePath (String, String, String)+debNameSplit fp =+ case (takeFileName fp) =~ "^(.*)_(.*)_(.*).deb$" of+ [[_, name, version, arch]] -> Right (name, version, arch)+ _ -> Left fp+++loadFiles :: [FilePath] -> IO Files+loadFiles files =+ let (dscs', files'') = partition (isSuffixOf ".dsc") files'+ (debs', files') = partition (isSuffixOf ".deb") files+ (tars', files''') = partition (isSuffixOf ".tar.gz") files''+ (diffs', rest) = partition (isSuffixOf ".diff.gz") files'''+ errors = concat [ if (length debs' < 1) then [NoDebs] else []+ , if (length dscs' > 1) then [TooManyDscs dscs'] else []+ , if (length tars' > 1) then [TooManyTars tars'] else []+ , if (length diffs' > 1) then [TooManyDiffs diffs'] else []+ , if (length rest > 0) then [UnknownFiles rest] else []+ ]+ in+ do when (not . null $ errors) (error $ show errors)+ dsc' <- mapM loadDsc (listToMaybe dscs')+ debs'' <- mapM loadDeb debs'+ return $ Files { dsc = dsc', debs = debs'', tar = listToMaybe tars', diff = listToMaybe diffs' }+ -- if (not . null $ errors) then throwDyn errors else return (debs, listToMaybe dscs, listToMaybe tars, listToMaybe diffs)+ where+ loadDsc :: FilePath -> IO (FilePath, Paragraph)+ loadDsc dsc' =+ do res <- parseControlFromFile dsc'+ case res of+ (Left e) -> error $ "Error parsing " ++ dsc' ++ "\n" ++ show e+ (Right (Control [p])) -> return (dsc', p)+ (Right c) -> error $ dsc' ++ " did not have exactly one paragraph: " ++ prettyShow c+ loadDeb :: FilePath -> IO (FilePath, Paragraph)+ loadDeb deb =+ do res <- Deb.fields deb+ case res of+ (Control [p]) -> return (deb, p)+ _ -> error $ deb ++ " did not have exactly one paragraph: " ++ prettyShow res+++getUploader :: IO String+getUploader =+ do debFullName <-+ do dfn <- try (getEnv "DEBFULLNAME")+ case dfn of+ (Right n) -> return n+ (Left (_ :: SomeException)) ->+ do dfn' <-try (getEnv "USER")+ case dfn' of+ (Right n) -> return n+ (Left (_ :: SomeException)) -> error $ "Could not determine user name, neither DEBFULLNAME nor USER enviroment variables were set."+ emailAddr <-+ do eml <- try (getEnv "DEBEMAIL")+ case eml of+ (Right e) -> return e+ (Left (_ :: SomeException)) ->+ do eml' <- try (getEnv "EMAIL")+ case eml' of+ (Right e) -> return e+ (Left (_ :: SomeException)) -> getHostName -- FIXME: this is not a FQDN+ return $ debFullName ++ " <" ++ emailAddr ++ ">"++-- * Utils++singleton :: [a] -> Bool+singleton [_] = True+singleton _ = False
+ src/Debian/VendorURI.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE TemplateHaskell #-}++module Debian.VendorURI+ ( VendorURI(..)+ , vendorURI+ , parseVendorURI+ ) where++import Control.Lens (makeLenses, review)+import Debian.TH (here)+import Debian.URI (parseURI, URI(uriPath))+import Distribution.Pretty (prettyShow)+import Language.Haskell.TH.Syntax (Loc)+import System.FilePath (splitDirectories)++newtype VendorURI = VendorURI {_vendorURI :: URI} deriving (Eq, Ord)++instance Show VendorURI where+ show (VendorURI uri) = "VendorURI (fromJust (parseURIReference " ++ show (show uri) ++ "))"++$(makeLenses ''VendorURI)++parseVendorURI :: [Loc] -> String -> Maybe VendorURI+parseVendorURI locs s = fmap (review vendorURI) (parseURI s)++-- toURI' :: VendorURI -> URI'+-- toURI' = URI' . show . view vendorURI
+ src/Debian/Version.hs view
@@ -0,0 +1,16 @@+-- |A module for parsing, comparing, and (eventually) modifying debian version+-- numbers. <http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version>+module Debian.Version+ (DebianVersion -- |Exported abstract because the internal representation is likely to change+ , prettyDebianVersion+ , parseDebianVersion+ , parseDebianVersion'+ , epoch+ , version+ , revision+ , buildDebianVersion+ , evr+ ) where++import Debian.Version.Common+import Debian.Version.String ()
+ src/Debian/Version/ByteString.hs view
@@ -0,0 +1,18 @@+{-# OPTIONS -fno-warn-orphans #-}+module Debian.Version.ByteString+ ( ParseDebianVersion(..)+ ) where++import Text.ParserCombinators.Parsec++import qualified Data.ByteString.Char8 as C++import Debian.Version.Common+import Debian.Version.Internal++instance ParseDebianVersion C.ByteString where+ parseDebianVersion byteStr =+ let str = C.unpack byteStr in+ case parse parseDV str str of+ Left e -> Left e+ Right dv -> Right (DebianVersion str dv)
+ src/Debian/Version/Common.hs view
@@ -0,0 +1,185 @@+-- |A module for parsing, comparing, and (eventually) modifying debian version+-- numbers. <http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version>+{-# LANGUAGE FlexibleInstances #-}+{-# OPTIONS -fno-warn-orphans -fno-warn-unused-do-bind #-}+module Debian.Version.Common+ ( DebianVersion -- |Exported abstract because the internal representation is likely to change+ , prettyDebianVersion+ , ParseDebianVersion(..)+ , parseDebianVersion'+ , evr -- DebianVersion -> (Maybe Int, String, Maybe String)+ , epoch+ , version+ , revision+ , buildDebianVersion+ , parseDV+ ) where++import Data.Char (ord, isDigit, isAlpha)+import Debian.Pretty (PP(..))+import Debian.Version.Internal+import Text.ParserCombinators.Parsec+import Text.Regex+import Text.PrettyPrint (Doc, render, text)+import Distribution.Pretty (Pretty(pretty))++prettyDebianVersion :: DebianVersion -> Doc+prettyDebianVersion (DebianVersion s _) = text s++instance Pretty (PP DebianVersion) where+ pretty = prettyDebianVersion . unPP++instance Eq DebianVersion where+ (DebianVersion _ v1) == (DebianVersion _ v2) = v1 == v2++instance Ord DebianVersion where+ compare (DebianVersion _ v1) (DebianVersion _ v2) = compare v1 v2++instance Show DebianVersion where+ show v = "(Debian.Version.parseDebianVersion (" ++ show (render (prettyDebianVersion v)) ++ " :: String))"++-- make ~ less than everything, and everything else higher that letters+order :: Char -> Int+order c+ | isDigit c = 0+ | isAlpha c = ord c+ | c == '~' = -1+ | otherwise = (ord c) + 256++-- |We have to do this wackiness because ~ is less than the empty string+compareNonNumeric :: [Char] -> [Char] -> Ordering+compareNonNumeric "" "" = EQ+compareNonNumeric "" ('~':_cs) = GT+compareNonNumeric ('~':_cs) "" = LT+compareNonNumeric "" _ = LT+compareNonNumeric _ "" = GT+compareNonNumeric (c1:cs1) (c2:cs2) =+ if (order c1) == (order c2)+ then compareNonNumeric cs1 cs2+ else compare (order c1) (order c2)++instance Eq NonNumeric where+ (NonNumeric s1 n1) == (NonNumeric s2 n2) =+ case compareNonNumeric s1 s2 of+ EQ -> n1 == n2+ _o -> False++instance Ord NonNumeric where+ compare (NonNumeric s1 n1) (NonNumeric s2 n2) =+ case compareNonNumeric s1 s2 of+ EQ -> compare n1 n2+ o -> o++instance Eq Numeric where+ (Numeric n1 mnn1) == (Numeric n2 mnn2) =+ case compare n1 n2 of+ EQ -> case compareMaybeNonNumeric mnn1 mnn2 of+ EQ -> True+ _ -> False+ _ -> False++compareMaybeNonNumeric :: Maybe NonNumeric -> Maybe NonNumeric -> Ordering+compareMaybeNonNumeric mnn1 mnn2 =+ case (mnn1, mnn2) of+ (Nothing, Nothing) -> EQ+ (Just (NonNumeric nn _), Nothing) -> compareNonNumeric nn ""+ (Nothing, Just (NonNumeric nn _)) -> compareNonNumeric "" nn+ (Just nn1, Just nn2) -> compare nn1 nn2++instance Ord Numeric where+ compare (Numeric n1 mnn1) (Numeric n2 mnn2) =+ case compare n1 n2 of+ EQ -> compareMaybeNonNumeric mnn1 mnn2+ o -> o++-- * Parser++class ParseDebianVersion a where+ parseDebianVersion :: a-> Either ParseError DebianVersion+-- |Convert a string to a debian version number. May throw an+-- exception if the string is unparsable -- but I am not sure if that+-- can currently happen. Are there any invalid version strings?+-- Perhaps ones with underscore, or something?++parseDebianVersion' :: ParseDebianVersion string => string -> DebianVersion+parseDebianVersion' str = either (\e -> error (show e)) id (parseDebianVersion str)++{-+showNN :: NonNumeric -> String+showNN (NonNumeric s n) = s ++ showN n++showN :: Found Numeric -> String+showN (Found (Numeric n nn)) = show n ++ maybe "" showNN nn+showN (Simulated _) = ""+-}++parseDV :: CharParser () (Found Int, NonNumeric, Found NonNumeric)+parseDV =+ do skipMany $ oneOf " \t"+ e <- parseEpoch+ upstreamVersion <- parseNonNumeric True True+ debianRevision <- option (Simulated (NonNumeric "" (Simulated (Numeric 0 Nothing)))) (char '-' >> parseNonNumeric True False >>= return . Found)+ return (e, upstreamVersion, debianRevision)++parseEpoch :: CharParser () (Found Int)+parseEpoch =+ option (Simulated 0) (try (many1 digit >>= \d -> char ':' >> return (Found (read d))))+++parseNonNumeric :: Bool -> Bool -> CharParser () NonNumeric+parseNonNumeric zeroOk upstream =+ do nn <- (if zeroOk then many else many1) ((noneOf "-0123456789") <|> (if upstream then upstreamDash else pzero))+ n <- parseNumeric upstream+ return $ NonNumeric nn n+ where+ upstreamDash :: CharParser () Char+ upstreamDash = try $ do char '-'+ lookAhead $ (many (noneOf "- \n\t") >> char '-')+ return '-'++parseNumeric :: Bool -> CharParser () (Found Numeric)+parseNumeric upstream =+ do n <- many1 (satisfy isDigit)+ nn <- option Nothing (parseNonNumeric False upstream >>= return . Just)+ return $ Found (Numeric (read n) nn)+ <|>+ return (Simulated (Numeric 0 Nothing))++{-+compareTest :: String -> String -> Ordering+compareTest str1 str2 =+ let v1 = either (error . show) id $ parse parseDV str1 str1+ v2 = either (error . show) id $ parse parseDV str2 str2+ in+ compare v1 v2+-}++-- |Split a DebianVersion into its three components: epoch, version,+-- revision. It is not safe to use the parsed version number for+-- this because you will lose information, such as leading zeros.+evr :: DebianVersion -> (Maybe Int, String, Maybe String)+evr (DebianVersion s _) =+ let re = mkRegex "^(([0-9]+):)?(([^-]*)|((.*)-([^-]*)))$" in+ -- ( ) ( ( ))+ -- ( e ) ( v ) (v2) ( r )+ case matchRegex re s of+ Just ["", _, _, v, "", _, _] -> (Nothing, v, Nothing)+ Just ["", _, _, _, _, v, r] -> (Nothing, v, Just r)+ Just [_, e, _, v, "", _, _] -> (Just (read e), v, Nothing)+ Just [_, e, _, _, _, v, r] -> (Just (read e), v, Just r)+ -- I really don't think this can happen.+ _ -> error ("Invalid Debian Version String: " ++ s)++epoch :: DebianVersion -> Maybe Int+epoch v = case evr v of (x, _, _) -> x+version :: DebianVersion -> String+version v = case evr v of (_, x, _) -> x+revision :: DebianVersion -> Maybe String+revision v = case evr v of (_, _, x) -> x++-- Build a Debian version number from epoch, version, revision+buildDebianVersion :: Maybe Int -> String -> Maybe String -> DebianVersion+buildDebianVersion e v r =+ either (error . show) (DebianVersion str) $ parse parseDV str str+ where+ str = (maybe "" (\ n -> show n ++ ":") e ++ v ++ maybe "" (\ s -> "-" ++ s) r)
+ src/Debian/Version/Internal.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE DeriveDataTypeable, FlexibleContexts #-}+module Debian.Version.Internal+ ( DebianVersion(..)+ , Numeric(..)+ , NonNumeric(..)+ , Found(..)+ ) where++import Data.Data (Data)+import Data.Typeable (Typeable)++-- Currently we store the original version string in the data-type so+-- that we can faithfully reproduce it quickly. Currently we do not+-- have any way to modify a version number -- so this works fine. May+-- have to change later.+data DebianVersion+ = DebianVersion String (Found Int, NonNumeric, Found NonNumeric) deriving (Data, Typeable)++data NonNumeric+ = NonNumeric String (Found Numeric)+ deriving (Show, Data, Typeable)++data Numeric+ = Numeric Int (Maybe NonNumeric)+ deriving (Show, Data, Typeable)++data Found a+ = Found { unFound :: a }+ | Simulated { unFound :: a }+ deriving (Show, Data, Typeable)++instance (Eq a) => Eq (Found a) where+ f1 == f2 = (unFound f1) == (unFound f2)++instance (Ord a) => Ord (Found a) where+ compare f1 f2 = compare (unFound f1) (unFound f2)
+ src/Debian/Version/String.hs view
@@ -0,0 +1,25 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}+{-# OPTIONS -fno-warn-orphans #-}+module Debian.Version.String+ ( ParseDebianVersion(..)+ ) where++import Text.ParserCombinators.Parsec++import Data.List (stripPrefix)+import Debian.Version.Common+import Debian.Version.Internal++instance ParseDebianVersion String where+ parseDebianVersion str =+ case parse parseDV str str of+ Left e -> Left e+ Right dv -> Right (DebianVersion str dv)+ +instance Read DebianVersion where+ readsPrec _ s =+ case stripPrefix "Debian.Version.parseDebianVersion " s of+ Just s' -> case reads s' :: [(String, String)] of+ []-> []+ (v, s'') : _ -> [(parseDebianVersion' v, s'')]+ Nothing -> []
+ src/Debian/Version/Text.hs view
@@ -0,0 +1,18 @@+{-# OPTIONS -fno-warn-orphans #-}+module Debian.Version.Text+ ( ParseDebianVersion(..)+ ) where++import Text.ParserCombinators.Parsec++import qualified Data.Text as T++import Debian.Version.Common+import Debian.Version.Internal++instance ParseDebianVersion T.Text where+ parseDebianVersion text =+ let str = T.unpack text in+ case parse parseDV str str of+ Left e -> Left e+ Right dv -> Right (DebianVersion str dv)
utils/Report.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE ForeignFunctionInterface, ScopedTypeVariables #-}+{-# LANGUAGE CPP, ForeignFunctionInterface, ScopedTypeVariables, TemplateHaskell #-} module Main where #if !MIN_VERSION_base(4,8,0)@@ -10,6 +10,7 @@ import Debian.Apt.Methods import Debian.Report import Debian.Sources+import Debian.TH (here) import Foreign.C.Types import System.Environment (getArgs, getProgName) import System.Exit (exitFailure)@@ -17,7 +18,6 @@ import Text.XML.HaXml.Pretty (document) import Text.XML.HaXml.Posn import Text.PrettyPrint.HughesPJ-import Text.Read (readMaybe) import System.IO import System.Posix.Env @@ -29,8 +29,8 @@ do (sourcesAFP, sourcesBFP) <- parseArgs let arch = "i386" -- not actually used for anything right now, could be when binary package list is enabled cacheDir = "." -- FIXME: replace with tempdir later- sourcesA <- liftM parseSourcesList $ readFile sourcesAFP- sourcesB <- liftM parseSourcesList $ readFile sourcesBFP+ sourcesA <- liftM (parseSourcesList [$here]) $ readFile sourcesAFP+ sourcesB <- liftM (parseSourcesList [$here]) $ readFile sourcesBFP trumpMap <- trumped (fetch emptyFetchCallbacks []) cacheDir arch sourcesA sourcesB print (showXML "trump.xsl" (trumpedXML trumpMap)) where@@ -96,7 +96,7 @@ do (cols, _) <- getWinSize case cols of -- 0 -> return . fmap read =<< getEnv "COLUMNS"- 0 -> either (\(e :: IOError) -> Nothing) (fmap read) <$> try (getEnv "COLUMNS")+ 0 -> either (\(_ :: IOError) -> Nothing) (fmap read) <$> try (getEnv "COLUMNS") _ -> return (Just cols)