Skip to content

Commit 0051a5e

Browse files
BrianPughdpgeorge
authored andcommitted
pathlib: Add initial pathlib implementation.
This adds most of the common functionality of pathlib.Path. The glob functionality could use some work; currently it only supports a single "*" wildcard; however, this is the vast majority of common use-cases and it won't fail silently if non-supported glob patterns are provided.
1 parent d1aaec7 commit 0051a5e

File tree

3 files changed

+534
-0
lines changed

3 files changed

+534
-0
lines changed

python-stdlib/pathlib/manifest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
metadata(version="0.0.1")
2+
3+
module("pathlib.py")

python-stdlib/pathlib/pathlib.py

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import errno
2+
import os
3+
4+
from micropython import const
5+
6+
_SEP = const("/")
7+
8+
9+
def _mode_if_exists(path):
10+
try:
11+
return os.stat(path)[0]
12+
except OSError as e:
13+
if e.errno == errno.ENOENT:
14+
return 0
15+
raise e
16+
17+
18+
def _clean_segment(segment):
19+
segment = str(segment)
20+
if not segment:
21+
return "."
22+
segment = segment.rstrip(_SEP)
23+
if not segment:
24+
return _SEP
25+
while True:
26+
no_double = segment.replace(_SEP + _SEP, _SEP)
27+
if no_double == segment:
28+
break
29+
segment = no_double
30+
return segment
31+
32+
33+
class Path:
34+
def __init__(self, *segments):
35+
segments_cleaned = []
36+
for segment in segments:
37+
segment = _clean_segment(segment)
38+
if segment[0] == _SEP:
39+
segments_cleaned = [segment]
40+
elif segment == ".":
41+
continue
42+
else:
43+
segments_cleaned.append(segment)
44+
45+
self._path = _clean_segment(_SEP.join(segments_cleaned))
46+
47+
def __truediv__(self, other):
48+
return Path(self._path, str(other))
49+
50+
def __repr__(self):
51+
return f'{type(self).__name__}("{self._path}")'
52+
53+
def __str__(self):
54+
return self._path
55+
56+
def __eq__(self, other):
57+
return self.absolute() == Path(other).absolute()
58+
59+
def absolute(self):
60+
path = self._path
61+
cwd = os.getcwd()
62+
if not path or path == ".":
63+
return cwd
64+
if path[0] == _SEP:
65+
return path
66+
return _SEP + path if cwd == _SEP else cwd + _SEP + path
67+
68+
def resolve(self):
69+
return self.absolute()
70+
71+
def open(self, mode="r", encoding=None):
72+
return open(self._path, mode, encoding=encoding)
73+
74+
def exists(self):
75+
return bool(_mode_if_exists(self._path))
76+
77+
def mkdir(self, parents=False, exist_ok=False):
78+
try:
79+
os.mkdir(self._path)
80+
return
81+
except OSError as e:
82+
if e.errno == errno.EEXIST and exist_ok:
83+
return
84+
elif e.errno == errno.ENOENT and parents:
85+
pass # handled below
86+
else:
87+
raise e
88+
89+
segments = self._path.split(_SEP)
90+
progressive_path = ""
91+
if segments[0] == "":
92+
segments = segments[1:]
93+
progressive_path = _SEP
94+
for segment in segments:
95+
progressive_path += _SEP + segment
96+
try:
97+
os.mkdir(progressive_path)
98+
except OSError as e:
99+
if e.errno != errno.EEXIST:
100+
raise e
101+
102+
def is_dir(self):
103+
return bool(_mode_if_exists(self._path) & 0x4000)
104+
105+
def is_file(self):
106+
return bool(_mode_if_exists(self._path) & 0x8000)
107+
108+
def _glob(self, path, pattern, recursive):
109+
# Currently only supports a single "*" pattern.
110+
n_wildcards = pattern.count("*")
111+
n_single_wildcards = pattern.count("?")
112+
113+
if n_single_wildcards:
114+
raise NotImplementedError("? single wildcards not implemented.")
115+
116+
if n_wildcards == 0:
117+
raise ValueError
118+
elif n_wildcards > 1:
119+
raise NotImplementedError("Multiple * wildcards not implemented.")
120+
121+
prefix, suffix = pattern.split("*")
122+
123+
for name, mode, *_ in os.ilistdir(path):
124+
full_path = path + _SEP + name
125+
if name.startswith(prefix) and name.endswith(suffix):
126+
yield full_path
127+
if recursive and mode & 0x4000: # is_dir
128+
yield from self._glob(full_path, pattern, recursive=recursive)
129+
130+
def glob(self, pattern):
131+
"""Iterate over this subtree and yield all existing files (of any
132+
kind, including directories) matching the given relative pattern.
133+
134+
Currently only supports a single "*" pattern.
135+
"""
136+
return self._glob(self._path, pattern, recursive=False)
137+
138+
def rglob(self, pattern):
139+
return self._glob(self._path, pattern, recursive=True)
140+
141+
def stat(self):
142+
return os.stat(self._path)
143+
144+
def read_bytes(self):
145+
with open(self._path, "rb") as f:
146+
return f.read()
147+
148+
def read_text(self, encoding=None):
149+
with open(self._path, "r", encoding=encoding) as f:
150+
return f.read()
151+
152+
def rename(self, target):
153+
os.rename(self._path, target)
154+
155+
def rmdir(self):
156+
os.rmdir(self._path)
157+
158+
def touch(self, exist_ok=True):
159+
if self.exists():
160+
if exist_ok:
161+
return # TODO: should update timestamp
162+
else:
163+
# In lieue of FileExistsError
164+
raise OSError(errno.EEXIST)
165+
with open(self._path, "w"):
166+
pass
167+
168+
def unlink(self, missing_ok=False):
169+
try:
170+
os.unlink(self._path)
171+
except OSError as e:
172+
if not (missing_ok and e.errno == errno.ENOENT):
173+
raise e
174+
175+
def write_bytes(self, data):
176+
with open(self._path, "wb") as f:
177+
f.write(data)
178+
179+
def write_text(self, data, encoding=None):
180+
with open(self._path, "w", encoding=encoding) as f:
181+
f.write(data)
182+
183+
def with_suffix(self, suffix):
184+
index = -len(self.suffix) or None
185+
return Path(self._path[:index] + suffix)
186+
187+
@property
188+
def stem(self):
189+
return self.name.rsplit(".", 1)[0]
190+
191+
@property
192+
def parent(self):
193+
tokens = self._path.rsplit(_SEP, 1)
194+
if len(tokens) == 2:
195+
if not tokens[0]:
196+
tokens[0] = _SEP
197+
return Path(tokens[0])
198+
return Path(".")
199+
200+
@property
201+
def name(self):
202+
return self._path.rsplit(_SEP, 1)[-1]
203+
204+
@property
205+
def suffix(self):
206+
elems = self._path.rsplit(".", 1)
207+
return "" if len(elems) == 1 else "." + elems[1]

0 commit comments

Comments
 (0)