sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

util.sh (245748B)


      1 # -*- mode: sh; mode: sh-bash -*-
      2 # bash script to be sourced from interactive shell
      3 
      4 #------------------------------------------------------------------------------
      5 # ble.sh options
      6 
      7 function bleopt/.read-arguments/process-option {
      8   local name=$1
      9   case $name in
     10   (help)
     11     flags=H$flags ;;
     12   (color|color=always)
     13     flags=c${flags//[cn]} ;;
     14   (color=never)
     15     flags=n${flags//[cn]} ;;
     16   (color=auto)
     17     flags=${flags//[cn]} ;;
     18   (color=*)
     19     ble/util/print "bleopt: '${name#*=}': unrecognized option argument for '--color'." >&2
     20     flags=E$flags ;;
     21   (reset)   flags=r$flags ;;
     22   (changed) flags=u$flags ;;
     23   (initialize) flags=I$flags ;;
     24   (*)
     25     ble/util/print "bleopt: unrecognized long option '--$name'." >&2
     26     flags=E$flags ;;
     27   esac
     28 }
     29 
     30 ## @fn bleopt/expand-variable-pattern pattern opts
     31 ##   @var[out] ret
     32 function bleopt/expand-variable-pattern {
     33   ret=()
     34   local pattern=$1
     35   if [[ $pattern == *@* ]]; then
     36     builtin eval -- "ret=(\"\${!${pattern%%@*}@}\")"
     37     ble/array#filter-by-glob ret "${pattern//@/?*}"
     38   elif [[ ${!pattern+set} || :$opts: == :allow-undefined: ]]; then
     39     ret=("$pattern")
     40   fi
     41   ((${#ret[@]}))
     42 }
     43 
     44 ## @fn bleopt/.read-arguments
     45 ##   @var[out] flags
     46 ##     H --help
     47 ##     c --color=always
     48 ##     n --color=never
     49 ##     r --reset
     50 ##     u --changed
     51 ##     I --initialize
     52 ##   @var[out] pvars
     53 ##   @var[out] specs
     54 function bleopt/.read-arguments {
     55   flags= pvars=() specs=()
     56   while (($#)); do
     57     local arg=$1; shift
     58     case $arg in
     59     (--)
     60       ble/array#push specs "$@"
     61       break ;;
     62     (-)
     63       ble/util/print "bleopt: unrecognized argument '$arg'." >&2
     64       flags=E$flags ;;
     65     (--*)
     66       bleopt/.read-arguments/process-option "${arg:2}" ;;
     67     (-*)
     68       local i c
     69       for ((i=1;i<${#arg};i++)); do
     70         c=${arg:i:1}
     71         case $c in
     72         (r) bleopt/.read-arguments/process-option reset ;;
     73         (u) bleopt/.read-arguments/process-option changed ;;
     74         (I) bleopt/.read-arguments/process-option initialize ;;
     75         (*)
     76           ble/util/print "bleopt: unrecognized option '-$c'." >&2
     77           flags=E$flags ;;
     78         esac
     79       done ;;
     80     (*)
     81       if local rex='^([_a-zA-Z0-9@]+)(:?=|$)(.*)'; [[ $arg =~ $rex ]]; then
     82         local name=${BASH_REMATCH[1]#bleopt_}
     83         local var=bleopt_$name
     84         local op=${BASH_REMATCH[2]}
     85         local value=${BASH_REMATCH[3]}
     86 
     87         # check/expand variable names
     88         if [[ $op == ':=' ]]; then
     89           if [[ $var == *@* ]]; then
     90             ble/util/print "bleopt: \`${var#bleopt_}': wildcard cannot be used in the definition." >&2
     91             flags=E$flags
     92             continue
     93           fi
     94         else
     95           local ret; bleopt/expand-variable-pattern "$var"
     96 
     97           # obsolete な物は除外
     98           var=()
     99           local v i=0
    100           for v in "${ret[@]}"; do
    101             ble/is-function "bleopt/obsolete:${v#bleopt_}" && continue
    102             var[i++]=$v
    103           done
    104 
    105           # 表示目的で obsolete しかない時は obsolete でも表示
    106           [[ ${#var[@]} == 0 ]] && var=("${ret[@]}")
    107 
    108           # 適した物が見つからない場合は失敗
    109           if ((${#var[@]}==0)); then
    110             ble/util/print "bleopt: option \`$name' not found" >&2
    111             flags=E$flags
    112             continue
    113           fi
    114         fi
    115 
    116         if [[ $op ]]; then
    117           var=("${var[@]}") # #D1570: WA bash-3.0 ${scal[@]/x} bug
    118           if ((_ble_bash>=40300)) && ! shopt -q compat42; then
    119             ble/array#push specs "${var[@]/%/"=$value"}" # WA #D1570 #D1751 checked
    120           else
    121             ble/array#push specs "${var[@]/%/=$value}" # WA #D1570 #D1738 checked
    122           fi
    123         else
    124           ble/array#push pvars "${var[@]}"
    125         fi
    126       else
    127         ble/util/print "bleopt: unrecognized argument '$arg'" >&2
    128         flags=E$flags
    129       fi ;;
    130     esac
    131   done
    132 }
    133 
    134 function bleopt/changed.predicate {
    135   local cur=$1 def=_ble_opt_def_${1#bleopt_}
    136   [[ ! ${!def+set} || ${!cur} != "${!def}" ]]
    137 }
    138 
    139 ## @fn bleopt args...
    140 ##   @params[in] args
    141 ##     args は以下の内の何れかの形式を持つ。
    142 ##
    143 ##     var=value
    144 ##       既存の設定変数に値を設定する。
    145 ##       設定変数が存在しないときはエラー。
    146 ##     var:=value
    147 ##       設定変数に値を設定する。
    148 ##       設定変数が存在しないときは新しく作成する。
    149 ##     var
    150 ##       変数の設定内容を表示する
    151 ##
    152 function bleopt {
    153   local flags pvars specs
    154   bleopt/.read-arguments "$@"
    155   if [[ $flags == *E* ]]; then
    156     return 2
    157   elif [[ $flags == *H* ]]; then
    158     ble/util/print-lines \
    159       'usage: bleopt [OPTION] [NAME|NAME=VALUE|NAME:=VALUE]...' \
    160       '    Set ble.sh options. Without arguments, this prints all the settings.' \
    161       '' \
    162       '  Options' \
    163       '    --help           Print this help.' \
    164       '    -r, --reset      Reset options to the default values' \
    165       '    -I, --initialize Re-initialize settings' \
    166       '    -u, --changed    Only select changed options' \
    167       '    --color[=always|never|auto]' \
    168       '                     Change color settings.' \
    169       '' \
    170       '  Arguments' \
    171       '    NAME        Print the value of the option.' \
    172       '    NAME=VALUE  Set the value to the option.' \
    173       '    NAME:=VALUE Set or create the value to the option.' \
    174       '' \
    175       '  NAME can contain "@" as a wildcard.' \
    176       ''
    177     return 0
    178   fi
    179 
    180   if ((${#pvars[@]}==0&&${#specs[@]}==0)); then
    181     local var ip=0
    182     for var in "${!bleopt_@}"; do
    183       ble/is-function "bleopt/obsolete:${var#bleopt_}" && continue
    184       pvars[ip++]=$var
    185     done
    186   fi
    187 
    188   [[ $flags == *u* ]] &&
    189     ble/array#filter pvars bleopt/changed.predicate
    190 
    191   # --reset: pvars を全て既定値の設定に読み替える
    192   if [[ $flags == *r* ]]; then
    193     local var
    194     for var in "${pvars[@]}"; do
    195       local name=${var#bleopt_}
    196       ble/is-function bleopt/obsolete:"$name" && continue
    197       local def=_ble_opt_def_$name
    198       [[ ${!def+set} && ${!var-} != "${!def}" ]] &&
    199         ble/array#push specs "$var=${!def}"
    200     done
    201     pvars=()
    202   elif [[ $flags == *I* ]]; then
    203     local var
    204     for var in "${pvars[@]}"; do
    205       bleopt/reinitialize "${var#bleopt_}"
    206     done
    207     pvars=()
    208   fi
    209 
    210   if ((${#specs[@]})); then
    211     local spec
    212     for spec in "${specs[@]}"; do
    213       local var=${spec%%=*} value=${spec#*=}
    214       [[ ${!var+set} && ${!var} == "$value" ]] && continue
    215       if ble/is-function bleopt/check:"${var#bleopt_}"; then
    216         local bleopt_source=${BASH_SOURCE[1]}
    217         local bleopt_lineno=${BASH_LINENO[0]}
    218         if ! bleopt/check:"${var#bleopt_}"; then
    219           flags=E$flags
    220           continue
    221         fi
    222       fi
    223       builtin eval -- "$var=\"\$value\""
    224     done
    225   fi
    226 
    227   if ((${#pvars[@]})); then
    228     # 着色
    229     local sgr0= sgr1= sgr2= sgr3=
    230     if [[ $flags == *c* || $flags != *n* && -t 1 ]]; then
    231       local ret
    232       ble/color/face2sgr command_function; sgr1=$ret
    233       ble/color/face2sgr syntax_varname; sgr2=$ret
    234       ble/color/face2sgr syntax_quoted; sgr3=$ret
    235       sgr0=$_ble_term_sgr0
    236     fi
    237 
    238     local var
    239     for var in "${pvars[@]}"; do
    240       local ret
    241       ble/string#quote-word "${!var}" sgrq="$sgr3":sgr0="$sgr0"
    242       ble/util/print "${sgr1}bleopt$sgr0 ${sgr2}${var#bleopt_}$sgr0=$ret"
    243     done
    244   fi
    245 
    246   [[ $flags != *E* ]]
    247 }
    248 
    249 function bleopt/declare/.check-renamed-option {
    250   var=bleopt_$2
    251 
    252   local sgr0= sgr1= sgr2= sgr3=
    253   if [[ -t 2 ]]; then
    254     sgr0=$_ble_term_sgr0
    255     sgr1=${_ble_term_setaf[2]}
    256     sgr2=${_ble_term_setaf[1]}$_ble_term_bold
    257     sgr3=${_ble_term_setaf[4]}$_ble_term_bold
    258   fi
    259 
    260   local locate=$sgr1${BASH_SOURCE[3]-'(stdin)'}:${BASH_LINENO[2]}$sgr0
    261   ble/util/print "$locate (bleopt): The option '$sgr2$1$sgr0' has been renamed. Please use '$sgr3$2$sgr0' instead." >&2
    262   if ble/is-function bleopt/check:"$2"; then
    263     bleopt/check:"$2"
    264     return "$?"
    265   fi
    266   return 0
    267 }
    268 function bleopt/declare {
    269   local type=$1 name=bleopt_$2 default_value=$3
    270   # local set=${!name+set} value=${!name-}
    271   case $type in
    272   (-o)
    273     builtin eval -- "$name='[obsolete: renamed to $3]'"
    274     builtin eval -- "function bleopt/check:$2 { bleopt/declare/.check-renamed-option $2 $3; }"
    275     builtin eval -- "function bleopt/obsolete:$2 { :; }" ;;
    276   (-n)
    277     builtin eval -- "_ble_opt_def_$2=\$3"
    278     builtin eval -- ": \"\${$name:=\$default_value}\"" ;;
    279   (*)
    280     builtin eval -- "_ble_opt_def_$2=\$3"
    281     builtin eval -- ": \"\${$name=\$default_value}\"" ;;
    282   esac
    283   return 0
    284 }
    285 function bleopt/reinitialize {
    286   local name=$1
    287   local defname=_ble_opt_def_$name
    288   local varname=bleopt_$name
    289   [[ ${!defname+set} ]] || return 1
    290   [[ ${!varname} == "${!defname}" ]] && return 0
    291   ble/is-function bleopt/obsolete:"$name" && return 0
    292   ble/is-function bleopt/check:"$name" || return 0
    293 
    294   # 一旦値を既定値に戻して改めてチェックを行う。
    295   local value=${!varname}
    296   builtin eval -- "$varname=\$$defname"
    297   bleopt/check:"$name" &&
    298     builtin eval "$varname=\$value"
    299 }
    300 
    301 ## @bleopt input_encoding
    302 bleopt/declare -n input_encoding UTF-8
    303 function bleopt/check:input_encoding {
    304   if ! ble/is-function "ble/encoding:$value/decode"; then
    305     ble/util/print "bleopt: Invalid value input_encoding='$value'." \
    306                  "A function 'ble/encoding:$value/decode' is not defined." >&2
    307     return 1
    308   elif ! ble/is-function "ble/encoding:$value/b2c"; then
    309     ble/util/print "bleopt: Invalid value input_encoding='$value'." \
    310                  "A function 'ble/encoding:$value/b2c' is not defined." >&2
    311     return 1
    312   elif ! ble/is-function "ble/encoding:$value/c2bc"; then
    313     ble/util/print "bleopt: Invalid value input_encoding='$value'." \
    314                  "A function 'ble/encoding:$value/c2bc' is not defined." >&2
    315     return 1
    316   elif ! ble/is-function "ble/encoding:$value/generate-binder"; then
    317     ble/util/print "bleopt: Invalid value input_encoding='$value'." \
    318                  "A function 'ble/encoding:$value/generate-binder' is not defined." >&2
    319     return 1
    320   elif ! ble/is-function "ble/encoding:$value/is-intermediate"; then
    321     ble/util/print "bleopt: Invalid value input_encoding='$value'." \
    322                  "A function 'ble/encoding:$value/is-intermediate' is not defined." >&2
    323     return 1
    324   fi
    325 
    326   # Note: ble/encoding:$value/clear は optional な設定である。
    327 
    328   if [[ $bleopt_input_encoding != "$value" ]]; then
    329     local bleopt_input_encoding=$value
    330     ble/decode/rebind
    331   fi
    332   return 0
    333 }
    334 
    335 ## @bleopt internal_stackdump_enabled
    336 ##   エラーが起こった時に関数呼出の構造を標準エラー出力に出力するかどうかを制御する。
    337 ##   算術式評価によって非零の値になる場合にエラーを出力する。
    338 ##   それ以外の場合にはエラーを出力しない。
    339 bleopt/declare -v internal_stackdump_enabled 0
    340 
    341 ## @bleopt openat_base
    342 ##   bash-4.1 未満で exec {var}>foo が使えない時に ble.sh で内部的に fd を割り当てる。
    343 ##   この時の fd の base を指定する。bleopt_openat_base, bleopt_openat_base+1, ...
    344 ##   という具合に順番に使用される。既定値は 30 である。
    345 bleopt/declare -n openat_base 30
    346 
    347 ## @bleopt pager
    348 bleopt/declare -v pager ''
    349 
    350 ## @bleopt editor
    351 bleopt/declare -v editor ''
    352 
    353 shopt -s checkwinsize
    354 
    355 #------------------------------------------------------------------------------
    356 # util
    357 
    358 function ble/util/setexit { return "$1"; }
    359 
    360 ## @var _ble_util_upvar_setup
    361 ## @var _ble_util_upvar
    362 ##
    363 ##   これらの変数は関数を定義する時に [-v varname] の引数を認識させ、
    364 ##   関数の結果を格納する変数名を外部から指定できるようにするのに用いる。
    365 ##   使用する際は関数を以下の様に記述する。既定の格納先変数は ret となる。
    366 ##
    367 ##     function MyFunction {
    368 ##       eval "$_ble_util_upvar_setup"
    369 ##
    370 ##       ret=... # 処理を行い、変数 ret に結果を格納するコード
    371 ##               # (途中で return などすると正しく動かない事に注意)
    372 ##
    373 ##       eval "$_ble_util_upvar"
    374 ##     }
    375 ##
    376 ##   既定の格納先変数を別の名前 (以下の例では arg) にする場合は次の様にする。
    377 ##
    378 ##     function MyFunction {
    379 ##       eval "${_ble_util_upvar_setup//ret/arg}"
    380 ##
    381 ##       arg=... # 処理を行い、変数 arg に結果を格納するコード
    382 ##
    383 ##       eval "${_ble_util_upvar//ret/arg}"
    384 ##     }
    385 ##
    386 _ble_util_upvar_setup='local var=ret ret; [[ $1 == -v ]] && var=$2 && shift 2'
    387 _ble_util_upvar='local "${var%%\[*\]}" && ble/util/upvar "$var" "$ret"'
    388 if ((_ble_bash>=50000)); then
    389   function ble/util/unlocal {
    390     if shopt -q localvar_unset; then
    391       shopt -u localvar_unset
    392       builtin unset -v "$@"
    393       shopt -s localvar_unset
    394     else
    395       builtin unset -v "$@"
    396     fi
    397   }
    398   function ble/util/upvar { ble/util/unlocal "${1%%\[*\]}" && builtin eval "$1=\"\$2\""; }
    399   function ble/util/uparr { ble/util/unlocal "$1" && builtin eval "$1=(\"\${@:2}\")"; }
    400 else
    401   function ble/util/unlocal { builtin unset -v "$@"; }
    402   function ble/util/upvar { builtin unset -v "${1%%\[*\]}" && builtin eval "$1=\"\$2\""; }
    403   function ble/util/uparr { builtin unset -v "$1" && builtin eval "$1=(\"\${@:2}\")"; }
    404 fi
    405 
    406 function ble/util/save-vars {
    407   local __ble_name __ble_prefix=$1; shift
    408   for __ble_name; do
    409     if ble/is-array "$__ble_name"; then
    410       builtin eval "$__ble_prefix$__ble_name=(\"\${$__ble_name[@]}\")"
    411     else
    412       builtin eval "$__ble_prefix$__ble_name=\"\$$__ble_name\""
    413     fi
    414   done
    415 }
    416 function ble/util/restore-vars {
    417   local __ble_name __ble_prefix=$1; shift
    418   for __ble_name; do
    419     if ble/is-array "$__ble_prefix$__ble_name"; then
    420       # Note: bash-4.2 以下では set -u で空配列に対する "${arr[@]}" が失敗す
    421       # るので ${arr[@]+"${arr[@]}"} とする。
    422       builtin eval "$__ble_name=(\${$__ble_prefix$__ble_name[@]+\"\${$__ble_prefix$__ble_name[@]}\"})"
    423     else
    424       builtin eval "$__ble_name=\"\${$__ble_prefix$__ble_name-}\""
    425     fi
    426   done
    427 }
    428 
    429 #
    430 # variable, array and strings
    431 #
    432 
    433 ## @fn ble/variable#get-attr varname
    434 ##   指定した変数の属性を取得します。
    435 ##   @var[out] attr
    436 if ((_ble_bash>=40400)); then
    437   function ble/variable#get-attr {
    438     if [[ $1 == -v ]]; then
    439       builtin eval -- "$2=\${!3@a}"
    440     else
    441       attr=${!1@a}
    442     fi
    443   }
    444   function ble/variable#has-attr { [[ ${!1@a} == *["$2"]* ]]; }
    445 else
    446   function ble/variable#get-attr {
    447     if [[ $1 == -v ]]; then
    448       local __ble_var=$2 __ble_tmp=$3
    449     else
    450       local __ble_var=attr __ble_tmp=$1
    451     fi
    452     ble/util/assign __ble_tmp 'declare -p "$__ble_tmp" 2>/dev/null'
    453     local rex='^declare -([a-zA-Z]*)'; [[ $__ble_tmp =~ $rex ]]
    454     builtin eval -- "$__ble_var=\${BASH_REMATCH[1]-}"
    455     return 0
    456   }
    457   function ble/variable#has-attr {
    458     local __ble_tmp=$1
    459     ble/util/assign __ble_tmp 'declare -p "$__ble_tmp" 2>/dev/null'
    460     local rex='^declare -([a-zA-Z]*)'
    461     [[ $__ble_tmp =~ $rex && ${BASH_REMATCH[1]} == *["$2"]* ]]
    462   }
    463 fi
    464 function ble/is-inttype { ble/variable#has-attr "$1" i; }
    465 function ble/is-readonly { ble/variable#has-attr "$1" r; }
    466 function ble/is-transformed { ble/variable#has-attr "$1" luc; }
    467 
    468 function ble/variable#is-declared { [[ ${!1+set} ]] || declare -p "$1" &>/dev/null; }
    469 function ble/variable#is-global/.test { ! local "$1"; }
    470 function ble/variable#is-global {
    471   (builtin readonly "$1"; ble/variable#is-global/.test "$1") 2>/dev/null
    472 }
    473 function ble/variable#copy-state {
    474   local src=$1 dst=$2
    475   if [[ ${!src+set} ]]; then
    476     builtin eval -- "$dst=\${$src}"
    477   else
    478     builtin unset -v "$dst[0]" 2>/dev/null || builtin unset -v "$dst"
    479   fi
    480 }
    481 
    482 _ble_array_prototype=()
    483 function ble/array#reserve-prototype {
    484   local n=$1 i
    485   for ((i=${#_ble_array_prototype[@]};i<n;i++)); do
    486     _ble_array_prototype[i]=
    487   done
    488 }
    489 
    490 ## @fn ble/is-array arr
    491 ##
    492 ##   Note: これに関しては様々な実現方法が考えられるが大体余りうまく動かない。
    493 ##
    494 ##   * ! declare +a arr だと現在の関数のローカル変数の判定になってしまう。
    495 ##   * bash-4.2 以降では ! declare -g +a arr を使えるが、
    496 ##     これだと呼び出し元の関数で定義されている配列が見えない。
    497 ##     というか現在のスコープの配列も見えない。
    498 ##   * 今の所は compgen -A arrayvar を用いているが、
    499 ##     この方法だと bash-4.3 以降では連想配列も配列と判定され、
    500 ##     bash-4.2 以下では連想配列は配列とはならない。
    501 if ((_ble_bash>=40400)); then
    502   function ble/is-array { [[ ${!1@a} == *a* ]]; }
    503   function ble/is-assoc { [[ ${!1@a} == *A* ]]; }
    504 else
    505   function ble/is-array {
    506     local "decl$1"
    507     ble/util/assign "decl$1" "declare -p $1" 2>/dev/null || return 1
    508     local rex='^declare -[b-zA-Z]*a'
    509     builtin eval "[[ \$decl$1 =~ \$rex ]]"
    510   }
    511   function ble/is-assoc {
    512     local "decl$1"
    513     ble/util/assign "decl$1" "declare -p $1" 2>/dev/null || return 1
    514     local rex='^declare -[a-zB-Z]*A'
    515     builtin eval "[[ \$decl$1 =~ \$rex ]]"
    516   }
    517   ((_ble_bash>=40000)) ||
    518     function ble/is-assoc { false; }
    519 fi
    520 
    521 ## @fn ble/array#set arr value...
    522 ##   配列に値を設定します。
    523 ##   Bash 4.4 で arr2=("${arr1[@]}") が遅い問題を回避する為の関数です。
    524 function ble/array#set { builtin eval "$1=(\"\${@:2}\")"; }
    525 
    526 ## @fn ble/array#push arr value...
    527 if ((_ble_bash>=40000)); then
    528   function ble/array#push {
    529     builtin eval "$1+=(\"\${@:2}\")"
    530   }
    531 elif ((_ble_bash>=30100)); then
    532   function ble/array#push {
    533     # Note (workaround Bash 3.1/3.2 bug): #D1198
    534     #   何故か a=("${@:2}") は IFS に特別な物が設定されていると
    535     #   "${*:2}" と同じ振る舞いになってしまう。
    536     IFS=$_ble_term_IFS builtin eval "$1+=(\"\${@:2}\")"
    537   }
    538 else
    539   function ble/array#push {
    540     while (($#>=2)); do
    541       builtin eval -- "$1[\${#$1[@]}]=\"\$2\""
    542       set -- "$1" "${@:3}"
    543     done
    544   }
    545 fi
    546 ## @fn ble/array#pop arr
    547 ##   @var[out] ret
    548 function ble/array#pop {
    549   builtin eval "local i$1=\$((\${#$1[@]}-1))"
    550   if ((i$1>=0)); then
    551     builtin eval "ret=\${$1[i$1]}"
    552     builtin unset -v "$1[i$1]"
    553     return 0
    554   else
    555     ret=
    556     return 1
    557   fi
    558 }
    559 ## @fn ble/array#unshift arr value...
    560 function ble/array#unshift {
    561   builtin eval -- "$1=(\"\${@:2}\" \"\${$1[@]}\")"
    562 }
    563 ## @fn ble/array#shift arr count
    564 function ble/array#shift {
    565   # Note: Bash 4.3 以下では ${arr[@]:${2:-1}} が offset='${2'
    566   # length='-1' に解釈されるので、先に算術式展開させる。
    567   builtin eval -- "$1=(\"\${$1[@]:$((${2:-1}))}\")"
    568 }
    569 ## @fn ble/array#reverse arr
    570 function ble/array#reverse {
    571   builtin eval "
    572   set -- \"\${$1[@]}\"; $1=()
    573   local e$1 i$1=\$#
    574   for e$1; do $1[--i$1]=\"\$e$1\"; done"
    575 }
    576 
    577 ## @fn ble/array#insert-at arr index elements...
    578 function ble/array#insert-at {
    579   builtin eval "$1=(\"\${$1[@]::$2}\" \"\${@:3}\" \"\${$1[@]:$2}\")"
    580 }
    581 ## @fn ble/array#insert-after arr needle elements...
    582 function ble/array#insert-after {
    583   local _ble_local_script='
    584     local iARR=0 eARR aARR=
    585     for eARR in "${ARR[@]}"; do
    586       ((iARR++))
    587       [[ $eARR == "$2" ]] && aARR=iARR && break
    588     done
    589     [[ $aARR ]] && ble/array#insert-at "$1" "$aARR" "${@:3}"
    590   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    591 }
    592 ## @fn ble/array#insert-before arr needle elements...
    593 function ble/array#insert-before {
    594   local _ble_local_script='
    595     local iARR=0 eARR aARR=
    596     for eARR in "${ARR[@]}"; do
    597       [[ $eARR == "$2" ]] && aARR=iARR && break
    598       ((iARR++))
    599     done
    600     [[ $aARR ]] && ble/array#insert-at "$1" "$aARR" "${@:3}"
    601   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    602 }
    603 ## @fn ble/array#filter arr predicate
    604 ##   @param[in] predicate
    605 ##     When a function name is specified, the target string is passed
    606 ##     to the function as the first argument.  Otherwise, the value of
    607 ##     PREDICATE is treated as a command string where the argument can
    608 ##     be referenced as $1.
    609 function ble/array#filter/.eval {
    610   builtin eval -- "$_ble_local_predicate_cmd"
    611 }
    612 function ble/array#filter {
    613   local _ble_local_predicate=$2
    614   if [[ $2 == *'$'* ]] || ! ble/is-function "$2"; then
    615     _ble_local_predicate=ble/array#filter/.eval
    616     _ble_local_predicate_cmd=$2
    617   fi
    618 
    619   local _ble_local_script='
    620     local -a aARR=() eARR
    621     for eARR in "${ARR[@]}"; do
    622       "$_ble_local_predicate" "$eARR" && ble/array#push "aARR" "$eARR"
    623     done
    624     ARR=("${aARR[@]}")
    625   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    626 }
    627 function ble/array#filter/not.predicate { ! "$_ble_local_pred" "$1"; }
    628 function ble/array#remove-if {
    629   local _ble_local_pred=$2
    630   ble/array#filter "$1" ble/array#filter/not.predicate
    631 }
    632 ## @fn ble/array#filter-by-regex arr regex
    633 function ble/array#filter/regex.predicate { [[ $1 =~ $_ble_local_rex ]]; }
    634 function ble/array#filter-by-regex {
    635   local _ble_local_rex=$2
    636   local LC_ALL= LC_COLLATE=C 2>/dev/null
    637   ble/array#filter "$1" ble/array#filter/regex.predicate
    638   ble/util/unlocal LC_COLLATE LC_ALL 2>/dev/null
    639 }
    640 function ble/array#remove-by-regex {
    641   local _ble_local_rex=$2
    642   local LC_ALL= LC_COLLATE=C 2>/dev/null
    643   ble/array#remove-if "$1" ble/array#filter/regex.predicate
    644   ble/util/unlocal LC_COLLATE LC_ALL 2>/dev/null
    645 }
    646 function ble/array#filter/glob.predicate { [[ $1 == $_ble_local_glob ]]; }
    647 function ble/array#filter-by-glob {
    648   local _ble_local_glob=$2
    649   local LC_ALL= LC_COLLATE=C 2>/dev/null
    650   ble/array#filter "$1" ble/array#filter/glob.predicate
    651   ble/util/unlocal LC_COLLATE LC_ALL 2>/dev/null
    652 }
    653 function ble/array#remove-by-glob {
    654   local _ble_local_glob=$2
    655   local LC_ALL= LC_COLLATE=C 2>/dev/null
    656   ble/array#remove-if "$1" ble/array#filter/glob.predicate
    657   ble/util/unlocal LC_COLLATE LC_ALL 2>/dev/null
    658 }
    659 ## @fn ble/array#remove arr element
    660 function ble/array#remove/.predicate { [[ $1 != "$_ble_local_value" ]]; }
    661 function ble/array#remove {
    662   local _ble_local_value=$2
    663   ble/array#filter "$1" ble/array#remove/.predicate
    664 }
    665 ## @fn ble/array#index arr needle
    666 ##   @var[out] ret
    667 function ble/array#index {
    668   local _ble_local_script='
    669     local eARR iARR=0
    670     for eARR in "${ARR[@]}"; do
    671       if [[ $eARR == "$2" ]]; then ret=$iARR; return 0; fi
    672       ((++iARR))
    673     done
    674     ret=-1; return 1
    675   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    676 }
    677 ## @fn ble/array#last-index arr needle
    678 ##   @var[out] ret
    679 function ble/array#last-index {
    680   local _ble_local_script='
    681     local eARR iARR=${#ARR[@]}
    682     while ((iARR--)); do
    683       [[ ${ARR[iARR]} == "$2" ]] && { ret=$iARR; return 0; }
    684     done
    685     ret=-1; return 1
    686   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    687 }
    688 ## @fn ble/array#remove-at arr index
    689 function ble/array#remove-at {
    690   local _ble_local_script='
    691     builtin unset -v "ARR[$2]"
    692     ARR=("${ARR[@]}")
    693   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    694 }
    695 function ble/array#fill-range {
    696   ble/array#reserve-prototype "$(($3-$2))"
    697   local _ble_script='
    698       local -a sARR; sARR=("${_ble_array_prototype[@]::$3-$2}")
    699       ARR=("${ARR[@]::$2}" "${sARR[@]/#/$4}" "${ARR[@]:$3}")' # WA #D1570 #D1738 checked
    700   ((_ble_bash>=40300)) && ! shopt -q compat42 &&
    701     _ble_script=${_ble_script//'$4'/'"$4"'}
    702   builtin eval -- "${_ble_script//ARR/$1}"
    703 }
    704 ## @fn ble/idict#replace arr needle [replacement]
    705 ##   needle に一致する要素を全て replacement に置換します。
    706 ##   replacement が指定されていない時は該当要素を unset します。
    707 ##   @var[in] arr
    708 ##   @var[in] needle
    709 ##   @var[in,opt] replacement
    710 function ble/idict#replace {
    711   local _ble_local_script='
    712     local iARR=0 extARR=1
    713     for iARR in "${!ARR[@]}"; do
    714       [[ ${ARR[iARR]} == "$2" ]] || continue
    715       extARR=0
    716       if (($#>=3)); then
    717         ARR[iARR]=$3
    718       else
    719         builtin unset -v '\''ARR[iARR]'\''
    720       fi
    721     done
    722     return "$extARR"
    723   '; builtin eval -- "${_ble_local_script//ARR/$1}"
    724 }
    725 
    726 function ble/idict#copy {
    727   local _ble_script='
    728     '$1'=()
    729     local i'$1$2'
    730     for i'$1$2' in "${!'$2'[@]}"; do
    731       '$1'[i'$1$2']=${'$2'[i'$1$2']}
    732     done'
    733   builtin eval -- "$_ble_script"
    734 }
    735 
    736 _ble_string_prototype='        '
    737 function ble/string#reserve-prototype {
    738   local n=$1 c
    739   for ((c=${#_ble_string_prototype};c<n;c*=2)); do
    740     _ble_string_prototype=$_ble_string_prototype$_ble_string_prototype
    741   done
    742 }
    743 
    744 ## @fn ble/string#repeat str count
    745 ##   @param[in] str
    746 ##   @param[in] count
    747 ##   @var[out] ret
    748 function ble/string#repeat {
    749   ble/string#reserve-prototype "$2"
    750   ret=${_ble_string_prototype::$2}
    751   ret=${ret// /"$1"}
    752 }
    753 
    754 ## @fn ble/string#common-prefix a b
    755 ##   @param[in] a b
    756 ##   @var[out] ret
    757 function ble/string#common-prefix {
    758   local a=$1 b=$2
    759   ((${#a}>${#b})) && local a=$b b=$a
    760   b=${b::${#a}}
    761   if [[ $a == "$b" ]]; then
    762     ret=$a
    763     return 0
    764   fi
    765 
    766   # l <= 解 < u, (${a:u}: 一致しない, ${a:l} 一致する)
    767   local l=0 u=${#a} m
    768   while ((l+1<u)); do
    769     ((m=(l+u)/2))
    770     if [[ ${a::m} == "${b::m}" ]]; then
    771       ((l=m))
    772     else
    773       ((u=m))
    774     fi
    775   done
    776 
    777   ret=${a::l}
    778 }
    779 
    780 ## @fn ble/string#common-suffix a b
    781 ##   @param[in] a b
    782 ##   @var[out] ret
    783 function ble/string#common-suffix {
    784   local a=$1 b=$2
    785   ((${#a}>${#b})) && local a=$b b=$a
    786   b=${b:${#b}-${#a}}
    787   if [[ $a == "$b" ]]; then
    788     ret=$a
    789     return 0
    790   fi
    791 
    792   # l < 解 <= u, (${a:l}: 一致しない, ${a:u} 一致する)
    793   local l=0 u=${#a} m
    794   while ((l+1<u)); do
    795     ((m=(l+u+1)/2))
    796     if [[ ${a:m} == "${b:m}" ]]; then
    797       ((u=m))
    798     else
    799       ((l=m))
    800     fi
    801   done
    802 
    803   ret=${a:u}
    804 }
    805 
    806 ## @fn ble/string#split arr sep str...
    807 ##   文字列を分割します。
    808 ##   空白類を分割に用いた場合は、空要素は削除されます。
    809 ##
    810 ##   @param[out] arr 分割した文字列を格納する配列名を指定します。
    811 ##   @param[in]  sep 分割に使用する文字を指定します。
    812 ##   @param[in]  str 分割する文字列を指定します。
    813 ##
    814 function ble/string#split {
    815   local IFS=$2
    816   if [[ -o noglob ]]; then
    817     # Note: 末尾の sep が無視されない様に、末尾に手で sep を 1 個追加している。
    818     builtin eval "$1=(\$3\$2)"
    819   else
    820     set -f
    821     builtin eval "$1=(\$3\$2)"
    822     set +f
    823   fi
    824 }
    825 function ble/string#split-words {
    826   local IFS=$_ble_term_IFS
    827   if [[ -o noglob ]]; then
    828     builtin eval "$1=(\$2)"
    829   else
    830     set -f
    831     builtin eval "$1=(\$2)"
    832     set +f
    833   fi
    834 }
    835 ## @fn ble/string#split-lines arr text
    836 ##   文字列を行に分割します。空行も省略されません。
    837 ##
    838 ##   @param[out] arr  分割した文字列を格納する配列名を指定します。
    839 ##   @param[in]  text 分割する文字列を指定します。
    840 ##   @var[out] ret
    841 ##
    842 if ((_ble_bash>=40000)); then
    843   function ble/string#split-lines {
    844     mapfile -t "$1" <<< "$2"
    845   }
    846 else
    847   function ble/string#split-lines {
    848     ble/util/mapfile "$1" <<< "$2"
    849   }
    850 fi
    851 ## @fn ble/string#count-char text chars
    852 ##   @param[in] text
    853 ##   @param[in] chars
    854 ##     検索対象の文字の集合を指定します。
    855 ##   @var[out] ret
    856 function ble/string#count-char {
    857   local text=$1 char=$2
    858   text=${text//[!"$char"]}
    859   ret=${#text}
    860 }
    861 
    862 ## @fn ble/string#count-string text string
    863 ##   @var[out] ret
    864 function ble/string#count-string {
    865   local text=${1//"$2"}
    866   ((ret=(${#1}-${#text})/${#2}))
    867 }
    868 
    869 ## @fn ble/string#index-of text needle [n]
    870 ##   @param[in] text
    871 ##   @param[in] needle
    872 ##   @param[in] n
    873 ##     この引数を指定したとき n 番目の一致を検索します。
    874 ##   @var[out] ret
    875 ##     一致した場合に見つかった位置を返します。
    876 ##     見つからなかった場合に -1 を返します。
    877 ##   @exit
    878 ##     一致した場合に成功し、見つからなかった場合に失敗します。
    879 function ble/string#index-of {
    880   local haystack=$1 needle=$2 count=${3:-1}
    881   ble/string#repeat '*"$needle"' "$count"; local pattern=$ret
    882   builtin eval "local transformed=\${haystack#$pattern}"
    883   ((ret=${#haystack}-${#transformed}-${#needle},
    884     ret<0&&(ret=-1),ret>=0))
    885 }
    886 
    887 ## @fn ble/string#last-index-of text needle [n]
    888 ##   @param[in] text
    889 ##   @param[in] needle
    890 ##   @param[in] n
    891 ##     この引数を指定したとき n 番目の一致を検索します。
    892 ##   @var[out] ret
    893 function ble/string#last-index-of {
    894   local haystack=$1 needle=$2 count=${3:-1}
    895   ble/string#repeat '"$needle"*' "$count"; local pattern=$ret
    896   builtin eval "local transformed=\${haystack%$pattern}"
    897   if [[ $transformed == "$haystack" ]]; then
    898     ret=-1
    899   else
    900     ret=${#transformed}
    901   fi
    902   ((ret>=0))
    903 }
    904 
    905 ## @fn ble/string#toggle-case text
    906 ## @fn ble/string#toupper text
    907 ## @fn ble/string#tolower text
    908 ##   @param[in] text
    909 ##   @var[out] ret
    910 _ble_util_string_lower_list=abcdefghijklmnopqrstuvwxyz
    911 _ble_util_string_upper_list=ABCDEFGHIJKLMNOPQRSTUVWXYZ
    912 function ble/string#toggle-case.impl {
    913   local LC_ALL= LC_COLLATE=C
    914   local text=$1 ch i
    915   local -a buff
    916   for ((i=0;i<${#text};i++)); do
    917     ch=${text:i:1}
    918     if [[ $ch == [A-Z] ]]; then
    919       ch=${_ble_util_string_upper_list%%"$ch"*}
    920       ch=${_ble_util_string_lower_list:${#ch}:1}
    921     elif [[ $ch == [a-z] ]]; then
    922       ch=${_ble_util_string_lower_list%%"$ch"*}
    923       ch=${_ble_util_string_upper_list:${#ch}:1}
    924     fi
    925     ble/array#push buff "$ch"
    926   done
    927   IFS= builtin eval 'ret="${buff[*]-}"'
    928 }
    929 function ble/string#toggle-case {
    930   ble/string#toggle-case.impl "$1" 2>/dev/null # suppress locale error #D1440
    931 }
    932 ## @fn ble/string#tolower text
    933 ## @fn ble/string#toupper text
    934 ##   @var[out] ret
    935 if ((_ble_bash>=40000)); then
    936   function ble/string#tolower { ret=${1,,}; }
    937   function ble/string#toupper { ret=${1^^}; }
    938 else
    939   function ble/string#tolower.impl {
    940     local LC_ALL= LC_COLLATE=C
    941     local i text=$1 ch
    942     local -a buff=()
    943     for ((i=0;i<${#text};i++)); do
    944       ch=${text:i:1}
    945       if [[ $ch == [A-Z] ]]; then
    946         ch=${_ble_util_string_upper_list%%"$ch"*}
    947         ch=${_ble_util_string_lower_list:${#ch}:1}
    948       fi
    949       ble/array#push buff "$ch"
    950     done
    951     IFS= builtin eval 'ret="${buff[*]-}"'
    952   }
    953   function ble/string#toupper.impl {
    954     local LC_ALL= LC_COLLATE=C
    955     local i text=$1 ch
    956     local -a buff=()
    957     for ((i=0;i<${#text};i++)); do
    958       ch=${text:i:1}
    959       if [[ $ch == [a-z] ]]; then
    960         ch=${_ble_util_string_lower_list%%"$ch"*}
    961         ch=${_ble_util_string_upper_list:${#ch}:1}
    962       fi
    963       ble/array#push buff "$ch"
    964     done
    965     IFS= builtin eval 'ret="${buff[*]-}"'
    966   }
    967   function ble/string#tolower {
    968     ble/string#tolower.impl "$1" 2>/dev/null # suppress locale error #D1440
    969   }
    970   function ble/string#toupper {
    971     ble/string#toupper.impl "$1" 2>/dev/null # suppress locale error #D1440
    972   }
    973 fi
    974 
    975 function ble/string#capitalize {
    976   local tail=$1
    977 
    978   # prefix
    979   local rex='^[^a-zA-Z0-9]*'
    980   [[ $tail =~ $rex ]]
    981   local out=$BASH_REMATCH
    982   tail=${tail:${#BASH_REMATCH}}
    983 
    984   # words
    985   rex='^[a-zA-Z0-9]+[^a-zA-Z0-9]*'
    986   while [[ $tail =~ $rex ]]; do
    987     local rematch=$BASH_REMATCH
    988     ble/string#toupper "${rematch::1}"; out=$out$ret
    989     ble/string#tolower "${rematch:1}" ; out=$out$ret
    990     tail=${tail:${#rematch}}
    991   done
    992   ret=$out$tail
    993 }
    994 
    995 ## @fn ble/string#trim text
    996 ##   @var[out] ret
    997 function ble/string#trim {
    998   ret=$1
    999   local rex=$'^[ \t\n]+'
   1000   [[ $ret =~ $rex ]] && ret=${ret:${#BASH_REMATCH}}
   1001   local rex=$'[ \t\n]+$'
   1002   [[ $ret =~ $rex ]] && ret=${ret::${#ret}-${#BASH_REMATCH}}
   1003 }
   1004 ## @fn ble/string#ltrim text
   1005 ##   @var[out] ret
   1006 function ble/string#ltrim {
   1007   ret=$1
   1008   local rex=$'^[ \t\n]+'
   1009   [[ $ret =~ $rex ]] && ret=${ret:${#BASH_REMATCH}}
   1010 }
   1011 ## @fn ble/string#rtrim text
   1012 ##   @var[out] ret
   1013 function ble/string#rtrim {
   1014   ret=$1
   1015   local rex=$'[ \t\n]+$'
   1016   [[ $ret =~ $rex ]] && ret=${ret::${#ret}-${#BASH_REMATCH}}
   1017 }
   1018 
   1019 ## @fn ble/string#escape-characters text chars1 [chars2]
   1020 ##   @param[in]     text
   1021 ##   @param[in]     chars1
   1022 ##   @param[in,opt] chars2
   1023 ##   @var[out] ret
   1024 if ((_ble_bash>=50200)); then
   1025   function ble/string#escape-characters {
   1026     ret=$1
   1027     if [[ $ret == *["$2"]* ]]; then
   1028       if [[ ! $3 ]]; then
   1029         local patsub_replacement=
   1030         shopt -q patsub_replacement && patsub_replacement=1
   1031         shopt -s patsub_replacement
   1032         ret=${ret//["$2"]/\\&} # #D1738 patsub_replacement
   1033         [[ $patsub_replacement ]] || shopt -u patsub_replacement
   1034       else
   1035         local chars1=$2 chars2=${3:-$2}
   1036         local i n=${#chars1} a b
   1037         for ((i=0;i<n;i++)); do
   1038           a=${chars1:i:1} b=\\${chars2:i:1} ret=${ret//"$a"/"$b"}
   1039         done
   1040       fi
   1041     fi
   1042   }
   1043 else
   1044   function ble/string#escape-characters {
   1045     ret=$1
   1046     if [[ $ret == *["$2"]* ]]; then
   1047       local chars1=$2 chars2=${3:-$2}
   1048       local i n=${#chars1} a b
   1049       for ((i=0;i<n;i++)); do
   1050         a=${chars1:i:1} b=\\${chars2:i:1} ret=${ret//"$a"/"$b"}
   1051       done
   1052     fi
   1053   }
   1054 fi
   1055 
   1056 
   1057 ## @fn ble/string#escape-for-sed-regex text
   1058 ## @fn ble/string#escape-for-awk-regex text
   1059 ## @fn ble/string#escape-for-extended-regex text
   1060 ## @fn ble/string#escape-for-bash-glob text
   1061 ## @fn ble/string#escape-for-bash-single-quote text
   1062 ## @fn ble/string#escape-for-bash-double-quote text
   1063 ## @fn ble/string#escape-for-bash-escape-string text
   1064 ##   @param[in] text
   1065 ##   @var[out] ret
   1066 function ble/string#escape-for-sed-regex {
   1067   ble/string#escape-characters "$1" '\.[*^$/'
   1068 }
   1069 function ble/string#escape-for-awk-regex {
   1070   ble/string#escape-characters "$1" '\.[*?+|^$(){}/'
   1071 }
   1072 function ble/string#escape-for-extended-regex {
   1073   ble/string#escape-characters "$1" '\.[*?+|^$(){}'
   1074 }
   1075 function ble/string#escape-for-bash-glob {
   1076   ble/string#escape-characters "$1" '\*?[('
   1077 }
   1078 function ble/string#escape-for-bash-single-quote {
   1079   local q="'" Q="'\''"
   1080   ret=${1//$q/$Q}
   1081 }
   1082 function ble/string#escape-for-bash-double-quote {
   1083   ble/string#escape-characters "$1" '\"$`'
   1084   local a b
   1085   a='!' b='"\!"' ret=${ret//"$a"/"$b"} # WA #D1751 checked
   1086 }
   1087 function ble/string#escape-for-bash-escape-string {
   1088   ble/string#escape-characters "$1" $'\\\a\b\e\f\n\r\t\v'\' '\abefnrtv'\'
   1089 }
   1090 ## @fn ble/string#escape-for-bash-specialchars text flags
   1091 ##   @param[in] text
   1092 ##   @param[in] flags
   1093 ##     c 単語中でチルダ展開を誘導する文字をエスケープします。
   1094 ##     b ブレース展開の文字もエスケープします。
   1095 ##     H 語頭の #, ~ のエスケープをしません。
   1096 ##     T 語頭のチルダのエスケープをしません。
   1097 ##     G グロブ文字をエスケープしません。
   1098 ##   @var[out] ret
   1099 function ble/string#escape-for-bash-specialchars {
   1100   local chars='\ "'\''`$|&;<>()!^'
   1101   # Note: = と : は文法的にはエスケープは不要だが
   1102   #   補完の際の COMP_WORDBREAKS を避ける為に必要である。
   1103   [[ $2 != *G* ]] && chars=$chars'*?['
   1104   [[ $2 == *c* ]] && chars=$chars'=:'
   1105   [[ $2 == *b* ]] && chars=$chars'{,}'
   1106   ble/string#escape-characters "$1" "$chars"
   1107   [[ $2 != *[HT]* && $ret == '~'* ]] && ret=\\$ret
   1108   [[ $2 != *H* && $ret == '#'* ]] && ret=\\$ret
   1109   if [[ $ret == *[$']\n\t']* ]]; then
   1110     local a b
   1111     a=']'   b=\\$a     ret=${ret//"$a"/"$b"}
   1112     a=$'\n' b="\$'\n'" ret=${ret//"$a"/"$b"} # WA #D1751 checked
   1113     a=$'\t' b=$'\\\t'  ret=${ret//"$a"/"$b"}
   1114   fi
   1115 
   1116   # 上の処理で extglob の ( も quote されてしまうので G の時には戻す。
   1117   if [[ $2 == *G* ]] && shopt -q extglob; then
   1118     local a b
   1119     a='!\(' b='!(' ret=${ret//"$a"/"$b"}
   1120     a='@\(' b='@(' ret=${ret//"$a"/"$b"}
   1121     a='?\(' b='?(' ret=${ret//"$a"/"$b"}
   1122     a='*\(' b='*(' ret=${ret//"$a"/"$b"}
   1123     a='+\(' b='+(' ret=${ret//"$a"/"$b"}
   1124   fi
   1125 }
   1126 
   1127 ## @fn ble/string#escape-for-display str [opts]
   1128 ##   str に含まれる制御文字を ^A などのキャレット表記に置き換えます。
   1129 ##
   1130 ##   @param[in] str
   1131 ##   @param[in] opts
   1132 ##     revert
   1133 ##       キャレット表記を反転表示します。
   1134 ##     sgr1=*
   1135 ##       キャレット表記に用いる SGR シーケンスを指定します。
   1136 ##       キャレット表記の開始に挿入されます。
   1137 ##     sgr0=*
   1138 ##       キャレット表記以外の部分に用いる地の SGR シーケンスを指定します。
   1139 ##       キャレット表記の終端に挿入されます。
   1140 ##
   1141 function ble/string#escape-for-display {
   1142   local head= tail=$1 opts=$2
   1143 
   1144   local sgr0= sgr1=
   1145   local rex_csi=$'\e\\[[ -?]*[@-~]'
   1146   if [[ :$opts: == *:revert:* ]]; then
   1147     ble/color/g2sgr "$_ble_color_gflags_Revert"
   1148     sgr1=$ret sgr0=$_ble_term_sgr0
   1149   else
   1150     if local rex=':sgr1=(('$rex_csi'|[^:])*):'; [[ :$opts: =~ $rex ]]; then
   1151       sgr1=${BASH_REMATCH[1]} sgr0=$_ble_term_sgr0
   1152     fi
   1153     if local rex=':sgr0=(('$rex_csi'|[^:])*):'; [[ :$opts: =~ $rex ]]; then
   1154       sgr0=${BASH_REMATCH[1]}
   1155     fi
   1156   fi
   1157 
   1158   while [[ $tail ]]; do
   1159     if ble/util/isprint+ "$tail"; then
   1160       head=$head${BASH_REMATCH}
   1161       tail=${tail:${#BASH_REMATCH}}
   1162     else
   1163       ble/util/s2c "${tail::1}"
   1164       local code=$ret
   1165       if ((code<32)); then
   1166         ble/util/c2s "$((code+64))"
   1167         ret=$sgr1^$ret$sgr0
   1168       elif ((code==127)); then
   1169         ret=$sgr1^?$sgr0
   1170       elif ((128<=code&&code<160)); then
   1171         ble/util/c2s "$((code-64))"
   1172         ret=${sgr1}M-^$ret$sgr0
   1173       else
   1174         ret=${tail::1}
   1175       fi
   1176       head=$head$ret
   1177       tail=${tail:1}
   1178     fi
   1179   done
   1180   ret=$head
   1181 }
   1182 
   1183 if ((_ble_bash>=40400)); then
   1184   function ble/string#quote-words {
   1185     local IFS=$_ble_term_IFS
   1186     ret="${*@Q}"
   1187   }
   1188   function ble/string#quote-command {
   1189     local IFS=$_ble_term_IFS
   1190     ret=$1; shift
   1191     (($#)) && ret="$ret ${*@Q}"
   1192   }
   1193 else
   1194   function ble/string#quote-words {
   1195     local q=\' Q="'\''" IFS=$_ble_term_IFS
   1196     ret=("${@//$q/$Q}")
   1197     ret=("${ret[@]/%/$q}") # WA #D1570 #D1738 checked
   1198     ret="${ret[*]/#/$q}"   # WA #D1570 #D1738 checked
   1199   }
   1200   function ble/string#quote-command {
   1201     if (($#<=1)); then
   1202       ret=$1
   1203       return 0
   1204     fi
   1205     local q=\' Q="'\''" IFS=$_ble_term_IFS
   1206     ret=("${@:2}")
   1207     ret=("${ret[@]//$q/$Q}")  # WA #D1570 #D1738 checked
   1208     ret=("${ret[@]/%/$q}")    # WA #D1570 #D1738 checked
   1209     ret="$1 ${ret[*]/#/$q}"   # WA #D1570 #D1738 checked
   1210   }
   1211 fi
   1212 ## @fn ble/string#quote-word text opts
   1213 function ble/string#quote-word {
   1214   ret=${1-}
   1215 
   1216   local rex_csi=$'\e\\[[ -?]*[@-~]'
   1217   local opts=${2-} sgrq= sgr0=
   1218   if [[ $opts ]]; then
   1219     local rex=':sgrq=(('$rex_csi'|[^:])*):'
   1220     if [[ :$opts: =~ $rex ]]; then
   1221       sgrq=${BASH_REMATCH[1]} sgr0=$_ble_term_sgr0
   1222     fi
   1223     rex=':sgr0=(('$rex_csi'|[^:])*):'
   1224     if [[ :$opts: =~ $rex ]]; then
   1225       sgr0=${BASH_REMATCH[1]}
   1226     elif [[ :$opts: == *:ansi:* ]]; then
   1227       sgr0=$'\e[m'
   1228     fi
   1229   fi
   1230 
   1231   if [[ ! $ret ]]; then
   1232     if [[ :$opts: == *:quote-empty:* ]]; then
   1233       ret=$sgrq\'\'$sgr0
   1234     fi
   1235     return 0
   1236   fi
   1237 
   1238   local chars=$'\a\b\e\f\n\r\t\v'
   1239   if [[ $ret == *["$chars"]* ]]; then
   1240     ble/string#escape-for-bash-escape-string "$ret"
   1241     ret=$sgrq\$\'$ret\'$sgr0
   1242     return 0
   1243   fi
   1244 
   1245   local chars=$_ble_term_IFS'"`$\<>()|&;*?[]!^=:{,}#~' q=\'
   1246   if [[ $ret == *["$chars"]* ]]; then
   1247     local Q="'$sgr0\'$sgrq'"
   1248     ret=$sgrq$q${ret//$q/$Q}$q$sgr0
   1249     ret=${ret#"$sgrq$q$q$sgr0"} ret=${ret%"$sgrq$q$q$sgr0"}
   1250   elif [[ $ret == *["$q"]* ]]; then
   1251     local Q="\'"
   1252     ret=${ret//$q/$Q}
   1253   fi
   1254 }
   1255 
   1256 function ble/string#match { [[ $1 =~ $2 ]]; }
   1257 
   1258 ## @fn ble/string#create-unicode-progress-bar/.block value
   1259 ##   @var[out] ret
   1260 function ble/string#create-unicode-progress-bar/.block {
   1261   local block=$1
   1262   if ((block<=0)); then
   1263     ble/util/c2w "$((0x2588))"
   1264     ble/string#repeat ' ' "$ret"
   1265   elif ((block>=8)); then
   1266     ble/util/c2s "$((0x2588))"
   1267     ((${#ret}==1)) || ret='*' # LC_CTYPE が非対応の文字の時
   1268   else
   1269     ble/util/c2s "$((0x2590-block))"
   1270     if ((${#ret}!=1)); then
   1271       # LC_CTYPE が非対応の文字の時
   1272       ble/util/c2w "$((0x2588))"
   1273       ble/string#repeat ' ' "$((ret-1))"
   1274       ret=$block$ret
   1275     fi
   1276   fi
   1277 }
   1278 
   1279 ## @fn ble/string#create-unicode-progress-bar value max width opts
   1280 ##   @param[in] opts
   1281 ##     unlimited ... 上限が不明である事を示します。
   1282 ##   @var[out] ret
   1283 function ble/string#create-unicode-progress-bar {
   1284   local value=$1 max=$2 width=$3 opts=:$4:
   1285 
   1286   local opt_unlimited=
   1287   if [[ $opts == *:unlimited:* ]]; then
   1288     opt_unlimited=1
   1289     ((value%=max,width--))
   1290   fi
   1291 
   1292   local progress=$((value*8*width/max))
   1293   local progress_fraction=$((progress%8)) progress_integral=$((progress/8))
   1294 
   1295   local out=
   1296   if ((progress_integral)); then
   1297     if [[ $opt_unlimited ]]; then
   1298       # unlimited の時は左は空白
   1299       ble/string#create-unicode-progress-bar/.block 0
   1300     else
   1301       ble/string#create-unicode-progress-bar/.block 8
   1302     fi
   1303     ble/string#repeat "$ret" "$progress_integral"
   1304     out=$ret
   1305   fi
   1306 
   1307   if ((progress_fraction)); then
   1308     if [[ $opt_unlimited ]]; then
   1309       # unlimited の時は2升を使って位置を表す
   1310       ble/string#create-unicode-progress-bar/.block "$progress_fraction"
   1311       out=$out$'\e[7m'$ret$'\e[27m'
   1312     fi
   1313 
   1314     ble/string#create-unicode-progress-bar/.block "$progress_fraction"
   1315     out=$out$ret
   1316     ((progress_integral++))
   1317   else
   1318     if [[ $opt_unlimited ]]; then
   1319       ble/string#create-unicode-progress-bar/.block 8
   1320       out=$out$ret
   1321     fi
   1322   fi
   1323 
   1324   if ((progress_integral<width)); then
   1325     ble/string#create-unicode-progress-bar/.block 0
   1326     ble/string#repeat "$ret" "$((width-progress_integral))"
   1327     out=$out$ret
   1328   fi
   1329 
   1330   ret=$out
   1331 }
   1332 # Note: Bash-4.1 以下では "LC_CTYPE=C 組み込みコマンド" の形式だと
   1333 #   locale がその場で適用されないバグがある。
   1334 function ble/util/strlen.impl {
   1335   local LC_ALL= LC_CTYPE=C
   1336   ret=${#1}
   1337 }
   1338 function ble/util/strlen {
   1339   ble/util/strlen.impl "$@" 2>/dev/null # suppress locale error #D1440
   1340 }
   1341 function ble/util/substr.impl {
   1342   local LC_ALL= LC_CTYPE=C
   1343   ret=${1:$2:$3}
   1344 }
   1345 function ble/util/substr {
   1346   ble/util/substr.impl "$@" 2>/dev/null # suppress locale error #D1440
   1347 }
   1348 
   1349 function ble/path#append {
   1350   local _ble_local_script='opts=$opts${opts:+:}$2'
   1351   _ble_local_script=${_ble_local_script//opts/"$1"}
   1352   builtin eval -- "$_ble_local_script"
   1353 }
   1354 function ble/path#prepend {
   1355   local _ble_local_script='opts=$2${opts:+:}$opts'
   1356   _ble_local_script=${_ble_local_script//opts/"$1"}
   1357   builtin eval -- "$_ble_local_script"
   1358 }
   1359 function ble/path#remove {
   1360   [[ $2 ]] || return 1
   1361   local _ble_local_script='
   1362     opts=:${opts//:/::}:
   1363     opts=${opts//:"$2":}
   1364     opts=${opts//::/:} opts=${opts#:} opts=${opts%:}'
   1365   _ble_local_script=${_ble_local_script//opts/"$1"}
   1366   builtin eval -- "$_ble_local_script"
   1367 }
   1368 function ble/path#remove-glob {
   1369   [[ $2 ]] || return 1
   1370   local _ble_local_script='
   1371     opts=:${opts//:/::}:
   1372     opts=${opts//:$2:}
   1373     opts=${opts//::/:} opts=${opts#:} opts=${opts%:}'
   1374   _ble_local_script=${_ble_local_script//opts/"$1"}
   1375   builtin eval -- "$_ble_local_script"
   1376 }
   1377 function ble/path#contains {
   1378   builtin eval "[[ :\${$1}: == *:\"\$2\":* ]]"
   1379 }
   1380 
   1381 ## @fn ble/opts#has opts key
   1382 function ble/opts#has {
   1383   local rex=':'$2'[=:]'
   1384   [[ :$1: =~ $rex ]]
   1385 }
   1386 ## @fn ble/opts#remove opts value
   1387 function ble/opts#remove {
   1388   ble/path#remove "$@"
   1389 }
   1390 
   1391 ## @fn ble/opts#extract-first-optarg opts key [default_value]
   1392 function ble/opts#extract-first-optarg {
   1393   ret=
   1394   local rex=':'$2'(=[^:]*)?:'
   1395   [[ :$1: =~ $rex ]] || return 1
   1396   if [[ ${BASH_REMATCH[1]} ]]; then
   1397     ret=${BASH_REMATCH[1]:1}
   1398   elif [[ ${3+set} ]]; then
   1399     ret=$3
   1400   fi
   1401   return 0
   1402 }
   1403 ## @fn ble/opts#extract-last-optarg opts key [default_value]
   1404 function ble/opts#extract-last-optarg {
   1405   ret=
   1406   local rex='.*:'$2'(=[^:]*)?:'
   1407   [[ :$1: =~ $rex ]] || return 1
   1408   if [[ ${BASH_REMATCH[1]} ]]; then
   1409     ret=${BASH_REMATCH[1]:1}
   1410   elif [[ ${3+set} ]]; then
   1411     ret=$3
   1412   fi
   1413   return 0
   1414 }
   1415 ## @fn ble/opts#extract-all-optargs opts key [default_value]
   1416 ##   extract all values from the string OPTS of the form
   1417 ##   "...:key=value1:...:key=value2:...:key:...".
   1418 ##
   1419 ##   @param[in] key
   1420 ##     This should not include any special characters of regular
   1421 ##     expressions---preferably composed of [-_[:alnum:]].
   1422 ##
   1423 ##   @arr[out] ret
   1424 function ble/opts#extract-all-optargs {
   1425   ret=()
   1426   local value=:$1: rex=':'$2'(=[^:]*)?(:.*)$' count=0
   1427   while [[ $value =~ $rex ]]; do
   1428     ((count++))
   1429     if [[ ${BASH_REMATCH[1]} ]]; then
   1430       ble/array#push ret "${BASH_REMATCH[1]:1}"
   1431     elif [[ ${3+set} ]]; then
   1432       ble/array#push ret "$3"
   1433     fi
   1434     value=${BASH_REMATCH[2]}
   1435   done
   1436   ((count))
   1437 }
   1438 
   1439 if ((_ble_bash>=40000)); then
   1440   _ble_util_set_declare=(declare -A NAME)
   1441   function ble/set#add { builtin eval -- "$1[x\$2]=1"; }
   1442   function ble/set#remove { builtin unset -v "$1[x\$2]"; }
   1443   function ble/set#contains { builtin eval "[[ \${$1[x\$2]+set} ]]"; }
   1444 else
   1445   _ble_util_set_declare=(declare NAME)
   1446   function ble/set#.escape {
   1447     _ble_local_value=${_ble_local_value//$_ble_term_FS/"$_ble_term_FS$_ble_term_FS"}
   1448     _ble_local_value=${_ble_local_value//:/"$_ble_term_FS."}
   1449   }
   1450   function ble/set#add {
   1451     local _ble_local_value=$2; ble/set#.escape
   1452     ble/path#append "$1" "$_ble_local_value"
   1453   }
   1454   function ble/set#remove {
   1455     local _ble_local_value=$2; ble/set#.escape
   1456     ble/path#remove "$1" "$_ble_local_value"
   1457   }
   1458   function ble/set#contains {
   1459     local _ble_local_value=$2; ble/set#.escape
   1460     builtin eval "[[ :\$$1: == *:\"\$_ble_local_value\":* ]]"
   1461   }
   1462 fi
   1463 
   1464 
   1465 #--------------------------------------
   1466 # dict
   1467 
   1468 _ble_util_adict_declare='declare NAME NAME_keylist'
   1469 ## @fn ble/dict#.resolve dict key
   1470 function ble/adict#.resolve {
   1471   # _ble_local_key
   1472   _ble_local_key=$2
   1473   _ble_local_key=${_ble_local_key//$_ble_term_FS/"$_ble_term_FS,"}
   1474   _ble_local_key=${_ble_local_key//:/"$_ble_term_FS."}
   1475 
   1476   local keylist=${1}_keylist; keylist=:${!keylist}
   1477   local vec=${keylist%%:"$_ble_local_key":*}
   1478   if [[ $vec != "$keylist" ]]; then
   1479     vec=${vec//[!:]}
   1480     _ble_local_index=${#vec}
   1481   else
   1482     _ble_local_index=-1
   1483   fi
   1484 }
   1485 function ble/adict#set {
   1486   local _ble_local_key _ble_local_index
   1487   ble/adict#.resolve "$1" "$2"
   1488   if ((_ble_local_index>=0)); then
   1489     builtin eval -- "$1[_ble_local_index]=\$3"
   1490   else
   1491     local _ble_local_script='
   1492       local _ble_local_vec=${NAME_keylist//[!:]}
   1493       NAME[${#_ble_local_vec}]=$3
   1494       NAME_keylist=$NAME_keylist$_ble_local_key:
   1495     '
   1496     builtin eval -- "${_ble_local_script//NAME/$1}"
   1497   fi
   1498   return 0
   1499 }
   1500 function ble/adict#get {
   1501   local _ble_local_key _ble_local_index
   1502   ble/adict#.resolve "$1" "$2"
   1503   if ((_ble_local_index>=0)); then
   1504     builtin eval -- "ret=\${$1[_ble_local_index]}; [[ \${$1[_ble_local_index]+set} ]]"
   1505   else
   1506     builtin eval -- ret=
   1507     return 1
   1508   fi
   1509 }
   1510 function ble/adict#unset {
   1511   local _ble_local_key _ble_local_index
   1512   ble/adict#.resolve "$1" "$2"
   1513   ((_ble_local_index>=0)) &&
   1514     builtin eval -- "builtin unset -v '$1[_ble_local_index]'"
   1515   return 0
   1516 }
   1517 function ble/adict#has {
   1518   local _ble_local_key _ble_local_index
   1519   ble/adict#.resolve "$1" "$2"
   1520   ((_ble_local_index>=0)) &&
   1521     builtin eval -- "[[ \${$1[_ble_local_index]+set} ]]"
   1522 }
   1523 function ble/adict#clear {
   1524   builtin eval -- "${1}_keylist= $1=()"
   1525 }
   1526 function ble/adict#keys {
   1527   local _ble_local_keylist=${1}_keylist
   1528   _ble_local_keylist=${!_ble_local_keylist%:}
   1529   ble/string#split ret : "$_ble_local_keylist"
   1530   if [[ $_ble_local_keylist == *"$_ble_term_FS"* ]]; then
   1531     ret=("${ret[@]//$_ble_term_FS./:}")             # WA #D1570 checked
   1532     ret=("${ret[@]//$_ble_term_FS,/$_ble_term_FS}") # WA #D1570 #D1738 checked
   1533   fi
   1534 
   1535   # filter out unset elements
   1536   local _ble_local_keys _ble_local_i _ble_local_ref=$1[_ble_local_i]
   1537   _ble_local_keys=("${ret[@]}") ret=()
   1538   for _ble_local_i in "${!_ble_local_keys[@]}"; do
   1539     [[ ${_ble_local_ref+set} ]] &&
   1540       ble/array#push ret "${_ble_local_keys[_ble_local_i]}"
   1541   done
   1542 }
   1543 
   1544 if ((_ble_bash>=40000)); then
   1545   _ble_util_dict_declare='declare -A NAME'
   1546   function ble/dict#set   { builtin eval -- "$1[x\$2]=\$3"; }
   1547   function ble/dict#get   { builtin eval -- "ret=\${$1[x\$2]-}; [[ \${$1[x\$2]+set} ]]"; }
   1548   function ble/dict#unset { builtin eval -- "builtin unset -v '$1[x\$2]'"; }
   1549   function ble/dict#has   { builtin eval -- "[[ \${$1[x\$2]+set} ]]"; }
   1550   function ble/dict#clear { builtin eval -- "$1=()"; }
   1551   function ble/dict#keys  { builtin eval -- 'ret=("${!'"$1"'[@]}"); ret=("${ret[@]#x}")'; }
   1552 else
   1553   _ble_util_dict_declare='declare NAME NAME_keylist='
   1554   function ble/dict#set   { ble/adict#set   "$@"; }
   1555   function ble/dict#get   { ble/adict#get   "$@"; }
   1556   function ble/dict#unset { ble/adict#unset "$@"; }
   1557   function ble/dict#has   { ble/adict#has   "$@"; }
   1558   function ble/dict#clear { ble/adict#clear "$@"; }
   1559   function ble/dict#keys  { ble/adict#keys  "$@"; }
   1560 fi
   1561 
   1562 if ((_ble_bash>=40200)); then
   1563   _ble_util_gdict_declare='{ builtin unset -v NAME; declare -gA NAME; NAME=(); }'
   1564   function ble/gdict#set   { ble/dict#set   "$@"; }
   1565   function ble/gdict#get   { ble/dict#get   "$@"; }
   1566   function ble/gdict#unset { ble/dict#unset "$@"; }
   1567   function ble/gdict#has   { ble/dict#has   "$@"; }
   1568   function ble/gdict#clear { ble/dict#clear "$@"; }
   1569   function ble/gdict#keys  { ble/dict#keys  "$@"; }
   1570 elif ((_ble_bash>=40000)); then
   1571   _ble_util_gdict_declare='{ if ! ble/is-assoc NAME; then if local _ble_local_test 2>/dev/null; then NAME_keylist=; else builtin unset -v NAME NAME_keylist; declare -A NAME; fi fi; NAME=(); }'
   1572   function ble/gdict#.is-adict {
   1573     local keylist=${1}_keylist
   1574     [[ ${!keylist+set} ]]
   1575   }
   1576   function ble/gdict#set   { if ble/gdict#.is-adict "$1"; then ble/adict#set   "$@"; else ble/dict#set   "$@"; fi; }
   1577   function ble/gdict#get   { if ble/gdict#.is-adict "$1"; then ble/adict#get   "$@"; else ble/dict#get   "$@"; fi; }
   1578   function ble/gdict#unset { if ble/gdict#.is-adict "$1"; then ble/adict#unset "$@"; else ble/dict#unset "$@"; fi; }
   1579   function ble/gdict#has   { if ble/gdict#.is-adict "$1"; then ble/adict#has   "$@"; else ble/dict#has   "$@"; fi; }
   1580   function ble/gdict#clear { if ble/gdict#.is-adict "$1"; then ble/adict#clear "$@"; else ble/dict#clear "$@"; fi; }
   1581   function ble/gdict#keys  { if ble/gdict#.is-adict "$1"; then ble/adict#keys  "$@"; else ble/dict#keys  "$@"; fi; }
   1582 else
   1583   _ble_util_gdict_declare='{ builtin unset -v NAME NAME_keylist; NAME_keylist= NAME=(); }'
   1584   function ble/gdict#set   { ble/adict#set   "$@"; }
   1585   function ble/gdict#get   { ble/adict#get   "$@"; }
   1586   function ble/gdict#unset { ble/adict#unset "$@"; }
   1587   function ble/gdict#has   { ble/adict#has   "$@"; }
   1588   function ble/gdict#clear { ble/adict#clear "$@"; }
   1589   function ble/gdict#keys  { ble/adict#keys  "$@"; }
   1590 fi
   1591 
   1592 
   1593 function ble/dict/.print {
   1594   declare -p "$2" &>/dev/null || return 1
   1595   local ret _ble_local_key _ble_local_value
   1596 
   1597   ble/util/print "builtin eval -- \"\${_ble_util_${1}_declare//NAME/$2}\""
   1598   ble/"$1"#keys "$2"
   1599   for _ble_local_key in "${ret[@]}"; do
   1600     ble/"$1"#get "$2" "$_ble_local_key"
   1601     ble/string#quote-word "$ret" quote-empty
   1602     _ble_local_value=$ret
   1603 
   1604     ble/string#quote-word "$_ble_local_key" quote-empty
   1605     _ble_local_key=$ret
   1606 
   1607     ble/util/print "ble/$1#set $2 $_ble_local_key $_ble_local_value"
   1608   done
   1609 }
   1610 function ble/dict#print { ble/dict/.print dict "$1"; }
   1611 function ble/adict#print { ble/dict/.print adict "$1"; }
   1612 function ble/gdict#print { ble/dict/.print gdict "$1"; }
   1613 
   1614 function ble/dict/.copy {
   1615   local ret
   1616   ble/"$1"#keys "$2"
   1617   ble/"$1"#clear "$3"
   1618   local _ble_local_key
   1619   for _ble_local_key in "${ret[@]}"; do
   1620     ble/"$1"#get "$2" "$_ble_local_key"
   1621     ble/"$1"#set "$3" "$_ble_local_key" "$ret"
   1622   done
   1623 }
   1624 function ble/dict#cp { ble/dict/.copy dict "$1" "$2"; }
   1625 function ble/adict#cp { ble/dict/.copy adict "$1" "$2"; }
   1626 function ble/gdict#cp { ble/dict/.copy gdict "$1" "$2"; }
   1627 
   1628 #------------------------------------------------------------------------------
   1629 # assign: reading files/streams into variables
   1630 #
   1631 
   1632 ## @fn ble/util/readfile var filename
   1633 ## @fn ble/util/mapfile arr < filename
   1634 ##   ファイルの内容を変数または配列に読み取ります。
   1635 ##
   1636 ##   @param[in] var
   1637 ##     読み取った内容の格納先の変数名を指定します。
   1638 ##   @param[in] arr
   1639 ##     読み取った内容を行毎に格納する配列の名前を指定します。
   1640 ##   @param[in] filename
   1641 ##     読み取るファイルの場所を指定します。
   1642 ##
   1643 ## Note: bash-5.2 以上で $(< file) を使う可能性も考えたが、末尾改行が
   1644 ##   消えてしまう事、末尾改行を未定義にしてまで使う程の速度差もない事、
   1645 ##   などから採用は見送る事にした。
   1646 if ((_ble_bash>=40000)); then
   1647   function ble/util/readfile { # 155ms for man bash
   1648     local -a _ble_local_buffer=()
   1649     mapfile _ble_local_buffer < "$2"; local _ble_local_ext=$?
   1650     IFS= builtin eval "$1=\"\${_ble_local_buffer[*]-}\""
   1651     return "$_ble_local_ext"
   1652   }
   1653   function ble/util/mapfile {
   1654     mapfile -t "$1"
   1655   }
   1656 else
   1657   function ble/util/readfile { # 465ms for man bash
   1658     [[ -r $2 && ! -d $2 ]] || return 1
   1659     local IFS=
   1660     ble/bash/read -d '' "$1" < "$2"
   1661     return 0
   1662   }
   1663   function ble/util/mapfile {
   1664     local IFS=
   1665     local _ble_local_i=0 _ble_local_val _ble_local_arr; _ble_local_arr=()
   1666     while ble/bash/read _ble_local_val || [[ $_ble_local_val ]]; do
   1667       _ble_local_arr[_ble_local_i++]=$_ble_local_val
   1668     done
   1669     builtin eval "$1=(\"\${_ble_local_arr[@]}\")"
   1670   }
   1671 fi
   1672 
   1673 function ble/util/copyfile {
   1674   local src=$1 dst=$2 content
   1675   ble/util/readfile content "$1" || return "$?"
   1676   ble/util/put "$content" >| "$dst"
   1677 }
   1678 
   1679 ## @fn ble/util/writearray [OPTIONS] arr
   1680 ##   配列の内容を読み出し可能な形式で出力します。
   1681 ##
   1682 ## OPTIONS
   1683 ##   --       以降の引数は通常引数
   1684 ##   -d delim 配列要素を区切るのに使う文字を設定します。
   1685 ##            既定値は改行 "\n" です。
   1686 ##   --nlfix  改行区切りで出力します。要素に改行が含まれる時は $'' を用
   1687 ##            いて内容をエスケープします。改行が含まれる要素番号の一覧
   1688 ##            を一番最後の要素に追加します。
   1689 ##
   1690 function ble/util/writearray/.read-arguments {
   1691   _ble_local_array=
   1692   _ble_local_nlfix=
   1693   _ble_local_delim=$'\n'
   1694   local flags=
   1695   while (($#)); do
   1696     local arg=$1; shift
   1697     if [[ $flags != *-* && $arg == -* ]]; then
   1698       case $arg in
   1699       (--nlfix) _ble_local_nlfix=1 ;;
   1700       (-d)
   1701         if (($#)); then
   1702           _ble_local_delim=$1; shift
   1703         else
   1704           ble/util/print "${FUNCNAME[1]}: '$arg': missing option argument." >&2
   1705           flags=E$flags
   1706         fi ;;
   1707       (--) flags=-$flags ;;
   1708       (*)
   1709         ble/util/print "${FUNCNAME[1]}: '$arg': unrecognized option." >&2
   1710         flags=E$flags ;;
   1711       esac
   1712     else
   1713       if local rex='^[_a-zA-Z][_a-zA-Z0-9]*$'; ! [[ $arg =~ $rex ]]; then
   1714         ble/util/print "${FUNCNAME[1]}: '$arg': invalid array name." >&2
   1715         flags=E$flags
   1716       elif [[ $flags == *A* ]]; then
   1717         ble/util/print "${FUNCNAME[1]}: '$arg': an array name has been already specified." >&2
   1718         flags=E$flags
   1719       else
   1720         _ble_local_array=$arg
   1721         flags=A$flags
   1722       fi
   1723     fi
   1724   done
   1725   [[ $_ble_local_nlfix ]] && _ble_local_delim=$'\n'
   1726   [[ $flags != *E* ]]
   1727 }
   1728 
   1729 _ble_bin_awk_libES='
   1730   function s2i_initialize(_, i) {
   1731     for (i = 0; i < 16; i++)
   1732       xdigit2int[sprintf("%x", i)] = i;
   1733     for (i = 10; i < 16; i++)
   1734       xdigit2int[sprintf("%X", i)] = i;
   1735   }
   1736   function s2i(s, base, _, i, n, r) {
   1737     if (!base) base = 10;
   1738     r = 0;
   1739     n = length(s);
   1740     for (i = 1; i <= n; i++)
   1741       r = r * base + xdigit2int[substr(s, i, 1)];
   1742     return r;
   1743   }
   1744 
   1745   # ENCODING: UTF-8
   1746   function c2s_initialize(_, i, n, buff) {
   1747     if (sprintf("%c", 945) == "α") {
   1748       C2S_UNICODE_PRINTF_C = 1;
   1749       n = split(ENVIRON["__ble_rawbytes"], buff);
   1750       for (i = 1; i <= n; i++)
   1751         c2s_byte2raw[127 + i] = buff[i];
   1752     } else {
   1753       C2S_UNICODE_PRINTF_C = 0;
   1754       for (i = 1; i <= 255; i++)
   1755         c2s_byte2char[i] = sprintf("%c", i);
   1756     }
   1757   }
   1758   function c2s(code, _, leadbyte_mark, leadbyte_sup, tail) {
   1759     if (C2S_UNICODE_PRINTF_C)
   1760       return sprintf("%c", code);
   1761 
   1762     leadbyte_sup = 128; # 0x80
   1763     leadbyte_mark = 0;
   1764     tail = "";
   1765     while (leadbyte_sup && code >= leadbyte_sup) {
   1766       leadbyte_sup /= 2;
   1767       leadbyte_mark = leadbyte_mark ? leadbyte_mark / 2 : 65472; # 0xFFC0
   1768       tail = c2s_byte2char[128 + int(code % 64)] tail;
   1769       code = int(code / 64);
   1770     }
   1771     return c2s_byte2char[(leadbyte_mark + code) % 256] tail;
   1772   }
   1773   function c2s_raw(code, _, ret) {
   1774     if (code >= 128 && C2S_UNICODE_PRINTF_C) {
   1775       ret = c2s_byte2raw[code];
   1776       if (ret != "") return ret;
   1777     }
   1778     return sprintf("%c", code);
   1779   }
   1780 
   1781   function es_initialize(_, c) {
   1782     s2i_initialize();
   1783     c2s_initialize();
   1784     es_control_chars["a"] = "\a";
   1785     es_control_chars["b"] = "\b";
   1786     es_control_chars["t"] = "\t";
   1787     es_control_chars["n"] = "\n";
   1788     es_control_chars["v"] = "\v";
   1789     es_control_chars["f"] = "\f";
   1790     es_control_chars["r"] = "\r";
   1791     es_control_chars["e"] = "\033";
   1792     es_control_chars["E"] = "\033";
   1793     es_control_chars["?"] = "?";
   1794     es_control_chars["'\''"] = "'\''";
   1795     es_control_chars["\""] = "\"";
   1796     es_control_chars["\\"] = "\\";
   1797 
   1798     for (c = 32; c < 127; c++)
   1799       es_s2c[sprintf("%c", c)] = c;
   1800   }
   1801   function es_unescape(s, _, head, c) {
   1802     head = "";
   1803     while (match(s, /^[^\\]*\\/)) {
   1804       head = head substr(s, 1, RLENGTH - 1);
   1805       s = substr(s, RLENGTH + 1);
   1806       if ((c = es_control_chars[substr(s, 1, 1)])) {
   1807         head = head c;
   1808         s = substr(s, 2);
   1809       } else if (match(s, /^[0-9]([0-9][0-9]?)?/)) {
   1810         head = head c2s_raw(s2i(substr(s, 1, RLENGTH), 8) % 256);
   1811         s = substr(s, RLENGTH + 1);
   1812       } else if (match(s, /^x[0-9a-fA-F][0-9a-fA-F]?/)) {
   1813         head = head c2s_raw(s2i(substr(s, 2, RLENGTH - 1), 16));
   1814         s = substr(s, RLENGTH + 1);
   1815       } else if (match(s, /^U[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]([0-9a-fA-F]([0-9a-fA-F][0-9a-fA-F]?)?)?/)) {
   1816         # \\U[0-9]{5,8}
   1817         head = head c2s(s2i(substr(s, 2, RLENGTH - 1), 16));
   1818         s = substr(s, RLENGTH + 1);
   1819       } else if (match(s, /^[uU][0-9a-fA-F]([0-9a-fA-F]([0-9a-fA-F][0-9a-fA-F]?)?)?/)) {
   1820         # \\[uU][0-9]{1,4}
   1821         head = head c2s(s2i(substr(s, 2, RLENGTH - 1), 16));
   1822         s = substr(s, RLENGTH + 1);
   1823       } else if (match(s, /^c[ -~]/)) {
   1824         # \\c[ -~] (non-ascii characters are unsupported)
   1825         c = es_s2c[substr(s, 2, 1)];
   1826         head = head c2s(_ble_bash >= 40400 && c == 63 ? 127 : c % 32);
   1827         s = substr(s, 3);
   1828       } else {
   1829         head = head "\\";
   1830       }
   1831     }
   1832     return head s;
   1833   }
   1834 '
   1835 _ble_bin_awk_libNLFIX='
   1836   ## @var nlfix_index
   1837   ## @var nlfix_indices
   1838   ## @var nlfix_rep_slash
   1839   ## @var nlfix_rep_double_slash
   1840   ## @fn nlfix_begin()
   1841   ## @fn nlfix_push(elem)
   1842   ## @fn nlfix_end()
   1843   function nlfix_begin(_, tmp) {
   1844     nlfix_rep_slash = "\\";
   1845     if (AWKTYPE == "xpg4") nlfix_rep_slash = "\\\\";
   1846 
   1847     nlfix_rep_double_slash = "\\\\";
   1848     sub(/.*/, nlfix_rep_double_slash, tmp);
   1849     if (tmp == "\\") nlfix_rep_double_slash = "\\\\\\\\";
   1850 
   1851     nlfix_indices = "";
   1852     nlfix_index = 0;
   1853   }
   1854   function nlfix_push(elem, file) {
   1855     if (elem ~ /\n/) {
   1856       gsub(/\\/,   nlfix_rep_double_slash, elem);
   1857       gsub(/'\''/, nlfix_rep_slash "'\''", elem);
   1858       gsub(/\007/, nlfix_rep_slash "a",    elem);
   1859       gsub(/\010/, nlfix_rep_slash "b",    elem);
   1860       gsub(/\011/, nlfix_rep_slash "t",    elem);
   1861       gsub(/\012/, nlfix_rep_slash "n",    elem);
   1862       gsub(/\013/, nlfix_rep_slash "v",    elem);
   1863       gsub(/\014/, nlfix_rep_slash "f",    elem);
   1864       gsub(/\015/, nlfix_rep_slash "r",    elem);
   1865       if (file)
   1866         printf("$'\''%s'\''\n", elem) > file;
   1867       else
   1868         printf("$'\''%s'\''\n", elem);
   1869       nlfix_indices = nlfix_indices != "" ? nlfix_indices " " nlfix_index : nlfix_index;
   1870     } else {
   1871       if (file)
   1872         printf("%s\n", elem) > file;
   1873       else
   1874         printf("%s\n", elem);
   1875     }
   1876     nlfix_index++;
   1877   }
   1878   function nlfix_end(file) {
   1879     if (file)
   1880       printf("%s\n", nlfix_indices) > file;
   1881     else
   1882       printf("%s\n", nlfix_indices);
   1883   }
   1884 '
   1885 _ble_util_writearray_rawbytes=
   1886 function ble/util/writearray {
   1887   local _ble_local_array
   1888   local -x _ble_local_nlfix _ble_local_delim
   1889   ble/util/writearray/.read-arguments "$@" || return 2
   1890 
   1891   # select the fastest awk implementation
   1892   local __ble_awk=ble/bin/awk __ble_awktype=$_ble_bin_awk_type
   1893   if ble/is-function ble/bin/mawk; then
   1894     __ble_awk=ble/bin/mawk __ble_awktype=mawk
   1895   elif ble/is-function ble/bin/nawk; then
   1896     __ble_awk=ble/bin/nawk __ble_awktype=nawk
   1897   fi
   1898 
   1899   # Note: printf も遅いが awk による parse の方が遅いので nlfix でない限りは直
   1900   # 接 printf を使う。但し、bash-5.2 以降では printf が格段に遅くなるので避ける。
   1901   if ((!_ble_local_nlfix)) && ! [[ _ble_bash -ge 50200 && $__ble_awktype == [mn]awk ]]; then
   1902     if [[ $_ble_local_delim ]]; then
   1903       if [[ $_ble_local_delim == *["%\'"]* ]]; then
   1904         local __ble_q=\' __ble_Q="'\''"
   1905         _ble_local_delim=${_ble_local_delim//'%'/'%%'}
   1906         _ble_local_delim=${_ble_local_delim//'\'/'\\'}
   1907         _ble_local_delim=${_ble_local_delim//$__ble_q/$__ble_Q}
   1908       fi
   1909       builtin eval "printf '%s$_ble_local_delim' \"\${$_ble_local_array[@]}\""
   1910     else
   1911       builtin eval "printf '%s\0' \"\${$_ble_local_array[@]}\""
   1912     fi
   1913     return "$?"
   1914   fi
   1915 
   1916   # Note: mawk は定義していない関数を使おうとすると、それが実際に決し
   1917   # て実行されないとしてもコンパイルに失敗して動かない。
   1918   local __ble_function_gensub_dummy=
   1919   [[ $__ble_awktype == gawk ]] ||
   1920     __ble_function_gensub_dummy='function gensub(rex, rep, n, str) { exit 3; }'
   1921 
   1922   # Note: gawk の内部では $'\302' 等から現在のコードにないバイトを生成できない
   1923   # ので外部から与える。
   1924   if [[ ! $_ble_util_writearray_rawbytes ]]; then
   1925     local IFS=$_ble_term_IFS __ble_tmp; __ble_tmp=('\'{2,3}{0..7}{0..7})
   1926     builtin eval "local _ble_util_writearray_rawbytes=\$'${__ble_tmp[*]}'"
   1927   fi
   1928   local -x __ble_rawbytes=$_ble_util_writearray_rawbytes
   1929 
   1930   local __ble_rex_dq='^"([^\\"]|\\.)*"'
   1931   local __ble_rex_es='^\$'\''([^\\'\'']|\\.)*'\'''
   1932   local __ble_rex_sq='^'\''([^'\'']|'\'\\\\\'\'')*'\'''
   1933   local __ble_rex_normal=$'^[^'$_ble_term_space'$`"'\''()|&;<>\\]' # Note: []{}?*#!~^, @(), +() は quote されていなくても OK とする
   1934   declare -p "$_ble_local_array" | "$__ble_awk" -v _ble_bash="$_ble_bash" '
   1935     '"$__ble_function_gensub_dummy"'
   1936     BEGIN {
   1937       DELIM = ENVIRON["_ble_local_delim"];
   1938       FLAG_NLFIX = ENVIRON["_ble_local_nlfix"];
   1939       if (FLAG_NLFIX) DELIM = "\n";
   1940 
   1941       IS_GAWK = AWKTYPE == "gawk";
   1942       IS_XPG4 = AWKTYPE == "xpg4";
   1943 
   1944       REP_SL = "\\";
   1945       if (IS_XPG4) REP_SL = "\\\\";
   1946 
   1947       es_initialize();
   1948 
   1949       decl = "";
   1950     }
   1951 
   1952     '"$_ble_bin_awk_libES"'
   1953     '"$_ble_bin_awk_libNLFIX"'
   1954 
   1955     # Note: "str" must not contain "&" or "\\\\".  When "&" is
   1956     # present, the escaping rule for "\\" changes in some awk.
   1957     # Now there is no problem because only DELIM (one character) is
   1958     # currently passed.
   1959     function str2rep(str) {
   1960       if (IS_XPG4) sub(/\\/, "\\\\\\\\", str);
   1961       return str;
   1962     }
   1963 
   1964 
   1965     function unquote_dq(s, _, head) {
   1966       if (IS_GAWK) {
   1967         return gensub(/\\([$`"\\])/, "\\1", "g", s);
   1968       } else {
   1969         if (s ~ /\\[$`"\\]/) {
   1970           gsub(/\\\$/, "$" , s);
   1971           gsub(/\\`/ , "`" , s);
   1972           gsub(/\\"/ , "\"", s);
   1973           gsub(/\\\\/, "\\", s);
   1974         }
   1975         return s;
   1976       }
   1977     }
   1978     function unquote_sq(s) {
   1979       gsub(/'\'\\\\\'\''/, "'\''", s);
   1980       return s;
   1981     }
   1982     function unquote_dqes(s) {
   1983       if (s ~ /^"/)
   1984         return unquote_dq(substr(s, 2, length(s) - 2));
   1985       else
   1986         return es_unescape(substr(s, 3, length(s) - 3));
   1987     }
   1988     function unquote(s) {
   1989       if (s ~ /^"/)
   1990         return unquote_dq(substr(s, 2, length(s) - 2));
   1991       else if (s ~ /^\$/)
   1992         return es_unescape(substr(s, 3, length(s) - 3));
   1993       else if (s ~ /^'\''/)
   1994         return unquote_sq(substr(s, 2, length(s) - 2));
   1995       else if (s ~ /^\\/)
   1996         return substr(s, 2, 1);
   1997       else
   1998         return s;
   1999     }
   2000 
   2001 #%  # 制御文字が要素に含まれていない場合は全て [1]="..." の形式になっている筈。
   2002     function analyze_elements_dq(decl, _, arr, i, n) {
   2003       if (decl ~ /^\[[0-9]+\]="([^'$'\1\2''"\n\\]|\\.)*"( \[[0-9]+\]="([^\1\2"\\]|\\.)*")*$/) {
   2004         if (IS_GAWK) {
   2005           decl = gensub(/\[[0-9]+\]="(([^"\\]|\\.)*)" ?/, "\\1\001", "g", decl);
   2006           sub(/\001$/, "", decl);
   2007           decl = gensub(/\\([\\$"`])/, "\\1", decl);
   2008         } else {
   2009           # Convert to a ^A-separated list
   2010           gsub(/\[[0-9]+\]="([^"\\]|\\.)*" /, "&\001", decl);
   2011           gsub(/" \001\[[0-9]+\]="/, "\001", decl);
   2012           sub(/^\[[0-9]+\]="/, "", decl);
   2013           sub(/"$/, "", decl);
   2014 
   2015           # Unescape
   2016           gsub(/\\\\/, "\002", decl);
   2017           gsub(/\\\$/, "$", decl);
   2018           gsub(/\\"/, "\"", decl);
   2019           gsub(/\\`/, "`", decl);
   2020           gsub(/\002/, REP_SL, decl);
   2021         }
   2022 
   2023         # Output
   2024         if (DELIM != "") {
   2025           gsub(/\001/, str2rep(DELIM), decl);
   2026           printf("%s", decl DELIM);
   2027         } else {
   2028           n = split(decl, arr, /\001/);
   2029           for (i = 1; i <= n; i++)
   2030             printf("%s%c", arr[i], 0);
   2031         }
   2032 
   2033 #%      # [N]="" の形式の時は要素内改行はないと想定
   2034         if (FLAG_NLFIX) printf("\n");
   2035 
   2036         return 1;
   2037       }
   2038       return 0;
   2039     }
   2040 
   2041     function _process_elem(elem) {
   2042       if (FLAG_NLFIX) {
   2043         nlfix_push(elem);
   2044       } else if (DELIM != "") {
   2045         printf("%s", elem DELIM);
   2046       } else {
   2047         printf("%s%c", elem, 0);
   2048       }
   2049     }
   2050 
   2051 #%  # 任意の場合は多少遅くなるがこちらの関数で処理する。
   2052     function analyze_elements_general(decl, _, arr, i, n, str, elem, m) {
   2053       if (FLAG_NLFIX)
   2054         nlfix_begin();
   2055 
   2056       # Note: We here assume that all the elements have the form [N]=...
   2057       # Note: We here assume that the original array has at least one element
   2058       n = split(decl, arr, /\]=/);
   2059       str = " " arr[1];
   2060       elem = "";
   2061       first = 1;
   2062       for (i = 2; i <= n; i++) {
   2063         str = str "]=" arr[i];
   2064         if (sub(/^ \[[0-9]+\]=/, "", str)) {
   2065           if (first)
   2066             first = 0;
   2067           else
   2068             _process_elem(elem);
   2069           elem = "";
   2070         }
   2071 
   2072         if (match(str, /('"$__ble_rex_dq"'|'"$__ble_rex_es"') /)) {
   2073           mlen = RLENGTH;
   2074           elem = elem unquote_dqes(substr(str, 1, mlen - 1));
   2075           str = substr(str, mlen);
   2076           continue;
   2077         } else if (i == n || str !~ /^[\$"]/) {
   2078           # Fallback: As far as all the values have the form "" or $'', the
   2079           # control would only enter this branch for the last element.
   2080           while (match(str, /'"$__ble_rex_dq"'|'"$__ble_rex_es"'|'"$__ble_rex_sq"'|'"$__ble_rex_normal"'|^\\./)) {
   2081             mlen = RLENGTH;
   2082             elem = elem unquote(substr(str, 1, mlen));
   2083             str = substr(str, mlen + 1);
   2084           }
   2085         }
   2086       }
   2087       _process_elem(elem);
   2088 
   2089       if (FLAG_NLFIX)
   2090         nlfix_end();
   2091       return 1;
   2092     }
   2093 
   2094     function process_declaration(decl) {
   2095 #%    # declare 除去
   2096       sub(/^declare +(-[-aAilucnrtxfFgGI]+ +)?(-- +)?/, "", decl);
   2097 
   2098 #%    # 全体 quote の除去
   2099       if (decl ~ /^([_a-zA-Z][_a-zA-Z0-9]*)='\''\(.*\)'\''$/) {
   2100         sub(/='\''\(/, "=(", decl);
   2101         sub(/\)'\''$/, ")", decl);
   2102         gsub(/'\'\\\\\'\''/, "'\''", decl);
   2103       }
   2104 
   2105 #%    # bash-3.0 の declare -p は改行について誤った出力をする。
   2106       if (_ble_bash < 30100) gsub(/\\\n/, "\n", decl);
   2107 
   2108 #%    # #D1238 bash-4.3 以前の declare -p は ^A, ^? を
   2109 #%    #   ^A^A, ^A^? と出力してしまうので補正する。
   2110 #%    # #D1325 更に Bash-3.0 では "x${_ble_term_DEL}y" とすると
   2111 #%    #   _ble_term_DEL の中身が消えてしまうので
   2112 #%    #   "x""${_ble_term_DEL}""y" とする必要がある。
   2113       if (_ble_bash < 40400) {
   2114         gsub(/\001\001/, "\001", decl);
   2115         gsub(/\001\177/, "\177", decl);
   2116       }
   2117 
   2118       sub(/^([_a-zA-Z][_a-zA-Z0-9]*)=\(['"$_ble_term_space"']*/, "", decl);
   2119       sub(/['"$_ble_term_space"']*\)['"$_ble_term_space"']*$/, "", decl);
   2120 
   2121 #%    # 空配列
   2122       if (decl == "") return 1;
   2123 
   2124 #%    # [N]="value" だけの時の高速実装。mawk だと却って遅くなる様だ
   2125       if (AWKTYPE != "mawk" && analyze_elements_dq(decl)) return 1;
   2126 
   2127       return analyze_elements_general(decl);
   2128     }
   2129     { decl = decl ? decl "\n" $0: $0; }
   2130     END { process_declaration(decl); }
   2131   '
   2132 }
   2133 function ble/util/readarray {
   2134   local _ble_local_array
   2135   local -x _ble_local_nlfix _ble_local_delim
   2136   ble/util/writearray/.read-arguments "$@" || return 2
   2137 
   2138   if ((_ble_bash>=40400)); then
   2139     local _ble_local_script='
   2140       mapfile -t -d "$_ble_local_delim" ARR'
   2141   elif ((_ble_bash>=40000)) && [[ $_ble_local_delim == $'\n' ]]; then
   2142     local _ble_local_script='
   2143       mapfile -t ARR'
   2144   else
   2145     local _ble_local_script='
   2146       local IFS= ARRI=0; ARR=()
   2147       while ble/bash/read -d "$_ble_local_delim" "ARR[ARRI++]"; do :; done'
   2148   fi
   2149 
   2150   if [[ $_ble_local_nlfix ]]; then
   2151     _ble_local_script=$_ble_local_script'
   2152       local ARRN=${#ARR[@]} ARRF ARRI
   2153       if ((ARRN--)); then
   2154         ble/string#split-words ARRF "${ARR[ARRN]}"
   2155         builtin unset -v "ARR[ARRN]"
   2156         for ARRI in "${ARRF[@]}"; do
   2157           builtin eval -- "ARR[ARRI]=${ARR[ARRI]}"
   2158         done
   2159       fi'
   2160   fi
   2161   builtin eval -- "${_ble_local_script//ARR/$_ble_local_array}"
   2162 }
   2163 
   2164 ## @fn ble/util/assign var command
   2165 ##   var=$(command) の高速な代替です。command はサブシェルではなく現在のシェル
   2166 ##   で実行されます。Bash 5.3 の var=${ command; } に等価です。
   2167 ##
   2168 ##   @param[in] var
   2169 ##     代入先の変数名を指定します。
   2170 ##   @param[in] command...
   2171 ##     実行するコマンドを指定します。
   2172 ##
   2173 ## @remarks util.bgproc.sh では « ble/util/assign bgpid '(set -m; command &
   2174 ##   bgpid=$!; ble/util/print "$bgpid")' » でプロセスグループが作られる事を想定
   2175 ##   している。例えば bgpid=$(...) はプロセスグループが作られないので使えない。
   2176 ##
   2177 _ble_util_assign_base=$_ble_base_run/$$.util.assign.tmp
   2178 _ble_util_assign_level=0
   2179 if ((_ble_bash>=40000)); then
   2180   function ble/util/assign/mktmp {
   2181     _ble_local_tmpfile=$_ble_util_assign_base.$((_ble_util_assign_level++))
   2182     ((BASH_SUBSHELL)) && _ble_local_tmpfile=$_ble_local_tmpfile.$BASHPID
   2183   }
   2184 else
   2185   function ble/util/assign/mktmp {
   2186     _ble_local_tmpfile=$_ble_util_assign_base.$((_ble_util_assign_level++))
   2187     ((BASH_SUBSHELL)) && _ble_local_tmpfile=$_ble_local_tmpfile.$RANDOM
   2188   }
   2189 fi
   2190 function ble/util/assign/rmtmp {
   2191   ((_ble_util_assign_level--))
   2192 #%if !release
   2193   if ((BASH_SUBSHELL)); then
   2194     printf 'caller %s\n' "${FUNCNAME[@]}" >| "$_ble_local_tmpfile"
   2195   else
   2196     >| "$_ble_local_tmpfile"
   2197   fi
   2198 #%else
   2199   >| "$_ble_local_tmpfile"
   2200 #%end
   2201 }
   2202 if ((_ble_bash>=50300)); then
   2203   function ble/util/assign {
   2204     builtin eval "$1=\${ builtin eval -- \"\$2\"; }"
   2205   }
   2206 elif ((_ble_bash>=40000)); then
   2207   # mapfile の方が read より高速
   2208   function ble/util/assign {
   2209     local _ble_local_tmpfile; ble/util/assign/mktmp
   2210     builtin eval -- "$2" >| "$_ble_local_tmpfile"
   2211     local _ble_local_ret=$? _ble_local_arr=
   2212     mapfile -t _ble_local_arr < "$_ble_local_tmpfile"
   2213     ble/util/assign/rmtmp
   2214     IFS=$'\n' builtin eval "$1=\"\${_ble_local_arr[*]}\""
   2215     return "$_ble_local_ret"
   2216   }
   2217 else
   2218   function ble/util/assign {
   2219     local _ble_local_tmpfile; ble/util/assign/mktmp
   2220     builtin eval -- "$2" >| "$_ble_local_tmpfile"
   2221     local _ble_local_ret=$? IFS=
   2222     ble/bash/read -d '' "$1" < "$_ble_local_tmpfile"
   2223     ble/util/assign/rmtmp
   2224     builtin eval "$1=\${$1%\$_ble_term_nl}"
   2225     return "$_ble_local_ret"
   2226   }
   2227 fi
   2228 ## @fn ble/util/assign-array arr command args...
   2229 ##   mapfile -t arr < <(command ...) の高速な代替です。
   2230 ##   command はサブシェルではなく現在のシェルで実行されます。
   2231 ##
   2232 ##   @param[in] arr
   2233 ##     代入先の配列名を指定します。
   2234 ##   @param[in] command
   2235 ##     実行するコマンドを指定します。
   2236 ##   @param[in] args...
   2237 ##     command から参照する引数 ($3 $4 ...) を指定します。
   2238 ##
   2239 if ((_ble_bash>=40000)); then
   2240   function ble/util/assign-array {
   2241     local _ble_local_tmpfile; ble/util/assign/mktmp
   2242     builtin eval -- "$2" >| "$_ble_local_tmpfile"
   2243     local _ble_local_ret=$?
   2244     mapfile -t "$1" < "$_ble_local_tmpfile"
   2245     ble/util/assign/rmtmp
   2246     return "$_ble_local_ret"
   2247   }
   2248 else
   2249   function ble/util/assign-array {
   2250     local _ble_local_tmpfile; ble/util/assign/mktmp
   2251     builtin eval -- "$2" >| "$_ble_local_tmpfile"
   2252     local _ble_local_ret=$?
   2253     ble/util/mapfile "$1" < "$_ble_local_tmpfile"
   2254     ble/util/assign/rmtmp
   2255     return "$_ble_local_ret"
   2256   }
   2257 fi
   2258 
   2259 if ! ((_ble_bash>=40400)); then
   2260   function ble/util/assign-array0 {
   2261     local _ble_local_tmpfile; ble/util/assign/mktmp
   2262     builtin eval -- "$2" >| "$_ble_local_tmpfile"
   2263     local _ble_local_ret=$?
   2264     mapfile -d '' -t "$1" < "$_ble_local_tmpfile"
   2265     ble/util/assign/rmtmp
   2266     return "$_ble_local_ret"
   2267   }
   2268 else
   2269   function ble/util/assign-array0 {
   2270     local _ble_local_tmpfile; ble/util/assign/mktmp
   2271     builtin eval -- "$2" >| "$_ble_local_tmpfile"
   2272     local _ble_local_ret=$?
   2273     local IFS= i=0 _ble_local_arr
   2274     while ble/bash/read -d '' "_ble_local_arr[i++]"; do :; done < "$_ble_local_tmpfile"
   2275     ble/util/assign/rmtmp
   2276     [[ ${_ble_local_arr[--i]} ]] || builtin unset -v "_ble_local_arr[i]"
   2277     ble/util/unlocal i IFS
   2278     builtin eval "$1=(\"\${_ble_local_arr[@]}\")"
   2279     return "$_ble_local_ret"
   2280   }
   2281 fi
   2282 
   2283 ## @fn ble/util/assign.has-output command
   2284 function ble/util/assign.has-output {
   2285   local _ble_local_tmpfile; ble/util/assign/mktmp
   2286   builtin eval -- "$1" >| "$_ble_local_tmpfile"
   2287   [[ -s $_ble_local_tmpfile ]]
   2288   local _ble_local_ret=$?
   2289   ble/util/assign/rmtmp
   2290   return "$_ble_local_ret"
   2291 }
   2292 
   2293 function ble/util/assign-words {
   2294   ble/util/assign "$1" "$2"
   2295   ble/string#split-words "$1" "${!1}"
   2296 }
   2297 
   2298 
   2299 # ble/bin/awk の初期化に ble/util/assign を使うので
   2300 ble/bin/awk/.instantiate
   2301 
   2302 #
   2303 # functions
   2304 #
   2305 
   2306 ## @fn ble/is-function function
   2307 ##   関数 function が存在するかどうかを検査します。
   2308 ##
   2309 ##   @param[in] function
   2310 ##     存在を検査する関数の名前を指定します。
   2311 ##
   2312 if ((_ble_bash>=30200)); then
   2313   function ble/is-function {
   2314     declare -F "$1" &>/dev/null
   2315   }
   2316 else
   2317   # bash-3.1 has bug in declare -f.
   2318   # it does not accept a function name containing non-alnum chars.
   2319   function ble/is-function {
   2320     local type
   2321     ble/util/type type "$1"
   2322     [[ $type == function ]]
   2323   }
   2324 fi
   2325 
   2326 ## @fn ble/function#getdef function
   2327 ##   @var[out] def
   2328 ##
   2329 ## Note: declare -pf "$name" が -o posix に依存しない関数定義の取得方
   2330 ##   法であるかに思えたが、declare -pf "$name" を使うと -t 属性が付い
   2331 ##   ていた時に末尾に declare -ft name という余分な属性付加のコマンド
   2332 ##   が入ってしまう。或いはこの属性も一緒に保存できた方が良いのかもし
   2333 ##   れないが、取り敢えず今は属性が入らない様に declare -pf name は使
   2334 ##   わない。
   2335 if ((_ble_bash>=30200)); then
   2336   function ble/function#getdef {
   2337     local name=$1
   2338     ble/is-function "$name" || return 1
   2339     if [[ -o posix ]]; then
   2340       ble/util/assign def 'type "$name"'
   2341       def=${def#*$'\n'}
   2342     else
   2343       ble/util/assign def 'declare -f "$name"'
   2344     fi
   2345   }
   2346 else
   2347   function ble/function#getdef {
   2348     local name=$1
   2349     ble/is-function "$name" || return 1
   2350     ble/util/assign def 'type "$name"'
   2351     def=${def#*$'\n'}
   2352   }
   2353 fi
   2354 
   2355 ## @fn ble/function#evaldef def
   2356 ##   関数を定義します。基本的に eval に等価ですが評価時に extglob を保
   2357 ##   証します。
   2358 function ble/function#evaldef {
   2359   local reset_extglob=
   2360   if ! shopt -q extglob; then
   2361     reset_extglob=1
   2362     shopt -s extglob
   2363   fi
   2364   builtin eval -- "$1"; local ext=$?
   2365   [[ ! $reset_extglob ]] || shopt -u extglob
   2366   return "$ext"
   2367 }
   2368 
   2369 builtin eval -- "${_ble_util_gdict_declare//NAME/_ble_util_function_traced}"
   2370 function ble/function#trace {
   2371   local func
   2372   for func; do
   2373     declare -ft "$func" &>/dev/null || continue
   2374     ble/gdict#set _ble_util_function_traced "$func" 1
   2375   done
   2376 }
   2377 function ble/function#has-trace {
   2378   ble/gdict#has _ble_util_function_traced "$1"
   2379 }
   2380 function ble/function#has-attr {
   2381   local __ble_tmp=$1
   2382   ble/util/assign-array __ble_tmp 'declare -pf "$__ble_tmp" 2>/dev/null'
   2383   local nline=${#__ble_tmp[@]}
   2384   ((nline)) &&
   2385     ble/string#match "${__ble_tmp[nline-1]}" '^declare -([a-zA-Z]*)' &&
   2386     [[ ${BASH_REMATCH[1]} == *["$2"]* ]]
   2387 }
   2388 
   2389 ## @fn ble/function/is-global-trace-context
   2390 ##   この関数の呼び出し元の文脈で確実に global の DEBUG が見えているかどうかを
   2391 ##   判定します。
   2392 function ble/function/is-global-trace-context {
   2393   # Note: 例え set -T が設定されていたとしても、それが global で設定された物な
   2394   #   のか呼び出しの何処かの深さで設定された物なのか分からない。なので、set -T
   2395   #   が設定されていたからと言って無条件に global が見えているとは限らない。
   2396   # Note: ble に属する関数は勝手に set -T を一時的に有効にしたりする事は基本的
   2397   #   にないので許可する。但し、内部で一時的に restore-bash-options している時
   2398   #   はあるが、その内部で ble-attach 乃至は ble/function/is-global-trace-context等
   2399   #   を実行する事はないと仮定する。
   2400   local func depth=1 ndepth=${#FUNCNAME[*]}
   2401   for func in "${FUNCNAME[@]:1}"; do
   2402     local src=${BASH_SOURCE[depth]}
   2403     [[ $- == *T* && ( $func == ble || $func == ble[-/]* || $func == source && $src == "$_ble_base_blesh_raw" ) ]] ||
   2404       [[ $func == source && depth -eq ndepth-1 && BASH_LINENO[depth] -eq 0 && ( ${src##*/} == .bashrc || ${src##*/} == .bash_profile || ${src##*/} == .profile ) ]] ||
   2405       ble/gdict#has _ble_util_function_traced "$func" || return 1
   2406     ((depth++))
   2407   done
   2408   return 0
   2409 }
   2410 
   2411 ## @fn ble/function#try function args...
   2412 ##   関数 function が存在している時に限り関数を呼び出します。
   2413 ##
   2414 ##   @param[in] function
   2415 ##     存在を検査して実行する関数の名前を指定します。
   2416 ##   @param[in] args
   2417 ##     関数に渡す引数を指定します。
   2418 ##   @exit 関数が呼び出された場合はその終了ステータスを返します。
   2419 ##     関数が存在しなかった場合は 127 を返します。
   2420 ##
   2421 function ble/function#try {
   2422   local lastexit=$?
   2423   ble/is-function "$1" || return 127
   2424   ble/util/setexit "$lastexit"
   2425   "$@"
   2426 }
   2427 
   2428 function ble/function#get-source-and-lineno {
   2429   local ret unset_extdebug=
   2430   shopt -q extdebug || { unset_extdebug=1; shopt -s extdebug; }
   2431   ble/util/assign ret "declare -F '$1' 2>/dev/null"; local ext=$?
   2432   [[ ! $unset_extdebug ]] || shopt -u extdebug
   2433   if ((ext==0)); then
   2434     ret=${ret#*' '}
   2435     lineno=${ret%%' '*}
   2436     source=${ret#*' '}
   2437     [[ $lineno && ! ${lineno//[0-9]} && $source ]] || return 1
   2438   fi
   2439   return "$ext"
   2440 }
   2441 
   2442 ## @fn ble/function#advice type function proc
   2443 ##   既存の関数の振る舞いを変更します。
   2444 ##
   2445 ##   @param[in] type
   2446 ##     before を指定した時、処理 proc を関数 function の前に挿入します。
   2447 ##     after を指定した時、処理 proc を関数 function の後に挿入します。
   2448 ##     around を指定した時、関数 function の呼び出し前後に処理 proc を行います。
   2449 ##     around proc の中では本来の関数を呼び出す為に ble/function#advice/do
   2450 ##     を実行する必要があります。
   2451 ##
   2452 ##   @fn ble/function#advice/do
   2453 ##     around proc の中から呼び出せる関数です。
   2454 ##     本来の関数を呼び出します。
   2455 ##
   2456 ##   @arr[in,out] ADVICE_WORDS
   2457 ##     proc の中から参照できる変数です。関数の呼び出しに使うコマンドを提供しま
   2458 ##     す。例えば元の関数呼び出しが function arg1 arg2 だった場合、
   2459 ##     ADVICE_WORDS=(function arg1 arg2) が設定されます。before/around に於いて
   2460 ##     本来の関数の呼び出し前にこの配列を書き換える事で呼び出す関数または関数の
   2461 ##     引数を変更する事ができます。
   2462 ##
   2463 ##   @var[in.out] ADVICE_EXIT
   2464 ##     proc の中から参照できる変数です。after/around に於いて関数実行後の戻り値
   2465 ##     を参照または変更するのに使います。
   2466 ##
   2467 ##   @var[in.out] ADVICE_FUNCNAME
   2468 ##     proc の中から参照できる変数です。FUNCNAME から ble/function#advice の調
   2469 ##     整による余分な関数呼び出しを取り除いたものを保持します。
   2470 ##
   2471 function ble/function#advice/do {
   2472   ble/util/setexit "$advice_lastexit" "$advice_lastarg"
   2473   ble/function#advice/original:"${ADVICE_WORDS[@]}"
   2474   ADVICE_EXIT=$?
   2475 }
   2476 function ble/function#advice/.proc {
   2477   local advice_lastexit=$? advice_lastarg=$_
   2478 
   2479   local ADVICE_WORDS ADVICE_EXIT=127
   2480   ADVICE_WORDS=("$@")
   2481   local -a ADVICE_FUNCNAME=()
   2482   local func
   2483   for func in "${FUNCNAME[@]}"; do
   2484     [[ $func == ble/function#advice/* ]] ||
   2485       ble/array#push ADVICE_FUNCNAME "$func"
   2486   done
   2487   ble/util/unlocal func
   2488 
   2489   ble/function#try "ble/function#advice/before:$1"
   2490   if ble/is-function "ble/function#advice/around:$1"; then
   2491     "ble/function#advice/around:$1"
   2492   else
   2493     ble/function#advice/do
   2494   fi
   2495   ble/function#try "ble/function#advice/after:$1"
   2496   return "$ADVICE_EXIT"
   2497 }
   2498 function ble/function#advice {
   2499   local type=$1 name=$2 proc=$3
   2500   if ! ble/is-function "$name"; then
   2501     local t=; ble/util/type t "$name"
   2502     case $t in
   2503     (builtin|file) builtin eval "function $name { : ZBe85Oe28nBdg; command $name \"\$@\"; }" ;;
   2504     (*)
   2505       ble/util/print "ble/function#advice: $name is not a function." >&2
   2506       return 1 ;;
   2507     esac
   2508   fi
   2509 
   2510   local def; ble/function#getdef "$name"
   2511   case $type in
   2512   (remove)
   2513     if [[ $def == *'ble/function#advice/.proc'* ]]; then
   2514       ble/function#getdef "ble/function#advice/original:$name"
   2515       if [[ $def ]]; then
   2516         if [[ $def == *ZBe85Oe28nBdg* ]]; then
   2517           builtin unset -f "$name"
   2518         else
   2519           ble/function#evaldef "${def#*:}"
   2520         fi
   2521       fi
   2522     fi
   2523     builtin unset -f ble/function#advice/{before,after,around,original}:"$name" 2>/dev/null
   2524     return 0 ;;
   2525   (before|after|around)
   2526     if [[ $def != *'ble/function#advice/.proc'* ]]; then
   2527       ble/function#evaldef "ble/function#advice/original:$def"
   2528       builtin eval "function $name { ble/function#advice/.proc \"\$FUNCNAME\" \"\$@\"; }"
   2529     fi
   2530 
   2531     local q=\' Q="'\''"
   2532     builtin eval "ble/function#advice/$type:$name() { builtin eval '${proc//$q/$Q}'; }"
   2533     return 0 ;;
   2534   (*)
   2535     ble/util/print "ble/function#advice unknown advice type '$type'" >&2
   2536     return 2 ;;
   2537   esac
   2538 }
   2539 
   2540 ## @fn ble/function#push name [proc]
   2541 ## @fn ble/function#pop name
   2542 ##   関数定義を保存・復元する関数です。
   2543 ##
   2544 function ble/function#push {
   2545   local name=$1 proc=$2
   2546   if ble/is-function "$name"; then
   2547     local index=0
   2548     while ble/is-function "ble/function#push/$index:$name"; do
   2549       ((++index))
   2550     done
   2551 
   2552     local def; ble/function#getdef "$name"
   2553     ble/function#evaldef "ble/function#push/$index:$def"
   2554   fi
   2555 
   2556   if [[ $proc ]]; then
   2557     local q=\' Q="'\''"
   2558     builtin eval "function $name { builtin eval -- '${proc//$q/$Q}'; }"
   2559   else
   2560     builtin unset -f "$name"
   2561   fi
   2562   return 0
   2563 }
   2564 function ble/function#pop {
   2565   local name=$1 proc=$2
   2566 
   2567   local index=-1
   2568   while ble/is-function "ble/function#push/$((index+1)):$name"; do
   2569     ((++index))
   2570   done
   2571 
   2572   if ((index<0)); then
   2573     if ble/is-function "$name"; then
   2574       builtin unset -f "$name"
   2575       return 0
   2576     elif ble/bin#has "$name"; then
   2577       ble/util/print "ble/function#pop: $name is not a function." >&2
   2578       return 1
   2579     else
   2580       return 0
   2581     fi
   2582   else
   2583     local def; ble/function#getdef "ble/function#push/$index:$name"
   2584     ble/function#evaldef "${def#*:}"
   2585     builtin unset -f "ble/function#push/$index:$name"
   2586     return 0
   2587   fi
   2588 }
   2589 function ble/function#push/call-top {
   2590   local func=${FUNCNAME[1]}
   2591   if ! ble/is-function "$func"; then
   2592     ble/util/print "ble/function#push/call-top: This function should be called from a function" >&2
   2593     return 1
   2594   fi
   2595   local index=0
   2596   if [[ $func == ble/function#push/?*:?* ]]; then
   2597     index=${func#*/*/}; index=${index%%:*}
   2598     func=${func#*:}
   2599   else
   2600     while ble/is-function "ble/function#push/$index:$func"; do ((index++)); done
   2601   fi
   2602   if ((index==0)); then
   2603     command "$func" "$@"
   2604   else
   2605     "ble/function#push/$((index-1)):$func" "$@"
   2606   fi
   2607 }
   2608 
   2609 : "${_ble_util_lambda_count:=0}"
   2610 ## @fn ble/function#lambda var body
   2611 ##   無名関数を定義しその実際の名前を変数 var に格納します。
   2612 function ble/function#lambda {
   2613   local _ble_local_q=\' _ble_local_Q="'\''"
   2614   ble/util/set "$1" ble/function#lambda/$((_ble_util_lambda_count++))
   2615   builtin eval -- "function ${!1} { builtin eval -- '${2//$_ble_local_q/$_ble_local_Q}'; }"
   2616 }
   2617 
   2618 ## @fn ble/function#suppress-stderr function_name
   2619 ##   @param[in] function_name
   2620 function ble/function#suppress-stderr {
   2621   local name=$1
   2622   if ! ble/is-function "$name"; then
   2623     ble/util/print "$FUNCNAME: '$name' is not a function name" >&2
   2624     return 2
   2625   fi
   2626 
   2627   # 重複して suppress-stderr した時の為、未定義の時のみ実装を待避
   2628   local lambda=ble/function#suppress-stderr:$name
   2629   if ! ble/is-function "$lambda"; then
   2630     local def; ble/function#getdef "$name"
   2631     ble/function#evaldef "ble/function#suppress-stderr:$def"
   2632   fi
   2633 
   2634   builtin eval "function $name { $lambda \"\$@\" 2>/dev/null; }"
   2635   return 0
   2636 }
   2637 
   2638 #
   2639 # miscallaneous utils
   2640 #
   2641 
   2642 # Note: "printf -v" for an array element is only allowed in bash-4.1
   2643 # or later.
   2644 if ((_ble_bash>=40100)); then
   2645   function ble/util/set {
   2646     builtin printf -v "$1" %s "$2"
   2647   }
   2648 else
   2649   function ble/util/set {
   2650     builtin eval -- "$1=\"\$2\""
   2651   }
   2652 fi
   2653 
   2654 if ((_ble_bash>=30100)); then
   2655   function ble/util/sprintf {
   2656     builtin printf -v "$@"
   2657   }
   2658 else
   2659   function ble/util/sprintf {
   2660     local -a args; args=("${@:2}")
   2661     ble/util/assign "$1" 'builtin printf "${args[@]}"'
   2662   }
   2663 fi
   2664 
   2665 ## @fn ble/util/type varname command
   2666 ##   @param[out] varname
   2667 ##     結果を格納する変数名を指定します。
   2668 ##   @param[in] command
   2669 ##     種類を判定するコマンド名を指定します。
   2670 function ble/util/type {
   2671   ble/util/assign-array "$1" 'builtin type -a -t -- "$3" 2>/dev/null' "$2"
   2672 }
   2673 
   2674 if ((_ble_bash>=40000)); then
   2675   function ble/is-alias {
   2676     [[ ${BASH_ALIASES[$1]+set} ]]
   2677   }
   2678   function ble/alias#active {
   2679     shopt -q expand_aliases &&
   2680       [[ ${BASH_ALIASES[$1]+set} ]]
   2681   }
   2682   ## @fn ble/alias#expand word
   2683   ##   @var[out] ret
   2684   ##   @exit
   2685   ##     エイリアス展開が実際に行われた時に成功します。
   2686   function ble/alias#expand {
   2687     ret=$1
   2688     shopt -q expand_aliases &&
   2689       ret=${BASH_ALIASES[$ret]-$ret}
   2690   }
   2691   function ble/alias/list {
   2692     ret=("${!BASH_ALIASES[@]}")
   2693   }
   2694 else
   2695   function ble/is-alias {
   2696     [[ $1 != *=* ]] && alias "$1" &>/dev/null
   2697   }
   2698   function ble/alias#active {
   2699     shopt -q expand_aliases &&
   2700       [[ $1 != *=* ]] && alias "$1" &>/dev/null
   2701   }
   2702   function ble/alias#expand {
   2703     ret=$1
   2704     local type; ble/util/type type "$ret"
   2705     [[ $type != alias ]] && return 1
   2706     local data; ble/util/assign data 'LC_ALL=C alias "$ret"' &>/dev/null
   2707     [[ $data == 'alias '*=* ]] && builtin eval "ret=${data#alias *=}"
   2708   }
   2709   function ble/alias/list {
   2710     ret=()
   2711     local data iret=0
   2712     ble/util/assign-array data 'alias -p'
   2713     for data in "${data[@]}"; do
   2714       [[ $data == 'alias '*=* ]] &&
   2715         data=${data%%=*} &&
   2716         builtin eval "ret[iret++]=${data#alias }"
   2717     done
   2718   }
   2719 fi
   2720 
   2721 if ((_ble_bash>=40000)); then
   2722   # #D1341 対策 変数代入形式だと組み込みコマンドにロケールが適用されない。
   2723   function ble/util/is-stdin-ready {
   2724     local IFS= LC_ALL= LC_CTYPE=C
   2725     builtin read -t 0
   2726   }
   2727   # suppress locale error #D1440
   2728   ble/function#suppress-stderr ble/util/is-stdin-ready
   2729 else
   2730   function ble/util/is-stdin-ready { false; }
   2731 fi
   2732 
   2733 # Note: BASHPID は Bash-4.0 以上
   2734 
   2735 if ((_ble_bash>=40000)); then
   2736   function ble/util/getpid { :; }
   2737   function ble/util/is-running-in-subshell { [[ $$ != $BASHPID ]]; }
   2738 else
   2739   ## @fn ble/util/getpid
   2740   ##   @var[out] BASHPID
   2741   function ble/util/getpid {
   2742     local command='echo $PPID'
   2743     ble/util/assign BASHPID 'ble/bin/sh -c "$command"'
   2744   }
   2745   function ble/util/is-running-in-subshell {
   2746     # Note: bash-4.3 以下では BASH_SUBSHELL はパイプやプロセス置換で増えないの
   2747     #   で信頼性が低いらしい。唯、関数内で実行している限りは大丈夫なのかもしれ
   2748     #   ない。
   2749     ((BASH_SUBSHELL==0)) || return 0
   2750     local BASHPID; ble/util/getpid
   2751     [[ $$ != $BASHPID ]]
   2752   }
   2753 fi
   2754 
   2755 ## @fn ble/fd#is-open fd
   2756 ##   指定したファイルディスクリプタが開いているかどうか判定します。
   2757 function ble/fd#is-open { builtin : >&"$1"; } 2>/dev/null
   2758 
   2759 _ble_util_openat_nextfd=
   2760 function ble/fd#alloc/.nextfd {
   2761   [[ $_ble_util_openat_nextfd ]] ||
   2762     _ble_util_openat_nextfd=${bleopt_openat_base:-30}
   2763   # Note: Bash 3.1 では exec fd>&- で明示的に閉じても駄目。
   2764   #   開いた後に読み取りプロセスで読み取りに失敗する。
   2765   #   なので開いていない fd を探す必要がある。#D0992
   2766   # Note: 指定された fd が開いているかどうかを
   2767   #   可搬に高速に判定する方法を見つけたので
   2768   #   常に開いていない fd を探索する。#D1318
   2769   # Note: fd が枯渇すると探索が無限ループになるので fd 探索範囲の上限を 1024 に
   2770   #   制限する。もし見つからない場合には初期値の fd を上書きする。
   2771   local _ble_local_init=$_ble_util_openat_nextfd
   2772   local _ble_local_limit=$((_ble_local_init+1024))
   2773   while ((_ble_util_openat_nextfd<_ble_local_limit)) &&
   2774           ble/fd#is-open "$_ble_util_openat_nextfd"; do
   2775     ((_ble_util_openat_nextfd++))
   2776   done
   2777   if ((_ble_util_openat_nextfd>=_ble_local_limit)); then
   2778     _ble_util_openat_nextfd=$_ble_local_init
   2779     builtin eval "exec $_ble_util_openat_nextfd>&-"
   2780   fi
   2781   (($1=_ble_util_openat_nextfd++))
   2782 }
   2783 
   2784 ## @fn ble/fd#alloc fdvar redirect [opts]
   2785 ##   "exec {fdvar}>foo" に該当する操作を実行します。
   2786 ##   @param[out] fdvar
   2787 ##     指定した変数に使用されたファイルディスクリプタを代入します。
   2788 ##   @param[in] redirect
   2789 ##     リダイレクトを指定します。
   2790 ##   @param[in,opt] opts
   2791 ##     export
   2792 ##       指定した変数を export します。
   2793 ##     inherit
   2794 ##       既に fdvar が存在して有効な fd であれば何もしません。新しく fd を確保
   2795 ##       した場合には終了処理を登録しません。また上記の export を含みます。
   2796 ##     share
   2797 ##       >&NUMBER の形式のリダイレクトの場合に fd を複製する代わりに単に NUMBER
   2798 ##       を fdvar に代入します。
   2799 ##     overwrite
   2800 ##       既に fdvar が存在する場合その fd を上書きします。
   2801 _ble_util_openat_fdlist=()
   2802 function ble/fd#alloc {
   2803   local _ble_local_preserve=
   2804   if [[ :$3: == *:inherit:* ]]; then
   2805     [[ ${!1-} ]] &&
   2806       ble/fd#is-open "${!1}" &&
   2807       return 0
   2808   fi
   2809 
   2810   if [[ :$3: == *:share:* ]]; then
   2811     local _ble_local_ret='[<>]&['$_ble_term_IFS']*([0-9]+)['$_ble_term_IFS']*$'
   2812     if [[ $2 =~ $rex ]]; then
   2813       builtin eval -- "$1=${BASH_REMATCH[1]}"
   2814       return 0
   2815     fi
   2816   fi
   2817 
   2818   if [[ ${!1-} && :$3: == *:overwrite:* ]]; then
   2819     _ble_local_preserve=1
   2820     builtin eval "exec ${!1}$2"
   2821   elif ((_ble_bash>=40100)) && [[ :$3: != *:base:* ]]; then
   2822     builtin eval "exec {$1}$2"
   2823   else
   2824     ble/fd#alloc/.nextfd "$1"
   2825     # Note: Bash 3.2/3.1 のバグを避けるため、
   2826     #   >&- を用いて一旦明示的に閉じる必要がある #D0857
   2827     builtin eval "exec ${!1}>&- ${!1}$2"
   2828   fi; local _ble_local_ext=$?
   2829 
   2830   if [[ :$3: == *:inherit:* || :$3: == *:export:* ]]; then
   2831     export "$1"
   2832   elif [[ ! $_ble_local_preserve ]]; then
   2833     ble/array#push _ble_util_openat_fdlist "${!1}"
   2834   fi
   2835   return "$_ble_local_ext"
   2836 }
   2837 function ble/fd#finalize {
   2838   local fd
   2839   for fd in "${_ble_util_openat_fdlist[@]}"; do
   2840     builtin eval "exec $fd>&-"
   2841   done
   2842   _ble_util_openat_fdlist=()
   2843 }
   2844 ## @fn ble/fd#close fd
   2845 ##   指定した fd を閉じます。
   2846 function ble/fd#close {
   2847   set -- "$(($1))"
   2848   (($1>=3)) || return 1
   2849   builtin eval "exec $1>&-"
   2850   ble/array#remove _ble_util_openat_fdlist "$1"
   2851   return 0
   2852 }
   2853 
   2854 ## @var _ble_util_fd_stdout
   2855 ## @var _ble_util_fd_stderr
   2856 ## @var _ble_util_fd_null
   2857 ## @var _ble_util_fd_zero
   2858 ##   既に定義されている場合は継承する
   2859 if [[ $_ble_init_command ]]; then
   2860   # コマンドモードで実行している時には標準ストリームはそのまま使う。
   2861   _ble_util_fd_stdin=0
   2862   _ble_util_fd_stdout=1
   2863   _ble_util_fd_stderr=2
   2864 else
   2865   if [[ -t 0 ]]; then
   2866     ble/fd#alloc _ble_util_fd_stdin '<&0' base:overwrite:export
   2867   else
   2868     ble/fd#alloc _ble_util_fd_stdin '< /dev/tty' base:inherit
   2869   fi
   2870   if [[ -t 1 ]]; then
   2871     ble/fd#alloc _ble_util_fd_stdout '>&1' base:overwrite:export
   2872   else
   2873     ble/fd#alloc _ble_util_fd_stdout '> /dev/tty' base:inherit
   2874   fi
   2875   if [[ -t 2 ]]; then
   2876     ble/fd#alloc _ble_util_fd_stderr '>&2' base:overwrite:export
   2877   else
   2878     ble/fd#alloc _ble_util_fd_stderr ">&$_ble_util_fd_stdout" base:inherit:share
   2879   fi
   2880 fi
   2881 ble/fd#alloc _ble_util_fd_null '<> /dev/null' base:inherit
   2882 [[ -c /dev/zero ]] &&
   2883   ble/fd#alloc _ble_util_fd_zero '< /dev/zero' base:inherit
   2884 
   2885 function ble/fd#close-all-tty {
   2886   local -a fds=()
   2887   if [[ -d /proc/$$/fd ]]; then
   2888     ble/util/getpid
   2889     local fd
   2890     for fd in /proc/"$BASHPID"/fd/[0-9]*; do
   2891       ble/array#push fds "${fd##*/}"
   2892     done
   2893   else
   2894     fd=({0..255})
   2895   fi
   2896 
   2897   # Note: 0 1 2 及び _ble_util_fd_std{in,out,err} を閉じる事を考えたが、どうも
   2898   # redirect によって待避されている物などたくさんある様なので全部チェックする事
   2899   # にした。
   2900   local fd
   2901   for fd in "${fds[@]}"; do
   2902     if ble/string#match "$fd" '^[0-9]+$' && [[ -t $fd ]]; then
   2903       builtin eval "exec $fd>&- $fd>&$_ble_util_fd_null"
   2904       ble/array#remove _ble_util_openat_fdlist "$fd"
   2905     fi
   2906   done
   2907 }
   2908 ## @fn ble/util/nohup command [opts]
   2909 ##   @param[in] command
   2910 ##   @param[in,opt] opts
   2911 ##     @opts print-bgpid
   2912 function ble/util/nohup {
   2913   if ((!BASH_SUBSHELL)); then
   2914     (ble/util/nohup "$@")
   2915     return "$?"
   2916   fi
   2917 
   2918   ble/fd#close-all-tty
   2919   shopt -u huponexit
   2920   builtin eval -- "$1" &>/dev/null </dev/null & { local pid=$!; disown; }
   2921   if [[ :$2: == *:print-bgpid:* ]]; then
   2922     ble/util/print "$pid"
   2923   fi
   2924 }
   2925 
   2926 function ble/util/print-quoted-command {
   2927   local ret; ble/string#quote-command "$@"
   2928   ble/util/print "$ret"
   2929 }
   2930 function ble/util/declare-print-definitions {
   2931   (($#==0)) && return 0
   2932 
   2933   # Note (#D2055): mawk 1.3.3-20090705 bug の為に [:space:] を正規表現内部で使
   2934   # 用する事ができない。Ubuntu 16.04 LTS 及び Ubuntu 18.04 LTS で mawk
   2935   # 1.3.3-20090705 が使用されている。なので _ble_term_space という変数に
   2936   # <SP><TAB> を入れて使う。
   2937 
   2938   declare -p "$@" | ble/bin/awk -v _ble_bash="$_ble_bash" -v OSTYPE="$OSTYPE" '
   2939     BEGIN {
   2940       decl = "";
   2941 
   2942 #%    # 対策 #D1270: MSYS2 で ^M を代入すると消える
   2943       flag_escape_cr = OSTYPE == "msys";
   2944     }
   2945 
   2946     function fix_value(value) {
   2947 #%    # bash-3.0 の declare -p は改行について誤った出力をする。
   2948       if (_ble_bash < 30100) gsub(/\\\n/, "\n", value);
   2949 
   2950 #%    # #D1238 bash-4.3 以前の declare -p は ^A, ^? を
   2951 #%    #   ^A^A, ^A^? と出力してしまうので補正する。
   2952 #%    # #D1325 更に Bash-3.0 では "x${_ble_term_DEL}y" とすると
   2953 #%    #   _ble_term_DEL の中身が消えてしまうので
   2954 #%    #   "x""${_ble_term_DEL}""y" とする必要がある。
   2955       if (_ble_bash < 30100) {
   2956         gsub(/\001\001/, "\"\"${_ble_term_SOH}\"\"", value);
   2957         gsub(/\001\177/, "\"\"${_ble_term_DEL}\"\"", value);
   2958       } else if (_ble_bash < 40400) {
   2959         gsub(/\001\001/, "${_ble_term_SOH}", value);
   2960         gsub(/\001\177/, "${_ble_term_DEL}", value);
   2961       }
   2962 
   2963       if (flag_escape_cr)
   2964         gsub(/\015/, "${_ble_term_CR}", value);
   2965       return value;
   2966     }
   2967 
   2968 #%  # #D1522 #D1614 Bash-3.2 未満で配列要素に ^A または ^? を含む場合は
   2969 #%  #   arr=(...) の形式だと評価時に ^A, ^? が倍加するので、
   2970 #%  #   要素ごとに代入を行う必要がある。
   2971     function print_array_elements(decl, _, name, out, key, value) {
   2972       if (match(decl, /^[_a-zA-Z][_a-zA-Z0-9]*=\(/) == 0) return 0;
   2973       name = substr(decl, 1, RLENGTH - 2);
   2974       decl = substr(decl, RLENGTH + 1, length(decl) - RLENGTH - 1);
   2975       sub(/^['"$_ble_term_space"']+/, decl);
   2976 
   2977       out = name "=()\n";
   2978 
   2979       while (match(decl, /^\[[0-9]+\]=/)) {
   2980         key = substr(decl, 2, RLENGTH - 3);
   2981         decl = substr(decl, RLENGTH + 1);
   2982 
   2983         value = "";
   2984         if (match(decl, /^('\''[^'\'']*'\''|\$'\''([^\\'\'']|\\.)*'\''|\$?"([^\\"]|\\.)*"|\\.|[^'"$_ble_term_space"'"'\''`;&|()])*/)) {
   2985           value = substr(decl, 1, RLENGTH)
   2986           decl = substr(decl, RLENGTH + 1)
   2987         }
   2988 
   2989         out = out name "[" key "]=" fix_value(value) "\n";
   2990         sub(/^['"$_ble_term_space"']+/, decl);
   2991       }
   2992 
   2993       if (decl != "") return 0;
   2994 
   2995       print out;
   2996       return 1;
   2997     }
   2998 
   2999     function declflush(_, isArray) {
   3000       if (!decl) return 0;
   3001       isArray = (decl ~ /^declare +-[ilucnrtxfFgGI]*[aA]/);
   3002 
   3003 #%    # declare 除去
   3004       sub(/^declare +(-[-aAilucnrtxfFgGI]+ +)?(-- +)?/, "", decl);
   3005       if (isArray) {
   3006         if (decl ~ /^([_a-zA-Z][_a-zA-Z0-9]*)='\''\(.*\)'\''$/) {
   3007           sub(/='\''\(/, "=(", decl);
   3008           sub(/\)'\''$/, ")", decl);
   3009           gsub(/'\'\\\\\'\''/, "'\''", decl);
   3010         }
   3011 
   3012         if (_ble_bash < 40000 && decl ~ /[\001\177]/)
   3013           if (print_array_elements(decl))
   3014             return 1;
   3015       }
   3016 
   3017       print fix_value(decl);
   3018       decl = "";
   3019       return 1;
   3020     }
   3021     /^declare / {
   3022       declflush();
   3023       decl = $0;
   3024       next;
   3025     }
   3026     { decl = decl "\n" $0; }
   3027     END { declflush(); }
   3028   '
   3029 }
   3030 
   3031 ## @fn ble/util/print-global-definitions/.print-decl name opts
   3032 ##   指定された変数の宣言を出力します。
   3033 ##   @param[in] name
   3034 ##     処理対象の変数名を指定します。
   3035 ##   @param[in] opts
   3036 ##     指定した名前の変数が見つからない時 unset が指定されます。
   3037 ##   @stdout
   3038 ##     変数宣言を出力します。指定した名前の変数が見つからない時は unset 状態に
   3039 ##     するコマンドを出力します。
   3040 function ble/util/print-global-definitions/.print-decl {
   3041   local __ble_name=$1 __ble_decl=
   3042   if [[ ! ${!__ble_name+set} || :$2: == *:unset:* ]]; then
   3043     __ble_decl="declare $__ble_name; builtin unset -v $__ble_name"
   3044   elif ble/variable#has-attr "$__ble_name" aA; then
   3045     if ((_ble_bash>=40000)); then
   3046       ble/util/assign __ble_decl "declare -p $__ble_name" 2>/dev/null
   3047       __ble_decl=${__ble_decl#declare -* }
   3048     else
   3049       ble/util/assign __ble_decl "ble/util/declare-print-definitions $__ble_name" 2>/dev/null
   3050     fi
   3051     if ble/is-array "$__ble_name"; then
   3052       __ble_decl="declare -a $__ble_decl"
   3053     else
   3054       __ble_decl="declare -A $__ble_decl"
   3055     fi
   3056   else
   3057     __ble_decl=${!__ble_name}
   3058     __ble_decl="declare $__ble_name='${__ble_decl//$__ble_q/$__ble_Q}'"
   3059   fi
   3060   ble/util/print "$__ble_decl"
   3061 }
   3062 
   3063 ## @fn ble/util/print-global-definitions varnames...
   3064 ##   @var[in] varnames
   3065 ##
   3066 ##   指定した変数のグローバル変数としての定義を出力します。
   3067 ##
   3068 ##   制限: 途中同名の readonly ローカル変数がある場合は、
   3069 ##   グローバル変数の値は取得できないので unset を返す。
   3070 ##   そもそも readonly な変数には問題が多いので ble.sh では使わない。
   3071 ##
   3072 ##   制限: __ble_* という変数名はこの関数の実装に使用するので、
   3073 ##   対応しない。
   3074 ##
   3075 ##   Note: bash-4.2 にはバグがあって、グローバル変数が存在しない時に
   3076 ##   declare -g -r var とすると、ローカルに新しく読み取り専用の var 変数が作られる。
   3077 ##   現在の実装では問題にならない。
   3078 ##
   3079 function ble/util/print-global-definitions {
   3080   local __ble_opts=
   3081   [[ $1 == --hidden-only ]] && { __ble_opts=hidden-only; shift; }
   3082   ble/util/for-global-variables ble/util/print-global-definitions/.print-decl "$__ble_opts" "$@"
   3083 }
   3084 
   3085 ## @fn ble/util/for-global-variables proc opts varnames...
   3086 ##   @fn proc name opts
   3087 ##     @param[in] name
   3088 ##       処理対象の変数名を指定します。
   3089 ##     @param[in] opts
   3090 ##       指定した名前の変数が見つからない時 unset が指定されます。
   3091 ##   @param[in] opts
   3092 ##     hidden-only が含まれている時、対応するグローバル変数が別のローカル変数で
   3093 ##     被覆されている時にのみ proc を呼び出します。
   3094 ##   @param[in] varnames...
   3095 ##     処理対象の変数名の集合を指定します。
   3096 function ble/util/for-global-variables {
   3097   local __ble_proc=$1 __ble_opts=$2; shift 2
   3098   local __ble_hidden_only=
   3099   [[ :$__ble_opts: == *:hidden-only:* ]] && __ble_hidden_only=1
   3100   (
   3101     ((_ble_bash>=50000)) && shopt -u localvar_unset
   3102     __ble_error=
   3103     __ble_q="'" __ble_Q="'\''"
   3104     # 補完で 20 階層も関数呼び出しが重なることはなかろう
   3105     __ble_MaxLoop=20
   3106     builtin unset -v "${!_ble_processed_@}"
   3107 
   3108     for __ble_name; do
   3109       [[ ${__ble_name//[_a-zA-Z0-9]} || $__ble_name == __ble_* ]] && continue
   3110       ((__ble_processed_$__ble_name)) && continue
   3111       ((__ble_processed_$__ble_name=1))
   3112 
   3113       __ble_found=
   3114       if ((_ble_bash>=40200)); then
   3115         declare -g -r "$__ble_name"
   3116         for ((__ble_i=0;__ble_i<__ble_MaxLoop;__ble_i++)); do
   3117           if ! builtin unset -v "$__ble_name"; then
   3118             # Note: Even if the variable is declared readonly at the top level,
   3119             # we can test if the visible variable is global or not by using
   3120             # `(readonly var; ! local var)' because `local var' succeeds if the
   3121             # visible variable is local readonly.
   3122             if builtin readonly "$__ble_name"; ble/variable#is-global/.test "$__ble_name"; then
   3123               __ble_found=1
   3124               [[ $__ble_hidden_only && $__ble_i == 0 ]] ||
   3125                 "$__ble_proc" "$__ble_name"
   3126             fi
   3127             break
   3128           fi
   3129         done
   3130       else
   3131         for ((__ble_i=0;__ble_i<__ble_MaxLoop;__ble_i++)); do
   3132           if ble/variable#is-global "$__ble_name"; then
   3133             __ble_found=1
   3134             [[ $__ble_hidden_only && $__ble_i == 0 ]] ||
   3135               "$__ble_proc" "$__ble_name"
   3136             break
   3137           fi
   3138           builtin unset -v "$__ble_name" || break
   3139         done
   3140       fi
   3141 
   3142       if [[ ! $__ble_found ]]; then
   3143         __ble_error=1
   3144         [[ $__ble_hidden_only && $__ble_i == 0 ]] ||
   3145           "$__ble_proc" "$__ble_name" unset
   3146       fi
   3147     done
   3148 
   3149     [[ ! $__ble_error ]]
   3150   ) 2>/dev/null
   3151 }
   3152 
   3153 ## @fn ble/util/has-glob-pattern pattern
   3154 ##   指定したパターンがグロブパターンを含むかどうかを判定します。
   3155 ##
   3156 ## Note: Bash 5.0 では変数に \ が含まれている時に echo $var を実行すると
   3157 ##   パス名展開と解釈されて failglob, nullglob などが有効になるが、
   3158 ##   echo \[a\] の様に明示的に書いている場合にはパス名展開と解釈されない。
   3159 ##   この判定では明示的に書いた時にグロブパターンと認識されるかどうかに基づく。
   3160 function ble/util/has-glob-pattern {
   3161   [[ $1 ]] || return 1
   3162 
   3163   local restore=:
   3164   if ! shopt -q nullglob 2>/dev/null; then
   3165     restore="$restore;shopt -u nullglob"
   3166     shopt -s nullglob
   3167   fi
   3168   if shopt -q failglob 2>/dev/null; then
   3169     restore="$restore;shopt -s failglob"
   3170     shopt -u failglob
   3171   fi
   3172 
   3173   local dummy=$_ble_base_run/$$.dummy ret
   3174   builtin eval "ret=(\"\$dummy\"/${1#/})" 2>/dev/null
   3175   builtin eval -- "$restore"
   3176   [[ ! $ret ]]
   3177 }
   3178 
   3179 ## @fn ble/util/is-cygwin-slow-glob word
   3180 ##   Cygwin では // で始まるパスの展開は遅い (#D1168) のでその判定を行う。
   3181 function ble/util/is-cygwin-slow-glob {
   3182   # Note: core-complete.sh ではエスケープを行うので
   3183   #   "'//...'" 等の様な文字列が "$1" に渡される。
   3184   [[ ( $OSTYPE == cygwin || $OSTYPE == msys ) && ${1#\'} == //* && ! -o noglob ]] &&
   3185     ble/util/has-glob-pattern "$1"
   3186 }
   3187 
   3188 ## @fn ble/util/eval-pathname-expansion pattern
   3189 ##   @var[out] ret
   3190 function ble/util/eval-pathname-expansion {
   3191   ret=()
   3192   if ble/util/is-cygwin-slow-glob; then # Note: #D1168
   3193     if shopt -q failglob &>/dev/null; then
   3194       return 1
   3195     elif shopt -q nullglob &>/dev/null; then
   3196       return 0
   3197     else
   3198       set -f
   3199       ble/util/eval-pathname-expansion "$1"; local ext=$1
   3200       set +f
   3201       return "$ext"
   3202     fi
   3203   fi
   3204 
   3205   # adjust glob settings
   3206   local canon=
   3207   if [[ :$2: == *:canonical:* ]]; then
   3208     canon=1
   3209     local set=$- shopt gignore=$GLOBIGNORE
   3210     if ((_ble_bash>=40100)); then
   3211       shopt=$BASHOPTS
   3212     else
   3213       shopt=
   3214       shopt -q failglob && shopt=$shopt:failglob
   3215       shopt -q nullglob && shopt=$shopt:nullglob
   3216       shopt -q extglob && shopt=$shopt:extglob
   3217       shopt -q dotglob && shopt=$shopt:dotglob
   3218     fi
   3219     shopt -u failglob
   3220     shopt -s nullglob
   3221     shopt -s extglob
   3222     set +f
   3223     GLOBIGNORE=
   3224   fi
   3225 
   3226   # Note: eval で囲んでおかないと failglob 失敗時に続きが実行されない
   3227   # Note: failglob で失敗した時のエラーメッセージは殺す
   3228   builtin eval "ret=($1)" 2>/dev/null; local ext=$?
   3229 
   3230   # restore glob settings
   3231   if [[ $canon ]]; then
   3232     # Note: dotglob is changed by GLOBIGNORE
   3233     GLOBIGNORE=$gignore
   3234     if [[ :$shopt: == *:dotglob:* ]]; then shopt -s dotglob; else shopt -u dotglob; fi
   3235     [[ $set == *f* ]] && set -f
   3236     [[ :$shopt: != *:extglob:* ]] && shopt -u extglob
   3237     [[ :$shopt: != *:nullglob:* ]] && shopt -u nullglob
   3238     [[ :$shopt: == *:failglob:* ]] && shopt -s failglob
   3239   fi
   3240 
   3241   return "$ext"
   3242 }
   3243 
   3244 
   3245 # 正規表現は _ble_bash>=30000
   3246 _ble_util_rex_isprint='^[ -~]+'
   3247 ## @fn ble/util/isprint+ str
   3248 ##
   3249 ##   @var[out] BASH_REMATCH ble-exit/text/update/position で使用する。
   3250 function ble/util/isprint+ {
   3251   local LC_ALL= LC_COLLATE=C
   3252   [[ $1 =~ $_ble_util_rex_isprint ]]
   3253 }
   3254 # suppress locale error #D1440
   3255 ble/function#suppress-stderr ble/util/isprint+
   3256 
   3257 if ((_ble_bash>=40200)); then
   3258   function ble/util/strftime {
   3259     if [[ $1 = -v ]]; then
   3260       builtin printf -v "$2" "%($3)T" "${4:--1}"
   3261     else
   3262       builtin printf "%($1)T" "${2:--1}"
   3263     fi
   3264   }
   3265 else
   3266   function ble/util/strftime {
   3267     if [[ $1 = -v ]]; then
   3268       local fmt=$3 time=$4
   3269       ble/util/assign "$2" 'ble/bin/date +"$fmt" $time'
   3270     else
   3271       ble/bin/date +"$1" $2
   3272     fi
   3273   }
   3274 fi
   3275 
   3276 #%< util.hook.sh
   3277 
   3278 #------------------------------------------------------------------------------
   3279 # ble/util/msleep
   3280 
   3281 #%include benchmark.sh
   3282 
   3283 function ble/util/msleep/.check-builtin-sleep {
   3284   local ret; ble/util/readlink "$BASH"
   3285   local bash_prefix=${ret%/*/*}
   3286   if [[ -s $bash_prefix/lib/bash/sleep ]] &&
   3287     (enable -f "$bash_prefix/lib/bash/sleep" sleep && builtin sleep 0.0) &>/dev/null; then
   3288     enable -f "$bash_prefix/lib/bash/sleep" sleep
   3289     return 0
   3290   else
   3291     return 1
   3292   fi
   3293 }
   3294 function ble/util/msleep/.check-sleep-decimal-support {
   3295   local version; ble/util/assign version 'LC_ALL=C ble/bin/sleep --version 2>&1' 2>/dev/null # suppress locale error #D1440
   3296   [[ $version == *'GNU coreutils'* || $OSTYPE == darwin* && $version == 'usage: sleep seconds' ]]
   3297 }
   3298 
   3299 _ble_util_msleep_delay=2000 # [usec]
   3300 function ble/util/msleep/.core {
   3301   local sec=${1%%.*}
   3302   ((10#0${1##*.}&&sec++)) # 小数部分は切り上げ
   3303   ble/bin/sleep "$sec"
   3304 }
   3305 function ble/util/msleep {
   3306   local v=$((1000*$1-_ble_util_msleep_delay))
   3307   ((v<=0)) && v=0
   3308   ble/util/sprintf v '%d.%06d' "$((v/1000000))" "$((v%1000000))"
   3309   ble/util/msleep/.core "$v"
   3310 }
   3311 
   3312 _ble_util_msleep_calibrate_count=0
   3313 function ble/util/msleep/.calibrate-loop {
   3314   local _ble_measure_threshold=10000
   3315   local ret nsec _ble_measure_count=1 v=0
   3316   _ble_util_msleep_delay=0 ble-measure -q 'ble/util/msleep 1'
   3317   local delay=$((nsec/1000-1000)) count=$_ble_util_msleep_calibrate_count
   3318   ((count<=0||delay<_ble_util_msleep_delay)) && _ble_util_msleep_delay=$delay # 最小値
   3319   # ((_ble_util_msleep_delay=(count*_ble_util_msleep_delay+delay)/(count+1))) # 平均値
   3320 }
   3321 function ble/util/msleep/calibrate {
   3322   ble/util/msleep/.calibrate-loop &>/dev/null
   3323   ((++_ble_util_msleep_calibrate_count<5)) &&
   3324     ble/util/idle.continue
   3325 }
   3326 
   3327 ## @fn ble/util/msleep/.use-read-timeout type
   3328 ##   @param[in] type
   3329 ##     FILE.OPEN
   3330 ##       FILE=fifo mkfifo によりファイルを作成します。
   3331 ##       FILE=zero /dev/zero を開きます。
   3332 ##       FILE=ptmx /dev/ptmx を開きます。
   3333 ##       OPEN=open 毎回ファイルを開きます。
   3334 ##       OPEN=exec1 ファイルを読み取り専用で開きます。
   3335 ##       OPEN=exec2 ファイルを読み書き両用で開きます。
   3336 ##     socket
   3337 ##       /dev/udp/0.0.0.0/80 を使います。
   3338 ##     procsub
   3339 ##       9< <(sleep) を使います。
   3340 function ble/util/msleep/.use-read-timeout {
   3341   local msleep_type=$1 opts=${2-}
   3342   _ble_util_msleep_fd=
   3343   case $msleep_type in
   3344   (socket)
   3345     _ble_util_msleep_delay1=10000 # short msleep にかかる時間 [usec]
   3346     _ble_util_msleep_delay2=50000 # /bin/sleep 0 にかかる時間 [usec]
   3347     function ble/util/msleep/.core2 {
   3348       ((v-=_ble_util_msleep_delay2))
   3349       ble/bin/sleep "$((v/1000000))"
   3350       ((v%=1000000))
   3351     }
   3352     function ble/util/msleep {
   3353       local v=$((1000*$1-_ble_util_msleep_delay1))
   3354       ((v<=0)) && v=100
   3355       ((v>1000000+_ble_util_msleep_delay2)) &&
   3356         ble/util/msleep/.core2
   3357       ble/util/sprintf v '%d.%06d' "$((v/1000000))" "$((v%1000000))"
   3358       ! ble/bash/read-timeout "$v" v < /dev/udp/0.0.0.0/80
   3359     }
   3360     function ble/util/msleep/.calibrate-loop {
   3361       local _ble_measure_threshold=10000
   3362       local ret nsec _ble_measure_count=1 v=0
   3363 
   3364       _ble_util_msleep_delay1=0 ble-measure 'ble/util/msleep 1'
   3365       local delay=$((nsec/1000-1000)) count=$_ble_util_msleep_calibrate_count
   3366       ((count<=0||delay<_ble_util_msleep_delay1)) && _ble_util_msleep_delay1=$delay # 最小値
   3367 
   3368       _ble_util_msleep_delay2=0 ble-measure 'ble/util/msleep/.core2'
   3369       local delay=$((nsec/1000))
   3370       ((count<=0||delay<_ble_util_msleep_delay2)) && _ble_util_msleep_delay2=$delay # 最小値
   3371     } ;;
   3372   (procsub)
   3373     _ble_util_msleep_delay=300
   3374     ble/fd#alloc _ble_util_msleep_fd '< <(
   3375       [[ $- == *i* ]] && builtin trap -- '' INT QUIT
   3376       while kill -0 $$; do command sleep 300; done &>/dev/null
   3377     )'
   3378     function ble/util/msleep {
   3379       local v=$((1000*$1-_ble_util_msleep_delay))
   3380       ((v<=0)) && v=100
   3381       ble/util/sprintf v '%d.%06d' "$((v/1000000))" "$((v%1000000))"
   3382       ! ble/bash/read-timeout "$v" -u "$_ble_util_msleep_fd" v
   3383     } ;;
   3384   (*.*)
   3385     if local rex='^(fifo|zero|ptmx)\.(open|exec)([12])(-[a-z]+)?$'; [[ $msleep_type =~ $rex ]]; then
   3386       local file=${BASH_REMATCH[1]}
   3387       local open=${BASH_REMATCH[2]}
   3388       local direction=${BASH_REMATCH[3]}
   3389       local fall=${BASH_REMATCH[4]}
   3390 
   3391       # tmpfile
   3392       case $file in
   3393       (fifo)
   3394         _ble_util_msleep_tmp=$_ble_base_run/$$.util.msleep.pipe
   3395         if [[ ! -p $_ble_util_msleep_tmp ]]; then
   3396           [[ -e $_ble_util_msleep_tmp ]] && ble/bin/rm -rf "$_ble_util_msleep_tmp"
   3397           ble/bin/mkfifo "$_ble_util_msleep_tmp"
   3398         fi ;;
   3399       (zero)
   3400         open=dup
   3401         _ble_util_msleep_tmp=$_ble_util_fd_zero ;;
   3402       (ptmx)
   3403         _ble_util_msleep_tmp=/dev/ptmx ;;
   3404       esac
   3405 
   3406       # redirection type
   3407       local redir='<'
   3408       ((direction==2)) && redir='<>'
   3409 
   3410       # open type
   3411       if [[ $open == dup ]]; then
   3412         _ble_util_msleep_fd=$_ble_util_msleep_tmp
   3413         _ble_util_msleep_read='! ble/bash/read-timeout "$v" -u "$_ble_util_msleep_fd" v'
   3414       elif [[ $open == exec ]]; then
   3415         ble/fd#alloc _ble_util_msleep_fd "$redir \"\$_ble_util_msleep_tmp\""
   3416         _ble_util_msleep_read='! ble/bash/read-timeout "$v" -u "$_ble_util_msleep_fd" v'
   3417       else
   3418         _ble_util_msleep_read='! ble/bash/read-timeout "$v" v '$redir' "$_ble_util_msleep_tmp"'
   3419       fi
   3420 
   3421       # fallback/switch
   3422       if [[ $fall == '-coreutil' ]]; then
   3423         _ble_util_msleep_switch=200 # [msec]
   3424         _ble_util_msleep_delay1=2000 # short msleep にかかる時間 [usec]
   3425         _ble_util_msleep_delay2=50000 # /bin/sleep 0 にかかる時間 [usec]
   3426         function ble/util/msleep {
   3427           if (($1<_ble_util_msleep_switch)); then
   3428             local v=$((1000*$1-_ble_util_msleep_delay1))
   3429             ((v<=0)) && v=100
   3430             ble/util/sprintf v '%d.%06d' "$((v/1000000))" "$((v%1000000))"
   3431             builtin eval -- "$_ble_util_msleep_read"
   3432           else
   3433             local v=$((1000*$1-_ble_util_msleep_delay2))
   3434             ((v<=0)) && v=100
   3435             ble/util/sprintf v '%d.%06d' "$((v/1000000))" "$((v%1000000))"
   3436             ble/bin/sleep "$v"
   3437           fi
   3438         }
   3439         function ble/util/msleep/.calibrate-loop {
   3440           local _ble_measure_threshold=10000
   3441           local ret nsec _ble_measure_count=1
   3442 
   3443           _ble_util_msleep_switch=200
   3444           _ble_util_msleep_delay1=0 ble-measure 'ble/util/msleep 1'
   3445           local delay=$((nsec/1000-1000)) count=$_ble_util_msleep_calibrate_count
   3446           ((count<=0||delay<_ble_util_msleep_delay1)) && _ble_util_msleep_delay1=$delay # 最小値を選択
   3447 
   3448           _ble_util_msleep_delay2=0 ble-measure 'ble/bin/sleep 0'
   3449           local delay=$((nsec/1000))
   3450           ((count<=0||delay<_ble_util_msleep_delay2)) && _ble_util_msleep_delay2=$delay # 最小値を選択
   3451           ((_ble_util_msleep_switch=_ble_util_msleep_delay2/1000+10))
   3452         }
   3453       else
   3454         function ble/util/msleep {
   3455           local v=$((1000*$1-_ble_util_msleep_delay))
   3456           ((v<=0)) && v=100
   3457           ble/util/sprintf v '%d.%06d' "$((v/1000000))" "$((v%1000000))"
   3458           builtin eval -- "$_ble_util_msleep_read"
   3459         }
   3460       fi
   3461     fi ;;
   3462   esac
   3463 
   3464   # Note: 古い Cygwin では双方向パイプで "Communication error on send" というエラーになる。
   3465   #   期待通りの振る舞いをしなかったらプロセス置換に置き換える。 #D1449
   3466   # #D1467 Cygwin/Linux では timeout は 142 だが、これはシステム依存。
   3467   #   man bash にある様に 128 より大きいかどうかで判定
   3468   if [[ :$opts: == *:check:* && $_ble_util_msleep_fd ]]; then
   3469     if ble/bash/read-timeout 0.000001 -u "$_ble_util_msleep_fd" _ble_util_msleep_dummy 2>/dev/null; (($?<=128)); then
   3470       ble/fd#close _ble_util_msleep_fd
   3471       _ble_util_msleep_fd=
   3472       return 1
   3473     fi
   3474   fi
   3475   return 0
   3476 }
   3477 
   3478 _ble_util_msleep_builtin_available=
   3479 if ((_ble_bash>=40400)) && ble/util/msleep/.check-builtin-sleep; then
   3480   _ble_util_msleep_builtin_available=1
   3481   _ble_util_msleep_delay=300
   3482   function ble/util/msleep/.core { builtin sleep "$1"; }
   3483 
   3484   ## @fn ble/builtin/sleep/.read-time time
   3485   ##   @var[out] a1 b1
   3486   ##     それぞれ整数部と小数部を返します。
   3487   ##   @var[in,out] flags
   3488   function ble/builtin/sleep/.read-time {
   3489     a1=0 b1=0
   3490     local unit= exp=
   3491     if local rex='^\+?([0-9]*)\.([0-9]*)([eE][-+]?[0-9]+)?([smhd]?)$'; [[ $1 =~ $rex ]]; then
   3492       a1=${BASH_REMATCH[1]}
   3493       b1=${BASH_REMATCH[2]}00000000000000
   3494       b1=$((10#0${b1::14}))
   3495       exp=${BASH_REMATCH[3]}
   3496       unit=${BASH_REMATCH[4]}
   3497     elif rex='^\+?([0-9]+)([eE][-+]?[0-9]+)?([smhd]?)$'; [[ $1 =~ $rex ]]; then
   3498       a1=${BASH_REMATCH[1]}
   3499       exp=${BASH_REMATCH[2]}
   3500       unit=${BASH_REMATCH[3]}
   3501     else
   3502       ble/util/print "ble/builtin/sleep: invalid time spec '$1'" >&2
   3503       flags=E$flags
   3504       return 2
   3505     fi
   3506 
   3507     if [[ $exp ]]; then
   3508       case $exp in
   3509       ([eE]-*)
   3510         ((exp=10#0${exp:2}))
   3511         while ((exp--)); do
   3512           ((b1=a1%10*frac_scale/10+b1/10,a1/=10))
   3513         done ;;
   3514       ([eE]*)
   3515         exp=${exp:1}
   3516         ((exp=${exp#+}))
   3517         while ((exp--)); do
   3518           ((b1*=10,a1=a1*10+b1/frac_scale,b1%=frac_scale))
   3519         done ;;
   3520       esac
   3521     fi
   3522 
   3523     local scale=
   3524     case $unit in
   3525     (d) ((scale=24*3600)) ;;
   3526     (h) ((scale=3600)) ;;
   3527     (m) ((scale=60)) ;;
   3528     esac
   3529     if [[ $scale ]]; then
   3530       ((b1*=scale))
   3531       ((a1=a1*scale+b1/frac_scale))
   3532       ((b1%=frac_scale))
   3533     fi
   3534     return 0
   3535   }
   3536 
   3537   function ble/builtin/sleep {
   3538     local set shopt; ble/base/.adjust-bash-options set shopt
   3539     local frac_scale=100000000000000
   3540     local a=0 b=0 flags=
   3541     if (($#==0)); then
   3542       ble/util/print "ble/builtin/sleep: no argument" >&2
   3543       flags=E$flags
   3544     fi
   3545     while (($#)); do
   3546       case $1 in
   3547       (--version) flags=v$flags ;;
   3548       (--help)    flags=h$flags ;;
   3549       (-*)
   3550         flags=E$flags
   3551         ble/util/print "ble/builtin/sleep: unknown option '$1'" >&2 ;;
   3552       (*)
   3553         if local a1 b1; ble/builtin/sleep/.read-time "$1"; then
   3554           ((b+=b1))
   3555           ((a=a+a1+b/frac_scale))
   3556           ((b%=frac_scale))
   3557         fi ;;
   3558       esac
   3559       shift
   3560     done
   3561     if [[ $flags == *h* ]]; then
   3562       ble/util/print-lines \
   3563         'usage: sleep NUMBER[SUFFIX]...' \
   3564         'Pause for the time specified by the sum of the arguments. SUFFIX is one of "s"' \
   3565         '(seconds), "m" (minutes), "h" (hours) or "d" (days).' \
   3566         '' \
   3567         'OPTIONS' \
   3568         '     --help    Show this help.' \
   3569         '     --version Show version.'
   3570     fi
   3571     if [[ $flags == *v* ]]; then
   3572       ble/util/print "sleep (ble) $BLE_VERSION"
   3573     fi
   3574     if [[ $flags == *E* ]]; then
   3575       ble/util/setexit 2
   3576     elif [[ $flags == *[vh]* ]]; then
   3577       ble/util/setexit 0
   3578     else
   3579       b=00000000000000$b
   3580       b=${b:${#b}-14}
   3581       builtin sleep "$a.$b"
   3582     fi
   3583     local ext=$?
   3584     ble/base/.restore-bash-options set shopt 1
   3585     return "$ext"
   3586   }
   3587   function sleep { ble/builtin/sleep "$@"; }
   3588 elif [[ -f $_ble_base/lib/init-msleep.sh ]] &&
   3589        source "$_ble_base/lib/init-msleep.sh" &&
   3590        ble/util/msleep/.load-compiled-builtin
   3591 then
   3592   # 自前で sleep.so をコンパイルする。
   3593   #
   3594   # Note: #D1452 #D1468 #D1469 元々使っていた read -t による手法が
   3595   # Bash のバグでブロックする事が分かった。bash 4.3..5.1 ならばどの OS
   3596   # でも再現する。仕方が無いので自前で loadable builtin をコンパイルす
   3597   # る事にした。と思ったがライセンスの問題でこれを有効にする訳には行か
   3598   # ない。
   3599   function ble/util/msleep { ble/builtin/msleep "$1"; }
   3600 elif ((40000<=_ble_bash&&!(40300<=_ble_bash&&_ble_bash<50200))) &&
   3601        [[ $OSTYPE != cygwin* && $OSTYPE != mingw* && $OSTYPE != haiku* && $OSTYPE != minix* ]]
   3602 then
   3603   # FIFO (mkfifo) を予め読み書き両用で開いて置き read -t する方法。
   3604   #
   3605   # Note: #D1452 #D1468 #D1469 Bash 4.3 以降では一般に read -t が
   3606   # SIGALRM との race condition で固まる可能性がある。socket
   3607   # (/dev/udp) や fifo で特に問題が発生しやすい。特に Cygwin で顕著。
   3608   # 但し、発生する頻度は環境や用法・手法によって異なる。Cygwin/MSYS,
   3609   # Haiku 及び Minix では fifo は思う様に動かない。
   3610   ble/util/msleep/.use-read-timeout fifo.exec2
   3611 elif ((_ble_bash>=40000)) && ble/fd#is-open "$_ble_util_fd_zero"; then
   3612   # /dev/zero に対して read -t する方法。
   3613   #
   3614   # Note: #D1452 #D1468 #D1469 元々使っていた FIFO に対する方法が安全
   3615   # でない時は /dev/zero に対して read -t する。0 を読み続ける事になる
   3616   # ので CPU を使う事になるが短時間の sleep の時のみに使う事にして我慢
   3617   # する事にする。確認した全ての OS で /dev/zero は存在した (Linux,
   3618   # Cygwin, FreeBSD, Solaris, Minix, Haiku, MSYS2)。
   3619   ble/util/msleep/.use-read-timeout zero.exec1-coreutil
   3620 elif ble/bin#freeze-utility-path sleepenh; then
   3621   function ble/util/msleep/.core { ble/bin/sleepenh "$1" &>/dev/null; }
   3622 elif ble/bin#freeze-utility-path usleep; then
   3623   function ble/util/msleep {
   3624     local v=$((1000*$1-_ble_util_msleep_delay))
   3625     ((v<=0)) && v=0
   3626     ble/bin/usleep "$v" &>/dev/null
   3627   }
   3628 elif ble/util/msleep/.check-sleep-decimal-support; then
   3629   function ble/util/msleep/.core { ble/bin/sleep "$1"; }
   3630 fi
   3631 
   3632 function ble/util/sleep {
   3633   local msec=$((${1%%.*}*1000))
   3634   if [[ $1 == *.* ]]; then
   3635     frac=${1##*.}000
   3636     ((msec+=10#0${frac::3}))
   3637   fi
   3638   ble/util/msleep "$msec"
   3639 }
   3640 
   3641 #------------------------------------------------------------------------------
   3642 # ble/util/conditional-sync
   3643 
   3644 function ble/util/conditional-sync/.collect-descendant-pids {
   3645   local pid=$1 awk_script='
   3646     $1 ~ /^[0-9]+$/ && $2 ~ /^[0-9]+$/ {
   3647       child[$2,child[$2]++]=$1;
   3648     }
   3649     function print_recursive(pid, _, n, i) {
   3650       if (child[pid]) {
   3651         n = child[pid];
   3652         child[pid] = 0; # avoid infinite loop
   3653         for (i = 0; i < n; i++) {
   3654           print_recursive(child[pid, i]);
   3655         }
   3656       }
   3657       print pid;
   3658     }
   3659     END { print_recursive(pid); }
   3660   '
   3661   ble/util/assign ret 'ble/bin/ps -A -o pid,ppid'
   3662   ble/util/assign-array ret 'ble/bin/awk -v pid="$pid" "$awk_script" <<< "$ret"'
   3663 }
   3664 
   3665 ## @fn ble/util/conditional-sync/.kill
   3666 ##   @var[in] __ble_pid
   3667 ##   @var[in] __ble_opts
   3668 function ble/util/conditional-sync/.kill {
   3669   [[ $__ble_pid ]] || return 0
   3670   local kill_pids
   3671   if [[ :$__ble_opts: == *:killall:* ]]; then
   3672     ble/util/conditional-sync/.collect-descendant-pids "$__ble_pid"
   3673     kill_pids=("${ret[@]}")
   3674   else
   3675     kill_pids=("$__ble_pid")
   3676   fi
   3677 
   3678   # Note #D2031: In Cygwin/MSYS2 (Windows), we somehow need to fork at least
   3679   # one process before `kill` to make sure it works.
   3680   if [[ $OSTYPE == cygwin* || $OSTYPE == msys* ]]; then
   3681     (ble/util/setexit 0)
   3682   fi
   3683 
   3684   if [[ :$__ble_opts: == *:SIGKILL:* ]]; then
   3685     builtin kill -9 "${kill_pids[@]}" &>/dev/null
   3686   else
   3687     builtin kill -- "${kill_pids[@]}" &>/dev/null
   3688   fi
   3689 } &>/dev/null
   3690 
   3691 ## @fn ble/util/conditional-sync command [condition weight opts]
   3692 ##   Evaluate COMMAND and kill it when CONDITION becomes unsatisfied before
   3693 ##   COMMAND ends.
   3694 ##
   3695 ##   @param[in] command
   3696 ##     The command that is evaluated in a subshell.  If an empty string is
   3697 ##     specified, only the CONDITION is checked for the synchronization and any
   3698 ##     background subshell is not started.
   3699 ##
   3700 ##   @param[in,opt] condition
   3701 ##     The command to test the condition to continue to run the command.  The
   3702 ##     default condition is "! ble/decode/has-input".  The following local
   3703 ##     variables are available from the condition command:
   3704 ##
   3705 ##     @var sync_elapsed
   3706 ##       Accumulated time of sleep in milliseconds.
   3707 ##
   3708 ##   @param[in,opt] weight
   3709 ##     The interval of checking CONDITION in milliseconds.  The default is
   3710 ##     "100".
   3711 ##   @param[in,opt] opts
   3712 ##     A colon-separated list of the following fields to control the detailed
   3713 ##     behavior:
   3714 ##
   3715 ##     @opt progressive-weight
   3716 ##       The interval of checking CONDITION is gradually increased and stops
   3717 ##       at the value specified by WEIGHT.
   3718 ##     @opt timeout=TIMEOUT
   3719 ##       When this is specified, COMMAND is unconditionally terminated when
   3720 ##       it does not end until the time specified by TIMEOUT in milliseconds
   3721 ##     @opt killall
   3722 ##       Kill also all the children and descendant processes. When this is
   3723 ##       unspecified, only the subshell used to run COMMAND is killed.
   3724 ##     @opt SIGKILL
   3725 ##       The processes are killed by SIGKILL.  When this is unspecified, the
   3726 ##       processes are killed by SIGTERM.
   3727 ##
   3728 ##     @opt pid=PID
   3729 ##       When specified, COMMAND is not evaluate, and the function instead
   3730 ##       waits for the exit of the process specified by PID.  If a negative
   3731 ##       integer is specified, it is treated as PGID.  When the condition is
   3732 ##       unsatisfied or the timeout has been reached, the specified process
   3733 ##       will be killed.
   3734 ##
   3735 ##     @opt no-wait-pid
   3736 ##       Do not wait for the exit status of the background process
   3737 ##
   3738 function ble/util/conditional-sync {
   3739   local __ble_command=$1
   3740   local __ble_continue=${2:-'! ble/decode/has-input'}
   3741   local __ble_weight=$3; ((__ble_weight<=0&&(__ble_weight=100)))
   3742   local __ble_opts=$4
   3743 
   3744   local __ble_timeout= __ble_rex=':timeout=([^:]+):'
   3745   [[ :$__ble_opts: =~ $__ble_rex ]] && ((__ble_timeout=BASH_REMATCH[1]))
   3746 
   3747   [[ :$__ble_opts: == *:progressive-weight:* ]] &&
   3748     local __ble_weight_max=$__ble_weight __ble_weight=1
   3749 
   3750   # read opt "pid=PID/-PGID"
   3751   local ret
   3752   ble/opts#extract-last-optarg "$__ble_opts" pid
   3753   local __ble_pid=$ret
   3754   ble/util/unlocal ret
   3755 
   3756   local sync_elapsed=0
   3757   if [[ $__ble_timeout ]] && ((__ble_timeout<=0)); then
   3758     ble/util/conditional-sync/.kill
   3759     return 142
   3760   fi
   3761   builtin eval -- "$__ble_continue" || return 148
   3762   (
   3763     [[ $__ble_pid ]] || builtin eval -- "$__ble_command" & __ble_pid=$!
   3764     while
   3765       # check timeout
   3766       if [[ $__ble_timeout ]]; then
   3767         if ((__ble_timeout<=0)); then
   3768           ble/util/conditional-sync/.kill
   3769           return 142
   3770         fi
   3771         ((__ble_weight>__ble_timeout)) && __ble_weight=$__ble_timeout
   3772         ((__ble_timeout-=__ble_weight))
   3773       fi
   3774 
   3775       ble/util/msleep "$__ble_weight"
   3776       ((sync_elapsed+=__ble_weight))
   3777       [[ :$__ble_opts: == *:progressive-weight:* ]] &&
   3778         ((__ble_weight<<=1,__ble_weight>__ble_weight_max&&(__ble_weight=__ble_weight_max)))
   3779       [[ ! $__ble_pid ]] || builtin kill -0 "$__ble_pid" &>/dev/null
   3780     do
   3781       if ! builtin eval -- "$__ble_continue"; then
   3782         ble/util/conditional-sync/.kill
   3783         return 148
   3784       fi
   3785     done
   3786     [[ ! $__ble_pid || :$__ble_opts: == *:no-wait-pid:* ]] || wait "$__ble_pid"
   3787   )
   3788 }
   3789 
   3790 #------------------------------------------------------------------------------
   3791 
   3792 ## @fn ble/util/cat [files..]
   3793 ##   cat の代替。直接扱えない NUL で区切って読み出す。
   3794 function ble/util/cat/.impl {
   3795   local content= IFS=
   3796   while ble/bash/read -d '' content; do
   3797     printf '%s\0' "$content"
   3798   done
   3799   [[ $content ]] && printf '%s' "$content"
   3800 }
   3801 function ble/util/cat {
   3802   if (($#)); then
   3803     local file
   3804     for file; do ble/util/cat/.impl < "$1"; done
   3805   else
   3806     ble/util/cat/.impl
   3807   fi
   3808 }
   3809 
   3810 _ble_util_less_fallback=
   3811 function ble/util/get-pager {
   3812   if [[ ! $_ble_util_less_fallback ]]; then
   3813     if type -t less &>/dev/null; then
   3814       _ble_util_less_fallback=less
   3815     elif type -t pager &>/dev/null; then
   3816       _ble_util_less_fallback=pager
   3817     elif type -t more &>/dev/null; then
   3818       _ble_util_less_fallback=more
   3819     else
   3820       _ble_util_less_fallback=cat
   3821     fi
   3822   fi
   3823 
   3824   builtin eval "$1=\${bleopt_pager:-\${PAGER:-\$_ble_util_less_fallback}}"
   3825 }
   3826 function ble/util/pager {
   3827   local pager; ble/util/get-pager pager
   3828   builtin eval -- "$pager \"\$@\""
   3829 }
   3830 
   3831 _ble_util_file_stat=
   3832 function ble/file/has-stat {
   3833   if [[ ! $_ble_util_file_stat ]]; then
   3834     _ble_util_file_stat=-
   3835     if ble/bin#freeze-utility-path -n stat; then
   3836       # 参考: http://stackoverflow.com/questions/17878684/best-way-to-get-file-modified-time-in-seconds
   3837       if ble/bin/stat -c %Y / &>/dev/null; then
   3838         _ble_util_file_stat=c
   3839       elif ble/bin/stat -f %m / &>/dev/null; then
   3840         _ble_util_file_stat=f
   3841       fi
   3842     fi
   3843   fi
   3844 
   3845   function ble/file/has-stat { [[ $_ble_util_file_stat != - ]]; } || return 1
   3846   ble/file/has-stat
   3847 }
   3848 
   3849 ## @fn ble/file#mtime filename
   3850 ##   ファイル filename の mtime を取得します。
   3851 ##   @param[in] filename ファイル名を指定します。
   3852 ##
   3853 ##   @var[out] ret
   3854 ##     時刻を Unix Epoch で取得します。
   3855 ##     秒以下の少数も取得できる場合には ret[1] に小数部を格納します。
   3856 ##
   3857 function ble/file#mtime {
   3858   # fallback: print current time
   3859   function ble/file#mtime { ble/util/strftime -v ret '%s %N'; ble/string#split-words ret "$ret"; ((0)); } || return 1
   3860 
   3861   if ble/bin/date -r / +%s &>/dev/null; then
   3862     function ble/file#mtime { local file=$1; ble/util/assign-words ret 'ble/bin/date -r "$file" +"%s %N"' 2>/dev/null; }
   3863   elif ble/file/has-stat; then
   3864     # 参考: http://stackoverflow.com/questions/17878684/best-way-to-get-file-modified-time-in-seconds
   3865     case $_ble_util_file_stat in
   3866     (c) function ble/file#mtime { local file=$1; ble/util/assign ret 'ble/bin/stat -c %Y "$file"' 2>/dev/null; } ;;
   3867     (f) function ble/file#mtime { local file=$1; ble/util/assign ret 'ble/bin/stat -f %m "$file"' 2>/dev/null; } ;;
   3868     esac
   3869   fi
   3870 
   3871   ble/file#mtime "$@"
   3872 }
   3873 
   3874 function ble/file#inode {
   3875   # fallback
   3876   function ble/file#inode { ret=; ((0)); } || return 1
   3877 
   3878   if ble/bin#freeze-utility-path -n ls &&
   3879       ble/util/assign-words ret 'ble/bin/ls -di /' 2>/dev/null &&
   3880       ((${#ret[@]}==2)) && ble/string#match "$ret" '^[0-9]+$'
   3881   then
   3882     function ble/file#inode { local file=$1; ble/util/assign-words ret 'ble/bin/ls -di "$file"' 2>/dev/null; }
   3883   elif ble/file/has-stat; then
   3884     case $_ble_util_file_stat in
   3885     (c) function ble/file#inode { local file=$1; ble/util/assign-words ret 'ble/bin/stat -c %i "$file"' 2>/dev/null; } ;;
   3886     (f) function ble/file#inode { local file=$1; ble/util/assign-words ret 'ble/bin/stat -f %i "$file"' 2>/dev/null; } ;;
   3887     esac
   3888   fi
   3889 
   3890   ble/file#inode "$@"
   3891 }
   3892 
   3893 function ble/file#hash {
   3894   local file=$1 size
   3895   if ! ble/util/assign size 'ble/bin/wc -c "$file" 2>/dev/null'; then
   3896     ret=error:$RANDOM
   3897     return 1
   3898   fi
   3899   ble/string#split-words size "$size"
   3900   ble/file#hash/.impl
   3901 }
   3902 if ble/bin#freeze-utility-path -n git; then
   3903   function ble/file#hash/.impl {
   3904     ble/util/assign ret 'ble/bin/git hash-object "$file"'
   3905     ret="size:$size;hash:$ret"
   3906   }
   3907 elif ble/bin#freeze-utility-path -n openssl; then
   3908   function ble/file#hash/.impl {
   3909     ble/util/assign-words ret 'ble/bin/openssl sha1 -r "$file"'
   3910     ret="size:$size;sha1:$ret"
   3911   }
   3912 elif ble/bin#freeze-utility-path -n sha1sum; then
   3913   function ble/file#hash/.impl {
   3914     ble/util/assign-words ret 'ble/bin/sha1sum "$file"'
   3915     ret="size:$size;sha1:$ret"
   3916   }
   3917 elif ble/bin#freeze-utility-path -n sha1; then
   3918   function ble/file#hash/.impl {
   3919     ble/util/assign-words ret 'ble/bin/sha1 -r "$file"'
   3920     ret="size:$size;sha1:$ret"
   3921   }
   3922 elif ble/bin#freeze-utility-path -n md5sum; then
   3923   function ble/file#hash/.impl {
   3924     ble/util/assign-words ret 'ble/bin/md5sum "$file"'
   3925     ret="size:$size;md5:$ret"
   3926   }
   3927 elif ble/bin#freeze-utility-path -n md5; then
   3928   function ble/file#hash/.impl {
   3929     ble/util/assign-words ret 'ble/bin/md5 -r "$file"'
   3930     ret="size:$size;md5:$ret"
   3931   }
   3932 elif ble/bin#freeze-utility-path -n cksum; then
   3933   function ble/file#hash/.impl {
   3934     ble/util/assign-words ret 'ble/bin/cksum "$file"'
   3935     ret="size:$size;cksum:$ret"
   3936   }
   3937 else
   3938   function ble/file#hash/.impl {
   3939     ret="size:$size"
   3940   }
   3941 fi
   3942 
   3943 #------------------------------------------------------------------------------
   3944 ## @fn ble/util/buffer text
   3945 _ble_util_buffer=()
   3946 function ble/util/buffer {
   3947   _ble_util_buffer[${#_ble_util_buffer[@]}]=$1
   3948 }
   3949 function ble/util/buffer.print {
   3950   ble/util/buffer "$1"$'\n'
   3951 }
   3952 function ble/util/buffer.flush {
   3953   IFS= builtin eval 'local text="${_ble_util_buffer[*]-}"'
   3954   _ble_util_buffer=()
   3955   [[ $text ]] || return 0
   3956 
   3957   # Note: 出力の瞬間だけカーソルを非表示にする。Windows terminal など途中
   3958   # のカーソル移動も無理やり表示しようとする端末に対する対策。
   3959   [[ $_ble_term_state == internal ]] &&
   3960     [[ $_ble_term_cursor_hidden_internal != hidden ]] &&
   3961     [[ $text != *"$_ble_term_civis"* && $text != *"$_ble_term_cvvis"* ]] &&
   3962     text=$_ble_term_civis$text$_ble_term_cvvis
   3963 
   3964   ble/util/put "$text"
   3965 }
   3966 function ble/util/buffer.clear {
   3967   _ble_util_buffer=()
   3968 }
   3969 
   3970 #------------------------------------------------------------------------------
   3971 # class dirty-range, urange
   3972 
   3973 function ble/dirty-range#load {
   3974   local prefix=
   3975   if [[ $1 == --prefix=* ]]; then
   3976     prefix=${1#--prefix=}
   3977     ((beg=${prefix}beg,
   3978       end=${prefix}end,
   3979       end0=${prefix}end0))
   3980   fi
   3981 }
   3982 
   3983 function ble/dirty-range#clear {
   3984   local prefix=
   3985   if [[ $1 == --prefix=* ]]; then
   3986     prefix=${1#--prefix=}
   3987     shift
   3988   fi
   3989 
   3990   ((${prefix}beg=-1,
   3991     ${prefix}end=-1,
   3992     ${prefix}end0=-1))
   3993 }
   3994 
   3995 ## @fn ble/dirty-range#update [--prefix=PREFIX] beg end end0
   3996 ##   @param[out] PREFIX
   3997 ##   @param[in]  beg    変更開始点。beg<0 は変更がない事を表す
   3998 ##   @param[in]  end    変更終了点。end<0 は変更が末端までである事を表す
   3999 ##   @param[in]  end0   変更前の end に対応する位置。
   4000 function ble/dirty-range#update {
   4001   local prefix=
   4002   if [[ $1 == --prefix=* ]]; then
   4003     prefix=${1#--prefix=}
   4004     shift
   4005     [[ $prefix ]] && local beg end end0
   4006   fi
   4007 
   4008   local begB=$1 endB=$2 endB0=$3
   4009   ((begB<0)) && return 1
   4010 
   4011   local begA endA endA0
   4012   ((begA=${prefix}beg,endA=${prefix}end,endA0=${prefix}end0))
   4013 
   4014   local delta
   4015   if ((begA<0)); then
   4016     ((beg=begB,
   4017       end=endB,
   4018       end0=endB0))
   4019   else
   4020     ((beg=begA<begB?begA:begB))
   4021     if ((endA<0||endB<0)); then
   4022       ((end=-1,end0=-1))
   4023     else
   4024       ((end=endB,end0=endA0,
   4025         (delta=endA-endB0)>0?(end+=delta):(end0-=delta)))
   4026     fi
   4027   fi
   4028 
   4029   if [[ $prefix ]]; then
   4030     ((${prefix}beg=beg,
   4031       ${prefix}end=end,
   4032       ${prefix}end0=end0))
   4033   fi
   4034 }
   4035 
   4036 ## @fn ble/urange#clear [--prefix=prefix]
   4037 ##
   4038 ##   @param[in,opt] prefix=
   4039 ##   @var[in,out]   {prefix}umin {prefix}umax
   4040 ##
   4041 function ble/urange#clear {
   4042   local prefix=
   4043   if [[ $1 == --prefix=* ]]; then
   4044     prefix=${1#*=}; shift
   4045   fi
   4046   ((${prefix}umin=-1,${prefix}umax=-1))
   4047 }
   4048 ## @fn ble/urange#update [--prefix=prefix] min max
   4049 ##
   4050 ##   @param[in,opt] prefix=
   4051 ##   @param[in]     min max
   4052 ##   @var[in,out]   {prefix}umin {prefix}umax
   4053 ##
   4054 function ble/urange#update {
   4055   local prefix=
   4056   if [[ $1 == --prefix=* ]]; then
   4057     prefix=${1#*=}; shift
   4058   fi
   4059   local min=$1 max=$2
   4060   ((0<=min&&min<max)) || return 1
   4061   (((${prefix}umin<0||min<${prefix}umin)&&(${prefix}umin=min),
   4062     (${prefix}umax<0||${prefix}umax<max)&&(${prefix}umax=max)))
   4063 }
   4064 ## @fn ble/urange#shift [--prefix=prefix] dbeg dend dend0
   4065 ##
   4066 ##   @param[in,opt] prefix=
   4067 ##   @param[in]     dbeg dend dend0
   4068 ##   @var[in,out]   {prefix}umin {prefix}umax
   4069 ##
   4070 function ble/urange#shift {
   4071   local prefix=
   4072   if [[ $1 == --prefix=* ]]; then
   4073     prefix=${1#*=}; shift
   4074   fi
   4075   local dbeg=$1 dend=$2 dend0=$3 shift=$4
   4076   ((dbeg>=0)) || return 1
   4077   [[ $shift ]] || ((shift=dend-dend0))
   4078   ((${prefix}umin>=0&&(
   4079       dbeg<=${prefix}umin&&(${prefix}umin<=dend0?(${prefix}umin=dend):(${prefix}umin+=shift)),
   4080       dbeg<=${prefix}umax&&(${prefix}umax<=dend0?(${prefix}umax=dbeg):(${prefix}umax+=shift))),
   4081     ${prefix}umin<${prefix}umax||(
   4082       ${prefix}umin=-1,
   4083       ${prefix}umax=-1)))
   4084 }
   4085 
   4086 #------------------------------------------------------------------------------
   4087 ## @fn ble/util/joblist opts
   4088 ##   現在のジョブ一覧を取得すると共に、ジョブ状態の変化を調べる。
   4089 ##
   4090 ##   @param[in] opts
   4091 ##     ignore-volatile-jobs
   4092 ##
   4093 ##   @var[in,out] _ble_util_joblist_events
   4094 ##   @var[out]    joblist                ジョブ一覧を格納する配列
   4095 ##   @var[in,out] _ble_util_joblist_jobs 内部使用
   4096 ##   @var[in,out] _ble_util_joblist_list 内部使用
   4097 ##
   4098 ##   @remark 実装方法について。
   4099 ##   終了したジョブを確認するために内部で2回 jobs を呼び出す。
   4100 ##   比較のために前回の jobs の呼び出し結果も _ble_util_joblist_{jobs,list} (#1) に記録する。
   4101 ##   先ず jobs0,list (#2) に1回目の jobs 呼び出し結果を格納して #1 と #2 の比較を行いジョブ状態の変化を調べる。
   4102 ##   次に #1 に2回目の jobs 呼び出し結果を上書きして #2 と #1 の比較を行い終了ジョブを調べる。
   4103 ##
   4104 _ble_util_joblist_jobs=
   4105 _ble_util_joblist_list=()
   4106 _ble_util_joblist_events=()
   4107 function ble/util/joblist {
   4108   local opts=$1 jobs0
   4109   ble/util/assign jobs0 'jobs'
   4110   if [[ $jobs0 == "$_ble_util_joblist_jobs" ]]; then
   4111     # 前回の呼び出し結果と同じならば状態変化はないものとして良い。終了・強制終
   4112     # 了したジョブがあるとしたら "終了" だとか "Terminated" だとかいう表示にな
   4113     # っているはずだが、その様な表示は二回以上は為されないので必ず変化がある。
   4114     joblist=("${_ble_util_joblist_list[@]}")
   4115     return 0
   4116   elif [[ ! $jobs0 ]]; then
   4117     # 前回の呼び出しで存在したジョブが新しい呼び出しで無断で消滅することは恐ら
   4118     # くない。今回の結果が空という事は本来は前回の結果も空のはずであり、だとす
   4119     # ると上の分岐に入るはずなのでここには来ないはずだ。しかしここに入った時の
   4120     # 為に念を入れて空に設定して戻るようにする。
   4121     _ble_util_joblist_jobs=
   4122     _ble_util_joblist_list=()
   4123     joblist=()
   4124     return 0
   4125   fi
   4126 
   4127   local lines list ijob
   4128   ble/string#split lines $'\n' "$jobs0"
   4129   if ((${#lines[@]})); then
   4130     ble/util/joblist.split list "${lines[@]}"
   4131   else
   4132     list=()
   4133   fi
   4134 
   4135   # check changed jobs from _ble_util_joblist_list to list
   4136   if [[ $jobs0 != "$_ble_util_joblist_jobs" ]]; then
   4137     for ijob in "${!list[@]}"; do
   4138       if [[ ${_ble_util_joblist_list[ijob]} && ${list[ijob]#'['*']'[-+ ]} != "${_ble_util_joblist_list[ijob]#'['*']'[-+ ]}" ]]; then
   4139         if [[ ${list[ijob]} != *'__ble_suppress_joblist__'* ]]; then
   4140           ble/array#push _ble_util_joblist_events "${list[ijob]}"
   4141         fi
   4142         list[ijob]=
   4143       fi
   4144     done
   4145   fi
   4146 
   4147   ble/util/assign _ble_util_joblist_jobs 'jobs'
   4148   _ble_util_joblist_list=()
   4149   if [[ $_ble_util_joblist_jobs != "$jobs0" ]]; then
   4150     ble/string#split lines $'\n' "$_ble_util_joblist_jobs"
   4151     ble/util/joblist.split _ble_util_joblist_list "${lines[@]}"
   4152 
   4153     # check removed jobs through list -> _ble_util_joblist_list.
   4154     if [[ :$opts: != *:ignore-volatile-jobs:* ]]; then
   4155       for ijob in "${!list[@]}"; do
   4156         local job0=${list[ijob]}
   4157         if [[ $job0 && ! ${_ble_util_joblist_list[ijob]} ]]; then
   4158           if [[ $job0 != *'__ble_suppress_joblist__'* ]]; then
   4159             ble/array#push _ble_util_joblist_events "$job0"
   4160           fi
   4161         fi
   4162       done
   4163     fi
   4164   else
   4165     for ijob in "${!list[@]}"; do
   4166       [[ ${list[ijob]} ]] &&
   4167         _ble_util_joblist_list[ijob]=${list[ijob]}
   4168     done
   4169   fi
   4170   joblist=("${_ble_util_joblist_list[@]}")
   4171 } 2>/dev/null
   4172 
   4173 function ble/util/joblist.split {
   4174   local arr=$1; shift
   4175   local line ijob= rex_ijob='^\[([0-9]+)\]'
   4176   for line; do
   4177     [[ $line =~ $rex_ijob ]] && ijob=${BASH_REMATCH[1]}
   4178     [[ $ijob ]] && builtin eval "$arr[ijob]=\${$arr[ijob]}\${$arr[ijob]:+\$_ble_term_nl}\$line"
   4179   done
   4180 }
   4181 
   4182 ## @fn ble/util/joblist.check
   4183 ##   ジョブ状態変化の確認だけ行います。
   4184 ##   内部的に jobs を呼び出す直前に、ジョブ状態変化を取り逃がさない為に明示的に呼び出します。
   4185 function ble/util/joblist.check {
   4186   local joblist
   4187   ble/util/joblist "$@"
   4188 }
   4189 ## @fn ble/util/joblist.has-events
   4190 ##   未出力のジョブ状態変化の記録があるかを確認します。
   4191 function ble/util/joblist.has-events {
   4192   local joblist
   4193   ble/util/joblist
   4194   ((${#_ble_util_joblist_events[@]}))
   4195 }
   4196 
   4197 ## @fn ble/util/joblist.flush
   4198 ##   ジョブ状態変化の確認とそれまでに検出した変化の出力を行います。
   4199 function ble/util/joblist.flush {
   4200   local joblist
   4201   ble/util/joblist
   4202   ((${#_ble_util_joblist_events[@]})) || return 1
   4203   printf '%s\n' "${_ble_util_joblist_events[@]}"
   4204   _ble_util_joblist_events=()
   4205 }
   4206 function ble/util/joblist.bflush {
   4207   local joblist out
   4208   ble/util/joblist
   4209   ((${#_ble_util_joblist_events[@]})) || return 1
   4210   ble/util/sprintf out '%s\n' "${_ble_util_joblist_events[@]}"
   4211   ble/util/buffer "$out"
   4212   _ble_util_joblist_events=()
   4213 }
   4214 
   4215 ## @fn ble/util/joblist.clear
   4216 ##   bash 自身によってジョブ状態変化が出力される場合には比較用のバッファを clear します。
   4217 function ble/util/joblist.clear {
   4218   _ble_util_joblist_jobs=
   4219   _ble_util_joblist_list=()
   4220 }
   4221 
   4222 #------------------------------------------------------------------------------
   4223 ## @fn ble/util/save-editing-mode varname
   4224 ##   現在の編集モード (emacs/vi/none) を変数に設定します。
   4225 ##
   4226 ##   @param varname 設定する変数の変数名を指定します。
   4227 ##
   4228 function ble/util/save-editing-mode {
   4229   if [[ -o emacs ]]; then
   4230     builtin eval "$1=emacs"
   4231   elif [[ -o vi ]]; then
   4232     builtin eval "$1=vi"
   4233   else
   4234     builtin eval "$1=none"
   4235   fi
   4236 }
   4237 ## @fn ble/util/restore-editing-mode varname
   4238 ##   編集モードを復元します。
   4239 ##
   4240 ##   @param varname 編集モードを記録した変数の変数名を指定します。
   4241 ##
   4242 function ble/util/restore-editing-mode {
   4243   case ${!1} in
   4244   (emacs) set -o emacs ;;
   4245   (vi) set -o vi ;;
   4246   (none) set +o emacs ;;
   4247   esac
   4248 }
   4249 
   4250 ## @fn ble/util/reset-keymap-of-editing-mode
   4251 ##   既定の keymap に戻す。bind 'set keymap vi-insert' 等で
   4252 ##   既定の keymap 以外になっている事がある。
   4253 ##   set -o emacs/vi を実行すれば既定の keymap に戻る。#D1038
   4254 function ble/util/reset-keymap-of-editing-mode {
   4255   if [[ -o emacs ]]; then
   4256     set -o emacs
   4257   elif [[ -o vi ]]; then
   4258     set -o vi
   4259   fi
   4260 }
   4261 
   4262 ## @fn ble/util/rlvar#load
   4263 ##   @var[out] _ble_local_rlvars
   4264 function ble/util/rlvar#load {
   4265   # Note (#D1823): suppress warnings in a non-interactive session
   4266   ble/util/assign _ble_local_rlvars 'builtin bind -v 2>/dev/null'
   4267   _ble_local_rlvars=$'\n'$_ble_local_rlvars
   4268 }
   4269 
   4270 ## @fn ble/util/rlvar#has name
   4271 ##   指定した readline 変数に bash が対応しているか確認します。
   4272 function ble/util/rlvar#has {
   4273   if [[ ! ${_ble_local_rlvars:-} ]]; then
   4274     local _ble_local_rlvars
   4275     ble/util/rlvar#load
   4276   fi
   4277   [[ $_ble_local_rlvars == *$'\n'"set $1 "* ]]
   4278 }
   4279 
   4280 ## @fn ble/util/rlvar#test name [default(0 or 1)]
   4281 function ble/util/rlvar#test {
   4282   if [[ ! ${_ble_local_rlvars:-} ]]; then
   4283     local _ble_local_rlvars
   4284     ble/util/rlvar#load
   4285   fi
   4286   if [[ $_ble_local_rlvars == *$'\n'"set $1 on"* ]]; then
   4287     return 0
   4288   elif [[ $_ble_local_rlvars == *$'\n'"set $1 off"* ]]; then
   4289     return 1
   4290   elif (($#>=2)); then
   4291     (($2))
   4292     return "$?"
   4293   else
   4294     return 2
   4295   fi
   4296 }
   4297 ## @fn ble/util/rlvar#read name [default_value]
   4298 function ble/util/rlvar#read {
   4299   [[ ${2+set} ]] && ret=$2
   4300   if [[ ! ${_ble_local_rlvars:-} ]]; then
   4301     local _ble_local_rlvars
   4302     ble/util/rlvar#load
   4303   fi
   4304   local rhs=${_ble_local_rlvars#*$'\n'"set $1 "}
   4305   [[ $rhs != "$_ble_local_rlvars" ]] && ret=${rhs%%$'\n'*}
   4306 }
   4307 
   4308 ## @fn ble/util/rlvar#bind-bleopt name bleopt [opts]
   4309 function ble/util/rlvar#bind-bleopt {
   4310   local name=$1 bleopt=$2 opts=$3
   4311   if [[ ! ${_ble_local_rlvars:-} ]]; then
   4312     local _ble_local_rlvars
   4313     ble/util/rlvar#load
   4314   fi
   4315 
   4316   # Bash が readlie 変数に対応している場合、bleopt に対する代入と合わせて
   4317   # readline 変数にも対応する値を設定する。
   4318   if ble/util/rlvar#has "$name"; then
   4319     # 値の同期
   4320     # Note (#D1148): ble.sh の側で Bash と異なる既定値を持っている物については
   4321     # (初期化時に --keep-rlvars を指定していない限りは) ble.sh の側に書き換えて
   4322     # しまう。多くのユーザは自分で設定しないので便利な機能が off になっている。
   4323     # 一方で設定するユーザは自分で off に戻すぐらいはできるだろう。
   4324     if [[ :$_ble_base_arguments_opts: == *:keep-rlvars:* ]]; then
   4325       local ret; ble/util/rlvar#read "$name"
   4326       [[ :$opts: == *:bool:* && $ret == off ]] && ret=
   4327       bleopt "$bleopt=$ret"
   4328     else
   4329       local var=bleopt_$bleopt val=off
   4330       [[ ${!var:-} ]] && val=on
   4331       # Note: #D1823 suppress stderr for non-interactive warning
   4332       builtin bind "set $name $val" 2>/dev/null
   4333     fi
   4334 
   4335     local proc_original=
   4336     if ble/is-function "bleopt/check:$bleopt"; then
   4337       ble/function#push "bleopt/check:$bleopt"
   4338       proc_original='ble/function#push/call-top "$@" || return "$?"'
   4339     fi
   4340 
   4341     # Note: #D1823 suppress stderr for non-interactive warning
   4342     local proc_set='builtin bind "set '$name' $value" 2>/dev/null'
   4343     if [[ :$opts: == *:bool:* ]]; then
   4344       proc_set='
   4345         if [[ $value ]]; then
   4346           builtin bind "set '$name' on" 2>/dev/null
   4347         else
   4348           builtin bind "set '$name' off" 2>/dev/null
   4349         fi'
   4350     fi
   4351 
   4352     builtin eval -- "
   4353       function bleopt/check:$bleopt {
   4354         $proc_original
   4355         $proc_set
   4356         return 0
   4357       }"
   4358   fi
   4359 
   4360   local proc_bleopt='bleopt '$bleopt'="$1"'
   4361   if [[ :$opts: == *:bool:* ]]; then
   4362     proc_bleopt='
   4363       local value; ble/string#split-words value "$1"
   4364       if [[ ${value-} == 1 || ${value-} == [Oo][Nn] ]]; then
   4365         bleopt '$bleopt'="$value"
   4366       else
   4367         bleopt '$bleopt'=
   4368       fi'
   4369   fi
   4370   builtin eval -- "
   4371     function ble/builtin/bind/set:$name {
   4372       $proc_bleopt
   4373       return 0
   4374     }"
   4375 }
   4376 
   4377 #------------------------------------------------------------------------------
   4378 # Functions for modules
   4379 
   4380 ## @fn ble/util/invoke-hook array
   4381 ##   array に登録されているコマンドを実行します。
   4382 function ble/util/invoke-hook {
   4383   local -a hooks; builtin eval "hooks=(\"\${$1[@]}\")"
   4384   local hook ext=0
   4385   for hook in "${hooks[@]}"; do builtin eval -- "$hook \"\${@:2}\"" || ext=$?; done
   4386   return "$ext"
   4387 }
   4388 
   4389 ## @fn ble/util/.read-arguments-for-no-option-command commandname args...
   4390 ##   @var[out] flags args
   4391 function ble/util/.read-arguments-for-no-option-command {
   4392   local commandname=$1; shift
   4393   flags= args=()
   4394 
   4395   local flag_literal=
   4396   while (($#)); do
   4397     local arg=$1; shift
   4398     if [[ ! $flag_literal ]]; then
   4399       case $arg in
   4400       (--) flag_literal=1 ;;
   4401       (--help) flags=h$flags ;;
   4402       (-*)
   4403         ble/util/print "$commandname: unrecognized option '$arg'" >&2
   4404         flags=e$flags ;;
   4405       (*)
   4406         ble/array#push args "$arg" ;;
   4407       esac
   4408     else
   4409       ble/array#push args "$arg"
   4410     fi
   4411   done
   4412 }
   4413 
   4414 
   4415 ## @fn ble-autoload scriptfile functions...
   4416 ##   関数が定義されたファイルを自動で読み取る設定を行います。
   4417 ##   scriptfile には functions の実体を定義します。
   4418 ##   functions に指定した関数が初めて呼び出された時に、
   4419 ##   scriptfile が自動的に source されます。
   4420 ##
   4421 ##   @param[in] scriptfile
   4422 ##     functions が定義されているファイル
   4423 ##
   4424 ##     注意: このファイル内でグローバルに変数を定義する際は
   4425 ##     declare/typeset を用いないで下さい。
   4426 ##     autoload を行う関数内から source されるので、
   4427 ##     その関数のローカル変数として扱われてしまいます。
   4428 ##     連想配列などの特殊変数を定義したい場合は ble-autoload
   4429 ##     の設定時に同時に行って下さい。
   4430 ##     ※declare -g は bash-4.3 以降です
   4431 ##
   4432 ##   @param[in] functions...
   4433 ##     定義する関数名のリスト
   4434 ##
   4435 ##     scriptfile の source の起点となる関数です。
   4436 ##     scriptfile に定義される関数名を全て列挙する必要はなく、
   4437 ##     scriptfile 呼出の起点として使用する関数のみで充分です。
   4438 ##
   4439 function ble/util/autoload {
   4440   local file=$1; shift
   4441   ble/util/import/is-loaded "$file" && return 0
   4442 
   4443   # ※$FUNCNAME は元から環境変数に設定されている場合、
   4444   #   特別変数として定義されない。
   4445   #   この場合無闇にコマンドとして実行するのは危険である。
   4446 
   4447   local q=\' Q="'\''" funcname
   4448   for funcname; do
   4449     builtin eval "function $funcname {
   4450       builtin unset -f $funcname
   4451       ble-import '${file//$q/$Q}' &&
   4452         $funcname \"\$@\"
   4453     }"
   4454   done
   4455 }
   4456 function ble/util/autoload/.print-usage {
   4457   ble/util/print 'usage: ble-autoload SCRIPTFILE FUNCTION...'
   4458   ble/util/print '  Setup delayed loading of functions defined in the specified script file.'
   4459 } >&2
   4460 ## @fn ble/util/autoload/.read-arguments args...
   4461 ##   @var[out] file functions flags
   4462 function ble/util/autoload/.read-arguments {
   4463   file= flags= functions=()
   4464 
   4465   local args
   4466   ble/util/.read-arguments-for-no-option-command ble-autoload "$@"
   4467 
   4468   # check empty arguments
   4469   local arg index=0
   4470   for arg in "${args[@]}"; do
   4471     if [[ ! $arg ]]; then
   4472       if ((index==0)); then
   4473         ble/util/print 'ble-autoload: the script filename should not be empty.' >&2
   4474       else
   4475         ble/util/print 'ble-autoload: function names should not be empty.' >&2
   4476       fi
   4477       flags=e$flags
   4478     fi
   4479     ((index++))
   4480   done
   4481 
   4482   [[ $flags == *h* ]] && return 0
   4483 
   4484   if ((${#args[*]}==0)); then
   4485     ble/util/print 'ble-autoload: script filename is not specified.' >&2
   4486     flags=e$flags
   4487   elif ((${#args[*]}==1)); then
   4488     ble/util/print 'ble-autoload: function names are not specified.' >&2
   4489     flags=e$flags
   4490   fi
   4491 
   4492   file=${args[0]} functions=("${args[@]:1}")
   4493 }
   4494 function ble-autoload {
   4495   local file flags
   4496   local -a functions=()
   4497   ble/util/autoload/.read-arguments "$@"
   4498   if [[ $flags == *[eh]* ]]; then
   4499     [[ $flags == *e* ]] && builtin printf '\n'
   4500     ble/util/autoload/.print-usage
   4501     [[ $flags == *e* ]] && return 2
   4502     return 0
   4503   fi
   4504 
   4505   ble/util/autoload "$file" "${functions[@]}"
   4506 }
   4507 
   4508 ## @fn ble-import scriptfile...
   4509 ##   指定したファイルを検索して source で読み込みます。
   4510 ##   既に import 済みのファイルは読み込みません。
   4511 ##
   4512 ##   @param[in] scriptfile
   4513 ##     読み込むファイルを指定します。
   4514 ##     絶対パスで指定した場合にはそのファイルを使用します。
   4515 ##     それ以外の場合には $_ble_base:$_ble_base/local:$_ble_base/share から検索します。
   4516 ##
   4517 _ble_util_import_files=()
   4518 
   4519 bleopt/declare -n import_path "${XDG_DATA_HOME:-$HOME/.local/share}/blesh/local"
   4520 
   4521 ## @fn ble/util/import/search/.check-directory name dir
   4522 ##   @var[out] ret
   4523 function ble/util/import/search/.check-directory {
   4524   local name=$1 dir=${2%/}
   4525   [[ -d ${dir:=/} ]] || return 1
   4526 
   4527   # {lib,contrib}/ で始まるパスの時は lib,contrib ディレクトリのみで探索
   4528   if [[ $name == lib/* ]]; then
   4529     [[ $dir == */lib ]] || return 1
   4530     dir=${dir%/lib}
   4531   elif [[ $name == contrib/* ]]; then
   4532     [[ $dir == */contrib ]] || return 1
   4533     dir=${dir%/contrib}
   4534   fi
   4535 
   4536   if [[ -f $dir/$name ]]; then
   4537     ret=$dir/$name
   4538     return 0
   4539   elif [[ $name != *.bash && -f $dir/$name.bash ]]; then
   4540     ret=$dir/$name.bash
   4541     return 0
   4542   elif [[ $name != *.sh && -f $dir/$name.sh ]]; then
   4543     ret=$dir/$name.sh
   4544     return 0
   4545   fi
   4546   return 1
   4547 }
   4548 function ble/util/import/search {
   4549   ret=$1
   4550   if [[ $ret != /* && $ret != ./* && $ret != ../* ]]; then
   4551     local -a dirs=()
   4552     if [[ $bleopt_import_path ]]; then
   4553       local tmp; ble/string#split tmp : "$bleopt_import_path"
   4554       ble/array#push dirs "${tmp[@]}"
   4555     fi
   4556     ble/array#push dirs "$_ble_base"{,/contrib,/lib}
   4557 
   4558     "${_ble_util_set_declare[@]//NAME/checked}" # WA #D1570 checked
   4559     local path
   4560     for path in "${dirs[@]}"; do
   4561       ble/set#contains checked "$path" && continue
   4562       ble/set#add checked "$path"
   4563       ble/util/import/search/.check-directory "$ret" "$path" && break
   4564     done
   4565   fi
   4566   [[ -e $ret && ! -d $ret ]]
   4567 }
   4568 function ble/util/import/encode-filename {
   4569   ret=$1
   4570   local chars=%$'\t\n !"$&\'();<>\\^`|' # <emacs bug `>
   4571   if [[ $ret == *["$chars"]* ]]; then
   4572     local i n=${#chars} reps a b
   4573     reps=(%{25,08,0A,2{0..2},24,2{6..9},3B,3C,3E,5C,5E,60,7C})
   4574     for ((i=0;i<n;i++)); do
   4575       a=${chars:i:1} b=${reps[i]} ret=${ret//"$a"/"$b"}
   4576     done
   4577   fi
   4578   return 0
   4579 }
   4580 function ble/util/import/is-loaded {
   4581   local ret
   4582   ble/util/import/search "$1" &&
   4583     ble/util/import/encode-filename "$ret" &&
   4584     ble/is-function ble/util/import/guard:"$ret"
   4585 }
   4586 # called by ble/base/unload (ble.pp)
   4587 function ble/util/import/finalize {
   4588   local file ret
   4589   for file in "${_ble_util_import_files[@]}"; do
   4590     ble/util/import/encode-filename "$file"; local enc=$ret
   4591     local guard=ble/util/import/guard:$enc
   4592     builtin unset -f "$guard"
   4593 
   4594     local onload=ble/util/import/onload:$enc
   4595     if ble/is-function "$onload"; then
   4596       "$onload" ble/util/unlocal
   4597       builtin unset -f "$onload"
   4598     fi
   4599   done
   4600   _ble_util_import_files=()
   4601 }
   4602 ## @fn ble/util/import/.read-arguments args...
   4603 ##   @var[out] files not_found
   4604 ##   @var[out] flags
   4605 ##     E error
   4606 ##     h help
   4607 ##     d delay
   4608 ##     f force
   4609 ##     q query
   4610 function ble/util/import/.read-arguments {
   4611   flags= files=() not_found=()
   4612   while (($#)); do
   4613     local arg=$1; shift
   4614     if [[ $flags != *-* ]]; then
   4615       case $arg in
   4616       (--)
   4617         flags=-$flags
   4618         continue ;;
   4619       (--*)
   4620         case $arg in
   4621         (--delay) flags=d$flags ;;
   4622         (--help)  flags=h$flags ;;
   4623         (--force) flags=f$flags ;;
   4624         (--query) flags=q$flags ;;
   4625         (*)
   4626           ble/util/print "ble-import: unrecognized option '$arg'" >&2
   4627           flags=E$flags ;;
   4628         esac
   4629         continue ;;
   4630       (-?*)
   4631         local i c
   4632         for ((i=1;i<${#arg};i++)); do
   4633           c=${arg:i:1}
   4634           case $c in
   4635           ([dfq]) flags=$c$flags ;;
   4636           (*)
   4637             ble/util/print "ble-import: unrecognized option '-$c'" >&2
   4638             flags=E$flags ;;
   4639           esac
   4640         done
   4641         continue ;;
   4642       esac
   4643     fi
   4644 
   4645     local ret
   4646     if ! ble/util/import/search "$arg"; then
   4647       ble/array#push not_found "$arg"
   4648       continue
   4649     fi; local file=$ret
   4650     ble/array#push files "$file"
   4651   done
   4652 
   4653   # 存在しないファイルがあった時
   4654   if [[ $flags != *[fq]* ]] && ((${#not_found[@]})); then
   4655     local file
   4656     for file in "${not_found[@]}"; do
   4657       ble/util/print "ble-import: file '$file' not found" >&2
   4658     done
   4659     flags=E$flags
   4660   fi
   4661 
   4662   return 0
   4663 }
   4664 function ble/util/import {
   4665   local files file ext=0 ret enc
   4666   files=("$@")
   4667   set -- # Note #D: source によって引数が継承されるのを防ぐ
   4668   for file in "${files[@]}"; do
   4669     ble/util/import/encode-filename "$file"; enc=$ret
   4670     local guard=ble/util/import/guard:$enc
   4671     ble/is-function "$guard" && return 0
   4672     [[ -e $file ]] || return 1
   4673     source "$file" || { ext=$?; continue; }
   4674     builtin eval "function $guard { :; }"
   4675     ble/array#push _ble_util_import_files "$file"
   4676 
   4677     local onload=ble/util/import/onload:$enc
   4678     ble/function#try "$onload" ble/util/invoke-hook
   4679   done
   4680   return "$ext"
   4681 }
   4682 ## @fn ble/util/import/option:query
   4683 ##   @var[in] files not_found
   4684 function ble/util/import/option:query {
   4685   if ((${#not_found[@]})); then
   4686     return 127
   4687   elif ((${#files[@]})); then
   4688     local file
   4689     for file in "${files[@]}"; do
   4690       ble/util/import/is-loaded "$file" || return 1
   4691     done
   4692     return 0
   4693   else
   4694     ble/util/print-lines "${_ble_util_import_files[@]}"
   4695     return "$?"
   4696   fi
   4697 }
   4698 
   4699 function ble-import {
   4700   local files flags not_found
   4701   ble/util/import/.read-arguments "$@"
   4702   if [[ $flags == *[Eh]* ]]; then
   4703     [[ $flags == *E* ]] && ble/util/print
   4704     ble/util/print-lines \
   4705       'usage: ble-import [-dfq|--delay|--force|--query] [--] [SCRIPTFILE...]' \
   4706       'usage: ble-import --help' \
   4707       '    Search and source script files that have not yet been loaded.' \
   4708       '' \
   4709       '  OPTIONS' \
   4710       '    --help        Show this help.' \
   4711       '    -d, --delay   Delay actual loading of the files if possible.' \
   4712       '    -f, --force   Ignore non-existent files without errors.' \
   4713       '    -q, --query   When SCRIPTFILEs are specified, test if all of these files' \
   4714       '                  are already loaded.  Without SCRIPTFILEs, print the list of' \
   4715       '                  already imported files.' \
   4716       '' \
   4717       >&2
   4718     [[ $flags == *E* ]] && return 2
   4719     return 0
   4720   fi
   4721 
   4722   if [[ $flags == *q* ]]; then
   4723     ble/util/import/option:query
   4724     return "$?"
   4725   fi
   4726 
   4727   if ((!${#files[@]})); then
   4728     [[ $flags == *f* ]] && return 0
   4729     ble/util/print 'ble-import: files are not specified.' >&2
   4730     return 2
   4731   fi
   4732 
   4733   if [[ $flags == *d* ]] && ble/is-function ble/util/idle.push; then
   4734     local ret
   4735     ble/string#quote-command ble/util/import "${files[@]}"
   4736     ble/util/idle.push "$ret"
   4737     return 0
   4738   fi
   4739 
   4740   ble/util/import "${files[@]}"
   4741 }
   4742 
   4743 _ble_util_import_onload_count=0
   4744 function ble/util/import/eval-after-load {
   4745   local ret file
   4746   if ! ble/util/import/search "$1"; then
   4747     ble/util/print "ble-import: file '$1' not found." >&2
   4748     return 2
   4749   fi; file=$ret
   4750 
   4751   ble/util/import/encode-filename "$file"; local enc=$ret
   4752   local guard=ble/util/import/guard:$enc
   4753   if ble/is-function "$guard"; then
   4754     builtin eval -- "$2"
   4755   else
   4756     local onload=ble/util/import/onload:$enc
   4757     if ! ble/is-function "$onload"; then
   4758       local q=\' Q="'\''" list=_ble_util_import_onload_$((_ble_util_import_onload_count++))
   4759       builtin eval -- "$list=(); function $onload { \"\$1\" $list \"\${@:2}\"; }"
   4760     fi
   4761     "$onload" ble/array#push "$2"
   4762   fi
   4763 }
   4764 
   4765 ## @fn ble-stackdump [message]
   4766 ##   現在のコールスタックの状態を出力します。
   4767 ##
   4768 ##   @param[in,opt] message
   4769 ##     スタック情報の前に表示するメッセージを指定します。
   4770 ##   @var[in] _ble_util_stackdump_title
   4771 ##     スタック情報の前に表示するタイトルを指定します。
   4772 ##
   4773 _ble_util_stackdump_title=stackdump
   4774 _ble_util_stackdump_start=
   4775 function ble/util/stackdump {
   4776   ((bleopt_internal_stackdump_enabled)) || return 1
   4777   local message=$1 nl=$'\n' IFS=$_ble_term_IFS
   4778   message="$_ble_term_sgr0$_ble_util_stackdump_title: $message$nl"
   4779   local extdebug= iarg=$BASH_ARGC args=
   4780   shopt -q extdebug 2>/dev/null && extdebug=1
   4781   local i i0=${_ble_util_stackdump_start:-1} iN=${#FUNCNAME[*]}
   4782   for ((i=i0;i<iN;i++)); do
   4783     if [[ $extdebug ]] && ((BASH_ARGC[i])); then
   4784       args=("${BASH_ARGV[@]:iarg:BASH_ARGC[i]}")
   4785       ble/array#reverse args
   4786       args=" ${args[*]}"
   4787       ((iarg+=BASH_ARGC[i]))
   4788     else
   4789       args=
   4790     fi
   4791     message="$message  @ ${BASH_SOURCE[i]}:${BASH_LINENO[i-1]} (${FUNCNAME[i]}$args)$nl"
   4792   done
   4793   ble/util/put "$message"
   4794 }
   4795 function ble-stackdump {
   4796   local flags args
   4797   ble/util/.read-arguments-for-no-option-command ble-stackdump "$@"
   4798   if [[ $flags == *[eh]* ]]; then
   4799     [[ $flags == *e* ]] && ble/util/print
   4800     {
   4801       ble/util/print 'usage: ble-stackdump command [message]'
   4802       ble/util/print '  Print stackdump.'
   4803     } >&2
   4804     [[ $flags == *e* ]] && return 2
   4805     return 0
   4806   fi
   4807 
   4808   local _ble_util_stackdump_start=2
   4809   local IFS=$_ble_term_IFS
   4810   ble/util/stackdump "${args[*]}"
   4811 }
   4812 
   4813 ## @fn ble-assert command [message]
   4814 ##   コマンドを評価し失敗した時にメッセージを表示します。
   4815 ##
   4816 ##   @param[in] command
   4817 ##     評価するコマンドを指定します。eval で評価されます。
   4818 ##   @param[in,opt] message
   4819 ##     失敗した時に表示するメッセージを指定します。
   4820 ##
   4821 function ble/util/assert {
   4822   local expr=$1 message=$2
   4823   if ! builtin eval -- "$expr"; then
   4824     shift
   4825     local _ble_util_stackdump_title='assertion failure'
   4826     local _ble_util_stackdump_start=3
   4827     ble/util/stackdump "$expr$_ble_term_nl$message" >&2
   4828     return 1
   4829   else
   4830     return 0
   4831   fi
   4832 }
   4833 function ble-assert {
   4834   local flags args
   4835   ble/util/.read-arguments-for-no-option-command ble-assert "$@"
   4836   if [[ $flags != *h* ]]; then
   4837     if ((${#args[@]}==0)); then
   4838       ble/util/print 'ble-assert: command is not specified.' >&2
   4839       flags=e$flags
   4840     fi
   4841   fi
   4842   if [[ $flags == *[eh]* ]]; then
   4843     [[ $flags == *e* ]] && ble/util/print
   4844     {
   4845       ble/util/print 'usage: ble-assert command [message]'
   4846       ble/util/print '  Evaluate command and print stackdump on fail.'
   4847     } >&2
   4848     [[ $flags == *e* ]] && return 2
   4849     return 0
   4850   fi
   4851 
   4852   local IFS=$_ble_term_IFS
   4853   ble/util/assert "${args[0]}" "${args[*]:1}"
   4854 }
   4855 
   4856 #------------------------------------------------------------------------------
   4857 # Event loop
   4858 
   4859 bleopt/declare -v debug_idle ''
   4860 
   4861 ## @fn ble/util/clock
   4862 ##   時間を計測するのに使うことができるミリ秒単位の軽量な時計です。
   4863 ##   計測の起点は ble.sh のロード時です。
   4864 ##   @var[out] ret
   4865 _ble_util_clock_base=
   4866 _ble_util_clock_reso=
   4867 _ble_util_clock_type=
   4868 function ble/util/clock/.initialize {
   4869   local LC_ALL= LC_NUMERIC=C
   4870   if ((_ble_bash>=50000)) && {
   4871        local now=$EPOCHREALTIME
   4872        [[ $now == *.???* && $now != $EPOCHREALTIME ]]; }; then
   4873     # implementation with EPOCHREALTIME
   4874     builtin readonly EPOCHREALTIME
   4875     _ble_util_clock_base=$((10#0${now%.*}))
   4876     _ble_util_clock_reso=1
   4877     _ble_util_clock_type=EPOCHREALTIME
   4878     function ble/util/clock {
   4879       local LC_ALL= LC_NUMERIC=C
   4880       local now=$EPOCHREALTIME
   4881       local integral=$((10#0${now%%.*}-_ble_util_clock_base))
   4882       local mantissa=${now#*.}000; mantissa=${mantissa::3}
   4883       ((ret=integral*1000+10#0$mantissa))
   4884     }
   4885     ble/function#suppress-stderr ble/util/clock # locale
   4886   elif [[ -r /proc/uptime ]] && {
   4887          local uptime
   4888          ble/util/readfile uptime /proc/uptime
   4889          ble/string#split-words uptime "$uptime"
   4890          [[ $uptime == *.* ]]; }; then
   4891     # implementation with /proc/uptime
   4892     _ble_util_clock_base=$((10#0${uptime%.*}))
   4893     _ble_util_clock_reso=10
   4894     _ble_util_clock_type=uptime
   4895     function ble/util/clock {
   4896       local now
   4897       ble/util/readfile now /proc/uptime
   4898       ble/string#split-words now "$now"
   4899       local integral=$((10#0${now%%.*}-_ble_util_clock_base))
   4900       local fraction=${now#*.}000; fraction=${fraction::3}
   4901       ((ret=integral*1000+10#0$fraction))
   4902     }
   4903   elif ((_ble_bash>=40200)); then
   4904     printf -v _ble_util_clock_base '%(%s)T' -1
   4905     _ble_util_clock_reso=1000
   4906     _ble_util_clock_type=printf
   4907     function ble/util/clock {
   4908       local now; printf -v now '%(%s)T' -1
   4909       ((ret=(now-_ble_util_clock_base)*1000))
   4910     }
   4911   elif [[ $SECONDS && ! ${SECONDS//[0-9]} ]]; then
   4912     builtin readonly SECONDS
   4913     _ble_util_clock_base=$SECONDS
   4914     _ble_util_clock_reso=1000
   4915     _ble_util_clock_type=SECONDS
   4916     function ble/util/clock {
   4917       local now=$SECONDS
   4918       ((ret=(now-_ble_util_clock_base)*1000))
   4919     }
   4920   else
   4921     ble/util/strftime -v _ble_util_clock_base '%s'
   4922     _ble_util_clock_reso=1000
   4923     _ble_util_clock_type=date
   4924     function ble/util/clock {
   4925       ble/util/strftime -v ret '%s'
   4926       ((ret=(ret-_ble_util_clock_base)*1000))
   4927     }
   4928   fi
   4929 }
   4930 ble/util/clock/.initialize 2>/dev/null
   4931 
   4932 if ((_ble_bash>=40000)); then
   4933   ## @fn[custom] ble/util/idle/IS_IDLE
   4934   ##   他にするべき処理がない時 (アイドル時) に終了ステータス 0 を返します。
   4935   ##   Note: この設定関数は ble-decode.sh で上書きされます。
   4936   function ble/util/idle/IS_IDLE { ! ble/util/is-stdin-ready; }
   4937 
   4938   _ble_util_idle_sclock=0
   4939   function ble/util/idle/.sleep {
   4940     local msec=$1
   4941     ((msec<=0)) && return 0
   4942     ble/util/msleep "$msec"
   4943     ((_ble_util_idle_sclock+=msec))
   4944   }
   4945 
   4946   function ble/util/idle.clock/.initialize {
   4947     function ble/util/idle.clock/.initialize { :; }
   4948 
   4949     ## @fn ble/util/idle.clock
   4950     ##   タスクスケジューリングに使用する時計
   4951     ##   @var[out] ret
   4952     function ble/util/idle.clock/.restart { :; }
   4953     if [[ ! $_ble_util_clock_type || $_ble_util_clock_type == date ]]; then
   4954       function ble/util/idle.clock {
   4955         ret=$_ble_util_idle_sclock
   4956       }
   4957     elif ((_ble_util_clock_reso<=100)); then
   4958       function ble/util/idle.clock {
   4959         ble/util/clock
   4960       }
   4961     else
   4962       ## @fn ble/util/idle/.adjusted-clock
   4963       ##   参照時計 (rclock) と sleep 累積時間 (sclock) を元にして、
   4964       ##   参照時計を秒以下に解像度を上げた時計 (aclock) を提供します。
   4965       ##
   4966       ## @var[in,out] _ble_util_idle_aclock_tick_rclock
   4967       ## @var[in,out] _ble_util_idle_aclock_tick_sclock
   4968       ##   最後に参照時計が切り替わった時の rclock と sclock の値を保持します。
   4969       ##
   4970       ## @var[in,out] _ble_util_idle_aclock_shift
   4971       ##   時刻のシフト量を表します。
   4972       ##
   4973       ##   初期化時の秒以下の時刻が分からないため、
   4974       ##   取り敢えず 0.000 になっていると想定して時刻を測り始めます。
   4975       ##   最初の秒の切り替わりの時点でずれの量が判明するので、それを記録します。
   4976       ##   一様時計を提供する為に、以降もこのずれを適用する為に使用します。
   4977       ##
   4978       _ble_util_idle_aclock_shift=
   4979       _ble_util_idle_aclock_tick_rclock=
   4980       _ble_util_idle_aclock_tick_sclock=
   4981       function ble/util/idle.clock/.restart {
   4982         _ble_util_idle_aclock_shift=
   4983         _ble_util_idle_aclock_tick_rclock=
   4984         _ble_util_idle_aclock_tick_sclock=
   4985       }
   4986       function ble/util/idle/.adjusted-clock {
   4987         local resolution=$_ble_util_clock_reso
   4988         local sclock=$_ble_util_idle_sclock
   4989         local ret; ble/util/clock; local rclock=$((ret/resolution*resolution))
   4990 
   4991         if [[ $_ble_util_idle_aclock_tick_rclock != "$rclock" ]]; then
   4992           if [[ $_ble_util_idle_aclock_tick_rclock && ! $_ble_util_idle_aclock_shift ]]; then
   4993             local delta=$((sclock-_ble_util_idle_aclock_tick_sclock))
   4994             ((_ble_util_idle_aclock_shift=delta<resolution?resolution-delta:0))
   4995           fi
   4996           _ble_util_idle_aclock_tick_rclock=$rclock
   4997           _ble_util_idle_aclock_tick_sclock=$sclock
   4998         fi
   4999 
   5000         ((ret=rclock+(sclock-_ble_util_idle_aclock_tick_sclock)-_ble_util_idle_aclock_shift))
   5001       }
   5002       function ble/util/idle.clock {
   5003         ble/util/idle/.adjusted-clock
   5004       }
   5005     fi
   5006   }
   5007 
   5008   function ble/util/idle/.initialize-options {
   5009     local interval='ble_util_idle_elapsed>600000?500:(ble_util_idle_elapsed>60000?200:(ble_util_idle_elapsed>5000?100:20))'
   5010     ((_ble_bash>50000)) && [[ $_ble_util_msleep_builtin_available ]] && interval=20
   5011     bleopt/declare -v idle_interval "$interval"
   5012   }
   5013   ble/util/idle/.initialize-options
   5014 
   5015   ## @arr _ble_util_idle_task
   5016   ##   タスク一覧を保持します。各要素は一つのタスクを表し、
   5017   ##   status|command の形式の文字列です。
   5018   ##   command にはタスクを実行する coroutine を指定します。
   5019   ##   status は以下の何れかの値を持ちます。
   5020   ##
   5021   ##     R
   5022   ##       現在実行中のタスクである事を表します。
   5023   ##       ble/util/idle.push で設定されます。
   5024   ##     I
   5025   ##       次のユーザの入力を待っているタスクです。
   5026   ##       タスク内から ble/util/idle.wait-user-input で設定します。
   5027   ##     S<rtime>
   5028   ##       時刻 <rtime> になるのを待っているタスクです。
   5029   ##       タスク内から ble/util/idle.sleep で設定します。
   5030   ##     W<stime>
   5031   ##       sleep 累積時間 <stime> になるのを待っているタスクです。
   5032   ##       タスク内から ble/util/idle.isleep で設定します。
   5033   ##     E<filename>
   5034   ##       ファイルまたはディレクトリ <filename> が現れるのを待っているタスクです。
   5035   ##       タスク内から ble/util/idle.wait-filename で設定します。
   5036   ##     F<filename>
   5037   ##       ファイル <filename> が有限のサイズになるのを待っているタスクです。
   5038   ##       タスク内から ble/util/idle.wait-file-content で設定します。
   5039   ##     P<pid>
   5040   ##       プロセス <pid> (ユーザからアクセス可能) が終了するのを待っているタスクです。
   5041   ##       タスク内から ble/util/idle.wait-process で設定します。
   5042   ##     C<command>
   5043   ##       コマンド <command> の実行結果が真になるのを待っているタスクです。
   5044   ##       タスク内から ble/util/idle.wait-condition で設定します。
   5045   ##     Z
   5046   ##       停止中のタスクです。外部から状態を設定する事によって再開します。
   5047   ##
   5048   _ble_util_idle_task=()
   5049   _ble_util_idle_lasttask=
   5050   _ble_util_idle_SEP=$_ble_term_FS
   5051 
   5052   ## @fn ble/util/idle.do
   5053   ##   待機状態の処理を開始します。
   5054   ##
   5055   ##   @exit
   5056   ##     待機処理を何かしら実行した時に成功 (0) を返します。
   5057   ##     何も実行しなかった時に失敗 (1) を返します。
   5058   ##
   5059   function ble/util/idle.do {
   5060     local IFS=$_ble_term_IFS
   5061     ble/util/idle/IS_IDLE || return 1
   5062     ((${#_ble_util_idle_task[@]}==0)) && return 1
   5063     ble/util/buffer.flush >&2
   5064 
   5065     local ret
   5066     ble/util/idle.clock/.initialize
   5067     ble/util/idle.clock/.restart
   5068     ble/util/idle.clock
   5069     local _ble_idle_clock_start=$ret
   5070     local _ble_idle_sclock_start=$_ble_util_idle_sclock
   5071     local _ble_idle_is_first=1
   5072     local _ble_idle_processed=
   5073     local _ble_idle_info_shown=
   5074     local _ble_idle_after_task=0
   5075     while :; do
   5076       local _ble_idle_key
   5077       local _ble_idle_next_time= _ble_idle_next_itime= _ble_idle_running= _ble_idle_waiting=
   5078       for _ble_idle_key in "${!_ble_util_idle_task[@]}"; do
   5079         ble/util/idle/IS_IDLE || break 2
   5080         local _ble_idle_to_process=
   5081         local _ble_idle_status=${_ble_util_idle_task[_ble_idle_key]%%"$_ble_util_idle_SEP"*}
   5082         case ${_ble_idle_status::1} in
   5083         (R) _ble_idle_to_process=1 ;;
   5084         (I) [[ $_ble_idle_is_first ]] && _ble_idle_to_process=1 ;;
   5085         (S) ble/util/idle/.check-clock "$_ble_idle_status" && _ble_idle_to_process=1 ;;
   5086         (W) ble/util/idle/.check-clock "$_ble_idle_status" && _ble_idle_to_process=1 ;;
   5087         (F) [[ -s ${_ble_idle_status:1} ]] && _ble_idle_to_process=1 ;;
   5088         (E) [[ -e ${_ble_idle_status:1} ]] && _ble_idle_to_process=1 ;;
   5089         (P) ! builtin kill -0 ${_ble_idle_status:1} &>/dev/null && _ble_idle_to_process=1 ;;
   5090         (C) builtin eval -- "${_ble_idle_status:1}" && _ble_idle_to_process=1 ;;
   5091         (Z) ;;
   5092         (*) builtin unset -v '_ble_util_idle_task[_ble_idle_key]'
   5093         esac
   5094 
   5095         if [[ $_ble_idle_to_process ]]; then
   5096           local _ble_idle_command=${_ble_util_idle_task[_ble_idle_key]#*"$_ble_util_idle_SEP"}
   5097           _ble_idle_processed=1
   5098           ble/util/idle.do/.call-task "$_ble_idle_command"
   5099 
   5100           # Note: #D1450 _ble_idle_command が 148 を返したとしても idle.do は中
   5101           # 断しない事にした。IS_IDLE と条件が同じとは限らないので。
   5102           # ((ext==148)) && return 0
   5103 
   5104           ((_ble_idle_after_task++))
   5105         elif [[ $_ble_idle_status == [FEPC]* ]]; then
   5106           _ble_idle_waiting=1
   5107         fi
   5108       done
   5109 
   5110       _ble_idle_is_first=
   5111       ble/util/idle.do/.sleep-until-next; local ext=$?
   5112       ((ext==148)) && break
   5113 
   5114       [[ $_ble_idle_next_itime$_ble_idle_next_time$_ble_idle_running$_ble_idle_waiting ]] || break
   5115     done
   5116 
   5117     [[ $_ble_idle_info_shown ]] &&
   5118       ble/edit/info/immediate-default
   5119     ble/util/idle.do/.do-after-task
   5120     [[ $_ble_idle_processed ]]
   5121   }
   5122   ## @fn ble/util/idle.do/.do-after-task
   5123   ##   @var[ref] _ble_idle_after_task
   5124   function ble/util/idle.do/.do-after-task {
   5125     if ((_ble_idle_after_task)); then
   5126       # 50ms 以上の待機時間があれば再描画などの処理を試行する。
   5127       blehook/invoke idle_after_task
   5128       _ble_idle_after_task=0
   5129     fi
   5130   }
   5131   ## @fn ble/util/idle.do/.call-task command
   5132   ##   @var[in,out] _ble_idle_next_time
   5133   ##   @var[in,out] _ble_idle_next_itime
   5134   ##   @var[in,out] _ble_idle_running
   5135   ##   @var[in,out] _ble_idle_waiting
   5136   function ble/util/idle.do/.call-task {
   5137     local _ble_local_command=$1
   5138     local ble_util_idle_status=
   5139     local ble_util_idle_elapsed=$((_ble_util_idle_sclock-_ble_idle_sclock_start))
   5140     if [[ $bleopt_debug_idle && ( $_ble_edit_info_scene == default || $_ble_idle_info_shown ) ]]; then
   5141       _ble_idle_info_shown=1
   5142       ble/edit/info/immediate-show text "${EPOCHREALTIME:+[$EPOCHREALTIME] }idle: $_ble_local_command"
   5143     fi
   5144     builtin eval -- "$_ble_local_command"; local ext=$?
   5145     if ((ext==148)); then
   5146       _ble_util_idle_task[_ble_idle_key]=R$_ble_util_idle_SEP$_ble_local_command
   5147     elif [[ $ble_util_idle_status ]]; then
   5148       _ble_util_idle_task[_ble_idle_key]=$ble_util_idle_status$_ble_util_idle_SEP$_ble_local_command
   5149       if [[ $ble_util_idle_status == [WS]* ]]; then
   5150         local scheduled_time=${ble_util_idle_status:1}
   5151         if [[ $ble_util_idle_status == W* ]]; then
   5152           local next=_ble_idle_next_itime
   5153         else
   5154           local next=_ble_idle_next_time
   5155         fi
   5156         if [[ ! ${!next} ]] || ((scheduled_time<next)); then
   5157           builtin eval "$next=\$scheduled_time"
   5158         fi
   5159       elif [[ $ble_util_idle_status == R ]]; then
   5160         _ble_idle_running=1
   5161       elif [[ $ble_util_idle_status == [FEPC]* ]]; then
   5162         _ble_idle_waiting=1
   5163       fi
   5164     else
   5165       builtin unset -v '_ble_util_idle_task[_ble_idle_key]'
   5166     fi
   5167     return "$ext"
   5168   }
   5169   ## @fn ble/util/idle/.check-clock status
   5170   ##   @var[in,out] _ble_idle_next_itime
   5171   ##   @var[in,out] _ble_idle_next_time
   5172   function ble/util/idle/.check-clock {
   5173     local status=$1
   5174     if [[ $status == W* ]]; then
   5175       local next=_ble_idle_next_itime
   5176       local current_time=$_ble_util_idle_sclock
   5177     elif [[ $status == S* ]]; then
   5178       local ret
   5179       local next=_ble_idle_next_time
   5180       ble/util/idle.clock; local current_time=$ret
   5181     else
   5182       return 1
   5183     fi
   5184 
   5185     local scheduled_time=${status:1}
   5186     if ((scheduled_time<=current_time)); then
   5187       return 0
   5188     elif [[ ! ${!next} ]] || ((scheduled_time<next)); then
   5189       builtin eval "$next=\$scheduled_time"
   5190     fi
   5191     return 1
   5192   }
   5193   ## @fn ble/util/idle.do/.sleep-until-next
   5194   ##   @var[in] _ble_idle_next_time
   5195   ##   @var[in] _ble_idle_next_itime
   5196   ##   @var[in] _ble_idle_running
   5197   ##   @var[in] _ble_idle_waiting
   5198   function ble/util/idle.do/.sleep-until-next {
   5199     ble/util/idle/IS_IDLE || return 148
   5200     [[ $_ble_idle_running ]] && return 0
   5201     local isfirst=1
   5202     while
   5203       # ファイル等他の条件を待っている時は一回だけで外に戻り状態確認する
   5204       [[ $_ble_idle_waiting && ! $isfirst ]] && break
   5205 
   5206       local sleep_amount=
   5207       if [[ $_ble_idle_next_itime ]]; then
   5208         local clock=$_ble_util_idle_sclock
   5209         local sleep1=$((_ble_idle_next_itime-clock))
   5210         if [[ ! $sleep_amount ]] || ((sleep1<sleep_amount)); then
   5211           sleep_amount=$sleep1
   5212         fi
   5213       fi
   5214       if [[ $_ble_idle_next_time ]]; then
   5215         local ret; ble/util/idle.clock; local clock=$ret
   5216         local sleep1=$((_ble_idle_next_time-clock))
   5217         if [[ ! $sleep_amount ]] || ((sleep1<sleep_amount)); then
   5218           sleep_amount=$sleep1
   5219         fi
   5220       fi
   5221       [[ $_ble_idle_waiting ]] || ((sleep_amount>0))
   5222     do
   5223       # Note: 変数 ble_util_idle_elapsed は
   5224       #   $((bleopt_idle_interval)) の評価時に参照される。
   5225       local ble_util_idle_elapsed=$((_ble_util_idle_sclock-_ble_idle_sclock_start))
   5226 
   5227       # sleep_amount が十分に長い場合に idle_after_task が必要あれば実行する
   5228       ((sleep_amount>50)) && ble/util/idle.do/.do-after-task
   5229 
   5230       local interval=$((bleopt_idle_interval))
   5231 
   5232       if [[ ! $sleep_amount ]] || ((interval<sleep_amount)); then
   5233         sleep_amount=$interval
   5234       fi
   5235       ble/util/idle/.sleep "$sleep_amount"
   5236       ble/util/idle/IS_IDLE || return 148
   5237       isfirst=
   5238     done
   5239   }
   5240 
   5241   function ble/util/idle.push/.impl {
   5242     local base=$1 entry=$2
   5243     local i=$base
   5244     while [[ ${_ble_util_idle_task[i]-} ]]; do ((i++)); done
   5245     _ble_util_idle_task[i]=$entry
   5246     _ble_util_idle_lasttask=$i
   5247   }
   5248   function ble/util/idle.push {
   5249     local status=R nice=0
   5250     while [[ $1 == -* ]]; do
   5251       local sleep= isleep=
   5252       case $1 in
   5253       (-[SWPFEC]) status=${1:1}$2; shift 2 ;;
   5254       (-[SWPFECIRZ]*) status=${1:1}; shift ;;
   5255       (-n) nice=$2; shift 2 ;;
   5256       (-n*) nice=${1#-n}; shift ;;
   5257       (--sleep)    sleep=$2; shift 2 ;;
   5258       (--sleep=*)  sleep=${1#*=}; shift ;;
   5259       (--isleep)   isleep=$2; shift 2 ;;
   5260       (--isleep=*) isleep=${1#*=}; shift ;;
   5261       (*) break ;;
   5262       esac
   5263 
   5264       if [[ $sleep ]]; then
   5265         local ret; ble/util/idle.clock
   5266         status=S$((ret+sleep))
   5267       elif [[ $isleep ]]; then
   5268         status=W$((_ble_util_idle_sclock+isleep))
   5269       fi
   5270     done
   5271     ble/util/idle.push/.impl "$nice" "$status$_ble_util_idle_SEP$1"
   5272   }
   5273   function ble/util/idle.push-background {
   5274     ble/util/idle.push -n 10000 "$@"
   5275   }
   5276   function ble/util/idle.cancel {
   5277     local command=$1 i removed=
   5278     for i in "${!_ble_util_idle_task[@]}"; do
   5279       [[ ${_ble_util_idle_task[i]} == *"$_ble_util_idle_SEP$command" ]] &&
   5280         builtin unset -v '_ble_util_idle_task[i]' &&
   5281         removed=1
   5282     done
   5283     [[ $removed ]]
   5284   }
   5285 
   5286   function ble/util/is-running-in-idle {
   5287     [[ ${ble_util_idle_status+set} ]]
   5288   }
   5289   function ble/util/idle.suspend {
   5290     [[ ${ble_util_idle_status+set} ]] || return 2
   5291     ble_util_idle_status=Z
   5292   }
   5293   function ble/util/idle.sleep {
   5294     [[ ${ble_util_idle_status+set} ]] || return 2
   5295     local ret; ble/util/idle.clock
   5296     ble_util_idle_status=S$((ret+$1))
   5297   }
   5298   function ble/util/idle.isleep {
   5299     [[ ${ble_util_idle_status+set} ]] || return 2
   5300     ble_util_idle_status=W$((_ble_util_idle_sclock+$1))
   5301   }
   5302   ## @fn ble/util/idle.sleep-until clock opts
   5303   function ble/util/idle.sleep-until {
   5304     [[ ${ble_util_idle_status+set} ]] || return 2
   5305     if [[ :$2: == *:checked:* ]]; then
   5306       local ret; ble/util/idle.clock
   5307       (($1>ret)) || return 1
   5308     fi
   5309     ble_util_idle_status=S$1
   5310   }
   5311   ## @fn ble/util/idle.isleep-until sclock opts
   5312   function ble/util/idle.isleep-until {
   5313     [[ ${ble_util_idle_status+set} ]] || return 2
   5314     if [[ :$2: == *:checked:* ]]; then
   5315       (($1>_ble_util_idle_sclock)) || return 1
   5316     fi
   5317     ble_util_idle_status=W$1
   5318   }
   5319   function ble/util/idle.wait-user-input {
   5320     [[ ${ble_util_idle_status+set} ]] || return 2
   5321     ble_util_idle_status=I
   5322   }
   5323   function ble/util/idle.wait-process {
   5324     [[ ${ble_util_idle_status+set} ]] || return 2
   5325     ble_util_idle_status=P$1
   5326   }
   5327   function ble/util/idle.wait-file-content {
   5328     [[ ${ble_util_idle_status+set} ]] || return 2
   5329     ble_util_idle_status=F$1
   5330   }
   5331   function ble/util/idle.wait-filename {
   5332     [[ ${ble_util_idle_status+set} ]] || return 2
   5333     ble_util_idle_status=E$1
   5334   }
   5335   function ble/util/idle.wait-condition {
   5336     [[ ${ble_util_idle_status+set} ]] || return 2
   5337     ble_util_idle_status=C$1
   5338   }
   5339   function ble/util/idle.continue {
   5340     [[ ${ble_util_idle_status+set} ]] || return 2
   5341     ble_util_idle_status=R
   5342   }
   5343 
   5344   function ble/util/idle/.delare-external-modifier {
   5345     local name=$1
   5346     builtin eval -- 'function ble/util/idle#'$name' {
   5347       local index=$1
   5348       [[ ${_ble_util_idle_task[index]+set} ]] || return 2
   5349       local ble_util_idle_status=${_ble_util_idle_task[index]%%"$_ble_util_idle_SEP"*}
   5350       local ble_util_idle_command=${_ble_util_idle_task[index]#*"$_ble_util_idle_SEP"}
   5351       ble/util/idle.'$name' "${@:2}"
   5352       _ble_util_idle_task[index]=$ble_util_idle_status$_ble_util_idle_SEP$ble_util_idle_command
   5353     }'
   5354   }
   5355   # @fn ble/util/idle#suspend
   5356   # @fn ble/util/idle#sleep time
   5357   # @fn ble/util/idle#isleep time
   5358   ble/util/idle/.delare-external-modifier suspend
   5359   ble/util/idle/.delare-external-modifier sleep
   5360   ble/util/idle/.delare-external-modifier isleep
   5361 
   5362   ble/util/idle.push-background 'ble/util/msleep/calibrate'
   5363 else
   5364   function ble/util/idle.do { false; }
   5365 fi
   5366 
   5367 #------------------------------------------------------------------------------
   5368 # ble/util/fiberchain
   5369 
   5370 _ble_util_fiberchain=()
   5371 _ble_util_fiberchain_prefix=
   5372 function ble/util/fiberchain#initialize {
   5373   _ble_util_fiberchain=()
   5374   _ble_util_fiberchain_prefix=$1
   5375 }
   5376 function ble/util/fiberchain#resume/.core {
   5377   _ble_util_fiberchain=()
   5378   local fib_clock=0
   5379   local fib_ntask=$#
   5380   while (($#)); do
   5381     ((fib_ntask--))
   5382     local fiber=${1%%:*} fib_suspend= fib_kill=
   5383     local argv; ble/string#split-words argv "$fiber"
   5384     [[ $1 == *:* ]] && fib_suspend=${1#*:}
   5385     "$_ble_util_fiberchain_prefix/$argv.fib" "${argv[@]:1}"
   5386 
   5387     if [[ $fib_kill ]]; then
   5388       break
   5389     elif [[ $fib_suspend ]]; then
   5390       _ble_util_fiberchain=("$fiber:$fib_suspend" "${@:2}")
   5391       return 148
   5392     fi
   5393     shift
   5394   done
   5395 }
   5396 function ble/util/fiberchain#resume {
   5397   ble/util/fiberchain#resume/.core "${_ble_util_fiberchain[@]}"
   5398 }
   5399 ## @fn ble/util/fiberchain#push fiber...
   5400 ##   @param[in] fiber
   5401 ##     複数指定することができます。
   5402 ##     一つ一つは空白区切りの単語を並べた文字列です。
   5403 ##     コロン ":" を含むことはできません。
   5404 ##     一番最初の単語にファイバー名 name を指定します。
   5405 ##     引数 args... があれば二つ目以降の単語として指定します。
   5406 ##
   5407 ##   @remarks
   5408 ##     実際に実行されるファイバーは以下のコマンドになります。
   5409 ##     "$_ble_util_fiber_chain_prefix/$name.fib" "${args[@]}"
   5410 ##
   5411 function ble/util/fiberchain#push {
   5412   ble/array#push _ble_util_fiberchain "$@"
   5413 }
   5414 function ble/util/fiberchain#clear {
   5415   _ble_util_fiberchain=()
   5416 }
   5417 
   5418 #------------------------------------------------------------------------------
   5419 # **** terminal controls ****
   5420 
   5421 bleopt/declare -v vbell_default_message ' Wuff, -- Wuff!! '
   5422 bleopt/declare -v vbell_duration 2000
   5423 bleopt/declare -n vbell_align left
   5424 
   5425 function ble/term:cygwin/initialize.hook {
   5426   # RIの修正
   5427   # Note: Cygwin console では何故か RI (ESC M) が
   5428   #   1行スクロールアップとして実装されている。
   5429   #   一方で CUU (CSI A) で上にスクロールできる。
   5430   printf '\eM\e[B' >&"$_ble_util_fd_stderr"
   5431   _ble_term_ri=$'\e[A'
   5432 
   5433   # DLの修正
   5434   function ble/canvas/put-dl.draw {
   5435     local value=${1-1} i
   5436     ((value)) || return 1
   5437 
   5438     # Note: DL が最終行まで消去する時、何も消去されない…。
   5439     DRAW_BUFF[${#DRAW_BUFF[*]}]=$'\e[2K'
   5440     if ((value>1)); then
   5441       local ret
   5442       ble/string#repeat $'\e[B\e[2K' "$((value-1))"; local a=$ret
   5443       DRAW_BUFF[${#DRAW_BUFF[*]}]=$ret$'\e['$((value-1))'A'
   5444     fi
   5445 
   5446     DRAW_BUFF[${#DRAW_BUFF[*]}]=${_ble_term_dl//'%d'/$value}
   5447   }
   5448 }
   5449 
   5450 function ble/term/DA2R.hook {
   5451   blehook term_DA2R-=ble/term/DA2R.hook
   5452   case $_ble_term_TERM in
   5453   (contra:*)
   5454     _ble_term_cuu=$'\e[%dk'
   5455     _ble_term_cud=$'\e[%de'
   5456     _ble_term_cuf=$'\e[%da'
   5457     _ble_term_cub=$'\e[%dj'
   5458     _ble_term_cup=$'\e[%l;%cf' ;;
   5459   (cygwin:*)
   5460     ble/term:cygwin/initialize.hook ;;
   5461   esac
   5462 }
   5463 function ble/term/.initialize {
   5464   if [[ -s $_ble_base_cache/term.$TERM && $_ble_base_cache/term.$TERM -nt $_ble_base/lib/init-term.sh ]]; then
   5465     source "$_ble_base_cache/term.$TERM"
   5466   else
   5467     source "$_ble_base/lib/init-term.sh"
   5468   fi
   5469 
   5470   ble/string#reserve-prototype "$_ble_term_it"
   5471   blehook term_DA2R!=ble/term/DA2R.hook
   5472 }
   5473 ble/term/.initialize
   5474 
   5475 function ble/term/put {
   5476   BUFF[${#BUFF[@]}]=$1
   5477 }
   5478 function ble/term/cup {
   5479   local x=$1 y=$2 esc=$_ble_term_cup
   5480   esc=${esc//'%x'/$x}
   5481   esc=${esc//'%y'/$y}
   5482   esc=${esc//'%c'/$((x+1))}
   5483   esc=${esc//'%l'/$((y+1))}
   5484   BUFF[${#BUFF[@]}]=$esc
   5485 }
   5486 function ble/term/flush {
   5487   IFS= builtin eval 'ble/util/put "${BUFF[*]}"'
   5488   BUFF=()
   5489 }
   5490 
   5491 # **** vbell/abell ****
   5492 
   5493 function ble/term/audible-bell {
   5494   ble/util/put '' 1>&2
   5495 }
   5496 
   5497 # visible-bell の表示の管理について。
   5498 #
   5499 # vbell の表示の削除には worker サブシェルを使用する。
   5500 # 現在の表示内容及び消去に関しては二つのファイルを使う。
   5501 #
   5502 #   workerfile=$_ble_base_run/$$.visible-bell.$i
   5503 #     1つの worker に対して1つ割り当てられ、
   5504 #     その worker が生きている間は非空である。
   5505 #     またそのタイムスタンプは worker 起動時刻を表す。
   5506 #
   5507 #   _ble_term_visible_bell_ftime=$_ble_base_run/$$.visible-bell.time
   5508 #     最後に表示の更新を行った時刻を記録するのに使う。
   5509 #
   5510 # 前回の表示内容は以下の配列に格納する。
   5511 #
   5512 # @arr _ble_term_visible_bell_prev=(vbell_type message [x0 y0 x y])
   5513 
   5514 _ble_term_visible_bell_prev=()
   5515 _ble_term_visible_bell_ftime=$_ble_base_run/$$.visible-bell.time
   5516 
   5517 _ble_term_visible_bell_show='%message%'
   5518 _ble_term_visible_bell_clear=
   5519 function ble/term/visible-bell:term/init {
   5520   if [[ ! $_ble_term_visible_bell_clear ]]; then
   5521     local -a BUFF=()
   5522     ble/term/put "$_ble_term_ri_or_cuu1$_ble_term_sc$_ble_term_sgr0"
   5523     ble/term/cup 0 0
   5524     ble/term/put "$_ble_term_el%message%$_ble_term_sgr0$_ble_term_rc${_ble_term_cud//'%d'/1}"
   5525     IFS= builtin eval '_ble_term_visible_bell_show="${BUFF[*]}"'
   5526 
   5527     BUFF=()
   5528     ble/term/put "$_ble_term_sc$_ble_term_sgr0"
   5529     ble/term/cup 0 0
   5530     ble/term/put "$_ble_term_el2$_ble_term_rc"
   5531     IFS= builtin eval '_ble_term_visible_bell_clear="${BUFF[*]}"'
   5532   fi
   5533 
   5534   # 一行に収まる様に切り詰める
   5535   local cols=${COLUMNS:-80}
   5536   ((_ble_term_xenl||cols--))
   5537   local message=${1::cols}
   5538   _ble_term_visible_bell_prev=(term "$message")
   5539 }
   5540 function ble/term/visible-bell:term/show {
   5541   local sgr=$1 message=${_ble_term_visible_bell_prev[1]}
   5542   message=${_ble_term_visible_bell_show//'%message%'/"$sgr$message"}
   5543   ble/util/put "$message" >&2
   5544 }
   5545 function ble/term/visible-bell:term/update {
   5546   ble/term/visible-bell:term/show "$@"
   5547 }
   5548 function ble/term/visible-bell:term/clear {
   5549   local sgr=$1
   5550   ble/util/put "$_ble_term_visible_bell_clear" >&2
   5551 }
   5552 
   5553 function ble/term/visible-bell:canvas/init {
   5554   local message=$1
   5555 
   5556   local lines=1 cols=${COLUMNS:-80}
   5557   ((_ble_term_xenl||cols--))
   5558   local x= y=
   5559   local ret sgr0= sgr1=
   5560   ble/canvas/trace-text "$message" nonewline:external-sgr
   5561   message=$ret
   5562 
   5563   local x0=0 y0=0
   5564   if [[ $bleopt_vbell_align == right ]]; then
   5565     ((x0=COLUMNS-1-x,x0<0&&(x0=0)))
   5566   elif [[ $bleopt_vbell_align == center ]]; then
   5567     ((x0=(COLUMNS-1-x)/2,x0<0&&(x0=0)))
   5568   fi
   5569 
   5570   _ble_term_visible_bell_prev=(canvas "$message" "$x0" "$y0" "$x" "$y")
   5571 }
   5572 function ble/term/visible-bell:canvas/show {
   5573   local sgr=$1 opts=$2
   5574   local message=${_ble_term_visible_bell_prev[1]}
   5575   local x0=${_ble_term_visible_bell_prev[2]}
   5576   local y0=${_ble_term_visible_bell_prev[3]}
   5577   local x=${_ble_term_visible_bell_prev[4]}
   5578   local y=${_ble_term_visible_bell_prev[5]}
   5579 
   5580   local -a DRAW_BUFF=()
   5581   [[ :$opts: != *:update:* && $_ble_attached ]] && # WA #D1495
   5582     [[ $_ble_term_ri || :$opts: != *:erased:* && :$opts: != *:update:* ]] &&
   5583     ble/canvas/panel/ensure-tmargin.draw
   5584   if [[ $_ble_term_rc ]]; then
   5585     local ret=
   5586     [[ :$opts: != *:update:* && $_ble_attached ]] && ble/canvas/panel/save-position goto-top-dock # WA #D1495
   5587     ble/canvas/put.draw "$_ble_term_ri_or_cuu1$_ble_term_sc$_ble_term_sgr0"
   5588     ble/canvas/put-cup.draw "$((y0+1))" "$((x0+1))"
   5589     ble/canvas/put.draw "$sgr$message$_ble_term_sgr0"
   5590     ble/canvas/put.draw "$_ble_term_rc"
   5591     ble/canvas/put-cud.draw 1
   5592     [[ :$opts: != *:update:* && $_ble_attached ]] && ble/canvas/panel/load-position.draw "$ret" # WA #D1495
   5593   else
   5594     ble/canvas/put.draw "$_ble_term_ri_or_cuu1$_ble_term_sgr0"
   5595     ble/canvas/put-hpa.draw "$((1+x0))"
   5596     ble/canvas/put.draw "$sgr$message$_ble_term_sgr0"
   5597     ble/canvas/put-cud.draw 1
   5598     ble/canvas/put-hpa.draw "$((1+_ble_canvas_x))"
   5599   fi
   5600   ble/canvas/bflush.draw
   5601   ble/util/buffer.flush >&2
   5602 }
   5603 function ble/term/visible-bell:canvas/update {
   5604   ble/term/visible-bell:canvas/show "$@"
   5605 }
   5606 function ble/term/visible-bell:canvas/clear {
   5607   local sgr=$1
   5608   local x0=${_ble_term_visible_bell_prev[2]}
   5609   local y0=${_ble_term_visible_bell_prev[3]}
   5610   local x=${_ble_term_visible_bell_prev[4]}
   5611   local y=${_ble_term_visible_bell_prev[5]}
   5612 
   5613   local -a DRAW_BUFF=()
   5614   if [[ $_ble_term_rc ]]; then
   5615     local ret=
   5616     #[[ $_ble_attached ]] && ble/canvas/panel/save-position goto-top-dock # WA #D1495
   5617     ble/canvas/put.draw "$_ble_term_sc$_ble_term_sgr0"
   5618     ble/canvas/put-cup.draw "$((y0+1))" "$((x0+1))"
   5619     ble/canvas/put.draw "$sgr"
   5620     ble/canvas/put-spaces.draw "$x"
   5621     #ble/canvas/put-ech.draw "$x"
   5622     #ble/canvas/put.draw "$_ble_term_el"
   5623     ble/canvas/put.draw "$_ble_term_sgr0$_ble_term_rc"
   5624     #[[ $_ble_attached ]] && ble/canvas/panel/load-position.draw "$ret" # WA #D1495
   5625   else
   5626     : # 親プロセスの _ble_canvas_x が分からないので座標がずれる
   5627     # ble/util/buffer.flush >&2
   5628     # ble/canvas/put.draw "$_ble_term_ri_or_cuu1$_ble_term_sgr0"
   5629     # ble/canvas/put-hpa.draw "$((1+x0))"
   5630     # ble/canvas/put.draw "$sgr"
   5631     # ble/canvas/put-spaces.draw "$x"
   5632     # ble/canvas/put.draw "$_ble_term_sgr0"
   5633     # ble/canvas/put-cud.draw 1
   5634     # ble/canvas/put-hpa.draw "$((1+_ble_canvas_x))" # 親プロセスの _ble_canvas_x?
   5635   fi
   5636   ble/canvas/flush.draw >&2
   5637 }
   5638 
   5639 function ble/term/visible-bell/defface.hook {
   5640   ble/color/defface vbell       reverse
   5641   ble/color/defface vbell_flash reverse,fg=green
   5642   ble/color/defface vbell_erase bg=252
   5643 }
   5644 blehook color_defface_load+=ble/term/visible-bell/defface.hook
   5645 
   5646 function ble/term/visible-bell/.show {
   5647   local bell_type=${_ble_term_visible_bell_prev[0]}
   5648   ble/term/visible-bell:"$bell_type"/show "$@"
   5649 }
   5650 function ble/term/visible-bell/.update {
   5651   local bell_type=${_ble_term_visible_bell_prev[0]}
   5652   ble/term/visible-bell:"$bell_type"/update "$1" "$2:update"
   5653 }
   5654 function ble/term/visible-bell/.clear {
   5655   local bell_type=${_ble_term_visible_bell_prev[0]}
   5656   ble/term/visible-bell:"$bell_type"/clear "$@"
   5657   >| "$_ble_term_visible_bell_ftime"
   5658 }
   5659 
   5660 function ble/term/visible-bell/.erase-previous-visible-bell {
   5661   local ret workers
   5662   ble/util/eval-pathname-expansion '"$_ble_base_run/$$.visible-bell."*' canonical
   5663   workers=("${ret[@]}")
   5664 
   5665   local workerfile
   5666   for workerfile in "${workers[@]}"; do
   5667     if [[ -s $workerfile && ! ( $workerfile -ot $_ble_term_visible_bell_ftime ) ]]; then
   5668       ble/term/visible-bell/.clear "$sgr0"
   5669       return 0
   5670     fi
   5671   done
   5672   return 1
   5673 }
   5674 
   5675 function ble/term/visible-bell/.create-workerfile {
   5676   local i=0
   5677   while
   5678     workerfile=$_ble_base_run/$$.visible-bell.$i
   5679     [[ -s $workerfile ]]
   5680   do ((i++)); done
   5681   ble/util/print 1 >| "$workerfile"
   5682 }
   5683 ## @fn ble/term/visible-bell/.worker
   5684 ##   @var[in] workerfile
   5685 function ble/term/visible-bell/.worker {
   5686   # Note: ble/util/assign は使えない。本体の ble/util/assign と一時ファイルが衝突する可能性がある。
   5687   ble/util/msleep 50
   5688   [[ $workerfile -ot $_ble_term_visible_bell_ftime ]] && return 0 >| "$workerfile"
   5689   ble/term/visible-bell/.update "$sgr2"
   5690 
   5691   if [[ :$opts: == *:persistent:* ]]; then
   5692     local dead_workerfile=$_ble_base_run/$$.visible-bell.Z
   5693     ble/util/print 1 >| "$dead_workerfile"
   5694     return 0 >| "$workerfile"
   5695   fi
   5696 
   5697   # load time duration settings
   5698   local msec=$bleopt_vbell_duration
   5699 
   5700   # wait
   5701   ble/util/msleep "$msec"
   5702   [[ $workerfile -ot $_ble_term_visible_bell_ftime ]] && return 0 >| "$workerfile"
   5703 
   5704   # check and clear
   5705   ble/term/visible-bell/.clear "$sgr0"
   5706 
   5707   >| "$workerfile"
   5708 }
   5709 
   5710 ## @fn ble/term/visible-bell message [opts]
   5711 function ble/term/visible-bell {
   5712   local message=$1 opts=$2
   5713   message=${message:-$bleopt_vbell_default_message}
   5714 
   5715   # Note: 1行しかない時は表示しない。0行の時は全てログに行くので出力する。空文
   5716   # 字列の時は設定されていないだけなので表示する。
   5717   ((LINES==1)) && return 0
   5718 
   5719   if ble/is-function ble/canvas/trace-text; then
   5720     ble/term/visible-bell:canvas/init "$message"
   5721   else
   5722     ble/term/visible-bell:term/init "$message"
   5723   fi
   5724 
   5725   local sgr0=$_ble_term_sgr0
   5726   local sgr1=${_ble_term_setaf[2]}$_ble_term_rev
   5727   local sgr2=$_ble_term_rev
   5728   if ble/is-function ble/color/face2sgr; then
   5729     local ret
   5730     ble/color/face2sgr vbell_flash; sgr1=$ret
   5731     ble/color/face2sgr vbell; sgr2=$ret
   5732     ble/color/face2sgr vbell_erase; sgr0=$ret
   5733   fi
   5734 
   5735   local show_opts=
   5736   ble/term/visible-bell/.erase-previous-visible-bell && show_opts=erased
   5737   ble/term/visible-bell/.show "$sgr1" "$show_opts"
   5738 
   5739   local workerfile; ble/term/visible-bell/.create-workerfile
   5740   # Note: __ble_suppress_joblist__ を指定する事によって、
   5741   #   終了したジョブの一覧に現れない様にする。
   5742   #   対策しないと read の置き換え実装でジョブ一覧が表示されてしまう。
   5743   # Note: 標準出力を閉じて置かないと $() の中で
   5744   #   read を呼び出した時に visible-bell worker がブロックしてしまう。
   5745   # ref #D1000, #D1087
   5746   ( ble/term/visible-bell/.worker __ble_suppress_joblist__ 1>/dev/null & )
   5747 }
   5748 function ble/term/visible-bell/cancel-erasure {
   5749   >| "$_ble_term_visible_bell_ftime"
   5750 }
   5751 function ble/term/visible-bell/erase {
   5752   local sgr0=$_ble_term_sgr0
   5753   if ble/is-function ble/color/face2sgr; then
   5754     local ret
   5755     ble/color/face2sgr vbell_erase; sgr0=$ret
   5756   fi
   5757   ble/term/visible-bell/.erase-previous-visible-bell
   5758 }
   5759 
   5760 #---- stty --------------------------------------------------------------------
   5761 
   5762 # 改行 (C-m, C-j) の取り扱いについて
   5763 #   入力の C-m が C-j に勝手に変換されない様に -icrnl を指定する必要がある。
   5764 #   (-nl の設定の中に icrnl が含まれているので、これを取り消さなければならない)
   5765 #   一方で、出力の LF は CR LF に変換されて欲しいので onlcr は保持する。
   5766 #   (これは -nl の設定に含まれている)
   5767 #
   5768 # -icanon について
   5769 #   stty icanon を設定するプログラムがある。これを設定すると入力が buffering され
   5770 #   その場で入力を受信する事ができない。結果として hang した様に見える。
   5771 #   従って、enter で -icanon を設定する事にする。
   5772 
   5773 ## @var _ble_term_stty_state
   5774 ##   現在 stty で制御文字の効果が解除されているかどうかを保持します。
   5775 ##
   5776 ## Note #D1238: arr=(...) の形式を用いると Bash 3.2 では勝手に ^? が ^A^? に化けてしまう
   5777 ##   仕方がないので此処では ble/array#push を使って以下の配列を初期化する事にする。
   5778 _ble_term_stty_state=
   5779 _ble_term_stty_flags_enter=()
   5780 _ble_term_stty_flags_leave=()
   5781 ble/array#push _ble_term_stty_flags_enter intr undef quit undef susp undef
   5782 ble/array#push _ble_term_stty_flags_leave intr '' quit '' susp ''
   5783 function ble/term/stty/.initialize-flags {
   5784   # # ^U, ^V, ^W, ^?
   5785   # # Note: lnext, werase は POSIX にはないので stty の項目に存在する
   5786   # #   かチェックする。
   5787   # # Note (#D1683): ble/decode/bind/adjust-uvw が正しい対策。以下の対
   5788   # #   策の効果は不明。寧ろ vim :term 内部で ^? が効かなくなるなど問
   5789   # #   題を起こす様なので取り敢えず無効化する。
   5790   # ble/array#push _ble_term_stty_flags_enter kill undef erase undef
   5791   # ble/array#push _ble_term_stty_flags_leave kill '' erase ''
   5792   # local stty; ble/util/assign stty 'stty -a'
   5793   # if [[ $stty == *' lnext '* ]]; then
   5794   #   ble/array#push _ble_term_stty_flags_enter lnext undef
   5795   #   ble/array#push _ble_term_stty_flags_leave lnext ''
   5796   # fi
   5797   # if [[ $stty == *' werase '* ]]; then
   5798   #   ble/array#push _ble_term_stty_flags_enter werase undef
   5799   #   ble/array#push _ble_term_stty_flags_leave werase ''
   5800   # fi
   5801 
   5802   if [[ $TERM == minix ]]; then
   5803     local stty; ble/util/assign stty 'stty -a'
   5804     if [[ $stty == *' rprnt '* ]]; then
   5805       ble/array#push _ble_term_stty_flags_enter rprnt undef
   5806       ble/array#push _ble_term_stty_flags_leave rprnt ''
   5807     elif [[ $stty == *' reprint '* ]]; then
   5808       ble/array#push _ble_term_stty_flags_enter reprint undef
   5809       ble/array#push _ble_term_stty_flags_leave reprint ''
   5810     fi
   5811   fi
   5812 }
   5813 ble/term/stty/.initialize-flags
   5814 
   5815 function ble/term/stty/initialize {
   5816   ble/bin/stty -ixon -echo -nl -icrnl -icanon \
   5817                "${_ble_term_stty_flags_enter[@]}"
   5818   _ble_term_stty_state=1
   5819 }
   5820 function ble/term/stty/leave {
   5821   [[ ! $_ble_term_stty_state ]] && return 0
   5822   ble/bin/stty echo -nl icanon \
   5823                "${_ble_term_stty_flags_leave[@]}"
   5824   _ble_term_stty_state=
   5825 }
   5826 function ble/term/stty/enter {
   5827   [[ $_ble_term_stty_state ]] && return 0
   5828   ble/bin/stty -echo -nl -icrnl -icanon \
   5829                "${_ble_term_stty_flags_enter[@]}"
   5830   _ble_term_stty_state=1
   5831 }
   5832 function ble/term/stty/finalize {
   5833   ble/term/stty/leave
   5834 }
   5835 function ble/term/stty/TRAPEXIT {
   5836   # exit の場合は echo
   5837   ble/bin/stty echo -nl \
   5838                "${_ble_term_stty_flags_leave[@]}"
   5839 
   5840   # Note (#D): WA for bash-5.2 stty: bash-5.2 以降では EXIT trap よりも後に readline
   5841   # が stty を復元しようとするので、セッション終了後に制御端末が壊れた状態にな
   5842   # る。親プロセスが同じ端末に属していてかつ ble.sh セッションでない場合には、
   5843   # 入力に支障を来すので制御端末の状態を手動で復元する様に表示を行う。
   5844   if ((_ble_bash>=50200)) && [[ :$1: == *:EXIT:* && ! -e $_ble_base_run/$PPID.load ]]; then
   5845     local lines
   5846     ble/util/assign-array lines 'ble/bin/ps -o tty "$$" "$PPID"'
   5847     ((${#lines[@]}>=3)) && lines=("${lines[@]:${#lines[@]}-2}")
   5848     if [[ ${lines[0]} == ${lines[1]} ]]; then
   5849       local sgr=$_ble_term_bold${_ble_term_setaf[4]} sgr0=$_ble_term_sgr0
   5850       ble/util/print "ble: Please run \`${sgr}stty sane$sgr0' to recover the correct TTY state." >&"${_ble_util_fd_stderr:-2}"
   5851     fi
   5852   fi
   5853 }
   5854 
   5855 function ble/term/update-winsize {
   5856   # (0) checkwinsize による実装 (2167.054 usec/eval)
   5857   if ((_ble_bash<50200||50300<=_ble_bash)); then
   5858     function ble/term/update-winsize {
   5859       if shopt -q checkwinsize; then
   5860         (:)
   5861       else
   5862         shopt -s checkwinsize
   5863         (:)
   5864         shopt -u checkwinsize
   5865       fi 2>&"$_ble_util_fd_stderr"
   5866     }
   5867     ble/term/update-winsize
   5868     return 0
   5869   fi
   5870 
   5871   local ret
   5872 
   5873   # (a) "tput lines cols" または "tput li co" による実装 (2909.052 usec/eval)
   5874   if ble/bin#freeze-utility-path tput; then
   5875     if ble/util/assign-words ret 'ble/bin/tput lines cols' 2>/dev/null &&
   5876         [[ ${#ret[@]} -eq 2 && ${ret[0]} =~ ^[0-9]+$ && ${ret[1]} =~ ^[0-9]+$ ]]
   5877     then
   5878       LINES=${ret[0]} COLUMNS=${ret[1]}
   5879       function ble/term/update-winsize {
   5880         local -x ret LINES= COLUMNS=
   5881         ble/util/assign-words ret 'ble/bin/tput lines cols' 2>/dev/null
   5882         ble/util/unlocal LINES COLUMNS
   5883         [[ ${ret[0]} ]] && LINES=${ret[0]}
   5884         [[ ${ret[1]} ]] && COLUMNS=${ret[1]}
   5885       }
   5886       return 0
   5887     elif ble/util/assign-words ret 'ble/bin/tput li co' 2>/dev/null &&
   5888         [[ ${#ret[@]} -eq 2 && ${ret[0]} =~ ^[0-9]+$ && ${ret[1]} =~ ^[0-9]+$ ]]
   5889     then
   5890       LINES=${ret[0]} COLUMNS=${ret[1]}
   5891       function ble/term/update-winsize {
   5892         local -x ret LINES= COLUMNS=
   5893         ble/util/assign-words ret 'ble/bin/tput li co' 2>/dev/null
   5894         ble/util/unlocal LINES COLUMNS
   5895         [[ ${ret[0]} ]] && LINES=${ret[0]}
   5896         [[ ${ret[1]} ]] && COLUMNS=${ret[1]}
   5897       }
   5898       return 0
   5899     fi
   5900   fi
   5901 
   5902   # (b) "stty size" による実装 (2976.172 usec/eval)
   5903   if ble/util/assign-words ret 'ble/bin/stty size' 2>/dev/null &&
   5904       [[ ${#ret[@]} -eq 2 && ${ret[0]} =~ ^[0-9]+$ && ${ret[1]} =~ ^[0-9]+$ ]]
   5905   then
   5906     LINES=${ret[0]} COLUMNS=${ret[1]}
   5907     function ble/term/update-winsize {
   5908       local ret
   5909       ble/util/assign-words ret 'ble/bin/stty size' 2>/dev/null
   5910       [[ ${ret[0]} ]] && LINES=${ret[0]}
   5911       [[ ${ret[1]} ]] && COLUMNS=${ret[1]}
   5912     }
   5913     return 0
   5914   fi
   5915 
   5916   # (c) "resize" による実装 (3108.696 usec/eval)
   5917   if ble/bin#freeze-utility-path resize &&
   5918       ble/util/assign ret 'ble/bin/resize' &&
   5919       ble/string#match "$ret" 'COLUMNS=([0-9]+).*LINES=([0-9]+)'
   5920   then
   5921     LINES=${BASH_REMATCH[2]} COLUMNS=${BASH_REMATCH[1]}
   5922     function ble/term/update-winsize {
   5923       local ret
   5924       ble/util/assign ret 'ble/bin/resize' 2>/dev/null
   5925       ble/string#match ret 'COLUMNS=([0-9]+).*LINES=([0-9]+)'
   5926       [[ ${BASH_REMATCH[2]} ]] && LINES=${BASH_REMATCH[2]}
   5927       [[ ${BASH_REMATCH[1]} ]] && COLUMNS=${BASH_REMATCH[1]}
   5928     }
   5929     return 0
   5930   fi
   5931 
   5932   # (d) "bash -O checkwinsize -c ..." による実装 (bash-4.3 以上) (9094.595 usec/eval)
   5933   function ble/term/update-winsize {
   5934     local ret script='LINES= COLUMNS=; (:); [[ $COLUMNS && $LINES ]] && builtin echo "$LINES $COLUMNS"'
   5935     ble/util/assign-words ret '"$BASH" -O checkwinsize -c "$script"' 2>&"$_ble_util_fd_stderr"
   5936     [[ ${ret[0]} ]] && LINES=${ret[0]}
   5937     [[ ${ret[1]} ]] && COLUMNS=${ret[1]}
   5938   }
   5939   ble/term/update-winsize
   5940   return 0
   5941 }
   5942 
   5943 # bash-5.2 では "bind -x" 内部で checkwinsize が動作しないので
   5944 # ble/term/stty/enter に於いて自前で端末サイズを取得して LINES COLUMNS を更新す
   5945 # る。
   5946 if ((50200<=_ble_bash&&_ble_bash<50300)); then
   5947   ## @fn ble/term/update-winsize/.stty-enter.advice
   5948   ##   ble/term/stty/enter の実装を "stty size" を用いて調節します。
   5949   ##
   5950   ##   最初の ble/term/stty/enter の呼び出し時に'stty "${enter_options[@]}"
   5951   ##   size' が動くか検査し、使えそうならば今後はstty に size を追加して呼び出
   5952   ##   してそれを元にして LINES, COLUMNS を再設定する様にする。
   5953   ##
   5954   ##   テスト自体が stty の設定を変更するので、初回の ble/term/stty/enter の呼
   5955   ##   び出しの時にテストも含めて調整を実行することにしている。
   5956   function ble/term/update-winsize/.stty-enter.advice {
   5957     local ret stderr test_command='ble/bin/stty -echo -nl -icrnl -icanon "${_ble_term_stty_flags_enter[@]}" size'
   5958     if ble/util/assign stderr 'ble/util/assign-words ret "$test_command" 2>&1' &&
   5959         [[ ! $stderr ]] &&
   5960         ((${#ret[@]}==2)) &&
   5961         [[ ${ret[0]} =~ ^[0-9]+$ && ${ret[1]} =~ ^[0-9]+$ ]]
   5962     then
   5963       LINES=${ret[0]} COLUMNS=${ret[1]}
   5964       function ble/term/stty/enter {
   5965         [[ $_ble_term_stty_state ]] && return 0
   5966         local ret
   5967         ble/util/assign-words ret 'ble/bin/stty -echo -nl -icrnl -icanon "${_ble_term_stty_flags_enter[@]}" size'
   5968         [[ ${ret[0]} =~ ^[0-9]+$ ]] && LINES=${ret[0]}
   5969         [[ ${ret[1]} =~ ^[0-9]+$ ]] && COLUMNS=${ret[1]}
   5970         _ble_term_stty_state=1
   5971       }
   5972     else
   5973       ble/term/update-winsize
   5974       ble/function#advice before ble/term/stty/enter ble/term/update-winsize
   5975     fi
   5976     builtin unset -f "$FUNCNAME"
   5977   }
   5978   ble/function#advice before ble/term/stty/enter ble/term/update-winsize/.stty-enter.advice
   5979 fi
   5980 
   5981 
   5982 #---- cursor state ------------------------------------------------------------
   5983 
   5984 bleopt/declare -v term_cursor_external 0
   5985 
   5986 _ble_term_cursor_current=unknown
   5987 _ble_term_cursor_internal=0
   5988 _ble_term_cursor_hidden_current=unknown
   5989 _ble_term_cursor_hidden_internal=reveal
   5990 
   5991 # #D1516 今迄にカーソル変更がなく、且つ既定値に戻そうとしている時は何
   5992 #   もしない為、初めから 0 にしておく事にする。xterm.js で DECSCUSR(0)
   5993 #   がユーザー既定値でない事への対策。外部コマンドがカーソル形状を復元
   5994 #   するという事を前提にしている。
   5995 # #D1873 単に 0 を指定しているだけだと cursor をユーザー設定していなく
   5996 #   ても、コマンド実行後の term/enter の時に結局 unknown が設定されて、
   5997 #   DECSCUSR(0) が送信されて問題になる。未だ一度も ble.sh として変更し
   5998 #   ていない事を表す値として default という物を導入する事にした。
   5999 #   default の時には term/enter 時のクリアをしない。
   6000 _ble_term_cursor_current=default
   6001 
   6002 function ble/term/cursor-state/.update {
   6003   local state=$(($1))
   6004   [[ ${_ble_term_cursor_current/default/0} == "$state" ]] && return 0
   6005 
   6006   if [[ ! $_ble_term_Ss ]]; then
   6007     case $_ble_term_TERM in
   6008     (mintty:*|xterm:*|RLogin:*|kitty:*|screen:*|tmux:*|contra:*|cygwin:*|wezterm:*|wt:*)
   6009       local _ble_term_Ss=$'\e[@1 q' ;;
   6010     esac
   6011   fi
   6012   local ret=${_ble_term_Ss//@1/"$state"}
   6013 
   6014   # Note: 既に pass-through seq が含まれている時はスキップする。
   6015   [[ $ret && $ret != $'\eP'*$'\e\\' ]] &&
   6016     ble/term/quote-passthrough "$ret" '' all
   6017 
   6018   ble/util/buffer "$ret"
   6019 
   6020   _ble_term_cursor_current=$state
   6021 }
   6022 function ble/term/cursor-state/set-internal {
   6023   _ble_term_cursor_internal=$1
   6024   [[ $_ble_term_state == internal ]] &&
   6025     ble/term/cursor-state/.update "$1"
   6026 }
   6027 
   6028 function ble/term/cursor-state/.update-hidden {
   6029   local state=$1
   6030   [[ $state != hidden ]] && state=reveal
   6031   [[ $_ble_term_cursor_hidden_current == "$state" ]] && return 0
   6032 
   6033   if [[ $state == hidden ]]; then
   6034     ble/util/buffer "$_ble_term_civis"
   6035   else
   6036     ble/util/buffer "$_ble_term_cvvis"
   6037   fi
   6038 
   6039   _ble_term_cursor_hidden_current=$state
   6040 }
   6041 function ble/term/cursor-state/hide {
   6042   _ble_term_cursor_hidden_internal=hidden
   6043   [[ $_ble_term_state == internal ]] &&
   6044     ble/term/cursor-state/.update-hidden hidden
   6045 }
   6046 function ble/term/cursor-state/reveal {
   6047   _ble_term_cursor_hidden_internal=reveal
   6048   [[ $_ble_term_state == internal ]] &&
   6049     ble/term/cursor-state/.update-hidden reveal
   6050 }
   6051 
   6052 #---- DECSET(2004): bracketed paste mode --------------------------------------
   6053 
   6054 function ble/term/bracketed-paste-mode/.init {
   6055   local _ble_local_rlvars; ble/util/rlvar#load
   6056 
   6057   bleopt/declare -v term_bracketed_paste_mode on
   6058   if ((_ble_bash>=50100)) && ! ble/util/rlvar#test enable-bracketed-paste; then
   6059     # Bash 5.1 以降では既定で on なのでもし無効になっていたら意図的にユーザーが
   6060     # off にしたという事。
   6061     bleopt term_bracketed_paste_mode=
   6062   fi
   6063   function bleopt/check:term_bracketed_paste_mode {
   6064     if [[ $_ble_term_bracketedPasteMode_internal ]]; then
   6065       if [[ $value ]]; then
   6066         [[ $bleopt_term_bracketed_paste_mode ]] || ble/util/buffer $'\e[?2004h'
   6067       else
   6068         [[ ! $bleopt_term_bracketed_paste_mode ]] || ble/util/buffer $'\e[?2004l'
   6069       fi
   6070     fi
   6071   }
   6072   ble/util/rlvar#bind-bleopt enable-bracketed-paste term_bracketed_paste_mode bool
   6073 
   6074   builtin unset -f "$FUNCNAME"
   6075 }
   6076 ble/term/bracketed-paste-mode/.init
   6077 
   6078 _ble_term_bracketedPasteMode_internal=
   6079 function ble/term/bracketed-paste-mode/enter {
   6080   _ble_term_bracketedPasteMode_internal=1
   6081   [[ ${bleopt_term_bracketed_paste_mode-} ]] &&
   6082     ble/util/buffer $'\e[?2004h'
   6083 }
   6084 function ble/term/bracketed-paste-mode/leave {
   6085   _ble_term_bracketedPasteMode_internal=
   6086   [[ ${bleopt_term_bracketed_paste_mode-} ]] &&
   6087     ble/util/buffer $'\e[?2004l'
   6088 }
   6089 if [[ $TERM == minix ]]; then
   6090   # Minix console は DECSET も使えない
   6091   function ble/term/bracketed-paste-mode/enter { :; }
   6092   function ble/term/bracketed-paste-mode/leave { :; }
   6093 fi
   6094 
   6095 #---- DA2 ---------------------------------------------------------------------
   6096 
   6097 _ble_term_TERM=()
   6098 _ble_term_DA1R=()
   6099 _ble_term_DA2R=()
   6100 _ble_term_TERM_done=
   6101 
   6102 ## @fn ble/term/DA2/initialize-term [depth]
   6103 ##   @var[out] _ble_term_TERM
   6104 function ble/term/DA2/initialize-term {
   6105   local depth=$1
   6106   local da2r=${_ble_term_DA2R[depth]}
   6107   local rex='^[0-9]*(;[0-9]*)*$'; [[ $da2r =~ $rex ]] || return 1
   6108   local da2r_vec
   6109   ble/string#split da2r_vec ';' "$da2r"
   6110   da2r_vec=("${da2r_vec[@]/#/10#0}") # 0で始まっていても10進数で解釈; WA #D1570 checked (is-array)
   6111 
   6112   case $da2r in
   6113   # Note #D1946: Terminology は xterm と区別が付かないが決め打ちの様なので、丁
   6114   # 度 xterm の該当 version を使っている可能性は低いと見て、取り敢えず
   6115   # terminology と判断する事にする。
   6116   ('0;271;0')  _ble_term_TERM[depth]=terminology:200 ;;   # 2012-10-05 https://github.com/borisfaure/terminology/commit/500e7be8b2b876462ed567ef6c90527f37482adb
   6117   ('41;285;0') _ble_term_TERM[depth]=terminology:300 ;;   # 2013-01-22 https://github.com/borisfaure/terminology/commit/526cc2aeacc0ae54825cbc3a3e2ab64f612f83c9
   6118   ('61;337;0') _ble_term_TERM[depth]=terminology:10400 ;; # 2019-01-20 https://github.com/borisfaure/terminology/commit/96bbfd054b271f7ad7f31e699b13c12cb8fbb2e2
   6119 
   6120   # Note #D1909: wezterm が 2022-04-07 に DA2 を変更している。xterm-277 と区別
   6121   # が付かないが、ちょうど該当 xterm version (2012-01-08) を使っている可能性は
   6122   # 低いと見て取り敢えず wezterm とする。更に mlterm-3.4.2..3.7.1
   6123   # (201412..201608) も 1;277;0 を使っていた。
   6124   ('0;0;0') _ble_term_TERM[depth]=wezterm:0 ;;
   6125   ('1;277;0') _ble_term_TERM[depth]=wezterm:20220408 ;; # 2022-04-07 https://github.com/wez/wezterm/commit/ad91e3776808507cbef9e6d758b89d7ca92a4c7e
   6126 
   6127   # Konsole も大体決め打ちにしている。最近変更した様だ。
   6128   ('0;115;0') _ble_term_TERM[depth]=konsole:30000  ;; # 2001-09-16 https://github.com/KDE/konsole/commit/2d93fed82aa27e89c9d7301d09d2e24e4fa4416d
   6129   ('1;115;0') _ble_term_TERM[depth]=konsole:220380 ;; # 2022-02-24 https://github.com/KDE/konsole/commit/0cc64dcf7b90075bd17e46653df3069208d6a590
   6130 
   6131   # mlterm (#D1999)
   6132   # - 0;96;0   v3.1.0 (2012-03-24) https://github.com/arakiken/mlterm/commit/6ca37d7f99337194d8c893cc48285c0614762535
   6133   # * 1;96;0   v3.1.2 (2012-05-20) https://github.com/arakiken/mlterm/commit/6293d0af9cf1e78fd6c35620824b62ff6c87370b
   6134   # * 1;277;0  v3.4.2 (2014-12-27) https://github.com/arakiken/mlterm/commit/c4fb36291ec67daf73c48c5b60d1af88ad0487e6
   6135   # - 1;279;0  v3.7.2 (2016-08-06) https://github.com/arakiken/mlterm/commit/24a2a4886b70f747fba4ea7c07d6e50a6a49039d
   6136   # * 24;279;0 v3.7.2 (2016-08-11) https://github.com/arakiken/mlterm/commit/d094f0f4a31224e1b8d2fa15c6ab37bd1c4c4713
   6137   ('1;96;0')   _ble_term_TERM[depth]=mlterm:30102 ;;
   6138   ('1;277;0')  _ble_term_TERM[depth]=mlterm:30402 ;; # Note: wezterm:20220408 と同じ。wezterm の方を優先
   6139   ('24;279;0') _ble_term_TERM[depth]=mlterm:30702 ;;
   6140 
   6141   ('0;10;1') # Windows Terminal
   6142     # 現状ハードコードされている。
   6143     # https://github.com/microsoft/terminal/blob/bcc38d04/src/terminal/adapter/adaptDispatch.cpp#L779-L782
   6144     _ble_term_TERM[depth]=wt:0 ;;
   6145   ('0;'*';1')
   6146     if ((da2r_vec[1]>=1001)); then
   6147       # Alacritty
   6148       # https://github.com/alacritty/alacritty/blob/4734b2b8/alacritty_terminal/src/term/mod.rs#L1315
   6149       # https://github.com/alacritty/alacritty/blob/4734b2b8/alacritty_terminal/src/term/mod.rs#L3104
   6150       _ble_term_TERM[depth]=alacritty:$((da2r_vec[1]))
   6151     fi ;;
   6152   ('1;0'?????';0')
   6153     _ble_term_TERM[depth]=foot:${da2r:3:5} ;;
   6154   ('1;'*)
   6155     if ((4000<=da2r_vec[1]&&da2r_vec[1]<=4009&&3<=da2r_vec[2])); then
   6156       _ble_term_TERM[depth]=kitty:$((da2r_vec[1]-4000))
   6157     elif ((2000<=da2r_vec[1]&&da2r_vec[1]<5400&&da2r_vec[2]==0)); then
   6158       local version=$((da2r_vec[1]))
   6159       _ble_term_TERM[depth]=vte:$version
   6160       if ((version<4000)); then
   6161         # Note #D1785: vte 0.40.0 未満では DECSCUSR に対応していない。更に未知のシーケ
   6162         # ンスを無視する事もできない。それにも拘らず vte-based な端末は
   6163         # TERM=xterm を設定するので DECSCUSR が出力されて表示が乱れる原因になる。
   6164         # vte の version を見て強制的に DECSCUSR を off にする。
   6165         _ble_term_Ss=
   6166       fi
   6167     fi ;;
   6168   ('65;'*)
   6169     if ((5300<=da2r_vec[1]&&da2r_vec[2]==1)); then
   6170       _ble_term_TERM[depth]=vte:$((da2r_vec[1]))
   6171     elif ((da2r_vec[1]>=100)); then
   6172       _ble_term_TERM[depth]=RLogin:$((da2r_vec[1]))
   6173     fi ;;
   6174   ('67;'*)
   6175     local rex='^67;[0-9]{3,};0$'
   6176     if [[ $TERM == cygwin && $da2r =~ $rex ]]; then
   6177       _ble_term_TERM[depth]=cygwin:$((da2r_vec[1]))
   6178     fi ;;
   6179   ('77;'*';0')
   6180     _ble_term_TERM[depth]=mintty:$((da2r_vec[1])) ;;
   6181   ('83;'*)
   6182     local rex='^83;[0-9]+;0$'
   6183     [[ $da2r =~ $rex ]] && _ble_term_TERM[depth]=screen:$((da2r_vec[1])) ;;
   6184   ('84;0;0')
   6185     _ble_term_TERM[depth]=tmux:0 ;;
   6186   ('99;'*)
   6187     _ble_term_TERM[depth]=contra:$((da2r_vec[1])) ;;
   6188   esac
   6189   [[ ${_ble_term_TERM[depth]} ]] && return 0
   6190 
   6191   # xterm
   6192   if rex='^xterm(-|$)'; [[ $TERM =~ $rex ]]; then
   6193     local version=$((da2r_vec[1]))
   6194     if rex='^1;[0-9]+;0$'; [[ $da2r =~ $rex ]]; then
   6195       # Note: vte (2000以上), kitty (4000以上) は処理済み
   6196       true
   6197     elif rex='^0;[0-9]+;0$'; [[ $da2r =~ $rex ]]; then
   6198       ((95<=version))
   6199     elif rex='^(2|24|1[89]|41|6[145]);[0-9]+;0$'; [[ $da2r =~ $rex ]]; then
   6200       ((280<=version))
   6201     elif rex='^32;[0-9]+;0$'; [[ $da2r =~ $rex ]]; then
   6202       ((354<=version&&version<2000))
   6203     else
   6204       false
   6205     fi && { _ble_term_TERM[depth]=xterm:$version; return 0; }
   6206   fi
   6207 
   6208   _ble_term_TERM[depth]=unknown:-
   6209   return 0
   6210 }
   6211 
   6212 function ble/term/DA1/notify { _ble_term_DA1R=$1; blehook/invoke term_DA1R; }
   6213 function ble/term/DA2/notify {
   6214   # Note #D1485: screen で attach した時に外側の端末の DA2R が混入する
   6215   # 事がある。2回目以降に受信した内容は ble.sh の内部では使用しない事
   6216   # にする。
   6217   local depth=${#_ble_term_DA2R[@]}
   6218   if ((depth==0)) || ble/string#match "${_ble_term_TERM[depth-1]}" '^(screen|tmux):'; then
   6219     _ble_term_DA2R[depth]=$1
   6220     ble/term/DA2/initialize-term "$depth"
   6221 
   6222     local is_outermost=1
   6223     case ${_ble_term_TERM[depth]} in
   6224     (screen:*|tmux:*)
   6225       # 外側の端末にも DA2 要求を出す。[ Note: 最初の DA2 要求は
   6226       # ble/decode/attach (decode.sh) から送信されている。 ]
   6227       local ret is_outermost=
   6228       ble/term/quote-passthrough $'\e[>c' "$((depth+1))"
   6229       ble/util/buffer "$ret" ;;
   6230     (contra:*)
   6231       if [[ ! ${_ble_term_Ss-} ]]; then
   6232         _ble_term_Ss=$'\e[@1 q'
   6233       fi ;;
   6234     (terminology:*)
   6235       # Note #D1946: Terminology にはカーソル位置を戻した時に xenl 状態
   6236       # (ty->termstate.wrapnext) が残ってしまうバグがある。これを避ける為に一旦
   6237       # CR で行頭に戻ってから DECRC する。
   6238       _ble_term_sc=$'\e7' _ble_term_rc=$'\r\e8' ;;
   6239     esac
   6240 
   6241     if [[ $is_outermost ]]; then
   6242       _ble_term_TERM_done=1
   6243       ble/term/modifyOtherKeys/reset
   6244     fi
   6245 
   6246     # 外側の端末情報は以降では処理しない
   6247     ((depth)) && return 0
   6248   fi
   6249 
   6250   blehook/invoke term_DA2R
   6251 }
   6252 
   6253 ## @fn ble/term/quote-passthrough seq [level] [opts]
   6254 ##   指定したシーケンスを、端末マルチプレクサを通過する様に加工します。
   6255 ##
   6256 ##   @param[in] seq
   6257 ##     送信するシーケンスを指定します。
   6258 ##
   6259 ##   @param[in,opt] level
   6260 ##     シーケンスを届ける階層。0 が一番内側の Bash が動作している端末マルチプレ
   6261 ##     クサ。省略した場合は一番外側の端末にシーケンスを届ける。
   6262 ##
   6263 ##   @param[in,opt] opts
   6264 ##     コロン区切りの設定。
   6265 ##
   6266 ##     all
   6267 ##       指定した階層以下の全ての端末・端末マルチプレクサに同じシーケンスを送信
   6268 ##       する。[ Note: terminal multiplexer 自体が処理して外側に作用するかもし
   6269 ##       れないので、先に pass-through で外側に送った後に terminal multiplexer
   6270 ##       自体にも送る。 ]
   6271 ##
   6272 ##   @var[out] ret
   6273 ##     加工されたシーケンスを格納します。
   6274 ##
   6275 function ble/term/quote-passthrough {
   6276   local seq=$1 level=${2:-$((${#_ble_term_DA2R[@]}-1))} opts=$3
   6277   local all=; [[ :$opts: == *:all:* ]] && all=1
   6278   ret=$seq
   6279   [[ $seq ]] || return 0
   6280   local i
   6281   for ((i=level;--i>=0;)); do
   6282     if [[ ${_ble_term_TERM[i]} == tmux:* ]]; then
   6283       # Note: tmux では pass-through seq の中に含まれる \e は \e\e の様に
   6284       # escape する。
   6285       ret=$'\ePtmux;'${ret//$'\e'/$'\e\e'}$'\e\\'${all:+$seq}
   6286     else
   6287       # Note: screen は、最初に現れる \e\\ で pass-through sequence が終わって
   6288       # しまうので単純に pass-through sequence を入れ子にはできない。なので、例
   6289       # えば "\ePXXX\e\\YYY" を pass-through する時には、\e と \\ の間で
   6290       # [\ePXXX\e][\\YYY] の様に分割して、それぞれ pass-through する。
   6291       ret=$'\eP'${ret//$'\e\\'/$'\e\e\\\eP\\'}$'\e\\'${all:+$seq}
   6292     fi
   6293   done
   6294 }
   6295 
   6296 _ble_term_DECSTBM=
   6297 _ble_term_DECSTBM_reset=
   6298 function ble/term/test-DECSTBM.hook1 {
   6299   (($1==2)) && _ble_term_DECSTBM=$'\e[%s;%sr'
   6300 }
   6301 function ble/term/test-DECSTBM.hook2 {
   6302   if [[ $_ble_term_DECSTBM ]]; then
   6303     if (($1==2)); then
   6304       # Failed to reset DECSTBM with \e[;r
   6305       _ble_term_DECSTBM_reset=$'\e[r'
   6306     else
   6307       _ble_term_DECSTBM_reset=$'\e[;r'
   6308     fi
   6309   fi
   6310 }
   6311 function ble/term/test-DECSTBM {
   6312   # Note: kitty 及び wezterm では SCORC と区別できる形の \e[;r では復
   6313   # 帰できない。
   6314   local -a DRAW_BUFF=()
   6315   ble/canvas/panel/goto-top-dock.draw
   6316   ble/canvas/put.draw "$_ble_term_sc"$'\e[1;2r'
   6317   ble/canvas/put-cup.draw 2 1
   6318   ble/canvas/put-cud.draw 1
   6319   ble/term/CPR/request.draw ble/term/test-DECSTBM.hook1
   6320   ble/canvas/put.draw $'\e[;r'
   6321   ble/canvas/put-cup.draw 2 1
   6322   ble/canvas/put-cud.draw 1
   6323   ble/term/CPR/request.draw ble/term/test-DECSTBM.hook2
   6324   ble/canvas/put.draw $'\e[r'"$_ble_term_rc"
   6325   ble/canvas/bflush.draw
   6326 }
   6327 
   6328 #---- DSR(6) ------------------------------------------------------------------
   6329 # CPR (CURSOR POSITION REPORT)
   6330 
   6331 _ble_term_CPR_timeout=60
   6332 _ble_term_CPR_last_seconds=$SECONDS
   6333 _ble_term_CPR_hook=()
   6334 function ble/term/CPR/request.buff {
   6335   ((SECONDS>_ble_term_CPR_last_seconds+_ble_term_CPR_timeout)) &&
   6336     _ble_term_CPR_hook=()
   6337   _ble_term_CPR_last_seconds=$SECONDS
   6338   ble/array#push _ble_term_CPR_hook "$1"
   6339   ble/util/buffer $'\e[6n'
   6340   return 147
   6341 }
   6342 function ble/term/CPR/request.draw {
   6343   ((SECONDS>_ble_term_CPR_last_seconds+_ble_term_CPR_timeout)) &&
   6344     _ble_term_CPR_hook=()
   6345   _ble_term_CPR_last_seconds=$SECONDS
   6346   ble/array#push _ble_term_CPR_hook "$1"
   6347   ble/canvas/put.draw $'\e[6n'
   6348   return 147
   6349 }
   6350 function ble/term/CPR/notify {
   6351   local hook=${_ble_term_CPR_hook[0]}
   6352   ble/array#shift _ble_term_CPR_hook
   6353   [[ ! $hook ]] || builtin eval -- "$hook $1 $2"
   6354 }
   6355 
   6356 #---- SGR(>4): modifyOtherKeys ------------------------------------------------
   6357 
   6358 bleopt/declare -v term_modifyOtherKeys_external auto
   6359 bleopt/declare -v term_modifyOtherKeys_internal auto
   6360 bleopt/declare -v term_modifyOtherKeys_passthrough_kitty_protocol ''
   6361 
   6362 _ble_term_modifyOtherKeys_current=
   6363 _ble_term_modifyOtherKeys_current_method=
   6364 _ble_term_modifyOtherKeys_current_TERM=
   6365 function ble/term/modifyOtherKeys/.update {
   6366   local IFS=$_ble_term_IFS state=${1%%:*}
   6367   [[ $1 == "$_ble_term_modifyOtherKeys_current" ]] &&
   6368     [[ $state != 2 || "${_ble_term_TERM[*]}" == "$_ble_term_modifyOtherKeys_current_TERM" ]] &&
   6369     return 0
   6370 
   6371   # Note: RLogin では modifyStringKeys (\e[>5m) も指定しないと駄目。
   6372   #   また、RLogin は modifyStringKeys にすると S-数字 を
   6373   #   記号に翻訳してくれないので注意。
   6374   local previous=${_ble_term_modifyOtherKeys_current%%:*} method
   6375   if [[ $state == 2 ]]; then
   6376     case $_ble_term_TERM in
   6377     (RLogin:*) method=RLogin_modifyStringKeys ;;
   6378     (kitty:*)
   6379       local da2r_vec
   6380       ble/string#split da2r_vec ';' "$_ble_term_DA2R"
   6381       if ((da2r_vec[2]>=23)); then
   6382         method=kitty_keyboard_protocol
   6383       else
   6384         method=kitty_modifyOtherKeys
   6385       fi ;;
   6386     (screen:*|tmux:*)
   6387       method=modifyOtherKeys
   6388 
   6389       if [[ $bleopt_term_modifyOtherKeys_passthrough_kitty_protocol ]]; then
   6390         # Note (#D1843): if the outermost terminal is kitty-0.23+, we directly
   6391         #   send keyboard-protocol sequences to the outermost kitty.
   6392         local index=$((${#_ble_term_TERM[*]}-1))
   6393         if [[ ${_ble_term_TERM[index]} == kitty:* ]]; then
   6394           local da2r_vec
   6395           ble/string#split da2r_vec ';' "${_ble_term_DA2R[index]}"
   6396           ((da2r_vec[2]>=23)) && method=kitty_keyboard_protocol
   6397         fi
   6398       fi ;;
   6399     (*)
   6400       method=modifyOtherKeys
   6401       if [[ $1 == *:auto ]]; then
   6402         # 問題を起こす端末で無効化。
   6403         ble/term/modifyOtherKeys/.supported || method=disabled
   6404       fi ;;
   6405     esac
   6406 
   6407     # Note #D2062: mc の内部にいる時は外側の端末に関係なく modifyOtherKeys は無
   6408     # 効化する。C-o が効かなくなるし、その他の mc に対するコマンドも効かなくな
   6409     # る可能性がある。
   6410     [[ $MC_SID ]] && method=disabled
   6411 
   6412     # 別の方式で有効化されている時は先に解除しておく。
   6413     if ((previous>=2)) &&
   6414       [[ $method != "$_ble_term_modifyOtherKeys_current_method" ]]
   6415     then
   6416       ble/term/modifyOtherKeys/.update 1
   6417       previous=1
   6418     fi
   6419   else
   6420     method=$_ble_term_modifyOtherKeys_current_method
   6421   fi
   6422   _ble_term_modifyOtherKeys_current=$1
   6423   _ble_term_modifyOtherKeys_current_method=$method
   6424   _ble_term_modifyOtherKeys_current_TERM="${_ble_term_TERM[*]}"
   6425 
   6426   case $method in
   6427   (RLogin_modifyStringKeys)
   6428     case $state in
   6429     (0) ble/util/buffer $'\e[>5;0m' ;;
   6430     (1) ble/util/buffer $'\e[>5;1m' ;;
   6431     (2) ble/util/buffer $'\e[>5;1m\e[>5;2m' ;;
   6432     esac
   6433     ;; # fallback to modifyOtherKeys
   6434   (kitty_modifyOtherKeys)
   6435     # Note: kitty has quirks in its implementation of modifyOtherKeys.
   6436     # Note #D1549: 1 では無効にならない。変な振る舞い。
   6437     # Note #D1626: 更に最近の kitty では \e[>4;0m でも駄目で \e[>4m としなければならない様だ。
   6438     case $state in
   6439     (0|1) ble/util/buffer $'\e[>4;0m\e[>4m' ;;
   6440     (2)   ble/util/buffer $'\e[>4;1m\e[>4;2m\e[m' ;;
   6441     esac
   6442     return 0 ;;
   6443   (kitty_keyboard_protocol)
   6444     # Note: Kovid removed the support for modifyOtherKeys in kitty 0.24 after
   6445     #   vim has pointed out the quirk of kitty.  The kitty keyboard mode only
   6446     #   has push/pop operations so that their numbers need to be balanced.
   6447     local seq=
   6448     case $state in
   6449     (0|1) # pop keyboard mode
   6450       # When this is empty, ble.sh has not yet pushed any keyboard modes, so
   6451       # we just ignore the keyboard mode change.
   6452       [[ $previous ]] || return 0
   6453 
   6454       ((previous>=2)) && seq=$'\e[<u' ;;
   6455     (2) # push keyboard mode
   6456       ((previous>=2)) || seq=$'\e[>1u' ;;
   6457     esac
   6458     if [[ $seq ]]; then
   6459       # Note (#D1843): we directly send kitty-keyboard-protocol sequences to
   6460       #   the outermost terminal.
   6461       local ret
   6462       ble/term/quote-passthrough "$seq"
   6463       ble/util/buffer "$ret"
   6464 
   6465       # find innermost tmux and adjust its modifyOtherKeys state (do not care
   6466       # about screen which is transparent for the user input keys)
   6467       local level
   6468       for ((level=1;level<${#_ble_term_TERM[@]}-1;level++)); do
   6469         [[ ${_ble_term_TERM[level]} == tmux:* ]] || continue
   6470         case $state in
   6471         (0) seq=$'\e[>4;0m\e[m' ;;
   6472         (1) seq=$'\e[>4;1m\e[m' ;;
   6473         (2) seq=$'\e[>4;1m\e[>4;2m\e[m' ;;
   6474         esac
   6475         ble/term/quote-passthrough "$seq" "$level"
   6476         ble/util/buffer "$ret"
   6477         break
   6478       done
   6479     fi
   6480     return 0 ;;
   6481   (disabled)
   6482     return 0 ;;
   6483   esac
   6484 
   6485   # Note: 対応していない端末が SGR と勘違いしても
   6486   #  大丈夫な様に SGR を最後にクリアしておく。
   6487   # Note: \e[>4;2m の時は、対応していない端末のため
   6488   #   一端 \e[>4;1m にしてから \e[>4;2m にする。
   6489   case $state in
   6490   (0) ble/util/buffer $'\e[>4;0m\e[m' ;;
   6491   (1) ble/util/buffer $'\e[>4;1m\e[m' ;;
   6492   (2) ble/util/buffer $'\e[>4;1m\e[>4;2m\e[m' ;;
   6493   esac
   6494 }
   6495 function ble/term/modifyOtherKeys/.supported {
   6496   [[ $_ble_term_TERM_done ]] || return 1
   6497 
   6498   # libvte は SGR(>4) を直接画面に表示してしまう。
   6499   [[ $_ble_term_TERM == vte:* ]] && return 1
   6500 
   6501   # 改造版 Poderosa は通知でウィンドウサイズを毎回変更するので表示が乱れてしまう
   6502   [[ $MWG_LOGINTERM == rosaterm ]] && return 1
   6503 
   6504   case $TERM in
   6505   (linux)
   6506     # Note #D1213: linux (kernel 5.0.0) は "\e[>" でエスケープシーケンスを閉じ
   6507     # てしまう。5.4.8 は大丈夫だがそれでも modifyOtherKeys に対応していない。
   6508     return 1 ;;
   6509   (minix|sun*)
   6510     # minix, Solaris のコンソールもそのまま出力してしまう。
   6511     return 1 ;;
   6512   (st|st-*)
   6513     # Note #D1631: st のエラーログに unknown csi が出るとの文句が出たので無効化。
   6514     # 恐らく将来に亘って st は modifyOtherKeys には対応しないだろう。
   6515     return 1 ;;
   6516   esac
   6517 
   6518   return 0
   6519 }
   6520 function ble/term/modifyOtherKeys/enter {
   6521   local value=$bleopt_term_modifyOtherKeys_internal
   6522   if [[ $value == auto ]]; then
   6523     value=2:auto
   6524   fi
   6525   ble/term/modifyOtherKeys/.update "$value"
   6526 }
   6527 function ble/term/modifyOtherKeys/leave {
   6528   local value=$bleopt_term_modifyOtherKeys_external
   6529   if [[ $value == auto ]]; then
   6530     value=1:auto
   6531   fi
   6532   ble/term/modifyOtherKeys/.update "$value"
   6533 }
   6534 function ble/term/modifyOtherKeys/reset {
   6535   ble/term/modifyOtherKeys/.update "$_ble_term_modifyOtherKeys_current"
   6536 }
   6537 
   6538 #---- Alternate Screen Buffer mode --------------------------------------------
   6539 
   6540 _ble_term_altscr_state=
   6541 function ble/term/enter-altscr {
   6542   [[ $_ble_term_altscr_state ]] && return 0
   6543   _ble_term_altscr_state=("$_ble_canvas_x" "$_ble_canvas_y")
   6544   if [[ $_ble_term_rmcup ]]; then
   6545     ble/util/buffer "$_ble_term_smcup"
   6546   else
   6547     local -a DRAW_BUFF=()
   6548     ble/canvas/put.draw $'\e[?1049h'
   6549     ble/canvas/put-cup.draw "$LINES" 0
   6550     ble/canvas/put-ind.draw "$LINES"
   6551     ble/canvas/bflush.draw
   6552   fi
   6553 }
   6554 function ble/term/leave-altscr {
   6555   [[ $_ble_term_altscr_state ]] || return 0
   6556   if [[ $_ble_term_rmcup ]]; then
   6557     ble/util/buffer "$_ble_term_rmcup"
   6558   else
   6559     local -a DRAW_BUFF=()
   6560     ble/canvas/put-cup.draw "$LINES" 0
   6561     ble/canvas/put-ind.draw
   6562     ble/canvas/put.draw $'\e[?1049l'
   6563     ble/canvas/bflush.draw
   6564   fi
   6565   _ble_canvas_x=${_ble_term_altscr_state[0]}
   6566   _ble_canvas_y=${_ble_term_altscr_state[1]}
   6567   _ble_term_altscr_state=()
   6568 }
   6569 
   6570 #---- rl variable: convert-meta -----------------------------------------------
   6571 
   6572 _ble_term_rl_convert_meta_adjusted=
   6573 _ble_term_rl_convert_meta_external=
   6574 function ble/term/rl-convert-meta/enter {
   6575   [[ $_ble_term_rl_convert_meta_adjusted ]] && return 0
   6576   _ble_term_rl_convert_meta_adjusted=1
   6577 
   6578   if ble/util/rlvar#test convert-meta; then
   6579     _ble_term_rl_convert_meta_external=on
   6580     builtin bind 'set convert-meta off'
   6581   else
   6582     _ble_term_rl_convert_meta_external=off
   6583   fi
   6584 }
   6585 function ble/term/rl-convert-meta/leave {
   6586   [[ $_ble_term_rl_convert_meta_adjusted ]] || return 1
   6587   _ble_term_rl_convert_meta_adjusted=
   6588 
   6589   [[ $_ble_term_rl_convert_meta_external == on ]] &&
   6590     builtin bind 'set convert-meta on'
   6591 }
   6592 
   6593 #---- terminal enter/leave ----------------------------------------------------
   6594 
   6595 function ble/term/enter-for-widget {
   6596   ble/term/bracketed-paste-mode/enter
   6597   ble/term/modifyOtherKeys/enter
   6598   ble/term/cursor-state/.update "$_ble_term_cursor_internal"
   6599   ble/term/cursor-state/.update-hidden "$_ble_term_cursor_hidden_internal"
   6600   ble/util/buffer.flush >&2
   6601 }
   6602 function ble/term/leave-for-widget {
   6603   ble/term/visible-bell/erase
   6604   ble/term/bracketed-paste-mode/leave
   6605   ble/term/modifyOtherKeys/leave
   6606   ble/term/cursor-state/.update "$bleopt_term_cursor_external"
   6607   ble/term/cursor-state/.update-hidden reveal
   6608   ble/util/buffer.flush >&2
   6609 }
   6610 
   6611 _ble_term_state=external
   6612 function ble/term/enter {
   6613   [[ $_ble_term_state == internal ]] && return 0
   6614   ble/term/stty/enter
   6615   ble/term/rl-convert-meta/enter
   6616   ble/term/enter-for-widget
   6617   _ble_term_state=internal
   6618 }
   6619 function ble/term/leave {
   6620   [[ $_ble_term_state == external ]] && return 0
   6621   ble/term/stty/leave
   6622   ble/term/rl-convert-meta/leave
   6623   ble/term/leave-for-widget
   6624   [[ $_ble_term_cursor_current == default ]] ||
   6625     _ble_term_cursor_current=unknown # vim は復元してくれない
   6626   _ble_term_cursor_hidden_current=unknown
   6627   _ble_term_state=external
   6628 }
   6629 
   6630 function ble/term/finalize {
   6631   ble/term/stty/finalize
   6632   ble/term/leave
   6633   ble/util/buffer.flush >&2
   6634 }
   6635 function ble/term/initialize {
   6636   ble/term/stty/initialize
   6637   ble/term/test-DECSTBM
   6638   ble/term/enter
   6639 }
   6640 
   6641 #------------------------------------------------------------------------------
   6642 # String manipulations
   6643 
   6644 _ble_util_s2c_table_enabled=
   6645 ## @fn ble/util/s2c text [index]
   6646 ##   @param[in] text
   6647 ##   @param[in,opt] index
   6648 ##   @var[out] ret
   6649 if ((_ble_bash>=50300)); then
   6650   # printf "'c" で Unicode が読める (どの LC_CTYPE でも Unicode になる)
   6651   function ble/util/s2c {
   6652     builtin printf -v ret %d "'$1"
   6653   }
   6654 elif ((_ble_bash>=40100)); then
   6655   function ble/util/s2c {
   6656     # Note #D1881: bash-5.2 以前では printf %d "'x" に対して mbstate_t 状態が
   6657     # 残ってしまう。なので一旦 clear を試みる。
   6658     if ble/util/is-unicode-output; then
   6659       builtin printf -v ret %d "'μ"
   6660     else
   6661       builtin printf -v ret %d "'x"
   6662     fi
   6663     builtin printf -v ret %d "'$1"
   6664   }
   6665 elif ((_ble_bash>=40000&&!_ble_bash_loaded_in_function)); then
   6666   # - 連想配列にキャッシュできる
   6667   # - printf "'c" で unicode が読める
   6668   declare -A _ble_util_s2c_table
   6669   _ble_util_s2c_table_enabled=1
   6670   function ble/util/s2c {
   6671     [[ $_ble_util_locale_triple != "$LC_ALL:$LC_CTYPE:$LANG" ]] &&
   6672       ble/util/.cache/update-locale
   6673 
   6674     local s=${1::1}
   6675     ret=${_ble_util_s2c_table[x$s]}
   6676     [[ $ret ]] && return 0
   6677 
   6678     ble/util/sprintf ret %d "'$s"
   6679     _ble_util_s2c_table[x$s]=$ret
   6680   }
   6681 elif ((_ble_bash>=40000)); then
   6682   function ble/util/s2c {
   6683     ble/util/sprintf ret %d "'${1::1}"
   6684   }
   6685 else
   6686   # bash-3 では printf %d "'あ" 等としても
   6687   # "あ" を構成する先頭バイトの値が表示されるだけである。
   6688   # 何とかして unicode 値に変換するコマンドを見つけるか、
   6689   # 各バイトを取り出して unicode に変換するかする必要がある。
   6690   # bash-3 では read -n 1 を用いてバイト単位で読み取れる。これを利用する。
   6691   function ble/util/s2c {
   6692     local s=${1::1}
   6693     if [[ $s == [$'\x01'-$'\x7F'] ]]; then
   6694       if [[ $s == $'\x7F' ]]; then
   6695         # Note: bash-3.0 では printf %d "'^?" とすると 0 になってしまう。
   6696         #   printf %d \'^? であれば問題なく 127 になる。
   6697         ret=127
   6698       else
   6699         ble/util/sprintf ret %d "'$s"
   6700       fi
   6701       return 0
   6702     fi
   6703 
   6704     local bytes byte
   6705     ble/util/assign bytes '
   6706       local IFS=
   6707       while ble/bash/read -n 1 byte; do
   6708         builtin printf "%d " "'\''$byte"
   6709       done <<< "$s"
   6710       IFS=$_ble_term_IFS
   6711     '
   6712     "ble/encoding:$bleopt_input_encoding/b2c" $bytes
   6713   }
   6714 fi
   6715 
   6716 # ble/util/c2s
   6717 
   6718 ## @fn ble/util/c2s.impl char
   6719 ##   @var[out] ret
   6720 if ((_ble_bash>=40200)); then
   6721   # $'...' in bash-4.2 supports \uXXXX and \UXXXXXXXX sequences.
   6722 
   6723   # workarounds of bashbug that printf '\uFFFF' results in a broken surrogate
   6724   # pair in systems where sizeof(wchar_t) == 2.
   6725   function ble/util/.has-bashbug-printf-uffff {
   6726     ((40200<=_ble_bash&&_ble_bash<50000)) || return 1
   6727     local LC_ALL=C.UTF-8 2>/dev/null # Workaround: CentOS 7 に C.UTF-8 がなかった
   6728     local ret
   6729     builtin printf -v ret '\uFFFF'
   6730     ((${#ret}==2))
   6731   }
   6732   # suppress locale error #D1440
   6733   ble/function#suppress-stderr ble/util/.has-bashbug-printf-uffff
   6734 
   6735   if ble/util/.has-bashbug-printf-uffff; then
   6736     function ble/util/c2s.impl {
   6737       if ((0xE000<=$1&&$1<=0xFFFF)) && [[ $_ble_util_locale_encoding == UTF-8 ]]; then
   6738         builtin printf -v ret '\\x%02x' "$((0xE0|$1>>12&0x0F))" "$((0x80|$1>>6&0x3F))" "$((0x80|$1&0x3F))"
   6739       else
   6740         builtin printf -v ret '\\U%08x' "$1"
   6741       fi
   6742       builtin eval "ret=\$'$ret'"
   6743     }
   6744     function ble/util/chars2s.impl {
   6745       if [[ $_ble_util_locale_encoding == UTF-8 ]]; then
   6746         local -a buff=()
   6747         local c i=0
   6748         for c; do
   6749           ble/util/c2s.cached "$c"
   6750           buff[i++]=$ret
   6751         done
   6752         IFS= builtin eval 'ret="${buff[*]}"'
   6753       else
   6754         builtin printf -v ret '\\U%08x' "$@"
   6755         builtin eval "ret=\$'$ret'"
   6756       fi
   6757     }
   6758   else
   6759     function ble/util/c2s.impl {
   6760       builtin printf -v ret '\\U%08x' "$1"
   6761       builtin eval "ret=\$'$ret'"
   6762     }
   6763     function ble/util/chars2s.impl {
   6764       builtin printf -v ret '\\U%08x' "$@"
   6765       builtin eval "ret=\$'$ret'"
   6766     }
   6767   fi
   6768 else
   6769   _ble_text_xdigit=(0 1 2 3 4 5 6 7 8 9 A B C D E F)
   6770   _ble_text_hexmap=()
   6771   for ((i=0;i<256;i++)); do
   6772     _ble_text_hexmap[i]=${_ble_text_xdigit[i>>4&0xF]}${_ble_text_xdigit[i&0xF]}
   6773   done
   6774 
   6775   # 動作確認済 3.1, 3.2, 4.0, 4.2, 4.3
   6776   function ble/util/c2s.impl {
   6777     if (($1<0x80)); then
   6778       builtin eval "ret=\$'\\x${_ble_text_hexmap[$1]}'"
   6779       return 0
   6780     fi
   6781 
   6782     local bytes i iN seq=
   6783     ble/encoding:"$_ble_util_locale_encoding"/c2b "$1"
   6784     for ((i=0,iN=${#bytes[@]};i<iN;i++)); do
   6785       seq="$seq\\x${_ble_text_hexmap[bytes[i]&0xFF]}"
   6786     done
   6787     builtin eval "ret=\$'$seq'"
   6788   }
   6789 
   6790   function ble/util/chars2s.loop {
   6791     for c; do
   6792       ble/util/c2s.cached "$c"
   6793       buff[i++]=$ret
   6794     done
   6795   }
   6796   function ble/util/chars2s.impl {
   6797     # Note: 大量の引数を抱えた関数からの関数呼び出しは重いので
   6798     # B=160 毎に小分けにして関数を呼び出す事にする。
   6799     local -a buff=()
   6800     local c i=0 b N=$# B=160
   6801     for ((b=0;b+B<N;b+=B)); do
   6802       ble/util/chars2s.loop "${@:b+1:B}"
   6803     done
   6804     ble/util/chars2s.loop "${@:b+1:N-b}"
   6805     IFS= builtin eval 'ret="${buff[*]}"'
   6806   }
   6807 fi
   6808 
   6809 # どうもキャッシュするのが一番速い様だ
   6810 _ble_util_c2s_table=()
   6811 ## @fn ble/util/c2s char
   6812 ##   @var[out] ret
   6813 function ble/util/c2s {
   6814   [[ $_ble_util_locale_triple != "$LC_ALL:$LC_CTYPE:$LANG" ]] &&
   6815     ble/util/.cache/update-locale
   6816 
   6817   ret=${_ble_util_c2s_table[$1]-}
   6818   if [[ ! $ret ]]; then
   6819     ble/util/c2s.impl "$1"
   6820     _ble_util_c2s_table[$1]=$ret
   6821   fi
   6822 }
   6823 function ble/util/c2s.cached {
   6824   # locale check のない版
   6825   ret=${_ble_util_c2s_table[$1]-}
   6826   if [[ ! $ret ]]; then
   6827     ble/util/c2s.impl "$1"
   6828     _ble_util_c2s_table[$1]=$ret
   6829   fi
   6830 }
   6831 function ble/util/chars2s {
   6832   [[ $_ble_util_locale_triple != "$LC_ALL:$LC_CTYPE:$LANG" ]] &&
   6833     ble/util/.cache/update-locale
   6834   ble/util/chars2s.impl "$@"
   6835 }
   6836 
   6837 ## @fn ble/util/c2bc
   6838 ##   gets a byte count of the encoded data of the char
   6839 ##   指定した文字を現在の符号化方式で符号化した時のバイト数を取得します。
   6840 ##   @param[in]  $1 = code
   6841 ##   @param[out] ret
   6842 function ble/util/c2bc {
   6843   "ble/encoding:$bleopt_input_encoding/c2bc" "$1"
   6844 }
   6845 
   6846 ## @fn ble/util/.cache/update-locale
   6847 ##
   6848 ##  使い方
   6849 ##
   6850 ##    [[ $_ble_util_locale_triple != "$LC_ALL:$LC_CTYPE:$LANG" ]] &&
   6851 ##      ble/util/.cache/update-locale
   6852 ##
   6853 _ble_util_locale_triple=
   6854 _ble_util_locale_ctype=
   6855 _ble_util_locale_encoding=UTF-8
   6856 function ble/util/.cache/update-locale {
   6857   _ble_util_locale_triple=$LC_ALL:$LC_CTYPE:$LANG
   6858 
   6859   # clear cache if LC_CTYPE is changed
   6860   local ret; ble/string#tolower "${LC_ALL:-${LC_CTYPE:-$LANG}}"
   6861   if [[ $_ble_util_locale_ctype != "$ret" ]]; then
   6862     _ble_util_locale_ctype=$ret
   6863     _ble_util_c2s_table=()
   6864     [[ $_ble_util_s2c_table_enabled ]] &&
   6865       _ble_util_s2c_table=()
   6866 
   6867     _ble_util_locale_encoding=C
   6868     if local rex='\.([^@]+)'; [[ $_ble_util_locale_ctype =~ $rex ]]; then
   6869       local enc=${BASH_REMATCH[1]}
   6870       if [[ $enc == utf-8 || $enc == utf8 ]]; then
   6871         enc=UTF-8
   6872       fi
   6873 
   6874       ble/is-function "ble/encoding:$enc/b2c" &&
   6875         _ble_util_locale_encoding=$enc
   6876     fi
   6877   fi
   6878 }
   6879 
   6880 function ble/util/is-unicode-output {
   6881   [[ $_ble_util_locale_triple != "$LC_ALL:$LC_CTYPE:$LANG" ]] &&
   6882     ble/util/.cache/update-locale
   6883   [[ $_ble_util_locale_encoding == UTF-8 ]]
   6884 }
   6885 
   6886 #------------------------------------------------------------------------------
   6887 
   6888 ## @fn ble/util/s2chars text
   6889 ##   @var[out] ret
   6890 function ble/util/s2chars {
   6891   local text=$1 n=${#1} i chars
   6892   chars=()
   6893   for ((i=0;i<n;i++)); do
   6894     ble/util/s2c "${text:i:1}"
   6895     ble/array#push chars "$ret"
   6896   done
   6897   ret=("${chars[@]}")
   6898 }
   6899 function ble/util/s2bytes {
   6900   local LC_ALL= LC_CTYPE=C
   6901   ble/util/s2chars "$1"; local ext=$?
   6902   ble/util/unlocal LC_ALL LC_CTYPE
   6903   ble/util/.cache/update-locale
   6904   return "$?"
   6905 } &>/dev/null
   6906 
   6907 # bind で使用される keyseq の形式
   6908 
   6909 ## @fn ble/util/c2keyseq char
   6910 ##   @var[out] ret
   6911 function ble/util/c2keyseq {
   6912   local char=$(($1))
   6913   case $char in
   6914   (7)   ret='\a' ;;
   6915   (8)   ret='\b' ;;
   6916   (9)   ret='\t' ;;
   6917   (10)  ret='\n' ;;
   6918   (11)  ret='\v' ;;
   6919   (12)  ret='\f' ;;
   6920   (13)  ret='\r' ;;
   6921   (27)  ret='\e' ;;
   6922   (92)  ret='\\' ;;
   6923   (127) ret='\d' ;;
   6924   (28)  ret='\x1c' ;; # workaround \C-\, \C-\\
   6925   (156) ret='\x9c' ;; # workaround \M-\C-\, \M-\C-\\
   6926   (*)
   6927     if ((char<32||128<=char&&char<160)); then
   6928       local char7=$((char&0x7F))
   6929       if ((1<=char7&&char7<=26)); then
   6930         ble/util/c2s "$((char7+96))"
   6931       else
   6932         ble/util/c2s "$((char7+64))"
   6933       fi
   6934       ret='\C-'$ret
   6935       ((char&0x80)) && ret='\M-'$ret
   6936     else
   6937       ble/util/c2s "$char"
   6938     fi ;;
   6939   esac
   6940 }
   6941 ## @fn ble/util/chars2keyseq char...
   6942 ##   @var[out] ret
   6943 function ble/util/chars2keyseq {
   6944   local char str=
   6945   for char; do
   6946     ble/util/c2keyseq "$char"
   6947     str=$str$ret
   6948   done
   6949   ret=$str
   6950 }
   6951 ## @fn ble/util/keyseq2chars keyseq
   6952 ##   @arr[out] ret
   6953 function ble/util/keyseq2chars {
   6954   local keyseq=$1
   6955   local -a chars=()
   6956   local mods=
   6957   local rex='^([^\]+)|^\\([CM]-|[0-7]{1,3}|x[0-9a-fA-F]{1,2}|.)?'
   6958   while [[ $keyseq ]]; do
   6959     local text=${keyseq::1} esc
   6960     [[ $keyseq =~ $rex ]] &&
   6961       text=${BASH_REMATCH[1]} esc=${BASH_REMATCH[2]}
   6962 
   6963     if [[ $text ]]; then
   6964       keyseq=${keyseq:${#text}}
   6965       ble/util/s2chars "$text"
   6966     else
   6967       keyseq=${keyseq:1+${#esc}}
   6968       ret=()
   6969       case $esc in
   6970       ([CM]-)  mods=$mods${esc::1}; continue ;;
   6971       (x?*)    ret=$((16#${esc#x})) ;;
   6972       ([0-7]*) ret=$((8#$esc)) ;;
   6973       (a) ret=7 ;;
   6974       (b) ret=8 ;;
   6975       (t) ret=9 ;;
   6976       (n) ret=10 ;;
   6977       (v) ret=11 ;;
   6978       (f) ret=12 ;;
   6979       (r) ret=13 ;;
   6980       (e) ret=27 ;;
   6981       (d) ret=127 ;;
   6982       (*) ble/util/s2c "$esc" ;;
   6983       esac
   6984     fi
   6985 
   6986     [[ $mods == *C* ]] && ((ret=ret==63?127:(ret&0x1F)))
   6987     [[ $mods == *M* ]] && ble/array#push chars 27
   6988     #[[ $mods == *M* ]] && ((ret|=0x80))
   6989     mods=
   6990     ble/array#push chars "${ret[@]}"
   6991   done
   6992 
   6993   if [[ $mods ]]; then
   6994     [[ $mods == *M* ]] && ble/array#push chars 27
   6995     ble/array#push chars 0
   6996   fi
   6997 
   6998   ret=("${chars[@]}")
   6999 }
   7000 
   7001 #------------------------------------------------------------------------------
   7002 
   7003 ## @fn ble/encoding:UTF-8/b2c byte...
   7004 ##   @var[out] ret
   7005 function ble/encoding:UTF-8/b2c {
   7006   local bytes b0 n i
   7007   bytes=("$@")
   7008   ret=0
   7009   ((b0=bytes[0]&0xFF))
   7010   ((n=b0>=0xF0
   7011     ?(b0>=0xFC?5:(b0>=0xF8?4:3))
   7012     :(b0>=0xE0?2:(b0>=0xC0?1:0)),
   7013     ret=n?b0&0x7F>>n:b0))
   7014   for ((i=1;i<=n;i++)); do
   7015     ((ret=ret<<6|0x3F&bytes[i]))
   7016   done
   7017 }
   7018 
   7019 ## @fn ble/encoding:UTF-8/c2b char
   7020 ##   @arr[out] bytes
   7021 function ble/encoding:UTF-8/c2b {
   7022   local code=$1 n i
   7023   ((code=code&0x7FFFFFFF,
   7024     n=code<0x80?0:(
   7025       code<0x800?1:(
   7026         code<0x10000?2:(
   7027           code<0x200000?3:(
   7028             code<0x4000000?4:5))))))
   7029   if ((n==0)); then
   7030     bytes=("$code")
   7031   else
   7032     bytes=()
   7033     for ((i=n;i;i--)); do
   7034       ((bytes[i]=0x80|code&0x3F,
   7035         code>>=6))
   7036     done
   7037     ((bytes[0]=code&0x3F>>n|0xFF80>>n&0xFF))
   7038   fi
   7039 }
   7040 
   7041 ## @fn ble/encoding:C/b2c byte
   7042 ##   @var[out] ret
   7043 function ble/encoding:C/b2c {
   7044   local byte=$1
   7045   ((ret=byte&0xFF))
   7046 }
   7047 ## @fn ble/encoding:C/c2b char
   7048 ##   @arr[out] bytes
   7049 function ble/encoding:C/c2b {
   7050   local code=$1
   7051   bytes=("$((code&0xFF))")
   7052 }
   7053 
   7054 #------------------------------------------------------------------------------
   7055 # builtin readonly
   7056 
   7057 builtin eval -- "${_ble_util_gdict_declare//NAME/_ble_builtin_readonly_blacklist}"
   7058 
   7059 function ble/builtin/readonly/.initialize-blacklist {
   7060   function ble/builtin/readonly/.initialize-blacklist { return 0; }
   7061 
   7062   local -a list=()
   7063 
   7064   # Bash variables ble.sh uses
   7065   ble/array#push list FUNCNEST IFS IGNOREEOF POSIXLY_CORRECT TMOUT # adjust
   7066   ble/array#push list PWD OLDPWD CDPATH # readlink
   7067   ble/array#push list BASHPID GLOBIGNORE MAPFILE REPLY # util
   7068   ble/array#push list INPUTRC # decode
   7069   ble/array#push list LINES COLUMNS # canvas
   7070   ble/array#push list HIST{CONTROL,IGNORE,SIZE,TIMEFORMAT} # history
   7071   ble/array#push list PROMPT_COMMAND PS1 # prompt
   7072   ble/array#push list BASH_COMMAND BASH_REMATCH HISTCMD LINENO PIPESTATUS TIMEFORMAT # exec
   7073   ble/array#push list BASH_XTRACEFD PS4 # debug
   7074 
   7075   # Other common variables that ble.sh uses
   7076   ble/array#push list CC LESS MANOPT MANPAGER PAGER PATH MANPATH
   7077 
   7078   # Other uppercase variables that ble.sh internally uses in each module
   7079   ble/array#push list BUFF # util
   7080   ble/array#push list KEYS KEYMAP WIDGET LASTWIDGET # decode
   7081   ble/array#push list DRAW_BUFF # canvas
   7082   ble/array#push list D{MIN,MAX,MAX0} {HIGHLIGHT,PREV}_{BUFF,UMAX,UMIN} LEVEL LAYER_{UMAX,UMIN} # color
   7083   ble/array#push list HISTINDEX_NEXT FILE LINE INDEX INDEX_FILE # history
   7084   ble/array#push list ARG FLAG REG # vi
   7085   ble/array#push list COMP{1,2,S,V} ACTION CAND DATA INSERT PREFIX_LEN # core-complete
   7086   ble/array#push list PRETTY_NAME NAME VERSION # edit (/etc/os-release)
   7087 
   7088   local v
   7089   for v in "${list[@]}"; do ble/gdict#set _ble_builtin_readonly_blacklist "$v" 1; done
   7090 }
   7091 function ble/builtin/readonly/.check-variable-name {
   7092   # Local variables are always allowed to make readonly.  Note: this
   7093   # implementation does not block propagating tempenv variables being
   7094   # propagated to the global context.  There is no way to reliably detect the
   7095   # tempenv variables.
   7096   ble/variable#is-global "$1" || return 0
   7097 
   7098   # If the variable starts with "_" but does not start with "_ble", it could be
   7099   # a global variable used by another framework.  We allow such namespaced
   7100   # variables being readonly.
   7101   if [[ $1 == _* && $1 != _ble* && $1 != __ble* ]]; then
   7102     return 0
   7103   fi
   7104 
   7105   # These special variables should not be made readonly.
   7106   case $1 in
   7107   (?)                    return 1;; # single character variables
   7108   (BLE_*|ADVICE_*)       return 1;; # ble.sh variables
   7109   (COMP_*|COMPREPLY)     return 1;; # completion variables
   7110   (READLINE_*)           return 1;; # readline variables
   7111   (LC_*|LANG)            return 1;; # locale variables
   7112   esac
   7113 
   7114   # If the variable name is in the black list, the variable cannot be readonly.
   7115   ble/builtin/readonly/.initialize-blacklist
   7116   if ble/gdict#get _ble_builtin_readonly_blacklist; then
   7117     return 1
   7118   fi
   7119 
   7120   # Otherwise, the variables that do not contain lowercase characters are
   7121   # allowed to become readonly.
   7122   if [[ $1 != *[a-z]* ]]; then
   7123     return 0
   7124   fi
   7125 
   7126   return 1
   7127 }
   7128 
   7129 builtin eval -- "${_ble_util_gdict_declare//NAME/_ble_builtin_readonly_mark}"
   7130 _ble_builtin_readonly_message_count=0
   7131 blehook internal_PREEXEC!='_ble_builtin_readonly_message_count=0'
   7132 
   7133 ## @fn ble/builtin/readonly/.print-warning
   7134 ##   @var[out] _ble_local_caller
   7135 function ble/builtin/readonly/.print-warning {
   7136   [[ -t 2 ]] || return 0
   7137 
   7138   # If the caller iformation has not been initialized:
   7139   if [[ ! $_ble_local_caller ]]; then
   7140     _ble_local_caller=-
   7141     local i n=${#FUNCNAME[@]}
   7142     for ((i=1;i<n;i++)); do
   7143       [[ ${FUNCNAME[i]} == *readonly ]] && continue
   7144       [[ ${FUNCNAME[i]} == ble/function#advice/* ]] && continue
   7145       _ble_local_caller="${BASH_SOURCE[i]}:${BASH_LINENO[i-1]}"
   7146       break
   7147     done
   7148   fi
   7149 
   7150   local s_caller=
   7151   if [[ $_ble_local_caller != - ]]; then
   7152     ! ble/gdict#has _ble_builtin_readonly_mark "$_ble_local_caller:$1" || return 0
   7153     ble/gdict#set _ble_builtin_readonly_mark "$_ble_local_caller:$1" yes
   7154     s_caller=" ($_ble_local_caller)"
   7155   else
   7156     # We show messages only up to ten times
   7157     ((_ble_builtin_readonly_message_count++<10)) || return 0
   7158   fi
   7159 
   7160   _ble_local_flags=w$_ble_local_flags
   7161   ble/util/print "ble.sh$s_caller: An attempt to make variable \`$1' readonly was blocked." >&2
   7162 
   7163   return 0
   7164 }
   7165 function ble/builtin/readonly {
   7166   local _ble_local_set _ble_local_shopt
   7167   ble/base/.adjust-bash-options _ble_local_set _ble_local_shopt
   7168 
   7169   local _ble_local_flags=
   7170   local -a _ble_local_options=()
   7171   local _ble_local_caller= # used by print-warning
   7172   while (($#)); do
   7173     if ble/string#match "$1" '^([_a-zA-Z][_a-zA-Z0-9]*)($|=)'; then
   7174       _ble_local_flags=v$_ble_local_flags
   7175       local _ble_local_var=${BASH_REMATCH[1]}
   7176       if [[ ${BASH_REMATCH[2]} == = ]]; then
   7177         ble/util/sprintf "$_ble_local_var" "${1#*=}"
   7178       fi
   7179 
   7180       if ble/builtin/readonly/.check-variable-name "$_ble_local_var"; then
   7181         _ble_local_flags=r$_ble_local_flags
   7182         ble/array#push _ble_local_options "$_ble_local_var"
   7183       else
   7184         ble/builtin/readonly/.print-warning "$1"
   7185       fi
   7186     else
   7187       ble/array#push _ble_local_options "$1"
   7188     fi
   7189     shift
   7190   done
   7191 
   7192   if [[ $_ble_local_flags == *w* ]]; then
   7193     ble/util/print 'ble.sh: The global variables with unprefixed lowercase names or special names should not be made readonly. It can break arbitrary Bash configurations.' >&2
   7194   fi
   7195   local _ble_local_ext=0
   7196   if [[ $_ble_local_flags != *v* || $_ble_local_flags == *r* ]]; then
   7197     # We call `builtin readonly' only when no variables are specified
   7198     # (e.g. readonly, readonly --help), or at least one variable are allowed to
   7199     # become readonly.
   7200     builtin readonly "${_ble_local_options[@]}"
   7201     _ble_local_ext=$?
   7202   fi
   7203   ble/base/.restore-bash-options _ble_local_set _ble_local_shopt
   7204   return "$?"
   7205 }
   7206 
   7207 function readonly { ble/builtin/readonly "$@"; }
   7208 
   7209 #------------------------------------------------------------------------------
   7210 # ble/util/message
   7211 
   7212 >| "$_ble_base_run/$$.util.message-listening"
   7213 >> "$_ble_base_run/$$.util.message"
   7214 _ble_util_message_precmd=()
   7215 
   7216 ## @fn ble/util/message/.encode-data target data
   7217 ##   @param[in] target
   7218 ##     送信対象のプロセスの PID を指定します
   7219 ##   @param[in] data
   7220 ##     送るデータを指定します
   7221 ##   @var[out] ret
   7222 function ble/util/message/.encode-data {
   7223   local target=$1 data=$2
   7224   if ((${#data}<256)); then
   7225     ble/string#quote-word "$data"
   7226     ret=eval:$ret
   7227   else
   7228     ble/util/getpid
   7229 
   7230     local index=0 file
   7231     while
   7232       file=$_ble_base_run/$target.util.message.data-$BASHPID-$index
   7233       [[ -e $file ]]
   7234     do ((++index)); done
   7235 
   7236     ble/util/put "$data" >| "$file"
   7237 
   7238     ret=file:${file##*.}
   7239   fi
   7240 }
   7241 ## @fn ble/util/message/.decode-data data
   7242 ##   @var[out] ret
   7243 function ble/util/message/.decode-data {
   7244   ret=$1
   7245   case $ret in
   7246   (eval:*)
   7247     local value=${ret#eval:}
   7248     ble/syntax:bash/simple-word/is-simple "$value" &&
   7249       builtin eval -- "ret=($value)" ;;
   7250   (file:*)
   7251     local file=$_ble_base_run/$$.util.message.${ret#file:}
   7252     ble/util/readfile ret "$file"
   7253     ble/array#push _ble_local_remove "$file"
   7254   esac
   7255 }
   7256 
   7257 function ble/util/message.post {
   7258   local target=${1:-$$} event=${2-} type=${3-} data=${4-}
   7259 
   7260   if ! [[ $type && $type != *["$_ble_term_IFS"]* ]]; then
   7261     ble/util/print "ble/util/message: invalid message type format '$type'" >&2
   7262     return 2
   7263   elif [[ $target == $$ ]] && ! ble/is-function ble/util/message/handler:"$type"; then
   7264     ble/util/print "ble/util/message: unknown message type name '$type'" >&2
   7265     return 2
   7266   fi
   7267 
   7268   case $event in
   7269   (precmd) ;;
   7270   (*)
   7271     ble/util/print "ble/util/message: unknown event type '$event'" >&2
   7272     return 2 ;;
   7273   esac
   7274 
   7275   if [[ $target == broadcast ]]; then
   7276     local ret file
   7277     ble/util/eval-pathname-expansion '"$_ble_base_run"/+([0-9]).util.message-listening' canonical
   7278     for file in "${ret[@]}"; do
   7279       file=${file%-listening}
   7280       local pid=${file##*/}; pid=${pid%%.*}
   7281       if builtin kill -0 "$pid"; then
   7282         ble/util/message/.encode-data "$pid" "$data"
   7283         ble/util/print "$event $type $ret" >> "$file"
   7284       fi
   7285     done
   7286 
   7287   elif ble/string#match "$target" '^[0-9]+$'; then
   7288     if ! builtin kill -0 "$target"; then
   7289       ble/util/print "ble/util/message: target process $target is not found" >&2
   7290       return 2
   7291     elif [[ ! -f $_ble_base_run/$target.util.message-listening ]]; then
   7292       ble/util/print "ble/util/message: target process $target is not listening ble-messages" >&2
   7293       return 2
   7294     fi
   7295 
   7296     local ret
   7297     ble/util/message/.encode-data "$target" "$data"
   7298     ble/util/print "$event $type $ret" >> "$_ble_base_run/$target.util.message"
   7299   else
   7300     ble/util/print "ble/util/message: unknown target '$target'" >&2
   7301     return 2
   7302   fi
   7303 }
   7304 function ble/util/message.check {
   7305   local file=$_ble_base_run/$$.util.message
   7306   while [[ -f $file && -s $file ]]; do
   7307     local fread=$file
   7308     ble/bin/mv -f "$file" "$file-reading" && fread=$file-reading
   7309 
   7310     local IFS=$_ble_term_IFS event type data
   7311     while ble/bash/read event type data || [[ $event ]]; do
   7312       # check message handler
   7313       [[ $type ]] && ble/is-function ble/util/message/handler:"$type" || continue
   7314 
   7315       case $event in
   7316       (precmd) ble/array#push _ble_util_message_precmd "$type $data" ;;
   7317       esac
   7318     done < "$fread"
   7319 
   7320     >| "$fread"
   7321   done
   7322 }
   7323 function ble/util/message.process {
   7324   ble/util/message.check
   7325 
   7326   local event=$1
   7327   case $event in
   7328   (precmd)
   7329     local _ble_local_messages
   7330     _ble_local_messages=("${_ble_util_message_precmd[@]}")
   7331     _ble_util_message_precmd=()
   7332 
   7333     local _ble_local_message
   7334     local -a _ble_local_remove=()
   7335     for _ble_local_message in "${_ble_local_messages[@]}"; do
   7336       local _ble_local_event=${_ble_local_message%%' '*}
   7337       local ret; ble/util/message/.decode-data "${_ble_local_message#* }"
   7338       local _ble_local_data=$ret
   7339       ble/util/unlocal ret
   7340       ble/util/message/handler:"$_ble_local_event" "$_ble_local_data"
   7341     done
   7342 
   7343     ((${#_ble_local_remove[@]})) &&ble/bin/rm -f "${_ble_local_remove[@]}" ;;
   7344   (*)
   7345     ble/util/print "ble/util/message: unknown event type '$event'" >&2
   7346     return 2 ;;
   7347   esac
   7348 }
   7349 
   7350 function ble/util/message/handler:print {
   7351   ble/edit/enter-command-layout # #D1800 pair=leave-command-layout
   7352   ble/util/print "$1" >&2
   7353   ble/edit/leave-command-layout # #D1800 pair=enter-command-layout
   7354 }
   7355 
   7356 blehook internal_PRECMD!='ble/util/message.process precmd'