Broaden legal types for directory(), file(), and link() methods (#54)

This changes the FileSystem entity creation methods to allow
`String`, `Uri`, or `FileSystemEntity` as arguments.

This not only provides parity with dart:io's `fromUri()` factory
constructors, but it allows the caller to easily wrap a native
dart:io entity.
diff --git a/lib/src/backends/chroot.dart b/lib/src/backends/chroot.dart
index 20b875f..dbaa698 100644
--- a/lib/src/backends/chroot.dart
+++ b/lib/src/backends/chroot.dart
@@ -4,6 +4,7 @@
 import 'dart:convert';
 
 import 'package:file/file.dart';
+import 'package:file/src/common.dart' as common;
 import 'package:file/src/forwarding.dart';
 import 'package:file/src/io.dart' as io;
 import 'package:path/path.dart' as p;
diff --git a/lib/src/backends/chroot/chroot_file_system.dart b/lib/src/backends/chroot/chroot_file_system.dart
index 2263df0..dad728f 100644
--- a/lib/src/backends/chroot/chroot_file_system.dart
+++ b/lib/src/backends/chroot/chroot_file_system.dart
@@ -54,13 +54,13 @@
   String get _localRoot => p.rootPrefix(root);
 
   @override
-  Directory directory(String path) => new _ChrootDirectory(this, path);
+  Directory directory(path) => new _ChrootDirectory(this, common.getPath(path));
 
   @override
-  File file(String path) => new _ChrootFile(this, path);
+  File file(path) => new _ChrootFile(this, common.getPath(path));
 
   @override
-  Link link(String path) => new _ChrootLink(this, path);
+  Link link(path) => new _ChrootLink(this, common.getPath(path));
 
   /// Gets the current working directory for this file system. Note that this
   /// does *not* proxy to the underlying file system's current directory in
diff --git a/lib/src/backends/local/local_file_system.dart b/lib/src/backends/local/local_file_system.dart
index d5a19a1..bc23038 100644
--- a/lib/src/backends/local/local_file_system.dart
+++ b/lib/src/backends/local/local_file_system.dart
@@ -8,14 +8,14 @@
   const LocalFileSystem();
 
   @override
-  Directory directory(String path) =>
+  Directory directory(path) =>
       new _LocalDirectory(this, shim.newDirectory(path));
 
   @override
-  File file(String path) => new _LocalFile(this, shim.newFile(path));
+  File file(path) => new _LocalFile(this, shim.newFile(path));
 
   @override
-  Link link(String path) => new _LocalLink(this, shim.newLink(path));
+  Link link(path) => new _LocalLink(this, shim.newLink(path));
 
   @override
   Directory get currentDirectory => directory(shim.currentDirectory.path);
diff --git a/lib/src/backends/memory.dart b/lib/src/backends/memory.dart
index b5e2e7e..6881e83 100644
--- a/lib/src/backends/memory.dart
+++ b/lib/src/backends/memory.dart
@@ -5,6 +5,7 @@
 import 'dart:math' show min;
 
 import 'package:file/file.dart';
+import 'package:file/src/common.dart' as common;
 import 'package:file/src/io.dart' as io;
 import 'package:path/path.dart' as p;
 
diff --git a/lib/src/backends/memory/memory_file_system.dart b/lib/src/backends/memory/memory_file_system.dart
index 1d03c84..3985537 100644
--- a/lib/src/backends/memory/memory_file_system.dart
+++ b/lib/src/backends/memory/memory_file_system.dart
@@ -51,13 +51,13 @@
   }
 
   @override
-  Directory directory(String path) => new _MemoryDirectory(this, path);
+  Directory directory(path) => new _MemoryDirectory(this, common.getPath(path));
 
   @override
-  File file(String path) => new _MemoryFile(this, path);
+  File file(path) => new _MemoryFile(this, common.getPath(path));
 
   @override
-  Link link(String path) => new _MemoryLink(this, path);
+  Link link(path) => new _MemoryLink(this, common.getPath(path));
 
   @override
   Directory get currentDirectory => directory(_cwd);
diff --git a/lib/src/common.dart b/lib/src/common.dart
new file mode 100644
index 0000000..483d44f
--- /dev/null
+++ b/lib/src/common.dart
@@ -0,0 +1,13 @@
+import 'package:file/src/io.dart' as io;
+
+String getPath(path) {
+  if (path is io.FileSystemEntity) {
+    return path.path;
+  } else if (path is String) {
+    return path;
+  } else if (path is Uri) {
+    return path.toFilePath();
+  } else {
+    throw new ArgumentError('Invalid type for "path": ${path?.runtimeType}');
+  }
+}
diff --git a/lib/src/interface/file_system.dart b/lib/src/interface/file_system.dart
index de3e9d2..1ed7099 100644
--- a/lib/src/interface/file_system.dart
+++ b/lib/src/interface/file_system.dart
@@ -10,13 +10,19 @@
   const FileSystem();
 
   /// Returns a reference to a [Directory] at [path].
