packages feed

matterhorn-50200.2.0: src/Types/UserNames.hs

module Types.UserNames
  ( findUsernames
  , isNameFragment
  , takeWhileNameFragment
  )
where

import qualified Cheapskate as C
import           Data.Char ( isAlpha )
import qualified Data.Foldable as F
import qualified Data.Set as S
import qualified Data.Text as T

import           Prelude ()
import           Prelude.MH


findUsernames :: C.Blocks -> S.Set T.Text
findUsernames = S.unions . F.toList . fmap blockFindUsernames

blockFindUsernames :: C.Block -> S.Set T.Text
blockFindUsernames (C.Para is) =
    inlineFindUsernames $ F.toList is
blockFindUsernames (C.Header _ is) =
    inlineFindUsernames $ F.toList is
blockFindUsernames (C.Blockquote bs) =
    findUsernames bs
blockFindUsernames (C.List _ _ bs) =
    S.unions $ F.toList $ findUsernames <$> bs
blockFindUsernames _ =
    mempty

inlineFindUsernames :: [C.Inline] -> S.Set T.Text
inlineFindUsernames [] = mempty
inlineFindUsernames (C.Str "@" : rest) =
    let (strs, remaining) = takeWhileNameFragment rest
    in if null strs
       then inlineFindUsernames remaining
       else S.insert (T.concat $ getInlineStr <$> strs) $ inlineFindUsernames remaining
inlineFindUsernames (_ : rest) =
    inlineFindUsernames rest

getInlineStr :: C.Inline -> T.Text
getInlineStr (C.Str s) = s
getInlineStr _ = ""

takeWhileNameFragment :: [C.Inline] -> ([C.Inline], [C.Inline])
takeWhileNameFragment [] = ([], [])
takeWhileNameFragment rest =
    let (strs, remaining) = break (not . isNameFragment) rest
        -- Does the last element in strs start with a letter? If
        -- not, move it to the remaining list. This avoids pulling
        -- punctuation-only tokens into usernames, e.g. "Hello,
        -- @foobar."
        (strs', remaining') =
            if length strs <= 1
            then (strs, remaining)
            else let (initStrs, [lastStr]) = splitAt (length strs - 1) strs
                 in if isAlpha $ T.head $ getInlineStr lastStr
                    then (strs, remaining)
                    else (initStrs, lastStr : remaining)
    in (strs', remaining')

isValidNameChar :: Char -> Bool
isValidNameChar c = isAlpha c || c == '_' || c == '.' || c == '-'

isNameFragment :: C.Inline -> Bool
isNameFragment (C.Str t) =
    not (T.null t) && isValidNameChar (T.head t)
isNameFragment _ = False