OILS / pylib / os_path.py View on Github | oilshell.org

169 lines, 74 significant
1"""
2os_path.py - Copy of code from Python's posixpath.py and genericpath.py.
3"""
4
5import posix_ as posix
6
7from mycpp import mylib
8from typing import Tuple, List
9
10extsep = '.'
11sep = '/'
12
13
14def join(s1, s2):
15 # type: (str, str) -> str
16 """Join pathnames.
17
18 Ignore the previous parts if a part is absolute. Insert a '/' unless the
19 first part is empty or already ends in '/'.
20
21 Special case of os.path.join() which avoids varargs.
22 """
23 if s2.startswith('/') or len(s1) == 0:
24 # absolute path
25 return s2
26
27 if s1.endswith('/'):
28 return s1 + s2
29
30 return '%s/%s' % (s1, s2)
31
32
33if mylib.PYTHON:
34 def rstrip_slashes(s):
35 # type: (str) -> str
36 """Helper for split() and dirname()."""
37
38 # This is an awkward implementation from the Python stdlib, but we rewrite it
39 # in C++.
40 n = len(s)
41 if n and s != '/'*n:
42 s = s.rstrip('/')
43 return s
44
45
46# Split a path in head (everything up to the last '/') and tail (the
47# rest). If the path ends in '/', tail will be empty. If there is no
48# '/' in the path, head will be empty.
49# Trailing '/'es are stripped from head unless it is the root.
50
51def split(p):
52 # type: (str) -> Tuple[str, str]
53 """Split a pathname. Returns tuple "(head, tail)" where "tail" is
54 everything after the final slash. Either part may be empty."""
55 i = p.rfind('/') + 1
56 head = p[:i]
57 tail = p[i:]
58 head = rstrip_slashes(head)
59 return head, tail
60
61
62# Split a path in root and extension.
63# The extension is everything starting at the last dot in the last
64# pathname component; the root is everything before that.
65# It is always true that root + ext == p.
66
67# Generic implementation of splitext, to be parametrized with
68# the separators
69def _splitext(p, sep, extsep):
70 # type: (str, str, str) -> Tuple[str, str]
71 """Split the extension from a pathname.
72
73 Extension is everything from the last dot to the end, ignoring
74 leading dots. Returns "(root, ext)"; ext may be empty."""
75
76 sepIndex = p.rfind(sep)
77 dotIndex = p.rfind(extsep)
78 if dotIndex > sepIndex:
79 # skip all leading dots
80 filenameIndex = sepIndex + 1
81 while filenameIndex < dotIndex:
82 if p[filenameIndex] != extsep:
83 return p[:dotIndex], p[dotIndex:]
84 filenameIndex += 1
85
86 return p, ''
87
88
89# Split a path in root and extension.
90# The extension is everything starting at the last dot in the last
91# pathname component; the root is everything before that.
92# It is always true that root + ext == p.
93
94def splitext(p):
95 # type: (str) -> Tuple[str, str]
96 return _splitext(p, sep, extsep)
97
98
99# Return the tail (basename) part of a path, same as split(path)[1].
100
101def basename(p):
102 # type: (str) -> str
103 """Returns the final component of a pathname"""
104 i = p.rfind('/') + 1
105 return p[i:]
106
107
108# Return the head (dirname) part of a path, same as split(path)[0].
109
110def dirname(p):
111 # type: (str) -> str
112 """Returns the directory component of a pathname"""
113 i = p.rfind('/') + 1
114 head = p[:i]
115 head = rstrip_slashes(head)
116 return head
117
118
119# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B.
120# It should be understood that this may change the meaning of the path
121# if it contains symbolic links!
122
123def normpath(path):
124 # type: (str) -> str
125 """Normalize path, eliminating double slashes, etc."""
126
127 slash = '/'
128 dot = '.'
129 if path == '':
130 return dot
131 initial_slashes = path.startswith('/') # type: int
132 # POSIX allows one or two initial slashes, but treats three or more
133 # as single slash.
134 if (initial_slashes and
135 path.startswith('//') and not path.startswith('///')):
136 initial_slashes = 2
137 comps = path.split('/')
138 new_comps = [] # type: List[str]
139 for comp in comps:
140 if len(comp) == 0 or comp == '.': # mycpp rewrite: comp in ('', '.')
141 continue
142 if (comp != '..' or (initial_slashes == 0 and len(new_comps) == 0) or
143 (len(new_comps) and new_comps[-1] == '..')):
144 new_comps.append(comp)
145 elif len(new_comps):
146 new_comps.pop()
147 comps = new_comps
148 path = slash.join(comps)
149 if initial_slashes:
150 path = slash*initial_slashes + path
151 return path if len(path) else dot
152
153
154# Return whether a path is absolute.
155# Trivial in Posix, harder on the Mac or MS-DOS.
156
157def isabs(s):
158 # type: (str) -> bool
159 """Test whether a path is absolute"""
160 return s.startswith('/')
161
162
163def abspath(path):
164 # type: (str) -> str
165 """Return an absolute path."""
166 if not isabs(path):
167 cwd = posix.getcwd()
168 path = join(cwd, path)
169 return normpath(path)