hackport 0.7.2.2 → 0.7.3.0
raw patch · 62 files changed
+2946/−2231 lines, 62 filesdep +lifted-basedep +monad-controldep +optparse-applicativedep ~mtldep ~parsec
Dependencies added: lifted-base, monad-control, optparse-applicative, parser-combinators
Dependency ranges changed: mtl, parsec
Files
- .github/workflows/haskell.yml +6/−3
- .gitignore +1/−0
- .gitmodules +1/−1
- GlobalFlags.hs +51/−0
- exe/Main.hs +216/−425
- hackport.cabal +91/−63
- hie.yaml +0/−55
- src/Cabal2Ebuild.hs +4/−0
- src/Error.hs +7/−9
- src/HackPort/GlobalFlags.hs +0/−51
- src/Hackport/Command/List.hs +38/−0
- src/Hackport/Command/MakeEbuild.hs +22/−0
- src/Hackport/Command/Merge.hs +13/−0
- src/Hackport/Command/Status.hs +11/−0
- src/Hackport/Command/Update.hs +16/−0
- src/Hackport/Env.hs +110/−0
- src/Hackport/Util.hs +54/−0
- src/Merge.hs +132/−88
- src/Merge/Dependencies.hs +1/−1
- src/Merge/Utils.hs +5/−15
- src/Overlays.hs +25/−24
- src/Portage/EBuild.hs +20/−18
- src/Portage/Metadata.hs +79/−58
- src/Portage/Metadata/RemoteId.hs +490/−0
- src/Portage/Overlay.hs +6/−5
- src/Portage/Resolve.hs +1/−1
- src/Status.hs +27/−54
- src/Status/Types.hs +42/−0
- src/Util.hs +14/−0
- tests/Merge/UtilsSpec.hs +0/−153
- tests/Portage/CabalSpec.hs +0/−69
- tests/Portage/Dependency/PrintSpec.hs +0/−327
- tests/Portage/EBuildSpec.hs +0/−34
- tests/Portage/GHCCoreSpec.hs +0/−23
- tests/Portage/MetadataSpec.hs +0/−104
- tests/Portage/PackageIdSpec.hs +0/−69
- tests/Portage/VersionSpec.hs +0/−27
- tests/QuickCheck/Instances.hs +0/−44
- tests/RunTests.hs +0/−15
- tests/Spec.hs +0/−1
- tests/doctests-v2.hs +0/−39
- tests/doctests-v2/Main.hs +35/−0
- tests/doctests.hs +0/−36
- tests/doctests/Main.hs +36/−0
- tests/normalize_deps.hs +0/−250
- tests/normalize_deps/Main.hs +250/−0
- tests/print_deps.hs +0/−140
- tests/print_deps/Main.hs +140/−0
- tests/resolveCat.hs +0/−29
- tests/resolveCat/Main.hs +27/−0
- tests/spec/Main.hs +1/−0
- tests/spec/Merge/UtilsSpec.hs +141/−0
- tests/spec/Portage/CabalSpec.hs +69/−0
- tests/spec/Portage/Dependency/PrintSpec.hs +327/−0
- tests/spec/Portage/EBuildSpec.hs +34/−0
- tests/spec/Portage/GHCCoreSpec.hs +23/−0
- tests/spec/Portage/Metadata/RemoteIdSpec.hs +98/−0
- tests/spec/Portage/MetadataSpec.hs +127/−0
- tests/spec/Portage/PackageIdSpec.hs +69/−0
- tests/spec/Portage/VersionSpec.hs +27/−0
- tests/spec/QuickCheck/Instances.hs +44/−0
- tests/spec/RunTests.hs +15/−0
.github/workflows/haskell.yml view
@@ -35,11 +35,14 @@ ${{ runner.os }}-build- ${{ runner.os }}- - - name: Install dependencies+ - name: Configure run: | cabal update- cabal build --only-dependencies --enable-tests --enable-benchmarks+ cabal configure --enable-tests --enable-benchmarks --flag=pedantic+ - name: Install dependencies+ run: |+ cabal build --only-dependencies - name: Build- run: cabal v2-build --enable-tests all+ run: cabal build - name: Run all enabled tests run: cabal test --test-option=--color --test-show-details=streaming
.gitignore view
@@ -4,3 +4,4 @@ .ghc.environment* cabal.project.local report.html+hie.yaml
.gitmodules view
@@ -3,4 +3,4 @@ url = https://github.com/gentoo-haskell/cabal.git [submodule "hackage-security"] path = hackage-security- url = https://github.com/well-typed/hackage-security.git+ url = https://github.com/gentoo-haskell/hackage-security.git
+ GlobalFlags.hs view
@@ -0,0 +1,51 @@+module HackPort.GlobalFlags+ ( GlobalFlags(..)+ , defaultGlobalFlags+ , withHackPortContext+ ) where++import qualified Distribution.Verbosity as DV+import qualified Distribution.Simple.Setup as DSS+import qualified Distribution.Client.Config as DCC+import qualified Distribution.Client.GlobalFlags as DCG+import qualified Distribution.Client.Types as DCT+import qualified Distribution.Utils.NubList as DUN++import qualified Network.URI as NU++import System.FilePath ((</>))++import qualified Overlays++-- | Type containing global flags.+data GlobalFlags =+ GlobalFlags { globalVersion :: DSS.Flag Bool+ , globalNumericVersion :: DSS.Flag Bool+ , globalPathToOverlay :: DSS.Flag (Maybe FilePath)+ , globalPathToPortage :: DSS.Flag (Maybe FilePath)+ }++-- | Default 'GlobalFlags'.+defaultGlobalFlags :: GlobalFlags+defaultGlobalFlags =+ GlobalFlags { globalVersion = DSS.Flag False+ , globalNumericVersion = DSS.Flag False+ , globalPathToOverlay = DSS.Flag Nothing+ , globalPathToPortage = DSS.Flag Nothing+ }++-- | Default remote repository. Defaults to [hackage](hackage.haskell.org).+defaultRemoteRepo :: DCT.RemoteRepo+defaultRemoteRepo = DCC.addInfoForKnownRepos $ (DCT.emptyRemoteRepo (DCT.RepoName name)) { DCT.remoteRepoURI = uri }+ where+ Just uri = NU.parseURI "https://hackage.haskell.org/"+ name = "hackage.haskell.org"++withHackPortContext :: DV.Verbosity -> GlobalFlags -> (DCG.RepoContext -> IO a) -> IO a+withHackPortContext verbosity global_flags callback = do+ overlayPath <- Overlays.getOverlayPath verbosity (DSS.fromFlag $ globalPathToOverlay global_flags)+ let flags = DCG.defaultGlobalFlags {+ DCG.globalRemoteRepos = DUN.toNubList [defaultRemoteRepo]+ , DCG.globalCacheDir = DSS.Flag $ overlayPath </> ".hackport"+ }+ DCG.withRepoContext verbosity flags callback
exe/Main.hs view
@@ -1,491 +1,282 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE ApplicativeDo #-} module Main (main) where +import Control.Applicative.Combinators+import qualified Control.Applicative.Combinators.NonEmpty as CNE import Control.Monad-import Data.Maybe-import Data.List-import qualified Data.Semigroup as S+import Data.Bitraversable+import Data.Monoid (Endo(..))+import qualified Options.Applicative as Opt+import qualified Options.Applicative.Help as Opt+import qualified Text.Parsec as P+import qualified Text.Parsec.String as P+import qualified Distribution.Verbosity as V --- cabal-import Distribution.Simple.Setup- ( Flag(..), fromFlag- , trueArg- , optionVerbosity- )+import Status.Types (StatusDirection(..)) -import Distribution.Simple.Command -- commandsRun-import Distribution.Simple.Utils ( dieNoVerbosity, cabalVersion, warn )-import qualified Distribution.PackageDescription.Parsec as Cabal-import qualified Distribution.Package as Cabal-import Distribution.Verbosity (Verbosity, normal)+import Hackport.Env+import Hackport.Command.List+import Hackport.Command.MakeEbuild+import Hackport.Command.Update+import Hackport.Command.Status+import Hackport.Command.Merge import Data.Version (showVersion) import Distribution.Pretty (prettyShow)-import Distribution.Parsec (simpleParsec)--import qualified Distribution.Client.Setup as CabalInstall-import qualified Distribution.Client.Types as CabalInstall-import qualified Distribution.Client.Update as CabalInstall--import qualified Distribution.Client.IndexUtils as CabalInstall-import qualified Distribution.Solver.Types.SourcePackage as CabalInstall-import qualified Distribution.Solver.Types.PackageIndex as CabalInstall--import Portage.Overlay as Overlay ( loadLazy, inOverlay )-import Portage.Host as Host ( getInfo, portage_dir )-import Portage.PackageId ( normalizeCabalPackageId )--import System.Environment ( getArgs, getProgName )-import System.Directory ( doesDirectoryExist )-import System.Exit ( exitFailure )-{-# LANGUAGE CPP #-}--import System.FilePath ( (</>) )--import qualified HackPort.GlobalFlags as H--import Error-import Status-import Overlays-import Merge+import Distribution.Simple.Utils (cabalVersion) import qualified Paths_cabal_install import qualified Paths_hackport --------------------------------------------------------------------------- List--------------------------------------------------------------------------data ListFlags = ListFlags {- listVerbosity :: Flag Verbosity- }--instance S.Semigroup ListFlags where- a <> b = ListFlags {- listVerbosity = combine listVerbosity- }- where combine field = field a S.<> field b--instance Monoid ListFlags where- mempty = ListFlags {- listVerbosity = mempty- }-#if !(MIN_VERSION_base(4,11,0))- mappend a b = ListFlags {- listVerbosity = combine listVerbosity- }- where combine field = field a `mappend` field b-#endif--defaultListFlags :: ListFlags-defaultListFlags = ListFlags {- listVerbosity = Flag normal- }--listCommand :: CommandUI ListFlags-listCommand = CommandUI {- commandName = "list",- commandSynopsis = "List package versions matching pattern",- commandUsage = usagePackages "list",- commandDescription = Nothing,- commandNotes = Nothing,-- commandDefaultFlags = defaultListFlags,- commandOptions = \_showOrParseArgs ->- [ optionVerbosity listVerbosity (\v flags -> flags { listVerbosity = v })- {-- , option [] ["overlay"]- "Use cached packages list from specified overlay"- listOverlayPath (\v flags -> flags { listOverlayPath = v })- (reqArgFlag "PATH")- -}- ]- }--listAction :: ListFlags -> [String] -> H.GlobalFlags -> IO ()-listAction flags extraArgs globalFlags = do- let verbosity = fromFlag (listVerbosity flags)- H.withHackPortContext verbosity globalFlags $ \repoContext -> do- overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)- index <- fmap CabalInstall.packageIndex (CabalInstall.getSourcePackages verbosity repoContext)- overlay <- Overlay.loadLazy overlayPath- let pkgs | null extraArgs = CabalInstall.allPackages index- | otherwise = concatMap (concatMap snd . CabalInstall.searchByNameSubstring index) extraArgs- normalized = map (normalizeCabalPackageId . CabalInstall.srcpkgPackageId) pkgs- let decorated = map (\p -> (Overlay.inOverlay overlay p, p)) normalized- mapM_ (putStrLn . pretty) decorated+main :: IO ()+main = join $ Opt.customExecParser prefs globalParser where- pretty :: (Bool, Cabal.PackageIdentifier) -> String- pretty (isInOverlay, pkgId) =- let dec | isInOverlay = " * "- | otherwise = " "- in dec ++ prettyShow pkgId-+ prefs :: Opt.ParserPrefs+ prefs = Opt.prefs+ $ Opt.showHelpOnEmpty+ <> Opt.showHelpOnError+ <> Opt.noBacktrack+ <> Opt.helpLongEquals -------------------------------------------------------------------------- Make Ebuild+-- Global ----------------------------------------------------------------------- -data MakeEbuildFlags = MakeEbuildFlags {- makeEbuildVerbosity :: Flag Verbosity- , makeEbuildCabalFlags :: Flag (Maybe String)- }+globalParser :: Opt.ParserInfo (IO ())+globalParser = Opt.info (Opt.helper <*> parser) infoMod+ where+ infoMod :: Opt.InfoMod (IO ())+ infoMod = Opt.progDesc+ $ "HackPort is an .ebuild generator from .cabal files "+ ++ "with hackage index support" -instance S.Semigroup MakeEbuildFlags where- a <> b = MakeEbuildFlags {- makeEbuildVerbosity = combine makeEbuildVerbosity- , makeEbuildCabalFlags = makeEbuildCabalFlags b- }- where combine field = field a S.<> field b+ parser :: Opt.Parser (IO ())+ parser = versionParser+ <|> numericVersionParser+ <|> mainParser+ where+ versionParser :: Opt.Parser (IO ())+ versionParser = Opt.flag' printVersion+ $ Opt.short 'V'+ <> Opt.long "version"+ <> Opt.help "Print version information"+ where+ printVersion = putStrLn+ $ "hackport version "+ ++ showVersion Paths_hackport.version+ ++ "\nusing cabal-install "+ ++ showVersion Paths_cabal_install.version+ ++ " and the Cabal library version "+ ++ prettyShow cabalVersion -instance Monoid MakeEbuildFlags where- mempty = MakeEbuildFlags {- makeEbuildVerbosity = mempty- , makeEbuildCabalFlags = mempty- }-#if !MIN_VERSION_base(4,11,0)- mappend a b = MakeEbuildFlags {- makeEbuildVerbosity = combine makeEbuildVerbosity- , makeEbuildCabalFlags = makeEbuildCabalFlags b- }- where combine field = field a `mappend` field b-#endif+ numericVersionParser :: Opt.Parser (IO ())+ numericVersionParser = Opt.flag' printNumericVersion+ $ Opt.long "numeric-version"+ <> Opt.help "Print just the version number"+ where+ printNumericVersion = putStrLn $ showVersion Paths_hackport.version -defaultMakeEbuildFlags :: MakeEbuildFlags-defaultMakeEbuildFlags = MakeEbuildFlags {- makeEbuildVerbosity = Flag normal- , makeEbuildCabalFlags = Flag Nothing- }+ mainParser :: Opt.Parser (IO ())+ mainParser = do+ v <- silentFlag+ <|> verbosityOption+ <|> (`appEndo` V.normal) . mconcat <$> many verbosityFlag -makeEbuildAction :: MakeEbuildFlags -> [String] -> H.GlobalFlags -> IO ()-makeEbuildAction flags args globalFlags = do- (catstr,cabals) <- case args of- (category:cabal1:cabaln) -> return (category, cabal1:cabaln)- _ -> throwEx (ArgumentError "make-ebuild needs at least two arguments. <category> <cabal-1> <cabal-n>")- cat <- case simpleParsec catstr of- Just c -> return c- Nothing -> throwEx (ArgumentError ("could not parse category: " ++ catstr))- let verbosity = fromFlag (makeEbuildVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)- forM_ cabals $ \cabalFileName -> do- pkg <- Cabal.readGenericPackageDescription normal cabalFileName- mergeGenericPackageDescription verbosity overlayPath cat pkg False (fromFlag $ makeEbuildCabalFlags flags)+ op <- optional $ Opt.strOption+ $ Opt.short 'p'+ <> Opt.long "overlay-path"+ <> Opt.metavar "PATH"+ <> helpMulti+ [ "Override search path list where .hackport/ lives"+ , "(default list: ['.', paludis-ovls or emerge-ovls])"+ ] -makeEbuildCommand :: CommandUI MakeEbuildFlags-makeEbuildCommand = CommandUI {- commandName = "make-ebuild",- commandSynopsis = "Make an ebuild from a .cabal file",- commandUsage = \_ -> [],- commandDescription = Nothing,- commandNotes = Nothing,+ pp <- optional $ Opt.strOption+ $ Opt.long "portage-path"+ <> Opt.metavar "PATH"+ <> Opt.help "Override path to your portage tree" - commandDefaultFlags = defaultMakeEbuildFlags,- commandOptions = \_showOrParseArgs ->- [ optionVerbosity makeEbuildVerbosity (\v flags -> flags { makeEbuildVerbosity = v })+ mode <- Opt.hsubparser+ $ subcmd "list" listAction listParser+ <> subcmd "make-ebuild" makeEbuildAction makeEbuildParser+ <> subcmd "update" updateAction updateParser+ <> subcmd "status" statusAction statusParser+ <> subcmd "merge" mergeAction mergeParser - , option "f" ["flags"]- (unlines [ "Set cabal flags to certain state."- , "Example: --flags=-all_extensions"- ])- makeEbuildCabalFlags- (\cabal_flags v -> v{ makeEbuildCabalFlags = cabal_flags })- (reqArg' "cabal_flags" (Flag . Just) (\(Flag ms) -> catMaybes [ms]))- ]- }+ pure $ mode GlobalEnv+ { globalVerbosity = v+ , globalPathToOverlay = op+ , globalPathToPortage = pp+ } --------------------------------------------------------------------------- Update------------------------------------------------------------------------+ where+ subcmd+ :: String+ -> Env env ()+ -> Opt.ParserInfo env+ -> Opt.Mod Opt.CommandFields (GlobalEnv -> IO ())+ subcmd name env = Opt.command name . fmap (runEnv env) -data UpdateFlags = UpdateFlags {- updateVerbosity :: Flag Verbosity- }+ silentFlag :: Opt.Parser V.Verbosity+ silentFlag = Opt.flag' V.silent+ $ Opt.long "silent"+ <> Opt.help "Use the \"silent\" verbosity level" -instance S.Semigroup UpdateFlags where- a <> b = UpdateFlags {- updateVerbosity = combine updateVerbosity- }- where combine field = field a S.<> field b+ verbosityString :: P.Parser V.Verbosity+ verbosityString = choice+ [ V.silent <$ (P.try (P.string "0") <|> P.string "silent")+ , V.normal <$ (P.try (P.string "1") <|> P.string "normal")+ , V.verbose <$ (P.try (P.string "2") <|> P.string "verbose")+ , V.deafening <$ (P.try (P.string "3") <|> P.string "deafening")+ ] <* P.eof -instance Monoid UpdateFlags where- mempty = UpdateFlags {- updateVerbosity = mempty- }-#if !(MIN_VERSION_base(4,11,0))- mappend a b = UpdateFlags {- updateVerbosity = combine updateVerbosity- }- where combine field = field a `mappend` field b-#endif+ verbosityFlag :: Opt.Parser (Endo V.Verbosity)+ verbosityFlag = Opt.flag' (Endo V.moreVerbose)+ $ Opt.short 'v'+ <> Opt.help "Increase verbosity level" -defaultUpdateFlags :: UpdateFlags-defaultUpdateFlags = UpdateFlags {- updateVerbosity = Flag normal- } -updateCommand :: CommandUI UpdateFlags-updateCommand = CommandUI {- commandName = "update",- commandSynopsis = "Update the local package database",- commandUsage = usageFlags "update",- commandDescription = Nothing,- commandNotes = Nothing,+ verbosityOption :: Opt.Parser V.Verbosity+ verbosityOption = Opt.option readm+ $ Opt.long "verbosity"+ <> helpMulti+ [ "Set verbosity level "+ , "(0-3 or (silent,normal,verbose,deafening))"+ ]+ where+ readm :: Opt.ReadM V.Verbosity+ readm = Opt.eitherReader+ $ either (Left . err) Right+ . P.runParser verbosityString () "command line option" - commandDefaultFlags = defaultUpdateFlags,- commandOptions = \_ ->- [ optionVerbosity updateVerbosity (\v flags -> flags { updateVerbosity = v })+ err :: P.ParseError -> String+ err _ = ($ "") $ Opt.displayS $ Opt.renderCompact $ Opt.extractChunk $ Opt.vsepChunks+ [ Opt.stringChunk "Takes a number (0-3) or one of the following values:"+ , Opt.tabulate =<< traverse (bitraverse Opt.stringChunk Opt.stringChunk)+ [ ("silent", "No output")+ , ("normal", "Default verbosity")+ , ("verbose", "Increased verbosity")+ , ("deafening", "Maximum verbosity")+ ]+ ] - {-- , option [] ["server"]- "Set the server you'd like to update the cache from"- updateServerURI (\v flags -> flags { updateServerURI = v} )- (reqArgFlag "SERVER")- -}- ]- } -updateAction :: UpdateFlags -> [String] -> H.GlobalFlags -> IO ()-updateAction flags extraArgs globalFlags = do- unless (null extraArgs) $- dieNoVerbosity $ "'update' doesn't take any extra arguments: " ++ unwords extraArgs- let verbosity = fromFlag (updateVerbosity flags) - H.withHackPortContext verbosity globalFlags $ \repoContext ->- -- TODO: parse user's flags as cabal-iinstall does.- -- Currently I'm lazy to adapt new flag and user:- -- defaultUpdateFlags- let updateFlags = commandDefaultFlags CabalInstall.updateCommand- in CabalInstall.update verbosity updateFlags repoContext- -------------------------------------------------------------------------- Status+-- List ----------------------------------------------------------------------- -data StatusFlags = StatusFlags {- statusVerbosity :: Flag Verbosity,- statusDirection :: Flag StatusDirection- }+listParser :: Opt.ParserInfo ListEnv+listParser = Opt.info parser infoMod+ where+ parser :: Opt.Parser ListEnv+ parser = do+ ps <- many $ Opt.strArgument $ Opt.metavar "PACKAGE"+ pure $ ListEnv ps -defaultStatusFlags :: StatusFlags-defaultStatusFlags = StatusFlags {- statusVerbosity = Flag normal,- statusDirection = Flag PortagePlusOverlay- }+ infoMod :: Opt.InfoMod ListEnv+ infoMod = Opt.progDesc "List package versions matching pattern" -statusCommand :: CommandUI StatusFlags-statusCommand = CommandUI {- commandName = "status",- commandSynopsis = "Show up-to-date status against other repos (hackage, ::gentoo)",- commandUsage = usagePackages "status",- commandDescription = Nothing,- commandNotes = Nothing,+-----------------------------------------------------------------------+-- Make Ebuild+----------------------------------------------------------------------- - commandDefaultFlags = defaultStatusFlags,- commandOptions = \_ ->- [ optionVerbosity statusVerbosity (\v flags -> flags { statusVerbosity = v })- , option [] ["to-portage"]- "Print only packages likely to be interesting to move to the portage tree."- statusDirection (\v flags -> flags { statusDirection = v })- (noArg (Flag OverlayToPortage))- , option [] ["from-hackage"]- "Print only packages likely to be interesting to move from hackage tree."- statusDirection (\v flags -> flags { statusDirection = v })- (noArg (Flag HackageToOverlay))- ]- }+makeEbuildParser :: Opt.ParserInfo MakeEbuildEnv+makeEbuildParser = Opt.info parser infoMod+ where+ parser :: Opt.Parser MakeEbuildEnv+ parser = do+ cat <- Opt.strArgument $ Opt.metavar "<CATEGORY>"+ cabalFiles <- CNE.some $ Opt.strArgument $ Opt.metavar "[EBUILD FILE]"+ flags <- optional cabalFlagsParser+ notOnHackage <- Opt.switch+ $ Opt.long "not-on-hackage"+ <> Opt.help "Skip writing the hackage remote-id to metadata.xml" -statusAction :: StatusFlags -> [String] -> H.GlobalFlags -> IO ()-statusAction flags args globalFlags = do- let verbosity = fromFlag (statusVerbosity flags)- direction = fromFlag (statusDirection flags)- portagePath <- getPortageDir verbosity globalFlags- overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)+ pure $ MakeEbuildEnv cat cabalFiles flags (not notOnHackage) - H.withHackPortContext verbosity globalFlags $ \repoContext ->- runStatus verbosity portagePath overlayPath direction args repoContext+ infoMod :: Opt.InfoMod MakeEbuildEnv+ infoMod = Opt.progDesc "Make an ebuild from a .cabal file" -------------------------------------------------------------------------- Merge+-- Update ----------------------------------------------------------------------- -data MergeFlags = MergeFlags {- mergeVerbosity :: Flag Verbosity- , mergeCabalFlags :: Flag (Maybe String)- }+updateParser :: Opt.ParserInfo ()+updateParser = Opt.info parser infoMod+ where+ parser :: Opt.Parser ()+ parser = pure () -instance S.Semigroup MergeFlags where- a <> b = MergeFlags {- mergeVerbosity = combine mergeVerbosity- , mergeCabalFlags = mergeCabalFlags b- }- where combine field = field a S.<> field b+ infoMod :: Opt.InfoMod ()+ infoMod = Opt.progDesc "Update the local package database" -instance Monoid MergeFlags where- mempty = MergeFlags {- mergeVerbosity = mempty- , mergeCabalFlags = mempty- }-#if !(MIN_VERSION_base(4,11,0))- mappend a b = MergeFlags {- mergeVerbosity = combine mergeVerbosity- , mergeCabalFlags = mergeCabalFlags b- }- where combine field = field a `mappend` field b-#endif+-----------------------------------------------------------------------+-- Status+----------------------------------------------------------------------- -defaultMergeFlags :: MergeFlags-defaultMergeFlags = MergeFlags {- mergeVerbosity = Flag normal- , mergeCabalFlags = Flag Nothing- }+statusParser :: Opt.ParserInfo StatusEnv+statusParser = Opt.info parser infoMod+ where+ parser :: Opt.Parser StatusEnv+ parser = do+ dir <- choice+ [ Opt.flag' OverlayToPortage+ $ Opt.long "to-portage"+ <> Opt.help "Print only packages likely to be interesting to move to the portage tree."+ , Opt.flag' HackageToOverlay+ $ Opt.long "from-hackage"+ <> Opt.help "Print only packages likely to be interesting to move from hackage tree."+ , pure PortagePlusOverlay+ ] -mergeCommand :: CommandUI MergeFlags-mergeCommand = CommandUI {- commandName = "merge",- commandSynopsis = "Make an ebuild out of hackage package",- commandUsage = usagePackages "merge",- commandDescription = Nothing,- commandNotes = Nothing,+ pkgs <- many $ Opt.strArgument $ Opt.metavar "PACKAGE" - commandDefaultFlags = defaultMergeFlags,- commandOptions = \_showOrParseArgs ->- [ optionVerbosity mergeVerbosity (\v flags -> flags { mergeVerbosity = v })+ pure StatusEnv+ { statusDirection = dir+ , statusPackages = pkgs+ } - , option "f" ["flags"]- (unlines [ "Set cabal flags to certain state."- , "Example: --flags=-all_extensions"- ])- mergeCabalFlags- (\cabal_flags v -> v{ mergeCabalFlags = cabal_flags})- (reqArg' "cabal_flags" (Flag . Just) (\(Flag ms) -> catMaybes [ms]))+ infoMod :: Opt.InfoMod StatusEnv+ infoMod = Opt.progDescDoc $ docMulti Opt.sep+ [ "Show up-to-date status against other repos"+ , "(hackage, ::gentoo)" ]- } -mergeAction :: MergeFlags -> [String] -> H.GlobalFlags -> IO ()-mergeAction flags extraArgs globalFlags = do- let verbosity = fromFlag (mergeVerbosity flags)- overlayPath <- getOverlayPath verbosity (fromFlag $ H.globalPathToOverlay globalFlags)-- H.withHackPortContext verbosity globalFlags $ \repoContext ->- merge verbosity repoContext extraArgs overlayPath (fromFlag $ mergeCabalFlags flags)- -------------------------------------------------------------------------- Utils+-- Merge ----------------------------------------------------------------------- -usagePackages :: String -> String -> String-usagePackages op_name pname =- "Usage: " ++ pname ++ " " ++ op_name ++ " [FLAGS] [PACKAGE]\n\n"- ++ "Flags for " ++ op_name ++ ":"+mergeParser :: Opt.ParserInfo MergeEnv+mergeParser = Opt.info parser infoMod+ where+ parser :: Opt.Parser MergeEnv+ parser = do+ flags <- optional $ cabalFlagsParser+ pkg <- Opt.strArgument $ Opt.metavar "PACKAGE" -usageFlags :: String -> String -> String-usageFlags flag_name pname =- "Usage: " ++ pname ++ " " ++ flag_name ++ " [FLAGS]\n\n"- ++ "Flags for " ++ flag_name ++ ":"+ pure $ MergeEnv flags pkg -getPortageDir :: Verbosity -> H.GlobalFlags -> IO FilePath-getPortageDir verbosity globalFlags = do- let portagePathM = fromFlag (H.globalPathToPortage globalFlags)- portagePath <- case portagePathM of- Nothing -> Host.portage_dir <$> Host.getInfo- Just path -> return path- exists <- doesDirectoryExist $ portagePath </> "dev-haskell"- when (not exists) $- warn verbosity $ "Looks like an invalid portage directory: " ++ portagePath- return portagePath+ infoMod :: Opt.InfoMod MergeEnv+ infoMod = Opt.progDesc "Make an ebuild out of hackage package" -------------------------------------------------------------------------- Main+-- Misc ----------------------------------------------------------------------- -globalCommand :: CommandUI H.GlobalFlags-globalCommand = CommandUI {- commandName = "",- commandSynopsis = "HackPort is an .ebuild generator from .cabal files with hackage index support",- commandDescription = Just $ \pname ->- let- commands' = commands ++ [commandAddAction helpCommandUI undefined]- cmdDescs = getNormalCommandDescriptions commands'- maxlen = maximum $ [length name | (name, _) <- cmdDescs]- align str = str ++ replicate (maxlen - length str) ' '- in- "Commands:\n"- ++ unlines [ " " ++ align name ++ " " ++ descr- | (name, descr) <- cmdDescs ]- ++ "\n"- ++ "For more information about a command use\n"- ++ " " ++ pname ++ " COMMAND --help\n\n"- ++ "Typical steps for generating ebuilds from hackage packages:\n"- ++ concat [ " " ++ pname ++ " " ++ x ++ "\n"- | x <- ["update", "merge <package>"]]- ++ "\n"- ++ "Advanced usage:\n"- ++ concat [ " " ++ pname ++ " " ++ x ++ "\n"- | x <- ["update", "make-ebuild <CATEGORY> <ebuild.name>"]],-- commandNotes = Nothing,- commandUsage = \pname -> "Usage: " ++ pname ++ " [GLOBAL FLAGS] [COMMAND [FLAGS]]\n",- commandDefaultFlags = H.defaultGlobalFlags,- commandOptions = \_showOrParseArgs ->- [ option ['V'] ["version"]- "Print version information"- H.globalVersion (\v flags -> flags { H.globalVersion = v })- trueArg- , option [] ["numeric-version"]- "Print just the version number"- H.globalNumericVersion (\v flags -> flags { H.globalNumericVersion = v })- trueArg- , option ['p'] ["overlay-path"]- "Override search path list where .hackport/ lives (default list: ['.', paludis-ovls or emerge-ovls])"- H.globalPathToOverlay (\ovrl_path flags -> flags { H.globalPathToOverlay = ovrl_path })- (reqArg' "PATH" (Flag . Just) (\(Flag ms) -> catMaybes [ms]))- , option [] ["portage-path"]- "Override path to your portage tree"- H.globalPathToPortage (\port_path flags -> flags { H.globalPathToPortage = port_path })- (reqArg' "PATH" (Flag . Just) (\(Flag ms) -> catMaybes [ms]))- ]- }--mainWorker :: [String] -> IO ()-mainWorker args =- case commandsRun globalCommand commands args of- CommandHelp help -> printHelp help- CommandList opts -> printOptionsList opts- CommandErrors errs -> printErrors errs- CommandReadyToGo (globalflags, commandParse) -> do- case commandParse of- _ | fromFlag (H.globalVersion globalflags) -> printVersion- | fromFlag (H.globalNumericVersion globalflags) -> printNumericVersion- CommandHelp help -> printHelp help- CommandList opts -> printOptionsList opts- CommandErrors errs -> printErrors errs- CommandReadyToGo action -> catchEx (action globalflags) errorHandler- where- printHelp help = getProgName >>= putStr . help- printOptionsList = putStr . unlines- printErrors errs = do- putStr (concat (intersperse "\n" errs))- exitFailure- printNumericVersion = putStrLn $ showVersion Paths_hackport.version- printVersion = putStrLn $ "hackport version "- ++ showVersion Paths_hackport.version- ++ "\nusing cabal-install "- ++ showVersion Paths_cabal_install.version- ++ " and the Cabal library version "- ++ prettyShow cabalVersion- errorHandler :: HackPortError -> IO ()- errorHandler e = do- putStrLn (hackPortShowError e)+helpMulti :: [String] -> Opt.Mod f a+helpMulti = Opt.helpDoc . docMulti Opt.sep -commands :: [Command (H.GlobalFlags -> IO ())]-commands =- [ listCommand `commandAddAction` listAction- , makeEbuildCommand `commandAddAction` makeEbuildAction- , statusCommand `commandAddAction` statusAction- , updateCommand `commandAddAction` updateAction- , mergeCommand `commandAddAction` mergeAction- ]+docMulti :: ([Opt.Doc] -> Opt.Doc) -> [String] -> Maybe Opt.Doc+docMulti f = Opt.unChunk . fmap f . traverse Opt.paragraph -main :: IO ()-main = getArgs >>= mainWorker+cabalFlagsParser :: Opt.Parser String+cabalFlagsParser = Opt.strOption+ $ Opt.short 'f'+ <> Opt.long "flags"+ <> Opt.metavar "cabal_flags"+ <> helpMulti+ [ "Set cabal flags to certain state. Example:"+ , "--flags=-all_extensions"+ ]
hackport.cabal view
@@ -1,6 +1,6 @@-Cabal-Version: 2.2+Cabal-Version: 3.0 Name: hackport-Version: 0.7.2.2+Version: 0.7.3.0 License: GPL-3.0-or-later License-file: LICENSE Author: Henning Günther, Duncan Coutts, Lennart Kolmodin@@ -20,7 +20,27 @@ manual: True default: False +flag gentoo-tests+ description: Build tests that require a running Gentoo system+ manual: True+ default: False++flag pedantic+ description: Enable -Werror+ default: False+ manual: True++-- Turn off all warnings (for external libs and doctests-v2)+common no-warnings+ ghc-options: -Wno-default++common warnings+ ghc-options: -Wall+ if flag(pedantic)+ ghc-options: -Werror+ library hackport-external-libs-Cabal+ import: no-warnings Default-Language: Haskell2010 Hs-Source-Dirs: cabal, cabal/Cabal @@ -299,6 +319,7 @@ Paths_Cabal library hackport-external-libs-hackage-security+ import: no-warnings Default-Language: Haskell2010 Hs-Source-Dirs: hackage-security/hackage-security/src @@ -399,6 +420,7 @@ Text.JSON.Canonical library hackport-external-libs-cabal-install+ import: no-warnings Default-Language: Haskell2010 Hs-Source-Dirs: cabal, cabal/cabal-install @@ -504,6 +526,7 @@ Distribution.Solver.Types.Settings library hackport-internal+ import: warnings Default-Language: Haskell2010 Hs-Source-Dirs: src Build-Depends:@@ -517,8 +540,12 @@ directory, extensible-exceptions, filepath,+ lifted-base,+ monad-control,+ mtl, network-uri, parallel >= 3.2.1.0,+ parsec, pretty, process, split,@@ -529,21 +556,35 @@ QuickCheck, template-haskell + default-extensions:+ FlexibleContexts+ MultiParamTypeClasses+ other-extensions:+ CPP+ ConstraintKinds DeriveDataTypeable+ LambdaCase+ PatternGuards exposed-modules: Error Merge Overlays Status- HackPort.GlobalFlags+ Status.Types+ Hackport.Command.List+ Hackport.Command.MakeEbuild+ Hackport.Command.Update+ Hackport.Command.Status+ Hackport.Command.Merge+ Hackport.Env+ Hackport.Util Portage.Host Portage.Overlay Portage.PackageId Paths_hackport - other-modules: AnsiColor Cabal2Ebuild Merge.Dependencies@@ -560,6 +601,7 @@ Portage.EMeta Portage.GHCCore Portage.Metadata+ Portage.Metadata.RemoteId Portage.Resolve Portage.Tables Portage.Use@@ -570,7 +612,8 @@ Paths_hackport Executable hackport- ghc-options: -Wall -threaded +RTS -N -RTS -with-rtsopts=-N+ import: warnings+ ghc-options: -threaded +RTS -N -RTS -with-rtsopts=-N ghc-prof-options: -caf-all -auto-all -rtsopts Main-Is: Main.hs Default-Language: Haskell2010@@ -581,18 +624,27 @@ hackport-internal, base, directory,- filepath+ filepath,+ optparse-applicative <0.17,+ parsec,+ parser-combinators other-modules: Paths_hackport + other-extensions:+ ApplicativeDo+ CPP+ Test-Suite test-resolve-category+ import: warnings -- requires a local Portage overlay, thus fails in a sandboxed test.- buildable: False- ghc-options: -Wall+ if !flag(gentoo-tests)+ buildable: False Type: exitcode-stdio-1.0 Default-Language: Haskell98- Main-Is: resolveCat.hs- Hs-Source-Dirs: src, tests- Build-Depends: hackport-external-libs,+ Main-Is: Main.hs+ Hs-Source-Dirs: tests/resolveCat+ Build-Depends: hackport-external-libs-Cabal,+ hackport-internal, array, base, binary,@@ -618,14 +670,14 @@ DoAndIfThenElse Test-Suite test-print-deps+ import: warnings -- This test-suite has been incorporated into the 'spec' test-suite, -- and may be removed in the future. buildable: False- ghc-options: -Wall Type: exitcode-stdio-1.0 Default-Language: Haskell98- Main-Is: print_deps.hs- Hs-Source-Dirs: src, tests+ Main-Is: Main.hs+ Hs-Source-Dirs: tests/print_deps Build-Depends: hackport-external-libs, array, base,@@ -651,14 +703,14 @@ DoAndIfThenElse Test-Suite test-normalize-deps+ import: warnings -- This test-suite has been incorporated into the 'spec' test-suite, -- and may be removed in the future. buildable: False- ghc-options: -Wall Type: exitcode-stdio-1.0 Default-Language: Haskell98- Main-Is: normalize_deps.hs- Hs-Source-Dirs: src, tests+ Main-Is: Main.hs+ Hs-Source-Dirs: tests/normalize_deps Build-Depends: hackport-external-libs, array, base,@@ -685,12 +737,13 @@ DoAndIfThenElse test-suite doctests+ import: warnings x-doctest-components: lib:hackport-internal exe:hackport type: exitcode-stdio-1.0- Hs-Source-Dirs: tests+ Hs-Source-Dirs: tests/doctests ghc-options: -threaded default-language: Haskell98- main-is: doctests.hs+ main-is: Main.hs build-depends: base, doctest >= 0.8, cabal-doctest,@@ -705,27 +758,32 @@ buildable: False test-suite doctests-v2+ import: warnings type: exitcode-stdio-1.0 default-language: Haskell98- Hs-Source-Dirs: tests- main-is: doctests-v2.hs- build-depends: base, process+ Hs-Source-Dirs: tests/doctests-v2+ main-is: Main.hs+ build-depends: base,+ process,+ -- Force doctests-v2 to be built last.+ -- This should reduce duplicated work: this test runs+ -- `cabal repl` which will try to compile the whole project.+ hackport-internal build-tool-depends:- cabal-install:cabal >= 3.2,- doctest:doctest >= 0.8-- other-extensions:- CPP+-- , cabal-install:cabal >= 3.4+ , doctest:doctest >= 0.8+ -- Force doctests-v2 to be built last+ , hackport:hackport if flag(cabal-v1) buildable: False test-suite spec- ghc-options: -Wall+ import: warnings Type: exitcode-stdio-1.0 Default-Language: Haskell2010- Main-Is: Spec.hs- Hs-Source-Dirs: src, tests+ Main-Is: Main.hs+ Hs-Source-Dirs: tests/spec other-modules: Merge.UtilsSpec Portage.CabalSpec@@ -733,45 +791,14 @@ Portage.EBuildSpec Portage.GHCCoreSpec Portage.MetadataSpec+ Portage.Metadata.RemoteIdSpec Portage.PackageIdSpec Portage.VersionSpec QuickCheck.Instances-- -- hackport-internal- Error- Merge- Overlays- Status- HackPort.GlobalFlags- Portage.Host- Portage.Overlay- Portage.PackageId- Paths_hackport- AnsiColor- Cabal2Ebuild- Merge.Dependencies- Merge.Utils- Portage.Cabal- Portage.Dependency- Portage.Dependency.Builder- Portage.Dependency.Normalize- Portage.Dependency.Print- Portage.Dependency.Types- Portage.EBuild- Portage.EBuild.CabalFeature- Portage.EBuild.Render- Portage.EMeta- Portage.GHCCore- Portage.Metadata- Portage.Resolve- Portage.Tables- Portage.Use- Portage.Version- Util- Build-Depends: hackport-external-libs-Cabal, hackport-external-libs-cabal-install,+ hackport-internal, async, base, bytestring,@@ -783,6 +810,7 @@ hspec >= 2.0, network-uri, parallel,+ parsec, pretty, process, QuickCheck >= 2.0,
− hie.yaml
@@ -1,55 +0,0 @@-cradle:- cabal:- - path: "cabal"- component: "hackport:lib:hackport-external-libs-Cabal"-- - path: "cabal/Cabal"- component: "hackport:lib:hackport-external-libs-Cabal"-- - path: "hackage-security/hackage-security/src"- component: "hackport:lib:hackport-external-libs-hackage-security"-- - path: "cabal"- component: "hackport:lib:hackport-external-libs-cabal-install"-- - path: "cabal/cabal-install"- component: "hackport:lib:hackport-external-libs-cabal-install"-- - path: "src"- component: "hackport:lib:hackport-internal"-- - path: "exe/Main.hs"- component: "hackport:exe:hackport"-- - path: "exe/Paths_hackport.hs"- component: "hackport:exe:hackport"-- - path: "src"- component: "hackport:test:test-resolve-category"-- - path: "tests"- component: "hackport:test:test-resolve-category"-- - path: "src"- component: "hackport:test:test-print-deps"-- - path: "tests"- component: "hackport:test:test-print-deps"-- - path: "src"- component: "hackport:test:test-normalize-deps"-- - path: "tests"- component: "hackport:test:test-normalize-deps"-- - path: "tests"- component: "hackport:test:doctests"-- - path: "tests"- component: "hackport:test:doctests-v2"-- - path: "src"- component: "hackport:test:spec"-- - path: "tests"- component: "hackport:test:spec"
src/Cabal2Ebuild.hs view
@@ -25,6 +25,7 @@ ( PackageDescription(..), license) import qualified Distribution.Package as Cabal ( PackageIdentifier(..) , Dependency(..))+import qualified Distribution.Types.SourceRepo as Cabal import qualified Distribution.Version as Cabal ( VersionRange , cataVersionRange, normaliseVersionRange , majorUpperBound, mkVersion )@@ -55,6 +56,7 @@ E.hackage_name= cabalPkgName, E.version = prettyShow (Cabal.pkgVersion (Cabal.package pkg)), E.revision = getHackageRevision,+ E.sourceURIs = getSourceURIs, E.description = ST.fromShortText $ if ST.null (Cabal.synopsis pkg) then Cabal.description pkg else Cabal.synopsis pkg,@@ -83,6 +85,8 @@ case Map.lookup "x-revision" (Map.fromList (Cabal.customFieldsPD pkg)) of Just rev -> rev _ -> "0"+ getSourceURIs = catMaybes+ $ Cabal.repoLocation <$> Cabal.sourceRepos pkg thisHomepage = if (ST.null $ Cabal.homepage pkg) then E.homepage E.ebuildTemplate else ST.fromShortText $ Cabal.homepage pkg
src/Error.hs view
@@ -1,9 +1,14 @@ {-# LANGUAGE DeriveDataTypeable #-} -module Error (HackPortError(..), throwEx, catchEx, hackPortShowError) where+module Error+ ( HackPortError(..)+ , hackPortShowError+ , throw+ , catch+ ) where import Data.Typeable-import Control.Exception.Extensible as EE+import Control.Exception -- | Type holding all of the 'HackPortError' constructors. data HackPortError@@ -34,13 +39,6 @@ instance Exception HackPortError where --- | Throw a 'HackPortError'.-throwEx :: HackPortError -> IO a-throwEx = EE.throw---- | Catch a 'HackPortError'.-catchEx :: IO a -> (HackPortError -> IO a) -> IO a-catchEx = EE.catch -- | Show the error string for a given 'HackPortError'. hackPortShowError :: HackPortError -> String
− src/HackPort/GlobalFlags.hs
@@ -1,51 +0,0 @@-module HackPort.GlobalFlags- ( GlobalFlags(..)- , defaultGlobalFlags- , withHackPortContext- ) where--import qualified Distribution.Verbosity as DV-import qualified Distribution.Simple.Setup as DSS-import qualified Distribution.Client.Config as DCC-import qualified Distribution.Client.GlobalFlags as DCG-import qualified Distribution.Client.Types as DCT-import qualified Distribution.Utils.NubList as DUN--import qualified Network.URI as NU--import System.FilePath ((</>))--import qualified Overlays---- | Type containing global flags.-data GlobalFlags =- GlobalFlags { globalVersion :: DSS.Flag Bool- , globalNumericVersion :: DSS.Flag Bool- , globalPathToOverlay :: DSS.Flag (Maybe FilePath)- , globalPathToPortage :: DSS.Flag (Maybe FilePath)- }---- | Default 'GlobalFlags'.-defaultGlobalFlags :: GlobalFlags-defaultGlobalFlags =- GlobalFlags { globalVersion = DSS.Flag False- , globalNumericVersion = DSS.Flag False- , globalPathToOverlay = DSS.Flag Nothing- , globalPathToPortage = DSS.Flag Nothing- }---- | Default remote repository. Defaults to [hackage](hackage.haskell.org).-defaultRemoteRepo :: DCT.RemoteRepo-defaultRemoteRepo = DCC.addInfoForKnownRepos $ (DCT.emptyRemoteRepo (DCT.RepoName name)) { DCT.remoteRepoURI = uri }- where- Just uri = NU.parseURI "https://hackage.haskell.org/"- name = "hackage.haskell.org"--withHackPortContext :: DV.Verbosity -> GlobalFlags -> (DCG.RepoContext -> IO a) -> IO a-withHackPortContext verbosity global_flags callback = do- overlayPath <- Overlays.getOverlayPath verbosity (DSS.fromFlag $ globalPathToOverlay global_flags)- let flags = DCG.defaultGlobalFlags {- DCG.globalRemoteRepos = DUN.toNubList [defaultRemoteRepo]- , DCG.globalCacheDir = DSS.Flag $ overlayPath </> ".hackport"- }- DCG.withRepoContext verbosity flags callback
+ src/Hackport/Command/List.hs view
@@ -0,0 +1,38 @@+module Hackport.Command.List+ ( listAction+ ) where++import qualified Distribution.Package as Cabal+import Distribution.Pretty (prettyShow)++import qualified Distribution.Client.Types as CabalInstall+import qualified Distribution.Client.IndexUtils as CabalInstall+import qualified Distribution.Solver.Types.SourcePackage as CabalInstall+import qualified Distribution.Solver.Types.PackageIndex as CabalInstall++import Portage.Overlay as Overlay (loadLazy, inOverlay)+import Portage.PackageId (normalizeCabalPackageId)+import Overlays++import Hackport.Util (withHackportContext)+import Hackport.Env++listAction :: Env ListEnv ()+listAction = do+ (GlobalEnv verbosity _ _, ListEnv pkgList) <- ask+ withHackportContext $ \repoContext -> do+ overlayPath <- getOverlayPath+ index <- fmap CabalInstall.packageIndex+ $ liftIO $ CabalInstall.getSourcePackages verbosity repoContext+ overlay <- Overlay.loadLazy overlayPath+ let pkgs | null pkgList = CabalInstall.allPackages index+ | otherwise = concatMap (concatMap snd . CabalInstall.searchByNameSubstring index) pkgList+ normalized = map (normalizeCabalPackageId . CabalInstall.srcpkgPackageId) pkgs+ let decorated = map (\p -> (Overlay.inOverlay overlay p, p)) normalized+ mapM_ (liftIO . putStrLn . pretty) decorated+ where+ pretty :: (Bool, Cabal.PackageIdentifier) -> String+ pretty (isInOverlay, pkgId) =+ let dec | isInOverlay = " * "+ | otherwise = " "+ in dec ++ prettyShow pkgId
+ src/Hackport/Command/MakeEbuild.hs view
@@ -0,0 +1,22 @@+module Hackport.Command.MakeEbuild+ ( makeEbuildAction+ ) where++import qualified Distribution.PackageDescription.Parsec as Cabal+import Distribution.Parsec (simpleParsec)+import qualified Distribution.Verbosity as V++import Error+import Merge++import Hackport.Env++makeEbuildAction :: Env MakeEbuildEnv ()+makeEbuildAction = do+ (_, MakeEbuildEnv catstr cabals flags _) <- ask+ cat <- case simpleParsec catstr of+ Just c -> return c+ Nothing -> throw (ArgumentError ("could not parse category: " ++ catstr))+ forM_ cabals $ \cabalFileName -> do+ pkg <- liftIO $ Cabal.readGenericPackageDescription V.normal cabalFileName+ mergeGenericPackageDescription cat pkg False flags
+ src/Hackport/Command/Merge.hs view
@@ -0,0 +1,13 @@+module Hackport.Command.Merge+ ( mergeAction+ ) where++import Merge (merge)++import Hackport.Util (withHackportContext)+import Hackport.Env++mergeAction :: Env MergeEnv ()+mergeAction = askEnv >>= \(MergeEnv flags pkg) -> do+ withHackportContext $ \repoContext ->+ merge repoContext pkg flags
+ src/Hackport/Command/Status.hs view
@@ -0,0 +1,11 @@+module Hackport.Command.Status+ ( statusAction+ ) where++import Status++import Hackport.Env+import Hackport.Util++statusAction :: Env StatusEnv ()+statusAction = withHackportContext runStatus
+ src/Hackport/Command/Update.hs view
@@ -0,0 +1,16 @@+module Hackport.Command.Update+ ( updateAction+ ) where++import Distribution.Simple.Command (CommandUI(..))+import qualified Distribution.Client.Setup as CabalInstall+import qualified Distribution.Client.Update as CabalInstall++import Hackport.Util (withHackportContext)+import Hackport.Env++updateAction :: Env env ()+updateAction = askGlobalEnv >>= \(GlobalEnv verbosity _ _) ->+ withHackportContext $ \repoContext -> do+ let updateFlags = commandDefaultFlags CabalInstall.updateCommand+ liftIO $ CabalInstall.update verbosity updateFlags repoContext
+ src/Hackport/Env.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE FlexibleInstances #-}++module Hackport.Env+ (+ -- * Env monad+ MonadEnv+ , HasEnv (..)+ , HasGlobalEnv (..)+ , Env+ , runEnv+ -- * Global env+ , GlobalEnv (..)+ -- * Subcommand env+ -- ** List+ , ListEnv (..)+ -- ** Make Ebuild+ , MakeEbuildEnv (..)+ -- ** Status+ , StatusEnv (..)+ -- ** Merge+ , MergeEnv (..)+ -- * Metadata+ , WritesMetadata (..)+ , UseHackageRemote+ -- * Re-exports+ , module Control.Monad.Reader+ ) where++import Control.Monad.Reader+import Data.List.NonEmpty (NonEmpty (..))+import Data.Semigroup (Last(..))+import qualified Distribution.Verbosity as V++import Status.Types (StatusDirection)++type MonadEnv env m = (HasGlobalEnv m, HasEnv env m, MonadIO m)+type Env env = ReaderT (GlobalEnv, env) IO++class HasGlobalEnv m where+ askGlobalEnv :: m GlobalEnv++instance Monad m => HasGlobalEnv (ReaderT (GlobalEnv,env) m) where+ askGlobalEnv = asks fst++class HasEnv env m where+ askEnv :: m env++instance Monad m => HasEnv env (ReaderT (a,env) m) where+ askEnv = asks snd++data GlobalEnv = GlobalEnv+ { globalVerbosity :: V.Verbosity+ , globalPathToOverlay :: Maybe FilePath+ , globalPathToPortage :: Maybe FilePath+ }+ deriving (Show, Eq, Ord)++instance Semigroup GlobalEnv where+ GlobalEnv v1 po1 pp1 <> GlobalEnv v2 po2 pp2 =+ GlobalEnv+ (getLast (Last v1 <> Last v2))+ (getLast <$> ((Last <$> po1) <> (Last <$> po2)))+ (getLast <$> ((Last <$> pp1) <> (Last <$> pp2)))++instance Monoid GlobalEnv where+ mempty = GlobalEnv V.normal Nothing Nothing++newtype ListEnv = ListEnv+ { listPackages :: [String]+ }+ deriving (Show, Eq, Ord)++class WritesMetadata a where+ useHackageRemote :: a -> UseHackageRemote++type UseHackageRemote = Bool++data MakeEbuildEnv = MakeEbuildEnv+ { makeEbuildCategory :: String+ , makeEbuildCabalFiles :: NonEmpty FilePath+ , makeEbuildCabalFlags :: Maybe String+ , makeEbuildUseHackageRemote :: UseHackageRemote+ }+ deriving (Show, Eq, Ord)++instance WritesMetadata MakeEbuildEnv where+ useHackageRemote = makeEbuildUseHackageRemote++data StatusEnv = StatusEnv+ { statusDirection :: StatusDirection+ , statusPackages :: [String]+ }+ deriving (Eq)++data MergeEnv = MergeEnv+ { mergeCabalFlags :: Maybe String+ , mergePackage :: String+ }++instance WritesMetadata MergeEnv where+ useHackageRemote _ = True++runEnv+ :: Env env a+ -> env+ -> GlobalEnv+ -> IO a+runEnv env e global = runReaderT env (global, e)
+ src/Hackport/Util.hs view
@@ -0,0 +1,54 @@+module Hackport.Util+ ( getPortageDir+ , withHackportContext+ , defaultRemoteRepo+ ) where++import Control.Monad.Trans.Control+import Data.Maybe (fromJust)+import qualified Network.URI as NU+import System.Directory ( doesDirectoryExist )+import System.FilePath ( (</>) )++import Distribution.Simple.Utils (warn)+import qualified Distribution.Simple.Setup as DSS+import qualified Distribution.Client.Config as DCC+import qualified Distribution.Client.GlobalFlags as DCG+import qualified Distribution.Client.Types as DCT+import qualified Distribution.Utils.NubList as DUN++import Portage.Host as Host ( getInfo, portage_dir )+import Overlays (getOverlayPath)++import Hackport.Env++getPortageDir :: Env env FilePath+getPortageDir = do+ (GlobalEnv verbosity _ portagePathM, _) <- ask+ portagePath <- case portagePathM of+ Nothing -> liftIO $ Host.portage_dir <$> Host.getInfo+ Just path -> return path+ exists <- liftIO $ doesDirectoryExist $ portagePath </> "dev-haskell"+ unless exists $ liftIO $+ warn verbosity $ "Looks like an invalid portage directory: " ++ portagePath+ return portagePath++withHackportContext :: (DCG.RepoContext -> Env env a) -> Env env a+withHackportContext callback = do+ (GlobalEnv verbosity _ _, _) <- ask+ overlayPath <- getOverlayPath+ let flags = DCG.defaultGlobalFlags {+ DCG.globalRemoteRepos = DUN.toNubList [defaultRemoteRepo]+ , DCG.globalCacheDir = DSS.Flag $ overlayPath </> ".hackport"+ }+ control+ $ \runInIO -> DCG.withRepoContext verbosity flags+ $ runInIO . (callback <=< restoreM)++-- | Default remote repository. Defaults to [hackage](hackage.haskell.org).+defaultRemoteRepo :: DCT.RemoteRepo+defaultRemoteRepo = DCC.addInfoForKnownRepos+ (DCT.emptyRemoteRepo (DCT.RepoName name)) { DCT.remoteRepoURI = uri }+ where+ uri = fromJust $ NU.parseURI "https://hackage.haskell.org/"+ name = "hackage.haskell.org"
src/Merge.hs view
@@ -5,13 +5,16 @@ Core functionality of the @merge@ command of @HackPort@. -}++{-# LANGUAGE ViewPatterns #-}+ module Merge ( merge , mergeGenericPackageDescription ) where import Control.Monad-import Control.Exception+import Control.Exception.Lifted import qualified Data.Text as T import qualified Data.Text.IO as T import Data.Function (on)@@ -25,14 +28,14 @@ import qualified Distribution.Package as Cabal import qualified Distribution.Version as Cabal import qualified Distribution.PackageDescription as Cabal+import qualified Distribution.Simple.Utils as Cabal import qualified Distribution.PackageDescription.PrettyPrint as Cabal (showPackageDescription) import qualified Distribution.Solver.Types.SourcePackage as CabalInstall import qualified Distribution.Solver.Types.PackageIndex as CabalInstall import Distribution.Pretty (prettyShow)-import Distribution.Verbosity-import Distribution.Simple.Utils + -- cabal-install import Distribution.Client.IndexUtils ( getSourcePackages ) import qualified Distribution.Client.GlobalFlags as CabalInstall@@ -61,6 +64,7 @@ import qualified Portage.PackageId as Portage import qualified Portage.Version as Portage import qualified Portage.Metadata as Portage+import qualified Portage.Metadata.RemoteId as Portage import qualified Portage.Overlay as Overlay import qualified Portage.Resolve as Portage import qualified Portage.Dependency as Portage@@ -71,6 +75,11 @@ import qualified Merge.Dependencies as Merge import qualified Merge.Utils as Merge +import Util+import Overlays (getOverlayPath)++import Hackport.Env+ {- Requested features: * Add files to git?@@ -87,7 +96,7 @@ -- return the available package with that version. Latest version is chosen -- if no preference. resolveVersion :: [UnresolvedSourcePackage] -> Maybe Cabal.Version -> Maybe UnresolvedSourcePackage-resolveVersion avails Nothing = Just $ L.maximumBy (comparing (Cabal.pkgVersion . CabalInstall.srcpkgPackageId)) avails+resolveVersion avails Nothing = Just $ L.maximumBy (Cabal.comparing (Cabal.pkgVersion . CabalInstall.srcpkgPackageId)) avails resolveVersion avails (Just ver) = listToMaybe (filter match avails) where match avail = ver == Cabal.pkgVersion (CabalInstall.srcpkgPackageId avail)@@ -102,84 +111,106 @@ -- -- Various information is printed in between these steps depending on the -- 'Verbosity'.-merge :: Verbosity -> CabalInstall.RepoContext -> [String] -> FilePath -> Maybe String -> IO ()-merge verbosity repoContext args overlayPath users_cabal_flags = do+merge+ :: WritesMetadata env+ => CabalInstall.RepoContext+ -> String+ -> Maybe String+ -> Env env ()+merge repoContext packageString users_cabal_flags+ = askGlobalEnv >>= \(GlobalEnv verbosity _ _) -> do+ overlayPath <- getOverlayPath (m_category, user_pName, m_version) <-- case Merge.readPackageString args of- Left err -> throwEx err+ case Merge.readPackageString packageString of+ Left err -> throw err Right (c,p,m_v) -> case m_v of Nothing -> return (c,p,Nothing) Just v -> case Portage.toCabalVersion v of- Nothing -> throwEx (ArgumentError "illegal version")+ Nothing -> throw (ArgumentError "illegal version") Just ver -> return (c,p,Just ver) - debug verbosity $ "Category: " ++ show m_category- debug verbosity $ "Package: " ++ show user_pName- debug verbosity $ "Version: " ++ show m_version+ debug $ "Category: " ++ show m_category+ debug $ "Package: " ++ show user_pName+ debug $ "Version: " ++ show m_version let user_pname_str = Cabal.unPackageName user_pName - overlay <- Overlay.loadLazy overlayPath+ overlay <- liftIO $ Overlay.loadLazy overlayPath -- portage_path <- Host.portage_dir `fmap` Host.getInfo -- portage <- Overlay.loadLazy portage_path- index <- fmap packageIndex $ getSourcePackages verbosity repoContext+ index <- fmap packageIndex $ liftIO $ getSourcePackages verbosity repoContext -- find all packages that maches the user specified package name availablePkgs <- case map snd (CabalInstall.searchByName index user_pname_str) of- [] -> throwEx (PackageNotFound user_pname_str)+ [] -> throw (PackageNotFound user_pname_str) [pkg] -> return pkg pkgs -> do let cabal_pkg_to_pn pkg = Cabal.unPackageName $ Cabal.pkgName (CabalInstall.srcpkgPackageId pkg) names = map (cabal_pkg_to_pn . L.head) pkgs- notice verbosity $ "Ambiguous names: " ++ L.intercalate ", " names+ notice $ "Ambiguous names: " ++ L.intercalate ", " names forM_ pkgs $ \ps -> do let p_name = (cabal_pkg_to_pn . L.head) ps- notice verbosity $ p_name ++ ": " ++ (L.intercalate ", " $ map (prettyShow . Cabal.pkgVersion . CabalInstall.srcpkgPackageId) ps)+ notice $ p_name ++ ": " ++ (L.intercalate ", " $ map (prettyShow . Cabal.pkgVersion . CabalInstall.srcpkgPackageId) ps) return $ concat pkgs + -- select a single package taking into account the user specified version- selectedPkg <-+ selectedPkg <- liftIO $ case resolveVersion availablePkgs m_version of Nothing -> do putStrLn "No such version for that package, available versions:" forM_ availablePkgs $ \ avail -> putStrLn (prettyShow . CabalInstall.srcpkgPackageId $ avail)- throwEx (ArgumentError "no such version for that package")+ throw (ArgumentError "no such version for that package") Just avail -> return avail ++ -- print some info- info verbosity "Selecting package:"+ info "Selecting package:" forM_ availablePkgs $ \ avail -> do let match_text | CabalInstall.srcpkgPackageId avail == CabalInstall.srcpkgPackageId selectedPkg = "* "- | otherwise = "- "- info verbosity $ match_text ++ (prettyShow . CabalInstall.srcpkgPackageId $ avail)+ | otherwise = "- "+ info $ match_text ++ (prettyShow . CabalInstall.srcpkgPackageId $ avail) let cabal_pkgId = CabalInstall.srcpkgPackageId selectedPkg norm_pkgName = Cabal.packageName (Portage.normalizeCabalPackageId cabal_pkgId)- cat <- maybe (Portage.resolveCategory verbosity overlay norm_pkgName) return m_category- mergeGenericPackageDescription verbosity overlayPath cat (CabalInstall.srcpkgDescription selectedPkg) True users_cabal_flags+ cat <- liftIO $ maybe (Portage.resolveCategory verbosity overlay norm_pkgName) return m_category+ mergeGenericPackageDescription+ cat+ (CabalInstall.srcpkgDescription selectedPkg)+ True+ users_cabal_flags + -- Maybe generate a diff let pkgPath = overlayPath </> (Portage.unCategory cat) </> (Cabal.unPackageName norm_pkgName) newPkgId = Portage.fromCabalPackageId cat cabal_pkgId- pkgDir <- listDirectory pkgPath+ pkgDir <- liftIO $ listDirectory pkgPath case Merge.getPreviousPackageId pkgDir newPkgId of- Just validPkg -> do info verbosity "Generating a diff..."- diffEbuilds overlayPath validPkg newPkgId- _ -> info verbosity "Nothing to diff!"+ Just validPkg -> do info "Generating a diff..."+ liftIO $ diffEbuilds overlayPath validPkg newPkgId+ _ -> info "Nothing to diff!" -- used to be FlagAssignment in Cabal but now it's an opaque type type CabalFlags = [(Cabal.FlagName, Bool)] -- | Generate an ebuild from a 'Cabal.GenericPackageDescription'.-mergeGenericPackageDescription :: Verbosity -> FilePath -> Portage.Category -> Cabal.GenericPackageDescription -> Bool -> Maybe String -> IO ()-mergeGenericPackageDescription verbosity overlayPath cat pkgGenericDesc fetch users_cabal_flags = do- overlay <- Overlay.loadLazy overlayPath+mergeGenericPackageDescription+ :: WritesMetadata env+ => Portage.Category+ -> Cabal.GenericPackageDescription+ -> Bool+ -> Maybe String+ -> Env env ()+mergeGenericPackageDescription cat pkgGenericDesc fetch users_cabal_flags = do+ overlayPath <- getOverlayPath+ overlay <- liftIO $ Overlay.loadLazy overlayPath let merged_cabal_pkg_name = Cabal.pkgName (Cabal.package (Cabal.packageDescription pkgGenericDesc)) merged_PN = Portage.cabal_pn_to_PN merged_cabal_pkg_name pkgdir = overlayPath </> Portage.unCategory cat </> merged_PN- existing_meta <- EM.findExistingMeta pkgdir+ existing_meta <- liftIO $ EM.findExistingMeta pkgdir let requested_cabal_flags = Merge.first_just_of [users_cabal_flags, EM.cabal_flags existing_meta] -- accepts things, like: "cabal_flag:iuse_name", "+cabal_flag", "-cabal_flag"@@ -210,7 +241,7 @@ (user_specified_fas, cf_to_iuse_rename) = read_fas requested_cabal_flags - debug verbosity "searching for minimal suitable ghc version"+ debug "searching for minimal suitable ghc version" (compiler_info, ghc_packages, pkgDesc0, _flags, pix) <- case GHCCore.minimumGHCVersionToBuildPackage pkgGenericDesc (Cabal.mkFlagAssignment user_specified_fas) of Just v -> return v Nothing -> let pn = prettyShow merged_cabal_pkg_name@@ -222,7 +253,7 @@ , " # fix " ++ pn ++ ".cabal" , " $ hackport make-ebuild " ++ cn ++ " " ++ pn ++ ".cabal" ]-+-- let (accepted_deps, skipped_deps) = Portage.partition_depends ghc_packages merged_cabal_pkg_name (Merge.exeAndLibDeps pkgDesc0) @@ -351,25 +382,25 @@ -- but using it instead of 'active_flag_descs' avoids forcing the very computation we -- are trying to warn the user about. when (length cabal_flag_descs >= 12) $- notice verbosity $ "There are up to " +++ notice $ "There are up to " ++ A.bold (show (2^(length cabal_flag_descs) :: Int)) ++ " possible flag combinations.\n" ++ A.inColor A.Yellow True A.Default "This may take a while." - debug verbosity $ "buildDepends pkgDesc0 raw: " ++ Cabal.showPackageDescription pkgDesc0- debug verbosity $ "buildDepends pkgDesc0: " ++ show (map prettyShow (Merge.exeAndLibDeps pkgDesc0))- debug verbosity $ "buildDepends pkgDesc: " ++ show (map prettyShow (Merge.buildDepends pkgDesc))+ debug $ "buildDepends pkgDesc0 raw: " ++ Cabal.showPackageDescription pkgDesc0+ debug $ "buildDepends pkgDesc0: " ++ show (map prettyShow (Merge.exeAndLibDeps pkgDesc0))+ debug $ "buildDepends pkgDesc: " ++ show (map prettyShow (Merge.buildDepends pkgDesc)) - notice verbosity $ "Accepted depends: " ++ show (map prettyShow accepted_deps)- notice verbosity $ "Skipped depends: " ++ show (map prettyShow skipped_deps)- notice verbosity $ "Dead flags: " ++ show (map pp_fa irresolvable_flag_assignments)- notice verbosity $ "Dropped flags: " ++ show (map (Cabal.unFlagName.fst) common_fa)- notice verbosity $ "Active flags: " ++ show (map Cabal.unFlagName active_flags)- notice verbosity $ "Irrelevant flags: " ++ show (map Cabal.unFlagName irrelevant_flags)+ notice $ "Accepted depends: " ++ show (map prettyShow accepted_deps)+ notice $ "Skipped depends: " ++ show (map prettyShow skipped_deps)+ notice $ "Dead flags: " ++ show (map pp_fa irresolvable_flag_assignments)+ notice $ "Dropped flags: " ++ show (map (Cabal.unFlagName.fst) common_fa)+ notice $ "Active flags: " ++ show (map Cabal.unFlagName active_flags)+ notice $ "Irrelevant flags: " ++ show (map Cabal.unFlagName irrelevant_flags) -- mapM_ print tdeps forM_ ghc_packages $- \name -> info verbosity $ "Excluded packages (comes with ghc): " ++ Cabal.unPackageName name+ \name -> info $ "Excluded packages (comes with ghc): " ++ Cabal.unPackageName name let pp_fn (cabal_fn, yesno) = b yesno ++ Cabal.unFlagName cabal_fn where b True = ""@@ -414,13 +445,13 @@ let active_flag_descs_renamed = (\f -> f { Cabal.flagName = Cabal.mkFlagName . cfn_to_iuse . Cabal.unFlagName . Cabal.flagName $ f }) <$> active_flag_descs- iuse_flag_descs <- Merge.dropIfUseExpands active_flag_descs_renamed- mergeEbuild verbosity existing_meta pkgdir ebuild iuse_flag_descs+ iuse_flag_descs <- liftIO $ Merge.dropIfUseExpands active_flag_descs_renamed+ mergeEbuild existing_meta pkgdir ebuild iuse_flag_descs when fetch $ do let cabal_pkgId = Cabal.packageId (Merge.packageDescription pkgDesc) norm_pkgName = Cabal.packageName (Portage.normalizeCabalPackageId cabal_pkgId)- fetchDigestAndCheck verbosity (overlayPath </> prettyShow cat </> prettyShow norm_pkgName)+ fetchDigestAndCheck (overlayPath </> prettyShow cat </> prettyShow norm_pkgName) $ Portage.fromCabalPackageId cat cabal_pkgId -- | Run @ebuild@ and @pkgcheck@ commands in the directory of the@@ -428,63 +459,76 @@ -- -- This will ensure well-formed ebuilds and @metadata.xml@, and will update (if possible) -- the @Manifest@ file.-fetchDigestAndCheck :: Verbosity- -> FilePath -- ^ directory of ebuild- -> Portage.PackageId -- ^ newest ebuild- -> IO ()-fetchDigestAndCheck verbosity ebuildDir pkgId =+fetchDigestAndCheck+ :: FilePath -- ^ directory of ebuild+ -> Portage.PackageId -- ^ newest ebuild+ -> Env env ()+fetchDigestAndCheck ebuildDir pkgId = do let ebuild = prettyShow (Portage.cabalPkgName . Portage.packageId $ pkgId) ++ "-" ++ prettyShow (Portage.pkgVersion pkgId) <.> "ebuild"- in withWorkingDirectory ebuildDir $ do- notice verbosity "Recalculating digests..."- emEx <- system $ "ebuild " ++ ebuild ++ " manifest > /dev/null 2>&1"+ withWorkingDirectory ebuildDir $ do+ notice "Recalculating digests..."+ emEx <- liftIO $ system $ "ebuild " ++ ebuild ++ " manifest > /dev/null 2>&1" when (emEx /= ExitSuccess) $- notice verbosity "ebuild manifest failed horribly. Do something about it!"+ notice "ebuild manifest failed horribly. Do something about it!" - notice verbosity $ "Running " ++ A.bold "pkgcheck scan..."+ notice $ "Running " ++ A.bold "pkgcheck scan..." - (psEx,psOut,_) <- readCreateProcessWithExitCode (shell "pkgcheck scan --color True") ""- - when (psEx /= ExitSuccess) $ -- this should never be true, even with QA issues.- notice verbosity $ A.inColor A.Red True A.Default "pkgcheck scan failed."- notice verbosity psOut+ (psEx,psOut,_) <- liftIO $ readCreateProcessWithExitCode (shell "pkgcheck scan --color True") "" - return ()+ when (psEx /= ExitSuccess) $ -- this should never be true, even with QA issues.+ notice $ A.inColor A.Red True A.Default "pkgcheck scan failed."+ notice psOut -withWorkingDirectory :: FilePath -> IO a -> IO a+withWorkingDirectory :: FilePath -> Env env a -> Env env a withWorkingDirectory newDir action = do- oldDir <- getCurrentDirectory+ oldDir <- liftIO $ getCurrentDirectory bracket- (setCurrentDirectory newDir)- (\_ -> setCurrentDirectory oldDir)+ (liftIO $ setCurrentDirectory newDir)+ (\_ -> liftIO $ setCurrentDirectory oldDir) (\_ -> action) -- | Write the ebuild (and sometimes a new @metadata.xml@) to its directory.-mergeEbuild :: Verbosity -> EM.EMeta -> FilePath -> E.EBuild -> [Cabal.PackageFlag] -> IO ()-mergeEbuild verbosity existing_meta pkgdir ebuild flags = do+mergeEbuild+ :: WritesMetadata env+ => EM.EMeta+ -> FilePath+ -> E.EBuild+ -> [Cabal.PackageFlag]+ -> Env env ()+mergeEbuild existing_meta pkgdir ebuild flags+ = ask >>= \(_, useHackageRemote -> useHackageRemoteId) -> do+ let edir = pkgdir elocal = E.name ebuild ++"-"++ E.version ebuild <.> "ebuild" epath = edir </> elocal emeta = "metadata.xml" mpath = edir </> emeta- yet_meta <- doesFileExist mpath+ yet_meta <- liftIO $ doesFileExist mpath -- If there is an existing @metadata.xml@, read it in as a 'T.Text'. -- Otherwise return 'T.empty'. We only use this once more to directly -- compare to @default_meta@ before writing it. current_meta <- if yet_meta- then T.readFile mpath+ then liftIO $ T.readFile mpath else return T.empty -- Either create an object of the 'Portage.Metadata' type from a valid @current_meta@, -- or supply a default minimal metadata object. Note the difference to @current_meta@: -- @current_meta@ is of type 'T.Text', @current_meta'@ is of type 'Portage.Metadata'.- let current_meta' = fromMaybe Portage.makeMinimalMetadata- (Portage.pureMetadataFromFile current_meta)+ let current_meta' = fromMaybe (Portage.minimalMetadata useHackageRemoteId ebuild)+ (Portage.parseMetadataXML current_meta)+ hackageRemoteId =+ if useHackageRemoteId+ then S.singleton $ Portage.RemoteIdHackage $ E.hackage_name ebuild+ else S.empty -- Create the @metadata.xml@ string, adding new USE flags (if any) to those of -- the existing @metadata.xml@. If an existing flag has a new and old description, -- the new one takes precedence.- default_meta = Portage.makeDefaultMetadata- $ Merge.metaFlags flags `Map.union`- Portage.metadataUseFlags current_meta'+ default_meta = Portage.printMetadata $+ current_meta' <> mempty+ { Portage.metadataUseFlags = Merge.metaFlags flags+ , Portage.metadataRemoteIds =+ Portage.matchURIs (E.sourceURIs ebuild) <> hackageRemoteId+ } -- Create a 'Map.Map' of USE flags with updated descriptions. new_flags = Map.differenceWith (\new old -> if (new /= old) then Just $ old ++ A.bold (" -> " ++ new)@@ -492,8 +536,8 @@ (Merge.metaFlags flags) $ Portage.metadataUseFlags current_meta' - createDirectoryIfMissing True edir- now <- TC.getCurrentTime+ liftIO $ createDirectoryIfMissing True edir+ now <- liftIO $ TC.getCurrentTime let (existing_keywords, existing_license) = (EM.keywords existing_meta, EM.license existing_meta) new_keywords = maybe (E.keywords ebuild) (map Merge.to_unstable) existing_keywords@@ -507,19 +551,19 @@ } s_ebuild' = E.showEBuild now ebuild' - notice verbosity $ "Current keywords: " ++ show existing_keywords ++ " -> " ++ show new_keywords- notice verbosity $ "Current license: " ++ show existing_license ++ " -> " ++ show new_license+ notice $ "Current keywords: " ++ show existing_keywords ++ " -> " ++ show new_keywords+ notice $ "Current license: " ++ show existing_license ++ " -> " ++ show new_license - notice verbosity $ "Writing " ++ elocal- length s_ebuild' `seq` T.writeFile epath (T.pack s_ebuild')+ notice $ "Writing " ++ elocal+ length s_ebuild' `seq` liftIO (T.writeFile epath (T.pack s_ebuild')) when (current_meta /= default_meta) $ do when (current_meta /= T.empty) $ do- notice verbosity $ A.bold $ "Default and current " ++ emeta ++ " differ."+ notice $ A.bold $ "Default and current " ++ emeta ++ " differ." if (new_flags /= Map.empty)- then notice verbosity $ "New or updated USE flags:\n" +++ then notice $ "New or updated USE flags:\n" ++ (unlines $ Portage.prettyPrintFlagsHuman new_flags)- else notice verbosity "No new USE flags."+ else notice "No new USE flags." - notice verbosity $ "Writing " ++ emeta- T.writeFile mpath default_meta+ notice $ "Writing " ++ emeta+ liftIO $ T.writeFile mpath default_meta
src/Merge/Dependencies.hs view
@@ -15,7 +15,6 @@ ) where import Control.DeepSeq (NFData(..))-import Control.Parallel.Strategies import Data.Maybe ( isJust, isNothing ) import qualified Data.List as L #if !MIN_VERSION_base(4,11,0)@@ -580,4 +579,5 @@ ,("SDL2_image", ("media-libs", "sdl2-image", Portage.AnySlot)) ,("SDL2_mixer", ("media-libs", "sdl2-mixer", Portage.AnySlot)) ,("zlib", ("sys-libs", "zlib", Portage.AnySlot))+ ,("libpcre", ("dev-libs", "libpcre", Portage.AnySlot)) ]
src/Merge/Utils.hs view
@@ -36,29 +36,19 @@ import qualified Distribution.Package as Cabal import qualified Distribution.PackageDescription as Cabal --- | Parse a ['String'] as a valid package string. E.g. @category\/name-1.0.0@.+-- | Parse a 'String' as a valid package string. E.g. @category\/name-1.0.0@. -- Return 'HackPortError' if the string to parse is invalid. ----- When the ['String'] is valid:+-- When the 'String' is valid: ----- >>> readPackageString ["dev-haskell/packagename1-1.0.0"]+-- >>> readPackageString "dev-haskell/packagename1-1.0.0" -- Right (Just (Category {unCategory = "dev-haskell"}),PackageName "packagename1",Just (Version {versionNumber = [1,0,0], versionChar = Nothing, versionSuffix = [], versionRevision = 0}))------ When the ['String'] is empty:------ >>> readPackageString []--- Left ...-readPackageString :: [String]+readPackageString :: String -> Either HackPortError ( Maybe Portage.Category , Cabal.PackageName , Maybe Portage.Version )-readPackageString args = do- packageString <-- case args of- [] -> Left (ArgumentError "Need an argument, [category/]package[-version]")- [pkg] -> return pkg- _ -> Left (ArgumentError ("Too many arguments: " ++ unwords args))+readPackageString packageString = do case Portage.parseFriendlyPackage packageString of Right v@(_,_,Nothing) -> return v -- we only allow versions we can convert into cabal versions
src/Overlays.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE ScopedTypeVariables #-}+ module Overlays ( getOverlayPath ) where@@ -10,47 +12,46 @@ import Error import Portage.Host---- cabal-import Distribution.Verbosity-import Distribution.Simple.Utils ( info )+import Util (info)+import Hackport.Env hiding (local) -getOverlayPath :: Verbosity -> Maybe FilePath -> IO String-getOverlayPath verbosity override_overlay = do+-- getOverlayPath :: forall m. (HasGlobalEnv m, MonadIO m) => m String+getOverlayPath :: Env env String+getOverlayPath = askGlobalEnv >>= \(GlobalEnv _ override_overlay _) -> do overlays <- if isJust override_overlay- then do info verbosity $ "Forced " ++ fromJust override_overlay+ then do info $ "Forced " ++ fromJust override_overlay return [fromJust override_overlay] else getOverlays case overlays of- [] -> throwEx NoOverlay+ [] -> throw NoOverlay [x] -> return x mul -> search mul where- search :: [String] -> IO String+ search :: [String] -> Env env String search mul = do- let loop [] = throwEx (MultipleOverlays mul)+ let loop [] = throw (MultipleOverlays mul) loop (x:xs) = do- info verbosity $ "Checking '" ++ x ++ "'..."- found <- SD.doesDirectoryExist (x </> ".hackport")+ info $ "Checking '" ++ x ++ "'..."+ found <- liftIO $ SD.doesDirectoryExist (x </> ".hackport") if found then do- info verbosity "OK!"+ info "OK!" return x else do- info verbosity "Not ok."+ info "Not ok." loop xs- info verbosity "There are several overlays in your configuration."- mapM_ (info verbosity . (" * " ++)) mul- info verbosity "Looking for one with a HackPort cache..."+ info "There are several overlays in your configuration."+ mapM_ (info . (" * " ++)) mul+ info "Looking for one with a HackPort cache..." overlay <- loop mul- info verbosity $ "I choose " ++ overlay- info verbosity "Override my decision with hackport --overlay /my/overlay"+ info $ "I choose " ++ overlay+ info "Override my decision with hackport --overlay /my/overlay" return overlay -getOverlays :: IO [String]+getOverlays :: MonadIO m => m [String] getOverlays = do local <- getLocalOverlay- overlays <- overlay_list `fmap` getInfo+ overlays <- liftIO $ overlay_list `fmap` getInfo return $ nub $ map clean $ maybeToList local ++ overlays@@ -59,11 +60,11 @@ '/':p -> reverse p _ -> path -getLocalOverlay :: IO (Maybe FilePath)+getLocalOverlay :: MonadIO m => m (Maybe FilePath) getLocalOverlay = do- curDir <- SD.getCurrentDirectory+ curDir <- liftIO $ SD.getCurrentDirectory let lookIn = map joinPath . reverse . inits . splitPath $ curDir fmap listToMaybe (filterM probe lookIn) where- probe dir = SD.doesDirectoryExist (dir </> "dev-haskell")+ probe dir = liftIO $ SD.doesDirectoryExist (dir </> "dev-haskell")
src/Portage/EBuild.hs view
@@ -38,30 +38,31 @@ #endif -- | Type representing the information contained in an @.ebuild@.-data EBuild = EBuild {- name :: String,- category :: String,- hackage_name :: String, -- might differ a bit (we mangle case)- version :: String,- revision :: String,- hackportVersion :: String,- description :: String,- homepage :: String,- license :: Either String String,- slot :: String,- keywords :: [String],- iuse :: [String],- depend :: Dependency,- depend_extra :: [String],- rdepend :: Dependency,- rdepend_extra :: [String]+data EBuild = EBuild+ { name :: String+ , category :: String+ , hackage_name :: String -- might differ a bit (we mangle case)+ , version :: String+ , revision :: String+ , hackportVersion :: String+ , sourceURIs :: [String]+ , description :: String+ , homepage :: String+ , license :: Either String String+ , slot :: String+ , keywords :: [String]+ , iuse :: [String]+ , depend :: Dependency+ , depend_extra :: [String]+ , rdepend :: Dependency+ , rdepend_extra :: [String] , features :: [CabalFeature] , cabal_pn :: Maybe String -- ^ Just 'myOldName' if the package name contains upper characters , src_prepare :: [String] -- ^ raw block for src_prepare() contents , src_configure :: [String] -- ^ raw block for src_configure() contents , used_options :: [(String, String)] -- ^ hints to ebuild writers/readers -- on what hackport options were used to produce an ebuild- }+ } getHackportVersion :: Version -> String getHackportVersion Version {versionBranch=(x:s)} = foldl (\y z -> y ++ "." ++ (show z)) (show x) s@@ -75,6 +76,7 @@ hackage_name = "FooBar", version = "0.1", revision = "0",+ sourceURIs = [], hackportVersion = getHackportVersion Paths_hackport.version, description = "", homepage = "https://hackage.haskell.org/package/${HACKAGE_N}",
src/Portage/Metadata.hs view
@@ -7,62 +7,66 @@ -} module Portage.Metadata ( Metadata(..)- , metadataFromFile- , pureMetadataFromFile+ , UseFlags+ , readMetadataFile+ , parseMetadataXML , stripGlobalUseFlags -- exported for hspec , prettyPrintFlags -- exported for hspec , prettyPrintFlagsHuman- , makeDefaultMetadata- , makeMinimalMetadata+ , printMetadata+ , minimalMetadata ) where import qualified AnsiColor as A -import qualified Data.List as L+import Control.Monad import qualified Data.Map.Strict as Map+import qualified Data.Set as S import qualified Data.Text as T import qualified Data.Text.IO as T import Text.XML.Light +import Portage.EBuild as EBuild+import Portage.Metadata.RemoteId++import Hackport.Env++type UseFlags = Map.Map String String+ -- | A data type for the Gentoo-specific @metadata.xml@ file. -- Currently defines functions for the maintainer email and -- USE flags and their descriptions. data Metadata = Metadata { metadataEmails :: [String] -- ^ This should /always/ be [\"haskell@gentoo.org\"].- , metadataUseFlags :: Map.Map String String -- ^ Only /active/ USE flags, if any.- } deriving (Eq, Show)+ , metadataUseFlags :: UseFlags -- ^ Only /active/ USE flags, if any.+ , metadataRemoteIds :: S.Set RemoteId+ } deriving (Eq, Show, Ord) +instance Semigroup Metadata where+ Metadata e1 u1 r1 <> Metadata e2 u2 r2 = Metadata (e1<>e2) (u1<>u2) (r1<>r2)++instance Monoid Metadata where+ mempty = Metadata mempty mempty mempty+ -- | Maybe return a 'Metadata' from a 'T.Text'. -- -- Trying to parse an empty 'T.Text' should return 'Nothing': ----- >>> pureMetadataFromFile T.empty+-- >>> parseMetadataXML T.empty -- Nothing------ Parsing a @metadata.xml@ /without/ USE flags should /always/ be equivalent--- to 'makeMinimalMetadata':------ >>> pureMetadataFromFile (makeDefaultMetadata Map.empty) == Just makeMinimalMetadata--- True------ Parsing a @metadata.xml@ /with/ USE flags should /always/ be equivalent--- to 'makeMinimalMetadata' /plus/ the supplied USE flags:------ >>> pureMetadataFromFile (makeDefaultMetadata (Map.fromList [("name","description")])) == Just (makeMinimalMetadata {metadataUseFlags = Map.fromList [("name","description")] } )--- True-pureMetadataFromFile :: T.Text -> Maybe Metadata-pureMetadataFromFile file = parseXMLDoc file >>= \doc -> parseMetadata doc+parseMetadataXML :: T.Text -> Maybe Metadata+parseMetadataXML = parseMetadata <=< parseXMLDoc --- | Apply 'pureMetadataFromFile' to a 'FilePath'.-metadataFromFile :: FilePath -> IO (Maybe Metadata)-metadataFromFile fp = pureMetadataFromFile <$> T.readFile fp+-- | Apply 'parseMetadataXML' to a 'FilePath'.+readMetadataFile :: FilePath -> IO (Maybe Metadata)+readMetadataFile fp = parseMetadataXML <$> T.readFile fp -- | Extract the maintainer email and USE flags from a supplied XML 'Element'. -- -- If we're parsing a blank 'Element' or otherwise empty @metadata.xml@: -- >>> parseMetadata blank_element--- Just (Metadata {metadataEmails = [], metadataUseFlags = fromList []})+-- Just (Metadata {metadataEmails = [], metadataUseFlags = fromList [], metadataRemoteIds = fromList []}) parseMetadata :: Element -> Maybe Metadata parseMetadata xml = return Metadata { metadataEmails = strContent <$> findElements (unqual "email") xml@@ -75,13 +79,15 @@ a = concatMap elContent y b = cdData <$> onlyText a in Map.fromList $ zip z b+ -- TODO: Read remote-ids from existing metadata.xml+ , metadataRemoteIds = S.empty } -- | Remove global @USE@ flags from the flags 'Map.Map', as these should not be -- within the local @metadata.xml@. For now, this is manually specified rather than -- parsing @use.desc@.-stripGlobalUseFlags :: Map.Map String String -> Map.Map String String-stripGlobalUseFlags m = foldr1 Map.intersection (Map.delete <$> globals <*> [m])+stripGlobalUseFlags :: UseFlags -> UseFlags+stripGlobalUseFlags m = foldr Map.delete m globals where globals = [ "debug" , "examples"@@ -89,41 +95,56 @@ ] -- | Pretty print as valid XML a list of flags and their descriptions--- from a given 'Map.Map'.-prettyPrintFlags :: Map.Map String String -> [String]-prettyPrintFlags m = (\(name,description) ->- "\t\t" ++- (showElement- . add_attr (Attr (blank_name { qName = "name" }) name)- . unode "flag" $ description))- <$> (Map.toAscList . stripGlobalUseFlags $ m)+-- from a given 'Map.Map'. This wraps the flags in "<use>...</use>" and is+-- meant to be passed to 'unlines'. If the Map is empty, it will return no+-- output.+prettyPrintFlags :: UseFlags -> [String]+prettyPrintFlags m+ | Map.null m = []+ | otherwise = ["\t<use>"] ++ go ++ ["\t</use>"]+ where+ go = printFlag <$> Map.toAscList (stripGlobalUseFlags m)+ printFlag (n, d) =+ "\t\t" +++ showElement+ (add_attr+ (Attr (blank_name { qName = "name" }) n)+ (unode "flag" d)+ ) -- | Pretty print a human-readable list of flags and their descriptions -- from a given 'Map.Map'.-prettyPrintFlagsHuman :: Map.Map String String -> [String]-prettyPrintFlagsHuman m = (\(name,description) -> A.bold (name ++ ": ") ++- (L.intercalate " " . lines $ description))+prettyPrintFlagsHuman :: UseFlags -> [String]+prettyPrintFlagsHuman m = (\(n,d) -> A.bold (n ++ ": ") +++ (unwords . lines $ d)) <$> (Map.toAscList . stripGlobalUseFlags $ m)- + -- | A minimal metadata for use as a fallback value.-makeMinimalMetadata :: Metadata-makeMinimalMetadata = Metadata { metadataEmails = ["haskell@gentoo.org"]- , metadataUseFlags = Map.empty- }+minimalMetadata :: UseHackageRemote -> EBuild.EBuild -> Metadata+minimalMetadata useHackage ebuild =+ mempty+ { metadataEmails = ["haskell@gentoo.org"]+ , metadataRemoteIds =+ if useHackage+ then S.singleton $ RemoteIdHackage $ EBuild.hackage_name ebuild+ else S.empty+ } -- don't use Text.XML.Light as we like our own pretty printer -- | Pretty print the @metadata.xml@ string.-makeDefaultMetadata :: Map.Map String String -> T.Text-makeDefaultMetadata flags = T.pack $- unlines [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"- , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"- , "<pkgmetadata>"- , "\t<maintainer type=\"project\">"- , "\t\t<email>haskell@gentoo.org</email>"- , "\t\t<name>Gentoo Haskell</name>"- , "\t</maintainer>"- ++ if flags == Map.empty- then ""- else "\n\t<use>\n" ++ (unlines $ prettyPrintFlags flags) ++ "\t</use>"- , "</pkgmetadata>"- ]+printMetadata :: Metadata -> T.Text+printMetadata (Metadata _ flags rids) = T.pack $+ unlines $+ [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+ , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"+ , "<pkgmetadata>"+ , "\t<maintainer type=\"project\">"+ , "\t\t<email>haskell@gentoo.org</email>"+ , "\t\t<name>Gentoo Haskell</name>"+ , "\t</maintainer>"+ ]+ ++ prettyPrintFlags flags+ ++ prettyPrintRemoteIds rids+ +++ [ "</pkgmetadata>"+ ]
+ src/Portage/Metadata/RemoteId.hs view
@@ -0,0 +1,490 @@+{-# LANGUAGE ApplicativeDo #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TupleSections #-}++module Portage.Metadata.RemoteId+ (+ -- * Remote id+ RemoteId (..)+ -- * Pretty printing+ , prettyPrintRemoteIds+ , prettyPrintRemoteId+ -- * Parsing+ , URIParser (..)+ , matchURIs+ , matchURI+ , runUriParser+ , definedParsers+ -- ** Individual parsers+ , hackageParser+ , cranParser+ , ctanParser+ , gentooParser+ , githubParser+ , gitlabParser+ , launchpadParser+ , osdnParser+ , peclParser+ , pypiParser+ , rubygemsParser+ , sourceforgeParser+ , vimParser+ -- ** Utility+ -- *** URI scheme+ , httpScheme+ -- *** URI domain+ , Domain+ , domainOrWWW+ , subdomain+ -- *** URI path+ , Path+ , stripPrefix+ , stripPrefixP+ , gitPath+ -- *** Misc+ , ignore+ , allChars+ ) where++import Control.Monad+import Data.Foldable (asum)+import qualified Data.List as L+import Data.Maybe (catMaybes, mapMaybe)+import qualified Data.Set as S+import Network.URI (URI(..), URIAuth(..), parseAbsoluteURI)+import System.FilePath.Posix+import Text.Parsec+import Text.Parsec.String++data RemoteId+ = RemoteIdHackage String -- ^ Hackage package+ | RemoteIdCRAN String -- ^ CRAN package+ | RemoteIdCTAN String -- ^ CTAN package+ | RemoteIdGentoo String -- ^ Gentoo project+ | RemoteIdGithub String String -- ^ Github user and repo+ | RemoteIdGitlab String String -- ^ Gitlab user and repo+ | RemoteIdLaunchpad String -- ^ Launchpad project+ | RemoteIdOSDN String -- ^ OSDN project+ | RemoteIdPECL String -- ^ PECL package+ | RemoteIdPyPI String -- ^ PyPI project+ | RemoteIdRubygems String -- ^ Rubygems gem+ | RemoteIdSourceforge String -- ^ Sourceforge project+ | RemoteIdVim String -- ^ Vim script+ deriving (Show, Eq, Ord)++-- | A set of parsers to use on a 'URI'. Each parser can produce an arbitrary+-- type. These intermediate types are coalesced in 'mkRemoteId' and+-- are hidden from the top-level using the @ExistentialQuantification@+-- language extension.+data URIParser = forall scheme user regname port path query fragment. URIParser+ { schemeParser :: Parser scheme -- | ^ scheme+ , userParser :: Parser user -- | ^ user+ , regnameParser :: Parser regname -- | ^ domain+ , portParser :: Parser port -- | ^ port+ , pathParser :: Parser path -- | ^ path+ , queryParser :: Parser query -- | ^ query+ , fragmentParser :: Parser fragment -- | ^ fragment+ -- | coalescing function+ , mkRemoteId :: scheme -> user -> regname -> port -> path -> query -> fragment -> RemoteId+ }++type Domain = String+type Path = String++-- | Pretty print a 'S.Set' of 'RemoteId's as XML. Wraps the block in @"<upstream>"@.+prettyPrintRemoteIds :: S.Set RemoteId -> [String]+prettyPrintRemoteIds set+ | S.null set = []+ | otherwise =+ ["\t<upstream>"]+ ++ (prettyPrintRemoteId <$> S.toAscList set)+ ++ ["\t</upstream>"]++-- | Pretty print a single 'RemoteId'.+prettyPrintRemoteId :: RemoteId -> String+prettyPrintRemoteId = \case+ RemoteIdHackage p -> pp "hackage" p+ RemoteIdCRAN p -> pp "cran" p+ RemoteIdCTAN p -> pp "ctan" p+ RemoteIdGentoo p -> pp "gentoo" p+ RemoteIdGithub u r -> pp "github" $ u ++ "/" ++ r+ RemoteIdGitlab u r -> pp "gitlab" $ u ++ "/" ++ r+ RemoteIdLaunchpad p -> pp "launchpad" p+ RemoteIdOSDN p -> pp "osdn" p+ RemoteIdPECL p -> pp "pecl" p+ RemoteIdPyPI p -> pp "pypi" p+ RemoteIdRubygems g -> pp "rubygems" g+ RemoteIdSourceforge p -> pp "sourceforge" p+ RemoteIdVim s -> pp "vim" s+ where+ pp t v = "\t\t<remote-id type=\"" ++ t ++ "\">" ++ v ++ "</remote-id>"++-- | Run 'matchURI' on all given strings, collecting the result in a 'S.Set'.+matchURIs :: [String] -> S.Set RemoteId+matchURIs = S.fromList . mapMaybe matchURI++-- | Try to parse the given string using any of the defined URI parsers+matchURI :: String -> Maybe RemoteId+matchURI str = asum $ map runUriP definedParsers+ where+ runUriP :: URIParser -> Maybe RemoteId+ runUriP p = eitherToMaybe $ runUriParser p str++ -- @Maybe@ is an Alternative, whereas @Either e@ is not. This is needed+ -- to make 'asum' work.+ eitherToMaybe :: Either e a -> Maybe a+ eitherToMaybe (Left _) = Nothing+ eitherToMaybe (Right x) = Just x++-- | All parsers defined in this module+definedParsers :: [URIParser]+definedParsers =+ [ hackageParser+ , cranParser+ , ctanParser+ , gentooParser+ , githubParser+ , gitlabParser+ , launchpadParser+ , osdnParser+ , peclParser+ , pypiParser+ , rubygemsParser+ , sourceforgeParser+ , vimParser+ ]++-- | @'hackage': 'https://hackage.haskell.org/package/{project}'@+hackageParser :: URIParser+hackageParser = URIParser+ httpScheme+ ignore+ (string "hackage.haskell.org")+ ignore+ (do+ (p:_) <- stripPrefixP "/package"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdHackage p)++-- | @'cran': 'https://cran.r-project.org/web/packages/{project}/'@+cranParser :: URIParser+cranParser = URIParser+ httpScheme+ ignore+ (string "cran.r-project.org")+ ignore+ (do+ (p:_) <- stripPrefixP "/web/packages"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdCRAN p)++-- | @'ctan': 'https://ctan.org/pkg/{project}'@+ctanParser :: URIParser+ctanParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "ctan.org")+ ignore+ (do+ (p:_) <- stripPrefixP "/pkg"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdCTAN p)++-- | @'gentoo': 'https://gitweb.gentoo.org/{project}.git/'@+gentooParser :: URIParser+gentooParser = URIParser+ httpScheme+ ignore+ (string "gitweb.gentoo.org")+ ignore+ (do+ (s:_) <- stripPrefixP "/"+ gitPath s+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdGentoo p)++-- | @'github': 'https://github.com/{project}'@+githubParser :: URIParser+githubParser = URIParser+ (choice [httpScheme, string "git:"])+ ignore+ (domainOrWWW "github.com")+ ignore+ (do+ (u:r:_) <- stripPrefixP "/"+ (u,) <$> gitPath r+ )+ ignore+ ignore+ (\_ _ _ _ (u,r) _ _ -> RemoteIdGithub u r)++-- | @'gitlab': 'https://gitlab.com/{project}'@+gitlabParser :: URIParser+gitlabParser = URIParser+ (choice [httpScheme, string "git:"])+ ignore+ (domainOrWWW "gitlab.com")+ ignore+ (do+ (u:r:_) <- stripPrefixP "/"+ (u,) <$> gitPath r+ )+ ignore+ ignore+ (\_ _ _ _ (u,r) _ _ -> RemoteIdGitlab u r)++-- | @'launchpad': 'https://launchpad.net/{project}'@+launchpadParser :: URIParser+launchpadParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "launchpad.net")+ ignore+ (do+ (p:_) <- stripPrefixP "/"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdLaunchpad p)++-- | @'osdn': 'https://osdn.net/projects/{project}/'@+osdnParser :: URIParser+osdnParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "osdn.net")+ ignore+ (do+ (p:_) <- stripPrefixP "/projects"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdOSDN p)++-- | @'pecl': 'https://pecl.php.net/package/{project}'@+peclParser :: URIParser+peclParser = URIParser+ httpScheme+ ignore+ (string "pecl.php.net")+ ignore+ (do+ (p:_) <- stripPrefixP "/package"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdPECL p)++-- | @'pypi': 'https://pypi.org/project/{project}/'@+pypiParser :: URIParser+pypiParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "pypi.org")+ ignore+ (do+ (p:_) <- stripPrefixP "/project"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdPyPI p)++-- | @'rubygems': 'https://rubygems.org/gems/{project}'@+rubygemsParser :: URIParser+rubygemsParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "rubygems.org")+ ignore+ (do+ (g:_) <- stripPrefixP "/gems"+ pure g+ )+ ignore+ ignore+ (\_ _ _ _ g _ _ -> RemoteIdRubygems g)++-- | @'sourceforge': 'https://sourceforge.net/projects/{project}/'@+sourceforgeParser :: URIParser+sourceforgeParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "sourceforge.net")+ ignore+ (do+ (p:_) <- stripPrefixP "/projects"+ pure p+ )+ ignore+ ignore+ (\_ _ _ _ p _ _ -> RemoteIdSourceforge p)++-- | @'vim': 'https://vim.org/scripts/script.php?script_id={project}'@+vimParser :: URIParser+vimParser = URIParser+ httpScheme+ ignore+ (domainOrWWW "vim.org")+ ignore+ (string "/scripts/script.php")+ (do+ _ <- char '?'+ ss <- sepBy1 (optionMaybe scriptParser) (char '&')+ (s:_) <- pure $ catMaybes ss -- The first successful 'scriptParser'+ pure s+ )+ ignore+ (\_ _ _ _ _ s _ -> RemoteIdVim s)+ where+ scriptParser :: Parser String+ scriptParser = string "script_id=" *> many1 (noneOf ['=','&','#'])++-- | Run a specified 'URIParser' with a string+--+-- Internally, uses 'parseAbsoluteURI' to create a 'URI', and then uses each+-- parser specified in 'URIParser' on a specific part of the uri. These+-- intermediate results are coalesced with the supplied 'mkRemoteId'.+runUriParser+ :: URIParser+ -> String+ -> Either ParseError RemoteId+runUriParser (URIParser {..}) = join . parseIt go+ where+ go :: Parser (Either ParseError RemoteId)+ go = do+ cs <- allChars+ case parseAbsoluteURI cs of+ Just (URI scheme (Just (URIAuth user regname port)) path query fragment) ->+ pure $ mkRemoteId+ <$> parseIt schemeParser scheme+ <*> parseIt userParser user+ <*> parseIt regnameParser regname+ <*> parseIt portParser port+ <*> parseIt pathParser path+ <*> parseIt queryParser query+ <*> parseIt fragmentParser fragment+ _ -> fail $ "Could not parse as an absolute URI: " ++ show cs++ parseIt :: Parser a -> String -> Either ParseError a+ parseIt p = parse p ""++-- | Convenience function for 'stripPrefix', which uses 'allChars' as the+-- target path. Throws a parse error if 'stripPrefix' fails.+stripPrefixP+ :: Path -- ^ The prefix path to strip+ -> Parser [String]+stripPrefixP pre = do+ targ <- allChars+ case stripPrefix pre targ of+ Just ps -> pure ps+ Nothing -> fail $ "Path prefix does not match: \n"+ ++ "pre = " ++ show pre ++ "\n"+ ++ "targ = " ++ show targ ++ "\n"+ ++ "L.stripPrefix " ++ show (splitDirectories pre)+ ++ " " ++ show (splitDirectories targ) ++ " = Nothing"++-- | Strips a path of a prefix, then returns the result split along path+-- seperators. Returns 'Nothing' if the prefix path does not match the+-- beginning of the target path.+--+-- Examples:+--+-- >>> stripPrefix "/web/packages" "/web/packages/foo/"+-- Just ["foo"]+--+-- >>> stripPrefix "/" "/foo/bar"+-- Just ["foo","bar"]+--+-- >>> stripPrefix "" "/foo/bar"+-- Just ["/","foo","bar"]+--+-- >>> stripPrefix "/some/thing" "/something/else"+-- Nothing+stripPrefix+ :: Path -- ^ The prefix path to strip+ -> Path -- ^ The target path to strip from+ -> Maybe [String]+stripPrefix pre targ =+ L.stripPrefix+ (splitDirectories pre)+ (splitDirectories targ)++-- | Compares the input stream to the given domain. Parser succeeds if+-- either of the following is true:+--+-- * The input stream matches the target domain exactly+-- * The input stream matches the target domain prepended by @"www."@+--+-- e.g.+--+-- > domainOrWWW "github.com"+--+-- will match on @"github.com"@ or @"www.github.com"@+domainOrWWW+ :: Domain+ -> Parser ()+domainOrWWW targ = do+ sub <- allChars+ if sub == ("www." ++ targ) || sub == targ+ then pure ()+ else fail $ "domainOrWWW did not match:"+ ++ "\nsub: " ++ show sub+ ++ "\ntarg: " ++ show targ++-- | Remove any ".git" suffix from the specified string+gitPath+ :: String+ -> Parser String+gitPath p = case parse go "" p of+ Left e -> fail $ show e -- Not ideal, but it works+ Right r -> pure r+ where+ go :: Parser String+ go = choice+ [ try $ manyTill anyChar (string ".git") <* eof+ , allChars+ ]++-- | Compares the input stream to the given domain. Parser succeeds if the+-- input stream is a subdomain of the target.+subdomain+ :: Domain -- ^ Target domain to match against+ -> Parser ()+subdomain targ = do+ sub <- allChars+ if targ `L.isSuffixOf` sub+ then pure ()+ else fail $ show sub ++ " is not a subdomain of " ++ show targ++ignore :: Parser ()+ignore = pure ()++-- | Matches either of the strings @"http:"@ or @"https:"@+httpScheme :: Parser String+httpScheme = choice+ [ try $ string "https:"+ , string "http:"+ ]++-- | Match on every character from the input stream+allChars :: Parser String+allChars = many anyChar
src/Portage/Overlay.hs view
@@ -19,6 +19,7 @@ import Distribution.Parsec (simpleParsec) import Distribution.Simple.Utils ( comparing, equating ) +import Control.Monad.IO.Class import Data.List as List import qualified Data.Map as Map import Data.Map (Map)@@ -60,10 +61,10 @@ in cabal_pn == overlay_pn && (not (null ebs))) om om = overlayMap overlay -loadLazy :: FilePath -> IO Overlay+loadLazy :: MonadIO m => FilePath -> m Overlay loadLazy path = do dir <- getDirectoryTree path- metadata <- unsafeInterleaveIO $ mkMetadataMap path dir+ metadata <- liftIO $ unsafeInterleaveIO $ mkMetadataMap path dir return $ mkOverlay metadata $ readOverlayByPackage dir where allowed v = case v of@@ -92,7 +93,7 @@ mkMetadataMap :: FilePath -> DirectoryTree -> IO (Map Portage.PackageName Portage.Metadata) mkMetadataMap root dir = fmap (Map.mapMaybe id) $- traverse Portage.metadataFromFile $+ traverse Portage.readMetadataFile $ Map.fromList [ (Portage.mkPackageName category package, root </> category </> package </> "metadata.xml") | Directory category packages <- dir@@ -162,8 +163,8 @@ type DirectoryTree = [DirectoryEntry] data DirectoryEntry = File FilePath | Directory FilePath [DirectoryEntry] -getDirectoryTree :: FilePath -> IO DirectoryTree-getDirectoryTree = dirEntries+getDirectoryTree :: MonadIO m => FilePath -> m DirectoryTree+getDirectoryTree = liftIO . dirEntries where dirEntries :: FilePath -> IO [DirectoryEntry]
src/Portage/Resolve.hs view
@@ -40,7 +40,7 @@ return devhaskell else do warn verbosity "Multiple matches and no known default. Override by specifying " warn verbosity "package category like so 'hackport merge categoryname/package[-version]."- throwEx (ArgumentError "Specify package category and try again.")+ throw (ArgumentError "Specify package category and try again.") where devhaskell = Portage.Category "dev-haskell"
src/Status.hs view
@@ -1,8 +1,5 @@ module Status- ( FileStatus(..)- , StatusDirection(..)- , fromStatus- , status+ ( status , runStatus ) where @@ -27,9 +24,8 @@ import Control.Monad -- cabal-import qualified Distribution.Verbosity as Cabal import qualified Distribution.Package as Cabal (pkgName)-import qualified Distribution.Simple.Utils as Cabal (comparing, die', equating)+import qualified Distribution.Simple.Utils as Cabal (die', equating) import Distribution.Pretty (prettyShow) import Distribution.Parsec (simpleParsec) @@ -39,46 +35,20 @@ import qualified Distribution.Solver.Types.PackageIndex as CabalInstall import qualified Distribution.Solver.Types.SourcePackage as CabalInstall ( SourcePackage(..) ) -data StatusDirection- = PortagePlusOverlay- | OverlayToPortage- | HackageToOverlay- deriving Eq--data FileStatus a- = Same a- | Differs a a- | OverlayOnly a- | PortageOnly a- | HackageOnly a- deriving (Show,Eq)--instance Ord a => Ord (FileStatus a) where- compare = Cabal.comparing fromStatus--instance Functor FileStatus where- fmap f st =- case st of- Same a -> Same (f a)- Differs a b -> Differs (f a) (f b)- OverlayOnly a -> OverlayOnly (f a)- PortageOnly a -> PortageOnly (f a)- HackageOnly a -> HackageOnly (f a)--fromStatus :: FileStatus a -> a-fromStatus fs =- case fs of- Same a -> a- Differs a _ -> a -- second status is lost- OverlayOnly a -> a- PortageOnly a -> a- HackageOnly a -> a+import Overlays+import Status.Types+import Hackport.Env+import Hackport.Util -loadHackage :: Cabal.Verbosity -> CabalInstall.RepoContext -> Overlay -> IO [[PackageId]]-loadHackage verbosity repoContext overlay = do- CabalInstall.SourcePackageDb { CabalInstall.packageIndex = pindex } <- CabalInstall.getSourcePackages verbosity repoContext+loadHackage+ :: CabalInstall.RepoContext+ -> Overlay+ -> Env env [[PackageId]]+loadHackage repoContext overlay = askGlobalEnv >>= \(GlobalEnv verbosity _ _) -> do+ CabalInstall.SourcePackageDb { CabalInstall.packageIndex = pindex } <-+ liftIO $ CabalInstall.getSourcePackages verbosity repoContext let get_cat cabal_pkg = case resolveCategories overlay (Cabal.pkgName cabal_pkg) of [] -> Category "dev-haskell" [cat] -> cat@@ -89,10 +59,12 @@ (CabalInstall.allPackagesByName pindex) return pkg_infos -status :: Cabal.Verbosity -> FilePath -> FilePath -> CabalInstall.RepoContext -> IO (Map PackageName [FileStatus ExistingEbuild])-status verbosity portdir overlaydir repoContext = do+status :: CabalInstall.RepoContext -> Env StatusEnv (Map PackageName [FileStatus ExistingEbuild])+status repoContext = do+ portdir <- getPortageDir+ overlaydir <- getOverlayPath overlay <- loadLazy overlaydir- hackage <- loadHackage verbosity repoContext overlay+ hackage <- loadHackage repoContext overlay portage <- filterByEmail ("haskell@gentoo.org" `elem`) <$> loadLazy portdir let (over, both, port) = portageDiff (overlayMap overlay) (overlayMap portage) @@ -138,17 +110,18 @@ ebuilds <- Map.lookup (packageId pkgid) overlay List.find (\e -> ebuildId e == pkgid) ebuilds -runStatus :: Cabal.Verbosity -> FilePath -> FilePath -> StatusDirection -> [String] -> CabalInstall.RepoContext -> IO ()-runStatus verbosity portdir overlaydir direction pkgs repoContext = do+runStatus :: CabalInstall.RepoContext -> Env StatusEnv ()+runStatus repoContext =+ ask >>= \(GlobalEnv verbosity _ _, StatusEnv direction pkgs) -> do let pkgFilter = case direction of OverlayToPortage -> toPortageFilter PortagePlusOverlay -> id HackageToOverlay -> fromHackageFilter pkgs' <- forM pkgs $ \p -> case simpleParsec p of- Nothing -> Cabal.die' verbosity ("Could not parse package name: " ++ p ++ ". Format cat/pkg")+ Nothing -> liftIO $ Cabal.die' verbosity ("Could not parse package name: " ++ p ++ ". Format cat/pkg") Just pn -> return pn- tree0 <- status verbosity portdir overlaydir repoContext+ tree0 <- status repoContext let tree = pkgFilter tree0 if (null pkgs') then statusPrinter tree@@ -193,8 +166,8 @@ HackageOnly _ | not (null inEbuilds) -> Just sts _ -> Nothing -statusPrinter :: Map PackageName [FileStatus ExistingEbuild] -> IO ()-statusPrinter packages = do+statusPrinter :: MonadIO m => Map PackageName [FileStatus ExistingEbuild] -> m ()+statusPrinter packages = liftIO $ do putStrLn $ toColor (Same "Green") ++ ": package in portage and overlay are the same" putStrLn $ toColor (Differs "Yellow" "") ++ ": package in portage and overlay differs" putStrLn $ toColor (OverlayOnly "Red") ++ ": package only exist in the overlay"@@ -236,8 +209,8 @@ -- | Compares two ebuilds, returns True if they are equal. -- Disregards comments.-equals :: FilePath -> FilePath -> IO Bool-equals fp1 fp2 = do+equals :: MonadIO m => FilePath -> FilePath -> m Bool+equals fp1 fp2 = liftIO $ do -- don't leave halfopenfiles f1 <- BS.readFile fp1 f2 <- BS.readFile fp2
+ src/Status/Types.hs view
@@ -0,0 +1,42 @@+module Status.Types+ ( FileStatus(..)+ , StatusDirection(..)+ , fromStatus+ ) where++import qualified Distribution.Simple.Utils as Cabal (comparing)++data StatusDirection+ = PortagePlusOverlay+ | OverlayToPortage+ | HackageToOverlay+ deriving Eq++data FileStatus a+ = Same a+ | Differs a a+ | OverlayOnly a+ | PortageOnly a+ | HackageOnly a+ deriving (Show,Eq)++instance Ord a => Ord (FileStatus a) where+ compare = Cabal.comparing fromStatus++instance Functor FileStatus where+ fmap f st =+ case st of+ Same a -> Same (f a)+ Differs a b -> Differs (f a) (f b)+ OverlayOnly a -> OverlayOnly (f a)+ PortageOnly a -> PortageOnly (f a)+ HackageOnly a -> HackageOnly (f a)++fromStatus :: FileStatus a -> a+fromStatus fs =+ case fs of+ Same a -> a+ Differs a _ -> a -- second status is lost+ OverlayOnly a -> a+ PortageOnly a -> a+ HackageOnly a -> a
src/Util.hs view
@@ -8,11 +8,16 @@ module Util ( run_cmd -- :: String -> IO (Maybe String)+ , debug+ , notice+ , info ) where import System.IO import System.Process import System.Exit (ExitCode(..))+import qualified Distribution.Simple.Utils as Cabal+import Hackport.Env -- 'run_cmd' executes command and returns it's standard output -- as 'String'.@@ -29,3 +34,12 @@ return $ if (output == "" || exitCode /= ExitSuccess) then Nothing else Just output++debug :: (HasGlobalEnv m, MonadIO m) => String -> m ()+debug s = askGlobalEnv >>= \(GlobalEnv v _ _) -> liftIO $ Cabal.debug v s++notice :: (HasGlobalEnv m, MonadIO m) => String -> m ()+notice s = askGlobalEnv >>= \(GlobalEnv v _ _) -> liftIO $ Cabal.notice v s++info :: (HasGlobalEnv m, MonadIO m) => String -> m ()+info s = askGlobalEnv >>= \(GlobalEnv v _ _) -> liftIO $ Cabal.info v s
− tests/Merge/UtilsSpec.hs
@@ -1,153 +0,0 @@-module Merge.UtilsSpec (spec) where--import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck-import QuickCheck.Instances--import Control.Applicative (liftA2)-import qualified Data.Map.Strict as Map-import Data.Maybe (catMaybes)-import qualified Data.List as L--import Error-import Merge.Utils-import Portage.PackageId--import qualified Distribution.Package as Cabal-import qualified Distribution.PackageDescription as Cabal-import Distribution.Pretty (prettyShow)--spec :: Spec-spec = do- describe "readPackageString" $ do- prop "returns a Right tuple containing the parsed information or a Left ArgumentError" $ do- let cat = Category "dev-haskell"- name = Cabal.mkPackageName "package-name1"- in \(ComplexVersion version) ->- readPackageString [prettyShow cat ++ "/" ++ prettyShow name ++- if (versionNumber version) == []- then ""- else "-" ++ prettyShow version]- `shouldBe`- if (versionChar version) /= Nothing ||- (versionSuffix version) /= [] ||- (versionRevision version) /= 0- then Left (ArgumentError ("Could not parse [category/]package[-version]: "- ++ prettyShow cat ++ "/" ++- prettyShow name ++- if (versionNumber version) == []- then ""- else "-" ++ prettyShow version))- else Right ( Just cat- , name- , if (versionNumber version) == []- then Nothing- else Just version- )- it "returns a Left HackPortError if the package string is empty" $ do- readPackageString []- `shouldBe`- Left (ArgumentError "Need an argument, [category/]package[-version]")- prop "returns a Left HackPortError if fed too many arguments" $ do- \(NonEmpty args) ->- if length args > 1- then readPackageString args `shouldBe`- Left (ArgumentError ("Too many arguments: " ++ unwords args))- else readPackageString args `shouldNotBe`- Left (ArgumentError ("Too many arguments: " ++ unwords args))- - describe "getPreviousPackageId" $ do- context "when there is a previous version available" $ do- it "returns the PackageId of the previous version" $ do- let ebuildDir = [ "foo-bar2-3.0.0b_rc2-r1.ebuild"- , "foo-bar2-3.0.0b_rc2-r2.ebuild"- , "foo-bar2-3.0.1.ebuild"- , "metadata.xml"- , "Manifest"- , "files"- ]- newPkgId = PackageId (PackageName (Category "dev-haskell")- (Cabal.mkPackageName "foo-bar2"))- (Version [3,0,2] Nothing [] 0 )- in getPreviousPackageId ebuildDir newPkgId `shouldBe`- Just (PackageId (PackageName (Category "dev-haskell")- (Cabal.mkPackageName "foo-bar2"))- (Version [3,0,1] Nothing [] 0))- context "if there is no previous version available" $ do- it "returns Nothing" $ do- let ebuildDir = [ "foo-bar2-3.0.0b_rc2-r1.ebuild"- , "foo-bar2-3.0.0b_rc2-r2.ebuild"- , "foo-bar2-3.0.1.ebuild"- , "metadata.xml"- , "Manifest"- , "files"- ]- newPkgId = PackageId (PackageName (Category "dev-haskell")- (Cabal.mkPackageName "foo-bar2"))- (Version [3,0,0] (Just 'a') [] 0 )- in getPreviousPackageId ebuildDir newPkgId `shouldBe` Nothing-- describe "drop_prefix" $ do- context "when an IUSE has a with/use prefix" $ do- prop "drops the prefix" $ do- let prefix = ["with","use"]- sep = ["-","_"]- prefixes = liftA2 (++) prefix sep- \flag -> L.nub (drop_prefix <$> (liftA2 (++) prefixes [flag])) == [flag]- context "when an IUSE has neither a with nor a use prefix" $ do- prop "preserves the existing string" $ do- \prefix flag -> L.nub (drop_prefix <$> (liftA2 (++) [prefix] ['-':flag])) ==- if prefix == "with" || prefix == "use"- then [flag]- else [prefix ++ '-':flag]-- describe "squash_debug" $ do- it "squashes debug-related flags under the debug global USE flag" $ do- squash_debug "use-debug-foo" `shouldBe` "debug"- squash_debug "debug-foo" `shouldBe` "debug"- squash_debug "foo-debugger" `shouldBe` "debug"- it "ignores debug-unrelated flags" $ do- squash_debug "foo-bar" `shouldBe` "foo-bar"-- describe "convert_underscores" $ do- it "converts underscores (_) into hyphens (-)" $ do- convert_underscores "foo_bar" `shouldBe` "foo-bar"- it "ignores mangling of separators other than underscores" $ do- convert_underscores "foo-bar" `shouldBe` "foo-bar"- it "ignores mangling of characters which are not separators" $ do- convert_underscores "foobar" `shouldBe` "foobar"-- describe "mangle_iuse" $ do- it "performs all IUSE mangling" $ do- mangle_iuse "use_foo_bar" `shouldBe` "foo-bar"- mangle_iuse "with-baz-quux" `shouldBe` "baz-quux"- mangle_iuse "use_debugging-symbols" `shouldBe` "debug"- mangle_iuse "foo-bar-baz_quux" `shouldBe` "foo-bar-baz-quux"-- describe "to_unstable" $ do- prop "creates an unstable keyword from a stable keyword, or preserves a mask" $ do- \a -> to_unstable a ==- case a of- '~':_ -> a- '-':_ -> a- _ -> '~':a-- describe "metaFlags" $ do- prop "converts a [Cabal.PackageFlag] into a Map.Map String String" $ do- \name desc -> metaFlags [(Cabal.emptyFlag (Cabal.mkFlagName name))- { Cabal.flagDescription = desc }] ==- Map.fromList [(mangle_iuse name,desc)]-- describe "dropIfUseExpand" $ do- it "drops a USE flag if it is a USE_EXPAND, otherwise preserves it" $ do- let use_expands = ["cpu_flags_x86","cpu_flags_arm"]- flags = Cabal.emptyFlag . Cabal.mkFlagName <$>- [ "cpu_flags_x86_sse4_2"- , "foo"- , "bar"- , "baz"- , "cpu_flags_arm_v8"- ]- Cabal.unFlagName . Cabal.flagName <$> catMaybes (dropIfUseExpand use_expands <$> flags)- `shouldBe` ["foo","bar","baz"]
− tests/Portage/CabalSpec.hs
@@ -1,69 +0,0 @@-module Portage.CabalSpec (spec) where--import Test.Hspec--import qualified Distribution.SPDX as SPDX--import Portage.Cabal--spec :: Spec-spec = do- describe "convertLicense" $ do- it "converts AGPL_3_0_or_later into AGPL-3+" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.AGPL_3_0_or_later)- `shouldBe`- Right "AGPL-3+"- it "converts AGPL_3_0_only into AGPL-3" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.AGPL_3_0_only)- `shouldBe`- Right "AGPL-3"- it "converts GPL_2_0_or_later into GPL-2+" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_2_0_or_later)- `shouldBe`- Right "GPL-2+"- it "converts GPL_2_0_only into GPL-2" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_2_0_only)- `shouldBe`- Right "GPL-2"- it "converts GPL_3_0_or_later into GPL-3+" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_3_0_or_later)- `shouldBe`- Right "GPL-3+"- it "converts GPL_3_0_only into GPL-3" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_3_0_only)- `shouldBe`- Right "GPL-3"- -- Unfortunately, Cabal treats LGPL_2_0_only and LGPL_2_0_or_later as identical,- -- just as it does with LGPL_2_1_only and LGPL_2_1_or_later. This means- -- we cannot handle LGPL-2.0+ or LGPL-2,1+ without directly dealing with- -- the SPDX licence, before it is converted into a Cabal-style licence.- it "converts LGPL_2_0_or_later into LGPL-2, without a trailing plus (+)" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_0_or_later)- `shouldBe`- Right "LGPL-2"- it "converts LGPL_2_0_only into LGPL-2" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_0_only)- `shouldBe`- Right "LGPL-2"- it "converts LGPL_2_1_or_later into LGPL-2.1, without a trailing plus (+)" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_1_or_later)- `shouldBe`- Right "LGPL-2.1"- it "converts LGPL_2_1_only into LGPL-2.1" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_1_only)- `shouldBe`- Right "LGPL-2.1"- -- But these next two cases are correctly handled:- it "converts LGPL_3_0_or_later into LGPL-3+" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_3_0_or_later)- `shouldBe`- Right "LGPL-3+"- it "converts LGPL_3_0_only into LGPL-3" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_3_0_only)- `shouldBe`- Right "LGPL-3"- context "when a licence string is invalid" $ do- it "converts the unknown licence into an error string" $ do- convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.EUPL_1_1)- `shouldBe`- Left "license unknown to cabal. Please pick it manually."
− tests/Portage/Dependency/PrintSpec.hs
@@ -1,327 +0,0 @@-module Portage.Dependency.PrintSpec (spec) where--import Test.Hspec--import Control.Monad (forM_)-import Data.List (intercalate)--import qualified Portage.PackageId as P--import qualified Portage.Dependency.Builder as P-import qualified Portage.Dependency.Print as P-import qualified Portage.Dependency.Types as P-import qualified Portage.Dependency.Normalize as P--import qualified Portage.Use as P--spec :: Spec-spec = do- describe "dep2str" $ do- it "converts an empty Dependency into an empty string" $ do- let d_all = P.DependAllOf- d_any = P.DependAnyOf- d_use u dep = P.mkUseDependency (True, P.Use u) dep- deps = [ d_all []- , d_any []- , d_use "f" (d_all [])- , P.DependAllOf [d_any []]- , P.DependAnyOf [d_all []]- -- Deep Useless Use Tree :]- , d_use "f" $- d_use "g" $- d_all [d_any []- , d_use "h" $- d_all [ d_all []- , d_any []- ]- , d_use "i" $- d_all [ d_all []- , d_any []- ]- ]- ]- forM_ deps $ \d ->- P.dep2str 0 (P.normalize_depend d) `shouldBe` ""-- it "converts a Dependency into a normalized string" $ do- let def_attr = P.DAttr P.AnySlot []- p_v v = P.Version { P.versionNumber = v- , P.versionChar = Nothing- , P.versionSuffix = []- , P.versionRevision = 0- }- d_all = P.DependAllOf- d_any = P.DependAnyOf- d_ge pn v = P.DependAtom $ P.Atom pn- (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)- def_attr- d_lt pn v = P.DependAtom $ P.Atom pn- (P.DRange P.ZeroB (P.StrictUB $ p_v v))- def_attr- -- for the deps_norm test, formerly normalize_deps- pnm = P.mkPackageName "dev-haskell" "mtl"- pnp = P.mkPackageName "dev-haskell" "parsec"- pnq = P.mkPackageName "dev-haskell" "quickcheck"- d_p pn = P.DependAtom $ P.Atom (P.mkPackageName "c" pn)- (P.DRange P.ZeroB P.InfinityB)- def_attr- d_use u d = P.mkUseDependency (True, P.Use u) d- d_nuse u d = P.mkUseDependency (False, P.Use u) d-- deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"- ( d_all [ d_any [ d_all [ d_ge pnm [2, 0]- , d_lt pnm [2, 1]- ]- , d_all [ d_ge pnm [2, 1, 1]- , d_lt pnm [2, 2]- ]- ]- ]- , [ "|| ( ( >=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.1 )"- , " ( >=dev-haskell/mtl-2.1.1 <dev-haskell/mtl-2.2 ) )"- ]- )- -- remove duplicate entries- , ( let d = d_all [d_ge pnm [2, 0], d_lt pnm [2, 2]]- in d_all [d, d]- , [ ">=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.2" ]- )- ]- forM_ deps $ \(d,expected) ->- P.dep2str 0 (P.normalize_depend d) `shouldBe` (intercalate "\n" expected)-- -- from normalize_deps.hs- let deps_norm = [ ( d_all [ d_ge pnm [1,0]- , d_use "foo" (d_all [ d_ge pnm [1,0] -- duplicate- , d_ge pnp [2,1]- ])- ]- , [ ">=dev-haskell/mtl-1.0"- , "foo? ( >=dev-haskell/parsec-2.1 )"- ]- )- , ( d_all [ d_ge pnm [1,0]- , d_use "foo" (d_any [ d_ge pnm [1,0] -- already satisfied- , d_ge pnp [2,1]- ])- ]- , [ ">=dev-haskell/mtl-1.0" ]- )- , ( d_any [ d_all [ d_ge pnm [1,0] -- common subdep- , d_ge pnq [1,2]- ]- , d_all [ d_ge pnm [1,0]- , d_ge pnp [3,1]- ]- ]- , [ ">=dev-haskell/mtl-1.0"- , "|| ( >=dev-haskell/parsec-3.1"- , " >=dev-haskell/quickcheck-1.2 )"- ]- )- , ( d_all [ d_use "foo" $ d_use "bar" $ d_ge pnm [1,0]- , d_use "bar" $ d_use "foo" $ d_ge pnm [1,0]- ]- , [ "bar? ( foo? ( >=dev-haskell/mtl-1.0 ) )" ]- )- , ( d_all [ d_use "foo" $ d_ge pnm [1,0]- , d_use "foo" $ d_ge pnp [3,1]- ]- , [ "foo? ( >=dev-haskell/mtl-1.0"- , " >=dev-haskell/parsec-3.1 )"- ]- )- , ( d_all [ d_use "a" $ d_use "b" $ d_ge pnm [1,0]- , d_nuse "a" $ d_use "b" $ d_ge pnm [1,0]- ]- , [ "b? ( >=dev-haskell/mtl-1.0 )"- ]- )- -- 'test?' is special- , ( d_use "a" $ d_use "test" $ d_ge pnm [1,0]- , [ "test? ( a? ( >=dev-haskell/mtl-1.0 ) )"- ]- )- , -- pop context for complementary depends, like- -- a? ( x y a ) !a? ( x y na )- -- leads to- -- x y a? ( a ) !a? ( na )- ( d_all [ d_use "a" $ d_all $ map d_p [ "x", "y", "a" ]- , d_nuse "a" $ d_all $ map d_p [ "x", "y", "na" ]- ]- , [ "c/x"- , "c/y"- , "a? ( c/a )"- , "!a? ( c/na )"- ]- )- , -- push stricter context into less strict- ( d_all [ d_ge pnm [2,0]- , d_use "a" $ d_ge pnm [1,0]- ]- ,- [ ">=dev-haskell/mtl-2.0" ]- )- , -- propagate use guarded depend into deeply nested one- ( d_all [ d_use "a" $ d_all $ map d_p [ "x" ]- , d_use "test" $ d_use "a" $ d_all $ map d_p [ "x", "t" ]- ]- , [ "test? ( a? ( c/t ) )"- , "a? ( c/x )"- ]- )- , -- lift nested use context for complementary depends- -- a? ( b? ( y ) ) b? ( x )- ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]- , d_nuse "a" $ d_use "b" $ d_p "x"- ]- , [ "a? ( b? ( c/y ) )"- , "b? ( c/x )"- ]- )- , -- more advanced lift of complementary deps- -- a? ( b? ( x y ) )- -- !a? ( b? ( y z ) )- ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]- , d_nuse "a" $ d_use "b" $ d_all $ map d_p [ "y", "z" ]- ]- , [ "a? ( b? ( c/x ) )"- , "!a? ( b? ( c/z ) )"- , "b? ( c/y )"- ]- )- , -- completely expanded set of USEs- -- a? ( b? ( c? ( x y z ) )- -- a? ( b? ( !c? ( x y ) )- -- a? ( !b? ( c? ( x z ) )- -- a? ( !b? ( !c? ( x ) )- --- -- !a? ( b? ( c? ( y z ) )- -- !a? ( b? ( !c? ( y ) )- -- !a? ( !b? ( c? ( z ) )- -- !a? ( !b? ( !c? ( ) )- ( d_all [ d_use "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "x", "y", "z" ]- , d_use "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "x", "y" ]- , d_use "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "x", "z" ]- , d_use "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ "x" ]- , d_nuse "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "y", "z" ]- , d_nuse "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "y" ]- , d_nuse "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "z" ]- , d_nuse "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ ]- ]- , [ "a? ( c/x )"- , "b? ( c/y )"- , "c? ( c/z )"- ]- )- , -- pop simple common subdepend- -- a? ( b? ( d ) )- -- !a? ( b? ( d ) )- ( d_all [ d_use "a" $ d_use "b" $ d_p "d"- , d_nuse "a" $ d_use "b" $ d_p "d"- ]- , [ "b? ( c/d )"- ]- )- , -- pop '|| ( some thing )' depend- ( let any_part = d_any $ map d_p ["a", "b"] in- d_all [ d_use "u" $ d_all [ any_part , d_p "z"]- , d_nuse "u" $ any_part- ]- , [ "|| ( c/a"- , " c/b )"- , "u? ( c/z )"- ]- )- , -- simplify slightly more complex counterguard- -- v? ( c/d )- -- u? ( !v? ( c/d ) )- -- to- -- v? ( c/d )- -- u? ( c/d )- ( d_all [ d_use "v" $ d_p "d"- , d_use "u" $ d_nuse "v" $ d_p "d"- ]- , [ "u? ( c/d )"- , "v? ( c/d )"- ]- )- - , -- ffi? ( c/d c/e )- -- !ffi ( !gmp ( c/d c/e ) )- -- gmp? ( c/d c/e )- -- to- -- ( c/d c/e )- ( let de = d_all [ d_p "d" , d_p "e" ]- in d_all [ d_use "ffi" de- , d_nuse "ffi" $ d_nuse "gmp" $ de- , d_use "gmp" $ de- ]- , [ "c/d"- , "c/e"- ]- )- - {- TODO: another popular case- , -- simplify even more complex counterguard- -- u? ( c/d )- -- !u? ( v? ( c/d ) )- -- to- -- u? ( c/d )- -- v? ( c/d )- ( d_all [ d_use "u" $ d_p "d"- , d_nuse "u" $ d_use "v" $ d_p "d"- ]- , [ "u? ( c/d )"- , "v? ( c/d )"- ]- )- -}- ]- forM_ deps_norm $ \(d, expected) ->- (P.dep2str 0 $ P.normalize_depend d) `shouldBe` (intercalate "\n" expected)-- describe "dep2str_noindent" $ do- it "converts a Dependency into a string" $ do- let pn = P.mkPackageName "dev-haskell" "mtl"- def_attr = P.DAttr P.AnySlot []- p_v v = P.Version { P.versionNumber = v- , P.versionChar = Nothing- , P.versionSuffix = []- , P.versionRevision = 0- }- d_all = P.DependAllOf- d_any = P.DependAnyOf- d_ge v = P.DependAtom $ P.Atom pn- (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)- def_attr- d_lt v = P.DependAtom $ P.Atom pn- (P.DRange P.ZeroB (P.StrictUB $ p_v v))- def_attr- deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"- ( d_all [ d_any [ d_all [ d_ge [2, 0]- , d_lt [2, 1]- ]- , d_all [ d_ge [2, 1, 1]- , d_lt [2, 2]- ]- ]- ]- , [ "|| ( ( >=dev-haskell/mtl-2.0"- , " <dev-haskell/mtl-2.1 )"- , " ( >=dev-haskell/mtl-2.1.1"- , " <dev-haskell/mtl-2.2 ) )"- ]- )- -- remove duplicate entries- , ( let d = d_all [d_ge [2, 0], d_lt [2, 2]]- in d_all [d, d]- , [ ">=dev-haskell/mtl-2.0"- , "<dev-haskell/mtl-2.2"- , ">=dev-haskell/mtl-2.0"- , "<dev-haskell/mtl-2.2"- ]- )- ]- forM_ deps $ \(d, expected) ->- P.dep2str_noindent d `shouldBe` intercalate "\n" expected
− tests/Portage/EBuildSpec.hs
@@ -1,34 +0,0 @@-module Portage.EBuildSpec (spec) where--import Test.Hspec--import Portage.EBuild--spec :: Spec-spec = do- describe "sort_iuse" $ do- it "sorts IUSE alphabetically despite pluses (+)" $ do- sort_iuse ["+a","c","+b","d"] `shouldBe` ["+a","+b","c","d"]- describe "drop_tdot" $ do- it "drops trailing dots (.)" $ do- drop_tdot "foo." `shouldBe` "foo"- drop_tdot "foo..." `shouldBe` "foo"- drop_tdot "foo" `shouldBe` "foo"- describe "quote" $ do- it "should correctly surround a string with special characters in quotes" $ do- quote "Reading, writing and manipulating '.tar' archives." ""- `shouldBe`- "\"Reading, writing and manipulating \'.tar\' archives.\""- quote "Extras for the \"contravariant\" package" ""- `shouldBe`- "\"Extras for the \\\"contravariant\\\" package\""- describe "toHttps" $ do- it "should not convert whitelisted http-only homepages into https homepages" $ do- toHttps "http://leksah.org" `shouldBe` "http://leksah.org"- toHttps "http://darcs.net/" `shouldBe` "http://darcs.net/"- toHttps "http://khumba.net/" `shouldBe` "http://khumba.net/"- it "should otherwise convert all homepages into https-aware homepages" $ do- toHttps "http://pandoc.org" `shouldBe` "https://pandoc.org"- toHttps "http://www.yesodweb.com/" `shouldBe` "https://www.yesodweb.com/"- it "should ignore any conversions of homepages already marked as https-aware" $ do- toHttps "https://github.com" `shouldBe` "https://github.com"
− tests/Portage/GHCCoreSpec.hs
@@ -1,23 +0,0 @@-module Portage.GHCCoreSpec (spec) where--import Test.Hspec--import qualified Distribution.Version as Cabal-import qualified Distribution.Package as Cabal--import Portage.GHCCore--spec :: Spec-spec = do- describe "cabalFromGHC" $ do- it "returns the corresponding Cabal version for a valid GHC version" $ do- cabalFromGHC [8,8,3] `shouldBe` Just (Cabal.mkVersion [3,0,1,0])- it "returns Nothing for an invalid GHC version" $ do- cabalFromGHC [9,9,9,9] `shouldBe` Nothing- describe "packageIsCoreInAnyGHC" $ do- it "returns True for the binary package" $ do- packageIsCoreInAnyGHC (Cabal.mkPackageName "binary") `shouldBe` True- it "returns False for the haskeline package (because it is upgradeable)" $ do- packageIsCoreInAnyGHC (Cabal.mkPackageName "haskeline") `shouldBe` False- it "returns False for the yesod package" $ do- packageIsCoreInAnyGHC (Cabal.mkPackageName "yesod") `shouldBe` False
− tests/Portage/MetadataSpec.hs
@@ -1,104 +0,0 @@-module Portage.MetadataSpec (spec) where--import Test.Hspec-import Test.Hspec.QuickCheck--import qualified Data.List as L-import qualified Data.Text as T-import qualified Data.Map.Strict as Map--import Portage.Metadata--spec :: Spec-spec = do- describe "pureMetadataFromFile" $ do- it "returns Nothing from an empty Text" $ do- pureMetadataFromFile T.empty `shouldBe` Nothing- it "equals makeMinimalMetadata with no USE flags" $ do- pureMetadataFromFile (makeDefaultMetadata Map.empty) `shouldBe` Just makeMinimalMetadata- it "equals makeMinimalMetadata plus the supplied USE flags" $ do- let flags = Map.singleton "name" "description"- pureMetadataFromFile (makeDefaultMetadata flags) `shouldBe` Just (makeMinimalMetadata { metadataUseFlags = flags })-- describe "stripGlobalUseFlags" $ do- it "should remove specified global USE flags from the metadata.xml" $ do- stripGlobalUseFlags (Map.singleton "debug" "description") `shouldBe` Map.empty- stripGlobalUseFlags (Map.singleton "examples" "description") `shouldBe` Map.empty- stripGlobalUseFlags (Map.singleton "static" "description") `shouldBe` Map.empty- prop "should ignore USE flags that are not specified as global" $ do- \name description -> stripGlobalUseFlags (Map.singleton name description) ==- if name `elem` ["debug","examples","static"]- then Map.empty- else Map.singleton name description-- describe "prettyPrintFlags" $ do- it "correctly handles special XML characters contained in strings" $ do- let name = "foo"- desc = "bar < 1.1.0"- in prettyPrintFlags (Map.singleton name desc) `shouldBe`- ["\t\t<flag name=\"" ++ name ++ "\">"- ++ "bar < 1.1.0" ++ "</flag>"]- it "correctly formats a single USE flag name with its description" $ do- let name = "foo"- description = "bar"- in prettyPrintFlags (Map.singleton name description) `shouldBe`- ["\t\t<flag name=\"" ++ name ++- "\">" ++ (L.intercalate " " . lines $ description)- ++ "</flag>"]- it "correctly formats multiple USE flag names with their descriptions" $ do- let f1 = "flag1"- f2 = "flag2"- d1 = "foo_desc"- d2 = "bar_desc"- in prettyPrintFlags (Map.fromList [(f1,d1),(f2,d2)]) `shouldBe`- ["\t\t<flag name=\"" ++ f1- ++ "\">" ++ (L.intercalate " " . lines $ d1)- ++ "</flag>"- ,- "\t\t<flag name=\"" ++ f2- ++ "\">" ++ (L.intercalate " " . lines $ d2)- ++ "</flag>"]-- prop "should have a length equal to the number of USE flags" $ do- \flags -> length (prettyPrintFlags flags) == Map.size flags- - describe "makeDefaultMetadata" $ do- context "when writing a minimal metadata.xml with no USE flags" $ do- it "should have a certain number of lines" $ do- -- This is the number of lines in a skeleton metadata.xml.- -- If it does not equal this number, the formatting may be wrong.- length (T.lines (makeDefaultMetadata Map.empty)) `shouldBe` 8- it "should have a certain format" $ do- let correctMetadata = T.pack $ unlines- [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"- , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"- , "<pkgmetadata>"- , "\t<maintainer type=\"project\">"- , "\t\t<email>haskell@gentoo.org</email>"- , "\t\t<name>Gentoo Haskell</name>"- , "\t</maintainer>"- , "</pkgmetadata>"- ]- in makeDefaultMetadata Map.empty `shouldBe` correctMetadata- context "when writing a metadata.xml with USE flags" $ do- it "should have a certain number of lines" $ do- let flags = Map.singleton "name" "description"- in length (T.lines (makeDefaultMetadata flags))- `shouldBe` 10 + (Map.size flags)- it "should have a certain format, including the <use> element" $ do- let flags = Map.fromList [("flag1","desc1"),("flag2","desc2")]- correctMetadata = T.pack $ unlines- [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"- , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"- , "<pkgmetadata>"- , "\t<maintainer type=\"project\">"- , "\t\t<email>haskell@gentoo.org</email>"- , "\t\t<name>Gentoo Haskell</name>"- , "\t</maintainer>"- , "\t<use>"- , "\t\t<flag name=\"flag1\">desc1</flag>"- , "\t\t<flag name=\"flag2\">desc2</flag>"- , "\t</use>"- , "</pkgmetadata>"- ]- in makeDefaultMetadata flags `shouldBe` correctMetadata
− tests/Portage/PackageIdSpec.hs
@@ -1,69 +0,0 @@-module Portage.PackageIdSpec (spec) where--import Test.Hspec-import Test.Hspec.QuickCheck-import Test.QuickCheck-import QuickCheck.Instances--import qualified Data.Char as Char-import qualified Distribution.Package as Cabal-import Distribution.Pretty (prettyShow)--import Portage.PackageId--spec :: Spec-spec = do- describe "packageIdToFilePath" $ do- prop "transforms a PackageId into a FilePath" $ do- let cat = Category "dev-haskell"- name = Cabal.mkPackageName "foo-bar2+"- in \(ComplexVersion version) ->- packageIdToFilePath (PackageId (PackageName cat name) version)- `shouldBe`- "dev-haskell/" ++ prettyShow name ++ "/" ++ prettyShow name ++ "-" ++- prettyShow version ++ ".ebuild"-- describe "filePathToPackageId" $ do- prop "returns a Just PackageId from a valid FilePath" $ do- let cat = Category "dev-haskell"- name = Cabal.mkPackageName "foo-bar2+"- in \(ComplexVersion version) ->- filePathToPackageId cat- (prettyShow name ++ "-" ++ prettyShow version)- `shouldBe`- Just (PackageId (PackageName cat name) version)- prop "returns Nothing on a malformed FilePath" $ do- let cat = Category "dev-haskell"- name = Cabal.mkPackageName "foo-bar-2+"- in \(ComplexVersion version) ->- filePathToPackageId cat (prettyShow name ++ "-" ++ prettyShow version)- `shouldBe`- Nothing-- describe "normalizeCabalPackageName" $ do- prop "converts a Cabal.PackageName into lowercase" $ do- \(PrintableString s) -> normalizeCabalPackageName (Cabal.mkPackageName s)- `shouldBe` Cabal.mkPackageName (Char.toLower <$> s)-- describe "parseFriendlyPackage" $ do- prop "parses a package string as [category/]name[-version]" $ do- let cat = Category "dev-haskell"- name = Cabal.mkPackageName "package-name1+"- in \(ComplexVersion version) ->- parseFriendlyPackage (prettyShow cat ++ "/" ++ prettyShow name- ++ "-" ++ prettyShow version)- `shouldBe`- Right (Just cat, name, Just version)- prop "returns an error string if parsing a malformed package string" $ do- let cat = Category "dev-haskell"- name = Cabal.mkPackageName "package-name-1+"- in \(ComplexVersion version) ->- parseFriendlyPackage (prettyShow cat ++ "/" ++ prettyShow name- ++ "-" ++ prettyShow version)- `shouldNotBe`- Right (Just cat, name, Just version)-- describe "cabal_pn_to_PN" $ do- prop "pretty-prints a Cabal PackageName as a lowercase String" $ do- \(PrintableString s) -> cabal_pn_to_PN (Cabal.mkPackageName s)- `shouldBe` Char.toLower <$> s
− tests/Portage/VersionSpec.hs
@@ -1,27 +0,0 @@-module Portage.VersionSpec (spec) where--import Test.Hspec-import Test.Hspec.QuickCheck-import QuickCheck.Instances--import qualified Distribution.Version as Cabal--import Portage.Version--spec :: Spec-spec = do- describe "is_live" $ do- prop "determines if a Portage version is live" $ do- \(ComplexVersion v) -> is_live v `shouldBe`- if last (versionNumber v) >= 9999- then True else False- - describe "fromCabalVersion" $ do- prop "converts from a Cabal version to a Portage version" $ do- \verNum -> fromCabalVersion (Cabal.mkVersion verNum) == Version verNum Nothing [] 0- - describe "toCabalVersion" $ do- prop "converts from a Portage version to a Cabal version" $ do- \(ComplexVersion v) -> toCabalVersion v `shouldBe`- if versionChar v == Nothing && versionSuffix v == []- then Just (Cabal.mkVersion (versionNumber v)) else Nothing
− tests/QuickCheck/Instances.hs
@@ -1,44 +0,0 @@-module QuickCheck.Instances (ComplexVersion(..)) where--import Test.QuickCheck--import Portage.Version (Suffix(..), Version(..))------------------------------------------------------------------------------------- Types------------------------------------------------------------------------------------ | Wrapper for 'Suffix', intended for use in an 'Arbitrary' instance--- to return a single, valid 'Suffix'.-newtype ValidSuffix = ValidSuffix { getSuffix :: Suffix }- deriving (Eq,Ord,Show)---- | Wrapper For 'Version', intended for use in an 'Arbitrary' instance--- where we want to generate the most complex 'Version' possible.-newtype ComplexVersion = ComplexVersion { getVersion :: Version }- deriving (Eq,Ord,Show)------------------------------------------------------------------------------------- Instances------------------------------------------------------------------------------------ | Return a single, valid 'ValidSuffix'.-instance Arbitrary ValidSuffix where- arbitrary = oneof [ ValidSuffix . Alpha . getNonNegative <$> arbitrary- , ValidSuffix . Beta . getNonNegative <$> arbitrary- , ValidSuffix . Pre . getNonNegative <$> arbitrary- , ValidSuffix . RC . getNonNegative <$> arbitrary- , ValidSuffix . P . getNonNegative <$> arbitrary- ]- --- | Return a valid 'ComplexVersion' with a non-empty 'versionNumber',--- an optional 'versionChar' if valid, a ['Suffix'] which may be empty,--- and a 'NonNegative' 'versionRevision' which may be zero.------ This is used to generate a 'Version' of high complexity to--- stress-test our parsers for a range of valid inputs.-instance Arbitrary ComplexVersion where- arbitrary = do- v <- listOf1 $ getNonNegative <$> arbitrary- c <- oneof $ [Just <$> choose ('a','z'), elements [Nothing]]- s <- listOf $ getSuffix <$> arbitrary- (NonNegative r) <- arbitrary- return $ ComplexVersion $ Version v (if length v == 1 then Nothing else c) s r
− tests/RunTests.hs
@@ -1,15 +0,0 @@-module RunTests- (run_tests) where--import Control.Monad (when)--import System.Exit (exitFailure)-import Test.HUnit--something_broke :: Counts -> Bool-something_broke stats = errors stats + failures stats > 0--run_tests :: Test -> IO ()-run_tests tests =- do stats <- runTestTT tests- when (something_broke stats) exitFailure
− tests/Spec.hs
@@ -1,1 +0,0 @@-{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
− tests/doctests-v2.hs
@@ -1,39 +0,0 @@-{-# LANGUAGE CPP #-}--module Main (main) where--import Control.Monad.Fail (fail)-import Data.Foldable (for_)-import System.Exit (ExitCode (..), exitWith)-import System.Process (readProcess, createProcess, proc, waitForProcess, getProcessExitCode)--#if !MIN_VERSION_base(4,13,0)-import Prelude hiding (fail)-#endif---main :: IO ()-main = do- doctestPath <- head . lines <$> readProcess "cabal" ["list-bin", "doctest"] []-- let components =- [ "hackport:lib:hackport-internal"- , "hackport:exe:hackport"- ]-- for_ components $ \component -> do- let cabalArgs =- [ "repl"- , "--with-compiler=" ++ doctestPath- , component- ]- putStrLn $ "Running: cabal " ++ unwords cabalArgs- (_, _, _, processHandle) <- createProcess (proc "cabal" cabalArgs)- waitForProcess processHandle- exitCode <- getProcessExitCode processHandle- case exitCode of- Nothing -> fail "No exit code from cabal process..."- Just ExitSuccess -> pure ()- Just c@(ExitFailure i) -> do- putStrLn $ "\nFailure: cabal process returned exit code " ++ show i- exitWith c
+ tests/doctests-v2/Main.hs view
@@ -0,0 +1,35 @@+module Main (main) where++import Data.Foldable (for_)+import System.Exit (ExitCode (..), exitWith)+import System.Process (readProcess, createProcess, proc, waitForProcess, getProcessExitCode)+++main :: IO ()+main = do++ doctestPath:_ <- lines <$> readProcess "cabal" ["list-bin", "doctest"] []++ let components =+ [ "hackport:lib:hackport-internal"+ , "hackport:exe:hackport"+ ]++ for_ components $ \component -> do+ let cabalArgs =+ [ "repl"+ -- -Wtype-defaults screws up some doctests when run with -Werror+ , "--ghc-option=-Wno-type-defaults"+ , "--with-compiler=" ++ doctestPath+ , component+ ]+ putStrLn $ "Running: cabal " ++ unwords cabalArgs+ (_, _, _, processHandle) <- createProcess (proc "cabal" cabalArgs)+ _ <- waitForProcess processHandle+ exitCode <- getProcessExitCode processHandle+ case exitCode of+ Nothing -> fail "No exit code from cabal process..."+ Just ExitSuccess -> pure ()+ Just c@(ExitFailure i) -> do+ putStrLn $ "\nFailure: cabal process returned exit code " ++ show i+ exitWith c
− tests/doctests.hs
@@ -1,36 +0,0 @@-module Main where--import Build_doctests (Component (..), Name (..), components)-import Test.DocTest (doctest)-import Data.Foldable (for_)-import qualified Data.List as L-import GHC.IO.Encoding (setLocaleEncoding)-import System.Directory (getCurrentDirectory, makeAbsolute)-import System.Environment.Compat (unsetEnv)-import System.FilePath.Glob (glob)-import System.FilePath.Posix ((</>))-import System.IO (utf8)--main :: IO ()-main = do- setLocaleEncoding utf8- unsetEnv "GHC_ENVIRONMENT"- pwd <- getCurrentDirectory- prefix <- makeAbsolute pwd-- for_ components $ \(Component name pkgs flags _) -> do- putStrLn "----------"- print name-- maybeSources <- case name of- NameLib (Just "hackport-internal") -> Just <$> glob (prefix </> "src" </> "**/*.hs")- NameExe "hackport" -> Just <$> glob (prefix </> "exe" </> "**/*.hs")- _ -> fail $- "Unexpected component: " ++ show name ++ "\n" ++- "Please edit tests/doctests.hs to add sources for this component."-- for_ maybeSources $ \sources -> do- let args = flags ++ pkgs ++ sources- putStrLn "Flags passed:"- for_ args $ \a -> putStr " " *> print a- doctest args
+ tests/doctests/Main.hs view
@@ -0,0 +1,36 @@+module Main where++import Build_doctests (Component (..), Name (..), components)+import Test.DocTest (doctest)+import Data.Foldable (for_)+import GHC.IO.Encoding (setLocaleEncoding)+import System.Directory (getCurrentDirectory, makeAbsolute)+import System.Environment.Compat (unsetEnv)+import System.FilePath.Glob (glob)+import System.FilePath.Posix ((</>))+import System.IO (utf8)++main :: IO ()+main = do+ setLocaleEncoding utf8+ unsetEnv "GHC_ENVIRONMENT"+ pwd <- getCurrentDirectory+ prefix <- makeAbsolute pwd++ for_ components $ \(Component name pkgs flags _) -> do+ putStrLn "----------"+ print name++ maybeSources <- case name of+ NameLib (Just "hackport-internal") -> Just <$> glob (prefix </> "src" </> "**/*.hs")+ NameExe "hackport" -> Just <$> glob (prefix </> "exe" </> "**/*.hs")+ _ -> fail $+ "Unexpected component: " ++ show name ++ "\n" +++ "Please edit tests/doctests.hs to add sources for this component."++ for_ maybeSources $ \sources -> do+ let noWarnFlags = ["-Wwarn", "-Wno-default"]+ let args = flags ++ noWarnFlags ++ pkgs ++ sources+ putStrLn "Flags passed:"+ for_ args $ \a -> putStr " " *> print a+ doctest args
− tests/normalize_deps.hs
@@ -1,250 +0,0 @@-import Control.Monad (forM_)-import Data.List--import Test.HUnit--import qualified Portage.Dependency as P-import qualified Portage.Dependency.Normalize as PN-import qualified Portage.PackageId as P-import qualified Portage.Use as P-import qualified RunTests as RT--tests :: Test-tests = TestList [ TestLabel "normalize_in_use_and_top" test_normalize_in_use_and_top- ]--pnm :: P.PackageName-pnm = P.mkPackageName "dev-haskell" "mtl"-pnp :: P.PackageName-pnp = P.mkPackageName "dev-haskell" "parsec"-pnq :: P.PackageName-pnq = P.mkPackageName "dev-haskell" "quickcheck"--def_attr :: P.DAttr-def_attr = P.DAttr P.AnySlot []--p_v :: [Int] -> P.Version-p_v v = P.Version { P.versionNumber = v- , P.versionChar = Nothing- , P.versionSuffix = []- , P.versionRevision = 0- }--d_all :: [P.Dependency] -> P.Dependency-d_all = P.DependAllOf-d_any :: [P.Dependency] -> P.Dependency-d_any = P.DependAnyOf--d_ge :: P.PackageName -> [Int] -> P.Dependency-d_ge pn v = P.DependAtom $ P.Atom pn- (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)- def_attr--d_p :: String -> P.Dependency-d_p pn = P.DependAtom $ P.Atom (P.mkPackageName "c" pn)- (P.DRange P.ZeroB P.InfinityB)- def_attr--d_use :: String -> P.Dependency -> P.Dependency-d_use u d = P.mkUseDependency (True, P.Use u) d--d_nuse :: String -> P.Dependency -> P.Dependency-d_nuse u d = P.mkUseDependency (False, P.Use u) d--test_normalize_in_use_and_top :: Test-test_normalize_in_use_and_top = TestCase $ do- let deps = [ ( d_all [ d_ge pnm [1,0]- , d_use "foo" (d_all [ d_ge pnm [1,0] -- duplicate- , d_ge pnp [2,1]- ])- ]- , [ ">=dev-haskell/mtl-1.0"- , "foo? ( >=dev-haskell/parsec-2.1 )"- ]- )- , ( d_all [ d_ge pnm [1,0]- , d_use "foo" (d_any [ d_ge pnm [1,0] -- already satisfied- , d_ge pnp [2,1]- ])- ]- , [ ">=dev-haskell/mtl-1.0" ]- )- , ( d_any [ d_all [ d_ge pnm [1,0] -- common subdep- , d_ge pnq [1,2]- ]- , d_all [ d_ge pnm [1,0]- , d_ge pnp [3,1]- ]- ]- , [ ">=dev-haskell/mtl-1.0"- , "|| ( >=dev-haskell/parsec-3.1"- , " >=dev-haskell/quickcheck-1.2 )"- ]- )- , ( d_all [ d_use "foo" $ d_use "bar" $ d_ge pnm [1,0]- , d_use "bar" $ d_use "foo" $ d_ge pnm [1,0]- ]- , [ "bar? ( foo? ( >=dev-haskell/mtl-1.0 ) )" ]- )- , ( d_all [ d_use "foo" $ d_ge pnm [1,0]- , d_use "foo" $ d_ge pnp [3,1]- ]- , [ "foo? ( >=dev-haskell/mtl-1.0"- , " >=dev-haskell/parsec-3.1 )"- ]- )- , ( d_all [ d_use "a" $ d_use "b" $ d_ge pnm [1,0]- , d_nuse "a" $ d_use "b" $ d_ge pnm [1,0]- ]- , [ "b? ( >=dev-haskell/mtl-1.0 )"- ]- )- -- 'test?' is special- , ( d_use "a" $ d_use "test" $ d_ge pnm [1,0]- , [ "test? ( a? ( >=dev-haskell/mtl-1.0 ) )"- ]- )- , -- pop context for complementary depends, like- -- a? ( x y a ) !a? ( x y na )- -- leads to- -- x y a? ( a ) !a? ( na )- ( d_all [ d_use "a" $ d_all $ map d_p [ "x", "y", "a" ]- , d_nuse "a" $ d_all $ map d_p [ "x", "y", "na" ]- ]- , [ "c/x"- , "c/y"- , "a? ( c/a )"- , "!a? ( c/na )"- ]- )- , -- push stricter context into less strict- ( d_all [ d_ge pnm [2,0]- , d_use "a" $ d_ge pnm [1,0]- ]- ,- [ ">=dev-haskell/mtl-2.0" ]- )- , -- propagate use guarded depend into deeply nested one- ( d_all [ d_use "a" $ d_all $ map d_p [ "x" ]- , d_use "test" $ d_use "a" $ d_all $ map d_p [ "x", "t" ]- ]- , [ "test? ( a? ( c/t ) )"- , "a? ( c/x )"- ]- )- , -- lift nested use context for complementary depends- -- a? ( b? ( y ) ) b? ( x )- ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]- , d_nuse "a" $ d_use "b" $ d_p "x"- ]- , [ "a? ( b? ( c/y ) )"- , "b? ( c/x )"- ]- )- , -- more advanced lift of complementary deps- -- a? ( b? ( x y ) )- -- !a? ( b? ( y z ) )- ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]- , d_nuse "a" $ d_use "b" $ d_all $ map d_p [ "y", "z" ]- ]- , [ "a? ( b? ( c/x ) )"- , "!a? ( b? ( c/z ) )"- , "b? ( c/y )"- ]- )- , -- completely expanded set of USEs- -- a? ( b? ( c? ( x y z ) )- -- a? ( b? ( !c? ( x y ) )- -- a? ( !b? ( c? ( x z ) )- -- a? ( !b? ( !c? ( x ) )- --- -- !a? ( b? ( c? ( y z ) )- -- !a? ( b? ( !c? ( y ) )- -- !a? ( !b? ( c? ( z ) )- -- !a? ( !b? ( !c? ( ) )- ( d_all [ d_use "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "x", "y", "z" ]- , d_use "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "x", "y" ]- , d_use "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "x", "z" ]- , d_use "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ "x" ]- , d_nuse "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "y", "z" ]- , d_nuse "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "y" ]- , d_nuse "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "z" ]- , d_nuse "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ ]- ]- , [ "a? ( c/x )"- , "b? ( c/y )"- , "c? ( c/z )"- ]- )- , -- pop simple common subdepend- -- a? ( b? ( d ) )- -- !a? ( b? ( d ) )- ( d_all [ d_use "a" $ d_use "b" $ d_p "d"- , d_nuse "a" $ d_use "b" $ d_p "d"- ]- , [ "b? ( c/d )"- ]- )- , -- pop '|| ( some thing )' depend- ( let any_part = d_any $ map d_p ["a", "b"] in- d_all [ d_use "u" $ d_all [ any_part , d_p "z"]- , d_nuse "u" $ any_part- ]- , [ "|| ( c/a"- , " c/b )"- , "u? ( c/z )"- ]- )- , -- simplify slightly more complex counterguard- -- v? ( c/d )- -- u? ( !v? ( c/d ) )- -- to- -- v? ( c/d )- -- u? ( c/d )- ( d_all [ d_use "v" $ d_p "d"- , d_use "u" $ d_nuse "v" $ d_p "d"- ]- , [ "u? ( c/d )"- , "v? ( c/d )"- ]- )-- , -- ffi? ( c/d c/e )- -- !ffi ( !gmp ( c/d c/e ) )- -- gmp? ( c/d c/e )- -- to- -- ( c/d c/e )- ( let de = d_all [ d_p "d" , d_p "e" ]- in d_all [ d_use "ffi" de- , d_nuse "ffi" $ d_nuse "gmp" $ de- , d_use "gmp" $ de- ]- , [ "c/d"- , "c/e"- ]- )-- {- TODO: another popular case- , -- simplify even more complex counterguard- -- u? ( c/d )- -- !u? ( v? ( c/d ) )- -- to- -- u? ( c/d )- -- v? ( c/d )- ( d_all [ d_use "u" $ d_p "d"- , d_nuse "u" $ d_use "v" $ d_p "d"- ]- , [ "u? ( c/d )"- , "v? ( c/d )"- ]- )- -}- ]- forM_ deps $ \(d, expected) ->- let actual = P.dep2str 0 $ PN.normalize_depend d- in assertEqual ("expecting matching result for " ++ show d)- (intercalate "\n" expected)- actual--main :: IO ()-main = RT.run_tests tests
+ tests/normalize_deps/Main.hs view
@@ -0,0 +1,250 @@+import Control.Monad (forM_)+import Data.List++import Test.HUnit++import qualified Portage.Dependency as P+import qualified Portage.Dependency.Normalize as PN+import qualified Portage.PackageId as P+import qualified Portage.Use as P+import qualified RunTests as RT++tests :: Test+tests = TestList [ TestLabel "normalize_in_use_and_top" test_normalize_in_use_and_top+ ]++pnm :: P.PackageName+pnm = P.mkPackageName "dev-haskell" "mtl"+pnp :: P.PackageName+pnp = P.mkPackageName "dev-haskell" "parsec"+pnq :: P.PackageName+pnq = P.mkPackageName "dev-haskell" "quickcheck"++def_attr :: P.DAttr+def_attr = P.DAttr P.AnySlot []++p_v :: [Int] -> P.Version+p_v v = P.Version { P.versionNumber = v+ , P.versionChar = Nothing+ , P.versionSuffix = []+ , P.versionRevision = 0+ }++d_all :: [P.Dependency] -> P.Dependency+d_all = P.DependAllOf+d_any :: [P.Dependency] -> P.Dependency+d_any = P.DependAnyOf++d_ge :: P.PackageName -> [Int] -> P.Dependency+d_ge pn v = P.DependAtom $ P.Atom pn+ (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)+ def_attr++d_p :: String -> P.Dependency+d_p pn = P.DependAtom $ P.Atom (P.mkPackageName "c" pn)+ (P.DRange P.ZeroB P.InfinityB)+ def_attr++d_use :: String -> P.Dependency -> P.Dependency+d_use u d = P.mkUseDependency (True, P.Use u) d++d_nuse :: String -> P.Dependency -> P.Dependency+d_nuse u d = P.mkUseDependency (False, P.Use u) d++test_normalize_in_use_and_top :: Test+test_normalize_in_use_and_top = TestCase $ do+ let deps = [ ( d_all [ d_ge pnm [1,0]+ , d_use "foo" (d_all [ d_ge pnm [1,0] -- duplicate+ , d_ge pnp [2,1]+ ])+ ]+ , [ ">=dev-haskell/mtl-1.0"+ , "foo? ( >=dev-haskell/parsec-2.1 )"+ ]+ )+ , ( d_all [ d_ge pnm [1,0]+ , d_use "foo" (d_any [ d_ge pnm [1,0] -- already satisfied+ , d_ge pnp [2,1]+ ])+ ]+ , [ ">=dev-haskell/mtl-1.0" ]+ )+ , ( d_any [ d_all [ d_ge pnm [1,0] -- common subdep+ , d_ge pnq [1,2]+ ]+ , d_all [ d_ge pnm [1,0]+ , d_ge pnp [3,1]+ ]+ ]+ , [ ">=dev-haskell/mtl-1.0"+ , "|| ( >=dev-haskell/parsec-3.1"+ , " >=dev-haskell/quickcheck-1.2 )"+ ]+ )+ , ( d_all [ d_use "foo" $ d_use "bar" $ d_ge pnm [1,0]+ , d_use "bar" $ d_use "foo" $ d_ge pnm [1,0]+ ]+ , [ "bar? ( foo? ( >=dev-haskell/mtl-1.0 ) )" ]+ )+ , ( d_all [ d_use "foo" $ d_ge pnm [1,0]+ , d_use "foo" $ d_ge pnp [3,1]+ ]+ , [ "foo? ( >=dev-haskell/mtl-1.0"+ , " >=dev-haskell/parsec-3.1 )"+ ]+ )+ , ( d_all [ d_use "a" $ d_use "b" $ d_ge pnm [1,0]+ , d_nuse "a" $ d_use "b" $ d_ge pnm [1,0]+ ]+ , [ "b? ( >=dev-haskell/mtl-1.0 )"+ ]+ )+ -- 'test?' is special+ , ( d_use "a" $ d_use "test" $ d_ge pnm [1,0]+ , [ "test? ( a? ( >=dev-haskell/mtl-1.0 ) )"+ ]+ )+ , -- pop context for complementary depends, like+ -- a? ( x y a ) !a? ( x y na )+ -- leads to+ -- x y a? ( a ) !a? ( na )+ ( d_all [ d_use "a" $ d_all $ map d_p [ "x", "y", "a" ]+ , d_nuse "a" $ d_all $ map d_p [ "x", "y", "na" ]+ ]+ , [ "c/x"+ , "c/y"+ , "a? ( c/a )"+ , "!a? ( c/na )"+ ]+ )+ , -- push stricter context into less strict+ ( d_all [ d_ge pnm [2,0]+ , d_use "a" $ d_ge pnm [1,0]+ ]+ ,+ [ ">=dev-haskell/mtl-2.0" ]+ )+ , -- propagate use guarded depend into deeply nested one+ ( d_all [ d_use "a" $ d_all $ map d_p [ "x" ]+ , d_use "test" $ d_use "a" $ d_all $ map d_p [ "x", "t" ]+ ]+ , [ "test? ( a? ( c/t ) )"+ , "a? ( c/x )"+ ]+ )+ , -- lift nested use context for complementary depends+ -- a? ( b? ( y ) ) b? ( x )+ ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]+ , d_nuse "a" $ d_use "b" $ d_p "x"+ ]+ , [ "a? ( b? ( c/y ) )"+ , "b? ( c/x )"+ ]+ )+ , -- more advanced lift of complementary deps+ -- a? ( b? ( x y ) )+ -- !a? ( b? ( y z ) )+ ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]+ , d_nuse "a" $ d_use "b" $ d_all $ map d_p [ "y", "z" ]+ ]+ , [ "a? ( b? ( c/x ) )"+ , "!a? ( b? ( c/z ) )"+ , "b? ( c/y )"+ ]+ )+ , -- completely expanded set of USEs+ -- a? ( b? ( c? ( x y z ) )+ -- a? ( b? ( !c? ( x y ) )+ -- a? ( !b? ( c? ( x z ) )+ -- a? ( !b? ( !c? ( x ) )+ --+ -- !a? ( b? ( c? ( y z ) )+ -- !a? ( b? ( !c? ( y ) )+ -- !a? ( !b? ( c? ( z ) )+ -- !a? ( !b? ( !c? ( ) )+ ( d_all [ d_use "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "x", "y", "z" ]+ , d_use "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "x", "y" ]+ , d_use "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "x", "z" ]+ , d_use "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ "x" ]+ , d_nuse "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "y", "z" ]+ , d_nuse "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "y" ]+ , d_nuse "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "z" ]+ , d_nuse "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ ]+ ]+ , [ "a? ( c/x )"+ , "b? ( c/y )"+ , "c? ( c/z )"+ ]+ )+ , -- pop simple common subdepend+ -- a? ( b? ( d ) )+ -- !a? ( b? ( d ) )+ ( d_all [ d_use "a" $ d_use "b" $ d_p "d"+ , d_nuse "a" $ d_use "b" $ d_p "d"+ ]+ , [ "b? ( c/d )"+ ]+ )+ , -- pop '|| ( some thing )' depend+ ( let any_part = d_any $ map d_p ["a", "b"] in+ d_all [ d_use "u" $ d_all [ any_part , d_p "z"]+ , d_nuse "u" $ any_part+ ]+ , [ "|| ( c/a"+ , " c/b )"+ , "u? ( c/z )"+ ]+ )+ , -- simplify slightly more complex counterguard+ -- v? ( c/d )+ -- u? ( !v? ( c/d ) )+ -- to+ -- v? ( c/d )+ -- u? ( c/d )+ ( d_all [ d_use "v" $ d_p "d"+ , d_use "u" $ d_nuse "v" $ d_p "d"+ ]+ , [ "u? ( c/d )"+ , "v? ( c/d )"+ ]+ )++ , -- ffi? ( c/d c/e )+ -- !ffi ( !gmp ( c/d c/e ) )+ -- gmp? ( c/d c/e )+ -- to+ -- ( c/d c/e )+ ( let de = d_all [ d_p "d" , d_p "e" ]+ in d_all [ d_use "ffi" de+ , d_nuse "ffi" $ d_nuse "gmp" $ de+ , d_use "gmp" $ de+ ]+ , [ "c/d"+ , "c/e"+ ]+ )++ {- TODO: another popular case+ , -- simplify even more complex counterguard+ -- u? ( c/d )+ -- !u? ( v? ( c/d ) )+ -- to+ -- u? ( c/d )+ -- v? ( c/d )+ ( d_all [ d_use "u" $ d_p "d"+ , d_nuse "u" $ d_use "v" $ d_p "d"+ ]+ , [ "u? ( c/d )"+ , "v? ( c/d )"+ ]+ )+ -}+ ]+ forM_ deps $ \(d, expected) ->+ let actual = P.dep2str 0 $ PN.normalize_depend d+ in assertEqual ("expecting matching result for " ++ show d)+ (intercalate "\n" expected)+ actual++main :: IO ()+main = RT.run_tests tests
− tests/print_deps.hs
@@ -1,140 +0,0 @@-import Control.Monad (forM_)-import Data.List--import Test.HUnit--import qualified Portage.Dependency as P-import qualified Portage.Dependency.Normalize as PN-import qualified Portage.PackageId as P-import qualified Portage.Use as P-import qualified RunTests as RT--tests :: Test-tests = TestList [ TestLabel "print_empty" test_print_empty- , TestLabel "print_mixed" test_print_mixed- , TestLabel "print_denorm" test_print_denorm- ]--test_print_empty :: Test-test_print_empty = TestCase $ do- let expect_empty = ""- d_all = P.DependAllOf- d_any = P.DependAnyOf- d_use u dep = P.mkUseDependency (True, P.Use u) dep- deps = [ d_all []- , d_any []- , d_use "f" (d_all [])- , P.DependAllOf [d_any []]- , P.DependAnyOf [d_all []]- -- Deep Useless Use Tree :]- , d_use "f" $- d_use "g" $- d_all [d_any []- , d_use "h" $- d_all [ d_all []- , d_any []- ]- , d_use "i" $- d_all [ d_all []- , d_any []- ]- ]- ]- forM_ deps $ \d ->- let actual_result = P.dep2str 0 $ PN.normalize_depend d- in assertEqual ("expecting empty result for " ++ show d)- expect_empty- actual_result--test_print_mixed :: Test-test_print_mixed = TestCase $ do- let pn = P.mkPackageName "dev-haskell" "mtl"- def_attr = P.DAttr P.AnySlot []- p_v v = P.Version { P.versionNumber = v- , P.versionChar = Nothing- , P.versionSuffix = []- , P.versionRevision = 0- }- d_all = P.DependAllOf- d_any = P.DependAnyOf- d_ge v = P.DependAtom $ P.Atom pn- (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)- def_attr- d_lt v = P.DependAtom $ P.Atom pn- (P.DRange P.ZeroB (P.StrictUB $ p_v v))- def_attr- deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"- ( d_all [ d_any [ d_all [ d_ge [2, 0]- , d_lt [2, 1]- ]- , d_all [ d_ge [2, 1, 1]- , d_lt [2, 2]- ]- ]- ]- , [ "|| ( ( >=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.1 )"- , " ( >=dev-haskell/mtl-2.1.1 <dev-haskell/mtl-2.2 ) )"- ]- )- -- remove duplicate entries- , ( let d = d_all [d_ge [2, 0], d_lt [2, 2]]- in d_all [d, d]- , [ ">=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.2" ]- )- ]- forM_ deps $ \(d, expected) ->- let actual = P.dep2str 0 $ PN.normalize_depend d- in assertEqual ("expecting empty result for " ++ show d)- (intercalate "\n" expected)- actual--test_print_denorm :: Test-test_print_denorm = TestCase $ do- let pn = P.mkPackageName "dev-haskell" "mtl"- def_attr = P.DAttr P.AnySlot []- p_v v = P.Version { P.versionNumber = v- , P.versionChar = Nothing- , P.versionSuffix = []- , P.versionRevision = 0- }- d_all = P.DependAllOf- d_any = P.DependAnyOf- d_ge v = P.DependAtom $ P.Atom pn- (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)- def_attr- d_lt v = P.DependAtom $ P.Atom pn- (P.DRange P.ZeroB (P.StrictUB $ p_v v))- def_attr- deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"- ( d_all [ d_any [ d_all [ d_ge [2, 0]- , d_lt [2, 1]- ]- , d_all [ d_ge [2, 1, 1]- , d_lt [2, 2]- ]- ]- ]- , [ "|| ( ( >=dev-haskell/mtl-2.0"- , " <dev-haskell/mtl-2.1 )"- , " ( >=dev-haskell/mtl-2.1.1"- , " <dev-haskell/mtl-2.2 ) )"- ]- )- -- remove duplicate entries- , ( let d = d_all [d_ge [2, 0], d_lt [2, 2]]- in d_all [d, d]- , [ ">=dev-haskell/mtl-2.0"- , "<dev-haskell/mtl-2.2"- , ">=dev-haskell/mtl-2.0"- , "<dev-haskell/mtl-2.2"- ]- )- ]- forM_ deps $ \(d, expected) ->- let actual = P.dep2str_noindent d- in assertEqual ("expecting empty result for " ++ show d)- (intercalate "\n" expected)- actual--main :: IO ()-main = RT.run_tests tests
+ tests/print_deps/Main.hs view
@@ -0,0 +1,140 @@+import Control.Monad (forM_)+import Data.List++import Test.HUnit++import qualified Portage.Dependency as P+import qualified Portage.Dependency.Normalize as PN+import qualified Portage.PackageId as P+import qualified Portage.Use as P+import qualified RunTests as RT++tests :: Test+tests = TestList [ TestLabel "print_empty" test_print_empty+ , TestLabel "print_mixed" test_print_mixed+ , TestLabel "print_denorm" test_print_denorm+ ]++test_print_empty :: Test+test_print_empty = TestCase $ do+ let expect_empty = ""+ d_all = P.DependAllOf+ d_any = P.DependAnyOf+ d_use u dep = P.mkUseDependency (True, P.Use u) dep+ deps = [ d_all []+ , d_any []+ , d_use "f" (d_all [])+ , P.DependAllOf [d_any []]+ , P.DependAnyOf [d_all []]+ -- Deep Useless Use Tree :]+ , d_use "f" $+ d_use "g" $+ d_all [d_any []+ , d_use "h" $+ d_all [ d_all []+ , d_any []+ ]+ , d_use "i" $+ d_all [ d_all []+ , d_any []+ ]+ ]+ ]+ forM_ deps $ \d ->+ let actual_result = P.dep2str 0 $ PN.normalize_depend d+ in assertEqual ("expecting empty result for " ++ show d)+ expect_empty+ actual_result++test_print_mixed :: Test+test_print_mixed = TestCase $ do+ let pn = P.mkPackageName "dev-haskell" "mtl"+ def_attr = P.DAttr P.AnySlot []+ p_v v = P.Version { P.versionNumber = v+ , P.versionChar = Nothing+ , P.versionSuffix = []+ , P.versionRevision = 0+ }+ d_all = P.DependAllOf+ d_any = P.DependAnyOf+ d_ge v = P.DependAtom $ P.Atom pn+ (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)+ def_attr+ d_lt v = P.DependAtom $ P.Atom pn+ (P.DRange P.ZeroB (P.StrictUB $ p_v v))+ def_attr+ deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"+ ( d_all [ d_any [ d_all [ d_ge [2, 0]+ , d_lt [2, 1]+ ]+ , d_all [ d_ge [2, 1, 1]+ , d_lt [2, 2]+ ]+ ]+ ]+ , [ "|| ( ( >=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.1 )"+ , " ( >=dev-haskell/mtl-2.1.1 <dev-haskell/mtl-2.2 ) )"+ ]+ )+ -- remove duplicate entries+ , ( let d = d_all [d_ge [2, 0], d_lt [2, 2]]+ in d_all [d, d]+ , [ ">=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.2" ]+ )+ ]+ forM_ deps $ \(d, expected) ->+ let actual = P.dep2str 0 $ PN.normalize_depend d+ in assertEqual ("expecting empty result for " ++ show d)+ (intercalate "\n" expected)+ actual++test_print_denorm :: Test+test_print_denorm = TestCase $ do+ let pn = P.mkPackageName "dev-haskell" "mtl"+ def_attr = P.DAttr P.AnySlot []+ p_v v = P.Version { P.versionNumber = v+ , P.versionChar = Nothing+ , P.versionSuffix = []+ , P.versionRevision = 0+ }+ d_all = P.DependAllOf+ d_any = P.DependAnyOf+ d_ge v = P.DependAtom $ P.Atom pn+ (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)+ def_attr+ d_lt v = P.DependAtom $ P.Atom pn+ (P.DRange P.ZeroB (P.StrictUB $ p_v v))+ def_attr+ deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"+ ( d_all [ d_any [ d_all [ d_ge [2, 0]+ , d_lt [2, 1]+ ]+ , d_all [ d_ge [2, 1, 1]+ , d_lt [2, 2]+ ]+ ]+ ]+ , [ "|| ( ( >=dev-haskell/mtl-2.0"+ , " <dev-haskell/mtl-2.1 )"+ , " ( >=dev-haskell/mtl-2.1.1"+ , " <dev-haskell/mtl-2.2 ) )"+ ]+ )+ -- remove duplicate entries+ , ( let d = d_all [d_ge [2, 0], d_lt [2, 2]]+ in d_all [d, d]+ , [ ">=dev-haskell/mtl-2.0"+ , "<dev-haskell/mtl-2.2"+ , ">=dev-haskell/mtl-2.0"+ , "<dev-haskell/mtl-2.2"+ ]+ )+ ]+ forM_ deps $ \(d, expected) ->+ let actual = P.dep2str_noindent d+ in assertEqual ("expecting empty result for " ++ show d)+ (intercalate "\n" expected)+ actual++main :: IO ()+main = RT.run_tests tests
− tests/resolveCat.hs
@@ -1,29 +0,0 @@-import qualified Distribution.Package as Cabal--import qualified Portage.Overlay as Portage-import qualified Portage.Resolve as Portage-import qualified Portage.PackageId as Portage-import qualified Portage.Host as Portage--import Test.HUnit--import qualified RunTests as RT--tests :: Test-tests = TestList [ TestLabel "resolve cabal" (test_resolveCategory "dev-haskell" "cabal")- , TestLabel "resolve ghc" (test_resolveCategory "dev-lang" "ghc")- , TestLabel "resolve Cabal" (test_resolveCategory "dev-haskell" "Cabal")- , TestLabel "resolve DaRcS" (test_resolveCategory "dev-vcs" "DaRcS")- ]--test_resolveCategory :: String -> String -> Test-test_resolveCategory cat pkg = TestCase $ do- portage_dir <- Portage.portage_dir `fmap` Portage.getInfo- portage <- Portage.loadLazy portage_dir- let cabal = Cabal.mkPackageName pkg- hits = Portage.resolveFullPortageName portage cabal- expected = Just (Portage.PackageName (Portage.Category cat) (Portage.normalizeCabalPackageName cabal))- assertEqual ("expecting to find package " ++ pkg) expected hits--main :: IO ()-main = RT.run_tests tests
+ tests/resolveCat/Main.hs view
@@ -0,0 +1,27 @@+import qualified Distribution.Package as Cabal++import qualified Portage.Overlay as Portage+import qualified Portage.Resolve as Portage+import qualified Portage.PackageId as Portage+import qualified Portage.Host as Portage++import Test.HUnit++tests :: Test+tests = TestList [ TestLabel "resolve cabal" (test_resolveCategory "dev-haskell" "cabal")+ , TestLabel "resolve ghc" (test_resolveCategory "dev-lang" "ghc")+ , TestLabel "resolve Cabal" (test_resolveCategory "dev-haskell" "Cabal")+ , TestLabel "resolve DaRcS" (test_resolveCategory "dev-vcs" "DaRcS")+ ]++test_resolveCategory :: String -> String -> Test+test_resolveCategory cat pkg = TestCase $ do+ portage_dir <- Portage.portage_dir `fmap` Portage.getInfo+ portage <- Portage.loadLazy portage_dir+ let cabal = Cabal.mkPackageName pkg+ hits = Portage.resolveFullPortageName portage cabal+ expected = Just (Portage.PackageName (Portage.Category cat) (Portage.normalizeCabalPackageName cabal))+ assertEqual ("expecting to find package " ++ pkg) expected hits++main :: IO ()+main = runTestTTAndExit tests
+ tests/spec/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ tests/spec/Merge/UtilsSpec.hs view
@@ -0,0 +1,141 @@+module Merge.UtilsSpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck+import QuickCheck.Instances++import Control.Applicative (liftA2)+import qualified Data.Map.Strict as Map+import Data.Maybe (catMaybes)+import qualified Data.List as L++import Error+import Merge.Utils+import Portage.PackageId++import qualified Distribution.Package as Cabal+import qualified Distribution.PackageDescription as Cabal+import Distribution.Pretty (prettyShow)++spec :: Spec+spec = do+ describe "readPackageString" $ do+ prop "returns a Right tuple containing the parsed information or a Left ArgumentError" $ do+ let cat = Category "dev-haskell"+ name = Cabal.mkPackageName "package-name1"+ in \(ComplexVersion version) ->+ readPackageString (prettyShow cat ++ "/" ++ prettyShow name +++ if (versionNumber version) == []+ then ""+ else "-" ++ prettyShow version)+ `shouldBe`+ if (versionChar version) /= Nothing ||+ (versionSuffix version) /= [] ||+ (versionRevision version) /= 0+ then Left (ArgumentError ("Could not parse [category/]package[-version]: "+ ++ prettyShow cat ++ "/" +++ prettyShow name +++ if (versionNumber version) == []+ then ""+ else "-" ++ prettyShow version))+ else Right ( Just cat+ , name+ , if (versionNumber version) == []+ then Nothing+ else Just version+ )++ describe "getPreviousPackageId" $ do+ context "when there is a previous version available" $ do+ it "returns the PackageId of the previous version" $ do+ let ebuildDir = [ "foo-bar2-3.0.0b_rc2-r1.ebuild"+ , "foo-bar2-3.0.0b_rc2-r2.ebuild"+ , "foo-bar2-3.0.1.ebuild"+ , "metadata.xml"+ , "Manifest"+ , "files"+ ]+ newPkgId = PackageId (PackageName (Category "dev-haskell")+ (Cabal.mkPackageName "foo-bar2"))+ (Version [3,0,2] Nothing [] 0 )+ in getPreviousPackageId ebuildDir newPkgId `shouldBe`+ Just (PackageId (PackageName (Category "dev-haskell")+ (Cabal.mkPackageName "foo-bar2"))+ (Version [3,0,1] Nothing [] 0))+ context "if there is no previous version available" $ do+ it "returns Nothing" $ do+ let ebuildDir = [ "foo-bar2-3.0.0b_rc2-r1.ebuild"+ , "foo-bar2-3.0.0b_rc2-r2.ebuild"+ , "foo-bar2-3.0.1.ebuild"+ , "metadata.xml"+ , "Manifest"+ , "files"+ ]+ newPkgId = PackageId (PackageName (Category "dev-haskell")+ (Cabal.mkPackageName "foo-bar2"))+ (Version [3,0,0] (Just 'a') [] 0 )+ in getPreviousPackageId ebuildDir newPkgId `shouldBe` Nothing++ describe "drop_prefix" $ do+ context "when an IUSE has a with/use prefix" $ do+ prop "drops the prefix" $ do+ let prefix = ["with","use"]+ sep = ["-","_"]+ prefixes = liftA2 (++) prefix sep+ \flag -> L.nub (drop_prefix <$> (liftA2 (++) prefixes [flag])) == [flag]+ context "when an IUSE has neither a with nor a use prefix" $ do+ prop "preserves the existing string" $ do+ \prefix flag -> L.nub (drop_prefix <$> (liftA2 (++) [prefix] ['-':flag])) ==+ if prefix == "with" || prefix == "use"+ then [flag]+ else [prefix ++ '-':flag]++ describe "squash_debug" $ do+ it "squashes debug-related flags under the debug global USE flag" $ do+ squash_debug "use-debug-foo" `shouldBe` "debug"+ squash_debug "debug-foo" `shouldBe` "debug"+ squash_debug "foo-debugger" `shouldBe` "debug"+ it "ignores debug-unrelated flags" $ do+ squash_debug "foo-bar" `shouldBe` "foo-bar"++ describe "convert_underscores" $ do+ it "converts underscores (_) into hyphens (-)" $ do+ convert_underscores "foo_bar" `shouldBe` "foo-bar"+ it "ignores mangling of separators other than underscores" $ do+ convert_underscores "foo-bar" `shouldBe` "foo-bar"+ it "ignores mangling of characters which are not separators" $ do+ convert_underscores "foobar" `shouldBe` "foobar"++ describe "mangle_iuse" $ do+ it "performs all IUSE mangling" $ do+ mangle_iuse "use_foo_bar" `shouldBe` "foo-bar"+ mangle_iuse "with-baz-quux" `shouldBe` "baz-quux"+ mangle_iuse "use_debugging-symbols" `shouldBe` "debug"+ mangle_iuse "foo-bar-baz_quux" `shouldBe` "foo-bar-baz-quux"++ describe "to_unstable" $ do+ prop "creates an unstable keyword from a stable keyword, or preserves a mask" $ do+ \a -> to_unstable a ==+ case a of+ '~':_ -> a+ '-':_ -> a+ _ -> '~':a++ describe "metaFlags" $ do+ prop "converts a [Cabal.PackageFlag] into a Map.Map String String" $ do+ \name desc -> metaFlags [(Cabal.emptyFlag (Cabal.mkFlagName name))+ { Cabal.flagDescription = desc }] ==+ Map.fromList [(mangle_iuse name,desc)]++ describe "dropIfUseExpand" $ do+ it "drops a USE flag if it is a USE_EXPAND, otherwise preserves it" $ do+ let use_expands = ["cpu_flags_x86","cpu_flags_arm"]+ flags = Cabal.emptyFlag . Cabal.mkFlagName <$>+ [ "cpu_flags_x86_sse4_2"+ , "foo"+ , "bar"+ , "baz"+ , "cpu_flags_arm_v8"+ ]+ Cabal.unFlagName . Cabal.flagName <$> catMaybes (dropIfUseExpand use_expands <$> flags)+ `shouldBe` ["foo","bar","baz"]
+ tests/spec/Portage/CabalSpec.hs view
@@ -0,0 +1,69 @@+module Portage.CabalSpec (spec) where++import Test.Hspec++import qualified Distribution.SPDX as SPDX++import Portage.Cabal++spec :: Spec+spec = do+ describe "convertLicense" $ do+ it "converts AGPL_3_0_or_later into AGPL-3+" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.AGPL_3_0_or_later)+ `shouldBe`+ Right "AGPL-3+"+ it "converts AGPL_3_0_only into AGPL-3" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.AGPL_3_0_only)+ `shouldBe`+ Right "AGPL-3"+ it "converts GPL_2_0_or_later into GPL-2+" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_2_0_or_later)+ `shouldBe`+ Right "GPL-2+"+ it "converts GPL_2_0_only into GPL-2" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_2_0_only)+ `shouldBe`+ Right "GPL-2"+ it "converts GPL_3_0_or_later into GPL-3+" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_3_0_or_later)+ `shouldBe`+ Right "GPL-3+"+ it "converts GPL_3_0_only into GPL-3" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.GPL_3_0_only)+ `shouldBe`+ Right "GPL-3"+ -- Unfortunately, Cabal treats LGPL_2_0_only and LGPL_2_0_or_later as identical,+ -- just as it does with LGPL_2_1_only and LGPL_2_1_or_later. This means+ -- we cannot handle LGPL-2.0+ or LGPL-2,1+ without directly dealing with+ -- the SPDX licence, before it is converted into a Cabal-style licence.+ it "converts LGPL_2_0_or_later into LGPL-2, without a trailing plus (+)" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_0_or_later)+ `shouldBe`+ Right "LGPL-2"+ it "converts LGPL_2_0_only into LGPL-2" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_0_only)+ `shouldBe`+ Right "LGPL-2"+ it "converts LGPL_2_1_or_later into LGPL-2.1, without a trailing plus (+)" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_1_or_later)+ `shouldBe`+ Right "LGPL-2.1"+ it "converts LGPL_2_1_only into LGPL-2.1" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_2_1_only)+ `shouldBe`+ Right "LGPL-2.1"+ -- But these next two cases are correctly handled:+ it "converts LGPL_3_0_or_later into LGPL-3+" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_3_0_or_later)+ `shouldBe`+ Right "LGPL-3+"+ it "converts LGPL_3_0_only into LGPL-3" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.LGPL_3_0_only)+ `shouldBe`+ Right "LGPL-3"+ context "when a licence string is invalid" $ do+ it "converts the unknown licence into an error string" $ do+ convertLicense (SPDX.License $ SPDX.simpleLicenseExpression SPDX.EUPL_1_1)+ `shouldBe`+ Left "license unknown to cabal. Please pick it manually."
+ tests/spec/Portage/Dependency/PrintSpec.hs view
@@ -0,0 +1,327 @@+module Portage.Dependency.PrintSpec (spec) where++import Test.Hspec++import Control.Monad (forM_)+import Data.List (intercalate)++import qualified Portage.PackageId as P++import qualified Portage.Dependency.Builder as P+import qualified Portage.Dependency.Print as P+import qualified Portage.Dependency.Types as P+import qualified Portage.Dependency.Normalize as P++import qualified Portage.Use as P++spec :: Spec+spec = do+ describe "dep2str" $ do+ it "converts an empty Dependency into an empty string" $ do+ let d_all = P.DependAllOf+ d_any = P.DependAnyOf+ d_use u dep = P.mkUseDependency (True, P.Use u) dep+ deps = [ d_all []+ , d_any []+ , d_use "f" (d_all [])+ , P.DependAllOf [d_any []]+ , P.DependAnyOf [d_all []]+ -- Deep Useless Use Tree :]+ , d_use "f" $+ d_use "g" $+ d_all [d_any []+ , d_use "h" $+ d_all [ d_all []+ , d_any []+ ]+ , d_use "i" $+ d_all [ d_all []+ , d_any []+ ]+ ]+ ]+ forM_ deps $ \d ->+ P.dep2str 0 (P.normalize_depend d) `shouldBe` ""++ it "converts a Dependency into a normalized string" $ do+ let def_attr = P.DAttr P.AnySlot []+ p_v v = P.Version { P.versionNumber = v+ , P.versionChar = Nothing+ , P.versionSuffix = []+ , P.versionRevision = 0+ }+ d_all = P.DependAllOf+ d_any = P.DependAnyOf+ d_ge pn v = P.DependAtom $ P.Atom pn+ (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)+ def_attr+ d_lt pn v = P.DependAtom $ P.Atom pn+ (P.DRange P.ZeroB (P.StrictUB $ p_v v))+ def_attr+ -- for the deps_norm test, formerly normalize_deps+ pnm = P.mkPackageName "dev-haskell" "mtl"+ pnp = P.mkPackageName "dev-haskell" "parsec"+ pnq = P.mkPackageName "dev-haskell" "quickcheck"+ d_p pn = P.DependAtom $ P.Atom (P.mkPackageName "c" pn)+ (P.DRange P.ZeroB P.InfinityB)+ def_attr+ d_use u d = P.mkUseDependency (True, P.Use u) d+ d_nuse u d = P.mkUseDependency (False, P.Use u) d++ deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"+ ( d_all [ d_any [ d_all [ d_ge pnm [2, 0]+ , d_lt pnm [2, 1]+ ]+ , d_all [ d_ge pnm [2, 1, 1]+ , d_lt pnm [2, 2]+ ]+ ]+ ]+ , [ "|| ( ( >=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.1 )"+ , " ( >=dev-haskell/mtl-2.1.1 <dev-haskell/mtl-2.2 ) )"+ ]+ )+ -- remove duplicate entries+ , ( let d = d_all [d_ge pnm [2, 0], d_lt pnm [2, 2]]+ in d_all [d, d]+ , [ ">=dev-haskell/mtl-2.0 <dev-haskell/mtl-2.2" ]+ )+ ]+ forM_ deps $ \(d,expected) ->+ P.dep2str 0 (P.normalize_depend d) `shouldBe` (intercalate "\n" expected)++ -- from normalize_deps.hs+ let deps_norm = [ ( d_all [ d_ge pnm [1,0]+ , d_use "foo" (d_all [ d_ge pnm [1,0] -- duplicate+ , d_ge pnp [2,1]+ ])+ ]+ , [ ">=dev-haskell/mtl-1.0"+ , "foo? ( >=dev-haskell/parsec-2.1 )"+ ]+ )+ , ( d_all [ d_ge pnm [1,0]+ , d_use "foo" (d_any [ d_ge pnm [1,0] -- already satisfied+ , d_ge pnp [2,1]+ ])+ ]+ , [ ">=dev-haskell/mtl-1.0" ]+ )+ , ( d_any [ d_all [ d_ge pnm [1,0] -- common subdep+ , d_ge pnq [1,2]+ ]+ , d_all [ d_ge pnm [1,0]+ , d_ge pnp [3,1]+ ]+ ]+ , [ ">=dev-haskell/mtl-1.0"+ , "|| ( >=dev-haskell/parsec-3.1"+ , " >=dev-haskell/quickcheck-1.2 )"+ ]+ )+ , ( d_all [ d_use "foo" $ d_use "bar" $ d_ge pnm [1,0]+ , d_use "bar" $ d_use "foo" $ d_ge pnm [1,0]+ ]+ , [ "bar? ( foo? ( >=dev-haskell/mtl-1.0 ) )" ]+ )+ , ( d_all [ d_use "foo" $ d_ge pnm [1,0]+ , d_use "foo" $ d_ge pnp [3,1]+ ]+ , [ "foo? ( >=dev-haskell/mtl-1.0"+ , " >=dev-haskell/parsec-3.1 )"+ ]+ )+ , ( d_all [ d_use "a" $ d_use "b" $ d_ge pnm [1,0]+ , d_nuse "a" $ d_use "b" $ d_ge pnm [1,0]+ ]+ , [ "b? ( >=dev-haskell/mtl-1.0 )"+ ]+ )+ -- 'test?' is special+ , ( d_use "a" $ d_use "test" $ d_ge pnm [1,0]+ , [ "test? ( a? ( >=dev-haskell/mtl-1.0 ) )"+ ]+ )+ , -- pop context for complementary depends, like+ -- a? ( x y a ) !a? ( x y na )+ -- leads to+ -- x y a? ( a ) !a? ( na )+ ( d_all [ d_use "a" $ d_all $ map d_p [ "x", "y", "a" ]+ , d_nuse "a" $ d_all $ map d_p [ "x", "y", "na" ]+ ]+ , [ "c/x"+ , "c/y"+ , "a? ( c/a )"+ , "!a? ( c/na )"+ ]+ )+ , -- push stricter context into less strict+ ( d_all [ d_ge pnm [2,0]+ , d_use "a" $ d_ge pnm [1,0]+ ]+ ,+ [ ">=dev-haskell/mtl-2.0" ]+ )+ , -- propagate use guarded depend into deeply nested one+ ( d_all [ d_use "a" $ d_all $ map d_p [ "x" ]+ , d_use "test" $ d_use "a" $ d_all $ map d_p [ "x", "t" ]+ ]+ , [ "test? ( a? ( c/t ) )"+ , "a? ( c/x )"+ ]+ )+ , -- lift nested use context for complementary depends+ -- a? ( b? ( y ) ) b? ( x )+ ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]+ , d_nuse "a" $ d_use "b" $ d_p "x"+ ]+ , [ "a? ( b? ( c/y ) )"+ , "b? ( c/x )"+ ]+ )+ , -- more advanced lift of complementary deps+ -- a? ( b? ( x y ) )+ -- !a? ( b? ( y z ) )+ ( d_all [ d_use "a" $ d_use "b" $ d_all $ map d_p [ "x", "y" ]+ , d_nuse "a" $ d_use "b" $ d_all $ map d_p [ "y", "z" ]+ ]+ , [ "a? ( b? ( c/x ) )"+ , "!a? ( b? ( c/z ) )"+ , "b? ( c/y )"+ ]+ )+ , -- completely expanded set of USEs+ -- a? ( b? ( c? ( x y z ) )+ -- a? ( b? ( !c? ( x y ) )+ -- a? ( !b? ( c? ( x z ) )+ -- a? ( !b? ( !c? ( x ) )+ --+ -- !a? ( b? ( c? ( y z ) )+ -- !a? ( b? ( !c? ( y ) )+ -- !a? ( !b? ( c? ( z ) )+ -- !a? ( !b? ( !c? ( ) )+ ( d_all [ d_use "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "x", "y", "z" ]+ , d_use "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "x", "y" ]+ , d_use "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "x", "z" ]+ , d_use "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ "x" ]+ , d_nuse "a" $ d_use "b" $ d_use "c" $ d_all $ map d_p [ "y", "z" ]+ , d_nuse "a" $ d_use "b" $ d_nuse "c" $ d_all $ map d_p [ "y" ]+ , d_nuse "a" $ d_nuse "b" $ d_use "c" $ d_all $ map d_p [ "z" ]+ , d_nuse "a" $ d_nuse "b" $ d_nuse "c" $ d_all $ map d_p [ ]+ ]+ , [ "a? ( c/x )"+ , "b? ( c/y )"+ , "c? ( c/z )"+ ]+ )+ , -- pop simple common subdepend+ -- a? ( b? ( d ) )+ -- !a? ( b? ( d ) )+ ( d_all [ d_use "a" $ d_use "b" $ d_p "d"+ , d_nuse "a" $ d_use "b" $ d_p "d"+ ]+ , [ "b? ( c/d )"+ ]+ )+ , -- pop '|| ( some thing )' depend+ ( let any_part = d_any $ map d_p ["a", "b"] in+ d_all [ d_use "u" $ d_all [ any_part , d_p "z"]+ , d_nuse "u" $ any_part+ ]+ , [ "|| ( c/a"+ , " c/b )"+ , "u? ( c/z )"+ ]+ )+ , -- simplify slightly more complex counterguard+ -- v? ( c/d )+ -- u? ( !v? ( c/d ) )+ -- to+ -- v? ( c/d )+ -- u? ( c/d )+ ( d_all [ d_use "v" $ d_p "d"+ , d_use "u" $ d_nuse "v" $ d_p "d"+ ]+ , [ "u? ( c/d )"+ , "v? ( c/d )"+ ]+ )+ + , -- ffi? ( c/d c/e )+ -- !ffi ( !gmp ( c/d c/e ) )+ -- gmp? ( c/d c/e )+ -- to+ -- ( c/d c/e )+ ( let de = d_all [ d_p "d" , d_p "e" ]+ in d_all [ d_use "ffi" de+ , d_nuse "ffi" $ d_nuse "gmp" $ de+ , d_use "gmp" $ de+ ]+ , [ "c/d"+ , "c/e"+ ]+ )+ + {- TODO: another popular case+ , -- simplify even more complex counterguard+ -- u? ( c/d )+ -- !u? ( v? ( c/d ) )+ -- to+ -- u? ( c/d )+ -- v? ( c/d )+ ( d_all [ d_use "u" $ d_p "d"+ , d_nuse "u" $ d_use "v" $ d_p "d"+ ]+ , [ "u? ( c/d )"+ , "v? ( c/d )"+ ]+ )+ -}+ ]+ forM_ deps_norm $ \(d, expected) ->+ (P.dep2str 0 $ P.normalize_depend d) `shouldBe` (intercalate "\n" expected)++ describe "dep2str_noindent" $ do+ it "converts a Dependency into a string" $ do+ let pn = P.mkPackageName "dev-haskell" "mtl"+ def_attr = P.DAttr P.AnySlot []+ p_v v = P.Version { P.versionNumber = v+ , P.versionChar = Nothing+ , P.versionSuffix = []+ , P.versionRevision = 0+ }+ d_all = P.DependAllOf+ d_any = P.DependAnyOf+ d_ge v = P.DependAtom $ P.Atom pn+ (P.DRange (P.NonstrictLB $ p_v v) P.InfinityB)+ def_attr+ d_lt v = P.DependAtom $ P.Atom pn+ (P.DRange P.ZeroB (P.StrictUB $ p_v v))+ def_attr+ deps = [ -- from agda: "mtl ==2.0.* || >=2.1.1 && <2.2"+ ( d_all [ d_any [ d_all [ d_ge [2, 0]+ , d_lt [2, 1]+ ]+ , d_all [ d_ge [2, 1, 1]+ , d_lt [2, 2]+ ]+ ]+ ]+ , [ "|| ( ( >=dev-haskell/mtl-2.0"+ , " <dev-haskell/mtl-2.1 )"+ , " ( >=dev-haskell/mtl-2.1.1"+ , " <dev-haskell/mtl-2.2 ) )"+ ]+ )+ -- remove duplicate entries+ , ( let d = d_all [d_ge [2, 0], d_lt [2, 2]]+ in d_all [d, d]+ , [ ">=dev-haskell/mtl-2.0"+ , "<dev-haskell/mtl-2.2"+ , ">=dev-haskell/mtl-2.0"+ , "<dev-haskell/mtl-2.2"+ ]+ )+ ]+ forM_ deps $ \(d, expected) ->+ P.dep2str_noindent d `shouldBe` intercalate "\n" expected
+ tests/spec/Portage/EBuildSpec.hs view
@@ -0,0 +1,34 @@+module Portage.EBuildSpec (spec) where++import Test.Hspec++import Portage.EBuild++spec :: Spec+spec = do+ describe "sort_iuse" $ do+ it "sorts IUSE alphabetically despite pluses (+)" $ do+ sort_iuse ["+a","c","+b","d"] `shouldBe` ["+a","+b","c","d"]+ describe "drop_tdot" $ do+ it "drops trailing dots (.)" $ do+ drop_tdot "foo." `shouldBe` "foo"+ drop_tdot "foo..." `shouldBe` "foo"+ drop_tdot "foo" `shouldBe` "foo"+ describe "quote" $ do+ it "should correctly surround a string with special characters in quotes" $ do+ quote "Reading, writing and manipulating '.tar' archives." ""+ `shouldBe`+ "\"Reading, writing and manipulating \'.tar\' archives.\""+ quote "Extras for the \"contravariant\" package" ""+ `shouldBe`+ "\"Extras for the \\\"contravariant\\\" package\""+ describe "toHttps" $ do+ it "should not convert whitelisted http-only homepages into https homepages" $ do+ toHttps "http://leksah.org" `shouldBe` "http://leksah.org"+ toHttps "http://darcs.net/" `shouldBe` "http://darcs.net/"+ toHttps "http://khumba.net/" `shouldBe` "http://khumba.net/"+ it "should otherwise convert all homepages into https-aware homepages" $ do+ toHttps "http://pandoc.org" `shouldBe` "https://pandoc.org"+ toHttps "http://www.yesodweb.com/" `shouldBe` "https://www.yesodweb.com/"+ it "should ignore any conversions of homepages already marked as https-aware" $ do+ toHttps "https://github.com" `shouldBe` "https://github.com"
+ tests/spec/Portage/GHCCoreSpec.hs view
@@ -0,0 +1,23 @@+module Portage.GHCCoreSpec (spec) where++import Test.Hspec++import qualified Distribution.Version as Cabal+import qualified Distribution.Package as Cabal++import Portage.GHCCore++spec :: Spec+spec = do+ describe "cabalFromGHC" $ do+ it "returns the corresponding Cabal version for a valid GHC version" $ do+ cabalFromGHC [8,8,3] `shouldBe` Just (Cabal.mkVersion [3,0,1,0])+ it "returns Nothing for an invalid GHC version" $ do+ cabalFromGHC [9,9,9,9] `shouldBe` Nothing+ describe "packageIsCoreInAnyGHC" $ do+ it "returns True for the binary package" $ do+ packageIsCoreInAnyGHC (Cabal.mkPackageName "binary") `shouldBe` True+ it "returns False for the haskeline package (because it is upgradeable)" $ do+ packageIsCoreInAnyGHC (Cabal.mkPackageName "haskeline") `shouldBe` False+ it "returns False for the yesod package" $ do+ packageIsCoreInAnyGHC (Cabal.mkPackageName "yesod") `shouldBe` False
+ tests/spec/Portage/Metadata/RemoteIdSpec.hs view
@@ -0,0 +1,98 @@++module Portage.Metadata.RemoteIdSpec where++import Control.Monad+import Test.Hspec hiding (Example(..))++import Portage.Metadata.RemoteId++spec :: Spec+spec = do+ describe "runUriParser" $ do+ it "parses URI as RemoteId correctly" $ do+ forM_ contrivedExamples $ \(Example u p r) ->+ runUriParser p u `shouldBe` Right r+ it "parses real-world examples correctly" $ do+ forM_ realWorldExamples $ \(Example u p r) ->+ runUriParser p u `shouldBe` Right r+ describe "matchURI" $ do+ it "matches URI correctly" $ do+ forM_ contrivedExamples $ \(Example u _ r) ->+ matchURI u `shouldBe` Just r+ it "matches real-world examples correctly" $ do+ forM_ realWorldExamples $ \(Example u _ r) ->+ matchURI u `shouldBe` Just r++data Example = Example+ { exampleURI :: String+ , exampleParser :: URIParser+ , exampleRemoteId :: RemoteId+ }++contrivedExamples :: [Example]+contrivedExamples =+ [ Example+ "https://cran.r-project.org/web/packages/foo/"+ cranParser+ (RemoteIdCRAN "foo")+ , Example+ "https://ctan.org/pkg/foo"+ ctanParser+ (RemoteIdCTAN "foo")+ , Example+ "https://gitweb.gentoo.org/foo.git/"+ gentooParser+ (RemoteIdGentoo "foo")+ , Example+ "https://github.com/foo/bar"+ githubParser+ (RemoteIdGithub "foo" "bar")+ , Example+ "https://gitlab.com/foo/bar"+ gitlabParser+ (RemoteIdGitlab "foo" "bar")+ , Example+ "https://launchpad.net/foo"+ launchpadParser+ (RemoteIdLaunchpad "foo")+ , Example+ "https://osdn.net/projects/foo/"+ osdnParser+ (RemoteIdOSDN "foo")+ , Example+ "https://pecl.php.net/package/foo"+ peclParser+ (RemoteIdPECL "foo")+ , Example+ "https://pypi.org/project/foo/"+ pypiParser+ (RemoteIdPyPI "foo")+ , Example+ "https://rubygems.org/gems/foo"+ rubygemsParser+ (RemoteIdRubygems "foo")+ , Example+ "https://sourceforge.net/projects/foo/"+ sourceforgeParser+ (RemoteIdSourceforge "foo")+ , Example+ "https://vim.org/scripts/script.php?script_id=foo"+ vimParser+ (RemoteIdVim "foo")+ ]++realWorldExamples :: [Example]+realWorldExamples =+ [ Example+ "git://github.com/gentoo-haskell/hackport.git"+ githubParser+ (RemoteIdGithub "gentoo-haskell" "hackport")+ , Example+ "https://github.com/haskell/parsec"+ githubParser+ (RemoteIdGithub "haskell" "parsec")+ , Example+ "https://github.com/dhall-lang/dhall-haskell/tree/master/dhall-toml"+ githubParser+ (RemoteIdGithub "dhall-lang" "dhall-haskell")+ ]
+ tests/spec/Portage/MetadataSpec.hs view
@@ -0,0 +1,127 @@+module Portage.MetadataSpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck++import qualified Data.Text as T+import qualified Data.Map.Strict as Map+import qualified Data.Set as S++import qualified Portage.EBuild as E+import Portage.Metadata+import Portage.Metadata.RemoteId++spec :: Spec+spec = do+-- TODO: These tests are based off old behavior and should be remade at some point+-- describe "parseMetadataXML" $ do+-- it "returns Nothing from an empty Text" $ do+-- parseMetadataXML T.empty `shouldBe` Nothing+-- it "equals makeMinimalMetadata with no USE flags" $ do+-- parseMetadataXML (printMetadata Map.empty) `shouldBe` Just minimalMetadata+-- it "equals makeMinimalMetadata plus the supplied USE flags" $ do+-- let flags = Map.singleton "name" "description"+-- parseMetadataXML (makeDefaultMetadata flags) `shouldBe` Just (makeMinimalMetadata { metadataUseFlags = flags })++ describe "stripGlobalUseFlags" $ do+ it "should remove specified global USE flags from the metadata.xml" $ do+ stripGlobalUseFlags (Map.singleton "debug" "description") `shouldBe` Map.empty+ stripGlobalUseFlags (Map.singleton "examples" "description") `shouldBe` Map.empty+ stripGlobalUseFlags (Map.singleton "static" "description") `shouldBe` Map.empty+ prop "should ignore USE flags that are not specified as global" $ do+ \name description -> stripGlobalUseFlags (Map.singleton name description) ==+ if name `elem` ["debug","examples","static"]+ then Map.empty+ else Map.singleton name description++ describe "prettyPrintFlags" $ do+ it "correctly handles special XML characters contained in strings" $ do+ let name = "foo"+ desc = "bar < 1.1.0"+ in prettyPrintFlags (Map.singleton name desc) `shouldBe`+ [ "\t<use>"+ , "\t\t<flag name=\"" ++ name ++ "\">"+ ++ "bar < 1.1.0" ++ "</flag>"+ , "\t</use>"+ ]+ it "correctly formats a single USE flag name with its description" $ do+ let name = "foo"+ description = "bar"+ in prettyPrintFlags (Map.singleton name description) `shouldBe`+ [ "\t<use>"+ , "\t\t<flag name=\"" ++ name+ ++ "\">" ++ (unwords . lines $ description)+ ++ "</flag>"+ , "\t</use>"+ ]+ it "correctly formats multiple USE flag names with their descriptions" $ do+ let f1 = "flag1"+ f2 = "flag2"+ d1 = "foo_desc"+ d2 = "bar_desc"+ in prettyPrintFlags (Map.fromList [(f1,d1),(f2,d2)]) `shouldBe`+ [ "\t<use>"+ , "\t\t<flag name=\"" ++ f1+ ++ "\">" ++ (unwords . lines $ d1)+ ++ "</flag>"+ , "\t\t<flag name=\"" ++ f2+ ++ "\">" ++ (unwords . lines $ d2)+ ++ "</flag>"+ , "\t</use>"+ ]++ describe "printMetadata" $ do+ context "when writing a minimal metadata.xml with no USE flags" $ do+ it "should have a certain format" $+ let correctMetadata = T.pack $ unlines+ [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+ , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"+ , "<pkgmetadata>"+ , "\t<maintainer type=\"project\">"+ , "\t\t<email>haskell@gentoo.org</email>"+ , "\t\t<name>Gentoo Haskell</name>"+ , "\t</maintainer>"+ , "\t<upstream>"+ , "\t\t<remote-id type=\"hackage\">FooBar</remote-id>"+ , "\t</upstream>"+ , "</pkgmetadata>"+ ]+ in printMetadata (minimalMetadata True E.ebuildTemplate) `shouldBe` correctMetadata+ context "when writing a minimal metadata.xml with no USE flags and --no-hackage-remote-id" $ do+ it "should have a certain format" $+ let correctMetadata = T.pack $ unlines+ [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+ , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"+ , "<pkgmetadata>"+ , "\t<maintainer type=\"project\">"+ , "\t\t<email>haskell@gentoo.org</email>"+ , "\t\t<name>Gentoo Haskell</name>"+ , "\t</maintainer>"+ , "</pkgmetadata>"+ ]+ in printMetadata (minimalMetadata False E.ebuildTemplate) `shouldBe` correctMetadata+ context "when writing a metadata.xml with USE flags and a GitHub remote-id" $ do+ it "should have a certain format, including the <use> element" $ do+ let meta = minimalMetadata True E.ebuildTemplate <> mempty+ { metadataUseFlags = Map.fromList [("flag1","desc1"),("flag2","desc2")]+ , metadataRemoteIds = S.singleton $ RemoteIdGithub "foo" "bar"+ }+ correctMetadata = T.pack $ unlines+ [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+ , "<!DOCTYPE pkgmetadata SYSTEM \"https://www.gentoo.org/dtd/metadata.dtd\">"+ , "<pkgmetadata>"+ , "\t<maintainer type=\"project\">"+ , "\t\t<email>haskell@gentoo.org</email>"+ , "\t\t<name>Gentoo Haskell</name>"+ , "\t</maintainer>"+ , "\t<use>"+ , "\t\t<flag name=\"flag1\">desc1</flag>"+ , "\t\t<flag name=\"flag2\">desc2</flag>"+ , "\t</use>"+ , "\t<upstream>"+ , "\t\t<remote-id type=\"hackage\">FooBar</remote-id>"+ , "\t\t<remote-id type=\"github\">foo/bar</remote-id>"+ , "\t</upstream>"+ , "</pkgmetadata>"+ ]+ in printMetadata meta `shouldBe` correctMetadata
+ tests/spec/Portage/PackageIdSpec.hs view
@@ -0,0 +1,69 @@+module Portage.PackageIdSpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck+import Test.QuickCheck+import QuickCheck.Instances++import qualified Data.Char as Char+import qualified Distribution.Package as Cabal+import Distribution.Pretty (prettyShow)++import Portage.PackageId++spec :: Spec+spec = do+ describe "packageIdToFilePath" $ do+ prop "transforms a PackageId into a FilePath" $ do+ let cat = Category "dev-haskell"+ name = Cabal.mkPackageName "foo-bar2+"+ in \(ComplexVersion version) ->+ packageIdToFilePath (PackageId (PackageName cat name) version)+ `shouldBe`+ "dev-haskell/" ++ prettyShow name ++ "/" ++ prettyShow name ++ "-" +++ prettyShow version ++ ".ebuild"++ describe "filePathToPackageId" $ do+ prop "returns a Just PackageId from a valid FilePath" $ do+ let cat = Category "dev-haskell"+ name = Cabal.mkPackageName "foo-bar2+"+ in \(ComplexVersion version) ->+ filePathToPackageId cat+ (prettyShow name ++ "-" ++ prettyShow version)+ `shouldBe`+ Just (PackageId (PackageName cat name) version)+ prop "returns Nothing on a malformed FilePath" $ do+ let cat = Category "dev-haskell"+ name = Cabal.mkPackageName "foo-bar-2+"+ in \(ComplexVersion version) ->+ filePathToPackageId cat (prettyShow name ++ "-" ++ prettyShow version)+ `shouldBe`+ Nothing++ describe "normalizeCabalPackageName" $ do+ prop "converts a Cabal.PackageName into lowercase" $ do+ \(PrintableString s) -> normalizeCabalPackageName (Cabal.mkPackageName s)+ `shouldBe` Cabal.mkPackageName (Char.toLower <$> s)++ describe "parseFriendlyPackage" $ do+ prop "parses a package string as [category/]name[-version]" $ do+ let cat = Category "dev-haskell"+ name = Cabal.mkPackageName "package-name1+"+ in \(ComplexVersion version) ->+ parseFriendlyPackage (prettyShow cat ++ "/" ++ prettyShow name+ ++ "-" ++ prettyShow version)+ `shouldBe`+ Right (Just cat, name, Just version)+ prop "returns an error string if parsing a malformed package string" $ do+ let cat = Category "dev-haskell"+ name = Cabal.mkPackageName "package-name-1+"+ in \(ComplexVersion version) ->+ parseFriendlyPackage (prettyShow cat ++ "/" ++ prettyShow name+ ++ "-" ++ prettyShow version)+ `shouldNotBe`+ Right (Just cat, name, Just version)++ describe "cabal_pn_to_PN" $ do+ prop "pretty-prints a Cabal PackageName as a lowercase String" $ do+ \(PrintableString s) -> cabal_pn_to_PN (Cabal.mkPackageName s)+ `shouldBe` Char.toLower <$> s
+ tests/spec/Portage/VersionSpec.hs view
@@ -0,0 +1,27 @@+module Portage.VersionSpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck+import QuickCheck.Instances++import qualified Distribution.Version as Cabal++import Portage.Version++spec :: Spec+spec = do+ describe "is_live" $ do+ prop "determines if a Portage version is live" $ do+ \(ComplexVersion v) -> is_live v `shouldBe`+ if last (versionNumber v) >= 9999+ then True else False+ + describe "fromCabalVersion" $ do+ prop "converts from a Cabal version to a Portage version" $ do+ \verNum -> fromCabalVersion (Cabal.mkVersion verNum) == Version verNum Nothing [] 0+ + describe "toCabalVersion" $ do+ prop "converts from a Portage version to a Cabal version" $ do+ \(ComplexVersion v) -> toCabalVersion v `shouldBe`+ if versionChar v == Nothing && versionSuffix v == []+ then Just (Cabal.mkVersion (versionNumber v)) else Nothing
+ tests/spec/QuickCheck/Instances.hs view
@@ -0,0 +1,44 @@+module QuickCheck.Instances (ComplexVersion(..)) where++import Test.QuickCheck++import Portage.Version (Suffix(..), Version(..))++--------------------------------------------------------------------------------+-- Types+--------------------------------------------------------------------------------+-- | Wrapper for 'Suffix', intended for use in an 'Arbitrary' instance+-- to return a single, valid 'Suffix'.+newtype ValidSuffix = ValidSuffix { getSuffix :: Suffix }+ deriving (Eq,Ord,Show)++-- | Wrapper For 'Version', intended for use in an 'Arbitrary' instance+-- where we want to generate the most complex 'Version' possible.+newtype ComplexVersion = ComplexVersion { getVersion :: Version }+ deriving (Eq,Ord,Show)++--------------------------------------------------------------------------------+-- Instances+--------------------------------------------------------------------------------+-- | Return a single, valid 'ValidSuffix'.+instance Arbitrary ValidSuffix where+ arbitrary = oneof [ ValidSuffix . Alpha . getNonNegative <$> arbitrary+ , ValidSuffix . Beta . getNonNegative <$> arbitrary+ , ValidSuffix . Pre . getNonNegative <$> arbitrary+ , ValidSuffix . RC . getNonNegative <$> arbitrary+ , ValidSuffix . P . getNonNegative <$> arbitrary+ ]+ +-- | Return a valid 'ComplexVersion' with a non-empty 'versionNumber',+-- an optional 'versionChar' if valid, a ['Suffix'] which may be empty,+-- and a 'NonNegative' 'versionRevision' which may be zero.+--+-- This is used to generate a 'Version' of high complexity to+-- stress-test our parsers for a range of valid inputs.+instance Arbitrary ComplexVersion where+ arbitrary = do+ v <- listOf1 $ getNonNegative <$> arbitrary+ c <- oneof $ [Just <$> choose ('a','z'), elements [Nothing]]+ s <- listOf $ getSuffix <$> arbitrary+ (NonNegative r) <- arbitrary+ return $ ComplexVersion $ Version v (if length v == 1 then Nothing else c) s r
+ tests/spec/RunTests.hs view
@@ -0,0 +1,15 @@+module RunTests+ (run_tests) where++import Control.Monad (when)++import System.Exit (exitFailure)+import Test.HUnit++something_broke :: Counts -> Bool+something_broke stats = errors stats + failures stats > 0++run_tests :: Test -> IO ()+run_tests tests =+ do stats <- runTestTT tests+ when (something_broke stats) exitFailure