Move generator options into generator.dart (#2100)

* Remove --hosted-url as it is actually unused

* Move templatesDir option to generator context

* Separate html generator options from generic ones

* Move base generator context options to generator.dart

* Move initEmptyGenerators

* Make initHtmlGenerators take HtmlGeneratorContext instead

* Recombine options into one GeneratorContext

* Temporarily dupe useBaseHrefs in GeneratorContext
diff --git a/lib/dartdoc.dart b/lib/dartdoc.dart
index 2c10570..66a8a8a 100644
--- a/lib/dartdoc.dart
+++ b/lib/dartdoc.dart
@@ -13,6 +13,7 @@
 import 'dart:io';
 
 import 'package:dartdoc/src/dartdoc_options.dart';
+import 'package:dartdoc/src/empty_generator.dart';
 import 'package:dartdoc/src/generator.dart';
 import 'package:dartdoc/src/html/html_generator.dart';
 import 'package:dartdoc/src/logging.dart';
@@ -36,8 +37,7 @@
 // Update when pubspec version changes by running `pub run build_runner build`
 const String dartdocVersion = packageVersion;
 
-/// Helper class to initialize the default generators since they require
-/// GeneratorContext.
+/// Helper class that consolidates option contexts for instantiating generators.
 class DartdocGeneratorOptionContext extends DartdocOptionContext
     with GeneratorContext {
   DartdocGeneratorOptionContext(DartdocOptionSet optionSet, Directory dir)
@@ -64,7 +64,7 @@
   /// and returns a Dartdoc object with them.
   static Future<Dartdoc> withDefaultGenerators(
       DartdocGeneratorOptionContext config) async {
-    List<Generator> generators = await initGenerators(config);
+    List<Generator> generators = await initHtmlGenerators(config);
     return Dartdoc._(config, generators);
   }
 
diff --git a/lib/src/dartdoc_options.dart b/lib/src/dartdoc_options.dart
index 409f876..7385131 100644
--- a/lib/src/dartdoc_options.dart
+++ b/lib/src/dartdoc_options.dart
@@ -1421,8 +1421,6 @@
   bool isPackageExcluded(String name) =>
       excludePackages.any((pattern) => name == pattern);
 
-  String get templatesDir => optionSet['templatesDir'].valueAt(context);
-
   // TODO(jdkoren): temporary while we confirm href base behavior doesn't break important clients
   bool get useBaseHref => optionSet['useBaseHref'].valueAt(context);
 }
@@ -1627,17 +1625,6 @@
             'exist. Executables for different platforms are specified by '
             'giving the platform name as a key, and a list of strings as the '
             'command.'),
-    DartdocOptionArgOnly<String>("templatesDir", null,
-        isDir: true,
-        mustExist: true,
-        hide: true,
-        help:
-            'Path to a directory containing templates to use instead of the default ones. '
-            'Directory must contain an html file for each of the following: 404error, category, '
-            'class, constant, constructor, enum, function, index, library, method, mixin, '
-            'property, top_level_constant, top_level_property, typedef. Partial templates are '
-            'supported; they must begin with an underscore, and references to them must omit the '
-            'leading underscore (e.g. use {{>foo}} to reference the partial template _foo.html).'),
     DartdocOptionArgOnly<bool>('useBaseHref', false,
         help:
             'Use <base href> in generated files (legacy behavior). This option '
diff --git a/lib/src/empty_generator.dart b/lib/src/empty_generator.dart
index d879574..666df06 100644
--- a/lib/src/empty_generator.dart
+++ b/lib/src/empty_generator.dart
@@ -2,6 +2,7 @@
 
 import 'dart:async';
 
+import 'package:dartdoc/src/dartdoc_options.dart';
 import 'package:dartdoc/src/generator.dart';
 import 'package:dartdoc/src/model/model.dart';
 import 'package:dartdoc/src/model_utils.dart';
@@ -39,3 +40,7 @@
   @override
   final Map<String, Warnable> writtenFiles = {};
 }
+
+Future<List<Generator>> initEmptyGenerators(DartdocOptionContext config) async {
+  return [EmptyGenerator()];
+}
diff --git a/lib/src/generator.dart b/lib/src/generator.dart
index d062ae9..6ea61e1 100644
--- a/lib/src/generator.dart
+++ b/lib/src/generator.dart
@@ -6,9 +6,14 @@
 library dartdoc.generator;
 
 import 'dart:async' show Stream, Future;
+import 'dart:io' show Directory;
+import 'dart:isolate';
 
+import 'package:dartdoc/src/dartdoc_options.dart';
 import 'package:dartdoc/src/model/model.dart' show PackageGraph;
+import 'package:dartdoc/src/package_meta.dart';
 import 'package:dartdoc/src/warnings.dart';
+import 'package:path/path.dart' as path;
 
 /// An abstract class that defines a generator that generates documentation for
 /// a given package.
@@ -25,3 +30,110 @@
   /// Fetches all filenames written by this generator.
   Map<String, Warnable> get writtenFiles;
 }
