c6e2418c89
Also standardized the way of validating a url. Also, one test was working, but was not actually testing what it was supposed to be testing. Review URL: https://codereview.chromium.org//12211097 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@18290 260f80e4-7a28-3924-810f-c04153c831b5
255 lines
8.2 KiB
Dart
255 lines
8.2 KiB
Dart
// Copyright (c) 2012, 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.
|
|
|
|
library pubspec;
|
|
|
|
import '../../pkg/yaml/lib/yaml.dart';
|
|
import '../../pkg/path/lib/path.dart' as path;
|
|
|
|
import 'io.dart';
|
|
import 'package.dart';
|
|
import 'source.dart';
|
|
import 'source_registry.dart';
|
|
import 'utils.dart';
|
|
import 'version.dart';
|
|
|
|
/// The parsed and validated contents of a pubspec file.
|
|
class Pubspec {
|
|
/// This package's name.
|
|
final String name;
|
|
|
|
/// This package's version.
|
|
final Version version;
|
|
|
|
/// The packages this package depends on.
|
|
final List<PackageRef> dependencies;
|
|
|
|
/// The environment-related metadata.
|
|
final PubspecEnvironment environment;
|
|
|
|
/// All pubspec fields. This includes the fields from which other properties
|
|
/// are derived.
|
|
final Map<String, Object> fields;
|
|
|
|
/// Loads the pubspec for a package [name] located in [packageDir].
|
|
factory Pubspec.load(String name, String packageDir, SourceRegistry sources) {
|
|
var pubspecPath = path.join(packageDir, 'pubspec.yaml');
|
|
if (!fileExists(pubspecPath)) throw new PubspecNotFoundException(name);
|
|
|
|
try {
|
|
var pubspec = new Pubspec.parse(readTextFile(pubspecPath), sources);
|
|
|
|
if (pubspec.name == null) {
|
|
throw new PubspecHasNoNameException(name);
|
|
}
|
|
|
|
if (name != null && pubspec.name != name) {
|
|
throw new PubspecNameMismatchException(name, pubspec.name);
|
|
}
|
|
|
|
return pubspec;
|
|
} on FormatException catch (ex) {
|
|
throw 'Could not parse $pubspecPath:\n${ex.message}';
|
|
}
|
|
}
|
|
|
|
Pubspec(this.name, this.version, this.dependencies, this.environment,
|
|
[Map<String, Object> fields])
|
|
: this.fields = fields == null ? {} : fields;
|
|
|
|
Pubspec.empty()
|
|
: name = null,
|
|
version = Version.none,
|
|
dependencies = <PackageRef>[],
|
|
environment = new PubspecEnvironment(),
|
|
fields = {};
|
|
|
|
/// Whether or not the pubspec has no contents.
|
|
bool get isEmpty =>
|
|
name == null && version == Version.none && dependencies.isEmpty;
|
|
|
|
// TODO(rnystrom): Make this a static method to match corelib.
|
|
/// Parses the pubspec whose text is [contents]. If the pubspec doesn't define
|
|
/// version for itself, it defaults to [Version.none].
|
|
factory Pubspec.parse(String contents, SourceRegistry sources) {
|
|
var name = null;
|
|
var version = Version.none;
|
|
|
|
if (contents.trim() == '') return new Pubspec.empty();
|
|
|
|
var parsedPubspec = loadYaml(contents);
|
|
if (parsedPubspec == null) return new Pubspec.empty();
|
|
|
|
if (parsedPubspec is! Map) {
|
|
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".');
|
|
}
|
|
}
|
|
|
|
if (parsedPubspec.containsKey('version')) {
|
|
version = new Version.parse(parsedPubspec['version']);
|
|
}
|
|
|
|
var dependencies = _parseDependencies(sources,
|
|
parsedPubspec['dependencies']);
|
|
|
|
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".');
|
|
}
|
|
|
|
var sdkYaml = environmentYaml['sdk'];
|
|
if (sdkYaml is! String) {
|
|
throw new FormatException(
|
|
'The "sdk" field of "environment" should be a string, but was '
|
|
'"$sdkYaml".');
|
|
}
|
|
|
|
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');
|
|
}
|
|
|
|
if (parsedPubspec.containsKey('author') &&
|
|
parsedPubspec['author'] is! String) {
|
|
throw new FormatException(
|
|
'The "author" field should be a string, but was '
|
|
'${parsedPubspec["author"]}.');
|
|
}
|
|
|
|
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.');
|
|
}
|
|
}
|
|
|
|
return new Pubspec(name, version, dependencies, environment, parsedPubspec);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 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".');
|
|
}
|
|
}
|
|
|
|
List<PackageRef> _parseDependencies(SourceRegistry sources, yaml) {
|
|
var dependencies = <PackageRef>[];
|
|
|
|
// Allow an empty dependencies key.
|
|
if (yaml == null) return dependencies;
|
|
|
|
if (yaml is! Map || yaml.keys.any((e) => e is! String)) {
|
|
throw new FormatException(
|
|
'The pubspec dependencies should be a map of package names, but '
|
|
'was ${yaml}.');
|
|
}
|
|
|
|
yaml.forEach((name, spec) {
|
|
var description, source;
|
|
var versionConstraint = new VersionRange();
|
|
if (spec == null) {
|
|
description = name;
|
|
source = sources.defaultSource;
|
|
} else if (spec is String) {
|
|
description = name;
|
|
source = sources.defaultSource;
|
|
versionConstraint = new VersionConstraint.parse(spec);
|
|
} else if (spec is Map) {
|
|
if (spec.containsKey('version')) {
|
|
versionConstraint = new VersionConstraint.parse(spec.remove('version'));
|
|
}
|
|
|
|
var sourceNames = spec.keys.toList();
|
|
if (sourceNames.length > 1) {
|
|
throw new FormatException(
|
|
'Dependency $name may only have one source: $sourceNames.');
|
|
}
|
|
|
|
var sourceName = only(sourceNames);
|
|
if (sourceName is! String) {
|
|
throw new FormatException(
|
|
'Source name $sourceName should be a string.');
|
|
}
|
|
|
|
source = sources[sourceName];
|
|
description = spec[sourceName];
|
|
} else {
|
|
throw new FormatException(
|
|
'Dependency specification $spec should be a string or a mapping.');
|
|
}
|
|
|
|
source.validateDescription(description, fromLockFile: false);
|
|
|
|
dependencies.add(new PackageRef(
|
|
name, source, versionConstraint, description));
|
|
});
|
|
|
|
return dependencies;
|
|
}
|
|
|
|
/// The environment-related metadata in the pubspec. Corresponds to the data
|
|
/// under the "environment:" key in the pubspec.
|
|
class PubspecEnvironment {
|
|
/// The version constraint specifying which SDK versions this package works
|
|
/// with.
|
|
final VersionConstraint sdkVersion;
|
|
|
|
PubspecEnvironment([VersionConstraint sdk])
|
|
: sdkVersion = sdk != null ? sdk : VersionConstraint.any;
|
|
}
|