From 275d413f96f26cf5d619585f428a545aa0a790e7 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 3 Apr 2017 09:28:53 -0700 Subject: [PATCH 01/17] Change binary name to just 'semantic' --- semantic-diff.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 8504360a7..d9d9c6c29 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -116,7 +116,7 @@ library ghc-options: -Wall -fno-warn-name-shadowing -O2 -j ghc-prof-options: -fprof-auto -executable semantic-diff +executable semantic hs-source-dirs: app main-is: Main.hs ghc-options: -threaded -rtsopts "-with-rtsopts=-N -A4m -n2m" -static -j -O2 -pgml=script/g++ From 1dd54d35d950eeef4d4254707e5cb0fbe1f93222 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 3 Apr 2017 11:30:25 -0700 Subject: [PATCH 02/17] Rework argument parsing to use subparsers and commands --- src/Arguments.hs | 173 +++++++++---------------------------- src/Command/Parse.hs | 20 ++--- src/Renderer.hs | 11 ++- src/SemanticDiff.hs | 150 ++++++++++++++++++++------------ test/Command/Parse/Spec.hs | 21 ++--- 5 files changed, 161 insertions(+), 214 deletions(-) diff --git a/src/Arguments.hs b/src/Arguments.hs index 6124b88fe..c10c5cf41 100644 --- a/src/Arguments.hs +++ b/src/Arguments.hs @@ -1,153 +1,58 @@ {-# OPTIONS_GHC -funbox-strict-fields #-} -module Arguments (Arguments(..), CmdLineOptions(..), DiffMode(..), ExtraArg(..), RunMode(..), programArguments, args, diffPathsArgs, parseArgs) where +{-# LANGUAGE DuplicateRecordFields #-} +module Arguments where -import Data.Functor.Both import Data.Maybe -import Data.List.Split import Prologue hiding ((<>)) import Prelude -import System.Environment -import System.Directory -import System.FilePath.Posix (takeFileName, (-<.>)) -import System.IO.Error (IOError) - import qualified Renderer as R -data ExtraArg = ShaPair (Both (Maybe String)) - | FileArg FilePath - deriving (Show) +data DiffMode = DiffCommits String String [FilePath] | DiffPaths FilePath FilePath + deriving (Show) -data DiffMode = PathDiff (Both FilePath) - | CommitDiff - deriving (Show) - -data RunMode = Diff - | Parse - deriving (Show) - --- | The command line options to the application (arguments for optparse-applicative). -data CmdLineOptions = CmdLineOptions - { outputFormat :: R.Format - , maybeTimeout :: Maybe Float - , outputFilePath :: Maybe FilePath - , commitSha' :: Maybe String - , noIndex :: Bool - , extraArgs :: [ExtraArg] - , debug' :: Bool - , runMode' :: RunMode - } - --- | Arguments for the program (includes command line, environment, and defaults). -data Arguments = Arguments - { gitDir :: FilePath - , alternateObjectDirs :: [FilePath] - , format :: R.Format - , timeoutInMicroseconds :: Int - , outputPath :: Maybe FilePath - , commitSha :: Maybe String +data DiffArguments = DiffArguments + { diffFormat :: R.Format , diffMode :: DiffMode - , runMode :: RunMode - , shaRange :: Both (Maybe String) - , filePaths :: [FilePath] + , gitDir :: FilePath + , alternateObjectDirs :: [FilePath] } + deriving (Show) + +data ParseMode = ParseCommit String [FilePath] | ParsePaths [FilePath] + deriving (Show) + +data ParseArguments = ParseArguments + { parseFormat :: R.ParseFormat + , parseMode :: ParseMode , debug :: Bool + , gitDir :: FilePath + , alternateObjectDirs :: [FilePath] } + deriving (Show) + +data ProgramMode = Parse ParseArguments | Diff DiffArguments + deriving (Show) + +data Arguments = Arguments + { programMode :: ProgramMode + , outputFilePath :: Maybe FilePath } deriving (Show) --- | Returns Arguments for the program from parsed command line arguments. -programArguments :: CmdLineOptions -> IO Arguments -programArguments CmdLineOptions{..} = do - pwd <- getCurrentDirectory - gitDir <- fromMaybe pwd <$> lookupEnv "GIT_DIR" - eitherObjectDirs <- try $ parseObjectDirs . toS <$> getEnv "GIT_ALTERNATE_OBJECT_DIRECTORIES" - outputPath <- getOutputPath outputFilePath - let alternateObjectDirs = case (eitherObjectDirs :: Either IOError [FilePath]) of - (Left _) -> [] - (Right objectDirs) -> objectDirs - - let filePaths = fetchPaths extraArgs - pure Arguments - { gitDir = gitDir - , alternateObjectDirs = alternateObjectDirs - , format = outputFormat - , timeoutInMicroseconds = maybe defaultTimeout toMicroseconds maybeTimeout - , outputPath = outputPath - , commitSha = commitSha' - , diffMode = case (noIndex, filePaths) of - (True, [fileA, fileB]) -> PathDiff (both fileA fileB) - (_, _) -> CommitDiff - , runMode = runMode' - , shaRange = fetchShas extraArgs - , filePaths = filePaths - , debug = debug' - } - where - fetchPaths :: [ExtraArg] -> [FilePath] - fetchPaths [] = [] - fetchPaths (FileArg x:xs) = x : fetchPaths xs - fetchPaths (_:xs) = fetchPaths xs - - fetchShas :: [ExtraArg] -> Both (Maybe String) - fetchShas [] = both Nothing Nothing - fetchShas (ShaPair x:_) = x - fetchShas (_:xs) = fetchShas xs - - getOutputPath Nothing = pure Nothing - getOutputPath (Just path) = do - isDir <- doesDirectoryExist path - pure . Just $ if isDir then takeFileName path -<.> ".html" else path - - -- | Quickly assemble an Arguments data record with defaults. args :: FilePath -> String -> String -> [String] -> R.Format -> Arguments -args gitDir sha1 sha2 filePaths format = Arguments - { gitDir = gitDir - , alternateObjectDirs = [] - , format = format - , timeoutInMicroseconds = defaultTimeout - , outputPath = Nothing - , commitSha = Nothing - , diffMode = CommitDiff - , runMode = Diff - , shaRange = Just <$> both sha1 sha2 - , filePaths = filePaths - , debug = False +args gitDir sha1 sha2 paths format = Arguments + { programMode = Diff DiffArguments + { diffFormat = format + , diffMode = DiffCommits sha1 sha2 paths + , gitDir = gitDir + , alternateObjectDirs = [] + } + , outputFilePath = Nothing } -diffPathsArgs :: FilePath -> Both FilePath -> R.Format -> Arguments -diffPathsArgs gitDir paths format = Arguments - { gitDir = gitDir - , alternateObjectDirs = [] - , format = format - , timeoutInMicroseconds = defaultTimeout - , outputPath = Nothing - , commitSha = Nothing - , diffMode = PathDiff paths - , runMode = Diff - , shaRange = both Nothing Nothing - , filePaths = [] +parseArgs :: [String] -> R.ParseFormat -> ParseArguments +parseArgs paths format = ParseArguments + { parseFormat = format + , parseMode = ParsePaths paths , debug = False - } - -parseArgs :: [String] -> R.Format -> Arguments -parseArgs filePaths format = Arguments - { gitDir = "" + , gitDir = "" , alternateObjectDirs = [] - , format = format - , timeoutInMicroseconds = defaultTimeout - , outputPath = Nothing - , commitSha = Nothing - , diffMode = CommitDiff - , runMode = Parse - , shaRange = both Nothing Nothing - , filePaths = filePaths - , debug = False } - --- | 7 seconds -defaultTimeout :: Int -defaultTimeout = 7 * 1000000 - -toMicroseconds :: Float -> Int -toMicroseconds num = floor $ num * 1000000 - -parseObjectDirs :: FilePath -> [FilePath] -parseObjectDirs = splitWhen (== ':') diff --git a/src/Command/Parse.hs b/src/Command/Parse.hs index 7da316a28..906819d35 100644 --- a/src/Command/Parse.hs +++ b/src/Command/Parse.hs @@ -65,15 +65,15 @@ parseNodeToJSONFields ParseNode{..} = <> [ "identifier" .= identifier | isJust identifier ] -- | Parses file contents into an SExpression format for the provided arguments. -parseSExpression :: Arguments -> IO ByteString +parseSExpression :: ParseArguments -> IO ByteString parseSExpression = pure . printTerms TreeOnly <=< parse <=< sourceBlobsFromArgs where parse = traverse (\sourceBlob@SourceBlob{..} -> parserForType (toS (takeExtension path)) sourceBlob) type RAlgebra t a = Base t (t, a) -> a -parseRoot :: (FilePath -> f ParseNode -> root) -> (ParseNode -> [f ParseNode] -> f ParseNode) -> Arguments -> IO [root] -parseRoot construct combine args@Arguments{..} = do +parseRoot :: (FilePath -> f ParseNode -> root) -> (ParseNode -> [f ParseNode] -> f ParseNode) -> ParseArguments -> IO [root] +parseRoot construct combine args@ParseArguments{..} = do blobs <- sourceBlobsFromArgs args for blobs (\ sourceBlob@SourceBlob{..} -> do parsedTerm <- parseWithDecorator (decorator source) path sourceBlob @@ -85,11 +85,11 @@ parseRoot construct combine args@Arguments{..} = do ParseNode (toS category) range head sourceSpan (identifierFor syntax) -- | Constructs IndexFile nodes for the provided arguments and encodes them to JSON. -parseIndex :: Arguments -> IO ByteString +parseIndex :: ParseArguments -> IO ByteString parseIndex = fmap (toS . encode) . parseRoot IndexFile (\ node siblings -> node : concat siblings) -- | Constructs ParseTreeFile nodes for the provided arguments and encodes them to JSON. -parseTree :: Arguments -> IO ByteString +parseTree :: ParseArguments -> IO ByteString parseTree = fmap (toS . encode) . parseRoot ParseTreeFile Rose -- | Determines the term decorator to use when parsing. @@ -139,11 +139,11 @@ identifierFor :: (HasField fields (Maybe SourceText), HasField fields Category, identifierFor = fmap toS . extractLeafValue . unwrap <=< maybeIdentifier -- | For the file paths and commit sha provided, extract only the BlobEntries and represent them as SourceBlobs. -sourceBlobsFromArgs :: Arguments -> IO [SourceBlob] -sourceBlobsFromArgs Arguments{..} = - case commitSha of - Just commitSha' -> sourceBlobsFromSha commitSha' gitDir filePaths - _ -> sourceBlobsFromPaths filePaths +sourceBlobsFromArgs :: ParseArguments -> IO [SourceBlob] +sourceBlobsFromArgs ParseArguments{..} = + case parseMode of + ParseCommit sha paths -> sourceBlobsFromSha sha gitDir paths + ParsePaths paths -> sourceBlobsFromPaths paths -- | Return a parser incorporating the provided TermDecorator. parseWithDecorator :: TermDecorator (Syntax Text) DefaultFields field -> FilePath -> Parser (Syntax Text) (Record (field ': DefaultFields)) diff --git a/src/Renderer.hs b/src/Renderer.hs index 70b0450b2..794e703e5 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -3,6 +3,7 @@ module Renderer ( DiffRenderer(..) , runDiffRenderer , Format(..) +, ParseFormat(..) , Summaries(..) , File(..) ) where @@ -42,7 +43,10 @@ runDiffRenderer renderer = foldMap . uncurry $ case renderer of ToCRenderer -> R.toc -- | The available types of diff rendering. -data Format = Split | Patch | JSON | Summary | SExpression | TOC | Index | ParseTree +data Format = Split | Patch | JSON | Summary | SExpression | TOC + deriving (Show) + +data ParseFormat = JSONTree | JSONIndex | SExpressionTree deriving (Show) newtype File = File { unFile :: Text } @@ -59,3 +63,8 @@ instance Listable Format where \/ cons0 Summary \/ cons0 SExpression \/ cons0 TOC + +instance Listable ParseFormat where + tiers = cons0 JSONTree + \/ cons0 JSONIndex + \/ cons0 SExpressionTree diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index 7db2a6a8a..f644ea218 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -4,91 +4,133 @@ module SemanticDiff (main) where import Arguments import Command import Command.Parse -import Development.GitRev import Data.Aeson -import qualified Data.ByteString as B import Data.Functor.Both +import Data.List.Split (splitWhen) import Data.String import Data.Version (showVersion) +import Development.GitRev import Options.Applicative hiding (action) -import qualified Paths_semantic_diff as Library (version) import Prologue hiding (concurrently, fst, snd, readFile) +import qualified Data.ByteString as B +import qualified Paths_semantic_diff as Library (version) import qualified Renderer as R import qualified Renderer.SExpression as R import Source +import System.Directory +import System.Environment +import System.FilePath.Posix (takeFileName, (-<.>)) +import System.IO.Error (IOError) import Text.Regex main :: IO () main = do - args@Arguments{..} <- programArguments =<< execParser argumentsParser - text <- case runMode of - Diff -> runCommand $ do - let render = case format of + gitDir <- findGitDir + alternates <- findAlternates + args@Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) + print args + + text <- case programMode of + Diff DiffArguments{..} -> runCommand $ do + let render = case diffFormat of R.Split -> fmap encodeText . renderDiffs R.SplitRenderer R.Patch -> fmap encodeText . renderDiffs R.PatchRenderer R.JSON -> fmap encodeJSON . renderDiffs R.JSONDiffRenderer R.Summary -> fmap encodeSummaries . renderDiffs R.SummaryRenderer R.SExpression -> renderDiffs (R.SExpressionDiffRenderer R.TreeOnly) R.TOC -> fmap encodeSummaries . renderDiffs R.ToCRenderer - _ -> fmap encodeText . renderDiffs R.PatchRenderer diffs <- case diffMode of - PathDiff paths -> do + DiffPaths pathA pathB -> do + let paths = both pathA pathB blobs <- traverse readFile paths terms <- traverse (traverse parseBlob) blobs diff' <- maybeDiff terms - return [(fromMaybe . emptySourceBlob <$> paths <*> blobs, diff')] - CommitDiff -> do - blobPairs <- readFilesAtSHAs gitDir alternateObjectDirs filePaths (fromMaybe (toS nullOid) <$> shaRange) + pure [(fromMaybe . emptySourceBlob <$> paths <*> blobs, diff')] + DiffCommits sha1 sha2 paths -> do + blobPairs <- readFilesAtSHAs gitDir alternateObjectDirs paths (both sha1 sha2) concurrently blobPairs . uncurry $ \ path blobs -> do terms <- concurrently blobs (traverse parseBlob) diff' <- maybeDiff terms - return (fromMaybe <$> pure (emptySourceBlob path) <*> blobs, diff') + pure (fromMaybe <$> pure (emptySourceBlob path) <*> blobs, diff') render (diffs >>= \ (blobs, diff) -> (,) blobs <$> toList diff) - Parse -> case format of - R.Index -> parseIndex args - R.SExpression -> parseSExpression args - _ -> parseTree args + Parse args'@ParseArguments{..} -> case parseFormat of + R.JSONTree -> parseTree args' + R.JSONIndex -> parseIndex args' + R.SExpressionTree -> parseSExpression args' + outputPath <- getOutputPath outputFilePath writeToOutput outputPath (text <> "\n") - where encodeText = encodeUtf8 . R.unFile - encodeJSON = toS . encode - encodeSummaries = toS . encode + + where + encodeText = encodeUtf8 . R.unFile + encodeJSON = toS . encode + encodeSummaries = toS . encode + + findGitDir = do + pwd <- getCurrentDirectory + fromMaybe pwd <$> lookupEnv "GIT_DIR" + + findAlternates = do + eitherObjectDirs <- try $ splitWhen (== ':') . toS <$> getEnv "GIT_ALTERNATE_OBJECT_DIRECTORIES" + pure $ case (eitherObjectDirs :: Either IOError [FilePath]) of + (Left _) -> [] + (Right objectDirs) -> objectDirs + + getOutputPath Nothing = pure Nothing + getOutputPath (Just path) = do + isDir <- doesDirectoryExist path + pure . Just $ if isDir then takeFileName path -<.> ".html" else path + + writeToOutput :: Maybe FilePath -> ByteString -> IO () + writeToOutput = maybe B.putStr B.writeFile -- | A parser for the application's command-line arguments. -argumentsParser :: ParserInfo CmdLineOptions -argumentsParser = info (version <*> helper <*> argumentsP) - (fullDesc <> progDesc "Set the GIT_DIR environment variable to specify the git repository. Set GIT_ALTERNATE_OBJECT_DIRECTORIES to specify location of alternates." - <> header "semantic-diff - Show semantic changes between commits") +arguments :: FilePath -> [FilePath] -> ParserInfo Arguments +arguments gitDir alternates = info (version <*> helper <*> argumentsParser) description where - argumentsP :: Parser CmdLineOptions - argumentsP = CmdLineOptions - <$> (flag R.Split R.Patch (long "patch" <> help "output a patch(1)-compatible diff") - <|> flag R.Split R.JSON (long "json" <> help "output a json diff") - <|> flag' R.Split (long "split" <> help "output a split diff") - <|> flag' R.Summary (long "summary" <> help "output a diff summary") - <|> flag' R.SExpression (long "sexpression" <> help "output an s-expression diff tree") - <|> flag' R.TOC (long "toc" <> help "output a table of contents diff summary") - <|> flag' R.Index (long "index" <> help "output indexable JSON parse output") - <|> flag' R.ParseTree (long "parse-tree" <> help "output JSON parse tree structure")) - <*> optional (option auto (long "timeout" <> help "timeout for per-file diffs in seconds, defaults to 7 seconds")) - <*> optional (strOption (long "output" <> short 'o' <> help "output directory for split diffs, defaults to stdout if unspecified")) - <*> optional (strOption (long "commit" <> short 'c' <> help "single commit entry for parsing")) - <*> switch (long "no-index" <> help "compare two paths on the filesystem") - <*> some (argument (eitherReader parseShasAndFiles) (metavar "SHA_A..SHAB FILES...")) - <*> switch (long "debug" <> short 'd' <> help "set debug mode for parsing which outputs sourcetext for each syntax node") - <*> flag Diff Parse (long "parse" <> short 'p' <> help "parses a source file without diffing") - where - parseShasAndFiles :: String -> Either String ExtraArg - parseShasAndFiles s = case matchRegex regex s of - Just ["", sha2] -> Right . ShaPair $ both Nothing (Just sha2) - Just [sha1, sha2] -> Right . ShaPair $ Just <$> both sha1 sha2 - _ -> Right $ FileArg s - where regex = mkRegexWithOpts "([0-9a-f]{40})\\.\\.([0-9a-f]{40})" True False + version = infoOption versionString (long "version" <> short 'v' <> help "Output the version of the program") + versionString = "semantic-diff version " <> showVersion Library.version <> " (" <> $(gitHash) <> ")" + description = fullDesc <> progDesc "Set the GIT_DIR environment variable to specify a different git repository. Set GIT_ALTERNATE_OBJECT_DIRECTORIES to specify location of alternates." + <> header "semantic -- Parse and diff semantically" -versionString :: String -versionString = "semantic-diff version " <> showVersion Library.version <> " (" <> $(gitHash) <> ")" + argumentsParser = Arguments + <$> hsubparser (diffCommand <> parseCommand) + <*> optional (strOption (long "output" <> short 'o' <> help "Output path (directory for split diffs), defaults to stdout")) -version :: Parser (a -> a) -version = infoOption versionString (long "version" <> short 'V' <> help "output the version of the program") + diffCommand = command "diff" (info diffArgumentsParser (progDesc "Show changes between commits or paths")) + diffArgumentsParser = Diff + <$> ( DiffArguments + <$> (flag R.Patch R.Patch (long "patch" <> help "Output a patch(1)-compatible diff (default)") + <|> flag' R.JSON (long "json" <> help "Output a json diff") + <|> flag' R.Split (long "split" <> help "Output a split diff") + <|> flag' R.Summary (long "summary" <> help "Output a diff summary") + <|> flag' R.SExpression (long "sexpression" <> help "Output an s-expression diff tree") + <|> flag' R.TOC (long "toc" <> help "Output a table of contents diff summary")) + <*> ( DiffPaths + <$> argument str (metavar "FILE_A") + <*> argument str (metavar "FILE_B") + <|> DiffCommits + <$> option (eitherReader parseSha) (long "sha1" <> metavar "SHA" <> help "Starting commit SHA") + <*> option (eitherReader parseSha) (long "sha2" <> metavar "SHA" <> help "Ending commit SHA") + <*> many (argument str (metavar "FILES...")) ) + <*> pure gitDir + <*> pure alternates ) -writeToOutput :: Maybe FilePath -> ByteString -> IO () -writeToOutput = maybe B.putStr B.writeFile + parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print parse trees for a commit or paths")) + parseArgumentsParser = Parse + <$> ( ParseArguments + <$> ( flag R.SExpressionTree R.SExpressionTree (long "sexpression" <> help "Output s-expression formatted parse trees (default)") + <|> flag' R.JSONTree (long "json" <> help "Output JSON formatted parse trees")) + <*> ( ParsePaths + <$> some (argument str (metavar "FILES...")) + <|> ParseCommit + <$> option (eitherReader parseSha) (long "sha" <> metavar "SHA" <> help "Commit SHA") + <*> some (argument str (metavar "FILES...")) ) + <*> switch (long "debug") + <*> pure gitDir + <*> pure alternates ) + + parseSha :: String -> Either String String + parseSha s = case matchRegex regex s of + Just [sha] -> Right sha + _ -> Left $ s <> " is not a valid SHA-1" + where regex = mkRegexWithOpts "([0-9a-f]{40})" True False diff --git a/test/Command/Parse/Spec.hs b/test/Command/Parse/Spec.hs index 23006a6f2..f602a2ea4 100644 --- a/test/Command/Parse/Spec.hs +++ b/test/Command/Parse/Spec.hs @@ -1,34 +1,25 @@ module Command.Parse.Spec where import Command.Parse -import Data.Functor.Listable import Prelude import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall) import Test.Hspec.Expectations.Pretty import Test.Hspec.LeanCheck -import Test.LeanCheck import Arguments import Renderer spec :: Spec -spec = parallel $ - context "parse" $ - prop "all valid formats should produce output" . forAll (isParseFormat `filterT` tiers) $ +spec = parallel $ do + context "parse" $ do + prop "all valid formats should produce output" $ \format -> case format of - SExpression -> do + SExpressionTree -> do output <- parseSExpression $ parseArgs ["test/fixtures/ruby/and-or.A.rb"] format output `shouldNotBe` "" - Index -> do + JSONIndex -> do output <- parseIndex $ parseArgs ["test/fixtures/ruby/and-or.A.rb"] format output `shouldNotBe` "" - _ -> do + JSONTree -> do output <- parseTree $ parseArgs ["test/fixtures/ruby/and-or.A.rb"] format output `shouldNotBe` "" - -isParseFormat :: Format -> Bool -isParseFormat a | Index <- a = True - | ParseTree <- a = True - | JSON <- a = True - | SExpression <- a = True - | otherwise = False From 9273f4969a8deb2ed1d1fa0bab91f8c41ba414e1 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 5 Apr 2017 15:13:33 -0700 Subject: [PATCH 03/17] Update docs and scripts with new command line args --- .atom-build.yml | 11 ----------- script/generate-example | 16 ++++++++-------- 2 files changed, 8 insertions(+), 19 deletions(-) delete mode 100644 .atom-build.yml diff --git a/.atom-build.yml b/.atom-build.yml deleted file mode 100644 index 741285538..000000000 --- a/.atom-build.yml +++ /dev/null @@ -1,11 +0,0 @@ -# Build configuration for https://atom.io/packages/build -cmd: stack build -name: semantic-diff -env: - PATH: ~/.local/bin:~/Developer/Tools:~/Library/Haskell/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -targets: - test: - cmd: stack build semantic-diff:test - keymap: cmd-u -errorMatch: -- \n(?/[^:]+):(?\d+):((?\d+):)? diff --git a/script/generate-example b/script/generate-example index 3c151dcbf..b56a64884 100755 --- a/script/generate-example +++ b/script/generate-example @@ -40,28 +40,28 @@ generate_example () { diffFileBA="${fileB%%.*}.diffB-A.txt" status $parseFileA - stack exec semantic-diff -- --sexpression --parse $fileA > $parseFileA + stack exec semantic parse -- --sexpression $fileA > $parseFileA status $parseFileB - stack exec semantic-diff -- --sexpression --parse $fileB > $parseFileB + stack exec semantic parse -- --sexpression $fileB > $parseFileB status $diffFileAddA - stack exec semantic-diff -- --sexpression --no-index /dev/null $fileA > $diffFileAddA + stack exec semantic diff -- --sexpression /dev/null $fileA > $diffFileAddA status $diffFileRemoveA - stack exec semantic-diff -- --sexpression --no-index $fileA /dev/null > $diffFileRemoveA + stack exec semantic diff -- --sexpression $fileA /dev/null > $diffFileRemoveA status $diffFileAddB - stack exec semantic-diff -- --sexpression --no-index /dev/null $fileB > $diffFileAddB + stack exec semantic diff -- --sexpression --no-index /dev/null $fileB > $diffFileAddB status $diffFileRemoveB - stack exec semantic-diff -- --sexpression --no-index $fileB /dev/null > $diffFileRemoveB + stack exec semantic diff -- --sexpression $fileB /dev/null > $diffFileRemoveB status $diffFileAB - stack exec semantic-diff -- --sexpression --no-index $fileA $fileB > $diffFileAB + stack exec semantic diff -- --sexpression $fileA $fileB > $diffFileAB status $diffFileBA - stack exec semantic-diff -- --sexpression --no-index $fileB $fileA > $diffFileBA + stack exec semantic diff -- --sexpression $fileB $fileA > $diffFileBA } if [[ -d $1 ]]; then From e76597dcdbf624f4445690ac90c15bce69b56c2c Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 5 Apr 2017 15:45:30 -0700 Subject: [PATCH 04/17] Don't print out args in main --- src/SemanticDiff.hs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index f644ea218..e8d8a5ac4 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -27,9 +27,7 @@ main :: IO () main = do gitDir <- findGitDir alternates <- findAlternates - args@Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) - print args - + Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) text <- case programMode of Diff DiffArguments{..} -> runCommand $ do let render = case diffFormat of From 797a08521bc2f1b7ae9b0bdae8965313f309f3e6 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 5 Apr 2017 17:52:51 -0700 Subject: [PATCH 05/17] Only write trailing newline for json output --- src/SemanticDiff.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index e8d8a5ac4..36808511f 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -13,6 +13,7 @@ import Development.GitRev import Options.Applicative hiding (action) import Prologue hiding (concurrently, fst, snd, readFile) import qualified Data.ByteString as B +import qualified Data.ByteString.Lazy as BL import qualified Paths_semantic_diff as Library (version) import qualified Renderer as R import qualified Renderer.SExpression as R @@ -56,12 +57,12 @@ main = do R.JSONIndex -> parseIndex args' R.SExpressionTree -> parseSExpression args' outputPath <- getOutputPath outputFilePath - writeToOutput outputPath (text <> "\n") + writeToOutput outputPath text where encodeText = encodeUtf8 . R.unFile - encodeJSON = toS . encode - encodeSummaries = toS . encode + encodeJSON = toS . (<> "\n") . encode + encodeSummaries = toS . (<> "\n") . encode findGitDir = do pwd <- getCurrentDirectory From da129c2efc4550fc9168b2c4a1f4b9af32be0c02 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 6 Apr 2017 08:25:29 -0700 Subject: [PATCH 06/17] Remove left over import --- src/SemanticDiff.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index 36808511f..1a39ef65e 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -13,7 +13,6 @@ import Development.GitRev import Options.Applicative hiding (action) import Prologue hiding (concurrently, fst, snd, readFile) import qualified Data.ByteString as B -import qualified Data.ByteString.Lazy as BL import qualified Paths_semantic_diff as Library (version) import qualified Renderer as R import qualified Renderer.SExpression as R From 7f588418a7b900884bb5bc6ac56655c4b4fbedec Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Thu, 6 Apr 2017 09:39:23 -0700 Subject: [PATCH 07/17] Add in flag for JSON index format --- src/SemanticDiff.hs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index 1a39ef65e..0550211e8 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -97,12 +97,12 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc diffCommand = command "diff" (info diffArgumentsParser (progDesc "Show changes between commits or paths")) diffArgumentsParser = Diff <$> ( DiffArguments - <$> (flag R.Patch R.Patch (long "patch" <> help "Output a patch(1)-compatible diff (default)") - <|> flag' R.JSON (long "json" <> help "Output a json diff") - <|> flag' R.Split (long "split" <> help "Output a split diff") - <|> flag' R.Summary (long "summary" <> help "Output a diff summary") - <|> flag' R.SExpression (long "sexpression" <> help "Output an s-expression diff tree") - <|> flag' R.TOC (long "toc" <> help "Output a table of contents diff summary")) + <$> ( flag R.Patch R.Patch (long "patch" <> help "Output a patch(1)-compatible diff (default)") + <|> flag' R.JSON (long "json" <> help "Output a json diff") + <|> flag' R.Split (long "split" <> help "Output a split diff") + <|> flag' R.Summary (long "summary" <> help "Output a diff summary") + <|> flag' R.SExpression (long "sexpression" <> help "Output an s-expression diff tree") + <|> flag' R.TOC (long "toc" <> help "Output a table of contents diff summary") ) <*> ( DiffPaths <$> argument str (metavar "FILE_A") <*> argument str (metavar "FILE_B") @@ -116,8 +116,9 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print parse trees for a commit or paths")) parseArgumentsParser = Parse <$> ( ParseArguments - <$> ( flag R.SExpressionTree R.SExpressionTree (long "sexpression" <> help "Output s-expression formatted parse trees (default)") - <|> flag' R.JSONTree (long "json" <> help "Output JSON formatted parse trees")) + <$> ( flag R.SExpressionTree R.SExpressionTree (long "sexpression" <> help "Output s-expression parse trees (default)") + <|> flag' R.JSONTree (long "json" <> help "Output JSON parse trees") + <|> flag' R.JSONIndex (long "index" <> help "Output JSON parse trees in index format") ) <*> ( ParsePaths <$> some (argument str (metavar "FILES...")) <|> ParseCommit From a03f0da1063e6f7341ac95b4634690ec2adca088 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 11:26:37 -0700 Subject: [PATCH 08/17] Command.Parse doesn't need to know about Arguments --- src/Arguments.hs | 9 --------- src/Command/Parse.hs | 32 +++++++++++--------------------- src/SemanticDiff.hs | 16 ++++++++++++---- test/Command/Parse/Spec.hs | 29 ++++++++++++----------------- 4 files changed, 35 insertions(+), 51 deletions(-) diff --git a/src/Arguments.hs b/src/Arguments.hs index c10c5cf41..b0d2f4981 100644 --- a/src/Arguments.hs +++ b/src/Arguments.hs @@ -47,12 +47,3 @@ args gitDir sha1 sha2 paths format = Arguments } , outputFilePath = Nothing } - -parseArgs :: [String] -> R.ParseFormat -> ParseArguments -parseArgs paths format = ParseArguments - { parseFormat = format - , parseMode = ParsePaths paths - , debug = False - , gitDir = "" - , alternateObjectDirs = [] - } diff --git a/src/Command/Parse.hs b/src/Command/Parse.hs index 906819d35..7c4278159 100644 --- a/src/Command/Parse.hs +++ b/src/Command/Parse.hs @@ -1,7 +1,6 @@ {-# LANGUAGE DataKinds, GADTs, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeFamilies, TypeOperators #-} module Command.Parse where -import Arguments import Category import Data.Aeson (ToJSON, toJSON, encode, object, (.=)) import Data.Aeson.Types (Pair) @@ -65,19 +64,17 @@ parseNodeToJSONFields ParseNode{..} = <> [ "identifier" .= identifier | isJust identifier ] -- | Parses file contents into an SExpression format for the provided arguments. -parseSExpression :: ParseArguments -> IO ByteString -parseSExpression = - pure . printTerms TreeOnly <=< parse <=< sourceBlobsFromArgs +parseSExpression :: [SourceBlob] -> IO ByteString +parseSExpression blobs = + pure . printTerms TreeOnly =<< parse blobs where parse = traverse (\sourceBlob@SourceBlob{..} -> parserForType (toS (takeExtension path)) sourceBlob) type RAlgebra t a = Base t (t, a) -> a -parseRoot :: (FilePath -> f ParseNode -> root) -> (ParseNode -> [f ParseNode] -> f ParseNode) -> ParseArguments -> IO [root] -parseRoot construct combine args@ParseArguments{..} = do - blobs <- sourceBlobsFromArgs args - for blobs (\ sourceBlob@SourceBlob{..} -> do - parsedTerm <- parseWithDecorator (decorator source) path sourceBlob - pure $! construct path (para algebra parsedTerm)) +parseRoot :: Bool -> (FilePath -> f ParseNode -> root) -> (ParseNode -> [f ParseNode] -> f ParseNode) -> [SourceBlob] -> IO [root] +parseRoot debug construct combine blobs = for blobs (\ sourceBlob@SourceBlob{..} -> do + parsedTerm <- parseWithDecorator (decorator source) path sourceBlob + pure $! construct path (para algebra parsedTerm)) where algebra (annotation :< syntax) = combine (makeNode annotation (Prologue.fst <$> syntax)) (toList (Prologue.snd <$> syntax)) decorator = parseDecorator debug makeNode :: Record (Maybe SourceText ': DefaultFields) -> Syntax Text (Term (Syntax Text) (Record (Maybe SourceText ': DefaultFields))) -> ParseNode @@ -85,12 +82,12 @@ parseRoot construct combine args@ParseArguments{..} = do ParseNode (toS category) range head sourceSpan (identifierFor syntax) -- | Constructs IndexFile nodes for the provided arguments and encodes them to JSON. -parseIndex :: ParseArguments -> IO ByteString -parseIndex = fmap (toS . encode) . parseRoot IndexFile (\ node siblings -> node : concat siblings) +parseIndex :: Bool -> [SourceBlob] -> IO ByteString +parseIndex debug = fmap (toS . encode) . parseRoot debug IndexFile (\ node siblings -> node : concat siblings) -- | Constructs ParseTreeFile nodes for the provided arguments and encodes them to JSON. -parseTree :: ParseArguments -> IO ByteString -parseTree = fmap (toS . encode) . parseRoot ParseTreeFile Rose +parseTree :: Bool -> [SourceBlob] -> IO ByteString +parseTree debug = fmap (toS . encode) . parseRoot debug ParseTreeFile Rose -- | Determines the term decorator to use when parsing. parseDecorator :: (Functor f, HasField fields Range) => Bool -> (Source -> TermDecorator f fields (Maybe SourceText)) @@ -138,13 +135,6 @@ sourceBlobsFromSha commitSha gitDir filePaths = do identifierFor :: (HasField fields (Maybe SourceText), HasField fields Category, StringConv leaf Text) => Syntax leaf (Term (Syntax leaf) (Record fields)) -> Maybe Text identifierFor = fmap toS . extractLeafValue . unwrap <=< maybeIdentifier --- | For the file paths and commit sha provided, extract only the BlobEntries and represent them as SourceBlobs. -sourceBlobsFromArgs :: ParseArguments -> IO [SourceBlob] -sourceBlobsFromArgs ParseArguments{..} = - case parseMode of - ParseCommit sha paths -> sourceBlobsFromSha sha gitDir paths - ParsePaths paths -> sourceBlobsFromPaths paths - -- | Return a parser incorporating the provided TermDecorator. parseWithDecorator :: TermDecorator (Syntax Text) DefaultFields field -> FilePath -> Parser (Syntax Text) (Record (field ': DefaultFields)) parseWithDecorator decorator path blob = decorateTerm decorator <$> parserForType (toS (takeExtension path)) blob diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index 0550211e8..1a21257df 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -29,6 +29,7 @@ main = do alternates <- findAlternates Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) text <- case programMode of + Diff DiffArguments{..} -> runCommand $ do let render = case diffFormat of R.Split -> fmap encodeText . renderDiffs R.SplitRenderer @@ -51,10 +52,17 @@ main = do diff' <- maybeDiff terms pure (fromMaybe <$> pure (emptySourceBlob path) <*> blobs, diff') render (diffs >>= \ (blobs, diff) -> (,) blobs <$> toList diff) - Parse args'@ParseArguments{..} -> case parseFormat of - R.JSONTree -> parseTree args' - R.JSONIndex -> parseIndex args' - R.SExpressionTree -> parseSExpression args' + + Parse ParseArguments{..} -> do + let renderTree = case parseFormat of + R.JSONTree -> parseTree debug + R.JSONIndex -> parseIndex debug + R.SExpressionTree -> parseSExpression + blobs <- case parseMode of + ParseCommit sha paths -> sourceBlobsFromSha sha gitDir paths + ParsePaths paths -> sourceBlobsFromPaths paths + renderTree blobs + outputPath <- getOutputPath outputFilePath writeToOutput outputPath text diff --git a/test/Command/Parse/Spec.hs b/test/Command/Parse/Spec.hs index f602a2ea4..3c61b2293 100644 --- a/test/Command/Parse/Spec.hs +++ b/test/Command/Parse/Spec.hs @@ -1,25 +1,20 @@ module Command.Parse.Spec where import Command.Parse +import Control.Monad import Prelude import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall) import Test.Hspec.Expectations.Pretty -import Test.Hspec.LeanCheck -import Arguments -import Renderer spec :: Spec -spec = parallel $ do - context "parse" $ do - prop "all valid formats should produce output" $ - \format -> - case format of - SExpressionTree -> do - output <- parseSExpression $ parseArgs ["test/fixtures/ruby/and-or.A.rb"] format - output `shouldNotBe` "" - JSONIndex -> do - output <- parseIndex $ parseArgs ["test/fixtures/ruby/and-or.A.rb"] format - output `shouldNotBe` "" - JSONTree -> do - output <- parseTree $ parseArgs ["test/fixtures/ruby/and-or.A.rb"] format - output `shouldNotBe` "" +spec = parallel . context "parse" $ do + let blobs = sourceBlobsFromPaths ["test/fixtures/ruby/and-or.A.rb"] + it "should produce s-expression trees" $ do + output <- parseSExpression =<< blobs + output `shouldNotBe` "" + it "should produce JSON trees" $ do + output <- parseTree False =<< blobs + output `shouldNotBe` "" + it "should produce JSON index" $ do + output <- parseIndex False =<< blobs + output `shouldNotBe` "" From c862b7bcf5687ccfcbab6c02a05bf660f6a4c1d5 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 15:04:45 -0700 Subject: [PATCH 09/17] Remove benchmark target --- bench/Main.hs | 67 ---------------------------------------- bench/SemanticDiffPar.hs | 12 ------- semantic-diff.cabal | 17 ---------- 3 files changed, 96 deletions(-) delete mode 100644 bench/Main.hs delete mode 100644 bench/SemanticDiffPar.hs diff --git a/bench/Main.hs b/bench/Main.hs deleted file mode 100644 index ae5260f29..000000000 --- a/bench/Main.hs +++ /dev/null @@ -1,67 +0,0 @@ -{-# LANGUAGE DeriveAnyClass, FlexibleInstances #-} -{-# OPTIONS_GHC -fno-warn-orphans #-} -module Main where - -import Arguments -import Criterion.Main -import Data.Function -import Data.List (genericLength) -import Data.String -import Patch -import Prologue -import qualified Renderer as R -import SemanticDiff (fetchDiffs) -import qualified SemanticDiffPar -import SES -import System.Directory (makeAbsolute) - -main :: IO () -main = defaultMain - [ bgroup "ses" - [ bench "0,0" (nf (uncurry benchmarkSES) ([], [])) - , bench "1,1, =" (nf (uncurry benchmarkSES) ([lower], [lower])) - , bench "1,1, ≠" (nf (uncurry benchmarkSES) ([lower], [upper])) - , bench "10,10, =" (nf (uncurry benchmarkSES) (replicate 10 lower, replicate 10 lower)) - , bench "10,10, ≠" (nf (uncurry benchmarkSES) (replicate 10 lower, replicate 10 upper)) - , bench "100,100, =" (nf (uncurry benchmarkSES) (replicate 100 lower, replicate 100 lower)) - , bench "100,100, ≠" (nf (uncurry benchmarkSES) (replicate 100 lower, replicate 100 upper)) - ] - , syncAsyncBenchmark - ] - where lower = ['a'..'z'] - upper = ['A'..'Z'] - -benchmarkSES :: [String] -> [String] -> [Either String (Patch String)] -benchmarkSES = ses compare cost - where compare a b = if a == b then Just (Left a) else Nothing - cost = either (const 0) (sum . fmap genericLength) - -instance NFData a => NFData (Patch a) - -syncAsyncBenchmark :: Benchmark -syncAsyncBenchmark = - bgroup "async vs par" [ - bench "async" . whnfIO $ SemanticDiff.fetchDiffs =<< theArgs, - bench "par" . whnfIO $ SemanticDiffPar.fetchDiffs =<< theArgs - ] - -theArgs :: IO Arguments -theArgs = do - jqueryPath <- makeAbsolute "test/repos/jquery" - pure $ args jqueryPath sha1 sha2 files R.Patch - where - sha1 = "70526981916945dc4093e116a3de61b1777d4718" - sha2 = "e5ffcb0838c894e26f4ff32dfec162cf624d8d7d" - files = [ - "src/manipulation/getAll.js", - "src/manipulation/support.js", - "src/manipulation/wrapMap.js", - "src/offset.js", - "test/unit/css.js", - "test/unit/deferred.js", - "test/unit/deprecated.js", - "test/unit/effects.js", - "test/unit/event.js", - "test/unit/offset.js", - "test/unit/wrap.js" - ] diff --git a/bench/SemanticDiffPar.hs b/bench/SemanticDiffPar.hs deleted file mode 100644 index cfa440087..000000000 --- a/bench/SemanticDiffPar.hs +++ /dev/null @@ -1,12 +0,0 @@ -module SemanticDiffPar where - -import Arguments -import qualified Control.Monad.Par.IO as ParIO -import Control.Monad.Reader -import qualified Data.Text as T -import Prologue -import qualified Renderer as R -import SemanticDiff - -fetchDiffs :: Arguments -> IO [T.Text] -fetchDiffs args@Arguments{..} = pure . pure . R.concatOutputs =<< (ParIO.runParIO . liftIO $ for filePaths (fetchDiff args)) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index d9d9c6c29..6e5b6fbf3 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -127,23 +127,6 @@ executable semantic default-language: Haskell2010 default-extensions: FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards -benchmark semantic-diff-bench - type: exitcode-stdio-1.0 - main-is: Main.hs - hs-source-dirs: bench - other-modules: SemanticDiffPar - build-depends: base - , criterion - , directory - , leancheck - , monad-par - , mtl - , semantic-diff - , text >= 1.2.1.3 - ghc-options: -threaded -rtsopts -with-rtsopts=-N -j -O2 -static -pgml=script/g++ - default-language: Haskell2010 - default-extensions: FlexibleInstances, OverloadedStrings, NoImplicitPrelude, RecordWildCards - test-suite test type: exitcode-stdio-1.0 hs-source-dirs: test From e9edb42e539a04c75f0a4d952220e4d98b60b226 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 15:10:24 -0700 Subject: [PATCH 10/17] Remove render formats and use render/encode functions directly --- src/Arguments.hs | 44 +++++++++--------- src/Command.hs | 59 +++++++++++++++++++++++- src/Command/Parse.hs | 6 ++- src/Renderer.hs | 34 +++++--------- src/Renderer/SExpression.hs | 1 + src/SemanticDiff.hs | 89 +++++++++++++++---------------------- test/Command/Parse/Spec.hs | 2 +- 7 files changed, 134 insertions(+), 101 deletions(-) diff --git a/src/Arguments.hs b/src/Arguments.hs index b0d2f4981..b257e063e 100644 --- a/src/Arguments.hs +++ b/src/Arguments.hs @@ -2,48 +2,50 @@ {-# LANGUAGE DuplicateRecordFields #-} module Arguments where +import Command import Data.Maybe -import Prologue hiding ((<>)) import Prelude -import qualified Renderer as R + data DiffMode = DiffCommits String String [FilePath] | DiffPaths FilePath FilePath - deriving (Show) + deriving Show data DiffArguments = DiffArguments - { diffFormat :: R.Format + { encodeDiff :: DiffEncoder , diffMode :: DiffMode , gitDir :: FilePath , alternateObjectDirs :: [FilePath] } - deriving (Show) data ParseMode = ParseCommit String [FilePath] | ParsePaths [FilePath] - deriving (Show) + deriving Show data ParseArguments = ParseArguments - { parseFormat :: R.ParseFormat + { renderParseTree :: ParseTreeRenderer , parseMode :: ParseMode , debug :: Bool , gitDir :: FilePath , alternateObjectDirs :: [FilePath] } - deriving (Show) data ProgramMode = Parse ParseArguments | Diff DiffArguments - deriving (Show) + deriving Show data Arguments = Arguments { programMode :: ProgramMode , outputFilePath :: Maybe FilePath - } deriving (Show) + } deriving Show --- | Quickly assemble an Arguments data record with defaults. -args :: FilePath -> String -> String -> [String] -> R.Format -> Arguments -args gitDir sha1 sha2 paths format = Arguments - { programMode = Diff DiffArguments - { diffFormat = format - , diffMode = DiffCommits sha1 sha2 paths - , gitDir = gitDir - , alternateObjectDirs = [] - } - , outputFilePath = Nothing - } + +instance Show DiffArguments where + showsPrec d DiffArguments{..} = showParen (d >= 10) $ showString "DiffArguments " + . showsPrec 10 (encodeDiff []) . showChar ' ' + . showsPrec 10 diffMode . showChar ' ' + . showsPrec 10 gitDir . showChar ' ' + . showsPrec 10 alternateObjectDirs + +instance Show ParseArguments where + showsPrec d ParseArguments{..} = showParen (d >= 10) $ showString "ParseArguments " + -- . showsPrec 10 (renderParseTree []) . showChar ' ' + . showsPrec 10 parseMode . showChar ' ' + . showsPrec 10 debug . showChar ' ' + . showsPrec 10 gitDir . showChar ' ' + . showsPrec 10 alternateObjectDirs diff --git a/src/Command.hs b/src/Command.hs index 6f2b2d880..adddf1ef0 100644 --- a/src/Command.hs +++ b/src/Command.hs @@ -10,6 +10,14 @@ module Command , maybeDiff , renderDiffs , concurrently +, patch +, split +, json +, summary +, sExpression +, toc +, DiffEncoder +, ParseTreeRenderer -- Evaluation , runCommand ) where @@ -20,15 +28,15 @@ import Control.Exception (catch) import Control.Monad.Free.Freer import Control.Monad.IO.Class import Control.Parallel.Strategies +import Data.Aeson hiding (json) import qualified Data.ByteString as B import Data.Functor.Both +import Data.Functor.Classes import Data.List ((\\), nub) import Data.RandomWalkSimilarity import Data.Record import Data.String import Diff -import Info -import Interpreter import GHC.Conc (numCapabilities) import qualified Git import Git.Blob @@ -37,14 +45,19 @@ import Git.Libgit2.Backend import Git.Repository import Git.Types import GitmonClient +import Info +import Interpreter import Language import Patch import Prologue hiding (concurrently, Concurrently, readFile) +import qualified Renderer as R +import qualified Renderer.SExpression as R import Renderer import Source import Syntax import System.FilePath import Term +import Text.Show -- | High-level commands encapsulating the work done to perform a diff or parse operation. @@ -188,5 +201,47 @@ runRenderDiffs :: Monoid output => DiffRenderer fields output -> [(Both SourceBl runRenderDiffs = runDiffRenderer +type DiffEncoder = [(Both SourceBlob, Diff (Syntax Text) (Record DefaultFields))] -> Command ByteString + +patch :: DiffEncoder +patch = fmap encodeText . renderDiffs R.PatchRenderer + +split :: DiffEncoder +split = fmap encodeText . renderDiffs R.SplitRenderer + +json :: DiffEncoder +json = fmap encodeJSON . renderDiffs R.JSONDiffRenderer + +summary :: DiffEncoder +summary = fmap encodeSummaries . renderDiffs R.SummaryRenderer + +sExpression :: DiffEncoder +sExpression = renderDiffs (R.SExpressionDiffRenderer R.TreeOnly) + +toc :: DiffEncoder +toc = fmap encodeSummaries . renderDiffs R.ToCRenderer + +encodeJSON :: Map Text Value -> ByteString +encodeJSON = toS . (<> "\n") . encode + +encodeText :: File -> ByteString +encodeText = encodeUtf8 . R.unFile + +encodeSummaries :: Summaries -> ByteString +encodeSummaries = toS . (<> "\n") . encode + + instance MonadIO Command where liftIO io = LiftIO io `Then` return + +instance Show1 CommandF where + liftShowsPrec sp sl d command = case command of + ReadFile path -> showsUnaryWith showsPrec "ReadFile" d path + ReadFilesAtSHAs gitDir alternates paths shas -> showsQuaternaryWith showsPrec showsPrec showsPrec showsPrec "ReadFilesAtSHAs" d gitDir alternates paths shas + where showsQuaternaryWith sp1 sp2 sp3 sp4 name d x y z w = showParen (d > 10) $ + showString name . showChar ' ' . sp1 11 x . showChar ' ' . sp2 11 y . showChar ' ' . sp3 11 z . showChar ' ' . sp4 11 w + Parse language _ -> showsBinaryWith showsPrec (const showChar) "Parse" d language '_' + Diff _ -> showsUnaryWith (const showChar) "Diff" d '_' + RenderDiffs renderer _ -> showsBinaryWith showsPrec (const showChar) "RenderDiffs" d renderer '_' + Concurrently commands f -> showsBinaryWith (liftShowsPrec sp sl) (const showChar) "Concurrently" d (traverse f commands) '_' + LiftIO _ -> showsUnaryWith (const showChar) "LiftIO" d '_' diff --git a/src/Command/Parse.hs b/src/Command/Parse.hs index 7c4278159..6fafff23c 100644 --- a/src/Command/Parse.hs +++ b/src/Command/Parse.hs @@ -31,6 +31,8 @@ import Text.Parser.TreeSitter.JavaScript import Text.Parser.TreeSitter.Ruby import Text.Parser.TreeSitter.TypeScript +type ParseTreeRenderer = Bool -> [SourceBlob] -> IO ByteString + data ParseTreeFile = ParseTreeFile { parseTreeFilePath :: FilePath, node :: Rose ParseNode } deriving (Show) data Rose a = Rose a [Rose a] @@ -64,8 +66,8 @@ parseNodeToJSONFields ParseNode{..} = <> [ "identifier" .= identifier | isJust identifier ] -- | Parses file contents into an SExpression format for the provided arguments. -parseSExpression :: [SourceBlob] -> IO ByteString -parseSExpression blobs = +parseSExpression :: Bool -> [SourceBlob] -> IO ByteString +parseSExpression _ blobs = pure . printTerms TreeOnly =<< parse blobs where parse = traverse (\sourceBlob@SourceBlob{..} -> parserForType (toS (takeExtension path)) sourceBlob) diff --git a/src/Renderer.hs b/src/Renderer.hs index 794e703e5..23aa0d28c 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -2,16 +2,15 @@ module Renderer ( DiffRenderer(..) , runDiffRenderer -, Format(..) -, ParseFormat(..) , Summaries(..) , File(..) ) where import Data.Aeson (ToJSON, Value) import Data.Functor.Both +import Data.Functor.Classes +import Text.Show import Data.Map as Map hiding (null) -import Data.Functor.Listable import Data.Record import Diff import Info @@ -33,6 +32,7 @@ data DiffRenderer fields output where SExpressionDiffRenderer :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> DiffRenderer fields ByteString ToCRenderer :: HasDefaultFields fields => DiffRenderer fields Summaries + runDiffRenderer :: Monoid output => DiffRenderer fields output -> [(Both SourceBlob, Diff (Syntax Text) (Record fields))] -> output runDiffRenderer renderer = foldMap . uncurry $ case renderer of SplitRenderer -> (File .) . R.split @@ -42,29 +42,17 @@ runDiffRenderer renderer = foldMap . uncurry $ case renderer of SExpressionDiffRenderer format -> R.sExpression format ToCRenderer -> R.toc --- | The available types of diff rendering. -data Format = Split | Patch | JSON | Summary | SExpression | TOC - deriving (Show) - -data ParseFormat = JSONTree | JSONIndex | SExpressionTree - deriving (Show) - newtype File = File { unFile :: Text } deriving Show +instance Show (DiffRenderer fields output) where + showsPrec _ SplitRenderer = showString "SplitRenderer" + showsPrec _ PatchRenderer = showString "PatchRenderer" + showsPrec _ JSONDiffRenderer = showString "JSONDiffRenderer" + showsPrec _ SummaryRenderer = showString "SummaryRenderer" + showsPrec d (SExpressionDiffRenderer format) = showsUnaryWith showsPrec "SExpressionDiffRenderer" d format + showsPrec _ ToCRenderer = showString "ToCRenderer" + instance Monoid File where mempty = File mempty mappend (File a) (File b) = File (a <> "\n" <> b) - -instance Listable Format where - tiers = cons0 Split - \/ cons0 Patch - \/ cons0 JSON - \/ cons0 Summary - \/ cons0 SExpression - \/ cons0 TOC - -instance Listable ParseFormat where - tiers = cons0 JSONTree - \/ cons0 JSONIndex - \/ cons0 SExpressionTree diff --git a/src/Renderer/SExpression.hs b/src/Renderer/SExpression.hs index b4c7b78ce..f4057531d 100644 --- a/src/Renderer/SExpression.hs +++ b/src/Renderer/SExpression.hs @@ -15,6 +15,7 @@ import Syntax import Term data SExpressionFormat = TreeOnly | TreeAndRanges + deriving (Show) sExpression :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> Both SourceBlob -> Diff (Syntax Text) (Record fields) -> ByteString sExpression format _ diff = printDiff diff 0 format diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index 1a21257df..a1c9e9a3e 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -2,9 +2,8 @@ module SemanticDiff (main) where import Arguments -import Command +import Command hiding (diff, parse) import Command.Parse -import Data.Aeson import Data.Functor.Both import Data.List.Split (splitWhen) import Data.String @@ -14,8 +13,6 @@ import Options.Applicative hiding (action) import Prologue hiding (concurrently, fst, snd, readFile) import qualified Data.ByteString as B import qualified Paths_semantic_diff as Library (version) -import qualified Renderer as R -import qualified Renderer.SExpression as R import Source import System.Directory import System.Environment @@ -28,49 +25,13 @@ main = do gitDir <- findGitDir alternates <- findAlternates Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) - text <- case programMode of - - Diff DiffArguments{..} -> runCommand $ do - let render = case diffFormat of - R.Split -> fmap encodeText . renderDiffs R.SplitRenderer - R.Patch -> fmap encodeText . renderDiffs R.PatchRenderer - R.JSON -> fmap encodeJSON . renderDiffs R.JSONDiffRenderer - R.Summary -> fmap encodeSummaries . renderDiffs R.SummaryRenderer - R.SExpression -> renderDiffs (R.SExpressionDiffRenderer R.TreeOnly) - R.TOC -> fmap encodeSummaries . renderDiffs R.ToCRenderer - diffs <- case diffMode of - DiffPaths pathA pathB -> do - let paths = both pathA pathB - blobs <- traverse readFile paths - terms <- traverse (traverse parseBlob) blobs - diff' <- maybeDiff terms - pure [(fromMaybe . emptySourceBlob <$> paths <*> blobs, diff')] - DiffCommits sha1 sha2 paths -> do - blobPairs <- readFilesAtSHAs gitDir alternateObjectDirs paths (both sha1 sha2) - concurrently blobPairs . uncurry $ \ path blobs -> do - terms <- concurrently blobs (traverse parseBlob) - diff' <- maybeDiff terms - pure (fromMaybe <$> pure (emptySourceBlob path) <*> blobs, diff') - render (diffs >>= \ (blobs, diff) -> (,) blobs <$> toList diff) - - Parse ParseArguments{..} -> do - let renderTree = case parseFormat of - R.JSONTree -> parseTree debug - R.JSONIndex -> parseIndex debug - R.SExpressionTree -> parseSExpression - blobs <- case parseMode of - ParseCommit sha paths -> sourceBlobsFromSha sha gitDir paths - ParsePaths paths -> sourceBlobsFromPaths paths - renderTree blobs - outputPath <- getOutputPath outputFilePath + text <- case programMode of + Diff args -> diff args + Parse args -> parse args writeToOutput outputPath text where - encodeText = encodeUtf8 . R.unFile - encodeJSON = toS . (<> "\n") . encode - encodeSummaries = toS . (<> "\n") . encode - findGitDir = do pwd <- getCurrentDirectory fromMaybe pwd <$> lookupEnv "GIT_DIR" @@ -89,6 +50,30 @@ main = do writeToOutput :: Maybe FilePath -> ByteString -> IO () writeToOutput = maybe B.putStr B.writeFile +diff :: DiffArguments -> IO ByteString +diff DiffArguments{..} = runCommand $ do + diffs <- case diffMode of + DiffPaths pathA pathB -> do + let paths = both pathA pathB + blobs <- traverse readFile paths + terms <- traverse (traverse parseBlob) blobs + diff' <- maybeDiff terms + pure [(fromMaybe . emptySourceBlob <$> paths <*> blobs, diff')] + DiffCommits sha1 sha2 paths -> do + blobPairs <- readFilesAtSHAs gitDir alternateObjectDirs paths (both sha1 sha2) + concurrently blobPairs . uncurry $ \ path blobs -> do + terms <- concurrently blobs (traverse parseBlob) + diff' <- maybeDiff terms + pure (fromMaybe <$> pure (emptySourceBlob path) <*> blobs, diff') + encodeDiff (diffs >>= \ (blobs, diff) -> (,) blobs <$> toList diff) + +parse :: ParseArguments -> IO ByteString +parse ParseArguments{..} = do + blobs <- case parseMode of + ParseCommit sha paths -> sourceBlobsFromSha sha gitDir paths + ParsePaths paths -> sourceBlobsFromPaths paths + renderParseTree debug blobs + -- | A parser for the application's command-line arguments. arguments :: FilePath -> [FilePath] -> ParserInfo Arguments arguments gitDir alternates = info (version <*> helper <*> argumentsParser) description @@ -105,12 +90,12 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc diffCommand = command "diff" (info diffArgumentsParser (progDesc "Show changes between commits or paths")) diffArgumentsParser = Diff <$> ( DiffArguments - <$> ( flag R.Patch R.Patch (long "patch" <> help "Output a patch(1)-compatible diff (default)") - <|> flag' R.JSON (long "json" <> help "Output a json diff") - <|> flag' R.Split (long "split" <> help "Output a split diff") - <|> flag' R.Summary (long "summary" <> help "Output a diff summary") - <|> flag' R.SExpression (long "sexpression" <> help "Output an s-expression diff tree") - <|> flag' R.TOC (long "toc" <> help "Output a table of contents diff summary") ) + <$> ( flag patch patch (long "patch" <> help "Output a patch(1)-compatible diff (default)") + <|> flag' split (long "split" <> help "Output a split diff") + <|> flag' json (long "json" <> help "Output a json diff") + <|> flag' summary (long "summary" <> help "Output a diff summary") + <|> flag' sExpression (long "sexpression" <> help "Output an s-expression diff tree") + <|> flag' toc (long "toc" <> help "Output a table of contents diff summary") ) <*> ( DiffPaths <$> argument str (metavar "FILE_A") <*> argument str (metavar "FILE_B") @@ -124,9 +109,9 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print parse trees for a commit or paths")) parseArgumentsParser = Parse <$> ( ParseArguments - <$> ( flag R.SExpressionTree R.SExpressionTree (long "sexpression" <> help "Output s-expression parse trees (default)") - <|> flag' R.JSONTree (long "json" <> help "Output JSON parse trees") - <|> flag' R.JSONIndex (long "index" <> help "Output JSON parse trees in index format") ) + <$> ( flag parseSExpression parseSExpression (long "sexpression" <> help "Output s-expression parse trees (default)") + <|> flag' parseTree (long "json" <> help "Output JSON parse trees") + <|> flag' parseIndex (long "index" <> help "Output JSON parse trees in index format") ) <*> ( ParsePaths <$> some (argument str (metavar "FILES...")) <|> ParseCommit diff --git a/test/Command/Parse/Spec.hs b/test/Command/Parse/Spec.hs index 3c61b2293..18c111b0a 100644 --- a/test/Command/Parse/Spec.hs +++ b/test/Command/Parse/Spec.hs @@ -10,7 +10,7 @@ spec :: Spec spec = parallel . context "parse" $ do let blobs = sourceBlobsFromPaths ["test/fixtures/ruby/and-or.A.rb"] it "should produce s-expression trees" $ do - output <- parseSExpression =<< blobs + output <- parseSExpression False =<< blobs output `shouldNotBe` "" it "should produce JSON trees" $ do output <- parseTree False =<< blobs From 5b1d104383b849bbfb48e3a98f90384bf4e0ac9e Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 16:07:00 -0700 Subject: [PATCH 11/17] Clarify naming a bit --- src/Arguments.hs | 2 +- src/Command.hs | 38 ++++++++++++++++++++------------------ src/Command/Parse.hs | 14 ++++++-------- src/SemanticDiff.hs | 32 ++++++++++++++++---------------- test/Command/Parse/Spec.hs | 6 +++--- 5 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Arguments.hs b/src/Arguments.hs index b257e063e..fa3d18906 100644 --- a/src/Arguments.hs +++ b/src/Arguments.hs @@ -44,7 +44,7 @@ instance Show DiffArguments where instance Show ParseArguments where showsPrec d ParseArguments{..} = showParen (d >= 10) $ showString "ParseArguments " - -- . showsPrec 10 (renderParseTree []) . showChar ' ' + -- . showsPrec 10 (renderParseTree False []) . showChar ' ' . showsPrec 10 parseMode . showChar ' ' . showsPrec 10 debug . showChar ' ' . showsPrec 10 gitDir . showChar ' ' diff --git a/src/Command.hs b/src/Command.hs index adddf1ef0..fdeac9561 100644 --- a/src/Command.hs +++ b/src/Command.hs @@ -10,12 +10,12 @@ module Command , maybeDiff , renderDiffs , concurrently -, patch -, split -, json -, summary -, sExpression -, toc +, patchDiff +, splitDiff +, jsonDiff +, summaryDiff +, sExpressionDiff +, tocDiff , DiffEncoder , ParseTreeRenderer -- Evaluation @@ -201,25 +201,27 @@ runRenderDiffs :: Monoid output => DiffRenderer fields output -> [(Both SourceBl runRenderDiffs = runDiffRenderer +type ParseTreeRenderer = Bool -> [SourceBlob] -> IO ByteString + type DiffEncoder = [(Both SourceBlob, Diff (Syntax Text) (Record DefaultFields))] -> Command ByteString -patch :: DiffEncoder -patch = fmap encodeText . renderDiffs R.PatchRenderer +patchDiff :: DiffEncoder +patchDiff = fmap encodeText . renderDiffs R.PatchRenderer -split :: DiffEncoder -split = fmap encodeText . renderDiffs R.SplitRenderer +splitDiff :: DiffEncoder +splitDiff = fmap encodeText . renderDiffs R.SplitRenderer -json :: DiffEncoder -json = fmap encodeJSON . renderDiffs R.JSONDiffRenderer +jsonDiff :: DiffEncoder +jsonDiff = fmap encodeJSON . renderDiffs R.JSONDiffRenderer -summary :: DiffEncoder -summary = fmap encodeSummaries . renderDiffs R.SummaryRenderer +summaryDiff :: DiffEncoder +summaryDiff = fmap encodeSummaries . renderDiffs R.SummaryRenderer -sExpression :: DiffEncoder -sExpression = renderDiffs (R.SExpressionDiffRenderer R.TreeOnly) +sExpressionDiff :: DiffEncoder +sExpressionDiff = renderDiffs (R.SExpressionDiffRenderer R.TreeOnly) -toc :: DiffEncoder -toc = fmap encodeSummaries . renderDiffs R.ToCRenderer +tocDiff :: DiffEncoder +tocDiff = fmap encodeSummaries . renderDiffs R.ToCRenderer encodeJSON :: Map Text Value -> ByteString encodeJSON = toS . (<> "\n") . encode diff --git a/src/Command/Parse.hs b/src/Command/Parse.hs index 6fafff23c..f21965bad 100644 --- a/src/Command/Parse.hs +++ b/src/Command/Parse.hs @@ -31,8 +31,6 @@ import Text.Parser.TreeSitter.JavaScript import Text.Parser.TreeSitter.Ruby import Text.Parser.TreeSitter.TypeScript -type ParseTreeRenderer = Bool -> [SourceBlob] -> IO ByteString - data ParseTreeFile = ParseTreeFile { parseTreeFilePath :: FilePath, node :: Rose ParseNode } deriving (Show) data Rose a = Rose a [Rose a] @@ -66,8 +64,8 @@ parseNodeToJSONFields ParseNode{..} = <> [ "identifier" .= identifier | isJust identifier ] -- | Parses file contents into an SExpression format for the provided arguments. -parseSExpression :: Bool -> [SourceBlob] -> IO ByteString -parseSExpression _ blobs = +sExpressionParseTree :: Bool -> [SourceBlob] -> IO ByteString +sExpressionParseTree _ blobs = pure . printTerms TreeOnly =<< parse blobs where parse = traverse (\sourceBlob@SourceBlob{..} -> parserForType (toS (takeExtension path)) sourceBlob) @@ -84,12 +82,12 @@ parseRoot debug construct combine blobs = for blobs (\ sourceBlob@SourceBlob{..} ParseNode (toS category) range head sourceSpan (identifierFor syntax) -- | Constructs IndexFile nodes for the provided arguments and encodes them to JSON. -parseIndex :: Bool -> [SourceBlob] -> IO ByteString -parseIndex debug = fmap (toS . encode) . parseRoot debug IndexFile (\ node siblings -> node : concat siblings) +jsonIndexParseTree :: Bool -> [SourceBlob] -> IO ByteString +jsonIndexParseTree debug = fmap (toS . encode) . parseRoot debug IndexFile (\ node siblings -> node : concat siblings) -- | Constructs ParseTreeFile nodes for the provided arguments and encodes them to JSON. -parseTree :: Bool -> [SourceBlob] -> IO ByteString -parseTree debug = fmap (toS . encode) . parseRoot debug ParseTreeFile Rose +jsonParseTree :: Bool -> [SourceBlob] -> IO ByteString +jsonParseTree debug = fmap (toS . encode) . parseRoot debug ParseTreeFile Rose -- | Determines the term decorator to use when parsing. parseDecorator :: (Functor f, HasField fields Range) => Bool -> (Source -> TermDecorator f fields (Maybe SourceText)) diff --git a/src/SemanticDiff.hs b/src/SemanticDiff.hs index a1c9e9a3e..15edad7eb 100644 --- a/src/SemanticDiff.hs +++ b/src/SemanticDiff.hs @@ -2,7 +2,7 @@ module SemanticDiff (main) where import Arguments -import Command hiding (diff, parse) +import Command import Command.Parse import Data.Functor.Both import Data.List.Split (splitWhen) @@ -27,8 +27,8 @@ main = do Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) outputPath <- getOutputPath outputFilePath text <- case programMode of - Diff args -> diff args - Parse args -> parse args + Diff args -> runDiff args + Parse args -> runParse args writeToOutput outputPath text where @@ -50,8 +50,8 @@ main = do writeToOutput :: Maybe FilePath -> ByteString -> IO () writeToOutput = maybe B.putStr B.writeFile -diff :: DiffArguments -> IO ByteString -diff DiffArguments{..} = runCommand $ do +runDiff :: DiffArguments -> IO ByteString +runDiff DiffArguments{..} = runCommand $ do diffs <- case diffMode of DiffPaths pathA pathB -> do let paths = both pathA pathB @@ -67,8 +67,8 @@ diff DiffArguments{..} = runCommand $ do pure (fromMaybe <$> pure (emptySourceBlob path) <*> blobs, diff') encodeDiff (diffs >>= \ (blobs, diff) -> (,) blobs <$> toList diff) -parse :: ParseArguments -> IO ByteString -parse ParseArguments{..} = do +runParse :: ParseArguments -> IO ByteString +runParse ParseArguments{..} = do blobs <- case parseMode of ParseCommit sha paths -> sourceBlobsFromSha sha gitDir paths ParsePaths paths -> sourceBlobsFromPaths paths @@ -90,12 +90,12 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc diffCommand = command "diff" (info diffArgumentsParser (progDesc "Show changes between commits or paths")) diffArgumentsParser = Diff <$> ( DiffArguments - <$> ( flag patch patch (long "patch" <> help "Output a patch(1)-compatible diff (default)") - <|> flag' split (long "split" <> help "Output a split diff") - <|> flag' json (long "json" <> help "Output a json diff") - <|> flag' summary (long "summary" <> help "Output a diff summary") - <|> flag' sExpression (long "sexpression" <> help "Output an s-expression diff tree") - <|> flag' toc (long "toc" <> help "Output a table of contents diff summary") ) + <$> ( flag patchDiff patchDiff (long "patch" <> help "Output a patch(1)-compatible diff (default)") + <|> flag' splitDiff (long "split" <> help "Output a split diff") + <|> flag' jsonDiff (long "json" <> help "Output a json diff") + <|> flag' summaryDiff (long "summary" <> help "Output a diff summary") + <|> flag' sExpressionDiff (long "sexpression" <> help "Output an s-expression diff tree") + <|> flag' tocDiff (long "toc" <> help "Output a table of contents diff summary") ) <*> ( DiffPaths <$> argument str (metavar "FILE_A") <*> argument str (metavar "FILE_B") @@ -109,9 +109,9 @@ arguments gitDir alternates = info (version <*> helper <*> argumentsParser) desc parseCommand = command "parse" (info parseArgumentsParser (progDesc "Print parse trees for a commit or paths")) parseArgumentsParser = Parse <$> ( ParseArguments - <$> ( flag parseSExpression parseSExpression (long "sexpression" <> help "Output s-expression parse trees (default)") - <|> flag' parseTree (long "json" <> help "Output JSON parse trees") - <|> flag' parseIndex (long "index" <> help "Output JSON parse trees in index format") ) + <$> ( flag sExpressionParseTree sExpressionParseTree (long "sexpression" <> help "Output s-expression parse trees (default)") + <|> flag' jsonParseTree (long "json" <> help "Output JSON parse trees") + <|> flag' jsonIndexParseTree (long "index" <> help "Output JSON parse trees in index format") ) <*> ( ParsePaths <$> some (argument str (metavar "FILES...")) <|> ParseCommit diff --git a/test/Command/Parse/Spec.hs b/test/Command/Parse/Spec.hs index 18c111b0a..6070d95ae 100644 --- a/test/Command/Parse/Spec.hs +++ b/test/Command/Parse/Spec.hs @@ -10,11 +10,11 @@ spec :: Spec spec = parallel . context "parse" $ do let blobs = sourceBlobsFromPaths ["test/fixtures/ruby/and-or.A.rb"] it "should produce s-expression trees" $ do - output <- parseSExpression False =<< blobs + output <- sExpressionParseTree False =<< blobs output `shouldNotBe` "" it "should produce JSON trees" $ do - output <- parseTree False =<< blobs + output <- jsonParseTree False =<< blobs output `shouldNotBe` "" it "should produce JSON index" $ do - output <- parseIndex False =<< blobs + output <- jsonIndexParseTree False =<< blobs output `shouldNotBe` "" From 6e3af1b75f01c4beafce6546eeb75fa2bd1c959a Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 17:10:34 -0700 Subject: [PATCH 12/17] SemanticDiff.hs => Semantic.hs and smoke test specs --- app/Main.hs | 2 +- semantic-diff.cabal | 3 ++- src/Arguments.hs | 24 +++++------------------- src/Command.hs | 22 ++++++++++++++++++++++ src/{SemanticDiff.hs => Semantic.hs} | 5 +++-- test/SemanticSpec.hs | 23 +++++++++++++++++++++++ test/Spec.hs | 2 ++ 7 files changed, 58 insertions(+), 23 deletions(-) rename src/{SemanticDiff.hs => Semantic.hs} (97%) create mode 100644 test/SemanticSpec.hs diff --git a/app/Main.hs b/app/Main.hs index f913510b8..b9c831403 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,3 +1,3 @@ module Main (main) where -import SemanticDiff (main) +import Semantic (main) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 6e5b6fbf3..6fe8d070b 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -50,7 +50,7 @@ library , Renderer.Summary , Renderer.SExpression , Renderer.TOC - , SemanticDiff + , Semantic , SES , SES.Myers , Source @@ -138,6 +138,7 @@ test-suite test , Data.Mergeable.Spec , Data.RandomWalkSimilarity.Spec , DiffSpec + , SemanticSpec , SummarySpec , GitmonClientSpec , InterpreterSpec diff --git a/src/Arguments.hs b/src/Arguments.hs index fa3d18906..26d35cc2e 100644 --- a/src/Arguments.hs +++ b/src/Arguments.hs @@ -1,5 +1,5 @@ -{-# OPTIONS_GHC -funbox-strict-fields #-} {-# LANGUAGE DuplicateRecordFields #-} +{-# OPTIONS_GHC -funbox-strict-fields #-} module Arguments where import Command @@ -14,7 +14,8 @@ data DiffArguments = DiffArguments { encodeDiff :: DiffEncoder , diffMode :: DiffMode , gitDir :: FilePath - , alternateObjectDirs :: [FilePath] } + , alternateObjectDirs :: [FilePath] + } deriving Show data ParseMode = ParseCommit String [FilePath] | ParsePaths [FilePath] deriving Show @@ -24,7 +25,8 @@ data ParseArguments = ParseArguments , parseMode :: ParseMode , debug :: Bool , gitDir :: FilePath - , alternateObjectDirs :: [FilePath] } + , alternateObjectDirs :: [FilePath] + } deriving Show data ProgramMode = Parse ParseArguments | Diff DiffArguments deriving Show @@ -33,19 +35,3 @@ data Arguments = Arguments { programMode :: ProgramMode , outputFilePath :: Maybe FilePath } deriving Show - - -instance Show DiffArguments where - showsPrec d DiffArguments{..} = showParen (d >= 10) $ showString "DiffArguments " - . showsPrec 10 (encodeDiff []) . showChar ' ' - . showsPrec 10 diffMode . showChar ' ' - . showsPrec 10 gitDir . showChar ' ' - . showsPrec 10 alternateObjectDirs - -instance Show ParseArguments where - showsPrec d ParseArguments{..} = showParen (d >= 10) $ showString "ParseArguments " - -- . showsPrec 10 (renderParseTree False []) . showChar ' ' - . showsPrec 10 parseMode . showChar ' ' - . showsPrec 10 debug . showChar ' ' - . showsPrec 10 gitDir . showChar ' ' - . showsPrec 10 alternateObjectDirs diff --git a/src/Command.hs b/src/Command.hs index fdeac9561..7223040f0 100644 --- a/src/Command.hs +++ b/src/Command.hs @@ -1,4 +1,5 @@ {-# LANGUAGE DataKinds, GADTs #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module Command ( Command -- Constructors @@ -32,6 +33,7 @@ import Data.Aeson hiding (json) import qualified Data.ByteString as B import Data.Functor.Both import Data.Functor.Classes +import Data.Functor.Listable import Data.List ((\\), nub) import Data.RandomWalkSimilarity import Data.Record @@ -233,6 +235,26 @@ encodeSummaries :: Summaries -> ByteString encodeSummaries = toS . (<> "\n") . encode +instance Show ParseTreeRenderer where + showsPrec d _ = showParen (d >= 10) $ showString "ParseTreeRenderer " + +instance Listable ParseTreeRenderer where + tiers = cons0 jsonParseTree + \/ cons0 jsonIndexParseTree + \/ cons0 sExpressionParseTree + +instance Show DiffEncoder where + showsPrec d encodeDiff = showParen (d >= 10) $ showString "DiffEncoder " + . showsPrec 10 (encodeDiff []) . showChar ' ' + +instance Listable DiffEncoder where + tiers = cons0 patchDiff + \/ cons0 splitDiff + \/ cons0 jsonDiff + \/ cons0 summaryDiff + \/ cons0 sExpressionDiff + \/ cons0 tocDiff + instance MonadIO Command where liftIO io = LiftIO io `Then` return diff --git a/src/SemanticDiff.hs b/src/Semantic.hs similarity index 97% rename from src/SemanticDiff.hs rename to src/Semantic.hs index 15edad7eb..7eae3e2fc 100644 --- a/src/SemanticDiff.hs +++ b/src/Semantic.hs @@ -1,5 +1,5 @@ {-# LANGUAGE TemplateHaskell #-} -module SemanticDiff (main) where +module Semantic (main, runDiff, runParse) where import Arguments import Command @@ -24,7 +24,8 @@ main :: IO () main = do gitDir <- findGitDir alternates <- findAlternates - Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) + args@Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) + print args outputPath <- getOutputPath outputFilePath text <- case programMode of Diff args -> runDiff args diff --git a/test/SemanticSpec.hs b/test/SemanticSpec.hs new file mode 100644 index 000000000..ea1b744c4 --- /dev/null +++ b/test/SemanticSpec.hs @@ -0,0 +1,23 @@ +module SemanticSpec where + +import Prologue +import Arguments +import Semantic +import Test.Hspec hiding (shouldBe, shouldNotBe, shouldThrow, errorCall) +import Test.Hspec.Expectations.Pretty +import Test.Hspec.LeanCheck + +spec :: Spec +spec = parallel $ do + describe "runDiff" $ do + prop "produces diffs for all formats" $ + \ encoder -> do + let mode = DiffPaths "test/fixtures/ruby/and-or.A.rb" "test/fixtures/ruby/and-or.B.rb" + output <- runDiff $ DiffArguments encoder mode "" [] + output `shouldNotBe` "" + describe "runParse" $ do + prop "produces parse trees for all formats" $ + \ renderer -> do + let mode = ParsePaths ["test/fixtures/ruby/and-or.A.rb"] + output <- runParse $ ParseArguments renderer mode False "" [] + output `shouldNotBe` "" diff --git a/test/Spec.hs b/test/Spec.hs index 53bb27f4c..736db745d 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -18,6 +18,7 @@ import qualified SourceSpec import qualified TermSpec import qualified TOCSpec import qualified IntegrationSpec +import qualified SemanticSpec import Test.Hspec main :: IO () @@ -37,6 +38,7 @@ main = hspec $ do describe "SES.Myers" SES.Myers.Spec.spec describe "Source" SourceSpec.spec describe "Term" TermSpec.spec + describe "Semantic" SemanticSpec.spec describe "TOC" TOCSpec.spec describe "Integration" IntegrationSpec.spec From 4227f63065c7beda614ddea63ce9a4d31b14fedd Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 17:13:04 -0700 Subject: [PATCH 13/17] Don't want to actually print out args --- src/Semantic.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Semantic.hs b/src/Semantic.hs index 7eae3e2fc..dbb0d2803 100644 --- a/src/Semantic.hs +++ b/src/Semantic.hs @@ -25,7 +25,6 @@ main = do gitDir <- findGitDir alternates <- findAlternates args@Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) - print args outputPath <- getOutputPath outputFilePath text <- case programMode of Diff args -> runDiff args From 9ed78b0202b4998e35f6ddd0672889712f7d345e Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Mon, 10 Apr 2017 17:14:30 -0700 Subject: [PATCH 14/17] No need for args variable --- src/Semantic.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Semantic.hs b/src/Semantic.hs index dbb0d2803..7cf49d96d 100644 --- a/src/Semantic.hs +++ b/src/Semantic.hs @@ -24,7 +24,7 @@ main :: IO () main = do gitDir <- findGitDir alternates <- findAlternates - args@Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) + Arguments{..} <- customExecParser (prefs showHelpOnEmpty) (arguments gitDir alternates) outputPath <- getOutputPath outputFilePath text <- case programMode of Diff args -> runDiff args From da90d950e970b09e0d342c8abf1af9d41d654ddc Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 12 Apr 2017 13:12:08 -0700 Subject: [PATCH 15/17] Extra whitespace --- src/Renderer.hs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Renderer.hs b/src/Renderer.hs index 23aa0d28c..b95fdebb4 100644 --- a/src/Renderer.hs +++ b/src/Renderer.hs @@ -32,7 +32,6 @@ data DiffRenderer fields output where SExpressionDiffRenderer :: (HasField fields Category, HasField fields SourceSpan) => SExpressionFormat -> DiffRenderer fields ByteString ToCRenderer :: HasDefaultFields fields => DiffRenderer fields Summaries - runDiffRenderer :: Monoid output => DiffRenderer fields output -> [(Both SourceBlob, Diff (Syntax Text) (Record fields))] -> output runDiffRenderer renderer = foldMap . uncurry $ case renderer of SplitRenderer -> (File .) . R.split From 4d4eb5b22d5b6a990a37e25dd2bd1eb4f7cbf44d Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 12 Apr 2017 13:32:33 -0700 Subject: [PATCH 16/17] Bump to 0.3.0 --- semantic-diff.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semantic-diff.cabal b/semantic-diff.cabal index 2bb58dd66..0b861da5f 100644 --- a/semantic-diff.cabal +++ b/semantic-diff.cabal @@ -1,5 +1,5 @@ name: semantic-diff -version: 0.2.0 +version: 0.3.0 synopsis: Initial project template from stack description: Please see README.md homepage: http://github.com/github/semantic-diff#readme From e628e15016fe55922397b8b4d6d3bf58047d1163 Mon Sep 17 00:00:00 2001 From: Timothy Clem Date: Wed, 12 Apr 2017 13:33:50 -0700 Subject: [PATCH 17/17] Just refer to it as 'semantic' in version string --- src/Semantic.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Semantic.hs b/src/Semantic.hs index 7cf49d96d..ccbd8a87b 100644 --- a/src/Semantic.hs +++ b/src/Semantic.hs @@ -79,7 +79,7 @@ arguments :: FilePath -> [FilePath] -> ParserInfo Arguments arguments gitDir alternates = info (version <*> helper <*> argumentsParser) description where version = infoOption versionString (long "version" <> short 'v' <> help "Output the version of the program") - versionString = "semantic-diff version " <> showVersion Library.version <> " (" <> $(gitHash) <> ")" + versionString = "semantic version " <> showVersion Library.version <> " (" <> $(gitHash) <> ")" description = fullDesc <> progDesc "Set the GIT_DIR environment variable to specify a different git repository. Set GIT_ALTERNATE_OBJECT_DIRECTORIES to specify location of alternates." <> header "semantic -- Parse and diff semantically"