Index: utils/pub/io.dart |
diff --git a/utils/pub/io.dart b/utils/pub/io.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..845b5b4c60e26b9ca8b3b655bf172a9078db542f |
--- /dev/null |
+++ b/utils/pub/io.dart |
@@ -0,0 +1,231 @@ |
+// Copyright (c) 2012, 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. |
+ |
+/** |
+ * Helper functionality to make working with IO easier. |
+ */ |
+#library('pub_io'); |
+ |
+#import('dart:io'); |
+ |
+#import('../lib/file_system.dart', prefix: 'fs'); |
+ |
+/** |
+ * Joins a number of path string parts into a single path. Handles |
+ * platform-specific path separators. Parts can be [String], [Directory], or |
+ * [File] objects. |
+ */ |
+String join(part1, [part2, part3, part4]) { |
+ final parts = _getPath(part1).split('/'); |
+ |
+ for (final part in [part2, part3, part4]) { |
+ if (part == null) continue; |
+ |
+ for (final piece in _getPath(part).split('/')) { |
+ if (piece == '..' && parts.length > 0 && |
+ parts.last() != '.' && parts.last() != '..') { |
+ parts.removeLast(); |
+ } else if (piece != '') { |
+ if (parts.length > 0 && parts.last() == '.') { |
+ parts.removeLast(); |
+ } |
+ parts.add(piece); |
+ } |
+ } |
+ } |
+ |
+ return Strings.join(parts, Platform.pathSeparator()); |
+} |
+ |
+/** |
+ * Gets the basename, the file name without any leading directory path, for |
+ * [file], which can either be a [String], [File], or [Directory]. |
+ */ |
+String basename(file) { |
+ return fs.basename(_getPath(file)); |
+} |
+ |
+/** |
+ * Reads the contents of the text file [file], which can either be a [String] or |
+ * a [File]. |
+ */ |
+Future<String> readTextFile(file) { |
+ file = new File(_getPath(file)); |
+ final completer = new Completer<String>(); |
+ file.onError = (error) => completer.completeException(error); |
+ file.readAsText(Encoding.UTF_8, (text) => completer.complete(text)); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Creates [file] (which can either be a [String] or a [File]), and writes |
+ * [contents] to it. Completes when the file is written and closed. |
+ */ |
+Future<File> writeTextFile(file, String contents) { |
+ file = new File(_getPath(file)); |
+ final completer = new Completer<File>(); |
+ file.onError = (error) => completer.completeException(error); |
+ file.open(FileMode.WRITE, (opened) { |
+ opened.onError = (error) => completer.completeException(error); |
+ opened.onNoPendingWrites = () { |
+ opened.close(() => completer.complete(file)); |
+ }; |
+ opened.writeString(contents); |
+ }); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Creates a directory [dir]. Returns a [Future] that completes when the |
+ * directory is created. |
+ */ |
+Future<Directory> createDir(dir) { |
+ final completer = new Completer<Directory>(); |
+ dir = _getDirectory(dir); |
+ dir.onError = (error) => completer.completeException(error); |
+ dir.create(() => completer.complete(dir)); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Creates a temp directory whose name will be based on [dir] with a unique |
+ * suffix appended to it. Returns a [Future] that completes when the directory |
+ * is created. |
+ */ |
+Future<Directory> createTempDir(dir) { |
+ final completer = new Completer<Directory>(); |
+ dir = _getDirectory(dir); |
+ dir.onError = (error) => completer.completeException(error); |
+ dir.createTemp(() => completer.complete(dir)); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Asynchronously recursively deletes [dir], which can be a [String] or a |
+ * [Directory]. Returns a [Future] that completes when the deletion is done. |
+ */ |
+Future<Directory> deleteDir(dir) { |
+ final completer = new Completer<Directory>(); |
+ dir = _getDirectory(dir); |
+ dir.onError = (error) => completer.completeException(error); |
+ dir.deleteRecursively(() => completer.complete(dir)); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Asynchronously lists the contents of [dir], which can be a [String] directory |
+ * path or a [Directory]. If [recursive] is `true`, lists subdirectory contents |
+ * (defaults to `false`). If [includeSpecialFiles] is `true`, includes |
+ * hidden `.DS_Store` files (defaults to `false`, other hidden files may be |
+ * omitted later). |
+ */ |
+Future<List<String>> listDir(dir, |
+ [bool recursive = false, bool includeSpecialFiles = false]) { |
+ final completer = new Completer<List<String>>(); |
+ final contents = <String>[]; |
+ |
+ dir = _getDirectory(dir); |
+ |
+ dir.onDone = (done) { |
+ // TODO(rnystrom): May need to sort here if it turns out onDir and onFile |
+ // aren't guaranteed to be called in a certain order. So far, they seem to. |
+ if (done) completer.complete(contents); |
+ }; |
+ |
+ dir.onError = (error) => completer.completeException(error); |
+ dir.onDir = (file) => contents.add(file); |
+ dir.onFile = (file) { |
+ if (!includeSpecialFiles) { |
+ if (basename(file) == '.DS_Store') return; |
+ } |
+ contents.add(file); |
+ }; |
+ |
+ dir.list(recursive: recursive); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Spawns and runs the process located at [executable], passing in [args]. |
+ * Returns a [Future] that will complete the results of the process after it |
+ * has ended. |
+ */ |
+Future<ProcessResult> runProcess(String executable, List<String> args) { |
+ int exitCode; |
+ |
+ final process = new Process.start(executable, args); |
+ |
+ final outStream = new StringInputStream(process.stdout); |
+ final processStdout = <String>[]; |
+ |
+ final errStream = new StringInputStream(process.stderr); |
+ final processStderr = <String>[]; |
+ |
+ final completer = new Completer<ProcessResult>(); |
+ |
+ checkComplete() { |
+ // Wait until the process is done and its output streams are closed. |
+ if (!outStream.closed) return; |
+ if (!errStream.closed) return; |
+ if (exitCode == null) return; |
+ |
+ completer.complete(new ProcessResult( |
+ processStdout, processStderr, exitCode)); |
+ } |
+ |
+ outStream.onLine = () => processStdout.add(outStream.readLine()); |
+ outStream.onClosed = checkComplete; |
+ outStream.onError = (error) => completer.completeException(error); |
+ |
+ errStream.onLine = () => processStderr.add(errStream.readLine()); |
+ errStream.onClosed = checkComplete; |
+ errStream.onError = (error) => completer.completeException(error); |
+ |
+ process.onExit = (actualExitCode) { |
+ exitCode = actualExitCode; |
+ checkComplete(); |
+ }; |
+ |
+ process.onError = (error) => completer.completeException(error); |
+ |
+ return completer.future; |
+} |
+ |
+/** |
+ * Contains the results of invoking a [Process] and waiting for it to complete. |
+ */ |
+class ProcessResult { |
+ final List<String> stdout; |
+ final List<String> stderr; |
+ final int exitCode; |
+ |
+ const ProcessResult(this.stdout, this.stderr, this.exitCode); |
+} |
+ |
+/** |
+ * Gets the path string for [entry], which can either already be a path string, |
+ * or be a [File] or [Directory]. Allows working generically with "file-like" |
+ * objects. |
+ */ |
+String _getPath(entry) { |
+ if (entry is String) return entry; |
+ if (entry is File) return entry.name; |
+ if (entry is Directory) return entry.path; |
+ throw 'Entry $entry is not a supported type.'; |
+} |
+ |
+/** |
+ * Gets a [Directory] for [entry], which can either already be one, or be a |
+ * [String]. |
+ */ |
+Directory _getDirectory(entry) { |
+ if (entry is Directory) return entry; |
+ return new Directory(entry); |
+} |