diff --git a/src/Hpack.hs b/src/Hpack.hs index 27c683ea..d8adda71 100644 --- a/src/Hpack.hs +++ b/src/Hpack.hs @@ -4,7 +4,10 @@ module Hpack ( , version , main #ifdef TEST +, hpackWithVersion , parseVerbosity +, extractVersion +, parseVersion #endif ) where @@ -15,22 +18,25 @@ import Control.DeepSeq import Control.Exception import Control.Monad.Compat import Data.List.Compat -import Data.Version (showVersion) +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 import Hpack.Run -programVersion :: String -programVersion = "hpack version " ++ showVersion version +programVersion :: Version -> String +programVersion v = "hpack version " ++ Version.showVersion v -header :: String -header = unlines [ - "-- This file has been generated from " ++ packageConfig ++ " by " ++ programVersion ++ "." +header :: Version -> String +header v = unlines [ + "-- This file has been generated from " ++ packageConfig ++ " by " ++ programVersion v ++ "." , "--" , "-- see: https://github.com/sol/hpack" , "" @@ -40,11 +46,11 @@ main :: IO () main = do args <- getArgs case args of - ["--version"] -> putStrLn programVersion + ["--version"] -> putStrLn (programVersion version) ["--help"] -> printHelp _ -> case parseVerbosity args of (verbose, [dir]) -> hpack dir verbose - (verbose, []) -> hpack "." verbose + (verbose, []) -> hpack "" verbose _ -> do printHelp exitFailure @@ -63,19 +69,44 @@ 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 dir verbose = do +hpack = hpackWithVersion version + +hpackWithVersion :: Version -> FilePath -> Bool -> IO () +hpackWithVersion v dir verbose = do (warnings, name, new) <- run dir forM_ warnings $ \warning -> hPutStrLn stderr ("WARNING: " ++ warning) - old <- force . either (const Nothing) (Just . stripHeader) <$> tryJust (guard . isDoesNotExistError) (readFile name) - if (old == Just (lines new)) then do - output (name ++ " is up-to-date") - else do - (writeFile name $ header ++ new) - output ("generated " ++ name) + + old <- either (const Nothing) (Just . splitHeader) <$> tryJust (guard . isDoesNotExistError) (readFile name >>= (return $!!)) + let oldVersion = fmap fst old >>= extractVersion + + 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 + 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 d3f34431..d24ae64d 100644 --- a/test/HpackSpec.hs +++ b/test/HpackSpec.hs @@ -1,9 +1,21 @@ module HpackSpec (spec) where +import Prelude () +import Prelude.Compat + +import Control.Monad.Compat +import Control.DeepSeq +import Data.Version (Version(..), showVersion) + import Test.Hspec +import Test.Mockery.Directory +import Test.QuickCheck import Hpack +makeVersion :: [Int] -> Version +makeVersion v = Version v [] + spec :: Spec spec = do describe "parseVerbosity" $ do @@ -13,3 +25,47 @@ spec = do context "with --silent" $ 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 + inTempDirectory $ do + writeFile "package.yaml" "name: foo" + hpackWithVersion (makeVersion [0,8,0]) "." False + old <- readFile "foo.cabal" >>= (return $!!) + hpackWithVersion (makeVersion [0,10,0]) "." False + readFile "foo.cabal" `shouldReturn` old + + context "when cabal file was generated with a newer version of hpack" $ do + it "does not write a new cabal file" $ 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