<commit_before>/**
 * Copyright (C) 2013 Jan Pipek (jan.pipek@gmail.com)
 *
 * MIT license (see LICENSE.txt)
 */
TableModel = (function($) {
    var TableModel = function($table, options) {
        this.getTable = function() {
            return $table;
        };
        this.options = $.extend({}, defaultOptions, options);

        this.cellListeners = [];
        this.rowListeners = [];
        this.columnListeners = [];

        this.options.wireTableEvents.call(this);
        $table.data("table-model", this);
    };

    var addListener = function(type, handler) {
        this[type + "Listeners"].push(handler);
    };

    var callListeners = function(type, arguments) {
        var listeners = this[type + "Listeners"];
        for (var index in listeners) {
            listeners[index].apply(null, arguments);
        }
    };

    var getRows = function($table) {
        if ($table.has("thead, tbody")) {
            var trs = $table.children("thead:not(.tableFloatingHeader)").children("tr");
            trs = trs.add($table.children("tbody").children("tr"));
            trs = trs.add($table.children("tfoot").children("tr"));
            return trs;
        } else {
            return $table.children("tr");
        }
    };

    var staticWireTableEvents = function() {
        var $table = this.getTable();
        var tableModel = this;

        console.log($table);

        var rows = getRows($table);
        rows.each(function(rowIndex) {
            var $row = $(this);
            $row.addClass("row-" + rowIndex);
            $row.data("row", rowIndex);
            $row.children("td, th").each(function(columnIndex) {
                var $cell = $(this);
                $cell.addClass("column-" + columnIndex);
                $cell.data("row", rowIndex);
                $cell.data("column", columnIndex);
            });
        });

        $table.on("change", "td input, td textarea", function() {
            var $input = $(this);
            var $cell = $input.closest("td, th");
            var row = $cell.data("row");
            var column = $cell.data("column");
            $cell.data("value", $input.val());
            onCellValueChange.call(tableModel, row, column);
        });

        if (this.options.recalculateOnType) {
            $table.on("keyup", "td input, td textarea", function() {
                var $input = $(this);
                var $cell = $input.closest("td, th");
                if ($input.val() != $cell.data("value")) {
                    $input.trigger("change");
                }
            });
        }
    };

    var staticFindCell = function(row, column) {
        return this.getTable().find(".row-" + row).find(".column-" + column);
    };

    var defaultReadCellValue = function(cell) {
        if (cell.data("value") !== undefined) {
            return cell.data("value");
        }
        if (cell.has("input").length) {
            return cell.children("input").val();
        } else if (cell.has("textarea").length) {
            return cell.children("textarea").text();
        } else {
            return cell.text();
        }
    };

    var defaultSetCellValue = function(cell, value) {
        cell.data("value", value);

        // Use precision if set
        var precision = undefined
        if (this.options.precision !== undefined) {
            var precision = this.options.precision;
        }
        if (cell.data("precision") !== undefined) {
            var precision = cell.data("precision")
        }
        if (precision !== undefined) {
            value = value.toFixed(precision);
        }

        if (cell.has("input").length) {
            cell.children("input").val(value);
        } else if (cell.has("textarea").length) {
            cell.children("textarea").text(value);
        } else {
            cell.text(value);
        }
    };

    var onCellValueChange = function(row, column) {
        callListeners.call(this, "cell", [row, column]);
        callListeners.call(this, "row", [row]);
        callListeners.call(this, "column", [column]);
    };

    var bindExpression = function(tableModel, cell, row, column, expression) {
        var selection = expression.sourceSelection
        var applyHandler = function() {
            var value = evaluate(tableModel, expression);
            tableModel.options.setCellValue.call(tableModel, cell, value);
            onCellValueChange.call(tableModel, row, column);
        };
        tableModel.onCellChange(function(row, column) {
            if (expression.sourceSelection.includes(row, column)) {
                applyHandler();
            }
        });
        applyHandler();
    };

    var defaultOptions = {
        findCell : staticFindCell,

        wireTableEvents : staticWireTableEvents,

        readCellValue : defaultReadCellValue,

        setCellValue : defaultSetCellValue,

        recalculateOnType : false
    };

    TableModel.prototype = {
        /**
         * Returns the current value of the cell.
         *
         * It does not return underlying expression, just the value.
         */
        get : function(row, column) {
            var cell = this.getCell.call(this, row, column);
            var value = this.options.readCellValue.call(this, cell);
            return value;
        },

        /**
         * Return the cell indexed by row and column.
         */
        getCell : function(row, column) {
            return this.options.findCell.call(this, row, column);
        },

        /**
         * Assign a value or a dynamic expression to the cell.
         */
        set : function(row, column, value) {
            var cell = this.options.findCell.call(this, row, column);

            if (isExpression(value)) {
                bindExpression(this, cell, row, column, value);
                // TODO: Before returning true, examine if the value
                //   really changed.
                return true;
            } else {
                var originalValue = this.options.readCellValue.call(this, cell);
                if (value !== originalValue) {
                    this.options.setCellValue.call(this, cell, value);
                    onCellValueChange.call(this, row, column);
                    return true;
                } else {
                    return false;
                }
            }
        },

        /**
         * Adds a listener that calls handler whenever expression
         * value changes.
         *
         * @param expression Expression based on cell values
         *     (accepts expressions as complicated as cells themselves)
         * @param handler Function to be called. It should accept new value
         *     of the expression as parameter.
         */
        listen : function(expression, handler) {
            var tableModel = this;
            this.onCellChange(function(row, column) {
                if (expression.sourceSelection.includes(row, column)) {
                    var value = evaluate(tableModel, expression);
                    handler(value);
                }
            });
        },

        /**
          * Register handler, that gets called whenever cell value changes.
          *
          * The handler receives two parameters: row and column
          */
        onCellChange : function(listener) {
            addListener.call(this, "cell", listener);
        },

        /**
          * Register handler, that gets called whenever a cell in a column changes.
          *
          * The handler receives one parameter: column
          */
        onColumnChange : function(listener) {
            addListener.call(this, "column", listener);
        },

        /**
          * Register handler, that gets called whenever a cell in a row changes.
          *
          * The handler receives one paramater: row
          */
        onRowChange : function(listener) {
            addListener.call(this, "row", listener);
        }
    };

    /**
     * Check if argument behaves (duck-typing) like a selection.
     */
    var isSelection = function(arg) {
        return (arg.all && arg.includes);
    };

    /**
      * Interpret argument as a selection.
      *
      * Proper selections are left as-is.
      * Arrays of pairs and a single pair are converted
      *   to a collection of cells.
      * Otherwise exception is thrown.
      */
    var asSelection = function(arg) {
        if (isSelection(arg)) {
            return arg;
        } else if ($.isArray(arg)) {
            var array = arg;

            if ((arg.length == 2) && !$.isArray(arg[0])) {
                array = [ arg ];
            }

            var selection = {
                includes : function(row, column) {
                    for (var i in array) {
                        if (array[i][0] == row && array[i][1] == column) {
                            return true;
                        }
                    }
                    return false;
                },
                all : function() {
                    return array;
                },
                empty : function() {
                    return !array.length;
                }
            };
            return selection;
        } else {
            throw "asSelection: argument cannot be interpreted as selection";
        }
    };

    /**
      * Namespace of all cell selections.
      *
      * Selections are collections of cells that:
      * - know if they are empty (method empty)
      * - know if they include a specific cell (method includes)
      * - know all their cells (method all)
      *
      * Any two (or more collections) can be combined
      *     using Table.model.select.combine(selections...)
      *
      */
    TableModel.select = {
        /**
         * Empty selection.
         */
        empty : function() {
            var selection = {
                includes : function() {
                    return false;
                },
                all : function() {
                    return [];
                },
                empty : function() {
                    return true;
                }
            }
        },

        /**
         * Selection consisting of a single cell.
         */
        cell : function(row, column) {
            return asSelection([row, column]);
        },

        /**
         * Selection consisting of a rectangular area.
         *
         * The coordinates are inclusive,
         *     e.g. right == left if a single column is selected.
         */
        range : function(top, left, bottom, right) {
            var selection = {
                includes : function(row, column) {
                    result = (
                        row >= top &&
                        row <= bottom &&
                        column >= left &&
                        column <= right
                    );
                    return result;
                },
                all : function() {
                    var indices = [];
                    for (i = top; i <= bottom; i++) {
                        for (j = left; j <= right; j++) {
                            indices.push([i, j]);
                        }
                    }
                    return indices;
                },
                empty : function() {
                    return !((bottom - top) >= 0 && (right - left) >= 0);
                }
            };
            return selection;
        },

        /**
         * A combination of selections.
         *
         * Any number of selections or cell [row,column] pairs
         *     can be supplied.
         */
        combine : function() {
            var selections = [];

            if (arguments.length == 1 && $.isArray(arguments[0])) {
                var args = arguments[0];
            } else {
                var args = arguments;
            }

            $.each(args, function(index, argument) {
                var selection = asSelection(argument);
                if (!selection.empty()) {
                    selections.push(selection);
                }
            });

            var selection = {
                includes : function(row, column) {
                    for (var index in selections) {
                        if (selections[index].includes(row, column)) {
                            return true;
                        }
                    }
                    return false;
                },
                all : function() {
                    var indices = [];
                    for (var index in selections) {
                        indices = indices.concat(selections[index].all())
                    }
                    return indices;
                },
                empty : function() {
                    return !selections.length;
                }
            }
            return selection;
        }
    };

    /**
     * Check (duck-typing) if argument can be evaluated as expression.
     */
    var isExpression = function(arg) {
        return !!arg.evaluate;
    }

    /**
      * Evaluate an expression.
      *
      * @param tableModel Model has to be supplied because expressions
      *     don't store information about able in themselves.
      * @param expression Evaluate an expression starting with its
      *     arguments that can be expressions as well.
      */
    var evaluate = function(tableModel, expression) {
        var values = [];
        $.each (expression.args, function(index, arg) {
            var value;
            if (isExpression(arg)) {
                value = evaluate(tableModel, arg);
            } else if (isSelection(arg)) {
                value = [];
                $.each(arg.all(), function(i, cellIndex) {
                    value.push(tableModel.get(cellIndex[0], cellIndex[1]));
                });
            } else {
                value = arg;
            }

            if (expression.flatten && $.isArray(value)) {
                values = values.concat(value);
            } else {
                values.push(value);
            }
        });
        return expression.evaluate(values);
    }

    /**
     * Find a selection of all cells the expression depends on.
     */
    var findExpressionSourceSelection = function(expression) {
        var selections = [];
        $.each(expression.args, function(index, arg) {
            if (isSelection(arg)) {
                // console.log(arg.all());
                selections.push(arg);
            } else if (isExpression(arg)) {
                var selection = arg.sourceSelection;
                selections.push(selection);
            }
        });
        if (selections.length == 0) return TableModel.select.empty();
        if (selections.length == 1) return selections[0];
        var combination = TableModel.select.combine(selections);
        return combination;
    }

    /**
     * Try to convert argument to number.
     *
     * Helper function used in expressions.
     */
    var asNumber = function(value) {
        var number = parseFloat(value);
        if (number == value) {
            return number;
        } else {
            return 0;
        }
    };

    /**
      * Default options for expression constructor.
      *
      * They are overridden using options argument of the constructor.
      */
    var defaultExpressionOptions = {
        flatten: false
    }

    /**
      * Base constructor of all expressions.
      *
      * @param args - all arguments passed to
      * @param evaluateFunction
      * @param options - various options
      *
      * Options (see defaultExpression options):
      *   - flatten - when evaluating arguments, make a single array
      *     out of all argument values (default: false)
      *
      * Exported as TableModel.expression.Base.
      */
    var Expression = function(args, evaluateFunction, options) {
        this.args = args;
        this.sourceSelection = findExpressionSourceSelection(this);
        this.evaluate = evaluateFunction;
        this.options = $.extend({}, defaultExpressionOptions, options);
        $.extend(this, options);
    };

    /**
      * Namespace with all pre-defined expressions.
      *
      * Also shortcut as TableModel.e.
      * If you want to implement a custom expression, use TableModel.expression.Base.
      */
    TableModel.expression = {
        Base : Expression,

        sum : function() {
            return new Expression(arguments, function(values) {
                var result = 0;
                $.each(values, function(index, value) {
                    result += asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        product : function() {
            return new Expression(arguments, function(values) {
                var result = 1;
                $.each(values, function(index, value) {
                    result *= asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        countIf : function(selection, condition) {
            return new Expression([selection], function(values) {
                var count = 0;
                $.each(values, function(index, item) {
                    if (condition instanceof RegExp && item.match(condition)) {
                        count++;
                    } else if (condition == item) {
                        count++;
                    }
                });
                return count;
            }, {
                flatten: true
            });
        }
    }

    // Shortcuts
    TableModel.s = TableModel.select;
    TableModel.e = TableModel.expression;

    // Make it a jQuery plugin
    $.fn.tableModel = function(options) {
        $.each(this, function() {
            new TableModel($(this), options);
        });
        return this;
    }
    $.extend($.fn.tableModel, TableModel);

    return TableModel;
})(jQuery);
<commit_msg>set expression.tableModel in evaluate()<commit_after>/**
 * Copyright (C) 2013 Jan Pipek (jan.pipek@gmail.com)
 *
 * MIT license (see LICENSE.txt)
 */
TableModel = (function($) {
    var TableModel = function($table, options) {
        this.getTable = function() {
            return $table;
        };
        this.options = $.extend({}, defaultOptions, options);

        this.cellListeners = [];
        this.rowListeners = [];
        this.columnListeners = [];

        this.options.wireTableEvents.call(this);
        $table.data("table-model", this);
    };

    var addListener = function(type, handler) {
        this[type + "Listeners"].push(handler);
    };

    var callListeners = function(type, arguments) {
        var listeners = this[type + "Listeners"];
        for (var index in listeners) {
            listeners[index].apply(null, arguments);
        }
    };

    var getRows = function($table) {
        if ($table.has("thead, tbody")) {
            var trs = $table.children("thead:not(.tableFloatingHeader)").children("tr");
            trs = trs.add($table.children("tbody").children("tr"));
            trs = trs.add($table.children("tfoot").children("tr"));
            return trs;
        } else {
            return $table.children("tr");
        }
    };

    var staticWireTableEvents = function() {
        var $table = this.getTable();
        var tableModel = this;

        console.log($table);

        var rows = getRows($table);
        rows.each(function(rowIndex) {
            var $row = $(this);
            $row.addClass("row-" + rowIndex);
            $row.data("row", rowIndex);
            $row.children("td, th").each(function(columnIndex) {
                var $cell = $(this);
                $cell.addClass("column-" + columnIndex);
                $cell.data("row", rowIndex);
                $cell.data("column", columnIndex);
            });
        });

        $table.on("change", "td input, td textarea", function() {
            var $input = $(this);
            var $cell = $input.closest("td, th");
            var row = $cell.data("row");
            var column = $cell.data("column");
            $cell.data("value", $input.val());
            onCellValueChange.call(tableModel, row, column);
        });

        if (this.options.recalculateOnType) {
            $table.on("keyup", "td input, td textarea", function() {
                var $input = $(this);
                var $cell = $input.closest("td, th");
                if ($input.val() != $cell.data("value")) {
                    $input.trigger("change");
                }
            });
        }
    };

    var staticFindCell = function(row, column) {
        return this.getTable().find(".row-" + row).find(".column-" + column);
    };

    var defaultReadCellValue = function(cell) {
        if (cell.data("value") !== undefined) {
            return cell.data("value");
        }
        if (cell.has("input").length) {
            return cell.children("input").val();
        } else if (cell.has("textarea").length) {
            return cell.children("textarea").text();
        } else {
            return cell.text();
        }
    };

    var defaultSetCellValue = function(cell, value) {
        cell.data("value", value);

        // Use precision if set
        var precision = undefined
        if (this.options.precision !== undefined) {
            var precision = this.options.precision;
        }
        if (cell.data("precision") !== undefined) {
            var precision = cell.data("precision")
        }
        if (precision !== undefined) {
            value = value.toFixed(precision);
        }

        if (cell.has("input").length) {
            cell.children("input").val(value);
        } else if (cell.has("textarea").length) {
            cell.children("textarea").text(value);
        } else {
            cell.text(value);
        }
    };

    var onCellValueChange = function(row, column) {
        callListeners.call(this, "cell", [row, column]);
        callListeners.call(this, "row", [row]);
        callListeners.call(this, "column", [column]);
    };

    var bindExpression = function(tableModel, cell, row, column, expression) {
        var selection = expression.sourceSelection
        var applyHandler = function() {
            var value = evaluate(tableModel, expression);
            tableModel.options.setCellValue.call(tableModel, cell, value);
            onCellValueChange.call(tableModel, row, column);
        };
        tableModel.onCellChange(function(row, column) {
            if (expression.sourceSelection.includes(row, column)) {
                applyHandler();
            }
        });
        applyHandler();
    };

    var defaultOptions = {
        findCell : staticFindCell,

        wireTableEvents : staticWireTableEvents,

        readCellValue : defaultReadCellValue,

        setCellValue : defaultSetCellValue,

        recalculateOnType : false
    };

    TableModel.prototype = {
        /**
         * Returns the current value of the cell.
         *
         * It does not return underlying expression, just the value.
         */
        get : function(row, column) {
            var cell = this.getCell.call(this, row, column);
            var value = this.options.readCellValue.call(this, cell);
            return value;
        },

        /**
         * Return the cell indexed by row and column.
         */
        getCell : function(row, column) {
            return this.options.findCell.call(this, row, column);
        },

        /**
         * Assign a value or a dynamic expression to the cell.
         */
        set : function(row, column, value) {
            var cell = this.options.findCell.call(this, row, column);

            if (isExpression(value)) {
                bindExpression(this, cell, row, column, value);
                // TODO: Before returning true, examine if the value
                //   really changed.
                return true;
            } else {
                var originalValue = this.options.readCellValue.call(this, cell);
                if (value !== originalValue) {
                    this.options.setCellValue.call(this, cell, value);
                    onCellValueChange.call(this, row, column);
                    return true;
                } else {
                    return false;
                }
            }
        },

        /**
         * Adds a listener that calls handler whenever expression
         * value changes.
         *
         * @param expression Expression based on cell values
         *     (accepts expressions as complicated as cells themselves)
         * @param handler Function to be called. It should accept new value
         *     of the expression as parameter.
         */
        listen : function(expression, handler) {
            var tableModel = this;
            this.onCellChange(function(row, column) {
                if (expression.sourceSelection.includes(row, column)) {
                    var value = evaluate(tableModel, expression);
                    handler(value);
                }
            });
        },

        /**
          * Register handler, that gets called whenever cell value changes.
          *
          * The handler receives two parameters: row and column
          */
        onCellChange : function(listener) {
            addListener.call(this, "cell", listener);
        },

        /**
          * Register handler, that gets called whenever a cell in a column changes.
          *
          * The handler receives one parameter: column
          */
        onColumnChange : function(listener) {
            addListener.call(this, "column", listener);
        },

        /**
          * Register handler, that gets called whenever a cell in a row changes.
          *
          * The handler receives one paramater: row
          */
        onRowChange : function(listener) {
            addListener.call(this, "row", listener);
        }
    };

    /**
     * Check if argument behaves (duck-typing) like a selection.
     */
    var isSelection = function(arg) {
        return (arg.all && arg.includes);
    };

    /**
      * Interpret argument as a selection.
      *
      * Proper selections are left as-is.
      * Arrays of pairs and a single pair are converted
      *   to a collection of cells.
      * Otherwise exception is thrown.
      */
    var asSelection = function(arg) {
        if (isSelection(arg)) {
            return arg;
        } else if ($.isArray(arg)) {
            var array = arg;

            if ((arg.length == 2) && !$.isArray(arg[0])) {
                array = [ arg ];
            }

            var selection = {
                includes : function(row, column) {
                    for (var i in array) {
                        if (array[i][0] == row && array[i][1] == column) {
                            return true;
                        }
                    }
                    return false;
                },
                all : function() {
                    return array;
                },
                empty : function() {
                    return !array.length;
                }
            };
            return selection;
        } else {
            throw "asSelection: argument cannot be interpreted as selection";
        }
    };

    /**
      * Namespace of all cell selections.
      *
      * Selections are collections of cells that:
      * - know if they are empty (method empty)
      * - know if they include a specific cell (method includes)
      * - know all their cells (method all)
      *
      * Any two (or more collections) can be combined
      *     using Table.model.select.combine(selections...)
      *
      */
    TableModel.select = {
        /**
         * Empty selection.
         */
        empty : function() {
            var selection = {
                includes : function() {
                    return false;
                },
                all : function() {
                    return [];
                },
                empty : function() {
                    return true;
                }
            }
        },

        /**
         * Selection consisting of a single cell.
         */
        cell : function(row, column) {
            return asSelection([row, column]);
        },

        /**
         * Selection consisting of a rectangular area.
         *
         * The coordinates are inclusive,
         *     e.g. right == left if a single column is selected.
         */
        range : function(top, left, bottom, right) {
            var selection = {
                includes : function(row, column) {
                    result = (
                        row >= top &&
                        row <= bottom &&
                        column >= left &&
                        column <= right
                    );
                    return result;
                },
                all : function() {
                    var indices = [];
                    for (i = top; i <= bottom; i++) {
                        for (j = left; j <= right; j++) {
                            indices.push([i, j]);
                        }
                    }
                    return indices;
                },
                empty : function() {
                    return !((bottom - top) >= 0 && (right - left) >= 0);
                }
            };
            return selection;
        },

        /**
         * A combination of selections.
         *
         * Any number of selections or cell [row,column] pairs
         *     can be supplied.
         */
        combine : function() {
            var selections = [];

            if (arguments.length == 1 && $.isArray(arguments[0])) {
                var args = arguments[0];
            } else {
                var args = arguments;
            }

            $.each(args, function(index, argument) {
                var selection = asSelection(argument);
                if (!selection.empty()) {
                    selections.push(selection);
                }
            });

            var selection = {
                includes : function(row, column) {
                    for (var index in selections) {
                        if (selections[index].includes(row, column)) {
                            return true;
                        }
                    }
                    return false;
                },
                all : function() {
                    var indices = [];
                    for (var index in selections) {
                        indices = indices.concat(selections[index].all())
                    }
                    return indices;
                },
                empty : function() {
                    return !selections.length;
                }
            }
            return selection;
        }
    };

    /**
     * Check (duck-typing) if argument can be evaluated as expression.
     */
    var isExpression = function(arg) {
        return !!arg.evaluate;
    }

    /**
      * Evaluate an expression.
      *
      * @param tableModel Model has to be supplied because expressions
      *     don't store information about able in themselves.
      * @param expression Evaluate an expression starting with its
      *     arguments that can be expressions as well.
      *
      * It also sets expression.tableModel, so that tableModel
      * can be use in the expression handler.
      */
    var evaluate = function(tableModel, expression) {
        expression.tableModel = tableModel;

        var values = [];
        $.each (expression.args, function(index, arg) {
            var value;
            if (isExpression(arg)) {
                value = evaluate(tableModel, arg);
            } else if (isSelection(arg)) {
                value = [];
                $.each(arg.all(), function(i, cellIndex) {
                    value.push(tableModel.get(cellIndex[0], cellIndex[1]));
                });
            } else {
                value = arg;
            }

            if (expression.flatten && $.isArray(value)) {
                values = values.concat(value);
            } else {
                values.push(value);
            }
        });
        return expression.evaluate(values);
    }

    /**
     * Find a selection of all cells the expression depends on.
     */
    var findExpressionSourceSelection = function(expression) {
        var selections = [];
        $.each(expression.args, function(index, arg) {
            if (isSelection(arg)) {
                // console.log(arg.all());
                selections.push(arg);
            } else if (isExpression(arg)) {
                var selection = arg.sourceSelection;
                selections.push(selection);
            }
        });
        if (selections.length == 0) return TableModel.select.empty();
        if (selections.length == 1) return selections[0];
        var combination = TableModel.select.combine(selections);
        return combination;
    }

    /**
     * Try to convert argument to number.
     *
     * Helper function used in expressions.
     */
    var asNumber = function(value) {
        var number = parseFloat(value);
        if (number == value) {
            return number;
        } else {
            return 0;
        }
    };

    /**
      * Default options for expression constructor.
      *
      * They are overridden using options argument of the constructor.
      */
    var defaultExpressionOptions = {
        flatten: false
    }

    /**
      * Base constructor of all expressions.
      *
      * @param args - all arguments passed to the expression
      * @param evaluateFunction
      * @param options - various options
      *
      * Options (see defaultExpression options):
      *   - flatten - when evaluating arguments, make a single array
      *     out of all argument values (default: false)
      *
      * Exported as TableModel.expression.Base.
      */
    var Expression = function(args, evaluateFunction, options) {
        this.args = args;
        this.sourceSelection = findExpressionSourceSelection(this);
        this.evaluate = evaluateFunction;
        this.options = $.extend({}, defaultExpressionOptions, options);
        $.extend(this, options);
    };

    /**
      * Namespace with all pre-defined expressions.
      *
      * Also shortcut as TableModel.e.
      * If you want to implement a custom expression, use TableModel.expression.Base.
      */
    TableModel.expression = {
        Base : Expression,

        /**
          * Sum of a selection or values.
          */
        sum : function() {
            return new Expression(arguments, function(values) {
                var result = 0;
                $.each(values, function(index, value) {
                    result += asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        product : function() {
            return new Expression(arguments, function(values) {
                var result = 1;
                $.each(values, function(index, value) {
                    result *= asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        countIf : function(selection, condition) {
            return new Expression([selection], function(values) {
                var count = 0;
                $.each(values, function(index, item) {
                    if (condition instanceof RegExp && item.match(condition)) {
                        count++;
                    } else if (condition == item) {
                        count++;
                    }
                });
                return count;
            }, {
                flatten: true
            });
        }
    }

    // Shortcuts
    TableModel.s = TableModel.select;
    TableModel.e = TableModel.expression;

    // Make it a jQuery plugin
    $.fn.tableModel = function(options) {
        $.each(this, function() {
            new TableModel($(this), options);
        });
        return this;
    }
    $.extend($.fn.tableModel, TableModel);

    return TableModel;
})(jQuery);
<|endoftext|>


<commit_before>/**
 * Copyright (C) 2013 Jan Pipek (jan.pipek@gmail.com)
 *
 * MIT license (see LICENSE.txt)
 */
TableModel = (function($) {
    var TableModel = function($table, options) {
        this.getTable = function() {
            return $table;
        };
        this.options = $.extend({}, defaultOptions, options);

        this.cellListeners = [];
        this.rowListeners = [];
        this.columnListeners = [];

        this.options.wireTableEvents.call(this);
        $table.data("table-model", this);
    };

    var addListener = function(type, handler) {
        this[type + "Listeners"].push(handler);
    };

    var callListeners = function(type, arguments) {
        var listeners = this[type + "Listeners"];
        for (var index in listeners) {
            listeners[index].apply(null, arguments);
        }
    };

    var getRows = function($table) {
        if ($table.has("thead, tbody")) {
            var trs = $table.children("thead:not(.tableFloatingHeader)").children("tr");
            trs = trs.add($table.children("tbody").children("tr"));
            trs = trs.add($table.children("tfoot").children("tr"));
            return trs;
        } else {
            return $table.children("tr");
        }
    };

    var staticWireTableEvents = function() {
        var $table = this.getTable();
        var tableModel = this;

        console.log($table);

        var rows = getRows($table);
        rows.each(function(rowIndex) {
            var $row = $(this);
            $row.addClass("row-" + rowIndex);
            $row.data("row", rowIndex);
            $row.children("td, th").each(function(columnIndex) {
                var $cell = $(this);
                $cell.addClass("column-" + columnIndex);
                $cell.data("row", rowIndex);
                $cell.data("column", columnIndex);
            });
        });

        $table.on("change", "td input, td textarea", function() {
            var $input = $(this);
            var $cell = $input.closest("td, th");
            var row = $cell.data("row");
            var column = $cell.data("column");
            $cell.data("value", $input.val());
            onCellValueChange.call(tableModel, row, column);
        });

        if (this.options.recalculateOnType) {
            $table.on("keyup", "td input, td textarea", function() {
                var $input = $(this);
                var $cell = $input.closest("td, th");
                if ($input.val() != $cell.data("value")) {
                    $input.trigger("change");
                }
            });
        }
    };

    var staticFindCell = function(row, column) {
        return this.getTable().find(".row-" + row).find(".column-" + column);
    };

    var defaultReadCellValue = function(cell) {
        if (cell.data("value") !== undefined) {
            return cell.data("value");
        }
        if (cell.has("input").length) {
            return cell.children("input").val();
        } else if (cell.has("textarea").length) {
            return cell.children("textarea").text();
        } else {
            return cell.text();
        }
    };

    var defaultSetCellValue = function(cell, value) {
        cell.data("value", value);

        // Use precision if set
        var precision = undefined
        if (this.options.precision !== undefined) {
            var precision = this.options.precision;
        }
        if (cell.data("precision") !== undefined) {
            var precision = cell.data("precision")
        }
        if (precision !== undefined) {
            value = value.toFixed(precision);
        }

        if (cell.has("input").length) {
            cell.children("input").val(value);
        } else if (cell.has("textarea").length) {
            cell.children("textarea").text(value);
        } else {
            cell.text(value);
        }
    };

    var onCellValueChange = function(row, column) {
        callListeners.call(this, "cell", [row, column]);
        callListeners.call(this, "row", [row]);
        callListeners.call(this, "column", [column]);
    };

    var bindExpression = function(tableModel, cell, row, column, expression) {
        var selection = expression.sourceSelection
        var applyHandler = function() {
            var value = evaluate(tableModel, expression);
            tableModel.options.setCellValue.call(tableModel, cell, value);
            onCellValueChange.call(tableModel, row, column);
        };
        tableModel.onCellChange(function(row, column) {
            if (expression.sourceSelection.includes(row, column)) {
                applyHandler();
            }
        });
        applyHandler();
    };

    var defaultOptions = {
        findCell : staticFindCell,

        wireTableEvents : staticWireTableEvents,

        readCellValue : defaultReadCellValue,

        setCellValue : defaultSetCellValue,

        recalculateOnType : false
    };

    TableModel.prototype = {
        /**
         * Returns the current value of the cell.
         *
         * It does not return underlying expression, just the value.
         */
        get : function(row, column) {
            var cell = this.getCell.call(this, row, column);
            var value = this.options.readCellValue.call(this, cell);
            return value;
        },

        /**
         * Return the cell indexed by row and column.
         */
        getCell : function(row, column) {
            return this.options.findCell.call(this, row, column);
        },

        /**
         * Assign a value or a dynamic expression to the cell.
         */
        set : function(row, column, value) {
            var cell = this.options.findCell.call(this, row, column);

            if (isExpression(value)) {
                bindExpression(this, cell, row, column, value);
                // TODO: Before returning true, examine if the value
                //   really changed.
                return true;
            } else {
                var originalValue = this.options.readCellValue.call(this, cell);
                if (value !== originalValue) {
                    this.options.setCellValue.call(this, cell, value);
                    onCellValueChange.call(this, row, column);
                    return true;
                } else {
                    return false;
                }
            }
        },

        /**
         * Adds a listener that calls handler whenever expression
         * value changes.
         *
         * @param expression Expression based on cell values
         *     (accepts expressions as complicated as cells themselves)
         * @param handler Function to be called. It should accept new value
         *     of the expression as parameter.
         */
        listen : function(expression, handler) {
            var tableModel = this;
            this.onCellChange(function(row, column) {
                if (expression.sourceSelection.includes(row, column)) {
                    var value = evaluate(tableModel, expression);
                    handler(value);
                }
            });
        },

        /**
          * Register handler, that gets called whenever cell value changes.
          *
          * The handler receives two parameters: row and column
          */
        onCellChange : function(listener) {
            addListener.call(this, "cell", listener);
        },

        /**
          * Register handler, that gets called whenever a cell in a column changes.
          *
          * The handler receives one parameter: column
          */
        onColumnChange : function(listener) {
            addListener.call(this, "column", listener);
        },

        /**
          * Register handler, that gets called whenever a cell in a row changes.
          *
          * The handler receives one paramater: row
          */
        onRowChange : function(listener) {
            addListener.call(this, "row", listener);
        }
    };

    /**
     * Check if argument behaves (duck-typing) like a selection.
     */
    var isSelection = function(arg) {
        return (arg.all && arg.includes);
    };

    /**
      * Interpret argument as a selection.
      *
      * Proper selections are left as-is.
      * Arrays of pairs and a single pair are converted
      *   to a collection of cells.
      * Otherwise exception is thrown.
      */
    var asSelection = function(arg) {
        if (isSelection(arg)) {
            return arg;
        } else if ($.isArray(arg)) {
            var array = arg;

            if ((arg.length == 2) && !$.isArray(arg[0])) {
                array = [ arg ];
            }

            var selection = {
                includes : function(row, column) {
                    for (var i in array) {
                        if (array[i][0] == row && array[i][1] == column) {
                            return true;
                        }
                    }
                    return false;
                },
                all : function() {
                    return array;
                },
                empty : function() {
                    return !array.length;
                }
            };
            return selection;
        } else {
            throw "asSelection: argument cannot be interpreted as selection";
        }
    };

    /**
      * Namespace of all cell selections.
      *
      * Selections are collections of cells that:
      * - know if they are empty (method empty)
      * - know if they include a specific cell (method includes)
      * - know all their cells (method all)
      *
      * Any two (or more collections) can be combined
      *     using Table.model.select.combine(selections...)
      *
      */
    TableModel.select = {
        /**
         * Empty selection.
         */
        empty : function() {
            var selection = {
                includes : function() {
                    return false;
                },
                all : function() {
                    return [];
                },
                empty : function() {
                    return true;
                }
            }
        },

        /**
         * Selection consisting of a single cell.
         */
        cell : function(row, column) {
            return asSelection([row, column]);
        },

        /**
         * Selection consisting of a rectangular area.
         *
         * The coordinates are inclusive,
         *     e.g. right == left if a single column is selected.
         */
        range : function(top, left, bottom, right) {
            var selection = {
                includes : function(row, column) {
                    result = (
                        row >= top &&
                        row <= bottom &&
                        column >= left &&
                        column <= right
                    );
                    return result;
                },
                all : function() {
                    var indices = [];
                    for (i = top; i <= bottom; i++) {
                        for (j = left; j <= right; j++) {
                            indices.push([i, j]);
                        }
                    }
                    return indices;
                },
                empty : function() {
                    return !((bottom - top) >= 0 && (right - left) >= 0);
                }
            };
            return selection;
        },

        /**
         * A combination of selections.
         *
         * Any number of selections or cell [row,column] pairs
         *     can be supplied.
         */
        combine : function() {
            var selections = [];

            if (arguments.length == 1 && $.isArray(arguments[0])) {
                var args = arguments[0];
            } else {
                var args = arguments;
            }

            $.each(args, function(index, argument) {
                var selection = asSelection(argument);
                if (!selection.empty()) {
                    selections.push(selection);
                }
            });

            var selection = {
                includes : function(row, column) {
                    for (var index in selections) {
                        if (selections[index].includes(row, column)) {
                            return true;
                        }
                    }
                    return false;
                },
                all : function() {
                    var indices = [];
                    for (var index in selections) {
                        indices = indices.concat(selections[index].all())
                    }
                    return indices;
                },
                empty : function() {
                    return !selections.length;
                }
            }
            return selection;
        }
    };

    /**
     * Check (duck-typing) if argument can be evaluated as expression.
     */
    var isExpression = function(arg) {
        return !!arg.evaluate;
    }

    /**
      * Evaluate an expression.
      *
      * @param tableModel Model has to be supplied because expressions
      *     don't store information about able in themselves.
      * @param expression Evaluate an expression starting with its
      *     arguments that can be expressions as well.
      *
      * It also sets expression.tableModel, so that tableModel
      * can be use in the expression handler.
      */
    var evaluate = function(tableModel, expression) {
        expression.tableModel = tableModel;

        var values = [];
        $.each (expression.args, function(index, arg) {
            var value;
            if (isExpression(arg)) {
                value = evaluate(tableModel, arg);
            } else if (isSelection(arg)) {
                value = [];
                $.each(arg.all(), function(i, cellIndex) {
                    value.push(tableModel.get(cellIndex[0], cellIndex[1]));
                });
            } else {
                value = arg;
            }

            if (expression.flatten && $.isArray(value)) {
                values = values.concat(value);
            } else {
                values.push(value);
            }
        });
        return expression.evaluate(values);
    }

    /**
     * Find a selection of all cells the expression depends on.
     */
    var findExpressionSourceSelection = function(expression) {
        var selections = [];
        $.each(expression.args, function(index, arg) {
            if (isSelection(arg)) {
                // console.log(arg.all());
                selections.push(arg);
            } else if (isExpression(arg)) {
                var selection = arg.sourceSelection;
                selections.push(selection);
            }
        });
        if (selections.length == 0) return TableModel.select.empty();
        if (selections.length == 1) return selections[0];
        var combination = TableModel.select.combine(selections);
        return combination;
    }

    /**
     * Try to convert argument to number.
     *
     * Helper function used in expressions.
     */
    var asNumber = function(value) {
        var number = parseFloat(value);
        if (number == value) {
            return number;
        } else {
            return 0;
        }
    };

    /**
      * Default options for expression constructor.
      *
      * They are overridden using options argument of the constructor.
      */
    var defaultExpressionOptions = {
        flatten: false
    }

    /**
      * Base constructor of all expressions.
      *
      * @param args - all arguments passed to the expression
      * @param evaluateFunction
      * @param options - various options
      *
      * Options (see defaultExpression options):
      *   - flatten - when evaluating arguments, make a single array
      *     out of all argument values (default: false)
      *
      * Exported as TableModel.expression.Base.
      */
    var Expression = function(args, evaluateFunction, options) {
        this.args = args;
        this.sourceSelection = findExpressionSourceSelection(this);
        this.evaluate = evaluateFunction;
        this.options = $.extend({}, defaultExpressionOptions, options);
        $.extend(this, options);
    };

    /**
      * Namespace with all pre-defined expressions.
      *
      * Also shortcut as TableModel.e.
      * If you want to implement a custom expression, use TableModel.expression.Base.
      */
    TableModel.expression = {
        Base : Expression,

        /**
          * Sum of a selection or values.
          */
        sum : function() {
            return new Expression(arguments, function(values) {
                var result = 0;
                $.each(values, function(index, value) {
                    result += asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        product : function() {
            return new Expression(arguments, function(values) {
                var result = 1;
                $.each(values, function(index, value) {
                    result *= asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        countIf : function(selection, condition) {
            return new Expression([selection], function(values) {
                var count = 0;
                $.each(values, function(index, item) {
                    if (condition instanceof RegExp && item.match(condition)) {
                        count++;
                    } else if (condition == item) {
                        count++;
                    }
                });
                return count;
            }, {
                flatten: true
            });
        },

        /**
         * Map sourcevalues => targetvalues using handler.
         *
         * @param selection Selection or an array of values
         * @param function(value, [row, column], tableModel) handler
         *   There are no limitation for the handler, it can be a generic function.
         * @return Array converted values as array
         *
         * If the first parameter is a selection, handler
         * receives row & column parameters as well.
         */
        map : function(selection, handler) {
            return new Expression([selection], function(values) {
                var result = [];
                var tableModel = this.tableModel;

                if (isSelection(selection)) {
                    $.each(selection.all(), function(index, item) {
                        var row = item[0];
                        var column = item[1];
                        var value = tableModel.get(row, column);
                        result.push(handler(value, tableModel, row, column));
                    });
                } else {
                    $.each(values, function(index, item) {
                        result.push(handler(item, tableModel));
                    });
                }
                return result;
            }, {
                flatten: true
            });
        }
    }

    // Shortcuts
    TableModel.s = TableModel.select;
    TableModel.e = TableModel.expression;

    // Make it a jQuery plugin
    $.fn.tableModel = function(options) {
        $.each(this, function() {
            new TableModel($(this), options);
        });
        return this;
    }
    $.extend($.fn.tableModel, TableModel);

    return TableModel;
})(jQuery);
<commit_msg>Applied jsFormat automatic formatting<commit_after>/**
 * Copyright (C) 2013 Jan Pipek (jan.pipek@gmail.com)
 *
 * See https://github.com/janpipek/table-model
 *
 * MIT license (see LICENSE.txt)
 */
TableModel = (function($) {
    var TableModel = function($table, options) {
        this.getTable = function() {
            return $table;
        };
        this.options = $.extend({}, defaultOptions, options);

        this.cellListeners = [];
        this.rowListeners = [];
        this.columnListeners = [];

        this.options.wireTableEvents.call(this);
        $table.data("table-model", this);
    };

    var addListener = function(type, handler) {
        this[type + "Listeners"].push(handler);
    };

    var callListeners = function(type, arguments) {
        var listeners = this[type + "Listeners"];
        for (var index in listeners) {
            listeners[index].apply(null, arguments);
        }
    };

    var getRows = function($table) {
        if ($table.has("thead, tbody")) {
            var trs = $table.children("thead:not(.tableFloatingHeader)").children("tr");
            trs = trs.add($table.children("tbody").children("tr"));
            trs = trs.add($table.children("tfoot").children("tr"));
            return trs;
        } else {
            return $table.children("tr");
        }
    };

    var staticWireTableEvents = function() {
        var $table = this.getTable();
        var tableModel = this;

        // console.log($table);

        var rows = getRows($table);
        rows.each(function(rowIndex) {
            var $row = $(this);
            $row.addClass("row-" + rowIndex);
            $row.data("row", rowIndex);
            $row.children("td, th").each(function(columnIndex) {
                var $cell = $(this);
                $cell.addClass("column-" + columnIndex);
                $cell.data("row", rowIndex);
                $cell.data("column", columnIndex);
            });
        });

        $table.on("change", "td input, td textarea", function() {
            var $input = $(this);
            var $cell = $input.closest("td, th");
            var row = $cell.data("row");
            var column = $cell.data("column");
            $cell.data("value", $input.val());
            onCellValueChange.call(tableModel, row, column);
        });

        if (this.options.recalculateOnType) {
            $table.on("keyup", "td input, td textarea", function() {
                var $input = $(this);
                var $cell = $input.closest("td, th");
                if ($input.val() != $cell.data("value")) {
                    $input.trigger("change");
                }
            });
        }
    };

    var staticFindCell = function(row, column) {
        return this.getTable().find(".row-" + row).find(".column-" + column);
    };

    var defaultReadCellValue = function(cell) {
        if (cell.data("value") !== undefined) {
            return cell.data("value");
        }
        if (cell.has("input").length) {
            return cell.children("input").val();
        } else if (cell.has("textarea").length) {
            return cell.children("textarea").text();
        } else {
            return cell.text();
        }
    };

    var defaultSetCellValue = function(cell, value) {
        cell.data("value", value);

        // Use precision if set
        var precision = undefined
        if (this.options.precision !== undefined) {
            var precision = this.options.precision;
        }
        if (cell.data("precision") !== undefined) {
            var precision = cell.data("precision")
        }
        if (precision !== undefined) {
            value = value.toFixed(precision);
        }

        if (cell.has("input").length) {
            cell.children("input").val(value);
        } else if (cell.has("textarea").length) {
            cell.children("textarea").text(value);
        } else {
            cell.text(value);
        }
    };

    var onCellValueChange = function(row, column) {
        callListeners.call(this, "cell", [row, column]);
        callListeners.call(this, "row", [row]);
        callListeners.call(this, "column", [column]);
    };

    var bindExpression = function(tableModel, cell, row, column, expression) {
        var selection = expression.sourceSelection
        var applyHandler = function() {
            var value = evaluate(tableModel, expression);
            tableModel.options.setCellValue.call(tableModel, cell, value);
            onCellValueChange.call(tableModel, row, column);
        };
        tableModel.onCellChange(function(row, column) {
            if (expression.sourceSelection.includes(row, column)) {
                applyHandler();
            }
        });
        applyHandler();
    };

    var defaultOptions = {
        findCell: staticFindCell,

        wireTableEvents: staticWireTableEvents,

        readCellValue: defaultReadCellValue,

        setCellValue: defaultSetCellValue,

        recalculateOnType: false
    };

    TableModel.prototype = {
        /**
         * Returns the current value of the cell.
         *
         * It does not return underlying expression, just the value.
         */
        get: function(row, column) {
            var cell = this.getCell.call(this, row, column);
            var value = this.options.readCellValue.call(this, cell);
            return value;
        },

        /**
         * Return the cell indexed by row and column.
         */
        getCell: function(row, column) {
            return this.options.findCell.call(this, row, column);
        },

        /**
         * Assign a value or a dynamic expression to the cell.
         */
        set: function(row, column, value) {
            var cell = this.options.findCell.call(this, row, column);

            if (isExpression(value)) {
                bindExpression(this, cell, row, column, value);
                // TODO: Before returning true, examine if the value
                //   really changed.
                return true;
            } else {
                var originalValue = this.options.readCellValue.call(this, cell);
                if (value !== originalValue) {
                    this.options.setCellValue.call(this, cell, value);
                    onCellValueChange.call(this, row, column);
                    return true;
                } else {
                    return false;
                }
            }
        },

        /**
         * Adds a listener that calls handler whenever expression
         * value changes.
         *
         * @param expression Expression based on cell values
         *     (accepts expressions as complicated as cells themselves)
         * @param handler Function to be called. It should accept new value
         *     of the expression as parameter.
         */
        listen: function(expression, handler) {
            var tableModel = this;
            this.onCellChange(function(row, column) {
                if (expression.sourceSelection.includes(row, column)) {
                    var value = evaluate(tableModel, expression);
                    handler(value);
                }
            });
        },

        /**
         * Register handler, that gets called whenever cell value changes.
         *
         * The handler receives two parameters: row and column
         */
        onCellChange: function(listener) {
            addListener.call(this, "cell", listener);
        },

        /**
         * Register handler, that gets called whenever a cell in a column changes.
         *
         * The handler receives one parameter: column
         */
        onColumnChange: function(listener) {
            addListener.call(this, "column", listener);
        },

        /**
         * Register handler, that gets called whenever a cell in a row changes.
         *
         * The handler receives one paramater: row
         */
        onRowChange: function(listener) {
            addListener.call(this, "row", listener);
        }
    };

    /**
     * Check if argument behaves (duck-typing) like a selection.
     */
    var isSelection = function(arg) {
        return (arg.all && arg.includes);
    };

    /**
     * Interpret argument as a selection.
     *
     * Proper selections are left as-is.
     * Arrays of pairs and a single pair are converted
     *   to a collection of cells.
     * Otherwise exception is thrown.
     */
    var asSelection = function(arg) {
        if (isSelection(arg)) {
            return arg;
        } else if ($.isArray(arg)) {
            var array = arg;

            if ((arg.length == 2) && !$.isArray(arg[0])) {
                array = [arg];
            }

            var selection = {
                includes: function(row, column) {
                    for (var i in array) {
                        if (array[i][0] == row && array[i][1] == column) {
                            return true;
                        }
                    }
                    return false;
                },
                all: function() {
                    return array;
                },
                empty: function() {
                    return !array.length;
                }
            };
            return selection;
        } else {
            throw "asSelection: argument cannot be interpreted as selection";
        }
    };

    /**
     * Namespace of all cell selections.
     *
     * Selections are collections of cells that:
     * - know if they are empty (method empty)
     * - know if they include a specific cell (method includes)
     * - know all their cells (method all)
     *
     * Any two (or more collections) can be combined
     *     using Table.model.select.combine(selections...)
     *
     */
    TableModel.select = {
        /**
         * Empty selection.
         */
        empty: function() {
            var selection = {
                includes: function() {
                    return false;
                },
                all: function() {
                    return [];
                },
                empty: function() {
                    return true;
                }
            }
        },

        /**
         * Selection consisting of a single cell.
         */
        cell: function(row, column) {
            return asSelection([row, column]);
        },

        /**
         * Selection consisting of a rectangular area.
         *
         * The coordinates are inclusive,
         *     e.g. right == left if a single column is selected.
         */
        range: function(top, left, bottom, right) {
            var selection = {
                includes: function(row, column) {
                    result = (
                    row >= top && row <= bottom && column >= left && column <= right);
                    return result;
                },
                all: function() {
                    var indices = [];
                    for (i = top; i <= bottom; i++) {
                        for (j = left; j <= right; j++) {
                            indices.push([i, j]);
                        }
                    }
                    return indices;
                },
                empty: function() {
                    return !((bottom - top) >= 0 && (right - left) >= 0);
                }
            };
            return selection;
        },

        /**
         * A combination of selections.
         *
         * Any number of selections or cell [row,column] pairs
         *     can be supplied.
         */
        combine: function() {
            var selections = [];

            if (arguments.length == 1 && $.isArray(arguments[0])) {
                var args = arguments[0];
            } else {
                var args = arguments;
            }

            $.each(args, function(index, argument) {
                var selection = asSelection(argument);
                if (!selection.empty()) {
                    selections.push(selection);
                }
            });

            var selection = {
                includes: function(row, column) {
                    for (var index in selections) {
                        if (selections[index].includes(row, column)) {
                            return true;
                        }
                    }
                    return false;
                },
                all: function() {
                    var indices = [];
                    for (var index in selections) {
                        indices = indices.concat(selections[index].all())
                    }
                    return indices;
                },
                empty: function() {
                    return !selections.length;
                }
            }
            return selection;
        }
    };

    /**
     * Check (duck-typing) if argument can be evaluated as expression.
     */
    var isExpression = function(arg) {
        return !!arg.evaluate;
    }

    /**
     * Evaluate an expression.
     *
     * @param tableModel Model has to be supplied because expressions
     *     don't store information about able in themselves.
     * @param expression Evaluate an expression starting with its
     *     arguments that can be expressions as well.
     *
     * It also sets expression.tableModel, so that tableModel
     * can be use in the expression handler.
     */
    var evaluate = function(tableModel, expression) {
        expression.tableModel = tableModel;

        var values = [];
        $.each(expression.args, function(index, arg) {
            var value;
            if (isExpression(arg)) {
                value = evaluate(tableModel, arg);
            } else if (isSelection(arg)) {
                value = [];
                $.each(arg.all(), function(i, cellIndex) {
                    value.push(tableModel.get(cellIndex[0], cellIndex[1]));
                });
            } else {
                value = arg;
            }

            if (expression.flatten && $.isArray(value)) {
                values = values.concat(value);
            } else {
                values.push(value);
            }
        });
        return expression.evaluate(values);
    }

    /**
     * Find a selection of all cells the expression depends on.
     */
    var findExpressionSourceSelection = function(expression) {
        var selections = [];
        $.each(expression.args, function(index, arg) {
            if (isSelection(arg)) {
                // console.log(arg.all());
                selections.push(arg);
            } else if (isExpression(arg)) {
                var selection = arg.sourceSelection;
                selections.push(selection);
            }
        });
        if (selections.length == 0) return TableModel.select.empty();
        if (selections.length == 1) return selections[0];
        var combination = TableModel.select.combine(selections);
        return combination;
    }

    /**
     * Try to convert argument to number.
     *
     * Helper function used in expressions.
     */
    var asNumber = function(value) {
        var number = parseFloat(value);
        if (number == value) {
            return number;
        } else {
            return 0;
        }
    };

    /**
     * Default options for expression constructor.
     *
     * They are overridden using options argument of the constructor.
     */
    var defaultExpressionOptions = {
        flatten: false
    }

    /**
     * Base constructor of all expressions.
     *
     * @param args - all arguments passed to the expression
     * @param evaluateFunction
     * @param options - various options
     *
     * Options (see defaultExpression options):
     *   - flatten - when evaluating arguments, make a single array
     *     out of all argument values (default: false)
     *
     * Exported as TableModel.expression.Base.
     */
    var Expression = function(args, evaluateFunction, options) {
        this.args = args;
        this.sourceSelection = findExpressionSourceSelection(this);
        this.evaluate = evaluateFunction;
        this.options = $.extend({}, defaultExpressionOptions, options);
        $.extend(this, options);
    };

    /**
     * Namespace with all pre-defined expressions.
     *
     * Also shortcut as TableModel.e.
     * If you want to implement a custom expression, use TableModel.expression.Base.
     */
    TableModel.expression = {
        Base: Expression,

        /**
         * Sum of a selection or values.
         */
        sum: function() {
            return new Expression(arguments, function(values) {
                var result = 0;
                $.each(values, function(index, value) {
                    result += asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        product: function() {
            return new Expression(arguments, function(values) {
                var result = 1;
                $.each(values, function(index, value) {
                    result *= asNumber(value);
                });
                return result;
            }, {
                flatten: true
            });
        },

        countIf: function(selection, condition) {
            return new Expression([selection], function(values) {
                var count = 0;
                $.each(values, function(index, item) {
                    if (condition instanceof RegExp && item.match(condition)) {
                        count++;
                    } else if (condition == item) {
                        count++;
                    }
                });
                return count;
            }, {
                flatten: true
            });
        },

        /**
         * Map sourcevalues => targetvalues using handler.
         *
         * @param selection Selection or an array of values
         * @param function(value, [row, column], tableModel) handler
         *   There are no limitation for the handler, it can be a generic function.
         * @return Array converted values as array
         *
         * If the first parameter is a selection, handler
         * receives row & column parameters as well.
         */
        map: function(selection, handler) {
            return new Expression([selection], function(values) {
                var result = [];
                var tableModel = this.tableModel;

                if (isSelection(selection)) {
                    $.each(selection.all(), function(index, item) {
                        var row = item[0];
                        var column = item[1];
                        var value = tableModel.get(row, column);
                        result.push(handler(value, tableModel, row, column));
                    });
                } else {
                    $.each(values, function(index, item) {
                        result.push(handler(item, tableModel));
                    });
                }
                return result;
            }, {
                flatten: true
            });
        }
    }

    // Shortcuts
    TableModel.s = TableModel.select;
    TableModel.e = TableModel.expression;

    // Make it a jQuery plugin
    $.fn.tableModel = function(options) {
        $.each(this, function() {
            new TableModel($(this), options);
        });
        return this;
    }
    $.extend($.fn.tableModel, TableModel);

    return TableModel;
})(jQuery);<|endoftext|>


<commit_before>
var geonames = require('geonames-stream'),
  // suggester = require('pelias-suggester-pipeline'),
  through = require('through2'),
  resolvers = require('./resolvers'),
  dbclient = require('pelias-dbclient')(),
  model = require( 'pelias-model' ),
  peliasConfig = require( 'pelias-config' ).generate(),
  peliasAdminLookup = require( 'pelias-admin-lookup' ),
  categoryMapping = require( '../metadata/category_mapping.json' ),
  logger = require( 'pelias-logger' ).get( 'geonames' );

function mapper( data, enc, next ){
  var record;
  try {
    record = new model.Document( 'geoname', data._id )
      .setName( 'default', data.name.trim() )
      .setCentroid({
        lat: data.latitude,
        lon: data.longitude
      });

    try {
      record.setMeta( 'fcode', data.feature_code );
    }
    catch ( err ) {}

    try {
      record.setAlpha3( resolvers.alpha3(data.country_code) );
    } catch( err ){}

    try {
      record.setAdmin( 'admin0', resolvers.country_name( data.country_code ) );
    } catch( err ){}

    try {
      record.setAdmin( 'admin1', resolvers.admin1_name( data ) );
    } catch( err ){}

    try {
      record.setAdmin( 'admin2', resolvers.admin2_name( data ) );
    } catch( err ){}

    try {
      var population = parseInt(data.population, 10);
      if (population) {
        record.setPopulation( population );
      }
    } catch( err ){}

    if( typeof data.feature_code === 'string' ){
      var featureCode = data.feature_code.toUpperCase();
      if( categoryMapping.hasOwnProperty( featureCode ) ){
        var peliasCategories = categoryMapping[ featureCode ];
        peliasCategories.forEach( function ( category ){
          try {
            record.addCategory( category );
          } catch ( ex ) {
            logger.error( 'Failed to set category `%s` with exception `%s`.', category, ex);
          }
        });
      }
    }

  } catch( e ){
    logger.warn(
      'Failed to create a Document from:', data, 'Exception:', e
    );
  }

  if( record !== undefined ){
    this.push( record );
  }
  next();
}

/**
 * Sets values inside Documents' `_meta` objects to indicate that
 * `peliasAdminLookup` shouldn't set certain admin values, depending on their
 * `fcodes`, or feature codes (as documented here:
 * http://www.geonames.org/export/codes.html). This should prevent records for
 * cities/continents/etc. from getting 'neighborhood' names set, which would
 * happen because the corresponding point would likely intersect some
 * neighborhood polygon all the way down.
 */
var adminLookupDontSet = (function (){
  var fcodeAdminDontSet = {
    ADM1: [ 'neighborhood', 'locality', 'local_admin', 'admin2' ],
    ADM2: [ 'neighborhood', 'locality', 'local_admin' ],
    CONT: [ 'neighborhood', 'locality', 'local_admin', 'admin2', 'admin1', 'admin0' ]
  };
  var noNeighborhoods = [
    'PPL', 'STM', 'LK', 'ISL', 'VAL', 'ADM4', 'ADM3', 'WAD', 'AREA', 'CAPE',
    'PPLA3', 'MTS', 'FRST', 'RVN', 'ISLET', 'COVE', 'PPLA2', 'SWMP', 'HDLD',
    'SLP', 'CLF', 'AIRF', 'PPLF', 'GRGE', 'PPLA', 'CNYN', 'BDG', 'PPLC', 'PPLX'
  ];
  noNeighborhoods.forEach( function ( code ){
    fcodeAdminDontSet[ code ] = [ 'neighborhood' ];
  });

  return through.obj( function write( data, _, next ){
    var fcode = data.getMeta( 'fcode' );
    if( fcode in fcodeAdminDontSet ){
      data.setMeta( 'adminLookup', {dontSet: fcodeAdminDontSet[ fcode ]} );
    }
    this.push( data );
    next();
  });
})();

module.exports = function( filename ){
  var pipeline = resolvers.selectSource( filename )
    .pipe( geonames.pipeline )
    .pipe( through.obj( mapper ) );
    // .pipe( suggester.pipeline );

  if( peliasConfig.imports.geonames.adminLookup ){
    pipeline = pipeline
      .pipe( adminLookupDontSet )
      .pipe( peliasAdminLookup.stream() );
  }

  pipeline
    .pipe( through.obj( function( item, enc, next ){
      this.push({
        _index: 'pelias',
        _type: item.getType(),
        _id: item.getId(),
        data: item
      });
      next();
    }))
    .pipe( dbclient );
};
<commit_msg>hack shingles<commit_after>
var geonames = require('geonames-stream'),
  // suggester = require('pelias-suggester-pipeline'),
  through = require('through2'),
  resolvers = require('./resolvers'),
  dbclient = require('pelias-dbclient')(),
  model = require( 'pelias-model' ),
  peliasConfig = require( 'pelias-config' ).generate(),
  peliasAdminLookup = require( 'pelias-admin-lookup' ),
  categoryMapping = require( '../metadata/category_mapping.json' ),
  logger = require( 'pelias-logger' ).get( 'geonames' );

function mapper( data, enc, next ){
  var record;
  try {
    record = new model.Document( 'geoname', data._id )
      .setName( 'default', data.name.trim() )
      .setCentroid({
        lat: data.latitude,
        lon: data.longitude
      });

    try {
      record.setMeta( 'fcode', data.feature_code );
    }
    catch ( err ) {}

    try {
      record.setAlpha3( resolvers.alpha3(data.country_code) );
    } catch( err ){}

    try {
      record.setAdmin( 'admin0', resolvers.country_name( data.country_code ) );
    } catch( err ){}

    try {
      record.setAdmin( 'admin1', resolvers.admin1_name( data ) );
    } catch( err ){}

    try {
      record.setAdmin( 'admin2', resolvers.admin2_name( data ) );
    } catch( err ){}

    try {
      var population = parseInt(data.population, 10);
      if (population) {
        record.setPopulation( population );
      }
    } catch( err ){}

    if( typeof data.feature_code === 'string' ){
      var featureCode = data.feature_code.toUpperCase();
      if( categoryMapping.hasOwnProperty( featureCode ) ){
        var peliasCategories = categoryMapping[ featureCode ];
        peliasCategories.forEach( function ( category ){
          try {
            record.addCategory( category );
          } catch ( ex ) {
            logger.error( 'Failed to set category `%s` with exception `%s`.', category, ex);
          }
        });
      }
    }

  } catch( e ){
    logger.warn(
      'Failed to create a Document from:', data, 'Exception:', e
    );
  }

  if( record !== undefined ){
    record.shingle = record.name;
    this.push( record );
  }
  next();
}

/**
 * Sets values inside Documents' `_meta` objects to indicate that
 * `peliasAdminLookup` shouldn't set certain admin values, depending on their
 * `fcodes`, or feature codes (as documented here:
 * http://www.geonames.org/export/codes.html). This should prevent records for
 * cities/continents/etc. from getting 'neighborhood' names set, which would
 * happen because the corresponding point would likely intersect some
 * neighborhood polygon all the way down.
 */
var adminLookupDontSet = (function (){
  var fcodeAdminDontSet = {
    ADM1: [ 'neighborhood', 'locality', 'local_admin', 'admin2' ],
    ADM2: [ 'neighborhood', 'locality', 'local_admin' ],
    CONT: [ 'neighborhood', 'locality', 'local_admin', 'admin2', 'admin1', 'admin0' ]
  };
  var noNeighborhoods = [
    'PPL', 'STM', 'LK', 'ISL', 'VAL', 'ADM4', 'ADM3', 'WAD', 'AREA', 'CAPE',
    'PPLA3', 'MTS', 'FRST', 'RVN', 'ISLET', 'COVE', 'PPLA2', 'SWMP', 'HDLD',
    'SLP', 'CLF', 'AIRF', 'PPLF', 'GRGE', 'PPLA', 'CNYN', 'BDG', 'PPLC', 'PPLX'
  ];
  noNeighborhoods.forEach( function ( code ){
    fcodeAdminDontSet[ code ] = [ 'neighborhood' ];
  });

  return through.obj( function write( data, _, next ){
    var fcode = data.getMeta( 'fcode' );
    if( fcode in fcodeAdminDontSet ){
      data.setMeta( 'adminLookup', {dontSet: fcodeAdminDontSet[ fcode ]} );
    }
    this.push( data );
    next();
  });
})();

module.exports = function( filename ){
  var pipeline = resolvers.selectSource( filename )
    .pipe( geonames.pipeline )
    .pipe( through.obj( mapper ) );
    // .pipe( suggester.pipeline );

  if( peliasConfig.imports.geonames.adminLookup ){
    pipeline = pipeline
      .pipe( adminLookupDontSet )
      .pipe( peliasAdminLookup.stream() );
  }

  pipeline
    .pipe( through.obj( function( item, enc, next ){
      this.push({
        _index: 'pelias',
        _type: item.getType(),
        _id: item.getId(),
        data: item
      });
      next();
    }))
    .pipe( dbclient );
};
<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

  var path = require('path');
  var ph_libutil = require("phantomizer-libutil");

  grunt.registerMultiTask("phantomizer-imgopt", "Optimize png/jpeg pictures", function () {

    var sub_tasks = []
    var current_grunt_task = this.nameArgs;
    var user_config = grunt.config();

    var meta_factory = ph_libutil.meta;

    var wd = process.cwd();

    var options = this.options({
      optimizationLevel: 0
      ,"progressive":true
      ,"cache":false
      ,out_dir: ""
      ,meta_dir: ""
      ,paths: []
      ,in_files: {}
    });

    grunt.verbose.writeflags(options, 'Options'); // debug call
    var out_dir = options.out_dir;
    var meta_dir = options.meta_dir;
    var paths = options.paths;
    var files = options.in_files || {};
    var sub_task_options = {};

    var current_task_name = "phantomizer-imgopt";

    var meta_manager = new meta_factory( wd, meta_dir );

    for( var src_file in files ){
      var out_file = out_dir+"/"+files[src_file];
      var meta_file = "/"+files[src_file]+"";
      var meta_infile = src_file+"";

      if( meta_manager.is_fresh(meta_file) == false
        && meta_manager.is_fresh(meta_infile,current_task_name+src_file) == false ){

        var file = find_in_paths(paths,src_file);

        var deps = [];
        deps.push(file);

        if ( grunt.file.exists(process.cwd()+"/Gruntfile.js")) {
          deps.push(process.cwd()+"/Gruntfile.js")
        }
        if ( grunt.file.exists(user_config.project_dir+"/../config.json")) {
          deps.push( user_config.project_dir+"/../config.json")
        }
        deps.push(__filename);

        var sub_task_name = "jit"+sub_tasks.length;
        sub_task_options[sub_task_name] = {
          options: {
            cache: options.cache,
            optimizationLevel: options.optimizationLevel
            ,progressive: options.progressive
          },
          files: {
          }
        }
        sub_task_options[sub_task_name].files[out_file] = file;
        grunt.log.ok("Creating "+out_file)
        grunt.log.ok("optimizationLevel:"+options.optimizationLevel)

        // create a cache entry, so that later we can regen or check freshness
        var entry = meta_manager.create(deps);
        entry.require_task(current_grunt_task, options);
        entry.save(meta_file);

        //-

        grunt.config.set("imagemin", sub_task_options)
        sub_tasks.push( "imagemin"+":"+sub_task_name )
      }else{
        grunt.log.ok("your build is fresh !\n\t"+out_file)
      }
    }

    grunt.task.run( sub_tasks )
  });

  function find_in_paths(paths, src){
    for( var t in paths ){
      if( grunt.file.exists(paths[t]+src) ){
        return path.resolve(paths[t]+src)
      }
    }
    return false
  }

  grunt.registerMultiTask("phantomizer-dir-imgopt", "Optimize png/jpeg pictures", function () {

    var sub_tasks = []
    var current_target = this.target;

    var options = this.options({
      optimizationLevel: 2
      ,"progressive":true
      ,"cache":false
      ,"pngquant":true
      ,"interlaced":true
      ,paths: []
      ,out_path: ""
      ,pattern: '**/*.{png,jpg,jpeg,gif}'
      ,outputFormat: "progress"
    });

    grunt.verbose.writeflags(options, 'Options');

    var paths = options.paths;
    var out_path = options.out_path;
    if(paths.toLowerCase) paths = [paths];

    for( var n in paths ){
      var p = paths[n];
      var sub_task_name = "imgmin"+sub_tasks.length;

      p = path.normalize(p);

      var sub_task_options = grunt.config.get("imagemin") || {};
      sub_task_options = clone_subtasks_options(sub_task_options, sub_task_name, current_target);
      sub_task_options[sub_task_name].options.verbose = true;
      sub_task_options[sub_task_name] = {
        options: {
          cache: options.cache,
          optimizationLevel: options.optimizationLevel,
          progressive: options.progressive,
          pngquant: options.pngquant,
          interlaced: options.interlaced,
          outputFormat: options.outputFormat
        },
        files: [{
          expand: true,
          cwd: p+'',
          src: options.pattern,
          dest: out_path
        }]
      };
      grunt.log.ok("Optimizing images\n\t"+p);
      grunt.config.set("imagemin", sub_task_options)
      sub_tasks.push( "imagemin"+":"+sub_task_name )
    }

    grunt.task.run( sub_tasks )
  });

  function clone_subtasks_options(task_options, task_name, current_target){
    var _ = grunt.util._;
    if( task_options[current_target] ) task_options[task_name] = _.clone(task_options[current_target], true);
    if( !task_options[task_name] ) task_options[task_name] = {};
    if( !task_options[task_name].options ) task_options[task_name].options = {};
    return task_options;
  }

};<commit_msg>Add a new Facade : Phantomizer<commit_after>'use strict';

module.exports = function(grunt) {

  var path = require('path');
  var ph_libutil = require("phantomizer-libutil");

  grunt.registerMultiTask("phantomizer-imgopt", "Optimize png/jpeg pictures", function () {

    var sub_tasks = []
    var current_grunt_task = this.nameArgs;
    var user_config = grunt.config();

    var options = this.options({
      optimizationLevel: 0
      ,"progressive":true
      ,"cache":false
      ,out_dir: ""
      ,paths: []
      ,in_files: {}
    });

    grunt.verbose.writeflags(options, 'Options'); // debug call
    var out_dir = options.out_dir;
    var paths = options.paths;
    var files = options.in_files || {};
    var sub_task_options = {};

    var current_task_name = "phantomizer-imgopt";

    var phantomizer = ph_libutil.get("main");
    var meta_manager = phantomizer.get_meta_manager();

    for( var src_file in files ){
      var out_file = out_dir+"/"+files[src_file];
      var meta_file = "/"+files[src_file]+"";
      var meta_infile = src_file+"";

      if( meta_manager.is_fresh(meta_file) == false
        && meta_manager.is_fresh(meta_infile,current_task_name+src_file) == false ){

        var file = find_in_paths(paths,src_file);

        // create a cache entry, so that later we can regen or check freshness
        var entry = meta_manager.create([]);
        entry.append_dependency( __filename )
        entry.append_dependency( file )
        entry.append_dependency( process.cwd()+"/Gruntfile.js")
        entry.append_dependency( user_config.project_dir+"/../config.json")

        var sub_task_name = "jit"+sub_tasks.length;
        sub_task_options[sub_task_name] = {
          options: {
            cache: options.cache,
            optimizationLevel: options.optimizationLevel
            ,progressive: options.progressive
          },
          files: {
          }
        }
        sub_task_options[sub_task_name].files[out_file] = file;
        grunt.log.ok("Creating "+out_file)
        grunt.log.ok("optimizationLevel:"+options.optimizationLevel)

        entry.require_task(current_grunt_task, options);
        entry.save(meta_file);

        //-

        grunt.config.set("imagemin", sub_task_options)
        sub_tasks.push( "imagemin"+":"+sub_task_name )
      }else{
        grunt.log.ok("your build is fresh !\n\t"+out_file)
      }
    }

    grunt.task.run( sub_tasks )
  });

  function find_in_paths(paths, src){
    for( var t in paths ){
      if( grunt.file.exists(paths[t]+src) ){
        return path.resolve(paths[t]+src)
      }
    }
    return false
  }

  grunt.registerMultiTask("phantomizer-dir-imgopt", "Optimize png/jpeg pictures", function () {

    var sub_tasks = []
    var current_target = this.target;

    var options = this.options({
      optimizationLevel: 2
      ,"progressive":true
      ,"cache":false
      ,"pngquant":true
      ,"interlaced":true
      ,paths: []
      ,out_path: ""
      ,pattern: '**/*.{png,jpg,jpeg,gif}'
      ,outputFormat: "progress"
    });

    grunt.verbose.writeflags(options, 'Options');

    var paths = options.paths;
    var out_path = options.out_path;
    if(paths.toLowerCase) paths = [paths];

    for( var n in paths ){
      var p = paths[n];
      var sub_task_name = "imgmin"+sub_tasks.length;

      p = path.normalize(p);

      var sub_task_options = grunt.config.get("imagemin") || {};
      sub_task_options = clone_subtasks_options(sub_task_options, sub_task_name, current_target);
      sub_task_options[sub_task_name].options.verbose = true;
      sub_task_options[sub_task_name] = {
        options: {
          cache: options.cache,
          optimizationLevel: options.optimizationLevel,
          progressive: options.progressive,
          pngquant: options.pngquant,
          interlaced: options.interlaced,
          outputFormat: options.outputFormat
        },
        files: [{
          expand: true,
          cwd: p+'',
          src: options.pattern,
          dest: out_path
        }]
      };
      grunt.log.ok("Optimizing images\n\t"+p);
      grunt.config.set("imagemin", sub_task_options)
      sub_tasks.push( "imagemin"+":"+sub_task_name )
    }

    grunt.task.run( sub_tasks )
  });

  function clone_subtasks_options(task_options, task_name, current_target){
    var _ = grunt.util._;
    if( task_options[current_target] ) task_options[task_name] = _.clone(task_options[current_target], true);
    if( !task_options[task_name] ) task_options[task_name] = {};
    if( !task_options[task_name].options ) task_options[task_name].options = {};
    return task_options;
  }

};<|endoftext|>


<commit_before>module.exports = function(grunt) {

  /**
   * Define "drush" tasks.
   *
   * grunt drush:make
   *   Builds the Drush make file to the build/html directory.
   */
  grunt.loadNpmTasks('grunt-drush');
  grunt.loadNpmTasks('grunt-newer');

  var path = require('path'),
    _ = require('lodash');

  // If no path is configured for Drush, fallback to the system path.
  var cmd = grunt.config('config.drush.cmd') !== undefined ? {cmd: path.resolve(grunt.config('config.drush.cmd'))} : {};

  // Allow extra arguments for drush to be supplied.
  var make_args = ['make', '<%= config.srcPaths.make %>', '<%= config.buildPaths.temp %>'];
  var extra_args = grunt.config.get('config.drush.make.args');

  if (extra_args && extra_args.length) {
    extra_args.unshift(make_args[0]);
    extra_args.push(make_args[1]);
    extra_args.push(make_args[2]);
    make_args = extra_args;
  }

  var limit = grunt.option('concurrency') || require('../lib/util').concurrency;
  make_args.push('--concurrency=' + limit);
  grunt.verbose.writeln('Configured for concurrency=' + limit);

  grunt.config('drush', {
    make: {
      args: args,
      options: _.extend({}, cmd)
    },
    liteinstall: {
      args: ['site-install', '-y', 'standard', '--db-url=sqlite://drupal:drupal@drupal.sqlite'],
      options: _.extend({
        cwd: '<%= config.buildPaths.html %>'
      }, cmd)
    },
    runserver: {
      args: ['runserver', '8080'],
      options: _.extend({
        cwd: '<%= config.buildPaths.html %>'
      }, cmd)
    }
  });

  grunt.registerTask('drushmake', 'Prepare the build directory and run "drush make"', function() {
    grunt.task.run('mkdir:init', 'clean:temp', 'drush:make', 'clean:default', 'copy:tempbuild', 'clean:temp');
  });

  // The "drushmake" task will run make only if the src file specified here is
  // newer than the dest file specified. This includes all make files in the
  // source directory to catch make files included from the primary one.
  grunt.config('drushmake', {
    default: {
      src: ['<%= config.srcPaths.make %>', '<%= config.srcPaths.drupal %>/**/*.make'],
      dest: '<%= config.buildPaths.html %>'
    }
  });

  grunt.config('help.drushmake', {
    group: 'Dependency Management'
  });
  grunt.config('help.newer', {
    group: 'Dependency Management',
    description: 'Use "newer:drushmake" to run the drushmake task only if the make file was updated.'
  });
};
<commit_msg>Use make_args in place of args<commit_after>module.exports = function(grunt) {

  /**
   * Define "drush" tasks.
   *
   * grunt drush:make
   *   Builds the Drush make file to the build/html directory.
   */
  grunt.loadNpmTasks('grunt-drush');
  grunt.loadNpmTasks('grunt-newer');

  var path = require('path'),
    _ = require('lodash');

  // If no path is configured for Drush, fallback to the system path.
  var cmd = grunt.config('config.drush.cmd') !== undefined ? {cmd: path.resolve(grunt.config('config.drush.cmd'))} : {};

  // Allow extra arguments for drush to be supplied.
  var make_args = ['make', '<%= config.srcPaths.make %>', '<%= config.buildPaths.temp %>'];
  var extra_args = grunt.config.get('config.drush.make.args');

  if (extra_args && extra_args.length) {
    extra_args.unshift(make_args[0]);
    extra_args.push(make_args[1]);
    extra_args.push(make_args[2]);
    make_args = extra_args;
  }

  var limit = grunt.option('concurrency') || require('../lib/util').concurrency;
  make_args.push('--concurrency=' + limit);
  grunt.verbose.writeln('Configured for concurrency=' + limit);

  grunt.config('drush', {
    make: {
      args: make_args,
      options: _.extend({}, cmd)
    },
    liteinstall: {
      args: ['site-install', '-y', 'standard', '--db-url=sqlite://drupal:drupal@drupal.sqlite'],
      options: _.extend({
        cwd: '<%= config.buildPaths.html %>'
      }, cmd)
    },
    runserver: {
      args: ['runserver', '8080'],
      options: _.extend({
        cwd: '<%= config.buildPaths.html %>'
      }, cmd)
    }
  });

  grunt.registerTask('drushmake', 'Prepare the build directory and run "drush make"', function() {
    grunt.task.run('mkdir:init', 'clean:temp', 'drush:make', 'clean:default', 'copy:tempbuild', 'clean:temp');
  });

  // The "drushmake" task will run make only if the src file specified here is
  // newer than the dest file specified. This includes all make files in the
  // source directory to catch make files included from the primary one.
  grunt.config('drushmake', {
    default: {
      src: ['<%= config.srcPaths.make %>', '<%= config.srcPaths.drupal %>/**/*.make'],
      dest: '<%= config.buildPaths.html %>'
    }
  });

  grunt.config('help.drushmake', {
    group: 'Dependency Management'
  });
  grunt.config('help.newer', {
    group: 'Dependency Management',
    description: 'Use "newer:drushmake" to run the drushmake task only if the make file was updated.'
  });
};
<|endoftext|>