import System.Environment (getArgs) import System.Directory (getAppUserDataDirectory, createDirectoryIfMissing) import Control.Applicative ((<$>)) import Data.Maybe (catMaybes) import Data.Char (isSpace) import Data.List (isPrefixOf, foldl', sort) import Data.Ord (comparing) import System.Exit (exitWith, ExitCode (ExitSuccess), exitFailure) import System.Cmd (rawSystem) import Network.HTTP (simpleHTTP, getRequest, getResponseBody) import qualified Data.ByteString.Lazy as L import qualified Codec.Archive.Tar as Tar import Control.Exception (throwIO) import qualified Data.Map as Map import Control.Monad (unless) import Data.Maybe (fromMaybe) import qualified Paths_cabal_nirvana import Data.Version (showVersion, parseVersion) import Text.ParserCombinators.ReadP (readP_to_S) currVersion :: String currVersion = showVersion Paths_cabal_nirvana.version indexFile :: IO FilePath indexFile = do dir <- getAppUserDataDirectory "cabal" return $ dir ++ "/packages/hackage.haskell.org/00-index.tar" nirvanaFile :: IO FilePath nirvanaFile = do dir <- getAppUserDataDirectory "cabal-nirvana" createDirectoryIfMissing True dir return $ dir ++ "/default" data Package = Package { name :: String , version :: String } readPackages :: FilePath -> IO [Package] readPackages fp = readFile fp >>= (catMaybes <$>) . mapM parse . lines where parse s | all isSpace s = return Nothing | "--" `isPrefixOf` s = return Nothing parse s = case words s of [a, b] -> return $ Just $ Package a b _ -> error $ "Invalid nirvana line: " ++ show s main :: IO () main = do args <- getArgs case args of [] -> do ec <- rawSystem "cabal" ["update"] unless (ec == ExitSuccess) $ exitWith ec checkVersion fetch Nothing start Nothing ["fetch"] -> fetch Nothing ["fetch", url] -> fetch $ Just url ["start"] -> start Nothing ["start", nfp] -> start $ Just nfp ["test"] -> test Nothing ["test", fp] -> test $ Just fp _ -> do putStrLn "Available commands:" putStrLn " cabal-nirvana" putStrLn " cabal-nirvana fetch [URL]" putStrLn " cabal-nirvana start [package file]" putStrLn " cabal-nirvana test [package file]" beginLine, endLine :: String beginLine = "-- cabal-nirvana begin" endLine = "-- cabal-nirvana end" removeNirvana :: [String] -> [String] removeNirvana [] = [] removeNirvana (x:xs) | x == beginLine = removeNirvana $ drop 1 $ dropWhile (/= endLine) xs | otherwise = x : removeNirvana xs addNirvana :: [Package] -> [String] -> [String] addNirvana ps = (++ ss) where ss = beginLine : foldr (:) [endLine] (map toS ps) toS (Package n v) = concat [ "constraint: " , n , " == " , v ] fetch :: Maybe String -> IO () fetch murl = do str <- simpleHTTP (getRequest url) >>= getResponseBody nirvanaFile >>= flip writeFile str where url = fromMaybe "http://yesodweb.github.com/nirvana" murl checkVersion :: IO () checkVersion = do ifp <- indexFile entries <- Tar.read <$> L.readFile ifp versions <- go id entries case reverse $ sort versions of [] -> return () v:_ | v == Paths_cabal_nirvana.version -> return () | v < Paths_cabal_nirvana.version -> do putStrLn "You appear to be running an unreleased version." putStrLn "If not, please install the latest version from Hackage:" putStrLn " cabal install cabal-nirvana" | otherwise -> do putStrLn "Your version of cabal-nirvana is out-of-date." putStrLn $ "Your version: " ++ showVersion Paths_cabal_nirvana.version putStrLn $ "Latest version: " ++ showVersion v putStrLn "Please update by running:" putStrLn " cabal install cabal-nirvana" exitFailure where go front Tar.Done = return $ front [] go _ (Tar.Fail e) = throwIO e go front (Tar.Next e es) = go front' es where fp = Tar.entryPath e (p, fp') = break (== '/') fp v = takeWhile (/= '/') $ drop 1 fp' front' | p == "cabal-nirvana" = case filter (null . snd) $ readP_to_S parseVersion v of (v', _):_ -> front . (v':) [] -> front | otherwise = front start :: Maybe String -> IO () start mnfp = do nfp <- maybe nirvanaFile return mnfp packages <- readPackages nfp let packMap = Map.fromList $ map (\(Package n v) -> (n, v)) packages start' packMap where start' packMap = do ifp <- indexFile entries <- Tar.read <$> L.readFile ifp go id entries >>= L.writeFile ifp . Tar.write where go front Tar.Done = return $ front [] go _ (Tar.Fail e) = throwIO e go front (Tar.Next e es) = go front' es where fp = Tar.entryPath e (p, fp') = break (== '/') fp v = takeWhile (/= '/') $ drop 1 fp' front' | toRemove p v = front | otherwise = front . (e:) toRemove _ "" = False toRemove p v = case Map.lookup p packMap of Nothing -> False Just v' -> v /= v' test :: Maybe FilePath -> IO () test mfp = do fp <- maybe nirvanaFile return mfp packages <- readPackages fp let ws = map (\(Package n v) -> concat [n, "-", v]) packages putStrLn $ "cabal-dev install " ++ unwords ws ec <- rawSystem "cabal-dev" $ "install" : ws exitWith ec