<commit_before>"use strict";

var path = require("path");
var Tokenizer = require("./tokenizer.js");
var TokenType = Tokenizer.TokenType;

/**

Compiled variable names:

$   - template data
__e - exec_utils
__w - current write function
__s - virtual sections
__c - cutouts
__o - output callback
__p - parent write function
__a - reference to __s[sectionName] (for inheritance)
__i - index for parent virtual section in __a[__i]

*/

/** Converts a filename to a function name. */
function getTemplateFunctionName(filename) { // @todo not really used anymore, remove?
    return "__cutter_" + path.resolve(filename).replace(/\\|\/|:/g, "_").replace(/\./g, "_dot_");
}

/** Compiles the template and returns JavaScript source code. */
function compile(template, filename) {
    var tokenizer = new Tokenizer(template);
    var globalIdentifiers = {}; // set of all global identifiers
    var tokens = [];

    var token;
    try {
        while (token = tokenizer.nextToken()) {
            var identifiers = token.identifiers();
            if (identifiers) {
                for (var i = 0; i < identifiers.length; i++) {
                    globalIdentifiers[identifiers[i]] = true;
                }
            }

            // a control token can mean a lot of things (write, if, loop etc.)
            token.transform();
            tokens.push(token);
        }
        tokens.push(null); // push EOF token
    }
    catch (e) {
        if (e instanceof Tokenizer.TokenizerException) {
            throw new CompilerException(e.message, filename, e.line);
        }
        else throw e;
    }

    // compiler structure
    var comp = new Compiler({
        filename: filename,
        output: "",
        deps: [],
        tokens: tokens,
        count: tokens.length,
        offset: 0,
        identifiers: Object.keys(globalIdentifiers),
        blocks: 0,
        leadingSpaces: "",
        firstOnLine: true,
        skip: 0,
        currentLine: 0
    });

    comp.compileTemplate();

    return comp.output;
}

function CompilerException(message, filename, line) {
    this.message = message;
    this.filename = filename;
    this.line = line;
}

/** Creates a compiler object with specified status. */
function Compiler(status) {
    for (var key in status) {
        this[key] = status[key];
    }
}

