7import Control.Monad (filterM, forM, msum, void)
8import Control.Monad.Except (catchError, lift, liftIO)
9import Data.Map (Map, (!))
10import qualified Data.Map as Map
11import qualified Data.Map.Merge.Lazy as Map
12import qualified System.IO as IO
13import Text.PrettyPrint.ANSI.Leijen ((<>), (<+>))
14import qualified Text.PrettyPrint.ANSI.Leijen as P
16import qualified Elm.Package as Pkg
17import Elm.Package (Name, Version)
19import Deps.Explorer (Explorer)
20import qualified Deps.Explorer as Explorer
21import qualified Deps.Verify as Verify
22import qualified Deps.Solver as Solver
23import Elm.Project.Constraint (Constraint)
24import qualified Elm.Project.Constraint as Con
25import qualified Elm.Project.Json as Project
26import qualified Elm.Project.Root as Root
27import qualified Reporting.Exit as Exit
28import qualified Reporting.Task as Task
35install :: Name -> Task.Task ()
37 do (root, oldProject) <- Root.unsafeGet
38 makePlan pkg oldProject $ \newProject ->
41 do liftIO $ Project.write root newProject
42 void $ Verify.verify root newProject
45 do liftIO $ Project.write root oldProject
48 upgrade `catchError` revert
55makePlan :: Name -> Project.Project -> (Project.Project -> Task.Task ()) -> Task.Task ()
56makePlan pkg project attemptInstall =
58 Project.App info@(Project.AppInfo elm srcDirs deps test trans) ->
59 do changes <- addToApp pkg info
61 let news = Map.mapMaybe keepNew changes
62 let newDeps = addNews (Just pkg) news deps
63 let newTest = addNews Nothing news test
64 let newTrans = Map.union (Map.difference news (Map.union newDeps newTest)) trans
65 let newInfo = Project.AppInfo elm srcDirs newDeps newTest newTrans
66 let newProject = Project.App newInfo
68 withApproval Pkg.versionToString changes (attemptInstall newProject)
70 Project.Pkg info@(Project.PkgInfo _ _ _ _ _ deps test _) ->
71 do changes <- addToPkg pkg info
73 let news = Map.mapMaybe keepNew changes
74 let newProject = Project.Pkg $
76 { Project._pkg_deps = addNews (Just pkg) news deps
77 , Project._pkg_test_deps = addNews Nothing news test
80 withApproval (show . Con.toString) changes (attemptInstall newProject)
83addNews :: Maybe Name -> Map Name a -> Map Name a -> Map Name a
87 (Map.mapMaybeMissing (\k v -> if Just k == pkg then Just v else Nothing))
88 (Map.zipWithMatched (\_ _ n -> n))
103detectChanges :: (Eq a) => Map Name a -> Map Name a -> Map Name (Change a)
104detectChanges old new =
106 (Map.mapMissing (\_ v -> Remove v))
107 (Map.mapMissing (\_ v -> Insert v))
108 (Map.zipWithMaybeMatched keepChange)
113keepChange :: (Eq v) => k -> v -> v -> Maybe (Change v)
114keepChange _ old new =
118 Just (Change old new)
121keepNew :: Change a -> Maybe a
138addToApp :: Name -> Project.AppInfo -> Task.Task (Map Name (Change Version))
139addToApp pkg info@(Project.AppInfo _ _ deps tests trans) =
141 do Explorer.exists pkg
142 let old = Map.unions [ deps, tests, trans ]
143 result <- Solver.run (addToAppHelp pkg info)
146 return $ detectChanges old new
149 do badNames <- filterM isBadElm (pkg : Map.keys old)
150 lift $ Task.throw (Exit.NoSolution badNames)
153addToAppHelp :: Name -> Project.AppInfo -> Solver.Solver (Map Name Version)
154addToAppHelp pkg (Project.AppInfo _ _ deps tests trans) =
160 Map.union directs trans
163 Solver.solve $ Map.insert pkg Con.anything constraints
166 [ Map.map Con.exactly everything
167 , Map.map Con.exactly directs
168 , Map.map Con.untilNextMinor directs
169 , Map.map Con.untilNextMajor directs
170 , Map.map (\_ -> Con.anything) directs
178addToPkg :: Name -> Project.PkgInfo -> Task.Task (Map Name (Change Constraint))
179addToPkg pkg info@(Project.PkgInfo _ _ _ _ _ deps tests _) =
181 do Explorer.exists pkg
182 let old = Map.union deps tests
183 result <- Solver.run (addToPkgHelp pkg info)
186 return $ detectChanges old new
189 do let pkgs = Map.keys deps ++ Map.keys tests
190 badNames <- filterM isBadElm (pkg : pkgs)
191 lift $ Task.throw (Exit.NoSolution badNames)
194addToPkgHelp :: Name -> Project.PkgInfo -> Solver.Solver (Map Name Constraint)
195addToPkgHelp pkg (Project.PkgInfo _ _ _ _ _ deps tests _) =
196 do let directs = Map.union deps tests
197 let newCons = Map.insert pkg Con.anything directs
198 solution <- Solver.solve newCons
199 let con = Con.untilNextMajor (solution ! pkg)
200 return $ Map.insert pkg con directs
207isBadElm :: Name -> Explorer Bool
209 do versions <- Explorer.getVersions name
211 elmVersions <- forM versions $ \vsn ->
212 Explorer._elm <$> Explorer.getConstraints name vsn
214 return (not (any Con.goodElm elmVersions))
221withApproval :: (a -> String) -> Map Name (Change a) -> Task.Task () -> Task.Task ()
222withApproval toString changes attemptInstall =
225 Map.foldrWithKey (widen toString) (Widths 0 0 0) changes
228 Map.foldrWithKey (addChange toString widths) (Docs [] [] []) changes
231 P.displayIO IO.stdout $ P.renderPretty 1 80 doc
235 do liftIO $ putStrLn "Already installed. No changes necessary!"
237 Docs inserts [] [] ->
238 do liftIO $ putStrLn "Adding the following dependencies:\n"
239 liftIO $ printDoc $ P.indent 2 (P.vcat (reverse inserts))
240 liftIO $ putStrLn "\n"
244 do liftIO $ putStrLn "Here is my plan:"
245 liftIO $ printDoc $ viewChangeDocs changeDocs
246 liftIO $ putStrLn "\nDo you approve? [Y/n]: "
247 Task.withApproval attemptInstall
256 { _doc_inserts :: [P.Doc]
257 , _doc_changes :: [P.Doc]
258 , _doc_removes :: [P.Doc]
262viewChangeDocs :: ChangeDocs -> P.Doc
263viewChangeDocs (Docs inserts changes removes) =
264 P.indent 2 $ P.vcat $ concat $
265 [ viewNonZero "Add:" inserts
266 , viewNonZero "Change:" changes
267 , viewNonZero "Remove:" removes
271viewNonZero :: String -> [P.Doc] -> [P.Doc]
272viewNonZero title entries =
278 , P.indent 2 (P.vcat (reverse entries))
286addChange :: (a -> String) -> Widths -> Name -> Change a -> ChangeDocs -> ChangeDocs
287addChange toString widths name change (Docs inserts changes removes) =
290 Docs (viewInsert toString widths name new : inserts) changes removes
293 Docs inserts (viewChange toString widths name old new : changes) removes
296 Docs inserts changes (viewRemove toString widths name old : removes)
299viewInsert :: (a -> String) -> Widths -> Name -> a -> P.Doc
300viewInsert toString (Widths nameWidth leftWidth _) name new =
301 viewName nameWidth name <+> pad leftWidth (toString new)
304viewChange :: (a -> String) -> Widths -> Name -> a -> a -> P.Doc
305viewChange toString (Widths nameWidth leftWidth rightWidth) name old new =
307 [ viewName nameWidth name
308 , pad leftWidth (toString old)
310 , pad rightWidth (toString new)
314viewRemove :: (a -> String) -> Widths -> Name -> a -> P.Doc
315viewRemove toString (Widths nameWidth leftWidth _) name old =
316 viewName nameWidth name <+> pad leftWidth (toString old)
319viewName :: Int -> Name -> P.Doc
321 P.fill (width + 3) (P.text (Pkg.toString name))
324pad :: Int -> String -> P.Doc
326 P.text (replicate (width - length string) ' ') <> P.text string
341widen :: (a -> String) -> Name -> Change a -> Widths -> Widths
342widen toString pkg change (Widths name left right) =
348 max name (length (Pkg.toString pkg))
352 Widths newName (max left (toLength new)) right
355 Widths newName (max left (toLength old)) (max right (toLength new))
358 Widths newName (max left (toLength old)) right
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list