hpack 0.19.3 → 0.20.0
raw patch · 9 files changed
+275/−110 lines, 9 filesdep +cryptonitePVP ok
version bump matches the API change (PVP)
Dependencies added: cryptonite
API changes (from Hackage documentation)
+ Hpack: ExistingCabalFileWasModifiedManually :: Status
+ Hpack: Force :: Force
+ Hpack: NoForce :: Force
+ Hpack: NoVerbose :: Verbose
+ Hpack: Verbose :: Verbose
+ Hpack: data Force
+ Hpack: data Verbose
+ Hpack: instance GHC.Classes.Eq Hpack.Result
+ Hpack: instance GHC.Classes.Eq Hpack.Status
+ Hpack: instance GHC.Show.Show Hpack.Result
+ Hpack: instance GHC.Show.Show Hpack.Status
- Hpack: hpack :: Maybe FilePath -> Bool -> IO ()
+ Hpack: hpack :: Maybe FilePath -> Verbose -> Force -> IO ()
- Hpack: hpackResult :: Maybe FilePath -> IO Result
+ Hpack: hpackResult :: Maybe FilePath -> Force -> IO Result
Files
- CHANGELOG.md +3/−0
- hpack.cabal +8/−2
- src/Hpack.hs +51/−52
- src/Hpack/CabalFile.hs +54/−0
- src/Hpack/Options.hs +15/−4
- src/Hpack/Util.hs +14/−2
- test/Hpack/CabalFileSpec.hs +38/−0
- test/Hpack/OptionsSpec.hs +13/−5
- test/HpackSpec.hs +79/−45
CHANGELOG.md view
@@ -1,3 +1,6 @@+## Changes in 0.20.0+ - Do not overwrite any existing cabal file if it has been modified manually+ ## Changes in 0.19.3 - Add support for `frameworks` and `extra-frameworks-dirs`
hpack.cabal view
@@ -1,9 +1,9 @@--- This file has been generated from package.yaml by hpack version 0.19.1.+-- This file has been generated from package.yaml by hpack version 0.19.3. -- -- see: https://github.com/sol/hpack name: hpack-version: 0.19.3+version: 0.20.0 synopsis: An alternative format for Haskell packages description: See README at <https://github.com/sol/hpack#readme> category: Development@@ -34,6 +34,7 @@ , base-compat >=0.8 , bytestring , containers+ , cryptonite , deepseq , directory , filepath@@ -47,6 +48,7 @@ Hpack.Run Hpack.Yaml other-modules:+ Hpack.CabalFile Hpack.Dependency Hpack.FormattingHints Hpack.GenericsUtil@@ -70,6 +72,7 @@ , base-compat >=0.8 , bytestring , containers+ , cryptonite , deepseq , directory , filepath@@ -100,6 +103,7 @@ , base-compat >=0.8 , bytestring , containers+ , cryptonite , deepseq , directory , filepath@@ -113,6 +117,7 @@ , yaml other-modules: Helper+ Hpack.CabalFileSpec Hpack.ConfigSpec Hpack.DependencySpec Hpack.FormattingHintsSpec@@ -124,6 +129,7 @@ Hpack.UtilSpec HpackSpec Hpack+ Hpack.CabalFile Hpack.Config Hpack.Dependency Hpack.FormattingHints
src/Hpack.hs view
@@ -4,12 +4,13 @@ , hpackResult , Result(..) , Status(..)+, Verbose(..)+, Force(..) , version , main #ifdef TEST-, hpackWithVersion-, extractVersion-, parseVersion+, header+, hpackWithVersionResult , splitDirectory #endif ) where@@ -19,10 +20,6 @@ import Control.Monad.Compat import qualified Data.ByteString as B-import Data.List.Compat-import Data.Maybe-import qualified Data.Text as T-import Data.Text.Encoding (encodeUtf8) import Data.Version (Version) import qualified Data.Version as Version import System.Environment@@ -30,22 +27,24 @@ import System.IO import System.FilePath import System.Directory-import Text.ParserCombinators.ReadP import Paths_hpack (version) import Hpack.Options import Hpack.Config import Hpack.Run import Hpack.Util+import Hpack.CabalFile programVersion :: Version -> String programVersion v = "hpack version " ++ Version.showVersion v -header :: FilePath -> Version -> String-header p v = unlines [+header :: FilePath -> Version -> Hash -> String+header p v hash = unlines [ "-- This file has been generated from " ++ p ++ " by " ++ programVersion v ++ "." , "--" , "-- see: https://github.com/sol/hpack"+ , "--"+ , "-- hash: " ++ hash , "" ] @@ -56,8 +55,8 @@ PrintVersion -> putStrLn (programVersion version) Help -> printHelp Run options -> case options of- Options _verbose True dir -> hpackStdOut dir- Options verbose False dir -> hpack dir verbose+ Options _verbose _force True dir -> hpackStdOut dir+ Options verbose force False dir -> hpack dir verbose force ParseError -> do printHelp exitFailure@@ -65,47 +64,40 @@ printHelp :: IO () printHelp = do hPutStrLn stderr $ unlines [- "Usage: hpack [ --silent ] [ dir ] [ - ]"+ "Usage: hpack [ --silent ] [ --force | -f ] [ PATH ] [ - ]" , " hpack --version"+ , " hpack --help" ] -safeInit :: [a] -> [a]-safeInit [] = []-safeInit xs = init xs--extractVersion :: [String] -> Maybe Version-extractVersion = listToMaybe . mapMaybe (stripPrefix prefix >=> parseVersion . safeInit)- where- prefix = "-- This file has been generated from package.yaml by hpack version "--parseVersion :: String -> Maybe Version-parseVersion xs = case [v | (v, "") <- readP_to_S Version.parseVersion xs] of- [v] -> Just v- _ -> Nothing--hpack :: Maybe FilePath -> Bool -> IO ()+hpack :: Maybe FilePath -> Verbose -> Force -> IO () hpack = hpackWithVersion version -hpackResult :: Maybe FilePath -> IO Result+hpackResult :: Maybe FilePath -> Force -> IO Result hpackResult = hpackWithVersionResult version data Result = Result { resultWarnings :: [String] , resultCabalFile :: String , resultStatus :: Status-}+} deriving (Eq, Show) -data Status = Generated | AlreadyGeneratedByNewerHpack | OutputUnchanged+data Status =+ Generated+ | ExistingCabalFileWasModifiedManually+ | AlreadyGeneratedByNewerHpack+ | OutputUnchanged+ deriving (Eq, Show) -hpackWithVersion :: Version -> Maybe FilePath -> Bool -> IO ()-hpackWithVersion v p verbose = do- r <- hpackWithVersionResult v p+hpackWithVersion :: Version -> Maybe FilePath -> Verbose -> Force -> IO ()+hpackWithVersion v p verbose force = do+ r <- hpackWithVersionResult v p force printWarnings (resultWarnings r)- when verbose $ putStrLn $+ when (verbose == Verbose) $ putStrLn $ case resultStatus r of Generated -> "generated " ++ resultCabalFile r OutputUnchanged -> resultCabalFile r ++ " is up-to-date" AlreadyGeneratedByNewerHpack -> resultCabalFile r ++ " was generated with a newer version of hpack, please upgrade and try again."+ ExistingCabalFileWasModifiedManually -> resultCabalFile r ++ " was modified manually, please use --force to overwrite." printWarnings :: [String] -> IO () printWarnings warnings = do@@ -122,33 +114,40 @@ dir = takeDirectory p in (guard (p /= file) >> Just dir, if null file then packageConfig else file) -hpackWithVersionResult :: Version -> Maybe FilePath -> IO Result-hpackWithVersionResult v p = do+mkStatus :: [String] -> Version -> CabalFile -> Status+mkStatus new v (CabalFile mOldVersion mHash old) = case (mOldVersion, mHash) of+ (Nothing, _) -> ExistingCabalFileWasModifiedManually+ (Just oldVersion, _) | oldVersion < makeVersion [0, 20, 0] -> Generated+ (_, Nothing) -> ExistingCabalFileWasModifiedManually+ (Just oldVersion, Just hash)+ | v < oldVersion -> AlreadyGeneratedByNewerHpack+ | sha256 (unlines old) /= hash -> ExistingCabalFileWasModifiedManually+ | old == new -> OutputUnchanged+ | otherwise -> Generated++hpackWithVersionResult :: Version -> Maybe FilePath -> Force -> IO Result+hpackWithVersionResult v p force = do (dir, file) <- splitDirectory p (warnings, cabalFile, new) <- run dir file- old <- fmap splitHeader <$> tryReadFile cabalFile- let oldVersion = fmap fst old >>= extractVersion- status <-- if (oldVersion <= Just v) then- if (fmap snd old == Just (lines new)) then- return OutputUnchanged- else do- B.writeFile cabalFile $ encodeUtf8 $ T.pack $ header file v ++ new- return Generated- else- return AlreadyGeneratedByNewerHpack+ oldCabalFile <- readCabalFile cabalFile+ let+ status = case force of+ Force -> Generated+ NoForce -> maybe Generated (mkStatus (lines new) v) oldCabalFile+ case status of+ Generated -> do+ let hash = sha256 new+ B.writeFile cabalFile . encodeUtf8 $ header file v hash ++ new+ _ -> return () return Result { resultWarnings = warnings , resultCabalFile = cabalFile , resultStatus = status }- where- splitHeader :: String -> ([String], [String])- splitHeader = fmap (dropWhile null) . span ("--" `isPrefixOf`) . lines hpackStdOut :: Maybe FilePath -> IO () hpackStdOut p = do (dir, file) <- splitDirectory p (warnings, _cabalFile, new) <- run dir file- B.putStr $ encodeUtf8 $ T.pack new+ B.putStr (encodeUtf8 new) printWarnings warnings
+ src/Hpack/CabalFile.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ViewPatterns #-}+module Hpack.CabalFile where++import Prelude ()+import Prelude.Compat++import Control.Monad.Compat+import Data.List.Compat+import Data.Maybe+import Data.Version (Version(..))+import qualified Data.Version as Version+import Text.ParserCombinators.ReadP++import Hpack.Util++makeVersion :: [Int] -> Version+makeVersion v = Version v []++data CabalFile = CabalFile {+ cabalFileHpackVersion :: Maybe Version+, cabalFileHash :: Maybe Hash+, cabalFileContents :: [String]+} deriving (Eq, Show)++readCabalFile :: FilePath -> IO (Maybe CabalFile)+readCabalFile cabalFile = fmap parse <$> tryReadFile cabalFile+ where+ parse :: String -> CabalFile+ parse (splitHeader -> (h, c)) = CabalFile (extractVersion h) (extractHash h) c++ splitHeader :: String -> ([String], [String])+ splitHeader = fmap (dropWhile null) . span ("--" `isPrefixOf`) . lines++extractHash :: [String] -> Maybe Hash+extractHash = extract "-- hash: " Just++extractVersion :: [String] -> Maybe Version+extractVersion = extract prefix (parseVersion . safeInit)+ where+ prefix = "-- This file has been generated from package.yaml by hpack version "++extract :: String -> (String -> Maybe a) -> [String] -> Maybe a+extract prefix parse = listToMaybe . mapMaybe (stripPrefix prefix >=> parse)++safeInit :: [a] -> [a]+safeInit [] = []+safeInit xs = init xs++parseVersion :: String -> Maybe Version+parseVersion xs = case [v | (v, "") <- readP_to_S Version.parseVersion xs] of+ [v] -> Just v+ _ -> Nothing
src/Hpack/Options.hs view
@@ -6,8 +6,15 @@ data ParseResult = Help | PrintVersion | Run Options | ParseError deriving (Eq, Show) +data Verbose = Verbose | NoVerbose+ deriving (Eq, Show)++data Force = Force | NoForce+ deriving (Eq, Show)+ data Options = Options {- optionsVerbose :: Bool+ optionsVerbose :: Verbose+, optionsForce :: Force , optionsToStdout :: Bool , optionsTarget :: Maybe FilePath } deriving (Eq, Show)@@ -17,12 +24,16 @@ ["--version"] -> PrintVersion ["--help"] -> Help _ -> case targets of- Just (target, toStdout) -> Run (Options verbose toStdout target)+ Just (target, toStdout) -> Run (Options verbose force toStdout target) Nothing -> ParseError where silentFlag = "--silent"- verbose = not (silentFlag `elem` xs)- ys = filter (/= silentFlag) xs+ forceFlags = ["--force", "-f"]++ flags = [silentFlag] ++ forceFlags+ verbose = if silentFlag `elem` xs then NoVerbose else Verbose+ force = if any (`elem` xs) forceFlags then Force else NoForce+ ys = filter (`notElem` flags) xs targets = case ys of ["-"] -> Just (Nothing, True)
src/Hpack/Util.hs view
@@ -15,6 +15,9 @@ , expandGlobs , sort , lexicographically+, encodeUtf8+, Hash+, sha256 ) where import Prelude ()@@ -29,13 +32,14 @@ import Data.List.Compat hiding (sort) import Data.Ord import qualified Data.Text as T-import Data.Text.Encoding (decodeUtf8With)+import qualified Data.Text.Encoding as Encoding import Data.Text.Encoding.Error (lenientDecode) import System.IO.Error import System.Directory import System.FilePath import qualified System.FilePath.Posix as Posix import System.FilePath.Glob+import Crypto.Hash import Hpack.Haskell @@ -110,7 +114,7 @@ tryReadFile :: FilePath -> IO (Maybe String) tryReadFile file = do r <- tryJust (guard . isDoesNotExistError) (B.readFile file)- return $ either (const Nothing) (Just . T.unpack . decodeUtf8With lenientDecode) r+ return $ either (const Nothing) (Just . T.unpack . Encoding.decodeUtf8With lenientDecode) r toPosixFilePath :: FilePath -> FilePath toPosixFilePath = Posix.joinPath . splitDirectories@@ -149,3 +153,11 @@ , pathSepInRanges = False , errorRecovery = True }++encodeUtf8 :: String -> B.ByteString+encodeUtf8 = Encoding.encodeUtf8 . T.pack++type Hash = String++sha256 :: String -> Hash+sha256 c = show (hash (encodeUtf8 c) :: Digest SHA256)
+ test/Hpack/CabalFileSpec.hs view
@@ -0,0 +1,38 @@+module Hpack.CabalFileSpec (spec) where++import Helper+import Test.QuickCheck+import Data.Version (showVersion)+import Control.Monad.Compat++import Paths_hpack (version)++import Hpack (header)+import Hpack.CabalFile++spec :: Spec+spec = do+ describe "readCabalFile" $ do+ let+ file = "package.yaml"+ hash = "some-hash"+ it "includes hash" $ do+ inTempDirectory $ do+ writeFile file $ header file version hash+ readCabalFile file `shouldReturn` Just (CabalFile (Just version) (Just hash) [])++ describe "extractVersion" $ do+ it "extracts Hpack version from a cabal file" $ do+ let cabalFile = ["-- This file has been generated from package.yaml by hpack version 0.10.0."]+ extractVersion cabalFile `shouldBe` Just (makeVersion [0, 10, 0])++ it "is total" $ do+ let cabalFile = ["-- This file has been generated from package.yaml by hpack version "]+ extractVersion cabalFile `shouldBe` Nothing++ describe "parseVersion" $ do+ it "is inverse to showVersion" $ do+ let positive = getPositive <$> arbitrary+ forAll (replicateM 3 positive) $ \xs -> do+ let v = makeVersion xs+ parseVersion (showVersion v) `shouldBe` Just v
test/Hpack/OptionsSpec.hs view
@@ -20,22 +20,30 @@ context "by default" $ do it "returns Run" $ do- parseOptions [] `shouldBe` Run (Options True False Nothing)+ parseOptions [] `shouldBe` Run (Options Verbose NoForce False Nothing) it "includes target" $ do- parseOptions ["foo"] `shouldBe` Run (Options True False (Just "foo"))+ parseOptions ["foo"] `shouldBe` Run (Options Verbose NoForce False (Just "foo")) context "with superfluous arguments" $ do it "returns ParseError" $ do parseOptions ["foo", "bar"] `shouldBe` ParseError context "with --silent" $ do- it "sets optionsVerbose to False" $ do- parseOptions ["--silent"] `shouldBe` Run (Options False False Nothing)+ it "sets optionsVerbose to NoVerbose" $ do+ parseOptions ["--silent"] `shouldBe` Run (Options NoVerbose NoForce False Nothing) + context "with --force" $ do+ it "sets optionsForce to Force" $ do+ parseOptions ["--force"] `shouldBe` Run (Options Verbose Force False Nothing)++ context "with -f" $ do+ it "sets optionsForce to Force" $ do+ parseOptions ["-f"] `shouldBe` Run (Options Verbose Force False Nothing)+ context "with -" $ do it "sets optionsToStdout to True" $ do- parseOptions ["-"] `shouldBe` Run (Options True True Nothing)+ parseOptions ["-"] `shouldBe` Run (Options Verbose NoForce True Nothing) it "rejects - for target" $ do parseOptions ["-", "-"] `shouldBe` ParseError
test/HpackSpec.hs view
@@ -3,64 +3,98 @@ import Helper import Prelude ()-import Prelude.Compat+import Prelude.Compat hiding (readFile)+import qualified Prelude.Compat as Prelude -import Control.Monad.Compat import Control.DeepSeq-import Data.Version (Version(..), showVersion) -import Test.QuickCheck--import Hpack+import Hpack.Config (packageConfig)+import Hpack.CabalFile+import Hpack hiding (hpack) -makeVersion :: [Int] -> Version-makeVersion v = Version v []+readFile :: FilePath -> IO String+readFile name = Prelude.readFile name >>= (return $!!) spec :: Spec spec = do- describe "extractVersion" $ do- it "extracts Hpack version from a cabal file" $ do- let cabalFile = ["-- This file has been generated from package.yaml by hpack version 0.10.0."]- extractVersion cabalFile `shouldBe` Just (Version [0, 10, 0] [])+ describe "hpackWithVersionResult" $ do+ context "with existing cabal file" $ around_ inTempDirectory $ before_ (writeFile packageConfig "name: foo") $ do+ let+ file = "foo.cabal" - it "is total" $ do- let cabalFile = ["-- This file has been generated from package.yaml by hpack version "]- extractVersion cabalFile `shouldBe` Nothing+ hpackWithVersion v = hpackWithVersionResult v Nothing NoForce+ hpack = hpackWithVersionResult version Nothing NoForce+ hpackForce = hpackWithVersionResult version Nothing Force - describe "parseVersion" $ do- it "is inverse to showVersion" $ do- let positive = getPositive <$> arbitrary- forAll (replicateM 3 positive) $ \xs -> do- let v = Version xs []- parseVersion (showVersion v) `shouldBe` Just v+ generated = Result [] file Generated+ modifiedManually = Result [] file ExistingCabalFileWasModifiedManually+ outputUnchanged = Result [] file OutputUnchanged+ alreadyGeneratedByNewerHpack = Result [] file AlreadyGeneratedByNewerHpack - describe "hpackWithVersion" $ do- context "when only the hpack version in the cabal file header changed" $ do- it "does not write a new cabal file" $ do- inTempDirectory $ do- writeFile "package.yaml" "name: foo"- hpackWithVersion (makeVersion [0,8,0]) Nothing False- old <- readFile "foo.cabal" >>= (return $!!)- hpackWithVersion (makeVersion [0,10,0]) Nothing False- readFile "foo.cabal" `shouldReturn` old+ context "when cabal file was created manually" $ do+ it "does not overwrite existing cabal file" $ do+ let existing = "some existing cabal file"+ writeFile file existing+ hpack `shouldReturn` modifiedManually+ readFile file `shouldReturn` existing - context "when exsting cabal file was generated with a newer version of hpack" $ do- it "does not re-generate" $ do- inTempDirectory $ do- writeFile "package.yaml" $ unlines [- "name: foo"- , "version: 0.1.0"- ]- hpackWithVersion (makeVersion [0,10,0]) Nothing False- old <- readFile "foo.cabal" >>= (return $!!)+ context "with --force" $ do+ it "overwrites existing cabal file" $ do+ _ <- hpack+ expected <- readFile file+ writeFile file "some existing cabal file"+ hpackForce `shouldReturn` generated+ readFile file `shouldReturn` expected - writeFile "package.yaml" $ unlines [- "name: foo"- , "version: 0.2.0"- ]+ context "when cabal file was created with hpack < 0.20.0" $ do+ it "overwrites existing cabal file" $ do+ _ <- hpack+ expected <- readFile file+ writeFile file "-- This file has been generated from package.yaml by hpack version 0.19.3."+ hpack `shouldReturn` generated+ readFile file `shouldReturn` expected - hpackWithVersion (makeVersion [0,8,0]) Nothing False- readFile "foo.cabal" `shouldReturn` old+ context "when cabal file was created with hpack >= 0.20.0" $ do+ context "when hash is missing" $ do+ it "does not overwrite existing cabal file" $ do+ let existing = "-- This file has been generated from package.yaml by hpack version 0.20.0."+ writeFile file existing+ hpack `shouldReturn` modifiedManually+ readFile file `shouldReturn` existing++ context "when hash is present" $ do+ context "when exsting cabal file was generated with a newer version of hpack" $ do+ it "does not overwrite existing cabal file" $ do+ writeFile packageConfig $ unlines [+ "name: foo"+ , "version: 0.1.0"+ ]+ _ <- hpackWithVersion (makeVersion [0,22,0])+ old <- readFile file++ writeFile packageConfig $ unlines [+ "name: foo"+ , "version: 0.2.0"+ ]++ hpackWithVersion (makeVersion [0,20,0]) `shouldReturn` alreadyGeneratedByNewerHpack+ readFile file `shouldReturn` old++ context "when cabal file was modified manually" $ do+ it "does not overwrite existing cabal file" $ do+ _ <- hpack+ old <- readFile file+ let modified = old ++ "foo\n"+ writeFile file modified+ _ <- hpack+ readFile file `shouldReturn` modified++ context "when only the hpack version in the cabal file header changed" $ do+ it "does not overwrite existing cabal file" $ do+ _ <- hpackWithVersion (makeVersion [0,20,0])+ old <- readFile file+ hpack `shouldReturn` outputUnchanged+ readFile file `shouldReturn` old describe "splitDirectory" $ do context "when given Nothing" $ do