ttc 1.4.0.0 → 1.5.0.0
raw patch · 11 files changed
+3723/−3687 lines, 11 filesdep ~basedep ~bytestringdep ~tasty
Dependency ranges changed: base, bytestring, tasty, tasty-hunit, template-haskell, text, text-short
Files
- CHANGELOG.md +23/−2
- LICENSE +1/−1
- README.md +10/−174
- src/Data/TTC.hs +2298/−1991
- src/Data/TTC/Wrapper.hs +163/−0
- test/Data/TTC/Test.hs +989/−1456
- test/Data/TTC/Wrapper/Test.hs +174/−0
- test/Spec.hs +2/−0
- test/TestString.hs +0/−31
- test/TestTypes/Ex.hs +31/−0
- ttc.cabal +32/−32
CHANGELOG.md view
@@ -1,10 +1,10 @@-# `ttc-haskell` Changelog+# `ttc` Changelog This project follows the [Haskell package versioning policy][PVP], with versions in `A.B.C.D` format. `A` may be incremented arbitrarily for non-technical reasons, but [semantic versioning][SemVer] is otherwise followed, where `A.B` is the major version, `C` is the minor version, and `D`-is the patch version. Initial development uses versions `0.0.0.D`, for which+is the patch version. Initial development uses versions `0.0.C.D`, for which every version is considered breaking. [PVP]: <https://pvp.haskell.org/>@@ -23,6 +23,27 @@ * Changes are listed in arbitrary order and present tense. [KaC]: <https://keepachangelog.com/en/1.0.0/>++## 1.5.0.0 (2025-01-02)++### Breaking++* Change type argument order for easier use with `TypeApplications`+* Add `RenderDefault` `Bool` instance+* Add `ParseDefault` `Bool` instance+* Add missing `RenderDefault` and `ParseDefault` instances for `TLB.Builder`,+ `ST.ShortText`, `BSB.Builder`, and `SBS.ShortByteString`+* Remove support for GHC 8.6, constraining lower bounds+* Remove support for GHC 8.4, constraining lower bounds+* Remove support for GHC 8.2, constraining lower bounds+* Change minimal Cabal from 1.24 to 3.0++### Non-Breaking++* Add `Data.TTC.Wrapper` module+* Add Template Haskell functions for loading default instances+* Bump `base` dependency version upper bound+* Bump `template-haskell` dependency version upper bound ## 1.4.0.0 (2023-12-03)
LICENSE view
@@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2019-2023 Travis Cardwell+Copyright (c) 2019-2025 Travis Cardwell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal
README.md view
@@ -6,32 +6,20 @@ [](https://stackage.org/package/ttc) [](https://stackage.org/nightly/package/ttc) -* [Overview](#overview)- * [`Textual`](#textual)- * [`Render`](#render)- * [`Parse`](#parse)- * [Constant Validation](#constant-validation)-* [Related Work](#related-work)- * [Rendering and Parsing](#rendering-and-parsing)- * [Validating Constants](#validating-constants)- * [String Type Conversion](#string-type-conversion)- * [Arbitrary Type Conversion](#arbitrary-type-conversion)-* [Project](#project)- * [Links](#links)- * [Tags](#tags)- * [Contribution](#contribution)- * [License](#license)- ## Overview TTC, an initialism of _Textual Type Classes_, is a library that provides `Render` and `Parse` type classes for conversion between data types and textual data types (strings). Use the `Show` and `Read` type classes for debugging/development, and use the `Render` and `Parse` type classes for your-own purposes. The library also provides a `Textual` type class for conversion-between textual data types as well as functions for validating constants at+own purposes. The library also provides various ways to validate constants at compile-time. +This package (`ttc`) uses a `Textual` type class for conversion between+textual data types. This package uses simple types and compiles quickly, but+the supported textual data types are fixed. It is not possible for users to+add support for additional textual data types.+ Since a type may have at most one instance of a given type class, special care must be taken when defining type class instances in a shared library. In particular, orphan instances should generally *not* be used in shared@@ -53,7 +41,7 @@ 2. [Render and Parse][] 3. [Validated Constants][] 4. [Best Practices][]-* The [examples][] directory in the GitHub repository contains usage examples.+* The [`ttc-examples`][] directory in the repository contains usage examples. [API documentation]: <https://hackage.haskell.org/package/ttc#modules> [series of articles]: <https://www.extrema.is/articles/ttc-textual-type-classes>@@ -61,7 +49,7 @@ [Render and Parse]: <https://www.extrema.is/articles/ttc-textual-type-classes/render-and-parse> [Validated Constants]: <https://www.extrema.is/articles/ttc-textual-type-classes/validated-constants> [Best Practices]: <https://www.extrema.is/articles/ttc-textual-type-classes/best-practices>-[examples]: <https://github.com/ExtremaIS/ttc-haskell/tree/main/examples>+[`ttc-examples`]: <https://github.com/ExtremaIS/ttc-haskell/tree/main/ttc-examples> ### `Textual` @@ -186,160 +174,8 @@ For more details, see the [Validated Constants][] article. -## Related Work--### Rendering and Parsing--The [relude][] library has polymorphic versions of `show` and `readEither` in-`Relude.String.Conversion`, as well as various type classes for converting-between string types. This does not encourage using `Show` and `Read`-instances with syntactically valid Haskell syntax, and it encourages the using-of the `String` data type.--The [rio][] library has a `Display` type class with a similar goal as-`TTC.Render`. Since the library encourages a uniform usage of textual data-types, `Display` only provides functions for rendering to `Text` and a builder-format. It does not have a type class similar to `TTC.Parse`.--The [text-display][] library defines a `Display` type class intended to render-user-facing text. It uses a `Builder` type internally and renders to a `Text`-value.--Harry Garrood has an interesting series of blog posts about type classes and-`Show`:--* [Down with Show! Part 1: Rules of thumb for when to use a type class][]-* [Down with Show! Part 2: What's wrong with the Show type class][]-* [Down with Show! Part 3: A replacement for Show][]--[relude]: <https://hackage.haskell.org/package/relude>-[rio]: <https://hackage.haskell.org/package/rio>-[text-display]: <https://hackage.haskell.org/package/text-display>-[Down with Show! Part 1: Rules of thumb for when to use a type class]: <https://harry.garrood.me/blog/down-with-show-part-1/>-[Down with Show! Part 2: What's wrong with the Show type class]: <https://harry.garrood.me/blog/down-with-show-part-2/>-[Down with Show! Part 3: A replacement for Show]: <https://harry.garrood.me/blog/down-with-show-part-3/>--### Validating Constants--The [qq-literals][] library creates a `QuasiQuoter` from a parse function of-type `String -> Either String a`. The functionality is similar to TTC's-`mkUntypedValidQQ` function. The `mkUntypedValidQQ` function allows the user-to choose the name of the `QuasiQuoter` because a name like `valid` is-preferred when used via a qualified import while a name like `username` may be-preferred when not using qualified imports. Note that `mkUntypedValidQQ` also-splices in an explicit type signature.--The [validated-literals][] library has a `Validate` type class that is similar-to `TTC.Parse` but supports conversion between arbitrary types, not just from-textual data types. Template Haskell functions are provided to perform-validation at compile-time. Result types must either have `Lift` instances or-equivalent implementations.--Chris Done posted [a gist][] about implementing statically checked overloaded-strings.--[qq-literals]: <https://hackage.haskell.org/package/qq-literals>-[validated-literals]: <https://hackage.haskell.org/package/validated-literals>-[a gist]: <https://gist.github.com/chrisdone/809296b769ee36d352ae4f8dbe89a364>--### String Type Conversion--There are a number of libraries that simplify conversion between string types.--The following libraries provide type classes with two type variables. The-primary benefit of this approach is that one can add support for any string-type. The drawback of this approach is that implementations of `Render` and-`Parse` using such a type class would have to be done via a fixed type,-resulting in unnecessary conversion when using other types.--* [string-conv][]-* [string-conversions][]--The following library provide type classes with a single type variable, but-conversion is done via a fixed type.--* [hxt-regex-xmlschema][] has a `StringLike` type class and does conversion- via the `String` type-* [ListLike][] has a `StringLike` type class and does conversion via the- `String` type-* [monoid-subclasses][] provides a `TextualMonoid` type class that provides an- abstract API over textual types, using `String` as the underlying type-* [stringlike][] converts via the `Text` type-* [tagsoup][] has a `StringLike` type class that provides an abstract API over- textual types and a `castString` function that converts via the `String`- type-* [text-conversions][] converts via the `Text` type-* [textual][] (deprecated) converts via the `String` type--[string-conv]: <https://hackage.haskell.org/package/string-conv>-[string-conversions]: <https://hackage.haskell.org/package/string-conversions>-[hxt-regex-xmlschema]: <https://hackage.haskell.org/package/hxt-regex-xmlschema>-[ListLike]: <https://hackage.haskell.org/package/ListLike>-[monoid-subclasses]: <https://hackage.haskell.org/package/monoid-subclasses>-[stringlike]: <https://hackage.haskell.org/package/stringlike>-[tagsoup]: <https://hackage.haskell.org/package/tagsoup>-[text-conversions]: <https://hackage.haskell.org/package/text-conversions>-[textual]: <https://hackage.haskell.org/package/textual>--### Arbitrary Type Conversion--There are also a number of libraries that provide type classes for conversion-between arbitrary types, including string types.--* [basement][] provides type classes for conversion that may fail as well as- conversion that cannot fail-* [convertible][]-* [witch][] provides type classes for conversion that may fail as well as- conversion that cannot fail--[basement]: <https://hackage.haskell.org/package/basement>-[convertible]: <https://hackage.haskell.org/package/convertible>-[witch]: <https://hackage.haskell.org/package/witch>- ## Project -### Links--* Hackage: <https://hackage.haskell.org/package/ttc>-* Stackage: <https://stackage.org/package/ttc>-* Flora: <https://flora.pm/packages/@hackage/ttc>-* GitHub: <https://github.com/ExtremaIS/ttc-haskell>-* GitHub Actions CI: <https://github.com/ExtremaIS/ttc-haskell/actions>--### Branches--The `main` branch is reserved for releases. It may be considered stable, and-`HEAD` is always the latest release.--The `develop` branch is the primary development branch. It contains changes-that have not yet been released, and it is not necessarily stable.--[Hackage revisions][] are made for metadata changes, such as relaxation of-constraints when new versions of dependencies are released. The `ttc.cabal`-metadata in the `main` branch may therefore not match that of Hackage. The-`ttc.cabal` metadata in the `develop` branch may match, *unless* work is being-done on a new release that contains other changes.--[Hackage revisions]: <https://github.com/haskell-infra/hackage-trustees/blob/master/revisions-information.md#hackage-metadata-revisions--what-they-are-how-they-work>--### Tags--All releases are tagged in the `main` branch. Release tags are signed using-the [`security@extrema.is` GPG key][].--[`security@extrema.is` GPG key]: <https://keyserver.ubuntu.com/pks/lookup?search=0x1D484E4B4705FADF&fingerprint=on&op=index>--### Contribution--Issues and feature requests are tracked on GitHub:-<https://github.com/ExtremaIS/ttc-haskell/issues>--Issues may also be submitted via email to <bugs@extrema.is>.--### License--This project is released under the [MIT License][] as specified in the-[`LICENSE`][] file.+See the [project README][] for general project information. -[MIT License]: <https://opensource.org/licenses/MIT>-[`LICENSE`]: <LICENSE>+[project README]: <https://github.com/ExtremaIS/ttc-haskell#ttc-textual-type-classes>
src/Data/TTC.hs view
@@ -1,1994 +1,2301 @@ ------------------------------------------------------------------------------ -- | -- Module : Data.TTC--- Description : textual type classes--- Copyright : Copyright (c) 2019-2023 Travis Cardwell--- License : MIT------ TTC, an initialism of /Textual Type Classes/, is a library that provides--- type classes for conversion between data types and textual data types--- (strings).------ This library is meant to be imported qualified, as follows:------ @--- import qualified Data.TTC as TTC--- @---------------------------------------------------------------------------------{-# LANGUAGE CPP #-}-{-# LANGUAGE DefaultSignatures #-}-{-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--#if __GLASGOW_HASKELL__ >= 900-{-# LANGUAGE ExplicitForAll #-}-#endif--module Data.TTC- ( -- * Textual- Textual- , convert- -- ** \"To\" Conversions- -- $TextualTo- , toS- , toT- , toTL- , toTLB- , toST- , toBS- , toBSL- , toBSB- , toSBS- -- ** \"From\" Conversions- -- $TextualFrom- , fromS- , fromT- , fromTL- , fromTLB- , fromST- , fromBS- , fromBSL- , fromBSB- , fromSBS- -- ** \"As\" Conversions- -- $TextualAs- , asS- , asT- , asTL- , asTLB- , asST- , asBS- , asBSL- , asBSB- , asSBS- -- * Render- , Render(..)- , RenderDefault(..)- -- ** Rendering Specific Types- -- $RenderSpecific- , renderS- , renderT- , renderTL- , renderTLB- , renderST- , renderBS- , renderBSL- , renderBSB- , renderSBS- -- ** Render Utilities- , renderWithShow- -- * Parse- , Parse(..)- , ParseDefault(..)- -- ** Parsing From Specific Types- -- $ParseSpecific- , parseS- , parseT- , parseTL- , parseTLB- , parseST- , parseBS- , parseBSL- , parseBSB- , parseSBS- -- ** 'Maybe' Parsing- -- $ParseMaybe- , parseMaybe- , parseMaybeS- , parseMaybeT- , parseMaybeTL- , parseMaybeTLB- , parseMaybeST- , parseMaybeBS- , parseMaybeBSL- , parseMaybeBSB- , parseMaybeSBS- -- ** 'MonadFail' Parsing- -- $ParseOrFail- , parseOrFail- , parseOrFailS- , parseOrFailT- , parseOrFailTL- , parseOrFailTLB- , parseOrFailST- , parseOrFailBS- , parseOrFailBSL- , parseOrFailBSB- , parseOrFailSBS- -- ** Unsafe Parsing- -- $ParseUnsafe- , parseUnsafe- , parseUnsafeS- , parseUnsafeT- , parseUnsafeTL- , parseUnsafeTLB- , parseUnsafeST- , parseUnsafeBS- , parseUnsafeBSL- , parseUnsafeBSB- , parseUnsafeSBS- -- ** Parse With A Single Error Message- -- $ParseWithASingleErrorMessage- , withError- , withErrorS- , withErrorT- , withErrorTL- , withErrorTLB- , withErrorST- , withErrorBS- , withErrorBSL- , withErrorBSB- , withErrorSBS- -- ** Parse With An Error Prefix- -- $ParseWithAnErrorPrefix- , prefixError- , prefixErrorS- , prefixErrorT- , prefixErrorTL- , prefixErrorTLB- , prefixErrorST- , prefixErrorBS- , prefixErrorBSL- , prefixErrorBSB- , prefixErrorSBS- -- ** Parse Enums- , parseEnum- , parseEnum'- -- ** Read Instances- , parseWithRead- , parseWithRead'- , maybeParseWithRead- , readsEnum- , readsWithParse- -- ** Constant Validation- -- $ParseValid- , valid- , validOf- , mkValid- , untypedValidOf- , mkUntypedValid- , mkUntypedValidQQ- ) where---- https://hackage.haskell.org/package/base-#if __GLASGOW_HASKELL__ <= 806-import Control.Monad.Fail (MonadFail)-#endif-import Data.Int (Int16, Int32, Int64, Int8)-import Data.Proxy (Proxy(Proxy), asProxyTypeOf)-import Data.String (IsString(fromString))-import Data.Word (Word16, Word32, Word64, Word8)-import GHC.Stack (HasCallStack)-import Text.Read (readMaybe)---- https://hackage.haskell.org/package/bytestring-import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as BSB-import qualified Data.ByteString.Lazy as BSL-import qualified Data.ByteString.Short as SBS---- https://hackage.haskell.org/package/template-haskell-import qualified Language.Haskell.TH as TH-import qualified Language.Haskell.TH.Quote as Q-import qualified Language.Haskell.TH.Syntax as THS---- https://hackage.haskell.org/package/text-import qualified Data.Text as T-import qualified Data.Text.Encoding as TE-import qualified Data.Text.Encoding.Error as TEE-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TLB-import qualified Data.Text.Lazy.Encoding as TLE---- https://hackage.haskell.org/package/text-short-import qualified Data.Text.Short as ST----------------------------------------------------------------------------------- $Textual---- | The 'Textual' type class is used to convert between the following textual--- data types:------ * 'String' (@S@)--- * Strict 'T.Text' (@T@)--- * Lazy 'TL.Text' (@TL@)--- * @Text@ 'TLB.Builder' (@TLB@)--- * 'ST.ShortText' (@ST@)--- * Strict 'BS.ByteString' (@BS@)--- * Lazy 'BSL.ByteString' (@BSL@)--- * @ByteString@ 'BSB.Builder' (@BSB@) (Note: @Data.Binary.Builder@--- re-exports this type, so TTC can be used with @binary@ as well.)--- * 'SBS.ShortByteString' (@SBS@)------ @ByteString@ values are assumed to be UTF-8 encoded text. Invalid bytes--- are replaced with the Unicode replacement character @U+FFFD@. In cases--- where different behavior is required, process @ByteString@ values /before/--- using this class.------ This type class has two key features:------ * Type conversion is /not/ done through a fixed type (such as 'String' or--- 'T.Text').--- * It has a single type variable, making it easy to write functions that--- accept arguments and/or return values that may be any of the supported--- textual data types.------ Note that support for additional data types cannot be implemented by--- writing instances. Adding support for additional data types would require--- changing the class definition itself.------ For more details, see the following article:--- <https://www.extrema.is/articles/ttc-textual-type-classes/textual-type-class>------ @since 0.1.0.0-class Textual t where- -- | Convert to a 'String'- --- -- @since 0.1.0.0- toS :: t -> String-- -- | Convert to strict 'T.Text'- --- -- @since 0.1.0.0- toT :: t -> T.Text-- -- | Convert to lazy 'TL.Text'- --- -- @since 0.1.0.0- toTL :: t -> TL.Text-- -- | Convert to a @Text@ 'TLB.Builder'- --- -- @since 1.1.0.0- toTLB :: t -> TLB.Builder-- -- | Convert to 'ST.ShortText'- --- -- @since 1.4.0.0- toST :: t -> ST.ShortText-- -- | Convert to a strict 'BS.ByteString'- --- -- @since 0.1.0.0- toBS :: t -> BS.ByteString-- -- | Convert to a lazy 'BS.ByteString'- --- -- @since 0.1.0.0- toBSL :: t -> BSL.ByteString-- -- | Convert to a @ByteString@ 'BSB.Builder'- --- -- @since 1.1.0.0- toBSB :: t -> BSB.Builder-- -- | Convert to a 'SBS.ShortByteString'- --- -- @since 1.1.0.0- toSBS :: t -> SBS.ShortByteString-- -- | Convert between any supported textual data types- --- -- @since 0.1.0.0- convert :: Textual t' => t' -> t--instance Textual String where- toS = id- toT = T.pack- toTL = TL.pack- toTLB = TLB.fromString- toST = ST.fromString- toBS = TE.encodeUtf8 . T.pack- toBSL = TLE.encodeUtf8 . TL.pack- toBSB = BSB.byteString . TE.encodeUtf8 . T.pack- toSBS = SBS.toShort . TE.encodeUtf8 . T.pack- convert = toS- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual T.Text where- toS = T.unpack- toT = id- toTL = TL.fromStrict- toTLB = TLB.fromText- toST = ST.fromText- toBS = TE.encodeUtf8- toBSL = TLE.encodeUtf8 . TL.fromStrict- toBSB = BSB.byteString . TE.encodeUtf8- toSBS = SBS.toShort . TE.encodeUtf8- convert = toT- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual TL.Text where- toS = TL.unpack- toT = TL.toStrict- toTL = id- toTLB = TLB.fromLazyText- toST = ST.fromText . TL.toStrict- toBS = BSL.toStrict . TLE.encodeUtf8- toBSL = TLE.encodeUtf8- toBSB = BSB.lazyByteString . TLE.encodeUtf8- toSBS = SBS.toShort . BSL.toStrict . TLE.encodeUtf8- convert = toTL- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual TLB.Builder where- toS = TL.unpack . TLB.toLazyText- toT = TL.toStrict . TLB.toLazyText- toTL = TLB.toLazyText- toTLB = id- toST = ST.fromText . TL.toStrict . TLB.toLazyText- toBS = BSL.toStrict . TLE.encodeUtf8 . TLB.toLazyText- toBSL = TLE.encodeUtf8 . TLB.toLazyText- toBSB = BSB.lazyByteString . TLE.encodeUtf8 . TLB.toLazyText- toSBS = SBS.toShort . BSL.toStrict . TLE.encodeUtf8 . TLB.toLazyText- convert = toTLB- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual ST.ShortText where- toS = ST.toString- toT = ST.toText- toTL = TL.fromStrict . ST.toText- toTLB = TLB.fromText . ST.toText- toST = id- toBS = ST.toByteString- toBSL = BSL.fromStrict . ST.toByteString- toBSB = BSB.byteString . ST.toByteString- toSBS = ST.toShortByteString- convert = toST- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual BS.ByteString where- toS = T.unpack . TE.decodeUtf8With TEE.lenientDecode- toT = TE.decodeUtf8With TEE.lenientDecode- toTL = TLE.decodeUtf8With TEE.lenientDecode . BSL.fromStrict- toTLB = TLB.fromText . TE.decodeUtf8With TEE.lenientDecode- toST = ST.fromText . TE.decodeUtf8With TEE.lenientDecode- toBS = id- toBSL = BSL.fromStrict- toBSB = BSB.byteString- toSBS = SBS.toShort- convert = toBS- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual BSL.ByteString where- toS = TL.unpack . TLE.decodeUtf8With TEE.lenientDecode- toT = TL.toStrict . TLE.decodeUtf8With TEE.lenientDecode- toTL = TLE.decodeUtf8With TEE.lenientDecode- toTLB = TLB.fromLazyText . TLE.decodeUtf8With TEE.lenientDecode- toST = ST.fromText . TL.toStrict . TLE.decodeUtf8With TEE.lenientDecode- toBS = BSL.toStrict- toBSL = id- toBSB = BSB.lazyByteString- toSBS = SBS.toShort . BSL.toStrict- convert = toBSL- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual BSB.Builder where- toS =- TL.unpack . TLE.decodeUtf8With TEE.lenientDecode . BSB.toLazyByteString- toT =- TL.toStrict . TLE.decodeUtf8With TEE.lenientDecode . BSB.toLazyByteString- toTL = TLE.decodeUtf8With TEE.lenientDecode . BSB.toLazyByteString- toTLB- = TLB.fromLazyText- . TLE.decodeUtf8With TEE.lenientDecode- . BSB.toLazyByteString- toST- = ST.fromText- . TL.toStrict- . TLE.decodeUtf8With TEE.lenientDecode- . BSB.toLazyByteString- toBS = BSL.toStrict . BSB.toLazyByteString- toBSL = BSB.toLazyByteString- toBSB = id- toSBS = SBS.toShort . BSL.toStrict . BSB.toLazyByteString- convert = toBSB- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}--instance Textual SBS.ShortByteString where- toS = T.unpack . TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort- toT = TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort- toTL = TLE.decodeUtf8With TEE.lenientDecode . BSL.fromStrict . SBS.fromShort- toTLB = TLB.fromText . TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort- toST = ST.fromText . TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort- toBS = SBS.fromShort- toBSL = BSL.fromStrict . SBS.fromShort- toBSB = BSB.byteString . SBS.fromShort- toSBS = id- convert = toSBS- {-# INLINE toS #-}- {-# INLINE toT #-}- {-# INLINE toTL #-}- {-# INLINE toTLB #-}- {-# INLINE toST #-}- {-# INLINE toBS #-}- {-# INLINE toBSL #-}- {-# INLINE toBSB #-}- {-# INLINE toSBS #-}- {-# INLINE convert #-}----------------------------------------------------------------------------------- $TextualTo------ These functions are equivalent to 'convert', but they specify the type--- being converted to. Use them to avoid having to write type annotations in--- cases where the type is ambiguous.---- $TextualFrom------ These functions are equivalent to 'convert', but they specify the type--- being converted from. Use them to avoid having to write type annotations--- in cases where the type is ambiguous.---- | Convert from a 'String'------ @since 0.1.0.0-fromS :: Textual t => String -> t-fromS = convert-{-# INLINE fromS #-}---- | Convert from strict 'T.Text'------ @since 0.1.0.0-fromT :: Textual t => T.Text -> t-fromT = convert-{-# INLINE fromT #-}---- | Convert from lazy 'TL.Text'------ @since 0.1.0.0-fromTL :: Textual t => TL.Text -> t-fromTL = convert-{-# INLINE fromTL #-}---- | Convert from a @Text@ 'TLB.Builder'------ @since 1.1.0.0-fromTLB :: Textual t => TLB.Builder -> t-fromTLB = convert-{-# INLINE fromTLB #-}---- | Convert from a 'ST.ShortText'------ @since 1.4.0.0-fromST :: Textual t => ST.ShortText -> t-fromST = convert-{-# INLINE fromST #-}---- | Convert from a strict 'BS.ByteString'------ @since 0.1.0.0-fromBS :: Textual t => BS.ByteString -> t-fromBS = convert-{-# INLINE fromBS #-}---- | Convert from a lazy 'BSL.ByteString'------ @since 0.1.0.0-fromBSL :: Textual t => BSL.ByteString -> t-fromBSL = convert-{-# INLINE fromBSL #-}---- | Convert from a @ByteString@ 'TLB.Builder'------ @since 1.1.0.0-fromBSB :: Textual t => BSB.Builder -> t-fromBSB = convert-{-# INLINE fromBSB #-}---- | Convert from a 'SBS.ShortByteString'------ @since 1.1.0.0-fromSBS :: Textual t => SBS.ShortByteString -> t-fromSBS = convert-{-# INLINE fromSBS #-}----------------------------------------------------------------------------------- $TextualAs------ These functions are used to convert a 'Textual' argument of a function to a--- specific type. Use them to reduce boilerplate in small function--- definitions.---- | Convert an argument to a 'String'------ @since 0.1.0.0-asS :: Textual t => (String -> a) -> t -> a-asS f = f . convert-{-# INLINE asS #-}---- | Convert an argument to strict 'T.Text'------ @since 0.1.0.0-asT :: Textual t => (T.Text -> a) -> t -> a-asT f = f . convert-{-# INLINE asT #-}---- | Convert an argument to lazy 'TL.Text'------ @since 0.1.0.0-asTL :: Textual t => (TL.Text -> a) -> t -> a-asTL f = f . convert-{-# INLINE asTL #-}---- | Convert an argument to a @Text@ 'TLB.Builder'------ @since 1.1.0.0-asTLB :: Textual t => (TLB.Builder -> a) -> t -> a-asTLB f = f . convert-{-# INLINE asTLB #-}---- | Convert an argument to a 'ST.ShortText'------ @since 1.4.0.0-asST :: Textual t => (ST.ShortText -> a) -> t -> a-asST f = f . convert-{-# INLINE asST #-}---- | Convert an argument to a strict 'BS.ByteString'------ @since 0.1.0.0-asBS :: Textual t => (BS.ByteString -> a) -> t -> a-asBS f = f . convert-{-# INLINE asBS #-}---- | Convert an argument to a lazy 'BSL.ByteString'------ @since 0.1.0.0-asBSL :: Textual t => (BSL.ByteString -> a) -> t -> a-asBSL f = f . convert-{-# INLINE asBSL #-}---- | Convert an argument to a @ByteString@ 'TLB.Builder'------ @since 1.1.0.0-asBSB :: Textual t => (BSB.Builder -> a ) -> t -> a-asBSB f = f . convert-{-# INLINE asBSB #-}---- | Convert an argument to a 'SBS.ShortByteString'------ @since 1.1.0.0-asSBS :: Textual t => (SBS.ShortByteString -> a) -> t -> a-asSBS f = f . convert-{-# INLINE asSBS #-}----------------------------------------------------------------------------------- $Render---- | The 'Render' type class renders a data type as a textual data type.------ There are no default instances for the 'Render' type class, so that all--- instances can be customized per project when desired. Instances for some--- basic data types are defined for the 'RenderDefault' type class, however,--- and you can load the 'Render' instance as follows:------ @--- instance TTC.Render Int--- @------ Since a type may have at most one instance of a given type class, special--- care must be taken when defining type class instances in a shared library.--- In particular, orphan instances should generally not be used in shared--- libraries since they prevent users of the libraries from writing their own--- instances.------ See the @uname@ and @prompt@ example programs in the @examples@ directory.------ For more details, see the following article:--- <https://www.extrema.is/articles/ttc-textual-type-classes/render-and-parse>------ @since 0.1.0.0-class Render a where- render :: Textual t => a -> t-- default render :: (RenderDefault a, Textual t) => a -> t- render = renderDefault------------------------------------------------------------------------------------ | The 'RenderDefault' type class provides some default 'Render' instances.------ * The 'Char' instance renders a single-character string.--- * Numeric type instances all render using the 'Show' instance.--- * Textual type instances all convert to the target 'Textual' data type.------ @since 1.1.0.0-class RenderDefault a where- renderDefault :: Textual t => a -> t--instance RenderDefault Char where- renderDefault c = fromS [c]--instance RenderDefault Double where- renderDefault = renderWithShow--instance RenderDefault Float where- renderDefault = renderWithShow--instance RenderDefault Int where- renderDefault = renderWithShow--instance RenderDefault Int8 where- renderDefault = renderWithShow--instance RenderDefault Int16 where- renderDefault = renderWithShow--instance RenderDefault Int32 where- renderDefault = renderWithShow--instance RenderDefault Int64 where- renderDefault = renderWithShow--instance RenderDefault Integer where- renderDefault = renderWithShow--instance RenderDefault Word where- renderDefault = renderWithShow--instance RenderDefault Word8 where- renderDefault = renderWithShow--instance RenderDefault Word16 where- renderDefault = renderWithShow--instance RenderDefault Word32 where- renderDefault = renderWithShow--instance RenderDefault Word64 where- renderDefault = renderWithShow--instance RenderDefault String where- renderDefault = fromS--instance RenderDefault BSL.ByteString where- renderDefault = fromBSL--instance RenderDefault BS.ByteString where- renderDefault = fromBS--instance RenderDefault TL.Text where- renderDefault = fromTL--instance RenderDefault T.Text where- renderDefault = fromT----------------------------------------------------------------------------------- $RenderSpecific------ These functions are equivalent to 'render', but they specify the type being--- rendered to. Use them to avoid having to write type annotations in cases--- where the type is ambiguous.---- | Render to a 'String'------ @since 0.1.0.0-renderS :: Render a => a -> String-renderS = render-{-# INLINE renderS #-}---- | Render to strict 'T.Text'------ @since 0.1.0.0-renderT :: Render a => a -> T.Text-renderT = render-{-# INLINE renderT #-}---- | Render to lazy 'TL.Text'------ @since 0.1.0.0-renderTL :: Render a => a -> TL.Text-renderTL = render-{-# INLINE renderTL #-}---- | Render to a @Text@ 'TLB.Builder'------ @since 0.4.0.0-renderTLB :: Render a => a -> TLB.Builder-renderTLB = render-{-# INLINE renderTLB #-}---- | Render to a 'ST.ShortText'------ @since 1.4.0.0-renderST :: Render a => a -> ST.ShortText-renderST = render-{-# INLINE renderST #-}---- | Render to a strict 'BS.ByteString'------ @since 0.1.0.0-renderBS :: Render a => a -> BS.ByteString-renderBS = render-{-# INLINE renderBS #-}---- | Render to a lazy 'BSL.ByteString'------ @since 0.1.0.0-renderBSL :: Render a => a -> BSL.ByteString-renderBSL = render-{-# INLINE renderBSL #-}---- | Render to a @ByteString@ 'BSB.Builder'------ @since 0.4.0.0-renderBSB :: Render a => a -> BSB.Builder-renderBSB = render-{-# INLINE renderBSB #-}---- | Render to a 'SBS.ShortByteString'------ @since 0.4.0.0-renderSBS :: Render a => a -> SBS.ShortByteString-renderSBS = render-{-# INLINE renderSBS #-}----------------------------------------------------------------------------------- $RenderUtils---- | Render a value to a textual data type using the 'Show' instance------ @since 0.1.0.0-renderWithShow :: (Show a, Textual t) => a -> t-renderWithShow = convert . show-{-# INLINE renderWithShow #-}----------------------------------------------------------------------------------- $Parse---- | The 'Parse' type class parses a data type from a textual data type.------ There are no default instances for the 'Parse' type class, so that all--- instances can be customized per project when desired. Instances for some--- basic data types are defined for the 'ParseDefault' type class, however,--- and you can load the 'Parse' instance as follows:------ @--- instance TTC.Parse Int--- @------ Since a type may have at most one instance of a given type class, special--- care must be taken when defining type class instances in a shared library.--- In particular, orphan instances should generally not be used in shared--- libraries since they prevent users of the libraries from writing their own--- instances.------ See the @uname@ and @prompt@ example programs in the @examples@ directory.------ For more details, see the following article:--- <https://www.extrema.is/articles/ttc-textual-type-classes/render-and-parse>------ @since 0.3.0.0-class Parse a where- parse :: (Textual t, Textual e) => t -> Either e a-- default parse :: (Textual t, Textual e, ParseDefault a) => t -> Either e a- parse = parseDefault---- This function is equivalent to 'parse' with the error type fixed to--- 'String', used internally when the error is ignored.------ @since 0.3.0.0-parse' :: (Parse a, Textual t) => t -> Either String a-parse' = parse-{-# INLINE parse' #-}------------------------------------------------------------------------------------ | The 'ParseDefault' type class provides some default 'Parse' instances.------ * The 'Char' instance parses single-character strings.--- * Numeric type instances all parse using the 'Read' instance.--- * Textual type instances all convert from the source 'Textual' data type.------ @since 1.1.0.0-class ParseDefault a where- parseDefault :: (Textual t, Textual e) => t -> Either e a--instance ParseDefault Char where- parseDefault = asS $ \case- [c] -> Right c- _cs -> Left $ fromS "invalid Char"--instance ParseDefault Double where- parseDefault = parseWithRead' "Double"--instance ParseDefault Float where- parseDefault = parseWithRead' "Float"--instance ParseDefault Int where- parseDefault = parseWithRead' "Int"--instance ParseDefault Int8 where- parseDefault = parseWithRead' "Int8"--instance ParseDefault Int16 where- parseDefault = parseWithRead' "Int16"--instance ParseDefault Int32 where- parseDefault = parseWithRead' "Int32"--instance ParseDefault Int64 where- parseDefault = parseWithRead' "Int64"--instance ParseDefault Integer where- parseDefault = parseWithRead' "Integer"--instance ParseDefault Word where- parseDefault = parseWithRead' "Word"--instance ParseDefault Word8 where- parseDefault = parseWithRead' "Word8"--instance ParseDefault Word16 where- parseDefault = parseWithRead' "Word16"--instance ParseDefault Word32 where- parseDefault = parseWithRead' "Word32"--instance ParseDefault Word64 where- parseDefault = parseWithRead' "Word64"--instance ParseDefault String where- parseDefault = Right . toS--instance ParseDefault BSL.ByteString where- parseDefault = Right . toBSL--instance ParseDefault BS.ByteString where- parseDefault = Right . toBS--instance ParseDefault TL.Text where- parseDefault = Right . toTL--instance ParseDefault T.Text where- parseDefault = Right . toT----------------------------------------------------------------------------------- $ParseSpecific------ These functions are equivalent to 'parse', but they specify the type being--- parsed from. Use them to avoid having to write type annotations in cases--- where the type is ambiguous.---- | Parse from a 'String'------ @since 0.3.0.0-parseS :: (Parse a, Textual e) => String -> Either e a-parseS = parse-{-# INLINE parseS #-}---- | Parse from strict 'T.Text'------ @since 0.3.0.0-parseT :: (Parse a, Textual e) => T.Text -> Either e a-parseT = parse-{-# INLINE parseT #-}---- | Parse from lazy 'TL.Text'------ @since 0.3.0.0-parseTL :: (Parse a, Textual e) => TL.Text -> Either e a-parseTL = parse-{-# INLINE parseTL #-}---- | Parse from a @Text@ 'TLB.Builder'------ @since 1.1.0.0-parseTLB :: (Parse a, Textual e) => TLB.Builder -> Either e a-parseTLB = parse-{-# INLINE parseTLB #-}---- | Parse from a 'ST.ShortText'------ @since 1.4.0.0-parseST :: (Parse a, Textual e) => ST.ShortText -> Either e a-parseST = parse-{-# INLINE parseST #-}---- | Parse from a strict 'BS.ByteString'------ @since 0.3.0.0-parseBS :: (Parse a, Textual e) => BS.ByteString -> Either e a-parseBS = parse-{-# INLINE parseBS #-}---- | Parse from a lazy 'BSL.ByteString'------ @since 0.3.0.0-parseBSL :: (Parse a, Textual e) => BSL.ByteString -> Either e a-parseBSL = parse-{-# INLINE parseBSL #-}---- | Parse from a @ByteString@ 'BSB.Builder'------ @since 1.1.0.0-parseBSB :: (Parse a, Textual e) => BSB.Builder -> Either e a-parseBSB = parse-{-# INLINE parseBSB #-}---- | Parse from a 'SBS.ShortByteString'------ @since 1.1.0.0-parseSBS :: (Parse a, Textual e) => SBS.ShortByteString -> Either e a-parseSBS = parse-{-# INLINE parseSBS #-}----------------------------------------------------------------------------------- $ParseMaybe------ The 'parseMaybe' function parses to a 'Maybe' type instead of an 'Either'--- type. The rest of the functions are equivalent to 'parseMaybe', but they--- specify the type being parsed from. Use them to avoid having to write type--- annotations in cases where the type is ambiguous.---- | Parse to a 'Maybe' type------ @since 0.3.0.0-parseMaybe :: (Parse a, Textual t) => t -> Maybe a-parseMaybe = either (const Nothing) Just . parse'-{-# INLINE parseMaybe #-}---- | Parse from a 'String' to a 'Maybe' type------ @since 0.3.0.0-parseMaybeS :: Parse a => String -> Maybe a-parseMaybeS = parseMaybe-{-# INLINE parseMaybeS #-}---- | Parse from strict 'T.Text' to a 'Maybe' type------ @since 0.3.0.0-parseMaybeT :: Parse a => T.Text -> Maybe a-parseMaybeT = parseMaybe-{-# INLINE parseMaybeT #-}---- | Parse from lazy 'TL.Text' to a 'Maybe' type------ @since 0.3.0.0-parseMaybeTL :: Parse a => TL.Text -> Maybe a-parseMaybeTL = parseMaybe-{-# INLINE parseMaybeTL #-}---- | Parse from a @Text@ 'TLB.Builder' to a 'Maybe' type------ @since 1.1.0.0-parseMaybeTLB :: Parse a => TLB.Builder -> Maybe a-parseMaybeTLB = parseMaybe-{-# INLINE parseMaybeTLB #-}---- | Parse from a 'ST.ShortText' to a 'Maybe' type------ @since 1.4.0.0-parseMaybeST :: Parse a => ST.ShortText -> Maybe a-parseMaybeST = parseMaybe-{-# INLINE parseMaybeST #-}---- | Parse from a strict 'BS.ByteString' to a 'Maybe' type------ @since 0.3.0.0-parseMaybeBS :: Parse a => BS.ByteString -> Maybe a-parseMaybeBS = parseMaybe-{-# INLINE parseMaybeBS #-}---- | Parse from a lazy 'BSL.ByteString' to a 'Maybe' type------ @since 0.3.0.0-parseMaybeBSL :: Parse a => BSL.ByteString -> Maybe a-parseMaybeBSL = parseMaybe-{-# INLINE parseMaybeBSL #-}---- | Parse from a @ByteString@ 'BSB.Builder' to a 'Maybe' type------ @since 1.1.0.0-parseMaybeBSB :: Parse a => BSB.Builder -> Maybe a-parseMaybeBSB = parseMaybe-{-# INLINE parseMaybeBSB #-}---- | Parse from a 'SBS.ShortByteString' to a 'Maybe' type------ @since 1.1.0.0-parseMaybeSBS :: Parse a => SBS.ShortByteString -> Maybe a-parseMaybeSBS = parseMaybe-{-# INLINE parseMaybeSBS #-}----------------------------------------------------------------------------------- $ParseOrFail------ The 'parseOrFail' function fails using 'MonadFail' on error instead of--- using an 'Either' type. The rest of the functions are equivalent to--- 'parseOrFail', but they specify the type being parsed from. Use them to--- avoid having to write type annotations in cases where the type is--- ambiguous.---- | Parse or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFail :: (MonadFail m, Parse a, Textual t) => t -> m a-parseOrFail = either fail pure . parse-{-# INLINE parseOrFail #-}---- | Parse from a 'String' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailS :: (MonadFail m, Parse a) => String -> m a-parseOrFailS = parseOrFail-{-# INLINE parseOrFailS #-}---- | Parse from strict 'T.Text' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailT :: (MonadFail m, Parse a) => T.Text -> m a-parseOrFailT = parseOrFail-{-# INLINE parseOrFailT #-}---- | Parse from lazy 'TL.Text' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailTL :: (MonadFail m, Parse a) => TL.Text -> m a-parseOrFailTL = parseOrFail-{-# INLINE parseOrFailTL #-}---- | Parse from a @Text@ 'TLB.Builder' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailTLB :: (MonadFail m, Parse a) => TLB.Builder -> m a-parseOrFailTLB = parseOrFail-{-# INLINE parseOrFailTLB #-}---- | Parse from a 'ST.ShortText' or fail using 'MonadFail'------ @since 1.4.0.0-parseOrFailST :: (MonadFail m, Parse a) => ST.ShortText -> m a-parseOrFailST = parseOrFail-{-# INLINE parseOrFailST #-}---- | Parse from a strict 'BS.ByteString' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailBS :: (MonadFail m, Parse a) => BS.ByteString -> m a-parseOrFailBS = parseOrFail-{-# INLINE parseOrFailBS #-}---- | Parse from a lazy 'BSL.ByteString' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailBSL :: (MonadFail m, Parse a) => BSL.ByteString -> m a-parseOrFailBSL = parseOrFail-{-# INLINE parseOrFailBSL #-}---- | Parse from a @ByteString@ 'BSB.Builder' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailBSB :: (MonadFail m, Parse a) => BSB.Builder -> m a-parseOrFailBSB = parseOrFail-{-# INLINE parseOrFailBSB #-}---- | Parse from a 'SBS.ShortByteString' or fail using 'MonadFail'------ @since 1.3.0.0-parseOrFailSBS :: (MonadFail m, Parse a) => SBS.ShortByteString -> m a-parseOrFailSBS = parseOrFail-{-# INLINE parseOrFailSBS #-}----------------------------------------------------------------------------------- $ParseUnsafe------ The 'parseUnsafe' function raises an exception on error instead of using an--- 'Either' type. It should only be used when an error is not possible. The--- rest of the functions are equivalent to 'parseUnsafe', but they specify the--- type being parsed from. Use them to avoid having to write type annotations--- in cases where the type is ambiguous.---- | Unsafely parse------ @since 0.1.0.0-parseUnsafe :: (HasCallStack, Parse a, Textual t) => t -> a-parseUnsafe = either (error . ("parseUnsafe: " ++)) id . parse-{-# INLINE parseUnsafe #-}---- | Unsafely parse from a 'String'------ @since 0.1.0.0-parseUnsafeS :: (HasCallStack, Parse a) => String -> a-parseUnsafeS = parseUnsafe-{-# INLINE parseUnsafeS #-}---- | Unsafely parse from strict 'T.Text'------ @since 0.1.0.0-parseUnsafeT :: (HasCallStack, Parse a) => T.Text -> a-parseUnsafeT = parseUnsafe-{-# INLINE parseUnsafeT #-}---- | Unsafely parse from lazy 'TL.Text'------ @since 0.1.0.0-parseUnsafeTL :: (HasCallStack, Parse a) => TL.Text -> a-parseUnsafeTL = parseUnsafe-{-# INLINE parseUnsafeTL #-}---- | Unsafely parse from a @Text@ 'TLB.Builder'------ @since 1.1.0.0-parseUnsafeTLB :: (HasCallStack, Parse a) => TLB.Builder -> a-parseUnsafeTLB = parseUnsafe-{-# INLINE parseUnsafeTLB #-}---- | Unsafely parse from a 'ST.ShortText'------ @since 1.4.0.0-parseUnsafeST :: (HasCallStack, Parse a) => ST.ShortText -> a-parseUnsafeST = parseUnsafe-{-# INLINE parseUnsafeST #-}---- | Unsafely parse from a strict 'BS.ByteString'------ @since 0.1.0.0-parseUnsafeBS :: (HasCallStack, Parse a) => BS.ByteString -> a-parseUnsafeBS = parseUnsafe-{-# INLINE parseUnsafeBS #-}---- | Unsafely parse from a lazy 'BSL.ByteString'------ @since 0.1.0.0-parseUnsafeBSL :: (HasCallStack, Parse a) => BSL.ByteString -> a-parseUnsafeBSL = parseUnsafe-{-# INLINE parseUnsafeBSL #-}---- | Unsafely parse from a @ByteString@ 'BSB.Builder'------ @since 1.1.0.0-parseUnsafeBSB :: (HasCallStack, Parse a) => BSB.Builder -> a-parseUnsafeBSB = parseUnsafe-{-# INLINE parseUnsafeBSB #-}---- | Unsafely parse from a 'SBS.ShortByteString'------ @since 1.1.0.0-parseUnsafeSBS :: (HasCallStack, Parse a) => SBS.ShortByteString -> a-parseUnsafeSBS = parseUnsafe-{-# INLINE parseUnsafeSBS #-}----------------------------------------------------------------------------------- $ParseWithASingleErrorMessage------ The 'withError' function takes an error message and a 'Maybe' value. It--- returns a 'Parse' result: the error when the 'Maybe' value is 'Nothing', or--- the value inside the 'Just'. This provides a convenient way to return the--- same error message for any parse error. The rest of the functions are--- equivalent to 'withError', but they specify the type of the error message.--- Use them to avoid having to write type annotations in cases where the type--- is ambiguous.---- | Create a 'Parse' result from a 'Textual' error message and a 'Maybe'--- value------ @since 1.2.0.0-withError- :: (Textual e', Textual e)- => e'- -> Maybe a- -> Either e a-withError err = maybe (Left $ convert err) Right-{-# INLINE withError #-}---- | Create a 'Parse' result from a 'String' error message and a 'Maybe' value------ @since 1.2.0.0-withErrorS- :: Textual e- => String- -> Maybe a- -> Either e a-withErrorS = withError-{-# INLINE withErrorS #-}---- | Create a 'Parse' result from a 'T.Text' error message and a 'Maybe' value------ @since 1.2.0.0-withErrorT- :: Textual e- => T.Text- -> Maybe a- -> Either e a-withErrorT = withError-{-# INLINE withErrorT #-}---- | Create a 'Parse' result from a 'TL.Text' error message and a 'Maybe'--- value------ @since 1.2.0.0-withErrorTL- :: Textual e- => TL.Text- -> Maybe a- -> Either e a-withErrorTL = withError-{-# INLINE withErrorTL #-}---- | Create a 'Parse' result from a 'TLB.Builder' error message and a 'Maybe'--- value------ @since 1.2.0.0-withErrorTLB- :: Textual e- => TLB.Builder- -> Maybe a- -> Either e a-withErrorTLB = withError-{-# INLINE withErrorTLB #-}---- | Create a 'Parse' result from a 'ST.ShortText' error message and a 'Maybe'--- value------ @since 1.4.0.0-withErrorST- :: Textual e- => ST.ShortText- -> Maybe a- -> Either e a-withErrorST = withError-{-# INLINE withErrorST #-}---- | Create a 'Parse' result from a 'BS.ByteString' error message and a--- 'Maybe' value------ @since 1.2.0.0-withErrorBS- :: Textual e- => BS.ByteString- -> Maybe a- -> Either e a-withErrorBS = withError-{-# INLINE withErrorBS #-}---- | Create a 'Parse' result from a 'BSL.ByteString' error message and a--- 'Maybe' value------ @since 1.2.0.0-withErrorBSL- :: Textual e- => BSL.ByteString- -> Maybe a- -> Either e a-withErrorBSL = withError-{-# INLINE withErrorBSL #-}---- | Create a 'Parse' result from a 'BSB.Builder' error message and a--- 'Maybe' value------ @since 1.2.0.0-withErrorBSB- :: Textual e- => BSB.Builder- -> Maybe a- -> Either e a-withErrorBSB = withError-{-# INLINE withErrorBSB #-}---- | Create a 'Parse' result from a 'SBS.ShortByteString' error message and a--- 'Maybe' value------ @since 1.2.0.0-withErrorSBS- :: Textual e- => SBS.ShortByteString- -> Maybe a- -> Either e a-withErrorSBS = withError-{-# INLINE withErrorSBS #-}----------------------------------------------------------------------------------- $ParseWithAnErrorPrefix------ The 'prefixError' function adds a common prefix to error messages of a--- 'Parse' result. The rest of the functions are equivalent to 'prefixError',--- but they specify the type of the error message. Use them to avoid having--- to write type annotations in cases where the type is ambiguous.---- | Add a prefix to 'Textual' error messages of a 'Parse' result------ @since 1.2.0.0-prefixError- :: (Monoid e', Textual e', Textual e)- => e'- -> Either e' a- -> Either e a-prefixError prefix = either (Left . convert . mappend prefix) Right-{-# INLINE prefixError #-}---- | Add a prefix to 'String' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorS- :: Textual e- => String- -> Either String a- -> Either e a-prefixErrorS = prefixError-{-# INLINE prefixErrorS #-}---- | Add a prefix to 'T.Text' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorT- :: Textual e- => T.Text- -> Either T.Text a- -> Either e a-prefixErrorT = prefixError-{-# INLINE prefixErrorT #-}---- | Add a prefix to 'TL.Text' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorTL- :: Textual e- => TL.Text- -> Either TL.Text a- -> Either e a-prefixErrorTL = prefixError-{-# INLINE prefixErrorTL #-}---- | Add a prefix to 'TLB.Builder' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorTLB- :: Textual e- => TLB.Builder- -> Either TLB.Builder a- -> Either e a-prefixErrorTLB = prefixError-{-# INLINE prefixErrorTLB #-}---- | Add a prefix to 'ST.ShortText' error messages of a 'Parse' result------ @since 1.4.0.0-prefixErrorST- :: Textual e- => ST.ShortText- -> Either ST.ShortText a- -> Either e a-prefixErrorST = prefixError-{-# INLINE prefixErrorST #-}---- | Add a prefix to 'BS.ByteString' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorBS- :: Textual e- => BS.ByteString- -> Either BS.ByteString a- -> Either e a-prefixErrorBS = prefixError-{-# INLINE prefixErrorBS #-}---- | Add a prefix to 'BSL.ByteString' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorBSL- :: Textual e- => BSL.ByteString- -> Either BSL.ByteString a- -> Either e a-prefixErrorBSL = prefixError-{-# INLINE prefixErrorBSL #-}---- | Add a prefix to 'BSB.Builder' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorBSB- :: Textual e- => BSB.Builder- -> Either BSB.Builder a- -> Either e a-prefixErrorBSB = prefixError-{-# INLINE prefixErrorBSB #-}---- | Add a prefix to 'SBS.ShortByteString' error messages of a 'Parse' result------ @since 1.2.0.0-prefixErrorSBS- :: Textual e- => SBS.ShortByteString- -> Either SBS.ShortByteString a- -> Either e a-prefixErrorSBS = prefixError-{-# INLINE prefixErrorSBS #-}----------------------------------------------------------------------------------- $ParseEnums---- | Parse a value in an enumeration------ This function is intended to be used with types that have few choices, as--- the implementation uses a linear algorithm.------ See the @enum@ example program in the @examples@ directory.------ @since 0.1.0.0-parseEnum- :: (Bounded a, Enum a, Render a, Textual t)- => Bool -- ^ case-insensitive when 'True'- -> Bool -- ^ accept unique prefixes when 'True'- -> e -- ^ invalid input error- -> e -- ^ ambiguous input error- -> t -- ^ textual input to parse- -> Either e a -- ^ error or parsed value-parseEnum allowCI allowPrefix invalidError ambiguousError t =- let t' = norm $ toT t- in case [v | v <- [minBound ..], t' `match` norm (render v)] of- [v] -> Right v- [] -> Left invalidError- _vs -> Left ambiguousError- where- norm :: T.Text -> T.Text- norm = if allowCI then T.toLower else id-- match :: T.Text -> T.Text -> Bool- match = if allowPrefix then T.isPrefixOf else (==)---- | Parse a value in an enumeration, with 'Textual' error messages------ The following English error messages are returned:------ * \"invalid {name}\" when there are no matches--- * \"ambiguous {name}\" when there is more than one match------ @since 0.4.0.0-parseEnum'- :: (Bounded a, Enum a, Render a, Textual t, Textual e)- => String -- ^ name to include in error messages- -> Bool -- ^ case-insensitive when 'True'- -> Bool -- ^ accept unique prefixes when 'True'- -> t -- ^ textual input to parse- -> Either e a -- ^ error or parsed value-parseEnum' name allowCI allowPrefix =- parseEnum- allowCI allowPrefix- (fromS $ "invalid " ++ name)- (fromS $ "ambiguous " ++ name)-{-# INLINEABLE parseEnum' #-}----------------------------------------------------------------------------------- $ReadInstanced---- | Parse a value using the 'Read' instance------ @since 0.1.0.0-parseWithRead- :: (Read a, Textual t)- => e -- ^ invalid input error- -> t -- ^ textual input to parse- -> Either e a -- ^ error or parsed value-parseWithRead invalidError = maybe (Left invalidError) Right . readMaybe . toS-{-# INLINEABLE parseWithRead #-}---- | Parse a value using the 'Read' instance, with 'Textual' error messages------ The following English error message is returned:------ * \"invalid {name}\" when the parse fails------ @since 0.3.0.0-parseWithRead'- :: (Read a, Textual t, Textual e)- => String -- ^ name to include in error messages- -> t -- ^ textual input to parse- -> Either e a -- ^ error or parsed value-parseWithRead' name = parseWithRead (fromS $ "invalid " ++ name)-{-# INLINEABLE parseWithRead' #-}---- | Parse a value to a 'Maybe' type using the 'Read' instance------ @since 0.3.0.0-maybeParseWithRead- :: (Read a, Textual t)- => t -- ^ textual input to parse- -> Maybe a -- ^ parsed value or 'Nothing' if invalid-maybeParseWithRead = readMaybe . toS---- | Implement 'ReadS' using 'parseEnum'------ This implementation expects all of the input to be consumed.------ @since 0.1.0.0-readsEnum- :: (Bounded a, Enum a, Render a)- => Bool -- ^ case-insensitive when 'True'- -> Bool -- ^ accept unique prefixes when 'True'- -> ReadS a-readsEnum allowCI allowPrefix s =- case parseEnum allowCI allowPrefix () () s of- Right v -> [(v, "")]- Left{} -> []-{-# INLINEABLE readsEnum #-}---- | Implement 'ReadS' using a 'Parse' instance------ This implementation expects all of the input to be consumed.------ @since 0.3.0.0-readsWithParse- :: Parse a- => ReadS a-readsWithParse s = case parseMaybe s of- Just v -> [(v, "")]- Nothing -> []-{-# INLINEABLE readsWithParse #-}----------------------------------------------------------------------------------- $ParseValid------ The follow functions provide a number of ways to use a 'Parse' instance to--- validate constants at compile-time.------ If you can use Template Haskell typed expressions in your project, use--- 'valid', 'mkValid', or 'validOf'. Use 'valid' to define constants for--- types that have a 'THS.Lift' instance. For types that do not have a--- 'THS.Lift' instance, use 'mkValid' to define a validation function for that--- type using a 'Proxy', or use 'validOf' to pass the 'Proxy' when defining--- constants.------ Typed expressions were not supported in @haskell-src-exts <1.22.0@, which--- causes problems with old versions of @hlint@. If the issue affects you,--- you may use 'mkUntypedValid', 'mkUntypedValidQQ', or 'untypedValidOf'--- instead of the above functions. Use 'mkUntypedValid' to define a--- validation function for a type using a 'Proxy', or use 'untypedValidOf' to--- pass the 'Proxy' when defining constants. Alternatively, use--- 'mkUntypedValidQQ' to define a validation quasi-quoter.------ For more details, see the following article:--- <https://www.extrema.is/articles/ttc-textual-type-classes/validated-constants>---- | Validate a constant at compile-time using a 'Parse' instance------ This function parses the 'String' at compile-time and fails compilation on--- error. When valid, the result is compiled in, so the result type must have--- a 'THS.Lift' instance. When this is inconvenient, use one of the--- alternative functions in this library.------ This function uses a Template Haskell typed expression. Typed expressions--- were not supported in @haskell-src-exts <1.22.0@, which causes problems--- with old versions of @hlint@. If the issue affects you, use--- @hlint -i "Parse error"@ to ignore parse errors or use one of the--- alternative functions in this library.------ Note that the typed Template Haskell API changed in GHC 9. The type--- displayed in this documentation is determined by the version of GHC used to--- build the documentation.------ The type of this function in GHC 9 or later is as follows:------ @--- valid--- :: (MonadFail m, THS.Quote m, Parse a, THS.Lift a)--- => String--- -> THS.Code m a--- @------ The type of this function in previous versions of GHC is as follows:------ @--- valid--- :: (Parse a, THS.Lift a)--- => String--- -> TH.Q (TH.TExp a)--- @------ This function is used the same way in all GHC versions. See the @valid@,--- @invalid@, and @lift@ example programs in the @examples@ directory. The--- following is example usage from the @valid@ example:------ @--- sample :: Username--- sample = $$(TTC.valid "tcard")--- @------ @since 0.1.0.0-#if __GLASGOW_HASKELL__ >= 900-valid- :: (MonadFail m, THS.Quote m, Parse a, THS.Lift a)- => String- -> THS.Code m a-valid s = case parse s of- Right x -> [|| x ||]- Left err -> THS.Code . fail $ "Invalid constant: " ++ err-#else-valid- :: (Parse a, THS.Lift a)- => String- -> TH.Q (TH.TExp a)-valid s = case parse s of- Right x -> [|| x ||]- Left err -> fail $ "Invalid constant: " ++ err-#endif---- | This instance enables use of 'valid' without having to type @valid@. The--- [OverloadedStrings](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_strings.html)--- extension must be enabled in the module where this functionality is used.--- Note that this reduces the number of characters in the code, but it can--- also make the code more difficult to understand by somebody who is not--- already familiar with it. Typing @valid@ gives people a way to investigate--- and understand what is going on.------ Note that the typed Template Haskell API changed in GHC 9. The type--- displayed in this documentation is determined by the version of GHC used to--- build the documentation.------ The type of this instance in GHC 9 or later is as follows:------ @--- (MonadFail m, THS.Quote m, Parse a, THS.Lift a) => IsString (THS.Code m a)--- @------ The type of this instance in previous versions of GHC is as follows:------ @--- (Parse a, THS.Lift a) => IsString (TH.Q (TH.TExp a))--- @------ This functionality can be used as follows in all supported versions of GHC.--- The following is example usage from the @valid@ example:------ @--- sample2 :: Username--- sample2 = $$("alice")--- @------ The parenthesis are not required from GHC 9. The following is example--- usage from the @valid@ example:------ @--- sample2 :: Username--- sample2 = $$"alice"--- @------ @since 1.3.0.0-#if __GLASGOW_HASKELL__ >= 900-instance (MonadFail m, THS.Quote m, Parse a, THS.Lift a)- => IsString (THS.Code m a) where- fromString = valid-#else-instance (Parse a, THS.Lift a) => IsString (TH.Q (TH.TExp a)) where- fromString = valid-#endif---- | Validate a constant at compile-time using a 'Parse' instance------ This function requires a 'Proxy' of the result type. Use 'mkValid' to--- avoid having to pass a 'Proxy' during constant definition.------ This function parses the 'String' at compile-time and fails compilation on--- error. When valid, the 'String' is compiled in, to be parsed again at--- run-time. Since the result is not compiled in, no 'THS.Lift' instance is--- required.------ This function uses a Template Haskell typed expression. Typed expressions--- were not supported in @haskell-src-exts <1.22.0@, which causes problems--- with old versions of @hlint@. If the issue affects you, use--- @hlint -i "Parse error"@ to ignore parse errors or use 'untypedValidOf'--- instead.------ Note that the typed Template Haskell API changed in GHC 9. The type--- displayed in this documentation is determined by the version of GHC used to--- build the documentation.------ The type of this function in GHC 9 or later is as follows:------ @--- validOf--- :: (MonadFail m, THS.Quote m, Parse a)--- => Proxy a--- -> String--- -> THS.Code m a--- @------ The type of this function in previous versions of GHC is as follows:------ @--- validOf--- :: Parse a--- => Proxy a--- -> String--- -> TH.Q (TH.TExp a)--- @------ This function is used the same way in all GHC versions. See the @validof@--- example program in the @examples@ directory. The following is example--- usage from the @validof@ example:------ @--- sample :: Username--- sample = $$(TTC.validOf (Proxy :: Proxy Username) "tcard")--- @------ @since 0.1.0.0-#if __GLASGOW_HASKELL__ >= 900-validOf- :: (MonadFail m, THS.Quote m, Parse a)- => Proxy a- -> String- -> THS.Code m a-validOf proxy s = case (`asProxyTypeOf` proxy) <$> parse s of- Right{} -> [|| parseUnsafeS s ||]- Left err -> THS.Code . fail $ "Invalid constant: " ++ err-#else-validOf- :: Parse a- => Proxy a- -> String- -> TH.Q (TH.TExp a)-validOf proxy s = case (`asProxyTypeOf` proxy) <$> parse s of- Right{} -> [|| parseUnsafeS s ||]- Left err -> fail $ "Invalid constant: " ++ err-#endif---- | Make a @valid@ function using 'validOf' for the given type------ Create a @valid@ function for a type in order to avoid having to write a--- 'Proxy' when defining constants.------ This function uses a Template Haskell typed expression. Typed expressions--- were not supported in @haskell-src-exts <1.22.0@, which causes problems--- with old versions of @hlint@. If the issue affects you, use--- @hlint -i "Parse error"@ to ignore parse errors or use 'mkUntypedValid'--- instead.------ Note that the typed Template Haskell API changed in GHC 9. The type--- displayed in this documentation is determined by the version of GHC used to--- build the documentation.------ The type of the created @valid@ function in GHC 9 or later is as follows:------ @--- \$funName--- :: forall m. (MonadFail m, THS.Quote m)--- => String--- -> THS.Code m $resultType--- @------ The type of the created @valid@ function in previous versions of GHC is as--- follows:------ @--- \$funName--- :: String--- -> TH.Q (TH.TExp $resultType)--- @------ This function is used the same way in all GHC versions. See the @mkvalid@--- example program in the @examples@ directory. The following is example--- usage from the @mkvalid@ example:------ @--- \$(TTC.mkValid "valid" ''Username)--- @------ The created @valid@ function can then be used as follows:------ @--- sample :: Username--- sample = $$(Username.valid "tcard")--- @------ @since 0.1.0.0-mkValid- :: String- -> TH.Name- -> TH.DecsQ-mkValid funName typeName = do- let funName' = TH.mkName funName- resultType = pure $ TH.ConT typeName-#if __GLASGOW_HASKELL__ >= 900- funType <-- [t|- forall m . (MonadFail m, THS.Quote m) =>- String -> THS.Code m $resultType- |]-#else- funType <- [t| String -> TH.Q (TH.TExp $resultType) |]-#endif- body <- [| validOf (Proxy :: Proxy $resultType) |]- return- [ TH.SigD funName' funType- , TH.FunD funName' [TH.Clause [] (TH.NormalB body) []]- ]---- | Validate a constant at compile-time using a 'Parse' instance------ This function requires a 'Proxy' of the result type. Use 'mkUntypedValid'--- to avoid having to pass a 'Proxy' during constant definition.------ This function parses the 'String' at compile-time and fails compilation on--- error. When valid, the 'String' is compiled in, to be parsed again at--- run-time. Since the result is not compiled in, no 'THS.Lift' instance is--- required.------ See the @uvalidof@ example program in the @examples@ directory. The--- following is example usage from the @uvalidof@ example:------ @--- sample :: Username--- sample = $(TTC.untypedValidOf (Proxy :: Proxy Username) "tcard")--- @------ @since 0.2.0.0-untypedValidOf- :: Parse a- => Proxy a- -> String- -> TH.ExpQ-untypedValidOf proxy s = case (`asProxyTypeOf` proxy) <$> parse s of- Right{} -> [| parseUnsafeS s |]- Left err -> fail $ "Invalid constant: " ++ err---- | Make a @valid@ function using 'untypedValidOf' for the given type------ Create a @valid@ function for a type in order to avoid having to write a--- 'Proxy' when defining constants.------ See the @mkuvalid@ example program in the @examples@ directory. The--- following is example usage from the @mkuvalid@ example:------ @--- \$(TTC.mkUntypedValid "valid" ''Username)--- @------ The created @valid@ function can then be used as follows:------ @--- sample :: Username--- sample = $(Username.valid "tcard")--- @------ @since 0.2.0.0-mkUntypedValid- :: String- -> TH.Name- -> TH.DecsQ-mkUntypedValid funName typeName = do- let funName' = TH.mkName funName- resultType = pure $ TH.ConT typeName- funType <- [t| String -> TH.ExpQ |]- body <- [| untypedValidOf (Proxy :: Proxy $resultType) |]- return- [ TH.SigD funName' funType- , TH.FunD funName' [TH.Clause [] (TH.NormalB body) []]- ]---- | Make a @valid@ quasi-quoter using 'untypedValidOf' for the given type------ See the @uvalidqq@ example program in the @examples@ directory. The--- following is example usage from the @uvalidqq@ example:------ @--- \$(TTC.mkUntypedValidQQ "valid" ''Username)--- @------ The created @valid@ function can then be used as follows:------ @--- sample :: Username--- sample = [Username.valid|tcard|]--- @------ @since 0.2.0.0-mkUntypedValidQQ- :: String- -> TH.Name- -> TH.DecsQ-mkUntypedValidQQ funName typeName = do- let funName' = TH.mkName funName- resultType = pure $ TH.ConT typeName- expE <- [| untypedValidOf (Proxy :: Proxy $resultType) |]- expP <- [| error "pattern not supported" |]- expT <- [| error "type not supported" |]- expD <- [| error "declaration not supported" |]- let body = TH.NormalB $ TH.RecConE 'Q.QuasiQuoter- [ ('Q.quoteExp, expE)- , ('Q.quotePat, expP)- , ('Q.quoteType, expT)- , ('Q.quoteDec, expD)- ]- return- [ TH.SigD funName' $ TH.ConT ''Q.QuasiQuoter- , TH.FunD funName' [TH.Clause [] body []]- ]+-- Description : Textual Type Classes+-- Copyright : Copyright (c) 2019-2025 Travis Cardwell+-- License : MIT+--+-- TTC, an initialism of /Textual Type Classes/, is a library that provides+-- type classes for conversion between data types and textual data types+-- (strings).+--+-- The 'Render' type class renders a data type as a textual data type, similar+-- to 'Show'. Use 'Render' in your business logic, and only use 'Show' for+-- debugging, as use of 'Show' instances in business logic is a common source+-- of bugs.+--+-- The 'Parse' type class parses a data type from a textual data type, similar+-- to 'Read'. Unlike 'Read', 'Parse' allows you to specify meaningful error+-- messages.+--+-- 'Render' and 'Parse' work with multiple textual data types. They are not+-- limited to 'String' (like 'Show' and 'Read'), and implementations can use+-- the textual data type that is most appropriate for each data type.+--+-- Conversion between textual data types is managed by the 'Textual' type+-- class. This library provides instances to support the following textual+-- data types:+--+-- * 'String' (@S@)+-- * Strict 'T.Text' (@T@)+-- * Lazy 'TL.Text' (@TL@)+-- * @Text@ 'TLB.Builder' (@TLB@)+-- * 'ST.ShortText' (@ST@)+-- * Strict 'BS.ByteString' (@BS@)+-- * Lazy 'BSL.ByteString' (@BSL@)+-- * @ByteString@ 'BSB.Builder' (@BSB@)+-- * 'SBS.ShortByteString' (@SBS@)+--+-- This library is meant to be imported qualified, as follows:+--+-- @+-- import qualified Data.TTC as TTC+-- @+--+-- Note that this library has a similar API to the+-- [ETTC](https://github.com/ExtremaIS/ttc-haskell/tree/main/ettc) library,+-- which uses a @Utf8Convertible@ type class instead of 'Textual'. The TTC+-- API types are simpler, but it is not possible to add support for additional+-- textual data types without changing the library itself. The ETTC API types+-- are more complex, leading to longer compilation times, but one can add+-- support for additional textual data types by defining new @Utf8Convertible@+-- instances. Both libraries are maintained, allowing you to use the one that+-- best matches the needs of your project.+------------------------------------------------------------------------------++{-# LANGUAGE CPP #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE ExplicitForAll #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.TTC+ ( -- * Textual+ Textual+ , convert+ -- ** \"To\" Conversions+ -- $TextualTo+ , toS+ , toT+ , toTL+ , toTLB+ , toST+ , toBS+ , toBSL+ , toBSB+ , toSBS+ -- ** \"From\" Conversions+ -- $TextualFrom+ , fromS+ , fromT+ , fromTL+ , fromTLB+ , fromST+ , fromBS+ , fromBSL+ , fromBSB+ , fromSBS+ -- ** \"As\" Conversions+ -- $TextualAs+ , asS+ , asT+ , asTL+ , asTLB+ , asST+ , asBS+ , asBSL+ , asBSB+ , asSBS+ -- * Render+ , Render(..)+ , RenderDefault(..)+ -- ** Render Utility Functions+ -- $RenderUtilityFunctions+ , renderWithShow+ -- ** Rendering Specific Types+ -- $RenderSpecific+ , renderS+ , renderT+ , renderTL+ , renderTLB+ , renderST+ , renderBS+ , renderBSL+ , renderBSB+ , renderSBS+ -- * Parse+ , Parse(..)+ , ParseDefault(..)+ -- ** Parse Utility Functions+ -- $ParseUtilityFunctions+ -- *** Parse With A Single Error Message+ -- $ParseWithASingleErrorMessage+ , withError+ , withErrorS+ , withErrorT+ , withErrorTL+ , withErrorTLB+ , withErrorST+ , withErrorBS+ , withErrorBSL+ , withErrorBSB+ , withErrorSBS+ -- *** Parse With An Error Prefix+ -- $ParseWithAnErrorPrefix+ , prefixError+ , prefixErrorS+ , prefixErrorT+ , prefixErrorTL+ , prefixErrorTLB+ , prefixErrorST+ , prefixErrorBS+ , prefixErrorBSL+ , prefixErrorBSB+ , prefixErrorSBS+ -- *** 'Read' Parsing+ , parseWithRead+ , parseWithRead'+ , maybeParseWithRead+ -- *** 'Enum' Parsing+ , parseEnum+ , parseEnum'+ -- ** Parsing From Specific Types+ -- $ParseSpecific+ , parseS+ , parseT+ , parseTL+ , parseTLB+ , parseST+ , parseBS+ , parseBSL+ , parseBSB+ , parseSBS+ -- ** 'Maybe' Parsing+ -- $ParseMaybe+ , parseMaybe+ , parseMaybeS+ , parseMaybeT+ , parseMaybeTL+ , parseMaybeTLB+ , parseMaybeST+ , parseMaybeBS+ , parseMaybeBSL+ , parseMaybeBSB+ , parseMaybeSBS+ -- ** 'MonadFail' Parsing+ -- $ParseOrFail+ , parseOrFail+ , parseOrFailS+ , parseOrFailT+ , parseOrFailTL+ , parseOrFailTLB+ , parseOrFailST+ , parseOrFailBS+ , parseOrFailBSL+ , parseOrFailBSB+ , parseOrFailSBS+ -- ** Unsafe Parsing+ -- $ParseUnsafe+ , parseUnsafe+ , parseUnsafeS+ , parseUnsafeT+ , parseUnsafeTL+ , parseUnsafeTLB+ , parseUnsafeST+ , parseUnsafeBS+ , parseUnsafeBSL+ , parseUnsafeBSB+ , parseUnsafeSBS+ -- ** 'ReadS' Instances+ , readsWithParse+ , readsEnum+ -- * Template Haskell+ -- ** Constant Validation+ -- $ConstantValidation+ , valid+ , validOf+ , mkValid+ , untypedValidOf+ , mkUntypedValid+ , mkUntypedValidQQ+ -- * Default Instances+ -- $DefaultInstances+ , defaultRenderInstance+ , defaultRenderInstances+ , defaultParseInstance+ , defaultParseInstances+ , defaultRenderAndParseInstance+ , defaultRenderAndParseInstances+ ) where++-- https://hackage.haskell.org/package/base+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Proxy (Proxy(Proxy), asProxyTypeOf)+import Data.String (IsString(fromString))+import Data.Word (Word8, Word16, Word32, Word64)+import GHC.Stack (HasCallStack)+import Text.Read (readMaybe)++-- https://hackage.haskell.org/package/bytestring+import qualified Data.ByteString as BS+import qualified Data.ByteString.Builder as BSB+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Short as SBS++-- https://hackage.haskell.org/package/template-haskell+import qualified Language.Haskell.TH as TH+import qualified Language.Haskell.TH.Quote as Q+import qualified Language.Haskell.TH.Syntax as THS++-- https://hackage.haskell.org/package/text+import qualified Data.Text as T+import qualified Data.Text.Encoding as TE+import qualified Data.Text.Encoding.Error as TEE+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB+import qualified Data.Text.Lazy.Encoding as TLE++-- https://hackage.haskell.org/package/text-short+import qualified Data.Text.Short as ST++------------------------------------------------------------------------------+-- $Textual++-- | Convert from one textual data type to another+--+-- The following textual data types are supported:+--+-- * 'String' (@S@)+-- * Strict 'T.Text' (@T@)+-- * Lazy 'TL.Text' (@TL@)+-- * @Text@ 'TLB.Builder' (@TLB@)+-- * 'ST.ShortText' (@ST@)+-- * Strict 'BS.ByteString' (@BS@)+-- * Lazy 'BSL.ByteString' (@BSL@)+-- * @ByteString@ 'BSB.Builder' (@BSB@)+-- * 'SBS.ShortByteString' (@SBS@)+--+-- Note that support for additional textual data types cannot be implemented+-- by writing instances. Adding support for additional textual data types+-- requires changing the class definition itself. If you need support for+-- additional textual data types, consider using the+-- [ETTC](https://github.com/ExtremaIS/ttc-haskell/tree/main/ettc) library+-- instead.+--+-- Encoded values are assumed to be valid UTF-8 encoded text. Conversions+-- must be pure, and any invalid bytes must be replaced with the Unicode+-- replacement character @U+FFFD@. In cases where different behavior is+-- required, process encoded values separately.+--+-- For more details, see the following article:+-- <https://www.extrema.is/articles/ttc-textual-type-classes/textual-type-class>+--+-- @since 0.1.0.0+class Textual t where+ -- | Convert from a textual data type to a 'String'+ --+ -- @since 0.1.0.0+ toS :: t -> String++ -- | Convert from a textual data type to strict 'T.Text'+ --+ -- @since 0.1.0.0+ toT :: t -> T.Text++ -- | Convert from a textual data type to lazy 'TL.Text'+ --+ -- @since 0.1.0.0+ toTL :: t -> TL.Text++ -- | Convert from a textual data type to a @Text@ 'TLB.Builder'+ --+ -- @since 1.1.0.0+ toTLB :: t -> TLB.Builder++ -- | Convert from a textual data type to 'ST.ShortText'+ --+ -- @since 1.4.0.0+ toST :: t -> ST.ShortText++ -- | Convert from a textual data type to a strict 'BS.ByteString'+ --+ -- @since 0.1.0.0+ toBS :: t -> BS.ByteString++ -- | Convert from a textual data type to a lazy 'BS.ByteString'+ --+ -- @since 0.1.0.0+ toBSL :: t -> BSL.ByteString++ -- | Convert from a textual data type to a @ByteString@ 'BSB.Builder'+ --+ -- @since 1.1.0.0+ toBSB :: t -> BSB.Builder++ -- | Convert from a textual data type to a 'SBS.ShortByteString'+ --+ -- @since 1.1.0.0+ toSBS :: t -> SBS.ShortByteString++ -- | Convert from one textual data type to another+ --+ -- @since 0.1.0.0+ convert' :: Textual t' => t' -> t++instance Textual String where+ toS = id+ toT = T.pack+ toTL = TL.pack+ toTLB = TLB.fromString+ toST = ST.fromString+ toBS = TE.encodeUtf8 . T.pack+ toBSL = TLE.encodeUtf8 . TL.pack+ toBSB = BSB.byteString . TE.encodeUtf8 . T.pack+ toSBS = SBS.toShort . TE.encodeUtf8 . T.pack+ convert' = toS+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual T.Text where+ toS = T.unpack+ toT = id+ toTL = TL.fromStrict+ toTLB = TLB.fromText+ toST = ST.fromText+ toBS = TE.encodeUtf8+ toBSL = TLE.encodeUtf8 . TL.fromStrict+ toBSB = BSB.byteString . TE.encodeUtf8+ toSBS = SBS.toShort . TE.encodeUtf8+ convert' = toT+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual TL.Text where+ toS = TL.unpack+ toT = TL.toStrict+ toTL = id+ toTLB = TLB.fromLazyText+ toST = ST.fromText . TL.toStrict+ toBS = BSL.toStrict . TLE.encodeUtf8+ toBSL = TLE.encodeUtf8+ toBSB = BSB.lazyByteString . TLE.encodeUtf8+ toSBS = SBS.toShort . BSL.toStrict . TLE.encodeUtf8+ convert' = toTL+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual TLB.Builder where+ toS = TL.unpack . TLB.toLazyText+ toT = TL.toStrict . TLB.toLazyText+ toTL = TLB.toLazyText+ toTLB = id+ toST = ST.fromText . TL.toStrict . TLB.toLazyText+ toBS = BSL.toStrict . TLE.encodeUtf8 . TLB.toLazyText+ toBSL = TLE.encodeUtf8 . TLB.toLazyText+ toBSB = BSB.lazyByteString . TLE.encodeUtf8 . TLB.toLazyText+ toSBS = SBS.toShort . BSL.toStrict . TLE.encodeUtf8 . TLB.toLazyText+ convert' = toTLB+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual ST.ShortText where+ toS = ST.toString+ toT = ST.toText+ toTL = TL.fromStrict . ST.toText+ toTLB = TLB.fromText . ST.toText+ toST = id+ toBS = ST.toByteString+ toBSL = BSL.fromStrict . ST.toByteString+ toBSB = BSB.byteString . ST.toByteString+ toSBS = ST.toShortByteString+ convert' = toST+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual BS.ByteString where+ toS = T.unpack . TE.decodeUtf8With TEE.lenientDecode+ toT = TE.decodeUtf8With TEE.lenientDecode+ toTL = TLE.decodeUtf8With TEE.lenientDecode . BSL.fromStrict+ toTLB = TLB.fromText . TE.decodeUtf8With TEE.lenientDecode+ toST = ST.fromText . TE.decodeUtf8With TEE.lenientDecode+ toBS = id+ toBSL = BSL.fromStrict+ toBSB = BSB.byteString+ toSBS = SBS.toShort+ convert' = toBS+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual BSL.ByteString where+ toS = TL.unpack . TLE.decodeUtf8With TEE.lenientDecode+ toT = TL.toStrict . TLE.decodeUtf8With TEE.lenientDecode+ toTL = TLE.decodeUtf8With TEE.lenientDecode+ toTLB = TLB.fromLazyText . TLE.decodeUtf8With TEE.lenientDecode+ toST = ST.fromText . TL.toStrict . TLE.decodeUtf8With TEE.lenientDecode+ toBS = BSL.toStrict+ toBSL = id+ toBSB = BSB.lazyByteString+ toSBS = SBS.toShort . BSL.toStrict+ convert' = toBSL+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual BSB.Builder where+ toS =+ TL.unpack . TLE.decodeUtf8With TEE.lenientDecode . BSB.toLazyByteString+ toT =+ TL.toStrict . TLE.decodeUtf8With TEE.lenientDecode . BSB.toLazyByteString+ toTL = TLE.decodeUtf8With TEE.lenientDecode . BSB.toLazyByteString+ toTLB+ = TLB.fromLazyText+ . TLE.decodeUtf8With TEE.lenientDecode+ . BSB.toLazyByteString+ toST+ = ST.fromText+ . TL.toStrict+ . TLE.decodeUtf8With TEE.lenientDecode+ . BSB.toLazyByteString+ toBS = BSL.toStrict . BSB.toLazyByteString+ toBSL = BSB.toLazyByteString+ toBSB = id+ toSBS = SBS.toShort . BSL.toStrict . BSB.toLazyByteString+ convert' = toBSB+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++instance Textual SBS.ShortByteString where+ toS = T.unpack . TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort+ toT = TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort+ toTL = TLE.decodeUtf8With TEE.lenientDecode . BSL.fromStrict . SBS.fromShort+ toTLB = TLB.fromText . TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort+ toST = ST.fromText . TE.decodeUtf8With TEE.lenientDecode . SBS.fromShort+ toBS = SBS.fromShort+ toBSL = BSL.fromStrict . SBS.fromShort+ toBSB = BSB.byteString . SBS.fromShort+ toSBS = id+ convert' = toSBS+ {-# INLINE toS #-}+ {-# INLINE toT #-}+ {-# INLINE toTL #-}+ {-# INLINE toTLB #-}+ {-# INLINE toST #-}+ {-# INLINE toBS #-}+ {-# INLINE toBSL #-}+ {-# INLINE toBSB #-}+ {-# INLINE toSBS #-}+ {-# INLINE convert' #-}++------------------------------------------------------------------------------++-- | Convert from one textual data type to another+--+-- The order of the type arguments was changed in version 1.5.0.0.+--+-- @since 0.1.0.0+convert :: forall t t'. (Textual t, Textual t') => t -> t'+convert = convert'+{-# INLINE convert #-}++------------------------------------------------------------------------------+-- $TextualTo+--+-- These functions are equivalent to 'convert', but they specify the type+-- being converted to. Use them to avoid having to write type annotations in+-- cases where the type is ambiguous. Using these functions may make code+-- easier to understand even in cases where the types are not ambiguous.++-- $TextualFrom+--+-- These functions are equivalent to 'convert', but they specify the type+-- being converted from. Use them to avoid having to write type annotations+-- in cases where the type is ambiguous. Using these functions may make code+-- easier to understand even in cases where the types are not ambiguous.++-- | Convert from a 'String' to a textual data type+--+-- @since 0.1.0.0+fromS :: Textual t => String -> t+fromS = convert'+{-# INLINE fromS #-}++-- | Convert from strict 'T.Text' to a textual data type+--+-- @since 0.1.0.0+fromT :: Textual t => T.Text -> t+fromT = convert'+{-# INLINE fromT #-}++-- | Convert from lazy 'TL.Text' to a textual data type+--+-- @since 0.1.0.0+fromTL :: Textual t => TL.Text -> t+fromTL = convert'+{-# INLINE fromTL #-}++-- | Convert from a @Text@ 'TLB.Builder' to a textual data type+--+-- @since 1.1.0.0+fromTLB :: Textual t => TLB.Builder -> t+fromTLB = convert'+{-# INLINE fromTLB #-}++-- | Convert from a 'ST.ShortText' to a textual data type+--+-- @since 1.4.0.0+fromST :: Textual t => ST.ShortText -> t+fromST = convert'+{-# INLINE fromST #-}++-- | Convert from a strict 'BS.ByteString' to a textual data type+--+-- @since 0.1.0.0+fromBS :: Textual t => BS.ByteString -> t+fromBS = convert'+{-# INLINE fromBS #-}++-- | Convert from a lazy 'BSL.ByteString' to a textual data type+--+-- @since 0.1.0.0+fromBSL :: Textual t => BSL.ByteString -> t+fromBSL = convert'+{-# INLINE fromBSL #-}++-- | Convert from a @ByteString@ 'TLB.Builder' to a textual data type+--+-- @since 1.1.0.0+fromBSB :: Textual t => BSB.Builder -> t+fromBSB = convert'+{-# INLINE fromBSB #-}++-- | Convert from a 'SBS.ShortByteString' to a textual data type+--+-- @since 1.1.0.0+fromSBS :: Textual t => SBS.ShortByteString -> t+fromSBS = convert'+{-# INLINE fromSBS #-}++------------------------------------------------------------------------------+-- $TextualAs+--+-- These functions are used to convert a textual data type argument to a+-- specific type. Use them to reduce boilerplate in small function+-- definitions.++-- | Convert a textual data type argument to a 'String'+--+-- @since 0.1.0.0+asS :: forall t a. Textual t => (String -> a) -> t -> a+asS f = f . convert'+{-# INLINE asS #-}++-- | Convert a textual data type argument to strict 'T.Text'+--+-- @since 0.1.0.0+asT :: forall t a. Textual t => (T.Text -> a) -> t -> a+asT f = f . convert'+{-# INLINE asT #-}++-- | Convert a textual data type argument to lazy 'TL.Text'+--+-- @since 0.1.0.0+asTL :: forall t a. Textual t => (TL.Text -> a) -> t -> a+asTL f = f . convert'+{-# INLINE asTL #-}++-- | Convert a textual data type argument to a @Text@ 'TLB.Builder'+--+-- @since 1.1.0.0+asTLB :: forall t a. Textual t => (TLB.Builder -> a) -> t -> a+asTLB f = f . convert'+{-# INLINE asTLB #-}++-- | Convert a textual data type argument to a 'ST.ShortText'+--+-- @since 1.4.0.0+asST :: forall t a. Textual t => (ST.ShortText -> a) -> t -> a+asST f = f . convert'+{-# INLINE asST #-}++-- | Convert a textual data type argument to a strict 'BS.ByteString'+--+-- @since 0.1.0.0+asBS :: forall t a. Textual t => (BS.ByteString -> a) -> t -> a+asBS f = f . convert'+{-# INLINE asBS #-}++-- | Convert a textual data type argument to a lazy 'BSL.ByteString'+--+-- @since 0.1.0.0+asBSL :: forall t a. Textual t => (BSL.ByteString -> a) -> t -> a+asBSL f = f . convert'+{-# INLINE asBSL #-}++-- | Convert a textual data type argument to a @ByteString@ 'TLB.Builder'+--+-- @since 1.1.0.0+asBSB :: forall t a. Textual t => (BSB.Builder -> a ) -> t -> a+asBSB f = f . convert'+{-# INLINE asBSB #-}++-- | Convert a textual data type argument to a 'SBS.ShortByteString'+--+-- @since 1.1.0.0+asSBS :: forall t a. Textual t => (SBS.ShortByteString -> a) -> t -> a+asSBS f = f . convert'+{-# INLINE asSBS #-}++------------------------------------------------------------------------------+-- $Render++-- | Render a data type as a textual data type+--+-- Use 'Render' in your business logic, and only use 'Show' for debugging, as+-- use of 'Show' instances in business logic is a common source of bugs.+--+-- When defining an instance, render to the textual data type that is most+-- natural for the data type, and then use 'convert' to handle the conversion+-- to any textual data type. This is particularly wrappers around a textual+-- data type. Example:+--+-- @+-- newtype Username = Username { usernameText :: Text }+--+-- instance TTC.Render Username where+-- render = TTC.convert . usernameText+-- @+--+-- To use @render@ in a context where the types are ambiguous, use the+-- [@TypeApplications@](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_applications.html)+-- GHC extension to specify one or both types. Example:+--+-- @+-- -- Render to Text+-- render @_ @Text foo+-- @+--+-- Alternatively, use one of the functions that render to a specific textual+-- data type (such as 'renderS'). Using these functions may make code easier+-- to understand even in cases where the types are not ambiguous.+--+-- See the @uname@ and @prompt@ example programs in the @ttc-examples@+-- directory of the source repository.+--+-- For more details, see the following article:+-- <https://www.extrema.is/articles/ttc-textual-type-classes/render-and-parse>+--+-- Since a type may have at most one instance of a given type class, special+-- care must be taken when defining type class instances in a shared library.+-- In particular, orphan instances should generally not be used in shared+-- libraries since they prevent users of the libraries from writing their own+-- instances. Use @newtype@ wrappers instead.+--+-- There are no default instances for the 'Render' type class, so that all+-- instances can be customized per project when desired. Instances for some+-- basic data types are defined for the 'RenderDefault' type class, however,+-- and the Template Haskell functions documented below can be used to load+-- these definitions with minimal boilerplate.+--+-- @since 0.1.0.0+class Render a where+ -- | Render a data type as a textual data type+ --+ -- @since 0.1.0.0+ render :: Textual t => a -> t++ default render :: (RenderDefault a, Textual t) => a -> t+ render = renderDefault++------------------------------------------------------------------------------++-- | Default 'Render' instances for some common types+--+-- * The 'Bool' instance renders using the 'Show' instance. This instance was+-- added in version 1.5.0.0.+-- * The 'Char' instance renders a single-character string.+-- * Numeric type instances all render using the 'Show' instance.+-- * Textual data type instances all convert to the target textual data type.+--+-- @since 1.1.0.0+class RenderDefault a where+ -- | Render a data type as a textual data type+ --+ -- @since 1.1.0.0+ renderDefault :: Textual t => a -> t++instance RenderDefault Bool where+ renderDefault = renderWithShow++instance RenderDefault Char where+ renderDefault c = fromS [c]++instance RenderDefault Double where+ renderDefault = renderWithShow++instance RenderDefault Float where+ renderDefault = renderWithShow++instance RenderDefault Integer where+ renderDefault = renderWithShow++instance RenderDefault Int where+ renderDefault = renderWithShow++instance RenderDefault Int8 where+ renderDefault = renderWithShow++instance RenderDefault Int16 where+ renderDefault = renderWithShow++instance RenderDefault Int32 where+ renderDefault = renderWithShow++instance RenderDefault Int64 where+ renderDefault = renderWithShow++instance RenderDefault Word where+ renderDefault = renderWithShow++instance RenderDefault Word8 where+ renderDefault = renderWithShow++instance RenderDefault Word16 where+ renderDefault = renderWithShow++instance RenderDefault Word32 where+ renderDefault = renderWithShow++instance RenderDefault Word64 where+ renderDefault = renderWithShow++instance RenderDefault String where+ renderDefault = fromS++instance RenderDefault T.Text where+ renderDefault = fromT++instance RenderDefault TL.Text where+ renderDefault = fromTL++instance RenderDefault TLB.Builder where+ renderDefault = fromTLB++instance RenderDefault ST.ShortText where+ renderDefault = fromST++instance RenderDefault BS.ByteString where+ renderDefault = fromBS++instance RenderDefault BSL.ByteString where+ renderDefault = fromBSL++instance RenderDefault BSB.Builder where+ renderDefault = fromBSB++instance RenderDefault SBS.ShortByteString where+ renderDefault = fromSBS++------------------------------------------------------------------------------+-- $RenderUtilityFunctions+--+-- These functions are used to implement 'Render' instances.++-- | Render a value to a textual data type using a 'Show' instance+--+-- To use this function in a context where the types are ambiguous, use the+-- [@TypeApplications@](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_applications.html)+-- GHC extension to specify one or both types. Example:+--+-- @+-- -- Render to Text+-- renderWithShow @Text foo+-- @+--+-- See the @enum@ example program in the @ttc-examples@ directory of the+-- source repository.+--+-- @since 0.1.0.0+renderWithShow :: forall t a. (Show a, Textual t) => a -> t+renderWithShow = convert' . show+{-# INLINE renderWithShow #-}++------------------------------------------------------------------------------+-- $RenderSpecific+--+-- These functions are equivalent to 'render', but they specify the textual+-- data type being rendered to. Use them to avoid having to write type+-- annotations in cases where the type is ambiguous. Using these functions+-- may make code easier to understand even in cases where the types are not+-- ambiguous.++-- | Render to a 'String'+--+-- @since 0.1.0.0+renderS :: Render a => a -> String+renderS = render+{-# INLINE renderS #-}++-- | Render to strict 'T.Text'+--+-- @since 0.1.0.0+renderT :: Render a => a -> T.Text+renderT = render+{-# INLINE renderT #-}++-- | Render to lazy 'TL.Text'+--+-- @since 0.1.0.0+renderTL :: Render a => a -> TL.Text+renderTL = render+{-# INLINE renderTL #-}++-- | Render to a @Text@ 'TLB.Builder'+--+-- @since 0.4.0.0+renderTLB :: Render a => a -> TLB.Builder+renderTLB = render+{-# INLINE renderTLB #-}++-- | Render to a 'ST.ShortText'+--+-- @since 1.4.0.0+renderST :: Render a => a -> ST.ShortText+renderST = render+{-# INLINE renderST #-}++-- | Render to a strict 'BS.ByteString'+--+-- @since 0.1.0.0+renderBS :: Render a => a -> BS.ByteString+renderBS = render+{-# INLINE renderBS #-}++-- | Render to a lazy 'BSL.ByteString'+--+-- @since 0.1.0.0+renderBSL :: Render a => a -> BSL.ByteString+renderBSL = render+{-# INLINE renderBSL #-}++-- | Render to a @ByteString@ 'BSB.Builder'+--+-- @since 0.4.0.0+renderBSB :: Render a => a -> BSB.Builder+renderBSB = render+{-# INLINE renderBSB #-}++-- | Render to a 'SBS.ShortByteString'+--+-- @since 0.4.0.0+renderSBS :: Render a => a -> SBS.ShortByteString+renderSBS = render+{-# INLINE renderSBS #-}++------------------------------------------------------------------------------+-- $Parse++-- | Parse a data type from a textual data type+--+-- Unlike 'Read', 'Parse' allows you to specify meaningful error messages.+--+-- When defining an instance, first convert the textual data type to the+-- textual data type that is most natural for the data type. The @as@+-- functions (such as 'asS') provide a convenient way to do this. Note that+-- error is also a textual data type. The 'withError' and 'prefixError'+-- functions can be used to reduce boilerplate. Example:+--+-- @+-- newtype Username = Username { usernameText :: Text }+--+-- instance TTC.Parse Username where+-- parse = TTC.asT $ \t -> TTC.prefixErrorS "invalid username: " $ do+-- unless (T.all isAsciiLower t) $ Left "not only lowercase ASCII letters"+-- let len = T.length t+-- when (len < 3) $ Left "fewer than 3 characters"+-- when (len > 12) $ Left "more than 12 characters"+-- pure $ Username t+-- @+--+-- To use @parse@ in a context where the types are ambiguous, use the+-- [@TypeApplications@](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/type_applications.html)+-- GHC extension to specify one or more types. Example:+--+-- @+-- -- Parse from Text+-- parse @_ @Text foo+--+-- -- Parse using String errors+-- parse @_ @_ @String foo+--+-- -- Parse from Text using String errors+-- parse @_ @Text @String foo+-- @+--+-- Alternatively, use one of the functions that parse from a specific textual+-- data type (such as 'renderS'). Using these functions may make code easier+-- to understand even in cases where the types are not ambiguous.+--+-- See the @uname@ and @prompt@ example programs in the @ttc-examples@+-- directory of the source repository.+--+-- For more details, see the following article:+-- <https://www.extrema.is/articles/ttc-textual-type-classes/render-and-parse>+--+-- Since a type may have at most one instance of a given type class, special+-- care must be taken when defining type class instances in a shared library.+-- In particular, orphan instances should generally not be used in shared+-- libraries since they prevent users of the libraries from writing their own+-- instances. Use @newtype@ wrappers instead.+--+-- There are no default instances for the 'Parse' type class, so that all+-- instances can be customized per project when desired. Instances for some+-- basic data types are defined for the 'ParseDefault' type class, however,+-- and the Template Haskell functions documented below can be used to load+-- these definitions with minimal boilerplate.+--+-- @since 0.3.0.0+class Parse a where+ -- | Parse a data type from a textual data type+ --+ -- @since 0.3.0.0+ parse :: (Textual t, Textual e) => t -> Either e a++ default parse :: (Textual t, Textual e, ParseDefault a) => t -> Either e a+ parse = parseDefault++-- This function is equivalent to 'parse' with the error type fixed to+-- 'String', used internally when the error is ignored.+--+-- @since 0.3.0.0+parse' :: forall t a. (Parse a, Textual t) => t -> Either String a+parse' = parse+{-# INLINE parse' #-}++------------------------------------------------------------------------------++-- | The 'ParseDefault' type class provides some default 'Parse' instances.+--+-- * The 'Bool' instance parses using the 'Read' instance. This instance was+-- added in version 1.5.0.0.+-- * The 'Char' instance parses single-character strings.+-- * Numeric type instances all parse using the 'Read' instance.+-- * Textual data type instances all convert from the source textual data+-- type.+--+-- @since 1.1.0.0+class ParseDefault a where+ -- | Parse a data type from a textual data type+ --+ -- @since 1.1.0.0+ parseDefault :: (Textual t, Textual e) => t -> Either e a++instance ParseDefault Bool where+ parseDefault = parseWithRead' "Bool"++instance ParseDefault Char where+ parseDefault = asS $ \case+ [c] -> Right c+ _cs -> Left $ fromS "invalid Char"++instance ParseDefault Double where+ parseDefault = parseWithRead' "Double"++instance ParseDefault Float where+ parseDefault = parseWithRead' "Float"++instance ParseDefault Integer where+ parseDefault = parseWithRead' "Integer"++instance ParseDefault Int where+ parseDefault = parseWithRead' "Int"++instance ParseDefault Int8 where+ parseDefault = parseWithRead' "Int8"++instance ParseDefault Int16 where+ parseDefault = parseWithRead' "Int16"++instance ParseDefault Int32 where+ parseDefault = parseWithRead' "Int32"++instance ParseDefault Int64 where+ parseDefault = parseWithRead' "Int64"++instance ParseDefault Word where+ parseDefault = parseWithRead' "Word"++instance ParseDefault Word8 where+ parseDefault = parseWithRead' "Word8"++instance ParseDefault Word16 where+ parseDefault = parseWithRead' "Word16"++instance ParseDefault Word32 where+ parseDefault = parseWithRead' "Word32"++instance ParseDefault Word64 where+ parseDefault = parseWithRead' "Word64"++instance ParseDefault String where+ parseDefault = Right . toS++instance ParseDefault T.Text where+ parseDefault = Right . toT++instance ParseDefault TL.Text where+ parseDefault = Right . toTL++instance ParseDefault TLB.Builder where+ parseDefault = Right . toTLB++instance ParseDefault ST.ShortText where+ parseDefault = Right . toST++instance ParseDefault BS.ByteString where+ parseDefault = Right . toBS++instance ParseDefault BSL.ByteString where+ parseDefault = Right . toBSL++instance ParseDefault BSB.Builder where+ parseDefault = Right . toBSB++instance ParseDefault SBS.ShortByteString where+ parseDefault = Right . toSBS++------------------------------------------------------------------------------+-- $ParseUtilityFunctions+--+-- These functions are used to implement 'Parse' instances.++------------------------------------------------------------------------------+-- $ParseWithASingleErrorMessage+--+-- The 'withError' function takes an error message and a 'Maybe' value. It+-- returns a 'Parse' result: the error when the 'Maybe' value is 'Nothing', or+-- the value inside the 'Just'. This provides a convenient way to return the+-- same error message for any parse error. The rest of the functions are+-- equivalent to 'withError', but they specify the type of the error message.+-- Use them to avoid having to write type annotations in cases where the type+-- is ambiguous.++-- | Create a 'Parse' result from a 'Textual' error message and a 'Maybe'+-- value+--+-- @since 1.2.0.0+withError+ :: forall e' e a. (Textual e', Textual e)+ => e'+ -> Maybe a+ -> Either e a+withError err = maybe (Left $ convert' err) Right+{-# INLINE withError #-}++-- | Create a 'Parse' result from a 'String' error message and a 'Maybe' value+--+-- @since 1.2.0.0+withErrorS+ :: forall e a. Textual e+ => String+ -> Maybe a+ -> Either e a+withErrorS = withError+{-# INLINE withErrorS #-}++-- | Create a 'Parse' result from a 'T.Text' error message and a 'Maybe' value+--+-- @since 1.2.0.0+withErrorT+ :: forall e a. Textual e+ => T.Text+ -> Maybe a+ -> Either e a+withErrorT = withError+{-# INLINE withErrorT #-}++-- | Create a 'Parse' result from a 'TL.Text' error message and a 'Maybe'+-- value+--+-- @since 1.2.0.0+withErrorTL+ :: forall e a. Textual e+ => TL.Text+ -> Maybe a+ -> Either e a+withErrorTL = withError+{-# INLINE withErrorTL #-}++-- | Create a 'Parse' result from a 'TLB.Builder' error message and a 'Maybe'+-- value+--+-- @since 1.2.0.0+withErrorTLB+ :: forall e a. Textual e+ => TLB.Builder+ -> Maybe a+ -> Either e a+withErrorTLB = withError+{-# INLINE withErrorTLB #-}++-- | Create a 'Parse' result from a 'ST.ShortText' error message and a 'Maybe'+-- value+--+-- @since 1.4.0.0+withErrorST+ :: forall e a. Textual e+ => ST.ShortText+ -> Maybe a+ -> Either e a+withErrorST = withError+{-# INLINE withErrorST #-}++-- | Create a 'Parse' result from a 'BS.ByteString' error message and a+-- 'Maybe' value+--+-- @since 1.2.0.0+withErrorBS+ :: forall e a. Textual e+ => BS.ByteString+ -> Maybe a+ -> Either e a+withErrorBS = withError+{-# INLINE withErrorBS #-}++-- | Create a 'Parse' result from a 'BSL.ByteString' error message and a+-- 'Maybe' value+--+-- @since 1.2.0.0+withErrorBSL+ :: forall e a. Textual e+ => BSL.ByteString+ -> Maybe a+ -> Either e a+withErrorBSL = withError+{-# INLINE withErrorBSL #-}++-- | Create a 'Parse' result from a 'BSB.Builder' error message and a+-- 'Maybe' value+--+-- @since 1.2.0.0+withErrorBSB+ :: forall e a. Textual e+ => BSB.Builder+ -> Maybe a+ -> Either e a+withErrorBSB = withError+{-# INLINE withErrorBSB #-}++-- | Create a 'Parse' result from a 'SBS.ShortByteString' error message and a+-- 'Maybe' value+--+-- @since 1.2.0.0+withErrorSBS+ :: forall e a. Textual e+ => SBS.ShortByteString+ -> Maybe a+ -> Either e a+withErrorSBS = withError+{-# INLINE withErrorSBS #-}++------------------------------------------------------------------------------+-- $ParseWithAnErrorPrefix+--+-- The 'prefixError' function adds a common prefix to error messages of a+-- 'Parse' result. The rest of the functions are equivalent to 'prefixError',+-- but they specify the type of the error message. Use them to avoid having+-- to write type annotations in cases where the type is ambiguous.++-- | Add a prefix to 'Textual' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixError+ :: forall e' e a. (Monoid e', Textual e', Textual e)+ => e'+ -> Either e' a+ -> Either e a+prefixError prefix = either (Left . convert' . mappend prefix) Right+{-# INLINE prefixError #-}++-- | Add a prefix to 'String' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorS+ :: forall e a. Textual e+ => String+ -> Either String a+ -> Either e a+prefixErrorS = prefixError+{-# INLINE prefixErrorS #-}++-- | Add a prefix to 'T.Text' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorT+ :: forall e a. Textual e+ => T.Text+ -> Either T.Text a+ -> Either e a+prefixErrorT = prefixError+{-# INLINE prefixErrorT #-}++-- | Add a prefix to 'TL.Text' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorTL+ :: forall e a. Textual e+ => TL.Text+ -> Either TL.Text a+ -> Either e a+prefixErrorTL = prefixError+{-# INLINE prefixErrorTL #-}++-- | Add a prefix to 'TLB.Builder' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorTLB+ :: forall e a. Textual e+ => TLB.Builder+ -> Either TLB.Builder a+ -> Either e a+prefixErrorTLB = prefixError+{-# INLINE prefixErrorTLB #-}++-- | Add a prefix to 'ST.ShortText' error messages of a 'Parse' result+--+-- @since 1.4.0.0+prefixErrorST+ :: forall e a. Textual e+ => ST.ShortText+ -> Either ST.ShortText a+ -> Either e a+prefixErrorST = prefixError+{-# INLINE prefixErrorST #-}++-- | Add a prefix to 'BS.ByteString' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorBS+ :: forall e a. Textual e+ => BS.ByteString+ -> Either BS.ByteString a+ -> Either e a+prefixErrorBS = prefixError+{-# INLINE prefixErrorBS #-}++-- | Add a prefix to 'BSL.ByteString' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorBSL+ :: forall e a. Textual e+ => BSL.ByteString+ -> Either BSL.ByteString a+ -> Either e a+prefixErrorBSL = prefixError+{-# INLINE prefixErrorBSL #-}++-- | Add a prefix to 'BSB.Builder' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorBSB+ :: forall e a. Textual e+ => BSB.Builder+ -> Either BSB.Builder a+ -> Either e a+prefixErrorBSB = prefixError+{-# INLINE prefixErrorBSB #-}++-- | Add a prefix to 'SBS.ShortByteString' error messages of a 'Parse' result+--+-- @since 1.2.0.0+prefixErrorSBS+ :: forall e a. Textual e+ => SBS.ShortByteString+ -> Either SBS.ShortByteString a+ -> Either e a+prefixErrorSBS = prefixError+{-# INLINE prefixErrorSBS #-}++------------------------------------------------------------------------------+-- $ReadParsing++-- | Parse a value using a 'Read' instance+--+-- @since 0.1.0.0+parseWithRead+ :: forall t e a. (Read a, Textual t)+ => e -- ^ invalid input error+ -> t -- ^ textual input to parse+ -> Either e a -- ^ error or parsed value+parseWithRead invalidError = maybe (Left invalidError) Right . readMaybe . toS+{-# INLINEABLE parseWithRead #-}++-- | Parse a value using a 'Read' instance with default error messages+--+-- The following English error message is returned:+--+-- * \"invalid {name}\" when the parse fails+--+-- @since 0.3.0.0+parseWithRead'+ :: forall t e a. (Read a, Textual t, Textual e)+ => String -- ^ name to include in error messages+ -> t -- ^ textual input to parse+ -> Either e a -- ^ error or parsed value+parseWithRead' name = parseWithRead (fromS $ "invalid " ++ name)+{-# INLINEABLE parseWithRead' #-}++-- | Parse a value to a 'Maybe' result using a 'Read' instance+--+-- @since 0.3.0.0+maybeParseWithRead+ :: forall t a. (Read a, Textual t)+ => t -- ^ textual input to parse+ -> Maybe a -- ^ parsed value or 'Nothing' if invalid+maybeParseWithRead = readMaybe . toS++------------------------------------------------------------------------------+-- $EnumParsing++-- | Parse a value in an enumeration+--+-- The 'Render' instance determines the textual values to parse from.+--+-- This function is intended to be used with types that have few choices, as+-- the implementation uses a linear algorithm.+--+-- See the @enum@ example program in the @ttc-examples@ directory of the+-- source repository.+--+-- @since 0.1.0.0+parseEnum+ :: forall t e a. (Bounded a, Enum a, Render a, Textual t)+ => Bool -- ^ case-insensitive when 'True'+ -> Bool -- ^ accept unique prefixes when 'True'+ -> e -- ^ invalid input error+ -> e -- ^ ambiguous input error+ -> t -- ^ textual input to parse+ -> Either e a -- ^ error or parsed value+parseEnum allowCI allowPrefix invalidError ambiguousError t =+ let t' = norm $ toT t+ in case [v | v <- [minBound ..], t' `match` norm (render v)] of+ [v] -> Right v+ [] -> Left invalidError+ _vs -> Left ambiguousError+ where+ norm :: T.Text -> T.Text+ norm = if allowCI then T.toLower else id++ match :: T.Text -> T.Text -> Bool+ match = if allowPrefix then T.isPrefixOf else (==)++-- | Parse a value in an enumeration using default error messages+--+-- The 'Render' instance determines the textual values to parse from.+--+-- The following English error messages are returned:+--+-- * \"invalid {name}\" when there are no matches+-- * \"ambiguous {name}\" when there is more than one match+--+-- This function is intended to be used with types that have few choices, as+-- the implementation uses a linear algorithm.+--+-- @since 0.4.0.0+parseEnum'+ :: forall t e a. (Bounded a, Enum a, Render a, Textual t, Textual e)+ => String -- ^ name to include in error messages+ -> Bool -- ^ case-insensitive when 'True'+ -> Bool -- ^ accept unique prefixes when 'True'+ -> t -- ^ textual input to parse+ -> Either e a -- ^ error or parsed value+parseEnum' name allowCI allowPrefix =+ parseEnum+ allowCI allowPrefix+ (fromS $ "invalid " ++ name)+ (fromS $ "ambiguous " ++ name)+{-# INLINEABLE parseEnum' #-}++------------------------------------------------------------------------------+-- $ParseSpecific+--+-- These functions are equivalent to 'parse', but they specify the textual+-- data type being parsed from. Use them to avoid having to write type+-- annotations in cases where the type is ambiguous. Using these functions+-- may make code easier to understand even in cases where the types are not+-- ambiguous.++-- | Parse from a 'String'+--+-- @since 0.3.0.0+parseS :: forall e a. (Parse a, Textual e) => String -> Either e a+parseS = parse+{-# INLINE parseS #-}++-- | Parse from strict 'T.Text'+--+-- @since 0.3.0.0+parseT :: forall e a. (Parse a, Textual e) => T.Text -> Either e a+parseT = parse+{-# INLINE parseT #-}++-- | Parse from lazy 'TL.Text'+--+-- @since 0.3.0.0+parseTL :: forall e a. (Parse a, Textual e) => TL.Text -> Either e a+parseTL = parse+{-# INLINE parseTL #-}++-- | Parse from a @Text@ 'TLB.Builder'+--+-- @since 1.1.0.0+parseTLB :: forall e a. (Parse a, Textual e) => TLB.Builder -> Either e a+parseTLB = parse+{-# INLINE parseTLB #-}++-- | Parse from a 'ST.ShortText'+--+-- @since 1.4.0.0+parseST :: forall e a. (Parse a, Textual e) => ST.ShortText -> Either e a+parseST = parse+{-# INLINE parseST #-}++-- | Parse from a strict 'BS.ByteString'+--+-- @since 0.3.0.0+parseBS :: forall e a. (Parse a, Textual e) => BS.ByteString -> Either e a+parseBS = parse+{-# INLINE parseBS #-}++-- | Parse from a lazy 'BSL.ByteString'+--+-- @since 0.3.0.0+parseBSL :: forall e a. (Parse a, Textual e) => BSL.ByteString -> Either e a+parseBSL = parse+{-# INLINE parseBSL #-}++-- | Parse from a @ByteString@ 'BSB.Builder'+--+-- @since 1.1.0.0+parseBSB :: forall e a. (Parse a, Textual e) => BSB.Builder -> Either e a+parseBSB = parse+{-# INLINE parseBSB #-}++-- | Parse from a 'SBS.ShortByteString'+--+-- @since 1.1.0.0+parseSBS+ :: forall e a. (Parse a, Textual e)+ => SBS.ShortByteString+ -> Either e a+parseSBS = parse+{-# INLINE parseSBS #-}++------------------------------------------------------------------------------+-- $ParseMaybe+--+-- The 'parseMaybe' function parses to a 'Maybe' result instead of an 'Either'+-- result.+--+-- The rest of the functions are equivalent to 'parseMaybe', but they specify+-- the type being parsed from. Use them to avoid having to write type+-- annotations in cases where the type is ambiguous. Using these functions+-- may make code easier to understand even in cases where the types are not+-- ambiguous.++-- | Parse to a 'Maybe' result+--+-- @since 0.3.0.0+parseMaybe :: forall t a. (Parse a, Textual t) => t -> Maybe a+parseMaybe = either (const Nothing) Just . parse'+{-# INLINE parseMaybe #-}++-- | Parse from a 'String' to a 'Maybe' result+--+-- @since 0.3.0.0+parseMaybeS :: Parse a => String -> Maybe a+parseMaybeS = parseMaybe+{-# INLINE parseMaybeS #-}++-- | Parse from strict 'T.Text' to a 'Maybe' result+--+-- @since 0.3.0.0+parseMaybeT :: Parse a => T.Text -> Maybe a+parseMaybeT = parseMaybe+{-# INLINE parseMaybeT #-}++-- | Parse from lazy 'TL.Text' to a 'Maybe' result+--+-- @since 0.3.0.0+parseMaybeTL :: Parse a => TL.Text -> Maybe a+parseMaybeTL = parseMaybe+{-# INLINE parseMaybeTL #-}++-- | Parse from a @Text@ 'TLB.Builder' to a 'Maybe' result+--+-- @since 1.1.0.0+parseMaybeTLB :: Parse a => TLB.Builder -> Maybe a+parseMaybeTLB = parseMaybe+{-# INLINE parseMaybeTLB #-}++-- | Parse from a 'ST.ShortText' to a 'Maybe' result+--+-- @since 1.4.0.0+parseMaybeST :: Parse a => ST.ShortText -> Maybe a+parseMaybeST = parseMaybe+{-# INLINE parseMaybeST #-}++-- | Parse from a strict 'BS.ByteString' to a 'Maybe' result+--+-- @since 0.3.0.0+parseMaybeBS :: Parse a => BS.ByteString -> Maybe a+parseMaybeBS = parseMaybe+{-# INLINE parseMaybeBS #-}++-- | Parse from a lazy 'BSL.ByteString' to a 'Maybe' result+--+-- @since 0.3.0.0+parseMaybeBSL :: Parse a => BSL.ByteString -> Maybe a+parseMaybeBSL = parseMaybe+{-# INLINE parseMaybeBSL #-}++-- | Parse from a @ByteString@ 'BSB.Builder' to a 'Maybe' result+--+-- @since 1.1.0.0+parseMaybeBSB :: Parse a => BSB.Builder -> Maybe a+parseMaybeBSB = parseMaybe+{-# INLINE parseMaybeBSB #-}++-- | Parse from a 'SBS.ShortByteString' to a 'Maybe' result+--+-- @since 1.1.0.0+parseMaybeSBS :: Parse a => SBS.ShortByteString -> Maybe a+parseMaybeSBS = parseMaybe+{-# INLINE parseMaybeSBS #-}++------------------------------------------------------------------------------+-- $ParseOrFail+--+-- The 'parseOrFail' function fails using 'MonadFail' on error instead of+-- using an 'Either' result.+--+-- The rest of the functions are equivalent to 'parseOrFail', but they specify+-- the type being parsed from. Use them to avoid having to write type+-- annotations in cases where the type is ambiguous. Using these functions+-- may make code easier to understand even in cases where the types are not+-- ambiguous.++-- | Parse or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFail :: forall t a m. (MonadFail m, Parse a, Textual t) => t -> m a+parseOrFail = either fail pure . parse+{-# INLINE parseOrFail #-}++-- | Parse from a 'String' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailS :: forall a m. (MonadFail m, Parse a) => String -> m a+parseOrFailS = parseOrFail+{-# INLINE parseOrFailS #-}++-- | Parse from strict 'T.Text' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailT :: forall a m. (MonadFail m, Parse a) => T.Text -> m a+parseOrFailT = parseOrFail+{-# INLINE parseOrFailT #-}++-- | Parse from lazy 'TL.Text' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailTL :: forall a m. (MonadFail m, Parse a) => TL.Text -> m a+parseOrFailTL = parseOrFail+{-# INLINE parseOrFailTL #-}++-- | Parse from a @Text@ 'TLB.Builder' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailTLB :: forall a m. (MonadFail m, Parse a) => TLB.Builder -> m a+parseOrFailTLB = parseOrFail+{-# INLINE parseOrFailTLB #-}++-- | Parse from a 'ST.ShortText' or fail using 'MonadFail'+--+-- @since 1.4.0.0+parseOrFailST :: forall a m. (MonadFail m, Parse a) => ST.ShortText -> m a+parseOrFailST = parseOrFail+{-# INLINE parseOrFailST #-}++-- | Parse from a strict 'BS.ByteString' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailBS :: forall a m. (MonadFail m, Parse a) => BS.ByteString -> m a+parseOrFailBS = parseOrFail+{-# INLINE parseOrFailBS #-}++-- | Parse from a lazy 'BSL.ByteString' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailBSL :: forall a m. (MonadFail m, Parse a) => BSL.ByteString -> m a+parseOrFailBSL = parseOrFail+{-# INLINE parseOrFailBSL #-}++-- | Parse from a @ByteString@ 'BSB.Builder' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailBSB :: forall a m. (MonadFail m, Parse a) => BSB.Builder -> m a+parseOrFailBSB = parseOrFail+{-# INLINE parseOrFailBSB #-}++-- | Parse from a 'SBS.ShortByteString' or fail using 'MonadFail'+--+-- @since 1.3.0.0+parseOrFailSBS+ :: forall a m. (MonadFail m, Parse a)+ => SBS.ShortByteString+ -> m a+parseOrFailSBS = parseOrFail+{-# INLINE parseOrFailSBS #-}++------------------------------------------------------------------------------+-- $ParseUnsafe+--+-- The 'parseUnsafe' function raises an exception on error instead of using an+-- 'Either' result. It should only be used when an error is not possible.+--+-- The rest of the functions are equivalent to 'parseUnsafe', but they specify+-- the type being parsed from. Use them to avoid having to write type+-- annotations in cases where the type is ambiguous. Using these functions+-- may make code easier to understand even in cases where the types are not+-- ambiguous.++-- | Parse or raise an exception+--+-- @since 0.1.0.0+parseUnsafe :: forall t a. (HasCallStack, Parse a, Textual t) => t -> a+parseUnsafe = either (error . ("parseUnsafe: " ++)) id . parse+{-# INLINE parseUnsafe #-}++-- | Parse from a 'String' or raise an exception+--+-- @since 0.1.0.0+parseUnsafeS :: (HasCallStack, Parse a) => String -> a+parseUnsafeS = parseUnsafe+{-# INLINE parseUnsafeS #-}++-- | Parse from strict 'T.Text' or raise an exception+--+-- @since 0.1.0.0+parseUnsafeT :: (HasCallStack, Parse a) => T.Text -> a+parseUnsafeT = parseUnsafe+{-# INLINE parseUnsafeT #-}++-- | Parse from lazy 'TL.Text' or raise an exception+--+-- @since 0.1.0.0+parseUnsafeTL :: (HasCallStack, Parse a) => TL.Text -> a+parseUnsafeTL = parseUnsafe+{-# INLINE parseUnsafeTL #-}++-- | Parse from a @Text@ 'TLB.Builder' or raise an exception+--+-- @since 1.1.0.0+parseUnsafeTLB :: (HasCallStack, Parse a) => TLB.Builder -> a+parseUnsafeTLB = parseUnsafe+{-# INLINE parseUnsafeTLB #-}++-- | Parse from a 'ST.ShortText' or raise an exception+--+-- @since 1.4.0.0+parseUnsafeST :: (HasCallStack, Parse a) => ST.ShortText -> a+parseUnsafeST = parseUnsafe+{-# INLINE parseUnsafeST #-}++-- | Parse from a strict 'BS.ByteString' or raise an exception+--+-- @since 0.1.0.0+parseUnsafeBS :: (HasCallStack, Parse a) => BS.ByteString -> a+parseUnsafeBS = parseUnsafe+{-# INLINE parseUnsafeBS #-}++-- | Parse from a lazy 'BSL.ByteString' or raise an exception+--+-- @since 0.1.0.0+parseUnsafeBSL :: (HasCallStack, Parse a) => BSL.ByteString -> a+parseUnsafeBSL = parseUnsafe+{-# INLINE parseUnsafeBSL #-}++-- | Parse from a @ByteString@ 'BSB.Builder' or raise an exception+--+-- @since 1.1.0.0+parseUnsafeBSB :: (HasCallStack, Parse a) => BSB.Builder -> a+parseUnsafeBSB = parseUnsafe+{-# INLINE parseUnsafeBSB #-}++-- | Parse from a 'SBS.ShortByteString' or raise an exception+--+-- @since 1.1.0.0+parseUnsafeSBS :: (HasCallStack, Parse a) => SBS.ShortByteString -> a+parseUnsafeSBS = parseUnsafe+{-# INLINE parseUnsafeSBS #-}++------------------------------------------------------------------------------+-- $ReadSInstances++-- | Implement 'ReadS' using a 'Parse' instance+--+-- This implementation expects all of the input to be consumed.+--+-- @since 0.3.0.0+readsWithParse+ :: Parse a+ => ReadS a+readsWithParse s = case parseMaybe s of+ Just v -> [(v, "")]+ Nothing -> []+{-# INLINEABLE readsWithParse #-}++-- | Implement 'ReadS' using 'parseEnum'+--+-- This implementation expects all of the input to be consumed.+--+-- @since 0.1.0.0+readsEnum+ :: (Bounded a, Enum a, Render a)+ => Bool -- ^ case-insensitive when 'True'+ -> Bool -- ^ accept unique prefixes when 'True'+ -> ReadS a+readsEnum allowCI allowPrefix s =+ case parseEnum allowCI allowPrefix () () s of+ Right v -> [(v, "")]+ Left{} -> []+{-# INLINEABLE readsEnum #-}++------------------------------------------------------------------------------+-- $ConstantValidation+--+-- The follow functions provide a number of ways to use a 'Parse' instance to+-- validate constants at compile-time.+--+-- If you can use Template Haskell typed expressions in your project, use+-- 'valid', 'mkValid', or 'validOf'. Use 'valid' to define constants for+-- types that have a 'THS.Lift' instance. For types that do not have a+-- 'THS.Lift' instance, use 'mkValid' to define a validation function for that+-- type using a 'Proxy', or use 'validOf' to pass the 'Proxy' when defining+-- constants.+--+-- Typed expressions were not supported in @haskell-src-exts <1.22.0@, which+-- causes problems with old versions of @hlint@. If the issue affects you,+-- you may use 'mkUntypedValid', 'mkUntypedValidQQ', or 'untypedValidOf'+-- instead of the above functions. Use 'mkUntypedValid' to define a+-- validation function for a type using a 'Proxy', or use 'untypedValidOf' to+-- pass the 'Proxy' when defining constants. Alternatively, use+-- 'mkUntypedValidQQ' to define a validation quasi-quoter.+--+-- For more details, see the following article:+-- <https://www.extrema.is/articles/ttc-textual-type-classes/validated-constants>++-- | Validate a constant at compile-time using a 'Parse' instance+--+-- This function parses the 'String' at compile-time and fails compilation on+-- error. When valid, the result is compiled in, so the result type must have+-- a 'THS.Lift' instance. When this is inconvenient, use one of the+-- alternative functions in this library.+--+-- This function uses a Template Haskell typed expression. Typed expressions+-- were not supported in @haskell-src-exts <1.22.0@, which causes problems+-- with old versions of @hlint@. If the issue affects you, use+-- @hlint -i "Parse error"@ to ignore parse errors or use one of the+-- alternative functions in this library.+--+-- Note that the typed Template Haskell API changed in GHC 9. The type+-- displayed in this documentation is determined by the version of GHC used to+-- build the documentation.+--+-- The type of this function in GHC 9 or later is as follows:+--+-- @+-- valid+-- :: (MonadFail m, THS.Quote m, Parse a, THS.Lift a)+-- => String+-- -> THS.Code m a+-- @+--+-- The type of this function in previous versions of GHC is as follows:+--+-- @+-- valid+-- :: (Parse a, THS.Lift a)+-- => String+-- -> TH.Q (TH.TExp a)+-- @+--+-- This function is used the same way in all GHC versions. See the @valid@+-- and @invalid@ example programs in the @ttc-examples@ directory of the+-- source repository. The following is example usage from the @valid@+-- example:+--+-- @+-- sample :: Username+-- sample = $$(TTC.valid "tcard")+-- @+--+-- @since 0.1.0.0+#if __GLASGOW_HASKELL__ >= 900+valid+ :: (MonadFail m, THS.Quote m, Parse a, THS.Lift a)+ => String+ -> THS.Code m a+valid s = case parse s of+ Right x -> [|| x ||]+ Left err -> THS.Code . fail $ "Invalid constant: " ++ err+#else+valid+ :: (Parse a, THS.Lift a)+ => String+ -> TH.Q (TH.TExp a)+valid s = case parse s of+ Right x -> [|| x ||]+ Left err -> fail $ "Invalid constant: " ++ err+#endif++-- | This instance enables use of 'valid' without having to type @valid@. The+-- [OverloadedStrings](https://ghc.gitlab.haskell.org/ghc/doc/users_guide/exts/overloaded_strings.html)+-- extension must be enabled in the module where this functionality is used.+-- Note that this reduces the number of characters in the code, but it can+-- also make the code more difficult to understand by somebody who is not+-- already familiar with it. Typing @valid@ gives people a way to investigate+-- and understand what is going on.+--+-- Note that the typed Template Haskell API changed in GHC 9. The type+-- displayed in this documentation is determined by the version of GHC used to+-- build the documentation.+--+-- The type of this instance in GHC 9 or later is as follows:+--+-- @+-- (MonadFail m, THS.Quote m, Parse a, THS.Lift a) => IsString (THS.Code m a)+-- @+--+-- The type of this instance in previous versions of GHC is as follows:+--+-- @+-- (Parse a, THS.Lift a) => IsString (TH.Q (TH.TExp a))+-- @+--+-- This functionality can be used as follows in all supported versions of GHC.+-- The following is example usage from the @valid@ example:+--+-- @+-- sample2 :: Username+-- sample2 = $$("alice")+-- @+--+-- The parenthesis are not required from GHC 9. The following is example+-- usage from the @valid@ example:+--+-- @+-- sample2 :: Username+-- sample2 = $$"alice"+-- @+--+-- @since 1.3.0.0+#if __GLASGOW_HASKELL__ >= 900+instance (MonadFail m, THS.Quote m, Parse a, THS.Lift a)+ => IsString (THS.Code m a) where+ fromString = valid+#else+instance (Parse a, THS.Lift a) => IsString (TH.Q (TH.TExp a)) where+ fromString = valid+#endif++-- | Validate a constant at compile-time using a 'Parse' instance+--+-- This function requires a 'Proxy' of the result type. Use 'mkValid' to+-- avoid having to pass a 'Proxy' during constant definition.+--+-- This function parses the 'String' at compile-time and fails compilation on+-- error. When valid, the 'String' is compiled in, to be parsed again at+-- run-time. Since the result is not compiled in, no 'THS.Lift' instance is+-- required.+--+-- This function uses a Template Haskell typed expression. Typed expressions+-- were not supported in @haskell-src-exts <1.22.0@, which causes problems+-- with old versions of @hlint@. If the issue affects you, use+-- @hlint -i "Parse error"@ to ignore parse errors or use 'untypedValidOf'+-- instead.+--+-- Note that the typed Template Haskell API changed in GHC 9. The type+-- displayed in this documentation is determined by the version of GHC used to+-- build the documentation.+--+-- The type of this function in GHC 9 or later is as follows:+--+-- @+-- validOf+-- :: (MonadFail m, THS.Quote m, Parse a)+-- => Proxy a+-- -> String+-- -> THS.Code m a+-- @+--+-- The type of this function in previous versions of GHC is as follows:+--+-- @+-- validOf+-- :: Parse a+-- => Proxy a+-- -> String+-- -> TH.Q (TH.TExp a)+-- @+--+-- This function is used the same way in all GHC versions. See the @validof@+-- example program in the @ttc-examples@ directory of the source repository.+-- The following is example usage from the @validof@ example:+--+-- @+-- sample :: Username+-- sample = $$(TTC.validOf (Proxy :: Proxy Username) "tcard")+-- @+--+-- @since 0.1.0.0+#if __GLASGOW_HASKELL__ >= 900+validOf+ :: (MonadFail m, THS.Quote m, Parse a)+ => Proxy a+ -> String+ -> THS.Code m a+validOf proxy s = case (`asProxyTypeOf` proxy) <$> parse s of+ Right{} -> [|| parseUnsafeS s ||]+ Left err -> THS.Code . fail $ "Invalid constant: " ++ err+#else+validOf+ :: Parse a+ => Proxy a+ -> String+ -> TH.Q (TH.TExp a)+validOf proxy s = case (`asProxyTypeOf` proxy) <$> parse s of+ Right{} -> [|| parseUnsafeS s ||]+ Left err -> fail $ "Invalid constant: " ++ err+#endif++-- | Make a @valid@ function using 'validOf' for the given type+--+-- Create a @valid@ function for a type in order to avoid having to write a+-- 'Proxy' when defining constants.+--+-- This function uses a Template Haskell typed expression. Typed expressions+-- were not supported in @haskell-src-exts <1.22.0@, which causes problems+-- with old versions of @hlint@. If the issue affects you, use+-- @hlint -i "Parse error"@ to ignore parse errors or use 'mkUntypedValid'+-- instead.+--+-- Note that the typed Template Haskell API changed in GHC 9. The type+-- displayed in this documentation is determined by the version of GHC used to+-- build the documentation.+--+-- The type of the created @valid@ function in GHC 9 or later is as follows:+--+-- @+-- \$funName+-- :: forall m. (MonadFail m, THS.Quote m)+-- => String+-- -> THS.Code m $resultType+-- @+--+-- The type of the created @valid@ function in previous versions of GHC is as+-- follows:+--+-- @+-- \$funName+-- :: String+-- -> TH.Q (TH.TExp $resultType)+-- @+--+-- This function is used the same way in all GHC versions. See the @mkvalid@+-- example program in the @ttc-examples@ directory of the source repository.+-- The following is example usage from the @mkvalid@ example:+--+-- @+-- \$(TTC.mkValid "valid" ''Username)+-- @+--+-- The created @valid@ function can then be used as follows:+--+-- @+-- sample :: Username+-- sample = $$(Username.valid "tcard")+-- @+--+-- @since 0.1.0.0+mkValid+ :: String+ -> TH.Name+ -> TH.DecsQ+mkValid funName typeName = do+ let funName' = TH.mkName funName+ resultType = pure $ TH.ConT typeName+#if __GLASGOW_HASKELL__ >= 900+ funType <-+ [t|+ forall m . (MonadFail m, THS.Quote m) =>+ String -> THS.Code m $resultType+ |]+#else+ funType <- [t| String -> TH.Q (TH.TExp $resultType) |]+#endif+ body <- [| validOf (Proxy :: Proxy $resultType) |]+ return+ [ TH.SigD funName' funType+ , TH.FunD funName' [TH.Clause [] (TH.NormalB body) []]+ ]++-- | Validate a constant at compile-time using a 'Parse' instance+--+-- This function requires a 'Proxy' of the result type. Use 'mkUntypedValid'+-- to avoid having to pass a 'Proxy' during constant definition.+--+-- This function parses the 'String' at compile-time and fails compilation on+-- error. When valid, the 'String' is compiled in, to be parsed again at+-- run-time. Since the result is not compiled in, no 'THS.Lift' instance is+-- required.+--+-- See the @uvalidof@ example program in the @ttc-examples@ directory of the+-- source repository. The following is example usage from the @uvalidof@+-- example:+--+-- @+-- sample :: Username+-- sample = $(TTC.untypedValidOf (Proxy :: Proxy Username) "tcard")+-- @+--+-- @since 0.2.0.0+untypedValidOf+ :: Parse a+ => Proxy a+ -> String+ -> TH.ExpQ+untypedValidOf proxy s = case (`asProxyTypeOf` proxy) <$> parse s of+ Right{} -> [| parseUnsafeS s |]+ Left err -> fail $ "Invalid constant: " ++ err++-- | Make a @valid@ function using 'untypedValidOf' for the given type+--+-- Create a @valid@ function for a type in order to avoid having to write a+-- 'Proxy' when defining constants.+--+-- See the @mkuvalid@ example program in the @ttc-examples@ directory of the+-- source repository. The following is example usage from the @mkuvalid@+-- example:+--+-- @+-- \$(TTC.mkUntypedValid "valid" ''Username)+-- @+--+-- The created @valid@ function can then be used as follows:+--+-- @+-- sample :: Username+-- sample = $(Username.valid "tcard")+-- @+--+-- @since 0.2.0.0+mkUntypedValid+ :: String+ -> TH.Name+ -> TH.DecsQ+mkUntypedValid funName typeName = do+ let funName' = TH.mkName funName+ resultType = pure $ TH.ConT typeName+ funType <- [t| String -> TH.ExpQ |]+ body <- [| untypedValidOf (Proxy :: Proxy $resultType) |]+ return+ [ TH.SigD funName' funType+ , TH.FunD funName' [TH.Clause [] (TH.NormalB body) []]+ ]++-- | Make a @valid@ quasi-quoter using 'untypedValidOf' for the given type+--+-- See the @mkuvalidqq@ example program in the @ttc-examples@ directory of the+-- source repository. The following is example usage from the @mkuvalidqq@+-- example:+--+-- @+-- \$(TTC.mkUntypedValidQQ "valid" ''Username)+-- @+--+-- The created @valid@ function can then be used as follows:+--+-- @+-- sample :: Username+-- sample = [Username.valid|tcard|]+-- @+--+-- @since 0.2.0.0+mkUntypedValidQQ+ :: String+ -> TH.Name+ -> TH.DecsQ+mkUntypedValidQQ funName typeName = do+ let funName' = TH.mkName funName+ resultType = pure $ TH.ConT typeName+ expE <- [| untypedValidOf (Proxy :: Proxy $resultType) |]+ expP <- [| error "pattern not supported" |]+ expT <- [| error "type not supported" |]+ expD <- [| error "declaration not supported" |]+ let body = TH.NormalB $ TH.RecConE 'Q.QuasiQuoter+ [ ('Q.quoteExp, expE)+ , ('Q.quotePat, expP)+ , ('Q.quoteType, expT)+ , ('Q.quoteDec, expD)+ ]+ return+ [ TH.SigD funName' $ TH.ConT ''Q.QuasiQuoter+ , TH.FunD funName' [TH.Clause [] body []]+ ]++------------------------------------------------------------------------------+-- $DefaultInstances+--+-- These Template Haskell functions provide an easy way to load default+-- 'Render' and 'Parse' instances for common types. See the documentation for+-- 'Render' and 'Parse' for details about default instances. Remember that+-- loading such default instances should be avoided in libraries.++-- | Load the default 'Render' instance for a type+--+-- Example:+--+-- @+-- TTC.defaultRenderInstance ''Int+-- @+--+-- @since 1.5.0.0+defaultRenderInstance :: TH.Name -> TH.DecsQ+defaultRenderInstance typeName =+ let a = pure $ TH.ConT typeName+ in [d| instance Render $a |]++-- | Load the default 'Render' instances for any number of types+--+-- Example:+--+-- @+-- TTC.defaultRenderInstances [''Int, ''Int8, ''Int16, ''Int32, ''Int64]+-- @+--+-- @since 1.5.0.0+defaultRenderInstances :: [TH.Name] -> TH.DecsQ+defaultRenderInstances = fmap concat . mapM defaultRenderInstance++-- | Load the default 'Parse' instance for a type+--+-- Example:+--+-- @+-- TTC.defaultParseInstance ''Int+-- @+--+-- @since 1.5.0.0+defaultParseInstance :: TH.Name -> TH.DecsQ+defaultParseInstance typeName =+ let a = pure $ TH.ConT typeName+ in [d| instance Parse $a |]++-- | Load the default 'Parse' instances for any number of types+--+-- Example:+--+-- @+-- TTC.defaultParseInstances [''Int, ''Int8, ''Int16, ''Int32, ''Int64]+-- @+--+-- @since 1.5.0.0+defaultParseInstances :: [TH.Name] -> TH.DecsQ+defaultParseInstances = fmap concat . mapM defaultParseInstance++-- | Load the default 'Render' and 'Parse' instance for a type+--+-- Example:+--+-- @+-- TTC.defaultRenderAndParseInstance ''Int+-- @+--+-- @since 1.5.0.0+defaultRenderAndParseInstance :: TH.Name -> TH.DecsQ+defaultRenderAndParseInstance typeName = do+ -- NOTE This function is implemented this way for compatibility with old+ -- versions of GHC/base.+ renderDecs <- defaultRenderInstance typeName+ parseDecs <- defaultParseInstance typeName+ pure $ renderDecs ++ parseDecs++-- | Load the default 'Render' and 'Parse' instances for any number of types+--+-- Example:+--+-- @+-- TTC.defaultRenderAndParseInstances+-- [''Int, ''Int8, ''Int16, ''Int32, ''Int64]+-- @+--+-- @since 1.5.0.0+defaultRenderAndParseInstances :: [TH.Name] -> TH.DecsQ+defaultRenderAndParseInstances =+ fmap concat . mapM defaultRenderAndParseInstance
+ src/Data/TTC/Wrapper.hs view
@@ -0,0 +1,163 @@+------------------------------------------------------------------------------+-- |+-- Module : Data.TTC.Wrapper+-- Description : TTC wrapper types+-- Copyright : Copyright (c) 2019-2025 Travis Cardwell+-- License : MIT+--+-- This module defines a @newtype@ wrapper for each 'TTC.Textual' data type.+-- Each wrapper has a 'TTC.Render' instance that converts the wrapped type and+-- a 'TTC.Parse' instance that wraps the argument (never failing). Data types+-- that are coercible to a 'TTC.Textual' data type can use these wrappers to+-- derive instances using @DerivingVia@.+--+-- See the @wrapper@ example program in the @ttc-examples@ directory of the+-- source repository.+------------------------------------------------------------------------------++module Data.TTC.Wrapper+ ( -- * Wrapper Types+ WrapperS(..)+ , WrapperT(..)+ , WrapperTL(..)+ , WrapperTLB(..)+ , WrapperST(..)+ , WrapperBS(..)+ , WrapperBSL(..)+ , WrapperBSB(..)+ , WrapperSBS(..)+ ) where++-- https://hackage.haskell.org/package/bytestring+import qualified Data.ByteString as BS+import qualified Data.ByteString.Builder as BSB+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Short as SBS++-- https://hackage.haskell.org/package/text+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB++-- https://hackage.haskell.org/package/text-short+import qualified Data.Text.Short as ST++-- (ttc)+import qualified Data.TTC as TTC++------------------------------------------------------------------------------++-- | 'String' wrapper type+--+-- @since 1.5.0.0+newtype WrapperS = WrapperS { unWrapperS :: String }++instance TTC.Parse WrapperS where+ parse = TTC.asS $ pure . WrapperS++instance TTC.Render WrapperS where+ render = TTC.fromS . unWrapperS++------------------------------------------------------------------------------++-- | Strict 'T.Text' wrapper type+--+-- @since 1.5.0.0+newtype WrapperT = WrapperT { unWrapperT :: T.Text }++instance TTC.Parse WrapperT where+ parse = TTC.asT $ pure . WrapperT++instance TTC.Render WrapperT where+ render = TTC.fromT . unWrapperT++------------------------------------------------------------------------------++-- | Lazy 'TL.Text' wrapper type+--+-- @since 1.5.0.0+newtype WrapperTL = WrapperTL { unWrapperTL :: TL.Text }++instance TTC.Parse WrapperTL where+ parse = TTC.asTL $ pure . WrapperTL++instance TTC.Render WrapperTL where+ render = TTC.fromTL . unWrapperTL++------------------------------------------------------------------------------++-- | Text 'TLB.Builder' wrapper type+--+-- @since 1.5.0.0+newtype WrapperTLB = WrapperTLB { unWrapperTLB :: TLB.Builder }++instance TTC.Parse WrapperTLB where+ parse = TTC.asTLB $ pure . WrapperTLB++instance TTC.Render WrapperTLB where+ render = TTC.fromTLB . unWrapperTLB++------------------------------------------------------------------------------++-- | 'ST.ShortText' wrapper type+--+-- @since 1.5.0.0+newtype WrapperST = WrapperST { unWrapperST :: ST.ShortText }++instance TTC.Parse WrapperST where+ parse = TTC.asST $ pure . WrapperST++instance TTC.Render WrapperST where+ render = TTC.fromST . unWrapperST++------------------------------------------------------------------------------++-- | Strict 'BS.ByteString' wrapper type+--+-- @since 1.5.0.0+newtype WrapperBS = WrapperBS { unWrapperBS :: BS.ByteString }++instance TTC.Parse WrapperBS where+ parse = TTC.asBS $ pure . WrapperBS++instance TTC.Render WrapperBS where+ render = TTC.fromBS . unWrapperBS++------------------------------------------------------------------------------++-- | Lazy 'BSL.ByteString' wrapper type+--+-- @since 1.5.0.0+newtype WrapperBSL = WrapperBSL { unWrapperBSL :: BSL.ByteString }++instance TTC.Parse WrapperBSL where+ parse = TTC.asBSL $ pure . WrapperBSL++instance TTC.Render WrapperBSL where+ render = TTC.fromBSL . unWrapperBSL++------------------------------------------------------------------------------++-- | ByteString 'BSB.Builder' wrapper type+--+-- @since 1.5.0.0+newtype WrapperBSB = WrapperBSB { unWrapperBSB :: BSB.Builder }++instance TTC.Parse WrapperBSB where+ parse = TTC.asBSB $ pure . WrapperBSB++instance TTC.Render WrapperBSB where+ render = TTC.fromBSB . unWrapperBSB++------------------------------------------------------------------------------++-- | 'SBS.ShortByteString' wrapper type+--+-- @since 1.5.0.0+newtype WrapperSBS = WrapperSBS { unWrapperSBS :: SBS.ShortByteString }++instance TTC.Parse WrapperSBS where+ parse = TTC.asSBS $ pure . WrapperSBS++instance TTC.Render WrapperSBS where+ render = TTC.fromSBS . unWrapperSBS
test/Data/TTC/Test.hs view
@@ -1,1461 +1,994 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE TemplateHaskell #-}--{-# OPTIONS_GHC -fno-warn-orphans #-}--module Data.TTC.Test (tests) where---- https://hackage.haskell.org/package/base-import Control.Exception (ErrorCall, Exception, evaluate, handle)-import Control.Monad (when)-import Data.Int (Int16, Int32, Int64, Int8)-import Data.Proxy (Proxy(Proxy), asProxyTypeOf)-import Data.Word (Word16, Word32, Word64, Word8)-import GHC.Stack (HasCallStack)-import Text.Read (readMaybe)---- https://hackage.haskell.org/package/bytestring-import qualified Data.ByteString as BS-import qualified Data.ByteString.Builder as BSB-import qualified Data.ByteString.Lazy as BSL-import qualified Data.ByteString.Short as SBS---- https://hackage.haskell.org/package/tasty-import Test.Tasty (TestTree, testGroup)---- https://hackage.haskell.org/package/tasty-hunit-import Test.Tasty.HUnit ((@=?), Assertion, assertFailure, testCase)---- https://hackage.haskell.org/package/text-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.Builder as TLB---- https://hackage.haskell.org/package/text-short-import qualified Data.Text.Short as ST---- (ttc)-import qualified Data.TTC as TTC---- (ttc:test)-import qualified TestString-import TestString (TestString(TestString))----------------------------------------------------------------------------------- $Instances--instance TTC.Render Char-instance TTC.Parse Char--instance TTC.Render Double-instance TTC.Parse Double--instance TTC.Render Float-instance TTC.Parse Float--instance TTC.Render Int-instance TTC.Parse Int--instance TTC.Render Int8-instance TTC.Parse Int8--instance TTC.Render Int16-instance TTC.Parse Int16--instance TTC.Render Int32-instance TTC.Parse Int32--instance TTC.Render Int64-instance TTC.Parse Int64--instance TTC.Render Integer-instance TTC.Parse Integer--instance TTC.Render Word-instance TTC.Parse Word--instance TTC.Render Word8-instance TTC.Parse Word8--instance TTC.Render Word16-instance TTC.Parse Word16--instance TTC.Render Word32-instance TTC.Parse Word32--instance TTC.Render Word64-instance TTC.Parse Word64--instance TTC.Render String-instance TTC.Parse String--instance TTC.Render BSL.ByteString-instance TTC.Parse BSL.ByteString--instance TTC.Render BS.ByteString-instance TTC.Parse BS.ByteString--instance TTC.Render TL.Text-instance TTC.Parse TL.Text--instance TTC.Render T.Text-instance TTC.Parse T.Text----------------------------------------------------------------------------------instance Eq BSB.Builder where- x == y = BSB.toLazyByteString x == BSB.toLazyByteString y--#if !MIN_VERSION_bytestring(0,11,1)-instance Show BSB.Builder where- show = show . BSB.toLazyByteString-#endif----------------------------------------------------------------------------------- $HelperFunctions--assertRaises- :: (HasCallStack, Exception e, Show a)- => Proxy e- -> a- -> Assertion-assertRaises proxy x =- handle- (const (return ()) . (`asProxyTypeOf` proxy))- (assertFailure . ("expected exception; got: " ++) . show =<< evaluate x)----------------------------------------------------------------------------------- $TestData--xS :: String-xS = "test テスト"--xT :: T.Text-xT = "test テスト"--xTL :: TL.Text-xTL = "test テスト"--xTLB :: TLB.Builder-xTLB = "test テスト"--xST :: ST.ShortText-xST = "test テスト"--xBS :: BS.ByteString-xBS = "test \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"--xBSL :: BSL.ByteString-xBSL = "test \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"--xBSB :: BSB.Builder-xBSB = "test テスト"--xSBS :: SBS.ShortByteString-xSBS = "test \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"--xiS :: String-xiS = "test \xfffd"--xiT :: T.Text-xiT = "test \xfffd"--xiTL :: TL.Text-xiTL = "test \xfffd"--xiTLB :: TLB.Builder-xiTLB = "test \xfffd"--xiST :: ST.ShortText-xiST = "test \xfffd"--xiBS :: BS.ByteString-xiBS = "test \xe3"--xiBSL :: BSL.ByteString-xiBSL = "test \xe3"--xiBSB :: BSB.Builder-xiBSB = "test \xfffd"--xiSBS :: SBS.ShortByteString-xiSBS = "test \xfffd"----------------------------------------------------------------------------------newtype PosInt = PosInt Int- deriving Eq--instance Show PosInt where- show (PosInt i) = "(PosInt " ++ show i ++ ")"--instance TTC.Parse PosInt where- parse = TTC.asS $ \ s -> case readMaybe s of- Just i- | i >= 0 -> Right $ PosInt i- | otherwise -> Left $ TTC.fromS "not positive"- Nothing -> Left $ TTC.fromS "not an integer"--instance TTC.Render PosInt where- render (PosInt i) = TTC.convert $ show i--answer :: PosInt-answer = PosInt 42--answerS :: String-answerS = "42"--answerT :: T.Text-answerT = "42"--answerTL :: TL.Text-answerTL = "42"--answerTLB :: TLB.Builder-answerTLB = "42"--answerST :: ST.ShortText-answerST = "42"--answerBS :: BS.ByteString-answerBS = "42"--answerBSL :: BSL.ByteString-answerBSL = "42"--answerBSB :: BSB.Builder-answerBSB = "42"--answerSBS :: SBS.ShortByteString-answerSBS = "42"--answerZ :: Int-answerZ = 42--data IntError = IntInvalid- deriving (Eq, Show)----------------------------------------------------------------------------------data Color- = Red- | Green- | Blue- | White- | Black- deriving (Bounded, Enum, Eq, Show)--instance Read Color where- readsPrec _ = TTC.readsEnum True True--instance TTC.Render Color where- render Red = TTC.fromT "red"- render Green = TTC.fromT "green"- render Blue = TTC.fromT "blue"- render White = TTC.fromT "white"- render Black = TTC.fromT "black"--data ColorError- = ColorInvalid- | ColorAmbiguous- deriving (Eq, Show)--redS :: String-redS = "red"--redT :: T.Text-redT = "red"--redTL :: TL.Text-redTL = "red"--redTLB :: TLB.Builder-redTLB = "red"--redST :: ST.ShortText-redST = "red"--redBS :: BS.ByteString-redBS = "red"--redBSL :: BSL.ByteString-redBSL = "red"--redBSB :: BSB.Builder-redBSB = "red"--redSBS :: SBS.ShortByteString-redSBS = "red"----------------------------------------------------------------------------------newtype PartialParser = PartialParser String- deriving (Eq, Show)--instance TTC.Parse PartialParser where- parse = TTC.asS $ \s -> do- when (null s) $ Left (TTC.fromT undefined)- pure $ PartialParser s----------------------------------------------------------------------------------- $Textual--testToS :: TestTree-testToS = testGroup "toS"- [ testCase "S" $ xS @=? TTC.toS xS- , testCase "T" $ xS @=? TTC.toS xT- , testCase "TL" $ xS @=? TTC.toS xTL- , testCase "TLB" $ xS @=? TTC.toS xTLB- , testCase "ST" $ xS @=? TTC.toS xST- , testCase "BS" $ xS @=? TTC.toS xBS- , testCase "BS/invalid" $ xiS @=? TTC.toS xiBS- , testCase "BSL" $ xS @=? TTC.toS xBSL- , testCase "BSL/invalid" $ xiS @=? TTC.toS xiBSL- , testCase "BSB" $ xS @=? TTC.toS xBSB- , testCase "BSB/invalid" $ xiS @=? TTC.toS xiBSB- , testCase "SBS" $ xS @=? TTC.toS xSBS- , testCase "SBS/invalid" $ xiS @=? TTC.toS xiSBS- ]--testToT :: TestTree-testToT = testGroup "toT"- [ testCase "S" $ xT @=? TTC.toT xS- , testCase "T" $ xT @=? TTC.toT xT- , testCase "TL" $ xT @=? TTC.toT xTL- , testCase "TLB" $ xT @=? TTC.toT xTLB- , testCase "ST" $ xT @=? TTC.toT xST- , testCase "BS" $ xT @=? TTC.toT xBS- , testCase "BS/invalid" $ xiT @=? TTC.toT xiBS- , testCase "BSL" $ xT @=? TTC.toT xBSL- , testCase "BSL/invalid" $ xiT @=? TTC.toT xiBSL- , testCase "BSB" $ xT @=? TTC.toT xBSB- , testCase "BSB/invalid" $ xiT @=? TTC.toT xiBSB- , testCase "SBS" $ xT @=? TTC.toT xSBS- , testCase "SBS/invalid" $ xiT @=? TTC.toT xiSBS- ]--testToTL :: TestTree-testToTL = testGroup "toTL"- [ testCase "S" $ xTL @=? TTC.toTL xS- , testCase "T" $ xTL @=? TTC.toTL xT- , testCase "TL" $ xTL @=? TTC.toTL xTL- , testCase "TLB" $ xTL @=? TTC.toTL xTLB- , testCase "ST" $ xTL @=? TTC.toTL xST- , testCase "BS" $ xTL @=? TTC.toTL xBS- , testCase "BS/invalid" $ xiTL @=? TTC.toTL xiBS- , testCase "BSL" $ xTL @=? TTC.toTL xBSL- , testCase "BSL/invalid" $ xiTL @=? TTC.toTL xiBSL- , testCase "BSB" $ xTL @=? TTC.toTL xBSB- , testCase "BSB/invalid" $ xiTL @=? TTC.toTL xiBSB- , testCase "SBS" $ xTL @=? TTC.toTL xSBS- , testCase "SBS/invalid" $ xiTL @=? TTC.toTL xiSBS- ]--testToTLB :: TestTree-testToTLB = testGroup "toTLB"- [ testCase "S" $ xTLB @=? TTC.toTLB xS- , testCase "T" $ xTLB @=? TTC.toTLB xT- , testCase "TL" $ xTLB @=? TTC.toTLB xTL- , testCase "TLB" $ xTLB @=? TTC.toTLB xTLB- , testCase "ST" $ xTLB @=? TTC.toTLB xST- , testCase "BS" $ xTLB @=? TTC.toTLB xBS- , testCase "BS/invalid" $ xiTLB @=? TTC.toTLB xiBS- , testCase "BSL" $ xTLB @=? TTC.toTLB xBSL- , testCase "BSL/invalid" $ xiTLB @=? TTC.toTLB xiBSL- , testCase "BSB" $ xTLB @=? TTC.toTLB xBSB- , testCase "BSB/invalid" $ xiTLB @=? TTC.toTLB xiBSB- , testCase "SBS" $ xTLB @=? TTC.toTLB xSBS- , testCase "SBS/invalid" $ xiTLB @=? TTC.toTLB xiSBS- ]--testToST :: TestTree-testToST = testGroup "toST"- [ testCase "S" $ xST @=? TTC.toST xS- , testCase "T" $ xST @=? TTC.toST xT- , testCase "TL" $ xST @=? TTC.toST xTL- , testCase "TLB" $ xST @=? TTC.toST xTLB- , testCase "ST" $ xST @=? TTC.toST xST- , testCase "BS" $ xST @=? TTC.toST xBS- , testCase "BS/invalid" $ xiST @=? TTC.toST xiBS- , testCase "BSL" $ xST @=? TTC.toST xBSL- , testCase "BSL/invalid" $ xiST @=? TTC.toST xiBSL- , testCase "BSB" $ xST @=? TTC.toST xBSB- , testCase "BSB/invalid" $ xiST @=? TTC.toST xiBSB- , testCase "SBS" $ xST @=? TTC.toST xSBS- , testCase "SBS/invalid" $ xiST @=? TTC.toST xiSBS- ]--testToBS :: TestTree-testToBS = testGroup "toBS"- [ testCase "S" $ xBS @=? TTC.toBS xS- , testCase "T" $ xBS @=? TTC.toBS xT- , testCase "TL" $ xBS @=? TTC.toBS xTL- , testCase "TLB" $ xBS @=? TTC.toBS xTLB- , testCase "ST" $ xBS @=? TTC.toBS xST- , testCase "BS" $ xBS @=? TTC.toBS xBS- , testCase "BSL" $ xBS @=? TTC.toBS xBSL- , testCase "BSB" $ xBS @=? TTC.toBS xBSB- , testCase "SBS" $ xBS @=? TTC.toBS xSBS- ]--testToBSL :: TestTree-testToBSL = testGroup "toBSL"- [ testCase "S" $ xBSL @=? TTC.toBSL xS- , testCase "T" $ xBSL @=? TTC.toBSL xT- , testCase "TL" $ xBSL @=? TTC.toBSL xTL- , testCase "TLB" $ xBSL @=? TTC.toBSL xTLB- , testCase "ST" $ xBSL @=? TTC.toBSL xST- , testCase "BS" $ xBSL @=? TTC.toBSL xBS- , testCase "BSL" $ xBSL @=? TTC.toBSL xBSL- , testCase "BSB" $ xBSL @=? TTC.toBSL xBSB- , testCase "SBS" $ xBSL @=? TTC.toBSL xSBS- ]--testToBSB :: TestTree-testToBSB = testGroup "toBSB"- [ testCase "S" $ xBSB @=? TTC.toBSB xS- , testCase "T" $ xBSB @=? TTC.toBSB xT- , testCase "TL" $ xBSB @=? TTC.toBSB xTL- , testCase "TLB" $ xBSB @=? TTC.toBSB xTLB- , testCase "ST" $ xBSB @=? TTC.toBSB xST- , testCase "BS" $ xBSB @=? TTC.toBSB xBS- , testCase "BSL" $ xBSB @=? TTC.toBSB xBSL- , testCase "BSB" $ xBSB @=? TTC.toBSB xBSB- , testCase "SBS" $ xBSB @=? TTC.toBSB xSBS- ]--testToSBS :: TestTree-testToSBS = testGroup "toSBS"- [ testCase "S" $ xSBS @=? TTC.toSBS xS- , testCase "T" $ xSBS @=? TTC.toSBS xT- , testCase "TL" $ xSBS @=? TTC.toSBS xTL- , testCase "TLB" $ xSBS @=? TTC.toSBS xTLB- , testCase "ST" $ xSBS @=? TTC.toSBS xST- , testCase "BS" $ xSBS @=? TTC.toSBS xBS- , testCase "BSL" $ xSBS @=? TTC.toSBS xBSL- , testCase "BSB" $ xSBS @=? TTC.toSBS xBSB- , testCase "SBS" $ xSBS @=? TTC.toSBS xSBS- ]--testConvert :: TestTree-testConvert = testGroup "convert"- [ testCase "S->S" $ xS @=? TTC.convert xS- , testCase "S->T" $ xT @=? TTC.convert xS- , testCase "S->TL" $ xTL @=? TTC.convert xS- , testCase "S->TLB" $ xTLB @=? TTC.convert xS- , testCase "S->ST" $ xST @=? TTC.convert xS- , testCase "S->BS" $ xBS @=? TTC.convert xS- , testCase "S->BSL" $ xBSL @=? TTC.convert xS- , testCase "S->BSB" $ xBSB @=? TTC.convert xS- , testCase "S->SBS" $ xSBS @=? TTC.convert xS- , testCase "T->S" $ xS @=? TTC.convert xT- , testCase "T->T" $ xT @=? TTC.convert xT- , testCase "T->TL" $ xTL @=? TTC.convert xT- , testCase "T->TLB" $ xTLB @=? TTC.convert xT- , testCase "T->ST" $ xST @=? TTC.convert xT- , testCase "T->BS" $ xBS @=? TTC.convert xT- , testCase "T->BSL" $ xBSL @=? TTC.convert xT- , testCase "T->BSB" $ xBSB @=? TTC.convert xT- , testCase "T->SBS" $ xSBS @=? TTC.convert xT- , testCase "TL->S" $ xS @=? TTC.convert xTL- , testCase "TL->T" $ xT @=? TTC.convert xTL- , testCase "TL->TL" $ xTL @=? TTC.convert xTL- , testCase "TL->TLB" $ xTLB @=? TTC.convert xTL- , testCase "TL->ST" $ xST @=? TTC.convert xTL- , testCase "TL->BS" $ xBS @=? TTC.convert xTL- , testCase "TL->BSL" $ xBSL @=? TTC.convert xTL- , testCase "TL->BSB" $ xBSB @=? TTC.convert xTL- , testCase "TL->SBS" $ xSBS @=? TTC.convert xTL- , testCase "TLB->S" $ xS @=? TTC.convert xTLB- , testCase "TLB->T" $ xT @=? TTC.convert xTLB- , testCase "TLB->TL" $ xTL @=? TTC.convert xTLB- , testCase "TLB->TLB" $ xTLB @=? TTC.convert xTLB- , testCase "TLB->ST" $ xST @=? TTC.convert xTLB- , testCase "TLB->BS" $ xBS @=? TTC.convert xTLB- , testCase "TLB->BSL" $ xBSL @=? TTC.convert xTLB- , testCase "TLB->BSB" $ xBSB @=? TTC.convert xTLB- , testCase "TLB->SBS" $ xSBS @=? TTC.convert xTLB- , testCase "BS->S" $ xS @=? TTC.convert xBS- , testCase "BS->T" $ xT @=? TTC.convert xBS- , testCase "BS->TL" $ xTL @=? TTC.convert xBS- , testCase "BS->TLB" $ xTLB @=? TTC.convert xBS- , testCase "BS->ST" $ xST @=? TTC.convert xBS- , testCase "BS->BS" $ xBS @=? TTC.convert xBS- , testCase "BS->BSL" $ xBSL @=? TTC.convert xBS- , testCase "BS->BSB" $ xBSB @=? TTC.convert xBS- , testCase "BS->SBS" $ xSBS @=? TTC.convert xBS- , testCase "BSL->S" $ xS @=? TTC.convert xBSL- , testCase "BSL->T" $ xT @=? TTC.convert xBSL- , testCase "BSL->TL" $ xTL @=? TTC.convert xBSL- , testCase "BSL->TLB" $ xTLB @=? TTC.convert xBSL- , testCase "BSL->ST" $ xST @=? TTC.convert xBSL- , testCase "BSL->BS" $ xBS @=? TTC.convert xBSL- , testCase "BSL->BSL" $ xBSL @=? TTC.convert xBSL- , testCase "BSL->BSB" $ xBSB @=? TTC.convert xBSL- , testCase "BSL->SBS" $ xSBS @=? TTC.convert xBSL- , testCase "BSB->S" $ xS @=? TTC.convert xBSB- , testCase "BSB->T" $ xT @=? TTC.convert xBSB- , testCase "BSB->TL" $ xTL @=? TTC.convert xBSB- , testCase "BSB->TLB" $ xTLB @=? TTC.convert xBSB- , testCase "BSB->ST" $ xST @=? TTC.convert xBSB- , testCase "BSB->BS" $ xBS @=? TTC.convert xBSB- , testCase "BSB->BSL" $ xBSL @=? TTC.convert xBSB- , testCase "BSB->BSB" $ xBSB @=? TTC.convert xBSB- , testCase "BSB->SBS" $ xSBS @=? TTC.convert xBSB- , testCase "SBS->S" $ xS @=? TTC.convert xSBS- , testCase "SBS->T" $ xT @=? TTC.convert xSBS- , testCase "SBS->TL" $ xTL @=? TTC.convert xSBS- , testCase "SBS->TLB" $ xTLB @=? TTC.convert xSBS- , testCase "SBS->ST" $ xST @=? TTC.convert xSBS- , testCase "SBS->BS" $ xBS @=? TTC.convert xSBS- , testCase "SBS->BSL" $ xBSL @=? TTC.convert xSBS- , testCase "SBS->BSB" $ xBSB @=? TTC.convert xSBS- , testCase "SBS->SBS" $ xSBS @=? TTC.convert xSBS- ]--testFromS :: TestTree-testFromS = testGroup "fromS"- [ testCase "S" $ xS @=? TTC.fromS xS- , testCase "T" $ xT @=? TTC.fromS xS- , testCase "TL" $ xTL @=? TTC.fromS xS- , testCase "TLB" $ xTLB @=? TTC.fromS xS- , testCase "ST" $ xST @=? TTC.fromS xS- , testCase "BS" $ xBS @=? TTC.fromS xS- , testCase "BSL" $ xBSL @=? TTC.fromS xS- , testCase "BSB" $ xBSB @=? TTC.fromS xS- , testCase "SBS" $ xSBS @=? TTC.fromS xS- ]--testFromT :: TestTree-testFromT = testGroup "fromT"- [ testCase "S" $ xS @=? TTC.fromT xT- , testCase "T" $ xT @=? TTC.fromT xT- , testCase "TL" $ xTL @=? TTC.fromT xT- , testCase "TLB" $ xTLB @=? TTC.fromT xT- , testCase "ST" $ xST @=? TTC.fromT xT- , testCase "BS" $ xBS @=? TTC.fromT xT- , testCase "BSL" $ xBSL @=? TTC.fromT xT- , testCase "BSB" $ xBSB @=? TTC.fromT xT- , testCase "SBS" $ xSBS @=? TTC.fromT xT- ]--testFromTL :: TestTree-testFromTL = testGroup "fromTL"- [ testCase "S" $ xS @=? TTC.fromTL xTL- , testCase "T" $ xT @=? TTC.fromTL xTL- , testCase "TL" $ xTL @=? TTC.fromTL xTL- , testCase "TLB" $ xTLB @=? TTC.fromTL xTL- , testCase "ST" $ xST @=? TTC.fromTL xTL- , testCase "BS" $ xBS @=? TTC.fromTL xTL- , testCase "BSL" $ xBSL @=? TTC.fromTL xTL- , testCase "BSB" $ xBSB @=? TTC.fromTL xTL- , testCase "SBS" $ xSBS @=? TTC.fromTL xTL- ]--testFromTLB :: TestTree-testFromTLB = testGroup "fromTLB"- [ testCase "S" $ xS @=? TTC.fromTLB xTLB- , testCase "T" $ xT @=? TTC.fromTLB xTLB- , testCase "TL" $ xTL @=? TTC.fromTLB xTLB- , testCase "TLB" $ xTLB @=? TTC.fromTLB xTLB- , testCase "ST" $ xST @=? TTC.fromTLB xTLB- , testCase "BS" $ xBS @=? TTC.fromTLB xTLB- , testCase "BSL" $ xBSL @=? TTC.fromTLB xTLB- , testCase "BSB" $ xBSB @=? TTC.fromTLB xTLB- , testCase "SBS" $ xSBS @=? TTC.fromTLB xTLB- ]--testFromBS :: TestTree-testFromBS = testGroup "fromBS"- [ testCase "S" $ xS @=? TTC.fromBS xBS- , testCase "T" $ xT @=? TTC.fromBS xBS- , testCase "TL" $ xTL @=? TTC.fromBS xBS- , testCase "TLB" $ xTLB @=? TTC.fromBS xBS- , testCase "ST" $ xST @=? TTC.fromBS xBS- , testCase "BS" $ xBS @=? TTC.fromBS xBS- , testCase "BSL" $ xBSL @=? TTC.fromBS xBS- , testCase "BSB" $ xBSB @=? TTC.fromBS xBS- , testCase "SBS" $ xSBS @=? TTC.fromBS xBS- ]--testFromBSL :: TestTree-testFromBSL = testGroup "fromBSL"- [ testCase "S" $ xS @=? TTC.fromBSL xBSL- , testCase "T" $ xT @=? TTC.fromBSL xBSL- , testCase "TL" $ xTL @=? TTC.fromBSL xBSL- , testCase "TLB" $ xTLB @=? TTC.fromBSL xBSL- , testCase "ST" $ xST @=? TTC.fromBSL xBSL- , testCase "BS" $ xBS @=? TTC.fromBSL xBSL- , testCase "BSL" $ xBSL @=? TTC.fromBSL xBSL- , testCase "BSB" $ xBSB @=? TTC.fromBSL xBSL- , testCase "SBS" $ xSBS @=? TTC.fromBSL xBSL- ]--testFromBSB :: TestTree-testFromBSB = testGroup "fromBSB"- [ testCase "S" $ xS @=? TTC.fromBSB xBSB- , testCase "T" $ xT @=? TTC.fromBSB xBSB- , testCase "TL" $ xTL @=? TTC.fromBSB xBSB- , testCase "TLB" $ xTLB @=? TTC.fromBSB xBSB- , testCase "ST" $ xST @=? TTC.fromBSB xBSB- , testCase "BS" $ xBS @=? TTC.fromBSB xBSB- , testCase "BSL" $ xBSL @=? TTC.fromBSB xBSB- , testCase "BSB" $ xBSB @=? TTC.fromBSB xBSB- , testCase "SBS" $ xSBS @=? TTC.fromBSB xBSB- ]--testFromSBS :: TestTree-testFromSBS = testGroup "fromSBS"- [ testCase "S" $ xS @=? TTC.fromSBS xSBS- , testCase "T" $ xT @=? TTC.fromSBS xSBS- , testCase "TL" $ xTL @=? TTC.fromSBS xSBS- , testCase "TLB" $ xTLB @=? TTC.fromSBS xSBS- , testCase "ST" $ xST @=? TTC.fromSBS xSBS- , testCase "BS" $ xBS @=? TTC.fromSBS xSBS- , testCase "BSL" $ xBSL @=? TTC.fromSBS xSBS- , testCase "BSB" $ xBSB @=? TTC.fromSBS xSBS- , testCase "SBS" $ xSBS @=? TTC.fromSBS xSBS- ]--testAsS :: TestTree-testAsS = testGroup "asS"- [ testCase "S" $ xS @=? TTC.asS id xS- , testCase "T" $ xS @=? TTC.asS id xT- , testCase "TL" $ xS @=? TTC.asS id xTL- , testCase "TLB" $ xS @=? TTC.asS id xTLB- , testCase "ST" $ xS @=? TTC.asS id xST- , testCase "BS" $ xS @=? TTC.asS id xBS- , testCase "BSL" $ xS @=? TTC.asS id xBSL- , testCase "BSB" $ xS @=? TTC.asS id xBSB- , testCase "SBS" $ xS @=? TTC.asS id xSBS- ]--testAsT :: TestTree-testAsT = testGroup "asT"- [ testCase "S" $ xT @=? TTC.asT id xS- , testCase "T" $ xT @=? TTC.asT id xT- , testCase "TL" $ xT @=? TTC.asT id xTL- , testCase "TLB" $ xT @=? TTC.asT id xTLB- , testCase "ST" $ xT @=? TTC.asT id xST- , testCase "BS" $ xT @=? TTC.asT id xBS- , testCase "BSL" $ xT @=? TTC.asT id xBSL- , testCase "BSB" $ xT @=? TTC.asT id xBSB- , testCase "SBS" $ xT @=? TTC.asT id xSBS- ]--testAsTL :: TestTree-testAsTL = testGroup "asTL"- [ testCase "S" $ xTL @=? TTC.asTL id xS- , testCase "T" $ xTL @=? TTC.asTL id xT- , testCase "TL" $ xTL @=? TTC.asTL id xTL- , testCase "TLB" $ xTL @=? TTC.asTL id xTLB- , testCase "ST" $ xTL @=? TTC.asTL id xST- , testCase "BS" $ xTL @=? TTC.asTL id xBS- , testCase "BSL" $ xTL @=? TTC.asTL id xBSL- , testCase "BSB" $ xTL @=? TTC.asTL id xBSB- , testCase "SBS" $ xTL @=? TTC.asTL id xSBS- ]--testAsTLB :: TestTree-testAsTLB = testGroup "asTLB"- [ testCase "S" $ xTLB @=? TTC.asTLB id xS- , testCase "T" $ xTLB @=? TTC.asTLB id xT- , testCase "TL" $ xTLB @=? TTC.asTLB id xTL- , testCase "TLB" $ xTLB @=? TTC.asTLB id xTLB- , testCase "ST" $ xTLB @=? TTC.asTLB id xST- , testCase "BS" $ xTLB @=? TTC.asTLB id xBS- , testCase "BSL" $ xTLB @=? TTC.asTLB id xBSL- , testCase "BSB" $ xTLB @=? TTC.asTLB id xBSB- , testCase "SBS" $ xTLB @=? TTC.asTLB id xSBS- ]--testAsBS :: TestTree-testAsBS = testGroup "asBS"- [ testCase "S" $ xBS @=? TTC.asBS id xS- , testCase "T" $ xBS @=? TTC.asBS id xT- , testCase "TL" $ xBS @=? TTC.asBS id xTL- , testCase "TLB" $ xBS @=? TTC.asBS id xTLB- , testCase "ST" $ xBS @=? TTC.asBS id xST- , testCase "BS" $ xBS @=? TTC.asBS id xBS- , testCase "BSL" $ xBS @=? TTC.asBS id xBSL- , testCase "BSB" $ xBS @=? TTC.asBS id xBSB- , testCase "SBS" $ xBS @=? TTC.asBS id xSBS- ]--testAsBSL :: TestTree-testAsBSL = testGroup "asBSL"- [ testCase "S" $ xBSL @=? TTC.asBSL id xS- , testCase "T" $ xBSL @=? TTC.asBSL id xT- , testCase "TL" $ xBSL @=? TTC.asBSL id xTL- , testCase "TLB" $ xBSL @=? TTC.asBSL id xTLB- , testCase "ST" $ xBSL @=? TTC.asBSL id xST- , testCase "BS" $ xBSL @=? TTC.asBSL id xBS- , testCase "BSL" $ xBSL @=? TTC.asBSL id xBSL- , testCase "BSB" $ xBSL @=? TTC.asBSL id xBSB- , testCase "SBS" $ xBSL @=? TTC.asBSL id xSBS- ]--testAsBSB :: TestTree-testAsBSB = testGroup "asBSB"- [ testCase "S" $ xBSB @=? TTC.asBSB id xS- , testCase "T" $ xBSB @=? TTC.asBSB id xT- , testCase "TL" $ xBSB @=? TTC.asBSB id xTL- , testCase "TLB" $ xBSB @=? TTC.asBSB id xTLB- , testCase "ST" $ xBSB @=? TTC.asBSB id xST- , testCase "BS" $ xBSB @=? TTC.asBSB id xBS- , testCase "BSL" $ xBSB @=? TTC.asBSB id xBSL- , testCase "BSB" $ xBSB @=? TTC.asBSB id xBSB- , testCase "SBS" $ xBSB @=? TTC.asBSB id xSBS- ]--testAsSBS :: TestTree-testAsSBS = testGroup "asSBS"- [ testCase "S" $ xSBS @=? TTC.asSBS id xS- , testCase "T" $ xSBS @=? TTC.asSBS id xT- , testCase "TL" $ xSBS @=? TTC.asSBS id xTL- , testCase "TLB" $ xSBS @=? TTC.asSBS id xTLB- , testCase "ST" $ xSBS @=? TTC.asSBS id xST- , testCase "BS" $ xSBS @=? TTC.asSBS id xBS- , testCase "BSL" $ xSBS @=? TTC.asSBS id xBSL- , testCase "BSB" $ xSBS @=? TTC.asSBS id xBSB- , testCase "SBS" $ xSBS @=? TTC.asSBS id xSBS- ]----------------------------------------------------------------------------------- $Render--testRender :: TestTree-testRender = testGroup "render"- [ testCase "S" $ answerS @=? TTC.render answer- , testCase "T" $ answerT @=? TTC.render answer- , testCase "TL" $ answerTL @=? TTC.render answer- , testCase "TLB" $ answerTLB @=? TTC.render answer- , testCase "ST" $ answerST @=? TTC.render answer- , testCase "BS" $ answerBS @=? TTC.render answer- , testCase "BSL" $ answerBSL @=? TTC.render answer- , testCase "BSB" $ answerBSB @=? TTC.render answer- , testCase "SBS" $ answerSBS @=? TTC.render answer- ]--testRenderDefault :: TestTree-testRenderDefault = testGroup "RenderDefault"- [ testCase "Char" $ "*" @=? TTC.renderS '*'- , let x = 3.14159 :: Double- in testCase "Double" $ show x @=? TTC.renderS x- , let x = 3.14159 :: Float- in testCase "Float" $ show x @=? TTC.renderS x- , let n = 42 :: Int- in testCase "Int" $ show n @=? TTC.renderS n- , let n = 42 :: Int8- in testCase "Int8" $ show n @=? TTC.renderS n- , let n = 42 :: Int16- in testCase "Int16" $ show n @=? TTC.renderS n- , let n = 42 :: Int32- in testCase "Int32" $ show n @=? TTC.renderS n- , let n = 42 :: Int64- in testCase "Int64" $ show n @=? TTC.renderS n- , let n = 42 :: Integer- in testCase "Integer" $ show n @=? TTC.renderS n- , let w = 42 :: Word- in testCase "Word" $ show w @=? TTC.renderS w- , let w = 42 :: Word8- in testCase "Word8" $ show w @=? TTC.renderS w- , let w = 42 :: Word16- in testCase "Word16" $ show w @=? TTC.renderS w- , let w = 42 :: Word32- in testCase "Word32" $ show w @=? TTC.renderS w- , let w = 42 :: Word64- in testCase "Word64" $ show w @=? TTC.renderS w- , testCase "String" $ xS @=? TTC.renderS xS- , testCase "BSL.ByteString" $ xS @=? TTC.renderS xBSL- , testCase "BS.ByteString" $ xS @=? TTC.renderS xBS- , testCase "TL.Text" $ xS @=? TTC.renderS xTL- , testCase "T.Text" $ xS @=? TTC.renderS xT- ]--testRenderS :: TestTree-testRenderS = testCase "renderS" $ answerS @=? TTC.renderS answer--testRenderT :: TestTree-testRenderT = testCase "renderT" $ answerT @=? TTC.renderT answer--testRenderTL :: TestTree-testRenderTL = testCase "renderTL" $ answerTL @=? TTC.renderTL answer--testRenderTLB :: TestTree-testRenderTLB = testCase "renderTLB" $ answerTLB @=? TTC.renderTLB answer--testRenderST :: TestTree-testRenderST = testCase "renderST" $ answerST @=? TTC.renderST answer--testRenderBS :: TestTree-testRenderBS = testCase "renderBS" $ answerBS @=? TTC.renderBS answer--testRenderBSL :: TestTree-testRenderBSL = testCase "renderBSL" $ answerBSL @=? TTC.renderBSL answer--testRenderBSB :: TestTree-testRenderBSB = testCase "renderBSB" $- answerBSL @=? BSB.toLazyByteString (TTC.renderBSB answer)--testRenderSBS :: TestTree-testRenderSBS = testCase "renderSBS" $ answerSBS @=? TTC.renderSBS answer--testRenderWithShow :: TestTree-testRenderWithShow = testGroup "renderWithShow"- [ testCase "S" $ answerS @=? TTC.renderWithShow answerZ- , testCase "T" $ answerT @=? TTC.renderWithShow answerZ- , testCase "TL" $ answerTL @=? TTC.renderWithShow answerZ- , testCase "TLB" $ answerTLB @=? TTC.renderWithShow answerZ- , testCase "ST" $ answerST @=? TTC.renderWithShow answerZ- , testCase "BS" $ answerBS @=? TTC.renderWithShow answerZ- , testCase "BSL" $ answerBSL @=? TTC.renderWithShow answerZ- , testCase "BSB" $ answerBSB @=? TTC.renderWithShow answerZ- , testCase "SBS" $ answerSBS @=? TTC.renderWithShow answerZ- ]----------------------------------------------------------------------------------- $Parse--testParse :: TestTree-testParse = testGroup "parse"- [ testCase "S" $ Just answer @=? TTC.parseMaybe answerS- , testCase "T" $ Just answer @=? TTC.parseMaybe answerT- , testCase "TL" $ Just answer @=? TTC.parseMaybe answerTL- , testCase "TLB" $ Just answer @=? TTC.parseMaybe answerTLB- , testCase "ST" $ Just answer @=? TTC.parseMaybe answerST- , testCase "BS" $ Just answer @=? TTC.parseMaybe answerBS- , testCase "BSL" $ Just answer @=? TTC.parseMaybe answerBSL- , testCase "BSB" $ Just answer @=? TTC.parseMaybe answerBSB- , testCase "SBS" $ Just answer @=? TTC.parseMaybe answerSBS- , testCase "negative" $ Left "not positive" @=?- (TTC.parse ('-' : answerS) :: Either String PosInt)- , testCase "invalid" $ Left "not an integer" @=?- (TTC.parse ('a' : answerS) :: Either String PosInt)- ]--testParseDefault :: TestTree-testParseDefault = testGroup "ParseDefault"- [ let parse = TTC.parse :: String -> Either String Char- in testGroup "Char"- [ testCase "OK" $ Right '*' @=? parse "*"- , testCase "empty" $ Left "invalid Char" @=? parse ""- , testCase "multiple" $ Left "invalid Char" @=? parse "**"- ]- , let parse = TTC.parse :: String -> Either String Double- s = show (3.14159 :: Double)- in testGroup "Double"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Double" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Float- s = show (3.14159 :: Float)- in testGroup "Float"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Float" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Int- s = show (42 :: Int)- in testGroup "Int"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Int" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Int8- s = show (42 :: Int8)- in testGroup "Int8"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Int8" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Int16- s = show (42 :: Int16)- in testGroup "Int16"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Int16" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Int32- s = show (42 :: Int32)- in testGroup "Int32"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Int32" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Int64- s = show (42 :: Int64)- in testGroup "Int64"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Int64" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Integer- s = show (42 :: Integer)- in testGroup "Integer"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Integer" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Word- s = show (42 :: Word)- in testGroup "Word"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Word" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Word8- s = show (42 :: Word8)- in testGroup "Word8"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Word8" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Word16- s = show (42 :: Word16)- in testGroup "Word16"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Word16" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Word32- s = show (42 :: Word32)- in testGroup "Word32"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Word32" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String Word64- s = show (42 :: Word64)- in testGroup "Word64"- [ testCase "OK" $ Right (read s) @=? parse s- , testCase "invalid" $ Left "invalid Word64" @=? parse "invalid"- ]- , let parse = TTC.parse :: String -> Either String String- in testGroup "String"- [ testCase "empty" $ Right "" @=? parse ""- , testCase "nonempty" $ Right xS @=? parse xS- ]- , let parse = TTC.parse :: String -> Either String BSL.ByteString- in testGroup "BSL.ByteString"- [ testCase "empty" $ Right BSL.empty @=? parse ""- , testCase "nonempty" $ Right xBSL @=? parse xS- ]- , let parse = TTC.parse :: String -> Either String BS.ByteString- in testGroup "BS.ByteString"- [ testCase "empty" $ Right BS.empty @=? parse ""- , testCase "nonempty" $ Right xBS @=? parse xS- ]- , let parse = TTC.parse :: String -> Either String TL.Text- in testGroup "TL.Text"- [ testCase "empty" $ Right TL.empty @=? parse ""- , testCase "nonempty" $ Right xTL @=? parse xS- ]- , let parse = TTC.parse :: String -> Either String T.Text- in testGroup "T.Text"- [ testCase "empty" $ Right T.empty @=? parse ""- , testCase "nonempty" $ Right xT @=? parse xS- ]- ]--testParseS :: TestTree-testParseS =- let parseS = TTC.parseS :: String -> Either String PosInt- in testCase "parseS" $ Right answer @=? parseS answerS--testParseT :: TestTree-testParseT =- let parseT = TTC.parseT :: T.Text -> Either String PosInt- in testCase "parseT" $ Right answer @=? parseT answerT--testParseTL :: TestTree-testParseTL =- let parseTL = TTC.parseTL :: TL.Text -> Either String PosInt- in testCase "parseTL" $ Right answer @=? parseTL answerTL--testParseTLB :: TestTree-testParseTLB =- let parseTLB = TTC.parseTLB :: TLB.Builder -> Either String PosInt- in testCase "parseTLB" $ Right answer @=? parseTLB answerTLB--testParseST :: TestTree-testParseST =- let parseST = TTC.parseST :: ST.ShortText -> Either String PosInt- in testCase "parseST" $ Right answer @=? parseST answerST--testParseBS :: TestTree-testParseBS =- let parseBS = TTC.parseBS :: BS.ByteString -> Either String PosInt- in testCase "parseBS" $ Right answer @=? parseBS answerBS--testParseBSL :: TestTree-testParseBSL =- let parseBSL = TTC.parseBSL :: BSL.ByteString -> Either String PosInt- in testCase "parseBSL" $ Right answer @=? parseBSL answerBSL--testParseBSB :: TestTree-testParseBSB =- let parseBSB = TTC.parseBSB :: BSB.Builder -> Either String PosInt- in testCase "parseBSB" $ Right answer @=? parseBSB answerBSB--testParseSBS :: TestTree-testParseSBS =- let parseSBS = TTC.parseSBS :: SBS.ShortByteString -> Either String PosInt- in testCase "parseSBS" $ Right answer @=? parseSBS answerSBS--testParseMaybe :: TestTree-testParseMaybe = testGroup "parseMaybe"- [ testCase "S" $ Just answer @=? TTC.parseMaybe answerS- , testCase "T" $ Just answer @=? TTC.parseMaybe answerT- , testCase "TL" $ Just answer @=? TTC.parseMaybe answerTL- , testCase "TLB" $ Just answer @=? TTC.parseMaybe answerTLB- , testCase "ST" $ Just answer @=? TTC.parseMaybe answerST- , testCase "BS" $ Just answer @=? TTC.parseMaybe answerBS- , testCase "BSL" $ Just answer @=? TTC.parseMaybe answerBSL- , testCase "BSB" $ Just answer @=? TTC.parseMaybe answerBSB- , testCase "SBS" $ Just answer @=? TTC.parseMaybe answerSBS- -- successful 'parseMaybe' does not have any error type conversion:- , testCase "noerror" $- Just (PartialParser "test") @=? TTC.parseMaybe ("test" :: String)- , testCase "negative" $- Nothing @=? (TTC.parseMaybe ('-' : answerS) :: Maybe PosInt)- , testCase "invalid" $- Nothing @=? (TTC.parseMaybe ('a' : answerS) :: Maybe PosInt)- ]--testParseMaybeS :: TestTree-testParseMaybeS = testCase "parseMaybeS" $- Just answer @=? TTC.parseMaybeS answerS--testParseMaybeT :: TestTree-testParseMaybeT = testCase "parseMaybeT" $- Just answer @=? TTC.parseMaybeT answerT--testParseMaybeTL :: TestTree-testParseMaybeTL = testCase "parseMaybeTL" $- Just answer @=? TTC.parseMaybeTL answerTL--testParseMaybeTLB :: TestTree-testParseMaybeTLB = testCase "parseMaybeTLB" $- Just answer @=? TTC.parseMaybeTLB answerTLB--testParseMaybeST :: TestTree-testParseMaybeST = testCase "parseMaybeST" $- Just answer @=? TTC.parseMaybeST answerST--testParseMaybeBS :: TestTree-testParseMaybeBS = testCase "parseMaybeBS" $- Just answer @=? TTC.parseMaybeBS answerBS--testParseMaybeBSL :: TestTree-testParseMaybeBSL = testCase "parseMaybeBSL" $- Just answer @=? TTC.parseMaybeBSL answerBSL--testParseMaybeBSB :: TestTree-testParseMaybeBSB = testCase "parseMaybeBSB" $- Just answer @=? TTC.parseMaybeBSB answerBSB--testParseMaybeSBS :: TestTree-testParseMaybeSBS = testCase "parseMaybeSBS" $- Just answer @=? TTC.parseMaybeSBS answerSBS--testParseOrFail :: TestTree-testParseOrFail = testGroup "parseOrFail"- [ testCase "S" $ Just answer @=? TTC.parseOrFail answerS- , testCase "T" $ Just answer @=? TTC.parseOrFail answerT- , testCase "TL" $ Just answer @=? TTC.parseOrFail answerTL- , testCase "TLB" $ Just answer @=? TTC.parseOrFail answerTLB- , testCase "ST" $ Just answer @=? TTC.parseOrFail answerST- , testCase "BS" $ Just answer @=? TTC.parseOrFail answerBS- , testCase "BSL" $ Just answer @=? TTC.parseOrFail answerBSL- , testCase "BSB" $ Just answer @=? TTC.parseOrFail answerBSB- , testCase "SBS" $ Just answer @=? TTC.parseOrFail answerSBS- -- successful 'parseOrFail' does not have any error type conversion:- , testCase "noerror" $- Just (PartialParser "test") @=? TTC.parseOrFail ("test" :: String)- , testCase "negative" $- Nothing @=? (TTC.parseOrFail ('-' : answerS) :: Maybe PosInt)- , testCase "invalid" $- Nothing @=? (TTC.parseOrFail ('a' : answerS) :: Maybe PosInt)- ]--testParseOrFailS :: TestTree-testParseOrFailS = testCase "parseOrFailS" $- Just answer @=? TTC.parseOrFailS answerS--testParseOrFailT :: TestTree-testParseOrFailT = testCase "parseOrFailT" $- Just answer @=? TTC.parseOrFailT answerT--testParseOrFailTL :: TestTree-testParseOrFailTL = testCase "parseOrFailTL" $- Just answer @=? TTC.parseOrFailTL answerTL--testParseOrFailTLB :: TestTree-testParseOrFailTLB = testCase "parseOrFailTLB" $- Just answer @=? TTC.parseOrFailTLB answerTLB--testParseOrFailST :: TestTree-testParseOrFailST = testCase "parseOrFailST" $- Just answer @=? TTC.parseOrFailST answerST--testParseOrFailBS :: TestTree-testParseOrFailBS = testCase "parseOrFailBS" $- Just answer @=? TTC.parseOrFailBS answerBS--testParseOrFailBSL :: TestTree-testParseOrFailBSL = testCase "parseOrFailBSL" $- Just answer @=? TTC.parseOrFailBSL answerBSL--testParseOrFailBSB :: TestTree-testParseOrFailBSB = testCase "parseOrFailBSB" $- Just answer @=? TTC.parseOrFailBSB answerBSB--testParseOrFailSBS :: TestTree-testParseOrFailSBS = testCase "parseOrFailSBS" $- Just answer @=? TTC.parseOrFailSBS answerSBS--testParseUnsafe :: TestTree-testParseUnsafe = testGroup "parseUnsafe"- [ testCase "S" $ answer @=? TTC.parseUnsafe answerS- , testCase "T" $ answer @=? TTC.parseUnsafe answerT- , testCase "TL" $ answer @=? TTC.parseUnsafe answerTL- , testCase "TLB" $ answer @=? TTC.parseUnsafe answerTLB- , testCase "ST" $ answer @=? TTC.parseUnsafe answerST- , testCase "BS" $ answer @=? TTC.parseUnsafe answerBS- , testCase "BSL" $ answer @=? TTC.parseUnsafe answerBSL- , testCase "BSB" $ answer @=? TTC.parseUnsafe answerBSB- , testCase "SBS" $ answer @=? TTC.parseUnsafe answerSBS- , testCase "negative" $ assertRaises (Proxy :: Proxy ErrorCall)- (TTC.parseUnsafe ('-' : answerS) :: PosInt)- , testCase "invalid" $ assertRaises (Proxy :: Proxy ErrorCall)- (TTC.parseUnsafe ('a' : answerS) :: PosInt)- ]--testParseUnsafeS :: TestTree-testParseUnsafeS = testCase "parseUnsafeS" $- answer @=? TTC.parseUnsafeS answerS--testParseUnsafeT :: TestTree-testParseUnsafeT = testCase "parseUnsafeT" $- answer @=? TTC.parseUnsafeT answerT--testParseUnsafeTL :: TestTree-testParseUnsafeTL = testCase "parseUnsafeTL" $- answer @=? TTC.parseUnsafeTL answerTL--testParseUnsafeTLB :: TestTree-testParseUnsafeTLB = testCase "parseUnsafeTLB" $- answer @=? TTC.parseUnsafeTLB answerTLB--testParseUnsafeST :: TestTree-testParseUnsafeST = testCase "parseUnsafeST" $- answer @=? TTC.parseUnsafeST answerST--testParseUnsafeBS :: TestTree-testParseUnsafeBS = testCase "parseUnsafeBS" $- answer @=? TTC.parseUnsafeBS answerBS--testParseUnsafeBSL :: TestTree-testParseUnsafeBSL = testCase "parseUnsafeBSL" $- answer @=? TTC.parseUnsafeBSL answerBSL--testParseUnsafeBSB :: TestTree-testParseUnsafeBSB = testCase "parseUnsafeBSB" $- answer @=? TTC.parseUnsafeBSB answerBSB--testParseUnsafeSBS :: TestTree-testParseUnsafeSBS = testCase "parseUnsafeSBS" $- answer @=? TTC.parseUnsafeSBS answerSBS--testWithError :: TestTree-testWithError = testGroup "withError"- [ testCase "OK" $ Right answer @=?- (TTC.withError undefString (Just answer) :: Either T.Text PosInt)- , testCase "S" $ Left "err" @=?- (TTC.withErrorS "err" Nothing :: Either T.Text PosInt)- , testCase "T" $ Left "err" @=?- (TTC.withErrorT "err" Nothing :: Either String PosInt)- , testCase "TL" $ Left "err" @=?- (TTC.withErrorTL "err" Nothing :: Either String PosInt)- , testCase "TLB" $ Left "err" @=?- (TTC.withErrorTLB "err" Nothing :: Either String PosInt)- , testCase "ST" $ Left "err" @=?- (TTC.withErrorST "err" Nothing :: Either String PosInt)- , testCase "BS" $ Left "err" @=?- (TTC.withErrorBS "err" Nothing :: Either String PosInt)- , testCase "BSL" $ Left "err" @=?- (TTC.withErrorBSL "err" Nothing :: Either String PosInt)- , testCase "BSB" $ Left "err" @=?- (TTC.withErrorBSB "err" Nothing :: Either String PosInt)- , testCase "SBS" $ Left "err" @=?- (TTC.withErrorSBS "err" Nothing :: Either String PosInt)- ]- where- undefString :: String- undefString = undefined--testPrefixError :: TestTree-testPrefixError = testGroup "prefixError"- [ testCase "OK" $ Right answer @=?- (TTC.prefixError undefString (Right answer) :: Either String PosInt)- , testCase "S" $ Left err @=?- (TTC.prefixErrorS "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "T" $ Left err @=?- (TTC.prefixErrorT "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "TL" $ Left err @=?- (TTC.prefixErrorTL "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "TLB" $ Left err @=?- (TTC.prefixErrorTLB "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "ST" $ Left err @=?- (TTC.prefixErrorTLB "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "BS" $ Left err @=?- (TTC.prefixErrorTL "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "BSL" $ Left err @=?- (TTC.prefixErrorTL "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "BSB" $ Left err @=?- (TTC.prefixErrorTL "error: " (Left "uh-oh") :: Either String PosInt)- , testCase "SBS" $ Left err @=?- (TTC.prefixErrorTL "error: " (Left "uh-oh") :: Either String PosInt)- ]- where- undefString, err :: String- undefString = undefined- err = "error: uh-oh"--testParseWithRead :: TestTree-testParseWithRead = testGroup "parseWithRead"- [ testCase "S" $ Right answerZ @=? TTC.parseWithRead IntInvalid answerS- , testCase "T" $ Right answerZ @=? TTC.parseWithRead IntInvalid answerT- , testCase "TL" $ Right answerZ @=? TTC.parseWithRead IntInvalid answerTL- , testCase "TLB" $- Right answerZ @=? TTC.parseWithRead IntInvalid answerTLB- , testCase "ST" $ Right answerZ @=? TTC.parseWithRead IntInvalid answerST- , testCase "BS" $ Right answerZ @=? TTC.parseWithRead IntInvalid answerBS- , testCase "BSL" $- Right answerZ @=? TTC.parseWithRead IntInvalid answerBSL- , testCase "BSB" $- Right answerZ @=? TTC.parseWithRead IntInvalid answerBSB- , testCase "SBS" $- Right answerZ @=? TTC.parseWithRead IntInvalid answerSBS- , testCase "invalid" $ Left IntInvalid @=?- (TTC.parseWithRead IntInvalid ('a' : answerS) :: Either IntError Int)- ]--testParseWithRead' :: TestTree-testParseWithRead' = testGroup "parseWithRead'"- [ testCase "S" $ Right answerZ @=? parseWithRead' answerS- , testCase "T" $ Right answerZ @=? parseWithRead' answerT- , testCase "TL" $ Right answerZ @=? parseWithRead' answerTL- , testCase "TLB" $ Right answerZ @=? parseWithRead' answerTLB- , testCase "ST" $ Right answerZ @=? parseWithRead' answerST- , testCase "BS" $ Right answerZ @=? parseWithRead' answerBS- , testCase "BSL" $ Right answerZ @=? parseWithRead' answerBSL- , testCase "BSB" $ Right answerZ @=? parseWithRead' answerBSB- , testCase "SBS" $ Right answerZ @=? parseWithRead' answerSBS- , testCase "invalid" $- Left "invalid Int" @=? parseWithRead' ('a' : answerS)- ]- where- parseWithRead' :: TTC.Textual t => t -> Either String Int- parseWithRead' = TTC.parseWithRead' "Int"--testMaybeParseWithRead :: TestTree-testMaybeParseWithRead = testGroup "maybeParseWithRead"- [ testCase "S" $ Just answerZ @=? TTC.maybeParseWithRead answerS- , testCase "T" $ Just answerZ @=? TTC.maybeParseWithRead answerT- , testCase "TL" $ Just answerZ @=? TTC.maybeParseWithRead answerTL- , testCase "TLB" $ Just answerZ @=? TTC.maybeParseWithRead answerTLB- , testCase "ST" $ Just answerZ @=? TTC.maybeParseWithRead answerST- , testCase "BS" $ Just answerZ @=? TTC.maybeParseWithRead answerBS- , testCase "BSL" $ Just answerZ @=? TTC.maybeParseWithRead answerBSL- , testCase "BSB" $ Just answerZ @=? TTC.maybeParseWithRead answerBSB- , testCase "SBS" $ Just answerZ @=? TTC.maybeParseWithRead answerSBS- , testCase "invalid" $ Nothing @=?- (TTC.maybeParseWithRead ('a' : answerS) :: Maybe Int)- ]--testParseEnum :: TestTree-testParseEnum = testGroup "parseEnum"- [ testCase "S" $ Right Red @=? parse False False redS- , testCase "T" $ Right Red @=? parse False False redT- , testCase "TL" $ Right Red @=? parse False False redTL- , testCase "TLB" $ Right Red @=? parse False False redTLB- , testCase "ST" $ Right Red @=? parse False False redST- , testCase "BS" $ Right Red @=? parse False False redBS- , testCase "BSL" $ Right Red @=? parse False False redBSL- , testCase "BSB" $ Right Red @=? parse False False redBSB- , testCase "SBS" $ Right Red @=? parse False False redSBS- , testCase "CI" $ Right Red @=? parse True False ("Red" :: String)- , testCase "!CI" $ Left ColorInvalid @=?- parse False False ("Red" :: String)- , testCase "prefix" $ Right Red @=? parse False True ("r" :: String)- , testCase "ambiguous" $ Left ColorAmbiguous @=?- parse False True ("bl" :: String)- ]- where- parse :: TTC.Textual t => Bool -> Bool -> t -> Either ColorError Color- parse allowCI allowPrefix =- TTC.parseEnum allowCI allowPrefix ColorInvalid ColorAmbiguous--testParseEnum' :: TestTree-testParseEnum' = testGroup "parseEnum'"- [ testCase "S" $ Right Red @=? parse False False redS- , testCase "T" $ Right Red @=? parse False False redT- , testCase "TL" $ Right Red @=? parse False False redTL- , testCase "TLB" $ Right Red @=? parse False False redTLB- , testCase "ST" $ Right Red @=? parse False False redST- , testCase "BS" $ Right Red @=? parse False False redBS- , testCase "BSL" $ Right Red @=? parse False False redBSL- , testCase "BSB" $ Right Red @=? parse False False redBSB- , testCase "SBS" $ Right Red @=? parse False False redSBS- , testCase "CI" $ Right Red @=? parse True False ("Red" :: String)- , testCase "!CI" $ Left "invalid Color" @=?- parse False False ("Red" :: String)- , testCase "prefix" $ Right Red @=? parse False True ("r" :: String)- , testCase "ambiguous" $ Left "ambiguous Color" @=?- parse False True ("bl" :: String)- ]- where- parse :: TTC.Textual t => Bool -> Bool -> t -> Either String Color- parse = TTC.parseEnum' "Color"--testReadsWithParse :: TestTree-testReadsWithParse = testGroup "readsWithParse"- [ testCase "valid" $ [(answer, "")] @=? TTC.readsWithParse answerS- , testCase "invalid" $ [] @=? (TTC.readsWithParse :: ReadS PosInt) "-42"- ]--testReadsEnum :: TestTree-testReadsEnum = testGroup "readsEnum"- [ testCase "valid" $ Just Red @=? readMaybe "R"- , testCase "invalid" $ Nothing @=? (readMaybe "bl" :: Maybe Color)- ]----------------------------------------------------------------------------------- $Valid--testValid :: TestTree-testValid = testCase "valid" $ TestString "test" @=? validConst- where- validConst :: TestString- validConst = $$(TTC.valid "test")--testValidIsString :: TestTree-testValidIsString =- testCase "validIsString" $ TestString "test" @=? validConst- where- validConst :: TestString- validConst = $$("test")--testValidOf :: TestTree-testValidOf = testCase "validOf" $- TestString "test" @=? $$(TTC.validOf (Proxy :: Proxy TestString) "test")--testMkValid :: TestTree-testMkValid = testCase "mkValid" $- TestString "test" @=? $$(TestString.valid "test")--testUntypedValidOf :: TestTree-testUntypedValidOf = testCase "untypedValidOf" $- TestString "test" @=?- $(TTC.untypedValidOf (Proxy :: Proxy TestString) "test")--testMkUntypedValid :: TestTree-testMkUntypedValid = testCase "mkUntypedValid" $- TestString "test" @=? $(TestString.untypedValid "test")--testMkUntypedValidQQ :: TestTree-testMkUntypedValidQQ = testCase "mkUntypedValidQQ" $- TestString "test" @=? [TestString.untypedValidQQ|test|]----------------------------------------------------------------------------------tests :: TestTree-tests = testGroup "Data.TTC"- [ testGroup "Textual"- [ testToS- , testToT- , testToTL- , testToTLB- , testToST- , testToBS- , testToBSL- , testToBSB- , testToSBS- , testConvert- , testFromS- , testFromT- , testFromTL- , testFromTLB- , testFromBS- , testFromBSL- , testFromBSB- , testFromSBS- , testAsS- , testAsT- , testAsTL- , testAsTLB- , testAsBS- , testAsBSL- , testAsBSB- , testAsSBS- ]- , testGroup "Render"- [ testRender- , testRenderDefault- , testRenderS- , testRenderT- , testRenderTL- , testRenderTLB- , testRenderST- , testRenderBS- , testRenderBSL- , testRenderBSB- , testRenderSBS- , testRenderWithShow- ]- , testGroup "Parse"- [ testParse- , testParseDefault- , testParseS- , testParseT- , testParseTL- , testParseTLB- , testParseST- , testParseBS- , testParseBSL- , testParseBSB- , testParseSBS- , testParseMaybe- , testParseMaybeS- , testParseMaybeT- , testParseMaybeTL- , testParseMaybeTLB- , testParseMaybeST- , testParseMaybeBS- , testParseMaybeBSL- , testParseMaybeBSB- , testParseMaybeSBS- , testParseOrFail- , testParseOrFailS- , testParseOrFailT- , testParseOrFailTL- , testParseOrFailTLB- , testParseOrFailST- , testParseOrFailBS- , testParseOrFailBSL- , testParseOrFailBSB- , testParseOrFailSBS- , testParseUnsafe- , testParseUnsafeS- , testParseUnsafeT- , testParseUnsafeTL- , testParseUnsafeTLB- , testParseUnsafeST- , testParseUnsafeBS- , testParseUnsafeBSL- , testParseUnsafeBSB- , testParseUnsafeSBS- , testWithError- , testPrefixError- , testParseWithRead- , testParseWithRead'- , testMaybeParseWithRead- , testParseEnum- , testParseEnum'+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.TTC.Test (tests) where++-- https://hackage.haskell.org/package/base+import Control.Exception (ErrorCall, Exception, evaluate, handle)+import Data.Int (Int8, Int16, Int32, Int64)+import Data.Proxy (Proxy(Proxy), asProxyTypeOf)+import Data.String (IsString)+import Data.Word (Word8, Word16, Word32, Word64)+import GHC.Stack (HasCallStack)+import Text.Read (readMaybe)++-- https://hackage.haskell.org/package/bytestring+import qualified Data.ByteString as BS+import qualified Data.ByteString.Builder as BSB+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Short as SBS++-- https://hackage.haskell.org/package/tasty+import Test.Tasty (TestName, TestTree, testGroup)++-- https://hackage.haskell.org/package/tasty-hunit+import Test.Tasty.HUnit ((@=?), Assertion, assertFailure, testCase)++-- https://hackage.haskell.org/package/text+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB++-- https://hackage.haskell.org/package/text-short+import qualified Data.Text.Short as ST++-- (ttc)+import qualified Data.TTC as TTC++-- (ttc:test)+import qualified TestTypes.Ex as Ex+import TestTypes.Ex (Ex(Ex))++------------------------------------------------------------------------------+-- $Instances++TTC.defaultRenderAndParseInstances+ [ ''Bool, ''Char, ''Double, ''Float, ''Integer+ , ''Int, ''Int8, ''Int16, ''Int32, ''Int64+ , ''Word, ''Word8, ''Word16, ''Word32, ''Word64+ , ''String+ , ''T.Text, ''TL.Text, ''TLB.Builder, ''ST.ShortText+ , ''BS.ByteString, ''BSL.ByteString, ''BSB.Builder, ''SBS.ShortByteString+ ]++------------------------------------------------------------------------------++instance Eq BSB.Builder where+ x == y = BSB.toLazyByteString x == BSB.toLazyByteString y++#if !MIN_VERSION_bytestring(0,11,1)+instance Show BSB.Builder where+ show = show . BSB.toLazyByteString+#endif++------------------------------------------------------------------------------+-- $HelperFunctions++assertRaises+ :: (HasCallStack, Exception e, Show a)+ => Proxy e+ -> a+ -> Assertion+assertRaises proxy x =+ handle+ (const (return ()) . (`asProxyTypeOf` proxy))+ (assertFailure . ("expected exception; got: " ++) . show =<< evaluate x)++------------------------------------------------------------------------------+-- $TestData++xS :: String+xS = "test テスト"++xT :: T.Text+xT = "test テスト"++xTL :: TL.Text+xTL = "test テスト"++xTLB :: TLB.Builder+xTLB = "test テスト"++xST :: ST.ShortText+xST = "test テスト"++xBS :: BS.ByteString+xBS = "test \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"++xBSL :: BSL.ByteString+xBSL = "test \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"++xBSB :: BSB.Builder+xBSB = "test テスト"++xSBS :: SBS.ShortByteString+xSBS = "test \xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88"++-- U+FFFD is the Unicode replacement character+xiS :: String+xiS = "test \xfffd"++xiT :: T.Text+xiT = T.pack xiS++xiTL :: TL.Text+xiTL = TL.pack xiS++xiTLB :: TLB.Builder+xiTLB = TLB.fromString xiS++xiST :: ST.ShortText+xiST = ST.pack xiS++-- Lone continuation byte is invalid+xiBS :: BS.ByteString+xiBS = "test \xe3"++xiBSL :: BSL.ByteString+xiBSL = BSL.fromStrict xiBS++xiBSB :: BSB.Builder+xiBSB = BSB.byteString xiBS++xiSBS :: SBS.ShortByteString+xiSBS = SBS.toShort xiBS++------------------------------------------------------------------------------++newtype PosInt = PosInt Int+ deriving (Eq, Show)++instance TTC.Parse PosInt where+ parse = TTC.asS $ \ s -> case readMaybe s of+ Just i+ | i >= 0 -> Right $ PosInt i+ | otherwise -> Left $ TTC.fromS "not positive"+ Nothing -> Left $ TTC.fromS "not an integer"++instance TTC.Render PosInt where+ render (PosInt i) = TTC.convert $ show i++answer :: PosInt+answer = PosInt 42++answerS :: String+answerS = "42"++answerT :: T.Text+answerT = "42"++answerTL :: TL.Text+answerTL = "42"++answerTLB :: TLB.Builder+answerTLB = "42"++answerST :: ST.ShortText+answerST = "42"++answerBS :: BS.ByteString+answerBS = "42"++answerBSL :: BSL.ByteString+answerBSL = "42"++answerBSB :: BSB.Builder+answerBSB = "42"++answerSBS :: SBS.ShortByteString+answerSBS = "42"++answerZ :: Int+answerZ = 42++data IntError = IntInvalid+ deriving (Eq, Show)++------------------------------------------------------------------------------++data Color+ = Red+ | Green+ | Blue+ | White+ | Black+ deriving (Bounded, Enum, Eq, Show)++instance Read Color where+ readsPrec _ = TTC.readsEnum True True++instance TTC.Render Color where+ render = TTC.fromT . \case+ Red -> "red"+ Green -> "green"+ Blue -> "blue"+ White -> "white"+ Black -> "black"++data ColorError+ = ColorInvalid+ | ColorAmbiguous+ deriving (Eq, Show)++redS :: String+redS = "red"++redT :: T.Text+redT = "red"++redTL :: TL.Text+redTL = "red"++redTLB :: TLB.Builder+redTLB = "red"++redST :: ST.ShortText+redST = "red"++redBS :: BS.ByteString+redBS = "red"++redBSL :: BSL.ByteString+redBSL = "red"++redBSB :: BSB.Builder+redBSB = "red"++redSBS :: SBS.ShortByteString+redSBS = "red"++------------------------------------------------------------------------------++newtype PartialParser = PartialParser String+ deriving (Eq, Show)++instance TTC.Parse PartialParser where+ parse = TTC.asS $ \case+ "" -> Left $ TTC.fromT undefined+ s -> Right $ PartialParser s++------------------------------------------------------------------------------+-- $Textual++testConvert :: TestTree+testConvert = testGroup "convert" $+ [ testCase "@@" $+ "test テスト" @=? TTC.convert @String @String "test テスト"+ , testCase "@_" $ xT @=? TTC.convert @String "test テスト"+ , testCase "_@" $ "test テスト" @=? TTC.convert @_ @TL.Text xT+ ]+ ++ mkTests "S" xS+ ++ mkTests "T" xT+ ++ mkTests "TL" xTL+ ++ mkTests "TLB" xTLB+ ++ mkTests "ST" xST+ ++ mkTests "BS" xBS+ ++ mkTests "BSL" xBSL+ ++ mkTests "BSB" xBSB+ ++ mkTests "SBS" xSBS+ where+ mkTests :: TTC.Textual a => String -> a -> [TestTree]+ mkTests s x =+ [ testCase (s ++ "->S") $ xS @=? TTC.convert x+ , testCase (s ++ "->T") $ xT @=? TTC.convert x+ , testCase (s ++ "->TL") $ xTL @=? TTC.convert x+ , testCase (s ++ "->TLB") $ xTLB @=? TTC.convert x+ , testCase (s ++ "->ST") $ xST @=? TTC.convert x+ , testCase (s ++ "->BS") $ xBS @=? TTC.convert x+ , testCase (s ++ "->BSL") $ xBSL @=? TTC.convert x+ , testCase (s ++ "->BSB") $ xBSB @=? TTC.convert x+ , testCase (s ++ "->SBS") $ xSBS @=? TTC.convert x+ ]++------------------------------------------------------------------------------++testToX :: TestTree+testToX = testGroup "to*"+ [ mkTests "toS" TTC.toS xS xiS+ , mkTests "toT" TTC.toT xT xiT+ , mkTests "toTL" TTC.toTL xTL xiTL+ , mkTests "toTLB" TTC.toTLB xTLB xiTLB+ , mkTests "toST" TTC.toST xST xiST+ , mkTests "toBS" TTC.toBS xBS xiBS+ , mkTests "toBSL" TTC.toBSL xBSL xiBSL+ , mkTests "toBSB" TTC.toBSB xBSB xiBSB+ , mkTests "toSBS" TTC.toSBS xSBS xiSBS+ ]+ where+ mkTests+ :: (Eq a, Show a)+ => TestName+ -> (forall t. TTC.Textual t => t -> a)+ -> a+ -> a+ -> TestTree+ mkTests testName f x xi = testGroup testName+ [ testCase "@" $ x @=? f @T.Text "test テスト"+ , testCase "S" $ x @=? f xS+ , testCase "T" $ x @=? f xT+ , testCase "TL" $ x @=? f xTL+ , testCase "TLB" $ x @=? f xTLB+ , testCase "ST" $ x @=? f xST+ , testCase "BS" $ x @=? f xBS+ , testCase "BSL" $ x @=? f xBSL+ , testCase "BSB" $ x @=? f xBSB+ , testCase "SBS" $ x @=? f xSBS+ , testCase "BS/invalid" $ xi @=? f xiBS+ , testCase "BSL/invalid" $ xi @=? f xiBSL+ , testCase "BSB/invalid" $ xi @=? f xiBSB+ , testCase "SBS/invalid" $ xi @=? f xiSBS+ ]++------------------------------------------------------------------------------++testFromX :: TestTree+testFromX = testGroup "from*"+ [ mkTests "fromS" TTC.fromS xS Nothing+ , mkTests "fromT" TTC.fromT xT Nothing+ , mkTests "fromTL" TTC.fromTL xTL Nothing+ , mkTests "fromTLB" TTC.fromTLB xTLB Nothing+ , mkTests "fromST" TTC.fromST xST Nothing+ , mkTests "fromBS" TTC.fromBS xBS (Just xiBS)+ , mkTests "fromBSL" TTC.fromBSL xBSL (Just xiBSL)+ , mkTests "fromBSB" TTC.fromBSB xBSB (Just xiBSB)+ , mkTests "fromSBS" TTC.fromSBS xSBS (Just xiSBS)+ ]+ where+ mkTests+ :: (Eq a, Show a)+ => TestName+ -> (forall t. TTC.Textual t => a -> t)+ -> a+ -> Maybe a+ -> TestTree+ mkTests testName f x mXi = testGroup testName $+ [ testCase "@" $ "test テスト" @=? f @String x+ , testCase "S" $ xS @=? f x+ , testCase "T" $ xT @=? f x+ , testCase "TL" $ xTL @=? f x+ , testCase "TLB" $ xTLB @=? f x+ , testCase "ST" $ xST @=? f x+ , testCase "BS" $ xBS @=? f x+ , testCase "BSL" $ xBSL @=? f x+ , testCase "BSB" $ xBSB @=? f x+ , testCase "SBS" $ xSBS @=? f x+ ] +++ case mXi of+ Nothing -> []+ Just xi ->+ [ testCase "S/Invalid" $ xiS @=? f xi+ , testCase "T/Invalid" $ xiT @=? f xi+ , testCase "TL/Invalid" $ xiTL @=? f xi+ , testCase "TLB/Invalid" $ xiTLB @=? f xi+ , testCase "ST/Invalid" $ xiST @=? f xi+ , testCase "BS/Invalid" $ xiBS @=? f xi+ , testCase "BSL/Invalid" $ xiBSL @=? f xi+ , testCase "BSB/Invalid" $ xiBSB @=? f xi+ , testCase "SBS/Invalid" $ xiSBS @=? f xi+ ]++------------------------------------------------------------------------------++testAsX :: TestTree+testAsX = testGroup "as*"+ [ mkTests "asS" TTC.asS xS xiS+ , mkTests "asT" TTC.asT xT xiT+ , mkTests "asTL" TTC.asTL xTL xiTL+ , mkTests "asTLB" TTC.asTLB xTLB xiTLB+ , mkTests "asST" TTC.asST xST xiST+ , mkTests "asBS" TTC.asBS xBS xiBS+ , mkTests "asBSL" TTC.asBSL xBSL xiBSL+ , mkTests "asBSB" TTC.asBSB xBSB xiBSB+ , mkTests "asSBS" TTC.asSBS xSBS xiSBS+ ]+ where+ mkTests+ :: (Eq a, Show a)+ => TestName+ -> (forall t. TTC.Textual t => (a -> a) -> t -> a)+ -> a+ -> a+ -> TestTree+ mkTests testName f x xi = testGroup testName+ [ testCase "@" $ x @=? f @T.Text id "test テスト"+ , testCase "S" $ x @=? f id xS+ , testCase "T" $ x @=? f id xT+ , testCase "TL" $ x @=? f id xTL+ , testCase "TLB" $ x @=? f id xTLB+ , testCase "ST" $ x @=? f id xST+ , testCase "BS" $ x @=? f id xBS+ , testCase "BSL" $ x @=? f id xBSL+ , testCase "BSB" $ x @=? f id xBSB+ , testCase "SBS" $ x @=? f id xSBS+ , testCase "BS/invalid" $ xi @=? f id xiBS+ , testCase "BSL/invalid" $ xi @=? f id xiBSL+ , testCase "BSB/invalid" $ xi @=? f id xiBSB+ , testCase "SBS/invalid" $ xi @=? f id xiSBS+ ]++------------------------------------------------------------------------------+-- $Render++testRender :: TestTree+testRender = testGroup "render"+ [ testCase "_@" $ "42" @=? TTC.render @_ @String answer+ , testCase "S" $ answerS @=? TTC.render answer+ , testCase "T" $ answerT @=? TTC.render answer+ , testCase "TL" $ answerTL @=? TTC.render answer+ , testCase "TLB" $ answerTLB @=? TTC.render answer+ , testCase "ST" $ answerST @=? TTC.render answer+ , testCase "BS" $ answerBS @=? TTC.render answer+ , testCase "BSL" $ answerBSL @=? TTC.render answer+ , testCase "BSB" $ answerBSB @=? TTC.render answer+ , testCase "SBS" $ answerSBS @=? TTC.render answer+ ]++------------------------------------------------------------------------------++testRenderDefault :: TestTree+testRenderDefault = testGroup "RenderDefault"+ [ testCase "Bool" $ "True" @=? TTC.renderS True+ , testCase "Char" $ "*" @=? TTC.renderS '*'+ , mkTestShow @Double "Double" 3.14159+ , mkTestShow @Float "Float" 3.14159+ , mkTestShow @Integer "Integer" 42+ , mkTestShow @Int "Int" 42+ , mkTestShow @Int8 "Int8" 42+ , mkTestShow @Int16 "Int16" 42+ , mkTestShow @Int32 "Int32" 42+ , mkTestShow @Int64 "Int64" 42+ , mkTestShow @Word "Word" 42+ , mkTestShow @Word8 "Word8" 42+ , mkTestShow @Word16 "Word16" 42+ , mkTestShow @Word32 "Word32" 42+ , mkTestShow @Word64 "Word64" 42+ , testCase "String" $ xS @=? TTC.renderS xS+ , testCase "T.Text" $ xS @=? TTC.renderS xT+ , testCase "TL.Text" $ xS @=? TTC.renderS xTL+ , testCase "TLB.Builder" $ xS @=? TTC.renderS xTLB+ , testCase "ST.ShortText" $ xS @=? TTC.renderS xST+ , testCase "BS.ByteString" $ xS @=? TTC.renderS xBS+ , testCase "BSL.ByteString" $ xS @=? TTC.renderS xBSL+ , testCase "BSB.Builder" $ xS @=? TTC.renderS xBSB+ , testCase "SBS.ShortByteString" $ xS @=? TTC.renderS xSBS+ ]+ where+ mkTestShow :: (Show a, TTC.Render a) => TestName -> a -> TestTree+ mkTestShow testName x = testCase testName $ show x @=? TTC.renderS x++------------------------------------------------------------------------------++testRenderWithShow :: TestTree+testRenderWithShow = testGroup "renderWithShow"+ [ testCase "@" $ "42" @=? TTC.renderWithShow @String answerZ+ , testCase "S" $ answerS @=? TTC.renderWithShow answerZ+ , testCase "T" $ answerT @=? TTC.renderWithShow answerZ+ , testCase "TL" $ answerTL @=? TTC.renderWithShow answerZ+ , testCase "TLB" $ answerTLB @=? TTC.renderWithShow answerZ+ , testCase "ST" $ answerST @=? TTC.renderWithShow answerZ+ , testCase "BS" $ answerBS @=? TTC.renderWithShow answerZ+ , testCase "BSL" $ answerBSL @=? TTC.renderWithShow answerZ+ , testCase "BSB" $ answerBSB @=? TTC.renderWithShow answerZ+ , testCase "SBS" $ answerSBS @=? TTC.renderWithShow answerZ+ ]++------------------------------------------------------------------------------++testRenderX :: TestTree+testRenderX = testGroup "render*"+ [ testCase "renderS" $ answerS @=? TTC.renderS answer+ , testCase "renderT" $ answerT @=? TTC.renderT answer+ , testCase "renderTL" $ answerTL @=? TTC.renderTL answer+ , testCase "renderTLB" $ answerTLB @=? TTC.renderTLB answer+ , testCase "renderST" $ answerST @=? TTC.renderST answer+ , testCase "renderBS" $ answerBS @=? TTC.renderBS answer+ , testCase "renderBSL" $ answerBSL @=? TTC.renderBSL answer+ , testCase "renderBSB" $ answerBSB @=? TTC.renderBSB answer+ , testCase "renderSBS" $ answerSBS @=? TTC.renderSBS answer+ ]++------------------------------------------------------------------------------+-- $Parse++testParse :: TestTree+testParse = testGroup "parse"+ [ testCase "_@@" $ Right answer @=? TTC.parse @_ @String @String "42"+ , testCase "S" $ Right answer @=? parse answerS+ , testCase "T" $ Right answer @=? parse answerT+ , testCase "TL" $ Right answer @=? parse answerTL+ , testCase "TLB" $ Right answer @=? parse answerTLB+ , testCase "ST" $ Right answer @=? parse answerST+ , testCase "BS" $ Right answer @=? parse answerBS+ , testCase "BSL" $ Right answer @=? parse answerBSL+ , testCase "BSB" $ Right answer @=? parse answerBSB+ , testCase "SBS" $ Right answer @=? parse answerSBS+ , testCase "negative" $ Left "not positive" @=? parse @String "-42"+ , testCase "invalid" $ Left "not an integer" @=? parse @String "4a2"+ ]+ where+ parse :: TTC.Textual t => t -> Either String PosInt+ parse = TTC.parse++------------------------------------------------------------------------------++testParseDefault :: TestTree+testParseDefault = testGroup "ParseDefault"+ [ testGroup "Bool"+ [ testCase "True" $ Right True @=? parse "True"+ , testCase "False" $ Right False @=? parse "False"+ , testCase "invalid" $ Left "invalid Bool" @=? parse @Bool "false"+ ]+ , testGroup "Char"+ [ testCase "valid" $ Right '*' @=? parse "*"+ , testCase "empty" $ Left "invalid Char" @=? parse @Char ""+ , testCase "multiple" $ Left "invalid Char" @=? parse @Char "**"+ ]+ , mkTestsShow @Double "Double" 3.14159+ , mkTestsShow @Float "Float" 3.14159+ , mkTestsShow @Integer "Integer" 42+ , mkTestsShow @Int "Int" 42+ , mkTestsShow @Int8 "Int8" 42+ , mkTestsShow @Int16 "Int16" 42+ , mkTestsShow @Int32 "Int32" 42+ , mkTestsShow @Int64 "Int64" 42+ , mkTestsShow @Word "Word" 42+ , mkTestsShow @Word8 "Word8" 42+ , mkTestsShow @Word16 "Word16" 42+ , mkTestsShow @Word32 "Word32" 42+ , mkTestsShow @Word64 "Word64" 42+ , mkTestsTextual @String "String" xS+ , mkTestsTextual @T.Text "T.Text" xT+ , mkTestsTextual @TL.Text "TL.Text" xTL+ , mkTestsTextual @TLB.Builder "TLB.Builder" xTLB+ , mkTestsTextual @ST.ShortText "ST.ShortText" xST+ , mkTestsTextual @BS.ByteString "BS.ByteString" xBS+ , mkTestsTextual @BSL.ByteString "BSL.ByteString" xBSL+ , mkTestsTextual @BSB.Builder "BSB.Builder" xBSB+ , mkTestsTextual @SBS.ShortByteString "SBS.ShortByteString" xSBS+ ]+ where+ mkTestsShow+ :: forall a. (Eq a, Show a, TTC.Parse a)+ => TestName+ -> a+ -> TestTree+ mkTestsShow testName x = testGroup testName+ [ testCase "valid" $ Right x @=? parse (show x)+ , testCase "invalid" $+ Left ("invalid " ++ testName) @=? parse @a "invalid"+ ]++ mkTestsTextual+ :: forall a. (Eq a, IsString a, Show a, TTC.Parse a)+ => TestName+ -> a+ -> TestTree+ mkTestsTextual testName x = testGroup testName+ [ testCase "empty" $ Right "" @=? parse @a ""+ , testCase "nonempty" $ Right x @=? parse xS+ ]++ parse :: TTC.Parse a => String -> Either String a+ parse = TTC.parse++------------------------------------------------------------------------------++testWithError :: TestTree+testWithError = testGroup "withError"+ [ testCase "valid" $+ Right answer @=? TTC.withError @String @String undefined (Just answer)+ , testCase "invalid" $+ Left "err" @=? TTC.withError @String @String @PosInt "err" Nothing+ ]++testWithErrorX :: TestTree+testWithErrorX = testGroup "withError*"+ [ mkTests "withErrorS" TTC.withErrorS+ , mkTests "withErrorT" TTC.withErrorT+ , mkTests "withErrorTL" TTC.withErrorTL+ , mkTests "withErrorTLB" TTC.withErrorTLB+ , mkTests "withErrorST" TTC.withErrorST+ , mkTests "withErrorBS" TTC.withErrorBS+ , mkTests "withErrorBSL" TTC.withErrorBSL+ , mkTests "withErrorBSB" TTC.withErrorBSB+ , mkTests "withErrorSBS" TTC.withErrorSBS+ ]+ where+ mkTests+ :: IsString e'+ => TestName+ -> (e' -> Maybe PosInt -> Either String PosInt)+ -> TestTree+ mkTests testName f = testGroup testName+ [ testCase "valid" $ Right answer @=? f undefined (Just answer)+ , testCase "invalid" $ Left "err" @=? f "err" Nothing+ ]++------------------------------------------------------------------------------++testPrefixError :: TestTree+testPrefixError = testGroup "prefixError"+ [ testCase "valid" $+ Right answer+ @=? TTC.prefixError @String @String "oops: " (Right answer)+ , testCase "invalid" $+ Left "oops: err"+ @=? TTC.prefixError @String @String @PosInt "oops: " (Left "err")+ ]++testPrefixErrorX :: TestTree+testPrefixErrorX = testGroup "prefixError*"+ [ mkTests "prefixErrorS" TTC.prefixErrorS+ , mkTests "prefixErrorT" TTC.prefixErrorT+ , mkTests "prefixErrorTL" TTC.prefixErrorTL+ , mkTests "prefixErrorTLB" TTC.prefixErrorTLB+ , mkTests "prefixErrorST" TTC.prefixErrorST+ , mkTests "prefixErrorBS" TTC.prefixErrorBS+ , mkTests "prefixErrorBSL" TTC.prefixErrorBSL+ , mkTests "prefixErrorBSB" TTC.prefixErrorBSB+ , mkTests "prefixErrorSBS" TTC.prefixErrorSBS+ ]+ where+ mkTests+ :: IsString e'+ => TestName+ -> (e' -> Either e' PosInt -> Either String PosInt)+ -> TestTree+ mkTests testName f = testGroup testName+ [ testCase "valid" $ Right answer @=? f "oops: " (Right answer)+ , testCase "invalid" $ Left "oops: err" @=? f "oops: " (Left "err")+ ]++------------------------------------------------------------------------------++testParseWithRead :: TestTree+testParseWithRead = testGroup "parseWithRead"+ [ testCase "@" $+ Right answerZ @=? TTC.parseWithRead @String IntInvalid "42"+ , testCase "S" $ Right answerZ @=? parseWithRead answerS+ , testCase "T" $ Right answerZ @=? parseWithRead answerT+ , testCase "TL" $ Right answerZ @=? parseWithRead answerTL+ , testCase "TLB" $ Right answerZ @=? parseWithRead answerTLB+ , testCase "ST" $ Right answerZ @=? parseWithRead answerST+ , testCase "BS" $ Right answerZ @=? parseWithRead answerBS+ , testCase "BSL" $ Right answerZ @=? parseWithRead answerBSL+ , testCase "BSB" $ Right answerZ @=? parseWithRead answerBSB+ , testCase "SBS" $ Right answerZ @=? parseWithRead answerSBS+ , testCase "invalid" $ Left IntInvalid @=? parseWithRead @String "4a2"+ ]+ where+ parseWithRead :: TTC.Textual t => t -> Either IntError Int+ parseWithRead = TTC.parseWithRead IntInvalid++testParseWithRead' :: TestTree+testParseWithRead' = testGroup "parseWithRead'"+ [ testCase "@" $+ Right answerZ @=? TTC.parseWithRead' @String @String "Int" "42"+ , testCase "S" $ Right answerZ @=? parseWithRead' answerS+ , testCase "T" $ Right answerZ @=? parseWithRead' answerT+ , testCase "TL" $ Right answerZ @=? parseWithRead' answerTL+ , testCase "TLB" $ Right answerZ @=? parseWithRead' answerTLB+ , testCase "ST" $ Right answerZ @=? parseWithRead' answerST+ , testCase "BS" $ Right answerZ @=? parseWithRead' answerBS+ , testCase "BSL" $ Right answerZ @=? parseWithRead' answerBSL+ , testCase "BSB" $ Right answerZ @=? parseWithRead' answerBSB+ , testCase "SBS" $ Right answerZ @=? parseWithRead' answerSBS+ , testCase "invalid" $ Left "invalid Int" @=? parseWithRead' @String "4a2"+ ]+ where+ parseWithRead' :: TTC.Textual t => t -> Either String Int+ parseWithRead' = TTC.parseWithRead' "Int"++testMaybeParseWithRead :: TestTree+testMaybeParseWithRead = testGroup "maybeParseWithRead"+ [ testCase "@" $ Just answerZ @=? TTC.maybeParseWithRead @String "42"+ , testCase "S" $ Just answerZ @=? TTC.maybeParseWithRead answerS+ , testCase "T" $ Just answerZ @=? TTC.maybeParseWithRead answerT+ , testCase "TL" $ Just answerZ @=? TTC.maybeParseWithRead answerTL+ , testCase "TLB" $ Just answerZ @=? TTC.maybeParseWithRead answerTLB+ , testCase "ST" $ Just answerZ @=? TTC.maybeParseWithRead answerST+ , testCase "BS" $ Just answerZ @=? TTC.maybeParseWithRead answerBS+ , testCase "BSL" $ Just answerZ @=? TTC.maybeParseWithRead answerBSL+ , testCase "BSB" $ Just answerZ @=? TTC.maybeParseWithRead answerBSB+ , testCase "SBS" $ Just answerZ @=? TTC.maybeParseWithRead answerSBS+ , testCase "invalid" $+ Nothing @=? TTC.maybeParseWithRead @String @Int "4a2"+ ]++------------------------------------------------------------------------------++testParseEnum :: TestTree+testParseEnum = testGroup "parseEnum"+ [ testCase "@" $+ Right Red+ @=? TTC.parseEnum @String+ False False ColorInvalid ColorAmbiguous "red"+ , testCase "S" $ Right Red @=? parseEnum False False redS+ , testCase "T" $ Right Red @=? parseEnum False False redT+ , testCase "TL" $ Right Red @=? parseEnum False False redTL+ , testCase "TLB" $ Right Red @=? parseEnum False False redTLB+ , testCase "ST" $ Right Red @=? parseEnum False False redST+ , testCase "BS" $ Right Red @=? parseEnum False False redBS+ , testCase "BSL" $ Right Red @=? parseEnum False False redBSL+ , testCase "BSB" $ Right Red @=? parseEnum False False redBSB+ , testCase "SBS" $ Right Red @=? parseEnum False False redSBS+ , testCase "CI" $ Right Red @=? parseEnum @String True False "Red"+ , testCase "!CI" $+ Left ColorInvalid @=? parseEnum @String False False "Red"+ , testCase "prefix" $ Right Red @=? parseEnum @String False True "r"+ , testCase "ambiguous" $+ Left ColorAmbiguous @=? parseEnum @String False True "bl"+ ]+ where+ parseEnum :: TTC.Textual t => Bool -> Bool -> t -> Either ColorError Color+ parseEnum allowCI allowPrefix =+ TTC.parseEnum allowCI allowPrefix ColorInvalid ColorAmbiguous++testParseEnum' :: TestTree+testParseEnum' = testGroup "parseEnum'"+ [ testCase "@" $+ Right Red+ @=? TTC.parseEnum' @String @String "Color" False False "red"+ , testCase "S" $ Right Red @=? parseEnum' False False redS+ , testCase "T" $ Right Red @=? parseEnum' False False redT+ , testCase "TL" $ Right Red @=? parseEnum' False False redTL+ , testCase "TLB" $ Right Red @=? parseEnum' False False redTLB+ , testCase "ST" $ Right Red @=? parseEnum' False False redST+ , testCase "BS" $ Right Red @=? parseEnum' False False redBS+ , testCase "BSL" $ Right Red @=? parseEnum' False False redBSL+ , testCase "BSB" $ Right Red @=? parseEnum' False False redBSB+ , testCase "SBS" $ Right Red @=? parseEnum' False False redSBS+ , testCase "CI" $ Right Red @=? parseEnum' @String True False "Red"+ , testCase "!CI" $+ Left "invalid Color" @=? parseEnum' @String False False "Red"+ , testCase "prefix" $ Right Red @=? parseEnum' @String False True "r"+ , testCase "ambiguous" $+ Left "ambiguous Color" @=? parseEnum' @String False True "bl"+ ]+ where+ parseEnum' :: TTC.Textual t => Bool -> Bool -> t -> Either String Color+ parseEnum' = TTC.parseEnum' "Color"++------------------------------------------------------------------------------++testParseX :: TestTree+testParseX = testGroup "parse*"+ [ mkTests "parseS" TTC.parseS answerS+ , mkTests "parseT" TTC.parseT answerT+ , mkTests "parseTL" TTC.parseTL answerTL+ , mkTests "parseTLB" TTC.parseTLB answerTLB+ , mkTests "parseST" TTC.parseST answerST+ , mkTests "parseBS" TTC.parseBS answerBS+ , mkTests "parseBSL" TTC.parseBSL answerBSL+ , mkTests "parseBSB" TTC.parseBSB answerBSB+ , mkTests "parseSBS" TTC.parseSBS answerSBS+ ]+ where+ mkTests+ :: IsString a+ => TestName+ -> (a -> Either String PosInt)+ -> a+ -> TestTree+ mkTests testName f x = testGroup testName+ [ testCase "valid" $ Right answer @=? f x+ , testCase "invalid" $ Left "not an integer" @=? f "4a2"+ ]++------------------------------------------------------------------------------++testParseMaybe :: TestTree+testParseMaybe = testGroup "parseMaybe"+ [ testCase "@" $ Just answer @=? TTC.parseMaybe @String "42"+ , testCase "S" $ Just answer @=? TTC.parseMaybe answerS+ , testCase "T" $ Just answer @=? TTC.parseMaybe answerT+ , testCase "TL" $ Just answer @=? TTC.parseMaybe answerTL+ , testCase "TLB" $ Just answer @=? TTC.parseMaybe answerTLB+ , testCase "ST" $ Just answer @=? TTC.parseMaybe answerST+ , testCase "BS" $ Just answer @=? TTC.parseMaybe answerBS+ , testCase "BSL" $ Just answer @=? TTC.parseMaybe answerBSL+ , testCase "BSB" $ Just answer @=? TTC.parseMaybe answerBSB+ , testCase "SBS" $ Just answer @=? TTC.parseMaybe answerSBS+ , testCase "noerror" $+ Just (PartialParser "test") @=? TTC.parseMaybe @String "test"+ , testCase "negative" $ Nothing @=? TTC.parseMaybe @String @PosInt "-42"+ , testCase "invalid" $ Nothing @=? TTC.parseMaybe @String @PosInt "4a2"+ ]++testParseMaybeX :: TestTree+testParseMaybeX = testGroup "parseMaybe*"+ [ mkTestsParseMaybe "parseMaybeS" TTC.parseMaybeS answerS+ , mkTestsParseMaybe "parseMaybeT" TTC.parseMaybeT answerT+ , mkTestsParseMaybe "parseMaybeTL" TTC.parseMaybeTL answerTL+ , mkTestsParseMaybe "parseMaybeTLB" TTC.parseMaybeTLB answerTLB+ , mkTestsParseMaybe "parseMaybeST" TTC.parseMaybeST answerST+ , mkTestsParseMaybe "parseMaybeBS" TTC.parseMaybeBS answerBS+ , mkTestsParseMaybe "parseMaybeBSL" TTC.parseMaybeBSL answerBSL+ , mkTestsParseMaybe "parseMaybeBSB" TTC.parseMaybeBSB answerBSB+ , mkTestsParseMaybe "parseMaybeSBS" TTC.parseMaybeSBS answerSBS+ ]++mkTestsParseMaybe+ :: IsString a+ => TestName+ -> (a -> Maybe PosInt)+ -> a+ -> TestTree+mkTestsParseMaybe testName f x = testGroup testName+ [ testCase "valid" $ Just answer @=? f x+ , testCase "invalid" $ Nothing @=? f "4a2"+ ]++------------------------------------------------------------------------------++testParseOrFail :: TestTree+testParseOrFail = testGroup "parseOrFail"+ [ testCase "@" $ Just answer @=? TTC.parseOrFail @String "42"+ , testCase "S" $ Just answer @=? TTC.parseOrFail answerS+ , testCase "T" $ Just answer @=? TTC.parseOrFail answerT+ , testCase "TL" $ Just answer @=? TTC.parseOrFail answerTL+ , testCase "TLB" $ Just answer @=? TTC.parseOrFail answerTLB+ , testCase "ST" $ Just answer @=? TTC.parseOrFail answerST+ , testCase "BS" $ Just answer @=? TTC.parseOrFail answerBS+ , testCase "BSL" $ Just answer @=? TTC.parseOrFail answerBSL+ , testCase "BSB" $ Just answer @=? TTC.parseOrFail answerBSB+ , testCase "SBS" $ Just answer @=? TTC.parseOrFail answerSBS+ , testCase "noerror" $+ Just (PartialParser "test") @=? TTC.parseOrFail @String "test"+ , testCase "negative" $ Nothing @=? TTC.parseOrFail @String @PosInt "-42"+ , testCase "invalid" $ Nothing @=? TTC.parseOrFail @String @PosInt "4a2"+ ]++testParseOrFailX :: TestTree+testParseOrFailX = testGroup "parseOrFail*"+ [ mkTestsParseMaybe "parseOrFailS" TTC.parseOrFailS answerS+ , mkTestsParseMaybe "parseOrFailT" TTC.parseOrFailT answerT+ , mkTestsParseMaybe "parseOrFailTL" TTC.parseOrFailTL answerTL+ , mkTestsParseMaybe "parseOrFailTLB" TTC.parseOrFailTLB answerTLB+ , mkTestsParseMaybe "parseOrFailST" TTC.parseOrFailST answerST+ , mkTestsParseMaybe "parseOrFailBS" TTC.parseOrFailBS answerBS+ , mkTestsParseMaybe "parseOrFailBSL" TTC.parseOrFailBSL answerBSL+ , mkTestsParseMaybe "parseOrFailBSB" TTC.parseOrFailBSB answerBSB+ , mkTestsParseMaybe "parseOrFailSBS" TTC.parseOrFailSBS answerSBS+ ]++------------------------------------------------------------------------------++testParseUnsafe :: TestTree+testParseUnsafe = testGroup "parseUnsafe"+ [ testCase "@" $ answer @=? TTC.parseUnsafe @String "42"+ , testCase "S" $ answer @=? TTC.parseUnsafe answerS+ , testCase "T" $ answer @=? TTC.parseUnsafe answerT+ , testCase "TL" $ answer @=? TTC.parseUnsafe answerTL+ , testCase "TLB" $ answer @=? TTC.parseUnsafe answerTLB+ , testCase "ST" $ answer @=? TTC.parseUnsafe answerST+ , testCase "BS" $ answer @=? TTC.parseUnsafe answerBS+ , testCase "BSL" $ answer @=? TTC.parseUnsafe answerBSL+ , testCase "BSB" $ answer @=? TTC.parseUnsafe answerBSB+ , testCase "SBS" $ answer @=? TTC.parseUnsafe answerSBS+ , testCase "negative" . assertRaises (Proxy :: Proxy ErrorCall) $+ TTC.parseUnsafe @String @PosInt "-42"+ , testCase "invalid" . assertRaises (Proxy :: Proxy ErrorCall) $+ TTC.parseUnsafe @String @PosInt "4a2"+ ]++testParseUnsafeX :: TestTree+testParseUnsafeX = testGroup "parseUnsafe*"+ [ mkTests "parseUnsafeS" TTC.parseUnsafeS answerS+ , mkTests "parseUnsafeT" TTC.parseUnsafeT answerT+ , mkTests "parseUnsafeTL" TTC.parseUnsafeTL answerTL+ , mkTests "parseUnsafeTLB" TTC.parseUnsafeTLB answerTLB+ , mkTests "parseUnsafeST" TTC.parseUnsafeST answerST+ , mkTests "parseUnsafeBS" TTC.parseUnsafeBS answerBS+ , mkTests "parseUnsafeBSL" TTC.parseUnsafeBSL answerBSL+ , mkTests "parseUnsafeBSB" TTC.parseUnsafeBSB answerBSB+ , mkTests "parseUnsafeSBS" TTC.parseUnsafeSBS answerSBS+ ]+ where+ mkTests+ :: IsString a+ => TestName+ -> (a -> PosInt)+ -> a+ -> TestTree+ mkTests testName f x = testGroup testName+ [ testCase "valid" $ answer @=? f x+ , testCase "invalid" . assertRaises (Proxy :: Proxy ErrorCall) $ f "4a2"+ ]++------------------------------------------------------------------------------++testReadsWithParse :: TestTree+testReadsWithParse = testGroup "readsWithParse"+ [ testCase "valid" $ [(answer, "")] @=? TTC.readsWithParse answerS+ , testCase "invalid" $ [] @=? (TTC.readsWithParse :: ReadS PosInt) "-42"+ ]++testReadsEnum :: TestTree+testReadsEnum = testGroup "readsEnum"+ [ testCase "valid" $ Just Red @=? readMaybe "R"+ , testCase "invalid" $ Nothing @=? readMaybe @Color "bl"+ ]++------------------------------------------------------------------------------+-- $Valid++testValid :: TestTree+testValid = testCase "valid" $ Ex "test" @=? validConst+ where+ validConst :: Ex+ validConst = $$(TTC.valid "test")++testValidIsString :: TestTree+testValidIsString =+ testCase "validIsString" $ Ex "test" @=? validConst+ where+ validConst :: Ex+ validConst = $$("test")++testValidOf :: TestTree+testValidOf = testCase "validOf" $+ Ex "test" @=? $$(TTC.validOf (Proxy :: Proxy Ex) "test")++testMkValid :: TestTree+testMkValid = testCase "mkValid" $ Ex "test" @=? $$(Ex.valid "test")++testUntypedValidOf :: TestTree+testUntypedValidOf = testCase "untypedValidOf" $+ Ex "test" @=? $(TTC.untypedValidOf (Proxy :: Proxy Ex) "test")++testMkUntypedValid :: TestTree+testMkUntypedValid = testCase "mkUntypedValid" $+ Ex "test" @=? $(Ex.untypedValid "test")++testMkUntypedValidQQ :: TestTree+testMkUntypedValidQQ = testCase "mkUntypedValidQQ" $+ Ex "test" @=? [Ex.untypedValidQQ|test|]++------------------------------------------------------------------------------++tests :: TestTree+tests = testGroup "Data.TTC"+ [ testGroup "Textual"+ [ testConvert+ , testToX+ , testFromX+ , testAsX+ ]+ , testGroup "Render"+ [ testRender+ , testRenderDefault+ , testRenderWithShow+ , testRenderX+ ]+ , testGroup "Parse"+ [ testParse+ , testParseDefault+ , testWithError+ , testWithErrorX+ , testPrefixError+ , testPrefixErrorX+ , testParseWithRead+ , testParseWithRead'+ , testMaybeParseWithRead+ , testParseEnum+ , testParseEnum'+ , testParseX+ , testParseMaybe+ , testParseMaybeX+ , testParseOrFail+ , testParseOrFailX+ , testParseUnsafe+ , testParseUnsafeX , testReadsWithParse , testReadsEnum ]
+ test/Data/TTC/Wrapper/Test.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}++module Data.TTC.Wrapper.Test (tests) where++-- https://hackage.haskell.org/package/bytestring+import qualified Data.ByteString as BS+import qualified Data.ByteString.Builder as BSB+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Short as SBS++-- https://hackage.haskell.org/package/tasty+import Test.Tasty (TestTree, testGroup)++-- https://hackage.haskell.org/package/tasty-hunit+import Test.Tasty.HUnit ((@=?), testCase)++-- https://hackage.haskell.org/package/text+import qualified Data.Text as T+import qualified Data.Text.Lazy as TL+import qualified Data.Text.Lazy.Builder as TLB++-- https://hackage.haskell.org/package/text-short+import qualified Data.Text.Short as ST++-- (ttc)+import qualified Data.TTC as TTC+import qualified Data.TTC.Wrapper as TTCW++------------------------------------------------------------------------------++instance Eq BSB.Builder where+ x == y = BSB.toLazyByteString x == BSB.toLazyByteString y++#if !MIN_VERSION_bytestring(0,11,1)+instance Show BSB.Builder where+ show = show . BSB.toLazyByteString+#endif++------------------------------------------------------------------------------+-- $TestData++xS :: String+xS = "test テスト"++xT :: T.Text+xT = "test テスト"++------------------------------------------------------------------------------+-- $WrapperS++newtype ExS = ExS String+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperS+ deriving TTC.Render via TTCW.WrapperS++testWrapperS :: TestTree+testWrapperS = testCase "WrapperS" $+ Right xT @=? (TTC.render <$> TTC.parse @ExS @_ @String xT)++------------------------------------------------------------------------------+-- $WrapperT++newtype ExT = ExT T.Text+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperT+ deriving TTC.Render via TTCW.WrapperT++testWrapperT :: TestTree+testWrapperT = testCase "WrapperT" $+ Right xS @=? (TTC.render <$> TTC.parse @ExT @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperTL++newtype ExTL = ExTL TL.Text+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperTL+ deriving TTC.Render via TTCW.WrapperTL++testWrapperTL :: TestTree+testWrapperTL = testCase "WrapperTL" $+ Right xS @=? (TTC.render <$> TTC.parse @ExTL @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperTLB++newtype ExTLB = ExTLB TLB.Builder+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperTLB+ deriving TTC.Render via TTCW.WrapperTLB++testWrapperTLB :: TestTree+testWrapperTLB = testCase "WrapperTLB" $+ Right xS @=? (TTC.render <$> TTC.parse @ExTLB @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperST++newtype ExST = ExST ST.ShortText+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperST+ deriving TTC.Render via TTCW.WrapperST++testWrapperST :: TestTree+testWrapperST = testCase "WrapperST" $+ Right xS @=? (TTC.render <$> TTC.parse @ExST @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperBS++newtype ExBS = ExBS BS.ByteString+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperBS+ deriving TTC.Render via TTCW.WrapperBS++testWrapperBS :: TestTree+testWrapperBS = testCase "WrapperBS" $+ Right xS @=? (TTC.render <$> TTC.parse @ExBS @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperBSL++newtype ExBSL = ExBSL BSL.ByteString+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperBSL+ deriving TTC.Render via TTCW.WrapperBSL++testWrapperBSL :: TestTree+testWrapperBSL = testCase "WrapperBSL" $+ Right xS @=? (TTC.render <$> TTC.parse @ExBSL @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperBSB++newtype ExBSB = ExBSB BSB.Builder+ deriving (Eq, Show)+ deriving TTC.Parse via TTCW.WrapperBSB+ deriving TTC.Render via TTCW.WrapperBSB++testWrapperBSB :: TestTree+testWrapperBSB = testCase "WrapperBSB" $+ Right xS @=? (TTC.render <$> TTC.parse @ExBSB @_ @String xS)++------------------------------------------------------------------------------+-- $WrapperSBS++newtype ExSBS = ExSBS SBS.ShortByteString+ deriving (Eq, Ord, Show)+ deriving TTC.Parse via TTCW.WrapperSBS+ deriving TTC.Render via TTCW.WrapperSBS++testWrapperSBS :: TestTree+testWrapperSBS = testCase "WrapperSBS" $+ Right xS @=? (TTC.render <$> TTC.parse @ExSBS @_ @String xS)++------------------------------------------------------------------------------++tests :: TestTree+tests = testGroup "Data.TTC.Wrapper"+ [ testWrapperS+ , testWrapperT+ , testWrapperTL+ , testWrapperTLB+ , testWrapperST+ , testWrapperBS+ , testWrapperBSL+ , testWrapperBSB+ , testWrapperSBS+ ]
test/Spec.hs view
@@ -5,10 +5,12 @@ -- (ttc:test) import qualified Data.TTC.Test+import qualified Data.TTC.Wrapper.Test ------------------------------------------------------------------------------ main :: IO () main = defaultMain $ testGroup "test" [ Data.TTC.Test.tests+ , Data.TTC.Wrapper.Test.tests ]
− test/TestString.hs
@@ -1,31 +0,0 @@-{-# LANGUAGE DeriveLift #-}-{-# LANGUAGE TemplateHaskell #-}--module TestString where---- https://hackage.haskell.org/package/template-haskell-import qualified Language.Haskell.TH.Syntax as THS---- (ttc)-import qualified Data.TTC as TTC----------------------------------------------------------------------------------- $Type--newtype TestString = TestString String- deriving (Eq, Ord, Show, THS.Lift)--instance TTC.Render TestString where- render (TestString s) = TTC.convert s--instance TTC.Parse TestString where- parse = TTC.asS $ Right . TestString----------------------------------------------------------------------------------- $API--$(TTC.mkValid "valid" ''TestString)--$(TTC.mkUntypedValid "untypedValid" ''TestString)--$(TTC.mkUntypedValidQQ "untypedValidQQ" ''TestString)
+ test/TestTypes/Ex.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE DeriveLift #-}+{-# LANGUAGE TemplateHaskell #-}++module TestTypes.Ex where++-- https://hackage.haskell.org/package/template-haskell+import qualified Language.Haskell.TH.Syntax as THS++-- (ttc)+import qualified Data.TTC as TTC++------------------------------------------------------------------------------+-- $Type++newtype Ex = Ex { exString :: String }+ deriving (Eq, Ord, Show, THS.Lift)++instance TTC.Parse Ex where+ parse = TTC.asS $ pure . Ex++instance TTC.Render Ex where+ render = TTC.convert . exString++------------------------------------------------------------------------------+-- $API++$(TTC.mkValid "valid" ''Ex)++$(TTC.mkUntypedValid "untypedValid" ''Ex)++$(TTC.mkUntypedValidQQ "untypedValidQQ" ''Ex)
ttc.cabal view
@@ -1,37 +1,35 @@-name: ttc-version: 1.4.0.0-category: Data, Text-synopsis: Textual Type Classes+cabal-version: 3.0+name: ttc+version: 1.5.0.0+synopsis: Textual Type Classes description: This library provides type classes for conversion between data types and textual data types (strings). Please see the README on GitHub at- <https://github.com/ExtremaIS/ttc-haskell#readme>.+ <https://github.com/ExtremaIS/ttc-haskell/tree/main/ttc#readme>.+homepage: https://github.com/ExtremaIS/ttc-haskell/tree/main/ttc#readme+bug-reports: https://github.com/ExtremaIS/ttc-haskell/issues+license: MIT+license-file: LICENSE+author: Travis Cardwell <travis.cardwell@extrema.is>+maintainer: Travis Cardwell <travis.cardwell@extrema.is>+copyright: Copyright (c) 2019-2025 Travis Cardwell+category: Data, Text+build-type: Simple -homepage: https://github.com/ExtremaIS/ttc-haskell#readme-bug-reports: https://github.com/ExtremaIS/ttc-haskell/issues-author: Travis Cardwell <travis.cardwell@extrema.is>-maintainer: Travis Cardwell <travis.cardwell@extrema.is>-copyright: Copyright (c) 2019-2023 Travis Cardwell-license: MIT-license-file: LICENSE+extra-doc-files:+ CHANGELOG.md+ README.md -cabal-version: 1.24-build-type: Simple tested-with:- GHC ==8.2.2- || ==8.4.4- || ==8.6.5- || ==8.8.4+ GHC ==8.8.4 || ==8.10.7 || ==9.0.2 || ==9.2.8 || ==9.4.8- || ==9.6.3- || ==9.8.1--extra-source-files:- CHANGELOG.md- README.md+ || ==9.6.6+ || ==9.8.4+ || ==9.10.1+ || ==9.12.1 source-repository head type: git@@ -41,12 +39,13 @@ hs-source-dirs: src exposed-modules: Data.TTC+ , Data.TTC.Wrapper build-depends:- base >=4.10.1 && <4.20- , bytestring >=0.10.8 && <0.13- , template-haskell >=2.12 && <2.22- , text >=1.2.2 && <2.2- , text-short >=0.1 && <0.2+ base >=4.13.0.0 && <4.22+ , bytestring >=0.10.10.1 && <0.13+ , template-haskell >=2.15.0.0 && <2.24+ , text >=1.2.4.0 && <2.2+ , text-short >=0.1.3 && <0.2 default-language: Haskell2010 default-extensions: OverloadedStrings@@ -58,12 +57,13 @@ main-is: Spec.hs other-modules: Data.TTC.Test- , TestString+ , Data.TTC.Wrapper.Test+ , TestTypes.Ex build-depends: base , bytestring- , tasty >=0.11 && <1.6- , tasty-hunit >=0.8 && <0.11+ , tasty >=1.2.3 && <1.6+ , tasty-hunit >=0.10.0.3 && <0.11 , template-haskell , text , text-short