Add copyPath and copyPathSync. (#30)
* .
* Dartfmt.
* Oops.
* Clarify.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 69ab12b..c2565ea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,10 @@
}
```
+- Added a `copyPath` and `copyPathSync` function, similar to `cp -R`.
+
+- Added a dependency on `package:path`.
+
- Added the remaining missing arguments to `ProcessManager.spawnX` which
forward to `Process.start`. It is now an interchangeable function for running
a process.
diff --git a/lib/io.dart b/lib/io.dart
index 3ad63fa..39e4450 100644
--- a/lib/io.dart
+++ b/lib/io.dart
@@ -2,6 +2,7 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+export 'src/copy_path.dart' show copyPath, copyPathSync;
export 'src/exit_code.dart' show ExitCode;
export 'src/permissions.dart' show isExecutable;
export 'src/process_manager.dart' show ProcessManager, Spawn, StartProcess;
diff --git a/lib/src/copy_path.dart b/lib/src/copy_path.dart
new file mode 100644
index 0000000..ed790ed
--- /dev/null
+++ b/lib/src/copy_path.dart
@@ -0,0 +1,70 @@
+// Copyright 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:path/path.dart' as p;
+
+bool _doNothing(String from, String to) {
+ if (p.canonicalize(from) == p.canonicalize(to)) {
+ return true;
+ }
+ if (p.isWithin(from, to)) {
+ throw new ArgumentError('Cannot copy from $from to $to');
+ }
+ return false;
+}
+
+/// Copies all of the files in the [from] directory to [to].
+///
+/// This is similar to `cp -R <from> <to>`:
+/// * Symlinks are supported.
+/// * Existing files are over-written, if any.
+/// * If [to] is within [from], throws [ArgumentError] (an infinite operation).
+/// * If [from] and [to] are canonically the same, no operation occurs.
+///
+/// Returns a future that completes when complete.
+Future<Null> copyPath(String from, String to) async {
+ if (_doNothing(from, to)) {
+ return;
+ }
+ await new Directory(to).create(recursive: true);
+ await for (final file in new Directory(from).list(recursive: true)) {
+ final copyTo = p.join(to, p.relative(file.path, from: from));
+ if (file is Directory) {
+ await new Directory(copyTo).create(recursive: true);
+ } else if (file is File) {
+ await new File(file.path).copy(copyTo);
+ } else if (file is Link) {
+ await new Link(copyTo).create(await file.target(), recursive: true);
+ }
+ }
+}
+
+/// Copies all of the files in the [from] directory to [to].
+///
+/// This is similar to `cp -R <from> <to>`:
+/// * Symlinks are supported.
+/// * Existing files are over-written, if any.
+/// * If [to] is within [from], throws [ArgumentError] (an infinite operation).
+/// * If [from] and [to] are canonically the same, no operation occurs.
+///
+/// This action is performed synchronously (blocking I/O).
+void copyPathSync(String from, String to) {
+ if (_doNothing(from, to)) {
+ return;
+ }
+ new Directory(to).createSync(recursive: true);
+ for (final file in new Directory(from).listSync(recursive: true)) {
+ final copyTo = p.join(to, p.relative(file.path, from: from));
+ if (file is Directory) {
+ new Directory(copyTo).createSync(recursive: true);
+ } else if (file is File) {
+ new File(file.path).copySync(copyTo);
+ } else if (file is Link) {
+ new Link(copyTo).createSync(file.targetSync(), recursive: true);
+ }
+ }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 8427910..14b60a6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -11,9 +11,10 @@
dependencies:
charcode: ^1.0.0
meta: ^1.0.2
+ path: ^1.5.1
string_scanner: ">=0.1.5 <2.0.0"
dev_dependencies:
dart_style: ^1.0.7
- path: ^1.0.0
test: ^0.12.0
+ test_descriptor: ^1.0.0
diff --git a/test/copy_path_test.dart b/test/copy_path_test.dart
new file mode 100644
index 0000000..ac06bb5
--- /dev/null
+++ b/test/copy_path_test.dart
@@ -0,0 +1,47 @@
+// Copyright 2017, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn('vm')
+import 'dart:async';
+
+import 'package:io/io.dart';
+import 'package:path/path.dart' as p;
+import 'package:test/test.dart';
+import 'package:test_descriptor/test_descriptor.dart' as d;
+
+void main() {
+ test('should copy a directory (async)', () async {
+ await _create();
+ await copyPath(p.join(d.sandbox, 'parent'), p.join(d.sandbox, 'copy'));
+ await _validate();
+ });
+
+ test('should copy a directory (sync)', () async {
+ await _create();
+ copyPathSync(p.join(d.sandbox, 'parent'), p.join(d.sandbox, 'copy'));
+ await _validate();
+ });
+
+ test('should catch an infinite operation', () async {
+ await _create();
+ expect(
+ copyPath(
+ p.join(d.sandbox, 'parent'),
+ p.join(d.sandbox, 'parent', 'child'),
+ ),
+ throwsArgumentError,
+ );
+ });
+}
+
+d.DirectoryDescriptor _struct() {
+ return d.dir('parent', [
+ d.dir('child', [
+ d.file('foo.txt'),
+ ]),
+ ]);
+}
+
+Future _create() => _struct().create();
+Future _validate() => _struct().validate();