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();