com.github.jknack.handlebars.maven.PrecompilePlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.github.jknack.handlebars.maven.PrecompilePlugin.java

Source

/**
 * Copyright (c) 2012-2013 Edgar Espina
 *
 * This file is part of Handlebars.java.
 *
 * 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.
 */
/**
 * This copy of Woodstox XML processor is licensed under the
 * Apache (Software) License, version 2.0 ("the License").
 * See the License for details about distribution rights, and the
 * specific rights regarding derivate works.
 *
 * You may obtain a copy of the License at:
 *
 * http://www.apache.org/licenses/
 *
 * A copy is also included in the downloadable source code package
 * containing Woodstox, in file "ASL2.0", under the same directory
 * as this file.
 */
package com.github.jknack.handlebars.maven;

import static org.apache.commons.lang3.StringUtils.join;
import static org.apache.commons.lang3.Validate.notNull;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;

import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;

import com.github.jknack.handlebars.Context;
import com.github.jknack.handlebars.Handlebars;
import com.github.jknack.handlebars.Helper;
import com.github.jknack.handlebars.HelperRegistry;
import com.github.jknack.handlebars.Options;
import com.github.jknack.handlebars.TagType;
import com.github.jknack.handlebars.Template;
import com.github.jknack.handlebars.helper.I18nHelper;
import com.github.jknack.handlebars.helper.PrecompileHelper;
import com.github.jknack.handlebars.io.FileTemplateLoader;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.ClosureCodingConvention;
import com.google.javascript.jscomp.CompilationLevel;
import com.google.javascript.jscomp.Compiler;
import com.google.javascript.jscomp.CompilerOptions;
import com.google.javascript.jscomp.DiagnosticGroups;
import com.google.javascript.jscomp.JSError;
import com.google.javascript.jscomp.Result;
import com.google.javascript.jscomp.SourceFile;

/**
 * Compile Handlebars templates to JavaScript using Rhino.
 *
 * @author edgar.espina
 * @since 1.1.0
 */
@Mojo(name = "precompile", defaultPhase = LifecyclePhase.PREPARE_PACKAGE, threadSafe = true)
public class PrecompilePlugin extends HandlebarsPlugin {

    /**
     * A prefix location, default is ${basedir}/src/main/webapp.
     */
    @Parameter(defaultValue = "${basedir}/src/main/webapp")
    private String prefix;

    /**
     * The file extension, default is: .hbs.
     */
    @Parameter(defaultValue = ".hbs")
    private String suffix = ".hbs";

    /**
     * The template files to precompile.
     */
    @Parameter
    private List<String> templates;

    /**
     * The output file.
     */
    @Parameter(defaultValue = "${project.build.directory}/${project.build.finalName}/js/helpers.js")
    private String output;

    /**
     * True, if the handlebars.runtime.js file need to be included in the output. Default is: false.
     */
    @Parameter
    private boolean includeRuntime;

    /**
     * Minimize the output. Default is: false.
     */
    @Parameter
    private boolean minimize;

    /**
     * True, if the output should be in the AMD format. Default is false.
     */
    @Parameter
    private boolean amd;

    /**
     * The encoding char set. Default is: UTF-8.
     */
    @Parameter
    private String encoding = "UTF-8";

