Skip to content

Commit

Permalink
global: work around case-interleaving collation
Browse files Browse the repository at this point in the history
  • Loading branch information
akinomyoga committed Dec 23, 2023
1 parent 0fdbe3b commit a3b94bb
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 45 deletions.
2 changes: 1 addition & 1 deletion ble.pp
Original file line number Diff line number Diff line change
Expand Up @@ -2128,7 +2128,7 @@ function ble/dispatch {
(version|--version) ble/util/print "ble.sh, version $BLE_VERSION (noarch)" ;;
(check|--test) ble/base/sub:test "$@" ;;
(*)
if ble/string#match "$cmd" '^[-a-z0-9]+$' && ble/is-function "ble-$cmd"; then
if ble/string#match "$cmd" '^[-a-zA-Z0-9]+$' && ble/is-function "ble-$cmd"; then
"ble-$cmd" "$@"
elif ble/is-function ble/bin/ble; then
ble/bin/ble "$cmd" "$@"
Expand Down
1 change: 1 addition & 0 deletions docs/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- term: add workarounds for `eterm` `#D2087` a643f0ea
- global: adjust bash options for utilities outside the ble context (motivated by jkemp814) `#D2092` 6b144de7
- decode,syntax: quote `$#` in arguments properly `#D2097` 40a625d3
- global: work around case-interleaving collation (reported by dongxi8) `#D2103` xxxxxxxx

## Contrib

Expand Down
9 changes: 8 additions & 1 deletion lib/core-complete.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6993,6 +6993,13 @@ function ble/complete/insert-braces/.compose {
return substr(str, 1, length(head)) == head;
}
# Note: value ~ /[[:lower:]]/ cannot be used in mawk. value ~ /[a-z]/ may
# match uppercase characters in some strange locales, e.g., en_US.UTF-8 in
# Ubuntu 16.04 LTS.
function islower(s) {
return s == tolower(s);
}
BEGIN {
RS = '"$char_RS"';
rex_atom = ENVIRON["rex_atom"];
Expand Down Expand Up @@ -7104,7 +7111,7 @@ function ble/complete/insert-braces/.compose {
for (i = 0; i < ikey; i++) {
while (dict[value = keys[i]]--) {
if (value ~ /^([a-zA-Z])$/) {
alpha = (value ~ /^[a-z]$/) ? lower : upper;
alpha = islower(value) ? lower : upper;
beg = end = value;
b = e = index(alpha, value);
while (b > 1 && dict[tmp = substr(alpha, b - 1, 1)]) {
Expand Down
4 changes: 2 additions & 2 deletions lib/core-syntax.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1648,7 +1648,7 @@ function ble/syntax:bash/simple-word/detect-separated-path {
[[ $word ]] || return 1

local rex_url='^[a-z]+://'
[[ :$opts: == *:url:* && $word =~ $rex_url ]] && return 1
[[ :$opts: == *:url:* ]] && ble/string#match-safe "$word" "$rex_url" && return 1

# read eval options
local eval_opts=$opts notilde=
Expand Down Expand Up @@ -1695,7 +1695,7 @@ function ble/syntax:bash/simple-word/locate-filename/.exists {
[[ $path == // ]]
else
[[ -e $path || -h $path ]]
fi || [[ :$opts: == *:url:* && $path =~ $rex_url ]]
fi || [[ :$opts: == *:url:* ]] && ble/string#match-safe "$path" "$rex_url"
}
## @fn ble/syntax:bash/simple-word/locate-filename word [sep] [opts]
## @param[in] word
Expand Down
4 changes: 2 additions & 2 deletions lib/keymap.vi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ function ble/keymap:vi/string#encode-rot13 {
local -a buff=() ch
for ((i=0;i<${#text};i++)); do
ch=${text:i:1}
if [[ $ch == [A-Z] ]]; then
if ble/string#isupper "$ch"; then
ch=${_ble_util_string_upper_list%%"$ch"*}
ch=${_ble_util_string_upper_list:(${#ch}+13)%26:1}
elif [[ $ch == [a-z] ]]; then
elif ble/string#islower "$ch"; then
ch=${_ble_util_string_lower_list%%"$ch"*}
ch=${_ble_util_string_lower_list:(${#ch}+13)%26:1}
fi
Expand Down
30 changes: 16 additions & 14 deletions lib/test-util.sh
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,8 @@ function is-global { (builtin readonly "$1"; ! local "$1" 2>/dev/null); }
ble/test code:'ret=xyz:xyz:xyz; '$cmd' ret xyz' ret=
done

export LC_ALL= LC_COLLATE=C 2>/dev/null # suppress locale error #D1440

ble/test code:'ret=a; ble/path#remove ret \?' ret=a
ble/test code:'ret=aa; ble/path#remove ret \?' ret=aa
ble/test code:'ret=a:b; ble/path#remove ret \?' ret=a:b
Expand Down Expand Up @@ -1007,7 +1009,7 @@ function is-global { (builtin readonly "$1"; ! local "$1" 2>/dev/null); }
$Dict#set $dict banana yellow
$Dict#set $dict orange orange
$Dict#set $dict melon green

ret=unchanged
ble/test $Dict'#has '$dict' banana' ret=unchanged # 先頭
ble/test $Dict'#has '$dict' apple' ret=unchanged #
Expand All @@ -1017,7 +1019,7 @@ function is-global { (builtin readonly "$1"; ! local "$1" 2>/dev/null); }
ble/test $Dict'#get '$dict' apple' ret=red #
ble/test $Dict'#get '$dict' melon' ret=green # 末尾
ble/test '! '$Dict'#get '$dict' pear' ret= # 存在しない項目

# 空白類
ble/test '! '$Dict'#has '$dict' ""' # 末尾空要素で引けるか
ble/test '! '$Dict'#get '$dict' ""' # 末尾空要素で引けるか
Expand All @@ -1034,7 +1036,7 @@ function is-global { (builtin readonly "$1"; ! local "$1" 2>/dev/null); }
ble/test $Dict'#has '$dict' apple' # 既存項目を破壊していないか
ble/test $Dict'#get '$dict' " apple "' ret=' red ' # 空白で trim されないか
ble/test $Dict'#get '$dict' apple' ret=red # 既存項目を破壊していないか

# FS, colon
ble/test '! '$Dict'#has '$dict' "${_ble_term_FS}"' # 単一FS
ble/test '! '$Dict'#has '$dict' ":"' # 単一コロン
Expand All @@ -1056,7 +1058,7 @@ function is-global { (builtin readonly "$1"; ! local "$1" 2>/dev/null); }
ble/test $Dict'#get '$dict' ":"' ret=Colon # 単一コロン
ble/test $Dict'#get '$dict' "apple${_ble_term_FS}banana"' ret=RedYellow # FSを含む見出し
ble/test $Dict'#get '$dict' apple:banana' ret=__red_yellow__ # コロンを含む見出し

# unset
$Dict#unset $dict banana
$Dict#unset $dict apple
Expand Down Expand Up @@ -1372,7 +1374,7 @@ function is-global { (builtin readonly "$1"; ! local "$1" 2>/dev/null); }
ble/util/print [; ble/function#advice/do; ble/util/print ]
ADVICE_EXIT=99'
ble/test f1 stdout={A,[,'original quick',],B} exit=99

ble/function#advice remove f1
ble/test f1 stdout='original' exit=0
ble/test 'f1 1' stdout='original 1' exit=0
Expand Down Expand Up @@ -2048,16 +2050,16 @@ fi
ble/test "ble/util/chars2keyseq 98 $char 99" ret="b${keyseq}c"
ble/test "ble/util/keyseq2chars 'b${keyseq}c'; ret=\"\${ret[*]}\"" ret="98 ${3:-$char} 99"
}
check1 '7' '\a'
check1 '8' '\b'
check1 '9' '\t'
check1 '10' '\n'
check1 '11' '\v'
check1 '12' '\f'
check1 '13' '\r'
check1 '27' '\e'
check1 '7' '\a'
check1 '8' '\b'
check1 '9' '\t'
check1 '10' '\n'
check1 '11' '\v'
check1 '12' '\f'
check1 '13' '\r'
check1 '27' '\e'
check1 '127' '\d'
check1 '92' '\\'
check1 '92' '\\'
check1 '28' '\x1c' # workaround bashbug \C-\, \C-\\
check1 '156' '\x9c' # workaround bashbug \C-\, \C-\\

Expand Down
1 change: 1 addition & 0 deletions make_command.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

umask 022
shopt -s nullglob
LC_ALL= LC_COLLATE=C

function mkd {
[[ -d $1 ]] || mkdir -p "$1"
Expand Down
113 changes: 113 additions & 0 deletions note.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7063,6 +7063,119 @@ bash_tips
Done (実装ログ)
-------------------------------------------------------------------------------

2023-12-22

* util(readonly): work around non-standard collation order (reported by dongxi8) [#D2103]
https://github.com/akinomyoga/ble.sh/issues/335#issuecomment-1598485650

恐らく中国語の collation order は aAbBcC 的な何かになっている。なので小文字
である事を判定するのに [a-z] とすると失敗する。

他に類似の物があるだろうかと思ったが多くの場合には [a-zA-Z] となっているの
で即座に問題は置きない。但し、diacritical marks のついているアルファベット
が混ざっている場合は多くあってそれが混入すると困る場合は実は結構ある。そう
いう場合には対応しきれていない気がする。

以下は既に対策済みだった物。よく考えた見たら _ble_util_string_lower_list 等
の変数に全てのアルファベットを格納しているのだから単にそれを使って判定すれ
ば良い。26文字の一文字ずつについて判定するのが大変に思うかもしれないが、
locale の切り替えの方が格段に重いので、寧ろその様にした方が高速である。関数
として ble/string#isupper, ble/string#islower を用意する事にした。指定した
引数が一文字かつ大文字または小文字であるかを判定する。

./src/util.sh:928:[[ $ch == [A-Z] ]]
./src/util.sh:931:[[ $ch == [a-z] ]]
./src/util.sh:955:[[ $ch == [A-Z] ]]
./src/util.sh:969:[[ $ch == [a-z] ]]

これは encode-rot13 での判定である。そして上と同様なので、単に
ble/string#isupper もしくは ble/string#islower を使えば良い。

./lib/keymap.vi.sh:93:[[ $ch == [a-z] ]]

これも ble/string#isupper を用いれば良い。

./src/edit.sh:745:[[ $c == [A-Z] ]]

以下はそれぞれ a または A を除くアルファベットという物だった。範囲の指定の
仕方を工夫して予期せぬ一致を回避する事にした。範囲指定を修正した。但し、
LC_COLLATE を調整しない限りは diacritical mark のついている変な物も原理上範
囲に含まれてしまうが、そもそも一致対象の文字列は declare -p の出力なので変
な文字が含まれている可能性は排除して良い。

./src/util.sh:515:[b-zA-Z]
./src/util.sh:523:[a-zB-Z]

これはそもそも使用文脈が限られているので小文字しかチェックしていなかったが
大文字が混入しても良いもの。ややこしいので A-Z0-9 も許容する事にする。

./src/util.sh:3403:[12])(-[a-z]+)?$'; [[ $msleep_type =~ $rex ]]
./ble.pp:2131:[-a-z0-9]

これは今回の問題箇所。外側で LC_COLLATE を調整する事で対策した。

./src/util.sh:7194:[[ $1 != *[a-z]* ]]

これらは blehook において lowercase letter が含まれる hook 名は private
hook として既定で除外する機能の為に使われている物である。blehook でも
LC_COLLATE の調整を行う事にした。

./src/util.hook.sh:140:[[ $pat == *[a-z]* || $flags == *a* ]]
./src/util.hook.sh:141:[a-z]
./src/util.hook.sh:164:[[ $pat == *[a-z]* || $flags == *a* ]]
./src/util.hook.sh:165:[a-z]
./src/util.hook.sh:209:[[ $flags == *a* ]] || ble/array#remove-by-glob print '_ble_hook_h_*[a-z]

これは awk スクリプトに含まれる物だった。他の方法で小文字判定をする事にした。

./lib/core-complete.sh:7115:[a-z]

これは https:// 等の URL 先頭のスキームを検出するのに使っている。大文字を含
んでいる物も一致させると C: 等の様な物も一致してしまって不都合である。なの
で、小文字に限定したい。然し毎回 LC_COLLATE 等を調整するのは面倒なので
ble/string#match-safe という関数を新しく追加する事にした。

./lib/core-syntax.sh:1661:[a-z]
./lib/core-syntax.sh:1729:[a-z]
./lib/core-syntax.sh:1754:[a-z]

これは簡易判定に用いている物であって後でまたちゃんと判定し直しているので気
にしなくて良い。

./lib/core-syntax.sh:3530:[a-z]+|^\[\[?|^[{}!]'; [[ $tail =~ $rex ]]

make_command.sh に関してはスクリプト全体で LC_COLLATE=C を設定すれば良い。

./make_command.sh:235:[/[:space:]<>"'\''][a-z]\.txt|/dev/(pts/|pty)[0-9]
./make_command.sh:303:[A-Z]
./make_command.sh:491:[A-Z]
./make_command.sh:492:[_A-Z0-9]
./make_command.sh:493:[_A-Z0-9]
./make_command.sh:494:[_A-Z0-9]
./make_command.sh:496:[_A-Z0-9]
./make_command.sh:497:[_A-Z0-9]
./make_command.sh:617:[a-z]

実験用スクリプトについては気にしない

./memo/D1341.locale-and-casematch.sh:7:[[ A == [a-z] ]]
./memo/D1341.locale-and-casematch.sh:27:[[ A == [a-z] ]]
./memo/D1760.fzf-completion-reduced.sh:10:[[a-z0-9.,:-]
./memo/D1760.fzf-completion-reduced.sh:27:[[a-z0-9.,:-]
./memo/benchmark/array-is-array.sh:84:[b-zA-Z]

これは LC_COLLATE=C を設定する事にした。subshell なので解除しなくて良い。設
定する時にはコマンドを何も指定せずに変数代入だけで LC_COLLATE=C 2>/dev/null
としてもエラーを抑制できない。代わりに export LC_COLLATE=C という形で指定す
る事にした。

./lib/test-util.sh:920:[a-zX-Z]
./lib/test-util.sh:921:[a-zX-Z]
./lib/test-util.sh:922:[a-zX-Z]
./lib/test-util.sh:929:[a-zX-Z]
./lib/test-util.sh:930:[a-zX-Z]
./lib/test-util.sh:931:[a-zX-Z]

2023-12-17

* 2023-12-11 ble-import に関する説明を書く? (motivated by Dominiquini) [#D2102]
Expand Down
2 changes: 1 addition & 1 deletion src/edit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ function ble/prompt/initialize {
if [[ $WINDIR == [a-zA-Z]:\\* ]]; then
local bsl='\' sl=/
local c=${WINDIR::1} path=${WINDIR:3}
if [[ $c == [A-Z] ]]; then
if ble/string#isupper "$c"; then
if ((_ble_bash>=40000)); then
c=${c,?}
else
Expand Down
3 changes: 3 additions & 0 deletions src/util.hook.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ function blehook {
local set shopt
ble/base/adjust-BASH_REMATCH
ble/base/.adjust-bash-options set shopt
local LC_ALL= LC_COLLATE=C 2>/dev/null # suppress locale error #D1440

local flags print process
local rex1='^([_a-zA-Z@][_a-zA-Z0-9@]*)$'
Expand All @@ -199,6 +200,7 @@ function blehook {
blehook/.print-help
fi
[[ $flags != *E* ]]; local ext=$?
ble/util/unlocal LC_ALL LC_COLLATE 2>/dev/null # suppress locale error #D1440
ble/base/.restore-bash-options set shopt
ble/base/restore-BASH_REMATCH
return "$ext"
Expand Down Expand Up @@ -248,6 +250,7 @@ function blehook {
blehook/.print "${print[@]}"
fi

ble/util/unlocal LC_ALL LC_COLLATE 2>/dev/null # suppress locale error #D1440
ble/base/.restore-bash-options set shopt
ble/base/restore-BASH_REMATCH
return "$ext"
Expand Down
Loading

0 comments on commit a3b94bb

Please sign in to comment.