module Install where import Control.Monad.Error import qualified Data.List as List import qualified Data.Map as Map import System.Directory (doesFileExist, removeDirectoryRecursive) import System.FilePath (()) import qualified CommandLine.Helpers as Cmd import qualified Elm.Package.Constraint as Constraint 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.Solution as Solution import qualified Elm.Package.Version as V import qualified Install.Fetch as Fetch import qualified Install.Plan as Plan import qualified Install.Solver as Solver import qualified Manager import qualified Store data Args = Everything | Latest N.Name | Exactly N.Name V.Version install :: Bool -> Args -> Manager.Manager () install autoYes args = do exists <- liftIO (doesFileExist Path.description) description <- case exists of True -> Desc.read Path.description False -> initialDescription case args of Everything -> upgrade autoYes description Latest name -> do version <- latestVersion name newDescription <- addConstraint autoYes name version description upgrade autoYes newDescription Exactly name version -> do newDescription <- addConstraint autoYes name version description upgrade autoYes newDescription -- INSTALL EVERYTHING upgrade :: Bool -> Desc.Description -> Manager.Manager () upgrade autoYes description = do newSolution <- Solver.solve (Desc.dependencies description) exists <- liftIO (doesFileExist Path.solvedDependencies) oldSolution <- if exists then Solution.read Path.solvedDependencies else return Map.empty let plan = Plan.create oldSolution newSolution approve <- liftIO (getApproval autoYes plan) if approve then runPlan newSolution plan else liftIO $ putStrLn "Okay, I did not change anything!" getApproval :: Bool -> Plan.Plan -> IO Bool getApproval autoYes plan = case autoYes || Plan.isEmpty plan of True -> return True False -> do putStrLn "Some new packages are needed. Here is the upgrade plan." putStrLn (Plan.display plan) putStr "Do you approve of this plan? (y/n) " Cmd.yesOrNo runPlan :: Solution.Solution -> Plan.Plan -> Manager.Manager () runPlan solution plan = do let installs = Map.toList (Plan.installs plan) ++ Map.toList (Map.map snd (Plan.upgrades plan)) let removals = Map.toList (Plan.removals plan) ++ Map.toList (Map.map fst (Plan.upgrades plan)) -- fetch new dependencies Cmd.inDir Path.packagesDirectory $ forM_ installs $ \(name, version) -> do liftIO (putStrLn ("Downloading " ++ N.toString name)) Fetch.package name version -- try to build new dependencies liftIO (Solution.write Path.solvedDependencies solution) -- remove dependencies that are not needed Cmd.inDir Path.packagesDirectory $ forM_ removals $ \(name, version) -> liftIO $ removeDirectoryRecursive (N.toFilePath name V.toString version) liftIO $ putStrLn "Packages configured successfully!" -- MODIFY DESCRIPTION latestVersion :: N.Name -> Manager.Manager V.Version latestVersion name = do versionCache <- Store.readVersionCache case Map.lookup name versionCache of Just versions -> return $ maximum versions Nothing -> throwError $ unlines [ "No versions of package '" ++ N.toString name ++ "' were found!" , "Is it spelled correctly?" ] addConstraint :: Bool -> N.Name -> V.Version -> Desc.Description -> Manager.Manager Desc.Description addConstraint autoYes name version description = case List.lookup name (Desc.dependencies description) of Nothing -> addNewDependency autoYes name version description Just constraint | Constraint.isSatisfied constraint version -> return description | otherwise -> throwError $ "This is a tricky update, you should modify " ++ Path.description ++ " yourself.\n" ++ "Package " ++ N.toString name ++ " is already listed as a dependency:\n\n " ++ showDependency name constraint ++ "\n\n" ++ "You probably want one of the following constraints instead:\n\n " ++ Constraint.toString (Constraint.expand constraint version) ++ "\n " ++ Constraint.toString (Constraint.untilNextMajor version) ++ "\n" addNewDependency :: Bool -> N.Name -> V.Version -> Desc.Description -> Manager.Manager Desc.Description addNewDependency autoYes name version description = do confirm <- case autoYes of True -> return True False -> do answer <- liftIO confirmNewAddition liftIO (putStrLn "") return answer case confirm of False -> do liftIO $ putStrLn noConfirmation return description True -> do let newDescription = description { Desc.dependencies = newConstraints } liftIO $ Desc.write newDescription return newDescription where newConstraint = Constraint.untilNextMajor version newConstraints = (name, newConstraint) : Desc.dependencies description noConfirmation = "Cannot install the new package unless it appears in " ++ Path.description ++ ".\n" ++ "If you do not like the constraint I suggested, change it manually and then run:\n" ++ "\n elm-package install\n\n" ++ "This will install everything listed in " ++ Path.description ++ "." confirmNewAddition = do putStrLn $ "To install " ++ N.toString name ++ " I would like to add the following\n" ++ "dependency to " ++ Path.description ++ ":\n\n " ++ showDependency name newConstraint ++ "\n" putStr $ "May I add that to " ++ Path.description ++ " for you? (y/n) " Cmd.yesOrNo showDependency :: N.Name -> Constraint.Constraint -> String showDependency name constraint = show (N.toString name) ++ ": " ++ show (Constraint.toString constraint) initialDescription :: Manager.Manager Desc.Description initialDescription = do let core = N.Name "elm-lang" "core" version <- latestVersion core let desc = Desc.defaultDescription { Desc.dependencies = [ (core, Constraint.untilNextMajor version) ] } liftIO (Desc.write desc) return desc