-
Notifications
You must be signed in to change notification settings - Fork 40
/
Copy pathIni.fs
167 lines (142 loc) · 7.24 KB
/
Ini.fs
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright (c) Microsoft Corporation.
/// INI configuration file helpers
module Microsoft.FSharpLu.Ini
open System.IO
open System.Text.RegularExpressions
open Microsoft.FSharpLu.Collections
/// The name of an unnamed section in an INI configuration
[<Literal>]
let UnnamedSectionName = "_unnamed_"
/// The parsed section of an INI configuration
type Section = Map<string,string>
/// The Parsed INI configuration
type Configuration = Map<string,Section>
/// Gets a section from an Configuration (returns an empty section if the section name was not found)
let getSection (sectionName:string) (configuration:Configuration) =
configuration.TryFind(sectionName) |> Option.defaultValue Map.empty
/// True if the section name was found Ini configuration
let hasSection (sectionName:string) (configuration:Configuration) : bool =
configuration.TryFind(sectionName) |> Option.isSome
/// Tries to get a section from an Configuration
let tryGetSection (sectionName:string) (configuration:Configuration) =
configuration.TryFind(sectionName)
/// True if the named parameter exists in the section
let hasParameter (parameterName:string) (section:Section) : bool =
section.TryFind(parameterName) |> Option.isSome
/// Tries to get the value of a parameter in the section
let tryGetValue (parameterName:string) (section:Section) =
section.TryFind(parameterName)
/// Gets the value of a parameter in the section, fail if the parameter name is not found
let getValueOrFail (parameterName:string) (section:Section) =
match section.TryFind(parameterName) with
| Some value ->
value
| None ->
failwith (sprintf "ini parameter %s not found" parameterName)
/// Gets the value of a parameter in the section (returns the specified default value if not found)
let getValueOrDefault (parameterName:string) (defaultValue:string) (section:Section) =
section.TryFind(parameterName) |> Option.defaultValue defaultValue
// True if the parameter exists in the section
let hasParameterInSection (sectionName:string) (parameterName:string) (configuration:Configuration) =
configuration
|> getSection sectionName
|> hasParameter parameterName
/// Tries to get the value of a parameter in the named section
let tryGetValueFromSection (sectionName:string) (parameterName:string) (configuration:Configuration) =
configuration
|> getSection sectionName
|> tryGetValue parameterName
/// Gets the value of a parameter in the named section (returns the default value if not found)
let getValueFromSectionOrDefault (sectionName:string) (parameterName:string) (defaultValue:string) (configuration:Configuration) =
configuration
|> tryGetValueFromSection sectionName parameterName
|> Option.defaultValue defaultValue
/// Regex parser for INI configuration file syntax
let private matcher = Regex("\s*\[(?<section>[^\]]+?)\s*]|^;(?<comment>.*)$|\s*(?<name>[^;=]+?)\s*=\s*(?<value>.*?)\s*$|(?<whitespace>\s*)", RegexOptions.Compiled|||RegexOptions.Singleline)
let private (|Section|NameValue|Whitespace|Comment|Error|) (line:string) =
match matcher.Match(line) with
| matchResult when matchResult.Success && matchResult.Groups.["section"].Success ->
Section matchResult.Groups.["section"].Value
| matchResult when matchResult.Success && matchResult.Groups.["name"].Success && matchResult.Groups.["value"].Success ->
NameValue( matchResult.Groups.["name"].Value, matchResult.Groups.["value"].Value)
| matchResult when matchResult.Success && matchResult.Groups.["comment"].Success ->
Comment matchResult.Groups.["comment"].Value
| matchResult when matchResult.Success && matchResult.Groups.["whitespace"].Success ->
Whitespace
| _ ->
Error
/// Parse an ini configuration from a sequence of lines
let parseConfigurationFromLines (lines:string seq) (sourceContext:string) : Configuration =
let leadingNameValueLinesWithNoSection =
lines
|> Seq.takeWhile(fun line -> match line with Section _ -> false | _ -> true)
let configuration:Configuration =
lines |> Seq.foldi(fun lineNumber sectionMap line ->
match line with
| Section sectionName ->
let section:Section =
lines
|> Seq.skip (lineNumber + 1)
|> Seq.takeWhile(fun line -> match line with Section name -> false | _ -> true )
|> Seq.foldi(fun sectionLineNumber section line ->
match line with
| NameValue(name,value) ->
section.Add(name,value)
| Error ->
failwith (sprintf "Invalid line detected in ini file at line #%i in section %s of %s" (lineNumber + sectionLineNumber) sectionName sourceContext)
| _ ->
section
) Map.empty
sectionMap.Add(sectionName, section)
| Error ->
failwith (sprintf "Invalid line detected in ini file at line #%i of %s" lineNumber sourceContext)
| _ ->
sectionMap
) Map.empty
if Seq.isEmpty leadingNameValueLinesWithNoSection then
configuration
else
let unnamedSection:Section =
leadingNameValueLinesWithNoSection
|> Seq.foldi(fun lineNumber section line ->
match line with
| NameValue(name,value) ->
section.Add(name,value)
| Error ->
failwith (sprintf "Invalid line detected in ini file at line #%i in unnamed section of %s" lineNumber sourceContext)
| _ ->
section
) Map.empty
configuration.Add(UnnamedSectionName,unnamedSection)
/// Read and parse an ini file and return the IniFile type containing of all sections which, in turn, contains a dictionary of all name/value pairs for that section
let readConfigurationFile (path:string) =
parseConfigurationFromLines (System.IO.File.ReadAllLines(path)) path
/// Convert to a list of ini text lines
let configurationToLines (configuration:Configuration) =
let unnamedSection =
if configuration.ContainsKey(UnnamedSectionName) then
configuration.Item(UnnamedSectionName)
|> Seq.map(fun nameValuePair -> sprintf "%s=%s" nameValuePair.Key nameValuePair.Value)
|> Seq.toList
else
[]
let namedSections =
configuration
|> Seq.sortBy(fun nvp -> nvp.Key)
|> Seq.filter(fun nvp -> nvp.Key <> UnnamedSectionName)
|> Seq.map(fun nameValuePair ->
sprintf "[%s]" nameValuePair.Key
::
(nameValuePair.Value
|> Seq.sortBy(fun nvp -> nvp.Key)
|> Seq.map(fun nameValuePair -> sprintf "%s=%s" nameValuePair.Key nameValuePair.Value) |> Seq.toList)
)
List.append unnamedSection (List.concat namedSections)
/// Writes the ini file to disk
let writeConfigurationFile (configuration:Configuration) (path:string) =
File.WriteAllLines(path, configurationToLines configuration)
/// Converts this IniFile type to a string
let configurationToString configuration =
configurationToLines configuration
|> List.toSeq
|> String.concat System.Environment.NewLine