001    /**
002     * Copyright (c) 2000-2013 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.tools;
016    
017    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
018    import com.liferay.portal.kernel.io.unsync.UnsyncPrintWriter;
019    import com.liferay.portal.kernel.util.FastDateFormatFactoryUtil;
020    import com.liferay.portal.kernel.util.FileUtil;
021    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
022    import com.liferay.portal.kernel.util.PropsUtil;
023    import com.liferay.portal.kernel.util.StringPool;
024    import com.liferay.portal.kernel.util.StringUtil;
025    import com.liferay.portal.kernel.util.SystemProperties;
026    import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
027    import com.liferay.portal.kernel.util.Validator;
028    import com.liferay.portal.model.ModelHintsConstants;
029    import com.liferay.portal.scripting.ruby.RubyExecutor;
030    import com.liferay.portal.servlet.filters.minifier.MinifierFilter;
031    import com.liferay.portal.util.FastDateFormatFactoryImpl;
032    import com.liferay.portal.util.FileImpl;
033    import com.liferay.portal.util.PortalImpl;
034    import com.liferay.portal.util.PortalUtil;
035    import com.liferay.portal.util.PropsImpl;
036    
037    import java.io.File;
038    
039    import java.util.ArrayList;
040    import java.util.HashMap;
041    import java.util.List;
042    import java.util.Map;
043    
044    import org.apache.tools.ant.DirectoryScanner;
045    
046    /**
047     * @author Brian Wing Shun Chan
048     */
049    public class SassToCssBuilder {
050    
051            public static File getCacheFile(String fileName) {
052                    fileName = StringUtil.replace(
053                            fileName, StringPool.BACK_SLASH, StringPool.SLASH);
054    
055                    int pos = fileName.lastIndexOf(StringPool.SLASH);
056    
057                    String cacheFileName =
058                            fileName.substring(0, pos + 1) + ".sass-cache/" +
059                                    fileName.substring(pos + 1);
060    
061                    return new File(cacheFileName);
062            }
063    
064            public static void main(String[] args) {
065                    Map<String, String> arguments = ArgumentsUtil.parseArguments(args);
066    
067                    List<String> dirNames = new ArrayList<String>();
068    
069                    String dirName = arguments.get("sass.dir");
070    
071                    if (Validator.isNotNull(dirName)) {
072                            dirNames.add(dirName);
073                    }
074                    else {
075                            for (int i = 0;; i++ ) {
076                                    dirName = arguments.get("sass.dir." + i);
077    
078                                    if (Validator.isNotNull(dirName)) {
079                                            dirNames.add(dirName);
080                                    }
081                                    else {
082                                            break;
083                                    }
084                            }
085                    }
086    
087                    try {
088                            new SassToCssBuilder(dirNames);
089                    }
090                    catch (Exception e) {
091                            e.printStackTrace();
092                    }
093            }
094    
095            public static String parseStaticTokens(String content) {
096                    return StringUtil.replace(
097                            content,
098                            new String[] {
099                                    "@model_hints_constants_text_display_height@",
100                                    "@model_hints_constants_text_display_width@",
101                                    "@model_hints_constants_textarea_display_height@",
102                                    "@model_hints_constants_textarea_display_width@"
103                            },
104                            new String[] {
105                                    ModelHintsConstants.TEXT_DISPLAY_HEIGHT,
106                                    ModelHintsConstants.TEXT_DISPLAY_WIDTH,
107                                    ModelHintsConstants.TEXTAREA_DISPLAY_HEIGHT,
108                                    ModelHintsConstants.TEXTAREA_DISPLAY_WIDTH
109                            });
110            }
111    
112            public SassToCssBuilder(List<String> dirNames) throws Exception {
113                    Class<?> clazz = getClass();
114    
115                    ClassLoader classLoader = clazz.getClassLoader();
116    
117                    _initUtil(classLoader);
118    
119                    _rubyScript = StringUtil.read(
120                            classLoader,
121                            "com/liferay/portal/servlet/filters/dynamiccss/main.rb");
122    
123                    _tempDir = SystemProperties.get(SystemProperties.TMP_DIR);
124    
125                    for (String dirName : dirNames) {
126    
127                            // Create a new Ruby executor as a workaround for a bug with Ruby
128                            // that breaks "ant build-css" when it parses too many CSS files
129    
130                            _rubyExecutor = new RubyExecutor();
131    
132                            _rubyExecutor.setExecuteInSeparateThread(false);
133    
134                            _parseSassDirectory(dirName);
135                    }
136            }
137    
138            private String _getContent(File file) throws Exception {
139                    String content = FileUtil.read(file);
140    
141                    content = MinifierFilter.aggregateCss(file.getParent(), content);
142    
143                    return parseStaticTokens(content);
144            }
145    
146            private String _getCssThemePath(String fileName) {
147                    int pos = fileName.lastIndexOf("/css/");
148    
149                    return fileName.substring(0, pos + 4);
150            }
151    
152            private void _initUtil(ClassLoader classLoader) {
153                    FastDateFormatFactoryUtil fastDateFormatFactoryUtil =
154                            new FastDateFormatFactoryUtil();
155    
156                    fastDateFormatFactoryUtil.setFastDateFormatFactory(
157                            new FastDateFormatFactoryImpl());
158    
159                    FileUtil fileUtil = new FileUtil();
160    
161                    fileUtil.setFile(new FileImpl());
162    
163                    PortalClassLoaderUtil.setClassLoader(classLoader);
164    
165                    PortalUtil portalUtil = new PortalUtil();
166    
167                    portalUtil.setPortal(new PortalImpl());
168    
169                    PropsUtil.setProps(new PropsImpl());
170            }
171    
172            private void _parseSassDirectory(String dirName) throws Exception {
173                    DirectoryScanner directoryScanner = new DirectoryScanner();
174    
175                    directoryScanner.setBasedir(dirName);
176                    directoryScanner.setExcludes(
177                            new String[] {
178                                    "**\\_diffs\\**", "**\\.sass-cache*\\**",
179                                    "**\\.sass_cache_*\\**", "**\\_sass_cache_*\\**",
180                                    "**\\_styled\\**", "**\\_unstyled\\**"
181                            });
182                    directoryScanner.setIncludes(new String[] {"**\\*.css"});
183    
184                    directoryScanner.scan();
185    
186                    String[] fileNames = directoryScanner.getIncludedFiles();
187    
188                    for (String fileName : fileNames) {
189                            fileName = StringUtil.replace(
190                                    dirName + StringPool.SLASH + fileName, StringPool.BACK_SLASH,
191                                    StringPool.SLASH);
192    
193                            try {
194                                    long start = System.currentTimeMillis();
195    
196                                    if (_parseSassFile(fileName)) {
197                                            long end = System.currentTimeMillis();
198    
199                                            System.out.println(
200                                                    "Parsed " + fileName + " in " + (end - start) + " ms");
201                                    }
202                            }
203                            catch (Exception e) {
204                                    System.out.println("Unable to parse " + fileName);
205    
206                                    e.printStackTrace();
207                            }
208                    }
209            }
210    
211            private boolean _parseSassFile(String fileName) throws Exception {
212                    File file = new File(fileName);
213                    File cacheFile = getCacheFile(fileName);
214    
215                    if (file.lastModified() == cacheFile.lastModified()) {
216                            return false;
217                    }
218    
219                    Map<String, Object> inputObjects = new HashMap<String, Object>();
220    
221                    inputObjects.put("content", _getContent(file));
222                    inputObjects.put("cssRealPath", fileName);
223                    inputObjects.put("cssThemePath", _getCssThemePath(fileName));
224                    inputObjects.put("sassCachePath", _tempDir);
225    
226                    UnsyncByteArrayOutputStream unsyncByteArrayOutputStream =
227                            new UnsyncByteArrayOutputStream();
228    
229                    UnsyncPrintWriter unsyncPrintWriter = UnsyncPrintWriterPool.borrow(
230                            unsyncByteArrayOutputStream);
231    
232                    inputObjects.put("out", unsyncPrintWriter);
233    
234                    _rubyExecutor.eval(null, inputObjects, null, _rubyScript);
235    
236                    unsyncPrintWriter.flush();
237    
238                    String parsedContent = unsyncByteArrayOutputStream.toString();
239    
240                    FileUtil.write(cacheFile, parsedContent);
241    
242                    cacheFile.setLastModified(file.lastModified());
243    
244                    return true;
245            }
246    
247            private RubyExecutor _rubyExecutor;
248            private String _rubyScript;
249            private String _tempDir;
250    
251    }