slackbuilder/lib/SlackBuilder/Info.hs

167 lines
5.9 KiB
Haskell
Raw Normal View History

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
, 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)
import Text.Megaparsec (Parsec, count, eof, parse, takeWhile1P)
2024-11-27 22:41:03 +01:00
import Text.Megaparsec.Byte (hspace1, space, string, hexDigitChar)
2023-10-03 18:53:41 +02:00
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
import SlackBuilder.Trans
( SlackBuilderT(..)
, SlackBuilderException(..)
, relativeToRepository
)
import System.FilePath ((</>), (<.>))
import Control.Monad.IO.Class (MonadIO(..))
import Conduit (MonadThrow(throwM))
2024-11-27 22:41:03 +01:00
import Control.Monad (void)
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 ()
2024-11-27 22:41:03 +01:00
variableSeparator = void $ some $ hspace1 <|> void (string "\\\n")
2023-10-03 18:53:41 +02:00
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
2024-11-27 22:41:03 +01:00
|| x == Word8._period
2023-10-04 22:36:19 +02:00
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
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 " "