├── .gitignore
├── LICENSE
├── README.md
├── pom.xml
├── sass-java-gems
├── Gemfile
├── Gemfile.lock
├── pom.xml
└── src
│ ├── main
│ └── resources
│ │ └── .gitkeep
│ └── test
│ └── java
│ └── com
│ └── darrinholst
│ └── sass_java
│ └── GemsIT.java
├── sass-java-maven
├── README.md
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── darrinholst
│ └── sass_java
│ └── SassJavaMojo.java
└── sass-java
├── pom.xml
└── src
├── main
└── java
│ └── com
│ └── darrinholst
│ └── sass_java
│ ├── Clock.java
│ ├── Compiler.java
│ ├── Config.java
│ └── SassCompilingFilter.java
└── test
├── java
└── com
│ └── darrinholst
│ └── sass_java
│ ├── FakeClock.java
│ └── SassCompilingFilterTest.java
└── webapp
├── WEB-INF
├── lib
│ └── slf4j-simple-1.7.7.jar
├── sass
│ ├── application.scss
│ ├── config.rb
│ └── reset.scss
└── web.xml
└── index.html
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | *.iml
3 | *.ipr
4 | *.iws
5 | .sass-cache
6 | *.idea
7 | .bundle
8 | *.css
9 | *.css.map
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Darrin Holst
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining
6 | a copy of this software and associated documentation files (the
7 | "Software"), to deal in the Software without restriction, including
8 | without limitation the rights to use, copy, modify, merge, publish,
9 | distribute, sublicense, and/or sell copies of the Software, and to
10 | permit persons to whom the Software is furnished to do so, subject to
11 | the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be
14 | included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 | Compile sass to css on-the-fly via a j2ee servlet filter
3 |
4 | ## Usage
5 |
6 | Add the dependency to `pom.xml`
7 |
8 | ```xml
9 |
10 | com.darrinholst
11 | sass-java
12 | 3.4.4
13 |
14 | ```
15 |
16 | Add the filter to `web.xml`
17 |
18 | ```xml
19 |
20 | SassCompiler
21 | com.darrinholst.sass_java.SassCompilingFilter
22 |
23 |
24 |
25 | SassCompiler
26 | *.css
27 |
28 | ```
29 |
30 | Create a `WEB-INF/sass/config.rb`
31 |
32 | ```ruby
33 | css_dir = "../../stylesheets"
34 | sass_dir = "."
35 | ```
36 |
37 | Put your sass templates in `WEB-INF/sass` and each request for a css
38 | file will regenerate as needed.
39 |
40 | ## Versioning
41 | The first 3 nodes of the version will match the version of sass that is being used. Current versions of sass and compass can be found in the [Gemfile](https://github.com/darrinholst/sass-java/blob/master/sass-java-gems/Gemfile).
42 |
43 | ## Configuring
44 | Configuration is done through a combination of filter init parameters and the `config.rb` file. The following filter init parameters are available to control the execution of the filter:
45 |
46 | * **configLocation** - the location of the config.rb (default WEB-INF/sass/config.rb)
47 | * **onlyRunWhenKey** - the system property or environment variable to check to see if sass compilation should run, use this to turn sass generation off in production
48 | * **onlyRunWhenValue** - the corresponding value to check to see if sass compilation should run
49 |
50 | A common practice is to turn sass generation off in production and precompile in your build process. An example of how to do this based off a system property is:
51 |
52 | ```xml
53 |
54 | SassCompiler
55 | com.darrinholst.sass_java.SassCompilingFilter
56 |
57 | onlyRunWhenKey
58 | RUNTIME_ENVIRONMENT
59 |
60 |
61 | onlyRunWhenValue
62 | local
63 |
64 |
65 | ```
66 |
67 | With this configuration the filter will check a system property or environment variable called RUNTIME_ENVIRONMENT and only run the sass compilation if that value is equal to local
68 |
69 | See the [compass config documentation](http://compass-style.org/help/documentation/configuration-reference/) to find out about all the wonderful things you can put in `config.rb`. For those config options that reference a file or directory, the working directory that compass will be executed in is the directory that contains `config.rb`.
70 |
71 | ## Precompiling
72 | Use the [maven plugin](https://github.com/darrinholst/sass-java/blob/master/sass-java-maven/README.md)
73 |
74 | ## Development
75 | The magic behind how this works comes from packaging up the sass gems into a jar that the filter can then use via jruby. The process to jar up the gems is described [here](http://blog.nicksieger.com/articles/2009/01/10/jruby-1-1-6-gems-in-a-jar/). That process is mavenized [here](https://github.com/darrinholst/sass-java/blob/master/sass-java-gems/pom.xml#L34). So to change sass or compass versions All Ya Gotta Do™ is update the [Gemfile](https://github.com/darrinholst/sass-java/blob/master/sass-java-gems/Gemfile) and `mvn clean install`.
76 |
77 | ## Releasing
78 | First time? [Read this](http://central.sonatype.org/pages/working-with-pgp-signatures.html)
79 | `mvn release:prepare release:perform`
80 | That worked? [Read this](http://central.sonatype.org/pages/releasing-the-deployment.html)
81 |
82 | ## Try it out
83 | 1. git clone https://github.com/darrinholst/sass-java.git
84 | 2. cd sass-java
85 | 3. mvn install
86 | 4. mvn jetty:run -pl sass-java
87 | 5. open http://localhost:8080
88 | 6. change src/test/sample-webapp/WEB-INF/sass/application.scss
89 | 7. refresh
90 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 | com.darrinholst
6 | sass-java-parent
7 | 3.4.20.1-SNAPSHOT
8 | pom
9 |
10 |
11 | org.sonatype.oss
12 | oss-parent
13 | 7
14 |
15 |
16 |
17 |
18 |
19 | org.jruby
20 | jruby-complete
21 | 1.7.15
22 |
23 |
24 | junit
25 | junit
26 | 4.8.2
27 |
28 |
29 |
30 |
31 |
32 | sass-java-gems
33 | sass-java
34 | sass-java-maven
35 |
36 |
37 | Sass for Java
38 |
39 |
40 |
41 | MIT License
42 | http://www.opensource.org/licenses/mit-license.php
43 |
44 |
45 |
46 |
47 | https://github.com/darrinholst/sass-java
48 | scm:git:git@github.com:darrinholst/sass-java.git
49 |
50 |
51 |
--------------------------------------------------------------------------------
/sass-java-gems/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'compass', '1.0.1'
4 | gem 'sass', '3.4.20'
5 | gem 'ffi', '1.9.10'
6 |
--------------------------------------------------------------------------------
/sass-java-gems/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | chunky_png (1.3.5)
5 | compass (1.0.1)
6 | chunky_png (~> 1.2)
7 | compass-core (~> 1.0.1)
8 | compass-import-once (~> 1.0.5)
9 | rb-fsevent (>= 0.9.3)
10 | rb-inotify (>= 0.9)
11 | sass (>= 3.3.13, < 3.5)
12 | compass-core (1.0.3)
13 | multi_json (~> 1.0)
14 | sass (>= 3.3.0, < 3.5)
15 | compass-import-once (1.0.5)
16 | sass (>= 3.2, < 3.5)
17 | ffi (1.9.10-java)
18 | multi_json (1.11.2)
19 | rb-fsevent (0.9.6)
20 | rb-inotify (0.9.5)
21 | ffi (>= 0.5.0)
22 | sass (3.4.20)
23 |
24 | PLATFORMS
25 | java
26 |
27 | DEPENDENCIES
28 | compass (= 1.0.1)
29 | ffi (= 1.9.10)
30 | sass (= 3.4.20)
31 |
32 | BUNDLED WITH
33 | 1.11.1
34 |
--------------------------------------------------------------------------------
/sass-java-gems/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | com.darrinholst
7 | sass-java-parent
8 | 3.4.20.1-SNAPSHOT
9 |
10 |
11 | sass-java-gems
12 | 3.4.20.1-SNAPSHOT
13 | jar
14 |
15 |
16 |
17 | org.jruby
18 | jruby-complete
19 | runtime
20 |
21 |
22 | junit
23 | junit
24 | test
25 |
26 |
27 |
28 |
29 |
30 |
31 | org.codehaus.mojo
32 | exec-maven-plugin
33 | 1.3.2
34 |
35 |
36 | install-bundler
37 | prepare-package
38 |
39 | exec
40 |
41 |
42 | java
43 |
44 | -classpath
45 | org.jruby:jruby-complete
46 | org.jruby.Main
47 | -S
48 | gem
49 | install
50 | -i
51 | ${project.build.directory}/bundler
52 | bundler
53 |
54 |
55 |
56 |
57 | bundle-install
58 | prepare-package
59 |
60 | exec
61 |
62 |
63 |
64 | ${project.build.directory}/bundler
65 |
66 | java
67 |
68 | -classpath
69 | org.jruby:jruby-complete
70 | org.jruby.Main
71 | -S
72 | ${project.build.directory}/bundler/bin/bundle
73 | install
74 | --gemfile
75 | ${basedir}/Gemfile
76 | --path
77 | ${project.build.directory}/bundle
78 |
79 |
80 |
81 |
82 | jar-it-up
83 | package
84 |
85 | exec
86 |
87 |
88 | jar
89 |
90 | cf
91 | ${project.build.directory}/${project.name}-${project.version}.jar
92 | -C
93 | ${project.build.directory}/bundle/jruby/1.9
94 | .
95 |
96 |
97 |
98 |
99 |
100 |
101 | org.apache.maven.plugins
102 | maven-surefire-plugin
103 | 2.17
104 |
105 |
106 | integration-test
107 |
108 | test
109 |
110 |
111 |
112 | ${project.build.directory}/${project.name}-${project.version}.jar
113 |
114 |
115 | **/*IT.java
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/sass-java-gems/src/main/resources/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/darrinholst/sass-java/9e440151a5a6f38b51f504e2ffad0d10328f87f5/sass-java-gems/src/main/resources/.gitkeep
--------------------------------------------------------------------------------
/sass-java-gems/src/test/java/com/darrinholst/sass_java/GemsIT.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import org.jruby.embed.ScriptingContainer;
4 | import org.junit.Test;
5 |
6 | import static org.junit.Assert.assertEquals;
7 |
8 | public class GemsIT {
9 | @Test
10 | public void correctSassIsPackaged() {
11 | assertEquals("3.4.20", new ScriptingContainer().runScriptlet("require 'sass';Sass.version[:number]"));
12 | }
13 |
14 | @Test
15 | public void correctCompassIsPackaged() {
16 | assertEquals("1.0.1", new ScriptingContainer().runScriptlet("require 'compass';Compass::VERSION"));
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/sass-java-maven/README.md:
--------------------------------------------------------------------------------
1 | # Overview
2 | Compile sass during compile phase of a maven build.
3 |
4 | ## Usage
5 |
6 | Add the dependency to `pom.xml`
7 |
8 | ```xml
9 |
10 | com.darrinholst
11 | sass-java-maven-plugin
12 | 3.4.4.3
13 |
14 |
15 |
16 | compile
17 |
18 |
19 |
20 |
21 | ```
22 |
23 | Alternatively you can run the plugin from the command-line like this:
24 | ```
25 | mvn com.darrinholst:sass-java-maven-plugin:compile
26 | ```
27 |
28 | ## Advanced configuration
29 |
30 | To make running the plugin even more simple it is possible to add `com.darrinholst` to
31 | the list of plugin-groups in your `[userhome]/.m2/settings.xml`.
32 |
33 | ```xml
34 |
35 | com.darrinholst
36 |
37 | ```
38 |
39 | After that you'll be able to run the command as a short-hand. Even if the plugin has not
40 | been configured for your project, even when a pom.xml isn't even available.
41 |
42 | ```
43 | mvn sass-java:compile
44 | ```
45 |
46 | ### Config file location
47 |
48 | The default for the location of the `config.rb` file is set to the same location as the
49 | sass-java filter, being `${project.basedir}/src/main/webapp/WEB-INF/sass/config.rb`. You
50 | can configure this location in the `pom.xml` using the configurations element.
51 |
52 | ```xml
53 |
54 | com.darrinholst
55 | sass-java-maven-plugin
56 | 3.4.4.3
57 |
58 |
59 |
60 | ${project.basedir}/src/main/webapp/WEB-INF/sass/config.rb
61 |
62 |
63 | compile
64 |
65 |
66 |
67 |
68 | ```
69 |
70 | Alternatively you can set the property on commandline as follows:
71 |
72 | ```
73 | mvn -Dsass-java.configFile="${project.basedir}/src/main/webapp/WEB-INF/sass/config.rb" com.darrinholst:sass-java-maven-plugin:compile
74 | ```
75 |
--------------------------------------------------------------------------------
/sass-java-maven/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | com.darrinholst
7 | sass-java-parent
8 | 3.4.20.1-SNAPSHOT
9 |
10 |
11 | sass-java-maven-plugin
12 | 3.4.20.1-SNAPSHOT
13 | maven-plugin
14 |
15 |
16 |
17 | com.darrinholst
18 | sass-java
19 | ${project.version}
20 |
21 |
22 | org.apache.maven
23 | maven-plugin-api
24 | 3.2.3
25 |
26 |
27 | org.apache.maven.plugin-tools
28 | maven-plugin-annotations
29 | 3.3
30 | provided
31 |
32 |
33 |
34 |
35 |
36 |
37 | org.apache.maven.plugins
38 | maven-plugin-plugin
39 | 3.3
40 |
41 |
42 | true
43 |
44 |
45 |
46 | mojo-descriptor
47 |
48 | descriptor
49 |
50 |
51 |
52 |
53 | help-goal
54 |
55 | helpmojo
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/sass-java-maven/src/main/java/com/darrinholst/sass_java/SassJavaMojo.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import org.apache.maven.plugin.AbstractMojo;
4 | import org.apache.maven.plugin.MojoExecutionException;
5 | import org.apache.maven.plugin.MojoFailureException;
6 | import org.apache.maven.plugins.annotations.LifecyclePhase;
7 | import org.apache.maven.plugins.annotations.Mojo;
8 | import org.apache.maven.plugins.annotations.Parameter;
9 |
10 | import java.io.File;
11 |
12 | @Mojo(name = "compile", defaultPhase = LifecyclePhase.COMPILE, requiresDirectInvocation = false)
13 | public class SassJavaMojo extends AbstractMojo {
14 | private Compiler compiler = new Compiler();
15 |
16 | @Parameter(defaultValue = "${project.basedir}", readonly = true)
17 | private File basedir;
18 |
19 | @Parameter(defaultValue = "${project.basedir}/src/main/webapp/WEB-INF/sass/config.rb", property = "sass-java.configFile")
20 | private File config;
21 |
22 | public void execute() throws MojoExecutionException, MojoFailureException {
23 | if (config.exists()) {
24 | compiler.setConfigLocation(config);
25 | compiler.compile();
26 | } else {
27 | throw new MojoExecutionException("Configuration does not exist at " + config.getAbsolutePath());
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/sass-java/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4.0.0
4 |
5 |
6 | com.darrinholst
7 | sass-java-parent
8 | 3.4.20.1-SNAPSHOT
9 |
10 |
11 | sass-java
12 | 3.4.20.1-SNAPSHOT
13 | jar
14 |
15 |
16 |
17 | org.jruby
18 | jruby-complete
19 |
20 |
21 | com.darrinholst
22 | sass-java-gems
23 | 3.4.20.1-SNAPSHOT
24 |
25 |
26 | org.slf4j
27 | slf4j-api
28 | 1.6.1
29 |
30 |
31 | javax.servlet
32 | servlet-api
33 | 2.5
34 | provided
35 |
36 |
37 | org.slf4j
38 | slf4j-simple
39 | 1.7.7
40 | test
41 |
42 |
43 | junit
44 | junit
45 | test
46 |
47 |
48 | commons-io
49 | commons-io
50 | 1.3.2
51 | test
52 |
53 |
54 | org.mockito
55 | mockito-core
56 | 1.8.5
57 | test
58 |
59 |
60 |
61 |
62 |
63 |
64 | org.apache.maven.plugins
65 | maven-surefire-plugin
66 | 2.17
67 |
68 |
69 | ${project.parent.relativePath}/sass-java-gems/target/sass-java-gems-${project.version}.jar
70 |
71 |
72 |
73 |
74 | org.eclipse.jetty
75 | jetty-maven-plugin
76 | 9.2.3.v20140905
77 |
78 | src/test/webapp
79 | foo
80 | 9999
81 |
82 |
83 | RUNTIME_ENVIRONMENT
84 | local
85 |
86 |
87 |
88 |
89 |
106 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/sass-java/src/main/java/com/darrinholst/sass_java/Clock.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import java.util.Date;
4 |
5 | public class Clock {
6 | private static Clock delegate = new Clock();
7 |
8 | public static void setDelegate(Clock clock) {
9 | delegate = clock;
10 | }
11 |
12 | public static Date now() {
13 | return delegate.getCurrentTime();
14 | }
15 |
16 | protected Date getCurrentTime() {
17 | return new Date();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/sass-java/src/main/java/com/darrinholst/sass_java/Compiler.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import org.jruby.embed.ScriptingContainer;
4 |
5 | import java.io.File;
6 | import java.io.PrintWriter;
7 | import java.io.StringWriter;
8 |
9 | public class Compiler {
10 | private boolean initialized;
11 | private File configLocation;
12 |
13 | public void compile() {
14 | initialize();
15 | new ScriptingContainer().runScriptlet(buildCompileScript());
16 | }
17 |
18 | private void initialize() {
19 | if (!initialized) {
20 | new ScriptingContainer().runScriptlet(buildInitializationScript());
21 | initialized = true;
22 | }
23 | }
24 |
25 | private String buildCompileScript() {
26 | StringWriter raw = new StringWriter();
27 | PrintWriter script = new PrintWriter(raw);
28 |
29 | script.println("Dir.chdir(File.dirname('" + getConfigLocation() + "')) do ");
30 | script.println(" compiler = Compass.sass_compiler ");
31 | script.println(" compiler.logger = Compass::NullLogger.new ");
32 | script.println(" compiler.clean! ");
33 | script.println(" compiler.compile! ");
34 | script.println("end ");
35 | script.flush();
36 |
37 | return raw.toString();
38 | }
39 |
40 | private String buildInitializationScript() {
41 | StringWriter raw = new StringWriter();
42 | PrintWriter script = new PrintWriter(raw);
43 |
44 | script.println("require 'compass' ");
45 | script.println("require 'compass/sass_compiler' ");
46 | script.println("Compass.add_project_configuration '" + getConfigLocation() + "' ");
47 | script.println("Compass.configure_sass_plugin! ");
48 | script.flush();
49 |
50 | return raw.toString();
51 | }
52 |
53 | private String getConfigLocation() {
54 | return replaceSlashes(configLocation.getAbsolutePath());
55 | }
56 |
57 | private String replaceSlashes(String path) {
58 | return path.replaceAll("\\\\", "/");
59 | }
60 |
61 | public void setConfigLocation(File configLocation) {
62 | this.configLocation = configLocation;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/sass-java/src/main/java/com/darrinholst/sass_java/Config.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import javax.servlet.FilterConfig;
4 | import java.io.File;
5 |
6 | public class Config {
7 | private FilterConfig filterConfig;
8 |
9 | public Config(FilterConfig filterConfig) {
10 | this.filterConfig = filterConfig;
11 | }
12 |
13 | public File getRootPath() {
14 | return new File(filterConfig.getServletContext().getRealPath("/"));
15 | }
16 |
17 | public String getString(String parameterName) {
18 | return getString(parameterName, null);
19 | }
20 |
21 | public String getString(String parameterName, String defaultValue) {
22 | String value = filterConfig.getInitParameter(parameterName);
23 |
24 | if (value == null) {
25 | value = defaultValue;
26 | }
27 |
28 | return value;
29 | }
30 |
31 | public boolean getBoolean(String parameterName, boolean defaultValue) {
32 | String value = filterConfig.getInitParameter(parameterName);
33 |
34 | if (value == null) {
35 | return defaultValue;
36 | } else {
37 | return Boolean.parseBoolean(value);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/sass-java/src/main/java/com/darrinholst/sass_java/SassCompilingFilter.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import javax.servlet.*;
7 | import java.io.File;
8 | import java.io.IOException;
9 | import java.lang.*;
10 | import java.util.concurrent.atomic.AtomicBoolean;
11 |
12 | public class SassCompilingFilter implements Filter {
13 | private static final Logger LOG = LoggerFactory.getLogger(SassCompilingFilter.class);
14 | private static final int DWELL = 1000;
15 | protected static final String RETHROW_EXCEPTIONS_PARAM = "rethrowExceptions";
16 | protected static final String ONLY_RUN_KEY_PARAM = "onlyRunWhenKey";
17 | protected static final String ONLY_RUN_VALUE_PARAM = "onlyRunWhenValue";
18 | protected static final String CONFIG_LOCATION_PARAM = "configLocation";
19 | protected static final String DEFAULT_CONFIG_LOCATION = "WEB-INF" + File.separator + "sass" + File.separator + "config.rb";
20 |
21 | private long lastRun;
22 | private String onlyRunWhenKey;
23 | private String onlyRunWhenValue;
24 | private boolean rethrowExceptions;
25 | private Compiler compiler = new Compiler();
26 | private AtomicBoolean compiling = new AtomicBoolean(false);
27 |
28 | public void init(FilterConfig filterConfig) throws ServletException {
29 | Config config = new Config(filterConfig);
30 | onlyRunWhenKey = config.getString(ONLY_RUN_KEY_PARAM);
31 | onlyRunWhenValue = config.getString(ONLY_RUN_VALUE_PARAM);
32 | rethrowExceptions = config.getBoolean(RETHROW_EXCEPTIONS_PARAM, false);
33 |
34 | compiler.setConfigLocation(new File(
35 | config.getRootPath(),
36 | config.getString(CONFIG_LOCATION_PARAM, DEFAULT_CONFIG_LOCATION)
37 | ));
38 |
39 | if (environmentAllowsRunning()) compiler.compile();
40 |
41 | }
42 |
43 | public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
44 | if (shouldRun()) {
45 | run();
46 | }
47 |
48 | while (compiling.get()) waitABit();
49 |
50 | filterChain.doFilter(servletRequest, servletResponse);
51 | }
52 |
53 | private void waitABit() {
54 | try {
55 | Thread.sleep(100L);
56 | } catch (InterruptedException e) {
57 |
58 | }
59 | }
60 |
61 | private void run() {
62 | LOG.debug("compiling sass");
63 |
64 | try {
65 | compiling.set(true);
66 | compiler.compile();
67 | } catch (Exception e) {
68 | LOG.warn("exception thrown while compiling sass", e);
69 |
70 | if (rethrowExceptions) {
71 | throw new RuntimeException(e);
72 | }
73 | } finally {
74 | compiling.set(false);
75 | }
76 | }
77 |
78 | private boolean shouldRun() {
79 | return !compiling.get() && environmentAllowsRunning() && timeToRun();
80 | }
81 |
82 | private boolean environmentAllowsRunning() {
83 | if (onlyRunWhenKey != null) {
84 | String value = System.getProperty(onlyRunWhenKey, System.getenv(onlyRunWhenKey));
85 | return onlyRunWhenValue.equalsIgnoreCase(value);
86 | }
87 | return true;
88 | }
89 |
90 | private synchronized boolean timeToRun() {
91 | long now = Clock.now().getTime();
92 |
93 | if (now - lastRun >= DWELL) {
94 | lastRun = Clock.now().getTime();
95 | return true;
96 | } else {
97 | return false;
98 | }
99 | }
100 |
101 | public void destroy() {
102 | }
103 |
104 | public void setCompiler(Compiler compiler) {
105 | this.compiler = compiler;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/sass-java/src/test/java/com/darrinholst/sass_java/FakeClock.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import java.util.Date;
4 |
5 | public class FakeClock extends Clock {
6 | protected Date stopped = new Date();
7 |
8 | @Override
9 | protected Date getCurrentTime() {
10 | return stopped;
11 | }
12 |
13 | public void incrementSeconds(int seconds) {
14 | stopped = new Date(stopped.getTime() + (seconds * 1000));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sass-java/src/test/java/com/darrinholst/sass_java/SassCompilingFilterTest.java:
--------------------------------------------------------------------------------
1 | package com.darrinholst.sass_java;
2 |
3 | import org.apache.commons.io.FileUtils;
4 | import org.junit.After;
5 | import org.junit.Before;
6 | import org.junit.Rule;
7 | import org.junit.Test;
8 | import org.junit.rules.TemporaryFolder;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.ArgumentCaptor;
11 | import org.mockito.Mock;
12 | import org.mockito.runners.MockitoJUnitRunner;
13 |
14 | import javax.servlet.*;
15 | import java.io.File;
16 | import java.io.FileOutputStream;
17 | import java.io.IOException;
18 | import java.util.ArrayList;
19 | import java.util.Arrays;
20 | import java.util.List;
21 | import java.util.concurrent.CountDownLatch;
22 | import java.util.concurrent.atomic.AtomicBoolean;
23 |
24 | import static com.darrinholst.sass_java.SassCompilingFilter.*;
25 | import static java.util.Arrays.asList;
26 | import static junit.framework.Assert.*;
27 | import static org.mockito.Matchers.eq;
28 | import static org.mockito.Mockito.*;
29 |
30 | @RunWith(MockitoJUnitRunner.class)
31 | public class SassCompilingFilterTest {
32 | private static final String ONLY_RUN_KEY = "unit_test_environment";
33 | private static final String CSS_LOCATION = "css";
34 | private static final String SASS_LOCATION = "WEB-INF/sass";
35 |
36 | @Rule
37 | public TemporaryFolder temporaryFolder = new TemporaryFolder();
38 |
39 | @Mock
40 | ServletRequest servletRequest;
41 | @Mock
42 | ServletResponse servletResponse;
43 | @Mock
44 | ServletContext servletContext;
45 | @Mock
46 | FilterConfig filterConfig;
47 | @Mock
48 | FilterChain filterChain;
49 |
50 | private String webAppRoot;
51 | private FakeClock clock;
52 | private SassCompilingFilter filter;
53 |
54 | @Before
55 | public void setup() throws Exception {
56 | webAppRoot = temporaryFolder.newFolder("").getAbsolutePath();
57 | Clock.setDelegate(clock = new FakeClock());
58 | when(filterConfig.getServletContext()).thenReturn(servletContext);
59 | when(filterConfig.getInitParameter(RETHROW_EXCEPTIONS_PARAM)).thenReturn("true");
60 | when(servletContext.getRealPath("/")).thenReturn(webAppRoot);
61 |
62 | filter = new SassCompilingFilter();
63 | }
64 |
65 | @After
66 | public void teardown() throws Exception {
67 | System.clearProperty(ONLY_RUN_KEY);
68 | }
69 |
70 | @Test
71 | public void initShouldCompileFiles() throws ServletException {
72 | com.darrinholst.sass_java.Compiler compiler = mock(Compiler.class);
73 |
74 | filter.setCompiler(compiler);
75 |
76 | initFilter();
77 |
78 | verify(compiler).compile();
79 | }
80 |
81 | @Test
82 | public void compilerShouldNotBeInvokedIfItIsAlreadyCompiling() throws Exception {
83 | StubCompiler compiler = new StubCompiler(1L, 2000L, 1L);
84 | filter.setCompiler(compiler);
85 |
86 | setupDefaultDirectoriesAndConfigFile();
87 | initFilter();
88 |
89 | Thread thread = processRequestOnAnotherThread(servletRequest);
90 | clock.incrementSeconds(4);
91 | runFilter(servletRequest);
92 |
93 | thread.join();
94 |
95 | assertEquals(2, compiler.getNumberOfCompiles());
96 | }
97 |
98 | @Test
99 | public void requestsShouldBlockUntilTheCompilingHasCompleted() throws Exception {
100 | ArgumentCaptor captor = ArgumentCaptor.forClass(ServletRequest.class);
101 | filter.setCompiler(new StubCompiler(2000L, 1L));
102 | final ServletRequest request = mock(ServletRequest.class, "request");
103 | ServletRequest otherRequest = mock(ServletRequest.class, "otherRequest");
104 |
105 | setupDefaultDirectoriesAndConfigFile();
106 | initFilter();
107 |
108 | Thread thread = processRequestOnAnotherThread(request);
109 | runFilter(otherRequest);
110 |
111 | thread.join();
112 |
113 | verify(filterChain, times(2)).doFilter(captor.capture(), eq(servletResponse));
114 | assertEquals(Arrays.asList(request, otherRequest), captor.getAllValues());
115 | }
116 |
117 | @Test
118 | public void compiles() throws Exception {
119 | setupDefaultDirectoriesAndConfigFile();
120 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
121 |
122 | initAndRunFilter();
123 |
124 | assertEquals(asList("foo.css", "foo.css.map"), directoryListing(CSS_LOCATION));
125 | }
126 |
127 | @Test
128 | public void doesNotRunIfLastRunWasLessThanOneSecondAgo() throws Exception {
129 | setupDefaultDirectoriesAndConfigFile();
130 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
131 |
132 | initAndRunFilter();
133 |
134 | assertDirectoryNotEmpty(CSS_LOCATION);
135 | clearDirectory(CSS_LOCATION);
136 | assertDirectoryEmpty(CSS_LOCATION);
137 |
138 | runFilter();
139 |
140 | assertDirectoryEmpty(CSS_LOCATION);
141 | }
142 |
143 | @Test
144 | public void runsIfLastRunWasGreaterThanOneSecondAgo() throws Exception {
145 | setupDefaultDirectoriesAndConfigFile();
146 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
147 |
148 | initAndRunFilter();
149 |
150 | assertDirectoryNotEmpty(CSS_LOCATION);
151 | clearDirectory(CSS_LOCATION);
152 | assertDirectoryEmpty(CSS_LOCATION);
153 | clock.incrementSeconds(1);
154 |
155 | runFilter();
156 |
157 | assertDirectoryNotEmpty(CSS_LOCATION);
158 | }
159 |
160 | @Test
161 | public void runsOnlyInDevelopmentMode() throws Exception {
162 | setupDefaultDirectoriesAndConfigFile();
163 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
164 | when(filterConfig.getInitParameter(ONLY_RUN_KEY_PARAM)).thenReturn(ONLY_RUN_KEY);
165 | when(filterConfig.getInitParameter(ONLY_RUN_VALUE_PARAM)).thenReturn("development");
166 | System.setProperty(ONLY_RUN_KEY, "development");
167 |
168 | initAndRunFilter();
169 |
170 | assertDirectoryNotEmpty(CSS_LOCATION);
171 | }
172 |
173 | @Test
174 | public void runsOnlyInDevelopmentModeAndDoesNotCareAboutCase() throws Exception {
175 | setupDefaultDirectoriesAndConfigFile();
176 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
177 | when(filterConfig.getInitParameter(ONLY_RUN_KEY_PARAM)).thenReturn(ONLY_RUN_KEY);
178 | when(filterConfig.getInitParameter(ONLY_RUN_VALUE_PARAM)).thenReturn("development");
179 | System.setProperty(ONLY_RUN_KEY, "dEvElOpMeNt");
180 |
181 | initAndRunFilter();
182 |
183 | assertDirectoryNotEmpty(CSS_LOCATION);
184 | }
185 |
186 | @Test
187 | public void doesNotRunInUnknownModes() throws Exception {
188 | setupDefaultDirectoriesAndConfigFile();
189 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
190 | when(filterConfig.getInitParameter(ONLY_RUN_KEY_PARAM)).thenReturn(ONLY_RUN_KEY);
191 | when(filterConfig.getInitParameter(ONLY_RUN_VALUE_PARAM)).thenReturn("development");
192 |
193 | initAndRunFilter();
194 |
195 | assertDirectoryEmpty(CSS_LOCATION);
196 | }
197 |
198 | @Test
199 | public void doesNotRunInProductionMode() throws Exception {
200 | setupDefaultDirectoriesAndConfigFile();
201 | addScssFileTo(fullPathOf(SASS_LOCATION), "foo");
202 | when(filterConfig.getInitParameter(ONLY_RUN_KEY_PARAM)).thenReturn(ONLY_RUN_KEY);
203 | when(filterConfig.getInitParameter(ONLY_RUN_VALUE_PARAM)).thenReturn("development");
204 | System.setProperty(ONLY_RUN_KEY, "production");
205 |
206 | initAndRunFilter();
207 |
208 | assertDirectoryEmpty(CSS_LOCATION);
209 | }
210 |
211 | @Test
212 | public void importWorks() throws Exception {
213 | setupDefaultDirectoriesAndConfigFile();
214 | addScssFileTo(fullPathOf(SASS_LOCATION), "_other", "body {color: #000}");
215 | addScssFileTo(fullPathOf(SASS_LOCATION), "base", "@import 'other';");
216 |
217 | initAndRunFilter();
218 |
219 | assertEquals(asList("base.css", "base.css.map"), directoryListing(CSS_LOCATION));
220 | String expected = "body{color:#000}" + System.getProperty("line.separator") + "/*# sourceMappingURL=base.css.map */";
221 | assertEquals(expected, contentsOf(fullPathOf(CSS_LOCATION), "base.css").trim());
222 | }
223 |
224 | @Test
225 | public void multipleThreads() throws Exception {
226 | setupDefaultDirectoriesAndConfigFile();
227 | initFilter();
228 |
229 | CountDownLatch latch = new CountDownLatch(2);
230 |
231 | FilterThread thread1 = new FilterThread(latch);
232 | FilterThread thread2 = new FilterThread(latch);
233 |
234 | thread1.start();
235 | thread2.start();
236 |
237 | thread1.join();
238 | thread2.join();
239 |
240 | assertFalse("exception thrown in 1st thread", thread1.exceptionThrown);
241 | assertFalse("exception thrown in 2nd thread", thread2.exceptionThrown);
242 | }
243 |
244 | private class FilterThread extends Thread {
245 | private CountDownLatch latch;
246 | public boolean exceptionThrown;
247 |
248 | public FilterThread(CountDownLatch countdown) {
249 | this.latch = countdown;
250 | }
251 |
252 | @Override
253 | public void run() {
254 | try {
255 | latch.countDown();
256 | latch.await();
257 | runFilter();
258 | } catch (Exception e) {
259 | exceptionThrown = true;
260 | throw new RuntimeException(e);
261 | }
262 | }
263 | }
264 |
265 | private Thread processRequestOnAnotherThread(final ServletRequest request) throws InterruptedException {
266 | final AtomicBoolean requestProcessing = new AtomicBoolean(false);
267 | Thread thread = new Thread(new Runnable() {
268 | public void run() {
269 | requestProcessing.set(true);
270 | runFilter(request);
271 | }
272 | });
273 | thread.start();
274 |
275 | while (!requestProcessing.get()) {
276 | Thread.sleep(10L);
277 | }
278 |
279 | return thread;
280 | }
281 |
282 | private String contentsOf(File directory, String filename) throws Exception {
283 | return FileUtils.readFileToString(new File(directory, filename));
284 | }
285 |
286 | private void initAndRunFilter(String... parameters) throws ServletException, IOException {
287 | initFilter(parameters);
288 | runFilter();
289 | }
290 |
291 | private void initFilter(String... parameters) throws ServletException {
292 | for (int i = 0; i < parameters.length; i += 2) {
293 | when(filterConfig.getInitParameter(parameters[i])).thenReturn(parameters[i + 1]);
294 | }
295 |
296 | filter.init(filterConfig);
297 | }
298 |
299 | private void runFilter() {
300 | runFilter(servletRequest);
301 | }
302 |
303 | private void runFilter(ServletRequest request) {
304 | try {
305 | filter.doFilter(request, servletResponse, filterChain);
306 | } catch (Exception e) {
307 | throw new RuntimeException(e);
308 | }
309 | }
310 |
311 | private void addScssFileTo(File directory, String name) throws Exception {
312 | addScssFileTo(directory, name, "");
313 | }
314 |
315 | private void addScssFileTo(File directory, String name, String content) throws Exception {
316 | File file = new File(directory, name + ".scss");
317 | assertTrue(file.createNewFile());
318 | FileOutputStream output = new FileOutputStream(file);
319 | output.write(content.getBytes());
320 | output.close();
321 | }
322 |
323 | private void setupDefaultDirectoriesAndConfigFile() throws Exception {
324 | setupDirectories(CSS_LOCATION, SASS_LOCATION);
325 |
326 | File file = new File(fullPathOf(SASS_LOCATION), "config.rb");
327 | assertTrue(file.createNewFile());
328 | FileOutputStream output = new FileOutputStream(file);
329 | output.write(("" +
330 | "css_dir = '../../" + CSS_LOCATION + "'\n" +
331 | "sass_dir = '.'\n" +
332 | "line_comments = false\n" +
333 | "sourcemap = true\n" +
334 | "output_style = :compressed\n").getBytes());
335 | output.close();
336 | }
337 |
338 | private void setupDirectories(String cssLocation, String sassLocation) {
339 | assertTrue(fullPathOf(cssLocation).mkdirs());
340 | assertTrue(fullPathOf(sassLocation).mkdirs());
341 | }
342 |
343 | private File fullPathOf(String directory) {
344 | return new File(webAppRoot, directory);
345 | }
346 |
347 | private void assertDirectoryNotEmpty(String directoryName) {
348 | assertTrue(directoryName + " should not have been empty", directoryListing(directoryName).size() > 0);
349 | }
350 |
351 | private void assertDirectoryEmpty(String directoryName) {
352 | List list = directoryListing(directoryName);
353 | assertTrue("didn't expect " + list.toString() + " to be in " + directoryName, list.size() == 0);
354 | }
355 |
356 | private void clearDirectory(String directoryName) {
357 | List filenames = directoryListing(directoryName);
358 |
359 | for (String filename : filenames) {
360 | File file = new File(fullPathOf(directoryName), filename);
361 | assertTrue("trying to delete " + filename, file.delete());
362 | }
363 | }
364 |
365 | private List directoryListing(String directoryName) {
366 | return asList(fullPathOf(directoryName).list());
367 | }
368 |
369 | private class StubCompiler extends Compiler {
370 | private ArrayList compileTimes = new ArrayList();
371 | private int i;
372 |
373 | private StubCompiler(Long... compileTimes) {
374 | this.compileTimes.addAll(Arrays.asList(compileTimes));
375 | }
376 |
377 | @Override
378 | public void compile() {
379 | try {
380 | Thread.sleep(compileTimes.get(i++));
381 | } catch (InterruptedException e) {
382 | }
383 | }
384 |
385 | public int getNumberOfCompiles() {
386 | return i;
387 | }
388 | }
389 | }
390 |
--------------------------------------------------------------------------------
/sass-java/src/test/webapp/WEB-INF/lib/slf4j-simple-1.7.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/darrinholst/sass-java/9e440151a5a6f38b51f504e2ffad0d10328f87f5/sass-java/src/test/webapp/WEB-INF/lib/slf4j-simple-1.7.7.jar
--------------------------------------------------------------------------------
/sass-java/src/test/webapp/WEB-INF/sass/application.scss:
--------------------------------------------------------------------------------
1 | @import "compass/css3/text-shadow";
2 |
3 | $background-color: #ccc;
4 |
5 | body {
6 | background: $background-color;
7 | padding: 50px;
8 | }
9 |
10 | h1 {
11 | font-size: 50px;
12 | }
13 |
14 | h2 {
15 | font-size: 40px;
16 | padding-top: 20px;
17 | @include single-text-shadow(magenta, 3px, 3px);
18 | }
19 |
--------------------------------------------------------------------------------
/sass-java/src/test/webapp/WEB-INF/sass/config.rb:
--------------------------------------------------------------------------------
1 | css_dir = "../../stylesheets"
2 | sass_dir = "."
3 | sourcemap = true
4 | sass_options = {:quiet => true}
5 |
6 |
--------------------------------------------------------------------------------
/sass-java/src/test/webapp/WEB-INF/sass/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 | /* HTML5 display-role reset for older browsers */
27 | article, aside, details, figcaption, figure,
28 | footer, header, hgroup, menu, nav, section {
29 | display: block;
30 | }
31 | body {
32 | line-height: 1;
33 | }
34 | ol, ul {
35 | list-style: none;
36 | }
37 | blockquote, q {
38 | quotes: none;
39 | }
40 | blockquote:before, blockquote:after,
41 | q:before, q:after {
42 | content: '';
43 | content: none;
44 | }
45 | table {
46 | border-collapse: collapse;
47 | border-spacing: 0;
48 | }
--------------------------------------------------------------------------------
/sass-java/src/test/webapp/WEB-INF/web.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | index.html
7 |
8 |
9 |
10 | SassCompiler
11 | com.darrinholst.sass_java.SassCompilingFilter
12 |
13 | onlyRunWhenKey
14 | RUNTIME_ENVIRONMENT
15 |
16 |
17 | onlyRunWhenValue
18 | local
19 |
20 |
21 | configLocation
22 | WEB-INF/sass/config.rb
23 |
24 |
25 |
26 |
27 | SassCompiler
28 | *.css
29 |
30 |
--------------------------------------------------------------------------------
/sass-java/src/test/webapp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | SASS
4 |
5 |
6 |
7 |
8 | ZOMG...SASS IN JAVA!
9 | Now with Compass!
10 |
11 |
12 |
--------------------------------------------------------------------------------