-
Notifications
You must be signed in to change notification settings - Fork 6
/
source.sh
executable file
·375 lines (327 loc) · 11.6 KB
/
source.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
#!/usr/bin/env bash
# -*- coding: utf-8 -*-
# @Synopsis Bash modular application framework.
# @Copyright Copyright 2009, James Pic
# @License Apache, unless otherwise specified by a file or a comment.
#
# <h4>The dumb framework</h4>
#
# This file is the framework. It is *all* the framework. The framework is
# nothing else but this file. All other features, such as configuration
# management, unit testing, documentation, logging etc ... are done in
# separate, reuseable, consistent, simple modules.
#
# <h4>Framework variables</h4>
#
# The role of this framework is to manage repositories of modules with the
# following variables:
# - $module_repo_paths is an associative array of :
# repo name => repo absolute path
# - $module_paths is an associative array of:
# module name => module absolute path
# - $module_status is an associative array of:
# module name => module status (string)
#
# <h4>Definition of a module</h4>
#
# A module is defined by a subdirectory of a repository with a source.sh file
# in it. That's all a module need. The module directory name is the name of the
# module. If hopefully it declares functions or variables then those should be
# prefixed by the module name and an underscore for example:
# yourmodule_somevar, yourmodule_somefunc.
#
# A module may have dependencies and control of their loading is inversed,
# which means that specifically named functions may be declared in the module
# source.sh file if required by the module:
# - modulename_pre_load(): prepare for sourcing dependencies,
# - modulename_load(): load dependencies,
# - modulename_post_load(): prepare to be useable,
#
# These function may also check if the system its module is being load on is
# suitable or not, and call module_unset() otherwise. For example, if a
# module requires a linux-vserver kernel or a BSD system.
#
# <h4>Inversion of control and polite functions</h4>
#
# Inversion of control: the overall program's flow of control is not dictated
# by the caller, but by the framework. This applies for the framework and
# several modules, like conf, at a basic level, and in a polite way.
#
# Polite functions: Generic reuseable functions usually take a module name
# string argument. It should let the actual module to overload what is it about
# to process. For example, conf_save() is polite, calling `conf_save
# yourmodule` will first check if yourmodule_conf_save() exists, and run it
# then return 0 if it does. Note that an overloading function can call its
# caller. "Civilized coding" sucks way less than reinventing OOP in Bash.
#
# That said, this is the general useage example:
#
## # add your repo:
## module_repo_add /path/to/yourrepo
## # find modules and submodules in your repo:
## module_repo yourrepo
## # source a module:
## module_source yourmodule # would call yourmodule_source
## # pre load a module:
## module_pre_load yourmodule # would call yourmodule_pre_load
## # then load a module:
## module_load yourmodule # would call yourmodule_load
## # then post load a module:
## module_post_load yourmodule # would call yourmodule_post_load
#
# Or, run all at once:
#
## module_repo_use /path/to/yourrepo
## module yourmodule # will do the source, pre_load, load and post_load
## module # same, with all modules
#
# <h4>Module status</h4>
#
# A module status corresponds to the last thing that was done with it, for
# instance either of the following values:
# - *find*: the module source.sh file was found:
# it is ready to source,
# - *source*: the module source.sh file was sourced:
# it is ready to _pre_load(),
# - *pre_load*: the module _pre_load() function was called:
# it is ready to _load(),
# - *load*: the module _load() function was called:
# it is ready to _post_load(),
# - *post_load*: the module _post_load() function was called:
# it is ready to use,
#
# <h4>Module dependencies</h4>
#
# Some research should be done, a possibility is a "loading queue"
# implementation.
#
# For now, call the module_load_core_modules() function before loading really
# optionnal modules.
#
# <h4>License agreement</h4>
#
# You swear that:
# - you will never use trailing slashes in paths,
# - you will always prefix your variables and functions correctly,
# - you will not pollute the environment with temporary variables,
# - you will not break backward compatibility, unless you warned,
# Check bash version. We need at least 4.0.x
# Lets not use anything like =~ here because
# that may not work on old bash versions.
#if [[ "$(awk -F. '{print $1 $2}' <<< $BASH_VERSION)" -lt 40 ]]; then
#echo "Sorry your bash version is too old!"
#echo "You need at least version 3.2 of bash"
#echo "Please install a newer version:"
#echo " * Either use your distro's packages"
#echo " * Or see http://www.gnu.org/software/bash/"
#return 2
#fi
# string repo name => string repo absolute path
declare -A module_repo_paths
# string module name => string module absolute path
declare -A module_paths
# string module name => string module status
declare -A module_status
# Temporary solution against module dependencies
function module_load_core_modules() {
module hack conf mlog
}
# (Re)-loads one or several module repository. See module/source.sh file
# documentation.
# @param repository names separated by spaces
function module_repo() {
module_repo_add "$@"
module_repo_find "${!module_repo_paths[@]}"
}
# (Re)-loads one or several modules. See module/source.sh file documentation.
# @param module names separated by spaces
function module() {
module_source "$@"
module_pre_load "$@"
module_load "$@"
module_post_load "$@"
}
# Adds one or several repo path after removing the trailing slash.
#
# If anything is strange, then use absolute paths.
#
# @param path to repositories separated by spaces
# @variable $module_repo_paths is updated
function module_repo_add() {
local abs_path=""
local repo_name=""
local len=0
while [[ -n "$1" ]]; do
# in case hack module is not loaded yet, then hack_realpath should
# not be depended on.
if [[ $OSTYPE =~ bsd ]]; then
abs_path="$(realpath "$1")"
else
abs_path="$(readlink -f "$1")"
fi
if [[ ${abs_path:(-1)} == "/" ]]; then
len=$(( ${#abs_path}-1 ))
abs_path="${abs_path:0:$len}"
fi
repo_name="${abs_path##*/}"
module_repo_paths[$repo_name]="$abs_path"
shift
done
}
# This function finds all modules and nested submodules in a given repo.
#
# With this example layout:
# - /yourpath/
# - /yourpath/foo/
# - /yourpath/foo/source.sh
# - /yourpath/foo/bar/source.sh
#
# It will register:
# - module "foo" with path "/yourpath/foo"
# - module "foo_bar" with path "/yourpath/foo/bar"
#
# It that example case, foo_bar functions should be prefixed by foo_bar_
# instead of just foo_.
#
# @param repository names separated by spaces
# @variable $module_paths and $module_status are updated
# @variable $module_repo_paths is read
function module_repo_find() {
local path=""
local module_path=""
local module_name=""
local rel_path=""
while [[ -n "$1" ]]; do
path="${module_repo_paths[$1]}"
while read -r source_path; do
module_path="$(dirname "$source_path")"
rel_path="${module_path#*"$path"/}"
module_name="${rel_path//\//_}"
module_paths[$module_name]="$module_path"
module_status[$module_name]="find"
done < <(find "$path" -name source.sh -print0 | xargs -0n 1)
shift
done
}
# Sources the source.sh file of the given modules
# @param module names, separated by space
# @variable $module_paths is read
# @variable $module_status is updated
# @polite will try yourmodule_source(), useful for reloading control
function module_source() {
while [[ -n "$1" ]]; do
local module_source="${1}_source"
if [[ -n "$(declare -f $module_source)" ]]; then
$module_source
else
local modsrc="${module_paths[$1]}"/source.sh
if [[ -r $modsrc ]]; then
source "$modsrc"
else
echo "Module '$1' source not found at $modsrc" >&2
fi
fi
module_status[$1]="source"
shift
done
}
# Run the _pre_load() function of any given module.
# @param module names, separated by space
# @variable $module_status is updated
# @calls yourmodule_pre_load()
function module_pre_load() {
while [[ -n "$1" ]]; do
local module_pre_load="${1}_pre_load"
if [[ -n "$(declare -f $module_pre_load)" ]]; then
$module_pre_load
fi
module_status[$1]="pre_load"
shift
done
}
# Run the _load() function of any given module.
# @param module names, separated by space
# @variable $module_status is updated
# @calls yourmodule_load()
function module_load() {
while [[ -n "$1" ]]; do
local module_load="${1}_load"
if [[ -n "$(declare -f $module_load)" ]]; then
$module_load
fi
module_status[$1]="load"
shift
done
}
# Run the _post_load() function of any given module.
# @param module names, separated by space
# @variable $module_status is updated
# @calls yourmodule_post_load()
function module_post_load() {
while [[ -n "$1" ]]; do
local module_post_load="${1}_post_load"
if [[ -n "$(declare -f $module_post_load)" ]]; then
$module_post_load
fi
module_status[$1]="post_load"
shift
done
}
# Will unset anything starting with given module names.
# This function should be called if a module figures that the system is not
# suitable.
# @param module names, separated by space
# @polite Will try to run yourmodule_unset().
function module_unset() {
local word=""
while [[ -n "$1" ]]; do
# polite module snippet
local module_overload="${1}_unset"
if [[ -n "$(declare -f $module_overload)" ]]; then
if [[ ! ${FUNCNAME[*]} =~ $module_overload ]]; then
$module_overload
return $?
fi
fi
local declared=$(declare | grep -o "^${1}.*")
for word in $declared; do
if [[ $word =~ ^$1 ]]; then
local to_unset=$(echo $word| grep -o "^${1}[^=(]*")
unset $to_unset
fi
done
shift
done
}
# This function takes a module name as first parameter and outputs its
# absolute path.
# It provides a reliable way for a script in your module to know its own
# location on the file system.
# This function is used by any module _load() function.
# Example usage:
# source "$(module_get_path yourmodule)"/functions.sh
# Example submodule usage:
# source "$(module_get_path yourmodule_submodule)"/functions.sh
# @param Module name
function module_get_path() {
echo ${module_paths[$1]}
}
# This function dumps all module variables.
function module_debug() {
if [[ 0 -lt ${#module_repo_paths[@]} ]]; then
echo "List of repo names and paths":
for repo_name in ${!module_repo_paths[@]}; do
echo " - ${repo_name} from '${module_repo_paths[$repo_name]}'"
done
else
echo "No repositories: use 'module_repo <path>' to add."
fi
if [[ 0 -lt ${#module_paths[@]} ]]; then
echo "List of loaded modules, statuses and paths:"
for module_name in ${!module_paths[@]}; do
echo " - ${module_name}, ${module_status[$module_name]} from '${module_paths[$module_name]}'"
done
else
echo "No loaded modules: use 'module <name>' to load."
fi
}