Dart Documentationio

io library

Helper functionality to make working with IO easier.

Properties

final Stream<String> stdinLines #

A line-by-line stream of standard input.

final Stream<String> stdinLines = streamToLines(
   new ByteStream(stdin).toStringStream())

Functions

ByteStream createTarGz(List contents, {baseDir}) #

Create a .tar.gz archive from a list of entries. Each entry can be a String, Directory, or File object. The root of the archive is considered to be baseDir, which defaults to the current working directory. Returns a ByteStream that will emit the contents of the archive.

ByteStream createTarGz(List contents, {baseDir}) {
 var buffer = new StringBuffer();
 buffer.write('Creating .tag.gz stream containing:\n');
 contents.forEach((file) => buffer.write('$file\n'));
 log.fine(buffer.toString());

 var controller = new StreamController<List<int>>();

 if (baseDir == null) baseDir = path.current;
 baseDir = path.absolute(baseDir);
 contents = contents.map((entry) {
   entry = path.absolute(entry);
   if (!isBeneath(entry, baseDir)) {
     throw 'Entry $entry is not inside $baseDir.';
   }
   return path.relative(entry, from: baseDir);
 }).toList();

 if (Platform.operatingSystem != "windows") {
   var args = ["--create", "--gzip", "--directory", baseDir];
   args.addAll(contents);
   // TODO(nweiz): It's possible that enough command-line arguments will make
   // the process choke, so at some point we should save the arguments to a
   // file and pass them in via --files-from for tar and -i@filename for 7zip.
   startProcess("tar", args).then((process) {
     store(process.stdout, controller);
   }).catchError((e) {
     // We don't have to worry about double-signaling here, since the store()
     // above will only be reached if startProcess succeeds.
     controller.addError(e);
     controller.close();
   });
   return new ByteStream(controller.stream);
 }

 withTempDir((tempDir) {
   // Create the tar file.
   var tarFile = path.join(tempDir, "intermediate.tar");
   var args = ["a", "-w$baseDir", tarFile];
   args.addAll(contents.map((entry) => '-i!"$entry"'));

   // Note: This line of code gets munged by create_sdk.py to be the correct
   // relative path to 7zip in the SDK.
   var pathTo7zip = '../../third_party/7zip/7za.exe';
   var command = relativeToPub(pathTo7zip);

   // We're passing 'baseDir' both as '-w' and setting it as the working
   // directory explicitly here intentionally. The former ensures that the
   // files added to the archive have the correct relative path in the archive.
   // The latter enables relative paths in the "-i" args to be resolved.
   return runProcess(command, args, workingDir: baseDir).then((_) {
     // GZIP it. 7zip doesn't support doing both as a single operation. Send
     // the output to stdout.
     args = ["a", "unused", "-tgzip", "-so", tarFile];
     return startProcess(command, args);
   }).then((process) {
     // Ignore 7zip's stderr. 7zip writes its normal output to stderr. We don't
     // want to show that since it's meaningless.
     //
     // TODO(rnystrom): Should log the stderr and display it if an actual error
     // occurs.
     return store(process.stdout, controller);
   });
 }).catchError((e) {
   // We don't have to worry about double-signaling here, since the store()
   // above will only be reached if everything succeeds.
   controller.addError(e);
   controller.close();
 });
 return new ByteStream(controller.stream);
}

Future<bool> extractTarGz(Stream<List<int>> stream, String destination) #

Extracts a .tar.gz file from stream to destination. Returns whether or not the extraction was successful.

Future<bool> extractTarGz(Stream<List<int>> stream, String destination) {
 log.fine("Extracting .tar.gz stream to $destination.");

 if (Platform.operatingSystem == "windows") {
   return _extractTarGzWindows(stream, destination);
 }

 return startProcess("tar",
     ["--extract", "--gunzip", "--directory", destination]).then((process) {
   // Ignore errors on process.std{out,err}. They'll be passed to
   // process.exitCode, and we don't want them being top-levelled by
   // std{out,err}Sink.
   store(process.stdout.handleError((_) {}), stdout, closeSink: false);
   store(process.stderr.handleError((_) {}), stderr, closeSink: false);
   return Future.wait([
     store(stream, process.stdin),
     process.exitCode
   ]);
 }).then((results) {
   var exitCode = results[1];
   if (exitCode != 0) {
     throw "Failed to extract .tar.gz stream to $destination (exit code "
       "$exitCode).";
   }
   log.fine("Extracted .tar.gz stream to $destination. Exit code $exitCode.");
 });
}

Future withTempDir(Future fn(String path)) #

