Extern tag matcher with digit and dot patterns
All checks were successful
Build / audit (push) Successful in 14m36s
Build / test (push) Successful in 15m5s

This commit is contained in:
Eugen Wissner 2024-03-24 13:20:22 +01:00
parent 7e59a8460d
commit 7c9c890056
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
3 changed files with 57 additions and 53 deletions

View File

@ -11,7 +11,6 @@ module SlackBuilder.LatestVersionCheck
, latestPackagist , latestPackagist
, latestText , latestText
, match , match
, stableTagTransform
) where ) where
import SlackBuilder.Config import SlackBuilder.Config
@ -47,22 +46,12 @@ import qualified Data.Aeson.KeyMap as KeyMap
import GHC.Records (HasField(..)) import GHC.Records (HasField(..))
import Control.Monad.Trans.Reader (asks) import Control.Monad.Trans.Reader (asks)
import Control.Monad.IO.Class (MonadIO(..)) import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad ((>=>))
data PackageOwner = PackageOwner data PackageOwner = PackageOwner
{ owner :: Text { owner :: Text
, name :: Text , name :: Text
} deriving (Eq, Show) } deriving (Eq, Show)
-- | Removes the leading "v" from the version string and returns the result if
-- it looks like a version.
stableTagTransform :: Text -> Maybe Text
stableTagTransform = Text.stripPrefix "v" >=> checkForStable
where
checkForStable tag
| Text.any (`elem` ['-', '+']) tag = Nothing
| otherwise = Just tag
data MatchState = MatchState data MatchState = MatchState
{ ignoring :: Bool { ignoring :: Bool
, matched :: Text , matched :: Text
@ -72,28 +61,43 @@ data MatchState = MatchState
data MatchToken data MatchToken
= OpenParenMatchToken = OpenParenMatchToken
| CloseParenMatchToken | CloseParenMatchToken
| AsteriskMatchToken (Maybe Char) | AsteriskMatchToken
| SymbolMatchToken Char | SymbolMatchToken Char
| OneOfMatchToken [Char]
deriving (Eq, Show) deriving (Eq, Show)
-- | Removes the leading "v" from the version string and returns the result if
-- it looks like a version.
match :: Text -> Text -> Maybe Text match :: Text -> Text -> Maybe Text
match fullPattern input = match fullPattern input =
case Text.foldl' go (Just startState) input of case Text.foldl' go (Just startState) input of
Just state@MatchState{ pattern' = [] } -> Just $ getField @"matched" state Just state@MatchState{ pattern' = [] } -> Just $ getField @"matched" state
Just state@MatchState{ pattern' = [CloseParenMatchToken] } -> Just state@MatchState{ pattern' = [CloseParenMatchToken] } ->
Just $ getField @"matched" state Just $ getField @"matched" state
Just state@MatchState{ pattern' = [AsteriskMatchToken _] } -> Just state@MatchState{ pattern' = [AsteriskMatchToken] } ->
Just $ getField @"matched" state
Just state@MatchState{ pattern' = [OneOfMatchToken _] } ->
Just $ getField @"matched" state Just $ getField @"matched" state
_ -> Nothing _ -> Nothing
where where
reserved = ['*', '(', ')'] digits = toEnum <$> [fromEnum '0' .. fromEnum '9']
parsePattern :: Text -> [MatchToken] parsePattern :: Text -> [MatchToken]
parsePattern input' parsePattern input'
| Just (firstChar, remaining) <- Text.uncons input'
, firstChar == '\\' =
case Text.uncons remaining of
Nothing -> []
Just ('d', remaining') -> OneOfMatchToken digits
: parsePattern remaining'
Just ('.', remaining') -> OneOfMatchToken ('.' : digits)
: parsePattern remaining'
Just ('\\', remaining') -> SymbolMatchToken '\\'
: parsePattern remaining'
Just (_, remaining') -> parsePattern remaining'
| Just (firstChar, remaining) <- Text.uncons input' = | Just (firstChar, remaining) <- Text.uncons input' =
let token = let token =
case firstChar of case firstChar of
'*' -> AsteriskMatchToken '*' -> AsteriskMatchToken
$ Text.find (not . (`elem` reserved)) remaining
'(' -> OpenParenMatchToken '(' -> OpenParenMatchToken
')' -> CloseParenMatchToken ')' -> CloseParenMatchToken
s -> SymbolMatchToken s s -> SymbolMatchToken s
@ -109,14 +113,16 @@ match fullPattern input =
case token of case token of
OpenParenMatchToken -> go (Just state{ ignoring = True, pattern' = remaining }) nextCharacter OpenParenMatchToken -> go (Just state{ ignoring = True, pattern' = remaining }) nextCharacter
CloseParenMatchToken -> go (Just state{ ignoring = False, pattern' = remaining }) nextCharacter CloseParenMatchToken -> go (Just state{ ignoring = False, pattern' = remaining }) nextCharacter
AsteriskMatchToken stopChar AsteriskMatchToken -> Just $ matchSymbolToken state nextCharacter
| Just nextCharacter == stopChar ->
go (Just state{ pattern' = remaining }) nextCharacter
| otherwise -> Just $ matchSymbolToken state nextCharacter
SymbolMatchToken patternCharacter SymbolMatchToken patternCharacter
| patternCharacter == nextCharacter -> Just | patternCharacter == nextCharacter -> Just
$ matchSymbolToken state{ pattern' = remaining } nextCharacter $ matchSymbolToken state{ pattern' = remaining } nextCharacter
| otherwise -> Nothing | otherwise -> Nothing
OneOfMatchToken chars
| nextCharacter `elem` chars ->
Just $ matchSymbolToken state nextCharacter
| otherwise ->
go (Just state{ pattern' = remaining }) nextCharacter
go _ _ = Nothing go _ _ = Nothing
matchSymbolToken state nextCharacter matchSymbolToken state nextCharacter
| getField @"ignoring" state = state | getField @"ignoring" state = state
@ -214,9 +220,9 @@ $(deriveJSON defaultOptions ''GhQuery)
latestGitHub latestGitHub
:: PackageOwner :: PackageOwner
-> (Text -> Maybe Text) -> Text
-> SlackBuilderT (Maybe Text) -> SlackBuilderT (Maybe Text)
latestGitHub PackageOwner{..} versionTransform = do latestGitHub PackageOwner{..} pattern' = do
ghToken' <- SlackBuilderT $ asks ghToken ghToken' <- SlackBuilderT $ asks ghToken
ghResponse <- runReq defaultHttpConfig $ ghResponse <- runReq defaultHttpConfig $
let uri = https "api.github.com" /: "graphql" let uri = https "api.github.com" /: "graphql"
@ -238,14 +244,13 @@ latestGitHub PackageOwner{..} versionTransform = do
$ responseBody ghResponse $ responseBody ghResponse
refs' = Vector.reverse refs' = Vector.reverse
$ Vector.catMaybes $ Vector.catMaybes
$ versionTransform . getField @"name" <$> ghNodes $ match pattern' . getField @"name" <$> ghNodes
liftIO $ print $ getField @"name" <$> ghNodes
pure $ refs' !? 0 pure $ refs' !? 0
where where
githubQuery = githubQuery =
"query ($name: String!, $owner: String!) {\n\ "query ($name: String!, $owner: String!) {\n\
\ repository(name: $name, owner: $owner) {\n\ \ repository(name: $name, owner: $owner) {\n\
\ refs(last: 10, query: \"!(RC3)\", refPrefix: \"refs/tags/\", orderBy: { field: TAG_COMMIT_DATE, direction: ASC }) {\n\ \ refs(last: 10, refPrefix: \"refs/tags/\", orderBy: { field: TAG_COMMIT_DATE, direction: ASC }) {\n\
\ nodes {\n\ \ nodes {\n\
\ id,\n\ \ id,\n\
\ name\n\ \ name\n\

View File

@ -45,7 +45,7 @@ autoUpdatable =
$ Package.StaticPlaceholder "https://github.com/universal-ctags/ctags/archive/" $ Package.StaticPlaceholder "https://github.com/universal-ctags/ctags/archive/"
:| templateTail :| templateTail
in Package.Updater in Package.Updater
{ detectLatest = latestGitHub ghArguments stableTagTransform { detectLatest = latestGitHub ghArguments "(v)\\."
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = False , is64 = False
} }
@ -77,7 +77,7 @@ autoUpdatable =
:| Package.VersionPlaceholder :| Package.VersionPlaceholder
: [Package.StaticPlaceholder "/jitsi-meet-x86_64.AppImage"] : [Package.StaticPlaceholder "/jitsi-meet-x86_64.AppImage"]
in Package.Updater in Package.Updater
{ detectLatest = latestGitHub ghArguments $ Text.stripPrefix "v" { detectLatest = latestGitHub ghArguments "(v)*"
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = True , is64 = True
} }
@ -90,16 +90,12 @@ autoUpdatable =
{ owner = "php" { owner = "php"
, name = "php-src" , name = "php-src"
} }
checkVersion x
| not $ Text.isInfixOf "RC" x
, Text.isPrefixOf "php-8.2." x = Text.stripPrefix "php-" x
| otherwise = Nothing
template = Package.DownloadTemplate template = Package.DownloadTemplate
$ Package.StaticPlaceholder "https://www.php.net/distributions/php-" $ Package.StaticPlaceholder "https://www.php.net/distributions/php-"
:| Package.VersionPlaceholder :| Package.VersionPlaceholder
: [Package.StaticPlaceholder ".tar.xz"] : [Package.StaticPlaceholder ".tar.xz"]
in Package.Updater in Package.Updater
{ detectLatest = latestGitHub ghArguments checkVersion { detectLatest = latestGitHub ghArguments "(php-)8.2.\\d"
, getVersion = downloadWithTemplate template , getVersion = downloadWithTemplate template
, is64 = False , is64 = False
} }
@ -122,7 +118,7 @@ autoUpdatable =
:| Package.VersionPlaceholder :| Package.VersionPlaceholder
: templateTail : templateTail
in Package.Updater in Package.Updater
{ detectLatest = latestGitHub ghArguments stableTagTransform { detectLatest = latestGitHub ghArguments "(v)\\."
, getVersion = reuploadWithTemplate template [RawCommand "go" ["mod", "vendor"]] , getVersion = reuploadWithTemplate template [RawCommand "go" ["mod", "vendor"]]
, is64 = False , is64 = False
} }
@ -142,7 +138,7 @@ autoUpdatable =
: Package.VersionPlaceholder : Package.VersionPlaceholder
: [Package.StaticPlaceholder ".tar.gz"] : [Package.StaticPlaceholder ".tar.gz"]
in Package.Updater in Package.Updater
{ detectLatest = latestGitHub ghArguments stableTagTransform { detectLatest = latestGitHub ghArguments "(v)\\."
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = False , is64 = False
} }
@ -183,7 +179,7 @@ autoUpdatable =
: Package.VersionPlaceholder : Package.VersionPlaceholder
: [Package.StaticPlaceholder ".tar.gz"] : [Package.StaticPlaceholder ".tar.gz"]
in Package.Updater in Package.Updater
{ detectLatest = latestGitHub ghArguments $ Text.stripPrefix "v" { detectLatest = latestGitHub ghArguments "(v)\\."
, getVersion = reuploadWithTemplate template [] , getVersion = reuploadWithTemplate template []
, is64 = True , is64 = True
} }
@ -230,18 +226,18 @@ autoUpdatable =
dscannerArguments = PackageOwner{ owner = "dlang-community", name = "D-Scanner" } dscannerArguments = PackageOwner{ owner = "dlang-community", name = "D-Scanner" }
dcdArguments = PackageOwner{ owner = "dlang-community", name = "DCD" } dcdArguments = PackageOwner{ owner = "dlang-community", name = "DCD" }
latestDub = Package.Updater latestDub = Package.Updater
{ detectLatest = latestGitHub dubArguments stableTagTransform { detectLatest = latestGitHub dubArguments "(v)\\."
, getVersion = downloadWithTemplate dubTemplate , getVersion = downloadWithTemplate dubTemplate
, is64 = False , is64 = False
} }
latestDscanner = Package.Updater latestDscanner = Package.Updater
{ detectLatest = latestGitHub dscannerArguments stableTagTransform { detectLatest = latestGitHub dscannerArguments "(v)\\."
, getVersion = cloneFromGit dscannerURI "v" , getVersion = cloneFromGit dscannerURI "v"
, is64 = False , is64 = False
} }
dcdURI = [uri|https://github.com/dlang-community/DCD.git|] dcdURI = [uri|https://github.com/dlang-community/DCD.git|]
latestDcd = Package.Updater latestDcd = Package.Updater
{ detectLatest = latestGitHub dcdArguments stableTagTransform { detectLatest = latestGitHub dcdArguments "(v)\\."
, getVersion = cloneFromGit dcdURI "v" , getVersion = cloneFromGit dcdURI "v"
, is64 = False , is64 = False
} }

View File

@ -11,18 +11,6 @@ import Test.Hspec (Spec, describe, it, shouldBe)
spec :: Spec spec :: Spec
spec = do spec = do
describe "stableTagTransform" $ do
it "excludes tags with +" $
let given = "v2.6.0+unreleased"
actual = stableTagTransform given
in actual `shouldBe` Nothing
it "recognizes a stable version" $
let given = "v2.6.0"
actual = stableTagTransform given
expected = Just "2.6.0"
in actual `shouldBe` expected
describe "match" $ do describe "match" $ do
it "matches an exact tag prefixed with v" $ it "matches an exact tag prefixed with v" $
let expected = Just "2.6.0" let expected = Just "2.6.0"
@ -34,7 +22,22 @@ spec = do
actual = match "(v)*" "v2.6.0" actual = match "(v)*" "v2.6.0"
in actual `shouldBe` expected in actual `shouldBe` expected
it "ignores suffix after wildcard" $ it "matches digits" $
let expected = Just "2.6.0" let expected = Just "2.6.0"
actual = match "(v)*(-rc1)" "v2.6.0-rc1" actual = match "(v)2.6.\\d" "v2.6.0"
in actual `shouldBe` expected
it "matches digits and dot" $
let expected = Just "2.6.0"
actual = match "(v)\\." "v2.6.0"
in actual `shouldBe` expected
it "rejects unexpected suffix" $
let expected = Nothing
actual = match "(v)\\." "v2.6.0-rc1"
in actual `shouldBe` expected
it "rejects remaining umatched characters" $
let expected = Nothing
actual = match "2.6.0-rc1" "2.6.0"
in actual `shouldBe` expected in actual `shouldBe` expected