Add include/exclude to supported config options (#1700)

* Add include/exclude to supported config options

* Cleanup and more consistently use buildDartdoc in test

* dartfmt and sort options in readme
diff --git a/README.md b/README.md
index e6aa914..a62be49 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,9 @@
 
   * **categoryOrder**:  Specify the order of categories, below, for display in the sidebar and
     the package page.
+  * **exclude**:  Specify a list of library names to avoid generating docs for,
+    overriding any specified in include.
+  * **include**:  Specify a list of library names to generate docs for, ignoring all others.
   * **linkTo**:  For other packages depending on this one, if this map is defined those packages
     will use the settings here to control how hyperlinks to the package are generated.
     This will override the default for packages hosted on pub.dartlang.org.
@@ -133,12 +136,10 @@
   * **ambiguousReexportScorerMinConfidence**:  The ambiguous reexport scorer will emit a warning if
   it is not at least this confident.  Default: 0.1
   * **examplePathPrefix**:  Specify the prefix for the example paths, defaulting to the project root.
-  * **exclude**:  Specify a list of library names to avoid generating docs for, ignoring all others.
   * **favicon**:  A path to a favicon for the generated docs.
   * **footer**: A list of paths to footer files containing HTML text.
   * **footerText**: A list of paths to text files for optional text next to the package name and version
   * **header**:  A list of paths to header files containing HTML text.
-  * **include**:  Specify a list of library names to generate docs for, ignoring all others.
   * **includeExternal**:  Specify a list of library filenames to add to the list of documented libraries.
  
 ### Categories
diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart
index 2b76997..fe111d9 100644
--- a/bin/dartdoc.dart
+++ b/bin/dartdoc.dart
@@ -42,9 +42,6 @@
     createGeneratorOptions,
   ]);
 
-  DartdocProgramOptionContext config =
-      new DartdocProgramOptionContext(optionSet, Directory.current);
-
   try {
     optionSet.parseArguments(arguments);
   } on FormatException catch (e) {
@@ -63,6 +60,8 @@
     _printVersionAndExit(optionSet.argParser);
   }
 
+  DartdocProgramOptionContext config =
+      new DartdocProgramOptionContext(optionSet, null);
   startLogging(config);
 
   Directory outputDir = new Directory(config.output);
diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart
index 8ee26a6..b4caa0f 100644
--- a/lib/src/dartdoc_options.dart
+++ b/lib/src/dartdoc_options.dart
@@ -883,9 +883,17 @@
   // TODO(jcollins-g): Allow passing in structured data to initialize a
   // [DartdocOptionContext]'s arguments instead of having to parse strings
   // via optionSet.
+  /// If [entity] is null, assume this is the initialization case and use
+  /// the inputDir flag to determine the context.
   DartdocOptionContext(this.optionSet, FileSystemEntity entity) {
-    context = new Directory(pathLib
-        .canonicalize(entity is File ? entity.parent.path : entity.path));
+    if (entity == null) {
+      String inputDir = optionSet['inputDir'].valueAt(Directory.current) ??
+          Directory.current.path;
+      context = new Directory(inputDir);
+    } else {
+      context = new Directory(pathLib
+          .canonicalize(entity is File ? entity.parent.path : entity.path));
+    }
   }
 
   /// Build a DartdocOptionContext from an analyzer element (using its source
diff --git a/lib/src/model.dart b/lib/src/model.dart
index c41682a..effeeff 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -3597,7 +3597,8 @@
         width = int.parse(match[2]);
       } on FormatException {
         warn(PackageWarning.invalidParameter,
-            message: 'An animation has an invalid width ($name): ${match[2]}. The '
+            message:
+                'An animation has an invalid width ($name): ${match[2]}. The '
                 'width must be an integer.');
         return '';
       }
@@ -3606,7 +3607,8 @@
         height = int.parse(match[3]);
       } on FormatException {
         warn(PackageWarning.invalidParameter,
-            message: 'An animation has an invalid height ($name): ${match[3]}. The '
+            message:
+                'An animation has an invalid height ($name): ${match[3]}. The '
                 'height must be an integer.');
         return '';
       }
@@ -3615,7 +3617,8 @@
         movieUrl = Uri.parse(match[4]);
       } on FormatException catch (e) {
         warn(PackageWarning.invalidParameter,
-            message: 'An animation URL could not be parsed ($name): ${match[4]}\n$e');
+            message:
+                'An animation URL could not be parsed ($name): ${match[4]}\n$e');
         return '';
       }
       final String overlayName = '${name}_play_button_';
