add a --no-generate-docs flag that is faster and only displays warnings (#1909)

* basic non-documenting mode works

* Basically works now, needs tests

* Add a test

* dartfmt

* Cleanup

* Add test for doc generation presence/absence with --quiet

* Fix packages output parsing problem
diff --git a/bin/dartdoc.dart b/bin/dartdoc.dart
index 830624b..930eba0 100644
--- a/bin/dartdoc.dart
+++ b/bin/dartdoc.dart
@@ -20,6 +20,7 @@
       : super(optionSet, dir);
 
   bool get asyncStackTraces => optionSet['asyncStackTraces'].valueAt(context);
+  bool get generateDocs => optionSet['generateDocs'].valueAt(context);
   bool get help => optionSet['help'].valueAt(context);
   bool get version => optionSet['version'].valueAt(context);
 }
@@ -29,6 +30,10 @@
     new DartdocOptionArgOnly<bool>('asyncStackTraces', false,
         help: 'Display coordinated asynchronous stack traces (slow)',
         negatable: true),
+    new DartdocOptionArgOnly<bool>('generateDocs', true,
+        help:
+            'Generate docs into the output directory (or only display warnings if false).',
+        negatable: true),
     new DartdocOptionArgOnly<bool>('help', false,
         abbr: 'h', help: 'Show command help.', negatable: false),
     new DartdocOptionArgOnly<bool>('version', false,
@@ -82,29 +87,23 @@
   }
   startLogging(config);
 
