talash-0.1.0.0: src/Talash/Internal.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ExistentialQuantification #-}
module Talash.Internal ( -- * Search
SearchFunctions(..) , makeMatcher , lister , displayer , searchFunctionsFuzzy , searchFunctionsOL , searchWithMatcher
-- * Help for reading vectors
, readVectorStdIn , fileNamesSorted , readVectorHandle , readVectorHandleWith, emptyIndices
-- * Exports
, module Export ) where
import Control.Exception as Export (finally , catch, bracket , AsyncException)
import qualified Data.Text as T
import Data.Text.AhoCorasick.Automaton (CaseSensitivity (..))
import qualified Data.Text.IO as T
import Data.Vector (Vector , (!), force , generate , take, singleton , convert, enumFromN, unfoldr, unfoldrM , uniq , modify, concat)
import Data.Vector.Algorithms.Intro (sort)
import qualified Data.Vector.Unboxed as U
import qualified Data.Vector.Unboxed.Sized as S
import GHC.Compact (Compact , compact , getCompact)
import GHC.TypeNats
import Intro hiding (sort, take , modify)
import Lens.Micro as Export (ASetter' , over, set, (^.) , _1 , _2 , _3 , (.~) , (?~) , (%~))
import Lens.Micro.TH as Export ( makeLenses )
import System.IO as Export ( Handle , hIsEOF , isEOF, hClose, stdin)
import Talash.Core hiding (makeMatcher)
data SearchFunctions a = SearchFunctions {
-- | Construct the matcher given the string to match.
_makeMatcher :: Text -> Maybe (Matcher a) ,
-- | Obtain the result for searching.
_lister :: forall n. KnownNat n => MatcherSized n a -- The n will be determined by the SomeMatcher constructed above
-> Vector Text -- The vector holding the candidates
-> U.Vector Int -- An unboxed vector of indices. Only these indices will be matched against the matcher
-> (U.Vector Int , U.Vector (Indices n)) ,
-- | Given the matcher @m@, the matched string @t@ and the indices of matches in @t@ divide @t@ in alternating strings that are a matches
-- and the gap between these matches. The first of these is always a gap and can be empty. The rest should be non empty.
_displayer :: forall n. KnownNat n => MatcherSized n a -> Text -> S.Vector n Int -> [Text] }
makeLenses ''SearchFunctions
emptyIndices :: Int -> U.Vector (Indices 0)
emptyIndices n = U.generate n ( , S.empty)
-- | searchWithMatcher carries out one step of the search. Note that the search can stops before going through the whole vector of text. In that case the returned
-- vector of indices should contain not only the indices matched candidates but also the indices of candidates that weren't tested for a match.
searchWithMatcher :: SearchFunctions a -- ^ The configuration to use to carry out the search.
-> Vector Text -- ^ The vector @v@ of candidates.
-> Maybe Text -- ^ The query string
-> U.Vector Int -- ^ The subset of indices of @v@ to search against. If input changes from @talas@ to @talash@ we only search among candidates that matched @talas@.
-> (U.Vector Int , (Int , Vector [Text])) -- ^ The indices of the matched candidates (see the note above) and the matched candidates broken up according to the match.
searchWithMatcher fs v t s = maybe nc go ((fs ^. makeMatcher) =<< t)
where
nc = (U.enumFromN 0 (length v) , (0 , force . map (\i -> [v ! (i ^. _1)]) . convert . emptyIndices . min 512 . length $ v))
go (Matcher f') = (iv , (U.length iv , force . map (\i -> (fs ^. displayer) f' (v ! (i ^. _1)) (i ^. _2)) . convert $ mv))
where
(iv , mv) = (fs ^. lister) f' v s
-- | Read a vector of newline separated candidates from the stdin.
readVectorStdIn :: IO (Vector Text)
readVectorStdIn = finally (unfoldrM (const . ifM isEOF (pure Nothing) . map (\ !l -> Just (l , ())) $ T.getLine) ()) (hClose stdin)
-- | Read a vector of newline separated candidates from a handle.
readVectorHandle :: Handle -> IO (Vector Text)
readVectorHandle h = finally (unfoldrM (const . ifM (hIsEOF h) (pure Nothing) . map (\ !l -> Just (l , ())) $ T.hGetLine h) ()) (hClose h)
-- | A generalized version of readVectorHandle allowing for the transformation of candidates and the resulting vector. See fileNamesSorted for an example of use.
readVectorHandleWith :: (Text -> Text) -- ^ The function to transform the candidates.
-> (Vector Text -> Vector Text) -- ^ The function to apply to the constructed vector before compacting.
-> Handle -- ^ The handle to read from
-> IO (Vector Text)
readVectorHandleWith f t h = finally (t <$> unfoldrM (const . ifM (hIsEOF h) (pure Nothing) . map (\ !l -> Just (f $! l , ())) $ T.hGetLine h) ())
(hClose h)
-- | Read a vector of filenames from the handle, get the basename by removing the path of the directory. Finally sort and deduplicate the resulting filenames.
-- Useful to get the list of executables from PATH for example.
fileNamesSorted :: Handle -> IO (Vector Text)
fileNamesSorted = readVectorHandleWith (T.takeWhileEnd (/= '/')) (uniq . modify sort)
-- | Search functions suitable for fuzzy matching. The candidate @c@ will match the query @s@ if @c@ contains all the characters in @s@ in order. In general there
-- can be several ways of matching. This tries to find a match with the minimum number of parts. It does not find the minimum number of parts, if that requires
-- reducing the extent of the partial match during search. E.g. matching @"as"@ against @"talash"@ the split will be @["tal","as","h"]@ and not
-- @["t","a","la","s","h"]@. While matching @"talash best match testing hat"@ against @"tea"@ will not result in @["talash best match ","te","sting h","a","t"]@ since
-- @"te"@ occurs only after we have match all three letters and we can't know if we will find the @"a"@ without going through the string.
searchFunctionsFuzzy :: SearchFunctions MatchPart
searchFunctionsFuzzy = SearchFunctions (fuzzyMatcher IgnoreCase) (searchSome (fuzzySettings 512)) (\m t -> parts (S.fromSized <$> sizes m) t . S.fromSized)
-- | Search functions that match the words in i.e. space separated substring in any order. @"talash best"@ will match @"be as"@ with the split
-- @["tal","as","h","be","st"]@ but "talash best" will not match @"bet"@.
searchFunctionsOL :: SearchFunctions Int
searchFunctionsOL = SearchFunctions (orderlessMatcher IgnoreCase) (searchSome (orderlessSettings 512)) (\m t -> partsOrderless (S.fromSized <$> sizes m) t . S.fromSized)
-- testSearch :: IO ()
-- testSearch = (\v -> traverse_ print . take 64 . snd . snd . searchWithMatcher searchFunctionsFuzzy v (Just "figrun") . U.enumFromN 0 . length $ v) . getCompact
-- =<< readVectorStdIn