/*
* 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.restricted;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* A class for building code for a function call expression in Python. It builds to a PyExpr so it
* could be used function call code recursively.
*
* <p>Sample Output: {@code some_func_call(1, "str", foo='bar', foo=nested_call(42))}
*
*/
public final class PyFunctionExprBuilder {
private static final Function<Map.Entry<String, PyExpr>, String> KEYWORD_ARG_MAPPER =
new Function<Map.Entry<String, PyExpr>, String>() {
@Override
public String apply(Map.Entry<String, PyExpr> entry) {
String key = entry.getKey();
PyExpr value = entry.getValue();
return key + "=" + value.getText();
}
};
private static final Function<PyExpr, String> LIST_ARG_MAPPER =
new Function<PyExpr, String>() {
@Override
public String apply(PyExpr arg) {
return arg.getText();
}
};
private final String funcName;
private final Deque<PyExpr> argList;
private final Map<String, PyExpr> kwargMap;
private String unpackedKwargs = null;
/** @param funcName The name of the function. */
public PyFunctionExprBuilder(String funcName) {
this.funcName = funcName;
this.argList = new ArrayDeque<>();
this.kwargMap = new LinkedHashMap<>();
}
public PyFunctionExprBuilder addArg(PyExpr arg) {
this.argList.add(arg);
return this;
}
public PyFunctionExprBuilder addArg(String str) {
this.argList.add(new PyStringExpr("'" + str + "'"));
return this;
}
public PyFunctionExprBuilder addArg(boolean b) {
this.argList.add(new PyExpr(b ? "True" : "False", Integer.MAX_VALUE));
return this;
}
public PyFunctionExprBuilder addArg(int i) {
this.argList.add(new PyExpr(String.valueOf(i), Integer.MAX_VALUE));
return this;
}
public PyFunctionExprBuilder addArg(double i) {
this.argList.add(new PyExpr(String.valueOf(i), Integer.MAX_VALUE));
return this;
}
public PyFunctionExprBuilder addArg(long i) {
this.argList.add(new PyExpr(String.valueOf(i), Integer.MAX_VALUE));
return this;
}
public String getFuncName() {
return this.funcName;
}
public PyFunctionExprBuilder addKwarg(String key, PyExpr argValue) {
kwargMap.put(key, argValue);
return this;
}
public PyFunctionExprBuilder addKwarg(String key, String str) {
kwargMap.put(key, new PyStringExpr("'" + str + "'"));
return this;
}
public PyFunctionExprBuilder addKwarg(String key, int i) {
kwargMap.put(key, new PyExpr(String.valueOf(i), Integer.MAX_VALUE));
return this;
}
public PyFunctionExprBuilder addKwarg(String key, double i) {
kwargMap.put(key, new PyExpr(String.valueOf(i), Integer.MAX_VALUE));
return this;
}
public PyFunctionExprBuilder addKwarg(String key, long i) {
kwargMap.put(key, new PyExpr(String.valueOf(i), Integer.MAX_VALUE));
return this;
}
/**
* Unpacking keyword arguments will expand a dictionary into a series of keyword arguments.
*
* <p>NOTE: Keyword unpacking behavior is only guaranteed for mapping expressions. Non-mapping
* expressions which attempt to unpack will result in Python runtime errors.
*
* @param mapping The mapping expression to unpack.
* @return This PyFunctionExprBuilder instance.
*/
public PyFunctionExprBuilder setUnpackedKwargs(PyExpr mapping) {
if (unpackedKwargs != null) {
throw new UnsupportedOperationException("Only one kwarg unpacking allowed per expression.");
}
StringBuilder expr = new StringBuilder("**");
if (mapping.getPrecedence() < Integer.MAX_VALUE) {
expr.append("(").append(mapping.getText()).append(")");
} else {
expr.append(mapping.getText());
}
unpackedKwargs = expr.toString();
return this;
}
/** Returns a valid Python function call as a String. */
public String build() {
StringBuilder sb = new StringBuilder(funcName + "(");
Joiner joiner = Joiner.on(", ").skipNulls();
// Join args and kwargs into simple strings.
String args = joiner.join(Iterables.transform(argList, LIST_ARG_MAPPER));
String kwargs = joiner.join(Iterables.transform(kwargMap.entrySet(), KEYWORD_ARG_MAPPER));
// Strip empty strings.
args = Strings.emptyToNull(args);
kwargs = Strings.emptyToNull(kwargs);
// Join all pieces together.
joiner.appendTo(sb, args, kwargs, unpackedKwargs);
sb.append(")");
return sb.toString();
}
/**
* Use when the output function is unknown in Python runtime.
*
* @return A PyExpr represents the function code.
*/
public PyExpr asPyExpr() {
return new PyExpr(build(), Integer.MAX_VALUE);
}
/**
* Use when the output function is known to be a String in Python runtime.
*
* @return A PyStringExpr represents the function code.
*/
public PyStringExpr asPyStringExpr() {
return new PyStringExpr(build());
}
}