-  Directory directory(String path);
+  ///
+  /// [path] can be either a [`String`], a [`Uri`], or a [`FileSystemEntity`].
+  Directory directory(path);
 
   /// Returns a reference to a [File] at [path].
-  File file(String path);
+  ///
+  /// [path] can be either a [`String`], a [`Uri`], or a [`FileSystemEntity`].
+  File file(path);
 
   /// Returns a reference to a [Link] at [path].
-  Link link(String path);
+  ///
+  /// [path] can be either a [`String`], a [`Uri`], or a [`FileSystemEntity`].
+  Link link(path);
 
   /// Creates a directory object pointing to the current working directory.
   Directory get currentDirectory;
diff --git a/lib/src/io/shim_dart_io.dart b/lib/src/io/shim_dart_io.dart
index 175372b..356b3f8 100644
--- a/lib/src/io/shim_dart_io.dart
+++ b/lib/src/io/shim_dart_io.dart
@@ -2,9 +2,11 @@
 
 import 'dart:io' as io;
 
-io.Directory newDirectory(String path) => new io.Directory(path);
-io.File newFile(String path) => new io.File(path);
-io.Link newLink(String path) => new io.Link(path);
+import 'package:file/src/common.dart' as common;
+
+io.Directory newDirectory(path) => new io.Directory(common.getPath(path));
+io.File newFile(path) => new io.File(common.getPath(path));
+io.Link newLink(path) => new io.Link(common.getPath(path));
 io.Directory get currentDirectory => io.Directory.current;
 set currentDirectory(dynamic path) => io.Directory.current = path;
 Future<io.FileStat> stat(String path) => io.FileStat.stat(path);
diff --git a/lib/src/io/shim_internal.dart b/lib/src/io/shim_internal.dart
index 4eb15a6..b3de276 100644
--- a/lib/src/io/shim_internal.dart
+++ b/lib/src/io/shim_internal.dart
@@ -5,9 +5,9 @@
 const String _requiresIOMsg = 'This operation requires the use of dart:io';
 dynamic _requiresIO() => throw new UnsupportedError(_requiresIOMsg);
 
-io.Directory newDirectory(String _) => _requiresIO();
-io.File newFile(String _) => _requiresIO();
-io.Link newLink(String _) => _requiresIO();
+io.Directory newDirectory(_) => _requiresIO();
+io.File newFile(_) => _requiresIO();
+io.Link newLink(_) => _requiresIO();
 io.Directory get currentDirectory => _requiresIO();
 set currentDirectory(dynamic _) => _requiresIO();
 Future<io.FileStat> stat(String _) => _requiresIO();
diff --git a/test/common_tests.dart b/test/common_tests.dart
index e35a764..d65c73b 100644
--- a/test/common_tests.dart
+++ b/test/common_tests.dart
@@ -66,6 +66,60 @@
     });
 
     group('FileSystem', () {
+      group('directory', () {
+        test('allowsStringArgument', () {
+          expect(fs.directory(ns('/foo')), isDirectory);
+        });
+
+        test('allowsUriArgument', () {
+          expect(fs.directory(Uri.parse('file:///')), isDirectory);
+        });
+
+        test('allowsDirectoryArgument', () {
+          expect(fs.directory(new io.Directory(ns('/foo'))), isDirectory);
+        });
+
+        test('disallowsOtherArgumentType', () {
+          expect(() => fs.directory(123), throwsArgumentError);
+        });
+      });
+
+      group('file', () {
+        test('allowsStringArgument', () {
+          expect(fs.file(ns('/foo')), isFile);
+        });
+
+        test('allowsUriArgument', () {
+          expect(fs.file(Uri.parse('file:///')), isFile);
+        });
+
+        test('allowsDirectoryArgument', () {
+          expect(fs.file(new io.File(ns('/foo'))), isFile);
+        });
+
+        test('disallowsOtherArgumentType', () {
+          expect(() => fs.file(123), throwsArgumentError);
+        });
+      });
+
+      group('link', () {
+        test('allowsStringArgument', () {
+          expect(fs.link(ns('/foo')), isLink);
+        });
+
+        test('allowsUriArgument', () {
+          expect(fs.link(Uri.parse('file:///')), isLink);
+        });
+
+        test('allowsDirectoryArgument', () {
+          expect(fs.link(new io.File(ns('/foo'))), isLink);
+        });
+
+        test('disallowsOtherArgumentType', () {
+          expect(() => fs.link(123), throwsArgumentError);
+        });
+      });
+
       group('currentDirectory', () {
         test('defaultsToRoot', () {
           expect(fs.currentDirectory.path, root);