<commit_before>(function () {

    var defaults = {
        column : {
            stacked: false,
            groupPadding: 1,
            columnWidth: function() { return this.rangeBand; },
            offset: function() { return 0; }
        }
    };

    function render(data, layer, options) {
        var duration = 400;
        var opt = options.column;
        var w = options.chart.plotWidth;
        var h = options.chart.plotHeight;
        var _this = this;
        var x = function (v) { return Math.round(_this.xScale(v)); };
        var y = function (v) { return Math.round(_this.yScale(v)); };
        var dataKey = function (d) { return d.data; };
        var chartOffset = _.nw.getValue(opt.offset, 0, this);
        var rangeBand = _.nw.getValue(opt.columnWidth, this.rangeBand, this);
        var enter = _.partialRight((options.column.stacked ? stacked : grouped), true);
        var update = options.column.stacked ? stacked : grouped;
        var filteredData = _.map(data, function (series, j) {
            return {
                name: series.name,
                data: _.filter(series.data, function (d, i) {
                    return i === 0 ? true : x(d.x) !== x(series.data[i-1].x);
                })
            };
        });

        var stack = d3.layout.stack().values(function (d) {
            return d.data;
        });

        var series = layer.selectAll('g.series')
                .data(stack(filteredData));

        series.enter()
            .append('g')
            .attr('class', function (d, i) { return 'series s-' + (i+1); });

        series.exit()
            .remove();

        var cols = series.selectAll('.column')
                .data(dataKey);

        var offset = function (d, i) { return rangeBand / data.length * i; };
        var width = rangeBand / data.length - opt.groupPadding;

        cols.enter()
            .append('rect')
            .attr('class', 'column tooltip-tracker')
            .call(enter);

        if (options.chart.animations) {
            cols.exit()
                .transition().duration(duration)
                .attr('y', h)
                .attr('height', function () { return 0; })
                .remove();
            cols.transition().duration(duration)
                .call(update);
        } else {
            cols.exit().remove();
            cols.call(update);
        }

        function stacked(col, enter) {
            col.attr('x', function (d) { return x(d.x) + chartOffset; })
                .attr('width', function () { return rangeBand; });

            if (enter) {
                col
                    .attr('y', function (d) { return h; })
                    .attr('height', function (d) { return 0; });
            } else {
                col
                    .attr('y', function (d) { return y(d.y) + y(d.y0) - h; })
                    .attr('height', function (d) { return h - y(d.y); });
            }
        }

        function grouped(col, enter) {
            var width = rangeBand / data.length - opt.groupPadding;
            var offset = function (d, i) { return rangeBand / data.length * i; };

            col.attr('x', function (d, i, j) { return x(d.x) + offset(d, j) + chartOffset; })
                .attr('width', width);

            if (enter)
                col.attr('y', _.nw.clampLeft(h, 0))
                    .attr('height', 0);
            else
                col.attr('height', function (d) { return h - y(d.y); })
                    .attr('y', function (d) { return y(d.y); });
        }
    }

    render.defaults = defaults;

    /*
    * Adds a column chart (vertical columns) to the Narwhal instance.
    *
    * This visualization requires `.cartesian()`.
    *
    * ### Example:
    *
    *     new Narwhal({el: '.myChart'})
    *           .cartesian()
    *           .column([1,2,3,4])
    *           .render();
    *
    * @name column(data, options)
    * @param {object|array} data The _data series_ to be rendered with this visualization. This can be in any of the supported formats.
    * @param {object} [options] Options particular to this visualization that override the defaults.
    * @api public
    *
    */
    Narwhal.export('column', render);

})();
<commit_msg>Formatting<commit_after>(function () {

    var defaults = {
        column : {
            stacked: false,
            groupPadding: 1,
            columnWidth: function() { return this.rangeBand; },
            offset: function() { return 0; }
        }
    };

    function render(data, layer, options) {
        var duration = 400;
        var opt = options.column;
        var w = options.chart.plotWidth;
        var h = options.chart.plotHeight;
        var _this = this;
        var x = function (v) { return Math.round(_this.xScale(v)); };
        var y = function (v) { return Math.round(_this.yScale(v)); };
        var dataKey = function (d) { return d.data; };
        var chartOffset = _.nw.getValue(opt.offset, 0, this);
        var rangeBand = _.nw.getValue(opt.columnWidth, this.rangeBand, this);
        var enter = _.partialRight((options.column.stacked ? stacked : grouped), true);
        var update = options.column.stacked ? stacked : grouped;
        var filteredData = _.map(data, function (series, j) {
            return {
                name: series.name,
                data: _.filter(series.data, function (d, i) {
                    return i === 0 ? true : x(d.x) !== x(series.data[i-1].x);
                })
            };
        });

        var stack = d3.layout.stack().values(function (d) {
            return d.data;
        });

        var series = layer.selectAll('g.series')
                .data(stack(filteredData));

        series.enter()
            .append('g')
            .attr('class', function (d, i) { return 'series s-' + (i+1); });

        series.exit()
            .remove();

        var cols = series.selectAll('.column')
                .data(dataKey);

        var offset = function (d, i) { return rangeBand / data.length * i; };
        var width = rangeBand / data.length - opt.groupPadding;

        cols.enter()
            .append('rect')
            .attr('class', 'column tooltip-tracker')
            .call(enter);

        if (options.chart.animations) {
            cols.exit()
                .transition().duration(duration)
                .attr('y', h)
                .attr('height', function () { return 0; })
                .remove();
            cols.transition().duration(duration)
                .call(update);
        } else {
            cols.exit().remove();
            cols.call(update);
        }

        function stacked(col, enter) {
            col.attr('x', function (d) { return x(d.x) + chartOffset; })
                .attr('width', function () { return rangeBand; });

            if (enter) {
                col
                    .attr('y', function (d) { return h; })
                    .attr('height', function (d) { return 0; });
            } else {
                col
                    .attr('y', function (d) { return y(d.y) + y(d.y0) - h; })
                    .attr('height', function (d) { return h - y(d.y); });
            }
        }

        function grouped(col, enter) {
            var width = rangeBand / data.length - opt.groupPadding;
            var offset = function (d, i) { return rangeBand / data.length * i; };

            col.attr('x', function (d, i, j) { return x(d.x) + offset(d, j) + chartOffset; })
                .attr('width', width);

            if (enter) {
                col.attr('y', _.nw.clampLeft(h, 0))
                    .attr('height', 0);
            } else {
                col.attr('height', function (d) { return h - y(d.y); })
                    .attr('y', function (d) { return y(d.y); });
            }
        }
    }

    render.defaults = defaults;

    /*
    * Adds a column chart (vertical columns) to the Narwhal instance.
    *
    * This visualization requires `.cartesian()`.
    *
    * ### Example:
    *
    *     new Narwhal({el: '.myChart'})
    *           .cartesian()
    *           .column([1,2,3,4])
    *           .render();
    *
    * @name column(data, options)
    * @param {object|array} data The _data series_ to be rendered with this visualization. This can be in any of the supported formats.
    * @param {object} [options] Options particular to this visualization that override the defaults.
    * @api public
    *
    */
    Narwhal.export('column', render);

})();
<|endoftext|>


<commit_before>/* ==========================================================
 * Support.js v1.1.0
 * ==========================================================
 * Copyright 2012 xsokev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================== */
define([
    "dojo/query",
    "dojo/_base/lang",
    "dojo/dom-attr",
    "dojo/_base/array",
    "dojo/_base/json",
    "dojo/NodeList-data"
],
function (query, lang, attr, array, json) {
    "use strict";

    lang.extend(query.NodeList, {
        show:function () {
            return this.forEach(function (node) {
                node.style.display = 'block';
            });
        },
        hide:function () {
            return this.forEach(function (node) {
                node.style.display = 'none';
            });
        }
    });

    var _transition = (function () {
        var transitionEnd = (function () {
            var el = document.createElement('bootstrap');
            var transEndEventNames = {
                'WebkitTransition':'webkitTransitionEnd',
                'MozTransition':'transitionend',
                'OTransition':'oTransitionEnd otransitionend',
                'transition':'transitionend'
            };
            for (var name in transEndEventNames) {
                if (el.style[name] !== undefined) {
                    return transEndEventNames[name];
                }
            }
        })();
        return transitionEnd && {
            end:transitionEnd
        };
    })();

    var _loadData = function(node){
        //load data attributes
        var elm = query(node)[0];
        if(elm){
            var _this = this;
            var attrs = elm.attributes;
            array.forEach(attrs, function(attr){
                if(attr.name.indexOf("data-") >= 0){
                    _this.setData(node, attr.name.substr(5), _attrValue(attr.value));
                }
            });
        }
    };

    var _attrValue = function(val){
        if (!val) { return; }
        if (val.indexOf('{') === 0 && val.indexOf('}') === val.length-1) {
            return json.fromJson(val);
        } else if (val.indexOf('[') === 0 && val.indexOf(']') === val.length-1) {
            return json.fromJson(val);
        } else {
            return val;
        }
    };

    return {
        trans: _transition,
        getData: function(node, key, def){
            key = key || undefined;
            def = def || undefined;
            if(key !== undefined && lang.isString(key)){
                var data = query(node).data(key);
                if (data && data[0] === undefined) {
                    if(query(node)[0]){ data = attr.get(query(node)[0], 'data-'+key); }
                    if (data !== undefined){ data = _attrValue(data); }
                    if (data === undefined && def !== undefined){
                        data = this.setData(node, key, def);
                    }
                }
                return (lang.isArray(data) && data.length > 0) ? data[0] : data;
            } else {
                _loadData.call(this, node);
                return query(node).data()[0];
            }
        },
        setData: function(node, key, value){
            var data = query(node).data(key, value);
            return value;
        },
        removeData: function(node, key){
            return query(node).removeData(key);
        },
        toCamel: function(str){
            return str.replace(/(\-[a-z])/g, function($1){ return $1.toUpperCase().replace('-',''); });
        },
        toDash: function(str){
            return str.replace(/([A-Z])/g, function($1){ return "-"+$1.toLowerCase(); });
        },
        toUnderscore: function(str){
            return str.replace(/([A-Z])/g, function($1){ return "_"+$1.toLowerCase(); });
        }
    };
});<commit_msg>Fixes issues #10, #11, #12<commit_after>/* ==========================================================
 * Support.js v1.1.0
 * ==========================================================
 * Copyright 2012 xsokev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ========================================================== */
