Hide keyboard shortcuts

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 

6 

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> 

16 

17static inline int chmod_wrapper(const char *name_p, u32 mode) 

18{ 

19 return chmod(name_p, mode); 

20} 

21""" 

22 

23class Path: 

24 """A file system path. 

25 

26 """ 

27 

28 _parts: [string] 

29 

30 func __init__(self, path: string): 

31 if path == "": 

32 path = "." 

33 

34 self._parts = [] 

35 

36 if path[0] == '/': 

37 self._parts.append("/") 

38 

39 for part in path.split("/"): 

40 if part in ["", "."]: 

41 continue 

42 

43 self._parts.append(part) 

44 

45 func ==(self, other: Path) -> bool: 

46 return self.to_string() == other.to_string() 

47 

48 func !=(self, other: Path) -> bool: 

49 return not (self == other) 

50 

51 func name(self) -> string: 

52 """The final component. 

53 

54 """ 

55 

56 if self._parts.length() == 0: 

57 return "" 

58 

59 name = self._parts[-1] 

60 

61 if name == "/": 

62 return "" 

63 

64 return name 

65 

66 func stem(self) -> string: 

67 """The final component without extension. 

68 

69 """ 

70 

71 extension_length = self.extension().length() 

72 

73 if extension_length > 0: 

74 return self.name()[:-extension_length] 

75 else: 

76 return self.name() 

77 

78 func extension(self) -> string: 

79 """The final component extension, including the dot. 

80 

81 """ 

82 

83 name = self.name() 

84 last_dot_index = name.find_reverse('.') 

85 

86 if last_dot_index <= 0 or last_dot_index == (name.length() - 1): 

87 return "" 

88 

89 return name[last_dot_index:] 

90 

91 func parent(self) -> Path: 

92 """The parent path. 

93 

94 """ 

95 

96 return Path("/".join(self._parts[:-1])) 

97 

98 func join(self, other: string) -> Path: 

99 """Join with other path. 

100 

101 """ 

102 

103 if self.name() == "" and self._parts.length() == 0: 

104 return Path("./" + other) 

105 else: 

106 return Path(self.to_string() + "/" + other) 

107 

108 func join(self, other: Path) -> Path: 

109 """Join with other path. 

110 

111 """ 

112 

113 return self.join(other.to_string()) 

114 

115 func is_absolute(self) -> bool: 

116 """Check if absolute. 

117 

118 """ 

119 

120 return self._parts.length() > 0 and self._parts[0] == "/" 

121 

122 func is_relative(self) -> bool: 

123 """Check if relative. 

124 

125 """ 

126 

127 return not self.is_absolute() 

128 

129 func with_name(self, name: string) -> Path: 

130 """Returns a new path with given name. 

131 

132 """ 

133 

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) 

140 

141 func with_stem(self, stem: string) -> Path: 

142 """Returns a new path with given stem. 

143 

144 """ 

145 

146 return self.with_name(stem + self.extension()) 

147 

148 func with_extension(self, extension: string) -> Path: 

149 """Returns a new path with given extension. 

150 

151 """ 

152 

153 if ((extension.length() > 0 and extension[0] != '.') 

154 or extension.length() == 1): 

155 raise OsError(f"Invalid extension '{extension}'.") 

156 

157 return self.with_name(self.stem() + extension) 

158 

159 func read_text(self) -> string: 

160 """Read file contents. 

161 

162 """ 

163 

164 return TextFile(self).read() 

165 

166 func write_text(self, data: string): 

167 """Write file contents. 

168 

169 """ 

170 

171 TextFile(self, "w").write(data) 

172 

173 func read_binary(self) -> bytes: 

174 """Read file contents. 

175 

176 """ 

177 

178 return BinaryFile(self).read() 

179 

180 func write_binary(self, data: bytes): 

181 """Write file contents. 

182 

183 """ 

184 

185 BinaryFile(self, "w").write(data) 

186 

187 func open_binary(self, mode: string = "r") -> BinaryFile: 

188 """Open binary file. 

189 

