craft-0.0.0.1: src/Craft/Config/SSH.hs
module Craft.Config.SSH where
import Control.Lens hiding (noneOf)
import Data.Char (toLower)
import Data.Maybe (catMaybes)
import Text.Megaparsec
import Craft hiding (try)
import Craft.Config
import Craft.Internal.Helpers
import Craft.Internal.Helpers.Parsing
import Craft.SSH
data Section
= Host String Body
| Match String Body
deriving Eq
type Sections = [Section]
type Body = [(String, String)]
newtype SSHConfig = SSHConfig { _sshfmt :: Sections }
deriving Eq
instance Show SSHConfig where
show sshcfg = unlines . map show $ _sshfmt sshcfg
data UserConfig
= UserConfig
{ _user :: User
, _userConfigs :: SSHConfig
}
deriving Eq
makeLenses ''SSHConfig
makeLenses ''UserConfig
instance ConfigFormat SSHConfig where
showConfig = show
parseConfig = sshConfigParse
userPath :: UserConfig -> Path Abs FileP
userPath uc = (userDir (uc ^. user) ^. path) </> $(mkRelFile "config")
get :: Path Abs FileP -> Craft (Maybe (Config SSHConfig))
get = Craft.Config.get
bodyLookup :: String -> Body -> Maybe String
bodyLookup key body =
lookup (map toLower key) $ map ((,) <$> map toLower . fst <*> snd) body
cfgLookup :: String -> String -> SSHConfig -> Maybe String
cfgLookup sectionName key (SSHConfig sections') =
case body of
Just b -> bodyLookup key b
Nothing -> Nothing
where
body = maybeHead . catMaybes $ map f sections'
f (Host name b) | name == sectionName = Just b
| otherwise = Nothing
f _ = Nothing
maybeHead [] = Nothing
maybeHead (x:_) = Just x
instance Craftable UserConfig UserConfig where
watchCraft cfg = do
craft_ $ userDir $ cfg ^. user
w <- watchCraft_ $ file (userPath cfg)
& mode .~ Mode RW O O
& ownerAndGroup .~ cfg ^. user
& strContent .~ show cfg
return (w, cfg)
sshConfigParse :: Path Abs FileP -> String -> Craft SSHConfig
sshConfigParse fp s =
case runParser parser (show fp) s of
Left err -> $craftError $ show err
Right secs -> return $ SSHConfig secs
instance Show UserConfig where
show uc = show $ uc ^. userConfigs
showBody :: Body -> String
showBody body = indent 4 . unlines $ map (\(n, v) -> n ++ " " ++ v) body
instance Show Section where
show (Host hostname body) = "Host " ++ hostname ++ "\n" ++ showBody body
show (Match match body) = "Match " ++ match ++ "\n" ++ showBody body
parseTitle :: Parsec String (String, String)
parseTitle = do
space
type' <- try (string' "host") <|> string' "match"
void spaceChar
notFollowedBy eol
space
name <- anyChar `someTill` eol
return (type', name)
parseBody :: Parsec String [(String, String)]
parseBody = many bodyLine
bodyLine :: Parsec String (String, String)
bodyLine = do
notFollowedBy parseTitle
space
name <- someTill anyChar spaceChar
notFollowedBy eol
space
val <- between (char '"') (char '"') (many $ noneOf "\"")
<|> someTill anyChar end
void $ optional (space <|> void (many eol))
return (name, trim val)
parseSection :: Parsec String Section
parseSection = do
title <- parseTitle
body <- parseBody
return $ case map toLower (fst title) of
"host" -> Host (snd title) body
"match" -> Match (snd title) body
_ -> error "Craft.Config.SSH.parse failed. This should be impossible."
parser :: Parsec String Sections
parser = some parseSection