packages feed

hackage-server-0.5.0: Distribution/Server/Features/Search/ExtractNameTerms.hs

{-# LANGUAGE BangPatterns, NamedFieldPuns, GeneralizedNewtypeDeriving #-}

module Distribution.Server.Features.Search.ExtractNameTerms (
    extractPackageNameTerms,
    extractModuleNameTerms,
  ) where

import Data.Text (Text)
import qualified Data.Text as T
import Data.Char (isUpper, isDigit)
import Data.List
import Data.List.Split hiding (Splitter)
import Data.Maybe (maybeToList)

import Data.Functor.Identity
import Control.Monad
import Control.Monad.List
import Control.Monad.Writer
import Control.Monad.State


extractModuleNameTerms :: String -> [Text]
extractModuleNameTerms modname =
  map T.toCaseFold $
  nub $
  map T.pack $
  flip runSplitter modname $ do
    _ <- forEachPart splitDot
    _ <- forEachPart splitCamlCase
    satisfy (not . singleChar)
    get >>= emit

extractPackageNameTerms :: String -> [Text]
extractPackageNameTerms pkgname =
  map T.toCaseFold $
  nub $
  map T.pack $
  flip runSplitter pkgname $ do

    fstComponentHyphen <- forEachPart splitHyphen

    satisfy (`notElem` ["hs", "haskell"])

    _ <- forEachPart stripPrefixH

    fstComponentCaml <- forEachPart splitCamlCase

    fstComponent2 <- forEachPart splitOn2

    when (fstComponentHyphen && fstComponentCaml && fstComponent2) $ do
      forEachPartAndWhole stripPrefix_h
    _ <- forEachPart (maybeToList . stripPrefix "lib")
    _ <- forEachPart (maybeToList . stripSuffix "lib")
    _ <- forEachPart stripSuffixNum
    satisfy (not . singleChar)

    get >>= emit

newtype Split a = Split (StateT String (ListT (WriterT [String] Identity)) a)
  deriving (Monad, MonadPlus, MonadState String)

emit :: String -> Split ()
emit x = Split (lift (lift (tell [x])))

forEach :: [a] -> Split a
forEach = msum . map return

runSplitter :: Split () -> String -> [String]
runSplitter (Split m) s = snd (runWriter (runListT (runStateT m s)))

singleChar :: String -> Bool
singleChar [_] = True
singleChar _   = False

satisfy :: (String -> Bool) -> Split ()
satisfy p = get >>= guard . p

forEachPart :: (String -> [String]) -> Split Bool
forEachPart parts = do
  t <- get
  case parts t of
    []             -> return True
    [t'] | t == t' -> return True
    ts             -> do emit t
                         (t', n) <- forEach (zip ts [1::Int ..])
                         put t'
                         return (n==1)

forEachPartAndWhole :: (String -> [String]) -> Split ()
forEachPartAndWhole parts = do
  t <- get
  case parts t of
    []             -> return ()
    ts             -> forEach (t:ts) >>= put
  

splitDot :: String -> [String]
splitDot = split (dropBlanks $ dropDelims $ whenElt (=='.'))

splitHyphen :: String -> [String]
splitHyphen = split (dropBlanks $ dropDelims $ whenElt (=='-'))

splitCamlCase :: String -> [String]
splitCamlCase = split (dropInitBlank $ condense $ keepDelimsL $ whenElt isUpper)

stripPrefixH :: String -> [String]
stripPrefixH ('H':'S':frag)   | all isUpper frag = [frag]
stripPrefixH "HTTP"                              = []
stripPrefixH ('H':frag@(c:_)) | isUpper c        = [frag]
stripPrefixH _                                   = []

stripPrefix_h :: String -> [String]
stripPrefix_h "http"         = []
stripPrefix_h "html"         = []
stripPrefix_h ('h':'s':frag) = ['s':frag, frag]
stripPrefix_h ('h':frag) {- | Set.notMember (T.pack w) ws -} = [frag]
stripPrefix_h _              = []

stripSuffix :: String -> String -> Maybe String
stripSuffix s t = fmap reverse (stripPrefix (reverse s) (reverse t))

stripSuffixNum :: String -> [String]
stripSuffixNum s
  | null rd || null rs' = []
  | otherwise           = [s', d]
  where
    rs        = reverse s
    (rd, rs') = span isDigit rs
    d         = reverse rd
    s'        = reverse rs'

splitOn2 :: String -> [String]
splitOn2 t =
  case break (=='2') t of
    (from@(_:_), '2':to@(c:_))
      | not (isDigit c)
      , not (length from == 1 && length to == 1)
      -> [from, to]
    _ -> []


-------------------
-- experiment
--
{-
main = do
    pkgsFile <- readFile "pkgs3"
    let pkgs     :: [PackageDescription]
        pkgs = map read (lines pkgsFile)

--    print "forcing pkgs..."
--    evaluate (foldl' (\a p -> seq p a) () pkgs)

    sequence_
      [ putStrLn $ display (packageName pkg) ++ ": " ++ display mod ++ " -> "
                ++ intercalate ", " (map T.unpack $ extractModuleNameTerms (display mod))
      | pkg <- pkgs
      , Just lib <- [library pkg]
      , let mods = exposedModules lib
      , mod <- mods ]
-}