diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,8 @@
+# fix-whitespace
+
+Version history.
+
+## 0.0.5 released 2021-03-11
+
+- initial release
+- tested with GHC 8.0.2 - 9.0.1
diff --git a/FixWhitespace.hs b/FixWhitespace.hs
new file mode 100644
--- /dev/null
+++ b/FixWhitespace.hs
@@ -0,0 +1,225 @@
+-- Liang-Ting Chen 2019-10-13:
+-- this program is partially re-written so that the configuration part is
+-- controlled by a configuration file `fix-whitespace.yaml" in the base
+-- directory instead.
+
+import Control.Monad
+
+import Data.Char as Char
+import Data.List.Extra (nubOrd)
+import Data.Text (Text)
+import qualified Data.Text as Text
+import qualified Data.Text.IO as Text  -- Strict IO.
+
+import System.Directory ( getCurrentDirectory, doesFileExist)
+import System.Environment
+import System.Exit
+import System.FilePath
+import System.FilePattern
+import System.FilePattern.Directory
+import System.IO
+import System.Console.GetOpt
+
+import ParseConfig
+
+-- Modes.
+data Mode
+  = Fix    -- ^ Fix whitespace issues.
+  | Check  -- ^ Check if there are any whitespace issues.
+    deriving (Show, Eq)
+
+type Verbose = Bool
+
+data Options = Options
+  { optVerbose :: Verbose
+  -- ^ Display the location of a file being checked or not.
+  , optHelp    :: Bool
+  -- ^ Display the help information.
+  , optMode    :: Mode
+  , optConfig  :: FilePath
+  -- ^ The location to the configuration file.
+  }
+
+defaultOptions = Options
+  { optVerbose = False
+  , optHelp    = False
+  , optMode    = Fix
+  , optConfig  = "fix-whitespace.yaml"
+  }
+
+options :: [OptDescr (Options -> Options)]
+options =
+  [ Option ['h']     ["help"]
+      (NoArg (\opts -> opts { optHelp = True }))
+      "Show this help information."
+  , Option ['v']     ["verbose"]
+      (NoArg (\opts -> opts { optVerbose = True }))
+      "Show files as they are being checked."
+  , Option []        ["config"]
+      (ReqArg (\loc opts -> opts { optConfig = loc }) "CONFIG")
+      "Override the project configuration fix-whitespace.yaml."
+  , Option []        ["check"]
+      (NoArg (\opts -> opts { optMode = Check }))
+      (unlines
+        [ "With --check the program does not change any files,"
+        , "it just checks if any files would have been changed."
+        , "In this case it returns with a non-zero exit code."
+        ])
+  ]
+
+compilerOpts :: String -> IO (Options, [String])
+compilerOpts progName = do
+  argv <- getArgs
+  case getOpt Permute options argv of
+      (o, n, []  ) -> return (foldl (flip id) defaultOptions o, n)
+      (_, _, errs) -> ioError (userError (concat errs ++ "\n" ++ shortUsageHeader progName))
+
+shortUsageHeader, usageHeader, usage :: String -> String
+
+shortUsageHeader progName =
+  "Usage: " ++ progName ++ " [-h|--help] [-v|--verbose] [--check] [--config CONFIG] [FILES]"
+
+usageHeader progName = unlines
+  [ shortUsageHeader progName
+  , ""
+  , "The program does the following"
+  , ""
+  , "* Removes trailing whitespace."
+  , "* Removes trailing lines containing nothing but whitespace."
+  , "* Ensures that the file ends in a newline character."
+  , ""
+  , "for files specified in [FILES] or"
+  , ""
+  , "\t" ++ optConfig defaultOptions
+  , ""
+  , "under the current directory."
+  , ""
+  , "Background: Agda was reported to fail to compile on Windows"
+  , "because a file did not end with a newline character (Agda"
+  , "uses -Werror)."
+  , ""
+  , "Available options:"
+  ]
+
+usage progName = usageInfo (usageHeader progName) options
+
+main :: IO ()
+main = do
+  progName <- getProgName
+  (opts, nonOpts) <- compilerOpts progName
+
+  -- check if the user asks for help
+  when (optHelp opts) $ putStr (usage progName) >> exitSuccess
+
+  -- check if the configuration file exists
+  configExist <- doesFileExist $ optConfig opts
+  unless (configExist || not (null nonOpts)) $ do
+    hPutStr stderr (unlines
+      [ "fix-whitespace.yaml is not found and there are no files specified as arguments."
+      , ""
+      , shortUsageHeader progName
+      ])
+    exitFailure
+
+  let mode    = optMode    opts
+      verbose = optVerbose opts
+      config  = optConfig  opts
+
+  base <- getCurrentDirectory
+
+  files <- if not $ null nonOpts
+    then getDirectoryFiles base nonOpts
+    else do
+      Config incDirs0 excDirs0 incFiles excFiles <- parseConfig config
+      let incDirs = map (++ "/**/") incDirs0
+      let excDirs = map (++ "/**/") excDirs0
+
+      -- File patterns to always include
+      -- when not matching an excluded file pattern
+      let incWhitelistPatterns = concatMap (\d -> map (d ++) incFiles) incDirs
+      -- File patterns to always exclude
+      let excBlacklistPatterns = map ("**/" ++) excFiles
+
+      -- Files to include when not in an excluded directory
+      -- and when not matching an excluded file pattern
+      let incPatterns = map ("**/" ++) incFiles
+      -- Directory and file patterns to exclude
+      let excPatterns = (map (++ "*") excDirs)
+                     ++ (map ("**/" ++) excFiles)
+
+      when verbose $ do
+        putStrLn "Include whitelist:"
+        putStrLn (unlines incWhitelistPatterns)
+
+        putStrLn "Exclude blacklist:"
+        putStrLn (unlines excBlacklistPatterns)
+
+        putStrLn "Include:"
+        putStrLn (unlines incPatterns)
+
+        putStrLn "Exclude:"
+        putStrLn (unlines excPatterns)
+
+      files0 <- getDirectoryFilesIgnore base incWhitelistPatterns excBlacklistPatterns
+      files1 <- getDirectoryFilesIgnore base incPatterns excPatterns
+      return (nubOrd (files0 ++ files1))
+
+  changes <- mapM (fix mode verbose) files
+
+  when (or changes && mode == Check) exitFailure
+
+fix :: Mode -> Verbose -> FilePath -> IO Bool
+fix mode verbose f = do
+
+  new <- withFile f ReadMode $ \h -> do
+    hSetEncoding h utf8
+    s <- Text.hGetContents h
+    let s' = transform s
+    return $ if s' == s then Nothing else Just s'
+  case new of
+    Nothing -> do
+      when verbose (putStrLn $ "[ Checked ] " ++ f)
+      return False
+    Just s  -> do
+      hPutStrLn stderr $
+        "[ Violation " ++
+        (if mode == Fix then "fixed" else "detected") ++
+        " ] " ++ f
+      when (mode == Fix) $
+        withFile f WriteMode $ \h -> do
+          hSetEncoding h utf8
+          Text.hPutStr h s
+      return True
+
+-- | Transforms the contents of a file.
+
+transform :: Text -> Text
+transform =
+  Text.unlines .
+  removeFinalEmptyLinesExceptOne .
+  map (removeTrailingWhitespace .  convertTabs) .
+  Text.lines
+  where
+  removeFinalEmptyLinesExceptOne =
+    reverse . dropWhile1 Text.null . reverse
+
+  removeTrailingWhitespace =
+    Text.dropWhileEnd ((`elem` [Space,Format]) . generalCategory)
+
+  convertTabs =
+    Text.pack . reverse . fst . foldl convertOne ([], 0) . Text.unpack
+
+  convertOne (a, p) '\t' = (addSpaces n a, p + n)
+                           where
+                             n = 8 - p `mod` 8
+  convertOne (a, p) c = (c:a, p+1)
+
+  addSpaces 0 x = x
+  addSpaces n x = addSpaces (n-1) (' ':x)
+
+-- | 'dropWhile' except keep the first of the dropped elements
+dropWhile1 :: (a -> Bool) -> [a] -> [a]
+dropWhile1 _ [] = []
+dropWhile1 p (x:xs)
+  | p x       = x : dropWhile p xs
+  | otherwise = x : xs
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2005-2021 remains with the authors.
+
+fix-whitespace was originally written by Nils Anders Danielsson as
+part of Agda 2 with contributions from Ulf Norell,
+Andrés Sicard-Ramírez, Andreas Abel, Philipp Hausmann, Jesper Cockx,
+Vlad Semenov, and Liang-Ting Chen.
+
+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/ParseConfig.hs b/ParseConfig.hs
new file mode 100644
--- /dev/null
+++ b/ParseConfig.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module ParseConfig
+  ( Config(..)
+  , parseConfig
+  ) where
+
+import Data.Maybe (fromMaybe)
+import qualified Data.Yaml as Y
+import Data.Yaml (FromJSON(..), (.:?))
+
+data RawConfig = RawConfig
+  { __included_dirs  :: Maybe [FilePath]
+  , __excluded_dirs  :: Maybe [FilePath]
+  , __included_files :: Maybe [FilePath]
+  , __excluded_files :: Maybe [FilePath]
+  } deriving Show
+
+data Config = Config
+  { included_dirs  :: [FilePath]
+  , excluded_dirs  :: [FilePath]
+  , included_files :: [FilePath]
+  , excluded_files :: [FilePath]
+  } deriving Show
+
+instance FromJSON RawConfig where
+  parseJSON (Y.Object v) =
+    RawConfig <$>
+    v .:? "included-dirs"  <*>
+    v .:? "excluded-dirs"  <*>
+    v .:? "included-files" <*>
+    v .:? "excluded-files"
+  parseJSON _ = fail "Expected Object for Config value"
+
+parseRawConfig :: FilePath -> IO (Either Y.ParseException RawConfig)
+parseRawConfig = Y.decodeFileEither
+
+parseConfig :: FilePath -> IO Config
+parseConfig fp = do
+  result <- Y.decodeFileEither fp
+  case result of
+    Left _    -> return defaultConfig
+    Right cfg -> return $ defaults cfg
+  where
+    defaults (RawConfig _incDirs _excDirs _incFiles _excFiles) =
+      let incDirs  = fromMaybe [] _incDirs
+          excDirs  = fromMaybe [] _excDirs
+          incFiles = fromMaybe [] _incFiles
+          excFiles = fromMaybe [] _excFiles
+      in Config incDirs excDirs incFiles excFiles
+
+defaultConfig :: Config
+defaultConfig = Config [] [] [] []
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+fix-whitespace: Fixes whitespace issues
+=======================================
+
+[![Hackage version](https://img.shields.io/hackage/v/fix-whitespace.svg?label=Hackage)](http://hackage.haskell.org/package/fix-whitespace)
+[![Hackage CI](https://matrix.hackage.haskell.org/api/v2/packages/fix-whitespace/badge)](https://matrix.hackage.haskell.org/package/fix-whitespace)
+[![fix-whitespace on Stackage Nightly](https://stackage.org/package/fix-whitespace/badge/nightly)](https://stackage.org/nightly/package/fix-whitespace)
+[![Stackage LTS version](https://www.stackage.org/package/fix-whitespace/badge/lts?label=Stackage)](https://www.stackage.org/package/fix-whitespace)
+[![Build status](https://github.com/agda/fix-whitespace/workflows/Build%20by%20Stack/badge.svg)](https://github.com/agda/fix-whitespace/actions)
+
+
+This tool can keep your project and repository clean of trailing
+whitespace and missing terminal newline.
+
+Usage: `fix-whitespace [-h|--help] [-v|--verbose] [--check] [--config CONFIG] [FILES]`
+
+The program does the following to files specified in `FILES` or in the
+configuration file `fix-whitespace.yaml` under the current directory
+(and its subdirectories):
+
+  * Remove trailing whitespace.
+  * Remove trailing lines containing nothing but whitespace.
+  * Ensure that the file ends in a newline character.
+
+Available options:
+
+*  `-h  --help`
+
+   Show this help information.
+
+*  `-v  --verbose`
+
+   Show files as they are being checked.
+
+*  `--config=CONFIG`
+
+   Override the project configuration `fix-whitespace.yaml`.
+
+*  `--check`
+
+   With `--check` the program does not change any files,
+   it just checks if any files would have been changed.
+   In this case it returns with a non-zero exit code.
+
+For an example configuration file see [the one of Agda](https://github.com/agda/agda/blob/f9a181685397517b5d14943ca88a1c0acacc2075/fix-whitespace.yaml).
diff --git a/fix-whitespace.cabal b/fix-whitespace.cabal
new file mode 100644
--- /dev/null
+++ b/fix-whitespace.cabal
@@ -0,0 +1,50 @@
+name:            fix-whitespace
+version:         0.0.5
+cabal-version:   >= 1.10
+build-type:      Simple
+description:     Removes trailing whitespace, lines containing only whitespace and ensure that every file ends in a newline character.
+license:         OtherLicense
+license-file:    LICENSE
+author:          fix-whitespace was originally written by Nils Anders Danielsson as part of Agda 2 with contributions from Ulf Norell, Andrés Sicard-Ramírez, Andreas Abel, Philipp Hausmann, Jesper Cockx, Vlad Semenov, and Liang-Ting Chen.
+homepage:        https://github.com/agda/fix-whitespace
+bug-reports:     https://github.com/agda/fix-whitespace/issues
+maintainer:      Liang-Ting Chen <liang.ting.chen.tw@gmail.com>
+Category:        Text
+Synopsis:        Fixes whitespace issues.
+tested-with:     GHC == 8.0.2
+                 GHC == 8.2.2
+                 GHC == 8.4.4
+                 GHC == 8.6.5
+                 GHC == 8.8.4
+                 GHC == 8.10.3
+                 GHC == 8.10.4
+                 GHC == 9.0.1
+
+extra-source-files:
+  CHANGELOG.md
+  README.md
+
+source-repository head
+  type: git
+  location: https://github.com/agda/fix-whitespace.git
+
+executable fix-whitespace
+  hs-source-dirs:   .
+  main-is:          FixWhitespace.hs
+  other-modules:    ParseConfig
+  default-language: Haskell2010
+
+  build-depends:  base         >= 4.9.0.0  &&  < 4.16
+                , directory    >= 1.2.6.2  &&  < 1.4
+                , extra        >= 1.1      &&  < 2.0
+                , filepath     >= 1.4.1.0  &&  < 1.5
+                , filepattern  >= 0.1.2    &&  < 0.1.3
+                , text         >= 1.2.3.0  &&  < 1.3
+                , yaml         >= 0.8.4    &&  < 0.12
+
+  -- ASR (2018-10-16).
+  -- text-1.2.3.0 required for supporting GHC 8.4.1, 8.4.2 and
+  -- 8.4.3. See Issue #3277.
+  -- The other GHC versions can restrict to >= 1.2.3.1.
+  if impl(ghc < 8.4.1) || impl(ghc > 8.4.3)
+    build-depends: text >= 1.2.3.1
