Allow for rg in addition to ag

Why?
====

rg is oftentimes faster for searching across a codebase than ag;
however, it's newer and potentially more unfamiliar, so ag still remains
the default.

This is an introduction to supporting rg, but without updating the
Homebrew recipe yet. Given a trial run, I can imagine switching to it as
a default eventually.
This commit is contained in:
Joshua Clayton 2016-11-30 05:39:04 -05:00
parent 596efc8734
commit 64f62d0141
No known key found for this signature in database
GPG Key ID: 5B6558F77E9A8118
8 changed files with 99 additions and 37 deletions

View File

@ -363,6 +363,10 @@ Unused leverages [Ag](https://github.com/ggreer/the_silver_searcher) to
analyze the codebase; as such, you'll need to have `ag` available in your analyze the codebase; as such, you'll need to have `ag` available in your
`$PATH`. This is set as an explicit dependency in Homebrew. `$PATH`. This is set as an explicit dependency in Homebrew.
Alternatively, if you'd like to use
[RipGrep](https://github.com/BurntSushi/ripgrep), you can do so with the
`--search rg` flag. Be sure to have RipGrep installed first.
## Testing ## Testing
To run the test suite, run: To run the test suite, run:

View File

@ -21,7 +21,7 @@ import Unused.Parser (parseResults)
import Unused.ResponseFilter (withOneOccurrence, withLikelihoods, ignoringPaths) import Unused.ResponseFilter (withOneOccurrence, withLikelihoods, ignoringPaths)
import Unused.ResultsClassifier (ParseConfigError, LanguageConfiguration(..), loadAllConfigurations) import Unused.ResultsClassifier (ParseConfigError, LanguageConfiguration(..), loadAllConfigurations)
import Unused.TagsSource (TagSearchOutcome, loadTagsFromFile, loadTagsFromPipe) import Unused.TagsSource (TagSearchOutcome, loadTagsFromFile, loadTagsFromPipe)
import Unused.TermSearch (SearchResults(..), SearchTerm, fromResults) import Unused.TermSearch (SearchResults(..), SearchBackend(..), SearchTerm, fromResults)
import Unused.Types (TermMatchSet, RemovalLikelihood(..)) import Unused.Types (TermMatchSet, RemovalLikelihood(..))
type AppConfig = MonadReader Options type AppConfig = MonadReader Options
@ -45,6 +45,7 @@ data Options = Options
, oWithoutCache :: Bool , oWithoutCache :: Bool
, oFromStdIn :: Bool , oFromStdIn :: Bool
, oCommitCount :: Maybe Int , oCommitCount :: Maybe Int
, oSearchBackend :: SearchBackend
} }
runProgram :: Options -> IO () runProgram :: Options -> IO ()
@ -57,10 +58,14 @@ run = do
terms <- termsWithAlternatesFromConfig terms <- termsWithAlternatesFromConfig
liftIO $ renderHeader terms liftIO $ renderHeader terms
results <- withCache . (`executeSearch` terms) =<< searchRunner backend <- searchBackend
results <- withCache . flip (executeSearch backend) terms =<< searchRunner
printResults =<< retrieveGitContext =<< fmap (`parseResults` results) loadAllConfigs printResults =<< retrieveGitContext =<< fmap (`parseResults` results) loadAllConfigs
searchBackend :: AppConfig m => m SearchBackend
searchBackend = asks oSearchBackend
termsWithAlternatesFromConfig :: App [SearchTerm] termsWithAlternatesFromConfig :: App [SearchTerm]
termsWithAlternatesFromConfig = do termsWithAlternatesFromConfig = do
aliases <- concatMap lcTermAliases <$> loadAllConfigs aliases <- concatMap lcTermAliases <$> loadAllConfigs

View File

@ -6,6 +6,7 @@ import qualified Data.Maybe as M
import Options.Applicative import Options.Applicative
import Unused.CLI (SearchRunner(..)) import Unused.CLI (SearchRunner(..))
import Unused.Grouping (CurrentGrouping(..)) import Unused.Grouping (CurrentGrouping(..))
import Unused.TermSearch (SearchBackend(..))
import Unused.Types (RemovalLikelihood(..)) import Unused.Types (RemovalLikelihood(..))
import Unused.Util (stringToInt) import Unused.Util (stringToInt)
@ -38,6 +39,7 @@ parseOptions =
<*> parseWithoutCache <*> parseWithoutCache
<*> parseFromStdIn <*> parseFromStdIn
<*> parseCommitCount <*> parseCommitCount
<*> parseSearchBackend
parseSearchRunner :: Parser SearchRunner parseSearchRunner :: Parser SearchRunner
parseSearchRunner = parseSearchRunner =
@ -116,3 +118,17 @@ parseCommitCount =
commitParser = optional $ strOption $ commitParser = optional $ strOption $
long "commits" long "commits"
<> help "Number of recent commit SHAs to display per token" <> help "Number of recent commit SHAs to display per token"
parseSearchBackend :: Parser SearchBackend
parseSearchBackend = M.fromMaybe Ag <$> maybeBackend
where
maybeBackend = optional $ parseBackend <$> parseBackendOption
parseBackendOption =
strOption $
long "search"
<> help "[Allowed: ag, rg] Select searching backend"
parseBackend :: String -> SearchBackend
parseBackend "ag" = Ag
parseBackend "rg" = Rg
parseBackend _ = Ag

