prune-juice (empty) → 0.1
raw patch · 10 files changed
+514/−0 lines, 10 filesdep +aesondep +basedep +bytestring
Dependencies added: aeson, base, bytestring, containers, directory, file-path-th, filepath, hpack, hspec, megaparsec, mtl, optparse-applicative, prune-juice, text, turtle, yaml
Files
- LICENSE +21/−0
- app/main.hs +61/−0
- prune-juice.cabal +99/−0
- src/Data/Prune/Dependency.hs +49/−0
- src/Data/Prune/ImportParser.hs +72/−0
- src/Data/Prune/Package.hs +73/−0
- src/Data/Prune/Types.hs +56/−0
- test/Data/Prune/ImportParserSpec.hs +46/−0
- test/Data/Prune/PackageSpec.hs +26/−0
- test/main.hs +11/−0
+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2021 Dan Fithian++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ app/main.hs view
@@ -0,0 +1,61 @@+import Prelude++import Control.Applicative (many)+import Control.Monad (unless)+import Control.Monad.IO.Class (liftIO)+import Control.Monad.State (execStateT, put)+import Data.Foldable (for_, traverse_)+import Data.Text (Text, pack, unpack)+import Data.Traversable (for)+import System.Exit (ExitCode(ExitFailure, ExitSuccess), exitWith)+import qualified Data.Set as Set+import qualified Options.Applicative as Opt++import Data.Prune.Dependency (getDependencyByModule)+import Data.Prune.ImportParser (getCompilableUsedDependencies)+import Data.Prune.Package (parseStackYaml)+import qualified Data.Prune.Types as T++data Opts = Opts+ { optsStackYamlFile :: FilePath+ , optsPackages :: [Text]+ }++parseArgs :: IO Opts+parseArgs = Opt.execParser (Opt.info (Opt.helper <*> parser) $ Opt.progDesc "Prune a Stack project's dependencies")+ where+ parser = Opts+ <$> Opt.strOption (+ Opt.long "stack-yaml-file"+ <> Opt.metavar "STACK_YAML_FILE"+ <> Opt.help "Location of stack.yaml"+ <> Opt.value "stack.yaml"+ <> Opt.showDefault )+ <*> many ( pack <$> Opt.strOption (+ Opt.long "package"+ <> Opt.metavar "PACKAGE"+ <> Opt.help "Package name(s)" ) )+++main :: IO ()+main = do+ Opts {..} <- parseArgs+ packages <- parseStackYaml optsStackYamlFile optsPackages++ dependencyByModule <- liftIO $ getDependencyByModule optsStackYamlFile packages+ code <- flip execStateT ExitSuccess $ for_ packages $ \T.Package {..} -> do+ baseUsedDependencies <- fmap mconcat . for packageCompilables $ \compilable@T.Compilable {..} -> do+ usedDependencies <- liftIO $ getCompilableUsedDependencies dependencyByModule compilable+ let (baseUsedDependencies, otherUsedDependencies) = Set.partition (flip Set.member packageBaseDependencies) usedDependencies+ otherUnusedDependencies = Set.difference compilableDependencies otherUsedDependencies+ unless (Set.null otherUnusedDependencies) $ do+ liftIO . putStrLn . unpack $ "Some unused dependencies for " <> pack (show compilableType) <> " " <> T.unCompilableName compilableName <> " in package " <> packageName+ traverse_ (liftIO . putStrLn . unpack . (" " <>) . T.unDependencyName) $ Set.toList otherUnusedDependencies+ put $ ExitFailure 1+ pure baseUsedDependencies+ let baseUnusedDependencies = Set.difference packageBaseDependencies baseUsedDependencies+ unless (Set.null baseUnusedDependencies) $ do+ liftIO . putStrLn . unpack $ "Some unused base dependencies for package " <> packageName+ liftIO . traverse_ (putStrLn . unpack . (" " <>) . T.unDependencyName) $ Set.toList baseUnusedDependencies+ put $ ExitFailure 1+ exitWith code
+ prune-juice.cabal view
@@ -0,0 +1,99 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.33.0.+--+-- see: https://github.com/sol/hpack+--+-- hash: c82d607d1298918a330352ff35cee89ea7edd8f5b4321866f33d55b7558eb1c7++name: prune-juice+version: 0.1+synopsis: Prune unused Haskell dependencies+description: Prune unused Haskell dependencies from a Stack/Hpack project+category: Development+maintainer: Dan Fithian+copyright: 2020 Dan Fithian+license: MIT+license-file: LICENSE+build-type: Simple++library+ exposed-modules:+ Data.Prune.Dependency+ Data.Prune.ImportParser+ Data.Prune.Package+ Data.Prune.Types+ other-modules:+ Paths_prune_juice+ hs-source-dirs:+ src+ default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveDataTypeable DeriveGeneric DerivingStrategies DerivingVia EmptyDataDecls FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings QuasiQuotes RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TemplateHaskell TupleSections TypeApplications TypeFamilies TypeOperators ViewPatterns+ ghc-options: -Wall -fwarn-tabs -fwarn-redundant-constraints -Wincomplete-uni-patterns -eventlog+ build-depends:+ aeson+ , base <5.0+ , bytestring+ , containers+ , directory+ , filepath+ , hpack+ , megaparsec+ , mtl+ , text+ , turtle+ , yaml+ default-language: Haskell2010++executable prune-juice+ main-is: main.hs+ other-modules:+ Paths_prune_juice+ hs-source-dirs:+ app+ default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveDataTypeable DeriveGeneric DerivingStrategies DerivingVia EmptyDataDecls FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings QuasiQuotes RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TemplateHaskell TupleSections TypeApplications TypeFamilies TypeOperators ViewPatterns+ ghc-options: -Wall -fwarn-tabs -fwarn-redundant-constraints -Wincomplete-uni-patterns -eventlog+ build-depends:+ aeson+ , base <5.0+ , bytestring+ , containers+ , directory+ , filepath+ , hpack+ , megaparsec+ , mtl+ , optparse-applicative+ , prune-juice+ , text+ , turtle+ , yaml+ default-language: Haskell2010++test-suite test+ type: exitcode-stdio-1.0+ main-is: main.hs+ other-modules:+ Data.Prune.ImportParserSpec+ Data.Prune.PackageSpec+ Paths_prune_juice+ hs-source-dirs:+ test+ default-extensions: ConstraintKinds DataKinds DefaultSignatures DeriveDataTypeable DeriveGeneric DerivingStrategies DerivingVia EmptyDataDecls FlexibleContexts FlexibleInstances GADTs GeneralizedNewtypeDeriving LambdaCase MultiParamTypeClasses MultiWayIf NamedFieldPuns NoImplicitPrelude NoMonomorphismRestriction OverloadedStrings QuasiQuotes RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TemplateHaskell TupleSections TypeApplications TypeFamilies TypeOperators ViewPatterns+ ghc-options: -Wall -fwarn-tabs -fwarn-redundant-constraints -Wincomplete-uni-patterns -eventlog+ build-depends:+ aeson+ , base <5.0+ , bytestring+ , containers+ , directory+ , file-path-th+ , filepath+ , hpack+ , hspec+ , megaparsec+ , mtl+ , prune-juice+ , text+ , turtle+ , yaml+ default-language: Haskell2010
+ src/Data/Prune/Dependency.hs view
@@ -0,0 +1,49 @@+module Data.Prune.Dependency where++import Prelude hiding (words)++import Data.Foldable (find)+import Data.Map (Map)+import Data.Maybe (mapMaybe)+import Data.Text (Text, isPrefixOf, pack, strip, unpack, words)+import Data.Traversable (for)+import System.Exit (ExitCode(ExitFailure, ExitSuccess))+import Turtle (shellStrict)+import qualified Data.Map as Map+import qualified Data.Set as Set++import Data.Prune.ImportParser (parseExposedModules)+import qualified Data.Prune.Types as T++runOrFail :: Text -> IO Text+runOrFail cmd = shellStrict cmd mempty >>= \case+ (ExitSuccess, out) -> pure out+ (ExitFailure _, out) -> fail . unpack $ "Failed to \"" <> cmd <> "\" due to " <> out++getDependencyByModule :: FilePath -> [T.Package] -> IO (Map T.ModuleName T.DependencyName)+getDependencyByModule stackYamlFile packages = do+ let allDependencies = foldMap T.packageBaseDependencies packages <> foldMap T.compilableDependencies (foldMap T.packageCompilables packages)+ tupleDependency x = (, x) <$> find (\d -> isPrefixOf (T.unDependencyName d) x) allDependencies+ compilerBin <- strip <$> runOrFail ("stack --stack-yaml " <> pack stackYamlFile <> " path --compiler-bin")+ snapshotPkgDb <- strip <$> runOrFail ("stack --stack-yaml " <> pack stackYamlFile <> " path --snapshot-pkg-db")+ globalPkgDb <- strip <$> runOrFail ("stack --stack-yaml " <> pack stackYamlFile <> " path --global-pkg-db")+ localPkgDb <- strip <$> runOrFail ("stack --stack-yaml " <> pack stackYamlFile <> " path --local-pkg-db")+ let snapshotGhcPkg = compilerBin <> "/ghc-pkg --package-db " <> snapshotPkgDb+ globalGhcPkg = compilerBin <> "/ghc-pkg --package-db " <> globalPkgDb+ localGhcPkg = compilerBin <> "/ghc-pkg --package-db " <> localPkgDb+ snapshotPkgs <- mapMaybe tupleDependency . words . strip+ <$> runOrFail (snapshotGhcPkg <> " list --simple-output")+ globalPkgs <- mapMaybe tupleDependency . words . strip+ <$> runOrFail (globalGhcPkg <> " list --simple-output")+ localPkgs <- mapMaybe tupleDependency . words . strip+ <$> runOrFail (localGhcPkg <> " list --simple-output")+ snapshotDependencyByModule <- fmap mconcat . for snapshotPkgs $ \(dependencyName, pkg) -> do+ moduleNames <- parseExposedModules . unpack . strip =<< runOrFail (snapshotGhcPkg <> " field " <> pkg <> " exposed-modules")+ pure . Map.fromList . map (, dependencyName) . Set.toList $ moduleNames+ globalDependencyByModule <- fmap mconcat . for globalPkgs $ \(dependencyName, pkg) -> do+ moduleNames <- parseExposedModules . unpack . strip =<< runOrFail (globalGhcPkg <> " field " <> pkg <> " exposed-modules")+ pure . Map.fromList . map (, dependencyName) . Set.toList $ moduleNames+ localDependencyByModule <- fmap mconcat . for localPkgs $ \(dependencyName, pkg) -> do+ moduleNames <- parseExposedModules . unpack . strip =<< runOrFail (localGhcPkg <> " field " <> pkg <> " exposed-modules")+ pure . Map.fromList . map (, dependencyName) . Set.toList $ moduleNames+ pure $ snapshotDependencyByModule <> globalDependencyByModule <> localDependencyByModule
+ src/Data/Prune/ImportParser.hs view
@@ -0,0 +1,72 @@+module Data.Prune.ImportParser where++import Prelude++import Control.Applicative ((<|>), optional, some)+import Control.Monad (void)+import Data.List (isPrefixOf)+import Data.Map (Map)+import Data.Set (Set)+import Data.Text (pack)+import Data.Traversable (for)+import Data.Void (Void)+import Text.Megaparsec (Parsec, between, oneOf, parse)+import Text.Megaparsec.Char (alphaNumChar, char, space, string, symbolChar)+import qualified Data.Map as Map+import qualified Data.Set as Set++import qualified Data.Prune.Types as T++type Parser = Parsec Void String++padded :: Parser a -> Parser a+padded = between space space++quoted :: Parser a -> Parser a+quoted = between (ptoken "\"") (ptoken "\"")++ptoken :: String -> Parser String+ptoken = padded . string++operator :: Parser String+operator = concat <$> sequence [ptoken "(", symbolChars, ptoken ")"]++symbolChars :: Parser String+symbolChars = some (oneOf ("!#$%&*+./<=>?@^|-~:\\" :: String)) <|> some symbolChar++symbol :: Parser String+symbol = padded $ operator <|> some (alphaNumChar <|> oneOf ("._'" :: String))++pkgName :: Parser String+pkgName = some (alphaNumChar <|> char '-')++oneImport :: Parser T.ModuleName+oneImport = void (string "import") *> space+ *> optional (void (string "qualified") *> space)+ *> optional (void (padded (quoted pkgName)) *> space)+ *> (T.ModuleName . pack <$> (symbol <* space))++exposedModules :: Parser (Set T.ModuleName)+exposedModules = void (string "exposed-modules:") *> space+ *> (Set.fromList <$> some (T.ModuleName . pack <$> symbol))++parseFileImports :: FilePath -> IO (Set T.ModuleName)+parseFileImports fp = do+ either (fail . ("Failed to parse imports due to " <>) . show) (pure . Set.fromList) . traverse (parse oneImport fp) . filter (isPrefixOf "import ") . lines+ =<< readFile fp++parseExposedModules :: String -> IO (Set T.ModuleName)+parseExposedModules input =+ if null input+ then pure mempty+ else either (\e -> fail $ "Failed to parse exposed modules due to " <> show e <> " original input " <> input) pure $ parse exposedModules "" input++getUsedDependencies :: Map T.ModuleName T.DependencyName -> Set T.ModuleName -> Set T.DependencyName+getUsedDependencies dependencyByModule = foldr go mempty . Set.toList+ where+ go next acc = acc <> maybe mempty Set.singleton (Map.lookup next dependencyByModule)++getCompilableUsedDependencies :: Map T.ModuleName T.DependencyName -> T.Compilable -> IO (Set T.DependencyName)+getCompilableUsedDependencies dependencyByModule T.Compilable {..} = fmap mconcat . for (Set.toList compilableFiles) $ \fp -> do+ moduleNames <- parseFileImports fp+ pure $ getUsedDependencies dependencyByModule moduleNames
+ src/Data/Prune/Package.hs view
@@ -0,0 +1,73 @@+module Data.Prune.Package where++import Prelude++import Data.Map (Map)+import Data.Set (Set)+import Data.Text (Text, pack)+import Data.Traversable (for)+import Hpack.Config+ ( Section, decodeOptionsTarget, decodeResultPackage, defaultDecodeOptions, packageBenchmarks+ , packageConfig, packageExecutables, packageInternalLibraries, packageLibrary, packageName+ , packageTests, readPackageConfig, sectionDependencies, sectionSourceDirs, unDependencies+ )+import System.Directory (doesDirectoryExist, listDirectory, pathIsSymbolicLink)+import System.FilePath.Posix ((</>), isExtensionOf)+import qualified Data.ByteString as BS+import qualified Data.Map as Map+import qualified Data.Set as Set+import qualified Data.Yaml as Yaml++import qualified Data.Prune.Types as T++listFilesRecursive :: FilePath -> IO (Set FilePath)+listFilesRecursive dir = do+ dirs <- listDirectory dir+ fmap mconcat . for dirs $ \case+ -- don't include "hidden" directories, i.e. those that start with a '.'+ '.' : _ -> pure mempty+ fn -> do+ let+ path = if dir == "." then fn else dir </> fn+ isDir <- doesDirectoryExist path+ isSymlink <- pathIsSymbolicLink path+ case (isSymlink, isDir) of+ (True, _) -> pure mempty+ (_, True) -> listFilesRecursive path+ _ -> pure $ Set.singleton path++getSectionDependencyNames :: Section a -> Set T.DependencyName+getSectionDependencyNames = Set.fromList . map (T.DependencyName . pack) . Map.keys . unDependencies . sectionDependencies++getSectionFiles :: FilePath -> Section a -> IO (Set FilePath)+getSectionFiles fp section = fmap mconcat . for (sectionSourceDirs section) $ \dir -> do+ allFiles <- listFilesRecursive $ fp </> dir+ pure $ Set.filter (isExtensionOf "hs") allFiles++getSectionCompilables :: FilePath -> T.CompilableType -> Set T.DependencyName -> Map String (Section a) -> IO [T.Compilable]+getSectionCompilables fp typ baseDependencies sections = for (Map.toList sections) $ \(name, section) -> do+ sourceFiles <- getSectionFiles fp section+ pure $ T.Compilable (T.CompilableName (pack name)) typ (Set.difference (getSectionDependencyNames section) baseDependencies) sourceFiles++parsePackageYaml :: FilePath -> IO T.Package+parsePackageYaml fp = do+ package <- either fail (pure . decodeResultPackage) =<< readPackageConfig (defaultDecodeOptions { decodeOptionsTarget = fp </> packageConfig })+ let baseDependencies = maybe mempty getSectionDependencyNames $ packageLibrary package+ libraries <- getSectionCompilables fp T.CompilableTypeLibrary baseDependencies $ maybe mempty (Map.singleton (packageName package)) $ packageLibrary package+ internalLibraries <- getSectionCompilables fp T.CompilableTypeLibrary baseDependencies $ packageInternalLibraries package+ executables <- getSectionCompilables fp T.CompilableTypeExecutable baseDependencies $ packageExecutables package+ tests <- getSectionCompilables fp T.CompilableTypeTest baseDependencies $ packageTests package+ benchmarks <- getSectionCompilables fp T.CompilableTypeBenchmark baseDependencies $ packageBenchmarks package+ pure T.Package+ { packageName = pack $ packageName package+ , packageBaseDependencies = baseDependencies+ , packageCompilables = libraries <> internalLibraries <> executables <> tests <> benchmarks+ }++parseStackYaml :: FilePath -> [Text] -> IO [T.Package]+parseStackYaml stackYamlFile packages = do+ T.StackYaml {..} <- either (fail . ("Couldn't parse stack.yaml due to " <>) . show) pure . Yaml.decodeEither' =<< BS.readFile stackYamlFile+ rawPackages <- traverse parsePackageYaml stackYamlPackages+ if null packages+ then pure rawPackages+ else pure $ filter (flip elem packages . T.packageName) rawPackages
+ src/Data/Prune/Types.hs view
@@ -0,0 +1,56 @@+module Data.Prune.Types where++import Prelude++import Data.Aeson ((.:), FromJSON, parseJSON, withObject)+import Data.Set (Set)+import Data.Text (Text)++data CompilableType+ = CompilableTypeLibrary+ | CompilableTypeExecutable+ | CompilableTypeTest+ | CompilableTypeBenchmark+ deriving (Eq, Ord)++instance Show CompilableType where+ show = \case+ CompilableTypeLibrary -> "library"+ CompilableTypeExecutable -> "executable"+ CompilableTypeTest -> "test"+ CompilableTypeBenchmark -> "benchmark"++newtype CompilableName = CompilableName { unCompilableName :: Text }+ deriving (Eq, Ord, Show)++data DependencyName = DependencyName { unDependencyName :: Text }+ deriving (Eq, Ord, Show)++data ModuleName = ModuleName { unModuleName :: Text }+ deriving (Eq, Ord, Show)++data Compilable = Compilable+ { compilableName :: CompilableName+ , compilableType :: CompilableType+ , compilableDependencies :: Set DependencyName+ , compilableFiles :: Set FilePath+ }+ deriving (Eq, Ord, Show)++data Package = Package+ { packageName :: Text+ , packageBaseDependencies :: Set DependencyName+ , packageCompilables :: [Compilable]+ }+ deriving (Eq, Ord, Show)++data StackYaml = StackYaml+ { stackYamlPackages :: [FilePath]+ -- ^ FIXME not every package is this way.+ }+ deriving (Eq, Ord, Show)++instance FromJSON StackYaml where+ parseJSON = withObject "StackYaml" $ \obj ->+ StackYaml+ <$> obj .: "packages"
+ test/Data/Prune/ImportParserSpec.hs view
@@ -0,0 +1,46 @@+module Data.Prune.ImportParserSpec where++import Prelude++import Test.Hspec (Spec, describe, it, shouldBe)+import Text.Megaparsec (parse)+import qualified Data.Set as Set++import qualified Data.Prune.Types as T++-- the module being tested+import Data.Prune.ImportParser++spec :: Spec+spec = describe "Data.Prune.ImportParser" $ do+ it "should parse wildcards" $ do+ parse oneImport "" "import Foo.Bar" `shouldBe` Right (T.ModuleName "Foo.Bar")++ it "should parse imports" $ do+ parse oneImport "" "import Foo.Bar (foo, bar)" `shouldBe` Right (T.ModuleName "Foo.Bar")++ it "should parse qualified" $ do+ parse oneImport "" "import qualified Foo.Bar as Baz" `shouldBe` Right (T.ModuleName "Foo.Bar")++ it "should parse package imports" $ do+ parse oneImport "" "import \"foobar\" Foo.Bar ()" `shouldBe` Right (T.ModuleName "Foo.Bar")++ it "should parse something complicated" $ do+ parse oneImport "" "import qualified \"foobar\" Foo.Bar as Baz (foo, bar) " `shouldBe` Right (T.ModuleName "Foo.Bar")++ it "should parse exposed modules - single line" $ do+ parse exposedModules "" "exposed-modules: Hpack Hpack.Config Hpack.Render Hpack.Yaml"+ `shouldBe` Right (Set.fromList [T.ModuleName "Hpack", T.ModuleName "Hpack.Config", T.ModuleName "Hpack.Render", T.ModuleName "Hpack.Yaml"])++ it "should parse exposed modules - multiline" $ do+ parse exposedModules "" "exposed-modules: Text.Megaparsec Text.Megaparsec.Byte\n\+ \Text.Megaparsec.Byte.Lexer Text.Megaparsec.Char\n\+ \Text.Megaparsec.Char.Lexer Text.Megaparsec.Debug\n\+ \Text.Megaparsec.Error Text.Megaparsec.Error.Builder\n\+ \Text.Megaparsec.Internal Text.Megaparsec.Pos Text.Megaparsec.Stream"+ `shouldBe` Right ( Set.fromList+ [ T.ModuleName "Text.Megaparsec", T.ModuleName "Text.Megaparsec.Byte"+ , T.ModuleName "Text.Megaparsec.Byte.Lexer", T.ModuleName "Text.Megaparsec.Char"+ , T.ModuleName "Text.Megaparsec.Char.Lexer", T.ModuleName "Text.Megaparsec.Debug"+ , T.ModuleName "Text.Megaparsec.Error", T.ModuleName "Text.Megaparsec.Error.Builder"+ , T.ModuleName "Text.Megaparsec.Internal", T.ModuleName "Text.Megaparsec.Pos", T.ModuleName "Text.Megaparsec.Stream" ] )
+ test/Data/Prune/PackageSpec.hs view
@@ -0,0 +1,26 @@+module Data.Prune.PackageSpec where++import Prelude++import Data.Foldable (find)+import Data.List (isSuffixOf)+import System.FilePath.TH (fileRelativeToAbsolute)+import Test.Hspec (Spec, describe, it, shouldBe, shouldSatisfy)+import qualified Data.Set as Set++import qualified Data.Prune.Types as T++-- the module being tested+import Data.Prune.Package++spec :: Spec+spec = describe "Data.Prune.Package" $ do+ let stackYamlFile = $(fileRelativeToAbsolute "../../../stack.yaml")+ it "parses stack.yaml" $ do+ [T.Package {..}] <- parseStackYaml stackYamlFile []+ packageName `shouldBe` "prune-juice"+ packageBaseDependencies `shouldSatisfy` Set.member (T.DependencyName "base")+ T.Compilable {..} <- maybe (fail "No compilable with the name \"test\"") pure $ find ((==) (T.CompilableName "test") . T.compilableName) packageCompilables+ compilableType `shouldBe` T.CompilableTypeTest+ compilableDependencies `shouldSatisfy` not . Set.null+ compilableFiles `shouldSatisfy` not . Set.null . Set.filter (isSuffixOf "PackageSpec.hs")
+ test/main.hs view
@@ -0,0 +1,11 @@+import Prelude++import Test.Hspec (hspec)++import qualified Data.Prune.ImportParserSpec+import qualified Data.Prune.PackageSpec++main :: IO ()+main = hspec $ do+ Data.Prune.ImportParserSpec.spec+ Data.Prune.PackageSpec.spec