001 /**
002 * Copyright 2005-2014 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krad.theme.preprocessor;
017
018 import org.apache.commons.lang.StringUtils;
019 import org.apache.log4j.Logger;
020 import org.kuali.rice.krad.theme.util.ThemeBuilderConstants;
021 import org.kuali.rice.krad.theme.util.ThemeBuilderUtils;
022 import org.lesscss.LessCompiler;
023 import org.lesscss.LessException;
024 import org.lesscss.LessSource;
025
026 import java.io.File;
027 import java.io.IOException;
028 import java.util.List;
029 import java.util.Properties;
030
031 /**
032 * Pre processor that picks up Less files in the theme directory and compiles to CSS
033 *
034 * <p>
035 * Less files are compiled using the Apache lesscss compiler
036 * </p>
037 *
038 * @author Kuali Rice Team (rice.collab@kuali.org)
039 * @see org.lesscss.LessCompiler
040 */
041 public class LessThemePreProcessor implements ThemePreProcessor {
042 private static final Logger LOG = Logger.getLogger(LessThemePreProcessor.class);
043
044 /**
045 * Processes Less files that should be included for the given theme
046 *
047 * <p>
048 * The list of Less files for the theme is collected by a helper method then iterated over and compiled
049 * using the less compiler. The list of Less files that were processed is written as a property in the theme
050 * properties for direct Less support in development mode
051 * </p>
052 *
053 * @param themeName name of the theme to process
054 * @param themeDirectory directory containing the theme assets
055 * @param themeProperties properties for the theme containing its configuration
056 * @see #getLessFileNamesForTheme(java.lang.String, java.io.File, java.util.Properties, java.io.File)
057 */
058 public void processTheme(String themeName, File themeDirectory, Properties themeProperties) {
059 if (LOG.isDebugEnabled()) {
060 LOG.debug("Performing Less compilation for theme " + themeName);
061 }
062
063 File stylesheetsDirectory = new File(themeDirectory, ThemeBuilderConstants.ThemeDirectories.STYLESHEETS);
064 if (!stylesheetsDirectory.exists()) {
065 throw new RuntimeException("Stylesheets directory does not exist for theme: " + themeName);
066 }
067
068 List<String> lessFileNames = getLessFileNamesForTheme(themeName, themeDirectory, themeProperties,
069 stylesheetsDirectory);
070
071 LessCompiler lessCompiler = new LessCompiler();
072
073 // not compressing here since that will be done later in the build process
074 lessCompiler.setCompress(false);
075
076 for (String lessFileName : lessFileNames) {
077 LOG.info("compiling less file: " + lessFileName);
078
079 File sourceLessFile = new File(stylesheetsDirectory, lessFileName);
080 File compiledLessFile = new File(stylesheetsDirectory, lessFileName.replace(
081 ThemeBuilderConstants.FileExtensions.LESS, ThemeBuilderConstants.FileExtensions.CSS));
082
083 try {
084 LessSource lessSource = new LessSource(sourceLessFile);
085
086 lessCompiler.compile(lessSource, compiledLessFile, true);
087 } catch (IOException e) {
088 throw new RuntimeException("Error while compiling LESS source: " + lessFileName, e);
089 } catch (LessException e) {
090 throw new RuntimeException("Error while compiling LESS source: " + lessFileName, e);
091 }
092 }
093
094 // add list of less files to theme properties so it can be picked up by the view theme
095 // runtime in development mode
096 themeProperties.put(ThemeBuilderConstants.DerivedConfiguration.THEME_LESS_FILES,
097 StringUtils.join(lessFileNames, ","));
098 }
099
100 /**
101 * Builds the list of Less files names that should be processed for the given theme
102 *
103 * <p>
104 * All files with the <code>.less</code> extension that are in the theme's <code>stylesheets</code>
105 * directory are picked up as part of the theme (this includes files that are overlaid from a parent).
106 *
107 * All subdirectories of stylesheets are also picked up, with the exception of the <code>include</code>
108 * subdirectory. Other exclusions can be configured using the <code>lessExcludes</code> property in the
109 * theme's properties file
110 * </p>
111 *
112 * @param themeName name of the theme to pull less files for
113 * @param themeDirectory directory containing the theme's assets
114 * @param themeProperties config properties for the theme
115 * @param stylesheetsDirectory theme directory which contains the stylesheets, less files will be
116 * picked up here
117 * @return list of less file names (any path is relative to the stylesheets directory)
118 */
119 protected List<String> getLessFileNamesForTheme(String themeName, File themeDirectory, Properties themeProperties,
120 File stylesheetsDirectory) {
121 String[] lessIncludes = ThemeBuilderUtils.getPropertyValueAsArray(
122 ThemeBuilderConstants.ThemeConfiguration.LESS_INCLUDES, themeProperties);
123
124 if ((lessIncludes == null) || (lessIncludes.length == 0)) {
125 lessIncludes = new String[1];
126
127 lessIncludes[0] = ThemeBuilderConstants.Patterns.ANT_MATCH_ALL + ThemeBuilderConstants.FileExtensions.LESS;
128 }
129
130 String[] lessExcludes = ThemeBuilderUtils.getPropertyValueAsArray(
131 ThemeBuilderConstants.ThemeConfiguration.LESS_EXCLUDES, themeProperties);
132
133 lessExcludes = ThemeBuilderUtils.addToArray(lessExcludes,
134 ThemeBuilderConstants.ThemeDirectories.INCLUDES + ThemeBuilderConstants.Patterns.ANT_MATCH_DIR);
135
136 return ThemeBuilderUtils.getDirectoryContents(stylesheetsDirectory, lessIncludes, lessExcludes);
137 }
138 }