();
+ /**
+ * 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);
}