-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlibini
360 lines (304 loc) · 9.43 KB
/
libini
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
#!/usr/bin/env bash
. bpm
bpm::include array
bpm::include assign
bpm::include string
bpm::include is
# Retrieves a list of sections, list of keys, or a key's value.
#
# $1 - Target variable.
# $2 - Prefix, required.
# $3 - Section, optional. Required if you want to access a key.
# $4 - Key name, optional
#
# Examples
#
# # Assume the following INI file:
# #
# # [sectionName]
# # keyName = theValue
#
# ini::parse INI "$iniFileContents"
# ini::get target INI
# set | grep ^target=
# # target=([0]="" [1]="sectionName")
#
# ini::get target INI sectionName
# set | grep ^target=
# # target=([0]="keyName")
#
# ini::get target INI sectionName keyName
# # target="theValue"
#
# Returns nothing
ini::get() {
local keyName target value
target=${1-}
shift
# shellcheck disable=SC2068
ini::keyName keyName "$@"
if [[ "$#" -lt 3 ]]; then
if [[ -z "${!keyName+_}" ]]; then
value=()
else
# shellcheck disable=SC1087
eval "value=( \${$keyName[@]+\"\${$keyName[@]}\"} )"
fi
# shellcheck disable=SC2068
local "$target" && assign::array "$target" ${value[@]+"${value[@]}"}
elif ! is::set "$keyName"; then
local "$target" && assign::value "$target" ""
else
local "$target" && assign::byRef "$target" "$keyName"
fi
}
# Private: Sets up a new, blank INI structure in memory.
#
# Hardcode the empty section name as the first entry. Required to have a
# single element and this also initializes the values.
#
# $1 - Prefix, required.
#
# Returns nothing.
ini::initializePrefix() {
assign::array "$1" ""
ini::keyName emptySection "$1" ""
assign::array "$emptySection"
}
# Encode a prefix, section and key in order to generate a safe variable name
# for Bash. All section names and key names are case insensitive.
#
# $1 - Prefix for environment variables, required.
# $2 - Section, optional. Required if you want to access a key.
# $3 - Key name, optional.
ini::keyName() {
local name part target
target="$1"
shift
name="$1"
if [[ $# -gt 1 ]]; then
string::toUpper part "$2"
string::toHex part "$part"
name+="_$part"
if [[ $# -gt 2 ]]; then
string::toUpper part "$3"
string::toHex part "$part"
name+="_$part"
fi
fi
local "$target" && assign::value "$target" "$name"
}
# Parses an INI file into the environment.
#
# $1 - Prefix for environment variables that will be assigned.
# $2 - INI file contents
#
# Variables are stored in memory, encoded in hex to ensure the variable names
# are valid variables.
#
# PREFIX=(array of unencoded section names)
# PREFIX_SECTIONENCODED=(array of unencoded key names)
# PREFIX_SECTIONENCODED_KEYENCODED=value
#
# Duplicated section names are merged together. Duplicated keys will have their
# values changed into an array so all of the values will be captured. For
# example, the INI on the left will be parsed into variables similar to the
# right side. Keep in mind that section names and keys are case insensitive.
#
# [Section1] PREFIX=(SECTION1 SECTION2
# Key=value
# kEy=value2 PREFIX_SECTION1=(KEY)
#
# [Section2] PREFIX_SECTION2=(ANOTHER)
# another=value
# PREFIX_SECTION1_KEY=(value value2 value3)
# [SECTION1]
# KEY=value3 PREFIX_SECTION2_ANOTHER=value
#
# You should not use the generated environment variables directly. Instead, use
# the other utility functions (eg. `ini::get`) to access the values.
#
# $1: Prefix for variables.
# $2: INI file contents
ini::parse() {
local IFS line prefix section
prefix=$1
section=
IFS=$'\n'
ini::initializePrefix "$prefix"
while read line; do
string::trim line "$line"
#: Eliminate comments starting with # or ;
line=${line#\#*}
line=${line#;*}
if [[ "${line:0:1}" == '[' ]] && [[ "${line:${#line}-1}" == "]" ]]; then
#: Remove brackets
line=${line:1:${#line}-2}
string::trim section "$line"
elif [[ -n "$line" ]]; then
IFS="=" read key value <<< "$line"
string::trim key "$key"
if [[ -n "$key" ]]; then
string::trim value "$value"
ini::set "$prefix" "$section" "$key" "$value"
fi
fi
done <<< "$2"
}
# Generates a unique INI file prefix that has not yet been used.
#
# $1 - Destination variable name for the INI prefix.
#
# Returns nothing.
ini::prefix() {
local prefix
#: 1 in 32k
prefix="ini_${RANDOM}"
while is::set "$prefix"; do
#: 1 in 1 billion
prefix="ini_${RANDOM}_${RANDOM}"
done
local "$1" && assign::value "$1" "$prefix"
}
# Removes a key from a section or a section from a loaded INI. Frees up
# memory by unsetting environment variables. If no section is specified,
# removes all of the INI from memory.
#
# $1 - Prefix for environment variables, required.
# $2 - Section name, optional.
# $3 - Key name, optional.
#
# Returns nothing.
ini::remove() {
local index key keys encoded IFS upper
IFS=$'\n' # Needed for array slicing before Bash 4.0-rc1
if [[ $# -gt 2 ]]; then
# Remove key
ini::keyName encoded "$1" "$2" "$3"
unset "$encoded"
# Remove key from the section
ini::keyName encoded "$1" "$2"
assign::byRef keys "$encoded"
string::toUpper upper "$3"
if array::indexOf index "$upper" ${keys[@]+"${keys[@]}"}; then
keys=("${keys[@]:0:index}" "${keys[@]:index+1}")
fi
assign::array "$encoded" ${keys[@]+"${keys[@]}"}
elif [[ $# -gt 1 ]]; then
# Remove keys from section
ini::keyName encoded "$1" "$2"
assign::byRef keys "$encoded"
if [[ "${#keys[@]}" -gt 0 ]]; then
for key in "${keys[@]}"; do
ini::remove "$1" "$2" "$key"
done
fi
# Remove the section
unset "$encoded"
# Remove the section from the list of sections
ini::keyName encoded "$1"
assign::byRef keys "$encoded"
string::toUpper upper "$2"
if array::indexOf index "$upper" ${keys[@]+"${keys[@]}"}; then
keys=("${keys[@]:0:index}" "${keys[@]:index+1}")
fi
assign::array "$encoded" ${keys[@]+"${keys[@]}"}
else
# Remove sections
ini::keyName encoded "$1"
assign::byRef keys "$1"
for key in "${keys[@]}"; do
ini::remove "$1" "$key"
done
# Remove the list
unset "$1"
fi
}
# Assigns a value to a property
#
# This will set the value if it isn't already assigned. If it is
# assigned, it will add the value to the array in memory.
#
# $1 - Prefix for environment variables, required.
# $2 - Section name, required.
# $3 - Key, required.
# $4 - Value, required.
#
# Returns nothing.
ini::set() {
local keyEncoded keys keyUpper previousValue sectionEncoded sectionUpper sections
assign::byRef sections "$1"
if ! is::array sections; then
ini::initializePrefix "$1"
sections=("")
fi
string::toUpper sectionUpper "$2"
ini::keyName sectionEncoded "$1" "$2"
string::toUpper keyUpper "$3"
ini::keyName keyEncoded "$1" "$2" "$3"
if array::contains "$sectionUpper" ${sections[@]+"${sections[@]}"}; then
assign::byRef keys "$sectionEncoded"
else
sections[${#sections[@]}]=$sectionUpper
assign::array "$1" "${sections[@]}"
keys=()
fi
if array::contains "$keyUpper" ${keys[@]+"${keys[@]}"}; then
if ! is::array "$keyEncoded"; then
assign::value previousValue "${!keyEncoded}"
previousValue=( "$previousValue" )
else
assign::byRef previousValue "$keyEncoded"
fi
assign::array "$keyEncoded" "${previousValue[@]}" "$4"
else
keys[${#keys[@]}]=$keyUpper
assign::array "$sectionEncoded" "${keys[@]}"
assign::value "$keyEncoded" "$4"
fi
}
# Transfers the in-memory contents of the INI file back into a string.
#
# You will lose all of your comments that were in the INI file. The order of
# the sections may not be preserved. The order of the keys may not be
# preserved. Any values that were overwritten in the original INI file
# shall be eliminated.
#
# Think of it as a cleansing to see what's really parsed instead of losing
# data.
#
# $1 - Destination variable name for the string.
# $2 - INI prefix
#
# Returns nothing.
ini::toString() {
local key keys nl prefix result section sections value values
prefix=$2
nl=$'\n'
ini::get sections "$prefix"
result=
for section in "${sections[@]}"; do
keys=()
ini::get keys "$prefix" "$section"
if [[ "${#keys[@]}" -gt 0 ]]; then
# Skipping the section name for root entries always works
# because it's always hardcoded as the first section name.
if [[ -n "$section" ]]; then
result="$result[$section]$nl"
fi
for key in "${keys[@]}"; do
values=()
ini::get values "$prefix" "$section" "$key"
if is::array values; then
for value in "${values[@]}"; do
result="$result$key=$value$nl"
done
else
result="$result$key=$values$nl"
fi
done
result="$result$nl"
fi
done
local "$1" && assign::value "$1" "$result"
}