Skip to content

Commit a7bf22a

Browse files
committed
Add --consecutive option
Resolves #68 Add an option to check for maximum count of consecutive empty lines. If zero, unlimited cmount is allowed. If 1, there cannot be empty lines, if 2 there can be one empty line, and so on. I fail with naming, having value 1 to disallow any empty lines makes sense, but it's not "maximum amount of empty lines", it's more of "fail if there is that much empty lines".
1 parent 35db3d7 commit a7bf22a

File tree

5 files changed

+70
-13
lines changed

5 files changed

+70
-13
lines changed

Diff for: FixWhitespace.hs

+17-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import System.IO ( IOMode(WriteMode), hPutStr, hPut
2121
import Text.Read ( readMaybe )
2222

2323
import Data.Text.FixWhitespace ( CheckResult(CheckOK, CheckViolation, CheckIOError), checkFile, displayLineError
24-
, TabSize, Verbose, defaultTabSize )
24+
, TabSize, ConsecutiveEmptyLines, Verbose, defaultTabSize, defaultConsecutiveEmptyLines )
2525

2626
import ParseConfig ( Config(Config), parseConfig )
2727
import qualified Paths_fix_whitespace as PFW ( version )
@@ -49,6 +49,7 @@ data Options = Options
4949
-- ^ The location to the configuration file.
5050
, optTabSize :: String
5151
-- ^ The number of spaces to expand a tab character to. @"0"@ for keeping tabs.
52+
, optConsEL :: String
5253
}
5354

5455
defaultOptions :: Options
@@ -59,6 +60,7 @@ defaultOptions = Options
5960
, optMode = Fix
6061
, optConfig = defaultConfigFile
6162
, optTabSize = show defaultTabSize
63+
, optConsEL = show defaultConsecutiveEmptyLines
6264
}
6365

6466
options :: [OptDescr (Options -> Options)]
@@ -81,6 +83,12 @@ options =
8183
[ "Expand tab characters to TABSIZE (default: " ++ show defaultTabSize ++ ") many spaces."
8284
, "Keep tabs if 0 is given as TABSIZE."
8385
])
86+
, Option ['n'] ["consecutive"]
87+
(ReqArg (\ns opts -> opts { optConsEL = ns }) "LINES")
88+
(unlines
89+
[ "Maximum consecutive empty lines (default: " ++ show defaultConsecutiveEmptyLines ++ ")."
90+
, "Unlimited if 0 is given as LINES."
91+
])
8492
, Option [] ["config"]
8593
(ReqArg (\loc opts -> opts { optConfig = loc }) "CONFIG")
8694
(concat ["Override the project configuration ", defaultConfigFile, "."])
@@ -105,7 +113,7 @@ shortUsageHeader :: String -> String
105113
shortUsageHeader progName = unwords
106114
[ "Usage:"
107115
, progName
108-
, "[-h|--help] [-v|--verbose] [--check] [--config CONFIG] [-t|--tab TABSIZE] [FILES]"
116+
, "[-h|--help] [-v|--verbose] [--check] [--config CONFIG] [-t|--tab TABSIZE] [-n|--consecutive LINES] [FILES]"
109117
]
110118

111119
usageHeader :: String -> String
@@ -159,6 +167,9 @@ main = do
159167
tabSize <- maybe (die "Error: Illegal TABSIZE, must be an integer.") return $
160168
readMaybe $ optTabSize opts
161169

170+
consecutiveLines <- maybe (die "Error: Illegal LINES, must be an integer.") return $
171+
readMaybe $ optConsEL opts
172+
162173
base <- getCurrentDirectory
163174

164175
files <- if not $ null nonOpts
@@ -198,13 +209,13 @@ main = do
198209
files1 <- getDirectoryFilesIgnore base incPatterns excPatterns
199210
return (nubOrd (files0 ++ files1))
200211

201-
changes <- mapM (fix mode verbose tabSize) files
212+
changes <- mapM (fix mode verbose tabSize consecutiveLines) files
202213

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

205-
fix :: Mode -> Verbose -> TabSize -> FilePath -> IO Bool
206-
fix mode verbose tabSize f =
207-
checkFile tabSize verbose f >>= \case
216+
fix :: Mode -> Verbose -> TabSize -> ConsecutiveEmptyLines -> FilePath -> IO Bool
217+
fix mode verbose tabSize consecutiveLines f =
218+
checkFile tabSize consecutiveLines verbose f >>= \case
208219

209220
CheckOK -> do
210221
when verbose $

Diff for: src/Data/Text/FixWhitespace.hs

+49-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ module Data.Text.FixWhitespace
99
, transform
1010
, transformWithLog
1111
, TabSize
12+
, ConsecutiveEmptyLines
1213
, Verbose
1314
, defaultTabSize
15+
, defaultConsecutiveEmptyLines
1416
)
1517
where
1618

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

3032
type Verbose = Bool
3133
type TabSize = Int
34+
type ConsecutiveEmptyLines = Int
3235

3336
-- | Default tab size.
3437
--
3538
defaultTabSize :: TabSize
3639
defaultTabSize = 8
3740

