diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2014, Nikita Volkov
+
+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/api-tests/APITests.hs b/api-tests/APITests.hs
new file mode 100644
--- /dev/null
+++ b/api-tests/APITests.hs
@@ -0,0 +1,51 @@
+{-# OPTIONS_GHC -F -pgmF htfpp #-}
+
+import Prelude
+import Test.Framework
+import qualified Cases
+
+main = htfMain $ htf_thisModulesTests
+
+
+test_spinalizeCamelCase = do
+  assertEqual "abc-def" $ Cases.spinalize "abcDef"
+  assertEqual "abc-def" $ Cases.spinalize "abcDEF"
+  assertEqual "my-html-processor" $ Cases.spinalize "myHTMLProcessor"
+
+test_spinalizeSymbols = do
+  assertEqual "abc-def" $ Cases.spinalize "abc_def"
+  assertEqual "abc-def" $ Cases.spinalize "abc-def"
+  assertEqual "abc-def" $ Cases.spinalize "abc/def"
+  assertEqual "abc-def" $ Cases.spinalize "abc,def"
+  assertEqual "abc-def" $ Cases.spinalize "abc.def"
+  assertEqual "abc-def" $ Cases.spinalize "abc:def"
+
+test_spinalizeMultipleDelimiters = do
+  assertEqual "abc-def" $ Cases.spinalize "abc_-:,/def"
+  assertEqual "abc-def" $ Cases.spinalize "abc /-def"
+    
+test_spinalizeSpaces = do
+  assertEqual "abc-def" $ Cases.spinalize "abc def"
+  assertEqual "abc-def" $ Cases.spinalize "abc\ndef"
+  assertEqual "abc-def" $ Cases.spinalize "abc\rdef"
+  assertEqual "abc-def" $ Cases.spinalize "abc\r\n\rdef"
+  assertEqual "abc-def" $ Cases.spinalize "abc\tdef"
+  assertEqual "abc-def" $ Cases.spinalize "abc  def"
+
+test_spinalizeTrimming = do
+  assertEqual "abc-def" $ Cases.spinalize " abc def "
+  assertEqual "abc-def" $ Cases.spinalize "/abc/def/"
+
+test_spinalizeNumbers = do
+  assertEqual "abc-12" $ Cases.spinalize "abc12"
+
+test_spinalizeUnicode = do
+  assertEqual "ёжик-лижет-мёд" $ Cases.spinalize "Ёжик лижет мёд."
+
+test_snakify = do
+  assertEqual "ёжик_лижет_мёд" $ Cases.snakify "Ёжик лижет мёд."
+
+test_camelize = do
+  assertEqual "abcDef" $ Cases.camelize "abc-def"
+  assertEqual "AbcDef" $ Cases.camelize "Abc-def"
+  assertEqual "parseDbmXml" $ Cases.camelize "parse DBM XML"
diff --git a/benchmark/Benchmark.hs b/benchmark/Benchmark.hs
new file mode 100644
--- /dev/null
+++ b/benchmark/Benchmark.hs
@@ -0,0 +1,13 @@
+
+import Prelude
+import Cases
+import qualified CriterionPlus as C
+import qualified Data.Text as Text
+
+main = do
+  C.benchmark $ do
+
+    C.standoff "" $ do
+      let !text = Text.replicate 100 "Abc 123 / dsf asdf ;lkj. "
+      C.subject "camelize" $ do
+        C.nfIO $ return $ camelize text
diff --git a/cases.cabal b/cases.cabal
new file mode 100644
--- /dev/null
+++ b/cases.cabal
@@ -0,0 +1,214 @@
+name:
+  cases
+version:
+  0.1.0
+synopsis:
+  A converter for spinal, snake and camel case
+description:
+  A parser-based converter library for spinal, snake and camel case.
+category:
+  Text
+homepage:
+  https://github.com/nikita-volkov/cases 
+bug-reports:
+  https://github.com/nikita-volkov/cases/issues 
+author:
+  Nikita Volkov <nikita.y.volkov@mail.ru>
+maintainer:
+  Nikita Volkov <nikita.y.volkov@mail.ru>
+copyright:
+  (c) 2014, Nikita Volkov
+license:
+  MIT
+license-file:
+  LICENSE
+build-type:
+  Simple
+cabal-version:
+  >=1.10
+
+
+source-repository head
+  type:
+    git
+  location:
+    git://github.com/nikita-volkov/cases.git
+
+
+library
+  hs-source-dirs:
+    library
+  other-modules:
+    Cases.Prelude
+  exposed-modules:
+    Cases
+  build-depends:
+    -- data:
+    attoparsec >= 0.10 && < 0.12,
+    text,
+    -- control:
+    mtl,
+    -- debugging:
+    loch-th == 0.2.*,
+    placeholders == 0.1.*,
+    -- general:
+    base >= 4.5 && < 5
+  default-extensions:
+    Arrows
+    BangPatterns
+    ConstraintKinds
+    DataKinds
+    DefaultSignatures
+    DeriveDataTypeable
+    DeriveFunctor
+    DeriveGeneric
+    EmptyDataDecls
+    FlexibleContexts
+    FlexibleInstances
+    FunctionalDependencies
+    GADTs
+    GeneralizedNewtypeDeriving
+    ImpredicativeTypes
+    LambdaCase
+    LiberalTypeSynonyms
+    MultiParamTypeClasses
+    MultiWayIf
+    NoImplicitPrelude
+    NoMonomorphismRestriction
+    OverloadedStrings
+    PatternGuards
+    ParallelListComp
+    QuasiQuotes
+    RankNTypes
+    RecordWildCards
+    ScopedTypeVariables
+    StandaloneDeriving
+    TemplateHaskell
+    TupleSections
+    TypeFamilies
+    TypeOperators
+  default-language:
+    Haskell2010
+
+
+test-suite api-tests
+  type:             
+    exitcode-stdio-1.0
+  hs-source-dirs:   
+    api-tests
+  main-is:          
+    APITests.hs
+  ghc-options:
+    -O2
+    -threaded
+    "-with-rtsopts=-N"
+  build-depends:
+    -- testing:
+    cases,
+    QuickCheck,
+    HUnit,
+    HTF == 0.11.*,
+    -- data:
+    text,
+    -- debugging:
+    loch-th == 0.2.*,
+    placeholders == 0.1.*,
+    -- general:
+    base >= 4.5 && < 5
+  default-extensions:
+    Arrows
+    BangPatterns
+    ConstraintKinds
+    DataKinds
+    DefaultSignatures
+    DeriveDataTypeable
+    DeriveFunctor
+    DeriveGeneric
+    EmptyDataDecls
+    FlexibleContexts
+    FlexibleInstances
+    FunctionalDependencies
+    GADTs
+    GeneralizedNewtypeDeriving
+    ImpredicativeTypes
+    LambdaCase
+    LiberalTypeSynonyms
+    MultiParamTypeClasses
+    MultiWayIf
+    NoImplicitPrelude
+    NoMonomorphismRestriction
+    OverloadedStrings
+    PatternGuards
+    ParallelListComp
+    QuasiQuotes
+    RankNTypes
+    RecordWildCards
+    ScopedTypeVariables
+    StandaloneDeriving
+    TemplateHaskell
+    TupleSections
+    TypeFamilies
+    TypeOperators
+  default-language:
+    Haskell2010
+
+
+benchmark benchmark
+  type:             
+    exitcode-stdio-1.0
+  hs-source-dirs:   
+    benchmark
+  main-is:      
+    Benchmark.hs
+  ghc-options:
+    -O2
+    -threaded
+    "-with-rtsopts=-N"
+  build-depends:
+    cases,
+    mwc-random == 0.13.*,
+    -- benchmarking:
+    criterion-plus == 0.1.*,
+    -- data:
+    text,
+    -- debugging:
+    loch-th == 0.2.*,
+    placeholders == 0.1.*,
+    -- 
+    base >= 4.5 && < 5
+  default-extensions:
+    Arrows
+    BangPatterns
+    ConstraintKinds
+    DataKinds
+    DefaultSignatures
+    DeriveDataTypeable
+    DeriveFunctor
+    DeriveGeneric
+    EmptyDataDecls
+    FlexibleContexts
+    FlexibleInstances
+    FunctionalDependencies
+    GADTs
+    GeneralizedNewtypeDeriving
+    ImpredicativeTypes
+    LambdaCase
+    LiberalTypeSynonyms
+    MultiParamTypeClasses
+    MultiWayIf
+    NoImplicitPrelude
+    NoMonomorphismRestriction
+    OverloadedStrings
+    PatternGuards
+    QuasiQuotes
+    RankNTypes
+    RecordWildCards
+    ScopedTypeVariables
+    StandaloneDeriving
+    TemplateHaskell
+    TupleSections
+    TypeFamilies
+    TypeOperators
+  default-language:
+    Haskell2010
+
diff --git a/library/Cases.hs b/library/Cases.hs
new file mode 100644
--- /dev/null
+++ b/library/Cases.hs
@@ -0,0 +1,181 @@
+module Cases
+(
+  -- * Processor
+  process,
+  -- ** Case Transformers
+  CaseTransformer,
+  lower,
+  upper,
+  title,
+  -- ** Delimiters
+  Delimiter,
+  spinal,
+  snake,
+  camel,
+  -- * Default Processors
+  spinalize,
+  snakify,
+  camelize,
+)
+where
+
+import Cases.Prelude hiding (Word)
+import qualified Data.Attoparsec.Text as A
+import qualified Data.Text as TS
+import qualified Data.Char as C
+
+
+-- * Part
+-------------------------
+
+-- | A parsed info and a text of a part.
+data Part = 
+  Word Case TS.Text |
+  Digits TS.Text
+
+data Case = Title | Upper | Lower
+
+partToText :: Part -> TS.Text
+partToText = \case
+  Word _ t -> t
+  Digits t -> t
+
+
+-- * Parsers
+-------------------------
+
+upperParser :: A.Parser Part
+upperParser = Word Upper <$> TS.pack <$> A.many1 char where
+  char = do
+    c <- A.satisfy C.isUpper
+    ok <- maybe True (not . C.isLower) <$> A.peekChar
+    if ok
+      then return c
+      else empty
+
+lowerParser :: A.Parser Part
+lowerParser = Word Lower <$> (A.takeWhile1 C.isLower)
+
+titleParser :: A.Parser Part
+titleParser = Word Title <$> (TS.cons <$> headChar <*> remainder) where
+  headChar = A.satisfy C.isUpper
+  remainder = A.takeWhile1 C.isLower
+
+digitsParser :: A.Parser Part
+digitsParser = Digits <$> (A.takeWhile1 C.isDigit)
+
+partParser :: A.Parser Part
+partParser = titleParser <|> upperParser <|> lowerParser <|> digitsParser
+
+-- |
+-- A parser, which does in-place processing, using the supplied 'Folder'.
+partsParser :: Monoid r => Folder r -> A.Parser r
+partsParser fold = loop mempty where
+  loop r = 
+    (partParser >>= loop . fold r) <|> 
+    (A.anyChar *> loop r) <|>
+    (A.endOfInput *> pure r)
+
+
+-- * Folders
+-------------------------
+
+type Folder r = r -> Part -> r
+
+type Delimiter = Folder (Maybe TS.Text)
+
+spinal :: Delimiter
+spinal = 
+  (. partToText) . 
+  fmap Just . 
+  maybe id (\l r -> l <> "-" <> r)
+
+snake :: Delimiter
+snake = 
+  (. partToText) . 
+  fmap Just . 
+  maybe id (\l r -> l <> "_" <> r)
+
+camel :: Delimiter
+camel = 
+  fmap Just .
+  maybe partToText (\l r -> l <> partToText (title r))
+
+
+-- * CaseTransformers
+-------------------------
+
+type CaseTransformer = Part -> Part
+
+lower :: CaseTransformer
+lower = \case
+  Word c t -> Word Lower t' where
+    t' = case c of
+      Title -> TS.uncons t |> \case
+        Nothing -> t
+        Just (h, t) -> TS.cons (C.toLower h) t
+      Upper -> TS.toLower t
+      Lower -> t
+  p -> p
+
+upper :: CaseTransformer
+upper = \case
+  Word c t -> Word Upper t' where
+    t' = case c of
+      Title -> TS.uncons t |> \case
+        Nothing -> t
+        Just (h, t) -> TS.cons h (TS.toUpper t)
+      Upper -> t
+      Lower -> TS.toUpper t
+  p -> p
+
+title :: CaseTransformer
+title = \case
+  Word c t -> Word Title t' where
+    t' = case c of
+      Title -> t
+      Upper -> TS.uncons t |> \case
+        Nothing -> t  
+        Just (h, t) -> TS.cons (C.toUpper h) (TS.toLower t)
+      Lower -> TS.uncons t |> \case
+        Nothing -> t
+        Just (h, t) -> TS.cons (C.toUpper h) t
+  p -> p
+
+
+-- * API
+-------------------------
+
+-- |
+-- Extract separate words from an arbitrary text using a smart parser and
+-- produce a new text using case transformation and delimiter functions.
+-- 
+-- Note: to skip case transformation use the 'id' function.
+process :: CaseTransformer -> Delimiter -> TS.Text -> TS.Text
+process tr fo = 
+  fromMaybe "" .
+  either ($bug . ("Parse failure: " <>)) id .
+  A.parseOnly (partsParser $ (. tr) . fo)
+
+-- |
+-- Transform an arbitrary text into a lower spinal case.
+-- 
+-- Same as @('process' 'lower' 'spinal')@.
+spinalize :: TS.Text -> TS.Text
+spinalize = process lower spinal
+
+-- |
+-- Transform an arbitrary text into a lower snake case.
+-- 
+-- Same as @('process' 'lower' 'snake')@.
+snakify :: TS.Text -> TS.Text
+snakify = process lower snake
+
+-- |
+-- Transform an arbitrary text into a camel case, 
+-- while preserving the case of the first character.
+-- 
+-- Same as @('process' 'id' 'camel')@.
+camelize :: TS.Text -> TS.Text
+camelize = process id camel
+
diff --git a/library/Cases/Prelude.hs b/library/Cases/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/library/Cases/Prelude.hs
@@ -0,0 +1,92 @@
+module Cases.Prelude
+( 
+  module Exports,
+
+  traceM,
+  bug,
+  bottom,
+
+  (?:),
+  (|>),
+  (<|),
+  (|$>),
+)
+where
+
+-- base
+-------------------------
+import Prelude as Exports hiding (concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, FilePath, id, (.))
+import Control.Monad as Exports hiding (mapM_, sequence_, forM_, msum, mapM, sequence, forM)
+import Control.Applicative as Exports
+import Control.Arrow as Exports hiding (left, right)
+import Control.Category as Exports
+import Data.Monoid as Exports
+import Data.Foldable as Exports
+import Data.Traversable as Exports hiding (for)
+import Data.Maybe as Exports
+import Data.Either as Exports
+import Data.List as Exports hiding (concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')
+import Data.Tuple as Exports
+import Data.Function as Exports hiding ((.), id)
+import Data.Ord as Exports (Down(..))
+import Data.String as Exports
+import Data.Int as Exports
+import Data.Word as Exports
+import Data.Ratio as Exports
+import Data.Fixed as Exports
+import Data.Ix as Exports
+import Data.Data as Exports
+import Text.Read as Exports (readMaybe, readEither)
+import Control.Exception as Exports hiding (tryJust, try, assert)
+import Control.Concurrent as Exports hiding (yield)
+import System.Mem as Exports
+import System.Mem.StableName as Exports
+import System.Timeout as Exports
+import System.Exit as Exports
+import System.IO.Unsafe as Exports
+import System.IO as Exports (Handle, hClose)
+import System.IO.Error as Exports
+import Unsafe.Coerce as Exports
+import GHC.Generics as Exports (Generic)
+import GHC.IO.Exception as Exports
+import Data.IORef as Exports
+import Data.STRef as Exports
+import Control.Monad.ST as Exports
+import Debug.Trace as Exports hiding (traceM)
+
+-- placeholders
+-------------------------
+import Development.Placeholders as Exports
+
+-- custom
+-------------------------
+import qualified Debug.Trace.LocationTH
+
+traceM :: (Monad m) => String -> m ()
+traceM s = trace s $ return ()
+
+bug = [e| $(Debug.Trace.LocationTH.failure) . (msg <>) |]
+  where
+    msg = "A \"cases\" package bug: " :: String
+
+bottom = [e| $bug "Bottom evaluated" |]
+
+(?:) :: Maybe a -> a -> a
+maybeA ?: b = fromMaybe b maybeA
+{-# INLINE (?:) #-}
+
+(|>) :: a -> (a -> b) -> b
+a |> aToB = aToB a
+{-# INLINE (|>) #-}
+
+(<|) :: (a -> b) -> a -> b
+aToB <| a = aToB a
+{-# INLINE (<|) #-}
+
+-- | 
+-- The following are all the same:
+-- fmap f a == f <$> a == a |> fmap f == a |$> f
+-- 
+-- This operator accomodates the left-to-right operators: >>=, >>>, |>.
+(|$>) = flip fmap
+{-# INLINE (|$>) #-}
