diff --git a/Data/Ascii.hs b/Data/Ascii.hs
deleted file mode 100644
--- a/Data/Ascii.hs
+++ /dev/null
@@ -1,61 +0,0 @@
-module Data.Ascii
-    ( -- * Datatypes
-      Ascii
-    , CIAscii
-    , AsciiBuilder
-      -- * Construction
-      -- ** Safe
-    , fromByteString
-    , fromChars
-    , fromText
-      -- ** Unsafe
-    , unsafeFromByteString
-    , unsafeFromString
-    , unsafeFromText
-      -- * Extraction
-    , toByteString
-    , toString
-    , toText
-      -- * Case insensitive
-    , toCIAscii
-    , fromCIAscii
-    , ciToByteString
-      -- * Builder
-    , toAsciiBuilder
-    , fromAsciiBuilder
-    , unsafeFromBuilder
-    , toBuilder
-      -- * Character-level functions and predicates
-    , fromChar
-    , toChar
-    , ascii
-    , isAscii
-    , isControl
-    , isPrintable
-    , isWhiteSpace
-    , isSpaceOrTab
-    , isLower
-    , isUpper
-    , toLower
-    , toUpper
-    , isAlpha
-    , isDigit
-    , isAlphaNum
-    , fromDigit
-    , unsafeFromDigit
-    , fromOctDigit
-    , unsafeFromOctDigit
-    , isUpHexDigit
-    , fromUpHexDigit
-    , unsafeFromUpHexDigit
-    , isLowHexDigit
-    , fromLowHexDigit
-    , unsafeFromLowHexDigit
-    , isHexDigit
-    , fromHexDigit
-    , unsafeFromHexDigit
-    ) where
-
-import Data.Ascii.Blaze
-import Data.Ascii.ByteString
-import Data.Ascii.Word8
diff --git a/Data/Ascii/Blaze.hs b/Data/Ascii/Blaze.hs
deleted file mode 100644
--- a/Data/Ascii/Blaze.hs
+++ /dev/null
@@ -1,27 +0,0 @@
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-
-module Data.Ascii.Blaze where
-
-import Data.Ascii.ByteString
-
--- base
-import Data.Monoid (Monoid)
-import Data.Semigroup (Semigroup)
-
--- blaze-builder
-import qualified Blaze.ByteString.Builder as Blaze
-
-newtype AsciiBuilder = AsciiBuilder (Blaze.Builder)
-    deriving (Semigroup, Monoid)
-
-unsafeFromBuilder :: Blaze.Builder -> AsciiBuilder
-unsafeFromBuilder = AsciiBuilder
-
-toBuilder :: AsciiBuilder -> Blaze.Builder
-toBuilder (AsciiBuilder b) = b
-
-toAsciiBuilder :: Ascii -> AsciiBuilder
-toAsciiBuilder (Ascii bs) = AsciiBuilder $ Blaze.fromByteString bs
-
-fromAsciiBuilder :: AsciiBuilder -> Ascii
-fromAsciiBuilder (AsciiBuilder b) = Ascii $ Blaze.toByteString b
diff --git a/Data/Ascii/ByteString.hs b/Data/Ascii/ByteString.hs
deleted file mode 100644
--- a/Data/Ascii/ByteString.hs
+++ /dev/null
@@ -1,75 +0,0 @@
-{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving #-}
-
-module Data.Ascii.ByteString where
-
--- base
-import Data.Data (Data)
-import Data.Typeable (Typeable)
-import Data.String (IsString (..))
-import qualified Data.Char as C
-import Data.Monoid (Monoid)
-import Data.Semigroup (Semigroup)
-
--- bytestring
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Char8 as S8
-import Data.ByteString (ByteString)
-
--- case-insensitive
-import Data.CaseInsensitive (FoldCase, CI, mk, original)
-
--- hashable
-import Data.Hashable (Hashable)
-
--- text
-import Data.Text (Text)
-import qualified Data.Text as T
-import qualified Data.Text.Encoding as TE
-
-newtype Ascii = Ascii ByteString
-    deriving (Show, Eq, Read, Ord, Data, Typeable, IsString, FoldCase, Hashable, Semigroup, Monoid)
-
-type CIAscii = CI Ascii
-
-fromByteString :: ByteString -> Maybe Ascii
-fromByteString bs
-    | S.all (< 128) bs = Just $ Ascii bs
-    | otherwise = Nothing
-
--- | Renamed to avoid clash with 'fromString'
-fromChars :: String -> Maybe Ascii
-fromChars s
-    | all C.isAscii s = Just $ Ascii $ S8.pack s
-    | otherwise = Nothing
-
-fromText :: Text -> Maybe Ascii
-fromText t
-    | T.all C.isAscii t = Just $ Ascii $ TE.encodeUtf8 t
-    | otherwise = Nothing
-
-unsafeFromByteString :: ByteString -> Ascii
-unsafeFromByteString = Ascii
-
-unsafeFromString :: String -> Ascii
-unsafeFromString = Ascii . S8.pack
-
-unsafeFromText :: Text -> Ascii
-unsafeFromText = Ascii . TE.encodeUtf8
-
-toCIAscii :: Ascii -> CIAscii
-toCIAscii = mk
-
-fromCIAscii :: CIAscii -> Ascii
-fromCIAscii = original
-
-toByteString :: Ascii -> ByteString
-toByteString (Ascii bs) = bs
-
-toString :: Ascii -> String
-toString (Ascii bs) = S8.unpack bs
-
-toText :: Ascii -> Text
-toText (Ascii bs) = TE.decodeUtf8 bs
-
-ciToByteString :: CIAscii -> ByteString
-ciToByteString = toByteString . original
diff --git a/Data/Ascii/Word8.hs b/Data/Ascii/Word8.hs
deleted file mode 100644
--- a/Data/Ascii/Word8.hs
+++ /dev/null
@@ -1,148 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-module Data.Ascii.Word8 where
-
--- base
-import Data.Word (Word8)
-import qualified Data.Char as C
-
-fromChar :: Char -> Maybe Word8
-fromChar c = if i < 128 then Just (fromIntegral i) else Nothing
-  where i = C.ord c
-
-toChar :: Word8 -> Char
-toChar = C.chr . fromIntegral
-
--- | Unsafe version of 'fromChar'
-ascii :: Char -> Word8
-ascii = fromIntegral . C.ord
-{-# INLINE ascii #-}
-
-isAscii :: Word8 -> Bool
-isAscii = (< 128)
-
-isControl :: Word8 -> Bool
-isControl w = w < 32 || w == 127
-
-isPrintable :: Word8 -> Bool
-isPrintable w = w >= 32 && w < 127
-
-isWhiteSpace :: Word8 -> Bool
-isWhiteSpace w = w == ascii ' ' || w >= 9 && w <= 13
-
-isSpaceOrTab :: Word8 -> Bool
-isSpaceOrTab w = w == ascii ' ' || w == ascii '\t'
-
-isLower :: Word8 -> Bool
-isLower w = w >= ascii 'a' && w <= ascii 'z'
-
-isUpper :: Word8 -> Bool
-isUpper w = w >= ascii 'A' && w <= ascii 'Z'
-
-toLower :: Word8 -> Word8
-toLower w | isUpper w = w + 32
-          | otherwise = w
-
-toUpper :: Word8 -> Word8
-toUpper w | isLower w = w - 32
-          | otherwise = w
-
-isAlpha :: Word8 -> Bool
-isAlpha w = isUpper w || isLower w
-
-isDigit :: Word8 -> Bool
-isDigit w = w >= ascii '0' && w <= ascii '9'
-
-isAlphaNum :: Word8 -> Bool
-isAlphaNum w = isDigit w || isAlpha w
-
-fromDigit :: Num a => Word8 -> Maybe a
-fromDigit w | isDigit w = Just $ unsafeFromDigit w
-            | otherwise = Nothing
-#if __GLASGOW_HASKELL__ >= 700
-{-# INLINABLE fromDigit #-}
-#endif
-
-unsafeFromDigit :: Num a => Word8 -> a
-unsafeFromDigit w = fromIntegral (w - ascii '0')
-{-# INLINE unsafeFromDigit #-}
-
-isOctDigit :: Word8 -> Bool
-isOctDigit w = w >= ascii '0' && w <= ascii '7'
-
-fromOctDigit :: Num a => Word8 -> Maybe a
-fromOctDigit w | isOctDigit w = Just $ unsafeFromOctDigit w
-               | otherwise    = Nothing
-#if __GLASGOW_HASKELL__ >= 700
-{-# INLINABLE fromOctDigit #-}
-#endif
-
-unsafeFromOctDigit :: Num a => Word8 -> a
-unsafeFromOctDigit = unsafeFromDigit
-{-# INLINE unsafeFromOctDigit #-}
-
-isLowAF :: Word8 -> Bool
-isLowAF w = w >= ascii 'a' && w <= ascii 'f'
-{-# INLINE isLowAF #-}
-
-fromLowAF :: Num a => Word8 -> a
-fromLowAF w = fromIntegral (w - ascii 'a' + 10)
-{-# INLINE fromLowAF #-}
-
-isLowHexDigit :: Word8 -> Bool
-isLowHexDigit w = isDigit w || isLowAF w
-
-fromLowHexDigit :: Num a => Word8 -> Maybe a
-fromLowHexDigit w | isDigit w = Just $ unsafeFromDigit w
-                  | isLowAF w = Just $ fromLowAF w
-                  | otherwise = Nothing
-#if __GLASGOW_HASKELL__ >= 700
-{-# INLINABLE fromLowHexDigit #-}
-#endif
-
-unsafeFromLowHexDigit :: Num a => Word8 -> a
-unsafeFromLowHexDigit w | w < ascii 'a' = unsafeFromDigit w
-                        | otherwise     = fromLowAF w
-{-# INLINE unsafeFromLowHexDigit #-}
-
-isUpAF :: Word8 -> Bool
-isUpAF w = w >= ascii 'A' && w <= ascii 'F'
-{-# INLINE isUpAF #-}
-
-fromUpAF :: Num a => Word8 -> a
-fromUpAF w = fromIntegral (w - ascii 'A' + 10)
-{-# INLINE fromUpAF #-}
-
-isUpHexDigit :: Word8 -> Bool
-isUpHexDigit w = isDigit w || isUpAF w
-
-fromUpHexDigit :: Num a => Word8 -> Maybe a
-fromUpHexDigit w | isDigit w = Just $ unsafeFromDigit w
-                 | isUpAF w  = Just $ fromUpAF w
-                 | otherwise = Nothing
-#if __GLASGOW_HASKELL__ >= 700
-{-# INLINABLE fromUpHexDigit #-}
-#endif
-
-unsafeFromUpHexDigit :: Num a => Word8 -> a
-unsafeFromUpHexDigit w | w < ascii 'A' = unsafeFromDigit w
-                       | otherwise     = fromUpAF w
-{-# INLINE unsafeFromUpHexDigit #-}
-
-isHexDigit :: Word8 -> Bool
-isHexDigit w = isDigit w || isUpAF w || isLowAF w
-
-fromHexDigit :: Num a => Word8 -> Maybe a
-fromHexDigit w | isDigit w = Just $ unsafeFromDigit w
-               | isUpAF w  = Just $ fromUpAF w
-               | isLowAF w = Just $ fromLowAF w
-               | otherwise = Nothing
-#if __GLASGOW_HASKELL__ >= 700
-{-# INLINABLE fromHexDigit #-}
-#endif
-
-unsafeFromHexDigit :: Num a => Word8 -> a
-unsafeFromHexDigit w | w < ascii 'A' = unsafeFromDigit w
-                     | w < ascii 'a' = fromUpAF w
-                     | otherwise     = fromLowAF w
-{-# INLINE unsafeFromHexDigit #-}
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
--- a/LICENSE
+++ /dev/null
@@ -1,30 +0,0 @@
-Copyright Michael Snoyman 2011, Typeclass Consulting LLC 2018
-
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-
-    * 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.
-
-    * Neither the name of Michael Snoyman nor the names of other
-      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
-OWNER 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.
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/usr/bin/env runhaskell
-import Distribution.Simple
-main = defaultMain
diff --git a/ascii.cabal b/ascii.cabal
--- a/ascii.cabal
+++ b/ascii.cabal
@@ -1,40 +1,74 @@
-Name:                ascii
-Version:             0.0.5.2
-Synopsis:            Type-safe, bytestring-based ASCII values
-Description:         Type-safe, bytestring-based ASCII values.
-License:             BSD3
-License-file:        LICENSE
-Author:              Michael Snoyman
-Maintainer:          Chris Martin, Julie Moronuki
-Stability:           Stable
-Category:            Data
-Homepage:            https://github.com/typeclasses/ascii
-Bug-Reports:         https://github.com/typeclasses/ascii/issues
-Build-type:          Simple
-Cabal-version:       >= 1.6
-Tested-with:         GHC == 8.4.3
-                   , GHC == 8.2.2
-                   , GHC == 7.10.3
-                   , GHC == 7.8.4
-                   , GHC == 7.6.3
-                   , GHC == 7.4.2
-                   , GHC == 7.2.2
-                   , GHC == 7.0.4
+cabal-version: 3.0
 
-Source-Repository head
-    Type:     git
-    Location: git://github.com/typeclasses/ascii.git
+name: ascii
+version: 1.7.0.2
+synopsis: The ASCII character set and encoding
+category: Data, Text
 
-Library
-  Exposed-modules:     Data.Ascii
-                     , Data.Ascii.Blaze
-                     , Data.Ascii.ByteString
-                     , Data.Ascii.Word8
-  Build-depends:       base             >= 4             && < 5
-                     , bytestring       >= 0.9           && < 0.11
-                     , text             >= 0.11          && < 1.3
-                     , blaze-builder    >= 0.2.1.4       && < 0.5
-                     , case-insensitive >= 0.2           && < 1.3
-                     , hashable         >= 1.0           && < 1.3
-                     , semigroups
-  Ghc-options:         -Wall
+description:
+    This package provides a variety of ways to work with ASCII text.
+
+license: Apache-2.0
+license-file: license.txt
+
+author: Chris Martin
+maintainer: Chris Martin, Julie Moronuki
+
+homepage: https://github.com/typeclasses/ascii
+bug-reports: https://github.com/typeclasses/ascii/issues
+
+extra-source-files: *.md
+
+source-repository head
+    type: git
+    location: git://github.com/typeclasses/ascii.git
+
+common base
+    default-language: GHC2021
+    ghc-options: -Wall
+    default-extensions: NoImplicitPrelude
+    build-depends:
+      , ascii-caseless == 0.0.0.*
+      , ascii-char == 1.0.1.*
+      , ascii-group == 1.0.0.*
+      , ascii-case == 1.0.1.*
+      , ascii-numbers == 1.2.0.*
+      , ascii-predicates == 1.0.1.*
+      , ascii-superset == 1.3.0.*
+      , ascii-th == 1.2.0.*
+      , base ^>= 4.16 || ^>= 4.17 || ^>= 4.18 || ^>= 4.19
+      , bytestring ^>= 0.11.4 || ^>= 0.12
+      , text ^>= 1.2.5 || ^>= 2.0 || ^>= 2.1
+
+library
+    import: base
+    hs-source-dirs: library
+    exposed-modules: ASCII
+    reexported-modules:
+      , ASCII.Case
+      , ASCII.Caseless
+      , ASCII.CaseRefinement
+      , ASCII.Char
+      , ASCII.Decimal
+      , ASCII.Group
+      , ASCII.Hexadecimal
+      , ASCII.Isomorphism
+      , ASCII.Lists
+      , ASCII.ListsAndPredicates
+      , ASCII.Predicates
+      , ASCII.QuasiQuoters
+      , ASCII.Refinement
+      , ASCII.Superset
+      , ASCII.Superset.Text
+      , ASCII.TemplateHaskell
+      , ASCII.Word4
+
+test-suite test-ascii
+    import: base
+    type: exitcode-stdio-1.0
+    hs-source-dirs: test
+    main-is: Main.hs
+    default-extensions: OverloadedStrings QuasiQuotes
+    build-depends:
+      , ascii
+      , hspec ^>= 2.9.7 || ^>= 2.10 || ^>= 2.11
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
@@ -0,0 +1,305 @@
+### 1.7.0.2 (2025-01-20)
+
+Version bumps
+
+### 1.7.0.1 (2023-06-26)
+
+Upgrade language to GHC2021
+
+### 1.7.0.0 (2023-03-01)
+
+Rename `toAsciiCharMaybe` to `toCharMaybe`
+
+Add `toCharListMaybe`
+
+### 1.6.0.0 (2023-02-08)
+
+Raise `ascii-superset` to `1.3`. This removes the `ASCII.Lift` module.
+
+Raise `ascii-numbers` to `1.2`.
+
+From the `ASCII` module, the `Lift` class and `lift` function are removed.
+
+The removed `lift` function is replaced with the `lift` function from
+`ASCII.Refinement`. If you were using `lift` specialized as
+`ASCII a -> a`, then this is not a breaking change. Otherwise, migrate
+by using one the new functions below.
+
+```haskell
+fromChar        :: FromChar char               => Char      -> char
+fromCharList    :: FromString string           => [Char]    -> string
+fromDigit       :: DigitSuperset char          => Digit     -> char
+fromDigitList   :: DigitStringSuperset string  => [Digit]   -> string
+fromHexChar     :: HexCharSuperset char        => HexChar   -> char
+fromHexCharList :: HexStringSuperset string    => [HexChar] -> string
+
+forgetCase      :: ASCII'case letterCase superset -> ASCII superset
+```
+
+### 1.5.4.0 (2023-02-08)
+
+Raise `ascii-superset` to `1.2.7`
+
+Adds module `ASCII.Superset.Text`
+
+Added to the `ASCII` module:
+
+- Class `ToText`
+- Functions `toStrictText`, `toLazyText`, `toUnicodeCharList`
+
+### 1.5.3.0 (2023-02-07)
+
+Raise `ascii-superset` to `1.2.6`
+
+Added class `StringSupersetConversion` and the following function:
+
+```haskell
+convertRefinedString ::
+    StringSupersetConversion a b => ASCII a -> ASCII b
+```
+
+### 1.5.2.0 (2023-01-25)
+
+New functions: `asciiByteStringToText` and `asciiByteStringToTextLazy`
+
+### 1.5.1.0 (2023-01-06)
+
+Raise `ascii-superset` version to `1.2.5`. This adds a new instance:
+
+```haskell
+instance Lift (ASCII'case letterCase superset) (ASCII superset)
+```
+
+### 1.5.0.0 (2023-01-06)
+
+Raise `ascii-th` version to `1.2.0`. This changes the constraints on `lower` and
+`upper` quasi-quotations in an expression context. Previously, the constraint
+was `FromString`. The constraints are now `ToCasefulString 'LowerCase` and
+`ToCasefulString 'UpperCase` respectively. This expands the range of types
+inhabited by lower/uppercase quotes to include `ASCII'lower` and `ASCII'upper`,
+which were previously not able to be expressed using quasi-quotations.
+
+### 1.4.2.0 (2023-01-05)
+
+Raise `ascii-superset` version from `1.2.0` to `1.2.4`. This adds classes
+`ToCasefulChar` and `ToCasefulString` to the `ASCII.Superset` module. It also
+adds some instances for the various other superset classes.
+
+### 1.4.1.1 (2023-01-05)
+
+Change test suite from `hedgehog` to `hspec`
+
+### 1.4.1.0 (2023-01-05)
+
+Raise `ascii-char` version to `1.0.1`. This adds `Word8` conversions to the
+`ASCII.Char` module.
+
+### 1.4.0.0 (2023-01-03)
+
+Additions to the `ASCII` module: `disregardCase`, `ASCII'case`, `ASCII'upper`,
+`ASCII'lower`, `KnownCase (..)`, `refineCharToCase`, `refineStringToCase`
+
+Update `ascii-superset` to `1.2.0`. This adds `CharSuperset (toCaseChar)`,
+`StringSuperset (toCaseString)`, `refineCharToCase`, and `refineStringToCase`.
+
+The constraint on `toCaseChar` is relaxed from `CharIso` to `CharSuperset`.
+The constraint on `toCaseString` is relaxed from `StringIso` to `StringSuperset`.
+
+### 1.3.1.0 (2023-01-03)
+
+Update `ascii-th` to `1.1.1`.
+
+This adds, most notably, to the `ASCII.QuasiQuoters` module.
+The new quasi-quoters are `caseless`, `lower`, and `upper`.
+These are also re-exported from the `ASCII` module.
+
+### 1.3.0.0 (2023-01-03)
+
+Update `ascii-superset` to `1.1.0`.
+
+This adds several classes to the `ASCII.Superset` module: `ToChar`, `FromChar`,
+`ToString`, `FromString`, `ToCaselessChar`, and `ToCaselessString`.
+
+This is a breaking change because these are superclasses of the existing
+`CharSuperset` and `StringSuperset` classes, and they take methods from them.
+
+### 1.2.6.0 (2023-01-02)
+
+Update `ascii-superset` to `1.0.2`. This adds the `ASCII.CaseRefinement` module.
+
+### 1.2.5.0 (2023-01-02)
+
+Add the `ASCII.Caseless` module (re-exported from the `ascii-caseless` package)
+
+Additions to the `ASCII` module:
+
+* `CaselessChar`
+
+### 1.2.4.1 (2022-12-30)
+
+Metadata changes only
+
+### 1.2.4.0 (2022-12-23)
+
+Bump version of `ascii-case` to `1.0.1`. This adds the following function to the
+`ASCII.Case` module:
+
+```haskell
+opposite :: Case -> Case
+```
+
+### 1.2.3.0 (2022-05-04)
+
+Add `isVisible :: Char -> Bool`. Visible characters include all print characters
+other than `Space`.
+
+### 1.2.2.0 (2022-04-29)
+
+Add `type UnicodeChar = Data.Char.Char` type alias to `ASCII` module
+
+### 1.2.1.0 (2022-04-29)
+
+New polymorphic narrowing functions:
+
+* `toAsciiCharMaybe :: CharSuperset char => char -> Maybe Char`
+* `toDigitMaybe :: DigitSuperset char => char -> Maybe Digit`
+* `toHexCharMaybe :: HexCharSuperset char => char -> Maybe HexChar`
+
+New monomorphic character conversion functions:
+
+* `digitToWord8 :: Digit -> Word8`
+* `word8ToDigitMaybe :: Word8 -> Maybe Digit`
+* `word8ToDigitUnsafe :: Word8 -> Digit`
+* `digitToChar :: Digit -> Char`
+* `charToDigitMaybe :: Char -> Maybe Digit`
+* `charToDigitUnsafe :: Char -> Digit`
+* `digitToUnicode :: Digit -> Unicode.Char`
+* `unicodeToDigitMaybe :: Unicode.Char -> Maybe Digit`
+* `unicodeToDigitUnsafe :: Unicode.Char -> Digit`
+* `hexCharToWord8 :: HexChar -> Word8`
+* `word8ToHexCharMaybe :: Word8 -> Maybe HexChar`
+* `word8ToHexCharUnsafe :: Word8 -> HexChar`
+* `hexCharToChar :: HexChar -> Char`
+* `charToHexCharMaybe :: Char -> Maybe HexChar`
+* `charToHexCharUnsafe :: Char -> HexChar`
+* `hexCharToUnicode :: HexChar -> Unicode.Char`
+* `unicodeToHexCharMaybe :: Unicode.Char -> Maybe HexChar`
+* `unicodeToHexCharUnsafe :: Unicode.Char -> HexChar`
+
+### 1.2.0.0 (2022-04-20)
+
+Update to `ascii-numbers` version `1.1.0`. The major change is that there are
+now `Lift` instances for `Digit` and `HexChar`.
+
+### 1.1.3.0
+
+Added functions `digitString` and `hexCharString`
+
+### 1.1.2.0
+
+Add dependency on `ascii-numbers`
+
+New modules:
+
+* `ASCII.Decimal`
+* `ASCII.Hexadecimal`
+
+New types:
+
+* `Digit`
+* `HexChar`
+
+New classes:
+
+* `DigitSuperset`
+* `DigitStringSuperset`
+* `HexCharSuperset`
+* `HexStringSuperset`
+
+New functions:
+
+* `showIntegralDecimal`
+* `showIntegralHexadecimal`
+* `readIntegralDecimal`
+* `readIntegralHexadecimal`
+
+* `showNaturalDigits`
+* `readNaturalDigits`
+* `showNaturalHexChars`
+* `readNaturalHexChars`
+
+* `showNaturalDecimal`
+* `showNaturalHexadecimal`
+* `readNaturalDecimal`
+* `readNaturalHexadecimal`
+
+Dropped support for old versions:
+
+* Drop support for `base` 4.11 (GHC 8.4)
+* Drop support for `base` 4.12 (GHC 8.6)
+
+### 1.1.1.4
+
+Switch test-suite over to `hedgehog`
+
+### 1.1.1.2
+
+Support GHC 9.2
+
+### 1.1.1.0
+
+New functions:
+
+  - `isAlphaNum`
+  - `isLetter`
+  - `isDigit`
+  - `isOctDigit`
+  - `isHexDigit`
+  - `isSpace`
+  - `isPunctuation`
+  - `isSymbol`
+
+### 1.1.0.0
+
+The dependency on the 'data-ascii' package is removed, and the following modules
+are no longer re-exported:
+
+  - `Data.Ascii`
+  - `Data.Ascii.Blaze`
+  - `Data.Ascii.ByteString`
+  - `Data.Ascii.Word8`
+
+### 1.0.1.6
+
+Add a test suite
+
+Raise `text` lower bound to 1.2.3
+
+### 1.0.1.4
+
+Support GHC 9.0
+
+### 1.0.1.2
+
+Support `bytestring-0.11`
+
+### 1.0.1.0
+
+New functions:
+
+  - `byteStringToUnicodeStringMaybe`
+  - `unicodeStringToByteStringMaybe`
+  - `byteListToUnicodeStringMaybe`
+  - `unicodeStringToByteListMaybe`
+  - `convertCharMaybe`
+  - `convertCharOrFail`
+  - `convertStringMaybe`
+  - `convertStringOrFail`
+
+### 1.0.0.2
+
+Support GHC 8.10
+
+### 1.0.0.0
+
+Completely redesigned the library
diff --git a/library/ASCII.hs b/library/ASCII.hs
new file mode 100644
--- /dev/null
+++ b/library/ASCII.hs
@@ -0,0 +1,872 @@
+{-| The __American Standard Code for Information Interchange__ (ASCII) comprises
+a set of 128 characters, each represented by 7 bits. 33 of these characters are
+/'Control' codes/; a few of these are still in use, but most are obsolete relics
+of the early days of computing. The other 95 are /'Printable' characters/ such
+as letters and numbers, mostly corresponding to the keys on an American English
+keyboard.
+
+Nowadays instead of ASCII we typically work with text using an encoding such as
+UTF-8 that can represent the entire Unicode character set, which includes over a
+hundred thousand characters and is not limited to the symbols of any particular
+writing system or culture. However, ASCII is still relevant to network
+protocols; for example, we can see it in the specification of
+<https://www.rfc-editor.org/rfc/rfc9110.html#name-syntax-notation HTTP>.
+
+There is a convenient relationship between ASCII and Unicode: the ASCII
+characters are the first 128 characters of the much larger Unicode character set.
+The <https://www.unicode.org/charts/PDF/U0000.pdf C0 Controls and Basic Latin>
+section of the Unicode standard contains a list of all the ASCII characters.
+You may also find this list replicated in the "ASCII.Char" module; each ASCII
+character corresponds to a constructor of the 'ASCII.Char' type.
+
+We do not elaborate on the semantics of the control characters here,
+because this information is both obsolete and restricted by copyright law.
+It is described by a document entitled
+/"Coded Character Sets - 7-Bit American National Standard Code for Information Interchange (7-Bit ASCII)"/,
+published by American National Standards Institute (ANSI) and available for purchase
+<https://webstore.ansi.org/Standards/INCITS/INCITS1986R2012 on their website>.
+-}
+module ASCII
+  (
+    {- * @Char@ -}
+
+        {- ** ASCII -}
+            {- $char -} Char,
+
+        {- ** Unicode -}
+            UnicodeChar,
+
+        {- ** Case-insensitive -}
+            {- $caselessChar -} CaselessChar,
+
+    {- * Character classifications -}
+
+        {- ** Print/control groups -}
+            {- $groups -} Group (..), charGroup, inGroup,
+
+        {- ** Upper/lower case -}
+            {- $case -} Case (..), letterCase, isCase, toCaseChar, toCaseString,
+            disregardCase, refineCharToCase, refineStringToCase,
+
+        {- ** Letters -}
+            isLetter,
+
+        {- ** Letters and numbers -}
+            isAlphaNum,
+
+        {- ** Decimal digits -}
+            {- $digit -} isDigit, Digit,
+
+        {- ** Hexadecimal digits -}
+            {- $hexchar -} isHexDigit, HexChar,
+
+        {- ** Octal digits -}
+            isOctDigit,
+
+        {- ** Spaces and symbols -}
+            isSpace, isPunctuation, isSymbol, isVisible,
+
+    {- * Monomorphic character conversions -}
+
+        {- $monomorphicConversions -}
+
+        {- ** @ASCII.Char@ ↔ @Int@ -}
+            {- $intConversions -}
+            charToInt, intToCharMaybe, intToCharUnsafe,
+
+        {- ** @ASCII.Char@ ↔ @Word8@ -}
+            {- $word8Conversions -}
+            charToWord8, word8ToCharMaybe, word8ToCharUnsafe,
+
+        {- ** @ASCII.Char@ ↔ @UnicodeChar@ -}
+            {- $unicodeCharConversions -}
+            charToUnicode, unicodeToCharMaybe, unicodeToCharUnsafe,
+
+    {- * Monomorphic digit conversions -}
+
+        {- ** @Digit@ ↔ @Word8@ -}
+            {- $digitWord8Conversions -}
+            digitToWord8, word8ToDigitMaybe, word8ToDigitUnsafe,
+
+        {- ** @Digit@ ↔ @ASCII.Char@ -}
+            {- $digitCharConversions -}
+            digitToChar, charToDigitMaybe, charToDigitUnsafe,
+
+        {- ** @Digit@ ↔ @UnicodeChar@ -}
+            {- $digitUnicodeConversions -}
+            digitToUnicode, unicodeToDigitMaybe, unicodeToDigitUnsafe,
+
+        {- ** @HexChar@ ↔ @Word8@ -}
+            {- $hexCharWord8Conversions -}
+            hexCharToWord8, word8ToHexCharMaybe, word8ToHexCharUnsafe,
+
+        {- ** @HexChar@ ↔ @ASCII.Char@ -}
+            {- $hexCharCharConversions -}
+            hexCharToChar, charToHexCharMaybe, charToHexCharUnsafe,
+
+        {- ** @HexChar@ ↔ @UnicodeChar@ -}
+            {- $hexCharUnicodeConversions -}
+            hexCharToUnicode, unicodeToHexCharMaybe, unicodeToHexCharUnsafe,
+
+    {- * Monomorphic string conversions -}
+
+        {- ** @ASCII.Char@ ↔ @String@ -}
+            {- $unicodeStringConversions -}
+            charListToUnicodeString, unicodeStringToCharListMaybe,
+            unicodeStringToCharListUnsafe,
+
+        {- ** @ASCII.Char@ ↔ @Text@ -}
+            {- $textConversions -}
+            charListToText, textToCharListMaybe, textToCharListUnsafe,
+
+        {- ** @ASCII.Char@ ↔ @ByteString@ -}
+            {- $byteStringConversions -}
+            charListToByteString, byteStringToCharListMaybe,
+            byteStringToCharListUnsafe,
+
+        {- ** @ASCII ByteString@ -> @Text@ -}
+            asciiByteStringToText, asciiByteStringToTextLazy,
+
+    {- * Monomorphic conversions between ASCII supersets -}
+
+        {- $monoSupersetConversions -}
+
+        {- ** @ByteString@ ↔ @String@ -}
+            byteStringToUnicodeStringMaybe, unicodeStringToByteStringMaybe,
+
+        {- ** @[Word8]@ ↔ @String@ -}
+            byteListToUnicodeStringMaybe, unicodeStringToByteListMaybe,
+
+    {- * Monomorphic numeric string conversions -}
+
+        {- ** @Natural@ ↔ @[Digit]@ -}
+            showNaturalDigits, readNaturalDigits,
+
+        {- ** @Natural@ ↔ @[HexChar]@ -}
+            showNaturalHexChars, readNaturalHexChars,
+
+    {- * Refinement types -}
+        {- $refinement -} ASCII,
+        ASCII'case, ASCII'upper, ASCII'lower, KnownCase (..),
+
+    {- * Polymorphic conversions -}
+
+        {- ** Narrowing -}
+            toCharMaybe, toCharListMaybe, toDigitMaybe, toHexCharMaybe,
+
+        {- ** Validate -}
+            validateChar, validateString,
+
+        {- ** Widening -}
+            {- $lift -} lift,
+            {- $toText -} toStrictText, toLazyText, toUnicodeCharList,
+            {- $fromChar -} fromChar, fromCharList,
+            {- $fromDigit -} fromDigit, fromDigitList,
+            {- $fromHexChar -} fromHexChar, fromHexCharList,
+            {- $forgetCase -} forgetCase,
+
+        {- ** Convert -}
+            {- $supersetConversions -}
+            convertCharMaybe, convertCharOrFail, convertStringMaybe,
+            convertStringOrFail, convertRefinedString,
+
+        {- ** Integral strings -}
+            {- $numbers -}
+            showIntegralDecimal, showIntegralHexadecimal,
+            readIntegralDecimal, readIntegralHexadecimal,
+
+        {- ** Natural strings -}
+            showNaturalDecimal, showNaturalHexadecimal,
+            readNaturalDecimal, readNaturalHexadecimal,
+
+        {- ** Single-digit strings -}
+            digitString, hexCharString,
+
+    {- * Classes -}
+
+        {- ** Supersets of ASCII -}
+            CharSuperset, StringSuperset,
+            StringSupersetConversion, ToText,
+
+        {- ** Equivalents to ASCII -}
+            CharIso, StringIso,
+
+        {- ** Supersets of numeric characters -}
+            DigitSuperset, DigitStringSuperset, HexCharSuperset, HexStringSuperset,
+
+    {- * Quasi-quoters -}
+        char, string, caseless, lower, upper,
+  )
+  where
+
+import ASCII.Case (Case (..))
+import ASCII.CaseRefinement (KnownCase (..), ASCII'case, ASCII'upper, ASCII'lower, refineCharToCase, refineStringToCase, forgetCase)
+import ASCII.Caseless (CaselessChar)
+import ASCII.Char (Char)
+import ASCII.Decimal (Digit, DigitStringSuperset, DigitSuperset, fromDigit, fromDigitList)
+import ASCII.Group (Group (..))
+import ASCII.Hexadecimal (HexChar, HexCharSuperset, HexStringSuperset, fromHexChar, fromHexCharList)
+import ASCII.Isomorphism (CharIso, StringIso)
+import ASCII.QuasiQuoters (char, string, caseless, lower, upper)
+import ASCII.Refinement (ASCII, lift, validateChar, validateString)
+import ASCII.Superset (CharSuperset, StringSuperset, toCharMaybe, toCharListMaybe, fromChar, fromCharList)
+import ASCII.SupersetConversion (StringSupersetConversion)
+import ASCII.Superset.Text (ToText (..))
+
+import Control.Monad ((>=>))
+import Control.Monad.Fail (MonadFail)
+import Data.Bits (Bits)
+import Data.Bool (Bool (..))
+import Data.Foldable (any)
+import Data.Function ((.))
+import Data.Int (Int)
+import Data.Maybe (Maybe, maybe)
+import Data.Word (Word8)
+import Numeric.Natural (Natural)
+import Prelude (Integral)
+
+import qualified ASCII.Case
+import qualified ASCII.Caseless
+import qualified ASCII.Decimal
+import qualified ASCII.Group
+import qualified ASCII.Hexadecimal
+import qualified ASCII.Isomorphism
+import qualified ASCII.Predicates
+import qualified ASCII.Superset
+import qualified ASCII.SupersetConversion as SupersetConversion
+
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as LBS
+import qualified Data.Char as Unicode
+import qualified Data.String as Unicode
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as Text
+import qualified Data.Text.Lazy as LText
+import qualified Data.Text.Lazy.Encoding as LText
+
+{- $char
+
+See also: "ASCII.Char" -}
+
+{- $caselessChar
+
+See also: "ASCII.Caseless" -}
+
+{-| A character in the full range of Unicode
+
+ASCII 'Char' is a subset of this type. Convert using 'charToUnicode' and
+'unicodeToCharMaybe'. -}
+type UnicodeChar = Unicode.Char
+
+{- $groups
+
+ASCII characters are broadly categorized into two groups: /control codes/ and
+/printable characters/.
+
+See also: "ASCII.Group" -}
+
+{-| Determine which group a particular character belongs to
+
+@
+charGroup CapitalLetterA == Printable
+
+charGroup EndOfTransmission == Control
+@ -}
+charGroup :: CharIso char => char -> Group
+charGroup = ASCII.Group.charGroup . ASCII.Isomorphism.toChar
+
+{-| Test whether a character belongs to a particular ASCII group
+
+@
+not (inGroup Printable EndOfTransmission)
+
+inGroup Control EndOfTransmission
+
+map (inGroup Printable) ([-1, 5, 65, 97, 127, 130] :: [Int])
+    == [False, False, True, True, False, False]
+@ -}
+inGroup :: CharSuperset char => Group -> char -> Bool
+inGroup g = maybe False (ASCII.Group.inGroup g) . ASCII.Superset.toCharMaybe
+
+{- $case
+
+/Case/ is a property of letters. /A-Z/ are /upper case/ letters, and /a-z/ are
+/lower case/ letters. No other ASCII characters have case.
+
+See also: "ASCII.Case" -}
+
+{-| Determines whether a character is an ASCII letter, and if so, whether it is
+    upper or lower case
+
+@
+map letterCase [SmallLetterA, CapitalLetterA, ExclamationMark]
+    == [Just LowerCase, Just UpperCase, Nothing]
+
+map letterCase ([string|Hey!|] :: [ASCII Word8])
+    == [Just UpperCase, Just LowerCase, Just LowerCase, Nothing]
+@ -}
+letterCase :: CharSuperset char => char -> Maybe Case
+letterCase = ASCII.Superset.toCharMaybe >=> ASCII.Case.letterCase
+
+{-| Determines whether a character is an ASCII letter of a particular case
+
+@
+map (isCase UpperCase) [SmallLetterA, CapitalLetterA, ExclamationMark]
+    == [False, True, False]
+
+map (isCase UpperCase) ([string|Hey!|] :: [ASCII Word8])
+    == [True, False, False, False]
+
+map (isCase UpperCase) ([-1, 65, 97, 150] :: [Int])
+    == [False, True, False, False]
+@ -}
+isCase :: CharSuperset char => Case -> char -> Bool
+isCase c = maybe False (ASCII.Case.isCase c) . ASCII.Superset.toCharMaybe
+
+{-| Maps a letter character to its upper/lower case equivalent
+
+@
+toCaseChar UpperCase SmallLetterA == CapitalLetterA
+
+(                     [char|a|] :: ASCII Word8) == asciiUnsafe 97
+(toCaseChar UpperCase [char|a|] :: ASCII Word8) == asciiUnsafe 65
+@ -}
+toCaseChar :: CharSuperset char => Case -> char -> char
+toCaseChar = ASCII.Superset.toCaseChar
+
+{-| Maps each of the characters in a string to its upper/lower case equivalent
+
+@
+toCaseString UpperCase [CapitalLetterH, SmallLetterE, SmallLetterY, ExclamationMark]
+    == [CapitalLetterH, CapitalLetterE, CapitalLetterY, ExclamationMark]
+
+(toCaseString UpperCase [string|Hey!|] :: ASCII Text) == asciiUnsafe "HEY!"
+@ -}
+toCaseString :: StringSuperset string => Case -> string -> string
+toCaseString = ASCII.Superset.toCaseString
+
+{-| Convert from ASCII character to caseless ASCII character, discarding the
+    case if the character is a letter -}
+disregardCase :: Char -> CaselessChar
+disregardCase = ASCII.Caseless.disregardCase
+
+{- $monomorphicConversions
+
+These are a few simple monomorphic functions to convert between ASCII and types
+representing some other character set.
+
+This is not intended to be an exhaustive list of all possible conversions. For
+more options, see "ASCII.Superset". -}
+
+{- $intConversions
+
+These functions convert between the ASCII 'Char' type and 'Int'. -}
+
+{-|
+
+@
+map charToInt [Null, CapitalLetterA, SmallLetterA, Delete] == [0, 65, 97, 127]
+@ -}
+charToInt :: Char -> Int
+charToInt = ASCII.Superset.fromChar
+
+intToCharMaybe :: Int -> Maybe Char
+intToCharMaybe = ASCII.Superset.toCharMaybe
+
+intToCharUnsafe :: Int -> Char
+intToCharUnsafe = ASCII.Superset.toCharUnsafe
+
+{- $word8Conversions
+
+These functions convert between the ASCII 'Char' type and 'Word8'. -}
+
+{-|
+
+@
+map charToWord8 [Null, CapitalLetterA, SmallLetterA, Delete] == [0, 65, 97, 127]
+@ -}
+charToWord8 :: Char -> Word8
+charToWord8 = ASCII.Superset.fromChar
+
+word8ToCharMaybe :: Word8 -> Maybe Char
+word8ToCharMaybe = ASCII.Superset.toCharMaybe
+
+word8ToCharUnsafe :: Word8 -> Char
+word8ToCharUnsafe = ASCII.Superset.toCharUnsafe
+
+{- $unicodeCharConversions
+
+These functions convert between the ASCII 'Char' type and the 'UnicodeChar'
+type. -}
+
+charToUnicode :: Char -> UnicodeChar
+charToUnicode = ASCII.Superset.fromChar
+
+unicodeToCharMaybe :: UnicodeChar -> Maybe Char
+unicodeToCharMaybe = ASCII.Superset.toCharMaybe
+
+unicodeToCharUnsafe :: UnicodeChar -> Char
+unicodeToCharUnsafe = ASCII.Superset.toCharUnsafe
+
+{- $digitWord8Conversions
+
+These functions convert between the ASCII 'Digit' type and ASCII digits in their
+byte encoding.
+
+These conversions do /not/ correspond to the numeric interpretations of 'Digit'
+and 'Word8'. For example, 'digitToWord8' 'ASCII.Decimal.Digit0' is 48, not 0. -}
+
+digitToWord8 :: Digit -> Word8
+digitToWord8 = ASCII.Decimal.fromDigit
+
+word8ToDigitMaybe :: Word8 -> Maybe Digit
+word8ToDigitMaybe = ASCII.Decimal.toDigitMaybe
+
+word8ToDigitUnsafe :: Word8 -> Digit
+word8ToDigitUnsafe = ASCII.Decimal.toDigitUnsafe
+
+{- $digitCharConversions
+
+These functions convert between the ASCII 'Digit' type and the ASCII 'Char'
+type. -}
+
+digitToChar :: Digit -> Char
+digitToChar = ASCII.Decimal.fromDigit
+
+charToDigitMaybe :: Char -> Maybe Digit
+charToDigitMaybe = ASCII.Decimal.toDigitMaybe
+
+charToDigitUnsafe :: Char -> Digit
+charToDigitUnsafe = ASCII.Decimal.toDigitUnsafe
+
+{- $digitUnicodeConversions
+
+These functions convert between the ASCII 'Digit' type and the 'UnicodeChar'
+type. -}
+
+digitToUnicode :: Digit -> UnicodeChar
+digitToUnicode = ASCII.Decimal.fromDigit
+
+unicodeToDigitMaybe :: UnicodeChar -> Maybe Digit
+unicodeToDigitMaybe = ASCII.Decimal.toDigitMaybe
+
+unicodeToDigitUnsafe :: UnicodeChar -> Digit
+unicodeToDigitUnsafe = ASCII.Decimal.toDigitUnsafe
+
+
+{- $hexCharWord8Conversions
+
+These functions convert between the ASCII 'HexChar' type and ASCII characters in
+their byte encoding.
+
+These conversions do /not/ correspond to the numeric interpretations of
+'HexChar' and 'Word8'. For example, 'hexCharToWord8'
+'ASCII.Hexadecimal.CapitalLetterA' is 65, not 10. -}
+
+hexCharToWord8 :: HexChar -> Word8
+hexCharToWord8 = ASCII.Hexadecimal.fromHexChar
+
+word8ToHexCharMaybe :: Word8 -> Maybe HexChar
+word8ToHexCharMaybe = ASCII.Hexadecimal.toHexCharMaybe
+
+word8ToHexCharUnsafe :: Word8 -> HexChar
+word8ToHexCharUnsafe = ASCII.Hexadecimal.toHexCharUnsafe
+
+{- $hexCharCharConversions
+
+These functions convert between the ASCII 'HexChar' type and the ASCII 'Char'
+type. -}
+
+hexCharToChar :: HexChar -> Char
+hexCharToChar = ASCII.Hexadecimal.fromHexChar
+
+charToHexCharMaybe :: Char -> Maybe HexChar
+charToHexCharMaybe = ASCII.Hexadecimal.toHexCharMaybe
+
+charToHexCharUnsafe :: Char -> HexChar
+charToHexCharUnsafe = ASCII.Hexadecimal.toHexCharUnsafe
+
+{- $hexCharUnicodeConversions
+
+These functions convert between the ASCII 'HexChar' type and the 'UnicodeChar'
+type. -}
+
+hexCharToUnicode :: HexChar -> UnicodeChar
+hexCharToUnicode = ASCII.Hexadecimal.fromHexChar
+
+unicodeToHexCharMaybe :: UnicodeChar -> Maybe HexChar
+unicodeToHexCharMaybe = ASCII.Hexadecimal.toHexCharMaybe
+
+unicodeToHexCharUnsafe :: UnicodeChar -> HexChar
+unicodeToHexCharUnsafe = ASCII.Hexadecimal.toHexCharUnsafe
+
+{- $unicodeStringConversions
+
+These functions convert between @['Char']@ (a list of ASCII characters) and
+'Unicode.String' (a list of Unicode characters). -}
+
+charListToUnicodeString :: [Char] -> Unicode.String
+charListToUnicodeString = ASCII.Superset.fromCharList
+
+unicodeStringToCharListMaybe :: Unicode.String -> Maybe [Char]
+unicodeStringToCharListMaybe = ASCII.Superset.toCharListMaybe
+
+unicodeStringToCharListUnsafe :: Unicode.String -> [Char]
+unicodeStringToCharListUnsafe = ASCII.Superset.toCharListUnsafe
+
+{- $textConversions
+
+These functions convert between @['Char']@ and 'Text.Text'. -}
+
+{-|
+
+@
+charListToText [CapitalLetterH, SmallLetterI, ExclamationMark] == "Hi!"
+@ -}
+charListToText :: [Char] -> Text.Text
+charListToText = ASCII.Superset.fromCharList
+
+textToCharListMaybe :: Text.Text -> Maybe [Char]
+textToCharListMaybe = ASCII.Superset.toCharListMaybe
+
+textToCharListUnsafe :: Text.Text -> [Char]
+textToCharListUnsafe = ASCII.Superset.toCharListUnsafe
+
+{- $byteStringConversions
+
+These functions convert between @['Char']@ and 'BS.ByteString'. -}
+
+charListToByteString :: [Char] -> BS.ByteString
+charListToByteString = ASCII.Superset.fromCharList
+
+byteStringToCharListMaybe :: BS.ByteString -> Maybe [Char]
+byteStringToCharListMaybe = ASCII.Superset.toCharListMaybe
+
+byteStringToCharListUnsafe :: BS.ByteString -> [Char]
+byteStringToCharListUnsafe = ASCII.Superset.toCharListUnsafe
+
+asciiByteStringToText :: ASCII BS.ByteString -> Text.Text
+asciiByteStringToText = Text.decodeUtf8 . ASCII.Refinement.lift
+
+asciiByteStringToTextLazy :: ASCII LBS.ByteString -> LText.Text
+asciiByteStringToTextLazy = LText.decodeUtf8 . ASCII.Refinement.lift
+
+{- $refinement
+
+See also: "ASCII.Refinement", "ASCII.CaseRefinement" -}
+
+{- $lift
+
+See also: "ASCII.Refinement" -}
+
+{- $toText
+
+See also: "ASCII.Superset.ToText" -}
+
+{- $fromChar
+
+See also: "ASCII.Superset" -}
+
+{- $fromDigit
+
+See also: "ASCII.Decimal" -}
+
+{- $fromHexChar
+
+See also: "ASCII.Hexadecimal" -}
+
+{- $forgetCase
+
+See also: "ASCII.CaseRefinement" -}
+
+{- $supersetConversions
+
+These functions all convert from one ASCII-superset type to another, failing if
+any of the characters in the input is outside the ASCII character set. -}
+
+convertCharMaybe :: (CharSuperset char1, CharSuperset char2) => char1 -> Maybe char2
+convertCharMaybe = ASCII.Superset.convertCharMaybe
+
+convertCharOrFail :: (CharSuperset char1, CharSuperset char2, MonadFail context) => char1 -> context char2
+convertCharOrFail = ASCII.Superset.convertCharOrFail
+
+convertStringMaybe :: (StringSuperset string1, StringSuperset string2) => string1 -> Maybe string2
+convertStringMaybe = ASCII.Superset.convertStringMaybe
+
+convertStringOrFail :: (StringSuperset string1, StringSuperset string2, MonadFail context) => string1 -> context string2
+convertStringOrFail = ASCII.Superset.convertStringOrFail
+
+{- $monoSupersetConversions
+
+These functions are all specializations of 'convertStringMaybe'.
+They convert a string from one ASCII-superset type to another.
+
+@
+ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x50] == Just \"HTTP"
+@
+
+If any of the characters in the input is outside the ASCII character set, the
+result is 'Nothing'.
+
+@
+ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x80] == Nothing
+@ -}
+byteStringToUnicodeStringMaybe :: BS.ByteString -> Maybe Unicode.String
+byteStringToUnicodeStringMaybe = convertStringMaybe
+
+unicodeStringToByteStringMaybe :: Unicode.String -> Maybe BS.ByteString
+unicodeStringToByteStringMaybe = convertStringMaybe
+
+byteListToUnicodeStringMaybe :: [Word8] -> Maybe Unicode.String
+byteListToUnicodeStringMaybe = convertStringMaybe
+
+unicodeStringToByteListMaybe :: Unicode.String -> Maybe [Word8]
+unicodeStringToByteListMaybe = convertStringMaybe
+
+{-| Returns True for ASCII letters:
+
+- 'ASCII.Char.SmallLetterA' to 'ASCII.Char.SmallLetterZ'
+- 'ASCII.Char.CapitalLetterA' to 'ASCII.Char.CapitalLetterZ' -}
+isLetter :: CharSuperset char => char -> Bool
+isLetter x = any ASCII.Predicates.isLetter (convertCharMaybe x)
+
+{-| Returns True for the characters from 'ASCII.Char.Digit0' to
+'ASCII.Char.Digit9'. -}
+isDigit :: CharSuperset char => char -> Bool
+isDigit x = any ASCII.Predicates.isDigit (convertCharMaybe x)
+
+{-| Returns True for the characters from 'ASCII.Char.Digit0' to
+'ASCII.Char.Digit7'. -}
+isOctDigit :: CharSuperset char => char -> Bool
+isOctDigit x = any ASCII.Predicates.isOctDigit (convertCharMaybe x)
+
+{-| Returns True for characters in any of the following ranges:
+
+- 'ASCII.Char.Digit0' to 'ASCII.Char.Digit9'
+- 'ASCII.Char.CapitalLetterA' to 'ASCII.Char.CapitalLetterF'
+- 'ASCII.Char.SmallLetterA' to 'ASCII.Char.SmallLetterF' -}
+isHexDigit :: CharSuperset char => char -> Bool
+isHexDigit x = any ASCII.Predicates.isHexDigit (convertCharMaybe x)
+
+{-| Returns True for the following characters:
+
+- 'ASCII.Char.Space'
+- 'ASCII.Char.HorizontalTab'
+- 'ASCII.Char.LineFeed'
+- 'ASCII.Char.VerticalTab'
+- 'ASCII.Char.FormFeed'
+- 'ASCII.Char.CarriageReturn' -}
+isSpace :: CharSuperset char => char -> Bool
+isSpace x = any ASCII.Predicates.isSpace (convertCharMaybe x)
+
+{-| Returns True if the character is either an ASCII letter ('isLetter') or an
+ASCII digit ('isDigit'). -}
+isAlphaNum :: CharSuperset char => char -> Bool
+isAlphaNum x = any ASCII.Predicates.isAlphaNum (convertCharMaybe x)
+
+{-| Returns True for the following characters:
+
+- 'ASCII.Char.ExclamationMark'
+- 'ASCII.Char.QuotationMark'
+- 'ASCII.Char.NumberSign'
+- 'ASCII.Char.PercentSign'
+- 'ASCII.Char.Ampersand'
+- 'ASCII.Char.Apostrophe'
+- 'ASCII.Char.LeftParenthesis'
+- 'ASCII.Char.RightParenthesis'
+- 'ASCII.Char.Asterisk'
+- 'ASCII.Char.Comma'
+- 'ASCII.Char.HyphenMinus'
+- 'ASCII.Char.FullStop'
+- 'ASCII.Char.Slash'
+- 'ASCII.Char.Colon'
+- 'ASCII.Char.Semicolon'
+- 'ASCII.Char.QuestionMark'
+- 'ASCII.Char.AtSign'
+- 'ASCII.Char.LeftSquareBracket'
+- 'ASCII.Char.Backslash'
+- 'ASCII.Char.RightSquareBracket'
+- 'ASCII.Char.Underscore'
+- 'ASCII.Char.LeftCurlyBracket'
+- 'ASCII.Char.RightCurlyBracket' -}
+isPunctuation :: CharSuperset char => char -> Bool
+isPunctuation x = any ASCII.Predicates.isPunctuation (convertCharMaybe x)
+
+{-| Returns True for the following characters:
+
+- 'ASCII.Char.DollarSign'
+- 'ASCII.Char.PlusSign'
+- 'ASCII.Char.LessThanSign'
+- 'ASCII.Char.EqualsSign'
+- 'ASCII.Char.GreaterThanSign'
+- 'ASCII.Char.Caret'
+- 'ASCII.Char.GraveAccent'
+- 'ASCII.Char.VerticalLine'
+- 'ASCII.Char.Tilde' -}
+isSymbol :: CharSuperset char => char -> Bool
+isSymbol x = any ASCII.Predicates.isSymbol (convertCharMaybe x)
+
+{- |Returns True for visible characters
+
+This includes all print characters except 'ASCII.Char.Space'. -}
+isVisible :: CharSuperset char => char -> Bool
+isVisible x = any ASCII.Predicates.isVisible (convertCharMaybe x)
+
+{- $numbers
+
+See also: "ASCII.Decimal" and "ASCII.Hexadecimal" -}
+
+{-| Gives the ASCII string representation of an integer in decimal (base 10)
+    notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9',
+    leading with 'ASCII.Char.HyphenMinus' for negative numbers
+
+For example, @'showIntegralDecimal' (-512 :: 'Prelude.Integer')@ = @"-512"@. -}
+showIntegralDecimal :: (Integral n, StringSuperset string) => n -> string
+showIntegralDecimal = ASCII.Decimal.showIntegral
+
+{-| Gives the ASCII string representation of an integer in hexadecimal (base 16)
+    notation
+
+The characters 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9' represent digits
+0 though 9. The representation of digits 10 to 15 is determined by the value of
+'Case' parameter: 'UpperCase' means 'ASCII.Char.CapitalLetterA' to
+'ASCII.Char.CapitalLetterF', and 'LowerCase' means 'ASCII.Char.SmallLetterA' to
+'ASCII.Char.SmallLetterF'. For negative numbers, the resulting string begins
+with 'ASCII.Char.HyphenMinus'.
+
+@
+'showIntegralHexadecimal' 'UpperCase' ('Prelude.negate' (256 + 12) :: 'Prelude.Integer') == "-10C"
+@ -}
+showIntegralHexadecimal :: (Integral n, StringSuperset string) =>
+    Case -> n -> string
+showIntegralHexadecimal = ASCII.Hexadecimal.showIntegral
+
+{-| Roughly the inverse of 'showIntegralDecimal'
+
+* Leading zeroes are accepted, as in @"0074"@ and @"-0074"@
+
+Conditions where the result is 'Data.Maybe.Nothing':
+
+* If the input is empty
+* If the input contains any other extraneous characters
+* If the resulting number would be outside the range supported by the 'Integral'
+  (determined by its 'Bits' instance) -}
+readIntegralDecimal :: (StringSuperset string, Integral number, Bits number) =>
+    string -> Maybe number
+readIntegralDecimal = ASCII.Decimal.readIntegral
+
+{-| Roughly the inverse of 'showIntegralHexadecimal'
+
+* Upper and lower case letters are treated equally
+* Leading zeroes are accepted, as in @"006a"@ and @"-006a"@
+
+Conditions where the result is 'Data.Maybe.Nothing':
+
+* If the input is empty
+* If the input contains any other extraneous characters
+* If the resulting number would be outside the range supported by the 'Integral'
+  (determined by its 'Bits' instance) -}
+readIntegralHexadecimal :: (StringSuperset string, Integral number, Bits number) =>
+    string -> Maybe number
+readIntegralHexadecimal = ASCII.Hexadecimal.readIntegral
+
+{-| Gives the ASCII string representation of an natural number in decimal
+    (base 10) notation, using digits 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9'
+
+@
+'showNaturalDecimal' 512 == @"512"
+@ -}
+showNaturalDecimal :: DigitStringSuperset string => Natural -> string
+showNaturalDecimal = ASCII.Decimal.showNatural
+
+{-| Gives the ASCII string representation of an integer in hexadecimal (base 16)
+    notation
+
+Characters 'ASCII.Char.Digit0' through 'ASCII.Char.Digit9' represent digits 0
+though 9. The representation of digits 10 to 15 is determined by the value of
+'Case' parameter: 'UpperCase' means 'ASCII.Char.CapitalLetterA' to
+'ASCII.Char.CapitalLetterF', and 'LowerCase' means 'ASCII.Char.SmallLetterA' to
+'ASCII.Char.SmallLetterF'.
+
+@
+'showNaturalHexadecimal' 'UpperCase' (256 + 12) == "10C"
+@ -}
+showNaturalHexadecimal :: HexStringSuperset string => Case -> Natural -> string
+showNaturalHexadecimal = ASCII.Hexadecimal.showNatural
+
+{-| Roughly the inverse of 'showNaturalDecimal'
+
+* Leading zeroes are accepted, as in @"0074"@
+
+Conditions where the result is 'Data.Maybe.Nothing':
+
+* If the input is empty
+* If the input contains any other extraneous characters -}
+readNaturalDecimal :: DigitStringSuperset string => string -> Maybe Natural
+readNaturalDecimal = ASCII.Decimal.readNatural
+
+{-| Roughly the inverse of 'showNaturalHexadecimal'
+
+* Upper and lower case letters are treated equally
+* Leading zeroes are accepted, as in @"006a"@
+
+Conditions where the result is 'Data.Maybe.Nothing':
+
+* If the input is empty
+* If the input contains any other extraneous characters -}
+readNaturalHexadecimal :: HexStringSuperset string => string -> Maybe Natural
+readNaturalHexadecimal = ASCII.Hexadecimal.readNatural
+
+{- $digit
+
+See also: "ASCII.Decimal" -}
+
+{- $hexchar
+
+See also: "ASCII.Hexadecimal" -}
+
+{-| Specialization of 'showNaturalDecimal'
+
+See also: 'showIntegralDecimal' -}
+showNaturalDigits :: Natural -> [Digit]
+showNaturalDigits = showNaturalDecimal
+
+{-| Specialization of 'readNaturalDecimal'
+
+See also: 'readIntegralDecimal' -}
+readNaturalDigits :: [Digit] -> Maybe Natural
+readNaturalDigits = readNaturalDecimal
+
+{-| Specialization of 'showNaturalHexadecimal'
+
+See also: 'showIntegralHexadecimal' -}
+
+showNaturalHexChars :: Case -> Natural -> [HexChar]
+showNaturalHexChars = showNaturalHexadecimal
+
+{-| Specialization of 'readNaturalHexadecimal'
+
+See also: 'readIntegralHexadecimal' -}
+readNaturalHexChars :: [HexChar] -> Maybe Natural
+readNaturalHexChars = readNaturalHexadecimal
+
+{-| A string containing a single digit character 0-9 -}
+digitString :: DigitStringSuperset string => Digit -> string
+digitString x = ASCII.Decimal.fromDigitList [x]
+
+{-| A string containing a single hexadecimal digit character
+    0-9, A-F, or a-f -}
+hexCharString :: HexStringSuperset string => HexChar -> string
+hexCharString x = ASCII.Hexadecimal.fromHexCharList [x]
+
+toDigitMaybe :: DigitSuperset char => char -> Maybe Digit
+toDigitMaybe = ASCII.Decimal.toDigitMaybe
+
+toHexCharMaybe :: HexCharSuperset char => char -> Maybe HexChar
+toHexCharMaybe = ASCII.Hexadecimal.toHexCharMaybe
+
+{-| For example, this function can convert @ASCII ByteString@
+    to @ASCII Text@ and vice versa -}
+convertRefinedString ::
+    StringSupersetConversion a b => ASCII a -> ASCII b
+convertRefinedString = SupersetConversion.convertRefinedString
diff --git a/license.txt b/license.txt
new file mode 100644
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,13 @@
+Copyright 2021 Mission Valley Software LLC
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,69 @@
+## What is ASCII?
+
+The *American Standard Code for Information Interchange* (ASCII) comprises a set
+of 128 characters, each represented by 7 bits. 33 of these characters are
+"control codes"; a few of these are still in use, but most are obsolete relics
+of the early days of computing. The other 95 are "printable characters" such as
+letters and numbers, mostly corresponding to the keys on an American English
+keyboard.
+
+Nowadays instead of ASCII we typically work with text using an encoding such as
+UTF-8 that can represent the entire Unicode character set, which includes over a
+hundred thousand characters and is not limited to the symbols of any particular
+writing system or culture. However, ASCII is still relevant to network
+protocols; for example, we can see it in the specification of [HTTP].
+
+There is a convenient relationship between ASCII and Unicode: the ASCII
+characters are the first 128 characters of the much larger Unicode character
+set. The [C0 Controls and Basic Latin][unicode] section of the Unicode standard
+contains a list of all the ASCII characters.
+
+## Haskell packages
+
+This repository contains the main API, the [`ASCII`][ascii] module in the
+`ascii` package, which is an amalgamation of smaller packages in other
+repositories.
+
+  * If you only need the ASCII [`Char`][char] type, you can use the
+    `ascii-char` package, which is minimal so that it can be kept stable.
+
+  * The `ascii-group` package defines the [`Group`][group] type (`Control` and
+    `Printable`), and the `ascii-case` package defines the [`Case`][case] type
+    (`UpperCase` and `LowerCase`). These package are also small and stable.
+
+  * The `ascii-predicates` package provides [additional ways of categorizing
+    characters][predicates] similar to what you can find in [the `base`
+    package][base].
+
+  * For case-insensitivity, use the `ascii-caseless` package.
+
+  * The `ascii-superset` package defines [`CharSuperset` and
+    `StringSuperset`][superset] classes to generalize types that represent
+    characters and strings, respectively, in character sets larger than ASCII.
+    It also defines the [`ASCII`][refinement] type constructor, which is used
+    to indicate that a value from some ASCII superset is confined to ASCII.
+
+  * The `ascii-numbers` package provides utilities for working with numbers
+    represented using ASCII digits 0-9, ASCII letters A-F to represent
+    hexadecimal digits 10-15, and the `HyphenMinus` character for negation.
+
+  * The `ascii-th` package provides a [quasi-quoter][qq] that allows one to
+    safely and conveniently express ASCII string literals. The generated
+    expressions are polymorphic and can take the form of any type belonging to
+    the `StringSuperset` class, including `[ASCII.Char]`, [`String`][string],
+    [`ByteString`][bytestring], and [`Text`][text].
+
+  [HTTP]: https://www.rfc-editor.org/rfc/rfc9110.html#name-syntax-notation
+  [unicode]: https://www.unicode.org/charts/PDF/U0000.pdf
+  [ascii]: https://hackage.haskell.org/package/ascii/docs/ASCII.html
+  [char]: https://hackage.haskell.org/package/ascii-char/docs/ASCII-Char.html
+  [group]: https://hackage.haskell.org/package/ascii-group/docs/ASCII-Group.html
+  [case]: https://hackage.haskell.org/package/ascii-case/docs/ASCII-Case.html
+  [predicates]: https://hackage.haskell.org/package/ascii-predicates/docs/ASCII-ListsAndPredicates.html
+  [base]: https://hackage.haskell.org/package/base/docs/Data-Char.html
+  [superset]: https://hackage.haskell.org/package/ascii-superset/docs/ASCII-Superset.html
+  [refinement]: https://hackage.haskell.org/package/ascii-superset/docs/ASCII-Refinement.html
+  [qq]: https://hackage.haskell.org/package/ascii-th/docs/ASCII-QuasiQuoters.html
+  [string]: https://hackage.haskell.org/package/base/docs/Data-String.html
+  [bytestring]: https://hackage.haskell.org/package/bytestring
+  [text]: https://hackage.haskell.org/package/text
diff --git a/test/Main.hs b/test/Main.hs
new file mode 100644
--- /dev/null
+++ b/test/Main.hs
@@ -0,0 +1,148 @@
+module Main (main) where
+
+import ASCII
+
+import ASCII.Char (Char (..))
+import ASCII.Refinement (asciiUnsafe)
+
+import Data.Bool (Bool (..))
+import Data.Function (($))
+import Data.Int (Int)
+import Data.List (map)
+import Data.Maybe (Maybe (..))
+import Data.Text (Text)
+import Data.Word (Word8)
+import Prelude (enumFromTo, maxBound, minBound)
+import System.IO (IO)
+
+import Test.Hspec (describe, hspec, it, shouldBe)
+
+main :: IO ()
+main = hspec $ do
+
+    describe "charGroup" $ do
+        let c ~> g = charGroup c `shouldBe` g
+
+        it "A is printable"                 $ CapitalLetterA    ~> Printable
+        it "end-of-transmission is control" $ EndOfTransmission ~> Control
+
+    describe "inGroup" $ do
+
+        describe "tests Char" $ do
+            it "end-of-transmission is not printable" $
+                inGroup Printable EndOfTransmission `shouldBe` False
+            it "end-of-transmission is control" $
+                inGroup Control   EndOfTransmission `shouldBe` True
+
+        describe "tests Int" $ do
+            let i ~> b = inGroup Printable (i :: Int) `shouldBe` b
+
+            it "-1 is not printable (isn't anything)"  $ (-1) ~> False
+            it "5 is not printable"                    $ 5    ~> False
+            it "65 is printable"                       $ 65   ~> True
+            it "97 is printable"                       $ 97   ~> True
+            it "127 is not printable"                  $ 127  ~> False
+            it "128 is not printable (isn't anything)" $ 128  ~> False
+
+    describe "letterCase" $ do
+        let ch ~> ca = letterCase ch `shouldBe` ca
+
+        describe "works with Char" $ do
+            it "a is lower"    $ SmallLetterA    ~> Just LowerCase
+            it "A is upper"    $ CapitalLetterA  ~> Just UpperCase
+            it "! has no case" $ ExclamationMark ~> Nothing
+
+        it "works with ASCII Word8" $ do
+            map letterCase ([string|Hey!|] :: [ASCII Word8]) `shouldBe`
+                [Just UpperCase, Just LowerCase, Just LowerCase, Nothing]
+
+    describe "isCase" $ do
+
+        describe "works with Char" $ do
+            let c ~> b = isCase UpperCase c `shouldBe` b
+
+            it "a is not upper" $ SmallLetterA    ~> False
+            it "A is upper"     $ CapitalLetterA  ~> True
+            it "! is not upper" $ ExclamationMark ~> False
+
+        it "works with ASCII Word8" $ do
+            map (isCase UpperCase) ([string|Hey!|] :: [ASCII Word8])
+                `shouldBe` [True, False, False, False]
+
+        describe "works with Int" $ do
+            let i ~> b = isCase UpperCase (i :: Int) `shouldBe` b
+
+            it "-1 is not upper (isn't anything)"  $ (-1) ~> False
+            it "65 is upper"                       $ 65   ~> True
+            it "97 is not upper"                   $ 97   ~> False
+            it "128 is not upper (isn't anything)" $ 128  ~> False
+
+    describe "toCaseChar" $ do
+
+        describe "works with Char" $ do
+            it "a to upper is A" $ toCaseChar UpperCase SmallLetterA
+                `shouldBe` CapitalLetterA
+
+        describe "works with ASCII Word8" $ do
+            it "a to upper is 65" $ toCaseChar UpperCase ([char|a|] :: ASCII Word8)
+                `shouldBe` (asciiUnsafe 65 :: ASCII Word8)
+
+    describe "toCaseString" $ do
+        it "converts each Char to a case" $ do
+            let check a b = toCaseString UpperCase a `shouldBe` b
+            check [ CapitalLetterH, SmallLetterE,   SmallLetterY,   ExclamationMark ]
+                  [ CapitalLetterH, CapitalLetterE, CapitalLetterY, ExclamationMark ]
+
+        it "works with ASCII Text" $
+            (toCaseString UpperCase [string|Hey!|] :: ASCII Text)
+                `shouldBe` asciiUnsafe "HEY!"
+
+    describe "charToInt" $ do
+        let c ~> i = charToInt c `shouldBe` i
+
+        it "Null is 0"     $ Null           ~> 0
+        it "A is 65"       $ CapitalLetterA ~> 65
+        it "a is 97"       $ SmallLetterA   ~> 97
+        it "Delete is 127" $ Delete         ~> 127
+
+    describe "charToWord8" $ do
+        let c ~> i = charToWord8 c `shouldBe` i
+
+        it "Null is 0"     $ Null           ~> 0
+        it "A is 65"       $ CapitalLetterA ~> 65
+        it "a is 97"       $ SmallLetterA   ~> 97
+        it "Delete is 127" $ Delete         ~> 127
+
+    describe "charListToText" $ do
+        it "packs a list of Char into Text" $
+            charListToText [CapitalLetterH, SmallLetterI, ExclamationMark]
+                `shouldBe` "Hi!"
+
+    describe "fromChar" $ do
+        it "converts Char to Word8" $ (fromChar CapitalLetterA :: Word8) `shouldBe` 65
+
+    describe "fromCharList" $ do
+        it "converts [Char] to Text" $
+            (fromCharList [CapitalLetterH, SmallLetterI, ExclamationMark] :: Text)
+                `shouldBe` "Hi!"
+
+    describe "byteListToUnicodeStringMaybe" $ do
+        it "converts [Word8] to String" $
+            ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x50]
+                `shouldBe` Just "HTTP"
+        it "can fail" $ ASCII.byteListToUnicodeStringMaybe [0x48, 0x54, 0x54, 0x80]
+            `shouldBe` Nothing
+
+    describe "digitString" $ do
+        it "converts Digit to a single character of Text" $ do
+            let f = digitString :: Digit -> Text
+            map f (enumFromTo minBound maxBound) `shouldBe`
+                ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]
+
+    describe "hexCharString" $ do
+        it "converts HexChar to a single character of Text" $ do
+            let f = hexCharString :: HexChar -> Text
+            map f (enumFromTo minBound maxBound) `shouldBe`
+                [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
+                , "A", "B", "C", "D", "E", "F"
+                , "a", "b", "c", "d", "e", "f" ]
