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

de.larsgrefer.sass.embedded.connection.ConnectionFactory Maven / Gradle / Ivy

package de.larsgrefer.sass.embedded.connection;

import androidx.annotation.RequiresApi;
import com.google.protobuf.ByteString;
import de.larsgrefer.sass.embedded.util.PropertyUtils;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author Lars Grefer
 */
@Slf4j
@UtilityClass
@RequiresApi(1000)
public class ConnectionFactory {

    private static BundledPackageProvider bundledPackageProvider = new BundledPackageProvider();
    private static DownloadingPackageProvider downloadingPackageProvider = new DownloadingPackageProvider();

    public static ProcessConnection bundled() throws IOException {
        return fromPackageProvider(bundledPackageProvider);
    }

    public static ProcessConnection downloaded() throws IOException {
        return fromPackageProvider(downloadingPackageProvider);
    }

    public static ProcessConnection fromPackageProvider(DartSassPackageProvider dartSassPackageProvider) throws IOException {
        File executable;
        try {
            executable = dartSassPackageProvider.getDartSassExecutable();
        } catch (IOException | RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e);
        }

        return ofExecutable(executable);
    }

    /**
     * Create a new {@link ProcessConnection} for the given dart-sass executable.
     *
     * @param executable The sass executable.
     * @return A fresh {@link ProcessConnection}.
     * @throws IOException if the subprocess can not be started.
     */
    public static ProcessConnection ofExecutable(File executable) throws IOException {
        if (executable == null || !executable.isFile()) {
            throw new IllegalArgumentException(executable + " is not a file");
        }

        List cmd = new ArrayList<>();

        if (executable.getName().equals("sass.bat") && System.getProperty("os.name").toLowerCase().contains("win")) {
            File dir = executable.getParentFile();

            File dartExe = new File(dir, "src/dart.exe");
            File sassSnapshot = new File(dir, "src/sass.snapshot");

            if (dartExe.isFile() && dartExe.canExecute() && sassSnapshot.isFile()) {
                cmd.add(dartExe.getAbsolutePath());
                cmd.add(sassSnapshot.getAbsolutePath());
            }
        } else if (executable.length() < 1024) { // small shell-script
            File dir = executable.getParentFile();

            File dart = new File(dir, "src/dart");
            File sassSnapshot = new File(dir, "src/sass.snapshot");

            if (dart.isFile() && dart.canExecute() && sassSnapshot.isFile()) {
                cmd.add(dart.getAbsolutePath());
                cmd.add(sassSnapshot.getAbsolutePath());
            }
        }

        if (cmd.isEmpty()) {
            if (!executable.canExecute()) {
                throw new IllegalArgumentException(executable + " can not be executed");
            }

            cmd.add(executable.getAbsolutePath());
        }

        cmd.add("--embedded");

        return ofExecutable(cmd);
    }

    /**
     * Create a new {@link ProcessConnection} for the given dart-sass command.
     *
     * @param executable The sass commandline (including --embedded).
     * @return A fresh {@link ProcessConnection}.
     * @throws IOException if the subprocess can not be started.
     */
    public static ProcessConnection ofExecutable(List executable) throws IOException {
        String expectedProtocolVersion = getExpectedProtocolVersion();
        String protocolVersion = findProtocolVersion(executable);
        if (!expectedProtocolVersion.equalsIgnoreCase(protocolVersion)) {
            log.warn("This Host uses protocolVersion {} but {} provides {}", expectedProtocolVersion, executable, protocolVersion);
        }

        ProcessBuilder processBuilder = new ProcessBuilder(executable);
        return new ProcessConnection(processBuilder);
    }

    public static String getExpectedProtocolVersion() {
        return PropertyUtils.getEmbeddedProtocolVersion();
    }

    private static final Pattern protocolVersionPattern = Pattern.compile("\"protocolVersion\": \"(.*?)\"");

    @SneakyThrows(InterruptedException.class)
    String findProtocolVersion(List executable) throws IOException {
        List command = new ArrayList<>(executable);
        command.add("--version");

        Process testProcess = new ProcessBuilder(command)
                .redirectOutput(ProcessBuilder.Redirect.PIPE)
                .start();

        String stdOut;
        try (InputStream in = testProcess.getInputStream()) {
            stdOut = ByteString.readFrom(in).toStringUtf8();
        }

        int exitCode = testProcess.waitFor();

        if (exitCode != 0) {
            throw new IllegalStateException(executable + " exited with " + exitCode);
        }

        Matcher matcher = protocolVersionPattern.matcher(stdOut);

        if (matcher.find()) {
            return matcher.group(1);
        } else {
            throw new IllegalStateException("Can't find protocolVersion in " + stdOut);
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy