packages feed

dnf-repo-0.7: src/Updates.hs

{-# LANGUAGE OverloadedStrings #-}

module Updates (printUpdates)
where

import Data.Aeson (FromJSON, eitherDecode', parseJSON, withObject, (.:))
import Data.List.Extra (intercalate, sortOn, trim)
import Data.Maybe (maybeToList)
import Data.String (fromString)
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import SimpleCmd (error', (+-+))
import System.Process.Typed (proc, readProcessStdout_)

printUpdates :: [String] -> IO ()
printUpdates args = do
  json <- readProcessStdout_ $ proc "sudo" $ ["dnf5", "-q", "advisory", "info", "--json"] ++ args
  case renderUpdates json of
    Left err -> error' $ "failed to parse advisory json:" +-+ err
    Right out -> putStr out
  where
    -- Compact "dnf5 advisory info --json"
    renderUpdates json =
      case eitherDecode' json of
        Right arr -> Right $ unlines $ map formatUpdate $ sortOn advName arr
        Left err -> Left err

-- FIXME use local time
formatUpdate :: Advisory -> String
formatUpdate adv =
  let name = advName adv
      title = "#" +-+ trim (advTitle adv)
      typ = advType adv
      sev = advSeverity adv
      date = show (advIssued adv)
      desc = trim $ advDescription adv
      refs = map formatRef (advReferences adv)
      prose = lines desc ++ refs
      body = map ("  " ++) prose
  in unlines $
     title :
     intercalate "  " ([' ' : name, typ] ++ maybeToList sev ++ [date]) : body
  where
    formatRef ref =
      refUrl ref +-+ ":" +-+ refTitle ref

data Advisory = Advisory {
  advName :: String,
  advTitle :: String,
  advSeverity :: Maybe String,
  advType :: String,
  advIssued :: UTCTime,
  advDescription :: String,
  advReferences :: [Reference]
  }

data Reference = Reference {
  refUrl :: String,
  refTitle :: String
  }

instance FromJSON Advisory where
  parseJSON =
    withObject "Advisory" $ \obj ->
      Advisory
      <$> obj .: "Name"
      <*> obj .: "Title"
      <*> (maybeSeverity <$> obj .: "Severity")
      <*> obj .: "Type"
      <*> (secondsToUTC <$> obj .: "Issued")
      <*> obj .: "Description"
      <*> obj .: "references"
    where
      maybeSeverity s =
        if s `elem` ["","None"]
        then Nothing
        else Just s

      secondsToUTC = posixSecondsToUTCTime . fromInteger

instance FromJSON Reference where
  parseJSON =
    withObject "Reference" $ \obj ->
      Reference
      <$> obj .: fromString "Url"
      <*> obj .: fromString "Title"