190 """ 

191 

192 return BinaryFile(self, mode) 

193 

194 func open_text(self, mode: string = "r") -> TextFile: 

195 """Open binary file. 

196 

197 """ 

198 

199 return TextFile(self, mode) 

200 

201 func exists(self) -> bool: 

202 """Check for existence. 

203 

204 """ 

205 

206 res: bool = False 

207 path_utf8 = to_utf8(self.to_string()) 

208 

209 c""" 

210 res = std::filesystem::exists((const char *)path_utf8.m_bytes->data()); 

211 """ 

212 

213 return res 

214 

215 func mkdir(self, exists_ok: bool = False): 

216 """Create directories. 

217 

218 Give `exists_ok` as ``True`` to ignore errors. 

219 

220 """ 

221 

222 path_utf8 = to_utf8(self.to_string()) 

223 message: string? = None 

224 

225 c""" 

226 std::error_code ec; 

227 bool ok; 

228 

229 ok = std::filesystem::create_directories((char *)path_utf8.m_bytes->data(), ec); 

230 

231 if (!ok) { 

232 if (ec) { 

233 message = String(ec.message()); 

234 } else if (!exists_ok) { 

235 message = String(strerror(EEXIST)); 

236 } 

237 } 

238 """ 

239 

240 if message is not None: 

241 raise OsError(message) 

242 

243 func chmod(self, mode: u32): 

244 """Change permissions. 

245 

246 """ 

247 

248 message: string? = None 

249 path_utf8 = to_utf8(self.to_string()) 

250 

251 c""" 

252 if (chmod_wrapper((const char *)path_utf8.m_bytes->data(), mode) != 0) { 

253 message = String(strerror(errno)); 

254 } 

255 """ 

256 

257 if message is not None: 

258 raise OsError(message) 

259 

260 func ls(self) -> [Path]: 

261 """Returns all files and folders. 

262 

263 """ 

264 

265 dpath = self.to_string() 

266 

267 if dpath == "": 

268 dpath = "." 

269 

270 path_utf8 = to_utf8(dpath) 

271 entries: [Path] = [] 

272 

273 c""" 

274 char *path_p = (char *)path_utf8.m_bytes->data(); 

275 

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 """ 

280 

281 return entries 

282 

283 func rm(self, recursive: bool = False, force: bool = False): 

284 """Remove files and directories. 

285 

286 Give `recursive` as ``True`` to remove all files and folders 

287 recursivly. 

288 

289 Give `force` as ``True`` to ignore errors. 

290 

291 """ 

292 

293 path_utf8 = to_utf8(self.to_string()) 

294 message: string? = None 

295 

296 c""" 

297 std::error_code ec; 

298 bool ok; 

299 char *path_p = (char *)path_utf8.m_bytes->data(); 

300 

301 if (recursive) { 

302 ok = (std::filesystem::remove_all(path_p, ec) > 0); 

303 } else { 

304 ok = std::filesystem::remove(path_p, ec); 

305 } 

306 

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 """ 

317 

318 if message is not None: 

319 raise OsError(message) 

320 

321 func touch(self): 

322 """Update modification time and creates if missing. 

323 

324 """ 

325 

326 path_utf8 = to_utf8(self.to_string()) 

327 fd = 0 

328 

329 c""" 

330 fd = open((char *)path_utf8.m_bytes->data(), O_CREAT, 0666); 

331 

332 if (fd != -1) { 

333 close(fd); 

334 } 

335 """ 

336 

337 if fd == -1: 

338 raise OsError(f"Touch failed.") 

339 

340 func cd(self): 

341 """Change directory. 

342 

343 """ 

344 

345 path_utf8 = to_utf8(self.to_string()) 

346 message: string? = None 

347 

348 c""" 

349 std::error_code ec; 

350 

351 std::filesystem::current_path((char *)path_utf8.m_bytes->data(), ec); 

352 

353 if (ec) { 

354 message = String(ec.message()); 

355 } 

356 """ 

357 

358 if message is not None: 

359 raise OsError(message) 

360 

361 func cp(self, dst: Path, recursive: bool = False): 

362 """Copy files and directories. 

