diff --git a/src/Hpack.hs b/src/Hpack.hs index 4482606f..bc9eb08c 100644 --- a/src/Hpack.hs +++ b/src/Hpack.hs @@ -6,6 +6,8 @@ module Hpack ( #ifdef TEST , hpackWithVersion , parseVerbosity +, extractVersion +, parseVersion #endif ) where @@ -16,12 +18,14 @@ import Control.DeepSeq import Control.Exception import Control.Monad.Compat import Data.List.Compat +import Data.Maybe import Data.Version (Version) import qualified Data.Version as Version import System.Environment import System.Exit import System.IO import System.IO.Error +import Text.ParserCombinators.ReadP import Paths_hpack (version) import Hpack.Config @@ -65,6 +69,20 @@ parseVerbosity xs = (verbose, ys) verbose = not (silentFlag `elem` xs) ys = filter (/= silentFlag) xs +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 :: FilePath -> Bool -> IO () hpack = hpackWithVersion version @@ -73,16 +91,20 @@ hpackWithVersion v dir verbose = do (warnings, name, new) <- run dir forM_ warnings $ \warning -> hPutStrLn stderr ("WARNING: " ++ warning) - old <- either (const Nothing) (Just . stripHeader) <$> tryJust (guard . isDoesNotExistError) (readFile name >>= (return $!!)) + old <- either (const Nothing) (Just . splitHeader) <$> tryJust (guard . isDoesNotExistError) (readFile name >>= (return $!!)) + let oldVersion = fmap fst old >>= extractVersion - if (old == Just (lines new)) then do - output (name ++ " is up-to-date") + if (oldVersion <= Just v) then do + if (fmap snd old == Just (lines new)) then do + output (name ++ " is up-to-date") + else do + (writeFile name $ header v ++ new) + output ("generated " ++ name) else do - (writeFile name $ header v ++ new) - output ("generated " ++ name) + output (name ++ " was generated with a newer version of hpack, please upgrade and try again.") where - stripHeader :: String -> [String] - stripHeader = dropWhile null . dropWhile ("--" `isPrefixOf`) . lines + splitHeader :: String -> ([String], [String]) + splitHeader = fmap (dropWhile null) . span ("--" `isPrefixOf`) . lines output :: String -> IO () output message diff --git a/test/HpackSpec.hs b/test/HpackSpec.hs index 180f2f1f..a0679172 100644 --- a/test/HpackSpec.hs +++ b/test/HpackSpec.hs @@ -3,11 +3,13 @@ module HpackSpec (spec) where import Prelude () import Prelude.Compat +import Control.Monad.Compat import Control.DeepSeq -import Data.Version (Version(..)) +import Data.Version (Version(..), showVersion) import Test.Hspec import Test.Mockery.Directory +import Test.QuickCheck import Hpack @@ -24,6 +26,22 @@ spec = do it "returns False" $ do parseVerbosity ["--silent"] `shouldBe` (False, []) + 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] []) + + 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 = Version xs [] + parseVersion (showVersion v) `shouldBe` Just v + 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 @@ -33,3 +51,21 @@ spec = do old <- readFile "foo.cabal" >>= (return $!!) hpackWithVersion (makeVersion [0,10,0]) "." False readFile "foo.cabal" `shouldReturn` old + + 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]) "." False + old <- readFile "foo.cabal" >>= (return $!!) + + writeFile "package.yaml" $ unlines [ + "name: foo" + , "version: 0.2.0" + ] + + hpackWithVersion (makeVersion [0,8,0]) "." False + readFile "foo.cabal" `shouldReturn` old