Creates a temporary directory and passes its path to fn. Once the Future returned by fn completes, the temporary directory and all its contents will be deleted. fn can also return null, in which case the temporary directory is deleted immediately afterwards.

Returns a future that completes to the value that the future returned from fn completes to.

Future withTempDir(Future fn(String path)) {
 return new Future.sync(() {
   var tempDir = createTempDir();
   return new Future.sync(() => fn(tempDir))
       .whenComplete(() => deleteEntry(tempDir));
 });
}

Future timeout(Future input, int milliseconds, String description) #

Wraps input to provide a timeout. If input completes before milliseconds have passed, then the return value completes in the same way. However, if milliseconds pass before input has completed, it completes with a TimeoutException with description (which should be a fragment describing the action that timed out).

Note that timing out will not cancel the asynchronous operation behind input.

Future timeout(Future input, int milliseconds, String description) {
 var completer = new Completer();
 var timer = new Timer(new Duration(milliseconds: milliseconds), () {
   completer.completeError(new TimeoutException(
       'Timed out while $description.'));
 });
 input.then((value) {
   if (completer.isCompleted) return;
   timer.cancel();
   completer.complete(value);
 }).catchError((e) {
   if (completer.isCompleted) return;
   timer.cancel();
   completer.completeError(e);
 });
 return completer.future;
}

Future<PubProcess> startProcess(String executable, List<String> args, {workingDir, Map<String, String> environment}) #

Spawns the process located at executable, passing in args. Returns a Future that will complete with the Process once it's been started.

The spawned process will inherit its parent's environment variables. If environment is provided, that will be used to augment (not replace) the the inherited variables.

Future<PubProcess> startProcess(String executable, List<String> args,
   {workingDir, Map<String, String> environment}) =>
 _doProcess(Process.start, executable, args, workingDir, environment)
     .then((process) => new PubProcess(process));

Future<PubProcessResult> runProcess(String executable, List<String> args, {workingDir, Map<String, String> environment}) #

Spawns and runs the process located at executable, passing in args. Returns a Future that will complete with the results of the process after it has ended.

The spawned process will inherit its parent's environment variables. If environment is provided, that will be used to augment (not replace) the the inherited variables.

Future<PubProcessResult> runProcess(String executable, List<String> args,
   {workingDir, Map<String, String> environment}) {
 return _doProcess(Process.run, executable, args, workingDir, environment)
     .then((result) {
   // TODO(rnystrom): Remove this and change to returning one string.
   List<String> toLines(String output) {
     var lines = splitLines(output);
     if (!lines.isEmpty && lines.last == "") lines.removeLast();
     return lines;
   }

   var pubResult = new PubProcessResult(toLines(result.stdout),
                               toLines(result.stderr),
                               result.exitCode);

   log.processResult(executable, pubResult);
   return pubResult;
 });
}

Future store(Stream stream, EventSink sink, {bool cancelOnError: true, closeSink: true}) #

Pipes all data and errors from stream into sink. When stream is done, the returned Future is completed and sink is closed if closeSink is true.

When an error occurs on stream, that error is passed to sink. If cancelOnError is true, Future will be completed successfully and no more data or errors will be piped from stream to sink. If cancelOnError and closeSink are both true, sink will then be closed.

Future store(Stream stream, EventSink sink,
   {bool cancelOnError: true, closeSink: true}) {
 var completer = new Completer();
 stream.listen(sink.add,
     onError: (e) {
       sink.addError(e);
       if (cancelOnError) {
         completer.complete();
         if (closeSink) sink.close();
       }
     },
     onDone: () {
       if (closeSink) sink.close();
       completer.complete();
     }, cancelOnError: cancelOnError);
 return completer.future;
}

Pair<EventSink, Future> consumerToSink(StreamConsumer consumer) #

Returns a EventSink that pipes all data to consumer and a Future that will succeed when EventSink is closed or fail with any errors that occur while writing.

Pair<EventSink, Future> consumerToSink(StreamConsumer consumer) {
 var controller = new StreamController();
 var done = controller.stream.pipe(consumer);
 return new Pair<EventSink, Future>(controller.sink, done);
}

Future drainStream(Stream stream) #

Reads and discards all output from stream. Returns a Future that completes when the stream is closed.

Future drainStream(Stream stream) {
 return stream.fold(null, (x, y) {});
}

Future<bool> confirm(String message) #

Displays a message and reads a yes/no confirmation from the user. Returns a Future that completes to true if the user confirms or false if they do not.

This will automatically append " (y/n)?" to the message, so message should just be a fragment like, "Are you sure you want to proceed".

