module Diff.Display (packageChanges) where import Data.Char (isDigit) import qualified Data.Map as Map import Text.PrettyPrint ((<+>), (<>)) import qualified Text.PrettyPrint as P import qualified Elm.Compiler.Type as Type import qualified Diff.Compare as D packageChanges :: D.PackageChanges -> String packageChanges pkgChanges@(D.PackageChanges added changed removed) = "This is a " ++ show (D.packageChangeMagnitude pkgChanges) ++ " change.\n\n" ++ showAdded ++ showRemoved ++ showChanged where showRemoved | null removed = "" | otherwise = "------ Removed modules - MAJOR ------\n" ++ concatMap ("\n " ++) removed ++ "\n\n\n" showAdded | null added = "" | otherwise = "------ Added modules - MINOR ------\n" ++ concatMap ("\n " ++) added ++ "\n\n\n" showChanged | Map.null changed = "" | otherwise = concatMap moduleChanges (Map.toList changed) moduleChanges :: (String, D.ModuleChanges) -> String moduleChanges (name, changes) = "------ Changes to module " ++ name ++ " - " ++ show magnitude ++ " ------" ++ display "Added" adtAdd aliasAdd valueAdd ++ display "Removed" adtRemove aliasRemove valueRemove ++ display "Changed" adtChange aliasChange valueChange ++ "\n\n\n" where magnitude = D.moduleChangeMagnitude changes (adtAdd, adtChange, adtRemove) = changesToDocs adtDoc (D.adtChanges changes) (aliasAdd, aliasChange, aliasRemove) = changesToDocs aliasDoc (D.aliasChanges changes) (valueAdd, valueChange, valueRemove) = changesToDocs valueDoc (D.valueChanges changes) changesToDocs :: (k -> v -> P.Doc) -> D.Changes k v -> ([P.Doc], [P.Doc], [P.Doc]) changesToDocs toDoc (D.Changes added changed removed) = ( map indented (Map.toList added) , map diffed (Map.toList changed) , map indented (Map.toList removed) ) where indented (name, value) = P.text " " <> toDoc name value diffed (name, (oldValue, newValue)) = P.vcat [ P.text " - " <> toDoc name oldValue , P.text " + " <> toDoc name newValue , P.text "" ] display :: String -> [P.Doc] -> [P.Doc] -> [P.Doc] -> String display categoryName adts aliases values | null (adts ++ aliases ++ values) = "" | otherwise = P.renderStyle (P.style { P.lineLength = 80 }) $ P.vcat $ P.text "" : P.text category : adts ++ aliases ++ values where category = "\n " ++ categoryName ++ ":" -- PRETTY PRINTING adtDoc :: String -> ([String], Map.Map String [Type.Type]) -> P.Doc adtDoc name (tvars, ctors) = P.hang setup 4 (P.sep (zipWith (<+>) separators ctorDocs)) where setup = P.text "type" <+> P.text name <+> P.hsep (map P.text tvars) separators = map P.text ("=" : repeat "|") ctorDocs = map ctorDoc (Map.toList ctors) ctorDoc (ctor, tipes) = P.hsep (P.text ctor : map parenDoc tipes) aliasDoc :: String -> ([String], Type.Type) -> P.Doc aliasDoc name (tvars, tipe) = P.hang (setup <+> P.equals) 4 (typeDoc tipe) where setup = P.text "type" <+> P.text "alias" <+> P.text name <+> P.hsep (map P.text tvars) valueDoc :: String -> Type.Type -> P.Doc valueDoc name tipe = P.text name <+> P.colon <+> typeDoc tipe parenDoc = generalTypeDoc True typeDoc = generalTypeDoc False generalTypeDoc :: Bool -> Type.Type -> P.Doc generalTypeDoc parens tipe = case tipe of Type.Var x -> P.text x Type.Type name -> P.text name Type.Lambda t t' -> let (args, result) = collectLambdas [t] t' in (if parens then P.parens else id) $ foldr arrow (typeDoc result) args Type.App t ts -> case t : ts of [ Type.Type name, tipe ] | name == "_List" -> P.lbrack <> typeDoc tipe <> P.rbrack Type.Type name : types | take 6 name == "_Tuple" && all isDigit (drop 6 name) -> P.parens (P.hsep (P.punctuate P.comma (map typeDoc types))) types -> (if parens then P.parens else id) $ P.hsep (map parenDoc types) Type.Record fields maybeExt -> P.sep [ P.hang start 2 fieldDocs, P.rbrace ] where start = case maybeExt of Nothing -> P.lbrace Just ext -> P.lbrace <+> typeDoc ext <+> P.text "|" fieldDocs = P.sep (P.punctuate P.comma (map fieldDoc fields)) fieldDoc (name, tipe) = P.text name <+> P.colon <+> typeDoc tipe arrow :: Type.Type -> P.Doc -> P.Doc arrow arg result = argDoc <+> P.text "->" <+> result where argDoc = case arg of Type.Lambda _ _ -> P.parens (typeDoc arg) _ -> typeDoc arg collectLambdas :: [Type.Type] -> Type.Type -> ([Type.Type], Type.Type) collectLambdas args result = case result of Type.Lambda t t' -> collectLambdas (t:args) t' _ -> (reverse args, result)