com.squarespace.less.cli.LessC.java Source code

Java tutorial

Introduction

Here is the source code for com.squarespace.less.cli.LessC.java

Source

/**
 * Copyright (c) 2014 SQUARESPACE, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.squarespace.less.cli;

import static com.squarespace.less.LessCompiler.LESSJS_VERSION;

import java.io.InputStream;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import net.sourceforge.argparse4j.ArgumentParsers;
import net.sourceforge.argparse4j.impl.Arguments;
import net.sourceforge.argparse4j.inf.ArgumentParser;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace;

import org.apache.commons.lang3.StringUtils;

import com.squarespace.less.LessBuildProperties;
import com.squarespace.less.LessOptions;

/**
 * Main command line interface for the compiler.
 *
 * TODO: move to separate gradle subproject
 */
public class LessC {

    private static final boolean IS_WINDOWS = System.getProperty("os.name").toLowerCase().startsWith("window");

    private static final String LESS_REPOSITORY = "http://github.com/squarespace/less-compiler";

    private static final String IMPLEMENTATION = "[Java, Squarespace]";

    private static final String PROGRAM_NAME = "lessc";

    private final PrintStream err;

    /**
     * Constructs a command-line compiler which writes output to the
     * given out and err streams.
     */
    public LessC(PrintStream err) {
        this.err = err;
    }

    /**
     * Main entry point for the command-line compiler.
     */
    public static void main(String[] rawArgs) {
        System.exit(process(rawArgs, System.out, System.err, System.in));
    }

    /**
     * Effectively this is the main() method, but separated so it can be
     * unit tested and all output captured.
     */
    public static int process(String[] rawArgs, PrintStream out, PrintStream err, InputStream in) {
        LessC cmd = new LessC(err);

        // Bit of a catch-22 here at the moment, since we need to parse the arguments
        // and report errors before knowing which implementation to invoke.
        Args args = cmd.parseArguments(rawArgs);
        if (args == null) {
            System.exit(BaseCompile.ERR);
        }

        // Select the implementation based on the parsed arguments.
        BaseCompile impl = null;
        if (args.batchMode()) {
            impl = new CompileBatch(args, out, err);
        } else {
            impl = new CompileSingle(args, out, err, in);
        }
        return impl.process();
    }

