| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library docgen.generator; | |
| 6 | |
| 7 import 'dart:async'; | |
| 8 import 'dart:collection'; | |
| 9 import 'dart:convert'; | |
| 10 import 'dart:io'; | |
| 11 | |
| 12 import 'package:markdown/markdown.dart' as markdown; | |
| 13 import 'package:path/path.dart' as path; | |
| 14 | |
| 15 import 'package:compiler/compiler.dart' as api; | |
| 16 import 'package:compiler/src/filenames.dart'; | |
| 17 import 'package:compiler/src/mirrors/analyze.dart' | |
| 18 as dart2js; | |
| 19 import 'package:compiler/src/source_file_provider.dart'; | |
| 20 | |
| 21 import 'exports/dart2js_mirrors.dart' as dart2js_mirrors; | |
| 22 import 'exports/libraries.dart'; | |
| 23 import 'exports/mirrors_util.dart' as dart2js_util; | |
| 24 import 'exports/source_mirrors.dart'; | |
| 25 | |
| 26 import 'io.dart'; | |
| 27 import 'library_helpers.dart'; | |
| 28 import 'models.dart'; | |
| 29 import 'package_helpers.dart'; | |
| 30 | |
| 31 const String DEFAULT_OUTPUT_DIRECTORY = 'docs'; | |
| 32 | |
| 33 /// The directory where the output docs are generated. | |
| 34 String get outputDirectory => _outputDirectory; | |
| 35 String _outputDirectory; | |
| 36 | |
| 37 /// Library names to explicitly exclude. | |
| 38 /// | |
| 39 /// Set from the command line option | |
| 40 /// --exclude-lib. | |
| 41 List<String> _excluded; | |
| 42 | |
| 43 /// The path of the pub script. | |
| 44 String pubScript; | |
| 45 | |
| 46 /// The path of Dart binary. | |
| 47 String dartBinary; | |
| 48 | |
| 49 /// Docgen constructor initializes the link resolver for markdown parsing. | |
| 50 /// Also initializes the command line arguments. | |
| 51 /// | |
| 52 /// [packageRoot] is the packages directory of the directory being analyzed. | |
| 53 /// If [includeSdk] is `true`, then any SDK libraries explicitly imported will | |
| 54 /// also be documented. | |
| 55 /// If [parseSdk] is `true`, then all Dart SDK libraries will be documented. | |
| 56 /// This option is useful when only the SDK libraries are needed. | |
| 57 /// | |
| 58 /// Returned Future completes with true if document generation is successful. | |
| 59 Future<bool> generateDocumentation(List<String> files, {String packageRoot, | |
| 60 bool outputToYaml: true, bool includePrivate: false, bool includeSdk: false, | |
| 61 bool parseSdk: false, String introFileName: '', | |
| 62 out: DEFAULT_OUTPUT_DIRECTORY, List<String> excludeLibraries: const [], bool | |
| 63 includeDependentPackages: false, String startPage, String dartBinaryValue, | |
| 64 String pubScriptValue, bool indentJSON: false, String sdk}) { | |
| 65 _excluded = excludeLibraries; | |
| 66 dartBinary = dartBinaryValue; | |
| 67 pubScript = pubScriptValue; | |
| 68 | |
| 69 logger.onRecord.listen((record) => print(record.message)); | |
| 70 | |
| 71 _ensureOutputDirectory(out); | |
| 72 var updatedPackageRoot = _obtainPackageRoot(packageRoot, parseSdk, files); | |
| 73 | |
| 74 var requestedLibraries = _findLibrariesToDocument(files, | |
| 75 includeDependentPackages); | |
| 76 | |
| 77 var allLibraries = []..addAll(requestedLibraries); | |
| 78 if (includeSdk) { | |
| 79 allLibraries.addAll(_listSdk()); | |
| 80 } | |
| 81 | |
| 82 return getMirrorSystem(allLibraries, includePrivate, | |
| 83 packageRoot: updatedPackageRoot, parseSdk: parseSdk, sdkRoot: sdk) | |
| 84 .then((MirrorSystem mirrorSystem) { | |
| 85 if (mirrorSystem.libraries.isEmpty) { | |
| 86 throw new StateError('No library mirrors were created.'); | |
| 87 } | |
| 88 initializeTopLevelLibraries(mirrorSystem); | |
| 89 | |
| 90 var availableLibraries = mirrorSystem.libraries.values | |
| 91 .where((each) => each.uri.scheme == 'file'); | |
| 92 var availableLibrariesByPath = | |
| 93 new Map.fromIterables(availableLibraries.map((each) => each.uri), | |
| 94 availableLibraries); | |
| 95 var librariesToDocument = requestedLibraries | |
| 96 .map((each) { | |
| 97 return availableLibrariesByPath | |
| 98 .putIfAbsent(each, () => throw "Missing library $each"); | |
| 99 }).toList(); | |
| 100 librariesToDocument.addAll((includeSdk || parseSdk) ? sdkLibraries : []); | |
| 101 librariesToDocument.removeWhere((x) => _excluded.contains( | |
| 102 dart2js_util.nameOf(x))); | |
| 103 _documentLibraries(librariesToDocument, includeSdk, parseSdk, introFileName, | |
| 104 startPage, indentJSON); | |
| 105 return true; | |
| 106 }); | |
| 107 } | |
| 108 | |
| 109 | |
| 110 /// Analyzes set of libraries by getting a mirror system and triggers the | |
| 111 /// documentation of the libraries. | |
| 112 Future<MirrorSystem> getMirrorSystem(List<Uri> libraries, | |
| 113 bool includePrivate, {String packageRoot, bool parseSdk: false, | |
| 114 String sdkRoot}) { | |
| 115 if (libraries.isEmpty) throw new StateError('No Libraries.'); | |
| 116 | |
| 117 includePrivateMembers = includePrivate; | |
| 118 | |
| 119 // Finds the root of SDK library based off the location of docgen. | |
| 120 // We have two different places to look, depending if we're in a development | |
| 121 // repo or in a built SDK, either sdk or dart-sdk respectively | |
| 122 if (sdkRoot == null) { | |
| 123 var root = rootDirectory; | |
| 124 sdkRoot = path.normalize(path.absolute(path.join(root, 'sdk'))); | |
| 125 if (!new Directory(sdkRoot).existsSync()) { | |
| 126 sdkRoot = path.normalize(path.absolute(path.join(root, 'dart-sdk'))); | |
| 127 } | |
| 128 } | |
| 129 logger.info('SDK Root: ${sdkRoot}'); | |
| 130 return analyzeLibraries(libraries, sdkRoot, packageRoot: packageRoot); | |
| 131 } | |
| 132 | |
| 133 /// Writes [text] to a file in the output directory. | |
| 134 void _writeToFile(String text, String filename) { | |
| 135 if (text == null) return; | |
| 136 | |
| 137 var filePath = path.join(_outputDirectory, filename); | |
| 138 | |
| 139 var parentDir = new Directory(path.dirname(filePath)); | |
| 140 if (!parentDir.existsSync()) parentDir.createSync(recursive: true); | |
| 141 | |
| 142 try { | |
| 143 new File(filePath) | |
| 144 .writeAsStringSync(text, mode: FileMode.WRITE); | |
| 145 } on FileSystemException catch (e) { | |
| 146 print('Failed to write to the path $filePath. Do you have write ' | |
| 147 'permissions to that directory? If not, please specify a different ' | |
| 148 'output directory using the --out option.'); | |
| 149 exit(1); | |
| 150 } | |
| 151 } | |
| 152 | |
| 153 /// Resolve all the links in the introductory comments for a given library or | |
| 154 /// package as specified by [filename]. | |
| 155 String _readIntroductionFile(String fileName, bool includeSdk) { | |
| 156 var linkResolver = (name) => globalFixReference(name); | |
| 157 var defaultText = includeSdk ? _DEFAULT_SDK_INTRODUCTION : ''; | |
| 158 var introText = defaultText; | |
| 159 if (fileName.isNotEmpty) { | |
| 160 var introFile = new File(fileName); | |
| 161 introText = introFile.existsSync() ? introFile.readAsStringSync() : | |
| 162 defaultText; | |
| 163 } | |
| 164 return markdown.markdownToHtml(introText, linkResolver: linkResolver, | |
| 165 inlineSyntaxes: MARKDOWN_SYNTAXES); | |
| 166 } | |
| 167 | |
| 168 int _indexableComparer(Indexable a, Indexable b) { | |
| 169 if (a is Library && b is Library) { | |
| 170 var compare = a.packageName.compareTo(b.packageName); | |
| 171 if (compare == 0) { | |
| 172 compare = a.name.compareTo(b.name); | |
| 173 } | |
| 174 return compare; | |
| 175 } | |
| 176 | |
| 177 if (a is Library) return -1; | |
| 178 if (b is Library) return 1; | |
| 179 | |
| 180 return a.qualifiedName.compareTo(b.qualifiedName); | |
| 181 } | |
| 182 | |
| 183 /// Creates documentation for filtered libraries. | |
| 184 void _documentLibraries(List<LibraryMirror> libs, bool includeSdk, | |
| 185 bool parseSdk, String introFileName, String startPage, bool indentJson) { | |
| 186 libs.forEach((lib) { | |
| 187 // Files belonging to the SDK have a uri that begins with 'dart:'. | |
| 188 if (includeSdk || !lib.uri.toString().startsWith('dart:')) { | |
| 189 generateLibrary(lib); | |
| 190 } | |
| 191 }); | |
| 192 | |
| 193 var filteredEntities = new SplayTreeSet<Indexable>(_indexableComparer); | |
| 194 for (Indexable item in allIndexables) { | |
| 195 if (isFullChainVisible(item)) { | |
| 196 if (item is! Method || | |
| 197 (item is Method && item.methodInheritedFrom == null)) { | |
| 198 filteredEntities.add(item); | |
| 199 } | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 // Outputs a JSON file with all libraries and their preview comments. | |
| 204 // This will help the viewer know what libraries are available to read in. | |
| 205 Map<String, dynamic> libraryMap = { | |
| 206 'libraries': filteredEntities.where((e) => e is Library).map((e) => | |
| 207 e.previewMap).toList(), | |
| 208 'introduction': _readIntroductionFile(introFileName, includeSdk), | |
| 209 'filetype': 'json', | |
| 210 'sdkVersion': packageVersion(coreLibrary.mirror) | |
| 211 }; | |
| 212 | |
| 213 var encoder = new JsonEncoder.withIndent(indentJson ? ' ' : null); | |
| 214 | |
| 215 _writeOutputFiles(libraryMap, filteredEntities, startPage, encoder); | |
| 216 } | |
| 217 | |
| 218 /// Output all of the libraries and classes into json files for consumption by a | |
| 219 /// viewer. | |
| 220 void _writeOutputFiles(Map<String, dynamic> libraryMap, Iterable<Indexable> | |
| 221 filteredEntities, String startPage, JsonEncoder encoder) { | |
| 222 if (startPage != null) libraryMap['start-page'] = startPage; | |
| 223 | |
| 224 _writeToFile(encoder.convert(libraryMap), 'library_list.json'); | |
| 225 | |
| 226 // Output libraries and classes to file after all information is generated. | |
| 227 filteredEntities.where((e) => e is Class || e is Library).forEach((output) { | |
| 228 _writeIndexableToFile(output, encoder); | |
| 229 }); | |
| 230 | |
| 231 // Outputs all the qualified names documented with their type. | |
| 232 // This will help generate search results. | |
| 233 var sortedEntities = filteredEntities | |
| 234 .map((e) => '${e.qualifiedName} ${e.typeName}') | |
| 235 .toList(); | |
| 236 | |
| 237 sortedEntities.sort(); | |
| 238 | |
| 239 var buffer = new StringBuffer() | |
| 240 ..writeAll(sortedEntities, '\n') | |
| 241 ..write('\n'); | |
| 242 | |
| 243 _writeToFile(buffer.toString(), 'index.txt'); | |
| 244 | |
| 245 var index = new SplayTreeMap.fromIterable(filteredEntities, | |
| 246 key: (e) => e.qualifiedName, value: (e) => e.typeName); | |
| 247 | |
| 248 _writeToFile(encoder.convert(index), 'index.json'); | |
| 249 } | |
| 250 | |
| 251 /// Helper method to serialize the given Indexable out to a file. | |
| 252 void _writeIndexableToFile(Indexable result, JsonEncoder encoder) { | |
| 253 var outputFile = result.fileName + '.json'; | |
| 254 var output = encoder.convert(result.toMap()); | |
| 255 _writeToFile(output, outputFile); | |
| 256 } | |
| 257 | |
| 258 /// Set the location of the ouput directory, and ensure that the location is | |
| 259 /// available on the file system. | |
| 260 void _ensureOutputDirectory(String outputDirectory) { | |
| 261 _outputDirectory = outputDirectory; | |
| 262 var dir = new Directory(_outputDirectory); | |
| 263 if (dir.existsSync()) dir.deleteSync(recursive: true); | |
| 264 } | |
| 265 | |
| 266 /// Analyzes set of libraries and provides a mirror system which can be used | |
| 267 /// for static inspection of the source code. | |
| 268 Future<MirrorSystem> analyzeLibraries(List<Uri> libraries, String | |
| 269 libraryRoot, {String packageRoot}) { | |
| 270 SourceFileProvider provider = new CompilerSourceFileProvider(); | |
| 271 api.DiagnosticHandler diagnosticHandler = new FormattingDiagnosticHandler( | |
| 272 provider) | |
| 273 ..showHints = false | |
| 274 ..showWarnings = false; | |
| 275 Uri libraryUri = new Uri.file(appendSlash(libraryRoot)); | |
| 276 Uri packageUri = null; | |
| 277 if (packageRoot == null) { | |
| 278 packageRoot = Platform.packageRoot; | |
| 279 } | |
| 280 packageUri = new Uri.file(appendSlash(packageRoot)); | |
| 281 return dart2js.analyze(libraries, libraryUri, packageUri, | |
| 282 provider.readStringFromUri, diagnosticHandler, ['--preserve-comments', | |
| 283 '--categories=Client,Server'])..catchError((error) { | |
| 284 logger.severe('Error: Failed to create mirror system. '); | |
| 285 // TODO(janicejl): Use the stack trace package when bug is resolved. | |
| 286 // Currently, a string is thrown when it fails to create a mirror | |
| 287 // system, and it is not possible to use the stack trace. BUG(#11622) | |
| 288 // To avoid printing the stack trace. | |
| 289 exit(1); | |
| 290 }); | |
| 291 } | |
| 292 | |
| 293 /// For this run of docgen, determine the packageRoot value. | |
| 294 /// | |
| 295 /// If packageRoot is not explicitly passed, we examine the files we're | |
| 296 /// documenting to attempt to find a package root. | |
| 297 String _obtainPackageRoot(String packageRoot, bool parseSdk, | |
| 298 List<String> files) { | |
| 299 if (packageRoot == null && !parseSdk) { | |
| 300 var type = FileSystemEntity.typeSync(files.first); | |
| 301 if (type == FileSystemEntityType.DIRECTORY) { | |
| 302 var files2 = listDir(files.first, recursive: true); | |
| 303 // Return '' means that there was no pubspec.yaml and therefore no | |
| 304 // packageRoot. | |
| 305 packageRoot = files2.firstWhere((f) => f.endsWith( | |
| 306 '${path.separator}pubspec.yaml'), orElse: () => ''); | |
| 307 if (packageRoot != '') { | |
| 308 packageRoot = path.join(path.dirname(packageRoot), 'packages'); | |
| 309 } | |
| 310 } else if (type == FileSystemEntityType.FILE) { | |
| 311 logger.warning('WARNING: No package root defined. If Docgen fails, try ' | |
| 312 'again by setting the --package-root option.'); | |
| 313 } | |
| 314 } | |
| 315 logger.info('Package Root: ${packageRoot}'); | |
| 316 return path.normalize(path.absolute(packageRoot)); | |
| 317 } | |
| 318 | |
| 319 /// Given the user provided list of items to document, expand all directories | |
| 320 /// to document out into specific files and add any dependent packages for | |
| 321 /// documentation if desired. | |
| 322 List<Uri> _findLibrariesToDocument(List<String> args, bool | |
| 323 includeDependentPackages) { | |
| 324 if (includeDependentPackages) { | |
| 325 args.addAll(_allDependentPackageDirs(args.first)); | |
| 326 } | |
| 327 | |
| 328 var libraries = new List<Uri>(); | |
| 329 for (var arg in args) { | |
| 330 if (FileSystemEntity.typeSync(arg) == FileSystemEntityType.FILE) { | |
| 331 if (arg.endsWith('.dart')) { | |
| 332 var lib = new Uri.file(path.absolute(arg)); | |
| 333 libraries.add(lib); | |
| 334 logger.info('Added to libraries: $lib'); | |
| 335 } | |
| 336 } else { | |
| 337 libraries.addAll(_findFilesToDocumentInPackage(arg)); | |
| 338 } | |
| 339 } | |
| 340 return libraries; | |
| 341 } | |
| 342 | |
| 343 /// Given a package name, explore the directory and pull out all top level | |
| 344 /// library files in the "lib" directory to document. | |
| 345 List<Uri> _findFilesToDocumentInPackage(String packageDir) { | |
| 346 var libraries = []; | |
| 347 // To avoid anaylzing package files twice, only files with paths not | |
| 348 // containing '/packages' will be added. The only exception is if the file | |
| 349 // to analyze already has a '/package' in its path. | |
| 350 var files = listDir(packageDir, recursive: true, listDir: _packageDirList) | |
| 351 .where((f) => f.endsWith('.dart') && | |
| 352 (!f.contains('${path.separator}packages') || | |
| 353 packageDir.contains('${path.separator}packages'))) | |
| 354 .toList(); | |
| 355 | |
| 356 var packageLibDir = path.join(packageDir, 'lib'); | |
| 357 var packageLibSrcDir = path.join(packageLibDir, 'src'); | |
| 358 | |
| 359 files.forEach((String lib) { | |
| 360 // Only include libraries within the lib dir that are not in lib/src | |
| 361 if (path.isWithin(packageLibDir, lib) && | |
| 362 !path.isWithin(packageLibSrcDir, lib)) { | |
| 363 // Only add the file if it does not contain 'part of' | |
| 364 // TODO(janicejl): Remove when Issue(12406) is resolved. | |
| 365 var contents = new File(lib).readAsStringSync(); | |
| 366 | |
| 367 | |
| 368 if (contents.contains(new RegExp('\npart of ')) || | |
| 369 contents.startsWith(new RegExp('part of '))) { | |
| 370 logger.warning('Skipping part "$lib". ' | |
| 371 'Part files should be in "lib/src".'); | |
| 372 } else { | |
| 373 libraries.add(new Uri.file(path.normalize(path.absolute(lib)))); | |
| 374 logger.info('Added to libraries: $lib'); | |
| 375 } | |
| 376 } | |
| 377 }); | |
| 378 return libraries; | |
| 379 } | |
| 380 | |
| 381 /// If [dir] contains both a `lib` directory and a `pubspec.yaml` file treat | |
| 382 /// it like a package and only return the `lib` dir. | |
| 383 /// | |
| 384 /// This ensures that packages don't have non-`lib` content documented. | |
| 385 List<FileSystemEntity> _packageDirList(Directory dir) { | |
| 386 var entities = dir.listSync(); | |
| 387 | |
| 388 var pubspec = entities.firstWhere((e) => e is File && | |
| 389 path.basename(e.path) == 'pubspec.yaml', orElse: () => null); | |
| 390 | |
| 391 var libDir = entities.firstWhere((e) => e is Directory && | |
| 392 path.basename(e.path) == 'lib', orElse: () => null); | |
| 393 | |
| 394 if (pubspec != null && libDir != null) { | |
| 395 return [libDir]; | |
| 396 } else { | |
| 397 return entities; | |
| 398 } | |
| 399 } | |
| 400 | |
| 401 /// All of the directories for our dependent packages | |
| 402 /// If this is not a package, return an empty list. | |
| 403 List<String> _allDependentPackageDirs(String packageDirectory) { | |
| 404 var packageName = packageNameFor(packageDirectory); | |
| 405 if (packageName == '') return []; | |
| 406 var dependentsJson = Process.runSync(pubScript, ['list-package-dirs'], | |
| 407 workingDirectory: packageDirectory, runInShell: true); | |
| 408 if (dependentsJson.exitCode != 0) { | |
| 409 print(dependentsJson.stderr); | |
| 410 } | |
| 411 var dependents = JSON.decode(dependentsJson.stdout)['packages']; | |
| 412 return dependents != null ? dependents.values.toList() : []; | |
| 413 } | |
| 414 | |
| 415 /// For all the libraries, return a list of the libraries that are part of | |
| 416 /// the SDK. | |
| 417 List<Uri> _listSdk() { | |
| 418 var sdk = new List<Uri>(); | |
| 419 LIBRARIES.forEach((String name, LibraryInfo info) { | |
| 420 if (info.documented) { | |
| 421 sdk.add(Uri.parse('dart:$name')); | |
| 422 logger.info('Add to SDK: ${sdk.last}'); | |
| 423 } | |
| 424 }); | |
| 425 return sdk; | |
| 426 } | |
| 427 | |
| 428 /// Currently left public for testing purposes. :-/ | |
| 429 void generateLibrary(dart2js_mirrors.Dart2JsLibraryMirror library) { | |
| 430 var result = new Library(library); | |
| 431 result.updateLibraryPackage(library); | |
| 432 logger.fine('Generated library for ${result.name}'); | |
| 433 } | |
| 434 | |
| 435 /// If we can't find the SDK introduction text, which will happen if running | |
| 436 /// from a snapshot and using --parse-sdk or --include-sdk, then use this | |
| 437 /// hard-coded version. This should be updated to be consistent with the text | |
| 438 /// in docgen/doc/sdk-introduction.md | |
| 439 // TODO(alanknight): It would be better if we could resolve the references to | |
| 440 // dart:core etc. at load-time in the viewer. dartbug.com/20112 | |
| 441 const _DEFAULT_SDK_INTRODUCTION = | |
| 442 """ | |
| 443 Welcome to the Dart API reference documentation, | |
| 444 covering the official Dart API libraries. | |
| 445 Some of the most fundamental Dart libraries include: | |
| 446 | |
| 447 * [dart:core](./dart:core): | |
| 448 Core functionality such as strings, numbers, collections, errors, | |
| 449 dates, and URIs. | |
| 450 * [dart:html](./dart:html): | |
| 451 DOM manipulation for web apps. | |
| 452 * [dart:io](./dart:io): | |
| 453 I/O for command-line apps. | |
| 454 | |
| 455 Except for dart:core, you must import a library before you can use it. | |
| 456 Here's an example of importing dart:html, dart:math, and a | |
| 457 third popular library called | |
| 458 [polymer.dart](http://www.dartlang.org/polymer-dart/): | |
| 459 | |
| 460 import 'dart:html'; | |
| 461 import 'dart:math'; | |
| 462 import 'package:polymer/polymer.dart'; | |
| 463 | |
| 464 Polymer.dart is an example of a library that isn't | |
| 465 included in the Dart download, | |
| 466 but is easy to get and update using the _pub package manager_. | |
| 467 For information on finding, using, and publishing libraries (and more) | |
| 468 with pub, see | |
| 469 [pub.dartlang.org](http://pub.dartlang.org). | |
| 470 | |
| 471 The main site for learning and using Dart is | |
| 472 [www.dartlang.org](http://www.dartlang.org). | |
| 473 Check out these pages: | |
| 474 | |
| 475 * [Dart homepage](http://www.dartlang.org) | |
| 476 * [Tutorials](http://www.dartlang.org/docs/tutorials/) | |
| 477 * [Programmer's Guide](http://www.dartlang.org/docs/) | |
| 478 * [Samples](http://www.dartlang.org/samples/) | |
| 479 * [A Tour of the Dart Libraries](http://www.dartlang.org/docs/dart-up-and-runn
ing/contents/ch03.html) | |
| 480 | |
| 481 This API reference is automatically generated from the source code in the | |
| 482 [Dart project](https://code.google.com/p/dart/). | |
| 483 If you'd like to contribute to this documentation, see | |
| 484 [Contributing](https://code.google.com/p/dart/wiki/Contributing) | |
| 485 and | |
| 486 [Writing API Documentation](https://code.google.com/p/dart/wiki/WritingApiDocume
ntation). | |
| 487 """; | |
| OLD | NEW |