{-# LANGUAGE RecordWildCards #-}
module Test.Tasty.WebDriver.Config (
DriverName(..)
, RemoteEndPool(..)
, addRemoteEndForDriver
, getRemoteEndForDriver
, RemoteEnd(..)
, parseRemoteEnd
, parseRemoteEndConfig
, parseRemoteEndOption
, parseOptionWithArgument
) where
import Data.List
( unlines, isPrefixOf, isSuffixOf, nub )
import qualified Data.Map.Strict as MS
( fromListWith, insert, lookup, adjust, fromList, unionWith, Map )
import Data.Typeable
( Typeable, Proxy(Proxy) )
import Network.URI
( URI(..), URIAuth(..), parseURI )
import Text.Read
( readMaybe )
data DriverName
= Geckodriver
| Chromedriver
deriving (Eq, Ord, Typeable)
instance Show DriverName where
show Geckodriver = "geckodriver"
show Chromedriver = "chromedriver"
newtype RemoteEndPool = RemoteEndPool
{ freeRemoteEnds :: MS.Map DriverName [RemoteEnd]
} deriving (Eq, Show)
instance Semigroup RemoteEndPool where
x <> y = RemoteEndPool
{ freeRemoteEnds = MS.unionWith (++) (freeRemoteEnds x) (freeRemoteEnds y)
}
instance Monoid RemoteEndPool where
mempty = RemoteEndPool
{ freeRemoteEnds = MS.fromList []
}
mappend = (<>)
addRemoteEndForDriver :: DriverName -> RemoteEnd -> RemoteEndPool -> RemoteEndPool
addRemoteEndForDriver driver remote pool = RemoteEndPool
{ freeRemoteEnds = MS.adjust (remote:) driver $ freeRemoteEnds pool
}
getRemoteEndForDriver :: DriverName -> RemoteEndPool -> (RemoteEndPool, Maybe (Maybe RemoteEnd))
getRemoteEndForDriver driver pool =
case MS.lookup driver (freeRemoteEnds pool) of
Nothing -> (pool, Nothing)
Just z -> case z of
[] -> (pool, Just Nothing)
(r:rs) -> (pool { freeRemoteEnds = MS.insert driver rs $ freeRemoteEnds pool }, Just $ Just r)
data RemoteEnd = RemoteEnd
{ remoteEndHost :: String
, remoteEndPort :: Int
, remoteEndPath :: String
} deriving Eq
instance Show RemoteEnd where
show remote = concat
[ remoteEndHost remote
, ":"
, show $ remoteEndPort remote
, remoteEndPath remote
]
parseRemoteEndConfig :: String -> Either String RemoteEndPool
parseRemoteEndConfig str = do
freeEnds <- fmap (MS.fromListWith (++)) $ tokenizeRemoteEndConfig $ filter (/= "") $ lines str
return RemoteEndPool
{ freeRemoteEnds = freeEnds
}
tokenizeRemoteEndConfig :: [String] -> Either String [(DriverName, [RemoteEnd])]
tokenizeRemoteEndConfig ls = case ls of
[] -> return []
(first:rest) -> do
driver <- case first of
"geckodriver" -> return Geckodriver
"chromedriver" -> return Chromedriver
_ -> Left $ "Unrecognized driver name '" ++ first ++ "'."
let (remotes, remainder) = span ("- " `isPrefixOf`) rest
ends <- mapM (parseRemoteEnd . drop 2) remotes
config <- tokenizeRemoteEndConfig remainder
return $ (driver, nub ends) : config
parseRemoteEndOption :: String -> Either String RemoteEndPool
parseRemoteEndOption str = do
freeEnds <- fmap (MS.fromListWith (++)) $ tokenizeRemoteEndOption $ words str
return RemoteEndPool
{ freeRemoteEnds = freeEnds
}
tokenizeRemoteEndOption :: [String] -> Either String [(DriverName, [RemoteEnd])]
tokenizeRemoteEndOption ws = case ws of
[] -> return []
(first:rest) -> do
driver <- case first of
"geckodriver" -> return Geckodriver
"chromedriver" -> return Chromedriver
_ -> Left $ "Unrecognized driver name '" ++ first ++ "'."
let (remotes, remainder) = break (`elem` ["geckodriver","chromedriver"]) rest
ends <- mapM parseRemoteEnd remotes
option <- tokenizeRemoteEndOption remainder
return $ (driver, nub ends) : option
parseRemoteEnd :: String -> Either String RemoteEnd
parseRemoteEnd str = case parseURI str of
Nothing -> Left $ "Could not parse remote end URI '" ++ str ++ "'."
Just URI{..} -> case uriAuthority of
Nothing -> Left $ "Error parsing authority for URI '" ++ str ++ "'."
Just URIAuth{..} -> case uriPort of
"" -> Right RemoteEnd
{ remoteEndHost = uriUserInfo ++ uriRegName
, remoteEndPort = 4444
, remoteEndPath = uriPath
}
':':ds -> case readMaybe ds of
Nothing -> Left $ "Error parsing port for URI '" ++ str ++ "'."
Just k -> Right RemoteEnd
{ remoteEndHost = uriUserInfo ++ uriRegName
, remoteEndPort = k
, remoteEndPath = uriPath
}
p -> Left $ "Unexpected port '" ++ p ++ "' in URI '" ++ str ++ "'."
parseOptionWithArgument
:: String
-> [String]
-> Maybe (Maybe String)
parseOptionWithArgument option args = case args of
(opt:arg:rest) -> if opt == option
then case arg of
'-':_ -> Nothing
_ -> Just (Just arg)
else parseOptionWithArgument option (arg:rest)
_ -> Just Nothing