From eecca0915c9b90d3823a54f51bc484d977e54b2e Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 7 Mar 2017 14:01:46 -0800 Subject: [PATCH 01/11] Better name for output argument --- src/Arguments.hs | 12 ++++++------ src/SemanticDiff.hs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Arguments.hs b/src/Arguments.hs index 9ca2ff8fa..3934fde7a 100644 --- a/src/Arguments.hs +++ b/src/Arguments.hs @@ -42,7 +42,7 @@ data Arguments = Arguments , alternateObjectDirs :: [Text] , format :: R.Format , timeoutInMicroseconds :: Int - , output :: Maybe FilePath + , outputPath :: Maybe FilePath , diffMode :: DiffMode , runMode :: RunMode , shaRange :: Both (Maybe String) @@ -56,7 +56,7 @@ programArguments CmdLineOptions{..} = do pwd <- getCurrentDirectory gitDir <- fromMaybe pwd <$> lookupEnv "GIT_DIR" eitherObjectDirs <- try $ parseObjectDirs . toS <$> getEnv "GIT_ALTERNATE_OBJECT_DIRECTORIES" - output <- getOutputPath outputFilePath + outputPath <- getOutputPath outputFilePath let alternateObjectDirs = case (eitherObjectDirs :: Either IOError [Text]) of (Left _) -> [] (Right objectDirs) -> objectDirs @@ -67,7 +67,7 @@ programArguments CmdLineOptions{..} = do , alternateObjectDirs = alternateObjectDirs , format = outputFormat , timeoutInMicroseconds = maybe defaultTimeout toMicroseconds maybeTimeout - , output = output + , outputPath = outputPath , diffMode = case (noIndex, filePaths) of (True, [fileA, fileB]) -> PathDiff (both fileA fileB) (_, _) -> CommitDiff @@ -100,7 +100,7 @@ args gitDir sha1 sha2 filePaths format = Arguments , alternateObjectDirs = [] , format = format , timeoutInMicroseconds = defaultTimeout - , output = Nothing + , outputPath = Nothing , diffMode = CommitDiff , runMode = Diff , shaRange = Just <$> both sha1 sha2 @@ -114,7 +114,7 @@ diffPathsArgs gitDir paths format = Arguments , alternateObjectDirs = [] , format = format , timeoutInMicroseconds = defaultTimeout - , output = Nothing + , outputPath = Nothing , diffMode = PathDiff paths , runMode = Diff , shaRange = both Nothing Nothing @@ -128,7 +128,7 @@ parseArgs filePaths format = Arguments , alternateObjectDirs = [] , format = format , timeoutInMicroseconds = defaultTimeout - , output = Nothing + , outputPath = Nothing , diffMode = CommitDiff , runMode = Parse , shaRange = both Nothing Nothing diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index daaeae812..1f3f37ac1 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -23,7 +23,7 @@ main = do text <- case runMode of Diff -> diff args Parse -> parse args - writeToOutput output text + writeToOutput outputPath text -- | A parser for the application's command-line arguments. argumentsParser :: ParserInfo CmdLineOptions From 3647e740f716f6a9ccd8aff6a623db024c2e2a7f Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Tue, 7 Mar 2017 14:08:32 -0800 Subject: [PATCH 02/11] Render sexpression output as ByteString --- src/Category.hs | 4 ++++ src/DiffCommand.hs | 13 +++++++------ src/ParseCommand.hs | 11 ++++++----- src/Renderer.hs | 20 +++++++++++++++----- src/Renderer/JSON.hs | 2 +- src/Renderer/SExpression.hs | 12 ++++++------ src/SemanticDiff.hs | 20 +++----------------- 7 files changed, 42 insertions(+), 40 deletions(-) diff --git a/src/Category.hs b/src/Category.hs index 0f746db2c..0dc96f9b5 100644 --- a/src/Category.hs +++ b/src/Category.hs @@ -5,6 +5,7 @@ module Category where import Prologue import Data.Functor.Listable import Data.Text (pack) +import Data.Text.Encoding (encodeUtf8) -- | A standardized category of AST node. Used to determine the semantics for -- | semantic diffing and define comparability of nodes. @@ -238,6 +239,9 @@ instance Hashable Category instance (StringConv Category Text) where strConv _ = pack . show +instance (StringConv Category ByteString) where + strConv _ = encodeUtf8 . show + instance Listable Category where tiers = cons0 Program \/ cons0 ParseError diff --git a/src/DiffCommand.hs b/src/DiffCommand.hs index 5f583d892..47f6a5100 100644 --- a/src/DiffCommand.hs +++ b/src/DiffCommand.hs @@ -5,6 +5,7 @@ import Data.Aeson hiding (json) import Data.Functor.Both as Both import Data.List ((\\)) import Data.String +import Data.Text.Encoding (encodeUtf8) import GHC.Conc (numCapabilities) import Prologue hiding (fst, snd, null) import qualified Control.Concurrent.Async.Pool as Async @@ -36,13 +37,13 @@ import Source import Syntax import Term -diff :: Arguments -> IO Text +diff :: Arguments -> IO ByteString diff args@Arguments{..} = case diffMode of PathDiff paths -> diffPaths args paths CommitDiff -> diffCommits args -- | Compare changes between two commits. -diffCommits :: Arguments -> IO Text +diffCommits :: Arguments -> IO ByteString diffCommits args@Arguments{..} = do ts <- fetchTerms args pure $ maybe mempty concatOutputs ts @@ -51,7 +52,7 @@ diffCommits args@Arguments{..} = do else timeout timeoutInMicroseconds (fetchDiffs args) -- | Compare two paths on the filesystem (compariable to git diff --no-index). -diffPaths :: Arguments -> Both FilePath -> IO Text +diffPaths :: Arguments -> Both FilePath -> IO ByteString diffPaths args@Arguments{..} paths = do sources <- traverse readAndTranscodeFile paths let sourceBlobs = SourceBlob <$> sources <*> pure mempty <*> paths <*> pure (Just defaultPlainBlob) @@ -188,12 +189,12 @@ truncatedDiff Arguments{..} sources = pure $ case format of TOC -> TOCOutput mempty -- | Prints a rendered diff to stdio or a filepath given a parser, diff arguments and two source blobs. -printDiff :: (ToJSON (Record fields), DefaultFields fields) => Parser (Syntax Text) (Record fields) -> Arguments -> Both SourceBlob -> IO Text +printDiff :: (ToJSON (Record fields), DefaultFields fields) => Parser (Syntax Text) (Record fields) -> Arguments -> Both SourceBlob -> IO ByteString printDiff parser arguments sources = do rendered <- textDiff parser arguments sources pure $ case rendered of - SplitOutput text -> text - PatchOutput text -> text + SplitOutput text -> encodeUtf8 text + PatchOutput text -> encodeUtf8 text SExpressionOutput text -> text JSONOutput series -> encodingToText (toJSON series) SummaryOutput summaries -> encodingToText (toJSON summaries) diff --git a/src/ParseCommand.hs b/src/ParseCommand.hs index b09a4a862..6355205a2 100644 --- a/src/ParseCommand.hs +++ b/src/ParseCommand.hs @@ -7,6 +7,7 @@ import Data.Aeson (ToJSON) import Data.Aeson.Encode.Pretty import Data.Record import qualified Data.Text as T +import qualified Data.ByteString as B import Info import Language import Language.Markdown @@ -32,18 +33,18 @@ data ParseJSON = ParseJSON , children :: [ParseJSON] } deriving (Show, Generic, ToJSON) -parse :: Arguments -> IO Text +parse :: Arguments -> IO ByteString parse Arguments{..} = do sources <- traverse readAndTranscodeFile filePaths terms <- zipWithM (\parser sourceBlob -> parser sourceBlob) parsers (sourceBlobs sources) - pure . T.intercalate "\n" $ case format of - SExpression -> [foldr (\t acc -> printTerm t 0 TreeOnly <> acc) "" terms] - _ -> toS . encodePretty . cata algebra <$> terms - + pure $ B.intercalate "\n" (outputLines terms) <> "\n" where sourceBlobs sources = Source.SourceBlob <$> sources <*> pure mempty <*> filePaths <*> pure (Just Source.defaultPlainBlob) parsers = parserWithSource <$> filePaths + outputLines terms = case format of + SExpression -> [foldr (\t acc -> printTerm t 0 TreeOnly <> acc) "" terms] + _ -> toS . encodePretty . cata algebra <$> terms algebra :: TermF (Syntax leaf) (Record '[SourceText, Range, Category, SourceSpan]) ParseJSON -> ParseJSON algebra term = case term of diff --git a/src/Renderer.hs b/src/Renderer.hs index 377bc1022..cf89256d4 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -4,7 +4,8 @@ import Data.Aeson (Value, toEncoding) import Data.Aeson.Encoding (encodingToLazyByteString) import Data.Functor.Both import Data.Map as Map hiding (null) -import Data.Text as T (intercalate) +import Data.Text.Encoding (encodeUtf8) +import qualified Data.ByteString as B import Data.Functor.Listable import Prologue import Source (SourceBlob) @@ -18,7 +19,7 @@ type Renderer annotation = Both SourceBlob -> Diff (Syntax Text) annotation -> O data Format = Split | Patch | JSON | Summary | SExpression | TOC deriving (Show) -data Output = SplitOutput Text | PatchOutput Text | JSONOutput (Map Text Value) | SummaryOutput (Map Text (Map Text [Value])) | SExpressionOutput Text | TOCOutput (Map Text (Map Text [Value])) +data Output = SplitOutput Text | PatchOutput Text | JSONOutput (Map Text Value) | SummaryOutput (Map Text (Map Text [Value])) | SExpressionOutput ByteString | TOCOutput (Map Text (Map Text [Value])) deriving (Show) -- Returns a key representing the filename. If the filenames are different, @@ -37,7 +38,7 @@ toSummaryKey = runBothWith $ \before after -> -- For Summaries, each file output is merged into one 'Object' consisting of lists of -- changes and errors. -- Split and Patch output is appended together with newlines. -concatOutputs :: [Output] -> Text +concatOutputs :: [Output] -> ByteString concatOutputs list | isJSON list = toS . encodingToLazyByteString . toEncoding $ concatJSON list where concatJSON :: [Output] -> Map Text Value @@ -49,7 +50,8 @@ concatOutputs list | isSummary list = toS . encodingToLazyByteString . toEncodin concatSummaries (SummaryOutput hash : rest) = Map.unionWith (Map.unionWith (<>)) hash (concatSummaries rest) concatSummaries (TOCOutput hash : rest) = Map.unionWith (Map.unionWith (<>)) hash (concatSummaries rest) concatSummaries _ = mempty -concatOutputs list | isText list = T.intercalate "\n" (toText <$> list) +concatOutputs list | isByteString list = B.intercalate "\n" (toByteString <$> list) +concatOutputs list | isText list = B.intercalate "\n" (encodeUtf8 . toText <$> list) concatOutputs _ = mempty isJSON :: [Output] -> Bool @@ -70,9 +72,17 @@ isText _ = False toText :: Output -> Text toText (SplitOutput text) = text toText (PatchOutput text) = text -toText (SExpressionOutput text) = text toText _ = mempty +isByteString :: [Output] -> Bool +isByteString (SExpressionOutput _ : _) = True +isByteString _ = False + +toByteString :: Output -> ByteString +toByteString (SExpressionOutput text) = text +toByteString _ = B.empty + + instance Listable Format where tiers = cons0 Split \/ cons0 Patch diff --git a/src/Renderer/JSON.hs b/src/Renderer/JSON.hs index 4e6e69923..09fb9192a 100644 --- a/src/Renderer/JSON.hs +++ b/src/Renderer/JSON.hs @@ -39,7 +39,7 @@ instance (ToJSON leaf, ToJSON (Record fields), HasField fields Category, HasFiel instance ToJSON Category where toJSON (Other s) = String s - toJSON s = String . T.pack $ show s + toJSON s = String (toS s) instance ToJSON Range where toJSON (Range start end) = A.Array . Vector.fromList $ toJSON <$> [ start, end ] diff --git a/src/Renderer/SExpression.hs b/src/Renderer/SExpression.hs index 8d26e868d..4ca5bb808 100644 --- a/src/Renderer/SExpression.hs +++ b/src/Renderer/SExpression.hs @@ -4,7 +4,7 @@ module Renderer.SExpression (sExpression, printTerm, SExpressionFormat(..)) wher import Data.Bifunctor.Join import Data.Foldable import Data.Record -import Data.Text hiding (foldr, replicate) +import Data.ByteString hiding (foldr, replicate, spanEnd) import Prologue hiding (toList, intercalate) import Category as C @@ -18,9 +18,9 @@ import Term data SExpressionFormat = TreeOnly | TreeAndRanges sExpression :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> Renderer (Record fields) -sExpression format _ diff = SExpressionOutput $ printDiff diff 0 format +sExpression format _ diff = SExpressionOutput $ printDiff diff 0 format <> "\n" -printDiff :: (HasField fields Category, HasField fields SourceSpan) => Diff (Syntax Text) (Record fields) -> Int -> SExpressionFormat -> Text +printDiff :: (HasField fields Category, HasField fields SourceSpan) => Diff (Syntax Text) (Record fields) -> Int -> SExpressionFormat -> ByteString printDiff diff level format = case runFree diff of (Pure patch) -> case patch of Insert term -> pad (level - 1) <> "{+" <> printTerm term level format <> "+}" @@ -33,7 +33,7 @@ printDiff diff level format = case runFree diff of | n < 1 = "\n" | otherwise = "\n" <> mconcat (replicate n " ") -printTerm :: (HasField fields Category, HasField fields SourceSpan) => Term (Syntax t) (Record fields) -> Int -> SExpressionFormat -> Text +printTerm :: (HasField fields Category, HasField fields SourceSpan) => Term (Syntax t) (Record fields) -> Int -> SExpressionFormat -> ByteString printTerm term level format = go term level 0 where pad p n | n < 1 = "" @@ -42,7 +42,7 @@ printTerm term level format = go term level 0 (annotation :< Leaf _) -> pad parentLevel level <> "(" <> showAnnotation annotation format <> ")" (annotation :< syntax) -> pad parentLevel level <> "(" <> showAnnotation annotation format <> foldr (\t acc -> go t parentLevel (level + 1) <> acc) "" syntax <> ")" -showAnnotation :: (HasField fields Category, HasField fields SourceSpan) => Record fields -> SExpressionFormat -> Text +showAnnotation :: (HasField fields Category, HasField fields SourceSpan) => Record fields -> SExpressionFormat -> ByteString showAnnotation annotation TreeOnly = categoryName annotation showAnnotation annotation TreeAndRanges = categoryName annotation <> " " <> showSourceSpan annotation where @@ -51,5 +51,5 @@ showAnnotation annotation TreeAndRanges = categoryName annotation <> " " <> show end = showPoint . spanEnd . getField showPoint SourcePos{..} = "[" <> show line <> ", " <> show column <> "]" -categoryName :: HasField fields Category => Record fields -> Text +categoryName :: HasField fields Category => Record fields -> ByteString categoryName = toS . category diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index 1f3f37ac1..2cc427600 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -13,9 +13,7 @@ import qualified Renderer as R import Development.GitRev import DiffCommand import ParseCommand -import qualified Data.Text.IO as TextIO -import System.IO -import System.Environment (lookupEnv) +import qualified Data.ByteString as B main :: IO () main = do @@ -59,17 +57,5 @@ versionString = "semantic-diff version " <> showVersion Library.version <> " (" version :: Parser (a -> a) version = infoOption versionString (long "version" <> short 'V' <> help "output the version of the program") -writeToOutput :: Maybe FilePath -> Text -> IO () -writeToOutput output text = case output of - Nothing -> do - setEncoding - TextIO.hPutStrLn stdout text - Just path -> withFile path WriteMode (`TextIO.hPutStr` text) - where - setEncoding = do - lang <- lookupEnv "LANG" - case lang of - -- If LANG is set and isn't the empty string, leave the encoding. - Just x | x /= "" -> pure () - -- Otherwise default to utf8. - _ -> hSetEncoding stdout utf8 +writeToOutput :: Maybe FilePath -> ByteString -> IO () +writeToOutput = maybe B.putStr B.writeFile From 853e71495f67d372d10c92385e0eca2c637c20be Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 07:39:28 -0800 Subject: [PATCH 03/11] Update transcode docs --- src/Source.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Source.hs b/src/Source.hs index 0ae597806..9e718a5e2 100644 --- a/src/Source.hs +++ b/src/Source.hs @@ -36,11 +36,11 @@ readAndTranscodeFile path = do _ -> B.readFile path transcode text --- From https://github.com/haskell/bytestring/pull/79/files +-- Based on https://github.com/haskell/bytestring/pull/79/files fileSize :: FilePath -> IO Integer fileSize f = withBinaryFile f ReadMode $ \h -> do -- hFileSize fails if file is not regular file (like /dev/null). Catch - -- exception and try reading anyway. + -- exception and return 0 in that case. filesz <- catch (hFileSize h) useZeroIfNotRegularFile pure $ fromIntegral filesz `max` 0 where useZeroIfNotRegularFile :: IOException -> IO Integer From 569fdd031b383ee2330908dede2914e81f7a2aa3 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 11:05:49 -0800 Subject: [PATCH 04/11] Construct sexpression output with ByteString --- src/ParseCommand.hs | 8 ++++---- src/Renderer/SExpression.hs | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/ParseCommand.hs b/src/ParseCommand.hs index 6355205a2..1aea48cea 100644 --- a/src/ParseCommand.hs +++ b/src/ParseCommand.hs @@ -38,13 +38,13 @@ parse Arguments{..} = do sources <- traverse readAndTranscodeFile filePaths terms <- zipWithM (\parser sourceBlob -> parser sourceBlob) parsers (sourceBlobs sources) - pure $ B.intercalate "\n" (outputLines terms) <> "\n" + pure $! toByteString terms where sourceBlobs sources = Source.SourceBlob <$> sources <*> pure mempty <*> filePaths <*> pure (Just Source.defaultPlainBlob) parsers = parserWithSource <$> filePaths - outputLines terms = case format of - SExpression -> [foldr (\t acc -> printTerm t 0 TreeOnly <> acc) "" terms] - _ -> toS . encodePretty . cata algebra <$> terms + toByteString terms = case format of + SExpression -> printTerms TreeOnly terms + _ -> B.intercalate "\n" (toS . encodePretty . cata algebra <$> terms) algebra :: TermF (Syntax leaf) (Record '[SourceText, Range, Category, SourceSpan]) ParseJSON -> ParseJSON algebra term = case term of diff --git a/src/Renderer/SExpression.hs b/src/Renderer/SExpression.hs index 4ca5bb808..ba2e86792 100644 --- a/src/Renderer/SExpression.hs +++ b/src/Renderer/SExpression.hs @@ -1,12 +1,10 @@ -{-# LANGUAGE RankNTypes, ScopedTypeVariables #-} -module Renderer.SExpression (sExpression, printTerm, SExpressionFormat(..)) where +{-# LANGUAGE RankNTypes, ScopedTypeVariables, OverloadedStrings #-} +module Renderer.SExpression (sExpression, printTerm, printTerms, SExpressionFormat(..)) where import Data.Bifunctor.Join -import Data.Foldable import Data.Record -import Data.ByteString hiding (foldr, replicate, spanEnd) -import Prologue hiding (toList, intercalate) - +import Data.ByteString hiding (foldr, spanEnd) +import Prologue hiding (replicate, encodeUtf8) import Category as C import Diff import Renderer @@ -28,16 +26,23 @@ printDiff diff level format = case runFree diff of Replace a b -> pad (level - 1) <> "{ " <> printTerm a level format <> pad (level - 1) <> "->" <> printTerm b level format <> " }" (Free (Join (_, annotation) :< syntax)) -> pad' level <> "(" <> showAnnotation annotation format <> foldr (\d acc -> printDiff d (level + 1) format <> acc) "" syntax <> ")" where + pad' :: Int -> ByteString pad' n = if n < 1 then "" else pad n + pad :: Int -> ByteString pad n | n < 0 = "" | n < 1 = "\n" - | otherwise = "\n" <> mconcat (replicate n " ") + | otherwise = "\n" <> replicate (2 * n) space + +printTerms :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> [Term (Syntax t) (Record fields)] -> ByteString +printTerms format terms = foldr (\t acc -> printTerm t 0 format <> acc) "" terms <> "\n" printTerm :: (HasField fields Category, HasField fields SourceSpan) => Term (Syntax t) (Record fields) -> Int -> SExpressionFormat -> ByteString printTerm term level format = go term level 0 where + pad :: Int -> Int -> ByteString pad p n | n < 1 = "" - | otherwise = "\n" <> mconcat (replicate (p + n) " ") + | otherwise = "\n" <> replicate (2 * (p + n)) space + go :: (HasField fields Category, HasField fields SourceSpan) => Term (Syntax t) (Record fields) -> Int -> Int -> ByteString go term parentLevel level = case runCofree term of (annotation :< Leaf _) -> pad parentLevel level <> "(" <> showAnnotation annotation format <> ")" (annotation :< syntax) -> pad parentLevel level <> "(" <> showAnnotation annotation format <> foldr (\t acc -> go t parentLevel (level + 1) <> acc) "" syntax <> ")" @@ -53,3 +58,6 @@ showAnnotation annotation TreeAndRanges = categoryName annotation <> " " <> show categoryName :: HasField fields Category => Record fields -> ByteString categoryName = toS . category + +space :: Word8 +space = fromIntegral $ ord ' ' From a62b720dad0ec77e30527b775beaf4aa526b0648 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 13:10:10 -0800 Subject: [PATCH 05/11] Sexpression output is not text anymore --- src/Renderer.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Renderer.hs b/src/Renderer.hs index cf89256d4..ca7cb78fb 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -66,7 +66,6 @@ isSummary _ = False isText :: [Output] -> Bool isText (SplitOutput _ : _) = True isText (PatchOutput _ : _) = True -isText (SExpressionOutput _ : _) = True isText _ = False toText :: Output -> Text From 58889e10382f9593aa5a397e77af31fa35a7173e Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 13:26:20 -0800 Subject: [PATCH 06/11] Newline on end of JSON output, no longer pretty --- src/ParseCommand.hs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/ParseCommand.hs b/src/ParseCommand.hs index 1aea48cea..29a316bd0 100644 --- a/src/ParseCommand.hs +++ b/src/ParseCommand.hs @@ -3,8 +3,7 @@ module ParseCommand where import Arguments import Category -import Data.Aeson (ToJSON) -import Data.Aeson.Encode.Pretty +import Data.Aeson (ToJSON, encode) import Data.Record import qualified Data.Text as T import qualified Data.ByteString as B @@ -44,7 +43,7 @@ parse Arguments{..} = do parsers = parserWithSource <$> filePaths toByteString terms = case format of SExpression -> printTerms TreeOnly terms - _ -> B.intercalate "\n" (toS . encodePretty . cata algebra <$> terms) + _ -> B.intercalate "\n" (toS . encode . cata algebra <$> terms) <> "\n" algebra :: TermF (Syntax leaf) (Record '[SourceText, Range, Category, SourceSpan]) ParseJSON -> ParseJSON algebra term = case term of From 0cc056c06b922fd7163739355233a1f0b938d3fa Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 13:40:27 -0800 Subject: [PATCH 07/11] Straightforward json encoding --- src/Renderer.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Renderer.hs b/src/Renderer.hs index ca7cb78fb..f6a12ad02 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -1,13 +1,13 @@ module Renderer (Renderer, Output(..), concatOutputs, toSummaryKey, Format(..)) where -import Data.Aeson (Value, toEncoding) -import Data.Aeson.Encoding (encodingToLazyByteString) +import Data.Aeson (Value, encode) import Data.Functor.Both import Data.Map as Map hiding (null) import Data.Text.Encoding (encodeUtf8) +import Data.ByteString.Lazy (toStrict) import qualified Data.ByteString as B import Data.Functor.Listable -import Prologue +import Prologue hiding (toStrict) import Source (SourceBlob) import Syntax import Diff @@ -39,12 +39,12 @@ toSummaryKey = runBothWith $ \before after -> -- changes and errors. -- Split and Patch output is appended together with newlines. concatOutputs :: [Output] -> ByteString -concatOutputs list | isJSON list = toS . encodingToLazyByteString . toEncoding $ concatJSON list +concatOutputs list | isJSON list = toStrict . encode $ concatJSON list where concatJSON :: [Output] -> Map Text Value concatJSON (JSONOutput hash : rest) = Map.union hash (concatJSON rest) concatJSON _ = mempty -concatOutputs list | isSummary list = toS . encodingToLazyByteString . toEncoding $ concatSummaries list +concatOutputs list | isSummary list = toStrict . encode $ concatSummaries list where concatSummaries :: [Output] -> Map Text (Map Text [Value]) concatSummaries (SummaryOutput hash : rest) = Map.unionWith (Map.unionWith (<>)) hash (concatSummaries rest) From 68d1f1cd97a910d1456c19f90f3438763ec7949d Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 13:56:08 -0800 Subject: [PATCH 08/11] Fix up tests to use ByteString too --- semantic-diff.cabal | 1 + test/DiffCommandSpec.hs | 4 ++-- test/IntegrationSpec.hs | 22 ++++++++++++---------- test/TOCSpec.hs | 4 ++-- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 2c6918fac..46026583a 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -158,6 +158,7 @@ test-suite test , array , base , bifunctors + , bytestring , deepseq , filepath , gitlib diff --git a/test/DiffCommandSpec.hs b/test/DiffCommandSpec.hs index 0b95b6db9..cdc47799d 100644 --- a/test/DiffCommandSpec.hs +++ b/test/DiffCommandSpec.hs @@ -9,8 +9,8 @@ import Prologue (($), fmap, (.), pure, for, panic) import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall) import Test.Hspec.Expectations.Pretty import Test.Hspec.LeanCheck -import Data.Text.Lazy.Encoding as E import Data.Text.Lazy as T +import qualified Data.ByteString.Lazy as BL import Data.Map import qualified Data.Vector as V import Arguments @@ -58,7 +58,7 @@ spec = parallel $ do fetchDiffsOutput :: (Object -> Text) -> Arguments -> IO (Maybe (Map Text Value), Maybe (Map Text [Text])) fetchDiffsOutput f arguments = do diffs <- fetchDiffs arguments - let json = fromJust . decode . E.encodeUtf8 $ T.fromChunks [concatOutputs diffs] + let json = fromJust . decode . BL.fromStrict $ concatOutputs diffs pure (errors json, summaries f json) -- Diff Summaries payloads look like this: diff --git a/test/IntegrationSpec.hs b/test/IntegrationSpec.hs index c9fb427aa..a2c52b957 100644 --- a/test/IntegrationSpec.hs +++ b/test/IntegrationSpec.hs @@ -1,10 +1,12 @@ -{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving #-} +{-# LANGUAGE DataKinds, GeneralizedNewtypeDeriving, OverloadedStrings #-} module IntegrationSpec where import Category as C import Data.Functor.Both import Data.Record import qualified Data.Text as T +import qualified Data.ByteString as B +import Data.Text.Encoding (decodeUtf8) import GHC.Show (Show(..)) import Data.List (union, concat, transpose) import Info @@ -109,7 +111,7 @@ testParse path expectedOutput = do let blob = sourceBlob source path term <- parserWithSource path blob let actual = (Verbatim . stripWhitespace) $ printTerm term 0 TreeOnly - expected <- (Verbatim . stripWhitespace) <$> readFile expectedOutput + expected <- (Verbatim . stripWhitespace) <$> B.readFile expectedOutput actual `shouldBe` expected testDiff :: Renderer (Record '[Range, Category, SourceSpan]) -> Both FilePath -> FilePath -> Expectation @@ -117,7 +119,7 @@ testDiff renderer paths diff = do sources <- traverse readAndTranscodeFile' paths diff' <- diffFiles parser renderer (sourceBlobs sources) let actual = (Verbatim . stripWhitespace. concatOutputs . pure) diff' - expected <- (Verbatim . stripWhitespace) <$> readFile diff + expected <- (Verbatim . stripWhitespace) <$> B.readFile diff actual `shouldBe` expected where parser = parserForFilepath filePath @@ -126,14 +128,14 @@ testDiff renderer paths diff = do | otherwise = readAndTranscodeFile path filePath = if fst paths /= "" then fst paths else snd paths -stripWhitespace :: Text -> Text -stripWhitespace = T.foldl' go T.empty - where go acc x | x `elem` [' ', '\t', '\n'] = acc - | otherwise = T.snoc acc x +stripWhitespace :: ByteString -> ByteString +stripWhitespace = B.foldl' go B.empty + where go acc x | x `B.elem` " \t\n" = acc + | otherwise = B.snoc acc x --- | A wrapper around `Text` with a more readable `Show` instance. -newtype Verbatim = Verbatim Text +-- | A wrapper around `ByteString` with a more readable `Show` instance. +newtype Verbatim = Verbatim ByteString deriving (Eq, NFData) instance Show Verbatim where - showsPrec _ (Verbatim text) = ('\n':) . (T.unpack text ++) + showsPrec _ (Verbatim byteString) = ('\n':) . (T.unpack (decodeUtf8 byteString) ++) diff --git a/test/TOCSpec.hs b/test/TOCSpec.hs index 5db9365de..9a298e77c 100644 --- a/test/TOCSpec.hs +++ b/test/TOCSpec.hs @@ -112,13 +112,13 @@ spec = parallel $ do sourceBlobs <- blobsForPaths (both "ruby/methods.A.rb" "ruby/methods.B.rb") let parser = parserForFilepath (path (fst sourceBlobs)) output <- diffFiles parser toc sourceBlobs - concatOutputs (pure output) `shouldBe` ("{\"changes\":{\"ruby/methods.A.rb -> ruby/methods.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"category\":\"Method\",\"term\":\"self.foo\",\"changeType\":\"added\"},{\"span\":{\"start\":[4,1],\"end\":[6,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"},{\"span\":{\"start\":[4,1],\"end\":[5,4]},\"category\":\"Method\",\"term\":\"baz\",\"changeType\":\"removed\"}]},\"errors\":{}}" :: Text) + concatOutputs (pure output) `shouldBe` ("{\"changes\":{\"ruby/methods.A.rb -> ruby/methods.B.rb\":[{\"span\":{\"start\":[1,1],\"end\":[2,4]},\"category\":\"Method\",\"term\":\"self.foo\",\"changeType\":\"added\"},{\"span\":{\"start\":[4,1],\"end\":[6,4]},\"category\":\"Method\",\"term\":\"bar\",\"changeType\":\"modified\"},{\"span\":{\"start\":[4,1],\"end\":[5,4]},\"category\":\"Method\",\"term\":\"baz\",\"changeType\":\"removed\"}]},\"errors\":{}}" ) it "encodes to final JSON if there are parse errors" $ do sourceBlobs <- blobsForPaths (both "ruby/methods.A.rb" "ruby/methods.X.rb") let parser = parserForFilepath (path (fst sourceBlobs)) output <- diffFiles parser toc sourceBlobs - concatOutputs (pure output) `shouldBe` ("{\"changes\":{},\"errors\":{\"ruby/methods.A.rb -> ruby/methods.X.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,1]},\"error\":\"def bar\\nen\\n\"}]}}" :: Text) + concatOutputs (pure output) `shouldBe` ("{\"changes\":{},\"errors\":{\"ruby/methods.A.rb -> ruby/methods.X.rb\":[{\"span\":{\"start\":[1,1],\"end\":[3,1]},\"error\":\"def bar\\nen\\n\"}]}}" ) type Diff' = SyntaxDiff String '[Range, Category, SourceSpan] type Term' = SyntaxTerm String '[Range, Category, SourceSpan] From c6be51156de4b65e4eddf273d71d2dd23e39d9ac Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 14:02:52 -0800 Subject: [PATCH 09/11] Whitespace --- src/ParseCommand.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ParseCommand.hs b/src/ParseCommand.hs index 29a316bd0..2f0f9af34 100644 --- a/src/ParseCommand.hs +++ b/src/ParseCommand.hs @@ -36,7 +36,6 @@ parse :: Arguments -> IO ByteString parse Arguments{..} = do sources <- traverse readAndTranscodeFile filePaths terms <- zipWithM (\parser sourceBlob -> parser sourceBlob) parsers (sourceBlobs sources) - pure $! toByteString terms where sourceBlobs sources = Source.SourceBlob <$> sources <*> pure mempty <*> filePaths <*> pure (Just Source.defaultPlainBlob) From 8bf09776e4ccd70b116bbaa925001cec9f4b5b2d Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 14:45:47 -0800 Subject: [PATCH 10/11] toS is a little cleaner here --- src/Renderer.hs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Renderer.hs b/src/Renderer.hs index f6a12ad02..7900e96a9 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -4,10 +4,9 @@ import Data.Aeson (Value, encode) import Data.Functor.Both import Data.Map as Map hiding (null) import Data.Text.Encoding (encodeUtf8) -import Data.ByteString.Lazy (toStrict) import qualified Data.ByteString as B import Data.Functor.Listable -import Prologue hiding (toStrict) +import Prologue import Source (SourceBlob) import Syntax import Diff @@ -39,12 +38,12 @@ toSummaryKey = runBothWith $ \before after -> -- changes and errors. -- Split and Patch output is appended together with newlines. concatOutputs :: [Output] -> ByteString -concatOutputs list | isJSON list = toStrict . encode $ concatJSON list +concatOutputs list | isJSON list = toS . encode $ concatJSON list where concatJSON :: [Output] -> Map Text Value concatJSON (JSONOutput hash : rest) = Map.union hash (concatJSON rest) concatJSON _ = mempty -concatOutputs list | isSummary list = toStrict . encode $ concatSummaries list +concatOutputs list | isSummary list = toS . encode $ concatSummaries list where concatSummaries :: [Output] -> Map Text (Map Text [Value]) concatSummaries (SummaryOutput hash : rest) = Map.unionWith (Map.unionWith (<>)) hash (concatSummaries rest) From cf390cdfbab12c4433b55159d2254ffce78ca551 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 8 Mar 2017 14:57:17 -0800 Subject: [PATCH 11/11] Cleanup encodingToText --- src/DiffCommand.hs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/DiffCommand.hs b/src/DiffCommand.hs index 47f6a5100..726f29cc7 100644 --- a/src/DiffCommand.hs +++ b/src/DiffCommand.hs @@ -196,8 +196,6 @@ printDiff parser arguments sources = do SplitOutput text -> encodeUtf8 text PatchOutput text -> encodeUtf8 text SExpressionOutput text -> text - JSONOutput series -> encodingToText (toJSON series) - SummaryOutput summaries -> encodingToText (toJSON summaries) - TOCOutput summaries -> encodingToText (toJSON summaries) - where - encodingToText = toS . encode + JSONOutput series -> toS $ encode series + SummaryOutput summaries -> toS $ encode summaries + TOCOutput summaries -> toS $ encode summaries