text-encode (empty) → 0.1.0.0
raw patch · 13 files changed
+1060/−0 lines, 13 filesdep +aesondep +basedep +bytestring
Dependencies added: aeson, base, bytestring, casing, cassava, http-api-data, http-types, persistent, postgresql-simple, sqlite-simple, text, text-encode
Files
- LICENSE +26/−0
- README.md +17/−0
- src/dev-main/Main.hs +2/−0
- src/dev/DevMain.hs +72/−0
- src/text-encode-aeson/Text/Encode/Aeson.hs +60/−0
- src/text-encode-cassava/Text/Encode/Cassava.hs +39/−0
- src/text-encode-http-api-data/Text/Encode/HttpApiData.hs +59/−0
- src/text-encode-persistent/Text/Encode/Persistent.hs +42/−0
- src/text-encode-postgresql-simple/Text/Encode/PostgresqlSimple.hs +32/−0
- src/text-encode-sqlite-simple/Text/Encode/SqliteSimple.hs +33/−0
- src/text-encode/Text/Encode.hs +190/−0
- src/text-encode/Text/Encode/Casing.hs +270/−0
- text-encode.cabal +218/−0
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright 2025 Daniel Brice++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its contributors+ may be used to endorse or promote products derived from this software+ without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,17 @@+# text-encode++Classes and newtypes for deriving uniform textual encodings.++This repository hosts seven packages.+The core package is `text-encode`.+Subsidiary packages are provided in order to avoid unnecessary dependencies in downstream packages.+The subsidiary packages re-export the modules from the core package, so there is no need to import the core package if you are importing one or more of the subsidiary packages.++- `text-encode-aeson`+- `text-encode-cassava`+- `text-encode-http-api-data`+- `text-encode-persistent`+- `text-encode-postgresql-simple`+- `text-encode-sqlite-simple`++See the module documentation in `Text.Encode` for usage.
+ src/dev-main/Main.hs view
@@ -0,0 +1,2 @@+module Main (main) where+import DevMain (main)
+ src/dev/DevMain.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE OverloadedStrings #-}++module DevMain (main) where++import Text.Encode+import Text.Encode.Aeson ()+import Text.Encode.Cassava ()+import Text.Encode.Persistent ()++import Data.Aeson qualified as Aeson+import Data.ByteString.Lazy (LazyByteString)+import Data.Csv qualified as Cassava+import Data.Functor (($>))+import Database.Persist qualified as Persistent++data MyType = MyTypeFoo | MyTypeBar | MyTypeFooBar+ deriving stock (Read, Show, Eq, Ord, Bounded, Enum)+ deriving TextEncode+ via DeriveTextEncode (Cased 'Pascal 'QuietSnake (DropPrefix "MyType" ReadShowEncode)) MyType+ deriving+ ( Aeson.FromJSON, Aeson.ToJSON+ , Cassava.FromField, Cassava.ToField+ , Persistent.PersistField+ )+ via ViaTextEncode MyType++data YourType = YourTypeFoo | YourTypeBar | YourTypeFooBar+ deriving stock (Read, Show, Eq, Ord, Bounded, Enum)+ deriving TextEncode+ via DeriveTextEncode BoundedEnumEncode YourType+ deriving+ ( Aeson.FromJSON, Aeson.ToJSON+ , Cassava.FromField, Cassava.ToField+ , Persistent.PersistField+ )+ via ViaTextEncode YourType++myCases :: [(MyType, LazyByteString)]+myCases =+ [ (MyTypeFoo, "foo")+ , (MyTypeBar, "bar")+ , (MyTypeFooBar, "foo_bar")+ ]++yourCases :: [(YourType, LazyByteString)]+yourCases =+ [ (YourTypeFoo, "0")+ , (YourTypeBar, "1")+ , (YourTypeFooBar, "2")+ ]++quoted :: LazyByteString -> LazyByteString+quoted = ("\"" <>) . (<> "\"")++checkCase :: (Eq a, Show a, Aeson.FromJSON a, Aeson.ToJSON a) => (a, LazyByteString) -> IO Int+checkCase (dec, enc) = do+ let enc' = quoted enc+ putStr $ "Checking:\t" <> show dec <> " <-> " <> show enc+ let encodingWorks = Aeson.encode dec == enc'+ let decodingWorks = Aeson.decode enc' == Just dec+ if encodingWorks && decodingWorks+ then putStrLn "\tPassed." $> 0+ else putStrLn "\tFAILED!" $> 1++main :: IO ()+main = do+ myFailures <- traverse checkCase myCases+ yourFailures <- traverse checkCase yourCases+ let failures = sum (myFailures <> yourFailures)+ if failures == 0+ then putStrLn "All tests passed."+ else putStrLn $ "There were " <> show failures <> " failures."
+ src/text-encode-aeson/Text/Encode/Aeson.hs view
@@ -0,0 +1,60 @@+module Text.Encode.Aeson (+ module Text.Encode,+ JsonEncode,+) where++import Text.Encode++import Data.Aeson (+ FromJSON (..),+ FromJSONKey (..),+ ToJSON (..),+ ToJSONKey (..),+ Value (..),+ eitherDecode,+ encode+ )+import Data.ByteString.Lazy.Char8 qualified as LC8+import Data.Coerce (coerce)+import Data.Text.Lazy qualified as LT+import Data.Text.Lazy.Encoding qualified as LTE+import Data.Typeable (Typeable)++instance (TextEncode a, Typeable a) => FromJSON (ViaTextEncode a) where+ parseJSON (String txt) = either fail pure $ decodeText txt+ parseJSON raw = fail $ typedError @a "parseJSON" "Expected String, got " $ encode raw++instance TextEncode a => ToJSON (ViaTextEncode a) where+ toJSON = String . encodeText+ toEncoding = toEncoding . encodeText++ {-# INLINE toJSON #-}+ {-# INLINE toEncoding #-}++instance (TextEncode a, Typeable a) => FromJSONKey (ViaTextEncode a)+instance TextEncode a => ToJSONKey (ViaTextEncode a)++data JsonEncode++instance (FromJSON a, ToJSON a) => TextEncode (DeriveTextEncode JsonEncode a) where+ encodeLazyByteString = coerce $ encode @a+ decodeLazyByteString = coerce $ eitherDecode @a+ encodeByteString = LC8.toStrict . encodeLazyByteString+ decodeByteString = decodeLazyByteString . LC8.fromStrict+ encodeString = LC8.unpack <$> encodeLazyByteString+ decodeString = decodeLazyByteString . LC8.pack+ encodeLazyText = LTE.decodeLatin1 . encodeLazyByteString+ decodeLazyText = decodeLazyByteString . LTE.encodeUtf8+ encodeText = LT.toStrict . encodeLazyText+ decodeText = decodeLazyText . LT.fromStrict++ {-# INLINE encodeLazyByteString #-}+ {-# INLINE decodeLazyByteString #-}+ {-# INLINE encodeByteString #-}+ {-# INLINE decodeByteString #-}+ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}+ {-# INLINE encodeLazyText #-}+ {-# INLINE decodeLazyText #-}+ {-# INLINE encodeText #-}+ {-# INLINE decodeText #-}
+ src/text-encode-cassava/Text/Encode/Cassava.hs view
@@ -0,0 +1,39 @@+module Text.Encode.Cassava (+ module Text.Encode,+ CsvEncode,+) where++import Text.Encode++import Data.ByteString.Char8 qualified as C8+import Data.Coerce (coerce)+import Data.Csv (FromField (..), ToField (..), runParser)+import Data.Text.Encoding qualified as T++instance TextEncode a => FromField (ViaTextEncode a) where+ parseField = either fail pure . coerce (decodeByteString @a)++ {-# INLINE parseField #-}++instance TextEncode a => ToField (ViaTextEncode a) where+ toField = coerce $ encodeByteString @a++ {-# INLINE toField #-}++data CsvEncode++instance (FromField a, ToField a) => TextEncode (DeriveTextEncode CsvEncode a) where+ encodeByteString = coerce $ toField @a+ decodeByteString = coerce $ runParser . parseField @a++ encodeText = T.decodeLatin1 . encodeByteString+ decodeText = decodeByteString . T.encodeUtf8+ encodeString = C8.unpack . encodeByteString+ decodeString = decodeByteString . C8.pack++ {-# INLINE encodeByteString #-}+ {-# INLINE decodeByteString #-}+ {-# INLINE encodeText #-}+ {-# INLINE decodeText #-}+ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}
+ src/text-encode-http-api-data/Text/Encode/HttpApiData.hs view
@@ -0,0 +1,59 @@+module Text.Encode.HttpApiData (+ module Text.Encode,+ UrlEncode,+) where++import Text.Encode++import Data.Bifunctor (first)+import Data.Coerce (coerce)+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+import Web.FormUrlEncoded (FromFormKey (..), ToFormKey (..))+import Web.HttpApiData (FromHttpApiData (..), ToHttpApiData (..))++instance TextEncode a => ToFormKey (ViaTextEncode a) where+ toFormKey = coerce $ encodeText @a++ {-# INLINE toFormKey #-}++instance TextEncode a => FromFormKey (ViaTextEncode a) where+ parseFormKey = coerce $ first T.pack . decodeText @a++ {-# INLINE parseFormKey #-}++instance TextEncode a => ToHttpApiData (ViaTextEncode a) where+ toUrlPiece = coerce $ encodeText @a+ toHeader = TE.encodeUtf8 . toUrlPiece+ toQueryParam = toUrlPiece++ {-# INLINE toUrlPiece #-}+ {-# INLINE toHeader #-}+ {-# INLINE toQueryParam #-}++instance TextEncode a => FromHttpApiData (ViaTextEncode a) where+ parseUrlPiece = coerce $ first T.pack . decodeText @a+ parseHeader = parseUrlPiece . TE.decodeLatin1+ parseQueryParam = parseUrlPiece++ {-# INLINE parseUrlPiece #-}+ {-# INLINE parseHeader #-}+ {-# INLINE parseQueryParam #-}++data UrlEncode++instance (FromHttpApiData a, ToHttpApiData a) => TextEncode (DeriveTextEncode UrlEncode a) where+ encodeText = coerce $ toUrlPiece @a+ decodeText = coerce $ first T.unpack . parseUrlPiece @a++ encodeString = T.unpack . encodeText+ decodeString = decodeText . T.pack+ encodeByteString = TE.encodeUtf8 . encodeText+ decodeByteString = decodeText . TE.decodeLatin1++ {-# INLINE encodeText #-}+ {-# INLINE decodeText #-}+ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}+ {-# INLINE encodeByteString #-}+ {-# INLINE decodeByteString #-}
+ src/text-encode-persistent/Text/Encode/Persistent.hs view
@@ -0,0 +1,42 @@+module Text.Encode.Persistent (+ module Text.Encode,+ PersistentEncode,+) where++import Text.Encode++import Data.Bifunctor+import Data.Coerce+import Data.Text qualified as T+import Data.Text.Encoding qualified as TE+import Data.Typeable+import Database.Persist.Class+import Database.Persist.PersistValue++instance (TextEncode a, Typeable a) => PersistField (ViaTextEncode a) where+ {-# INLINE toPersistValue #-}+ toPersistValue = coerce $ PersistText . encodeText @(ViaTextEncode a)++ fromPersistValue (PersistText bs) = coerce $ first T.pack $ decodeText @(ViaTextEncode a) bs+ fromPersistValue x = Left $ T.pack $ "DeriveTextEncode PersistentEncode requires PersistText: " <> show x++data PersistentEncode++instance PersistField a => TextEncode (DeriveTextEncode PersistentEncode a) where+ encodeText (DeriveTextEncode x) =+ case toPersistValue x of+ (PersistText txt) -> txt+ x' -> error $ "DeriveTextEncode PersistentEncode requires PersistText: " <> show x'++ decodeText = bimap T.unpack DeriveTextEncode . fromPersistValue . PersistText++ encodeByteString = TE.encodeUtf8 . encodeText+ decodeByteString = decodeText . TE.decodeLatin1+ encodeString = T.unpack . encodeText+ decodeString = decodeText . T.pack++ {-# INLINE decodeText #-}+ {-# INLINE encodeByteString #-}+ {-# INLINE decodeByteString #-}+ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}
+ src/text-encode-postgresql-simple/Text/Encode/PostgresqlSimple.hs view
@@ -0,0 +1,32 @@+module Text.Encode.PostgresqlSimple (+ module Text.Encode,+ TextEncodePostgresqlSimpleError,+) where++import Text.Encode++import Control.Exception (Exception)+import Control.Monad ((<=<))+import Data.Coerce (coerce)+import Data.Text qualified as T+import Data.Typeable (Typeable)+import Database.PostgreSQL.Simple.FromField (FromField (..), conversionError)+import Database.PostgreSQL.Simple.ToField (ToField (..))++newtype TextEncodePostgresqlSimpleError = TextEncodePostgresqlSimpleError String+ deriving (Show)++instance Exception TextEncodePostgresqlSimpleError++instance (TextEncode a, Typeable a) => FromField (ViaTextEncode a) where+ fromField f =+ either (conversionError . TextEncodePostgresqlSimpleError) (pure . ViaTextEncode)+ . decodeText @a+ <=< fromField @T.Text f++ {-# INLINE fromField #-}++instance (TextEncode a, Typeable a) => ToField (ViaTextEncode a) where+ toField = coerce $ toField . encodeText @a++ {-# INLINE toField #-}
+ src/text-encode-sqlite-simple/Text/Encode/SqliteSimple.hs view
@@ -0,0 +1,33 @@+module Text.Encode.SqliteSimple (+ module Text.Encode,+ TextEncodeSqliteSimpleError,+) where++import Text.Encode++import Control.Exception (Exception, toException)+import Control.Monad ((<=<))+import Data.Coerce (coerce)+import Data.Text qualified as T+import Data.Typeable (Typeable)+import Database.SQLite.Simple.FromField (FromField (..))+import Database.SQLite.Simple.Ok (Ok (..))+import Database.SQLite.Simple.ToField (ToField (..))++newtype TextEncodeSqliteSimpleError = TextEncodeSqliteSimpleError String+ deriving (Show)++instance Exception TextEncodeSqliteSimpleError++instance (TextEncode a, Typeable a) => FromField (ViaTextEncode a) where+ fromField =+ either (Errors . pure . toException . TextEncodeSqliteSimpleError) (pure . ViaTextEncode)+ . decodeText @a+ <=< fromField @T.Text++ {-# INLINE fromField #-}++instance (TextEncode a, Typeable a) => ToField (ViaTextEncode a) where+ toField = coerce $ toField . encodeText @a++ {-# INLINE toField #-}
+ src/text-encode/Text/Encode.hs view
@@ -0,0 +1,190 @@+-- | This module provides classes and newtypes for deriving uniform textual encodings.+--+-- An instance @'TextEncode' A@ defines a uniform textual representation for data of type @A@.+-- Writing instances is usually straightforward, or they may be derived using 'DeriveTextEncode'.+-- Use 'ViaTextEncode' to derive instances of other classes based on a type's 'TextEncode' instance.+--+-- For example,+-- @+-- import Text.Encode+--+-- import Text.Encode.Aeson ()+-- import Text.Encode.Cassava ()+-- import Text.Encode.Persistent ()+--+-- data MyType = MyTypeFoo | MyTypeBar | MyTypeFooBar+-- deriving stock (Read, Show)+-- deriving 'TextEncode'+-- via 'DeriveTextEncode' ('Cased' 'Pascal' 'QuietSnake' ('DropPrefix' "MyType" 'ReadShowEncode')) MyType+-- deriving+-- ( Aeson.FromJSON, Aeson.ToJSON+-- , Cassava.FromField, Cassava.ToField+-- , Persistent.PersistField+-- )+-- via 'ViaTextEncode' MyType+-- @+--+-- This will derive a 'TextEncode' instance for @MyType@ based on the stock 'Read' and 'Show' instances,+-- modified by the 'Cased' and 'DropPrefix' options. Uniform, mutually-consistent instances for Aeson,+-- Cassava, and Persistent classes are then derived from the derived 'TextEncode' instance.+module Text.Encode (+ -- * Textual Encodings+ TextEncode (..),+ -- * Deriving 'TextEncode'+ DeriveTextEncode (..),+ -- ** Base encodings+ BoundedEnumEncode,+ ReadShowEncode,+ -- ** Add or remove prefixes+ DropPrefix,+ AddPrefix,+ -- ** Transform casing+ Casing (..),+ CaseConversion (..),+ Cased,+ -- * Deriving other classes+ ViaTextEncode (..),+ -- * Utilities+ typedError,+ decodeError,+ maybeDecode,+) where++import Text.Encode.Casing++import Control.Monad (guard, (<=<))+import Data.ByteString.Char8 qualified as C8+import Data.ByteString.Lazy.Char8 qualified as LC8+import Data.Coerce (coerce)+import Data.Kind (Type)+import Data.Text qualified as T+import Data.Text.Lazy qualified as LT+import Data.Typeable (Typeable, Proxy (..), typeRep)+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)+import Text.Read (readMaybe)++-- | An instance @'TextEncode' a@ defines a uniform textual representation for data of type @a@.+--+-- Instances are assumed to follow these mutual-coherence properties:+-- * @decodeString . encodeString === Right@+-- * @fmap encodeString . decodeString === Right@+-- * @decodeText . "Data.Text".'pack' . encodeString === Right@+-- * @fmap encodeString . decodeByteString === Right . "Data.ByteString.Char8".'unpack'@+-- and permutations thereof.+--+-- Mutually-coherent default definitions of the methods are provided, though+-- users may supply their own implementations for performance reasons.+-- It is the user's responsibility to ensure that these definitions mutually cohere.+class TextEncode a where+ {-# MINIMAL encodeString, decodeString #-}+ encodeString :: a -> String+ decodeString :: String -> Either String a+ encodeByteString :: a -> C8.ByteString+ decodeByteString :: C8.ByteString -> Either String a+ encodeLazyByteString :: a -> LC8.ByteString+ decodeLazyByteString :: LC8.ByteString -> Either String a+ encodeText :: a -> T.Text+ decodeText :: T.Text -> Either String a+ encodeLazyText :: a -> LT.Text+ decodeLazyText :: LT.Text -> Either String a++ encodeText = T.pack . encodeString+ decodeText = decodeString . T.unpack+ encodeLazyText = LT.fromStrict . encodeText+ decodeLazyText = decodeText . LT.toStrict+ encodeByteString = C8.pack . encodeString+ decodeByteString = decodeString . C8.unpack+ encodeLazyByteString = LC8.fromStrict . encodeByteString+ decodeLazyByteString = decodeByteString . LC8.toStrict++ {-# INLINE encodeByteString #-}+ {-# INLINE decodeByteString #-}+ {-# INLINE encodeLazyByteString #-}+ {-# INLINE decodeLazyByteString #-}+ {-# INLINE encodeText #-}+ {-# INLINE decodeText #-}+ {-# INLINE encodeLazyText #-}+ {-# INLINE decodeLazyText #-}++-- | Derive instances of various classes based on an instance of 'TextEncode'.+newtype ViaTextEncode a = ViaTextEncode a+ deriving TextEncode via a++instance TextEncode a => Show (ViaTextEncode a) where+ show (ViaTextEncode a) = encodeString a++instance TextEncode a => Read (ViaTextEncode a) where+ readsPrec _ = either (const []) (pure . (, "") . ViaTextEncode) . decodeString++newtype DeriveTextEncode (opt :: Type) a = DeriveTextEncode a+ deriving (Eq, Ord, Read, Show, Bounded, Enum) via a++data BoundedEnumEncode++instance (Bounded a, Enum a, Typeable a) => TextEncode (DeriveTextEncode BoundedEnumEncode a) where+ encodeString = show . fromEnum+ decodeString = coerce $ maybeDecode @a $ toEnumMaybe <=< readMaybe++ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}++data ReadShowEncode++instance (Read a, Show a, Typeable a) => TextEncode (DeriveTextEncode ReadShowEncode a) where+ encodeString = show+ decodeString = coerce $ maybeDecode @a readMaybe++ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}++data DropPrefix (pfx :: Symbol) (opt :: Type)++instance (KnownSymbol pfx, TextEncode (DeriveTextEncode opt a)) => TextEncode (DeriveTextEncode (DropPrefix pfx opt) a) where+ encodeString = coerce $ dropPfx @pfx . encodeString @(DeriveTextEncode opt a)+ decodeString = coerce $ decodeString @(DeriveTextEncode opt a) . (symbolVal (Proxy @pfx) <>)++ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}++data AddPrefix (pfx :: Symbol) (opt :: Type)++instance (KnownSymbol pfx, TextEncode (DeriveTextEncode opt a)) => TextEncode (DeriveTextEncode (AddPrefix pfx opt) a) where+ encodeString = coerce $ (symbolVal (Proxy @pfx) <>) . encodeString @(DeriveTextEncode opt a)+ decodeString = coerce $ decodeString @(DeriveTextEncode opt a) . dropPfx @pfx++ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}++data Cased (decoding :: Casing) (encoding :: Casing) (opt :: Type)++instance (CaseConversion d e, TextEncode (DeriveTextEncode opt a)) => TextEncode (DeriveTextEncode (Cased d e opt) a) where+ encodeString = coerce $ encoding @d @e . encodeString @(DeriveTextEncode opt a)+ decodeString = coerce $ decodeString @(DeriveTextEncode opt a) . decoding @d @e++ {-# INLINE encodeString #-}+ {-# INLINE decodeString #-}++type FunctionName = String+type Message = String+type Input a = a++typedError :: forall a b. (Typeable a, Show b) => FunctionName -> Message -> Input b -> String+typedError fn msg input = mconcat [fn, " @", show (typeRep $ Proxy @a), ": ", msg, show input]++decodeError :: forall a b. Typeable a => Input String -> Either String b+decodeError input = Left $ typedError @a "decode" "Failed to decode " input++maybeDecode :: forall a. Typeable a => (String -> Maybe a) -> Input String -> Either String a+maybeDecode f raw = maybe (decodeError @a raw) Right $ f raw++toEnumMaybe :: forall a. (Bounded a, Enum a) => Int -> Maybe a+toEnumMaybe n = toEnum n <$ guard (fromEnum (minBound @a) <= n && n <= fromEnum (maxBound @a))++dropPfx :: forall pfx. KnownSymbol pfx => String -> String+dropPfx = drop $ length $ symbolVal $ Proxy @pfx++{-# INLINE typedError #-}+{-# INLINE decodeError #-}+{-# INLINE maybeDecode #-}+{-# INLINE toEnumMaybe #-}+{-# INLINE dropPfx #-}
+ src/text-encode/Text/Encode/Casing.hs view
@@ -0,0 +1,270 @@+module Text.Encode.Casing (+ Casing(..),+ CaseConversion(..),+) where++import Text.Casing qualified as C++data Casing+ = Camel+ | Kebab+ | Pascal+ | QuietSnake+ | ScreamingSnake+ | Snake++class CaseConversion (decoding :: Casing) (encoding :: Casing) where+ encoding :: String -> String+ decoding :: String -> String++instance CaseConversion 'Camel 'Camel where+ encoding = id+ decoding = id++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Camel 'Kebab where+ encoding = C.toKebab . C.fromHumps+ decoding = C.toCamel . C.fromKebab++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Camel 'Pascal where+ encoding = C.toPascal . C.fromHumps+ decoding = C.toCamel . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Camel 'QuietSnake where+ encoding = C.toQuietSnake . C.fromHumps+ decoding = C.toCamel . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Camel 'ScreamingSnake where+ encoding = C.toScreamingSnake . C.fromHumps+ decoding = C.toCamel . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Camel 'Snake where+ encoding = C.toSnake . C.fromHumps+ decoding = C.toCamel . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Kebab 'Camel where+ encoding = C.toCamel . C.fromKebab+ decoding = C.toKebab . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Kebab 'Kebab where+ encoding = id+ decoding = id++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Kebab 'Pascal where+ encoding = C.toPascal . C.fromKebab+ decoding = C.toKebab . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Kebab 'QuietSnake where+ encoding = C.toQuietSnake . C.fromKebab+ decoding = C.toKebab . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Kebab 'ScreamingSnake where+ encoding = C.toScreamingSnake . C.fromKebab+ decoding = C.toKebab . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Kebab 'Snake where+ encoding = C.toSnake . C.fromKebab+ decoding = C.toKebab . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Pascal 'Camel where+ encoding = C.toCamel . C.fromHumps+ decoding = C.toPascal . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Pascal 'Kebab where+ encoding = C.toKebab . C.fromHumps+ decoding = C.toPascal . C.fromKebab++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Pascal 'Pascal where+ encoding = id+ decoding = id++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Pascal 'QuietSnake where+ encoding = C.toQuietSnake . C.fromHumps+ decoding = C.toPascal . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Pascal 'ScreamingSnake where+ encoding = C.toScreamingSnake . C.fromHumps+ decoding = C.toPascal . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Pascal 'Snake where+ encoding = C.toSnake . C.fromHumps+ decoding = C.toPascal . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'QuietSnake 'Camel where+ encoding = C.toCamel . C.fromSnake+ decoding = C.toQuietSnake . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'QuietSnake 'Kebab where+ encoding = C.toKebab . C.fromSnake+ decoding = C.toQuietSnake . C.fromKebab++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'QuietSnake 'Pascal where+ encoding = C.toPascal . C.fromSnake+ decoding = C.toQuietSnake . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'QuietSnake 'QuietSnake where+ encoding = id+ decoding = id++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'QuietSnake 'ScreamingSnake where+ encoding = C.toScreamingSnake . C.fromSnake+ decoding = C.toQuietSnake . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'QuietSnake 'Snake where+ encoding = C.toSnake . C.fromSnake+ decoding = C.toQuietSnake . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'ScreamingSnake 'Camel where+ encoding = C.toCamel . C.fromSnake+ decoding = C.toScreamingSnake . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'ScreamingSnake 'Kebab where+ encoding = C.toKebab . C.fromSnake+ decoding = C.toScreamingSnake . C.fromKebab++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'ScreamingSnake 'Pascal where+ encoding = C.toPascal . C.fromSnake+ decoding = C.toScreamingSnake . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'ScreamingSnake 'QuietSnake where+ encoding = C.toQuietSnake . C.fromSnake+ decoding = C.toScreamingSnake . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'ScreamingSnake 'ScreamingSnake where+ encoding = id+ decoding = id++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'ScreamingSnake 'Snake where+ encoding = C.toSnake . C.fromSnake+ decoding = C.toScreamingSnake . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Snake 'Camel where+ encoding = C.toCamel . C.fromSnake+ decoding = C.toSnake . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Snake 'Kebab where+ encoding = C.toKebab . C.fromSnake+ decoding = C.toSnake . C.fromKebab++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Snake 'Pascal where+ encoding = C.toPascal . C.fromSnake+ decoding = C.toSnake . C.fromHumps++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Snake 'QuietSnake where+ encoding = C.toQuietSnake . C.fromSnake+ decoding = C.toSnake . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Snake 'ScreamingSnake where+ encoding = C.toScreamingSnake . C.fromSnake+ decoding = C.toSnake . C.fromSnake++ {-# INLINE encoding #-}+ {-# INLINE decoding #-}++instance CaseConversion 'Snake 'Snake where+ encoding = id+ decoding = id++ {-# INLINE decoding #-}+ {-# INLINE encoding #-}
+ text-encode.cabal view
@@ -0,0 +1,218 @@+cabal-version: 3.0+name: text-encode+version: 0.1.0.0+homepage: https://github.com/friedbrice/text-encode#readme+bug-reports: https://github.com/friedbrice/text-encode/issues+author: Daniel Brice+maintainer: danielbrice@gmail.com+copyright: Copyright Daniel Brice+license: BSD-3-Clause+license-file: LICENSE+build-type: Simple+category: Serialization+synopsis:+ Classes and newtypes for deriving uniform textual encodings.++description:+ Classes and newtypes for deriving uniform textual encodings.+ See the Text.Encode module documentation for details and usage examples.++extra-source-files: README.md+tested-with:+ GHC ==9.2.8 || ==9.4.8 || ==9.6.7 || ==9.8.4 || ==9.10.2++source-repository head+ type: git+ location: https://github.com/friedbrice/text-encode++common dialect+ ghc-options: -Wall+ build-depends: base >=4.16 && <5+ default-language: Haskell2010+ default-extensions:+ AllowAmbiguousTypes+ DataKinds+ DerivingVia+ FlexibleContexts+ FlexibleInstances+ ImportQualifiedPost+ KindSignatures+ MultiParamTypeClasses+ ScopedTypeVariables+ TupleSections+ TypeApplications+ UndecidableInstances++common opts-dist+ ghc-options: -O2++common opts-dev+ ghc-options: -O0 -Wwarn+ default-extensions:+ InstanceSigs+ PartialTypeSignatures++common deps-lib+ build-depends:+ , bytestring >=0.11 && <0.13+ , casing >=0.1 && <0.2+ , text >=1.2 && <2.2++common deps-aeson+ build-depends:+ , aeson >=2.0 && <2.3+ , bytestring >=0.11 && <0.13+ , text >=1.2 && <2.2++common deps-cassava+ build-depends:+ , bytestring >=0.11 && <0.13+ , cassava >=0.5 && <0.6+ , text >=1.2 && <2.2++common deps-http-api-data+ build-depends:+ , bytestring >=0.11 && <0.13+ , http-api-data >=0.4 && <0.7+ , http-types >=0.12 && <0.13+ , text >=1.2 && <2.2++common deps-persistent+ build-depends:+ , persistent >=2.13 && <2.18+ , text >=1.2 && <2.2++common deps-postgresql-simple+ build-depends:+ , postgresql-simple >=0.6 && <0.8+ , text >=1.2 && <2.2++common deps-sqlite-simple+ build-depends:+ , sqlite-simple >=0.4 && <0.5+ , text >=1.2 && <2.2++common deps-dev++library+ import: dialect+ import: opts-dist+ import: deps-lib+ hs-source-dirs: src/text-encode++ -- cabal-fmt: expand src/text-encode+ exposed-modules:+ Text.Encode+ Text.Encode.Casing++library text-encode-aeson+ import: dialect+ import: opts-dist+ import: deps-aeson+ visibility: public+ ghc-options: -Wno-orphans+ ghc-options: -Wno-orphans+ build-depends: text-encode+ hs-source-dirs: src/text-encode-aeson++ -- cabal-fmt: expand src/text-encode-aeson+ exposed-modules: Text.Encode.Aeson++library text-encode-cassava+ import: dialect+ import: opts-dist+ import: deps-cassava+ visibility: public+ ghc-options: -Wno-orphans+ build-depends: text-encode+ hs-source-dirs: src/text-encode-cassava++ -- cabal-fmt: expand src/text-encode-cassava+ exposed-modules: Text.Encode.Cassava++library text-encode-http-api-data+ import: dialect+ import: opts-dist+ import: deps-http-api-data+ visibility: public+ ghc-options: -Wno-orphans+ build-depends: text-encode+ hs-source-dirs: src/text-encode-http-api-data++ -- cabal-fmt: expand src/text-encode-http-api-data+ exposed-modules: Text.Encode.HttpApiData++library text-encode-persistent+ import: dialect+ import: opts-dist+ import: deps-persistent+ visibility: public+ ghc-options: -Wno-orphans+ build-depends: text-encode+ hs-source-dirs: src/text-encode-persistent++ -- cabal-fmt: expand src/text-encode-persistent+ exposed-modules: Text.Encode.Persistent++library text-encode-postgresql-simple+ import: dialect+ import: opts-dist+ import: deps-postgresql-simple+ visibility: public+ ghc-options: -Wno-orphans+ build-depends: text-encode+ hs-source-dirs: src/text-encode-postgresql-simple++ -- cabal-fmt: expand src/text-encode-postgresql-simple+ exposed-modules: Text.Encode.PostgresqlSimple++library text-encode-sqlite-simple+ import: dialect+ import: opts-dist+ import: deps-sqlite-simple+ visibility: public+ ghc-options: -Wno-orphans+ build-depends: text-encode+ hs-source-dirs: src/text-encode-sqlite-simple++ -- cabal-fmt: expand src/text-encode-sqlite-simple+ exposed-modules: Text.Encode.SqliteSimple++test-suite dev+ import: dialect+ import: opts-dev+ import: deps-lib+ import: deps-aeson+ import: deps-cassava+ import: deps-http-api-data+ import: deps-persistent+ import: deps-postgresql-simple+ import: deps-sqlite-simple+ import: deps-dev+ type: exitcode-stdio-1.0+ ghc-options: -Wno-orphans+ main-is: Main.hs+ hs-source-dirs:+ src/text-encode src/text-encode-aeson src/text-encode-cassava+ src/text-encode-http-api-data src/text-encode-persistent+ src/text-encode-postgresql-simple src/text-encode-sqlite-simple+ src/dev src/dev-main++ -- cabal-fmt: expand src/text-encode+ -- cabal-fmt: expand src/text-encode-aeson+ -- cabal-fmt: expand src/text-encode-cassava+ -- cabal-fmt: expand src/text-encode-http-api-data+ -- cabal-fmt: expand src/text-encode-persistent+ -- cabal-fmt: expand src/text-encode-postgresql-simple+ -- cabal-fmt: expand src/text-encode-sqlite-simple+ -- cabal-fmt: expand src/dev+ other-modules:+ DevMain+ Text.Encode+ Text.Encode.Aeson+ Text.Encode.Casing+ Text.Encode.Cassava+ Text.Encode.HttpApiData+ Text.Encode.Persistent+ Text.Encode.PostgresqlSimple+ Text.Encode.SqliteSimple