packages feed

elm-package-0.2: src/Diff/Display.hs

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)