    /**
     * Constructs the parser for the command line arguments and parses
     * the arguments.  Also prints the compiler version string.
     */
    public Args parseArguments(String[] args) {
        String version = buildVersion();

        ArgumentParser parser = ArgumentParsers.newArgumentParser(PROGRAM_NAME)
                .description("Compile .less files into .css").version(version)
                .setDefault("recursion_limit", LessOptions.DEFAULT_RECURSION_LIMIT)
                .setDefault("indent", LessOptions.DEFAULT_INDENT);

        parser.addArgument("--batch", "-b").action(Arguments.storeTrue()).help("Batch mode");

        parser.addArgument("--debug").type(LessDebugMode.class).choices(LessDebugMode.values())
                .help("Enables debug mode.");

        parser.addArgument("--indent", "-i").metavar("SPACES").type(Integer.class)
                .help("Number of spaces of indent.");

        parser.addArgument("--import-once").action(Arguments.storeTrue())
                .help("When enabled, stylesheets will only be imported once.");

        parser.addArgument("--include-paths", "-I").metavar("PATH").type(String.class)
                .help("Set include paths. Separated by ':'. Use ';' on Windows");

        parser.addArgument("--lint", "-l").action(Arguments.storeTrue()).help("Syntax check only (lint).");

        parser.addArgument("--mixin-recursion-limit", "-r").metavar("LIMIT").type(Integer.class)
                .setDefault(LessOptions.DEFAULT_RECURSION_LIMIT).help("Sets the recursion depth limit.");

        parser.addArgument("--import-recursion-limit", "-R").metavar("LIMIT").type(Integer.class)
                .setDefault(LessOptions.DEFAULT_RECURSION_LIMIT).help("Sets the import recursion depth limit.");

        parser.addArgument("--statistics", "-s").action(Arguments.storeTrue()).help("Output compile statistics");

        parser.addArgument("--strict").action(Arguments.storeTrue())
                .help("Enables strict mode. Throws errors instead of warnings for some invalid rules.");

        parser.addArgument("--tracing", "-t").action(Arguments.storeTrue()).help("Enables tracing for execution.");

        parser.addArgument("--version", "-v").action(Arguments.version()).help("Show the version and exit");

        parser.addArgument("--verbose", "-V").action(Arguments.storeTrue()).help("Enables verbose mode");

        parser.addArgument("--wait", "-w").action(Arguments.storeTrue())
                .help("Waits for user input before executing. For profiling purposes.");

        parser.addArgument("--compress", "-x").action(Arguments.storeTrue())
                .help("Enables compressing whitespace (minification)");

        parser.addArgument("input").type(String.class).help("Input file, or a directory in batch mode.");

        parser.addArgument("output").type(String.class).nargs("?")
                .help("Output file, or a directory in batch mode.");

        try {
            Namespace res = parser.parseArgs(args);

            // Options used by the compiler.
            LessOptions opts = new LessOptions();
            opts.compress(res.getBoolean("compress"));
            opts.importOnce(res.getBoolean("import_once"));
            opts.importPaths(parseImportPaths(res));
            opts.indent(res.getInt("indent"));
            opts.mixinRecursionLimit(res.getInt("mixin_recursion_limit"));
            opts.importRecursionLimit(res.getInt("import_recursion_limit"));
            opts.strict(res.getBoolean("strict"));
            opts.tracing(res.getBoolean("tracing"));
            opts.hideWarnings(false);

            // Options used by the command.
            Args cmdArgs = new Args();
            cmdArgs.programName = PROGRAM_NAME;
            cmdArgs.input = res.getString("input");
            cmdArgs.output = res.getString("output");
            cmdArgs.batchMode = res.getBoolean("batch");
            cmdArgs.compilerOptions = opts;
            cmdArgs.debugMode = res.<LessDebugMode>get("debug");
            cmdArgs.lintOnly = res.getBoolean("lint");
            cmdArgs.statistics = res.getBoolean("statistics");
            cmdArgs.verbose = res.getBoolean("verbose");
            cmdArgs.waitForUser = res.getBoolean("wait");

            if (cmdArgs.verbose() && cmdArgs.debugMode() != null) {
                dumpArguments(res);
            }

            return cmdArgs;

        } catch (ArgumentParserException e) {
            parser.handleError(e);
            return null;
        }
    }

    private List<String> parseImportPaths(Namespace res) {
        String paths = res.getString("include_paths");
        if (paths == null) {
            return null;
        }
        char sep = IS_WINDOWS ? ';' : ':';
        String[] parts = StringUtils.split(paths, sep);
        return Arrays.asList(parts);
    }

    private void dumpArguments(Namespace ns) {
        err.println("Command line arguments:");
        Map<String, Object> attrs = ns.getAttrs();
        for (String key : attrs.keySet()) {
            err.printf(" %16s: %s\n", key, attrs.get(key));
        }
    }

    /**
     * Build the version string.
     */
    private String buildVersion() {
        StringBuilder buf = new StringBuilder();
        buf.append("${prog} version ").append(LessBuildProperties.version()).append(' ').append(IMPLEMENTATION)
                .append('\n');
        buf.append("      repository: ").append(LESS_REPOSITORY).append('\n');
        buf.append("   compatibility: ").append(LESSJS_VERSION).append('\n');
        buf.append("      build date: ").append(LessBuildProperties.date()).append('\n');
        buf.append("    build commit: ").append(LessBuildProperties.commit()).append('\n');
        return buf.toString();
    }

    /**
     * Command line and compiler arguments.
     */
    public static class Args {

        private String programName;

        private String input;

        private String output;

        private boolean batchMode;

        private LessOptions compilerOptions;

        private LessDebugMode debugMode;

        private boolean lintOnly;

        private boolean statistics;

        private boolean verbose;

        private boolean waitForUser;

        private Args() {
        }

        public String programName() {
            return programName;
        }

        public String input() {
            return input;
        }

        public String output() {
            return output;
        }

        public boolean batchMode() {
            return batchMode;
        }

        public LessOptions compilerOptions() {
            return compilerOptions;
        }

        public LessDebugMode debugMode() {
            return debugMode;
        }

        public boolean lintOnly() {
            return lintOnly;
        }

        public boolean statsEnabled() {
            return statistics;
        }

        public boolean verbose() {
            return verbose;
        }

        public boolean waitForUser() {
            return waitForUser;
        }

    }

}