Skip to content

Commit d599dcf

Browse files
committed
feat(_comp_abspath): handle ".."
1 parent e1a70c6 commit d599dcf

File tree

2 files changed

+143
-10
lines changed

2 files changed

+143
-10
lines changed

bash_completion

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2181,15 +2181,22 @@ _comp_compgen_fstypes()
21812181
_comp_abspath()
21822182
{
21832183
REPLY=$1
2184-
case $REPLY in
2185-
/*) ;;
2186-
../*) REPLY=$PWD/${REPLY:3} ;;
2187-
*) REPLY=$PWD/$REPLY ;;
2188-
esac
2189-
while [[ $REPLY == */./* ]]; do
2190-
REPLY=${REPLY//\/.\//\/}
2191-
done
2184+
[[ $REPLY == /* ]] || REPLY=$PWD/$REPLY
21922185
REPLY=${REPLY//+(\/)/\/}
2186+
while true; do
2187+
# Process "." and "..". To avoid reducing "/../../ => /", we convert
2188+
# "/*/../" one by one. "/.." at the beginning is ignored. Then, /*/../
2189+
# in the middle is processed. Finally, /*/.. at the end is removed.
2190+
case $REPLY in
2191+
*/./*) REPLY=${REPLY//\/.\//\/} ;;
2192+
*/.) REPLY=${REPLY%/.} ;;
2193+
/..?(/*)) REPLY=${REPLY#/..} ;;
2194+
*/+([^/])/../*) REPLY=${REPLY/\/+([^\/])\/..\//\/} ;;
2195+
*/+([^/])/..) REPLY=${REPLY%/+([^/])/..} ;;
2196+
*) break ;;
2197+
esac
2198+
done
2199+
[[ $REPLY ]] || REPLY=/
21932200
}
21942201

21952202
# Get real command.

test/t/unit/test_unit_abspath.py

Lines changed: 128 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def test_relative(self, bash, functions):
4646
)
4747
assert output.strip().endswith("/shared/foo/bar")
4848

49-
def test_cwd(self, bash, functions):
49+
def test_cwd1(self, bash, functions):
5050
output = assert_bash_exec(
5151
bash,
5252
"__tester ./foo/./bar",
@@ -55,7 +55,34 @@ def test_cwd(self, bash, functions):
5555
)
5656
assert output.strip().endswith("/shared/foo/bar")
5757

58-
def test_parent(self, bash, functions):
58+
def test_cwd2(self, bash, functions):
59+
output = assert_bash_exec(
60+
bash,
61+
"__tester /.",
62+
want_output=True,
63+
want_newline=False,
64+
)
65+
assert output.strip() == "/"
66+
67+
def test_cwd3(self, bash, functions):
68+
output = assert_bash_exec(
69+
bash,
70+
"__tester /foo/.",
71+
want_output=True,
72+
want_newline=False,
73+
)
74+
assert output.strip() == "/foo"
75+
76+
def test_cwd4(self, bash, functions):
77+
output = assert_bash_exec(
78+
bash,
79+
"__tester /././.",
80+
want_output=True,
81+
want_newline=False,
82+
)
83+
assert output.strip() == "/"
84+
85+
def test_parent1(self, bash, functions):
5986
output = assert_bash_exec(
6087
bash,
6188
"__tester ../shared/foo/bar",
@@ -65,3 +92,102 @@ def test_parent(self, bash, functions):
6592
assert output.strip().endswith(
6693
"/shared/foo/bar"
6794
) and not output.strip().endswith("../shared/foo/bar")
95+
96+
def test_parent2(self, bash, functions):
97+
output = assert_bash_exec(
98+
bash,
99+
"__tester /foo/..",
100+
want_output=True,
101+
want_newline=False,
102+
)
103+
assert output.strip() == "/"
104+
105+
def test_parent3(self, bash, functions):
106+
output = assert_bash_exec(
107+
bash,
108+
"__tester /..",
109+
want_output=True,
110+
want_newline=False,
111+
)
112+
assert output.strip() == "/"
113+
114+
def test_parent4(self, bash, functions):
115+
output = assert_bash_exec(
116+
bash,
117+
"__tester /../foo/bar",
118+
want_output=True,
119+
want_newline=False,
120+
)
121+
assert output.strip() == "/foo/bar"
122+
123+
def test_parent5(self, bash, functions):
124+
output = assert_bash_exec(
125+
bash,
126+
"__tester /../../foo/bar",
127+
want_output=True,
128+
want_newline=False,
129+
)
130+
assert output.strip() == "/foo/bar"
131+
132+
def test_parent6(self, bash, functions):
133+
output = assert_bash_exec(
134+
bash,
135+
"__tester /foo/../bar",
136+
want_output=True,
137+
want_newline=False,
138+
)
139+
assert output.strip() == "/bar"
140+
141+
def test_parent7(self, bash, functions):
142+
output = assert_bash_exec(
143+
bash,
144+
"__tester /foo/../../bar",
145+
want_output=True,
146+
want_newline=False,
147+
)
148+
assert output.strip() == "/bar"
149+
150+
def test_parent8(self, bash, functions):
151+
output = assert_bash_exec(
152+
bash,
153+
"__tester /dir1/dir2/dir3/../dir4/../../foo",
154+
want_output=True,
155+
want_newline=False,
156+
)
157+
assert output.strip() == "/dir1/foo"
158+
159+
def test_parent9(self, bash, functions):
160+
output = assert_bash_exec(
161+
bash,
162+
"__tester //dir1/dir2///../foo",
163+
want_output=True,
164+
want_newline=False,
165+
)
166+
assert output.strip() == "/dir1/foo"
167+
168+
def test_parent10(self, bash, functions):
169+
output = assert_bash_exec(
170+
bash,
171+
"__tester /dir1/dir2/dir3/..",
172+
want_output=True,
173+
want_newline=False,
174+
)
175+
assert output.strip() == "/dir1/dir2"
176+
177+
def test_parent11(self, bash, functions):
178+
output = assert_bash_exec(
179+
bash,
180+
"__tester /dir1/dir2/dir3/../..",
181+
want_output=True,
182+
want_newline=False,
183+
)
184+
assert output.strip() == "/dir1"
185+
186+
def test_parent12(self, bash, functions):
187+
output = assert_bash_exec(
188+
bash,
189+
"__tester /dir1/dir2/dir3/../../../..",
190+
want_output=True,
191+
want_newline=False,
192+
)
193+
assert output.strip() == "/"

0 commit comments

Comments
 (0)