Skip to content
Browse files

Adding support for Sass compilation

  • Loading branch information...
1 parent 1062172 commit 58783681ca3f757bc47606c44b1602043c08e57f @stephenc stephenc committed Jan 31, 2013
View
33 pom.xml
@@ -14,7 +14,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
@@ -31,9 +32,6 @@
<name>JavaScript Zip Maven Plugin</name>
<description>A plugin for managing JavaScript Zip archives.</description>
<url>http://jszip.org/jszip-maven-plugin</url>
- <prerequisites>
- <maven>3.0.4</maven>
- </prerequisites>
<scm>
<connection>scm:git:git://github.com/jszip/jszip-maven-plugin.git</connection>
@@ -63,6 +61,23 @@
<dependencies>
+ <!-- jruby engine -->
+ <dependency>
+ <groupId>org.jruby</groupId>
+ <artifactId>jruby-complete</artifactId>
+ <version>1.6.8</version>
+ </dependency>
+
+ <!-- SASS gem -->
+ <dependency>
+ <groupId>org.jszip.jruby</groupId>
+ <artifactId>sass-gems</artifactId>
+ <version>3.2.5</version>
+ <!--<groupId>com.sass-lang</groupId>-->
+ <!--<artifactId>sass-gems</artifactId>-->
+ <!--<version>3.1.1</version>-->
+ </dependency>
+
<!-- javascript engine -->
<dependency>
<groupId>org.mozilla</groupId>
@@ -308,16 +323,6 @@
<artifactId>commons-lang3</artifactId>
<version>3.1</version>
</dependency>
- <dependency>
- <groupId>org.jruby</groupId>
- <artifactId>jruby-complete</artifactId>
- <version>1.7.2</version>
- </dependency>
- <dependency>
- <groupId>me.n4u.sass</groupId>
- <artifactId>sass-gems</artifactId>
- <version>3.2.1</version>
- </dependency>
<!-- test dependencies -->
<dependency>
<groupId>junit</groupId>
View
90 src/main/java/org/jszip/maven/CompileSASSMojo.java
@@ -2,11 +2,29 @@
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
+import org.codehaus.plexus.util.IOUtil;
+import org.jruby.embed.EmbedEvalUnit;
+import org.jruby.embed.ScriptingContainer;
+import org.jruby.javasupport.JavaEmbedUtils;
+import org.jszip.pseudo.io.PseudoDirectoryScanner;
+import org.jszip.pseudo.io.PseudoFile;
+import org.jszip.pseudo.io.PseudoFileOutputStream;
+import org.jszip.pseudo.io.PseudoFileSystem;
+import org.jszip.rhino.GlobalFunctions;
+import org.jszip.rhino.MavenLogErrorReporter;
+import org.jszip.sass.SassEngine;
+import org.mozilla.javascript.Context;
+import org.mozilla.javascript.Scriptable;
+import org.mozilla.javascript.ScriptableObject;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
@Mojo(name = "compile-sass", defaultPhase = LifecyclePhase.PROCESS_RESOURCES,
@@ -20,6 +38,12 @@
private boolean sassSkip;
/**
+ * Force compilation even if the source Sass file is older than the destination CSS file.
+ */
+ @Parameter(property = "jszip.sass.forceIfOlder", defaultValue = "false")
+ private boolean sassForceIfOlder;
+
+ /**
* Indicates whether the build will continue even if there are compilation errors.
*/
@Parameter(property = "jszip.sass.failOnError", defaultValue = "true")
@@ -40,6 +64,12 @@
private List<String> sassExcludes;
/**
+ * The character encoding scheme to be applied when reading SASS files.
+ */
+ @Parameter( defaultValue = "${project.build.sourceEncoding}" )
+ private String encoding;
+
+ /**
* @see org.apache.maven.plugin.Mojo#execute()
*/
public void execute() throws MojoExecutionException, MojoFailureException {
@@ -54,6 +84,64 @@ public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info("Webapp directory '" + webappDirectory + " does not exist. Nothing to do.");
return;
}
- throw new UnsupportedOperationException("Unimplemented");
+ final List<PseudoFileSystem.Layer> layers = buildVirtualFileSystemLayers();
+ final PseudoFileSystem fs = new PseudoFileSystem(layers);
+ SassEngine engine = new SassEngine(fs, encoding == null ? "utf-8" : encoding);
+ Context.enter();
+ try {
+ fs.installInContext();
+
+ // look for files to compile
+
+ PseudoDirectoryScanner scanner = new PseudoDirectoryScanner();
+
+ scanner.setBasedir(fs.getPseudoFile("/virtual"));
+
+ if (sassIncludes != null && !sassIncludes.isEmpty()) {
+ scanner.setIncludes(processIncludesExcludes(sassIncludes));
+ } else {
+ scanner.setIncludes(new String[]{"**/*.sass","**/*.scss"});
+ }
+
+ if (sassExcludes != null && !sassExcludes.isEmpty()) {
+ scanner.setExcludes(processIncludesExcludes(sassExcludes));
+ } else {
+ scanner.setExcludes(new String[]{"**/_*.sass","**/_*.scss"});
+ }
+
+ scanner.scan();
+
+ final List<String> includedFiles = new ArrayList<String>(Arrays.asList(scanner.getIncludedFiles()));
+ getLog().debug("Files to compile: " + includedFiles);
+
+ for (String fileName : includedFiles) {
+ final PseudoFile dest = fs.getPseudoFile("/target/" + fileName.replaceFirst("\\.s[ac]ss$", ".css"));
+ if (!sassForceIfOlder) {
+ if (dest.isFile()) {
+ final PseudoFile src = fs.getPseudoFile("/virtual/" + fileName);
+ if (src.lastModified() < dest.lastModified()) {
+ continue;
+ }
+ }
+ }
+ if (!dest.getParentFile().isDirectory()) {
+ dest.getParentFile().mkdirs();
+ }
+
+ final String css = engine.toCSS("/virtual/" + fileName);
+ PseudoFileOutputStream fos = null;
+ try {
+ fos = new PseudoFileOutputStream(dest);
+ IOUtil.copy(css, fos);
+ } catch (IOException e) {
+ throw new MojoFailureException("Could not write CSS file produced from " + fileName, e);
+ } finally {
+ IOUtil.close(fos);
+ }
+ }
+ } finally {
+ fs.removeFromContext();
+ Context.exit();
+ }
}
}
View
106 src/main/java/org/jszip/sass/PseudoFileSystemImporter.java
@@ -0,0 +1,106 @@
+package org.jszip.sass;
+
+import org.codehaus.plexus.util.IOUtil;
+import org.jszip.pseudo.io.PseudoFile;
+import org.jszip.pseudo.io.PseudoFileInputStream;
+import org.jszip.pseudo.io.PseudoFileSystem;
+import org.mozilla.javascript.Context;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+/**
+ * @author stephenc
+ * @since 31/01/2013 12:01
+ */
+public class PseudoFileSystemImporter {
+
+ private final PseudoFileSystem fs;
+ private final String encoding;
+
+ public PseudoFileSystemImporter(PseudoFileSystem fs, String encoding) {
+ this.fs = fs;
+ this.encoding = encoding;
+ }
+
+ /**
+ * Find a Sass file, if it exists.
+ * <p/>
+ * This is the primary entry point of the Importer.
+ * It corresponds directly to an `@import` statement in Sass.
+ * It should do three basic things:
+ * <p/>
+ * * Determine if the URI is in this importer's format.
+ * If not, return nil.
+ * * Determine if the file indicated by the URI actually exists and is readable.
+ * If not, return nil.
+ * * Read the file and place the contents in a {Sass::Engine}.
+ * Return that engine.
+ * <p/>
+ * If this importer's format allows for file extensions,
+ * it should treat them the same way as the default {Filesystem} importer.
+ * If the URI explicitly has a `.sass` or `.scss` filename,
+ * the importer should look for that exact file
+ * and import it as the syntax indicated.
+ * If it doesn't exist, the importer should return nil.
+ * <p/>
+ * If the URI doesn't have either of these extensions,
+ * the importer should look for files with the extensions.
+ * If no such files exist, it should return nil.
+ * <p/>
+ * The {Sass::Engine} to be returned should be passed `options`,
+ * with a few modifications. `:syntax` should be set appropriately,
+ * `:filename` should be set to `uri`,
+ * and `:importer` should be set to this importer.
+ *
+ * @param uri [String] The URI to import.
+ * @return the contents of the uri.
+ */
+ public String find(String uri) throws IOException {
+ Context.enter();
+ try {
+ fs.installInContext();
+ final PseudoFile file = fs.getPseudoFile(uri);
+ if (file.isFile()) {
+ InputStream is = null;
+ try {
+ is = new PseudoFileInputStream(file);
+ return IOUtil.toString(is, encoding);
+ } finally {
+ IOUtil.close(is);
+ }
+ }
+ return null;
+ } finally {
+ fs.removeFromContext();
+ Context.exit();
+ }
+ }
+
+ /**
+ * Returns the time the given Sass file was last modified.
+ * <p/>
+ * If the given file has been deleted or the time can't be accessed
+ * for some other reason, this should return nil.
+ *
+ * @param uri [String] The URI of the file to check.
+ * Comes from a `:filename` option set on an engine returned by this importer.
+ * @return [Time, nil]
+ */
+ public Date mtime(String uri) {
+ Context.enter();
+ try {
+ fs.installInContext();
+ final PseudoFile file = fs.getPseudoFile(uri);
+ if (file.isFile()) {
+ return new Date(file.lastModified());
+ }
+ return null;
+ } finally {
+ fs.removeFromContext();
+ Context.exit();
+ }
+ }
+
+}
View
35 src/main/java/org/jszip/sass/SassEngine.java
@@ -0,0 +1,35 @@
+package org.jszip.sass;
+
+import org.codehaus.plexus.util.FileUtils;
+import org.jruby.Ruby;
+import org.jruby.embed.EmbedEvalUnit;
+import org.jruby.embed.ScriptingContainer;
+import org.jruby.javasupport.JavaEmbedUtils;
+import org.jszip.pseudo.io.PseudoFileSystem;
+
+import java.io.File;
+
+/**
+ * @author stephenc
+ * @since 31/01/2013 15:30
+ */
+public class SassEngine {
+
+ private final PseudoFileSystem fs;
+ private ScriptingContainer container;
+ private final EmbedEvalUnit evalUnit;
+
+ public SassEngine(PseudoFileSystem fs, String encoding) {
+ this.fs = fs;
+ this.container = new ScriptingContainer();
+ this.container.put("filesystem", new PseudoFileSystemImporter(fs, encoding));
+ this.container.put("filename", null);
+ evalUnit = this.container.parse(getClass().getResourceAsStream("sass-engine.rb"), "sass-engine.rb");
+ }
+
+ public String toCSS(String name) {
+ container.put("filename", name);
+ return (String)JavaEmbedUtils.rubyToJava(evalUnit.run());
+
+ }
+}
View
103 src/main/resources/org/jszip/sass/sass-engine.rb
@@ -0,0 +1,103 @@
+require 'rubygems'
+require 'sass'
+
+module Sass
+ module Importers
+ class Proxy < Base
+ def initialize(delegate)
+ @delegate = delegate
+ end
+
+ def find_relative(uri, base, options)
+ find(base.split('/').reverse.drop(1).reverse.join('/')+'/'+uri,options)
+ end
+
+ def extensions
+ {'sass' => :sass, 'scss' => :scss}
+ end
+
+ def find(uri, options)
+ ext = _ext(uri)
+ if ext
+ contents = @delegate.find(uri)
+ options[:syntax] = extensions[ext]
+ options[:filename] = uri
+ contents && Sass::Engine.new(contents, options)
+ else
+ extensions.map do |ext,syntax|
+ name = "#{uri}.#{ext}"
+ contents = @delegate.find(name)
+ if contents
+ options[:syntax] = syntax
+ options[:filename] = name
+ return Sass::Engine.new(contents, options)
+ end
+ end
+ extensions.map do |ext,syntax|
+ name = "#{_dir(uri)}_#{_name(uri)}.#{ext}"
+ contents = @delegate.find(name)
+ if contents
+ options[:syntax] = syntax
+ options[:filename] = name
+ return Sass::Engine.new(contents, options)
+ end
+ end
+ return nil
+ end
+ end
+
+ def mtime(uri, options)
+ @delegate.mtime(uri)
+ end
+
+ def key(uri, options)
+ ["proxy", uri]
+ end
+
+ def to_s
+ "Proxy"
+ end
+
+ private
+
+ def _dir(uri)
+ lastSep = uri.rindex('/')
+ lastSep && uri[0..lastSep]
+ end
+
+ def _name(uri)
+ lastSep = uri.rindex('/')
+ if lastSep
+ lastDot = uri[lastSep + 1..-1].rindex('.')
+ if lastDot
+ lastDot += lastSep + 1
+ end
+ else
+ lastDot = uri.rindex('.');
+ end
+ return uri[(lastSep || -1)+1..(lastDot || 0)-1]
+ end
+
+ def _ext(uri)
+ lastSep = uri.rindex('/')
+ if lastSep
+ lastDot = uri[lastSep + 1..-1].rindex('.')
+ if lastDot
+ lastDot += lastSep + 1
+ end
+ else
+ lastDot = uri.rindex('.');
+ end
+ return lastDot && uri[lastDot+1..-1]
+ end
+
+ end
+ end
+end
+
+options={
+ :importer => Sass::Importers::Proxy.new(filesystem),
+ :filename => filename,
+ :cache => false
+}
+options[:importer].find(filename,options).render
View
61 src/test/java/org/jszip/sass/SmokeTest.java
@@ -0,0 +1,61 @@
+package org.jszip.sass;
+
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.IOUtil;
+import org.jruby.Ruby;
+import org.jruby.embed.EmbedEvalUnit;
+import org.jruby.embed.ScriptingContainer;
+import org.jruby.javasupport.JavaEmbedUtils;
+import org.jszip.pseudo.io.PseudoFileSystem;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author stephenc
+ * @since 31/01/2013 09:34
+ */
+public class SmokeTest {
+
+ @Rule
+ public TemporaryFolder folder = new TemporaryFolder();
+
+ public String loadResource(String name) throws IOException {
+ InputStream stream = null;
+ try {
+ stream = getClass().getResourceAsStream(name);
+ return IOUtil.toString(stream);
+ } finally {
+ IOUtil.close(stream);
+ }
+ }
+
+ @Test
+ public void smokes() throws IOException {
+ ScriptingContainer container = new ScriptingContainer();
+ final Ruby runtime = container.getProvider().getRuntime();
+ final PseudoFileSystem fs = new PseudoFileSystem(new PseudoFileSystem.FileLayer(folder.getRoot()));
+ FileUtils.fileWrite(new File(folder.getRoot(), "foo.scss"), "utf-8", loadResource("foo.scss"));
+ FileUtils.fileWrite(new File(folder.getRoot(), "bar.sass"), "utf-8", loadResource("bar.sass"));
+ container.put("filesystem", new PseudoFileSystemImporter(fs, "utf-8"));
+ container.put("filename", "/foo.scss");
+ final EmbedEvalUnit evalUnit = container.parse(getClass().getResourceAsStream("sass-engine.rb"), "sass-engine.rb");
+ assertThat(JavaEmbedUtils.rubyToJava(evalUnit.run()).toString(), containsString("8px"));
+ }
+
+ @Test
+ public void engine() throws IOException {
+ final PseudoFileSystem fs = new PseudoFileSystem(new PseudoFileSystem.FileLayer(folder.getRoot()));
+ FileUtils.fileWrite(new File(folder.getRoot(), "foo.scss"), "utf-8", loadResource("foo.scss"));
+ FileUtils.fileWrite(new File(folder.getRoot(), "bar.sass"), "utf-8", loadResource("bar.sass"));
+ SassEngine engine = new SassEngine(fs, "utf-8");
+ assertThat(engine.toCSS("/foo.scss"), containsString("8px"));
+ }
+}
View
1 src/test/resources/org/jszip/sass/bar.sass
@@ -0,0 +1 @@
+$margin: 16px
View
6 src/test/resources/org/jszip/sass/foo.scss
@@ -0,0 +1,6 @@
+@import 'bar';
+
+.border {
+ padding: $margin / 2;
+ margin: $margin / 2;
+}

0 comments on commit 5878368

Please sign in to comment.
Something went wrong with that request. Please try again.