+
+/// Dartdoc options related to generators generally.
+mixin GeneratorContext on DartdocOptionContextBase {
+  List<String> get footer => optionSet['footer'].valueAt(context);
+
+  /// _footerText is only used to construct synthetic options.
+  // ignore: unused_element
+  List<String> get _footerText => optionSet['footerText'].valueAt(context);
+
+  List<String> get footerTextPaths =>
+      optionSet['footerTextPaths'].valueAt(context);
+
+  List<String> get header => optionSet['header'].valueAt(context);
+
+  bool get prettyIndexJson => optionSet['prettyIndexJson'].valueAt(context);
+
+  String get favicon => optionSet['favicon'].valueAt(context);
+
+  String get relCanonicalPrefix =>
+      optionSet['relCanonicalPrefix'].valueAt(context);
+
+  String get templatesDir => optionSet['templatesDir'].valueAt(context);
+
+  // TODO(jdkoren): duplicated temporarily so that GeneratorContext is enough for configuration.
+  bool get useBaseHref => optionSet['useBaseHref'].valueAt(context);
+}
+
+Uri _sdkFooterCopyrightUri;
+
+Future<void> _setSdkFooterCopyrightUri() async {
+  if (_sdkFooterCopyrightUri == null) {
+    // TODO(jdkoren): find a way to make this not specific to HTML, or have
+    // alternatives for other supported formats.
+    _sdkFooterCopyrightUri = await Isolate.resolvePackageUri(
+        Uri.parse('package:dartdoc/resources/sdk_footer_text.html'));
+  }
+}
+
+Future<List<DartdocOption>> createGeneratorOptions() async {
+  await _setSdkFooterCopyrightUri();
+  return <DartdocOption>[
+    DartdocOptionArgFile<List<String>>('footer', [],
+        isFile: true,
+        help:
+            'Paths to files with content to add to page footers, but possibly '
+            'outside of dedicated footer elements for the generator (e.g. '
+            'outside of <footer> for an HTML generator). To add text content '
+            'to dedicated footer elements, use --footer-text instead.',
+        mustExist: true,
+        splitCommas: true),
+    DartdocOptionArgFile<List<String>>('footerText', [],
+        isFile: true,
+        help: 'Paths to files with content to add to page footers (next to the '
+            'package name and version).',
+        mustExist: true,
+        splitCommas: true),
+    DartdocOptionSyntheticOnly<List<String>>(
+      'footerTextPaths',
+      (DartdocSyntheticOption<List<String>> option, Directory dir) {
+        final List<String> footerTextPaths = <String>[];
+        final PackageMeta topLevelPackageMeta =
+            option.root['topLevelPackageMeta'].valueAt(dir);
+        // TODO(jcollins-g): Eliminate special casing for SDK and use config file.
+        if (topLevelPackageMeta.isSdk == true) {
+          footerTextPaths
+              .add(path.canonicalize(_sdkFooterCopyrightUri.toFilePath()));
+        }
+        footerTextPaths.addAll(option.parent['footerText'].valueAt(dir));
+        return footerTextPaths;
+      },
+      isFile: true,
+      help: 'paths to footer-text-files (adding special case for SDK)',
+      mustExist: true,
+    ),
+    DartdocOptionArgFile<List<String>>('header', [],
+        isFile: true,
+        help: 'Paths to files with content to add to page headers.',
+        splitCommas: true),
+    DartdocOptionArgOnly<bool>('prettyIndexJson', false,
+        help:
+            'Generates `index.json` with indentation and newlines. The file is '
+            'larger, but it\'s also easier to diff.',
+        negatable: false),
+    DartdocOptionArgFile<String>('favicon', null,
+        isFile: true,
+        help: 'A path to a favicon for the generated docs.',
+        mustExist: true),
+    DartdocOptionArgOnly<String>('relCanonicalPrefix', null,
+        help:
+            'If provided, add a rel="canonical" prefixed with provided value. '
+            'Consider using if building many versions of the docs for public '
+            'SEO; learn more at https://goo.gl/gktN6F.'),
+    DartdocOptionArgOnly<String>("templatesDir", null,
+        isDir: true,
+        mustExist: true,
+        hide: true,
+        help:
+            'Path to a directory with templates to use instead of the default '
+            'ones. Directory must contain a file for each of the following: '
+            '404error, category, class, constant, constructor, enum, function, '
+            'index, library, method, mixin, property, top_level_constant, '
+            'top_level_property, typedef. Partial templates are supported; '
+            'they must begin with an underscore, and references to them must '
+            'omit the leading underscore (e.g. use {{>foo}} to reference the '
+            'partial template _foo.html).'),
+  ];
+}
diff --git a/lib/src/html/html_generator.dart b/lib/src/html/html_generator.dart
index 8cac333..29be73c 100644
--- a/lib/src/html/html_generator.dart
+++ b/lib/src/html/html_generator.dart
@@ -131,7 +131,6 @@
 }
 
 class HtmlGeneratorOptions implements HtmlOptions {
-  final String url;
   final String faviconPath;
   final bool prettyIndexJson;
   final String templatesDir;
@@ -146,8 +145,7 @@
   final bool useBaseHref;
 
   HtmlGeneratorOptions(
-      {this.url,
-      this.relCanonicalPrefix,
+      {this.relCanonicalPrefix,
       this.faviconPath,
       String toolVersion,
       this.prettyIndexJson = false,
@@ -156,109 +154,23 @@
       : 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 {
+Future<List<Generator>> initHtmlGenerators(GeneratorContext context) async {
   // TODO(jcollins-g): Rationalize based on GeneratorContext all the way down
   // through the generators.
   HtmlGeneratorOptions options = HtmlGeneratorOptions(
-      url: config.hostedUrl,
-      relCanonicalPrefix: config.relCanonicalPrefix,
+      relCanonicalPrefix: context.relCanonicalPrefix,
       toolVersion: dartdocVersion,
-      faviconPath: config.favicon,
-      prettyIndexJson: config.prettyIndexJson,
-      templatesDir: config.templatesDir,
-      useBaseHref: config.useBaseHref);
-
+      faviconPath: context.favicon,
+      prettyIndexJson: context.prettyIndexJson,
+      templatesDir: context.templatesDir,
+      useBaseHref: context.useBaseHref);
   return [
     await HtmlGenerator.create(
       options: options,
-      headers: config.header,
-      footers: config.footer,
-      footerTexts: config.footerTextPaths,
+      headers: context.header,
+      footers: context.footer,
+      footerTexts: context.footerTextPaths,
     )
   ];
 }
-
-Uri _sdkFooterCopyrightUri;
-Future<void> _setSdkFooterCopyrightUri() async {
-  if (_sdkFooterCopyrightUri == null) {
-    _sdkFooterCopyrightUri = await Isolate.resolvePackageUri(
-        Uri.parse('package:dartdoc/resources/sdk_footer_text.html'));
-  }
-}
-
-abstract class GeneratorContext implements DartdocOptionContext {
-  String get favicon => optionSet['favicon'].valueAt(context);
-  List<String> get footer => optionSet['footer'].valueAt(context);
-
-  /// _footerText is only used to construct synthetic options.
-  // ignore: unused_element
-  List<String> get _footerText => optionSet['footerText'].valueAt(context);
-  List<String> get footerTextPaths =>
-      optionSet['footerTextPaths'].valueAt(context);
-  List<String> get header => optionSet['header'].valueAt(context);
-  String get hostedUrl => optionSet['hostedUrl'].valueAt(context);
-  bool get prettyIndexJson => optionSet['prettyIndexJson'].valueAt(context);
-  String get relCanonicalPrefix =>
-      optionSet['relCanonicalPrefix'].valueAt(context);
-}
-
-Future<List<DartdocOption>> createGeneratorOptions() async {
-  await _setSdkFooterCopyrightUri();
-  return <DartdocOption>[
-    DartdocOptionArgFile<String>('favicon', null,
-        isFile: true,
-        help: 'A path to a favicon for the generated docs.',
-        mustExist: true),
-    DartdocOptionArgFile<List<String>>('footer', [],
-        isFile: true,
-        help: 'paths to footer files containing HTML text.',
-        mustExist: true,
-        splitCommas: true),
-    DartdocOptionArgFile<List<String>>('footerText', [],
-        isFile: true,
-        help:
-            'paths to footer-text files (optional text next to the package name '
-            'and version).',
-        mustExist: true,
-        splitCommas: true),
-    DartdocOptionSyntheticOnly<List<String>>(
-      'footerTextPaths',
-      (DartdocSyntheticOption<List<String>> option, Directory dir) {
-        final List<String> footerTextPaths = <String>[];
-        final PackageMeta topLevelPackageMeta =
-            option.root['topLevelPackageMeta'].valueAt(dir);
-        // TODO(jcollins-g): Eliminate special casing for SDK and use config file.
-        if (topLevelPackageMeta.isSdk == true) {
-          footerTextPaths
-              .add(path.canonicalize(_sdkFooterCopyrightUri.toFilePath()));
-        }
-        footerTextPaths.addAll(option.parent['footerText'].valueAt(dir));
-        return footerTextPaths;
-      },
-      isFile: true,
-      help: 'paths to footer-text-files (adding special case for SDK)',
-      mustExist: true,
-    ),
-    DartdocOptionArgFile<List<String>>('header', [],
-        isFile: true,
-        help: 'paths to header files containing HTML text.',
-        splitCommas: true),
-    DartdocOptionArgOnly<String>('hostedUrl', null,
-        help:
-            'URL where the docs will be hosted (used to generate the sitemap).'),
-    DartdocOptionArgOnly<bool>('prettyIndexJson', false,
-        help:
-            "Generates `index.json` with indentation and newlines. The file is larger, but it's also easier to diff.",
-        negatable: false),
-    DartdocOptionArgOnly<String>('relCanonicalPrefix', null,
-        help:
-            'If provided, add a rel="canonical" prefixed with provided value. '
-            'Consider using if\nbuilding many versions of the docs for public '
-            'SEO; learn more at https://goo.gl/gktN6F.'),
-  ];
-}