Limit LESS CSS compilation to certain files.

git-svn-id: https://svn.libreccm.org/ccm/trunk@4999 8810af33-2d31-482b-a856-94f89814c4df
master
jensp 2017-09-14 12:29:07 +00:00
parent 522ab912e5
commit ce1e06ca2c
3 changed files with 192 additions and 181 deletions

View File

@ -15,7 +15,6 @@
* License along with this library; if not, write to the Free Software * License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/ */
package com.arsdigita.themedirector; package com.arsdigita.themedirector;
import com.arsdigita.persistence.DataCollection; import com.arsdigita.persistence.DataCollection;
@ -30,24 +29,31 @@ import com.arsdigita.util.parameter.IntegerParameter;
import java.util.Collection; import java.util.Collection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import com.arsdigita.util.StringUtils; import com.arsdigita.util.StringUtils;
import com.arsdigita.util.parameter.StringArrayParameter;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
/** /**
* This is the configuration file for the Theme application * This is the configuration file for the Theme application
*/ */
public class ThemeDirectorConfig extends AbstractConfig { public class ThemeDirectorConfig extends AbstractConfig {
/** Internal logger instance to faciliate debugging. Enable logging output /**
* by editing /WEB-INF/conf/log4j.properties int the runtime environment * Internal logger instance to faciliate debugging. Enable logging output by
* and set com.arsdigita.themedirector.ThemeDirectorConfig=DEBUG * editing /WEB-INF/conf/log4j.properties int the runtime environment and
* by uncommenting or adding the line. */ * set com.arsdigita.themedirector.ThemeDirectorConfig=DEBUG by uncommenting
private static final Logger s_log = Logger.getLogger(ThemeDirectorConfig.class); * or adding the line.
*/
private static final Logger s_log = Logger.getLogger(
ThemeDirectorConfig.class);
/** Singelton config object. */ /**
* Singelton config object.
*/
private static ThemeDirectorConfig s_conf; private static ThemeDirectorConfig s_conf;
/** /**
@ -55,6 +61,7 @@ public class ThemeDirectorConfig extends AbstractConfig {
* *
* Singelton pattern, don't instantiate a config object using the * Singelton pattern, don't instantiate a config object using the
* constructor directly! * constructor directly!
*
* @return * @return
*/ */
public static synchronized ThemeDirectorConfig getInstance() { public static synchronized ThemeDirectorConfig getInstance() {
@ -68,70 +75,84 @@ public class ThemeDirectorConfig extends AbstractConfig {
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
// Set of Configuration Parameters // Set of Configuration Parameters
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
/**
* Directory that all of the default themes are copied from.
*/
private final Parameter m_defaultThemePath = new StringParameter(
"themedirector.default_theme_path",
Parameter.OPTIONAL, "/themes/master/");
/** Directory that all of the default themes are copied from. */ /**
private final Parameter m_defaultThemePath = * File containing the default themes directory. Used in conjuntion with
new StringParameter * com.arsdigita.themedirectory.default_directory_filter to dictate the
("themedirector.default_theme_path", * final default directory.
Parameter.OPTIONAL, "/themes/master/"); */
private final Parameter m_defaultThemeManifest = new StringParameter(
"themedirector.default_theme_manifest",
Parameter.OPTIONAL, "ccm-themedirector.web.mf");
/** File containing the default themes directory. Used in conjuntion with /**
com.arsdigita.themedirectory.default_directory_filter to dictate the * list of file extensions that should be included when the designer
final default directory. */ * requests 'all graphics files'
private final Parameter m_defaultThemeManifest = */
new StringParameter private final Parameter m_fileExtParam = new StringParameter(
("themedirector.default_theme_manifest", "themedirector.file_extensions",
Parameter.OPTIONAL, "ccm-themedirector.web.mf"); Parameter.REQUIRED,
"bmp css eot gif jpeg jpg js png svg ttf woff xml xsl");
/** list of file extensions that should be included when the designer private final Parameter m_lessFilesParam = new StringArrayParameter(
requests 'all graphics files' */ "themedirectory.less_files.compile",
private final Parameter m_fileExtParam = Parameter.OPTIONAL,
new StringParameter new String[]{"styles/style.less"});
("themedirector.file_extensions",
Parameter.REQUIRED, "bmp css eot gif jpeg jpg js png svg ttf woff xml xsl");
/** number of seconds before checking for updated development files. /**
in a multi-jvm installation. (0 means never start) */ * number of seconds before checking for updated development files. in a
private final Parameter m_themeDevFileWatchStartupDelay = * multi-jvm installation. (0 means never start)
new IntegerParameter */
("themedirector.theme_dev_file_watch_startup_delay", private final Parameter m_themeDevFileWatchStartupDelay
Parameter.REQUIRED, new Integer(60*2)); = new IntegerParameter(
"themedirector.theme_dev_file_watch_startup_delay",
Parameter.REQUIRED, new Integer(60 * 2));
/** Number of seconds between checks for updated development files /**
in a multi-jvm installation. */ * Number of seconds between checks for updated development files in a
private final Parameter m_themeDevFileWatchPollDelay = * multi-jvm installation.
new IntegerParameter */
("themedirector.theme_dev_file_watch_poll_delay", private final Parameter m_themeDevFileWatchPollDelay = new IntegerParameter(
Parameter.REQUIRED, new Integer(60*90)); //once every 90 minutes "themedirector.theme_dev_file_watch_poll_delay",
Parameter.REQUIRED, new Integer(60 * 90)); //once every 90 minutes
/** Number of seconds before checking for update of published theme files /**
in a multi-jvm installation. (0 means never start) */ * Number of seconds before checking for update of published theme files in
private final Parameter m_themePubFileWatchStartupDelay = * a multi-jvm installation. (0 means never start)
new IntegerParameter */
("themedirector.theme_pub_file_watch_startup_delay", private final Parameter m_themePubFileWatchStartupDelay
Parameter.REQUIRED, new Integer(60*2)); = new IntegerParameter(
"themedirector.theme_pub_file_watch_startup_delay",
Parameter.REQUIRED, new Integer(60 * 2));
/** number of seconds between checks for recently published theme files /**
in a multi-jvm installation. */ * number of seconds between checks for recently published theme files in a
private final Parameter m_themePubFileWatchPollDelay = * multi-jvm installation.
new IntegerParameter */
("themedirector.theme_pub_file_watch_poll_delay", private final Parameter m_themePubFileWatchPollDelay = new IntegerParameter(
Parameter.REQUIRED, new Integer(60*60)); // default to once an hour "themedirector.theme_pub_file_watch_poll_delay",
Parameter.REQUIRED, new Integer(60 * 60)); // default to once an hour
/**
/** */ * */
private Collection m_downloadFileExtensions = null; private Collection m_downloadFileExtensions = null;
/** /**
* Constructor. * Constructor. Singelton pattern, don't instantiate a config object using
* Singelton pattern, don't instantiate a config object using the * the constructor directly! Use getInstance() instead.
constructor directly! Use getInstance() instead.
*/ */
public ThemeDirectorConfig() { public ThemeDirectorConfig() {
register(m_defaultThemePath); register(m_defaultThemePath);
register(m_defaultThemeManifest); register(m_defaultThemeManifest);
register(m_fileExtParam); register(m_fileExtParam);
register(m_lessFilesParam);
register(m_themeDevFileWatchStartupDelay); register(m_themeDevFileWatchStartupDelay);
register(m_themeDevFileWatchPollDelay); register(m_themeDevFileWatchPollDelay);
@ -141,34 +162,30 @@ public class ThemeDirectorConfig extends AbstractConfig {
loadInfo(); loadInfo();
} }
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
// Set of Configuration Parameters // Set of Configuration Parameters
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
/** /**
* Retrieves the path to a directory containing a complete set of theme * Retrieves the path to a directory containing a complete set of theme
* files to copy into a new theme as a default implementation. * files to copy into a new theme as a default implementation. By default it
* By default it is set to a master directory containing the distribution's * is set to a master directory containing the distribution's default theme.
* default theme. When creating a new theme it is copied over to provide * When creating a new theme it is copied over to provide a default for the
* a default for the new theme. * new theme.
* *
* Developer's note: * Developer's note: Previously it was used as a string to filter a files
* Previously it was used as a string to filter a files directory stored in * directory stored in the Manifest file. Matching files were copied to the
* the Manifest file. Matching files were copied to the new theme's directory. * new theme's directory. The reason to use this approach is not documented.
* The reason to use this approach is not documented. As a guess: it enables * As a guess: it enables to copy from more than one directory, if those
* to copy from more than one directory, if those directories share a common * directories share a common name part which is specified here. But because
* name part which is specified here. But because all files must be present * all files must be present at deploy time to be included into the Manifest
* at deploy time to be included into the Manifest file it makes no sense. * file it makes no sense. From original comment: "Specifically, if this is
* From original comment: "Specifically, if this is not null * not null (or the empty string) than any file that is used as part of the
* (or the empty string) than any file that is used as part of * default directory must start with this string."
* the default directory must start with this string."
* *
* @return name of a directory containing a default theme implementation * @return name of a directory containing a default theme implementation
*/ */
public String getDefaultThemePath() { public String getDefaultThemePath() {
String defaultThemePath = (String)get(m_defaultThemePath); String defaultThemePath = (String) get(m_defaultThemePath);
if (defaultThemePath == null || defaultThemePath.trim().length() == 0) { if (defaultThemePath == null || defaultThemePath.trim().length() == 0) {
return null; return null;
} }
@ -200,23 +217,22 @@ public class ThemeDirectorConfig extends AbstractConfig {
// } // }
// return ctx; // return ctx;
// } // }
/** /**
* This returns the name of the manifest file containing a list of default * This returns the name of the manifest file containing a list of default
* theme. * theme.
* *
* @return * @return
*
* @deprecated replaced by a direct copy from the default directory * @deprecated replaced by a direct copy from the default directory
*/ */
public String getDefaultThemeManifest() { public String getDefaultThemeManifest() {
return (String)get(m_defaultThemeManifest); return (String) get(m_defaultThemeManifest);
} }
private static final String DEFAULT_THEME_URL = ThemeDirector.DEFAULT_THEME
private static final String DEFAULT_THEME_URL = + "." + Theme.URL;
ThemeDirector.DEFAULT_THEME + "." + Theme.URL; private static final String DEFAULT_THEME_URL_ATTRIBUTE
private static final String DEFAULT_THEME_URL_ATTRIBUTE = = "defaultThemeURLAttribute";
"defaultThemeURLAttribute";
/** /**
* Purpose undocumented. * Purpose undocumented.
@ -225,7 +241,7 @@ public class ThemeDirectorConfig extends AbstractConfig {
*/ */
public Collection getDownloadFileExtensions() { public Collection getDownloadFileExtensions() {
if (m_downloadFileExtensions == null) { if (m_downloadFileExtensions == null) {
String extensions = (String)get(m_fileExtParam); String extensions = (String) get(m_fileExtParam);
if (extensions != null) { if (extensions != null) {
extensions = StringUtils.stripWhiteSpace(extensions.trim()); extensions = StringUtils.stripWhiteSpace(extensions.trim());
String[] extArray = StringUtils.split(extensions, ' '); String[] extArray = StringUtils.split(extensions, ' ');
@ -237,79 +253,89 @@ public class ThemeDirectorConfig extends AbstractConfig {
return m_downloadFileExtensions; return m_downloadFileExtensions;
} }
/** /**
* Purpose undocumented. * Purpose undocumented.
* *
* @param req * @param req
*
* @return * @return
*/ */
public static String getDefaultThemeURL( HttpServletRequest req ) { public static String getDefaultThemeURL(HttpServletRequest req) {
String themeURL = (String) req.getAttribute( DEFAULT_THEME_URL_ATTRIBUTE ); String themeURL = (String) req.getAttribute(DEFAULT_THEME_URL_ATTRIBUTE);
if( null != themeURL ) return themeURL; if (null != themeURL) {
return themeURL;
}
DataCollection themeApps = SessionManager.getSession().retrieve DataCollection themeApps = SessionManager.getSession().retrieve(
( ThemeDirector.BASE_DATA_OBJECT_TYPE ); ThemeDirector.BASE_DATA_OBJECT_TYPE);
themeApps.addPath( DEFAULT_THEME_URL ); themeApps.addPath(DEFAULT_THEME_URL);
if( themeApps.next() ) { if (themeApps.next()) {
themeURL = (String) themeApps.get( DEFAULT_THEME_URL ); themeURL = (String) themeApps.get(DEFAULT_THEME_URL);
if( s_log.isDebugEnabled() ) { if (s_log.isDebugEnabled()) {
s_log.debug( "Theme app: " + themeApps.get( "id" ) ); s_log.debug("Theme app: " + themeApps.get("id"));
s_log.debug( "Default Theme URL: " + themeApps.get( DEFAULT_THEME_URL ) ); s_log.debug("Default Theme URL: " + themeApps.get(
DEFAULT_THEME_URL));
} }
if( themeApps.next() ) { if (themeApps.next()) {
s_log.warn( "There appear to be multiple themes applications loaded" ); s_log.warn(
"There appear to be multiple themes applications loaded");
themeApps.close(); themeApps.close();
} }
} else { } else {
s_log.warn( "There doesn't appear to be a themes application loaded" ); s_log.warn("There doesn't appear to be a themes application loaded");
} }
req.setAttribute( DEFAULT_THEME_URL_ATTRIBUTE, themeURL ); req.setAttribute(DEFAULT_THEME_URL_ATTRIBUTE, themeURL);
return themeURL; return themeURL;
} }
/** /**
* The number of seconds to wait before checking the database * The number of seconds to wait before checking the database for the first
* for the first time. A value of 0 means that the thread * time. A value of 0 means that the thread should not be started. This
* should not be started. This checks for published files. * checks for published files.
* *
* @return * @return
*/ */
public Integer getThemePubFileWatchStartupDelay() { public Integer getThemePubFileWatchStartupDelay() {
return (Integer)get(m_themePubFileWatchStartupDelay); return (Integer) get(m_themePubFileWatchStartupDelay);
} }
/** /**
* Returns the number of seconds between checking for updated * Returns the number of seconds between checking for updated files in the
* files in the file system. This checks for published files. * file system. This checks for published files.
* *
* @return * @return
*/ */
public Integer getThemePubFileWatchPollDelay() { public Integer getThemePubFileWatchPollDelay() {
return (Integer)get(m_themePubFileWatchPollDelay); return (Integer) get(m_themePubFileWatchPollDelay);
} }
/** /**
* The number of seconds to wait before checking the database * The number of seconds to wait before checking the database for the first
* for the first time. A value of 0 means that the thread * time. A value of 0 means that the thread should not be started. This
* should not be started. This checks for development files. * checks for development files.
*
* @return * @return
*/ */
public Integer getThemeDevFileWatchStartupDelay() { public Integer getThemeDevFileWatchStartupDelay() {
return (Integer)get(m_themeDevFileWatchStartupDelay); return (Integer) get(m_themeDevFileWatchStartupDelay);
} }
/** /**
* Returns the number of seconds between checking for updated * Returns the number of seconds between checking for updated files in the
* files in the file system. This checks for development files. * file system. This checks for development files.
*
* @return * @return
*/ */
public Integer getThemeDevFileWatchPollDelay() { public Integer getThemeDevFileWatchPollDelay() {
return (Integer)get(m_themeDevFileWatchPollDelay); return (Integer) get(m_themeDevFileWatchPollDelay);
} }
public String[] getLessFiles() {
return (String[]) get(m_lessFilesParam);
}
} }

View File

@ -38,3 +38,7 @@ themedirector.theme_pub_file_watch_poll_delay.purpose=This is the number of seco
themedirector.theme_pub_file_watch_poll_delay.example=[300] themedirector.theme_pub_file_watch_poll_delay.example=[300]
themedirector.theme_pub_file_watch_poll_delay.format=[integer] themedirector.theme_pub_file_watch_poll_delay.format=[integer]
themedirectory.less_files.compile.title=Theme relative paths of LESS files to compile.
themedirectory.less_files.compile.purpose=Names of the LESS files to compile to CSS. Path is relative to theme dirctory.
themedirectory.less_files.compile.example=style/styles.less
themedirectory.less_files.compile.format=[string]

View File

@ -15,12 +15,14 @@
package com.arsdigita.themedirector.util; package com.arsdigita.themedirector.util;
import com.arsdigita.themedirector.Theme; import com.arsdigita.themedirector.Theme;
import com.arsdigita.themedirector.ThemeDirectorConfig;
import com.arsdigita.themedirector.ThemeFile; import com.arsdigita.themedirector.ThemeFile;
import com.arsdigita.themedirector.ThemeFileCollection; import com.arsdigita.themedirector.ThemeFileCollection;
import com.arsdigita.util.Assert; import com.arsdigita.util.Assert;
import com.arsdigita.util.UncheckedWrapperException; import com.arsdigita.util.UncheckedWrapperException;
import com.inet.lib.less.Less; import com.inet.lib.less.Less;
import com.inet.lib.less.LessException;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
import java.util.Date; import java.util.Date;
@ -35,12 +37,8 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.io.InputStreamReader; import java.util.ArrayList;
import java.util.List;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
/** /**
* This is a utility class that is able to take a theme and, when necessary, * This is a utility class that is able to take a theme and, when necessary,
@ -109,7 +107,7 @@ public class ThemeFileUtil {
/** /**
* Prepares the theme directory. At the moment only compiles LESS CSS files. * Prepares the theme directory. At the moment only compiles LESS CSS files.
* All files which do not end with {@code *.less}. Also files ending with * All files which do end with {@code *.less}. Also files ending with
* {@code *.inc.less}. are ignored. These files are interpreted as include * {@code *.inc.less}. are ignored. These files are interpreted as include
* files that are included into another LESS file. * files that are included into another LESS file.
* *
@ -123,75 +121,58 @@ public class ThemeFileUtil {
return; return;
} }
final List<Path> eligiblePaths = new ArrayList<>();
for (final String lessFileName : ThemeDirectorConfig.getInstance()
.getLessFiles()) {
if (lessFileName.startsWith("/")) {
eligiblePaths.add(root.resolve(lessFileName.substring(1)));
} else {
eligiblePaths.add(root.resolve(lessFileName));
}
}
try { try {
Files Files
.newDirectoryStream(root) .newDirectoryStream(root)
.forEach(path -> prepareThemeFile(path)); .forEach(path -> prepareThemeFile(path, eligiblePaths));
} catch (IOException ex) { } catch (IOException ex) {
throw new UncheckedWrapperException(ex); throw new UncheckedWrapperException(ex);
} }
} }
private static void prepareThemeFile(final Path path) { private static void prepareThemeFile(final Path path,
final List<Path> eligiablePaths) {
if (Files.isDirectory(path)) { if (Files.isDirectory(path)) {
try { try {
Files Files
.newDirectoryStream(path) .newDirectoryStream(path)
.forEach(current -> prepareThemeFile(current)); .forEach(current -> prepareThemeFile(current,
eligiablePaths));
} catch (IOException ex) { } catch (IOException ex) {
throw new UncheckedWrapperException(ex); throw new UncheckedWrapperException(ex);
} }
} else { } else {
final String fileName = path.toString(); final String fileName = path.toString();
if (fileName.toLowerCase().endsWith(".less") if (fileName.toLowerCase().endsWith(".less")
&& !fileName.toLowerCase().endsWith(".inc.less")) { && !fileName.toLowerCase().endsWith(".inc.less")
&& !path.getFileName().toString().startsWith(".")
&& eligiablePaths.contains(path)) {
try { try {
final String result = Less.compile(path.toFile(), false); final String result = Less.compile(path.toFile(), false);
final String outputPath = String.format( final String outputPath = String.format(
"%s.css", "%s.css",
fileName.substring(0, fileName.length() - ".less" fileName.substring(0,
.length())); fileName.length() - ".less".length()));
final Path output = Paths.get(outputPath); final Path output = Paths.get(outputPath);
Files.write(output, result.getBytes()); Files.write(output, result.getBytes());
} catch (IOException ex) { } catch (IOException | LessException ex) {
throw new UncheckedWrapperException(ex); throw new UncheckedWrapperException(
String.format("Error while compiling file \"%s\".",
fileName),
ex);
} }
// s_log.debug(String.format("Compiling %s to CSS...", fileName));
// final ScriptEngine scriptEngine = new ScriptEngineManager()
// .getEngineByName("JavaScript");
// try {
// scriptEngine.eval("var global = this;\n"
// + "var window = this;\n"
// + "var process = {env:{}};\n" + "\n"
// + "var console = {};\n"
// + "console.debug = print;\n"
// + "console.log = print;\n"
// + "console.warn = print;\n"
// + "console.error = print;");
// scriptEngine
// .eval(new InputStreamReader(ThemeFileUtil.class
// .getResourceAsStream(
// "/com/arsdigita/themedirector/util/less.min.js")));
// } catch (ScriptException ex) {
// throw new UncheckedWrapperException(ex);
// }
// final Invocable invocable = (Invocable) scriptEngine;
// final String lesscss;
// try {
// lesscss = new String(Files.readAllBytes(path));
// } catch (IOException ex) {
// throw new UncheckedWrapperException(ex);
// }
//
// try {
// final Object result = invocable.invokeFunction("render",
// lesscss);
// s_log.debug(result);
// } catch (NoSuchMethodException | ScriptException ex) {
// throw new UncheckedWrapperException(ex);
// }
} }
} }
} }