proteome-0.9.9.9: lib/Proteome/Grep/Process.hs
module Proteome.Grep.Process where
import qualified Data.Text as Text
import Data.Text (isInfixOf)
import Path (Abs, Dir, File, Path, parseAbsFile, relfile, toFilePath)
import Path.IO (findExecutable, isLocationOccupied)
import Ribosome (pathText)
import Ribosome.Final (inFinal_)
import Ribosome.Menu (MenuItem)
import qualified Streamly.Data.Fold as Fold
import qualified Streamly.Internal.Unicode.Stream as Stream
import qualified Streamly.Prelude as Stream
import Streamly.Prelude (IsStream)
import qualified Streamly.System.Process as Process
import Streamly.System.Process (ProcessFailure)
import qualified Proteome.Data.GrepError as GrepError (GrepError (..))
import Proteome.Data.GrepError (GrepError)
import Proteome.Data.GrepOutputLine (GrepOutputLine)
import Proteome.Grep.Parse (parseGrepOutput)
import Proteome.System.Path (findExe)
defaultGrepCmdline :: Text
defaultGrepCmdline =
"grep -Hnr {pattern} {path}"
defaultRgCmdline :: Text
defaultRgCmdline =
"rg --vimgrep --no-heading -e {pattern} {path}"
defaultCmdline ::
Member (Embed IO) r =>
Sem r Text
defaultCmdline =
findExecutable [relfile|rg|] <&> \case
Just _ ->
defaultRgCmdline
_ ->
defaultGrepCmdline
patternPlaceholder :: Text
patternPlaceholder =
"{pattern}"
pathPlaceholder :: Text
pathPlaceholder =
"{path}"
replaceOrAppend :: Text -> Text -> [Text] -> [Text]
replaceOrAppend placeholder target segments | any (placeholder `isInfixOf`) segments =
Text.replace placeholder target <$> segments
replaceOrAppend _ target segments =
segments <> [target]
parseAbsExe ::
Member (Stop GrepError) r =>
Text ->
Sem r (Path Abs File)
parseAbsExe exe =
stopEitherAs (GrepError.NoSuchExecutable exe) $ parseAbsFile (toString exe)
grepCmdline ::
Members [Stop GrepError, Embed IO] r =>
Text ->
Text ->
Path Abs Dir ->
[Text] ->
Sem r (Path Abs File, [Text])
grepCmdline cmdline patt destination opt = do
when (Text.null exe) do
stop GrepError.Empty
absExe <- if absolute exe then parseAbsExe exe else findExe exe
unlessM (isLocationOccupied destination) do
stop destError
pure (absExe, withDir destination)
where
absolute a =
Text.take 1 a == "/"
argSegments =
opt <> Text.words (Text.strip args)
(exe, args) =
Text.breakOn " " (Text.strip cmdline)
withDir dir =
replaceOrAppend pathPlaceholder (pathText dir) withPattern
withPattern =
replaceOrAppend patternPlaceholder patt argSegments
destError =
GrepError.NoSuchDestination destination
processOutput ::
IsStream t =>
String ->
[Text] ->
t IO Word8
processOutput exe args =
Process.toBytes exe (toString <$> args)
processLines ::
IsStream t =>
Path Abs File ->
[Text] ->
t IO Text
processLines exe args =
Stream.lines (toText <$> Fold.toList) $
Stream.decodeUtf8 $
processOutput (toFilePath exe) args
grepMenuItems ::
Functor (t IO) =>
Members [Log, Embed IO, Final IO] r =>
IsStream t =>
Path Abs Dir ->
Path Abs File ->
[Text] ->
Sem r (t IO (MenuItem GrepOutputLine))
grepMenuItems cwd exe args =
inFinal_ \ lower _ pur ->
pur $
Stream.handle processFailure $
Stream.mapMaybeM (fmap join . lower . parseGrepOutput cwd) $
processLines exe args
where
processFailure (_ :: ProcessFailure) =
Stream.nil