2023-12-23 22:15:10 +01:00
|
|
|
{- This Source Code Form is subject to the terms of the Mozilla Public License,
|
|
|
|
v. 2.0. If a copy of the MPL was not distributed with this file, You can
|
|
|
|
obtain one at https://mozilla.org/MPL/2.0/. -}
|
|
|
|
|
2023-12-12 18:51:44 +01:00
|
|
|
-- | Info file parsing and printing.
|
2023-10-03 18:53:41 +02:00
|
|
|
module SlackBuilder.Info
|
|
|
|
( PackageInfo(..)
|
|
|
|
, generate
|
|
|
|
, parseInfoFile
|
2024-11-25 17:08:28 +01:00
|
|
|
, readInfoFile
|
2023-10-03 18:53:41 +02:00
|
|
|
) where
|
|
|
|
|
2023-10-04 22:36:19 +02:00
|
|
|
import Control.Applicative (Alternative(..))
|
2023-10-03 18:53:41 +02:00
|
|
|
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 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)
|
2024-11-25 17:08:28 +01:00
|
|
|
import Text.Megaparsec (Parsec, count, eof, parse, takeWhile1P)
|
2023-10-03 18:53:41 +02:00
|
|
|
import Text.Megaparsec.Byte (space, string, hexDigitChar)
|
|
|
|
import Text.URI
|
2023-12-11 08:14:55 +01:00
|
|
|
( URI(..)
|
2023-10-03 18:53:41 +02:00
|
|
|
, parserBs
|
|
|
|
, render
|
|
|
|
)
|
2023-10-04 22:36:19 +02:00
|
|
|
import qualified Data.Word8 as Word8
|
2024-11-25 17:08:28 +01:00
|
|
|
import SlackBuilder.Trans
|
|
|
|
( SlackBuilderT(..)
|
|
|
|
, SlackBuilderException(..)
|
|
|
|
, relativeToRepository
|
|
|
|
)
|
|
|
|
import System.FilePath ((</>), (<.>))
|
|
|
|
import Control.Monad.IO.Class (MonadIO(..))
|
|
|
|
import Conduit (MonadThrow(throwM))
|
2023-10-03 18:53:41 +02:00
|
|
|
|
|
|
|
type GenParser = Parsec Void ByteString
|
|
|
|
|
2023-12-11 08:14:55 +01:00
|
|
|
-- | Data used to generate an .info file.
|
2023-10-03 18:53:41 +02:00
|
|
|
data PackageInfo = PackageInfo
|
|
|
|
{ pkgname :: String
|
|
|
|
, version :: Text
|
|
|
|
, homepage :: Text
|
|
|
|
, downloads :: [URI]
|
|
|
|
, checksums :: [Digest MD5]
|
2023-10-04 22:36:19 +02:00
|
|
|
, downloadX64 :: [URI]
|
|
|
|
, checksumX64 :: [Digest MD5]
|
|
|
|
, requires :: [ByteString]
|
|
|
|
, maintainer :: Text
|
|
|
|
, email :: Text
|
2023-10-03 18:53:41 +02:00
|
|
|
} deriving (Eq, Show)
|
|
|
|
|
|
|
|
variableEntry :: ByteString -> GenParser ByteString
|
|
|
|
variableEntry variable = string (Char8.append variable "=\"")
|
|
|
|
*> takeWhile1P Nothing (0x22 /=)
|
|
|
|
<* string "\"\n"
|
|
|
|
|
|
|
|
variableSeparator :: GenParser ()
|
|
|
|
variableSeparator = string " \\" *> space
|
|
|
|
|
2023-10-04 22:36:19 +02:00
|
|
|
packageDownloads :: ByteString -> GenParser [URI]
|
|
|
|
packageDownloads variableName = string (variableName <> "=\"")
|
2023-10-03 18:53:41 +02:00
|
|
|
*> sepBy parserBs variableSeparator
|
|
|
|
<* string "\"\n"
|
|
|
|
|
|
|
|
hexDigit :: GenParser Word8
|
2024-08-11 13:00:01 +02:00
|
|
|
hexDigit = count 2 hexDigitChar
|
|
|
|
>>= extractNumber . readHex . fmap (toEnum . fromIntegral)
|
|
|
|
where
|
|
|
|
extractNumber [(number, "")] = pure number
|
|
|
|
extractNumber _ = fail "Unable to convert a 2-digit hexadecimal number"
|
2023-10-03 18:53:41 +02:00
|
|
|
|
|
|
|
packageChecksum :: GenParser ByteString
|
|
|
|
packageChecksum = ByteString.pack <$> count 16 hexDigit
|
|
|
|
|
2023-10-04 22:36:19 +02:00
|
|
|
packageChecksums :: ByteString -> GenParser [ByteString]
|
|
|
|
packageChecksums variableName = string (variableName <> "=\"")
|
2023-10-03 18:53:41 +02:00
|
|
|
*> sepBy packageChecksum variableSeparator
|
|
|
|
<* string "\"\n"
|
|
|
|
|
2023-10-04 22:36:19 +02:00
|
|
|
packageRequires :: GenParser [ByteString]
|
|
|
|
packageRequires = string "REQUIRES=\""
|
2023-10-05 19:24:42 +02:00
|
|
|
*> sepBy (packageName <|> string "%README%") space
|
2023-10-04 22:36:19 +02:00
|
|
|
<* string "\"\n"
|
|
|
|
|
|
|
|
packageName :: GenParser ByteString
|
|
|
|
packageName = takeWhile1P Nothing isNameToken
|
|
|
|
where
|
|
|
|
isNameToken x = Word8.isAlphaNum x
|
|
|
|
|| x == Word8._hyphen
|
|
|
|
|| x == Word8._underscore
|
|
|
|
|
2023-10-03 18:53:41 +02:00
|
|
|
parseInfoFile :: GenParser PackageInfo
|
2024-03-06 13:40:36 +01:00
|
|
|
parseInfoFile = PackageInfo . Char8.unpack
|
|
|
|
<$> packagePrgnam
|
2023-10-03 18:53:41 +02:00
|
|
|
<*> (Text.decodeUtf8 <$> variableEntry "VERSION")
|
|
|
|
<*> (Text.decodeUtf8 <$> variableEntry "HOMEPAGE")
|
2023-10-04 22:36:19 +02:00
|
|
|
<*> packageDownloads "DOWNLOAD"
|
|
|
|
<*> (mapMaybe digestFromByteString <$> packageChecksums "MD5SUM")
|
|
|
|
<*> packageDownloads "DOWNLOAD_x86_64"
|
|
|
|
<*> (mapMaybe digestFromByteString <$> packageChecksums "MD5SUM_x86_64")
|
|
|
|
<*> packageRequires
|
|
|
|
<*> (Text.decodeUtf8 <$> variableEntry "MAINTAINER")
|
|
|
|
<*> (Text.decodeUtf8 <$> variableEntry "EMAIL")
|
2023-10-03 18:53:41 +02:00
|
|
|
<* eof
|
2023-10-04 22:36:19 +02:00
|
|
|
where
|
|
|
|
packagePrgnam = (string "PKGNAM" <|> string "PRGNAM")
|
|
|
|
>> string "=\""
|
|
|
|
*> packageName
|
|
|
|
<* "\"\n"
|
2023-10-03 18:53:41 +02:00
|
|
|
|
2024-11-25 17:08:28 +01:00
|
|
|
readInfoFile :: Text -> Text -> SlackBuilderT PackageInfo
|
|
|
|
readInfoFile category packageName' = do
|
|
|
|
let packageName'' = Text.unpack packageName'
|
|
|
|
|
|
|
|
infoPath <- relativeToRepository
|
|
|
|
$ Text.unpack category </> packageName'' </> packageName'' <.> "info"
|
|
|
|
infoContents <- liftIO $ ByteString.readFile infoPath
|
|
|
|
|
|
|
|
either (throwM . MalformedInfoFile) pure
|
|
|
|
$ parse parseInfoFile infoPath infoContents
|
|
|
|
|
2023-10-03 18:53:41 +02:00
|
|
|
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
|
2023-10-05 19:24:42 +02:00
|
|
|
builder = "PRGNAM=\"" <> Text.Builder.fromString (pkgname pkg) <> "\"\n"
|
2023-10-03 18:53:41 +02:00
|
|
|
<> "VERSION=\"" <> Text.Builder.fromText (version pkg) <> "\"\n"
|
|
|
|
<> "HOMEPAGE=\"" <> Text.Builder.fromText (homepage pkg) <> "\"\n"
|
2023-12-12 18:51:44 +01:00
|
|
|
<> downloadEntry
|
2023-10-03 18:53:41 +02:00
|
|
|
<> generateMultiEntry "MD5SUM" (digestToText <$> checksums pkg)
|
2023-10-08 12:28:46 +02:00
|
|
|
<> generateMultiEntry "DOWNLOAD_x86_64" (render <$> downloadX64 pkg)
|
|
|
|
<> generateMultiEntry "MD5SUM_x86_64" (digestToText <$> checksumX64 pkg)
|
2023-10-05 19:24:42 +02:00
|
|
|
<> "REQUIRES=\"" <> fromByteStringWords (requires pkg) <> "\"\n"
|
|
|
|
<> "MAINTAINER=\"" <> Text.Builder.fromText (maintainer pkg) <> "\"\n"
|
|
|
|
<> "EMAIL=\"" <> Text.Builder.fromText (email pkg) <> "\"\n"
|
|
|
|
fromByteStringWords = Text.Builder.fromText
|
|
|
|
. Text.unwords . fmap Text.decodeUtf8
|
2023-12-12 18:51:44 +01:00
|
|
|
downloadEntry
|
|
|
|
| null $ downloads pkg
|
|
|
|
, not $ null $ downloadX64 pkg = "DOWNLOAD=\"UNSUPPORTED\"\n"
|
|
|
|
| otherwise = generateMultiEntry "DOWNLOAD" $ render <$> downloads pkg
|
2023-10-03 18:53:41 +02:00
|
|
|
|
|
|
|
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 " "
|