OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| 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. |
| 4 |
| 5 library package_config.packages_file; |
| 6 |
| 7 import "package:charcode/ascii.dart"; |
| 8 import "src/util.dart" show isIdentifier; |
| 9 |
| 10 /// Parses a `.packages` file into a map from package name to base URI. |
| 11 /// |
| 12 /// The [source] is the byte content of a `.packages` file, assumed to be |
| 13 /// UTF-8 encoded. In practice, all sinficant parts of the file must be ASCII, |
| 14 /// so Latin-1 or Windows-1252 encoding will also work fine. |
| 15 /// |
| 16 /// If the file content is available as a string, its [String.codeUnits] can |
| 17 /// be used as the `source` argument of this function. |
| 18 /// |
| 19 /// The [baseLocation] is used as a base URI to resolve all relative |
| 20 /// URI references against. |
| 21 /// If the content was read from a file, `baseLocation` should be the |
| 22 /// location of that file. |
| 23 /// |
| 24 /// Returns a simple mapping from package name to package location. |
| 25 Map<String, Uri> parse(List<int> source, Uri baseLocation) { |
| 26 int index = 0; |
| 27 Map<String, Uri> result = <String, Uri>{}; |
| 28 while (index < source.length) { |
| 29 bool isComment = false; |
| 30 int start = index; |
| 31 int eqIndex = -1; |
| 32 int end = source.length; |
| 33 int char = source[index++]; |
| 34 if (char == $cr || char == $lf) { |
| 35 continue; |
| 36 } |
| 37 if (char == $equal) { |
| 38 throw new FormatException("Missing package name", source, index - 1); |
| 39 } |
| 40 isComment = char == $hash; |
| 41 while (index < source.length) { |
| 42 char = source[index++]; |
| 43 if (char == $equal && eqIndex < 0) { |
| 44 eqIndex = index - 1; |
| 45 } else if (char == $cr || char == $lf) { |
| 46 end = index - 1; |
| 47 break; |
| 48 } |
| 49 } |
| 50 if (isComment) continue; |
| 51 if (eqIndex < 0) { |
| 52 throw new FormatException("No '=' on line", source, index - 1); |
| 53 } |
| 54 var packageName = new String.fromCharCodes(source, start, eqIndex); |
| 55 if (!isIdentifier(packageName)) { |
| 56 throw new FormatException("Not a valid package name", packageName, 0); |
| 57 } |
| 58 var packageUri = new String.fromCharCodes(source, eqIndex + 1, end); |
| 59 var packageLocation = Uri.parse(packageUri); |
| 60 if (!packageLocation.path.endsWith('/')) { |
| 61 packageLocation = |
| 62 packageLocation.replace(path: packageLocation.path + "/"); |
| 63 } |
| 64 packageLocation = baseLocation.resolveUri(packageLocation); |
| 65 if (result.containsKey(packageName)) { |
| 66 throw new FormatException( |
| 67 "Same package name occured twice.", source, start); |
| 68 } |
| 69 result[packageName] = packageLocation; |
| 70 } |
| 71 return result; |
| 72 } |
| 73 |
| 74 /// Writes the mapping to a [StringSink]. |
| 75 /// |
| 76 /// If [comment] is provided, the output will contain this comment |
| 77 /// with `#` in front of each line. |
| 78 /// |
| 79 /// If [baseUri] is provided, package locations will be made relative |
| 80 /// to the base URI, if possible, before writing. |
| 81 void write(StringSink output, Map<String, Uri> packageMapping, |
| 82 {Uri baseUri, String comment}) { |
| 83 if (baseUri != null && !baseUri.isAbsolute) { |
| 84 throw new ArgumentError.value(baseUri, "baseUri", "Must be absolute"); |
| 85 } |
| 86 |
| 87 if (comment != null) { |
| 88 for (var commentLine in comment.split('\n')) { |
| 89 output.write('#'); |
| 90 output.writeln(commentLine); |
| 91 } |
| 92 } else { |
| 93 output.write("# generated by package:package_config at "); |
| 94 output.write(new DateTime.now()); |
| 95 output.writeln(); |
| 96 } |
| 97 |
| 98 packageMapping.forEach((String packageName, Uri uri) { |
| 99 // Validate packageName. |
| 100 if (!isIdentifier(packageName)) { |
| 101 throw new ArgumentError('"$packageName" is not a valid package name'); |
| 102 } |
| 103 output.write(packageName); |
| 104 output.write('='); |
| 105 // If baseUri provided, make uri relative. |
| 106 if (baseUri != null) { |
| 107 uri = _relativize(uri, baseUri); |
| 108 } |
| 109 output.write(uri); |
| 110 if (!uri.path.endsWith('/')) { |
| 111 output.write('/'); |
| 112 } |
| 113 output.writeln(); |
| 114 }); |
| 115 } |
| 116 |
| 117 /// Attempts to return a relative URI for [uri]. |
| 118 /// |
| 119 /// The result URI satisfies `baseUri.resolveUri(result) == uri`, |
| 120 /// but may be relative. |
| 121 /// The `baseUri` must be absolute. |
| 122 Uri _relativize(Uri uri, Uri baseUri) { |
| 123 assert(!baseUri.isAbsolute); |
| 124 if (uri.hasQuery || uri.hasFragment) { |
| 125 uri = new Uri( |
| 126 scheme: uri.scheme, |
| 127 userInfo: uri.hasAuthority ? uri.userInfo : null, |
| 128 host: uri.hasAuthority ? uri.host : null, |
| 129 port: uri.hasAuthority ? uri.port : null, |
| 130 path: uri.path); |
| 131 } |
| 132 |
| 133 // Already relative. We assume the caller knows what they are doing. |
| 134 if (!uri.isAbsolute) return uri; |
| 135 |
| 136 if (baseUri.scheme != uri.scheme) { |
| 137 return uri; |
| 138 } |
| 139 |
| 140 // If authority differs, we could remove the scheme, but it's not worth it. |
| 141 if (uri.hasAuthority != baseUri.hasAuthority) return uri; |
| 142 if (uri.hasAuthority) { |
| 143 if (uri.userInfo != baseUri.userInfo || |
| 144 uri.host.toLowerCase() != baseUri.host.toLowerCase() || |
| 145 uri.port != baseUri.port) { |
| 146 return uri; |
| 147 } |
| 148 } |
| 149 |
| 150 baseUri = baseUri.normalizePath(); |
| 151 List<String> base = baseUri.pathSegments.toList(); |
| 152 if (base.isNotEmpty) { |
| 153 base = new List<String>.from(base)..removeLast(); |
| 154 } |
| 155 uri = uri.normalizePath(); |
| 156 List<String> target = uri.pathSegments.toList(); |
| 157 int index = 0; |
| 158 while (index < base.length && index < target.length) { |
| 159 if (base[index] != target[index]) { |
| 160 break; |
| 161 } |
| 162 index++; |
| 163 } |
| 164 if (index == base.length) { |
| 165 return new Uri(path: target.skip(index).join('/')); |
| 166 } else if (index > 0) { |
| 167 return new Uri( |
| 168 path: '../' * (base.length - index) + target.skip(index).join('/')); |
| 169 } else { |
| 170 return uri; |
| 171 } |
| 172 } |
OLD | NEW |