Skip to content

Commit 0bfd246

Browse files
authored
Add option for merging consecutive empty lines (#70)
Add --consecutive option
2 parents 9af188b + fb84ef0 commit 0bfd246

File tree

5 files changed

+71
-13
lines changed

5 files changed

+71
-13
lines changed

FixWhitespace.hs

Lines changed: 18 additions & 6 deletions
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,8 @@ 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
53+
-- ^ The number of consecutive empty lines allowed. Unlimited if 0.
5254
}
5355

5456
defaultOptions :: Options
@@ -59,6 +61,7 @@ defaultOptions = Options
5961
, optMode = Fix
6062
, optConfig = defaultConfigFile
6163
, optTabSize = show defaultTabSize
64+
, optConsEL = show defaultConsecutiveEmptyLines
6265
}
6366

6467
options :: [OptDescr (Options -> Options)]
@@ -81,6 +84,12 @@ options =
8184
[ "Expand tab characters to TABSIZE (default: " ++ show defaultTabSize ++ ") many spaces."
8285
, "Keep tabs if 0 is given as TABSIZE."
8386
])
87+
, Option ['n'] ["consecutive"]
88+
(ReqArg (\ns opts -> opts { optConsEL = ns }) "LINES")
89+
(unlines
90+
[ "Maximum consecutive empty lines (default: " ++ show defaultConsecutiveEmptyLines ++ ")."
91+
, "Unlimited if 0 is given as LINES."
92+
])
8493
, Option [] ["config"]
8594
(ReqArg (\loc opts -> opts { optConfig = loc }) "CONFIG")
8695
(concat ["Override the project configuration ", defaultConfigFile, "."])
@@ -105,7 +114,7 @@ shortUsageHeader :: String -> String
105114
shortUsageHeader progName = unwords
106115
[ "Usage:"
107116
, progName
108-
, "[-h|--help] [-v|--verbose] [--check] [--config CONFIG] [-t|--tab TABSIZE] [FILES]"
117+
, "[-h|--help] [-v|--verbose] [--check] [--config CONFIG] [-t|--tab TABSIZE] [-n|--consecutive LINES] [FILES]"
109118
]
110119

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

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

164176
files <- if not $ null nonOpts
@@ -198,13 +210,13 @@ main = do
198210
files1 <- getDirectoryFilesIgnore base incPatterns excPatterns
199211
return (nubOrd (files0 ++ files1))
200212

201-
changes <- mapM (fix mode verbose tabSize) files
213+
changes <- mapM (fix mode verbose tabSize consecutiveLines) files
202214

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

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

209221
CheckOK -> do
210222
when verbose $

src/Data/Text/FixWhitespace.hs

Lines changed: 49 additions & 6 deletions
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

test/Golden.hs

Lines changed: 1 addition & 1 deletion
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

test/violations.golden

Lines changed: 1 addition & 0 deletions
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>

test/violations.txt

Lines changed: 2 additions & 0 deletions
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)