Revision: 6344 Author: [email protected] Date: Sat Oct 10 13:50:44 2009 Log: Add CssResource interface generator.
Patch by: bobv Review by: rjrjr http://code.google.com/p/google-web-toolkit/source/detail?r=6344 Added: /trunk/user/src/com/google/gwt/resources/css/InterfaceGenerator.java Modified: /trunk/doc/build.xml /trunk/user/src/com/google/gwt/resources/client/CssResource.java /trunk/user/src/com/google/gwt/resources/css/GenerateCssAst.java /trunk/user/src/com/google/gwt/resources/css/Minify.java /trunk/user/src/com/google/gwt/resources/css/ast/CssSelector.java /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java /trunk/user/test/com/google/gwt/resources/client/CSSResourceTest.java /trunk/user/test/com/google/gwt/resources/client/test.css /trunk/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java /trunk/user/test/com/google/gwt/resources/rg/CssTestCase.java ======================================= --- /dev/null +++ /trunk/user/src/com/google/gwt/resources/css/InterfaceGenerator.java Sat Oct 10 13:50:44 2009 @@ -0,0 +1,262 @@ +/* + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.resources.css; + +import com.google.gwt.core.ext.Generator; +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.dev.util.log.PrintWriterTreeLogger; +import com.google.gwt.resources.client.CssResource; +import com.google.gwt.resources.client.CssResource.ClassName; +import com.google.gwt.resources.css.ast.CssStylesheet; +import com.google.gwt.user.rebind.SourceWriter; +import com.google.gwt.user.rebind.StringSourceWriter; +import com.google.gwt.util.tools.ArgHandlerFile; +import com.google.gwt.util.tools.ArgHandlerFlag; +import com.google.gwt.util.tools.ArgHandlerString; +import com.google.gwt.util.tools.ToolBase; + +import java.io.File; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeSet; + +/** + * A utility class for creating a Java interface declaration for a given CSS + * file. + */ +public class InterfaceGenerator extends ToolBase { + + private static final Comparator<String> CSS_CLASS_COMPARATOR = new Comparator<String>() { + public int compare(String o1, String o2) { + return o1.compareToIgnoreCase(o2); + } + }; + + private static final TreeLogger.Type LOG_LEVEL = TreeLogger.WARN; + + public static void main(String[] args) { + (new InterfaceGenerator()).execImpl(args); + } + + private String interfaceName; + private File inputFile; + private TreeLogger logger; + private boolean standaloneFile; + + private InterfaceGenerator() { + // -standalone + registerHandler(new ArgHandlerFlag() { + + @Override + public String getPurpose() { + return "Add package and import statements to generated interface"; + } + + @Override + public String getTag() { + return "-standalone"; + } + + @Override + public boolean setFlag() { + standaloneFile = true; + logger.log(TreeLogger.DEBUG, "Creating standalone file"); + return true; + } + }); + + // -typeName some.package.MyCssResource + registerHandler(new ArgHandlerString() { + + @Override + public String getPurpose() { + return "The name of the generated CssResource subtype"; + } + + @Override + public String getTag() { + return "-typeName"; + } + + @Override + public String[] getTagArgs() { + return new String[] {"some.package.MyCssResource"}; + } + + @Override + public boolean isRequired() { + return true; + } + + @Override + public boolean setString(String str) { + if (str.length() == 0) { + return false; + } + if (!Character.isJavaIdentifierStart(str.charAt(0))) { + return false; + } + for (int i = 1, j = str.length(); i < j; i++) { + char c = str.charAt(i); + if (!(Character.isJavaIdentifierPart(c) || c == '.')) { + return false; + } + } + interfaceName = str; + logger.log(TreeLogger.DEBUG, "interfaceName = " + interfaceName); + return true; + } + }); + + // -css in.css + registerHandler(new ArgHandlerFile() { + + @Override + public String getPurpose() { + return "The input CSS file to process"; + } + + @Override + public String getTag() { + return "-css"; + } + + @Override + public boolean isRequired() { + return true; + } + + @Override + public void setFile(File file) { + inputFile = file; + logger.log(TreeLogger.DEBUG, "inputFile = " + file.getAbsolutePath()); + } + }); + } + + @Override + protected String getDescription() { + return "Create a CssResource interface based on a CSS file"; + } + + private void execImpl(String[] args) { + // Set up logging + PrintWriter logWriter = new PrintWriter(System.err); + logger = new PrintWriterTreeLogger(logWriter); + ((PrintWriterTreeLogger) logger).setMaxDetail(LOG_LEVEL); + + // Process args or die + if (!processArgs(args)) { + System.exit(-1); + } + + boolean error = false; + try { + System.out.println(process()); + } catch (MalformedURLException e) { + logger.log(TreeLogger.ERROR, "Unable to load CSS", e); + error = true; + } catch (UnableToCompleteException e) { + logger.log(TreeLogger.ERROR, "Unable to process CSS", e); + error = true; + } finally { + // Make sure the logs are emitted + logWriter.flush(); + } + + System.exit(error ? -1 : 0); + } + + /** + * Munge a CSS class name into a Java identifier. + */ + private String methodName(String className) { + StringBuilder sb = new StringBuilder(); + char c = className.charAt(0); + boolean nextUpCase = false; + + if (Character.isJavaIdentifierStart(c)) { + sb.append(Character.toLowerCase(c)); + } + + for (int i = 1, j = className.length(); i < j; i++) { + c = className.charAt(i); + if (!Character.isJavaIdentifierPart(c)) { + nextUpCase = true; + continue; + } + + if (nextUpCase) { + nextUpCase = false; + c = Character.toUpperCase(c); + } + sb.append(c); + } + return sb.toString(); + } + + private String process() throws MalformedURLException, + UnableToCompleteException { + // Create AST + CssStylesheet sheet = GenerateCssAst.exec(logger, inputFile.toURI().toURL()); + + // Sort all class names + Set<String> classNames = new TreeSet<String>(CSS_CLASS_COMPARATOR); + classNames.addAll(ExtractClassNamesVisitor.exec(sheet)); + + // Deduplicate method names + Set<String> methodNames = new HashSet<String>(); + + // Build the interface + SourceWriter sw = new StringSourceWriter(); + + int lastDot = interfaceName.lastIndexOf('.'); + if (standaloneFile) { + sw.println("// DO NOT EDIT"); + sw.println("// Automatically generated by " + + InterfaceGenerator.class.getName()); + sw.println("package " + interfaceName.substring(0, lastDot) + ";"); + sw.println("import " + CssResource.class.getCanonicalName() + ";"); + sw.println("import " + ClassName.class.getCanonicalName() + ";"); + } + + sw.println("interface " + interfaceName.substring(lastDot + 1) + + " extends CssResource {"); + sw.indent(); + for (String className : classNames) { + String methodName = methodName(className); + + while (!methodNames.add(methodName)) { + // Unusual, handles foo-bar and foo--bar + methodName += "_"; + } + + sw.println(); + if (!methodName.equals(className)) { + sw.println("@ClassName(\"" + Generator.escape(className) + "\")"); + } + sw.println("String " + methodName + "();"); + } + sw.outdent(); + sw.println("}"); + + return sw.toString(); + } +} ======================================= --- /trunk/doc/build.xml Tue Jun 16 08:37:30 2009 +++ /trunk/doc/build.xml Sat Oct 10 13:50:44 2009 @@ -10,7 +10,7 @@ <property.ensure name="gwt.dev.jar" location="${gwt.build.lib}/gwt-dev-${build.host.platform}.jar" /> <property name="USER_PKGS" - value="com.google.gwt.animation.client;com.google.gwt.benchmarks.client;com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.soyc;com.google.gwt.core.ext.linker;com.google.gwt.core.ext.typeinfo;com.google.gwt.debug.client;com.google.gwt.dom.client;com.google.gwt.event.dom.client;com.google.gwt.event.logical.shared;com.google.gwt.event.shared;com.google.gwt.http.client;com.google.gwt.i18n.client;com.google.gwt.i18n.client.constants;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.datepicker.client;com.google.gwt.user.server.rpc;com.google.gwt.xml.client"/> + value="com.google.gwt.animation.client;com.google.gwt.benchmarks.client;com.google.gwt.core.client;com.google.gwt.core.ext;com.google.gwt.core.ext.soyc;com.google.gwt.core.ext.linker;com.google.gwt.core.ext.typeinfo;com.google.gwt.debug.client;com.google.gwt.dom.client;com.google.gwt.event.dom.client;com.google.gwt.event.logical.shared;com.google.gwt.event.shared;com.google.gwt.http.client;com.google.gwt.i18n.client;com.google.gwt.i18n.client.constants;com.google.gwt.i18n.rebind.format;com.google.gwt.i18n.rebind.keygen;com.google.gwt.json.client;com.google.gwt.junit.client;com.google.gwt.benchmarks.client;com.google.gwt.resources.client;com.google.gwt.resources.ext;com.google.gwt.user.client;com.google.gwt.user.client.rpc;com.google.gwt.user.client.ui;com.google.gwt.user.datepicker.client;com.google.gwt.user.server.rpc;com.google.gwt.xml.client"/> <property name="LANG_PKGS" value="java.lang;java.lang.annotation;java.util;java.io;java.sql" /> <!-- Individual classes to include when we don't want to ======================================= --- /trunk/user/src/com/google/gwt/resources/client/CssResource.java Sat Oct 10 13:50:11 2009 +++ /trunk/user/src/com/google/gwt/resources/client/CssResource.java Sat Oct 10 13:50:44 2009 @@ -136,6 +136,10 @@ * * If a <code>String foo()</code> method were defined in <code>MyCss</code>, it * would return the string value "<code>foo</code>". + * <p> + * The utility tool <code>com.google.gwt.resources.css.InterfaceGenerator</code> + * can be used to automatically generate a Java interface from a + * CssResource-compatible CSS file. * * @see <a href="http://code.google.com/p/google-web-toolkit/wiki/CssResource" * >CssResource design doc</a> ======================================= --- /trunk/user/src/com/google/gwt/resources/css/GenerateCssAst.java Tue Aug 25 15:17:11 2009 +++ /trunk/user/src/com/google/gwt/resources/css/GenerateCssAst.java Sat Oct 10 13:50:44 2009 @@ -614,9 +614,11 @@ private static final String VALUE_FUNCTION_NAME = "value"; /** - * Create a CssStylesheet from the contents of a URL. + * Create a CssStylesheet from the contents of one or more URLs. If multiple + * URLs are provided, the generated stylesheet will be created as though the + * contents of the URLs had been concatenated. */ - public static CssStylesheet exec(TreeLogger logger, URL[] stylesheets) + public static CssStylesheet exec(TreeLogger logger, URL... stylesheets) throws UnableToCompleteException { Parser p = new Parser(); Errors errors = new Errors(logger); @@ -652,8 +654,8 @@ * Expresses an rgb function as a hex expression. * * @param colors a sequence of LexicalUnits, assumed to be - * <code>(VAL COMMA VAL COMMA VAL)</code> - * where VAL can be an INT or a PERCENT (which is then converted to INT) + * <code>(VAL COMMA VAL COMMA VAL)</code> where VAL can be an INT or + * a PERCENT (which is then converted to INT) * @return the minimal hex expression for the RGB color values */ private static Value colorValue(LexicalUnit colors) { @@ -745,11 +747,11 @@ /** * Return an integer value from 0-255 for a component of an RGB color. * - * @param color typed value from the CSS parser, which may be an INTEGER or - * a PERCENTAGE + * @param color typed value from the CSS parser, which may be an INTEGER or a + * PERCENTAGE * @return integer value from 0-255 * @throws IllegalArgumentException if the color is not an INTEGER or - * PERCENTAGE value + * PERCENTAGE value */ private static int getRgbComponentValue(LexicalUnit color) { switch (color.getLexicalUnitType()) { ======================================= --- /trunk/user/src/com/google/gwt/resources/css/Minify.java Mon May 18 11:47:32 2009 +++ /trunk/user/src/com/google/gwt/resources/css/Minify.java Sat Oct 10 13:50:44 2009 @@ -117,7 +117,7 @@ } try { - CssStylesheet sheet = GenerateCssAst.exec(logger, new URL[] {source}); + CssStylesheet sheet = GenerateCssAst.exec(logger, source); TextOutput out = new DefaultTextOutput(!pretty); (new CssGenerationVisitor(out)).accept(sheet); System.out.println(out.toString()); ======================================= --- /trunk/user/src/com/google/gwt/resources/css/ast/CssSelector.java Wed Aug 26 08:44:35 2009 +++ /trunk/user/src/com/google/gwt/resources/css/ast/CssSelector.java Sat Oct 10 13:50:44 2009 @@ -21,7 +21,7 @@ * An opaque view of a selector. */ public class CssSelector extends CssNode { - public static final Pattern CLASS_SELECTOR_PATTERN = Pattern.compile("\\.([^ :>+#.]*)"); + public static final Pattern CLASS_SELECTOR_PATTERN = Pattern.compile("\\.([^ \\[:>+#.]+)"); /* * TODO: Evaluate whether or not we need to have a type hierarchy of ======================================= --- /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java Fri Oct 9 22:09:58 2009 +++ /trunk/user/src/com/google/gwt/uibinder/rebind/model/ImplicitCssResource.java Sat Oct 10 13:50:44 2009 @@ -1,12 +1,12 @@ /* * Copyright 2009 Google Inc. - * + * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -78,7 +78,7 @@ /** * @return the set of CSS classnames in the underlying .css files - * + * * @throws UnableToCompleteException if the user has called for a .css file we * can't find. */ @@ -89,7 +89,7 @@ urls = getExternalCss(); } else { try { - urls = new URL[] {getGeneratedFile().toURL()}; + urls = new URL[] {getGeneratedFile().toURI().toURL()}; } catch (MalformedURLException e) { throw new RuntimeException(e); } ======================================= --- /trunk/user/test/com/google/gwt/resources/client/CSSResourceTest.java Sat Oct 10 13:50:11 2009 +++ /trunk/user/test/com/google/gwt/resources/client/CSSResourceTest.java Sat Oct 10 13:50:44 2009 @@ -19,14 +19,12 @@ import com.google.gwt.junit.client.GWTTestCase; import com.google.gwt.resources.client.CssResource.Import; import com.google.gwt.resources.client.CssResource.ImportedWithPrefix; -import com.google.gwt.resources.client.CssResource.NotStrict; import com.google.gwt.resources.client.CssResource.Shared; /** * Contains various full-stack tests of the CssResource system. */ public class CSSResourceTest extends GWTTestCase { - interface ChildResources extends Resources { ChildResources INSTANCE = GWT.create(ChildResources.class); @@ -63,6 +61,63 @@ int rawInt(); } + + /** + * Regenerate this interface by running InterfaceGenerator over test.css. + */ + interface FullTestCss extends CssResource { + String affectedBySprite(); + + String blahA(); + + String css3Color(); + + String externalA(); + + String externalB(); + + String externalC(); + + String extraSpriteClass(); + + @ClassName("FAIL") + String fAIL(); + + @ClassName("may-combine") + String mayCombine(); + + @ClassName("may-combine2") + String mayCombine2(); + + @ClassName("may-merge") + String mayMerge(); + + @ClassName("may-not-combine") + String mayNotCombine(); + + @ClassName("may-not-combine2") + String mayNotCombine2(); + + @ClassName("may-not-merge") + String mayNotMerge(); + + @ClassName("may-not-merge-or-combine-because-of-this") + String mayNotMergeOrCombineBecauseOfThis(); + + String multiClassA(); + + String multiClassB(); + + String replacement(); + + @ClassName("replacement-not-java-ident") + String replacementNotJavaIdent(); + + String spriteClass(); + + @ClassName("this-does-not-matter") + String thisDoesNotMatter(); + } interface HasDescendants extends CssResource { String foo(); @@ -116,8 +171,7 @@ MyCssResourceB b(); @Source("test.css") - @NotStrict - MyCssResourceWithSprite css(); + FullTestCss css(); @Source("32x32.png") DataResource dataMethod(); @@ -186,7 +240,7 @@ } public void testCss() { - MyCssResourceWithSprite css = Resources.INSTANCE.css(); + FullTestCss css = Resources.INSTANCE.css(); String text = css.getText(); report(text); @@ -202,7 +256,7 @@ assertFalse("replacement".equals(css.replacement())); assertTrue(text.contains("." + css.replacement())); assertTrue(text.contains("." + css.replacement() + ":after")); - assertTrue(text.contains("." + css.nameOverride())); + assertTrue(text.contains("." + css.replacementNotJavaIdent())); // Make sure renaming for multi-class selectors (.foo.bar) works assertFalse("multiClassA".equals(css.multiClassA())); @@ -218,8 +272,8 @@ // Check interestingly-named idents assertTrue(text.contains("\\-some-wacky-extension")); - assertTrue(text.contains(".ns\\:tag")); - assertTrue(text.contains(".ns\\:tag:pseudo")); + assertTrue(text.contains("ns\\:tag")); + assertTrue(text.contains("ns\\:tag:pseudo")); // Check escaped string values assertTrue(text.contains("\"Hello\\\\\\\" world\"")); @@ -247,12 +301,14 @@ assertTrue(text.indexOf("static:PASSED") < text.indexOf("runtime:PASSED")); assertTrue(text.indexOf("before:merge") != -1); assertTrue(text.indexOf("before:merge") < text.indexOf("after:merge")); - assertTrue(text.indexOf(".may-combine,.may-combine2") != -1); + assertTrue(text.indexOf("." + css.mayCombine() + ",." + css.mayCombine2()) != -1); assertTrue(text.indexOf("merge:merge") != -1); - assertTrue(text.indexOf("merge:merge") < text.indexOf("may-not-combine")); + assertTrue(text.indexOf("merge:merge") < text.indexOf("." + + css.mayNotCombine())); assertTrue(text.indexOf("may-not-combine") < text.indexOf("prevent:true")); assertTrue(text.indexOf("prevent:true") < text.indexOf("prevent-merge:true")); - assertTrue(text.indexOf("prevent:true") < text.indexOf("may-not-combine2")); + assertTrue(text.indexOf("prevent:true") < text.indexOf("." + + css.mayNotCombine2())); // Check commonly-used CSS3 constructs assertTrue(text.contains("background-color:rgba(0,0,0,0.5);")); ======================================= --- /trunk/user/test/com/google/gwt/resources/client/test.css Wed May 20 16:14:31 2009 +++ /trunk/user/test/com/google/gwt/resources/client/test.css Sat Oct 10 13:50:44 2009 @@ -112,11 +112,11 @@ top: literal("expression(document.compatMode==\"CSS1Compat\" ? documentElement.scrollTop:document.body.scrollTop \\ 2)"); } -.ns\:tag { +ns\:tag { border: red; } -.ns\:tag:pseudo { +ns\:tag:pseudo { content : "blah"; } ======================================= --- /trunk/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java Tue Sep 29 13:58:11 2009 +++ /trunk/user/test/com/google/gwt/resources/css/CssNodeClonerTest.java Sat Oct 10 13:50:44 2009 @@ -24,7 +24,6 @@ import com.google.gwt.resources.css.ast.CssStylesheet; import com.google.gwt.resources.rg.CssTestCase; -import java.net.URL; import java.util.List; /** @@ -34,8 +33,8 @@ public void testClone() throws UnableToCompleteException { CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL, - new URL[] {getClass().getClassLoader().getResource( - "com/google/gwt/resources/client/test.css")}); + getClass().getClassLoader().getResource( + "com/google/gwt/resources/client/test.css")); CssStylesheet cloned = CssNodeCloner.clone(CssStylesheet.class, sheet); @@ -45,8 +44,8 @@ public void testCloneList() throws UnableToCompleteException { CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL, - new URL[] {getClass().getClassLoader().getResource( - "com/google/gwt/resources/client/test.css")}); + getClass().getClassLoader().getResource( + "com/google/gwt/resources/client/test.css")); List<CssNode> cloned = CssNodeCloner.clone(CssNode.class, sheet.getNodes()); ======================================= --- /trunk/user/test/com/google/gwt/resources/rg/CssTestCase.java Tue Sep 29 13:58:11 2009 +++ /trunk/user/test/com/google/gwt/resources/rg/CssTestCase.java Sat Oct 10 13:50:44 2009 @@ -144,8 +144,8 @@ CssStylesheet testSheet = null; try { - expectedSheet = GenerateCssAst.exec(logger, new URL[] {expected}); - testSheet = GenerateCssAst.exec(logger, new URL[] {test}); + expectedSheet = GenerateCssAst.exec(logger, expected); + testSheet = GenerateCssAst.exec(logger, test); } catch (UnableToCompleteException e) { fail("Unable to parse stylesheet"); } --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---
