packages feed

cgrep-9.0.0: src/Util.hs

--
-- Copyright (c) 2013-2025 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 Util (
    partitionM,
    xor,
    prettyRead,
    spanGroup,
    spanGroupSeq,
    unquoteT,
    mapMaybe',
    findWithIndex,
    unsafeHead,
) where

import qualified Data.Sequence as S
import qualified Data.Text as T
import Text.Read (readMaybe)

partitionM :: (Monad m) => (a -> m Bool) -> [a] -> m ([a], [a])
partitionM _ [] = return ([], [])
partitionM f (x : xs) = do
    res <- f x
    (as, bs) <- partitionM f xs
    return ([x | res] <> as, [x | not res] <> bs)
{-# INLINE partitionM #-}

xor :: Bool -> Bool -> Bool
a `xor` b = a && not b || not a && b
{-# INLINE xor #-}

prettyRead :: (Read a) => String -> String -> a
prettyRead xs err =
    case readMaybe xs of
        Just v -> v
        _ -> errorWithoutStackTrace $ err <> ": parse error near '" <> take 40 xs <> "'"

spanGroup :: Int -> [a] -> [[a]]
spanGroup n xs
    | length xs < n = [] -- Stop if the remaining list is shorter than n
    | otherwise = take n xs : spanGroup n (drop 1 xs)

spanGroupSeq :: Int -> S.Seq a -> [S.Seq a]
spanGroupSeq _ S.Empty = []
spanGroupSeq 1 xs = [xs]
spanGroupSeq n xs = S.take n xs : spanGroupSeq n (S.drop 1 xs)
{-# INLINE spanGroupSeq #-}

{- | Removes a single pair of matching quotes ('"' or '\'')
| from the beginning and end of a Text.
-}
unquoteT :: T.Text -> T.Text
unquoteT txt =
    case T.uncons txt of
        -- Check if 'x' is a quote we care about
        Just (x, xs) | x == '"' || x == '\'' ->
            case T.unsnoc xs of
                Just (inner, y) | x == y -> inner
                _ -> txt
        _ -> txt -- Text was empty or first char wasn't a quote
{-# INLINE unquoteT #-}

mapMaybe' :: (Foldable f) => (a -> Maybe b) -> f a -> [b]
mapMaybe' f = foldr g []
  where
    g x rest
        | Just y <- f x = y : rest
        | otherwise = rest

findWithIndex :: forall a. (a -> Bool) -> [a] -> (# Int, Maybe a #)
findWithIndex predicate = go predicate 0
  where
    go :: (a -> Bool) -> Int -> [a] -> (# Int, Maybe a #)
    go _ _ [] = (# 0, Nothing #)
    go p !index (x : xs)
        | p x = (# index, Just x #)
        | otherwise = go p (index + 1) xs

unsafeHead :: [a] -> a
unsafeHead [] = error "unsafeHead: empty list"
unsafeHead (!x : _) = x
{-# INLINE unsafeHead #-}