2017-01-23 20:16:59 +03:00
|
|
|
{-# LANGUAGE ScopedTypeVariables #-}
|
2017-02-16 03:13:34 +03:00
|
|
|
module Renderer.TOC (toc, diffTOC, JSONSummary(..), Summarizable(..), isErrorSummary) where
|
2017-01-23 19:22:51 +03:00
|
|
|
|
2017-01-23 22:12:05 +03:00
|
|
|
import Category as C
|
|
|
|
import Data.Aeson
|
|
|
|
import Data.Functor.Both hiding (fst, snd)
|
2017-01-24 00:59:47 +03:00
|
|
|
import qualified Data.Functor.Both as Both
|
2017-02-14 03:15:38 +03:00
|
|
|
import Data.Text (toLower)
|
2017-01-23 19:22:51 +03:00
|
|
|
import Data.Record
|
2017-01-23 22:12:05 +03:00
|
|
|
import Diff
|
2017-01-23 19:22:51 +03:00
|
|
|
import Info
|
2017-01-23 22:12:05 +03:00
|
|
|
import Prologue
|
2017-04-04 00:15:58 +03:00
|
|
|
import Renderer.Summary (Summaries(..))
|
2017-01-23 22:12:05 +03:00
|
|
|
import qualified Data.List as List
|
|
|
|
import qualified Data.Map as Map hiding (null)
|
2017-02-10 01:10:16 +03:00
|
|
|
import Source hiding (null)
|
2017-01-23 22:12:05 +03:00
|
|
|
import Syntax as S
|
|
|
|
import Term
|
2017-01-24 01:26:43 +03:00
|
|
|
import Patch
|
2017-01-23 20:16:59 +03:00
|
|
|
|
2017-01-24 22:16:22 +03:00
|
|
|
data JSONSummary = JSONSummary { info :: Summarizable }
|
2017-01-23 23:58:20 +03:00
|
|
|
| ErrorSummary { error :: Text, errorSpan :: SourceSpan }
|
2017-01-23 22:12:05 +03:00
|
|
|
deriving (Generic, Eq, Show)
|
|
|
|
|
2017-01-24 00:33:07 +03:00
|
|
|
instance ToJSON JSONSummary where
|
2017-01-24 22:16:22 +03:00
|
|
|
toJSON JSONSummary{..} = object $ case info of
|
2017-02-18 01:57:29 +03:00
|
|
|
InSummarizable{..} -> [ "changeType" .= ("modified" :: Text), "category" .= toCategoryName parentCategory, "term" .= parentTermName, "span" .= parentSourceSpan ]
|
|
|
|
Summarizable{..} -> [ "changeType" .= summarizableChangeType, "category" .= toCategoryName summarizableCategory, "term" .= summarizableTermName, "span" .= summarizableSourceSpan ]
|
2017-01-24 22:16:22 +03:00
|
|
|
NotSummarizable -> panic "NotSummarizable should have been pruned"
|
2017-01-23 23:58:20 +03:00
|
|
|
toJSON ErrorSummary{..} = object [ "error" .= error, "span" .= errorSpan ]
|
2017-01-23 22:12:05 +03:00
|
|
|
|
2017-01-24 00:33:07 +03:00
|
|
|
isErrorSummary :: JSONSummary -> Bool
|
2017-01-23 22:12:05 +03:00
|
|
|
isErrorSummary ErrorSummary{} = True
|
|
|
|
isErrorSummary _ = False
|
2017-01-23 20:16:59 +03:00
|
|
|
|
2017-01-24 01:26:43 +03:00
|
|
|
data DiffInfo = LeafInfo { leafCategory :: Category, termName :: Text, leafSourceSpan :: SourceSpan }
|
2017-01-23 20:16:59 +03:00
|
|
|
| BranchInfo { branches :: [ DiffInfo ], branchCategory :: Category }
|
2017-01-23 23:58:20 +03:00
|
|
|
| ErrorInfo { infoSpan :: SourceSpan, termName :: Text }
|
2017-01-23 20:16:59 +03:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
|
|
data TOCSummary a = TOCSummary {
|
2017-01-25 21:45:56 +03:00
|
|
|
summaryPatch :: Patch a,
|
2017-01-24 22:16:22 +03:00
|
|
|
parentInfo :: Summarizable
|
|
|
|
} deriving (Eq, Functor, Show, Generic)
|
2017-01-23 20:16:59 +03:00
|
|
|
|
2017-01-24 22:16:22 +03:00
|
|
|
data Summarizable = Summarizable { summarizableCategory :: Category, summarizableTermName :: Text, summarizableSourceSpan :: SourceSpan, summarizableChangeType :: Text }
|
|
|
|
| InSummarizable { parentCategory :: Category, parentTermName :: Text, parentSourceSpan :: SourceSpan }
|
|
|
|
| NotSummarizable
|
2017-01-24 00:33:07 +03:00
|
|
|
deriving (Eq, Show)
|
|
|
|
|
2017-04-04 00:15:58 +03:00
|
|
|
toc :: HasDefaultFields fields => Both SourceBlob -> Diff (Syntax Text) (Record fields) -> Summaries
|
|
|
|
toc blobs diff = Summaries changes errors
|
2017-01-23 22:12:05 +03:00
|
|
|
where
|
|
|
|
changes = if null changes' then mempty else Map.singleton summaryKey (toJSON <$> changes')
|
|
|
|
errors = if null errors' then mempty else Map.singleton summaryKey (toJSON <$> errors')
|
|
|
|
(errors', changes') = List.partition isErrorSummary summaries
|
|
|
|
summaryKey = toSummaryKey (path <$> blobs)
|
|
|
|
summaries = diffTOC blobs diff
|
2017-01-23 20:16:59 +03:00
|
|
|
|
2017-03-31 23:48:06 +03:00
|
|
|
-- Returns a key representing the filename. If the filenames are different,
|
|
|
|
-- return 'before -> after'.
|
|
|
|
toSummaryKey :: Both FilePath -> Text
|
|
|
|
toSummaryKey = runBothWith $ \before after ->
|
|
|
|
toS $ case (before, after) of
|
|
|
|
("", after) -> after
|
|
|
|
(before, "") -> before
|
|
|
|
(before, after) | before == after -> after
|
|
|
|
(before, after) | not (null before) && not (null after) -> before <> " -> " <> after
|
|
|
|
(_, _) -> mempty
|
|
|
|
|
2017-03-31 22:18:27 +03:00
|
|
|
diffTOC :: (StringConv leaf Text, HasDefaultFields fields) => Both SourceBlob -> SyntaxDiff leaf fields -> [JSONSummary]
|
2017-02-14 04:30:33 +03:00
|
|
|
diffTOC blobs diff = removeDupes (diffToTOCSummaries (source <$> blobs) diff) >>= toJSONSummaries
|
2017-01-23 22:12:05 +03:00
|
|
|
where
|
2017-01-24 20:13:59 +03:00
|
|
|
removeDupes :: [TOCSummary DiffInfo] -> [TOCSummary DiffInfo]
|
2017-02-14 22:29:24 +03:00
|
|
|
removeDupes = foldl' go []
|
|
|
|
where
|
|
|
|
go xs x | (_, _ : _) <- find exactMatch x xs = xs
|
|
|
|
| (front, existingItem : back) <- find similarMatch x xs =
|
|
|
|
let
|
|
|
|
(Summarizable category name sourceSpan _) = parentInfo existingItem
|
|
|
|
replacement = x { parentInfo = Summarizable category name sourceSpan "modified" }
|
|
|
|
in
|
|
|
|
front <> (replacement : back)
|
|
|
|
| otherwise = xs <> [x]
|
|
|
|
find p x = List.break (p x)
|
|
|
|
exactMatch a b = parentInfo a == parentInfo b
|
|
|
|
similarMatch a b = case (parentInfo a, parentInfo b) of
|
|
|
|
(Summarizable catA nameA _ _, Summarizable catB nameB _ _) -> catA == catB && toLower nameA == toLower nameB
|
|
|
|
(_, _) -> False
|
2017-01-24 20:13:59 +03:00
|
|
|
|
2017-03-31 22:18:27 +03:00
|
|
|
diffToTOCSummaries :: (StringConv leaf Text, HasDefaultFields fields) => Both Source -> SyntaxDiff leaf fields -> [TOCSummary DiffInfo]
|
2017-05-08 18:16:37 +03:00
|
|
|
diffToTOCSummaries sources = para $ \diff -> case first (toTOCSummaries . runBothWith mapPatch toInfo) diff of
|
|
|
|
Free (annotations :< syntax) -> toList diff >>= \ summaries ->
|
|
|
|
fmap (contextualize (Both.snd sources) (Both.snd annotations :< fmap fst syntax)) (snd summaries)
|
2017-05-05 22:56:15 +03:00
|
|
|
Pure summaries -> summaries
|
2017-05-05 23:00:52 +03:00
|
|
|
where toInfo = termToDiffInfo <$> sources
|
2017-01-24 22:16:22 +03:00
|
|
|
|
2017-05-08 18:16:37 +03:00
|
|
|
contextualize source (annotation :< syntax) summary
|
|
|
|
| NotSummarizable <- parentInfo summary
|
|
|
|
, isSummarizable syntax
|
2017-05-08 18:18:23 +03:00
|
|
|
, Just terms <- traverse afterTerm syntax = summary { parentInfo = InSummarizable (category annotation) (toTermName source (cofree (annotation :< terms))) (sourceSpan annotation) }
|
2017-05-08 18:16:37 +03:00
|
|
|
| otherwise = summary
|
|
|
|
|
|
|
|
isSummarizable S.Method{} = True
|
|
|
|
isSummarizable S.Function{} = True
|
|
|
|
isSummarizable _ = False
|
|
|
|
|
2017-01-24 22:16:22 +03:00
|
|
|
-- Mark which leaves are summarizable.
|
|
|
|
toTOCSummaries :: Patch DiffInfo -> [TOCSummary DiffInfo]
|
2017-05-05 22:33:16 +03:00
|
|
|
toTOCSummaries patch = case afterOrBefore patch of
|
|
|
|
ErrorInfo{..} -> pure $ TOCSummary patch NotSummarizable
|
|
|
|
BranchInfo{..} -> flattenPatch patch >>= toTOCSummaries
|
|
|
|
LeafInfo{..} -> pure . TOCSummary patch $ case leafCategory of
|
|
|
|
C.Function -> Summarizable leafCategory termName leafSourceSpan (patchType patch)
|
|
|
|
C.Method -> Summarizable leafCategory termName leafSourceSpan (patchType patch)
|
|
|
|
C.SingletonMethod -> Summarizable leafCategory termName leafSourceSpan (patchType patch)
|
|
|
|
_ -> NotSummarizable
|
2017-01-24 22:16:22 +03:00
|
|
|
|
2017-01-26 23:07:18 +03:00
|
|
|
flattenPatch :: Patch DiffInfo -> [Patch DiffInfo]
|
2017-05-05 22:48:54 +03:00
|
|
|
flattenPatch patch = case toLeafInfos <$> patch of
|
|
|
|
Replace i1 i2 -> zipWith Replace i1 i2
|
|
|
|
Insert info -> Insert <$> info
|
|
|
|
Delete info -> Delete <$> info
|
2017-01-26 23:07:18 +03:00
|
|
|
|
2017-05-05 22:45:51 +03:00
|
|
|
toLeafInfos :: DiffInfo -> [DiffInfo]
|
|
|
|
toLeafInfos BranchInfo{..} = branches >>= toLeafInfos
|
|
|
|
toLeafInfos leaf = [leaf]
|
2017-01-26 23:07:18 +03:00
|
|
|
|
2017-01-24 22:16:22 +03:00
|
|
|
toJSONSummaries :: TOCSummary DiffInfo -> [JSONSummary]
|
2017-05-05 22:26:26 +03:00
|
|
|
toJSONSummaries TOCSummary{..} = toJSONSummaries' (afterOrBefore summaryPatch)
|
2017-01-24 22:16:22 +03:00
|
|
|
where
|
2017-03-28 22:32:45 +03:00
|
|
|
toJSONSummaries' diffInfo = case diffInfo of
|
2017-01-24 22:16:22 +03:00
|
|
|
ErrorInfo{..} -> pure $ ErrorSummary termName infoSpan
|
|
|
|
BranchInfo{..} -> branches >>= toJSONSummaries'
|
2017-01-24 22:25:44 +03:00
|
|
|
LeafInfo{..} -> case parentInfo of
|
|
|
|
NotSummarizable -> []
|
|
|
|
_ -> pure $ JSONSummary parentInfo
|
2017-01-23 22:12:05 +03:00
|
|
|
|
2017-03-31 22:18:27 +03:00
|
|
|
termToDiffInfo :: forall leaf fields. (StringConv leaf Text, HasDefaultFields fields) => Source -> SyntaxTerm leaf fields -> DiffInfo
|
2017-05-08 18:18:23 +03:00
|
|
|
termToDiffInfo source = para $ \ (annotation :< syntax) -> let termName = toTermName source (cofree (annotation :< (fst <$> syntax))) in case syntax of
|
2017-05-05 23:28:46 +03:00
|
|
|
S.Indexed children -> BranchInfo (snd <$> children) (category annotation)
|
|
|
|
S.Fixed children -> BranchInfo (snd <$> children) (category annotation)
|
|
|
|
S.AnonymousFunction _ _ -> LeafInfo C.AnonymousFunction termName (sourceSpan annotation)
|
|
|
|
S.Commented cs leaf -> BranchInfo (snd <$> (cs <> maybeToList leaf)) (category annotation)
|
|
|
|
S.ParseError _ -> ErrorInfo (sourceSpan annotation) termName
|
|
|
|
_ -> LeafInfo (category annotation) termName (sourceSpan annotation)
|
2017-01-23 19:22:51 +03:00
|
|
|
|
2017-05-08 18:18:23 +03:00
|
|
|
toTermName :: forall leaf fields. HasDefaultFields fields => Source -> SyntaxTerm leaf fields -> Text
|
2017-05-08 18:22:04 +03:00
|
|
|
toTermName source = para $ \ (annotation :< syntax) -> case syntax of
|
|
|
|
S.Function (_, identifier) _ _ -> identifier
|
|
|
|
S.Method _ (_, identifier) Nothing _ _ -> identifier
|
2017-05-08 18:24:25 +03:00
|
|
|
S.Method _ (_, identifier) (Just (receiver, receiverSource)) _ _
|
|
|
|
| S.Indexed [receiverParams] <- unwrap receiver
|
|
|
|
, S.ParameterDecl (Just ty) _ <- unwrap receiverParams -> "(" <> toTermName source ty <> ") " <> identifier
|
|
|
|
| otherwise -> receiverSource <> "." <> identifier
|
2017-05-08 18:22:04 +03:00
|
|
|
_ -> toText (Source.slice (byteRange annotation) source)
|
2017-02-18 01:57:29 +03:00
|
|
|
|
|
|
|
-- The user-facing category name
|
|
|
|
toCategoryName :: Category -> Text
|
2017-03-28 22:32:45 +03:00
|
|
|
toCategoryName category = case category of
|
2017-02-23 07:05:20 +03:00
|
|
|
C.SingletonMethod -> "Method"
|
2017-02-18 01:57:29 +03:00
|
|
|
c -> show c
|