<commit_before>var dotty = require('dotty');
var async = require('async');
var crypto = require('crypto');

var Schemator = function(object){
    var self = this;

    // All sanitizators goes here
    this.sanitizators = {
        pbkdf2 : function(value, config, callback){
            console.log(callback);
            crypto.pbkdf2(value, config[0], config[1], config[2], function(err, buffer){
                callback(err, buffer.toString('hex'));
            });
        },
        remove : function(value, config, callback){
            this.remove();
            callback();
        }
    };

    // All validators goes here
    this.validators = {
        required : function(value, config, callback){
            callback(undefined, value !== undefined ? undefined : config);
        },
        minLength : function(value, config, callback){
            if(!value || !value.toString || value.toString().length < config[0])
                return callback(undefined, config[1]);
            callback();
        },
        maxLength : function(value, config, callback){
            if(!value || !value.toString || value.toString().length > config[0])
                return callback(undefined, config[1]);
            callback();
        },
        email : function(value, config, callback){
            if(!/[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test(value))
                return callback(undefined, config);

            callback();
        },
        regex : function(value, config, callback){
            if(!config[0].test(value))
                return callback(undefined, config[1]);
            callback();
        },
        equalTo : function(value, config, callback){
            if(value !== this.readObject(config[0]))
                return callback(undefined, config[1]);
            callback();
        },
        alphanumeric : function(value, config, callback){
            if(!/^[a-z0-9]+$/i.test(value))
                return callback(undefined, config);
            callback();
        },
        alpha : function(value, config, callback){
            if(!/^[a-zA-Z]+$/.test(value))
                return callback(undefined, config);
            callback();
        },
        numeric : function(value, config, callback){
            if(!/^[0-9]+$/.test(value))
                return callback(undefined, config);
            callback();
        },
        inList : function(value, config, callback){
            if(config[0].indexOf(value) === -1)
                return callback(undefined, config[1]);
            callback();
        },
        notInList : function(value, config, callback){
            if(config[0].indexOf(value) !== -1)
                return callback(undefined, config[1]);
            callback();
        },
        optional : function(value, config, callback){
            if(!value)
                this.clearErrors();
            callback();
        }
    };

    // Here we will store all rules to use later
    this.rules = {
        sanitize : [],
        validate : []
    };

    // Store the configuration
    this.object = object;

    // Generate the rules
    dotty.deepKeys(object).forEach(function(el){
        var method = el[el.length - 1];
        if(method === 'sanitize' || method === 'validate')
            Object.keys(dotty.get(object, el)).forEach(function(key){
                var newOne = el.slice(0, el.length - 1);
                newOne.push(key);
                self.rules[method].push(newOne);
            });
    });
};

Schemator.prototype.run = function(object, callback){
    var self = this;
    var invalid = [];
    var result = {};
    var current = null;
    var context = {
        invalid : function(message, path){
            if(current)
                invalid.push([current.join('.'), message]);
        },
        clearErrors : function(path){
            var from = path ? path : current;
            invalid.forEach(function(el, index){
                if(el[0] === from.join('.'))
                    invalid.splice(index, 1);
            });
        },
        write : function(value, path){
            var to = path ? path : current;
            dotty.put(result, to, value);
        },
        remove : function(path){
            var to = path ? path : current;
            dotty.remove(result, to);
        },
        read : function(path){
            var from = path ? path : current;
            return dotty.get(result, from);
        },
        readObject : function(path){
            var from = path ? path : current;
            return dotty.get(object, from);
        }
    };

    async.series([
        function(callback){
            async.eachSeries(self.rules.validate, function(inspect, callback){
                var rule = inspect.slice(0);
                var func = rule[rule.length - 1];
                current = rule.slice(0, rule.length - 1);
                if(!self.validators[func])
                    return callback(new Error('Unknown validator ' + func));

                rule.splice(rule.length - 1, 0, 'validate');
                self.validators[func].apply(context, [dotty.get(object, current), dotty.get(self.object, rule), function(err, invalid){
                    if(invalid)
                        context.invalid(invalid);
                    else
                        context.write(dotty.get(object, current));

                    callback(err);
                }]);
            }, function(err){
                callback(err);
            });
        },
        function(callback){
            if(invalid.length)
                return callback();

            async.eachSeries(self.rules.sanitize, function(inspect, callback){
                var rule = inspect.slice(0);
                var func = rule[rule.length - 1];
                current = rule.slice(0, rule.length - 1);
                if(!self.sanitizators[func])
                    return callback(new Error('Unknown sanitizator ' + func));

                rule.splice(rule.length - 1, 0, 'sanitize');
                self.sanitizators[func].apply(context, [dotty.get(object, current), dotty.get(self.object, rule), function(err, result){
                    if(result)
                        context.write(result);

                    callback(err);
                }]);
            }, function(err){
                callback(err);
            });
        }
    ], function(err){
        if(err)
            callback(err);
        else if(invalid.length)
            callback(undefined, invalid);
        else
            callback(undefined, undefined, result);
    });
};

module.exports = Schemator;
<commit_msg>pbkdf2 will only try to hash strings<commit_after>var dotty = require('dotty');
var async = require('async');
var crypto = require('crypto');

var Schemator = function(object){
    var self = this;

    // All sanitizators goes here
    this.sanitizators = {
        pbkdf2 : function(value, config, callback){
            if(typeof value === 'string')
                crypto.pbkdf2(value, config[0], config[1], config[2], function(err, buffer){
                    callback(err, buffer.toString('hex'));
                });
            else
                callback();
        },
        remove : function(value, config, callback){
            this.remove();
            callback();
        }
    };

    // All validators goes here
    this.validators = {
        required : function(value, config, callback){
            callback(undefined, value !== undefined ? undefined : config);
        },
        minLength : function(value, config, callback){
            if(!value || !value.toString || value.toString().length < config[0])
                return callback(undefined, config[1]);
            callback();
        },
        maxLength : function(value, config, callback){
            if(!value || !value.toString || value.toString().length > config[0])
                return callback(undefined, config[1]);
            callback();
        },
        email : function(value, config, callback){
            if(!/[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/.test(value))
                return callback(undefined, config);

            callback();
        },
        regex : function(value, config, callback){
            if(!config[0].test(value))
                return callback(undefined, config[1]);
            callback();
        },
        equalTo : function(value, config, callback){
            if(value !== this.readObject(config[0]))
                return callback(undefined, config[1]);
            callback();
        },
        alphanumeric : function(value, config, callback){
            if(!/^[a-z0-9]+$/i.test(value))
                return callback(undefined, config);
            callback();
        },
        alpha : function(value, config, callback){
            if(!/^[a-zA-Z]+$/.test(value))
                return callback(undefined, config);
            callback();
        },
        numeric : function(value, config, callback){
            if(!/^[0-9]+$/.test(value))
                return callback(undefined, config);
            callback();
        },
        inList : function(value, config, callback){
            if(config[0].indexOf(value) === -1)
                return callback(undefined, config[1]);
            callback();
        },
        notInList : function(value, config, callback){
            if(config[0].indexOf(value) !== -1)
                return callback(undefined, config[1]);
            callback();
        },
        optional : function(value, config, callback){
            if(!value)
                this.clearErrors();
            callback();
        }
    };

    // Here we will store all rules to use later
    this.rules = {
        sanitize : [],
        validate : []
    };

    // Store the configuration
    this.object = object;

    // Generate the rules
    dotty.deepKeys(object).forEach(function(el){
        var method = el[el.length - 1];
        if(method === 'sanitize' || method === 'validate')
            Object.keys(dotty.get(object, el)).forEach(function(key){
                var newOne = el.slice(0, el.length - 1);
                newOne.push(key);
                self.rules[method].push(newOne);
            });
    });
};

Schemator.prototype.run = function(object, callback){
    var self = this;
    var invalid = [];
    var result = {};
    var current = null;
    var context = {
        invalid : function(message, path){
            if(current)
                invalid.push([current.join('.'), message]);
        },
        clearErrors : function(path){
            var from = path ? path : current;
            invalid.forEach(function(el, index){
                if(el[0] === from.join('.'))
                    invalid.splice(index, 1);
            });
        },
        write : function(value, path){
            var to = path ? path : current;
            dotty.put(result, to, value);
        },
        remove : function(path){
            var to = path ? path : current;
            dotty.remove(result, to);
        },
        read : function(path){
            var from = path ? path : current;
            return dotty.get(result, from);
        },
        readObject : function(path){
            var from = path ? path : current;
            return dotty.get(object, from);
        }
    };

    async.series([
        function(callback){
            async.eachSeries(self.rules.validate, function(inspect, callback){
                var rule = inspect.slice(0);
                var func = rule[rule.length - 1];
                current = rule.slice(0, rule.length - 1);
                if(!self.validators[func])
                    return callback(new Error('Unknown validator ' + func));

                rule.splice(rule.length - 1, 0, 'validate');
                self.validators[func].apply(context, [dotty.get(object, current), dotty.get(self.object, rule), function(err, invalid){
                    if(invalid)
                        context.invalid(invalid);
                    else
                        context.write(dotty.get(object, current));

                    callback(err);
                }]);
            }, function(err){
                callback(err);
            });
        },
        function(callback){
            if(invalid.length)
                return callback();

            async.eachSeries(self.rules.sanitize, function(inspect, callback){
                var rule = inspect.slice(0);
                var func = rule[rule.length - 1];
                current = rule.slice(0, rule.length - 1);
                if(!self.sanitizators[func])
                    return callback(new Error('Unknown sanitizator ' + func));

                rule.splice(rule.length - 1, 0, 'sanitize');
                self.sanitizators[func].apply(context, [dotty.get(object, current), dotty.get(self.object, rule), function(err, result){
                    if(result)
                        context.write(result);

                    callback(err);
                }]);
            }, function(err){
                callback(err);
            });
        }
    ], function(err){
        if(err)
            callback(err);
        else if(invalid.length)
            callback(undefined, invalid);
        else
            callback(undefined, undefined, result);
    });
};

module.exports = Schemator;
<|endoftext|>


<commit_before>(function (window, document, ajl) {
"use strict";

// ----------------------------------------------------------------------------
// Menu: 開閉メニュー
// ----------------------------------------------------------------------------
ajl.Menu = function (elem, options) {
    this.elem = elem;
    this.firstLevelMenuItems = [];
    this.nodeFirstIds = [];
    this.nodeLastIds = [];
    this.generateId = "AJL_MENU_" + Math.floor(Math.random() * 1000000);
    this.timerId = null;
    this.stack = [];
    this.methodStack = [];
    this.defaults = {
        activeClassName: "active",
        closeWaitTime: 1000,
        direction: "down",
        collect: function (id) {
            var selector = "#" + id + " > li > a, #" +
                            id + " > li > em > a, #" +
                            id + " > li > .js-haschild, #" +
                            id + " > li > em > .js-haschild";
            return document.querySelectorAll(selector);
        }
    };

    this.options = ajl.util.deepExtend({}, this.defaults, options);
};

ajl.Menu.prototype = {
    clearTimer: function (isKeyboardEvent) {
        if (this.timerId) {
            window.clearTimeout(this.timerId);
            this.timerId = null;
        } else if (isKeyboardEvent) {
            window.setTimeout(ajl.util.proxy(this, this.clearTimer, true), this.options.closeWaitTime / 4);
        }
    },

    hideMenu: function () {
        var openMenu = this.stack.shift();

        if (!openMenu) {
            return;
        }

        openMenu.previousElementSibling.classList.remove(this.options.activeClassName);
        openMenu.classList.remove(this.options.activeClassName);
        openMenu.setAttribute("aria-expanded", "false");
        openMenu.setAttribute("aria-hidden", "true");
        this.timerId = null;
    },

    hideMenuHandler: function (e) {
        if (navigator.userAgent.indexOf("Android") > -1 && e.type === "mousedown") {
            e.preventDefault();
            return false;
        }

        if (this.stack.length > 0 && !this.timerId) {
            this.timerId = setTimeout(ajl.util.proxy(this, this.hideMenu), this.options.closeWaitTime);
        }
    },

    showMenu: function (e) {
        var targetMenu,
            openMenu;

        if (navigator.userAgent.indexOf("Android") > -1 &&
            !e.currentTarget.classList.contains(this.options.activeClassName)) {
            e.preventDefault();
        }

        if (e.currentTarget.parentNode.tagName.toLowerCase() === "em") {
            targetMenu = e.currentTarget.parentNode.nextElementSibling;
        } else {
            targetMenu = e.currentTarget.nextElementSibling;
        }

        if (this.stack.length > 0) {
            this.clearTimer();
            openMenu = this.stack.shift();
            openMenu.previousElementSibling.classList.remove(this.options.activeClassName);
            openMenu.classList.remove(this.options.activeClassName);
            openMenu.setAttribute("aria-expanded", "false");
            openMenu.setAttribute("aria-hidden", "true");
        }

        if (targetMenu) {
            e.currentTarget.classList.add(this.options.activeClassName);
            targetMenu.classList.add(this.options.activeClassName);
            targetMenu.setAttribute("aria-expanded", "true");
            targetMenu.setAttribute("aria-hidden", "false");
            this.stack.push(targetMenu);
        }
    },

    focusMenu: function (id) {
        var menuId = this.elem.id.indexOf('AJL') > -1 ? this.generateId : this.elem.id;
        var selector = "#" + menuId + " a[data-item='" + id + "'], " +
                        "#" + menuId + " .js-haschild[data-item='" + id + "']";
        var nextFocusItem = document.querySelector(selector);
        nextFocusItem.focus();
    },

    keydownEventHandler: function (e) {
        var elem,
            menuId = parseInt(e.target.getAttribute("data-item"), 10),
            arrayIndex,
            nextFocusId,
            nextRootKey,
            prevRootKey,
            nextChildKey,
            prevChildKey;

        switch (this.options.direction) {
            case "down":
                nextRootKey = ajl.def.keyCode.RIGHT;
                prevRootKey = ajl.def.keyCode.LEFT;
                nextChildKey = ajl.def.keyCode.DOWN;
                prevChildKey = ajl.def.keyCode.UP;
                break;
        }

        if (e.keyCode === nextChildKey) {
            e.preventDefault();

            if ((e.target.parentNode.tagName.toLowerCase() === "em" && e.target.parentNode.nextElementSibling.tagName.toLowerCase() === "ul") ||
                (e.target.nextElementSibling && e.target.nextElementSibling.tagName.toLowerCase() === "ul")) {
                if (this.stack.length === 0) {
                    this.showMenu(e);
                }

                nextFocusId = menuId + 1;
                this.focusMenu(nextFocusId);
                this.clearTimer(true);
            } else if (e.target.parentNode.nextElementSibling) {
                nextFocusId = menuId + 1;

                if (this.firstLevelMenuItems.indexOf(menuId) > -1 &&
                    this.firstLevelMenuItems.indexOf(nextFocusId) > -1) {
                    return;
                }

                this.focusMenu(nextFocusId);
            }
        } else if (e.keyCode === prevChildKey) {
            e.preventDefault();

            if (e.target.parentNode.previousElementSibling) {
                nextFocusId = menuId - 1;

                if (this.firstLevelMenuItems.indexOf(menuId) > -1 &&
                    this.firstLevelMenuItems.indexOf(nextFocusId) > -1) {
                    return;
                }

                this.focusMenu(nextFocusId);
            } else if (e.target.parentNode.parentNode !== this.elem) {
                nextFocusId = menuId - 1;
                this.focusMenu(nextFocusId);
            }
        } else if (e.keyCode === nextRootKey) {
            if (e.target.getAttribute("role") === "menuitem") {
                arrayIndex = this.firstLevelMenuItems.indexOf(menuId);
                nextFocusId = this.firstLevelMenuItems[arrayIndex + 1];
                if (nextFocusId) {
                    this.hideMenu();
                    this.focusMenu(nextFocusId);
                }
            }
        } else if (e.keyCode === prevRootKey) {
            if (e.target.getAttribute("role") === "menuitem") {
                arrayIndex = this.firstLevelMenuItems.indexOf(menuId);
                nextFocusId = this.firstLevelMenuItems[arrayIndex - 1];
                if (nextFocusId > -1) {
                    this.hideMenu();
                    this.focusMenu(nextFocusId);
                }
            }
        }
    },

    destroy: function () {
        var i,
            j,
            nItems,
            menuItems,
            subMenu,
            subMenuItems,
            nSubMenuItems,
            menuId = 0,
            body = document.getElementsByTagName("body")[0];

        this.elem.classList.remove("ajl-menu-enabled");
        this.elem.removeAttribute("role");
        menuItems = this.options.collect(this.elem.id ? this.elem.id : this.generateId);
        if (this.elem.id.indexOf('AJL') > -1) {
            this.elem.id = "";
        }
        this.stack = [];

        ajl.event.remove(
            body,
            "mousedown",
            this.methodStack.hide,
            false
        );

        for (i = 0, nItems = menuItems.length; i < nItems; i += 1) {
            subMenu = menuItems[i].nextElementSibling;

            // if (i > 0) {
            //     menuItems[i].removeAttribute("tabindex");
            // }

            menuItems[i].parentNode.removeAttribute("role");
            menuItems[i].removeAttribute("role");

            if (menuItems[i].classList.contains("js-haschild")) {
                menuItems[i].removeAttribute("tabindex");
            }

            ajl.event.remove(
                menuItems[i],
                "mouseover",
                this.methodStack.show,
                false
            );
            ajl.event.remove(
                menuItems[i],
                "keydown",
                this.methodStack.keydown,
                false
            );

            if (subMenu) {
                menuItems[i].removeAttribute("aria-haspopup");
                menuItems[i].removeAttribute("aria-controls");
                subMenu.removeAttribute("tabindex");
                subMenu.removeAttribute("role");
                subMenu.removeAttribute("aria-expanded");
                subMenu.removeAttribute("aria-hidden");
                subMenu.classList.remove(this.options.activeClassName);
                if (subMenu.id.indexOf('AJL') > -1) {
                    subMenu.removeAttribute("id");
                }
                ajl.event.remove(
                    menuItems[i],
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.remove(
                    menuItems[i],
                    "keydown",
                    this.methodStack.keydown,
                    false
                );
                ajl.event.remove(
                    subMenu,
                    "mouseover, focus",
                    this.methodStack.timer,
                    false
                );
                ajl.event.remove(
                    subMenu,
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.remove(
                    subMenu,
                    "keydown",
                    this.methodStack.keydown,
                    false
                );

                if (navigator.userAgent.indexOf("Android") > -1) {
                    ajl.event.remove(
                        menuItems[i],
                        "touchstart, touchend",
                        this.methodStack.show,
                        false
                    );
                }

                subMenuItems = subMenu.querySelectorAll("li a");
                for (j = 0, nSubMenuItems = subMenuItems.length; j < nSubMenuItems; j += 1) {
                    subMenuItems[j].removeAttribute("tabindex");
                    subMenuItems[j].parentNode.removeAttribute("role");
                    subMenuItems[j].removeAttribute("role");
                }
            }
        }
    },

    init: function () {
        var i,
            j,
            nItems,
            menuItems,
            subMenu,
            subMenuItems,
            subMenuId,
            nSubMenuItems,
            menuId = 0,
            body = document.getElementsByTagName("body")[0];

        this.elem.classList.add("ajl-menu-enabled");
        this.elem.id = this.elem.id ? this.elem.id : this.generateId;
        this.elem.setAttribute("role", "menubar");
        menuItems = this.options.collect(this.elem.id ? this.elem.id : this.generateId);

        this.methodStack.show = ajl.util.proxy(this, this.showMenu);
        this.methodStack.hide = ajl.util.proxy(this, this.hideMenuHandler);
        this.methodStack.timer = ajl.util.proxy(this, this.clearTimer);
        this.methodStack.keydown = ajl.util.proxy(this, this.keydownEventHandler);

        ajl.event.add(
            body,
            "mousedown",
            this.methodStack.hide,
            false
        );

        for (i = 0, nItems = menuItems.length; i < nItems; i += 1) {
            this.firstLevelMenuItems.push(menuId);
            menuItems[i].parentNode.setAttribute("role", "none");
            menuItems[i].setAttribute("role", "menuitem");
            menuItems[i].setAttribute("data-item", menuId);
            menuId += 1;
            subMenu = menuItems[i].parentNode.tagName.toLowerCase() === "em" ?
                        menuItems[i].parentNode.nextElementSibling :
                        menuItems[i].nextElementSibling;

            // if (i > 0) {
            //     menuItems[i].setAttribute("tabindex", "-1");
            // }

            if (menuItems[i].classList.contains("js-haschild")) {
                menuItems[i].setAttribute("tabindex", 0);
            }

            ajl.event.add(
                menuItems[i],
                "mouseover",
                this.methodStack.show,
                false
            );
            ajl.event.add(
                menuItems[i],
                "keydown",
                this.methodStack.keydown,
                false
            );

            if (subMenu) {
                if (subMenu.id) {
                    menuItems[i].setAttribute("aria-controls", subMenu.id);
                } else {
                    subMenuId = "AJL_MENU_" + Math.floor(Math.random() * 100000);
                    menuItems[i].setAttribute("aria-controls", subMenuId);
                    subMenu.id = subMenuId;
                }

                menuItems[i].setAttribute("aria-haspopup", "true");
                subMenu.parentNode.setAttribute("role", "none");
                subMenu.setAttribute("role", "menu");
                subMenu.setAttribute("tabindex", "-1");
                subMenu.setAttribute("aria-expanded", "false");
                subMenu.setAttribute("aria-hidden", "true");
                ajl.event.add(
                    menuItems[i],
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.add(
                    subMenu,
                    "mouseover, focus",
                    this.methodStack.timer,
                    false
                );
                ajl.event.add(
                    subMenu,
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.add(
                    subMenu,
                    "keydown",
                    this.methodStack.keydown,
                    false
                );

                if (navigator.userAgent.indexOf("Android") > -1) {
                    ajl.event.add(
                        menuItems[i],
                        "touchstart, touchend",
                        this.methodStack.show,
                        false
                    );
                }

                subMenuItems = subMenu.querySelectorAll("li a");
                for (j = 0, nSubMenuItems = subMenuItems.length; j < nSubMenuItems; j += 1) {
                    subMenuItems[j].setAttribute("tabindex", "-1");
                    subMenuItems[j].setAttribute("role", "menuitem");
                    subMenuItems[j].setAttribute("data-item", menuId);
                    menuId += 1;
                }
            }
        }
    }
};

ajl.util.createRunner("Menu");

}(window, document, ajl));
<commit_msg>Menuにおけるキーボード操作性を改善<commit_after>(function (window, document, ajl) {
"use strict";

// ----------------------------------------------------------------------------
// Menu: 開閉メニュー
// ----------------------------------------------------------------------------
ajl.Menu = function (elem, options) {
    this.elem = elem;
    this.firstLevelMenuItems = [];
    this.nodeFirstIds = [];
    this.nodeLastIds = [];
    this.generateId = "AJL_MENU_" + Math.floor(Math.random() * 1000000);
    this.timerId = null;
    this.stack = [];
    this.methodStack = [];
    this.defaults = {
        activeClassName: "active",
        closeWaitTime: 1000,
        direction: "down",
        collect: function (id) {
            var selector = "#" + id + " > li > a, #" +
                            id + " > li > em > a, #" +
                            id + " > li > .js-haschild, #" +
                            id + " > li > em > .js-haschild";
            return document.querySelectorAll(selector);
        }
    };

    this.options = ajl.util.deepExtend({}, this.defaults, options);
};

ajl.Menu.prototype = {
    clearTimer: function (isKeyboardEvent) {
        if (this.timerId) {
            window.clearTimeout(this.timerId);
            this.timerId = null;
        } else if (isKeyboardEvent) {
            window.setTimeout(ajl.util.proxy(this, this.clearTimer, true), this.options.closeWaitTime / 4);
        }
    },

    hideMenu: function () {
        var openMenu = this.stack.shift();

        if (!openMenu) {
            return;
        }

        openMenu.previousElementSibling.classList.remove(this.options.activeClassName);
        openMenu.classList.remove(this.options.activeClassName);
        openMenu.setAttribute("aria-expanded", "false");
        openMenu.setAttribute("aria-hidden", "true");
        this.timerId = null;
    },

    hideMenuHandler: function (e) {
        if (navigator.userAgent.indexOf("Android") > -1 && e.type === "mousedown") {
            e.preventDefault();
            return false;
        }

        if (this.stack.length > 0 && !this.timerId) {
            this.timerId = setTimeout(ajl.util.proxy(this, this.hideMenu), this.options.closeWaitTime);
        }
    },

    showMenu: function (e) {
        var targetMenu,
            openMenu;

        if (navigator.userAgent.indexOf("Android") > -1 &&
            !e.currentTarget.classList.contains(this.options.activeClassName)) {
            e.preventDefault();
        }

        if (e.currentTarget.parentNode.tagName.toLowerCase() === "em") {
            targetMenu = e.currentTarget.parentNode.nextElementSibling;
        } else {
            targetMenu = e.currentTarget.nextElementSibling;
        }

        if (this.stack.length > 0) {
            this.clearTimer();
            openMenu = this.stack.shift();
            openMenu.previousElementSibling.classList.remove(this.options.activeClassName);
            openMenu.classList.remove(this.options.activeClassName);
            openMenu.setAttribute("aria-expanded", "false");
            openMenu.setAttribute("aria-hidden", "true");
        }

        if (targetMenu) {
            e.currentTarget.classList.add(this.options.activeClassName);
            targetMenu.classList.add(this.options.activeClassName);
            targetMenu.setAttribute("aria-expanded", "true");
            targetMenu.setAttribute("aria-hidden", "false");
            this.stack.push(targetMenu);
        }
    },

    focusMenu: function (id) {
        var menuId = this.elem.id.indexOf('AJL') > -1 ? this.generateId : this.elem.id;
        var selector = "#" + menuId + " a[data-item='" + id + "'], " +
                        "#" + menuId + " .js-haschild[data-item='" + id + "']";
        var nextFocusItem = document.querySelector(selector);
        nextFocusItem.focus();
    },

    keydownEventHandler: function (e) {
        var elem,
            menuId = parseInt(e.target.getAttribute("data-item"), 10),
            arrayIndex,
            nextFocusId,
            nextRootKey,
            prevRootKey,
            nextChildKey,
            prevChildKey;

        switch (this.options.direction) {
            case "down":
                nextRootKey = ajl.def.keyCode.RIGHT;
                prevRootKey = ajl.def.keyCode.LEFT;
                nextChildKey = ajl.def.keyCode.DOWN;
                prevChildKey = ajl.def.keyCode.UP;
                break;
        }

        if (e.keyCode === nextChildKey) {
            e.preventDefault();

            if ((e.target.parentNode.tagName.toLowerCase() === "em" && e.target.parentNode.nextElementSibling.tagName.toLowerCase() === "ul") ||
                (e.target.nextElementSibling && e.target.nextElementSibling.tagName.toLowerCase() === "ul")) {
                if (this.stack.length === 0) {
                    this.showMenu(e);
                }

                nextFocusId = menuId + 1;
                this.focusMenu(nextFocusId);
                this.clearTimer(true);
            } else if (e.target.parentNode.nextElementSibling) {
                nextFocusId = menuId + 1;

                if (this.firstLevelMenuItems.indexOf(menuId) > -1 &&
                    this.firstLevelMenuItems.indexOf(nextFocusId) > -1) {
                    return;
                }

                this.focusMenu(nextFocusId);
            }
        } else if (e.keyCode === prevChildKey) {
            e.preventDefault();

            if (e.target.parentNode.previousElementSibling) {
                nextFocusId = menuId - 1;

                if (this.firstLevelMenuItems.indexOf(menuId) > -1 &&
                    this.firstLevelMenuItems.indexOf(nextFocusId) > -1) {
                    return;
                }

                this.focusMenu(nextFocusId);
            } else if (e.target.parentNode.parentNode !== this.elem) {
                nextFocusId = menuId - 1;
                this.focusMenu(nextFocusId);
            }
        } else if (e.keyCode === nextRootKey) {
            if (e.target.getAttribute("role") === "menuitem") {
                const parentId = parseInt(e.target.dataset.parent, 10) || menuId;
                arrayIndex = this.firstLevelMenuItems.indexOf(parentId);
                nextFocusId = this.firstLevelMenuItems[arrayIndex + 1];
                if (nextFocusId) {
                    this.hideMenu();
                    this.focusMenu(nextFocusId);
                }
            }
        } else if (e.keyCode === prevRootKey) {
            if (e.target.getAttribute("role") === "menuitem") {
                const parentId = parseInt(e.target.dataset.parent, 10) || menuId;
                arrayIndex = this.firstLevelMenuItems.indexOf(parentId);
                nextFocusId = this.firstLevelMenuItems[arrayIndex - 1];
                if (nextFocusId > -1) {
                    this.hideMenu();
                    this.focusMenu(nextFocusId);
                }
            }
        }
    },

    destroy: function () {
        var i,
            j,
            nItems,
            menuItems,
            subMenu,
            subMenuItems,
            nSubMenuItems,
            menuId = 0,
            body = document.getElementsByTagName("body")[0];

        this.elem.classList.remove("ajl-menu-enabled");
        this.elem.removeAttribute("role");
        menuItems = this.options.collect(this.elem.id ? this.elem.id : this.generateId);
        if (this.elem.id.indexOf('AJL') > -1) {
            this.elem.id = "";
        }
        this.stack = [];

        ajl.event.remove(
            body,
            "mousedown",
            this.methodStack.hide,
            false
        );

        for (i = 0, nItems = menuItems.length; i < nItems; i += 1) {
            subMenu = menuItems[i].nextElementSibling;

            // if (i > 0) {
            //     menuItems[i].removeAttribute("tabindex");
            // }

            menuItems[i].parentNode.removeAttribute("role");
            menuItems[i].removeAttribute("role");

            if (menuItems[i].classList.contains("js-haschild")) {
                menuItems[i].removeAttribute("tabindex");
            }

            ajl.event.remove(
                menuItems[i],
                "mouseover",
                this.methodStack.show,
                false
            );
            ajl.event.remove(
                menuItems[i],
                "keydown",
                this.methodStack.keydown,
                false
            );

            if (subMenu) {
                menuItems[i].removeAttribute("aria-haspopup");
                menuItems[i].removeAttribute("aria-controls");
                subMenu.removeAttribute("tabindex");
                subMenu.removeAttribute("role");
                subMenu.removeAttribute("aria-expanded");
                subMenu.removeAttribute("aria-hidden");
                subMenu.classList.remove(this.options.activeClassName);
                if (subMenu.id.indexOf('AJL') > -1) {
                    subMenu.removeAttribute("id");
                }
                ajl.event.remove(
                    menuItems[i],
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.remove(
                    menuItems[i],
                    "keydown",
                    this.methodStack.keydown,
                    false
                );
                ajl.event.remove(
                    subMenu,
                    "mouseover, focus",
                    this.methodStack.timer,
                    false
                );
                ajl.event.remove(
                    subMenu,
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.remove(
                    subMenu,
                    "keydown",
                    this.methodStack.keydown,
                    false
                );

                if (navigator.userAgent.indexOf("Android") > -1) {
                    ajl.event.remove(
                        menuItems[i],
                        "touchstart, touchend",
                        this.methodStack.show,
                        false
                    );
                }

                subMenuItems = subMenu.querySelectorAll("li a");
                for (j = 0, nSubMenuItems = subMenuItems.length; j < nSubMenuItems; j += 1) {
                    subMenuItems[j].removeAttribute("tabindex");
                    subMenuItems[j].parentNode.removeAttribute("role");
                    subMenuItems[j].removeAttribute("role");
                    ajl.event.remove(
                        subMenuItems[j],
                        "keydown",
                        this.methodStack.keydown,
                        false
                    );
                }
            }
        }
    },

    init: function () {
        var i,
            j,
            nItems,
            menuItems,
            subMenu,
            subMenuItems,
            subMenuId,
            nSubMenuItems,
            menuId = 0,
            body = document.getElementsByTagName("body")[0];

        this.elem.classList.add("ajl-menu-enabled");
        this.elem.id = this.elem.id ? this.elem.id : this.generateId;
        this.elem.setAttribute("role", "menubar");
        menuItems = this.options.collect(this.elem.id ? this.elem.id : this.generateId);

        this.methodStack.show = ajl.util.proxy(this, this.showMenu);
        this.methodStack.hide = ajl.util.proxy(this, this.hideMenuHandler);
        this.methodStack.timer = ajl.util.proxy(this, this.clearTimer);
        this.methodStack.keydown = ajl.util.proxy(this, this.keydownEventHandler);

        ajl.event.add(
            body,
            "mousedown",
            this.methodStack.hide,
            false
        );

        for (i = 0, nItems = menuItems.length; i < nItems; i += 1) {
            this.firstLevelMenuItems.push(menuId);
            menuItems[i].parentNode.setAttribute("role", "none");
            menuItems[i].setAttribute("role", "menuitem");
            menuItems[i].setAttribute("data-item", menuId);
            subMenu = menuItems[i].parentNode.tagName.toLowerCase() === "em" ?
                        menuItems[i].parentNode.nextElementSibling :
                        menuItems[i].nextElementSibling;

            // if (i > 0) {
            //     menuItems[i].setAttribute("tabindex", "-1");
            // }

            if (menuItems[i].classList.contains("js-haschild")) {
                menuItems[i].setAttribute("tabindex", 0);
            }

            ajl.event.add(
                menuItems[i],
                "mouseover",
                this.methodStack.show,
                false
            );
            ajl.event.add(
                menuItems[i],
                "keydown",
                this.methodStack.keydown,
                false
            );

            if (subMenu) {
                if (subMenu.id) {
                    menuItems[i].setAttribute("aria-controls", subMenu.id);
                } else {
                    subMenuId = "AJL_MENU_" + Math.floor(Math.random() * 100000);
                    menuItems[i].setAttribute("aria-controls", subMenuId);
                    subMenu.id = subMenuId;
                }

                menuItems[i].setAttribute("aria-haspopup", "true");
                subMenu.parentNode.setAttribute("role", "none");
                subMenu.setAttribute("role", "menu");
                subMenu.setAttribute("tabindex", "-1");
                subMenu.setAttribute("aria-expanded", "false");
                subMenu.setAttribute("aria-hidden", "true");
                ajl.event.add(
                    menuItems[i],
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.add(
                    subMenu,
                    "mouseover, focus",
                    this.methodStack.timer,
                    false
                );
                ajl.event.add(
                    subMenu,
                    "mouseout, blur",
                    this.methodStack.hide,
                    false
                );
                ajl.event.add(
                    subMenu,
                    "keydown",
                    this.methodStack.keydown,
                    false
                );

                if (navigator.userAgent.indexOf("Android") > -1) {
                    ajl.event.add(
                        menuItems[i],
                        "touchstart, touchend",
                        this.methodStack.show,
                        false
                    );
                }

                subMenuItems = subMenu.querySelectorAll("li a");
                const parentId = menuId;
                for (j = 0, nSubMenuItems = subMenuItems.length; j < nSubMenuItems; j += 1) {
                    menuId += 1;
                    subMenuItems[j].setAttribute("tabindex", "-1");
                    subMenuItems[j].setAttribute("role", "menuitem");
                    subMenuItems[j].setAttribute("data-item", menuId);
                    subMenuItems[j].setAttribute("data-parent", parentId);
                    ajl.event.add(
                        subMenuItems[j],
                        "keydown",
                        this.methodStack.keydown,
                        false
                    );
                }
            }
            menuId += 1;
        }
    }
};

ajl.util.createRunner("Menu");

}(window, document, ajl));
<|endoftext|>


<commit_before>'use strict';

(function () {

  // Stored options
  var vendorPrefixes = {},
    obfuscate = true,
    minify = false,
    optionsSet = false;
  var existingClassNames = [];
  var existingAnimationNames = [];
  var isMediaQuery = /^@media/;
  var whitespace = /[\n\r\s]/g;
  var indent = '  ';
  var messagePrefix = 'ReactStyleSheets: ';
  var headTag = document.getElementsByTagName('head')[0];
  var styleTag;

  var commaSeparatedProperties = [
    'fontFamily'
  ];

  var noSuffixProperties = [
    'opacity',
    'animationIterationCount'
  ];

  var timeSuffixProperties = [
    'transition',
    'transitionDuration',
    'transitionDelay',
    'animation',
    'animationDuration',
    'animationDelay'
  ];

  var doubleColonSelectors = [
    'before',
    'after',
    'firstLetter',
    'firstLine',
    'selection'
  ];

  var nestedSelectors = [
    'active',
    'after',
    'before',
    'checked',
    'disabled',
    'empty',
    'enabled',
    'firstChild',
    'firstLetter',
    'firstLine',
    'firstOfType',
    'focus',
    'hover',
    'inRange',
    'invalid',
    // 'lang', // Takes arguments
    'lastChild',
    'lastOfType',
    'link',
    // 'not', // Takes arguments
    // 'nthChild', // Takes arguments
    // 'nthLastChild', // Takes arguments
    // 'nthLastOfType', // Takes arguments
    // 'nthOfType', // Takes arguments
    'onlyOfType',
    'onlyChild',
    'optional',
    'outOfRange',
    'readOnly',
    'readWrite',
    'required',
    'root',
    'selection',
    'target',
    'valid',
    'visited'
  ];

  function warn (message) {
    /* istanbul ignore else */
    if (typeof console !== 'undefined' && typeof console.warn === 'function') {
      console.warn(messagePrefix + message);
    }
  }

  function error (message) {
    throw new Error(messagePrefix + message);
  }

  if (!headTag) {
    error('Could not locate head tag. Ensure your javascript is included after the closing head tag.');
  }

  styleTag = document.createElement('style');
  styleTag.setAttribute('type', 'text/css');
  headTag.appendChild(styleTag);

  function toSpinalCase (value) {
    return value.replace(/(^|[a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  function formatValue (key, value) {
    if (Array.isArray(value)) {
      var separator = commaSeparatedProperties.indexOf(key) >= 0 ? ', ' : ' ';
      return value.map(formatValue.bind(null, key)).join(separator);
    } else if (typeof value === 'number') {
      if (value === 0 || noSuffixProperties.indexOf(key) >= 0) {
        return value;
      }

      if (timeSuffixProperties.indexOf(key) >= 0) {
        return value + 'ms';
      }

      return value + 'px';
    }

    return value;
  }

  function createStyles (tag, style) {
    var prefix = '\n' + tag + ' {\n';
    var nested = '';

    if (isMediaQuery.test(tag)) {
      error('Media queries must be nested inside your tag / class.');
    }

    for (var key in style) {
      if (!isMediaQuery.test(key) && key.indexOf('-') >= 0) {
        error('Found "-" in a style property name. Style property names must be defined in camelcase.');
      }

      var value = style[key];

      if (key in vendorPrefixes) {
        // Vendor prefixes
        for (var i = 0; i < vendorPrefixes[key].length; i += 1) {
          var vendorKey = '-' + vendorPrefixes[key][i] + '-' + key;
          prefix += indent + toSpinalCase(vendorKey) + ': ' + formatValue(key, value) + ';\n';
        }

        prefix += indent + toSpinalCase(key) + ': ' + formatValue(key, value) + ';\n';
      } else if (nestedSelectors.indexOf(key) >= 0) {
        // Nested styles
        if (typeof value !== 'object' || Array.isArray(value)) {
          error('Nested selectors such as hover, active, before, firstChild, etc, must be an object.');
        }

        var colon = doubleColonSelectors.indexOf(key) >= 0 ? '::' : ':';
        nested += createStyles(tag + colon + toSpinalCase(key), value);
      } else if (isMediaQuery.test(key)) {
        // Media queries
        if (typeof value !== 'object' || Array.isArray(value)) {
          error('Media queries must be an object.');
        }

        nested += '\n' + key + ' {\n';
        nested += createStyles(tag, value);
        nested += '\n}\n';
      } else if (typeof value === 'object' && !Array.isArray(value)) {
        // Sub styles e.g. {margin: {left, right}}
        for (var subKey in value) {
          var keySubKey = key + '-' + subKey;
          prefix += indent + toSpinalCase(keySubKey) + ': ' + formatValue(keySubKey, value[subKey]) + ';\n';
        }
      } else {
        // Basic values & arrays
        prefix += indent + toSpinalCase(key) + ': ' + formatValue(key, value) + ';\n';
      }
    }

    return prefix + '}\n' + nested;
  }

  function generateRandomString (length) {
    var concat = '';

    for (var i = 0; i < length; i += 1) {
      concat += String.fromCharCode(97 + Math.floor(Math.random() * 26));
    }

    return concat;
  }

  function generateUniqueName (name, existingNames, type) {
    if (obfuscate) {
      var obfuscatedName = name + '_' + generateRandomString(5);

      while (existingNames.indexOf(obfuscatedName) >= 0) {
        /* istanbul ignore next */
        obfuscatedName = name + '_' + generateRandomString(5);
      }

      existingNames.push(obfuscatedName);
      return obfuscatedName;
    }

    if (existingNames.indexOf(name) >= 0) {
      error('The ' + type + ' name "' + name + '" is already in use, please choose another.');
    }

    existingNames.push(name);
    return name;
  }

  function removeWhitespace (value) {
    return minify ? value.replace(whitespace, '') : value;
  }

  var ReactStyleSheets = {
    setOptions: function (options) {
      if (optionsSet) {
        warn('You tried to set options more than once. This is not advised.');
      }

      if (typeof options !== 'object') {
        error('Options must be an object.');
      }

      if (typeof options.vendorPrefixes !== 'undefined') {
        if (typeof options.vendorPrefixes !== 'object' || Array.isArray(options.vendorPrefixes)) {
          error('vendorPrefixes must be an object with style property names as keys, ' +
            'and arrays of prefixes as values.');
        } else {
          vendorPrefixes = options.vendorPrefixes;
        }
      }

      if (typeof options.obfuscate !== 'undefined') {
        obfuscate = options.obfuscate;
      }

      if (typeof options.minify !== 'undefined') {
        minify = options.minify;
      }

      optionsSet = true;
    },
    _createGlobalTagStyles: function (styles) {
      var concat = '';

      for (var tag in styles) {
        concat += createStyles(tag, styles[tag]);
      }

      return {
        keys: null,
        values: removeWhitespace(concat)
      };
    },
    createGlobalTagStyles: function (styles) {
      var generated = ReactStyleSheets._createGlobalTagStyles(styles);

      styleTag.innerHTML = styleTag.innerHTML + generated.values;
    },
    _createUniqueClassStyles: function (styles) {
      var classNames = {};
      var concat = '';

      for (var className in styles) {
        var obfuscatedClassName = generateUniqueName(className, existingClassNames, 'class');

        concat += createStyles('.' + obfuscatedClassName, styles[className]);

        classNames[className] = obfuscatedClassName;
      }

      return {
        keys: classNames,
        values: removeWhitespace(concat)
      };
    },
    createUniqueClassStyles: function (styles) {
      var generated = ReactStyleSheets._createUniqueClassStyles(styles);

      styleTag.innerHTML = styleTag.innerHTML + generated.values;

      return generated.keys;
    },
    _createUniqueKeyframeAnimation: function (styles) {
      var animations = {};
      var concat = '';

      for (var animationName in styles) {
        var obfuscatedAnimationName = generateUniqueName(animationName, existingAnimationNames, 'keyframe animation');

        concat += '\n@keyframes ' + obfuscatedAnimationName + ' {\n';
        var generated = ReactStyleSheets._createGlobalTagStyles(styles[animationName]);
        concat += generated.values;
        concat += '\n}\n';

        animations[animationName] = obfuscatedAnimationName;
      }

      return {
        keys: animations,
        values: removeWhitespace(concat)
      };
    },
    createUniqueKeyframeAnimation: function (styles) {
      var generated = ReactStyleSheets._createUniqueKeyframeAnimation(styles);

      styleTag.innerHTML = styleTag.innerHTML + generated.values;

      return generated.keys;
    }
  };

  /* istanbul ignore else */

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = ReactStyleSheets;
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define([], function () { // eslint-disable-line no-undef
      return ReactStyleSheets;
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global;
    } else if (typeof self !== 'undefined') {
      root = self;
    } else {
      root = this;
    }

    root.ReactStyleSheets = ReactStyleSheets;
  }

})();
<commit_msg>Reorder functions<commit_after>'use strict';

(function () {

  // Stored options
  var vendorPrefixes = {},
    obfuscate = true,
    minify = false,
    optionsSet = false;
  var existingClassNames = [];
  var existingAnimationNames = [];
  var isMediaQuery = /^@media/;
  var whitespace = /[\n\r\s]/g;
  var indent = '  ';
  var messagePrefix = 'ReactStyleSheets: ';
  var headTag = document.getElementsByTagName('head')[0];
  var styleTag;

  var commaSeparatedProperties = [
    'fontFamily'
  ];

  var noSuffixProperties = [
    'opacity',
    'animationIterationCount'
  ];

  var timeSuffixProperties = [
    'transition',
    'transitionDuration',
    'transitionDelay',
    'animation',
    'animationDuration',
    'animationDelay'
  ];

  var doubleColonSelectors = [
    'before',
    'after',
    'firstLetter',
    'firstLine',
    'selection'
  ];

  var nestedSelectors = [
    'active',
    'after',
    'before',
    'checked',
    'disabled',
    'empty',
    'enabled',
    'firstChild',
    'firstLetter',
    'firstLine',
    'firstOfType',
    'focus',
    'hover',
    'inRange',
    'invalid',
    // 'lang', // Takes arguments
    'lastChild',
    'lastOfType',
    'link',
    // 'not', // Takes arguments
    // 'nthChild', // Takes arguments
    // 'nthLastChild', // Takes arguments
    // 'nthLastOfType', // Takes arguments
    // 'nthOfType', // Takes arguments
    'onlyOfType',
    'onlyChild',
    'optional',
    'outOfRange',
    'readOnly',
    'readWrite',
    'required',
    'root',
    'selection',
    'target',
    'valid',
    'visited'
  ];

  function warn (message) {
    /* istanbul ignore else */
    if (typeof console !== 'undefined' && typeof console.warn === 'function') {
      console.warn(messagePrefix + message);
    }
  }

  function error (message) {
    throw new Error(messagePrefix + message);
  }

  if (!headTag) {
    error('Could not locate head tag. Ensure your javascript is included after the closing head tag.');
  }

  styleTag = document.createElement('style');
  styleTag.setAttribute('type', 'text/css');
  headTag.appendChild(styleTag);

  function toSpinalCase (value) {
    return value.replace(/(^|[a-z])([A-Z])/g, '$1-$2').toLowerCase();
  }

  function removeWhitespace (value) {
    return minify ? value.replace(whitespace, '') : value;
  }

  function generateRandomString (length) {
    var concat = '';

    for (var i = 0; i < length; i += 1) {
      concat += String.fromCharCode(97 + Math.floor(Math.random() * 26));
    }

    return concat;
  }

  function generateUniqueName (name, existingNames, type) {
    if (obfuscate) {
      var obfuscatedName = name + '_' + generateRandomString(5);

      while (existingNames.indexOf(obfuscatedName) >= 0) {
        /* istanbul ignore next */
        obfuscatedName = name + '_' + generateRandomString(5);
      }

      existingNames.push(obfuscatedName);
      return obfuscatedName;
    }

    if (existingNames.indexOf(name) >= 0) {
      error('The ' + type + ' name "' + name + '" is already in use, please choose another.');
    }

    existingNames.push(name);
    return name;
  }

  function formatValue (key, value) {
    if (Array.isArray(value)) {
      var separator = commaSeparatedProperties.indexOf(key) >= 0 ? ', ' : ' ';
      return value.map(formatValue.bind(null, key)).join(separator);
    } else if (typeof value === 'number') {
      if (value === 0 || noSuffixProperties.indexOf(key) >= 0) {
        return value;
      }

      if (timeSuffixProperties.indexOf(key) >= 0) {
        return value + 'ms';
      }

      return value + 'px';
    }

    return value;
  }

  function createStyles (tag, style) {
    var prefix = '\n' + tag + ' {\n';
    var nested = '';

    if (isMediaQuery.test(tag)) {
      error('Media queries must be nested inside your tag / class.');
    }

    for (var key in style) {
      if (!isMediaQuery.test(key) && key.indexOf('-') >= 0) {
        error('Found "-" in a style property name. Style property names must be defined in camelcase.');
      }

      var value = style[key];

      if (key in vendorPrefixes) {
        // Vendor prefixes
        for (var i = 0; i < vendorPrefixes[key].length; i += 1) {
          var vendorKey = '-' + vendorPrefixes[key][i] + '-' + key;
          prefix += indent + toSpinalCase(vendorKey) + ': ' + formatValue(key, value) + ';\n';
        }

        prefix += indent + toSpinalCase(key) + ': ' + formatValue(key, value) + ';\n';
      } else if (nestedSelectors.indexOf(key) >= 0) {
        // Nested styles
        if (typeof value !== 'object' || Array.isArray(value)) {
          error('Nested selectors such as hover, active, before, firstChild, etc, must be an object.');
        }

        var colon = doubleColonSelectors.indexOf(key) >= 0 ? '::' : ':';
        nested += createStyles(tag + colon + toSpinalCase(key), value);
      } else if (isMediaQuery.test(key)) {
        // Media queries
        if (typeof value !== 'object' || Array.isArray(value)) {
          error('Media queries must be an object.');
        }

        nested += '\n' + key + ' {\n';
        nested += createStyles(tag, value);
        nested += '\n}\n';
      } else if (typeof value === 'object' && !Array.isArray(value)) {
        // Sub styles e.g. {margin: {left, right}}
        for (var subKey in value) {
          var keySubKey = key + '-' + subKey;
          prefix += indent + toSpinalCase(keySubKey) + ': ' + formatValue(keySubKey, value[subKey]) + ';\n';
        }
      } else {
        // Basic values & arrays
        prefix += indent + toSpinalCase(key) + ': ' + formatValue(key, value) + ';\n';
      }
    }

    return prefix + '}\n' + nested;
  }

  var ReactStyleSheets = {
    setOptions: function (options) {
      if (optionsSet) {
        warn('You tried to set options more than once. This is not advised.');
      }

      if (typeof options !== 'object') {
        error('Options must be an object.');
      }

      if (typeof options.vendorPrefixes !== 'undefined') {
        if (typeof options.vendorPrefixes !== 'object' || Array.isArray(options.vendorPrefixes)) {
          error('vendorPrefixes must be an object with style property names as keys, ' +
            'and arrays of prefixes as values.');
        } else {
          vendorPrefixes = options.vendorPrefixes;
        }
      }

      if (typeof options.obfuscate !== 'undefined') {
        obfuscate = options.obfuscate;
      }

      if (typeof options.minify !== 'undefined') {
        minify = options.minify;
      }

      optionsSet = true;
    },
    _createGlobalTagStyles: function (styles) {
      var concat = '';

      for (var tag in styles) {
        concat += createStyles(tag, styles[tag]);
      }

      return {
        keys: null,
        values: removeWhitespace(concat)
      };
    },
    createGlobalTagStyles: function (styles) {
      var generated = ReactStyleSheets._createGlobalTagStyles(styles);

      styleTag.innerHTML = styleTag.innerHTML + generated.values;
    },
    _createUniqueClassStyles: function (styles) {
      var classNames = {};
      var concat = '';

      for (var className in styles) {
        var obfuscatedClassName = generateUniqueName(className, existingClassNames, 'class');

        concat += createStyles('.' + obfuscatedClassName, styles[className]);

        classNames[className] = obfuscatedClassName;
      }

      return {
        keys: classNames,
        values: removeWhitespace(concat)
      };
    },
    createUniqueClassStyles: function (styles) {
      var generated = ReactStyleSheets._createUniqueClassStyles(styles);

      styleTag.innerHTML = styleTag.innerHTML + generated.values;

      return generated.keys;
    },
    _createUniqueKeyframeAnimation: function (styles) {
      var animations = {};
      var concat = '';

      for (var animationName in styles) {
        var obfuscatedAnimationName = generateUniqueName(animationName, existingAnimationNames, 'keyframe animation');

        concat += '\n@keyframes ' + obfuscatedAnimationName + ' {\n';
        var generated = ReactStyleSheets._createGlobalTagStyles(styles[animationName]);
        concat += generated.values;
        concat += '\n}\n';

        animations[animationName] = obfuscatedAnimationName;
      }

      return {
        keys: animations,
        values: removeWhitespace(concat)
      };
    },
    createUniqueKeyframeAnimation: function (styles) {
      var generated = ReactStyleSheets._createUniqueKeyframeAnimation(styles);

      styleTag.innerHTML = styleTag.innerHTML + generated.values;

      return generated.keys;
    }
  };

  /* istanbul ignore else */

  // Export for commonjs / browserify
  if (typeof exports === 'object' && typeof module !== 'undefined') {
    module.exports = ReactStyleSheets;
  // Export for amd / require
  } else if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
    define([], function () { // eslint-disable-line no-undef
      return ReactStyleSheets;
    });
  // Export globally
  } else {
    var root;

    if (typeof window !== 'undefined') {
      root = window;
    } else if (typeof global !== 'undefined') {
      root = global;
    } else if (typeof self !== 'undefined') {
      root = self;
    } else {
      root = this;
    }

    root.ReactStyleSheets = ReactStyleSheets;
  }

})();
<|endoftext|>


<commit_before>import Express from 'express';
import { User } from '../db/index.js';
import authRequired from './lib/authRequired.js';
import adminRequired from './lib/adminRequired.js';
import upload from './lib/upload.js';
import gm from 'gm';
import fs from 'fs';

function checkModifiable(req, res, next) {
  if (req.selUser !== req.user) {
    adminRequired(req, res, next);
    return;
  }
  next();
}

export const userRouter = new Express.Router();

userRouter.get('/', (req, res) => {
  res.json(req.selUser);
});

userRouter.get('/scripts', (req, res) => {
  // Respond with dummy data
  res.json([
    {
      id: 142857,
      name: 'How to build a website for dummies',
      votes: 314,
      tags: [
        {
          id: 314159,
          name: 'linux',
          type: {
            id: 1004,
            name: 'os'
          }
        }
      ]
    }
  ]);
});

userRouter.get('/collections', (req, res) => {
  res.sendStatus(501);
});

/**
 * @api {post} /user/ Update the profile
 * @apiGroup User
 * @apiName SetUserProfile
 * @apiParam (Body) {String} [email] The email address.
 * @apiDescription Updates and returns the user profile.
 *
 *   This will fail if user hasn't signed in.
 *
 * @apiSuccessExample {json} If user has signed in:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If user hasn't signed in:
 *   HTTP/1.1 401 Unauthorized
 *   {
 *     "id": "AUTH_NOT_SIGNED_IN",
 *     "message": "Not signed in"
 *   }
 * @apiErrorExample {json} If email conflicts:
 *   HTTP/1.1 409 Conflict
 *   {
 *     "id": "AUTH_EMAIL_EXISTS",
 *     "message": "Email address is already in use."
 *   }
 */
userRouter.post('/', checkModifiable, (req, res) => {
  res.sendStatus(501);
});

/**
 * @api {put} /user/photo Update the profile photo
 * @apiGroup User
 * @apiName SetUserProfile photo
 * @apiParam (Body) {File} photo The photo to set.
 * @apiParam (Body) {Integer} x The X position.
 * @apiParam (Body) {Integer} y The Y position.
 * @apiParam (Body) {Integer} size The size of profile photo.
 * @apiDescription Updates and returns the user profile.
 *
 *   This will fail if user hasn't signed in.
 *
 *   If the photo, X, Y, or size is invalid, This will return 400 Bad Request.
 *
 * @apiSuccessExample {json} If updating the profile was successful:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If user hasn't signed in:
 *   HTTP/1.1 401 Unauthorized
 *   {
 *     "id": "AUTH_NOT_SIGNED_IN",
 *     "message": "Not signed in"
 *   }
 * @apiErrorExample {json} If photo is invalid:
 *   HTTP/1.1 400
 *   {
 *     "id": "PROFILE_IMAGE_INVALID",
 *     "message": "This is not a valid image file."
 *   }
 * @apiErrorExample {json} If dimensions are invalid:
 *   HTTP/1.1 400
 *   {
 *     "id": "PROFILE_IMAGE_DIMENSION_INVALID",
 *     "message": "Please provide valid image dimensions."
 *   }
 */
userRouter.post('/photo', checkModifiable, upload.single('photo'),
(req, res) => {
  const { file } = req;
  new Promise((resolve, reject) => {
    console.log(req.file);
    const finishedFile = `uploads/user_${req.selUser.id}_photo.png`;
    let { x, y, size } = req.body;
    // Convert x, y, size to integer.
    x = parseInt(x, 10);
    y = parseInt(y, 10);
    size = parseInt(size, 10);
    // First, we check if the file is an image.
    if (file == null) return reject('no image');
    if (!file.mimetype.startsWith('image/')) return reject('not image');
    // Check the dimensions...
    if (isNaN(x) || isNaN(y) || isNaN(size)) return reject('size invalid');
    if (x < 0 || y < 0 || size <= 0) return reject('size invalid');
    // Crop / resize the image.
    gm(file.path)
    .autoOrient()
    .crop(size, size, x, y)
    .resize(256, 256, '!')
    .noProfile()
    .write(finishedFile, (err) => {
      if (err) return reject(err);
      resolve(finishedFile);
    });
  })
  .then((path) => {
    // Upload the file to static server, etc
    // Set the profile image for now
    req.selUser.photo = `/${path}`;
    return req.selUser.save()
    .then(() => res.json(req.selUser));
  }, (err) => {
    res.status(400);
    res.json(err);
  })
  .then(() => {
    // Delete the uploaded file.
    if (file && file.path) fs.unlink(file.path);
  })
  .catch(err => {
    res.status(500);
    res.json(err);
  });
});

userRouter.delete('/', checkModifiable, (req, res) => {
  res.sendStatus(501);
});

export const router = new Express.Router();
export default router;

/**
 * @api {get} /users/ List users matching the criteria
 * @apiGroup User
 * @apiName SearchUsers
 * @apiParam (Query) {String} [username] The username to search.
 * @apiParam (Query) {String} [email] The email address to search.
 * @apiParam (Query) {Integer} [lastIndex] The last user id you've seen
 * @apiDescription Searches users matching the criteria, or lists all
 *   users if no criteria was given.
 *
 *   Username is searched using 'LIKE' query. If you need an exact match,
 *   You should use /users/:username instead.
 *
 *   Pagination is done with 'lastIndex'. Send last user index you've seen
 *   and this will send users starting from there.
 */
router.get('/users/', (req, res) => {
  let { username, email, lastIndex } = req.query;
  const where = {};
  if (username != null) {
    where.login = {
      $like: username.toLowerCase()
    };
  }
  if (email != null) {
    where.email = email;
  }
  lastIndex = parseInt(lastIndex);
  if (!isNaN(lastIndex)) {
    where.id = {
      $lt: lastIndex
    };
  }
  User.findAll({
    where,
    // TODO currently it's hardcoded. should be changed
    limit: 20,
    order: [
      ['username', 'ASC']
    ]
  })
  .then(users => {
    res.json(users);
  });
});

/**
 * @api {get} /users/:username Get a user with the username
 * @apiGroup User
 * @apiName GetUser
 * @apiParam (Parameter) {String} username The username.
 * @apiDescription Returns the user object.
 *
 *   This will return 404 Not Found if the user is not found.
 *
 * @apiSuccessExample {json} If the user is found:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If the user is not found:
 *   HTTP/1.1 404 Not Found
 *   {
 *     "id": "USER_NOT_FOUND",
 *     "message": "User with given username is not found."
 *   }
 */
router.use('/users/:username', (req, res, next) => {
  // Retrieve the user
  const { username } = req.params;
  if (username == null) {
    // This can't happen
    res.sendStatus(500);
    return;
  }
  User.findOne({
    where: { login: username.toLowerCase() }
  })
  .then(user => {
    if (user == null) {
      // Not found
      res.status(404);
      res.json({
        id: 'USER_NOT_FOUND',
        message: 'User with given username is not found.'
      });
      return;
    }
    req.selUser = user;
    next();
  });
}, userRouter);

/**
 * @api {get} /user/ Get current user
 * @apiGroup User
 * @apiName GetCurrentUser
 * @apiDescription Returns current user object.
 *
 *   This will fail if user hasn't signed in.
 *
 * @apiSuccessExample {json} If user has signed in:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If user hasn't signed in:
 *   HTTP/1.1 401 Unauthorized
 *   {
 *     "id": "AUTH_NOT_SIGNED_IN",
 *     "message": "Not signed in"
 *   }
 */
router.use('/user/', authRequired, (req, res, next) => {
  req.selUser = req.user;
  next();
}, userRouter);
<commit_msg>Fix user API sorting by username<commit_after>import Express from 'express';
import { User } from '../db/index.js';
import authRequired from './lib/authRequired.js';
import adminRequired from './lib/adminRequired.js';
import upload from './lib/upload.js';
import gm from 'gm';
import fs from 'fs';

function checkModifiable(req, res, next) {
  if (req.selUser !== req.user) {
    adminRequired(req, res, next);
    return;
  }
  next();
}

export const userRouter = new Express.Router();

userRouter.get('/', (req, res) => {
  res.json(req.selUser);
});

userRouter.get('/scripts', (req, res) => {
  // Respond with dummy data
  res.json([
    {
      id: 142857,
      name: 'How to build a website for dummies',
      votes: 314,
      tags: [
        {
          id: 314159,
          name: 'linux',
          type: {
            id: 1004,
            name: 'os'
          }
        }
      ]
    }
  ]);
});

userRouter.get('/collections', (req, res) => {
  res.sendStatus(501);
});

/**
 * @api {post} /user/ Update the profile
 * @apiGroup User
 * @apiName SetUserProfile
 * @apiParam (Body) {String} [email] The email address.
 * @apiDescription Updates and returns the user profile.
 *
 *   This will fail if user hasn't signed in.
 *
 * @apiSuccessExample {json} If user has signed in:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If user hasn't signed in:
 *   HTTP/1.1 401 Unauthorized
 *   {
 *     "id": "AUTH_NOT_SIGNED_IN",
 *     "message": "Not signed in"
 *   }
 * @apiErrorExample {json} If email conflicts:
 *   HTTP/1.1 409 Conflict
 *   {
 *     "id": "AUTH_EMAIL_EXISTS",
 *     "message": "Email address is already in use."
 *   }
 */
userRouter.post('/', checkModifiable, (req, res) => {
  res.sendStatus(501);
});

/**
 * @api {put} /user/photo Update the profile photo
 * @apiGroup User
 * @apiName SetUserProfile photo
 * @apiParam (Body) {File} photo The photo to set.
 * @apiParam (Body) {Integer} x The X position.
 * @apiParam (Body) {Integer} y The Y position.
 * @apiParam (Body) {Integer} size The size of profile photo.
 * @apiDescription Updates and returns the user profile.
 *
 *   This will fail if user hasn't signed in.
 *
 *   If the photo, X, Y, or size is invalid, This will return 400 Bad Request.
 *
 * @apiSuccessExample {json} If updating the profile was successful:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If user hasn't signed in:
 *   HTTP/1.1 401 Unauthorized
 *   {
 *     "id": "AUTH_NOT_SIGNED_IN",
 *     "message": "Not signed in"
 *   }
 * @apiErrorExample {json} If photo is invalid:
 *   HTTP/1.1 400
 *   {
 *     "id": "PROFILE_IMAGE_INVALID",
 *     "message": "This is not a valid image file."
 *   }
 * @apiErrorExample {json} If dimensions are invalid:
 *   HTTP/1.1 400
 *   {
 *     "id": "PROFILE_IMAGE_DIMENSION_INVALID",
 *     "message": "Please provide valid image dimensions."
 *   }
 */
userRouter.post('/photo', checkModifiable, upload.single('photo'),
(req, res) => {
  const { file } = req;
  new Promise((resolve, reject) => {
    console.log(req.file);
    const finishedFile = `uploads/user_${req.selUser.id}_photo.png`;
    let { x, y, size } = req.body;
    // Convert x, y, size to integer.
    x = parseInt(x, 10);
    y = parseInt(y, 10);
    size = parseInt(size, 10);
    // First, we check if the file is an image.
    if (file == null) return reject('no image');
    if (!file.mimetype.startsWith('image/')) return reject('not image');
    // Check the dimensions...
    if (isNaN(x) || isNaN(y) || isNaN(size)) return reject('size invalid');
    if (x < 0 || y < 0 || size <= 0) return reject('size invalid');
    // Crop / resize the image.
    gm(file.path)
    .autoOrient()
    .crop(size, size, x, y)
    .resize(256, 256, '!')
    .noProfile()
    .write(finishedFile, (err) => {
      if (err) return reject(err);
      resolve(finishedFile);
    });
  })
  .then((path) => {
    // Upload the file to static server, etc
    // Set the profile image for now
    req.selUser.photo = `/${path}`;
    return req.selUser.save()
    .then(() => res.json(req.selUser));
  }, (err) => {
    res.status(400);
    res.json(err);
  })
  .then(() => {
    // Delete the uploaded file.
    if (file && file.path) fs.unlink(file.path);
  })
  .catch(err => {
    res.status(500);
    res.json(err);
  });
});

userRouter.delete('/', checkModifiable, (req, res) => {
  res.sendStatus(501);
});

export const router = new Express.Router();
export default router;

/**
 * @api {get} /users/ List users matching the criteria
 * @apiGroup User
 * @apiName SearchUsers
 * @apiParam (Query) {String} [username] The username to search.
 * @apiParam (Query) {String} [email] The email address to search.
 * @apiParam (Query) {Integer} [lastIndex] The last user id you've seen
 * @apiDescription Searches users matching the criteria, or lists all
 *   users if no criteria was given.
 *
 *   Username is searched using 'LIKE' query. If you need an exact match,
 *   You should use /users/:username instead.
 *
 *   Pagination is done with 'lastIndex'. Send last user index you've seen
 *   and this will send users starting from there.
 */
router.get('/users/', (req, res) => {
  let { username, email, lastIndex } = req.query;
  const where = {};
  if (username != null) {
    where.login = {
      $like: username.toLowerCase()
    };
  }
  if (email != null) {
    where.email = email;
  }
  lastIndex = parseInt(lastIndex);
  if (!isNaN(lastIndex)) {
    where.id = {
      $lt: lastIndex
    };
  }
  User.findAll({
    where,
    // TODO currently it's hardcoded. should be changed
    limit: 20,
    order: [
      ['id', 'DESC']
    ]
  })
  .then(users => {
    res.json(users);
  });
});

/**
 * @api {get} /users/:username Get a user with the username
 * @apiGroup User
 * @apiName GetUser
 * @apiParam (Parameter) {String} username The username.
 * @apiDescription Returns the user object.
 *
 *   This will return 404 Not Found if the user is not found.
 *
 * @apiSuccessExample {json} If the user is found:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If the user is not found:
 *   HTTP/1.1 404 Not Found
 *   {
 *     "id": "USER_NOT_FOUND",
 *     "message": "User with given username is not found."
 *   }
 */
router.use('/users/:username', (req, res, next) => {
  // Retrieve the user
  const { username } = req.params;
  if (username == null) {
    // This can't happen
    res.sendStatus(500);
    return;
  }
  User.findOne({
    where: { login: username.toLowerCase() }
  })
  .then(user => {
    if (user == null) {
      // Not found
      res.status(404);
      res.json({
        id: 'USER_NOT_FOUND',
        message: 'User with given username is not found.'
      });
      return;
    }
    req.selUser = user;
    next();
  });
}, userRouter);

/**
 * @api {get} /user/ Get current user
 * @apiGroup User
 * @apiName GetCurrentUser
 * @apiDescription Returns current user object.
 *
 *   This will fail if user hasn't signed in.
 *
 * @apiSuccessExample {json} If user has signed in:
 *   HTTP/1.1 200 OK
 *   {
 *     "username": "Username",
 *     "email": "Email"
 *   }
 * @apiErrorExample {json} If user hasn't signed in:
 *   HTTP/1.1 401 Unauthorized
 *   {
 *     "id": "AUTH_NOT_SIGNED_IN",
 *     "message": "Not signed in"
 *   }
 */
router.use('/user/', authRequired, (req, res, next) => {
  req.selUser = req.user;
  next();
}, userRouter);
<|endoftext|>


<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2015, Joyent, Inc.
 */

var assert = require('assert-plus');
var cp = require('child_process');
var execFile = cp.execFile;
var spawn = cp.spawn;

var VMADM_PATH = '/usr/sbin/vmadm';

function vmadm() {}

/*
 * execVmadm options:
 *
 *  args (required)      -- the cmdline args for vmadm [array]
 *  log (required)       -- a bunyan logger
 *  req_id (recommended) -- a req_id to pass to vmadm
 *  vmadmLogger          -- an object with a .write() method that takes a single
 *                          object as its argument. vmadm evt logs go here.
 *
 * Calls callback with
 *
 *   ({code: <code>, signal: <signal>}, stdout [string], stderr [array])
 *
 * on vmadm exit.
 *
 */
function execVmadm(opts, callback) {
    assert.object(opts, 'opts');
    assert.object(opts.log, 'opts.log');
    assert.arrayOfString(opts.args, 'opts.args');

    var args = opts.args;
    var child;
    var execOpts = {env: {}};
    var stderrBuffer = '';
    var stderrLines = [];
    var stdoutBuffer = '';

    if (opts.req_id) {
        opts.log.info('setting req_id to %s', opts.req_id);
        execOpts.env.REQ_ID = opts.req_id;
    }
    execOpts.env.VMADM_DEBUG_LEVEL = 'debug';
    if (process.env.EXPERIMENTAL_VMJS_TRACING) {
        execOpts.env.EXPERIMENTAL_VMJS_TRACING =
            process.env.EXPERIMENTAL_VMJS_TRACING;
    }

    opts.log.info({ args: args, execOpts: execOpts }, 'executing vmadm');

    child = spawn(VMADM_PATH, args, execOpts);

    child.stdout.on('data', function _childStdoutHandler(data) {
        stdoutBuffer += data.toString();
    });

    child.stderr.on('data', function _childStderrHandler(data) {
        var chunk;
        var chunks;
        var logobj;

        stderrBuffer += data.toString();
        chunks = stderrBuffer.split('\n');
        while (chunks.length > 1) {
            chunk = chunks.shift();

            // log the record to the vmadmLogger if it's an evt log.
            logobj = undefined;
            if (opts.vmadmLogger) {
                if (chunk[0] === '{' && chunk.indexOf('"evt"') !== -1) {
                    try {
                        logobj = JSON.parse(chunk);
                    } catch (e) {
                        opts.log.warn({line: chunk, err: e},
                            'failed to JSON.parse line');
                    }
                }
            }
            if (logobj) {
                opts.vmadmLogger.write(logobj);
            }

            stderrLines.push(chunk);
        }
        stderrBuffer = chunks.pop();
    });

    child.on('close', function _childCloseHandler(code, signal) {
        if (stderrBuffer.length > 0) {
            opts.log.info('stderr from vmadm: ' + stderrBuffer);
            stderrLines.push(stderrBuffer);
        }
        opts.log.info('vmadm child closed with (%d, %d)', code, signal);
        callback({code: code, signal: signal}, stdoutBuffer, stderrLines);
    });

    if (opts.stdinData) {
        child.stdin.write(opts.stdinData);
    }
    child.stdin.end();
}

/**
 * Call `vmadm lookup -j UUID`.
 *
 * @param opts {Object} Options
 *      - uuid {String} The VM uuid.
 *      - log {Logger object}
 * @param vmopts {Object} Optional vm options
 *      - fields {Array} Return only the keys give in `fields` array
 * @param callback {Function} `function (err)`
 */

vmadm.load = function vmLoad(opts, vmopts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');

    if (!callback) {
        callback = vmopts;
    }

    function addargs(newargs) {
        Array.prototype.push.apply(args, newargs);
    }

    var args = ['lookup', '-1', '-j'];

    if (vmopts && vmopts.fields) {
        addargs(['-o', opts.fields.join(',')]);
    }

    args.push('uuid=' + opts.uuid);

    var execOpts = {
        args: args,
        log: opts.log,
        req_id: opts.req_id
    };

    opts.log.info('spawning vmadm for load');

    execVmadm(execOpts, function (result, stdout, stderr) {
        var err;
        var vm;
        var vms;

        if (result.code !== 0) {
            err = new Error('vmadm exited with code: %d signal: %d',
                result.code, result.signal);
            err.stderr = stderr;
            return callback(err);
        }

        try {
            vms = JSON.parse(stdout);
        } catch (jsonErr) {
            jsonErr.stdout = stdout;
            return callback(jsonErr);
        }

        if (vms.length) {
            vm = vms[0];
        }

        return callback(null, vm);
    });
};

/**
 * Call `vmadm create`.
 *
 * @param opts {Object} Options
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.create = function vmCreate(opts, callback) {
    assert.object(opts, 'opts');
    assert.object(opts.log, 'opts.log');

    var execOpts = {};
    var log = opts.log;
    delete opts.log;

    log.info('spawning vmadm for create');

    execOpts.log = log;
    execOpts.args = ['create'];
    execOpts.req_id = opts.req_id;
    delete opts.req_id;

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    execOpts.stdinData = JSON.stringify(opts);

    execVmadm(execOpts, function (result, stdout, stderr) {
        if (result.code !== 0) {
            callback(new Error(stderr.join('\n').trim()));
            return;
        }
        callback();
    });
};

/**
 * Call `vmadm delete <uuid>`.
 *
 * @param opts {Object} Options
 *      - uuid {String} UUID of VM to delete
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.delete = function vmDelete(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var args = ['delete', opts.uuid];

    var execOpts = {
        args: args,
        log: opts.log,
        req_id: opts.req_id
    };

    opts.log.info('spawning vmadm for delete');

    execVmadm(execOpts, function (result, stdout, stderr) {
        var err;

        if (result.code !== 0) {
            err = new Error('vmadm exited with code: %d signal: %d',
                result.code, result.signal);
            err.stderr = stderr;
            return callback(err);
        }
        return callback();
    });
};

/**
 * Call `vmadm update`.
 *
 * @param opts {Object} VMADM update payload
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.update = function vmUpdate(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var log = opts.log;
    delete opts.log;
    log.info('spawning vmadm for machine update');

    var vmadmproc = spawn(VMADM_PATH, ['update', opts.uuid]);
    var stderr = '';

    vmadmproc.stdin.write(JSON.stringify(opts));
    vmadmproc.stdin.end();

    vmadmproc.on('close', function (code) {
        if (code) {
            callback(new Error(stderr.trim()));
            return;
        }
        callback();
    });

    vmadmproc.stderr.on('data', function (data) {
        stderr += data.toString();
    });
};

/**
 * Call `vmadm reboot <uuid>`.
 *
 * @param opts {Object} Options
 *      - uuid {String} UUID of VM to reboot
 *      - force {Boolean} Whether to force the reboot.
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.reboot = function vmReboot(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var args = ['reboot', opts.uuid];

    if (opts.force) {
        args.push('-F');
    }

    var execopts = {
        args: args,
        log: opts.log
    };

    execVmadm(execopts, function (result, stdout, stderr) {
        var err;

        if (result.code !== 0) {
            err = new Error('vmadm exited with code: %d signal: %d',
                result.code, result.signal);
            err.stderr = stderr;
            return callback(err);
        }
        return callback();
    });
};

/**
 * Call `vmadm reprovision`.
 *
 * @param opts {Object} VMADM reprovision payload
 *      - log {Logger object}
 *      - uuid {String} uuid of vm to be reprovisioned
 * @param callback {Function} `function (err)`
 */

vmadm.reprovision = function vmReprovision(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var log = opts.log;
    delete opts.log;
    log.info('spawning vmadm for machine update');

    var vmadmproc = spawn(VMADM_PATH, ['reprovision', opts.uuid]);
    var stderr = '';

    vmadmproc.stdin.write(JSON.stringify(opts));
    vmadmproc.stdin.end();

    vmadmproc.on('close', function (code) {
        if (code) {
            callback(new Error(stderr.trim()));
            return;
        }
        callback();
    });

    vmadmproc.stderr.on('data', function (data) {
        stderr += data.toString();
    });
};

module.exports = vmadm;
<commit_msg>allow vmadmLogger from more functions<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/*
 * Copyright (c) 2015, Joyent, Inc.
 */

var assert = require('assert-plus');
var cp = require('child_process');
var execFile = cp.execFile;
var spawn = cp.spawn;

var VMADM_PATH = '/usr/sbin/vmadm';

function vmadm() {}

/*
 * Execute vmadm
 *
 * @param opts {Object} Options
 *      - args {Array} cmdline args for vmadm
 *      - log {Logger object}
 *      - req_id {String} request id for this request (to tie to callers)
 *      - vmadmLogger {Object} with a .write() method that takes a single object
 *        as its argument. vmadm evt logs go here.
 * @param callback {Function} `function (result, stdout, stderrLines)`
 *      - called on vmadm exit
 *      - result is an Object that contains code: ..., signal: ...
 *      - stdout is a string of all data written to stdout
 *      - stderrLines is an array of all the lines written to stderr
 *
 */
function execVmadm(opts, callback) {
    assert.object(opts, 'opts');
    assert.object(opts.log, 'opts.log');
    assert.arrayOfString(opts.args, 'opts.args');

    var args = opts.args;
    var child;
    var execOpts = {env: {}};
    var stderrBuffer = '';
    var stderrLines = [];
    var stdoutBuffer = '';

    if (opts.req_id) {
        opts.log.info('setting req_id to %s', opts.req_id);
        execOpts.env.REQ_ID = opts.req_id;
    }
    execOpts.env.VMADM_DEBUG_LEVEL = 'debug';
    if (process.env.EXPERIMENTAL_VMJS_TRACING) {
        execOpts.env.EXPERIMENTAL_VMJS_TRACING =
            process.env.EXPERIMENTAL_VMJS_TRACING;
    }

    opts.log.info({ args: args, execOpts: execOpts }, 'executing vmadm');

    child = spawn(VMADM_PATH, args, execOpts);

    child.stdout.on('data', function _childStdoutHandler(data) {
        stdoutBuffer += data.toString();
    });

    child.stderr.on('data', function _childStderrHandler(data) {
        var chunk;
        var chunks;
        var logobj;

        stderrBuffer += data.toString();
        chunks = stderrBuffer.split('\n');
        while (chunks.length > 1) {
            chunk = chunks.shift();

            // log the record to the vmadmLogger if it's an evt log.
            logobj = undefined;
            if (opts.vmadmLogger) {
                if (chunk[0] === '{' && chunk.indexOf('"evt"') !== -1) {
                    try {
                        logobj = JSON.parse(chunk);
                    } catch (e) {
                        opts.log.warn({line: chunk, err: e},
                            'failed to JSON.parse line');
                    }
                }
            }
            if (logobj) {
                opts.vmadmLogger.write(logobj);
            }

            stderrLines.push(chunk);
        }
        stderrBuffer = chunks.pop();
    });

    child.on('close', function _childCloseHandler(code, signal) {
        if (stderrBuffer.length > 0) {
            opts.log.info('stderr from vmadm: ' + stderrBuffer);
            stderrLines.push(stderrBuffer);
        }
        opts.log.info('vmadm child closed with (%d, %d)', code, signal);
        callback({code: code, signal: signal}, stdoutBuffer, stderrLines);
    });

    if (opts.stdinData) {
        child.stdin.write(opts.stdinData);
    }
    child.stdin.end();
}

/**
 * Call `vmadm lookup -j UUID`.
 *
 * @param opts {Object} Options
 *      - uuid {String} The VM uuid.
 *      - log {Logger object}
 * @param vmopts {Object} Optional vm options
 *      - fields {Array} Return only the keys give in `fields` array
 * @param callback {Function} `function (err)`
 */

vmadm.load = function vmLoad(opts, vmopts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');

    if (!callback) {
        callback = vmopts;
    }

    function addargs(newargs) {
        Array.prototype.push.apply(args, newargs);
    }

    var args = ['lookup', '-1', '-j'];

    if (vmopts && vmopts.fields) {
        addargs(['-o', opts.fields.join(',')]);
    }

    args.push('uuid=' + opts.uuid);

    var execOpts = {
        args: args,
        log: opts.log,
        req_id: opts.req_id
    };

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    opts.log.info('spawning vmadm for load');

    execVmadm(execOpts, function (result, stdout, stderr) {
        var err;
        var vm;
        var vms;

        if (result.code !== 0) {
            err = new Error('vmadm exited with code: %d signal: %d',
                result.code, result.signal);
            err.stderr = stderr;
            return callback(err);
        }

        try {
            vms = JSON.parse(stdout);
        } catch (jsonErr) {
            jsonErr.stdout = stdout;
            return callback(jsonErr);
        }

        if (vms.length) {
            vm = vms[0];
        }

        return callback(null, vm);
    });
};

/**
 * Call `vmadm create`.
 *
 * @param opts {Object} Options
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.create = function vmCreate(opts, callback) {
    assert.object(opts, 'opts');
    assert.object(opts.log, 'opts.log');

    var execOpts = {};
    var log = opts.log;
    delete opts.log;

    log.info('spawning vmadm for create');

    execOpts.log = log;
    execOpts.args = ['create'];
    execOpts.req_id = opts.req_id;
    delete opts.req_id;

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    execOpts.stdinData = JSON.stringify(opts);

    execVmadm(execOpts, function (result, stdout, stderr) {
        if (result.code !== 0) {
            callback(new Error(stderr.join('\n').trim()));
            return;
        }
        callback();
    });
};

/**
 * Call `vmadm delete <uuid>`.
 *
 * @param opts {Object} Options
 *      - uuid {String} UUID of VM to delete
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.delete = function vmDelete(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var args = ['delete', opts.uuid];

    var execOpts = {
        args: args,
        log: opts.log,
        req_id: opts.req_id
    };

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    opts.log.info('spawning vmadm for delete');

    execVmadm(execOpts, function (result, stdout, stderr) {
        var err;

        if (result.code !== 0) {
            err = new Error('vmadm exited with code: %d signal: %d',
                result.code, result.signal);
            err.stderr = stderr;
            return callback(err);
        }
        return callback();
    });
};

/**
 * Call `vmadm update`.
 *
 * @param opts {Object} VMADM update payload
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.update = function vmUpdate(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var execOpts = {};
    var log = opts.log;
    delete opts.log;

    log.info('spawning vmadm for machine update');

    execOpts.log = log;
    execOpts.args = ['update', opts.uuid];
    execOpts.req_id = opts.req_id;
    delete opts.req_id;

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    execOpts.stdinData = JSON.stringify(opts);

    execVmadm(execOpts, function (result, stdout, stderr) {
        if (result.code !== 0) {
            callback(new Error(stderr.join('\n').trim()));
            return;
        }
        callback();
    });
};

/**
 * Call `vmadm reboot <uuid>`.
 *
 * @param opts {Object} Options
 *      - uuid {String} UUID of VM to reboot
 *      - force {Boolean} Whether to force the reboot.
 *      - log {Logger object}
 * @param callback {Function} `function (err)`
 */

vmadm.reboot = function vmReboot(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var args = ['reboot', opts.uuid];

    if (opts.force) {
        args.push('-F');
    }

    var execOpts = {
        args: args,
        log: opts.log,
        req_id: opts.req_id
    };

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    execVmadm(execOpts, function (result, stdout, stderr) {
        var err;

        if (result.code !== 0) {
            err = new Error('vmadm exited with code: %d signal: %d',
                result.code, result.signal);
            err.stderr = stderr;
            return callback(err);
        }
        return callback();
    });
};

/**
 * Call `vmadm reprovision`.
 *
 * @param opts {Object} VMADM reprovision payload
 *      - log {Logger object}
 *      - uuid {String} uuid of vm to be reprovisioned
 * @param callback {Function} `function (err)`
 */

vmadm.reprovision = function vmReprovision(opts, callback) {
    assert.object(opts, 'opts');
    assert.string(opts.uuid, 'opts.uuid');
    assert.object(opts.log, 'opts.log');

    var execOpts = {};
    var log = opts.log;
    delete opts.log;

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    execOpts.log = log;
    execOpts.args = ['reprovision', opts.uuid];
    execOpts.req_id = opts.req_id;
    delete opts.req_id;

    if (opts.vmadmLogger) {
        execOpts.vmadmLogger = opts.vmadmLogger;
        delete opts.vmadmLogger;
    }

    execOpts.stdinData = JSON.stringify(opts);

    execVmadm(execOpts, function (result, stdout, stderr) {
        if (result.code !== 0) {
            callback(new Error(stderr.join('\n').trim()));
            return;
        }
        callback();
    });
};

module.exports = vmadm;
<|endoftext|>