function addSlashes(string) {
    return string
        .replace(/\\/g, '\\\\').
        replace(/\u0008/g, '\\b').
        replace(/\t/g, '\\t').
        replace(/\n/g, '\\n').
        replace(/\f/g, '\\f').
        replace(/\r/g, '\\r').
        replace(/'/g, '\\\'').
        replace(/"/g, '\\"');
}

/** Adds write() function initialization. */
Compiler.prototype.initWrite = function(name) {
    if (!name) name = "null";
    this.output += "var __w = __e.getWrite(" + name + ", __c, __o, __p);\n\n";
}

/** Adds local variables. */
Compiler.prototype.addVariables = function() {
    // @todo add only variables used in section
    for (var i = 0; i < this.identifiers.length; i++) {
        this.output += "var " + this.identifiers[i] + " = $[\"" + this.identifiers[i].substr(1) + "\"];\n";
    }
    this.output += "\n";
}

Compiler.prototype.compileTemplate = function() {
    this.compileHeader();

    this.output += "function run($, __s, __c, __o, __p) {\n";
    this.initWrite();
    this.addVariables();
    this.compileTokens();
    this.output += "}\n\n";

    this.output += "module.exports.run = run;\n";
    this.output += "module.exports.deps = [";
    for (var i = 0; i < this.deps.length; i++) {
        this.output += "\"" + addSlashes(this.deps[i]) + "\"";
        if (i != this.deps.length - 1) this.output += ", ";
    }
    this.output += "];\n";
    this.output += "module.exports.compiledDeps = __compiledDeps;\n";
}

Compiler.prototype.compileHeader = function() {
    this.output = "\"use strict\";\n\n";
    var execUtilsPath = path.relative(path.dirname(this.filename), path.join(__dirname, "exec_utils.js")).replace(/\\/g, "/");
    this.output += "var __e = require(\"./" + execUtilsPath + "\");\n";
    this.output += "var __compiledDeps = {};\n\n";
}

Compiler.prototype.addStaticText = function(text) {
    if (text !== "") this.output += "__w(\"" + addSlashes(text) + "\");\n";
}

/** Finds the index where the last empty line (leading whitespaces) begins. */
function lastEmptyLineIndex(str) {
    for (var i = str.length - 1; i >= 0; i--) {
        var c = str.charAt(i);
        if (c !== " " && c !== "\t") {
            if (c === "\r") return i + 1;
            if (c === "\n") {
                if (i > 0 && str.charAt(i - 1) === "\r") return i;
                else return i + 1;
            }
            //else return str.length;
            else return -1;
        }
    }
    return 0;
}

/**
 * If the next token is a static string and starts with a newline, the newline will be skipped.
 * Returns how many characters should be skipped.
 */
Compiler.prototype.skipWhitespaces = function() {
    if (this.tokens[this.offset] && this.tokens[this.offset].type == TokenType.STATIC) {
        var skip = 0;
        var content = this.tokens[this.offset].content;
        for (var i = 0; i < content.length; i++) {
            var c = content.charAt(i);
            if (c === " " || c === "\t") {
                skip++;
            }
            else if (c === "\n") {
                this.skip = skip + 1; break;
            }
            else if (c === "\r") {
                if (i < content.length - 1 && content.charAt(i + 1) === "\n") {
                    this.skip = skip + 2; break;
                }
                else {
                    this.skip = skip + 1; break;
                }
            }
            else {
                this.skip = 0; break;
            }
        }
    }
    else this.skip = 0;
    return this.skip;
}

/**
 * Adds leading whitespaces to the output. This function allows last-line leading whitespaces
 * to be moved inside blocks, loops etc., so the output code has a nice indentation.
 */
Compiler.prototype.addLeadingSpaces = function() {
    if (this.leadingSpaces !== "") {
        this.addStaticText(this.leadingSpaces);
        this.leadingSpaces = "";
        this.firstOnLine = false;
    }
}

/*
 * Adds trailing whitespaces to the output. This function allows to move newlines inside
 * blocks.
 */
Compiler.prototype.addTrailingSpaces = function() {
    if (this.skip !== 0) {
        var spaces = this.tokens[this.offset].content.substr(0, this.skip);
        this.addStaticText(spaces);
    }
}

/** Some magic with whitespaces and newlines. */
Compiler.prototype.handleWhitespaces = function() {
    if (this.skipWhitespaces() === 0) {
        this.addLeadingSpaces();
    }
    else {
        if (this.firstOnLine === false) this.addTrailingSpaces();

        this.leadingSpaces = "";
        this.firstOnLine = false;
    }
}

/** Recursively compiles tokens to JavaScript code. */
Compiler.prototype.compileTokens = function() {

    while (this.offset < this.count) {
        var token = this.tokens[this.offset++];
        if (token != null) this.currentLine = token.line;

        if (token == null) { // EOF
            if (this.blocks !== 0) throw new CompilerException("Unexpected EOF", this.filename, this.currentLine);

            this.addLeadingSpaces();
            return;
        }
        else if (token.type == TokenType.STATIC) {
            this.addLeadingSpaces();

            // find last line with whitespaces only
            var content = token.content.substr(this.skip);

            var index = lastEmptyLineIndex(content);
            if (index !== -1) {
                this.firstOnLine = true;
            }
            else {
                this.firstOnLine = false; index = content.length;
            }

            this.addStaticText(content.substr(0, index));

            this.leadingSpaces = content.substr(index);
            this.skip = 0;
        }
        else if (token.type == TokenType.COMMENT) {
            this.handleWhitespaces();
        }
        else if (token.type == TokenType.END) {
            if (this.blocks === 0) throw new CompilerException("Unexpected block end", this.filename, this.currentLine);
            if (token.content !== "") throw new CompilerException("Unexpected '" + token.content + "' after '/'", this.filename, this.currentLine);
            this.handleWhitespaces();
            return;
        }
        else if (token.type == TokenType.WRITE) {
            this.addLeadingSpaces();
            this.output += "__w(" + token.content + ");\n";
        }
        else if (token.type == TokenType.INLINE_JS) {
            this.handleWhitespaces();
            this.output += token.content + ";\n";
        }
        else if (token.type == TokenType.PARENT) {
            this.handleWhitespaces();
            var filename = token.content;

            this.compileChildTemplate();

            this.output += "__compiledDeps[\"" + addSlashes(filename) + "\"].run($, __s, __c, __o, __w);\n";
            this.deps.push(filename);
        }
        else if (token.type == TokenType.VIRTUAL) {
            this.handleWhitespaces();
            if (token.content == "parent") {
                this.output += "__a[__i]($, __s, __c, __o, __w, __a, __i + 1);\n";
            }
            else {
                this.output += "__s[\"" + token.content + "\"][0]($, __s, __c, __o, __w, __s[\"" + token.content + "\"], 1);\n";
            }
        }
        else if (token.type == TokenType.SECTION) {
            this.output += "if (!__s[\"" + token.content + "\"]) __s[\"" + token.content + "\"] = [];\n";
            this.output += "__s[\"" + token.content + "\"].push(";
            this.compileSection();
            this.output += ");\n";
        }
        else if (token.type == TokenType.INCLUDE) {
            this.handleWhitespaces();
            var filename = token.content;
            this.output += "__compiledDeps[\"" + addSlashes(filename) + "\"].run($, __s, __c, __o, __w);\n";
            this.deps.push(filename);
        }
        else if (token.type == TokenType.IF) {
            this.blocks++;

            this.output += "if (" + token.content + ") {\n";
            this.handleWhitespaces();
            this.compileTokens();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.ELSE) {
            this.output += "} else {\n";
            this.handleWhitespaces();
        }
        else if (token.type == TokenType.WHILE) {
            this.blocks++;

            this.output += "while (" + token.content + ") {\n";
            this.handleWhitespaces();
            this.compileTokens();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.FOR) {
            this.blocks++;

            this.output += "for (" + token.content + ") {\n";
            this.handleWhitespaces();
            this.compileTokens();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.CUTOUT) {
            this.compileCutout(token.content);
        }
        else if (token.type == TokenType.EXPORT) {
            this.handleWhitespaces();
            // split by first =
            var eqIndex = token.content.indexOf("=");
            var varName = token.content.substr(0, eqIndex).trim();
            if (varName.charAt(0) !== "$") throw new CompilerException("Cannot export local variable '" + varName + "'", this.filename, this.currentLine);
            var assign =  token.content.substr(eqIndex + 1).trim();
            this.output += "$[\"" + addSlashes(varName.substr(1)) + "\"] = " + assign + ";\n";
        }
        else {
            throw new CompilerException("Unexpected token '" + token.type + "'", this.filename, this.currentLine);
        }
    }
}

Compiler.prototype.compileChildTemplate = function() {
    while (this.offset < this.count) {
        var token = this.tokens[this.offset++];
        if (token != null) this.currentLine = token.line;

        if (token == null) { // EOF
            if (this.blocks !== 0) throw new CompilerException("Unexpected EOF", this.filename, this.currentLine);
            return;
        }
        else if (token.type == TokenType.STATIC) {
            if (token.content.trim() !== "") throw new CompilerException("Unexpected non-empty static content", this.filename, this.currentLine);
        }
        else if (token.type == TokenType.COMMENT) {

        }
        else if (token.type == TokenType.INLINE_JS) {
            this.output += token.content + ";\n";
        }
        else if (token.type == TokenType.END) {
            if (this.blocks === 0) throw new CompilerException("Unexpected block end", this.filename, this.currentLine);
            if (token.content !== "") throw new CompilerException("Unexpected '" + token.content + "' after '/'", this.filename, this.currentLine);
            return;
        }
        else if (token.type == TokenType.SECTION) {
            this.firstOnLine = true;
            this.output += "if (!__s[\"" + token.content + "\"]) __s[\"" + token.content + "\"] = [];\n";
            this.output += "__s[\"" + token.content + "\"].push(";
            this.compileSection();
            this.output += ");\n";
        }
        else if (token.type == TokenType.IF) {
            this.blocks++;

            this.output += "if (" + token.content + ") {\n";
            this.compileChildTemplate();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.ELSE) {
            this.output += "} else {\n";
        }
        else if (token.type == TokenType.EXPORT) {
            this.handleWhitespaces();
            // split by first =
            var eqIndex = token.content.indexOf("=");
            var varName = token.content.substr(0, eqIndex).trim();
            if (varName.charAt(0) !== "$") throw new CompilerException("Cannot export local variable '" + varName + "'", this.filename, this.currentLine);
            var assign =  token.content.substr(eqIndex + 1).trim();
            this.output += "$[\"" + addSlashes(varName.substr(1)) + "\"] = " + assign + ";\n";
        }
        else {
            throw new CompilerException("Unexpected token '" + token.type + "'", this.filename, this.currentLine);
        }
    }
}

Compiler.prototype.compileSection = function() {
    this.blocks++;

    this.output += "function($, __s, __c, __o, __p, __a, __i) {\n";
    this.initWrite();
    this.addVariables();
    this.handleWhitespaces();
    this.compileTokens();
    this.output += "}";

    this.blocks--;
}

Compiler.prototype.compileCutout = function(name) {
    this.blocks++;

    this.output += "(function(__p) {\n";
    this.initWrite(name);
    this.addVariables();
    this.handleWhitespaces();
    this.compileTokens();
    this.output += "}(__w));\n";

    this.blocks--;
}

module.exports.compile = compile;
module.exports.CompilerException = CompilerException;

<commit_msg>Renamed variables in compiled files.<commit_after>"use strict";

var path = require("path");
var Tokenizer = require("./tokenizer.js");
var TokenType = Tokenizer.TokenType;

/**

Compiled variable names:

$   - template data
__e - exec_utils
__w - current write function
__s - virtual sections
__c - cutouts
__o - output callback
__p - parent write function
__a - reference to __s[sectionName] (for inheritance)
__i - index for parent virtual section in __a[__i]
__d - compiled dependencies (included templates etc.)

*/

/** Converts a filename to a function name. */
function getTemplateFunctionName(filename) { // @todo not really used anymore, remove?
    return "__cutter_" + path.resolve(filename).replace(/\\|\/|:/g, "_").replace(/\./g, "_dot_");
}

/** Compiles the template and returns JavaScript source code. */
function compile(template, filename) {
    var tokenizer = new Tokenizer(template);
    var globalIdentifiers = {}; // set of all global identifiers
    var tokens = [];

    var token;
    try {
        while (token = tokenizer.nextToken()) {
            var identifiers = token.identifiers();
            if (identifiers) {
                for (var i = 0; i < identifiers.length; i++) {
                    globalIdentifiers[identifiers[i]] = true;
                }
            }

            // a control token can mean a lot of things (write, if, loop etc.)
            token.transform();
            tokens.push(token);
        }
        tokens.push(null); // push EOF token
    }
    catch (e) {
        if (e instanceof Tokenizer.TokenizerException) {
            throw new CompilerException(e.message, filename, e.line);
        }
        else throw e;
    }

    // compiler structure
    var comp = new Compiler({
        filename: filename,
        output: "",
        deps: [],
        tokens: tokens,
        count: tokens.length,
        offset: 0,
        identifiers: Object.keys(globalIdentifiers),
        blocks: 0,
        leadingSpaces: "",
        firstOnLine: true,
        skip: 0,
        currentLine: 0
    });

    comp.compileTemplate();

    return comp.output;
}

function CompilerException(message, filename, line) {
    this.message = message;
    this.filename = filename;
    this.line = line;
}

/** Creates a compiler object with specified status. */
function Compiler(status) {
    for (var key in status) {
        this[key] = status[key];
    }
}

function addSlashes(string) {
    return string
        .replace(/\\/g, '\\\\').
        replace(/\u0008/g, '\\b').
        replace(/\t/g, '\\t').
        replace(/\n/g, '\\n').
        replace(/\f/g, '\\f').
        replace(/\r/g, '\\r').
        replace(/'/g, '\\\'').
        replace(/"/g, '\\"');
}

/** Adds write() function initialization. */
Compiler.prototype.initWrite = function(name) {
    if (!name) name = "null";
    this.output += "var __w = __e.getWrite(" + name + ", __c, __o, __p);\n\n";
}

/** Adds local variables. */
Compiler.prototype.addVariables = function() {
    // @todo add only variables used in section
    for (var i = 0; i < this.identifiers.length; i++) {
        this.output += "var " + this.identifiers[i] + " = $[\"" + this.identifiers[i].substr(1) + "\"];\n";
    }
    this.output += "\n";
}

Compiler.prototype.compileTemplate = function() {
    this.compileHeader();

    this.output += "function run($, __s, __c, __o, __p) {\n";
    this.initWrite();
    this.addVariables();
    this.compileTokens();
    this.output += "}\n\n";

    this.output += "module.exports.run = run;\n";
    this.output += "module.exports.deps = [";
    for (var i = 0; i < this.deps.length; i++) {
        this.output += "\"" + addSlashes(this.deps[i]) + "\"";
        if (i != this.deps.length - 1) this.output += ", ";
    }
    this.output += "];\n";
    this.output += "module.exports.compiledDeps = __d;\n";
}

Compiler.prototype.compileHeader = function() {
    this.output = "\"use strict\";\n\n";
    var execUtilsPath = path.relative(path.dirname(this.filename), path.join(__dirname, "exec_utils.js")).replace(/\\/g, "/");
    this.output += "var __e = require(\"./" + execUtilsPath + "\");\n";
    this.output += "var __d = {};\n\n";
}

Compiler.prototype.addStaticText = function(text) {
    if (text !== "") this.output += "__w(\"" + addSlashes(text) + "\");\n";
}

/** Finds the index where the last empty line (leading whitespaces) begins. */
function lastEmptyLineIndex(str) {
    for (var i = str.length - 1; i >= 0; i--) {
        var c = str.charAt(i);
        if (c !== " " && c !== "\t") {
            if (c === "\r") return i + 1;
            if (c === "\n") {
                if (i > 0 && str.charAt(i - 1) === "\r") return i;
                else return i + 1;
            }
            //else return str.length;
            else return -1;
        }
    }
    return 0;
}

/**
 * If the next token is a static string and starts with a newline, the newline will be skipped.
 * Returns how many characters should be skipped.
 */
Compiler.prototype.skipWhitespaces = function() {
    if (this.tokens[this.offset] && this.tokens[this.offset].type == TokenType.STATIC) {
        var skip = 0;
        var content = this.tokens[this.offset].content;
        for (var i = 0; i < content.length; i++) {
            var c = content.charAt(i);
            if (c === " " || c === "\t") {
                skip++;
            }
            else if (c === "\n") {
                this.skip = skip + 1; break;
            }
            else if (c === "\r") {
                if (i < content.length - 1 && content.charAt(i + 1) === "\n") {
                    this.skip = skip + 2; break;
                }
                else {
                    this.skip = skip + 1; break;
                }
            }
            else {
                this.skip = 0; break;
            }
        }
    }
    else this.skip = 0;
    return this.skip;
}

/**
 * Adds leading whitespaces to the output. This function allows last-line leading whitespaces
 * to be moved inside blocks, loops etc., so the output code has a nice indentation.
 */
Compiler.prototype.addLeadingSpaces = function() {
    if (this.leadingSpaces !== "") {
        this.addStaticText(this.leadingSpaces);
        this.leadingSpaces = "";
        this.firstOnLine = false;
    }
}

/*
 * Adds trailing whitespaces to the output. This function allows to move newlines inside
 * blocks.
 */
Compiler.prototype.addTrailingSpaces = function() {
    if (this.skip !== 0) {
        var spaces = this.tokens[this.offset].content.substr(0, this.skip);
        this.addStaticText(spaces);
    }
}

/** Some magic with whitespaces and newlines. */
Compiler.prototype.handleWhitespaces = function() {
    if (this.skipWhitespaces() === 0) {
        this.addLeadingSpaces();
    }
    else {
        if (this.firstOnLine === false) this.addTrailingSpaces();

        this.leadingSpaces = "";
        this.firstOnLine = false;
    }
}

/** Recursively compiles tokens to JavaScript code. */
Compiler.prototype.compileTokens = function() {

    while (this.offset < this.count) {
        var token = this.tokens[this.offset++];
        if (token != null) this.currentLine = token.line;

        if (token == null) { // EOF
            if (this.blocks !== 0) throw new CompilerException("Unexpected EOF", this.filename, this.currentLine);

            this.addLeadingSpaces();
            return;
        }
        else if (token.type == TokenType.STATIC) {
            this.addLeadingSpaces();

            // find last line with whitespaces only
            var content = token.content.substr(this.skip);

            var index = lastEmptyLineIndex(content);
            if (index !== -1) {
                this.firstOnLine = true;
            }
            else {
                this.firstOnLine = false; index = content.length;
            }

            this.addStaticText(content.substr(0, index));

            this.leadingSpaces = content.substr(index);
            this.skip = 0;
        }
        else if (token.type == TokenType.COMMENT) {
            this.handleWhitespaces();
        }
        else if (token.type == TokenType.END) {
            if (this.blocks === 0) throw new CompilerException("Unexpected block end", this.filename, this.currentLine);
            if (token.content !== "") throw new CompilerException("Unexpected '" + token.content + "' after '/'", this.filename, this.currentLine);
            this.handleWhitespaces();
            return;
        }
        else if (token.type == TokenType.WRITE) {
            this.addLeadingSpaces();
            this.output += "__w(" + token.content + ");\n";
        }
        else if (token.type == TokenType.INLINE_JS) {
            this.handleWhitespaces();
            this.output += token.content + ";\n";
        }
        else if (token.type == TokenType.PARENT) {
            this.handleWhitespaces();
            var filename = token.content;

            this.compileChildTemplate();

            this.output += "__d[\"" + addSlashes(filename) + "\"].run($, __s, __c, __o, __w);\n";
            this.deps.push(filename);
        }
        else if (token.type == TokenType.VIRTUAL) {
            this.handleWhitespaces();
            if (token.content == "parent") {
                this.output += "__a[__i]($, __s, __c, __o, __w, __a, __i + 1);\n";
            }
            else {
                this.output += "__s[\"" + token.content + "\"][0]($, __s, __c, __o, __w, __s[\"" + token.content + "\"], 1);\n";
            }
        }
        else if (token.type == TokenType.SECTION) {
            this.output += "if (!__s[\"" + token.content + "\"]) __s[\"" + token.content + "\"] = [];\n";
            this.output += "__s[\"" + token.content + "\"].push(";
            this.compileSection();
            this.output += ");\n";
        }
        else if (token.type == TokenType.INCLUDE) {
            this.handleWhitespaces();
            var filename = token.content;
            this.output += "__d[\"" + addSlashes(filename) + "\"].run($, __s, __c, __o, __w);\n";
            this.deps.push(filename);
        }
        else if (token.type == TokenType.IF) {
            this.blocks++;

            this.output += "if (" + token.content + ") {\n";
            this.handleWhitespaces();
            this.compileTokens();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.ELSE) {
            this.output += "} else {\n";
            this.handleWhitespaces();
        }
        else if (token.type == TokenType.WHILE) {
            this.blocks++;

            this.output += "while (" + token.content + ") {\n";
            this.handleWhitespaces();
            this.compileTokens();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.FOR) {
            this.blocks++;

            this.output += "for (" + token.content + ") {\n";
            this.handleWhitespaces();
            this.compileTokens();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.CUTOUT) {
            this.compileCutout(token.content);
        }
        else if (token.type == TokenType.EXPORT) {
            this.handleWhitespaces();
            // split by first =
            var eqIndex = token.content.indexOf("=");
            var varName = token.content.substr(0, eqIndex).trim();
            if (varName.charAt(0) !== "$") throw new CompilerException("Cannot export local variable '" + varName + "'", this.filename, this.currentLine);
            var assign =  token.content.substr(eqIndex + 1).trim();
            this.output += "$[\"" + addSlashes(varName.substr(1)) + "\"] = " + assign + ";\n";
        }
        else {
            throw new CompilerException("Unexpected token '" + token.type + "'", this.filename, this.currentLine);
        }
    }
}

Compiler.prototype.compileChildTemplate = function() {
    while (this.offset < this.count) {
        var token = this.tokens[this.offset++];
        if (token != null) this.currentLine = token.line;

        if (token == null) { // EOF
            if (this.blocks !== 0) throw new CompilerException("Unexpected EOF", this.filename, this.currentLine);
            return;
        }
        else if (token.type == TokenType.STATIC) {
            if (token.content.trim() !== "") throw new CompilerException("Unexpected non-empty static content", this.filename, this.currentLine);
        }
        else if (token.type == TokenType.COMMENT) {

        }
        else if (token.type == TokenType.INLINE_JS) {
            this.output += token.content + ";\n";
        }
        else if (token.type == TokenType.END) {
            if (this.blocks === 0) throw new CompilerException("Unexpected block end", this.filename, this.currentLine);
            if (token.content !== "") throw new CompilerException("Unexpected '" + token.content + "' after '/'", this.filename, this.currentLine);
            return;
        }
        else if (token.type == TokenType.SECTION) {
            this.firstOnLine = true;
            this.output += "if (!__s[\"" + token.content + "\"]) __s[\"" + token.content + "\"] = [];\n";
            this.output += "__s[\"" + token.content + "\"].push(";
            this.compileSection();
            this.output += ");\n";
        }
        else if (token.type == TokenType.IF) {
            this.blocks++;

            this.output += "if (" + token.content + ") {\n";
            this.compileChildTemplate();
            this.output += "}\n";
            this.blocks--;
        }
        else if (token.type == TokenType.ELSE) {
            this.output += "} else {\n";
        }
        else if (token.type == TokenType.EXPORT) {
            this.handleWhitespaces();
            // split by first =
            var eqIndex = token.content.indexOf("=");
            var varName = token.content.substr(0, eqIndex).trim();
            if (varName.charAt(0) !== "$") throw new CompilerException("Cannot export local variable '" + varName + "'", this.filename, this.currentLine);
            var assign =  token.content.substr(eqIndex + 1).trim();
            this.output += "$[\"" + addSlashes(varName.substr(1)) + "\"] = " + assign + ";\n";
        }
        else {
            throw new CompilerException("Unexpected token '" + token.type + "'", this.filename, this.currentLine);
        }
    }
}

Compiler.prototype.compileSection = function() {
    this.blocks++;

    this.output += "function($, __s, __c, __o, __p, __a, __i) {\n";
    this.initWrite();
    this.addVariables();
    this.handleWhitespaces();
    this.compileTokens();
    this.output += "}";

    this.blocks--;
}

Compiler.prototype.compileCutout = function(name) {
    this.blocks++;

    this.output += "(function(__p) {\n";
    this.initWrite(name);
    this.addVariables();
    this.handleWhitespaces();
    this.compileTokens();
    this.output += "}(__w));\n";

    this.blocks--;
}

module.exports.compile = compile;
module.exports.CompilerException = CompilerException;

<|endoftext|>


<commit_before>odoo.define('web.test_utils_dom', function (require) {
"use strict";

var concurrency = require('web.concurrency');

/**
 * DOM Test Utils
 *
 * This module defines various utility functions to help simulate DOM events.
 *
 * Note that all methods defined in this module are exported in the main
 * testUtils file.
 */

/**
 * simulate a drag and drop operation between 2 jquery nodes: $el and $to.
 * This is a crude simulation, with only the mousedown, mousemove and mouseup
 * events, but it is enough to help test drag and drop operations with jqueryUI
 * sortable.
 *
 * @todo: remove the withTrailingClick option when the jquery update branch is
 *   merged.  This is not the default as of now, because handlers are triggered
 *   synchronously, which is not the same as the 'reality'.
 *
 * @param {jqueryElement|HTMLElement} $el
 * @param {jqueryElement|HTMLElement} $to
 * @param {Object} [options]
 * @param {string|Object} [options.position='center'] target position:
 *   can either be one of {'top', 'bottom', 'left', 'right'} or
 *   an object with two attributes (top and left))
 * @param {boolean} [options.disableDrop=false] whether to trigger the drop action
 * @param {boolean} [options.continueMove=false] whether to trigger the
 *   mousedown action (will only work after another call of this function with
 *   without this option)
 * @param {boolean} [options.withTrailingClick=false] if true, this utility
 *   function will also trigger a click on the target after the mouseup event
 *   (this is actually what happens when a drag and drop operation is done)
 * @param {jQuery|HTMLElement} [options.mouseenterTarget=undefined] target of the mouseenter event
 * @param {jQuery|HTMLElement} [options.mousedownTarget=undefined] target of the mousedown event
 * @param {jQuery|HTMLElement} [options.mousemoveTarget=undefined] target of the mousemove event
 * @param {jQuery|HTMLElement} [options.mouseupTarget=undefined] target of the mouseup event
 * @returns {Promise}
 */
function dragAndDrop($el, $to, options) {
    let el = null;
    if ($el instanceof EventTarget) {
        el = $el;
        $el = $(el);
    }
    if ($to instanceof EventTarget) {
        $to = $($to);
    }
    options = options || {};
    var position = options.position || 'center';
    var elementCenter = $el.offset();
    var toOffset = $to.offset();

    if (_.isObject(position)) {
        toOffset.top += position.top;
        toOffset.left += position.left;
    } else {
        toOffset.top += $to.outerHeight() / 2;
        toOffset.left += $to.outerWidth() / 2;
        var vertical_offset = (toOffset.top < elementCenter.top) ? -1 : 1;
        if (position === 'top') {
            toOffset.top -= $to.outerHeight() / 2 + vertical_offset;
        } else if (position === 'bottom') {
            toOffset.top += $to.outerHeight() / 2 - vertical_offset;
        } else if (position === 'left') {
            toOffset.left -= $to.outerWidth() / 2;
        } else if (position === 'right') {
            toOffset.left += $to.outerWidth() / 2;
        }
    }

    if ($to[0].ownerDocument !== document) {
        // we are in an iframe
        var bound = $('iframe')[0].getBoundingClientRect();
        toOffset.left += bound.left;
        toOffset.top += bound.top;
    }
    triggerEvent(options.mouseenterTarget || el || $el, 'mouseenter');
    if (!(options.continueMove)) {
        elementCenter.left += $el.outerWidth() / 2;
        elementCenter.top += $el.outerHeight() / 2;

        triggerEvent(options.mousedownTarget || el || $el, 'mousedown', {
            which: 1,
            pageX: elementCenter.left,
            pageY: elementCenter.top
        });
    }

    triggerEvent(options.mousemoveTarget || el || $el, 'mousemove', {
        which: 1,
        pageX: toOffset.left,
        pageY: toOffset.top
    });

    if (!options.disableDrop) {
        triggerEvent(options.mouseupTarget || el || $el, 'mouseup', {
            which: 1,
            pageX: toOffset.left,
            pageY: toOffset.top
        });
        if (options.withTrailingClick) {
            triggerEvent(options.mouseupTarget || el || $el, 'click');
        }
    } else {
        // It's impossible to drag another element when one is already
        // being dragged. So it's necessary to finish the drop when the test is
        // over otherwise it's impossible for the next tests to drag and
        // drop elements.
        $el.on('remove', function () {
            triggerEvent($el, 'mouseup');
        });
    }
    return concurrency.delay(0);
}

/**
 * simulate a mouse event with a custom event who add the item position. This is
 * sometimes necessary because the basic way to trigger an event (such as
 * $el.trigger('mousemove')); ) is too crude for some uses.
 *
 * @param {jQuery|HTMLElement} $el
 * @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
 * @returns {Promise}
 */
function triggerMouseEvent($el, type) {
    const el = $el instanceof jQuery ? $el[0] : $el;
    if (!el) {
        throw new Error(`no target found to trigger MouseEvent`);
    }
    const rect = el.getBoundingClientRect();
    // little fix since it seems on chrome, it triggers 1px too on the left
    const left = rect.x + 1;
    const top = rect.y;
    return triggerEvent($el, type, {
        which: 1,
        pageX: left, layerX: left, screenX: left,
        pageY: top, layerY: top, screenY: top,
    });
}

/**
 * simulate a mouse event with a custom event on a position x and y. This is
 * sometimes necessary because the basic way to trigger an event (such as
 * $el.trigger('mousemove')); ) is too crude for some uses.
 *
 * @param {integer} x
 * @param {integer} y
 * @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
 * @returns {HTMLElement}
 */
function triggerPositionalMouseEvent(x, y, type) {
    var ev = document.createEvent("MouseEvent");
    var el = document.elementFromPoint(x, y);
    ev.initMouseEvent(
        type,
        true /* bubble */,
        true /* cancelable */,
        window, null,
        x, y, x, y, /* coordinates */
        false, false, false, false, /* modifier keys */
        0 /*left button*/, null
    );
    el.dispatchEvent(ev);
    return el;
}

/**
 * simulate a keypress event for a given character
 *
 * @param {string} char the character, or 'ENTER'
 * @returns {Promise}
 */
function triggerKeypressEvent(char) {
    var keycode;
    if (char === "Enter") {
        keycode = $.ui.keyCode.ENTER;
    } else if (char === "Tab") {
        keycode = $.ui.keyCode.TAB;
    } else {
        keycode = char.charCodeAt(0);
    }
    return triggerEvent(document.body, 'keypress', {
        keyCode: keycode,
        which: keycode,
    });
}

/**
 * Click on a specified element. If the option first or last is not specified,
 * this method also check the unicity and the visibility of the target.
 *
 * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
 * @param {Object} [options]
 * @param {boolean} [options.first=false] if true, clicks on the first element
 * @param {boolean} [options.last=false] if true, clicks on the last element
 * @param {boolean} [options.allowInvisible=false] if true, clicks on the
 *   element event if it is invisible
 * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
 *   click event without calling the function click of jquery
 * @returns {Promise}
 */
function click(el, options) {
    options = options || {};
    var matches = typeof el === 'string' ? $(el) : el;
    var selectorMsg = typeof el === 'string' ? `(selector: ${el})` : '';
    if (!matches.filter) { // it might be an array of dom elements
        matches = $(matches);
    }
    var validMatches = options.allowInvisible ? matches : matches.filter(':visible');

    if (options.first) {
        if (validMatches.length === 1) {
            throw new Error(`There should be more than one visible target ${selectorMsg}.  If` +
                ' you are sure that there is exactly one target, please use the ' +
                'click function instead of the clickFirst function');
        }
        validMatches = validMatches.first();
    } else if (options.last) {
        if (validMatches.length === 1) {
            throw new Error(`There should be more than one visible target ${selectorMsg}.  If` +
                ' you are sure that there is exactly one target, please use the ' +
                'click function instead of the clickLast function');
        }
        validMatches = validMatches.last();
    }
    if (validMatches.length === 0 && matches.length > 0) {
        throw new Error(`Element to click on is not visible ${selectorMsg}`);
    } else if (validMatches.length !== 1) {
        throw new Error(`Found ${validMatches.length} elements to click on, instead of 1 ${selectorMsg}`);
    }
    if (options.shouldTriggerClick) {
        return triggerEvent(validMatches, 'click');
    } else {
        validMatches.click();
    }

    return concurrency.delay(0);
}

/**
 * Click on the first element of a list of elements.  Note that if the list has
 * only one visible element, we trigger an error. In that case, it is better to
 * use the click helper instead.
 *
 * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
 * @param {boolean} [options.allowInvisible=false] if true, clicks on the
 *   element event if it is invisible
 * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
 *   click event without calling the function click of jquery
 * @returns {Promise}
 */
function clickFirst(el, options) {
    return click(el, _.extend({}, options, {first: true}));
}

/**
 * Click on the last element of a list of elements.  Note that if the list has
 * only one visible element, we trigger an error. In that case, it is better to
 * use the click helper instead.
 *
 * @param {string|NodeList|jQuery} el
 * @param {boolean} [options.allowInvisible=false] if true, clicks on the
 *   element event if it is invisible
 * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
 *   click event without calling the function click of jquery
 * @returns {Promise}
 */
function clickLast(el, options) {
    return click(el, _.extend({}, options, {last: true}));
}

/**
 * Trigger events on the specified target
 * @param {jQuery|HTMLElement} $el should target a single dom node
 * @param {string[]} events the events you want to trigger
 * @returns {Promise}
 */
function triggerEvents($el, events) {
    if ($el instanceof jQuery) {
        if ($el.length !== 1) {
            throw new Error(`target has length ${$el.length} instead of 1`);
        }
    } else if (!($el instanceof EventTarget)) {
        throw new Error(`target is neither a jQuery element nor an HTML element`);
    }
    if (typeof events === 'string') {
        events = [events];
    }
    return events.reduce((previous, event) => {
        return previous.then(() => triggerEvent($el, event));
    }, Promise.resolve());
}

/**
 * Trigger an event on the specified target.
 * This function will dispatch a native event to an HTMLElement or a
 * jQuery event to a jQuery object.
 * @param {jQuery|HTMLElement} $el event target.
 * @param {string} type event type
 * @param {Object} [options] event attributes
 * @returns {Promise}
 */
function triggerEvent($el, type, options={}) {
    if ($el instanceof EventTarget) {
        const event = new Event(type);
        Object.assign(event, options);
        Object.defineProperty(event, 'target', {
            writable: false,
            value: $el,
        });
        $el.dispatchEvent(event);
    } else {
        const event = Object.keys(options).length ?
            $.Event(type, options) :
            type;
        $el.trigger(event);
    }
    return concurrency.delay(0);
}

/**
 * Opens the datepicker of a given element.
 *
 * @param {jQuery} $datepickerEl element to which a datepicker is attached
 */
function openDatepicker($datepickerEl) {
    return click($datepickerEl.find('.o_datepicker_input'));
}

return {
    triggerKeypressEvent: triggerKeypressEvent,
    triggerMouseEvent: triggerMouseEvent,
    triggerPositionalMouseEvent: triggerPositionalMouseEvent,
    dragAndDrop: dragAndDrop,
    openDatepicker: openDatepicker,
    click: click,
    clickFirst: clickFirst,
    clickLast: clickLast,
    triggerEvents: triggerEvents,
    triggerEvent: triggerEvent,
};

});
<commit_msg>[IMP] web: ctrlKey option in dragAndDrop<commit_after>odoo.define('web.test_utils_dom', function (require) {
"use strict";

var concurrency = require('web.concurrency');

/**
 * DOM Test Utils
 *
 * This module defines various utility functions to help simulate DOM events.
 *
 * Note that all methods defined in this module are exported in the main
 * testUtils file.
 */

/**
 * simulate a drag and drop operation between 2 jquery nodes: $el and $to.
 * This is a crude simulation, with only the mousedown, mousemove and mouseup
 * events, but it is enough to help test drag and drop operations with jqueryUI
 * sortable.
 *
 * @todo: remove the withTrailingClick option when the jquery update branch is
 *   merged.  This is not the default as of now, because handlers are triggered
 *   synchronously, which is not the same as the 'reality'.
 *
 * @param {jqueryElement|HTMLElement} $el
 * @param {jqueryElement|HTMLElement} $to
 * @param {Object} [options]
 * @param {string|Object} [options.position='center'] target position:
 *   can either be one of {'top', 'bottom', 'left', 'right'} or
 *   an object with two attributes (top and left))
 * @param {boolean} [options.disableDrop=false] whether to trigger the drop action
 * @param {boolean} [options.continueMove=false] whether to trigger the
 *   mousedown action (will only work after another call of this function with
 *   without this option)
 * @param {boolean} [options.withTrailingClick=false] if true, this utility
 *   function will also trigger a click on the target after the mouseup event
 *   (this is actually what happens when a drag and drop operation is done)
 * @param {jQuery|HTMLElement} [options.mouseenterTarget=undefined] target of the mouseenter event
 * @param {jQuery|HTMLElement} [options.mousedownTarget=undefined] target of the mousedown event
 * @param {jQuery|HTMLElement} [options.mousemoveTarget=undefined] target of the mousemove event
 * @param {jQuery|HTMLElement} [options.mouseupTarget=undefined] target of the mouseup event
 * @param {jQuery|HTMLElement} [options.ctrlKey=undefined] if the ctrl key should be considered pressed at the time of mouseup
 * @returns {Promise}
 */
function dragAndDrop($el, $to, options) {
    let el = null;
    if ($el instanceof EventTarget) {
        el = $el;
        $el = $(el);
    }
    if ($to instanceof EventTarget) {
        $to = $($to);
    }
    options = options || {};
    var position = options.position || 'center';
    var elementCenter = $el.offset();
    var toOffset = $to.offset();

    if (_.isObject(position)) {
        toOffset.top += position.top;
        toOffset.left += position.left;
    } else {
        toOffset.top += $to.outerHeight() / 2;
        toOffset.left += $to.outerWidth() / 2;
        var vertical_offset = (toOffset.top < elementCenter.top) ? -1 : 1;
        if (position === 'top') {
            toOffset.top -= $to.outerHeight() / 2 + vertical_offset;
        } else if (position === 'bottom') {
            toOffset.top += $to.outerHeight() / 2 - vertical_offset;
        } else if (position === 'left') {
            toOffset.left -= $to.outerWidth() / 2;
        } else if (position === 'right') {
            toOffset.left += $to.outerWidth() / 2;
        }
    }

    if ($to[0].ownerDocument !== document) {
        // we are in an iframe
        var bound = $('iframe')[0].getBoundingClientRect();
        toOffset.left += bound.left;
        toOffset.top += bound.top;
    }
    triggerEvent(options.mouseenterTarget || el || $el, 'mouseenter');
    if (!(options.continueMove)) {
        elementCenter.left += $el.outerWidth() / 2;
        elementCenter.top += $el.outerHeight() / 2;

        triggerEvent(options.mousedownTarget || el || $el, 'mousedown', {
            which: 1,
            pageX: elementCenter.left,
            pageY: elementCenter.top
        });
    }

    triggerEvent(options.mousemoveTarget || el || $el, 'mousemove', {
        which: 1,
        pageX: toOffset.left,
        pageY: toOffset.top
    });

    if (!options.disableDrop) {
        triggerEvent(options.mouseupTarget || el || $el, 'mouseup', {
            which: 1,
            pageX: toOffset.left,
            pageY: toOffset.top,
            ctrlKey: options.ctrlKey,
        });
        if (options.withTrailingClick) {
            triggerEvent(options.mouseupTarget || el || $el, 'click');
        }
    } else {
        // It's impossible to drag another element when one is already
        // being dragged. So it's necessary to finish the drop when the test is
        // over otherwise it's impossible for the next tests to drag and
        // drop elements.
        $el.on('remove', function () {
            triggerEvent($el, 'mouseup');
        });
    }
    return concurrency.delay(0);
}

/**
 * simulate a mouse event with a custom event who add the item position. This is
 * sometimes necessary because the basic way to trigger an event (such as
 * $el.trigger('mousemove')); ) is too crude for some uses.
 *
 * @param {jQuery|HTMLElement} $el
 * @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
 * @returns {Promise}
 */
function triggerMouseEvent($el, type) {
    const el = $el instanceof jQuery ? $el[0] : $el;
    if (!el) {
        throw new Error(`no target found to trigger MouseEvent`);
    }
    const rect = el.getBoundingClientRect();
    // little fix since it seems on chrome, it triggers 1px too on the left
    const left = rect.x + 1;
    const top = rect.y;
    return triggerEvent($el, type, {
        which: 1,
        pageX: left, layerX: left, screenX: left,
        pageY: top, layerY: top, screenY: top,
    });
}

/**
 * simulate a mouse event with a custom event on a position x and y. This is
 * sometimes necessary because the basic way to trigger an event (such as
 * $el.trigger('mousemove')); ) is too crude for some uses.
 *
 * @param {integer} x
 * @param {integer} y
 * @param {string} type a mouse event type, such as 'mousedown' or 'mousemove'
 * @returns {HTMLElement}
 */
function triggerPositionalMouseEvent(x, y, type) {
    var ev = document.createEvent("MouseEvent");
    var el = document.elementFromPoint(x, y);
    ev.initMouseEvent(
        type,
        true /* bubble */,
        true /* cancelable */,
        window, null,
        x, y, x, y, /* coordinates */
        false, false, false, false, /* modifier keys */
        0 /*left button*/, null
    );
    el.dispatchEvent(ev);
    return el;
}

/**
 * simulate a keypress event for a given character
 *
 * @param {string} char the character, or 'ENTER'
 * @returns {Promise}
 */
function triggerKeypressEvent(char) {
    var keycode;
    if (char === "Enter") {
        keycode = $.ui.keyCode.ENTER;
    } else if (char === "Tab") {
        keycode = $.ui.keyCode.TAB;
    } else {
        keycode = char.charCodeAt(0);
    }
    return triggerEvent(document.body, 'keypress', {
        keyCode: keycode,
        which: keycode,
    });
}

/**
 * Click on a specified element. If the option first or last is not specified,
 * this method also check the unicity and the visibility of the target.
 *
 * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
 * @param {Object} [options]
 * @param {boolean} [options.first=false] if true, clicks on the first element
 * @param {boolean} [options.last=false] if true, clicks on the last element
 * @param {boolean} [options.allowInvisible=false] if true, clicks on the
 *   element event if it is invisible
 * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
 *   click event without calling the function click of jquery
 * @returns {Promise}
 */
function click(el, options) {
    options = options || {};
    var matches = typeof el === 'string' ? $(el) : el;
    var selectorMsg = typeof el === 'string' ? `(selector: ${el})` : '';
    if (!matches.filter) { // it might be an array of dom elements
        matches = $(matches);
    }
    var validMatches = options.allowInvisible ? matches : matches.filter(':visible');

    if (options.first) {
        if (validMatches.length === 1) {
            throw new Error(`There should be more than one visible target ${selectorMsg}.  If` +
                ' you are sure that there is exactly one target, please use the ' +
                'click function instead of the clickFirst function');
        }
        validMatches = validMatches.first();
    } else if (options.last) {
        if (validMatches.length === 1) {
            throw new Error(`There should be more than one visible target ${selectorMsg}.  If` +
                ' you are sure that there is exactly one target, please use the ' +
                'click function instead of the clickLast function');
        }
        validMatches = validMatches.last();
    }
    if (validMatches.length === 0 && matches.length > 0) {
        throw new Error(`Element to click on is not visible ${selectorMsg}`);
    } else if (validMatches.length !== 1) {
        throw new Error(`Found ${validMatches.length} elements to click on, instead of 1 ${selectorMsg}`);
    }
    if (options.shouldTriggerClick) {
        return triggerEvent(validMatches, 'click');
    } else {
        validMatches.click();
    }

    return concurrency.delay(0);
}

/**
 * Click on the first element of a list of elements.  Note that if the list has
 * only one visible element, we trigger an error. In that case, it is better to
 * use the click helper instead.
 *
 * @param {string|NodeList|jQuery} el (if string: it is a (jquery) selector)
 * @param {boolean} [options.allowInvisible=false] if true, clicks on the
 *   element event if it is invisible
 * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
 *   click event without calling the function click of jquery
 * @returns {Promise}
 */
function clickFirst(el, options) {
    return click(el, _.extend({}, options, {first: true}));
}

/**
 * Click on the last element of a list of elements.  Note that if the list has
 * only one visible element, we trigger an error. In that case, it is better to
 * use the click helper instead.
 *
 * @param {string|NodeList|jQuery} el
 * @param {boolean} [options.allowInvisible=false] if true, clicks on the
 *   element event if it is invisible
 * @param {boolean} [options.shouldTriggerClick=false] if true, trigger the
 *   click event without calling the function click of jquery
 * @returns {Promise}
 */
function clickLast(el, options) {
    return click(el, _.extend({}, options, {last: true}));
}

/**
 * Trigger events on the specified target
 * @param {jQuery|HTMLElement} $el should target a single dom node
 * @param {string[]} events the events you want to trigger
 * @returns {Promise}
 */
function triggerEvents($el, events) {
    if ($el instanceof jQuery) {
        if ($el.length !== 1) {
            throw new Error(`target has length ${$el.length} instead of 1`);
        }
    } else if (!($el instanceof EventTarget)) {
        throw new Error(`target is neither a jQuery element nor an HTML element`);
    }
    if (typeof events === 'string') {
        events = [events];
    }
    return events.reduce((previous, event) => {
        return previous.then(() => triggerEvent($el, event));
    }, Promise.resolve());
}

/**
 * Trigger an event on the specified target.
 * This function will dispatch a native event to an HTMLElement or a
 * jQuery event to a jQuery object.
 * @param {jQuery|HTMLElement} $el event target.
 * @param {string} type event type
 * @param {Object} [options] event attributes
 * @returns {Promise}
 */
function triggerEvent($el, type, options={}) {
    if ($el instanceof EventTarget) {
        const event = new Event(type);
        Object.assign(event, options);
        Object.defineProperty(event, 'target', {
            writable: false,
            value: $el,
        });
        $el.dispatchEvent(event);
    } else {
        const event = Object.keys(options).length ?
            $.Event(type, options) :
            type;
        $el.trigger(event);
    }
    return concurrency.delay(0);
}

/**
 * Opens the datepicker of a given element.
 *
 * @param {jQuery} $datepickerEl element to which a datepicker is attached
 */
function openDatepicker($datepickerEl) {
    return click($datepickerEl.find('.o_datepicker_input'));
}

return {
    triggerKeypressEvent: triggerKeypressEvent,
    triggerMouseEvent: triggerMouseEvent,
    triggerPositionalMouseEvent: triggerPositionalMouseEvent,
    dragAndDrop: dragAndDrop,
    openDatepicker: openDatepicker,
    click: click,
    clickFirst: clickFirst,
    clickLast: clickLast,
    triggerEvents: triggerEvents,
    triggerEvent: triggerEvent,
};

});
<|endoftext|>


<commit_before>Object.assign(pc, function () {
    'use strict';

    var JSON_ADDRESS_MODE = {
        "repeat": pc.ADDRESS_REPEAT,
        "clamp": pc.ADDRESS_CLAMP_TO_EDGE,
        "mirror": pc.ADDRESS_MIRRORED_REPEAT
    };

    var JSON_FILTER_MODE = {
        "nearest": pc.FILTER_NEAREST,
        "linear": pc.FILTER_LINEAR,
        "nearest_mip_nearest": pc.FILTER_NEAREST_MIPMAP_NEAREST,
        "linear_mip_nearest": pc.FILTER_LINEAR_MIPMAP_NEAREST,
        "nearest_mip_linear": pc.FILTER_NEAREST_MIPMAP_LINEAR,
        "linear_mip_linear": pc.FILTER_LINEAR_MIPMAP_LINEAR
    };

    function arrayBufferCopy(src, dst, dstByteOffset, numBytes) {
        var i;
        var dst32Offset = dstByteOffset / 4;
        var tail = (numBytes % 4);
        var src32 = new Uint32Array(src.buffer, 0, (numBytes - tail) / 4);
        var dst32 = new Uint32Array(dst.buffer);
        for (i = 0; i < src32.length; i++) {
            dst32[dst32Offset + i] = src32[i];
        }
        for (i = numBytes - tail; i < numBytes; i++) {
            dst[dstByteOffset + i] = src[i];
        }
    }

    var TextureHandler = function (device, assets, loader) {
        this._device = device;
        this._assets = assets;
        this._loader = loader;

        // by default don't try cross-origin, because some browsers send different cookies (e.g. safari) if this is set.
        this.crossOrigin = undefined;
        if (assets.prefix) {
            // ensure we send cookies if we load images.
            this.crossOrigin = 'anonymous';
        }
    };

    Object.assign(TextureHandler.prototype, {
        load: function (url, callback) {
            var self = this;
            var image;

            var urlWithoutParams = url.indexOf('?') >= 0 ? url.split('?')[0] : url;

            var ext = pc.path.getExtension(urlWithoutParams).toLowerCase();
            if (ext === '.dds' || ext === '.ktx') {
                var options = {
                    cache: true,
                    responseType: "arraybuffer"
                };

                pc.http.get(url, options, function (err, response) {
                    if (!err) {
                        callback(null, response);
                    } else {
                        callback(err);
                    }
                });
            } else if ((ext === '.jpg') || (ext === '.jpeg') || (ext === '.gif') || (ext === '.png')) {
                image = new Image();
                // only apply cross-origin setting if this is an absolute URL, relative URLs can never be cross-origin
                if (self.crossOrigin !== undefined && pc.ABSOLUTE_URL.test(url)) {
                    image.crossOrigin = self.crossOrigin;
                }

                // Call success callback after opening Texture
                image.onload = function () {
                    callback(null, image);
                };

                // Call error callback with details.
                image.onerror = function (event) {
                    callback(pc.string.format("Error loading Texture from: '{0}'", url));
                };

                image.src = url;
            } else {
                var blobStart = urlWithoutParams.indexOf("blob:");
                if (blobStart >= 0) {
                    urlWithoutParams = urlWithoutParams.substr(blobStart);
                    url = urlWithoutParams;

                    image = new Image();

                    // Call success callback after opening Texture
                    image.onload = function () {
                        callback(null, image);
                    };

                    // Call error callback with details.
                    image.onerror = function (event) {
                        callback(pc.string.format("Error loading Texture from: '{0}'", url));
                    };

                    image.src = url;
                } else {
                    // Unsupported texture extension
                    // Use timeout because asset events can be hooked up after load gets called in some
                    // cases. For example, material loads a texture on 'add' event.
                    setTimeout(function () {
                        callback(pc.string.format("Error loading Texture: format not supported: '{0}'", ext));
                    }, 0);
                }
            }
        },

        open: function (url, data) {
            if (!url)
                return;

            var texture;
            var ext = pc.path.getExtension(url).toLowerCase();
            var format = null;

            // Every browser seems to pass data as an Image type. For some reason, the XDK
            // passes an HTMLImageElement. TODO: figure out why!
            // DDS textures are ArrayBuffers
            if ((data instanceof Image) || (data instanceof HTMLImageElement)) { // PNG, JPG or GIF
                var img = data;

                format = (ext === ".jpg" || ext === ".jpeg") ? pc.PIXELFORMAT_R8_G8_B8 : pc.PIXELFORMAT_R8_G8_B8_A8;
                texture = new pc.Texture(this._device, {
                    // #ifdef PROFILER
                    profilerHint: pc.TEXHINT_ASSET,
                    // #endif
                    width: img.width,
                    height: img.height,
                    format: format
                });
                texture.setSource(img);
            } else if (data instanceof ArrayBuffer) { // Container format
                var LEGACY = true;

                if (LEGACY) {
                    texture = _legacyDdsLoader(url, data, this._device);
                } else {
                    var textureData;

                    switch (ext) {
                        case '.dds':
                            textureData = new pc.DdsParser(data);
                            break;
                        case '.ktx':
                            textureData = new pc.KtxParser(data);
                            break;
                        case '.pvr':
                            console.warn('PVR container not supported.');
                            break;
                    }

                    if (!textureData) {
                        // #ifdef DEBUG
                        console.warn("This DDS or KTX pixel format is currently unsupported. Empty texture will be created instead.");
                        // #endif
                        return new pc.Texture(this._device, {
                            width: 4,
                            height: 4,
                            format: pc.PIXELFORMAT_R8_G8_B8
                        });
                    }

                    texture = new pc.Texture(this._device, {
                        // #ifdef PROFILER
                        profilerHint: pc.TEXHINT_ASSET,
                        // #endif
                        addressU: textureData.cubemap ? pc.ADDRESS_CLAMP_TO_EDGE : pc.ADDRESS_REPEAT,
                        addressV: textureData.cubemap ? pc.ADDRESS_CLAMP_TO_EDGE : pc.ADDRESS_REPEAT,
                        width: textureData.width,
                        height: textureData.height,
                        format: textureData.format,
                        cubemap: textureData.cubemap,
                        levels: textureData.levels
                    });

                    texture.upload();
                }

            }

            return texture;
        },

        patch: function (asset, assets) {
            var texture = asset.resource;

            if (!texture)
                return;

            if (texture.name !== asset.name)
                texture.name = asset.name;

            if (asset.data.hasOwnProperty('minfilter') && texture.minFilter !== JSON_FILTER_MODE[asset.data.minfilter])
                texture.minFilter = JSON_FILTER_MODE[asset.data.minfilter];

            if (asset.data.hasOwnProperty('magfilter') && texture.magFilter !== JSON_FILTER_MODE[asset.data.magfilter])
                texture.magFilter = JSON_FILTER_MODE[asset.data.magfilter];

            if (asset.data.hasOwnProperty('addressu') && texture.addressU !== JSON_ADDRESS_MODE[asset.data.addressu])
                texture.addressU = JSON_ADDRESS_MODE[asset.data.addressu];

            if (asset.data.hasOwnProperty('addressv') && texture.addressV !== JSON_ADDRESS_MODE[asset.data.addressv])
                texture.addressV = JSON_ADDRESS_MODE[asset.data.addressv];

            if (asset.data.hasOwnProperty('mipmaps') && texture.mipmaps !== asset.data.mipmaps)
                texture.mipmaps = asset.data.mipmaps;

            if (asset.data.hasOwnProperty('anisotropy') && texture.anisotropy !== asset.data.anisotropy)
                texture.anisotropy = asset.data.anisotropy;

            var rgbm = !!asset.data.rgbm;
            if (asset.data.hasOwnProperty('rgbm') && texture.rgbm !== rgbm)
                texture.rgbm = rgbm;
        }
    });

    var _legacyDdsLoader = function (url, data, graphicsDevice) {

        var ext = pc.path.getExtension(url).toLowerCase();

        if (ext === ".crn") {
            // Copy loaded file into Emscripten-managed memory
            var srcSize = data.byteLength;
            var bytes = new Uint8Array(data);
            var src = Module._malloc(srcSize);
            arrayBufferCopy(bytes, Module.HEAPU8, src, srcSize);

            // Decompress CRN to DDS (minus the header)
            var dst = Module._crn_decompress_get_data(src, srcSize);
            var dstSize = Module._crn_decompress_get_size(src, srcSize);

            data = Module.HEAPU8.buffer.slice(dst, dst + dstSize);
        }

        // DDS loading
        var header = new Uint32Array(data, 0, 128 / 4);

        var width = header[4];
        var height = header[3];
        var mips = Math.max(header[7], 1);
        var isFourCc = header[20] === 4;
        var fcc = header[21];
        var bpp = header[22];
        var isCubemap = header[28] === 65024; // TODO: check by bitflag

        var FCC_DXT1 = 827611204; // DXT1
        var FCC_DXT5 = 894720068; // DXT5
        var FCC_FP32 = 116; // RGBA32f

        // non standard
        var FCC_ETC1 = 826496069;
        var FCC_PVRTC_2BPP_RGB_1 = 825438800;
        var FCC_PVRTC_2BPP_RGBA_1 = 825504336;
        var FCC_PVRTC_4BPP_RGB_1 = 825439312;
        var FCC_PVRTC_4BPP_RGBA_1 = 825504848;

        var compressed = false;
        var floating = false;
        var etc1 = false;
        var pvrtc2 = false;
        var pvrtc4 = false;
        if (isFourCc) {
            if (fcc === FCC_DXT1) {
                format = pc.PIXELFORMAT_DXT1;
                compressed = true;
            } else if (fcc === FCC_DXT5) {
                format = pc.PIXELFORMAT_DXT5;
                compressed = true;
            } else if (fcc === FCC_FP32) {
                format = pc.PIXELFORMAT_RGBA32F;
                floating = true;
            } else if (fcc === FCC_ETC1) {
                format = pc.PIXELFORMAT_ETC1;
                compressed = true;
                etc1 = true;
            } else if (fcc === FCC_PVRTC_2BPP_RGB_1 || fcc === FCC_PVRTC_2BPP_RGBA_1) {
                format = fcc === FCC_PVRTC_2BPP_RGB_1 ? pc.PIXELFORMAT_PVRTC_2BPP_RGB_1 : pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1;
                compressed = true;
                pvrtc2 = true;
            } else if (fcc === FCC_PVRTC_4BPP_RGB_1 || fcc === FCC_PVRTC_4BPP_RGBA_1) {
                format = fcc === FCC_PVRTC_4BPP_RGB_1 ? pc.PIXELFORMAT_PVRTC_4BPP_RGB_1 : pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1;
                compressed = true;
                pvrtc4 = true;
            }
        } else {
            if (bpp === 32) {
                format = pc.PIXELFORMAT_R8_G8_B8_A8;
            }
        }

        if (!format) {
            // #ifdef DEBUG
            console.error("This DDS pixel format is currently unsupported. Empty texture will be created instead.");
            // #endif
            texture = new pc.Texture(graphicsDevice, {
                width: 4,
                height: 4,
                format: pc.PIXELFORMAT_R8_G8_B8
            });
            return texture;
        }

        var texOptions = {
            // #ifdef PROFILER
            profilerHint: pc.TEXHINT_ASSET,
            // #endif
            width: width,
            height: height,
            format: format,
            cubemap: isCubemap
        };
        texture = new pc.Texture(graphicsDevice, texOptions);
        if (isCubemap) {
            texture.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
            texture.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
        }

        var offset = 128;
        var faces = isCubemap ? 6 : 1;
        var mipSize;
        var DXT_BLOCK_WIDTH = 4;
        var DXT_BLOCK_HEIGHT = 4;
        var blockSize = fcc === FCC_DXT1 ? 8 : 16;
        var numBlocksAcross, numBlocksDown, numBlocks;
        for (var face = 0; face < faces; face++) {
            var mipWidth = width;
            var mipHeight = height;
            for (var i = 0; i < mips; i++) {
                if (compressed) {
                    if (etc1) {
                        mipSize = Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8;
                    } else if (pvrtc2) {
                        mipSize = Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4;
                    } else if (pvrtc4) {
                        mipSize = Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2;
                    } else {
                        numBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);
                        numBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);
                        numBlocks = numBlocksAcross * numBlocksDown;
                        mipSize = numBlocks * blockSize;
                    }
                } else {
                    mipSize = mipWidth * mipHeight * 4;
                }

                var mipBuff = floating ? new Float32Array(data, offset, mipSize) : new Uint8Array(data, offset, mipSize);
                if (!isCubemap) {
                    texture._levels[i] = mipBuff;
                } else {
                    if (!texture._levels[i]) texture._levels[i] = [];
                    texture._levels[i][face] = mipBuff;
                }
                offset += floating ? mipSize * 4 : mipSize;
                mipWidth = Math.max(mipWidth * 0.5, 1);
                mipHeight = Math.max(mipHeight * 0.5, 1);
            }
        }

        texture.name = url;
        texture.upload();

        return texture;
    };
    return {
        TextureHandler: TextureHandler
    };
}());
<commit_msg>Linting fixes<commit_after>Object.assign(pc, function () {
    'use strict';

    var JSON_ADDRESS_MODE = {
        "repeat": pc.ADDRESS_REPEAT,
        "clamp": pc.ADDRESS_CLAMP_TO_EDGE,
        "mirror": pc.ADDRESS_MIRRORED_REPEAT
    };

    var JSON_FILTER_MODE = {
        "nearest": pc.FILTER_NEAREST,
        "linear": pc.FILTER_LINEAR,
        "nearest_mip_nearest": pc.FILTER_NEAREST_MIPMAP_NEAREST,
        "linear_mip_nearest": pc.FILTER_LINEAR_MIPMAP_NEAREST,
        "nearest_mip_linear": pc.FILTER_NEAREST_MIPMAP_LINEAR,
        "linear_mip_linear": pc.FILTER_LINEAR_MIPMAP_LINEAR
    };

    function arrayBufferCopy(src, dst, dstByteOffset, numBytes) {
        var i;
        var dst32Offset = dstByteOffset / 4;
        var tail = (numBytes % 4);
        var src32 = new Uint32Array(src.buffer, 0, (numBytes - tail) / 4);
        var dst32 = new Uint32Array(dst.buffer);
        for (i = 0; i < src32.length; i++) {
            dst32[dst32Offset + i] = src32[i];
        }
        for (i = numBytes - tail; i < numBytes; i++) {
            dst[dstByteOffset + i] = src[i];
        }
    }

    var TextureHandler = function (device, assets, loader) {
        this._device = device;
        this._assets = assets;
        this._loader = loader;

        // by default don't try cross-origin, because some browsers send different cookies (e.g. safari) if this is set.
        this.crossOrigin = undefined;
        if (assets.prefix) {
            // ensure we send cookies if we load images.
            this.crossOrigin = 'anonymous';
        }
    };

    Object.assign(TextureHandler.prototype, {
        load: function (url, callback) {
            var self = this;
            var image;

            var urlWithoutParams = url.indexOf('?') >= 0 ? url.split('?')[0] : url;

            var ext = pc.path.getExtension(urlWithoutParams).toLowerCase();
            if (ext === '.dds' || ext === '.ktx') {
                var options = {
                    cache: true,
                    responseType: "arraybuffer"
                };

                pc.http.get(url, options, function (err, response) {
                    if (!err) {
                        callback(null, response);
                    } else {
                        callback(err);
                    }
                });
            } else if ((ext === '.jpg') || (ext === '.jpeg') || (ext === '.gif') || (ext === '.png')) {
                image = new Image();
                // only apply cross-origin setting if this is an absolute URL, relative URLs can never be cross-origin
                if (self.crossOrigin !== undefined && pc.ABSOLUTE_URL.test(url)) {
                    image.crossOrigin = self.crossOrigin;
                }

                // Call success callback after opening Texture
                image.onload = function () {
                    callback(null, image);
                };

                // Call error callback with details.
                image.onerror = function (event) {
                    callback(pc.string.format("Error loading Texture from: '{0}'", url));
                };

                image.src = url;
            } else {
                var blobStart = urlWithoutParams.indexOf("blob:");
                if (blobStart >= 0) {
                    urlWithoutParams = urlWithoutParams.substr(blobStart);
                    url = urlWithoutParams;

                    image = new Image();

                    // Call success callback after opening Texture
                    image.onload = function () {
                        callback(null, image);
                    };

                    // Call error callback with details.
                    image.onerror = function (event) {
                        callback(pc.string.format("Error loading Texture from: '{0}'", url));
                    };

                    image.src = url;
                } else {
                    // Unsupported texture extension
                    // Use timeout because asset events can be hooked up after load gets called in some
                    // cases. For example, material loads a texture on 'add' event.
                    setTimeout(function () {
                        callback(pc.string.format("Error loading Texture: format not supported: '{0}'", ext));
                    }, 0);
                }
            }
        },

        open: function (url, data) {
            if (!url)
                return;

            var texture;
            var ext = pc.path.getExtension(url).toLowerCase();
            var format = null;

            // Every browser seems to pass data as an Image type. For some reason, the XDK
            // passes an HTMLImageElement. TODO: figure out why!
            // DDS textures are ArrayBuffers
            if ((data instanceof Image) || (data instanceof HTMLImageElement)) { // PNG, JPG or GIF
                var img = data;

                format = (ext === ".jpg" || ext === ".jpeg") ? pc.PIXELFORMAT_R8_G8_B8 : pc.PIXELFORMAT_R8_G8_B8_A8;
                texture = new pc.Texture(this._device, {
                    // #ifdef PROFILER
                    profilerHint: pc.TEXHINT_ASSET,
                    // #endif
                    width: img.width,
                    height: img.height,
                    format: format
                });
                texture.setSource(img);
            } else if (data instanceof ArrayBuffer) { // Container format
                var LEGACY = true;

                if (LEGACY) {
                    texture = _legacyDdsLoader(url, data, this._device);
                } else {
                    var textureData;

                    switch (ext) {
                        case '.dds':
                            textureData = new pc.DdsParser(data);
                            break;
                        case '.ktx':
                            textureData = new pc.KtxParser(data);
                            break;
                        case '.pvr':
                            console.warn('PVR container not supported.');
                            break;
                    }

                    if (!textureData) {
                        // #ifdef DEBUG
                        console.warn("This DDS or KTX pixel format is currently unsupported. Empty texture will be created instead.");
                        // #endif
                        return new pc.Texture(this._device, {
                            width: 4,
                            height: 4,
                            format: pc.PIXELFORMAT_R8_G8_B8
                        });
                    }

                    texture = new pc.Texture(this._device, {
                        // #ifdef PROFILER
                        profilerHint: pc.TEXHINT_ASSET,
                        // #endif
                        addressU: textureData.cubemap ? pc.ADDRESS_CLAMP_TO_EDGE : pc.ADDRESS_REPEAT,
                        addressV: textureData.cubemap ? pc.ADDRESS_CLAMP_TO_EDGE : pc.ADDRESS_REPEAT,
                        width: textureData.width,
                        height: textureData.height,
                        format: textureData.format,
                        cubemap: textureData.cubemap,
                        levels: textureData.levels
                    });

                    texture.upload();
                }

            }

            return texture;
        },

        patch: function (asset, assets) {
            var texture = asset.resource;

            if (!texture)
                return;

            if (texture.name !== asset.name)
                texture.name = asset.name;

            if (asset.data.hasOwnProperty('minfilter') && texture.minFilter !== JSON_FILTER_MODE[asset.data.minfilter])
                texture.minFilter = JSON_FILTER_MODE[asset.data.minfilter];

            if (asset.data.hasOwnProperty('magfilter') && texture.magFilter !== JSON_FILTER_MODE[asset.data.magfilter])
                texture.magFilter = JSON_FILTER_MODE[asset.data.magfilter];

            if (asset.data.hasOwnProperty('addressu') && texture.addressU !== JSON_ADDRESS_MODE[asset.data.addressu])
                texture.addressU = JSON_ADDRESS_MODE[asset.data.addressu];

            if (asset.data.hasOwnProperty('addressv') && texture.addressV !== JSON_ADDRESS_MODE[asset.data.addressv])
                texture.addressV = JSON_ADDRESS_MODE[asset.data.addressv];

            if (asset.data.hasOwnProperty('mipmaps') && texture.mipmaps !== asset.data.mipmaps)
                texture.mipmaps = asset.data.mipmaps;

            if (asset.data.hasOwnProperty('anisotropy') && texture.anisotropy !== asset.data.anisotropy)
                texture.anisotropy = asset.data.anisotropy;

            var rgbm = !!asset.data.rgbm;
            if (asset.data.hasOwnProperty('rgbm') && texture.rgbm !== rgbm)
                texture.rgbm = rgbm;
        }
    });

    var _legacyDdsLoader = function (url, data, graphicsDevice) {

        var ext = pc.path.getExtension(url).toLowerCase();

        if (ext === ".crn") {
            // Copy loaded file into Emscripten-managed memory
            var srcSize = data.byteLength;
            var bytes = new Uint8Array(data);
            var src = Module._malloc(srcSize);
            arrayBufferCopy(bytes, Module.HEAPU8, src, srcSize);

            // Decompress CRN to DDS (minus the header)
            var dst = Module._crn_decompress_get_data(src, srcSize);
            var dstSize = Module._crn_decompress_get_size(src, srcSize);

            data = Module.HEAPU8.buffer.slice(dst, dst + dstSize);
        }

        // DDS loading
        var header = new Uint32Array(data, 0, 128 / 4);

        var width = header[4];
        var height = header[3];
        var mips = Math.max(header[7], 1);
        var isFourCc = header[20] === 4;
        var fcc = header[21];
        var bpp = header[22];
        var isCubemap = header[28] === 65024; // TODO: check by bitflag

        var FCC_DXT1 = 827611204; // DXT1
        var FCC_DXT5 = 894720068; // DXT5
        var FCC_FP32 = 116; // RGBA32f

        // non standard
        var FCC_ETC1 = 826496069;
        var FCC_PVRTC_2BPP_RGB_1 = 825438800;
        var FCC_PVRTC_2BPP_RGBA_1 = 825504336;
        var FCC_PVRTC_4BPP_RGB_1 = 825439312;
        var FCC_PVRTC_4BPP_RGBA_1 = 825504848;

        var compressed = false;
        var floating = false;
        var etc1 = false;
        var pvrtc2 = false;
        var pvrtc4 = false;
        var format = null;

        var texture;

        if (isFourCc) {
            if (fcc === FCC_DXT1) {
                format = pc.PIXELFORMAT_DXT1;
                compressed = true;
            } else if (fcc === FCC_DXT5) {
                format = pc.PIXELFORMAT_DXT5;
                compressed = true;
            } else if (fcc === FCC_FP32) {
                format = pc.PIXELFORMAT_RGBA32F;
                floating = true;
            } else if (fcc === FCC_ETC1) {
                format = pc.PIXELFORMAT_ETC1;
                compressed = true;
                etc1 = true;
            } else if (fcc === FCC_PVRTC_2BPP_RGB_1 || fcc === FCC_PVRTC_2BPP_RGBA_1) {
                format = fcc === FCC_PVRTC_2BPP_RGB_1 ? pc.PIXELFORMAT_PVRTC_2BPP_RGB_1 : pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1;
                compressed = true;
                pvrtc2 = true;
            } else if (fcc === FCC_PVRTC_4BPP_RGB_1 || fcc === FCC_PVRTC_4BPP_RGBA_1) {
                format = fcc === FCC_PVRTC_4BPP_RGB_1 ? pc.PIXELFORMAT_PVRTC_4BPP_RGB_1 : pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1;
                compressed = true;
                pvrtc4 = true;
            }
        } else {
            if (bpp === 32) {
                format = pc.PIXELFORMAT_R8_G8_B8_A8;
            }
        }

        if (!format) {
            // #ifdef DEBUG
            console.error("This DDS pixel format is currently unsupported. Empty texture will be created instead.");
            // #endif
            texture = new pc.Texture(graphicsDevice, {
                width: 4,
                height: 4,
                format: pc.PIXELFORMAT_R8_G8_B8
            });
            return texture;
        }

        var texOptions = {
            // #ifdef PROFILER
            profilerHint: pc.TEXHINT_ASSET,
            // #endif
            width: width,
            height: height,
            format: format,
            cubemap: isCubemap
        };
        texture = new pc.Texture(graphicsDevice, texOptions);
        if (isCubemap) {
            texture.addressU = pc.ADDRESS_CLAMP_TO_EDGE;
            texture.addressV = pc.ADDRESS_CLAMP_TO_EDGE;
        }

        var offset = 128;
        var faces = isCubemap ? 6 : 1;
        var mipSize;
        var DXT_BLOCK_WIDTH = 4;
        var DXT_BLOCK_HEIGHT = 4;
        var blockSize = fcc === FCC_DXT1 ? 8 : 16;
        var numBlocksAcross, numBlocksDown, numBlocks;
        for (var face = 0; face < faces; face++) {
            var mipWidth = width;
            var mipHeight = height;
            for (var i = 0; i < mips; i++) {
                if (compressed) {
                    if (etc1) {
                        mipSize = Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8;
                    } else if (pvrtc2) {
                        mipSize = Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4;
                    } else if (pvrtc4) {
                        mipSize = Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2;
                    } else {
                        numBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);
                        numBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);
                        numBlocks = numBlocksAcross * numBlocksDown;
                        mipSize = numBlocks * blockSize;
                    }
                } else {
                    mipSize = mipWidth * mipHeight * 4;
                }

                var mipBuff = floating ? new Float32Array(data, offset, mipSize) : new Uint8Array(data, offset, mipSize);
                if (!isCubemap) {
                    texture._levels[i] = mipBuff;
                } else {
                    if (!texture._levels[i]) texture._levels[i] = [];
                    texture._levels[i][face] = mipBuff;
                }
                offset += floating ? mipSize * 4 : mipSize;
                mipWidth = Math.max(mipWidth * 0.5, 1);
                mipHeight = Math.max(mipHeight * 0.5, 1);
            }
        }

        texture.name = url;
        texture.upload();

        return texture;
    };
    return {
        TextureHandler: TextureHandler
    };
}());
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import UsernameText from 'components/Texts/UsernameText';
import ErrorBoundary from 'components/Wrappers/ErrorBoundary';
import ContentLink from 'components/ContentLink';
import { timeSince } from 'helpers/timeStampHelpers';
import { connect } from 'react-redux';
import { Color } from 'constants/css';
import { truncateText } from 'helpers/stringHelpers';