Future<bool> confirm(String message) {
 log.fine('Showing confirm message: $message');
 stdout.write("$message (y/n)? ");
 return streamFirst(stdinLines)
     .then((line) => new RegExp(r"^[yY]").hasMatch(line));
}

String relativeToPub(String target) #

Resolves target relative to the location of pub.dart.

String relativeToPub(String target) {
 var scriptPath = new File(new Options().script).fullPathSync();

 // Walk up until we hit the "util(s)" directory. This lets us figure out where
 // we are if this function is called from pub.dart, or one of the tests,
 // which also live under "utils", or from the SDK where pub is in "util".
 var utilDir = path.dirname(scriptPath);
 while (path.basename(utilDir) != 'utils' &&
        path.basename(utilDir) != 'util') {
   if (path.basename(utilDir) == '') throw 'Could not find path to pub.';
   utilDir = path.dirname(utilDir);
 }

 return path.normalize(path.join(utilDir, 'pub', target));
}

Creates a new symlink that creates an alias at symlink that points to the lib directory of package target. If target does not have a lib directory, this shows a warning if appropriate and then does nothing.

If relative is true, creates a symlink with a relative path from the symlink to the target. Otherwise, uses the target path unmodified.

void createPackageSymlink(String name, String target, String symlink,
   {bool isSelfLink: false, bool relative: false}) {
 // See if the package has a "lib" directory.
 target = path.join(target, 'lib');
 log.fine("Creating ${isSelfLink ? "self" : ""}link for package '$name'.");
 if (dirExists(target)) {
   createSymlink(target, symlink, relative: relative);
   return;
 }

 // It's OK for the self link (i.e. the root package) to not have a lib
 // directory since it may just be a leaf application that only has
 // code in bin or web.
 if (!isSelfLink) {
   log.warning('Warning: Package "$name" does not have a "lib" directory so '
               'you will not be able to import any libraries from it.');
 }
}

Creates a new symlink at path symlink that points to target. Returns a Future which completes to the path to the symlink file.

If relative is true, creates a symlink with a relative path from the symlink to the target. Otherwise, uses the target path unmodified.

Note that on Windows, only directories may be symlinked to.

void createSymlink(String target, String symlink,
   {bool relative: false}) {
 if (relative) {
   // Relative junction points are not supported on Windows. Instead, just
   // make sure we have a clean absolute path because it will interpret a
   // relative path to be relative to the cwd, not the symlink, and will be
   // confused by forward slashes.
   if (Platform.operatingSystem == 'windows') {
     target = path.normalize(path.absolute(target));
   } else {
     target = path.normalize(
         path.relative(target, from: path.dirname(symlink)));
   }
 }

 log.fine("Creating $symlink pointing to $target");
 new Link(symlink).createSync(target);
}

void renameDir(String from, String to) #

Renames (i.e. moves) the directory from to to.

void renameDir(String from, String to) {
 log.io("Renaming directory $from to $to.");
 new Directory(from).renameSync(to);
}

void cleanDir(String dir) #

"Cleans" dir. If that directory already exists, it will be deleted. Then a new empty directory will be created.

void cleanDir(String dir) {
 if (entryExists(dir)) deleteEntry(dir);
 createDir(dir);
}

void deleteEntry(String path) #

Deletes whatever's at path, whether it's a file, directory, or symlink. If it's a directory, it will be deleted recursively.

void deleteEntry(String path) {
 if (linkExists(path)) {
   log.io("Deleting link $path.");
   new Link(path).deleteSync();
 } else if (dirExists(path)) {
   log.io("Deleting directory $path.");
   new Directory(path).deleteSync(recursive: true);
 } else if (fileExists(path)) {
   log.io("Deleting file $path.");
   new File(path).deleteSync();
 }
}

bool dirExists(String dir) #

Returns whether dir exists on the file system. This will return true for a symlink only if that symlink is unbroken and points to a directory.

bool dirExists(String dir) => new Directory(dir).existsSync();

List<String> listDir(String dir, {bool recursive: false, bool includeHidden: false}) #

Lists the contents of dir. If recursive is true, lists subdirectory contents (defaults to false). If includeHidden is true, includes files and directories beginning with . (defaults to false).

The returned paths are guaranteed to begin with dir.

