diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# 0.1.0.0
+
+  * first release
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Chris Dornan (c) 2019
+
+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 Chris Dornan 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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,32 @@
+# enum-text
+
+A simple toolkit for rendering enumerated types into `Text` `Builder` (used by
+the [`fmt`](http://hackage.haskell.org/package/fmt) package) and parsing them
+back again into Text with the provided `TextParsable` type class.
+
+To get the `Buildable` and `TextParsable` instances for an enumerated data type
+use the following pattern:
+
+```
+import Fmt
+import Text.Enum.Text
+
+data Foo = FOO_bar | FOO_bar_baz
+  deriving (Bounded,Enum,Eq,Ord,Show)
+
+instance EnumText     Foo
+instance Buildable    Foo where build     = buildEnumText
+instance TextParsable Foo where parseText = parseEnumText
+```
+
+This will use the default configuration for generating the text of each
+enumeration from the derived `show` text, namely:
+
+  * removing the prefix upto and including the first underscore (`_`);
+  * converting each subsequent underscore (`_`) into a dash (`-`).
+
+See the Haddocks for details on how to override this default configuration for
+any given enumeration type.
+
+Functions for rendering text, generating and parsing UTF-8 encoded ByteStrings
+(suitable for cassava) and `Hashable` functions are also provided `EnumText`.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/enum-text.cabal b/enum-text.cabal
new file mode 100644
--- /dev/null
+++ b/enum-text.cabal
@@ -0,0 +1,47 @@
+cabal-version: 1.12
+
+-- This file has been generated from package.yaml by hpack version 0.31.1.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: de41441f44fa188062adf3893567b62b7ee598e3ad3cac9d4a37c598b02684da
+
+name:           enum-text
+version:        0.1.0.0
+synopsis:       A text rendering and parsing toolkit for enumerated types
+description:    A text rendering and parsing toolkit for enumerated types. Please see the README on GitHub at <https://github.com/cdornan/enum-text#readme>
+category:       Text
+homepage:       https://github.com/cdornan/enum-text#readme
+bug-reports:    https://github.com/cdornan/enum-text/issues
+author:         Chris Dornan
+maintainer:     chris@chrisdornan.com
+copyright:      2019 Chris Dornan
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+    ChangeLog.md
+
+source-repository head
+  type: git
+  location: https://github.com/cdornan/enum-text
+
+library
+  exposed-modules:
+      Text.Enum.Text
+  other-modules:
+      Paths_enum_text
+  hs-source-dirs:
+      src
+  ghc-options: -Wall
+  build-depends:
+      array
+    , base >=4.8.2 && <10
+    , bytestring
+    , fmt
+    , hashable
+    , possibly
+    , text
+    , unordered-containers
+  default-language: Haskell2010
diff --git a/src/Text/Enum/Text.hs b/src/Text/Enum/Text.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Enum/Text.hs
@@ -0,0 +1,165 @@
+{-# LANGUAGE OverloadedStrings    #-}
+{-# LANGUAGE RecordWildCards      #-}
+{-# LANGUAGE ScopedTypeVariables  #-}
+
+module Text.Enum.Text
+  ( EnumText(..)
+  , TextParsable(..)
+  , EnumTextConfig(..)
+  , defaultEnumTextConfig
+  ) where
+
+import           Data.Array
+import qualified Data.ByteString.Char8          as B
+import           Data.Coerce
+import           Data.Hashable
+import           Data.Possibly
+import qualified Data.HashMap.Strict            as HM
+import qualified Data.Text                      as T
+import qualified Data.Text.Encoding             as TE
+import           Fmt
+
+
+-- | a class for 'T.Text' parsers.
+class TextParsable a where
+  parseText :: T.Text -> Possibly a
+
+{- | our toolkit for enumerated types which should be defined as follows:
+
+@
+import Fmt
+import Text.Enum.Text
+
+data Foo = FOO_bar | FOO_bar_baz
+  deriving (Bounded,Enum,Eq,Ord,Show)
+
+instance EnumText     Foo
+instance Buildable    Foo where build     = buildEnumText
+instance TextParsable Foo where parseText = parseEnumText
+@
+
+-}
+class ( Buildable     e
+      , Bounded       e
+      , Enum          e
+      , Eq            e
+      , Ord           e
+      , Show          e
+      , TextParsable  e
+      ) => EnumText e where
+
+  -- | Configures the textual representation of @e@ generated by renderEnumText.
+  configEnumText :: e -> EnumTextConfig
+  configEnumText _ = defaultEnumTextConfig
+
+  -- | Generate the standard textual representation according to
+  -- 'configEnumText' by default.
+  renderEnumText :: e -> T.Text
+  renderEnumText e = enumTextArray ! I e
+
+  -- | Sames as 'renderEnumText', but generating a 'Builder'.
+  buildEnumText :: e -> Builder
+  buildEnumText = build . renderEnumText
+
+  -- | Parses an @e@ according to the 'renderEnumText' render.
+  parseEnumText :: T.Text -> Possibly e
+  parseEnumText txt = maybe (Left msg) Right $ HM.lookup txt hashmap_t
+    where
+      msg = "parseEnumText: enumeration not recognised: "++show txt
+
+  -- | A cassava field encoder, using 'the renderEnumText' format.
+  toFieldEnumText :: e -> B.ByteString
+  toFieldEnumText e = enumByteStringArray ! I e
+
+  -- | A cassava field parser using the 'renderEnumText' format.
+  fromFieldEnumText_ :: Monad m => B.ByteString -> m e
+  fromFieldEnumText_ bs = maybe (fail msg) return $ HM.lookup bs hashmap_b
+    where
+      msg = "fromFieldEnumText_: enumeration not recognised: "++show bs
+
+  -- | For hashing @e@ with the 'renderEnumText' representation.
+  hashWithSaltEnumText :: Int -> e -> Int
+  hashWithSaltEnumText n = hashWithSalt n . toFieldEnumText
+
+
+-------------------------------------------------------------------------------
+-- EnumTextConfig, defaultEnumTextConfig
+-------------------------------------------------------------------------------
+
+-- | configures the default implementation of 'renderEnumText'
+data EnumTextConfig =
+  EnumTextConfig
+    { _etc_text_prep :: T.Text -> T.Text  -- ^ applied to the output of 'show'
+                                          -- once converted to 'T.Text'; by
+                                          -- default strips each data
+                                          -- constructor up to and including
+                                          -- the first '_'
+    , _etc_char_prep :: Char -> Char      -- ^ applied to each character of
+                                          -- the outpout of '_etc_text_prep'
+    }
+
+defaultEnumTextConfig :: EnumTextConfig
+defaultEnumTextConfig =
+  EnumTextConfig
+    { _etc_text_prep = defaultTextPrep
+    , _etc_char_prep = defaultCharPrep
+    }
+
+defaultTextPrep :: T.Text -> T.Text
+defaultTextPrep txt = case T.uncons $ T.dropWhile (/='_') txt of
+    Just (_,rst) | not $ T.null rst -> rst
+    _ -> error $ "defaultTextPrep: bad data constructor: "++T.unpack txt
+
+defaultCharPrep :: Char -> Char
+defaultCharPrep c = case c of
+    '_' -> '-'
+    _   -> c
+
+
+-------------------------------------------------------------------------------
+-- arrays
+-------------------------------------------------------------------------------
+
+newtype I a = I { _I :: a }
+  deriving (Eq,Ord)
+
+instance EnumText e => Ix (I e) where
+  range   (l,h)   = coerce [_I l.._I h]
+  index   (l,_) x = fromEnum (_I x) - fromEnum (_I l)
+  inRange (l,h) x = _I l <= _I x && _I x <= _I h
+
+-- | array of texts constructed with 'configEnumText'
+enumTextArray :: forall e . EnumText e => Array (I e) T.Text
+enumTextArray =
+    listArray (I minBound,I maxBound)
+      [ T.map _etc_char_prep $ _etc_text_prep $ T.pack $ show e
+        | e <- [minBound..maxBound :: e]
+        ]
+  where
+    EnumTextConfig{..} = configEnumText (minBound :: e)
+
+-- | array of 'B.ByteString' generated from 'renderEnumText'
+enumByteStringArray :: forall e . EnumText e => Array (I e) B.ByteString
+enumByteStringArray = listArray (I minBound,I maxBound)
+    [ TE.encodeUtf8 $ renderEnumText e
+      | e <- [minBound..maxBound :: e]
+      ]
+
+
+-------------------------------------------------------------------------------
+-- hashmaps
+-------------------------------------------------------------------------------
+
+-- | 'T.Text' 'HM.HashMap' based on 'renderEnumText' representation
+hashmap_t :: EnumText e => HM.HashMap T.Text e
+hashmap_t = HM.fromList
+    [ (renderEnumText c,c)
+      | c <- [minBound..maxBound]
+      ]
+
+-- | 'B.ByteString' 'HM.HashMap' based on 'renderEnumText' representation
+hashmap_b :: EnumText e => HM.HashMap B.ByteString e
+hashmap_b = HM.fromList
+    [ (TE.encodeUtf8 $ renderEnumText c,c)
+      | c <- [minBound..maxBound]
+      ]
