Chromium Code Reviews| Index: utils/pub/package.dart |
| diff --git a/utils/pub/package.dart b/utils/pub/package.dart |
| index 2942a92c67c1c4ec2af1a8a65d3b9b276d722f72..e1b19654c7b2791e69a6c0a1834dffa759ce5a98 100644 |
| --- a/utils/pub/package.dart |
| +++ b/utils/pub/package.dart |
| @@ -5,7 +5,23 @@ |
| /** |
| * A named, versioned, unit of code and resource reuse. |
| */ |
| -class Package { |
| +class Package implements Hashable { |
| + /** |
| + * Loads the package whose root directory is [packageDir]. |
| + */ |
| + static Future<Package> load(String packageDir) { |
| + final pubspecPath = join(packageDir, 'pubspec'); |
| + |
| + return _parsePubspec(pubspecPath).transform((dependencies) { |
| + return new Package._(packageDir, dependencies); |
| + }); |
| + } |
| + |
| + /** |
| + * The path to the directory containing the package. |
| + */ |
| + final String dir; |
| + |
| /** |
| * The name of the package. |
| */ |
| @@ -20,20 +36,93 @@ class Package { |
| final Collection<String> dependencies; |
| /** |
| - * The cache where this package is contained. |
| + * Constructs a package. This should not be called directly. Instead, acquire |
| + * packages from [load()]. |
| */ |
| - final PackageCache _cache; |
| + Package._(String dir, this.dependencies) |
| + : dir = dir, |
| + name = basename(dir); |
| /** |
| - * Constructs a package. This should not be called directly. Instead, acquire |
| - * packages from [PackageCache]. |
| + * Reads and returns all of the packages this package immediately depends on. |
| + */ |
| + Future<Collection<Package>> loadDependencies() { |
|
nweiz
2012/04/25 17:50:47
I don't think the distinction between loadDependen
Bob Nystrom
2012/04/25 22:20:29
"local" doesn't have the right connotation to me.
nweiz
2012/04/25 22:30:09
I like "loadImmediateDependencies". It is verbose,
|
| + return Futures.wait(dependencies.map((name) => cache.find(name))); |
| + } |
| + |
| + /** |
| + * Walks the entire dependency graph starting at this package and returns a |
| + * [Set] of all of the packages dependend on by this one, directly or |
| + * indirectly. This package is included in the result set. |
| + */ |
| + Future<Set<Package>> traverseDependencies() { |
| + final completer = new Completer<Set<Package>>(); |
| + final packages = new Set<Package>(); |
| + |
| + var pendingAsyncCalls = 0; |
| + |
| + walkPackage(Package package) { |
| + // Skip packages we've already traversed. |
| + if (packages.contains(package)) return; |
| + |
| + // Add the package. |
| + packages.add(package); |
| + |
| + // Recurse into its dependencies. |
| + pendingAsyncCalls++; |
| + package.loadDependencies().then((dependencies) { |
| + for (final dependency in dependencies) { |
|
nweiz
2012/04/25 17:50:47
I would write dependencies.forEach(walkPackage), b
Bob Nystrom
2012/04/25 22:20:29
Done.
|
| + walkPackage(dependency); |
| + } |
| + |
| + pendingAsyncCalls--; |
| + if (pendingAsyncCalls == 0) completer.complete(packages); |
| + }); |
| + } |
| + |
| + walkPackage(this); |
| + |
| + return completer.future; |
| + } |
| + |
| + /** |
| + * Generates a hashcode for the package. |
| + */ |
| + int hashCode() => name.hashCode(); |
|
nweiz
2012/04/25 17:50:47
If you define hashCode, you should also define ==.
Bob Nystrom
2012/04/25 22:20:29
Leaving a TODO for now. I definitely want somethin
|
| + |
| + /** |
| + * Returns a debug string for the package. |
| */ |
| - Package._(this._cache, this.name, this.dependencies); |
| + String toString() => '$name ($dir)'; |
| /** |
| - * Reads and returns all of the packages this package depends on. |
| + * Parses the pubspec at the given path and returns the list of package |
| + * dependencies it exposes. |
| */ |
| - Future<Collection<Package>> readDependencies() { |
| - return Futures.wait(dependencies.map((name) => _cache.find(name))); |
| + static Future<List<String>> _parsePubspec(String path) { |
| + final completer = new Completer<List<String>>(); |
| + |
| + // TODO(rnystrom): Handle the directory not existing. |
| + // TODO(rnystrom): Error-handling. |
| + final readFuture = readTextFile(path); |
| + readFuture.handleException((error) { |
| + // If there is no pubspec, we implicitly treat that as a package with no |
| + // dependencies. |
| + // TODO(rnystrom): Distinguish file not found from other real errors. |
| + completer.complete(<String>[]); |
| + return true; |
| + }); |
| + |
| + readFuture.then((pubspec) { |
| + // TODO(rnystrom): Use YAML parser when ready. For now, it's just a flat |
| + // list of newline-separated strings. |
| + final dependencyNames = pubspec.split('\n'). |
| + map((name) => name.trim()). |
| + filter((name) => (name != null) && (name != '')); |
| + |
| + completer.complete(dependencyNames); |
| + }); |
| + |
| + return completer.future; |
| } |
| } |