content: -- | Generic utilities.
  module FastTags.Util where
  import qualified Data.ByteString as ByteString
  import qualified Data.Char as Char
  import qualified Data.Function as Function
  import qualified Data.List as List
  import qualified Data.Map.Strict as Map
  import qualified Data.Set as Set
  import qualified Data.Text as Text
  import Data.Text (Text)
  import qualified Data.Text.Encoding as Encoding
  import qualified Data.Text.Encoding.Error as Encoding.Error
  
  -- | Read a UTF8 file, but don't crash on encoding errors.
  readFileLenient :: FilePath -> IO Text
  readFileLenient = fmap (Encoding.decodeUtf8With Encoding.Error.lenientDecode)
      . ByteString.readFile
  
  -- | Drop until the element before the matching one.  Return [] if the function
  -- never matches.
  dropBefore :: (a -> Bool) -> [a] -> [a]
  dropBefore f = go
      where
      go [] = []
      go [_] = []
      go xs@(_ : rest@(y:_))
          | f y = xs
          | otherwise = go rest
  
  sortOn :: (Ord k) => (a -> k) -> [a] -> [a]
  sortOn key = List.sortBy (compare `Function.on` key)
  
  -- | Split list into chunks delimited by specified element.
  split :: (Eq a) => a -> [a] -> [[a]]
  split _ [] = []
  split x xs = xs': split x (drop 1 xs'')
      where (xs', xs'') = break (==x) xs
  
  headt :: Text -> Maybe Char
  headt = fmap fst . Text.uncons
  
  mhead :: [a] -> Maybe a
  mhead [] = Nothing
  mhead (x:_) = Just x
  
  mlast :: [a] -> Maybe a
  mlast xs
      | null xs = Nothing
      | otherwise = Just (last xs)
  
  keyOn :: (a -> k) -> [a] -> [(k, a)]
  keyOn f xs = zip (map f xs) xs
  
  groupOn :: Eq k => (a -> k) -> [a] -> [[a]]
  groupOn key = List.groupBy (\a b -> key a == key b)
  
  -- | Group the unsorted list into @(key x, xs)@ where all @xs@ compare equal
  -- after @key@ is applied to them.  List is returned in sorted order.
  groupOnKey :: Ord key => (a -> key) -> [a] -> [(key, [a])]
  groupOnKey key = Map.toAscList . List.foldl' go Map.empty
      where go m x = Map.alter (Just . maybe [x] (x:)) (key x) m
  
  isSymbolCharacterCategory :: Char.GeneralCategory -> Bool
  isSymbolCharacterCategory cat = case cat of
      Char.ConnectorPunctuation -> True
      Char.DashPunctuation      -> True
      Char.OtherPunctuation     -> True
      Char.MathSymbol           -> True
      Char.CurrencySymbol       -> True
      Char.ModifierSymbol       -> True
      Char.OtherSymbol          -> True
      _                         -> False
  
  unique :: Ord a => [a] -> [a]
  unique = Set.toList . Set.fromList
  