v.next interface for package:file/file.dart (#7)

* v.next interface for package:file/file.dart

This changes the interface to chiefly expose the FileSystem
abstraction and deal in native dart:io types where at all possible:

- Expose all dart:io file-system static methods as instance methods
  on `FileSystem`, to allow injectable implementations (e.g. in-memory,
  local, mock, etc.)
- Create FileSystemEntity, File, Directory, Link as abstract classes
  that implement their native counterparts and add any extra methods
  we choose in this library (initially, only `get fileSystem => Filesystem`)

By going this route, it implies that each FileSystem implementation will
need to provide types that implement both the sync and async APIs (since
they implement the interfaces in native dart:io). However, I still plan
to provide SynchronousFileSystem & AsynchronousFileSystem, which will simply
throw UnsupportedError for the APIs they don't support.

This change will allow existing libraries/apps to seamlessly start using
FileSystem as a drop-in replacement for existing direct dart:io usage.
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..dc33797
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,7 @@
+#### 0.2.0
+
+* New interface
+
+#### 0.1.0
+
+* Initial version
diff --git a/lib/file.dart b/lib/file.dart
index 8be8c77..d1a73c0 100644
--- a/lib/file.dart
+++ b/lib/file.dart
@@ -1,5 +1,5 @@
-library file;
-
-export 'package:file/src/backends/memory.dart';
-export 'package:file/src/memory_utils.dart' show MemoryFileStorage;
-export 'package:file/src/interface.dart';
+export 'src/interface/directory.dart';
+export 'src/interface/file_system_entity.dart';
+export 'src/interface/file_system.dart';
+export 'src/interface/file.dart';
+export 'src/interface/link.dart';
diff --git a/lib/src/interface.dart b/lib/src/interface.dart
deleted file mode 100644
index 1a6c987..0000000
--- a/lib/src/interface.dart
+++ /dev/null
@@ -1,9 +0,0 @@
-library file.src.interface.file;
-
-import 'dart:async';
-
-part 'interface/directory.dart';
-part 'interface/file.dart';
-part 'interface/file_system.dart';
-part 'interface/file_system_entity.dart';
-part 'interface/link.dart';
diff --git a/lib/src/interface/directory.dart b/lib/src/interface/directory.dart
index 09a3b65..c039f2c 100644
--- a/lib/src/interface/directory.dart
+++ b/lib/src/interface/directory.dart
@@ -1,11 +1,7 @@
-part of file.src.interface.file;
+import 'dart:io' as io;
+
+import 'file_system_entity.dart';
 
 /// A reference to a directory on the file system.
-abstract class Directory implements FileSystemEntity {
-  @override
-  Future<bool> exists() async {
-    return await fileSystem.type(path) == FileSystemEntityType.DIRECTORY;
-  }
-
-  Stream<FileSystemEntity> list({bool recursive: false});
+abstract class Directory implements FileSystemEntity, io.Directory {
 }
diff --git a/lib/src/interface/file.dart b/lib/src/interface/file.dart
index ed11a09..c755848 100644
--- a/lib/src/interface/file.dart
+++ b/lib/src/interface/file.dart
@@ -1,19 +1,7 @@
-part of file.src.interface.file;
+import 'dart:io' as io;
+
+import 'file_system_entity.dart';
 
 /// A reference to a file on the file system.
-abstract class File implements FileSystemEntity {
-  @override
-  Future<bool> exists() async {
-    return await fileSystem.type(path) == FileSystemEntityType.FILE;
-  }
-
-  Future<List<int>> readAsBytes();
-
-  Future<String> readAsString();
-
-  /// Writes [bytes] to the file.
-  Future<File> writeAsBytes(List<int> bytes);
-
-  /// Writes [contents] to the file.
-  Future<File> writeAsString(String contents);
+abstract class File implements FileSystemEntity, io.File {
 }
diff --git a/lib/src/interface/file_system.dart b/lib/src/interface/file_system.dart
index fca70b6..ed6216b 100644
--- a/lib/src/interface/file_system.dart
+++ b/lib/src/interface/file_system.dart
@@ -1,4 +1,8 @@
-part of file.src.interface.file;
+import 'dart:async';
+import 'dart:io' as io;
+
+import 'directory.dart';
+import 'file.dart';
 
 /// A generic representation of a file system.
 abstract class FileSystem {
@@ -8,15 +12,101 @@
   /// Returns a reference to a [File] at [path].
   File file(String path);
 
-  /// Finds the type of file system object that a [path] points to.
+  /// Creates a directory object pointing to the current working directory.
+  Directory get currentDirectory;
+
+  /// Sets the current working directory to the specified path. The new value
+  /// set can be either a [Directory] or a [String].
   ///
-  /// Returns a Future<FileSystemEntityType> that completes with the result.
+  /// Relative paths will be resolved by the underlying file system
+  /// implementation (meaning it is up to the underlying implementation to
+  /// decide whether to support relative paths).
+  set currentDirectory(path);
+
+  /// Asynchronously calls the operating system's stat() function on [path].
+  /// Returns a Future which completes with a [FileStat] object containing
+  /// the data returned by stat().
+  /// If the call fails, completes the future with a [FileStat] object with
+  /// .type set to FileSystemEntityType.NOT_FOUND and the other fields invalid.
+  Future<io.FileStat> stat(String path);
+
+  /// Calls the operating system's stat() function on [path].
+  /// Returns a [FileStat] object containing the data returned by stat().
+  /// If the call fails, returns a [FileStat] object with .type set to
+  /// FileSystemEntityType.NOT_FOUND and the other fields invalid.
+  io.FileStat statSync(String path);
+
+  /// Checks whether two paths refer to the same object in the
+  /// file system. Returns a [Future<bool>] that completes with the result.
+  ///
+  /// Comparing a link to its target returns false, as does comparing two links
+  /// that point to the same target.  To check the target of a link, use
+  /// Link.target explicitly to fetch it.  Directory links appearing
+  /// inside a path are followed, though, to find the file system object.
+  ///
+  /// Completes the returned Future with an error if one of the paths points
+  /// to an object that does not exist.
+  Future<bool> identical(String path1, String path2);
+
+  /// Synchronously checks whether two paths refer to the same object in the
+  /// file system.
+  ///
+  /// Comparing a link to its target returns false, as does comparing two links
+  /// that point to the same target.  To check the target of a link, use
+  /// Link.target explicitly to fetch it.  Directory links appearing
+  /// inside a path are followed, though, to find the file system object.
+  ///
+  /// Throws an error if one of the paths points to an object that does not
+  /// exist.
+  bool identicalSync(String path1, String path2);
+
+  /// Tests if [watch] is supported on the current system.
+  bool get isWatchSupported;
+
+  /// Finds the type of file system object that a [path] points to. Returns
+  /// a Future<FileSystemEntityType> that completes with the result.
   ///
   /// [FileSystemEntityType.LINK] will only be returned if [followLinks] is
-  /// `false`, otherwise symbolic links are resolved and the result type is
-  /// returned instead.
+  /// `false`, and [path] points to a link
   ///
   /// If the [path] does not point to a file system object or an error occurs
   /// then [FileSystemEntityType.NOT_FOUND] is returned.
-  Future<FileSystemEntityType> type(String path, {bool followLinks: true});
+  Future<io.FileSystemEntityType> type(String path, {bool followLinks: true});
+
+  /// Syncronously finds the type of file system object that a [path] points
+  /// to. Returns a [FileSystemEntityType].
+  ///
+  /// [FileSystemEntityType.LINK] will only be returned if [followLinks] is
+  /// `false`, and [path] points to a link
+  ///
+  /// If the [path] does not point to a file system object or an error occurs
+  /// then [FileSystemEntityType.NOT_FOUND] is returned.
+  io.FileSystemEntityType typeSync(String path, {bool followLinks: true});
+
+  /// Checks if [`type(path)`](type) returns [io.FileSystemEntityType.FILE].
+  Future<bool> isFile(String path) async =>
+      await type(path) == io.FileSystemEntityType.FILE;
+
+  /// Synchronously checks if [`type(path)`](type) returns
+  /// [io.FileSystemEntityType.FILE].
+  bool isFileSync(String path) =>
+      typeSync(path) == io.FileSystemEntityType.FILE;
+
+  /// Checks if [`type(path)`](type) returns [io.FileSystemEntityType.DIRECTORY].
+  Future<bool> isDirectory(String path) async =>
+      await type(path) == io.FileSystemEntityType.DIRECTORY;
+
+  /// Synchronously checks if [`type(path)`](type) returns
+  /// [io.FileSystemEntityType.DIRECTORY].
+  bool isDirectorySync(String path) =>
+      typeSync(path) == io.FileSystemEntityType.DIRECTORY;
+
+  /// Checks if [`type(path)`](type) returns [io.FileSystemEntityType.LINK].
+  Future<bool> isLink(String path) async
+      => await type(path) == io.FileSystemEntityType.LINK;
+
+  /// Synchronously checks if [`type(path)`](type) returns
+  /// [io.FileSystemEntityType.LINK].
+  bool isLinkSync(String path) =>
+      typeSync(path) == io.FileSystemEntityType.LINK;
 }
diff --git a/lib/src/interface/file_system_entity.dart b/lib/src/interface/file_system_entity.dart
index 3b3415f..5b6a84e 100644
--- a/lib/src/interface/file_system_entity.dart
+++ b/lib/src/interface/file_system_entity.dart
@@ -1,92 +1,9 @@
-part of file.src.interface.file;
+import 'dart:io' as io;
+
+import 'file_system.dart';
 
 /// The common super class for [File], [Directory], and [Link] objects.
-///
-/// [FileSystemEntity] objects are returned from file listing operations. To
-/// determine if a [FileSystemEntity] is a [File], [Directory], or [Link]
-/// perform a type check:
-///     if (entity is File) (entity as File).remove();
-///
-/// Unlike the native `dart:io` package, all operations are asynchronous. This
-/// is because some backing implementations communicate over the network.
-abstract class FileSystemEntity {
-  Future<FileSystemEntity> copy(String newPath);
-
-  /// Creates the entity this reference represents.
-  ///
-  /// Returns a [Future<FileSystemEntity>] that completes with a reference to
-  /// the file system object that was created.
-  ///
-  /// If [recursive] is `false`, the default, the object is created only if all
-  /// directories in the [path] actually exist. If [recursive] is `true`, all
-  /// non-existing path components are created.
-  ///
-  /// Existing objects are left untouched by create.
-  ///
-  /// May complete with a [Future<FileSystemEntityException>] if the operation
-  /// fails.
-  Future<FileSystemEntity> create({bool recursive: false});
-
-  /// Deletes this [FileSystemEntity].
-  ///
-  /// If the [FileSystemEntity] is a [Directory], and if [recursive] is `false`,
-  /// the directory must be empty. Otherwise, if [recursive] is `true`, the
-  /// directory and all sub-directories and files in the directories are
-  /// deleted.
-  ///
-  /// If [recursive] is true, the [FileSystemEntity] is deleted even if the type
-  /// of the [FileSystemEntity] doesn't match the content of the file system.
-  /// This behavior allows delete to be used to unconditionally delete any file
-  /// system object.
-  ///
-  /// Returns a [Future<FileSystemEntity>] that completes with this
-  /// [FileSystemEntity] when the deletion is done. If the FileSystemEntity
-  /// cannot be deleted, the future completes with an exception.
-  Future<FileSystemEntity> delete({bool recursive: false});
-
-  /// Checks whether the file system entity with this [path] exists.
-  ///
-  /// Returns a [Future<bool>] that completes with the result.
-  ///
-  /// **NOTE**: Since the method is implemented on every super class, it will
-  /// complete with false if a *different* type of object exists. To check if
-  /// *any* object exists at a given path, use [FileSystem.type] method.
-  Future<bool> exists();
-
-  /// The backing implementation of this file system object.
+abstract class FileSystemEntity implements io.FileSystemEntity {
+  /// Returns the file system responsible for this entity.
   FileSystem get fileSystem;
-
-  /// Returns a reference to the parent directory of this file system object.
-  ///
-  /// If this object is a root directory, returns `null`.
-  Directory get parent;
-
-  /// The absolute location this entity refers to.
-  String get path;
-
-  Future<FileSystemEntity> rename(String newPath);
-}
-
-/// Exception thrown when a file operation fails.
-class FileSystemEntityException implements Exception {
-  final String message;
-
-  /// The file system path on which the error occurred.
-  ///
-  /// Can be `null` if the exception does not relate directly to an object.
-  final String path;
-
-  FileSystemEntityException(this.message, this.path);
-
-  String toString() => '${FileSystemEntityException}: $message: $path';
-}
-
-/// The type of an entity on the file system.
-enum FileSystemEntityType { DIRECTORY, FILE, LINK, NOT_FOUND }
-
-/// Returns the parent directory of [path].
-String getParentPath(String path) {
-  return path == '/' || path == ''
-      ? null
-      : path.substring(0, path.lastIndexOf('/'));
 }
diff --git a/lib/src/interface/link.dart b/lib/src/interface/link.dart
index 2e064d2..88dcf3e 100644
--- a/lib/src/interface/link.dart
+++ b/lib/src/interface/link.dart
@@ -1,9 +1,7 @@
-part of file.src.interface.file;
+import 'dart:io' as io;
+
+import 'file_system_entity.dart';
 
 /// A reference to a symbolic link on the file system.
-abstract class Link implements FileSystemEntity {
-  @override
-  Future<bool> exists() async {
-    return await fileSystem.type(path) == FileSystemEntityType.LINK;
-  }
+abstract class Link implements FileSystemEntity, io.Link {
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index 78cbbf2..91f302f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -2,9 +2,10 @@
 authors:
 - Matan Lurey <matanl@google.com>
 - Yegor Jbanov <yjbanov@google.com>
+- Todd Volkert <tvolkert@google.com>
 description: A pluggable, mockable file system abstraction for Dart.
 homepage: https://github.com/matanlurey/file
-version: 0.1.2
+version: 0.2.0
 dependencies:
 dev_dependencies:
   dart_style: