Skip to content

Commit db0b529

Browse files
authored
Make MemoryFileSystem support Windows-style paths (#82)
Fixes flutter#68
1 parent 21dfcff commit db0b529

12 files changed

+242
-71
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#### 4.0.1
22

33
* General library cleanup
4+
* Add `style` support in `MemoryFileSystem`, so that callers can choose to
5+
have a memory file system with windows-like paths. [#68]
6+
(https://github.com/google/file.dart/issues/68)
47

58
#### 4.0.0
69

lib/src/backends/chroot/chroot_file_system.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ class ChrootFileSystem extends FileSystem {
262262
bool followLinks: true,
263263
_NotFoundBehavior notFound: _NotFoundBehavior.allow,
264264
}) {
265+
if (path.isEmpty) {
266+
throw common.noSuchFileOrDirectory(path);
267+
}
268+
265269
p.Context ctx = this.path;
266270
String root = _localRoot;
267271
List<String> parts, ledger;

lib/src/backends/memory.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
export 'memory/memory_file_system.dart' show MemoryFileSystem;
6+
export 'memory/style.dart' show FileSystemStyle, StyleableFileSystem;

lib/src/backends/memory/common.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44

55
import 'package:file/src/common.dart' as common;
66

7-
/// The file separator.
8-
const String separator = '/';
9-
107
/// Generates a path to use in error messages.
118
typedef dynamic PathGenerator();
129

lib/src/backends/memory/memory_directory.dart

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import 'memory_file.dart';
1414
import 'memory_file_system_entity.dart';
1515
import 'memory_link.dart';
1616
import 'node.dart';
17+
import 'style.dart';
1718
import 'utils.dart' as utils;
1819

1920
/// Internal implementation of [Directory].
@@ -30,7 +31,10 @@ class MemoryDirectory extends MemoryFileSystemEntity
3031
io.FileSystemEntityType get expectedType => io.FileSystemEntityType.DIRECTORY;
3132

3233
@override
33-
Uri get uri => new Uri.directory(path);
34+
Uri get uri {
35+
return new Uri.directory(path,
36+
windows: fileSystem.style == FileSystemStyle.windows);
37+
}
3438

3539
@override
3640
bool existsSync() => backingOrNull?.stat?.type == expectedType;
@@ -121,7 +125,9 @@ class MemoryDirectory extends MemoryFileSystemEntity
121125
List<_PendingListTask> tasks = <_PendingListTask>[
122126
new _PendingListTask(
123127
node,
124-
path.endsWith(separator) ? path.substring(0, path.length - 1) : path,
128+
path.endsWith(fileSystem.path.separator)
129+
? path.substring(0, path.length - 1)
130+
: path,
125131
new Set<LinkNode>(),
126132
),
127133
];

lib/src/backends/memory/memory_file_system.dart

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,53 @@ import 'memory_file.dart';
1414
import 'memory_file_stat.dart';
1515
import 'memory_link.dart';
1616
import 'node.dart';
17+
import 'style.dart';
1718
import 'utils.dart' as utils;
1819

1920
const String _thisDir = '.';
2021
const String _parentDir = '..';
2122

2223
/// An implementation of [FileSystem] that exists entirely in memory with an
2324
/// internal representation loosely based on the Filesystem Hierarchy Standard.
24-
/// Notably, this means that this implementation will not look like a Windows
25-
/// file system even if it's being run on a Windows host operating system.
2625
///
2726
/// [MemoryFileSystem] is suitable for mocking and tests, as well as for
2827
/// caching or staging before writing or reading to a live system.
2928
///
3029
/// This implementation of the [FileSystem] interface does not directly use
3130
/// any `dart:io` APIs; it merely uses the library's enum values and interfaces.
3231
/// As such, it is suitable for use in the browser.
33-
abstract class MemoryFileSystem implements FileSystem {
32+
abstract class MemoryFileSystem implements StyleableFileSystem {
3433
/// Creates a new `MemoryFileSystem`.
3534
///
3635
/// The file system will be empty, and the current directory will be the
3736
/// root directory.
38-
factory MemoryFileSystem() = _MemoryFileSystem;
37+
///
38+
/// If [style] is specified, the file system will use the specified path
39+
/// style. The default is [FileSystemStyle.posix].
40+
factory MemoryFileSystem({FileSystemStyle style}) = _MemoryFileSystem;
3941
}
4042

4143
/// Internal implementation of [MemoryFileSystem].
4244
class _MemoryFileSystem extends FileSystem
4345
implements MemoryFileSystem, NodeBasedFileSystem {
4446
RootNode _root;
4547
String _systemTemp;
46-
String _cwd = separator;
48+
p.Context _context;
4749

48-
/// Creates a new `MemoryFileSystem`.
49-
///
50-
/// The file system will be empty, and the current directory will be the
51-
/// root directory.
52-
_MemoryFileSystem() {
50+
_MemoryFileSystem({this.style: FileSystemStyle.posix})
51+
: assert(style != null) {
5352
_root = new RootNode(this);
53+
_context = style.contextFor(style.root);
5454
}
5555

56+
@override
57+
final FileSystemStyle style;
58+
5659
@override
5760
RootNode get root => _root;
5861

5962
@override
60-
String get cwd => _cwd;
63+
String get cwd => _context.current;
6164

6265
@override
6366
Directory directory(dynamic path) => new MemoryDirectory(this, getPath(path));
@@ -69,19 +72,19 @@ class _MemoryFileSystem extends FileSystem
6972
Link link(dynamic path) => new MemoryLink(this, getPath(path));
7073

7174
@override
72-
p.Context get path => new p.Context(style: p.Style.posix, current: _cwd);
75+
p.Context get path => _context;
7376

7477
/// Gets the system temp directory. This directory will be created on-demand
7578
/// in the root of the file system. Once created, its location is fixed for
7679
/// the life of the process.
7780
@override
7881
Directory get systemTempDirectory {
79-
_systemTemp ??= directory(separator).createTempSync('.tmp_').path;
82+
_systemTemp ??= directory(style.root).createTempSync('.tmp_').path;
8083
return directory(_systemTemp)..createSync();
8184
}
8285

8386
@override
84-
Directory get currentDirectory => directory(_cwd);
87+
Directory get currentDirectory => directory(cwd);
8588

8689
@override
8790
set currentDirectory(dynamic path) {
@@ -98,8 +101,8 @@ class _MemoryFileSystem extends FileSystem
98101
Node node = findNode(value);
99102
checkExists(node, () => value);
100103
utils.checkIsDir(node, () => value);
101-
assert(utils.isAbsolute(value));
102-
_cwd = value;
104+
assert(_context.isAbsolute(value));
105+
_context = style.contextFor(value);
103106
}
104107

105108
@override
@@ -154,7 +157,7 @@ class _MemoryFileSystem extends FileSystem
154157
/// Gets the node backing for the current working directory. Note that this
155158
/// can return null if the directory has been deleted or moved from under our
156159
/// feet.
157-
DirectoryNode get _current => findNode(_cwd);
160+
DirectoryNode get _current => findNode(cwd);
158161

159162
@override
160163
Node findNode(
@@ -169,13 +172,15 @@ class _MemoryFileSystem extends FileSystem
169172
throw new ArgumentError.notNull('path');
170173
}
171174

172-
if (utils.isAbsolute(path)) {
175+
if (_context.isAbsolute(path)) {
173176
reference = _root;
177+
path = path.substring(style.drive.length);
174178
} else {
175179
reference ??= _current;
176180
}
177181

178-
List<String> parts = path.split(separator)..removeWhere(utils.isEmpty);
182+
List<String> parts = path.split(style.separator)
183+
..removeWhere(utils.isEmpty);
179184
DirectoryNode directory = reference.directory;
180185
Node child = directory;
181186

@@ -200,7 +205,9 @@ class _MemoryFileSystem extends FileSystem
200205
pathWithSymlinks.add(basename);
201206
}
202207

203-
PathGenerator subpath = utils.subpath(parts, 0, i);
208+
// Generates a subpath for the current segment.
209+
String subpath() => parts.sublist(0, i + 1).join(_context.separator);
210+
204211
if (utils.isLink(child) && (i < finalSegment || followTailLink)) {
205212
if (visitLinks || segmentVisitor == null) {
206213
if (segmentVisitor != null) {

lib/src/backends/memory/memory_file_system_entity.dart

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import 'package:meta/meta.dart';
1212
import 'common.dart';
1313
import 'memory_directory.dart';
1414
import 'node.dart';
15+
import 'style.dart';
1516
import 'utils.dart' as utils;
1617

1718
/// Validator function for use with `_renameSync`. This will be invoked if the
@@ -89,7 +90,10 @@ abstract class MemoryFileSystemEntity implements FileSystemEntity {
8990
}
9091

9192
@override
92-
Uri get uri => new Uri.file(path);
93+
Uri get uri {
94+
return new Uri.file(path,
95+
windows: fileSystem.style == FileSystemStyle.windows);
96+
}
9397

9498
@override
9599
Future<bool> exists() async => existsSync();
@@ -99,16 +103,21 @@ abstract class MemoryFileSystemEntity implements FileSystemEntity {
99103

100104
@override
101105
String resolveSymbolicLinksSync() {
106+
if (path.isEmpty) {
107+
throw common.noSuchFileOrDirectory(path);
108+
}
102109
List<String> ledger = <String>[];
103110
if (isAbsolute) {
104-
ledger.add('');
111+
ledger.add(fileSystem.style.drive);
105112
}
106113
Node node = fileSystem.findNode(path,
107114
pathWithSymlinks: ledger, followTailLink: true);
108115
checkExists(node, () => path);
109-
String resolved = ledger.join(separator);
110-
if (!utils.isAbsolute(resolved)) {
111-
resolved = fileSystem.cwd + separator + resolved;
116+
String resolved = ledger.join(fileSystem.path.separator);
117+
if (resolved == fileSystem.style.drive) {
118+
resolved = fileSystem.style.root;
119+
} else if (!fileSystem.path.isAbsolute(resolved)) {
120+
resolved = fileSystem.cwd + fileSystem.path.separator + resolved;
112121
}
113122
return fileSystem.path.normalize(resolved);
114123
}
@@ -137,12 +146,12 @@ abstract class MemoryFileSystemEntity implements FileSystemEntity {
137146
throw new UnsupportedError('Watching not supported in MemoryFileSystem');
138147

139148
@override
140-
bool get isAbsolute => utils.isAbsolute(path);
149+
bool get isAbsolute => fileSystem.path.isAbsolute(path);
141150

142151
@override
143152
FileSystemEntity get absolute {
144153
String absolutePath = path;
145-
if (!utils.isAbsolute(absolutePath)) {
154+
if (!fileSystem.path.isAbsolute(absolutePath)) {
146155
absolutePath = fileSystem.path.join(fileSystem.cwd, absolutePath);
147156
}
148157
return clone(absolutePath);

lib/src/backends/memory/node.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'package:file/src/io.dart' as io;
77

88
import 'common.dart';
99
import 'memory_file_stat.dart';
10+
import 'style.dart';
1011

1112
/// Visitor callback for use with [NodeBasedFileSystem.findNode].
1213
///
@@ -36,7 +37,7 @@ typedef Node SegmentVisitor(
3637

3738
/// A [FileSystem] whose internal structure is made up of a tree of [Node]
3839
/// instances, rooted at a single node.
39-
abstract class NodeBasedFileSystem implements FileSystem {
40+
abstract class NodeBasedFileSystem implements StyleableFileSystem {
4041
/// The root node.
4142
RootNode get root;
4243

lib/src/backends/memory/style.dart

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright (c) 2018, 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 'package:file/file.dart';
6+
import 'package:path/path.dart' as p;
7+
8+
/// Class that represents the path style that a memory file system should
9+
/// adopt.
10+
///
11+
/// This is primarily useful if you want to test how your code will behave
12+
/// when faced with particular paths or particular path separator characters.
13+
/// For instance, you may want to test that your code will work on Windows,
14+
/// while still using a memory file system in order to gain hermeticity in your
15+
/// tests.
16+
abstract class FileSystemStyle {
17+
const FileSystemStyle._();
18+
19+
/// Mimics the Unix file system style.
20+
///
21+
/// * This style does not have the notion of drives
22+
/// * All file system paths are rooted at `/`
23+
/// * The path separator is `/`
24+
///
25+
/// An example path in this style is `/path/to/file`.
26+
static const FileSystemStyle posix = const _Posix();
27+
28+
/// Mimics the Windows file system style.
29+
///
30+
/// * This style mounts its root folder on a single root drive (`C:`)
31+
/// * All file system paths are rooted at `C:\`
32+
/// * The path separator is `\`
33+
///
34+
/// An example path in this style is `C:\path\to\file`.
35+
static const FileSystemStyle windows = const _Windows();
36+
37+
/// The drive upon which the root directory is mounted.
38+
///
39+
/// While real-world file systems that have the notion of drives will support
40+
/// multiple drives per system, memory file system will only support one
41+
/// root drive.
42+
///
43+
/// This will be the empty string for styles that don't have the notion of
44+
/// drives (e.g. [posix]).
45+
String get drive;
46+
47+
/// The String that represents the delineation between a directory and its
48+
/// children.
49+
String get separator;
50+
51+
/// The string that represents the root of the file system.
52+
///
53+
/// Memory file system is always single-rooted.
54+
String get root => '$drive$separator';
55+
56+
/// Gets an object useful for manipulating paths in this style.
57+
///
58+
/// Relative path manipulations will be relative to the specified [path].
59+
p.Context contextFor(String path);
60+
}
61+
62+
class _Posix extends FileSystemStyle {
63+
const _Posix() : super._();
64+
65+
@override
66+
String get drive => '';
67+
68+
@override
69+
String get separator {
70+
return p.Style.posix.separator; // ignore: deprecated_member_use
71+
}
72+
73+
@override
74+
p.Context contextFor(String path) =>
75+
new p.Context(style: p.Style.posix, current: path);
76+
}
77+
78+
class _Windows extends FileSystemStyle {
79+
const _Windows() : super._();
80+
81+
@override
82+
String get drive => 'C:';
83+
84+
@override
85+
String get separator {
86+
return p.Style.windows.separator; // ignore: deprecated_member_use
87+
}
88+
89+
@override
90+
p.Context contextFor(String path) =>
91+
new p.Context(style: p.Style.windows, current: path);
92+
}
93+
94+
/// A file system that supports different styles.
95+
abstract class StyleableFileSystem implements FileSystem {
96+
/// The style used by this file system.
97+
FileSystemStyle get style;
98+
}

0 commit comments

Comments
 (0)