Browse Source

Add sfpi.sh, Shellfu's favorite plugin interface

Alois Mahdal 6 years ago
parent
commit
d5a36bbe12

+ 17
- 0
packaging/debian/control View File

@@ -102,6 +102,23 @@ Description: Shellfu/Bash module to export docs from Shellfu modules
102 102
  This sub-package contains Shellfu/Bash module to export documentation
103 103
  from other Shellfu modules that follow Shellfu coding style.
104 104
 
105
+Package: shellfu-bash-sfpi
106
+Architecture: all
107
+Depends: shellfu-bash, shellfu-bash-pretty, shellfu-bash-sfdoc
108
+Description: Shellfu/Bash module to export docs from Shellfu modules
109
+ Shellfu is an attempt to add modularity to your shell scripts.
110
+ .
111
+ With Shellfu you can develop your shell modules separately from your
112
+ scripts, and test, use, explore or study them without need to be aware
113
+ of details such as where the actual files are placed.
114
+ .
115
+ Shellfu is mostly intended for cases when there's need for non-trivial
116
+ amount of reliable body of shell scripts, and access to advanced modular
117
+ languages such as Python or Perl is limited.
118
+ .
119
+ This sub-package contains Shellfu/Bash module to help manage, discover,
120
+ select and load other Shellfu modules as plugins.
121
+
105 122
 Package: shellfu-devel
106 123
 Architecture: all
107 124
 Depends: shellfu-bash-pretty

+ 1
- 0
packaging/debian/shellfu-bash-sfpi.install View File

@@ -0,0 +1 @@
1
+/usr/share/shellfu/include-bash/sfpi.sh

+ 12
- 0
packaging/template.spec View File

@@ -66,6 +66,15 @@ Requires: shellfu-bash-pretty
66 66
 This sub-package contains Shellfu/Bash module to export documentation
67 67
 from other Shellfu modules that follow Shellfu coding style.
68 68
 
69
+%package bash-sfpi
70
+Summary: Shellfu/Bash plugin interface
71
+Requires: shellfu-bash
72
+Requires: shellfu-bash-sfdoc
73
+Requires: shellfu-bash-pretty
74
+%description bash-sfpi
75
+This sub-package contains Shellfu/Bash module to help manage, discover,
76
+select and load other Shellfu modules as plugins.
77
+
69 78
 %package devel
70 79
 Summary: Essential developer tools
71 80
 Requires: shellfu-bash-pretty
@@ -160,6 +169,9 @@ make test \
160 169
 %files bash-sfdoc
161 170
 %{_datadir}/%{name}/include-bash/sfdoc.sh
162 171
 
172
+%files bash-sfpi
173
+%{_datadir}/%{name}/include-bash/sfpi.sh
174
+
163 175
 %files devel
164 176
 %{_bindir}/sfembed
165 177
 

+ 8
- 8
src/bin/sfdoc View File

@@ -8,7 +8,7 @@ shellfu import sfdoc
8 8
 
9 9
 
10 10
 usage() {
11
-    mkusage \
11
+    mkusage "$@" \
12 12
         "[options] MODULE"                                              \
13 13
         "[options] --ls [MODULE]"                                       \
14 14
         "[options] --ls{var|fun} MODULE"                                \
@@ -102,18 +102,18 @@ main() {
102 102
         -O|--object)    spectype=obj;               shift ;;
103 103
         -d|--debug)     PRETTY_DEBUG=true;          shift ;;
104 104
         -a|--all)       SFDOC_SHOW_HIDDEN=true;     shift ;;
105
-        -I|--include)   SHELLFU_PATH="$2:$SHELLFU_PATH"; shift 2 || usage ;;
105
+        -I|--include)   SHELLFU_PATH="$2:$SHELLFU_PATH"; shift 2 || usage -w "no PTH?" ;;
106 106
         -s|--src)       action=src;                 shift; break ;;
