@@ -1,17 +1,325 @@
11{-# OPTIONS_GHC -Wall #-}
2−module Diff (run) where
2+{-# LANGUAGE OverloadedStrings #-}
3+module Diff
4+ ( Args(..)
5+ , run
6+ )
7+ where
38
49
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
14+
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
25+import qualified Http
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
632import qualified Reporting.Task as Task
7−import qualified Reporting.Progress.Terminal as Terminal
33+import qualified Stuff
834
935
1036
1137-- RUN
1238
1339
14−run :: Diff.Args -> () -> IO ()
40+data Args
41+ = CodeVsLatest
42+ | CodeVsExactly V.Version
43+ | LocalInquiry V.Version V.Version
44+ | GlobalInquiry Pkg.Name V.Version V.Version
45+
46+
47+run :: Args -> () -> IO ()
1548run args () =
16− do reporter <- Terminal.create
17− Task.run reporter $ Diff.diff args
49+ Reporting.attempt Exit.diffToReport $
50+ Task.run $
51+ do env <- getEnv
52+ diff env args
53+
54+
55+
56+-- ENVIRONMENT
57+
58+
59+data Env =
60+ Env
61+ { _root :: Maybe FilePath
62+ , _cache :: Stuff.PackageCache
63+ , _manager :: Http.Manager
64+ , _registry :: Registry.Registry
65+ }
66+
67+
68+getEnv :: Task Env
69+getEnv =
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)
75+
76+
77+
78+-- DIFF
79+
80+
81+type Task a =
82+ Task.Task Exit.Diff a
83+
84+
85+diff :: Env -> Args -> Task ()
86+diff env@(Env _ _ _ registry) args =
87+ case args of
88+ GlobalInquiry name v1 v2 ->
89+ case Registry.getVersions' name registry of
90+ Right vsns ->
91+ do oldDocs <- getDocs env name vsns (min v1 v2)
92+ newDocs <- getDocs env name vsns (max v1 v2)
93+ writeDiff oldDocs newDocs
94+
95+ Left suggestions ->
96+ Task.throw $ Exit.DiffUnknownPackage name suggestions
97+
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
103+
104+ CodeVsLatest ->
105+ do (details, name, vsns) <- getDetails env
106+ oldDocs <- getLatestDocs env name vsns
107+ newDocs <- Build.generateDocs details
108+ writeDiff oldDocs newDocs
109+
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
115+
116+
117+
118+-- GET DOCS
119+
120+
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)
126+
127+
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
131+
132+
133+
134+-- GET DETAILS
135+
136+
137+getDetails :: Env -> Task (Details.Details, Pkg.Name, Registry.KnownVersions)
138+getDetails (Env maybeRoot _ _ registry) =
139+ case maybeRoot of
140+ Nothing ->
141+ Task.throw $ Exit.DiffNoOutline
142+
143+ Just root ->
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
148+
149+ Details.ValidPkg pkg _ ->
150+ case Registry.getVersions pkg registry of
151+ Just vsns ->
152+ return (details, pkg, vsns)
153+
154+ Nothing ->
155+ Task.throw Exit.DiffUnpublished
156+
157+
158+
159+-- WRITE DOC
160+
161+
162+writeDiff :: Docs.Documentation -> Docs.Documentation -> Task ()
163+writeDiff oldDocs newDocs =
164+ let
165+ changes = DD.diff oldDocs newDocs
166+ localizer = L.fromNames (Map.union oldDocs newDocs)
167+ in
168+ Task.io $ Help.toStdout $ toDoc localizer changes <> "\n"
169+
170+
171+
172+-- TO DOC
173+
174+
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."
179+ else
180+ let
181+ magDoc =
182+ D.fromChars (M.toChars (DD.toMagnitude changes))
183+
184+ header =
185+ "This is a" <+> D.green magDoc <+> "change."
186+
187+ addedChunk =
188+ if null added then [] else
189+ [ Chunk "ADDED MODULES" M.MINOR $
190+ D.vcat $ map D.fromName added
191+ ]
192+
193+ removedChunk =
194+ if null removed then [] else
195+ [ Chunk "REMOVED MODULES" M.MAJOR $
196+ D.vcat $ map D.fromName removed
197+ ]
198+
199+ chunks =
200+ addedChunk ++ removedChunk ++ map (changesToChunk localizer) (Map.toList changed)
201+ in
202+ D.vcat (header : "" : map chunkToDoc chunks)
203+
204+
205+data Chunk =
206+ Chunk
207+ { _title :: String
208+ , _magnitude :: M.Magnitude
209+ , _details :: D.Doc
210+ }
211+
212+
213+chunkToDoc :: Chunk -> D.Doc
214+chunkToDoc (Chunk title magnitude details) =
215+ let
216+ header =
217+ "----" <+> D.fromChars title <+> "-" <+> D.fromChars (M.toChars magnitude) <+> "----"
218+ in
219+ D.vcat
220+ [ D.dullcyan header
221+ , ""
222+ , D.indent 4 details
223+ , ""
224+ , ""
225+ ]
226+
227+
228+changesToChunk :: L.Localizer -> (Name.Name, ModuleChanges) -> Chunk
229+changesToChunk localizer (name, changes@(ModuleChanges unions aliases values binops)) =
230+ let
231+ magnitude =
232+ DD.moduleChangeMagnitude changes
233+
234+ (unionAdd, unionChange, unionRemove) =
235+ changesToDocTriple (unionToDoc localizer) unions
236+
237+ (aliasAdd, aliasChange, aliasRemove) =
238+ changesToDocTriple (aliasToDoc localizer) aliases
239+
240+ (valueAdd, valueChange, valueRemove) =
241+ changesToDocTriple (valueToDoc localizer) values
242+
243+ (binopAdd, binopChange, binopRemove) =
244+ changesToDocTriple (binopToDoc localizer) binops
245+ in
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
251+ ]
252+
253+
254+changesToDocTriple :: (k -> v -> D.Doc) -> Changes k v -> ([D.Doc], [D.Doc], [D.Doc])
255+changesToDocTriple entryToDoc (Changes added changed removed) =
256+ let
257+ indented (name, value) =
258+ D.indent 4 (entryToDoc name value)
259+
260+ diffed (name, (oldValue, newValue)) =
261+ D.vcat
262+ [ " - " <> entryToDoc name oldValue
263+ , " + " <> entryToDoc name newValue
264+ , ""
265+ ]
266+ in
267+ ( map indented (Map.toList added)
268+ , map diffed (Map.toList changed)
269+ , map indented (Map.toList removed)
270+ )
271+
272+
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
276+ Nothing
277+
278+ else
279+ Just $ D.vcat $
280+ D.fromChars categoryName <> ":" : unions ++ aliases ++ binops ++ values
281+
282+
283+unionToDoc :: L.Localizer -> Name.Name -> Docs.Union -> D.Doc
284+unionToDoc localizer name (Docs.Union _ tvars ctors) =
285+ let
286+ setup =
287+ "type" <+> D.fromName name <+> D.hsep (map D.fromName tvars)
288+
289+ ctorDoc (ctor, tipes) =
290+ typeDoc localizer (Type.Type ctor tipes)
291+ in
292+ D.hang 4 (D.sep (setup : zipWith (<+>) ("=" : repeat "|") (map ctorDoc ctors)))
293+
294+
295+aliasToDoc :: L.Localizer -> Name.Name -> Docs.Alias -> D.Doc
296+aliasToDoc localizer name (Docs.Alias _ tvars tipe) =
297+ let
298+ declaration =
299+ "type" <+> "alias" <+> D.hsep (map D.fromName (name:tvars)) <+> "="
300+ in
301+ D.hang 4 (D.sep [ declaration, typeDoc localizer tipe ])
302+
303+
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 ]
307+
308+
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
312+ where
313+ details =
314+ " (" <> D.fromName assoc <> "/" <> D.fromInt n <> ")"
315+
316+ assoc =
317+ case associativity of
318+ Docs.Left -> "left"
319+ Docs.Non -> "non"
320+ Docs.Right -> "right"
321+
322+
323+typeDoc :: L.Localizer -> Type.Type -> D.Doc
324+typeDoc localizer tipe =
325+ Type.toDoc localizer Type.None tipe