From cba3556da8028ad3c06f83769b1690f1ad03d496 Mon Sep 17 00:00:00 2001 From: tpoindex Date: Wed, 11 Sep 2019 20:17:45 -0600 Subject: [PATCH 1/5] fix comma quoting in tup/tupx by quoting all commas --- src/fun.sh | 4 ++-- test/tup_test.sh | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/fun.sh b/src/fun.sh index ba7b33b..c8c4420 100755 --- a/src/fun.sh +++ b/src/fun.sh @@ -262,7 +262,7 @@ tup() { read arg tup $arg else - list "$@" | map lambda x . 'echo ${x/,/u002c}' | join , '(' ')' + list "$@" | map lambda x . 'echo ${x//,/u002c}' | join , '(' ')' fi } @@ -274,7 +274,7 @@ tupx() { else local n=$1 shift - echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr ',' '\n' | map lambda x . 'echo ${x/u002c/,}' + echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr ',' '\n' | map lambda x . 'echo ${x//u002c/,}' fi } diff --git a/test/tup_test.sh b/test/tup_test.sh index bdd5ac1..78cdeb8 100755 --- a/test/tup_test.sh +++ b/test/tup_test.sh @@ -9,6 +9,7 @@ testTupIfOneElement() { assertEquals '(")' $(tup '"') assertEquals "(')" $(tup "'") assertEquals "(u002c)" $(tup ",") + assertEquals "(u002cu002c)" $(tup ",,") assertEquals "(()" $(tup "(") assertEquals "())" $(tup ")") } @@ -38,6 +39,7 @@ testTupxIfZeroIndex() { testTupxIfSpecialChars() { assertEquals ',' "$(tup ',' | tupx 1)" + assertEquals ',,' "$(tup ',,' | tupx 1)" assertEquals '(' "$(tup '(' | tupx 1)" assertEquals ')' "$(tup ')' | tupx 1)" assertEquals '()' "$(tup '()' | tupx 1)" @@ -45,6 +47,7 @@ testTupxIfSpecialChars() { assertEquals '(' "$(tup '(' '(' | tupx 1)" assertEquals ')' "$(tup ')' ')' | tupx 1)" assertEquals ',' "$(tup 'u002c' | tupx 1)" + assertEquals ',,' "$(tup 'u002cu002c' | tupx 1)" } testTupxRange() { @@ -66,4 +69,4 @@ testTupr() { assertEquals '5' "$(tup 5 | tupr)" } -. ./shunit2-init.sh \ No newline at end of file +. ./shunit2-init.sh From 9c238777e1e3d0075a182e759dd6fc428a8e7632 Mon Sep 17 00:00:00 2001 From: tpoindex Date: Wed, 11 Sep 2019 20:58:30 -0600 Subject: [PATCH 2/5] ntup{x,l,r} - tuples that can be nested and without quoting issues, base64 encoded elements --- src/fun.sh | 30 ++++++++++++++++++++++++++++++ test/tup_test.sh | 12 ++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/fun.sh b/src/fun.sh index c8c4420..48b0894 100755 --- a/src/fun.sh +++ b/src/fun.sh @@ -286,6 +286,36 @@ tupr() { tupx 1- "$@" | last } +ntup() { + if [[ $# -eq 0 ]]; then + local arg + read arg + ntup $arg + else + list "$@" | map lambda x . 'echo "$x" | base64 --wrap=0 ; echo' | join , '(' ')' + fi +} + +ntupx() { + if [[ $# -eq 1 ]]; then + local arg + read arg + ntupx "$1" "$arg" + else + local n=$1 + shift + echo "$@" | stripl '(' | stripr ')' | cut -d',' -f${n} | tr , '\n' | map lambda x . 'echo "$x" | base64 -d' + fi +} + +ntupl() { + ntupx 1 "$@" +} + +ntupr() { + ntupx 1- "$@" | last +} + zip() { local list=$* cat - | while read x; do diff --git a/test/tup_test.sh b/test/tup_test.sh index 78cdeb8..25cd8bf 100755 --- a/test/tup_test.sh +++ b/test/tup_test.sh @@ -69,4 +69,16 @@ testTupr() { assertEquals '5' "$(tup 5 | tupr)" } +testNTup() { + assertEquals '(KFlRbz0sWWdvPSkK,Ywo=)' "$(ntup $(ntup a b) c)" + assertEquals '(YQo=,Ygo=)' "$(ntupl '(KFlRbz0sWWdvPSkK,Ywo=)')" + assertEquals 'a' "$(ntupl '(YQo=,Ygo=)')" + assertEquals 'b' "$(ntupr '(YQo=,Ygo=)')" + assertEquals 'c' "$(ntupr '(KFlRbz0sWWdvPSkK,Ywo=)')" + assertEquals 'a' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 1)" + assertEquals 'b' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 2)" + assertEquals 'c' "$(ntup $(ntup a b) c | ntupx 2)" + assertEquals 'a b' "$(ntup $(ntup a b) c | ntupx 1 | ntupx 1,2 | unlist)" +} + . ./shunit2-init.sh From 912b3f249116b506fae1c762d957d75c1dfe9b70 Mon Sep 17 00:00:00 2001 From: tpoindex Date: Wed, 11 Sep 2019 22:47:39 -0600 Subject: [PATCH 3/5] maybe monad and friends maybe - return a tuple of (Nothing) or (Just,value) maybemap - apply map function when maybe has a value and wrap in another maybe; else when nothing return nothing maybevalue - return value of maybe; else when nothing return optional default args --- src/fun.sh | 40 ++++++++++++++++++++++++++++++++++++++++ test/maybe_test.sh | 29 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100755 test/maybe_test.sh diff --git a/src/fun.sh b/src/fun.sh index 48b0894..e1ad6d9 100755 --- a/src/fun.sh +++ b/src/fun.sh @@ -358,3 +358,43 @@ call() { local args=$@ tup $f $args } + +maybe() { + if [[ $# -eq 0 ]]; then + local arg + read arg + maybe "$arg" + else + local x="$*" + local value=$(echo $x | strip) + if [[ ${#value} -eq 0 ]]; then + tup Nothing + else + tup Just "$value" + fi + fi +} + +maybemap() { + local x + read x + if [[ $(tupl $x) = "Nothing" ]]; then + echo $x + else + local y=$(tupr "$x") + local r=$(echo "$y" | map "$@") + maybe "$r" + fi +} + +maybevalue() { + local default="$*" + local x + read x + if [[ $(tupl $x) = "Nothing" ]]; then + echo "$default" + else + echo $(tupr $x) + fi +} + diff --git a/test/maybe_test.sh b/test/maybe_test.sh new file mode 100755 index 0000000..1003179 --- /dev/null +++ b/test/maybe_test.sh @@ -0,0 +1,29 @@ +#! /bin/bash + +testMaybe() { + assertEquals '(Just,1)' "$(maybe 1)" + assertEquals '(Just,1)' "$(echo 1 | maybe)" + assertEquals '(Nothing)' "$(maybe '')" + assertEquals '(Nothing)' "$(maybe ' ')" + assertEquals '(Nothing)' "$(maybe ' ' ' ' ' ')" + assertEquals '(Nothing)' "$(echo | maybe)" + assertEquals '(Just,1 2 3)' "$(maybe 1 2 3)" + assertEquals '(Just,1 2 3)' "$(echo 1 2 3 | maybe)" +} + +testMaybemap() { + assertEquals '(Just,3)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))')" + assertEquals '(Nothing)' "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))')" + + assertEquals '(Nothing)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo')" + assertEquals '(Nothing)' "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo' | maybemap lambda a . 'echo $(( a + 1 ))')" +} + +testMaybevalue() { + assertEquals 3 "$(echo 1 | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue 0)" + assertEquals 0 "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue 0)" + assertEquals 'a b c' "$(echo | maybe | maybemap lambda a . 'echo $(( a + 1 ))' | maybemap lambda a . 'echo $(( a + 1 ))' | maybevalue a b c)" +} + + +. ./shunit2-init.sh From 7b6531a74e6c80ebaf798d44dc68022acb9159ea Mon Sep 17 00:00:00 2001 From: tpoindex Date: Wed, 11 Sep 2019 23:46:47 -0600 Subject: [PATCH 4/5] fluent predicates for filtering --- src/fun.sh | 37 +++++++++++++++++++++++++++++ test/predicates_test.sh | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100755 test/predicates_test.sh diff --git a/src/fun.sh b/src/fun.sh index e1ad6d9..5d44542 100755 --- a/src/fun.sh +++ b/src/fun.sh @@ -398,3 +398,40 @@ maybevalue() { fi } + +# commonly used predicates for filter +# e.g. list 1 a 2 b 3 c | filter lambda x . 'isint $x' + +# inverse another test, e.g. "not isint $x" +not() { + local r=$("$@" 2>/dev/null) + $r && ret false || ret true +} + +isint() { + [ "$1" -eq "$1" ] 2>/dev/null && ret true || ret false +} + +isempty() { + [ -z "$1" ] && ret true || ret false +} + +isfile() { + [ -f "$1" ] && ret true || ret false +} + +isnonzerofile() { + [ -s "$1" ] && ret true || ret false +} + +isreadable() { + [ -r "$1" ] && ret true || ret false +} + +iswritable() { + [ -w "$1" ] && ret true || ret false +} + +isdir() { + [ -d "$1" ] && ret true || ret false +} diff --git a/test/predicates_test.sh b/test/predicates_test.sh new file mode 100755 index 0000000..79bff20 --- /dev/null +++ b/test/predicates_test.sh @@ -0,0 +1,52 @@ +#! /bin/bash + +testIsint() { + assertEquals 'true' $(isint 1) + assertEquals 'true' $(isint -1) + assertEquals 'false' $(isint a) + assertEquals 'false' $(isint "") + assertEquals '1 2 3 4 5' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . 'isint $x' | unlist )" + assertEquals '1 2' "$(list 1 a 2 b 3 c 4 d 5 e | filter lambda x . '($(isint $x) && [[ $x -le 2 ]] && ret true) || ret false ' | unlist )" + + assertEquals 'false' $(not isint 1) + assertEquals 'true' $(not isint a) +} + +testIsempty() { + assertEquals 'true' $(isempty "") + assertEquals 'false' $(isempty a) + + assertEquals 'true' $(not isempty a) + assertEquals 'false' $(not isempty "") +} + +testIsfile() { + f=$(mktemp) + + assertEquals 'true' $(isfile $f) + assertEquals 'false' $(isfile $f.xxx) + assertEquals 'false' $(isfile "") + assertEquals 'true' $(not isfile $f.xxx) + + assertEquals 'false' $(isnonzerofile $f) + echo hello world >$f + assertEquals 'true' $(isnonzerofile $f) + + assertEquals 'true' $(iswritable $f) + chmod 400 $f + assertEquals 'false' $(iswritable $f) + + assertEquals 'true' $(isreadable $f) + chmod 200 $f + assertEquals 'false' $(isreadable $f) + + chmod 600 $f + rm $f +} + +testIsdir() { + assertEquals 'true' $(isdir .) + assertEquals 'false' $(isdir sir_not_appearing_in_this_film) +} + +. ./shunit2-init.sh From 1ead6d0b87b0ba311f19ef99bab9d6128898145c Mon Sep 17 00:00:00 2001 From: tpoindex Date: Wed, 11 Sep 2019 23:53:56 -0600 Subject: [PATCH 5/5] avoid collisions with common /usr/bin/ commands rename head, tail, zip to lhead, ltail, and lzip to avoid collisions with command unix commands --- src/fun.sh | 6 +++--- test/head_test.sh | 16 ++++++++-------- test/tail_test.sh | 14 +++++++------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/fun.sh b/src/fun.sh index 5d44542..08f3bc1 100755 --- a/src/fun.sh +++ b/src/fun.sh @@ -8,11 +8,11 @@ take() { command head -n ${1} } -tail() { +ltail() { drop 1 } -head() { +lhead() { take 1 } @@ -316,7 +316,7 @@ ntupr() { ntupx 1- "$@" | last } -zip() { +lzip() { local list=$* cat - | while read x; do y=$(list $list | take 1) diff --git a/test/head_test.sh b/test/head_test.sh index 3ea2a7f..90ea201 100755 --- a/test/head_test.sh +++ b/test/head_test.sh @@ -1,16 +1,16 @@ #! /bin/bash -testHeadFromList() { - assertEquals 1 $(list {1..10} | head) - assertEquals 5 $(list 5 6 7 | head) +testLHeadFromList() { + assertEquals 1 $(list {1..10} | lhead) + assertEquals 5 $(list 5 6 7 | lhead) } -testHeadFromOneElementList() { - assertEquals 1 $(list 1 | head) +testLHeadFromOneElementList() { + assertEquals 1 $(list 1 | lhead) } -testHeadFromEmptyList() { - assertEquals "" "$(list | head)" +testLHeadFromEmptyList() { + assertEquals "" "$(list | lhead)" } -. ./shunit2-init.sh \ No newline at end of file +. ./shunit2-init.sh diff --git a/test/tail_test.sh b/test/tail_test.sh index 57971ad..b2b19f5 100755 --- a/test/tail_test.sh +++ b/test/tail_test.sh @@ -1,15 +1,15 @@ #! /bin/bash -testTailFrom10() { - assertEquals "2 3 4 5 6 7 8 9 10" "$(list {1..10} | tail | unlist)" +testLTailFrom10() { + assertEquals "2 3 4 5 6 7 8 9 10" "$(list {1..10} | ltail | unlist)" } -testTailFromOneElementList() { - assertEquals "" "$(list 1 | tail)" +testLTailFromOneElementList() { + assertEquals "" "$(list 1 | ltail)" } -testTailFromEmptyList() { - assertEquals "" "$(list | tail)" +testLTailFromEmptyList() { + assertEquals "" "$(list | ltail)" } -. ./shunit2-init.sh \ No newline at end of file +. ./shunit2-init.sh