View File

@ -16,11 +16,11 @@ renderHeader terms = do
U.resetScreen U.resetScreen
V.analysisHeader terms V.analysisHeader terms
executeSearch :: SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults executeSearch :: TS.SearchBackend -> SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
executeSearch runner terms = do executeSearch backend runner terms = do
renderHeader terms renderHeader terms
runSearch runner terms <* U.resetScreen runSearch backend runner terms <* U.resetScreen
runSearch :: SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults runSearch :: TS.SearchBackend -> SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
runSearch SearchWithProgress = I.progressWithIndicator TS.search I.createProgressBar runSearch b SearchWithProgress = I.progressWithIndicator (TS.search b) I.createProgressBar
runSearch SearchWithoutProgress = I.progressWithIndicator TS.search I.createSpinner runSearch b SearchWithoutProgress = I.progressWithIndicator (TS.search b) I.createSpinner

View File

@ -1,20 +1,33 @@
module Unused.TermSearch module Unused.TermSearch
( SearchResults(..) ( SearchResults(..)
, SearchBackend(..)
, SearchTerm , SearchTerm
, search , search
) where ) where
import qualified Data.Maybe as M import qualified Data.Maybe as M
import GHC.IO.Exception (ExitCode(ExitSuccess))
import qualified System.Process as P import qualified System.Process as P
import Unused.TermSearch.Internal (commandLineOptions, parseSearchResult) import Unused.TermSearch.Internal (commandLineOptions, parseSearchResult)
import Unused.TermSearch.Types (SearchResults(..)) import Unused.TermSearch.Types (SearchResults(..), SearchBackend(..))
import Unused.Types (SearchTerm, searchTermToString) import Unused.Types (SearchTerm, searchTermToString)
search :: SearchTerm -> IO SearchResults search :: SearchBackend -> SearchTerm -> IO SearchResults
search t = search backend t =
SearchResults . M.mapMaybe (parseSearchResult t) <$> (lines <$> ag (searchTermToString t)) SearchResults . M.mapMaybe (parseSearchResult backend t) <$> (lines <$> performSearch backend (searchTermToString t))
ag :: String -> IO String performSearch :: SearchBackend -> String -> IO String
ag t = do performSearch b t = extractSearchResults b <$> searchOutcome
(_, results, _) <- P.readProcessWithExitCode "ag" (commandLineOptions t) "" where
return results searchOutcome =
P.readProcessWithExitCode
(backendToCommand b)
(commandLineOptions b t)
""
backendToCommand Rg = "rg"
backendToCommand Ag = "ag"
extractSearchResults :: SearchBackend -> (ExitCode, String, String) -> String
extractSearchResults Rg (ExitSuccess, stdout, _) = stdout
extractSearchResults Rg (_, _, stderr) = stderr
extractSearchResults Ag (_, stdout, _) = stdout

View File

@ -6,26 +6,38 @@ module Unused.TermSearch.Internal
import qualified Data.Char as C import qualified Data.Char as C
import qualified Data.Maybe as M import qualified Data.Maybe as M
import qualified Data.Text as T import qualified Data.Text as T
import Unused.TermSearch.Types (SearchBackend(..))
import Unused.Types (SearchTerm(..), TermMatch(..)) import Unused.Types (SearchTerm(..), TermMatch(..))
import Unused.Util (stringToInt) import Unused.Util (stringToInt)
commandLineOptions :: String -> [String] commandLineOptions :: SearchBackend -> String -> [String]
commandLineOptions t = commandLineOptions backend t =
if regexSafeTerm t if regexSafeTerm t
then ["(\\W|^)" ++ t ++ "(\\W|$)", "."] ++ baseFlags then regexFlags backend t ++ baseFlags backend
else [t, ".", "-Q"] ++ baseFlags else nonRegexFlags backend t ++ baseFlags backend
where
baseFlags = ["-c", "--ackmate", "--ignore-dir", "tmp/unused"]
parseSearchResult :: SearchTerm -> String -> Maybe TermMatch parseSearchResult :: SearchBackend -> SearchTerm -> String -> Maybe TermMatch
parseSearchResult term = parseSearchResult backend term =
maybeTermMatch . map T.unpack . T.splitOn ":" . T.pack maybeTermMatch backend . map T.unpack . T.splitOn ":" . T.pack
where where
maybeTermMatch [_, path, count] = Just $ toTermMatch term path $ countInt count maybeTermMatch Rg [path, count] = Just $ toTermMatch term path $ countInt count
maybeTermMatch _ = Nothing maybeTermMatch Rg _ = Nothing
maybeTermMatch Ag [_, path, count] = Just $ toTermMatch term path $ countInt count
maybeTermMatch Ag _ = Nothing
countInt = M.fromMaybe 0 . stringToInt countInt = M.fromMaybe 0 . stringToInt
toTermMatch (OriginalTerm t) path = TermMatch t path Nothing toTermMatch (OriginalTerm t) path = TermMatch t path Nothing
toTermMatch (AliasTerm t a) path = TermMatch t path (Just a) toTermMatch (AliasTerm t a) path = TermMatch t path (Just a)
regexSafeTerm :: String -> Bool regexSafeTerm :: String -> Bool
regexSafeTerm = all (\c -> C.isAlphaNum c || c == '_' || c == '-') regexSafeTerm = all (\c -> C.isAlphaNum c || c == '_' || c == '-')
nonRegexFlags :: SearchBackend -> String -> [String]
nonRegexFlags Rg t = [t, ".", "-F"]
nonRegexFlags Ag t = [t, ".", "-Q"]
baseFlags :: SearchBackend -> [String]
baseFlags Rg = ["-c", "-j", "1"]
baseFlags Ag = ["-c", "--ackmate", "--ignore-dir", "tmp/unused"]
regexFlags :: SearchBackend -> String -> [String]
regexFlags _ t = ["(\\W|^)" ++ t ++ "(\\W|$)", "."]

