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    }