module Bump where import Control.Monad.Error (throwError, liftIO) import qualified Data.List as List import qualified Catalog import qualified CommandLine.Helpers as Cmd import qualified Diff.Compare as Compare import qualified Docs import qualified Elm.Docs as Docs import qualified Elm.Package.Description as Desc import qualified Elm.Package.Name as N import qualified Elm.Package.Paths as Path import qualified Elm.Package.Version as V import qualified Manager bump :: Manager.Manager () bump = do description <- Desc.read Path.description let name = Desc.name description let statedVersion = Desc.version description newDocs <- Docs.generate description maybeVersions <- Catalog.versions name case maybeVersions of Nothing -> validateInitialVersion description Just publishedVersions -> let baseVersions = map (\(old, _, _) -> old) (validBumps publishedVersions) in if statedVersion `elem` baseVersions then suggestVersion newDocs name statedVersion description else throwError (unbumpable baseVersions) return () unbumpable :: [V.Version] -> String unbumpable baseVersions = let versions = map head (List.group (List.sort baseVersions)) in unlines [ "To bump you must start with an already published version number in" , Path.description ++ ", giving us a starting point to bump from." , "" , "The version numbers that can be bumped include the following subset of" , "published versions:" , " " ++ List.intercalate ", " (map V.toString versions) , "" , "Switch back to one of these versions before running 'elm-package bump'" , "again." ] data Validity = Valid | Invalid | Changed V.Version validateInitialVersion :: Desc.Description -> Manager.Manager Validity validateInitialVersion description = do Cmd.out explanation if Desc.version description == V.initialVersion then Cmd.out goodMsg >> return Valid else changeVersion badMsg description V.initialVersion where explanation = unlines [ "This package has never been published before. Here's how things work:" , "" , " * Versions all have exactly three parts: MAJOR.MINOR.PATCH" , "" , " * All packages start with initial version " ++ V.toString V.initialVersion , "" , " * Versions are incremented based on how the API changes:" , "" , " PATCH - the API is the same, no risk of breaking code" , " MINOR - values have been added, existing values are unchanged" , " MAJOR - existing values have been changed or removed" , "" , " * I will bump versions for you, automatically enforcing these rules" , "" ] goodMsg = "The version number in " ++ Path.description ++ " is correct so you are all set!" badMsg = concat [ "It looks like the version in " ++ Path.description ++ " has been changed though!\n" , "Would you like me to change it back to " ++ V.toString V.initialVersion ++ "? (y/n) " ] changeVersion :: String -> Desc.Description -> V.Version -> Manager.Manager Validity changeVersion explanation description newVersion = do liftIO $ putStr explanation yes <- liftIO Cmd.yesOrNo case yes of False -> do Cmd.out "Okay, no changes were made." return Invalid True -> do liftIO $ Desc.write (description { Desc.version = newVersion }) Cmd.out $ "Version changed to " ++ V.toString newVersion ++ "." return (Changed newVersion) suggestVersion :: [Docs.Documentation] -> N.Name -> V.Version -> Desc.Description -> Manager.Manager Validity suggestVersion newDocs name version description = do changes <- Compare.computeChanges newDocs name version let newVersion = Compare.bumpBy changes version changeVersion (infoMsg changes newVersion) description newVersion where infoMsg changes newVersion = let old = V.toString version new = V.toString newVersion magnitude = show (Compare.packageChangeMagnitude changes) in concat [ "Based on your new API, this should be a ", magnitude, " change (", old, " => ", new, ")\n" , "Bail out of this command and run 'elm-package diff' for a full explanation.\n" , "\n" , "Should I perform the update (", old, " => ", new, ") in ", Path.description, "? (y/n) " ] validateVersion :: [Docs.Documentation] -> N.Name -> V.Version -> [V.Version] -> Manager.Manager Validity validateVersion newDocs name statedVersion publishedVersions = case List.find (\(_ ,new, _) -> statedVersion == new) bumps of Nothing -> let isPublished = statedVersion `elem` publishedVersions in throwError (if isPublished then alreadyPublished else invalidBump) Just (old, new, magnitude) -> do changes <- Compare.computeChanges newDocs name old let realNew = Compare.bumpBy changes old case new == realNew of False -> throwError (badBump old new realNew magnitude changes) True -> do Cmd.out (looksGood old new magnitude) return Valid where bumps = validBumps publishedVersions looksGood old new magnitude = "Version number " ++ V.toString new ++ " verified (" ++ show magnitude ++ " change, " ++ V.toString old ++ " => " ++ V.toString new ++ ")" alreadyPublished = "Version " ++ V.toString statedVersion ++ " has already been published, but you are trying to publish\n" ++ "it again! Run the following command to see what the new version should be.\n" ++ "\n elm-package bump\n" invalidBump = unlines [ "The version listed in " ++ Path.description ++ " is neither a previously" , "published version, nor a valid version bump." , "" , "Set the version number in " ++ Path.description ++ " to the released version" , "that you are improving upon. If you are working on the latest API, that means" , "you are modifying version " ++ V.toString (last publishedVersions) ++ "." , "" , "From there, we can compute which version comes next based on the API changes" , "when you run the following command." , "" , " elm-package bump" ] badBump old new realNew magnitude changes = unlines [ "It looks like you are trying to bump from version " ++ V.toString old ++ " to " ++ V.toString new ++ "." , "This implies you are making a " ++ show magnitude ++ " change, but when we compare" , "the " ++ V.toString old ++ " API to the API you have now it seems that it should" , "really be a " ++ show (Compare.packageChangeMagnitude changes) ++ " change (" ++ V.toString realNew ++ ")." , "" , "Run the following command to see the API diff we are working from:" , "" , " elm-package diff " ++ V.toString old , "" , "The easiest way to bump versions is to let us do it automatically. If you set" , "the version number in " ++ Path.description ++ " to the released version" , "that you are improving upon, we will compute which version should come next" , "when you run:" , "" , " elm-package bump" ] -- VALID BUMPS validBumps :: [V.Version] -> [(V.Version, V.Version, Compare.Magnitude)] validBumps publishedVersions = [ (majorPoint, V.bumpMajor majorPoint, Compare.MAJOR) ] ++ map (\v -> (v, V.bumpMinor v, Compare.MINOR)) minorPoints ++ map (\v -> (v, V.bumpPatch v, Compare.PATCH)) patchPoints where patchPoints = V.filterLatest V.majorAndMinor publishedVersions minorPoints = V.filterLatest V.major publishedVersions majorPoint = head publishedVersions