seqaid-0.1.1: Seqaid/Prepro.hs
-------------------------------------------------------------------------------
{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-} -- temporary... (debugging/testing)
#define DBG_OMNI 0
-- Sadly it seem there is a bad interaction between SOP.TH and Seqaid.TH,
-- so at least for the time being, data types you need to force will
-- need to be declared in a module which is not itself harnessed.
#define CAN_MIX_SOP_DERIVING_WITH_SEQAID_TH 0
-------------------------------------------------------------------------------
-- |
-- Module : Seqaid.Prepro
-- Copyright : (c) 2014, Andrew G. Seniuk
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : Andrew Seniuk <rasfar@gmail.com>
-- Stability : provisional
-- Portability : GHC
--
-- Seqaid preprocessor, run via GHC -F.
module Main where
--module Seqaid.Prepro where
import Seqaid.Config
import System.Environment
import Text.Regex.PCRE
import Text.Regex.Base.RegexLike
import Data.List ( foldl' )
import System.Process ( system ) -- debugging only
import GHC.Exts ( sortWith )
import Data.List ( groupBy )
import Data.List ( foldl1' )
import Data.List ( intercalate )
import Data.List ( nub )
import Data.List ( sort )
import Data.List ( group )
import Data.Maybe
import Control.Concurrent ( threadDelay )
--import System.Directory ( removeFile )
import System.Directory ( createDirectoryIfMissing )
import System.Directory ( getTemporaryDirectory )
import System.Directory ( doesFileExist )
import Distribution.PackageDescription
import Distribution.PackageDescription.Parse
import Distribution.Verbosity
import Distribution.Compiler
import Data.Char ( toUpper )
import Data.Char ( isSpace )
import System.IO ( openFile, IOMode(ReadMode), hFileSize, hClose )
-------------------------------------------------------------------------------
data SeqaidConfig
= SeqaidConfig {
seqaid_cfg_package :: String
, seqaid_cfg_modules :: [SeqaidConfigModule]
, seqaid_cfg_instances :: [(String,[String])]
} deriving ( Show )
data SeqaidConfigModule
= SeqaidConfigModule {
seqaid_cfg_fqname :: String
, seqaid_cfg_types :: [String]
, seqaid_cfg_binds :: [String]
} deriving ( Show )
-------------------------------------------------------------------------------
breakOn :: Char -> String -> String -> [String] -> [String]
breakOn c [] sacc acc
| null sacc = reverse acc
| otherwise = reverse (reverse sacc:acc)
breakOn c (h:t) sacc acc
| h == c = breakOn c t [] (reverse sacc:acc)
| otherwise = breakOn c t (h:sacc) acc
trim :: String -> String
trim s = reverse $ dropWhile (==' ') $ reverse $ dropWhile (==' ') s
parseTypes :: String -> [String]
parseTypes s = map trim $ breakOn ',' s [] []
parseBinds :: String -> [String]
parseBinds s = map trim $ breakOn ',' s [] []
parseInstancesStr :: String -> [(String,[String])]
parseInstancesStr s = parseInstances mts
where
mts = parseInstances' s :: [(String,String)]
parseInstances' :: String -> [(String,String)]
parseInstances' s = map splitFQN $ map trim $ breakOn ',' s [] []
parseInstances :: [(String,String)] -> [(String,[String])]
parseInstances mts = mts'''
where
mts' = sortWith snd mts :: [(String,String)]
mts'' = groupBy (\(x1,y1) (x2,y2) -> x1 == x2) mts' :: [[(String,String)]]
mts''' = map (foldl' (\ (_,ys) (x,y) -> (x,ys++[y])) ("",[])) mts'' :: [(String,[String])]
-- XXX I've seen GHC API (or TH?) code for this, no?
splitFQN :: String -> (String,String)
splitFQN s = ( reverse $ drop 1 $ dropWhile (/='.') $ reverse s
, reverse $ takeWhile (/='.') $ reverse s ) -- heh
parseConfigLines :: SeqaidConfig -> [String] -> SeqaidConfig
parseConfigLines config [] = config
parseConfigLines config (l:ls) = config'
where
l' = dropWhile (==' ') l
config'
| null l' = parseConfigLines config ls
| head l' == '#' = parseConfigLines config ls
| head l' == 'p'
= parseConfigLines (config { seqaid_cfg_package = dropWhile (==' ') $ drop 1 $ dropWhile (/=' ') l' }) ls
| head l' == 'm'
= parseConfigLines (config { seqaid_cfg_modules = seqaid_cfg_modules config ++ [ SeqaidConfigModule { seqaid_cfg_fqname = dropWhile (==' ') $ drop 1 $ dropWhile (/=' ') l' , seqaid_cfg_types = [], seqaid_cfg_binds = [] } ] }) ls
| head l' == 't'
= let modules = seqaid_cfg_modules config in
if null modules then error "#1 invalid seqaid.config"
else let last_module = last modules in
parseConfigLines (config { seqaid_cfg_modules = init modules ++ [ last_module { seqaid_cfg_types = parseTypes $ dropWhile (==' ') $ drop 1 $ dropWhile (/=' ') l' } ] }) ls
| head l' == 'b'
= let modules = seqaid_cfg_modules config in
if null modules then error "#1 invalid seqaid.config"
else let last_module = last modules in
parseConfigLines (config { seqaid_cfg_modules = init modules ++ [ last_module { seqaid_cfg_binds = parseBinds $ dropWhile (==' ') $ drop 1 $ dropWhile (/=' ') l' } ] }) ls
| head l' == 'i'
= parseConfigLines (config { seqaid_cfg_instances = parseInstancesStr $ dropWhile (==' ') $ drop 1 $ dropWhile (/=' ') l' }) ls
| otherwise = error "#2 invalid seqaid.config"
parseConfig :: IO SeqaidConfig
parseConfig = do
config_s <- readFile "seqaid.config"
let config_dflt = SeqaidConfig { seqaid_cfg_package = "", seqaid_cfg_modules = [], seqaid_cfg_instances = [] }
let config_dflt' = parseConfigLines config_dflt $ filter (not . isBlankLine) $ lines config_s
return config_dflt'
where
isBlankLine :: String -> Bool
isBlankLine s = null $ filter (not . isSpace) s
lookupModule :: String -> SeqaidConfig -> SeqaidConfigModule
lookupModule modname config = configmod
where
configmod = lookupModule' modname $ seqaid_cfg_modules config
lookupModule' :: String -> [SeqaidConfigModule] -> SeqaidConfigModule
lookupModule' modname [] = error "#3 invalid seqaid.config"
lookupModule' modname (h@(SeqaidConfigModule fqname types binds):t)
| modname == fqname = h
| otherwise = lookupModule' modname t
-------------------------------------------------------------------------------
main = do
args <- getArgs
if length args < 3
then error "takes a minimum of 3 arguments (should not be run manually!)"
else do
let origname = args!!0
let infile = args!!1
let outfile = args!!2
#if 0
ttt <- doesFileExist outfile
if ttt
then do
putStrLn "exists"
h <- openFile "outfile" ReadMode
siz <- hFileSize h
hClose h
putStrLn $ "File size is " ++ show siz
else putStrLn "doesn't exist"
error "DEVEXIT"
#else
outfile_already_exists <- doesFileExist outfile
okay_to_proceed
<- if outfile_already_exists
then do
h <- openFile "outfile" ReadMode
siz <- hFileSize h
hClose h
if 0 == siz then return True else return False
else return True
if not okay_to_proceed
then error "3rd argument exists (should not be run manually!)"
else return ()
#endif
let opts = drop 3 args
let internal = "internal" `elem` opts
let doomni = "omnitypic" `elem` opts
-- putStrLn $ "origname=" ++ origname ++ "\ninfile=" ++ infile ++ "\noutfile=" ++ outfile ++ "\ninternal=" ++ show internal ++ "\nomnitypic=" ++ show doomni
config@(SeqaidConfig packagename modules instances') <- parseConfig
-- putStrLn $ show config
let instances = instances'
boo@(cppopts_lst,ghcopts_lst) <- parseCabal $ packagename ++ ".cabal"
-- putStrLn $ show ghcopts_lst
#if CAN_MIX_SOP_DERIVING_WITH_SEQAID_TH
do
#else
-- Test that no module mentioned in the "instances:" field
-- is also mentioned in a "module:" field. (Probably a temporary
-- restriction; due to a bad interaction between Generics.SOP.TH
-- and Seqaid.TH. Considering Generics.SOP.TH output fails -dcore-lint,
-- the problem might be a bug in SOP.)
let sane = testSaneConfig config
if not sane then error "seqaid.config is not sane"
else do
#endif
lexmod_ <- readFile infile
let allmats = getAllTextMatches $ lexmod_ =~ "^ *module [^\n][^\n]*" :: [String]
let missing_module_declaration = null allmats
let indent
| missing_module_declaration
= replicate (fromJust $ guessBaseIndentationLevel lexmod_) ' '
--- | missing_module_declaration = ""
| otherwise
= let n = length $ takeWhile (==' ') $ head allmats in
replicate n ' '
let lexmod_mod
| missing_module_declaration
= indent ++ "module Main ( main ) where\n"
--- = error $ "module missing \"module\" declaration: " ++ origname
| otherwise
= head allmats
if missing_module_declaration
then putStrLn $ "Missing \"module\" declaration: " ++ origname ++ "\n Assuming module Main\n Base indentation level appears to be " ++ show (length indent)
-- then putStrLn $ "Missing \"module\" declaration: " ++ origname ++ "\n Assuming module Main and base indentation level 0"
else return ()
let lexmod__
| missing_module_declaration = lexmod_mod ++ lexmod_
| otherwise = lexmod_
-- The takeWhile is an extra precaution, since may encounter
-- module Foo(bar) where ...
let modname = takeWhile (/='(') $ ((words lexmod_mod)!!1) -- should be...
-- XXX Getting duplicate output from this (and output in
-- a seemingly-wrong order, too), so gave up on it for now.
-- (The point was only to avoid the TH "Loading pacakge" spam.)
#if 0
if ( not internal ) && "-v0" `elem` ghcopts_lst
--- [2 of 2] Compiling Main ( Seqaid/Prepro.hs, dist/dist-sandbox-c80c5f2e/build/seqaidpp/seqaidpp-tmp/Main.o )
then putStrLn $ "Compiling " ++ modname
else return ()
#endif
(wasInstanceProcessed,lexmod) <-
if modname `elem` map fst instances
then do -- SOP generic deriving of NFDataP and superclasses:
let (mod,types) = lookupInstance modname instances
let pragmas = unlines $ map (indent++) $ makePragmas
let imports = unlines $ map (indent++) $ makeImports
let seqaidvalidate = make_seqaid_validate types
let dis = makeDIs types
let is = makeIs types
let dgens = makeDGens types
let lexmod' = pragmas ++ lexmod__ :: String
let go2 s = s ++ imports ++ indent
let lexmod'' = replaceAll False
(makeRegex "^ *module [^\n][^\n]*") go2 lexmod'
let lexmod''' = lexmod''
++ (unlines (map (indent++) seqaidvalidate))
++ (unlines (map (indent++) dis))
++ (unlines (map (indent++) is))
++ (unlines (map (indent++) dgens))
#if CAN_MIX_SOP_DERIVING_WITH_SEQAID_TH
return (True,lexmod''')
#else
writeFile outfile lexmod'''
--- _ <- system $ "/bin/cat " ++ outfile
return (True,lexmod''')
#endif
else return (False,lexmod__)
#if CAN_MIX_SOP_DERIVING_WITH_SEQAID_TH
if wasInstanceProcessed
|| ( not $ modname `elem` map seqaid_cfg_fqname modules )
#else
if not $ modname `elem` map seqaid_cfg_fqname modules
#endif
then do
-- The module currently being processed is neither mentioned
-- in the "instances" nor in a "module" field of seqaid.config.
-- So do nothing. This is identity, for a GHC -F prepro:
writeFile outfile lexmod
--- _ <- system $ "/bin/cat " ++ outfile
return ()
else do
-- XXX Some (maybe all) the !'s are probably unneeded.
momnis <- if ( not doomni ) || internal
then return Nothing
else do
omnis <- omnitypic origname packagename modname boo
return $ Just omnis
let (omni_types,omni_imports') = fromJust momnis
let mod@(SeqaidConfigModule fqname types' binds) = lookupModule modname config
let types = if isNothing momnis
then types'
else nub $ types' ++ omni_types -- should suffice, here
-- Inject the module currently being compiled:
-- (1) Substitute all manual instrumentation with
-- something more convenient to the plugin phase.
--- lexmod <- readFile infile
let go [] = [] -- shouldn't be possible
go (c:cs) = c : "seqaidDispatch undefined "
let lexmod' = replaceAll True
(makeRegex "[^A-Za-z0-9'_]seqaid ") go lexmod
let pragmas_ = unlines $ map (indent++) $ makePragmas_
-- XXX It seems it is necessary to inject a
-- {-# LANGUAGE TemplateHaskell #-} pragma, even though
-- it may be given in .cabal with default-extensions: TemplateHaskell
-- and with ghc-opts: -XTemplateHaskell! And using
-- {-# LANGUAGE CPP #-} also works, for some unknown reason!
-- (2) Inject the pragmas:
let go4 s = pragmas_ ++ s
let lexmod''_ = replaceAll False
(makeRegex "^ *module ") go4 lexmod'
let omni_imports
= if isNothing momnis
then []
else unlines $ map (indent++) omni_imports'
-- (3) Inject the Seqaid.TH import statement:
-- Later: And also some type imports (for omnitypic wrapper injection).
let go2 s = indent ++ "import Seqaid.TH\n" ++ omni_imports ++ s
let lexmod'' = replaceAll False
(makeRegex "^ *import ") go2 lexmod''_
-- (4) Inject the seqaidTH splice call:
let tqqs = map (\x -> "[t| " ++ x ++ " |]") types
-- Doesn't help, since when GHC reports the error, it re-ppr's onto one line.
-- (Also, the module qualifiers are absent!)
-- let lexmod'''' = lexmod'' ++ indent ++ "seqaidTH [ " ++ intercalate ("\n" ++ indent ++ " , ") tqqs ++ " ]"
let lexmod'''' = lexmod'' ++ indent ++ "seqaidTH [ " ++ intercalate ", " tqqs ++ " ]" ++ "\n"
-- (5) Inject the strInstancesTH splice call:
-- (Or should this be above (4)?...)
let lexmod''''' = lexmod'''' ++ indent ++ "strInstancesTH" ++ "\n"
-- (6) Inject the bindsIncludedTH splice call:
-- (Or should this be above?...)
let lexmod'''''' = lexmod''''' ++ indent ++ "bindsIncludedTH " ++ show binds ++ "\n"
#if TRY_INJECT_NOINLINE_ON_REQUESTED_BINDS
let lexmod''''''' = lexmod'''''' ++ indent ++ "noinlineTH " ++ show binds ++ "\n"
#else
let lexmod''''''' = lexmod''''''
#endif
writeFile outfile lexmod'''''''
--- _ <- system $ "/bin/cat " ++ outfile
return ()
-------------------------------------------------------------------------------
-- Thanks to rampion in http://stackoverflow.com/questions/9071682/replacement-substition-with-haskell-regex-libraries
replaceAll :: Bool -> Regex -> (String -> String) -> String -> String
replaceAll all re f s = start end
where
allmats = getAllMatches $ match re s
(_, end, start) = foldl' go (0, s, id) $ (if all || null allmats then id else take 1) allmats
go (ind,read,write) (off,len) =
let (skip, start) = splitAt (off - ind) read
(matched, remaining) = splitAt len start
in (off + len, remaining, write . (skip++) . (f matched ++))
-------------------------------------------------------------------------------
make_seqaid_validate ts = lines $ make_seqaid_validate' ts
make_seqaid_validate' ts = "\
\seqaidValidate [ " ++ intercalate ", " (map ("''"++) ts) ++ " ]\n"
-------------------------------------------------------------------------------
makeDIs ts = lines $ makeDIs' ts
makeDIs' [] = []
makeDIs' (t:ts) = "\
\deriving instance Show " ++ t ++ "\n\
\deriving instance Generic " ++ t ++ "\n\
\deriving instance Typeable " ++ t ++ "\n\
\deriving instance Data " ++ t ++ "\n" ++ makeDIs' ts
-------------------------------------------------------------------------------
makeIs ts = lines $ makeIs' ts
makeIs' [] = []
makeIs' (t:ts) = "\
\instance NFDataP " ++ t ++ " where rnfp = grnfp\n\
\instance NFDataN " ++ t ++ " where rnfn = grnfn\n\
\instance NFData " ++ t ++ " where rnf = genericRnf\n" ++ makeIs' ts
-------------------------------------------------------------------------------
makeDGens ts = lines $ makeDGens' ts
makeDGens' [] = []
makeDGens' (t:ts) = "\
\deriveGeneric ''" ++ t ++ "\n" ++ makeDGens' ts
-------------------------------------------------------------------------------
makeITs mod ts = lines $ makeITs' ts
where
makeITs' [] = []
makeITs' (t:ts) = "\
\import " ++ qual ++ " ( " ++ name ++ "(..)" ++ " )" ++ "\n" ++ makeITs' ts
where
qual = mod
name = t
-------------------------------------------------------------------------------
makePragmas = lines $ "\
\\n\
\-- For NFDataP (which perforce includes NFDataN and NFData):\n\
\{-# LANGUAGE CPP #-}\n\
\{-# LANGUAGE TemplateHaskell #-}\n\
\{- LANGUAGE ScopedTypeVariables #-}\n\
\{-# LANGUAGE DataKinds #-}\n\
\{-# LANGUAGE TypeFamilies #-}\n\
\{- LANGUAGE ConstraintKinds #-}\n\
\{-# LANGUAGE GADTs #-} -- for GHC 7.6.3\n\
\{-# LANGUAGE DeriveGeneric #-}\n\
\{-# LANGUAGE DeriveDataTypeable #-}\n\
\{-# LANGUAGE StandaloneDeriving #-}\n\
\\n\
\{- LANGUAGE TypeSynonymInstances #-}\n\
\\n\
\-- RankNTypes wanted since some injected type signatures,\n\
\-- due to imported types, may require it.\n\
\{-# LANGUAGE RankNTypes #-}\n"
-------------------------------------------------------------------------------
makePragmas_ = lines $ "\
\\n\
\{- LANGUAGE CPP #-}\n\
\{-# LANGUAGE TemplateHaskell #-}\n"
-------------------------------------------------------------------------------
makeImports = lines $ "\
\\n\
\import Control.DeepSeq.Bounded\n\
\import Control.DeepSeq.Generics\n\
\\n\
\import Seqaid.TH ( seqaidValidate )\n\
\import Generics.SOP.TH\n\
\import GHC.Generics ( Generic )\n\
\import Data.Typeable ( Typeable )\n\
\import Data.Data ( Data )\n"
-------------------------------------------------------------------------------
testSaneConfig :: SeqaidConfig -> Bool
testSaneConfig config@(SeqaidConfig packagename modules instances) = b
where
bs = zipWith ($)
(map (elem . fst) instances)
(repeat $ map seqaid_cfg_fqname modules)
b = null bs || not (or bs)
-------------------------------------------------------------------------------
lookupInstance :: String -> [(String,[String])] -> (String,[String])
lookupInstance modname [] = error "lookupInstance: unexpected!"
lookupInstance modname (i@(nm,tys):t)
| modname == nm = i
| otherwise = lookupInstance modname t
-------------------------------------------------------------------------------
-- Use a trick to obtain the list of all subexpression types in the module.
-- This has a high compile time cost (as much as doubles build time),
-- but that may be acceptable for a diagnostic build, and it's
-- potentially quite a powerful fine-grained blanket harness.
omnitypic :: String -> String -> String -> ([String],[String]) -> IO ([String],[String])
omnitypic origname packagename modname (cppopts_lst,ghcopts_lst)= do
systmp <- getTemporaryDirectory
let ghctmp = systmp ++ "/ghctmp-seqaidpp"
--- let ghctmp = "/media/ramdisk/ghctmp-seqaidpp"
createDirectoryIfMissing False ghctmp
-- XXX this tmpfile is actually not used at all (just as a /dev/null)
let tmp_filename = "seqaid_tmp_321"
let tmp_pname = ghctmp ++ "/" ++ tmp_filename
let omni_types_pname = ghctmp ++ "/omnitypes.txt"
let omni_imports_pname = ghctmp ++ "/omniimports.txt"
let cppopts = intercalate " " cppopts_lst
let ghcopts' = intercalate " " ghcopts_lst
in_sandbox <- detectSandbox
let nowneeded = "\
\ -fplugin=Seqaid.Plugin \
\ -F -pgmF " ++ ( if in_sandbox then ".cabal-sandbox/bin/seqaidpp" else "seqaidpp" ) ++ " \
\ -optF omnitypic "
-- The -c option together with --make will prevent linking, while
-- still building all dependencies.
let ghcopts = ghcopts' ++ " \
\ --make \
\ -c \
\ " ++ nowneeded ++ " \
\ -fplugin-opt=Seqaid.Plugin:prepro=" ++ modname ++ " \
\ -fplugin-opt=Seqaid.Plugin:" ++ omni_types_pname ++ " \
\ -fplugin-opt=Seqaid.Plugin:" ++ omni_imports_pname ++ " \
\ -optF internal \
\ -XTemplateHaskell \
\ -with-rtsopts=-T \
\ -outputdir " ++ ghctmp
-- These are either optional, or already required to be in the .cabal file.
--
-- Probably IS going to be wanted:
--- \ -threaded \
-- Already required to be in the .cabal file:
-- Later: But it's now guarded by a condition (flag), so
-- it doesn't show up for parseCabal! (So added to the above.)
--- \ -fplugin=Seqaid.Plugin \
--- \ -F \
--- \ -pgmF seqaidpp \
-- Optional:
--- \ -optP-Wundef \
--- \ -fno-warn-overlapping-patterns \
-- Seem not to be needed if supply -with-rtsopts (which we do):
--- \ -rtsopts \
#if 0
putStrLn $ "CPPOPTS:\n" ++ show cppopts
putStrLn $ "GHCOPTS:\n" ++ show ghcopts
-- error "DEVEXIT"
#endif
let cmnd = "cabal exec -- ghc "
++ ghcopts ++ " "
++ cppopts ++ " "
#if 1
-- /dev/null would do ... if it exists
++ origname ++ " >> " ++ tmp_pname ++ " 2>&1"
#else
++ origname
#endif
#if DBG_OMNI
putStrLn cmnd
#endif
writeFile tmp_pname $ cmnd ++ "\n"
_ <- system cmnd
--- _ <- system $ "cat " ++ tmp_pname
omni_types' <- readFile omni_types_pname -- as a String, one type per line
-- (removing temp files, so stale temp files don't get read by wrong
-- module pass, in case that's what's happening...)
-- removeFile omni_types_pname
omni_imports' <- readFile omni_imports_pname
-- removeFile omni_imports_pname
#if DBG_OMNI
putStrLn omni_types'
putStrLn omni_imports'
-- error "DEVEXIT"
#endif
let omni_types = lines omni_types'
let omni_imports = lines omni_imports'
return (omni_types,omni_imports)
-------------------------------------------------------------------------------
mergeInstances :: [(String,[String])] -> [(String,[String])] -> [(String,[String])]
mergeInstances instances instances' = instances''
where
sis = splitInstances instances
sis' = splitInstances instances'
sis'' = nub $ sis ++ sis'
instances'' = parseInstances sis''
splitInstances :: [(String,[String])] -> [(String,String)]
splitInstances [] = []
splitInstances (h@(m,ts):t) = map (\x -> (m,x)) ts ++ splitInstances t
-------------------------------------------------------------------------------
parseCabal :: String -> IO ([String],[String])
parseCabal cabal_pname = do
gpd <- readPackageDescription silent cabal_pname
let flags = genPackageFlags gpd
let flags' = map (\ (MkFlag (FlagName a) b c d) -> (map toUpper a, c)) flags
let cppopts_flags = map (\ (x,y) -> " -D" ++ x ++ "=" ++ if y then "1" else "0") flags'
let cppopts_flags_string = intercalate " " cppopts_flags
#if 0
MkFlag
flagName :: FlagName
flagDescription :: String
flagDefault :: Bool
flagManual :: Bool
#endif
let condexes = condExecutables gpd
let condexe@(_, condexetree) = head condexes
let Executable _ _ buildinfo = condTreeData condexetree
#if DBG_OMNI
putStrLn $ "BUILDINFO:\n" ++ show buildinfo
#endif
let cppopts_cppopts = cppOptions buildinfo
let cppopts = cppopts_flags ++ cppopts_cppopts
let ghcopts = process_opts $ options buildinfo
return (cppopts,ghcopts)
where
process_opts :: [(CompilerFlavor, [String])] -> [String]
process_opts [] = []
process_opts (h@(cf,ss):t)
| GHC <- cf = ss ++ process_opts t
| otherwise = process_opts t
-------------------------------------------------------------------------------
guessBaseIndentationLevel :: String -> Maybe Int
guessBaseIndentationLevel str
| null lst' = Nothing
| otherwise = Just $ snd $ head lst'
where
lst = map (length . takeWhile (==' '))
$ filter (not . isDashDashCommentLine)
$ filter (/="")
$ lines str
-- lst' = sort $ zip lst [0..]
lst' = map (\x -> (length x,head x)) $ group $ sort lst -- histogram of indentation levels
isDashDashCommentLine :: String -> Bool
isDashDashCommentLine (' ':cs) = isDashDashCommentLine cs
isDashDashCommentLine ('-':'-':_) = True
isDashDashCommentLine _ = False
-------------------------------------------------------------------------------
-- (I know of no better way.)
detectSandbox :: IO Bool
detectSandbox = doesFileExist "cabal.sandbox.config"
-------------------------------------------------------------------------------