diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2018, Metrix.AI
+
+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/demo/Main.hs b/demo/Main.hs
new file mode 100644
--- /dev/null
+++ b/demo/Main.hs
@@ -0,0 +1,29 @@
+module Main where
+
+import Prelude
+import qualified Optima
+import qualified Attoparsec.Data as Attoparsec
+
+
+main = parseOpts >>= print where
+  parseOpts =
+    Optima.params "Demo"
+      (liftA3 (,,)
+        (Optima.param (Just 'a') "arg-a"
+          (Optima.value
+            "Description of A"
+            (Optima.showable True)
+            Optima.unformatted
+            (Optima.explicitlyParsed Attoparsec.bool)))
+        (Optima.param (Just 'b') "arg-b"
+          (Optima.value
+            "Description of B"
+            Optima.defaultless
+            Optima.unformatted
+            (Optima.explicitlyParsed Attoparsec.text)))
+        (Optima.param Nothing "arg-c"
+          (Optima.value
+            ""
+            Optima.defaultless
+            Optima.unformatted
+            (Optima.explicitlyParsed Attoparsec.utf8Bytes))))
diff --git a/library/Optima.hs b/library/Optima.hs
new file mode 100644
--- /dev/null
+++ b/library/Optima.hs
@@ -0,0 +1,229 @@
+module Optima
+(
+  -- * IO
+  params,
+  -- * Params
+  Params,
+  param,
+  -- * Param
+  Param,
+  value,
+  -- * Value
+  Value,
+  explicitlyParsed,
+  implicitlyParsed,
+  -- * Default
+  Default,
+  explicitlyRepresented,
+  showable,
+  defaultless,
+  -- * ValueFormat
+  ValueFormat,
+  enum,
+  unformatted,
+)
+where
+
+import Optima.Prelude
+import qualified Data.Text as Text
+import qualified Data.Attoparsec.Text as Attoparsec
+import qualified Options.Applicative as Optparse
+import qualified Attoparsec.Data as Attoparsec
+import qualified Text.Builder as TextBuilder
+
+
+-- * Types
+-------------------------
+
+{-|
+Parameters product parser.
+Should be used for composition of all application parameters.
+-}
+newtype Params a = Params (Optparse.Parser a)
+
+{-|
+Parameter parser.
+
+Includes the description of the parameter.
+-}
+newtype Param a = Param (Maybe Char -> Text -> Optparse.Parser a)
+
+{-|
+Parameter value parser.
+-}
+newtype Value a = Value (Attoparsec.Parser a)
+
+{-|
+Default value with its textual representation.
+-}
+data Default a = SpecifiedDefault a Text | UnspecifiedDefault
+
+{-|
+Parameter description.
+-}
+data ValueFormat a = EnumValueFormat [TextBuilder.Builder] | UnspecifiedFormat
+
+
+-- * Instances
+-------------------------
+
+deriving instance Functor Params
+deriving instance Applicative Params
+deriving instance Alternative Params
+
+deriving instance Functor Param
+
+deriving instance Functor Value
+deriving instance Applicative Value
+deriving instance Alternative Value
+deriving instance Monad Value
+deriving instance MonadPlus Value
+deriving instance MonadFail Value
+
+deriving instance Functor Default
+
+deriving instance Functor ValueFormat
+
+
+-- * Functions
+-------------------------
+
+-- ** IO
+-------------------------
+
+{-|
+Execute the parameters parser in IO,
+throwing an exception if anything goes wrong.
+-}
+params :: Text {-^ Description of the application -} -> Params a -> IO a
+params description (Params parser) =
+  Optparse.execParser (Optparse.info (Optparse.helper <*> parser) mods)
+  where
+    mods = Optparse.fullDesc <> Optparse.progDesc (Text.unpack description)
+
+
+-- ** Param
+-------------------------
+
+{-|
+Lift a single parameter parser.
+-}
+param :: Maybe Char {-^ Single-char name -} -> Text {-^ Long name -} -> Param a -> Params a
+param shortName longName (Param parser) = Params (parser shortName longName)
+
+
+-- ** Param
+-------------------------
+
+{-|
+Create a single parameter parser from a value parser and meta information.
+-}
+value :: Text {-^ Description. Can be empty -} -> Default a {-^ Default value -} -> ValueFormat a {-^ Value format -} -> Value a -> Param a
+value description def format (Value attoparsecParser) =
+  Param (\ shortName longName -> Optparse.option readM (mods shortName longName))
+  where
+    readM = Optparse.eitherReader (Attoparsec.parseOnly attoparsecParser . Text.pack)
+    mods shortName longName =
+      longParamName longName <>
+      foldMap Optparse.short shortName <>
+      paramHelp description format <>
+      defaultValue def
+
+
+-- ** Value
+-------------------------
+
+{-|
+Lift an Attoparsec parser into value parser.
+-}
+explicitlyParsed :: Attoparsec.Parser a -> Value a
+explicitlyParsed = Value
+
+{-|
+Lift an implicit lenient Attoparsec parser into value parser.
+-}
+implicitlyParsed :: Attoparsec.LenientParser a => Value a
+implicitlyParsed = Value Attoparsec.lenientParser
+
+
+-- ** Default
+-------------------------
+
+{-|
+Provide a default value with explicit textual representation.
+-}
+explicitlyRepresented :: a -> Text -> Default a
+explicitlyRepresented value representation = SpecifiedDefault value representation
+
+{-|
+Provide a default value with textual representation formed using the implicit Show instance.
+-}
+showable :: Show a => a -> Default a
+showable a = SpecifiedDefault a (Text.pack (show a))
+
+{-|
+Provide no default value.
+-}
+defaultless :: Default a
+defaultless = UnspecifiedDefault
+
+
+-- ** Value spec
+-------------------------
+
+{-|
+Derive value format specification from the Enum instance.
+-}
+enum :: (Bounded a, Enum a, Show a) => ValueFormat a
+enum = let
+  values = enumFromTo minBound (asTypeOf maxBound (descriptionToA description))
+  descriptionToA = undefined :: ValueFormat a -> a
+  description = EnumValueFormat (fmap (TextBuilder.string . show) values)
+  in description
+
+{-|
+Avoid specifying the format.
+-}
+unformatted :: ValueFormat a
+unformatted = UnspecifiedFormat
+
+
+-- ** Rendering building
+-------------------------
+
+buildValueFormat :: ValueFormat a -> TextBuilder.Builder
+buildValueFormat = \ case
+  EnumValueFormat values -> "(" <> TextBuilder.intercalate ", " values <> ")"
+  UnspecifiedFormat -> mempty
+
+buildHelp :: Text -> ValueFormat a -> TextBuilder.Builder
+buildHelp description valueFormat =
+  TextBuilder.intercalate (TextBuilder.char ' ')
+    (notNull (TextBuilder.text description) <> notNull (buildValueFormat valueFormat))
+  where
+    notNull :: TextBuilder.Builder -> [TextBuilder.Builder]
+    notNull = validate (not . TextBuilder.null)
+
+
+-- ** Rendering
+-------------------------
+
+renderIfNotEmpty :: TextBuilder.Builder -> Maybe Text
+renderIfNotEmpty = fmap TextBuilder.run . validate (not . TextBuilder.null)
+
+
+-- ** Mods
+-------------------------
+
+paramHelp :: Text -> ValueFormat a -> Optparse.Mod f a
+paramHelp description format =
+  foldMap (Optparse.help . Text.unpack) (renderIfNotEmpty (buildHelp description format))
+
+defaultValue :: Optparse.HasValue f => Default a -> Optparse.Mod f a
+defaultValue = \ case
+  SpecifiedDefault a text -> Optparse.value a <> Optparse.showDefaultWith (const (Text.unpack text))
+  UnspecifiedDefault -> mempty
+
+longParamName :: Optparse.HasName f => Text -> Optparse.Mod f a
+longParamName name =
+  maybe mempty (Optparse.long . Text.unpack) (validate (not . Text.null) name)
diff --git a/library/Optima/Prelude.hs b/library/Optima/Prelude.hs
new file mode 100644
--- /dev/null
+++ b/library/Optima/Prelude.hs
@@ -0,0 +1,80 @@
+module Optima.Prelude
+(
+  module Exports,
+  validate,
+)
+where
+
+-- base
+-------------------------
+import Control.Applicative as Exports
+import Control.Arrow as Exports
+import Control.Category as Exports
+import Control.Concurrent as Exports
+import Control.Exception as Exports
+import Control.Monad as Exports hiding (fail, mapM_, sequence_, forM_, msum, mapM, sequence, forM)
+import Control.Monad.IO.Class as Exports
+import Control.Monad.Fail as Exports
+import Control.Monad.Fix as Exports hiding (fix)
+import Control.Monad.ST as Exports
+import Data.Bits as Exports
+import Data.Bool as Exports
+import Data.Char as Exports
+import Data.Coerce as Exports
+import Data.Complex as Exports
+import Data.Data as Exports
+import Data.Dynamic as Exports
+import Data.Either as Exports
+import Data.Fixed as Exports
+import Data.Foldable as Exports
+import Data.Function as Exports hiding (id, (.))
+import Data.Functor as Exports
+import Data.Functor.Identity as Exports
+import Data.Int as Exports
+import Data.IORef as Exports
+import Data.Ix as Exports
+import Data.List as Exports hiding (sortOn, isSubsequenceOf, uncons, concat, foldr, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, find, maximumBy, minimumBy, mapAccumL, mapAccumR, foldl')
+import Data.Maybe as Exports
+import Data.Monoid as Exports hiding (Last(..), First(..), (<>))
+import Data.Ord as Exports
+import Data.Proxy as Exports
+import Data.Ratio as Exports
+import Data.Semigroup as Exports
+import Data.STRef as Exports
+import Data.String as Exports
+import Data.Traversable as Exports
+import Data.Tuple as Exports
+import Data.Unique as Exports
+import Data.Version as Exports
+import Data.Word as Exports
+import Debug.Trace as Exports
+import Foreign as Exports hiding (void)
+import GHC.Conc as Exports hiding (withMVar, threadWaitWriteSTM, threadWaitWrite, threadWaitReadSTM, threadWaitRead)
+import GHC.Exts as Exports (lazy, inline, sortWith, groupWith)
+import GHC.Generics as Exports (Generic)
+import GHC.IO.Exception as Exports
+import Numeric as Exports
+import Prelude as Exports hiding (fail, concat, foldr, mapM_, sequence_, foldl1, maximum, minimum, product, sum, all, and, any, concatMap, elem, foldl, foldr1, notElem, or, mapM, sequence, id, (.))
+import System.Environment as Exports
+import System.Exit as Exports
+import System.IO as Exports
+import System.IO.Error as Exports
+import System.IO.Unsafe as Exports
+import System.Mem as Exports
+import System.Mem.StableName as Exports
+import System.Timeout as Exports
+import Text.ParserCombinators.ReadP as Exports (ReadP, ReadS, readP_to_S, readS_to_P)
+import Text.ParserCombinators.ReadPrec as Exports (ReadPrec, readPrec_to_P, readP_to_Prec, readPrec_to_S, readS_to_Prec)
+import Text.Printf as Exports (printf, hPrintf)
+import Text.Read as Exports (Read(..), readMaybe, readEither)
+import Unsafe.Coerce as Exports
+
+-- text
+-------------------------
+import Data.Text as Exports (Text)
+
+
+validate :: Alternative m => (a -> Bool) -> a -> m a
+validate predicate value = if predicate value
+  then pure value
+  else empty
diff --git a/optima.cabal b/optima.cabal
new file mode 100644
--- /dev/null
+++ b/optima.cabal
@@ -0,0 +1,42 @@
+name: optima
+version: 0.1
+category: CLI, Parsing, Options
+synopsis: Simple command line interface arguments parser
+homepage: https://github.com/metrix-ai/optima
+bug-reports: https://github.com/metrix-ai/optima/issues
+author: Nikita Volkov <nikita.y.volkov@mail.ru>
+maintainer: Metrix.AI Tech Team <tech@metrix.ai>
+copyright: (c) 2018, Metrix.AI
+license: MIT
+license-file: LICENSE
+build-type: Simple
+cabal-version: >=1.10
+
+library
+  hs-source-dirs: library
+  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language: Haskell2010
+  exposed-modules:
+    Optima
+  other-modules:
+    Optima.Prelude
+  build-depends:
+    attoparsec ==0.13.*,
+    attoparsec-data ==1.*,
+    base >=4.9 && <5,
+    optparse-applicative >=0.14 && <0.15,
+    text ==1.*,
+    text-builder >=0.5.3 && <0.6
+
+test-suite demo
+  type: exitcode-stdio-1.0
+  hs-source-dirs: demo
+  default-extensions: Arrows, BangPatterns, ConstraintKinds, DataKinds, DefaultSignatures, DeriveDataTypeable, DeriveFoldable, DeriveFunctor, DeriveGeneric, DeriveTraversable, EmptyDataDecls, FlexibleContexts, FlexibleInstances, FunctionalDependencies, GADTs, GeneralizedNewtypeDeriving, LambdaCase, LiberalTypeSynonyms, MagicHash, MultiParamTypeClasses, MultiWayIf, NoImplicitPrelude, NoMonomorphismRestriction, OverloadedStrings, PatternGuards, ParallelListComp, QuasiQuotes, RankNTypes, RecordWildCards, ScopedTypeVariables, StandaloneDeriving, TemplateHaskell, TupleSections, TypeFamilies, TypeOperators, UnboxedTuples
+  default-language: Haskell2010
+  ghc-options: -O2 -threaded "-with-rtsopts=-N"
+  ghc-prof-options: -O2 -threaded -fprof-auto "-with-rtsopts=-N -p -s -h -i0.1"
+  main-is: Main.hs
+  build-depends:
+    optima,
+    attoparsec-data ==1.*,
+    rerebase ==1.*
