move deferred_library_check functionality to a library

BUG=
R=sigmund@google.com

Review URL: https://codereview.chromium.org//1411153009 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f8e71fb..b2ee44d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,8 @@
 # Changelog
 
+## 0.2.3
+- Moved `deferred_library_check` functionality to a library
+
 ## 0.2.2
 - Added `deferred_libary_check` tool
 
diff --git a/bin/deferred_library_check.dart b/bin/deferred_library_check.dart
index 0c092c5..d9cd151 100644
--- a/bin/deferred_library_check.dart
+++ b/bin/deferred_library_check.dart
@@ -40,7 +40,7 @@
 import 'dart:io';
 
 import 'package:dart2js_info/info.dart';
-import 'package:quiver/collection.dart';
+import 'package:dart2js_info/deferred_library_check.dart';
 import 'package:yaml/yaml.dart';
 
 Future main(List<String> args) async {
@@ -51,81 +51,9 @@
   var info = await infoFromFile(args[0]);
   var manifest = await manifestFromFile(args[1]);
 
-  // For each part in the manifest, record the expected "packages" for that
-  // part.
-  var packages = <String, String>{};
-  for (var part in manifest.keys) {
-    for (var package in manifest[part]['packages']) {
-      if (packages.containsKey(package)) {
-        print('You cannot specify that package "$package" maps to both parts '
-            '"$part" and "${packages[package]}".');
-        exit(1);
-      }
-      packages[package] = part;
-    }
-  }
-
-  var guessedPartMapping = new BiMap<String, String>();
-  guessedPartMapping['main'] = 'main';
-
-  bool anyFailed = false;
-
-  checkInfo(BasicInfo info) {
-    var lib = getLibraryOf(info);
-    if (lib != null && isPackageUri(lib.uri)) {
-      var packageName = getPackageName(lib.uri);
-      var outputUnitName = info.outputUnit.name;
-      var expectedPart;
-      if (packages.containsKey(packageName)) {
-        expectedPart = packages[packageName];
-      } else {
-        expectedPart = 'main';
-      }
-      var expectedOutputUnit = guessedPartMapping[expectedPart];
-      if (expectedOutputUnit == null) {
-        guessedPartMapping[expectedPart] = outputUnitName;
-      } else {
-        if (expectedOutputUnit != outputUnitName) {
-          // TODO(het): add options for how to treat unspecified packages
-          if (!packages.containsKey(packageName)) {
-            print('"${info.name}" from package "$packageName" was not declared '
-                'to be in an explicit part but was not in the main part');
-          } else {
-            var actualPart = guessedPartMapping.inverse[outputUnitName];
-            print('"${info.name}" from package "$packageName" was specified to '
-                'be in part $expectedPart but is in part $actualPart');
-          }
-          anyFailed = true;
-        }
-      }
-    }
-  }
-
-  info.functions.forEach(checkInfo);
-  info.fields.forEach(checkInfo);
-  if (anyFailed) {
-    print('The dart2js output did not meet the specification.');
-  } else {
-    print('The dart2js output meets the specification');
-  }
-}
-
-LibraryInfo getLibraryOf(Info info) {
-  var current = info;
-  while (current is! LibraryInfo) {
-    if (current == null) {
-      return null;
-    }
-    current = current.parent;
-  }
-  return current;
-}
-
-bool isPackageUri(Uri uri) => uri.scheme == 'package';
-
-String getPackageName(Uri uri) {
-  assert(isPackageUri(uri));
-  return uri.pathSegments.first;
+  var failures = checkDeferredLibraryManifest(info, manifest);
+  failures.forEach(print);
+  if (failures.isNotEmpty) exitCode = 1;
 }
 
 Future<AllInfo> infoFromFile(String fileName) async {
diff --git a/lib/deferred_library_check.dart b/lib/deferred_library_check.dart
new file mode 100644
index 0000000..d472eae
--- /dev/null
+++ b/lib/deferred_library_check.dart
@@ -0,0 +1,133 @@
+// Copyright (c) 2015, 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.
+
+/// This tool checks that the output from dart2js meets a given specification,
+/// given in a YAML file. The format of the YAML file is:
+///
+///     main:
+///       packages:
+///         - some_package
+///         - other_package
+///
+///     foo:
+///       packages:
+///         - foo
+///         - bar
+///
+///     baz:
+///       packages:
+///         - baz
+///         - quux
+///
+/// The YAML file consists of a list of declarations, one for each deferred
+/// part expected in the output. At least one of these parts must be named
+/// "main"; this is the main part that contains the program entrypoint. Each
+/// top-level part contains a list of package names that are expected to be
+/// contained in that part. Any package that is not explicitly listed is
+/// expected to be in the main part. For instance, in the example YAML above
+/// the part named "baz" is expected to contain the packages "baz" and "quux".
+///
+/// The names for parts given in the specification YAML file (besides "main")
+/// are arbitrary and just used for reporting when the output does not meet the
+/// specification.
+library dart2js_info.deferred_library_check;
+
+import 'info.dart';
+import 'package:quiver/collection.dart';
+
+List<ManifestComplianceFailure> checkDeferredLibraryManifest(
+    AllInfo info, Map manifest) {
+  // For each part in the manifest, record the expected "packages" for that
+  // part.
+  var packages = <String, String>{};
+  for (var part in manifest.keys) {
+    for (var package in manifest[part]['packages']) {
+      if (packages.containsKey(package)) {
+        throw new ArgumentError.value(
+            manifest,
+            'manifest',
+            'You cannot specify that package "$package" maps to both parts '
+            '"$part" and "${packages[package]}".');
+      }
+      packages[package] = part;
+    }
+  }
+
+  var guessedPartMapping = new BiMap<String, String>();
+  guessedPartMapping['main'] = 'main';
+
+  var failures = <ManifestComplianceFailure>[];
+
+  checkInfo(BasicInfo info) {
+    var lib = _getLibraryOf(info);
+    if (lib != null && _isPackageUri(lib.uri)) {
+      var packageName = _getPackageName(lib.uri);
+      var outputUnitName = info.outputUnit.name;
+      var expectedPart;
+      if (packages.containsKey(packageName)) {
+        expectedPart = packages[packageName];
+      } else {
+        expectedPart = 'main';
+      }
+      var expectedOutputUnit = guessedPartMapping[expectedPart];
+      if (expectedOutputUnit == null) {
+        guessedPartMapping[expectedPart] = outputUnitName;
+      } else {
+        if (expectedOutputUnit != outputUnitName) {
+          // TODO(het): add options for how to treat unspecified packages
+          if (!packages.containsKey(packageName)) {
+            failures.add(new ManifestComplianceFailure(info.name, packageName));
+          } else {
+            var actualPart = guessedPartMapping.inverse[outputUnitName];
+            failures.add(new ManifestComplianceFailure(
+                info.name, packageName, expectedPart, actualPart));
+          }
+        }
+      }
+    }
+  }
+
+  info.functions.forEach(checkInfo);
+  info.fields.forEach(checkInfo);
+
+  return failures;
+}
+
+LibraryInfo _getLibraryOf(Info info) {
+  var current = info;
+  while (current is! LibraryInfo) {
+    if (current == null) {
+      return null;
+    }
+    current = current.parent;
+  }
+  return current;
+}
+
+bool _isPackageUri(Uri uri) => uri.scheme == 'package';
+
+String _getPackageName(Uri uri) {
+  assert(_isPackageUri(uri));
+  return uri.pathSegments.first;
+}
+
+class ManifestComplianceFailure {
+  final String infoName;
+  final String packageName;
+  final String expectedPart;
+  final String actualPart;
+
+  const ManifestComplianceFailure(this.infoName, this.packageName,
+      [this.expectedPart, this.actualPart]);
+
+  String toString() {
+    if (expectedPart == null && actualPart == null) {
+      return '"$infoName" from package "$packageName" was not declared '
+          'to be in an explicit part but was not in the main part';
+    } else {
+      return '"$infoName" from package "$packageName" was specified to '
+          'be in part $expectedPart but is in part $actualPart';
+    }
+  }
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index ec57a49..267947c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dart2js_info
-version: 0.2.2+1
+version: 0.2.3
 description: >
   Libraries and tools to process data produced when running dart2js with
   --dump-info.