module Text.Password.Strength.Internal.Repeat (
RepeatMap,
Repeat,
mkRepeatMap,
repeatMatch
) where
import Control.Arrow ((&&&))
import Control.Lens ((^.), _1)
import Data.Function (on)
import Data.List (sortBy, subsequences, maximumBy)
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Text (Text)
import qualified Data.Text as Text
import Text.Password.Strength.Internal.Token
newtype RepeatMap = RepeatMap
{ getMap :: Map Text [Token] }
type Repeat = Int
mkRepeatMap :: Map Token a -> RepeatMap
mkRepeatMap = RepeatMap . Map.foldrWithKey f Map.empty
where f t _ = Map.insertWith (<>) (t ^. tokenChars) [t]
repeatMatch :: RepeatMap -> Token -> Maybe (Repeat, Token)
repeatMatch m t =
Map.lookup (t ^. tokenChars) (getMap m) >>=
ordered >>=
longestSequence >>=
mkToken
where
ordered :: [Token] -> Maybe [Token]
ordered [] = Nothing
ordered [_] = Nothing
ordered xs = Just $ sortBy (compare `on` (^. startIndex)) xs
longestSequence :: [Token] -> Maybe (Repeat, [Token])
longestSequence ts =
let f = filter (\(n,_) -> n >= 2) .
map (length &&& id) .
filter (all isSequence . lineUp) .
subsequences
in case f ts of
[] -> Nothing
xs -> Just $ maximumBy (compare `on` (^. _1)) xs
mkToken :: (Repeat, [Token]) -> Maybe (Repeat, Token)
mkToken (_, []) = Nothing
mkToken (n, ts) = Just $
let s = head ts ^. startIndex
e = last ts ^. endIndex
c = Text.replicate n (t ^. tokenChars)
l = Text.replicate n (t ^. tokenLower)
in (n, Token c l s e)
lineUp :: [Token] -> [(Token, Token)]
lineUp xs = zip xs (drop 1 xs)
isSequence :: (Token, Token) -> Bool
isSequence (x, y) = (y ^. startIndex) - (x ^. endIndex) == 1