Add --inject-html flag to enable {@inject-html}, and update README.md (#1793)

* Update the README.md to include information about {@inject-html}

* Add the --inject-html command line flag to enable HTML injection.

* Add negatable to --inject-html option
diff --git a/README.md b/README.md
index 4837c12..38e7c36 100644
--- a/README.md
+++ b/README.md
@@ -301,6 +301,35 @@
 /// # `This is the text that will be sent to the tool as input.`
 ```
 
+### Injecting HTML
+
+It happens rarely, but sometimes what you really need is to inject some raw HTML
+into the dartdoc output, without it being subject to Markdown processing
+beforehand. This can be useful when the output of an external tool is HTML, for
+instance. This is where the `{@inject-html}...{@end-inject-html}` tags come in.
+
+For security reasons, the `{@inject-html}` directive will be ignored unless the
+`--inject-html` flag is given on the dartdoc command line.
+
+Since this HTML fragment doesn't undergo Markdown processing, reference links
+and other normal processing won't happen on the contained fragment.
+
+So, this:
+```dart
+  ///     {@inject-html}
+  ///     <p>[The HTML to inject.]()</p>
+  ///     {@end-inject-html}
+```
+
+Will result in this be emitted in its place in the HTML output (notice that the
+markdown link isn't linked).
+```
+<p>[The HTML to inject.]()</p>
+```
+
+It's best to only inject HTML that is self-contained and doesn't depend upon
+other elements on the page, since those may change in future versions of Dartdoc.
+
 ### Auto including dependencies
 
 If `--auto-include-dependencies` flag is provided, dartdoc tries to automatically add
diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart
index 54cacdf..c4d6e9a 100644
--- a/lib/src/dartdoc_options.dart
+++ b/lib/src/dartdoc_options.dart
@@ -541,7 +541,7 @@
       bool hide = false,
       bool isDir = false,
       bool isFile = false,
-      bool negatable,
+      bool negatable = false,
       bool splitCommas})
       : super._(name, null, help, isDir, isFile, mustExist, null) {
     _hide = hide;
@@ -672,7 +672,7 @@
       bool hide = false,
       bool isDir = false,
       bool isFile = false,
-      bool negatable,
+      bool negatable = false,
       bool splitCommas})
       : super._(name, defaultsTo, help, isDir, isFile, mustExist, null) {
     _hide = hide;
@@ -707,7 +707,7 @@
       bool hide = false,
       bool isDir = false,
       bool isFile = false,
-      bool negatable,
+      bool negatable = false,
       bool parentDirOverridesChild: false,
       bool splitCommas})
       : super._(name, defaultsTo, help, isDir, isFile, mustExist, null) {
@@ -1045,7 +1045,7 @@
           defaultsTo: defaultsTo as bool,
           help: help,
           hide: hide,
-          negatable: negatable);
+          negatable: negatable ?? false);
     } else if (_isInt || _isDouble || _isString) {
       argParser.addOption(argName,
           abbr: abbr,
@@ -1140,6 +1140,7 @@
   List<String> get includeExternal =>
       optionSet['includeExternal'].valueAt(context);
   bool get includeSource => optionSet['includeSource'].valueAt(context);
+  bool get injectHtml => optionSet['injectHtml'].valueAt(context);
   ToolConfiguration get tools => optionSet['tools'].valueAt(context);
 
   /// _input is only used to construct synthetic options.
@@ -1240,8 +1241,8 @@
         mustExist: true),
     new DartdocOptionArgOnly<bool>('hideSdkText', false,
         hide: true,
-        help:
-            'Drop all text for SDK components.  Helpful for integration tests for dartdoc, probably not useful for anything else.',
+        help: 'Drop all text for SDK components.  Helpful for integration '
+            'tests for dartdoc, probably not useful for anything else.',
         negatable: true),
     new DartdocOptionArgFile<List<String>>('include', [],
         help: 'Library names to generate docs for.', splitCommas: true),
@@ -1254,6 +1255,9 @@
         splitCommas: true),
     new DartdocOptionArgOnly<bool>('includeSource', true,
         help: 'Show source code blocks.', negatable: true),
+    new DartdocOptionArgOnly<bool>('injectHtml', false,
+        help: 'Allow the use of the {@inject-html} directive to inject raw '
+            'HTML into dartdoc output.'),
     new DartdocOptionArgOnly<String>('input', Directory.current.path,
         isDir: true, help: 'Path to source directory', mustExist: true),
     new DartdocOptionSyntheticOnly<String>('inputDir',
@@ -1323,7 +1327,7 @@
             'A list of package names to place first when grouping libraries in packages. '
             'Unmentioned packages are sorted after these.'),
     new DartdocOptionArgOnly<bool>('sdkDocs', false,
-        help: 'Generate ONLY the docs for the Dart SDK.', negatable: false),
+        help: 'Generate ONLY the docs for the Dart SDK.'),
     new DartdocOptionArgSynth<String>('sdkDir',
         (DartdocSyntheticOption<String> option, Directory dir) {
       if (!option.parent['sdkDocs'].valueAt(dir) &&
@@ -1341,7 +1345,7 @@
     new DartdocOptionArgFile<bool>('showUndocumentedCategories', false,
         help: "Label categories that aren't documented", negatable: true),
     new DartdocOptionArgOnly<bool>('showWarnings', false,
-        help: 'Display all warnings.', negatable: false),
+        help: 'Display all warnings.'),
     new DartdocOptionSyntheticOnly<PackageMeta>('topLevelPackageMeta',
         (DartdocSyntheticOption<PackageMeta> option, Directory dir) {
       PackageMeta packageMeta = new PackageMeta.fromDir(
@@ -1357,8 +1361,7 @@
       return packageMeta;
     }, help: 'PackageMeta object for the default package.'),
     new DartdocOptionArgOnly<bool>('useCategories', true,
-        help: 'Display categories in the sidebar of packages',
-        negatable: false),
+        help: 'Display categories in the sidebar of packages'),
     new DartdocOptionArgOnly<bool>('validateLinks', true,
         help:
             'Runs the built-in link checker to display Dart context aware warnings for broken links (slow)',
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 6a5d129..5c6ba0f 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -4088,6 +4088,7 @@
   /// And the HTML fragment will not have been processed or changed by Markdown,
   /// but just injected verbatim.
   String _injectHtmlFragments(String rawDocs) {
+    if (!config.injectHtml) return rawDocs;
     final macroRegExp = new RegExp(r'<dartdoc-html>([a-f0-9]+)</dartdoc-html>');
     return rawDocs.replaceAllMapped(macroRegExp, (match) {
       String fragment = packageGraph.getHtmlFragment(match[1]);
@@ -4166,6 +4167,7 @@
   ///     &#123;@end-inject-html&#125;
   ///
   String _stripHtmlAndAddToIndex(String rawDocs) {
+    if (!config.injectHtml) return rawDocs;
     final templateRegExp = new RegExp(
         r'[ ]*{@inject-html\s*}([\s\S]+?){@end-inject-html}[ ]*\n?',
         multiLine: true);
diff --git a/test/model_test.dart b/test/model_test.dart
index 3e68bf7..bd33c5b 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -116,22 +116,59 @@
     });
   });
 
-  group('HTML Injection', () {
+  group('HTML Injection when allowed', () {
+    Class htmlInjection;
+    Method injectSimpleHtml;
+
+    PackageGraph injectionPackageGraph;
+    Library injectionExLibrary;
+
+    setUpAll(() async {
+      injectionPackageGraph = await utils.bootBasicPackage(
+          'testing/test_package', ['css', 'code_in_comments', 'excluded'],
+          additionalArguments: ['--inject-html']);
+
+      injectionExLibrary =
+          injectionPackageGraph.libraries.firstWhere((lib) => lib.name == 'ex');
+
+      htmlInjection = injectionExLibrary.classes
+          .firstWhere((c) => c.name == 'HtmlInjection');
+      injectSimpleHtml = htmlInjection.allInstanceMethods
+          .firstWhere((m) => m.name == 'injectSimpleHtml');
+      injectionPackageGraph.allLocalModelElements
+          .forEach((m) => m.documentation);
+    });
+
+    test("can inject HTML", () {
+      expect(
+          injectSimpleHtml.documentation,
+          contains(
+              '\n<dartdoc-html>bad2bbdd4a5cf9efb3212afff4449904756851aa</dartdoc-html>\n'));
+      expect(injectSimpleHtml.documentation,
+          isNot(contains('\n{@inject-html}\n')));
+      expect(injectSimpleHtml.documentationAsHtml,
+          contains('   <div style="opacity: 0.5;">[HtmlInjection]</div>'));
+    });
+  });
+
+  group('HTML Injection when not allowed', () {
     Class htmlInjection;
     Method injectSimpleHtml;
 
     setUp(() {
-      htmlInjection = exLibrary.classes.firstWhere((c) => c.name == 'HtmlInjection');
-      injectSimpleHtml =
-          htmlInjection.allInstanceMethods.firstWhere((m) => m.name == 'injectSimpleHtml');
+      htmlInjection =
+          exLibrary.classes.firstWhere((c) => c.name == 'HtmlInjection');
+      injectSimpleHtml = htmlInjection.allInstanceMethods
+          .firstWhere((m) => m.name == 'injectSimpleHtml');
       packageGraph.allLocalModelElements.forEach((m) => m.documentation);
     });
-    test("can inject HTML", () {
+    test("doesn't inject HTML if --inject-html option is not present", () {
       expect(
           injectSimpleHtml.documentation,
-          contains('\n<dartdoc-html>bad2bbdd4a5cf9efb3212afff4449904756851aa</dartdoc-html>\n'));
-      expect(injectSimpleHtml.documentationAsHtml,
-          contains('   <div style="opacity: 0.5;">[HtmlInjection]</div>'));
+          isNot(contains(
+              '\n<dartdoc-html>bad2bbdd4a5cf9efb3212afff4449904756851aa</dartdoc-html>\n')));
+      expect(injectSimpleHtml.documentation, isNot(contains('<dartdoc-html>')));
+      expect(injectSimpleHtml.documentationAsHtml, contains('{@inject-html}'));
     });
   });
 
@@ -1995,8 +2032,9 @@
           reason: "Can't find convertToMap function in ${fakePath}");
       if (Platform.isWindows) fakePath = fakePath.replaceAll('/', r'\\');
 
-      crossdartPackageGraph = await utils
-          .bootBasicPackage(utils.testPackageDir.path, [], withCrossdart: true);
+      crossdartPackageGraph = await utils.bootBasicPackage(
+          utils.testPackageDir.path, [],
+          additionalArguments: ['--add-crossdart']);
       crossdartFakeLibrary =
           crossdartPackageGraph.libraries.firstWhere((l) => l.name == 'fake');
       HasGenerics = crossdartFakeLibrary.classes
diff --git a/test/src/utils.dart b/test/src/utils.dart
index ed63e28..c14fe28 100644
--- a/test/src/utils.dart
+++ b/test/src/utils.dart
@@ -60,18 +60,22 @@
   if (dir.existsSync()) dir.deleteSync(recursive: true);
 }
 
-void init() async {
+void init({List<String> additionalArguments}) async {
   sdkDir = defaultSdkDir;
   sdkPackageMeta = new PackageMeta.fromDir(sdkDir);
+  additionalArguments ??= <String>[];
 
   testPackageGraph = await bootBasicPackage(
-      'testing/test_package', ['css', 'code_in_comments', 'excluded']);
+      'testing/test_package', ['css', 'code_in_comments', 'excluded'],
+      additionalArguments: additionalArguments);
   testPackageGraphGinormous = await bootBasicPackage(
       'testing/test_package', ['css', 'code_in_commnets', 'excluded'],
-      withAutoIncludedDependencies: true);
+      additionalArguments:
+          additionalArguments + ['--auto-include-dependencies']);
 
-  testPackageGraphSmall =
-      await bootBasicPackage('testing/test_package_small', []);
+  testPackageGraphSmall = await bootBasicPackage(
+      'testing/test_package_small', [],
+      additionalArguments: additionalArguments);
   testPackageGraphSdk = await bootSdkPackage();
 }
 
@@ -82,18 +86,17 @@
 
 Future<PackageGraph> bootBasicPackage(
     String dirPath, List<String> excludeLibraries,
-    {bool withAutoIncludedDependencies = false,
-    bool withCrossdart = false}) async {
+    {List<String> additionalArguments}) async {
   Directory dir = new Directory(dirPath);
+  additionalArguments ??= <String>[];
   return new PackageBuilder(await contextFromArgv([
-    '--input',
-    dir.path,
-    '--sdk-dir',
-    sdkDir.path,
-    '--exclude',
-    excludeLibraries.join(','),
-    '--${withCrossdart ? "" : "no-"}add-crossdart',
-    '--${withAutoIncludedDependencies ? "" : "no-"}auto-include-dependencies'
-  ]))
+            '--input',
+            dir.path,
+            '--sdk-dir',
+            sdkDir.path,
+            '--exclude',
+            excludeLibraries.join(','),
+          ] +
+          additionalArguments))
       .buildPackageGraph();
 }
diff --git a/testing/test_package_docs/ex/HtmlInjection-class.html b/testing/test_package_docs/ex/HtmlInjection-class.html
index e51f9c8..71d1d02 100644
--- a/testing/test_package_docs/ex/HtmlInjection-class.html
+++ b/testing/test_package_docs/ex/HtmlInjection-class.html
@@ -159,7 +159,8 @@
           </span>
           </dt>
         <dd>
-          Injects some HTML. <a href="ex/HtmlInjection/injectSimpleHtml.html">[...]</a>
+          Injects some HTML.
+{@inject-html} <a href="ex/HtmlInjection/injectSimpleHtml.html">[...]</a>
           
 </dd>
         <dt id="noSuchMethod" class="callable inherited">
diff --git a/testing/test_package_docs/ex/HtmlInjection/injectSimpleHtml.html b/testing/test_package_docs/ex/HtmlInjection/injectSimpleHtml.html
index 155e724..015141a 100644
--- a/testing/test_package_docs/ex/HtmlInjection/injectSimpleHtml.html
+++ b/testing/test_package_docs/ex/HtmlInjection/injectSimpleHtml.html
@@ -70,10 +70,9 @@
 (<wbr>)
     </section>
     <section class="desc markdown">
-      <p>Injects some HTML.</p>
-<p>
-   <div style="opacity: 0.5;">[HtmlInjection]</div>
-</p>
+      <p>Injects some HTML.
+{@inject-html}</p>   <div style="opacity: 0.5;">[HtmlInjection]</div>
+{@end-inject-html}
     </section>
     
     
diff --git a/testing/test_package_docs_dev/ex/HtmlInjection-class.html b/testing/test_package_docs_dev/ex/HtmlInjection-class.html
index e51f9c8..71d1d02 100644
--- a/testing/test_package_docs_dev/ex/HtmlInjection-class.html
+++ b/testing/test_package_docs_dev/ex/HtmlInjection-class.html
@@ -159,7 +159,8 @@
           </span>
           </dt>
         <dd>
-          Injects some HTML. <a href="ex/HtmlInjection/injectSimpleHtml.html">[...]</a>
+          Injects some HTML.
+{@inject-html} <a href="ex/HtmlInjection/injectSimpleHtml.html">[...]</a>
           
 </dd>
         <dt id="noSuchMethod" class="callable inherited">
diff --git a/testing/test_package_docs_dev/ex/HtmlInjection/injectSimpleHtml.html b/testing/test_package_docs_dev/ex/HtmlInjection/injectSimpleHtml.html
index 155e724..015141a 100644
--- a/testing/test_package_docs_dev/ex/HtmlInjection/injectSimpleHtml.html
+++ b/testing/test_package_docs_dev/ex/HtmlInjection/injectSimpleHtml.html
@@ -70,10 +70,9 @@
 (<wbr>)
     </section>
     <section class="desc markdown">
-      <p>Injects some HTML.</p>
-<p>
-   <div style="opacity: 0.5;">[HtmlInjection]</div>
-</p>
+      <p>Injects some HTML.
+{@inject-html}</p>   <div style="opacity: 0.5;">[HtmlInjection]</div>
+{@end-inject-html}
     </section>