define([
    "dojo/query",
    "dojo/_base/lang",
    "dojo/dom-attr",
    "dojo/_base/array",
    "dojo/_base/json",
    "dojo/NodeList-data"
],
function (query, lang, attr, array, json) {
    "use strict";

    lang.extend(query.NodeList, {
        show:function () {
            return this.forEach(function (node) {
                node.style.display = 'block';
            });
        },
        hide:function () {
            return this.forEach(function (node) {
                node.style.display = 'none';
            });
        }
    });

    var _transition = (function () {
        var transitionEnd = (function () {
            var el = document.createElement('bootstrap');
            var transEndEventNames = {
                'WebkitTransition':'webkitTransitionEnd',
                'MozTransition':'transitionend',
                'OTransition':'oTransitionEnd',
                'transition':'transitionend'
            };
            for (var name in transEndEventNames) {
                if (el.style[name] !== undefined) {
                    return transEndEventNames[name];
                }
            }
        })();
        return transitionEnd && {
            end:transitionEnd
        };
    })();

    var _loadData = function(node){
        //load data attributes
        var elm = query(node)[0];
        if(elm){
            var _this = this;
            var attrs = elm.attributes;
            array.forEach(attrs, function(attr){
                if(attr.name.indexOf("data-") >= 0){
                    _this.setData(node, attr.name.substr(5), _attrValue(attr.value));
                }
            });
        }
    };

    var _attrValue = function(val){
        if (!val) { return; }
        if (val.indexOf('{') === 0 && val.indexOf('}') === val.length-1) {
            return json.fromJson(val);
        } else if (val.indexOf('[') === 0 && val.indexOf(']') === val.length-1) {
            return json.fromJson(val);
        } else {
            return val;
        }
    };

    return {
        trans: _transition,
        getData: function(node, key, def){
            key = key || undefined;
            def = def || undefined;
            if(key !== undefined && lang.isString(key)){
                var data = query(node).data(key);
                if (data && data[0] === undefined) {
                    if(query(node)[0]){ data = attr.get(query(node)[0], 'data-'+key); }
                    if (data !== undefined){ data = _attrValue(data); }
                    if (data === undefined && def !== undefined){
                        data = this.setData(node, key, def);
                    }
                }
                return (lang.isArray(data) && data.length > 0) ? data[0] : data;
            } else {
                _loadData.call(this, node);
                return query(node).data()[0];
            }
        },
        setData: function(node, key, value){
            var data = query(node).data(key, value);
            return value;
        },
        removeData: function(node, key){
            return query(node).removeData(key);
        },
        toCamel: function(str){
            return str.replace(/(\-[a-z])/g, function($1){ return $1.toUpperCase().replace('-',''); });
        },
        toDash: function(str){
            return str.replace(/([A-Z])/g, function($1){ return "-"+$1.toLowerCase(); });
        },
        toUnderscore: function(str){
            return str.replace(/([A-Z])/g, function($1){ return "_"+$1.toLowerCase(); });
        }
    };
});<|endoftext|>


<commit_before>/*globals $, COMMCAREHQ, _, ko, CC_UTILS, console*/

