fix-whitespace (empty) → 0.0.5
raw patch · 6 files changed
+405/−0 lines, 6 filesdep +basedep +directorydep +extra
Dependencies added: base, directory, extra, filepath, filepattern, text, yaml
Files
- CHANGELOG.md +8/−0
- FixWhitespace.hs +225/−0
- LICENSE +25/−0
- ParseConfig.hs +53/−0
- README.md +44/−0
- fix-whitespace.cabal +50/−0
+ CHANGELOG.md view
@@ -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
+ FixWhitespace.hs view
@@ -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
+ LICENSE view
@@ -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.
+ ParseConfig.hs view
@@ -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 [] [] [] []
+ README.md view
@@ -0,0 +1,44 @@+fix-whitespace: Fixes whitespace issues+=======================================++[](http://hackage.haskell.org/package/fix-whitespace)+[](https://matrix.hackage.haskell.org/package/fix-whitespace)+[](https://stackage.org/nightly/package/fix-whitespace)+[](https://www.stackage.org/package/fix-whitespace)+[](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).
+ fix-whitespace.cabal view
@@ -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