class NotiItem extends Component {
  static propTypes = {
    myId: PropTypes.number.isRequired,
    notification: PropTypes.object.isRequired
  };
  render() {
    const {
      myId,
      notification: {
        actionObj = {},
        targetComment = {},
        targetObj = {},
        timeStamp,
        user = {}
      }
    } = this.props;
    let notificationMessage;
    const isReply = targetComment?.userId === myId;
    switch (actionObj.type) {
      case 'like':
        notificationMessage = (
          <>

              likes
            {' '}
            your{' '}
            <ContentLink
              type={targetObj.type}
              content={{
                id: targetObj.id,
                title: `${
                  targetObj.type === 'url' ? 'link' : targetObj.type
                } (${truncateText({ text: targetObj.content, limit: 100 })})`
              }}
            />
          </>
        );
        break;
      case 'reward':
        notificationMessage = (
          <>
            = 10
                    ? Color.rose()
                    : actionObj.amount >= 5
                    ? Color.orange()
                    : actionObj.amount >= 3
                    ? Color.pink()
                    : Color.lightBlue(),
                fontWeight: 'bold'
              }}
            >
              rewarded you {actionObj.amount === 1 ? 'a' : actionObj.amount}{' '}
              Twinkle
              {actionObj.amount > 1 ? 's' : ''}
            {' '}
            for your{' '}
            <ContentLink
              type={targetObj.type}
              content={{
                id: targetObj.id,
                title: `${
                  targetObj.type === 'url' ? 'link' : targetObj.type
                } (${truncateText({ text: targetObj.content, limit: 100 })})`
              }}
            />
          </>
        );
        break;
      case 'comment':
        notificationMessage = (
          <>
            <ContentLink
              type="comment"
              content={{
                id: actionObj.id,
                title: isReply
                  ? 'replied to'
                  : targetObj.type === 'user'
                  ? 'left a message on'
                  : 'commented on'
              }}
              style={{ color: Color.green() }}
            />{' '}
            your{' '}
            <ContentLink
              type={isReply ? 'comment' : targetObj.type}
              content={{
                id: isReply ? targetComment.id : targetObj.id,
                title: `${
                  isReply
                    ? 'comment'
                    : targetObj.type === 'user'
                    ? 'profile'
                    : targetObj.type === 'url'
                    ? 'link'
                    : targetObj.type
                }${
                  !isReply && targetObj.content === 'user'
                    ? ''
                    : ` (${truncateText({
                        text: isReply
                          ? targetComment.content
                          : targetObj.content,
                        limit: 100
                      })})`
                }`
              }}
            />
            :{' '}
            <ContentLink
              type="comment"
              content={{
                id: actionObj.id,
                title: `"${truncateText({
                  text: actionObj.content,
                  limit: 100
                })}"`
              }}
              style={{ color: Color.green() }}
            />
          </>
        );
        break;
      case 'subject':
        notificationMessage = (
          <>
            added a
            <ContentLink
              type="subject"
              content={{
                id: actionObj.id,
                title: `subject (${truncateText({
                  text: actionObj.content,
                  limit: 100
                })})`
              }}
              style={{ color: Color.green() }}
            />{' '}
            to your
            <ContentLink
              type={targetObj.type}
              content={{
                id: targetObj.id,
                title: `${
                  targetObj.type === 'url' ? 'link' : targetObj.type
                } (${truncateText({ text: targetObj.content, limit: 100 })})`
              }}
            />
          </>
        );
        break;
      default:
        notificationMessage = (
          There was an error - report to Mikey!
        );
        break;
    }
    return (
      <ErrorBoundary>

          <UsernameText user={user} color={Color.blue()} />

          {notificationMessage}

        {timeSince(timeStamp)}
      </ErrorBoundary>
    );
  }
}

