├── .version ├── test ├── shunit2-init.sh ├── last_test.sh ├── tail_test.sh ├── append_test.sh ├── head_test.sh ├── prepend_test.sh ├── unlist_test.sh ├── list_test.sh ├── try_test.sh ├── drop_test.sh ├── take_test.sh ├── catch_test.sh ├── lib │ ├── shlib │ ├── versions │ └── shflags ├── map_test.sh ├── lambda_test.sh ├── predicates_test.sh ├── maybe_test.sh ├── tup_test.sh ├── test_runner └── shunit2 ├── LICENSE ├── .gitignore ├── examples └── example.sh ├── src └── fun.sh └── README.md /.version: -------------------------------------------------------------------------------- 1 | 2.4 2 | -------------------------------------------------------------------------------- /test/shunit2-init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | oneTimeSetUp() { 4 | . ../src/fun.sh 5 | } 6 | 7 | # Load shUnit2. 8 | . ./shunit2 -------------------------------------------------------------------------------- /test/last_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testLastFromList() { 4 | assertEquals 10 $(list {1..10} | last) 5 | assertEquals 7 $(list 5 6 7 | last) 6 | } 7 | 8 | testLastFromOneElementList() { 9 | assertEquals 1 $(list 1 | last) 10 | } 11 | 12 | testLastFromEmptyList() { 13 | assertEquals "" "$(list | last)" 14 | } 15 | 16 | . ./shunit2-init.sh -------------------------------------------------------------------------------- /test/tail_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testLTailFrom10() { 4 | assertEquals "2 3 4 5 6 7 8 9 10" "$(list {1..10} | ltail | unlist)" 5 | } 6 | 7 | testLTailFromOneElementList() { 8 | assertEquals "" "$(list 1 | ltail)" 9 | } 10 | 11 | testLTailFromEmptyList() { 12 | assertEquals "" "$(list | ltail)" 13 | } 14 | 15 | . ./shunit2-init.sh 16 | -------------------------------------------------------------------------------- /test/append_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testAppendToEmptyList() { 4 | assertEquals 4 "$(list | append 4)" 5 | } 6 | 7 | testAppendToOneElementList() { 8 | assertEquals "1 4" "$(list 1 | append 4 | unlist)" 9 | } 10 | 11 | testAppendToList() { 12 | assertEquals "1 2 3 4 5 4" "$(list 1 2 3 4 5 | append 4 | unlist)" 13 | } 14 | 15 | . ./shunit2-init.sh -------------------------------------------------------------------------------- /test/head_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testLHeadFromList() { 4 | assertEquals 1 $(list {1..10} | lhead) 5 | assertEquals 5 $(list 5 6 7 | lhead) 6 | } 7 | 8 | testLHeadFromOneElementList() { 9 | assertEquals 1 $(list 1 | lhead) 10 | } 11 | 12 | testLHeadFromEmptyList() { 13 | assertEquals "" "$(list | lhead)" 14 | } 15 | 16 | . ./shunit2-init.sh 17 | -------------------------------------------------------------------------------- /test/prepend_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testPrependToEmptyList() { 4 | assertEquals 4 "$(list | prepend 4)" 5 | } 6 | 7 | testPrependToOneElementList() { 8 | assertEquals "4 1" "$(list 1 | prepend 4 | unlist)" 9 | } 10 | 11 | testPrependToList() { 12 | assertEquals "4 1 2 3 4 5" "$(list 1 2 3 4 5 | prepend 4 | unlist)" 13 | } 14 | 15 | . ./shunit2-init.sh -------------------------------------------------------------------------------- /test/unlist_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testUnlistFromList() { 4 | list=$(cat </dev/null 2>&1 \ 26 | || shlib_path_="${PWD}/${shlib_path_}" 27 | 28 | # clean up the path. if all seds supported true regular expressions, then 29 | # this is what it would be: 30 | shlib_old_=${shlib_path_} 31 | while true; do 32 | shlib_new_=`echo "${shlib_old_}" |sed 's/[^/]*\/\.\.\/*//;s/\/\.\//\//'` 33 | [ "${shlib_old_}" = "${shlib_new_}" ] && break 34 | shlib_old_=${shlib_new_} 35 | done 36 | echo "${shlib_new_}" 37 | 38 | unset shlib_path_ shlib_old_ shlib_new_ 39 | } 40 | -------------------------------------------------------------------------------- /test/map_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | testMapEmptyList() { 4 | assertEquals "" "$(list | map lambda x . 'echo $(($x + 1))')" 5 | } 6 | 7 | testMapEmptyList_ifNoArgumentsInLambda() { 8 | assertEquals "" "$(list | map lambda . 'echo 3')" 9 | } 10 | 11 | testMapOneElementList() { 12 | assertEquals "3" "$(list 2 | map lambda x . 'echo $(($x + 1))')" 13 | } 14 | 15 | testMapList() { 16 | assertEquals "2 3 4 5 6" "$(list {1..5} | map lambda x . 'echo $(($x + 1))' | unlist)" 17 | } 18 | 19 | testMapList_ifNoArgumentsInLambda() { 20 | assertEquals "9 9 9 9 9" "$(list {1..5} | map lambda . 'echo 9' | unlist)" 21 | } 22 | 23 | testMapList_ifManyArgumentsInLambda() { 24 | list {1..5} | map lambda x y . 'echo $(($x + $y))' 2> /dev/null \ 25 | && fail "There should be syntax error, because map is an one argument operation" 26 | } 27 | 28 | testFlatMap() { 29 | assertEquals "1 2 3 2 3 3" "$(list {1..3} | map lambda x . 'seq $x 3' | unlist)" 30 | assertEquals "d e h l l l o o r w" "$(list hello world | map lambda x . 'command fold -w 1 <<< $x' | sort | unlist)" 31 | } 32 | 33 | testMapNoLambdaSyntax() { 34 | assertEquals "1 2 3" "$(list 1 2 3 | map echo | unlist)" 35 | assertEquals "1 is a number 2 is a number 3 is a number" "$(list 1 2 3 | map 'echo $ is a number' | unlist)" 36 | } 37 | 38 | . ./shunit2-init.sh -------------------------------------------------------------------------------- /test/lambda_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testLambdaNoArguments_ifNoInput() { 4 | assertEquals 'Hi there' "$(echo | lambda . 'echo Hi there')" 5 | } 6 | 7 | testLambdaNoArguments_ifSomeInputArguments() { 8 | assertEquals 'Hi there' "$(echo 'xx\nyy\nzz' | lambda . 'echo Hi there')" 9 | } 10 | 11 | testLambdaOneArgument() { 12 | identity() { 13 | lambda x . '$x' 14 | } 15 | assertEquals "hi there !" "$(identity <<< 'echo hi there !')" 16 | assertEquals 3 $(lambda x . 'echo $(($x + 1))' <<< '2') 17 | assertEquals "hi there !" "$(λ x . 'echo $x' <<< 'hi there !')" 18 | } 19 | 20 | testLambdaSymbolTwoArguments() { 21 | assertEquals 3 $(echo -e '1\n2' | lambda x y . 'echo $(($x + $y))') 22 | assertEquals 5 $(echo -e '7\n2' | λ x y . 'echo $(($x - $y))') 23 | } 24 | 25 | testLambdaSymbolManyArguments() { 26 | assertEquals 5 $(echo -e '1\n2\n3\n4\n5' | lambda a b c d e . 'echo $(($a + $b + $c + $d - $e))') 27 | } 28 | 29 | testLambdaSymbolManyArguments_ifInsufficientNumberOfArgumentsInLambda() { 30 | assertEquals 6 $(echo -e '1\n2\n3\n4\n5' | lambda a b c . 'echo $(($a + $b + $c))') 31 | } 32 | 33 | testLambdaSymbolManyArguments_ifInsufficientNumberOfInputArguments() { 34 | echo -e '1\n2' | lambda a b c d e . 'echo $(($a + $b + $c + $d + $e))' 2> /dev/null \ 35 | && fail "There should be syntax error" 36 | } 37 | 38 | . ./shunit2-init.sh -------------------------------------------------------------------------------- /test/predicates_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testIsint() { 4 | assertEquals 'true' $(isint 1) 5 | assertEquals 'true' $(isint -1) 6 | assertEquals 'false' $(isint a) 7 | assertEquals 'false' $(isint "") 8 | assertEquals '1 2 3 4 5' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . 'isint $x' | unlist )" 9 | 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 )" 10 | 11 | assertEquals 'false' $(not isint 1) 12 | assertEquals 'true' $(not isint a) 13 | } 14 | 15 | testIsempty() { 16 | assertEquals 'true' $(isempty "") 17 | assertEquals 'false' $(isempty a) 18 | 19 | assertEquals 'true' $(not isempty a) 20 | assertEquals 'false' $(not isempty "") 21 | } 22 | 23 | testIsfile() { 24 | f=$(mktemp) 25 | 26 | assertEquals 'true' $(isfile $f) 27 | assertEquals 'false' $(isfile $f.xxx) 28 | assertEquals 'false' $(isfile "") 29 | assertEquals 'true' $(not isfile $f.xxx) 30 | 31 | assertEquals 'false' $(isnonzerofile $f) 32 | echo hello world >$f 33 | assertEquals 'true' $(isnonzerofile $f) 34 | 35 | assertEquals 'true' $(iswritable $f) 36 | chmod 400 $f 37 | assertEquals 'false' $(iswritable $f) 38 | 39 | assertEquals 'true' $(isreadable $f) 40 | chmod 200 $f 41 | assertEquals 'false' $(isreadable $f) 42 | 43 | chmod 600 $f 44 | rm $f 45 | } 46 | 47 | testIsdir() { 48 | assertEquals 'true' $(isdir .) 49 | assertEquals 'false' $(isdir sir_not_appearing_in_this_film) 50 | } 51 | 52 | . ./shunit2-init.sh 53 | -------------------------------------------------------------------------------- /test/maybe_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testMaybe() { 4 | assertEquals '(Just,1)' "$(maybe 1)" 5 | assertEquals '(Just,1)' "$(echo 1 | maybe)" 6 | assertEquals '(Nothing)' "$(maybe '')" 7 | assertEquals '(Nothing)' "$(maybe ' ')" 8 | assertEquals '(Nothing)' "$(maybe ' ' ' ' ' ')" 9 | assertEquals '(Nothing)' "$(echo | maybe)" 10 | assertEquals '(Just,1 2 3)' "$(maybe 1 2 3)" 11 | assertEquals '(Just,1 2 3)' "$(echo 1 2 3 | maybe)" 12 | } 13 | 14 | testMaybemap() { 15 | assertEquals '(Just,3)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))')" 16 | assertEquals '(Nothing)' "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))')" 17 | 18 | assertEquals '(Nothing)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo')" 19 | assertEquals '(Nothing)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo' | maybemap lambda a . 'echo $(( a + 1 ))')" 20 | } 21 | 22 | testMaybevalue() { 23 | assertEquals 3 "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue 0)" 24 | assertEquals 0 "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue 0)" 25 | assertEquals 'a b c' "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue a b c)" 26 | } 27 | 28 | 29 | . ./shunit2-init.sh 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | # Created by https://www.gitignore.io/api/intellij+iml 3 | 4 | ### Intellij+iml ### 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 7 | 8 | # User-specific stuff: 9 | .idea/**/workspace.xml 10 | .idea/**/tasks.xml 11 | .idea/dictionaries 12 | 13 | # Sensitive or high-churn files: 14 | .idea/**/dataSources/ 15 | .idea/**/dataSources.ids 16 | .idea/**/dataSources.xml 17 | .idea/**/dataSources.local.xml 18 | .idea/**/sqlDataSources.xml 19 | .idea/**/dynamic.xml 20 | .idea/**/uiDesigner.xml 21 | 22 | # Gradle: 23 | .idea/**/gradle.xml 24 | .idea/**/libraries 25 | 26 | # CMake 27 | cmake-build-debug/ 28 | 29 | # Mongo Explorer plugin: 30 | .idea/**/mongoSettings.xml 31 | 32 | ## File-based project format: 33 | *.iws 34 | 35 | ## Plugin-specific files: 36 | 37 | # IntelliJ 38 | /out/ 39 | 40 | # mpeltonen/sbt-idea plugin 41 | .idea_modules/ 42 | 43 | # JIRA plugin 44 | atlassian-ide-plugin.xml 45 | 46 | # Cursive Clojure plugin 47 | .idea/replstate.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | fabric.properties 54 | 55 | ### Intellij+iml Patch ### 56 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 57 | 58 | *.iml 59 | modules.xml 60 | .idea/misc.xml 61 | *.ipr 62 | 63 | # End of https://www.gitignore.io/api/intellij+iml 64 | -------------------------------------------------------------------------------- /test/tup_test.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | testTupIfEmpty() { 4 | assertEquals '()' $(tup '') 5 | } 6 | 7 | testTupIfOneElement() { 8 | assertEquals '(1)' $(tup 1) 9 | assertEquals '(")' $(tup '"') 10 | assertEquals "(')" $(tup "'") 11 | assertEquals "(u002c)" $(tup ",") 12 | assertEquals "(u002cu002c)" $(tup ",,") 13 | assertEquals "(()" $(tup "(") 14 | assertEquals "())" $(tup ")") 15 | } 16 | 17 | testTupHappyPath() { 18 | assertEquals '(1,2,3,4,5)' $(tup 1 2 3 4 5) 19 | assertEquals '(a-1,b-2,c-3)' $(tup 'a-1' 'b-2' 'c-3') 20 | assertEquals '(a b,c d e,f)' "$(tup 'a b' 'c d e' 'f')" 21 | } 22 | 23 | testTupxHappyPath() { 24 | assertEquals '4' $(tup 4 5 1 4 | tupx 1) 25 | assertEquals '5' $(tup 4 5 1 4 | tupx 2) 26 | assertEquals '1' $(tup 4 5 1 4 | tupx 3) 27 | assertEquals '4' $(tup 4 5 1 4 | tupx 4) 28 | 29 | } 30 | 31 | testTupxIfEmpty() { 32 | assertEquals '' "$(tup '' | tupx 1)" 33 | assertEquals '' "$(tup '' | tupx 5)" 34 | } 35 | 36 | testTupxIfZeroIndex() { 37 | assertEquals '' "$(tup 1 3 | tupx 0 2>/dev/null)" 38 | } 39 | 40 | testTupxIfSpecialChars() { 41 | assertEquals ',' "$(tup ',' | tupx 1)" 42 | assertEquals ',,' "$(tup ',,' | tupx 1)" 43 | assertEquals '(' "$(tup '(' | tupx 1)" 44 | assertEquals ')' "$(tup ')' | tupx 1)" 45 | assertEquals '()' "$(tup '()' | tupx 1)" 46 | assertEquals '(' "$(tup '(' ')' | tupx 1)" 47 | assertEquals '(' "$(tup '(' '(' | tupx 1)" 48 | assertEquals ')' "$(tup ')' ')' | tupx 1)" 49 | assertEquals ',' "$(tup 'u002c' | tupx 1)" 50 | assertEquals ',,' "$(tup 'u002cu002c' | tupx 1)" 51 | } 52 | 53 | testTupxRange() { 54 | assertEquals '4 5' "$(tup 4 5 1 4 | tupx 1-2 | unlist)" 55 | assertEquals '4 4' "$(tup 4 5 1 4 | tupx 1,4 | unlist)" 56 | assertEquals '4 5 4' "$(tup 4 5 1 4 | tupx 1,2,4 | unlist)" 57 | } 58 | 59 | testTupl() { 60 | assertEquals '4' "$(tup 4 5 | tupl)" 61 | assertEquals '4' "$(tup 4 5 6 | tupl)" 62 | assertEquals '6' "$(tup 6 | tupl)" 63 | assertEquals 'foo bar' "$(tup 'foo bar' 1 'one' 2 | tupl)" 64 | } 65 | 66 | testTupr() { 67 | assertEquals '5' "$(tup 4 5 | tupr)" 68 | assertEquals '5' "$(tup 1 4 5 | tupr)" 69 | assertEquals '5' "$(tup 5 | tupr)" 70 | } 71 | 72 | testNTup() { 73 | assertEquals '(KFlRbz0sWWdvPSkK,Ywo=)' "$(ntup $(ntup a b) c)" 74 | assertEquals '(YQo=,Ygo=)' "$(ntupl '(KFlRbz0sWWdvPSkK,Ywo=)')" 75 | assertEquals 'a' "$(ntupl '(YQo=,Ygo=)')" 76 | assertEquals 'b' "$(ntupr '(YQo=,Ygo=)')" 77 | assertEquals 'c' "$(ntupr '(KFlRbz0sWWdvPSkK,Ywo=)')" 78 | assertEquals 'a' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 1)" 79 | assertEquals 'b' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 2)" 80 | assertEquals 'c' "$(ntup $(ntup a b) c | ntupx 2)" 81 | assertEquals 'a b' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 1,2 | unlist)" 82 | } 83 | 84 | . ./shunit2-init.sh 85 | -------------------------------------------------------------------------------- /examples/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source ../src/fun.sh 3 | 4 | seq 1 4 | sum 5 | seq 1 4 | product 6 | factorial 4 7 | seq 1 4 | scanl lambda a b . 'echo $(plus $a $b)' 8 | echo map mul 9 | seq 1 4 | map lambda a . 'echo $(mul $a 2)' 10 | echo map sub 11 | seq 1 4 | map lambda a . 'echo $(sub $a 2)' 12 | echo map plsu 13 | seq 1 4 | map lambda a . 'echo $(plus $a 2)' 14 | echo map div 15 | seq 1 4 | map lambda a . 'echo $(div $a 2)' 16 | echo map mod 17 | seq 1 4 | map lambda a . 'echo $(mod $a 2)' 18 | echo 'list & head' 19 | list 1 2 3 4 5 | lhead 20 | list {1..2} | append {3..4} | prepend {99..102} 21 | list {1..2} | unlist 22 | list {1..10} | lhead 23 | list {1..10} | drop 7 24 | list {1..10} | take 3 25 | list {1..10} | last 26 | list {1..10} | map λ a . 'echo $(mul $a 2)' 27 | 28 | id() { 29 | λ x . '$x' 30 | } 31 | 32 | id <<< 'echo :)' 33 | 34 | foobar() { 35 | product | λ l . 'list {1..$l}' | sum | md5sum 36 | } 37 | 38 | list {1,2,3} | foobar 39 | 40 | echo -n abcdefg | revers_str # gfedcba 41 | echo -n abcdefg | splitc | join , '[' ']' # [a,b,c,d,e,f,g] 42 | echo -n abcdefg | splitc | revers | join , '[' ']' # [g,f,e,d,c,b,a] 43 | 44 | echo -n ' abcdefg' | splitc | foldr lambda a b . 'echo $a$b' # gfedcba 45 | 46 | echo 'ls' | try λ cmd status ret . 'echo $cmd [$status]; echo $ret' 47 | 48 | list {1..10} | filter lambda a . '[[ $(mod $a 2) -eq 0 ]] && ret true || ret false' | join , '[' ']' # [2,4,6,8,10] 49 | 50 | function add() { 51 | expr $1 + $2 52 | } 53 | 54 | 55 | curry add3 add 3 56 | add3 9 57 | 58 | list a b c d | foldl lambda acc el . 'echo -n $acc-$el' 59 | list '' a b c d | foldr lambda acc el . 'if [[ ! -z $acc ]]; then echo -n $acc-$el; else echo -n $el; fi' 60 | 61 | seq 1 4 | foldl lambda acc el . 'echo $(($acc + $el))' 62 | 63 | #1 - 2 - 3 - 4 64 | seq 1 4 | foldl lambda acc el . 'echo $(($acc - $el))' 65 | #1 - 4 - 3 - 2 66 | seq 1 4 | foldr lambda acc el . 'echo $(($acc - $el))' 67 | 68 | #1 + (1 + 1) * 2 + (4 + 1) * 3 + (15 + 1) * 4 = 64 69 | 70 | seq 1 4 | foldl lambda acc el . 'echo $(mul $(($acc + 1)) $el)' 71 | 72 | #1 + (1 + 1) * 4 + (8 + 1) * 3 + (27 + 1) * 2 = 56 73 | seq 1 4 | foldr lambda acc el . 'echo $(mul $(($acc + 1)) $el)' 74 | 75 | tup a 1 76 | tupl $(tup a 1) 77 | tupr $(tup a 1) 78 | tup a 1 | tupl 79 | tup a 1 | tupr 80 | 81 | seq 1 10 | buff lambda a b . 'echo $(($a + $b))' 82 | echo 'XX' 83 | seq 1 10 | buff lambda a b c d e . 'echo $(($a + $b + $c + $d + $e))' 84 | 85 | list a b c d e f | lzip $(seq 1 10) 86 | 87 | echo 88 | list a b c d e f | lzip $(seq 1 10) | last | tupr 89 | 90 | arg='[key1=value1,key2=value2,key3=value3]' 91 | get() { 92 | local pidx=$1 93 | local idx=$2 94 | local arg=$3 95 | echo $arg | tr -d '[]' | cut -d',' -f$idx | cut -d'=' -f$pidx 96 | } 97 | 98 | curry get_key get 1 99 | curry get_value get 2 100 | 101 | get_key 1 $arg 102 | get_value 1 $arg 103 | 104 | seq 1 3 | map lambda a . 'tup $(get_key $a $arg) $(get_value $a $arg)' 105 | 106 | echo 'ls /home' | try λ cmd status ret . 'echo $cmd [$status]; echo $ret' 107 | echo '/home' | try λ cmd status ret . 'echo $cmd [$status]; echo $ret' 108 | 109 | seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' 110 | seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' | last 111 | 112 | seq 2 3 | map lambda a . 'seq 1 $a' | join , [ ] 113 | list a b c | map lambda a . 'echo $a; echo $a | tr a-z A-z' | join , [ ] 114 | 115 | echo 0 | cat - <(curl -s https://raw.githubusercontent.com/ssledz/bash-fun/v1.1.1/src/fun.sh) | \ 116 | map lambda a . 'list $a' | foldl lambda acc el . 'echo $(($acc + 1))' 117 | 118 | echo 0 | cat - <(curl -s curl -s https://raw.githubusercontent.com/ssledz/bash-fun/v1.1.1/src/fun.sh) \ 119 | | foldl lambda acc el . 'echo $(($acc + 1))' 120 | 121 | 122 | factorial() { 123 | fact_iter() { 124 | local product=$1 125 | local counter=$2 126 | local max_count=$3 127 | if [[ $counter -gt $max_count ]]; then 128 | echo $product 129 | else 130 | fact_iter $(echo $counter\*$product | bc) $(($counter + 1)) $max_count 131 | fi 132 | } 133 | 134 | fact_iter 1 1 $1 135 | } 136 | 137 | factorial_trampoline() { 138 | fact_iter() { 139 | local product=$1 140 | local counter=$2 141 | local max_count=$3 142 | if [[ $counter -gt $max_count ]]; then 143 | res $product 144 | else 145 | call fact_iter $(echo $counter\*$product | bc) $(($counter + 1)) $max_count 146 | fi 147 | } 148 | 149 | with_trampoline fact_iter 1 1 $1 150 | } 151 | 152 | echo Factorial test 153 | 154 | time factorial 30 155 | time factorial_trampoline 30 156 | 157 | # would be error 158 | #time factorial 60 159 | time factorial_trampoline 60 -------------------------------------------------------------------------------- /test/test_runner: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Unit test suite runner. 5 | # 6 | # Copyright 2008-2017 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 license. 8 | # 9 | # Author: kate.ward@forestent.com (Kate Ward) 10 | # https://github.com/kward/shlib 11 | # 12 | # This script runs all the unit tests that can be found, and generates a nice 13 | # report of the tests. 14 | # 15 | ### ShellCheck (http://www.shellcheck.net/) 16 | # Disable source following. 17 | # shellcheck disable=SC1090,SC1091 18 | # expr may be antiquated, but it is the only solution in some cases. 19 | # shellcheck disable=SC2003 20 | 21 | # Return if test_runner already loaded. 22 | [ -z "${RUNNER_LOADED:-}" ] || return 0 23 | RUNNER_LOADED=0 24 | 25 | RUNNER_ARGV0=$(basename "$0") 26 | RUNNER_SHELLS='/bin/bash' 27 | RUNNER_TEST_SUFFIX='_test.sh' 28 | 29 | runner_warn() { echo "runner:WARN $*" >&2; } 30 | runner_error() { echo "runner:ERROR $*" >&2; } 31 | runner_fatal() { echo "runner:FATAL $*" >&2; exit 1; } 32 | 33 | runner_usage() { 34 | echo "usage: ${RUNNER_ARGV0} [-e key=val ...] [-s shell(s)] [-t test(s)]" 35 | } 36 | 37 | _runner_tests() { echo ./*${RUNNER_TEST_SUFFIX} |sed 's#./##g'; } 38 | _runner_testName() { 39 | # shellcheck disable=SC1117 40 | _runner_testName_=$(expr "${1:-}" : "\(.*\)${RUNNER_TEST_SUFFIX}") 41 | if [ -n "${_runner_testName_}" ]; then 42 | echo "${_runner_testName_}" 43 | else 44 | echo 'unknown' 45 | fi 46 | unset _runner_testName_ 47 | } 48 | 49 | main() { 50 | # Find and load versions library. 51 | for _runner_dir_ in . ${LIB_DIR:-lib}; do 52 | if [ -r "${_runner_dir_}/versions" ]; then 53 | _runner_lib_dir_="${_runner_dir_}" 54 | break 55 | fi 56 | done 57 | [ -n "${_runner_lib_dir_}" ] || runner_fatal 'Unable to find versions library.' 58 | . "${_runner_lib_dir_}/versions" || runner_fatal 'Unable to load versions library.' 59 | unset _runner_dir_ _runner_lib_dir_ 60 | 61 | # Process command line flags. 62 | env='' 63 | while getopts 'e:hs:t:' opt; do 64 | case ${opt} in 65 | e) # set an environment variable 66 | key=$(expr "${OPTARG}" : '\([^=]*\)=') 67 | val=$(expr "${OPTARG}" : '[^=]*=\(.*\)') 68 | # shellcheck disable=SC2166 69 | if [ -z "${key}" -o -z "${val}" ]; then 70 | runner_usage 71 | exit 1 72 | fi 73 | eval "${key}='${val}'" 74 | eval "export ${key}" 75 | env="${env:+${env} }${key}" 76 | ;; 77 | h) runner_usage; exit 0 ;; # help output 78 | s) shells=${OPTARG} ;; # list of shells to run 79 | t) tests=${OPTARG} ;; # list of tests to run 80 | *) runner_usage; exit 1 ;; 81 | esac 82 | done 83 | shift "$(expr ${OPTIND} - 1)" 84 | 85 | # Fill shells and/or tests. 86 | shells=${shells:-${RUNNER_SHELLS}} 87 | tests=${tests:-$(_runner_tests)} 88 | 89 | # Error checking. 90 | if [ -z "${tests}" ]; then 91 | runner_error 'no tests found to run; exiting' 92 | exit 1 93 | fi 94 | 95 | cat <&1; ) 158 | done 159 | done 160 | } 161 | 162 | # Execute main() if this is run in standalone mode (i.e. not from a unit test). 163 | [ -z "${SHUNIT_VERSION}" ] && main "$@" 164 | -------------------------------------------------------------------------------- /src/fun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | drop() { 4 | command tail -n +$(($1 + 1)) 5 | } 6 | 7 | take() { 8 | command head -n ${1} 9 | } 10 | 11 | ltail() { 12 | drop 1 13 | } 14 | 15 | lhead() { 16 | take 1 17 | } 18 | 19 | last() { 20 | command tail -n 1 21 | } 22 | 23 | list() { 24 | for i in "$@"; do 25 | echo "$i" 26 | done 27 | } 28 | 29 | unlist() { 30 | cat - | xargs 31 | } 32 | 33 | append() { 34 | cat - 35 | list "$@" 36 | } 37 | 38 | prepend() { 39 | list "$@" 40 | cat - 41 | } 42 | 43 | 44 | lambda() { 45 | 46 | lam() { 47 | local arg 48 | while [[ $# -gt 0 ]]; do 49 | arg="$1" 50 | shift 51 | if [[ $arg = '.' ]]; then 52 | echo "$@" 53 | return 54 | else 55 | echo "read $arg;" 56 | fi 57 | done 58 | } 59 | 60 | eval $(lam "$@") 61 | 62 | } 63 | 64 | λ() { 65 | lambda "$@" 66 | } 67 | 68 | map() { 69 | if [[ $1 != "λ" ]] && [[ $1 != "lambda" ]]; then 70 | 71 | local has_dollar=$(list $@ | grep '\$' | wc -l) 72 | 73 | if [[ $has_dollar -ne 0 ]]; then 74 | args=$(echo $@ | sed -e 's/\$/\$a/g') 75 | map λ a . $args 76 | else 77 | map λ a . "$@"' $a' 78 | fi 79 | else 80 | local x 81 | while read x; do 82 | echo "$x" | "$@" 83 | done 84 | fi 85 | } 86 | 87 | foldl() { 88 | local f="$@" 89 | local acc 90 | read acc 91 | while read elem; do 92 | acc="$({ echo $acc; echo $elem; } | $f )" 93 | done 94 | echo "$acc" 95 | } 96 | 97 | foldr() { 98 | local f="$@" 99 | local acc 100 | local zero 101 | read zero 102 | foldrr() { 103 | local elem 104 | 105 | if read elem; then 106 | acc=$(foldrr) 107 | # [[ -z $acc ]] && echo $elem && return 108 | else 109 | echo $zero && return 110 | fi 111 | 112 | acc="$({ echo $acc; echo $elem; } | $f )" 113 | echo "$acc" 114 | } 115 | 116 | foldrr 117 | } 118 | 119 | scanl() { 120 | local f="$@" 121 | local acc 122 | read acc 123 | echo $acc 124 | while read elem; do 125 | acc="$({ echo $acc; echo $elem; } | $f )" 126 | echo "$acc" 127 | done 128 | } 129 | 130 | mul() { 131 | ( set -f; echo $(($1 * $2)) ) 132 | } 133 | 134 | plus() { 135 | echo $(($1 + $2)) 136 | } 137 | 138 | sub() { 139 | echo $(($1 - $2)) 140 | } 141 | 142 | div() { 143 | echo $(($1 / $2)) 144 | } 145 | 146 | mod() { 147 | echo $(($1 % $2)) 148 | } 149 | 150 | 151 | sum() { 152 | foldl lambda a b . 'echo $(($a + $b))' 153 | } 154 | 155 | product() { 156 | foldl lambda a b . 'echo $(mul $a $b)' 157 | } 158 | 159 | factorial() { 160 | seq 1 $1 | product 161 | } 162 | 163 | splitc() { 164 | cat - | sed 's/./&\n/g' 165 | } 166 | 167 | join() { 168 | local delim=$1 169 | local pref=$2 170 | local suff=$3 171 | echo $pref$(cat - | foldl lambda a b . 'echo $a$delim$b')$suff 172 | } 173 | 174 | revers() { 175 | foldl lambda a b . 'append $b $a' 176 | } 177 | 178 | revers_str() { 179 | cat - | splitc | revers | join 180 | } 181 | 182 | catch() { 183 | local f="$@" 184 | local cmd=$(cat -) 185 | local val=$(2>&1 eval "$cmd"; echo $?) 186 | local cnt=$(list $val | wc -l) 187 | local status=$(list $val | last) 188 | $f < <(list "$cmd" $status $(list $val | take $((cnt - 1)) | unlist | tup)) 189 | } 190 | 191 | try() { 192 | local f="$@" 193 | catch lambda cmd status val . '[[ $status -eq 0 ]] && tupx 1- $val | unlist || { '"$f"' < <(list $status); }' 194 | } 195 | 196 | ret() { 197 | echo $@ 198 | } 199 | 200 | filter() { 201 | local x 202 | while read x; do 203 | ret=$(echo "$x" | "$@") 204 | $ret && echo $x 205 | done 206 | } 207 | 208 | pass() { 209 | echo > /dev/null 210 | } 211 | 212 | dropw() { 213 | local x 214 | while read x && $(echo "$x" | "$@"); do 215 | pass 216 | done 217 | [[ ! -z $x ]] && { echo $x; cat -; } 218 | } 219 | 220 | peek() { 221 | local x 222 | while read x; do 223 | ([ $# -eq 0 ] && 1>&2 echo $x || 1>&2 "$@" < <(echo $x)) 224 | echo $x 225 | done 226 | } 227 | 228 | stripl() { 229 | local arg=$1 230 | cat - | map lambda l . 'ret ${l##'$arg'}' 231 | } 232 | 233 | stripr() { 234 | local arg=$1 235 | cat - | map lambda l . 'ret ${l%%'$arg'}' 236 | } 237 | 238 | strip() { 239 | local arg=$1 240 | cat - | stripl "$arg" | stripr "$arg" 241 | } 242 | 243 | buff() { 244 | local cnt=-1 245 | for x in $@; do 246 | [[ $x = '.' ]] && break 247 | cnt=$(plus $cnt 1) 248 | done 249 | local args='' 250 | local i=$cnt 251 | while read arg; do 252 | [[ $i -eq 0 ]] && list $args | "$@" && i=$cnt && args='' 253 | args="$args $arg" 254 | i=$(sub $i 1) 255 | done 256 | [[ ! -z $args ]] && list $args | "$@" 257 | } 258 | 259 | tup() { 260 | if [[ $# -eq 0 ]]; then 261 | local arg 262 | read arg 263 | tup $arg 264 | else 265 | list "$@" | map lambda x . 'echo ${x//,/u002c}' | join , '(' ')' 266 | fi 267 | } 268 | 269 | tupx() { 270 | if [[ $# -eq 1 ]]; then 271 | local arg 272 | read arg 273 | tupx "$1" "$arg" 274 | else 275 | local n=$1 276 | shift 277 | echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr ',' '\n' | map lambda x . 'echo ${x//u002c/,}' 278 | fi 279 | } 280 | 281 | tupl() { 282 | tupx 1 "$@" 283 | } 284 | 285 | tupr() { 286 | tupx 1- "$@" | last 287 | } 288 | 289 | ntup() { 290 | if [[ $# -eq 0 ]]; then 291 | local arg 292 | read arg 293 | ntup $arg 294 | else 295 | list "$@" | map lambda x . 'echo "$x" | base64 --wrap=0 ; echo' | join , '(' ')' 296 | fi 297 | } 298 | 299 | ntupx() { 300 | if [[ $# -eq 1 ]]; then 301 | local arg 302 | read arg 303 | ntupx "$1" "$arg" 304 | else 305 | local n=$1 306 | shift 307 | echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr , '\n' | map lambda x . 'echo "$x" | base64 -d' 308 | fi 309 | } 310 | 311 | ntupl() { 312 | ntupx 1 "$@" 313 | } 314 | 315 | ntupr() { 316 | ntupx 1- "$@" | last 317 | } 318 | 319 | lzip() { 320 | local list=$* 321 | cat - | while read x; do 322 | y=$(list $list | take 1) 323 | tup $x $y 324 | list=$(list $list | drop 1) 325 | done 326 | } 327 | 328 | curry() { 329 | exportfun=$1; shift 330 | fun=$1; shift 331 | params=$* 332 | cmd=$"function $exportfun() { 333 | more_params=\$*; 334 | $fun $params \$more_params; 335 | }" 336 | eval $cmd 337 | } 338 | 339 | with_trampoline() { 340 | local f=$1; shift 341 | local args=$@ 342 | while [[ $f != 'None' ]]; do 343 | ret=$($f $args) 344 | # echo $ret 345 | f=$(tupl $ret) 346 | args=$(echo $ret | tupx 2- | tr ',' ' ') 347 | done 348 | echo $args 349 | } 350 | 351 | res() { 352 | local value=$1 353 | tup "None" $value 354 | } 355 | 356 | call() { 357 | local f=$1; shift 358 | local args=$@ 359 | tup $f $args 360 | } 361 | 362 | maybe() { 363 | if [[ $# -eq 0 ]]; then 364 | local arg 365 | read arg 366 | maybe "$arg" 367 | else 368 | local x="$*" 369 | local value=$(echo $x | strip) 370 | if [[ ${#value} -eq 0 ]]; then 371 | tup Nothing 372 | else 373 | tup Just "$value" 374 | fi 375 | fi 376 | } 377 | 378 | maybemap() { 379 | local x 380 | read x 381 | if [[ $(tupl $x) = "Nothing" ]]; then 382 | echo $x 383 | else 384 | local y=$(tupr "$x") 385 | local r=$(echo "$y" | map "$@") 386 | maybe "$r" 387 | fi 388 | } 389 | 390 | maybevalue() { 391 | local default="$*" 392 | local x 393 | read x 394 | if [[ $(tupl $x) = "Nothing" ]]; then 395 | echo "$default" 396 | else 397 | echo $(tupr $x) 398 | fi 399 | } 400 | 401 | 402 | # commonly used predicates for filter 403 | # e.g. list 1 a 2 b 3 c | filter lambda x . 'isint $x' 404 | 405 | # inverse another test, e.g. "not isint $x" 406 | not() { 407 | local r=$("$@" 2>/dev/null) 408 | $r && ret false || ret true 409 | } 410 | 411 | isint() { 412 | [ "$1" -eq "$1" ] 2>/dev/null && ret true || ret false 413 | } 414 | 415 | isempty() { 416 | [ -z "$1" ] && ret true || ret false 417 | } 418 | 419 | isfile() { 420 | [ -f "$1" ] && ret true || ret false 421 | } 422 | 423 | isnonzerofile() { 424 | [ -s "$1" ] && ret true || ret false 425 | } 426 | 427 | isreadable() { 428 | [ -r "$1" ] && ret true || ret false 429 | } 430 | 431 | iswritable() { 432 | [ -w "$1" ] && ret true || ret false 433 | } 434 | 435 | isdir() { 436 | [ -d "$1" ] && ret true || ret false 437 | } 438 | -------------------------------------------------------------------------------- /test/lib/versions: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Versions determines the versions of all installed shells. 5 | # 6 | # Copyright 2008-2017 Kate Ward. All Rights Reserved. 7 | # Released under the Apache 2.0 License. 8 | # 9 | # Author: kate.ward@forestent.com (Kate Ward) 10 | # https://github.com/kward/shlib 11 | # 12 | # This library provides reusable functions that determine actual names and 13 | # versions of installed shells and the OS. The library can also be run as a 14 | # script if set executable. 15 | # 16 | # Disable checks that aren't fully portable (POSIX != portable). 17 | # shellcheck disable=SC2006 18 | 19 | ARGV0=`basename "$0"` 20 | LSB_RELEASE='/etc/lsb-release' 21 | VERSIONS_SHELLS="ash /bin/bash /bin/dash /bin/ksh /bin/pdksh /bin/sh /bin/zsh" 22 | 23 | true; TRUE=$? 24 | false; FALSE=$? 25 | ERROR=2 26 | 27 | UNAME_R=`uname -r` 28 | UNAME_S=`uname -s` 29 | 30 | __versions_haveStrings=${ERROR} 31 | 32 | versions_osName() { 33 | os_name_='unrecognized' 34 | os_system_=${UNAME_S} 35 | os_release_=${UNAME_R} 36 | case ${os_system_} in 37 | CYGWIN_NT-*) os_name_='Cygwin' ;; 38 | Darwin) 39 | os_name_=`/usr/bin/sw_vers -productName` 40 | os_version_=`versions_osVersion` 41 | case ${os_version_} in 42 | 10.4|10.4.[0-9]*) os_name_='Mac OS X Tiger' ;; 43 | 10.5|10.5.[0-9]*) os_name_='Mac OS X Leopard' ;; 44 | 10.6|10.6.[0-9]*) os_name_='Mac OS X Snow Leopard' ;; 45 | 10.7|10.7.[0-9]*) os_name_='Mac OS X Lion' ;; 46 | 10.8|10.8.[0-9]*) os_name_='Mac OS X Mountain Lion' ;; 47 | 10.9|10.9.[0-9]*) os_name_='Mac OS X Mavericks' ;; 48 | 10.10|10.10.[0-9]*) os_name_='Mac OS X Yosemite' ;; 49 | 10.11|10.11.[0-9]*) os_name_='Mac OS X El Capitan' ;; 50 | 10.12|10.12.[0-9]*) os_name_='macOS Sierra' ;; 51 | 10.13|10.13.[0-9]*) os_name_='macOS High Sierra' ;; 52 | *) os_name_='macOS' ;; 53 | esac 54 | ;; 55 | FreeBSD) os_name_='FreeBSD' ;; 56 | Linux) os_name_='Linux' ;; 57 | SunOS) 58 | if grep 'OpenSolaris' /etc/release >/dev/null; then 59 | os_name_='OpenSolaris' 60 | else 61 | os_name_='Solaris' 62 | fi 63 | ;; 64 | esac 65 | 66 | echo ${os_name_} 67 | unset os_name_ os_system_ os_release_ os_version_ 68 | } 69 | 70 | versions_osVersion() { 71 | os_version_='unrecognized' 72 | os_system_=${UNAME_S} 73 | os_release_=${UNAME_R} 74 | case ${os_system_} in 75 | CYGWIN_NT-*) 76 | os_version_=`expr "${os_release_}" : '\([0-9]*\.[0-9]\.[0-9]*\).*'` 77 | ;; 78 | Darwin) 79 | os_version_=`/usr/bin/sw_vers -productVersion` 80 | ;; 81 | FreeBSD) 82 | os_version_=`expr "${os_release_}" : '\([0-9]*\.[0-9]*\)-.*'` 83 | ;; 84 | Linux) 85 | if [ -r '/etc/os-release' ]; then 86 | os_version_=`awk -F= '$1~/PRETTY_NAME/{print $2}' /etc/os-release \ 87 | |sed 's/"//g'` 88 | elif [ -r '/etc/redhat-release' ]; then 89 | os_version_=`cat /etc/redhat-release` 90 | elif [ -r '/etc/SuSE-release' ]; then 91 | os_version_=`head -n 1 /etc/SuSE-release` 92 | elif [ -r "${LSB_RELEASE}" ]; then 93 | if grep -q 'DISTRIB_ID=Ubuntu' "${LSB_RELEASE}"; then 94 | # shellcheck disable=SC2002 95 | os_version_=`cat "${LSB_RELEASE}" \ 96 | |awk -F= '$1~/DISTRIB_DESCRIPTION/{print $2}' \ 97 | |sed 's/"//g;s/ /-/g'` 98 | fi 99 | fi 100 | ;; 101 | SunOS) 102 | if grep 'OpenSolaris' /etc/release >/dev/null; then 103 | os_version_=`grep 'OpenSolaris' /etc/release |awk '{print $2"("$3")"}'` 104 | else 105 | major_=`echo "${os_release_}" |sed 's/[0-9]*\.\([0-9]*\)/\1/'` 106 | minor_=`grep Solaris /etc/release |sed 's/[^u]*\(u[0-9]*\).*/\1/'` 107 | os_version_="${major_}${minor_}" 108 | fi 109 | ;; 110 | esac 111 | 112 | echo "${os_version_}" 113 | unset os_name_ os_release_ os_version_ major_ minor_ 114 | } 115 | 116 | versions_shellVersion() { 117 | shell_=$1 118 | 119 | shell_present_=${FALSE} 120 | case "${shell_}" in 121 | ash) 122 | [ -x '/bin/busybox' ] && shell_present_=${TRUE} 123 | ;; 124 | *) 125 | [ -x "${shell_}" ] && shell_present_=${TRUE} 126 | ;; 127 | esac 128 | if [ ${shell_present_} -eq ${FALSE} ]; then 129 | echo 'not installed' 130 | return ${FALSE} 131 | fi 132 | 133 | version_='' 134 | case ${shell_} in 135 | */sh) 136 | # TODO(kward): fix this 137 | ## this could be one of any number of shells. try until one fits. 138 | #version_=`versions_shell_bash ${shell_}` 139 | ## dash cannot be self determined yet 140 | #[ -z "${version_}" ] && version_=`versions_shell_ksh ${shell_}` 141 | ## pdksh is covered in versions_shell_ksh() 142 | #[ -z "${version_}" ] && version_=`versions_shell_zsh ${shell_}` 143 | ;; 144 | ash) version_=`versions_shell_ash "${shell_}"` ;; 145 | */bash) version_=`versions_shell_bash "${shell_}"` ;; 146 | */dash) 147 | # simply assuming Ubuntu Linux until somebody comes up with a better 148 | # test. the following test will return an empty string if dash is not 149 | # installed. 150 | version_=`versions_shell_dash` 151 | ;; 152 | */ksh) version_=`versions_shell_ksh "${shell_}"` ;; 153 | */pdksh) version_=`versions_shell_pdksh "${shell_}"` ;; 154 | */zsh) version_=`versions_shell_zsh "${shell_}"` ;; 155 | *) version_='invalid' 156 | esac 157 | 158 | echo "${version_:-unknown}" 159 | unset shell_ version_ 160 | } 161 | 162 | # The ash shell is included in BusyBox. 163 | versions_shell_ash() { 164 | busybox --help |head -1 |sed 's/BusyBox v\([0-9.]*\) .*/\1/' 165 | } 166 | 167 | versions_shell_bash() { 168 | $1 --version 2>&1 |grep 'GNU bash' |sed 's/.*version \([^ ]*\).*/\1/' 169 | } 170 | 171 | versions_shell_dash() { 172 | eval dpkg >/dev/null 2>&1 173 | [ $? -eq 127 ] && return # return if dpkg not found 174 | 175 | dpkg -l |grep ' dash ' |awk '{print $3}' 176 | } 177 | 178 | versions_shell_ksh() { 179 | versions_shell_=$1 180 | versions_version_='' 181 | 182 | # Try a few different ways to figure out the version. 183 | if versions_version_=`${versions_shell_} --version : 2>&1`; then 184 | versions_version_=`echo "${versions_version_}" \ 185 | |sed 's/.*\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\).*/\1/'` 186 | fi 187 | if [ -z "${versions_version_}" ]; then 188 | _versions_have_strings 189 | versions_version_=`strings "${versions_shell_}" 2>&1 \ 190 | |grep Version \ 191 | |sed 's/^.*Version \(.*\)$/\1/;s/ s+ \$$//;s/ /-/g'` 192 | fi 193 | if [ -z "${versions_version_}" ]; then 194 | versions_version_=`versions_shell_pdksh "${versions_shell_}"` 195 | fi 196 | 197 | echo "${versions_version_}" 198 | unset versions_shell_ versions_version_ 199 | } 200 | 201 | versions_shell_pdksh() { 202 | _versions_have_strings 203 | strings "$1" 2>&1 \ 204 | |grep 'PD KSH' \ 205 | |sed -e 's/.*PD KSH \(.*\)/\1/;s/ /-/g' 206 | } 207 | 208 | versions_shell_zsh() { 209 | versions_shell_=$1 210 | 211 | # Try a few different ways to figure out the version. 212 | # shellcheck disable=SC2016 213 | versions_version_=`echo 'echo ${ZSH_VERSION}' |${versions_shell_}` 214 | 215 | if [ -z "${versions_version_}" ]; then 216 | versions_version_=`${versions_shell_} --version 2>&1 |awk '{print $2}'` 217 | fi 218 | 219 | echo "${versions_version_}" 220 | unset versions_shell_ versions_version_ 221 | } 222 | 223 | # Determine if the 'strings' binary installed. 224 | _versions_have_strings() { 225 | [ ${__versions_haveStrings} -ne ${ERROR} ] && return 226 | if eval strings /dev/null >/dev/null 2>&1; then 227 | __versions_haveStrings=${TRUE} 228 | return 229 | fi 230 | 231 | echo 'WARN: strings not installed. try installing binutils?' >&2 232 | __versions_haveStrings=${FALSE} 233 | } 234 | 235 | versions_main() { 236 | # Treat unset variables as an error. 237 | set -u 238 | 239 | os_name=`versions_osName` 240 | os_version=`versions_osVersion` 241 | echo "os: ${os_name} version: ${os_version}" 242 | 243 | for shell in ${VERSIONS_SHELLS}; do 244 | shell_version=`versions_shellVersion "${shell}"` 245 | echo "shell: ${shell} version: ${shell_version}" 246 | done 247 | } 248 | 249 | if [ "${ARGV0}" = 'versions' ]; then 250 | versions_main "$@" 251 | fi 252 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | [Introduction to fun.sh library](http://ssledz.github.io/presentations/bash-fun.html#/) 4 | 5 | # Quick start 6 | 7 | ```bash 8 | #!/bin/bash 9 | . <(test -e fun.sh || curl -Ls https://raw.githubusercontent.com/ssledz/bash-fun/master/src/fun.sh > fun.sh; cat fun.sh) 10 | 11 | seq 1 4 | sum 12 | ``` 13 | 14 | # Functions overview 15 | ||||||| 16 | |------|------|------|------|------|------| 17 | |**append**|**buff**|**call**|**catch**|**curry**|**div**| 18 | |**drop**|**dropw**|**factorial**|**filter**|**foldl**|**foldr**| 19 | |**isint**|**isempty**|**isfile**|**isnonzerofile**|**isreadable**|**iswritable**| 20 | |**isdir**|**join**|**lambda**|**last**|**lhead**|**list**| 21 | |**ltail**|**lzip**|**map**|**maybe**|**maybemap**|**maybevalue**| 22 | |**mod**|**mul**|**not**|**ntup**|**ntupl**|**ntupr**| 23 | |**ntupx**|**peek**|**plus**|**prepend**|**product**|**ret**| 24 | |**res**|**revers**|**revers_str**|**scanl**|**splitc**|**strip**| 25 | |**stripl**|**stripr**|**sub**|**sum**|**take**|**try**| 26 | |**tup**|**tupl**|**tupr**|**tupx**|**unlist**|**λ**| 27 | |**with_trampoline**| 28 | 29 | ## *list/unlist* 30 | 31 | ```bash 32 | $ list 1 2 3 33 | 1 34 | 2 35 | 3 36 | 37 | $ list 1 2 3 4 5 | unlist 38 | 1 2 3 4 5 39 | ``` 40 | 41 | ## *take/drop/ltail/lhead/last* 42 | 43 | ```bash 44 | $ list 1 2 3 4 | drop 2 45 | 3 46 | 4 47 | 48 | $ list 1 2 3 4 5 | lhead 49 | 1 50 | 51 | $ list 1 2 3 4 | ltail 52 | 2 53 | 3 54 | 4 55 | 56 | $ list 1 2 3 4 5 | last 57 | 5 58 | 59 | $ list 1 2 3 4 5 | take 2 60 | 1 61 | 2 62 | ``` 63 | 64 | ## *join* 65 | 66 | ```bash 67 | $ list 1 2 3 4 5 | join , 68 | 1,2,3,4,5 69 | 70 | $ list 1 2 3 4 5 | join , [ ] 71 | [1,2,3,4,5] 72 | ``` 73 | 74 | ## *map* 75 | 76 | ```bash 77 | $ seq 1 5 | map λ a . 'echo $((a + 5))' 78 | 6 79 | 7 80 | 8 81 | 9 82 | 10 83 | 84 | $ list a b s d e | map λ a . 'echo $a$(echo $a | tr a-z A-Z)' 85 | aA 86 | bB 87 | sS 88 | dD 89 | eE 90 | 91 | $ list 1 2 3 | map echo 92 | 1 93 | 2 94 | 3 95 | 96 | $ list 1 2 3 | map 'echo $ is a number' 97 | 1 is a number 98 | 2 is a number 99 | 3 is a number 100 | 101 | $ list 1 2 3 4 | map 'echo \($,$\) is a point' 102 | (1,1) is a point 103 | (2,2) is a point 104 | (3,3) is a point 105 | (4,4) is a point 106 | ``` 107 | 108 | ## *flat map* 109 | 110 | ```bash 111 | $ seq 2 3 | map λ a . 'seq 1 $a' | join , [ ] 112 | [1,2,1,2,3] 113 | 114 | $ list a b c | map λ a . 'echo $a; echo $a | tr a-z A-z' | join , [ ] 115 | [a,A,b,B,c,C] 116 | ``` 117 | 118 | ## *filter* 119 | 120 | ```bash 121 | $ seq 1 10 | filter λ a . '[[ $(mod $a 2) -eq 0 ]] && ret true || ret false' 122 | 2 123 | 4 124 | 6 125 | 8 126 | 10 127 | ``` 128 | 129 | ## *foldl/foldr* 130 | 131 | ```bash 132 | $ list a b c d | foldl λ acc el . 'echo -n $acc-$el' 133 | a-b-c-d 134 | 135 | $ list '' a b c d | foldr λ acc el .\ 136 | 'if [[ ! -z $acc ]]; then echo -n $acc-$el; else echo -n $el; fi' 137 | d-c-b-a 138 | ``` 139 | 140 | ```bash 141 | $ seq 1 4 | foldl λ acc el . 'echo $(($acc + $el))' 142 | 10 143 | ``` 144 | 145 | ```bash 146 | $ seq 1 4 | foldl λ acc el . 'echo $(mul $(($acc + 1)) $el)' 147 | 64 # 1 + (1 + 1) * 2 + (4 + 1) * 3 + (15 + 1) * 4 = 64 148 | 149 | $ seq 1 4 | foldr λ acc el . 'echo $(mul $(($acc + 1)) $el)' 150 | 56 # 1 + (1 + 1) * 4 + (8 + 1) * 3 + (27 + 1) * 2 = 56 151 | ``` 152 | 153 | ## *tup/tupx/tupl/tupr* 154 | 155 | ```bash 156 | $ tup a 1 157 | (a,1) 158 | 159 | $ tup 'foo bar' 1 'one' 2 160 | (foo bar,1,one,2) 161 | 162 | $ tup , 1 3 163 | (u002c,1,3) 164 | ``` 165 | 166 | ```bash 167 | $ tupl $(tup a 1) 168 | a 169 | 170 | $ tupr $(tup a 1) 171 | 1 172 | 173 | $ tup , 1 3 | tupl 174 | , 175 | 176 | $ tup 'foo bar' 1 'one' 2 | tupl 177 | foo bar 178 | 179 | $ tup 'foo bar' 1 'one' 2 | tupr 180 | 2 181 | ``` 182 | 183 | ```bash 184 | $ tup 'foo bar' 1 'one' 2 | tupx 2 185 | 1 186 | 187 | $ tup 'foo bar' 1 'one' 2 | tupx 1,3 188 | foo bar 189 | one 190 | 191 | $ tup 'foo bar' 1 'one' 2 | tupx 2-4 192 | 1 193 | one 194 | 2 195 | ``` 196 | 197 | ## *ntup/ntupx/ntupl/ntupr* 198 | 199 | ```bash 200 | $ ntup tuples that $(ntup safely nest) 201 | (dHVwbGVzCg==,dGhhdAo=,KGMyRm1aV3g1Q2c9PSxibVZ6ZEFvPSkK) 202 | 203 | echo '(dHVwbGVzCg==,dGhhdAo=,KGMyRm1aV3g1Q2c9PSxibVZ6ZEFvPSkK)' | ntupx 3 | ntupr 204 | nest 205 | 206 | $ ntup 'foo,bar' 1 one 1 207 | (Zm9vLGJhcgo=,MQo=,b25lCg==,MQo=) 208 | 209 | $ echo '(Zm9vLGJhcgo=,MQo=,b25lCg==,MQo=)' | ntupx 1 210 | foo,bar 211 | ``` 212 | 213 | ```bash 214 | $ ntupl $(ntup 'foo bar' 1 one 2) 215 | foo bar 216 | 217 | $ ntupr $(ntup 'foo bar' 1 one 2) 218 | 2 219 | ``` 220 | 221 | ## *buff* 222 | 223 | ```bash 224 | $ seq 1 10 | buff λ a b . 'echo $(($a + $b))' 225 | 3 226 | 7 227 | 11 228 | 15 229 | 19 230 | 231 | $ seq 1 10 | buff λ a b c d e . 'echo $(($a + $b + $c + $d + $e))' 232 | 15 233 | 40 234 | ``` 235 | 236 | ## *lzip* 237 | 238 | ```bash 239 | $ list a b c d e f | lzip $(seq 1 10) 240 | (a,1) 241 | (b,2) 242 | (c,3) 243 | (d,4) 244 | (e,5) 245 | (f,6) 246 | ``` 247 | 248 | ```bash 249 | $ list a b c d e f | lzip $(seq 1 10) | last | tupr 250 | 6 251 | ``` 252 | 253 | ## *curry* 254 | 255 | ```bash 256 | add2() { 257 | echo $(($1 + $2)) 258 | } 259 | ``` 260 | 261 | ```bash 262 | $ curry inc add2 1 263 | ``` 264 | 265 | ```bash 266 | $ inc 2 267 | 3 268 | 269 | $ seq 1 3 | map λ a . 'inc $a' 270 | 2 271 | 3 272 | 4 273 | ``` 274 | 275 | ## *peek* 276 | 277 | ```bash 278 | $ list 1 2 3 \ 279 | | peek lambda a . echo 'dbg a : $a' \ 280 | | map lambda a . 'mul $a 2' \ 281 | | peek lambda a . echo 'dbg b : $a' \ 282 | | sum 283 | 284 | dbg a : 1 285 | dbg a : 2 286 | dbg a : 3 287 | dbg b : 2 288 | dbg b : 4 289 | dbg b : 6 290 | 12 291 | ``` 292 | 293 | ```bash 294 | $ a=$(seq 1 4 | peek lambda a . echo 'dbg: $a' | sum) 295 | 296 | dbg: 1 297 | dbg: 2 298 | dbg: 3 299 | dbg: 4 300 | 301 | $ echo $a 302 | 303 | 10 304 | ``` 305 | 306 | ## *maybe/maybemap/maybevalue* 307 | 308 | ```bash 309 | $ list Hello | maybe 310 | (Just,Hello) 311 | 312 | $ list " " | maybe 313 | (Nothing) 314 | 315 | $ list Hello | maybe | maybemap λ a . 'tr oH Oh <<<$a' 316 | (Just,hellO) 317 | 318 | $ list " " | maybe | maybemap λ a . 'tr oH Oh <<<$a' 319 | (Nothing) 320 | 321 | $ echo bash-fun rocks | maybe | maybevalue DEFAULT 322 | bash-fun rocks 323 | 324 | $ echo | maybe | maybevalue DEFAULT 325 | DEFAULT 326 | 327 | ``` 328 | 329 | ## *not/isint/isempty* 330 | 331 | ```bash 332 | $ isint 42 333 | true 334 | 335 | $ list blah | isint 336 | false 337 | 338 | $ not true 339 | false 340 | 341 | $ not isint 777 342 | false 343 | 344 | $ list 1 2 "" c d 6 | filter λ a . 'isint $a' 345 | 1 346 | 2 347 | 6 348 | 349 | $ list 1 2 "" c d 6 | filter λ a . 'not isempty $a' 350 | 1 351 | 2 352 | c 353 | d 354 | 6 355 | ``` 356 | 357 | ## *isfile/isnonzerofile/isreadable/iswritable/isdir* 358 | 359 | ```bash 360 | $ touch /tmp/foo 361 | 362 | $ isfile /tmp/foo 363 | true 364 | 365 | $ not iswritable / 366 | true 367 | 368 | $ files="/etc/passwd /etc/sudoers /tmp /tmp/foo /no_such_file" 369 | 370 | $ list $files | filter λ a . 'isfile $a' 371 | /etc/passwd 372 | /etc/sudoers 373 | /tmp/foo 374 | 375 | $ list $files | filter λ a . 'isdir $a' 376 | /tmp 377 | 378 | $ list $files | filter λ a . 'isreadable $a' 379 | /etc/passwd 380 | /tmp 381 | /tmp/foo 382 | 383 | $ list $files | filter λ a . 'iswritable $a' 384 | /tmp 385 | /tmp/foo 386 | 387 | $ list $files | filter λ a . 'isnonzerofile $a' 388 | /etc/passwd 389 | /etc/sudoers 390 | /tmp 391 | 392 | $ list $files | filter λ a . 'not isfile $a' 393 | /tmp 394 | /no_such_file 395 | ``` 396 | 397 | ## *try/catch* 398 | 399 | ```bash 400 | $ echo 'expr 2 / 0' | try λ _ . 'echo 0' 401 | 0 402 | 403 | $ echo 'expr 2 / 0' | try λ status . 'echo $status' 404 | 2 405 | 406 | $ echo 'expr 2 / 2' | try λ _ . 'echo 0' 407 | 1 408 | ``` 409 | 410 | ```bash 411 | try λ _ . 'echo some errors during pull; exit 1' < <(echo git pull) 412 | ``` 413 | 414 | ```bash 415 | $ echo 'expr 2 / 0' \ 416 | | LANG=en catch λ cmd status val . 'echo cmd=$cmd,status=$status,val=$val' 417 | cmd=expr 2 / 0,status=2,val=(expr:,division,by,zero) 418 | ``` 419 | 420 | ```bash 421 | $ echo 'expr 2 / 2' | catch λ _ _ val . 'tupl $val' 422 | 1 423 | ``` 424 | 425 | ## *scanl* 426 | 427 | ```bash 428 | $ seq 1 5 | scanl lambda acc el . 'echo $(($acc + $el))' 429 | 1 430 | 3 431 | 6 432 | 10 433 | 15 434 | ``` 435 | 436 | ```bash 437 | $ seq 1 5 | scanl lambda a b . 'echo $(($a + $b))' | last 438 | 15 439 | ``` 440 | 441 | ## *with_trampoline/res/call* 442 | 443 | ```bash 444 | factorial() { 445 | fact_iter() { 446 | local product=$1 447 | local counter=$2 448 | local max_count=$3 449 | if [[ $counter -gt $max_count ]]; then 450 | res $product 451 | else 452 | call fact_iter $(echo $counter\*$product | bc) $(($counter + 1)) $max_count 453 | fi 454 | } 455 | 456 | with_trampoline fact_iter 1 1 $1 457 | } 458 | ``` 459 | 460 | ```bash 461 | $ time factorial 30 | fold -w 70 462 | 265252859812191058636308480000000 463 | 464 | real 0m1.854s 465 | user 0m0.072s 466 | sys 0m0.368s 467 | ``` 468 | 469 | ```bash 470 | time factorial 60 | fold -w 70 471 | 8320987112741390144276341183223364380754172606361245952449277696409600 472 | 000000000000 473 | 474 | real 0m3.635s 475 | user 0m0.148s 476 | sys 0m0.692s 477 | ``` 478 | 479 | ```bash 480 | $ time factorial 90 | fold -w 70 481 | 1485715964481761497309522733620825737885569961284688766942216863704985 482 | 393094065876545992131370884059645617234469978112000000000000000000000 483 | 484 | real 0m4.371s 485 | user 0m0.108s 486 | sys 0m0.436s 487 | ``` 488 | 489 | # Examples 490 | 491 | ```bash 492 | processNames() { 493 | 494 | uppercase() { 495 | local str=$1 496 | echo $(tr 'a-z' 'A-Z' <<< ${str:0:1})${str:1} 497 | } 498 | 499 | list $@ \ 500 | | filter λ name . '[[ ${#name} -gt 1 ]] && ret true || ret false' \ 501 | | map λ name . 'uppercase $name' \ 502 | | foldl λ acc el . 'echo $acc,$el' 503 | 504 | } 505 | 506 | processNames adam monika s slawek d daniel Bartek j k 507 | ``` 508 | 509 | ```bash 510 | Adam,Monika,Slawek,Daniel,Bartek 511 | ``` 512 | 513 | # Running tests 514 | 515 | ```bash 516 | cd test 517 | ./test_runner 518 | ``` 519 | 520 | # Contribution guidelines 521 | 522 | Feel free to ask questions in chat, open issues, or contribute by creating pull requests. 523 | 524 | In order to create a pull request 525 | * checkout master branch 526 | * introduce your changes & bump version 527 | * submit pull request 528 | 529 | # Resources 530 | * [Inspiration](https://quasimal.com/posts/2012-05-21-funsh.html) 531 | * [Functional Programming in Bash](https://medium.com/@joydeepubuntu/functional-programming-in-bash-145b6db336b7) 532 | -------------------------------------------------------------------------------- /test/shunit2: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # vim:et:ft=sh:sts=2:sw=2 3 | # 4 | # Copyright 2008-2018 Kate Ward. All Rights Reserved. 5 | # Released under the Apache 2.0 license. 6 | # 7 | # shUnit2 -- Unit testing framework for Unix shell scripts. 8 | # https://github.com/kward/shunit2 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # 12 | # shUnit2 is a xUnit based unit test framework for Bourne shell scripts. It is 13 | # based on the popular JUnit unit testing framework for Java. 14 | # 15 | # $() are not fully portable (POSIX != portable). 16 | # shellcheck disable=SC2006 17 | # expr may be antiquated, but it is the only solution in some cases. 18 | # shellcheck disable=SC2003 19 | # Commands are purposely escaped so they can be mocked outside shUnit2. 20 | # shellcheck disable=SC1001,SC1012 21 | 22 | # Return if shunit2 already loaded. 23 | \[ -n "${SHUNIT_VERSION:-}" ] && exit 0 24 | SHUNIT_VERSION='2.1.7' 25 | 26 | # Return values that scripts can use. 27 | SHUNIT_TRUE=0 28 | SHUNIT_FALSE=1 29 | SHUNIT_ERROR=2 30 | 31 | # Logging functions. 32 | _shunit_warn() { 33 | echo "${__shunit_ansi_yellow}shunit2:WARN${__shunit_ansi_none} $*" >&2 34 | } 35 | _shunit_error() { 36 | echo "${__shunit_ansi_red}shunit2:ERROR${__shunit_ansi_none} $*" >&2 37 | } 38 | _shunit_fatal() { 39 | echo "${__shunit_ansi_red}shunit2:FATAL${__shunit_ansi_none} $*" >&2 40 | exit ${SHUNIT_ERROR} 41 | } 42 | 43 | # Determine some reasonable command defaults. 44 | __SHUNIT_UNAME_S=`uname -s` 45 | case "${__SHUNIT_UNAME_S}" in 46 | BSD) __SHUNIT_CMD_EXPR='gexpr' ;; 47 | *) __SHUNIT_CMD_EXPR='expr' ;; 48 | esac 49 | 50 | __SHUNIT_CMD_ECHO_ESC='echo -e' 51 | # shellcheck disable=SC2039 52 | \[ "`echo -e test`" = '-e test' ] && __SHUNIT_CMD_ECHO_ESC='echo' 53 | 54 | # Commands a user can override if needed. 55 | SHUNIT_CMD_EXPR=${SHUNIT_CMD_EXPR:-${__SHUNIT_CMD_EXPR}} 56 | 57 | # Enable color output. Options are 'never', 'always', or 'auto'. 58 | SHUNIT_COLOR=${SHUNIT_COLOR:-auto} 59 | 60 | # Specific shell checks. 61 | if \[ -n "${ZSH_VERSION:-}" ]; then 62 | setopt |grep "^shwordsplit$" >/dev/null 63 | if \[ $? -ne ${SHUNIT_TRUE} ]; then 64 | _shunit_fatal 'zsh shwordsplit option is required for proper operation' 65 | fi 66 | if \[ -z "${SHUNIT_PARENT:-}" ]; then 67 | _shunit_fatal "zsh does not pass \$0 through properly. please declare \ 68 | \"SHUNIT_PARENT=\$0\" before calling shUnit2" 69 | fi 70 | fi 71 | 72 | # 73 | # Constants 74 | # 75 | 76 | __SHUNIT_MODE_SOURCED='sourced' 77 | __SHUNIT_MODE_STANDALONE='standalone' 78 | __SHUNIT_PARENT=${SHUNIT_PARENT:-$0} 79 | 80 | # ANSI colors. 81 | __SHUNIT_ANSI_NONE='\033[0m' 82 | __SHUNIT_ANSI_RED='\033[1;31m' 83 | __SHUNIT_ANSI_GREEN='\033[1;32m' 84 | __SHUNIT_ANSI_YELLOW='\033[1;33m' 85 | __SHUNIT_ANSI_CYAN='\033[1;36m' 86 | 87 | # Set the constants readonly. 88 | __shunit_constants=`set |grep '^__SHUNIT_' |cut -d= -f1` 89 | echo "${__shunit_constants}" |grep '^Binary file' >/dev/null && \ 90 | __shunit_constants=`set |grep -a '^__SHUNIT_' |cut -d= -f1` 91 | for __shunit_const in ${__shunit_constants}; do 92 | if \[ -z "${ZSH_VERSION:-}" ]; then 93 | readonly "${__shunit_const}" 94 | else 95 | case ${ZSH_VERSION} in 96 | [123].*) readonly "${__shunit_const}" ;; 97 | *) readonly -g "${__shunit_const}" # Declare readonly constants globally. 98 | esac 99 | fi 100 | done 101 | unset __shunit_const __shunit_constants 102 | 103 | # 104 | # Internal variables. 105 | # 106 | 107 | # Variables. 108 | __shunit_lineno='' # Line number of executed test. 109 | __shunit_mode=${__SHUNIT_MODE_SOURCED} # Operating mode. 110 | __shunit_reportGenerated=${SHUNIT_FALSE} # Is report generated. 111 | __shunit_script='' # Filename of unittest script (standalone mode). 112 | __shunit_skip=${SHUNIT_FALSE} # Is skipping enabled. 113 | __shunit_suite='' # Suite of tests to execute. 114 | 115 | # ANSI colors (populated by _shunit_configureColor()). 116 | __shunit_ansi_none='' 117 | __shunit_ansi_red='' 118 | __shunit_ansi_green='' 119 | __shunit_ansi_yellow='' 120 | __shunit_ansi_cyan='' 121 | 122 | # Counts of tests. 123 | __shunit_testSuccess=${SHUNIT_TRUE} 124 | __shunit_testsTotal=0 125 | __shunit_testsPassed=0 126 | __shunit_testsFailed=0 127 | 128 | # Counts of asserts. 129 | __shunit_assertsTotal=0 130 | __shunit_assertsPassed=0 131 | __shunit_assertsFailed=0 132 | __shunit_assertsSkipped=0 133 | 134 | # 135 | # Macros. 136 | # 137 | 138 | # shellcheck disable=SC2016,SC2089 139 | _SHUNIT_LINENO_='eval __shunit_lineno=""; if \[ "${1:-}" = "--lineno" ]; then \[ -n "$2" ] && __shunit_lineno="[$2] "; shift 2; fi' 140 | 141 | #----------------------------------------------------------------------------- 142 | # Assertion functions. 143 | # 144 | 145 | # Assert that two values are equal to one another. 146 | # 147 | # Args: 148 | # message: string: failure message [optional] 149 | # expected: string: expected value 150 | # actual: string: actual value 151 | # Returns: 152 | # integer: success (TRUE/FALSE/ERROR constant) 153 | assertEquals() { 154 | # shellcheck disable=SC2090 155 | ${_SHUNIT_LINENO_} 156 | if \[ $# -lt 2 -o $# -gt 3 ]; then 157 | _shunit_error "assertEquals() requires two or three arguments; $# given" 158 | _shunit_assertFail 159 | return ${SHUNIT_ERROR} 160 | fi 161 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 162 | 163 | shunit_message_=${__shunit_lineno} 164 | if \[ $# -eq 3 ]; then 165 | shunit_message_="${shunit_message_}$1" 166 | shift 167 | fi 168 | shunit_expected_=$1 169 | shunit_actual_=$2 170 | 171 | shunit_return=${SHUNIT_TRUE} 172 | if \[ "${shunit_expected_}" = "${shunit_actual_}" ]; then 173 | _shunit_assertPass 174 | else 175 | failNotEquals "${shunit_message_}" "${shunit_expected_}" "${shunit_actual_}" 176 | shunit_return=${SHUNIT_FALSE} 177 | fi 178 | 179 | unset shunit_message_ shunit_expected_ shunit_actual_ 180 | return ${shunit_return} 181 | } 182 | # shellcheck disable=SC2016,SC2034 183 | _ASSERT_EQUALS_='eval assertEquals --lineno "${LINENO:-}"' 184 | 185 | # Assert that two values are not equal to one another. 186 | # 187 | # Args: 188 | # message: string: failure message [optional] 189 | # expected: string: expected value 190 | # actual: string: actual value 191 | # Returns: 192 | # integer: success (TRUE/FALSE/ERROR constant) 193 | assertNotEquals() { 194 | # shellcheck disable=SC2090 195 | ${_SHUNIT_LINENO_} 196 | if \[ $# -lt 2 -o $# -gt 3 ]; then 197 | _shunit_error "assertNotEquals() requires two or three arguments; $# given" 198 | _shunit_assertFail 199 | return ${SHUNIT_ERROR} 200 | fi 201 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 202 | 203 | shunit_message_=${__shunit_lineno} 204 | if \[ $# -eq 3 ]; then 205 | shunit_message_="${shunit_message_}$1" 206 | shift 207 | fi 208 | shunit_expected_=$1 209 | shunit_actual_=$2 210 | 211 | shunit_return=${SHUNIT_TRUE} 212 | if \[ "${shunit_expected_}" != "${shunit_actual_}" ]; then 213 | _shunit_assertPass 214 | else 215 | failSame "${shunit_message_}" "$@" 216 | shunit_return=${SHUNIT_FALSE} 217 | fi 218 | 219 | unset shunit_message_ shunit_expected_ shunit_actual_ 220 | return ${shunit_return} 221 | } 222 | # shellcheck disable=SC2016,SC2034 223 | _ASSERT_NOT_EQUALS_='eval assertNotEquals --lineno "${LINENO:-}"' 224 | 225 | # Assert that a value is null (i.e. an empty string) 226 | # 227 | # Args: 228 | # message: string: failure message [optional] 229 | # actual: string: actual value 230 | # Returns: 231 | # integer: success (TRUE/FALSE/ERROR constant) 232 | assertNull() { 233 | # shellcheck disable=SC2090 234 | ${_SHUNIT_LINENO_} 235 | if \[ $# -lt 1 -o $# -gt 2 ]; then 236 | _shunit_error "assertNull() requires one or two arguments; $# given" 237 | _shunit_assertFail 238 | return ${SHUNIT_ERROR} 239 | fi 240 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 241 | 242 | shunit_message_=${__shunit_lineno} 243 | if \[ $# -eq 2 ]; then 244 | shunit_message_="${shunit_message_}$1" 245 | shift 246 | fi 247 | assertTrue "${shunit_message_}" "[ -z '$1' ]" 248 | shunit_return=$? 249 | 250 | unset shunit_message_ 251 | return ${shunit_return} 252 | } 253 | # shellcheck disable=SC2016,SC2034 254 | _ASSERT_NULL_='eval assertNull --lineno "${LINENO:-}"' 255 | 256 | # Assert that a value is not null (i.e. a non-empty string) 257 | # 258 | # Args: 259 | # message: string: failure message [optional] 260 | # actual: string: actual value 261 | # Returns: 262 | # integer: success (TRUE/FALSE/ERROR constant) 263 | assertNotNull() { 264 | # shellcheck disable=SC2090 265 | ${_SHUNIT_LINENO_} 266 | if \[ $# -gt 2 ]; then # allowing 0 arguments as $1 might actually be null 267 | _shunit_error "assertNotNull() requires one or two arguments; $# given" 268 | _shunit_assertFail 269 | return ${SHUNIT_ERROR} 270 | fi 271 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 272 | 273 | shunit_message_=${__shunit_lineno} 274 | if \[ $# -eq 2 ]; then 275 | shunit_message_="${shunit_message_}$1" 276 | shift 277 | fi 278 | shunit_actual_=`_shunit_escapeCharactersInString "${1:-}"` 279 | test -n "${shunit_actual_}" 280 | assertTrue "${shunit_message_}" $? 281 | shunit_return=$? 282 | 283 | unset shunit_actual_ shunit_message_ 284 | return ${shunit_return} 285 | } 286 | # shellcheck disable=SC2016,SC2034 287 | _ASSERT_NOT_NULL_='eval assertNotNull --lineno "${LINENO:-}"' 288 | 289 | # Assert that two values are the same (i.e. equal to one another). 290 | # 291 | # Args: 292 | # message: string: failure message [optional] 293 | # expected: string: expected value 294 | # actual: string: actual value 295 | # Returns: 296 | # integer: success (TRUE/FALSE/ERROR constant) 297 | assertSame() { 298 | # shellcheck disable=SC2090 299 | ${_SHUNIT_LINENO_} 300 | if \[ $# -lt 2 -o $# -gt 3 ]; then 301 | _shunit_error "assertSame() requires two or three arguments; $# given" 302 | _shunit_assertFail 303 | return ${SHUNIT_ERROR} 304 | fi 305 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 306 | 307 | shunit_message_=${__shunit_lineno} 308 | if \[ $# -eq 3 ]; then 309 | shunit_message_="${shunit_message_}$1" 310 | shift 311 | fi 312 | assertEquals "${shunit_message_}" "$1" "$2" 313 | shunit_return=$? 314 | 315 | unset shunit_message_ 316 | return ${shunit_return} 317 | } 318 | # shellcheck disable=SC2016,SC2034 319 | _ASSERT_SAME_='eval assertSame --lineno "${LINENO:-}"' 320 | 321 | # Assert that two values are not the same (i.e. not equal to one another). 322 | # 323 | # Args: 324 | # message: string: failure message [optional] 325 | # expected: string: expected value 326 | # actual: string: actual value 327 | # Returns: 328 | # integer: success (TRUE/FALSE/ERROR constant) 329 | assertNotSame() { 330 | # shellcheck disable=SC2090 331 | ${_SHUNIT_LINENO_} 332 | if \[ $# -lt 2 -o $# -gt 3 ]; then 333 | _shunit_error "assertNotSame() requires two or three arguments; $# given" 334 | _shunit_assertFail 335 | return ${SHUNIT_ERROR} 336 | fi 337 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 338 | 339 | shunit_message_=${__shunit_lineno} 340 | if \[ $# -eq 3 ]; then 341 | shunit_message_="${shunit_message_:-}$1" 342 | shift 343 | fi 344 | assertNotEquals "${shunit_message_}" "$1" "$2" 345 | shunit_return=$? 346 | 347 | unset shunit_message_ 348 | return ${shunit_return} 349 | } 350 | # shellcheck disable=SC2016,SC2034 351 | _ASSERT_NOT_SAME_='eval assertNotSame --lineno "${LINENO:-}"' 352 | 353 | # Assert that a value or shell test condition is true. 354 | # 355 | # In shell, a value of 0 is true and a non-zero value is false. Any integer 356 | # value passed can thereby be tested. 357 | # 358 | # Shell supports much more complicated tests though, and a means to support 359 | # them was needed. As such, this function tests that conditions are true or 360 | # false through evaluation rather than just looking for a true or false. 361 | # 362 | # The following test will succeed: 363 | # assertTrue 0 364 | # assertTrue "[ 34 -gt 23 ]" 365 | # The following test will fail with a message: 366 | # assertTrue 123 367 | # assertTrue "test failed" "[ -r '/non/existent/file' ]" 368 | # 369 | # Args: 370 | # message: string: failure message [optional] 371 | # condition: string: integer value or shell conditional statement 372 | # Returns: 373 | # integer: success (TRUE/FALSE/ERROR constant) 374 | assertTrue() { 375 | # shellcheck disable=SC2090 376 | ${_SHUNIT_LINENO_} 377 | if \[ $# -lt 1 -o $# -gt 2 ]; then 378 | _shunit_error "assertTrue() takes one or two arguments; $# given" 379 | _shunit_assertFail 380 | return ${SHUNIT_ERROR} 381 | fi 382 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 383 | 384 | shunit_message_=${__shunit_lineno} 385 | if \[ $# -eq 2 ]; then 386 | shunit_message_="${shunit_message_}$1" 387 | shift 388 | fi 389 | shunit_condition_=$1 390 | 391 | # See if condition is an integer, i.e. a return value. 392 | shunit_match_=`expr "${shunit_condition_}" : '\([0-9]*\)'` 393 | shunit_return=${SHUNIT_TRUE} 394 | if \[ -z "${shunit_condition_}" ]; then 395 | # Null condition. 396 | shunit_return=${SHUNIT_FALSE} 397 | elif \[ -n "${shunit_match_}" -a "${shunit_condition_}" = "${shunit_match_}" ] 398 | then 399 | # Possible return value. Treating 0 as true, and non-zero as false. 400 | \[ "${shunit_condition_}" -ne 0 ] && shunit_return=${SHUNIT_FALSE} 401 | else 402 | # Hopefully... a condition. 403 | ( eval "${shunit_condition_}" ) >/dev/null 2>&1 404 | \[ $? -ne 0 ] && shunit_return=${SHUNIT_FALSE} 405 | fi 406 | 407 | # Record the test. 408 | if \[ ${shunit_return} -eq ${SHUNIT_TRUE} ]; then 409 | _shunit_assertPass 410 | else 411 | _shunit_assertFail "${shunit_message_}" 412 | fi 413 | 414 | unset shunit_message_ shunit_condition_ shunit_match_ 415 | return ${shunit_return} 416 | } 417 | # shellcheck disable=SC2016,SC2034 418 | _ASSERT_TRUE_='eval assertTrue --lineno "${LINENO:-}"' 419 | 420 | # Assert that a value or shell test condition is false. 421 | # 422 | # In shell, a value of 0 is true and a non-zero value is false. Any integer 423 | # value passed can thereby be tested. 424 | # 425 | # Shell supports much more complicated tests though, and a means to support 426 | # them was needed. As such, this function tests that conditions are true or 427 | # false through evaluation rather than just looking for a true or false. 428 | # 429 | # The following test will succeed: 430 | # assertFalse 1 431 | # assertFalse "[ 'apples' = 'oranges' ]" 432 | # The following test will fail with a message: 433 | # assertFalse 0 434 | # assertFalse "test failed" "[ 1 -eq 1 -a 2 -eq 2 ]" 435 | # 436 | # Args: 437 | # message: string: failure message [optional] 438 | # condition: string: integer value or shell conditional statement 439 | # Returns: 440 | # integer: success (TRUE/FALSE/ERROR constant) 441 | assertFalse() { 442 | # shellcheck disable=SC2090 443 | ${_SHUNIT_LINENO_} 444 | if \[ $# -lt 1 -o $# -gt 2 ]; then 445 | _shunit_error "assertFalse() quires one or two arguments; $# given" 446 | _shunit_assertFail 447 | return ${SHUNIT_ERROR} 448 | fi 449 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 450 | 451 | shunit_message_=${__shunit_lineno} 452 | if \[ $# -eq 2 ]; then 453 | shunit_message_="${shunit_message_}$1" 454 | shift 455 | fi 456 | shunit_condition_=$1 457 | 458 | # See if condition is an integer, i.e. a return value. 459 | shunit_match_=`expr "${shunit_condition_}" : '\([0-9]*\)'` 460 | shunit_return=${SHUNIT_TRUE} 461 | if \[ -z "${shunit_condition_}" ]; then 462 | # Null condition. 463 | shunit_return=${SHUNIT_FALSE} 464 | elif \[ -n "${shunit_match_}" -a "${shunit_condition_}" = "${shunit_match_}" ] 465 | then 466 | # Possible return value. Treating 0 as true, and non-zero as false. 467 | \[ "${shunit_condition_}" -eq 0 ] && shunit_return=${SHUNIT_FALSE} 468 | else 469 | # Hopefully... a condition. 470 | ( eval "${shunit_condition_}" ) >/dev/null 2>&1 471 | \[ $? -eq 0 ] && shunit_return=${SHUNIT_FALSE} 472 | fi 473 | 474 | # Record the test. 475 | if \[ "${shunit_return}" -eq "${SHUNIT_TRUE}" ]; then 476 | _shunit_assertPass 477 | else 478 | _shunit_assertFail "${shunit_message_}" 479 | fi 480 | 481 | unset shunit_message_ shunit_condition_ shunit_match_ 482 | return "${shunit_return}" 483 | } 484 | # shellcheck disable=SC2016,SC2034 485 | _ASSERT_FALSE_='eval assertFalse --lineno "${LINENO:-}"' 486 | 487 | #----------------------------------------------------------------------------- 488 | # Failure functions. 489 | # 490 | 491 | # Records a test failure. 492 | # 493 | # Args: 494 | # message: string: failure message [optional] 495 | # Returns: 496 | # integer: success (TRUE/FALSE/ERROR constant) 497 | fail() { 498 | # shellcheck disable=SC2090 499 | ${_SHUNIT_LINENO_} 500 | if \[ $# -gt 1 ]; then 501 | _shunit_error "fail() requires zero or one arguments; $# given" 502 | return ${SHUNIT_ERROR} 503 | fi 504 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 505 | 506 | shunit_message_=${__shunit_lineno} 507 | if \[ $# -eq 1 ]; then 508 | shunit_message_="${shunit_message_}$1" 509 | shift 510 | fi 511 | 512 | _shunit_assertFail "${shunit_message_}" 513 | 514 | unset shunit_message_ 515 | return ${SHUNIT_FALSE} 516 | } 517 | # shellcheck disable=SC2016,SC2034 518 | _FAIL_='eval fail --lineno "${LINENO:-}"' 519 | 520 | # Records a test failure, stating two values were not equal. 521 | # 522 | # Args: 523 | # message: string: failure message [optional] 524 | # expected: string: expected value 525 | # actual: string: actual value 526 | # Returns: 527 | # integer: success (TRUE/FALSE/ERROR constant) 528 | failNotEquals() { 529 | # shellcheck disable=SC2090 530 | ${_SHUNIT_LINENO_} 531 | if \[ $# -lt 2 -o $# -gt 3 ]; then 532 | _shunit_error "failNotEquals() requires one or two arguments; $# given" 533 | return ${SHUNIT_ERROR} 534 | fi 535 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 536 | 537 | shunit_message_=${__shunit_lineno} 538 | if \[ $# -eq 3 ]; then 539 | shunit_message_="${shunit_message_}$1" 540 | shift 541 | fi 542 | shunit_expected_=$1 543 | shunit_actual_=$2 544 | 545 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }expected:<${shunit_expected_}> but was:<${shunit_actual_}>" 546 | 547 | unset shunit_message_ shunit_expected_ shunit_actual_ 548 | return ${SHUNIT_FALSE} 549 | } 550 | # shellcheck disable=SC2016,SC2034 551 | _FAIL_NOT_EQUALS_='eval failNotEquals --lineno "${LINENO:-}"' 552 | 553 | # Records a test failure, stating two values should have been the same. 554 | # 555 | # Args: 556 | # message: string: failure message [optional] 557 | # expected: string: expected value 558 | # actual: string: actual value 559 | # Returns: 560 | # integer: success (TRUE/FALSE/ERROR constant) 561 | failSame() 562 | { 563 | # shellcheck disable=SC2090 564 | ${_SHUNIT_LINENO_} 565 | if \[ $# -lt 2 -o $# -gt 3 ]; then 566 | _shunit_error "failSame() requires two or three arguments; $# given" 567 | return ${SHUNIT_ERROR} 568 | fi 569 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 570 | 571 | shunit_message_=${__shunit_lineno} 572 | if \[ $# -eq 3 ]; then 573 | shunit_message_="${shunit_message_}$1" 574 | shift 575 | fi 576 | 577 | _shunit_assertFail "${shunit_message_:+${shunit_message_} }expected not same" 578 | 579 | unset shunit_message_ 580 | return ${SHUNIT_FALSE} 581 | } 582 | # shellcheck disable=SC2016,SC2034 583 | _FAIL_SAME_='eval failSame --lineno "${LINENO:-}"' 584 | 585 | # Records a test failure, stating two values were not equal. 586 | # 587 | # This is functionally equivalent to calling failNotEquals(). 588 | # 589 | # Args: 590 | # message: string: failure message [optional] 591 | # expected: string: expected value 592 | # actual: string: actual value 593 | # Returns: 594 | # integer: success (TRUE/FALSE/ERROR constant) 595 | failNotSame() { 596 | # shellcheck disable=SC2090 597 | ${_SHUNIT_LINENO_} 598 | if \[ $# -lt 2 -o $# -gt 3 ]; then 599 | _shunit_error "failNotEquals() requires one or two arguments; $# given" 600 | return ${SHUNIT_ERROR} 601 | fi 602 | _shunit_shouldSkip && return ${SHUNIT_TRUE} 603 | 604 | shunit_message_=${__shunit_lineno} 605 | if \[ $# -eq 3 ]; then 606 | shunit_message_="${shunit_message_}$1" 607 | shift 608 | fi 609 | failNotEquals "${shunit_message_}" "$1" "$2" 610 | shunit_return=$? 611 | 612 | unset shunit_message_ 613 | return ${shunit_return} 614 | } 615 | # shellcheck disable=SC2016,SC2034 616 | _FAIL_NOT_SAME_='eval failNotSame --lineno "${LINENO:-}"' 617 | 618 | #----------------------------------------------------------------------------- 619 | # Skipping functions. 620 | # 621 | 622 | # Force remaining assert and fail functions to be "skipped". 623 | # 624 | # This function forces the remaining assert and fail functions to be "skipped", 625 | # i.e. they will have no effect. Each function skipped will be recorded so that 626 | # the total of asserts and fails will not be altered. 627 | # 628 | # Args: 629 | # None 630 | startSkipping() { __shunit_skip=${SHUNIT_TRUE}; } 631 | 632 | # Resume the normal recording behavior of assert and fail calls. 633 | # 634 | # Args: 635 | # None 636 | endSkipping() { __shunit_skip=${SHUNIT_FALSE}; } 637 | 638 | # Returns the state of assert and fail call skipping. 639 | # 640 | # Args: 641 | # None 642 | # Returns: 643 | # boolean: (TRUE/FALSE constant) 644 | isSkipping() { return ${__shunit_skip}; } 645 | 646 | #----------------------------------------------------------------------------- 647 | # Suite functions. 648 | # 649 | 650 | # Stub. This function should contains all unit test calls to be made. 651 | # 652 | # DEPRECATED (as of 2.1.0) 653 | # 654 | # This function can be optionally overridden by the user in their test suite. 655 | # 656 | # If this function exists, it will be called when shunit2 is sourced. If it 657 | # does not exist, shunit2 will search the parent script for all functions 658 | # beginning with the word 'test', and they will be added dynamically to the 659 | # test suite. 660 | # 661 | # This function should be overridden by the user in their unit test suite. 662 | # Note: see _shunit_mktempFunc() for actual implementation 663 | # 664 | # Args: 665 | # None 666 | #suite() { :; } # DO NOT UNCOMMENT THIS FUNCTION 667 | 668 | # Adds a function name to the list of tests schedule for execution. 669 | # 670 | # This function should only be called from within the suite() function. 671 | # 672 | # Args: 673 | # function: string: name of a function to add to current unit test suite 674 | suite_addTest() { 675 | shunit_func_=${1:-} 676 | 677 | __shunit_suite="${__shunit_suite:+${__shunit_suite} }${shunit_func_}" 678 | __shunit_testsTotal=`expr ${__shunit_testsTotal} + 1` 679 | 680 | unset shunit_func_ 681 | } 682 | 683 | # Stub. This function will be called once before any tests are run. 684 | # 685 | # Common one-time environment preparation tasks shared by all tests can be 686 | # defined here. 687 | # 688 | # This function should be overridden by the user in their unit test suite. 689 | # Note: see _shunit_mktempFunc() for actual implementation 690 | # 691 | # Args: 692 | # None 693 | #oneTimeSetUp() { :; } # DO NOT UNCOMMENT THIS FUNCTION 694 | 695 | # Stub. This function will be called once after all tests are finished. 696 | # 697 | # Common one-time environment cleanup tasks shared by all tests can be defined 698 | # here. 699 | # 700 | # This function should be overridden by the user in their unit test suite. 701 | # Note: see _shunit_mktempFunc() for actual implementation 702 | # 703 | # Args: 704 | # None 705 | #oneTimeTearDown() { :; } # DO NOT UNCOMMENT THIS FUNCTION 706 | 707 | # Stub. This function will be called before each test is run. 708 | # 709 | # Common environment preparation tasks shared by all tests can be defined here. 710 | # 711 | # This function should be overridden by the user in their unit test suite. 712 | # Note: see _shunit_mktempFunc() for actual implementation 713 | # 714 | # Args: 715 | # None 716 | #setUp() { :; } # DO NOT UNCOMMENT THIS FUNCTION 717 | 718 | # Note: see _shunit_mktempFunc() for actual implementation 719 | # Stub. This function will be called after each test is run. 720 | # 721 | # Common environment cleanup tasks shared by all tests can be defined here. 722 | # 723 | # This function should be overridden by the user in their unit test suite. 724 | # Note: see _shunit_mktempFunc() for actual implementation 725 | # 726 | # Args: 727 | # None 728 | #tearDown() { :; } # DO NOT UNCOMMENT THIS FUNCTION 729 | 730 | #------------------------------------------------------------------------------ 731 | # Internal shUnit2 functions. 732 | # 733 | 734 | # Create a temporary directory to store various run-time files in. 735 | # 736 | # This function is a cross-platform temporary directory creation tool. Not all 737 | # OSes have the `mktemp` function, so one is included here. 738 | # 739 | # Args: 740 | # None 741 | # Outputs: 742 | # string: the temporary directory that was created 743 | _shunit_mktempDir() { 744 | # Try the standard `mktemp` function. 745 | ( exec mktemp -dqt shunit.XXXXXX 2>/dev/null ) && return 746 | 747 | # The standard `mktemp` didn't work. Use our own. 748 | # shellcheck disable=SC2039 749 | if \[ -r '/dev/urandom' -a -x '/usr/bin/od' ]; then 750 | _shunit_random_=`/usr/bin/od -vAn -N4 -tx4 "${_shunit_file_}" 778 | #! /bin/sh 779 | exit ${SHUNIT_TRUE} 780 | EOF 781 | \chmod +x "${_shunit_file_}" 782 | done 783 | 784 | unset _shunit_file_ 785 | } 786 | 787 | # Final cleanup function to leave things as we found them. 788 | # 789 | # Besides removing the temporary directory, this function is in charge of the 790 | # final exit code of the unit test. The exit code is based on how the script 791 | # was ended (e.g. normal exit, or via Ctrl-C). 792 | # 793 | # Args: 794 | # name: string: name of the trap called (specified when trap defined) 795 | _shunit_cleanup() { 796 | _shunit_name_=$1 797 | 798 | case ${_shunit_name_} in 799 | EXIT) _shunit_signal_=0 ;; 800 | INT) _shunit_signal_=2 ;; 801 | TERM) _shunit_signal_=15 ;; 802 | *) 803 | _shunit_error "unrecognized trap value (${_shunit_name_})" 804 | _shunit_signal_=0 805 | ;; 806 | esac 807 | 808 | # Do our work. 809 | \rm -fr "${__shunit_tmpDir}" 810 | 811 | # Exit for all non-EXIT signals. 812 | if \[ "${_shunit_name_}" != 'EXIT' ]; then 813 | _shunit_warn "trapped and now handling the (${_shunit_name_}) signal" 814 | # Disable EXIT trap. 815 | trap 0 816 | # Add 128 to signal and exit. 817 | exit "`expr "${_shunit_signal_}" + 128`" 818 | elif \[ ${__shunit_reportGenerated} -eq ${SHUNIT_FALSE} ] ; then 819 | _shunit_assertFail 'Unknown failure encountered running a test' 820 | _shunit_generateReport 821 | exit ${SHUNIT_ERROR} 822 | fi 823 | 824 | unset _shunit_name_ _shunit_signal_ 825 | } 826 | 827 | # configureOutput based on user preferences, e.g. color. 828 | # 829 | # Args: 830 | # color: string: color mode (one of `always`, `auto`, or `none`). 831 | _shunit_configureColor() { 832 | _shunit_color_=${SHUNIT_FALSE} # By default, no color. 833 | case $1 in 834 | 'always') _shunit_color_=${SHUNIT_TRUE} ;; 835 | 'auto') 836 | ( exec tput >/dev/null 2>&1 ) # Check for existence of tput command. 837 | if [ $? -lt 127 ]; then 838 | _shunit_tput_=`tput colors` 839 | # shellcheck disable=SC2166,SC2181 840 | [ $? -eq 0 -a "${_shunit_tput_}" -ge 16 ] && _shunit_color_=${SHUNIT_TRUE} 841 | fi 842 | ;; 843 | 'none') ;; 844 | *) _shunit_fatal "unrecognized SHUNIT_COLOR option '${SHUNIT_COLOR}'" ;; 845 | esac 846 | 847 | case ${_shunit_color_} in 848 | ${SHUNIT_TRUE}) 849 | __shunit_ansi_none=${__SHUNIT_ANSI_NONE} 850 | __shunit_ansi_red=${__SHUNIT_ANSI_RED} 851 | __shunit_ansi_green=${__SHUNIT_ANSI_GREEN} 852 | __shunit_ansi_yellow=${__SHUNIT_ANSI_YELLOW} 853 | __shunit_ansi_cyan=${__SHUNIT_ANSI_CYAN} 854 | ;; 855 | ${SHUNIT_FALSE}) 856 | __shunit_ansi_none='' 857 | __shunit_ansi_red='' 858 | __shunit_ansi_green='' 859 | __shunit_ansi_yellow='' 860 | __shunit_ansi_cyan='' 861 | ;; 862 | esac 863 | 864 | unset _shunit_color_ _shunit_tput_ 865 | } 866 | 867 | # The actual running of the tests happens here. 868 | # 869 | # Args: 870 | # None 871 | _shunit_execSuite() { 872 | for _shunit_test_ in ${__shunit_suite}; do 873 | __shunit_testSuccess=${SHUNIT_TRUE} 874 | 875 | # disable skipping 876 | endSkipping 877 | 878 | # execute the per-test setup function 879 | setUp 880 | 881 | # execute the test 882 | echo "${_shunit_test_}" 883 | eval "${_shunit_test_}" 884 | 885 | # execute the per-test tear-down function 886 | tearDown 887 | 888 | # update stats 889 | if \[ ${__shunit_testSuccess} -eq ${SHUNIT_TRUE} ]; then 890 | __shunit_testsPassed=`expr ${__shunit_testsPassed} + 1` 891 | else 892 | __shunit_testsFailed=`expr ${__shunit_testsFailed} + 1` 893 | fi 894 | done 895 | 896 | unset _shunit_test_ 897 | } 898 | 899 | # Generates the user friendly report with appropriate OK/FAILED message. 900 | # 901 | # Args: 902 | # None 903 | # Output: 904 | # string: the report of successful and failed tests, as well as totals. 905 | _shunit_generateReport() { 906 | _shunit_ok_=${SHUNIT_TRUE} 907 | 908 | # If no exit code was provided one, determine an appropriate one. 909 | \[ "${__shunit_testsFailed}" -gt 0 \ 910 | -o ${__shunit_testSuccess} -eq ${SHUNIT_FALSE} ] \ 911 | && _shunit_ok_=${SHUNIT_FALSE} 912 | 913 | echo 914 | _shunit_msg_="Ran ${__shunit_ansi_cyan}${__shunit_testsTotal}${__shunit_ansi_none}" 915 | if \[ "${__shunit_testsTotal}" -eq 1 ]; then 916 | ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_} test." 917 | else 918 | ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_} tests." 919 | fi 920 | 921 | _shunit_failures_='' 922 | _shunit_skipped_='' 923 | \[ ${__shunit_assertsFailed} -gt 0 ] \ 924 | && _shunit_failures_="failures=${__shunit_assertsFailed}" 925 | \[ ${__shunit_assertsSkipped} -gt 0 ] \ 926 | && _shunit_skipped_="skipped=${__shunit_assertsSkipped}" 927 | 928 | if \[ ${_shunit_ok_} -eq ${SHUNIT_TRUE} ]; then 929 | _shunit_msg_="${__shunit_ansi_green}OK${__shunit_ansi_none}" 930 | \[ -n "${_shunit_skipped_}" ] \ 931 | && _shunit_msg_="${_shunit_msg_} (${__shunit_ansi_yellow}${_shunit_skipped_}${__shunit_ansi_none})" 932 | else 933 | _shunit_msg_="${__shunit_ansi_red}FAILED${__shunit_ansi_none}" 934 | _shunit_msg_="${_shunit_msg_} (${__shunit_ansi_red}${_shunit_failures_}${__shunit_ansi_none}" 935 | \[ -n "${_shunit_skipped_}" ] \ 936 | && _shunit_msg_="${_shunit_msg_},${__shunit_ansi_yellow}${_shunit_skipped_}${__shunit_ansi_none}" 937 | _shunit_msg_="${_shunit_msg_})" 938 | fi 939 | 940 | echo 941 | ${__SHUNIT_CMD_ECHO_ESC} "${_shunit_msg_}" 942 | __shunit_reportGenerated=${SHUNIT_TRUE} 943 | 944 | unset _shunit_failures_ _shunit_msg_ _shunit_ok_ _shunit_skipped_ 945 | } 946 | 947 | # Test for whether a function should be skipped. 948 | # 949 | # Args: 950 | # None 951 | # Returns: 952 | # boolean: whether the test should be skipped (TRUE/FALSE constant) 953 | _shunit_shouldSkip() { 954 | \[ ${__shunit_skip} -eq ${SHUNIT_FALSE} ] && return ${SHUNIT_FALSE} 955 | _shunit_assertSkip 956 | } 957 | 958 | # Records a successful test. 959 | # 960 | # Args: 961 | # None 962 | _shunit_assertPass() { 963 | __shunit_assertsPassed=`expr ${__shunit_assertsPassed} + 1` 964 | __shunit_assertsTotal=`expr ${__shunit_assertsTotal} + 1` 965 | } 966 | 967 | # Records a test failure. 968 | # 969 | # Args: 970 | # message: string: failure message to provide user 971 | _shunit_assertFail() { 972 | __shunit_testSuccess=${SHUNIT_FALSE} 973 | __shunit_assertsFailed=`expr "${__shunit_assertsFailed}" + 1` 974 | __shunit_assertsTotal=`expr "${__shunit_assertsTotal}" + 1` 975 | 976 | \[ $# -gt 0 ] && ${__SHUNIT_CMD_ECHO_ESC} \ 977 | "${__shunit_ansi_red}ASSERT:${__shunit_ansi_none}$*" 978 | } 979 | 980 | # Records a skipped test. 981 | # 982 | # Args: 983 | # None 984 | _shunit_assertSkip() { 985 | __shunit_assertsSkipped=`expr "${__shunit_assertsSkipped}" + 1` 986 | __shunit_assertsTotal=`expr "${__shunit_assertsTotal}" + 1` 987 | } 988 | 989 | # Prepare a script filename for sourcing. 990 | # 991 | # Args: 992 | # script: string: path to a script to source 993 | # Returns: 994 | # string: filename prefixed with ./ (if necessary) 995 | _shunit_prepForSourcing() { 996 | _shunit_script_=$1 997 | case "${_shunit_script_}" in 998 | /*|./*) echo "${_shunit_script_}" ;; 999 | *) echo "./${_shunit_script_}" ;; 1000 | esac 1001 | unset _shunit_script_ 1002 | } 1003 | 1004 | # Escape a character in a string. 1005 | # 1006 | # Args: 1007 | # c: string: unescaped character 1008 | # s: string: to escape character in 1009 | # Returns: 1010 | # string: with escaped character(s) 1011 | _shunit_escapeCharInStr() { 1012 | \[ -n "$2" ] || return # No point in doing work on an empty string. 1013 | 1014 | # Note: using shorter variable names to prevent conflicts with 1015 | # _shunit_escapeCharactersInString(). 1016 | _shunit_c_=$1 1017 | _shunit_s_=$2 1018 | 1019 | 1020 | # Escape the character. 1021 | # shellcheck disable=SC1003,SC2086 1022 | echo ''${_shunit_s_}'' |sed 's/\'${_shunit_c_}'/\\\'${_shunit_c_}'/g' 1023 | 1024 | unset _shunit_c_ _shunit_s_ 1025 | } 1026 | 1027 | # Escape a character in a string. 1028 | # 1029 | # Args: 1030 | # str: string: to escape characters in 1031 | # Returns: 1032 | # string: with escaped character(s) 1033 | _shunit_escapeCharactersInString() { 1034 | \[ -n "$1" ] || return # No point in doing work on an empty string. 1035 | 1036 | _shunit_str_=$1 1037 | 1038 | # Note: using longer variable names to prevent conflicts with 1039 | # _shunit_escapeCharInStr(). 1040 | for _shunit_char_ in '"' '$' "'" '`'; do 1041 | _shunit_str_=`_shunit_escapeCharInStr "${_shunit_char_}" "${_shunit_str_}"` 1042 | done 1043 | 1044 | echo "${_shunit_str_}" 1045 | unset _shunit_char_ _shunit_str_ 1046 | } 1047 | 1048 | # Extract list of functions to run tests against. 1049 | # 1050 | # Args: 1051 | # script: string: name of script to extract functions from 1052 | # Returns: 1053 | # string: of function names 1054 | _shunit_extractTestFunctions() { 1055 | _shunit_script_=$1 1056 | 1057 | # Extract the lines with test function names, strip of anything besides the 1058 | # function name, and output everything on a single line. 1059 | _shunit_regex_='^[ ]*(function )*test[A-Za-z0-9_]* *\(\)' 1060 | # shellcheck disable=SC2196 1061 | egrep "${_shunit_regex_}" "${_shunit_script_}" \ 1062 | |sed 's/^[^A-Za-z0-9_]*//;s/^function //;s/\([A-Za-z0-9_]*\).*/\1/g' \ 1063 | |xargs 1064 | 1065 | unset _shunit_regex_ _shunit_script_ 1066 | } 1067 | 1068 | #------------------------------------------------------------------------------ 1069 | # Main. 1070 | # 1071 | 1072 | # Determine the operating mode. 1073 | if \[ $# -eq 0 ]; then 1074 | __shunit_script=${__SHUNIT_PARENT} 1075 | __shunit_mode=${__SHUNIT_MODE_SOURCED} 1076 | else 1077 | __shunit_script=$1 1078 | \[ -r "${__shunit_script}" ] || \ 1079 | _shunit_fatal "unable to read from ${__shunit_script}" 1080 | __shunit_mode=${__SHUNIT_MODE_STANDALONE} 1081 | fi 1082 | 1083 | # Create a temporary storage location. 1084 | __shunit_tmpDir=`_shunit_mktempDir` 1085 | 1086 | # Provide a public temporary directory for unit test scripts. 1087 | # TODO(kward): document this. 1088 | SHUNIT_TMPDIR="${__shunit_tmpDir}/tmp" 1089 | \mkdir "${SHUNIT_TMPDIR}" 1090 | 1091 | # Setup traps to clean up after ourselves. 1092 | trap '_shunit_cleanup EXIT' 0 1093 | trap '_shunit_cleanup INT' 2 1094 | trap '_shunit_cleanup TERM' 15 1095 | 1096 | # Create phantom functions to work around issues with Cygwin. 1097 | _shunit_mktempFunc 1098 | PATH="${__shunit_tmpDir}:${PATH}" 1099 | 1100 | # Make sure phantom functions are executable. This will bite if `/tmp` (or the 1101 | # current `$TMPDIR`) points to a path on a partition that was mounted with the 1102 | # 'noexec' option. The noexec command was created with `_shunit_mktempFunc()`. 1103 | noexec 2>/dev/null || _shunit_fatal \ 1104 | 'Please declare TMPDIR with path on partition with exec permission.' 1105 | 1106 | # We must manually source the tests in standalone mode. 1107 | if \[ "${__shunit_mode}" = "${__SHUNIT_MODE_STANDALONE}" ]; then 1108 | # shellcheck disable=SC1090 1109 | . "`_shunit_prepForSourcing \"${__shunit_script}\"`" 1110 | fi 1111 | 1112 | # Configure default output coloring behavior. 1113 | _shunit_configureColor "${SHUNIT_COLOR}" 1114 | 1115 | # Execute the oneTimeSetUp function (if it exists). 1116 | oneTimeSetUp 1117 | 1118 | # Execute the suite function defined in the parent test script. 1119 | # DEPRECATED as of 2.1.0. 1120 | suite 1121 | 1122 | # If no suite function was defined, dynamically build a list of functions. 1123 | if \[ -z "${__shunit_suite}" ]; then 1124 | shunit_funcs_=`_shunit_extractTestFunctions "${__shunit_script}"` 1125 | for shunit_func_ in ${shunit_funcs_}; do 1126 | suite_addTest "${shunit_func_}" 1127 | done 1128 | fi 1129 | unset shunit_func_ shunit_funcs_ 1130 | 1131 | _shunit_execSuite 1132 | oneTimeTearDown 1133 | _shunit_generateReport 1134 | 1135 | # That's it folks. 1136 | \[ "${__shunit_testsFailed}" -eq 0 ] 1137 | exit $? 1138 | -------------------------------------------------------------------------------- /test/lib/shflags: -------------------------------------------------------------------------------- 1 | # vim:et:ft=sh:sts=2:sw=2 2 | # 3 | # Copyright 2008-2017 Kate Ward. All Rights Reserved. 4 | # Released under the Apache License 2.0 license. 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # shFlags -- Advanced command-line flag library for Unix shell scripts. 8 | # https://github.com/kward/shflags 9 | # 10 | # Author: kate.ward@forestent.com (Kate Ward) 11 | # 12 | # This module implements something like the gflags library available 13 | # from https://github.com/gflags/gflags. 14 | # 15 | # FLAG TYPES: This is a list of the DEFINE_*'s that you can do. All flags take 16 | # a name, default value, help-string, and optional 'short' name (one-letter 17 | # name). Some flags have other arguments, which are described with the flag. 18 | # 19 | # DEFINE_string: takes any input, and interprets it as a string. 20 | # 21 | # DEFINE_boolean: does not take any arguments. Say --myflag to set 22 | # FLAGS_myflag to true, or --nomyflag to set FLAGS_myflag to false. For short 23 | # flags, passing the flag on the command-line negates the default value, i.e. 24 | # if the default is true, passing the flag sets the value to false. 25 | # 26 | # DEFINE_float: takes an input and interprets it as a floating point number. As 27 | # shell does not support floats per-se, the input is merely validated as 28 | # being a valid floating point value. 29 | # 30 | # DEFINE_integer: takes an input and interprets it as an integer. 31 | # 32 | # SPECIAL FLAGS: There are a few flags that have special meaning: 33 | # --help (or -?) prints a list of all the flags in a human-readable fashion 34 | # --flagfile=foo read flags from foo. (not implemented yet) 35 | # -- as in getopt(), terminates flag-processing 36 | # 37 | # EXAMPLE USAGE: 38 | # 39 | # -- begin hello.sh -- 40 | # #! /bin/sh 41 | # . ./shflags 42 | # DEFINE_string name 'world' "somebody's name" n 43 | # FLAGS "$@" || exit $? 44 | # eval set -- "${FLAGS_ARGV}" 45 | # echo "Hello, ${FLAGS_name}." 46 | # -- end hello.sh -- 47 | # 48 | # $ ./hello.sh -n Kate 49 | # Hello, Kate. 50 | # 51 | # CUSTOMIZABLE BEHAVIOR: 52 | # 53 | # A script can override the default 'getopt' command by providing the path to 54 | # an alternate implementation by defining the FLAGS_GETOPT_CMD variable. 55 | # 56 | # NOTES: 57 | # 58 | # * Not all systems include a getopt version that supports long flags. On these 59 | # systems, only short flags are recognized. 60 | 61 | #============================================================================== 62 | # shFlags 63 | # 64 | # Shared attributes: 65 | # flags_error: last error message 66 | # flags_output: last function output (rarely valid) 67 | # flags_return: last return value 68 | # 69 | # __flags_longNames: list of long names for all flags 70 | # __flags_shortNames: list of short names for all flags 71 | # __flags_boolNames: list of boolean flag names 72 | # 73 | # __flags_opts: options parsed by getopt 74 | # 75 | # Per-flag attributes: 76 | # FLAGS_: contains value of flag named 'flag_name' 77 | # __flags__default: the default flag value 78 | # __flags__help: the flag help string 79 | # __flags__short: the flag short name 80 | # __flags__type: the flag type 81 | # 82 | # Notes: 83 | # - lists of strings are space separated, and a null value is the '~' char. 84 | # 85 | ### ShellCheck (http://www.shellcheck.net/) 86 | # $() are not fully portable (POSIX != portable). 87 | # shellcheck disable=SC2006 88 | # [ p -a q ] are well defined enough (vs [ p ] && [ q ]). 89 | # shellcheck disable=SC2166 90 | 91 | # Return if FLAGS already loaded. 92 | [ -n "${FLAGS_VERSION:-}" ] && return 0 93 | FLAGS_VERSION='1.2.3pre' 94 | 95 | # Return values that scripts can use. 96 | FLAGS_TRUE=0 97 | FLAGS_FALSE=1 98 | FLAGS_ERROR=2 99 | 100 | # Logging levels. 101 | FLAGS_LEVEL_DEBUG=0 102 | FLAGS_LEVEL_INFO=1 103 | FLAGS_LEVEL_WARN=2 104 | FLAGS_LEVEL_ERROR=3 105 | FLAGS_LEVEL_FATAL=4 106 | __FLAGS_LEVEL_DEFAULT=${FLAGS_LEVEL_WARN} 107 | 108 | # Determine some reasonable command defaults. 109 | __FLAGS_EXPR_CMD='expr --' 110 | __FLAGS_UNAME_S=`uname -s` 111 | if [ "${__FLAGS_UNAME_S}" = 'BSD' ]; then 112 | __FLAGS_EXPR_CMD='gexpr --' 113 | else 114 | _flags_output_=`${__FLAGS_EXPR_CMD} 2>&1` 115 | if [ $? -eq ${FLAGS_TRUE} -a "${_flags_output_}" = '--' ]; then 116 | # We are likely running inside BusyBox. 117 | __FLAGS_EXPR_CMD='expr' 118 | fi 119 | unset _flags_output_ 120 | fi 121 | 122 | # Commands a user can override if desired. 123 | FLAGS_EXPR_CMD=${FLAGS_EXPR_CMD:-${__FLAGS_EXPR_CMD}} 124 | FLAGS_GETOPT_CMD=${FLAGS_GETOPT_CMD:-getopt} 125 | 126 | # Specific shell checks. 127 | if [ -n "${ZSH_VERSION:-}" ]; then 128 | setopt |grep "^shwordsplit$" >/dev/null 129 | if [ $? -ne ${FLAGS_TRUE} ]; then 130 | _flags_fatal 'zsh shwordsplit option is required for proper zsh operation' 131 | fi 132 | if [ -z "${FLAGS_PARENT:-}" ]; then 133 | _flags_fatal "zsh does not pass \$0 through properly. please declare' \ 134 | \"FLAGS_PARENT=\$0\" before calling shFlags" 135 | fi 136 | fi 137 | 138 | # Can we use built-ins? 139 | ( echo "${FLAGS_TRUE#0}"; ) >/dev/null 2>&1 140 | if [ $? -eq ${FLAGS_TRUE} ]; then 141 | __FLAGS_USE_BUILTIN=${FLAGS_TRUE} 142 | else 143 | __FLAGS_USE_BUILTIN=${FLAGS_FALSE} 144 | fi 145 | 146 | 147 | # 148 | # Constants. 149 | # 150 | 151 | # Reserved flag names. 152 | __FLAGS_RESERVED_LIST=' ARGC ARGV ERROR FALSE GETOPT_CMD HELP PARENT TRUE ' 153 | __FLAGS_RESERVED_LIST="${__FLAGS_RESERVED_LIST} VERSION " 154 | 155 | # Determined getopt version (standard or enhanced). 156 | __FLAGS_GETOPT_VERS_STD=0 157 | __FLAGS_GETOPT_VERS_ENH=1 158 | 159 | # shellcheck disable=SC2120 160 | _flags_getopt_vers() { 161 | _flags_getopt_cmd_=${1:-${FLAGS_GETOPT_CMD}} 162 | case "`${_flags_getopt_cmd_} -lfoo '' --foo 2>&1`" in 163 | ' -- --foo') echo ${__FLAGS_GETOPT_VERS_STD} ;; 164 | ' --foo --') echo ${__FLAGS_GETOPT_VERS_ENH} ;; 165 | # Unrecognized output. Assuming standard getopt version. 166 | *) echo ${__FLAGS_GETOPT_VERS_STD} ;; 167 | esac 168 | unset _flags_getopt_cmd_ 169 | } 170 | # shellcheck disable=SC2119 171 | __FLAGS_GETOPT_VERS=`_flags_getopt_vers` 172 | 173 | # getopt optstring lengths 174 | __FLAGS_OPTSTR_SHORT=0 175 | __FLAGS_OPTSTR_LONG=1 176 | 177 | __FLAGS_NULL='~' 178 | 179 | # Flag info strings. 180 | __FLAGS_INFO_DEFAULT='default' 181 | __FLAGS_INFO_HELP='help' 182 | __FLAGS_INFO_SHORT='short' 183 | __FLAGS_INFO_TYPE='type' 184 | 185 | # Flag lengths. 186 | __FLAGS_LEN_SHORT=0 187 | __FLAGS_LEN_LONG=1 188 | 189 | # Flag types. 190 | __FLAGS_TYPE_NONE=0 191 | __FLAGS_TYPE_BOOLEAN=1 192 | __FLAGS_TYPE_FLOAT=2 193 | __FLAGS_TYPE_INTEGER=3 194 | __FLAGS_TYPE_STRING=4 195 | 196 | # Set the constants readonly. 197 | __flags_constants=`set |awk -F= '/^FLAGS_/ || /^__FLAGS_/ {print $1}'` 198 | for __flags_const in ${__flags_constants}; do 199 | # Skip certain flags. 200 | case ${__flags_const} in 201 | FLAGS_HELP) continue ;; 202 | FLAGS_PARENT) continue ;; 203 | esac 204 | # Set flag readonly. 205 | if [ -z "${ZSH_VERSION:-}" ]; then 206 | readonly "${__flags_const}" 207 | continue 208 | fi 209 | case ${ZSH_VERSION} in 210 | [123].*) readonly "${__flags_const}" ;; 211 | *) readonly -g "${__flags_const}" ;; # Declare readonly constants globally. 212 | esac 213 | done 214 | unset __flags_const __flags_constants 215 | 216 | # 217 | # Internal variables. 218 | # 219 | 220 | # Space separated lists. 221 | __flags_boolNames=' ' # Boolean flag names. 222 | __flags_longNames=' ' # Long flag names. 223 | __flags_shortNames=' ' # Short flag names. 224 | __flags_definedNames=' ' # Defined flag names (used for validation). 225 | 226 | __flags_columns='' # Screen width in columns. 227 | __flags_level=0 # Default logging level. 228 | __flags_opts='' # Temporary storage for parsed getopt flags. 229 | 230 | #------------------------------------------------------------------------------ 231 | # Private functions. 232 | # 233 | 234 | # Logging functions. 235 | _flags_debug() { 236 | [ ${__flags_level} -le ${FLAGS_LEVEL_DEBUG} ] || return 237 | echo "flags:DEBUG $*" >&2 238 | } 239 | _flags_info() { 240 | [ ${__flags_level} -le ${FLAGS_LEVEL_INFO} ] || return 241 | echo "flags:INFO $*" >&2 242 | } 243 | _flags_warn() { 244 | [ ${__flags_level} -le ${FLAGS_LEVEL_WARN} ] || return 245 | echo "flags:WARN $*" >&2 246 | } 247 | _flags_error() { 248 | [ ${__flags_level} -le ${FLAGS_LEVEL_ERROR} ] || return 249 | echo "flags:ERROR $*" >&2 250 | } 251 | _flags_fatal() { 252 | [ ${__flags_level} -le ${FLAGS_LEVEL_FATAL} ] || return 253 | echo "flags:FATAL $*" >&2 254 | exit ${FLAGS_ERROR} 255 | } 256 | 257 | # Get the logging level. 258 | flags_loggingLevel() { echo ${__flags_level}; } 259 | 260 | # Set the logging level. 261 | # 262 | # Args: 263 | # _flags_level_: integer: new logging level 264 | # Returns: 265 | # nothing 266 | flags_setLoggingLevel() { 267 | [ $# -ne 1 ] && _flags_fatal "flags_setLevel(): logging level missing" 268 | _flags_level_=$1 269 | [ "${_flags_level_}" -ge "${FLAGS_LEVEL_DEBUG}" \ 270 | -a "${_flags_level_}" -le "${FLAGS_LEVEL_FATAL}" ] \ 271 | || _flags_fatal "Invalid logging level '${_flags_level_}' specified." 272 | __flags_level=$1 273 | unset _flags_level_ 274 | } 275 | 276 | # Define a flag. 277 | # 278 | # Calling this function will define the following info variables for the 279 | # specified flag: 280 | # FLAGS_flagname - the name for this flag (based upon the long flag name) 281 | # __flags__default - the default value 282 | # __flags_flagname_help - the help string 283 | # __flags_flagname_short - the single letter alias 284 | # __flags_flagname_type - the type of flag (one of __FLAGS_TYPE_*) 285 | # 286 | # Args: 287 | # _flags_type_: integer: internal type of flag (__FLAGS_TYPE_*) 288 | # _flags_name_: string: long flag name 289 | # _flags_default_: default flag value 290 | # _flags_help_: string: help string 291 | # _flags_short_: string: (optional) short flag name 292 | # Returns: 293 | # integer: success of operation, or error 294 | _flags_define() { 295 | if [ $# -lt 4 ]; then 296 | flags_error='DEFINE error: too few arguments' 297 | flags_return=${FLAGS_ERROR} 298 | _flags_error "${flags_error}" 299 | return ${flags_return} 300 | fi 301 | 302 | _flags_type_=$1 303 | _flags_name_=$2 304 | _flags_default_=$3 305 | _flags_help_=${4:-§} # Special value '§' indicates no help string provided. 306 | _flags_short_=${5:-${__FLAGS_NULL}} 307 | 308 | _flags_debug "type:${_flags_type_} name:${_flags_name_}" \ 309 | "default:'${_flags_default_}' help:'${_flags_help_}'" \ 310 | "short:${_flags_short_}" 311 | 312 | _flags_return_=${FLAGS_TRUE} 313 | _flags_usName_="`_flags_underscoreName "${_flags_name_}"`" 314 | 315 | # Check whether the flag name is reserved. 316 | _flags_itemInList "${_flags_usName_}" "${__FLAGS_RESERVED_LIST}" 317 | if [ $? -eq ${FLAGS_TRUE} ]; then 318 | flags_error="flag name (${_flags_name_}) is reserved" 319 | _flags_return_=${FLAGS_ERROR} 320 | fi 321 | 322 | # Require short option for getopt that don't support long options. 323 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} \ 324 | -a "${__FLAGS_GETOPT_VERS}" -ne "${__FLAGS_GETOPT_VERS_ENH}" \ 325 | -a "${_flags_short_}" = "${__FLAGS_NULL}" ] 326 | then 327 | flags_error="short flag required for (${_flags_name_}) on this platform" 328 | _flags_return_=${FLAGS_ERROR} 329 | fi 330 | 331 | # Check for existing long name definition. 332 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then 333 | if _flags_itemInList "${_flags_usName_}" "${__flags_definedNames}"; then 334 | flags_error="definition for ([no]${_flags_name_}) already exists" 335 | _flags_warn "${flags_error}" 336 | _flags_return_=${FLAGS_FALSE} 337 | fi 338 | fi 339 | 340 | # Check for existing short name definition. 341 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} \ 342 | -a "${_flags_short_}" != "${__FLAGS_NULL}" ] 343 | then 344 | if _flags_itemInList "${_flags_short_}" "${__flags_shortNames}"; then 345 | flags_error="flag short name (${_flags_short_}) already defined" 346 | _flags_warn "${flags_error}" 347 | _flags_return_=${FLAGS_FALSE} 348 | fi 349 | fi 350 | 351 | # Handle default value. Note, on several occasions the 'if' portion of an 352 | # if/then/else contains just a ':' which does nothing. A binary reversal via 353 | # '!' is not done because it does not work on all shells. 354 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then 355 | case ${_flags_type_} in 356 | ${__FLAGS_TYPE_BOOLEAN}) 357 | if _flags_validBool "${_flags_default_}"; then 358 | case ${_flags_default_} in 359 | true|t|0) _flags_default_=${FLAGS_TRUE} ;; 360 | false|f|1) _flags_default_=${FLAGS_FALSE} ;; 361 | esac 362 | else 363 | flags_error="invalid default flag value '${_flags_default_}'" 364 | _flags_return_=${FLAGS_ERROR} 365 | fi 366 | ;; 367 | 368 | ${__FLAGS_TYPE_FLOAT}) 369 | if _flags_validFloat "${_flags_default_}"; then 370 | : 371 | else 372 | flags_error="invalid default flag value '${_flags_default_}'" 373 | _flags_return_=${FLAGS_ERROR} 374 | fi 375 | ;; 376 | 377 | ${__FLAGS_TYPE_INTEGER}) 378 | if _flags_validInt "${_flags_default_}"; then 379 | : 380 | else 381 | flags_error="invalid default flag value '${_flags_default_}'" 382 | _flags_return_=${FLAGS_ERROR} 383 | fi 384 | ;; 385 | 386 | ${__FLAGS_TYPE_STRING}) ;; # Everything in shell is a valid string. 387 | 388 | *) 389 | flags_error="unrecognized flag type '${_flags_type_}'" 390 | _flags_return_=${FLAGS_ERROR} 391 | ;; 392 | esac 393 | fi 394 | 395 | if [ ${_flags_return_} -eq ${FLAGS_TRUE} ]; then 396 | # Store flag information. 397 | eval "FLAGS_${_flags_usName_}='${_flags_default_}'" 398 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_TYPE}=${_flags_type_}" 399 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}=\ 400 | \"${_flags_default_}\"" 401 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_HELP}=\"${_flags_help_}\"" 402 | eval "__flags_${_flags_usName_}_${__FLAGS_INFO_SHORT}='${_flags_short_}'" 403 | 404 | # append flag names to name lists 405 | __flags_shortNames="${__flags_shortNames}${_flags_short_} " 406 | __flags_longNames="${__flags_longNames}${_flags_name_} " 407 | [ "${_flags_type_}" -eq "${__FLAGS_TYPE_BOOLEAN}" ] && \ 408 | __flags_boolNames="${__flags_boolNames}no${_flags_name_} " 409 | 410 | # Append flag names to defined names for later validation checks. 411 | __flags_definedNames="${__flags_definedNames}${_flags_usName_} " 412 | [ "${_flags_type_}" -eq "${__FLAGS_TYPE_BOOLEAN}" ] && \ 413 | __flags_definedNames="${__flags_definedNames}no${_flags_usName_} " 414 | fi 415 | 416 | flags_return=${_flags_return_} 417 | unset _flags_default_ _flags_help_ _flags_name_ _flags_return_ \ 418 | _flags_short_ _flags_type_ _flags_usName_ 419 | [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}" 420 | return ${flags_return} 421 | } 422 | 423 | # Underscore a flag name by replacing dashes with underscores. 424 | # 425 | # Args: 426 | # unnamed: string: log flag name 427 | # Output: 428 | # string: underscored name 429 | _flags_underscoreName() { 430 | echo "$1" |tr '-' '_' 431 | } 432 | 433 | # Return valid getopt options using currently defined list of long options. 434 | # 435 | # This function builds a proper getopt option string for short (and long) 436 | # options, using the current list of long options for reference. 437 | # 438 | # Args: 439 | # _flags_optStr: integer: option string type (__FLAGS_OPTSTR_*) 440 | # Output: 441 | # string: generated option string for getopt 442 | # Returns: 443 | # boolean: success of operation (always returns True) 444 | _flags_genOptStr() { 445 | _flags_optStrType_=$1 446 | 447 | _flags_opts_='' 448 | 449 | for _flags_name_ in ${__flags_longNames}; do 450 | _flags_usName_="`_flags_underscoreName "${_flags_name_}"`" 451 | _flags_type_="`_flags_getFlagInfo "${_flags_usName_}" "${__FLAGS_INFO_TYPE}"`" 452 | [ $? -eq ${FLAGS_TRUE} ] || _flags_fatal 'call to _flags_type_ failed' 453 | case ${_flags_optStrType_} in 454 | ${__FLAGS_OPTSTR_SHORT}) 455 | _flags_shortName_="`_flags_getFlagInfo \ 456 | "${_flags_usName_}" "${__FLAGS_INFO_SHORT}"`" 457 | if [ "${_flags_shortName_}" != "${__FLAGS_NULL}" ]; then 458 | _flags_opts_="${_flags_opts_}${_flags_shortName_}" 459 | # getopt needs a trailing ':' to indicate a required argument. 460 | [ "${_flags_type_}" -ne "${__FLAGS_TYPE_BOOLEAN}" ] && \ 461 | _flags_opts_="${_flags_opts_}:" 462 | fi 463 | ;; 464 | 465 | ${__FLAGS_OPTSTR_LONG}) 466 | _flags_opts_="${_flags_opts_:+${_flags_opts_},}${_flags_name_}" 467 | # getopt needs a trailing ':' to indicate a required argument 468 | [ "${_flags_type_}" -ne "${__FLAGS_TYPE_BOOLEAN}" ] && \ 469 | _flags_opts_="${_flags_opts_}:" 470 | ;; 471 | esac 472 | done 473 | 474 | echo "${_flags_opts_}" 475 | unset _flags_name_ _flags_opts_ _flags_optStrType_ _flags_shortName_ \ 476 | _flags_type_ _flags_usName_ 477 | return ${FLAGS_TRUE} 478 | } 479 | 480 | # Returns flag details based on a flag name and flag info. 481 | # 482 | # Args: 483 | # string: underscored flag name 484 | # string: flag info (see the _flags_define function for valid info types) 485 | # Output: 486 | # string: value of dereferenced flag variable 487 | # Returns: 488 | # integer: one of FLAGS_{TRUE|FALSE|ERROR} 489 | _flags_getFlagInfo() { 490 | # Note: adding gFI to variable names to prevent naming conflicts with calling 491 | # functions 492 | _flags_gFI_usName_=$1 493 | _flags_gFI_info_=$2 494 | 495 | # Example: given argument usName (underscored flag name) of 'my_flag', and 496 | # argument info of 'help', set the _flags_infoValue_ variable to the value of 497 | # ${__flags_my_flag_help}, and see if it is non-empty. 498 | _flags_infoVar_="__flags_${_flags_gFI_usName_}_${_flags_gFI_info_}" 499 | _flags_strToEval_="_flags_infoValue_=\"\${${_flags_infoVar_}:-}\"" 500 | eval "${_flags_strToEval_}" 501 | if [ -n "${_flags_infoValue_}" ]; then 502 | # Special value '§' indicates no help string provided. 503 | [ "${_flags_gFI_info_}" = ${__FLAGS_INFO_HELP} \ 504 | -a "${_flags_infoValue_}" = '§' ] && _flags_infoValue_='' 505 | flags_return=${FLAGS_TRUE} 506 | else 507 | # See if the _flags_gFI_usName_ variable is a string as strings can be 508 | # empty... 509 | # Note: the DRY principle would say to have this function call itself for 510 | # the next three lines, but doing so results in an infinite loop as an 511 | # invalid _flags_name_ will also not have the associated _type variable. 512 | # Because it doesn't (it will evaluate to an empty string) the logic will 513 | # try to find the _type variable of the _type variable, and so on. Not so 514 | # good ;-) 515 | # 516 | # Example cont.: set the _flags_typeValue_ variable to the value of 517 | # ${__flags_my_flag_type}, and see if it equals '4'. 518 | _flags_typeVar_="__flags_${_flags_gFI_usName_}_${__FLAGS_INFO_TYPE}" 519 | _flags_strToEval_="_flags_typeValue_=\"\${${_flags_typeVar_}:-}\"" 520 | eval "${_flags_strToEval_}" 521 | # shellcheck disable=SC2154 522 | if [ "${_flags_typeValue_}" = "${__FLAGS_TYPE_STRING}" ]; then 523 | flags_return=${FLAGS_TRUE} 524 | else 525 | flags_return=${FLAGS_ERROR} 526 | flags_error="missing flag info variable (${_flags_infoVar_})" 527 | fi 528 | fi 529 | 530 | echo "${_flags_infoValue_}" 531 | unset _flags_gFI_usName_ _flags_gfI_info_ _flags_infoValue_ _flags_infoVar_ \ 532 | _flags_strToEval_ _flags_typeValue_ _flags_typeVar_ 533 | [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_error "${flags_error}" 534 | return ${flags_return} 535 | } 536 | 537 | # Check for presence of item in a list. 538 | # 539 | # Passed a string (e.g. 'abc'), this function will determine if the string is 540 | # present in the list of strings (e.g. ' foo bar abc '). 541 | # 542 | # Args: 543 | # _flags_str_: string: string to search for in a list of strings 544 | # unnamed: list: list of strings 545 | # Returns: 546 | # boolean: true if item is in the list 547 | _flags_itemInList() { 548 | _flags_str_=$1 549 | shift 550 | 551 | case " ${*:-} " in 552 | *\ ${_flags_str_}\ *) flags_return=${FLAGS_TRUE} ;; 553 | *) flags_return=${FLAGS_FALSE} ;; 554 | esac 555 | 556 | unset _flags_str_ 557 | return ${flags_return} 558 | } 559 | 560 | # Returns the width of the current screen. 561 | # 562 | # Output: 563 | # integer: width in columns of the current screen. 564 | _flags_columns() { 565 | if [ -z "${__flags_columns}" ]; then 566 | if eval stty size >/dev/null 2>&1; then 567 | # stty size worked :-) 568 | # shellcheck disable=SC2046 569 | set -- `stty size` 570 | __flags_columns="${2:-}" 571 | fi 572 | fi 573 | if [ -z "${__flags_columns}" ]; then 574 | if eval tput cols >/dev/null 2>&1; then 575 | # shellcheck disable=SC2046 576 | set -- `tput cols` 577 | __flags_columns="${1:-}" 578 | fi 579 | fi 580 | echo "${__flags_columns:-80}" 581 | } 582 | 583 | # Validate a boolean. 584 | # 585 | # Args: 586 | # _flags__bool: boolean: value to validate 587 | # Returns: 588 | # bool: true if the value is a valid boolean 589 | _flags_validBool() { 590 | _flags_bool_=$1 591 | 592 | flags_return=${FLAGS_TRUE} 593 | case "${_flags_bool_}" in 594 | true|t|0) ;; 595 | false|f|1) ;; 596 | *) flags_return=${FLAGS_FALSE} ;; 597 | esac 598 | 599 | unset _flags_bool_ 600 | return ${flags_return} 601 | } 602 | 603 | # Validate a float. 604 | # 605 | # Args: 606 | # _flags_float_: float: value to validate 607 | # Returns: 608 | # bool: true if the value is a valid integer 609 | _flags_validFloat() { 610 | flags_return=${FLAGS_FALSE} 611 | [ -n "$1" ] || return ${flags_return} 612 | _flags_float_=$1 613 | 614 | if _flags_validInt "${_flags_float_}"; then 615 | flags_return=${FLAGS_TRUE} 616 | elif _flags_useBuiltin; then 617 | _flags_float_whole_=${_flags_float_%.*} 618 | _flags_float_fraction_=${_flags_float_#*.} 619 | if _flags_validInt "${_flags_float_whole_:-0}" -a \ 620 | _flags_validInt "${_flags_float_fraction_}"; then 621 | flags_return=${FLAGS_TRUE} 622 | fi 623 | unset _flags_float_whole_ _flags_float_fraction_ 624 | else 625 | flags_return=${FLAGS_TRUE} 626 | case ${_flags_float_} in 627 | -*) # Negative floats. 628 | _flags_test_=`${FLAGS_EXPR_CMD} "${_flags_float_}" :\ 629 | '\(-[0-9]*\.[0-9]*\)'` 630 | ;; 631 | *) # Positive floats. 632 | _flags_test_=`${FLAGS_EXPR_CMD} "${_flags_float_}" :\ 633 | '\([0-9]*\.[0-9]*\)'` 634 | ;; 635 | esac 636 | [ "${_flags_test_}" != "${_flags_float_}" ] && flags_return=${FLAGS_FALSE} 637 | unset _flags_test_ 638 | fi 639 | 640 | unset _flags_float_ _flags_float_whole_ _flags_float_fraction_ 641 | return ${flags_return} 642 | } 643 | 644 | # Validate an integer. 645 | # 646 | # Args: 647 | # _flags_int_: integer: value to validate 648 | # Returns: 649 | # bool: true if the value is a valid integer 650 | _flags_validInt() { 651 | flags_return=${FLAGS_FALSE} 652 | [ -n "$1" ] || return ${flags_return} 653 | _flags_int_=$1 654 | 655 | case ${_flags_int_} in 656 | -*.*) ;; # Ignore negative floats (we'll invalidate them later). 657 | -*) # Strip possible leading negative sign. 658 | if _flags_useBuiltin; then 659 | _flags_int_=${_flags_int_#-} 660 | else 661 | _flags_int_=`${FLAGS_EXPR_CMD} "${_flags_int_}" : '-\([0-9][0-9]*\)'` 662 | fi 663 | ;; 664 | esac 665 | 666 | case ${_flags_int_} in 667 | *[!0-9]*) flags_return=${FLAGS_FALSE} ;; 668 | *) flags_return=${FLAGS_TRUE} ;; 669 | esac 670 | 671 | unset _flags_int_ 672 | return ${flags_return} 673 | } 674 | 675 | # Parse command-line options using the standard getopt. 676 | # 677 | # Note: the flag options are passed around in the global __flags_opts so that 678 | # the formatting is not lost due to shell parsing and such. 679 | # 680 | # Args: 681 | # @: varies: command-line options to parse 682 | # Returns: 683 | # integer: a FLAGS success condition 684 | _flags_getoptStandard() { 685 | flags_return=${FLAGS_TRUE} 686 | _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` 687 | 688 | # Check for spaces in passed options. 689 | for _flags_opt_ in "$@"; do 690 | # Note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06. 691 | _flags_match_=`echo "x${_flags_opt_}x" |sed 's/ //g'` 692 | if [ "${_flags_match_}" != "x${_flags_opt_}x" ]; then 693 | flags_error='the available getopt does not support spaces in options' 694 | flags_return=${FLAGS_ERROR} 695 | break 696 | fi 697 | done 698 | 699 | if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then 700 | __flags_opts=`getopt "${_flags_shortOpts_}" "$@" 2>&1` 701 | _flags_rtrn_=$? 702 | if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then 703 | _flags_warn "${__flags_opts}" 704 | flags_error='unable to parse provided options with getopt.' 705 | flags_return=${FLAGS_ERROR} 706 | fi 707 | fi 708 | 709 | unset _flags_match_ _flags_opt_ _flags_rtrn_ _flags_shortOpts_ 710 | return ${flags_return} 711 | } 712 | 713 | # Parse command-line options using the enhanced getopt. 714 | # 715 | # Note: the flag options are passed around in the global __flags_opts so that 716 | # the formatting is not lost due to shell parsing and such. 717 | # 718 | # Args: 719 | # @: varies: command-line options to parse 720 | # Returns: 721 | # integer: a FLAGS success condition 722 | _flags_getoptEnhanced() { 723 | flags_return=${FLAGS_TRUE} 724 | _flags_shortOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_SHORT}` 725 | _flags_boolOpts_=`echo "${__flags_boolNames}" \ 726 | |sed 's/^ *//;s/ *$//;s/ /,/g'` 727 | _flags_longOpts_=`_flags_genOptStr ${__FLAGS_OPTSTR_LONG}` 728 | 729 | __flags_opts=`${FLAGS_GETOPT_CMD} \ 730 | -o "${_flags_shortOpts_}" \ 731 | -l "${_flags_longOpts_},${_flags_boolOpts_}" \ 732 | -- "$@" 2>&1` 733 | _flags_rtrn_=$? 734 | if [ ${_flags_rtrn_} -ne ${FLAGS_TRUE} ]; then 735 | _flags_warn "${__flags_opts}" 736 | flags_error='unable to parse provided options with getopt.' 737 | flags_return=${FLAGS_ERROR} 738 | fi 739 | 740 | unset _flags_boolOpts_ _flags_longOpts_ _flags_rtrn_ _flags_shortOpts_ 741 | return ${flags_return} 742 | } 743 | 744 | # Dynamically parse a getopt result and set appropriate variables. 745 | # 746 | # This function does the actual conversion of getopt output and runs it through 747 | # the standard case structure for parsing. The case structure is actually quite 748 | # dynamic to support any number of flags. 749 | # 750 | # Args: 751 | # argc: int: original command-line argument count 752 | # @: varies: output from getopt parsing 753 | # Returns: 754 | # integer: a FLAGS success condition 755 | _flags_parseGetopt() { 756 | _flags_argc_=$1 757 | shift 758 | 759 | flags_return=${FLAGS_TRUE} 760 | 761 | if [ "${__FLAGS_GETOPT_VERS}" -ne "${__FLAGS_GETOPT_VERS_ENH}" ]; then 762 | # The @$ must be unquoted as it needs to be re-split. 763 | # shellcheck disable=SC2068 764 | set -- $@ 765 | else 766 | # Note the quotes around the `$@' -- they are essential! 767 | eval set -- "$@" 768 | fi 769 | 770 | # Provide user with the number of arguments to shift by later. 771 | # NOTE: the FLAGS_ARGC variable is obsolete as of 1.0.3 because it does not 772 | # properly give user access to non-flag arguments mixed in between flag 773 | # arguments. Its usage was replaced by FLAGS_ARGV, and it is being kept only 774 | # for backwards compatibility reasons. 775 | FLAGS_ARGC=`_flags_math "$# - 1 - ${_flags_argc_}"` 776 | export FLAGS_ARGC 777 | 778 | # Handle options. note options with values must do an additional shift. 779 | while true; do 780 | _flags_opt_=$1 781 | _flags_arg_=${2:-} 782 | _flags_type_=${__FLAGS_TYPE_NONE} 783 | _flags_name_='' 784 | 785 | # Determine long flag name. 786 | case "${_flags_opt_}" in 787 | --) shift; break ;; # Discontinue option parsing. 788 | 789 | --*) # Long option. 790 | if _flags_useBuiltin; then 791 | _flags_opt_=${_flags_opt_#*--} 792 | else 793 | _flags_opt_=`${FLAGS_EXPR_CMD} "${_flags_opt_}" : '--\(.*\)'` 794 | fi 795 | _flags_len_=${__FLAGS_LEN_LONG} 796 | if _flags_itemInList "${_flags_opt_}" "${__flags_longNames}"; then 797 | _flags_name_=${_flags_opt_} 798 | else 799 | # Check for negated long boolean version. 800 | if _flags_itemInList "${_flags_opt_}" "${__flags_boolNames}"; then 801 | if _flags_useBuiltin; then 802 | _flags_name_=${_flags_opt_#*no} 803 | else 804 | _flags_name_=`${FLAGS_EXPR_CMD} "${_flags_opt_}" : 'no\(.*\)'` 805 | fi 806 | _flags_type_=${__FLAGS_TYPE_BOOLEAN} 807 | _flags_arg_=${__FLAGS_NULL} 808 | fi 809 | fi 810 | ;; 811 | 812 | -*) # Short option. 813 | if _flags_useBuiltin; then 814 | _flags_opt_=${_flags_opt_#*-} 815 | else 816 | _flags_opt_=`${FLAGS_EXPR_CMD} "${_flags_opt_}" : '-\(.*\)'` 817 | fi 818 | _flags_len_=${__FLAGS_LEN_SHORT} 819 | if _flags_itemInList "${_flags_opt_}" "${__flags_shortNames}"; then 820 | # Yes. Match short name to long name. Note purposeful off-by-one 821 | # (too high) with awk calculations. 822 | _flags_pos_=`echo "${__flags_shortNames}" \ 823 | |awk 'BEGIN{RS=" ";rn=0}$0==e{rn=NR}END{print rn}' \ 824 | e="${_flags_opt_}"` 825 | _flags_name_=`echo "${__flags_longNames}" \ 826 | |awk 'BEGIN{RS=" "}rn==NR{print $0}' rn="${_flags_pos_}"` 827 | fi 828 | ;; 829 | esac 830 | 831 | # Die if the flag was unrecognized. 832 | if [ -z "${_flags_name_}" ]; then 833 | flags_error="unrecognized option (${_flags_opt_})" 834 | flags_return=${FLAGS_ERROR} 835 | break 836 | fi 837 | 838 | # Set new flag value. 839 | _flags_usName_=`_flags_underscoreName "${_flags_name_}"` 840 | [ ${_flags_type_} -eq ${__FLAGS_TYPE_NONE} ] && \ 841 | _flags_type_=`_flags_getFlagInfo \ 842 | "${_flags_usName_}" ${__FLAGS_INFO_TYPE}` 843 | case ${_flags_type_} in 844 | ${__FLAGS_TYPE_BOOLEAN}) 845 | if [ ${_flags_len_} -eq ${__FLAGS_LEN_LONG} ]; then 846 | if [ "${_flags_arg_}" != "${__FLAGS_NULL}" ]; then 847 | eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" 848 | else 849 | eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}" 850 | fi 851 | else 852 | _flags_strToEval_="_flags_val_=\ 853 | \${__flags_${_flags_usName_}_${__FLAGS_INFO_DEFAULT}}" 854 | eval "${_flags_strToEval_}" 855 | # shellcheck disable=SC2154 856 | if [ "${_flags_val_}" -eq ${FLAGS_FALSE} ]; then 857 | eval "FLAGS_${_flags_usName_}=${FLAGS_TRUE}" 858 | else 859 | eval "FLAGS_${_flags_usName_}=${FLAGS_FALSE}" 860 | fi 861 | fi 862 | ;; 863 | 864 | ${__FLAGS_TYPE_FLOAT}) 865 | if _flags_validFloat "${_flags_arg_}"; then 866 | eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" 867 | else 868 | flags_error="invalid float value (${_flags_arg_})" 869 | flags_return=${FLAGS_ERROR} 870 | break 871 | fi 872 | ;; 873 | 874 | ${__FLAGS_TYPE_INTEGER}) 875 | if _flags_validInt "${_flags_arg_}"; then 876 | eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" 877 | else 878 | flags_error="invalid integer value (${_flags_arg_})" 879 | flags_return=${FLAGS_ERROR} 880 | break 881 | fi 882 | ;; 883 | 884 | ${__FLAGS_TYPE_STRING}) 885 | eval "FLAGS_${_flags_usName_}='${_flags_arg_}'" 886 | ;; 887 | esac 888 | 889 | # Handle special case help flag. 890 | if [ "${_flags_usName_}" = 'help' ]; then 891 | # shellcheck disable=SC2154 892 | if [ "${FLAGS_help}" -eq ${FLAGS_TRUE} ]; then 893 | flags_help 894 | flags_error='help requested' 895 | flags_return=${FLAGS_FALSE} 896 | break 897 | fi 898 | fi 899 | 900 | # Shift the option and non-boolean arguments out. 901 | shift 902 | [ "${_flags_type_}" != ${__FLAGS_TYPE_BOOLEAN} ] && shift 903 | done 904 | 905 | # Give user back non-flag arguments. 906 | FLAGS_ARGV='' 907 | while [ $# -gt 0 ]; do 908 | FLAGS_ARGV="${FLAGS_ARGV:+${FLAGS_ARGV} }'$1'" 909 | shift 910 | done 911 | 912 | unset _flags_arg_ _flags_len_ _flags_name_ _flags_opt_ _flags_pos_ \ 913 | _flags_strToEval_ _flags_type_ _flags_usName_ _flags_val_ 914 | return ${flags_return} 915 | } 916 | 917 | # Perform some path using built-ins. 918 | # 919 | # Args: 920 | # $@: string: math expression to evaluate 921 | # Output: 922 | # integer: the result 923 | # Returns: 924 | # bool: success of math evaluation 925 | _flags_math() { 926 | if [ $# -eq 0 ]; then 927 | flags_return=${FLAGS_FALSE} 928 | elif _flags_useBuiltin; then 929 | # Variable assignment is needed as workaround for Solaris Bourne shell, 930 | # which cannot parse a bare $((expression)). 931 | # shellcheck disable=SC2016 932 | _flags_expr_='$(($@))' 933 | eval echo ${_flags_expr_} 934 | flags_return=$? 935 | unset _flags_expr_ 936 | else 937 | eval expr "$@" 938 | flags_return=$? 939 | fi 940 | 941 | return ${flags_return} 942 | } 943 | 944 | # Cross-platform strlen() implementation. 945 | # 946 | # Args: 947 | # _flags_str: string: to determine length of 948 | # Output: 949 | # integer: length of string 950 | # Returns: 951 | # bool: success of strlen evaluation 952 | _flags_strlen() { 953 | _flags_str_=${1:-} 954 | 955 | if [ -z "${_flags_str_}" ]; then 956 | flags_output=0 957 | elif _flags_useBuiltin; then 958 | flags_output=${#_flags_str_} 959 | else 960 | flags_output=`${FLAGS_EXPR_CMD} "${_flags_str_}" : '.*'` 961 | fi 962 | flags_return=$? 963 | 964 | unset _flags_str_ 965 | echo "${flags_output}" 966 | return ${flags_return} 967 | } 968 | 969 | # Use built-in helper function to enable unit testing. 970 | # 971 | # Args: 972 | # None 973 | # Returns: 974 | # bool: true if built-ins should be used 975 | _flags_useBuiltin() { return ${__FLAGS_USE_BUILTIN}; } 976 | 977 | #------------------------------------------------------------------------------ 978 | # public functions 979 | # 980 | # A basic boolean flag. Boolean flags do not take any arguments, and their 981 | # value is either 1 (false) or 0 (true). For long flags, the false value is 982 | # specified on the command line by prepending the word 'no'. With short flags, 983 | # the presence of the flag toggles the current value between true and false. 984 | # Specifying a short boolean flag twice on the command results in returning the 985 | # value back to the default value. 986 | # 987 | # A default value is required for boolean flags. 988 | # 989 | # For example, lets say a Boolean flag was created whose long name was 'update' 990 | # and whose short name was 'x', and the default value was 'false'. This flag 991 | # could be explicitly set to 'true' with '--update' or by '-x', and it could be 992 | # explicitly set to 'false' with '--noupdate'. 993 | DEFINE_boolean() { _flags_define ${__FLAGS_TYPE_BOOLEAN} "$@"; } 994 | 995 | # Other basic flags. 996 | DEFINE_float() { _flags_define ${__FLAGS_TYPE_FLOAT} "$@"; } 997 | DEFINE_integer() { _flags_define ${__FLAGS_TYPE_INTEGER} "$@"; } 998 | DEFINE_string() { _flags_define ${__FLAGS_TYPE_STRING} "$@"; } 999 | 1000 | # Parse the flags. 1001 | # 1002 | # Args: 1003 | # unnamed: list: command-line flags to parse 1004 | # Returns: 1005 | # integer: success of operation, or error 1006 | FLAGS() { 1007 | # Define a standard 'help' flag if one isn't already defined. 1008 | [ -z "${__flags_help_type:-}" ] && \ 1009 | DEFINE_boolean 'help' false 'show this help' 'h' 1010 | 1011 | # Parse options. 1012 | if [ $# -gt 0 ]; then 1013 | if [ "${__FLAGS_GETOPT_VERS}" -ne "${__FLAGS_GETOPT_VERS_ENH}" ]; then 1014 | _flags_getoptStandard "$@" 1015 | else 1016 | _flags_getoptEnhanced "$@" 1017 | fi 1018 | flags_return=$? 1019 | else 1020 | # Nothing passed; won't bother running getopt. 1021 | __flags_opts='--' 1022 | flags_return=${FLAGS_TRUE} 1023 | fi 1024 | 1025 | if [ ${flags_return} -eq ${FLAGS_TRUE} ]; then 1026 | _flags_parseGetopt $# "${__flags_opts}" 1027 | flags_return=$? 1028 | fi 1029 | 1030 | [ ${flags_return} -eq ${FLAGS_ERROR} ] && _flags_fatal "${flags_error}" 1031 | return ${flags_return} 1032 | } 1033 | 1034 | # This is a helper function for determining the 'getopt' version for platforms 1035 | # where the detection isn't working. It simply outputs debug information that 1036 | # can be included in a bug report. 1037 | # 1038 | # Args: 1039 | # none 1040 | # Output: 1041 | # debug info that can be included in a bug report 1042 | # Returns: 1043 | # nothing 1044 | flags_getoptInfo() { 1045 | # Platform info. 1046 | _flags_debug "uname -a: `uname -a`" 1047 | _flags_debug "PATH: ${PATH}" 1048 | 1049 | # Shell info. 1050 | if [ -n "${BASH_VERSION:-}" ]; then 1051 | _flags_debug 'shell: bash' 1052 | _flags_debug "BASH_VERSION: ${BASH_VERSION}" 1053 | elif [ -n "${ZSH_VERSION:-}" ]; then 1054 | _flags_debug 'shell: zsh' 1055 | _flags_debug "ZSH_VERSION: ${ZSH_VERSION}" 1056 | fi 1057 | 1058 | # getopt info. 1059 | ${FLAGS_GETOPT_CMD} >/dev/null 1060 | _flags_getoptReturn=$? 1061 | _flags_debug "getopt return: ${_flags_getoptReturn}" 1062 | _flags_debug "getopt --version: `${FLAGS_GETOPT_CMD} --version 2>&1`" 1063 | 1064 | unset _flags_getoptReturn 1065 | } 1066 | 1067 | # Returns whether the detected getopt version is the enhanced version. 1068 | # 1069 | # Args: 1070 | # none 1071 | # Output: 1072 | # none 1073 | # Returns: 1074 | # bool: true if getopt is the enhanced version 1075 | flags_getoptIsEnh() { 1076 | test "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_ENH}" 1077 | } 1078 | 1079 | # Returns whether the detected getopt version is the standard version. 1080 | # 1081 | # Args: 1082 | # none 1083 | # Returns: 1084 | # bool: true if getopt is the standard version 1085 | flags_getoptIsStd() { 1086 | test "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_STD}" 1087 | } 1088 | 1089 | # This is effectively a 'usage()' function. It prints usage information and 1090 | # exits the program with ${FLAGS_FALSE} if it is ever found in the command line 1091 | # arguments. Note this function can be overridden so other apps can define 1092 | # their own --help flag, replacing this one, if they want. 1093 | # 1094 | # Args: 1095 | # none 1096 | # Returns: 1097 | # integer: success of operation (always returns true) 1098 | flags_help() { 1099 | if [ -n "${FLAGS_HELP:-}" ]; then 1100 | echo "${FLAGS_HELP}" >&2 1101 | else 1102 | echo "USAGE: ${FLAGS_PARENT:-$0} [flags] args" >&2 1103 | fi 1104 | if [ -n "${__flags_longNames}" ]; then 1105 | echo 'flags:' >&2 1106 | for flags_name_ in ${__flags_longNames}; do 1107 | flags_flagStr_='' 1108 | flags_boolStr_='' 1109 | flags_usName_=`_flags_underscoreName "${flags_name_}"` 1110 | 1111 | flags_default_=`_flags_getFlagInfo \ 1112 | "${flags_usName_}" ${__FLAGS_INFO_DEFAULT}` 1113 | flags_help_=`_flags_getFlagInfo \ 1114 | "${flags_usName_}" ${__FLAGS_INFO_HELP}` 1115 | flags_short_=`_flags_getFlagInfo \ 1116 | "${flags_usName_}" ${__FLAGS_INFO_SHORT}` 1117 | flags_type_=`_flags_getFlagInfo \ 1118 | "${flags_usName_}" ${__FLAGS_INFO_TYPE}` 1119 | 1120 | [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ 1121 | flags_flagStr_="-${flags_short_}" 1122 | 1123 | if [ "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_ENH}" ]; then 1124 | [ "${flags_short_}" != "${__FLAGS_NULL}" ] && \ 1125 | flags_flagStr_="${flags_flagStr_}," 1126 | # Add [no] to long boolean flag names, except the 'help' flag. 1127 | [ "${flags_type_}" -eq ${__FLAGS_TYPE_BOOLEAN} \ 1128 | -a "${flags_usName_}" != 'help' ] && \ 1129 | flags_boolStr_='[no]' 1130 | flags_flagStr_="${flags_flagStr_}--${flags_boolStr_}${flags_name_}:" 1131 | fi 1132 | 1133 | case ${flags_type_} in 1134 | ${__FLAGS_TYPE_BOOLEAN}) 1135 | if [ "${flags_default_}" -eq ${FLAGS_TRUE} ]; then 1136 | flags_defaultStr_='true' 1137 | else 1138 | flags_defaultStr_='false' 1139 | fi 1140 | ;; 1141 | ${__FLAGS_TYPE_FLOAT}|${__FLAGS_TYPE_INTEGER}) 1142 | flags_defaultStr_=${flags_default_} ;; 1143 | ${__FLAGS_TYPE_STRING}) flags_defaultStr_="'${flags_default_}'" ;; 1144 | esac 1145 | flags_defaultStr_="(default: ${flags_defaultStr_})" 1146 | 1147 | flags_helpStr_=" ${flags_flagStr_} ${flags_help_:+${flags_help_} }${flags_defaultStr_}" 1148 | _flags_strlen "${flags_helpStr_}" >/dev/null 1149 | flags_helpStrLen_=${flags_output} 1150 | flags_columns_=`_flags_columns` 1151 | 1152 | if [ "${flags_helpStrLen_}" -lt "${flags_columns_}" ]; then 1153 | echo "${flags_helpStr_}" >&2 1154 | else 1155 | echo " ${flags_flagStr_} ${flags_help_}" >&2 1156 | # Note: the silliness with the x's is purely for ksh93 on Ubuntu 6.06 1157 | # because it doesn't like empty strings when used in this manner. 1158 | flags_emptyStr_="`echo \"x${flags_flagStr_}x\" \ 1159 | |awk '{printf "%"length($0)-2"s", ""}'`" 1160 | flags_helpStr_=" ${flags_emptyStr_} ${flags_defaultStr_}" 1161 | _flags_strlen "${flags_helpStr_}" >/dev/null 1162 | flags_helpStrLen_=${flags_output} 1163 | 1164 | if [ "${__FLAGS_GETOPT_VERS}" -eq "${__FLAGS_GETOPT_VERS_STD}" \ 1165 | -o "${flags_helpStrLen_}" -lt "${flags_columns_}" ]; then 1166 | # Indented to match help string. 1167 | echo "${flags_helpStr_}" >&2 1168 | else 1169 | # Indented four from left to allow for longer defaults as long flag 1170 | # names might be used too, making things too long. 1171 | echo " ${flags_defaultStr_}" >&2 1172 | fi 1173 | fi 1174 | done 1175 | fi 1176 | 1177 | unset flags_boolStr_ flags_default_ flags_defaultStr_ flags_emptyStr_ \ 1178 | flags_flagStr_ flags_help_ flags_helpStr flags_helpStrLen flags_name_ \ 1179 | flags_columns_ flags_short_ flags_type_ flags_usName_ 1180 | return ${FLAGS_TRUE} 1181 | } 1182 | 1183 | # Reset shflags back to an uninitialized state. 1184 | # 1185 | # Args: 1186 | # none 1187 | # Returns: 1188 | # nothing 1189 | flags_reset() { 1190 | for flags_name_ in ${__flags_longNames}; do 1191 | flags_usName_=`_flags_underscoreName "${flags_name_}"` 1192 | flags_strToEval_="unset FLAGS_${flags_usName_}" 1193 | for flags_type_ in \ 1194 | ${__FLAGS_INFO_DEFAULT} \ 1195 | ${__FLAGS_INFO_HELP} \ 1196 | ${__FLAGS_INFO_SHORT} \ 1197 | ${__FLAGS_INFO_TYPE} 1198 | do 1199 | flags_strToEval_=\ 1200 | "${flags_strToEval_} __flags_${flags_usName_}_${flags_type_}" 1201 | done 1202 | eval "${flags_strToEval_}" 1203 | done 1204 | 1205 | # Reset internal variables. 1206 | __flags_boolNames=' ' 1207 | __flags_longNames=' ' 1208 | __flags_shortNames=' ' 1209 | __flags_definedNames=' ' 1210 | 1211 | # Reset logging level back to default. 1212 | flags_setLoggingLevel ${__FLAGS_LEVEL_DEFAULT} 1213 | 1214 | unset flags_name_ flags_type_ flags_strToEval_ flags_usName_ 1215 | } 1216 | 1217 | # 1218 | # Initialization 1219 | # 1220 | 1221 | # Set the default logging level. 1222 | flags_setLoggingLevel ${__FLAGS_LEVEL_DEFAULT} 1223 | --------------------------------------------------------------------------------