Compare commits

...

61 commits

Author SHA1 Message Date
705798eeb0 Added back revers_str and changed the example script 2020-12-14 10:19:02 -05:00
46722c9b5d Simplified, shellchecked, and name deconflicted for personal use 2020-12-14 10:08:36 -05:00
Sławomir Śledź
142e1bade4 Simplifying release procedure 2019-10-07 10:27:02 +02:00
Slawomir Sledz
4c16fc52f7
Merge pull request #4 from tpoindex/master
Update docs
2019-10-07 01:46:34 +02:00
Tom Poindexter
5e1cf52840
Add docs for ntup, maybe, is* predicates
Updated ntup docs to show nested tuples, and comma safe elements.
Added docs for maybe and is* predicates.
2019-10-02 17:54:48 -06:00
Tom Poindexter
da8e3ca3cf
Merge pull request #1 from ssledz/master
sync with upstream
2019-10-02 16:54:02 -06:00
Sławomir Śledź
b783ac9e8f Merge branch 'develop' 2019-10-01 00:37:03 +02:00
Slawomir Sledz
9840e94a5d
Create LICENSE 2019-10-01 00:34:01 +02:00
Sławomir Śledź
67c88a5b3f more docs 2019-10-01 00:24:56 +02:00
Sławomir Śledź
b590285ec9 Fixing examples & documentation + adding example of usage to documentation. 2019-10-01 00:02:34 +02:00
Sławomir Śledź
540840e9d2 Manually bumping version 2019-09-14 20:23:38 +02:00
tpoindex
1b234ff597 avoid collisions with common /usr/bin/ commands
rename head, tail, zip to lhead, ltail, and lzip to avoid collisions with command unix commands
2019-09-14 20:21:06 +02:00
tpoindex
44dbdd3fbc fluent predicates for filtering 2019-09-14 20:21:06 +02:00
tpoindex
b19ab5180b maybe monad and friends
maybe - return a tuple of (Nothing) or (Just,value)
maybemap - apply map function when maybe has a value and wrap in another maybe; else when nothing return nothing
maybevalue - return value of maybe; else when nothing return optional default args
2019-09-14 20:21:06 +02:00
tpoindex
7736a293a0 ntup{x,l,r} - tuples that can be nested and without quoting issues, base64 encoded elements 2019-09-14 20:21:06 +02:00
tpoindex
b40adbb643 fix comma quoting in tup/tupx by quoting all commas 2019-09-14 20:21:06 +02:00
Sławomir Śledź
febedd4c5c Bumping version 2019-09-14 19:54:57 +02:00
Slawomir Sledz
e5f3e8c5f3
Merge pull request #3 from tpoindex/master
Fix tup/tupx plus a few additions: safe nested tuples, maybe monad, filter predicates.
2019-09-14 11:55:48 +02:00
tpoindex
1ead6d0b87 avoid collisions with common /usr/bin/ commands
rename head, tail, zip to lhead, ltail, and lzip to avoid collisions with command unix commands
2019-09-11 23:53:56 -06:00
tpoindex
7b6531a74e fluent predicates for filtering 2019-09-11 23:46:47 -06:00
tpoindex
912b3f2491 maybe monad and friends
maybe - return a tuple of (Nothing) or (Just,value)
maybemap - apply map function when maybe has a value and wrap in another maybe; else when nothing return nothing
maybevalue - return value of maybe; else when nothing return optional default args
2019-09-11 22:47:39 -06:00
tpoindex
9c238777e1 ntup{x,l,r} - tuples that can be nested and without quoting issues, base64 encoded elements 2019-09-11 20:58:30 -06:00
tpoindex
cba3556da8 fix comma quoting in tup/tupx by quoting all commas 2019-09-11 20:17:45 -06:00
Sławomir Śledź
20242e1d3f [release] merge release/2.3 into develop 2019-08-31 02:14:44 +02:00
Sławomir Śledź
68125ee7d8 [release] merge previous version into master to avoid the increased version number 2019-08-31 02:14:44 +02:00
Sławomir Śledź
021477770c [release] prepare for next development iteration 2019-08-31 02:14:44 +02:00
Sławomir Śledź
61e96159ec [release] prepare release v2.3 2019-08-31 02:14:44 +02:00
Sławomir Śledź
bc44865f5a ext documentation 2019-08-31 02:03:35 +02:00
Sławomir Śledź
14dd7c3993 [release] merge previous version into master to avoid the increased version number 2019-08-31 01:55:31 +02:00
Sławomir Śledź
0f8f528fcb [release] merge release/2.2 into develop 2019-08-31 01:55:31 +02:00
Sławomir Śledź
a23cb10ac6 [release] prepare release v2.2 2019-08-31 01:55:31 +02:00
Sławomir Śledź
857a3c6109 [release] prepare for next development iteration 2019-08-31 01:55:31 +02:00
Slawomir Sledz
79e6873e8e
Merge pull request #2 from ssledz/feature-#1
#1 : Shorthand notations for lambdas
2019-08-31 01:54:16 +02:00
Sławomir Śledź
c1ae19f270 #1 : Shorthand notations for lambdas 2019-08-31 01:52:16 +02:00
Sławomir Śledź
5979ac8548 [release] merge previous version into master to avoid the increased version number 2018-03-27 23:14:57 +02:00
Sławomir Śledź
adfe8ec68b [release] merge release/2.1 into develop 2018-03-27 23:14:57 +02:00
Sławomir Śledź
22c81bd09a [release] prepare release v2.1 2018-03-27 23:14:57 +02:00
Sławomir Śledź
2a614cb04d [release] prepare for next development iteration 2018-03-27 23:14:57 +02:00
Sławomir Śledź
03418caa85 Three new functions: pass, dropw, peek 2018-03-18 12:01:29 +01:00
Sławomir Śledź
ffff49bd0e Documentation 2018-03-11 01:05:59 +01:00
Sławomir Śledź
47d2c2b4d4 [release] merge previous version into master to avoid the increased version number 2018-03-11 00:55:52 +01:00
Sławomir Śledź
5a0c51fd74 [release] merge release/2.0 into develop 2018-03-11 00:55:52 +01:00
Sławomir Śledź
d47c0e3240 [release] prepare release v2.0 2018-03-11 00:55:52 +01:00
Sławomir Śledź
f031d22b13 [release] prepare for next development iteration 2018-03-11 00:55:52 +01:00
Sławomir Śledź
6a9558a58f Documentation 2018-03-11 00:55:39 +01:00
Sławomir Śledź
23cd454242 Documentation 2018-03-11 00:53:54 +01:00
Sławomir Śledź
33faefc283 Documentation 2018-03-11 00:47:25 +01:00
Sławomir Śledź
2729b6b3c4 Add release scripts 2018-03-11 00:20:01 +01:00
Sławomir Śledź
c3425e0933 Fixing bug in try 2018-03-10 23:23:23 +01:00
Sławomir Śledź
6de7798c8a Bugfix in tup 2018-03-10 22:22:06 +01:00
Sławomir Śledź
de116fcf9e New version of try and new function 2018-03-10 22:07:30 +01:00
Sławomir Śledź
6b07f1d8f8 Fixing bug in ret function 2018-03-10 14:26:41 +01:00
Sławomir Śledź
9502f7a8a2 New implementation of tup/tupx/tupr 2018-03-10 14:12:05 +01:00
Sławomir Śledź
b5a3c9eea0 Adding with_trampoline function 2018-02-14 01:19:43 +01:00
Sławomir Śledź
cb9eff8b81 More tests... 2018-02-14 00:11:27 +01:00
Sławomir Śledź
f254b98a3f Add more tests 2018-02-13 23:29:46 +01:00
Sławomir Śledź
11f8cffec6 more tests 2018-01-24 23:01:43 +01:00
Sławomir Śledź
feb3c3f07c Adds tests driven by shunit2 framework 2018-01-24 22:22:48 +01:00
Sławomir Śledź
00d9c3916c Minor fix + add some examples. 2017-08-23 03:34:51 +02:00
Sławomir Śledź
3cf325d4aa Fixing bug in foldr 2017-08-23 01:03:01 +02:00
Sławomir Śledź
dcdbaf22de Add curry function 2017-08-22 09:37:44 +02:00
24 changed files with 3981 additions and 178 deletions

