Find the package category automatically
This commit is contained in:
parent
c8643a2fd4
commit
4c06ae274b
@ -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
|
||||||
|
136
src/Main.hs
136
src/Main.hs
@ -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
|
||||||
|
@ -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"))
|
||||||
|
Loading…
Reference in New Issue
Block a user