6767---
6868--- - `find_upwards` returns `nil` if file not found rather than an empty string
6969
70+
71+ -- TODO: could probably do with more `make_relative` tests
72+ -- - walk up close to root
73+ -- - add "walk_up" in test name
74+ -- TODO: shorten: i think `vim.list_contains` is not nvim-0.7 compat (maybe use like a set?)
75+ -- TODO: verify unix tests pass
76+ -- TODO: add windows test for path2_spec only?
77+
7078local bit = require " plenary.bit"
7179local uv = vim .loop
7280local iswin = uv .os_uname ().sysname == " Windows_NT"
@@ -80,6 +88,7 @@ local hasshellslash = vim.fn.exists "+shellslash" == 1
8088--- @field convert_altsep fun ( self : plenary._Path , p : string ): string
8189--- @field split_root fun ( self : plenary._Path , part : string ): string , string , string
8290--- @field join fun ( self : plenary._Path , path : string , ... : string ): string
91+ --- @field expand fun ( self : plenary._Path , parts : string[] , sep : string ?): string[]
8392
8493--- @class plenary._WindowsPath : plenary._Path
8594local _WindowsPath = {
@@ -200,6 +209,38 @@ function _WindowsPath:join(path, ...)
200209 return result_drive .. result_root .. table.concat (parts )
201210end
202211
212+ --- @param parts string[]
213+ --- @param sep string
214+ --- @return string[] new_path
215+ function _WindowsPath :expand (parts , sep )
216+ -- Variables have a percent sign on both sides: %ThisIsAVariable%
217+ -- The variable name can include spaces, punctuation and mixed case:
218+ -- %_Another Ex.ample%
219+ -- But they aren't case sensitive
220+ --
221+ -- A variable name may include any of the following characters:
222+ -- A-Z, a-z, 0-9, # $ ' ( ) * + , - . ? @ [ ] _ { } ~
223+ -- The first character of the name must not be numeric.
224+
225+ -- this would be MUCH cleaner to implement with LPEG but backwards compatibility...
226+ local pattern = " %%[A-Za-z#$'()*+,%-.?@[%]_{}~][A-Za-z0-9#$'()*+,%-.?@[%]_{}~]*%%"
227+
228+ local new_parts = {}
229+ for _ , part in ipairs (parts ) do
230+ part = part :gsub (pattern , function (m )
231+ local var_name = m :sub (2 ):sub (1 , - 2 )
232+
233+ --- @diagnostic disable-next-line : missing-parameter
234+ local var = uv .os_getenv (var_name )
235+ return var and (var :gsub (" \\ " , sep )) or m
236+ end )
237+
238+ table.insert (new_parts , part )
239+ end
240+
241+ return new_parts
242+ end
243+
203244--- @class plenary._PosixPath : plenary._Path
204245local _PosixPath = {
205246 sep = " /" ,
@@ -249,6 +290,34 @@ function _PosixPath:join(path, ...)
249290 return table.concat (parts )
250291end
251292
293+ --- @param parts string[]
294+ --- @return string[] new_path
295+ function _PosixPath :expand (parts )
296+ -- Environment variable names used by the utilities in the Shell and
297+ -- Utilities volume of IEEE Std 1003.1-2001 consist solely of uppercase
298+ -- letters, digits, and the '_' (underscore) from the characters defined in
299+ -- Portable Character Set and do not begin with a digit. Other characters may
300+ -- be permitted by an implementation; applications shall tolerate the
301+ -- presence of such names.
302+
303+ local pattern = " %$[A-Z_][A-Z0-9_]*"
304+
305+ local new_parts = {}
306+ for _ , part in ipairs (parts ) do
307+ part = part :gsub (pattern , function (m )
308+ local var_name = m :sub (2 )
309+
310+ --- @diagnostic disable-next-line : missing-parameter
311+ local var = uv .os_getenv (var_name )
312+ return var or m
313+ end )
314+
315+ table.insert (new_parts , part )
316+ end
317+
318+ return new_parts
319+ end
320+
252321local S_IF = {
253322 -- S_IFDIR = 0o040000 # directory
254323 DIR = 0x4000 ,
650719--- if given path doesn't exists and isn't already an absolute path, creates
651720--- one using the cwd
652721---
722+ --- DOES NOT expand environment variables and home/user constructs (`~` and `~user`).
723+ --- Use `expand` for this.
724+ ---
653725--- respects 'shellslash' on Windows
654726--- @return string
655727function Path :absolute ()
@@ -675,6 +747,18 @@ function Path:absolute()
675747 return self ._absolute
676748end
677749
750+ --- get the environment variable expanded filename
751+ --- @return string
752+ function Path :expand ()
753+ local relparts = self ._flavor :expand (self .relparts , self .sep )
754+ local filename = self :_filename (nil , nil , relparts )
755+
756+ filename = filename :gsub (" ^~([^" .. self .sep .. " ]+)" .. self .sep , function (m )
757+ return Path :new (self .path .home ):parent ().filename .. self .sep .. m .. self .sep
758+ end )
759+ return (filename :gsub (" ^~" , self .path .home ))
760+ end
761+
678762--- @param ... plenary .Path2Args
679763--- @return plenary.Path2
680764function Path :joinpath (...)
0 commit comments