All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.sass.linker.SassLinker Maven / Gradle / Ivy

/*
 * Vaadin Framework 7
 *
 * Copyright (C) 2000-2024 Vaadin Ltd
 *
 * This program is available under Vaadin Commercial License and Service Terms.
 *
 * See  for the full
 * license.
 */
package com.vaadin.sass.linker;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.w3c.css.sac.CSSException;

import com.google.gwt.core.ext.LinkerContext;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.linker.AbstractLinker;
import com.google.gwt.core.ext.linker.ArtifactSet;
import com.google.gwt.core.ext.linker.EmittedArtifact;
import com.google.gwt.core.ext.linker.LinkerOrder;
import com.google.gwt.core.ext.linker.LinkerOrder.Order;
import com.google.gwt.core.ext.linker.Shardable;
import com.vaadin.sass.internal.ScssStylesheet;

/**
 * Pre-linker that checks for the existence of SASS files in public folders,
 * compiles them to CSS files with the SassCompiler from Vaadin and adds the CSS
 * back into the artifact.
 *
 */
@LinkerOrder(Order.PRE)
@Shardable
public class SassLinker extends AbstractLinker {

    @Override
    public String getDescription() {
        return "Compiling SCSS files in public folders to standard CSS";
    }

    @Override
    public ArtifactSet link(TreeLogger logger, LinkerContext context,
            ArtifactSet artifacts, boolean onePermutation)
            throws UnableToCompleteException {

        if (!onePermutation) {
            // The artifact to return
            ArtifactSet toReturn = new ArtifactSet(artifacts);

            // The temporary scss files provided from the artefacts
            List scssFiles = new ArrayList();

            // The public files are provided as inputstream, but the compiler
            // needs real files, as they can contain references to other
            // files. They will be stored here, with their relative paths intact
            String tempFolderName = new Date().getTime() + File.separator;
            File tempFolder = createTempDir(tempFolderName);

            // Can't search here specifically for public resources, as the type
            // is different during compilation. This means we have to loop
            // through all the artifacts
            for (EmittedArtifact resource : artifacts
                    .find(EmittedArtifact.class)) {

                // Create the temporary files.
                String partialPath = resource.getPartialPath();
                if (partialPath.endsWith(".scss")) {
                    // In my opinion, the SCSS file does not need to be
                    // output to the web content folder, as they can't
                    // be used there
                    toReturn.remove(resource);

                    String fileName = partialPath;
                    File path = tempFolder;

                    int separatorIndex = fileName.lastIndexOf(File.separator);
                    if (-1 != separatorIndex) {
                        fileName = fileName.substring(separatorIndex + 1);

                        String filePath = partialPath.substring(0,
                                separatorIndex);
                        path = createTempDir(tempFolderName + filePath);
                    }

                    File tempfile = new File(path, fileName);
                    try {
                        boolean fileCreated = tempfile.createNewFile();
                        if (fileCreated) {

                            // write the received inputstream to the temp file
                            writeFromInputStream(resource.getContents(logger),
                                    tempfile);

                            // Store the file info for the compilation
                            scssFiles.add(new FileInfo(tempfile, partialPath));
                        } else {
                            logger.log(TreeLogger.WARN,
                                    "Duplicate file " + tempfile.getPath());
                        }
                    } catch (IOException e) {
                        logger.log(TreeLogger.ERROR,
                                "Could not write temporary file " + fileName,
                                e);
                    }
                }
            }

            // Compile the files and store them in the artifact
            logger.log(TreeLogger.INFO,
                    "Processing " + scssFiles.size() + " Sass file(s)");
            for (FileInfo fileInfo : scssFiles) {
                logger.log(TreeLogger.INFO, "   " + fileInfo.originalScssPath
                        + " -> " + fileInfo.getOriginalCssPath());

                try {
                    ScssStylesheet scss = ScssStylesheet
                            .get(fileInfo.getAbsolutePath());
                    if (!fileInfo.isMixin()) {
                        scss.compile();
                        InputStream is = new ByteArrayInputStream(
                                scss.printState().getBytes());

                        toReturn.add(this.emitInputStream(logger, is,
                                fileInfo.getOriginalCssPath()));
                    }

                    fileInfo.getFile().delete();
                } catch (CSSException e) {
                    logger.log(TreeLogger.ERROR, "SCSS compilation failed for "
                            + fileInfo.getOriginalCssPath(), e);
                } catch (IOException e) {
                    logger.log(TreeLogger.ERROR, "Could not write CSS file for "
                            + fileInfo.getOriginalCssPath(), e);
                } catch (Exception e) {
                    logger.log(TreeLogger.ERROR, "SCSS compilation failed for "
                            + fileInfo.getOriginalCssPath(), e);
                }
            }

            return toReturn;
        }

        return artifacts;
    }

    /**
     * Writes the contents of an InputStream out to a file.
     *
     * @param contents
     * @param tempfile
     * @throws IOException
     */
    private void writeFromInputStream(InputStream contents, File tempfile)
            throws IOException {
        // write the inputStream to a FileOutputStream
        OutputStream out = new FileOutputStream(tempfile);

        int read = 0;
        byte[] bytes = new byte[1024];

        while ((read = contents.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }

        contents.close();
        out.flush();
        out.close();
    }

    /**
     * Create folder in temporary space on disk.
     *
     * @param partialPath
     * @return
     */
    private File createTempDir(String partialPath) {
        String baseTempPath = System.getProperty("java.io.tmpdir");

        File tempDir = new File(baseTempPath + File.separator + partialPath);
        if (!tempDir.exists()) {
            tempDir.mkdirs();
        }

        tempDir.deleteOnExit();
        return tempDir;
    }

    /**
     * Temporal storage for file info from Artifact.
     */
    private class FileInfo {
        private String originalScssPath;
        private File file;

        public FileInfo(File file, String originalScssPath) {
            this.file = file;
            this.originalScssPath = originalScssPath;
        }

        public boolean isMixin() {
            return file.getName().startsWith("_");
        }

        public String getAbsolutePath() {
            return file.getAbsolutePath();
        }

        public String getOriginalCssPath() {
            return originalScssPath.substring(0, originalScssPath.length() - 5)
                    + ".css";
        }

        public File getFile() {
            return file;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy