diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Tobias Dammers
+
+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/Text/Casing.hs b/Text/Casing.hs
new file mode 100644
--- /dev/null
+++ b/Text/Casing.hs
@@ -0,0 +1,119 @@
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+-- | Conversions between several common identifier casing conventions:
+--
+-- - @PascalCase@ - no spacing between words, first letter in word is
+-- uppercase, all others are lowercase.
+-- - @camelCase@ - like @PascalCase@, but the very first letter is lowercase.
+-- - @kebab-case@ - everything lowercase, dash delimits words.
+-- - @snake_Case@ - underscores delimit words, case is unrestricted.
+-- - @quiet_snake_case@ - underscores delimit words, everything lowercase.
+-- - @SCREAMING_SNAKE_CASE@ - underscores delimit words, everything uppercase.
+module Text.Casing
+(
+-- * Types
+Identifier
+-- * Parsing
+, fromHumps
+, fromKebab
+, fromSnake
+, fromAny
+-- * Generating
+, toCamel, toPascal, toSnake, toQuietSnake, toScreamingSnake, toKebab
+-- * Shorthand functions
+, pascal, camel, snake, quietSnake, screamingSnake, kebab
+-- * Miscellaneous
+, dropPrefix
+)
+where
+
+import Data.Char
+import Data.List (intersperse)
+import Data.List.Split (wordsBy)
+import Control.Applicative
+
+-- | An opaque type that represents a parsed identifier.
+newtype Identifier a = Identifier { unIdentifier :: [a] }
+    deriving (Monad, Functor, Applicative, Show)
+
+wordCase :: String -> String
+wordCase "" = ""
+wordCase (x:xs) = toUpper x : map toLower xs
+
+-- | Convert from "humped" casing (@camelCase@ or @PascalCase@)
+fromHumps :: String -> Identifier String
+fromHumps = Identifier . go
+    where
+        go "" = [""]
+        go (x:[]) = [x:[]]
+        go (x:y:xs)
+            | (not $ isUpper x) && (isUpper y) =
+                let (z:zs) = go (y:xs)
+                in [x]:(z:zs)
+            | otherwise =
+                let (z:zs) = go (y:xs)
+                in (x:z):zs
+
+-- | Convert from @kebab-cased-identifiers@
+fromKebab :: String -> Identifier String
+fromKebab = Identifier . wordsBy (== '-')
+
+-- | Convert from @snake_cased@ (either flavor)
+fromSnake :: String -> Identifier String
+fromSnake = Identifier . wordsBy (== '_')
+
+-- | Convert from anything, including mixed casing.
+fromAny :: String -> Identifier String
+fromAny str = fromHumps str >>= fromKebab >>= fromSnake
+
+-- | To @PascalCase@
+toPascal :: Identifier String -> String
+toPascal = concat . map wordCase . unIdentifier
+
+-- | To @camelCase@
+toCamel :: Identifier String -> String
+toCamel (Identifier (x:xs)) = concat $ map toLower x:map wordCase xs
+
+-- | To @kebab-case@
+toKebab :: Identifier String -> String
+toKebab = concat . intersperse "-" . map (map toLower) . unIdentifier
+
+-- | To @snake_Case@
+toSnake :: Identifier String -> String
+toSnake = concat . intersperse "_" . unIdentifier
+
+-- | To @quiet_snake_case@
+toQuietSnake :: Identifier String -> String
+toQuietSnake = map toLower . toSnake
+
+-- | To @SCREAMING_SNAKE_CASE@
+toScreamingSnake :: Identifier String -> String
+toScreamingSnake = map toUpper . toSnake
+
+-- | Directly convert to @PascalCase@ through 'fromAny'
+pascal :: String -> String
+pascal = toPascal . fromAny
+
+-- | Directly convert to @camelCase@ through 'fromAny'
+camel :: String -> String
+camel = toCamel . fromAny
+
+-- | Directly convert to @snake_Case@ through 'fromAny'
+snake :: String -> String
+snake = toSnake . fromAny
+
+-- | Directly convert to @quiet_snake_case@ through 'fromAny'
+quietSnake :: String -> String
+quietSnake = toQuietSnake . fromAny
+
+-- | Directly convert to @SCREAMING_SNAKE_CASE@ through 'fromAny'
+screamingSnake :: String -> String
+screamingSnake = toScreamingSnake . fromAny
+
+-- | Directly convert to @kebab-case@ through 'fromAny'
+kebab :: String -> String
+kebab = toKebab . fromAny
+
+-- | Drop the first word from a parsed identifier. Typical usage is between
+-- parsing and writing, e.g.: @toKebab . dropPrefix . fromAny $ "strHelloWorld" == "hello-world"@
+dropPrefix :: Identifier String -> Identifier String
+dropPrefix = Identifier . drop 1 . unIdentifier
diff --git a/casing.cabal b/casing.cabal
new file mode 100644
--- /dev/null
+++ b/casing.cabal
@@ -0,0 +1,26 @@
+-- Initial casing.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                casing
+version:             0.1.0.0
+synopsis:            Convert between various source code casing conventions
+description:         Converts between camelCase, PascalCase, kebab-case, and
+                     three flavors of snake_case.
+license:             MIT
+license-file:        LICENSE
+author:              Tobias Dammers
+maintainer:          tdammers@gmail.com
+-- copyright:           
+category:            Text
+build-type:          Simple
+-- extra-source-files:  
+cabal-version:       >=1.10
+
+library
+  exposed-modules:     Text.Casing
+  -- other-modules:       
+  -- other-extensions:    
+  build-depends:       base >=4.7 && <5
+               ,       split
+  -- hs-source-dirs:      
+  default-language:    Haskell2010
