{-# LANGUAGE ViewPatterns, TupleSections, ScopedTypeVariables, DeriveDataTypeable, PatternGuards, GADTs #-} module Output.Tags(writeTags, completionTags, applyTags) where import Data.Bifunctor import Data.Function import Data.List.Extra import Data.Tuple.Extra import Data.Maybe import Foreign.Storable.Tuple() import qualified Data.Map as Map import qualified Data.Set as Set import qualified Data.Vector.Storable as V import qualified Data.ByteString.Char8 as BS import Input.Item import Query import General.Util import General.Store import General.Str --------------------------------------------------------------------- -- DATA TYPE -- matches (a,b) if i >= a && i <= b data Packages a where Packages :: Packages (BStr0, V.Vector (TargetId, TargetId)) deriving Typeable -- list of packages, sorted by popularity, lowercase, interspersed with \0 -- for each index in PackageNames, the first is the module item, any in the bounds are in that package data Modules a where Modules :: Modules (BStr0, V.Vector (TargetId, TargetId)) deriving Typeable -- list of modules, sorted by popularity, not unique, lowercase, interspersed with \0 -- for each index in ModuleNames, the first is the module item, any in the bounds are in that module data Categories a where Categories :: Categories (BStr0, Jagged (TargetId, TargetId)) deriving Typeable -- list of categories, sorted by name, interspersed with \0 -- for each index in CategoryNames, a range of items containing a category, first item is a package data Completions a where Completions :: Completions BStr0 deriving Typeable -- a list of things to complete to, interspersed with \0 writeTags :: StoreWrite -> (PkgName -> Bool) -> (PkgName -> [(String,String)]) -> [(Maybe TargetId, Item)] -> IO () writeTags store keep extra xs = do let splitPkg = splitIPackage xs let packages = addRange splitPkg storeWrite store Packages (bstr0Join $ map (strUnpack . fst) packages, V.fromList $ map snd packages) let categories = map (bimap snd reverse) $ Map.toList $ Map.fromListWith (++) [(((weightTag ex, both lower ex), joinPair ":" ex),[rng]) | (p,rng) <- packages, ex <- extra p] storeWrite store Categories (bstr0Join $ map fst categories, jaggedFromList $ map snd categories) let modules = addRange $ concatMap (splitIModule . snd) splitPkg storeWrite store Modules (bstr0Join $ map (lower . strUnpack . fst) modules, V.fromList $ map snd modules) storeWrite store Completions $ bstr0Join $ takeWhile ("set:" `isPrefixOf`) (map fst categories) ++ map ("package:"++) (sortOn lower $ map strUnpack $ nubOrd $ filter keep $ map fst packages) ++ map (joinPair ":") (sortOn (weightTag &&& both lower) $ nubOrd [ex | (p,_) <- packages, keep p, ex <- extra p, fst ex /= "set"]) where addRange :: [(Str, [(Maybe TargetId,a)])] -> [(Str, (TargetId, TargetId))] addRange xs = [(a, (minimum' is, maximum' is)) | (a,b) <- xs, let is = mapMaybe fst b, not $ strNull a, is /= []] weightTag ("set",x) = fromMaybe 0.9 $ lookup x [("stackage",0.0),("haskell-platform",0.1)] weightTag ("package",x) = 1 weightTag ("category",x) = 2 weightTag ("license",x) = 3 weightTag _ = 4 --------------------------------------------------------------------- -- SIMPLE SELECTORS completionTags :: StoreRead -> [String] completionTags store = map BS.unpack $ bstr0Split $ storeRead store Completions --------------------------------------------------------------------- -- DATA TYPE, PARSE, PRINT data Tag = IsExact | IsPackage | IsModule | EqPackage String | EqModule String | EqCategory String String deriving Eq parseTag :: String -> String -> Maybe Tag parseTag k v | k ~~ "is" = case () of _ | v ~~ "exact" -> Just IsExact | v ~~ "package" -> Just IsPackage | v ~~ "module" -> Just IsModule | otherwise -> Nothing | k ~~ "package" = if v == "" then Nothing else Just $ EqPackage v | k ~~ "module" = if v == "" then Nothing else Just $ EqModule v | v /= "" = Just $ EqCategory k v | otherwise = Nothing where -- make the assumption the first letter always disambiguates x ~~ lit = x /= "" && lower x `isPrefixOf` lit showTag :: Tag -> (String, String) showTag IsExact = ("is","exact") showTag IsPackage = ("is","package") showTag IsModule = ("is","module") showTag (EqPackage x) = ("package",x) showTag (EqModule x) = ("module",x) showTag (EqCategory k v) = (k,v) --------------------------------------------------------------------- -- TAG SEMANTICS -- | Given a tag, find the ranges of identifiers it covers (if it restricts the range) -- An empty range means an empty result, while a Nothing means a search on the entire range resolveTag :: StoreRead -> Tag -> (Tag, Maybe [(TargetId,TargetId)]) resolveTag store x = case x of IsExact -> (IsExact, Nothing) IsPackage -> (IsPackage, Just $ map (dupe . fst) $ V.toList packageIds) IsModule -> (IsModule, Just $ map (dupe . fst) $ V.toList moduleIds) EqPackage orig@(BS.pack -> val) -- look for people who are an exact prefix, sort by remaining length, if there are ties, pick the first one | res@(_:_) <- [(BS.length x, (i,x)) | (i,x) <- zipFrom 0 $ bstr0Split packageNames, val `BS.isPrefixOf` x] -> let (i,x) = snd $ minimumBy (compare `on` fst) res in (EqPackage $ BS.unpack x, Just [packageIds V.! i]) | otherwise -> (EqPackage orig , Just []) EqModule x -> (EqModule x, Just $ map (moduleIds V.!) $ findIndices (eqModule $ lower x) $ bstr0Split moduleNames) EqCategory cat val -> (EqCategory cat val, Just $ concat [ V.toList $ jaggedAsk categoryIds i | i <- elemIndices (BS.pack (cat ++ ":" ++ val)) $ bstr0Split categoryNames]) where eqModule x | Just x <- stripPrefix "." x, Just x <- stripSuffix "." x = (==) (BS.pack x) | Just x <- stripPrefix "." x = BS.isPrefixOf $ BS.pack x | otherwise = let y = BS.pack x; y2 = BS.pack ('.':x) in \v -> y `BS.isPrefixOf` v || y2 `BS.isInfixOf` v (packageNames, packageIds) = storeRead store Packages (categoryNames, categoryIds) = storeRead store Categories (moduleNames, moduleIds) = storeRead store Modules --------------------------------------------------------------------- -- TAG QUERIES -- | Given a query produce: (refined query, is:exact, filter, enumeration) -- You should apply the filter to other peoples results, or if you have nothing else, use the enumeration. applyTags :: StoreRead -> [Query] -> ([Query], Bool, TargetId -> Bool, [TargetId]) applyTags store qs = (qs2, exact, filt, searchTags store qs) where (qs2, exact, filt) = filterTags store qs filterTags :: StoreRead -> [Query] -> ([Query], Bool, TargetId -> Bool) filterTags ts qs = (map redo qs, exact, \i -> all ($ i) fs) where fs = map (filterTags2 ts . snd) $ groupSort $ map (scopeCategory &&& id) $ filter isQueryScope qs exact = Just IsExact `elem` [parseTag a b | QueryScope True a b <- qs] redo (QueryScope sense cat val) | Just (k,v) <- fmap (showTag . fst . resolveTag ts) $ parseTag cat val = QueryScope sense k v | otherwise = QueryNone $ ['-' | not sense] ++ cat ++ ":" ++ val redo q = q filterTags2 ts qs = \i -> not (negq i) && (noPosRestrict || posq i) where (posq,negq) = both inRanges (pos,neg) (pos, neg) = both (concatMap snd) $ partition fst xs xs = catMaybes restrictions noPosRestrict = all pred restrictions restrictions = map getRestriction qs pred Nothing = True pred (Just (sense, _)) = not sense getRestriction :: Query -> Maybe (Bool,[(TargetId, TargetId)]) getRestriction (QueryScope sense cat val) = do tag <- parseTag cat val ranges <- snd $ resolveTag ts tag pure (sense, ranges) -- | Given a search which has no type or string in it, run the query on the tag bits. -- Using for things like IsModule, EqCategory etc. searchTags :: StoreRead -> [Query] -> [TargetId] searchTags ts qs | x:xs <- [map fst $ maybe [] (fromMaybe [] . snd . resolveTag ts) $ parseTag cat val | QueryScope True cat val <- qs] = if null xs then x else filter (`Set.member` foldl1' Set.intersection (map Set.fromList xs)) x searchTags ts _ = maybe [] (map fst) $ snd $ resolveTag ts IsPackage