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