diff --git a/.gitattributes b/.gitattributes index 4c659165..2b114ab2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,7 +3,8 @@ lessc text eol=lf *.less text eol=lf *.css text eol=lf *.htm text eol=lf +gradlew.bat text eol=crlf *.jpg binary *.png binary -*.jpeg binary \ No newline at end of file +*.jpeg binary diff --git a/.gitignore b/.gitignore index 5d00c636..55e880c0 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,7 @@ test/sourcemaps/*.css # grunt .grunt + +# gradle +.gradle +out diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..18b53b2b --- /dev/null +++ b/build.gradle @@ -0,0 +1,170 @@ +import groovy.io.FileType + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.eriwen:gradle-js-plugin:1.8.0' + } +} + +apply plugin: 'js' + + +repositories { + mavenCentral() +} + +configurations { + rhino +} + +dependencies { + rhino 'org.mozilla:rhino:1.7R4' +} + +project.ext { + rhinoTestSrc = 'out/rhino-test.js' + testSrc = 'test/less' + testOut = 'out/test' +} + +javascript.source { + test { + js { + srcDir '.' + include 'test/rhino/test-header.js' + include 'dist/less-rhino-1.5.0.js' + } + } +} + +combineJs { + source = javascript.source.test.js.files + dest = file(rhinoTestSrc) +} + +task testRhino { + dependsOn 'testRhinoBase', 'testRhinoErrors', 'testRhinoLegacy', 'testRhinoStaticUrls', 'testRhinoCompression' +} + +task testRhinoBase(type: RhinoTest) { + options = [ '--strict-math=true', '--relative-urls' ] +} + +task testRhinoErrors(type: RhinoTest) { + options = [ '--strict-math=true', '--strict-units=true' ] + testDir = 'errors/' + expectErrors = true +} + +task testRhinoLegacy(type: RhinoTest) { + testDir = 'legacy/' +} + +task testRhinoStaticUrls(type: RhinoTest) { + options = [ '--strict-math=true', '--rootpath=folder (1)/' ] + testDir = 'static-urls/' +} + +task testRhinoCompression(type: RhinoTest) { + options = [ '--compress=true' ] + testDir = 'compression/' +} + +task setupTest { + dependsOn combineJs + doLast { + file(testOut).deleteDir() + } +} + +task clean << { + file(rhinoTestSrc).delete() + file(testOut).deleteDir() +} + + +class RhinoTest extends DefaultTask { + + RhinoTest() { + dependsOn 'setupTest' + } + + def testDir = '' + def options = [] + def expectErrors = false + + def stylize(str, style) { + def styles = [ + reset : [0, 0], + bold : [1, 22], + inverse : [7, 27], + underline : [4, 24], + yellow : [33, 39], + green : [32, 39], + red : [31, 39], + grey : [90, 39] + ]; + return '\033[' + styles[style][0] + 'm' + str + + '\033[' + styles[style][1] + 'm'; + } + + @TaskAction + def runTest() { + int testSuccesses = 0, testFailures = 0, testErrors = 0 + project.file('test/less/' + testDir).eachFileMatch(FileType.FILES, ~/.*\.less/) { lessFile -> + print lessFile + if (!project.hasProperty('test') || lessFile.name.startsWith(project.test)) { + def out = new java.io.ByteArrayOutputStream() + def execOptions = { + main = 'org.mozilla.javascript.tools.shell.Main' +// main = 'org.mozilla.javascript.tools.debugger.Main' + classpath = project.configurations.rhino + args = [project.rhinoTestSrc, lessFile] + options + standardOutput = out + ignoreExitValue = true + } + try { + def exec = project.javaexec(execOptions) + def actual = out.toString().trim() + def actualResult = project.file(lessFile.path.replace('test/less', project.testOut).replace('.less', '.css')) + project.file(actualResult.parent).mkdirs() + actualResult << actual + def expected + if (expectErrors) { + assert exec.exitValue != 0 + expected = project.file(lessFile.path.replace('.less', '.txt')).text.trim(). + replace('{path}', lessFile.parent + '/'). + replace('{pathhref}', ''). + replace('{404status}', '') + } else { + assert exec.exitValue == 0 + expected = project.file(lessFile.path.replace('.less', '.css').replace('/less/', '/css/')).text.trim() + } + assert actual == expected + testSuccesses++ + println stylize(' ok', 'green') + actualResult.delete() + } + catch (ex) { + println ex + println() + testErrors++; + } + catch (AssertionError ae) { + println stylize(' failed', 'red') +// println ae + testFailures++ + } + } else { + println stylize(' skipped', 'yellow') + } + } + println stylize(testSuccesses + ' ok', 'green') + println stylize(testFailures + ' assertion failed', testFailures == 0 ? 'green' : 'red') + println stylize(testErrors + ' errors', testErrors == 0 ? 'green' : 'red') +// assert testFailures + testErrors == 0 + } +} diff --git a/build/build.yml b/build/build.yml index 777bf024..6c7139b2 100644 --- a/build/build.yml +++ b/build/build.yml @@ -20,7 +20,7 @@ lib: lib/less # ================================= prepend: browser: ['build/require.js', 'build/browser-header.js'] - rhino: ['build/require-rhino.js', 'build/rhino-header.js'] + rhino: ['build/require-rhino.js', 'build/rhino-header.js', 'build/rhino-modules.js'] append: amd: build/amd.js @@ -89,16 +89,16 @@ rhino: # core - <%= build.less.parser %> + - <%= build.less.functions %> + - <%= build.less.colors %> + - <%= build.less.tree %> + - <%= build.less.treedir %> # glob all files - <%= build.less.env %> - <%= build.less.visitor %> - <%= build.less.import_visitor %> - <%= build.less.join %> - <%= build.less.to_css_visitor %> - <%= build.less.extend_visitor %> - - <%= build.less.functions %> - - <%= build.less.colors %> - - <%= build.less.tree %> - - <%= build.less.treedir %> # glob all files - <%= build.less.source_map_output %> # append rhino-specific code diff --git a/build/require-rhino.js b/build/require-rhino.js index 02bc83ce..397b4d0d 100644 --- a/build/require-rhino.js +++ b/build/require-rhino.js @@ -2,6 +2,11 @@ // Stub out `require` in rhino // function require(arg) { - return less[arg.split('/')[1]]; -}; + var split = arg.split('/'); + var resultModule = split.length == 1 ? less.modules[split[0]] : less[split[1]]; + if (!resultModule) { + throw { message: "Cannot find module '" + arg + "'"}; + } + return resultModule; +} diff --git a/build/rhino-header.js b/build/rhino-header.js index 891f0943..1e3f7a7c 100644 --- a/build/rhino-header.js +++ b/build/rhino-header.js @@ -1,4 +1,4 @@ if (typeof(window) === 'undefined') { less = {} } else { less = window.less = {} } tree = less.tree = {}; -less.mode = 'rhino'; \ No newline at end of file +less.mode = 'rhino'; diff --git a/build/rhino-modules.js b/build/rhino-modules.js new file mode 100644 index 00000000..0709f270 --- /dev/null +++ b/build/rhino-modules.js @@ -0,0 +1,128 @@ +(function() { + + console = function() { + var stdout = java.lang.Systen.out; + var stderr = java.lang.System.err; + + function doLog(out, type) { + return function() { + var args = java.lang.reflect.Array.newInstance(java.lang.Object, arguments.length - 1); + var format = arguments[0]; + var conversionIndex = 0; + // need to look for %d (integer) conversions because in Javascript all numbers are doubles + for (var i = 1; i < arguments.length; i++) { + var arg = arguments[i]; + if (conversionIndex != -1) { + conversionIndex = format.indexOf('%', conversionIndex); + } + if (conversionIndex >= 0 && conversionIndex < format.length) { + var conversion = format.charAt(conversionIndex + 1); + if (conversion === 'd' && typeof arg === 'number') { + arg = new java.lang.Integer(new java.lang.Double(arg).intValue()); + } + conversionIndex++; + } + args[i-1] = arg; + } + try { + out.println(type + java.lang.String.format(format, args)); + } catch(ex) { + stderr.println(ex); + } + } + } + return { + log: doLog(stdout, ''), + info: doLog(stdout, 'INFO: '), + error: doLog(stderr, 'ERROR: '), + warn: doLog(stderr, 'WARN: ') + }; + }(); + + less.modules = {}; + + less.modules.path = { + join: function() { + var parts = []; + for (i in arguments) { + parts = parts.concat(arguments[i].split('/')); + } + var result = []; + for (i in parts) { + var part = parts[i]; + if (part === '..' && result.length > 0) { + result.pop(); + } else if (part === '' && result.length > 0) { + // skip + } else if (part !== '.') { + result.push(part); + } + } + return result.join('/'); + }, + dirname: function(p) { + var path = p.split('/'); + path.pop(); + return path.join('/'); + }, + basename: function(p, ext) { + var base = p.split('/').pop(); + if (ext) { + var index = base.lastIndexOf(ext); + if (base.length === index + ext.length) { + base = base.substr(0, index); + } + } + return base; + }, + extname: function(p) { + var index = p.lastIndexOf('.'); + return index > 0 ? p.substring(index) : ''; + } + }; + + less.modules.fs = { + readFileSync: function(name) { + // read a file into a byte array + var file = new java.io.File(name); + var stream = new java.io.FileInputStream(file); + var buffer = []; + var c; + while ((c = stream.read()) != -1) { + buffer.push(c); + } + stream.close(); + return { + length: buffer.length, + toString: function(enc) { + if (enc === 'base64') { + return encodeBase64Bytes(buffer); + } else if (enc) { + return java.lang.String["(byte[],java.lang.String)"](buffer, enc); + } else { + return java.lang.String["(byte[])"](buffer); + } + } + }; + } + }; + + less.encoder = { + encodeBase64: function(str) { + return encodeBase64String(str); + } + }; + + // --------------------------------------------------------------------------------------------- + // private helper functions + // --------------------------------------------------------------------------------------------- + + function encodeBase64Bytes(bytes) { + // requires at least a JRE Platform 6 (or JAXB 1.0 on the classpath) + return javax.xml.bind.DatatypeConverter.printBase64Binary(bytes) + } + function encodeBase64String(str) { + return encodeBase64Bytes(new java.lang.String(str).getBytes()); + } + +})(); diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..667288ad Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..2d919c80 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Oct 27 15:19:43 CET 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.8-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/lib/less/encoder.js b/lib/less/encoder.js new file mode 100644 index 00000000..65902f7a --- /dev/null +++ b/lib/less/encoder.js @@ -0,0 +1,4 @@ +// base64 encoder implementation for node +exports.encodeBase64 = function(str) { + return new Buffer(str).toString('base64'); +}; diff --git a/lib/less/functions.js b/lib/less/functions.js index 20aab988..1e532e24 100644 --- a/lib/less/functions.js +++ b/lib/less/functions.js @@ -394,8 +394,8 @@ tree.functions = { var mimetype = mimetypeNode.value; var filePath = (filePathNode && filePathNode.value); - var fs = require("fs"), - path = require("path"), + var fs = require('fs'), + path = require('path'), useBase64 = false; if (arguments.length < 2) { @@ -519,9 +519,8 @@ tree.functions = { ''; if (useBase64) { - // only works in node, needs interface to what is supported in environment try { - returner = new Buffer(returner).toString('base64'); + returner = require('./encoder').encodeBase64(returner); // TODO browser implementation } catch(e) { useBase64 = false; } diff --git a/lib/less/rhino.js b/lib/less/rhino.js index 08f31102..f590ce92 100644 --- a/lib/less/rhino.js +++ b/lib/less/rhino.js @@ -1,34 +1,63 @@ /*jshint rhino:true, unused: false */ /*global name:true, less, loadStyleSheet */ -var name; -function error(e, filename) { +if (typeof initRhinoTest === 'function') { // definition of additional test functions (see rhino/test-header.js) + initRhinoTest(); +} - var content = "Error : " + filename + "\n"; +function formatError(ctx, options) { + options = options || {}; - filename = e.filename || filename; + var message = ""; + var extract = ctx.extract; + var error = []; +// var stylize = options.color ? require('./lessc_helper').stylize : function (str) { return str; }; + var stylize = function (str) { return str; }; - if (e.message) { - content += e.message + "\n"; + // only output a stack if it isn't a less error + if (ctx.stack && !ctx.type) { return stylize(ctx.stack, 'red'); } + + if (!ctx.hasOwnProperty('index') || !extract) { + return ctx.stack || ctx.message; } - var errorline = function (e, i, classname) { - if (e.extract[i]) { - content += - String(parseInt(e.line, 10) + (i - 1)) + - ":" + e.extract[i] + "\n"; + if (typeof(extract[0]) === 'string') { + error.push(stylize((ctx.line - 1) + ' ' + extract[0], 'grey')); + } + + if (typeof(extract[1]) === 'string') { + var errorTxt = ctx.line + ' '; + if (extract[1]) { + errorTxt += extract[1].slice(0, ctx.column) + + stylize(stylize(stylize(extract[1][ctx.column], 'bold') + + extract[1].slice(ctx.column + 1), 'red'), 'inverse'); } - }; - - if (e.stack) { - content += e.stack; - } else if (e.extract) { - content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n'; - errorline(e, 0); - errorline(e, 1); - errorline(e, 2); + error.push(errorTxt); } - print(content); + + if (typeof(extract[2]) === 'string') { + error.push(stylize((ctx.line + 1) + ' ' + extract[2], 'grey')); + } + error = error.join('\n') + stylize('', 'reset') + '\n'; + + message += stylize(ctx.type + 'Error: ' + ctx.message, 'red'); + ctx.filename && (message += stylize(' in ', 'red') + ctx.filename + + stylize(' on line ' + ctx.line + ', column ' + (ctx.column + 1) + ':', 'grey')); + + message += '\n' + error; + + if (ctx.callLine) { + message += stylize('from ', 'red') + (ctx.filename || '') + '/n'; + message += stylize(ctx.callLine, 'grey') + ' ' + ctx.callExtract + '/n'; + } + + return message; +} + +function writeError(ctx, options) { + options = options || {}; + if (options.silent) { return; } + print(formatError(ctx, options)); } function loadStyleSheet(sheet, callback, reload, remaining) { @@ -47,16 +76,65 @@ function loadStyleSheet(sheet, callback, reload, remaining) { }); parser.parse(input, function (e, root) { if (e) { - return error(e, sheetName); + return writeError(e); } try { callback(e, root, input, sheet, { local: false, lastModified: 0, remaining: remaining }, sheetName); } catch(e) { - error(e, sheetName); + writeError(e); } }); } +less.Parser.fileLoader = function (file, currentFileInfo, callback, env) { + + var href = file; + if (currentFileInfo && currentFileInfo.currentDirectory && !/^\//.test(file)) { + href = less.modules.path.join(currentFileInfo.currentDirectory, file); + } + + var path = less.modules.path.dirname(href); + + var newFileInfo = { + currentDirectory: path + '/', + filename: href + }; + + if (currentFileInfo) { + newFileInfo.entryPath = currentFileInfo.entryPath; + newFileInfo.rootpath = currentFileInfo.rootpath; + newFileInfo.rootFilename = currentFileInfo.rootFilename; + newFileInfo.relativeUrls = currentFileInfo.relativeUrls; + } else { + newFileInfo.entryPath = path; + newFileInfo.rootpath = less.rootpath || path; + newFileInfo.rootFilename = href; + newFileInfo.relativeUrls = env.relativeUrls; + } + + var j = file.lastIndexOf('/'); + if(newFileInfo.relativeUrls && !/^(?:[a-z-]+:|\/)/.test(file) && j != -1) { + var relativeSubDirectory = file.slice(0, j+1); + newFileInfo.rootpath = newFileInfo.rootpath + relativeSubDirectory; // append (sub|sup) directory path of imported file + } + newFileInfo.currentDirectory = path; + newFileInfo.filename = href; + + try { + var data = readFile(href); + } catch (e) { + callback({ type: 'File', message: "'" + less.modules.path.basename(href) + "' wasn't found" }); + return; + } + + try { + callback(null, data, href, newFileInfo, { lastModified: 0 }); + } catch (e) { + callback(e, null, href); + } +}; + + function writeFile(filename, content) { var fstream = new java.io.FileWriter(filename); var out = new java.io.BufferedWriter(fstream); @@ -66,53 +144,266 @@ function writeFile(filename, content) { // Command line integration via Rhino (function (args) { - var output, - compress = false, - i, - path; - - for(i = 0; i < args.length; i++) { - switch(args[i]) { - case "-x": - compress = true; + + var options = { + depends: false, + compress: false, + cleancss: false, + max_line_len: -1, + optimization: 1, + silent: false, + verbose: false, + lint: false, + paths: [], + color: true, + strictImports: false, + rootpath: '', + relativeUrls: false, + ieCompat: true, + strictMath: false, + strictUnits: false + }; + var continueProcessing = true, + currentErrorcode; + + var checkArgFunc = function(arg, option) { + if (!option) { + print(arg + " option requires a parameter"); + continueProcessing = false; + return false; + } + return true; + }; + + var checkBooleanArg = function(arg) { + var onOff = /^((on|t|true|y|yes)|(off|f|false|n|no))$/i.exec(arg); + if (!onOff) { + print(" unable to parse "+arg+" as a boolean. use one of on/t/true/y/yes/off/f/false/n/no"); + continueProcessing = false; + return false; + } + return Boolean(onOff[2]); + }; + + var warningMessages = ""; + + args = args.filter(function (arg) { + var match; + + if (match = arg.match(/^-I(.+)$/)) { + options.paths.push(match[1]); + return false; + } + + if (match = arg.match(/^--?([a-z][0-9a-z-]*)(?:=(.*))?$/i)) { arg = match[1]; } // was (?:=([^\s]*)), check! + else { return arg; } + + switch (arg) { + case 'v': + case 'version': + console.log("lessc " + less.version.join('.') + " (LESS Compiler) [JavaScript]"); + continueProcessing = false; + break; + case 'verbose': + options.verbose = true; + break; + case 's': + case 'silent': + options.silent = true; + break; + case 'l': + case 'lint': + options.lint = true; + break; + case 'strict-imports': + options.strictImports = true; + break; + case 'h': + case 'help': + //TODO +// require('../lib/less/lessc_helper').printUsage(); + continueProcessing = false; + break; + case 'x': + case 'compress': + options.compress = true; + break; + case 'M': + case 'depends': + options.depends = true; + break; + case 'yui-compress': + warningMessages += "yui-compress option has been removed. assuming clean-css."; + options.cleancss = true; + break; + case 'clean-css': + options.cleancss = true; + break; + case 'max-line-len': + if (checkArgFunc(arg, match[2])) { + options.maxLineLen = parseInt(match[2], 10); + if (options.maxLineLen <= 0) { + options.maxLineLen = -1; + } + } + break; + case 'no-color': + options.color = false; + break; + case 'no-ie-compat': + options.ieCompat = false; + break; + case 'no-js': + options.javascriptEnabled = false; + break; + case 'include-path': + if (checkArgFunc(arg, match[2])) { + options.paths = match[2].split(os.type().match(/Windows/) ? ';' : ':') + .map(function(p) { + if (p) { +// return path.resolve(process.cwd(), p); + return p; + } + }); + } + break; + case 'O0': options.optimization = 0; break; + case 'O1': options.optimization = 1; break; + case 'O2': options.optimization = 2; break; + case 'line-numbers': + if (checkArgFunc(arg, match[2])) { + options.dumpLineNumbers = match[2]; + } + break; + case 'source-map': + if (!match[2]) { + options.sourceMap = true; + } else { + options.sourceMap = match[2]; + } + break; + case 'source-map-rootpath': + if (checkArgFunc(arg, match[2])) { + options.sourceMapRootpath = match[2]; + } + break; + case 'source-map-inline': + options.outputSourceFiles = true; + break; + case 'rp': + case 'rootpath': + if (checkArgFunc(arg, match[2])) { + options.rootpath = match[2].replace(/\\/g, '/'); + } + break; + case "ru": + case "relative-urls": + options.relativeUrls = true; + break; + case "sm": + case "strict-math": + if (checkArgFunc(arg, match[2])) { + options.strictMath = checkBooleanArg(match[2]); + } + break; + case "su": + case "strict-units": + if (checkArgFunc(arg, match[2])) { + options.strictUnits = checkBooleanArg(match[2]); + } break; default: - if (!name) { - name = args[i]; - } else if (!output) { - output = args[i]; - } else { - print("unrecognised parameters"); - print("input_file [output_file] [-x]"); - } + console.log('invalid option ' + arg); + continueProcessing = false; + } + }); + + if (!continueProcessing) { + return; + } + + var name = args[0]; + if (name && name != '-') { +// name = path.resolve(process.cwd(), name); + } + var output = args[1]; + var outputbase = args[1]; + if (output) { + options.sourceMapOutputFilename = output; +// output = path.resolve(process.cwd(), output); + if (warningMessages) { + console.log(warningMessages); } } +// options.sourceMapBasepath = process.cwd(); + options.sourceMapBasepath = ''; + + if (options.sourceMap === true) { + if (!output) { + console.log("the sourcemap option only has an optional filename if the css filename is given"); + return; + } + options.sourceMapFullFilename = options.sourceMapOutputFilename + ".map"; + options.sourceMap = less.modules.path.basename(options.sourceMapFullFilename); + } + if (!name) { - print('No files present in the fileset; Check your pattern match in build.xml'); + console.log("lessc: no inout files"); + console.log(""); + // TODO +// require('../lib/less/lessc_helper').printUsage(); + currentErrorcode = 1; + return; + } + +// var ensureDirectory = function (filepath) { +// var dir = path.dirname(filepath), +// cmd, +// existsSync = fs.existsSync || path.existsSync; +// if (!existsSync(dir)) { +// if (mkdirp === undefined) { +// try {mkdirp = require('mkdirp');} +// catch(e) { mkdirp = null; } +// } +// cmd = mkdirp && mkdirp.sync || fs.mkdirSync; +// cmd(dir); +// } +// }; + + if (options.depends) { + if (!outputbase) { + console.log("option --depends requires an output path to be specified"); + return; + } + console.log(outputbase + ": "); + } + + if (!name) { + console.log('No files present in the fileset'); quit(1); } - path = name.split("/");path.pop();path=path.join("/"); var input = readFile(name); if (!input) { - print('lesscss: couldn\'t open file ' + name); + console.log('lesscss: couldn\'t open file ' + name); quit(1); } + options.filename = name; var result; try { - var parser = new less.Parser(); + var parser = new less.Parser(options); parser.parse(input, function (e, root) { if (e) { - error(e, name); + writeError(e, options); quit(1); } else { - result = root.toCSS({compress: compress || false}); + result = root.toCSS(options); if (output) { writeFile(output, result); - print("Written to " + output); + console.log("Written to " + output); } else { print(result); } @@ -121,8 +412,8 @@ function writeFile(filename, content) { }); } catch(e) { - error(e, name); + writeError(e, options); quit(1); } - print("done"); -}(arguments)); \ No newline at end of file + console.log("done"); +}(arguments)); diff --git a/lib/less/tree/javascript.js b/lib/less/tree/javascript.js index dc7910c4..dabe282a 100644 --- a/lib/less/tree/javascript.js +++ b/lib/less/tree/javascript.js @@ -36,7 +36,7 @@ tree.JavaScript.prototype = { try { result = expression.call(context); } catch (e) { - throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message + "'" , + throw { message: "JavaScript evaluation error: '" + e.name + ': ' + e.message.replace(/["]/g, "'") + "'" , index: this.index }; } if (typeof(result) === 'string') { diff --git a/test/rhino/test-header.js b/test/rhino/test-header.js new file mode 100644 index 00000000..124db124 --- /dev/null +++ b/test/rhino/test-header.js @@ -0,0 +1,13 @@ +function initRhinoTest() { + process = { title: 'dummy' }; + + less.tree.functions.add = function (a, b) { + return new(less.tree.Dimension)(a.value + b.value); + }; + less.tree.functions.increment = function (a) { + return new(less.tree.Dimension)(a.value + 1); + }; + less.tree.functions._color = function (str) { + if (str.value === "evil red") { return new(less.tree.Color)("600"); } + }; +}