Browse Source

Add arr.sh, couple of utilities for array manipulation

Alois Mahdal 4 years ago
parent
commit
d80a3cdc58

+ 17
- 0
packaging/debian/control View File

@@ -48,6 +48,23 @@ Description: Bash-specific base
48 48
  This sub-package contains Bash-specific parts of library infrastructure.
49 49
  Shellfu modules written for Bash should depend on this.
50 50
 
51
+Package: shellfu-bash-arr
52
+Architecture: all
53
+Depends: shellfu-bash, shellfu-bash-pretty, shellfu-sh-isa
54
+Description: Array utilities
55
+ Shellfu is an attempt to add modularity to your shell scripts.
56
+ .
57
+ With Shellfu you can develop your shell modules separately from your
58
+ scripts, and test, use, explore or study them without need to be aware
59
+ of details such as where the actual files are placed.
60
+ .
61
+ Shellfu is mostly intended for cases when there's need for non-trivial
62
+ amount of reliable body of shell scripts, and access to advanced modular
63
+ languages such as Python or Perl is limited.
64
+ .
65
+ This sub-package contains 'arr', Shellfu/Bash module with few utilities
66
+ for manipulating Bash arrays
67
+
51 68
 Package: shellfu-bash-inigrep
52 69
 Architecture: all
53 70
 Depends: shellfu-bash, shellfu-bash-pretty

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

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

+ 12
- 0
packaging/template.spec View File

@@ -40,6 +40,15 @@ Requires: shellfu
40 40
 This sub-package contains Bash-specific parts of library infrastructure.
41 41
 Shellfu modules written for Bash should depend on this.
42 42
 
43
+%package bash-arr
44
+Summary: Array utilities
45
+Requires: shellfu-bash
46
+Requires: shellfu-bash-pretty
47
+Requires: shellfu-sh-isa
48
+%description bash-arr
49
+This sub-package contains 'arr', Shellfu/Bash module with few utilities
50
+for manipulating Bash arrays
51
+
43 52
 %package bash-inigrep
44 53
 Summary: INI grepping Shellfu/Bash module
45 54
 Requires: perl
@@ -170,6 +179,9 @@ make test \
170 179
 %files bash
171 180
 %dir %{_datadir}/%{name}/include-bash
172 181
 
182
+%files bash-arr
183
+%{_datadir}/%{name}/include-bash/arr.sh
184
+
173 185
 %files bash-inigrep
174 186
 %{_datadir}/%{name}/include-bash/inigrep.sh
175 187
 

+ 98
- 0
src/include-bash/arr.sh View File

@@ -0,0 +1,98 @@
1
+#!/bin/bash
2
+
3
+shellfu import isa
4
+shellfu import pretty
5
+
6
+#
7
+# Array utilities
8
+#
9
+# Several utilities for loading and printing Bash arrays.
10
+#
11
+
12
+arr__fromcmd() {
13
+    #
14
+    # Save output lines from command $2.. to array named $1
15
+    #
16
+    # Usage:
17
+    #     arr__fromcmd ARRAY CMD [ARG]..
18
+    #
19
+    # Unlike `mapfile -t <<<"$(command)", this produces zero
20
+    # items if command output was empty.
21
+    #
22
+    local __arr__fromcmd__oarr=$1; shift
23
+    local __arr__fromcmd__cmd=("$@")
24
+    local __arr__fromcmd__tmp
25
+    local es
26
+    __arr__val_oarr "$__arr__fromcmd__oarr" || return 2
27
+    __arr__fromcmd__tmp=$(mktemp arr__fromcmd.__arr__fromcmd__tmp.XXXXX)
28
+    "${__arr__fromcmd__cmd[@]}" > "$__arr__fromcmd__tmp"; es=$?
29
+    mapfile -t "$__arr__fromcmd__oarr" <"$__arr__fromcmd__tmp"
30
+    rm "$__arr__fromcmd__tmp"
31
+    return $es
32
+}
33
+
34
+arr__has() {
35
+    #
36
+    # True if value $1 is in array $2
37
+    #
38
+    # Usage:
39
+    #     arr__has VALUE ARRAY
40
+    #
41
+    local want=$1
42
+    local iarr=$2
43
+    local copy
44
+    local value
45
+    local code
46
+    __arr__val_iarr "$iarr" || return 2
47
+    code=$(declare -p "$iarr"); code=${code/ $iarr=/ copy=}
48
+    eval "$code"
49
+    for value in "${copy[@]}"; do
50
+        test "$value" == "$want" && return 0
51
+    done
52
+    return 1
53
+}
54
+
55
+arr__join() {
56
+    #
57
+    # Join items in array $2 by string $1
58
+    #
59
+    # Usage:
60
+    #     arr__join DELIM ARRAY
61
+    #
62
+    # Example:
63
+    #
64
+    #     names=( Alice Bob )
65
+    #     arr__join , names
66
+    #     # prints 'Alice,Bob'
67
+    #
68
+    local delim=$1
69
+    local iarr=$2
70
+    local first=true
71
+    local item
72
+    local copy
73
+    local code
74
+    __arr__val_iarr "$iarr" || return 2
75
+    code=$(declare -p "$iarr"); code=${code/ $iarr=/ copy=}
76
+    eval "$code"
77
+    for item in "${copy[@]}"; do
78
+        $first || echo -n "$delim"
79
+        first=false
80
+        echo -n "$item"
81
+    done
82
+}
83
+
84
+__arr__val_iarr() {
85
+    #
86
+    # Validate input array named $1
87
+    #
88
+    local name=$1
89
+    set | grep -q "^$name=" || { warn "no such array: $name"; return 2; }
90
+}
91
+
92
+__arr__val_oarr() {
93
+    #
94
+    # Validate output array named $1
95
+    #
96
+    local name=$1
97
+    isa__name "$name" || { warn "invalid array name: $name"; return 2; }
98
+}

