packages feed

fquery-0.2.2: Adelie/QChangelog.hs

-- QChangelog.hs
--
-- Module to find the changelog of a package.

module Adelie.QChangelog (
  qChangelog,
  qLogFile
) where

import Data.Char (isDigit, isSpace)
import Control.Monad (unless)

import Adelie.Colour
import Adelie.CompareVersion
import Adelie.ListEx
import Adelie.Portage
import Adelie.Pretty

----------------------------------------------------------------

qLogFile :: [String] -> IO ()
qLogFile args = mapM_ (putStrLn.logFile) =<< findInstalledPackages args

logFile :: (String, String) -> String
logFile (cat, name) = 
  portageTree ++ '/':cat ++ '/':dropVersion name ++ "/ChangeLog"

----------------------------------------------------------------

qChangelog :: [String] -> IO ()
qChangelog []       = return ()
qChangelog [x]      = mapM_ (changelog Nothing)  =<< findInstalledPackages [x]
qChangelog (x:y:_)  = mapM_ (changelog (Just y)) =<< findInstalledPackages [x]

changelog :: Maybe String -> (String, String) -> IO ()
changelog end catname@(_, name) = do
  putStr "ChangeLog since " >> putCatNameLn catname
  log' <- readFile (logFile catname)
  puts name end log'

----------------------------------------------------------------

puts :: String -> Maybe String -> String -> IO ()

puts _ _ [] = return ()
puts inst end ('#':l) = puts inst end (dropUntilAfter isNewLine l)

puts inst end ('*':l0) = do
  case maybeCompareVersion package end of
    GT -> puts inst end next
    _  -> case compareVersion inst package of
      LT -> putSection package date >> putDesc ls >> puts inst end next
      _  -> puts inst end next
  where (section, next) = breakSection l0
        (line, ls) = break2 isNewLine section
        (l1, l2) = break2 ('(' ==) line
        package = takeWhile (not.isSpace) l1
        date = takeWhile (')' /=) l2

puts inst end (_:ls) = puts inst end ls

maybeCompareVersion :: String -> Maybe String -> Ordering
maybeCompareVersion _ Nothing   = LT
maybeCompareVersion a (Just b)  = compareVersion a b

----------------------------------------------------------------

breakSection :: String -> (String, String)
breakSection str = (reverse h, t)
  where (h, t) = breakSection' [] str

breakSection' :: String -> String -> (String, String)
breakSection' acc [] = (acc, [])
breakSection' acc nx@('\n':'*':_) = (acc, nx)
breakSection' acc (x:xs) = breakSection' (x:acc) xs

----------------------------------------------------------------

putSection :: String -> String -> IO ()
putSection package date = do
  putStr "* " >> inYellow (putStr package) 
  putStr " (" >> inWhite (putStr date) >> putStrLn ")"

----------------------------------------------------------------

putDesc :: String -> IO ()
putDesc [] = return ()
putDesc str = mapM_ putDesc' ls
  where ls = doublelines (dropWhile isNewLine str)

putDesc' :: String -> IO ()
putDesc' str = do
  if beginsWithDate (dropWhile isSpace str)
    then do
      putHeader header
      putBody body
    else
      putBody str
  where (header, body) = break2 (':' ==) str

doublelines :: String -> [String]
doublelines str =
  case t of
    []           -> h : []
    (_:'\n':xs)  -> h : doublelines xs
    (_:xs)       -> let (l0:l1) = doublelines xs 
                    in (h ++ ('\n' : l0)) : l1
  where (h, t) = break ('\n' ==) str

beginsWithDate :: String -> Bool
beginsWithDate []   = False
beginsWithDate [_]  = False
beginsWithDate (x:y:_)
  | not (isDigit x)         = False
  | not (isDigitOrSpace y)  = False
  | otherwise = True

----------------------------------------------------------------

putHeader :: String -> IO ()
putHeader str0 = do
  putChar '\n'
  inWhite (putStr date)
  unless (null name) (putStr name)
  red >> putStr ('<':mail) >> putChar '>' >> off2
  putStr "    " >> putFiles 76 files
  where
    (date, str1) = break2 (';' ==) str0
    (name, str2) = break2 ('<' ==) str1
    (mail, str3) = break2 ('>' ==) str2
    files = words str3

putFiles :: Int -> [String] -> IO ()
putFiles _ [] = putChar '\n'
putFiles rem' (";":files) = putFiles rem' files
putFiles rem' (f:files) =
  if rem' < len
    then do
      putChar '\n' >> putStr "    "
      putFiles 76 (f:files)
    else do
      cyan
      if last f == ','
        then putStr (dropTail 1 f) >> off >> putStr ", "
        else putStr f >> off >> putChar ' '
      putFiles (rem' - len - 1) files
  where len = length f

----------------------------------------------------------------

putBody :: String -> IO ()
putBody [] = putChar '\n'

putBody ('#':c0) =
  case bug of
    [] -> putChar '#' >> putBody cs
    _  -> inMagenta (putStr ('#':bug)) >> putBody cs
  where (bug, cs) = span isDigitOrSpace c0

putBody (c:cs) = putChar c >> putBody cs

----------------------------------------------------------------

isNewLine :: Char -> Bool
isNewLine '\n'  = True
isNewLine _     = False

isDigitOrSpace :: Char -> Bool
isDigitOrSpace x = isDigit x || isSpace x