| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Tools for code generation. | 6 * Tools for code generation. |
| 7 */ | 7 */ |
| 8 library codegen.tools; | 8 library codegen.tools; |
| 9 | 9 |
| 10 import 'dart:io'; | 10 import 'dart:io'; |
| 11 | 11 |
| 12 import 'package:html5lib/dom.dart' as dom; | 12 import 'package:html5lib/dom.dart' as dom; |
| 13 import 'package:path/path.dart'; | 13 import 'package:path/path.dart'; |
| 14 | 14 |
| 15 import 'html_tools.dart'; |
| 15 import 'text_formatter.dart'; | 16 import 'text_formatter.dart'; |
| 16 import 'html_tools.dart'; | 17 |
| 18 final RegExp trailingWhitespaceRegExp = new RegExp(r' +$', multiLine: true); |
| 17 | 19 |
| 18 /** | 20 /** |
| 19 * Join the given strings using camelCase. If [doCapitalize] is true, the first | 21 * Join the given strings using camelCase. If [doCapitalize] is true, the first |
| 20 * part will be capitalized as well. | 22 * part will be capitalized as well. |
| 21 */ | 23 */ |
| 22 String camelJoin(List<String> parts, {bool doCapitalize: false}) { | 24 String camelJoin(List<String> parts, {bool doCapitalize: false}) { |
| 23 List<String> upcasedParts = <String>[]; | 25 List<String> upcasedParts = <String>[]; |
| 24 for (int i = 0; i < parts.length; i++) { | 26 for (int i = 0; i < parts.length; i++) { |
| 25 if (i == 0 && !doCapitalize) { | 27 if (i == 0 && !doCapitalize) { |
| 26 upcasedParts.add(parts[i]); | 28 upcasedParts.add(parts[i]); |
| 27 } else { | 29 } else { |
| 28 upcasedParts.add(capitalize(parts[i])); | 30 upcasedParts.add(capitalize(parts[i])); |
| 29 } | 31 } |
| 30 } | 32 } |
| 31 return upcasedParts.join(); | 33 return upcasedParts.join(); |
| 32 } | 34 } |
| 33 | 35 |
| 34 /** | 36 /** |
| 35 * Capitalize and return the passed String. | 37 * Capitalize and return the passed String. |
| 36 */ | 38 */ |
| 37 String capitalize(String string) { | 39 String capitalize(String string) { |
| 38 return string[0].toUpperCase() + string.substring(1); | 40 return string[0].toUpperCase() + string.substring(1); |
| 39 } | 41 } |
| 40 | 42 |
| 41 final RegExp trailingWhitespaceRegExp = new RegExp(r' +$', multiLine: true); | 43 /** |
| 44 * Type of functions used to compute the contents of a set of generated files. |
| 45 */ |
| 46 typedef Map<String, FileContentsComputer> DirectoryContentsComputer(); |
| 47 |
| 48 /** |
| 49 * Type of functions used to compute the contents of a generated file. |
| 50 */ |
| 51 typedef String FileContentsComputer(); |
| 42 | 52 |
| 43 /** | 53 /** |
| 44 * Mixin class for generating code. | 54 * Mixin class for generating code. |
| 45 */ | 55 */ |
| 46 class CodeGenerator { | 56 class CodeGenerator { |
| 47 _CodeGeneratorState _state; | 57 _CodeGeneratorState _state; |
| 48 | 58 |
| 49 /** | 59 /** |
| 60 * Measure the width of the current indentation level. |
| 61 */ |
| 62 int get indentWidth => _state.nextIndent.length; |
| 63 |
| 64 /** |
| 50 * Execute [callback], collecting any code that is output using [write] | 65 * Execute [callback], collecting any code that is output using [write] |
| 51 * or [writeln], and return the result as a string. | 66 * or [writeln], and return the result as a string. |
| 52 */ | 67 */ |
| 53 String collectCode(void callback()) { | 68 String collectCode(void callback()) { |
| 54 _CodeGeneratorState oldState = _state; | 69 _CodeGeneratorState oldState = _state; |
| 55 try { | 70 try { |
| 56 _state = new _CodeGeneratorState(); | 71 _state = new _CodeGeneratorState(); |
| 57 callback(); | 72 callback(); |
| 58 return _state.buffer.toString().replaceAll(trailingWhitespaceRegExp, ''); | 73 return _state.buffer.toString().replaceAll(trailingWhitespaceRegExp, ''); |
| 59 } finally { | 74 } finally { |
| 60 _state = oldState; | 75 _state = oldState; |
| 61 } | 76 } |
| 62 } | 77 } |
| 63 | 78 |
| 64 /** | 79 /** |
| 65 * Output text without ending the current line. | 80 * Generate a doc comment based on the HTML in [docs]. |
| 81 * |
| 82 * If [javadocStyle] is true, then the output is compatable with Javadoc, |
| 83 * which understands certain HTML constructs. |
| 66 */ | 84 */ |
| 67 void write(Object obj) { | 85 void docComment(List<dom.Node> docs, {int width: 79, bool javadocStyle: |
| 68 _state.write(obj.toString()); | 86 false}) { |
| 87 if (containsOnlyWhitespace(docs)) { |
| 88 return; |
| 89 } |
| 90 writeln('/**'); |
| 91 indentBy(' * ', () { |
| 92 write(nodesToText(docs, width - _state.indent.length, javadocStyle)); |
| 93 }); |
| 94 writeln(' */'); |
| 69 } | 95 } |
| 70 | 96 |
| 71 /** | 97 /** |
| 72 * Output text, ending the current line. | |
| 73 */ | |
| 74 void writeln([Object obj = '']) { | |
| 75 _state.write('$obj\n'); | |
| 76 } | |
| 77 | |
| 78 /** | |
| 79 * Execute [callback], indenting any code it outputs by two spaces. | 98 * Execute [callback], indenting any code it outputs by two spaces. |
| 80 */ | 99 */ |
| 81 void indent(void callback()) => indentSpecial(' ', ' ', callback); | 100 void indent(void callback()) => indentSpecial(' ', ' ', callback); |
| 82 | 101 |
| 83 /** | 102 /** |
| 84 * Execute [callback], using [additionalIndent] to indent any code it outputs. | 103 * Execute [callback], using [additionalIndent] to indent any code it outputs. |
| 85 */ | 104 */ |
| 86 void indentBy(String additionalIndent, void callback()) => | 105 void indentBy(String additionalIndent, void callback()) => |
| 87 indentSpecial(additionalIndent, additionalIndent, callback); | 106 indentSpecial(additionalIndent, additionalIndent, callback); |
| 88 | 107 |
| 89 /** | 108 /** |
| 90 * Execute [callback], using [additionalIndent] to indent any code it outputs. | 109 * Execute [callback], using [additionalIndent] to indent any code it outputs. |
| 91 * The first line of output is indented by [firstAdditionalIndent] instead of | 110 * The first line of output is indented by [firstAdditionalIndent] instead of |
| 92 * [additionalIndent]. | 111 * [additionalIndent]. |
| 93 */ | 112 */ |
| 94 void indentSpecial(String firstAdditionalIndent, String additionalIndent, void | 113 void indentSpecial(String firstAdditionalIndent, String additionalIndent, void |
| 95 callback()) { | 114 callback()) { |
| 96 String oldNextIndent = _state.nextIndent; | 115 String oldNextIndent = _state.nextIndent; |
| 97 String oldIndent = _state.indent; | 116 String oldIndent = _state.indent; |
| 98 try { | 117 try { |
| 99 _state.nextIndent += firstAdditionalIndent; | 118 _state.nextIndent += firstAdditionalIndent; |
| 100 _state.indent += additionalIndent; | 119 _state.indent += additionalIndent; |
| 101 callback(); | 120 callback(); |
| 102 } finally { | 121 } finally { |
| 103 _state.nextIndent = oldNextIndent; | 122 _state.nextIndent = oldNextIndent; |
| 104 _state.indent = oldIndent; | 123 _state.indent = oldIndent; |
| 105 } | 124 } |
| 106 } | 125 } |
| 107 | 126 |
| 108 /** | |
| 109 * Measure the width of the current indentation level. | |
| 110 */ | |
| 111 int get indentWidth => _state.nextIndent.length; | |
| 112 | |
| 113 /** | |
| 114 * Generate a doc comment based on the HTML in [docs]. | |
| 115 * | |
| 116 * If [javadocStyle] is true, then the output is compatable with Javadoc, | |
| 117 * which understands certain HTML constructs. | |
| 118 */ | |
| 119 void docComment(List<dom.Node> docs, {int width: 79, bool javadocStyle: | |
| 120 false}) { | |
| 121 if (containsOnlyWhitespace(docs)) { | |
| 122 return; | |
| 123 } | |
| 124 writeln('/**'); | |
| 125 indentBy(' * ', () { | |
| 126 write(nodesToText(docs, width - _state.indent.length, javadocStyle)); | |
| 127 }); | |
| 128 writeln(' */'); | |
| 129 } | |
| 130 | |
| 131 void outputHeader({bool javaStyle: false}) { | 127 void outputHeader({bool javaStyle: false}) { |
| 132 String header; | 128 String header; |
| 133 if (javaStyle) { | 129 if (javaStyle) { |
| 134 header = ''' | 130 header = ''' |
| 135 /* | 131 /* |
| 136 * Copyright (c) 2014, the Dart project authors. | 132 * Copyright (c) 2014, the Dart project authors. |
| 137 * | 133 * |
| 138 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not u
se this file except | 134 * Licensed under the Eclipse Public License v1.0 (the "License"); you may not u
se this file except |
| 139 * in compliance with the License. You may obtain a copy of the License at | 135 * in compliance with the License. You may obtain a copy of the License at |
| 140 * | 136 * |
| (...skipping 13 matching lines...) Expand all Loading... |
| 154 // for details. All rights reserved. Use of this source code is governed by a | 150 // for details. All rights reserved. Use of this source code is governed by a |
| 155 // BSD-style license that can be found in the LICENSE file. | 151 // BSD-style license that can be found in the LICENSE file. |
| 156 // | 152 // |
| 157 // This file has been automatically generated. Please do not edit it manually. | 153 // This file has been automatically generated. Please do not edit it manually. |
| 158 // To regenerate the file, use the script | 154 // To regenerate the file, use the script |
| 159 // "pkg/analysis_server/tool/spec/generate_files". | 155 // "pkg/analysis_server/tool/spec/generate_files". |
| 160 '''; | 156 '''; |
| 161 } | 157 } |
| 162 writeln(header.trim()); | 158 writeln(header.trim()); |
| 163 } | 159 } |
| 164 } | |
| 165 | |
| 166 /** | |
| 167 * State used by [CodeGenerator]. | |
| 168 */ | |
| 169 class _CodeGeneratorState { | |
| 170 StringBuffer buffer = new StringBuffer(); | |
| 171 String nextIndent = ''; | |
| 172 String indent = ''; | |
| 173 bool indentNeeded = true; | |
| 174 | |
| 175 void write(String text) { | |
| 176 List<String> lines = text.split('\n'); | |
| 177 for (int i = 0; i < lines.length; i++) { | |
| 178 if (i == lines.length - 1 && lines[i].isEmpty) { | |
| 179 break; | |
| 180 } | |
| 181 if (indentNeeded) { | |
| 182 buffer.write(nextIndent); | |
| 183 nextIndent = indent; | |
| 184 } | |
| 185 indentNeeded = false; | |
| 186 buffer.write(lines[i]); | |
| 187 if (i != lines.length - 1) { | |
| 188 buffer.writeln(); | |
| 189 indentNeeded = true; | |
| 190 } | |
| 191 } | |
| 192 } | |
| 193 } | |
| 194 | |
| 195 /** | |
| 196 * Mixin class for generating HTML representations of code that are suitable | |
| 197 * for enclosing inside a <pre> element. | |
| 198 */ | |
| 199 abstract class HtmlCodeGenerator { | |
| 200 _HtmlCodeGeneratorState _state; | |
| 201 | |
| 202 /** | |
| 203 * Execute [callback], collecting any code that is output using [write], | |
| 204 * [writeln], [add], or [addAll], and return the result as a list of DOM | |
| 205 * nodes. | |
| 206 */ | |
| 207 List<dom.Node> collectHtml(void callback()) { | |
| 208 _HtmlCodeGeneratorState oldState = _state; | |
| 209 try { | |
| 210 _state = new _HtmlCodeGeneratorState(); | |
| 211 if (callback != null) { | |
| 212 callback(); | |
| 213 } | |
| 214 return _state.buffer; | |
| 215 } finally { | |
| 216 _state = oldState; | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 /** | |
| 221 * Add the given [node] to the HTML output. | |
| 222 */ | |
| 223 void add(dom.Node node) { | |
| 224 _state.add(node); | |
| 225 } | |
| 226 | |
| 227 /** | |
| 228 * Add the given [nodes] to the HTML output. | |
| 229 */ | |
| 230 void addAll(Iterable<dom.Node> nodes) { | |
| 231 for (dom.Node node in nodes) { | |
| 232 _state.add(node); | |
| 233 } | |
| 234 } | |
| 235 | 160 |
| 236 /** | 161 /** |
| 237 * Output text without ending the current line. | 162 * Output text without ending the current line. |
| 238 */ | 163 */ |
| 239 void write(Object obj) { | 164 void write(Object obj) { |
| 240 _state.write(obj.toString()); | 165 _state.write(obj.toString()); |
| 241 } | 166 } |
| 242 | 167 |
| 243 /** | 168 /** |
| 244 * Output text, ending the current line. | 169 * Output text, ending the current line. |
| 245 */ | 170 */ |
| 246 void writeln([Object obj = '']) { | 171 void writeln([Object obj = '']) { |
| 247 _state.write('$obj\n'); | 172 _state.write('$obj\n'); |
| 248 } | 173 } |
| 249 | |
| 250 /** | |
| 251 * Execute [callback], indenting any code it outputs by two spaces. | |
| 252 */ | |
| 253 void indent(void callback()) { | |
| 254 String oldIndent = _state.indent; | |
| 255 try { | |
| 256 _state.indent += ' '; | |
| 257 callback(); | |
| 258 } finally { | |
| 259 _state.indent = oldIndent; | |
| 260 } | |
| 261 } | |
| 262 | |
| 263 /** | |
| 264 * Execute [callback], wrapping its output in an element with the given | |
| 265 * [name] and [attributes]. | |
| 266 */ | |
| 267 void element(String name, Map<String, String> attributes, [void callback()]) { | |
| 268 add(makeElement(name, attributes, collectHtml(callback))); | |
| 269 } | |
| 270 } | 174 } |
| 271 | 175 |
| 272 /** | |
| 273 * State used by [HtmlCodeGenerator]. | |
| 274 */ | |
| 275 class _HtmlCodeGeneratorState { | |
| 276 List<dom.Node> buffer = <dom.Node>[]; | |
| 277 String indent = ''; | |
| 278 bool indentNeeded = true; | |
| 279 | |
| 280 void add(dom.Node node) { | |
| 281 if (node is dom.Text) { | |
| 282 write(node.text); | |
| 283 } else { | |
| 284 buffer.add(node); | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 void write(String text) { | |
| 289 if (text.isEmpty) { | |
| 290 return; | |
| 291 } | |
| 292 if (indentNeeded) { | |
| 293 buffer.add(new dom.Text(indent)); | |
| 294 } | |
| 295 List<String> lines = text.split('\n'); | |
| 296 if (lines.last.isEmpty) { | |
| 297 lines.removeLast(); | |
| 298 buffer.add(new dom.Text(lines.join('\n$indent') + '\n')); | |
| 299 indentNeeded = true; | |
| 300 } else { | |
| 301 buffer.add(new dom.Text(lines.join('\n$indent'))); | |
| 302 indentNeeded = false; | |
| 303 } | |
| 304 } | |
| 305 } | |
| 306 | |
| 307 /** | |
| 308 * Type of functions used to compute the contents of a generated file. | |
| 309 */ | |
| 310 typedef String FileContentsComputer(); | |
| 311 | |
| 312 /** | |
| 313 * Type of functions used to compute the contents of a set of generated files. | |
| 314 */ | |
| 315 typedef Map<String, FileContentsComputer> DirectoryContentsComputer(); | |
| 316 | |
| 317 abstract class GeneratedContent { | 176 abstract class GeneratedContent { |
| 318 FileSystemEntity get outputFile; | 177 FileSystemEntity get outputFile; |
| 319 bool check(); | 178 bool check(); |
| 320 void generate(); | 179 void generate(); |
| 321 } | 180 } |
| 322 | 181 |
| 323 /** | 182 /** |
| 183 * Class representing a single output directory (either generated code or |
| 184 * generated HTML). No other content should exisit in the directory. |
| 185 */ |
| 186 class GeneratedDirectory extends GeneratedContent { |
| 187 |
| 188 /** |
| 189 * The path to the directory that will have the generated content. |
| 190 */ |
| 191 final String outputDirPath; |
| 192 |
| 193 /** |
| 194 * Callback function which computes the directory contents. |
| 195 */ |
| 196 final DirectoryContentsComputer directoryContentsComputer; |
| 197 |
| 198 GeneratedDirectory(this.outputDirPath, this.directoryContentsComputer); |
| 199 |
| 200 /** |
| 201 * Get a Directory object representing the output directory. |
| 202 */ |
| 203 Directory get outputFile => |
| 204 new Directory(joinAll(posix.split(outputDirPath))); |
| 205 |
| 206 /** |
| 207 * Check whether the directory has the correct contents, and return true if it |
| 208 * does. |
| 209 */ |
| 210 @override |
| 211 bool check() { |
| 212 Map<String, FileContentsComputer> map = directoryContentsComputer(); |
| 213 try { |
| 214 map.forEach((String file, FileContentsComputer fileContentsComputer) { |
| 215 String expectedContents = fileContentsComputer(); |
| 216 File outputFile = |
| 217 new File(joinAll(posix.split(posix.join(outputDirPath, file)))); |
| 218 if (expectedContents != outputFile.readAsStringSync()) { |
| 219 return false; |
| 220 } |
| 221 }); |
| 222 int nonHiddenFileCount = 0; |
| 223 outputFile.listSync( |
| 224 recursive: false, |
| 225 followLinks: false).forEach((FileSystemEntity fileSystemEntity) { |
| 226 if (fileSystemEntity is File && |
| 227 !basename(fileSystemEntity.path).startsWith('.')) { |
| 228 nonHiddenFileCount++; |
| 229 } |
| 230 }); |
| 231 if (nonHiddenFileCount != map.length) { |
| 232 // The number of files generated doesn't match the number we expected to |
| 233 // generate. |
| 234 return false; |
| 235 } |
| 236 } catch (e) { |
| 237 // There was a problem reading the file (most likely because it didn't |
| 238 // exist). Treat that the same as if the file doesn't have the expected |
| 239 // contents. |
| 240 return false; |
| 241 } |
| 242 return true; |
| 243 } |
| 244 |
| 245 /** |
| 246 * Replace the directory with the correct contents. [spec] is the "tool/spec" |
| 247 * directory. If [spec] is unspecified, it is assumed to be the directory |
| 248 * containing Platform.executable. |
| 249 */ |
| 250 @override |
| 251 void generate() { |
| 252 try { |
| 253 // delete the contents of the directory (and the directory itself) |
| 254 outputFile.deleteSync(recursive: true); |
| 255 } catch (e) { |
| 256 // Error caught while trying to delete the directory, this can happen if |
| 257 // it didn't yet exist. |
| 258 } |
| 259 // re-create the empty directory |
| 260 outputFile.createSync(recursive: true); |
| 261 |
| 262 // generate all of the files in the directory |
| 263 Map<String, FileContentsComputer> map = directoryContentsComputer(); |
| 264 map.forEach((String file, FileContentsComputer fileContentsComputer) { |
| 265 File outputFile = new File(joinAll(posix.split(outputDirPath + file))); |
| 266 outputFile.writeAsStringSync(fileContentsComputer()); |
| 267 }); |
| 268 } |
| 269 } |
| 270 |
| 271 /** |
| 324 * Class representing a single output file (either generated code or generated | 272 * Class representing a single output file (either generated code or generated |
| 325 * HTML). | 273 * HTML). |
| 326 */ | 274 */ |
| 327 class GeneratedFile extends GeneratedContent { | 275 class GeneratedFile extends GeneratedContent { |
| 328 /** | 276 /** |
| 329 * The output file to which generated output should be written, relative to | 277 * The output file to which generated output should be written, relative to |
| 330 * the "tool/spec" directory. This filename uses the posix path separator | 278 * the "tool/spec" directory. This filename uses the posix path separator |
| 331 * ('/') regardless of the OS. | 279 * ('/') regardless of the OS. |
| 332 */ | 280 */ |
| 333 final String outputPath; | 281 final String outputPath; |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 365 * Replace the file with the correct contents. [spec] is the "tool/spec" | 313 * Replace the file with the correct contents. [spec] is the "tool/spec" |
| 366 * directory. If [spec] is unspecified, it is assumed to be the directory | 314 * directory. If [spec] is unspecified, it is assumed to be the directory |
| 367 * containing Platform.executable. | 315 * containing Platform.executable. |
| 368 */ | 316 */ |
| 369 void generate() { | 317 void generate() { |
| 370 outputFile.writeAsStringSync(computeContents()); | 318 outputFile.writeAsStringSync(computeContents()); |
| 371 } | 319 } |
| 372 } | 320 } |
| 373 | 321 |
| 374 /** | 322 /** |
| 375 * Class representing a single output directory (either generated code or | 323 * Mixin class for generating HTML representations of code that are suitable |
| 376 * generated HTML). No other content should exisit in the directory. | 324 * for enclosing inside a <pre> element. |
| 377 */ | 325 */ |
| 378 class GeneratedDirectory extends GeneratedContent { | 326 abstract class HtmlCodeGenerator { |
| 327 _HtmlCodeGeneratorState _state; |
| 379 | 328 |
| 380 /** | 329 /** |
| 381 * The path to the directory that will have the generated content. | 330 * Add the given [node] to the HTML output. |
| 382 */ | 331 */ |
| 383 final String outputDirPath; | 332 void add(dom.Node node) { |
| 384 | 333 _state.add(node); |
| 385 /** | |
| 386 * Callback function which computes the directory contents. | |
| 387 */ | |
| 388 final DirectoryContentsComputer directoryContentsComputer; | |
| 389 | |
| 390 GeneratedDirectory(this.outputDirPath, this.directoryContentsComputer); | |
| 391 | |
| 392 /** | |
| 393 * Get a Directory object representing the output directory. | |
| 394 */ | |
| 395 Directory get outputFile => | |
| 396 new Directory(joinAll(posix.split(outputDirPath))); | |
| 397 | |
| 398 /** | |
| 399 * Check whether the directory has the correct contents, and return true if it | |
| 400 * does. | |
| 401 */ | |
| 402 @override | |
| 403 bool check() { | |
| 404 Map<String, FileContentsComputer> map = directoryContentsComputer(); | |
| 405 try { | |
| 406 map.forEach((String file, FileContentsComputer fileContentsComputer) { | |
| 407 String expectedContents = fileContentsComputer(); | |
| 408 File outputFile = | |
| 409 new File(joinAll(posix.split(posix.join(outputDirPath, file)))); | |
| 410 if (expectedContents != outputFile.readAsStringSync()) { | |
| 411 return false; | |
| 412 } | |
| 413 }); | |
| 414 int nonHiddenFileCount = 0; | |
| 415 outputFile.listSync( | |
| 416 recursive: false, | |
| 417 followLinks: false).forEach((FileSystemEntity fileSystemEntity) { | |
| 418 if(fileSystemEntity is File && !basename(fileSystemEntity.path).startsW
ith('.')) { | |
| 419 nonHiddenFileCount++; | |
| 420 } | |
| 421 }); | |
| 422 if (nonHiddenFileCount != map.length) { | |
| 423 // The number of files generated doesn't match the number we expected to | |
| 424 // generate. | |
| 425 return false; | |
| 426 } | |
| 427 } catch (e) { | |
| 428 // There was a problem reading the file (most likely because it didn't | |
| 429 // exist). Treat that the same as if the file doesn't have the expected | |
| 430 // contents. | |
| 431 return false; | |
| 432 } | |
| 433 return true; | |
| 434 } | 334 } |
| 435 | 335 |
| 436 /** | 336 /** |
| 437 * Replace the directory with the correct contents. [spec] is the "tool/spec" | 337 * Add the given [nodes] to the HTML output. |
| 438 * directory. If [spec] is unspecified, it is assumed to be the directory | |
| 439 * containing Platform.executable. | |
| 440 */ | 338 */ |
| 441 @override | 339 void addAll(Iterable<dom.Node> nodes) { |
| 442 void generate() { | 340 for (dom.Node node in nodes) { |
| 341 _state.add(node); |
| 342 } |
| 343 } |
| 344 |
| 345 /** |
| 346 * Execute [callback], collecting any code that is output using [write], |
| 347 * [writeln], [add], or [addAll], and return the result as a list of DOM |
| 348 * nodes. |
| 349 */ |
| 350 List<dom.Node> collectHtml(void callback()) { |
| 351 _HtmlCodeGeneratorState oldState = _state; |
| 443 try { | 352 try { |
| 444 // delete the contents of the directory (and the directory itself) | 353 _state = new _HtmlCodeGeneratorState(); |
| 445 outputFile.deleteSync(recursive: true); | 354 if (callback != null) { |
| 446 } catch (e) { | 355 callback(); |
| 447 // Error caught while trying to delete the directory, this can happen if | 356 } |
| 448 // it didn't yet exist. | 357 return _state.buffer; |
| 358 } finally { |
| 359 _state = oldState; |
| 449 } | 360 } |
| 450 // re-create the empty directory | 361 } |
| 451 outputFile.createSync(recursive: true); | |
| 452 | 362 |
| 453 // generate all of the files in the directory | 363 /** |
| 454 Map<String, FileContentsComputer> map = directoryContentsComputer(); | 364 * Execute [callback], wrapping its output in an element with the given |
| 455 map.forEach((String file, FileContentsComputer fileContentsComputer) { | 365 * [name] and [attributes]. |
| 456 File outputFile = new File(joinAll(posix.split(outputDirPath + file))); | 366 */ |
| 457 outputFile.writeAsStringSync(fileContentsComputer()); | 367 void element(String name, Map<String, String> attributes, [void callback()]) { |
| 458 }); | 368 add(makeElement(name, attributes, collectHtml(callback))); |
| 369 } |
| 370 |
| 371 /** |
| 372 * Execute [callback], indenting any code it outputs by two spaces. |
| 373 */ |
| 374 void indent(void callback()) { |
| 375 String oldIndent = _state.indent; |
| 376 try { |
| 377 _state.indent += ' '; |
| 378 callback(); |
| 379 } finally { |
| 380 _state.indent = oldIndent; |
| 381 } |
| 382 } |
| 383 |
| 384 /** |
| 385 * Output text without ending the current line. |
| 386 */ |
| 387 void write(Object obj) { |
| 388 _state.write(obj.toString()); |
| 389 } |
| 390 |
| 391 /** |
| 392 * Output text, ending the current line. |
| 393 */ |
| 394 void writeln([Object obj = '']) { |
| 395 _state.write('$obj\n'); |
| 459 } | 396 } |
| 460 } | 397 } |
| 398 |
| 399 /** |
| 400 * State used by [CodeGenerator]. |
| 401 */ |
| 402 class _CodeGeneratorState { |
| 403 StringBuffer buffer = new StringBuffer(); |
| 404 String nextIndent = ''; |
| 405 String indent = ''; |
| 406 bool indentNeeded = true; |
| 407 |
| 408 void write(String text) { |
| 409 List<String> lines = text.split('\n'); |
| 410 for (int i = 0; i < lines.length; i++) { |
| 411 if (i == lines.length - 1 && lines[i].isEmpty) { |
| 412 break; |
| 413 } |
| 414 if (indentNeeded) { |
| 415 buffer.write(nextIndent); |
| 416 nextIndent = indent; |
| 417 } |
| 418 indentNeeded = false; |
| 419 buffer.write(lines[i]); |
| 420 if (i != lines.length - 1) { |
| 421 buffer.writeln(); |
| 422 indentNeeded = true; |
| 423 } |
| 424 } |
| 425 } |
| 426 } |
| 427 |
| 428 /** |
| 429 * State used by [HtmlCodeGenerator]. |
| 430 */ |
| 431 class _HtmlCodeGeneratorState { |
| 432 List<dom.Node> buffer = <dom.Node>[]; |
| 433 String indent = ''; |
| 434 bool indentNeeded = true; |
| 435 |
| 436 void add(dom.Node node) { |
| 437 if (node is dom.Text) { |
| 438 write(node.text); |
| 439 } else { |
| 440 buffer.add(node); |
| 441 } |
| 442 } |
| 443 |
| 444 void write(String text) { |
| 445 if (text.isEmpty) { |
| 446 return; |
| 447 } |
| 448 if (indentNeeded) { |
| 449 buffer.add(new dom.Text(indent)); |
| 450 } |
| 451 List<String> lines = text.split('\n'); |
| 452 if (lines.last.isEmpty) { |
| 453 lines.removeLast(); |
| 454 buffer.add(new dom.Text(lines.join('\n$indent') + '\n')); |
| 455 indentNeeded = true; |
| 456 } else { |
| 457 buffer.add(new dom.Text(lines.join('\n$indent'))); |
| 458 indentNeeded = false; |
| 459 } |
| 460 } |
| 461 } |
| OLD | NEW |