packages feed

hsdev-0.1.0.1: tools/Types.hs

{-# LANGUAGE OverloadedStrings, CPP #-}

module Types (
	-- * Server options
	ServerOpts(..), serverOpts, serverOptsToArgs,
	-- * Client options
	ClientOpts(..), clientOpts,
	-- * Messages and results
	ResultValue(..), Response(..),
	CommandResult(..), ok, err, errArgs, details,
	CommandCall(..), callArgs, addCallOpts, removeCallOpts, WithOpts(..),
	CommandOptions(..), CommandAction
	) where

import Control.Applicative
import Control.Monad.Error
import Data.Aeson
import Data.Map (Map)
import Data.Maybe (fromMaybe)
import Data.Monoid
import qualified Data.HashMap.Strict as HM
import qualified Data.Map as M
import System.Console.GetOpt
import Text.Read

import HsDev.Database
import HsDev.Project
import HsDev.Symbols
import HsDev.Tools.GhcMod (TypedRegion)
import qualified HsDev.Database.Async as DB
import HsDev.Util ((.::), (.::?))

import System.Command
import Update

#if mingw32_HOST_OS
import System.Win32.FileMapping.NamePool (Pool)
#endif

-- | Server options
data ServerOpts = ServerOpts {
	serverPort :: First Int,
	serverTimeout :: First Int,
	serverLog :: First String,
	serverCache :: First FilePath,
	serverLoadCache :: Any,
	serverAsClient :: Any }

instance DefaultConfig ServerOpts where
	defaultConfig = ServerOpts
		(First $ Just 4567)
		(First $ Just 1000)
		(First Nothing)
		(First Nothing)
		mempty
		mempty

instance Monoid ServerOpts where
	mempty = ServerOpts mempty mempty mempty mempty mempty mempty
	l `mappend` r = ServerOpts
		(serverPort l `mappend` serverPort r)
		(serverTimeout l `mappend` serverTimeout r)
		(serverLog l `mappend` serverLog r)
		(serverCache l `mappend` serverCache r)
		(serverLoadCache l `mappend` serverLoadCache r)
		(serverAsClient l `mappend` serverAsClient r)

-- | Server options command opts
serverOpts :: [OptDescr ServerOpts]
serverOpts = [
	Option [] ["port"] (ReqArg (\p -> mempty { serverPort = First (readMaybe p) }) "number") "listen port",
	Option [] ["timeout"] (ReqArg (\t -> mempty { serverTimeout = First (readMaybe t) }) "msec") "query timeout",
	Option ['l'] ["log"] (ReqArg (\l -> mempty { serverLog = First (Just l) }) "file") "log file",
	Option [] ["cache"] (ReqArg (\p -> mempty { serverCache = First (Just p) }) "path") "cache directory",
	Option [] ["load"] (NoArg (mempty { serverLoadCache = Any True })) "force load all data from cache on startup",
	Option ['c'] ["as-client"] (NoArg (mempty { serverAsClient = Any True })) "make server be client and connect to port specified"]

-- | Convert 'ServerOpts' to args
serverOptsToArgs :: ServerOpts -> [String]
serverOptsToArgs sopts = concat [
	arg' "port" show $ serverPort sopts,
	arg' "timeout" show $ serverTimeout sopts,
	arg' "log" id $ serverLog sopts,
	arg' "cache" id $ serverCache sopts,
	if getAny (serverLoadCache sopts) then ["--load"] else [],
	if getAny (serverAsClient sopts) then ["--as-client"] else []]
	where
		arg' :: String -> (a -> String) -> First a -> [String]
		arg' name str = maybe [] (\v -> ["--" ++ name, str v]) . getFirst

-- | Client options
data ClientOpts = ClientOpts {
	clientPort :: First Int,
	clientPretty :: Any,
	clientData :: Any }

instance DefaultConfig ClientOpts where
	defaultConfig = ClientOpts (First $ Just 4567) mempty mempty

instance Monoid ClientOpts where
	mempty = ClientOpts mempty mempty mempty
	l `mappend` r = ClientOpts
		(clientPort l `mappend` clientPort r)
		(clientPretty l `mappend` clientPretty r)
		(clientData l `mappend` clientData r)

-- | Client options command opts
clientOpts :: [OptDescr ClientOpts]
clientOpts = [
	Option [] ["port"] (ReqArg (\p -> mempty { clientPort = First (readMaybe p) }) "number") "connection port",
	Option [] ["pretty"] (NoArg (mempty { clientPretty = Any True })) "pretty json output",
	Option [] ["stdin"] (NoArg (mempty { clientData = Any True })) "pass data to stdin"]

data ResultValue =
	ResultDatabase Database |
	ResultDeclaration Declaration |
	ResultModuleDeclaration ModuleDeclaration |
	ResultModuleId ModuleId |
	ResultModule Module |
	ResultInspectedModule InspectedModule |
	ResultPackage ModulePackage |
	ResultProject Project |
	ResultTyped TypedRegion |
	ResultList [ResultValue] |
	ResultMap (Map String ResultValue) |
	ResultJSON Value |
	ResultString String |
	ResultNone

instance ToJSON ResultValue where
	toJSON (ResultDatabase db) = toJSON db
	toJSON (ResultDeclaration d) = toJSON d
	toJSON (ResultModuleDeclaration md) = toJSON md
	toJSON (ResultModuleId mid) = toJSON mid
	toJSON (ResultModule m) = toJSON m
	toJSON (ResultInspectedModule m) = toJSON m
	toJSON (ResultPackage p) = toJSON p
	toJSON (ResultProject p) = toJSON p
	toJSON (ResultTyped t) = toJSON t
	toJSON (ResultList l) = toJSON l
	toJSON (ResultMap m) = toJSON m
	toJSON (ResultJSON v) = toJSON v
	toJSON (ResultString s) = toJSON s
	toJSON ResultNone = toJSON $ object []

instance FromJSON ResultValue where
	parseJSON v = foldr1 (<|>) [
		do
			(Object m) <- parseJSON v
			if HM.null m then return ResultNone else mzero,
		ResultDatabase <$> parseJSON v,
		ResultDeclaration <$> parseJSON v,
		ResultModuleDeclaration <$> parseJSON v,
		ResultModuleId <$> parseJSON v,
		ResultModule <$> parseJSON v,
		ResultInspectedModule <$> parseJSON v,
		ResultPackage <$> parseJSON v,
		ResultProject <$> parseJSON v,
		ResultTyped <$> parseJSON v,
		ResultList <$> parseJSON v,
		ResultMap <$> parseJSON v,
		pure $ ResultJSON v,
		ResultString <$> parseJSON v]

data Response =
	ResponseStatus Status |
	ResponseMapFile String |
	Response Value

instance ToJSON Response where
	toJSON (ResponseStatus s) = toJSON s
	toJSON (ResponseMapFile s) = object ["file" .= s]
	toJSON (Response v) = v

instance FromJSON Response where
	parseJSON v = foldr1 (<|>) [
		ResponseStatus <$> parseJSON v,
		withObject "response" (\f -> (ResponseMapFile <$> (f .:: "file"))) v,
		pure $ Response v]

data CommandResult =
	ResultOk ResultValue |
	ResultError String (Map String ResultValue) |
	ResultProcess ((Status -> IO ()) -> IO ())

instance Error CommandResult where
	noMsg = ResultError noMsg mempty
	strMsg s = ResultError s mempty

ok :: CommandResult
ok = ResultOk ResultNone

err :: String -> CommandResult
err s = ResultError s M.empty

errArgs :: String -> [(String, ResultValue)] -> CommandResult
errArgs s as = ResultError s (M.fromList as)

-- | Add detailed information to error message
details :: [(String, ResultValue)] -> CommandResult -> CommandResult
details as (ResultError s cs) = ResultError s (M.union (M.fromList as) cs)
details _ r = r

data CommandCall = CommandCall {
	commandCallName :: [String],
	commandCallPosArgs :: [String],
	commandCallOpts :: Opts String }

instance ToJSON CommandCall where
	toJSON (CommandCall n ps opts) = object [
		"command" .= n,
		"args" .= ps,
		"opts" .= opts]

instance FromJSON CommandCall where
	parseJSON = withObject "command call" $ \v -> CommandCall <$>
		(v .:: "command") <*>
		(fromMaybe [] <$> (v .::? "args")) <*>
		(fromMaybe mempty <$> (v .::? "opts"))

callArgs :: CommandCall -> [String]
callArgs (CommandCall n ps opts) = n ++ ps ++ toArgs opts

-- | Add options
--
-- >cmdCall `addCallOpts` ["foo" %-- 15]
addCallOpts :: CommandCall -> [Opts String] -> CommandCall
addCallOpts cmdCall os = cmdCall {
	commandCallOpts = mconcat (commandCallOpts cmdCall : os) }

-- | Remove specified call options
--
-- >cmdCall `removeCallOpts` ["foo"]
removeCallOpts :: CommandCall -> [String] -> CommandCall
removeCallOpts cmdCall os = cmdCall {
	commandCallOpts = Opts $ foldr (.) id (map M.delete os) $ getOpts (commandCallOpts cmdCall) }

data WithOpts a = WithOpts {
	withOptsAct :: a,
	withOptsCommand :: CommandCall }

instance Functor WithOpts where
	fmap f (WithOpts x as) = WithOpts (f x) as

data CommandOptions = CommandOptions {
	commandDatabase :: DB.Async Database,
	commandWriteCache :: Database -> IO (),
	commandReadCache :: (FilePath -> ErrorT String IO Structured) -> IO (Maybe Database),
	commandRoot :: FilePath,
	commandLog :: String -> IO (),
	commandLogWait :: IO (),
#if mingw32_HOST_OS
	commandMmapPool :: Maybe Pool,
#endif
	commandLink :: IO (),
	commandHold :: IO (),
	commandExit :: IO () }

type CommandAction = WithOpts (Int -> CommandOptions -> IO CommandResult)