/*
* Copyright 2008 Google 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.google.template.soy;
import com.google.common.base.Optional;
import com.google.inject.Module;
import com.google.template.soy.base.SoySyntaxException;
import com.google.template.soy.jssrc.SoyJsSrcOptions;
import com.google.template.soy.xliffmsgplugin.XliffMsgPluginModule;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.kohsuke.args4j.Option;
/**
* Executable for compiling a set of Soy files into corresponding JS source files.
*
*/
public final class SoyToJsSrcCompiler extends AbstractSoyCompiler {
@Option(
name = "--outputPathFormat",
required = true,
usage =
"[Required] A format string that specifies how to build the path to each"
+ " output file. If not generating localized JS, then there will be one output"
+ " JS file (UTF-8) for each input Soy file. If generating localized JS, then"
+ " there will be one output JS file for each combination of input Soy file and"
+ " locale. The format string can include literal characters as well as the"
+ " placeholders {INPUT_PREFIX}, {INPUT_DIRECTORY}, {INPUT_FILE_NAME},"
+ " {INPUT_FILE_NAME_NO_EXT}, {LOCALE}, {LOCALE_LOWER_CASE}. Note"
+ " {LOCALE_LOWER_CASE} also turns dash into underscore, e.g. pt-BR becomes"
+ " pt_br."
)
protected String outputPathFormat;
@Option(
name = "--allowExternalCalls",
usage =
"Whether to allow external calls. New projects should set this to false, and"
+ " existing projects should remove existing external calls and then set this"
+ " to false. It will save you a lot of headaches. Currently defaults to true"
+ " for backward compatibility."
)
private boolean allowExternalCalls = true;
@Option(
name = "--syntaxVersion",
usage = "User-declared syntax version for the Soy file bundle (e.g. 2.0, 2.3)."
)
private String syntaxVersion = "";
@Option(
name = "--shouldGenerateJsdoc",
usage =
"Whether we should generate JSDoc with type info for the Closure Compiler."
+ " Note the generated JSDoc does not have description text, only types for the"
+ " benefit of the Closure Compiler."
)
private boolean shouldGenerateJsdoc = false;
@Option(
name = "--shouldProvideRequireSoyNamespaces",
usage =
"When this option is used, each generated JS file will contain (a) one single"
+ " goog.provide statement for the corresponding Soy file's namespace and"
+ " (b) goog.require statements for the namespaces of the called templates."
)
private boolean shouldProvideRequireSoyNamespaces = false;
@Option(
name = "--shouldDeclareTopLevelNamespaces",
usage =
"[Only applicable when generating regular JS code to define namespaces (i.e."
+ " not generating goog.provide/goog.require).] When this option is set to"
+ " false, each generated JS file will not attempt to declare the top-level"
+ " name in its namespace, instead assuming the top-level name is already"
+ " declared in the global scope. E.g. for namespace aaa.bbb, the code will not"
+ " attempt to declare aaa, but will still define aaa.bbb if it's not already"
+ " defined."
)
private boolean shouldDeclareTopLevelNamespaces = true;
@Option(
name = "--locales",
usage =
"[Required for generating localized JS] Comma-delimited list of locales for"
+ " which to generate localized JS. There will be one output JS file for each"
+ " combination of input Soy file and locale.",
handler = MainClassUtils.StringListOptionHandler.class
)
private List<String> locales = new ArrayList<>();
@Option(
name = "--messageFilePathFormat",
usage =
"[Required for generating localized JS] A format string that specifies how to"
+ " build the path to each translated messages file. The format string can"
+ " include literal characters as well as the placeholders {INPUT_PREFIX},"
+ " {LOCALE}, and {LOCALE_LOWER_CASE}. Note {LOCALE_LOWER_CASE} also turns dash"
+ " into underscore, e.g. pt-BR becomes pt_br. The format string must end with"
+ " an extension matching the message file format (case-insensitive)."
)
private String messageFilePathFormat = "";
@Option(
name = "--shouldGenerateGoogMsgDefs",
usage =
"When this option is used, all 'msg' blocks will be turned into goog.getMsg"
+ " definitions and corresponding usages. Must be used with either"
+ " --bidiGlobalDir, or --useGoogIsRtlForBidiGlobalDir, usually the latter."
+ " Also see --googMsgsAreExternal."
)
private boolean shouldGenerateGoogMsgDefs = false;
@Option(
name = "--googMsgsAreExternal",
usage =
"[Only applicable if --shouldGenerateGoogMsgDefs is true]"
+ " If this option is true, then we generate"
+ " \"var MSG_EXTERNAL_<soyGeneratedMsgId> = goog.getMsg(...);\"."
+ " If this option is false, then we generate"
+ " \"var MSG_UNNAMED_<uniquefier> = goog.getMsg(...);\"."
+ " [Explanation of true value]"
+ " Set this option to true if your project is having Closure Templates do"
+ " message extraction (e.g. with SoyMsgExtractor) and then having the Closure"
+ " Compiler do translated message insertion."
+ " [Explanation of false value]"
+ " Set this option to false if your project is having the Closure Compiler do"
+ " all of its localization, i.e. if you want the Closure Compiler to do both"
+ " message extraction and translated message insertion. A significant drawback"
+ " to this setup is that, if your templates are used from both JS and Java, you"
+ " will end up with two separate and possibly different sets of translations"
+ " for your messages."
)
private boolean googMsgsAreExternal = false;
@Option(
name = "--bidiGlobalDir",
usage =
"The bidi global directionality (ltr=1, rtl=-1). Only applicable if your Soy"
+ " code uses bidi functions/directives. Also note that this flag is usually not"
+ " necessary if a message file is provided, because by default the bidi global"
+ " directionality is simply inferred from the message file."
)
private int bidiGlobalDir = 0;
@Option(
name = "--useGoogIsRtlForBidiGlobalDir",
usage =
"[Only applicable if both --shouldGenerateGoogMsgDefs and"
+
" --shouldProvideRequireSoyNamespaces"
+
" is true]"
+ " Whether to determine the bidi global direction at template runtime by"
+ " evaluating goog.i18n.bidi.IS_RTL. Do not combine with --bidiGlobalDir."
)
private boolean useGoogIsRtlForBidiGlobalDir = false;
@Option(
name = "--messagePluginModule",
usage =
"Specifies the full class name of a Guice module that binds a SoyMsgPlugin."
+ " If not specified, the default is"
+ " com.google.template.soy.xliffmsgplugin.XliffMsgPluginModule, which binds"
+ " the XliffMsgPlugin."
)
private Module messagePluginModule = new XliffMsgPluginModule();
/**
* Compiles a set of Soy files into corresponding JS source files.
*
* @param args Should contain command-line flags and the list of paths to the Soy files.
* @throws IOException If there are problems reading the input files or writing the output file.
* @throws SoySyntaxException If a syntax error is detected.
*/
public static void main(final String[] args) throws IOException, SoySyntaxException {
new SoyToJsSrcCompiler().runMain(args);
}
@Override
void validateFlags() {
if (outputPathFormat.isEmpty()) {
exitWithError("Must provide the output path format.");
}
}
@Override
Optional<Module> msgPluginModule() {
return Optional.of(messagePluginModule);
}
@Override
void compile(SoyFileSet.Builder sfsBuilder) throws IOException {
if (!syntaxVersion.isEmpty()) {
sfsBuilder.setDeclaredSyntaxVersionName(syntaxVersion);
}
sfsBuilder.setAllowExternalCalls(allowExternalCalls);
SoyFileSet sfs = sfsBuilder.build();
// Create SoyJsSrcOptions.
SoyJsSrcOptions jsSrcOptions = new SoyJsSrcOptions();
jsSrcOptions.setShouldGenerateJsdoc(shouldGenerateJsdoc);
jsSrcOptions.setShouldProvideRequireSoyNamespaces(shouldProvideRequireSoyNamespaces);
jsSrcOptions.setShouldDeclareTopLevelNamespaces(shouldDeclareTopLevelNamespaces);
jsSrcOptions.setShouldGenerateGoogMsgDefs(shouldGenerateGoogMsgDefs);
jsSrcOptions.setGoogMsgsAreExternal(googMsgsAreExternal);
jsSrcOptions.setBidiGlobalDir(bidiGlobalDir);
jsSrcOptions.setUseGoogIsRtlForBidiGlobalDir(useGoogIsRtlForBidiGlobalDir);
// Compile.
boolean generateLocalizedJs = !locales.isEmpty();
if (generateLocalizedJs) {
sfs.compileToJsSrcFiles(
outputPathFormat, inputPrefix, jsSrcOptions, locales, messageFilePathFormat);
} else {
sfs.compileToJsSrcFiles(outputPathFormat, inputPrefix, jsSrcOptions, locales, null);
}
}
}