packages feed

cgrep-8.0.0: src/Main.hs

--
-- Copyright (c) 2013-2023 Nicola Bonelli <nicola@larthia.com>
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 2 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program; if not, write to the Free Software
-- Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
--

module Main where

import qualified Data.ByteString.Char8 as C
import qualified Codec.Binary.UTF8.String as UC

import Data.List ( isSuffixOf, (\\), isInfixOf, nub, sort, union, isPrefixOf, genericLength, partition, elemIndex )
import Data.Maybe ( catMaybes )
import Data.Char ( toLower )
import Data.Version ( showVersion )

import Control.Monad.Trans.Reader ( ReaderT(runReaderT), ask )
import Control.Monad ( when, void )

import qualified Data.Map as M
import GHC.Conc ( getNumCapabilities, setNumCapabilities )
import GHC.IO.Handle ( hIsTerminalDevice )

import System.IO ( stdin, stdout, stderr )
import System.Console.CmdArgs ( cmdArgsRun )
import System.Exit ( exitSuccess )
import System.Environment ( withArgs )

import CGrep.FileType ( readTypeList, readKindList )
import CGrep.FileTypeMap ( dumpFileTypeInfoMap, fileTypeInfoMap )
import CGrep.Parser.Atom ( wildCardMap )
import CGrep.Common ( trim8 )

import Verbose ( putMsgLnVerbose )
import Paths_cgrep ( version )
import CmdOptions ( options )
import Options ( Options(..) )
import Config
    ( dumpPalette, getConfig, Config(configFileTypes, configColors, configJobs, configFileKinds) )

import Util ( partitionM)
import Reader ( ReaderIO, Env (..) )
import Search ( parallelSearch, isRegexp )
import System.Posix.FilePath (RawFilePath)

import Data.List.Extra (notNull)
import Data.Functor ( ($>), void )
import Control.Applicative ( Alternative((<|>)) )

main :: IO ()
main = do
    -- check whether this is a terminal device
    isTermIn  <- hIsTerminalDevice stdin
    isTermOut <- hIsTerminalDevice stdout

    -- read config options
    (conf, _) <- getConfig

    -- read command-line options
    opt@Options{..} <- (if isTermOut
                then \o -> o { color = color o || configColors conf }
                else id) <$> cmdArgsRun options

    -- check for multiple backends...
    when (length (catMaybes [ if json then Just "" else Nothing ]) > 1) $
        error "Cgrep: you can use one back-end at time!"

    -- display lang-map and exit...
    when type_map $
        dumpFileTypeInfoMap fileTypeInfoMap >> exitSuccess

    -- display color palette and exit...
    when show_palette $
        dumpPalette >> exitSuccess

    -- check whether the pattern list is empty, display help message if it's the case
    when (null others && isTermIn && null file) $
        withArgs ["--help"] $ void (cmdArgsRun options)

    let others' = C.pack <$> others

    -- load patterns
    patterns <- if null file then pure $ readPatternsFromCommandLine others'
                             else readPatternsFromFile (C.pack file)

    let patterns' = map (if ignore_case then ic else id) patterns
            where ic | (not . isRegexp) opt && semantic = C.unwords . map (\p -> if p `elem` wildCardTokens then p else C.map toLower p) . C.words
                     | otherwise = C.map toLower
                        where wildCardTokens = "OR" : M.keys wildCardMap   -- "OR" is not included in wildCardMap

    -- display the configuration in use

    -- when (isJust confpath) $
    --    hPutStrLn stderr $ showBold opt ("Using '" <> fromJust confpath <> "' configuration file...")

    -- load files to parse:
    let paths = getFilePaths (notNull file) others'

    -- parse cmd line language list:
    let (l0, l1, l2) = readTypeList type_filter

    -- file type enabled:
    let types = (if null l0 then configFileTypes conf else l0 `union` l1) \\ l2
        kinds = if null kind_filter then configFileKinds conf else readKindList kind_filter


    runReaderT (do
        putMsgLnVerbose 1 stderr $ "cgrep " <> showVersion version <> "!"
        putMsgLnVerbose 1 stderr $ "File types: " <> show type_filter
        putMsgLnVerbose 1 stderr $ "File kinds: " <> show kinds
        ) (Env conf opt)

    -- specify number of cores
    cap <- case jobs <|> configJobs conf of
            (Just j) ->  setNumCapabilities (j+1) $> j
            Nothing  ->  getNumCapabilities

    -- run search
    runReaderT (parallelSearch paths patterns' types kinds isTermIn) (Env conf opt {jobs = Just cap})


readPatternsFromFile :: RawFilePath -> IO [C.ByteString]
readPatternsFromFile "" = return []
readPatternsFromFile f  = map trim8 . C.lines <$> C.readFile (C.unpack f)


readPatternsFromCommandLine :: [C.ByteString] -> [C.ByteString]
readPatternsFromCommandLine [] = []
readPatternsFromCommandLine xs | ":" `elem` xs = takeWhile (/= ":") xs
                               | otherwise = [ head xs ]


getFilePaths :: Bool -> [RawFilePath] -> [RawFilePath]
getFilePaths False xs = case ":" `elemIndex` xs of
    Nothing  -> if null xs then [] else tail xs
    (Just n) -> drop (n+1)  xs
getFilePaths True xs = xs