Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from . import BinaryFile
2from . import OsError
3from . import TextFile
4from . import cwd
5from .utils import to_utf8
7c"""source-before-namespace
8#include <fstream>
9#include <filesystem>
10#include <cstdlib>
11#include <sys/types.h>
12#include <sys/stat.h>
13#include <unistd.h>
14#include <string.h>
15#include <stdio.h>
17static inline int chmod_wrapper(const char *name_p, u32 mode)
18{
19 return chmod(name_p, mode);
20}
21"""
23class Path:
24 """A file system path.
26 """
28 _parts: [string]
30 func __init__(self, path: string):
31 if path == "":
32 path = "."
34 self._parts = []
36 if path[0] == '/':
37 self._parts.append("/")
39 for part in path.split("/"):
40 if part in ["", "."]:
41 continue
43 self._parts.append(part)
45 func ==(self, other: Path) -> bool:
46 return self.to_string() == other.to_string()
48 func !=(self, other: Path) -> bool:
49 return not (self == other)
51 func name(self) -> string:
52 """The final component.
54 """
56 if self._parts.length() == 0:
57 return ""
59 name = self._parts[-1]
61 if name == "/":
62 return ""
64 return name
66 func stem(self) -> string:
67 """The final component without extension.
69 """
71 extension_length = self.extension().length()
73 if extension_length > 0:
74 return self.name()[:-extension_length]
75 else:
76 return self.name()
78 func extension(self) -> string:
79 """The final component extension, including the dot.
81 """
83 name = self.name()
84 last_dot_index = name.find_reverse('.')
86 if last_dot_index <= 0 or last_dot_index == (name.length() - 1):
87 return ""
89 return name[last_dot_index:]
91 func parent(self) -> Path:
92 """The parent path.
94 """
96 return Path("/".join(self._parts[:-1]))
98 func join(self, other: string) -> Path:
99 """Join with other path.
101 """
103 if self.name() == "" and self._parts.length() == 0:
104 return Path("./" + other)
105 else:
106 return Path(self.to_string() + "/" + other)
108 func join(self, other: Path) -> Path:
109 """Join with other path.
111 """
113 return self.join(other.to_string())
115 func is_absolute(self) -> bool:
116 """Check if absolute.
118 """
120 return self._parts.length() > 0 and self._parts[0] == "/"
122 func is_relative(self) -> bool:
123 """Check if relative.
125 """
127 return not self.is_absolute()
129 func with_name(self, name: string) -> Path:
130 """Returns a new path with given name.
132 """
134 if name in ["", "."] or name.starts_with('/'):
135 raise OsError(f"Invalid name '{name}'.")
136 elif self.name() == "":
137 raise OsError(f"Path '{self}' has no name.")
138 else:
139 return self.parent().join(name)
141 func with_stem(self, stem: string) -> Path:
142 """Returns a new path with given stem.
144 """
146 return self.with_name(stem + self.extension())
148 func with_extension(self, extension: string) -> Path:
149 """Returns a new path with given extension.
151 """
153 if ((extension.length() > 0 and extension[0] != '.')
154 or extension.length() == 1):
155 raise OsError(f"Invalid extension '{extension}'.")
157 return self.with_name(self.stem() + extension)
159 func read_text(self) -> string:
160 """Read file contents.
162 """
164 return TextFile(self).read()
166 func write_text(self, data: string):
167 """Write file contents.
169 """
171 TextFile(self, "w").write(data)
173 func read_binary(self) -> bytes:
174 """Read file contents.
176 """
178 return BinaryFile(self).read()
180 func write_binary(self, data: bytes):
181 """Write file contents.
183 """
185 BinaryFile(self, "w").write(data)
187 func open_binary(self, mode: string = "r") -> BinaryFile:
188 """Open binary file.
190 """
192 return BinaryFile(self, mode)
194 func open_text(self, mode: string = "r") -> TextFile:
195 """Open binary file.
197 """
199 return TextFile(self, mode)
201 func exists(self) -> bool:
202 """Check for existence.
204 """
206 res: bool = False
207 path_utf8 = to_utf8(self.to_string())
209 c"""
210 res = std::filesystem::exists((const char *)path_utf8.m_bytes->data());
211 """
213 return res
215 func mkdir(self, exists_ok: bool = False):
216 """Create directories.
218 Give `exists_ok` as ``True`` to ignore errors.
220 """
222 path_utf8 = to_utf8(self.to_string())
223 message: string? = None
225 c"""
226 std::error_code ec;
227 bool ok;
229 ok = std::filesystem::create_directories((char *)path_utf8.m_bytes->data(), ec);
231 if (!ok) {
232 if (ec) {
233 message = String(ec.message());
234 } else if (!exists_ok) {
235 message = String(strerror(EEXIST));
236 }
237 }
238 """
240 if message is not None:
241 raise OsError(message)
243 func chmod(self, mode: u32):
244 """Change permissions.
246 """
248 message: string? = None
249 path_utf8 = to_utf8(self.to_string())
251 c"""
252 if (chmod_wrapper((const char *)path_utf8.m_bytes->data(), mode) != 0) {
253 message = String(strerror(errno));
254 }
255 """
257 if message is not None:
258 raise OsError(message)
260 func ls(self) -> [Path]:
261 """Returns all files and folders.
263 """
265 dpath = self.to_string()
267 if dpath == "":
268 dpath = "."
270 path_utf8 = to_utf8(dpath)
271 entries: [Path] = []
273 c"""
274 char *path_p = (char *)path_utf8.m_bytes->data();
276 for (const auto& entry : std::filesystem::directory_iterator(path_p)) {
277 entries->append(mys::make_shared<Path>(entry.path().filename().c_str()));
278 }
279 """
281 return entries
283 func rm(self, recursive: bool = False, force: bool = False):
284 """Remove files and directories.
286 Give `recursive` as ``True`` to remove all files and folders
287 recursivly.
289 Give `force` as ``True`` to ignore errors.
291 """
293 path_utf8 = to_utf8(self.to_string())
294 message: string? = None
296 c"""
297 std::error_code ec;
298 bool ok;
299 char *path_p = (char *)path_utf8.m_bytes->data();
301 if (recursive) {
302 ok = (std::filesystem::remove_all(path_p, ec) > 0);
303 } else {
304 ok = std::filesystem::remove(path_p, ec);
305 }
307 if (!ok) {
308 if (!force) {
309 if (ec) {
310 message = String(ec.message());
311 } else {
312 message = String(strerror(ENOENT));
313 }
314 }
315 }
316 """
318 if message is not None:
319 raise OsError(message)
321 func touch(self):
322 """Update modification time and creates if missing.
324 """
326 path_utf8 = to_utf8(self.to_string())
327 fd = 0
329 c"""
330 fd = open((char *)path_utf8.m_bytes->data(), O_CREAT, 0666);
332 if (fd != -1) {
333 close(fd);
334 }
335 """
337 if fd == -1:
338 raise OsError(f"Touch failed.")
340 func cd(self):
341 """Change directory.
343 """
345 path_utf8 = to_utf8(self.to_string())
346 message: string? = None
348 c"""
349 std::error_code ec;
351 std::filesystem::current_path((char *)path_utf8.m_bytes->data(), ec);
353 if (ec) {
354 message = String(ec.message());
355 }
356 """
358 if message is not None:
359 raise OsError(message)
361 func cp(self, dst: Path, recursive: bool = False):
362 """Copy files and directories.
364 """
366 src_utf8 = to_utf8(self.to_string())
367 dst_utf8 = to_utf8(str(dst))
368 message: string? = None
370 c"""
371 std::error_code ec;
372 std::filesystem::copy_options options;
374 if (recursive) {
375 options = std::filesystem::copy_options::recursive;
376 } else {
377 options = std::filesystem::copy_options::none;
378 }
380 std::filesystem::copy((char *)src_utf8.m_bytes->data(),
381 (char *)dst_utf8.m_bytes->data(),
382 options,
383 ec);
385 if (ec) {
386 message = String(ec.message());
387 }
388 """
390 if message is not None:
391 raise OsError(message)
393 func mv(self, dst: Path):
394 """Move files and directories.
396 """
398 self.cp(dst, recursive=True)
399 self.rm(recursive=True, force=True)
401 func to_string(self) -> string:
402 """Return the path as a string.
404 """
406 return "/".join(self._parts).replace("//", "/")
408 func __str__(self) -> string:
409 return self.to_string()
411test path():
412 assert Path("").to_string() == ""
413 assert Path("foo//bar").to_string() == "foo/bar"
414 assert Path("foo/./bar").to_string() == "foo/bar"
415 assert Path("foo/../bar").to_string() == "foo/../bar"
417test name():
418 assert Path("foo/bar.txt").name() == "bar.txt"
419 assert Path("").name() == ""
420 assert Path("foo.txt").name() == "foo.txt"
421 assert Path("/foo/bar").name() == "bar"
422 assert Path(".bar").name() == ".bar"
423 assert Path("foo.tar.gz").name() == "foo.tar.gz"
424 assert Path("a/b/").name() == "b"
426test stem():
427 assert Path("foo/bar.txt").stem() == "bar"
428 assert Path("").stem() == ""
429 assert Path("foo.txt").stem() == "foo"
430 assert Path("/foo/bar").stem() == "bar"
431 assert Path(".bar").stem() == ".bar"
432 assert Path("..out").stem() == "."
433 assert Path("foo.tar.gz").stem() == "foo.tar"
434 assert Path("foo.tar.gz.").stem() == "foo.tar.gz."
436test extension():
437 assert Path("foo/bar.txt").extension() == ".txt"
438 assert Path("").extension() == ""
439 assert Path("bar.tar.gz").extension() == ".gz"
440 assert Path("foo.txt").extension() == ".txt"
441 assert Path("/foo/bar").extension() == ""
442 assert Path(".bar").extension() == ""
443 assert Path("..out").extension() == ".out"
444 assert Path("foo.tar.gz").extension() == ".gz"
445 assert Path("foo.tar.gz.").extension() == ""
447test parent():
448 assert Path("foo/bar.txt").parent() == Path("foo")
449 assert Path(".").parent() == Path(".")
450 assert Path("foo").parent().parent() == Path(".")
452test join():
453 path = Path("foo").join("bar.txt")
454 assert path.to_string() == "foo/bar.txt"
456 path = Path("foo").join("/bar.txt")
457 assert path.to_string() == "foo/bar.txt"
459 path = Path("foo/").join("/bar.txt")
460 assert path.to_string() == "foo/bar.txt"
462 path = Path("foo").join(Path("bar.txt"))
463 assert path.to_string() == "foo/bar.txt"
465 path = Path(".").join(Path("bar.txt"))
466 assert path.to_string() == "bar.txt"
468 path = Path("./").join(Path("/bar.txt"))
469 assert path.to_string() == "bar.txt"
471 path = Path("").join(Path("/bar.txt"))
472 assert path.to_string() == "bar.txt"
474 path = Path("/").join(Path("bar.txt"))
475 assert path.to_string() == "/bar.txt"
477 path = Path("").join(Path("/"))
478 assert path.to_string() == ""
480test ls():
481 directory = Path("ls")
482 directory.rm(recursive=True, force=True)
483 directory.mkdir()
484 directory.join("a").mkdir()
485 directory.join("b").mkdir()
486 directory.join("c").touch()
488 entries = directory.ls()
490 assert entries.length() == 3
491 assert Path("a") in entries
492 assert Path("b") in entries
493 assert Path("c") in entries
495 directory.rm(recursive=True, force=True)
497test rm():
498 directory = Path("test_rm")
499 directory.rm(recursive=True, force=True)
500 directory.mkdir()
501 directory.rm(recursive=True)
502 directory.join("foo/bar").mkdir(exists_ok=True)
504 try:
505 message = ""
506 directory.rm()
507 except OsError as error:
508 message = error.message
510 assert message == "Directory not empty"
512 directory.rm(recursive=True)
514test rm_single_file():
515 filename = Path("test_rm_single_file")
516 filename.touch()
517 filename.rm()
519 try:
520 message = ""
521 filename.rm()
522 except OsError as error:
523 message = error.message
525 assert message == "No such file or directory"
527 filename.rm(force=True)
529test chmod():
530 filename = Path("cmod")
532 filename.rm(force=True)
534 try:
535 message = ""
536 filename.chmod(0o666)
537 except OsError as error:
538 message = error.message
540 assert message == "No such file or directory"
542 filename.touch()
543 filename.chmod(0o666)
544 filename.rm()
546test mkdir():
547 directory = Path("adir")
548 directory.rm(force=True)
549 directory.mkdir(exists_ok=True)
551 try:
552 message = ""
553 directory.mkdir()
554 except OsError as error:
555 message = error.message
557 assert message == "File exists"
559test cd():
560 Path("ccc").rm(recursive=True, force=True)
561 path = cwd()
563 try:
564 message = ""
565 Path("ccc").cd()
566 except OsError as error:
567 message = error.message
569 assert message == "No such file or directory"
571 Path("ccc").mkdir()
572 Path("ccc").cd()
573 assert cwd() != path
574 Path("apa").touch()
575 Path("..").cd()
576 assert cwd() == path
577 Path("ccc/apa").exists()
579 Path("ccc").rm(recursive=True, force=True)
581test cp():
582 Path("cp").rm(recursive=True, force=True)
583 Path("cp-2").rm(recursive=True, force=True)
585 Path("cp").mkdir()
586 Path("cp/foo.txt").touch()
587 Path("cp/bar.txt").touch()
588 Path("cp/foo").mkdir()
589 Path("cp/foo/fie.txt").touch()
591 Path("cp").cp(Path("cp-2"), recursive=True)
593 assert Path("cp/foo.txt").exists()
594 assert Path("cp/bar.txt").exists()
595 assert Path("cp/foo/fie.txt").exists()
596 assert Path("cp-2/foo.txt").exists()
597 assert Path("cp-2/bar.txt").exists()
598 assert Path("cp-2/foo/fie.txt").exists()
600 Path("cp").rm(recursive=True, force=True)
601 Path("cp-2").rm(recursive=True, force=True)
603test mv():
604 Path("mv").rm(recursive=True, force=True)
605 Path("mv-2").rm(recursive=True, force=True)
607 Path("mv").mkdir()
608 Path("mv/foo.txt").touch()
609 Path("mv/bar.txt").touch()
610 Path("mv/foo").mkdir()
611 Path("mv/foo/fie.txt").touch()
613 Path("mv").mv(Path("mv-2"))
615 assert not Path("mv").exists()
616 assert Path("mv-2/foo.txt").exists()
617 assert Path("mv-2/bar.txt").exists()
618 assert Path("mv-2/foo/fie.txt").exists()
620 Path("mv-2").rm(recursive=True, force=True)
622test with_name():
623 try:
624 assert Path("").with_name("foo.txt") == Path("")
625 assert False
626 except OsError:
627 pass
629 assert Path("foo.log").with_name("foo.txt") == Path("foo.txt")
630 assert Path("a/b").with_name("c") == Path("a/c")
632 try:
633 Path("/").with_name("c")
634 assert False
635 except OsError:
636 pass
638 try:
639 message = ""
640 Path("/k").with_name("")
641 except OsError as error:
642 message = error.message
644 assert message == "Invalid name ''."
646 try:
647 message = ""
648 Path("/k").with_name(".")
649 except OsError as error:
650 message = error.message
652 assert message == "Invalid name '.'."
654 try:
655 message = ""
656 Path("/k").with_name("/a")
657 except OsError as error:
658 message = error.message
660 assert message == "Invalid name '/a'."
662test with_stem():
663 assert Path("foo.log").with_stem("bar") == Path("bar.log")
664 assert Path("foo").with_stem("bar") == Path("bar")
665 assert Path("a/b.c").with_stem("d") == Path("a/d.c")
666 path = Path("a").with_stem("b.c")
667 assert path == Path("b.c")
668 assert path.stem() == "b"
669 assert path.extension() == ".c"
670 assert Path("a.b").with_stem("") == Path(".b")
672 try:
673 message = ""
674 Path(".").with_stem("a")
675 except OsError as error:
676 message = error.message
678 assert message == "Path '' has no name."
680 try:
681 message = ""
682 Path("/").with_stem("a")
683 except OsError as error:
684 message = error.message
686 assert message == "Path '/' has no name."
688 try:
689 message = ""
690 Path("a").with_stem("")
691 except OsError as error:
692 message = error.message
694 assert message == "Invalid name ''."
696test with_extension():
697 assert Path("foo.log").with_extension(".txt") == Path("foo.txt")
698 assert Path("a").with_extension(".b") == Path("a.b")
699 assert Path("a").with_extension("..b") == Path("a..b")
700 assert Path("a").with_extension("") == Path("a")
701 assert Path("a.b").with_extension("") == Path("a")
702 assert Path("a.b.c").with_extension("") == Path("a.b")
703 assert Path("a.b.c").with_extension("").with_extension("") == Path("a")
704 assert Path("a").with_extension("..") == Path("a..")
706 try:
707 message = ""
708 Path("a").with_extension("b")
709 except OsError as error:
710 message = error.message
712 assert message == "Invalid extension 'b'."
714 try:
715 message = ""
716 Path("a").with_extension(".")
717 except OsError as error:
718 message = error.message
720 assert message == "Invalid extension '.'."
722 try:
723 message = ""
724 Path("").with_extension(".b")
725 except OsError as error:
726 message = error.message
728 assert message == "Path '' has no name."
730test is_absolute():
731 assert Path("/").is_absolute()
732 assert Path("/a/b").is_absolute()
733 assert not Path("a/b").is_absolute()
734 assert not Path("b").is_absolute()
735 assert not Path("").is_absolute()
737test is_relative():
738 assert not Path("/").is_relative()
739 assert not Path("/a/b").is_relative()
740 assert Path("a/b").is_relative()
741 assert Path("b").is_relative()
742 assert Path("").is_relative()
744test touch():
745 path = Path("touch.txt")
746 path.rm(force=True)
748 path.touch()
749 assert path.exists()
750 assert path.read_text() == ""
752 path.write_text("Hi!")
753 assert path.read_text() == "Hi!"
754 path.touch()
755 assert path.read_text() == "Hi!"
757 path.rm(force=True)
759 try:
760 message = ""
761 Path("k/k/k/k/k/k/a").touch()
762 except OsError as error:
763 message = error.message
765 assert message == "Touch failed."