diff --git a/pom.xml b/pom.xml index 70324cf..066bc72 100644 --- a/pom.xml +++ b/pom.xml @@ -3,11 +3,11 @@ 4.0.0 org.lesscss lesscss - 1.7.0.1.2-SNAPSHOT + 1.8.0.1.2-SNAPSHOT jar Official LESS CSS Compiler for Java Official LESS CSS Compiler for Java - http://github.com/marceloverdijk/lesscss-java + http://github.com/Gr1ver/lesscss-java org.sonatype.oss @@ -244,13 +244,13 @@ GitHub - http://github.com/marceloverdijk/lesscss-java/issues + https://github.com/Gr1ver/lesscss-java/issues - scm:git:git@github.com:marceloverdijk/lesscss-java.git - scm:git:git@github.com:marceloverdijk/lesscss-java.git - http://github.com/marceloverdijk/lesscss-java + scm:git:git@github.com:Gr1ver/lesscss-java.git + scm:git:git@github.com:Gr1ver/lesscss-java.git + https://github.com/Gr1ver/lesscss-java diff --git a/src/main/java/org/lesscss/ExpressionEvaluator.java b/src/main/java/org/lesscss/ExpressionEvaluator.java new file mode 100644 index 0000000..dc994f9 --- /dev/null +++ b/src/main/java/org/lesscss/ExpressionEvaluator.java @@ -0,0 +1,8 @@ +package org.lesscss; + +/** + * @author gr1ver + */ +public interface ExpressionEvaluator { + public String evaluate(String string); +} diff --git a/src/main/java/org/lesscss/LessSource.java b/src/main/java/org/lesscss/LessSource.java index 30b6f98..7e3a140 100644 --- a/src/main/java/org/lesscss/LessSource.java +++ b/src/main/java/org/lesscss/LessSource.java @@ -14,6 +14,8 @@ */ package org.lesscss; +import static java.util.regex.Pattern.MULTILINE; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; @@ -23,8 +25,8 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import static java.util.regex.Pattern.MULTILINE; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.BOMInputStream; import org.lesscss.logging.LessLogger; @@ -47,8 +49,37 @@ public class LessSource { private Resource resource; private String content; private String normalizedContent; + private ExpressionEvaluator expressionEvaluator; private Map imports = new LinkedHashMap(); + /** + * Simple helper method to handle simple files. This delegates + * to @see #LessSource(Resource) . + *

+ * Expression evaluator is null. + *

