-- |Changelog and changes file support. module Debian.Changes ( ChangesFile(..) , ChangedFileSpec(..) , changesFileName , ChangeLogEntry(..) , parseLog , parseEntry , parseChanges ) where import Data.List (intercalate) import Data.Maybe import qualified Debian.Control.String as S import Debian.Release import Debian.URI() import Debian.Version import System.Posix.Types import Text.Regex -- |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 -- ^ 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 } -- |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 } -- |A changelog is a series of ChangeLogEntries data ChangeLogEntry = Entry { logPackage :: String , logVersion :: DebianVersion , logDists :: [ReleaseName] , logUrgency :: String , logComments :: String , logWho :: String , logDate :: String } instance Show ChangesFile where show = changesFileName changesFileName :: ChangesFile -> String changesFileName changes = changePackage changes ++ "_" ++ show (changeVersion changes) ++ "_" ++ archName (changeArch changes) ++ ".changes" instance Show ChangedFileSpec where show file = changedFileMD5sum file ++ " " ++ show (changedFileSize file) ++ " " ++ sectionName (changedFileSection file) ++ " " ++ changedFilePriority file ++ " " ++ changedFileName file instance Show ChangeLogEntry where show (Entry package version dists urgency details who date) = package ++ " (" ++ show version ++ ") " ++ intercalate " " (map releaseName' dists) ++ "; urgency=" ++ urgency ++ "\n\n" ++ details ++ " -- " ++ who ++ " " ++ date ++ "\n\n" -- |Show just the top line of a changelog entry (for debugging output.) showHeader :: ChangeLogEntry -> String showHeader (Entry package version dists urgency _ _ _) = package ++ " (" ++ show version ++ ") " ++ intercalate " " (map releaseName' dists) ++ "; urgency=" ++ urgency ++ "..." -- |Parse a Debian Changelog and return a lazy list of entries parseLog :: String -> [Either String ChangeLogEntry] parseLog text = case parseEntry text of Nothing -> [] Just (Left message) -> [Left message] Just (Right (entry, text')) -> Right entry : parseLog text' -- |Parse a single changelog entry, returning the entry and the remaining text. parseEntry :: String -> Maybe (Either String (ChangeLogEntry, String)) parseEntry text | dropWhile (\ x -> elem x " \t\n") text == "" = Nothing parseEntry text = case matchRegexAll entryRE text of Nothing -> Just (Left ("Parse error in changelog:\n" ++ text)) Just ("", _, remaining, [_, name, version, dists, urgency, _, details, _, _, _, _, _, who, date, _]) -> let entry = Entry name (parseDebianVersion version) (map parseReleaseName . words $ dists) urgency details who date in Just (Right (entry, remaining)) Just ("", _, _remaining, submatches) -> Just (Left ("Internal error 15, submatches=" ++ show submatches)) Just (before, _, _, _) -> Just (Left ("Parse error in changelog at:\n" ++ show before ++ "\nin:\n" ++ text)) where entryRE = mkRegex $ bol ++ blankLines ++ headerRE ++ nonSigLines ++ blankLines ++ signature ++ blankLines nonSigLines = "((( .*)|([ \t]*)\n)+)" -- In the debian repository, sometimes the extra space in front of the -- day-of-month is missing, sometimes an extra one is added. signature = "( -- ([^\n]*) (..., ? ?.. ... .... ........ .....))[ \t]*\n" -- |Parse the changelog information that shows up in the .changes -- file, i.e. a changelog entry with no signature. parseChanges :: String -> Maybe ChangeLogEntry parseChanges text = case matchRegex changesRE text of Nothing -> Nothing Just [_, name, version, dists, urgency, _, details] -> Just $ Entry name (parseDebianVersion version) (map parseReleaseName . words $ dists) urgency details "" "" Just x -> error $ "Unexpected match: " ++ show x where changesRE = mkRegexWithOpts (bol ++ blankLines ++ optWhite ++ headerRE ++ "(.*)$") False False headerRE = package ++ version ++ dists ++ urgency where package = "([^ \t(]*)" ++ optWhite version = "\\(([^)]*)\\)" ++ optWhite dists = "([^;]*);" ++ optWhite urgency = "urgency=([^\n]*)\n" ++ blankLines blankLines = blankLine ++ "*" blankLine = "(" ++ optWhite ++ "\n)" optWhite = "[ \t]*" bol = "^"