@@ -3655,7 +3658,7 @@
   </video>
 </div>
 
-''';  // String must end at beginning of line, or following inline text will be
+'''; // String must end at beginning of line, or following inline text will be
       // indented.
     });
   }
@@ -5742,7 +5745,8 @@
     if (config.include.isNotEmpty) {
       Iterable knownLibraryNames = libraries.map((l) => l.name);
       Set notFound = new Set.from(config.include)
-          .difference(new Set.from(knownLibraryNames));
+          .difference(new Set.from(knownLibraryNames))
+          .difference(new Set.from(config.exclude));
       if (notFound.isNotEmpty) {
         throw 'Did not find: [${notFound.join(', ')}] in '
             'known libraries: [${knownLibraryNames.join(', ')}]';
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index df6c17c..c9a7aa9 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -9,7 +9,6 @@
 
 import 'package:dartdoc/dartdoc.dart';
 import 'package:dartdoc/src/model.dart';
-import 'package:dartdoc/src/package_meta.dart';
 import 'package:path/path.dart' as pathLib;
 import 'package:test/test.dart';
 
@@ -28,24 +27,51 @@
       delete(tempDir);
     });
 
-    Future<DartdocGeneratorOptionContext> generatorContextFromArgvTemp(
-        List<String> argv) async {
-      return await generatorContextFromArgv(argv..addAll(outputParam));
+    Future<Dartdoc> buildDartdoc(
+        List<String> argv, Directory packageRoot) async {
+      return await Dartdoc.withDefaultGenerators(await generatorContextFromArgv(
+          argv..addAll(['--input', packageRoot.path])..addAll(outputParam)));
     }
 
+    group('include/exclude parameters', () {
+      test('with config file', () async {
+        Dartdoc dartdoc = await buildDartdoc([], testPackageImportExport);
+        DartdocResults results = await dartdoc.generateDocs();
+        PackageGraph p = results.packageGraph;
+        expect(p.localPublicLibraries.map((l) => l.name),
+            orderedEquals(['explicitly_included', 'more_included']));
+      });
+
+      test('with include command line argument', () async {
+        Dartdoc dartdoc = await buildDartdoc(
+            ['--include', 'another_included'], testPackageImportExport);
+        DartdocResults results = await dartdoc.generateDocs();
+        PackageGraph p = results.packageGraph;
+        expect(p.localPublicLibraries.length, equals(1));
+        expect(p.localPublicLibraries.first.name, equals('another_included'));
+      });
+
+      test('with exclude command line argument', () async {
+        Dartdoc dartdoc = await buildDartdoc(
+            ['--exclude', 'more_included'], testPackageImportExport);
+        DartdocResults results = await dartdoc.generateDocs();
+        PackageGraph p = results.packageGraph;
+        expect(p.localPublicLibraries.length, equals(1));
+        expect(
+            p.localPublicLibraries.first.name, equals('explicitly_included'));
+      });
+    });
+
     test('package without version produces valid semver in docs', () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageMinimumDir.path]));
+      Dartdoc dartdoc = await buildDartdoc([], testPackageMinimumDir);
       DartdocResults results = await dartdoc.generateDocs();
       PackageGraph p = results.packageGraph;
-      assert(p.defaultPackage.version == '0.0.0-unknown');
+      expect(p.defaultPackage.version, equals('0.0.0-unknown'));
     });
 
     test('basic interlinking test', () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageDir.path, '--link-to-remote']));
+      Dartdoc dartdoc =
+          await buildDartdoc(['--link-to-remote'], testPackageDir);
       DartdocResults results = await dartdoc.generateDocs();
       PackageGraph p = results.packageGraph;
       Package tuple = p.publicPackages.firstWhere((p) => p.name == 'tuple');
@@ -73,8 +99,7 @@
 
     test('generate docs for ${pathLib.basename(testPackageDir.path)} works',
         () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(['--input', testPackageDir.path]));
+      Dartdoc dartdoc = await buildDartdoc([], testPackageDir);
 
       DartdocResults results = await dartdoc.generateDocs();
       expect(results.packageGraph, isNotNull);
@@ -89,9 +114,7 @@
 
     test('generate docs for ${pathLib.basename(testPackageBadDir.path)} fails',
         () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageBadDir.path]));
