2 {-# LANGUAGE ScopedTypeVariables #-}
3 {-# LANGUAGE FlexibleContexts #-}
4 {-# LANGUAGE RankNTypes #-}
5 {-# LANGUAGE DeriveGeneric #-}
6 {-# LANGUAGE BangPatterns #-}
8 -----------------------------------------------------------------------------
10 -- Module : Distribution.Utils.Generic
11 -- Copyright : Isaac Jones, Simon Marlow 2003-2004
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.
24 module Distribution
.Utils
.Generic
(
25 -- * reading and writing files safely
86 isAbsoluteOnAnyPlatform
,
87 isRelativeOnAnyPlatform
,
91 import Distribution
.Compat
.Prelude
93 import Distribution
.Utils
.String
95 import Data
.Bits
((.&.), (.|
.), shiftL
)
98 import qualified Data
.Set
as Set
99 import qualified Data
.ByteString
as SBS
100 import qualified Data
.ByteString
.Lazy
as LBS
102 import System
.Directory
103 ( removeFile, renameFile )
104 import System
.FilePath
105 ( (<.>), splitFileName
)
107 ( withFile
, withBinaryFile
108 , openBinaryTempFileWithDefaultPermissions
109 , IOMode(ReadMode
), hGetContents, hClose )
110 import qualified Control
.Exception
as Exception
112 -- -----------------------------------------------------------------------------
115 -- | Wraps text to the default line width. Existing newlines are preserved.
116 wrapText
:: String -> String
118 . map (intercalate
"\n"
124 -- | Wraps a list of words to a list of lines of words of a particular width.
125 wrapLine
:: Int -> [String] -> [[String]]
126 wrapLine width
= wrap
0 []
127 where wrap
:: Int -> [String] -> [String] -> [[String]]
129 |
length w
+ 1 > width
130 = wrap
(length w
) [w
] ws
132 | col
+ length w
+ 1 > width
133 = reverse line
: wrap
0 [] (w
:ws
)
135 = let col
' = col
+ length w
+ 1
136 in wrap col
' (w
:line
) ws
138 wrap _ line
[] = [reverse line
]
140 -----------------------------------
141 -- Safely reading and writing files
143 -- | Gets the contents of a file, but guarantee that it gets closed.
145 -- The file is read lazily but if it is not fully consumed by the action then
146 -- the remaining input is truncated and the file is closed.
148 withFileContents
:: FilePath -> (String -> IO a
) -> IO a
149 withFileContents name action
=
150 withFile name ReadMode
151 (\hnd
-> hGetContents hnd
>>= action
)
153 -- | Writes a file atomically.
155 -- The file is either written successfully or an IO exception is raised and
156 -- the original file is left unchanged.
158 -- On windows it is not possible to delete a file that is open by a process.
159 -- This case will give an IO exception but the atomic property is not affected.
161 writeFileAtomic
:: FilePath -> LBS
.ByteString
-> IO ()
162 writeFileAtomic targetPath content
= do
163 let (targetDir
, targetFile
) = splitFileName targetPath
164 Exception
.bracketOnError
165 (openBinaryTempFileWithDefaultPermissions targetDir
$ targetFile
<.> "tmp")
166 (\(tmpPath
, handle
) -> hClose handle
>> removeFile tmpPath
)
167 (\(tmpPath
, handle
) -> do
168 LBS
.hPut handle content
170 renameFile tmpPath targetPath
)
172 -- ------------------------------------------------------------
174 -- ------------------------------------------------------------
176 -- | Decode 'String' from UTF8-encoded 'BS.ByteString'
178 -- Invalid data in the UTF8 stream (this includes code-points @U+D800@
179 -- through @U+DFFF@) will be decoded as the replacement character (@U+FFFD@).
181 fromUTF8BS
:: SBS
.ByteString
-> String
182 fromUTF8BS
= decodeStringUtf8
. SBS
.unpack
184 -- | Variant of 'fromUTF8BS' for lazy 'BS.ByteString's
186 fromUTF8LBS
:: LBS
.ByteString
-> String
187 fromUTF8LBS
= decodeStringUtf8
. LBS
.unpack
189 -- | Encode 'String' to UTF8-encoded 'SBS.ByteString'
191 -- Code-points in the @U+D800@-@U+DFFF@ range will be encoded
192 -- as the replacement character (i.e. @U+FFFD@).
194 toUTF8BS
:: String -> SBS
.ByteString
195 toUTF8BS
= SBS
.pack
. encodeStringUtf8
197 -- | Variant of 'toUTF8BS' for lazy 'BS.ByteString's
199 toUTF8LBS
:: String -> LBS
.ByteString
200 toUTF8LBS
= LBS
.pack
. encodeStringUtf8
202 -- | Check that strict 'ByteString' is valid UTF8. Returns 'Just offset' if it's not.
203 validateUTF8
:: SBS
.ByteString
-> Maybe Int
204 validateUTF8
= go
0 where
205 go off bs
= case SBS
.uncons bs
of
208 | c
<= 0x7F -> go
(off
+ 1) bs
'
209 | c
<= 0xBF -> Just off
210 | c
<= 0xDF -> twoBytes off c bs
'
211 | c
<= 0xEF -> moreBytes off
3 0x800 bs
' (fromIntegral $ c
.&. 0xF)
212 | c
<= 0xF7 -> moreBytes off
4 0x10000 bs
' (fromIntegral $ c
.&. 0x7)
213 | c
<= 0xFB -> moreBytes off
5 0x200000 bs
' (fromIntegral $ c
.&. 0x3)
214 | c
<= 0xFD -> moreBytes off
6 0x4000000 bs
' (fromIntegral $ c
.&. 0x1)
215 |
otherwise -> Just off
217 twoBytes off c0 bs
= case SBS
.uncons bs
of
220 | c1
.&. 0xC0 == 0x80 ->
221 if d
>= (0x80 :: Int)
222 then go
(off
+ 2) bs
'
224 |
otherwise -> Just off
226 d
= (fromIntegral (c0
.&. 0x1F) `shiftL`
6) .|
. fromIntegral (c1
.&. 0x3F)
228 moreBytes
:: Int -> Int -> Int -> SBS
.ByteString
-> Int -> Maybe Int
229 moreBytes off
1 overlong cs
' acc
230 | overlong
<= acc
, acc
<= 0x10FFFF, acc
< 0xD800 ||
0xDFFF < acc
236 moreBytes off byteCount overlong bs acc
= case SBS
.uncons bs
of
237 Just
(cn
, bs
') | cn
.&. 0xC0 == 0x80 ->
238 moreBytes
(off
+ 1) (byteCount
-1) overlong bs
' ((acc `shiftL`
6) .|
. fromIntegral cn
.&. 0x3F)
242 -- | Ignore a Unicode byte order mark (BOM) at the beginning of the input
244 ignoreBOM
:: String -> String
245 ignoreBOM
('\xFEFF
':string) = string
246 ignoreBOM
string = string
248 -- | Reads a UTF8 encoded text file as a Unicode String
250 -- Reads lazily using ordinary 'readFile'.
252 readUTF8File
:: FilePath -> IO String
253 readUTF8File f
= (ignoreBOM
. fromUTF8LBS
) <$> LBS
.readFile f
255 -- | Reads a UTF8 encoded text file as a Unicode String
257 -- Same behaviour as 'withFileContents'.
259 withUTF8FileContents
:: FilePath -> (String -> IO a
) -> IO a
260 withUTF8FileContents name action
=
261 withBinaryFile name ReadMode
262 (\hnd
-> LBS
.hGetContents hnd
>>= action
. ignoreBOM
. fromUTF8LBS
)
264 -- | Writes a Unicode String as a UTF8 encoded text file.
266 -- Uses 'writeFileAtomic', so provides the same guarantees.
268 writeUTF8File
:: FilePath -> String -> IO ()
269 writeUTF8File path
= writeFileAtomic path
. toUTF8LBS
271 -- | Fix different systems silly line ending conventions
272 normaliseLineEndings
:: String -> String
273 normaliseLineEndings
[] = []
274 normaliseLineEndings
('\r':'\n':s
) = '\n' : normaliseLineEndings s
-- windows
275 normaliseLineEndings
('\r':s
) = '\n' : normaliseLineEndings s
-- old OS X
276 normaliseLineEndings
( c
:s
) = c
: normaliseLineEndings s
278 -- ------------------------------------------------------------
280 -- ------------------------------------------------------------
282 -- | @dropWhileEndLE p@ is equivalent to @reverse . dropWhile p . reverse@, but
283 -- quite a bit faster. The difference between "Data.List.dropWhileEnd" and this
284 -- version is that the one in "Data.List" is strict in elements, but spine-lazy,
285 -- while this one is spine-strict but lazy in elements. That's what @LE@ stands
286 -- for - "lazy in elements".
290 -- >>> safeTail $ Data.List.dropWhileEnd (<3) [undefined, 5, 4, 3, 2, 1]
291 -- *** Exception: Prelude.undefined
294 -- >>> safeTail $ dropWhileEndLE (<3) [undefined, 5, 4, 3, 2, 1]
297 -- >>> take 3 $ Data.List.dropWhileEnd (<3) [5, 4, 3, 2, 1, undefined]
300 -- >>> take 3 $ dropWhileEndLE (<3) [5, 4, 3, 2, 1, undefined]
301 -- *** Exception: Prelude.undefined
304 dropWhileEndLE
:: (a
-> Bool) -> [a
] -> [a
]
305 dropWhileEndLE p
= foldr (\x r
-> if null r
&& p x
then [] else x
:r
) []
307 -- | @takeWhileEndLE p@ is equivalent to @reverse . takeWhile p . reverse@, but
308 -- is usually faster (as well as being easier to read).
309 takeWhileEndLE
:: (a
-> Bool) -> [a
] -> [a
]
310 takeWhileEndLE p
= fst . foldr go
([], False)
313 |
not done
&& p x
= (x
:rest
, False)
314 |
otherwise = (rest
, True)
316 -- | Like 'Data.List.nub', but has @O(n log n)@ complexity instead of
317 -- @O(n^2)@. Code for 'ordNub' and 'listUnion' taken from Niklas Hambรผchen's
318 -- <http://github.com/nh2/haskell-ordnub ordnub> package.
319 ordNub
:: Ord a
=> [a
] -> [a
]
322 -- | Like 'ordNub' and 'Data.List.nubBy'. Selects a key for each element and
323 -- takes the nub based on that key.
324 ordNubBy
:: Ord b
=> (a
-> b
) -> [a
] -> [a
]
325 ordNubBy f l
= go Set
.empty l
329 | y `Set
.member` s
= go s xs
330 |
otherwise = let !s
' = Set
.insert y s
335 -- | Like "Data.List.union", but has @O(n log n)@ complexity instead of
337 listUnion
:: (Ord a
) => [a
] -> [a
] -> [a
]
338 listUnion a b
= a
++ ordNub
(filter (`Set
.notMember` aSet
) b
)
340 aSet
= Set
.fromList a
342 -- | A right-biased version of 'ordNub'.
346 -- >>> ordNub [1,2,1] :: [Int]
349 -- >>> ordNubRight [1,2,1] :: [Int]
352 ordNubRight
:: (Ord a
) => [a
] -> [a
]
353 ordNubRight
= fst . foldr go
([], Set
.empty)
355 go x p
@(l
, s
) = if x `Set
.member` s
then p
356 else (x
:l
, Set
.insert x s
)
358 -- | A right-biased version of 'listUnion'.
362 -- >>> listUnion [1,2,3,4,3] [2,1,1]
365 -- >>> listUnionRight [1,2,3,4,3] [2,1,1]
368 listUnionRight
:: (Ord a
) => [a
] -> [a
] -> [a
]
369 listUnionRight a b
= ordNubRight
(filter (`Set
.notMember` bSet
) a
) ++ b
371 bSet
= Set
.fromList b
373 -- | A total variant of 'head'.
376 safeHead
:: [a
] -> Maybe a
377 safeHead
[] = Nothing
378 safeHead
(x
:_
) = Just x
380 -- | A total variant of 'tail'.
383 safeTail
:: [a
] -> [a
]
387 -- | A total variant of 'last'.
390 safeLast
:: [a
] -> Maybe a
391 safeLast
[] = Nothing
392 safeLast
(x
:xs
) = Just
(foldl (\_ a
-> a
) x xs
)
394 -- | A total variant of 'init'.
397 safeInit
:: [a
] -> [a
]
400 safeInit
(x
:xs
) = x
: safeInit xs
402 equating
:: Eq a
=> (b
-> a
) -> b
-> b
-> Bool
403 equating p x y
= p x
== p y
405 -- | Lower case string
407 -- >>> lowercase "Foobar"
409 lowercase
:: String -> String
410 lowercase
= map toLower
412 -- | Ascii characters
413 isAscii :: Char -> Bool
414 isAscii c
= fromEnum c
< 0x80
417 isAsciiAlpha
:: Char -> Bool
418 isAsciiAlpha c
= ('a
' <= c
&& c
<= 'z
')
419 ||
('A
' <= c
&& c
<= 'Z
')
421 -- | Ascii letters and digits.
423 -- >>> isAsciiAlphaNum 'a'
426 -- >>> isAsciiAlphaNum 'รค'
429 isAsciiAlphaNum
:: Char -> Bool
430 isAsciiAlphaNum c
= isAscii c
&& isAlphaNum c
432 unintersperse
:: Char -> String -> [String]
433 unintersperse mark
= unfoldr unintersperse1
where
437 let (this
, rest
) = break (== mark
) str
in
438 Just
(this
, safeTail rest
)
440 -- | Like 'break', but with 'Maybe' predicate
442 -- >>> breakMaybe (readMaybe :: String -> Maybe Int) ["foo", "bar", "1", "2", "quu"]
443 -- (["foo","bar"],Just (1,["2","quu"]))
445 -- >>> breakMaybe (readMaybe :: String -> Maybe Int) ["foo", "bar"]
446 -- (["foo","bar"],Nothing)
450 breakMaybe
:: (a
-> Maybe b
) -> [a
] -> ([a
], Maybe (b
, [a
]))
451 breakMaybe f
= go
id where
452 go
!acc
[] = (acc
[], Nothing
)
453 go
!acc
(x
:xs
) = case f x
of
454 Nothing
-> go
(acc
. (x
:)) xs
455 Just b
-> (acc
[], Just
(b
, xs
))
457 -- | Like 'span' but with 'Maybe' predicate
459 -- >>> spanMaybe listToMaybe [[1,2],[3],[],[4,5],[6,7]]
460 -- ([1,3],[[],[4,5],[6,7]])
462 -- >>> spanMaybe (readMaybe :: String -> Maybe Int) ["1", "2", "foo"]
467 spanMaybe
:: (a
-> Maybe b
) -> [a
] -> ([b
],[a
])
468 spanMaybe _ xs
@[] = ([], xs
)
469 spanMaybe p xs
@(x
:xs
') = case p x
of
470 Just y
-> let (ys
, zs
) = spanMaybe p xs
' in (y
: ys
, zs
)
473 -- | 'unfoldr' with monadic action.
475 -- >>> take 5 $ unfoldrM (\b r -> Just (r + b, b + 1)) (1 :: Int) 2
480 unfoldrM
:: Monad m
=> (b
-> m
(Maybe (a
, b
))) -> b
-> m
[a
]
481 unfoldrM f
= go
where
486 Just
(a
, b
') -> liftM (a
:) (go b
')
488 -- | The opposite of 'snoc', which is the reverse of 'cons'
492 -- >>> unsnoc [1, 2, 3]
500 unsnoc
:: [a
] -> Maybe ([a
], a
)
502 unsnoc
(x
:xs
) = Just
(unsnocNE
(x
:| xs
))
504 -- | Like 'unsnoc', but for 'NonEmpty' so without the 'Maybe'
508 -- >>> unsnocNE (1 :| [2, 3])
511 -- >>> unsnocNE (1 :| [])
516 unsnocNE
:: NonEmpty a
-> ([a
], a
)
517 unsnocNE
(x
:|xs
) = go x xs
where
519 go y
(z
:zs
) = let ~
(ws
, w
) = go z zs
in (y
: ws
, w
)
521 -------------------------------------------------------------------------------
523 -------------------------------------------------------------------------------
526 fstOf3
:: (a
,b
,c
) -> a
530 sndOf3
:: (a
,b
,c
) -> b
534 trdOf3
:: (a
,b
,c
) -> c
537 -- ------------------------------------------------------------
539 -- ------------------------------------------------------------
541 -- | 'isAbsoluteOnAnyPlatform' and 'isRelativeOnAnyPlatform' are like
542 -- 'System.FilePath.isAbsolute' and 'System.FilePath.isRelative' but have
543 -- platform independent heuristics.
544 -- The System.FilePath exists in two versions, Windows and Posix. The two
545 -- versions don't agree on what is a relative path and we don't know if we're
546 -- given Windows or Posix paths.
547 -- This results in false positives when running on Posix and inspecting
548 -- Windows paths, like the hackage server does.
549 -- System.FilePath.Posix.isAbsolute \"C:\\hello\" == False
550 -- System.FilePath.Windows.isAbsolute \"/hello\" == False
551 -- This means that we would treat paths that start with \"/\" to be absolute.
552 -- On Posix they are indeed absolute, while on Windows they are not.
554 -- The portable versions should be used when we might deal with paths that
555 -- are from another OS than the host OS. For example, the Hackage Server
556 -- deals with both Windows and Posix paths while performing the
557 -- PackageDescription checks. In contrast, when we run 'cabal configure' we
558 -- do expect the paths to be correct for our OS and we should not have to use
559 -- the platform independent heuristics.
560 isAbsoluteOnAnyPlatform
:: FilePath -> Bool
562 isAbsoluteOnAnyPlatform
(drive
:':':'\\':_
) = isAlpha drive
563 isAbsoluteOnAnyPlatform
(drive
:':':'/':_
) = isAlpha drive
565 isAbsoluteOnAnyPlatform
('\\':'\\':_
) = True
567 isAbsoluteOnAnyPlatform
('/':_
) = True
568 isAbsoluteOnAnyPlatform _
= False
570 -- | @isRelativeOnAnyPlatform = not . 'isAbsoluteOnAnyPlatform'@
571 isRelativeOnAnyPlatform
:: FilePath -> Bool
572 isRelativeOnAnyPlatform
= not . isAbsoluteOnAnyPlatform
575 -- >>> import Data.Maybe
576 -- >>> import Text.Read