Partially improve the cabalisation
This commit is contained in:
504
System/Plugins/Utils.hs
Normal file
504
System/Plugins/Utils.hs
Normal file
@ -0,0 +1,504 @@
|
||||
{-# OPTIONS -cpp #-}
|
||||
--
|
||||
-- Copyright (C) 2004 Don Stewart - http://www.cse.unsw.edu.au/~dons
|
||||
--
|
||||
-- This library is free software; you can redistribute it and/or
|
||||
-- modify it under the terms of the GNU Lesser General Public
|
||||
-- License as published by the Free Software Foundation; either
|
||||
-- version 2.1 of the License, or (at your option) any later version.
|
||||
--
|
||||
-- This library is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
-- Lesser General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU Lesser General Public
|
||||
-- License along with this library; if not, write to the Free Software
|
||||
-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||||
-- USA
|
||||
--
|
||||
|
||||
module System.Plugins.Utils (
|
||||
Arg,
|
||||
|
||||
hWrite,
|
||||
|
||||
mkUnique,
|
||||
hMkUnique,
|
||||
mkUniqueIn,
|
||||
hMkUniqueIn,
|
||||
|
||||
findFile,
|
||||
|
||||
mkTemp, mkTempIn, {- internal -}
|
||||
|
||||
replaceSuffix,
|
||||
outFilePath,
|
||||
dropSuffix,
|
||||
mkModid,
|
||||
changeFileExt,
|
||||
joinFileExt,
|
||||
splitFileExt,
|
||||
|
||||
isSublistOf, -- :: Eq a => [a] -> [a] -> Bool
|
||||
|
||||
dirname,
|
||||
basename,
|
||||
|
||||
(</>), (<.>), (<+>), (<>),
|
||||
|
||||
newer,
|
||||
|
||||
encode,
|
||||
decode,
|
||||
EncodedString,
|
||||
|
||||
panic
|
||||
|
||||
) where
|
||||
|
||||
#include "../../config.h"
|
||||
|
||||
import System.Plugins.Env ( isLoaded )
|
||||
import System.Plugins.Consts ( objSuf, hiSuf, tmpDir )
|
||||
import qualified System.MkTemp ( mkstemps )
|
||||
|
||||
import Data.Char
|
||||
import Data.List
|
||||
|
||||
import System.IO
|
||||
import System.Environment ( getEnv )
|
||||
import System.Directory
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- some misc types we use
|
||||
|
||||
type Arg = String
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | useful
|
||||
--
|
||||
panic s = ioError ( userError s )
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | writeFile for Handles
|
||||
--
|
||||
hWrite :: Handle -> String -> IO ()
|
||||
hWrite hdl src = hPutStr hdl src >> hClose hdl >> return ()
|
||||
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | mkstemps.
|
||||
--
|
||||
-- We use the Haskell version now... it is faster than calling into
|
||||
-- mkstemps(3).
|
||||
--
|
||||
|
||||
mkstemps :: String -> Int -> IO (String,Handle)
|
||||
mkstemps path slen = do
|
||||
m_v <- System.MkTemp.mkstemps path slen
|
||||
case m_v of Nothing -> error "mkstemps : couldn't create temp file"
|
||||
Just v' -> return v'
|
||||
|
||||
{-
|
||||
|
||||
mkstemps path slen = do
|
||||
withCString path $ \ ptr -> do
|
||||
let c_slen = fromIntegral $ slen+1
|
||||
fd <- throwErrnoIfMinus1 "mkstemps" $ c_mkstemps ptr c_slen
|
||||
name <- peekCString ptr
|
||||
hdl <- fdToHandle fd
|
||||
return (name, hdl)
|
||||
|
||||
foreign import ccall unsafe "mkstemps" c_mkstemps :: CString -> CInt -> IO Fd
|
||||
|
||||
-}
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | create a new temp file, returning name and handle.
|
||||
-- bit like the mktemp shell utility
|
||||
--
|
||||
mkTemp :: IO (String,Handle)
|
||||
mkTemp = do tmpd <- catch (getEnv "TMPDIR") (\_ -> return tmpDir)
|
||||
mkTempIn tmpd
|
||||
|
||||
mkTempIn :: String -> IO (String, Handle)
|
||||
mkTempIn tmpd = do
|
||||
(tmpf,hdl) <- mkstemps (tmpd++"/MXXXXXXXXX.hs") 3
|
||||
let modname = mkModid $ dropSuffix tmpf
|
||||
if and $ map (\c -> isAlphaNum c && c /= '_') modname
|
||||
then return (tmpf,hdl)
|
||||
else panic $ "Illegal characters in temp file: `"++tmpf++"'"
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- | Get a new temp file, unique from those in /tmp, and from those
|
||||
-- modules already loaded. Very nice for merge/eval uses.
|
||||
--
|
||||
-- Will run for a long time if we can't create a temp file, luckily
|
||||
-- mkstemps gives us a pretty big search space
|
||||
--
|
||||
mkUnique :: IO FilePath
|
||||
mkUnique = do (t,h) <- hMkUnique
|
||||
hClose h >> return t
|
||||
|
||||
hMkUnique :: IO (FilePath,Handle)
|
||||
hMkUnique = do (t,h) <- mkTemp
|
||||
alreadyLoaded <- isLoaded t -- not unique!
|
||||
if alreadyLoaded
|
||||
then hClose h >> removeFile t >> hMkUnique
|
||||
else return (t,h)
|
||||
|
||||
mkUniqueIn :: FilePath -> IO FilePath
|
||||
mkUniqueIn dir = do (t,h) <- hMkUniqueIn dir
|
||||
hClose h >> return t
|
||||
|
||||
hMkUniqueIn :: FilePath -> IO (FilePath,Handle)
|
||||
hMkUniqueIn dir = do (t,h) <- mkTempIn dir
|
||||
alreadyLoaded <- isLoaded t -- not unique!
|
||||
if alreadyLoaded
|
||||
then hClose h >> removeFile t >> hMkUniqueIn dir
|
||||
else return (t,h)
|
||||
|
||||
findFile :: [String] -> FilePath -> IO (Maybe FilePath)
|
||||
findFile [] _ = return Nothing
|
||||
findFile (ext:exts) file
|
||||
= do let l = changeFileExt file ext
|
||||
b <- doesFileExist l
|
||||
if b then return $ Just l
|
||||
else findFile exts file
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
-- some filename manipulation stuff
|
||||
|
||||
--
|
||||
-- | </>, <.> : join two path components
|
||||
--
|
||||
infixr 6 </>
|
||||
infixr 6 <.>
|
||||
|
||||
(</>), (<.>), (<+>), (<>) :: FilePath -> FilePath -> FilePath
|
||||
[] </> b = b
|
||||
a </> b = a ++ "/" ++ b
|
||||
|
||||
[] <.> b = b
|
||||
a <.> b = a ++ "." ++ b
|
||||
|
||||
[] <+> b = b
|
||||
a <+> b = a ++ " " ++ b
|
||||
|
||||
[] <> b = b
|
||||
a <> b = a ++ b
|
||||
|
||||
--
|
||||
-- | dirname : return the directory portion of a file path
|
||||
-- if null, return "."
|
||||
--
|
||||
dirname :: FilePath -> FilePath
|
||||
dirname p =
|
||||
let x = findIndices (== '\\') p
|
||||
y = findIndices (== '/') p
|
||||
in
|
||||
if not $ null x
|
||||
then if not $ null y
|
||||
then if (maximum x) > (maximum y) then dirname' '\\' p else dirname' '/' p
|
||||
else dirname' '\\' p
|
||||
else dirname' '/' p
|
||||
where
|
||||
dirname' chara pa =
|
||||
case reverse $ dropWhile (/= chara) $ reverse pa of
|
||||
[] -> "."
|
||||
pa' -> pa'
|
||||
|
||||
--
|
||||
-- | basename : return the filename portion of a path
|
||||
--
|
||||
basename :: FilePath -> FilePath
|
||||
basename p =
|
||||
let x = findIndices (== '\\') p
|
||||
y = findIndices (== '/') p
|
||||
in
|
||||
if not $ null x
|
||||
then if not $ null y
|
||||
then if (maximum x) > (maximum y) then basename' '\\' p else basename' '/' p
|
||||
else basename' '\\' p
|
||||
else basename' '/' p
|
||||
where
|
||||
basename' chara pa = reverse $ takeWhile (/= chara) $ reverse pa
|
||||
|
||||
--
|
||||
-- drop suffix
|
||||
--
|
||||
dropSuffix :: FilePath -> FilePath
|
||||
dropSuffix f = reverse . tail . dropWhile (/= '.') $ reverse f
|
||||
|
||||
--
|
||||
-- | work out the mod name from a filepath
|
||||
mkModid :: String -> String
|
||||
mkModid = (takeWhile (/= '.')) . reverse . (takeWhile (\x -> ('/'/= x) && ('\\' /= x))) . reverse
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
-- Code from Cabal ----------------------------------------
|
||||
|
||||
-- | Changes the extension of a file path.
|
||||
changeFileExt :: FilePath -- ^ The path information to modify.
|
||||
-> String -- ^ The new extension (without a leading period).
|
||||
-- Specify an empty string to remove an existing
|
||||
-- extension from path.
|
||||
-> FilePath -- ^ A string containing the modified path information.
|
||||
changeFileExt fpath ext = joinFileExt name ext
|
||||
where
|
||||
(name,_) = splitFileExt fpath
|
||||
|
||||
-- | The 'joinFileExt' function is the opposite of 'splitFileExt'.
|
||||
-- It joins a file name and an extension to form a complete file path.
|
||||
--
|
||||
-- The general rule is:
|
||||
--
|
||||
-- > filename `joinFileExt` ext == path
|
||||
-- > where
|
||||
-- > (filename,ext) = splitFileExt path
|
||||
joinFileExt :: String -> String -> FilePath
|
||||
joinFileExt fpath "" = fpath
|
||||
joinFileExt fpath ext = fpath ++ '.':ext
|
||||
|
||||
-- | Split the path into file name and extension. If the file doesn\'t have extension,
|
||||
-- the function will return empty string. The extension doesn\'t include a leading period.
|
||||
--
|
||||
-- Examples:
|
||||
--
|
||||
-- > splitFileExt "foo.ext" == ("foo", "ext")
|
||||
-- > splitFileExt "foo" == ("foo", "")
|
||||
-- > splitFileExt "." == (".", "")
|
||||
-- > splitFileExt ".." == ("..", "")
|
||||
-- > splitFileExt "foo.bar."== ("foo.bar.", "")
|
||||
splitFileExt :: FilePath -> (String, String)
|
||||
splitFileExt p =
|
||||
case break (== '.') fname of
|
||||
(suf@(_:_),_:pre) -> (reverse (pre++fpath), reverse suf)
|
||||
_ -> (p, [])
|
||||
where
|
||||
(fname,fpath) = break isPathSeparator (reverse p)
|
||||
|
||||
-- | Checks whether the character is a valid path separator for the host
|
||||
-- platform. The valid character is a 'pathSeparator' but since the Windows
|
||||
-- operating system also accepts a slash (\"\/\") since DOS 2, the function
|
||||
-- checks for it on this platform, too.
|
||||
isPathSeparator :: Char -> Bool
|
||||
isPathSeparator ch =
|
||||
#if defined(CYGWIN) || defined(__MINGW32__)
|
||||
ch == '/' || ch == '\\'
|
||||
#else
|
||||
ch == '/'
|
||||
#endif
|
||||
|
||||
-- Code from Cabal end ------------------------------------
|
||||
-----------------------------------------------------------
|
||||
|
||||
|
||||
-- | return the object file, given the .conf file
|
||||
-- i.e. /home/dons/foo.rc -> /home/dons/foo.o
|
||||
--
|
||||
-- we depend on the suffix we are given having a lead '.'
|
||||
--
|
||||
replaceSuffix :: FilePath -> String -> FilePath
|
||||
replaceSuffix [] _ = [] -- ?
|
||||
replaceSuffix f suf =
|
||||
case reverse $ dropWhile (/= '.') $ reverse f of
|
||||
[] -> f ++ suf -- no '.' in file name
|
||||
f' -> f' ++ tail suf
|
||||
|
||||
--
|
||||
-- Normally we create the .hi and .o files next to the .hs files.
|
||||
-- For some uses this is annoying (i.e. true EDSL users don't actually
|
||||
-- want to know that their code is compiled at all), and for hmake-like
|
||||
-- applications.
|
||||
--
|
||||
-- This code checks if "-o foo" or "-odir foodir" are supplied as args
|
||||
-- to make(), and if so returns a modified file path, otherwise it
|
||||
-- uses the source file to determing the path to where the object and
|
||||
-- .hi file will be put.
|
||||
--
|
||||
outFilePath :: FilePath -> [Arg] -> (FilePath,FilePath)
|
||||
outFilePath src args =
|
||||
let objs = find_o args -- user sets explicit object path
|
||||
paths = find_p args -- user sets a directory to put stuff in
|
||||
in case () of { _
|
||||
| not (null objs)
|
||||
-> let obj = last objs in (obj, mk_hi obj)
|
||||
|
||||
| not (null paths)
|
||||
-> let obj = last paths </> mk_o (basename src) in (obj, mk_hi obj)
|
||||
|
||||
| otherwise
|
||||
-> (mk_o src, mk_hi src)
|
||||
}
|
||||
where
|
||||
outpath = "-o"
|
||||
outdir = "-odir"
|
||||
|
||||
mk_hi s = replaceSuffix s hiSuf
|
||||
mk_o s = replaceSuffix s objSuf
|
||||
|
||||
find_o [] = []
|
||||
find_o (f:f':fs) | f == outpath = [f']
|
||||
| otherwise = find_o $! f':fs
|
||||
find_o _ = []
|
||||
|
||||
find_p [] = []
|
||||
find_p (f:f':fs) | f == outdir = [f']
|
||||
| otherwise = find_p $! f':fs
|
||||
find_p _ = []
|
||||
|
||||
------------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- | is file1 newer than file2?
|
||||
--
|
||||
-- needs some fixing to work with 6.0.x series. (is this true?)
|
||||
--
|
||||
-- fileExist still seems to throw exceptions on some platforms: ia64 in
|
||||
-- particular.
|
||||
--
|
||||
-- invarient : we already assume the first file, 'a', exists
|
||||
--
|
||||
newer :: FilePath -> FilePath -> IO Bool
|
||||
newer a b = do
|
||||
a_t <- getModificationTime a
|
||||
b_exists <- doesFileExist b
|
||||
if not b_exists
|
||||
then return True -- needs compiling
|
||||
else do b_t <- getModificationTime b
|
||||
return ( a_t > b_t ) -- maybe need recompiling
|
||||
|
||||
------------------------------------------------------------------------
|
||||
--
|
||||
-- | return the Z-Encoding of the string.
|
||||
--
|
||||
-- Stolen from GHC. Use -package ghc as soon as possible
|
||||
--
|
||||
type EncodedString = String
|
||||
|
||||
encode :: String -> EncodedString
|
||||
encode [] = []
|
||||
encode (c:cs) = encode_ch c ++ encode cs
|
||||
|
||||
unencodedChar :: Char -> Bool -- True for chars that don't need encoding
|
||||
unencodedChar 'Z' = False
|
||||
unencodedChar 'z' = False
|
||||
unencodedChar c = c >= 'a' && c <= 'z'
|
||||
|| c >= 'A' && c <= 'Z'
|
||||
|| c >= '0' && c <= '9'
|
||||
|
||||
--
|
||||
-- Decode is used for user printing.
|
||||
--
|
||||
decode :: EncodedString -> String
|
||||
decode [] = []
|
||||
decode ('Z' : d : rest) | isDigit d = decode_tuple d rest
|
||||
| otherwise = decode_upper d : decode rest
|
||||
decode ('z' : d : rest) | isDigit d = decode_num_esc d rest
|
||||
| otherwise = decode_lower d : decode rest
|
||||
decode (c : rest) = c : decode rest
|
||||
|
||||
decode_upper, decode_lower :: Char -> Char
|
||||
|
||||
decode_upper 'L' = '('
|
||||
decode_upper 'R' = ')'
|
||||
decode_upper 'M' = '['
|
||||
decode_upper 'N' = ']'
|
||||
decode_upper 'C' = ':'
|
||||
decode_upper 'Z' = 'Z'
|
||||
decode_upper ch = error $ "decode_upper can't handle this char `"++[ch]++"'"
|
||||
|
||||
decode_lower 'z' = 'z'
|
||||
decode_lower 'a' = '&'
|
||||
decode_lower 'b' = '|'
|
||||
decode_lower 'c' = '^'
|
||||
decode_lower 'd' = '$'
|
||||
decode_lower 'e' = '='
|
||||
decode_lower 'g' = '>'
|
||||
decode_lower 'h' = '#'
|
||||
decode_lower 'i' = '.'
|
||||
decode_lower 'l' = '<'
|
||||
decode_lower 'm' = '-'
|
||||
decode_lower 'n' = '!'
|
||||
decode_lower 'p' = '+'
|
||||
decode_lower 'q' = '\''
|
||||
decode_lower 'r' = '\\'
|
||||
decode_lower 's' = '/'
|
||||
decode_lower 't' = '*'
|
||||
decode_lower 'u' = '_'
|
||||
decode_lower 'v' = '%'
|
||||
decode_lower ch = error $ "decode_lower can't handle this char `"++[ch]++"'"
|
||||
|
||||
-- Characters not having a specific code are coded as z224U
|
||||
decode_num_esc :: Char -> [Char] -> String
|
||||
decode_num_esc d cs
|
||||
= go (digitToInt d) cs
|
||||
where
|
||||
go n (c : rest) | isDigit c = go (10*n + digitToInt c) rest
|
||||
go n ('U' : rest) = chr n : decode rest
|
||||
go _ other = error $
|
||||
"decode_num_esc can't handle this: \""++other++"\""
|
||||
|
||||
|
||||
encode_ch :: Char -> EncodedString
|
||||
encode_ch c | unencodedChar c = [c] -- Common case first
|
||||
|
||||
-- Constructors
|
||||
encode_ch '(' = "ZL" -- Needed for things like (,), and (->)
|
||||
encode_ch ')' = "ZR" -- For symmetry with (
|
||||
encode_ch '[' = "ZM"
|
||||
encode_ch ']' = "ZN"
|
||||
encode_ch ':' = "ZC"
|
||||
encode_ch 'Z' = "ZZ"
|
||||
|
||||
-- Variables
|
||||
encode_ch 'z' = "zz"
|
||||
encode_ch '&' = "za"
|
||||
encode_ch '|' = "zb"
|
||||
encode_ch '^' = "zc"
|
||||
encode_ch '$' = "zd"
|
||||
encode_ch '=' = "ze"
|
||||
encode_ch '>' = "zg"
|
||||
encode_ch '#' = "zh"
|
||||
encode_ch '.' = "zi"
|
||||
encode_ch '<' = "zl"
|
||||
encode_ch '-' = "zm"
|
||||
encode_ch '!' = "zn"
|
||||
encode_ch '+' = "zp"
|
||||
encode_ch '\'' = "zq"
|
||||
encode_ch '\\' = "zr"
|
||||
encode_ch '/' = "zs"
|
||||
encode_ch '*' = "zt"
|
||||
encode_ch '_' = "zu"
|
||||
encode_ch '%' = "zv"
|
||||
encode_ch c = 'z' : shows (ord c) "U"
|
||||
|
||||
decode_tuple :: Char -> EncodedString -> String
|
||||
decode_tuple d cs
|
||||
= go (digitToInt d) cs
|
||||
where
|
||||
go n (c : rest) | isDigit c = go (10*n + digitToInt c) rest
|
||||
go 0 ['T'] = "()"
|
||||
go n ['T'] = '(' : replicate (n-1) ',' ++ ")"
|
||||
go 1 ['H'] = "(# #)"
|
||||
go n ['H'] = '(' : '#' : replicate (n-1) ',' ++ "#)"
|
||||
go _ other = error $ "decode_tuple \'"++other++"'"
|
||||
|
||||
-- ---------------------------------------------------------------------
|
||||
|
||||
--
|
||||
-- 'isSublistOf' takes two arguments and returns 'True' iff the first
|
||||
-- list is a sublist of the second list. This means that the first list
|
||||
-- is wholly contained within the second list. Both lists must be
|
||||
-- finite.
|
||||
|
||||
isSublistOf :: Eq a => [a] -> [a] -> Bool
|
||||
isSublistOf [] _ = True
|
||||
isSublistOf _ [] = False
|
||||
isSublistOf x y@(_:ys)
|
||||
| isPrefixOf x y = True
|
||||
| otherwise = isSublistOf x ys
|
||||
|
Reference in New Issue
Block a user