1
.version Normal file
View file

@ -0,0 +1 @@
2.5b

22
LICENSE Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 Brandon Rozek
Copyright (c) 2019 Sławomir Śledź
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

294
README.md
View file

@ -1 +1,293 @@
# bash-fun
# Introduction
This is a fork of [ssledz's fun.sh library](https://github.com/ssledz/bash-fun).
This is mainly for my own personal use cases. So I would recommend using ssledz's version instead.
I mainly worked towards getting this library to mostly pass shellcheck, removed some functionality,
and name deconflicted some of the functions with what I had in my system.
# Quick start
```bash
#!/bin/bash
. <(test -e fun.sh || curl -Ls https://raw.githubusercontent.com/brandon-rozek/bash-fun/master/src/fun.sh > fun.sh; cat fun.sh)
seq 1 4 | sum
```
# Functions overview
|||||||
|------|------|------|------|------|------|
|**list_append**|**divide**|**take_while**|
|**list_drop**|**drop_while**|**factorial**|**filter**|**foldl**|
|**isint**|**isempty**|**isfile**|**isnonzerofile**|**isreadable**|**iswritable**|
|**isdir**|**list_join**|**lambda**|**list_last**|**list_head**|**list**|
|**list_tail**|**list_zip**|**list_map**|
|**mod**|**multiply**|**not**|
|**add**|**list_prepend**|**product**|**ret**|
|**revers**|**revers_str**|**scanl**|**splitc**|**strip**|
|**stripl**|**stripr**|**subtract**|**sum**|**take**|
|**tup**|**unlist**|**λ**|
## *list/unlist*
```bash
$ list 1 2 3
1
2
3
$ list 1 2 3 4 5 | unlist
1 2 3 4 5
```
## *list_take/list_drop/list_tail/list_head/list_last*
```bash
$ list 1 2 3 4 | list_drop 2
3
4
$ list 1 2 3 4 5 | list_head
1
$ list 1 2 3 4 | list_tail
2
3
4
$ list 1 2 3 4 5 | list_last
5
$ list 1 2 3 4 5 | list_take 2
1
2
```
## *join*
```bash
$ list 1 2 3 4 5 | list_join ,
1,2,3,4,5
```
## *map*
```bash
$ seq 1 5 | list_map λ a . 'echo $((a + 5))'
6
7
8
9
10
$ list a b s d e | list_map λ a . 'echo $a$(echo $a | tr a-z A-Z)'
aA
bB
sS
dD
eE
$ list 1 2 3 | list_map tee
1
2
3
```
## *filter*
```bash
$ seq 1 10 | filter even
2
4
6
8
10
```
## *foldl/foldr*
```bash
$ list a b c d | foldl λ acc el . 'echo -n $acc-$el'
a-b-c-d
```
```bash
$ seq 1 4 | foldl λ acc el . 'echo $(($acc + $el))'
10
```
```bash
$ seq 1 4 | foldl λ acc el . 'echo $(multiply $(($acc + 1)) $el)'
64 # 1 + (1 + 1) * 2 + (4 + 1) * 3 + (15 + 1) * 4 = 64
```
## *tup/tupx/tupl/tupr*
```bash
$ tup a 1
(a,1)
$ tup 'foo bar' 1 'one' 2
(foo bar,1,one,2)
$ tup , 1 3
(,,1,3)
```
```bash
$ echo tup a 1 | tupl
a
$ echo tup a 1 | tupr
1
$ tup 'foo bar' 1 'one' 2 | tupl
foo bar
$ tup 'foo bar' 1 'one' 2 | tupr
2
```
## *list_zip*
```bash
$ list a b c d e f | list_zip $(seq 1 10)
(a,1)
(b,2)
(c,3)
(d,4)
(e,5)
(f,6)
```
```bash
$ list a b c d e f | list_zip $(seq 1 10) | list_last | tupr
6
```
## *not/isint/isempty*
```bash
$ isint 42
true
$ list blah | isint
false
$ not true
false
$ not "isint 777"
false
$ list 1 2 "" c d 6 | filter λ a . 'isint $a'
1
2
6
$ list 1 2 "" c d 6 | filter λ a . 'not "isempty $a"'
1
2
c
d
6
```
## *isfile/isnonzerofile/isreadable/iswritable/isdir*
```bash
$ touch /tmp/foo
$ isfile /tmp/foo
true
$ not iswritable /
true
$ files="/etc/passwd /etc/sudoers /tmp /tmp/foo /no_such_file"
$ list $files | filter λ a . 'isfile $a'
/etc/passwd
/etc/sudoers
/tmp/foo
$ list $files | filter λ a . 'isdir $a'
/tmp
$ list $files | filter λ a . 'isreadable $a'
/etc/passwd
/tmp
/tmp/foo
$ list $files | filter λ a . 'iswritable $a'
/tmp
/tmp/foo
$ list $files | filter λ a . 'isnonzerofile $a'
/etc/passwd
/etc/sudoers
/tmp
$ list $files | filter λ a . 'not isfile $a'
/tmp
/no_such_file
```
## *scanl*
```bash
$ seq 1 5 | scanl lambda acc el . 'echo $(($acc + $el))'
1
3
6
10
15
```
```bash
$ seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' | list_last
15
```
# Examples
```bash
processNames() {
uppercase() {
local str=$1
echo $(tr 'a-z' 'A-Z' <<< ${str:0:1})${str:1}
}
list $@ \
| filter λ name . '[[ ${#name} -gt 1 ]] && ret true || ret false' \
| list_map λ name . 'uppercase $name' \
| foldl λ acc el . 'echo $acc,$el'
}
processNames adam monika s slawek d daniel Bartek j k
```
```bash
Adam,Monika,Slawek,Daniel,Bartek
```
# Running tests
TODO: Need to change the tests here
```bash
cd test
./test_runner
```
# Contribution guidelines
Feel free to ask questions in chat, open issues, or contribute by creating pull requests.
In order to create a pull request
* checkout master branch
* introduce your changes & bump version
* submit pull request
# Resources
* [Inspiration](https://quasimal.com/posts/2012-05-21-funsh.html)
* [Functional Programming in Bash](https://medium.com/@joydeepubuntu/functional-programming-in-bash-145b6db336b7)

View file

@ -1,30 +1,29 @@
#!/bin/bash
source ../src/fun.sh
seq 1 4 | sum
seq 1 4 | product
factorial 4
seq 1 4 | scanl lambda a b . 'echo $(add $a $b)'
echo map mul
seq 1 4 | map lambda a . 'echo $(mul $a 2)'
echo map sub
seq 1 4 | map lambda a . 'echo $(sub $a 2)'
echo map multiply
seq 1 4 | list_map lambda a . 'echo $(multiply $a 2)'
echo map minus
seq 1 4 | list_map lambda a . 'echo $(minus $a 2)'
echo map add
seq 1 4 | map lambda a . 'echo $(add $a 2)'
echo map div
seq 1 4 | map lambda a . 'echo $(div $a 2)'
echo map mod
seq 1 4 | map lambda a . 'echo $(mod $a 2)'
seq 1 4 | list_map lambda a . 'echo $(add $a 2)'
echo map divide
seq 1 4 | list_map lambda a . 'echo $(divide $a 2)'
echo list_map mod
seq 1 4 | list_map lambda a . 'echo $(mod $a 2)'
echo 'list & head'
list 1 2 3 4 5 | head
list {1..2} | append {3..4} | prepend {99..102}
list 1 2 3 4 5 | list_head
list {1..2} | list_append {3..4} | list_prepend {99..102}
list {1..2} | unlist
list {1..10} | head
list {1..10} | drop 7
list {1..10} | take 3
list {1..10} | last
list {1..10} | map λ a . 'echo $(mul $a 2)'
list {1..10} | list_head
list {1..10} | list_drop 7
list {1..10} | list_take 3
list {1..10} | list_last
list {1..10} | list_map λ a . 'echo $(multiply $a 2)'
id() {
λ x . '$x'
@ -38,12 +37,32 @@ foobar() {
list {1,2,3} | foobar
echo -n abcdefg | revers_str # gfedcba
echo -n abcdefg | splitc | join , '[' ']' # [a,b,c,d,e,f,g]
echo -n abcdefg | splitc | revers | join , '[' ']' # [g,f,e,d,c,b,a]
echo -n abcdefg | revers_str # gfedcba
echo -n abcdefg | splitc | list_join , # a,b,c,d,e,f,g
echo -n abcdefg | splitc | revers | list_join , # g,f,e,d,c,b,a
echo -n ' abcdefg' | splitc | foldr lambda a b . 'echo $a$b' # gfedcba
list {1..10} | filter lambda a . '[[ $(mod $a 2) -eq 0 ]] && ret true || ret false' | list_join , # 2,4,6,8,10
echo 'ls' | try λ cmd status ret . 'echo $cmd [$status]; echo $ret'
list a b c d | foldl lambda acc el . 'echo -n $acc-$el'
seq 1 4 | foldl lambda acc el . 'echo $(($acc + $el))'
list {1..10} | filter lambda a . '[[ $(mod $a 2) -eq 0 ]] && ret true || ret false' | join , '[' ']' # [2,4,6,8,10]
#1 - 2 - 3 - 4
seq 1 4 | foldl lambda acc el . 'echo $(($acc - $el))'
#1 + (1 + 1) * 2 + (4 + 1) * 3 + (15 + 1) * 4 = 64
seq 1 4 | foldl lambda acc el . 'echo $(multiply $(($acc + 1)) $el)'
tup a 1
tup a 1 | tupl
tup a 1 | tupr
list a b c d e f | list_zip $(seq 1 10)
echo
list a b c d e f | list_zip $(seq 1 10) | list_last | tupr
seq 1 5 | scanl lambda a b . 'echo $(($a + $b))'
seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' | list_last
seq 2 3 | list_map lambda a . 'seq 1 $a' | list_join ,
list a b c | list_map lambda a . 'echo $a; echo $a | tr a-z A-z' | list_join ,

625
src/fun.sh Executable file → Normal file
View file

@ -1,236 +1,553 @@
#!/bin/bash
drop() {
command tail -n +$(($1 + 1))
}
take() {
command head -n ${1}
}
tail() {
drop 1
}
head() {
take 1
}
last() {
command tail -n 1
}
#!/bin/sh
###############################################
## List Functions
###############################################
list() {
for i in "$@"; do
echo "$i"
done
for i in "$@"; do
echo "$i"
done
}
unlist() {
cat - | xargs
xargs
}
append() {
cat -
list "$@"
# Drop the first n items of a list.
list_drop() {
command tail -n +$(($1 + 1))
}
prepend() {
list "$@"
cat -
# Take the first n items of a list.
list_take() {
command head -n "$1"
}
# Take the 'tail' of a list.
# Otherwise known as dropping the first element.
list_tail() {
list_drop 1
}
# Take only the first element of the list.
list_head() {
list_take 1
}
# Take the last element of the list.
list_last() {
command tail -n 1
}
# Add the contents of standard input
# to the end of the list.
list_append() {
cat -
list "$@"
}
# Add the contents of standard input
# to the beginning of the list.
list_prepend() {
list "$@"
cat -
}
###############################################
## Lambdas and Lists
###############################################
# Defines an anonymous function.
lambda() {
# shellcheck disable=2039
local expression
lam() {
# shellcheck disable=2039
local arg
while [ $# -gt 0 ]; do
arg="$1"
shift
if [ "$arg" = '.' ]; then
echo "$@"
return
else
echo "read $arg;"
fi
done
}
lam() {
local arg
while [[ $# -gt 0 ]]; do
arg="$1"
shift
if [[ $arg = '.' ]]; then
echo "$@"
return
else
echo "read $arg;"
fi
done
}
eval $(lam "$@")
expression=$(lam "$@")
eval "$expression"
}
# Same as lambda.
# shellcheck disable=2039
λ() {
lambda "$@"
lambda "$@"
}
map() {
local x
while read x; do
echo "$x" | "$@"
done
# Print the number of arguments a lambda takes.
# shellcheck disable=2039
λ_num_args() {
# Calculates the number of arguments a lambda takes
minus "$#" 3
}
# Perform an operation to each
# element(s) of a list provided
# through standard input.
list_map() {
# shellcheck disable=2039
local x
# shellcheck disable=2039
local i
# shellcheck disable=2039
local arguments
# shellcheck disable=2039
local num_args
if [ "$1" = "λ" ] || [ "$1" = "lambda" ]; then
num_args=$(λ_num_args "$@")
while read -r x; do
arguments="$x"
i=2
while [ $i -le "$num_args" ] ; do
read -r x
arguments="$arguments $x"
i=$(add $i 1)
done
# We want to word split arguments, so no quotes
eval "list $arguments" | "$@"
done
else # Do not know the arity, assume 1
while read -r x; do
echo "$x" | "$@"
done
fi
}
# Perform a binary operation on a list
# where one element is the accumulation
# of the results so far.
# Ex: seq 3 | foldl lambda a b . 'minus $a $b'
# First is (1 - 2 = -1) then (-1 - 3 = -4).
foldl() {
local f="$@"
local acc
read acc
while read elem; do
acc="$({ echo $acc; echo $elem; } | $f )"
done
echo "$acc"
}
foldr() {
local f="$@"
local acc
read acc
foldrr() {
local elem
read elem && acc=$(foldrr)
acc="$({ echo $acc; echo $elem; } | $f )"
# shellcheck disable=2039
local acc
read -r acc
while read -r elem; do
acc=$({ echo "$acc"; echo "$elem"; } | "$@" )
done
echo "$acc"
}
foldrr
}
# Constructs a list where each element
# is the foldl of the 0th-ith elements of
# the list.
scanl() {
local f="$@"
local acc
read acc
echo $acc
while read elem; do
acc="$({ echo $acc; echo $elem; } | $f )"
# shellcheck disable=2039
local acc
read -r acc
echo "$acc"
done
while read -r elem; do
acc=$({ echo "$acc"; echo "$elem"; } | "$@" )
echo "$acc"
done
}
mul() {
( set -f; echo $(($1 * $2)) )
# Drops any elements of the list where the
# function performed on it evaluates to false.
filter() {
# shellcheck disable=2039
local x
while read -r x; do
ret=$(echo "$x" | "$@")
if_then "$ret" "echo $x"
done
}
# Keep taking elements until a certain condition
# is false.
take_while() {
# shellcheck disable=2039
local x
# shellcheck disable=2039
local condition
while read -r x; do
condition="$(echo "$x" | "$@")"
if_then_else "$condition" "echo $x" "break"
done
}
# Keep dropping elements until a certain condition
# is false.
drop_while() {
# shellcheck disable=2039
local x
while read -r x; do
condition="$(echo "$x" | "$@")"
if_then_else "$condition" 'do_nothing' 'break'
done
if_then "[ -n $x ]" "{ echo $x; cat -; }"
}
###############################################
## Arithmetic Functions
###############################################
multiply() {
# shellcheck disable=2039
local a
# shellcheck disable=2039
local b
a=$1
if [ $# -lt 2 ] ; then
read -r b
else
b=$2
fi
isint "$a" > /dev/null && \
isint "$b" > /dev/null && \
echo $((a * b))
}
add() {
echo $(($1 + $2))
# shellcheck disable=2039
local a
# shellcheck disable=2039
local b
a=$1
if [ $# -lt 2 ] ; then
read -r b
else
b=$2
fi
isint "$a" > /dev/null && \
isint "$b" > /dev/null && \
echo $((a + b))
}
sub() {
echo $(($1 - $2))
minus() {
# shellcheck disable=2039
local a
# shellcheck disable=2039
local b
a=$1
if [ $# -lt 2 ] ; then
b=$1
read -r a
else
b=$2
fi
isint "$a" > /dev/null && \
isint "$b" > /dev/null && \
echo $((a - b))
}
div() {
echo $(($1 / $2))
divide() {
# shellcheck disable=2039
local a
# shellcheck disable=2039
local b
a=$1
if [ $# -lt 2 ] ; then
b=$1
read -r a
else
b=$2
fi
isint "$a" > /dev/null && \
isint "$b" > /dev/null && \
echo $((a / b))
}
mod() {
echo $(($1 % $2))
# shellcheck disable=2039
local a
# shellcheck disable=2039
local b
a=$1
if [ $# -lt 2 ] ; then
b=$1
read -r a
else
b=$2
fi
isint "$a" > /dev/null && \
isint "$b" > /dev/null && \
echo $((a % b))
}
even() {
# shellcheck disable=2039
local n
# shellcheck disable=2039
local result
# shellcheck disable=2039
local result_code
if [ $# -lt 1 ] ; then
read -r n
else
n=$1
fi
result=$(mod "$n" 2)
result_code=$?
if [ $result_code -ne 0 ] ; then
ret false
else
result_to_bool "[ $result = 0 ]"
fi
}
odd() {
not even
}
less_than() {
# shellcheck disable=2039
local n
read -r n
if isint "$n" > /dev/null && \
[ "$n" -lt "$1" ] ; then
ret true
else
ret false
fi
}
sum() {
foldl lambda a b . 'echo $(($a + $b))'
foldl lambda a b . "add \$a \$b"
}
product() {
foldl lambda a b . 'echo $(mul $a $b)'
foldl lambda a b . "multiply \$a \$b"
}
factorial() {
seq 1 $1 | product
seq 1 "$1" | product
}
###############################################
## String Operations
###############################################
# Splits a string into a list where each element
# is one character.
splitc() {
cat - | sed 's/./&\n/g'
sed 's/./\n&/g' | list_tail
}
join() {
local delim=$1
local pref=$2
local suff=$3
echo $pref$(cat - | foldl lambda a b . 'echo $a$delim$b')$suff
# Takes a list and creates a string where
# each element is seperated by a delimiter.
list_join() {
# shellcheck disable=2039
local delim
delim=$1
foldl lambda a b . "echo \$a$delim\$b"
}
# Split a string into a list
# by a specified delimeter
str_split() {
sed "s/$1/\n/g"
}
# Reverses a list.
revers() {
foldl lambda a b . 'append $b $a'
# shellcheck disable=2039
local result
# shellcheck disable=2039
local n
while read -r n; do
result="$n\n$result"
done
echo "$result"
}
# Reverses a string
revers_str() {
cat - | splitc | revers | join
splitc | revers | list_join
}
try() {
local f="$@"
local cmd=$(cat -)
ret="$(2>&1 $cmd)"
local status=$?
list "$cmd" $status $(list $ret | join \#) | $f
# Removes multiple occurences of
# a single character from the beginning
# of the list.
lstrip() {
# shellcheck disable=2039
local c
if [ $# -eq 0 ] ; then
c=" "
else
c="$1"
fi
sed "s/^$c*//g"
}
# Removes multiple occurences of
# a single character from the end
# of the list.
rstrip() {
# shellcheck disable=2039
local c
if [ $# -eq 0 ] ; then
c=" "
else
c="$1"
fi
sed "s/$c*$//g"
}
# Removes multiple occurences of
# a single character from the beginning
# and end of the list.
strip() {
lstrip "$@" | rstrip "$@"
}
###############################################
## Tuple Functions
###############################################
# Creates a tuple, which is a string with
# multiple elements seperated by a comma,
# and it begins with a ( and ends with a ).
tup() {
# shellcheck disable=2039
local args
# shellcheck disable=2039
local result
if [ $# -eq 0 ]; then
args=$(unlist)
eval "tup $args"
else
result=$(list "$@" | list_join ,)
echo "($result)"
fi
}
# Takes a tuple and outputs it as a list
tup_to_list() {
local li
local f
local la
li=$(str_split ",")
# Remove '(' from the first element
f=$(echo "$li" | list_head)
f=$(echo "$f" | sed 's/^(//')
la=$(echo "$li" | list_last)
# If there is only one element in the list
# Remove ')' from the only element
if [ "$(echo "$la" | cut -c1)" = "(" ]; then
f=$(echo "$f" | sed "s/)$//")
echo "$f"
# If there is more than one element in the list
# Remove ')' from the last element
else
la=$(echo "$la" | sed "s/)$//")
# Remove the first and last element from li
li=$(echo "$li" | list_tail | sed '$d')
# Print the list
{ echo "$f"; echo "$li"; echo "$la"; }
fi
}
# Takes the first element of the tuple
tupl() {
tup_to_list | list_head
}
# Takes the last element of the tuple
tupr() {
tup_to_list | list_last
}
# Takes each element from a list in standard
# input and matches it with a list provided
# as the argument to this function.
# The result is a list of 2-tuples.
list_zip() {
# shellcheck disable=2039
local l
l=$(list "$@")
while read -r x; do
y=$(echo "$l" | list_take 1)
tup "$x" "$y"
l=$(echo "$l" | list_drop 1)
done
}
###############################################
## Logic Based Functions
###############################################
if_then() {
# shellcheck disable=2039
local result
eval "$1"
result=$?
if [ $result -eq 0 ] ; then
eval "$2"
fi
}
if_then_else() {
# shellcheck disable=2039
local result
eval "$1"
result=$?
if [ $result -eq 0 ] ; then
eval "$2"
else
eval "$3"
fi
}
result_to_bool() {
if_then_else "$1" 'ret true' 'ret false'
}
not() {
if_then_else "$1 > /dev/null" "ret false" "ret true"
}
ret() {
echo $1
echo "$@"
"$@"
}
filter() {
local x
while read x; do
ret=$(echo "$x" | "$@")
$ret && echo $x
done
do_nothing() {
echo > /dev/null
}
strip() {
local arg=$1
cat - | map lambda l . 'ret ${l##'$arg'}' | map lambda l . 'ret ${l%%'$arg'}'
###############################################
## Useful utility functions
###############################################
isint() {
result_to_bool "echo \"$1\" | grep -Eq '^-?[0-9]+$'"
}
buff() {
local cnt=-1
for x in $@; do
[[ $x = '.' ]] && break
cnt=$(add $cnt 1)
done
local args=''
local i=$cnt
while read arg; do
[[ $i -eq 0 ]] && list $args | "$@" && i=$cnt && args=''
args="$args $arg"
i=$(sub $i 1)
done
[[ ! -z $args ]] && list $args | "$@"
isempty() {
result_to_bool "[ -z \"$1\" ]"
}
tup() {
list "$@" | join , '(' ')'
isfile() {
result_to_bool "[ -f \"$1\" ]"
}
tupx() {
if [[ $# -eq 1 ]]; then
local arg
read arg
tupx "$1" "$arg"
else
local n=$1
shift
list "$@" | strip '\(' | strip '\)' | unlist | cut -d',' -f${n}
fi
isnonzerofile() {
result_to_bool "[ -s \"$1\" ]"
}
tupl() {
tupx 1 "$@"
isreadable() {
result_to_bool "[ -r \"$1\" ]"
}
tupr() {
tupx 2 "$@"
iswritable() {
result_to_bool "[ -w \"$1\" ]"
}
zip() {
local list=$*
cat - | while read x; do
y=$(list $list | take 1)
tup $x $y
list=$(list $list | drop 1)
done
isdir() {
result_to_bool "[ -d \"$1\" ]"
}

15
test/append_test.sh Executable file
View file

@ -0,0 +1,15 @@
#! /bin/bash
testAppendToEmptyList() {
assertEquals 4 "$(list | list_append 4)"
}
testAppendToOneElementList() {
assertEquals "1 4" "$(list 1 | list_append 4 | unlist)"
}
testAppendToList() {
assertEquals "1 2 3 4 5 4" "$(list 1 2 3 4 5 | list_append 4 | unlist)"
}
. ./shunit2-init.sh

23
test/drop_test.sh Executable file
View file

@ -0,0 +1,23 @@
#! /bin/bash
testDrop9From10() {
assertEquals 10 $(list {1..10} | list_drop 9)
}
testDrop8From10() {
assertEquals "9 10" "$(list {1..10} | list_drop 8 | unlist)"
}
testDropAll() {
assertEquals "" "$(list {1..10} | list_drop 10)"
}
testDropMoreThanAvailable() {
assertEquals "" "$(list {1..10} | list_drop 15)"
}
testDropZero() {
assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_drop 0 | unlist)"
}
. ./shunit2-init.sh

16
test/head_test.sh Executable file
View file

@ -0,0 +1,16 @@
#! /bin/bash
testLHeadFromList() {
assertEquals 1 $(list {1..10} | list_head)
assertEquals 5 $(list 5 6 7 | list_head)
}
testLHeadFromOneElementList() {
assertEquals 1 $(list 1 | list_head)
}
testLHeadFromEmptyList() {
assertEquals "" "$(list | list_head)"
}
. ./shunit2-init.sh

38
test/lambda_test.sh Executable file
View file

@ -0,0 +1,38 @@
#! /bin/bash
testLambdaNoArguments_ifNoInput() {
assertEquals 'Hi there' "$(echo | lambda . 'echo Hi there')"
}
testLambdaNoArguments_ifSomeInputArguments() {
assertEquals 'Hi there' "$(echo 'xx\nyy\nzz' | lambda . 'echo Hi there')"
}
testLambdaOneArgument() {
identity() {
lambda x . '$x'
}
assertEquals "hi there !" "$(identity <<< 'echo hi there !')"
assertEquals 3 $(lambda x . 'echo $(($x + 1))' <<< '2')
assertEquals "hi there !" "$(λ x . 'echo $x' <<< 'hi there !')"
}
testLambdaSymbolTwoArguments() {
assertEquals 3 $(echo -e '1\n2' | lambda x y . 'echo $(($x + $y))')
assertEquals 5 $(echo -e '7\n2' | λ x y . 'echo $(($x - $y))')
}
testLambdaSymbolManyArguments() {
assertEquals 5 $(echo -e '1\n2\n3\n4\n5' | lambda a b c d e . 'echo $(($a + $b + $c + $d - $e))')
}
testLambdaSymbolManyArguments_ifInsufficientNumberOfArgumentsInLambda() {
assertEquals 6 $(echo -e '1\n2\n3\n4\n5' | lambda a b c . 'echo $(($a + $b + $c))')
}
testLambdaSymbolManyArguments_ifInsufficientNumberOfInputArguments() {
echo -e '1\n2' | lambda a b c d e . 'echo $(($a + $b + $c + $d + $e))' 2> /dev/null \
&& fail "There should be syntax error"
}
. ./shunit2-init.sh

16
test/last_test.sh Executable file
View file

@ -0,0 +1,16 @@
#! /bin/bash
testLastFromList() {
assertEquals 10 $(list {1..10} | list_last)
assertEquals 7 $(list 5 6 7 | list_last)
}
testLastFromOneElementList() {
assertEquals 1 $(list 1 | list_last)
}
testLastFromEmptyList() {
assertEquals "" "$(list | list_last)"
}
. ./shunit2-init.sh

1222
test/lib/shflags Normal file

File diff suppressed because it is too large Load diff

39
test/lib/shlib Normal file
View file

@ -0,0 +1,39 @@
# vim:et:ft=sh:sts=2:sw=2
#
# Copyright 2008 Kate Ward. All Rights Reserved.
# Released under the LGPL (GNU Lesser General Public License).
#
# Author: kate.ward@forestent.com (Kate Ward)
#
# Library of shell functions.
# Convert a relative path into it's absolute equivalent.
#
# This function will automatically prepend the current working directory if the
# path is not already absolute. It then removes all parent references (../) to
# reconstruct the proper absolute path.
#
# Args:
# shlib_path_: string: relative path
# Outputs:
# string: absolute path
shlib_relToAbsPath()
{
shlib_path_=$1
# prepend current directory to relative paths
echo "${shlib_path_}" |grep '^/' >/dev/null 2>&1 \
|| shlib_path_="${PWD}/${shlib_path_}"
# clean up the path. if all seds supported true regular expressions, then
# this is what it would be:
shlib_old_=${shlib_path_}
while true; do
shlib_new_=`echo "${shlib_old_}" |sed 's/[^/]*\/\.\.\/*//;s/\/\.\//\//'`
[ "${shlib_old_}" = "${shlib_new_}" ] && break
shlib_old_=${shlib_new_}
done
echo "${shlib_new_}"
unset shlib_path_ shlib_old_ shlib_new_
}

251
test/lib/versions Executable file
View file

@ -0,0 +1,251 @@
#! /bin/sh
# vim:et:ft=sh:sts=2:sw=2
#
# Versions determines the versions of all installed shells.
#
# Copyright 2008-2017 Kate Ward. All Rights Reserved.
# Released under the Apache 2.0 License.
#
# Author: kate.ward@forestent.com (Kate Ward)
# https://github.com/kward/shlib
#
# This library provides reusable functions that determine actual names and
# versions of installed shells and the OS. The library can also be run as a
# script if set executable.
#
# Disable checks that aren't fully portable (POSIX != portable).
# shellcheck disable=SC2006
ARGV0=`basename "$0"`
LSB_RELEASE='/etc/lsb-release'
VERSIONS_SHELLS="ash /bin/bash /bin/dash /bin/ksh /bin/pdksh /bin/sh /bin/zsh"
true; TRUE=$?
false; FALSE=$?
ERROR=2
UNAME_R=`uname -r`
UNAME_S=`uname -s`
__versions_haveStrings=${ERROR}
versions_osName() {
os_name_='unrecognized'
os_system_=${UNAME_S}
os_release_=${UNAME_R}
case ${os_system_} in
CYGWIN_NT-*) os_name_='Cygwin' ;;
Darwin)
os_name_=`/usr/bin/sw_vers -productName`
os_version_=`versions_osVersion`
case ${os_version_} in
10.4|10.4.[0-9]*) os_name_='Mac OS X Tiger' ;;
10.5|10.5.[0-9]*) os_name_='Mac OS X Leopard' ;;
10.6|10.6.[0-9]*) os_name_='Mac OS X Snow Leopard' ;;
10.7|10.7.[0-9]*) os_name_='Mac OS X Lion' ;;
10.8|10.8.[0-9]*) os_name_='Mac OS X Mountain Lion' ;;
10.9|10.9.[0-9]*) os_name_='Mac OS X Mavericks' ;;
10.10|10.10.[0-9]*) os_name_='Mac OS X Yosemite' ;;
10.11|10.11.[0-9]*) os_name_='Mac OS X El Capitan' ;;
10.12|10.12.[0-9]*) os_name_='macOS Sierra' ;;
10.13|10.13.[0-9]*) os_name_='macOS High Sierra' ;;
*) os_name_='macOS' ;;
esac
;;
FreeBSD) os_name_='FreeBSD' ;;
Linux) os_name_='Linux' ;;
SunOS)
if grep 'OpenSolaris' /etc/release >/dev/null; then
os_name_='OpenSolaris'
else
os_name_='Solaris'
fi
;;
esac
echo ${os_name_}
unset os_name_ os_system_ os_release_ os_version_
}
versions_osVersion() {
os_version_='unrecognized'
os_system_=${UNAME_S}
os_release_=${UNAME_R}
case ${os_system_} in
CYGWIN_NT-*)
os_version_=`expr "${os_release_}" : '\([0-9]*\.[0-9]\.[0-9]*\).*'`
;;
Darwin)
os_version_=`/usr/bin/sw_vers -productVersion`
;;
FreeBSD)
os_version_=`expr "${os_release_}" : '\([0-9]*\.[0-9]*\)-.*'`
;;
Linux)
if [ -r '/etc/os-release' ]; then
os_version_=`awk -F= '$1~/PRETTY_NAME/{print $2}' /etc/os-release \
|sed 's/"//g'`
elif [ -r '/etc/redhat-release' ]; then
os_version_=`cat /etc/redhat-release`
elif [ -r '/etc/SuSE-release' ]; then
os_version_=`head -n 1 /etc/SuSE-release`
elif [ -r "${LSB_RELEASE}" ]; then
if grep -q 'DISTRIB_ID=Ubuntu' "${LSB_RELEASE}"; then
# shellcheck disable=SC2002
os_version_=`cat "${LSB_RELEASE}" \
|awk -F= '$1~/DISTRIB_DESCRIPTION/{print $2}' \
|sed 's/"//g;s/ /-/g'`
fi
fi
;;
SunOS)
if grep 'OpenSolaris' /etc/release >/dev/null; then
os_version_=`grep 'OpenSolaris' /etc/release |awk '{print $2"("$3")"}'`
else
major_=`echo "${os_release_}" |sed 's/[0-9]*\.\([0-9]*\)/\1/'`
minor_=`grep Solaris /etc/release |sed 's/[^u]*\(u[0-9]*\).*/\1/'`
os_version_="${major_}${minor_}"
fi
;;
esac
echo "${os_version_}"
unset os_name_ os_release_ os_version_ major_ minor_
}
versions_shellVersion() {
shell_=$1
shell_present_=${FALSE}
case "${shell_}" in
ash)
[ -x '/bin/busybox' ] && shell_present_=${TRUE}
;;
*)
[ -x "${shell_}" ] && shell_present_=${TRUE}
;;
esac
if [ ${shell_present_} -eq ${FALSE} ]; then
echo 'not installed'
return ${FALSE}
fi
version_=''
case ${shell_} in
*/sh)
# TODO(kward): fix this
## this could be one of any number of shells. try until one fits.
#version_=`versions_shell_bash ${shell_}`
## dash cannot be self determined yet
#[ -z "${version_}" ] && version_=`versions_shell_ksh ${shell_}`
## pdksh is covered in versions_shell_ksh()
#[ -z "${version_}" ] && version_=`versions_shell_zsh ${shell_}`
;;
ash) version_=`versions_shell_ash "${shell_}"` ;;
*/bash) version_=`versions_shell_bash "${shell_}"` ;;
*/dash)
# simply assuming Ubuntu Linux until somebody comes up with a better
# test. the following test will return an empty string if dash is not
# installed.
version_=`versions_shell_dash`
;;
*/ksh) version_=`versions_shell_ksh "${shell_}"` ;;
*/pdksh) version_=`versions_shell_pdksh "${shell_}"` ;;
*/zsh) version_=`versions_shell_zsh "${shell_}"` ;;
*) version_='invalid'
esac
echo "${version_:-unknown}"
unset shell_ version_
}
# The ash shell is included in BusyBox.
versions_shell_ash() {
busybox --help |head -1 |sed 's/BusyBox v\([0-9.]*\) .*/\1/'
}
versions_shell_bash() {
$1 --version 2>&1 |grep 'GNU bash' |sed 's/.*version \([^ ]*\).*/\1/'
}
versions_shell_dash() {
eval dpkg >/dev/null 2>&1
[ $? -eq 127 ] && return # return if dpkg not found
dpkg -l |grep ' dash ' |awk '{print $3}'
}
versions_shell_ksh() {
versions_shell_=$1
versions_version_=''
# Try a few different ways to figure out the version.
if versions_version_=`${versions_shell_} --version : 2>&1`; then
versions_version_=`echo "${versions_version_}" \
|sed 's/.*\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\).*/\1/'`
fi
if [ -z "${versions_version_}" ]; then
_versions_have_strings
versions_version_=`strings "${versions_shell_}" 2>&1 \
|grep Version \
|sed 's/^.*Version \(.*\)$/\1/;s/ s+ \$$//;s/ /-/g'`
fi
if [ -z "${versions_version_}" ]; then
versions_version_=`versions_shell_pdksh "${versions_shell_}"`
fi
echo "${versions_version_}"
unset versions_shell_ versions_version_
}
versions_shell_pdksh() {
_versions_have_strings
strings "$1" 2>&1 \
|grep 'PD KSH' \
|sed -e 's/.*PD KSH \(.*\)/\1/;s/ /-/g'
}
versions_shell_zsh() {
versions_shell_=$1
# Try a few different ways to figure out the version.
# shellcheck disable=SC2016
versions_version_=`echo 'echo ${ZSH_VERSION}' |${versions_shell_}`
if [ -z "${versions_version_}" ]; then
versions_version_=`${versions_shell_} --version 2>&1 |awk '{print $2}'`
fi
echo "${versions_version_}"
unset versions_shell_ versions_version_
}
# Determine if the 'strings' binary installed.
_versions_have_strings() {
[ ${__versions_haveStrings} -ne ${ERROR} ] && return
if eval strings /dev/null >/dev/null 2>&1; then
__versions_haveStrings=${TRUE}
return
fi
echo 'WARN: strings not installed. try installing binutils?' >&2
__versions_haveStrings=${FALSE}
}
versions_main() {
# Treat unset variables as an error.
set -u
os_name=`versions_osName`
os_version=`versions_osVersion`
echo "os: ${os_name} version: ${os_version}"
for shell in ${VERSIONS_SHELLS}; do
shell_version=`versions_shellVersion "${shell}"`
echo "shell: ${shell} version: ${shell_version}"
done
}
if [ "${ARGV0}" = 'versions' ]; then
versions_main "$@"
fi

25
test/list_test.sh Executable file
View file

@ -0,0 +1,25 @@
#! /bin/bash
testListFromOneElement() {
assertEquals 1 $(list 1)
}
testListFromEmpty() {
assertEquals "" "$(list)"
}
testListUnlist() {
assertEquals "1 3 6" "$(list 1 3 6 | unlist)"
}
testList() {
list=$(cat <<EOF
1
3
6
EOF
)
assertEquals "$list" "$(list 1 3 6)"
}
. ./shunit2-init.sh

33
test/map_test.sh Executable file
View file

@ -0,0 +1,33 @@
#!/bin/bash
testMapEmptyList() {
assertEquals "" "$(list | list_map lambda x . 'echo $(($x + 1))')"
}
testMapEmptyList_ifNoArgumentsInLambda() {
assertEquals "" "$(list | list_map lambda . 'echo 3')"
}
testMapOneElementList() {
assertEquals "3" "$(list 2 | list_map lambda x . 'echo $(($x + 1))')"
}
testMapList() {
assertEquals "2 3 4 5 6" "$(list {1..5} | list_map lambda x . 'echo $(($x + 1))' | unlist)"
}
testMapList_ifNoArgumentsInLambda() {
assertEquals "9 9 9 9 9" "$(list {1..5} | list_map lambda . 'echo 9' | unlist)"
}
testMapList_ifManyArgumentsInLambda() {
list {1..5} | list_map lambda x y . 'echo $(($x + $y))' 2> /dev/null \
&& fail "There should be syntax error, because map is an one argument operation"
}
testFlatMap() {
assertEquals "1 2 3 2 3 3" "$(list {1..3} | list_map lambda x . 'seq $x 3' | unlist)"
assertEquals "d e h l l l o o r w" "$(list hello world | list_map lambda x . 'command fold -w 1 <<< $x' | sort | unlist)"
}
. ./shunit2-init.sh

52
test/predicates_test.sh Executable file
View file

@ -0,0 +1,52 @@
#! /bin/bash
testIsint() {
assertEquals 'true' $(isint 1)
assertEquals 'true' $(isint -1)
assertEquals 'false' $(isint a)
assertEquals 'false' $(isint "")
assertEquals '1 2 3 4 5' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . 'isint $x' | unlist )"
assertEquals '1 2' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . '($(isint $x) && [[ $x -le 2 ]] && ret true) || ret false ' | unlist )"
assertEquals 'false' $(not "isint 1")
assertEquals 'true' $(not "isint a")
}
testIsempty() {
assertEquals 'true' $(isempty "")
assertEquals 'false' $(isempty a)
assertEquals 'true' $(not "isempty a")
assertEquals 'false' $(not "isempty \"\"")
}
testIsfile() {
f=$(mktemp)
assertEquals 'true' $(isfile $f)
assertEquals 'false' $(isfile $f.xxx)
assertEquals 'false' $(isfile "")
assertEquals 'true' $(not "isfile $f.xxx")
assertEquals 'false' $(isnonzerofile $f)
echo hello world >$f
assertEquals 'true' $(isnonzerofile $f)
assertEquals 'true' $(iswritable $f)
chmod 400 $f
assertEquals 'false' $(iswritable $f)
assertEquals 'true' $(isreadable $f)
chmod 200 $f
assertEquals 'false' $(isreadable $f)
chmod 600 $f
rm $f
}
testIsdir() {
assertEquals 'true' $(isdir .)
assertEquals 'false' $(isdir sir_not_appearing_in_this_film)
}
. ./shunit2-init.sh

15
test/prepend_test.sh Executable file
View file

@ -0,0 +1,15 @@
#! /bin/bash
testPrependToEmptyList() {
assertEquals 4 "$(list | list_prepend 4)"
}
testPrependToOneElementList() {
assertEquals "4 1" "$(list 1 | list_prepend 4 | unlist)"
}
testPrependToList() {
assertEquals "4 1 2 3 4 5" "$(list 1 2 3 4 5 | list_prepend 4 | unlist)"
}
. ./shunit2-init.sh

1137
test/shunit2 Executable file

File diff suppressed because it is too large Load diff

8
test/shunit2-init.sh Normal file
View file

@ -0,0 +1,8 @@
#!/bin/bash
oneTimeSetUp() {
. ../src/fun.sh
}
# Load shUnit2.
. ./shunit2

15
test/tail_test.sh Executable file
View file

@ -0,0 +1,15 @@
#! /bin/bash
testLTailFrom10() {
assertEquals "2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_tail | unlist)"
}
testLTailFromOneElementList() {
assertEquals "" "$(list 1 | list_tail)"
}
testLTailFromEmptyList() {
assertEquals "" "$(list | list_tail)"
}
. ./shunit2-init.sh

23
test/take_test.sh Executable file
View file

@ -0,0 +1,23 @@
#! /bin/bash
testTake9From10() {
assertEquals "1 2 3 4 5 6 7 8 9" "$(list {1..10} | list_take 9 | unlist)"
}
testTake8From10() {
assertEquals "1 2 3 4 5 6 7 8" "$(list {1..10} | list_take 8 | unlist)"
}
testTakeAll() {
assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_take 10 | unlist)"
}
testTakeMoreThanAvailable() {
assertEquals "1 2 3 4 5 6 7 8 9 10" "$(list {1..10} | list_take 15 | unlist)"
}
testTakeZero() {
assertEquals "" "$(list {1..10} | list_take 0 | unlist)"
}
. ./shunit2-init.sh

163
test/test_runner Executable file
View file

@ -0,0 +1,163 @@
#! /bin/sh
# vim:et:ft=sh:sts=2:sw=2
#
# Unit test suite runner.
#
# Copyright 2008-2017 Kate Ward. All Rights Reserved.
# Released under the Apache 2.0 license.
#
# Author: kate.ward@forestent.com (Kate Ward)
# https://github.com/kward/shlib
#
# This script runs all the unit tests that can be found, and generates a nice
# report of the tests.
#
### ShellCheck (http://www.shellcheck.net/)
# Disable source following.
# shellcheck disable=SC1090,SC1091
# expr may be antiquated, but it is the only solution in some cases.
# shellcheck disable=SC2003
# Return if test_runner already loaded.
[ -z "${RUNNER_LOADED:-}" ] || return 0
RUNNER_LOADED=0
RUNNER_ARGV0=$(basename "$0")
RUNNER_SHELLS='/bin/bash'
RUNNER_TEST_SUFFIX='_test.sh'
runner_warn() { echo "runner:WARN $*" >&2; }
runner_error() { echo "runner:ERROR $*" >&2; }
runner_fatal() { echo "runner:FATAL $*" >&2; exit 1; }
runner_usage() {
echo "usage: ${RUNNER_ARGV0} [-e key=val ...] [-s shell(s)] [-t test(s)]"
}
_runner_tests() { echo ./*${RUNNER_TEST_SUFFIX} |sed 's#./##g'; }
_runner_testName() {
# shellcheck disable=SC1117
_runner_testName_=$(expr "${1:-}" : "\(.*\)${RUNNER_TEST_SUFFIX}")
if [ -n "${_runner_testName_}" ]; then
echo "${_runner_testName_}"
else
echo 'unknown'
fi
unset _runner_testName_
}
main() {
# Find and load versions library.
for _runner_dir_ in . ${LIB_DIR:-lib}; do
if [ -r "${_runner_dir_}/versions" ]; then
_runner_lib_dir_="${_runner_dir_}"
break
fi
done
[ -n "${_runner_lib_dir_}" ] || runner_fatal 'Unable to find versions library.'
. "${_runner_lib_dir_}/versions" || runner_fatal 'Unable to load versions library.'
unset _runner_dir_ _runner_lib_dir_
# Process command line flags.
env=''
while getopts 'e:hs:t:' opt; do
case ${opt} in
e) # set an environment variable
key=$(expr "${OPTARG}" : '\([^=]*\)=')
val=$(expr "${OPTARG}" : '[^=]*=\(.*\)')
# shellcheck disable=SC2166
if [ -z "${key}" -o -z "${val}" ]; then
runner_usage
exit 1
fi
eval "${key}='${val}'"
eval "export ${key}"
env="${env:+${env} }${key}"
;;
h) runner_usage; exit 0 ;; # help output
s) shells=${OPTARG} ;; # list of shells to run
t) tests=${OPTARG} ;; # list of tests to run
*) runner_usage; exit 1 ;;
esac
done
shift "$(expr ${OPTIND} - 1)"
# Fill shells and/or tests.
shells=${shells:-${RUNNER_SHELLS}}
tests=${tests:-$(_runner_tests)}
# Error checking.
if [ -z "${tests}" ]; then
runner_error 'no tests found to run; exiting'
exit 1
fi
cat <<EOF
#------------------------------------------------------------------------------
# System data.
#
$ uname -mprsv
$(uname -mprsv)
OS Name: $(versions_osName)
OS Version: $(versions_osVersion)
### Test run info.
shells: ${shells}
tests: ${tests}
EOF
for key in ${env}; do
eval "echo \"${key}=\$${key}\""
done
# Run tests.
for shell in ${shells}; do
echo
cat <<EOF
#------------------------------------------------------------------------------
# Running the test suite with ${shell}.
#
EOF
# Check for existence of shell.
shell_bin=${shell}
shell_name=''
shell_present=${FALSE}
case ${shell} in
ash)
shell_bin=$(which busybox)
[ $? -eq "${TRUE}" ] && shell_present="${TRUE}"
shell_bin="${shell_bin} ash"
shell_name=${shell}
;;
*)
[ -x "${shell_bin}" ] && shell_present="${TRUE}"
shell_name=$(basename "${shell}")
;;
esac
if [ "${shell_present}" -eq "${FALSE}" ]; then
runner_warn "unable to run tests with the ${shell_name} shell"
continue
fi
shell_version=$(versions_shellVersion "${shell}")
echo "shell name: ${shell_name}"
echo "shell version: ${shell_version}"
# Execute the tests.
for t in ${tests}; do
echo
echo "--- Executing the '$(_runner_testName "${t}'")' test suite. ---"
# ${shell_bin} needs word splitting.
# shellcheck disable=SC2086
( exec ${shell_bin} "./${t}" 2>&1; )
done
done
}
# Execute main() if this is run in standalone mode (i.e. not from a unit test).
[ -z "${SHUNIT_VERSION}" ] && main "$@"

40
test/tup_test.sh Executable file
View file

@ -0,0 +1,40 @@
#! /bin/bash
testTupIfEmpty() {
assertEquals '()' $(tup '')
}
testTupIfOneElement() {
assertEquals '(1)' $(tup 1)
assertEquals '(")' $(tup '"')
assertEquals "(')" $(tup "'")
assertEquals "(,)" $(tup ",")
assertEquals "(,,)" $(tup ",,")
assertEquals "(()" $(tup "(")
assertEquals "())" $(tup ")")
}
testTupHappyPath() {
assertEquals '(1,2,3,4,5)' $(tup 1 2 3 4 5)
assertEquals '(a-1,b-2,c-3)' $(tup 'a-1' 'b-2' 'c-3')
assertEquals '(a b,c d e,f)' "$(tup 'a b' 'c d e' 'f')"
}
testTupxIfZeroIndex() {
assertEquals '' "$(tup 1 3 | tupx 0 2>/dev/null)"
}
testTupl() {
assertEquals '4' "$(tup 4 5 | tupl)"
assertEquals '4' "$(tup 4 5 6 | tupl)"
assertEquals '6' "$(tup 6 | tupl)"
assertEquals 'foo bar' "$(tup 'foo bar' 1 'one' 2 | tupl)"
}
testTupr() {
assertEquals '5' "$(tup 4 5 | tupr)"
assertEquals '5' "$(tup 1 4 5 | tupr)"
assertEquals '5' "$(tup 5 | tupr)"
}
. ./shunit2-init.sh

21
test/unlist_test.sh Executable file
View file

@ -0,0 +1,21 @@
#! /bin/bash
testUnlistFromList() {
list=$(cat <<EOF
1
2
6
EOF
)
assertEquals "1 2 6" "$(echo $list | unlist)"
}
testUnlistFromEmptyList() {
assertEquals "" "$(echo | unlist)"
}
testUnlistFromOneElementList() {
assertEquals "1" "$(echo 1 | unlist)"
}
. ./shunit2-init.sh