Add module with an info file parser

This commit is contained in:
Eugen Wissner 2023-10-03 18:53:41 +02:00
parent f4b7883cf2
commit d5df676df7
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
7 changed files with 325 additions and 87 deletions

View File

@ -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

View File

@ -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

View File

@ -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)

156
lib/SlackBuilder/Info.hs Normal file
View File

@ -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 " "

View File

@ -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)

View File

@ -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

View File

@ -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