content: module System.Console.Docopt.ParseUtils
      (
          module System.Console.Docopt.ParseUtils,
          module System.Console.Docopt.ApplicativeParsec,
          module Data.Char,
      )
      where
  
  import System.Console.Docopt.ApplicativeParsec
  
  import           Data.Map (Map)
  import qualified Data.Map as M
  
  import Data.Char (isSpace, toUpper, toLower)
  
  -- * Constants
  
  lowers, uppers, letters, numerics, specialChars, alphanumerics, alphanumSpecial :: String
  lowers = ['a'..'z']
  uppers = ['A'..'Z']
  letters = lowers++uppers
  numerics = ['0'..'9']++"-_"
  specialChars = " :/"
  alphanumerics = letters++numerics
  alphanumSpecial = alphanumerics ++ specialChars
  
  
  -- * Basic Parsers
  
  caseInsensitive :: String -> CharParser u String
  caseInsensitive = mapM (\c -> char (toLower c) <|> char (toUpper c))
  
  lookAhead_ :: CharParser u a -> CharParser u ()
  lookAhead_ p = lookAhead p >> return ()
  
  isNotFollowedBy :: Show a => CharParser u a -> CharParser u Bool
  isNotFollowedBy p = option False (notFollowedBy p >> return True)
  
  isInlineSpace :: Char -> Bool
  isInlineSpace c = notElem c "\n\r"
                     && isSpace c
  
  inlineSpace :: CharParser u Char
  inlineSpace = satisfy isInlineSpace
              <?> "inline-space"
  
  -- | like `spaces`, except does not match newlines
  inlineSpaces :: CharParser u ()
  inlineSpaces = skipMany (satisfy isInlineSpace)
               <?> "inline-spaces"
  
  inlineSpaces1 :: CharParser u ()
  inlineSpaces1 = skipMany1 (satisfy isInlineSpace)
                <?> "1+ inline-spaces"
  
  spaces1 :: CharParser u ()
  spaces1 = skipMany1 (satisfy isSpace)
          <?> ">=1 spaces"
  
  endline :: CharParser u Char
  endline = inlineSpaces >> newline
  
  optionalEndline :: CharParser u ()
  optionalEndline = inlineSpaces >> optional newline
  
  pipe :: CharParser u Char
  pipe = char '|' <?> "'|'"
  
  ellipsis :: CharParser u String
  ellipsis = inlineSpaces >> string "..."
           <?> "'...'"
  
  manyTill1 :: CharParser u a -> CharParser u b -> CharParser u [a]
  manyTill1 p end = do
    first <- p
    rest <- manyTill p end
    return $ first : rest
  
  -- |@skipUntil p@ ignores everything that comes before @p@.
  -- Returns what @p@ returns.
  skipUntil :: Show a => CharParser u a -> CharParser u ()
  skipUntil p = skipMany (notFollowedBy p >> anyChar)
  
  pGroup :: Char -> CharParser u a -> Char -> CharParser u [a]
  pGroup beg elemParser end = between (char beg) (inlineSpaces >> char end)
                              $ (inlineSpaces >> notFollowedBy pipe >> elemParser)
                                `sepBy`
                                (inlineSpaces >> pipe)
  
  betweenS :: String -> String -> CharParser u a -> CharParser u [a]
  betweenS b e p = between begin end manyP
                   where begin = try $ string b
                         end = try $ inlineSpaces >> string e
                         manyP = p `sepBy` inlineSpaces1
  
  
  -- | Data.Map utils
  alterAllWithKey :: Ord k => (k -> Maybe a -> Maybe a) -> [k] -> Map k a -> Map k a
  alterAllWithKey f ks m = foldl (\m' k -> M.alter (f k) k m') m ks
  