export default connect(state => ({
  myId: state.UserReducer.userId
}))(NotiItem);
<commit_msg>minor fix<commit_after>import React, { Component } from 'react';
import PropTypes from 'prop-types';
import UsernameText from 'components/Texts/UsernameText';
import ErrorBoundary from 'components/Wrappers/ErrorBoundary';
import ContentLink from 'components/ContentLink';
import { timeSince } from 'helpers/timeStampHelpers';
import { connect } from 'react-redux';
import { Color } from 'constants/css';
import { truncateText } from 'helpers/stringHelpers';

class NotiItem extends Component {
  static propTypes = {
    myId: PropTypes.number.isRequired,
    notification: PropTypes.object.isRequired
  };
  render() {
    const {
      myId,
      notification: {
        actionObj = {},
        targetComment = {},
        targetObj = {},
        timeStamp,
        user = {}
      }
    } = this.props;
    let notificationMessage;
    const isReply = targetComment?.userId === myId;
    switch (actionObj.type) {
      case 'like':
        notificationMessage = (
          <>

              likes
            {' '}
            your{' '}
            <ContentLink
              type={targetObj.type}
              content={{
                id: targetObj.id,
                title: `${
                  targetObj.type === 'url' ? 'link' : targetObj.type
                } (${truncateText({ text: targetObj.content, limit: 100 })})`
              }}
            />
          </>
        );
        break;
      case 'reward':
        notificationMessage = (
          <>
            = 10
                    ? Color.rose()
                    : actionObj.amount >= 5
                    ? Color.orange()
                    : actionObj.amount >= 3
                    ? Color.pink()
                    : Color.lightBlue(),
                fontWeight: 'bold'
              }}
            >
              rewarded you {actionObj.amount === 1 ? 'a' : actionObj.amount}{' '}
              Twinkle
              {actionObj.amount > 1 ? 's' : ''}
            {' '}
            for your{' '}
            <ContentLink
              type={targetObj.type}
              content={{
                id: targetObj.id,
                title: `${
                  targetObj.type === 'url' ? 'link' : targetObj.type
                } (${truncateText({ text: targetObj.content, limit: 100 })})`
              }}
            />
          </>
        );
        break;
      case 'comment':
        notificationMessage = (
          <>
            <ContentLink
              type="comment"
              content={{
                id: actionObj.id,
                title: isReply
                  ? 'replied to'
                  : targetObj.type === 'user'
                  ? 'left a message on'
                  : 'commented on'
              }}
              style={{ color: Color.green() }}
            />{' '}
            your{' '}
            <ContentLink
              type={isReply ? 'comment' : targetObj.type}
              content={{
                id: isReply ? targetComment.id : targetObj.id,
                title: `${
                  isReply
                    ? 'comment'
                    : targetObj.type === 'user'
                    ? 'profile'
                    : targetObj.type === 'url'
                    ? 'link'
                    : targetObj.type
                }${
                  !isReply && targetObj.content === 'user'
                    ? ''
                    : ` (${truncateText({
                        text: isReply
                          ? targetComment.content
                          : targetObj.content,
                        limit: 100
                      })})`
                }`
              }}
            />
            :{' '}
            <ContentLink
              type="comment"
              content={{
                id: actionObj.id,
                title: `"${truncateText({
                  text: actionObj.content,
                  limit: 100
                })}"`
              }}
              style={{ color: Color.green() }}
            />
          </>
        );
        break;
      case 'subject':
        notificationMessage = (
          <>
            added a
            <ContentLink
              type="subject"
              content={{
                id: actionObj.id,
                title: `subject (${truncateText({
                  text: actionObj.content,
                  limit: 100
                })})`
              }}
              style={{ color: Color.green() }}
            />{' '}
            to your
            <ContentLink
              type={targetObj.type}
              content={{
                id: targetObj.id,
                title: `${
                  targetObj.type === 'url' ? 'link' : targetObj.type
                } (${truncateText({ text: targetObj.content, limit: 100 })})`
              }}
            />
          </>
        );
        break;
      default:
        notificationMessage = (
          There was an error - report to Mikey!
        );
        break;
    }
    return (
      <ErrorBoundary>

          <UsernameText user={user} color={Color.blue()} />

          {notificationMessage}

        {timeSince(timeStamp)}
      </ErrorBoundary>
    );
  }
}