var VisitScheduler = (function () {
    'use strict';

    var ModuleScheduler = function(params){
        // Edits the schedule phases on the module setting page
        var self = this;
        self.home = params.home;

        self.init = function () {
            _.defer(function () {
                ko.applyBindings(self, self.home.get(0));
                self.home.on('textchange', 'input', self.change)
                // all select2's are represented by an input[type="hidden"]
                    .on('change', 'select, input[type="hidden"]', self.change)
                    .on('click', 'a:not(.header)', self.change)
                    .on('change', 'input[type="checkbox"]', self.change);

                // https://gist.github.com/mkelly12/424774/#comment-92080
                $('#module-scheduler input').on('textchange', self.change);
            });
        };

        self.change = function () {
            self.saveButton.fire('change');
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unchanged schedule settings",
            save: function() {
                self.saveButton.ajax({
                    type: 'post',
                    url: params.saveUrl,
                    data: {
                        phases: JSON.stringify(self.serializePhases()),
                        has_schedule: self.hasSchedule()
                    },
                    dataType: 'json',
                    success: function (data) {
                        COMMCAREHQ.app_manager.updateDOM(data.update);
                    }
                });
            }
        });

        var Phase = function(id, anchor, forms){
            var self = this;
            self.id = id;
            self.anchor = uiElement.input().val(anchor);
            self.anchor.observableVal = ko.observable(self.anchor.val());
            self.anchor.on("change", function(){
                self.anchor.observableVal(self.anchor.val());
            });
            CC_DETAIL_SCREEN.setUpAutocomplete(self.anchor, params.caseProperties);
            self.forms = ko.observable(forms);
            self.form_abbreviations = ko.computed(function(){
                return _.map(self.forms(), function(form){
                    return form === '' ? '(no abbreviation)' : form;
                }).join(', ');
            });
        };

        self.hasSchedule = ko.observable(params.hasSchedule);

        self.phases = ko.observableArray(
            _.map(params.schedulePhases, function(phase){
                return new Phase(phase.id, phase.anchor, phase.forms);
            })
        );
        self.phases.subscribe(function(phase){
            self.change();
        });

        self.selectedPhase = ko.observable();
        self.selectPhase = function(phase){
            self.selectedPhase(phase);
        };

        self.addPhase = function(){
            var NEW_PHASE_ID = -1;
            self.phases.push(new Phase(NEW_PHASE_ID, "", []));
        };

        self.removePhase = function(phase){
            self.phases.remove(phase);
        };

        self.serializePhases = function(){
            return _.map(self.phases(), function(phase){
                return {id: phase.id,
                        anchor: phase.anchor.val()};
            });
        };
    };

    var Scheduler = function (params) {
        var self = this;

        self.home = params.home;
        self.questions = params.questions;
        self.save_url = params.save_url;

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved schedule settings",
            save: function () {
                var isValid = self.validate();
                if (isValid){
                    var schedule = JSON.stringify(FormSchedule.unwrap(self.formSchedule));
                    self.saveButton.ajax({
                        type: 'post',
                        url: self.save_url,
                        data: {
                            schedule: schedule
                        },
                        dataType: 'json',
                        success: function (data) {
                            COMMCAREHQ.app_manager.updateDOM(data.update);
                        }
                    });
                }
            }
        });

        self.getQuestions = function (filter, excludeHidden, includeRepeat) {
            return CC_UTILS.getQuestions(self.questions, filter, excludeHidden, includeRepeat);
        };
        self.getAnswers = function (condition) {
            return CC_UTILS.getAnswers(self.questions, condition);
        };

        self.change = function () {
            self.saveButton.fire('change');
        };

        self.validate = function(){
            var errors = 0;
            var required = self.home.find(":required").not(":disabled");
            required.each(function(i, req){
                var $req = $(req);
                if ($req.val().trim().length === 0){
                    $req.closest(".control-group").addClass("error");
                    $req.siblings(".error-text").show();
                    errors += 1;
                }
                else{
                    $req.closest(".control-group").removeClass("error");
                    $req.siblings(".error-text").hide();
                }
            });

            if (errors || !self.formSchedule.scheduleEnabled()){
                self.home.find("#form-errors").show();
                return false;
            }
            else{
                self.home.find("#form-errors").hide();
                return true;
            }

        };

        self.schedulePhase = SchedulePhase.wrap(params.phase, self);
        self.formSchedule = FormSchedule.wrap(params, self, self.schedulePhase);

        self.init = function () {
            _.defer(function () {
                ko.applyBindings(self, self.home.get(0));
                self.home.on('textchange', 'input', self.change)
                     // all select2's are represented by an input[type="hidden"]
                     .on('change', 'select, input[type="hidden"]', self.change)
                     .on('click', 'a:not(.header)', self.change)
                     .on('change', 'input[type="checkbox"]', self.change);

                self.applyGlobalEventHandlers();
            });
        };

        self.applyGlobalEventHandlers = function(){
            // https://gist.github.com/mkelly12/424774/#comment-92080
            $('#visit-scheduler input').on('textchange', self.change);
        };
    };

    var ScheduleRelevancy = {
        mapping: function(self){
            return {
                include: [
                    'starts',
                    'expires',
                ]
            };
        },
        wrap: function(data){
            var self = {};
            ko.mapping.fromJS(data, ScheduleRelevancy.mapping(self), self);
            self.starts_type = ko.observable(self.starts() < 0 ? 'before' : 'after');
            self.expires_type = ko.observable(self.expires() < 0 ? 'before' : 'after');
            self.enableFormExpiry = ko.observable(self.expires() !== null);
            self.starts = ko.observable(Math.abs(self.starts()));
            self.expires = ko.observable(Math.abs(self.expires()));
            return self;
        },
        unwrap: function(self){
            return ko.mapping.toJS(self, ScheduleRelevancy.mapping(self));
        }
    };

    var ScheduleVisit = {
        mapping: function (self) {
            return {
                include: [
                    'due',
                    'type',
                    'starts',
                    'expires',
                    'repeats',
                    'increment',
                ]
            };
        },
        wrap: function (data, config) {
            var self = {
                config: config
            };
            ko.mapping.fromJS(data, ScheduleVisit.mapping(self), self);
            if (self.repeats()){
                self.due(self.increment());
                self.type('repeats');
            }
            self.starts(self.starts() * -1);
            return self;
        },

        unwrap: function (self) {
            return ko.mapping.toJS(self, ScheduleVisit.mapping(self));
        }
    };

    var SchedulePhase = {
        mapping: function(self){
            return {
                include: [
                    'anchor',
                ]
            };
        },
        wrap: function (data, config) {
            var self = {};
            ko.mapping.fromJS(data, SchedulePhase.mapping(self), self);
            return self;
        }
    };

    var FormSchedule = {
        mapping: function (self) {
            return {
                include: [
                    'expires',
                    'allow_unscheduled',
                    'transition_condition',
                    'termination_condition',
                    'schedule_form_id'
                ],
                visits: {
                    create: function (options) {
                        options.data.type = options.data.due < 0 ? 'before' : 'after';
                        options.data.due = Math.abs(options.data.due);
                        return ScheduleVisit.wrap(options.data,  self);
                    }
                }
            };
        },
        wrap: function (data, config, phase) {
            var self = {
                config: config,
                all_schedule_phase_anchors: data.all_schedule_phase_anchors,
                phase: phase
            };
            ko.mapping.fromJS(data.schedule, FormSchedule.mapping(self), self);

            // for compatibility with common template: case-config:condition
            self.allow = {
                repeats: function () {
                    return false;
                }
            };

            self.scheduleEnabled = ko.observable(data.schedule.enabled);
            self.transition = ko.computed(FormSchedule.conditionComputed(self.config, self.transition_condition));
            self.terminate = ko.computed(FormSchedule.conditionComputed(self.config, self.termination_condition));

            self.allowExpiry = ko.computed(function () {
                 return self.transition_condition.type() === 'never' &&
                     self.termination_condition.type() === 'never';
            });

            self.hasExpiry = ko.observable();

            self.hasRepeatVisit = ko.computed(function(){
                return self.visits().length > 0 && self.visits()[self.visits().length - 1].type() === 'repeats';
            });

            self.relevancy = ScheduleRelevancy.wrap(data.schedule);
            var xmlRe = /\s+|<+|>+|&+|"+|'+/g;
            self.schedule_form_id = ko.observable(data.schedule_form_id).snakeCase(xmlRe);

            self.addVisit = function () {
                self.visits.push(ScheduleVisit.wrap({
                    due: null,
                    type: 'after',
                    starts: null,
                    expires: null,
                    increment: null,
                    repeats: false
                }));
            };

            self.removeVisit = function(visit) {
                self.visits.remove(visit);
            };

            return self;
        },

        conditionComputed: function(config, condition) {
            return {
                read: function () {
                    if (condition) {
                        return condition.type() !== 'never';
                    } else {
                        return false;
                    }

                },
                write: function (value) {
                    condition.type(value ? 'always' : 'never');
                    config.saveButton.fire('change');
                }
            };
        },

        cleanCondition: function (condition) {
            if (condition.type() !== 'if') {
                condition.question(null);
                condition.answer(null);
                condition.operator(null);
            }
        },

        unwrap: function (self) {
            FormSchedule.cleanCondition(self.transition_condition);
            FormSchedule.cleanCondition(self.termination_condition);
            var schedule = ko.mapping.toJS(self, FormSchedule.mapping(self));
            schedule.enabled = self.scheduleEnabled();
            schedule.starts = self.relevancy.starts() * (self.relevancy.starts_type() === 'before' ? -1 : 1);
            if (self.relevancy.enableFormExpiry() && self.allowExpiry()){
                schedule.expires = self.relevancy.expires() * (self.relevancy.expires_type() === 'before' ? -1 : 1);
            }
            else{
                schedule.expires = null;
            }

            schedule.anchor = self.phase.anchor() || '';
            schedule.schedule_form_id = self.schedule_form_id();
            schedule.visits = _.map(schedule.visits, function(visit) {
                var due = visit.due * (visit.type === 'before' ? -1 : 1);
                var repeats = visit.type === 'repeats';
                return {
                    due: repeats ? null : due,
                    starts: visit.starts * -1,
                    expires: visit.expires,
                    repeats: repeats,
                    increment: repeats ? visit.due : null
                };
            });
            return schedule;
        }
    };

    return {
        Scheduler: Scheduler,
        ModuleScheduler: ModuleScheduler
    };
}());

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
    init: function(element, valueAccessor) {
        var list = valueAccessor();
        $(element).sortable({
            update: function(event, ui) {
                //retrieve our actual data item
                var item = ko.dataFor(ui.item.get(0));
                //figure out its new position
                var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
                //remove the item and add it back in the right spot
                if (position >= 0) {
                    list.remove(item);
                    list.splice(position, 0, item);
                }
                ui.item.remove();
            }
        });
    }
};

//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
    update: function(element, valueAccessor) {
        ko.bindingHandlers.visible.update(element, valueAccessor);
        if (valueAccessor()) {
            setTimeout(function() {
                $(element).focus().select();
            }, 0); //new tasks are not in DOM yet
        }
    }
};
<commit_msg>Add comment js comment<commit_after>/*globals $, COMMCAREHQ, _, ko, CC_UTILS, console*/

