View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.theme.preprocessor;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.krad.theme.util.ThemeBuilderConstants;
21  import org.kuali.rice.krad.theme.util.ThemeBuilderUtils;
22  import org.lesscss.LessCompiler;
23  import org.lesscss.LessException;
24  import org.lesscss.LessSource;
25  
26  import java.io.File;
27  import java.io.IOException;
28  import java.util.List;
29  import java.util.Properties;
30  
31  /**
32   * Pre processor that picks up Less files in the theme directory and compiles to CSS
33   *
34   * <p>
35   * Less files are compiled using the Apache lesscss compiler
36   * </p>
37   *
38   * @author Kuali Rice Team (rice.collab@kuali.org)
39   * @see org.lesscss.LessCompiler
40   */
41  public class LessThemePreProcessor implements ThemePreProcessor {
42      private static final Logger LOG = Logger.getLogger(LessThemePreProcessor.class);
43  
44      /**
45       * Processes Less files that should be included for the given theme
46       *
47       * <p>
48       * The list of Less files for the theme is collected by a helper method then iterated over and compiled
49       * using the less compiler. The list of Less files that were processed is written as a property in the theme
50       * properties for direct Less support in development mode
51       * </p>
52       *
53       * @param themeName name of the theme to process
54       * @param themeDirectory directory containing the theme assets
55       * @param themeProperties properties for the theme containing its configuration
56       * @see #getLessFileNamesForTheme(java.lang.String, java.io.File, java.util.Properties, java.io.File)
57       */
58      public void processTheme(String themeName, File themeDirectory, Properties themeProperties) {
59          if (LOG.isDebugEnabled()) {
60              LOG.debug("Performing Less compilation for theme " + themeName);
61          }
62  
63          File stylesheetsDirectory = new File(themeDirectory, ThemeBuilderConstants.ThemeDirectories.STYLESHEETS);
64          if (!stylesheetsDirectory.exists()) {
65              throw new RuntimeException("Stylesheets directory does not exist for theme: " + themeName);
66          }
67  
68          List<String> lessFileNames = getLessFileNamesForTheme(themeName, themeDirectory, themeProperties,
69                  stylesheetsDirectory);
70  
71          LessCompiler lessCompiler = new LessCompiler();
72  
73          // not compressing here since that will be done later in the build process
74          lessCompiler.setCompress(false);
75  
76          for (String lessFileName : lessFileNames) {
77              LOG.info("compiling less file: " + lessFileName);
78  
79              File sourceLessFile = new File(stylesheetsDirectory, lessFileName);
80              File compiledLessFile = new File(stylesheetsDirectory, lessFileName.replace(
81                      ThemeBuilderConstants.FileExtensions.LESS, ThemeBuilderConstants.FileExtensions.CSS));
82  
83              try {
84                  LessSource lessSource = new LessSource(sourceLessFile);
85  
86                  lessCompiler.compile(lessSource, compiledLessFile, true);
87              } catch (IOException e) {
88                  throw new RuntimeException("Error while compiling LESS source: " + lessFileName, e);
89              } catch (LessException e) {
90                  throw new RuntimeException("Error while compiling LESS source: " + lessFileName, e);
91              }
92          }
93  
94          // add list of less files to theme properties so it can be picked up by the view theme
95          // runtime in development mode
96          themeProperties.put(ThemeBuilderConstants.DerivedConfiguration.THEME_LESS_FILES,
97                  StringUtils.join(lessFileNames, ","));
98      }
99  
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 }