@@ -7,13 +7,11 @@ module Elm.Install
77
88import Control.Monad (filterM, forM, msum, void)
99import Control.Monad.Except (catchError, lift, liftIO)
10−import Data.Map (Map, (!))
10+import Data.Map ((!))
1111import qualified Data.Map as Map
1212import qualified Data.Map.Merge.Lazy as Map
13−import System.IO (stdout)
1413
1514import qualified Elm.Package as Pkg
16−import Elm.Package (Name, Version)
1715
1816import qualified Deps.Cache as Cache
1917import Deps.Explorer (Explorer)
@@ -35,57 +33,133 @@ import qualified Reporting.Task as Task
3533-- INSTALL
3634
3735
38−install :: Name -> Task.Task ()
36+install :: Pkg.Name -> Task.Task ()
3937install pkg =
4038 do (root, oldProject) <- Root.unsafeGet
41− makePlan pkg oldProject $ \newProject ->
42− let
43− upgrade =
44− do liftIO $ Project.write root newProject
45− void $ Verify.verify root newProject
4639
47− revert err =
48− do liftIO $ Project.write root oldProject
49− Task.throw err
50− in
51− upgrade `catchError` revert
40+ case oldProject of
41+ Project.App info ->
42+ do changes <- makeAppPlan pkg info
43+ attemptElmJsonChange root oldProject Pkg.versionToString changes
5244
45+ Project.Pkg info ->
46+ do changes <- makePkgPlan pkg info
47+ attemptElmJsonChange root oldProject Con.toString changes
5348
5449
55−-- MAKE PLAN
56−
57−
58−makePlan :: Name -> Project.Project -> (Project.Project -> Task.Task ()) -> Task.Task ()
59−makePlan pkg project attemptInstall =
60− case project of
61− Project.App info@(Project.AppInfo elm srcDirs deps test trans) ->
62− do registry <- Cache.optionalUpdate
63− changes <- addToApp registry pkg info
64−
65− let news = Map.mapMaybe keepNew changes
66− let newDeps = addNews (Just pkg) news deps
67− let newTest = addNews Nothing news test
68− let newTrans = Map.union (Map.difference news (Map.union newDeps newTest)) trans
69− let newInfo = Project.AppInfo elm srcDirs newDeps newTest newTrans
70− let newProject = Project.App newInfo
50+attemptElmJsonChange :: FilePath -> Project.Project -> (a -> String) -> Changes a -> Task.Task ()
51+attemptElmJsonChange root oldProject toString changes =
52+ let
53+ attempt newProject question =
54+ do approved <- Task.getApproval question
55+ if approved
56+ then upgrade newProject `catchError` revert
57+ else liftIO $ putStrLn "Okay, I did not change anything!"
58+
59+ upgrade newProject =
60+ do liftIO $ Project.write root newProject
61+ void $ Verify.verify root newProject
62+
63+ revert err =
64+ do liftIO $ Project.write root oldProject
65+ Task.throw err
66+ in
67+ case changes of
68+ AlreadyInstalled ->
69+ do liftIO $ putStrLn "It is already installed!"
70+
71+ PromoteTest newProject ->
72+ attempt newProject $ D.vcat $
73+ [ "I found it in your elm.json file!"
74+ , "In \"test-dependencies\" though."
75+ , "Should I move it into \"dependencies\" for more general use? [Y/n]: "
76+ ]
77+
78+ PromoteTrans newProject ->
79+ attempt newProject $ D.vcat $
80+ [ "I found it in your elm.json file!"
81+ , "In \"transitive-dependencies\" though."
82+ , "Should I move it into \"dependencies\" for more general use? [Y/n]: "
83+ ]
84+
85+ Changes changeDict newProject ->
86+ let
87+ widths = Map.foldrWithKey (widen toString) (Widths 0 0 0) changeDict
88+ changeDocs = Map.foldrWithKey (addChange toString widths) (Docs [] [] []) changeDict
89+ in
90+ attempt newProject $ D.vcat $
91+ [ "Here is my plan:"
92+ , viewChangeDocs changeDocs
93+ , ""
94+ , "Would you like me to update your elm.json accordingly? [Y/n]: "
95+ ]
96+
97+
98+
99+-- MAKE PLANS
100+
101+
102+data Changes vsn
103+ = AlreadyInstalled
104+ | PromoteTest Project.Project
105+ | PromoteTrans Project.Project
106+ | Changes (Map.Map Pkg.Name (Change vsn)) Project.Project
107+
108+
109+makeAppPlan :: Pkg.Name -> Project.AppInfo -> Task.Task (Changes Pkg.Version)
110+makeAppPlan pkg info@(Project.AppInfo elm srcDirs deps test trans) =
111+ if Map.member pkg deps then
112+ return AlreadyInstalled
113+ else
114+ case Map.lookup pkg test of
115+ Just vsn ->
116+ return $ PromoteTest $ Project.App $
117+ Project.AppInfo elm srcDirs (Map.insert pkg vsn deps) (Map.delete pkg test) trans
71118
72− withApproval Pkg.versionToString changes (attemptInstall newProject)
119+ Nothing ->
120+ case Map.lookup pkg trans of
121+ Just vsn ->
122+ return $ PromoteTrans $ Project.App $
123+ Project.AppInfo elm srcDirs (Map.insert pkg vsn deps) test (Map.delete pkg trans)
73124
74− Project.Pkg info@(Project.PkgInfo _ _ _ _ _ deps test _) ->
75− do registry <- Cache.optionalUpdate
76− changes <- addToPkg registry pkg info
125+ Nothing ->
126+ do registry <- Cache.optionalUpdate
127+ changes <- addToApp registry pkg info
77128
78− let news = Map.mapMaybe keepNew changes
79− let newProject = Project.Pkg $
80− info
81− { Project._pkg_deps = addNews (Just pkg) news deps
82− , Project._pkg_test_deps = addNews Nothing news test
83− }
129+ let news = Map.mapMaybe keepNew changes
130+ let newDeps = addNews (Just pkg) news deps
131+ let newTest = addNews Nothing news test
132+ let newTrans = Map.union (Map.difference news (Map.union newDeps newTest)) trans
84133
85− withApproval (show . Con.toString) changes (attemptInstall newProject)
134+ return $ Changes changes $ Project.App $
135+ Project.AppInfo elm srcDirs newDeps newTest newTrans
86136
87137
88−addNews :: Maybe Name -> Map Name a -> Map Name a -> Map Name a
138+makePkgPlan :: Pkg.Name -> Project.PkgInfo -> Task.Task (Changes Constraint)
139+makePkgPlan pkg info@(Project.PkgInfo _ _ _ _ _ deps test _) =
140+ if Map.member pkg deps then
141+ return AlreadyInstalled
142+ else
143+ case Map.lookup pkg test of
144+ Just con ->
145+ return $ PromoteTest $ Project.Pkg $
146+ info
147+ { Project._pkg_deps = Map.insert pkg con deps
148+ , Project._pkg_test_deps = Map.delete pkg test
149+ }
150+
151+ Nothing ->
152+ do registry <- Cache.optionalUpdate
153+ changes <- addToPkg registry pkg info
154+ let news = Map.mapMaybe keepNew changes
155+ return $ Changes changes $ Project.Pkg $
156+ info
157+ { Project._pkg_deps = addNews (Just pkg) news deps
158+ , Project._pkg_test_deps = addNews Nothing news test
159+ }
160+
161+
162+addNews :: Maybe Pkg.Name -> Map.Map Pkg.Name a -> Map.Map Pkg.Name a -> Map.Map Pkg.Name a
89163addNews pkg new old =
90164 Map.merge
91165 Map.preserveMissing
@@ -105,7 +179,7 @@ data Change a
105179 | Remove a
106180
107181
108−detectChanges :: (Eq a) => Map Name a -> Map Name a -> Map Name (Change a)
182+detectChanges :: (Eq a) => Map.Map Pkg.Name a -> Map.Map Pkg.Name a -> Map.Map Pkg.Name (Change a)
109183detectChanges old new =
110184 Map.merge
111185 (Map.mapMissing (\_ v -> Remove v))
@@ -140,7 +214,7 @@ keepNew change =
140214-- ADD TO APP
141215
142216
143−addToApp :: Cache.PackageRegistry -> Name -> Project.AppInfo -> Task.Task (Map Name (Change Version))
217+addToApp :: Cache.PackageRegistry -> Pkg.Name -> Project.AppInfo -> Task.Task (Map.Map Pkg.Name (Change Pkg.Version))
144218addToApp registry pkg info@(Project.AppInfo _ _ deps tests trans) =
145219 Explorer.run registry $
146220 do Explorer.exists pkg
@@ -155,7 +229,7 @@ addToApp registry pkg info@(Project.AppInfo _ _ deps tests trans) =
155229 lift $ Task.throw (Exit.Install (E.NoSolution badNames))
156230
157231
158−addToAppHelp :: Name -> Project.AppInfo -> Solver.Solver (Map Name Version)
232+addToAppHelp :: Pkg.Name -> Project.AppInfo -> Solver.Solver (Map.Map Pkg.Name Pkg.Version)
159233addToAppHelp pkg (Project.AppInfo _ _ deps tests trans) =
160234 let
161235 directs =
@@ -180,7 +254,7 @@ addToAppHelp pkg (Project.AppInfo _ _ deps tests trans) =
180254-- ADD TO PKG
181255
182256
183−addToPkg :: Cache.PackageRegistry -> Name -> Project.PkgInfo -> Task.Task (Map Name (Change Constraint))
257+addToPkg :: Cache.PackageRegistry -> Pkg.Name -> Project.PkgInfo -> Task.Task (Map.Map Pkg.Name (Change Constraint))
184258addToPkg registry pkg info@(Project.PkgInfo _ _ _ _ _ deps tests _) =
185259 Explorer.run registry $
186260 do Explorer.exists pkg
@@ -196,7 +270,7 @@ addToPkg registry pkg info@(Project.PkgInfo _ _ _ _ _ deps tests _) =
196270 lift $ Task.throw (Exit.Install (E.NoSolution badNames))
197271
198272
199−addToPkgHelp :: Name -> Project.PkgInfo -> Solver.Solver (Map Name Constraint)
273+addToPkgHelp :: Pkg.Name -> Project.PkgInfo -> Solver.Solver (Map.Map Pkg.Name Constraint)
200274addToPkgHelp pkg (Project.PkgInfo _ _ _ _ _ deps tests _) =
201275 do let directs = Map.union deps tests
202276 let newCons = Map.insert pkg Con.anything directs
@@ -209,7 +283,7 @@ addToPkgHelp pkg (Project.PkgInfo _ _ _ _ _ deps tests _) =
209283-- FAILURE HINTS
210284
211285
212−isBadElm :: Name -> Explorer Bool
286+isBadElm :: Pkg.Name -> Explorer Bool
213287isBadElm name =
214288 do versions <- Explorer.getVersions name
215289
@@ -220,36 +294,6 @@ isBadElm name =
220294
221295
222296
223−-- VIEW
224−
225−
226−withApproval :: (a -> String) -> Map Name (Change a) -> Task.Task () -> Task.Task ()
227−withApproval toString changes attemptInstall =
228− let
229− widths =
230− Map.foldrWithKey (widen toString) (Widths 0 0 0) changes
231−
232− changeDocs =
233− Map.foldrWithKey (addChange toString widths) (Docs [] [] []) changes
234− in
235− case changeDocs of
236− Docs [] [] [] ->
237− do liftIO $ putStrLn "Already installed. No changes necessary!"
238−
239− Docs inserts [] [] ->
240− do liftIO $ putStrLn "Adding the following dependencies:\n"
241− liftIO $ D.toAnsi stdout $ D.indent 2 (D.vcat (reverse inserts))
242− liftIO $ putStrLn "\n"
243− attemptInstall
244−
245− Docs _ _ _ ->
246− do liftIO $ putStrLn "Here is my plan:"
247− liftIO $ D.toAnsi stdout $ viewChangeDocs changeDocs
248− liftIO $ putStrLn "\nDo you approve? [Y/n]: "
249− Task.withApproval attemptInstall
250−
251−
252−
253297-- VIEW CHANGE DOCS
254298
255299
@@ -285,7 +329,7 @@ viewNonZero title entries =
285329-- VIEW CHANGE
286330
287331
288−addChange :: (a -> String) -> Widths -> Name -> Change a -> ChangeDocs -> ChangeDocs
332+addChange :: (a -> String) -> Widths -> Pkg.Name -> Change a -> ChangeDocs -> ChangeDocs
289333addChange toString widths name change (Docs inserts changes removes) =
290334 case change of
291335 Insert new ->
@@ -298,12 +342,12 @@ addChange toString widths name change (Docs inserts changes removes) =
298342 Docs inserts changes (viewRemove toString widths name old : removes)
299343
300344
301−viewInsert :: (a -> String) -> Widths -> Name -> a -> D.Doc
345+viewInsert :: (a -> String) -> Widths -> Pkg.Name -> a -> D.Doc
302346viewInsert toString (Widths nameWidth leftWidth _) name new =
303347 viewName nameWidth name <+> pad leftWidth (toString new)
304348
305349
306−viewChange :: (a -> String) -> Widths -> Name -> a -> a -> D.Doc
350+viewChange :: (a -> String) -> Widths -> Pkg.Name -> a -> a -> D.Doc
307351viewChange toString (Widths nameWidth leftWidth rightWidth) name old new =
308352 D.hsep
309353 [ viewName nameWidth name
@@ -313,12 +357,12 @@ viewChange toString (Widths nameWidth leftWidth rightWidth) name old new =
313357 ]
314358
315359
316−viewRemove :: (a -> String) -> Widths -> Name -> a -> D.Doc
360+viewRemove :: (a -> String) -> Widths -> Pkg.Name -> a -> D.Doc
317361viewRemove toString (Widths nameWidth leftWidth _) name old =
318362 viewName nameWidth name <+> pad leftWidth (toString old)
319363
320364
321−viewName :: Int -> Name -> D.Doc
365+viewName :: Int -> Pkg.Name -> D.Doc
322366viewName width name =
323367 D.fill (width + 3) (D.fromText (Pkg.toText name))
324368
@@ -340,7 +384,7 @@ data Widths =
340384 }
341385
342386
343−widen :: (a -> String) -> Name -> Change a -> Widths -> Widths
387+widen :: (a -> String) -> Pkg.Name -> Change a -> Widths -> Widths
344388widen toString pkg change (Widths name left right) =
345389 let
346390 toLength a =