    @Override
    protected void doExecute() throws Exception {
        notNull(prefix, "The prefix parameter is required.");
        notNull(output, "The output parameter is required.");

        File basedir = new File(prefix);
        File output = new File(this.output);
        boolean error = true;
        PrintWriter writer = null;
        InputStream runtimeIS = null;

        try {
            String realPrefix = basedir.getPath();

            Handlebars handlebars = new Handlebars(new FileTemplateLoader(basedir, suffix));

            final List<CharSequence> extras = new ArrayList<CharSequence>();

            @SuppressWarnings("unchecked")
            List<String> classpathElements = project.getRuntimeClasspathElements();
            final URL[] classpath = new URL[classpathElements.size()];
            for (int i = 0; i < classpath.length; i++) {
                classpath[i] = new File(classpathElements.get(i)).toURI().toURL();
            }

            i18nJs(handlebars, extras, classpath);

            i18n(handlebars);

            /**
             * Silent any missing helper.
             */
            silentHelpers(handlebars);

            File parent = output.getParentFile();
            if (parent != null && !parent.exists()) {
                parent.mkdirs();
            }

            writer = new PrintWriter(output, encoding);
            if (includeRuntime) {
                runtimeIS = getClass().getResourceAsStream("/handlebars.runtime.js");
                IOUtil.copy(runtimeIS, writer);
            }

            List<File> files;
            if (templates != null && templates.size() > 0) {
                files = new ArrayList<File>();
                for (String templateName : templates) {
                    File file = FileUtils.getFile(basedir + File.separator + templateName + suffix);
                    if (file.exists()) {
                        files.add(file);
                    }
                }
            } else {
                files = FileUtils.getFiles(basedir, "**/*" + suffix, null);
            }
            Collections.sort(files);
            getLog().info("Compiling templates...");
            getLog().debug("Options:");
            getLog().debug("  output: " + output);
            getLog().debug("  prefix: " + realPrefix);
            getLog().debug("  suffix: " + suffix);
            getLog().debug("  minimize: " + minimize);
            getLog().debug("  includeRuntime: " + includeRuntime);

            if (!amd) {
                writer.append("(function () {\n");
            }
            Context nullContext = Context.newContext(null);
            for (File file : files) {
                String templateName = file.getPath().replace(realPrefix, "").replace(suffix, "");
                if (templateName.startsWith(File.separator)) {
                    templateName = templateName.substring(File.separator.length());
                }
                templateName = templateName.replace(File.separator, "/");
                getLog().debug("compiling: " + templateName);

                handlebars.compile(templateName).apply(nullContext);

                Template template = handlebars.compileInline("{{precompile \"" + templateName + "\"}}");
                Map<String, Object> hash = new HashMap<String, Object>();
                hash.put("wrapper", amd ? "amd" : "none");
                Options opts = new Options.Builder(handlebars, PrecompileHelper.NAME, TagType.VAR, nullContext,
                        template).setHash(hash).build();

                writer.append("// Source: ").append(file.getPath()).append("\n");
                writer.append(PrecompileHelper.INSTANCE.apply(templateName, opts)).append("\n\n");
            }
            // extras
            for (CharSequence extra : extras) {
                writer.append(extra).append("\n");
            }
            if (!amd) {
                writer.append("\n})();");
            }
            writer.flush();
            IOUtil.close(writer);
            if (minimize) {
                minimize(output);
            }
            if (files.size() > 0) {
                getLog().info("  templates were saved in: " + output);
                error = false;
            } else {
                getLog().warn("  no templates were found");
            }
        } finally {
            IOUtil.close(runtimeIS);
            IOUtil.close(writer);
            if (error) {
                output.delete();
            }
        }
    }

    /**
     * Silent any missing helper.
     *
     * @param handlebars The handlebars object.
     */
    private void silentHelpers(final Handlebars handlebars) {
        handlebars.registerHelper(HelperRegistry.HELPER_MISSING, new Helper<Object>() {
            @Override
            public CharSequence apply(final Object context, final Options options) throws IOException {
                return null;
            }
        });
    }

    /**
     * Override i18n helper.
     *
     * @param handlebars The handlebars object.
     */
    private void i18n(final Handlebars handlebars) {
        handlebars.registerHelper(I18nHelper.i18n.name(), new Helper<String>() {
            @Override
            public CharSequence apply(final String context, final Options options) throws IOException {
                return null;
            }

            @Override
            public String toString() {
                return I18nHelper.i18n.name() + "-maven-plugin";
            }
        });
    }

