module Propellor.Property.ConfFile (
SectionStart,
SectionPast,
AdjustSection,
InsertSection,
adjustSection,
IniSection,
IniKey,
containsIniSetting,
lacksIniSetting,
hasIniSection,
lacksIniSection,
iniFileContains,
ShellKey,
containsShellSetting,
lacksShellSetting,
) where
import Propellor.Base
import Propellor.Property.File
import Data.List (isPrefixOf, foldl')
type SectionStart = Line -> Bool
type SectionPast = Line -> Bool
type AdjustSection = [Line] -> [Line]
type InsertSection = [Line] -> [Line]
adjustSection
:: Desc
-> SectionStart
-> SectionPast
-> AdjustSection
-> InsertSection
-> FilePath
-> Property UnixLike
adjustSection desc start past adjust insert = fileProperty desc go
where
go ls = let (pre, wanted, post) = foldl' find ([], [], []) ls
in if null wanted
then insert ls
else pre ++ adjust wanted ++ post
find (pre, wanted, post) l
| null wanted && null post && (not . start) l =
(pre ++ [l], wanted, post)
| (start l && null wanted && null post)
|| ((not . null) wanted && null post && (not . past) l) =
(pre, wanted ++ [l], post)
| otherwise = (pre, wanted, post ++ [l])
type IniSection = String
type IniKey = String
iniHeader :: IniSection -> String
iniHeader header = '[' : header ++ "]"
adjustIniSection
:: Desc
-> IniSection
-> AdjustSection
-> InsertSection
-> FilePath
-> Property UnixLike
adjustIniSection desc header =
adjustSection
desc
(== iniHeader header)
("[" `isPrefixOf`)
containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property UnixLike
containsIniSetting f (header, key, value) = adjustIniSection
(f ++ " section [" ++ header ++ "] contains " ++ key ++ "=" ++ value)
header
go
(++ [confheader, confline])
f
where
confheader = iniHeader header
confline = key ++ "=" ++ value
go [] = [confline]
go (l:ls) = if isKeyVal l then confline : ls else l : go ls
isKeyVal x = (filter (/= ' ') . takeWhile (/= '=')) x `elem` [key, '#':key]
lacksIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property UnixLike
lacksIniSetting f (header, key, value) = adjustIniSection
(f ++ " section [" ++ header ++ "] lacks " ++ key ++ "=" ++ value)
header
(filter (/= confline))
id
f
where
confline = key ++ "=" ++ value
hasIniSection :: FilePath -> IniSection -> [(IniKey, String)] -> Property UnixLike
hasIniSection f header keyvalues = adjustIniSection
("set " ++ f ++ " section [" ++ header ++ "]")
header
go
(++ confheader : conflines)
f
where
confheader = iniHeader header
conflines = map (\(key, value) -> key ++ "=" ++ value) keyvalues
go _ = confheader : conflines
lacksIniSection :: FilePath -> IniSection -> Property UnixLike
lacksIniSection f header = adjustIniSection
(f ++ " lacks section [" ++ header ++ "]")
header
(const [])
id
f
iniFileContains :: FilePath -> [(IniSection, [(IniKey, String)])] -> RevertableProperty UnixLike UnixLike
iniFileContains f l = f `hasContent` content <!> notPresent f
where
content = concatMap sectioncontent l
sectioncontent (section, keyvalues) = iniHeader section :
map (\(key, value) -> key ++ "=" ++ value) keyvalues
type ShellKey = String
containsShellSetting :: FilePath -> (ShellKey, String) -> Property UnixLike
containsShellSetting f (k, v) = adjust `before` dedup
where
adjust = adjustSection
(f ++ " contains " ++ k ++ "=" ++ v)
isline
(not . isline)
(const [line])
(++ [line])
f
dedup = fileProperty "" dedup' f
dedup' ls = let (pre, wanted, post) = foldl' find ([], [], []) ls
in pre ++ wanted ++ map commentIfIsline post
find (pre, wanted, post) l
| null wanted && (not . isline) l = (pre ++ [l], wanted, post)
| null wanted && isline l = (pre, [l], post)
| otherwise = (pre, wanted, post ++ [l])
commentIfIsline l
| isline l = '#':l
| otherwise = l
isline s = (k ++ "=") `isPrefixOf` s
line = k ++ "=" ++ shellEscape v
lacksShellSetting :: FilePath -> (ShellKey, String) -> Property UnixLike
lacksShellSetting f (k, v) =
fileProperty (f ++ "lacks shell setting " ++ k ++ "=" ++ v) go f
where
go ls = map commentOut ls
commentOut l
| (k ++ "=") `isPrefixOf` l = '#':l
| otherwise = l