blob: b8ee759211036ee26143f952aa0dab7e395bfc68 [file] [log] [blame]
// 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 package;
import 'io.dart';
import 'pubspec.dart';
import 'source.dart';
import 'source_registry.dart';
import 'version.dart';
/**
* A named, versioned, unit of code and resource reuse.
*/
class Package {
/**
* Loads the package whose root directory is [packageDir]. [name] is the
* expected name of that package (e.g. the name given in the dependency), or
* null if the package being loaded is the entrypoint package.
*/
static Future<Package> load(String name, String packageDir,
SourceRegistry sources) {
var pubspecPath = join(packageDir, 'pubspec.yaml');
return fileExists(pubspecPath).chain((exists) {
if (!exists) throw new PubspecNotFoundException(name);
return readTextFile(pubspecPath);
}).transform((contents) {
try {
var pubspec = new Pubspec.parse(contents, sources);
if (pubspec.name == null) throw new PubspecHasNoNameException(name);
if (name != null && pubspec.name != name) {
throw new PubspecNameMismatchException(name, pubspec.name);
}
return new Package._(packageDir, pubspec);
} on FormatException catch (ex) {
throw 'Could not parse $pubspecPath:\n${ex.message}';
}
});
}
/**
* The path to the directory containing the package.
*/
final String dir;
/**
* The name of the package.
*/
String get name {
if (pubspec.name != null) return pubspec.name;
if (dir != null) return basename(dir);
return null;
}
/**
* The package's version.
*/
Version get version => pubspec.version;
/**
* The parsed pubspec associated with this package.
*/
final Pubspec pubspec;
/**
* The ids of the packages that this package depends on. This is what is
* specified in the pubspec when this package depends on another.
*/
Collection<PackageRef> get dependencies => pubspec.dependencies;
/**
* Constructs a package with the given pubspec. The package will have no
* directory associated with it.
*/
Package.inMemory(this.pubspec)
: dir = null;
/**
* Constructs a package. This should not be called directly. Instead, acquire
* packages from [load()].
*/
Package._(this.dir, this.pubspec);
/**
* Returns a debug string for the package.
*/
String toString() => '$name $version ($dir)';
}
/**
* An unambiguous resolved reference to a package. A package ID contains enough
* information to correctly install the package.
*
* Note that it's possible for multiple distinct package IDs to point to
* different directories that happen to contain identical packages. For example,
* the same package may be available from multiple sources. As far as Pub is
* concerned, those packages are different.
*/
class PackageId implements Comparable {
/// The name of the package being identified.
final String name;
/**
* The [Source] used to look up this package given its [description].
*/
final Source source;
/**
* The package's version.
*/
final Version version;
/**
* The metadata used by the package's [source] to identify and locate it. It
* contains whatever [Source]-specific data it needs to be able to install
* the package. For example, the description of a git sourced package might
* by the URL "git://github.com/dart/uilib.git".
*/
final description;
PackageId(this.name, this.source, this.version, this.description);
int get hashCode => name.hashCode ^
source.name.hashCode ^
version.hashCode;
bool operator ==(other) {
if (other is! PackageId) return false;
// TODO(rnystrom): We're assuming here the name/version/source tuple is
// enough to uniquely identify the package and that we don't need to delve
// into the description.
return other.name == name &&
other.source.name == source.name &&
other.version == version;
}
String toString() {
if (source.isDefault) return "$name $version";
return "$name $version from $source";
}
int compareTo(Comparable other) {
if (other is! PackageId) throw new ArgumentError(other);
var sourceComp = source.name.compareTo(other.source.name);
if (sourceComp != 0) return sourceComp;
var nameComp = name.compareTo(other.name);
if (nameComp != 0) return nameComp;
return version.compareTo(other.version);
}
/**
* Returns the pubspec for this package.
*/
Future<Pubspec> describe() => source.describe(this);
/**
* Returns a future that completes to the resovled [PackageId] for this id.
*/
Future<PackageId> get resolved => source.resolveId(this);
}
/**
* A reference to a package. Unlike a [PackageId], a PackageRef may not
* unambiguously refer to a single package. It may describe a range of allowed
* packages.
*/
class PackageRef {
/// The name of the package being identified.
final String name;
/**
* The [Source] used to look up the package.
*/
final Source source;
/**
* The allowed package versions.
*/
final VersionConstraint constraint;
/**
* The metadata used to identify the package being referenced. The
* interpretation of this will vary based on the [source].
*/
final description;
PackageRef(this.name, this.source, this.constraint, this.description);
String toString() => "$name $constraint from $source ($description)";
/**
* Returns a [PackageId] generated from this [PackageRef] with the given
* concrete version.
*/
PackageId atVersion(Version version) =>
new PackageId(name, source, version, description);
}
class PubspecNotFoundException implements Exception {
final String name;
PubspecNotFoundException(this.name);
String toString() => 'Package "$name" doesn\'t have a pubspec.yaml file.';
}
class PubspecHasNoNameException implements Exception {
final String name;
PubspecHasNoNameException(this.name);
String toString() => 'Package "$name"\'s pubspec.yaml file is missing the '
'required "name" field (e.g. "name: $name").';
}
class PubspecNameMismatchException implements Exception {
final String expectedName;
final String actualName;
PubspecNameMismatchException(this.expectedName, this.actualName);
String toString() => 'The name you specified for your dependency, '
'"$expectedName", doesn\'t match the name "$actualName" in its pubspec.';
}