List<String> listDir(String dir, {bool recursive: false,
   bool includeHidden: false}) {
 List<String> doList(String dir, Set<String> listedDirectories) {
   var contents = <String>[];

   // Avoid recursive symlinks.
   var resolvedPath = new File(dir).fullPathSync();
   if (listedDirectories.contains(resolvedPath)) return [];

   listedDirectories = new Set<String>.from(listedDirectories);
   listedDirectories.add(resolvedPath);

   log.io("Listing directory $dir.");

   var children = <String>[];
   for (var entity in new Directory(dir).listSync()) {
     if (!includeHidden && path.basename(entity.path).startsWith('.')) {
       continue;
     }

     contents.add(entity.path);
     if (entity is Directory) {
       // TODO(nweiz): don't manually recurse once issue 4794 is fixed.
       // Note that once we remove the manual recursion, we'll need to
       // explicitly filter out files in hidden directories.
       if (recursive) {
         children.addAll(doList(entity.path, listedDirectories));
       }
     }
   }

   log.fine("Listed directory $dir:\n${contents.join('\n')}");
   contents.addAll(children);
   return contents;
 }

 return doList(dir, new Set<String>());
}

String createTempDir([dir = '']) #

Creates a temp directory whose name will be based on dir with a unique suffix appended to it. If dir is not provided, a temp directory will be created in a platform-dependent temporary location. Returns the path of the created directory.

String createTempDir([dir = '']) {
 var tempDir = new Directory(dir).createTempSync();
 log.io("Created temp directory ${tempDir.path}");
 return tempDir.path;
}

String ensureDir(String dirPath) #

Ensures that dirPath and all its parent directories exist. If they don't exist, creates them.

String ensureDir(String dirPath) {
 log.fine("Ensuring directory $dirPath exists.");
 var dir = new Directory(dirPath);
 if (dirPath == '.' || dirExists(dirPath)) return dirPath;

 ensureDir(path.dirname(dirPath));

 try {
   createDir(dirPath);
 } on DirectoryException catch (ex) {
   // Error 17 means the directory already exists (or 183 on Windows).
   if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) {
     log.fine("Got 'already exists' error when creating directory.");
   } else {
     throw ex;
   }
 }

 return dirPath;
}

String createDir(String dir) #

Creates a directory dir.

String createDir(String dir) {
 new Directory(dir).createSync();
 return dir;
}

Future<String> createFileFromStream(Stream<List<int>> stream, String file) #

Writes stream to a new file at path file. Will replace any file already at that path. Completes when the file is done being written.

Future<String> createFileFromStream(Stream<List<int>> stream, String file) {
 log.io("Creating $file from stream.");

 return stream.pipe(new File(file).openWrite()).then((_) {
   log.fine("Created $file from stream.");
   return file;
 });
}

String writeBinaryFile(String file, List<int> contents) #

Creates file and writes contents to it.

String writeBinaryFile(String file, List<int> contents) {
 log.io("Writing ${contents.length} bytes to binary file $file.");
 new File(file).openSync(mode: FileMode.WRITE)
     ..writeFromSync(contents)
     ..closeSync();
 log.fine("Wrote text file $file.");
 return file;
}

String writeTextFile(String file, String contents, {dontLogContents: false}) #

Creates file and writes contents to it.

If dontLogContents is true, the contents of the file will never be logged.

String writeTextFile(String file, String contents, {dontLogContents: false}) {
 // Sanity check: don't spew a huge file.
 log.io("Writing ${contents.length} characters to text file $file.");
 if (!dontLogContents && contents.length < 1024 * 1024) {
   log.fine("Contents:\n$contents");
 }

 new File(file).writeAsStringSync(contents);
 return file;
}

List<int> readBinaryFile(String file) #

Reads the contents of the binary file file.

List<int> readBinaryFile(String file) {
 log.io("Reading binary file $file.");
 var contents = new File(file).readAsBytesSync();
 log.io("Read ${contents.length} bytes from $file.");
 return contents;
}

String readTextFile(String file) #

Reads the contents of the text file file.

String readTextFile(String file) =>
   new File(file).readAsStringSync(encoding: Encoding.UTF_8);

bool fileExists(String file) #

Returns whether file exists on the file system. This will return true for a symlink only if that symlink is unbroken and points to a file.

bool fileExists(String file) => new File(file).existsSync();

bool linkExists(String link) #

Returns whether link exists on the file system. This will return true for any symlink, regardless of what it points at or whether it's broken.

bool linkExists(String link) => new Link(link).existsSync();

bool entryExists(String path) #

Determines if a file or directory exists at path.

bool entryExists(String path) =>
 dirExists(path) || fileExists(path) || linkExists(path);

bool isBeneath(String entry, String dir) #

Returns whether or not entry is nested somewhere within dir. This just performs a path comparison; it doesn't look at the actual filesystem.

bool isBeneath(String entry, String dir) {
 var relative = path.relative(entry, from: dir);
 return !path.isAbsolute(relative) && path.split(relative)[0] != '..';
}

Classes

Exceptions