-  Directory outputDir = new Directory(config.output);
-  logInfo("Generating documentation for '${config.topLevelPackageMeta}' into "
-      "${outputDir.absolute.path}${Platform.pathSeparator}");
-
-  Dartdoc dartdoc = await Dartdoc.withDefaultGenerators(config);
-
+  Dartdoc dartdoc = config.generateDocs
+      ? await Dartdoc.withDefaultGenerators(config)
+      : await Dartdoc.withEmptyGenerator(config);
   dartdoc.onCheckProgress.listen(logProgress);
   try {
     await Chain.capture(() async {
-      await runZoned(() async {
-        DartdocResults results = await dartdoc.generateDocs();
-        logInfo('Success! Docs generated into ${results.outDir.absolute.path}');
-      },
+      await runZoned(dartdoc.generateDocs,
           zoneSpecification: new ZoneSpecification(
               print: (Zone self, ZoneDelegate parent, Zone zone, String line) =>
                   logPrint(line)));
     }, onError: (e, Chain chain) {
       if (e is DartdocFailure) {
-        stderr.writeln('\nGeneration failed: ${e}.');
+        stderr.writeln('\ndartdoc failed: ${e}.');
         exitCode = 1;
         return;
       } else {
-        stderr.writeln('\nGeneration failed: ${e}\n${chain.terse}');
+        stderr.writeln('\ndartdoc failed: ${e}\n${chain.terse}');
         exitCode = 255;
         return;
       }
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index f2abfdf..b428213 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -68,6 +68,12 @@
     return new Dartdoc._(config, generators);
   }
 
+  /// An asynchronous factory method that builds
+  static Future<Dartdoc> withEmptyGenerator(DartdocOptionContext config) async {
+    List<Generator> generators = await initEmptyGenerators(config);
+    return new Dartdoc._(config, generators);
+  }
+
   /// Basic synchronous factory that gives a stripped down Dartdoc that won't
   /// use generators.  Useful for testing.
   factory Dartdoc.withoutGenerators(DartdocOptionContext config) {
@@ -101,7 +107,8 @@
         await generator.generate(packageGraph, outputDir.path);
         writtenFiles.addAll(generator.writtenFiles.map(pathLib.normalize));
       }
-      if (config.validateLinks) validateLinks(packageGraph, outputDir.path);
+      if (config.validateLinks && writtenFiles.isNotEmpty)
+        validateLinks(packageGraph, outputDir.path);
     }
 
     int warnings = packageGraph.packageWarningCounter.warningCount;
@@ -122,6 +129,8 @@
   }
 
   Future<DartdocResults> generateDocs() async {
+    logPrint("Documenting ${config.topLevelPackageMeta}...");
+
     DartdocResults dartdocResults = await generateDocsBase();
     if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) {
       throw new DartdocFailure(
@@ -134,7 +143,8 @@
       throw new DartdocFailure(
           "dartdoc encountered $errorCount} errors while processing.");
     }
-
+    logInfo(
+        'Success! Docs generated into ${dartdocResults.outDir.absolute.path}');
     return dartdocResults;
   }
 
diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart
new file mode 100644
index 0000000..476dd67
--- /dev/null
+++ b/lib/src/empty_generator.dart
@@ -0,0 +1,41 @@
+library dartdoc.empty_generator;
+
+import 'dart:async';
+
+import 'package:dartdoc/src/generator.dart';
+import 'package:dartdoc/src/model.dart';
+import 'package:dartdoc/src/model_utils.dart';
+
+/// A generator that does not generate files, but does traverse the [PackageGraph]
+/// and access [ModelElement.documetationAsHtml] for every element as though
+/// it were.
+class EmptyGenerator extends Generator {
+  @override
+  Future generate(PackageGraph _packageGraph, String outputDirectoryPath) {
+    _onFileCreated.add(_packageGraph.defaultPackage.documentationAsHtml);
+    for (var package in Set.from([_packageGraph.defaultPackage])
+      ..addAll(_packageGraph.localPackages)) {
+      for (var category in filterNonDocumented(package.categories)) {
+        _onFileCreated.add(category.documentationAsHtml);
+      }
+
+      for (Library lib in filterNonDocumented(package.libraries)) {
+        filterNonDocumented(lib.allModelElements)
+            .forEach((m) => _onFileCreated.add(m.documentationAsHtml));
+      }
+    }
+    return null;
+  }
+
+  final StreamController<void> _onFileCreated =
+      new StreamController(sync: true);
+
+  @override
+
+  /// Implementation fires on each model element processed rather than
+  /// file creation.
+  Stream<void> get onFileCreated => _onFileCreated.stream;
+
+  @override
+  Set<String> get writtenFiles => new Set();
+}
diff --git a/lib/src/generator.dart b/lib/src/generator.dart
index 20dd037..bd35988 100644
--- a/lib/src/generator.dart
+++ b/lib/src/generator.dart
@@ -6,7 +6,6 @@
 library dartdoc.generator;
 
 import 'dart:async' show Stream, Future;
-import 'dart:io' show File;
 
 import 'package:dartdoc/src/model.dart' show PackageGraph;
 
@@ -20,7 +19,7 @@
   Future generate(PackageGraph packageGraph, String outputDirectoryPath);
 
   /// Fires when a file is created.
-  Stream<File> get onFileCreated;
+  Stream<void> get onFileCreated;
 
   /// Fetches all filenames written by this generator.
   Set<String> get writtenFiles;
diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart
index f706060..aac3023 100644
--- a/lib/src/html/html_generator.dart
+++ b/lib/src/html/html_generator.dart
@@ -9,6 +9,7 @@
 import 'dart:isolate';
 
 import 'package:dartdoc/dartdoc.dart';
+import 'package:dartdoc/src/empty_generator.dart';
 import 'package:dartdoc/src/generator.dart';
 import 'package:dartdoc/src/html/html_generator_instance.dart';
 import 'package:dartdoc/src/html/template_data.dart';
@@ -41,11 +42,11 @@
   final HtmlGeneratorOptions _options;
   HtmlGeneratorInstance _instance;
 
-  final StreamController<File> _onFileCreated =
+  final StreamController<void> _onFileCreated =
       new StreamController(sync: true);
 
   @override
-  Stream<File> get onFileCreated => _onFileCreated.stream;
+  Stream<void> get onFileCreated => _onFileCreated.stream;
 
   @override
   final Set<String> writtenFiles = new Set<String>();
@@ -131,6 +132,10 @@
       : this.toolVersion = toolVersion ?? 'unknown';
 }
 
