packages feed

banwords 0.1.0.0 → 0.2.0.0

raw patch · 4 files changed

+62/−26 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Heuristics.BanWords: passesBlacklist :: Vector Text -> Text -> Maybe Text
- Heuristics.BanWords: passesBlacklistPlus :: Vector Text -> Text -> Maybe Text
- Heuristics.BanWords: skipNonAlphabetical :: Parser ()
+ Heuristics.BanWords: MatchPolicy :: (Vector Text -> Parser Text) -> MatchPolicy
+ Heuristics.BanWords: _unMatchPolicy :: MatchPolicy -> Vector Text -> Parser Text
+ Heuristics.BanWords: instance Monoid MatchPolicy
+ Heuristics.BanWords: newtype MatchPolicy
+ Heuristics.BanWords: passBlacklist :: MatchPolicy -> Vector Text -> Text -> Maybe Text
- Heuristics.BanWords: banAlmostExact :: Vector Text -> Parser Text
+ Heuristics.BanWords: banAlmostExact :: MatchPolicy
- Heuristics.BanWords: banExact :: Vector Text -> Parser Text
+ Heuristics.BanWords: banExact :: MatchPolicy

Files

README.md view
@@ -1,3 +1,28 @@ # Intro  A few tools for checking if a word is a close match against a blacklist.++# Example++```haskell+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Data.Monoid+import Data.Text (Text)+import Data.Vector (Vector)+import qualified Data.Vector as V+import Heuristics.BanWords++main :: IO ()+main =+  case passBlacklist banAlmostExact blacklist "acme " of+    Nothing -> putStrLn "Blocked 'acme '"+    Just _  -> error "This shouldn't happen!"++blacklist :: Vector Text+blacklist = exampleReserved -- Includes "admin", "security", and a few others.+         <> V.fromList ["gosh", "darn"] -- Supply your own profanity list.+         <> V.fromList ["acme"] -- Prevent users from impersonating your company.+```
banwords.cabal view
@@ -1,5 +1,5 @@ name:                 banwords-version:              0.1.0.0+version:              0.2.0.0 author:               Ian Grant Jeffries maintainer:           ian@housejeffries.com category:             Heuristics
src/Heuristics/BanWords.hs view
@@ -1,40 +1,44 @@ module Heuristics.BanWords where -import Prelude hiding (notElem)- import Control.Applicative import Data.Attoparsec.Text+import Data.Char import Data.Foldable import Data.Monoid import Data.Text (Text) import Data.Vector (Vector) import qualified Data.Vector as V --- | Block exact matches.-passesBlacklist :: Vector Text -> Text -> Maybe Text-passesBlacklist blacklist t =-  case parseOnly (banExact blacklist) t of-    Right _ -> Nothing-    Left _  -> Just t+newtype MatchPolicy = MatchPolicy { _unMatchPolicy :: Vector Text -> Parser Text } --- | Intended for internal use.-banExact :: Vector Text -> Parser Text-banExact bans = asum $ (\x -> string x *> endOfInput *> return x) <$> bans+instance Monoid MatchPolicy where+  mempty = MatchPolicy . const $ empty+  mappend f g = MatchPolicy $ \x -> _unMatchPolicy f x <|> _unMatchPolicy g x --- | Block exact matches, or exact matches that are surrounded by only non-alphabetical characters.-passesBlacklistPlus :: Vector Text -> Text -> Maybe Text-passesBlacklistPlus blacklist t =-  case parseOnly (banAlmostExact blacklist) t of+passBlacklist :: MatchPolicy -> Vector Text -> Text -> Maybe Text+passBlacklist f blacklist t =+  case parseOnly (_unMatchPolicy f blacklist) t of     Right _ -> Nothing     Left _  -> Just t --- | Intended for internal use.-banAlmostExact :: Vector Text -> Parser Text-banAlmostExact bans = asum $ (\x -> skipNonAlphabetical *> string x *> skipNonAlphabetical *> endOfInput *> return x) <$> bans+-- | Parses sucessfully (which means the word gets blocked) if any of the Texts+-- in the blacklist are an exact match.+banExact :: MatchPolicy+banExact = MatchPolicy $ \blacklist -> asum $ (\x -> string x *> endOfInput *> return x) <$> blacklist --- | Intended for internal use.-skipNonAlphabetical :: Parser ()-skipNonAlphabetical = skipWhile $ \c -> notElem c (['a'..'z'] <> ['A'..'Z'])+-- | Parses sucessfully (which means the word gets blocked) if any of the Texts+-- in the blacklist are a match.+--+-- Case is ignored for ASCII characters. Non-alphabetic Unicode characters at+-- either the start of the end of the word can be skipped.+--+-- E.g. " Admin" is banned against ["admin"], but "a admin" isn't.+banAlmostExact :: MatchPolicy+banAlmostExact = MatchPolicy $ \blacklist ->+  asum $ (\x -> skipNonAlphabetical *> asciiCI x *> skipNonAlphabetical *> endOfInput *> return x) <$> blacklist+  where+    skipNonAlphabetical :: Parser ()+    skipNonAlphabetical = skipWhile $ not . isAlpha  exampleReserved :: Vector Text exampleReserved = V.fromList
tests/Unit.hs view
@@ -22,16 +22,23 @@   ]  exactAcceptable :: Assertion-exactAcceptable = V.mapM_ (assertAllowed $ passesBlacklist exampleReserved) $ goodWords <> goodExtraChars <> badExtraChars+exactAcceptable =+  V.mapM_ (assertAllowed $ passBlacklist banExact exampleReserved)+    $ goodWords <> goodExtraChars <> badExtraChars <> V.fromList ["Hill, Admin"]  exactUnacceptable :: Assertion-exactUnacceptable = V.mapM_ (assertBanned $ passesBlacklist exampleReserved) (V.fromList ["admin", "security"])+exactUnacceptable =+  V.mapM_ (assertBanned $ passBlacklist banExact exampleReserved)+    $ V.fromList ["admin", "security"]  almostAcceptable :: Assertion-almostAcceptable = V.mapM_ (assertAllowed $ passesBlacklistPlus exampleReserved) $ goodWords <> goodExtraChars+almostAcceptable =+  V.mapM_ (assertAllowed $ passBlacklist banAlmostExact exampleReserved)+    $ goodWords <> goodExtraChars <> V.singleton "Hill"  almostUnacceptable :: Assertion-almostUnacceptable = V.mapM_ (assertBanned $ passesBlacklistPlus exampleReserved) badExtraChars+almostUnacceptable = V.mapM_ (assertBanned $ passBlacklist banAlmostExact exampleReserved)+  $ badExtraChars <> V.singleton "Admin"  goodExtraChars :: Vector Text goodExtraChars = V.fromList