1 # Module 'ntpath' -- common operations on WinNT/Win95 pathnames
2 """Common pathname manipulations, WindowsNT/95 version.
4 Instead of importing this module directly, import os and refer to this
12 from genericpath
import *
14 __all__
= ["normcase","isabs","join","splitdrive","split","splitext",
15 "basename","dirname","commonprefix","getsize","getmtime",
16 "getatime","getctime", "islink","exists","lexists","isdir","isfile",
17 "ismount", "expanduser","expandvars","normpath","abspath",
18 "splitunc","curdir","pardir","sep","pathsep","defpath","altsep",
19 "extsep","devnull","realpath","supports_unicode_filenames","relpath"]
21 # strings representing various path-related bits and pieces
22 # These are primarily for export; internally, they are hardcoded.
30 if 'ce' in sys
.builtin_module_names
:
32 elif 'os2' in sys
.builtin_module_names
:
38 if isinstance(path
, bytes
):
44 if isinstance(path
, bytes
):
49 def _get_altsep(path
):
50 if isinstance(path
, bytes
):
55 def _get_bothseps(path
):
56 if isinstance(path
, bytes
):
62 if isinstance(path
, bytes
):
68 if isinstance(path
, bytes
):
73 # Normalize the case of a pathname and map slashes to backslashes.
74 # Other normalizations (such as optimizing '../' away) are not done
75 # (this is done by normpath).
78 """Normalize case of pathname.
80 Makes all characters lowercase and all slashes into backslashes."""
81 return s
.replace(_get_altsep(s
), _get_sep(s
)).lower()
84 # Return whether a path is absolute.
85 # Trivial in Posix, harder on Windows.
86 # For Windows it is absolute if it starts with a slash or backslash (current
87 # volume), or if a pathname after the volume-letter-and-colon or UNC-resource
88 # starts with a slash or backslash.
91 """Test whether a path is absolute"""
93 return len(s
) > 0 and s
[:1] in _get_bothseps(s
)
96 # Join two (or more) paths.
99 """Join two or more pathname components, inserting "\\" as needed.
100 If any component is an absolute path, all previous path components
101 will be discarded."""
103 seps
= _get_bothseps(a
)
104 colon
= _get_colon(a
)
107 b_wins
= 0 # set to 1 iff b makes path irrelevant
112 # This probably wipes out path so far. However, it's more
113 # complicated if path begins with a drive letter. You get a+b
114 # (minus redundant slashes) in these four cases:
115 # 1. join('c:', '/a') == 'c:/a'
116 # 2. join('//computer/share', '/a') == '//computer/share/a'
117 # 3. join('c:/', '/a') == 'c:/a'
118 # 4. join('//computer/share/', '/a') == '//computer/share/a'
119 # But b wins in all of these cases:
120 # 5. join('c:/a', '/b') == '/b'
121 # 6. join('//computer/share/a', '/b') == '/b'
122 # 7. join('c:', 'd:/') == 'd:/'
123 # 8. join('c:', '//computer/share/') == '//computer/share/'
124 # 9. join('//computer/share', 'd:/') == 'd:/'
125 # 10. join('//computer/share', '//computer/share/') == '//computer/share/'
126 # 11. join('c:/', 'd:/') == 'd:/'
127 # 12. join('c:/', '//computer/share/') == '//computer/share/'
128 # 13. join('//computer/share/', 'd:/') == 'd:/'
129 # 14. join('//computer/share/', '//computer/share/') == '//computer/share/'
130 b_prefix
, b_rest
= splitdrive(b
)
132 # if b has a prefix, it always wins.
136 # b doesn't have a prefix.
137 # but isabs(b) returned true.
138 # and therefore b_rest[0] must be a slash.
139 # (but let's check that.)
140 assert(b_rest
and b_rest
[0] in seps
)
142 # so, b still wins if path has a rest that's more than a sep.
143 # you get a+b if path_rest is empty or only has a sep.
144 # (see cases 1-4 for times when b loses.)
145 path_rest
= splitdrive(path
)[1]
146 b_wins
= path_rest
and path_rest
not in seps
151 # Join, and ensure there's a separator.
153 if path
[-1:] in seps
:
154 if b
and b
[:1] in seps
:
158 elif path
[-1:] == colon
:
166 # path is not empty and does not end with a backslash,
167 # but b is empty; since, e.g., split('a/') produces
168 # ('a', ''), it's best if join() adds a backslash in
175 # Split a path in a drive specification (a drive letter followed by a
176 # colon) and the path specification.
177 # It is always true that drivespec + pathspec == p
179 """Split a pathname into drive/UNC sharepoint and relative path specifiers.
180 Returns a 2-tuple (drive_or_unc, path); either part may be empty.
183 result = splitdrive(p)
184 It is always true that:
185 result[0] + result[1] == p
187 If the path contained a drive letter, drive_or_unc will contain everything
188 up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir")
190 If the path contained a UNC path, the drive_or_unc will contain the host name
191 and share up to but not including the fourth directory separator character.
192 e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir")
194 Paths cannot contain both a drive letter and a UNC path.
197 empty
= _get_empty(p
)
201 if (normp
[0:2] == sep
*2) and (normp
[2:3] != sep
):
203 # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
204 # \\machine\mountpoint\directory\etc\...
205 # directory ^^^^^^^^^^^^^^^
206 index
= normp
.find(sep
, 2)
209 index2
= normp
.find(sep
, index
+ 1)
210 # a UNC path can't have two slashes in a row
211 # (after the initial two)
212 if index2
== index
+ 1:
216 return p
[:index2
], p
[index2
:]
217 if normp
[1:2] == _get_colon(p
):
224 """Deprecated since Python 3.1. Please use splitdrive() instead;
225 it now handles UNC paths.
227 Split a pathname into UNC mount point and relative path specifiers.
229 Return a 2-tuple (unc, rest); either part may be empty.
230 If unc is not empty, it has the form '//host/mount' (or similar
231 using backslashes). unc+rest is always the input path.
232 Paths containing drive letters never have an UNC part.
235 warnings
.warn("ntpath.splitunc is deprecated, use ntpath.splitdrive instead",
236 PendingDeprecationWarning
)
239 return p
[:0], p
# Drive letter present
241 if normcase(firstTwo
) == sep
+ sep
:
243 # vvvvvvvvvvvvvvvvvvvv equivalent to drive letter
244 # \\machine\mountpoint\directories...
245 # directory ^^^^^^^^^^^^^^^
247 index
= normp
.find(sep
, 2)
249 ##raise RuntimeError, 'illegal UNC path: "' + p + '"'
251 index
= normp
.find(sep
, index
+ 1)
254 return p
[:index
], p
[index
:]
258 # Split a path in head (everything up to the last '/') and tail (the
259 # rest). After the trailing '/' is stripped, the invariant
260 # join(head, tail) == p holds.
261 # The resulting head won't end in '/' unless it is the root.
266 Return tuple (head, tail) where tail is everything after the final slash.
267 Either part may be empty."""
269 seps
= _get_bothseps(p
)
271 # set i to index beyond p's last slash
273 while i
and p
[i
-1] not in seps
:
275 head
, tail
= p
[:i
], p
[i
:] # now tail has no slashes
276 # remove trailing slashes from head, unless it's all slashes
278 while head2
and head2
[-1:] in seps
:
281 return d
+ head
, tail
284 # Split a path in root and extension.
285 # The extension is everything starting at the last dot in the last
286 # pathname component; the root is everything before that.
287 # It is always true that root + ext == p.
290 return genericpath
._splitext
(p
, _get_sep(p
), _get_altsep(p
),
292 splitext
.__doc
__ = genericpath
._splitext
.__doc
__
295 # Return the tail (basename) part of a path.
298 """Returns the final component of a pathname"""
302 # Return the head (dirname) part of a path.
305 """Returns the directory component of a pathname"""
308 # Is a path a symbolic link?
309 # This will always return false on systems where posix.lstat doesn't exist.
312 """Test for symbolic link.
313 On WindowsNT/95 and OS/2 always returns false
317 # alias exists to lexists
320 # Is a path a mount point? Either a root (with or without drive letter)
321 # or an UNC path with at most a / or \ after the mount point.
324 """Test whether a path is a mount point (defined as root of drive)"""
325 seps
= _get_bothseps(path
)
326 root
, rest
= splitdrive(path
)
327 if root
and root
[0] in seps
:
328 return (not rest
) or (rest
in seps
)
332 # Expand paths beginning with '~' or '~user'.
333 # '~' means $HOME; '~user' means that user's home directory.
334 # If the path doesn't begin with '~', or if the user or $HOME is unknown,
335 # the path is returned unchanged (leaving error reporting to whatever
336 # function is called with the expanded path as argument).
337 # See also module 'glob' for expansion of *, ? and [...] in pathnames.
338 # (A function should also be defined to do full *sh-style environment
339 # variable expansion.)
341 def expanduser(path
):
342 """Expand ~ and ~user constructs.
344 If user or $HOME is unknown, do nothing."""
345 if isinstance(path
, bytes
):
349 if not path
.startswith(tilde
):
352 while i
< n
and path
[i
] not in _get_bothseps(path
):
355 if 'HOME' in os
.environ
:
356 userhome
= os
.environ
['HOME']
357 elif 'USERPROFILE' in os
.environ
:
358 userhome
= os
.environ
['USERPROFILE']
359 elif not 'HOMEPATH' in os
.environ
:
363 drive
= os
.environ
['HOMEDRIVE']
366 userhome
= join(drive
, os
.environ
['HOMEPATH'])
368 if isinstance(path
, bytes
):
369 userhome
= userhome
.encode(sys
.getfilesystemencoding())
372 userhome
= join(dirname(userhome
), path
[1:i
])
374 return userhome
+ path
[i
:]
377 # Expand paths containing shell variable substitutions.
378 # The following rules apply:
379 # - no expansion within single quotes
380 # - '$$' is translated into '$'
381 # - '%%' is translated into '%' if '%%' are not seen in %var1%%var2%
382 # - ${varname} is accepted.
383 # - $varname is accepted.
384 # - %varname% is accepted.
385 # - varnames can be made out of letters, digits and the characters '_-'
386 # (though is not verifed in the ${varname} and %varname% cases)
387 # XXX With COMMAND.COM you can use any characters in a variable name,
388 # XXX except '^|<>='.
390 def expandvars(path
):
391 """Expand shell variables of the forms $var, ${var} and %var%.
393 Unknown variables are left unchanged."""
394 if isinstance(path
, bytes
):
395 if ord('$') not in path
and ord('%') not in path
:
398 varchars
= bytes(string
.ascii_letters
+ string
.digits
+ '_-', 'ascii')
404 if '$' not in path
and '%' not in path
:
407 varchars
= string
.ascii_letters
+ string
.digits
+ '_-'
415 while index
< pathlen
:
416 c
= path
[index
:index
+1]
417 if c
== quote
: # no expansion within single quotes
418 path
= path
[index
+ 1:]
421 index
= path
.index(c
)
422 res
= res
+ c
+ path
[:index
+ 1]
426 elif c
== percent
: # variable or '%'
427 if path
[index
+ 1:index
+ 2] == percent
:
431 path
= path
[index
+1:]
434 index
= path
.index(percent
)
436 res
= res
+ percent
+ path
440 if isinstance(path
, bytes
):
441 var
= var
.decode('ascii')
442 if var
in os
.environ
:
443 value
= os
.environ
[var
]
445 value
= '%' + var
+ '%'
446 if isinstance(path
, bytes
):
447 value
= value
.encode('ascii')
449 elif c
== dollar
: # variable or '$$'
450 if path
[index
+ 1:index
+ 2] == dollar
:
453 elif path
[index
+ 1:index
+ 2] == brace
:
454 path
= path
[index
+2:]
457 if isinstance(path
, bytes
):
458 index
= path
.index(b
'}')
460 index
= path
.index('}')
462 if isinstance(path
, bytes
):
463 var
= var
.decode('ascii')
464 if var
in os
.environ
:
465 value
= os
.environ
[var
]
467 value
= '${' + var
+ '}'
468 if isinstance(path
, bytes
):
469 value
= value
.encode('ascii')
472 if isinstance(path
, bytes
):
473 res
= res
+ b
'${' + path
475 res
= res
+ '${' + path
480 c
= path
[index
:index
+ 1]
481 while c
and c
in varchars
:
482 if isinstance(path
, bytes
):
483 var
= var
+ c
.decode('ascii')
487 c
= path
[index
:index
+ 1]
488 if var
in os
.environ
:
489 value
= os
.environ
[var
]
492 if isinstance(path
, bytes
):
493 value
= value
.encode('ascii')
503 # Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B.
504 # Previously, this function also truncated pathnames to 8+3 format,
505 # but as this module is called "ntpath", that's obviously wrong!
508 """Normalize path, eliminating double slashes, etc."""
510 dotdot
= _get_dot(path
) * 2
511 path
= path
.replace(_get_altsep(path
), sep
)
512 prefix
, path
= splitdrive(path
)
514 # collapse initial backslashes
515 if path
.startswith(sep
):
516 prefix
= prefix
+ sep
517 path
= path
.lstrip(sep
)
519 comps
= path
.split(sep
)
521 while i
< len(comps
):
522 if not comps
[i
] or comps
[i
] == _get_dot(path
):
524 elif comps
[i
] == dotdot
:
525 if i
> 0 and comps
[i
-1] != dotdot
:
528 elif i
== 0 and prefix
.endswith(_get_sep(path
)):
534 # If the path is now empty, substitute '.'
535 if not prefix
and not comps
:
536 comps
.append(_get_dot(path
))
537 return prefix
+ sep
.join(comps
)
540 # Return an absolute path.
542 from nt
import _getfullpathname
544 except ImportError: # not running on Windows - mock up something sensible
546 """Return the absolute version of a path."""
548 if isinstance(path
, bytes
):
552 path
= join(cwd
, path
)
553 return normpath(path
)
555 else: # use native Windows method on Windows
557 """Return the absolute version of a path."""
559 if path
: # Empty path must return current working directory.
561 path
= _getfullpathname(path
)
563 pass # Bad path - return unchanged.
564 elif isinstance(path
, bytes
):
568 return normpath(path
)
570 # realpath is a no-op on systems without islink support
572 # Win9x family and earlier have no Unicode filename support.
573 supports_unicode_filenames
= (hasattr(sys
, "getwindowsversion") and
574 sys
.getwindowsversion()[3] >= 2)
576 def relpath(path
, start
=curdir
):
577 """Return a relative version of a path"""
581 start
= _get_dot(path
)
584 raise ValueError("no path specified")
586 start_abs
= abspath(normpath(start
))
587 path_abs
= abspath(normpath(path
))
588 start_drive
, start_rest
= splitdrive(start_abs
)
589 path_drive
, path_rest
= splitdrive(path_abs
)
590 if start_drive
!= path_drive
:
591 error
= "path is on mount '{0}', start on mount '{1}'".format(
592 path_drive
, start_drive
)
593 raise ValueError(error
)
595 start_list
= [x
for x
in start_rest
.split(sep
) if x
]
596 path_list
= [x
for x
in path_rest
.split(sep
) if x
]
597 # Work out how much of the filepath is shared by start and path.
599 for e1
, e2
in zip(start_list
, path_list
):
604 if isinstance(path
, bytes
):
608 rel_list
= [pardir
] * (len(start_list
)-i
) + path_list
[i
:]
610 return _get_dot(path
)
611 return join(*rel_list
)