PK ?E.V V build.properties# change the play.path to where your play framework is installed
play.path=../../playPK ?H build.xml
PK ?zjo5 5 commands.py# Here you can create play commands that are specific to the module, and extend existing commands
MODULE = 'press'
# Commands that are specific to your module
COMMANDS = ['press:hello']
def execute(**kargs):
command = kargs.get("command")
app = kargs.get("app")
args = kargs.get("args")
env = kargs.get("env")
if command == "press:hello":
print "~ Hello"
# This will be executed before any command (new, run...)
def before(**kargs):
command = kargs.get("command")
app = kargs.get("app")
args = kargs.get("args")
env = kargs.get("env")
# This will be executed after any command (new, run...)
def after(**kargs):
command = kargs.get("command")
app = kargs.get("app")
args = kargs.get("args")
env = kargs.get("env")
if command == "new":
pass
PK @> commands.pyc
%)Kc @ s. d Z d g Z d Z d Z d Z d S( t presss press:helloc K sV | i d } | i d } | i d } | i d } | d j o d GHn d S( Nt commandt appt argst envs press:hellos ~ Hello( t get( t kargsR R R R ( ( s( /Users/dirk/dev/ponele/press/commands.pyt execute s
c K s@ | i d } | i d } | i d } | i d } d S( NR R R R ( R ( R R R R R ( ( s( /Users/dirk/dev/ponele/press/commands.pyt before s c K sQ | i d } | i d } | i d } | i d } | d j o n d S( NR R R R t new( R ( R R R R R ( ( s( /Users/dirk/dev/ponele/press/commands.pyt after s
N( t MODULEt COMMANDSR R R
( ( ( s( /Users/dirk/dev/ponele/press/commands.pyt s PK @R" " manifestversion=1.0.27
frameworkVersions=
PK ?%w app/play.plugins100:press.PluginPK @v app/controllers/press/Press.javapackage controllers.press;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import play.exceptions.UnexpectedException;
import play.mvc.Controller;
import press.CachingStrategy;
import press.PluginConfig;
import press.ScriptCompressor;
import press.ScriptRequestHandler;
import press.StyleCompressor;
import press.StyleRequestHandler;
import press.io.CompressedFile;
import press.io.FileIO;
public class Press extends Controller {
public static final DateTimeFormatter httpDateTimeFormatter = DateTimeFormat
.forPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'");
public static void getCompressedJS(String key) {
key = FileIO.unescape(key);
CompressedFile compressedFile = new ScriptCompressor().getCompressedFile(key);
renderCompressedFile(compressedFile, "JavaScript");
}
public static void getCompressedCSS(String key) {
key = FileIO.unescape(key);
CompressedFile compressedFile = new StyleCompressor().getCompressedFile(key);
renderCompressedFile(compressedFile, "CSS");
}
public static void getSingleCompressedJS(String key) {
key = FileIO.unescape(key);
CompressedFile compressedFile = new ScriptCompressor().getSingleCompressedFile(key);
renderCompressedFile(compressedFile, "JavaScript");
}
public static void getSingleCompressedCSS(String key) {
key = FileIO.unescape(key);
CompressedFile compressedFile = new StyleCompressor().getSingleCompressedFile(key);
renderCompressedFile(compressedFile, "CSS");
}
private static void renderCompressedFile(CompressedFile compressedFile, String type) {
flash.keep();
if (compressedFile == null) {
renderBadResponse(type);
}
InputStream inputStream = compressedFile.inputStream();
// This seems to be buggy, so instead of passing the file length we
// reset the input stream and allow play to manually copy the bytes from
// the input stream to the response
// renderBinary(inputStream, compressedFile.name(),
// compressedFile.length());
try {
if (inputStream.markSupported()) {
inputStream.reset();
}
} catch (IOException e) {
throw new UnexpectedException(e);
}
// If the caching strategy is always, the timestamp is not part of the
// key. If we let the browser cache, then the browser will keep holding
// old copies, even after changing the files at the server and
// restarting the server, since the
// key will stay the same.
// If the caching strategy is never, we also don't want to cache at the
// browser, for obvious reasons.
// If the caching strategy is Change, then the modified timestamp is a
// part of the key, so if the file changes, the key in the html file
// will be modified, and the browser will request a new version. Each
// version can therefore be cached indefinitely.
if (PluginConfig.cache.equals(CachingStrategy.Change)) {
// Cache for a year
response.setHeader("Cache-Control", "max-age=" + 31536000);
response.setHeader("Expires", httpDateTimeFormatter.print(new DateTime().plusYears(1)));
}
renderBinary(inputStream, compressedFile.name());
}
public static void clearJSCache() {
if (!PluginConfig.cacheClearEnabled) {
forbidden();
}
int count = ScriptRequestHandler.clearCache();
renderText("Cleared " + count + " JS files from cache");
}
public static void clearCSSCache() {
if (!PluginConfig.cacheClearEnabled) {
forbidden();
}
int count = StyleRequestHandler.clearCache();
renderText("Cleared " + count + " CSS files from cache");
}
private static void renderBadResponse(String fileType) {
String response = "/*\n";
response += "The compressed " + fileType + " file could not be generated.\n";
response += "This can occur in two situations:\n";
response += "1. The time between when the page was rendered by the ";
response += "server and when the browser requested the compressed ";
response += "file was greater than the timeout. (The timeout is ";
response += "currently configured to be ";
response += PluginConfig.compressionKeyStorageTime + ")\n";
response += "2. There was an exception thrown while rendering the ";
response += "page.\n";
response += "*/";
renderBinaryResponse(response);
}
private static void renderBinaryResponse(String response) {
try {
renderBinary(new ByteArrayInputStream(response.getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
throw new UnexpectedException(e);
}
}
}PK ?g app/press/CachingStrategy.javapackage press;
import java.util.Arrays;
import java.util.List;
import play.templates.JavaExtensions;
public enum CachingStrategy {
Always, Never, Change;
public static CachingStrategy parse(String name) {
String lcName = name.trim().toLowerCase();
for (CachingStrategy strategy : CachingStrategy.values()) {
if (strategy.toString().toLowerCase().equals(lcName)) {
return strategy;
}
}
String msg = "Could not parse caching strategy name from '" + name + "'. ";
List strategies = Arrays.asList(CachingStrategy.values());
msg += "Caching strategy must be one of " + JavaExtensions.join(strategies, ", ");
throw new RuntimeException(msg);
}
}
PK @28 app/press/Compressor.javapackage press;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import play.PlayPlugin;
import play.exceptions.UnexpectedException;
import play.libs.Crypto;
import play.templates.JavaExtensions;
import play.vfs.VirtualFile;
import press.io.CompressedFile;
import press.io.FileIO;
public abstract class Compressor extends PlayPlugin {
static final String PRESS_SIGNATURE = "press-1.0";
static final String PATTERN_TEXT = "^/\\*" + PRESS_SIGNATURE + "\\*/$";
static final Pattern HEADER_PATTERN = Pattern.compile(PATTERN_TEXT);
// Directory where the source files are read from, eg "/public/javascripts"
String srcDir;
// Directory for the compressed output, eg "/public/javascripts/press/js"
String compressedDir;
// The extension of the output file, eg ".js"
private String extension;
public Compressor(String srcDir, String compressedDir, String extension) {
this.srcDir = PluginConfig.addTrailingSlash(srcDir);
this.compressedDir = PluginConfig.addTrailingSlash(compressedDir);
this.extension = extension;
}
/**
* Called when compressing a single file (as opposed to a group of files).
* Compresses the file on the fly, and returns the url to it. The file will
* have the same name as the original file with .min appended before the
* extension. eg the compressed output of widget.js will be in widget.min.js
*/
public String compressedSingleFileUrl(String fileName) {
PressLogger.trace("Request to compress single file %s", fileName);
int lastDot = fileName.lastIndexOf('.');
String compressedFileName = fileName.substring(0, lastDot) + ".min";
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
VirtualFile srcFile = FileIO.checkFileExists(fileName, srcDir);
List componentFiles = new ArrayList(1);
componentFiles.add(new FileInfo(compressedFileName, true, srcFile));
// Check whether the compressed file needs to be generated
String outputFilePath = compressedDir + compressedFileName;
CompressedFile outputFile = CompressedFile.create(outputFilePath);
if (outputFile.exists() && useCache()) {
// If not, return the existing file
PressLogger.trace("File has already been compressed");
} else {
// If so, generate it
writeCompressedFile(componentFiles, outputFile);
}
return outputFilePath;
}
public CompressedFile getSingleCompressedFile(String requestKey) {
// The compressed file has already been created, just retrieve its
// contents
return CompressedFile.create(requestKey);
}
public CompressedFile getCompressedFile(String key) {
List componentFiles = SourceFileManager.getSourceFiles(key);
// If there was nothing found for the given request key, return null.
// This shouldn't happen unless there was a very long delay between the
// template being rendered and the compressed file being requested
if (componentFiles == null) {
return null;
}
return getCompressedFile(componentFiles, compressedDir);
}
protected CompressedFile getCompressedFile(List componentFiles, String compressedDir) {
String joinedFileNames = null;
if (PluginConfig.cache.equals(CachingStrategy.Change)) {
joinedFileNames = JavaExtensions.join(
FileInfo.getFileNamesAndModifiedTimestamps(componentFiles), "");
} else {
joinedFileNames = JavaExtensions.join(FileInfo.getFileNames(componentFiles), "");
}
String fileName = Crypto.passwordHash(joinedFileNames);
fileName = FileIO.lettersOnly(fileName);
String filePath = compressedDir + fileName + extension;
CompressedFile file = null;
file = CompressedFile.create(filePath);
// If the file already exists in the cache, return it
boolean exists = file.exists();
if (exists && useCache()) {
PressLogger.trace("Using existing compressed file %s", filePath);
return file;
}
if (!exists) {
PressLogger.trace("Compressed file %s does not yet exist", filePath);
}
PressLogger.trace("Generating compressed file %s from %d component files", filePath,
componentFiles.size());
writeCompressedFile(componentFiles, file);
return file;
}
private void writeCompressedFile(List componentFiles, CompressedFile file) {
long timeStart = System.currentTimeMillis();
// If the file is being written by another thread, startWrite() will
// block until it is complete and then return null
Writer writer = file.startWrite();
if (writer == null) {
PressLogger.trace("Compressed file was generated by another thread");
return;
}
try {
writer.append(createFileHeader());
for (FileInfo componentFile : componentFiles) {
compress(componentFile, writer);
}
long timeAfter = System.currentTimeMillis();
PressLogger.trace("Time to compress files for '%s': %d milli-seconds",
FileIO.getFileNameFromPath(file.name()), (timeAfter - timeStart));
} catch (Exception e) {
throw new UnexpectedException(e);
} finally {
// Note that this flushes and closes the writer as well
file.close();
}
}
private void compress(FileInfo fileInfo, Writer out) throws Exception {
String fileName = fileInfo.file.getName();
// If the file should be compressed
if (fileInfo.compress) {
// Invoke the compressor
PressLogger.trace("Compressing %s", fileName);
compress(fileInfo.file, out);
} else {
// Otherwise just copy it
PressLogger.trace("Adding already compressed file %s", fileName);
FileIO.write(FileIO.getReader(fileInfo.file), out);
}
}
public static int clearCache(String compressedDir, String extension) {
return CompressedFile.clearCache(compressedDir, extension);
}
private static boolean useCache() {
PressLogger.trace("Caching strategy is %s", PluginConfig.cache);
if (PluginConfig.cache.equals(CachingStrategy.Never)) {
return false;
}
// If the caching strategy is Change, we can still use the cache,
// because we included the modification timestamp in the key name, so
// same key means that it is not modified.
return true;
}
public static String createFileHeader() {
return "/*" + PRESS_SIGNATURE + "*/\n";
}
public static boolean hasPressHeader(CompressedFile file) {
try {
if (!file.exists()) {
return false;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(file.inputStream()));
String firstLine = reader.readLine();
Matcher matcher = HEADER_PATTERN.matcher(firstLine);
if (matcher.matches()) {
return true;
}
return false;
} catch (IOException e) {
return false;
}
}
abstract protected void compress(File file, Writer out) throws IOException;
}
PK ?GJ app/press/ConfigHelper.javapackage press;
import play.Play;
import play.exceptions.ConfigurationException;
public class ConfigHelper {
public static String getString(String configKey) {
return Play.configuration.getProperty(configKey, null);
}
public static String getString(String configKey, String defaultValue) {
String value = Play.configuration.getProperty(configKey);
if (value == null || value.length() == 0) {
return defaultValue;
}
return value;
}
public static Boolean getBoolean(String configKey) {
return getBoolean(configKey, null);
}
public static Boolean getBoolean(String configKey, Boolean defaultValue) {
String asStr = Play.configuration.getProperty(configKey);
if (asStr == null || asStr.length() == 0) {
return defaultValue;
}
if (asStr.equals("true") || asStr.equals("false")) {
return Boolean.parseBoolean(asStr);
}
throw new ConfigurationException(configKey + " must be either true or false");
}
public static Integer getInt(String configKey) {
return getInt(configKey, null);
}
public static Integer getInt(String configKey, Integer defaultValue) {
String asStr = Play.configuration.getProperty(configKey);
if (asStr == null || asStr.length() == 0) {
return defaultValue;
}
return Integer.parseInt(asStr);
}
}
PK ?w_y y % app/press/DuplicateFileException.javapackage press;
@SuppressWarnings("serial")
public class DuplicateFileException extends PressException {
public DuplicateFileException(String fileType, String fileName, String tagName) {
super(buildMessage(fileType, fileName, tagName));
}
private static String buildMessage(String fileType, String fileName, String tagName) {
String msg = "Attempt to add the same " + fileType + " file ";
msg += "to compression twice: '" + fileName + "'\n";
msg += "Check that you're not including the same file in two different ";
msg += tagName + " tags.";
return msg;
}
}
PK @$i app/press/FileInfo.javapackage press;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import play.vfs.VirtualFile;
public class FileInfo implements Serializable {
String fileName;
boolean compress;
public File file;
public FileInfo(String fileName, boolean compress, VirtualFile file) {
this.fileName = fileName;
this.compress = compress;
this.file = file == null ? null : file.getRealFile();
}
public long getLastModified() {
return file.lastModified();
}
public static Collection getFileNames(List list) {
Collection fileNames = new ArrayList(list.size());
for (FileInfo fileInfo : list) {
fileNames.add(fileInfo.fileName);
}
return fileNames;
}
public static Collection getFileNamesAndModifiedTimestamps(List list) {
Collection fileNamesAndModifiedTimestamps = new ArrayList(list.size());
for (FileInfo fileInfo : list) {
fileNamesAndModifiedTimestamps.add(fileInfo.fileName + fileInfo.getLastModified());
}
return fileNamesAndModifiedTimestamps;
}
}
PK @d app/press/Plugin.javapackage press;
import java.lang.reflect.Method;
import play.PlayPlugin;
public class Plugin extends PlayPlugin {
static ThreadLocal rqManager = new ThreadLocal();
@Override
public void onApplicationStart() {
// Read the config each time the application is restarted
PluginConfig.readConfig();
// Clear the asset cache
RequestManager.clearCache();
}
@Override
public void beforeActionInvocation(Method actionMethod) {
// Before each action, reinitialize variables
rqManager.set(new RequestManager());
}
/**
* Add a single JS file to compression
*/
public static String addSingleJS(String fileName) {
return rqManager.get().addSingleFile(RequestManager.RQ_TYPE_SCRIPT, fileName);
}
/**
* Add a single CSS file to compression
*/
public static String addSingleCSS(String fileName) {
return rqManager.get().addSingleFile(RequestManager.RQ_TYPE_STYLE, fileName);
}
/**
* Adds the given source file(s) to the JS compressor, returning the file
* signature to be output in HTML
*/
public static String addJS(String src, boolean packFile) {
return rqManager.get().addMultiFile(RequestManager.RQ_TYPE_SCRIPT, src, packFile);
}
/**
* Adds the given source file(s) to the CSS compressor, returning the file
* signature to be output in HTML
*/
public static String addCSS(String src, boolean packFile) {
return rqManager.get().addMultiFile(RequestManager.RQ_TYPE_STYLE, src, packFile);
}
/**
* Outputs the tag indicating where the compressed JS should be included.
*/
public static String compressedJSTag() {
return rqManager.get().compressedTag(RequestManager.RQ_TYPE_SCRIPT);
}
/**
* Outputs the tag indicating where the compressed CSS should be included.
*/
public static String compressedCSSTag() {
return rqManager.get().compressedTag(RequestManager.RQ_TYPE_STYLE);
}
@Override
public void afterActionInvocation() {
// When the action finishes, save the list of files added for
// compression
if (rqManager.get() != null) {
rqManager.get().saveFileList();
}
rqManager.set(null);
}
@Override
public void onInvocationException(Throwable e) {
if (rqManager.get() != null) {
rqManager.get().errorOccurred();
}
}
}
PK ?h8R app/press/PluginConfig.javapackage press;
import play.Play;
import play.Play.Mode;
public class PluginConfig {
/**
* Stores the default configuration for the plugin
*/
public static class DefaultConfig {
// Whether the plugin is enabled
public static final boolean enabled = (Play.mode == Mode.PROD);
// The caching strategy
public static final CachingStrategy cache = CachingStrategy.Change;
// Whether the cache can be cleared through the web interface
// Default is to be available in dev only
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;
// 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
// finishes and the browser requests the compressed javascript (usually
// less than a second)
public static final String compressionKeyStorageTime = "2mn";
// The maximum amount of time in milli-seconds allowed for compression
// to occur before a timeout exception is thrown.
public static final int maxCompressionTimeMillis = 60000;
// Indicates whether the code output by press is compatible with the
// HTML standard. For example HTML requires that a closing LINK tag MUST
// NOT be output, while XHTML requires that it MUST be output
public static final boolean htmlCompatible = false;
// The domain which is storing the content. Can be set for use with a CDN.
// Will be used to turn a relative URI into an absolute URL.
public static final String contentHostingDomain = "";
public static class js {
// The directory where source javascript files are read from
public static final String srcDir = "/public/javascripts/";
// The directory where compressed javascript files are written to
public static final String compressedDir = "/public/javascripts/press/";
// Options for YUI JS compression
public static final int lineBreak = -1;
public static final boolean munge = true;
public static final boolean warn = false;
public static final boolean preserveAllSemiColons = false;
public static final boolean preserveStringLiterals = false;
}
public static class css {
// The directory where source css files are read from
public static final String srcDir = "/public/stylesheets/";
// The directory where compressed css files are written to
public static final String compressedDir = "/public/stylesheets/press/";
// Options for YUI CSS compression
public static final int lineBreak = -1;
}
}
public static boolean enabled;
public static CachingStrategy cache;
public static boolean cacheClearEnabled;
public static boolean inMemoryStorage;
public static String compressionKeyStorageTime;
public static int maxCompressionTimeMillis;
public static boolean htmlCompatible;
public static String contentHostingDomain;
public static class js {
public static String srcDir = DefaultConfig.js.srcDir;
public static String compressedDir = DefaultConfig.js.compressedDir;
// YUI JS compression options
public static int lineBreak = DefaultConfig.js.lineBreak;
public static boolean munge = DefaultConfig.js.munge;
public static boolean warn = DefaultConfig.js.warn;
public static boolean preserveAllSemiColons = DefaultConfig.js.preserveAllSemiColons;
public static boolean preserveStringLiterals = DefaultConfig.js.preserveStringLiterals;
}
public static class css {
public static String srcDir = DefaultConfig.css.srcDir;
public static String compressedDir = DefaultConfig.css.compressedDir;
public static int lineBreak = DefaultConfig.css.lineBreak;
}
// Required to make the class loader happy
public static boolean isInMemoryStorage() {
return inMemoryStorage;
}
/**
* Reads from the config file into memory. If the config file doesn't exist
* or is deleted, uses the default values.
*/
public static void readConfig() {
PressLogger.trace("Loading Press plugin configuration");
// press options
enabled = ConfigHelper.getBoolean("press.enabled", DefaultConfig.enabled);
String cacheDefault = DefaultConfig.cache.toString();
cache = CachingStrategy.parse(ConfigHelper.getString("press.cache", cacheDefault));
cacheClearEnabled = ConfigHelper.getBoolean("press.cache.clearEnabled",
DefaultConfig.cacheClearEnabled);
inMemoryStorage = ConfigHelper.getBoolean("press.inMemoryStorage", DefaultConfig.inMemoryStorage);
compressionKeyStorageTime = ConfigHelper.getString("press.key.lifetime",
DefaultConfig.compressionKeyStorageTime);
maxCompressionTimeMillis = ConfigHelper.getInt("press.compression.maxTimeMillis",
DefaultConfig.maxCompressionTimeMillis);
htmlCompatible = ConfigHelper.getBoolean("press.htmlCompatible",
DefaultConfig.htmlCompatible);
contentHostingDomain = ConfigHelper.getString("press.contentHostingDomain",
DefaultConfig.contentHostingDomain);
css.srcDir = ConfigHelper.getString("press.css.sourceDir", DefaultConfig.css.srcDir);
css.compressedDir = ConfigHelper.getString("press.css.outputDir",
DefaultConfig.css.compressedDir);
js.srcDir = ConfigHelper.getString("press.js.sourceDir", DefaultConfig.js.srcDir);
js.compressedDir = ConfigHelper.getString("press.js.outputDir",
DefaultConfig.js.compressedDir);
// YUI options
css.lineBreak = ConfigHelper.getInt("press.yui.css.lineBreak", DefaultConfig.css.lineBreak);
js.lineBreak = ConfigHelper.getInt("press.yui.js.lineBreak", DefaultConfig.js.lineBreak);
js.munge = ConfigHelper.getBoolean("press.yui.js.munge", DefaultConfig.js.munge);
js.warn = ConfigHelper.getBoolean("press.yui.js.warn", DefaultConfig.js.warn);
js.preserveAllSemiColons = ConfigHelper.getBoolean("press.yui.js.preserveAllSemiColons",
DefaultConfig.js.preserveAllSemiColons);
js.preserveStringLiterals = ConfigHelper.getBoolean("press.yui.js.preserveStringLiterals",
DefaultConfig.js.preserveStringLiterals);
// Add a trailing slash to directories, if necessary
css.srcDir = addTrailingSlash(css.srcDir);
css.compressedDir = addTrailingSlash(css.compressedDir);
js.srcDir = addTrailingSlash(js.srcDir);
js.compressedDir = addTrailingSlash(js.compressedDir);
// Log the newly loaded configuration
logConfig();
}
private static void logConfig() {
PressLogger.trace("enabled: %b", enabled);
PressLogger.trace("caching strategy: %s", cache);
PressLogger.trace("cache publicly clearable: %s", cacheClearEnabled);
PressLogger.trace("in memory storage: %s", inMemoryStorage);
PressLogger.trace("compression key storage time: %s", compressionKeyStorageTime);
PressLogger.trace("HTML compatible: %b", htmlCompatible);
PressLogger.trace("css source directory: %s", css.srcDir);
PressLogger.trace("css compressed output directory: %s", css.compressedDir);
PressLogger.trace("js source directory: %s", js.srcDir);
PressLogger.trace("js compressed output directory: %s", js.compressedDir);
PressLogger.trace("YUI css line break: %d", css.lineBreak);
PressLogger.trace("YUI js line break: %d", js.lineBreak);
PressLogger.trace("YUI js munge: %s", js.munge);
PressLogger.trace("YUI js warn: %s", js.warn);
PressLogger.trace("YUI js preserve all semi colons: %s", js.preserveAllSemiColons);
PressLogger.trace("YUI js preserve string literals: %s", js.preserveStringLiterals);
}
public static String addTrailingSlash(String dir) {
if (dir.charAt(dir.length() - 1) != '/') {
return dir + '/';
}
return dir;
}
}
PK ?0 app/press/PressException.javapackage press;
@SuppressWarnings("serial")
public class PressException extends RuntimeException {
public PressException(Exception e) {
super(e);
}
public PressException(String msg) {
super(msg);
}
}
PK ?3> app/press/PressLogger.javapackage press;
import play.Logger;
public class PressLogger {
public static void trace(String message, Object... args) {
Logger.trace("Press: " + message, args);
}
}
PK @U app/press/RequestHandler.javapackage press;
import java.util.HashMap;
import java.util.Map;
import play.mvc.Router;
import press.io.FileIO;
public abstract class RequestHandler {
Map files = new HashMap();
abstract protected SourceFileManager getSourceManager();
abstract protected Compressor getCompressor();
abstract String getSingleCompressedUrl(String requestKey);
abstract public String getMultiCompressedUrl(String requestKey);
abstract String getTag(String src);
public String getSrcDir() {
return getSourceManager().srcDir;
}
public void checkFileExists(String fileName) {
FileIO.checkFileExists(fileName, getSrcDir());
}
public String compressedSingleFileUrl(String fileName) {
return getCompressor().compressedSingleFileUrl(fileName);
}
public String add(String fileName, boolean packFile) {
return getSourceManager().add(fileName, packFile);
}
public void saveFileList() {
getSourceManager().saveFileList();
}
public String closeRequest() {
return getSourceManager().closeRequest();
}
protected void checkForDuplicates(String fileName) {
if (!files.containsKey(fileName)) {
files.put(fileName, true);
return;
}
SourceFileManager srcManager = getSourceManager();
throw new DuplicateFileException(srcManager.getFileType(), fileName,
srcManager.getTagName());
}
protected static String getCompressedUrl(String action, String requestKey) {
Map params = new HashMap();
params.put("key", FileIO.escape(requestKey));
return Router.reverse(action, params).url;
}
}
PK @gq
app/press/RequestManager.javapackage press;
import press.io.PressFileGlobber;
/**
* Manages the state of a single request to render the page
*/
public class RequestManager {
public static final boolean RQ_TYPE_SCRIPT = true;
public static final boolean RQ_TYPE_STYLE = !RQ_TYPE_SCRIPT;
private boolean errorOccurred = false;
private RequestHandler scriptRequestHandler = new ScriptRequestHandler();
private RequestHandler styleRequestHandler = new StyleRequestHandler();
private RequestHandler getRequestHandler(boolean rqType) {
return rqType == RQ_TYPE_SCRIPT ? scriptRequestHandler : styleRequestHandler;
}
public String addSingleFile(boolean rqType, String fileName) {
RequestHandler handler = getRequestHandler(rqType);
handler.checkFileExists(fileName);
String src = null;
if (performCompression()) {
String requestKey = handler.compressedSingleFileUrl(fileName);
if (PluginConfig.isInMemoryStorage()) {
src = handler.getSingleCompressedUrl(requestKey);
} else {
src = requestKey;
}
} else {
src = handler.getSrcDir() + fileName;
}
return handler.getTag(src);
}
public String addMultiFile(boolean rqType, String src, boolean packFile) {
RequestHandler handler = getRequestHandler(rqType);
String baseUrl = handler.getSrcDir();
String result = "";
for (String fileName : PressFileGlobber.getResolvedFiles(src, baseUrl)) {
handler.checkFileExists(fileName);
handler.checkForDuplicates(fileName);
if (performCompression()) {
result += handler.add(fileName, packFile) + "\n";
} else {
result += handler.getTag(baseUrl + fileName);
}
}
return result;
}
public String compressedTag(boolean rqType) {
RequestHandler handler = getRequestHandler(rqType);
if (performCompression()) {
String requestKey = handler.closeRequest();
return handler.getTag(handler.getMultiCompressedUrl(requestKey));
}
return "";
}
public void saveFileList() {
if (!performCompression()) {
return;
}
scriptRequestHandler.saveFileList();
styleRequestHandler.saveFileList();
}
public void errorOccurred() {
errorOccurred = true;
}
private boolean performCompression() {
return PluginConfig.enabled && !errorOccurred;
}
public static void clearCache() {
ScriptRequestHandler.clearCache();
StyleRequestHandler.clearCache();
}
}
PK @쵂 app/press/ScriptCompressor.javapackage press;
import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import play.Logger;
import play.vfs.VirtualFile;
import press.io.CompressedFile;
import press.io.FileIO;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
public class ScriptCompressor extends Compressor {
public static final String EXTENSION = ".js";
public ScriptCompressor() {
super(PluginConfig.js.srcDir, PluginConfig.js.compressedDir, EXTENSION);
}
/*
public static final String TAG_NAME = "#{press.script}";
public static final String FILE_TYPE = "JavaScript";
public JSCompressor() {
super(FILE_TYPE, EXTENSION, TAG_NAME, "#{press.compressed-script}", "", PluginConfig.js.srcDir, PluginConfig.js.compressedDir);
}
public String compressedSingleFileUrl(String fileName) {
return compressedSingleFileUrl(jsFileCompressor, fileName);
}
public static CompressedFile getCompressedFile(String key) {
return getCompressedFile(jsFileCompressor, key, PluginConfig.js.compressedDir, EXTENSION);
}
*/
public static int clearCache() {
return clearCache(PluginConfig.js.compressedDir, EXTENSION);
}
static class PressErrorReporter implements ErrorReporter {
private static final String PREFIX = "[YUI Compressor] ";
private static final String FORMAT_STRING = "%s:%d (char %d) %s";
String fileName;
public PressErrorReporter(String fileName) {
this.fileName = fileName;
}
public void warning(String message, String sourceName, int line, String lineSource,
int lineOffset) {
if (line < 0 || (line == 1 && lineOffset == 0)) {
Logger.warn(PREFIX + message);
} else {
Logger.warn(PREFIX + FORMAT_STRING, fileName, line, lineOffset, message);
}
}
public void error(String message, String sourceName, int line, String lineSource,
int lineOffset) {
if (line < 0 || (line == 1 && lineOffset == 0)) {
Logger.error(PREFIX + message);
} else {
Logger.error(PREFIX + FORMAT_STRING, fileName, line, lineOffset, message);
}
}
public EvaluatorException runtimeError(String message, String sourceName, int line,
String lineSource, int lineOffset) {
error(message, sourceName, line, lineSource, lineOffset);
return new EvaluatorException(message);
}
}
@Override
public void compress(File sourceFile, Writer out) throws IOException {
ErrorReporter errorReporter = new PressErrorReporter(sourceFile.getName());
Reader in = FileIO.getReader(sourceFile);
JavaScriptCompressor compressor = new JavaScriptCompressor(in, errorReporter);
compressor.compress(out, PluginConfig.js.lineBreak, PluginConfig.js.munge,
PluginConfig.js.warn, PluginConfig.js.preserveAllSemiColons,
PluginConfig.js.preserveStringLiterals);
}
}
PK @_F?! ! app/press/ScriptFileManager.javapackage press;
public class ScriptFileManager extends SourceFileManager {
public ScriptFileManager() {
super("JavaScript", ScriptCompressor.EXTENSION, "#{press.script}",
"#{press.compressed-script}", "", PluginConfig.js.srcDir);
}
}
PK @m]2 2 # app/press/ScriptRequestHandler.javapackage press;
public class ScriptRequestHandler extends RequestHandler {
private SourceFileManager srcManager = new ScriptFileManager();
private Compressor compressor = new ScriptCompressor();
@Override
public String getSingleCompressedUrl(String requestKey) {
return getCompressedUrl("press.Press.getSingleCompressedJS", requestKey);
}
@Override
public String getMultiCompressedUrl(String requestKey) {
return getCompressedUrl("press.Press.getCompressedJS", requestKey);
}
@Override
public String getTag(String src) {
return "\n";
}
@Override
protected SourceFileManager getSourceManager() {
return srcManager;
}
@Override
protected Compressor getCompressor() {
return compressor;
}
public static int clearCache() {
return ScriptCompressor.clearCache();
}
}
PK @6_1( 1( app/press/SourceFileManager.javapackage press;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import play.PlayPlugin;
import play.cache.Cache;
import play.exceptions.UnexpectedException;
import play.libs.Crypto;
import play.mvc.Http.Request;
import play.mvc.Http.Response;
import play.templates.JavaExtensions;
import play.vfs.VirtualFile;
import press.io.FileIO;
public abstract class SourceFileManager extends PlayPlugin {
// File type, eg "JavaScript"
String fileType;
// File extension, eg ".js"
String extension;
// Tag name, eg "#{press.script}"
String tagName;
// Compressed file tag name, eg "#{press.compressed-script}"
String compressedTagName;
// Signatures for the start and end of a request to compress a file,
// eg "" would result in a compress request like:
//
String pressRequestStart;
String pressRequestEnd;
// Directory where the source files are read from, eg "/public/javascripts"
String srcDir;
// The key used to identify this request
String requestKey = null;
// Keep track of the response object created when rendering started. It
// can change if there's a 404 or 500 error.
Response currentResponse;
// The list of files compressed as part of this request
Map fileInfos;
public SourceFileManager(String fileType, String extension, String tagName,
String compressedTagName, String pressRequestStart, String pressRequestEnd,
String srcDir) {
this.fileInfos = new HashMap();
this.currentResponse = Response.current();
this.fileType = fileType;
this.extension = extension;
this.pressRequestStart = pressRequestStart;
this.pressRequestEnd = pressRequestEnd;
this.tagName = tagName;
this.compressedTagName = compressedTagName;
this.srcDir = PluginConfig.addTrailingSlash(srcDir);
}
public String getTagName() {
return tagName;
}
public String getFileType() {
return fileType;
}
/**
* Adds a file to the list of files to be compressed
*
* @return the file request signature to be output in the HTML
*/
public String add(String fileName, boolean compress) {
if (compress) {
PressLogger.trace("Adding %s to output", fileName);
} else {
PressLogger.trace("Adding uncompressed file %s to output", fileName);
}
if (fileInfos.containsKey(fileName)) {
throw new DuplicateFileException(fileType, fileName, tagName);
}
// Add the file to the list of files to be compressed
fileInfos.put(fileName, new FileInfo(fileName, compress, checkFileExists(fileName)));
return getFileRequestSignature(fileName);
}
/**
* This must only be called once, as it indicates that the file is ready to
* be output
*
* @return the request key used to retrieve the compressed file
*/
public String closeRequest() {
if (requestKey != null) {
String msg = "There is more than one " + compressedTagName
+ " tag in the template output. " + "There must be one only.";
throw new PressException(msg);
}
requestKey = getRequestKey() + extension;
PressLogger
.trace("Adding key %s for compression of %d files", requestKey, fileInfos.size());
return requestKey;
}
/**
* The request key is is derived from the list of files - for the same list
* of files we should always return the same compressed javascript or css.
*/
private String getRequestKey() {
String key = "";
for (Entry entry : fileInfos.entrySet()) {
key += entry.getKey();
// If we use the 'Change' caching strategy, make the modified
// timestamp
// of each file part of the key.
if (PluginConfig.cache.equals(CachingStrategy.Change)) {
key += entry.getValue().getLastModified();
}
}
// Get a hash of the url to keep it short
String hashed = Crypto.passwordHash(key);
return FileIO.lettersOnly(hashed);
}
public void saveFileList() {
// If the request key has not been set, that means there was no request
// for compressed source anywhere in the template file, so we don't
// need to generate anything
if (requestKey == null) {
// If the file list is not empty, then there have been files added
// to compression but they will not be output. So throw an
// exception telling the user he needs to add some files.
if (fileInfos.size() > 0) {
String msg = fileInfos.size() + " files added to compression with ";
msg += tagName + " tag but no " + compressedTagName + " tag was found. ";
msg += "You must include a " + compressedTagName + " tag in the template ";
msg += "to output the compressed content of these files: ";
msg += JavaExtensions.join(fileInfos.keySet(), ", ");
throw new PressException(msg);
}
return;
}
// The press tag may not always have been executed by the template
// engine in the same order that the resulting
$