export default connect(state => ({
  myId: state.UserReducer.userId
}))(NotiItem);
<|endoftext|>


<commit_before>var t = require('tap');
var requireTransform = require('../lib/require-transform');

var src = "var foo = require('bar');";

var out = requireTransform(src, false);
t.match(out, /__mendel_require__\(/);
t.notMatch(out, /\brequire\(/);

out = requireTransform(src, true);
t.match(out, /__mendel_require__\(/);
t.notMatch(out, /\brequire\(/);

var wrapper = requireTransform.wrapper;
t.equal(out.indexOf(wrapper[0]), 0, 'wrapper prelude pos');
t.equal(out.indexOf(wrapper[1]), out.length - wrapper[1].length, 'wrapper epilogue pos');
<commit_msg>Refactored regex.<commit_after>var t = require('tap');
var requireTransform = require('../lib/require-transform');

var mendelRequireRegexp = /__mendel_require__\(/;
var requireRegexp = /\brequire\(/;

var src = "var foo = require('bar');";

var out = requireTransform(src, false);
t.match(out, mendelRequireRegexp);
t.notMatch(out, requireRegexp);

out = requireTransform(src, true);
t.match(out, mendelRequireRegexp);
t.notMatch(out, requireRegexp);

var wrapper = requireTransform.wrapper;
t.equal(out.indexOf(wrapper[0]), 0, 'wrapper prelude pos');
t.equal(out.indexOf(wrapper[1]), out.length - wrapper[1].length, 'wrapper epilogue pos');
<|endoftext|>