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 |