1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
module Propellor.Property.ConfFile (
-- * Generic conffiles with sections
SectionStart,
SectionPast,
AdjustSection,
InsertSection,
adjustSection,
-- * Windows .ini files
IniSection,
IniKey,
containsIniSetting,
lacksIniSection,
) where
import Propellor.Base
import Propellor.Property.File
import Data.List (isPrefixOf, foldl')
-- | find the line that is the start of the wanted section (eg, == "<Foo>")
type SectionStart = Line -> Bool
-- | find a line that indicates we are past the section
-- (eg, a new section header)
type SectionPast = Line -> Bool
-- | run on all lines in the section, including the SectionStart line;
-- can add, delete, and modify lines, or even delete entire section
type AdjustSection = [Line] -> [Line]
-- | if SectionStart does not find the section in the file, this is used to
-- insert the section somewhere within it
type InsertSection = [Line] -> [Line]
-- | Adjusts a section of conffile.
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])
-- | Name of a section of an .ini file. This value is put
-- in square braces to generate the section header.
type IniSection = String
-- | Name of a configuration setting within a .ini file.
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`)
-- | Ensures that a .ini file exists and contains a section
-- with a key=value setting.
containsIniSetting :: FilePath -> (IniSection, IniKey, String) -> Property UnixLike
containsIniSetting f (header, key, value) =
adjustIniSection
(f ++ " section [" ++ header ++ "] contains " ++ key ++ "=" ++ value)
header
go
(++ [confheader, confline]) -- add missing section at end
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]
-- | Ensures that a .ini file does not contain the specified section.
lacksIniSection :: FilePath -> IniSection -> Property UnixLike
lacksIniSection f header =
adjustIniSection
(f ++ " lacks section [" ++ header ++ "]")
header
(const []) -- remove all lines of section
id -- add no lines if section is missing
f
|