OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, 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 /** | |
6 * To generate docs for a library, run this script with the path to an | |
7 * entrypoint .dart file, like: | |
8 * | |
9 * $ dart dartdoc.dart foo.dart | |
10 * | |
11 * This will create a "docs" directory with the docs for your libraries. To | |
12 * create these beautiful docs, dartdoc parses your library and every library | |
13 * it imports (recursively). From each library, it parses all classes and | |
14 * members, finds the associated doc comments and builds crosslinked docs from | |
15 * them. | |
16 */ | |
17 library dartdoc; | |
18 | |
19 import 'dart:async'; | |
20 import 'dart:io'; | |
21 | |
22 import '../lib/dartdoc.dart'; | |
23 import '../lib/src/dartdoc/utils.dart'; | |
24 import 'package:args/args.dart'; | |
25 import 'package:path/path.dart' as path; | |
26 | |
27 /** | |
28 * Run this from the `lib/_internal/dartdoc` directory. | |
29 */ | |
30 main(List<String> arguments) { | |
31 // Need this because ArgParser.getUsage doesn't show command invocation. | |
32 final USAGE = 'Usage dartdoc [options] <entrypoint(s)>\n[options] include:'; | |
33 | |
34 final dartdoc = new Dartdoc(); | |
35 | |
36 final argParser = new ArgParser(); | |
37 | |
38 String libPath = path.join(scriptDir, '..', '..', '..', '..'); | |
39 | |
40 String packageRoot; | |
41 | |
42 argParser.addFlag('no-code', | |
43 help: 'Do not include source code in the documentation.', | |
44 defaultsTo: false, negatable: false, | |
45 callback: (noCode) => dartdoc.includeSource = !noCode); | |
46 | |
47 argParser.addOption('mode', abbr: 'm', | |
48 help: 'Define how HTML pages are generated.', | |
49 allowed: ['static', 'live-nav'], allowedHelp: { | |
50 'static': 'Generates completely static HTML containing\n' | |
51 'everything you need to browse the docs. The only\n' | |
52 'client side behavior is trivial stuff like syntax\n' | |
53 'highlighting code, and the find-as-you-type search\n' | |
54 'box.', | |
55 'live-nav': '(Default) Generated docs do not included baked HTML\n' | |
56 'navigation. Instead a single `nav.json` file is\n' | |
57 'created and the appropriate navigation is generated\n' | |
58 'client-side by parsing that and building HTML.\n' | |
59 '\tThis dramatically reduces the generated size of\n' | |
60 'the HTML since a large fraction of each static page\n' | |
61 'is just redundant navigation links.\n' | |
62 '\tIn this mode, the browser will do a XHR for\n' | |
63 'nav.json which means that to preview docs locallly,\n' | |
64 'you will need to enable requesting file:// links in\n' | |
65 'your browser or run a little local server like\n' | |
66 '`python -m SimpleHTTPServer`.'}, | |
67 defaultsTo: 'live-nav', | |
68 callback: (genMode) { | |
69 dartdoc.mode = (genMode == 'static' ? MODE_STATIC : MODE_LIVE_NAV); | |
70 }); | |
71 | |
72 argParser.addFlag('generate-app-cache', | |
73 help: 'Generates the App Cache manifest file, enabling\n' | |
74 'offline doc viewing.', | |
75 defaultsTo: false, negatable: false, | |
76 callback: (generate) => dartdoc.generateAppCache = generate); | |
77 | |
78 argParser.addFlag('omit-generation-time', | |
79 help: 'Omits generation timestamp from output.', | |
80 defaultsTo: false, negatable: false, | |
81 callback: (genTimestamp) => dartdoc.omitGenerationTime = genTimestamp); | |
82 | |
83 argParser.addFlag('verbose', abbr: 'v', | |
84 help: 'Print verbose information during generation.', | |
85 defaultsTo: false, negatable: false, | |
86 callback: (verb) => dartdoc.verbose = verb); | |
87 | |
88 argParser.addFlag('include-api', | |
89 help: 'Include the used API libraries in the generated\n' | |
90 'documentation. If the --link-api option is used,\n' | |
91 'this option is ignored.', | |
92 defaultsTo: false, negatable: false, | |
93 callback: (incApi) => dartdoc.includeApi = incApi); | |
94 | |
95 argParser.addFlag('link-api', | |
96 help: 'Link to the online language API in the generated\n' | |
97 'documentation. The option overrides inclusion\n' | |
98 'through --include-api or --include-lib.', | |
99 defaultsTo: false, negatable: false, | |
100 callback: (linkApi) => dartdoc.linkToApi = linkApi); | |
101 | |
102 argParser.addFlag('show-private', | |
103 help: 'Document private types and members.', | |
104 defaultsTo: false, | |
105 callback: (showPrivate) => dartdoc.showPrivate = showPrivate); | |
106 | |
107 argParser.addFlag('inherit-from-object', | |
108 help: 'Show members inherited from Object.', | |
109 defaultsTo: false, negatable: false, | |
110 callback: (inherit) => dartdoc.inheritFromObject = inherit); | |
111 | |
112 argParser.addFlag('enable-diagnostic-colors', negatable: false); | |
113 | |
114 argParser.addOption('out', | |
115 help: 'Generates files into directory specified. If\n' | |
116 'omitted the files are generated into ./docs/', | |
117 callback: (outDir) { | |
118 if(outDir != null) { | |
119 dartdoc.outputDir = outDir; | |
120 } | |
121 }); | |
122 | |
123 argParser.addOption('include-lib', | |
124 help: 'Use this option to explicitly specify which\n' | |
125 'libraries to include in the documentation. If\n' | |
126 'omitted, all used libraries are included by\n' | |
127 'default. Specify a comma-separated list of\n' | |
128 'library names, or call this option multiple times.', | |
129 callback: (incLibs) { | |
130 if(!incLibs.isEmpty) { | |
131 List<String> allLibs = new List<String>(); | |
132 for(final lst in incLibs) { | |
133 var someLibs = lst.split(','); | |
134 for(final lib in someLibs) { | |
135 allLibs.add(lib); | |
136 } | |
137 } | |
138 dartdoc.includedLibraries = allLibs; | |
139 } | |
140 }, allowMultiple: true); | |
141 | |
142 argParser.addOption('exclude-lib', | |
143 help: 'Use this option to explicitly specify which\n' | |
144 'libraries to exclude from the documentation. If\n' | |
145 'omitted, no libraries are excluded. Specify a\n' | |
146 'comma-separated list of library names, or call\n' | |
147 'this option multiple times.', | |
148 callback: (excLibs) { | |
149 if(!excLibs.isEmpty) { | |
150 List<String> allLibs = new List<String>(); | |
151 for(final lst in excLibs) { | |
152 var someLibs = lst.split(','); | |
153 for(final lib in someLibs) { | |
154 allLibs.add(lib); | |
155 } | |
156 } | |
157 dartdoc.excludedLibraries = allLibs; | |
158 } | |
159 }, allowMultiple: true); | |
160 | |
161 argParser.addOption('package-root', | |
162 help: 'Sets the package directory to the specified directory.\n' | |
163 'If omitted the package directory is the closest packages directory to' | |
164 ' the entrypoint.', | |
165 callback: (packageDir) { | |
166 if(packageDir != null) { | |
167 packageRoot = packageDir; | |
168 } | |
169 }); | |
170 | |
171 argParser.addOption('library-root', | |
172 help: 'Sets the library root directory to the specified directory.', | |
173 callback: (libraryRoot) { | |
174 if (libraryRoot != null) { | |
175 libPath = libraryRoot; | |
176 } | |
177 }); | |
178 | |
179 // TODO(amouravski): This method is deprecated. Remove on April 22. | |
180 argParser.addOption('pkg', | |
181 help: 'Deprecated: same as --package-root.', | |
182 callback: (packageDir) { | |
183 if(packageDir != null) { | |
184 packageRoot = packageDir; | |
185 } | |
186 }); | |
187 | |
188 dartdoc.dartdocPath = path.join(libPath, 'lib', '_internal', 'dartdoc'); | |
189 | |
190 if (arguments.isEmpty) { | |
191 print('No arguments provided.'); | |
192 print(USAGE); | |
193 print(argParser.getUsage()); | |
194 exit(1); | |
195 } | |
196 | |
197 final entrypoints = <Uri>[]; | |
198 try { | |
199 final option = argParser.parse(arguments, allowTrailingOptions: true); | |
200 | |
201 // This checks to see if the root of all entrypoints is the same. | |
202 // If it is not, then we display a warning, as package imports might fail. | |
203 var entrypointRoot; | |
204 for (final entrypoint in option.rest) { | |
205 var uri = Uri.parse(entrypoint); | |
206 | |
207 // If it looks like it was a file path (no scheme, or a one letter scheme | |
208 // which is likely a drive letter on Windows), turn it into a file URL. | |
209 if (uri.scheme == '' || uri.scheme.length == 1) { | |
210 uri = path.toUri(entrypoint); | |
211 } | |
212 | |
213 entrypoints.add(uri); | |
214 | |
215 if (uri.scheme != 'file') continue; | |
216 if (entrypointRoot == null) { | |
217 entrypointRoot = path.dirname(entrypoint); | |
218 } else if (entrypointRoot != path.dirname(entrypoint)) { | |
219 print('Warning: entrypoints are at different directories. "package:"' | |
220 ' imports may fail.'); | |
221 } | |
222 } | |
223 } on FormatException catch (e) { | |
224 print(e.message); | |
225 print(USAGE); | |
226 print(argParser.getUsage()); | |
227 exit(1); | |
228 } | |
229 | |
230 if (entrypoints.isEmpty) { | |
231 print('No entrypoints provided.'); | |
232 print(argParser.getUsage()); | |
233 exit(1); | |
234 } | |
235 | |
236 if (packageRoot == null) packageRoot = _getPackageRoot(entrypoints); | |
237 | |
238 cleanOutputDirectory(dartdoc.outputDir); | |
239 | |
240 // Start the analysis and documentation. | |
241 dartdoc.documentLibraries(entrypoints, libPath, packageRoot) | |
242 // Prepare the dart2js script code and copy static resources. | |
243 // TODO(amouravski): move compileScript out and pre-generate the client | |
244 // scripts. This takes a long time and the js hardly ever changes. | |
245 .then((_) => compileScript(dartdoc.mode, dartdoc.outputDir, libPath, | |
246 dartdoc.tmpPath)) | |
247 .then((_) => copyDirectory( | |
248 path.join(libPath, 'lib', '_internal', 'dartdoc', 'static'), | |
249 dartdoc.outputDir)) | |
250 .then((_) { | |
251 print(dartdoc.status); | |
252 if (dartdoc.totals == 0) { | |
253 exit(1); | |
254 } | |
255 }) | |
256 .catchError((e, trace) { | |
257 print('Error: generation failed: ${e}'); | |
258 if (trace != null) print("StackTrace: $trace"); | |
259 dartdoc.cleanup(); | |
260 exit(1); | |
261 }) | |
262 .whenComplete(() => dartdoc.cleanup()); | |
263 } | |
264 | |
265 String _getPackageRoot(List<Uri> entrypoints) { | |
266 // Check if there's a `packages` directory in the entry point directory. | |
267 var fileEntrypoint = entrypoints.firstWhere( | |
268 (entrypoint) => entrypoint.scheme == 'file', | |
269 orElse: () => null); | |
270 if (fileEntrypoint == null) return; | |
271 | |
272 var script = path.normalize(path.absolute(path.fromUri(fileEntrypoint))); | |
273 var dir = path.join(path.dirname(script), 'packages/'); | |
274 if (new Directory(dir).existsSync()) return dir; | |
275 | |
276 // If there is not, then check if the entrypoint is somewhere in a `lib` | |
277 // directory. | |
278 var parts = path.split(path.dirname(script)); | |
279 var libDir = parts.lastIndexOf('lib'); | |
280 if (libDir > 0) { | |
281 return path.join(path.joinAll(parts.take(libDir)), 'packages'); | |
282 } else { | |
283 return null; | |
284 } | |
285 } | |
OLD | NEW |