Skip to content

Add --consecutive option #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions FixWhitespace.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import System.IO ( IOMode(WriteMode), hPutStr, hPut
import Text.Read ( readMaybe )

import Data.Text.FixWhitespace ( CheckResult(CheckOK, CheckViolation, CheckIOError), checkFile, displayLineError
, TabSize, Verbose, defaultTabSize )
, TabSize, ConsecutiveEmptyLines, Verbose, defaultTabSize, defaultConsecutiveEmptyLines )

import ParseConfig ( Config(Config), parseConfig )
import qualified Paths_fix_whitespace as PFW ( version )
Expand Down Expand Up @@ -49,6 +49,8 @@ data Options = Options
-- ^ The location to the configuration file.
, optTabSize :: String
-- ^ The number of spaces to expand a tab character to. @"0"@ for keeping tabs.
, optConsEL :: String
-- ^ The number of consecutive empty lines allowed. Unlimited if 0.
}

defaultOptions :: Options
Expand All @@ -59,6 +61,7 @@ defaultOptions = Options
, optMode = Fix
, optConfig = defaultConfigFile
, optTabSize = show defaultTabSize
, optConsEL = show defaultConsecutiveEmptyLines
}

options :: [OptDescr (Options -> Options)]
Expand All @@ -81,6 +84,12 @@ options =
[ "Expand tab characters to TABSIZE (default: " ++ show defaultTabSize ++ ") many spaces."
, "Keep tabs if 0 is given as TABSIZE."
])
, Option ['n'] ["consecutive"]
(ReqArg (\ns opts -> opts { optConsEL = ns }) "LINES")
(unlines
[ "Maximum consecutive empty lines (default: " ++ show defaultConsecutiveEmptyLines ++ ")."
, "Unlimited if 0 is given as LINES."
])
, Option [] ["config"]
(ReqArg (\loc opts -> opts { optConfig = loc }) "CONFIG")
(concat ["Override the project configuration ", defaultConfigFile, "."])
Expand All @@ -105,7 +114,7 @@ shortUsageHeader :: String -> String
shortUsageHeader progName = unwords
[ "Usage:"
, progName
, "[-h|--help] [-v|--verbose] [--check] [--config CONFIG] [-t|--tab TABSIZE] [FILES]"
, "[-h|--help] [-v|--verbose] [--check] [--config CONFIG] [-t|--tab TABSIZE] [-n|--consecutive LINES] [FILES]"
]

usageHeader :: String -> String
Expand Down Expand Up @@ -159,6 +168,9 @@ main = do
tabSize <- maybe (die "Error: Illegal TABSIZE, must be an integer.") return $
readMaybe $ optTabSize opts

consecutiveLines <- maybe (die "Error: Illegal LINES, must be an integer.") return $
readMaybe $ optConsEL opts

base <- getCurrentDirectory

files <- if not $ null nonOpts
Expand Down Expand Up @@ -198,13 +210,13 @@ main = do
files1 <- getDirectoryFilesIgnore base incPatterns excPatterns
return (nubOrd (files0 ++ files1))

changes <- mapM (fix mode verbose tabSize) files
changes <- mapM (fix mode verbose tabSize consecutiveLines) files

when (or changes && mode == Check) exitFailure

fix :: Mode -> Verbose -> TabSize -> FilePath -> IO Bool
fix mode verbose tabSize f =
checkFile tabSize verbose f >>= \case
fix :: Mode -> Verbose -> TabSize -> ConsecutiveEmptyLines -> FilePath -> IO Bool
fix mode verbose tabSize consecutiveLines f =
checkFile tabSize consecutiveLines verbose f >>= \case

CheckOK -> do
when verbose $
Expand Down
55 changes: 49 additions & 6 deletions src/Data/Text/FixWhitespace.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ module Data.Text.FixWhitespace
, transform
, transformWithLog
, TabSize
, ConsecutiveEmptyLines
, Verbose
, defaultTabSize
, defaultConsecutiveEmptyLines
)
where

Expand All @@ -29,12 +31,17 @@ import Data.List.Extra.Drop ( dropWhileEnd1, dropWhile1 )

type Verbose = Bool
type TabSize = Int
type ConsecutiveEmptyLines = Int

-- | Default tab size.
--
defaultTabSize :: TabSize
defaultTabSize = 8

-- | Maximum consecutive empty lines
defaultConsecutiveEmptyLines :: ConsecutiveEmptyLines
defaultConsecutiveEmptyLines = 0

