Index: packages/polymer/bin/new_element.dart |
diff --git a/packages/polymer/bin/new_element.dart b/packages/polymer/bin/new_element.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fe6c0f87874422da5812234c4d0029062bca7d46 |
--- /dev/null |
+++ b/packages/polymer/bin/new_element.dart |
@@ -0,0 +1,249 @@ |
+// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/// Script to create boilerplate for a Polymer element. |
+/// Produces .dart and .html files for the element. |
+/// |
+/// Run this script with pub run: |
+/// |
+/// pub run polymer:new_element element-name [-o output_dir] |
+/// |
+library polymer.bin.new_element; |
+ |
+import 'dart:io'; |
+import 'package:args/args.dart'; |
+import 'package:path/path.dart' as path show absolute, dirname, join, split; |
+import 'package:polymer/html_element_names.dart'; |
+ |
+void printUsage(ArgParser parser) { |
+ print('pub run polymer:new_element [-o output_dir] [-e super-element] ' |
+ 'element-name'); |
+ print(parser.getUsage()); |
+} |
+ |
+void main(List<String> args) { |
+ var parser = new ArgParser(allowTrailingOptions: true); |
+ |
+ parser.addOption('output-dir', abbr: 'o', help: 'Output directory'); |
+ parser.addOption('extends', |
+ abbr: 'e', |
+ help: 'Extends polymer-element or DOM element (e.g., div, span)'); |
+ parser.addFlag('help', abbr: 'h'); |
+ |
+ var options, element; |
+ try { |
+ options = parser.parse(args); |
+ if (options['help']) { |
+ printUsage(parser); |
+ return; |
+ } |
+ if (options.rest == null || options.rest.isEmpty) { |
+ throw new FormatException('No element specified'); |
+ } |
+ element = options.rest[0]; |
+ if (!_isPolymerElement(element)) { |
+ throw new FormatException('Must specify polymer-element to create.\n' |
+ 'polymer-element must be all lowercase with at least 1 hyphen.'); |
+ } |
+ } catch (e) { |
+ print('$e\n'); |
+ printUsage(parser); |
+ exitCode = 1; |
+ return; |
+ } |
+ |
+ var outputDir, startDir; |
+ |
+ var outputPath = options['output-dir']; |
+ |
+ if (outputPath == null) { |
+ if ((new File('pubspec.yaml')).existsSync()) { |
+ print('When creating elements in root directory of package, ' |
+ '-o <dir> must be specified'); |
+ exitCode = 1; |
+ return; |
+ } |
+ outputDir = (new Directory('.')).resolveSymbolicLinksSync(); |
+ } else { |
+ var outputDirLocation = new Directory(outputPath); |
+ if (!outputDirLocation.existsSync()) { |
+ outputDirLocation.createSync(recursive: true); |
+ } |
+ outputDir = (new Directory(outputPath)).resolveSymbolicLinksSync(); |
+ } |
+ |
+ var pubspecDir = _findDirWithFile(outputDir, 'pubspec.yaml'); |
+ |
+ if (pubspecDir == null) { |
+ print('Could not find pubspec.yaml when walking up from $outputDir'); |
+ exitCode = 1; |
+ return; |
+ } |
+ |
+ var length = path.split(pubspecDir).length; |
+ var distanceToPackageRoot = path.split(outputDir).length - length; |
+ |
+ // See dartbug.com/20076 for the algorithm used here. |
+ if (distanceToPackageRoot > 0) { |
+ if (path.split(outputDir)[length] == 'lib') { |
+ distanceToPackageRoot++; |
+ } else { |
+ distanceToPackageRoot--; |
+ } |
+ } |
+ |
+ var superElement = options['extends']; |
+ |
+ if ((superElement == null) || |
+ _isDOMElement(superElement) || |
+ _isPolymerElement(superElement)) { |
+ try { |
+ _createBoilerPlate( |
+ element, options['extends'], outputDir, distanceToPackageRoot); |
+ } on Exception catch (e, t) { |
+ print('Error creating files in $outputDir'); |
+ print('$e $t'); |
+ exitCode = 1; |
+ return; |
+ } |
+ } else { |
+ if (superElement.contains('-')) { |
+ print('Extending invalid element "$superElement". Polymer elements ' |
+ 'may contain only lowercase letters at least one hyphen.'); |
+ } else { |
+ print('Extending invalid element "$superElement". $superElement is not ' |
+ ' a builtin DOM type.'); |
+ } |
+ exitCode = 1; |
+ return; |
+ } |
+ |
+ return; |
+} |
+ |
+String _findDirWithFile(String dir, String filename) { |
+ while (!new File(path.join(dir, filename)).existsSync()) { |
+ var parentDir = path.dirname(dir); |
+ // If we reached root and failed to find it, bail. |
+ if (parentDir == dir) return null; |
+ dir = parentDir; |
+ } |
+ return dir; |
+} |
+ |
+bool _isDOMElement(String element) => (HTML_ELEMENT_NAMES[element] != null); |
+ |
+bool _isPolymerElement(String element) { |
+ return element.contains('-') && (element.toLowerCase() == element); |
+} |
+ |
+String _toCamelCase(String s) { |
+ return s[0].toUpperCase() + s.substring(1); |
+} |
+ |
+void _createBoilerPlate(String element, String superClass, String directory, |
+ int distanceToPackageRoot) { |
+ var segments = element.split('-'); |
+ var capitalizedName = segments.map((e) => _toCamelCase(e)).join(''); |
+ var underscoreName = element.replaceAll('-', '_'); |
+ var pathToPackages = '../' * distanceToPackageRoot; |
+ |
+ bool superClassIsPolymer = |
+ (superClass == null ? false : _isPolymerElement(superClass)); |
+ |
+ var classDeclaration = ''; |
+ var importDartHtml = ''; |
+ var polymerCreatedString = ''; |
+ var extendsElementString = ''; |
+ var shadowString = ''; |
+ |
+ if (superClass == null) { |
+ classDeclaration = '\nclass $capitalizedName extends PolymerElement {'; |
+ } else if (superClassIsPolymer) { |
+ // The element being extended is a PolymerElement. |
+ var camelSuperClass = |
+ superClass.split('-').map((e) => _toCamelCase(e)).join(''); |
+ classDeclaration = 'class $capitalizedName extends $camelSuperClass {'; |
+ extendsElementString = ' extends="$superClass"'; |
+ shadowString = '\n <!-- Render extended element\'s Shadow DOM here -->\n' |
+ ' <shadow>\n </shadow>'; |
+ } else { |
+ // The element being extended is a DOM Class. |
+ importDartHtml = "import 'dart:html';\n"; |
+ classDeclaration = |
+ 'class $capitalizedName extends ${HTML_ELEMENT_NAMES[superClass]} ' |
+ 'with Polymer, Observable {'; |
+ polymerCreatedString = '\n polymerCreated();'; |
+ extendsElementString = ' extends="$superClass"'; |
+ } |
+ |
+ String html = ''' |
+<!-- import polymer-element's definition --> |
+<link rel="import" href="${pathToPackages}packages/polymer/polymer.html"> |
+ |
+<polymer-element name="$element"$extendsElementString> |
+ <template> |
+ <style> |
+ :host { |
+ display: block; |
+ } |
+ </style>$shadowString |
+ <!-- Template content here --> |
+ </template> |
+ <script type="application/dart" src="${underscoreName}.dart"></script> |
+</polymer-element> |
+'''; |
+ |
+ String htmlFile = path.join(directory, underscoreName + '.html'); |
+ new File(htmlFile).writeAsStringSync(html); |
+ |
+ String dart = ''' |
+${importDartHtml}import 'package:polymer/polymer.dart'; |
+ |
+/** |
+ * A Polymer $element element. |
+ */ |
+@CustomTag('$element') |
+$classDeclaration |
+ |
+ /// Constructor used to create instance of ${capitalizedName}. |
+ ${capitalizedName}.created() : super.created() {$polymerCreatedString |
+ } |
+ |
+ /* |
+ * Optional lifecycle methods - uncomment if needed. |
+ * |
+ |
+ /// Called when an instance of $element is inserted into the DOM. |
+ attached() { |
+ super.attached(); |
+ } |
+ |
+ /// Called when an instance of $element is removed from the DOM. |
+ detached() { |
+ super.detached(); |
+ } |
+ |
+ /// Called when an attribute (such as a class) of an instance of |
+ /// $element is added, changed, or removed. |
+ attributeChanged(String name, String oldValue, String newValue) { |
+ } |
+ |
+ /// Called when $element has been fully prepared (Shadow DOM created, |
+ /// property observers set up, event listeners attached). |
+ ready() { |
+ } |
+ |
+ */ |
+ |
+} |
+'''; |
+ |
+ String dartFile = path.join(directory, underscoreName + '.dart'); |
+ new File(dartFile).writeAsStringSync(dart); |
+ |
+ print('Successfully created:'); |
+ print(' ' + path.absolute(path.join(directory, underscoreName + '.dart'))); |
+ print(' ' + path.absolute(path.join(directory, underscoreName + '.html'))); |
+} |