/* MonkeyTalk - a cross-platform functional testing tool
Copyright (C) 2012 Gorilla Logic, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package com.gorillalogic.monkeytalk.api.js.tools;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.gorillalogic.monkeytalk.Command;
import com.gorillalogic.monkeytalk.CommandWorld;
import com.gorillalogic.monkeytalk.parser.MonkeyTalkParser;
import com.gorillalogic.monkeytalk.utils.FileUtils;
/**
*/
public class JSMTGenerator {
private static final Pattern SUBSTITUTION_VAR = Pattern.compile("([\\$|%]\\{\\w+)\\}");
public static final List<String> JAVASCRIPT_RESERVED = Arrays.asList(new String[] { "alert",
"frames", "outerHeight", "all", "frameRate", "outerWidth", "anchor", "function",
"packages", "anchors", "getClass", "pageXOffset", "area", "hasOwnProperty",
"pageYOffset", "Array", "hidden", "parent", "assign", "history", "parseFloat", "blur",
"image", "parseInt", "button", "images", "password", "checkbox", "Infinity", "pkcs11",
"clearInterval", "isFinite", "plugin", "clearTimeout", "isNaN", "prompt",
"clientInformation", "isPrototypeOf", "propertyIsEnum", "close", "java", "prototype",
"closed", "JavaArray", "radio", "confirm", "JavaClass", "reset", "constructor",
"JavaObject", "screenX", "crypto", "JavaPackage", "screenY", "Date", "innerHeight",
"scroll", "decodeURI", "innerWidth", "secure", "decodeURIComponent", "layer", "select",
"defaultStatus", "layers", "self", "document", "length", "setInterval", "element",
"link", "setTimeout", "elements", "location", "status", "embed", "Math", "String",
"embeds", "mimeTypes", "submit", "encodeURI", "name", "taint", "encodeURIComponent",
"NaN", "text", "escape", "navigate", "textarea", "eval", "navigator", "top", "event",
"Number", "toString", "fileUpload", "Object", "undefined", "focus",
"offscreenBuffering", "unescape", "form", "open", "untaint", "forms", "opener",
"valueOf", "frame", "option", "window", "abstract", "else", "instanceof", "super",
"boolean", "enum", "int", "switch", "break", "export", "interface", "synchronized",
"byte", "extends", "let", "this", "case", "false", "long", "throw", "catch", "final",
"native", "throws", "char", "finally", "new", "transient", "class", "float", "null",
"true", "const", "for", "package", "try", "continue", "function", "private", "typeof",
"debugger", "goto", "protected", "var", "default", "if", "public", "void", "delete",
"implements", "return", "volatile", "do", "import", "short", "while", "double", "in",
"static", "with" });
public static String createScript(String projectName, File f) {
List<Command> commands = MonkeyTalkParser.parseFile(f);
return createScript(projectName, f.getName(), commands);
}
public static String createScript(String projectName, String filename, List<Command> commands) {
Template script = new Template("templates/scriptgen/script.template.js");
script.init();
StringBuffer cmd_buf = new StringBuffer();
StringBuffer param_buf = new StringBuffer();
StringBuffer vars_buf = new StringBuffer();
StringBuffer default_buf = new StringBuffer();
String scriptName = FileUtils.removeExt(filename, CommandWorld.SCRIPT_EXT);
for (Command cmd : commands) {
if (cmd.isComment()) {
cmd_buf.append("\t//" + cmd.toString().substring(1) + "\n");
continue;
}
// Var: Generate default arg assignments for script
if ("vars.define".equalsIgnoreCase(cmd.getCommandName())) {
for (Entry<String, String> var : getVars(cmd).entrySet()) {
param_buf.append(param_buf.length() > 0 ? ", " : "");
vars_buf.append(vars_buf.length() > 0 ? " + " : "");
String vname = var.getKey();
param_buf.append(vname);
// x = x != undefined && x != "*" ? x : "default value";
default_buf.append("\t" + vname + " = (" + vname + " != undefined && " + vname
+ " != \"*\" ? " + vname + " : ");
if (var.getValue() == null) {
default_buf.append("\"<" + vname + ">\"");
} else if (var.getValue().startsWith("\"") && var.getValue().endsWith("\"")) {
default_buf.append(var.getValue());
} else {
default_buf.append("\"" + var.getValue() + "\"");
}
default_buf.append(");\n");
// we only need this for debug.vars
vars_buf.append("\"" + vname + "=\" + " + vname + " + \"\\n\"");
}
continue;
} else if (cmd.getCommandName().toLowerCase().startsWith("vars.verify")) {
StringBuilder args = new StringBuilder();
if (cmd.getArgs().size() > 0) {
args.append("\"").append(cmd.getArgs().get(0)).append("\"");
}
if (cmd.getArgs().size() > 1) {
args.append(", ").append(cmd.getArgs().get(1));
}
default_buf.append("\t// NOT SUPPORTED YET: app.vars().verify(").append(args)
.append(");\n");
continue;
} else if ("debug.vars".equalsIgnoreCase(cmd.getCommandName())) {
default_buf.append("\tapp.debug().print(").append(vars_buf).append(");\n");
continue;
} else if ("globals.define".equalsIgnoreCase(cmd.getCommandName())
|| "globals.set".equalsIgnoreCase(cmd.getCommandName())) {
for (Entry<String, String> var : getVars(cmd).entrySet()) {
String key = var.getKey();
String val = var.getValue();
if (val == null) {
val = "";
}
if (val.startsWith("\"") && val.endsWith("\"")) {
val = val.substring(1, val.length() - 1);
} else if (val.startsWith("'") && val.endsWith("'")) {
val = val.substring(1, val.length() - 1);
}
if (val.contains("'")) {
// escape single quotes
val = val.replace("'", "\\\'");
}
cmd_buf.append("\tapp.globals().set('").append(key).append("=\"").append(val)
.append("\"');\n");
cmd_buf.append("\t").append(key).append(" = '").append(val).append("';\n");
}
continue;
}
cmd_buf.append("\t");
List<String> args = cmd.getArgs();
// Get: Generate Assignment statement
if (cmd.getAction().equalsIgnoreCase("get")
|| cmd.getAction().equalsIgnoreCase("execAndReturn")) { // get and ret
if (args.size() > 0) {
cmd_buf.append("var " + args.get(0) + " = ");
}
}
// Generate argList for this command
StringBuffer arg_buf = new StringBuffer();
for (String arg : args) {
String argExpr = getArgExpr(arg);
arg_buf.append(arg_buf.length() > 0 ? ", " : "").append(argExpr);
}
if (cmd.getModifiers().size() > 0) {
StringBuilder mods_buf = new StringBuilder();
for (Entry<String, String> mod : cmd.getModifiers().entrySet()) {
String val = mod.getValue();
mods_buf.append(mods_buf.length() > 0 ? ", " : "");
mods_buf.append(mod.getKey()).append(":\"").append(val).append("\"");
}
arg_buf.append(arg_buf.length() > 0 ? ", " : "").append("{").append(mods_buf)
.append("}");
}
// Suppress "*" monkeyId
String monkeyId = cmd.getMonkeyId().equals("*") ? "" : getArgExpr(cmd.getMonkeyId());
String compName = cmd.getMonkeyId().split(".mt")[0].trim();
String component;
if ("script".equalsIgnoreCase(cmd.getComponentType())
&& cmd.getMonkeyId().trim().length() > 0 && compName.matches("\\w+")) {
// It's a script invocation but the monkeyId contains no substitution vars so we can
// use it as the componentType (ie, app.scriptName() instead of
// app.script("scriptName");
component = Template.lowerCamel(compName) + "()";
} else if ("*".equals(cmd.getComponentType())) {
// Use View componentType for *
component = "view(" + monkeyId + ")";
} else {
// Use the componentType for the scriptName and an expression for monkeyId (ie,
// app.type("monkeyId"))
component = Template.lowerCamel(cmd.getComponentType()) + "(" + monkeyId + ")";
}
String c = "app." + component + "." + Template.lowerCamel(cmd.getAction()) + "("
+ arg_buf.toString() + ");\n";
cmd_buf.append(c);
}
String[] parts = scriptName.split("\\.");
String action;
if (parts.length > 1) {
action = parts[1];
scriptName = parts[0];
} else {
action = "run";
}
script.replace("ACTION", action);
script.replace("LIB_NAME", projectName);
script.replace("SCRIPT_NAME", scriptName);
script.replace("PARAMS", param_buf.toString());
script.replace("DEFAULT_VALS", default_buf.toString());
script.replace("COMMANDS", Template.removeReturn(cmd_buf.toString()));
return script.toString();
};
public static Map<String, String> getVars(Command command) {
LinkedHashMap<String, String> vars = new LinkedHashMap<String, String>();
for (String arg : command.getArgs()) {
if (arg.contains("=")) {
String[] parts = arg.split("=");
String var = parts[0];
if (JAVASCRIPT_RESERVED.contains(var)) {
var = "_" + var;
}
String val = parts[1];
vars.put(var, val);
} else {
if (JAVASCRIPT_RESERVED.contains(arg)) {
arg = "_" + arg;
}
vars.put(arg, null);
}
}
return vars;
}
// Build a concatenation expression from text with embedded substitution args
private static String getArgExpr(String arg) {
StringBuffer expr = new StringBuffer();
String[] literals = SUBSTITUTION_VAR.split(arg);
if (literals.length > 0 && literals[0].length() > 0) {
expr.append(escQuote(literals[0]));
}
Matcher matcher = SUBSTITUTION_VAR.matcher(arg);
int i = 1;
while (matcher.find()) {
if (expr.length() > 0) {
expr.append(" + ");
}
String token = matcher.group(1);
String var = token.substring(2);
if (JAVASCRIPT_RESERVED.contains(var)) {
var = "_" + var;
}
if (token.startsWith("$")) {
expr.append(var);
} else {
try {
var = "arguments[" + (Integer.valueOf(var) - 1) + "]";
} catch (NumberFormatException e) {
// It's a named built-in var
if (var.equals("monkeyId")) {
var = "this.monkeyId";
} else {
// if we don't know it surround with quotes
var = "\"" + var + "\"";
}
}
expr.append(var);
}
if (literals.length > i && literals[i].length() > 0) {
expr.append(" + " + escQuote(literals[i]));
}
i++;
}
return expr.toString();
}
private static String escQuote(String arg) {
return "\"" + arg.replaceAll("\"", "\\\"") + "\"";
}
public static void main(String[] args) {
if (args.length < 2 || args.length > 3) {
System.err.println("Usage: java JSMTGenerator <proj name> <input.mt> [output.js]");
System.exit(1);
}
String projName = args[0];
File input = new File(args[1]);
if (!input.exists()) {
System.err.println("ERROR: input '" + args[1] + "' not found");
System.err.println("ERROR: workingDir='" + new File(".").getAbsolutePath() + "'");
System.exit(1);
}
if (!input.isFile()) {
System.err.println("ERROR: input '" + input.getAbsolutePath() + "' not a file!");
System.exit(1);
}
if (!input.getName().toLowerCase().endsWith(CommandWorld.SCRIPT_EXT)) {
System.err.println("ERROR: input '" + input.getAbsolutePath()
+ "' must have .mt extension");
System.exit(1);
}
File output = null;
if (args.length == 2) {
String outname = FileUtils.removeExt(input.getName(), CommandWorld.SCRIPT_EXT)
+ CommandWorld.JS_EXT;
output = new File(input.getParentFile(), outname);
} else {
output = new File(args[2]);
}
String js = JSMTGenerator.createScript(projName, input);
try {
FileUtils.writeFile(output, js);
} catch (IOException ex) {
ex.printStackTrace();
System.exit(1);
}
}
}