diff --git a/app/press/Compressor.java b/app/press/Compressor.java index 0f686c4..edf51e9 100644 --- a/app/press/Compressor.java +++ b/app/press/Compressor.java @@ -1,5 +1,6 @@ package press; +import java.io.StringReader; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -17,6 +18,7 @@ import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import play.Play; import play.PlayPlugin; import play.cache.Cache; @@ -99,7 +101,14 @@ public String compressedSingleFileUrl(FileCompressor compressor, String fileName int lastDot = fileName.lastIndexOf('.'); String compressedFileName = fileName.substring(0, lastDot) + ".min"; - compressedFileName += fileName.substring(lastDot); + /*if (fileName.endsWith(".less")){ + + compressedFileName += ".less.css"; + } + else { + */ + compressedFileName += fileName.substring(lastDot); + //} // The process for compressing a single file is the same as for a group // of files, the list just has a single entry @@ -397,8 +406,24 @@ private static void compress(FileCompressor compressor, FileInfo fileInfo, Write throws Exception { String fileName = fileInfo.file.getName(); - BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream( + BufferedReader in; + + if (fileName.endsWith(".less")) { + + LessCompiler lessCompiler = new LessCompiler(Play.mode == Play.Mode.DEV); + String css = lessCompiler.compile(fileInfo.file); + in = new BufferedReader(new StringReader(css)); + } + else { + + in = new BufferedReader(new InputStreamReader(new FileInputStream( fileInfo.file), "UTF-8")); + } + + if (fileName.endsWith(".css") || fileName.endsWith(".less")) { + + in = handleRelativeimages(in, fileInfo.file); + } // If the file should be compressed if (fileInfo.compress) { @@ -413,6 +438,66 @@ private static void compress(FileCompressor compressor, FileInfo fileInfo, Write } } + private static BufferedReader handleRelativeimages(BufferedReader in, File file) throws Exception { + + String filePath = file.getAbsolutePath(); + filePath = filePath.substring(filePath.lastIndexOf(PluginConfig.addTrailingSlash(PluginConfig.css.srcDir))); + + StringBuilder sb = new StringBuilder(); + int c; + while ((c = in.read()) != -1) { + sb.append((char) c); + } + + String css = sb.toString(); + + + String[] patterns = {"(url\\s*\\(\\s*[\"']?)(.*?)([\"']?\\s*\\))" + , "(src\\s*=\\s*[\"']?)(.*?)([\"']?\\s*[,\\)])"}; + + for (int n=0;n getImportsFromCacheOrFile(File lessFile) { + String cacheKey = "less_imports_" + lessFile.getPath() + lessFile.lastModified(); + + @SuppressWarnings("unchecked") + Set files = Cache.get(cacheKey, Set.class); + + if(files == null) { + try { + files = getImportsFromFile(lessFile); + Cache.set(cacheKey, files); + } catch(IOException e) { + Logger.error(e, "IOException trying to determine imports in LESS file"); + files = new HashSet(); + } + } + return files; + } + + protected Set getImportsFromFile(File lessFile) throws IOException { + BufferedReader r = new BufferedReader(new FileReader(lessFile)); + + Set files = new HashSet(); + String line; + while ((line = r.readLine()) != null) { + Matcher m = importPattern.matcher(line); + while (m.find()) { + File file = new File(lessFile.getParentFile(), m.group(1)); + files.add(file); + files.addAll(getImportsFromCacheOrFile(file)); + } + } + return files; + } + + protected String compile(File lessFile) { + try { + String css = lessEngine.compile(lessFile); + css = css.replaceAll("\\\\n", ""); + return css; + } catch (LessException e) { + return handleException(lessFile, e); + } + } + + public String compile(String lessContent) { + try { + String css = lessEngine.compile(lessContent); + css = css.replaceAll("\\\\n", ""); + return css; + } catch (LessException e) { + return handleException(null, e); + } + } + + public BufferedReader compile(BufferedReader in) throws IOException { + + StringBuilder sb = new StringBuilder(); + String line; + while ((line = in.readLine()) != null) { + sb.append(line); + } + + String css = compile(sb.toString()); + css = css.replaceAll("\\\\n", ""); + + + return new BufferedReader(new StringReader(css)); + } + + public String handleException(File lessFile, LessException e) { + Logger.warn(e, "Less exception"); + + String filename = e.getFilename(); + List extractList = e.getExtract(); + String extract = null; + if(extractList != null) { + extract = extractList.toString(); + } + + // LessEngine reports the file as null when it's not an @imported file + if(filename == null) { + filename = lessFile.getName(); + } + + // Try to detect missing imports (flaky) + if(extract == null && e.getCause() instanceof WrappedException) { + WrappedException we = (WrappedException) e.getCause(); + if(we.getCause() instanceof FileNotFoundException) { + FileNotFoundException fnfe = (FileNotFoundException) we.getCause(); + extract = fnfe.getMessage(); + } + } + + return formatMessage(filename, e.getLine(), e.getColumn(), extract, e.getErrorType()); + } + + public String formatMessage(String filename, int line, int column, String extract, String errorType) { + return "body:before {display: block; color: #c00; white-space: pre; font-family: monospace; background: #FDD9E1; border-top: 1px solid pink; border-bottom: 1px solid pink; padding: 10px; content: \"[LESS ERROR] " + + String.format("%s:%s: %s (%s)", filename, line, extract, errorType) + + "\"; }"; + } +} \ No newline at end of file diff --git a/app/press/Plugin.java b/app/press/Plugin.java index 7719065..a649e78 100644 --- a/app/press/Plugin.java +++ b/app/press/Plugin.java @@ -1,14 +1,27 @@ package press; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.ETAG; +import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.LAST_MODIFIED; + +import java.io.PrintStream; +import java.util.Date; + import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; +import play.Play; import play.PlayPlugin; import play.mvc.Router; import press.io.FileIO; import press.io.PressFileGlobber; +import play.mvc.Http; +import play.mvc.Http.Request; +import play.mvc.Http.Response; +import play.utils.Utils; +import play.vfs.VirtualFile; + public class Plugin extends PlayPlugin { static ThreadLocal jsCompressor = new ThreadLocal(); static ThreadLocal cssCompressor = new ThreadLocal(); @@ -64,7 +77,7 @@ public static String addSingleCSS(String fileName) { checkCSSFileExists(fileName); CSSCompressor compressor = cssCompressor.get(); String src = null; - if (performCompression()) { + if (performCompression() || fileName.endsWith(".less")) { String requestKey = compressor.compressedSingleFileUrl(fileName); if (PluginConfig.isInMemoryStorage()) { src = getSingleCompressedCSSUrl(requestKey); @@ -241,8 +254,7 @@ private static String getCompressedUrl(String action, String requestKey) { */ private static String getScriptTag(String src) { return "\n"; + + "\" type=\"text/javascript\" language=\"javascript\" charset=\"utf-8\">\n"; } /** @@ -253,4 +265,71 @@ private static String getLinkTag(String src) { return "" + (press.PluginConfig.htmlCompatible ? "" : "") + "\n"; } + + + + LessCompiler lessCompiler; + boolean useEtag = true; + @Override + public void onLoad() { + lessCompiler = new LessCompiler(Play.mode == Play.Mode.DEV); + useEtag = Play.configuration.getProperty("http.useETag", "true").equals("true"); + } + + @Override + public boolean serveStatic(VirtualFile file, Request request, Response response) { + + if(file.getName().endsWith(".less")) { + response.contentType = "text/css"; + try { + handleResponse(file, request, response); + } catch(Exception e) { + response.status = 500; + response.print("Bugger, the LESS processing failed:,\n"); + e.printStackTrace(new PrintStream(response.out)); + } + return true; + } + return super.serveStatic(file, request, response); + } + + private void handleResponse(VirtualFile file, Request request, Response response) { + long lastModified = lessCompiler.lastModifiedRecursive(file.getRealFile()); + final String etag = "\"" + lastModified + "-" + file.hashCode() + "\""; + + if(!request.isModified(etag, lastModified)) { + handleNotModified(request, response, etag); + } else { + handleOk(request, response, file, etag, lastModified); + } + } + + private void handleNotModified(Request request, Response response, String etag) { + if (request.method.equals("GET")) { + response.status = Http.StatusCode.NOT_MODIFIED; + } + if (useEtag) { + response.setHeader(ETAG, etag); + } + } + + private void handleOk(Request request, Response response, VirtualFile file, String etag, long lastModified) { + response.status = 200; + response.print(lessCompiler.get(file.getRealFile())); + response.setHeader(LAST_MODIFIED, Utils.getHttpDateFormatter().format(new Date(lastModified))); + if (useEtag) { + response.setHeader(ETAG, etag); + } + } + + + + + + + + + } + + diff --git a/app/press/PluginConfig.java b/app/press/PluginConfig.java index dc50635..f6f863e 100644 --- a/app/press/PluginConfig.java +++ b/app/press/PluginConfig.java @@ -19,7 +19,7 @@ public static class DefaultConfig { public static final boolean cacheClearEnabled = (Play.mode == Mode.DEV); // Whether to use the file system or memory to store compressed files - public static final boolean inMemoryStorage = false; + public static final boolean inMemoryStorage = true; // The amount of time that a compression key is stored for. // This only needs to be as long as the time between when the action diff --git a/commands.pyc b/commands.pyc index 13759ef..328c8f3 100644 Binary files a/commands.pyc and b/commands.pyc differ diff --git a/dist/press-.zip b/dist/press-.zip new file mode 100644 index 0000000..0208a5e Binary files /dev/null and b/dist/press-.zip differ diff --git a/lib/lesscss-engine-1.0.43.jar b/lib/lesscss-engine-1.0.43.jar new file mode 100644 index 0000000..8e9d377 Binary files /dev/null and b/lib/lesscss-engine-1.0.43.jar differ diff --git a/lib/play-press.jar b/lib/play-press.jar new file mode 100644 index 0000000..d1aea9b Binary files /dev/null and b/lib/play-press.jar differ diff --git a/lib/yuicompressor-2.4.6.jar b/lib/yuicompressor-2.4.7.jar similarity index 92% rename from lib/yuicompressor-2.4.6.jar rename to lib/yuicompressor-2.4.7.jar index 61f6318..3c9a408 100644 Binary files a/lib/yuicompressor-2.4.6.jar and b/lib/yuicompressor-2.4.7.jar differ