diff --git a/bake b/bake index 4b72059a..5b026bab 100755 --- a/bake +++ b/bake @@ -15,8 +15,17 @@ SCRIPT_PATH="$(_real_path "${BASH_SOURCE[0]}")" SCRIPT_DIR="$(dirname "$SCRIPT_PATH")" SCRIPT_FILE="$(basename "$SCRIPT_PATH")" +_install_bake(){ + mkdir -p "$SCRIPT_DIR/vendor" + echo "$SCRIPT_PATH -> _install_bake ▶︎【curl -o $SCRIPT_DIR/bake.bash https://github.com/chen56/bake/raw/main/bake.bash】" + curl -L -o "$SCRIPT_DIR/vendor/bake.bash" https://github.com/chen56/bake/raw/main/bake.bash ; +} +if ! [[ -f "$SCRIPT_DIR/vendor/bake.bash" ]]; then + _install_bake +fi + # include common script -source "$SCRIPT_DIR/bake.bash" +source "$SCRIPT_DIR/vendor/bake.bash" ########################################## # app cmd script diff --git a/test/bake2_test.bash b/test/bake2_test.bash deleted file mode 100755 index 8cdb47a3..00000000 --- a/test/bake2_test.bash +++ /dev/null @@ -1,470 +0,0 @@ -#!/usr/bin/env bash -set -o errtrace # -E trap inherited in sub script -set -o errexit # -e -set -o functrace # -T If set, any trap on DEBUG and RETURN are inherited by shell functions -set -o pipefail # default pipeline status==last command status, If set, status=any command fail -#set -o nounset # -u: don't use it ,it is crazy, 1.bash version is diff Behavior 2.we need like this: ${arr[@]+"${arr[@]}"} - -function _real_path() ( - cd -P "$(dirname -- "$1")" - file="$PWD/$(basename -- "$1")" - while [[ -L "$file" ]]; do - cd -P "$(dirname -- "$file")" - file="$(readlink -- "$file")" - cd -P "$(dirname -- "$file")" - file="$PWD/$(basename -- "$file")" - done - echo "$file" -) - -TEST_PATH="$(_real_path "${BASH_SOURCE[0]}")" -TEST_DIR="$(dirname "$TEST_PATH")" -TEST_FILE="$(basename "$TEST_PATH")" - -source "$TEST_DIR/../bake.bash" - - -bake.assert.fail() { - echo "$@" >&2 -} - -# 查找出所有test_函数并执行 -# 这种测试有点麻烦,不如bake.test -function bake.test.all() { - while IFS=$'\n' read -r functionName ; do - [[ "$functionName" != test.* ]] && continue ; - # run test - printf "test: %s %-50s" "${TEST_PATH}" "$functionName()" - # TIMEFORMAT: https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html - # %R==real %U==user %S==sys %P==(user+sys)/real - TIMEFORMAT="real %R user %U sys %S percent %P" - ( - # 隔离test在子shell里,防止环境互相影响 - time "$functionName" ; - )# 2>&1 - done <<< "$(compgen -A function)" -} - -@is_escape(){ - local actual="$1" expected="$2" msg="$3" - local escaped; escaped="$(printf '%q' "${actual}")" - escaped="${escaped#$\'}" # "$'str'" remove begin "$'" => "str'" - escaped="${escaped%\'*}" # "str'" remove end "'" => "str" - if [[ "$escaped" != "$expected" ]] ; then - bake.assert.fail "assert is_escape fail: $msg - actual : [$escaped] - is not escape: [$expected]" - echo "diff------------------------->" >&2 - diff <(echo -e "$expected") <(echo -e "$actual") >&2 - return 2 - fi -} - -# escape to 'xxx' or $'xxx' -# https://www.gnu.org/software/bash/manual/bash.html#ANSI_002dC-Quoting -bake.str.escape() { - # from 2016 bash 4.4 - # ${parameter@Q} : quoted in a format that can be reused as input - # to 'xxx' or $'xxx' - printf '%s\n' "${1@Q}" -} -# unescape from 'xxx' or $'xxx' -bake.str.unescape() { - local str=${1} - # $'xx' => xx - if [[ "$str" == "\$'"*"'" ]]; then - str="${str:2:-1}" - # 'xx' => xx - elif [[ "${str}" == "'"*"'" ]]; then - str="${str:1:-1}" - fi - # from 2016 bash 4.4 - # ${parameter@E} expanded as with the $'...' quoting mechansim - printf '%s' "${str@E}" -} - - -@is(){ - local actual="$1" expected="$2" msg="$3" - if [[ "$actual" != "$expected" ]] ; then - bake.assert.fail "assert is fail: $msg - actual : [$actual] - is not : [$expected] - -------------------------------------------------- - actual escape : [$(printf '%q' "$actual")] - is not escape : [$(printf '%q' "$expected")] - " - echo "diff------------------------->" >&2 - diff <(echo -e "$expected") <(echo -e "$actual") >&2 - return 2 - fi -} -@contains(){ - local actual="$1" expected="$2" msg="$3" - if [[ "$actual" != *"$expected"* ]] ; then - bake.assert.fail "assert fail: $msg - actual : [$actual] - is not contains: [$expected]" - return 2 - fi -} - - -# Usage: assert [msg] -# Sample: assert $(( 1+1 )) @is "2" -assert(){ - local actual="$1" op="$2" expected="$3" msg="$4" - "$2" "$actual" "$3" "$4" -} - -# above is test framework -# $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -# $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -# $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -# below is test case - -####################################################### -## study bash or some other -####################################################### -# IFS : https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_05 -study.string.escape(){ - # $'' 语法 - assert $'1\n2' @is '1 -2' - assert '1\n2' @is '1\n2' - - assert $'1 -2' @is $'1\n2' - - x="1 -2" - assert "$(printf '%q' "$x" )" @is "$'1\n2'" -} -study.read(){ - local expected=""; - echo -e "1\n2" | while read -r x ; do - expected+="$1," - done - assert "$expected" @is '1,2,' -} -study.function.return(){ - ( - s(){ - printf "%s" "a b -c d"; - } - str="$(s)" - assert "${str}" @is $'a b\nc d' - printf "${str}" | od -c -td1 - read -a arr <<< "${str}" - assert "${#arr[@]}" @is 2 - ) - - ( - # 需重新构造更清洗的用例 - q(){ - printf "%q" "a b -c d"; - } - str="$(q)" - assert "${str}" @is "$'a b\nc d'" - printf "${str}" | od -c -td1 - read -a arr <<< "${str}" - # read 按IFS默认$' \n\t',即用, ,分词 - # 由于printf "%q"会对非打印字符进行转义,已经被转为\和n,所以read分词时, - # 并没有任何 - assert "${#arr[@]}" @is 3 - assert "$(printf '[%s]' "${arr[@]}" )" @is "[$'a][bnc][d']" - - IFS=$'\n' read -a arr <<< "${str}" - # 我们重设分词符号只有$'\n', 但其实已被转义,所以并没有任何 - assert "${#arr[@]}" @is 1 - assert "$(printf '[%s]' "${arr[@]}" )" @is "[$'a bnc d']" - - # 函数的开发者有职责自己先区分好,我这个函数的输出,什么算是1行,算是1行的,就用printf "%q"转义 - # 那么,函数的调用者就有机会决定怎么分词 - # 函数的调用者有职责,对函数返回的字符串,自己先区分好,我拿到的函数输出,要不要分行处理 - # 就用IFS=$'\n' read -a arr <<< "${str}"分词 - ) -} -# Usage: pipe [args...] -# callback: callback -# Sample: cat file | pipe echo -study.pipe(){ - local func="$1" ; shift; - for arg in "$@"; do - $func "$arg" - done - if ! [[ -t 0 ]]; then - while read line; do - $func "$line" - done - fi -} - - -function test.bake.str.escape() { - assert "$(bake.str.escape $'1' )" @is "'1'" - assert "$(bake.str.escape $'1 ')" @is "'1 '" - assert "$(bake.str.escape $'1 2 "')" @is "'1 2 \"'" - assert "$(bake.str.escape $'1 "')" @is "'1 \"'" - assert "$(bake.str.escape $'1 2\n')" @is "\$'1 2\n'" - -} -function test.bake.str.unescape() { - assert "$(bake.str.unescape "$(bake.str.escape $'1' )")" @is $'1' - assert "$(bake.str.unescape "$(bake.str.escape $'1 2' )")" @is $'1 2' - assert "$(bake.str.unescape "$(bake.str.escape $'1 " ' )")" @is $'1 " ' - assert "$(bake.str.unescape "$(bake.str.escape $'1 \n ' )")" @is $'1 \n ' -} -test.study.declare(){ - ( - declare a=1 b=2 - assert "$a" @is "1" - assert "$b" @is "2" - ) - - ( - s='a=1 b=2' - declare $(printf "%s" "$s") - assert "$a" @is "1" - assert "$b" @is "2" - ) - ( - s='a=1 b=2' - declare $(printf "%s" "$s") - assert "$a" @is "1" - assert "$b" @is "2" - ) - - # declare ansi c quoting $'' - ( - err=$( declare $(printf "%s" "a=$'1 2'") 2>&1 ) || true - assert "$err" @contains "2'': not a valid identifier" - ) - - ( - - IFS=$'\n' - declare $(printf "%s" "a=$'1 2' -b=$'3 4'") - assert "$a" @is "$'1 2'" - assert "$b" @is "$'3 4'" - ) - - ( - # 目前看declare的脚本注射是比较安全的 - # "|| true" 防止set -o errexit - err=$( declare $(printf "%s" 'a=1 b=2 ls -al -- ll') 2>&1 ) || true - assert "$err" @contains "-al': not a valid identifier" - err=$( declare $(printf "%s" 'a=$(ls -al)') 2>&1 ) || true - assert "$err" @contains "-al)': not a valid identifier" - err=$( declare $(printf "%s" 'a=b $(ls)=x') 2>&1 ) || true - assert "$err" @contains $"ls)=x': not a valid identifier" - err=$( declare $(printf "%s" 'a=b `ls`=x') 2>&1 ) || true - assert "$err" @contains "=x': not a valid identifier" - ) - -} -# eval 用起来比declare 省心多了 -test.study.eval(){ - ( - eval "a=$'1 2' b=$'3 4'" - assert "$a" @is "1 2" - assert "$b" @is "3 4" - ) - - { - # 包含换行符也没问题 - eval "a=$'1 2' - b=$'3 4'" - assert "$a" @is "1 2" - assert "$b" @is "3 4" - } - - ( - # 数组也没问题 - eval "a=($'1 2' - $'3\n4')" - assert "${#a[@]}" @is 2 - assert "${a[0]}" @is "1 2" - assert "${a[1]}" @is $'3\n4' - ) - - { - # 关联数组也没问题 - eval "declare -A a=([a]=$'1\n2' [b]=$'2' )" - assert "${#a[@]}" @is 2 - assert "${a[a]}" @is $'1\n2' - assert "${a[b]}" @is $'2' - } - ( - # 用$'' ANSI C Quoting格式看来也是安全的 - eval "a=$'1 2' b=$'3 4 script inject not work'" - assert "$a" @is "1 2" - assert "$b" @is "3 4 script inject not work" - ) -} - -study.array(){ - a=("line1 a -line2" x) - assert "$(printf "[%s]" ${a[@]})" @is $'[line1][a][line2][x]' - assert "$(printf "[%s]" ${a[@]})" @is $'[line1][a][line2][x]' - # 由于"$@" 特殊语法,数组可已包含空格换行符, 不影响分词 - assert "$(printf "[%s]" "${a[@]}")" @is $'[line1 a\nline2][x]' - -} - -####################################################### -## bake test case -####################################################### - -test.assert_sample(){ - assert $((1+1)) @is 2 -} - -test.bake._path_dirname(){ - assert "$(bake._path_dirname a/b/c '/')" @is "a/b" - assert "$(bake._path_dirname a '/')" @is "" - assert "$(bake._path_dirname "" '/')" @is "" - - # abstract path - assert "$(bake._path_dirname /a/b/c '/')" @is "/a/b" - assert "$(bake._path_dirname /a '/')" @is "" - assert "$(bake._path_dirname / '/')" @is "" -} -test.bake._path_first(){ - assert "$(bake._path_first a/b/c '/')" @is "a" - assert "$(bake._path_first a '/')" @is "a" - assert "$(bake._path_first '' '/')" @is "" - - assert "$(bake._path_first /a/b/c '/')" @is "/a" -} - -test.bake._path_basename(){ - assert "$(bake._path_basename a/b/c '/')" @is "c" - assert "$(bake._path_basename a '/')" @is "a" - assert "$(bake._path_basename "" '/')" @is "" - - # abstract path - assert "$(bake._path_basename "/a" '/')" @is "a" - assert "$(bake._path_basename "/" '/')" @is "" -} - -test.bake._str_cutLeft(){ - assert "$(bake._str_cutLeft a/b/c 'a/b/')" @is "c" - assert "$(bake._str_cutLeft a/b/c '')" @is "a/b/c" - - assert "$(bake._str_cutLeft /a/b/c '/')" @is "a/b/c" - - assert "$(bake._str_cutLeft a/b/c 'notStart')" @is "a/b/c" - assert "$(bake._str_cutLeft a/b/c '/')" @is "a/b/c" -} - - -test.bake._cmd_up_chain(){ - assert "$(bake._cmd_up_chain a.b)" @is_escape "a.b\na\nroot" - assert "$(bake._cmd_up_chain 'root')" @is "root" - assert "$(bake._cmd_up_chain '')" @is "root" -} -test.bake._cmd_children(){ - assert "$(bake._cmd_children bake.test)" @is_escape "all" -} - -test.bake.str.split(){ - assert "$(bake._str_split "a/b" '/')" @is_escape "a\nb" - assert "$(bake._str_split "a/b/" '/')" @is_escape "a\nb" - - # abstract path - assert "$(bake._str_split "/a/b" '/')" @is_escape "\na\nb" - assert "$(bake._str_split "/a/b/" '/')" @is_escape "\na\nb" - - - # 包含破坏性特殊字符 - assert "$(bake._str_split $'a\nb' "/" )" @is_escape "a\nb" - assert "$(bake._str_split $'a\n/b' "/" )" @is_escape "a\n\nb" - assert "$(bake._str_split "a -/b -" )" @is_escape "a\n\nb" - - # default delimiter - assert "$(bake._str_split "a/b" )" @is_escape "a\nb" - - # other delimiter - assert "$(bake._str_split "a.b" '.')" @is_escape "a\nb" -} - -test.bake._cmd_register(){ - bake._cmd_register - assert "$(bake.info | grep test.bake._cmd_register)" \ - @contains "test.bake._cmd_register" -} -test.data.children(){ - assert "$(bake._data_children "bake.opt/opts")" @is_escape "abbr\ncmd\ndefault\ndesc\nname\nrequired\ntype" -} - -test.bake._opt_cmd_chain_opts(){ - assert "$(bake._opt_cmd_chain_opts "root")" @is \ -"root/opts/debug -root/opts/help" - - # "include parent option" - assert "$(bake._opt_cmd_chain_opts "bake.opt")" @is \ -"bake.opt/opts/abbr -bake.opt/opts/cmd -bake.opt/opts/default -bake.opt/opts/desc -bake.opt/opts/name -bake.opt/opts/required -bake.opt/opts/type -root/opts/debug -root/opts/help" -} - -test.cmd.parse(){ - bake.opt --cmd "test.cmd.parse" --name stringOpt --type string - bake.opt --cmd "test.cmd.parse" --name boolOpt --type bool - bake.opt --cmd "test.cmd.parse" --name listOpt --type list - - assert "$(bake.parse "test.cmd.parse" --boolOpt )" @is 'declare boolOpt="true"; -declare optShift=1;' - assert "$(bake.parse "test.cmd.parse" --stringOpt "1 2" )" @is 'declare stringOpt="1 2"; -declare optShift=2;' - - # list type option - assert "$(bake.parse "test.cmd.parse" --listOpt "a 1" --listOpt "b 2" )" @is 'declare listOpt=([0]="a 1" [1]="b 2"); -declare optShift=4;' - - # no exists cmd - assert "$(bake.parse "no.exists.func" --unknow_opt bool)" @is "declare optShift=0;" - - # no exists option - assert "$(bake.parse "test.cmd.parse" --no_exists_opt)" @is "declare optShift=0;" -} - -test.bake.opt(){ - bake.opt --cmd "test.opt.add" --name boolopt --type bool -} - -test.bake.opt.value.parse_and_get_value(){ - bake.opt --cmd "test.opt.add" --name xxx --type string - echo $(bake.parse "test.opt.add" --xxx chen) - eval "$(bake.parse "test.opt.add" --xxx chen)" - assert "$xxx" @is "chen" -} - - -function test(){ - bake.test.all -} - -# override test -root(){ - echo "you can run - ./test/$TEST_FILE test -h # test subcommands - ./test/$TEST_FILE test # or run all test in this file" -} - - -bake.go "$@" diff --git a/bake.bash b/vendor/bake.bash similarity index 82% rename from bake.bash rename to vendor/bake.bash index a9330bcc..32cb9866 100644 --- a/bake.bash +++ b/vendor/bake.bash @@ -7,51 +7,36 @@ set -o pipefail # default pipeline status==last command status, If set, status= _bake_version=v0.3.20240327 -# v0.2.20230525 - It can run normally on macos +# v0.2.20230528 - It can run normally on macos # todo -# 1. 尴尬:当前 无法判断错误命令:./bake no_this_cmd ,因为不知道这是否是此命令的参数, -# 干脆设一个简单的规则:只有叶子命令才能执行,这样非叶子命令就不需要有参数,好判断了 -# 2. 尴尬:当前 无法判断错误options:./bake --no_this_cmd ,同上 +# 1. 当前 无法判断错误命令:./bake no_this_cmd ,因为不知道这是否是此命令的参数, +# 需要设置设一个简单的规则:只有叶子命令才能正常执行,这样非叶子命令就不需要有参数 +# 2. 当前 无法判断错误options:./bake --no_this_opt ,同上 # 3. 类似flutter run [no-]pub 反向选项 # -# chinese----------------------------------------------------------------------- # bake == (bash)ake == 去Make的bash tool # bake 是个简单的命令行工具,以替代Makefile的子命令功能 # make工具的主要特点是处理文件依赖进行增量编译,但flutter、golang、java、js项目的build工具 # 太厉害了,这几年唯一还在用Makefile的理由就是他的子命令机制: "make build"、 # "make run", 可以方便的自定义单一入口的父子命令,但Makefile本身的语法套路也很复杂, # 很多批处理还是要靠bash, 这就尴尬了,工具太多,麻烦!本脚本尝试彻底摆脱使用Makefile。 -# 经尝试,代码很少啊 ,核心代码几百行啊,父子命令2百行左右,option解析2百行左右,功能足够了: +# 经尝试,代码很少啊 ,核心代码几百行啊,父子命令二三百行左右,option解析二三百行左右,功能足够了: # -# english----------------------------------------------------------------------- -# bake == (bash)ake == De-Make bash tool -# bake is a simple cli tool to replace the subcommand function of Makefile -# The main feature of the make tool is to process file dependencies for -# incremental compilation, but the build tools for flutter, golang, java, -# and js projects, It's so powerful, the only reason why I still use Makefile -# in recent years is its subcommand mechanism: "make build", -# "make run", it is convenient to customize the parent-child command of a -# single entry, but the syntax of the Makefile itself is also very complicated. -# A lot of batch processing still depends on bash, which is embarrassing, -# too many tools, So complicated! This script attempts to get rid of Makefiles -# altogether. After trying, the code is very little, the core code is -# hundreds of lines, the parent-child command is about 200 lines, -# and the option parse is about 200 lines, the function is enough: - -# chinese----------------------------------------------------------------------- # bake命令规则: # 1. 函数即命令,所有bake内的函数均可以在脚本外运行: # ./bake [all function] # bake内的所有函数均可以在脚本外直接运行 -# ./bake _self # 比如这个内部函数, 看bake内部变量,调试脚本用 -# ./bake test # 你如果定义过test()函数,就可以这样运行 -# 2. 带"."的函数,形成父子命令,比如 bake.opt()函数是bake.opt()的子命令,bake.opt() -# 是bake()的子命令,即便未定义父命令,也可以通过父命令列子命令看帮助: -# ./bake bake -h # 运行子命令,或看帮助 -# ./bake bake opt -h # 运行子命令,或看帮助 -# ./bake bake.opt -h # 运行子命令,或看帮助 -# ./bake bake.opt -h # 由于规则1,可直接用函数名运行子命令 -# 3. 你可以定义根命令"_root()" -# ./bake # 如果有_root()函数,就执行它 +# ./bake info # 比如这个内部函数, 看bake内部变量,调试脚本用 +# ./bake test # 你如果定义过test()函数,就可以这样运行 +# 2. 带"."的函数,形成父子命令: +# web.build(){ echo "build web app"; } +# web.test(){ echo "build web app"; } +# 可以这样调用 +# ./bake web -h # 运行子命令,或看帮助 +# ./bake web.build -h # 上面等同 +# ./bake web test -h # 运行子命令,或看帮助 +# ./bake web.test -h # 与上面等同 +# 3. 特殊的root命令表示根命令 +# ./bake # 如果有root()函数,就执行它 # 4. 像其他高级语言的cli工具一样,用简单变量就可以获取命令option: # # a. 先在bake文件里里定义app options # bake.opt --cmd build --name "target" --type string @@ -64,53 +49,12 @@ _bake_version=v0.3.20240327 # ./bake build --target "macos" # 5. bake尽量不依赖bash以外的其他工具,包括linux coreutils,更简单通用,但由于用了关联数组等 # 依赖bash4+ -# 6. 这个文件copy走,把"bake common script end line."之下的脚本替换成你的就可以用了. - -# english----------------------------------------------------------------------- -# bake rules: -# 1. bash function are cmd, all functions in bake can be run outside the script: -# ./bake [all func] # All functions in bake can be run outside script -# ./bake _self # For example, this internal function, -# # see bake internal variables, used to debug scripts -# ./bake test # If you have a test() function, you can run it like this -# 2. Functions with "." is parent-child commands, such as bake.opt() function -# is a subcommand of bake.opt(), bake.opt() is a subcommand of bake(), -# even the parent command is not defined, you can also view the help : -# ./bake bake -h # Run subcommands, or see help -# ./bake bake opt -h # Run subcommands, or see help -# ./bake bake.opt -h # Run subcommands, or see help -# ./bake bake.opt -h # Due to rule 1, the subcommand can be run directly with the function name -# 3. You can define the root command "_root()" -# ./bake # If there is a _root() function, execute it -# 4. Like other high-level language cli tools, command options can be obtained with simple variables: -# # a. Define options before the command function -# bake.opt --cmd build --name "target" --type string -# # b. Parse and use option -# function build() { -# eval "$(bake.parse "${FUNCNAME[0]}" "$@")"; -# echo "build ... your option: target: $target"; -# } -# # c. Call it: -# ./bake build --target "macos" -# 5. bake try not to depends on too many external tools other than bash, -# including linux coreutils, keep it simple, But because of the use of -# associative arrays arrays, Depend on bash4+ -# 6. Copy this file, replace the script under "bake common script end line." -# with yours and it will be ready to use. -# -# other command framework ref: -# https://pub.dev/documentation/args/latest/args/ArgParser-class.html -# https://github.com/spf13/pflag -# https://oclif.io/docs/flags -# -# know bash version -# ref: https://ftp.gnu.org/gnu/bash/ -# 1. ${parameter@operator} : from 2016 bash 4.4 -# ${parameter@Q} quoted in a format that can be reused as input -# ${parameter@E} expanded as with the $'...' quoting mechansim -# ${parameter@A} an assignment statement or declare command -# 2. associative array : from 2009 bash 4.0 -# declare -A +# 6. 有两种用法: +# - 这个文件copy走,把你的脚本放到本脚本最后即可. +# - 在你的脚本里直接curl下载本脚本后 source即可。 +# 范例可以看实际案例: +# - https://github.com/chen56/note/blob/main/bake +# - https://github.com/chen56/younpc/blob/main/bake # check bake dependencies