/* * Copyright 2015 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.collect.ImmutableMap; import com.google.common.io.Files; import com.google.template.soy.base.SoySyntaxException; import com.google.template.soy.basetree.SyntaxVersion; import com.google.template.soy.pysrc.SoyPySrcOptions; import java.io.File; import java.io.IOException; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.kohsuke.args4j.Option; /** * Executable for compiling a set of Soy files into corresponding Python source files. * * <p>Note: The Python output and runtime libraries are targeted at Python v2.7. Support for Python * v3.1+ is also intended through the use of __future__ and version agnostic syntax, HOWEVER at the * moment testing support is only guaranteed for v2.7. * */ public final class SoyToPySrcCompiler extends AbstractSoyCompiler { @Option( name = "--outputPathFormat", required = true, usage = "[Required] A format string that specifies how to build the path to each" + " output file. There will be one output Python file (UTF-8) for each input Soy" + " file. The format string can include literal characters as well as the" + " placeholders {INPUT_PREFIX}, {INPUT_DIRECTORY}, {INPUT_FILE_NAME}, and" + " {INPUT_FILE_NAME_NO_EXT}. Additionally periods are not allowed in the" + " outputted filename outside of the final py extension." ) private String outputPathFormat = ""; @Option( name = "--runtimePath", required = true, usage = "[Required] The module path used to find the python runtime libraries. This" + " should be in dot notation format." ) private String runtimePath = ""; @Option( name = "--environmentModulePath", usage = "A custom python module which will override the environment.py module if custom" + " functionality is required for interacting with your runtime environment. This" + " module must implement all functions of the environment module if provided." ) private String environmentModulePath = ""; @Option( name = "--translationClass", usage = "The full class name of the python runtime translation class." + " The name should include the absolute module path and class name in dot notation" + " format (e.g. \"my.package.module.TranslatorClass\")." + " It is required for {msg} command." ) private String translationClass = ""; @Option( name = "--bidiIsRtlFn", usage = "The full name of a function used to determine if bidi is rtl for setting global" + " directionality. The name should include the absolute module path and function" + "name in dot notation format (e.g. \"my.app.bidi.is_rtl\"). Only applicable if" + " your Soy code uses bidi functions/directives." ) private String bidiIsRtlFn = ""; @Option( name = "--syntaxVersion", usage = "User-declared syntax version for the Soy file bundle (e.g. 2.2, 2.3)." ) private String syntaxVersion = ""; @Option( name = "--namespaceManifestPath", usage = "A list of paths to a manifest file which provides a map of soy namespaces to" + " their Python paths. If this is provided, direct imports will be used," + " drastically improving runtime performance.", handler = MainClassUtils.StringListOptionHandler.class ) private List<String> namespaceManifestPaths = new ArrayList<>(); @Option( name = "--outputNamespaceManifest", usage = "Output a manifest file containing a map of all soy namespaces to their Python" + " paths.", handler = MainClassUtils.BooleanOptionHandler.class ) private boolean outputNamespaceManifest = false; /** * Compiles a set of Soy files into corresponding Python 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 SoyToPySrcCompiler().runMain(args); } @Override void validateFlags() { if (runtimePath.length() == 0) { exitWithError("Must provide the Python runtime library path."); } if (outputPathFormat.isEmpty()) { exitWithError("Must provide the output path format."); } } @Override void compile(SoyFileSet.Builder sfsBuilder) throws IOException { if (syntaxVersion.length() > 0) { SyntaxVersion parsedVersion = SyntaxVersion.forName(syntaxVersion); if (parsedVersion.num < SyntaxVersion.V2_0.num) { exitWithError("Declared syntax version must be 2.0 or greater."); } sfsBuilder.setDeclaredSyntaxVersionName(syntaxVersion); } // Disallow external call entirely in Python. sfsBuilder.setAllowExternalCalls(false); // Require strict templates in Python. sfsBuilder.setStrictAutoescapingRequired(true); SoyFileSet sfs = sfsBuilder.build(); // Load the manifest if available. ImmutableMap<String, String> manifest = loadNamespaceManifest(namespaceManifestPaths); if (!manifest.isEmpty() && !outputNamespaceManifest) { exitWithError("Namespace manifests provided without outputting a new manifest."); } // Create SoyPySrcOptions. SoyPySrcOptions pySrcOptions = new SoyPySrcOptions( runtimePath, environmentModulePath, bidiIsRtlFn, translationClass, manifest, outputNamespaceManifest); // Compile. sfs.compileToPySrcFiles(outputPathFormat, inputPrefix, pySrcOptions); } /** * Load the manifest files provided at namespaceManifestPaths, deserialize (via gson), and combine * into a map containing all soy namespaces to their Python paths. */ private ImmutableMap<String, String> loadNamespaceManifest(List<String> namespaceManifestPaths) { if (namespaceManifestPaths.isEmpty()) { return ImmutableMap.of(); } ImmutableMap.Builder<String, String> manifest = new ImmutableMap.Builder<>(); for (String manifestPath : namespaceManifestPaths) { try (Reader manifestFile = Files.newReader(new File(manifestPath), StandardCharsets.UTF_8)) { Properties prop = new Properties(); prop.load(manifestFile); for (String namespace : prop.stringPropertyNames()) { manifest.put(namespace, prop.getProperty(namespace)); } } catch (IOException e) { exitWithError("Unable to read the namespaceManifest file at " + manifestPath); } } return manifest.build(); } }