363 

364 """ 

365 

366 src_utf8 = to_utf8(self.to_string()) 

367 dst_utf8 = to_utf8(str(dst)) 

368 message: string? = None 

369 

370 c""" 

371 std::error_code ec; 

372 std::filesystem::copy_options options; 

373 

374 if (recursive) { 

375 options = std::filesystem::copy_options::recursive; 

376 } else { 

377 options = std::filesystem::copy_options::none; 

378 } 

379 

380 std::filesystem::copy((char *)src_utf8.m_bytes->data(), 

381 (char *)dst_utf8.m_bytes->data(), 

382 options, 

383 ec); 

384 

385 if (ec) { 

386 message = String(ec.message()); 

387 } 

388 """ 

389 

390 if message is not None: 

391 raise OsError(message) 

392 

393 func mv(self, dst: Path): 

394 """Move files and directories. 

395 

396 """ 

397 

398 self.cp(dst, recursive=True) 

399 self.rm(recursive=True, force=True) 

400 

401 func to_string(self) -> string: 

402 """Return the path as a string. 

403 

404 """ 

405 

406 return "/".join(self._parts).replace("//", "/") 

407 

408 func __str__(self) -> string: 

409 return self.to_string() 

410 

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" 

416 

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" 

425 

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." 

435 

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() == "" 

446 

447test parent(): 

448 assert Path("foo/bar.txt").parent() == Path("foo") 

449 assert Path(".").parent() == Path(".") 

450 assert Path("foo").parent().parent() == Path(".") 

451 

452test join(): 

453 path = Path("foo").join("bar.txt") 

454 assert path.to_string() == "foo/bar.txt" 

455 

456 path = Path("foo").join("/bar.txt") 

457 assert path.to_string() == "foo/bar.txt" 

458 

459 path = Path("foo/").join("/bar.txt") 

460 assert path.to_string() == "foo/bar.txt" 

461 

462 path = Path("foo").join(Path("bar.txt")) 

463 assert path.to_string() == "foo/bar.txt" 

464 

465 path = Path(".").join(Path("bar.txt")) 

466 assert path.to_string() == "bar.txt" 

467 

468 path = Path("./").join(Path("/bar.txt")) 

469 assert path.to_string() == "bar.txt" 

470 

471 path = Path("").join(Path("/bar.txt")) 

472 assert path.to_string() == "bar.txt" 

473 

474 path = Path("/").join(Path("bar.txt")) 

475 assert path.to_string() == "/bar.txt" 

476 

477 path = Path("").join(Path("/")) 

478 assert path.to_string() == "" 

479 

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() 

487 

488 entries = directory.ls() 

489 

490 assert entries.length() == 3 

491 assert Path("a") in entries 

492 assert Path("b") in entries 

493 assert Path("c") in entries 

494 

495 directory.rm(recursive=True, force=True) 

496 

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) 

503 

504 try: 

505 message = "" 

506 directory.rm() 

507 except OsError as error: 

508 message = error.message 

509 

510 assert message == "Directory not empty" 

511 

512 directory.rm(recursive=True) 

513 

514test rm_single_file(): 

515 filename = Path("test_rm_single_file") 

516 filename.touch() 

517 filename.rm() 

518 

519 try: 

520 message = "" 

521 filename.rm() 

522 except OsError as error: 

523 message = error.message 

524 

525 assert message == "No such file or directory" 

526 

527 filename.rm(force=True) 

528 

529test chmod(): 

530 filename = Path("cmod") 

531 

532 filename.rm(force=True) 

533 

534 try: 

535 message = "" 

536 filename.chmod(0o666) 

537 except OsError as error: 

538 message = error.message 

539 

540 assert message == "No such file or directory" 

541 

542 filename.touch() 

543 filename.chmod(0o666) 

544 filename.rm() 

545 

546test mkdir(): 

547 directory = Path("adir") 

548 directory.rm(force=True) 

549 directory.mkdir(exists_ok=True) 

550 

551 try: 

552 message = "" 

553 directory.mkdir() 

554 except OsError as error: 

555 message = error.message 

556 

557 assert message == "File exists" 

558 

559test cd(): 

560 Path("ccc").rm(recursive=True, force=True) 

561 path = cwd() 

562 

563 try: 

564 message = "" 

565 Path("ccc").cd() 

566 except OsError as error: 

567 message = error.message 

568 

569 assert message == "No such file or directory" 

570 

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() 

578 

579 Path("ccc").rm(recursive=True, force=True) 

580 

581test cp(): 

582 Path("cp").rm(recursive=True, force=True) 

583 Path("cp-2").rm(recursive=True, force=True) 

584 

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() 

590 

591 Path("cp").cp(Path("cp-2"), recursive=True) 

592 

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() 

599 

600 Path("cp").rm(recursive=True, force=True) 

601 Path("cp-2").rm(recursive=True, force=True) 

602 

603test mv(): 

604 Path("mv").rm(recursive=True, force=True) 

605 Path("mv-2").rm(recursive=True, force=True) 

606 

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() 

612 

613 Path("mv").mv(Path("mv-2")) 

614 

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() 

619 

620 Path("mv-2").rm(recursive=True, force=True) 

621 

622test with_name(): 

623 try: 

624 assert Path("").with_name("foo.txt") == Path("") 

625 assert False 

626 except OsError: 

627 pass 

628 

629 assert Path("foo.log").with_name("foo.txt") == Path("foo.txt") 

630 assert Path("a/b").with_name("c") == Path("a/c") 

631 

632 try: 

633 Path("/").with_name("c") 

634 assert False 

635 except OsError: 

636 pass 

637 

638 try: 

639 message = "" 

640 Path("/k").with_name("") 

641 except OsError as error: 

642 message = error.message 

643 

644 assert message == "Invalid name ''." 

645 

646 try: 

647 message = "" 

648 Path("/k").with_name(".") 

649 except OsError as error: 

650 message = error.message 

651 

652 assert message == "Invalid name '.'." 

653 

654 try: 

655 message = "" 

656 Path("/k").with_name("/a") 

657 except OsError as error: 

658 message = error.message 

659 

660 assert message == "Invalid name '/a'." 

661 

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") 

671 

672 try: 

673 message = "" 

674 Path(".").with_stem("a") 

675 except OsError as error: 

676 message = error.message 

677 

678 assert message == "Path '' has no name." 

679 

680 try: 

681 message = "" 

682 Path("/").with_stem("a") 

683 except OsError as error: 

684 message = error.message 

685 

686 assert message == "Path '/' has no name." 

687 

688 try: 

689 message = "" 

690 Path("a").with_stem("") 

691 except OsError as error: 

692 message = error.message 

693 

694 assert message == "Invalid name ''." 

695 

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..") 

705 

706 try: 

707 message = "" 

708 Path("a").with_extension("b") 

709 except OsError as error: 

710 message = error.message 

711 

712 assert message == "Invalid extension 'b'." 

713 

714 try: 

715 message = "" 

716 Path("a").with_extension(".") 

717 except OsError as error: 

718 message = error.message 

719 

720 assert message == "Invalid extension '.'." 

721 

722 try: 

723 message = "" 

724 Path("").with_extension(".b") 

725 except OsError as error: 

726 message = error.message 

727 

728 assert message == "Path '' has no name." 

729 

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() 

736 

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() 

743 

744test touch(): 

745 path = Path("touch.txt") 

746 path.rm(force=True) 

747 

748 path.touch() 

749 assert path.exists() 

750 assert path.read_text() == "" 

751 

752 path.write_text("Hi!") 

753 assert path.read_text() == "Hi!" 

754 path.touch() 

755 assert path.read_text() == "Hi!" 

756 

757 path.rm(force=True) 

758 

759 try: 

760 message = "" 

761 Path("k/k/k/k/k/k/a").touch() 

762 except OsError as error: 

763 message = error.message 

764 

765 assert message == "Touch failed."