diff --git a/HsImport.hs b/HsImport.hs
new file mode 100644
--- /dev/null
+++ b/HsImport.hs
@@ -0,0 +1,11 @@
+
+module HsImport
+   ( HsImportArgs
+   , hsImportArgs
+   , module HsImport.ImportSpec
+   , module HsImport.Main
+   ) where
+
+import HsImport.Args
+import HsImport.ImportSpec
+import HsImport.Main
diff --git a/HsImport/Args.hs b/HsImport/Args.hs
new file mode 100644
--- /dev/null
+++ b/HsImport/Args.hs
@@ -0,0 +1,33 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+
+module HsImport.Args 
+   ( HsImportArgs(..)
+   , hsImportArgs
+   ) where
+
+import System.Console.CmdArgs
+
+
+data HsImportArgs = HsImportArgs 
+   { moduleName    :: String
+   , symbolName    :: String
+   , inputSrcFile  :: FilePath
+   , outputSrcFile :: FilePath
+   } deriving (Data, Typeable, Show, Eq)
+
+
+hsImportArgs :: IO HsImportArgs
+hsImportArgs = cmdArgs $ HsImportArgs 
+   { moduleName    = def &= help "The module to import"
+   , symbolName    = def &= help "The symbol to import, if empty, the entire module is imported"
+   , outputSrcFile = def &= help "Save modified source file to file, if empty, the source file is modified inplace" &= typFile
+   , inputSrcFile  = def &= args &= typ "SOURCEFILE"
+   }
+   &= program "hsimport"
+   &= summary summaryInfo
+   &= help "A command line program for extending the import list of a Haskell source file."
+   &= helpArg [explicit, name "help", name "h"]
+   &= versionArg [explicit, name "version", name "v", summary versionInfo]
+   where
+      versionInfo = "hsimport version 0.1"
+      summaryInfo = ""
diff --git a/HsImport/ImportSpec.hs b/HsImport/ImportSpec.hs
new file mode 100644
--- /dev/null
+++ b/HsImport/ImportSpec.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE TemplateHaskell, PatternGuards #-}
+
+module HsImport.ImportSpec
+   ( ImportSpec(..)
+   , sourceFile
+   , parsedSrcFile
+   , moduleToImport
+   , symbolToImport
+   , saveToFile
+   , hsImportSpec
+   ) where
+
+import Control.Lens
+import qualified Language.Haskell.Exts as HS
+import qualified HsImport.Args as Args
+import HsImport.Args (HsImportArgs)
+
+data ImportSpec = ImportSpec 
+   { _sourceFile     :: FilePath
+   , _parsedSrcFile  :: HS.Module
+   , _moduleToImport :: String
+   , _symbolToImport :: Maybe String
+   , _saveToFile     :: Maybe FilePath
+   } deriving (Show)  
+
+makeLenses ''ImportSpec
+
+
+type Error = String
+hsImportSpec :: HsImportArgs -> IO (Either Error ImportSpec)
+hsImportSpec args
+   | Just error <- checkArgs args = return $ Left error
+   | otherwise = do
+      result <- HS.parseFile $ Args.inputSrcFile args
+      case result of
+           HS.ParseOk modul -> return $ Right $
+              ImportSpec (Args.inputSrcFile args) modul
+                         (Args.moduleName args) symbolName saveToFile
+
+           HS.ParseFailed _ error -> return $ Left error
+
+   where
+      symbolName =
+         case Args.symbolName args of
+              ""  -> Nothing
+              sym -> Just sym
+
+      saveToFile =
+         case Args.outputSrcFile args of
+              "" -> Nothing
+              fp -> Just fp
+
+      checkArgs args
+         | null . Args.inputSrcFile $ args = Just "Missing source file!"
+         | null . Args.moduleName $ args   = Just "Missing module name!"
+         | otherwise                       = Nothing
diff --git a/HsImport/Main.hs b/HsImport/Main.hs
new file mode 100644
--- /dev/null
+++ b/HsImport/Main.hs
@@ -0,0 +1,46 @@
+{-# Language PatternGuards #-}
+
+module HsImport.Main
+   ( hsImport
+   ) where
+
+import Control.Lens
+import System.Directory (copyFile)
+import qualified Data.Text as T
+import qualified Data.Text.IO as TIO
+import qualified Language.Haskell.Exts as HS
+import HsImport.ImportChange
+import HsImport.ImportSpec
+
+
+hsImport :: ImportSpec -> IO ()
+hsImport spec =
+   case importChange (spec ^. moduleToImport) (spec ^. symbolToImport) (spec ^. parsedSrcFile) of
+        ReplaceImport importDecl -> do
+           let numDrops = HS.srcLine . HS.importLoc $ importDecl
+               numTakes = max 0 (numDrops - 1)
+
+           modifyImports importDecl (spec ^. sourceFile) (outputFile spec) numTakes numDrops
+
+        AddImport importDecl -> do
+           let numTakes = HS.srcLine . HS.importLoc $ importDecl
+               numDrops = numTakes
+
+           modifyImports importDecl (spec ^. sourceFile) (outputFile spec) numTakes numDrops
+
+        NoImportChange
+           | Just saveTo <- spec ^. saveToFile -> copyFile (spec ^. sourceFile) saveTo
+           | otherwise                         -> return ()
+   where
+      modifyImports importDecl inputFile outputFile numTakes numDrops = do
+         file <- TIO.readFile inputFile
+         let importLine = HS.prettyPrint importDecl
+             lines_     = lines . T.unpack $ file
+             lines_'    = take numTakes lines_ ++ [importLine] ++ drop numDrops lines_
+             file'      = T.pack . unlines $ lines_'
+
+         TIO.writeFile outputFile file'
+
+      outputFile spec
+         | Just file <- spec ^. saveToFile = file
+         | otherwise                       = spec ^. sourceFile
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
+Copyright (c) 2011, Daniel Trstenjak
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * Neither the name of the <organization> nor the
+      names of its contributors may be used to endorse or promote products
+      derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL DANIEL TRSTENJAK BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Main.hs b/Main.hs
new file mode 100644
--- /dev/null
+++ b/Main.hs
@@ -0,0 +1,13 @@
+
+module Main where
+
+import System.Exit (exitFailure, exitSuccess)
+import HsImport
+
+main :: IO ()
+main = do
+   args      <- hsImportArgs
+   maybeSpec <- hsImportSpec args
+   case maybeSpec of
+        Left  error -> putStrLn ("hsimport: " ++ error) >> exitFailure
+        Right spec  -> hsImport spec                    >> exitSuccess
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/hsimport.cabal b/hsimport.cabal
new file mode 100644
--- /dev/null
+++ b/hsimport.cabal
@@ -0,0 +1,57 @@
+Name:          hsimport
+Version:       0.1
+License:       BSD3
+License-file:  LICENSE
+Author:        Daniel Trstenjak
+Maintainer:    daniel.trstenjak@gmail.com
+Build-Type:    Simple
+Category:      Utils
+Cabal-Version: >=1.6
+Synopsis:      A command line program for extending the import list of a Haskell source file.
+Description:   A command line program for extending the import list of a Haskell source file.
+
+source-repository head
+  type:     git
+  location: https://github.com/dan-t/hsimport
+
+Executable hsimport
+  Main-is:       Main.hs
+  other-modules: HsImport
+                 HsImport.Args
+                 HsImport.ImportSpec
+                 HsImport.Main
+  ghc-options:   -W -funbox-strict-fields
+  Build-Depends: base >= 3 && < 5,
+                 cmdargs >= 0.10.5 && < 0.11,
+                 haskell-src-exts >= 1.14.0 && < 1.15,
+                 lens >= 3.9.2 && < 4.0,
+                 mtl >= 2.1.2 && < 2.2,
+                 text >= 0.11.3.1 && < 0.12,
+                 split >= 0.2.2 && < 0.3,
+                 attoparsec >= 0.10.4.0 && < 0.11,
+                 directory >= 1.2.0.1 && < 1.3
+
+library
+  ghc-options:   -W -funbox-strict-fields
+  exposed-modules: HsImport
+                   HsImport.Args
+                   HsImport.ImportSpec
+                   HsImport.Main
+  Build-Depends: base >= 3 && < 5,
+                 cmdargs >= 0.10.5 && < 0.11,
+                 haskell-src-exts >= 1.14.0 && < 1.15,
+                 lens >= 3.9.2 && < 4.0,
+                 mtl >= 2.1.2 && < 2.2,
+                 text >= 0.11.3.1 && < 0.12,
+                 split >= 0.2.2 && < 0.3,
+                 attoparsec >= 0.10.4.0 && < 0.11,
+                 directory >= 1.2.0.1 && < 1.3
+
+Executable hsimport-tests
+  Main-is:       tests/Main.hs
+  ghc-options:   -W -funbox-strict-fields
+  Build-Depends: base >= 3 && < 5,
+                 tasty >= 0.6 && < 0.7,
+                 tasty-golden >= 2.2.0.1 && < 2.3,
+                 filepath >= 1.3.0.1 && < 1.4,
+                 process >= 1.1.0.2 && < 1.2
diff --git a/tests/Main.hs b/tests/Main.hs
new file mode 100644
--- /dev/null
+++ b/tests/Main.hs
@@ -0,0 +1,65 @@
+
+module Main where
+
+import Test.Tasty
+import Test.Tasty.Golden
+import System.Process
+import System.FilePath
+import Data.List (intercalate)
+
+main = defaultMain tests
+
+tests :: TestTree
+tests = testGroup "Tests" [moduleTests, symbolTests]
+
+moduleTests :: TestTree
+moduleTests = testGroup "Module Tests"
+   [ hsImportTest "ModuleTest1" "Foo.Bar" ""
+   , hsImportTest "ModuleTest2" "Foo.Bar.Blub" ""
+   , hsImportTest "ModuleTest3" "Control.Monad" ""
+   , hsImportTest "ModuleTest4" "Ugah.Argh2" ""
+   , hsImportTest "ModuleTest5" "Control.Monad" ""
+   , hsImportTest "ModuleTest6" "Control.Monad" ""
+   , hsImportTest "ModuleTest7" "Control.Monad" ""
+   , hsImportTest "ModuleTest8" "Control.Monad" ""
+   , hsImportTest "ModuleTest9" "Control.Monad" ""
+   ]
+
+
+symbolTests :: TestTree
+symbolTests = testGroup "Symbol Tests"
+   [ hsImportTest "SymbolTest1" "Foo.Bar" "foo"
+   , hsImportTest "SymbolTest2" "Foo.Bar.Blub" "foo"
+   , hsImportTest "SymbolTest3" "Control.Monad" "when"
+   , hsImportTest "SymbolTest4" "Ugah.Argh2" "argh"
+   , hsImportTest "SymbolTest5" "Control.Monad" "when"
+   , hsImportTest "SymbolTest6" "Control.Monad" "unless"
+   , hsImportTest "SymbolTest7" "Control.Monad" "unless"
+   , hsImportTest "SymbolTest8" "Control.Monad" "unless"
+   , hsImportTest "SymbolTest9" "Control.Monad" "unless"
+   , hsImportTest "SymbolTest10" "Control.Applicative" "<$>"
+   , hsImportTest "SymbolTest11" "Control.Applicative" "<$>"
+   , hsImportTest "SymbolTest12" "Control.Applicative" "<$>"
+   , hsImportTest "SymbolTest13" "Control.Applicative" "<*"
+   , hsImportTest "SymbolTest14" "Control.Applicative" "<*"
+   , hsImportTest "SymbolTest15" "Control.Applicative" "*>"
+   ]
+
+
+hsImportTest :: String -> String -> String -> TestTree
+hsImportTest testName moduleName symbolName =
+   goldenVsFile testName goldenFile outputFile command
+   where
+      command = do
+        handle <- runCommand $ "hsimport " ++ params
+        waitForProcess handle
+        return ()
+
+      params      = intercalate " " [moduleParam, symbolParam, outputParam, inputFile]
+      moduleParam = "-m '" ++ moduleName ++ "'"
+      symbolParam = if null symbolName then "" else "-s '" ++ symbolName ++ "'"
+      outputParam = "-o '" ++ outputFile ++ "'"
+
+      goldenFile = "tests" </> "goldenFiles" </> testName <.> "hs"
+      outputFile = "tests" </> "outputFiles" </> testName <.> "hs"
+      inputFile  = "tests" </> "inputFiles"  </> testName <.> "hs"
