Find the package category automatically
Some checks failed
Build / audit (push) Successful in 14m21s
Build / test (push) Failing after 5m36s

This commit is contained in:
Eugen Wissner 2024-02-17 14:15:01 +01:00
parent c8643a2fd4
commit 4c06ae274b
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
3 changed files with 87 additions and 68 deletions

View File

@ -6,7 +6,8 @@ module SlackBuilder.Package
( DownloadPlaceholder(..) ( DownloadPlaceholder(..)
, Download(..) , Download(..)
, DownloadTemplate(..) , DownloadTemplate(..)
, Package(..) , PackageDescription(..)
, PackageUpdateData(..)
, Maintainer(..) , Maintainer(..)
, Updater(..) , Updater(..)
, renderDownloadWithVersion , renderDownloadWithVersion
@ -23,13 +24,18 @@ import Control.Monad.Catch (MonadThrow)
import Data.Map (Map) import Data.Map (Map)
-- | Contains information how a package can be updated. -- | Contains information how a package can be updated.
data Package = Package data PackageDescription = PackageDescription
{ latest :: Updater { latest :: Updater
, downloaders :: Map Text Updater , downloaders :: Map Text Updater
, category :: Text
, name :: Text , name :: Text
} }
data PackageUpdateData = PackageUpdateData
{ description :: PackageDescription
, category :: Text
, version :: Text
}
-- | Download URI with the MD5 checksum of the target. -- | Download URI with the MD5 checksum of the target.
data Download = Download data Download = Download
{ download :: URI { download :: URI

View File

@ -26,13 +26,22 @@ import qualified Data.Text as Text
import qualified Data.Text.IO as Text.IO import qualified Data.Text.IO as Text.IO
import Control.Monad.Trans.Reader (ReaderT(..), asks) import Control.Monad.Trans.Reader (ReaderT(..), asks)
import SlackBuilder.Download import SlackBuilder.Download
import SlackBuilder.Package (Package(..)) import SlackBuilder.Package (PackageDescription(..), PackageUpdateData(..))
import qualified SlackBuilder.Package as Package import qualified SlackBuilder.Package as Package
import Text.URI (URI(..), mkURI) import Text.URI (URI(..), mkURI)
import Text.URI.QQ (uri) import Text.URI.QQ (uri)
import Data.Foldable (Foldable(..), for_, find) import Data.Foldable (Foldable(..), for_, find)
import qualified Text.URI as URI import qualified Text.URI as URI
import System.FilePath ((</>), (<.>), dropExtension, takeBaseName, makeRelative, splitFileName, takeDirectory, takeFileName) import System.FilePath
( (</>)
, (<.>)
, dropExtension
, takeBaseName
, splitFileName
, takeDirectory
, takeFileName
, dropTrailingPathSeparator
)
import SlackBuilder.Info import SlackBuilder.Info
import Text.Megaparsec (parse, errorBundlePretty) import Text.Megaparsec (parse, errorBundlePretty)
import GHC.Records (HasField(..)) import GHC.Records (HasField(..))
@ -55,10 +64,12 @@ import System.Directory (listDirectory, doesDirectoryExist, withCurrentDirectory
import Control.Monad (filterM) import Control.Monad (filterM)
import Data.List (isPrefixOf, isSuffixOf, partition) import Data.List (isPrefixOf, isSuffixOf, partition)
import Conduit (runConduitRes, (.|), sourceFile) import Conduit (runConduitRes, (.|), sourceFile)
import Data.Functor ((<&>))
import Data.Bifunctor (Bifunctor(..))
autoUpdatable :: [Package] autoUpdatable :: [PackageDescription]
autoUpdatable = autoUpdatable =
[ Package [ PackageDescription
{ latest = { latest =
let ghArguments = PackageOwner{ owner = "universal-ctags", name = "ctags" } let ghArguments = PackageOwner{ owner = "universal-ctags", name = "ctags" }
templateTail = templateTail =
@ -75,11 +86,10 @@ autoUpdatable =
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = False , is64 = False
} }
, category = "development"
, name = "universal-ctags" , name = "universal-ctags"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let packagistArguments = PackageOwner{ owner = "composer", name = "composer" } let packagistArguments = PackageOwner{ owner = "composer", name = "composer" }
template = Package.DownloadTemplate template = Package.DownloadTemplate
@ -90,11 +100,10 @@ autoUpdatable =
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = False , is64 = False
} }
, category = "development"
, name = "composer" , name = "composer"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let ghArguments = PackageOwner let ghArguments = PackageOwner
{ owner = "jitsi" { owner = "jitsi"
@ -109,11 +118,10 @@ autoUpdatable =
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = True , is64 = True
} }
, category = "network"
, name = "jitsi-meet-desktop" , name = "jitsi-meet-desktop"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let ghArguments = PackageOwner let ghArguments = PackageOwner
{ owner = "php" { owner = "php"
@ -132,11 +140,10 @@ autoUpdatable =
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = False , is64 = False
} }
, category = "development"
, name = "php82" , name = "php82"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let ghArguments = PackageOwner let ghArguments = PackageOwner
{ owner = "kovidgoyal" { owner = "kovidgoyal"
@ -156,11 +163,10 @@ autoUpdatable =
, getVersion = reuploadWithTemplate template [RawCommand "go" ["mod", "vendor"]] , getVersion = reuploadWithTemplate template [RawCommand "go" ["mod", "vendor"]]
, is64 = False , is64 = False
} }
, category = "system"
, name = "kitty" , name = "kitty"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let ghArguments = PackageOwner let ghArguments = PackageOwner
{ owner = "rdiff-backup" { owner = "rdiff-backup"
@ -177,11 +183,10 @@ autoUpdatable =
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = False , is64 = False
} }
, category = "system"
, name = "rdiff-backup" , name = "rdiff-backup"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let needle = "Linux—" let needle = "Linux—"
textArguments = TextArguments textArguments = TextArguments
@ -199,11 +204,10 @@ autoUpdatable =
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = True , is64 = True
} }
, category = "network"
, name = "webex" , name = "webex"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let ghArguments = PackageOwner let ghArguments = PackageOwner
{ owner = "librsync" { owner = "librsync"
@ -220,11 +224,10 @@ autoUpdatable =
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = True , is64 = True
} }
, category = "libraries"
, name = "librsync" , name = "librsync"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let textArguments = TextArguments let textArguments = TextArguments
{ textURL = "https://downloads.dlang.org/releases/LATEST" { textURL = "https://downloads.dlang.org/releases/LATEST"
@ -241,11 +244,10 @@ autoUpdatable =
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = False , is64 = False
} }
, category = "development"
, name = "dmd" , name = "dmd"
, downloaders = mempty , downloaders = mempty
} }
, Package , PackageDescription
{ latest = { latest =
let textArguments = TextArguments let textArguments = TextArguments
{ textURL = "https://downloads.dlang.org/releases/LATEST" { textURL = "https://downloads.dlang.org/releases/LATEST"
@ -259,7 +261,6 @@ autoUpdatable =
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = False , is64 = False
} }
, category = "development"
, name = "d-tools" , name = "d-tools"
, downloaders = , downloaders =
let dubArguments = PackageOwner{ owner = "dlang", name = "dub" } let dubArguments = PackageOwner{ owner = "dlang", name = "dub" }
@ -302,24 +303,31 @@ up2Date = \case
| otherwise -> throwM $ UpdaterNotFound packageName | otherwise -> throwM $ UpdaterNotFound packageName
where where
go package = getAndLogLatest package go package = getAndLogLatest package
>>= mapM_ (updatePackageIfRequired package) >>= mapM_ updatePackageIfRequired
>> liftIO (putStrLn "") >> liftIO (putStrLn "")
check :: SlackBuilderT () check :: SlackBuilderT ()
check = for_ autoUpdatable go check = for_ autoUpdatable go
where where
go package = getAndLogLatest package go package = getAndLogLatest package
>>= mapM_ (checkUpdateAvailability package) >>= mapM_ checkUpdateAvailability
>> liftIO (putStrLn "") >> liftIO (putStrLn "")
getAndLogLatest :: Package -> SlackBuilderT (Maybe Text) getAndLogLatest :: PackageDescription -> SlackBuilderT (Maybe PackageUpdateData)
getAndLogLatest Package{ latest = Package.Updater{ detectLatest }, name } getAndLogLatest description = do
= liftIO (putStrLn $ Text.unpack name <> ": Retreiving the latest version.") let PackageDescription{ latest = Package.Updater{ detectLatest }, name } = description
>> detectLatest liftIO (putStrLn $ Text.unpack name <> ": Retreiving the latest version.")
detectedVersion <- detectLatest
category <- fmap Text.pack
<$> findCategory (Text.unpack name)
pure $ PackageUpdateData description
<$> category
<*> detectedVersion
checkUpdateAvailability :: Package -> Text -> SlackBuilderT (Maybe PackageInfo) checkUpdateAvailability :: PackageUpdateData -> SlackBuilderT (Maybe PackageInfo)
checkUpdateAvailability Package{..} version = do checkUpdateAvailability PackageUpdateData{..} = do
let packagePath = Text.unpack category </> Text.unpack name </> (Text.unpack name <.> "info") let name' = Text.unpack $ getField @"name" description
packagePath = Text.unpack category </> name' </> (name' <.> "info")
repository' <- SlackBuilderT $ asks repository repository' <- SlackBuilderT $ asks repository
infoContents <- liftIO $ ByteString.readFile $ repository' </> packagePath infoContents <- liftIO $ ByteString.readFile $ repository' </> packagePath
@ -329,7 +337,7 @@ checkUpdateAvailability Package{..} version = do
liftIO $ do liftIO $ do
setSGR [SetColor Foreground Dull Green] setSGR [SetColor Foreground Dull Green]
Text.IO.putStrLn Text.IO.putStrLn
$ name <> " is up to date (Version " <> version <> ")." $ getField @"name" description <> " is up to date (Version " <> version <> ")."
setSGR [Reset] setSGR [Reset]
pure Nothing pure Nothing
| otherwise -> | otherwise ->
@ -337,7 +345,8 @@ checkUpdateAvailability Package{..} version = do
setSGR [SetColor Foreground Dull Yellow] setSGR [SetColor Foreground Dull Yellow]
Text.IO.putStr Text.IO.putStr
$ "A new version of " $ "A new version of "
<> name <> " " <> getField @"version" parsedInfoFile <> getField @"name" description
<> " " <> getField @"version" parsedInfoFile
<> " is available (" <> version <> ")." <> " is available (" <> version <> ")."
setSGR [Reset] setSGR [Reset]
putStrLn "" putStrLn ""
@ -345,10 +354,10 @@ checkUpdateAvailability Package{..} version = do
Left errorBundle -> liftIO (putStr $ errorBundlePretty errorBundle) Left errorBundle -> liftIO (putStr $ errorBundlePretty errorBundle)
>> pure Nothing >> pure Nothing
updatePackageIfRequired :: Package -> Text -> SlackBuilderT () updatePackageIfRequired :: PackageUpdateData -> SlackBuilderT ()
updatePackageIfRequired package version updatePackageIfRequired updateData
= checkUpdateAvailability package version = checkUpdateAvailability updateData
>>= mapM_ (updatePackage package version) >>= mapM_ (updatePackage updateData)
data DownloadUpdated = DownloadUpdated data DownloadUpdated = DownloadUpdated
{ result :: Package.Download { result :: Package.Download
@ -443,19 +452,21 @@ reuploadWithTemplate downloadTemplate commands packagePath version = do
, child_group = Nothing , child_group = Nothing
} }
updatePackage :: Package -> Text -> PackageInfo -> SlackBuilderT () updatePackage :: PackageUpdateData -> PackageInfo -> SlackBuilderT ()
updatePackage Package{..} version info = do updatePackage PackageUpdateData{..} info = do
let packagePath = category <> "/" <> name let packagePath = category <> "/" <> getField @"name" description
latest' = getField @"latest" description
repository' <- SlackBuilderT $ asks repository repository' <- SlackBuilderT $ asks repository
mainDownload <- (, getField @"is64" latest) mainDownload <- (, getField @"is64" latest')
<$> getField @"getVersion" latest packagePath version <$> getField @"getVersion" latest' packagePath version
moreDownloads <- traverse (updateDownload packagePath) downloaders moreDownloads <- traverse (updateDownload packagePath)
$ getField @"downloaders" description
let (downloads64, allDownloads) = partition snd let (downloads64, allDownloads) = partition snd
$ mainDownload $ mainDownload
: (liftA2 (,) (getField @"result") (getField @"is64") <$> toList moreDownloads) : (liftA2 (,) (getField @"result") (getField @"is64") <$> toList moreDownloads)
let infoFilePath = repository' </> Text.unpack packagePath let infoFilePath = repository' </> Text.unpack packagePath
</> (Text.unpack name <.> "info") </> (Text.unpack (getField @"name" description) <.> "info")
package' = info package' = info
{ version = version { version = version
, downloads = getField @"download" . fst <$> allDownloads , downloads = getField @"download" . fst <$> allDownloads
@ -469,16 +480,26 @@ updatePackage Package{..} version info = do
commit packagePath version commit packagePath version
findCategory :: FilePath -> IO [FilePath] findCategory :: FilePath -> SlackBuilderT (Maybe FilePath)
findCategory currentDirectory = do findCategory packageName = do
contents <- liftIO $ listDirectory currentDirectory repository' <- SlackBuilderT $ asks repository
case find (isSuffixOf ".info") contents of go repository' [] "" <&> fmap fst . find ((packageName ==) . snd)
Just _ -> pure [currentDirectory] where
Nothing -> do go currentDirectory found accumulatedDirectory = do
let contents' = (currentDirectory </>) <$> filter (not . isPrefixOf ".") contents let fullDirectory = currentDirectory </> accumulatedDirectory
directories <- filterM doesDirectoryExist contents' contents <- liftIO $ listDirectory fullDirectory
subCategories <- traverse findCategory directories case find (isSuffixOf ".info") contents of
pure $ concat subCategories Just _ ->
let result = first dropTrailingPathSeparator
$ splitFileName accumulatedDirectory
in pure $ result : found
Nothing ->
let accumulatedDirectories = (accumulatedDirectory </>)
<$> filter (not . isPrefixOf ".") contents
directoryFilter = liftIO . doesDirectoryExist
. (currentDirectory </>)
in filterM directoryFilter accumulatedDirectories
>>= traverse (go currentDirectory found) <&> concat
main :: IO () main :: IO ()
main = do main = do
@ -491,10 +512,5 @@ main = do
maybe (pure ()) Text.IO.putStrLn latestVersion maybe (pure ()) Text.IO.putStrLn latestVersion
where where
executeCommand = \case executeCommand = \case
CategoryCommand -> do
repository' <- SlackBuilderT $ asks repository
categories <- liftIO $ findCategory repository'
liftIO $ print $ splitFileName . makeRelative repository' <$> categories
pure Nothing
CheckCommand -> check >> pure Nothing CheckCommand -> check >> pure Nothing
Up2DateCommand packageName -> up2Date packageName >> pure Nothing Up2DateCommand packageName -> up2Date packageName >> pure Nothing

View File

@ -23,8 +23,7 @@ import Options.Applicative
) )
data SlackBuilderCommand data SlackBuilderCommand
= CategoryCommand = CheckCommand
| CheckCommand
| Up2DateCommand (Maybe Text) | Up2DateCommand (Maybe Text)
slackBuilderParser :: ParserInfo SlackBuilderCommand slackBuilderParser :: ParserInfo SlackBuilderCommand
@ -32,11 +31,9 @@ slackBuilderParser = info slackBuilderCommand fullDesc
slackBuilderCommand :: Parser SlackBuilderCommand slackBuilderCommand :: Parser SlackBuilderCommand
slackBuilderCommand = subparser slackBuilderCommand = subparser
$ command "category" (info categoryCommand mempty) $ command "check" (info checkCommand mempty)
<> command "check" (info checkCommand mempty)
<> command "up2date" (info up2DateCommand mempty) <> command "up2date" (info up2DateCommand mempty)
where where
categoryCommand = pure CategoryCommand
checkCommand = pure CheckCommand checkCommand = pure CheckCommand
up2DateCommand = Up2DateCommand up2DateCommand = Up2DateCommand
<$> optional (argument str (metavar "PKGNAM")) <$> optional (argument str (metavar "PKGNAM"))