41+
-- | Maximum consecutive empty lines
42+
defaultConsecutiveEmptyLines :: ConsecutiveEmptyLines
43+
defaultConsecutiveEmptyLines = 0
44+
3845
-- | Result of checking a file against the whitespace policy.
3946
--
4047
data CheckResult
@@ -54,30 +61,44 @@ data LineError = LineError Int Text
5461
-- | Check a file against the whitespace policy,
5562
-- returning a fix if violations occurred.
5663
--
57-
checkFile :: TabSize -> Verbose -> FilePath -> IO CheckResult
58-
checkFile tabSize verbose f =
64+
checkFile :: TabSize -> ConsecutiveEmptyLines -> Verbose -> FilePath -> IO CheckResult
65+
checkFile tabSize consecutiveLines verbose f =
5966
handle (\ (e :: IOException) -> return $ CheckIOError e) $
6067
withFile f ReadMode $ \ h -> do
6168
hSetEncoding h utf8
6269
s <- Text.hGetContents h
6370
let (s', lvs)
64-
| verbose = transformWithLog tabSize s
65-
| otherwise = (transform tabSize s, [])
71+
| verbose = transformWithLog tabSize consecutiveLines s
72+
| otherwise = (transform tabSize consecutiveLines s, [])
6673
return $ if s' == s then CheckOK else CheckViolation s' lvs
6774

6875
transform
6976
:: TabSize -- ^ Expand tab characters to so many spaces. Keep tabs if @<= 0@.
77+
-> ConsecutiveEmptyLines -- ^ Maximum count of consecutive empty lines. Unlimited if @<= 0@.
7078
-> Text -- ^ Text before transformation.
7179
-> Text -- ^ Text after transformation.
72-
transform tabSize =
80+
transform tabSize consecutiveLines =
7381
Text.unlines .
82+
(if consecutiveLines > 0 then squashConsecutiveEmptyLines 0 else id) .
7483
removeFinalEmptyLinesExceptOne .
7584
map (removeTrailingWhitespace . convertTabs tabSize) .
7685
Text.lines
7786
where
7887
removeFinalEmptyLinesExceptOne =
7988
reverse . dropWhile1 Text.null . reverse
8089

90+
squashConsecutiveEmptyLines :: Int -> [Text] -> [Text]
91+
squashConsecutiveEmptyLines _ [] = []
92+
squashConsecutiveEmptyLines n (l:ls)
93+
| Text.null l
94+
= if n >= consecutiveLines
95+
then squashConsecutiveEmptyLines n ls
96+
else
97+
l : squashConsecutiveEmptyLines (n + 1) ls
98+
99+
| otherwise
100+
= l : squashConsecutiveEmptyLines 0 ls
101+
81102
-- | The transformation monad: maintains info about lines that
82103
-- violate the rules. Used in the verbose mode to build a log.
83104
--
@@ -87,9 +108,10 @@ type TransformM = Writer [LineError]
87108
--
88109
transformWithLog
89110
:: TabSize -- ^ Expand tab characters to so many spaces. Keep tabs if @<= 0@.
111+
-> ConsecutiveEmptyLines -- ^ Maximum count of consecutive empty lines. Unlimited if @<= 0@.
90112
-> Text -- ^ Text before transformation.
91113
-> (Text, [LineError]) -- ^ Text after transformation and violating lines if any.
92-
transformWithLog tabSize =
114+
transformWithLog tabSize consecutiveLines =
93115
runWriter .
94116
fmap Text.unlines .
95117
fixAllViolations .
@@ -98,6 +120,8 @@ transformWithLog tabSize =
98120
where
99121
fixAllViolations :: [(Int,Text)] -> TransformM [Text]
100122
fixAllViolations =
123+
(if consecutiveLines > 0 then squashConsecutiveEmptyLines 1 0 else return)
124+
<=<
101125
removeFinalEmptyLinesExceptOne
102126
<=<
103127
mapM (fixLineWith $ removeTrailingWhitespace . convertTabs tabSize)
@@ -114,6 +138,25 @@ transformWithLog tabSize =
114138
lenLs' = length ls'
115139
els = replicate (lenLs - lenLs') ""
116140

141+
squashConsecutiveEmptyLines :: Int -> Int -> [Text] -> TransformM [Text]
142+
squashConsecutiveEmptyLines _ _ [] = return []
143+
squashConsecutiveEmptyLines i n (l:ls)
144+
| Text.null l
145+
= if n >= consecutiveLines
146+
then do
147+
tell [LineError i l]
148+
squashConsecutiveEmptyLinesAfterError (i + 1) ls
149+
else
150+
(l:) <$> squashConsecutiveEmptyLines (i + 1) (n + 1) ls
151+
152+
| otherwise
153+
= (l:) <$> squashConsecutiveEmptyLines (i + 1) 0 ls
154+
155+
squashConsecutiveEmptyLinesAfterError _ [] = return []
156+
squashConsecutiveEmptyLinesAfterError i (l:ls)
157+
| Text.null l = squashConsecutiveEmptyLinesAfterError (i + 1) ls
158+
| otherwise = squashConsecutiveEmptyLines i 0 (l:ls)
159+
117160
fixLineWith :: (Text -> Text) -> (Int, Text) -> TransformM Text
118161
fixLineWith fixer (i, l)
119162
| l == l' = pure l

Diff for: test/Golden.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ goldenTests = do
3434

3535
goldenValue :: FilePath -> IO ByteString
3636
goldenValue file = do
37-
checkFile defaultTabSize {-verbose: -}True file >>= \case
37+
checkFile defaultTabSize 1 {-verbose: -} True file >>= \case
3838

3939
CheckIOError e ->
4040
ioError e

Diff for: test/violations.golden

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
Violations:
22
test/violations.txt:1: Trailing·space·
33
test/violations.txt:3: Trailing·tab····
4+
test/violations.txt:5: <NEWLINE>

Diff for: test/violations.txt

+2
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ Trailing space
22

33
Trailing tab
44

5+
6+
57
Missing newline at end of file

0 commit comments

Comments
 (0)