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
`$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
To run the test suite, run:

View File

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

View File

@ -6,6 +6,7 @@ import qualified Data.Maybe as M
import Options.Applicative
import Unused.CLI (SearchRunner(..))
import Unused.Grouping (CurrentGrouping(..))
import Unused.TermSearch (SearchBackend(..))
import Unused.Types (RemovalLikelihood(..))
import Unused.Util (stringToInt)
@ -38,6 +39,7 @@ parseOptions =
<*> parseWithoutCache
<*> parseFromStdIn
<*> parseCommitCount
<*> parseSearchBackend
parseSearchRunner :: Parser SearchRunner
parseSearchRunner =
@ -116,3 +118,17 @@ parseCommitCount =
commitParser = optional $ strOption $
long "commits"
<> 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
V.analysisHeader terms
executeSearch :: SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
executeSearch runner terms = do
executeSearch :: TS.SearchBackend -> SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
executeSearch backend runner terms = do
renderHeader terms
runSearch runner terms <* U.resetScreen
runSearch backend runner terms <* U.resetScreen
runSearch :: SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
runSearch SearchWithProgress = I.progressWithIndicator TS.search I.createProgressBar
runSearch SearchWithoutProgress = I.progressWithIndicator TS.search I.createSpinner
runSearch :: TS.SearchBackend -> SearchRunner -> [TS.SearchTerm] -> IO TS.SearchResults
runSearch b SearchWithProgress = I.progressWithIndicator (TS.search b) I.createProgressBar
runSearch b SearchWithoutProgress = I.progressWithIndicator (TS.search b) I.createSpinner

View File

@ -1,20 +1,33 @@
module Unused.TermSearch
( SearchResults(..)
, SearchBackend(..)
, SearchTerm
, search
) where
import qualified Data.Maybe as M
import GHC.IO.Exception (ExitCode(ExitSuccess))
import qualified System.Process as P
import Unused.TermSearch.Internal (commandLineOptions, parseSearchResult)
import Unused.TermSearch.Types (SearchResults(..))
import Unused.TermSearch.Types (SearchResults(..), SearchBackend(..))
import Unused.Types (SearchTerm, searchTermToString)
search :: SearchTerm -> IO SearchResults
search t =
SearchResults . M.mapMaybe (parseSearchResult t) <$> (lines <$> ag (searchTermToString t))
search :: SearchBackend -> SearchTerm -> IO SearchResults
search backend t =
SearchResults . M.mapMaybe (parseSearchResult backend t) <$> (lines <$> performSearch backend (searchTermToString t))
ag :: String -> IO String
ag t = do
(_, results, _) <- P.readProcessWithExitCode "ag" (commandLineOptions t) ""
return results
performSearch :: SearchBackend -> String -> IO String
performSearch b t = extractSearchResults b <$> searchOutcome
where
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.Maybe as M
import qualified Data.Text as T
import Unused.TermSearch.Types (SearchBackend(..))
import Unused.Types (SearchTerm(..), TermMatch(..))
import Unused.Util (stringToInt)
commandLineOptions :: String -> [String]
commandLineOptions t =
commandLineOptions :: SearchBackend -> String -> [String]
commandLineOptions backend t =
if regexSafeTerm t
then ["(\\W|^)" ++ t ++ "(\\W|$)", "."] ++ baseFlags
else [t, ".", "-Q"] ++ baseFlags
where
baseFlags = ["-c", "--ackmate", "--ignore-dir", "tmp/unused"]
then regexFlags backend t ++ baseFlags backend
else nonRegexFlags backend t ++ baseFlags backend
parseSearchResult :: SearchTerm -> String -> Maybe TermMatch
parseSearchResult term =
maybeTermMatch . map T.unpack . T.splitOn ":" . T.pack
parseSearchResult :: SearchBackend -> SearchTerm -> String -> Maybe TermMatch
parseSearchResult backend term =
maybeTermMatch backend . map T.unpack . T.splitOn ":" . T.pack
where
maybeTermMatch [_, path, count] = Just $ toTermMatch term path $ countInt count
maybeTermMatch _ = Nothing
maybeTermMatch Rg [path, count] = Just $ toTermMatch term path $ countInt count
maybeTermMatch Rg _ = Nothing
maybeTermMatch Ag [_, path, count] = Just $ toTermMatch term path $ countInt count
maybeTermMatch Ag _ = Nothing
countInt = M.fromMaybe 0 . stringToInt
toTermMatch (OriginalTerm t) path = TermMatch t path Nothing
toTermMatch (AliasTerm t a) path = TermMatch t path (Just a)
regexSafeTerm :: String -> Bool
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
( SearchResults(..)
, SearchBackend(..)
) where
import Unused.Types (TermMatch)
data SearchBackend = Ag | Rg
newtype SearchResults = SearchResults { fromResults :: [TermMatch] } deriving (Monoid)

View File

@ -5,6 +5,7 @@ module Unused.TermSearch.InternalSpec
import Test.Hspec
import Unused.TermSearch.Internal
import Unused.TermSearch.Types
import Unused.Types
main :: IO ()
@ -14,17 +15,25 @@ spec :: Spec
spec = parallel $ do
describe "commandLineOptions" $ 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 "no_way!" `shouldBe` ["no_way!", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions "[]=" `shouldBe` ["[]=", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions "window.globalOverride" `shouldBe` ["window.globalOverride", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions Ag "can_do_things?" `shouldBe` ["can_do_things?", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions Ag "no_way!" `shouldBe` ["no_way!", ".", "-Q", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions Ag "[]=" `shouldBe` ["[]=", ".", "-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 "awesome_method" `shouldBe` ["(\\W|^)awesome_method(\\W|$)", ".", "-c", "--ackmate", "--ignore-dir", "tmp/unused"]
commandLineOptions Rg "can_do_things?" `shouldBe` ["can_do_things?", ".", "-F", "-c", "-j", "1"]
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
it "parses normal results from `ag` to a TermMatch" $
parseSearchResult (OriginalTerm "method_name") ":app/models/foo.rb:123" `shouldBe` (Just $ TermMatch "method_name" "app/models/foo.rb" Nothing 123)
it "parses normal results from `ag` to a TermMatch" $ do
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" $
parseSearchResult (OriginalTerm "method_name") "" `shouldBe` Nothing
it "returns Nothing when it cannot parse" $ do
parseSearchResult Ag (OriginalTerm "method_name") "" `shouldBe` Nothing
parseSearchResult Rg (OriginalTerm "method_name") "" `shouldBe` Nothing