67
67
---
68
68
--- - `find_upwards` returns `nil` if file not found rather than an empty string
69
69
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
+
70
78
local bit = require " plenary.bit"
71
79
local uv = vim .loop
72
80
local iswin = uv .os_uname ().sysname == " Windows_NT"
@@ -80,6 +88,7 @@ local hasshellslash = vim.fn.exists "+shellslash" == 1
80
88
--- @field convert_altsep fun ( self : plenary._Path , p : string ): string
81
89
--- @field split_root fun ( self : plenary._Path , part : string ): string , string , string
82
90
--- @field join fun ( self : plenary._Path , path : string , ... : string ): string
91
+ --- @field expand fun ( self : plenary._Path , parts : string[] , sep : string ?): string[]
83
92
84
93
--- @class plenary._WindowsPath : plenary._Path
85
94
local _WindowsPath = {
@@ -200,6 +209,38 @@ function _WindowsPath:join(path, ...)
200
209
return result_drive .. result_root .. table.concat (parts )
201
210
end
202
211
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
+
203
244
--- @class plenary._PosixPath : plenary._Path
204
245
local _PosixPath = {
205
246
sep = " /" ,
@@ -249,6 +290,34 @@ function _PosixPath:join(path, ...)
249
290
return table.concat (parts )
250
291
end
251
292
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
+
252
321
local S_IF = {
253
322
-- S_IFDIR = 0o040000 # directory
254
323
DIR = 0x4000 ,
650
719
--- if given path doesn't exists and isn't already an absolute path, creates
651
720
--- one using the cwd
652
721
---
722
+ --- DOES NOT expand environment variables and home/user constructs (`~` and `~user`).
723
+ --- Use `expand` for this.
724
+ ---
653
725
--- respects 'shellslash' on Windows
654
726
--- @return string
655
727
function Path :absolute ()
@@ -675,6 +747,18 @@ function Path:absolute()
675
747
return self ._absolute
676
748
end
677
749
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
+
678
762
--- @param ... plenary .Path2Args
679
763
--- @return plenary.Path2
680
764
function Path :joinpath (...)
0 commit comments