packages feed

megaparsec-utils 0.1.1.3 → 0.2.0.0

raw patch · 8 files changed

+547/−173 lines, 8 filesdep +bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring

API changes (from Hackage documentation)

+ Text.Megaparsec.Utils.Byte: boolParser :: (MonadParsec e s m, Token s ~ Word8) => m Bool
+ Text.Megaparsec.Utils.Byte: boundedEnumShowParser :: forall a e s m. (MonadParsec e s m, Token s ~ Word8, Bounded a, Enum a, Show a) => m a
+ Text.Megaparsec.Utils.Byte: commaSeparated :: (MonadParsec e s m, Token s ~ Word8) => m a -> m (NonEmpty a)
+ Text.Megaparsec.Utils.Byte: numParser :: (MonadFail m, MonadParsec e s m, Num a, Read a, Token s ~ Word8) => m a
+ Text.Megaparsec.Utils.Byte: posDecNumParser :: (MonadFail m, MonadParsec e s m, Read a, Token s ~ Word8) => m a
+ Text.Megaparsec.Utils.Byte: posNumParser :: (MonadFail m, MonadParsec e s m, Read a, Token s ~ Word8) => m a
+ Text.Megaparsec.Utils.Byte: uuidParser :: (MonadParsec e s m, Token s ~ Word8) => m UUID
+ Text.Megaparsec.Utils.Char: boolParser :: (MonadParsec e s m, Token s ~ Char) => m Bool
+ Text.Megaparsec.Utils.Char: boundedEnumShowParser :: forall a e s m. (MonadParsec e s m, Token s ~ Char, Bounded a, Enum a, Show a) => m a
+ Text.Megaparsec.Utils.Char: commaSeparated :: (MonadParsec e s m, Token s ~ Char) => m a -> m (NonEmpty a)
+ Text.Megaparsec.Utils.Char: numParser :: (MonadFail m, MonadParsec e s m, Num a, Read a, Token s ~ Char) => m a
+ Text.Megaparsec.Utils.Char: parsecToJSONParser :: ShowErrorComponent e => String -> Parsec e Text a -> Value -> Parser a
+ Text.Megaparsec.Utils.Char: posDecNumParser :: (MonadFail m, MonadParsec e s m, Read a, Token s ~ Char) => m a
+ Text.Megaparsec.Utils.Char: posNumParser :: (MonadFail m, MonadParsec e s m, Read a, Token s ~ Char) => m a
+ Text.Megaparsec.Utils.Char: uuidParser :: (MonadParsec e s m, Token s ~ Char) => m UUID
+ Text.Megaparsec.Utils.Common: mkBoolParser :: MonadParsec e s m => (Token s -> m (Token s)) -> (Char -> Token s) -> m Bool
+ Text.Megaparsec.Utils.Common: mkBoundedEnumShowParser :: forall a e s m. (MonadParsec e s m, Bounded a, Enum a, Show a) => (Token s -> m (Token s)) -> (Char -> Token s) -> m a
+ Text.Megaparsec.Utils.Common: mkCommaSeparated :: MonadParsec e s m => m (Token s) -> m a -> m (NonEmpty a)
+ Text.Megaparsec.Utils.Common: mkNumParser :: (MonadFail m, MonadParsec e s m, Num a) => m (Token s) -> m a -> m a
+ Text.Megaparsec.Utils.Common: mkPosDecNumParser :: (MonadFail m, MonadParsec e s m, Read a) => m (Token s) -> m (Token s) -> (Token s -> Char) -> m a
+ Text.Megaparsec.Utils.Common: mkPosNumParser :: (MonadFail m, MonadParsec e s m, Read a) => m (Token s) -> (Token s -> Char) -> m a
+ Text.Megaparsec.Utils.Common: mkUuidParser :: MonadParsec e s m => m (Token s) -> m (Token s) -> (Token s -> Char) -> m UUID
+ Text.Megaparsec.Utils.Common: occurrence :: MonadParsec e s m => m a -> m a
+ Text.Megaparsec.Utils.Common: occurrences :: MonadParsec e s m => m a -> m [a]
- Text.Megaparsec.Utils: boolParser :: Ord e => Parsec e String Bool
+ Text.Megaparsec.Utils: boolParser :: (MonadParsec e s m, Token s ~ Char) => m Bool
- Text.Megaparsec.Utils: boundedEnumShowParser :: forall a e. (Ord e, Bounded a, Enum a, Show a) => Parsec e String a
+ Text.Megaparsec.Utils: boundedEnumShowParser :: forall a e s m. (MonadParsec e s m, Token s ~ Char, Bounded a, Enum a, Show a) => m a
- Text.Megaparsec.Utils: commaSeparated :: Ord e => Parsec e String a -> Parsec e String (NonEmpty a)
+ Text.Megaparsec.Utils: commaSeparated :: (MonadParsec e s m, Token s ~ Char) => m a -> m (NonEmpty a)
- Text.Megaparsec.Utils: numParser :: (Ord e, Num a, Read a) => Parsec e String a
+ Text.Megaparsec.Utils: numParser :: (MonadFail m, MonadParsec e s m, Num a, Read a, Token s ~ Char) => m a
- Text.Megaparsec.Utils: occurrence :: Ord e => Parsec e String a -> Parsec e String a
+ Text.Megaparsec.Utils: occurrence :: MonadParsec e s m => m a -> m a
- Text.Megaparsec.Utils: occurrences :: Ord e => Parsec e String a -> Parsec e String [a]
+ Text.Megaparsec.Utils: occurrences :: MonadParsec e s m => m a -> m [a]
- Text.Megaparsec.Utils: posDecNumParser :: (Ord e, Read a) => Parsec e String a
+ Text.Megaparsec.Utils: posDecNumParser :: (MonadFail m, MonadParsec e s m, Read a, Token s ~ Char) => m a
- Text.Megaparsec.Utils: posNumParser :: (Ord e, Read a) => Parsec e String a
+ Text.Megaparsec.Utils: posNumParser :: (MonadFail m, MonadParsec e s m, Read a, Token s ~ Char) => m a
- Text.Megaparsec.Utils: uuidParser :: Ord e => Parsec e String UUID
+ Text.Megaparsec.Utils: uuidParser :: (MonadParsec e s m, Token s ~ Char) => m UUID

