|
@@ -0,0 +1,231 @@
|
|
1
|
+#!/bin/bash
|
|
2
|
+
|
|
3
|
+shellfu import sfdoc
|
|
4
|
+shellfu import pretty
|
|
5
|
+
|
|
6
|
+#
|
|
7
|
+# Shellfu plugin interface
|
|
8
|
+#
|
|
9
|
+# Discover, query, select and run plugins inside subshells so that they
|
|
10
|
+# can't interfere too easily.
|
|
11
|
+#
|
|
12
|
+#
|
|
13
|
+# EXAMPLE PLUGIN SETUP
|
|
14
|
+# ====================
|
|
15
|
+#
|
|
16
|
+# Have a Shellfu module 'foo` wanting to support plugins `bar` and
|
|
17
|
+# `baz`. We'll need to implement 3 modules: `foo` itself and one
|
|
18
|
+# for each plugin: `_foop_bar` and `_foop_baz`.
|
|
19
|
+#
|
|
20
|
+# For example, the pluging `bar` might look like:
|
|
21
|
+#
|
|
22
|
+# _foop_bar__process() {
|
|
23
|
+# #
|
|
24
|
+# # Actual "payload" function
|
|
25
|
+# #
|
|
26
|
+# do_something_meaningful_with "$@"
|
|
27
|
+# }
|
|
28
|
+#
|
|
29
|
+# _foop_bar__sfpimeta() {
|
|
30
|
+# #
|
|
31
|
+# # Get self meta-data key $1
|
|
32
|
+# #
|
|
33
|
+# local key=$1
|
|
34
|
+# case $key in
|
|
35
|
+# desc) echo "We do meaningful thing in terms of bar." ;;
|
|
36
|
+# esac
|
|
37
|
+# }
|
|
38
|
+#
|
|
39
|
+# _foop_bar__sfpi_compat() {
|
|
40
|
+# #
|
|
41
|
+# # Check if we can function in this environment
|
|
42
|
+# #
|
|
43
|
+# test -d /etc/bar
|
|
44
|
+# }
|
|
45
|
+#
|
|
46
|
+# The useful function is _foop_bar__process(), which does the actual thing
|
|
47
|
+# that the plugin is supposed to do. You can have at least one such function,
|
|
48
|
+# for obvious reasons.
|
|
49
|
+#
|
|
50
|
+# There are two optional function helping with the 'sfpi' discovery:
|
|
51
|
+#
|
|
52
|
+# The _foop_bar__sfpimeta() function enables parent query plugin meta-data
|
|
53
|
+# using sfpi__key() function. Meanings of individual keys are not defined.
|
|
54
|
+#
|
|
55
|
+# The _foop_bar__sfpi_compat() function allows parent let plugin decide if
|
|
56
|
+# it's compatible with current environment and prevent it from being loaded.
|
|
57
|
+# In other words, if this function returns 1, the plugin would not be listed
|
|
58
|
+# by sfpi__ls().
|
|
59
|
+#
|
|
60
|
+# The minimal foo.sh in this example could look like:
|
|
61
|
+#
|
|
62
|
+# SFPI__PREFIX=_foop # you NEED to set this ASAP
|
|
63
|
+#
|
|
64
|
+# foo() {
|
|
65
|
+# local stuff=$1
|
|
66
|
+# for plugin in $(sfpi__ls); do
|
|
67
|
+# sfpi__call "$plugin" process "$stuff"
|
|
68
|
+# done
|
|
69
|
+# }
|
|
70
|
+#
|
|
71
|
+
|
|
72
|
+#
|
|
73
|
+# Plugin name prefix
|
|
74
|
+#
|
|
75
|
+# In order for your plugins to be discoverable, name them with this
|
|
76
|
+# prefix. Common naming scheme is '_<APPNAME>p_<PLNAME>', i.e. if
|
|
77
|
+# your application was named `foo`, final layout could look something
|
|
78
|
+# like this:
|
|
79
|
+#
|
|
80
|
+# foo
|
|
81
|
+# _foop_bar
|
|
82
|
+# _foop_baz
|
|
83
|
+#
|
|
84
|
+# where you could say that your module has plugins `foo` and `bar`. In
|
|
85
|
+# the above example, value of $SFPI__PREFIX would be `_foop` (starting
|
|
86
|
+# with underscore to remain hidden in normal listings by *sfdoc*.
|
|
87
|
+#
|
|
88
|
+SFPI__PREFIX=
|
|
89
|
+
|
|
90
|
+sfpi__call() {
|
|
91
|
+ #
|
|
92
|
+ # Call plugin $1 function $2 in subshell
|
|
93
|
+ #
|
|
94
|
+ __sfpi__ckpfx
|
|
95
|
+ local plg=$1; shift
|
|
96
|
+ local fun=$1; shift
|
|
97
|
+ (
|
|
98
|
+ shellfu import "${SFPI__PREFIX}_${plg}"
|
|
99
|
+ type -t "${SFPI__PREFIX}_${plg}__$fun" >/dev/null || {
|
|
100
|
+ warn "plugin function not implemented: $fun in $plg"
|
|
101
|
+ exit 3
|
|
102
|
+ }
|
|
103
|
+ "${SFPI__PREFIX}_${plg}__$fun" "$@"
|
|
104
|
+ )
|
|
105
|
+}
|
|
106
|
+
|
|
107
|
+sfpi__import() {
|
|
108
|
+ #
|
|
109
|
+ # Import plugin $1 into current namespace
|
|
110
|
+ #
|
|
111
|
+ __sfpi__ckpfx
|
|
112
|
+ local plg=$1
|
|
113
|
+ local mod"${SFPI__PREFIX}_${plg}"=
|
|
114
|
+ shellfu try_import "$mod" || {
|
|
115
|
+ warn "module for plugin not found: $mod"
|
|
116
|
+ return 3
|
|
117
|
+ }
|
|
118
|
+ sfpi__is_compat "$mod" || {
|
|
119
|
+ warn "module not compatible: $mod"
|
|
120
|
+ return 3
|
|
121
|
+ }
|
|
122
|
+ shellfu import "$mod"
|
|
123
|
+}
|
|
124
|
+
|
|
125
|
+sfpi__get_fun() {
|
|
126
|
+ #
|
|
127
|
+ # Print name of plugin $1 function $2
|
|
128
|
+ #
|
|
129
|
+ __sfpi__ckpfx
|
|
130
|
+ local plg=$1
|
|
131
|
+ local fun=$2
|
|
132
|
+ echo "${SFPI__PREFIX}_${plg}__$fun"
|
|
133
|
+}
|
|
134
|
+
|
|
135
|
+sfpi__get_mod() {
|
|
136
|
+ #
|
|
137
|
+ # Print name of module implementing plugin $1
|
|
138
|
+ #
|
|
139
|
+ __sfpi__ckpfx
|
|
140
|
+ local plg=$1
|
|
141
|
+ echo "${SFPI__PREFIX}_${plg}"
|
|
142
|
+}
|
|
143
|
+
|
|
144
|
+sfpi__has() {
|
|
145
|
+ #
|
|
146
|
+ # True if plugin $1 has function $2
|
|
147
|
+ #
|
|
148
|
+ __sfpi__ckpfx
|
|
149
|
+ local mod="${SFPI__PREFIX}_$1"
|
|
150
|
+ local fun=$2
|
|
151
|
+ (
|
|
152
|
+ shellfu import "$mod"
|
|
153
|
+ type -t "${mod}__$fun" >/dev/null;
|
|
154
|
+ )
|
|
155
|
+}
|
|
156
|
+
|
|
157
|
+sfpi__is_compat() {
|
|
158
|
+ #
|
|
159
|
+ # True if plugin $1 is compatible
|
|
160
|
+ #
|
|
161
|
+ # Load plugin in subshell and look for compatibility function. If
|
|
162
|
+ # such function exists, it's called without arguments and
|
|
163
|
+ # just to call its compatibility function
|
|
164
|
+ # and let it decide if it's compatible with this environment.
|
|
165
|
+ #
|
|
166
|
+ # If plugin has no compatibility function, it's considered
|
|
167
|
+ # compatible.
|
|
168
|
+ #
|
|
169
|
+ __sfpi__ckpfx
|
|
170
|
+ local plg=$1
|
|
171
|
+ local mod="${SFPI__PREFIX}_$plg"
|
|
172
|
+ local fun=${mod}__sfpi_compat
|
|
173
|
+ (
|
|
174
|
+ shellfu import "$mod" || exit 3
|
|
175
|
+ sfpi__has "$plg" sfpi_compat || exit 0
|
|
176
|
+ "$fun"; es=$?
|
|
177
|
+ test $es -gt 1 && warn "illegal exit status: $es from ${fun}"
|
|
178
|
+ return $es
|
|
179
|
+ )
|
|
180
|
+}
|
|
181
|
+
|
|
182
|
+sfpi__key() {
|
|
183
|
+ #
|
|
184
|
+ # Safely get meta-data key $2 from plugin for item type $1
|
|
185
|
+ #
|
|
186
|
+ __sfpi__ckpfx
|
|
187
|
+ local plg=$1 # plugin name
|
|
188
|
+ local key=$2 # meta-data key
|
|
189
|
+ local value # ^^ value
|
|
190
|
+ local fun="${SFPI__PREFIX}_${plg}__sfpimeta" # plg getter function name
|
|
191
|
+ if type -t "$fun" >/dev/null;
|
|
192
|
+ then
|
|
193
|
+ value=$("$fun" "$key")
|
|
194
|
+ debug -v fun key value
|
|
195
|
+ test -n "$value" || value="(none)"
|
|
196
|
+ else
|
|
197
|
+ value='(undefined)'
|
|
198
|
+ fi
|
|
199
|
+ echo "$value"
|
|
200
|
+}
|
|
201
|
+
|
|
202
|
+sfpi__ls_all() {
|
|
203
|
+ #
|
|
204
|
+ # List all available plugins
|
|
205
|
+ #
|
|
206
|
+ __sfpi__ckpfx
|
|
207
|
+ SFDOC_SHOW_HIDDEN=true sfdoc__ls_m \
|
|
208
|
+ | grep "^$SFPI__PREFIX"'_[[:alpha:]][[:alnum:]]*$' \
|
|
209
|
+ | sed "s/^${SFPI__PREFIX}_//"
|
|
210
|
+}
|
|
211
|
+
|
|
212
|
+sfpi__ls() {
|
|
213
|
+ #
|
|
214
|
+ # List compatible plugins
|
|
215
|
+ #
|
|
216
|
+ __sfpi__ckpfx
|
|
217
|
+ local plg
|
|
218
|
+ for plg in $(sfpi__ls_all); do
|
|
219
|
+ sfpi__is_compat "$plg" && echo "$plg"
|
|
220
|
+ done
|
|
221
|
+ true
|
|
222
|
+}
|
|
223
|
+
|
|
224
|
+__sfpi__ckpfx() {
|
|
225
|
+ #
|
|
226
|
+ # Check if $SFPI__PREFIX has been set
|
|
227
|
+ #
|
|
228
|
+ debug -v SFPI__PREFIX
|
|
229
|
+ test -n "$SFPI__PREFIX" \
|
|
230
|
+ || die "bug: need to set SFPI__PREFIX first"
|
|
231
|
+}
|