Chromium Code Reviews| 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 * 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; | |
|
blois
2012/11/26 21:50:44
library names should be html_to_json.
Andrei Mouravski
2012/11/26 23:24:11
Done.
| |
| 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]. | |
| 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) { | |
| 46 final jsonFile = new File.fromPath(jsonPath); | |
| 47 var writeJson = convertedJson; | |
| 48 | |
| 49 if (jsonFile.existsSync()) { | |
| 50 var writeJson = _mergeJsonAndFile(convertedJson, jsonFile); | |
| 51 } else { | |
| 52 jsonFile.createSync(); | |
| 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 }; | |
| 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 | |
| 130 * to the list of comment lines. | |
| 131 */ | |
| 132 Future<Object> _convertFile(File file) { | |
| 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(); | |
| 154 | |
| 155 if (nextLine == null) return false; | |
| 156 | |
| 157 var lineObject = {}; | |
| 158 //lineObject[nextLine] = comment; | |
|
blois
2012/11/26 20:07:43
I assume this is where duplicated lines are being
Andrei Mouravski
2012/11/26 23:24:11
Yes. I'll print a warning.
| |
| 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('///'))) { | |
| 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'); | |
| 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 returned 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 [json2] 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 | |
| 233 // Assume that json2 is more current and take its data as opposed | |
| 234 // to json1's. | |
| 235 json1[k][vk] = json2[k][vk]; | |
| 236 } else { | |
| 237 json1[k].putIfAbsent(vk, () => vv); | |
| 238 } | |
| 239 }); | |
| 240 } else { | |
| 241 json1.putIfAbsent(k, () => v); | |
| 242 } | |
| 243 }); | |
| 244 } else { | |
| 245 throw new ArgumentError('JSON objects must both be Maps'); | |
| 246 } | |
| 247 | |
| 248 // TODO(amouravski): more error handling. | |
| 249 | |
| 250 return json1; | |
| 251 } | |
| 252 | |
| 253 | |
| 254 /** | |
| 255 * Tests for equality between two lists. | |
| 256 * | |
| 257 * This checks the first level of depth, so does not work for nested lists. | |
| 258 */ | |
| 259 bool _listsEqual(List list1, List list2) { | |
| 260 return list1.every((e) => list2.contains(e)) && | |
| 261 list2.every((e) => list1.contains(e)); | |
| 262 } | |
| 263 | |
| 264 | |
| 265 /** | |
| 266 * Print JSON in a much nicer format. | |
| 267 * | |
| 268 * For example: | |
| 269 * {"foo":["bar","baz"],"boo":{"far:"faz"}} | |
| 270 * | |
| 271 * becomes: | |
| 272 * | |
| 273 * { | |
| 274 * "foo": | |
| 275 * [ | |
| 276 * "bar", | |
| 277 * "baz" | |
| 278 * ], | |
| 279 * "boo": | |
| 280 * { | |
| 281 * "far": | |
| 282 * "faz" | |
| 283 * } | |
| 284 * } | |
| 285 */ | |
| 286 String prettyPrintJson(Object json, [depth = 0]) { | |
|
blois
2012/11/26 20:07:43
Probably (unfortunately) not now, but this should
Andrei Mouravski
2012/11/26 23:24:11
Agreed.
| |
| 287 var output; | |
| 288 | |
| 289 var printSpaces = Strings.join( | |
| 290 new List<String>(depth + 1).map((e) => ''), ' '); | |
|
sra1
2012/11/26 20:36:57
Call it 'indentation'.
printSpaces looks like a ve
Andrei Mouravski
2012/11/26 23:24:11
Done.
| |
| 291 | |
| 292 if (json is List) { | |
| 293 var recursiveOutput = | |
| 294 Strings.join(json.map((e) => prettyPrintJson(e, depth + 1)), ',\n'); | |
|
blois
2012/11/26 20:07:43
StringBuffer-ize?
Andrei Mouravski
2012/11/26 23:24:11
In the interest of getting everything in, I'm putt
| |
| 295 output = '${printSpaces}[\n' | |
| 296 '${recursiveOutput}' | |
| 297 '\n${printSpaces}]'; | |
| 298 } else if (json is Map) { | |
| 299 var mapList = json.keys.map((key) => | |
| 300 '${printSpaces}${JSON.stringify(key)}:\n${prettyPrintJson(json[key], dep th + 1)}'); | |
| 301 var recursiveOutput = | |
| 302 Strings.join(mapList, ',\n'); | |
| 303 output = '${printSpaces}{\n' | |
| 304 '${recursiveOutput}' | |
| 305 '\n${printSpaces}}'; | |
| 306 } else { | |
| 307 output = '${printSpaces}${JSON.stringify(json)}'; | |
| 308 } | |
| 309 return output; | |
| 310 } | |
| OLD | NEW |