Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(105)

Side by Side Diff: tools/html-json-doc/lib/HtmlToJson.dart

Issue 11280133: Both halves of the HTMLDoc to JSON doc converter! (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixed bin. Created 8 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 * Library for extracting the documentation comments from files generated by
7 * the HTML library. The comments are stored in a JSON file.
8 *
9 * Comments must be in either the block style with leading *s:
10 *
11 * /**
12 * * Comment here.
13 * */
14 *
15 * Or the triple-slash style:
16 *
17 * /// Docs go here.
18 * /// And here.
19 *
20 * Each member that is to be documented should be preceeded by a meta-comment
21 * containing the string `@docsEditable` such as:
22 *
23 * /// @docsEditable
24 */
25 library htmlToJson;
26
27 import 'dart:json';
28 import 'dart:io';
29
30
31 /// True if any errors were triggered through the conversion.
32 bool _anyErrors = false;
33
34
35 /**
36 * Convert files on [htmlPath] and write JSON to [jsonPath].
Bob Nystrom 2012/11/26 22:00:19 You do a mixture of /// and /** */ style doc comme
Andrei Mouravski 2012/11/27 03:11:45 Because nothing forces me to.
37 */
38 Future<bool> convert(Path htmlPath, Path jsonPath) {
39 var completer = new Completer();
40
41 var futureConvert = _convertFiles(htmlPath);
42
43 // TODO(amouravski): make this transform once I know what I want this file to
44 // return.
45 futureConvert.then((convertedJson) {
Bob Nystrom 2012/11/26 22:00:19 You don't actually use this variable for anything
Andrei Mouravski 2012/11/27 03:11:45 Done.
46 final jsonFile = new File.fromPath(jsonPath);
47 var writeJson = convertedJson;
48
49 if (jsonFile.existsSync()) {
50 var writeJson = _mergeJsonAndFile(convertedJson, jsonFile);
Bob Nystrom 2012/11/26 22:00:19 Do you intend to shadow writeJson here?
Andrei Mouravski 2012/11/27 03:11:45 Wow. Nope.
51 } else {
52 jsonFile.createSync();
Bob Nystrom 2012/11/26 22:00:19 I don't think this is necessary. openOutputStream
Andrei Mouravski 2012/11/27 03:11:45 Done.
53 }
54
55 var outputStream = jsonFile.openOutputStream();
56 outputStream.writeString(prettyPrintJson(writeJson));
57
58 outputStream.onNoPendingWrites = () {
59 completer.complete(_anyErrors);
60 };
61
62 outputStream.onClosed = () {
63 completer.complete(_anyErrors);
64 };
Bob Nystrom 2012/11/26 22:00:19 Also add: outputStream.onError = completer.comple
Andrei Mouravski 2012/11/27 03:11:45 Done.
65 });
66
67 return completer.future;
68 }
69
70
71 /**
72 * Convert all files on [htmlPath].
73 *
74 * Returns a future that completes to the converted JSON object.
75 */
76 Future<Object> _convertFiles(Path htmlPath) {
77 var completer = new Completer();
78
79 List<Future> fileFutures = [];
80
81 // Get a list of all HTML dart files.
82 // TODO(amouravski): discriminate .dart files.
83 final htmlDir = new Directory.fromPath(htmlPath);
84 final lister = htmlDir.list(recursive: false);
85
86 lister.onFile = (String path) {
87 final name = new Path.fromNative(path).filename;
88
89 // Ignore private classes.
90 if (name.startsWith('_')) return;
91
92 // Ignore non-dart files.
93 if (!name.endsWith('.dart')) return;
94
95 File file = new File(path);
96
97 // TODO(amouravski): Handle missing file.
98 if (!file.existsSync()) {
99 print('ERROR: cannot find file $path');
100 _anyErrors = true;
101 return;
102 }
103
104 fileFutures.add(_convertFile(file));
105 };
106
107
108 // Combine all JSON objects
109 lister.onDone = (_) {
110 Futures.wait(fileFutures).then((jsonList) {
111 var convertedJson = {};
112 jsonList.forEach((json) {
113 final k = json.keys[0];
114 convertedJson.putIfAbsent(k, () => json[k]);
115 });
116 completer.complete(convertedJson);
117 });
118 };
119
120 // TODO(amouravski): add more error handling.
121
122 return completer.future;
123 }
124
125
126 /**
127 * Convert a single file to JSON docs.
128 *
129 * Output is a single object which with one element that maps the file name
Bob Nystrom 2012/11/26 22:00:19 How about "Returns a map with one entry whose key
Andrei Mouravski 2012/11/27 03:11:45 Done.
130 * to the list of comment lines.
131 */
132 Future<Object> _convertFile(File file) {
Bob Nystrom 2012/11/26 22:00:19 Future<Map>
Andrei Mouravski 2012/11/27 03:11:45 Done.
133 var completer = new Completer();
134
135 var comments = {};
136
137 // Find all /// @docsEditable annotations.
138 InputStream file_stream = file.openInputStream();
139 StringInputStream inputLines = new StringInputStream(file_stream);
140
141 inputLines.onLine = () {
142 var comment = <String>[];
143
144 var docCommentFound = false;
145 String line;
146 while ((line = inputLines.readLine()) != null) {
147 var trimmedLine = line.trim();
148
149 // Sentinel found. Process the comment block.
150 if (trimmedLine.startsWith('///') &&
151 trimmedLine.contains('@docsEditable')) {
152 if (docCommentFound == true) {
153 var nextLine = inputLines.readLine();
Bob Nystrom 2012/11/26 22:00:19 I don't think you can do this. readLine() can fail
Andrei Mouravski 2012/11/27 03:11:45 Will get back to this if possible.
154
155 if (nextLine == null) return false;
156
157 var lineObject = {};
158 //lineObject[nextLine] = comment;
159 comments.putIfAbsent(nextLine, () => comment);
160 }
161
162 // Reset.
163 docCommentFound = false;
164 comment = <String>[];
165 } else if ( // Start a comment block.
166 trimmedLine.startsWith('/**') ||
167 trimmedLine.startsWith('///')) {
168 docCommentFound = true;
169 comment.add(line);
170 } else if (docCommentFound &&
171 (trimmedLine.startsWith('*') || trimmedLine.startsWith('///'))) {
Bob Nystrom 2012/11/26 22:00:19 This would barf if you did: /// A doc comment * n
Andrei Mouravski 2012/11/27 03:11:45 I will barf, yes. In the interest of time, I'm goi
172 comment.add(line);
173 } else {
174 // Reset if we're not in a comment.
175 docCommentFound = false;
176 comment = <String>[];
177 }
178 }
179 };
180
181 inputLines.onClosed = () {
182 var jsonObject = {};
183 jsonObject[new Path(file.fullPathSync()).filename] = comments;
184 completer.complete(jsonObject);
185 };
186
187 // TODO(amouravski): better error handling.
188
189 return completer.future;
190 }
191
192
193 /**
194 * Merge the new JSON object and the existing file.
195 */
196 Object _mergeJsonAndFile(Object json, File file) {
197 var completer = new Completer();
198
199 var fileJson = {};
200 var jsonRead = Strings.join(file.readAsLinesSync(), '\n');
Bob Nystrom 2012/11/26 22:00:19 file.readAsStringSync();
Andrei Mouravski 2012/11/27 03:11:45 I wrote this before I knew that method existed. :]
201
202 if (jsonRead == '') {
203 print('WARNING: no data read from '
204 '${new Path(file.fullPathSync()).filename}');
205 _anyErrors = true;
206 } else {
207 fileJson = JSON.parse(jsonRead);
208 }
209 return _mergeJson(json, fileJson);
210 }
211
212
213 /**
214 * Merge two JSON objects, such that the next JSON object is the
215 * union of both.
216 *
217 * Each JSON must be a map, with each value being a map.
218 */
219 Object _mergeJson(Object json1, Object json2) {
220 if (json1 is Map && json2 is Map) {
221 // Then check if [json2] contains any key form [json1], in which case
222 // add all of the values from [json] to the values of [json1].
223 json2.forEach((k, v) {
224 if (json1.containsKey(k)) {
225 v.forEach((vk, vv) {
226 if (json1[k].containsKey(vk) &&
227 !_listsEqual(json1[k][vk],vv)) {
228 // TODO(amouravski): add better warning message and only if there's
229 // a conflict.
230 print('WARNING: duplicate keys.');
231 _anyErrors = true;
232 } else {
233 json1[k].putIfAbsent(vk, () => vv);
234 }
235 });
236 } else {
237 json1.putIfAbsent(k, () => v);
238 }
Bob Nystrom 2012/11/26 22:00:19 This is a bit hard for me to read. What do you thi
Andrei Mouravski 2012/11/27 03:11:45 Will return to this, too.
239 });
240 } else {
241 throw new ArgumentError('JSON objects must both be Maps');
242 }
243
244 // TODO(amouravski): more error handling.
245
246 return json1;
247 }
248
249
250 /**
251 * Tests for equality between two lists.
252 *
253 * This checks the first level of depth, so does not work for nested lists.
254 */
255 bool _listsEqual(List list1, List list2) {
256 return list1.every((e) => list2.contains(e)) &&
257 list2.every((e) => list1.contains(e));
258 }
259
260
261 /**
262 * Print JSON in a much nicer format.
263 *
264 * For example:
265 * {"foo":["bar","baz"],"boo":{"far:"faz"}}
266 *
267 * becomes:
268 *
269 * {
270 * "foo":
271 * [
272 * "bar",
273 * "baz"
274 * ],
275 * "boo":
276 * {
277 * "far":
278 * "faz"
279 * }
280 * }
281 */
282 String prettyPrintJson(Object json, [depth = 0]) {
283 var output;
284
285 var printSpaces = Strings.join(
286 new List<String>(depth + 1).map((e) => ''), ' ');
Bob Nystrom 2012/11/26 22:00:19 This is a bit gratuitiously inefficient. How about
Andrei Mouravski 2012/11/27 03:11:45 Done. Gratuitious amounts of inefficiency. Powert
287
288 if (json is List) {
289 var recursiveOutput =
290 Strings.join(json.map((e) => prettyPrintJson(e, depth + 1)), ',\n');
291 output = '${printSpaces}[\n'
Bob Nystrom 2012/11/26 22:00:19 If it's a single identifier, you can omit the {},
Andrei Mouravski 2012/11/27 03:11:45 Done.
292 '${recursiveOutput}'
293 '\n${printSpaces}]';
294 } else if (json is Map) {
295 var mapList = json.keys.map((key) =>
296 '${printSpaces}"${key}":\n${prettyPrintJson(json[key], depth + 1)}');
Bob Nystrom 2012/11/26 22:00:19 Nit, but how about getting rid of the newline afte
Andrei Mouravski 2012/11/27 03:11:45 I'd like to, but that'd mean editing a lot of file
297 var recursiveOutput =
298 Strings.join(mapList, ',\n');
Bob Nystrom 2012/11/26 22:00:19 Nit: this can fit on one line.
Andrei Mouravski 2012/11/27 03:11:45 At one point it didn't.
299 output = '${printSpaces}{\n'
300 '${recursiveOutput}'
301 '\n${printSpaces}}';
302 } else {
303 output = '${printSpaces}"${json}"';
304 }
305 return output;
306 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698