@@ -136,31 +136,70 @@ end
136136
137137--- @param parts string[]
138138--- @param sep string
139- --- @return string[] new_path
139+ --- @return string[] new_parts
140140function _WindowsPath :expand (parts , sep )
141- -- Variables have a percent sign on both sides: %ThisIsAVariable%
142- -- The variable name can include spaces, punctuation and mixed case:
143- -- %_Another Ex.ample%
144- -- But they aren't case sensitive
145- --
146- -- A variable name may include any of the following characters:
147- -- A-Z, a-z, 0-9, # $ ' ( ) * + , - . ? @ [ ] _ { } ~
148- -- The first character of the name must not be numeric.
149-
150- -- this would be MUCH cleaner to implement with LPEG but backwards compatibility...
151- local pattern = " %%[A-Za-z#$'()*+,%-.?@[%]_{}~][A-Za-z0-9#$'()*+,%-.?@[%]_{}~]*%%"
152-
153141 local new_parts = {}
142+
143+ local function add_expand (sub_parts , var , part , start , end_ )
144+ --- @diagnostic disable-next-line : missing-parameter
145+ local val = uv .os_getenv (var )
146+ if val then
147+ table.insert (sub_parts , (val :gsub (" \\ " , sep )))
148+ else
149+ table.insert (sub_parts , part :sub (start , end_ ))
150+ end
151+ end
152+
154153 for _ , part in ipairs (parts ) do
155- part = part : gsub ( pattern , function ( m )
156- local var_name = m : sub ( 2 , - 2 )
154+ local sub_parts = {}
155+ local i = 1
157156
158- --- @diagnostic disable-next-line : missing-parameter
159- local var = uv .os_getenv (var_name )
160- return var and (var :gsub (" \\ " , sep )) or m
161- end )
157+ while i <= # part do
158+ local ch = part :sub (i , i )
159+ if ch == " '" then -- no expansion inside single quotes
160+ local end_ = part :find (" '" , i + 1 , true )
161+ if end_ then
162+ table.insert (sub_parts , part :sub (i , end_ ))
163+ i = end_
164+ else
165+ table.insert (sub_parts , ch )
166+ end
167+ elseif ch == " %" then
168+ local end_ = part :find (" %" , i + 1 , true )
169+ if end_ then
170+ local var = part :sub (i + 1 , end_ - 1 )
171+ add_expand (sub_parts , var , part , i , end_ )
172+ i = end_
173+ else
174+ table.insert (sub_parts , ch )
175+ end
176+ elseif ch == " $" then
177+ local nextch = part :sub (i + 1 , i + 1 )
178+ if nextch == " $" then
179+ i = i + 1
180+ table.insert (sub_parts , ch )
181+ elseif nextch == " {" then
182+ local end_ = part :find (" }" , i + 2 , true )
183+ if end_ then
184+ local var = part :sub (i + 2 , end_ - 1 )
185+ add_expand (sub_parts , var , part , i , end_ )
186+ i = end_
187+ else
188+ table.insert (sub_parts , ch )
189+ end
190+ else
191+ local end_ = part :find (" [^%w_]" , i + 1 , false ) or # part + 1
192+ local var = part :sub (i + 1 , end_ - 1 )
193+ add_expand (sub_parts , var , part , i , end_ - 1 )
194+ i = end_ - 1
195+ end
196+ else
197+ table.insert (sub_parts , ch )
198+ end
199+ i = i + 1
200+ end
162201
163- table.insert (new_parts , part )
202+ table.insert (new_parts , table.concat ( sub_parts ) )
164203 end
165204
166205 return new_parts
@@ -232,28 +271,47 @@ function _PosixPath:join(path, ...)
232271end
233272
234273--- @param parts string[]
235- --- @return string[] new_path
274+ --- @return string[] new_parts
236275function _PosixPath :expand (parts )
237- -- Environment variable names used by the utilities in the Shell and
238- -- Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
239- -- letters, digits, and the '_' (underscore) from the characters defined in
240- -- Portable Character Set and do not begin with a digit. Other characters may
241- -- be permitted by an implementation; applications shall tolerate the
242- -- presence of such names.
243-
244- local pattern = " %$[A-Z_][A-Z0-9_]*"
276+ local function add_expand (sub_parts , var , part , start , end_ )
277+ --- @diagnostic disable-next-line : missing-parameter
278+ local val = uv .os_getenv (var )
279+ if val then
280+ table.insert (sub_parts , val )
281+ else
282+ table.insert (sub_parts , part :sub (start , end_ ))
283+ end
284+ end
245285
246286 local new_parts = {}
247287 for _ , part in ipairs (parts ) do
248- part = part :gsub (pattern , function (m )
249- local var_name = m :sub (2 )
250-
251- --- @diagnostic disable-next-line : missing-parameter
252- local var = uv .os_getenv (var_name )
253- return var or m
254- end )
288+ local i = 1
289+ local sub_parts = {}
290+ while i <= # part do
291+ local ch = part :sub (i , i )
292+ if ch == " $" then
293+ if part :sub (i + 1 , i + 1 ) == " {" then
294+ local end_ = part :find (" }" , i + 2 , true )
295+ if end_ then
296+ local var = part :sub (i + 2 , end_ - 1 )
297+ add_expand (sub_parts , var , part , i , end_ )
298+ i = end_
299+ else
300+ table.insert (sub_parts , ch )
301+ end
302+ else
303+ local end_ = part :find (" [^%w_]" , i + 1 , false ) or # part + 1
304+ local var = part :sub (i + 1 , end_ - 1 )
305+ add_expand (sub_parts , var , part , i , end_ - 1 )
306+ i = end_ - 1
307+ end
308+ else
309+ table.insert (sub_parts , ch )
310+ end
311+ i = i + 1
312+ end
255313
256- table.insert (new_parts , part )
314+ table.insert (new_parts , table.concat ( sub_parts ) )
257315 end
258316
259317 return new_parts
@@ -714,15 +772,17 @@ function Path:absolute()
714772end
715773
716774--- get the environment variable expanded filename
775+ --- also expand ~/ but NOT ~user/ constructs
717776--- @return string
718777function Path :expand ()
719778 local relparts = self ._flavor :expand (self .relparts , self .sep )
720779 local filename = self :_filename (nil , nil , relparts )
721780
722- filename = filename :gsub (" ^~([^" .. self .sep .. " ]+)" .. self .sep , function (m )
723- return Path :new (self .path .home ):parent ().filename .. self .sep .. m .. self .sep
724- end )
725- return (filename :gsub (" ^~" , self .path .home ))
781+ if filename :sub (1 , 2 ) == " ~" .. self .sep then
782+ filename = self .path .home .. filename :sub (2 )
783+ end
784+
785+ return filename
726786end
727787
728788--- @param ... plenary .Path2Args
0 commit comments