@@ -1,17 +1,325 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2+{-# LANGUAGE OverloadedStrings #-}
2−module Diff (run) where
3+module Diff
5−import qualified Elm.Diff as Diff
10+import qualified Data.List as List
11+import qualified Data.Map as Map
12+import qualified Data.Maybe as Maybe
13+import qualified Data.Name as Name
15+import qualified Build
16+import Deps.Diff (PackageChanges(..), ModuleChanges(..), Changes(..))
17+import qualified Deps.Diff as DD
18+import qualified Deps.Registry as Registry
19+import qualified Elm.Compiler.Type as Type
20+import qualified Elm.Details as Details
21+import qualified Elm.Docs as Docs
22+import qualified Elm.Magnitude as M
23+import qualified Elm.Package as Pkg
24+import qualified Elm.Version as V
26+import qualified Reporting
27+import Reporting.Doc ((<>), (<+>))
28+import qualified Reporting.Doc as D
29+import qualified Reporting.Exit as Exit
30+import qualified Reporting.Exit.Help as Help
31+import qualified Reporting.Render.Type.Localizer as L
6import qualified Reporting.Task as Task
32import qualified Reporting.Task as Task
7−import qualified Reporting.Progress.Terminal as Terminal
33+import qualified Stuff
42+ | CodeVsExactly V.Version
43+ | LocalInquiry V.Version V.Version
44+ | GlobalInquiry Pkg.Name V.Version V.Version
14−run :: Diff.Args -> () -> IO ()
47+run :: Args -> () -> IO ()
15run args () =
48run args () =
16− do reporter <- Terminal.create
49+ Reporting.attempt Exit.diffToReport $
17− Task.run reporter $ Diff.diff args
50+ Task.run $
61+ { _root :: Maybe FilePath
62+ , _cache :: Stuff.PackageCache
63+ , _manager :: Http.Manager
64+ , _registry :: Registry.Registry
70+ do root <- Task.io $ Stuff.findRoot
71+ cache <- Task.io $ Stuff.getPackageCache
72+ manager <- Task.io $ Http.getManager
73+ registry <- Task.eio Exit.DiffBadRegistry $ Registry.latest manager cache
74+ return (Env root cache manager registry)
82+ Task.Task Exit.Diff a
85+diff :: Env -> Args -> Task ()
86+diff env@(Env _ _ _ registry) args =
88+ GlobalInquiry name v1 v2 ->
89+ case Registry.getVersions' name registry of
91+ do oldDocs <- getDocs env name vsns (min v1 v2)
92+ newDocs <- getDocs env name vsns (max v1 v2)
93+ writeDiff oldDocs newDocs
96+ Task.throw $ Exit.DiffUnknownPackage name suggestions
98+ LocalInquiry v1 v2 ->
99+ do (_, name, vsns) <- getDetails env
100+ oldDocs <- getDocs env name vsns (min v1 v2)
101+ newDocs <- getDocs env name vsns (max v1 v2)
102+ writeDiff oldDocs newDocs
105+ do (details, name, vsns) <- getDetails env
106+ oldDocs <- getLatestDocs env name vsns
107+ newDocs <- Build.generateDocs details
108+ writeDiff oldDocs newDocs
110+ CodeVsExactly version ->
111+ do (details, name, vsns) <- getDetails env
112+ oldDocs <- getDocs env name vsns version
113+ newDocs <- Build.generateDocs details
114+ writeDiff oldDocs newDocs
121+getDocs :: Env -> Pkg.Name -> Registry.KnownVersions -> V.Version -> Task Docs.Documentation
122+getDocs (Env _ cache manager _) name (Registry.KnownVersions latest previous) version =
123+ if latest == version || elem version previous
124+ then Task.eio Exit.DiffDocsProblem $ DD.getDocs cache manager name version
125+ else Task.throw $ Exit.DiffUnknownVersion name version (latest:previous)
128+getLatestDocs :: Env -> Pkg.Name -> Registry.KnownVersions -> Task Docs.Documentation
129+getLatestDocs (Env _ cache manager _) name (Registry.KnownVersions latest _) =
130+ Task.eio Exit.DiffDocsProblem $ DD.getDocs cache manager name latest
137+getDetails :: Env -> Task (Details.Details, Pkg.Name, Registry.KnownVersions)
138+getDetails (Env maybeRoot _ _ registry) =
141+ Task.throw $ Exit.DiffNoOutline
144+ do details <- Task.eio Exit.DiffBadDetails $ Details.load Reporting.silent root
145+ case Details._outline details of
146+ Details.ValidApp _ ->
147+ Task.throw $ Exit.DiffApplication
149+ Details.ValidPkg pkg _ ->
150+ case Registry.getVersions pkg registry of
152+ return (details, pkg, vsns)
155+ Task.throw Exit.DiffUnpublished
162+writeDiff :: Docs.Documentation -> Docs.Documentation -> Task ()
163+writeDiff oldDocs newDocs =
165+ changes = DD.diff oldDocs newDocs
166+ localizer = L.fromNames (Map.union oldDocs newDocs)
168+ Task.io $ Help.toStdout $ toDoc localizer changes <> "\n"
175+toDoc :: L.Localizer -> PackageChanges -> D.Doc
176+toDoc localizer changes@(PackageChanges added changed removed) =
177+ if null added && Map.null changed && null removed then
178+ "No API changes detected, so this is a" <+> D.green "PATCH" <+> "change."
182+ D.fromChars (M.toChars (DD.toMagnitude changes))
185+ "This is a" <+> D.green magDoc <+> "change."
188+ if null added then [] else
189+ [ Chunk "ADDED MODULES" M.MINOR $
190+ D.vcat $ map D.fromName added
194+ if null removed then [] else
195+ [ Chunk "REMOVED MODULES" M.MAJOR $
196+ D.vcat $ map D.fromName removed
200+ addedChunk ++ removedChunk ++ map (changesToChunk localizer) (Map.toList changed)
202+ D.vcat (header : "" : map chunkToDoc chunks)
208+ , _magnitude :: M.Magnitude
213+chunkToDoc :: Chunk -> D.Doc
214+chunkToDoc (Chunk title magnitude details) =
217+ "----" <+> D.fromChars title <+> "-" <+> D.fromChars (M.toChars magnitude) <+> "----"
222+ , D.indent 4 details
228+changesToChunk :: L.Localizer -> (Name.Name, ModuleChanges) -> Chunk
229+changesToChunk localizer (name, changes@(ModuleChanges unions aliases values binops)) =
232+ DD.moduleChangeMagnitude changes
234+ (unionAdd, unionChange, unionRemove) =
235+ changesToDocTriple (unionToDoc localizer) unions
237+ (aliasAdd, aliasChange, aliasRemove) =
238+ changesToDocTriple (aliasToDoc localizer) aliases
240+ (valueAdd, valueChange, valueRemove) =
241+ changesToDocTriple (valueToDoc localizer) values
243+ (binopAdd, binopChange, binopRemove) =
244+ changesToDocTriple (binopToDoc localizer) binops
246+ Chunk (Name.toChars name) magnitude $
247+ D.vcat $ List.intersperse "" $ Maybe.catMaybes $
248+ [ changesToDoc "Added" unionAdd aliasAdd valueAdd binopAdd
249+ , changesToDoc "Removed" unionRemove aliasRemove valueRemove binopRemove
250+ , changesToDoc "Changed" unionChange aliasChange valueChange binopChange
254+changesToDocTriple :: (k -> v -> D.Doc) -> Changes k v -> ([D.Doc], [D.Doc], [D.Doc])
255+changesToDocTriple entryToDoc (Changes added changed removed) =
257+ indented (name, value) =
258+ D.indent 4 (entryToDoc name value)
260+ diffed (name, (oldValue, newValue)) =
262+ [ " - " <> entryToDoc name oldValue
263+ , " + " <> entryToDoc name newValue
267+ ( map indented (Map.toList added)
268+ , map diffed (Map.toList changed)
269+ , map indented (Map.toList removed)
273+changesToDoc :: String -> [D.Doc] -> [D.Doc] -> [D.Doc] -> [D.Doc] -> Maybe D.Doc
274+changesToDoc categoryName unions aliases values binops =
275+ if null unions && null aliases && null values && null binops then
280+ D.fromChars categoryName <> ":" : unions ++ aliases ++ binops ++ values
283+unionToDoc :: L.Localizer -> Name.Name -> Docs.Union -> D.Doc
284+unionToDoc localizer name (Docs.Union _ tvars ctors) =
287+ "type" <+> D.fromName name <+> D.hsep (map D.fromName tvars)
289+ ctorDoc (ctor, tipes) =
290+ typeDoc localizer (Type.Type ctor tipes)
292+ D.hang 4 (D.sep (setup : zipWith (<+>) ("=" : repeat "|") (map ctorDoc ctors)))
295+aliasToDoc :: L.Localizer -> Name.Name -> Docs.Alias -> D.Doc
296+aliasToDoc localizer name (Docs.Alias _ tvars tipe) =
299+ "type" <+> "alias" <+> D.hsep (map D.fromName (name:tvars)) <+> "="
301+ D.hang 4 (D.sep [ declaration, typeDoc localizer tipe ])
304+valueToDoc :: L.Localizer -> Name.Name -> Docs.Value -> D.Doc
305+valueToDoc localizer name (Docs.Value _ tipe) =
306+ D.hang 4 $ D.sep [ D.fromName name <+> ":", typeDoc localizer tipe ]
309+binopToDoc :: L.Localizer -> Name.Name -> Docs.Binop -> D.Doc
310+binopToDoc localizer name (Docs.Binop _ tipe associativity (Docs.Precedence n)) =
311+ "(" <> D.fromName name <> ")" <+> ":" <+> typeDoc localizer tipe <> D.black details
314+ " (" <> D.fromName assoc <> "/" <> D.fromInt n <> ")"
317+ case associativity of
320+ Docs.Right -> "right"
323+typeDoc :: L.Localizer -> Type.Type -> D.Doc
324+typeDoc localizer tipe =
325+ Type.toDoc localizer Type.None tipe