{-# OPTIONS_GHC -fno-warn-orphans -XPatternGuards #-} -- | -- Module : Scion.Utils -- Copyright : (c) Thomas Schilling 2008 -- License : BSD-style -- -- Maintainer : nominolo@gmail.com -- Stability : experimental -- Portability : portable -- -- Various utilities. -- module Scion.Utils where import Scion.Types import GHC ( GhcMonad, ModSummary, spans, getLoc, Located , depanal, topSortModuleGraph, TypecheckedMod , mkPrintUnqualifiedForModule, moduleInfo ) import Digraph ( flattenSCCs ) import Outputable import Control.Monad import Data.Maybe ( fromMaybe ) import Data.Char (isLower, isUpper) import Text.JSON import Data.Foldable (foldlM) import System.FilePath import System.Directory (doesFileExist) import System.IO (openFile, hPutStrLn, hClose, IOMode(..)) import Data.List (isPrefixOf) thingsAroundPoint :: (Int, Int) -> [Located n] -> [Located n] thingsAroundPoint pt ls = [ l | l <- ls, spans (getLoc l) pt ] modulesInDepOrder :: GhcMonad m => m [ModSummary] modulesInDepOrder = do gr <- depanal [] False return $ flattenSCCs $ topSortModuleGraph False gr Nothing -- in dep-order foldModSummaries :: GhcMonad m => (a -> ModSummary -> m a) -> a -> m a foldModSummaries f seed = modulesInDepOrder >>= foldM f seed expectJust :: String -> Maybe a -> a expectJust _ (Just a) = a expectJust msg Nothing = dieHard $ "Just x expected.\n grep for \"" ++ msg ++ "\"" unqualifiedForModule :: TypecheckedMod m => m -> ScionM PrintUnqualified unqualifiedForModule tcm = do fromMaybe alwaysQualify `fmap` mkPrintUnqualifiedForModule (moduleInfo tcm) second :: (a -> b) -> (c, a) -> (c, b) second f (x,y) = (x, f y) ifM :: Monad m => m Bool -> m a -> m a -> m a ifM cm tm em = do c <- cm if c then tm else em ------------------------------------------------------------------------ -- JSON helper functions lookupKey :: JSON a => JSObject JSValue -> String -> Result a lookupKey = flip valFromObj makeObject :: [(String, JSValue)] -> JSValue makeObject = makeObj ------------------------------------------------------------------------------ -- an alternative to the broken Fuzzy module -- match sH simpleHTTP -- match siH simpleHTTP -- match sHTTP simpleHTTP -- match pSL putStrLn -- match lM liftM -- match DS Data.Set camelCaseMatch :: String -> String -> Bool camelCaseMatch (c:cs) (i:is) | c == i = (camelCaseMatch cs $ dropWhile (\c' -> isLower c' || c' == '.') . dropWhile isUpper $ is) || camelCaseMatch cs is -- to allow siH match simpleHTTP | otherwise = False camelCaseMatch [] [] = True camelCaseMatch [] _ = False camelCaseMatch _ [] = False instance JSON CabalConfiguration where readJSON (JSObject obj) | Ok "build-configuration" <- lookupKey obj "type" , Ok distDir' <- lookupKey obj "dist-dir" , Ok args <- lookupKey obj "extra-args" , Ok args2 <- readJSONs args = return $ CabalConfiguration distDir' args2 readJSON _ = fail "CabalConfiguration" showJSON (CabalConfiguration dd ea) = makeObject [ ("dist-dir", JSString (toJSString dd)) , ("extra-args", JSArray (map (JSString . toJSString) ea)) ] data ScionDefaultCabalConfig = ScionDefaultCabalConfig String instance JSON ScionDefaultCabalConfig where readJSON (JSObject obj) | Ok s <- lookupKey obj "scion-default-cabal-config" = return $ ScionDefaultCabalConfig s readJSON _ = fail "ScionDefaultCabalConfig" showJSON (ScionDefaultCabalConfig s) = makeObject $ [ ("scion-default-cabal-config", (JSString . toJSString) s) ] readFileComponentConfig :: JSValue -> Result (String, [String]) readFileComponentConfig (JSObject obj) | Ok "component-file" <- lookupKey obj "type" , Ok file <- lookupKey obj "file" , Ok args <- lookupKey obj "flags" , Ok args2 <- readJSONs args = return (file, args2) readFileComponentConfig _ = fail "reading component-file config" projectConfigFileFromDir :: FilePath -> FilePath projectConfigFileFromDir = ( ".scion-config") projectConfigFromDir :: FilePath -> ScionM ScionProjectConfig projectConfigFromDir = parseScionProjectConfig . projectConfigFileFromDir -- If the file exists append. Deleting settings you don't need is faster than looking them up.. -- So let's extend this creating a complete reference? -- Maybe we can even add flags from the cabal file automatically ? writeSampleConfig :: FilePath -> IO () writeSampleConfig file = do h <- openFile file AppendMode hPutStrLn h $ "\n" ++ unlines [ "// this is a demo scion project configuration file has been created for you" ,"// you can use it to write down a set of configurations you'd like to test" ,"//" ,"// make scion select the default scion entry" ,"{\"scion-default-cabal-config\":\"dist-scion\"}" ,"// default scion entry:" ,"{\"type\":\"build-configuration\", \"dist-dir\":\"dist-scion\", \"extra-args\": [], \"scion-default\": 1}" ,"//" ,"// some examples:" ,"{\"type\":\"build-configuration\", \"dist-dir\":\"dist-demo-simple-tools-from-path-default\", \"extra-args\": []}" ,"{\"type\":\"build-configuration\", \"dist-dir\":\"dist-demo-1\", \"extra-args\": [\"--with-hc-pkg=PATH\", \"--with-compiler=path-to-ghc\"]}" ,"{\"type\":\"build-configuration\", \"dist-dir\":\"dist-demo-2\", \"extra-args\": [\"--flags=BuildTestXHTML BuildTestSimple\", \"--disable-library-profiling\"]}" ,"//" ,"{\"type\":\"component-file\", \"file\": \"test-application.hs\", \"flags\":[\"-package\", \"parsec\"]}" ,"{\"type\":\"component-file\", \"file\": \"test-application.hs\", \"flags\":[]}" ] hClose h -- TODO ensure file handle is closed! -- the format of this file will change when scion matures.. -- However it's a quick and easy way for scion, the client and users to read and write the config parseScionProjectConfig :: FilePath -> ScionM ScionProjectConfig parseScionProjectConfig path = do de <- liftIO $ doesFileExist path if de then do lines' <- liftIO $ liftM ( filter (not . isPrefixOf "//") . lines) $ readFile path jsonParsed <- mapM parseLine lines' foldlM parseJSON emptyScionProjectConfig jsonParsed else return emptyScionProjectConfig where parseLine :: String -> ScionM JSValue parseLine l = case decodeStrict l of Ok r -> return r Error msg -> scionError $ "error parsing configuration line" ++ (show l) ++ " error : " ++ msg parseJSON :: ScionProjectConfig -> JSValue -> ScionM ScionProjectConfig parseJSON pc json = case readJSON json of Ok bc -> return $ pc { buildConfigurations = bc : buildConfigurations pc } Error msg1 -> case readFileComponentConfig json of Ok cf -> return $ pc { fileComponentExtraFlags = cf : fileComponentExtraFlags pc } Error msg2 -> case readJSON json of Ok (ScionDefaultCabalConfig name) -> return $ pc { scionDefaultCabalConfig = Just name } Error msg3 -> scionError $ "invalid JSON object " ++ (show json) ++ " error :" ++ msg1 ++ "\n" ++ msg2 ++ "\n" ++ msg3