summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEugen Wissner <belka@caraus.de>2023-10-03 18:53:41 +0200
committerEugen Wissner <belka@caraus.de>2023-10-03 18:53:41 +0200
commitd5df676df7a9bea716fdbdaff455fa1ab57073ac (patch)
treea2109b89652e9ae64829d1e3f0d67ad01e921cb5
parentf4b7883cf2a6739ca9cd7b8fe94cfe11df3918bc (diff)
downloadslackbuilder-d5df676df7a9bea716fdbdaff455fa1ab57073ac.tar.gz
Add module with an info file parser
-rw-r--r--Rakefile61
-rw-r--r--app/Main.hs21
-rw-r--r--app/SlackBuilder/CommandLine.hs11
-rw-r--r--lib/SlackBuilder/Info.hs156
-rw-r--r--lib/up2date.rb14
-rw-r--r--slackbuilder.cabal10
-rw-r--r--tests/SlackBuilder/InfoSpec.hs139
7 files changed, 325 insertions, 87 deletions
diff --git a/Rakefile b/Rakefile
index 29f9aaf..4912b59 100644
--- a/Rakefile
+++ b/Rakefile
@@ -92,61 +92,6 @@ module SlackBuilder
end
end
- class UniversalCtags < Updater
- def update(version)
- package = create_package version
-
- uri = "https://github.com/universal-ctags/ctags/archive/#{version}/ctags-#{version}.tar.gz"
- tarball = "slackbuilds/development/universal-ctags/ctags-#{version}.tar.gz"
- checksum = SlackBuilder.download URI(uri), tarball
- download = "https://download.dlackware.com/hosted-sources/universal-ctags/ctags-#{version}.tar.gz"
-
- write_info package, downloads: [Download.new(download, checksum)]
- update_slackbuild_version 'development/universal-ctags', version
- sh 'scp', tarball, "#{CONFIG[:remote_path]}/universal-ctags"
-
- commit 'development/universal-ctags', version
- end
-
- private
-
- def create_package(version)
- Package.new 'development/universal-ctags',
- version: version,
- homepage: 'https://ctags.io/',
- requires: ['%README%']
- end
- end
-
- class Composer < Updater
- def update(version)
- package = Package.new 'development/composer',
- version: version,
- homepage: 'https://getcomposer.org/'
-
- uri = "https://getcomposer.org/download/#{version}/composer.phar"
- checksum = SlackBuilder.download URI(uri), 'slackbuilds/development/composer/composer.phar'
- write_info package, downloads: [Download.new(uri, checksum)]
- update_slackbuild_version 'development/composer', version
-
- commit 'development/composer', version
- end
- end
-
- class JitsiMeetDesktop < Updater
- def update(version)
- package = Package.new 'network/jitsi-meet-desktop',
- version: version,
- homepage: 'https://jitsi.org/'
- uri = "https://github.com/jitsi/jitsi-meet-electron/releases/download/v#{version}/jitsi-meet-x86_64.AppImage"
- checksum = SlackBuilder.download URI(uri), 'slackbuilds/network/jitsi-meet-desktop/jitsi-meet-x86_64.AppImage'
-
- write_info package, downloads: [Download.new(uri, checksum, is64: true)]
- update_slackbuild_version 'network/jitsi-meet-desktop', version
- commit 'network/jitsi-meet-desktop', version
- end
- end
-
class PHP < Updater
def update(version)
package = Package.new 'development/php82',
@@ -167,15 +112,9 @@ module SlackBuilder
end
AUTO_UPDATABLE = {
- 'universal-ctags' => [SlackBuilder::GitHub.new('universal-ctags', 'ctags'), SlackBuilder::UniversalCtags.new],
- 'composer' => [SlackBuilder::Packagist.new('composer', 'composer'), SlackBuilder::Composer.new],
'php82' => [SlackBuilder::GitHub.new('php', 'php-src', 'php'), SlackBuilder::PHP.new],
'rdiff-backup' => [SlackBuilder::GitHub.new('rdiff-backup', 'rdiff-backup', 'rdiff-backup')],
'librsync' => [SlackBuilder::GitHub.new('librsync', 'librsync')],
- 'jitsi-meet-desktop' => [
- SlackBuilder::GitHub.new('jitsi', 'jitsi-meet-electron'),
- SlackBuilder::JitsiMeetDesktop.new
- ],
'dmd' => [SlackBuilder::LatestText.new('https://downloads.dlang.org/releases/LATEST')]
}.freeze
diff --git a/app/Main.hs b/app/Main.hs
index c82a4e9..e8cd335 100644
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -70,6 +70,25 @@ autoUpdatable =
, requires = mempty
, reupload = False
}
+ , Package
+ { latest =
+ let ghArguments = GhArguments
+ { owner = "jitsi"
+ , name = "jitsi-meet-electron"
+ , transform = Nothing
+ }
+ latest' = latestGitHub ghArguments $ Text.stripPrefix "v"
+ template = Package.DownloadTemplate
+ $ Package.StaticPlaceholder "https://github.com/jitsi/jitsi-meet-electron/releases/download/v"
+ :| Package.VersionPlaceholder
+ : [Package.StaticPlaceholder "/jitsi-meet-x86_64.AppImage"]
+ in Package.Updater latest' template
+ , category = "network"
+ , name = "jitsi-meet-desktop"
+ , homepage = Just [uri|https://jitsi.org/|]
+ , requires = mempty
+ , reupload = False
+ }
]
up2Date :: SlackBuilderT ()
@@ -134,8 +153,6 @@ main = do
Text.IO.putStrLn $ fromMaybe "" latestVersion
where
executeCommand = \case
- PackagistCommand packagistArguments ->
- latestPackagist packagistArguments
TextCommand textArguments -> latestText textArguments
GhCommand ghArguments@GhArguments{ transform }
-> latestGitHub ghArguments $ chooseTransformFunction transform
diff --git a/app/SlackBuilder/CommandLine.hs b/app/SlackBuilder/CommandLine.hs
index 48881e2..8624cb8 100644
--- a/app/SlackBuilder/CommandLine.hs
+++ b/app/SlackBuilder/CommandLine.hs
@@ -20,8 +20,7 @@ import Options.Applicative
)
data SlackBuilderCommand
- = PackagistCommand PackagistArguments
- | TextCommand TextArguments
+ = TextCommand TextArguments
| GhCommand GhArguments
| SlackBuildCommand Text Text
| CommitCommand Text Text
@@ -47,11 +46,6 @@ data GhArguments = GhArguments
newtype TextArguments = TextArguments Text
deriving (Eq, Show)
-packagistArguments :: Parser PackagistArguments
-packagistArguments = PackagistArguments
- <$> argument str (metavar "VENDOR")
- <*> argument str (metavar"NAME")
-
textArguments :: Parser TextArguments
textArguments = TextArguments <$> argument str (metavar "URL")
@@ -66,8 +60,7 @@ slackBuilderParser = info slackBuilderCommand fullDesc
slackBuilderCommand :: Parser SlackBuilderCommand
slackBuilderCommand = subparser
- $ command "packagist" (info (PackagistCommand <$> packagistArguments) mempty)
- <> command "text" (info (TextCommand <$> textArguments) mempty)
+ $ command "text" (info (TextCommand <$> textArguments) mempty)
<> command "github" (info (GhCommand <$> ghArguments) mempty)
<> command "slackbuild" (info slackBuildCommand mempty)
<> command "commit" (info commitCommand mempty)
diff --git a/lib/SlackBuilder/Info.hs b/lib/SlackBuilder/Info.hs
new file mode 100644
index 0000000..bedbc48
--- /dev/null
+++ b/lib/SlackBuilder/Info.hs
@@ -0,0 +1,156 @@
+module SlackBuilder.Info
+ ( PackageInfo(..)
+ , generate
+ , parseInfoFile
+ , update
+ , updateDownloadVersion
+ ) where
+
+import Control.Monad.Combinators (sepBy)
+import qualified Data.ByteArray as ByteArray
+import Data.ByteString (ByteString)
+import qualified Data.ByteString as ByteString
+import qualified Data.ByteString.Char8 as Char8
+import qualified Data.List.NonEmpty as NonEmpty
+import Data.Maybe (mapMaybe)
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text.Lazy as Lazy.Text
+import qualified Data.Text.Lazy.Builder as Text.Builder
+import qualified Data.Text.Lazy.Builder as Text (Builder)
+import Crypto.Hash (Digest, MD5, digestFromByteString)
+import Data.Void (Void)
+import Data.Word (Word8)
+import Numeric (readHex, showHex)
+import Text.Megaparsec (Parsec, count, eof, takeWhile1P)
+import Text.Megaparsec.Byte (space, string, hexDigitChar)
+import Text.URI
+ ( Authority(..)
+ , URI(..)
+ , mkPathPiece
+ , parserBs
+ , render
+ , unRText
+ )
+
+type GenParser = Parsec Void ByteString
+
+data PackageInfo = PackageInfo
+ { pkgname :: String
+ , version :: Text
+ , homepage :: Text
+ , downloads :: [URI]
+ , checksums :: [Digest MD5]
+ } deriving (Eq, Show)
+
+variableEntry :: ByteString -> GenParser ByteString
+variableEntry variable = string (Char8.append variable "=\"")
+ *> takeWhile1P Nothing (0x22 /=)
+ <* string "\"\n"
+
+variableSeparator :: GenParser ()
+variableSeparator = string " \\" *> space
+
+packageDownloads :: GenParser [URI]
+packageDownloads = string "DOWNLOAD=\""
+ *> sepBy parserBs variableSeparator
+ <* string "\"\n"
+
+hexDigit :: GenParser Word8
+hexDigit =
+ let digitPair = count 2 hexDigitChar
+ in fst . head . readHex . fmap (toEnum . fromIntegral) <$> digitPair
+
+packageChecksum :: GenParser ByteString
+packageChecksum = ByteString.pack <$> count 16 hexDigit
+
+packageChecksums :: GenParser [ByteString]
+packageChecksums = string "MD5SUM=\""
+ *> sepBy packageChecksum variableSeparator
+ <* string "\"\n"
+
+parseInfoFile :: GenParser PackageInfo
+parseInfoFile = PackageInfo
+ <$> (Char8.unpack <$> variableEntry "PKGNAM")
+ <*> (Text.decodeUtf8 <$> variableEntry "VERSION")
+ <*> (Text.decodeUtf8 <$> variableEntry "HOMEPAGE")
+ <*> packageDownloads
+ <*> (mapMaybe digestFromByteString <$> packageChecksums)
+ <* eof
+
+updateDownloadVersion :: PackageInfo -> Text -> Maybe String -> [URI]
+updateDownloadVersion package toVersion gnomeVersion
+ = updateDownload (version package) toVersion gnomeVersion
+ <$> downloads package
+
+updateDownload :: Text -> Text -> Maybe String -> URI -> URI
+updateDownload fromVersion toVersion gnomeVersion
+ = updateCoreVersion fromVersion toVersion gnomeVersion
+ . updatePackageVersion fromVersion toVersion gnomeVersion
+
+updatePackageVersion :: Text -> Text -> Maybe String -> URI -> URI
+updatePackageVersion fromVersion toVersion _gnomeVersion download = download
+ { uriPath = uriPath download >>= traverse (traverse updatePathPiece)
+ }
+ where
+ updatePathPiece = mkPathPiece
+ . Text.replace fromMajor toMajor
+ . Text.replace fromVersion toVersion
+ . unRText
+ fromMajor = major fromVersion
+ toMajor = major toVersion
+
+major :: Text -> Text
+major = Text.intercalate "." . take 2 . Text.splitOn "."
+
+updateCoreVersion :: Text -> Text -> Maybe String -> URI -> URI
+updateCoreVersion _fromVersion _toVersion (Just gnomeVersion) download
+ | Just (False, pathPieces) <- uriPath download
+ , (beforeCore, afterCore) <- NonEmpty.break (comparePathPiece "core") pathPieces
+ , _ : _ : _ : sources : afterSources <- afterCore
+ , comparePathPiece "sources" sources && not (null afterSources)
+ , Right Authority{..} <- uriAuthority download
+ , ".gnome.org" `Text.isSuffixOf` unRText authHost
+ , Nothing <- authPort =
+ download { uriPath = buildPath beforeCore afterSources }
+ where
+ comparePathPiece this that = Just that == mkPathPiece this
+ buildPath beforeCore afterSources = do
+ core <- mkPathPiece "core"
+ let textGnomeVersion = Text.pack gnomeVersion
+ minorGnomeVersion <- mkPathPiece $ major textGnomeVersion
+ patchGnomeVersion <- mkPathPiece textGnomeVersion
+ sources <- mkPathPiece "sources"
+ let afterCore = core : minorGnomeVersion : patchGnomeVersion : sources : afterSources
+ (False,) <$> NonEmpty.nonEmpty (beforeCore ++ afterCore)
+updateCoreVersion _ _ _ download = download
+
+update :: PackageInfo -> Text -> [URI] -> [Digest MD5] -> PackageInfo
+update old toVersion downloads' checksums' = old
+ { version = toVersion
+ , downloads = downloads'
+ , checksums = checksums'
+ }
+
+generate :: PackageInfo -> Text
+generate pkg = Lazy.Text.toStrict $ Text.Builder.toLazyText builder
+ where
+ digestToText = Text.pack . foldr hexAppender "" . ByteArray.unpack
+ hexAppender x acc
+ | x > 15 = showHex x acc
+ | otherwise = '0' : showHex x acc
+ builder = "PKGNAM=\"" <> Text.Builder.fromString (pkgname pkg) <> "\"\n"
+ <> "VERSION=\"" <> Text.Builder.fromText (version pkg) <> "\"\n"
+ <> "HOMEPAGE=\"" <> Text.Builder.fromText (homepage pkg) <> "\"\n"
+ <> generateMultiEntry "DOWNLOAD" (render <$> downloads pkg)
+ <> generateMultiEntry "MD5SUM" (digestToText <$> checksums pkg)
+
+generateMultiEntry :: Text -> [Text] -> Text.Builder
+generateMultiEntry name entries = Text.Builder.fromText name
+ <> "=\""
+ <> Text.Builder.fromText (Text.intercalate separator entries)
+ <> "\"\n"
+ where
+ padLength = Text.length name + 2
+ separator = " \\\n" <> Text.replicate padLength " "
diff --git a/lib/up2date.rb b/lib/up2date.rb
index d9eab15..5d63da3 100644
--- a/lib/up2date.rb
+++ b/lib/up2date.rb
@@ -35,20 +35,6 @@ module SlackBuilder
end
end
- # Request the latest version from the packagist API.
- class Packagist < Repository
- def initialize(vendor, name)
- super()
-
- @vendor = vendor
- @name = name
- end
-
- def latest
- `./bin/slackbuilder packagist #{@vendor} #{@name}`.strip
- end
- end
-
# Reads a remote LATEST file.
class LatestText < Repository
def initialize(latest_url)
diff --git a/slackbuilder.cabal b/slackbuilder.cabal
index b56c39d..0a7cdb5 100644
--- a/slackbuilder.cabal
+++ b/slackbuilder.cabal
@@ -18,9 +18,13 @@ extra-source-files: CHANGELOG.md
common dependencies
build-depends:
base ^>= 4.16.4.0,
+ bytestring ^>= 0.11.0,
cryptonite >= 0.30,
filepath ^>= 1.4.2,
+ megaparsec ^>= 9.5,
modern-uri ^>= 0.3.6,
+ memory ^>= 0.18,
+ parser-combinators ^>= 1.3,
text ^>= 2.0,
tomland ^>= 1.3.3,
transformers ^>= 0.5.6
@@ -28,18 +32,21 @@ common dependencies
default-extensions:
DataKinds
DuplicateRecordFields
+ ExplicitForAll
LambdaCase
NamedFieldPuns
OverloadedStrings
RecordWildCards
QuasiQuotes
TemplateHaskell
+ TupleSections
TypeApplications
library
import: dependencies
exposed-modules:
SlackBuilder.Config
+ SlackBuilder.Info
SlackBuilder.Package
SlackBuilder.Trans
hs-source-dirs: lib
@@ -58,7 +65,6 @@ executable slackbuilder
SlackBuilder.Updater
build-depends:
aeson ^>= 2.2.0,
- bytestring ^>= 0.11.0,
conduit ^>= 1.3.5,
http-client ^>= 0.7,
optparse-applicative ^>= 0.18.1,
@@ -77,10 +83,12 @@ test-suite slackbuilder-test
main-is: Spec.hs
other-modules:
+ SlackBuilder.InfoSpec
SlackBuilder.PackageSpec
hs-source-dirs: tests
build-depends:
hspec >= 2.10.9 && < 2.12,
+ hspec-megaparsec ^>= 2.2,
slackbuilder
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall
diff --git a/tests/SlackBuilder/InfoSpec.hs b/tests/SlackBuilder/InfoSpec.hs
new file mode 100644
index 0000000..28167b5
--- /dev/null
+++ b/tests/SlackBuilder/InfoSpec.hs
@@ -0,0 +1,139 @@
+module SlackBuilder.InfoSpec
+ ( spec
+ ) where
+
+import Crypto.Hash (Digest, MD5, digestFromByteString)
+import qualified Data.ByteString as ByteString
+import Data.ByteString.Char8 (ByteString)
+import Data.Maybe (maybeToList)
+import qualified Data.Text.Encoding as Text
+import Data.Void (Void)
+import SlackBuilder.Info
+import Test.Hspec (Spec, describe, it, shouldBe)
+import Test.Hspec.Megaparsec (parseSatisfies, shouldSucceedOn)
+import Text.Megaparsec (parse)
+import Text.Megaparsec.Error (ParseErrorBundle)
+import Text.URI (mkURI)
+
+parseInfoFile'
+ :: ByteString
+ -> Either (ParseErrorBundle ByteString Void) PackageInfo
+parseInfoFile' = parse parseInfoFile ""
+
+infoDownload0 :: ByteString
+infoDownload0 = "PKGNAM=\"pkgnam\"\n\
+ \VERSION=\"1.2.3\"\n\
+ \HOMEPAGE=\"homepage\"\n\
+ \DOWNLOAD=\"\"\n\
+ \MD5SUM=\"\"\n"
+
+infoDownload1 :: ByteString
+infoDownload1 = "PKGNAM=\"pkgnam\"\n\
+ \VERSION=\"1.2.3\"\n\
+ \HOMEPAGE=\"homepage\"\n\
+ \DOWNLOAD=\"https://dlackware.com/download.tar.gz\"\n\
+ \MD5SUM=\"0102030405060708090a0b0c0d0e0f10\"\n"
+
+maybeToDoubleList :: forall a. Maybe a -> [a]
+maybeToDoubleList xs = [y | x <- maybeToList xs, y <- [x, x]]
+
+checksumSample :: [Digest MD5]
+checksumSample = maybeToList $ digestFromByteString (ByteString.pack [1 .. 16])
+
+spec :: Spec
+spec = do
+ describe "parseInfoFile" $ do
+ it "returns package on a valid input" $
+ parseInfoFile' `shouldSucceedOn` infoDownload1
+
+ it "returns an array with one element if one download is given" $
+ let condition = (== 1) . length . checksums
+ in parseInfoFile' infoDownload1 `parseSatisfies` condition
+
+ it "translates checksum characters into the binary format" $
+ let expected = "0102030405060708090a0b0c0d0e0f10"
+ condition = (== expected) . show . head . checksums
+ in parseInfoFile' infoDownload1 `parseSatisfies` condition
+
+ it "accepts an empty downloads list" $
+ parseInfoFile' `shouldSucceedOn` infoDownload0
+
+ describe "generate" $ do
+ it "generates an .info file without downloads" $
+ let given = PackageInfo "pkgnam" "1.2.3" "homepage" [] []
+ in generate given `shouldBe` Text.decodeUtf8 infoDownload0
+
+ it "splits multiple downloads into multiple lines" $
+ let downloads' = maybeToDoubleList
+ $ mkURI "https://dlackware.com/download.tar.gz"
+ checksums' = maybeToDoubleList
+ $ digestFromByteString (ByteString.pack [1.. 16])
+ given = PackageInfo
+ "pkgnam" "1.2.3" "homepage" downloads' checksums'
+ expected = "PKGNAM=\"pkgnam\"\n\
+ \VERSION=\"1.2.3\"\n\
+ \HOMEPAGE=\"homepage\"\n\
+ \DOWNLOAD=\"https://dlackware.com/download.tar.gz \\\n\
+ \ https://dlackware.com/download.tar.gz\"\n\
+ \MD5SUM=\"0102030405060708090a0b0c0d0e0f10 \\\n\
+ \ 0102030405060708090a0b0c0d0e0f10\"\n"
+ in generate given `shouldBe` expected
+
+ it "prints the checksum as a sequence of hexadecimal numbers" $
+ let downloads' = maybeToList
+ $ mkURI "https://dlackware.com/download.tar.gz"
+ given = PackageInfo
+ "pkgnam" "1.2.3" "homepage" downloads' checksumSample
+ in generate given `shouldBe` Text.decodeUtf8 infoDownload1
+
+ describe "updateDownloadVersion" $ do
+ it "replaces the version" $
+ let downloads' = maybeToList
+ $ mkURI "https://dlackware.com/download-1.2.3.tar.gz"
+ testPackage = PackageInfo
+ "pkgnam" "1.2.3" "homepage" downloads' checksumSample
+ expected = maybeToList
+ $ mkURI "https://dlackware.com/download-2.3.4.tar.gz"
+ actual = updateDownloadVersion testPackage "2.3.4" Nothing
+ in actual `shouldBe` expected
+
+ it "updates the major version" $
+ let downloads' = maybeToList
+ $ mkURI "https://dlackware.com/1.2/download.tar.gz"
+ testPackage = PackageInfo
+ "pkgnam" "1.2.3" "homepage" downloads' checksumSample
+ expected = maybeToList
+ $ mkURI "https://dlackware.com/2.3/download.tar.gz"
+ actual = updateDownloadVersion testPackage "2.3.4" Nothing
+ in actual `shouldBe` expected
+
+ it "updates gnome version" $
+ let downloads' = maybeToList
+ $ mkURI "https://download.gnome.org/core/3.36/3.36.0/sources/gnome-calendar-3.36.0.tar.xz"
+ testPackage = PackageInfo
+ "gnome-calendar" "3.36.0" "https://wiki.gnome.org/Core/Calendar" downloads' checksumSample
+ expected = maybeToList
+ $ mkURI "https://download.gnome.org/core/3.36/3.36.4/sources/gnome-calendar-3.36.2.tar.xz"
+ actual = updateDownloadVersion testPackage "3.36.2" $ Just "3.36.4"
+ in actual `shouldBe` expected
+
+ it "updates versions without a patch number" $
+ let downloads' = maybeToList
+ $ mkURI "https://dlackware.com/gnome-contacts-3.36.tar.xz"
+ testPackage = PackageInfo
+ "gnome-contacts" "3.36" "homepage" downloads' checksumSample
+ expected = maybeToList
+ $ mkURI "https://dlackware.com/gnome-contacts-3.36.2.tar.xz"
+ actual = updateDownloadVersion testPackage "3.36.2" Nothing
+ in actual `shouldBe` expected
+
+ describe "update" $
+ it "replaces the version" $
+ let downloads' = maybeToList
+ $ mkURI "https://dlackware.com/1.2/download.tar.gz"
+ testPackage = PackageInfo
+ "pkgnam" "1.2.3" "homepage" downloads' checksumSample
+ expected = PackageInfo
+ "pkgnam" "2.3.4" "homepage" downloads' checksumSample
+ given = update testPackage "2.3.4" downloads' checksumSample
+ in given `shouldBe` expected