Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of github.com:aholstenson/dust

  • Loading branch information...
commit e90ae04a84a54e3826a9d13a7cc862f8a351b69b 2 parents a215ee7 + cb2edcd
@aholstenson authored
Showing with 808 additions and 48 deletions.
  1. +1 −1  dust-core/pom.xml
  2. +34 −0 dust-core/src/main/java/se/l4/dust/api/asset/AssetCache.java
  3. +18 −0 dust-core/src/main/java/se/l4/dust/api/asset/AssetEncounter.java
  4. +2 −2 dust-core/src/main/java/se/l4/dust/core/internal/NamespaceManagerImpl.java
  5. +81 −2 dust-core/src/main/java/se/l4/dust/core/internal/asset/AssetEncounterImpl.java
  6. +22 −10 dust-core/src/main/java/se/l4/dust/core/internal/asset/AssetManagerImpl.java
  7. +207 −0 dust-core/src/main/java/se/l4/dust/core/internal/asset/CacheFormat.java
  8. +49 −0 dust-core/src/main/java/se/l4/dust/core/internal/asset/CachedResource.java
  9. +1 −1  dust-core/src/main/java/se/l4/dust/core/internal/expression/ExpressionCompiler.java
  10. +39 −11 dust-css-less/src/main/java/se/l4/dust/css/less/LessProcessor.java
  11. +5 −0 dust-jaxrs/src/main/java/se/l4/dust/jaxrs/internal/template/TemplateWriter.java
  12. +15 −6 dust-js-closure/src/main/java/se/l4/dust/js/closure/ClosureAssetProcessor.java
  13. +11 −1 dust-js-coffeescript/src/main/java/se/l4/dust/js/coffeescript/CoffeeScriptProcessor.java
  14. +16 −4 dust-js-coffeescript/src/test/java/se/l4/dust/js/coffeescript/CoffeeScriptTest.java
  15. +0 −6 dust-yui/pom.xml
  16. +3 −4 dust-yui/src/main/java/se/l4/dust/yui/CssCompressProcessor.java
  17. +304 −0 dust-yui/src/main/java/se/l4/dust/yui/CssCompressor.java