    /**
     * Override i18nJs helper.
     *
     * @param handlebars The handlebars object
     * @param extras Append output here.
     * @param classpath The project classpath.
     */
    private void i18nJs(final Handlebars handlebars, final List<CharSequence> extras, final URL[] classpath) {
        handlebars.registerHelper(I18nHelper.i18nJs.name(), new Helper<String>() {
            @Override
            public CharSequence apply(final String context, final Options options) throws IOException {
                StringBuilder output = new StringBuilder();
                output.append("// i18nJs output:\n");
                output.append("// register an empty i18nJs helper:\n");
                output.append(registerHelper(I18nHelper.i18nJs.name(),
                        "I18n.locale = arguments[0] || \"" + Locale.getDefault() + "\";\n" + "return '';",
                        "arguments"));
                output.append("// redirect i18n helper to i18n.js:\n");
                output.append(registerHelper(I18nHelper.i18n.name(),
                        "var key = arguments[0],\n" + "  i18nOpts = {},\n" + "  len = arguments.length - 1,"
                                + "  options = arguments[len];\n" + "for(var i = 1; i < len; i++) {\n"
                                + "  i18nOpts['arg' + (i - 1)] = arguments[i];\n" + "}\n"
                                + "i18nOpts.locale = options.hash.locale;\n" + "return I18n.t(key, i18nOpts);"));
                extras.add(output);
                return null;
            }

            @Override
            public String toString() {
                return I18nHelper.i18nJs.name() + "-maven-plugin";
            }
        });
    }

    /**
     * JavaScript code for registering helpers.
     *
     * @param name The helper name.
     * @param body The helper body/
     * @param args The helper arguments.
     * @return JS code.
     */
    private CharSequence registerHelper(final String name, final String body, final String... args) {
        return String.format("Handlebars.registerHelper('%s', function (%s) {\n%s\n});\n\n", name, join(args, ", "),
                body);
    }

    /**
     * Minimize the output using google closure compiler.
     *
     * @param output The input file to minimize.
     * @throws IOException If something goes wrong.
     * @throws MojoFailureException If something goes wrong.
     */
    private void minimize(final File output) throws IOException, MojoFailureException {
        final CompilerOptions options = new CompilerOptions();
        options.setCodingConvention(new ClosureCodingConvention());
        options.setOutputCharset("UTF-8");
        options.setWarningLevel(DiagnosticGroups.CHECK_VARIABLES, CheckLevel.WARNING);
        CompilationLevel.SIMPLE_OPTIMIZATIONS.setOptionsForCompilationLevel(options);

        Compiler.setLoggingLevel(Level.SEVERE);
        Compiler compiler = new Compiler();
        compiler.disableThreads();
        compiler.initOptions(options);

        Result result = compiler.compile(Collections.<SourceFile>emptyList(),
                Arrays.asList(SourceFile.fromFile(output)), options);
        if (result.success) {
            FileUtils.fileWrite(output, compiler.toSource());
        } else {
            JSError[] errors = result.errors;
            throw new MojoFailureException(errors[0].toString());
        }
    }

    /**
     * @param includeRuntime True, if the handlebars.runtime.js file need to be included in the
     *        output. Default is: false.
     */
    public void setIncludeRuntime(final boolean includeRuntime) {
        this.includeRuntime = includeRuntime;
    }

    /**
     * @param minimize Minimize the output. Default is: false.
     */
    public void setMinimize(final boolean minimize) {
        this.minimize = minimize;
    }

    /**
     * @param output The output file.
     */
    public void setOutput(final String output) {
        this.output = output;
    }

    /**
     * @param amd True, if the output should be in the AMD format. Default is false.
     */
    public void setAmd(final boolean amd) {
        this.amd = amd;
    }

    /**
     * @param prefix A prefix location, default is ${basedir}/src/main/webapp.
     */
    public void setPrefix(final String prefix) {
        this.prefix = prefix;
    }

    /**
     * @param suffix The file extension, default is: .hbs.
     */
    public void setSuffix(final String suffix) {
        this.suffix = suffix;
    }

    /**
     *
     * @param template the template filename
     */
    public void addTemplate(final String template) {
        if (templates == null) {
            this.templates = new ArrayList<String>();
        }
        this.templates.add(template);
    }

}