/* * 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.pysrc.internal; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.google.common.collect.Multimap; import com.google.common.io.Files; import com.google.inject.Key; import com.google.inject.Provider; import com.google.template.soy.base.SoySyntaxException; import com.google.template.soy.error.ErrorReporter; import com.google.template.soy.internal.i18n.BidiGlobalDir; import com.google.template.soy.internal.i18n.SoyBidiUtils; import com.google.template.soy.pysrc.SoyPySrcOptions; import com.google.template.soy.pysrc.internal.PyApiCallScopeBindingAnnotations.PyCurrentManifest; import com.google.template.soy.shared.internal.ApiCallScopeUtils; import com.google.template.soy.shared.internal.GuiceSimpleScope; import com.google.template.soy.shared.internal.MainEntryPointUtils; import com.google.template.soy.shared.restricted.ApiCallScopeBindingAnnotations.ApiCall; import com.google.template.soy.sharedpasses.opti.SimplifyVisitor; import com.google.template.soy.soytree.SoyFileNode; import com.google.template.soy.soytree.SoyFileSetNode; import com.google.template.soy.soytree.TemplateRegistry; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Properties; import javax.inject.Inject; /** * Main entry point for the Python Src backend (output target). * * <p>Important: Do not use outside of Soy code (treat as superpackage-private). * */ public final class PySrcMain { /** The scope object that manages the API call scope. */ private final GuiceSimpleScope apiCallScope; /** The instanceof of SimplifyVisitor to use. */ private final SimplifyVisitor simplifyVisitor; /** Provider for getting an instance of GenPyCodeVisitor. */ private final Provider<GenPyCodeVisitor> genPyCodeVisitorProvider; /** * @param apiCallScope The scope object that manages the API call scope. * @param simplifyVisitor The instance of SimplifyVisitor to use. * @param genPyCodeVisitorProvider Provider for getting an instance of GenPyCodeVisitor. */ @Inject public PySrcMain( @ApiCall GuiceSimpleScope apiCallScope, SimplifyVisitor simplifyVisitor, Provider<GenPyCodeVisitor> genPyCodeVisitorProvider) { this.apiCallScope = apiCallScope; this.simplifyVisitor = simplifyVisitor; this.genPyCodeVisitorProvider = genPyCodeVisitorProvider; } /** * Generates Python source code given a Soy parse tree and an options object. * * @param soyTree The Soy parse tree to generate Python source code for. * @param pySrcOptions The compilation options relevant to this backend. * @param currentManifest The namespace manifest for current sources. * @return A list of strings where each string represents the Python source code that belongs in * one Python file. The generated Python files correspond one-to-one to the original Soy * source files. * @throws SoySyntaxException If a syntax error is found. */ public List<String> genPySrc( SoyFileSetNode soyTree, TemplateRegistry templateRegistry, SoyPySrcOptions pySrcOptions, ImmutableMap<String, String> currentManifest, ErrorReporter errorReporter) throws SoySyntaxException { try (GuiceSimpleScope.InScope inScope = apiCallScope.enter()) { // Seed the scoped parameters. inScope.seed(SoyPySrcOptions.class, pySrcOptions); inScope.seed( new Key<ImmutableMap<String, String>>(PyCurrentManifest.class) {}, currentManifest); BidiGlobalDir bidiGlobalDir = SoyBidiUtils.decodeBidiGlobalDirFromPyOptions(pySrcOptions.getBidiIsRtlFn()); ApiCallScopeUtils.seedSharedParams(inScope, null, bidiGlobalDir); simplifyVisitor.simplify(soyTree, templateRegistry); return genPyCodeVisitorProvider.get().gen(soyTree, errorReporter); } } /** * Generates Python source files given a Soy parse tree, an options object, and information on * where to put the output files. * * @param soyTree The Soy parse tree to generate Python source code for. * @param pySrcOptions The compilation options relevant to this backend. * @param outputPathFormat The format string defining how to build the output file path * corresponding to an input file path. * @param inputPathsPrefix The input path prefix, or empty string if none. * @throws SoySyntaxException If a syntax error is found. * @throws IOException If there is an error in opening/writing an output Python file. */ public void genPyFiles( SoyFileSetNode soyTree, TemplateRegistry templateRegistry, SoyPySrcOptions pySrcOptions, String outputPathFormat, String inputPathsPrefix, ErrorReporter errorReporter) throws SoySyntaxException, IOException { ImmutableList<SoyFileNode> srcsToCompile = ImmutableList.copyOf( Iterables.filter(soyTree.getChildren(), SoyFileNode.MATCH_SRC_FILENODE)); // Determine the output paths. List<String> soyNamespaces = getSoyNamespaces(soyTree); Multimap<String, Integer> outputs = MainEntryPointUtils.mapOutputsToSrcs( null, outputPathFormat, inputPathsPrefix, srcsToCompile); // Generate the manifest and add it to the current manifest. ImmutableMap<String, String> manifest = generateManifest(soyNamespaces, outputs); // Generate the Python source. List<String> pyFileContents = genPySrc(soyTree, templateRegistry, pySrcOptions, manifest, errorReporter); if (srcsToCompile.size() != pyFileContents.size()) { throw new AssertionError( String.format( "Expected to generate %d code chunk(s), got %d", srcsToCompile.size(), pyFileContents.size())); } // Write out the Python outputs. for (String outputFilePath : outputs.keySet()) { try (Writer out = Files.newWriter(new File(outputFilePath), StandardCharsets.UTF_8)) { for (int inputFileIndex : outputs.get(outputFilePath)) { out.write(pyFileContents.get(inputFileIndex)); } } } // Write out the manifest file. if (pySrcOptions.doesOutputNamespaceManifest()) { String manifestFormat = outputPathFormat.replace(".py", ".MF"); String manifestPath = MainEntryPointUtils.buildFilePath(manifestFormat, null, "manifest", ""); try (Writer out = Files.newWriter(new File(manifestPath), StandardCharsets.UTF_8)) { Properties prop = new Properties(); for (String namespace : manifest.keySet()) { prop.put(namespace, manifest.get(namespace)); } prop.store(out, null); } } } /** * Generate the manifest file by finding the output file paths and converting them into a Python * import format. */ private static ImmutableMap<String, String> generateManifest( List<String> soyNamespaces, Multimap<String, Integer> outputs) { ImmutableMap.Builder<String, String> manifest = new ImmutableMap.Builder<>(); for (String outputFilePath : outputs.keySet()) { for (int inputFileIndex : outputs.get(outputFilePath)) { String pythonPath = outputFilePath.replace(".py", "").replace('/', '.'); manifest.put(soyNamespaces.get(inputFileIndex), pythonPath); } } return manifest.build(); } private List<String> getSoyNamespaces(SoyFileSetNode soyTree) { List<String> namespaces = new ArrayList<>(); for (SoyFileNode soyFile : soyTree.getChildren()) { namespaces.add(soyFile.getNamespace()); } return namespaces; } }