+      Dartdoc dartdoc = await buildDartdoc([], testPackageBadDir);
 
       try {
         await dartdoc.generateDocs();
@@ -102,9 +125,7 @@
     });
 
     test('generate docs for a package that does not have a readme', () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageWithNoReadme.path]));
+      Dartdoc dartdoc = await buildDartdoc([], testPackageWithNoReadme);
 
       DartdocResults results = await dartdoc.generateDocs();
       expect(results.packageGraph, isNotNull);
@@ -117,9 +138,8 @@
     });
 
     test('generate docs including a single library', () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageDir.path, '--include', 'fake']));
+      Dartdoc dartdoc =
+          await buildDartdoc(['--include', 'fake'], testPackageDir);
 
       DartdocResults results = await dartdoc.generateDocs();
       expect(results.packageGraph, isNotNull);
@@ -132,9 +152,8 @@
     });
 
     test('generate docs excluding a single library', () async {
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageDir.path, '--exclude', 'fake']));
+      Dartdoc dartdoc =
+          await buildDartdoc(['--exclude', 'fake'], testPackageDir);
 
       DartdocResults results = await dartdoc.generateDocs();
       expect(results.packageGraph, isNotNull);
@@ -148,11 +167,7 @@
     });
 
     test('generate docs for package with embedder yaml', () async {
-      PackageMeta meta = new PackageMeta.fromDir(testPackageWithEmbedderYaml);
-      if (meta.needsPubGet) meta.runPubGet();
-      Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(
-          await generatorContextFromArgvTemp(
-              ['--input', testPackageWithEmbedderYaml.path]));
+      Dartdoc dartdoc = await buildDartdoc([], testPackageWithEmbedderYaml);
 
       DartdocResults results = await dartdoc.generateDocs();
       expect(results.packageGraph, isNotNull);
diff --git a/test/model_test.dart b/test/model_test.dart
index f0a22ed..103ade3 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -576,14 +576,11 @@
           isTrue);
     });
     test("Doesn't place animations in one line doc", () {
-      expect(
-          withAnimationInline.oneLineDoc, isNot(contains('<video')));
-      expect(
-          withAnimationInline.documentation, contains('<video'));
+      expect(withAnimationInline.oneLineDoc, isNot(contains('<video')));
+      expect(withAnimationInline.documentation, contains('<video'));
     });
     test("Handles animations inline properly", () {
-      expect(
-          withAnimationInline.documentation, isNot(contains('  works')));
+      expect(withAnimationInline.documentation, isNot(contains('  works')));
     });
   });
 
diff --git a/test/src/utils.dart b/test/src/utils.dart
index 9a650bd..a2dc71d 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -27,15 +27,18 @@
     new Directory('testing/test_package_embedder_yaml');
 final Directory testPackageWithNoReadme =
     new Directory('testing/test_package_small');
+final Directory testPackageImportExport =
+    new Directory('testing/test_package_include_exclude');
 
 /// Convenience factory to build a [DartdocGeneratorOptionContext] and associate
-/// it with a [DartdocOptionSet] based on the current working directory.
+/// it with a [DartdocOptionSet] based on the current working directory and/or
+/// the '--input' flag.
 Future<DartdocGeneratorOptionContext> generatorContextFromArgv(
     List<String> argv) async {
   DartdocOptionSet optionSet = await DartdocOptionSet.fromOptionGenerators(
       'dartdoc', [createDartdocOptions, createGeneratorOptions]);
   optionSet.parseArguments(argv);
-  return new DartdocGeneratorOptionContext(optionSet, Directory.current);
+  return new DartdocGeneratorOptionContext(optionSet, null);
 }
 
 /// Convenience factory to build a [DartdocOptionContext] and associate it with a
