001package org.jszip.less; 002 003import org.apache.maven.plugin.logging.Log; 004import org.codehaus.plexus.util.FileUtils; 005import org.codehaus.plexus.util.IOUtil; 006import org.jszip.css.CssCompilationError; 007import org.jszip.css.CssEngine; 008import org.jszip.pseudo.io.PseudoFileSystem; 009import org.jszip.rhino.GlobalFunctions; 010import org.jszip.rhino.JavaScriptTerminationException; 011import org.jszip.rhino.MavenLogErrorReporter; 012import org.mozilla.javascript.Context; 013import org.mozilla.javascript.ContextFactory; 014import org.mozilla.javascript.Function; 015import org.mozilla.javascript.JavaScriptException; 016import org.mozilla.javascript.Script; 017import org.mozilla.javascript.Scriptable; 018import org.mozilla.javascript.ScriptableObject; 019import org.mozilla.javascript.tools.shell.Global; 020import org.mozilla.javascript.tools.shell.QuitAction; 021import org.mozilla.javascript.tools.shell.ShellContextFactory; 022 023import java.io.File; 024import java.io.FileInputStream; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028 029/** 030 * @author stephenc 031 * @since 31/01/2013 23:43 032 */ 033public class LessEngine implements CssEngine { 034 035 private final PseudoFileSystem fs; 036 private final ContextFactory contextFactory; 037 private final Global global; 038 private final Scriptable scope; 039 private final Log log; 040 private final boolean lessCompress; 041 private final boolean showErrorExtracts; 042 private final Function function; 043 private final String encoding; 044 045 public LessEngine(PseudoFileSystem fs, String encoding, Log log, boolean lessCompress, File customLessScript, 046 boolean showErrorExtracts) throws IOException { 047 this.fs = fs; 048 this.encoding = encoding; 049 this.lessCompress = lessCompress; 050 this.showErrorExtracts = showErrorExtracts; 051 this.contextFactory = new ShellContextFactory(); 052 this.global = new Global(); 053 this.log = log; 054 global.initQuitAction(new QuitAction() { 055 public void quit(Context context, int exitCode) { 056 if (exitCode != 0) { 057 throw new JavaScriptTerminationException("Script exited with exit code of " + exitCode, exitCode); 058 } 059 } 060 }); 061 if (!global.isInitialized()) { 062 global.init(contextFactory); 063 } 064 global.defineFunctionProperties(new String[]{"print", "debug", "warn", "quit", "readFile"}, 065 GlobalFunctions.class, 066 ScriptableObject.DONTENUM); 067 final Context context = contextFactory.enterContext(); 068 try { 069 context.setErrorReporter(new MavenLogErrorReporter(log)); 070 context.putThreadLocal(Log.class, log); 071 global.defineProperty("arguments", new Object[0], ScriptableObject.DONTENUM); 072 scope = GlobalFunctions.createPseudoFileSystemScope(global, context); 073 074 compileScript(context, "less-env.js", null, "/org/jszip/less/less-env.js") 075 .exec(context, scope); 076 077 // now load less-rhino.js 078 079 compileScript(context, "less-rhino.js", customLessScript, "/org/jszip/less/less-rhino.js") 080 .exec(context, scope); 081 082 global.defineProperty("showErrorExtracts", showErrorExtracts, ScriptableObject.DONTENUM); 083 084 compileScript(context, "less-engine.js", null, "/org/jszip/less/less-engine.js") 085 .exec(context, scope); 086 087 function = (Function) scope.get("engine", scope); 088 089 } finally { 090 fs.removeFromContext(); 091 Context.exit(); 092 context.putThreadLocal(Log.class, null); 093 } 094 } 095 096 public String mapName(String sourceFileName) { 097 return sourceFileName.replaceFirst("\\.[lL][eE][sS][sS]$", ".css"); 098 } 099 100 public String toCSS(String name) throws CssCompilationError { 101 102 final Context context = contextFactory.enterContext(); 103 try { 104 context.setErrorReporter(new MavenLogErrorReporter(log)); 105 context.putThreadLocal(Log.class, log); 106 fs.installInContext(); 107 108 GlobalFunctions.setExitCode(0); 109 110 final String result = 111 (String) function.call(context, scope, scope, new Object[]{name, encoding, lessCompress}); 112 113 // check for errors 114 115 final Integer exitCode = GlobalFunctions.getExitCode(); 116 if (exitCode != 0) { 117 throw new CssCompilationError(name, -1, -1); 118 } 119 return result; 120 } catch (JavaScriptException e) { 121 if (e.getValue() instanceof Scriptable) { 122 Scriptable jse = (Scriptable) e.getValue(); 123 int line = jse.has("line", jse) ? ((Number) jse.get("line", jse)).intValue() : -1; 124 int col = jse.has("col", jse) ? ((Number) jse.get("col", jse)).intValue() : -1; 125 throw new CssCompilationError(name, line, col, e); 126 } 127 throw new CssCompilationError(name, -1, -1, e); 128 } finally { 129 fs.removeFromContext(); 130 Context.exit(); 131 context.putThreadLocal(Log.class, null); 132 } 133 } 134 135 private Script compileScript(Context context, String scriptName, File customScriptFile, 136 String bundledScriptResource) throws IOException { 137 String source; 138 int lineNo = 0; 139 InputStream inputStream = null; 140 InputStreamReader reader = null; 141 try { 142 if (customScriptFile != null && customScriptFile.isFile()) { 143 log.debug("Using custom " + scriptName + " from: " + customScriptFile); 144 inputStream = new FileInputStream(customScriptFile); 145 } else { 146 log.debug("Using bundled " + scriptName); 147 inputStream = getClass().getResourceAsStream(bundledScriptResource); 148 } 149 source = IOUtil.toString(inputStream, "UTF-8"); 150 if (source.startsWith("#!")) { 151 int i1 = source.indexOf('\n'); 152 int i2 = source.indexOf('\r'); 153 int index = (i1 == -1 || i2 == -1) ? Math.max(i1, i2) : Math.min(i1, i2); 154 if (index > 0) { 155 source = source.substring(index); 156 lineNo++; 157 } 158 } 159 } finally { 160 IOUtil.close(reader); 161 IOUtil.close(inputStream); 162 } 163 return context.compileString(source, scriptName, lineNo, null); 164 } 165 166}