diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Daniel Choi
+
+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/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,106 @@
+{-# LANGUAGE OverloadedStrings #-} 
+module Main where
+
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
+import System.Environment (getArgs)
+import Control.Applicative 
+import Data.List (transpose)
+import Data.Monoid
+import Options.Applicative
+
+data Options = Options {
+    matchMode :: MatchMode 
+  , matchStrings :: [Text]
+  } deriving (Show)
+
+data MatchMode = Series | Alternatives  deriving Show
+
+parseOpts :: Parser Options
+parseOpts = Options 
+    <$> flag Series Alternatives
+          (short 'a' <> long "alternatives" 
+          <> help "Treat match strings as alternatives for alignment, like regex /(a|b|c)/.")
+    <*> ((T.words . T.pack) <$> 
+          (argument str
+             (metavar "MATCH STRINGS" 
+             <> help "The strings to align on, between a pair of single quotes")))
+
+opts = info (helper <*> parseOpts)
+            (fullDesc <> progDesc "Align code text from STDIN on operators."
+                      <> header "align"
+                      <> footer "See https://github.com/danchoi/align for more info.")
+
+main = do
+  Options mode alignStrings <- execParser opts
+  input <- (T.lines . T.pack) <$> getContents
+  let result :: [Text]
+      result =
+        case mode of 
+          Series -> foldl (\lines sep -> align lines sep) input alignStrings 
+          Alternatives -> alignOnAlteratives alignStrings input 
+  T.putStr . T.unlines $ result
+
+-- Aligning in standard Series mode
+align :: [Text] -> Text -> [Text]
+align lines alignString =
+    let lines' :: [[Text]]
+        lines'        = map (splitOn alignString) lines
+        firstCol:rest = transpose lines'
+        firstCol'     = adjustWidth alignString firstCol
+        lines''       = transpose (firstCol':rest)
+    in map T.concat lines''
+
+-- split a line into two segments if it contains the alignstring, 
+-- and one if it doesn't
+splitOn :: Text -> Text -> [Text]
+splitOn alignString input =
+    let (x,y) = T.breakOn alignString input
+    in [z | z <- [x, trim alignString y], z /= mempty]
+    
+-- | Makes column cells in a column the same width
+-- Used for the standard Series mode.
+adjustWidth :: Text -> [Text] -> [Text]
+adjustWidth alignStr xs = 
+    map maybeAdjust xs
+  where maxWidth = maximum $ map (T.length . T.stripEnd) $ xs
+        maybeAdjust :: Text -> Text
+        maybeAdjust cell = T.justifyLeft maxWidth ' ' . T.stripEnd $ cell
+
+alignOnAlteratives :: [Text] -> [Text] -> [Text]
+alignOnAlteratives alts lines =
+    -- each row cell contains (Maybe alternative) that was split on
+    let lines' :: [[Text]]  
+        lines' = map (splitOnAny alts) lines
+        firstCol:rest = transpose lines'
+        firstCol' = adjustWidth' firstCol
+        lines'' = transpose (firstCol':rest)
+    in map T.concat lines''
+
+-- | adjust width of a column of cells
+adjustWidth' :: [Text] -> [Text]
+adjustWidth' xs =
+    map adj xs
+  where maxWidth = maximum $ map (T.length . T.stripEnd) xs
+        adj cell = T.justifyLeft maxWidth ' ' . T.stripEnd $ cell
+
+
+-- | Attempts to split line on any of the alternatives
+-- and returns the segments
+splitOnAny :: [Text] -> Text -> [Text]
+splitOnAny alts line = 
+    let alts' = filter (`T.isInfixOf` line) alts
+    in if null alts'
+       then [line]
+       else let alt = head alts'
+            in splitOn alt line
+
+-- | strips whitespace around text but makes sure that it's left-padded with one space
+trim :: Text -> Text -> Text
+trim alignString s = 
+    -- alignString will be on 1st elem of tuple:
+    let (x,y) = T.breakOnEnd alignString s
+    in T.cons ' ' 
+        $ mconcat [T.strip x, " ", T.strip y]
+
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,123 @@
+# align
+
+Simple unix filter to align text on specified substrings.
+
+`align` can be run from inside Vim or other editors with custom key bindings to
+align lines of code neatly on programming language operators like =>, ::, @=?,
+=, +, etc.
+
+## Usage
+
+`align` has two modes: Series mode and Alternative mode.
+
+### Series mode
+
+Assume you want to align this text:
+
+input.sample:
+```
+    "parseKeyExpr" ~: [ObjectKey "item"] @=? parseKeyExpr "item"
+  , "ngEvalToString" ~: "apple" @=? ngEvalToString testContext1 "item" 
+  , "ngEvalToString2" ~: "apple" @=? ngEvalToString testContext2 "item.name" 
+```
+
+You can do so with this command:
+
+    align '~: @=?' < input.sample
+
+which outputs:
+
+```
+    "parseKeyExpr"    ~: [ObjectKey "item"] @=? parseKeyExpr "item"
+  , "ngEvalToString"  ~: "apple"            @=? ngEvalToString testContext1 "item"
+  , "ngEvalToString2" ~: "apple"            @=? ngEvalToString testContext2 "item.name"
+```
+
+`align` takes an argument list of strings to align the text and performs
+the alignment operation on the text it gets from STDIN.
+
+`align` will only match each alignment string once, so if that string
+occurs multiple times in a line, you need to specify it than many times 
+in the argument.
+
+### Alternative mode
+
+Assume you want to align this text:
+
+input2.sample:
+```
+sendmailCustom :: FilePath        -- ^ sendmail executable path
+  -> [String]     -- ^ sendmail command-line options
+  -> L.ByteString -- ^ mail message as lazy bytestring
+   -> IO ()
+```
+
+Here you want to align `::` and `->` in the same column position. To
+do this use the `-a` flag:
+
+    align -a -- '-> ::' < input2.sample
+
+(FYI you need to add the `--` argument to prevent the `->` string, which starts
+with a dash, from being parsed as a command option.)
+
+This outputs:
+
+```
+sendmailCustom :: FilePath        -- ^ sendmail executable path
+               -> [String]     -- ^ sendmail command-line options
+               -> L.ByteString -- ^ mail message as lazy bytestring
+               -> IO ()
+```
+
+You can also align the comment (beginning with `--`) by using a pipeline:
+
+    cat input2.sample | align -a -- '-> ::' | align -- '--'
+
+which outputs:
+
+```
+sendmailCustom :: FilePath     -- ^ sendmail executable path
+               -> [String]     -- ^ sendmail command-line options
+               -> L.ByteString -- ^ mail message as lazy bytestring
+               -> IO ()
+```
+
+## How to use align in Vim 
+
+To use align from Vim, you can select some text, and then use a Vim
+filter command:
+
+    !align '~: @=?'
+
+For common alignment operations, you can make Vim commands and put them
+in your `.vimrc`, e.g.:
+
+```vimscript
+command! -range AlignHaskellTypeAnnotation :<line1>,<line2>!align '::'
+vnoremap <leader>h :AlignHaskellTypeAnnotation<cr>
+
+command! -range AlignHaskellTest :<line1>,<line2>!align '~: @=?'
+vnoremap <leader>H :AlignHaskellTest<cr>
+
+```
+
+## Installation
+
+You must first have the Haskll platform installed on your system:
+
+* [Haskell platform](https://www.haskell.org/platform)
+
+`git clone` this project to a local directory. Then from that directory, 
+run this command:
+
+```
+cabal install
+```
+
+This will likely install the `align` executable in ~/.cabal/bin, which should be on your PATH.
+
+
+## Author
+
+Daniel Choi <https://github.com/danchoi>
+
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/align-text.cabal b/align-text.cabal
new file mode 100644
--- /dev/null
+++ b/align-text.cabal
@@ -0,0 +1,27 @@
+-- Initial align.cabal generated by cabal init.  For further documentation,
+--  see http://haskell.org/cabal/users-guide/
+
+name:                align-text
+version:             0.1.0.0
+synopsis:            A simple unix filter to align text on specified substrings
+description:         A simple unix filter to align text on specified substrings 
+homepage:            https://github.com/danchoi/align-text
+license:             MIT
+license-file:        LICENSE
+author:              Daniel Choi
+maintainer:          dhchoi@gmail.com
+-- copyright:           
+category:            Text
+build-type:          Simple
+-- extra-source-files:  
+cabal-version:       >=1.10
+
+executable align
+  main-is:             Main.hs
+  -- other-modules:       
+  -- other-extensions:    
+  build-depends:       base >=4.7 && <4.8
+                     , text
+                     , optparse-applicative
+  -- hs-source-dirs:      
+  default-language:    Haskell2010
diff --git a/input.sample b/input.sample
new file mode 100644
--- /dev/null
+++ b/input.sample
@@ -0,0 +1,3 @@
+    "parseKeyExpr" ~: [ObjectKey "item"] @=? parseKeyExpr "item"
+  , "ngEvalToString" ~: "apple" @=?   ngEvalToString testContext1 "item" 
+  , "ngEvalToString2" ~: "apple" @=?   ngEvalToString testContext2 "item.name" 