View
2  dust-core/pom.xml
@@ -23,7 +23,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>10.0.1</version>
+ <version>11.0.1</version>
</dependency>
<dependency>
View
34 dust-core/src/main/java/se/l4/dust/api/asset/AssetCache.java
@@ -0,0 +1,34 @@
+package se.l4.dust.api.asset;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Cache interface for asset files, can be tied up the application using
+ * Dust to enable caching for asset transformations.
+ *
+ * @author Andreas Holstenson
+ *
+ */
+public interface AssetCache
+{
+ /**
+ * Get an output stream to use for storing a file the given name. The
+ * file use a forward slash (/) as its separator.
+ *
+ * @param name
+ * @return
+ */
+ OutputStream store(String name)
+ throws IOException;
+
+ /**
+ * Get an object from the cache.
+ *
+ * @param name
+ * @return
+ */
+ InputStream get(String name)
+ throws IOException;
+}
View
18 dust-core/src/main/java/se/l4/dust/api/asset/AssetEncounter.java
@@ -48,6 +48,24 @@
boolean isProduction();
/**
+ * Get the original resource as a cached instance. The identifier should
+ * be an identifier unique to the processor.
+ *
+ * @param id
+ * @return
+ */
+ Resource getCached(String id);
+
+ /**
+ * Cache the given resource for usage later. The identifier should be
+ * an identifier unique to the processor and not the resource.
+ *
+ * @param resource
+ * @return
+ */
+ AssetEncounter cache(String id, Resource resource);
+
+ /**
* Replace the resource with a new one.
*
* @param resource
View
4 dust-core/src/main/java/se/l4/dust/core/internal/NamespaceManagerImpl.java
@@ -7,7 +7,6 @@
import java.util.concurrent.ConcurrentHashMap;
import se.l4.dust.api.NamespaceManager;
-import se.l4.dust.api.asset.AssetException;
import com.google.inject.Singleton;
@@ -228,7 +227,8 @@ public FailingLocator(String uri)
@Override
public URL locateResource(String path)
{
- throw new AssetException("The namespace " + uri + " does not have any assets. Did you tie it to a package or class?");
+ return null;
+// throw new AssetException("The namespace " + uri + " does not have any assets. Did you tie it to a package or class?");
}
}
}
View
83 dust-core/src/main/java/se/l4/dust/core/internal/asset/AssetEncounterImpl.java
@@ -1,10 +1,19 @@
package se.l4.dust.core.internal.asset;
+import java.io.IOException;
+import java.io.InputStream;
+
import se.l4.dust.api.NamespaceManager;
import se.l4.dust.api.NamespaceManager.Namespace;
+import se.l4.dust.api.asset.AssetCache;
import se.l4.dust.api.asset.AssetEncounter;
+import se.l4.dust.api.asset.AssetException;
import se.l4.dust.api.resource.Resource;
+import com.google.common.hash.Hasher;
+import com.google.common.hash.Hashing;
+import com.google.common.io.Closeables;
+
/**
* Implementation of {@link AssetEncounter}.
*
@@ -20,17 +29,26 @@
private final String namespace;
private final String path;
+ private final AssetCache cache;
+ private String cacheKey;
+
private Resource replacedWith;
private String renamedTo;
- public AssetEncounterImpl(NamespaceManager namespaces, boolean production,
- Resource in, String namespace, String path)
+ public AssetEncounterImpl(
+ NamespaceManager namespaces,
+ boolean production,
+ Resource in,
+ String namespace,
+ String path,
+ AssetCache cache)
{
this.namespaces = namespaces;
this.production = production;
this.in = in;
this.namespace = namespace;
this.path = path;
+ this.cache = cache;
}
@Override
@@ -78,6 +96,67 @@ public AssetEncounter rename(String name)
return this;
}
+
+ @Override
+ public AssetEncounter cache(String id, Resource resource)
+ {
+ if(cache == null) return this;
+
+ cacheKey = id + "/" + hashInput();
+ try
+ {
+ CacheFormat.store(resource, cache, cacheKey);
+ }
+ catch(IOException e)
+ {
+ throw new AssetException("Unable to cache " + in + "; " + e.getMessage(), e);
+ }
+
+ return this;
+ }
+
+ @Override
+ public Resource getCached(String id)
+ {
+ if(cache == null) return null;
+
+ String cacheKey = id + "/" + hashInput();
+ try
+ {
+ CacheFormat format = CacheFormat.fromCachedStream(cache, cacheKey);
+ return format == null ? null : new CachedResource(format);
+ }
+ catch(IOException e)
+ {
+ throw new AssetException("Unable to use cache " + in + "; " + e.getMessage(), e);
+ }
+ }
+
+ private String hashInput()
+ {
+ Hasher hasher = Hashing.murmur3_128().newHasher();
+ InputStream input = null;
+ try
+ {
+ input = in.openStream();
+ byte[] buf = new byte[4096];
+ int len;
+ while((len = input.read(buf)) != -1)
+ {
+ hasher.putBytes(buf, 0, len);
+ }
+
+ return hasher.hash().toString();
+ }
+ catch(IOException e)
+ {
+ throw new AssetException("Unable to cache " + in + "; " + e.getMessage(), e);
+ }
+ finally
+ {
+ Closeables.closeQuietly(input);
+ }
+ }
public Resource getReplacedWith()
{
View
32 dust-core/src/main/java/se/l4/dust/core/internal/asset/AssetManagerImpl.java
@@ -12,18 +12,12 @@
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Pattern;
-import com.google.common.base.Function;
-import com.google.common.collect.ComputationException;
-import com.google.common.collect.MapMaker;
-import com.google.inject.Inject;
-import com.google.inject.Injector;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import com.google.inject.Stage;
+import javax.annotation.Nullable;
import se.l4.dust.api.Context;
import se.l4.dust.api.NamespaceManager;
import se.l4.dust.api.asset.Asset;
+import se.l4.dust.api.asset.AssetCache;
import se.l4.dust.api.asset.AssetException;
import se.l4.dust.api.asset.AssetManager;
import se.l4.dust.api.asset.AssetProcessor;
@@ -35,6 +29,15 @@
import se.l4.dust.api.resource.variant.ResourceVariantManager.ResourceCallback;
import se.l4.dust.core.internal.resource.MergedResourceVariant;
+import com.google.common.base.Function;
+import com.google.common.collect.ComputationException;
+import com.google.common.collect.MapMaker;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.Stage;
+
@Singleton
public class AssetManagerImpl
implements AssetManager
@@ -52,16 +55,20 @@
private final Map<String, AssetProcessor> extensionProcessors;
private final Injector injector;
+
+ private final AssetCache assetCache;
@Inject
public AssetManagerImpl(NamespaceManager manager,
ResourceVariantManager variants,
Injector injector,
+ @Nullable AssetCache assetCache,
Stage stage)
{
this.manager = manager;
this.variants = variants;
this.injector = injector;
+ this.assetCache = assetCache;
sources = new CopyOnWriteArrayList<Object>();
extensionProcessors = new ConcurrentHashMap<String, AssetProcessor>();
@@ -428,7 +435,7 @@ public Asset createAsset(String path, Resource resource)
AssetProcessor ext = extensionProcessors.get(extension);
if(ext != null)
{
- AssetEncounterImpl encounter = new AssetEncounterImpl(manager, production, current, namespace, lastName);
+ AssetEncounterImpl encounter = new AssetEncounterImpl(manager, production, current, namespace, lastName, assetCache);
ext.process(encounter);
@@ -467,7 +474,7 @@ public Asset createAsset(String path, Resource resource)
processed = true;
}
- AssetEncounterImpl encounter = new AssetEncounterImpl(manager, production, current, namespace, lastName);
+ AssetEncounterImpl encounter = new AssetEncounterImpl(manager, production, current, namespace, lastName, assetCache);
def.getProcessor().process(encounter);
applied.add(def);
@@ -622,6 +629,11 @@ public Resource getResource()
try
{
Resource resource = ns.locate(orignalPath);
+ if(resource == null)
+ {
+ throw new AssetException("Could not locate " + orignalPath);
+ }
+
if(resource.getLastModified() > asset.getResource().getLastModified())
{
// Recreation needed so we replace the old asset
View
207 dust-core/src/main/java/se/l4/dust/core/internal/asset/CacheFormat.java
@@ -0,0 +1,207 @@
+package se.l4.dust.core.internal.asset;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import se.l4.dust.api.asset.AssetCache;
+import se.l4.dust.api.resource.Resource;
+
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.common.io.CountingInputStream;
+
+/**
+ * Encapsulation of resources in cached form.
+ *
+ * @author Andreas Holstenson
+ *
+ */
+public class CacheFormat
+{
+ private final int length;
+ private final String contentType;
+ private final String contentEncoding;
+ private final long startOfFile;
+ private final AssetCache cache;
+ private final String key;
+ private final long lastModified;
+
+ private CacheFormat(AssetCache cache, String key, int length, String contentType, String contentEncoding, long lastModified, long startOfFile)
+ {
+ this.cache = cache;
+ this.key = key;
+ this.length = length;
+ this.contentType = contentType;
+ this.contentEncoding = contentEncoding;
+ this.lastModified = lastModified;
+ this.startOfFile = startOfFile;
+ }
+
+ public int getLength()
+ {
+ return length;
+ }
+
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ public String getContentEncoding()
+ {
+ return contentEncoding;
+ }
+
+ public long getLastModified()
+ {
+ return lastModified;
+ }
+
+ public InputStream openStream()
+ throws IOException
+ {
+ InputStream stream = cache.get(key);
+ stream.skip(startOfFile);
+ return stream;
+ }
+
+ public static CacheFormat fromCachedStream(AssetCache cache, String key)
+ throws IOException
+ {
+ InputStream stream = cache.get(key);
+ if(stream == null) return null;
+
+ try
+ {
+ CountingInputStream counting = new CountingInputStream(stream);
+ int version = counting.read();
+
+ int length = readInteger(counting);
+ String contentType = readString(counting);
+ String contentEncoding = readString(counting);
+ long lastModified = readLong(counting);
+
+ long startOfFile = counting.getCount();
+
+ return new CacheFormat(cache, key, length, contentType, contentEncoding, lastModified, startOfFile);
+ }
+ finally
+ {
+ Closeables.closeQuietly(stream);
+ }
+ }
+
+ private static int readInteger(InputStream stream)
+ throws IOException
+ {
+ int shift = 0;
+ int result = 0;
+ while(shift < 32)
+ {
+ final byte b = (byte) stream.read();
+ result |= (int) (b & 0x7F) << shift;
+ if((b & 0x80) == 0) return result;
+
+ shift += 7;
+ }
+
+ return result;
+ }
+
+ private static long readLong(InputStream in)
+ throws IOException
+ {
+ int shift = 0;
+ long result = 0;
+ while(shift < 64)
+ {
+ final byte b = (byte) in.read();
+ result |= (long) (b & 0x7F) << shift;
+ if((b & 0x80) == 0) return result;
+
+ shift += 7;
+ }
+
+ throw new EOFException("Invalid long");
+ }
+
+ private static String readString(InputStream stream)
+ throws IOException
+ {
+ int length = readInteger(stream);
+ byte[] buf = new byte[length];
+ ByteStreams.readFully(stream, buf);
+ return buf.length == 0 ? null : new String(buf);
+ }
+
+ public static void store(Resource resource, AssetCache cache, String key)
+ throws IOException
+ {
+ OutputStream out = cache.store(key);
+ InputStream in = resource.openStream();
+ try
+ {
+ out.write(1);
+
+ writeInteger(out, resource.getContentLength());
+ writeString(out, resource.getContentType());
+ writeString(out, resource.getContentEncoding());
+ writeLong(out, resource.getLastModified());
+
+ ByteStreams.copy(in, out);
+ }
+ finally
+ {
+ Closeables.closeQuietly(out);
+ Closeables.closeQuietly(in);
+ }
+ }
+
+ private static void writeInteger(OutputStream out, int value)
+ throws IOException
+ {
+ while(true)
+ {
+ if((value & ~0x7F) == 0)
+ {
+ out.write(value);
+ break;
+ }
+ else
+ {
+ out.write((value & 0x7f) | 0x80);
+ value >>>= 7;
+ }
+ }
+ }
+
+ private static void writeLong(OutputStream out, long value)
+ throws IOException
+ {
+ while(true)
+ {
+ if((value & ~0x7FL) == 0)
+ {
+ out.write((int) value);
+ break;
+ }
+ else
+ {
+ out.write(((int) value & 0x7f) | 0x80);
+ value >>>= 7;
+ }
+ }
+ }
+
+ private static void writeString(OutputStream out, String value)
+ throws IOException
+ {
+ if(value == null) value = "";
+
+ byte[] data = value.getBytes();
+ writeInteger(out, data.length);
+ out.write(data);
+ }
+}
View
49 dust-core/src/main/java/se/l4/dust/core/internal/asset/CachedResource.java
@@ -0,0 +1,49 @@
+package se.l4.dust.core.internal.asset;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import se.l4.dust.api.resource.Resource;
+
+public class CachedResource
+ implements Resource
+{
+ private final CacheFormat format;
+
+ public CachedResource(CacheFormat format)
+ {
+ this.format = format;
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return format.getContentType();
+ }
+
+ @Override
+ public int getContentLength()
+ {
+ return format.getLength();
+ }
+
+ @Override
+ public String getContentEncoding()
+ {
+ return format.getContentEncoding();
+ }
+
+ @Override
+ public long getLastModified()
+ {
+ return format.getLastModified();
+ }
+
+ @Override
+ public InputStream openStream()
+ throws IOException
+ {
+ return format.openStream();
+ }
+
+}
View
2  dust-core/src/main/java/se/l4/dust/core/internal/expression/ExpressionCompiler.java
@@ -180,7 +180,7 @@ public Expression compile()
*/
public String cast(Class<?> result)
{
- return "(" + castNoParens(result) + ")";
+ return result == void.class ? "" : "(" + castNoParens(result) + ")";
}
/**
View
50 dust-css-less/src/main/java/se/l4/dust/css/less/LessProcessor.java
@@ -1,20 +1,16 @@
package se.l4.dust.css.less;
+import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.StringReader;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.JavaScriptException;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
-import com.google.common.base.Charsets;
-import com.google.common.io.ByteStreams;
-import com.google.common.io.Closeables;
-import com.google.inject.Inject;
-import com.google.inject.Stage;
-
import se.l4.dust.api.DefaultContext;
import se.l4.dust.api.asset.Asset;
import se.l4.dust.api.asset.AssetEncounter;
@@ -25,6 +21,12 @@
import se.l4.dust.api.resource.Resource;
import se.l4.dust.js.env.JavascriptEnvironment;
+import com.google.common.base.Charsets;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import com.google.inject.Inject;
+import com.google.inject.Stage;
+
/**
* Processor of LESS style sheets. This processor will convert LESS files into
* pure CSS files on the fly.
@@ -49,6 +51,13 @@ public LessProcessor(Stage stage, AssetManager assets)
public void process(AssetEncounter encounter)
throws IOException
{
+ Resource cached = encounter.getCached("lesscss");
+ if(cached != null)
+ {
+ encounter.replaceWith(cached);
+ return;
+ }
+
String path = encounter.getPath();
if(path.endsWith(".less"))
{
@@ -90,22 +99,23 @@ public void process(AssetEncounter encounter)
.evaluate("compileResource(css);");
MemoryResource res = new MemoryResource("text/css", "UTF-8", ((String) result).getBytes("UTF-8"));
- encounter.replaceWith(res).rename(path);
+ encounter.cache("lesscss", res).replaceWith(res).rename(path);
}
catch(JavaScriptException e)
{
- throw processError(e);
+ throw processError(value, e);
}
}
/**
* Attempt to get a bit better output from the processor when a syntax
* error occurs for the LESS file.
+ * @param value2
*
* @param e
* @return
*/
- private IOException processError(JavaScriptException e)
+ private IOException processError(String lessValue, JavaScriptException e)
{
Scriptable value = (Scriptable) e.getValue();
@@ -136,8 +146,26 @@ else if(ScriptableObject.hasProperty(value, "type"))
String message = hasProperty(value, "message")
? (String) ScriptableObject.getProperty(value, "message")
: "Error during LESS processing";
-
- return new IOException(name + ": " + message + " on line " + line + ", column " + column);
+
+ // Extract the line
+ int current = 0;
+ String text = null;
+ try
+ {
+ BufferedReader reader = new BufferedReader(new StringReader(lessValue));
+ while((text = reader.readLine()) != null)
+ {
+ if(++current == line)
+ {
+ break;
+ }
+ }
+ }
+ catch(IOException e0)
+ {
+ }
+
+ return new IOException(name + ": " + message + " (line " + line + ", column " + column + ")\n\n\t" + text);
}
private boolean hasProperty(Scriptable obj, String prop)
View
5 dust-jaxrs/src/main/java/se/l4/dust/jaxrs/internal/template/TemplateWriter.java
@@ -106,6 +106,11 @@ public void writeTo(
// FIXME: Should we really do this?
httpHeaders.putSingle("Content-Type", "text/html; charset=utf-8");
+ if(! httpHeaders.containsKey("Expires"))
+ {
+ httpHeaders.putSingle("Expires", -1);
+ }
+
TemplateOutputStream out = new HtmlTemplateOutput(entityStream);
renderer.render(context, template, t, out);
out.close();
View
21 dust-js-closure/src/main/java/se/l4/dust/js/closure/ClosureAssetProcessor.java
@@ -4,17 +4,17 @@
import java.io.InputStream;
import java.util.logging.Level;
+import se.l4.dust.api.asset.AssetEncounter;
+import se.l4.dust.api.asset.AssetProcessor;
+import se.l4.dust.api.resource.MemoryResource;
+import se.l4.dust.api.resource.Resource;
+
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.JSSourceFile;
import com.google.javascript.jscomp.Result;
-import se.l4.dust.api.asset.AssetEncounter;
-import se.l4.dust.api.asset.AssetProcessor;
-import se.l4.dust.api.resource.MemoryResource;
-import se.l4.dust.api.resource.Resource;
-
/**
* The actual processor used for Closure compilation, do not use directly,
* instead use {@link Closure}.
@@ -48,6 +48,13 @@ public void process(AssetEncounter encounter)
return;
}
+ Resource cached = encounter.getCached("closure");
+ if(cached != null)
+ {
+ encounter.replaceWith(cached);
+ return;
+ }
+
Compiler.setLoggingLevel(Level.WARNING);
Compiler compiler = new Compiler();
@@ -80,7 +87,9 @@ public void process(AssetEncounter encounter)
streams[0].close();
MemoryResource mr = new MemoryResource("text/javascript", "UTF-8", source.getBytes("UTF-8"));
- encounter.replaceWith(mr);
+ encounter
+ .cache("closure", mr)
+ .replaceWith(mr);
}
}
View
12 dust-js-coffeescript/src/main/java/se/l4/dust/js/coffeescript/CoffeeScriptProcessor.java
@@ -33,6 +33,13 @@ public void process(AssetEncounter encounter)
path = path.substring(0, path.length() - EXTENSION.length()) + ".js";
}
+ Resource cached = encounter.getCached("coffeescript");
+ if(cached != null)
+ {
+ encounter.replaceWith(cached);
+ return;
+ }
+
Resource resource = encounter.getResource();
InputStream stream = resource.openStream();
ByteArrayOutputStream out = new ByteArrayOutputStream(resource.getContentLength());
@@ -61,7 +68,10 @@ public void process(AssetEncounter encounter)
.evaluate("compileResource(code);");
MemoryResource res = new MemoryResource("text/javascript", "UTF-8", ((String) result).getBytes("UTF-8"));
- encounter.replaceWith(res).rename(path);
+ encounter
+ .cache("coffeescript", res)
+ .replaceWith(res)
+ .rename(path);
}
catch(JavaScriptException e)
{
View
20 dust-js-coffeescript/src/test/java/se/l4/dust/js/coffeescript/CoffeeScriptTest.java
@@ -6,10 +6,6 @@
import org.junit.Test;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.Stage;
-
import se.l4.crayon.Crayon;
import se.l4.crayon.CrayonModule;
import se.l4.dust.api.NamespaceManager;
@@ -19,6 +15,10 @@
import se.l4.dust.api.resource.Resource;
import se.l4.dust.api.resource.UrlResource;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Stage;
+
public class CoffeeScriptTest
{
@Test
@@ -74,6 +74,18 @@ public String getNamepace()
{
return "dust:test";
}
+
+ @Override
+ public AssetEncounter cache(String id, Resource resource)
+ {
+ return this;
+ }
+
+ @Override
+ public Resource getCached(String id)
+ {
+ return null;
+ }
});
}
View
6 dust-yui/pom.xml
@@ -18,12 +18,6 @@
<artifactId>dust-core</artifactId>
<version>${project.version}</version>
</dependency>
-
- <dependency>
- <groupId>com.yahoo.platform.yui</groupId>
- <artifactId>yuicompressor</artifactId>
- <version>2.4.6</version>
- </dependency>
</dependencies>
</project>
View
7 dust-yui/src/main/java/se/l4/dust/yui/CssCompressProcessor.java
@@ -6,15 +6,14 @@
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
-import com.google.common.base.Charsets;
-import com.google.common.io.Closeables;
-import com.yahoo.platform.yui.compressor.CssCompressor;
-
import se.l4.dust.api.asset.AssetEncounter;
import se.l4.dust.api.asset.AssetProcessor;
import se.l4.dust.api.resource.MemoryResource;
import se.l4.dust.api.resource.Resource;
+import com.google.common.base.Charsets;
+import com.google.common.io.Closeables;
+
/**
* Processor that will compress CSS resources.
*
View
304 dust-yui/src/main/java/se/l4/dust/yui/CssCompressor.java
@@ -0,0 +1,304 @@
+/*
+ * YUI Compressor
+ * http://developer.yahoo.com/yui/compressor/
+ * Author: Julien Lecomte - http://www.julienlecomte.net/
+ * Author: Isaac Schlueter - http://foohack.com/
+ * Author: Stoyan Stefanov - http://phpied.com/
+ * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
+ * The copyrights embodied in the content of this file are licensed
+ * by Yahoo! Inc. under the BSD (revised) open source license.
+ */
+package se.l4.dust.yui;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CssCompressor {
+
+ private StringBuffer srcsb = new StringBuffer();
+
+ public CssCompressor(Reader in) throws IOException {
+ // Read the stream...
+ int c;
+ while ((c = in.read()) != -1) {
+ srcsb.append((char) c);
+ }
+ }
+
+ public void compress(Writer out, int linebreakpos)
+ throws IOException {
+
+ Pattern p;
+ Matcher m;
+ String css = srcsb.toString();
+ StringBuffer sb = new StringBuffer(css);
+
+ int startIndex = 0;
+ int endIndex = 0;
+ int i = 0;
+ int max = 0;
+ ArrayList preservedTokens = new ArrayList(0);
+ ArrayList comments = new ArrayList(0);
+ String token;
+ int totallen = css.length();
+ String placeholder;
+
+ // // leave data urls alone to increase parse performance.
+ // sb = new StringBuffer();
+ // p = Pattern.compile("url\\(.*data\\:(.*)\\)");
+ // m = p.matcher(css);
+ // while (m.find()) {
+ // token = m.group();
+ // token = token.substring(1, token.length() - 1);
+ // preservedTokens.add(token);
+ // String preserver = "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___";
+ // m.appendReplacement(sb, preserver);
+ // }
+ // m.appendTail(sb);
+ // css = sb.toString();
+
+ // collect all comment blocks...
+ while ((startIndex = sb.indexOf("/*", startIndex)) >= 0) {
+ endIndex = sb.indexOf("*/", startIndex + 2);
+ if (endIndex < 0) {
+ endIndex = totallen;
+ }
+
+ token = sb.substring(startIndex + 2, endIndex);
+ comments.add(token);
+ sb.replace(startIndex + 2, endIndex, "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + (comments.size() - 1) + "___");
+ startIndex += 2;
+ }
+ css = sb.toString();
+
+ // preserve strings so their content doesn't get accidentally minified
+ sb = new StringBuffer();
+ p = Pattern.compile("(\"([^\\\\\"]|\\\\.|\\\\)*\")|(\'([^\\\\\']|\\\\.|\\\\)*\')");
+ m = p.matcher(css);
+ while (m.find()) {
+ token = m.group();
+ char quote = token.charAt(0);
+ token = token.substring(1, token.length() - 1);
+
+ // maybe the string contains a comment-like substring?
+ // one, maybe more? put'em back then
+ if (token.indexOf("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_") >= 0) {
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+ token = token.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", comments.get(i).toString());
+ }
+ }
+
+ // minify alpha opacity in filter strings
+ token = token.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ preservedTokens.add(token);
+ String preserver = quote + "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___" + quote;
+ m.appendReplacement(sb, preserver);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+
+ // strings are safe, now wrestle the comments
+ for (i = 0, max = comments.size(); i < max; i += 1) {
+
+ token = comments.get(i).toString();
+ placeholder = "___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___";
+
+ // ! in the first position of the comment means preserve
+ // so push to the preserved tokens while stripping the !
+ if (token.startsWith("!")) {
+ preservedTokens.add(token);
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // \ in the last position looks like hack for Mac/IE5
+ // shorten that to /*\*/ and the next one to /**/
+ if (token.endsWith("\\")) {
+ preservedTokens.add("\\");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ i = i + 1; // attn: advancing the loop
+ preservedTokens.add("");
+ css = css.replace("___YUICSSMIN_PRESERVE_CANDIDATE_COMMENT_" + i + "___", "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ continue;
+ }
+
+ // keep empty comments after child selectors (IE7 hack)
+ // e.g. html >/**/ body
+ if (token.length() == 0) {
+ startIndex = css.indexOf(placeholder);
+ if (startIndex > 2) {
+ if (css.charAt(startIndex - 3) == '>') {
+ preservedTokens.add("");
+ css = css.replace(placeholder, "___YUICSSMIN_PRESERVED_TOKEN_" + (preservedTokens.size() - 1) + "___");
+ }
+ }
+ }
+
+ // in all other cases kill the comment
+ css = css.replace("/*" + placeholder + "*/", "");
+ }
+
+
+ // Normalize all whitespace strings to single spaces. Easier to work with that way.
+ css = css.replaceAll("\\s+", " ");
+
+ // Remove the spaces before the things that should not have spaces before them.
+ // But, be careful not to turn "p :link {...}" into "p:link{...}"
+ // Swap out any pseudo-class colons with the token, and then swap back.
+ sb = new StringBuffer();
+ p = Pattern.compile("(^|\\})(([^\\{:])+:)+([^\\{]*\\{)");
+ m = p.matcher(css);
+ while (m.find()) {
+ String s = m.group();
+ s = s.replaceAll(":", "___YUICSSMIN_PSEUDOCLASSCOLON___");
+ s = s.replaceAll( "\\\\", "\\\\\\\\" ).replaceAll( "\\$", "\\\\\\$" );
+ m.appendReplacement(sb, s);
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+ // Remove spaces before the things that should not have spaces before them.
+ css = css.replaceAll("\\s+([!{};:>+\\(\\)\\],])", "$1");
+ // bring back the colon
+ css = css.replaceAll("___YUICSSMIN_PSEUDOCLASSCOLON___", ":");
+
+ // retain space for special IE6 cases
+ css = css.replaceAll(":first\\-(line|letter)(\\{|,)", ":first-$1 $2");
+
+ // no space after the end of a preserved comment
+ css = css.replaceAll("\\*/ ", "*/");
+
+ // If there is a @charset, then only allow one, and push to the top of the file.
+ css = css.replaceAll("^(.*)(@charset \"[^\"]*\";)", "$2$1");
+ css = css.replaceAll("^(\\s*@charset [^;]+;\\s*)+", "$1");
+
+ // Put the space back in some cases, to support stuff like
+ // @media screen and (-webkit-min-device-pixel-ratio:0){
+ css = css.replaceAll("\\band\\(", "and (");
+
+ // Remove the spaces after the things that should not have spaces after them.
+ css = css.replaceAll("([!{}:;>+\\(\\[,])\\s+", "$1");
+
+ // remove unnecessary semicolons
+ css = css.replaceAll(";+}", "}");
+
+ // Replace 0(px,em,%) with 0.
+ css = css.replaceAll("([\\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", "$1$2");
+
+ // Replace 0 0 0 0; with 0.
+ css = css.replaceAll(":0 0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0 0(;|})", ":0$1");
+ css = css.replaceAll(":0 0(;|})", ":0$1");
+
+
+ // Replace background-position:0; with background-position:0 0;
+ // same for transform-origin
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(background-position|transform-origin|webkit-transform-origin|moz-transform-origin|o-transform-origin|ms-transform-origin):0(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0 0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Replace 0.6 to .6, but only when preceded by : or a white-space
+ css = css.replaceAll("(:|\\s)0+\\.(\\d+)", "$1.$2");
+
+ // Shorten colors from rgb(51,102,153) to #336699
+ // This makes it more likely that it'll get further compressed in the next step.
+ p = Pattern.compile("rgb\\s*\\(\\s*([0-9,\\s]+)\\s*\\)");
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ while (m.find()) {
+ String[] rgbcolors = m.group(1).split(",");
+ StringBuffer hexcolor = new StringBuffer("#");
+ for (i = 0; i < rgbcolors.length; i++) {
+ int val = Integer.parseInt(rgbcolors[i]);
+ if (val < 16) {
+ hexcolor.append("0");
+ }
+ hexcolor.append(Integer.toHexString(val));
+ }
+ m.appendReplacement(sb, hexcolor.toString());
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // Shorten colors from #AABBCC to #ABC. Note that we want to make sure
+ // the color is not preceded by either ", " or =. Indeed, the property
+ // filter: chroma(color="#FFFFFF");
+ // would become
+ // filter: chroma(color="#FFF");
+ // which makes the filter break in IE.
+ p = Pattern.compile("([^\"'=\\s])(\\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])");
+ m = p.matcher(css);
+ sb = new StringBuffer();
+ while (m.find()) {
+ // Test for AABBCC pattern
+ if (m.group(3).equalsIgnoreCase(m.group(4)) &&
+ m.group(5).equalsIgnoreCase(m.group(6)) &&
+ m.group(7).equalsIgnoreCase(m.group(8))) {
+ m.appendReplacement(sb, (m.group(1) + m.group(2) + "#" + m.group(3) + m.group(5) + m.group(7)).toLowerCase());
+ } else {
+ m.appendReplacement(sb, m.group().toLowerCase());
+ }
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // border: none -> border:0
+ sb = new StringBuffer();
+ p = Pattern.compile("(?i)(border|border-top|border-right|border-bottom|border-right|outline|background):none(;|})");
+ m = p.matcher(css);
+ while (m.find()) {
+ m.appendReplacement(sb, m.group(1).toLowerCase() + ":0" + m.group(2));
+ }
+ m.appendTail(sb);
+ css = sb.toString();
+
+ // shorter opacity IE filter
+ css = css.replaceAll("(?i)progid:DXImageTransform.Microsoft.Alpha\\(Opacity=", "alpha(opacity=");
+
+ // Remove empty rules.
+ css = css.replaceAll("[^\\}\\{/;]+\\{\\}", "");
+
+ if (linebreakpos >= 0) {
+ // Some source control tools don't like it when files containing lines longer
+ // than, say 8000 characters, are checked in. The linebreak option is used in
+ // that case to split long lines after a specific column.
+ i = 0;
+ int linestartpos = 0;
+ sb = new StringBuffer(css);
+ while (i < sb.length()) {
+ char c = sb.charAt(i++);
+ if (c == '}' && i - linestartpos > linebreakpos) {
+ sb.insert(i, '\n');
+ linestartpos = i;
+ }
+ }
+
+ css = sb.toString();
+ }
+
+ // Replace multiple semi-colons in a row by a single one
+ // See SF bug #1980989
+ css = css.replaceAll(";;+", ";");
+
+ // restore preserved comments and strings
+ for(i = 0, max = preservedTokens.size(); i < max; i++) {
+ css = css.replace("___YUICSSMIN_PRESERVED_TOKEN_" + i + "___", preservedTokens.get(i).toString());
+ }
+
+ // Trim the final string (for any leading or trailing white spaces)
+ css = css.trim();
+
+ // Write the output...
+ out.write(css);
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.