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