107 107
         -l|--ls)        action=lsx;                 shift; break ;;
108 108
         -L|--lsmod)     action=lsm;                 shift; break ;;
109 109
         --which)        action=wch;                 shift; break ;;
110
-        -o|--only-from) SHELLFU_PATH="$2"; SHELLFU_INCLUDE=""; shift 2 || usage ;;
110
+        -o|--only-from) SHELLFU_PATH="$2"; SHELLFU_INCLUDE=""; shift 2 || usage -w "no PTH?" ;;
111 111
         --lsvar)        action=lsv;                 shift; break ;;
112 112
         --lsfun)        action=lsf;                 shift; break ;;
113
-        -e|--export)    action=exp; format="$2";    shift 2 || usage; break ;;
114
-        --encoding)     encoding="$2";              shift 2 || usage ;;
115
-        --name)         RealModuleName="$2";        shift 2 || usage ;;
116
-        -*)                                         usage ;;
113
+        -e|--export)    action=exp; format="$2";    shift 2 || usage -w "no FMT?"; break ;;
114
+        --encoding)     encoding="$2";              shift 2 || usage -w "no ENC?" ;;
115
+        --name)         RealModuleName="$2";        shift 2 || usage -w "no NAME?" ;;
116
+        -*)                                         usage -w "unknown argument: $1" ;;
117 117
         *)                                          break ;;
118 118
     esac done
119 119
     mspec="$1"; shift
@@ -121,7 +121,7 @@ main() {
121 121
     debug -v action format module RealModuleName
122 122
     case $action:$mspec in
123 123
         lsx:*|lsm:*|wch:*) true ;;
124
-        *:)             usage ;;
124
+        *:)             usage -w "no MODULE?" ;;
125 125
     esac
126 126
     case $spectype in
127 127
         obj) module=$(find_by "$mspec") \

+ 231
- 0
src/include-bash/sfpi.sh View File

@@ -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
+}

+ 1
- 0
tests/shellfu_api/TF_RUN View File

@@ -20,6 +20,7 @@ flt_ours() {
20 20
         -e termcolors \
21 21
         -e pretty \
22 22
         -e sfdoc \
23
+        -e sfpi \
23 24
         -e mdfmt \
24 25
     #FIXME: remove the filter when TFKit learns to test in sandbox
25 26
 }

+ 10
- 0
tests/shellfu_api/oracle/functions.stdout View File

@@ -91,3 +91,13 @@ sfdoc:__sfdoc__strip_doc
91 91
 sfdoc:sfdoc__export
92 92
 sfdoc:sfdoc__ls
93 93
 sfdoc:sfdoc__ls_m
94
+sfpi:__sfpi__ckpfx
95
+sfpi:sfpi__call
96
+sfpi:sfpi__get_fun
97
+sfpi:sfpi__get_mod
98
+sfpi:sfpi__has
99
+sfpi:sfpi__import
100
+sfpi:sfpi__is_compat
101
+sfpi:sfpi__key
102
+sfpi:sfpi__ls
103
+sfpi:sfpi__ls_all

+ 1
- 0
tests/shellfu_api/oracle/modules.stdout View File

@@ -9,4 +9,5 @@ inigrep
9 9
 mdfmt
10 10
 pretty
11 11
 sfdoc
12
+sfpi
12 13
 termcolors

+ 1
- 0
tests/shellfu_api/oracle/variables.stdout View File

@@ -17,6 +17,7 @@ pretty:PRETTY_DEBUG_EXCLUDE
17 17
 pretty:PRETTY_USAGE
18 18
 pretty:PRETTY_VERBOSE
19 19
 sfdoc:SFDOC_SHOW_HIDDEN
20
+sfpi:SFPI__PREFIX
20 21
 termcolors:TERMCOLORS_BLACK
21 22
 termcolors:TERMCOLORS_BLUE
22 23
 termcolors:TERMCOLORS_CYAN