Skip to content

Commit 043c2a5

Browse files
authored
Merge pull request #97 from bashly-framework/fix/escaping
Fix completion escaping
2 parents 6cfc405 + bf4fb1f commit 043c2a5

File tree

15 files changed

+236
-233
lines changed

15 files changed

+236
-233
lines changed

lib/completely/pattern.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,16 @@ def compgen
5454
def compgen!
5555
result = []
5656
result << actions.join(' ').to_s if actions.any?
57-
result << %[-W "$(#{function_name} "#{words.join ' '}")"] if words.any?
57+
result << %[-W "$(#{function_name} #{quoted_words.join ' '})"] if words.any?
5858
result.any? ? result.join(' ') : nil
5959
end
60+
61+
def quoted_words
62+
@quoted_words ||= words.map { |word| %("#{escape_for_double_quotes word}") }
63+
end
64+
65+
def escape_for_double_quotes(word)
66+
word.gsub(/["\\]/, '\\\\\&')
67+
end
6068
end
6169
end

lib/completely/templates/template.erb

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,37 @@
55
# Modifying it manually is not recommended
66

77
<%= function_name %>_filter() {
8-
local words="$1"
8+
local words=("$@")
99
local cur=${COMP_WORDS[COMP_CWORD]}
1010
local result=()
11+
local want_options=0
1112

1213
# words the user already typed (excluding the command itself)
1314
local used=()
1415
if ((COMP_CWORD > 1)); then
1516
used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")
1617
fi
1718

18-
if [[ "${cur:0:1}" == "-" ]]; then
19-
# Completing an option: offer everything (including options)
20-
echo "$words"
21-
22-
else
23-
# Completing a non-option: offer only non-options,
24-
# and don't re-offer ones already used earlier in the line.
25-
for word in $words; do
19+
# Completing an option: offer everything.
20+
# Completing a non-option: drop options and already-used words.
21+
[[ "${cur:0:1}" == "-" ]] && want_options=1
22+
for word in "${words[@]}"; do
23+
if ((!want_options)); then
2624
[[ "${word:0:1}" == "-" ]] && continue
2725

28-
local seen=0
2926
for u in "${used[@]}"; do
3027
if [[ "$u" == "$word" ]]; then
31-
seen=1
32-
break
28+
continue 2
3329
fi
3430
done
35-
((!seen)) && result+=("$word")
36-
done
31+
fi
3732

38-
echo "${result[*]}"
39-
fi
33+
# compgen -W expects shell-escaped words in one space-delimited string.
34+
printf -v word '%q' "$word"
35+
result+=("$word")
36+
done
37+
38+
echo "${result[*]}"
4039
}
4140

4241
<%= function_name %>() {

spec/approvals/cli/generated-script

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,37 @@
55
# Modifying it manually is not recommended
66

77
_mygit_completions_filter() {
8-
local words="$1"
8+
local words=("$@")
99
local cur=${COMP_WORDS[COMP_CWORD]}
1010
local result=()
11+
local want_options=0
1112

1213
# words the user already typed (excluding the command itself)
1314
local used=()
1415
if ((COMP_CWORD > 1)); then
1516
used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")
1617
fi
1718

18-
if [[ "${cur:0:1}" == "-" ]]; then
19-
# Completing an option: offer everything (including options)
20-
echo "$words"
21-
22-
else
23-
# Completing a non-option: offer only non-options,
24-
# and don't re-offer ones already used earlier in the line.
25-
for word in $words; do
19+
# Completing an option: offer everything.
20+
# Completing a non-option: drop options and already-used words.
21+
[[ "${cur:0:1}" == "-" ]] && want_options=1
22+
for word in "${words[@]}"; do
23+
if ((!want_options)); then
2624
[[ "${word:0:1}" == "-" ]] && continue
2725

28-
local seen=0
2926
for u in "${used[@]}"; do
3027
if [[ "$u" == "$word" ]]; then
31-
seen=1
32-
break
28+
continue 2
3329
fi
3430
done
35-
((!seen)) && result+=("$word")
36-
done
31+
fi
3732

38-
echo "${result[*]}"
39-
fi
33+
# compgen -W expects shell-escaped words in one space-delimited string.
34+
printf -v word '%q' "$word"
35+
result+=("$word")
36+
done
37+
38+
echo "${result[*]}"
4039
}
4140

4241
_mygit_completions() {
@@ -59,15 +58,15 @@ _mygit_completions() {
5958
;;
6059

6160
'status'*)
62-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur")
61+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
6362
;;
6463

6564
'init'*)
6665
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
6766
;;
6867

6968
*)
70-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur")
69+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
7170
;;
7271

7372
esac

spec/approvals/cli/generated-script-alt

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,38 +5,37 @@
55
# Modifying it manually is not recommended
66

77
_mycomps_filter() {
8-
local words="$1"
8+
local words=("$@")
99
local cur=${COMP_WORDS[COMP_CWORD]}
1010
local result=()
11+
local want_options=0
1112

1213
# words the user already typed (excluding the command itself)
1314
local used=()
1415
if ((COMP_CWORD > 1)); then
1516
used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")
1617
fi
1718

18-
if [[ "${cur:0:1}" == "-" ]]; then
19-
# Completing an option: offer everything (including options)
20-
echo "$words"
21-
22-
else
23-
# Completing a non-option: offer only non-options,
24-
# and don't re-offer ones already used earlier in the line.
25-
for word in $words; do
19+
# Completing an option: offer everything.
20+
# Completing a non-option: drop options and already-used words.
21+
[[ "${cur:0:1}" == "-" ]] && want_options=1
22+
for word in "${words[@]}"; do
23+
if ((!want_options)); then
2624
[[ "${word:0:1}" == "-" ]] && continue
2725

28-
local seen=0
2926
for u in "${used[@]}"; do
3027
if [[ "$u" == "$word" ]]; then
31-
seen=1
32-
break
28+
continue 2
3329
fi
3430
done
35-
((!seen)) && result+=("$word")
36-
done
31+
fi
3732

38-
echo "${result[*]}"
39-
fi
33+
# compgen -W expects shell-escaped words in one space-delimited string.
34+
printf -v word '%q' "$word"
35+
result+=("$word")
36+
done
37+
38+
echo "${result[*]}"
4039
}
4140

4241
_mycomps() {
@@ -59,15 +58,15 @@ _mycomps() {
5958
;;
6059

6160
'status'*)
62-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "--help --verbose --branch -b")" -- "$cur")
61+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
6362
;;
6463

6564
'init'*)
6665
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mycomps_filter "--bare")" -- "$cur")
6766
;;
6867

6968
*)
70-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "-h -v --help --version init status")" -- "$cur")
69+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mycomps_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
7170
;;
7271

