2017-06-08 18:42:57 +03:00
|
|
|
|
{-# LANGUAGE DeriveAnyClass, MultiParamTypeClasses, RankNTypes, TypeOperators #-}
|
2017-05-10 18:56:06 +03:00
|
|
|
|
module Renderer.TOC
|
2017-06-16 19:03:39 +03:00
|
|
|
|
( renderToCDiff
|
2017-06-16 19:26:14 +03:00
|
|
|
|
, renderToCTerm
|
2017-05-10 18:56:06 +03:00
|
|
|
|
, diffTOC
|
2017-05-17 19:32:17 +03:00
|
|
|
|
, Summaries(..)
|
2017-05-10 18:56:06 +03:00
|
|
|
|
, JSONSummary(..)
|
2017-05-10 22:55:10 +03:00
|
|
|
|
, isValidSummary
|
2017-05-10 18:57:28 +03:00
|
|
|
|
, Declaration(..)
|
2017-05-10 18:58:08 +03:00
|
|
|
|
, declaration
|
2017-06-05 17:59:22 +03:00
|
|
|
|
, declarationAlgebra
|
2017-06-08 19:42:39 +03:00
|
|
|
|
, markupSectionAlgebra
|
2017-06-05 17:44:09 +03:00
|
|
|
|
, syntaxDeclarationAlgebra
|
2017-05-10 18:57:10 +03:00
|
|
|
|
, Entry(..)
|
2017-05-10 18:56:06 +03:00
|
|
|
|
, tableOfContentsBy
|
2017-05-11 22:36:25 +03:00
|
|
|
|
, dedupe
|
2017-05-10 20:18:47 +03:00
|
|
|
|
, entrySummary
|
2017-05-10 18:56:06 +03:00
|
|
|
|
) where
|
2017-01-23 19:22:51 +03:00
|
|
|
|
|
2017-01-23 22:12:05 +03:00
|
|
|
|
import Data.Aeson
|
2017-05-11 21:22:39 +03:00
|
|
|
|
import Data.Align (crosswalk)
|
2017-06-24 17:09:50 +03:00
|
|
|
|
import Data.Blob
|
2017-01-23 22:12:05 +03:00
|
|
|
|
import Data.Functor.Both hiding (fst, snd)
|
2017-01-24 00:59:47 +03:00
|
|
|
|
import qualified Data.Functor.Both as Both
|
2017-05-11 22:48:27 +03:00
|
|
|
|
import Data.Functor.Listable
|
2017-06-16 17:59:23 +03:00
|
|
|
|
import Data.List.NonEmpty (nonEmpty)
|
2017-06-08 18:42:57 +03:00
|
|
|
|
import Data.Record
|
2017-06-24 17:28:39 +03:00
|
|
|
|
import Data.Source as Source
|
2017-02-14 03:15:38 +03:00
|
|
|
|
import Data.Text (toLower)
|
2017-07-10 21:15:03 +03:00
|
|
|
|
import qualified Data.Text as T
|
2017-05-11 22:48:27 +03:00
|
|
|
|
import Data.Text.Listable
|
2017-05-11 23:33:52 +03:00
|
|
|
|
import Data.These
|
2017-06-08 18:42:57 +03:00
|
|
|
|
import Data.Union
|
2017-01-23 22:12:05 +03:00
|
|
|
|
import Diff
|
2017-01-23 19:22:51 +03:00
|
|
|
|
import Info
|
2017-07-19 01:58:37 +03:00
|
|
|
|
import Language
|
2017-05-11 21:22:39 +03:00
|
|
|
|
import Patch
|
2017-01-23 22:12:05 +03:00
|
|
|
|
import Prologue
|
|
|
|
|
import qualified Data.List as List
|
|
|
|
|
import qualified Data.Map as Map hiding (null)
|
|
|
|
|
import Syntax as S
|
2017-06-05 18:04:52 +03:00
|
|
|
|
import Data.Syntax.Algebra (RAlgebra)
|
2017-06-05 17:59:22 +03:00
|
|
|
|
import qualified Data.Syntax as Syntax
|
|
|
|
|
import qualified Data.Syntax.Declaration as Declaration
|
2017-06-08 19:42:39 +03:00
|
|
|
|
import qualified Data.Syntax.Markup as Markup
|
2017-01-23 22:12:05 +03:00
|
|
|
|
import Term
|
2017-01-23 20:16:59 +03:00
|
|
|
|
|
2017-07-19 02:49:39 +03:00
|
|
|
|
data Summaries = Summaries { changes, errors :: !(Map Text [Value]) }
|
2017-05-30 17:25:45 +03:00
|
|
|
|
deriving (Eq, Show)
|
2017-05-17 19:32:17 +03:00
|
|
|
|
|
|
|
|
|
instance Monoid Summaries where
|
2017-07-19 02:49:39 +03:00
|
|
|
|
mempty = Summaries mempty mempty
|
|
|
|
|
mappend (Summaries c1 e1) (Summaries c2 e2) = Summaries (Map.unionWith (<>) c1 c2) (Map.unionWith (<>) e1 e2)
|
2017-05-17 19:32:17 +03:00
|
|
|
|
|
|
|
|
|
instance StringConv Summaries ByteString where
|
|
|
|
|
strConv _ = toS . (<> "\n") . encode
|
|
|
|
|
|
|
|
|
|
instance ToJSON Summaries where
|
2017-07-19 02:49:39 +03:00
|
|
|
|
toJSON Summaries{..} = object [ "changes" .= changes, "errors" .= errors ]
|
2017-05-17 19:32:17 +03:00
|
|
|
|
|
2017-06-05 18:28:30 +03:00
|
|
|
|
data JSONSummary
|
2017-06-05 18:31:37 +03:00
|
|
|
|
= JSONSummary
|
2017-06-05 19:45:18 +03:00
|
|
|
|
{ summaryCategoryName :: Text
|
2017-06-05 18:31:37 +03:00
|
|
|
|
, summaryTermName :: Text
|
2017-06-24 16:30:34 +03:00
|
|
|
|
, summarySpan :: Span
|
2017-06-05 18:31:37 +03:00
|
|
|
|
, summaryChangeType :: Text
|
|
|
|
|
}
|
2017-07-19 03:18:09 +03:00
|
|
|
|
| ErrorSummary { error :: Text, errorSpan :: Span, errorLanguage :: Maybe Language }
|
2017-06-05 18:28:30 +03:00
|
|
|
|
deriving (Generic, Eq, Show)
|
2017-01-23 22:12:05 +03:00
|
|
|
|
|
2017-01-24 00:33:07 +03:00
|
|
|
|
instance ToJSON JSONSummary where
|
2017-07-19 02:49:39 +03:00
|
|
|
|
toJSON JSONSummary{..} = object [ "changeType" .= summaryChangeType, "category" .= summaryCategoryName, "term" .= summaryTermName, "span" .= summarySpan ]
|
2017-07-19 01:58:37 +03:00
|
|
|
|
toJSON ErrorSummary{..} = object [ "error" .= error, "span" .= errorSpan, "language" .= errorLanguage ]
|
2017-01-23 22:12:05 +03:00
|
|
|
|
|
2017-05-10 22:55:10 +03:00
|
|
|
|
isValidSummary :: JSONSummary -> Bool
|
|
|
|
|
isValidSummary ErrorSummary{} = False
|
|
|
|
|
isValidSummary _ = True
|
2017-01-23 20:16:59 +03:00
|
|
|
|
|
2017-05-10 18:57:28 +03:00
|
|
|
|
-- | A declaration’s identifier and type.
|
2017-05-10 19:39:12 +03:00
|
|
|
|
data Declaration
|
2017-05-10 19:40:07 +03:00
|
|
|
|
= MethodDeclaration { declarationIdentifier :: Text }
|
2017-05-10 19:39:12 +03:00
|
|
|
|
| FunctionDeclaration { declarationIdentifier :: Text }
|
2017-07-11 19:37:24 +03:00
|
|
|
|
| SectionDeclaration { declarationIdentifier :: Text, declarationLevel :: Int }
|
2017-07-20 00:17:39 +03:00
|
|
|
|
| ErrorDeclaration { declarationIdentifier :: Text, declarationLanguage :: Maybe Language }
|
2017-05-10 22:11:59 +03:00
|
|
|
|
deriving (Eq, Generic, NFData, Show)
|
2017-05-10 18:57:28 +03:00
|
|
|
|
|
2017-06-05 18:53:49 +03:00
|
|
|
|
getDeclaration :: HasField fields (Maybe Declaration) => Record fields -> Maybe Declaration
|
|
|
|
|
getDeclaration = getField
|
|
|
|
|
|
2017-05-10 19:01:26 +03:00
|
|
|
|
-- | Produce the annotations of nodes representing declarations.
|
2017-06-06 00:02:39 +03:00
|
|
|
|
declaration :: HasField fields (Maybe Declaration) => TermF f (Record fields) a -> Maybe (Record fields)
|
2017-06-05 17:51:31 +03:00
|
|
|
|
declaration (annotation :< _) = annotation <$ (getField annotation :: Maybe Declaration)
|
2017-05-10 18:58:08 +03:00
|
|
|
|
|
|
|
|
|
|
2017-06-05 17:44:09 +03:00
|
|
|
|
-- | Compute 'Declaration's for methods and functions in 'Syntax'.
|
2017-07-20 00:16:51 +03:00
|
|
|
|
syntaxDeclarationAlgebra :: HasField fields Range => Blob -> RAlgebra (SyntaxTermF Text fields) (SyntaxTerm Text fields) (Maybe Declaration)
|
|
|
|
|
syntaxDeclarationAlgebra Blob{..} r = case tailF r of
|
2017-05-10 19:39:12 +03:00
|
|
|
|
S.Function (identifier, _) _ _ -> Just $ FunctionDeclaration (getSource identifier)
|
|
|
|
|
S.Method _ (identifier, _) Nothing _ _ -> Just $ MethodDeclaration (getSource identifier)
|
2017-05-10 19:09:43 +03:00
|
|
|
|
S.Method _ (identifier, _) (Just (receiver, _)) _ _
|
|
|
|
|
| S.Indexed [receiverParams] <- unwrap receiver
|
2017-05-10 19:39:12 +03:00
|
|
|
|
, S.ParameterDecl (Just ty) _ <- unwrap receiverParams -> Just $ MethodDeclaration ("(" <> getSource ty <> ") " <> getSource identifier)
|
|
|
|
|
| otherwise -> Just $ MethodDeclaration (getSource receiver <> "." <> getSource identifier)
|
2017-07-20 00:17:39 +03:00
|
|
|
|
S.ParseError{} -> Just $ ErrorDeclaration (toText (Source.slice (byteRange (headF r)) blobSource)) blobLanguage
|
2017-05-10 19:09:43 +03:00
|
|
|
|
_ -> Nothing
|
2017-07-20 00:16:51 +03:00
|
|
|
|
where getSource = toText . flip Source.slice blobSource . byteRange . extract
|
2017-05-10 19:09:43 +03:00
|
|
|
|
|
2017-06-05 17:59:22 +03:00
|
|
|
|
-- | Compute 'Declaration's for methods and functions.
|
2017-07-12 23:20:24 +03:00
|
|
|
|
declarationAlgebra :: (Declaration.Function :< fs, Declaration.Method :< fs, Syntax.Error :< fs, Functor (Union fs), HasField fields Range)
|
2017-07-20 00:16:51 +03:00
|
|
|
|
=> Blob
|
2017-06-05 18:04:52 +03:00
|
|
|
|
-> RAlgebra (TermF (Union fs) (Record fields)) (Term (Union fs) (Record fields)) (Maybe Declaration)
|
2017-07-20 00:16:51 +03:00
|
|
|
|
declarationAlgebra Blob{..} r
|
2017-07-12 23:20:24 +03:00
|
|
|
|
| Just (Declaration.Function (identifier, _) _ _) <- prj (tailF r) = Just $ FunctionDeclaration (getSource (extract identifier))
|
2017-07-13 17:57:54 +03:00
|
|
|
|
| Just (Declaration.Method _ (identifier, _) _ _) <- prj (tailF r) = Just $ MethodDeclaration (getSource (extract identifier))
|
2017-07-20 00:17:39 +03:00
|
|
|
|
| Just Syntax.Error{} <- prj (tailF r) = Just $ ErrorDeclaration (getSource (headF r)) blobLanguage
|
2017-06-05 17:59:22 +03:00
|
|
|
|
| otherwise = Nothing
|
2017-07-20 00:16:51 +03:00
|
|
|
|
where getSource = toText . flip Source.slice blobSource . byteRange
|
2017-06-05 17:59:22 +03:00
|
|
|
|
|
2017-06-08 19:42:39 +03:00
|
|
|
|
-- | Compute 'Declaration's with the headings of 'Markup.Section's.
|
2017-07-12 23:20:24 +03:00
|
|
|
|
markupSectionAlgebra :: (Markup.Section :< fs, Syntax.Error :< fs, HasField fields Range, Functor (Union fs), Foldable (Union fs))
|
2017-07-20 00:16:51 +03:00
|
|
|
|
=> Blob
|
2017-06-08 19:42:39 +03:00
|
|
|
|
-> RAlgebra (TermF (Union fs) (Record fields)) (Term (Union fs) (Record fields)) (Maybe Declaration)
|
2017-07-20 00:16:51 +03:00
|
|
|
|
markupSectionAlgebra Blob{..} r
|
|
|
|
|
| Just (Markup.Section level (heading, _) _) <- prj (tailF r) = Just $ SectionDeclaration (maybe (getSource (extract heading)) (firstLine . toText . flip Source.slice blobSource . sconcat) (nonEmpty (byteRange . extract <$> toList (unwrap heading)))) level
|
2017-07-20 00:17:39 +03:00
|
|
|
|
| Just Syntax.Error{} <- prj (tailF r) = Just $ ErrorDeclaration (getSource (headF r)) blobLanguage
|
2017-06-08 19:42:39 +03:00
|
|
|
|
| otherwise = Nothing
|
2017-07-20 00:16:51 +03:00
|
|
|
|
where getSource = firstLine . toText . flip Source.slice blobSource . byteRange
|
2017-07-11 19:37:24 +03:00
|
|
|
|
firstLine = T.takeWhile (/= '\n')
|
2017-06-08 19:42:39 +03:00
|
|
|
|
|
2017-05-10 19:09:43 +03:00
|
|
|
|
|
2017-05-10 17:24:43 +03:00
|
|
|
|
-- | An entry in a table of contents.
|
2017-05-10 16:55:05 +03:00
|
|
|
|
data Entry a
|
2017-05-12 00:32:43 +03:00
|
|
|
|
= Unchanged { entryPayload :: a } -- ^ An entry for an unchanged portion of a diff (i.e. a diff node not containing any patches).
|
|
|
|
|
| Changed { entryPayload :: a } -- ^ An entry for a node containing changes.
|
|
|
|
|
| Inserted { entryPayload :: a } -- ^ An entry for a change occurring inside an 'Insert' 'Patch'.
|
|
|
|
|
| Deleted { entryPayload :: a } -- ^ An entry for a change occurring inside a 'Delete' 'Patch'.
|
|
|
|
|
| Replaced { entryPayload :: a } -- ^ An entry for a change occurring on the insertion side of a 'Replace' 'Patch'.
|
2017-05-10 16:55:05 +03:00
|
|
|
|
deriving (Eq, Show)
|
|
|
|
|
|
2017-05-11 22:36:02 +03:00
|
|
|
|
|
2017-05-10 17:21:00 +03:00
|
|
|
|
-- | Compute a table of contents for a diff characterized by a function mapping relevant nodes onto values in Maybe.
|
2017-05-10 17:20:51 +03:00
|
|
|
|
tableOfContentsBy :: Traversable f
|
2017-05-10 18:47:19 +03:00
|
|
|
|
=> (forall b. TermF f annotation b -> Maybe a) -- ^ A function mapping relevant nodes onto values in Maybe.
|
|
|
|
|
-> Diff f annotation -- ^ The diff to compute the table of contents for.
|
|
|
|
|
-> [Entry a] -- ^ A list of entries for relevant changed and unchanged nodes in the diff.
|
2017-06-16 19:57:23 +03:00
|
|
|
|
tableOfContentsBy selector = fromMaybe [] . iter diffAlgebra . fmap (Just . fmap patchEntry . crosswalk (termTableOfContentsBy selector))
|
2017-05-10 21:28:10 +03:00
|
|
|
|
where diffAlgebra r = case (selector (first Both.snd r), fold r) of
|
|
|
|
|
(Just a, Nothing) -> Just [Unchanged a]
|
|
|
|
|
(Just a, Just []) -> Just [Changed a]
|
2017-05-10 21:29:28 +03:00
|
|
|
|
(_ , entries) -> entries
|
2017-05-12 00:08:21 +03:00
|
|
|
|
patchEntry = these Deleted Inserted (const Replaced) . unPatch
|
2017-05-10 16:55:05 +03:00
|
|
|
|
|
2017-06-16 19:21:40 +03:00
|
|
|
|
termTableOfContentsBy :: Traversable f
|
|
|
|
|
=> (forall b. TermF f annotation b -> Maybe a)
|
|
|
|
|
-> Term f annotation
|
2017-06-16 19:25:20 +03:00
|
|
|
|
-> [a]
|
2017-06-16 19:21:40 +03:00
|
|
|
|
termTableOfContentsBy selector = cata termAlgebra
|
2017-06-16 19:25:20 +03:00
|
|
|
|
where termAlgebra r | Just a <- selector r = [a]
|
2017-06-16 19:21:40 +03:00
|
|
|
|
| otherwise = fold r
|
|
|
|
|
|
2017-06-06 00:02:50 +03:00
|
|
|
|
dedupe :: HasField fields (Maybe Declaration) => [Entry (Record fields)] -> [Entry (Record fields)]
|
2017-05-11 22:36:25 +03:00
|
|
|
|
dedupe = foldl' go []
|
|
|
|
|
where go xs x | (_, _:_) <- find (exactMatch `on` entryPayload) x xs = xs
|
2017-05-11 23:05:56 +03:00
|
|
|
|
| (front, similar : back) <- find (similarMatch `on` entryPayload) x xs =
|
2017-05-11 23:41:17 +03:00
|
|
|
|
front <> (Replaced (entryPayload similar) : back)
|
2017-05-11 22:36:25 +03:00
|
|
|
|
| otherwise = xs <> [x]
|
|
|
|
|
|
|
|
|
|
find p x = List.break (p x)
|
|
|
|
|
exactMatch = (==) `on` getDeclaration
|
|
|
|
|
similarMatch a b = sameCategory a b && similarDeclaration a b
|
2017-06-05 23:59:36 +03:00
|
|
|
|
sameCategory = (==) `on` fmap toCategoryName . getDeclaration
|
2017-05-11 22:36:25 +03:00
|
|
|
|
similarDeclaration = (==) `on` fmap (toLower . declarationIdentifier) . getDeclaration
|
2017-05-11 22:25:12 +03:00
|
|
|
|
|
2017-05-10 20:18:47 +03:00
|
|
|
|
-- | Construct a 'JSONSummary' from an 'Entry'. Returns 'Nothing' for 'Unchanged' patches.
|
2017-07-20 00:17:39 +03:00
|
|
|
|
entrySummary :: (HasField fields (Maybe Declaration), HasField fields Span) => Entry (Record fields) -> Maybe JSONSummary
|
|
|
|
|
entrySummary entry = case entry of
|
2017-05-10 20:18:47 +03:00
|
|
|
|
Unchanged _ -> Nothing
|
2017-07-20 00:17:39 +03:00
|
|
|
|
Changed a -> recordSummary a "modified"
|
|
|
|
|
Deleted a -> recordSummary a "removed"
|
|
|
|
|
Inserted a -> recordSummary a "added"
|
|
|
|
|
Replaced a -> recordSummary a "modified"
|
2017-06-16 20:00:46 +03:00
|
|
|
|
|
|
|
|
|
-- | Construct a 'JSONSummary' from a node annotation and a change type label.
|
2017-07-20 00:17:39 +03:00
|
|
|
|
recordSummary :: (HasField fields (Maybe Declaration), HasField fields Span) => Record fields -> Text -> Maybe JSONSummary
|
|
|
|
|
recordSummary record = case getDeclaration record of
|
|
|
|
|
Just (ErrorDeclaration text language) -> Just . const (ErrorSummary text (sourceSpan record) language)
|
2017-07-19 02:49:39 +03:00
|
|
|
|
Just declaration -> Just . JSONSummary (toCategoryName declaration) (declarationIdentifier declaration) (sourceSpan record)
|
2017-06-16 20:00:46 +03:00
|
|
|
|
Nothing -> const Nothing
|
2017-05-10 20:18:47 +03:00
|
|
|
|
|
2017-06-24 17:15:31 +03:00
|
|
|
|
renderToCDiff :: (HasField fields (Maybe Declaration), HasField fields Span, Traversable f) => Both Blob -> Diff f (Record fields) -> Summaries
|
2017-07-20 00:17:39 +03:00
|
|
|
|
renderToCDiff blobs = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . diffTOC
|
2017-07-19 03:18:09 +03:00
|
|
|
|
where toMap [] = mempty
|
2017-05-10 21:00:58 +03:00
|
|
|
|
toMap as = Map.singleton summaryKey (toJSON <$> as)
|
2017-06-24 17:21:54 +03:00
|
|
|
|
summaryKey = toS $ case runJoin (blobPath <$> blobs) of
|
2017-05-10 21:00:58 +03:00
|
|
|
|
(before, after) | null before -> after
|
|
|
|
|
| null after -> before
|
|
|
|
|
| before == after -> after
|
|
|
|
|
| otherwise -> before <> " -> " <> after
|
2017-03-31 23:48:06 +03:00
|
|
|
|
|
2017-06-24 17:15:31 +03:00
|
|
|
|
renderToCTerm :: (HasField fields (Maybe Declaration), HasField fields Span, Traversable f) => Blob -> Term f (Record fields) -> Summaries
|
2017-07-20 00:17:39 +03:00
|
|
|
|
renderToCTerm Blob{..} = uncurry Summaries . bimap toMap toMap . List.partition isValidSummary . termToC
|
2017-07-19 03:18:09 +03:00
|
|
|
|
where toMap [] = mempty
|
2017-07-19 01:59:55 +03:00
|
|
|
|
toMap as = Map.singleton (toS blobPath) (toJSON <$> as)
|
2017-06-16 19:26:14 +03:00
|
|
|
|
|
2017-07-20 00:17:39 +03:00
|
|
|
|
diffTOC :: (HasField fields (Maybe Declaration), HasField fields Span, Traversable f) => Diff f (Record fields) -> [JSONSummary]
|
|
|
|
|
diffTOC = mapMaybe entrySummary . dedupe . tableOfContentsBy declaration
|
2017-01-23 22:12:05 +03:00
|
|
|
|
|
2017-07-20 00:17:39 +03:00
|
|
|
|
termToC :: (HasField fields (Maybe Declaration), HasField fields Span, Traversable f) => Term f (Record fields) -> [JSONSummary]
|
|
|
|
|
termToC = mapMaybe (flip recordSummary "unchanged") . termTableOfContentsBy declaration
|
2017-06-16 19:26:14 +03:00
|
|
|
|
|
2017-02-18 01:57:29 +03:00
|
|
|
|
-- The user-facing category name
|
2017-06-05 23:56:45 +03:00
|
|
|
|
toCategoryName :: Declaration -> Text
|
|
|
|
|
toCategoryName declaration = case declaration of
|
|
|
|
|
FunctionDeclaration _ -> "Function"
|
|
|
|
|
MethodDeclaration _ -> "Method"
|
2017-07-11 19:37:24 +03:00
|
|
|
|
SectionDeclaration _ l -> "Heading " <> show l
|
2017-07-20 00:17:39 +03:00
|
|
|
|
ErrorDeclaration{} -> "ParseError"
|
2017-05-11 22:48:27 +03:00
|
|
|
|
|
|
|
|
|
instance Listable Declaration where
|
|
|
|
|
tiers
|
|
|
|
|
= cons1 (MethodDeclaration . unListableText)
|
|
|
|
|
\/ cons1 (FunctionDeclaration . unListableText)
|
2017-07-20 00:17:39 +03:00
|
|
|
|
\/ cons1 (flip ErrorDeclaration Nothing . unListableText)
|