var VisitScheduler = (function () {
    'use strict';

    var ModuleScheduler = function(params){
        // Edits the schedule phases on the module setting page
        var self = this;
        self.home = params.home;

        self.init = function () {
            _.defer(function () {
                ko.applyBindings(self, self.home.get(0));
                self.home.on('textchange', 'input', self.change)
                // all select2's are represented by an input[type="hidden"]
                    .on('change', 'select, input[type="hidden"]', self.change)
                    .on('click', 'a:not(.header)', self.change)
                    .on('change', 'input[type="checkbox"]', self.change);

                // https://gist.github.com/mkelly12/424774/#comment-92080
                // textchange doesn't work with live event binding
                $('#module-scheduler input').on('textchange', self.change);
            });
        };

        self.change = function () {
            self.saveButton.fire('change');
        };

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unchanged schedule settings",
            save: function() {
                self.saveButton.ajax({
                    type: 'post',
                    url: params.saveUrl,
                    data: {
                        phases: JSON.stringify(self.serializePhases()),
                        has_schedule: self.hasSchedule()
                    },
                    dataType: 'json',
                    success: function (data) {
                        COMMCAREHQ.app_manager.updateDOM(data.update);
                    }
                });
            }
        });

        var Phase = function(id, anchor, forms){
            var self = this;
            self.id = id;
            self.anchor = uiElement.input().val(anchor);
            self.anchor.observableVal = ko.observable(self.anchor.val());
            self.anchor.on("change", function(){
                self.anchor.observableVal(self.anchor.val());
            });
            CC_DETAIL_SCREEN.setUpAutocomplete(self.anchor, params.caseProperties);
            self.forms = ko.observable(forms);
            self.form_abbreviations = ko.computed(function(){
                return _.map(self.forms(), function(form){
                    return form === '' ? '(no abbreviation)' : form;
                }).join(', ');
            });
        };

        self.hasSchedule = ko.observable(params.hasSchedule);

        self.phases = ko.observableArray(
            _.map(params.schedulePhases, function(phase){
                return new Phase(phase.id, phase.anchor, phase.forms);
            })
        );
        self.phases.subscribe(function(phase){
            self.change();
        });

        self.selectedPhase = ko.observable();
        self.selectPhase = function(phase){
            self.selectedPhase(phase);
        };

        self.addPhase = function(){
            var NEW_PHASE_ID = -1;
            self.phases.push(new Phase(NEW_PHASE_ID, "", []));
        };

        self.removePhase = function(phase){
            self.phases.remove(phase);
        };

        self.serializePhases = function(){
            return _.map(self.phases(), function(phase){
                return {id: phase.id,
                        anchor: phase.anchor.val()};
            });
        };
    };

    var Scheduler = function (params) {
        var self = this;

        self.home = params.home;
        self.questions = params.questions;
        self.save_url = params.save_url;

        self.saveButton = COMMCAREHQ.SaveButton.init({
            unsavedMessage: "You have unsaved schedule settings",
            save: function () {
                var isValid = self.validate();
                if (isValid){
                    var schedule = JSON.stringify(FormSchedule.unwrap(self.formSchedule));
                    self.saveButton.ajax({
                        type: 'post',
                        url: self.save_url,
                        data: {
                            schedule: schedule
                        },
                        dataType: 'json',
                        success: function (data) {
                            COMMCAREHQ.app_manager.updateDOM(data.update);
                        }
                    });
                }
            }
        });

        self.getQuestions = function (filter, excludeHidden, includeRepeat) {
            return CC_UTILS.getQuestions(self.questions, filter, excludeHidden, includeRepeat);
        };
        self.getAnswers = function (condition) {
            return CC_UTILS.getAnswers(self.questions, condition);
        };

        self.change = function () {
            self.saveButton.fire('change');
        };

        self.validate = function(){
            var errors = 0;
            var required = self.home.find(":required").not(":disabled");
            required.each(function(i, req){
                var $req = $(req);
                if ($req.val().trim().length === 0){
                    $req.closest(".control-group").addClass("error");
                    $req.siblings(".error-text").show();
                    errors += 1;
                }
                else{
                    $req.closest(".control-group").removeClass("error");
                    $req.siblings(".error-text").hide();
                }
            });

            if (errors || !self.formSchedule.scheduleEnabled()){
                self.home.find("#form-errors").show();
                return false;
            }
            else{
                self.home.find("#form-errors").hide();
                return true;
            }

        };

        self.schedulePhase = SchedulePhase.wrap(params.phase, self);
        self.formSchedule = FormSchedule.wrap(params, self, self.schedulePhase);

        self.init = function () {
            _.defer(function () {
                ko.applyBindings(self, self.home.get(0));
                self.home.on('textchange', 'input', self.change)
                     // all select2's are represented by an input[type="hidden"]
                     .on('change', 'select, input[type="hidden"]', self.change)
                     .on('click', 'a:not(.header)', self.change)
                     .on('change', 'input[type="checkbox"]', self.change);

                self.applyGlobalEventHandlers();
            });
        };

        self.applyGlobalEventHandlers = function(){
            // https://gist.github.com/mkelly12/424774/#comment-92080
            // textchange doesn't work with live event binding
            $('#visit-scheduler input').on('textchange', self.change);
        };
    };

    var ScheduleRelevancy = {
        mapping: function(self){
            return {
                include: [
                    'starts',
                    'expires',
                ]
            };
        },
        wrap: function(data){
            var self = {};
            ko.mapping.fromJS(data, ScheduleRelevancy.mapping(self), self);
            self.starts_type = ko.observable(self.starts() < 0 ? 'before' : 'after');
            self.expires_type = ko.observable(self.expires() < 0 ? 'before' : 'after');
            self.enableFormExpiry = ko.observable(self.expires() !== null);
            self.starts = ko.observable(Math.abs(self.starts()));
            self.expires = ko.observable(Math.abs(self.expires()));
            return self;
        },
        unwrap: function(self){
            return ko.mapping.toJS(self, ScheduleRelevancy.mapping(self));
        }
    };

    var ScheduleVisit = {
        mapping: function (self) {
            return {
                include: [
                    'due',
                    'type',
                    'starts',
                    'expires',
                    'repeats',
                    'increment',
                ]
            };
        },
        wrap: function (data, config) {
            var self = {
                config: config
            };
            ko.mapping.fromJS(data, ScheduleVisit.mapping(self), self);
            if (self.repeats()){
                self.due(self.increment());
                self.type('repeats');
            }
            self.starts(self.starts() * -1);
            return self;
        },

        unwrap: function (self) {
            return ko.mapping.toJS(self, ScheduleVisit.mapping(self));
        }
    };

    var SchedulePhase = {
        mapping: function(self){
            return {
                include: [
                    'anchor',
                ]
            };
        },
        wrap: function (data, config) {
            var self = {};
            ko.mapping.fromJS(data, SchedulePhase.mapping(self), self);
            return self;
        }
    };

    var FormSchedule = {
        mapping: function (self) {
            return {
                include: [
                    'expires',
                    'allow_unscheduled',
                    'transition_condition',
                    'termination_condition',
                    'schedule_form_id'
                ],
                visits: {
                    create: function (options) {
                        options.data.type = options.data.due < 0 ? 'before' : 'after';
                        options.data.due = Math.abs(options.data.due);
                        return ScheduleVisit.wrap(options.data,  self);
                    }
                }
            };
        },
        wrap: function (data, config, phase) {
            var self = {
                config: config,
                all_schedule_phase_anchors: data.all_schedule_phase_anchors,
                phase: phase
            };
            ko.mapping.fromJS(data.schedule, FormSchedule.mapping(self), self);

            // for compatibility with common template: case-config:condition
            self.allow = {
                repeats: function () {
                    return false;
                }
            };

            self.scheduleEnabled = ko.observable(data.schedule.enabled);
            self.transition = ko.computed(FormSchedule.conditionComputed(self.config, self.transition_condition));
            self.terminate = ko.computed(FormSchedule.conditionComputed(self.config, self.termination_condition));

            self.allowExpiry = ko.computed(function () {
                 return self.transition_condition.type() === 'never' &&
                     self.termination_condition.type() === 'never';
            });

            self.hasExpiry = ko.observable();

            self.hasRepeatVisit = ko.computed(function(){
                return self.visits().length > 0 && self.visits()[self.visits().length - 1].type() === 'repeats';
            });

            self.relevancy = ScheduleRelevancy.wrap(data.schedule);
            var xmlRe = /\s+|<+|>+|&+|"+|'+/g;
            self.schedule_form_id = ko.observable(data.schedule_form_id).snakeCase(xmlRe);

            self.addVisit = function () {
                self.visits.push(ScheduleVisit.wrap({
                    due: null,
                    type: 'after',
                    starts: null,
                    expires: null,
                    increment: null,
                    repeats: false
                }));
            };

            self.removeVisit = function(visit) {
                self.visits.remove(visit);
            };

            return self;
        },

        conditionComputed: function(config, condition) {
            return {
                read: function () {
                    if (condition) {
                        return condition.type() !== 'never';
                    } else {
                        return false;
                    }

                },
                write: function (value) {
                    condition.type(value ? 'always' : 'never');
                    config.saveButton.fire('change');
                }
            };
        },

        cleanCondition: function (condition) {
            if (condition.type() !== 'if') {
                condition.question(null);
                condition.answer(null);
                condition.operator(null);
            }
        },

        unwrap: function (self) {
            FormSchedule.cleanCondition(self.transition_condition);
            FormSchedule.cleanCondition(self.termination_condition);
            var schedule = ko.mapping.toJS(self, FormSchedule.mapping(self));
            schedule.enabled = self.scheduleEnabled();
            schedule.starts = self.relevancy.starts() * (self.relevancy.starts_type() === 'before' ? -1 : 1);
            if (self.relevancy.enableFormExpiry() && self.allowExpiry()){
                schedule.expires = self.relevancy.expires() * (self.relevancy.expires_type() === 'before' ? -1 : 1);
            }
            else{
                schedule.expires = null;
            }

            schedule.anchor = self.phase.anchor() || '';
            schedule.schedule_form_id = self.schedule_form_id();
            schedule.visits = _.map(schedule.visits, function(visit) {
                var due = visit.due * (visit.type === 'before' ? -1 : 1);
                var repeats = visit.type === 'repeats';
                return {
                    due: repeats ? null : due,
                    starts: visit.starts * -1,
                    expires: visit.expires,
                    repeats: repeats,
                    increment: repeats ? visit.due : null
                };
            });
            return schedule;
        }
    };

    return {
        Scheduler: Scheduler,
        ModuleScheduler: ModuleScheduler
    };
}());

//connect items with observableArrays
ko.bindingHandlers.sortableList = {
    init: function(element, valueAccessor) {
        var list = valueAccessor();
        $(element).sortable({
            update: function(event, ui) {
                //retrieve our actual data item
                var item = ko.dataFor(ui.item.get(0));
                //figure out its new position
                var position = ko.utils.arrayIndexOf(ui.item.parent().children(), ui.item[0]);
                //remove the item and add it back in the right spot
                if (position >= 0) {
                    list.remove(item);
                    list.splice(position, 0, item);
                }
                ui.item.remove();
            }
        });
    }
};