diff --git a/testing/.gitignore b/testing/.gitignore
new file mode 100644
index 0000000..0a80a65
--- /dev/null
+++ b/testing/.gitignore
@@ -0,0 +1,4 @@
+*/doc/api/
+*/ios
+*/pubspec.lock
+*/.packages
diff --git a/testing/test_package/.gitignore b/testing/test_package/.gitignore
deleted file mode 100644
index 70e751d..0000000
--- a/testing/test_package/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-doc/api/
-pubspec.lock
diff --git a/testing/test_package_bad/.gitignore b/testing/test_package_bad/.gitignore
deleted file mode 100644
index 07e0351..0000000
--- a/testing/test_package_bad/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-doc/api/
diff --git a/testing/test_package_flutter_plugin/.gitignore b/testing/test_package_flutter_plugin/.gitignore
deleted file mode 100644
index 33b0ba3..0000000
--- a/testing/test_package_flutter_plugin/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-ios/
-doc/api/
-pubspec.lock
diff --git a/testing/test_package_imported/.gitignore b/testing/test_package_imported/.gitignore
deleted file mode 100644
index 07e0351..0000000
--- a/testing/test_package_imported/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-doc/api/
diff --git a/testing/test_package_include_exclude/bin/included.dart b/testing/test_package_include_exclude/bin/included.dart
new file mode 100644
index 0000000..24a7fc1
--- /dev/null
+++ b/testing/test_package_include_exclude/bin/included.dart
@@ -0,0 +1,6 @@
+/// This is a libraryish thing in the wrong place.
+library included;
+
+void main() {
+  print('hello, world');
+}
diff --git a/testing/test_package_include_exclude/bin/not_included.dart b/testing/test_package_include_exclude/bin/not_included.dart
new file mode 100644
index 0000000..617e54d
--- /dev/null
+++ b/testing/test_package_include_exclude/bin/not_included.dart
@@ -0,0 +1,5 @@
+library not_included;
+
+void main() {
+  print('another binary not included');
+}
diff --git a/testing/test_package_include_exclude/dartdoc_options.yaml b/testing/test_package_include_exclude/dartdoc_options.yaml
new file mode 100644
index 0000000..27f5f96
--- /dev/null
+++ b/testing/test_package_include_exclude/dartdoc_options.yaml
@@ -0,0 +1,3 @@
+dartdoc:
+  include: ['explicitly_included', 'more_included']
+  exclude: ['excluded']
diff --git a/testing/test_package_include_exclude/lib/another_included.dart b/testing/test_package_include_exclude/lib/another_included.dart
new file mode 100644
index 0000000..5c7e263
--- /dev/null
+++ b/testing/test_package_include_exclude/lib/another_included.dart
@@ -0,0 +1 @@
+library another_included;
diff --git a/testing/test_package_include_exclude/lib/excluded.dart b/testing/test_package_include_exclude/lib/excluded.dart
new file mode 100644
index 0000000..636e9a4
--- /dev/null
+++ b/testing/test_package_include_exclude/lib/excluded.dart
@@ -0,0 +1 @@
+library excluded;
diff --git a/testing/test_package_include_exclude/lib/explicitly_included.dart b/testing/test_package_include_exclude/lib/explicitly_included.dart
new file mode 100644
index 0000000..2e2fb7b
--- /dev/null
+++ b/testing/test_package_include_exclude/lib/explicitly_included.dart
@@ -0,0 +1 @@
+library explicitly_included;
diff --git a/testing/test_package_include_exclude/lib/more_included.dart b/testing/test_package_include_exclude/lib/more_included.dart
new file mode 100644
index 0000000..e5e716b
--- /dev/null
+++ b/testing/test_package_include_exclude/lib/more_included.dart
@@ -0,0 +1 @@
+library more_included;
diff --git a/testing/test_package_include_exclude/lib/not_explicitly_included.dart b/testing/test_package_include_exclude/lib/not_explicitly_included.dart
new file mode 100644
index 0000000..191ffab
--- /dev/null
+++ b/testing/test_package_include_exclude/lib/not_explicitly_included.dart
@@ -0,0 +1 @@
+library not_explicitly_included;
diff --git a/testing/test_package_include_exclude/lib/src/included_under_src.dart b/testing/test_package_include_exclude/lib/src/included_under_src.dart
new file mode 100644
index 0000000..6517436
--- /dev/null
+++ b/testing/test_package_include_exclude/lib/src/included_under_src.dart
@@ -0,0 +1 @@
+library included_under_src;
diff --git a/testing/test_package_include_exclude/pubspec.yaml b/testing/test_package_include_exclude/pubspec.yaml
new file mode 100644
index 0000000..aef72e6
--- /dev/null
+++ b/testing/test_package_include_exclude/pubspec.yaml
@@ -0,0 +1,2 @@
+name: test_package_include_exclude
+version: 0.0.1
diff --git a/testing/test_package_minimum/.gitignore b/testing/test_package_minimum/.gitignore
deleted file mode 100644
index 70e751d..0000000
--- a/testing/test_package_minimum/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-doc/api/
-pubspec.lock