Skip to content

Commit 6215560

Browse files
authored
Posix rename (#199)
1 parent b40c420 commit 6215560

File tree

9 files changed

+176
-27
lines changed

9 files changed

+176
-27
lines changed

pkgs/io_file/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ See
2727
| read file (bytes) | [ ] | [ ] | [ ] | [ ] | [ ] |
2828
| read file (lines) | [ ] | [ ] | [ ] | [ ] | [ ] |
2929
| read file (string) | [ ] | [ ] | [ ] | [ ] | [ ] |
30-
| rename | [ ] | [ ] | [ ] | [ ] | [ ] |
30+
| rename | [ ] | [X] | [ ] | [X] | [ ] |
3131
| set permissions | [ ] | [ ] | [ ] | [ ] | [ ] |
3232
| write file (bytes) | [ ] | [ ] | [ ] | [ ] | [ ] |
3333
| write file (string) | [ ] | [ ] | [ ] | [ ] | [ ] |

pkgs/io_file/example/io_file_example.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'package:io_file/io_file.dart';
5+
import 'package:io_file/posix_file_system.dart';
66

77
void main() {
8-
var awesome = Awesome();
9-
print('awesome: ${awesome.isAwesome}');
8+
// TODO(brianquinlan): Create a better example.
9+
PosixFileSystem().rename('foo.txt', 'bar.txt');
1010
}

pkgs/io_file/lib/io_file.dart

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,4 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
/// Support for doing something awesome.
6-
///
7-
/// More dartdocs go here.
8-
library;
9-
10-
export 'src/io_file_base.dart';
11-
12-
// TODO: Export any libraries intended for clients of this package.
5+
export 'src/file_system.dart';

pkgs/io_file/lib/src/io_file_base.dart renamed to pkgs/io_file/lib/posix_file_system.dart

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,4 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
// TODO: Put public facing types in this file.
6-
7-
/// Checks if you are awesome. Spoiler: you are.
8-
class Awesome {
9-
bool get isAwesome => true;
10-
}
5+
export 'src/posix_file_system.dart';

pkgs/io_file/lib/src/file_system.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
/// An abstract representation of a file system.
6+
base class FileSystem {
7+
/// Renames, and possibly moves a file system object from one path to another.
8+
///
9+
/// If `newPath` is a relative path, it is resolved against the current
10+
/// working directory. This means that simply changing the name of a file,
11+
/// but keeping it the original directory, requires creating a new complete
12+
/// path with the new name at the end.
13+
///
14+
///TODO(brianquinlan): add an example here.
15+
///
16+
/// On some platforms, a rename operation cannot move a file between
17+
/// different file systems. If that is the case, instead copy the file to the
18+
/// new location and then remove the original.
19+
///
20+
// If `newPath` identifies an existing file or link, that entity is removed
21+
// first. If `newPath` identifies an existing directory, the operation
22+
// fails and raises [PathExistsException].
23+
void rename(String oldPath, String newPath) {
24+
throw UnsupportedError('rename');
25+
}
26+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io' as io;
6+
7+
import 'package:stdlibc/stdlibc.dart' as stdlibc;
8+
9+
import 'file_system.dart';
10+
11+
Exception _getError(int errno, String message, String path) {
12+
//TODO(brianquinlan): In the long-term, do we need to avoid exceptions that
13+
// are part of `dart:io`? Can we move those exceptions into a different
14+
// namespace?
15+
final osError = io.OSError(stdlibc.strerror(errno) ?? '', errno);
16+
17+
if (errno == stdlibc.EPERM || errno == stdlibc.EACCES) {
18+
return io.PathAccessException(path, osError, message);
19+
} else if (errno == stdlibc.EEXIST) {
20+
return io.PathExistsException(path, osError, message);
21+
} else if (errno == stdlibc.ENOENT) {
22+
return io.PathNotFoundException(path, osError, message);
23+
} else {
24+
return io.FileSystemException(message, path, osError);
25+
}
26+
}
27+
28+
/// A [FileSystem] implementation for POSIX systems (e.g. Android, iOS, Linux,
29+
/// macOS).
30+
base class PosixFileSystem extends FileSystem {
31+
@override
32+
void rename(String oldPath, String newPath) {
33+
// See https://pubs.opengroup.org/onlinepubs/000095399/functions/rename.html
34+
if (stdlibc.rename(oldPath, newPath) != 0) {
35+
final errno = stdlibc.errno;
36+
throw _getError(errno, 'rename failed', oldPath);
37+
}
38+
}
39+
}

pkgs/io_file/pubspec.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@ publish_to: none
77
environment:
88
sdk: ^3.7.0
99

10+
dependencies:
11+
stdlibc:
12+
git:
13+
# Change this to a released version.
14+
url: https://github.com/canonical/stdlibc.dart.git
15+
win32: ^5.11.0
16+
1017
dev_dependencies:
1118
dart_flutter_team_lints: ^3.4.0
1219
test: ^1.24.0

pkgs/io_file/test/io_file_test.dart renamed to pkgs/io_file/test/file_system_test.dart

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,9 @@ import 'package:io_file/io_file.dart';
66
import 'package:test/test.dart';
77

88
void main() {
9-
group('A group of tests', () {
10-
final awesome = Awesome();
11-
12-
setUp(() {
13-
// Additional setup goes here.
14-
});
15-
16-
test('First Test', () {
17-
expect(awesome.isAwesome, isTrue);
9+
group('FileSystem', () {
10+
test('rename', () {
11+
expect(() => FileSystem().rename('a', 'b'), throwsUnsupportedError);
1812
});
1913
});
2014
}

pkgs/io_file/test/rename_test.dart

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@TestOn('posix')
6+
library;
7+
8+
import 'dart:io';
9+
10+
import 'package:io_file/posix_file_system.dart';
11+
import 'package:test/test.dart';
12+
13+
void main() {
14+
group('move', () {
15+
late String tmp;
16+
17+
setUp(
18+
() => tmp = Directory.systemTemp.createTempSync('move').absolute.path,
19+
);
20+
21+
tearDown(() => Directory(tmp).deleteSync(recursive: true));
22+
23+
//TODO(brianquinlan): test with a very long path.
24+
25+
test('move file absolute path', () {
26+
final path1 = '$tmp/file1';
27+
final path2 = '$tmp/file2';
28+
29+
File(path1).writeAsStringSync('Hello World');
30+
PosixFileSystem().rename(path1, path2);
31+
expect(File(path1).existsSync(), isFalse);
32+
expect(File(path2).existsSync(), isTrue);
33+
});
34+
35+
test('move file to existing', () {
36+
final path1 = '$tmp/file1';
37+
final path2 = '$tmp/file2';
38+
39+
File(path1).writeAsStringSync('Hello World #1');
40+
File(path2).writeAsStringSync('Hello World #2');
41+
PosixFileSystem().rename(path1, path2);
42+
expect(File(path1).existsSync(), isFalse);
43+
expect(File(path2).readAsStringSync(), 'Hello World #1');
44+
});
45+
46+
test('move directory absolute path', () {
47+
final path1 = '$tmp/dir1';
48+
final path2 = '$tmp/dir2';
49+
50+
Directory(path1).createSync(recursive: true);
51+
PosixFileSystem().rename(path1, path2);
52+
expect(Directory(path1).existsSync(), isFalse);
53+
expect(Directory(path2).existsSync(), isTrue);
54+
});
55+
56+
test('move non-existant', () {
57+
final path1 = '$tmp/file1';
58+
final path2 = '$tmp/file2';
59+
60+
expect(
61+
() => PosixFileSystem().rename(path1, path2),
62+
throwsA(
63+
isA<PathNotFoundException>()
64+
.having((e) => e.message, 'message', 'rename failed')
65+
.having(
66+
(e) => e.osError?.errorCode,
67+
'errorCode',
68+
2, // ENOENT
69+
),
70+
),
71+
);
72+
});
73+
74+
test('move to existant directory', () {
75+
final path1 = '$tmp/file1';
76+
final path2 = '$tmp/dir1';
77+
78+
File(path1).writeAsStringSync('Hello World');
79+
Directory(path2).createSync(recursive: true);
80+
81+
expect(
82+
() => PosixFileSystem().rename(path1, path2),
83+
throwsA(
84+
isA<FileSystemException>()
85+
.having((e) => e.message, 'message', 'rename failed')
86+
.having(
87+
(e) => e.osError?.errorCode,
88+
'errorCode',
89+
21, // EISDIR
90+
),
91+
),
92+
);
93+
});
94+
});
95+
}

0 commit comments

Comments
 (0)