//control visibility, give element focus, and select the contents (in order)
ko.bindingHandlers.visibleAndSelect = {
    update: function(element, valueAccessor) {
        ko.bindingHandlers.visible.update(element, valueAccessor);
        if (valueAccessor()) {
            setTimeout(function() {
                $(element).focus().select();
            }, 0); //new tasks are not in DOM yet
        }
    }
};
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var global = this;
traceur.define('semantics', function() {
  'use strict';

  var TokenType = traceur.syntax.TokenType;
  var ParseTreeVisitor = traceur.syntax.ParseTreeVisitor;
  var IdentifierExpression = traceur.syntax.trees.IdentifierExpression;
  var IdentifierToken = traceur.syntax.IdentifierToken;
  var ParseTreeType = traceur.syntax.trees.ParseTreeType;
  var SourcePosition = traceur.syntax.SourcePosition;

  /**
   * Finds the identifiers that are not bound in a program. Run this after all
   * module imports have been resolved.
   *
   * This is run after all transformations to simplify the analysis. In
   * particular we can ignore:
   *   - module imports
   *   - block scope (let/const)
   *   - foreach
   *   - generators
   *   - destructuring/rest
   *   - classes/traits
   * as all of these nodes will have been replaced. We assume that synthetic
   * variables (generated by Traceur) will bind correctly, so we don't worry
   * about binding them as well as user defined variables.
   *
   * @param {ErrorReporter} reporter
   * @extends {ParseTreeVisitor}
   * @constructor
   */
  function FreeVariableChecker(reporter) {
    ParseTreeVisitor.call(this);
    this.reporter_ = reporter;
  }

  /**
   * Represents the link in the scope chain.
   * @param {Scope} parent The parent scope, or null if top level scope.
   * @constructor
   */
  function Scope(parent) {
    this.parent = parent;
    this.references = Object.create(null);
    this.declarations = Object.create(null);
  }

  /**
   * Gets the name of an identifier expression or token
   * @param {IdentifierExpression|IdentifierToken|string} name
   * @returns {string}
   */
  function getVariableName(name) {
      if (name instanceof IdentifierExpression) {
        name = name.identifierToken;
      }
      if (name instanceof IdentifierToken) {
        name = name.value;
      }
      return name;
  }

  function getIdentifier(tree) {
    while (tree.type == ParseTreeType.PAREN_EXPRESSION) {
      tree = tree.expression;
    }
    if (tree.type == ParseTreeType.IDENTIFIER_EXPRESSION) {
      return tree;
    }
    return null;
  }

  /**
   * Checks the program for free variables, and reports an error when it
   * encounters any.
   *
   * @param {ErrorReporter} reporter
   * @param {Program} tree
   */
  FreeVariableChecker.checkProgram = function(reporter, tree) {
    new FreeVariableChecker(reporter).visitProgram(tree, global);
  }

  var proto = ParseTreeVisitor.prototype;
  FreeVariableChecker.prototype = {
    __proto__: proto,

    /** Current scope (block, program) */
    scope_: null,

    /**
     * Pushes a scope.
     * @return {Scope}
     */
    pushScope_: function() {
      return this.scope_ = new Scope(this.scope_);
    },

    /**
     * Pops scope, tracks proper matching of push_/pop_ operations.
     * @param {Scope} scope
     */
    pop_: function(scope) {
      if (this.scope_ != scope) {
        throw new Error('FreeVariableChecker scope mismatch');
      }

      this.validateScope_();

      this.scope_ = scope.parent;
    },

    visitBlock: function(tree) {
      // block scope was already dealt with
      this.visitStatements_(tree.statements);
    },

    visitProgram: function(tree, global) {
      var scope = this.pushScope_();

      // Declare variables from the global scope.
      // TODO(jmesserly): this should be done through the module loaders, and by
      // providing the user the option to import URLs like '@dom', but for now
      // just bind against everything in the global scope.
      Object.getOwnPropertyNames(global).forEach(this.declareVariable_, this);

      this.visitStatements_(tree.programElements);

      this.pop_(scope);
    },

    visitStatements_: function(statements) {
      statements.forEach(function(s) {
        if (s.type == ParseTreeType.FUNCTION_DECLARATION) {
          // Declare the function's name in the outer scope.
          // We need to do this here, and not inside visitFunctionDeclaration,
          // because function expressions shouldn't have their names added. Only
          // in statement contexts does this happen.
          this.declareVariable_(s.name);
        }
        this.visitAny(s);
      }, this);
    },

    visitFunctionDeclaration: function(tree) {
      var scope = this.pushScope_();

      // Declare the function name and formal parameters inside the function
      this.declareVariable_(tree.name);
      tree.formalParameterList.parameters.forEach(this.declareVariable_, this);

      this.visitAny(tree.functionBody);

      this.pop_(scope);
    },

    visitGetAccessor: function(tree) {
      var scope = this.pushScope_();

      this.visitAny(tree.body);

      this.pop_(scope);
    },

    visitSetAccessor: function(tree) {
      var scope = this.pushScope_();

      this.declareVariable_(tree.parameter);
      this.visitAny(tree.body);

      this.pop_(scope);
    },

    visitCatch: function(tree) {
      var scope = this.pushScope_();

      this.declareVariable_(tree.exceptionName);

      this.visitAny(tree.catchBody);

      this.pop_(scope);
    },

    visitVariableDeclarationList: function(tree) {
      if (tree.declarationType != TokenType.VAR) {
        throw new Error('let and const should have been rewritten');
      }

      tree.declarations.forEach(function(d) {
        this.declareVariable_(d.lvalue);
        this.visitAny(d.initializer);
      }, this);
    },

    visitIdentifierExpression: function(tree) {
      var name = getVariableName(tree);
      var scope = this.scope_;
      if (!(name in scope.references)) {
        scope.references[name] = tree.location;
      }
    },

    declareVariable_: function(tree) {
      var name = getVariableName(tree);
      if (name) {
        var scope = this.scope_;
        if (!(name in scope.declarations)) {
          scope.declarations[name] = tree.location;
        }
      }
    },

    /**
     * Once we've visited the body of a scope, we check that all variables were
     * declared. If they haven't been, we promote the references to the parent
     * scope (because ES can close over variables, as well as reference them
     * before declaration).
     *
     * At the top level scope we issue errors for any remaining free variables.
     */
    validateScope_: function() {
      var scope = this.scope_;

      // Promote any unresolved references to the parent scope.
      var errors = [];
      for (var name in scope.references) {
        if (!(name in scope.declarations)) {
          var location = scope.references[name];
          if (!scope.parent) {
            if (!location) {
              // If location is null, it means we're getting errors from code we
              // generated. This is an internal error.
              throw new Error('generated variable ' + name + ' is not defined');
            }

            // If we're at the top level scope, then issue an error for
            // remaining free variables.
            errors.push([location.start, '%s is not defined', name]);
          } else if (!(name in scope.parent.references)) {
            scope.parent.references[name] = location;
          }
        }
      }

      if (errors.length) {
        // Issue errors in source order.
        errors.sort(function(x, y) { return x[0].offset - y[0].offset; });
        errors.forEach(function(e) { this.reportError_.apply(this, e); }, this);
      }
    },

    /**
     * @param {SourcePosition} start location
     * @param {string} format
     * @param {...Object} var_args
     */
    reportError_: function(location, format, var_args) {
      var args = Array.prototype.slice.call(arguments);
      args[0] = location;
      this.reporter_.reportError.apply(this.reporter_, args);
    }
  };

  return {
    FreeVariableChecker: FreeVariableChecker
  };
});
<commit_msg>Traceur: Walk prototype chain of global when declaring globals.<commit_after>// Copyright 2011 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var global = this;
traceur.define('semantics', function() {
  'use strict';

  var TokenType = traceur.syntax.TokenType;
  var ParseTreeVisitor = traceur.syntax.ParseTreeVisitor;
  var IdentifierExpression = traceur.syntax.trees.IdentifierExpression;
  var IdentifierToken = traceur.syntax.IdentifierToken;
  var ParseTreeType = traceur.syntax.trees.ParseTreeType;
  var SourcePosition = traceur.syntax.SourcePosition;

  /**
   * Finds the identifiers that are not bound in a program. Run this after all
   * module imports have been resolved.
   *
   * This is run after all transformations to simplify the analysis. In
   * particular we can ignore:
   *   - module imports
   *   - block scope (let/const)
   *   - foreach
   *   - generators
   *   - destructuring/rest
   *   - classes/traits
   * as all of these nodes will have been replaced. We assume that synthetic
   * variables (generated by Traceur) will bind correctly, so we don't worry
   * about binding them as well as user defined variables.
   *
   * @param {ErrorReporter} reporter
   * @extends {ParseTreeVisitor}
   * @constructor
   */
  function FreeVariableChecker(reporter) {
    ParseTreeVisitor.call(this);
    this.reporter_ = reporter;
  }

  /**
   * Represents the link in the scope chain.
   * @param {Scope} parent The parent scope, or null if top level scope.
   * @constructor
   */
  function Scope(parent) {
    this.parent = parent;
    this.references = Object.create(null);
    this.declarations = Object.create(null);
  }

  /**
   * Gets the name of an identifier expression or token
   * @param {IdentifierExpression|IdentifierToken|string} name
   * @returns {string}
   */
  function getVariableName(name) {
      if (name instanceof IdentifierExpression) {
        name = name.identifierToken;
      }
      if (name instanceof IdentifierToken) {
        name = name.value;
      }
      return name;
  }

  function getIdentifier(tree) {
    while (tree.type == ParseTreeType.PAREN_EXPRESSION) {
      tree = tree.expression;
    }
    if (tree.type == ParseTreeType.IDENTIFIER_EXPRESSION) {
      return tree;
    }
    return null;
  }

  /**
   * Checks the program for free variables, and reports an error when it
   * encounters any.
   *
   * @param {ErrorReporter} reporter
   * @param {Program} tree
   */
  FreeVariableChecker.checkProgram = function(reporter, tree) {
    new FreeVariableChecker(reporter).visitProgram(tree, global);
  }

  var proto = ParseTreeVisitor.prototype;
  FreeVariableChecker.prototype = {
    __proto__: proto,

    /** Current scope (block, program) */
    scope_: null,

    /**
     * Pushes a scope.
     * @return {Scope}
     */
    pushScope_: function() {
      return this.scope_ = new Scope(this.scope_);
    },

    /**
     * Pops scope, tracks proper matching of push_/pop_ operations.
     * @param {Scope} scope
     */
    pop_: function(scope) {
      if (this.scope_ != scope) {
        throw new Error('FreeVariableChecker scope mismatch');
      }

      this.validateScope_();

      this.scope_ = scope.parent;
    },

    visitBlock: function(tree) {
      // block scope was already dealt with
      this.visitStatements_(tree.statements);
    },

    visitProgram: function(tree, global) {
      var scope = this.pushScope_();

      // Declare variables from the global scope.
      // TODO(jmesserly): this should be done through the module loaders, and by
      // providing the user the option to import URLs like '@dom', but for now
      // just bind against everything in the global scope.
      var object = global;
      while (object) {
        Object.getOwnPropertyNames(object).forEach(this.declareVariable_, this);
        object = Object.getPrototypeOf(object);
      }

      this.visitStatements_(tree.programElements);

      this.pop_(scope);
    },

    visitStatements_: function(statements) {
      statements.forEach(function(s) {
        if (s.type == ParseTreeType.FUNCTION_DECLARATION) {
          // Declare the function's name in the outer scope.
          // We need to do this here, and not inside visitFunctionDeclaration,
          // because function expressions shouldn't have their names added. Only
          // in statement contexts does this happen.
          this.declareVariable_(s.name);
        }
        this.visitAny(s);
      }, this);
    },

    visitFunctionDeclaration: function(tree) {
      var scope = this.pushScope_();

      // Declare the function name and formal parameters inside the function
      this.declareVariable_(tree.name);
      tree.formalParameterList.parameters.forEach(this.declareVariable_, this);

      this.visitAny(tree.functionBody);

      this.pop_(scope);
    },

    visitGetAccessor: function(tree) {
      var scope = this.pushScope_();

      this.visitAny(tree.body);

      this.pop_(scope);
    },

    visitSetAccessor: function(tree) {
      var scope = this.pushScope_();

      this.declareVariable_(tree.parameter);
      this.visitAny(tree.body);

      this.pop_(scope);
    },

    visitCatch: function(tree) {
      var scope = this.pushScope_();

      this.declareVariable_(tree.exceptionName);

      this.visitAny(tree.catchBody);

      this.pop_(scope);
    },

    visitVariableDeclarationList: function(tree) {
      if (tree.declarationType != TokenType.VAR) {
        throw new Error('let and const should have been rewritten');
      }

      tree.declarations.forEach(function(d) {
        this.declareVariable_(d.lvalue);
        this.visitAny(d.initializer);
      }, this);
    },

    visitIdentifierExpression: function(tree) {
      var name = getVariableName(tree);
      var scope = this.scope_;
      if (!(name in scope.references)) {
        scope.references[name] = tree.location;
      }
    },

    declareVariable_: function(tree) {
      var name = getVariableName(tree);
      if (name) {
        var scope = this.scope_;
        if (!(name in scope.declarations)) {
          scope.declarations[name] = tree.location;
        }
      }
    },

    /**
     * Once we've visited the body of a scope, we check that all variables were
     * declared. If they haven't been, we promote the references to the parent
     * scope (because ES can close over variables, as well as reference them
     * before declaration).
     *
     * At the top level scope we issue errors for any remaining free variables.
     */
    validateScope_: function() {
      var scope = this.scope_;

      // Promote any unresolved references to the parent scope.
      var errors = [];
      for (var name in scope.references) {
        if (!(name in scope.declarations)) {
          var location = scope.references[name];
          if (!scope.parent) {
            if (!location) {
              // If location is null, it means we're getting errors from code we
              // generated. This is an internal error.
              throw new Error('generated variable ' + name + ' is not defined');
            }

            // If we're at the top level scope, then issue an error for
            // remaining free variables.
            errors.push([location.start, '%s is not defined', name]);
          } else if (!(name in scope.parent.references)) {
            scope.parent.references[name] = location;
          }
        }
      }

      if (errors.length) {
        // Issue errors in source order.
        errors.sort(function(x, y) { return x[0].offset - y[0].offset; });
        errors.forEach(function(e) { this.reportError_.apply(this, e); }, this);
      }
    },

    /**
     * @param {SourcePosition} start location
     * @param {string} format
     * @param {...Object} var_args
     */
    reportError_: function(location, format, var_args) {
      var args = Array.prototype.slice.call(arguments);
      args[0] = location;
      this.reporter_.reportError.apply(this.reporter_, args);
    }
  };

  return {
    FreeVariableChecker: FreeVariableChecker
  };
});
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012-2015 S-Core Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var fsMgr = require('./fs-manager');
var Resource = fsMgr.Resource;
var pty = require('pty.js');
var path = require('path');
var _ = require('underscore');
var spawn = require('child_process').spawn;
//var express = require('express.io');
var express = require('express');
var shortid = require('shortid');

