Skip to content

Commit 9941e5c

Browse files
authored
Allow dartdoc to customize package builder and package meta creation (#2209)
1 parent 171e54f commit 9941e5c

File tree

7 files changed

+327
-211
lines changed

7 files changed

+327
-211
lines changed

bin/dartdoc.dart

Lines changed: 15 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,134 +5,27 @@
55
library dartdoc.bin;
66

77
import 'dart:async';
8-
import 'dart:io';
98

10-
import 'package:args/args.dart';
119
import 'package:dartdoc/dartdoc.dart';
12-
import 'package:dartdoc/src/logging.dart';
13-
import 'package:dartdoc/src/tool_runner.dart';
14-
import 'package:stack_trace/stack_trace.dart';
15-
16-
class DartdocProgramOptionContext extends DartdocGeneratorOptionContext
17-
with LoggingContext {
18-
DartdocProgramOptionContext(DartdocOptionSet optionSet, Directory dir)
19-
: super(optionSet, dir);
20-
21-
bool get asyncStackTraces => optionSet['asyncStackTraces'].valueAt(context);
22-
bool get generateDocs => optionSet['generateDocs'].valueAt(context);
23-
bool get help => optionSet['help'].valueAt(context);
24-
bool get version => optionSet['version'].valueAt(context);
25-
}
26-
27-
Future<List<DartdocOption>> createDartdocProgramOptions() async {
28-
return <DartdocOption>[
29-
DartdocOptionArgOnly<bool>('asyncStackTraces', false,
30-
help: 'Display coordinated asynchronous stack traces (slow)',
31-
negatable: true),
32-
DartdocOptionArgOnly<bool>('generateDocs', true,
33-
help:
34-
'Generate docs into the output directory (or only display warnings if false).',
35-
negatable: true),
36-
DartdocOptionArgOnly<bool>('help', false,
37-
abbr: 'h', help: 'Show command help.', negatable: false),
38-
DartdocOptionArgOnly<bool>('version', false,
39-
help: 'Display the version for $programName.', negatable: false),
40-
];
41-
}
10+
import 'package:dartdoc/options.dart';
4211

4312
/// Analyzes Dart files and generates a representation of included libraries,
4413
/// classes, and members. Uses the current directory to look for libraries.
4514
Future<void> main(List<String> arguments) async {
46-
var optionSet = await DartdocOptionSet.fromOptionGenerators('dartdoc', [
47-
createDartdocOptions,
48-
createDartdocProgramOptions,
49-
createLoggingOptions,
50-
createGeneratorOptions,
51-
]);
52-
53-
try {
54-
optionSet.parseArguments(arguments);
55-
} on FormatException catch (e) {
56-
stderr.writeln(' fatal error: ${e.message}');
57-
stderr.writeln('');
58-
_printUsage(optionSet.argParser);
59-
// Do not use exit() as this bypasses --pause-isolates-on-exit
60-
// TODO(jcollins-g): use exit once dart-lang/sdk#31747 is fixed.
61-
exitCode = 64;
15+
var config = await parseOptions(arguments);
16+
if (config == null) {
17+
// There was an error while parsing options.
6218
return;
6319
}
64-
if (optionSet['help'].valueAt(Directory.current)) {
65-
_printHelp(optionSet.argParser);
66-
exitCode = 0;
67-
return;
68-
}
69-
if (optionSet['version'].valueAt(Directory.current)) {
70-
_printVersion(optionSet.argParser);
71-
exitCode = 0;
72-
return;
73-
}
74-
75-
DartdocProgramOptionContext config;
76-
try {
77-
config = DartdocProgramOptionContext(optionSet, null);
78-
} on DartdocOptionError catch (e) {
79-
stderr.writeln(' fatal error: ${e.message}');
80-
stderr.writeln('');
81-
await stderr.flush();
82-
_printUsage(optionSet.argParser);
83-
exitCode = 64;
84-
return;
85-
}
86-
startLogging(config);
87-
88-
var dartdoc = config.generateDocs
89-
? await Dartdoc.fromContext(config)
90-
: await Dartdoc.withEmptyGenerator(config);
91-
dartdoc.onCheckProgress.listen(logProgress);
92-
try {
93-
await Chain.capture(() async {
94-
await runZoned(dartdoc.generateDocs,
95-
zoneSpecification: ZoneSpecification(
96-
print: (Zone self, ZoneDelegate parent, Zone zone, String line) =>
97-
logPrint(line)));
98-
}, onError: (e, Chain chain) {
99-
if (e is DartdocFailure) {
100-
stderr.writeln('\ndartdoc failed: ${e}.');
101-
if (config.verboseWarnings) {
102-
stderr.writeln(chain.terse);
103-
}
104-
exitCode = 1;
105-
return;
106-
} else {
107-
stderr.writeln('\ndartdoc failed: ${e}\n${chain.terse}');
108-
exitCode = 255;
109-
return;
110-
}
111-
}, when: config.asyncStackTraces);
112-
} finally {
113-
// Clear out any cached tool snapshots and temporary directories.
114-
// ignore: unawaited_futures
115-
SnapshotCache.instance.dispose();
116-
// ignore: unawaited_futures
117-
ToolTempFileTracker.instance.dispose();
118-
}
119-
exitCode = 0;
120-
return;
121-
}
122-
123-
/// Print help if we are passed the help option.
124-
void _printHelp(ArgParser parser) {
125-
print('Generate HTML documentation for Dart libraries.\n');
126-
print(parser.usage);
127-
}
128-
129-
/// Print usage information on invalid command lines.
130-
void _printUsage(ArgParser parser) {
131-
print('Usage: dartdoc [OPTIONS]\n');
132-
print(parser.usage);
133-
}
134-
135-
/// Print version information.
136-
void _printVersion(ArgParser parser) {
137-
print('dartdoc version: ${dartdocVersion}');
20+
// Set the default way to construct [PackageMeta].
21+
PackageMeta.setPackageMetaFactories(
22+
PubPackageMeta.fromElement,
23+
PubPackageMeta.fromFilename,
24+
PubPackageMeta.fromDir,
25+
);
26+
final packageBuilder = PubPackageBuilder(config);
27+
final dartdoc = config.generateDocs
28+
? await Dartdoc.fromContext(config, packageBuilder)
29+
: await Dartdoc.withEmptyGenerator(config, packageBuilder);
30+
dartdoc.executeGuarded();
13831
}

lib/dartdoc.dart

Lines changed: 84 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'package:dartdoc/src/generator/markdown_generator.dart';
2020
import 'package:dartdoc/src/logging.dart';
2121
import 'package:dartdoc/src/model/model.dart';
2222
import 'package:dartdoc/src/package_meta.dart';
23+
import 'package:dartdoc/src/tool_runner.dart';
2324
import 'package:dartdoc/src/tuple.dart';
2425
import 'package:dartdoc/src/utils.dart';
2526
import 'package:dartdoc/src/version.dart';
@@ -94,36 +95,53 @@ class DartdocFileWriter implements FileWriter {
9495

9596
/// Generates Dart documentation for all public Dart libraries in the given
9697
/// directory.
97-
class Dartdoc extends PackageBuilder {
98+
class Dartdoc {
9899
final Generator generator;
100+
final PackageBuilder packageBuilder;
101+
final DartdocOptionContext config;
99102
final Set<String> writtenFiles = {};
100103
Directory outputDir;
101104

102105
// Fires when the self checks make progress.
103106
final StreamController<String> _onCheckProgress =
104107
StreamController(sync: true);
105108

106-
Dartdoc._(DartdocOptionContext config, this.generator) : super(config) {
109+
Dartdoc._(this.config, this.generator, this.packageBuilder) {
107110
outputDir = Directory(config.output)..createSync(recursive: true);
108111
}
109112

110113
/// An asynchronous factory method that builds Dartdoc's file writers
111114
/// and returns a Dartdoc object with them.
112115
@Deprecated('Prefer fromContext() instead')
113116
static Future<Dartdoc> withDefaultGenerators(
114-
DartdocGeneratorOptionContext config) async {
115-
return Dartdoc._(config, await initHtmlGenerator(config));
117+
DartdocGeneratorOptionContext config,
118+
PackageBuilder packageBuilder,
119+
) async {
120+
return Dartdoc._(
121+
config,
122+
await initHtmlGenerator(config),
123+
packageBuilder,
124+
);
116125
}
117126

118127
/// Asynchronous factory method that builds Dartdoc with an empty generator.
119-
static Future<Dartdoc> withEmptyGenerator(DartdocOptionContext config) async {
120-
return Dartdoc._(config, await initEmptyGenerator(config));
128+
static Future<Dartdoc> withEmptyGenerator(
129+
DartdocOptionContext config,
130+
PackageBuilder packageBuilder,
131+
) async {
132+
return Dartdoc._(
133+
config,
134+
await initEmptyGenerator(config),
135+
packageBuilder,
136+
);
121137
}
122138

123139
/// Asynchronous factory method that builds Dartdoc with a generator
124140
/// determined by the given context.
125141
static Future<Dartdoc> fromContext(
126-
DartdocGeneratorOptionContext context) async {
142+
DartdocGeneratorOptionContext context,
143+
PackageBuilder packageBuilder,
144+
) async {
127145
Generator generator;
128146
switch (context.format) {
129147
case 'html':
@@ -135,7 +153,11 @@ class Dartdoc extends PackageBuilder {
135153
default:
136154
throw DartdocFailure('Unsupported output format: ${context.format}');
137155
}
138-
return Dartdoc._(context, generator);
156+
return Dartdoc._(
157+
context,
158+
generator,
159+
packageBuilder,
160+
);
139161
}
140162

141163
Stream<String> get onCheckProgress => _onCheckProgress.stream;
@@ -150,7 +172,7 @@ class Dartdoc extends PackageBuilder {
150172
Future<DartdocResults> generateDocsBase() async {
151173
var _stopwatch = Stopwatch()..start();
152174
double seconds;
153-
packageGraph = await buildPackageGraph();
175+
packageGraph = await packageBuilder.buildPackageGraph();
154176
seconds = _stopwatch.elapsedMilliseconds / 1000.0;
155177
var libs = packageGraph.libraries.length;
156178
logInfo("Initialized dartdoc with ${libs} librar${libs == 1 ? 'y' : 'ies'} "
@@ -188,22 +210,31 @@ class Dartdoc extends PackageBuilder {
188210
}
189211

190212
Future<DartdocResults> generateDocs() async {
191-
logInfo('Documenting ${config.topLevelPackageMeta}...');
213+
try {
214+
logInfo('Documenting ${config.topLevelPackageMeta}...');
192215

193-
var dartdocResults = await generateDocsBase();
194-
if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) {
195-
throw DartdocFailure('dartdoc could not find any libraries to document');
196-
}
216+
var dartdocResults = await generateDocsBase();
217+
if (dartdocResults.packageGraph.localPublicLibraries.isEmpty) {
218+
throw DartdocFailure(
219+
'dartdoc could not find any libraries to document');
220+
}
197221

198-
final errorCount =
199-
dartdocResults.packageGraph.packageWarningCounter.errorCount;
200-
if (errorCount > 0) {
201-
throw DartdocFailure(
202-
'dartdoc encountered $errorCount errors while processing.');
222+
final errorCount =
223+
dartdocResults.packageGraph.packageWarningCounter.errorCount;
224+
if (errorCount > 0) {
225+
throw DartdocFailure(
226+
'dartdoc encountered $errorCount errors while processing.');
227+
}
228+
logInfo(
229+
'Success! Docs generated into ${dartdocResults.outDir.absolute.path}');
230+
return dartdocResults;
231+
} finally {
232+
// Clear out any cached tool snapshots and temporary directories.
233+
// ignore: unawaited_futures
234+
SnapshotCache.instance.dispose();
235+
// ignore: unawaited_futures
236+
ToolTempFileTracker.instance.dispose();
203237
}
204-
logInfo(
205-
'Success! Docs generated into ${dartdocResults.outDir.absolute.path}');
206-
return dartdocResults;
207238
}
208239

209240
/// Warn on file paths.
@@ -429,6 +460,37 @@ class Dartdoc extends PackageBuilder {
429460
_doOrphanCheck(packageGraph, origin, visited);
430461
_doSearchIndexCheck(packageGraph, origin, visited);
431462
}
463+
464+
/// Runs [generateDocs] function and properly handles the errors.
465+
void executeGuarded() {
466+
onCheckProgress.listen(logProgress);
467+
// This function should *never* await `runZonedGuarded` because the errors
468+
// thrown in generateDocs are uncaught. We want this because uncaught errors
469+
// cause IDE debugger to automatically stop at the exception.
470+
//
471+
// If you await the zone, the code that comes after the await is not
472+
// executed if the zone dies due to uncaught error. To avoid this confusion,
473+
// never await `runZonedGuarded` and never change the return value of
474+
// [executeGuarded].
475+
runZonedGuarded(
476+
generateDocs,
477+
(e, chain) {
478+
if (e is DartdocFailure) {
479+
stderr.writeln('\ndartdoc failed: ${e}.');
480+
if (config.verboseWarnings) {
481+
stderr.writeln(chain);
482+
}
483+
exitCode = 1;
484+
} else {
485+
stderr.writeln('\ndartdoc failed: ${e}\n${chain}');
486+
exitCode = 255;
487+
}
488+
},
489+
zoneSpecification: ZoneSpecification(
490+
print: (_, __, ___, String line) => logPrint(line),
491+
),
492+
);
493+
}
432494
}
433495

434496
/// This class is returned if dartdoc fails in an expected way (for instance, if

0 commit comments

Comments
 (0)