@@ -1,3 +1,4 @@
1+{-# LANGUAGE OverloadedStrings #-}
1module Install
2module Install
@@ -5,15 +6,23 @@ module Install
8−import Control.Monad.Trans (liftIO)
9+import Data.Map ((!))
10+import qualified Data.Map as Map
11+import qualified Data.Map.Merge.Strict as Map
10−import qualified Elm.Install as Install
13+import qualified Deps.Solver as Solver
14+import qualified Deps.Registry as Registry
15+import qualified Elm.Constraint as C
11−import qualified Elm.PerUserCache as PerUserCache
16+import qualified Elm.Details as Details
12import qualified Elm.Package as Pkg
17import qualified Elm.Package as Pkg
18+import qualified Elm.Outline as Outline
19+import qualified Elm.Version as V
20+import qualified Reporting
21+import Reporting.Doc ((<>), (<+>))
22+import qualified Reporting.Doc as D
13import qualified Reporting.Exit as Exit
23import qualified Reporting.Exit as Exit
14−import qualified Reporting.Exit.Install as E
15import qualified Reporting.Task as Task
24import qualified Reporting.Task as Task
16−import qualified Reporting.Progress.Terminal as Terminal
25+import qualified Stuff
@@ -27,12 +36,405 @@ data Args
28run :: Args -> () -> IO ()
37run :: Args -> () -> IO ()
29run args () =
38run args () =
30− do reporter <- Terminal.create
39+ Reporting.attempt Exit.installToReport $
31− Task.run reporter $
40+ do maybeRoot <- Stuff.findRoot
43+ return (Left Exit.InstallNoProject)
32− case args of
46+ case args of
33− NoArgs ->
47+ NoArgs ->
34− do elmHome <- liftIO PerUserCache.getElmHome
48+ do elmHome <- Stuff.getElmHome
35− Task.throw (Exit.Install (E.NoArgs elmHome))
49+ return (Left (Exit.InstallNoArgs elmHome))
37− Install pkg ->
51+ Install pkg ->
38− Install.install pkg
52+ Task.run $
54+ oldOutline <- getOutline root
56+ Outline.App outline ->
57+ do changes <- makeAppPlan env pkg outline
58+ attemptChanges root env oldOutline V.toChars changes
60+ Outline.Pkg outline ->
61+ do changes <- makePkgPlan env pkg outline
62+ attemptChanges root env oldOutline C.toChars changes
66+getEnv :: Task Solver.Env
68+ do eitherEnv <- Task.io Solver.initEnv
70+ Right env -> return env
71+ Left problem -> Task.throw (error "TODO getEnv" problem)
74+getOutline :: FilePath -> Task Outline.Outline
76+ do eitherOutline <- Task.io (Outline.read root)
77+ case eitherOutline of
78+ Right outline -> return outline
79+ Left exit -> Task.throw (error "TODO getOutline" exit)
88+ | PromoteTest Outline.Outline
89+ | PromoteIndirect Outline.Outline
90+ | Changes (Map.Map Pkg.Name (Change vsn)) Outline.Outline
93+type Task = Task.Task Exit.Install
96+attemptChanges :: FilePath -> Solver.Env -> Outline.Outline -> (a -> String) -> Changes a -> Task ()
97+attemptChanges root env oldOutline toChars changes =
100+ Task.io $ putStrLn "It is already installed!"
102+ PromoteIndirect newOutline ->
103+ attemptChangesHelp root env oldOutline newOutline $
106+ ["I","found","it","in","your","elm.json","file,"
107+ ,"but","in","the",D.dullyellow "\"indirect\"","dependencies."
110+ ["Should","I","move","it","into",D.green "\"direct\""
111+ ,"dependencies","for","more","general","use?","[Y/n]: "
115+ PromoteTest newOutline ->
116+ attemptChangesHelp root env oldOutline newOutline $
119+ ["I","found","it","in","your","elm.json","file,"
120+ ,"but","in","the",D.dullyellow "\"test-dependencies\"","field."
123+ ["Should","I","move","it","into",D.green "\"dependencies\""
124+ ,"for","more","general","use?","[Y/n]: "
128+ Changes changeDict newOutline ->
130+ widths = Map.foldrWithKey (widen toChars) (Widths 0 0 0) changeDict
131+ changeDocs = Map.foldrWithKey (addChange toChars widths) (Docs [] [] []) changeDict
133+ attemptChangesHelp root env oldOutline newOutline $ D.vcat $
134+ [ "Here is my plan:"
135+ , viewChangeDocs changeDocs
137+ , "Would you like me to update your elm.json accordingly? [Y/n]: "
141+attemptChangesHelp :: FilePath -> Solver.Env -> Outline.Outline -> Outline.Outline -> D.Doc -> Task ()
142+attemptChangesHelp root env oldOutline newOutline question =
143+ Task.eio Exit.InstallHasBadDetails $
144+ do approved <- Reporting.ask question
147+ do Outline.write root newOutline
148+ result <- Details.verifyInstall root env newOutline
151+ do Outline.write root oldOutline
155+ do putStrLn "Success!"
158+ do putStrLn "Okay, I did not change anything!"
166+makeAppPlan :: Solver.Env -> Pkg.Name -> Outline.AppOutline -> Task (Changes V.Version)
167+makeAppPlan (Solver.Env cache _ connection registry) pkg outline@(Outline.AppOutline _ _ direct indirect testDirect testIndirect) =
168+ if Map.member pkg direct then
169+ return AlreadyInstalled
172+ -- is it already indirect?
173+ case Map.lookup pkg indirect of
175+ return $ PromoteIndirect $ Outline.App $
177+ { Outline._app_deps_direct = Map.insert pkg vsn direct
178+ , Outline._app_deps_indirect = Map.delete pkg indirect
182+ -- is it already a test dependency?
183+ case Map.lookup pkg testDirect of
185+ return $ PromoteTest $ Outline.App $
187+ { Outline._app_deps_direct = Map.insert pkg vsn direct
188+ , Outline._app_test_direct = Map.delete pkg testDirect
192+ -- is it already an indirect test dependency?
193+ case Map.lookup pkg testIndirect of
195+ return $ PromoteTest $ Outline.App $
197+ { Outline._app_deps_direct = Map.insert pkg vsn direct
198+ , Outline._app_test_indirect = Map.delete pkg testIndirect
202+ -- finally try to add it from scratch
203+ case Registry.getVersions' pkg registry of
206+ Solver.Online _ -> Task.throw (Exit.InstallUnknownPackageOnline suggestions)
207+ Solver.Offline -> Task.throw (Exit.InstallUnknownPackageOffline suggestions)
210+ do result <- Task.io $ Solver.addToApp cache connection registry pkg outline
212+ Solver.Ok (Solver.AppSolution old new app) ->
213+ return (Changes (detectChanges old new) (Outline.App app))
215+ Solver.NoSolution ->
216+ Task.throw Exit.InstallNoOnlineSolution
218+ Solver.NoOfflineSolution ->
219+ Task.throw Exit.InstallNoOfflineSolution
222+ Task.throw (Exit.InstallHadSolverTrouble exit)
229+makePkgPlan :: Solver.Env -> Pkg.Name -> Outline.PkgOutline -> Task (Changes C.Constraint)
230+makePkgPlan (Solver.Env cache _ connection registry) pkg outline@(Outline.PkgOutline _ _ _ _ _ deps test _) =
231+ if Map.member pkg deps then
232+ return AlreadyInstalled
234+ -- is already in test dependencies?
235+ case Map.lookup pkg test of
237+ return $ PromoteTest $ Outline.Pkg $
239+ { Outline._pkg_deps = Map.insert pkg con deps
240+ , Outline._pkg_test_deps = Map.delete pkg test
244+ -- try to add a new dependency
245+ case Registry.getVersions' pkg registry of
248+ Solver.Online _ -> Task.throw (Exit.InstallUnknownPackageOnline suggestions)
249+ Solver.Offline -> Task.throw (Exit.InstallUnknownPackageOffline suggestions)
251+ Right (Registry.KnownVersions _ _) ->
252+ do let old = Map.union deps test
253+ let cons = Map.insert pkg C.anything old
254+ result <- Task.io $ Solver.verify cache connection registry cons
256+ Solver.Ok solution ->
258+ (Solver.Details vsn _) = solution ! pkg
260+ con = C.untilNextMajor vsn
261+ new = Map.insert pkg con old
262+ changes = detectChanges old new
263+ news = Map.mapMaybe keepNew changes
265+ return $ Changes changes $ Outline.Pkg $
267+ { Outline._pkg_deps = addNews (Just pkg) news deps
268+ , Outline._pkg_test_deps = addNews Nothing news test
271+ Solver.NoSolution ->
272+ Task.throw Exit.InstallNoOnlineSolution
274+ Solver.NoOfflineSolution ->
275+ Task.throw Exit.InstallNoOfflineSolution
278+ Task.throw (Exit.InstallHadSolverTrouble exit)
281+addNews :: Maybe Pkg.Name -> Map.Map Pkg.Name C.Constraint -> Map.Map Pkg.Name C.Constraint -> Map.Map Pkg.Name C.Constraint
282+addNews pkg new old =
285+ (Map.mapMaybeMissing (\k c -> if Just k == pkg then Just c else Nothing))
286+ (Map.zipWithMatched (\_ _ n -> n))
301+detectChanges :: (Eq a) => Map.Map Pkg.Name a -> Map.Map Pkg.Name a -> Map.Map Pkg.Name (Change a)
302+detectChanges old new =
304+ (Map.mapMissing (\_ v -> Remove v))
305+ (Map.mapMissing (\_ v -> Insert v))
306+ (Map.zipWithMaybeMatched keepChange)
311+keepChange :: (Eq v) => k -> v -> v -> Maybe (Change v)
312+keepChange _ old new =
316+ Just (Change old new)
319+keepNew :: Change a -> Maybe a
338+ { _doc_inserts :: [D.Doc]
339+ , _doc_changes :: [D.Doc]
340+ , _doc_removes :: [D.Doc]
344+viewChangeDocs :: ChangeDocs -> D.Doc
345+viewChangeDocs (Docs inserts changes removes) =
346+ D.indent 2 $ D.vcat $ concat $
347+ [ viewNonZero "Add:" inserts
348+ , viewNonZero "Change:" changes
349+ , viewNonZero "Remove:" removes
353+viewNonZero :: String -> [D.Doc] -> [D.Doc]
354+viewNonZero title entries =
355+ if null entries then
360+ , D.indent 2 (D.vcat entries)
368+addChange :: (a -> String) -> Widths -> Pkg.Name -> Change a -> ChangeDocs -> ChangeDocs
369+addChange toChars widths name change (Docs inserts changes removes) =
372+ Docs (viewInsert toChars widths name new : inserts) changes removes
375+ Docs inserts (viewChange toChars widths name old new : changes) removes
378+ Docs inserts changes (viewRemove toChars widths name old : removes)
381+viewInsert :: (a -> String) -> Widths -> Pkg.Name -> a -> D.Doc
382+viewInsert toChars (Widths nameWidth leftWidth _) name new =
383+ viewName nameWidth name <+> pad leftWidth (toChars new)
386+viewChange :: (a -> String) -> Widths -> Pkg.Name -> a -> a -> D.Doc
387+viewChange toChars (Widths nameWidth leftWidth rightWidth) name old new =
389+ [ viewName nameWidth name
390+ , pad leftWidth (toChars old)
392+ , pad rightWidth (toChars new)
396+viewRemove :: (a -> String) -> Widths -> Pkg.Name -> a -> D.Doc
397+viewRemove toChars (Widths nameWidth leftWidth _) name old =
398+ viewName nameWidth name <+> pad leftWidth (toChars old)
401+viewName :: Int -> Pkg.Name -> D.Doc
402+viewName width name =
403+ D.fill (width + 3) (D.fromUtf8 (Pkg.toString name))
406+pad :: Int -> String -> D.Doc
408+ D.fromChars (replicate (width - length string) ' ') <> D.fromChars string
423+widen :: (a -> String) -> Pkg.Name -> Change a -> Widths -> Widths
424+widen toChars pkg change (Widths name left right) =
430+ max name (length (Pkg.toChars pkg))
434+ Widths newName (max left (toLength new)) right
437+ Widths newName (max left (toLength old)) (max right (toLength new))
440+ Widths newName (max left (toLength old)) right