View File

@ -2,8 +2,11 @@
module Unused.TermSearch.Types module Unused.TermSearch.Types
( SearchResults(..) ( SearchResults(..)
, SearchBackend(..)
) where ) where
import Unused.Types (TermMatch) import Unused.Types (TermMatch)
data SearchBackend = Ag | Rg
newtype SearchResults = SearchResults { fromResults :: [TermMatch] } deriving (Monoid) newtype SearchResults = SearchResults { fromResults :: [TermMatch] } deriving (Monoid)

View File

@ -5,6 +5,7 @@ module Unused.TermSearch.InternalSpec
import Test.Hspec import Test.Hspec
import Unused.TermSearch.Internal import Unused.TermSearch.Internal
import Unused.TermSearch.Types
import Unused.Types import Unused.Types
main :: IO () main :: IO ()
@ -14,17 +15,25 @@ spec :: Spec
spec = parallel $ do spec = parallel $ do
describe "commandLineOptions" $ do describe "commandLineOptions" $ do
it "does not use regular expressions when the term contains non-word characters" $ do it "does not use regular expressions when the term contains non-word characters" $ do
commandLineOptions "can_do_things?" `shouldBe` ["can_do_things?", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"] commandLineOptions Ag "can_do_things?" `shouldBe` ["can_do_things?", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions "no_way!" `shouldBe` ["no_way!", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"] commandLineOptions Ag "no_way!" `shouldBe` ["no_way!", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions "[]=" `shouldBe` ["[]=", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"] commandLineOptions Ag "[]=" `shouldBe` ["[]=", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions "window.globalOverride" `shouldBe` ["window.globalOverride", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"] commandLineOptions Ag "window.globalOverride" `shouldBe` ["window.globalOverride", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
it "uses regular expression match with surrounding non-word matches for accuracy" $ commandLineOptions Rg "can_do_things?" `shouldBe` ["can_do_things?", ".", "-F", "-c", "-j", "1"]
commandLineOptions "awesome_method" `shouldBe` ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "--ackmate", "--ignore-dir", "tmp/unused"] commandLineOptions Rg "no_way!" `shouldBe` ["no_way!", ".", "-F", "-c", "-j", "1"]
commandLineOptions Rg "[]=" `shouldBe` ["[]=", ".", "-F", "-c", "-j", "1"]
commandLineOptions Rg "window.globalOverride" `shouldBe` ["window.globalOverride", ".", "-F", "-c", "-j", "1"]
it "uses regular expression match with surrounding non-word matches for accuracy" $ do
commandLineOptions Ag "awesome_method" `shouldBe` ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions Rg "awesome_method" `shouldBe` ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "-j", "1"]
describe "parseSearchResult" $ do describe "parseSearchResult" $ do
it "parses normal results from `ag` to a TermMatch" $ it "parses normal results from `ag` to a TermMatch" $ do
parseSearchResult (OriginalTerm "method_name") ":app/models/foo.rb:123" `shouldBe` (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123) parseSearchResult Ag (OriginalTerm "method_name") ":app/models/foo.rb:123" `shouldBe` (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
parseSearchResult Rg (OriginalTerm "method_name") "app/models/foo.rb:123" `shouldBe` (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
it "returns Nothing when it cannot parse" $ it "returns Nothing when it cannot parse" $ do
parseSearchResult (OriginalTerm "method_name") "" `shouldBe` Nothing parseSearchResult Ag (OriginalTerm "method_name") "" `shouldBe` Nothing
parseSearchResult Rg (OriginalTerm "method_name") "" `shouldBe` Nothing