enum-text 0.5.2.1 → 0.5.3.0
raw patch · 3 files changed
+146/−7 lines, 3 filesdep +attoparsecdep +doctestdep +doctest-discoverPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: attoparsec, doctest, doctest-discover, enum-text, scientific
API changes (from Hackage documentation)
- Text.Enum.Text: instance (a Data.Type.Equality.~ GHC.Types.Char) => Text.Enum.Text.TextParsable [a]
- Text.Enum.Text: instance Text.Enum.Text.EnumText e => GHC.Arr.Ix (Text.Enum.Text.I e)
+ Text.Enum.Text: instance (a GHC.Types.~ GHC.Types.Char) => Text.Enum.Text.TextParsable [a]
+ Text.Enum.Text: instance Text.Enum.Text.EnumText e => GHC.Ix.Ix (Text.Enum.Text.I e)
Files
- enum-text.cabal +29/−4
- src/Text/Enum/Text.hs +116/−3
- test/Doctest.hs +1/−0
enum-text.cabal view
@@ -1,13 +1,11 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.31.2.+-- This file has been generated from package.yaml by hpack version 0.34.4. -- -- see: https://github.com/sol/hpack------ hash: 89afd88fe91337c68085eb43ff907f8be6ba391eefe6883a46038dec6e671fde name: enum-text-version: 0.5.2.1+version: 0.5.3.0 synopsis: A text rendering and parsing toolkit for enumerated types description: A text rendering and parsing toolkit for enumerated types. Please see the README on GitHub at <https://github.com/cdornan/enum-text#readme> category: Text@@ -37,11 +35,38 @@ ghc-options: -Wall build-depends: array+ , attoparsec , base >=4.9 && <10 , bytestring , fmt , hashable , possibly+ , scientific+ , text+ , time+ , unordered-containers+ default-language: Haskell2010++test-suite enum-text-tests+ type: exitcode-stdio-1.0+ main-is: Doctest.hs+ other-modules:+ Paths_enum_text+ hs-source-dirs:+ test+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ array+ , attoparsec+ , base >=4.9 && <10+ , bytestring+ , doctest+ , doctest-discover+ , enum-text+ , fmt+ , hashable+ , possibly+ , scientific , text , time , unordered-containers
src/Text/Enum/Text.hs view
@@ -13,12 +13,15 @@ , defaultEnumTextConfig ) where +import Control.Monad (void) import Control.Monad.Fail as CMF import Data.Array+import qualified Data.Attoparsec.Text as AP import qualified Data.ByteString.Char8 as B import Data.Coerce import Data.Hashable import Data.Possibly+import Data.Scientific (toBoundedInteger) import qualified Data.HashMap.Strict as HM import qualified Data.Text as T import qualified Data.Text.Encoding as TE@@ -161,7 +164,22 @@ parseText :: T.Text -> Possibly a instance TextParsable T.Text where parseText = return-instance TextParsable UTCTime where parseText = parseTextRead "UTCTime"++-- | Robust 'TextParsable' instance for 'UTCTime'.+--+-- Examples:+--+-- >>> show <$> parseText @UTCTime (T.pack "2020-01-01 10:12:30")+-- Right "2020-01-01 10:12:30 UTC"+--+-- >>> show <$> parseText @UTCTime (T.pack "2020-01-01T10:12:30")+-- Right "2020-01-01 10:12:30 UTC"+--+-- >>> show <$> parseText @UTCTime (T.pack "2020-01-01T10:12:30Z")+-- Right "2020-01-01 10:12:30 UTC"+--+instance TextParsable UTCTime where parseText = parseTextUsing parseUTC "UTCTime"+ instance TextParsable Day where parseText = parseTextRead "Day" instance TextParsable Int where parseText = parseDecimal instance a ~ Char => TextParsable [a] where parseText = return . T.unpack@@ -234,14 +252,109 @@ False -> Left $ "residual input: " ++ T.unpack r -- | Convert a 'Read' parser into a 'TextParsable'+--+-- Examples:+--+-- >>> parseTextRead @Int "Int" (T.pack "10")+-- Right 10+--+-- >>> parseTextRead @Int "Int" (T.pack "a")+-- Left "failed to parse Int: \"a\""+-- parseTextRead :: Read a => String -- ^ name of type bing parsed (for failure message) -> T.Text -- ^ 'T.Text' to be parsed -> Possibly a-parseTextRead ty_s txt =- maybe (Left $ typeError ty_s $ show str) Right $ readMaybe str+parseTextRead = parseTextUsing (readMaybe . T.unpack)++-- | Use the given function to turn the input string into a 'TextParsable'+--+-- Examples:+--+-- >>> parseTextUsing @Int (readMaybe . T.unpack) "Int" (T.pack "10")+-- Right 10+--+-- >>> parseTextUsing @Int (readMaybe . T.unpack) "Int" (T.pack "a")+-- Left "failed to parse Int: \"a\""+--+parseTextUsing :: (T.Text -> Maybe a)+ -> String -- ^ name of type bing parsed (for failure message)+ -> T.Text -- ^ 'T.Text' to be parsed+ -> Possibly a+parseTextUsing maybeParser ty_s txt =+ maybe (Left $ typeError ty_s $ show str) Right $ maybeParser txt where str = T.unpack txt typeError :: String -> String -> String typeError ty_s msg = "failed to parse "++ty_s++": "++msg++-------------------------------------------------------------------------------+-- support for parsing UTCTime (see enum-text#1)+-------------------------------------------------------------------------------++-- The following has been cribbed from the \"Data.API.Time\" module from the+-- \"api-tools\" package.++-- | Parse text as a 'UTCTime' in ISO 8601 format or a number of slight+-- variations thereof (the @T@ may be replaced with a space, and the seconds,+-- milliseconds and/or @Z@ timezone indicator may optionally be omitted).+--+-- Time zone designations other than @Z@ for UTC are not currently supported.+parseUTC :: T.Text -> Maybe UTCTime+parseUTC t = case AP.parseOnly (parserUTCTime <* AP.endOfInput) t of+ Left _ -> Nothing+ Right r -> Just r++parserUTCTime :: AP.Parser UTCTime+parserUTCTime = do+ day <- parserDay+ void $ AP.skip (\c -> c == ' ' || c == 'T')+ time <- parserTime+ mb_offset <- parserTimeZone+ pure (maybe id addUTCTime mb_offset $ UTCTime day time)++-- | Parser for @YYYY-MM-DD@ format.+parserDay :: AP.Parser Day+parserDay = do+ y :: Int <- AP.decimal <* AP.char '-'+ m :: Int <- AP.decimal <* AP.char '-'+ d :: Int <- AP.decimal+ case fromGregorianValid (fromIntegral y) m d of+ Just x -> pure x+ Nothing -> fail "invalid date"++-- | Parser for times in the format @HH:MM@, @HH:MM:SS@ or @HH:MM:SS.QQQ...@.+parserTime :: AP.Parser DiffTime+parserTime = do+ h :: Int <- AP.decimal+ void $ AP.char ':'+ m :: Int <- AP.decimal+ c <- AP.peekChar+ s <- case c of+ Just ':' -> AP.anyChar *> AP.scientific+ _ -> pure 0+ case toBoundedInteger (10^(12::Int) * (s + fromIntegral (60*(m + 60*h)))) of+ Just n -> pure (picosecondsToDiffTime (fromIntegral (n :: Int)))+ Nothing -> fail "seconds out of range"++-- | Parser for time zone indications such as @Z@, @ UTC@ or an explicit offset+-- like @+HH:MM@ or @-HH@. Returns 'Nothing' for UTC. Local times (without a+-- timezone designator) are assumed to be UTC. If there is an explicit offset,+-- returns its negation.+parserTimeZone :: AP.Parser (Maybe NominalDiffTime)+parserTimeZone = do+ c <- AP.option 'Z' AP.anyChar+ case c of+ 'Z' -> pure Nothing+ ' ' -> "UTC" *> pure Nothing+ '+' -> parse_offset True+ '-' -> parse_offset False+ _ -> fail "unexpected time zone character"+ where+ parse_offset pos = do+ hh :: Int <- read <$> AP.count 2 AP.digit+ AP.option () (AP.skip (== ':'))+ mm :: Int <- AP.option 0 (read <$> AP.count 2 AP.digit)+ let v = (if pos then negate else id) ((hh*60 + mm) * 60)+ return (Just (fromIntegral v))
+ test/Doctest.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF doctest-discover -optF test/doctest-config.json #-}