| 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.packagemap; | |
| 6 | |
| 7 class Packages { | |
| 8 static const int _EQUALS = 0x3d; | |
| 9 static const int _CR = 0x0d; | |
| 10 static const int _NL = 0x0a; | |
| 11 static const int _NUMBER_SIGN = 0x23; | |
| 12 | |
| 13 final Map<String, Uri> packageMapping; | |
| 14 | |
| 15 Packages(this.packageMapping); | |
| 16 | |
| 17 /// Resolves a URI to a non-package URI. | |
| 18 /// | |
| 19 /// If [uri] is a `package:` URI, the location is resolved wrt. the | |
| 20 /// [packageMapping]. | |
| 21 /// Otherwise the original URI is returned. | |
| 22 Uri resolve(Uri uri) { | |
| 23 if (uri.scheme.toLowerCase() != "package") { | |
| 24 return uri; | |
| 25 } | |
| 26 if (uri.hasAuthority) { | |
| 27 throw new ArgumentError.value(uri, "uri", "Must not have authority"); | |
| 28 } | |
| 29 if (uri.path.startsWith("/")) { | |
| 30 throw new ArgumentError.value( | |
| 31 uri, "uri", "Path must not start with '/'."); | |
| 32 } | |
| 33 // Normalizes the path by removing '.' and '..' segments. | |
| 34 uri = uri.normalizePath(); | |
| 35 String path = uri.path; | |
| 36 var slashIndex = path.indexOf('/'); | |
| 37 String packageName; | |
| 38 String rest; | |
| 39 if (slashIndex < 0) { | |
| 40 packageName = path; | |
| 41 rest = ""; | |
| 42 } else { | |
| 43 packageName = path.substring(0, slashIndex); | |
| 44 rest = path.substring(slashIndex + 1); | |
| 45 } | |
| 46 Uri packageLocation = packageMapping[packageName]; | |
| 47 if (packageLocation == null) { | |
| 48 throw new ArgumentError.value( | |
| 49 uri, "uri", "Unknown package name: $packageName"); | |
| 50 } | |
| 51 return packageLocation.resolveUri(new Uri(path: rest)); | |
| 52 } | |
| 53 | |
| 54 /// Parses a `packages.cfg` file into a `Packages` object. | |
| 55 /// | |
| 56 /// The [baseLocation] is used as a base URI to resolve all relative | |
| 57 /// URI references against. | |
| 58 /// | |
| 59 /// The `Packages` object allows resolving package: URIs and writing | |
| 60 /// the mapping back to a file or string. | |
| 61 /// The [packageMapping] will contain a simple mapping from package name | |
| 62 /// to package location. | |
| 63 static Packages parse(String source, Uri baseLocation) { | |
| 64 int index = 0; | |
| 65 Map<String, Uri> result = <String, Uri>{}; | |
| 66 while (index < source.length) { | |
| 67 bool isComment = false; | |
| 68 int start = index; | |
| 69 int eqIndex = -1; | |
| 70 int end = source.length; | |
| 71 int char = source.codeUnitAt(index++); | |
| 72 if (char == _CR || char == _NL) { | |
| 73 continue; | |
| 74 } | |
| 75 if (char == _EQUALS) { | |
| 76 throw new FormatException("Missing package name", source, index - 1); | |
| 77 } | |
| 78 isComment = char == _NUMBER_SIGN; | |
| 79 while (index < source.length) { | |
| 80 char = source.codeUnitAt(index++); | |
| 81 if (char == _EQUALS && eqIndex < 0) { | |
| 82 eqIndex = index - 1; | |
| 83 } else if (char == _NL || char == _CR) { | |
| 84 end = index - 1; | |
| 85 break; | |
| 86 } | |
| 87 } | |
| 88 if (isComment) continue; | |
| 89 if (eqIndex < 0) { | |
| 90 throw new FormatException("No '=' on line", source, index - 1); | |
| 91 } | |
| 92 _checkIdentifier(source, start, eqIndex); | |
| 93 var packageName = source.substring(start, eqIndex); | |
| 94 | |
| 95 var packageLocation = Uri.parse(source, eqIndex + 1, end); | |
| 96 if (!packageLocation.path.endsWith('/')) { | |
| 97 packageLocation = | |
| 98 packageLocation.replace(path: packageLocation.path + "/"); | |
| 99 } | |
| 100 packageLocation = baseLocation.resolveUri(packageLocation); | |
| 101 if (result.containsKey(packageName)) { | |
| 102 throw new FormatException( | |
| 103 "Same package name occured twice.", source, start); | |
| 104 } | |
| 105 result[packageName] = packageLocation; | |
| 106 } | |
| 107 return new Packages(result); | |
| 108 } | |
| 109 | |
| 110 /** | |
| 111 * Writes the mapping to a [StringSink]. | |
| 112 * | |
| 113 * If [comment] is provided, the output will contain this comment | |
| 114 * with `#` in front of each line. | |
| 115 * | |
| 116 * If [baseUri] is provided, package locations will be made relative | |
| 117 * to the base URI, if possible, before writing. | |
| 118 */ | |
| 119 void write(StringSink output, {Uri baseUri, String comment}) { | |
| 120 if (baseUri != null && !baseUri.isAbsolute) { | |
| 121 throw new ArgumentError.value(baseUri, "baseUri", "Must be absolute"); | |
| 122 } | |
| 123 | |
| 124 if (comment != null) { | |
| 125 for (var commentLine in comment.split('\n')) { | |
| 126 output.write('#'); | |
| 127 output.writeln(commentLine); | |
| 128 } | |
| 129 } else { | |
| 130 output.write("# generated by package:packagecfg at "); | |
| 131 output.write(new DateTime.now()); | |
| 132 output.writeln(); | |
| 133 } | |
| 134 | |
| 135 packageMapping.forEach((String packageName, Uri uri) { | |
| 136 // Validate packageName. | |
| 137 _checkIdentifier(packageName, 0, packageName.length); | |
| 138 output.write(packageName); | |
| 139 | |
| 140 output.write('='); | |
| 141 | |
| 142 // If baseUri provided, make uri relative. | |
| 143 if (baseUri != null) { | |
| 144 uri = relativize(uri, baseUri); | |
| 145 } | |
| 146 output.write(uri); | |
| 147 if (!uri.path.endsWith('/')) { | |
| 148 output.write('/'); | |
| 149 } | |
| 150 output.writeln(); | |
| 151 }); | |
| 152 } | |
| 153 | |
| 154 String toString() { | |
| 155 StringBuffer buffer = new StringBuffer(); | |
| 156 write(buffer); | |
| 157 return buffer.toString(); | |
| 158 } | |
| 159 | |
| 160 static Uri relativize(Uri uri, Uri baseUri) { | |
| 161 if (uri.hasQuery || uri.hasFragment) { | |
| 162 uri = new Uri( | |
| 163 scheme: uri.scheme, | |
| 164 userInfo: uri.hasAuthority ? uri.userInfo : null, | |
| 165 host: uri.hasAuthority ? uri.host : null, | |
| 166 port: uri.hasAuthority ? uri.port : null, | |
| 167 path: uri.path); | |
| 168 } | |
| 169 if (!baseUri.isAbsolute) { | |
| 170 throw new ArgumentError("Base uri '$baseUri' must be absolute."); | |
| 171 } | |
| 172 // Already relative. | |
| 173 if (!uri.isAbsolute) return uri; | |
| 174 | |
| 175 if (baseUri.scheme.toLowerCase() != uri.scheme.toLowerCase()) { | |
| 176 return uri; | |
| 177 } | |
| 178 // If authority differs, we could remove the scheme, but it's not worth it. | |
| 179 if (uri.hasAuthority != baseUri.hasAuthority) return uri; | |
| 180 if (uri.hasAuthority) { | |
| 181 if (uri.userInfo != baseUri.userInfo || | |
| 182 uri.host.toLowerCase() != baseUri.host.toLowerCase() || | |
| 183 uri.port != baseUri.port) { | |
| 184 return uri; | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 baseUri = baseUri.normalizePath(); | |
| 189 List<String> base = baseUri.pathSegments.toList(); | |
| 190 if (base.isNotEmpty) { | |
| 191 base = new List<String>.from(base)..removeLast(); | |
| 192 } | |
| 193 uri = uri.normalizePath(); | |
| 194 List<String> target = uri.pathSegments.toList(); | |
| 195 int index = 0; | |
| 196 while (index < base.length && index < target.length) { | |
| 197 if (base[index] != target[index]) { | |
| 198 break; | |
| 199 } | |
| 200 index++; | |
| 201 } | |
| 202 if (index == base.length) { | |
| 203 return new Uri(path: target.skip(index).join('/')); | |
| 204 } else if (index > 0) { | |
| 205 return new Uri( | |
| 206 path: '../' * (base.length - index) + target.skip(index).join('/')); | |
| 207 } else { | |
| 208 return uri; | |
| 209 } | |
| 210 } | |
| 211 | |
| 212 static bool _checkIdentifier(String string, int start, int end) { | |
| 213 const int a = 0x61; | |
| 214 const int z = 0x7a; | |
| 215 const int _ = 0x5f; | |
| 216 const int $ = 0x24; | |
| 217 if (start == end) return false; | |
| 218 for (int i = start; i < end; i++) { | |
| 219 var char = string.codeUnitAt(i); | |
| 220 if (char == _ || char == $) continue; | |
| 221 if ((char ^ 0x30) <= 9 && i > 0) continue; | |
| 222 char |= 0x20; // Lower-case letters. | |
| 223 if (char >= a && char <= z) continue; | |
| 224 throw new FormatException("Not an identifier", string, i); | |
| 225 } | |
| 226 return true; | |
| 227 } | |
| 228 } | |
| OLD | NEW |