1
1
defmodule Path do
2
+ defexception NoHomeError ,
3
+ message: "could not find the user home, please set the HOME environment variable"
4
+
2
5
@ doc """
3
6
This module provides conveniences for manipulating or
4
7
retrieving filesystem paths.
@@ -9,11 +12,57 @@ defmodule Path do
9
12
The majority of the functions in this module do not
10
13
interact with the file system, unless some few functions
11
14
that needs to query the filesystem to retrieve paths
12
- (like `Path.wildcard` and `Path.tmpdir `).
15
+ (like `Path.wildcard` and `Path.expand `).
13
16
"""
14
17
15
18
alias :filename , as: FN
16
19
20
+ @ doc """
21
+ Converts the given filename and returns an absolute name.
22
+ Differently from `Path.expand/1`, no attempt is made to
23
+ resolve `..`, `.` or `~`.
24
+
25
+ ## Unix examples
26
+
27
+ Path.absname("foo")
28
+ #=> "/usr/local/foo"
29
+
30
+ Path.absname("../x")
31
+ #=> "/usr/local/../x"
32
+
33
+ ## Windows
34
+
35
+ Path.absname("foo").
36
+ "D:/usr/local/foo"
37
+ Path.absname("../x").
38
+ "D:/usr/local/../x"
39
+
40
+ """
41
+ def absname ( path ) do
42
+ FN . absname ( path , get_cwd ( path ) )
43
+ end
44
+
45
+ @ doc """
46
+ Converts the given filename and returns an absolute name
47
+ relative to the given location. If the path is already
48
+ an absolute path, the relative path is ignored.
49
+
50
+ Differently from `Path.expand/2`, no attempt is made to
51
+ resolve `..`, `.` or `~`.
52
+
53
+ ## Examples
54
+
55
+ Path.absname("foo", "bar")
56
+ #=> "bar/foo"
57
+
58
+ Path.absname("../x", "bar")
59
+ #=> "bar/../x"
60
+
61
+ """
62
+ def absname ( path , relative_to ) do
63
+ FN . absname ( path , relative_to )
64
+ end
65
+
17
66
@ doc """
18
67
Expands the path by returning its absolute name and expanding
19
68
any `.` and `..` characters.
@@ -42,6 +91,46 @@ defmodule Path do
42
91
normalize FN . absname ( FN . absname ( path , relative_to ) , get_cwd ( path ) )
43
92
end
44
93
94
+ @ doc """
95
+ Returns the given `path` relative to the given `from` path.
96
+
97
+ This function does not query the filesystem, so it assumes
98
+ no symlinks in between the paths.
99
+
100
+ In case a direct relative path cannot be found, it returns
101
+ the original path.
102
+
103
+ ## Examples
104
+
105
+ Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
106
+ Path.relative_to("/usr/local/foo", "/") #=> "foo"
107
+ Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
108
+
109
+ """
110
+ def relative_to ( path , from ) when is_list ( path ) and is_binary ( from ) do
111
+ relative_to ( FN . split ( list_to_binary ( path ) ) , FN . split ( from ) , list_to_binary ( path ) )
112
+ end
113
+
114
+ def relative_to ( path , from ) when is_binary ( path ) and is_list ( from ) do
115
+ relative_to ( FN . split ( path ) , FN . split ( list_to_binary ( from ) ) , path )
116
+ end
117
+
118
+ def relative_to ( path , from ) do
119
+ relative_to ( FN . split ( path ) , FN . split ( from ) , path )
120
+ end
121
+
122
+ defp relative_to ( [ h | t1 ] , [ h | t2 ] , original ) do
123
+ relative_to ( t1 , t2 , original )
124
+ end
125
+
126
+ defp relative_to ( t1 , [ ] , _original ) do
127
+ FN . join ( t1 )
128
+ end
129
+
130
+ defp relative_to ( _ , _ , original ) do
131
+ original
132
+ end
133
+
45
134
@ doc """
46
135
Returns the last component of the path or the path
47
136
itself if it does not contain any directory separators.
@@ -233,25 +322,54 @@ defmodule Path do
233
322
234
323
## Helpers
235
324
325
+ defp get_home do
326
+ get_unix_home || get_windows_home || raise NoHomeError
327
+ end
328
+
329
+ defp get_unix_home do
330
+ System . get_env ( "HOME" )
331
+ end
332
+
333
+ defp get_windows_home do
334
+ System . get_env ( "USERPROFILE" ) || (
335
+ hd = System . get_env ( "HOMEDRIVE" )
336
+ hp = System . get_env ( "HOMEPATH" )
337
+ hd && hp && hd <> hp
338
+ )
339
+ end
340
+
236
341
defp get_cwd ( path ) when is_list ( path ) , do: File . cwd! |> binary_to_list
237
342
defp get_cwd ( _ ) , do: File . cwd!
238
343
239
- # Normalize the given path by removing "..".
240
- defp normalize ( path ) , do: normalize ( FN . split ( path ) , [ ] )
344
+ # Normalize the given path by expanding "..", "." and "~".
345
+
346
+ defp normalize ( path ) , do: do_normalize ( FN . split ( path ) )
347
+
348
+ defp do_normalize ( [ "~" | t ] ) do
349
+ do_normalize t , [ get_home ]
350
+ end
351
+
352
+ defp do_normalize ( [ '~' | t ] ) do
353
+ do_normalize t , [ get_home |> binary_to_list ]
354
+ end
355
+
356
+ defp do_normalize ( t ) do
357
+ do_normalize t , [ ]
358
+ end
241
359
242
- defp normalize ( [ top | t ] , [ _ | acc ] ) when top in [ ".." , '..' ] do
243
- normalize t , acc
360
+ defp do_normalize ( [ top | t ] , [ _ | acc ] ) when top in [ ".." , '..' ] do
361
+ do_normalize t , acc
244
362
end
245
363
246
- defp normalize ( [ top | t ] , acc ) when top in [ "." , '.' ] do
247
- normalize t , acc
364
+ defp do_normalize ( [ top | t ] , acc ) when top in [ "." , '.' ] do
365
+ do_normalize t , acc
248
366
end
249
367
250
- defp normalize ( [ h | t ] , acc ) do
251
- normalize t , [ h | acc ]
368
+ defp do_normalize ( [ h | t ] , acc ) do
369
+ do_normalize t , [ h | acc ]
252
370
end
253
371
254
- defp normalize ( [ ] , acc ) do
372
+ defp do_normalize ( [ ] , acc ) do
255
373
join Enum . reverse ( acc )
256
374
end
257
375
end
0 commit comments