-
Notifications
You must be signed in to change notification settings - Fork 0
/
flex_ini.sh
519 lines (424 loc) · 14.8 KB
/
flex_ini.sh
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
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
# Defaults
# --
# You may change these here or overwrite them
# within your scripts, whatever works best for
# your use case.
auto_create_ini_on_load=true
auto_save_on_changes=false
back_up_changes_on_save=true
back_up_changes_on_save_as=false
reassign_file_permissions_when_possible=false
tmp_directory="/tmp"
declare -gA ini_associations
declare -gA ini_unsaved_changes
declare -gA ini_loaded
# Private Functions
# --
# It's best to not call/modify these directly from your codebase
# unless you know what you're doing!
# @private private_flex_ini_error
# --
# Helper method to write consistent error logs
private_flex_ini_error() {
local msg="$1"
echo "[ FlexIni Error ] $msg"
}
# @private private_flex_ini_mark_as_changed
# --
# Marks an ini array as changed.
private_flex_ini_mark_as_changed() {
local ini_identifier=$(private_flex_ini_format_id "$1")
ini_unsaved_changes["$ini_identifier"]=true
}
# @private private_flex_ini_mark_as_unchanged
# --
# Marks an ini array as unchanged.
private_flex_ini_mark_as_unchanged() {
local ini_identifier=$(private_flex_ini_format_id "$1")
ini_unsaved_changes["$ini_identifier"]=false
}
# @private private_flex_ini_required
# --
# Helper function to identify required variables and return 1
# if they are not provided.
private_flex_ini_required() {
local k="$1"
local v="$2"
if [ -z "$v" ]; then
private_flex_ini_error "value $k is required but was not provided"
return 1
fi
}
# @private private_flex_ini_require_loaded
# --
# Function which returns 1 if the ini id has not
# yet beenloaded.
private_flex_ini_require_loaded() {
local ini_identifier="$1"
if ! private_flex_ini_has_been_loaded "$ini_identifier"; then
private_flex_ini_error "the ini id $ini_identifier has not-yet been loaded"
return 1
fi
}
# @private private_flex_ini_mark_as_loaded
# --
# Marks an ini array as already loaded.
private_flex_ini_mark_as_loaded() {
local ini_identifier=$(private_flex_ini_format_id "$1")
ini_loaded["$ini_identifier"]=true
}
# @private private_flex_ini_has_been_loaded
# --
# Returns 0 if the array has already been loaded,
# returns 1 if it has not.
private_flex_ini_has_been_loaded() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local loaded="${ini_loaded[$ini_identifier]}"
if [ "$loaded" == "true" ]; then
return 0
else
return 1
fi
}
# @private private_flex_ini_format_id
# --
# Format the supplied ini id to make sure it's
# compatible with naming an array.
private_flex_ini_format_id() {
local default_ini_array_name="default"
local ini_identifier="${1:-$default_ini_array_name}"
local formatted="${ini_identifier// /_}"
echo "$formatted" | tr - _
}
# @private private_flex_ini_get_array_name
# --
# Get the name of the array based on the ini id.
private_flex_ini_get_array_name() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local ini_name="${ini_identifier}_ini"
echo "$ini_name"
}
# @private private_get_ini_file_path
# --
# Get the file path for a specific ini id.
private_get_ini_file_path() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local FILE_PATH="${ini_associations[$ini_identifier]}"
if [ -z "$FILE_PATH" ]; then
log error "Error, no file associated with the ini id ${ini_identifier}"
return 1
fi
echo "${FILE_PATH}"
}
# @private private_flex_ini_init
# --
# Initialize an ini file and load it into its array. This
# function must be called initially so that we have an array
# loaded when you try to reference a particular id.
private_flex_ini_init() {
local ini_file="$1"
local ini_identifier=$(private_flex_ini_format_id "$2")
local ini=$(private_flex_ini_get_array_name "$ini_identifier")
declare -gA "$ini"
ini_associations["$ini_identifier"]="$ini_file"
}
# @private private_flex_ini_create
# --
# Create an ini file at the specified path.
private_flex_ini_create() {
local ini_file="$1"
private_flex_ini_required "ini_file" "$ini_file" || return 1
touch "$ini_file" || return 1
}
# Public Functions
# --
# These functions are the ones you want to call from your scripts
# since they are meant to work together and validate various items.
# @public flex_ini_load
# --
# You'll need to call this method first in order to load up your
# current values in your ini file and populate the array.
flex_ini_load() {
local ini_file="$1"
local ini_identifier=$(private_flex_ini_format_id "$2")
local force_reload="${3:-false}"
if [ "$force_reload" != "true" ] && private_flex_ini_has_been_loaded "$ini_identifier"; then
return 0
fi
if [ "$force_reload" == "true" ] && private_flex_ini_has_been_loaded "$ini_identifier"; then
flex_ini_clear "$ini_identifier"
fi
if [ ! -f "$ini_file" ]; then
if [ "$auto_create_ini_on_load" == "true" ]; then
if ! private_flex_ini_create "$ini_file"; then
flex_ini_error "ini file could not be auto-created at ${ini_file}"
return 1
fi
else
flex_ini_error "ini file not found at ${ini_file} and auto_create_ini_on_load was not 'true' so we did not try to create it"
return 1
fi
fi
private_flex_ini_init "$ini_file" "$ini_identifier"
local ini=$(private_flex_ini_get_array_name "$ini_identifier")
local section=""
local key=""
local value=""
local section_regex="^\[(.+)\]"
local key_regex="^([^ =]+) *= *(.*) *$"
local comment_regex="^;"
while IFS= read -r line; do
if [[ $line =~ $comment_regex ]]; then
continue
elif [[ $line =~ $section_regex ]]; then
section="${BASH_REMATCH[1]}."
elif [[ $line =~ $key_regex ]]; then
key="${BASH_REMATCH[1]}"
value="${BASH_REMATCH[2]}"
[[ $value == *\$* ]] && eval "value=\"$value\""
local name_var=$ini'["'${section}${key}'"]="'$value'"'
eval "$name_var"
fi
done <"$ini_file"
private_flex_ini_mark_as_unchanged "$ini_identifier"
private_flex_ini_mark_as_loaded "$ini_identifier"
}
# @public flex_ini_reload
# --
# Reload an already-loaded ini ID. Please note, if you have not, in
# fact, already loaded the ini ID, this function will fail.
flex_ini_reload() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local destination_ini_path=$(private_get_ini_file_path "$ini_identifier")
if [ -z "$destination_ini_path" ]; then
echo "[ Flex INI Error ] No INI filepath could be found from which to reload. Are you sure you loaded the config file for ${ini_identifier}?"
fi
flex_ini_clear "$ini_identifier"
flex_ini_load "$destination_ini_path" "$ini_identifier"
}
# @public flex_ini_clear
# --
# Clears an ini ID completely from our loaded configs. You
# probably shouldn't need to use this, but it could be helpful
# during debugging.
flex_ini_clear() {
local ini_identifier=$(private_flex_ini_format_id "$1")
private_flex_ini_require_loaded "$ini_identifier" || return 1
unset ini_unsaved_changes["$ini_identifier"]
unset ini_loaded["$ini_identifier"]
local current_array_name=$(private_flex_ini_get_array_name "$ini_identifier")
unset "$current_array_name"
}
# @public flex_ini_get
# --
# Fetches a value from the specified ini array.
flex_ini_get() {
local key="$1"
local ini_identifier=$(private_flex_ini_format_id "$2")
local array_name=$(private_flex_ini_get_array_name "$ini_identifier")
private_flex_ini_required "key" "$key" || return 1
local name_var='${'$array_name'['$key']}'
local value=$(eval echo "$name_var")
echo "$value"
}
# @public flex_ini_has
# --
# Returns 0 if the key exists, 1 if it does not.
flex_ini_has() {
local key="$1"
local ini_identifier=$(private_flex_ini_format_id "$2")
private_flex_ini_required "key" "$key" || return 1
private_flex_ini_require_loaded "$ini_identifier" || return 1
[[ $(flex_ini_get "$key" "$ini_identifier") ]]
}
# @public flex_ini_update
# --
# This creates or updates a value in your ini array.
# Note: this does not save it, you'll need to call
# that separately.
flex_ini_update() {
local key="$1"
local value="$2"
local ini_identifier=$(private_flex_ini_format_id "$3")
local array_name=$(private_flex_ini_get_array_name "$ini_identifier")
private_flex_ini_required "key" "$key" || return 1
private_flex_ini_require_loaded "$ini_identifier" || return 1
local assign_var=$array_name'["'$key'"]="'"${value}"'"'
eval "$assign_var" ||
return 1
if [ "$auto_save_on_changes" == "true" ]; then
flex_ini_save "$ini_identifier"
else
private_flex_ini_mark_as_changed "$ini_identifier"
fi
}
# @public flex_ini_delete
# --
# Remove a value from your ini array.
# Note: this does not save it, you'll need to call
# that separately.
flex_ini_delete() {
local key="$1"
local ini_identifier=$(private_flex_ini_format_id "$2")
private_flex_ini_required "key" "$key" || return 1
private_flex_ini_require_loaded "$ini_identifier" || return 1
local array_name=$(private_flex_ini_get_array_name "$ini_identifier")
local assign_var='unset '$array_name'["'$key'"]'
eval "$assign_var"
if [ "$auto_save_on_changes" == "true" ]; then
flex_ini_save "$ini_identifier"
else
private_flex_ini_mark_as_changed "$ini_identifier"
fi
}
# @public flex_ini_save
# --
# Save the values in the array back to the correct file.
# If using the file you specified when you loaded the ini
# config.
flex_ini_save() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local override_path="$2"
local default_destination_ini_path=$(private_get_ini_file_path "$ini_identifier")
private_flex_ini_require_loaded "$ini_identifier" || return 1
# The save-as logic path first
if [ -n "$override_path" ]; then
# pre-create the file if it doesn't exist just to make sure we
# are actually able to create a file there.
if [ ! -f "$override_path" ]; then
if ! touch "$override_path"; then
private_flex_ini_error "unable to create file specified at $override_path"
return 1
fi
fi
# set the destination ini path to whichever new path was specified
local destination_ini_path="$override_path"
# use the val from 'save as' to determine whether to back up
local should_back_up_before_save="${back_up_changes_on_save_as}"
local is_save_as_operation=true
else
# set the destination ini path to the one specified on load
local destination_ini_path="$default_destination_ini_path"
# use the val from 'save' to determine whether to back up
local should_back_up_before_save="${back_up_changes_on_save}"
local is_save_as_operation=false
fi
# If param is true, then fetch the user/group from the current ini
# file and attempt to re-assign ownership when file is saved if
# the current user is different from the owner of the file
if [ "$reassign_file_permissions_when_possible" == "true" ]; then
local file_owner=$(stat --format '%U' "${destination_ini_path}")
local file_group=$(stat --format '%G' "${destination_ini_path}")
local current_user=$(echo "$USER")
if [ "$file_owner" != "$current_user" ]; then
local should_reassign_file_permissions=true
fi
fi
local current_section=""
local has_free_keys=false
local ini_file=$(mktemp "${tmp_directory}/flexini.XXXXXX")
for key in $(flex_ini_keys "$ini_identifier"); do
[[ $key == *.* ]] && continue
has_free_keys=true
local value=$(flex_ini_get "$key" "$ini_identifier")
echo "$key = $value" >>"$ini_file"
done
[[ "${has_free_keys}" == "true" ]] && echo >>"$ini_file"
for key in $(flex_ini_keys "$ini_identifier"); do
[[ $key == *.* ]] || continue
local value=$(flex_ini_get "$key" "$ini_identifier")
IFS="." read -r section_name key_name <<<"$key"
if [[ "$current_section" != "$section_name" ]]; then
[[ $current_section ]] && echo >>"$ini_file"
echo "[$section_name]" >>"$ini_file"
current_section="$section_name"
fi
echo "$key_name = $value" >>"$ini_file"
done
# Back up the destination ini file if specified
if [ "$should_back_up_before_save" == "true" ]; then
cp -f "$destination_ini_path" "${destination_ini_path}.bak" ||
private_flex_ini_error "could not make the backup copy of the ini file at ${destination_ini_path}"
fi
# Replace the destination ini file with the tmp file
if ! mv -f "$ini_file" "$destination_ini_path"; then
private_flex_ini_error "could not move tmp ini file to its destination at ${destination_ini_path} -- data was not saved to disk"
return 1
fi
# Try to update the permissions of the file, if set
if [ "$should_reassign_file_permissions" == "true" ]; then
chown "${file_owner}":"${file_group}" "${destination_ini_path}" ||
private_flex_ini_error "our attempt to reassign file permissions to '${file_owner}:${file_group} failed for file at ${destination_ini_path}"
fi
# Only mark the ini as unchanged if we saved it to
# the original file.
if [ "$is_save_as_operation" != "true" ]; then
private_flex_ini_mark_as_unchanged "$ini_identifier"
fi
}
# @public flex_ini_save_as
# --
# A helper function to initiate a save-as.
flex_ini_save_as() {
local override_path="$1"
local ini_identifier=$(private_flex_ini_format_id "$2")
private_flex_ini_require_loaded "$ini_identifier" || return 1
flex_ini_save "$ini_identifier" "$override_path" || return 1
}
# @public flex_ini_has_unsaved
# --
# Returns 0 if the array has unsaved changes,
# returns 1 if it does not.
flex_ini_has_unsaved() {
local ini_identifier=$(private_flex_ini_format_id "$1")
private_flex_ini_require_loaded "$ini_identifier" || return 1
local has_unsaved="${ini_unsaved_changes["$ini_identifier"]}"
if [ "$has_unsaved" == "true" ]; then
return 0
else
return 1
fi
}
# @public flex_ini_reset
# --
# Removes all data from the stores. Be careful with this one!
flex_ini_reset() {
for i in "${!ini_associations[@]}"; do
local array_name=$(private_flex_ini_get_array_name "$i")
unset "$array_name"
unset ini_associations["$i"]
done
for i in "${!ini_unsaved_changes[@]}"; do
unset ini_unsaved_changes["$i"]
done
for i in "${!ini_loaded[@]}"; do
unset ini_loaded["$i"]
done
}
# @public flex_ini_show
# --
# Show all loaded key-value pairs (whether or not they've been saved).
flex_ini_show() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local file_path=$(private_get_ini_file_path "$ini_identifier")
private_flex_ini_require_loaded "$ini_identifier" || return 1
echo "[ ${ini_identifier} ]"
echo "--"
for key in $(flex_ini_keys "$ini_identifier"); do
local value=$(flex_ini_get "$key" "$ini_identifier")
echo "$key = $value"
done
echo ""
}
# @public flex_ini_keys
# --
# Get an array of all keys in your ini array (whether or not they)
# have been saved.
flex_ini_keys() {
local ini_identifier=$(private_flex_ini_format_id "$1")
local array_name=$(private_flex_ini_get_array_name "$ini_identifier")
private_flex_ini_require_loaded "$ini_identifier" || return 1
local name_var='${!'$array_name'[@]}'
local keys=($(eval echo "$name_var"))
for a in "${keys[@]}"; do echo "$a"; done | sort
}