diff --git a/Data/Stringable.hs b/Data/Stringable.hs
new file mode 100644
--- /dev/null
+++ b/Data/Stringable.hs
@@ -0,0 +1,160 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+module Data.Stringable
+       ( Stringable(..)
+       , CStringable(..) )
+       where
+
+import qualified Data.ByteString as B
+import qualified Data.ByteString.Internal as BI
+import qualified Data.ByteString.Lazy as BL
+import qualified Data.ByteString.Lazy.Internal as BLI
+import qualified Data.ByteString.Unsafe as BU
+import           Data.Either
+import qualified Data.Text as T
+import qualified Data.Text.Encoding as E
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as EL
+import qualified Filesystem.Path.CurrentOS as F
+import           Foreign.C.String
+import           Foreign.ForeignPtr
+import           GHC.Ptr
+import           Prelude hiding (FilePath)
+
+class Stringable a where
+  toString   :: a -> String
+  fromString :: String -> a
+  length     :: a -> Int
+
+  toText   :: a -> T.Text
+  toText   = T.pack . toString
+  fromText :: T.Text -> a
+  fromText = fromString . T.unpack
+
+  toLazyText   :: a -> TL.Text
+  toLazyText   = TL.pack . toString
+  fromLazyText :: TL.Text -> a
+  fromLazyText = fromString . TL.unpack
+
+  toByteString   :: a -> B.ByteString
+  toByteString   = E.encodeUtf8 . toText
+  fromByteString :: B.ByteString -> a
+  fromByteString = fromText . E.decodeUtf8
+
+  toLazyByteString   :: a -> BL.ByteString
+  toLazyByteString   = EL.encodeUtf8 . toLazyText
+  fromLazyByteString :: BL.ByteString -> a
+  fromLazyByteString = fromLazyText . EL.decodeUtf8
+
+  toFilePath   :: a -> F.FilePath
+  toFilePath   = F.fromText . toText
+  fromFilePath :: F.FilePath -> a
+  fromFilePath = fromText . either (error "Error in conversion") id . F.toText
+
+instance Stringable String where
+  toString   = id
+  fromString = id
+  length     = Prelude.length
+
+instance Stringable T.Text where
+  toString       = T.unpack
+  fromString     = T.pack
+  length         = T.length
+  toText         = id
+  fromText       = id
+  toLazyText     = TL.fromStrict
+  fromLazyText   = TL.toStrict
+  toByteString   = E.encodeUtf8
+  fromByteString = E.decodeUtf8
+
+instance Stringable TL.Text where
+  toString           = TL.unpack
+  fromString         = TL.pack
+  length             = T.length . toText
+  toText             = TL.toStrict
+  fromText           = TL.fromStrict
+  toLazyText         = id
+  fromLazyText       = id
+  toLazyByteString   = EL.encodeUtf8
+  fromLazyByteString = EL.decodeUtf8
+
+lazyFromStrictB :: B.ByteString -> BL.ByteString
+lazyFromStrictB = flip BLI.chunk BLI.Empty
+
+lazyToStrictB :: BL.ByteString -> B.ByteString
+lazyToStrictB lb = BI.unsafeCreate len $ go lb
+  where
+    len = BLI.foldlChunks (\l sb -> l + B.length sb) 0 lb
+
+    go  BLI.Empty                   _   = return ()
+    go (BLI.Chunk (BI.PS fp s l) r) ptr =
+        withForeignPtr fp $ \p -> do
+            BI.memcpy ptr (p `plusPtr` s) (fromIntegral l)
+            go r (ptr `plusPtr` l)
+
+instance Stringable B.ByteString where
+  toString           = T.unpack . E.decodeUtf8
+  fromString         = E.encodeUtf8 . T.pack
+  length             = B.length
+  toText             = E.decodeUtf8
+  fromText           = E.encodeUtf8
+  toLazyText         = EL.decodeUtf8 . toLazyByteString
+  fromLazyText       = fromLazyByteString . EL.encodeUtf8
+  toByteString       = id
+  fromByteString     = id
+  toLazyByteString   = lazyFromStrictB
+  fromLazyByteString = lazyToStrictB
+
+instance Stringable BL.ByteString where
+  toString           = TL.unpack . EL.decodeUtf8
+  fromString         = EL.encodeUtf8 . TL.pack
+  length             = B.length . toByteString
+  toText             = E.decodeUtf8 . toByteString
+  fromText           = fromByteString . E.encodeUtf8
+  toLazyText         = EL.decodeUtf8
+  fromLazyText       = EL.encodeUtf8
+  toByteString       = lazyToStrictB
+  fromByteString     = lazyFromStrictB
+  toLazyByteString   = id
+  fromLazyByteString = id
+
+instance Stringable F.FilePath where
+  toString   = toString . either (error "Error in conversion") id . F.toText
+  fromString = fromText . T.pack
+  length     = undefined
+  toText     = either (error "Error in conversion") id . F.toText
+  fromText   = fromText
+
+class Stringable a => CStringable a where
+  withCStringable :: a -> (CString -> IO b) -> IO b
+  withCStringable = withCString . toString
+
+  withCStringLenable :: a -> (CString -> Int -> IO b) -> IO b
+  withCStringLenable str f = withCStringLen (toString str) (uncurry f)
+
+instance CStringable String where
+  withCStringable = withCString
+
+withByteString :: B.ByteString -> (CString -> IO a) -> IO a
+withByteString = BU.unsafeUseAsCString
+
+withByteStringLen :: B.ByteString -> (CString -> Int -> IO a) -> IO a
+withByteStringLen str f = BU.unsafeUseAsCStringLen str (uncurry f)
+
+instance CStringable T.Text where
+  withCStringable    = withCStringable . E.encodeUtf8
+  withCStringLenable = withCStringLenable . E.encodeUtf8
+
+instance CStringable TL.Text where
+  withCStringable    = withCStringable . EL.encodeUtf8
+  withCStringLenable = withCStringLenable . EL.encodeUtf8
+
+instance CStringable B.ByteString where
+  withCStringable    = withByteString
+  withCStringLenable = withByteStringLen
+
+instance CStringable BL.ByteString where
+  withCStringable    = withByteString . B.concat . BL.toChunks
+  withCStringLenable = withByteStringLen . B.concat . BL.toChunks
+
+-- Stringable.hs
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 John Wiegley
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
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/stringable.cabal b/stringable.cabal
new file mode 100644
--- /dev/null
+++ b/stringable.cabal
@@ -0,0 +1,44 @@
+Name:                stringable
+Version:             0.1.0
+Synopsis:            A Stringable type class, in the spirit of Foldable and Traversable
+Description:         'Data.Stringable' provides a type class with a set of functions for
+                     converting to and from the most often used string-linke types in
+                     Haskell.
+License-file:        LICENSE
+License:             MIT
+Author:              John Wiegley
+Maintainer:          johnw@newartisans.com
+Build-Type:          Simple
+Cabal-Version:       >=1.10
+Category:            Data, Text, ByteString
+
+Source-repository head
+  type: git
+  location: git://github.com/jwiegley/stringable.git
+
+-- Test-suite smoke
+--   default-language: Haskell98
+--   type: exitcode-stdio-1.0
+--   main-is: Main.hs
+--   hs-source-dirs: tests
+--   build-depends:
+--       base >=3
+--     , stringable
+--     , process
+--     , system-filepath >= 0.4.7
+--     , system-fileio   >= 0.3.9
+--     , time            >= 1.4
+--     , text            >= 0.11.2
+--     , containers      >= 0.4.2
+--     , bytestring      >= 0.9.2.1
+--     , lens            >= 2.8
+
+Library
+  default-language: Haskell98
+  build-depends:
+      base            >= 3 && < 5
+    , system-filepath >= 0.4.7
+    , text            >= 0.11.2
+    , bytestring      >= 0.9.2.1
+  exposed-modules:
+    Data.Stringable
