Merge pull request #10634 from cabalism/hlint/unused-lang-pragma
[cabal.git] / Cabal-syntax / src / Distribution / Utils / Generic.hs
blob30cf8f7d75a7fc02f1e11023e89937d631f1fe71
1 {-# LANGUAGE BangPatterns #-}
2 {-# LANGUAGE CPP #-}
3 {-# LANGUAGE FlexibleContexts #-}
4 {-# LANGUAGE RankNTypes #-}
5 {-# LANGUAGE ScopedTypeVariables #-}
7 -----------------------------------------------------------------------------
9 -- |
10 -- Module : Distribution.Utils.Generic
11 -- Copyright : Isaac Jones, Simon Marlow 2003-2004
12 -- License : BSD3
13 -- portions Copyright (c) 2007, Galois Inc.
15 -- Maintainer : cabal-devel@haskell.org
16 -- Portability : portable
18 -- A large and somewhat miscellaneous collection of utility functions used
19 -- throughout the rest of the Cabal lib and in other tools that use the Cabal
20 -- lib like @cabal-install@. It has a very simple set of logging actions. It
21 -- has low level functions for running programs, a bunch of wrappers for
22 -- various directory and file functions that do extra logging.
23 module Distribution.Utils.Generic
24 ( -- * reading and writing files safely
25 withFileContents
26 , writeFileAtomic
28 -- * Unicode
30 -- ** Conversions
31 , fromUTF8BS
32 , fromUTF8LBS
33 , toUTF8BS
34 , toUTF8LBS
35 , validateUTF8
37 -- ** File I/O
38 , readUTF8File
39 , withUTF8FileContents
40 , writeUTF8File
42 -- ** BOM
43 , ignoreBOM
45 -- ** Misc
46 , normaliseLineEndings
48 -- * generic utils
49 , dropWhileEndLE
50 , takeWhileEndLE
51 , equating
52 , comparing
53 , isInfixOf
54 , intercalate
55 , lowercase
56 , isAscii
57 , isAsciiAlpha
58 , isAsciiAlphaNum
59 , listUnion
60 , listUnionRight
61 , ordNub
62 , ordNubBy
63 , ordNubRight
64 , safeHead
65 , safeTail
66 , safeLast
67 , safeInit
68 , unintersperse
69 , wrapText
70 , wrapLine
71 , unfoldrM
72 , spanMaybe
73 , breakMaybe
74 , unsnoc
75 , unsnocNE
77 -- * Triples
78 , fstOf3
79 , sndOf3
80 , trdOf3
82 -- * FilePath stuff
83 , isAbsoluteOnAnyPlatform
84 , isRelativeOnAnyPlatform
85 ) where
87 import Distribution.Compat.Prelude
88 import Prelude ()
90 import Distribution.Utils.String
92 import Data.Bits (shiftL, (.&.), (.|.))
93 import qualified Data.ByteString as SBS
94 import qualified Data.ByteString.Lazy as LBS
95 import Data.List
96 ( isInfixOf
98 import qualified Data.Set as Set
100 import qualified Control.Exception as Exception
101 import System.Directory
102 ( copyFile
103 , getTemporaryDirectory
104 , removeFile
105 , renameFile
107 import System.FilePath
108 ( takeFileName
109 , (<.>)
111 import System.IO
112 ( IOMode (ReadMode)
113 , hClose
114 , hGetContents
115 , openBinaryTempFileWithDefaultPermissions
116 , withBinaryFile
117 , withFile
120 -- -----------------------------------------------------------------------------
121 -- Helper functions
123 -- | Wraps text to the default line width. Existing newlines are preserved.
124 wrapText :: String -> String
125 wrapText =
126 unlines
127 . map
128 ( intercalate "\n"
129 . map unwords
130 . wrapLine 79
131 . words
133 . lines
135 -- | Wraps a list of words to a list of lines of words of a particular width.
136 wrapLine :: Int -> [String] -> [[String]]
137 wrapLine width = wrap 0 []
138 where
139 wrap :: Int -> [String] -> [String] -> [[String]]
140 wrap 0 [] (w : ws)
141 | length w + 1 > width =
142 wrap (length w) [w] ws
143 wrap col line (w : ws)
144 | col + length w + 1 > width =
145 reverse line : wrap 0 [] (w : ws)
146 wrap col line (w : ws) =
147 let col' = col + length w + 1
148 in wrap col' (w : line) ws
149 wrap _ [] [] = []
150 wrap _ line [] = [reverse line]
152 -----------------------------------
153 -- Safely reading and writing files
155 -- | Gets the contents of a file, but guarantee that it gets closed.
157 -- The file is read lazily; if it is not fully consumed by the action then an
158 -- exception is thrown.
159 withFileContents :: FilePath -> (String -> IO a) -> IO a
160 withFileContents name action =
161 withFile
162 name
163 ReadMode
164 (\hnd -> hGetContents hnd >>= action)
166 -- | Writes a file atomically.
168 -- The file is either written successfully or an IO exception is raised and
169 -- the original file is left unchanged.
171 -- On Unix:
173 -- - If the temp directory (@$TMPDIR@) is in a filesystem different than the
174 -- destination path, the renaming will be emulated via 'copyFile' then
175 -- 'deleteFile'.
177 -- On Windows:
179 -- - This operation is not guaranteed to be atomic, see 'renameFile'.
181 -- - It is not possible to delete a file that is open by a process. This case
182 -- will give an IO exception but the atomic property is not affected.
184 -- - If the temp directory (@TMP@/@TEMP@/..., see haddocks on
185 -- 'getTemporaryDirectory') is in a different drive than the destination path,
186 -- the write will be emulated via 'copyFile', then 'deleteFile'.
187 writeFileAtomic :: FilePath -> LBS.ByteString -> IO ()
188 writeFileAtomic targetPath content = do
189 let targetFile = takeFileName targetPath
190 tmpDir <- getTemporaryDirectory
191 Exception.bracketOnError
192 (openBinaryTempFileWithDefaultPermissions tmpDir $ targetFile <.> "tmp")
193 (\(tmpPath, handle) -> hClose handle >> removeFile tmpPath)
194 ( \(tmpPath, handle) -> do
195 LBS.hPut handle content
196 hClose handle
197 Exception.catch
198 (renameFile tmpPath targetPath)
199 ( \(_ :: Exception.SomeException) -> do
200 copyFile tmpPath targetPath
201 removeFile tmpPath
205 -- ------------------------------------------------------------
207 -- * Unicode stuff
209 -- ------------------------------------------------------------
211 -- | Decode 'String' from UTF8-encoded 'BS.ByteString'
213 -- Invalid data in the UTF8 stream (this includes code-points @U+D800@
214 -- through @U+DFFF@) will be decoded as the replacement character (@U+FFFD@).
215 fromUTF8BS :: SBS.ByteString -> String
216 fromUTF8BS = decodeStringUtf8 . SBS.unpack
218 -- | Variant of 'fromUTF8BS' for lazy 'BS.ByteString's
219 fromUTF8LBS :: LBS.ByteString -> String
220 fromUTF8LBS = decodeStringUtf8 . LBS.unpack
222 -- | Encode 'String' to UTF8-encoded 'SBS.ByteString'
224 -- Code-points in the @U+D800@-@U+DFFF@ range will be encoded
225 -- as the replacement character (i.e. @U+FFFD@).
226 toUTF8BS :: String -> SBS.ByteString
227 toUTF8BS = SBS.pack . encodeStringUtf8
229 -- | Variant of 'toUTF8BS' for lazy 'BS.ByteString's
230 toUTF8LBS :: String -> LBS.ByteString
231 toUTF8LBS = LBS.pack . encodeStringUtf8
233 -- | Check that strict 'ByteString' is valid UTF8. Returns 'Just offset' if it's not.
234 validateUTF8 :: SBS.ByteString -> Maybe Int
235 validateUTF8 = go 0
236 where
237 go off bs = case SBS.uncons bs of
238 Nothing -> Nothing
239 Just (c, bs')
240 | c <= 0x7F -> go (off + 1) bs'
241 | c <= 0xBF -> Just off
242 | c <= 0xDF -> twoBytes off c bs'
243 | c <= 0xEF -> moreBytes off 3 0x800 bs' (fromIntegral $ c .&. 0xF)
244 | c <= 0xF7 -> moreBytes off 4 0x10000 bs' (fromIntegral $ c .&. 0x7)
245 | c <= 0xFB -> moreBytes off 5 0x200000 bs' (fromIntegral $ c .&. 0x3)
246 | c <= 0xFD -> moreBytes off 6 0x4000000 bs' (fromIntegral $ c .&. 0x1)
247 | otherwise -> Just off
249 twoBytes off c0 bs = case SBS.uncons bs of
250 Nothing -> Just off
251 Just (c1, bs')
252 | c1 .&. 0xC0 == 0x80 ->
253 if d >= (0x80 :: Int)
254 then go (off + 2) bs'
255 else Just off
256 | otherwise -> Just off
257 where
258 d = (fromIntegral (c0 .&. 0x1F) `shiftL` 6) .|. fromIntegral (c1 .&. 0x3F)
260 moreBytes :: Int -> Int -> Int -> SBS.ByteString -> Int -> Maybe Int
261 moreBytes off 1 overlong cs' acc
262 | overlong <= acc
263 , acc <= 0x10FFFF
264 , acc < 0xD800 || 0xDFFF < acc =
265 go (off + 1) cs'
266 | otherwise =
267 Just off
268 moreBytes off byteCount overlong bs acc = case SBS.uncons bs of
269 Just (cn, bs')
270 | cn .&. 0xC0 == 0x80 ->
271 moreBytes (off + 1) (byteCount - 1) overlong bs' ((acc `shiftL` 6) .|. fromIntegral cn .&. 0x3F)
272 _ -> Just off
274 -- | Ignore a Unicode byte order mark (BOM) at the beginning of the input
275 ignoreBOM :: String -> String
276 ignoreBOM ('\xFEFF' : string) = string
277 ignoreBOM string = string
279 -- | Reads a UTF8 encoded text file as a Unicode String
281 -- Reads lazily using ordinary 'readFile'.
282 readUTF8File :: FilePath -> IO String
283 readUTF8File f = (ignoreBOM . fromUTF8LBS) <$> LBS.readFile f
285 -- | Reads a UTF8 encoded text file as a Unicode String
287 -- Same behaviour as 'withFileContents'.
288 withUTF8FileContents :: FilePath -> (String -> IO a) -> IO a
289 withUTF8FileContents name action =
290 withBinaryFile
291 name
292 ReadMode
293 (\hnd -> LBS.hGetContents hnd >>= action . ignoreBOM . fromUTF8LBS)
295 -- | Writes a Unicode String as a UTF8 encoded text file.
297 -- Uses 'writeFileAtomic', so provides the same guarantees.
298 writeUTF8File :: FilePath -> String -> IO ()
299 writeUTF8File path = writeFileAtomic path . toUTF8LBS
301 -- | Fix different systems silly line ending conventions
302 normaliseLineEndings :: String -> String
303 normaliseLineEndings [] = []
304 normaliseLineEndings ('\r' : '\n' : s) = '\n' : normaliseLineEndings s -- windows
305 normaliseLineEndings ('\r' : s) = '\n' : normaliseLineEndings s -- old OS X
306 normaliseLineEndings (c : s) = c : normaliseLineEndings s
308 -- ------------------------------------------------------------
310 -- * Common utils
312 -- ------------------------------------------------------------
314 -- | @dropWhileEndLE p@ is equivalent to @reverse . dropWhile p . reverse@, but
315 -- quite a bit faster. The difference between "Data.List.dropWhileEnd" and this
316 -- version is that the one in "Data.List" is strict in elements, but spine-lazy,
317 -- while this one is spine-strict but lazy in elements. That's what @LE@ stands
318 -- for - "lazy in elements".
320 -- Example:
322 -- >>> safeTail $ Data.List.dropWhileEnd (<3) [undefined, 5, 4, 3, 2, 1]
323 -- *** Exception: Prelude.undefined
324 -- ...
326 -- >>> safeTail $ dropWhileEndLE (<3) [undefined, 5, 4, 3, 2, 1]
327 -- [5,4,3]
329 -- >>> take 3 $ Data.List.dropWhileEnd (<3) [5, 4, 3, 2, 1, undefined]
330 -- [5,4,3]
332 -- >>> take 3 $ dropWhileEndLE (<3) [5, 4, 3, 2, 1, undefined]
333 -- *** Exception: Prelude.undefined
334 -- ...
335 dropWhileEndLE :: (a -> Bool) -> [a] -> [a]
336 dropWhileEndLE p = foldr (\x r -> if null r && p x then [] else x : r) []
338 -- | @takeWhileEndLE p@ is equivalent to @reverse . takeWhile p . reverse@, but
339 -- is usually faster (as well as being easier to read).
340 takeWhileEndLE :: (a -> Bool) -> [a] -> [a]
341 takeWhileEndLE p = fst . foldr go ([], False)
342 where
343 go x (rest, done)
344 | not done && p x = (x : rest, False)
345 | otherwise = (rest, True)
347 -- | Like 'Data.List.nub', but has @O(n log n)@ complexity instead of
348 -- @O(n^2)@. Code for 'ordNub' and 'listUnion' taken from Niklas Hambüchen's
349 -- <http://github.com/nh2/haskell-ordnub ordnub> package.
350 ordNub :: Ord a => [a] -> [a]
351 ordNub = ordNubBy id
353 -- | Like 'ordNub' and 'Data.List.nubBy'. Selects a key for each element and
354 -- takes the nub based on that key.
355 ordNubBy :: Ord b => (a -> b) -> [a] -> [a]
356 ordNubBy f l = go Set.empty l
357 where
358 go !_ [] = []
359 go !s (x : xs)
360 | y `Set.member` s = go s xs
361 | otherwise =
362 let !s' = Set.insert y s
363 in x : go s' xs
364 where
365 y = f x
367 -- | Like "Data.List.union", but has @O(n log n)@ complexity instead of
368 -- @O(n^2)@.
369 listUnion :: Ord a => [a] -> [a] -> [a]
370 listUnion a b = a ++ ordNub (filter (`Set.notMember` aSet) b)
371 where
372 aSet = Set.fromList a
374 -- | A right-biased version of 'ordNub'.
376 -- Example:
378 -- >>> ordNub [1,2,1] :: [Int]
379 -- [1,2]
381 -- >>> ordNubRight [1,2,1] :: [Int]
382 -- [2,1]
383 ordNubRight :: Ord a => [a] -> [a]
384 ordNubRight = fst . foldr go ([], Set.empty)
385 where
386 go x p@(l, s) =
387 if x `Set.member` s
388 then p
389 else (x : l, Set.insert x s)
391 -- | A right-biased version of 'listUnion'.
393 -- Example:
395 -- >>> listUnion [1,2,3,4,3] [2,1,1]
396 -- [1,2,3,4,3]
398 -- >>> listUnionRight [1,2,3,4,3] [2,1,1]
399 -- [4,3,2,1,1]
400 listUnionRight :: Ord a => [a] -> [a] -> [a]
401 listUnionRight a b = ordNubRight (filter (`Set.notMember` bSet) a) ++ b
402 where
403 bSet = Set.fromList b
405 -- | A total variant of 'head'.
407 -- @since 3.2.0.0
408 safeHead :: [a] -> Maybe a
409 safeHead [] = Nothing
410 safeHead (x : _) = Just x
412 -- | A total variant of 'tail'.
414 -- @since 3.2.0.0
415 safeTail :: [a] -> [a]
416 safeTail [] = []
417 safeTail (_ : xs) = xs
419 -- | A total variant of 'last'.
421 -- @since 3.2.0.0
422 safeLast :: [a] -> Maybe a
423 safeLast [] = Nothing
424 safeLast (x : xs) = Just (foldl (\_ a -> a) x xs)
426 -- | A total variant of 'init'.
428 -- @since 3.2.0.0
429 safeInit :: [a] -> [a]
430 safeInit [] = []
431 safeInit [_] = []
432 safeInit (x : xs) = x : safeInit xs
434 equating :: Eq a => (b -> a) -> b -> b -> Bool
435 equating p x y = p x == p y
437 -- | Lower case string
439 -- >>> lowercase "Foobar"
440 -- "foobar"
441 lowercase :: String -> String
442 lowercase = map toLower
444 -- | Ascii characters
445 isAscii :: Char -> Bool
446 isAscii c = fromEnum c < 0x80
448 -- | Ascii letters.
449 isAsciiAlpha :: Char -> Bool
450 isAsciiAlpha c =
451 ('a' <= c && c <= 'z')
452 || ('A' <= c && c <= 'Z')
454 -- | Ascii letters and digits.
456 -- >>> isAsciiAlphaNum 'a'
457 -- True
459 -- >>> isAsciiAlphaNum 'ä'
460 -- False
461 isAsciiAlphaNum :: Char -> Bool
462 isAsciiAlphaNum c = isAscii c && isAlphaNum c
464 unintersperse :: Char -> String -> [String]
465 unintersperse mark = unfoldr unintersperse1
466 where
467 unintersperse1 str
468 | null str = Nothing
469 | otherwise =
470 let (this, rest) = break (== mark) str
471 in Just (this, safeTail rest)
473 -- | Like 'break', but with 'Maybe' predicate
475 -- >>> breakMaybe (readMaybe :: String -> Maybe Int) ["foo", "bar", "1", "2", "quu"]
476 -- (["foo","bar"],Just (1,["2","quu"]))
478 -- >>> breakMaybe (readMaybe :: String -> Maybe Int) ["foo", "bar"]
479 -- (["foo","bar"],Nothing)
481 -- @since 2.2
482 breakMaybe :: (a -> Maybe b) -> [a] -> ([a], Maybe (b, [a]))
483 breakMaybe f = go id
484 where
485 go !acc [] = (acc [], Nothing)
486 go !acc (x : xs) = case f x of
487 Nothing -> go (acc . (x :)) xs
488 Just b -> (acc [], Just (b, xs))
490 -- | Like 'span' but with 'Maybe' predicate
492 -- >>> spanMaybe listToMaybe [[1,2],[3],[],[4,5],[6,7]]
493 -- ([1,3],[[],[4,5],[6,7]])
495 -- >>> spanMaybe (readMaybe :: String -> Maybe Int) ["1", "2", "foo"]
496 -- ([1,2],["foo"])
498 -- @since 2.2
499 spanMaybe :: (a -> Maybe b) -> [a] -> ([b], [a])
500 spanMaybe _ xs@[] = ([], xs)
501 spanMaybe p xs@(x : xs') = case p x of
502 Just y -> let (ys, zs) = spanMaybe p xs' in (y : ys, zs)
503 Nothing -> ([], xs)
505 -- | 'unfoldr' with monadic action.
507 -- >>> take 5 $ unfoldrM (\b r -> Just (r + b, b + 1)) (1 :: Int) 2
508 -- [3,4,5,6,7]
510 -- @since 2.2
511 unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> m [a]
512 unfoldrM f = go
513 where
514 go b = do
515 m <- f b
516 case m of
517 Nothing -> return []
518 Just (a, b') -> liftM (a :) (go b')
520 -- | The opposite of 'snoc', which is the reverse of 'cons'
522 -- Example:
524 -- >>> unsnoc [1, 2, 3]
525 -- Just ([1,2],3)
527 -- >>> unsnoc []
528 -- Nothing
530 -- @since 3.2.0.0
531 unsnoc :: [a] -> Maybe ([a], a)
532 unsnoc [] = Nothing
533 unsnoc (x : xs) = Just (unsnocNE (x :| xs))
535 -- | Like 'unsnoc', but for 'NonEmpty' so without the 'Maybe'
537 -- Example:
539 -- >>> unsnocNE (1 :| [2, 3])
540 -- ([1,2],3)
542 -- >>> unsnocNE (1 :| [])
543 -- ([],1)
545 -- @since 3.2.0.0
546 unsnocNE :: NonEmpty a -> ([a], a)
547 unsnocNE (x :| xs) = go x xs
548 where
549 go y [] = ([], y)
550 go y (z : zs) = let ~(ws, w) = go z zs in (y : ws, w)
552 -------------------------------------------------------------------------------
553 -- Triples
554 -------------------------------------------------------------------------------
556 -- | @since 3.4.0.0
557 fstOf3 :: (a, b, c) -> a
558 fstOf3 (a, _, _) = a
560 -- | @since 3.4.0.0
561 sndOf3 :: (a, b, c) -> b
562 sndOf3 (_, b, _) = b
564 -- | @since 3.4.0.0
565 trdOf3 :: (a, b, c) -> c
566 trdOf3 (_, _, c) = c
568 -- ------------------------------------------------------------
570 -- * FilePath stuff
572 -- ------------------------------------------------------------
574 -- | 'isAbsoluteOnAnyPlatform' and 'isRelativeOnAnyPlatform' are like
575 -- 'System.FilePath.isAbsolute' and 'System.FilePath.isRelative' but have
576 -- platform independent heuristics.
577 -- The System.FilePath exists in two versions, Windows and Posix. The two
578 -- versions don't agree on what is a relative path and we don't know if we're
579 -- given Windows or Posix paths.
580 -- This results in false positives when running on Posix and inspecting
581 -- Windows paths, like the hackage server does.
582 -- System.FilePath.Posix.isAbsolute \"C:\\hello\" == False
583 -- System.FilePath.Windows.isAbsolute \"/hello\" == False
584 -- This means that we would treat paths that start with \"/\" to be absolute.
585 -- On Posix they are indeed absolute, while on Windows they are not.
587 -- The portable versions should be used when we might deal with paths that
588 -- are from another OS than the host OS. For example, the Hackage Server
589 -- deals with both Windows and Posix paths while performing the
590 -- PackageDescription checks. In contrast, when we run 'cabal configure' we
591 -- do expect the paths to be correct for our OS and we should not have to use
592 -- the platform independent heuristics.
593 isAbsoluteOnAnyPlatform :: FilePath -> Bool
594 -- C:\\directory
595 isAbsoluteOnAnyPlatform (drive : ':' : '\\' : _) = isAlpha drive
596 isAbsoluteOnAnyPlatform (drive : ':' : '/' : _) = isAlpha drive
597 -- UNC
598 isAbsoluteOnAnyPlatform ('\\' : '\\' : _) = True
599 -- Posix root
600 isAbsoluteOnAnyPlatform ('/' : _) = True
601 isAbsoluteOnAnyPlatform _ = False
603 -- | @isRelativeOnAnyPlatform = not . 'isAbsoluteOnAnyPlatform'@
604 isRelativeOnAnyPlatform :: FilePath -> Bool
605 isRelativeOnAnyPlatform = not . isAbsoluteOnAnyPlatform
607 -- $setup
608 -- >>> import Data.Maybe
609 -- >>> import Text.Read