var authMgr = require('../../common/auth-manager');
var utils = require('../../common/utils');
var logger = require('../../common/log-manager');
var config = require('../../common/conf-manager').conf;

var ClientError = utils.ClientError;
var ServerError = utils.ServerError;

// Control Terminal Manager
/* termMgr = {
    processID1: {
        cid: clientID,
        term: child_object,
        status: 'RUN'/'STOP'
    },

    processID2: {
        ...
    }
}
*/
var termMgr = {};

exports.route = {
    // if occured io.emit('console:exec', data) event
    exec: function (req) {
        /*
            req.data = {
                cid: <client Id>,
                fsid: <fs Id>,
                cmd: <command>,
                args: <arguments>,
                opt: {
                    name: <name>,
                    cols: <columes>,
                    rows: <rows>,
                    cwd: <current working directory>,
                    env: <enviroments>
                }
            }
        */
        // TODO check security. Can caller access the fs(fsid)?
        // Make policy: Only fs owner can call console api?

        //logger.debug('console:exec', req.data, req.handshake);
        var cid = req.data.cid;
        var cmd = req.data.cmd;
        var args = req.data.args;

        if (!req.handshake.user) {
            return req.io.emit('error', {
                cid: cid,
                data: new Error('Unauthorized access')
            });
        }
        var rootPath = (new fsMgr.WebidaFS(req.data.fsid)).getRootPath();

        var name = req.data.opt.name || 'xterm-color';
        var cols = req.data.opt.cols || 80;
        var rows = req.data.opt.rows || 30;
        var cwd = path.resolve(rootPath, req.data.opt.cwd || '');

        var env = _.clone(process.env);
        for (var key in req.data.opt.env) {
            if (req.data.opt.env.hasOwnProperty(key)) {
                env[key] = req.data.opt.env[key];
            }
        }
        env.HOME = rootPath; // TODO rootPath is home?
        env.PATH = path.resolve(__dirname, '../bin') + ':' + env.PATH;

        var opt = {
            name: name,
            cols: cols,
            rows: rows,
            cwd: cwd,
            env: env
        };

        // execute console process
        var term = pty.spawn(cmd, args, opt);
        var pid = term.pid;

        // Add console's object info
        termMgr[pid] = {
            cid: cid,
            term: term,
            status: 'RUN'
        };

        // send to client about console's pid
        var resAck = {
            cid: cid,
            pid: pid
        };
        req.io.emit('ack', resAck);

        // register console's stdout event
        term.on('data', function (data) {
            // if commnad is invaild, occur error event.
            if (data.match(/^execvp\([0-9]*\)/)) {
                req.io.emit('error', {
                    cid: cid,
                    pid: pid,
                    data: cmd + ': command not found'
                });
            } else {
                req.io.emit('data', {
                    cid: cid,
                    pid: pid,
                    data: data
                });
            }
        });

        // register console's stderr event
        term.on('error', function (data) {
            // EIO, happens when someone closes our child
            // process: the only process in the terminal.

            // if close event occurs. execute self.kill() in pty.js
            if (data.code !== 'EIO') {
                req.io.emit('error', {
                    cid: cid,
                    pid: pid,
                    data: data
                });
            }
            logger.debug('console error', req, term, termMgr);
        });

        // register console's exit event
        term.on('close', function (data) {
            // if console program is ended, return ture / false.
            req.io.emit('close', {
                cid: cid,
                pid: pid,
                data: data
            });

            // Remove console's object info
            if (termMgr[pid]) {
                delete termMgr[pid];
            }
        });
    },
    // if occured io.emit('console:write', data) event
    write: function (req) {
        /*
            req.data = {
                pid: <process Id>,
                cmd: <user input>
            }
        */

        var pid = req.data.pid;
        var cmd = req.data.cmd;

        if (termMgr[pid]) {
            termMgr[pid].term.write(cmd);
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:kill', data) event
    kill: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;

        if (termMgr[pid]) {
            termMgr[pid].term.kill();
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:pause', data) event
    pause: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;

        if (termMgr[pid]) {
            termMgr[pid].status = 'STOP';
            termMgr[pid].term.pause();
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:resume', data) event
    resume: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;
        if (termMgr[pid]) {
            termMgr[pid].status = 'RUN';
            termMgr[pid].term.resume();
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:setEncoding', data) event
    setEncoding: function (req) {
        /*
            req.data = {
                pid: <process Id>,
                encoding : <data encoding> (default: utf8)
            }
        */
        var pid = req.data.pid;
        var encoding = req.data.encoding || 'utf8';

        if (termMgr[pid]) {
            termMgr[pid].term.setEncoding(encoding);
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },

    resize: function (req) {
        /*
            req.data = {
                pid: <process Id>,
                cols: <columns>,
                rows: <rows>
            }
        */
        var pid = req.data.pid;
        var cols = req.data.cols;
        var rows = req.data.rows;

        if (termMgr[pid]) {
            termMgr[pid].term.resize(cols, rows);
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }

    },

    getStatus: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;
        if (termMgr[pid]) {
            req.io.emit('status', {
                cid: termMgr[pid].cid,
                pid: termMgr[pid].pid,
                data: termMgr[pid].status
            });
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },

    disconnect: function () {
        for (var key in termMgr) {
            if (termMgr.hasOwnProperty(key)) {
                termMgr[key].term.kill();
            }
        }

        termMgr = {};
    }
};

var execTable = {};

function removeProc(proc) {
    logger.debug('Remove proc', proc.pid);
    proc.removeAllListeners();
    clearTimeout(proc._timeoutId);
    delete execTable[proc.pid];
}
function addProc(proc) {
    logger.debug('Add proc', proc.pid);
    proc._stdout = '';
    proc._stderr = '';
    proc._timeoutId = setTimeout(function () {
        logger.debug('Timeout proc', proc.pid);
        proc.kill();
    }, config.services.fs.exec.timeoutSecs * 1000);
    execTable[proc.pid] = proc;
}
function startProc(cwdRsc, cmd, args, callback) {
    logger.debug('Exec start', cmd, args);

    // TODO env will be removed
    var env = _.clone(process.env);
    env.HOME = cwdRsc.wfs.getRootPath();
    env.PATH = path.resolve(__dirname, '../bin') + ':' + env.PATH;

    /* execute command */
    var proc = spawn(cmd, args, {
        cwd: cwdRsc.localPath,
        detached: false,
        env: env
    });

    addProc(proc);

    proc.stdout.on('data', function (data) {
        proc._stdout += data;
    });

    proc.stderr.on('data', function (data) {
        proc._stderr += data;
    });

    proc.on('exit', function (code) {
        logger.debug('Exec close', proc.pid, 'code:'+code, 'stdout:'+proc._stdout, 'stderr:'+proc._stderr);
        removeProc(proc);
        /*
        if (code === 0) {
            return callback(code, proc._stdout, proc._stderr);
        }
        */
        if (code === null) {
            return callback(new Error('Abnormal exit'), proc._stdout, proc._stderr);
        }
        callback(null, proc._stdout, proc._stderr, code);
    });

    proc.on('error', function (err) {
        logger.debug('Exec error', proc.pid, err, proc._stdout, proc._stderr);
        removeProc(proc);
        if (err.errno === 'EPERM') {
            callback(new ClientError('Exec timeout'), proc._stdout, proc._stderr);
        } else {
            callback(new ServerError('Exec failed'), proc._stdout, proc._stderr);
        }
    });
}
function exec(cwdUrl, cmdInfo, callback) {
    function escapeShellCmdComponent(cmd) {
        return '"' + cmd.replace(/(["$`\\])/g, '\\$1') + '"';
    };

    var cwdRsc = new Resource(cwdUrl);
    /* request.info is command information
    * info.cmd : command
    * info.args : command arguments
    */
    var cmd = cmdInfo.cmd;
    if (!cmd) {
        return callback('Command is not specified.');
    }

    if (!config.services.fs.exec.validExecCommands.hasOwnProperty(cmd)) {
        return callback('Invalid command');
    }
    var subCmds = config.services.fs.exec.validExecCommands[cmd];
    if (subCmds && !_.contains(subCmds, cmdInfo.args[0])) {
        return callback('Invalid argument');
    }

    if (config.services.fs.lxc.useLxc) {
        var name = config.services.fs.lxc.containerNamePrefix + '-' + shortid.generate();
        var confPath = config.services.fs.lxc.confPath;
        var cwdLxcPath = path.join('/fs', cwdRsc.pathname);
        var cmdArgsStr = _.map(cmdInfo.args, escapeShellCmdComponent).join(' ');
        var cmdInLxc = 'cd "' + cwdLxcPath + '"; ' + cmd + ' ' + cmdArgsStr;

        var args = [
            '/usr/bin/lxc-execute',
            '-n', name,
            '-f', confPath,
            '-s', 'lxc.rootfs=' + config.services.fs.lxc.rootfsPath,
            '-s', 'lxc.mount.entry=' + cwdRsc.wfs.getRootPath() + ' fs none rw,bind 0 0',
            '-s', 'lxc.network.ipv4=10.0.3.2/24',
            '-s', 'lxc.network.ipv4.gateway=10.0.3.1',
            '--', 'su', config.services.fs.lxc.userid, '-c', cmdInLxc
        ];
        startProc(cwdRsc, 'sudo', args, callback);
    } else {
        startProc(cwdRsc, cmd, cmdInfo.args, callback);
    }
}
exports.exec = exec;

exports.router = new express.Router();

exports.router.post('/webida/api/fs/exec/:fsid/*',
    authMgr.verifyToken,
    function (req, res, next) {
        var rsc = 'fs:' + req.params.fsid + '/*';
        authMgr.checkAuthorize({uid:req.user.uid, action:'fs:exec', rsc:rsc}, res, next);
    },
    utils.keepConnect(),
    function (req, res) {
        var fsid = req.params.fsid;
        var cwdPath = path.join('/', req.params[0]);
        logger.info('exec path = ', cwdPath);
        var cwdUrl = 'wfs://' + fsid + cwdPath;
        var cmdInfo = JSON.parse(req.body.info);
        var sessionID = req.body.sessionID;
        var uid = req.user && req.user.uid;
        if (uid === undefined) {
            return res.sendfail(new ClientError('invalid uid'));
        }

        fsMgr.checkLock(fsid, cwdPath, cmdInfo, function(err) {
            if (err)
                return res.sendfail(err);

            exec(cwdUrl, cmdInfo, function (err, stdout, stderr, ret) {
                if (err)
                    return res.sendfail(err);

                fsMgr.updateByExec(cmdInfo,uid, fsid, cwdPath, cwdUrl, sessionID, function() {
                    return res.sendok({stdout: stdout, stderr: stderr, ret:ret});
                });
            });
        });
    }
);
<commit_msg>[BUGFIX] (WTC-3369) make lxc-execute use dynamic IP host address (2~254)<commit_after>/*
 * Copyright (c) 2012-2015 S-Core Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var fsMgr = require('./fs-manager');
var Resource = fsMgr.Resource;
var pty = require('pty.js');
var path = require('path');
var _ = require('underscore');
var spawn = require('child_process').spawn;
//var express = require('express.io');
var express = require('express');
var shortid = require('shortid');

var authMgr = require('../../common/auth-manager');
var utils = require('../../common/utils');
var logger = require('../../common/log-manager');
var config = require('../../common/conf-manager').conf;

var ClientError = utils.ClientError;
var ServerError = utils.ServerError;

// Control Terminal Manager
/* termMgr = {
    processID1: {
        cid: clientID,
        term: child_object,
        status: 'RUN'/'STOP'
    },

    processID2: {
        ...
    }
}
*/
var termMgr = {};

exports.route = {
    // if occured io.emit('console:exec', data) event
    exec: function (req) {
        /*
            req.data = {
                cid: <client Id>,
                fsid: <fs Id>,
                cmd: <command>,
                args: <arguments>,
                opt: {
                    name: <name>,
                    cols: <columes>,
                    rows: <rows>,
                    cwd: <current working directory>,
                    env: <enviroments>
                }
            }
        */
        // TODO check security. Can caller access the fs(fsid)?
        // Make policy: Only fs owner can call console api?

        //logger.debug('console:exec', req.data, req.handshake);
        var cid = req.data.cid;
        var cmd = req.data.cmd;
        var args = req.data.args;

        if (!req.handshake.user) {
            return req.io.emit('error', {
                cid: cid,
                data: new Error('Unauthorized access')
            });
        }
        var rootPath = (new fsMgr.WebidaFS(req.data.fsid)).getRootPath();

        var name = req.data.opt.name || 'xterm-color';
        var cols = req.data.opt.cols || 80;
        var rows = req.data.opt.rows || 30;
        var cwd = path.resolve(rootPath, req.data.opt.cwd || '');

        var env = _.clone(process.env);
        for (var key in req.data.opt.env) {
            if (req.data.opt.env.hasOwnProperty(key)) {
                env[key] = req.data.opt.env[key];
            }
        }
        env.HOME = rootPath; // TODO rootPath is home?
        env.PATH = path.resolve(__dirname, '../bin') + ':' + env.PATH;

        var opt = {
            name: name,
            cols: cols,
            rows: rows,
            cwd: cwd,
            env: env
        };

        // execute console process
        var term = pty.spawn(cmd, args, opt);
        var pid = term.pid;

        // Add console's object info
        termMgr[pid] = {
            cid: cid,
            term: term,
            status: 'RUN'
        };

        // send to client about console's pid
        var resAck = {
            cid: cid,
            pid: pid
        };
        req.io.emit('ack', resAck);

        // register console's stdout event
        term.on('data', function (data) {
            // if commnad is invaild, occur error event.
            if (data.match(/^execvp\([0-9]*\)/)) {
                req.io.emit('error', {
                    cid: cid,
                    pid: pid,
                    data: cmd + ': command not found'
                });
            } else {
                req.io.emit('data', {
                    cid: cid,
                    pid: pid,
                    data: data
                });
            }
        });

        // register console's stderr event
        term.on('error', function (data) {
            // EIO, happens when someone closes our child
            // process: the only process in the terminal.

            // if close event occurs. execute self.kill() in pty.js
            if (data.code !== 'EIO') {
                req.io.emit('error', {
                    cid: cid,
                    pid: pid,
                    data: data
                });
            }
            logger.debug('console error', req, term, termMgr);
        });

        // register console's exit event
        term.on('close', function (data) {
            // if console program is ended, return ture / false.
            req.io.emit('close', {
                cid: cid,
                pid: pid,
                data: data
            });

            // Remove console's object info
            if (termMgr[pid]) {
                delete termMgr[pid];
            }
        });
    },
    // if occured io.emit('console:write', data) event
    write: function (req) {
        /*
            req.data = {
                pid: <process Id>,
                cmd: <user input>
            }
        */

        var pid = req.data.pid;
        var cmd = req.data.cmd;

        if (termMgr[pid]) {
            termMgr[pid].term.write(cmd);
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:kill', data) event
    kill: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;

        if (termMgr[pid]) {
            termMgr[pid].term.kill();
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:pause', data) event
    pause: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;

        if (termMgr[pid]) {
            termMgr[pid].status = 'STOP';
            termMgr[pid].term.pause();
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:resume', data) event
    resume: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;
        if (termMgr[pid]) {
            termMgr[pid].status = 'RUN';
            termMgr[pid].term.resume();
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },
    // if occured io.emit('console:setEncoding', data) event
    setEncoding: function (req) {
        /*
            req.data = {
                pid: <process Id>,
                encoding : <data encoding> (default: utf8)
            }
        */
        var pid = req.data.pid;
        var encoding = req.data.encoding || 'utf8';

        if (termMgr[pid]) {
            termMgr[pid].term.setEncoding(encoding);
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },

    resize: function (req) {
        /*
            req.data = {
                pid: <process Id>,
                cols: <columns>,
                rows: <rows>
            }
        */
        var pid = req.data.pid;
        var cols = req.data.cols;
        var rows = req.data.rows;

        if (termMgr[pid]) {
            termMgr[pid].term.resize(cols, rows);
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }

    },

    getStatus: function (req) {
        /*
            req.data = {
                pid: <process Id>
            }
        */
        var pid = req.data.pid;
        if (termMgr[pid]) {
            req.io.emit('status', {
                cid: termMgr[pid].cid,
                pid: termMgr[pid].pid,
                data: termMgr[pid].status
            });
        } else {
            req.io.emit('error', {
                pid: pid,
                data: pid + ': arguments must be process ID'
            });
        }
    },

    disconnect: function () {
        for (var key in termMgr) {
            if (termMgr.hasOwnProperty(key)) {
                termMgr[key].term.kill();
            }
        }

        termMgr = {};
    }
};

var execTable = {};
var ipHostTable = {};
var ipHostLastUsed = 2;     // 2 ~ 254
function removeProc(proc) {
    logger.debug('Remove proc', proc.pid);
    proc.removeAllListeners();
    clearTimeout(proc._timeoutId);
    delete execTable[proc.pid];
    delete ipHostTable[proc.pid];
}
function addProc(proc, ipHostAddr) {
    logger.debug('Add proc', proc.pid);
    proc._stdout = '';
    proc._stderr = '';
    proc._timeoutId = setTimeout(function () {
        logger.debug('Timeout proc', proc.pid);
        proc.kill();
    }, config.services.fs.exec.timeoutSecs * 1000);
    execTable[proc.pid] = proc;
    ipHostTable[proc.pid] = ipHostAddr;
}
function startProc(cwdRsc, cmd, args, ipHostAddr, callback) {
    logger.debug('Exec start', cmd, args);

    // TODO env will be removed
    var env = _.clone(process.env);
    env.HOME = cwdRsc.wfs.getRootPath();
    env.PATH = path.resolve(__dirname, '../bin') + ':' + env.PATH;

    /* execute command */
    var proc = spawn(cmd, args, {
        cwd: cwdRsc.localPath,
        detached: false,
        env: env
    });

    addProc(proc, ipHostAddr);

    proc.stdout.on('data', function (data) {
        proc._stdout += data;
    });

    proc.stderr.on('data', function (data) {
        proc._stderr += data;
    });

    proc.on('exit', function (code) {
        logger.debug('Exec close', proc.pid, 'code:'+code, 'stdout:'+proc._stdout, 'stderr:'+proc._stderr);
        removeProc(proc);
        /*
        if (code === 0) {
            return callback(code, proc._stdout, proc._stderr);
        }
        */
        if (code === null) {
            return callback(new Error('Abnormal exit'), proc._stdout, proc._stderr);
        }
        callback(null, proc._stdout, proc._stderr, code);
    });

    proc.on('error', function (err) {
        logger.debug('Exec error', proc.pid, err, proc._stdout, proc._stderr);
        removeProc(proc);
        if (err.errno === 'EPERM') {
            callback(new ClientError('Exec timeout'), proc._stdout, proc._stderr);
        } else {
            callback(new ServerError('Exec failed'), proc._stdout, proc._stderr);
        }
    });
}
function exec(cwdUrl, cmdInfo, callback) {
    function escapeShellCmdComponent(cmd) {
        return '"' + cmd.replace(/(["$`\\])/g, '\\$1') + '"';
    }

    function getAvailableIPHostAddress(){
        var usedIPHostAddr = _.values(ipHostTable);
        var nextIpHostAddr = ((ipHostLastUsed++ - 2) % 253) + 2;
        while(usedIPHostAddr.indexOf(nextIpHostAddr) > -1){
            nextIpHostAddr = ((ipHostLastUsed++ - 2) % 253) + 2;
        }
        return nextIpHostAddr;
    }

    var cwdRsc = new Resource(cwdUrl);
    /* request.info is command information
    * info.cmd : command
    * info.args : command arguments
    */
    var cmd = cmdInfo.cmd;
    if (!cmd) {
        return callback('Command is not specified.');
    }

    if (!config.services.fs.exec.validExecCommands.hasOwnProperty(cmd)) {
        return callback('Invalid command');
    }
    var subCmds = config.services.fs.exec.validExecCommands[cmd];
    if (subCmds && !_.contains(subCmds, cmdInfo.args[0])) {
        return callback('Invalid argument');
    }

    if (config.services.fs.lxc.useLxc) {
        var name = config.services.fs.lxc.containerNamePrefix + '-' + shortid.generate();
        var confPath = config.services.fs.lxc.confPath;
        var cwdLxcPath = path.join('/fs', cwdRsc.pathname);
        var cmdArgsStr = _.map(cmdInfo.args, escapeShellCmdComponent).join(' ');
        var cmdInLxc = 'cd "' + cwdLxcPath + '"; ' + cmd + ' ' + cmdArgsStr;

        var ipHostAddr = getAvailableIPHostAddress();
        var args = [
            '/usr/bin/lxc-execute',
            '-n', name,
            '-f', confPath,
            '-s', 'lxc.rootfs=' + config.services.fs.lxc.rootfsPath,
            '-s', 'lxc.mount.entry=' + cwdRsc.wfs.getRootPath() + ' fs none rw,bind 0 0',
            '-s', 'lxc.network.ipv4=10.0.3.'+ ipHostAddr +'/24',
            '-s', 'lxc.network.ipv4.gateway=auto',
            '--', 'su', config.services.fs.lxc.userid, '-c', cmdInLxc
        ];
        startProc(cwdRsc, 'sudo', args, ipHostAddr, callback);
    } else {
        startProc(cwdRsc, cmd, cmdInfo.args, null, callback);
    }
}
exports.exec = exec;

exports.router = new express.Router();

exports.router.post('/webida/api/fs/exec/:fsid/*',
    authMgr.verifyToken,
    function (req, res, next) {
        var rsc = 'fs:' + req.params.fsid + '/*';
        authMgr.checkAuthorize({uid:req.user.uid, action:'fs:exec', rsc:rsc}, res, next);
    },
    utils.keepConnect(),
    function (req, res) {
        var fsid = req.params.fsid;
        var cwdPath = path.join('/', req.params[0]);
        logger.info('exec path = ', cwdPath);
        var cwdUrl = 'wfs://' + fsid + cwdPath;
        var cmdInfo = JSON.parse(req.body.info);
        var sessionID = req.body.sessionID;
        var uid = req.user && req.user.uid;
        if (uid === undefined) {
            return res.sendfail(new ClientError('invalid uid'));
        }

        fsMgr.checkLock(fsid, cwdPath, cmdInfo, function(err) {
            if (err) {
                return res.sendfail(err);
            }
            exec(cwdUrl, cmdInfo, function (err, stdout, stderr, ret) {
                if (err) {
                    return res.sendfail(err);
                }
                fsMgr.updateByExec(cmdInfo,uid, fsid, cwdPath, cwdUrl, sessionID, function() {
                    return res.sendok({stdout: stdout, stderr: stderr, ret:ret});
                });
            });
        });
    }
);
<|endoftext|>