| // Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import "package_config_impl.dart"; |
| import "package:charcode/ascii.dart"; |
| |
| import "util.dart" show isValidPackageName, relativizeUri; |
| import "errors.dart"; |
| |
| /// Parses a `.packages` file into a [PackageConfig]. |
| /// |
| /// The [source] is the byte content of a `.packages` file, assumed to be |
| /// UTF-8 encoded. In practice, all significant parts of the file must be ASCII, |
| /// so Latin-1 or Windows-1252 encoding will also work fine. |
| /// |
| /// If the file content is available as a string, its [String.codeUnits] can |
| /// be used as the `source` argument of this function. |
| /// |
| /// The [baseLocation] is used as a base URI to resolve all relative |
| /// URI references against. |
| /// If the content was read from a file, `baseLocation` should be the |
| /// location of that file. |
| /// |
| /// Returns a simple package configuration where each package's |
| /// [Package.packageUriRoot] is the same as its [Package.root] |
| /// and it has no [Package.languageVersion]. |
| PackageConfig parse(List<int> source, Uri baseLocation) { |
| if (baseLocation.isScheme("package")) { |
| throw PackageConfigArgumentError( |
| baseLocation, "baseLocation", "Must not be a package: URI"); |
| } |
| int index = 0; |
| List<Package> packages = []; |
| Set<String> packageNames = {}; |
| while (index < source.length) { |
| bool isComment = false; |
| int start = index; |
| int separatorIndex = -1; |
| int end = source.length; |
| int char = source[index++]; |
| if (char == $cr || char == $lf) { |
| continue; |
| } |
| if (char == $colon) { |
| throw PackageConfigFormatException( |
| "Missing package name", source, index - 1); |
| } |
| isComment = char == $hash; |
| while (index < source.length) { |
| char = source[index++]; |
| if (char == $colon && separatorIndex < 0) { |
| separatorIndex = index - 1; |
| } else if (char == $cr || char == $lf) { |
| end = index - 1; |
| break; |
| } |
| } |
| if (isComment) continue; |
| if (separatorIndex < 0) { |
| throw PackageConfigFormatException("No ':' on line", source, index - 1); |
| } |
| var packageName = String.fromCharCodes(source, start, separatorIndex); |
| if (!isValidPackageName(packageName)) { |
| throw PackageConfigFormatException( |
| "Not a valid package name", packageName, 0); |
| } |
| var packageValue = String.fromCharCodes(source, separatorIndex + 1, end); |
| Uri packageLocation = baseLocation.resolve(packageValue); |
| if (packageLocation.isScheme("package")) { |
| throw PackageConfigFormatException( |
| "Package URI as location for package", source, separatorIndex + 1); |
| } |
| if (packageLocation.hasQuery || packageLocation.hasFragment) { |
| throw PackageConfigFormatException( |
| "Location URI must not have query or fragment", source, start); |
| } |
| if (!packageLocation.path.endsWith('/')) { |
| packageLocation = |
| packageLocation.replace(path: packageLocation.path + "/"); |
| } |
| if (packageNames.contains(packageName)) { |
| throw PackageConfigFormatException( |
| "Same package name occured more than once", source, start); |
| } |
| packages.add(SimplePackage( |
| packageName, packageLocation, packageLocation, null, null)); |
| packageNames.add(packageName); |
| } |
| return SimplePackageConfig(1, packages, null); |
| } |
| |
| /// Writes the configuration to a [StringSink]. |
| /// |
| /// If [comment] is provided, the output will contain this comment |
| /// with `# ` in front of each line. |
| /// Lines are defined as ending in line feed (`'\n'`). If the final |
| /// line of the comment doesn't end in a line feed, one will be added. |
| /// |
| /// If [baseUri] is provided, package locations will be made relative |
| /// to the base URI, if possible, before writing. |
| /// |
| /// If [allowDefaultPackage] is `true`, the [packageMapping] may contain an |
| /// empty string mapping to the _default package name_. |
| /// |
| /// All the keys of [packageMapping] must be valid package names, |
| /// and the values must be URIs that do not have the `package:` scheme. |
| void write(StringSink output, PackageConfig config, |
| {Uri baseUri, String comment}) { |
| if (baseUri != null && !baseUri.isAbsolute) { |
| throw PackageConfigArgumentError(baseUri, "baseUri", "Must be absolute"); |
| } |
| |
| if (comment != null) { |
| var lines = comment.split('\n'); |
| if (lines.last.isEmpty) lines.removeLast(); |
| for (var commentLine in lines) { |
| output.write('# '); |
| output.writeln(commentLine); |
| } |
| } else { |
| output.write("# generated by package:package_config at "); |
| output.write(DateTime.now()); |
| output.writeln(); |
| } |
| for (var package in config.packages) { |
| var packageName = package.name; |
| var uri = package.packageUriRoot; |
| // Validate packageName. |
| if (!isValidPackageName(packageName)) { |
| throw PackageConfigArgumentError( |
| config, "config", '"$packageName" is not a valid package name'); |
| } |
| if (uri.scheme == "package") { |
| throw PackageConfigArgumentError( |
| config, "config", "Package location must not be a package URI: $uri"); |
| } |
| output.write(packageName); |
| output.write(':'); |
| // If baseUri provided, make uri relative. |
| if (baseUri != null) { |
| uri = relativizeUri(uri, baseUri); |
| } |
| if (!uri.path.endsWith('/')) { |
| uri = uri.replace(path: uri.path + '/'); |
| } |
| output.write(uri); |
| output.writeln(); |
| } |
| } |