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 /** | 5 /** |
6 * Helper functionality to make working with IO easier. | 6 * Helper functionality to make working with IO easier. |
7 */ | 7 */ |
8 library io; | 8 library io; |
9 | 9 |
10 import 'dart:io'; | 10 import 'dart:io'; |
11 import 'dart:isolate'; | 11 import 'dart:isolate'; |
12 import 'dart:uri'; | 12 import 'dart:uri'; |
13 | 13 |
14 // TODO(nweiz): Make this import better. | 14 // TODO(nweiz): Make this import better. |
15 import '../../pkg/http/lib/http.dart' as http; | 15 import '../../pkg/http/lib/http.dart' as http; |
16 import 'curl_client.dart'; | |
16 import 'utils.dart'; | 17 import 'utils.dart'; |
17 import 'curl_client.dart'; | 18 import 'path.dart' as path; |
18 | 19 |
19 bool _isGitInstalledCache; | 20 bool _isGitInstalledCache; |
20 | 21 |
21 /// The cached Git command. | 22 /// The cached Git command. |
22 String _gitCommandCache; | 23 String _gitCommandCache; |
23 | 24 |
24 /** Gets the current working directory. */ | |
25 String get currentWorkingDir => new File('.').fullPathSync(); | |
26 | |
27 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); | 25 final NEWLINE_PATTERN = new RegExp("\r\n?|\n\r?"); |
28 | 26 |
29 /** | 27 /** |
30 * Prints the given string to `stderr` on its own line. | 28 * Prints the given string to `stderr` on its own line. |
31 */ | 29 */ |
32 void printError(value) { | 30 void printError(value) { |
33 stderr.writeString(value.toString()); | 31 stderr.writeString(value.toString()); |
34 stderr.writeString('\n'); | 32 stderr.writeString('\n'); |
35 } | 33 } |
36 | 34 |
37 | 35 |
38 /** | 36 /** |
39 * Joins a number of path string parts into a single path. Handles | 37 * Joins a number of path string parts into a single path. Handles |
40 * platform-specific path separators. Parts can be [String], [Directory], or | 38 * platform-specific path separators. Parts can be [String], [Directory], or |
41 * [File] objects. | 39 * [File] objects. |
42 */ | 40 */ |
43 String join(part1, [part2, part3, part4]) { | 41 String join(part1, [part2, part3, part4]) { |
44 final parts = sanitizePath(part1).split('/'); | 42 part1 = _getPath(part1); |
43 if (part2 != null) part2 = _getPath(part2); | |
44 if (part3 != null) part3 = _getPath(part3); | |
45 if (part4 != null) part4 = _getPath(part4); | |
45 | 46 |
46 for (final part in [part2, part3, part4]) { | 47 // TODO(nweiz): Don't use "?part" in path.dart. |
47 if (part == null) continue; | 48 if (part4 != null) { |
48 | 49 return path.join(part1, part2, part3, part4); |
49 for (final piece in _getPath(part).split('/')) { | 50 } else if (part3 != null) { |
50 if (piece == '..' && parts.length > 0 && | 51 return path.join(part1, part2, part3); |
51 parts.last != '.' && parts.last != '..') { | 52 } else if (part2 != null) { |
52 parts.removeLast(); | 53 return path.join(part1, part2); |
53 } else if (piece != '') { | 54 } else { |
54 if (parts.length > 0 && parts.last == '.') { | 55 return path.join(part1); |
55 parts.removeLast(); | |
56 } | |
57 parts.add(piece); | |
58 } | |
59 } | |
60 } | 56 } |
61 | |
62 return Strings.join(parts, Platform.pathSeparator); | |
63 } | 57 } |
64 | 58 |
65 /// Splits [path] into its individual components. | 59 /// Gets the basename, the file name without any leading directory path, for |
66 List<String> splitPath(path) => sanitizePath(path).split('/'); | 60 /// [file], which can either be a [String], [File], or [Directory]. |
61 String basename(file) => path.filename(_getPath(file)); | |
67 | 62 |
68 /** | 63 // TODO(nweiz): move this into path.dart. |
69 * Gets the basename, the file name without any leading directory path, for | 64 /// Gets the the leading directory path for [file], which can either be a |
70 * [file], which can either be a [String], [File], or [Directory]. | 65 /// [String], [File], or [Directory]. |
71 */ | 66 String dirname(file) { |
72 // TODO(rnystrom): Copied from file_system (so that we don't have to add | 67 file = _sanitizePath(file); |
73 // file_system to the SDK). Should unify. | |
74 String basename(file) { | |
75 file = sanitizePath(file); | |
76 | 68 |
77 int lastSlash = file.lastIndexOf('/', file.length); | 69 int lastSlash = file.lastIndexOf('/', file.length); |
78 if (lastSlash == -1) { | 70 if (lastSlash == -1) { |
79 return file; | |
80 } else { | |
81 return file.substring(lastSlash + 1); | |
82 } | |
83 } | |
84 | |
85 /** | |
86 * Gets the the leading directory path for [file], which can either be a | |
87 * [String], [File], or [Directory]. | |
88 */ | |
89 // TODO(nweiz): Copied from file_system (so that we don't have to add | |
90 // file_system to the SDK). Should unify. | |
91 String dirname(file) { | |
92 file = sanitizePath(file); | |
93 | |
94 int lastSlash = file.lastIndexOf('/', file.length); | |
95 if (lastSlash == -1) { | |
96 return '.'; | 71 return '.'; |
97 } else { | 72 } else { |
98 return file.substring(0, lastSlash); | 73 return file.substring(0, lastSlash); |
99 } | 74 } |
100 } | 75 } |
101 | 76 |
77 // TODO(nweiz): move this into path.dart. | |
78 /// Splits [path] into its individual components. | |
79 List<String> splitPath(path) => _sanitizePath(path).split('/'); | |
80 | |
102 /// Returns whether or not [entry] is nested somewhere within [dir]. This just | 81 /// Returns whether or not [entry] is nested somewhere within [dir]. This just |
103 /// performs a path comparison; it doesn't look at the actual filesystem. | 82 /// performs a path comparison; it doesn't look at the actual filesystem. |
104 bool isBeneath(entry, dir) => | 83 bool isBeneath(entry, dir) { |
105 sanitizePath(entry).startsWith('${sanitizePath(dir)}/'); | 84 var relative = relativeTo(entry, dir); |
85 return !path.isAbsolute(relative) && !relative.startsWith('..'); | |
86 } | |
87 | |
88 // TODO(nweiz): move this into path.dart. | |
89 /// Returns the path to [target] from [base]. | |
90 String relativeTo(target, base) => | |
91 new path.Builder(root: base).relative(target); | |
106 | 92 |
107 /** | 93 /** |
108 * Asynchronously determines if [path], which can be a [String] file path, a | 94 * Asynchronously determines if [path], which can be a [String] file path, a |
109 * [File], or a [Directory] exists on the file system. Returns a [Future] that | 95 * [File], or a [Directory] exists on the file system. Returns a [Future] that |
110 * completes with the result. | 96 * completes with the result. |
111 */ | 97 */ |
112 Future<bool> exists(path) { | 98 Future<bool> exists(path) { |
113 path = _getPath(path); | 99 path = _getPath(path); |
114 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { | 100 return Futures.wait([fileExists(path), dirExists(path)]).transform((results) { |
115 return results[0] || results[1]; | 101 return results[0] || results[1]; |
(...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
392 'Warning: Package "$name" does not have a "lib" directory so you ' | 378 'Warning: Package "$name" does not have a "lib" directory so you ' |
393 'will not be able to import any libraries from it.'); | 379 'will not be able to import any libraries from it.'); |
394 } | 380 } |
395 | 381 |
396 return new Future.immediate(to); | 382 return new Future.immediate(to); |
397 }); | 383 }); |
398 } | 384 } |
399 | 385 |
400 /// Given [entry] which may be a [String], [File], or [Directory] relative to | 386 /// Given [entry] which may be a [String], [File], or [Directory] relative to |
401 /// the current working directory, returns its full canonicalized path. | 387 /// the current working directory, returns its full canonicalized path. |
402 String getFullPath(entry) { | 388 String getFullPath(entry) => path.absolute(_getPath(entry)); |
403 var path = _getPath(entry); | |
404 | |
405 // Don't do anything if it's already absolute. | |
406 if (isAbsolute(path)) return path; | |
407 | |
408 // Using Path.join here instead of File().fullPathSync() because the former | |
409 // does not require an actual file to exist at that path. | |
410 return new Path.fromNative(currentWorkingDir).join(new Path(path)) | |
411 .toNativePath(); | |
412 } | |
413 | 389 |
414 /// Returns whether or not [entry] is an absolute path. | 390 /// Returns whether or not [entry] is an absolute path. |
415 bool isAbsolute(entry) => _splitAbsolute(entry).first != null; | 391 bool isAbsolute(entry) => path.isAbsolute(_getPath(entry)); |
416 | 392 |
417 /// Splits [entry] into two components: the absolute path prefix and the | 393 /// Resolves [target] relative to the location of pub.dart. |
418 /// remaining path. Takes into account Windows' quirky absolute paths syntaxes. | 394 String relativeToPub(String target) { |
419 Pair<String, String> _splitAbsolute(entry) { | |
420 var path = _getPath(entry); | |
421 | |
422 if (Platform.operatingSystem != 'windows') { | |
423 return !path.startsWith('/') ? new Pair(null, path) | |
424 : new Pair('/', path.substring(1)); | |
425 } | |
426 | |
427 // An absolute path on Windows is either UNC (two leading backslashes), | |
428 // or a drive letter followed by a colon and a slash. | |
429 var match = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])').firstMatch(path); | |
430 return match == null ? new Pair(null, path) | |
431 : new Pair(match.group(0), path.substring(match.end)); | |
432 } | |
433 | |
434 /// Resolves [path] relative to the location of pub.dart. | |
435 String relativeToPub(String path) { | |
436 var scriptPath = new File(new Options().script).fullPathSync(); | 395 var scriptPath = new File(new Options().script).fullPathSync(); |
437 | 396 |
438 // Walk up until we hit the "util(s)" directory. This lets us figure out where | 397 // Walk up until we hit the "util(s)" directory. This lets us figure out where |
439 // we are if this function is called from pub.dart, or one of the tests, | 398 // we are if this function is called from pub.dart, or one of the tests, |
440 // which also live under "utils", or from the SDK where pub is in "util". | 399 // which also live under "utils", or from the SDK where pub is in "util". |
441 var utilDir = new Path.fromNative(scriptPath).directoryPath; | 400 var utilDir = dirname(scriptPath); |
442 while (utilDir.filename != 'utils' && utilDir.filename != 'util') { | 401 while (basename(utilDir) != 'utils' && basename(utilDir) != 'util') { |
443 if (utilDir.filename == '') throw 'Could not find path to pub.'; | 402 if (basename(utilDir) == '') throw 'Could not find path to pub.'; |
444 utilDir = utilDir.directoryPath; | 403 utilDir = dirname(utilDir); |
445 } | 404 } |
446 | 405 |
447 return utilDir.append('pub').append(path).canonicalize().toNativePath(); | 406 return path.normalize(join(utilDir, 'pub', target)); |
448 } | 407 } |
449 | 408 |
450 /// A StringInputStream reading from stdin. | 409 /// A StringInputStream reading from stdin. |
451 final _stringStdin = new StringInputStream(stdin); | 410 final _stringStdin = new StringInputStream(stdin); |
452 | 411 |
453 /// Returns a single line read from a [StringInputStream]. By default, reads | 412 /// Returns a single line read from a [StringInputStream]. By default, reads |
454 /// from stdin. | 413 /// from stdin. |
455 /// | 414 /// |
456 /// A [StringInputStream] passed to this should have no callbacks registered. | 415 /// A [StringInputStream] passed to this should have no callbacks registered. |
457 Future<String> readLine([StringInputStream stream]) { | 416 Future<String> readLine([StringInputStream stream]) { |
(...skipping 397 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
855 if (result.exitCode != 0) { | 814 if (result.exitCode != 0) { |
856 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' | 815 throw 'Could not un-gzip (exit code ${result.exitCode}). Error:\n' |
857 '${Strings.join(result.stdout, "\n")}\n' | 816 '${Strings.join(result.stdout, "\n")}\n' |
858 '${Strings.join(result.stderr, "\n")}'; | 817 '${Strings.join(result.stderr, "\n")}'; |
859 } | 818 } |
860 // Find the tar file we just created since we don't know its name. | 819 // Find the tar file we just created since we don't know its name. |
861 return listDir(tempDir); | 820 return listDir(tempDir); |
862 }).chain((files) { | 821 }).chain((files) { |
863 var tarFile; | 822 var tarFile; |
864 for (var file in files) { | 823 for (var file in files) { |
865 if (new Path(file).extension == 'tar') { | 824 if (path.extension(file) == '.tar') { |
866 tarFile = file; | 825 tarFile = file; |
867 break; | 826 break; |
868 } | 827 } |
869 } | 828 } |
870 | 829 |
871 if (tarFile == null) throw 'The gzip file did not contain a tar file.'; | 830 if (tarFile == null) throw 'The gzip file did not contain a tar file.'; |
872 | 831 |
873 // Untar the archive into the destination directory. | 832 // Untar the archive into the destination directory. |
874 return runProcess(command, ['x', tarFile], workingDir: destination); | 833 return runProcess(command, ['x', tarFile], workingDir: destination); |
875 }).chain((result) { | 834 }).chain((result) { |
(...skipping 11 matching lines...) Expand all Loading... | |
887 | 846 |
888 /// Create a .tar.gz archive from a list of entries. Each entry can be a | 847 /// Create a .tar.gz archive from a list of entries. Each entry can be a |
889 /// [String], [Directory], or [File] object. The root of the archive is | 848 /// [String], [Directory], or [File] object. The root of the archive is |
890 /// considered to be [baseDir], which defaults to the current working directory. | 849 /// considered to be [baseDir], which defaults to the current working directory. |
891 /// Returns an [InputStream] that will emit the contents of the archive. | 850 /// Returns an [InputStream] that will emit the contents of the archive. |
892 InputStream createTarGz(List contents, {baseDir}) { | 851 InputStream createTarGz(List contents, {baseDir}) { |
893 // TODO(nweiz): Propagate errors to the returned stream (including non-zero | 852 // TODO(nweiz): Propagate errors to the returned stream (including non-zero |
894 // exit codes). See issue 3657. | 853 // exit codes). See issue 3657. |
895 var stream = new ListInputStream(); | 854 var stream = new ListInputStream(); |
896 | 855 |
897 if (baseDir == null) baseDir = currentWorkingDir; | 856 if (baseDir == null) baseDir = path.current; |
898 baseDir = getFullPath(baseDir); | 857 baseDir = getFullPath(baseDir); |
899 contents = contents.map((entry) { | 858 contents = contents.map((entry) { |
900 entry = getFullPath(entry); | 859 entry = getFullPath(entry); |
901 if (!isBeneath(entry, baseDir)) { | 860 if (!isBeneath(entry, baseDir)) { |
902 throw 'Entry $entry is not inside $baseDir.'; | 861 throw 'Entry $entry is not inside $baseDir.'; |
903 } | 862 } |
904 return new Path.fromNative(entry).relativeTo(new Path.fromNative(baseDir)) | 863 return relativeTo(entry, baseDir); |
905 .toNativePath(); | |
906 }); | 864 }); |
907 | 865 |
908 if (Platform.operatingSystem != "windows") { | 866 if (Platform.operatingSystem != "windows") { |
909 var args = ["--create", "--gzip", "--directory", baseDir]; | 867 var args = ["--create", "--gzip", "--directory", baseDir]; |
910 args.addAll(contents.map(_getPath)); | 868 args.addAll(contents.map(_getPath)); |
911 // TODO(nweiz): It's possible that enough command-line arguments will make | 869 // TODO(nweiz): It's possible that enough command-line arguments will make |
912 // the process choke, so at some point we should save the arguments to a | 870 // the process choke, so at some point we should save the arguments to a |
913 // file and pass them in via --files-from for tar and -i@filename for 7zip. | 871 // file and pass them in via --files-from for tar and -i@filename for 7zip. |
914 startProcess("tar", args).then((process) { | 872 startProcess("tar", args).then((process) { |
915 pipeInputToInput(process.stdout, stream); | 873 pipeInputToInput(process.stdout, stream); |
(...skipping 73 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
989 */ | 947 */ |
990 String _getPath(entry) { | 948 String _getPath(entry) { |
991 if (entry is String) return entry; | 949 if (entry is String) return entry; |
992 if (entry is File) return entry.name; | 950 if (entry is File) return entry.name; |
993 if (entry is Directory) return entry.path; | 951 if (entry is Directory) return entry.path; |
994 throw 'Entry $entry is not a supported type.'; | 952 throw 'Entry $entry is not a supported type.'; |
995 } | 953 } |
996 | 954 |
997 /// Gets the path string for [entry], normalizing backslashes to forward slashes | 955 /// Gets the path string for [entry], normalizing backslashes to forward slashes |
998 /// on Windows. | 956 /// on Windows. |
999 String sanitizePath(entry) { | 957 String _sanitizePath(entry) { |
1000 entry = _getPath(entry); | 958 entry = _getPath(entry); |
1001 if (Platform.operatingSystem != 'windows') return entry; | 959 if (Platform.operatingSystem != 'windows') return entry; |
1002 | 960 |
1003 var split = _splitAbsolute(entry); | 961 var split = _splitAbsolute(entry); |
1004 if (split.first == null) return split.last.replaceAll('\\', '/'); | 962 if (split.first == null) return split.last.replaceAll('\\', '/'); |
1005 | 963 |
1006 // For absolute Windows paths, we don't want the prefix (either "\\" or e.g. | 964 // For absolute Windows paths, we don't want the prefix (either "\\" or e.g. |
1007 // "C:\") to look like a normal path component, so we ensure that it only | 965 // "C:\") to look like a normal path component, so we ensure that it only |
1008 // contains backslashes. | 966 // contains backslashes. |
1009 return '${split.first.replaceAll('/', '\\')}' | 967 return '${split.first.replaceAll('/', '\\')}' |
1010 '${split.last.replaceAll('\\', '/')}'; | 968 '${split.last.replaceAll('\\', '/')}'; |
1011 } | 969 } |
1012 | 970 |
971 /// Splits [entry] into two components: the absolute path prefix and the | |
972 /// remaining path. Takes into account Windows' quirky absolute paths syntaxes. | |
Bob Nystrom
2012/12/08 03:36:53
Add a TODO to move this (or something similar) int
nweiz
2012/12/08 03:43:45
Done.
| |
973 Pair<String, String> _splitAbsolute(entry) { | |
974 var path = _getPath(entry); | |
975 | |
976 if (Platform.operatingSystem != 'windows') { | |
977 return !path.startsWith('/') ? new Pair(null, path) | |
978 : new Pair('/', path.substring(1)); | |
979 } | |
980 | |
981 // An absolute path on Windows is either UNC (two leading backslashes), | |
982 // or a drive letter followed by a colon and a slash. | |
983 var match = new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])').firstMatch(path); | |
984 return match == null ? new Pair(null, path) | |
985 : new Pair(match.group(0), path.substring(match.end)); | |
986 } | |
987 | |
1013 /** | 988 /** |
1014 * Gets a [Directory] for [entry], which can either already be one, or be a | 989 * Gets a [Directory] for [entry], which can either already be one, or be a |
1015 * [String]. | 990 * [String]. |
1016 */ | 991 */ |
1017 Directory _getDirectory(entry) { | 992 Directory _getDirectory(entry) { |
1018 if (entry is Directory) return entry; | 993 if (entry is Directory) return entry; |
1019 return new Directory(entry); | 994 return new Directory(entry); |
1020 } | 995 } |
1021 | 996 |
1022 /** | 997 /** |
1023 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. | 998 * Gets a [Uri] for [uri], which can either already be one, or be a [String]. |
1024 */ | 999 */ |
1025 Uri _getUri(uri) { | 1000 Uri _getUri(uri) { |
1026 if (uri is Uri) return uri; | 1001 if (uri is Uri) return uri; |
1027 return new Uri.fromString(uri); | 1002 return new Uri.fromString(uri); |
1028 } | 1003 } |
OLD | NEW |