+ * + * @param input a File to use as input. + * + * @throws IOException + */ + public LessSource(File input) throws IOException { + this( new FileResource(input) ); + } + + /** + * Simple helper method to handle simple files. This delegates + * to @see #LessSource(Resource) . + * + * @param input a File to use as input. + * @param expressionEvaluator used to evaluate expressions in imports + * + * @throws IOException + */ + public LessSource(File input, ExpressionEvaluator expressionEvaluator) throws IOException { + this(new FileResource(input), expressionEvaluator); + } + /** * Constructs a new LessSource. *

@@ -57,6 +88,9 @@ public class LessSource { *

* The resource is read using the default Charset of the platform *

+ *

+ * Expression evaluator is null. + *

* * @param resource The File reference to the LESS source to read. * @throws FileNotFoundException If the LESS source (or one of its imports) could not be found. @@ -66,11 +100,32 @@ public LessSource(Resource resource) throws IOException { this(resource, Charset.defaultCharset()); } + /** + * Constructs a new LessSource. + *

+ * This will read the metadata and content of the LESS source, and will automatically resolve the imports. + *

+ *

+ * The resource is read using the default Charset of the platform + *

+ * + * @param resource The File reference to the LESS source to read. + * @param expressionEvaluator used to evaluate expressions in imports + * @throws FileNotFoundException If the LESS source (or one of its imports) could not be found. + * @throws IOException If the LESS source cannot be read. + */ + public LessSource(Resource resource, ExpressionEvaluator expressionEvaluator) throws IOException { + this(resource, Charset.defaultCharset(), expressionEvaluator); + } + /** * Constructs a new LessSource. *

* This will read the metadata and content of the LESS resource, and will automatically resolve the imports. *

+ *

+ * Expression evaluator is null. + *

* * @param resource The File reference to the LESS resource to read. * @param charset charset used to read the less resource. @@ -78,27 +133,39 @@ public LessSource(Resource resource) throws IOException { * @throws IOException If the LESS resource cannot be read. */ public LessSource(Resource resource, Charset charset) throws IOException { + this(resource, charset, null); + } + + /** + * Constructs a new LessSource. + *

+ * This will read the metadata and content of the LESS resource, and will automatically resolve the imports. + *

+ * + * @param resource The File reference to the LESS resource to read. + * @param charset charset used to read the less resource. + * @param expressionEvaluator used to evaluate expressions in imports + * @throws FileNotFoundException If the LESS resource (or one of its imports) could not be found. + * @throws IOException If the LESS resource cannot be read. + */ + public LessSource(Resource resource, Charset charset, ExpressionEvaluator expressionEvaluator) throws IOException { if (resource == null) { throw new IllegalArgumentException("Resource must not be null."); } if (!resource.exists()) { throw new IOException("Resource " + resource + " not found."); } + if(expressionEvaluator == null) { + logger.debug("Evaluator is Null"); + } this.resource = resource; + this.expressionEvaluator = expressionEvaluator; this.content = this.normalizedContent = loadResource(resource, charset); resolveImports(); } - /** - * Simple helper method to handle simple files. This delegates - * to @see #LessSource(Resource) . - * - * @param input a File to use as input. - * - * @throws IOException - */ - public LessSource(File input) throws IOException { - this( new FileResource(input) ); + private static String defaultString(String str1, String defaultStr) { + return str1 == null ? defaultStr : str1; } private String loadResource(Resource resource, Charset charset) throws IOException { @@ -192,14 +259,15 @@ public Map getImports() { private void resolveImports() throws IOException { Matcher importMatcher = IMPORT_PATTERN.matcher(normalizedContent); while (importMatcher.find()) { - String importedResource = importMatcher.group(5); + String importedResource = normalizePath(evaluateExpressions(importMatcher.group(5))); + importedResource = importedResource.matches(".*\\.(le?|c)ss$") ? importedResource : importedResource + ".less"; String importType = importMatcher.group(3)==null ? importedResource.substring(importedResource.lastIndexOf(".") + 1) : importMatcher.group(3); if (importType.equals("less")) { logger.debug("Importing %s", importedResource); if( !imports.containsKey(importedResource) ) { - LessSource importedLessSource = new LessSource(getImportedResource(importedResource)); + LessSource importedLessSource = new LessSource(getImportedResource(importedResource), expressionEvaluator); imports.put(importedResource, importedLessSource); normalizedContent = includeImportedContent(importedLessSource, importMatcher); @@ -212,12 +280,18 @@ private void resolveImports() throws IOException { } } + private String normalizePath(String path) { + return path == null ? null : defaultString(FilenameUtils.normalize(path), path); + } + private Resource getImportedResource(String importedResource) throws IOException { try { if( importedResource.startsWith("http:") || importedResource.startsWith("https:") ) { return new HttpResource(importedResource); } else { - return resource.createRelative(importedResource); + File importedFile = new File(importedResource); + return importedFile.isAbsolute() ? new FileResource(importedFile) : + resource.createRelative(importedFile.getPath()); } } catch (URISyntaxException e) { throw (IOException)new IOException( importedResource ).initCause(e); @@ -229,19 +303,35 @@ private String includeImportedContent(LessSource importedLessSource, Matcher imp builder.append(normalizedContent.substring(0, importMatcher.start(1))); String mediaQuery = importMatcher.group(8); - if( mediaQuery != null && mediaQuery.length() > 0) { + if( (mediaQuery != null) && (mediaQuery.length() > 0)) { builder.append("@media"); builder.append( mediaQuery ); builder.append("{\n"); } builder.append(importedLessSource.getNormalizedContent()); - if( mediaQuery != null && mediaQuery.length() > 0 ) { + if( (mediaQuery != null) && (mediaQuery.length() > 0) ) { builder.append("}\n"); } builder.append(normalizedContent.substring(importMatcher.end(1))); return builder.toString(); } + /** + * Performs call to evaluator if it is specified + * @param str + * @return evaluated string if evaluator is specified, original string otherwise + */ + private String evaluateExpressions(String str) { + String result; + if(expressionEvaluator == null) { + result = str; + } else { + result = expressionEvaluator.evaluate(str); + logger.debug(String.format("Evaluate string: %s result in: %s", str, result)); + } + return result; + } + public String getName() { return resource.getName(); } diff --git a/src/test/java/org/lesscss/LessSourceTest.java b/src/test/java/org/lesscss/LessSourceTest.java index 8d18a4d..a1a441d 100644 --- a/src/test/java/org/lesscss/LessSourceTest.java +++ b/src/test/java/org/lesscss/LessSourceTest.java @@ -14,33 +14,35 @@ */ package org.lesscss; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.AdditionalMatchers; -import org.mockito.Matchers; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; +import static org.hamcrest.CoreMatchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.matchers.JUnitMatchers.containsString; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; +import static org.powermock.api.mockito.PowerMockito.when; +import static org.powermock.api.mockito.PowerMockito.whenNew; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URISyntaxException; import java.net.URL; import java.nio.charset.Charset; import java.util.LinkedHashMap; import java.util.Map; -import static org.hamcrest.CoreMatchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.junit.matchers.JUnitMatchers.containsString; -import static org.powermock.api.mockito.PowerMockito.*; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; @PrepareForTest({FileUtils.class, IOUtils.class, LessSource.class, FileResource.class}) @RunWith(PowerMockRunner.class) @@ -55,11 +57,11 @@ public class LessSourceTest { @Mock private LessSource import1; @Mock private LessSource import2; @Mock private LessSource import3; - + private Map imports; private long lastModified = 1l; - + @Before public void setUp() throws Exception { imports = new LinkedHashMap(); @@ -67,24 +69,24 @@ public void setUp() throws Exception { imports.put("import2", import2); imports.put("import3", import3); - URL sourceUrl = getClass().getResource("/compatibility/a_source.less"); - sourceFile = new File(sourceUrl.getFile()); + URL sourceUrl = getClass().getResource("/compatibility/a_source.less"); + sourceFile = new File(sourceUrl.toURI().getSchemeSpecificPart()); } - + @Test public void testNewLessSourceWithoutImports() throws Exception { - + FileResource fileResource = new FileResource(sourceFile); lessSource = new LessSource(fileResource); - + assertEquals(sourceFile.getAbsolutePath(), lessSource.getAbsolutePath()); assertEquals("content", lessSource.getContent()); assertEquals("content", lessSource.getNormalizedContent()); assertEquals(sourceFile.lastModified(), lessSource.getLastModified()); assertEquals(sourceFile.lastModified(), lessSource.getLastModifiedIncludingImports()); assertEquals(0, lessSource.getImports().size()); - + verifyStatic(); FileUtils.readFileToString(sourceFile); } @@ -93,38 +95,38 @@ public void testNewLessSourceWithoutImports() throws Exception { public void testNewLessSourceFileNull() throws Exception { lessSource = new LessSource((Resource)null); } - + @Test(expected = IOException.class) public void testNewLessSourceFileNotFound() throws Exception { when(file.exists()).thenReturn(false); lessSource = new LessSource(new FileResource(file)); } - + @Test public void testLastModifiedIncludingImportsWhenNoImportModifiedLater() throws Exception { mockFile(true,"content","absolutePath"); - + when(import1.getLastModifiedIncludingImports()).thenReturn(0l); when(import2.getLastModifiedIncludingImports()).thenReturn(0l); when(import3.getLastModifiedIncludingImports()).thenReturn(0l); - + lessSource = new LessSource(new FileResource(file)); FieldUtils.writeField(lessSource, "imports", imports, true); - + assertEquals(1l, lessSource.getLastModifiedIncludingImports()); } - + @Test public void testLastModifiedIncludingImportsWhenImportModifiedLater() throws Exception { mockFile(true,"content","absolutePath"); - + when(import1.getLastModifiedIncludingImports()).thenReturn(0l); when(import2.getLastModifiedIncludingImports()).thenReturn(2l); when(import3.getLastModifiedIncludingImports()).thenReturn(0l); - + lessSource = new LessSource(new FileResource(file)); FieldUtils.writeField(lessSource, "imports", imports, true); - + assertEquals(2l, lessSource.getLastModifiedIncludingImports()); } @@ -140,9 +142,9 @@ public void testWithBadEncodingLessFile() throws Exception { assertThat(content, not(containsString("↓"))); } - private String readLessSourceWithEncoding(String encoding) throws IOException, IllegalAccessException { + private String readLessSourceWithEncoding(String encoding) throws IOException, IllegalAccessException, URISyntaxException { URL sourceUrl = getClass().getResource("/compatibility/utf8-content.less"); - File sourceFile = new File(sourceUrl.getFile()); + File sourceFile = new File(sourceUrl.toURI().getSchemeSpecificPart()); LessSource lessSource = new LessSource(new FileResource(sourceFile), Charset.forName(encoding)); return (String) FieldUtils.readField(lessSource, "content", true); }