text-display (empty) → 0.0.1.0
raw patch · 6 files changed
+659/−0 lines, 6 filesdep +basedep +bytestringdep +hspec
Dependencies added: base, bytestring, hspec, should-not-typecheck, text, text-display
Files
- CHANGELOG.md +9/−0
- LICENSE +20/−0
- README.md +69/−0
- src/Data/Text/Display.hs +414/−0
- test/Main.hs +86/−0
- text-display.cabal +61/−0
+ CHANGELOG.md view
@@ -0,0 +1,9 @@+# CHANGELOG++## [Unreleased]++## [v0.0.1.0] – 20/10/2021+* Initial release++[Unreleased]: https://github.com/kleidukos/text-display/compare/v0.0.1.0...HEAD+[v0.0.1.0]: https://github.com/kleidukos/text-display/releases/tag/v0.0.1.0
+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2021 Hécate Moonlight++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,69 @@+<p align="center">++<img src="https://raw.githubusercontent.com/haskell-text/text-display/main/images/logo.png" />+</p>++<p align="center">+<a href="https://github.com/Kleidukos/text-display/actions"></a>+ <img src="https://img.shields.io/github/workflow/status/Kleidukos/text-display/CI?style=flat-square" alt="CI badge" />+</a>+<a href="https://haskell.org">+ <img src="https://img.shields.io/badge/Made%20in-Haskell-%235e5086?logo=haskell&style=flat-square" alt="made with Haskell"/>+</a>+</p>++<p align="center">+<a href='https://ko-fi.com/Q5Q327ZHW' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi1.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>+</p>++> *A Typeclass for user-facing output*+++## Description++The `text-display` library offers a way for developers to print a textual representation of datatypes that does not+have to abide by the rules of the [Show typeclass][Show].++If you wish to learn more about how things are done and why, please read the [DESIGN.md](./DESIGN.md) file.++## Examples++There are two methods to implement `Display` for your type:++The first one is a manual implementation:++```haskell+data ManualType = MT Int++-- >>> display (MT 32)+-- "MT 32"+instance Display ManualType where+ displayPrec prec (MT i) = displayParen (prec > 10) $ "MT " <> displayPrec 11 i+```++But this can be quite time-consuming, especially if your datatype already has+an existing `Show` that you wish to reuse. In which case, you can piggy-back+on this instance like this:++```haskell+{-# LANGUAGE DerivingVia #-}+data AutomaticallyDerived = AD+ -- We derive 'Show'+ deriving Show + -- We take advantage of the 'Show' instance to derive 'Display' from it+ deriving Display+ via (ShowInstance AutomaticallyDerived) +```++But let's say you want to redact an instance of `Display`? You can do it locally, through+the `OpaqueInstance` helper. It is most useful to hide tokens or passwords:++```haskell+data UserToken = UserToken UUID + deriving Display + via (OpaqueInstance "[REDACTED]" UserToken) + +display $ UserToken "7a01d2ce-31ff-11ec-8c10-5405db82c3cd"+-- => "[REDACTED]" +```+[Show]: https://hackage.haskell.org/package/base/docs/Text-Show.html#v:Show
+ src/Data/Text/Display.hs view
@@ -0,0 +1,414 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE StrictData #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE UndecidableInstances #-}++{-|+ Module : Data.Text.Entity+ Copyright : © Hécate Moonlight, 2021+ License : MIT+ Maintainer : hecate@glitchbra.in+ Stability : stable++ Use 'display' to produce user-facing text++-}+module Data.Text.Display+ ( -- * Documentation+ display+ , Display(..)+ , -- * Deriving your instance automatically+ ShowInstance(..)+ , OpaqueInstance(..)+ , -- * Writing your instance by hand+ displayParen+ -- * Design choices+ -- $designChoices+ ) where++import Control.Exception hiding (TypeError)+import Data.ByteString+import Data.Int+import Data.Kind+import Data.List.NonEmpty+import Data.Text (Text)+import Data.Text.Lazy.Builder (Builder)+import Data.Word+import GHC.Show (showLitString)+import GHC.TypeLits+import qualified Data.ByteString.Lazy as BL+import qualified Data.Text.Lazy.Builder as TB+import qualified Data.Text.Lazy.Builder.Int as TB+import qualified Data.Text.Lazy.Builder.RealFloat as TB+import qualified Data.Text.Lazy as TL+import Data.Proxy++-- | A typeclass for user-facing output.+--+-- @since 0.0.1.0+class Display a where+ {-# MINIMAL displayBuilder | displayPrec #-}+ -- | Implement this method to describe how to convert your value to 'Builder'.+ displayBuilder :: a -> Builder+ displayBuilder = displayPrec 0++ -- | The method 'displayList' is provided to allow for a specialised+ -- way to render lists of a certain value.+ -- This is used to render the list of 'Char' as a string of characters+ -- enclosed in double quotes, rather than between square brackets and+ -- separated by commas.+ --+ -- === Example+ --+ -- > instance Display Char where+ -- > displayBuilder '\'' = "'\\''"+ -- > displayBuilder c = "'" <> TB.singleton c <> "\'"+ -- > -- 'displayList' is overloaded, so that when the @Display [a]@ instance calls 'displayList',+ -- > -- we end up with a nice string enclosed between double quotes.+ -- > displayList cs = TB.fromString $ "\"" <> showLitString cs "\""+ --+ -- > instance Display a => Display [a] where+ -- > -- In this instance, 'displayBuilder' is defined in terms of 'displayList', which for most types+ -- > -- is defined as the default written in the class declaration.+ -- > -- But when a ~ Char, there is an explicit implementation that is selected instead, which+ -- > -- provides the rendering of the character string between double quotes.+ -- > displayBuilder = displayList+ --+ -- ==== How implementations are selected+ --+ -- > displayBuilder ([1,2,3] :: [Int])+ -- > → displayBuilder @[Int] = displayBuilderList @Int+ -- > → Default `displayList`+ -- >+ -- > displayBuilder ("abc" :: [Char])+ -- > → displayBuilder @[Char] = displayBuilderList @Char+ -- > → Custom `displayList`+ displayList :: [a] -> Builder+ displayList [] = "[]"+ displayList (x:xs) = displayList' xs ("[" <> displayBuilder x)+ where+ displayList' :: [a] -> Builder -> Builder+ displayList' [] acc = acc <> "]"+ displayList' (y:ys) acc = displayList' ys (acc <> "," <> displayBuilder y)++ -- | The method 'displayPrec' allows you to write instances that+ -- require nesting. The precedence parameter can be thought of as a+ -- suggestion coming from the surrounding context for how tightly to bind. If the precedence+ -- parameter is higher than the precedence of the operator (or constructor, function, etc.)+ -- being displayed, then that suggests that the output will need to be surrounded in parentheses+ -- in order to bind tightly enough (see 'displayParen').+ --+ -- For example, if an operator constructor is being displayed, then the precedence requirement+ -- for its arguments will be the precedence of the operator. Meaning, if the argument+ -- binds looser than the surrounding operator, then it will require parentheses.+ --+ -- Note that function/constructor application has an effective precedence of 10.+ --+ -- === Examples+ --+ -- > instance Display a => Display (Maybe a) where+ -- > -- In this instance, we define 'displayPrec' rather than 'displayBuilder' as we need to decide+ -- > -- whether or not to surround ourselves in parentheses based on the surrounding context.+ -- > -- If the precedence parameter is higher than 10 (the precedence of constructor application)+ -- > -- then we indeed need to surround ourselves in parentheses to avoid malformed outputs+ -- > -- such as @Just Just 5@.+ -- > -- We then set the precedence parameter of the inner 'displayPrec' to 11, as even+ -- > -- constructor application is not strong enough to avoid parentheses.+ -- > displayPrec _ Nothing = "Nothing"+ -- > displayPrec prec (Just a) = displayParen (prec > 10) $ "Just " <> displayPrec 11 a+ --+ -- > data Pair a b = a :*: b+ -- > infix 5 :*: -- arbitrary choice of precedence+ -- > instance (Display a, Display b) => Display (Pair a b) where+ -- > displayPrec prec (a :*: b) = displayParen (prec > 5) $ displayPrec 6 a <> " :*: " <> displayPrec 6 b+ displayPrec+ :: Int -- ^ The precedence level passed in by the surrounding context+ -> a+ -> Builder+ displayPrec _ = displayBuilder+++-- | Convert a value to a readable 'Text'.+--+-- === Examples+-- >>> display 3+-- "3"+--+-- >>> display True+-- "True"+--+-- @since 0.0.1.0+display :: Display a => a -> Text+display a = TL.toStrict $ TB.toLazyText $ displayBuilder a++-- | 🚫 You should not try to display functions!+--+-- 💡 Write a 'newtype' wrapper that represents your domain more accurately.+-- If you are not consciously trying to use `display` on a function,+-- make sure that you are not missing an argument somewhere.+--+-- @since 0.0.1.0+instance CannotDisplayBareFunctions => Display (a -> b) where+ displayBuilder = undefined++-- | @since 0.0.1.0+type family CannotDisplayBareFunctions :: Constraint where+ CannotDisplayBareFunctions = TypeError+ ( 'Text "🚫 You should not try to display functions!" ':$$:+ 'Text "💡 Write a 'newtype' wrapper that represents your domain more accurately." ':$$:+ 'Text " If you are not consciously trying to use `display` on a function," ':$$:+ 'Text " make sure that you are not missing an argument somewhere."+ )++-- | 🚫 You should not try to display strict ByteStrings!+--+-- 💡 Always provide an explicit encoding.+-- Use 'Data.Text.Encoding.decudeUtf8'' or 'Data.Text.Encoding.decudeUtf8With' to convert from UTF-8+--+-- @since 0.0.1.0+instance CannotDisplayByteStrings => Display ByteString where+ displayBuilder = undefined++-- | 🚫 You should not try to display lazy ByteStrings!+--+-- 💡 Always provide an explicit encoding.+-- Use 'Data.Text.Encoding.decudeUtf8'' or 'Data.Text.Encoding.decudeUtf8With' to convert from UTF-8+--+-- @since 0.0.1.0+instance CannotDisplayByteStrings => Display BL.ByteString where+ displayBuilder = undefined++type family CannotDisplayByteStrings :: Constraint where+ CannotDisplayByteStrings = TypeError+ ( 'Text "🚫 You should not try to display ByteStrings!" ':$$:+ 'Text "💡 Always provide an explicit encoding" ':$$:+ 'Text "Use 'Data.Text.Encoding.decudeUtf8'' or 'Data.Text.Encoding.decudeUtf8With' to convert from UTF-8"+ )++-- | A utility function that surrounds the given 'Builder' with parentheses when the Bool parameter is True.+-- Useful for writing instances that may require nesting. See the 'displayPrec' documentation for more+-- information.+--+-- @since 0.0.1.0+displayParen :: Bool -> Builder -> Builder+displayParen b txt = if b then "(" <> txt <> ")" else txt++-- | This wrapper allows you to create an opaque instance for your type,+-- useful for redacting sensitive content like tokens or passwords.+--+-- === Example+--+-- > data UserToken = UserToken UUID+-- > deriving Display+-- > via (OpaqueInstance "[REDACTED]" UserToken)+--+-- > display $ UserToken "7a01d2ce-31ff-11ec-8c10-5405db82c3cd"+-- > "[REDACTED]"+--+-- @since 0.0.1.0+newtype OpaqueInstance (str :: Symbol) (a :: Type) = Opaque a++-- | This wrapper allows you to create an opaque instance for your type,+-- useful for redacting sensitive content like tokens or passwords.+--+-- @since 0.0.1.0+instance KnownSymbol str => Display (OpaqueInstance str a) where+ displayBuilder _ = TB.fromString $ symbolVal (Proxy @str)++-- | This wrapper allows you to rely on a pre-existing 'Show' instance in order to+-- derive 'Display' from it.+--+-- === Example+--+-- > data AutomaticallyDerived = AD+-- > -- We derive 'Show'+-- > deriving stock Show+-- > -- We take advantage of the 'Show' instance to derive 'Display' from it+-- > deriving Display+-- > via (ShowInstance AutomaticallyDerived)+--+-- @since 0.0.1.0+newtype ShowInstance (a :: Type)+ = ShowInstance a+ deriving newtype+ ( Show -- ^ @since 0.0.1.0+ )++-- | This wrapper allows you to rely on a pre-existing 'Show' instance in order to derive 'Display' from it.+--+-- @since 0.0.1.0+instance Show e => Display (ShowInstance e) where+ displayBuilder s = TB.fromString $ show s++-- @since 0.0.1.0+newtype DisplayDecimal e+ = DisplayDecimal e+ deriving newtype+ (Integral, Real, Enum, Ord, Num, Eq)++-- @since 0.0.1.0+instance Integral e => Display (DisplayDecimal e) where+ displayBuilder = TB.decimal++-- @since 0.0.1.0+newtype DisplayRealFloat e+ = DisplayRealFloat e+ deriving newtype+ (RealFloat, RealFrac, Real, Ord, Eq, Num, Fractional, Floating)++-- @since 0.0.1.0+instance RealFloat e => Display (DisplayRealFloat e) where+ displayBuilder = TB.realFloat++-- | @since 0.0.1.0+deriving via (ShowInstance ()) instance Display ()++-- | @since 0.0.1.0+deriving via (ShowInstance Bool) instance Display Bool++-- | @since 0.0.1.0+instance Display Char where+ displayBuilder '\'' = "'\\''"+ displayBuilder c = "'" <> TB.singleton c <> "\'"+ -- 'displayList' is overloaded, so that when the @Display [a]@ instance calls 'displayList',+ -- we end up with a nice string enclosed between double quotes.+ displayList cs = TB.fromString $ "\"" <> showLitString cs "\""++-- | Lazy 'TL.Text'+--+-- @since 0.0.1.0+instance Display TL.Text where+ displayBuilder = TB.fromLazyText++-- | Strict 'Data.Text.Text'+--+-- @since 0.0.1.0+instance Display Text where+ displayBuilder = TB.fromText++-- | @since 0.0.1.0+instance Display a => Display [a] where+ {-# SPECIALISE instance Display [String] #-}+ {-# SPECIALISE instance Display [Char] #-}+ {-# SPECIALISE instance Display [Int] #-}+ -- In this instance, 'displayBuilder' is defined in terms of 'displayList', which for most types+ -- is defined as the default written in the class declaration.+ -- But when @a ~ Char@, there is an explicit implementation that is selected instead, which+ -- provides the rendering of the character string between double quotes.+ displayBuilder = displayList++-- | @since 0.0.1.0+instance Display a => Display (NonEmpty a) where+ displayBuilder (a :| as) = displayBuilder a <> TB.fromString " :| " <> displayBuilder as++-- | @since 0.0.1.0+instance Display a => Display (Maybe a) where+ -- In this instance, we define 'displayPrec' rather than 'displayBuilder' as we need to decide+ -- whether or not to surround ourselves in parentheses based on the surrounding context.+ -- If the precedence parameter is higher than 10 (the precedence of constructor application)+ -- then we indeed need to surround ourselves in parentheses to avoid malformed outputs+ -- such as @Just Just 5@.+ -- We then set the precedence parameter of the inner 'displayPrec' to 11, as even+ -- constructor application is not strong enough to avoid parentheses.+ displayPrec _ Nothing = "Nothing"+ displayPrec prec (Just a) = displayParen (prec > 10) $ "Just " <> displayPrec 11 a++-- | @since 0.0.1.0+deriving via (DisplayRealFloat Double) instance Display Double++-- | @since 0.0.1.0+deriving via (DisplayRealFloat Float) instance Display Float++-- | @since 0.0.1.0+deriving via (DisplayDecimal Int) instance Display Int++-- | @since 0.0.1.0+deriving via (DisplayDecimal Int8) instance Display Int8++-- | @since 0.0.1.0+deriving via (DisplayDecimal Int16) instance Display Int16++-- | @since 0.0.1.0+deriving via (DisplayDecimal Int32) instance Display Int32++-- | @since 0.0.1.0+deriving via (DisplayDecimal Int64) instance Display Int64++-- | @since 0.0.1.0+deriving via (DisplayDecimal Integer) instance Display Integer++-- | @since 0.0.1.0+deriving via (DisplayDecimal Word) instance Display Word++-- | @since 0.0.1.0+deriving via (DisplayDecimal Word8) instance Display Word8++-- | @since 0.0.1.0+deriving via (DisplayDecimal Word16) instance Display Word16++-- | @since 0.0.1.0+deriving via (DisplayDecimal Word32) instance Display Word32++-- | @since 0.0.1.0+deriving via (DisplayDecimal Word64) instance Display Word64++-- | @since 0.0.1.0+deriving via (ShowInstance IOException) instance Display IOException++-- | @since 0.0.1.0+deriving via (ShowInstance SomeException) instance Display SomeException++-- | @since 0.0.1.0+instance (Display a, Display b) => Display (a, b) where+ displayBuilder (a, b) = "(" <> displayBuilder a <> "," <> displayBuilder b <> ")"++-- | @since 0.0.1.0+instance (Display a, Display b, Display c) => Display (a, b, c) where+ displayBuilder (a, b, c) = "(" <> displayBuilder a <> "," <> displayBuilder b <> "," <> displayBuilder c <> ")"++-- | @since 0.0.1.0+instance (Display a, Display b, Display c, Display d) => Display (a, b, c, d) where+ displayBuilder (a, b, c, d) = "(" <> displayBuilder a <> "," <> displayBuilder b <> "," <> displayBuilder c <> "," <> displayBuilder d <> ")"++-- $designChoices+--+-- === A “Lawless Typeclass”+--+-- The `Display` typeclass does not contain any law. This is a controversial choice for some people,+-- but the truth is that there are not any laws to ask of the consumer that are not already enforced+-- by the type system and the internals of the `Data.Text.Internal.Text` type.+--+-- === "🚫 You should not try to display functions!"+--+-- Sometimes, when using the library, you may encounter this message:+--+-- > • 🚫 You should not try to display functions!+-- > 💡 Write a 'newtype' wrapper that represents your domain more accurately.+-- > If you are not consciously trying to use `display` on a function,+-- > make sure that you are not missing an argument somewhere.+--+-- The `display` library does not allow the definition and usage of `Display` on+-- bare function types (@(a -> b)@).+-- Experience and time have shown that due to partial application being baked in the language,+-- many users encounter a partial application-related error message when a simple missing+-- argument to a function is the root cause.+--+-- There may be legitimate uses of a `Display` instance on a function type.+-- But these usages are extremely dependent on their domain of application.+-- That is why it is best to wrap them in a newtype that can better+-- express and enforce the domain.+--+-- === "🚫 You should not try to display ByteStrings!"+--+-- An arbitrary ByteStrings cannot be safely converted to text without prior knowledge of its encoding.+--+-- As such, in order to avoid dangerously blind conversions, it is recommended to use a specialised+-- function such as `Data.Text.Encoding.decudeUtf8'` or `Data.Text.Encoding.decudeUtf8With` if you wish to turn a UTF8-encoded ByteString+-- to Text.
+ test/Main.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE DataKinds #-}+{-# OPTIONS_GHC -fdefer-type-errors -Wno-deferred-type-errors #-}++module Main where++import Data.ByteString+import Data.List.NonEmpty+import Data.Text (Text)+import Test.Hspec+import Test.ShouldNotTypecheck (shouldNotTypecheck)+import qualified Data.List.NonEmpty as NE+import qualified Data.Text as T++import Data.Text.Display++main :: IO ()+main = hspec spec++data AutomaticallyDerived = AD+ deriving stock Show+ deriving Display via (ShowInstance AutomaticallyDerived)++data ManualType = MT Int++instance Display ManualType where+ displayPrec prec (MT i) = displayParen (prec > 10) $ "MT " <> displayPrec 11 i++data OpaqueType = OpaqueType Int+ deriving Display+ via (OpaqueInstance "<opaque>" OpaqueType)++spec :: Spec+spec = do+ describe "Display Tests" $ do+ it "Display instance for Text stays the same" $+ display ("3" :: Text) `shouldBe` ("3" :: Text)+ it "Deriving via its own Show instance works" $+ T.unpack (display AD) `shouldBe` show AD+ it "Opaque types stay opaque" $+ display (OpaqueType 3 :: OpaqueType) `shouldBe` "<opaque>"+ it "Manual instance is stays the same" $+ display (MT 32) `shouldBe` "MT 32"+ it "List instance is equivalent to Show" $ do+ let list = [1 .. 5] :: [Int]+ T.unpack (display list) `shouldBe` show list+ it "Single-element List instance is equivalent to Show" $ do+ let list = [1] :: [Int]+ T.unpack (display list) `shouldBe` show list+ it "NonEmpty instance is equivalent to Show" $ do+ let ne = NE.fromList [1 .. 5] :: NonEmpty Int+ T.unpack (display ne) `shouldBe` show ne+ it "Just True instance is equivalent to Show" $ do+ T.unpack (display (Just True)) `shouldBe` show (Just True)+ it "Nested Maybe instance is equivalent to Show" $ do+ let nestedMaybe = Just (Just 5) :: Maybe (Maybe Int)+ T.unpack (display nestedMaybe) `shouldBe` show nestedMaybe+ it "Nothing instance is equivalent to Show" $ do+ T.unpack (display (Nothing @Bool)) `shouldBe` show (Nothing @Bool)+ it "String instance is equivalent to Show" $ do+ let string = "Bonjour \"tout le monde\" !" :: String+ T.unpack (display string) `shouldBe` show string+ it "Char 'c' instance is equivalent to Show" $ do+ T.unpack (display 'c') `shouldBe` show 'c'+ it "Char '\'' instance is equivalent to Show" $ do+ T.unpack (display '\'') `shouldBe` show '\''+ it "2-Tuple instance is equivalent to Show" $ do+ let tuple = (1 :: Int, True)+ T.unpack (display tuple) `shouldBe` show tuple+ it "3-Tuple instance is equivalent to Show" $ do+ let tuple = (1 :: Int, True, "hahahha" :: String)+ T.unpack (display tuple) `shouldBe` show tuple++ it "Should not compile for a function instance" $+ shouldNotTypecheck (display id) `shouldThrow` anyErrorCall+ it "Should not compile for ByteStrings" $+ let bs = "badstring" :: ByteString+ in shouldNotTypecheck (display bs) `shouldThrow` anyErrorCall++ describe "displayParen tests" $ do+ it "surrounds with parens when True" $+ displayParen True "foo" `shouldBe` "(foo)"+ it "doesn't surround with parens when False" $+ displayParen False "foo" `shouldBe` "foo"
+ text-display.cabal view
@@ -0,0 +1,61 @@+cabal-version: 3.0+name: text-display+version: 0.0.1.0+category: Text+synopsis: A typeclass for user-facing output+description:+ The 'Display' typeclass provides a solution for user-facing output that does not have to abide by the rules of the Show typeclass.++homepage: https://github.com/haskell-text/text-display#readme+bug-reports: https://github.com/haskell-text/text-display/issues+author: Hécate Moonlight+maintainer: Hécate Moonlight+license: MIT+build-type: Simple+tested-with: GHC ==8.8.4 || ==8.10.7 || ==9.0.1 || ==9.2.1+extra-source-files:+ CHANGELOG.md+ LICENSE+ README.md++source-repository head+ type: git+ location: https://github.com/haskell-text/text-display++common common-extensions+ default-language: Haskell2010++common common-ghc-options+ ghc-options:+ -Wall -Wcompat -Widentities -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Wpartial-fields -Wredundant-constraints+ -fhide-source-paths -Wno-unused-do-bind -fwrite-ide-info+ -hiedir=.hie -haddock++common common-rts-options+ ghc-options: -rtsopts -threaded -with-rtsopts=-N++library+ import: common-extensions+ import: common-ghc-options+ hs-source-dirs: src+ exposed-modules: Data.Text.Display+ build-depends:+ , base >= 4.12 && <= 5+ , bytestring ^>=0.11+ , text ^>=1.2++test-suite text-display-test+ import: common-extensions+ import: common-ghc-options+ import: common-rts-options+ type: exitcode-stdio-1.0+ main-is: Main.hs+ hs-source-dirs: test+ build-depends:+ , base+ , bytestring+ , text-display+ , hspec+ , text+ , should-not-typecheck