hpack 0.4.0 → 0.5.0
raw patch · 7 files changed
+358/−180 lines, 7 filesdep +GlobPVP ok
version bump matches the API change (PVP)
Dependencies added: Glob
API changes (from Hackage documentation)
- Hpack.Config: executableCppOptions :: Executable -> [CppOption]
- Hpack.Config: executableDefaultExtensions :: Executable -> [String]
- Hpack.Config: executableDependencies :: Executable -> [[Dependency]]
- Hpack.Config: executableGhcOptions :: Executable -> [GhcOption]
- Hpack.Config: executableSourceDirs :: Executable -> [FilePath]
- Hpack.Config: instance (Data a, FromJSON a) => FromJSON (CaptureUnknownFields a)
- Hpack.Config: instance Selector S1_0_17PackageConfig
- Hpack.Config: instance Selector S1_0_18PackageConfig
- Hpack.Config: instance Selector S1_0_19PackageConfig
- Hpack.Config: instance Selector S1_0_20PackageConfig
- Hpack.Config: instance Selector S1_0_21PackageConfig
- Hpack.Config: instance Selector S1_0_2ExecutableSection
- Hpack.Config: instance Selector S1_0_2LibrarySection
- Hpack.Config: instance Selector S1_0_3ExecutableSection
- Hpack.Config: instance Selector S1_0_3LibrarySection
- Hpack.Config: instance Selector S1_0_4ExecutableSection
- Hpack.Config: instance Selector S1_0_4LibrarySection
- Hpack.Config: instance Selector S1_0_5ExecutableSection
- Hpack.Config: instance Selector S1_0_5LibrarySection
- Hpack.Config: instance Selector S1_0_6ExecutableSection
- Hpack.Config: instance Selector S1_0_6LibrarySection
- Hpack.Config: libraryCppOptions :: Library -> [CppOption]
- Hpack.Config: libraryDefaultExtensions :: Library -> [String]
- Hpack.Config: libraryDependencies :: Library -> [[Dependency]]
- Hpack.Config: libraryGhcOptions :: Library -> [GhcOption]
- Hpack.Config: librarySourceDirs :: Library -> [FilePath]
+ Hpack.Config: Section :: a -> [FilePath] -> [[Dependency]] -> [String] -> [GhcOption] -> [CppOption] -> Section a
+ Hpack.Config: data Section a
+ Hpack.Config: instance (HasFieldNames a, FromJSON a) => FromJSON (CaptureUnknownFields a)
+ Hpack.Config: instance Constructor C1_0CommonOptions
+ Hpack.Config: instance Constructor C1_0WithCommonOptions
+ Hpack.Config: instance Data CommonOptions
+ Hpack.Config: instance Data a => Data (WithCommonOptions a)
+ Hpack.Config: instance Datatype D1CommonOptions
+ Hpack.Config: instance Datatype D1WithCommonOptions
+ Hpack.Config: instance Eq CommonOptions
+ Hpack.Config: instance Eq a => Eq (Section a)
+ Hpack.Config: instance Eq a => Eq (WithCommonOptions a)
+ Hpack.Config: instance Foldable Section
+ Hpack.Config: instance FromJSON CommonOptions
+ Hpack.Config: instance FromJSON a => FromJSON (WithCommonOptions a)
+ Hpack.Config: instance Functor Section
+ Hpack.Config: instance Generic (WithCommonOptions a)
+ Hpack.Config: instance Generic CommonOptions
+ Hpack.Config: instance HasFieldNames CommonOptions
+ Hpack.Config: instance HasFieldNames ExecutableSection
+ Hpack.Config: instance HasFieldNames LibrarySection
+ Hpack.Config: instance HasFieldNames PackageConfig
+ Hpack.Config: instance HasFieldNames a => HasFieldNames (WithCommonOptions a)
+ Hpack.Config: instance Selector S1_0_0CommonOptions
+ Hpack.Config: instance Selector S1_0_1CommonOptions
+ Hpack.Config: instance Selector S1_0_2CommonOptions
+ Hpack.Config: instance Selector S1_0_3CommonOptions
+ Hpack.Config: instance Selector S1_0_4CommonOptions
+ Hpack.Config: instance Show CommonOptions
+ Hpack.Config: instance Show a => Show (Section a)
+ Hpack.Config: instance Show a => Show (WithCommonOptions a)
+ Hpack.Config: instance Traversable Section
+ Hpack.Config: instance Typeable CommonOptions
+ Hpack.Config: instance Typeable WithCommonOptions
+ Hpack.Config: sectionCppOptions :: Section a -> [CppOption]
+ Hpack.Config: sectionData :: Section a -> a
+ Hpack.Config: sectionDefaultExtensions :: Section a -> [String]
+ Hpack.Config: sectionDependencies :: Section a -> [[Dependency]]
+ Hpack.Config: sectionGhcOptions :: Section a -> [GhcOption]
+ Hpack.Config: sectionSourceDirs :: Section a -> [FilePath]
+ Hpack.Run: formatDescription :: Int -> String -> String
- Hpack.Config: Executable :: String -> FilePath -> [FilePath] -> [String] -> [[Dependency]] -> [String] -> [GhcOption] -> [CppOption] -> Executable
+ Hpack.Config: Executable :: String -> FilePath -> [String] -> Executable
- Hpack.Config: Library :: [FilePath] -> [String] -> [String] -> [[Dependency]] -> [String] -> [GhcOption] -> [CppOption] -> Library
+ Hpack.Config: Library :: [String] -> [String] -> Library
- Hpack.Config: Package :: String -> String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> [String] -> [String] -> [String] -> Maybe String -> Maybe FilePath -> [FilePath] -> Maybe SourceRepository -> Maybe Library -> [Executable] -> [Executable] -> Package
+ Hpack.Config: Package :: String -> String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> Maybe String -> [String] -> [String] -> [String] -> Maybe String -> Maybe FilePath -> [FilePath] -> Maybe SourceRepository -> Maybe (Section Library) -> [Section Executable] -> [Section Executable] -> Package
- Hpack.Config: packageExecutables :: Package -> [Executable]
+ Hpack.Config: packageExecutables :: Package -> [Section Executable]
- Hpack.Config: packageLibrary :: Package -> Maybe Library
+ Hpack.Config: packageLibrary :: Package -> Maybe (Section Library)
- Hpack.Config: packageTests :: Package -> [Executable]
+ Hpack.Config: packageTests :: Package -> [Section Executable]
Files
- hpack.cabal +4/−1
- src/Hpack/Config.hs +147/−105
- src/Hpack/Run.hs +46/−34
- src/Hpack/Util.hs +22/−0
- test/Hpack/ConfigSpec.hs +33/−31
- test/Hpack/RunSpec.hs +42/−9
- test/Hpack/UtilSpec.hs +64/−0
hpack.cabal view
@@ -3,7 +3,7 @@ -- see: https://github.com/sol/hpack name: hpack-version: 0.4.0+version: 0.5.0 synopsis: An alternative format for Haskell packages homepage: https://github.com/sol/hpack#readme bug-reports: https://github.com/sol/hpack/issues@@ -31,6 +31,7 @@ , deepseq , directory , filepath+ , Glob , text , unordered-containers , yaml@@ -47,6 +48,7 @@ , deepseq , directory , filepath+ , Glob , text , unordered-containers , yaml@@ -74,6 +76,7 @@ , deepseq , directory , filepath+ , Glob , text , unordered-containers , yaml
src/Hpack/Config.hs view
@@ -1,9 +1,14 @@-{-# LANGUAGE DeriveGeneric #-}-{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-} {-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-} module Hpack.Config ( packageConfig , readPackageConfig@@ -12,28 +17,29 @@ , GitRef(..) , packageDependencies , GhcOption+, Section(..) , Library(..) , Executable(..) , SourceRepository(..) ) where -import Prelude ()-import Prelude.Compat import Control.Applicative import Control.Monad.Compat-import Data.List (nub, sort, (\\))-import Data.String-import Data.Maybe-import Data.Yaml-import GHC.Generics+import Data.Aeson.Types+import Data.Data import Data.HashMap.Lazy (HashMap) import qualified Data.HashMap.Lazy as Map+import Data.List (nub, sort, (\\))+import Data.Maybe+import Data.String import Data.Text (Text) import qualified Data.Text as T-import System.FilePath+import Data.Yaml+import GHC.Generics+import Prelude ()+import Prelude.Compat import System.Directory-import Data.Data-import Data.Aeson.Types+import System.FilePath import Hpack.Util @@ -51,12 +57,20 @@ hyphenize :: String -> String -> String hyphenize name = camelTo '-' . drop (length name) +class HasFieldNames a where+ fieldNames :: a -> [String]+ default fieldNames :: Data a => a -> [String]+ fieldNames a = map (hyphenize name) (constrFields constr)+ where+ constr = toConstr a+ name = showConstr constr+ data CaptureUnknownFields a = CaptureUnknownFields { captureUnknownFieldsFields :: [String] , captureUnknownFieldsValue :: a } deriving (Eq, Show, Generic, Data, Typeable) -instance (Data a, FromJSON a) => FromJSON (CaptureUnknownFields a) where+instance (HasFieldNames a, FromJSON a) => FromJSON (CaptureUnknownFields a) where parseJSON v = captureUnknownFields <$> parseJSON v where captureUnknownFields a = case v of@@ -64,37 +78,51 @@ where unknown = keys \\ fields keys = map T.unpack (Map.keys o)- constr = toConstr a- name = showConstr constr- fields = map (hyphenize name) (constrFields constr)+ fields = fieldNames a _ -> CaptureUnknownFields [] a data LibrarySection = LibrarySection {- librarySectionSourceDirs :: Maybe (List FilePath)-, librarySectionExposedModules :: Maybe (List String)+ librarySectionExposedModules :: Maybe (List String) , librarySectionOtherModules :: Maybe (List String)-, librarySectionDependencies :: Maybe (List Dependency)-, librarySectionDefaultExtensions :: Maybe (List String)-, librarySectionGhcOptions :: Maybe (List GhcOption)-, librarySectionCppOptions :: Maybe (List CppOption) } deriving (Eq, Show, Generic, Data, Typeable) +instance HasFieldNames LibrarySection+ instance FromJSON LibrarySection where parseJSON = genericParseJSON_ data ExecutableSection = ExecutableSection { executableSectionMain :: FilePath-, executableSectionSourceDirs :: Maybe (List FilePath) , executableSectionOtherModules :: Maybe (List String)-, executableSectionDependencies :: Maybe (List Dependency)-, executableSectionDefaultExtensions :: Maybe (List String)-, executableSectionGhcOptions :: Maybe (List GhcOption)-, executableSectionCppOptions :: Maybe (List CppOption) } deriving (Eq, Show, Generic, Data, Typeable) +instance HasFieldNames ExecutableSection+ instance FromJSON ExecutableSection where parseJSON = genericParseJSON_ +data WithCommonOptions a = WithCommonOptions a CommonOptions+ deriving (Eq, Show, Generic, Data, Typeable)++instance HasFieldNames a => HasFieldNames (WithCommonOptions a) where+ fieldNames (WithCommonOptions a options) = (fieldNames a ++ fieldNames options) \\ ["config"] -- FIXME : test for removing "config"++instance FromJSON a => FromJSON (WithCommonOptions a) where+ parseJSON v = WithCommonOptions <$> parseJSON v <*> parseJSON v++data CommonOptions = CommonOptions {+ commonOptionsSourceDirs :: Maybe (List FilePath)+, commonOptionsDependencies :: Maybe (List Dependency)+, commonOptionsDefaultExtensions :: Maybe (List String)+, commonOptionsGhcOptions :: Maybe (List GhcOption)+, commonOptionsCppOptions :: Maybe (List CppOption)+} deriving (Eq, Show, Generic, Data, Typeable)++instance HasFieldNames CommonOptions++instance FromJSON CommonOptions where+ parseJSON = genericParseJSON_+ data PackageConfig = PackageConfig { packageConfigName :: Maybe String , packageConfigVersion :: Maybe String@@ -110,21 +138,18 @@ , packageConfigLicense :: Maybe String , packageConfigExtraSourceFiles :: Maybe (List FilePath) , packageConfigGithub :: Maybe Text-, packageConfigSourceDirs :: Maybe (List FilePath)-, packageConfigDependencies :: Maybe (List Dependency)-, packageConfigDefaultExtensions :: Maybe (List String)-, packageConfigGhcOptions :: Maybe (List GhcOption)-, packageConfigCppOptions :: Maybe (List CppOption)-, packageConfigLibrary :: Maybe (CaptureUnknownFields LibrarySection)-, packageConfigExecutables :: Maybe (HashMap String (CaptureUnknownFields ExecutableSection))-, packageConfigTests :: Maybe (HashMap String (CaptureUnknownFields ExecutableSection))+, packageConfigLibrary :: Maybe (CaptureUnknownFields (WithCommonOptions LibrarySection))+, packageConfigExecutables :: Maybe (HashMap String (CaptureUnknownFields (WithCommonOptions ExecutableSection)))+, packageConfigTests :: Maybe (HashMap String (CaptureUnknownFields (WithCommonOptions ExecutableSection))) } deriving (Eq, Show, Generic, Data, Typeable) +instance HasFieldNames PackageConfig+ packageDependencies :: Package -> [Dependency] packageDependencies Package{..} = nub . sort $- (concat $ concatMap executableDependencies packageExecutables)- ++ (concat $ concatMap executableDependencies packageTests)- ++ maybe [] (concat . libraryDependencies) packageLibrary+ (concat $ concatMap sectionDependencies packageExecutables)+ ++ (concat $ concatMap sectionDependencies packageTests)+ ++ maybe [] (concat . sectionDependencies) packageLibrary instance FromJSON PackageConfig where parseJSON value = handleNullValues <$> genericParseJSON_ value@@ -213,60 +238,55 @@ , packageLicenseFile :: Maybe FilePath , packageExtraSourceFiles :: [FilePath] , packageSourceRepository :: Maybe SourceRepository-, packageLibrary :: Maybe Library-, packageExecutables :: [Executable]-, packageTests :: [Executable]+, packageLibrary :: Maybe (Section Library)+, packageExecutables :: [Section Executable]+, packageTests :: [Section Executable] } deriving (Eq, Show) data Library = Library {- librarySourceDirs :: [FilePath]-, libraryExposedModules :: [String]+ libraryExposedModules :: [String] , libraryOtherModules :: [String]-, libraryDependencies :: [[Dependency]]-, libraryDefaultExtensions :: [String]-, libraryGhcOptions :: [GhcOption]-, libraryCppOptions :: [CppOption] } deriving (Eq, Show) data Executable = Executable { executableName :: String , executableMain :: FilePath-, executableSourceDirs :: [FilePath] , executableOtherModules :: [String]-, executableDependencies :: [[Dependency]]-, executableDefaultExtensions :: [String]-, executableGhcOptions :: [GhcOption]-, executableCppOptions :: [CppOption] } deriving (Eq, Show) +data Section a = Section {+ sectionData :: a+, sectionSourceDirs :: [FilePath]+, sectionDependencies :: [[Dependency]]+, sectionDefaultExtensions :: [String]+, sectionGhcOptions :: [GhcOption]+, sectionCppOptions :: [CppOption]+} deriving (Eq, Show, Functor, Foldable, Traversable)+ data SourceRepository = SourceRepository { sourceRepositoryUrl :: String , sourceRepositorySubdir :: Maybe String } deriving (Eq, Show) -mkPackage :: (CaptureUnknownFields PackageConfig) -> IO ([String], Package)-mkPackage (CaptureUnknownFields unknownFields PackageConfig{..}) = do- let dependencies = fromMaybeList packageConfigDependencies- sourceDirs = fromMaybeList packageConfigSourceDirs- defaultExtensions = fromMaybeList packageConfigDefaultExtensions- ghcOptions = fromMaybeList packageConfigGhcOptions- cppOptions = fromMaybeList packageConfigCppOptions- convert f = f sourceDirs dependencies defaultExtensions ghcOptions cppOptions-- mLibrary <- mapM (convert toLibrary) mLibrarySection- executables <- convert toExecutables mExecutableSections- tests <- convert toExecutables mTestSections+mkPackage :: (CaptureUnknownFields (WithCommonOptions PackageConfig)) -> IO ([String], Package)+mkPackage (CaptureUnknownFields unknownFields (WithCommonOptions PackageConfig{..} globalOptions@CommonOptions{..})) = do+ mLibrary <- mapM (toLibrary globalOptions) mLibrarySection+ executables <- toExecutables globalOptions (map (fmap captureUnknownFieldsValue) executableSections)+ tests <- toExecutables globalOptions (map (fmap captureUnknownFieldsValue) testsSections) name <- maybe (takeBaseName <$> getCurrentDirectory) return packageConfigName licenseFileExists <- doesFileExist "LICENSE" missingSourceDirs <- nub . sort <$> filterM (fmap not <$> doesDirectoryExist) (- maybe [] librarySourceDirs mLibrary- ++ concatMap executableSourceDirs executables- ++ concatMap executableSourceDirs tests+ maybe [] sectionSourceDirs mLibrary+ ++ concatMap sectionSourceDirs executables+ ++ concatMap sectionSourceDirs tests ) + (extraSourceFilesWarnings, extraSourceFiles) <-+ expandGlobs (fromMaybeList packageConfigExtraSourceFiles)+ let package = Package { packageName = name , packageVersion = fromMaybe "0.0.0" packageConfigVersion@@ -281,7 +301,7 @@ , packageCopyright = fromMaybeList packageConfigCopyright , packageLicense = packageConfigLicense , packageLicenseFile = guard licenseFileExists >> Just "LICENSE"- , packageExtraSourceFiles = fromMaybeList packageConfigExtraSourceFiles+ , packageExtraSourceFiles = extraSourceFiles , packageSourceRepository = sourceRepository , packageLibrary = mLibrary , packageExecutables = executables@@ -291,26 +311,32 @@ warnings = formatUnknownFields "package description" unknownFields ++ maybe [] (formatUnknownFields "library section") (captureUnknownFieldsFields <$> packageConfigLibrary)- ++ formatUnknownSectionFields "executable" packageConfigExecutables- ++ formatUnknownSectionFields "test" packageConfigTests+ ++ formatUnknownSectionFields "executable" executableSections+ ++ formatUnknownSectionFields "test" testsSections ++ formatMissingSourceDirs missingSourceDirs+ ++ extraSourceFilesWarnings return (warnings, package) where- mLibrarySection :: Maybe LibrarySection- mLibrarySection = captureUnknownFieldsValue <$> packageConfigLibrary+ executableSections :: [(String, CaptureUnknownFields (WithCommonOptions ExecutableSection))]+ executableSections = toList packageConfigExecutables - mExecutableSections :: Maybe (HashMap String ExecutableSection)- mExecutableSections = fmap captureUnknownFieldsValue <$> packageConfigExecutables+ testsSections :: [(String, CaptureUnknownFields (WithCommonOptions ExecutableSection))]+ testsSections = toList packageConfigTests - mTestSections :: Maybe (HashMap String ExecutableSection)- mTestSections = fmap captureUnknownFieldsValue <$> packageConfigTests+ toList :: Maybe (HashMap String a) -> [(String, a)]+ toList = Map.toList . fromMaybe mempty + mLibrarySection :: Maybe (WithCommonOptions LibrarySection)+ mLibrarySection = captureUnknownFieldsValue <$> packageConfigLibrary++ formatUnknownFields :: String -> [String] -> [String] formatUnknownFields name = map f . sort where f field = "Ignoring unknown field " ++ show field ++ " in " ++ name - formatUnknownSectionFields sectionType = maybe [] (concatMap f . Map.toList . fmap captureUnknownFieldsFields)+ formatUnknownSectionFields :: String -> [(String, CaptureUnknownFields a)] -> [String]+ formatUnknownSectionFields sectionType = concatMap f . map (fmap captureUnknownFieldsFields) where f :: (String, [String]) -> [String] f (section, fields) = formatUnknownFields (sectionType ++ " section " ++ show section) fields@@ -342,20 +368,53 @@ where fromGithub = (++ "/issues") . sourceRepositoryUrl <$> sourceRepository -toLibrary :: [FilePath] -> [Dependency] -> [String] -> [GhcOption] -> [CppOption] -> LibrarySection -> IO Library-toLibrary globalSourceDirs globalDependencies globalDefaultExtensions globalGhcOptions globalCppOptions LibrarySection{..} = do- modules <- concat <$> mapM getModules sourceDirs+toLibrary :: CommonOptions -> WithCommonOptions LibrarySection -> IO (Section Library)+toLibrary globalOptions library = traverse fromLibrarySection section+ where+ section :: Section LibrarySection+ section = toSection globalOptions library - let (exposedModules, otherModules) = determineModules modules librarySectionExposedModules librarySectionOtherModules+ sourceDirs :: [FilePath]+ sourceDirs = sectionSourceDirs section - return (Library sourceDirs exposedModules otherModules dependencies defaultExtensions ghcOptions cppOptions)+ fromLibrarySection :: LibrarySection -> IO Library+ fromLibrarySection LibrarySection{..} = do+ modules <- concat <$> mapM getModules sourceDirs+ let (exposedModules, otherModules) = determineModules modules librarySectionExposedModules librarySectionOtherModules+ return (Library exposedModules otherModules)++toExecutables :: CommonOptions -> [(String, WithCommonOptions ExecutableSection)] -> IO [Section Executable]+toExecutables globalOptions executables = mapM toExecutable sections where- sourceDirs = globalSourceDirs ++ fromMaybeList librarySectionSourceDirs- dependencies = filter (not . null) [globalDependencies, fromMaybeList librarySectionDependencies]- defaultExtensions = globalDefaultExtensions ++ fromMaybeList librarySectionDefaultExtensions- ghcOptions = globalGhcOptions ++ fromMaybeList librarySectionGhcOptions- cppOptions = globalCppOptions ++ fromMaybeList librarySectionCppOptions+ sections :: [(String, Section ExecutableSection)]+ sections = map (fmap $ toSection globalOptions) executables + toExecutable :: (String, Section ExecutableSection) -> IO (Section Executable)+ toExecutable (name, section) = traverse fromExecutableSection section+ where+ sourceDirs :: [FilePath]+ sourceDirs = sectionSourceDirs section++ fromExecutableSection :: ExecutableSection -> IO Executable+ fromExecutableSection ExecutableSection{..} = do+ modules <- maybe (filterMain . concat <$> mapM getModules sourceDirs) (return . fromList) executableSectionOtherModules+ return (Executable name executableSectionMain modules)+ where+ filterMain :: [String] -> [String]+ filterMain = maybe id (filter . (/=)) (toModule $ splitDirectories executableSectionMain)++toSection :: CommonOptions -> WithCommonOptions a -> Section a+toSection globalOptions (WithCommonOptions a options)+ = Section a sourceDirs dependencies defaultExtensions ghcOptions cppOptions+ where+ sourceDirs = merge commonOptionsSourceDirs+ defaultExtensions = merge commonOptionsDefaultExtensions+ ghcOptions = merge commonOptionsGhcOptions+ cppOptions = merge commonOptionsCppOptions+ merge selector = fromMaybeList (selector globalOptions) ++ fromMaybeList (selector options)+ getDependencies = fromMaybeList . commonOptionsDependencies+ dependencies = filter (not . null) [getDependencies globalOptions, getDependencies options]+ determineModules :: [String] -> Maybe (List String) -> Maybe (List String) -> ([String], [String]) determineModules modules mExposedModules mOtherModules = case (mExposedModules, mOtherModules) of (Nothing, Nothing) -> (modules, [])@@ -373,23 +432,6 @@ where toModules :: [[FilePath]] -> [String] toModules = catMaybes . map toModule--toExecutables :: [FilePath] -> [Dependency] -> [String] -> [GhcOption] -> [CppOption] -> Maybe (HashMap String ExecutableSection) -> IO [Executable]-toExecutables globalSourceDirs globalDependencies globalDefaultExtensions globalGhcOptions globalCppOptions executables =- (mapM toExecutable . Map.toList) (fromMaybe mempty executables)- where- toExecutable (name, ExecutableSection{..}) = do- modules <- maybe (filterMain . concat <$> mapM getModules sourceDirs) (return . fromList) executableSectionOtherModules- return $ Executable name executableSectionMain sourceDirs modules dependencies defaultExtensions ghcOptions cppOptions- where- dependencies = filter (not . null) [globalDependencies, fromMaybeList executableSectionDependencies]- sourceDirs = globalSourceDirs ++ fromMaybeList executableSectionSourceDirs- defaultExtensions = globalDefaultExtensions ++ fromMaybeList executableSectionDefaultExtensions- ghcOptions = globalGhcOptions ++ fromMaybeList executableSectionGhcOptions- cppOptions = globalCppOptions ++ fromMaybeList executableSectionCppOptions-- filterMain :: [String] -> [String]- filterMain = maybe id (filter . (/=)) (toModule $ splitDirectories executableSectionMain) fromMaybeList :: Maybe (List a) -> [a] fromMaybeList = maybe [] fromList
src/Hpack/Run.hs view
@@ -1,15 +1,21 @@-{-# LANGUAGE QuasiQuotes, RecordWildCards #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-} module Hpack.Run ( run -- exported for testing , renderPackage , renderSourceRepository+, formatDescription ) where -import Control.Applicative+import Prelude ()+import Prelude.Compat+ import Control.Monad+import Data.Char import Data.Maybe-import Data.List+import Data.List.Compat import System.Exit.Compat import Hpack.Util@@ -77,7 +83,7 @@ ("name", Just packageName) , ("version", Just packageVersion) , ("synopsis", packageSynopsis)- , ("description", (formatDescription <$> packageDescription))+ , ("description", (formatDescription alignment <$> packageDescription)) , ("category", packageCategory) , ("stability", packageStability) , ("homepage", packageHomepage)@@ -99,11 +105,20 @@ defaultFieldOrder :: [String] defaultFieldOrder = map fst fields - formatDescription = intercalate separator . intersperse "." . lines- where- n = max alignment $ length ("description: ")- separator = "\n" ++ replicate n ' '+formatDescription :: Int -> String -> String+formatDescription alignment description = case map emptyLineToDot $ lines description of+ x : xs -> intercalate "\n" (x : map (indentation ++) xs)+ [] -> ""+ where+ n = max alignment (length "description: ")+ indentation = replicate n ' ' + emptyLineToDot xs+ | isEmptyLine xs = "."+ | otherwise = xs++ isEmptyLine = all isSpace+ renderSourceRepository :: SourceRepository -> String renderSourceRepository SourceRepository{..} = concat [ "source-repository head\n"@@ -112,47 +127,44 @@ , maybe "" ((" subdir: " ++) . (++ "\n")) sourceRepositorySubdir ] -renderExecutables :: [Executable] -> [String]+renderExecutables :: [Section Executable] -> [String] renderExecutables = map renderExecutable -renderExecutable :: Executable -> String-renderExecutable executable@Executable{..} =+renderExecutable :: Section Executable -> String+renderExecutable section@(sectionData -> Executable{..}) = "executable " ++ executableName ++ "\n"- ++ renderExecutableSection executable+ ++ renderExecutableSection section -renderTests :: [Executable] -> [String]+renderTests :: [Section Executable] -> [String] renderTests = map renderTest -renderTest :: Executable -> String-renderTest executable@Executable{..} =+renderTest :: Section Executable -> String+renderTest section@(sectionData -> Executable{..}) = "test-suite " ++ executableName ++ "\n" ++ " type: exitcode-stdio-1.0\n"- ++ renderExecutableSection executable+ ++ renderExecutableSection section -renderExecutableSection :: Executable -> String-renderExecutableSection Executable{..} =- renderSourceDirs executableSourceDirs- ++ " main-is: " ++ executableMain ++ "\n"- ++ renderOtherModules executableOtherModules- ++ renderDependencies executableDependencies- ++ renderDefaultExtensions executableDefaultExtensions- ++ renderGhcOptions executableGhcOptions- ++ renderCppOptions executableCppOptions- ++ " default-language: Haskell2010\n"+renderExecutableSection :: Section Executable -> String+renderExecutableSection section@(sectionData -> Executable{..}) =+ " main-is: " ++ executableMain ++ "\n"+ ++ renderSection section -renderLibrary :: Library -> String-renderLibrary Library{..} =+renderLibrary :: Section Library -> String+renderLibrary section@(sectionData -> Library{..}) = "library\n"- ++ renderSourceDirs librarySourceDirs ++ renderExposedModules libraryExposedModules ++ renderOtherModules libraryOtherModules- ++ renderDependencies libraryDependencies- ++ renderDefaultExtensions libraryDefaultExtensions- ++ renderGhcOptions libraryGhcOptions- ++ renderCppOptions libraryCppOptions- ++ " default-language: Haskell2010\n"+ ++ renderSection section +renderSection :: Section a -> String+renderSection Section{..} =+ renderSourceDirs sectionSourceDirs+ ++ renderDependencies sectionDependencies+ ++ renderDefaultExtensions sectionDefaultExtensions+ ++ renderGhcOptions sectionGhcOptions+ ++ renderCppOptions sectionCppOptions+ ++ " default-language: Haskell2010\n" renderSourceDirs :: [String] -> String renderSourceDirs dirs
src/Hpack/Util.hs view
@@ -6,6 +6,7 @@ , tryReadFile , sniffAlignment , extractFieldOrderHint+, expandGlobs -- exported for testing , splitField@@ -20,6 +21,7 @@ import Data.List import System.Directory import System.FilePath+import System.FilePath.Glob import Data.Data import Data.Aeson.Types@@ -88,3 +90,23 @@ where isNameChar = (`elem` nameChars) nameChars = ['a'..'z'] ++ ['A'..'Z'] ++ "-"++expandGlobs :: [String] -> IO ([String], [FilePath])+expandGlobs patterns = do+ files <- (fst <$> globDir compiledPatterns ".") >>= mapM removeDirectories+ let warnings = [warn pattern | ([], pattern) <- zip files patterns]+ return (warnings, combineResults files)+ where+ combineResults = nub . map (makeRelative ".") . sort . concat+ warn pattern = "Specified pattern " ++ show pattern ++ " for extra-source-files does not match any files"+ compiledPatterns = map (compileWith options) patterns+ removeDirectories = filterM doesFileExist+ options = CompOptions {+ characterClasses = False+ , characterRanges = False+ , numberRanges = False+ , wildcards = True+ , recursiveWildcards = True+ , pathSepInRanges = False+ , errorRecovery = True+ }
test/Hpack/ConfigSpec.hs view
@@ -2,12 +2,12 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} module Hpack.ConfigSpec (- main-, spec+ spec , package , executable , library+, section ) where import Helper@@ -18,18 +18,18 @@ import Hpack.Config -main :: IO ()-main = hspec spec- package :: Package package = Package "foo" "0.0.0" Nothing Nothing Nothing Nothing Nothing Nothing [] [] [] Nothing Nothing [] Nothing Nothing [] [] executable :: String -> String -> Executable-executable name main_ = Executable name main_ [] [] [] [] [] []+executable name main_ = Executable name main_ [] library :: Library-library = Library [] [] [] [] [] [] []+library = Library [] [] +section :: a -> Section a+section a = Section a [] [] [] [] []+ spec :: Spec spec = do describe "parseJSON" $ do@@ -219,6 +219,8 @@ - CHANGES.markdown - README.markdown |]+ touch "CHANGES.markdown"+ touch "README.markdown" Right (_, c) <- readPackageConfig "package.yaml" packageExtraSourceFiles c `shouldBe` ["CHANGES.markdown", "README.markdown"] @@ -257,9 +259,9 @@ |] Right (_, c) <- readPackageConfig "package.yaml" c `shouldBe` package {- packageLibrary = Just library {libraryCppOptions = ["-DFOO", "-DLIB"]}- , packageExecutables = [(executable "foo" "Main.hs") {executableCppOptions = ["-DFOO", "-DFOO"]}]- , packageTests = [(executable "spec" "Spec.hs") {executableCppOptions = ["-DFOO", "-DTEST"]}]+ packageLibrary = Just (section library) {sectionCppOptions = ["-DFOO", "-DLIB"]}+ , packageExecutables = [(section $ executable "foo" "Main.hs") {sectionCppOptions = ["-DFOO", "-DFOO"]}]+ , packageTests = [(section $ executable "spec" "Spec.hs") {sectionCppOptions = ["-DFOO", "-DTEST"]}] } context "when reading library section" $ do@@ -283,7 +285,7 @@ - bar |] Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {librarySourceDirs = ["foo", "bar"]}+ packageLibrary c `shouldBe` Just (section library) {sectionSourceDirs = ["foo", "bar"]} it "accepts default-extensions" $ do writeFile "package.yaml" [i|@@ -293,7 +295,7 @@ - Bar |] Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {libraryDefaultExtensions = ["Foo", "Bar"]}+ packageLibrary c `shouldBe` Just (section library) {sectionDefaultExtensions = ["Foo", "Bar"]} it "accepts global default-extensions" $ do writeFile "package.yaml" [i|@@ -303,7 +305,7 @@ library: {} |] Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {libraryDefaultExtensions = ["Foo", "Bar"]}+ packageLibrary c `shouldBe` Just (section library) {sectionDefaultExtensions = ["Foo", "Bar"]} it "accepts global source-dirs" $ do writeFile "package.yaml" [i|@@ -313,7 +315,7 @@ library: {} |] Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {librarySourceDirs = ["foo", "bar"]}+ packageLibrary c `shouldBe` Just (section library) {sectionSourceDirs = ["foo", "bar"]} it "allows to specify exposed-modules" $ do writeFile "package.yaml" [i|@@ -324,7 +326,7 @@ touch "src/Foo.hs" touch "src/Bar.hs" Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}+ packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]} it "allows to specify other-modules" $ do writeFile "package.yaml" [i|@@ -335,7 +337,7 @@ touch "src/Foo.hs" touch "src/Bar.hs" Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}+ packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]} it "allows to specify both exposed-modules and other-modules" $ do writeFile "package.yaml" [i|@@ -346,7 +348,7 @@ |] touch "src/Baz.hs" Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}+ packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Foo"], libraryOtherModules = ["Bar"]}) {sectionSourceDirs = ["src"]} context "when neither exposed-module nor other-module are specified" $ do it "exposes all modules" $ do@@ -357,7 +359,7 @@ touch "src/Foo.hs" touch "src/Bar.hs" Right (_, c) <- readPackageConfig "package.yaml"- packageLibrary c `shouldBe` Just library {librarySourceDirs = ["src"], libraryExposedModules = ["Bar", "Foo"]}+ packageLibrary c `shouldBe` Just (section library{libraryExposedModules = ["Bar", "Foo"]}) {sectionSourceDirs = ["src"]} context "when reading executable section" $ do it "warns on unknown fields" $ do@@ -381,7 +383,7 @@ main: driver/Main.hs |] Right (_, c) <- readPackageConfig "package.yaml"- packageExecutables c `shouldBe` [executable "foo" "driver/Main.hs"]+ packageExecutables c `shouldBe` [section $ executable "foo" "driver/Main.hs"] it "accepts source-dirs" $ do writeFile "package.yaml" [i|@@ -393,7 +395,7 @@ - bar |] Right (_, c) <- readPackageConfig "package.yaml"- packageExecutables c `shouldBe` [(executable "foo" "Main.hs") {executableSourceDirs = ["foo", "bar"]}]+ packageExecutables c `shouldBe` [(section $ executable "foo" "Main.hs") {sectionSourceDirs = ["foo", "bar"]}] it "accepts global source-dirs" $ do writeFile "package.yaml" [i|@@ -405,7 +407,7 @@ main: Main.hs |] Right (_, c) <- readPackageConfig "package.yaml"- packageExecutables c `shouldBe` [(executable "foo" "Main.hs") {executableSourceDirs = ["foo", "bar"]}]+ packageExecutables c `shouldBe` [(section $ executable "foo" "Main.hs") {sectionSourceDirs = ["foo", "bar"]}] it "infers other-modules" $ do touch "src/Main.hs"@@ -419,7 +421,7 @@ source-dirs: src |] Right (_, [r]) <- (fmap . fmap) packageExecutables <$> readPackageConfig "package.yaml"- executableOtherModules r `shouldBe` ["Bar", "Baz", "Foo"]+ executableOtherModules (sectionData r) `shouldBe` ["Bar", "Baz", "Foo"] it "allows to specify other-modules" $ do touch "src/Foo.hs"@@ -432,7 +434,7 @@ other-modules: Baz |] Right (_, [r]) <- (fmap . fmap) packageExecutables <$> readPackageConfig "package.yaml"- executableOtherModules r `shouldBe` ["Baz"]+ executableOtherModules (sectionData r) `shouldBe` ["Baz"] it "accepts default-extensions" $ do writeFile "package.yaml" [i|@@ -444,7 +446,7 @@ - Bar |] Right (_, c) <- readPackageConfig "package.yaml"- packageExecutables c `shouldBe` [(executable "foo" "driver/Main.hs") {executableDefaultExtensions = ["Foo", "Bar"]}]+ packageExecutables c `shouldBe` [(section $ executable "foo" "driver/Main.hs") {sectionDefaultExtensions = ["Foo", "Bar"]}] it "accepts global default-extensions" $ do writeFile "package.yaml" [i|@@ -456,7 +458,7 @@ main: driver/Main.hs |] Right (_, c) <- readPackageConfig "package.yaml"- packageExecutables c `shouldBe` [(executable "foo" "driver/Main.hs") {executableDefaultExtensions = ["Foo", "Bar"]}]+ packageExecutables c `shouldBe` [(section $ executable "foo" "driver/Main.hs") {sectionDefaultExtensions = ["Foo", "Bar"]}] it "accepts GHC options" $ do writeFile "package.yaml" [i|@@ -466,7 +468,7 @@ ghc-options: -Wall |] Right (_, c) <- readPackageConfig "package.yaml"- c `shouldBe` package {packageExecutables = [(executable "foo" "driver/Main.hs") {executableGhcOptions = ["-Wall"]}]}+ c `shouldBe` package {packageExecutables = [(section $ executable "foo" "driver/Main.hs") {sectionGhcOptions = ["-Wall"]}]} it "accepts global GHC options" $ do writeFile "package.yaml" [i|@@ -476,7 +478,7 @@ main: driver/Main.hs |] Right (_, c) <- readPackageConfig "package.yaml"- c `shouldBe` package {packageExecutables = [(executable "foo" "driver/Main.hs") {executableGhcOptions = ["-Wall"]}]}+ c `shouldBe` package {packageExecutables = [(section $ executable "foo" "driver/Main.hs") {sectionGhcOptions = ["-Wall"]}]} context "when reading test section" $ do it "warns on unknown fields" $ do@@ -500,7 +502,7 @@ main: test/Spec.hs |] Right (_, c) <- readPackageConfig "package.yaml"- c `shouldBe` package {packageTests = [executable "spec" "test/Spec.hs"]}+ c `shouldBe` package {packageTests = [section $ executable "spec" "test/Spec.hs"]} it "accepts single dependency" $ do writeFile "package.yaml" [i|@@ -510,7 +512,7 @@ dependencies: hspec |] Right (_, c) <- readPackageConfig "package.yaml"- c `shouldBe` package {packageTests = [(executable "spec" "test/Spec.hs") {executableDependencies = [["hspec"]]}]}+ c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = [["hspec"]]}]} it "accepts list of dependencies" $ do writeFile "package.yaml" [i|@@ -522,7 +524,7 @@ - QuickCheck |] Right (_, c) <- readPackageConfig "package.yaml"- c `shouldBe` package {packageTests = [(executable "spec" "test/Spec.hs") {executableDependencies = [["hspec", "QuickCheck"]]}]}+ c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = [["hspec", "QuickCheck"]]}]} context "when both global and section specific dependencies are specified" $ do it "combines dependencies" $ do@@ -536,7 +538,7 @@ dependencies: hspec |] Right (_, c) <- readPackageConfig "package.yaml"- c `shouldBe` package {packageTests = [(executable "spec" "test/Spec.hs") {executableDependencies = [["base"], ["hspec"]]}]}+ c `shouldBe` package {packageTests = [(section $ executable "spec" "test/Spec.hs") {sectionDependencies = [["base"], ["hspec"]]}]} context "when a specified source directory does not exist" $ do it "warns" $ do
test/Hpack/RunSpec.hs view
@@ -1,15 +1,13 @@ {-# LANGUAGE OverloadedStrings #-}-module Hpack.RunSpec (main, spec) where+module Hpack.RunSpec (spec) where import Test.Hspec+import Data.List -import Hpack.ConfigSpec hiding (main, spec)+import Hpack.ConfigSpec hiding (spec) import Hpack.Config import Hpack.Run -main :: IO ()-main = hspec spec- spec :: Spec spec = do describe "renderPackage" $ do@@ -30,7 +28,7 @@ ] it "includes description" $ do- renderPackage 0 [] package {packageDescription = Just "foo\nbar\n"} `shouldBe` unlines [+ renderPackage 0 [] package {packageDescription = Just "foo\n\nbar\n"} `shouldBe` unlines [ "name: foo" , "version: 0.0.0" , "description: foo"@@ -41,7 +39,7 @@ ] it "aligns description" $ do- renderPackage 16 [] package {packageDescription = Just "foo\nbar\n"} `shouldBe` unlines [+ renderPackage 16 [] package {packageDescription = Just "foo\n\nbar\n"} `shouldBe` unlines [ "name: foo" , "version: 0.0.0" , "description: foo"@@ -110,7 +108,7 @@ context "when rendering executable section" $ do it "includes dependencies" $ do- renderPackage 0 [] package {packageExecutables = [(executable "foo" "Main.hs") {executableDependencies = [["foo", "bar"], ["foo", "baz"]]}]} `shouldBe` unlines [+ renderPackage 0 [] package {packageExecutables = [(section $ executable "foo" "Main.hs") {sectionDependencies = [["foo", "bar"], ["foo", "baz"]]}]} `shouldBe` unlines [ "name: foo" , "version: 0.0.0" , "build-type: Simple"@@ -128,7 +126,7 @@ ] it "includes GHC options" $ do- renderPackage 0 [] package {packageExecutables = [(executable "foo" "Main.hs") {executableGhcOptions = ["-Wall", "-Werror"]}]} `shouldBe` unlines [+ renderPackage 0 [] package {packageExecutables = [(section $ executable "foo" "Main.hs") {sectionGhcOptions = ["-Wall", "-Werror"]}]} `shouldBe` unlines [ "name: foo" , "version: 0.0.0" , "build-type: Simple"@@ -139,6 +137,41 @@ , " ghc-options: -Wall -Werror" , " default-language: Haskell2010" ]++ describe "formatDescription" $ do+ it "formats description" $ do+ let description = unlines [+ "foo"+ , "bar"+ ]+ "description: " ++ formatDescription 0 description `shouldBe` intercalate "\n" [+ "description: foo"+ , " bar"+ ]++ it "takes specified alignment into account" $ do+ let description = unlines [+ "foo"+ , "bar"+ , "baz"+ ]+ "description: " ++ formatDescription 15 description `shouldBe` intercalate "\n" [+ "description: foo"+ , " bar"+ , " baz"+ ]++ it "formats empty lines" $ do+ let description = unlines [+ "foo"+ , " "+ , "bar"+ ]+ "description: " ++ formatDescription 0 description `shouldBe` intercalate "\n" [+ "description: foo"+ , " ."+ , " bar"+ ] describe "renderSourceRepository" $ do it "renders source-repository without subdir correctly" $ do
test/Hpack/UtilSpec.hs view
@@ -3,6 +3,8 @@ import Helper import Data.Aeson+import Data.List+import System.Directory import Hpack.Util @@ -100,3 +102,65 @@ it "rejects invalid fields" $ do splitField "foo bar" `shouldBe` Nothing++ describe "expandGlobs" $ around_ inTempDirectory $ do+ it "accepts simple files" $ do+ touch "foo.js"+ expandGlobs ["foo.js"] `shouldReturn` ([], ["foo.js"])++ it "removes duplicates" $ do+ touch "foo.js"+ expandGlobs ["foo.js", "*.js"] `shouldReturn` ([], ["foo.js"])++ it "rejects directories" $ do+ touch "foo"+ createDirectory "bar"+ expandGlobs ["*"] `shouldReturn` ([], ["foo"])++ it "rejects character ranges" $ do+ touch "foo1"+ touch "foo2"+ touch "foo[1,2]"+ expandGlobs ["foo[1,2]"] `shouldReturn` ([], ["foo[1,2]"])++ context "when expanding *" $ do+ it "expands by extension" $ do+ let files = [+ "files/foo.js"+ , "files/bar.js"+ , "files/baz.js"]+ mapM_ touch files+ touch "files/foo.hs"+ expandGlobs ["files/*.js"] `shouldReturn` ([], sort files)++ it "rejects dot-files" $ do+ touch "foo/bar"+ touch "foo/.baz"+ expandGlobs ["foo/*"] `shouldReturn` ([], ["foo/bar"])++ it "accepts dot-files when explicitly asked to" $ do+ touch "foo/bar"+ touch "foo/.baz"+ expandGlobs ["foo/.*"] `shouldReturn` ([], ["foo/.baz"])++ it "matches at most one directory component" $ do+ touch "foo/bar/baz.js"+ touch "foo/bar.js"+ expandGlobs ["*/*.js"] `shouldReturn` ([], ["foo/bar.js"])++ context "when expanding **" $ do+ it "matches arbitrary many directory components" $ do+ let file = "foo/bar/baz.js"+ touch file+ expandGlobs ["**/*.js"] `shouldReturn` ([], [file])++ context "when a pattern does not match anything" $ do+ it "warns" $ do+ expandGlobs ["foo"] `shouldReturn`+ (["Specified pattern \"foo\" for extra-source-files does not match any files"], [])++ context "when a pattern only matches a directory" $ do+ it "warns" $ do+ createDirectory "foo"+ expandGlobs ["foo"] `shouldReturn`+ (["Specified pattern \"foo\" for extra-source-files does not match any files"], [])