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 +0/−33
- megaparsec-utils.cabal +10/−5
- src/Text/Megaparsec/Utils.hs +14/−129
- src/Text/Megaparsec/Utils/Byte.hs +120/−0
- src/Text/Megaparsec/Utils/Char.hs +143/−0
- src/Text/Megaparsec/Utils/Common.hs +188/−0
- test/Text/Megaparsec/UtilsSpec.hs +6/−6
- test/Text/Megaparsec/UtilsStreamSpec.hs +66/−0
− 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