+ 229
- 0
tests/arr/TF_RUN View File

@@ -0,0 +1,229 @@
1
+#!/bin/bash
2
+#shellcheck disable=SC1090
3
+
4
+. "$TF_DIR/include/subtest.sh"
5
+. "$TF_DIR/include/tools.sh"
6
+
7
+. "$(sfpath)" || tf_exit_error "failed to init shellfu"
8
+export PRETTY=plain
9
+shellfu import arr    || tf_exit_error "failed to import arr"
10
+
11
+
12
+tf_enum_subtests() {
13
+    echo arr__fromcmd,inval
14
+    echo arr__fromcmd,c_null
15
+    echo arr__fromcmd,c_zlz
16
+    echo arr__fromcmd,c_lzl
17
+    echo arr__fromcmd,c_lz
18
+    echo arr__fromcmd,c_zl
19
+    echo arr__fromcmd,c_zz
20
+    echo arr__fromcmd,c_zzz
21
+    echo arr__fromcmd,c_l
22
+    echo arr__fromcmd,c_ll
23
+    echo arr__fromcmd,c_lll
24
+    echo arr__has,nsuch
25
+    echo arr__has,c_null
26
+    echo arr__has,w_null_yes
27
+    echo arr__has,w_null_not
28
+    echo arr__has,m_not
29
+    echo arr__has,m_mid
30
+    echo arr__has,m_a
31
+    echo arr__has,m_z
32
+    echo arr__join,nsuch
33
+    echo arr__join,d_null
34
+    echo arr__join,c_null
35
+    echo arr__join,d_mult
36
+    echo arr__join,c_zz
37
+    echo arr__join,c_lll
38
+    echo arr__join,c_z
39
+    echo arr__join,c_l
40
+}
41
+
42
+bashfix() {
43
+    local x=${BASH_VERSION%%.*}
44
+    local yz=${BASH_VERSION#$x.}
45
+    local y=${yz%%.**}
46
+    if test "$x" -eq 4 && test "$y" -lt 4; then
47
+        tr -d "'"
48
+    else
49
+        cat
50
+    fi
51
+}
52
+
53
+cmd_printing() {
54
+    local what=$1
55
+    case $1 in
56
+        c_null)   : ;;
57
+        c_z)      echo ;;
58
+        c_zz)     echo; echo ;;
59
+        c_zzz)    echo; echo; echo ;;
60
+        c_l)      echo all ;;
61
+        c_ll)     echo first; echo last ;;
62
+        c_lll)    echo first; echo mid; echo last ;;
63
+        c_zlz)    echo; echo mid; echo ;;
64
+        c_lzl)    echo first; echo; echo last ;;
65
+        c_lz)     echo left; echo  ;;
66
+        c_zl)     echo; echo right  ;;
67
+    esac
68
+}
69
+
70
+mkadecl() {
71
+    local name=$1
72
+    local shape=$2
73
+    echo -n "declare -a $name="
74
+    case $shape in
75
+        inval)    echo '()' ;;
76
+        c_null)   echo '()' ;;
77
+        c_z)      echo '([0]="")' ;;
78
+        c_zz)     echo '([0]="" [1]="")' ;;
79
+        c_zzz)    echo '([0]="" [1]="" [2]="")' ;;
80
+        c_l)      echo '([0]="all")' ;;
81
+        c_ll)     echo '([0]="first" [1]="last")' ;;
82
+        c_lll)    echo '([0]="first" [1]="mid" [2]="last")' ;;
83
+        c_zlz)    echo '([0]="" [1]="mid" [2]="")' ;;
84
+        c_lzl)    echo '([0]="first" [1]="" [2]="last")' ;;
85
+        c_lz)     echo '([0]="left" [1]="")' ;;
86
+        c_zl)     echo '([0]="" [1]="right")' ;;
87
+    esac
88
+}
89
+
90
+getprop() {
91
+    #
92
+    # Make artifact or oracle $what for $TestName
93
+    #
94
+    local what=$1
95
+    case $TestName:$what in
96
+
97
+        # arr__fromcmd
98
+
99
+        arr__fromcmd,inval:args)    echo bad=array=name; echo echo; echo hello ;;
100
+        arr__fromcmd,c_*:args)      echo cout; echo cmd_printing; echo "${TestName#*,}" ;;
101
+
102
+        arr__fromcmd,*:o_decl)      mkadecl cout "${TestName#*,}" ;;
103
+
104
+        arr__fromcmd,inval:o_es)    echo 2 ;;
105
+        arr__fromcmd,*:o_es)        echo 0 ;;
106
+
107
+        # arr__has
108
+
109
+        arr__has,w_null_yes:a_decl) mkadecl cin c_lzl ;;
110
+        arr__has,w_null_not:a_decl) mkadecl cin c_lll ;;
111
+        arr__has,c_null:a_decl)     mkadecl cin c_null ;;
112
+        arr__has,m_not:a_decl)      mkadecl cin c_lll ;;
113
+        arr__has,m_mid:a_decl)      mkadecl cin c_lll ;;
114
+        arr__has,m_a:a_decl)        mkadecl cin c_lll ;;
115
+        arr__has,m_z:a_decl)        mkadecl cin c_lll ;;
116
+
117
+        arr__has,nsuch:args)        echo foo; echo nonesuch ;;
118
+        arr__has,c_null:args)       echo foo;   echo cin ;;
119
+        arr__has,w_null*:args)      echo "";    echo cin ;;
120
+        arr__has,m_not:args)        echo bar;   echo cin ;;
121
+        arr__has,m_mid:args)        echo mid;   echo cin ;;
122
+        arr__has,m_a:args)          echo first; echo cin ;;
123
+        arr__has,m_z:args)          echo last;  echo cin ;;
124
+
125
+        arr__has,nsuch:o_es)        echo 2 ;;
126
+        arr__has,m_not:o_es)        echo 1 ;;
127
+        arr__has,c_null:o_es)       echo 1 ;;
128
+        arr__has,w_null_not:o_es)   echo 1 ;;
129
+        arr__has,*:o_es)            echo 0 ;;
130
+
131
+        # arr__join
132
+
133
+        arr__join,nsuch:a_decl)     mkadecl cin c_lll ;;
134
+        arr__join,d_null:a_decl)    mkadecl cin c_lll ;;
135
+        arr__join,d_mult:a_decl)    mkadecl cin c_lll ;;
136
+        arr__join,c_null:a_decl)    mkadecl cin c_null ;;
137
+        arr__join,c_zz:a_decl)      mkadecl cin c_zz ;;
138
+        arr__join,c_zzz:a_decl)     mkadecl cin c_zzz ;;
139
+        arr__join,c_lll:a_decl)     mkadecl cin c_lll ;;
140
+        arr__join,c_z:a_decl)       mkadecl cin c_z ;;
141
+        arr__join,c_l:a_decl)       mkadecl cin c_l ;;
142
+
143
+        arr__join,nsuch:args)       echo oxo;  echo nonesuch ;;
144
+        arr__join,d_null:args)      echo "";   echo cin ;;
145
+        arr__join,d_mult:args)      echo oxo;  echo cin ;;
146
+        arr__join,c_null:args)      echo .;    echo cin ;;
147
+        arr__join,c_zz:args)        echo .;    echo cin ;;
148
+        arr__join,c_zzz:args)       echo .;    echo cin ;;
149
+        arr__join,c_lll:args)       echo .;    echo cin ;;
150
+        arr__join,c_z:args)         echo .;    echo cin ;;
151
+        arr__join,c_l:args)         echo .;    echo cin ;;
152
+
153
+        arr__join,nsuch:o_out)      echo -n ;;
154
+        arr__join,d_null:o_out)     echo -n firstmidlast ;;
155
+        arr__join,d_mult:o_out)     echo -n firstoxomidoxolast ;;
156
+        arr__join,c_null:o_out)     echo -n '' ;;
157
+        arr__join,c_zz:o_out)       echo -n . ;;
158
+        arr__join,c_zzz:o_out)      echo -n .. ;;
159
+        arr__join,c_lll:o_out)      echo -n first.mid.last ;;
160
+        arr__join,c_z:o_out)        echo -n ;;
161
+        arr__join,c_l:o_out)        echo -n all ;;
162
+
163
+        arr__join,nsuch:o_es)       echo 2 ;;
164
+        arr__join,*:o_es)           echo 0 ;;
165
+
166
+    esac
167
+}
168
+
169
+mkresult() {
170
+    local sut
171
+    local ttype
172
+    local args=()
173
+    local cout=()
174
+    local cin=()
175
+    local r_es
176
+    ttype=${TestName##*,}
177
+    sut=${TestName%,$ttype}
178
+    type -t "$sut" >/dev/null || {
179
+        tf_exit_error "no such function: $sut"
180
+    }
181
+    readarray -t args <<<"$(getprop args)"
182
+    tf_debugv args
183
+    case $sut in
184
+        arr__fromcmd)
185
+            cout=()
186
+            "$sut" "${args[@]}"; r_es=$?
187
+            declare -p cout | bashfix
188
+            ;;
189
+        *)
190
+            eval "$(getprop a_decl)"
191
+            "$sut" "${args[@]}"; r_es=$?
192
+            ;;
193
+    esac
194
+    return "$r_es"
195
+}
196
+
197
+mkoracle() {
198
+    case $TestName in
199
+        *,nsuch)
200
+            echo 'no such array: nonesuch' >&2
201
+            ;;
202
+        arr__fromcmd,inval)
203
+            getprop o_decl
204
+            echo 'invalid array name: bad=array=name' >&2
205
+            ;;
206
+        arr__fromcmd,*)
207
+            getprop o_decl
208
+            ;;
209
+        arr__join,*)
210
+            getprop o_out
211
+            ;;
212
+    esac >"oracle/$TestName.out" 2>"oracle/$TestName.err"
213
+}
214
+
215
+tf_do_subtest() {
216
+    local TestName=$1
217
+    local o_es
218
+    mkdir -p oracle result
219
+    o_es=$(getprop o_es)
220
+    mkoracle
221
+    tf_testflt \
222
+        -n "$TestName" \
223
+        -O "oracle/$TestName.out" \
224
+        -E "oracle/$TestName.err" \
225
+        -S "$o_es" \
226
+        "mkresult"
227
+}
228
+
229
+tf_do_subtests

+ 1
- 0
tests/shellfu_api/TF_RUN View File

@@ -23,6 +23,7 @@ flt_ours() {
23 23
         -e sfdoc \
24 24
         -e sfpi \
25 25
         -e mdfmt \
26
+        -e arr \
26 27
     #FIXME: remove the filter when TFKit learns to test in sandbox
27 28
 }
28 29
 

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

@@ -35,6 +35,11 @@ _pretty_plain:_pretty__mkhelp
35 35
 _pretty_plain:_pretty__mkusage
36 36
 _pretty_plain:_pretty__think
37 37
 _pretty_plain:_pretty__warn
38
+arr:__arr__val_iarr
39
+arr:__arr__val_oarr
40
+arr:arr__fromcmd
41
+arr:arr__has
42
+arr:arr__join
38 43
 charmenu:charmenu
39 44
 exit:exit_error
40 45
 exit:exit_no

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

@@ -4,6 +4,7 @@ _pretty_html
4 4
 _pretty_journald
5 5
 _pretty_notify
6 6
 _pretty_plain
7
+arr
7 8
 charmenu
8 9
 exit
9 10
 inigrep