-- | Result of checking a file against the whitespace policy.
--
data CheckResult
Expand All @@ -54,30 +61,44 @@ data LineError = LineError Int Text
-- | Check a file against the whitespace policy,
-- returning a fix if violations occurred.
--
checkFile :: TabSize -> Verbose -> FilePath -> IO CheckResult
checkFile tabSize verbose f =
checkFile :: TabSize -> ConsecutiveEmptyLines -> Verbose -> FilePath -> IO CheckResult
checkFile tabSize consecutiveLines verbose f =
handle (\ (e :: IOException) -> return $ CheckIOError e) $
withFile f ReadMode $ \ h -> do
hSetEncoding h utf8
s <- Text.hGetContents h
let (s', lvs)
| verbose = transformWithLog tabSize s
| otherwise = (transform tabSize s, [])
| verbose = transformWithLog tabSize consecutiveLines s
| otherwise = (transform tabSize consecutiveLines s, [])
return $ if s' == s then CheckOK else CheckViolation s' lvs

transform
:: TabSize -- ^ Expand tab characters to so many spaces. Keep tabs if @<= 0@.
-> ConsecutiveEmptyLines -- ^ Maximum count of consecutive empty lines. Unlimited if @<= 0@.
-> Text -- ^ Text before transformation.
-> Text -- ^ Text after transformation.
transform tabSize =
transform tabSize consecutiveLines =
Text.unlines .
(if consecutiveLines > 0 then squashConsecutiveEmptyLines 0 else id) .
removeFinalEmptyLinesExceptOne .
map (removeTrailingWhitespace . convertTabs tabSize) .
Text.lines
where
removeFinalEmptyLinesExceptOne =
reverse . dropWhile1 Text.null . reverse

squashConsecutiveEmptyLines :: Int -> [Text] -> [Text]
squashConsecutiveEmptyLines _ [] = []
squashConsecutiveEmptyLines n (l:ls)
| Text.null l
= if n >= consecutiveLines
then squashConsecutiveEmptyLines n ls
else
l : squashConsecutiveEmptyLines (n + 1) ls

| otherwise
= l : squashConsecutiveEmptyLines 0 ls

-- | The transformation monad: maintains info about lines that
-- violate the rules. Used in the verbose mode to build a log.
--
Expand All @@ -87,9 +108,10 @@ type TransformM = Writer [LineError]
--
transformWithLog
:: TabSize -- ^ Expand tab characters to so many spaces. Keep tabs if @<= 0@.
-> ConsecutiveEmptyLines -- ^ Maximum count of consecutive empty lines. Unlimited if @<= 0@.
-> Text -- ^ Text before transformation.
-> (Text, [LineError]) -- ^ Text after transformation and violating lines if any.
transformWithLog tabSize =
transformWithLog tabSize consecutiveLines =
runWriter .
fmap Text.unlines .
fixAllViolations .
Expand All @@ -98,6 +120,8 @@ transformWithLog tabSize =
where
fixAllViolations :: [(Int,Text)] -> TransformM [Text]
fixAllViolations =
(if consecutiveLines > 0 then squashConsecutiveEmptyLines 1 0 else return)
<=<
removeFinalEmptyLinesExceptOne
<=<
mapM (fixLineWith $ removeTrailingWhitespace . convertTabs tabSize)
Expand All @@ -114,6 +138,25 @@ transformWithLog tabSize =
lenLs' = length ls'
els = replicate (lenLs - lenLs') ""

squashConsecutiveEmptyLines :: Int -> Int -> [Text] -> TransformM [Text]
squashConsecutiveEmptyLines _ _ [] = return []
squashConsecutiveEmptyLines i n (l:ls)
| Text.null l
= if n >= consecutiveLines
then do
tell [LineError i l]
squashConsecutiveEmptyLinesAfterError (i + 1) ls
else
(l:) <$> squashConsecutiveEmptyLines (i + 1) (n + 1) ls

| otherwise
= (l:) <$> squashConsecutiveEmptyLines (i + 1) 0 ls

squashConsecutiveEmptyLinesAfterError _ [] = return []
squashConsecutiveEmptyLinesAfterError i (l:ls)
| Text.null l = squashConsecutiveEmptyLinesAfterError (i + 1) ls
| otherwise = squashConsecutiveEmptyLines i 0 (l:ls)

fixLineWith :: (Text -> Text) -> (Int, Text) -> TransformM Text
fixLineWith fixer (i, l)
| l == l' = pure l
Expand Down
2 changes: 1 addition & 1 deletion test/Golden.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ goldenTests = do

goldenValue :: FilePath -> IO ByteString
goldenValue file = do
checkFile defaultTabSize {-verbose: -}True file >>= \case
checkFile defaultTabSize 1 {-verbose: -} True file >>= \case

CheckIOError e ->
ioError e
Expand Down
1 change: 1 addition & 0 deletions test/violations.golden
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
Violations:
test/violations.txt:1: Trailing·space·
test/violations.txt:3: Trailing·tab····
test/violations.txt:5: <NEWLINE>
2 changes: 2 additions & 0 deletions test/violations.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ Trailing space

Trailing tab



Missing newline at end of file