sistema_progs

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

make_command.sh (30869B)


      1 #!/usr/bin/env bash
      2 
      3 umask 022
      4 shopt -s nullglob
      5 
      6 function mkd {
      7   [[ -d $1 ]] || mkdir -p "$1"
      8 }
      9 
     10 function download {
     11   local url=$1 dst=$2
     12   if [[ ! -s $dst ]]; then
     13     [[ $dst == ?*/* ]] && mkd "${dst%/*}"
     14     if type wget &>/dev/null; then
     15       wget "$url" -O "$dst.part" && mv "$dst.part" "$dst"
     16     else
     17       echo "make_command: 'wget' not found." >&2
     18       exit 2
     19     fi
     20   fi
     21 }
     22 
     23 function ble/array#push {
     24   while (($#>=2)); do
     25     builtin eval "$1[\${#$1[@]}]=\$2"
     26     set -- "$1" "${@:3}"
     27   done
     28 }
     29 
     30 function sub:help {
     31   printf '%s\n' \
     32          'usage: make_command.sh SUBCOMMAND args...' \
     33          '' 'SUBCOMMAND' ''
     34   local sub
     35   for sub in $(declare -F | sed -n 's|^declare -[fx]* sub:\([^/]*\)$|\1|p'); do
     36     if declare -f sub:"$sub"/help &>/dev/null; then
     37       sub:"$sub"/help
     38     else
     39       printf '  %s\n' "$sub"
     40     fi
     41   done
     42   printf '\n'
     43 }
     44 
     45 #------------------------------------------------------------------------------
     46 
     47 function sub:install {
     48   # read options
     49   local flag_error= flag_release=
     50   local opt_strip_comment=
     51   while [[ $1 == -* ]]; do
     52     local arg=$1; shift
     53     case $arg in
     54     (--release) flag_release=1 ;;
     55     (--strip-comment=*)
     56       opt_strip_comment=${arg#*=} ;;
     57     (*) echo "install: unknown option $arg" >&2
     58         flag_error=1 ;;
     59     esac
     60   done
     61   [[ $flag_error ]] && return 1
     62 
     63   local src=$1
     64   local dst=$2
     65   mkd "${dst%/*}"
     66   if [[ $src == *.sh ]]; then
     67     local nl=$'\n' q=\'
     68 
     69     # header comment
     70     local script='1i\
     71 # Copyright 2015 Koichi Murase <myoga.murase@gmail.com>. All rights reserved.\
     72 # This script is a part of blesh (https://github.com/akinomyoga/ble.sh)\
     73 # provided under the BSD-3-Clause license.  Do not edit this file because this\
     74 # is not the original source code: Various pre-processing has been applied.\
     75 # Also, the code comments and blank lines are stripped off in the installation\
     76 # process.  Please find the corresponding source file(s) in the repository\
     77 # "akinomyoga/ble.sh".'
     78     if [[ $src == out/ble.sh ]]; then
     79       script=$script'\
     80 #\
     81 # Source: /ble.pp'
     82       local file
     83       for file in $(git ls-files src); do
     84         [[ $file == *.sh ]] || continue
     85         script=$script"\\
     86 # Source: /$file"
     87       done
     88     else
     89       script=$script'\
     90 #\
     91 # Source: /'"${src#out/}"
     92     fi
     93 
     94     # strip comments
     95     if [[ $opt_strip_comment != no ]]; then
     96       script=$script'
     97 /<<[[:space:]]*EOF/,/^[[:space:]]*EOF/{p;d;}
     98 /^[[:space:]]*#/d
     99 /^[[:space:]]*$/d'
    100     else
    101       script=$script'\
    102 #------------------------------------------------------------------------------'
    103     fi
    104 
    105     [[ $flag_release ]] &&
    106       script=$script$nl's/^\([[:space:]]*_ble_base_repository=\)'$q'.*'$q'\([[:space:]]*\)$/\1'${q}release:$dist_git_branch$q'/'
    107     sed "$script" "$src" >| "$dst.part" && mv "$dst.part" "$dst"
    108   else
    109     cp "$src" "$dst"
    110   fi
    111 }
    112 function sub:install/help {
    113   printf '  install src dst\n'
    114 }
    115 
    116 function sub:uninstall {
    117   rm -rf "$@"
    118 
    119   local file children
    120   for file; do
    121     while
    122       file=${file%/*}
    123       [[ -d $file ]] || break
    124       children=("$file"/* "$file"/.*)
    125       ((${#children[@]} == 0))
    126     do
    127       rmdir "$file"
    128     done
    129   done
    130 }
    131 
    132 function sub:dist {
    133   local dist_git_branch=$(git rev-parse --abbrev-ref HEAD)
    134   local tmpdir=ble-$FULLVER
    135   local src
    136   for src in "$@"; do
    137     local dst=$tmpdir${src#out}
    138     sub:install --release "$src" "$dst"
    139   done
    140   [[ -d dist ]] || mkdir -p dist
    141   tar caf "dist/$tmpdir.$(date +'%Y%m%d').tar.xz" "$tmpdir" && rm -r "$tmpdir"
    142 }
    143 
    144 function sub:ignoreeof-messages {
    145   (
    146     cd ~/local/build/bash-4.3/po
    147     sed -nr '/msgid "Use \\"%s\\" to leave the shell\.\\n"/{n;s/^[[:space:]]*msgstr "(.*)"[^"]*$/\1/p;}' *.po | while builtin read -r line || [[ $line ]]; do
    148       [[ $line ]] || continue
    149       echo $(printf "$line" exit) # $() は末端の改行を削除するため
    150     done
    151   ) >| lib/core-edit.ignoreeof-messages.new
    152 }
    153 
    154 #------------------------------------------------------------------------------
    155 # sub:check
    156 # sub:check-all
    157 
    158 function sub:check {
    159   local bash=${1-bash}
    160   "$bash" out/ble.sh --test
    161 }
    162 function sub:check-all {
    163   local -x _ble_make_command_check_count=0
    164   local bash rex_version='^bash-([0-9]+)\.([0-9]+)$'
    165   for bash in $(compgen -c -- bash- | grep -E '^bash-(dev|[0-9]+\.[0-9]+)$' | sort -Vr); do
    166     [[ $bash =~ $rex_version && ${BASH_REMATCH[1]} -ge 3 ]] || continue
    167     "$bash" out/ble.sh --test || return 1
    168     ((_ble_make_command_check_count++))
    169   done
    170 }
    171 
    172 #------------------------------------------------------------------------------
    173 # sub:scan
    174 
    175 _make_rex_escseq='(\[[ -?]*[@-~])*'
    176 
    177 function sub:scan/grc-source {
    178   local -a options=(--color --exclude=./{test,memo,ext,wiki,contrib,[TD]????.*} --exclude=\*.{md,awk} --exclude=./{GNUmakefile,make_command.sh})
    179   grc "${options[@]}" "$@"
    180 }
    181 function sub:scan/list-command {
    182   local -a options=(--color --exclude=./{test,memo,ext,wiki,contrib,[TD]????.*} --exclude=\*.{md,awk})
    183 
    184   # read arguments
    185   local flag_exclude_this= flag_error=
    186   local command=
    187   while (($#)); do
    188     local arg=$1; shift
    189     case $arg in
    190     (--exclude-this)
    191       flag_exclude_this=1 ;;
    192     (--exclude=*)
    193       ble/array#push options "$arg" ;;
    194     (--)
    195       [[ $1 ]] && command=$1
    196       break ;;
    197     (-*)
    198       echo "check: unknown option '$arg'" >&2
    199       flag_error=1 ;;
    200     (*)
    201       command=$arg ;;
    202     esac
    203   done
    204   if [[ ! $command ]]; then
    205     echo "check: command name is not specified." >&2
    206     flag_error=1
    207   fi
    208   [[ $flag_error ]] && return 1
    209 
    210   [[ $flag_exclude_this ]] && ble/array#push options --exclude=./make_command.sh
    211   grc "${options[@]}" "(^|[^-./\${}=#])\b$command"'\b([[:space:]|&;<>()`"'\'']|$)'
    212 }
    213 
    214 function sub:scan/builtin {
    215   echo "--- $FUNCNAME $1 ---"
    216   local command=$1 esc=$_make_rex_escseq
    217   sub:scan/list-command --exclude-this --exclude={generate-release-note.sh,lib/test-*.sh,make,ext} "$command" "${@:2}" |
    218     grep -Ev "$rex_grep_head([[:space:]]*|[[:alnum:][:space:]]*[[:space:]])#|(\b|$esc)(builtin|function)$esc([[:space:]]$esc)+$command(\b|$esc)" |
    219     grep -Ev "$command(\b|$esc)=" |
    220     grep -Ev "ble\.sh $esc\($esc$command$esc\)$esc" |
    221     sed -E 'h;s/'"$_make_rex_escseq"'//g
    222         \Z^\./lib/test-[^:]+\.sh:[0-9]+:.*ble/test Zd
    223       s/^[^:]*:[0-9]+:[[:space:]]*//
    224         \Z(\.awk|push|load|==|#(push|pop)) \b'"$command"'\bZd
    225       g'
    226 }
    227 
    228 function sub:scan/check-todo-mark {
    229   echo "--- $FUNCNAME ---"
    230   grc --color --exclude=./make_command.sh '@@@'
    231 }
    232 function sub:scan/a.txt {
    233   echo "--- $FUNCNAME ---"
    234   grc --color --exclude={test,ext,./lib/test-\*.sh,./make_command.sh,\*.md} --exclude=check-mem.sh '[/[:space:]<>"'\''][a-z]\.txt|/dev/(pts/|pty)[0-9]*|/dev/tty' |
    235     sed -E 'h;s/'"$_make_rex_escseq"'//g
    236       \Z^\./memo/Zd
    237       \Zgithub302-perlre-server\.bashZd
    238       \Z^\./contrib/integration/fzf-git.bash:[0-9]+:Zd
    239     s/^[^:]*:[0-9]+:[[:space:]]*//
    240       \Z^[[:space:]]*#Zd
    241       \ZDEBUG_LEAKVARZd
    242       \Z\[\[ -t 4 && -t 5 ]]Zd
    243       \Z^ble/fd#alloc .*Zd
    244       \Zbuiltin read -et 0.000001 dummy </dev/ttyZd
    245       g'
    246 }
    247 
    248 function sub:scan/bash300bug {
    249   echo "--- $FUNCNAME ---"
    250   # bash-3.0 では local arr=(1 2 3) とすると
    251   # local arr='(1 2 3)' と解釈されてしまう。
    252   grc '(local|declare|typeset) [_a-zA-Z]+=\(' --exclude=./{test,ext} --exclude=./make_command.sh --exclude=ChangeLog.md --color |
    253     grep -v '#D0184'
    254 
    255   # bash-3.0 では local -a arr=("$hello") とすると
    256   # クォートしているにも拘らず $hello の中身が単語分割されてしまう。
    257   grc '(local|declare|typeset) -a [[:alnum:]_]+=\([^)]*[\"'\''`]' --exclude=./{test,ext} --exclude=./make_command.sh --color |
    258     grep -v '#D0525'
    259 
    260   # bash-3.0 では "${scalar[@]/xxxx}" は全て空になる
    261   grc '\$\{[_a-zA-Z0-9]+\[[*@]\]/' --exclude=./{text,ext} --exclude=./make_command.sh --exclude=\*.md --color |
    262     grep -v '#D1570'
    263 
    264   # bash-3.0 では "..${var-$'hello'}.." は (var が存在しない時) "..'hello'..." になる。
    265   grc '".*\$\{[^{}]*\$'\''([^\\'\'']|\\.)*'\''\}.*"' --exclude={./make_command.sh,memo,\*.md} --color |
    266     grep -v '#D1774'
    267 
    268 }
    269 
    270 function sub:scan/bash301bug-array-element-length {
    271   echo "--- $FUNCNAME ---"
    272   # bash-3.1 で ${#arr[index]} を用いると、
    273   # 日本語の文字数が変になる。
    274   grc '\$\{#[[:alnum:]]+\[[^@*]' --exclude={test,ChangeLog.md} --color |
    275     grep -Ev '^([^#]*[[:space:]])?#' |
    276     grep -v '#D0182'
    277 }
    278 
    279 function sub:scan/bash400bug {
    280   echo "--- $FUNCNAME ---"
    281 
    282   # bash-3.0..4.0 で $'' 内に \' を入れていると '' の入れ子状態が反転して履歴展
    283   # 開が '' の内部で起こってしまう。
    284   grc '\$'\''([^\'\'']|\\[^'\''])*\\'\''([^\'\'']|\\.|'\''([^\'\'']|\\*)'\'')*![^=[:space:]]' --exclude={test,ChangeLog.md} --color |
    285     grep -v '9f0644470'
    286 }
    287 
    288 function sub:scan/bash401-histexpand-bgpid {
    289   echo "--- $FUNCNAME ---"
    290   grc '"\$!"' --exclude={test,ChangeLog.md} --color |
    291     grep -Ev '#D2028'
    292 }
    293 
    294 function sub:scan/bash404-no-argument-return {
    295   echo "--- $FUNCNAME ---"
    296   grc --color 'return[[:space:]]*($|[;|&<>])' --exclude={test,wiki,ChangeLog.md,make,docs,make_command.sh} |
    297     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    298 
    299       \Z@returnZd
    300       \Z\) return;Zd
    301       \Zreturn;[[:space:]]*$Zd
    302       \Zif \(REQ == "[A-Z]+"\)Zd
    303       \Z\(return\|ret\)Zd
    304       \Z_ble_trap_done=return$Zd
    305       \Z\bwe return\bZd
    306 
    307       g'
    308 }
    309 
    310 function sub:scan/bash501-arith-base {
    311   echo "--- $FUNCNAME ---"
    312   # bash-5.1 で $((10#)) の取り扱いが変わった。
    313   grc '\b10#\$' --exclude={test,ChangeLog.md}
    314 }
    315 
    316 function sub:scan/bash502-patsub_replacement {
    317   echo "--- $FUNCNAME ---"
    318   # bash-5.2 patsub_replacement で ${var/pat/string} の string 中の & が特別な
    319   # 意味を持つ様になったので、特に意識する場合を除いては quote が必要になった。
    320   grc --color '\$\{[[:alnum:]_]+(\[[^][]*\])?//?([^{}]|\{[^{}]*\})+/[^{}"'\'']*([&$]|\\)' --exclude=./test |
    321     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    322       \Z//?\$q/\$Q\}Zd
    323       \Z//?\$q/\$qq\}Zd
    324       \Z//?\$qq/\$q\}Zd
    325       \Z//?\$__ble_q/\$__ble_Q\}Zd
    326       \Z//?\$_ble_local_q/\$_ble_local_Q\}Zd
    327       \Z/\$\(\([^()]+\)\)\}Zd
    328       \Z/\$'\''([^\\]|\\.)+'\''\}Zd
    329 
    330       \Z\$\{[_a-zA-Z0-9]+//(ARR|DICT|PREFIX|NAME)/\$([_a-zA-Z0-9]+|\{[_a-zA-Z0-9#:-]+\})\}Zd
    331       \Z\$\{[_a-zA-Z0-9]+//'\''%[dlcxy]'\''/\$[_a-zA-Z0-9]+\}Zd # src/canvas.sh
    332 
    333       \Z#D1738Zd
    334       \Z\$\{_ble_edit_str//\$'\''\\n'\''/\$'\''\\n'\''"\$comment_begin"\}Zd # edit.sh
    335       g'
    336 
    337   grc --color '"[^"]*\$\{[[:alnum:]_]+(\[[^][]*\])?//?([^{}]|\{[^{}]*\})+/[^{}"'\'']*"[^"]*([&$]|\\)' --exclude=./test |
    338     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    339       \Z#D1751Zd
    340       g'
    341 }
    342 
    343 function sub:scan/gawk402bug-regex-check {
    344   echo "--- $FUNCNAME ---"
    345   grc --color '\[\^?\][^]]*\[:[^]]*:\].[^]]*\]' --exclude={test,ext,\*.md} | grep -Ev '#D1709 safe'
    346 }
    347 
    348 function sub:scan/assign {
    349   echo "--- $FUNCNAME ---"
    350   local command="$1"
    351   grc --color --exclude=./test --exclude=./memo '\$\([^()]' |
    352     grep -Ev "$rex_grep_head#|[[:space:]]#"
    353 }
    354 
    355 function sub:scan/memo-numbering {
    356   echo "--- $FUNCNAME ---"
    357 
    358   grep -ao '\[#D....\]' note.txt memo/done.txt | awk '
    359     function report_error(message) {
    360       printf("memo-numbering: \x1b[1;31m%s\x1b[m\n", message) > "/dev/stderr";
    361     }
    362     !/\[#D[0-9]{4}\]/ {
    363       report_error("invalid  number \"" $0 "\".");
    364       next;
    365     }
    366     {
    367       num = $0;
    368       gsub(/^\[#D0+|\]$/, "", num);
    369       if (prev != "" && num != prev - 1) {
    370         if (prev < num) {
    371           report_error("reverse ordering " num " has come after " prev ".");
    372         } else if (prev == num) {
    373           report_error("duplicate number " num ".");
    374         } else {
    375           for (i = prev - 1; i > num; i--) {
    376             report_error("memo-numbering: missing number " i ".");
    377           }
    378         }
    379       }
    380       prev = num;
    381     }
    382     END {
    383       if (prev != 1) {
    384         for (i = prev - 1; i >= 1; i--)
    385           report_error("memo-numbering: missing number " i ".");
    386       }
    387     }
    388   '
    389   cat note.txt memo/done.txt | sed -n '0,/^[[:space:]]\{1,\}Done/d;/  \* .*\[#D....\]$/d;/^  \* /p'
    390 }
    391 
    392 # 誤って ((${#arr[@]})) を ((${arr[@]})) などと書いてしまうミス。
    393 function sub:scan/array-count-in-arithmetic-expression {
    394   echo "--- $FUNCNAME ---"
    395   grc --exclude=./make_command.sh '\(\([^[:space:]]*\$\{[[:alnum:]_]+\[[@*]\]\}'
    396 }
    397 
    398 # unset 変数名 としていると誤って関数が消えることがある。
    399 function sub:scan/unset-variable {
    400   echo "--- $FUNCNAME ---"
    401   sub:scan/list-command unset --exclude-this |
    402     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    403       \Zunset[[:space:]]-[vf]Zd
    404       \Z^[[:space:]]*#Zd
    405       \Zunset _ble_init_(version|arg|exit|command)\bZd
    406       \Zbuiltins1=\(.* unset .*\)Zd
    407       \Zfunction unsetZd
    408       \Zreadonly -f unsetZd
    409       \Z'\''\(unset\)'\''Zd
    410       \Z"\$__ble_proc" "\$__ble_name" unsetZd
    411       \Zulimit umask unalias unset waitZd
    412       \ZThe variable will be unset initiallyZd
    413       g'
    414 }
    415 function sub:scan/eval-literal {
    416   echo "--- $FUNCNAME ---"
    417   sub:scan/grc-source 'builtin eval "\$' |
    418     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    419       \Zeval "(\$[[:alnum:]_]+)+(\[[^]["'\''\$`]+\])?\+?=Zd
    420       g'
    421 }
    422 
    423 function sub:scan/WA-localvar_inherit {
    424   echo "--- $FUNCNAME ---"
    425   grc 'local [^;&|()]*"\$\{[_a-zA-Z0-9]+\[@*\]\}"' |
    426     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    427       \Ztest_command='\''ble/bin/stty -echo -nl -icrnl -icanon "\$\{_ble_term_stty_flags_enter\[@]}" size'\''Zd
    428       g'
    429 }
    430 
    431 function sub:scan/mistake-_ble_bash {
    432   echo "--- $FUNCNAME ---"
    433   grc '\(\(.*\b_ble_base\b.*\)\)'
    434 }
    435 
    436 function sub:scan/command-layout {
    437   echo "--- $FUNCNAME ---"
    438   grc '/(enter-command-layout|\.insert-newline|\.newline)([[:space:]]|$)' --exclude=./{text,ext} --exclude=./make_command.sh --exclude=\*.md --color |
    439     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    440       \Z^[[:space:]]*#Zd
    441       \Z^[[:space:]]*function [^[:space:]]* \{$Zd
    442       \Z[: ]keep-infoZd
    443       \Z#D1800Zd
    444       g'
    445 }
    446 
    447 function sub:scan/word-splitting-number {
    448   echo "--- $FUNCNAME ---"
    449   # #D1835 一般には IFS に整数が含まれるている場合もあるので ${#...} や
    450   # $((...)) や >&$fd であってもちゃんと quote する必要がある。
    451   grc '[<>]&\$|([[:space:]]|=\()\$(\(\(|\{#|\?)' --exclude={docs,mwg_pp.awk,memo} |
    452     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    453       \Z^[^#]*(^|[[:space:]])#Zd
    454       \Z^([^"]|"[^\#]*")*"[^"]*([& (]\$)Zd
    455       \Z^[^][]*\[\[[^][]*([& (]\$)Zd
    456       \Z\(\([_a-zA-Z0-9]+=\(\$Zd
    457       \Z\$\{#[_a-zA-Z0-9]+\}[<>?&]Zd
    458       \Z \$\{\#[_a-zA-Z0-9]+\[@\]\} -gt 0 \]\]Zd
    459       \Zcase \$\? inZd
    460       \Zcase \$\(\(.*\)\) inZd
    461       g'
    462 }
    463 
    464 function sub:scan/check-readonly-unsafe {
    465   echo "--- $FUNCNAME ---"
    466   local rex_varname='\b(_[_a-zA-Z0-9]+|[_A-Z][_A-Z0-9]+)\b'
    467   grc -Wg,-n -Wg,--color=always -o "$rex_varname"'\+?=\b|(/assign|/assign-array|#split) '"$rex_varname"'| -v '"$rex_varname"' ' --exclude={memo,wiki,test,make,\*.md,make_command.sh,GNUmakefile} |
    468     sed -E 'h;s/'"$_make_rex_escseq"'//g
    469 
    470       # Exceptions in each file
    471       /^\.\/ble.pp:[0-9]*:BLEOPT=$/d
    472       /^\.\/lib\/core-complete.sh:[0-9]+:KEY=$/d
    473       /^\.\/lib\/core-syntax.sh:[0-9]+:VAR=$/d
    474       /^\.\/lib\/init-(cmap|term).sh:[0-9]+:TERM=$/d
    475       /^\.\/src\/edit.sh:[0-9]+:_dirty=$/d
    476       /^\.\/src\/history.sh:[0-9]+:_history_index=$/d
    477       /^\.\/src\/util.sh:[0-9]+:(ARRI|OPEN|TERM)=$/d
    478       /^\.\/lib\/core-cmdspec.sh:[0-9]+:OLD=$/d
    479 
    480       # (extract only variable names)
    481       s/^[^:]*:[0-9]+:[[:space:]]*//;
    482       s/^-v (.*) $/\1/;s/\+?=$//;s/^.+ //;
    483 
    484       # other frameworks & integrations
    485       /^__bp_blesh_invoking_through_blesh$/d
    486       /^BP_PROMPT_COMMAND_.*$/d
    487 
    488       # common variables
    489       /^__?ble[_a-zA-Z0-9]*$/d
    490       /^[A-Z]$/d
    491       /^BLE_[_A-Z0-9]*$/d
    492       /^ADVICE_[_A-Z0-9]*$/d
    493       /^COMP_[_A-Z0-9]*$/d
    494       /^COMPREPLY$/d
    495       /^READLINE_[_A-Z0-9]*$/d
    496       /^LC_[_A-Z0-9]*$/d
    497       /^LANG$/d
    498 
    499       # other uppercase variables that ble.sh is allowed to use.
    500       /^(FUNCNEST|IFS|IGNOREEOF|POSIXLY_CORRECT|TMOUT)$/d
    501       /^(PWD|OLDPWD|CDPATH)$/d
    502       /^(BASHPID|GLOBIGNORE|MAPFILE|REPLY)$/d
    503       /^INPUTRC$/d
    504       /^(LINES|COLUMNS)$/d
    505       /^HIST(CONTROL|IGNORE|SIZE|TIMEFORMAT)$/d
    506       /^(PROMPT_COMMAND|PS1)$/d
    507       /^(BASH_COMMAND|BASH_REMATCH|HISTCMD|LINENO|PIPESTATUS|TIMEFORMAT)$/d
    508       /^(BASH_XTRACEFD|PS4)$/d
    509       /^(CC|LESS|MANOPT|MANPAGER|PAGER|PATH|MANPATH)$/d
    510       /^(BUFF|KEYS|KEYMAP|WIDGET|LASTWIDGET|DRAW_BUFF)$/d
    511       /^(D(MIN|MAX|MAX0)|(HIGHLIGHT|PREV)_(BUFF|UMAX|UMIN)|LEVEL|LAYER_(UMAX|UMIN))$/d
    512       /^(HISTINDEX_NEXT|FILE|LINE|INDEX|INDEX_FILE)$/d
    513       /^(ARG|FLAG|REG)$/d
    514       /^(COMP[12SV]|ACTION|CAND|DATA|INSERT|PREFIX_LEN)$/d
    515       /^(PRETTY_NAME|NAME|VERSION)$/d
    516 
    517       # variables in awk/comments/etc
    518       /^AWKTYPE$/d
    519       /^FOO$/d
    520       g'
    521 }
    522 
    523 function sub:scan {
    524   if ! type grc >/dev/null; then
    525     echo 'blesh check: grc not found. grc can be found in github.com:akinomyoga/mshex.git/' >&2
    526     exit
    527   fi
    528 
    529   local esc=$_make_rex_escseq
    530   local rex_grep_head="^$esc[[:graph:]]+$esc:$esc[[:digit:]]*$esc:$esc"
    531 
    532   # builtin return break continue : eval echo unset は unset しているので大丈夫のはず
    533 
    534   #sub:scan/builtin 'history'
    535   sub:scan/builtin 'echo' --exclude=./lib/keymap.vi_test.sh --exclude=./ble.pp |
    536     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    537       \Z\bstty[[:space:]]+echoZd
    538       \Zecho \$PPIDZd
    539       \Zble/keymap:vi_test/check Zd
    540       \Zmandb-help=%'\''help echo'\''Zd
    541       \Zalias aaa4='\''echo'\''Zd
    542       g'
    543   #sub:scan/builtin '(compopt|type|printf)'
    544   sub:scan/builtin 'bind' |
    545     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    546       \Zinvalid bind typeZd
    547       \Zline = "bind"Zd
    548       \Z'\''  bindZd
    549       \Z\(bind\)    ble-bindZd
    550       \Z^alias bind cd command compgenZd
    551       \Zoutputs of the "bind" builtinZd
    552       \Zif ble/string#match "\$_ble_edit_str" '\''bindZd
    553       g'
    554   sub:scan/builtin 'read' |
    555     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    556       \ZDo not read Zd
    557       \Zfailed to read Zd
    558       \Zpushd read readonly set shoptZd
    559       g'
    560   sub:scan/builtin 'exit' |
    561     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    562       \Zble.pp.*return 1 2>/dev/null || exit 1Zd
    563       \Z^[-[:space:][:alnum:]_./:=$#*]+('\''[^'\'']*|"[^"()`]*|([[:space:]]|^)#.*)\bexit\bZd
    564       \Z\(exit\) ;;Zd
    565       \Zprint NR; exit;Zd;g'
    566   sub:scan/builtin 'eval' |
    567     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    568       \Z\('\''eval'\''\)Zd
    569       \Zbuiltins1=\(.* eval .*\)Zd
    570       \Z\^eval --Zd
    571       \Zt = "eval -- \$"Zd
    572       \Ztext = "eval -- \$'\''Zd
    573       \Zcmd '\''eval -- %q'\''Zd
    574       \Z\$\(eval \$\(call .*\)\)Zd
    575       \Z^[[:space:]]*local rex_[_a-zA-Z0-9]+='\''[^'\'']*'\''[[:space:]]*$Zd
    576       \ZLINENO=\$_ble_edit_LINENO evalZd
    577       \Z^ble/cmdspec/opts Zd
    578       g'
    579   sub:scan/builtin 'unset' |
    580     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    581       \Zunset _ble_init_(version|arg|exit|command)\bZd
    582       \Zreadonly -f unsetZd
    583       \Zunset -f builtinZd
    584       \Z'\''\(unset\)'\''Zd
    585       \Z"\$__ble_proc" "\$__ble_name" unsetZd
    586       \Zumask unalias unset wait$Zd
    587       \ZThe variable will be unset initiallyZd
    588       g'
    589   sub:scan/builtin 'unalias' |
    590     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    591       \Zbuiltins1=\(.* unalias .*\)Zd
    592       \Zumask unalias unset wait$Zd
    593       g'
    594 
    595   #sub:scan/assign
    596   sub:scan/builtin 'trap' |
    597     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    598       \Z_ble_trap_handler="trap -- '\''\$\{_ble_trap_handler//\$q/\$Q}'\'' \$nZd
    599       \Zline = "bind"Zd
    600       \Ztrap_command=["'\'']trap -- Zd
    601       \Z_ble_builtin_trap_handlers_reload\[sig\]="trap -- Zd
    602       \Zlocal trap$Zd
    603       \Z"trap -- '\''"Zd
    604       \Z\('\'' trap '\''\*Zd
    605       \Z\(trap \| ble/builtin/trap\) .*;;Zd
    606       \Zble/function#trace trap Zd
    607       \Z# EXIT trapZd
    608       \Zread readonly set shopt trapZd
    609       \Zble/util/print "custom trap"Zd
    610       g'
    611 
    612   sub:scan/builtin 'readonly' |
    613     sed -E 'h;s/'"$_make_rex_escseq"'//g;s/^[^:]*:[0-9]+:[[:space:]]*//
    614       \Z^[[:space:]]*#Zd
    615       \ZWA readonlyZd
    616       \Z\('\''declare'\''(\|'\''[a-z]+'\'')+\)Zd
    617       \Z readonly was blocked\.Zd
    618       \Z\[\[ \$\{FUNCNAME\[i]} == \*readonly ]]Zd
    619       \Zread readonly set shopt trapZd
    620       g'
    621 
    622   sub:scan/a.txt
    623   sub:scan/check-todo-mark
    624   sub:scan/bash300bug
    625   sub:scan/bash301bug-array-element-length
    626   sub:scan/bash400bug
    627   sub:scan/bash401-histexpand-bgpid
    628   sub:scan/bash404-no-argument-return
    629   sub:scan/bash501-arith-base
    630   sub:scan/bash502-patsub_replacement
    631   sub:scan/gawk402bug-regex-check
    632   sub:scan/array-count-in-arithmetic-expression
    633   sub:scan/unset-variable
    634   sub:scan/eval-literal
    635   sub:scan/WA-localvar_inherit
    636   sub:scan/mistake-_ble_bash
    637   sub:scan/command-layout
    638   sub:scan/word-splitting-number
    639   sub:scan/check-readonly-unsafe
    640 
    641   sub:scan/memo-numbering
    642 }
    643 
    644 function sub:show-contrib/canonicalize {
    645   sed 's/, /\n/g;s/ and /\n/g' | sed 's/[[:space:]]/_/g' | LANG=C sort
    646 }
    647 function sub:show-contrib/count {
    648   LANG=C sort | uniq -c | LANG=C sort -rnk1 |
    649     awk 'function xflush() {if(c!=""){printf("%4d %s\n",c,n);}} {if($1!=c){xflush();c=$1;n=$2}else{n=n", "$2;}}END{xflush()}' |
    650     ifold -w 131 -s --indent=' +[0-9] +'
    651 }
    652 function sub:show-contrib {
    653   local cache_contrib_github=out/contrib-github.txt
    654   if [[ ! ( $cache_contrib_github -nt .git/refs/remotes/origin/master ) ]]; then
    655     {
    656       wget 'https://api.github.com/repos/akinomyoga/ble.sh/issues?state=all&per_page=100&pulls=true' -O -
    657       wget 'https://api.github.com/repos/akinomyoga/ble.sh/issues?state=all&per_page=100&pulls=true&page=2' -O -
    658       wget 'https://api.github.com/repos/akinomyoga/blesh-contrib/issues?state=all&per_page=100&pulls=true' -O -
    659     } |
    660       sed -n 's/^[[:space:]]*"login": "\(.*\)",$/\1/p' |
    661       sub:show-contrib/canonicalize > "$cache_contrib_github"
    662   fi
    663 
    664   echo "Contributions (from GitHub Issues/PRs)"
    665   < "$cache_contrib_github" sub:show-contrib/count
    666 
    667   echo "Contributions (from memo.txt)"
    668   sed -En 's/^  \* .*\([^()]+ by ([^()]+)\).*/\1/p' memo/done.txt note.txt | sub:show-contrib/canonicalize | sub:show-contrib/count
    669 
    670   echo "Contributions (from ChangeLog.md)"
    671   sed -n 's/.*([^()]* by \([^()]*\)).*/\1/p' docs/ChangeLog.md | sub:show-contrib/canonicalize | sub:show-contrib/count
    672 
    673   echo "Σ: Issues/PRs + max(memo.txt,ChangeLog)"
    674 
    675   LANG=C join -j 2 -e 0 \
    676       <(sed -En 's/^  \* .*\([^()]+ by ([^()]+)\).*/\1/p' memo/done.txt note.txt | sub:show-contrib/canonicalize | uniq -c | LANG=C sort -k2) \
    677       <(sed -n 's/.*([^()]* by \([^()]*\)).*/\1/p' docs/ChangeLog.md | sub:show-contrib/canonicalize | uniq -c | LANG=C sort -k2) |
    678     LANG=C join -e 0 -1 1 - -2 2 <(uniq -c "$cache_contrib_github" | LANG=C sort -k2) |
    679     awk 'function max(x,y){return x<y?y:x;}{printf("%4d %s\n",max($2,$3)+$4,$1)}' |
    680     sort -rnk1 |
    681     awk 'function xflush() {if(c!=""){printf("%4d %s\n",c,n);}} {if($1!=c){xflush();c=$1;n=$2}else{n=n", "$2;}}END{xflush()}' |
    682     ifold -w 131 -s --indent=' +[0-9] +'
    683   echo
    684 }
    685 
    686 #------------------------------------------------------------------------------
    687 # sub:release-note
    688 #
    689 # 使い方
    690 # ./make_command.sh release-note v0.3.2..v0.3.3
    691 
    692 function sub:release-note/help {
    693   printf '  release-note v0.3.2..v0.3.3 [--changelog CHANGELOG]\n'
    694 }
    695 
    696 function sub:release-note/read-arguments {
    697   flags=
    698   fname_changelog=memo/ChangeLog.md
    699   while (($#)); do
    700     local arg=$1; shift 1
    701     case $arg in
    702     (--changelog)
    703       if (($#)); then
    704         fname_changelog=$1; shift
    705       else
    706         flags=E$flags
    707         echo "release-note: missing option argument for '$arg'." >&2
    708       fi ;;
    709     esac
    710   done
    711 }
    712 
    713 function sub:release-note/.find-commit-pairs {
    714   {
    715     echo __MODE_HEAD__
    716     git log --format=format:'%h%s' --date-order --abbrev-commit "$1"; echo
    717     echo __MODE_MASTER__
    718     git log --format=format:'%h%s' --date-order --abbrev-commit master; echo
    719   } | awk -F '' '
    720     /^__MODE_HEAD__$/ {
    721       mode = "head";
    722       nlist = 0;
    723       next;
    724     }
    725     /^__MODE_MASTER__$/ { mode = "master"; next; }
    726 
    727     function reduce_title(str) {
    728       str = $2;
    729       #if (match(str, /^.*\[(originally: )?(.+: .+)\]$/, m)) str = m[2];
    730       gsub(/["`]/, "", str);
    731       #print str >"/dev/stderr";
    732       return str;
    733     }
    734 
    735     mode == "head" {
    736       i = nlist++;
    737       titles[i] = $2;
    738       commit_head[i] = $1;
    739       title2index[reduce_title($2)] = i;
    740     }
    741     mode == "master" && (i = title2index[reduce_title($2)]) != "" && commit_master[i] == "" {
    742       commit_master[i] = $1;
    743     }
    744 
    745     END {
    746       for (i = 0; i < nlist; i++) {
    747         print commit_head[i] ":" commit_master[i] ":" titles[i];
    748       }
    749     }
    750   '
    751 }
    752 
    753 function sub:release-note {
    754   local flags fname_changelog
    755   sub:release-note/read-arguments "$@"
    756 
    757   ## @arr commits
    758   ##   この配列は after:before の形式の要素を持つ。
    759   ##   但し after は前の version から release までに加えられた変更の commit である。
    760   ##   そして before は after に対応する master における commit である。
    761   local -a commits
    762   IFS=$'\n' eval 'commits=($(sub:release-note/.find-commit-pairs "$@"))'
    763 
    764   local commit_pair
    765   for commit_pair in "${commits[@]}"; do
    766     local hash=${commit_pair%%:*}
    767     commit_pair=${commit_pair:${#hash}+1}
    768     local hash_base=${commit_pair%%:*}
    769     local title=${commit_pair#*:}
    770 
    771     local rex_hash_base=$hash_base
    772     if ((${#hash_base} == 7)); then
    773       rex_hash_base=$hash_base[0-9a-f]?
    774     elif ((${#hash_base} == 8)); then
    775       rex_hash_base=$hash_base?
    776     fi
    777 
    778     local result=
    779     [[ $hash_base ]] && result=$(awk '
    780         sub(/^##+ +/, "") { heading = "[" $0 "] "; next; }
    781         sub(/\y'"$rex_hash_base"'\y/, "'"$hash (master: $hash_base)"'") {print heading $0;}
    782       ' "$fname_changelog")
    783     if [[ $result ]]; then
    784       echo "$result"
    785     elif [[ $title ]]; then
    786       echo "- $title $hash (master: ${hash_base:-N/A}) ■NOT-FOUND■"
    787     else
    788       echo "■not found $hash"
    789     fi
    790   done | tac
    791 }
    792 
    793 # 以下の様な形式のファイルをセクション毎に分けて出力します。
    794 #
    795 # [Fixes] - foo bar
    796 # [New features] - foo bar
    797 # [Fixes] - foo bar
    798 # [Fixes] - foo bar
    799 # ...
    800 function sub:release-note-sort {
    801   local file=$1
    802   awk '
    803     match($0, /\[[^][]+\]/) {
    804       key = substr($0, 1, RLENGTH);
    805       gsub(/^\[|]$/, "", key);
    806 
    807       line = substr($0, RLENGTH + 1);
    808       gsub(/^[[:space:]]+|[[:space:]]+$/, "", line);
    809       if (line == "") next;
    810       if (line !~ /^- /) line = "- " line;
    811 
    812       if (sect[key] == "")
    813         keys[nkey++] = key;
    814       sect[key] = sect[key] line "\n"
    815       next;
    816     }
    817     {print}
    818 
    819     END {
    820       for (i=0;i<nkey;i++) {
    821         key = keys[i];
    822         print "## " key;
    823         print sect[key];
    824       }
    825     }
    826   ' "$file"
    827 }
    828 
    829 #------------------------------------------------------------------------------
    830 
    831 function sub:list-functions/help {
    832   printf '  list-functions [-p] files...\n'
    833 }
    834 function sub:list-functions {
    835   local -a files; files=()
    836   local opt_literal=
    837   local i=0 N=$# args; args=("$@")
    838   while ((i<N)); do
    839     local arg=${args[i++]}
    840     if [[ ! $opt_literal && $arg == -* ]]; then
    841       if [[ $arg == -- ]]; then
    842         opt_literal=1
    843       elif [[ $arg == --* ]]; then
    844         printf 'list-functions: unknown option "%s"\n' "$arg" >&2
    845         opt_error=1
    846       elif [[ $arg == -* ]]; then
    847         local j
    848         for ((j=1;j<${#arg};j++)); do
    849           local o=${arg:j:1}
    850           case $o in
    851           (p) opt_public=1 ;;
    852           (*) printf 'list-functions: unknown option "-%c"\n' "$o" >&2
    853               opt_error=1 ;;
    854           esac
    855         done
    856       fi
    857     else
    858       files+=("$arg")
    859     fi
    860   done
    861 
    862   if ((${#files[@]}==0)); then
    863     files=($(find out -name \*.sh -o -name \*.bash))
    864   fi
    865 
    866   if [[ $opt_public ]]; then
    867     local rex_function_name='[^[:space:]()/]*'
    868   else
    869     local rex_function_name='[^[:space:]()]*'
    870   fi
    871   sed -n 's/^[[:space:]]*function \('"$rex_function_name"'\)[[:space:]].*/\1/p' "${files[@]}" | sort -u
    872 }
    873 
    874 function sub:first-defined {
    875   local name dir
    876   for name; do
    877     for dir in ../ble-0.{1..3} ../ble.sh; do
    878       (cd "$dir"; grc "$name" &>/dev/null) || continue
    879       echo "$name $dir"
    880       return 0
    881     done
    882   done
    883   echo "$name not found"
    884   return 1
    885 }
    886 function sub:first-defined/help {
    887   printf '  first-defined ERE...\n'
    888 }
    889 
    890 #------------------------------------------------------------------------------
    891 
    892 function sub:scan-words {
    893   # sed -E "s/'[^']*'//g;s/(^| )[[:space:]]*#.*/ /g" $(findsrc --exclude={wiki,test,\*.md}) |
    894   #   grep -hoE '\$\{?[_a-zA-Z][_a-zA-Z0-9]*\b|\b[_a-zA-Z][-:._/a-zA-Z0-9]*\b' |
    895   #   sed -E 's/^\$\{?//g;s.^ble/widget/..;\./.!d;/:/d' |
    896   #   sort | uniq -c | sort -n
    897   sed -E "s/(^| )[[:space:]]*#.*/ /g" $(findsrc --exclude={memo,wiki,test,\*.md}) |
    898     grep -hoE '\b[_a-zA-Z][_a-zA-Z0-9]{3,}\b' |
    899     sed -E 's/^bleopt_//' |
    900     sort | uniq -c | sort -n | less
    901 }
    902 function sub:scan-varnames {
    903   sed -E "s/(^| )[[:space:]]*#.*/ /g" $(findsrc --exclude={wiki,test,\*.md}) |
    904     grep -hoE '\$\{?[_a-zA-Z][_a-zA-Z0-9]*\b|\b[_a-zA-Z][_a-zA-Z0-9]*=' |
    905     sed -E 's/^\$\{?(.*)/\1$/g;s/[$=]//' |
    906     sort | uniq -c | sort -n | less
    907 }
    908 
    909 function sub:check-dependency/identify-funcdef {
    910   local funcname=$1
    911   grep -En "\bfunction $funcname +\{" ble.pp src/*.sh | awk -F : -v funcname="$funcname" '
    912     {
    913       if ($1 == "ble.pp") {
    914         if (funcname ~ /^ble\/util\/assign$|^ble\/bin\/grep$/) next;
    915         if (funcname == "ble/util/print" && $2 < 30) next;
    916       } else if ($1 == "src/benchmark.sh") {
    917         if (funcname ~ /^ble\/util\/(unlocal|print|print-lines)$/) next;
    918       }
    919       print $1 ":" $2;
    920       exit
    921     }
    922   '
    923 }
    924 
    925 function sub:check-dependency {
    926   local file=$1
    927   grep -Eo '\bble(hook|opt|-[[:alnum:]]+)?/[^();&|[:space:]'\''"]+' "$file" | sort -u |
    928     grep -Fvx "$(grep -Eo '\bfunction [^();&|[:space:]'\''"]+ +\{' "$file" | sed -E 's/^function | +\{$//g' | sort -u)" |
    929     while read -r funcname; do
    930       location=$(sub:check-dependency/identify-funcdef "$funcname")
    931       echo "${location:-unknown:0}:$funcname"
    932     done | sort -t : -Vk 1,2 | less -FSXR
    933 }
    934 
    935 #------------------------------------------------------------------------------
    936 # sub:check-readline-bindable
    937 
    938 function sub:check-readline-bindable {
    939   join -v1 <(
    940     for bash in bash $(compgen -c -- bash-); do
    941       [[ $bash == bash-[12]* ]] && continue
    942       "$bash" -c 'bind -l' 2>/dev/null
    943     done | sort -u
    944   ) <(sort lib/core-decode.emacs-rlfunc.txt)
    945 }
    946 
    947 #------------------------------------------------------------------------------
    948 
    949 if (($#==0)); then
    950   sub:help
    951 elif declare -f sub:"$1" &>/dev/null; then
    952   sub:"$@"
    953 else
    954   echo "unknown subcommand '$1'" >&2
    955   builtin exit 1
    956 fi