+Future<List<Generator>> initEmptyGenerators(DartdocOptionContext config) async {
+  return [EmptyGenerator()];
+}
+
 /// Initialize and setup the generators.
 Future<List<Generator>> initGenerators(GeneratorContext config) async {
   // TODO(jcollins-g): Rationalize based on GeneratorContext all the way down
diff --git a/lib/src/logging.dart b/lib/src/logging.dart
index c52b05f..ae67052 100644
--- a/lib/src/logging.dart
+++ b/lib/src/logging.dart
@@ -96,13 +96,15 @@
       assert(message.isNotEmpty);
 
       if (record.level < Level.WARNING) {
-        if (config.showProgress && message.endsWith('...')) {
-          // Assume there may be more progress to print, so omit the trailing
-          // newline
-          writingProgress = true;
-          stdout.write(message);
-        } else {
-          print(message);
+        if (!config.quiet) {
+          if (config.showProgress && message.endsWith('...')) {
+            // Assume there may be more progress to print, so omit the trailing
+            // newline
+            writingProgress = true;
+            stdout.write(message);
+          } else {
+            print(message);
+          }
         }
       } else {
         stderr.writeln(message);
@@ -114,6 +116,7 @@
 abstract class LoggingContext implements DartdocOptionContextBase {
   bool get json => optionSet['json'].valueAt(context);
   bool get showProgress => optionSet['showProgress'].valueAt(context);
+  bool get quiet => optionSet['quiet'].valueAt(context);
 }
 
 Future<List<DartdocOption>> createLoggingOptions() async {
@@ -124,5 +127,15 @@
     new DartdocOptionArgOnly<bool>('showProgress', false,
         help: 'Display progress indications to console stdout',
         negatable: false),
+    new DartdocOptionArgSynth<bool>('quiet',
+        (DartdocSyntheticOption option, Directory dir) {
+      if (option.root['generateDocs']?.valueAt(dir) == false) {
+        return true;
+      }
+      return false;
+    },
+        abbr: 'q',
+        negatable: true,
+        help: 'Only show warnings and errors; silence all other output.'),
   ];
 }
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 44c7199..9bfe5fe 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -3251,7 +3251,8 @@
 
   /// Separate from _buildDocumentationLocal for overriding.
   String _buildDocumentationBaseSync() {
-    assert(_rawDocs == null, 'reentrant calls to _buildDocumentation* not allowed');
+    assert(_rawDocs == null,
+        'reentrant calls to _buildDocumentation* not allowed');
     // Do not use the sync method if we need to evaluate tools or templates.
     assert(!isCanonical ||
         !needsPrecacheRegExp.hasMatch(documentationComment ?? ''));
@@ -3271,7 +3272,8 @@
   /// Separate from _buildDocumentationLocal for overriding.  Can only be
   /// used as part of [PackageGraph.setUpPackageGraph].
   Future<String> _buildDocumentationBase() async {
-    assert(_rawDocs == null, 'reentrant calls to _buildDocumentation* not allowed');
+    assert(_rawDocs == null,
+        'reentrant calls to _buildDocumentation* not allowed');
     // Do not use the sync method if we need to evaluate tools or templates.
     if (config.dropTextFrom.contains(element.library.name)) {
       _rawDocs = '';
diff --git a/test/dartdoc_test.dart b/test/dartdoc_test.dart
index f9282ec..70b7cd1 100644
--- a/test/dartdoc_test.dart
+++ b/test/dartdoc_test.dart
@@ -131,6 +131,36 @@
         await Future.wait(CoverageSubprocessLauncher.coverageResults);
       });
 
+      test('running --no-generate-docs is quiet and does not generate docs',
+          () async {
+        Directory outputDir =
+            await Directory.systemTemp.createTemp('dartdoc.testEmpty.');
+        List<String> outputLines = [];
+        await subprocessLauncher.runStreamed(Platform.resolvedExecutable,
+            [dartdocPath, '--output', outputDir.path, '--no-generate-docs'],
+            perLine: outputLines.add, workingDirectory: _testPackagePath);
+        expect(outputLines, isNot(contains(matches('^parsing'))));
+        expect(outputLines, contains(matches('^  warning:')));
+        expect(
+            outputLines.last, matches(r'^found \d+ warnings and \d+ errors'));
+        expect(outputDir.listSync(), isEmpty);
+      });
+
+      test('running --quiet is quiet and does generate docs',
+          () async {
+        Directory outputDir =
+        await Directory.systemTemp.createTemp('dartdoc.testEmpty.');
+        List<String> outputLines = [];
+        await subprocessLauncher.runStreamed(Platform.resolvedExecutable,
+          [dartdocPath, '--output', outputDir.path, '--quiet'],
+          perLine: outputLines.add, workingDirectory: _testPackagePath);
+        expect(outputLines, isNot(contains(matches('^parsing'))));
+        expect(outputLines, contains(matches('^  warning:')));
+        expect(
+          outputLines.last, matches(r'^found \d+ warnings and \d+ errors'));
+        expect(outputDir.listSync(), isNotEmpty);
+      });
+
       test('invalid parameters return non-zero and print a fatal-error',
           () async {
         List outputLines = [];
diff --git a/test/model_test.dart b/test/model_test.dart
index c7452b9..ff7ea0f 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -676,7 +676,8 @@
     });
 
     test('can be reexported even if the file suffix is not .dart', () {
-      expect(fakeLibrary.allClasses.map((c) => c.name), contains('MyClassFromADartFile'));
+      expect(fakeLibrary.allClasses.map((c) => c.name),
+          contains('MyClassFromADartFile'));
     });
 
     test('that is deprecated has a deprecated css class in linkedName', () {
diff --git a/test/src/utils.dart b/test/src/utils.dart
index 637bccc..acfcad0 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -160,12 +160,14 @@
       'coverage:format_coverage',
       '--lcov',
       '-v',
-      '-b', '.',
+      '-b',
+      '.',
       '--packages=.packages',
       '--sdk-root=${pathLib.canonicalize(pathLib.join(pathLib.dirname(Platform.executable), '..'))}',
       '--out=${pathLib.canonicalize(outputFile.path)}',
       '--report-on=bin,lib',
-      '-i', tempDir.path,
+      '-i',
+      tempDir.path,
     ]);
   }
 
diff --git a/tool/travis.sh b/tool/travis.sh
index f3d2a03..3b96e36 100755
--- a/tool/travis.sh
+++ b/tool/travis.sh
@@ -34,7 +34,7 @@
   fi
   PACKAGE_NAME=access PACKAGE_VERSION=">=1.0.1+2" pub run grinder build-pub-package
   # Negative test for flutter_plugin_tools, make sure right error message is displayed.
-  PACKAGE_NAME=flutter_plugin_tools PACKAGE_VERSION=">=0.0.14+1" pub run grinder build-pub-package 2>&1 | grep "Generation failed: dartdoc could not find any libraries to document.$"
+  PACKAGE_NAME=flutter_plugin_tools PACKAGE_VERSION=">=0.0.14+1" pub run grinder build-pub-package 2>&1 | grep "dartdoc failed: dartdoc could not find any libraries to document.$"
   PACKAGE_NAME=shelf_exception_handler PACKAGE_VERSION=">=0.2.0" pub run grinder build-pub-package
 elif [ "$DARTDOC_BOT" = "sdk-analyzer" ]; then
   echo "Running main dartdoc bot against the SDK analyzer"