001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.krad.theme.preprocessor; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.log4j.Logger; 020import org.kuali.rice.krad.theme.util.ThemeBuilderConstants; 021import org.kuali.rice.krad.theme.util.ThemeBuilderUtils; 022import org.lesscss.LessCompiler; 023import org.lesscss.LessException; 024import org.lesscss.LessSource; 025import org.springframework.core.io.ClassPathResource; 026import org.springframework.core.io.Resource; 027 028import java.io.File; 029import java.io.IOException; 030import java.net.URL; 031import java.util.List; 032import java.util.Properties; 033 034/** 035 * Pre processor that picks up Less files in the theme directory and compiles to CSS 036 * 037 * <p> 038 * Less files are compiled using the Apache lesscss compiler 039 * </p> 040 * 041 * @author Kuali Rice Team (rice.collab@kuali.org) 042 * @see org.lesscss.LessCompiler 043 */ 044public class LessThemePreProcessor implements ThemePreProcessor { 045 private static final Logger LOG = Logger.getLogger(LessThemePreProcessor.class); 046 047 private static final String LESS_JS_FILE = "less-1.7.0.min.js"; 048 049 /** 050 * Processes Less files that should be included for the given theme 051 * 052 * <p> 053 * The list of Less files for the theme is collected by a helper method then iterated over and compiled 054 * using the less compiler. The list of Less files that were processed is written as a property in the theme 055 * properties for direct Less support in development mode 056 * </p> 057 * 058 * @param themeName name of the theme to process 059 * @param themeDirectory directory containing the theme assets 060 * @param themeProperties properties for the theme containing its configuration 061 * @see #getLessFileNamesForTheme(java.lang.String, java.io.File, java.util.Properties, java.io.File) 062 */ 063 public void processTheme(String themeName, File themeDirectory, Properties themeProperties) { 064 if (LOG.isDebugEnabled()) { 065 LOG.debug("Performing Less compilation for theme " + themeName); 066 } 067 068 File stylesheetsDirectory = new File(themeDirectory, ThemeBuilderConstants.ThemeDirectories.STYLESHEETS); 069 if (!stylesheetsDirectory.exists()) { 070 throw new RuntimeException("Stylesheets directory does not exist for theme: " + themeName); 071 } 072 073 List<String> lessFileNames = getLessFileNamesForTheme(themeName, themeDirectory, themeProperties, 074 stylesheetsDirectory); 075 076 LessCompiler lessCompiler = new LessCompiler(); 077 078 URL lessJS = this.getClass().getClassLoader().getResource(LESS_JS_FILE); 079 lessCompiler.setLessJs(lessJS); 080 081 // not compressing here since that will be done later in the build process 082 lessCompiler.setCompress(false); 083 084 for (String lessFileName : lessFileNames) { 085 LOG.info("compiling less file: " + lessFileName); 086 087 File sourceLessFile = new File(stylesheetsDirectory, lessFileName); 088 File compiledLessFile = new File(stylesheetsDirectory, lessFileName.replace( 089 ThemeBuilderConstants.FileExtensions.LESS, ThemeBuilderConstants.FileExtensions.CSS)); 090 091 try { 092 LessSource lessSource = new LessSource(sourceLessFile); 093 094 lessCompiler.compile(lessSource, compiledLessFile, true); 095 } catch (IOException e) { 096 throw new RuntimeException("Error while compiling LESS source: " + lessFileName, e); 097 } catch (LessException e) { 098 throw new RuntimeException("Error while compiling LESS source: " + lessFileName, e); 099 } 100 } 101 102 // add list of less files to theme properties so it can be picked up by the view theme 103 // runtime in development mode 104 themeProperties.put(ThemeBuilderConstants.DerivedConfiguration.THEME_LESS_FILES, 105 StringUtils.join(lessFileNames, ",")); 106 } 107 108 /** 109 * Builds the list of Less files names that should be processed for the given theme 110 * 111 * <p> 112 * All files with the <code>.less</code> extension that are in the theme's <code>stylesheets</code> 113 * directory are picked up as part of the theme (this includes files that are overlaid from a parent). 114 * 115 * All subdirectories of stylesheets are also picked up, with the exception of the <code>include</code> 116 * subdirectory. Other exclusions can be configured using the <code>lessExcludes</code> property in the 117 * theme's properties file 118 * </p> 119 * 120 * @param themeName name of the theme to pull less files for 121 * @param themeDirectory directory containing the theme's assets 122 * @param themeProperties config properties for the theme 123 * @param stylesheetsDirectory theme directory which contains the stylesheets, less files will be 124 * picked up here 125 * @return list of less file names (any path is relative to the stylesheets directory) 126 */ 127 protected List<String> getLessFileNamesForTheme(String themeName, File themeDirectory, Properties themeProperties, 128 File stylesheetsDirectory) { 129 String[] lessIncludes = ThemeBuilderUtils.getPropertyValueAsArray( 130 ThemeBuilderConstants.ThemeConfiguration.LESS_INCLUDES, themeProperties); 131 132 if ((lessIncludes == null) || (lessIncludes.length == 0)) { 133 lessIncludes = new String[1]; 134 135 lessIncludes[0] = ThemeBuilderConstants.Patterns.ANT_MATCH_ALL + ThemeBuilderConstants.FileExtensions.LESS; 136 } 137 138 String[] lessExcludes = ThemeBuilderUtils.getPropertyValueAsArray( 139 ThemeBuilderConstants.ThemeConfiguration.LESS_EXCLUDES, themeProperties); 140 141 lessExcludes = ThemeBuilderUtils.addToArray(lessExcludes, 142 ThemeBuilderConstants.ThemeDirectories.INCLUDES + ThemeBuilderConstants.Patterns.ANT_MATCH_DIR); 143 144 return ThemeBuilderUtils.getDirectoryContents(stylesheetsDirectory, lessIncludes, lessExcludes); 145 } 146}