| Index: sdk/lib/_internal/pub/lib/src/pubspec.dart
|
| diff --git a/sdk/lib/_internal/pub/lib/src/pubspec.dart b/sdk/lib/_internal/pub/lib/src/pubspec.dart
|
| index e01cab1583142c5776895616e536a151fba62243..2100cc60171a05c8b26f29a25c3f68ed45d55f07 100644
|
| --- a/sdk/lib/_internal/pub/lib/src/pubspec.dart
|
| +++ b/sdk/lib/_internal/pub/lib/src/pubspec.dart
|
| @@ -74,6 +74,13 @@ class Pubspec {
|
| bool get isEmpty =>
|
| name == null && version == Version.none && dependencies.isEmpty;
|
|
|
| + /// Returns a Pubspec object for an already-parsed map representing its
|
| + /// contents.
|
| + ///
|
| + /// This will validate that [contents] is a valid pubspec.
|
| + factory Pubspec.fromMap(Map contents, SourceRegistry sources) =>
|
| + _parseMap(null, contents, sources);
|
| +
|
| // TODO(rnystrom): Instead of allowing a null argument here, split this up
|
| // into load(), parse(), and _parse() like LockFile does.
|
| /// Parses the pubspec stored at [filePath] whose text is [contents]. If the
|
| @@ -82,9 +89,6 @@ class Pubspec {
|
| /// file system.
|
| factory Pubspec.parse(String filePath, String contents,
|
| SourceRegistry sources) {
|
| - var name = null;
|
| - var version = Version.none;
|
| -
|
| if (contents.trim() == '') return new Pubspec.empty();
|
|
|
| var parsedPubspec = loadYaml(contents);
|
| @@ -94,141 +98,147 @@ class Pubspec {
|
| throw new FormatException('The pubspec must be a YAML mapping.');
|
| }
|
|
|
| - if (parsedPubspec.containsKey('name')) {
|
| - name = parsedPubspec['name'];
|
| - if (name is! String) {
|
| - throw new FormatException(
|
| - 'The pubspec "name" field should be a string, but was "$name".');
|
| - }
|
| - }
|
| + return _parseMap(filePath, parsedPubspec, sources);
|
| + }
|
| +}
|
|
|
| - if (parsedPubspec.containsKey('version')) {
|
| - version = new Version.parse(parsedPubspec['version']);
|
| - }
|
| +/// Evaluates whether the given [url] for [field] is valid.
|
| +///
|
| +/// Throws [FormatException] on an invalid url.
|
| +void _validateFieldUrl(url, String field) {
|
| + if (url is! String) {
|
| + throw new FormatException(
|
| + 'The "$field" field should be a string, but was "$url".');
|
| + }
|
|
|
| - var dependencies = _parseDependencies(filePath, sources,
|
| - parsedPubspec['dependencies']);
|
| -
|
| - var devDependencies = _parseDependencies(filePath, sources,
|
| - parsedPubspec['dev_dependencies']);
|
| -
|
| - // Make sure the same package doesn't appear as both a regular and dev
|
| - // dependency.
|
| - var dependencyNames = dependencies.map((dep) => dep.name).toSet();
|
| - var collisions = dependencyNames.intersection(
|
| - devDependencies.map((dep) => dep.name).toSet());
|
| -
|
| - if (!collisions.isEmpty) {
|
| - var packageNames;
|
| - if (collisions.length == 1) {
|
| - packageNames = 'Package "${collisions.first}"';
|
| - } else {
|
| - var names = collisions.toList();
|
| - names.sort();
|
| - var buffer = new StringBuffer();
|
| - buffer.write("Packages ");
|
| - for (var i = 0; i < names.length; i++) {
|
| - buffer.write('"');
|
| - buffer.write(names[i]);
|
| - buffer.write('"');
|
| - if (i == names.length - 2) {
|
| - buffer.write(", ");
|
| - } else if (i == names.length - 1) {
|
| - buffer.write(", and ");
|
| - }
|
| - }
|
| + var goodScheme = new RegExp(r'^https?:');
|
| + if (!goodScheme.hasMatch(url)) {
|
| + throw new FormatException(
|
| + 'The "$field" field should be an "http:" or "https:" URL, but '
|
| + 'was "$url".');
|
| + }
|
| +}
|
|
|
| - packageNames = buffer.toString();
|
| - }
|
| +Pubspec _parseMap(String filePath, Map map, SourceRegistry sources) {
|
| + var name = null;
|
| + var version = Version.none;
|
| +
|
| + if (map.containsKey('name')) {
|
| + name = map['name'];
|
| + if (name is! String) {
|
| throw new FormatException(
|
| - '$packageNames cannot appear in both "dependencies" and '
|
| - '"dev_dependencies".');
|
| + 'The pubspec "name" field should be a string, but was "$name".');
|
| }
|
| + }
|
|
|
| - var environmentYaml = parsedPubspec['environment'];
|
| - var sdkConstraint = VersionConstraint.any;
|
| - if (environmentYaml != null) {
|
| - if (environmentYaml is! Map) {
|
| - throw new FormatException(
|
| - 'The pubspec "environment" field should be a map, but was '
|
| - '"$environmentYaml".');
|
| - }
|
| + if (map.containsKey('version')) {
|
| + version = new Version.parse(map['version']);
|
| + }
|
|
|
| - var sdkYaml = environmentYaml['sdk'];
|
| - if (sdkYaml is! String) {
|
| - throw new FormatException(
|
| - 'The "sdk" field of "environment" should be a string, but was '
|
| - '"$sdkYaml".');
|
| + var dependencies = _parseDependencies(filePath, sources,
|
| + map['dependencies']);
|
| +
|
| + var devDependencies = _parseDependencies(filePath, sources,
|
| + map['dev_dependencies']);
|
| +
|
| + // Make sure the same package doesn't appear as both a regular and dev
|
| + // dependency.
|
| + var dependencyNames = dependencies.map((dep) => dep.name).toSet();
|
| + var collisions = dependencyNames.intersection(
|
| + devDependencies.map((dep) => dep.name).toSet());
|
| +
|
| + if (!collisions.isEmpty) {
|
| + var packageNames;
|
| + if (collisions.length == 1) {
|
| + packageNames = 'Package "${collisions.first}"';
|
| + } else {
|
| + var names = collisions.toList();
|
| + names.sort();
|
| + var buffer = new StringBuffer();
|
| + buffer.write("Packages ");
|
| + for (var i = 0; i < names.length; i++) {
|
| + buffer.write('"');
|
| + buffer.write(names[i]);
|
| + buffer.write('"');
|
| + if (i == names.length - 2) {
|
| + buffer.write(", ");
|
| + } else if (i == names.length - 1) {
|
| + buffer.write(", and ");
|
| + }
|
| }
|
|
|
| - sdkConstraint = new VersionConstraint.parse(sdkYaml);
|
| - }
|
| - var environment = new PubspecEnvironment(sdkConstraint);
|
| -
|
| - // Even though the pub app itself doesn't use these fields, we validate
|
| - // them here so that users find errors early before they try to upload to
|
| - // the server:
|
| - // TODO(rnystrom): We should split this validation into separate layers:
|
| - // 1. Stuff that is required in any pubspec to perform any command. Things
|
| - // like "must have a name". That should go here.
|
| - // 2. Stuff that is required to upload a package. Things like "homepage
|
| - // must use a valid scheme". That should go elsewhere. pub upload should
|
| - // call it, and we should provide a separate command to show the user,
|
| - // and also expose it to the editor in some way.
|
| -
|
| - if (parsedPubspec.containsKey('homepage')) {
|
| - _validateFieldUrl(parsedPubspec['homepage'], 'homepage');
|
| - }
|
| - if (parsedPubspec.containsKey('documentation')) {
|
| - _validateFieldUrl(parsedPubspec['documentation'], 'documentation');
|
| + packageNames = buffer.toString();
|
| }
|
| + throw new FormatException(
|
| + '$packageNames cannot appear in both "dependencies" and '
|
| + '"dev_dependencies".');
|
| + }
|
|
|
| - if (parsedPubspec.containsKey('author') &&
|
| - parsedPubspec['author'] is! String) {
|
| + var environmentYaml = map['environment'];
|
| + var sdkConstraint = VersionConstraint.any;
|
| + if (environmentYaml != null) {
|
| + if (environmentYaml is! Map) {
|
| throw new FormatException(
|
| - 'The "author" field should be a string, but was '
|
| - '${parsedPubspec["author"]}.');
|
| + 'The pubspec "environment" field should be a map, but was '
|
| + '"$environmentYaml".');
|
| }
|
|
|
| - if (parsedPubspec.containsKey('authors')) {
|
| - var authors = parsedPubspec['authors'];
|
| - if (authors is List) {
|
| - // All of the elements must be strings.
|
| - if (!authors.every((author) => author is String)) {
|
| - throw new FormatException('The "authors" field should be a string '
|
| - 'or a list of strings, but was "$authors".');
|
| - }
|
| - } else if (authors is! String) {
|
| - throw new FormatException('The pubspec "authors" field should be a '
|
| - 'string or a list of strings, but was "$authors".');
|
| - }
|
| -
|
| - if (parsedPubspec.containsKey('author')) {
|
| - throw new FormatException('A pubspec should not have both an "author" '
|
| - 'and an "authors" field.');
|
| - }
|
| + var sdkYaml = environmentYaml['sdk'];
|
| + if (sdkYaml is! String) {
|
| + throw new FormatException(
|
| + 'The "sdk" field of "environment" should be a string, but was '
|
| + '"$sdkYaml".');
|
| }
|
|
|
| - return new Pubspec(name, version, dependencies, devDependencies,
|
| - environment, parsedPubspec);
|
| + sdkConstraint = new VersionConstraint.parse(sdkYaml);
|
| + }
|
| + var environment = new PubspecEnvironment(sdkConstraint);
|
| +
|
| + // Even though the pub app itself doesn't use these fields, we validate
|
| + // them here so that users find errors early before they try to upload to
|
| + // the server:
|
| + // TODO(rnystrom): We should split this validation into separate layers:
|
| + // 1. Stuff that is required in any pubspec to perform any command. Things
|
| + // like "must have a name". That should go here.
|
| + // 2. Stuff that is required to upload a package. Things like "homepage
|
| + // must use a valid scheme". That should go elsewhere. pub upload should
|
| + // call it, and we should provide a separate command to show the user,
|
| + // and also expose it to the editor in some way.
|
| +
|
| + if (map.containsKey('homepage')) {
|
| + _validateFieldUrl(map['homepage'], 'homepage');
|
| + }
|
| + if (map.containsKey('documentation')) {
|
| + _validateFieldUrl(map['documentation'], 'documentation');
|
| }
|
| -}
|
|
|
| -/// Evaluates whether the given [url] for [field] is valid.
|
| -///
|
| -/// Throws [FormatException] on an invalid url.
|
| -void _validateFieldUrl(url, String field) {
|
| - if (url is! String) {
|
| + if (map.containsKey('author') && map['author'] is! String) {
|
| throw new FormatException(
|
| - 'The "$field" field should be a string, but was "$url".');
|
| + 'The "author" field should be a string, but was '
|
| + '${map["author"]}.');
|
| }
|
|
|
| - var goodScheme = new RegExp(r'^https?:');
|
| - if (!goodScheme.hasMatch(url)) {
|
| - throw new FormatException(
|
| - 'The "$field" field should be an "http:" or "https:" URL, but '
|
| - 'was "$url".');
|
| + if (map.containsKey('authors')) {
|
| + var authors = map['authors'];
|
| + if (authors is List) {
|
| + // All of the elements must be strings.
|
| + if (!authors.every((author) => author is String)) {
|
| + throw new FormatException('The "authors" field should be a string '
|
| + 'or a list of strings, but was "$authors".');
|
| + }
|
| + } else if (authors is! String) {
|
| + throw new FormatException('The pubspec "authors" field should be a '
|
| + 'string or a list of strings, but was "$authors".');
|
| + }
|
| +
|
| + if (map.containsKey('author')) {
|
| + throw new FormatException('A pubspec should not have both an "author" '
|
| + 'and an "authors" field.');
|
| + }
|
| }
|
| +
|
| + return new Pubspec(name, version, dependencies, devDependencies,
|
| + environment, map);
|
| }
|
|
|
| List<PackageDep> _parseDependencies(String pubspecPath, SourceRegistry sources,
|
|
|