OLD | NEW |
---|---|
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 /// Helper functionality to make working with IO easier. | 5 /// Helper functionality to make working with IO easier. |
6 library io; | 6 library io; |
7 | 7 |
8 import 'dart:async'; | 8 import 'dart:async'; |
9 import 'dart:io'; | 9 import 'dart:io'; |
10 import 'dart:isolate'; | 10 import 'dart:isolate'; |
11 import 'dart:json'; | 11 import 'dart:json'; |
12 import 'dart:uri'; | 12 import 'dart:uri'; |
13 | 13 |
14 import '../../pkg/path/lib/path.dart' as path; | 14 import '../../pkg/path/lib/path.dart' as path; |
15 import '../../pkg/http/lib/http.dart' show ByteStream; | 15 import '../../pkg/http/lib/http.dart' show ByteStream; |
16 import 'error_group.dart'; | 16 import 'error_group.dart'; |
17 import 'exit_codes.dart' as exit_codes; | 17 import 'exit_codes.dart' as exit_codes; |
18 import 'log.dart' as log; | 18 import 'log.dart' as log; |
19 import 'utils.dart'; | 19 import 'utils.dart'; |
20 | 20 |
21 export '../../pkg/http/lib/http.dart' show ByteStream; | 21 export '../../pkg/http/lib/http.dart' show ByteStream; |
22 | 22 |
23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 23 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
24 | 24 |
25 /// Joins a number of path string parts into a single path. Handles | 25 /// Joins a number of path string parts into a single path. Handles |
26 /// platform-specific path separators. Parts can be [String], [Directory], or | 26 /// platform-specific path separators. Parts can be [String], [Directory], or |
27 /// [File] objects. | 27 /// [File] objects. |
28 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { | 28 String join(part1, [part2, part3, part4, part5, part6, part7, part8]) { |
nweiz
2013/02/14 00:10:37
Can we get rid of this?
Bob Nystrom
2013/02/14 01:14:19
That will be a bit more invasive, so I figured I'd
| |
29 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] | 29 var parts = [part1, part2, part3, part4, part5, part6, part7, part8] |
30 .map((part) => part == null ? null : _getPath(part)).toList(); | 30 .map((part) => part == null ? null : _getPath(part)).toList(); |
31 | 31 |
32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], | 32 return path.join(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], |
33 parts[6], parts[7]); | 33 parts[6], parts[7]); |
34 } | 34 } |
35 | 35 |
36 /// Gets the basename, the file name without any leading directory path, for | |
37 /// [file], which can either be a [String], [File], or [Directory]. | |
38 String basename(file) => path.basename(_getPath(file)); | |
39 | |
40 /// Gets the the leading directory path for [file], which can either be a | |
41 /// [String], [File], or [Directory]. | |
42 String dirname(file) => path.dirname(_getPath(file)); | |
43 | |
44 /// Splits [entry] into its individual components. | 36 /// Splits [entry] into its individual components. |
45 List<String> splitPath(entry) => path.split(_getPath(entry)); | 37 List<String> splitPath(entry) => path.split(_getPath(entry)); |
nweiz
2013/02/14 00:10:37
Can we get rid of this?
Bob Nystrom
2013/02/14 01:14:19
Done.
| |
46 | 38 |
47 /// Returns whether or not [entry] is nested somewhere within [dir]. This just | 39 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
48 /// performs a path comparison; it doesn't look at the actual filesystem. | 40 /// performs a path comparison; it doesn't look at the actual filesystem. |
49 bool isBeneath(entry, dir) { | 41 bool isBeneath(entry, dir) { |
nweiz
2013/02/14 00:10:37
Since this just calls path methods now, we should
Bob Nystrom
2013/02/14 01:14:19
Done.
| |
50 var relative = relativeTo(entry, dir); | 42 var relative = path.relative(entry, from: dir); |
51 return !path.isAbsolute(relative) && splitPath(relative)[0] != '..'; | 43 return !path.isAbsolute(relative) && splitPath(relative)[0] != '..'; |
52 } | 44 } |
53 | 45 |
54 /// Returns the path to [target] from [base]. | |
55 String relativeTo(target, base) => path.relative(target, from: base); | |
56 | |
57 /// Determines if [path], which can be a [String] file path, a [File], or a | 46 /// Determines if [path], which can be a [String] file path, a [File], or a |
58 /// [Directory] exists on the file system. | 47 /// [Directory] exists on the file system. |
59 bool entryExists(path) => fileExists(path) || dirExists(path); | 48 bool entryExists(path) => fileExists(path) || dirExists(path); |
60 | 49 |
61 /// Determines if [file], which can be a [String] file path or a [File], exists | 50 /// Determines if [file], which can be a [String] file path or a [File], exists |
62 /// on the file system. | 51 /// on the file system. |
63 bool fileExists(file) => _getFile(file).existsSync(); | 52 bool fileExists(file) => _getFile(file).existsSync(); |
64 | 53 |
65 /// Reads the contents of the text file [file], which can either be a [String] | 54 /// Reads the contents of the text file [file], which can either be a [String] |
66 /// or a [File]. | 55 /// or a [File]. |
(...skipping 19 matching lines...) Expand all Loading... | |
86 | 75 |
87 // Sanity check: don't spew a huge file. | 76 // Sanity check: don't spew a huge file. |
88 log.io("Writing ${contents.length} characters to text file $path."); | 77 log.io("Writing ${contents.length} characters to text file $path."); |
89 if (!dontLogContents && contents.length < 1024 * 1024) { | 78 if (!dontLogContents && contents.length < 1024 * 1024) { |
90 log.fine("Contents:\n$contents"); | 79 log.fine("Contents:\n$contents"); |
91 } | 80 } |
92 | 81 |
93 return file..writeAsStringSync(contents); | 82 return file..writeAsStringSync(contents); |
94 } | 83 } |
95 | 84 |
96 /// Deletes [file], which can be a [String] or a [File]. Returns a [Future] | 85 /// Deletes [file], which can be a [String] or a [File]. |
97 /// that completes when the deletion is done. | |
98 File deleteFile(file) => _getFile(file)..delete(); | 86 File deleteFile(file) => _getFile(file)..delete(); |
99 | 87 |
100 /// Creates [file] (which can either be a [String] or a [File]), and writes | 88 /// Creates [file] (which can either be a [String] or a [File]), and writes |
101 /// [contents] to it. | 89 /// [contents] to it. |
102 File writeBinaryFile(file, List<int> contents) { | 90 File writeBinaryFile(file, List<int> contents) { |
103 var path = _getPath(file); | 91 var path = _getPath(file); |
104 file = new File(path); | 92 file = new File(path); |
105 | 93 |
106 log.io("Writing ${contents.length} bytes to binary file $path."); | 94 log.io("Writing ${contents.length} bytes to binary file $path."); |
107 file.openSync(FileMode.WRITE) | 95 file.openSync(FileMode.WRITE) |
(...skipping 13 matching lines...) Expand all Loading... | |
121 | 109 |
122 var file = new File(path); | 110 var file = new File(path); |
123 return stream.pipe(wrapOutputStream(file.openOutputStream())).then((_) { | 111 return stream.pipe(wrapOutputStream(file.openOutputStream())).then((_) { |
124 log.fine("Created $path from stream."); | 112 log.fine("Created $path from stream."); |
125 }); | 113 }); |
126 } | 114 } |
127 | 115 |
128 /// Creates a directory [dir]. | 116 /// Creates a directory [dir]. |
129 Directory createDir(dir) => _getDirectory(dir)..createSync(); | 117 Directory createDir(dir) => _getDirectory(dir)..createSync(); |
130 | 118 |
131 /// Ensures that [path] and all its parent directories exist. If they don't | 119 /// Ensures that [dirPath] and all its parent directories exist. If they don't |
132 /// exist, creates them. | 120 /// exist, creates them. |
133 Directory ensureDir(path) { | 121 Directory ensureDir(dirPath) { |
134 path = _getPath(path); | 122 dirPath = _getPath(dirPath); |
135 | 123 |
136 log.fine("Ensuring directory $path exists."); | 124 log.fine("Ensuring directory $dirPath exists."); |
137 var dir = new Directory(path); | 125 var dir = new Directory(dirPath); |
138 if (path == '.' || dirExists(path)) return dir; | 126 if (dirPath == '.' || dirExists(dirPath)) return dir; |
139 | 127 |
140 ensureDir(dirname(path)); | 128 ensureDir(path.dirname(dirPath)); |
141 | 129 |
142 try { | 130 try { |
143 createDir(dir); | 131 createDir(dir); |
144 } on DirectoryIOException catch (ex) { | 132 } on DirectoryIOException catch (ex) { |
145 // Error 17 means the directory already exists (or 183 on Windows). | 133 // Error 17 means the directory already exists (or 183 on Windows). |
146 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { | 134 if (ex.osError.errorCode == 17 || ex.osError.errorCode == 183) { |
147 log.fine("Got 'already exists' error when creating directory."); | 135 log.fine("Got 'already exists' error when creating directory."); |
148 } else { | 136 } else { |
149 throw ex; | 137 throw ex; |
150 } | 138 } |
151 } | 139 } |
152 | 140 |
153 return dir; | 141 return dir; |
154 } | 142 } |
155 | 143 |
156 /// Creates a temp directory whose name will be based on [dir] with a unique | 144 /// Creates a temp directory whose name will be based on [dir] with a unique |
157 /// suffix appended to it. If [dir] is not provided, a temp directory will be | 145 /// suffix appended to it. If [dir] is not provided, a temp directory will be |
158 /// created in a platform-dependent temporary location. Returns a [Future] that | 146 /// created in a platform-dependent temporary location. Returns the path of the |
159 /// completes when the directory is created. | 147 /// created directory. |
160 Directory createTempDir([dir = '']) { | 148 String createTempDir([dir = '']) { |
161 var tempDir = _getDirectory(dir).createTempSync(); | 149 var tempDir = _getDirectory(dir).createTempSync(); |
162 log.io("Created temp directory ${tempDir.path}"); | 150 log.io("Created temp directory ${tempDir.path}"); |
163 return tempDir; | 151 return tempDir.path; |
164 } | 152 } |
165 | 153 |
166 /// Asynchronously recursively deletes [dir], which can be a [String] or a | 154 /// Asynchronously recursively deletes [dir], which can be a [String] or a |
167 /// [Directory]. Returns a [Future] that completes when the deletion is done. | 155 /// [Directory]. Returns a [Future] that completes when the deletion is done. |
168 Future<Directory> deleteDir(dir) { | 156 Future<Directory> deleteDir(dir) { |
169 dir = _getDirectory(dir); | 157 dir = _getDirectory(dir); |
170 | 158 |
171 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", | 159 return _attemptRetryable(() => log.ioAsync("delete directory ${dir.path}", |
172 dir.delete(recursive: true))); | 160 dir.delete(recursive: true))); |
173 } | 161 } |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
211 var stackTrace; | 199 var stackTrace; |
212 try { | 200 try { |
213 throw ""; | 201 throw ""; |
214 } catch (_, localStackTrace) { | 202 } catch (_, localStackTrace) { |
215 stackTrace = localStackTrace; | 203 stackTrace = localStackTrace; |
216 } | 204 } |
217 | 205 |
218 var children = []; | 206 var children = []; |
219 lister.onError = (error) => completer.completeError(error, stackTrace); | 207 lister.onError = (error) => completer.completeError(error, stackTrace); |
220 lister.onDir = (file) { | 208 lister.onDir = (file) { |
221 if (!includeHiddenFiles && basename(file).startsWith('.')) return; | 209 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; |
222 file = join(dir, basename(file)); | 210 file = join(dir, path.basename(file)); |
223 contents.add(file); | 211 contents.add(file); |
224 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that | 212 // TODO(nweiz): don't manually recurse once issue 7358 is fixed. Note that |
225 // once we remove the manual recursion, we'll need to explicitly filter | 213 // once we remove the manual recursion, we'll need to explicitly filter |
226 // out files in hidden directories. | 214 // out files in hidden directories. |
227 if (recursive) { | 215 if (recursive) { |
228 children.add(doList(new Directory(file), listedDirectories)); | 216 children.add(doList(new Directory(file), listedDirectories)); |
229 } | 217 } |
230 }; | 218 }; |
231 | 219 |
232 lister.onFile = (file) { | 220 lister.onFile = (file) { |
233 if (!includeHiddenFiles && basename(file).startsWith('.')) return; | 221 if (!includeHiddenFiles && path.basename(file).startsWith('.')) return; |
234 contents.add(join(dir, basename(file))); | 222 contents.add(join(dir, path.basename(file))); |
235 }; | 223 }; |
236 | 224 |
237 return completer.future.then((contents) { | 225 return completer.future.then((contents) { |
238 return Future.wait(children).then((childContents) { | 226 return Future.wait(children).then((childContents) { |
239 contents.addAll(flatten(childContents)); | 227 contents.addAll(flatten(childContents)); |
240 return contents; | 228 return contents; |
241 }); | 229 }); |
242 }); | 230 }); |
243 } | 231 } |
244 | 232 |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
354 // code in bin or web. | 342 // code in bin or web. |
355 if (!isSelfLink) { | 343 if (!isSelfLink) { |
356 log.warning('Warning: Package "$name" does not have a "lib" directory so ' | 344 log.warning('Warning: Package "$name" does not have a "lib" directory so ' |
357 'you will not be able to import any libraries from it.'); | 345 'you will not be able to import any libraries from it.'); |
358 } | 346 } |
359 | 347 |
360 return _getFile(to); | 348 return _getFile(to); |
361 }); | 349 }); |
362 } | 350 } |
363 | 351 |
364 /// Given [entry] which may be a [String], [File], or [Directory] relative to | |
365 /// the current working directory, returns its full canonicalized path. | |
366 String getFullPath(entry) => path.absolute(_getPath(entry)); | |
367 | |
368 /// Returns whether or not [entry] is an absolute path. | |
369 bool isAbsolute(entry) => path.isAbsolute(_getPath(entry)); | |
370 | |
371 /// Resolves [target] relative to the location of pub.dart. | 352 /// Resolves [target] relative to the location of pub.dart. |
372 String relativeToPub(String target) { | 353 String relativeToPub(String target) { |
373 var scriptPath = new File(new Options().script).fullPathSync(); | 354 var scriptPath = new File(new Options().script).fullPathSync(); |
374 | 355 |
375 // Walk up until we hit the "util(s)" directory. This lets us figure out where | 356 // Walk up until we hit the "util(s)" directory. This lets us figure out where |
376 // we are if this function is called from pub.dart, or one of the tests, | 357 // we are if this function is called from pub.dart, or one of the tests, |
377 // which also live under "utils", or from the SDK where pub is in "util". | 358 // which also live under "utils", or from the SDK where pub is in "util". |
378 var utilDir = dirname(scriptPath); | 359 var utilDir = path.dirname(scriptPath); |
379 while (basename(utilDir) != 'utils' && basename(utilDir) != 'util') { | 360 while (path.basename(utilDir) != 'utils' && |
380 if (basename(utilDir) == '') throw 'Could not find path to pub.'; | 361 path.basename(utilDir) != 'util') { |
381 utilDir = dirname(utilDir); | 362 if (path.basename(utilDir) == '') throw 'Could not find path to pub.'; |
363 utilDir = path.dirname(utilDir); | |
382 } | 364 } |
383 | 365 |
384 return path.normalize(join(utilDir, 'pub', target)); | 366 return path.normalize(join(utilDir, 'pub', target)); |
385 } | 367 } |
386 | 368 |
387 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr | 369 // TODO(nweiz): add a ByteSink wrapper to make writing strings to stdout/stderr |
388 // nicer. | 370 // nicer. |
389 | 371 |
390 /// A sink that writes to standard output. Errors piped to this stream will be | 372 /// A sink that writes to standard output. Errors piped to this stream will be |
391 /// surfaced to the top-level error handler. | 373 /// surfaced to the top-level error handler. |
(...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
721 | 703 |
722 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] | 704 /// Creates a temporary directory and passes its path to [fn]. Once the [Future] |
723 /// returned by [fn] completes, the temporary directory and all its contents | 705 /// returned by [fn] completes, the temporary directory and all its contents |
724 /// will be deleted. | 706 /// will be deleted. |
725 /// | 707 /// |
726 /// Returns a future that completes to the value that the future returned from | 708 /// Returns a future that completes to the value that the future returned from |
727 /// [fn] completes to. | 709 /// [fn] completes to. |
728 Future withTempDir(Future fn(String path)) { | 710 Future withTempDir(Future fn(String path)) { |
729 return defer(() { | 711 return defer(() { |
730 var tempDir = createTempDir(); | 712 var tempDir = createTempDir(); |
731 return fn(tempDir.path).whenComplete(() { | 713 return fn(tempDir).whenComplete(() { |
732 return deleteDir(tempDir); | 714 return deleteDir(tempDir); |
733 }); | 715 }); |
734 }); | 716 }); |
735 } | 717 } |
736 | 718 |
737 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a | 719 /// Extracts a `.tar.gz` file from [stream] to [destination], which can be a |
738 /// directory or a path. Returns whether or not the extraction was successful. | 720 /// directory or a path. Returns whether or not the extraction was successful. |
739 Future<bool> extractTarGz(Stream<List<int>> stream, destination) { | 721 Future<bool> extractTarGz(Stream<List<int>> stream, destination) { |
740 destination = _getPath(destination); | 722 destination = _getPath(destination); |
741 | 723 |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
828 var buffer = new StringBuffer(); | 810 var buffer = new StringBuffer(); |
829 buffer.add('Creating .tag.gz stream containing:\n'); | 811 buffer.add('Creating .tag.gz stream containing:\n'); |
830 contents.forEach((file) => buffer.add('$file\n')); | 812 contents.forEach((file) => buffer.add('$file\n')); |
831 log.fine(buffer.toString()); | 813 log.fine(buffer.toString()); |
832 | 814 |
833 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 815 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
834 // exit codes). See issue 3657. | 816 // exit codes). See issue 3657. |
835 var controller = new StreamController<List<int>>(); | 817 var controller = new StreamController<List<int>>(); |
836 | 818 |
837 if (baseDir == null) baseDir = path.current; | 819 if (baseDir == null) baseDir = path.current; |
838 baseDir = getFullPath(baseDir); | 820 baseDir = path.absolute(baseDir); |
839 contents = contents.map((entry) { | 821 contents = contents.map((entry) { |
840 entry = getFullPath(entry); | 822 entry = path.absolute(_getPath(entry)); |
841 if (!isBeneath(entry, baseDir)) { | 823 if (!isBeneath(entry, baseDir)) { |
842 throw 'Entry $entry is not inside $baseDir.'; | 824 throw 'Entry $entry is not inside $baseDir.'; |
843 } | 825 } |
844 return relativeTo(entry, baseDir); | 826 return path.relative(entry, from: baseDir); |
845 }).toList(); | 827 }).toList(); |
846 | 828 |
847 if (Platform.operatingSystem != "windows") { | 829 if (Platform.operatingSystem != "windows") { |
848 var args = ["--create", "--gzip", "--directory", baseDir]; | 830 var args = ["--create", "--gzip", "--directory", baseDir]; |
849 args.addAll(contents.map(_getPath)); | 831 args.addAll(contents.map(_getPath)); |
850 // TODO(nweiz): It's possible that enough command-line arguments will make | 832 // TODO(nweiz): It's possible that enough command-line arguments will make |
851 // the process choke, so at some point we should save the arguments to a | 833 // the process choke, so at some point we should save the arguments to a |
852 // file and pass them in via --files-from for tar and -i@filename for 7zip. | 834 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
853 startProcess("tar", args).then((process) { | 835 startProcess("tar", args).then((process) { |
854 store(process.stdout, controller); | 836 store(process.stdout, controller); |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
941 Directory _getDirectory(entry) { | 923 Directory _getDirectory(entry) { |
942 if (entry is Directory) return entry; | 924 if (entry is Directory) return entry; |
943 return new Directory(entry); | 925 return new Directory(entry); |
944 } | 926 } |
945 | 927 |
946 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 928 /// Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
947 Uri _getUri(uri) { | 929 Uri _getUri(uri) { |
948 if (uri is Uri) return uri; | 930 if (uri is Uri) return uri; |
949 return Uri.parse(uri); | 931 return Uri.parse(uri); |
950 } | 932 } |
OLD | NEW |