Files

− CHANGELOG.md
@@ -1,33 +0,0 @@-Changelog--## [1.1.3](https://github.com/haskell-github-trust/megaparsec-utils/compare/1.1.2...1.1.3) (2025-08-07)--## [1.1.2](https://github.com/haskell-github-trust/megaparsec-utils/compare/1.1.1...1.1.2) (2025-07-29)--## [1.1.1](https://github.com/haskell-github-trust/megaparsec-utils/compare/1.1.0...1.1.1) (2025-07-28)--# [1.1.0](https://github.com/haskell-github-trust/megaparsec-utils/compare/1.0.0...1.1.0) (2025-07-28)---### Features--* add on/off to booleans ([1e9b885](https://github.com/haskell-github-trust/megaparsec-utils/commit/1e9b88532ec86ef3bf229de01c0a5a81a89261ec))--# 1.0.0 (2025-07-28)---### Bug Fixes--* fix date parser with day short names ([64f3229](https://github.com/haskell-github-trust/megaparsec-utils/commit/64f3229dcfd660bdd770938da0bbacbf84c02dc3))-* fix invalid generation of TimeOfDay ([6bc728e](https://github.com/haskell-github-trust/megaparsec-utils/commit/6bc728ea31e2cfb380927c53ee544f44b399ece7))-* fix type errors in boundedEnumShowParser ([9c0fe3d](https://github.com/haskell-github-trust/megaparsec-utils/commit/9c0fe3db132bfe123be2d71e7c92f5928113cc26))---### Features--* add aeson parser transformer ([84857eb](https://github.com/haskell-github-trust/megaparsec-utils/commit/84857eb2d5221f8547e9130c2b8503e7e59888a4))-* add date parser ([357d03f](https://github.com/haskell-github-trust/megaparsec-utils/commit/357d03f265e053f128be8711aec53e0b7659e563))-* add duration parsers ([a89bd88](https://github.com/haskell-github-trust/megaparsec-utils/commit/a89bd885b867b65cb7db3fcad4635cd38cba2227))-* add gregorian day parser ([46d4178](https://github.com/haskell-github-trust/megaparsec-utils/commit/46d4178945ad8bff5b309b95d9a2901a53b60ef9))-* add parser for decimal numbers ([42de75f](https://github.com/haskell-github-trust/megaparsec-utils/commit/42de75fec47b29caf1de59334b7a9c5e05f87a2f))-* add time parsers ([f267ffd](https://github.com/haskell-github-trust/megaparsec-utils/commit/f267ffd718f701046965318f99aa73b7f9e7b335))
megaparsec-utils.cabal view
@@ -1,11 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.37.0.+-- This file has been generated from package.yaml by hpack version 0.39.6. -- -- see: https://github.com/sol/hpack  name:           megaparsec-utils-version:        0.1.1.3+version:        0.2.0.0 synopsis:       Parsers and utilities for the Megaparsec library. description:    Common parsers and utilities to use with the Megaparsec library. category:       Parsing@@ -18,12 +18,11 @@ license-file:   LICENSE build-type:     Simple tested-with:-    GHC == 9.8+    GHC == 9.10+  , GHC == 9.8   , GHC == 9.6-  , GHC == 9.4 extra-source-files:     README.md-    CHANGELOG.md  source-repository head   type: git@@ -32,6 +31,9 @@ library   exposed-modules:       Text.Megaparsec.Utils+      Text.Megaparsec.Utils.Byte+      Text.Megaparsec.Utils.Char+      Text.Megaparsec.Utils.Common   other-modules:       Paths_megaparsec_utils   hs-source-dirs:@@ -40,6 +42,7 @@   build-depends:       aeson >=2.0 && <3     , base >=4.7 && <5+    , bytestring >=0.11 && <0.13     , megaparsec >=9.0 && <10     , parser-combinators >=1.0 && <2     , text >=2.0 && <3@@ -51,6 +54,7 @@   main-is: Spec.hs   other-modules:       Text.Megaparsec.UtilsSpec+      Text.Megaparsec.UtilsStreamSpec       Paths_megaparsec_utils   hs-source-dirs:       test@@ -59,6 +63,7 @@       QuickCheck     , aeson >=2.0 && <3     , base >=4.7 && <5+    , bytestring >=0.11 && <0.13     , hspec     , megaparsec >=9.0 && <10     , megaparsec-utils
src/Text/Megaparsec/Utils.hs view
@@ -1,7 +1,3 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeFamilies #-}- -- | -- Module      : Text.Megaparsec.Utils -- Description : Various generic parsers and combinators.@@ -10,133 +6,38 @@ -- Maintainer  : drlkf@drlkf.net -- Stability   : experimental ----- Generic utilities and common parsers.+-- Re-exports of 'Text.Megaparsec.Utils.Char' and 'Text.Megaparsec.Utils.Common',+-- plus 'String'-based conversion helpers. New code should use+-- 'Text.Megaparsec.Utils.Char' or 'Text.Megaparsec.Utils.Byte' directly. module Text.Megaparsec.Utils (   -- * Scalar parsers-  boolParser,-  numParser,-  posDecNumParser,-  posNumParser,-  uuidParser,+  Char.boolParser,+  Char.numParser,+  Char.posDecNumParser,+  Char.posNumParser,+  Char.uuidParser,    -- * Combinators-  commaSeparated,-  occurrence,-  occurrences,+  Char.commaSeparated,+  Common.occurrence,+  Common.occurrences,    -- * Compatibility utilities-  boundedEnumShowParser,+  Char.boundedEnumShowParser,   parsecToReadsPrec,   parsecToJSONParser, ) where -import Control.Applicative (many, some, (<|>))-import Control.Applicative.Combinators (choice)-import Control.Monad (replicateM)-import Control.Monad.Combinators (optional) import Data.Aeson.Types (Parser, Value, withText)-import Data.Functor (($>))-import Data.List (intercalate, sortOn)-import Data.List.NonEmpty (NonEmpty ((:|)))-import Data.Maybe (fromJust) import qualified Data.Text as T (unpack)-import Data.UUID (UUID)-import qualified Data.UUID as U (fromString) import Text.Megaparsec (   Parsec,   ShowErrorComponent,-  anySingle,   errorBundlePretty,   runParser,-  try,  )-import Text.Megaparsec.Char (-  char,-  digitChar,-  hexDigitChar,-  string',- )-import Text.Read (readMaybe)---- | Parse a case-insensitive human-readable boolean, including C-style numbers,--- English yes-no and @on@ / @off@.-boolParser-  :: Ord e-  => Parsec e String Bool-boolParser = true <|> false- where-  true = True <$ choice (map string' ["true", "y", "yes", "on", "1"])-  false = False <$ choice (map string' ["false", "n", "no", "off", "0"])---- | Parse a 'Bounded' 'Enum' type that has a 'Show' instance, trying all--- possibilities, case-insensitive, in the 'Enum' order.-boundedEnumShowParser-  :: forall a e-   . Ord e-  => Bounded a-  => Enum a-  => Show a-  => Parsec e String a-boundedEnumShowParser =-  choice . map parseShow $ sortOn (negate . length . show) [(minBound :: a) ..]- where-  parseShow a = string' (show a) $> a---- | Parse a comma-separated list of items.-commaSeparated-  :: Ord e-  => Parsec e String a-  -> Parsec e String (NonEmpty a)-commaSeparated p = (:|) <$> p <*> many (char ',' >> p)---- | Parse any occurrence of a given parser. Consumes any input before occurrence.-occurrence-  :: Ord e-  => Parsec e String a-  -> Parsec e String a-occurrence p = go- where-  go = p <|> (anySingle >> go)---- | Parse all occurrences of a given parser.-occurrences-  :: Ord e-  => Parsec e String a-  -> Parsec e String [a]-occurrences = some . try . occurrence . try---- | Parse a positive number, with or without decimals prefixed by a @.@.-posDecNumParser-  :: Ord e-  => Read a-  => Parsec e String a-posDecNumParser = do-  num <- some digitChar-  dec <- maybe "" ("." <>) <$> optional (char '.' >> some digitChar)--  let str = num <> dec--  maybe (fail ("could not read from input: " <> str)) pure (readMaybe str)---- | Parse a positive integer.-posNumParser-  :: Ord e-  => Read a-  => Parsec e String a-posNumParser = do-  digits <- some digitChar-  maybe-    (fail ("could not read from digits: " <> digits))-    pure-    (readMaybe digits) ---- | Parse an integer, without any space between minus sign and digits.-numParser-  :: Ord e-  => Num a-  => Read a-  => Parsec e String a-numParser = (char '-' >> negate <$> posNumParser) <|> posNumParser+import qualified Text.Megaparsec.Utils.Char as Char+import qualified Text.Megaparsec.Utils.Common as Common  -- | Convert a 'Parsec' parser into a 'Parser' suited for 'Data.Aeson.FromJSON' -- instances.@@ -156,19 +57,3 @@   :: Parsec e String a   -> ReadS a parsecToReadsPrec p = either (const []) (\x -> [(x, "")]) . runParser p "string"---- | Parse a RFC4122-compliant UUID.-uuidParser-  :: Ord e-  => Parsec e String UUID-uuidParser = do-  part1 <- replicateM 8 hexDigitChar <* char '-'-  part2 <- replicateM 4 hexDigitChar <* char '-'-  part3 <- replicateM 4 hexDigitChar <* char '-'-  part4 <- replicateM 4 hexDigitChar <* char '-'-  part5 <- replicateM 12 hexDigitChar--  pure-    (fromJust-     (U.fromString-      (intercalate "-" [part1, part2, part3, part4, part5])))
+ src/Text/Megaparsec/Utils/Byte.hs view
@@ -0,0 +1,120 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-- |+-- Module      : Text.Megaparsec.Utils.Byte+-- Description : Parsers for streams of 'Word8'.+-- Copyright   : (c) drlkf, 2026+-- License     : GPL-3+-- Maintainer  : drlkf@drlkf.net+-- Stability   : experimental+--+-- Generic parsers for streams of 'Word8' (strict and lazy 'ByteString').+module Text.Megaparsec.Utils.Byte (+  -- * Scalar parsers+  boolParser,+  numParser,+  posDecNumParser,+  posNumParser,+  uuidParser,++  -- * Combinators+  commaSeparated,++  -- * Compatibility utilities+  boundedEnumShowParser,+) where++import Data.List.NonEmpty (NonEmpty)+import Data.UUID (UUID)+import Data.Word (Word8)+import Text.Megaparsec (MonadParsec, Token)+import Text.Megaparsec.Byte (char, char', digitChar, hexDigitChar)+import Text.Megaparsec.Utils.Common (+  mkBoolParser,+  mkBoundedEnumShowParser,+  mkCommaSeparated,+  mkNumParser,+  mkPosDecNumParser,+  mkPosNumParser,+  mkUuidParser,+ )++-- | Parse a case-insensitive human-readable boolean, including C-style numbers,+-- English yes-no and @on@ / @off@.+--+-- @since 0.2.0.0+boolParser+  :: MonadParsec e s m+  => Token s ~ Word8+  => m Bool+boolParser = mkBoolParser char' (toEnum . fromEnum)++-- | Parse a 'Bounded' 'Enum' type that has a 'Show' instance, trying all+-- possibilities, case-insensitive, in the 'Enum' order.+--+-- @since 0.2.0.0+boundedEnumShowParser+  :: forall a e s m+   . MonadParsec e s m+  => Token s ~ Word8+  => Bounded a+  => Enum a+  => Show a+  => m a+boundedEnumShowParser = mkBoundedEnumShowParser char' (toEnum . fromEnum)++-- | Parse a comma-separated list of items.+--+-- @since 0.2.0.0+commaSeparated+  :: MonadParsec e s m+  => Token s ~ Word8+  => m a+  -> m (NonEmpty a)+commaSeparated = mkCommaSeparated (char 44)++-- | Parse a positive number, with or without decimals prefixed by a @.@.+--+-- @since 0.2.0.0+posDecNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Read a+  => Token s ~ Word8+  => m a+posDecNumParser = mkPosDecNumParser digitChar (char 46) (toEnum . fromIntegral)++-- | Parse a positive integer.+--+-- @since 0.2.0.0+posNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Read a+  => Token s ~ Word8+  => m a+posNumParser = mkPosNumParser digitChar (toEnum . fromIntegral)++-- | Parse an integer, without any space between minus sign and digits.+--+-- @since 0.2.0.0+numParser+  :: MonadFail m+  => MonadParsec e s m+  => Num a+  => Read a+  => Token s ~ Word8+  => m a+numParser = mkNumParser (char 45) posNumParser++-- | Parse a RFC4122-compliant UUID.+--+-- @since 0.2.0.0+uuidParser+  :: MonadParsec e s m+  => Token s ~ Word8+  => m UUID+uuidParser = mkUuidParser hexDigitChar (char 45) (toEnum . fromIntegral)
+ src/Text/Megaparsec/Utils/Char.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++-- |+-- Module      : Text.Megaparsec.Utils.Char+-- Description : Parsers for streams of 'Char'.+-- Copyright   : (c) drlkf, 2024+-- License     : GPL-3+-- Maintainer  : drlkf@drlkf.net+-- Stability   : experimental+--+-- Generic parsers for streams of 'Char' ('String', 'Text', lazy 'Text').+module Text.Megaparsec.Utils.Char (+  -- * Scalar parsers+  boolParser,+  numParser,+  posDecNumParser,+  posNumParser,+  uuidParser,++  -- * Combinators+  commaSeparated,++  -- * Compatibility utilities+  boundedEnumShowParser,+  parsecToJSONParser,+) where++import Data.Aeson.Types (Parser, Value, withText)+import Data.List.NonEmpty (NonEmpty)+import Data.Text (Text)+import Data.UUID (UUID)+import Text.Megaparsec (+  MonadParsec,+  Parsec,+  ShowErrorComponent,+  Token,+  errorBundlePretty,+  runParser,+ )+import Text.Megaparsec.Char (char, char', digitChar, hexDigitChar)+import Text.Megaparsec.Utils.Common (+  mkBoolParser,+  mkBoundedEnumShowParser,+  mkCommaSeparated,+  mkNumParser,+  mkPosDecNumParser,+  mkPosNumParser,+  mkUuidParser,+ )++-- | Parse a case-insensitive human-readable boolean, including C-style numbers,+-- English yes-no and @on@ / @off@.+--+-- @since 0.2.0.0+boolParser+  :: MonadParsec e s m+  => Token s ~ Char+  => m Bool+boolParser = mkBoolParser char' id++-- | Parse a 'Bounded' 'Enum' type that has a 'Show' instance, trying all+-- possibilities, case-insensitive, in the 'Enum' order.+--+-- @since 0.2.0.0+boundedEnumShowParser+  :: forall a e s m+   . MonadParsec e s m+  => Token s ~ Char+  => Bounded a+  => Enum a+  => Show a+  => m a+boundedEnumShowParser = mkBoundedEnumShowParser char' id++-- | Parse a comma-separated list of items.+--+-- @since 0.2.0.0+commaSeparated+  :: MonadParsec e s m+  => Token s ~ Char+  => m a+  -> m (NonEmpty a)+commaSeparated = mkCommaSeparated (char ',')++-- | Parse a positive number, with or without decimals prefixed by a @.@.+--+-- @since 0.2.0.0+posDecNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Read a+  => Token s ~ Char+  => m a+posDecNumParser = mkPosDecNumParser digitChar (char '.') id++-- | Parse a positive integer.+--+-- @since 0.2.0.0+posNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Read a+  => Token s ~ Char+  => m a+posNumParser = mkPosNumParser digitChar id++-- | Parse an integer, without any space between minus sign and digits.+--+-- @since 0.2.0.0+numParser+  :: MonadFail m+  => MonadParsec e s m+  => Num a+  => Read a+  => Token s ~ Char+  => m a+numParser = mkNumParser (char '-') posNumParser++-- | Convert a 'Parsec' parser on 'Text' into a 'Parser' suited for+-- 'Data.Aeson.FromJSON' instances.+--+-- @since 0.2.0.0+parsecToJSONParser+  :: ShowErrorComponent e+  => String+  -- ^ Parser name.+  -> Parsec e Text a+  -- ^ Parser.+  -> (Value -> Parser a)+parsecToJSONParser n p =+  withText n (either (fail . errorBundlePretty) pure . runParser p n)++-- | Parse a RFC4122-compliant UUID.+--+-- @since 0.2.0.0+uuidParser+  :: MonadParsec e s m+  => Token s ~ Char+  => m UUID+uuidParser = mkUuidParser hexDigitChar (char '-') id
+ src/Text/Megaparsec/Utils/Common.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}++-- |+-- Module      : Text.Megaparsec.Utils.Common+-- Description : Stream-agnostic combinators.+-- Copyright   : (c) drlkf, 2024+-- License     : GPL-3+-- Maintainer  : drlkf@drlkf.net+-- Stability   : experimental+--+-- Combinators that work on any stream.+module Text.Megaparsec.Utils.Common (+  occurrence,+  occurrences,++  -- * Shared parser builders+  mkBoolParser,+  mkBoundedEnumShowParser,+  mkCommaSeparated,+  mkNumParser,+  mkPosDecNumParser,+  mkPosNumParser,+  mkUuidParser,+) where++import Control.Applicative (many, some, (<|>))+import Control.Applicative.Combinators (choice)+import Control.Monad (replicateM)+import Control.Monad.Combinators (optional)+import Data.Foldable (traverse_)+import Data.Functor (($>))+import Data.List (intercalate, sortOn)+import Data.List.NonEmpty (NonEmpty ((:|)))+import Data.Maybe (fromJust)+import Data.UUID (UUID)+import qualified Data.UUID as U (fromString)+import Text.Megaparsec (MonadParsec, Token, anySingle, try)+import Text.Read (readMaybe)++-- | Parse any occurrence of a given parser. Consumes any input before occurrence.+--+-- @since 0.2.0.0+occurrence :: MonadParsec e s m => m a -> m a+occurrence p = go+ where+  go = p <|> (anySingle >> go)++-- | Parse all occurrences of a given parser.+--+-- @since 0.2.0.0+occurrences :: MonadParsec e s m => m a -> m [a]+occurrences = some . try . occurrence . try++-- | Build a case-insensitive human-readable boolean parser, including C-style+-- numbers, English yes-no and @on@ / @off@.+--+-- @since 0.2.0.0+mkBoolParser+  :: MonadParsec e s m+  => (Token s -> m (Token s))+  -- ^ Case-insensitive single-token parser.+  -> (Char -> Token s)+  -- ^ Convert a literal 'Char' to a token.+  -> m Bool+mkBoolParser char' fromChar = true <|> false+ where+  true = True <$ choice (map (try . traverse_ (char' . fromChar)) ["true", "y", "yes", "on", "1"])+  false = False <$ choice (map (try . traverse_ (char' . fromChar)) ["false", "n", "no", "off", "0"])++-- | Build a parser for a 'Bounded' 'Enum' type that has a 'Show' instance,+-- trying all possibilities, case-insensitive, in the 'Enum' order.+--+-- @since 0.2.0.0+mkBoundedEnumShowParser+  :: forall a e s m+   . MonadParsec e s m+  => Bounded a+  => Enum a+  => Show a+  => (Token s -> m (Token s))+  -- ^ Case-insensitive single-token parser.+  -> (Char -> Token s)+  -- ^ Convert a literal 'Char' to a token.+  -> m a+mkBoundedEnumShowParser char' fromChar =+  choice . map parseShow $ sortOn (negate . length . show) [(minBound :: a) ..]+ where+  parseShow a = try (traverse_ (char' . fromChar) (show a)) $> a++-- | Build a parser for a comma-separated list of items.+--+-- @since 0.2.0.0+mkCommaSeparated+  :: MonadParsec e s m+  => m (Token s)+  -- ^ Comma parser.+  -> m a+  -> m (NonEmpty a)+mkCommaSeparated comma p = (:|) <$> p <*> many (comma >> p)++-- | Build a parser for a positive number, with or without decimals prefixed by+-- a @.@.+--+-- @since 0.2.0.0+mkPosDecNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Read a+  => m (Token s)+  -- ^ Digit parser.+  -> m (Token s)+  -- ^ Decimal-point parser.+  -> (Token s -> Char)+  -- ^ Convert a token to a 'Char'.+  -> m a+mkPosDecNumParser digitChar dot toChar = do+  num <- some digitChar+  dec <- optional (dot >> some digitChar)++  let toStr = map toChar+      str = toStr num <> maybe "" (('.' :) . toStr) dec++  maybe (fail ("could not read from input: " <> str)) pure (readMaybe str)++-- | Build a parser for a positive integer.+--+-- @since 0.2.0.0+mkPosNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Read a+  => m (Token s)+  -- ^ Digit parser.+  -> (Token s -> Char)+  -- ^ Convert a token to a 'Char'.+  -> m a+mkPosNumParser digitChar toChar = do+  digits <- some digitChar+  let str = map toChar digits+  maybe+    (fail ("could not read from digits: " <> str))+    pure+    (readMaybe str)++-- | Build a parser for an integer, without any space between minus sign and+-- digits.+--+-- @since 0.2.0.0+mkNumParser+  :: MonadFail m+  => MonadParsec e s m+  => Num a+  => m (Token s)+  -- ^ Minus-sign parser.+  -> m a+  -- ^ Positive-number parser.+  -> m a+mkNumParser minus posNumParser = (minus >> negate <$> posNumParser) <|> posNumParser++-- | Build a parser for a RFC4122-compliant UUID.+--+-- @since 0.2.0.0+mkUuidParser+  :: MonadParsec e s m+  => m (Token s)+  -- ^ Hex-digit parser.+  -> m (Token s)+  -- ^ Dash parser.+  -> (Token s -> Char)+  -- ^ Convert a token to a 'Char'.+  -> m UUID+mkUuidParser hexDigitChar dash toChar = do+  part1 <- replicateM 8 hexDigitChar <* dash+  part2 <- replicateM 4 hexDigitChar <* dash+  part3 <- replicateM 4 hexDigitChar <* dash+  part4 <- replicateM 4 hexDigitChar <* dash+  part5 <- replicateM 12 hexDigitChar++  let toStr = map toChar++  pure+    ( fromJust+        ( U.fromString+            (intercalate "-" (map toStr [part1, part2, part3, part4, part5]))+        )+    )
test/Text/Megaparsec/UtilsSpec.hs view
@@ -158,8 +158,8 @@      context "posDecNumParser" $ do       it "no decimals" . property $ \v ->-        parseMaybe' posDecNumParser (show (abs (v :: Int)))-          `shouldBe` Just (fromIntegral (abs v))+        parseMaybe' (posDecNumParser :: Parsec Void String Int) (show (abs (v :: Int)))+          `shouldBe` Just (abs v)        it "decimals" . property $ \v ->         parseMaybe' posDecNumParser (printf "%f" (abs (v :: Double)))@@ -235,17 +235,17 @@      context "invalid" $ do       it "empty" $-        parseOrPrettyError (commaSeparated (numParser @Void @Int)) ""+        parseOrPrettyError (commaSeparated (numParser :: Parsec Void String Int)) ""         `shouldSatisfy` isLeft        it "first" $-        parseOrPrettyError (commaSeparated (numParser @Void @Int)) "test"+        parseOrPrettyError (commaSeparated (numParser :: Parsec Void String Int)) "test"         `shouldSatisfy` isLeft        it "first partially correct" $-        parseOrPrettyError (commaSeparated ((numParser @Void @Int) <* eof)) "test"+        parseOrPrettyError (commaSeparated ((numParser :: Parsec Void String Int) <* eof)) "test"         `shouldSatisfy` isLeft        it "second" $-        parseOrPrettyError (commaSeparated (numParser @Void @Int)) "test"+        parseOrPrettyError (commaSeparated (numParser :: Parsec Void String Int)) "test"         `shouldSatisfy` isLeft
+ test/Text/Megaparsec/UtilsStreamSpec.hs view
@@ -0,0 +1,66 @@+{-# LANGUAGE TypeApplications #-}++module Text.Megaparsec.UtilsStreamSpec (+  spec,+) where++import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as B+import Data.Text (Text)+import qualified Data.Text as T+import Data.Void (Void)+import Test.Hspec (Spec, describe, it, shouldBe)+import Test.QuickCheck (property)+import Text.Megaparsec (ParseErrorBundle, Parsec, runParser)+import qualified Text.Megaparsec.Utils.Byte as Byte+import qualified Text.Megaparsec.Utils.Char as Char+import Text.Megaparsec.Utils.Common (occurrence)++parseText :: Parsec Void Text a -> Text -> Either (ParseErrorBundle Text Void) a+parseText p = runParser p "test"++parseBytes+  :: Parsec Void ByteString a+  -> ByteString+  -> Either (ParseErrorBundle ByteString Void) a+parseBytes p = runParser p "test"++charNumParser :: Parsec Void Text Int+charNumParser = Char.numParser++byteNumParser :: Parsec Void ByteString Int+byteNumParser = Byte.numParser++spec :: Spec+spec = do+  describe "Char parsers on Text" $ do+    it "numParser" . property $ \v ->+      parseText charNumParser (T.pack (show (v :: Int))) `shouldBe` Right v++    it "boolParser" $ do+      parseText Char.boolParser (T.pack "yes") `shouldBe` Right True+      parseText Char.boolParser (T.pack "OFF") `shouldBe` Right False++    it "uuidParser" $ do+      parseText Char.uuidParser (T.pack "123e4567-e89b-12d3-a456-426614174000")+        `shouldBe` Right (read "123e4567-e89b-12d3-a456-426614174000")++    it "occurrence" . property $ \v ->+      parseText (occurrence charNumParser) (T.pack ("abc " <> show (v :: Int)))+        `shouldBe` Right v++  describe "Byte parsers on ByteString" $ do+    it "numParser" . property $ \v ->+      parseBytes byteNumParser (B.pack (show (v :: Int))) `shouldBe` Right v++    it "boolParser" $ do+      parseBytes Byte.boolParser (B.pack "yes") `shouldBe` Right True+      parseBytes Byte.boolParser (B.pack "OFF") `shouldBe` Right False++    it "uuidParser" $ do+      parseBytes Byte.uuidParser (B.pack "123e4567-e89b-12d3-a456-426614174000")+        `shouldBe` Right (read "123e4567-e89b-12d3-a456-426614174000")++    it "occurrence" . property $ \v ->+      parseBytes (occurrence byteNumParser) (B.pack ("abc " <> show (v :: Int)))+        `shouldBe` Right v