7372
esac

spec/approvals/cli/generated-wrapped-script

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,37 @@ give_comps() {
66
echo $'# Modifying it manually is not recommended'
77
echo $''
88
echo $'_mygit_completions_filter() {'
9-
echo $' local words="$1"'
9+
echo $' local words=("$@")'
1010
echo $' local cur=${COMP_WORDS[COMP_CWORD]}'
1111
echo $' local result=()'
12+
echo $' local want_options=0'
1213
echo $''
1314
echo $' # words the user already typed (excluding the command itself)'
1415
echo $' local used=()'
1516
echo $' if ((COMP_CWORD > 1)); then'
1617
echo $' used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")'
1718
echo $' fi'
1819
echo $''
19-
echo $' if [[ "${cur:0:1}" == "-" ]]; then'
20-
echo $' # Completing an option: offer everything (including options)'
21-
echo $' echo "$words"'
22-
echo $''
23-
echo $' else'
24-
echo $' # Completing a non-option: offer only non-options,'
25-
echo $' # and don\'t re-offer ones already used earlier in the line.'
26-
echo $' for word in $words; do'
20+
echo $' # Completing an option: offer everything.'
21+
echo $' # Completing a non-option: drop options and already-used words.'
22+
echo $' [[ "${cur:0:1}" == "-" ]] && want_options=1'
23+
echo $' for word in "${words[@]}"; do'
24+
echo $' if ((!want_options)); then'
2725
echo $' [[ "${word:0:1}" == "-" ]] && continue'
2826
echo $''
29-
echo $' local seen=0'
3027
echo $' for u in "${used[@]}"; do'
3128
echo $' if [[ "$u" == "$word" ]]; then'
32-
echo $' seen=1'
33-
echo $' break'
29+
echo $' continue 2'
3430
echo $' fi'
3531
echo $' done'
36-
echo $' ((!seen)) && result+=("$word")'
37-
echo $' done'
32+
echo $' fi'
3833
echo $''
39-
echo $' echo "${result[*]}"'
40-
echo $' fi'
34+
echo $' # compgen -W expects shell-escaped words in one space-delimited string.'
35+
echo $' printf -v word \'%q\' "$word"'
36+
echo $' result+=("$word")'
37+
echo $' done'
38+
echo $''
39+
echo $' echo "${result[*]}"'
4140
echo $'}'
4241
echo $''
4342
echo $'_mygit_completions() {'
@@ -60,15 +59,15 @@ give_comps() {
6059
echo $' ;;'
6160
echo $''
6261
echo $' \'status\'*)'
63-
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur")'
62+
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")'
6463
echo $' ;;'
6564
echo $''
6665
echo $' \'init\'*)'
6766
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")'
6867
echo $' ;;'
6968
echo $''
7069
echo $' *)'
71-
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur")'
70+
echo $' while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")'
7271
echo $' ;;'
7372
echo $''
7473
echo $' esac'

spec/approvals/cli/test/completely-tester-1.sh

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,38 +13,37 @@ fi
1313
# Modifying it manually is not recommended
1414

1515
_mygit_completions_filter() {
16-
local words="$1"
16+
local words=("$@")
1717
local cur=${COMP_WORDS[COMP_CWORD]}
1818
local result=()
19+
local want_options=0
1920

2021
# words the user already typed (excluding the command itself)
2122
local used=()
2223
if ((COMP_CWORD > 1)); then
2324
used=("${COMP_WORDS[@]:1:$((COMP_CWORD - 1))}")
2425
fi
2526

26-
if [[ "${cur:0:1}" == "-" ]]; then
27-
# Completing an option: offer everything (including options)
28-
echo "$words"
29-
30-
else
31-
# Completing a non-option: offer only non-options,
32-
# and don't re-offer ones already used earlier in the line.
33-
for word in $words; do
27+
# Completing an option: offer everything.
28+
# Completing a non-option: drop options and already-used words.
29+
[[ "${cur:0:1}" == "-" ]] && want_options=1
30+
for word in "${words[@]}"; do
31+
if ((!want_options)); then
3432
[[ "${word:0:1}" == "-" ]] && continue
3533

36-
local seen=0
3734
for u in "${used[@]}"; do
3835
if [[ "$u" == "$word" ]]; then
39-
seen=1
40-
break
36+
continue 2
4137
fi
4238
done
43-
((!seen)) && result+=("$word")
44-
done
39+
fi
4540

46-
echo "${result[*]}"
47-
fi
41+
# compgen -W expects shell-escaped words in one space-delimited string.
42+
printf -v word '%q' "$word"
43+
result+=("$word")
44+
done
45+
46+
echo "${result[*]}"
4847
}
4948

5049
_mygit_completions() {
@@ -67,15 +66,15 @@ _mygit_completions() {
6766
;;
6867

6968
'status'*)
70-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help --verbose --branch -b")" -- "$cur")
69+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "--help" "--verbose" "--branch" "-b")" -- "$cur")
7170
;;
7271

7372
'init'*)
7473
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -A directory -W "$(_mygit_completions_filter "--bare")" -- "$cur")
7574
;;
7675

7776
*)
78-
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h -v --help --version init status")" -- "$cur")
77+
while read -r; do COMPREPLY+=("$REPLY"); done < <(compgen -W "$(_mygit_completions_filter "-h" "-v" "--help" "--version" "init" "status")" -- "$cur")
7978
;;
8079

8180
esac

0 commit comments

Comments
 (0)