blob: c0fdf6e5b7c3d1524087f396bd32410f532b8c1c [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/**
* To generate docs for a library, run this script with the path to an
* entrypoint .dart file, like:
*
* $ dart dartdoc.dart foo.dart
*
* This will create a "docs" directory with the docs for your libraries. To
* create these beautiful docs, dartdoc parses your library and every library
* it imports (recursively). From each library, it parses all classes and
* members, finds the associated doc comments and builds crosslinked docs from
* them.
*/
library dartdoc;
import 'dart:async';
import 'dart:io';
// TODO(rnystrom): Use "package:" URL (#4968).
import '../lib/dartdoc.dart';
import '../lib/src/dartdoc/utils.dart';
import 'package:args/args.dart';
import 'package:pathos/path.dart' as path;
/**
* Run this from the `lib/_internal/dartdoc` directory.
*/
main() {
mainWithOptions(new Options());
}
/**
* We use this to include dartdoc in a single snapshot with dart2js.
* (They share 90% of the code)
*/
mainWithOptions(Options options) {
// Need this because ArgParser.getUsage doesn't show command invocation.
final USAGE = 'Usage dartdoc [options] <entrypoint(s)>\n[options] include:';
final args = options.arguments;
final dartdoc = new Dartdoc();
final argParser = new ArgParser();
Path libPath = scriptDir.append('../../../../');
String packageRoot;
argParser.addFlag('no-code',
help: 'Do not include source code in the documentation.',
defaultsTo: false, negatable: false,
callback: (noCode) => dartdoc.includeSource = !noCode);
argParser.addOption('mode', abbr: 'm',
help: 'Define how HTML pages are generated.',
allowed: ['static', 'live-nav'], allowedHelp: {
'static': 'Generates completely static HTML containing\n'
'everything you need to browse the docs. The only\n'
'client side behavior is trivial stuff like syntax\n'
'highlighting code, and the find-as-you-type search\n'
'box.',
'live-nav': '(Default) Generated docs do not included baked HTML\n'
'navigation. Instead a single `nav.json` file is\n'
'created and the appropriate navigation is generated\n'
'client-side by parsing that and building HTML.\n'
'\tThis dramatically reduces the generated size of\n'
'the HTML since a large fraction of each static page\n'
'is just redundant navigation links.\n'
'\tIn this mode, the browser will do a XHR for\n'
'nav.json which means that to preview docs locallly,\n'
'you will need to enable requesting file:// links in\n'
'your browser or run a little local server like\n'
'`python -m SimpleHTTPServer`.'},
defaultsTo: 'live-nav',
callback: (genMode) {
dartdoc.mode = (genMode == 'static' ? MODE_STATIC : MODE_LIVE_NAV);
});
argParser.addFlag('generate-app-cache',
help: 'Generates the App Cache manifest file, enabling\n'
'offline doc viewing.',
defaultsTo: false, negatable: false,
callback: (generate) => dartdoc.generateAppCache = generate);
argParser.addFlag('omit-generation-time',
help: 'Omits generation timestamp from output.',
defaultsTo: false, negatable: false,
callback: (genTimestamp) => dartdoc.omitGenerationTime = genTimestamp);
argParser.addFlag('verbose', abbr: 'v',
help: 'Print verbose information during generation.',
defaultsTo: false, negatable: false,
callback: (verb) => dartdoc.verbose = verb);
argParser.addFlag('include-api',
help: 'Include the used API libraries in the generated\n'
'documentation. If the --link-api option is used,\n'
'this option is ignored.',
defaultsTo: false, negatable: false,
callback: (incApi) => dartdoc.includeApi = incApi);
argParser.addFlag('link-api',
help: 'Link to the online language API in the generated\n'
'documentation. The option overrides inclusion\n'
'through --include-api or --include-lib.',
defaultsTo: false, negatable: false,
callback: (linkApi) => dartdoc.linkToApi = linkApi);
argParser.addFlag('show-private',
help: 'Document private types and members.',
defaultsTo: false,
callback: (showPrivate) => dartdoc.showPrivate = showPrivate);
argParser.addFlag('inherit-from-object',
help: 'Show members inherited from Object.',
defaultsTo: false, negatable: false,
callback: (inherit) => dartdoc.inheritFromObject = inherit);
argParser.addFlag('enable-diagnostic-colors', negatable: false);
argParser.addOption('out',
help: 'Generates files into directory specified. If\n'
'omitted the files are generated into ./docs/',
callback: (outDir) {
if(outDir != null) {
dartdoc.outputDir = new Path(outDir);
}
});
argParser.addOption('include-lib',
help: 'Use this option to explicitly specify which\n'
'libraries to include in the documentation. If\n'
'omitted, all used libraries are included by\n'
'default. Specify a comma-separated list of\n'
'library names, or call this option multiple times.',
callback: (incLibs) {
if(!incLibs.isEmpty) {
List<String> allLibs = new List<String>();
for(final lst in incLibs) {
var someLibs = lst.split(',');
for(final lib in someLibs) {
allLibs.add(lib);
}
}
dartdoc.includedLibraries = allLibs;
}
}, allowMultiple: true);
argParser.addOption('exclude-lib',
help: 'Use this option to explicitly specify which\n'
'libraries to exclude from the documentation. If\n'
'omitted, no libraries are excluded. Specify a\n'
'comma-separated list of library names, or call\n'
'this option multiple times.',
callback: (excLibs) {
if(!excLibs.isEmpty) {
List<String> allLibs = new List<String>();
for(final lst in excLibs) {
var someLibs = lst.split(',');
for(final lib in someLibs) {
allLibs.add(lib);
}
}
dartdoc.excludedLibraries = allLibs;
}
}, allowMultiple: true);
argParser.addOption('package-root',
help: 'Sets the package directory to the specified directory.\n'
'If omitted the package directory is the closest packages directory to'
' the entrypoint.',
callback: (packageDir) {
if(packageDir != null) {
packageRoot = packageDir;
}
});
argParser.addOption('library-root',
help: 'Sets the library root directory to the specified directory.',
callback: (libraryRoot) {
if (libraryRoot != null) {
libPath = new Path(libraryRoot);
}
});
// TODO(amouravski): This method is deprecated. Remove on April 22.
argParser.addOption('pkg',
help: 'Deprecated: same as --package-root.',
callback: (packageDir) {
if(packageDir != null) {
packageRoot = packageDir;
}
});
dartdoc.dartdocPath = libPath.append('lib/_internal/dartdoc');
if (args.isEmpty) {
print('No arguments provided.');
print(USAGE);
print(argParser.getUsage());
exit(1);
}
final entrypoints = <Uri>[];
try {
final option = argParser.parse(args);
// This checks to see if the root of all entrypoints is the same.
// If it is not, then we display a warning, as package imports might fail.
var entrypointRoot;
for (final entrypoint in option.rest) {
var uri = Uri.parse(entrypoint);
if (uri.scheme == '') uri = pathToFileUri(entrypoint);
entrypoints.add(uri);
if (uri.scheme != 'file') continue;
if (entrypointRoot == null) {
entrypointRoot = path.dirname(entrypoint);
} else if (entrypointRoot != path.dirname(entrypoint)) {
print('Warning: entrypoints are at different directories. "package:"'
' imports may fail.');
}
}
} on FormatException catch (e) {
print(e.message);
print(USAGE);
print(argParser.getUsage());
exit(1);
}
if (entrypoints.isEmpty) {
print('No entrypoints provided.');
print(argParser.getUsage());
exit(1);
}
if (packageRoot == null) packageRoot = _getPackageRoot(entrypoints);
cleanOutputDirectory(dartdoc.outputDir);
// Start the analysis and documentation.
dartdoc.documentLibraries(entrypoints, libPath, packageRoot)
// Prepare the dart2js script code and copy static resources.
// TODO(amouravski): move compileScript out and pre-generate the client
// scripts. This takes a long time and the js hardly ever changes.
.then((_) => compileScript(dartdoc.mode, dartdoc.outputDir, libPath,
dartdoc.tmpPath))
.then((_) => copyDirectory(libPath.append('lib/_internal/dartdoc/static'),
dartdoc.outputDir))
.then((_) {
print(dartdoc.status);
if (dartdoc.totals == 0) {
exit(1);
}
})
.catchError((e) {
print('Error: generation failed: ${e}');
var trace = getAttachedStackTrace(e);
if (trace != null) print("StackTrace: $trace");
dartdoc.cleanup();
exit(1);
})
.whenComplete(() => dartdoc.cleanup());
}
String _getPackageRoot(List<Uri> entrypoints) {
// Check if there's a `packages` directory in the entry point directory.
var fileEntrypoint = entrypoints.firstWhere(
(entrypoint) => entrypoint.scheme == 'file',
orElse: () => null);
if (fileEntrypoint == null) return;
var script = path.normalize(path.absolute(fileUriToPath(fileEntrypoint)));
var dir = path.join(path.dirname(script), 'packages/');
if (new Directory(dir).existsSync()) return dir;
// If there is not, then check if the entrypoint is somewhere in a `lib`
// directory.
var parts = path.split(path.dirname(script));
var libDir = parts.lastIndexOf('lib');
if (libDir > 0) {
return path.join(path.joinAll(parts.take(libDir)), 'packages');
} else {
return null;
}
}