1 {-# LANGUAGE BangPatterns #-}
3 {-# LANGUAGE DeriveGeneric #-}
4 {-# LANGUAGE FlexibleContexts #-}
5 {-# LANGUAGE RankNTypes #-}
6 {-# LANGUAGE ScopedTypeVariables #-}
8 -----------------------------------------------------------------------------
11 -- Module : Distribution.Utils.Generic
12 -- Copyright : Isaac Jones, Simon Marlow 2003-2004
14 -- portions Copyright (c) 2007, Galois Inc.
16 -- Maintainer : cabal-devel@haskell.org
17 -- Portability : portable
19 -- A large and somewhat miscellaneous collection of utility functions used
20 -- throughout the rest of the Cabal lib and in other tools that use the Cabal
21 -- lib like @cabal-install@. It has a very simple set of logging actions. It
22 -- has low level functions for running programs, a bunch of wrappers for
23 -- various directory and file functions that do extra logging.
24 module Distribution
.Utils
.Generic
25 ( -- * reading and writing files safely
40 , withUTF8FileContents
47 , normaliseLineEndings
84 , isAbsoluteOnAnyPlatform
85 , isRelativeOnAnyPlatform
88 import Distribution
.Compat
.Prelude
91 import Distribution
.Utils
.String
93 import Data
.Bits
(shiftL
, (.&.), (.|
.))
94 import qualified Data
.ByteString
as SBS
95 import qualified Data
.ByteString
.Lazy
as LBS
99 import qualified Data
.Set
as Set
101 import qualified Control
.Exception
as Exception
102 import System
.Directory
106 import System
.FilePath
114 , openBinaryTempFileWithDefaultPermissions
119 -- -----------------------------------------------------------------------------
122 -- | Wraps text to the default line width. Existing newlines are preserved.
123 wrapText
:: String -> String
134 -- | Wraps a list of words to a list of lines of words of a particular width.
135 wrapLine
:: Int -> [String] -> [[String]]
136 wrapLine width
= wrap
0 []
138 wrap
:: Int -> [String] -> [String] -> [[String]]
140 |
length w
+ 1 > width
=
141 wrap
(length w
) [w
] ws
142 wrap col line
(w
: ws
)
143 | col
+ length w
+ 1 > width
=
144 reverse line
: wrap
0 [] (w
: ws
)
145 wrap col line
(w
: ws
) =
146 let col
' = col
+ length w
+ 1
147 in wrap col
' (w
: line
) ws
149 wrap _ line
[] = [reverse line
]
151 -----------------------------------
152 -- Safely reading and writing files
154 -- | Gets the contents of a file, but guarantee that it gets closed.
156 -- The file is read lazily but if it is not fully consumed by the action then
157 -- the remaining input is truncated and the file is closed.
158 withFileContents
:: FilePath -> (String -> IO a
) -> IO a
159 withFileContents name action
=
163 (\hnd
-> hGetContents hnd
>>= action
)
165 -- | Writes a file atomically.
167 -- The file is either written successfully or an IO exception is raised and
168 -- the original file is left unchanged.
170 -- On windows it is not possible to delete a file that is open by a process.
171 -- This case will give an IO exception but the atomic property is not affected.
172 writeFileAtomic
:: FilePath -> LBS
.ByteString
-> IO ()
173 writeFileAtomic targetPath content
= do
174 let (targetDir
, targetFile
) = splitFileName targetPath
175 Exception
.bracketOnError
176 (openBinaryTempFileWithDefaultPermissions targetDir
$ targetFile
<.> "tmp")
177 (\(tmpPath
, handle
) -> hClose handle
>> removeFile tmpPath
)
178 ( \(tmpPath
, handle
) -> do
179 LBS
.hPut handle content
181 renameFile tmpPath targetPath
184 -- ------------------------------------------------------------
188 -- ------------------------------------------------------------
190 -- | Decode 'String' from UTF8-encoded 'BS.ByteString'
192 -- Invalid data in the UTF8 stream (this includes code-points @U+D800@
193 -- through @U+DFFF@) will be decoded as the replacement character (@U+FFFD@).
194 fromUTF8BS
:: SBS
.ByteString
-> String
195 fromUTF8BS
= decodeStringUtf8
. SBS
.unpack
197 -- | Variant of 'fromUTF8BS' for lazy 'BS.ByteString's
198 fromUTF8LBS
:: LBS
.ByteString
-> String
199 fromUTF8LBS
= decodeStringUtf8
. LBS
.unpack
201 -- | Encode 'String' to UTF8-encoded 'SBS.ByteString'
203 -- Code-points in the @U+D800@-@U+DFFF@ range will be encoded
204 -- as the replacement character (i.e. @U+FFFD@).
205 toUTF8BS
:: String -> SBS
.ByteString
206 toUTF8BS
= SBS
.pack
. encodeStringUtf8
208 -- | Variant of 'toUTF8BS' for lazy 'BS.ByteString's
209 toUTF8LBS
:: String -> LBS
.ByteString
210 toUTF8LBS
= LBS
.pack
. encodeStringUtf8
212 -- | Check that strict 'ByteString' is valid UTF8. Returns 'Just offset' if it's not.
213 validateUTF8
:: SBS
.ByteString
-> Maybe Int
216 go off bs
= case SBS
.uncons bs
of
219 | c
<= 0x7F -> go
(off
+ 1) bs
'
220 | c
<= 0xBF -> Just off
221 | c
<= 0xDF -> twoBytes off c bs
'
222 | c
<= 0xEF -> moreBytes off
3 0x800 bs
' (fromIntegral $ c
.&. 0xF)
223 | c
<= 0xF7 -> moreBytes off
4 0x10000 bs
' (fromIntegral $ c
.&. 0x7)
224 | c
<= 0xFB -> moreBytes off
5 0x200000 bs
' (fromIntegral $ c
.&. 0x3)
225 | c
<= 0xFD -> moreBytes off
6 0x4000000 bs
' (fromIntegral $ c
.&. 0x1)
226 |
otherwise -> Just off
228 twoBytes off c0 bs
= case SBS
.uncons bs
of
231 | c1
.&. 0xC0 == 0x80 ->
232 if d
>= (0x80 :: Int)
233 then go
(off
+ 2) bs
'
235 |
otherwise -> Just off
237 d
= (fromIntegral (c0
.&. 0x1F) `shiftL`
6) .|
. fromIntegral (c1
.&. 0x3F)
239 moreBytes
:: Int -> Int -> Int -> SBS
.ByteString
-> Int -> Maybe Int
240 moreBytes off
1 overlong cs
' acc
243 , acc
< 0xD800 ||
0xDFFF < acc
=
247 moreBytes off byteCount overlong bs acc
= case SBS
.uncons bs
of
249 | cn
.&. 0xC0 == 0x80 ->
250 moreBytes
(off
+ 1) (byteCount
- 1) overlong bs
' ((acc `shiftL`
6) .|
. fromIntegral cn
.&. 0x3F)
253 -- | Ignore a Unicode byte order mark (BOM) at the beginning of the input
254 ignoreBOM
:: String -> String
255 ignoreBOM
('\xFEFF
' : string) = string
256 ignoreBOM
string = string
258 -- | Reads a UTF8 encoded text file as a Unicode String
260 -- Reads lazily using ordinary 'readFile'.
261 readUTF8File
:: FilePath -> IO String
262 readUTF8File f
= (ignoreBOM
. fromUTF8LBS
) <$> LBS
.readFile f
264 -- | Reads a UTF8 encoded text file as a Unicode String
266 -- Same behaviour as 'withFileContents'.
267 withUTF8FileContents
:: FilePath -> (String -> IO a
) -> IO a
268 withUTF8FileContents name action
=
272 (\hnd
-> LBS
.hGetContents hnd
>>= action
. ignoreBOM
. fromUTF8LBS
)
274 -- | Writes a Unicode String as a UTF8 encoded text file.
276 -- Uses 'writeFileAtomic', so provides the same guarantees.
277 writeUTF8File
:: FilePath -> String -> IO ()
278 writeUTF8File path
= writeFileAtomic path
. toUTF8LBS
280 -- | Fix different systems silly line ending conventions
281 normaliseLineEndings
:: String -> String
282 normaliseLineEndings
[] = []
283 normaliseLineEndings
('\r' : '\n' : s
) = '\n' : normaliseLineEndings s
-- windows
284 normaliseLineEndings
('\r' : s
) = '\n' : normaliseLineEndings s
-- old OS X
285 normaliseLineEndings
(c
: s
) = c
: normaliseLineEndings s
287 -- ------------------------------------------------------------
291 -- ------------------------------------------------------------
293 -- | @dropWhileEndLE p@ is equivalent to @reverse . dropWhile p . reverse@, but
294 -- quite a bit faster. The difference between "Data.List.dropWhileEnd" and this
295 -- version is that the one in "Data.List" is strict in elements, but spine-lazy,
296 -- while this one is spine-strict but lazy in elements. That's what @LE@ stands
297 -- for - "lazy in elements".
301 -- >>> safeTail $ Data.List.dropWhileEnd (<3) [undefined, 5, 4, 3, 2, 1]
302 -- *** Exception: Prelude.undefined
305 -- >>> safeTail $ dropWhileEndLE (<3) [undefined, 5, 4, 3, 2, 1]
308 -- >>> take 3 $ Data.List.dropWhileEnd (<3) [5, 4, 3, 2, 1, undefined]
311 -- >>> take 3 $ dropWhileEndLE (<3) [5, 4, 3, 2, 1, undefined]
312 -- *** Exception: Prelude.undefined
314 dropWhileEndLE
:: (a
-> Bool) -> [a
] -> [a
]
315 dropWhileEndLE p
= foldr (\x r
-> if null r
&& p x
then [] else x
: r
) []
317 -- | @takeWhileEndLE p@ is equivalent to @reverse . takeWhile p . reverse@, but
318 -- is usually faster (as well as being easier to read).
319 takeWhileEndLE
:: (a
-> Bool) -> [a
] -> [a
]
320 takeWhileEndLE p
= fst . foldr go
([], False)
323 |
not done
&& p x
= (x
: rest
, False)
324 |
otherwise = (rest
, True)
326 -- | Like 'Data.List.nub', but has @O(n log n)@ complexity instead of
327 -- @O(n^2)@. Code for 'ordNub' and 'listUnion' taken from Niklas Hambรผchen's
328 -- <http://github.com/nh2/haskell-ordnub ordnub> package.
329 ordNub
:: Ord a
=> [a
] -> [a
]
332 -- | Like 'ordNub' and 'Data.List.nubBy'. Selects a key for each element and
333 -- takes the nub based on that key.
334 ordNubBy
:: Ord b
=> (a
-> b
) -> [a
] -> [a
]
335 ordNubBy f l
= go Set
.empty l
339 | y `Set
.member` s
= go s xs
341 let !s
' = Set
.insert y s
346 -- | Like "Data.List.union", but has @O(n log n)@ complexity instead of
348 listUnion
:: Ord a
=> [a
] -> [a
] -> [a
]
349 listUnion a b
= a
++ ordNub
(filter (`Set
.notMember` aSet
) b
)
351 aSet
= Set
.fromList a
353 -- | A right-biased version of 'ordNub'.
357 -- >>> ordNub [1,2,1] :: [Int]
360 -- >>> ordNubRight [1,2,1] :: [Int]
362 ordNubRight
:: Ord a
=> [a
] -> [a
]
363 ordNubRight
= fst . foldr go
([], Set
.empty)
368 else (x
: l
, Set
.insert x s
)
370 -- | A right-biased version of 'listUnion'.
374 -- >>> listUnion [1,2,3,4,3] [2,1,1]
377 -- >>> listUnionRight [1,2,3,4,3] [2,1,1]
379 listUnionRight
:: Ord a
=> [a
] -> [a
] -> [a
]
380 listUnionRight a b
= ordNubRight
(filter (`Set
.notMember` bSet
) a
) ++ b
382 bSet
= Set
.fromList b
384 -- | A total variant of 'head'.
387 safeHead
:: [a
] -> Maybe a
388 safeHead
[] = Nothing
389 safeHead
(x
: _
) = Just x
391 -- | A total variant of 'tail'.
394 safeTail
:: [a
] -> [a
]
396 safeTail
(_
: xs
) = xs
398 -- | A total variant of 'last'.
401 safeLast
:: [a
] -> Maybe a
402 safeLast
[] = Nothing
403 safeLast
(x
: xs
) = Just
(foldl (\_ a
-> a
) x xs
)
405 -- | A total variant of 'init'.
408 safeInit
:: [a
] -> [a
]
411 safeInit
(x
: xs
) = x
: safeInit xs
413 equating
:: Eq a
=> (b
-> a
) -> b
-> b
-> Bool
414 equating p x y
= p x
== p y
416 -- | Lower case string
418 -- >>> lowercase "Foobar"
420 lowercase
:: String -> String
421 lowercase
= map toLower
423 -- | Ascii characters
424 isAscii :: Char -> Bool
425 isAscii c
= fromEnum c
< 0x80
428 isAsciiAlpha
:: Char -> Bool
430 ('a
' <= c
&& c
<= 'z
')
431 ||
('A
' <= c
&& c
<= 'Z
')
433 -- | Ascii letters and digits.
435 -- >>> isAsciiAlphaNum 'a'
438 -- >>> isAsciiAlphaNum 'รค'
440 isAsciiAlphaNum
:: Char -> Bool
441 isAsciiAlphaNum c
= isAscii c
&& isAlphaNum c
443 unintersperse
:: Char -> String -> [String]
444 unintersperse mark
= unfoldr unintersperse1
449 let (this
, rest
) = break (== mark
) str
450 in Just
(this
, safeTail rest
)
452 -- | Like 'break', but with 'Maybe' predicate
454 -- >>> breakMaybe (readMaybe :: String -> Maybe Int) ["foo", "bar", "1", "2", "quu"]
455 -- (["foo","bar"],Just (1,["2","quu"]))
457 -- >>> breakMaybe (readMaybe :: String -> Maybe Int) ["foo", "bar"]
458 -- (["foo","bar"],Nothing)
461 breakMaybe
:: (a
-> Maybe b
) -> [a
] -> ([a
], Maybe (b
, [a
]))
464 go
!acc
[] = (acc
[], Nothing
)
465 go
!acc
(x
: xs
) = case f x
of
466 Nothing
-> go
(acc
. (x
:)) xs
467 Just b
-> (acc
[], Just
(b
, xs
))
469 -- | Like 'span' but with 'Maybe' predicate
471 -- >>> spanMaybe listToMaybe [[1,2],[3],[],[4,5],[6,7]]
472 -- ([1,3],[[],[4,5],[6,7]])
474 -- >>> spanMaybe (readMaybe :: String -> Maybe Int) ["1", "2", "foo"]
478 spanMaybe
:: (a
-> Maybe b
) -> [a
] -> ([b
], [a
])
479 spanMaybe _ xs
@[] = ([], xs
)
480 spanMaybe p xs
@(x
: xs
') = case p x
of
481 Just y
-> let (ys
, zs
) = spanMaybe p xs
' in (y
: ys
, zs
)
484 -- | 'unfoldr' with monadic action.
486 -- >>> take 5 $ unfoldrM (\b r -> Just (r + b, b + 1)) (1 :: Int) 2
490 unfoldrM
:: Monad m
=> (b
-> m
(Maybe (a
, b
))) -> b
-> m
[a
]
497 Just
(a
, b
') -> liftM (a
:) (go b
')
499 -- | The opposite of 'snoc', which is the reverse of 'cons'
503 -- >>> unsnoc [1, 2, 3]
510 unsnoc
:: [a
] -> Maybe ([a
], a
)
512 unsnoc
(x
: xs
) = Just
(unsnocNE
(x
:| xs
))
514 -- | Like 'unsnoc', but for 'NonEmpty' so without the 'Maybe'
518 -- >>> unsnocNE (1 :| [2, 3])
521 -- >>> unsnocNE (1 :| [])
525 unsnocNE
:: NonEmpty a
-> ([a
], a
)
526 unsnocNE
(x
:| xs
) = go x xs
529 go y
(z
: zs
) = let ~
(ws
, w
) = go z zs
in (y
: ws
, w
)
531 -------------------------------------------------------------------------------
533 -------------------------------------------------------------------------------
536 fstOf3
:: (a
, b
, c
) -> a
540 sndOf3
:: (a
, b
, c
) -> b
544 trdOf3
:: (a
, b
, c
) -> c
547 -- ------------------------------------------------------------
551 -- ------------------------------------------------------------
553 -- | 'isAbsoluteOnAnyPlatform' and 'isRelativeOnAnyPlatform' are like
554 -- 'System.FilePath.isAbsolute' and 'System.FilePath.isRelative' but have
555 -- platform independent heuristics.
556 -- The System.FilePath exists in two versions, Windows and Posix. The two
557 -- versions don't agree on what is a relative path and we don't know if we're
558 -- given Windows or Posix paths.
559 -- This results in false positives when running on Posix and inspecting
560 -- Windows paths, like the hackage server does.
561 -- System.FilePath.Posix.isAbsolute \"C:\\hello\" == False
562 -- System.FilePath.Windows.isAbsolute \"/hello\" == False
563 -- This means that we would treat paths that start with \"/\" to be absolute.
564 -- On Posix they are indeed absolute, while on Windows they are not.
566 -- The portable versions should be used when we might deal with paths that
567 -- are from another OS than the host OS. For example, the Hackage Server
568 -- deals with both Windows and Posix paths while performing the
569 -- PackageDescription checks. In contrast, when we run 'cabal configure' we
570 -- do expect the paths to be correct for our OS and we should not have to use
571 -- the platform independent heuristics.
572 isAbsoluteOnAnyPlatform
:: FilePath -> Bool
574 isAbsoluteOnAnyPlatform
(drive
: ':' : '\\' : _
) = isAlpha drive
575 isAbsoluteOnAnyPlatform
(drive
: ':' : '/' : _
) = isAlpha drive
577 isAbsoluteOnAnyPlatform
('\\' : '\\' : _
) = True
579 isAbsoluteOnAnyPlatform
('/' : _
) = True
580 isAbsoluteOnAnyPlatform _
= False
582 -- | @isRelativeOnAnyPlatform = not . 'isAbsoluteOnAnyPlatform'@
583 isRelativeOnAnyPlatform
:: FilePath -> Bool
584 isRelativeOnAnyPlatform
= not . isAbsoluteOnAnyPlatform
587 -- >>> import Data.Maybe
588 -- >>> import Text.Read